summaryrefslogtreecommitdiffstats
path: root/contrib/subversion
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/subversion')
-rw-r--r--contrib/subversion/BUGS2
-rw-r--r--contrib/subversion/CHANGES4623
-rw-r--r--contrib/subversion/COMMITTERS234
-rw-r--r--contrib/subversion/INSTALL1466
-rw-r--r--contrib/subversion/LICENSE270
-rw-r--r--contrib/subversion/Makefile.in907
-rw-r--r--contrib/subversion/NOTICE23
-rw-r--r--contrib/subversion/README84
-rw-r--r--contrib/subversion/aclocal.m455
-rwxr-xr-xcontrib/subversion/autogen.sh210
-rw-r--r--contrib/subversion/build-outputs.mk2894
-rw-r--r--contrib/subversion/build.conf1377
-rwxr-xr-xcontrib/subversion/configure27324
-rw-r--r--contrib/subversion/configure.ac1521
-rw-r--r--contrib/subversion/doc/README28
-rw-r--r--contrib/subversion/doc/doxygen.conf1522
-rw-r--r--contrib/subversion/doc/programmer/WritingChangeLogs.txt220
-rw-r--r--contrib/subversion/doc/user/cvs-crossover-guide.html906
-rw-r--r--contrib/subversion/doc/user/lj_article.txt323
-rw-r--r--contrib/subversion/doc/user/svn-best-practices.html350
-rw-r--r--contrib/subversion/gen-make.opts2
-rwxr-xr-xcontrib/subversion/gen-make.py326
-rwxr-xr-xcontrib/subversion/get-deps.sh173
-rw-r--r--contrib/subversion/subversion/include/mod_authz_svn.h61
-rw-r--r--contrib/subversion/subversion/include/mod_dav_svn.h99
-rw-r--r--contrib/subversion/subversion/include/private/README4
-rw-r--r--contrib/subversion/subversion/include/private/ra_svn_sasl.h86
-rw-r--r--contrib/subversion/subversion/include/private/svn_adler32.h52
-rw-r--r--contrib/subversion/subversion/include/private/svn_atomic.h123
-rw-r--r--contrib/subversion/subversion/include/private/svn_auth_private.h220
-rw-r--r--contrib/subversion/subversion/include/private/svn_cache.h486
-rw-r--r--contrib/subversion/subversion/include/private/svn_client_private.h299
-rw-r--r--contrib/subversion/subversion/include/private/svn_cmdline_private.h228
-rw-r--r--contrib/subversion/subversion/include/private/svn_dav_protocol.h68
-rw-r--r--contrib/subversion/subversion/include/private/svn_debug.h107
-rw-r--r--contrib/subversion/subversion/include/private/svn_delta_private.h128
-rw-r--r--contrib/subversion/subversion/include/private/svn_dep_compat.h184
-rw-r--r--contrib/subversion/subversion/include/private/svn_diff_private.h115
-rw-r--r--contrib/subversion/subversion/include/private/svn_diff_tree.h357
-rw-r--r--contrib/subversion/subversion/include/private/svn_doxygen.h32
-rw-r--r--contrib/subversion/subversion/include/private/svn_editor.h1194
-rw-r--r--contrib/subversion/subversion/include/private/svn_eol_private.h93
-rw-r--r--contrib/subversion/subversion/include/private/svn_error_private.h54
-rw-r--r--contrib/subversion/subversion/include/private/svn_fs_private.h189
-rw-r--r--contrib/subversion/subversion/include/private/svn_fs_util.h217
-rw-r--r--contrib/subversion/subversion/include/private/svn_fspath.h175
-rw-r--r--contrib/subversion/subversion/include/private/svn_io_private.h99
-rw-r--r--contrib/subversion/subversion/include/private/svn_log.h260
-rw-r--r--contrib/subversion/subversion/include/private/svn_magic.h55
-rw-r--r--contrib/subversion/subversion/include/private/svn_mergeinfo_private.h270
-rw-r--r--contrib/subversion/subversion/include/private/svn_mutex.h117
-rw-r--r--contrib/subversion/subversion/include/private/svn_named_atomic.h162
-rw-r--r--contrib/subversion/subversion/include/private/svn_opt_private.h156
-rw-r--r--contrib/subversion/subversion/include/private/svn_pseudo_md5.h83
-rw-r--r--contrib/subversion/subversion/include/private/svn_ra_private.h280
-rw-r--r--contrib/subversion/subversion/include/private/svn_ra_svn_private.h826
-rw-r--r--contrib/subversion/subversion/include/private/svn_repos_private.h121
-rw-r--r--contrib/subversion/subversion/include/private/svn_skel.h236
-rw-r--r--contrib/subversion/subversion/include/private/svn_sqlite.h519
-rw-r--r--contrib/subversion/subversion/include/private/svn_string_private.h222
-rw-r--r--contrib/subversion/subversion/include/private/svn_subr_private.h340
-rw-r--r--contrib/subversion/subversion/include/private/svn_temp_serializer.h207
-rw-r--r--contrib/subversion/subversion/include/private/svn_token.h98
-rw-r--r--contrib/subversion/subversion/include/private/svn_utf_private.h87
-rw-r--r--contrib/subversion/subversion/include/private/svn_wc_private.h1847
-rw-r--r--contrib/subversion/subversion/include/svn_auth.h1282
-rw-r--r--contrib/subversion/subversion/include/svn_base64.h123
-rw-r--r--contrib/subversion/subversion/include/svn_cache_config.h90
-rw-r--r--contrib/subversion/subversion/include/svn_checksum.h278
-rw-r--r--contrib/subversion/subversion/include/svn_client.h6475
-rw-r--r--contrib/subversion/subversion/include/svn_cmdline.h376
-rw-r--r--contrib/subversion/subversion/include/svn_compat.h104
-rw-r--r--contrib/subversion/subversion/include/svn_config.h808
-rw-r--r--contrib/subversion/subversion/include/svn_ctype.h196
-rw-r--r--contrib/subversion/subversion/include/svn_dav.h398
-rw-r--r--contrib/subversion/subversion/include/svn_delta.h1367
-rw-r--r--contrib/subversion/subversion/include/svn_diff.h1118
-rw-r--r--contrib/subversion/subversion/include/svn_dirent_uri.h805
-rw-r--r--contrib/subversion/subversion/include/svn_dso.h99
-rw-r--r--contrib/subversion/subversion/include/svn_error.h662
-rw-r--r--contrib/subversion/subversion/include/svn_error_codes.h1521
-rw-r--r--contrib/subversion/subversion/include/svn_fs.h2530
-rw-r--r--contrib/subversion/subversion/include/svn_hash.h265
-rw-r--r--contrib/subversion/subversion/include/svn_io.h2282
-rw-r--r--contrib/subversion/subversion/include/svn_iter.h139
-rw-r--r--contrib/subversion/subversion/include/svn_md5.h91
-rw-r--r--contrib/subversion/subversion/include/svn_mergeinfo.h612
-rw-r--r--contrib/subversion/subversion/include/svn_nls.h56
-rw-r--r--contrib/subversion/subversion/include/svn_opt.h779
-rw-r--r--contrib/subversion/subversion/include/svn_path.h734
-rw-r--r--contrib/subversion/subversion/include/svn_pools.h114
-rw-r--r--contrib/subversion/subversion/include/svn_props.h714
-rw-r--r--contrib/subversion/subversion/include/svn_quoprint.h77
-rw-r--r--contrib/subversion/subversion/include/svn_ra.h2468
-rw-r--r--contrib/subversion/subversion/include/svn_ra_svn.h668
-rw-r--r--contrib/subversion/subversion/include/svn_repos.h3406
-rw-r--r--contrib/subversion/subversion/include/svn_sorts.h223
-rw-r--r--contrib/subversion/subversion/include/svn_string.h577
-rw-r--r--contrib/subversion/subversion/include/svn_subst.h708
-rw-r--r--contrib/subversion/subversion/include/svn_time.h94
-rw-r--r--contrib/subversion/subversion/include/svn_types.h1287
-rw-r--r--contrib/subversion/subversion/include/svn_user.h56
-rw-r--r--contrib/subversion/subversion/include/svn_utf.h252
-rw-r--r--contrib/subversion/subversion/include/svn_version.h411
-rw-r--r--contrib/subversion/subversion/include/svn_wc.h8182
-rw-r--r--contrib/subversion/subversion/include/svn_xml.h381
-rw-r--r--contrib/subversion/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c517
-rw-r--r--contrib/subversion/subversion/libsvn_auth_gnome_keyring/version.c35
-rw-r--r--contrib/subversion/subversion/libsvn_auth_kwallet/kwallet.cpp458
-rw-r--r--contrib/subversion/subversion/libsvn_auth_kwallet/version.c35
-rw-r--r--contrib/subversion/subversion/libsvn_client/add.c1326
-rw-r--r--contrib/subversion/subversion/libsvn_client/blame.c837
-rw-r--r--contrib/subversion/subversion/libsvn_client/cat.c308
-rw-r--r--contrib/subversion/subversion/libsvn_client/changelist.c144
-rw-r--r--contrib/subversion/subversion/libsvn_client/checkout.c198
-rw-r--r--contrib/subversion/subversion/libsvn_client/cleanup.c63
-rw-r--r--contrib/subversion/subversion/libsvn_client/client.h1124
-rw-r--r--contrib/subversion/subversion/libsvn_client/cmdline.c363
-rw-r--r--contrib/subversion/subversion/libsvn_client/commit.c1031
-rw-r--r--contrib/subversion/subversion/libsvn_client/commit_util.c1981
-rw-r--r--contrib/subversion/subversion/libsvn_client/compat_providers.c136
-rw-r--r--contrib/subversion/subversion/libsvn_client/copy.c2422
-rw-r--r--contrib/subversion/subversion/libsvn_client/copy_foreign.c571
-rw-r--r--contrib/subversion/subversion/libsvn_client/ctx.c112
-rw-r--r--contrib/subversion/subversion/libsvn_client/delete.c595
-rw-r--r--contrib/subversion/subversion/libsvn_client/deprecated.c2966
-rw-r--r--contrib/subversion/subversion/libsvn_client/diff.c2723
-rw-r--r--contrib/subversion/subversion/libsvn_client/diff_local.c633
-rw-r--r--contrib/subversion/subversion/libsvn_client/diff_summarize.c317
-rw-r--r--contrib/subversion/subversion/libsvn_client/export.c1589
-rw-r--r--contrib/subversion/subversion/libsvn_client/externals.c1139
-rw-r--r--contrib/subversion/subversion/libsvn_client/import.c964
-rw-r--r--contrib/subversion/subversion/libsvn_client/info.c402
-rw-r--r--contrib/subversion/subversion/libsvn_client/iprops.c270
-rw-r--r--contrib/subversion/subversion/libsvn_client/list.c579
-rw-r--r--contrib/subversion/subversion/libsvn_client/locking_commands.c552
-rw-r--r--contrib/subversion/subversion/libsvn_client/log.c868
-rw-r--r--contrib/subversion/subversion/libsvn_client/merge.c12674
-rw-r--r--contrib/subversion/subversion/libsvn_client/mergeinfo.c2191
-rw-r--r--contrib/subversion/subversion/libsvn_client/mergeinfo.h414
-rw-r--r--contrib/subversion/subversion/libsvn_client/patch.c3043
-rw-r--r--contrib/subversion/subversion/libsvn_client/prop_commands.c1559
-rw-r--r--contrib/subversion/subversion/libsvn_client/ra.c1147
-rw-r--r--contrib/subversion/subversion/libsvn_client/relocate.c289
-rw-r--r--contrib/subversion/subversion/libsvn_client/repos_diff.c1405
-rw-r--r--contrib/subversion/subversion/libsvn_client/resolved.c148
-rw-r--r--contrib/subversion/subversion/libsvn_client/revert.c201
-rw-r--r--contrib/subversion/subversion/libsvn_client/revisions.c191
-rw-r--r--contrib/subversion/subversion/libsvn_client/status.c767
-rw-r--r--contrib/subversion/subversion/libsvn_client/switch.c487
-rw-r--r--contrib/subversion/subversion/libsvn_client/update.c707
-rw-r--r--contrib/subversion/subversion/libsvn_client/upgrade.c327
-rw-r--r--contrib/subversion/subversion/libsvn_client/url.c63
-rw-r--r--contrib/subversion/subversion/libsvn_client/util.c457
-rw-r--r--contrib/subversion/subversion/libsvn_client/version.c33
-rw-r--r--contrib/subversion/subversion/libsvn_delta/cancel.c378
-rw-r--r--contrib/subversion/subversion/libsvn_delta/compat.c2010
-rw-r--r--contrib/subversion/subversion/libsvn_delta/compose_delta.c837
-rw-r--r--contrib/subversion/subversion/libsvn_delta/debug_editor.c437
-rw-r--r--contrib/subversion/subversion/libsvn_delta/debug_editor.h49
-rw-r--r--contrib/subversion/subversion/libsvn_delta/default_editor.c161
-rw-r--r--contrib/subversion/subversion/libsvn_delta/delta.h96
-rw-r--r--contrib/subversion/subversion/libsvn_delta/deprecated.c48
-rw-r--r--contrib/subversion/subversion/libsvn_delta/depth_filter_editor.c485
-rw-r--r--contrib/subversion/subversion/libsvn_delta/editor.c956
-rw-r--r--contrib/subversion/subversion/libsvn_delta/path_driver.c298
-rw-r--r--contrib/subversion/subversion/libsvn_delta/svndiff.c1103
-rw-r--r--contrib/subversion/subversion/libsvn_delta/text_delta.c1041
-rw-r--r--contrib/subversion/subversion/libsvn_delta/version.c33
-rw-r--r--contrib/subversion/subversion/libsvn_delta/xdelta.c514
-rw-r--r--contrib/subversion/subversion/libsvn_diff/deprecated.c289
-rw-r--r--contrib/subversion/subversion/libsvn_diff/diff.c199
-rw-r--r--contrib/subversion/subversion/libsvn_diff/diff.h217
-rw-r--r--contrib/subversion/subversion/libsvn_diff/diff3.c529
-rw-r--r--contrib/subversion/subversion/libsvn_diff/diff4.c314
-rw-r--r--contrib/subversion/subversion/libsvn_diff/diff_file.c2414
-rw-r--r--contrib/subversion/subversion/libsvn_diff/diff_memory.c1161
-rw-r--r--contrib/subversion/subversion/libsvn_diff/diff_tree.c1705
-rw-r--r--contrib/subversion/subversion/libsvn_diff/lcs.c375
-rw-r--r--contrib/subversion/subversion/libsvn_diff/parse-diff.c1373
-rw-r--r--contrib/subversion/subversion/libsvn_diff/token.c198
-rw-r--r--contrib/subversion/subversion/libsvn_diff/util.c591
-rw-r--r--contrib/subversion/subversion/libsvn_fs/access.c105
-rw-r--r--contrib/subversion/subversion/libsvn_fs/editor.c850
-rw-r--r--contrib/subversion/subversion/libsvn_fs/fs-loader.c1602
-rw-r--r--contrib/subversion/subversion/libsvn_fs/fs-loader.h502
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/bdb-err.c106
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/bdb-err.h115
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/bdb_compat.c34
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/bdb_compat.h135
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/changes-table.c457
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/changes-table.h94
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/checksum-reps-table.c208
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/checksum-reps-table.h89
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/copies-table.c210
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/copies-table.h93
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/dbt.c170
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/dbt.h120
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/env.c719
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/env.h159
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/lock-tokens-table.c157
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/lock-tokens-table.h96
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/locks-table.c328
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/locks-table.h110
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/miscellaneous-table.c135
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/miscellaneous-table.h71
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/node-origins-table.c145
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/node-origins-table.h76
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/nodes-table.c259
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/nodes-table.h121
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/reps-table.c204
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/reps-table.h94
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/rev-table.c221
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/rev-table.h85
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/strings-table.c541
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/strings-table.h143
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/txn-table.c325
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/txn-table.h100
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/uuids-table.c149
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/bdb/uuids-table.h69
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/dag.c1758
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/dag.h587
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/err.c177
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/err.h98
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/fs.c1436
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/fs.h357
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/id.c208
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/id.h81
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/key-gen.c131
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/key-gen.h100
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/lock.c594
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/lock.h120
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/node-rev.c126
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/node-rev.h101
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/notes/TODO137
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/notes/fs-history270
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/notes/structure1086
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/reps-strings.c1617
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/reps-strings.h176
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/revs-txns.c1067
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/revs-txns.h231
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/trail.c292
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/trail.h239
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/tree.c5451
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/tree.h99
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/util/fs_skels.c1515
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/util/fs_skels.h177
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/uuid.c116
-rw-r--r--contrib/subversion/subversion/libsvn_fs_base/uuid.h49
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/caching.c692
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/dag.c1338
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/dag.h581
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/fs.c456
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/fs.h523
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/fs_fs.c11469
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/fs_fs.h575
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/id.c405
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/id.h116
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/key-gen.c159
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/key-gen.h91
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/lock.c1079
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/lock.h103
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/rep-cache-db.h83
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/rep-cache-db.sql65
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/rep-cache.c381
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/rep-cache.h101
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/structure621
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/temp_serializer.c1341
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/temp_serializer.h266
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/tree.c4420
-rw-r--r--contrib/subversion/subversion/libsvn_fs_fs/tree.h98
-rw-r--r--contrib/subversion/subversion/libsvn_fs_util/fs-util.c223
-rw-r--r--contrib/subversion/subversion/libsvn_ra/compat.c952
-rw-r--r--contrib/subversion/subversion/libsvn_ra/debug_reporter.c151
-rw-r--r--contrib/subversion/subversion/libsvn_ra/debug_reporter.h49
-rw-r--r--contrib/subversion/subversion/libsvn_ra/deprecated.c509
-rw-r--r--contrib/subversion/subversion/libsvn_ra/deprecated.h60
-rw-r--r--contrib/subversion/subversion/libsvn_ra/editor.c339
-rw-r--r--contrib/subversion/subversion/libsvn_ra/ra_loader.c1576
-rw-r--r--contrib/subversion/subversion/libsvn_ra/ra_loader.h562
-rw-r--r--contrib/subversion/subversion/libsvn_ra/util.c242
-rw-r--r--contrib/subversion/subversion/libsvn_ra/wrapper_template.h512
-rw-r--r--contrib/subversion/subversion/libsvn_ra_local/ra_local.h97
-rw-r--r--contrib/subversion/subversion/libsvn_ra_local/ra_plugin.c1766
-rw-r--r--contrib/subversion/subversion/libsvn_ra_local/split_url.c97
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/README84
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/blame.c375
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/blncache.c179
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/blncache.h90
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/commit.c2468
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/get_deleted_rev.c178
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/getdate.c161
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/getlocations.c201
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/getlocationsegments.c206
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/getlocks.c277
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/inherited_props.c344
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/locks.c654
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/log.c604
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/merge.c430
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/mergeinfo.c246
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/options.c625
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/property.c1263
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/ra_serf.h1785
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/replay.c920
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/sb_bucket.c185
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/serf.c1246
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/update.c3639
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/util.c2614
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/util_error.c100
-rw-r--r--contrib/subversion/subversion/libsvn_ra_serf/xml.c825
-rw-r--r--contrib/subversion/subversion/libsvn_ra_svn/client.c2739
-rw-r--r--contrib/subversion/subversion/libsvn_ra_svn/cram.c221
-rw-r--r--contrib/subversion/subversion/libsvn_ra_svn/cyrus_auth.c954
-rw-r--r--contrib/subversion/subversion/libsvn_ra_svn/deprecated.c234
-rw-r--r--contrib/subversion/subversion/libsvn_ra_svn/editorp.c1044
-rw-r--r--contrib/subversion/subversion/libsvn_ra_svn/internal_auth.c121
-rw-r--r--contrib/subversion/subversion/libsvn_ra_svn/marshal.c2289
-rw-r--r--contrib/subversion/subversion/libsvn_ra_svn/protocol625
-rw-r--r--contrib/subversion/subversion/libsvn_ra_svn/ra_svn.h249
-rw-r--r--contrib/subversion/subversion/libsvn_ra_svn/streams.c255
-rw-r--r--contrib/subversion/subversion/libsvn_ra_svn/version.c33
-rw-r--r--contrib/subversion/subversion/libsvn_repos/authz.c1075
-rw-r--r--contrib/subversion/subversion/libsvn_repos/commit.c1381
-rw-r--r--contrib/subversion/subversion/libsvn_repos/delta.c1074
-rw-r--r--contrib/subversion/subversion/libsvn_repos/deprecated.c1017
-rw-r--r--contrib/subversion/subversion/libsvn_repos/dump.c1503
-rw-r--r--contrib/subversion/subversion/libsvn_repos/fs-wrap.c844
-rw-r--r--contrib/subversion/subversion/libsvn_repos/hooks.c890
-rw-r--r--contrib/subversion/subversion/libsvn_repos/load-fs-vtable.c1140
-rw-r--r--contrib/subversion/subversion/libsvn_repos/load.c684
-rw-r--r--contrib/subversion/subversion/libsvn_repos/log.c2369
-rw-r--r--contrib/subversion/subversion/libsvn_repos/node_tree.c431
-rw-r--r--contrib/subversion/subversion/libsvn_repos/notify.c44
-rw-r--r--contrib/subversion/subversion/libsvn_repos/replay.c1591
-rw-r--r--contrib/subversion/subversion/libsvn_repos/reporter.c1610
-rw-r--r--contrib/subversion/subversion/libsvn_repos/repos.c2132
-rw-r--r--contrib/subversion/subversion/libsvn_repos/repos.h425
-rw-r--r--contrib/subversion/subversion/libsvn_repos/rev_hunt.c1699
-rw-r--r--contrib/subversion/subversion/libsvn_subr/adler32.c101
-rw-r--r--contrib/subversion/subversion/libsvn_subr/atomic.c85
-rw-r--r--contrib/subversion/subversion/libsvn_subr/auth.c652
-rw-r--r--contrib/subversion/subversion/libsvn_subr/auth.h49
-rw-r--r--contrib/subversion/subversion/libsvn_subr/base64.c567
-rw-r--r--contrib/subversion/subversion/libsvn_subr/cache-inprocess.c648
-rw-r--r--contrib/subversion/subversion/libsvn_subr/cache-membuffer.c2369
-rw-r--r--contrib/subversion/subversion/libsvn_subr/cache-memcache.c583
-rw-r--r--contrib/subversion/subversion/libsvn_subr/cache.c265
-rw-r--r--contrib/subversion/subversion/libsvn_subr/cache.h109
-rw-r--r--contrib/subversion/subversion/libsvn_subr/cache_config.c169
-rw-r--r--contrib/subversion/subversion/libsvn_subr/checksum.c500
-rw-r--r--contrib/subversion/subversion/libsvn_subr/cmdline.c1312
-rw-r--r--contrib/subversion/subversion/libsvn_subr/compat.c159
-rw-r--r--contrib/subversion/subversion/libsvn_subr/config.c1208
-rw-r--r--contrib/subversion/subversion/libsvn_subr/config_auth.c277
-rw-r--r--contrib/subversion/subversion/libsvn_subr/config_file.c1260
-rw-r--r--contrib/subversion/subversion/libsvn_subr/config_impl.h161
-rw-r--r--contrib/subversion/subversion/libsvn_subr/config_win.c259
-rw-r--r--contrib/subversion/subversion/libsvn_subr/crypto.c705
-rw-r--r--contrib/subversion/subversion/libsvn_subr/crypto.h141
-rw-r--r--contrib/subversion/subversion/libsvn_subr/ctype.c319
-rw-r--r--contrib/subversion/subversion/libsvn_subr/date.c393
-rw-r--r--contrib/subversion/subversion/libsvn_subr/debug.c155
-rw-r--r--contrib/subversion/subversion/libsvn_subr/deprecated.c1304
-rw-r--r--contrib/subversion/subversion/libsvn_subr/dirent_uri.c2597
-rw-r--r--contrib/subversion/subversion/libsvn_subr/dirent_uri.h40
-rw-r--r--contrib/subversion/subversion/libsvn_subr/dso.c117
-rw-r--r--contrib/subversion/subversion/libsvn_subr/eol.c108
-rw-r--r--contrib/subversion/subversion/libsvn_subr/error.c800
-rwxr-xr-xcontrib/subversion/subversion/libsvn_subr/genctype.py114
-rw-r--r--contrib/subversion/subversion/libsvn_subr/gpg_agent.c463
-rw-r--r--contrib/subversion/subversion/libsvn_subr/hash.c642
-rw-r--r--contrib/subversion/subversion/libsvn_subr/internal_statements.h76
-rw-r--r--contrib/subversion/subversion/libsvn_subr/internal_statements.sql47
-rw-r--r--contrib/subversion/subversion/libsvn_subr/io.c4768
-rw-r--r--contrib/subversion/subversion/libsvn_subr/iter.c216
-rw-r--r--contrib/subversion/subversion/libsvn_subr/lock.c60
-rw-r--r--contrib/subversion/subversion/libsvn_subr/log.c396
-rw-r--r--contrib/subversion/subversion/libsvn_subr/macos_keychain.c263
-rw-r--r--contrib/subversion/subversion/libsvn_subr/magic.c161
-rw-r--r--contrib/subversion/subversion/libsvn_subr/md5.c110
-rw-r--r--contrib/subversion/subversion/libsvn_subr/md5.h71
-rw-r--r--contrib/subversion/subversion/libsvn_subr/mergeinfo.c2631
-rw-r--r--contrib/subversion/subversion/libsvn_subr/mutex.c83
-rw-r--r--contrib/subversion/subversion/libsvn_subr/named_atomic.c655
-rw-r--r--contrib/subversion/subversion/libsvn_subr/nls.c132
-rw-r--r--contrib/subversion/subversion/libsvn_subr/opt.c1240
-rw-r--r--contrib/subversion/subversion/libsvn_subr/opt.h54
-rw-r--r--contrib/subversion/subversion/libsvn_subr/path.c1315
-rw-r--r--contrib/subversion/subversion/libsvn_subr/pool.c142
-rw-r--r--contrib/subversion/subversion/libsvn_subr/prompt.c954
-rw-r--r--contrib/subversion/subversion/libsvn_subr/properties.c507
-rw-r--r--contrib/subversion/subversion/libsvn_subr/pseudo_md5.c422
-rw-r--r--contrib/subversion/subversion/libsvn_subr/quoprint.c309
-rw-r--r--contrib/subversion/subversion/libsvn_subr/sha1.c82
-rw-r--r--contrib/subversion/subversion/libsvn_subr/sha1.h70
-rw-r--r--contrib/subversion/subversion/libsvn_subr/simple_providers.c734
-rw-r--r--contrib/subversion/subversion/libsvn_subr/skel.c881
-rw-r--r--contrib/subversion/subversion/libsvn_subr/sorts.c309
-rw-r--r--contrib/subversion/subversion/libsvn_subr/spillbuf.c615
-rw-r--r--contrib/subversion/subversion/libsvn_subr/sqlite.c1294
-rw-r--r--contrib/subversion/subversion/libsvn_subr/sqlite3wrapper.c62
-rw-r--r--contrib/subversion/subversion/libsvn_subr/ssl_client_cert_providers.c209
-rw-r--r--contrib/subversion/subversion/libsvn_subr/ssl_client_cert_pw_providers.c506
-rw-r--r--contrib/subversion/subversion/libsvn_subr/ssl_server_trust_providers.c234
-rw-r--r--contrib/subversion/subversion/libsvn_subr/stream.c1826
-rw-r--r--contrib/subversion/subversion/libsvn_subr/string.c1273
-rw-r--r--contrib/subversion/subversion/libsvn_subr/subst.c2025
-rw-r--r--contrib/subversion/subversion/libsvn_subr/sysinfo.c1132
-rw-r--r--contrib/subversion/subversion/libsvn_subr/sysinfo.h69
-rw-r--r--contrib/subversion/subversion/libsvn_subr/target.c335
-rw-r--r--contrib/subversion/subversion/libsvn_subr/temp_serializer.c382
-rw-r--r--contrib/subversion/subversion/libsvn_subr/time.c277
-rw-r--r--contrib/subversion/subversion/libsvn_subr/token.c98
-rw-r--r--contrib/subversion/subversion/libsvn_subr/types.c340
-rw-r--r--contrib/subversion/subversion/libsvn_subr/user.c86
-rw-r--r--contrib/subversion/subversion/libsvn_subr/username_providers.c306
-rw-r--r--contrib/subversion/subversion/libsvn_subr/utf.c1075
-rw-r--r--contrib/subversion/subversion/libsvn_subr/utf_validate.c485
-rw-r--r--contrib/subversion/subversion/libsvn_subr/utf_width.c283
-rw-r--r--contrib/subversion/subversion/libsvn_subr/validate.c102
-rw-r--r--contrib/subversion/subversion/libsvn_subr/version.c291
-rw-r--r--contrib/subversion/subversion/libsvn_subr/win32_crashrpt.c805
-rw-r--r--contrib/subversion/subversion/libsvn_subr/win32_crashrpt.h35
-rw-r--r--contrib/subversion/subversion/libsvn_subr/win32_crashrpt_dll.h93
-rw-r--r--contrib/subversion/subversion/libsvn_subr/win32_crypto.c492
-rw-r--r--contrib/subversion/subversion/libsvn_subr/win32_xlate.c238
-rw-r--r--contrib/subversion/subversion/libsvn_subr/win32_xlate.h52
-rw-r--r--contrib/subversion/subversion/libsvn_subr/xml.c655
-rw-r--r--contrib/subversion/subversion/libsvn_wc/README195
-rw-r--r--contrib/subversion/subversion/libsvn_wc/adm_crawler.c1239
-rw-r--r--contrib/subversion/subversion/libsvn_wc/adm_files.c584
-rw-r--r--contrib/subversion/subversion/libsvn_wc/adm_files.h161
-rw-r--r--contrib/subversion/subversion/libsvn_wc/adm_ops.c1400
-rw-r--r--contrib/subversion/subversion/libsvn_wc/ambient_depth_filter_editor.c715
-rw-r--r--contrib/subversion/subversion/libsvn_wc/cleanup.c231
-rw-r--r--contrib/subversion/subversion/libsvn_wc/conflicts.c3141
-rw-r--r--contrib/subversion/subversion/libsvn_wc/conflicts.h443
-rw-r--r--contrib/subversion/subversion/libsvn_wc/context.c116
-rw-r--r--contrib/subversion/subversion/libsvn_wc/copy.c1048
-rw-r--r--contrib/subversion/subversion/libsvn_wc/crop.c361
-rw-r--r--contrib/subversion/subversion/libsvn_wc/delete.c508
-rw-r--r--contrib/subversion/subversion/libsvn_wc/deprecated.c4582
-rw-r--r--contrib/subversion/subversion/libsvn_wc/diff.h144
-rw-r--r--contrib/subversion/subversion/libsvn_wc/diff_editor.c2747
-rw-r--r--contrib/subversion/subversion/libsvn_wc/diff_local.c541
-rw-r--r--contrib/subversion/subversion/libsvn_wc/entries.c2738
-rw-r--r--contrib/subversion/subversion/libsvn_wc/entries.h164
-rw-r--r--contrib/subversion/subversion/libsvn_wc/externals.c1686
-rw-r--r--contrib/subversion/subversion/libsvn_wc/info.c580
-rw-r--r--contrib/subversion/subversion/libsvn_wc/lock.c1656
-rw-r--r--contrib/subversion/subversion/libsvn_wc/lock.h91
-rw-r--r--contrib/subversion/subversion/libsvn_wc/merge.c1424
-rw-r--r--contrib/subversion/subversion/libsvn_wc/node.c1418
-rw-r--r--contrib/subversion/subversion/libsvn_wc/old-and-busted.c1340
-rw-r--r--contrib/subversion/subversion/libsvn_wc/props.c2344
-rw-r--r--contrib/subversion/subversion/libsvn_wc/props.h154
-rw-r--r--contrib/subversion/subversion/libsvn_wc/questions.c621
-rw-r--r--contrib/subversion/subversion/libsvn_wc/relocate.c170
-rw-r--r--contrib/subversion/subversion/libsvn_wc/revert.c886
-rw-r--r--contrib/subversion/subversion/libsvn_wc/revision_status.c67
-rw-r--r--contrib/subversion/subversion/libsvn_wc/status.c3047
-rw-r--r--contrib/subversion/subversion/libsvn_wc/token-map.h70
-rw-r--r--contrib/subversion/subversion/libsvn_wc/translate.c452
-rw-r--r--contrib/subversion/subversion/libsvn_wc/translate.h189
-rw-r--r--contrib/subversion/subversion/libsvn_wc/tree_conflicts.c513
-rw-r--r--contrib/subversion/subversion/libsvn_wc/tree_conflicts.h93
-rw-r--r--contrib/subversion/subversion/libsvn_wc/update_editor.c5486
-rw-r--r--contrib/subversion/subversion/libsvn_wc/upgrade.c2376
-rw-r--r--contrib/subversion/subversion/libsvn_wc/util.c636
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc-checks.h55
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc-checks.sql77
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc-metadata.h516
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc-metadata.sql951
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc-queries.h3100
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc-queries.sql1693
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc.h808
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc_db.c15050
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc_db.h3413
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc_db_pristine.c925
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc_db_private.h458
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc_db_update_move.c2631
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc_db_util.c228
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wc_db_wcroot.c900
-rw-r--r--contrib/subversion/subversion/libsvn_wc/wcroot_anchor.c227
-rw-r--r--contrib/subversion/subversion/libsvn_wc/workqueue.c1666
-rw-r--r--contrib/subversion/subversion/libsvn_wc/workqueue.h235
-rw-r--r--contrib/subversion/subversion/svn/add-cmd.c113
-rw-r--r--contrib/subversion/subversion/svn/blame-cmd.c419
-rw-r--r--contrib/subversion/subversion/svn/cat-cmd.c118
-rw-r--r--contrib/subversion/subversion/svn/changelist-cmd.c149
-rw-r--r--contrib/subversion/subversion/svn/checkout-cmd.c173
-rw-r--r--contrib/subversion/subversion/svn/cl-conflicts.c454
-rw-r--r--contrib/subversion/subversion/svn/cl-conflicts.h80
-rw-r--r--contrib/subversion/subversion/svn/cl.h852
-rw-r--r--contrib/subversion/subversion/svn/cleanup-cmd.c104
-rw-r--r--contrib/subversion/subversion/svn/client_errors.h97
-rw-r--r--contrib/subversion/subversion/svn/commit-cmd.c186
-rw-r--r--contrib/subversion/subversion/svn/conflict-callbacks.c1369
-rw-r--r--contrib/subversion/subversion/svn/copy-cmd.c184
-rw-r--r--contrib/subversion/subversion/svn/delete-cmd.c95
-rw-r--r--contrib/subversion/subversion/svn/deprecated.c41
-rw-r--r--contrib/subversion/subversion/svn/diff-cmd.c476
-rw-r--r--contrib/subversion/subversion/svn/export-cmd.c128
-rw-r--r--contrib/subversion/subversion/svn/file-merge.c959
-rw-r--r--contrib/subversion/subversion/svn/help-cmd.c153
-rw-r--r--contrib/subversion/subversion/svn/import-cmd.c132
-rw-r--r--contrib/subversion/subversion/svn/info-cmd.c683
-rw-r--r--contrib/subversion/subversion/svn/list-cmd.c424
-rw-r--r--contrib/subversion/subversion/svn/lock-cmd.c110
-rw-r--r--contrib/subversion/subversion/svn/log-cmd.c875
-rw-r--r--contrib/subversion/subversion/svn/merge-cmd.c467
-rw-r--r--contrib/subversion/subversion/svn/mergeinfo-cmd.c349
-rw-r--r--contrib/subversion/subversion/svn/mkdir-cmd.c104
-rw-r--r--contrib/subversion/subversion/svn/move-cmd.c105
-rw-r--r--contrib/subversion/subversion/svn/notify.c1222
-rw-r--r--contrib/subversion/subversion/svn/patch-cmd.c98
-rw-r--r--contrib/subversion/subversion/svn/propdel-cmd.c103
-rw-r--r--contrib/subversion/subversion/svn/propedit-cmd.c356
-rw-r--r--contrib/subversion/subversion/svn/propget-cmd.c493
-rw-r--r--contrib/subversion/subversion/svn/proplist-cmd.c336
-rw-r--r--contrib/subversion/subversion/svn/props.c356
-rw-r--r--contrib/subversion/subversion/svn/propset-cmd.c191
-rw-r--r--contrib/subversion/subversion/svn/relocate-cmd.c120
-rw-r--r--contrib/subversion/subversion/svn/resolve-cmd.c131
-rw-r--r--contrib/subversion/subversion/svn/resolved-cmd.c88
-rw-r--r--contrib/subversion/subversion/svn/revert-cmd.c81
-rw-r--r--contrib/subversion/subversion/svn/schema/blame.rnc42
-rw-r--r--contrib/subversion/subversion/svn/schema/common.rnc77
-rw-r--r--contrib/subversion/subversion/svn/schema/diff.rnc39
-rw-r--r--contrib/subversion/subversion/svn/schema/info.rnc134
-rw-r--r--contrib/subversion/subversion/svn/schema/list.rnc45
-rw-r--r--contrib/subversion/subversion/svn/schema/log.rnc55
-rw-r--r--contrib/subversion/subversion/svn/schema/props.rnc36
-rw-r--r--contrib/subversion/subversion/svn/schema/status.rnc92
-rw-r--r--contrib/subversion/subversion/svn/status-cmd.c416
-rw-r--r--contrib/subversion/subversion/svn/status.c607
-rw-r--r--contrib/subversion/subversion/svn/svn.147
-rw-r--r--contrib/subversion/subversion/svn/svn.c2961
-rw-r--r--contrib/subversion/subversion/svn/switch-cmd.c199
-rw-r--r--contrib/subversion/subversion/svn/unlock-cmd.c68
-rw-r--r--contrib/subversion/subversion/svn/update-cmd.c196
-rw-r--r--contrib/subversion/subversion/svn/upgrade-cmd.c78
-rw-r--r--contrib/subversion/subversion/svn/util.c1109
-rw-r--r--contrib/subversion/subversion/svn_private_config.h.in257
-rw-r--r--contrib/subversion/subversion/svn_private_config.hw110
-rw-r--r--contrib/subversion/subversion/svnadmin/svnadmin.147
-rw-r--r--contrib/subversion/subversion/svnadmin/svnadmin.c2380
-rw-r--r--contrib/subversion/subversion/svndumpfilter/svndumpfilter.147
-rw-r--r--contrib/subversion/subversion/svndumpfilter/svndumpfilter.c1658
-rw-r--r--contrib/subversion/subversion/svnlook/svnlook.147
-rw-r--r--contrib/subversion/subversion/svnlook/svnlook.c2830
-rw-r--r--contrib/subversion/subversion/svnmucc/svnmucc.147
-rw-r--r--contrib/subversion/subversion/svnmucc/svnmucc.c1460
-rw-r--r--contrib/subversion/subversion/svnrdump/dump_editor.c1280
-rw-r--r--contrib/subversion/subversion/svnrdump/load_editor.c1211
-rw-r--r--contrib/subversion/subversion/svnrdump/svnrdump.147
-rw-r--r--contrib/subversion/subversion/svnrdump/svnrdump.c1185
-rw-r--r--contrib/subversion/subversion/svnrdump/svnrdump.h129
-rw-r--r--contrib/subversion/subversion/svnrdump/util.c73
-rw-r--r--contrib/subversion/subversion/svnserve/cyrus_auth.c377
-rw-r--r--contrib/subversion/subversion/svnserve/log-escape.c143
-rw-r--r--contrib/subversion/subversion/svnserve/serve.c3678
-rw-r--r--contrib/subversion/subversion/svnserve/server.h186
-rw-r--r--contrib/subversion/subversion/svnserve/svnserve.8138
-rw-r--r--contrib/subversion/subversion/svnserve/svnserve.c1175
-rw-r--r--contrib/subversion/subversion/svnserve/svnserve.conf.5100
-rw-r--r--contrib/subversion/subversion/svnserve/winservice.c490
-rw-r--r--contrib/subversion/subversion/svnserve/winservice.h64
-rw-r--r--contrib/subversion/subversion/svnsync/svnsync.147
-rw-r--r--contrib/subversion/subversion/svnsync/svnsync.c2305
-rw-r--r--contrib/subversion/subversion/svnsync/sync.c643
-rw-r--r--contrib/subversion/subversion/svnsync/sync.h85
-rw-r--r--contrib/subversion/subversion/svnversion/svnversion.147
-rw-r--r--contrib/subversion/subversion/svnversion/svnversion.c300
-rw-r--r--contrib/subversion/win-tests.py858
575 files changed, 448342 insertions, 0 deletions
diff --git a/contrib/subversion/BUGS b/contrib/subversion/BUGS
new file mode 100644
index 0000000..6ce79ae
--- /dev/null
+++ b/contrib/subversion/BUGS
@@ -0,0 +1,2 @@
+This document has been moved to
+http://subversion.apache.org/docs/community-guide/issues.html
diff --git a/contrib/subversion/CHANGES b/contrib/subversion/CHANGES
new file mode 100644
index 0000000..b0e61f2
--- /dev/null
+++ b/contrib/subversion/CHANGES
@@ -0,0 +1,4623 @@
+Version 1.8.0
+(18 Jun 2013, from /branches/1.8.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.8.0
+
+ User-visible changes:
+ - General:
+ * require serf as client-side http library (neon support removed) (r1349694)
+ * deprecate the Berkeley DB FS backend (libsvn_fs_base) (r1464985 et al)
+
+ - Major new features:
+ * working copy records moves as first-class operation (issue #3631, #4232)
+ * merge uses reintegrate mode automatically when needed (r1369896 et al)
+ * FSFS: Packing of revision property shards (issue #3944)
+ * support inheritable properties (r1395109)
+ * repository can suggest config for autoprops and ignores (r1401908)
+ * support gpg-agent for password caching (r1151069)
+ * authz rules can be stored inside the repository (r1424780)
+
+ - Minor new features and improvements (client-side):
+ * doubled svn:// protocol throughput (r1325899)
+ * optimize file/dir truename checks on Windows (r1435527)
+ * new 'commit --include-externals' option (related to issues #1167, #3563)
+ * new --include-externals option for 'svn list' (issue #4225)
+ * remove extraneous externals output from 'svn status -q' (issue #1935)
+ * reject some attempts to merge between unrelated branches (r1215273)
+ * new --ignore-properties option for 'svn diff' (r1239553, -617)
+ * new --properties-only option for 'svn diff' (r1336110)
+ * new --patch-compatible option for 'svn diff' (r1239561)
+ * new --no-diff-added option for 'svn diff' (r1433958)
+ * new w/c subtree duplication tool (tools/client-side/detach.py)
+ * new mergeinfo fixup tool (tools/client-side/mergeinfo-sanitizer.py)
+ * 'svn diff' can compare arbitrary files and directories (r1310291, et al)
+ * ra_serf avoids re-downloading content present in pristine store (r1333936)
+ * 'svn mergeinfo' now honors the --revision (-r) option (issue #4199)
+ * 'svn mergeinfo' now shows a summary graph by default (issue #4239)
+ * new --search and --search-and options for 'svn log' (r1354666, -83518)
+ * 'svn log' reports the node kind even for pre-1.6 revision files (r1242958)
+ * sort path list generated by "svn log -v --xml" (r1299323)
+ * new built-in interactive text conflict merge tool (r1357864, et al)
+ * 'svn --version' shows build system info (r1368662)
+ * 'svn --version --verbose' shows runtime environment info (r1370813 et al)
+ * 'svn' is now non-interactive when not run in a terminal device (r1424037)
+ * 'svn propset' checks spelling of reserved property names (r1470781)
+ * improve working copy performance on network disks (issue #4176)
+ * support for custom keyword definitions in svn:keywords (issue #890)
+ * svn:ignore __pycache__ directories by default (r1150073)
+ * 'svn diff --git' include copyfrom revision in "copied" headers (r1155279)
+ * svn:mergeinfo related operations now use much less memory (r1149519 et al)
+ * get list of supported schemes for RA libraries (r1148134)
+ * 'svn checkout' skips file externals from other repositories (r1153110)
+ * 'svn resolve' exits non-zero if conflicts could not be resolved (r1150439)
+ * let HTTPv2-aware clients fetch v2-style resources (r1161202)
+ * 'svn status' with better NLS support (r1157537, -682)
+ * better tracking of shallow-yet-complete merges (issues #4056, #4057)
+ * make 'svn status --quiet' w/ externals quieter still (issue #1935)
+ * ensure that conflict paths are shown relative-ized (r1337520)
+ * improve performance of local multi-target deletions (r1195873)
+ * various interactive conflict resolver improvements in 'svn' (r1440421 etc)
+ * improved tree diff implementation for diff and merge (r1440599 et al)
+ * tree conflicts on directories detected better during merges (issue #3150)
+ * allow reverting unmodified copies with 'svn remove' (r1442611)
+ * make 'svn diff' with mixed URL and local path targets work (r1442640)
+ * make 'svn patch' re-add deleted directories if needed (r1445333)
+ * make repos-wc diffs fully ancestry-aware (r1445904)
+ * 'svn diff --git' now implies 'svn diff --show-copies-as-adds' (r1446279)
+ * 'svn diff --show-copies-as-adds' now implies --notice-ancestry (r1446279)
+ * improved tree-conflict detection for 'svn switch' (r1449413, r1450582)
+ * allow up to 8 revision number digits in 'svn status -v' output (r1428637)
+ * show node kind (file or dir) in tree conflict descriptions (r1429907)
+ * restore deleted switched paths upon next update (issue #4295)
+ * add support for copying paths from foreign repositories (issue #3590)
+ * fix merge -cA,B with --accept option aborts if rA conflicts (issue #4238)
+ * 'svn resolve' interactive support; no longer requires --accept (r1336929)
+ * notify when removing externals leaves behind modified files (r1366021)
+ * new 'http-max-connections' configuration option for serf (r1421559)
+ * new 'http-bulk-updates' configuration option for serf (r1421490)
+ * 'svn cleanup' now runs SQLite "vacuum" to reclaim space (r1418459)
+ * 'svn info' displays repository-relative URL (r1415365)
+ * fix serf memory leak on checkout (issue #4194)
+ * detect duplicate paths setting svn:externals (issue #4227)
+ * make ra_serf work over HTTP/1.0 proxies (issue #3979)
+ * make ra_serf accept gzip compression for all responses (r1407454)
+ * double ra_serf performance for checkout and export (r1407545)
+ * improve network and disk i/o interleaving in ra_serf (r1407934)
+ * avoid assert in ra_serf when REPORT response was truncated (r1407935)
+ * rewrite ra_serf XML parser (r1409259 et al)
+ * ra_serf can create transaction with inline txnprops (r1375167)
+ * partially fix replace+propset of locked file fails over DAV (issue #3674)
+ * fix ra_serf doesn't handle bad baseline error from server (issue #4127)
+ * decreased default http timeout for ra_serf (issue #3968)
+ * prevent ra_serf from corrupting the working copy (issue #3993)
+ * ra_serf transmits property changes inline to reduce requests (r1378927)
+ * allow client to avoid SSL certificate prompts (issue #2410)
+ * improve interactive resolution of property conflicts (r1387678 et al)
+ * make ra_serf raise an error upon delta-base mismatch (issue #4235)
+ * tune ra_svn transmit buffer handling (r1391788)
+ * make 'svnrdump' work with serf (issue #4116)
+ * fix 'svnrdump' on path below repository root (issue #4101)
+ * support ipv6 in URLs (e.g. http://[::1]/svn/repos) (r1454047)
+ * conflict resolver now iterates paths in a sorted order (r1461820)
+ * mod_dav_svn does keyword expansion with 'kw=1' query arg (r1466055)
+ * add support for custom keyword definitions (issue #890)
+
+ - Minor new features and improvements (server-side):
+ * improve performance of config file parsing (r1344347 et al)
+ * new 'svnadmin load --revision' load filtering support (issue #3734)
+ * new 'svnadmin hotcopy --incremental' support for FSFS (issue #3815)
+ * new 'svnadmin lock' / 'svnadmin unlock' subcommands (issue #3942, #4092)
+ * new SVNUseUTF8 configuration option for mod_dav_svn (issue #2487)
+ * new SVNHooksEnv configuration option for mod_dav_svn (r1239966)
+ * new SvnPubSub distributed commit hooks (tools/server-side/svnpubsub)
+ * new light-weight benchmarking client (tools/client-side/svn-bench)
+ * svndumpfilter dependency analysis (tools/server-side/svnpredumpfilter.py)
+ * new automatic working copy updater (tools/server-side/svnpubsub)
+ * new 'svnadmin freeze' subcommand (r1376228)
+ * 'svndumpfilter' now supports --delta dumpfiles (r1351009, -3745)
+ * new --drop-all-emtpy-revs option for 'svndumpfilter' (issue #3681)
+ * client version info now reported to commit hooks (issue #4124)
+ * txn name now reported to post-commit hooks (r1240856)
+ * support for server-side keyword expansion in mod_dav_svn (r1466055)
+ * FSFS now able to cache revision properties (r1326307)
+ * FSFS cache for changed-paths increases 'svn log' performance (r1378358)
+ * FSFS cache mergeinfo requested during 'log -g' (r1395439)
+ * many FSFS caching improvements (r1390435, r1390447)
+ * directory and property deltification option in FSFS (issue #4084)
+ * fine-grained control deltification behavior via fsfs.conf (r1311476)
+ * FSFS de-duplication ("rep sharing") now works within a revision (r1397773)
+ * FSFS de-duplication now works for properties as well (r1243312)
+ * read FSFS data using fewer fopen calls (issue #3372)
+ * 'svnadmin verify' will now check meta data (issues #3956, #4211)
+ * 'svnadmin verify' now checks for issue #4129 style corruption (r1304656)
+ * new --client-speed option for svnserve (r1391788)
+ * new --single-threaded option in svnserve (r1296018)
+ * hook script templates are now marked as executable (r1153414)
+ * error out on non-canonical fspaths in the authz file (r1166111)
+ * improve path lookup performance in FSFS (r1442088)
+ * svnserve now logs explicit path and reason for authz failures (r1446542)
+ * validate offsets from rep-cache to prevent FSFS corruption (issue #4277)
+ * new AuthzSVNGroupsFile option to store authz groups separately (r1438407)
+ * new 'SVNAllowBulkUpdates prefer' option for mod_dav_svn (r1417642, et al)
+ * new 'SVNMasterVersion' option for mod_dav_svn (r1398962)
+ * added virtual-host support to 'svnserve' (r1401296)
+ * new fsfs-stats tool which prints FSFS repository stats (r1410995)
+ * new fsfs-reorg tool to optimize FSFS packing (r1383214, r1385395)
+ * new --compatible-version option for 'svnadmin create' (r1407279 )
+ * new --ignore-properties option for 'svnlook diff' (r1407905)
+ * new --properties-only option for 'svnlook diff' (r1407905)
+ * new --diff-cmd option for 'svnlook diff' (r1413449)
+ * allow leading "r"'s in http: ?p= and ?r= query parameters (r1221463)
+ * faster 'svn ls' for large directories (r1296627)
+ * mod_dav_svn now advertises supported POST types (r1375123)
+ * mod_dav_svn can create transaction with inline txnprops (r1375167)
+ * run start-commit hook after transaction creation (r1376201)
+ * avoid byte-for-byte comparison where it can be avoided (r1390641)
+ * various server-side performance improvements for 'log -g' (r1395442 et al)
+ * allow up to 10Gbit throughput with svnserve (r1391788)
+ * install mod_dontdothat correctly (r1454450)
+ * svnadmin verify can now verify transactions (r1462353)
+ * FSFS verifies revisions as they are added (r1462409)
+
+ - Client-side bugfixes:
+ * fix inconsistent 'svn log' output for empty revisions (issue #3964)
+ * fix mis-ordered text output of 'svn log --diff' on Windows (r1220783)
+ * fix 'svn log --diff' on moved file (issue #4153).
+ * fix 'svn revert' of 'svn move' (issue #876)
+ * fix file externals wrongly "resurrecting" a deleted file (#4017)
+ * fix reporting of corrupted 1.6 w/cs by 'svn upgrade' (r1182904, -9)
+ * fix bug caused by URI-decoding local merge source paths (r1210539)
+ * fix properties out of sync with repos after merge and revert (issue #4305)
+ * fix merge of replacement on local delete fails (issue #4011)
+ * fix replacements on deletes produce wrong tree conflicts (issue #3806)
+ * made ra_serf handle location headers that are not RFC-compliant (r1443906)
+ * merge no longer errors out after resolving all conflicts (issue #4316)
+ * fix svn blame mis-categorizing file type as binary (issue #2089)
+ * fix externals not removed when working copy is made shallow (issue #3741)
+ * fix update under add with not-present parent (issue #4111)
+ * fix revert of files with svn:needs-lock under copied dirs (r1343168)
+ * fix repos->wc diff of local copied/moved-here directories (r1341927)
+ * fix repos->wc diff of local copied/moved-here files (r1341544)
+ * fix "svn diff -cN PATH" where PATH was deleted in rN (r1338708)
+ * fix dependency on APR hash order in several logic paths (r1338350 et al)
+ * fix path inconsistencies in 'svn diff' output (r1338291)
+ * fix misleading error message printed by 'svn switch' (issue #2337)
+ * fix bug in mergeinfo recording during foreign-repos merge (r1430310)
+ * fix spurious merge conflicts for binary files with keywords (issue #4221)
+ * fix patching symlinks with 'svn patch' (issue #4273)
+ * make 'svn switch' refresh lock information (issue #3376)
+ * fix 'svn diff' output doesn't apply as patch without fuzz (issue #3362)
+ * fix mergeinfo recording for multiple-revision-range merge (issue #4306)
+ * fix diffs shown by 'show-diff' conflict prompt option (r1438879)
+ * don't print an update summary header with no content (r1439480)
+ * make 'svn rm' remove externals registrations below its targets (r1361256)
+ * fix crashes in ra_serf where AVG 2012 Surf-Shield is in use (issue #4175)
+ * don't raise conflicts on identical binary files (issue #4128)
+ * improve error messages when wc.db missing (issue #4118)
+ * fix 'svn diff' showing wrong text change (issue #4270)
+ * fix 'svn diff -rN' failing to show local replace (issue #3797)
+ * fix 'svn diff' showing wrong revision (issue #4010)
+ * fix 'svn merge' showing spurious notifications (issue #2910)
+ * parse '.@HEAD' correctly (issue #3606)
+ * fix 'svn revert' after conflict in sparse working copy (issue #4168)
+ * fix bug in global/per-server config handling in serf (r1421516)
+ * properly display errors from serf (r1398742)
+ * fix crash in ra_serf (r1408291)
+ * fixed svnmucc propset and propdel on repository root (issue #3663)
+ * fix 'svn info' output with ancient svnserve servers (pre-1.2) (r1409732)
+ * ra_serf shows error message for 408 Request Timeout response (r1410983)
+ * fix handling of "\ No newline ..." in diff/patch (r1411723, r1412382)
+ * allow infinite http timeout in ra_serf (r1411976)
+ * using unknown svn: property names now requires --force (issue #4261)
+ * fix handling of case insensitive configuration files (r1215089)
+ * properly handle errors during password caching (r1380695)
+ * fix svnversion output not always a number (issue #4226)
+ * fix conflict resolver losing executable bit of a file (r1391019)
+ * fix redundant notifications when merging with ra_serf (issue #3802)
+ * fix 'svn add --force /path/to/wcroot' should work (issue #4241)
+ * fix file permissions changed after commit (issue #4331)
+ * improve handling of http errors in ra_serf (1452792, 1452870)
+ * include checksum of missing pristines in error message (r1452800)
+ * fix an assert when merging against a replaced source (issue #4132)
+ * fix replacement in merge source has incorrect notification (issue #4138)
+ * improve performance of checkout (r1453791)
+ * fixed documentation regarding merge source (issue #3247)
+ * fix merge errors out after resolving conflicts (issue #4316)
+ * fix delete/move with file external in unversioned dir (issue #4293)
+ * fix resolving tree conflict with local node missing (r1461848)
+ * fix invalid read during diff suffix scanning (issue #4339)
+ * fix assertion when running 'svn log <SOME_URL>@PREV' (r1462134)
+ * optimize enumerating configuration options (r1464478)
+ * revert will now sleep for timestamps if using commit times (r1464769)
+ * don't allow externals to be deleted with 'svn rm' (r1464992)
+ * improved memory usage in ra_serf and ra_local (r1465280)
+ * replace some assertions with more helpful error messages (r1465975)
+ * fixed long keyword expansion truncated (issue #4349)
+
+ - Server-side bugfixes:
+ * SVNParentPath / repository listing now authz-filtered (r1408184)
+ * user/group names in the authz config file are case-sensitive (r1475772)
+ * limit commit runtime for nodes with very deep histories (r1224836)
+ * 'svnadmin recover' truncates rep-cache at the right point (issue #4077)
+ * fix crashes in dumpstream loading with skipped revs (r1214202, r1214216)
+ * fix 'svn log -g' incorrectly treating rename as merge (issue #4022)
+ * fix bug where fsfs file-hinting fails (issue #4320)
+ * don't leak path of repository on server's disk to clients (r1330906)
+ * remove spurious is-fresh-txn-root from empty revision files (issue #4031)
+ * fix a stdout handling problem in 'svnlook diff' (r1411971)
+ * fix erratic behaviour in 'svnlook diff' showing property diffs (r1412224)
+ * fix inconsistent authz error messages in 'svn log' in svnserve (r1292462)
+ * fix svndumpfilter for empty paths in included or excluded lists (r1294583)
+ * make fsfs packing threadsafe (r1376011)
+ * don't error out on intermittent memcached failures (r1394470)
+ * fix a ra_svn deadlock with zero-copy server option (r1465622)
+
+ - Other tool improvements and bugfixes:
+ * 'svnmucc' promoted to first-class supported utility (issue #3308, #4279)
+ * make 'svnmucc' prompt for log messages (issue #3418)
+ * rename 'svnauthz-validate' to 'svnauthz' (issue #4284)
+ * make 'svnauthz' optionally validate user/path access (r1197588)
+ * fix mailer.py test suite problems (r1449582)
+ * fix mailer.py not showing dirs with property deletions (r1449582)
+ * make mailer.py generate Date and Message-ID headers (r1449592)
+ * new '-?' option support for 'svnmucc' (r1339428)
+ * provide the repository name to mailer.py (r1439592)
+ * add '--force-interactive' to svnmucc (r1457789)
+ * add '--trust-server-cert' to svnmucc (r1458995)
+
+ Developer-visible changes:
+ - General:
+ * now require Python 2.5 for tests and dev tools (r1243627)
+ * now require bzip2 for tests and dev tools (r1148512)
+ * configure defaults to --without-apache-libexecdir (r1469862)
+ * support builds with APR pool debugging (r1176894)
+ * 'make extraclean' is more thorough now (r1149460)
+ * support for Serf 2 (r1147538)
+ * introduction of editor v2 (via private APIs only) (r1166332 et al)
+ * improve SQLite setup for compatibility with OS X 10.7. (r1181666)
+ * rework switch statement to accomodate OWC compiler limitations (r1204407)
+ * new --enable-sqlite-compatibility-version configure option (r1201421)
+ * make test suite LD_LIBRARY_PATH include just-built auth plugins (r1200474)
+ * packages/ directory removed, contents were outdated and unused (r1442167)
+ * rename 'makefile.ezt' to 'build-outputs.mk.ezt' (r1444822)
+ * use expensive compiler optimizations with --enable-optimize (r1445063)
+ * in Visual C++ builds, move temp files to different directory (r1446416)
+ * remove --with-ssl and --with-gssapi configure options (r1449023)
+ * require at least serf 1.2.0 as build dependency (issue #4296)
+ * fix error tracing to record file/line properly (r1331242)
+ * add --log-level argument to win-tests.py (r1335461)
+ * improve GDB pretty-printing of svn types (r1351336, r1364750, r1365035)
+ * load third-party FS modules (if --enable-runtime-module-search) (r1362434)
+ * enable running the regression tests over https (r1349699)
+ * support 'make davautocheck' on OS X (r1421583)
+ * new '--enable-gcov' configure option (r1416646)
+ * fix build with Apache HTTPD 2.5 (r1408985)
+ * allow running the test suite through a http proxy (r1410195)
+ * don't use non-constant initializers in struct variables (r1412911)
+ * allow generation of Visual Studio 2012 compatible projects (r1245152)
+ * nicer pretty-printing of Subversion data types in gdb (r1367262 et al)
+ * teach serf build on Windows to use static APR/Util and OpenSSL (r1371338)
+ * add --ssl-cert option to win-tests.py to run tests over https (r1372760)
+ * don't strip Content-Type header form .po files on Windows (r1380056)
+ * configure now script auto-detects GNOME keyring (r1387230)
+ * allow configure to detect BDB on Debian-based Linux distros (r1390633)
+ * auto-detect serf via pkg-config (r1391662)
+ * improve queries for compatability with SQLite 3.7.16 (r1455239)
+ * remove support for in-tree apr, apr-util and apr-memcache (r1456924)
+ * FSFS caching supports prefixes now (r1462436)
+ * maintainer mode now prints symbolic error codes (r1465157)
+ * don't require NLS support for kwallet support (r1466445)
+ * make Julian happy (r1413030)
+
+ - API changes:
+ * fix inconsistent handling of log revs without changed paths (issue #3694)
+ * deprecated SVN_ERR_SQLITE_UNSUPPORTED_SCHEMA (r1173240)
+ * provide API to clear cached auth credentials (issue #2775)
+ * improve repository location information in various APIs (issue #4170)
+ * major rewrite of conflict storage and handling APIs (r1354973 et al)
+ * hide (deprecate) svn_wc APIs that use editors (r1243339)
+ * svn_stringbuf_ensure() allocates an extra byte for terminator (r1308966)
+ * switch and update apis are now more consistent (r1465292)
+ * deprecated svn_client_merge_reintegrate (r1466742)
+ * deprecated low level ra_svn apis (r1466907)
+
+ - Bindings:
+ * star-imports in swig-py only import 'svn_*' symbols (r1303375)
+ * fix compilation of Perl bindings on Mandriva 2007 (issue #2617)
+ * new JavaHL testing targets (r1182983)
+ * enable returning an error on malfunctions for JavaHL (r1366215)
+ * MacOS X build fix to cope with missing GNOME keyring (r1397844)
+ * fix swig bindings tests on MacOS X (r1397846)
+ * fix assertion failure in JavaHL error reporting (r1405922)
+ * support ruby 1.9 (r1407206)
+ * JavaHL: Include OSGI Manifest information in svn-javahl.jar (r1234864)
+ * new svn_auth_set_gnome_keyring_unlock_prompt_func function (r1241554)
+ * fix svn_txdelta window ops for python bindings (r1389054)
+ * fix build of Perl bindings with newer versions of SWIG (r1389658)
+ * add missing API functions to Perl bindings (issue #2646)
+ * add missing API functions to Python bindings (r1392038 et al)
+ * add missing API functions to JavaHL bindings (issue #4326)
+ * fix some reference counting bugs in swig-py bindings (r1464899, r1466524)
+
+
+Version 1.7.10
+(30 May 2013, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.10
+
+ User-visible changes:
+ - Client-side bugfixes:
+ * fix 'svn revert' "no such table: revert_list" spurious error (issue #4168)
+ * fix 'svn diff' doesn't show some locally added files (issue #3797)
+ * fix changelist filtering when --changelist values aren't UTF8 (r1475724)
+ * fix 'svn diff --git' shows wrong copyfrom (issue #4294)
+ * fix 'svn diff -x-w' shows wrong changes (issues #4133 and #4270, r1427278)
+ * fix 'svn blame' sometimes shows every line as modified (issue #4034)
+ * fix regression in 'svn status -u' output for externals (r1434750)
+ * fix file permissions change on commit of file with keywords (issue #4331)
+ * improve some fatal error messages (r1465975)
+ * fix externals not removed when working copy is made shallow (issue #3741)
+
+ - Server-side bugfixes:
+ * fix FSFS repository corruption due to newline in filename (issue #4340)
+ * fix svnserve exiting when a client connection is aborted (r1482759)
+ * fix svnserve memory use after clear (issue #4365)
+ * fix repository corruption on power/disk failure on Windows (r1483781)
+
+ Developer-visible changes
+ - General:
+ * make get-deps.sh compatible with Solaris /bin/sh (r1451678)
+ * fix infinite recursion bug in get-deps.sh (r1421541, r1424977)
+ * fix uninitialised output parameter of svn_fs_commit_txn() (r1461743)
+
+ - Bindings:
+ * fix JavaHL thread-safety bug (r1476359)
+
+
+Version 1.7.9
+(04 Apr 2013, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.9
+ User-visible changes
+ - Client-side bugfixes:
+ * improved error messages about svn:date and svn:author props. (r1440620)
+ * fix local_relpath assertion (issue #4257)
+ * fix memory leak in `svn log` over svn:// (r1458341)
+ * fix incorrect authz failure when using neon http library (issue #4332)
+ * fix segfault when using kwallet (r1421103)
+
+ - Server-side bugfixes:
+ * svnserve will log the replayed rev not the low-water rev. (r1461278)
+ * mod_dav_svn will omit some property values for activity urls (r1453780)
+ * fix an assertion in mod_dav_svn when acting as a proxy on / (issue #4272)
+ * improve memory usage when committing properties in mod_dav_svn (r1443929)
+ * fix svnrdump to load dump files with non-LF line endings (issue #4263)
+ * fix assertion when rep-cache is inaccessible (r1422100)
+ * improved logic in mod_dav_svn's implementation of lock. (r1455352)
+ * avoid executing unnecessary code in log with limit (r1459599)
+
+ Developer-visible changes:
+ - General:
+ * fix an assertion in dav_svn_get_repos_path() on Windows (r1425368)
+ * fix get-deps.sh to correctly download zlib (r13520131)
+ * doxygen docs will now ignore prefixes when producing the index (r1429201)
+ * fix get-deps.sh on freebsd (r1423646)
+
+ - Bindings:
+ * javahl status api now respects the ignoreExternals boolean (r1435361)
+
+
+Version 1.7.8
+(17 Dec 2012, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.8
+ User-visible changes
+ - Client- and server-side bugfixes:
+ * Fix typos in pt_BR, es and zh_TW translations (r1402417, r1402421)
+
+ - Client-side bugfixes:
+ * fix crash with --username option on Windows (r1396285)
+ * add missing attributes to "svn log -v --xml" output (r1398100)
+ * fix svn patch ignoring hunks after no trailing newline (r1399174)
+ * fix hang with ra_serf during error processing (r1403583)
+ * ignore file externals with mergeinfo when merging (r1401915)
+ * fix "svnmucc cp" segfault with a missing last argument (issue #4079)
+ * fix conflict handling on symlinks (issue #4091)
+
+ - Server-side bugfixes:
+ * properly detect threading availability (r1398325)
+ * fix "svnadmin load --bypass-prop-validation" (r1237779)
+ * fix parsing of [groupsfoo] sections in authz file (issue #3531)
+ * add Vary: header to GET responses to improve cacheability (r1390653)
+ * fix fs_fs to cleanup after failed rep transmission (r1403964, et al)
+ * fix mod_dav_svn to complain about revisions > HEAD (r1403588)
+
+ Developer-visible changes:
+ - General:
+ * fix incorrect status returned by 1.6 API (r1403258)
+ * fix compilation with g++ 4.7 (r1345740)
+ * fix svn_uri_get_file_url_from_dirent on Windows (r1409146)
+
+
+Version 1.7.7
+(09 Oct 2012, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.7
+ User-visible changes
+ - Client- and server-side bugfixes:
+ * fix memory read bug (r137614)
+ * update Chinese translation
+
+ - Client-side bugfixes:
+ * fix issues with applying Git patch files (r1374800, et al)
+ * fix status does not descend into dir externals after upgrade (issue #4016)
+ * fix file externals don't update with old mod_dav_svn (issue #4224)
+ * fix external diff tool duplicates Index: lines with 'svn diff' (r1380697)
+ * fix GNOME keyring library fails with very old glib (r1378847)
+ * fix unknown password stores in config file cause error (r1375052)
+ * fix assertions in ra_serf running against server root (r1365519, et al)
+ * fix ra_serf checkout/export aborts early on Windows (issue #4174)
+
+ - Server-side bugfixes:
+ * fix an assert with SVNAutoVersioning in mod_dav_svn (issue #4231)
+ * fix unbounded memory use with SVNPathAuthz short_circuit (r1387943)
+ * fix svndumpfilter exclude --targets requires leading slash (issue #4234)
+ * fix connection ttl for memcache should be 50 seconds (r1391641)
+ * stabilize order of paths in dumpfiles with APR 1.4.6 (r1344864, et al)
+
+ Developer-visible changes:
+ - General:
+ * print "All tests successful" at the end of 'make check' (r1375089)
+ * fix sandbox violation in a test (r1371282)
+ * fix tests fail when running within a format 30 WC (r1391188, et al)
+ * fix return value of svn_client_update4() incorrect (r1380295)
+ * fix make check summary missing test failures (r1390965)
+ * fix build does not fail when apache httpd is not available (r1374198)
+
+ - Bindings:
+ * fix swig-pl build fails with swig 2.0.7 and newer. (r1389658)
+ * fix swig-py runtime problems with swig 2.0.5 and newer (r1351117)
+
+
+Version 1.7.6
+(15 Aug 2012, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.6
+
+ User-visible changes:
+ - Client- and server-side bugfixes:
+
+ - Client-side bugfixes:
+ * Fix "svn status -u --depth empty FILE" (r1348822, r1349215)
+ * Fix example output of 'svn help status' (issue #3962)
+ * propset of svn:eol-style might not notice related text changes (r1353572)
+ * sort output of 'svn propget -R' (r1355699)
+ * sort output of 'svn proplist' (r1355698)
+ * sort output of 'svn status' (r1341012)
+ * avoid a filestat per working copy find operation (r1340556)
+ * optimize 'svn upgrade' performance on large working copies (r1342984)
+ * allow 'file:///C:\repos' style arguments on Windows, like 1.6 (r1346765)
+ * fix ra_serf against Subversion 1.2 servers (r1349367)
+ * fix 'svn upgrade' on working copies with certain tree conflicts (r1345482)
+ * avoid workqueue references to system temp dir (r1367854)
+ * allow non-existant canonical paths (r1367853)
+ * fix 'svn revert --depth files' to operate on files (r1365554)
+ * fix ra_serf XML namespace handling against malicious server (r1337441)
+ * fix relocate with server-relative externals (issue 4216)
+ * change two asserts into errors for TortoiseSVN (r1368128, r1368065)
+ * don't attempt to anchor an operation outside a wc root (r1361341)
+
+ - Server-side bugfixes:
+ * partial sync drops properties when converting to adds (issue #4184)
+ * replaying a copy and delete of an unreadable child fails (issue #4121)
+ * allow svnlook to operate on r0 (r1362508)
+ * make FSFS revision files independent of APR hash order (r1367498)
+
+ - Other tool improvements and bugfixes:
+ * move mod_dontdothat to install-tools (r1307177)
+
+ Developer-visible changes:
+ - General:
+ * fix running tests against httpd 2.4 (r1291594)
+ * use constant struct initialisers for C89 compatibility (r1352068)
+
+ - Bindings:
+ * JavaHL: Don't assert on some invalid input (r1354626, r1354652)
+ * JavaHL: Add missing new in 1.7 notifications (r1351772)
+
+
+Version 1.7.5
+(17 May 2012, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.5
+
+ User-visible changes:
+ - Client- and server-side bugfixes:
+ * http: report deleted-revision upon delete during update (r1327474)
+
+ - Client-side bugfixes:
+ * avoid potential segfault when canonicalizing properties (r1296369)
+ * improve memory and file-handle management with externals (issue #4130)
+ * serf: convert assertions to "MERGE failed" errors (r1302417)
+ * fix undefined behaviour during multi-segment reverse merges (issue #4144)
+ * fix potential use of already freed memory during diff operation (r1311935)
+ * improve performance of scan for the working copy root (r1306334)
+ * cmdline: fix segfault during 'svn diff' argument processing (r1311702)
+ * fix regression from 1.6 in update with --depth option (issue #4136)
+ * propset: avoid undefined behaviour in error path (r1325361)
+ * reset sqlite statements, partly for sqlite-3.7.11 compat (r1328846, et al)
+ * fix assertion during 'svn diff -r BASE:HEAD ^/trunk' (issue #4161)
+ * notify upon 'update' just removing locks on files (r1329876)
+ * neon: fix potential use of freed memory during commits (r1329388)
+ * 'status --xml' doesn't show repository deletes correctly (issue #4167)
+ * fix assert on svn:externals with drive letter on Windows (issue #4073)
+ * fix 'svn update --depth=empty' against 1.4 servers (issue #4046)
+ * handle missing svn:date reported by svnserve gracefully (r1306111)
+ * fix merges which first add a subtree and then delete it (issue #4166)
+ * fix a regression with checkout of file externals (issue #4087)
+ * don't add spurious mergeinfo to subtrees in edge-case merge (issue #4169)
+ * improve performance of status on large working copies (issue #4178)
+
+ - Server-side bugfixes:
+ * fix non-fatal FSFS corruption bug with concurrent commits (issue #4129)
+ * mod_dav_svn: raise an error on MERGE of non-existent resource (r1298343)
+ * mod_dav_svn: support compiling/running under httpd-2.4 (r1232267)
+ * mod_dav_svn: forbid BDB repositories under httpd's event MPM (issue #4157)
+
+ - Other tool improvements and bugfixes:
+ * emacs support: updates to dsvn.el and vc-svn.el (r1200896, et al)
+
+ Developer-visible changes:
+ - General:
+ * windows example distribution scripts: include svnrdump (r1295007)
+ * fix running the test suite with jsvn (r1335555)
+
+ - Bindings:
+ * swig-py tests: avoid FAILs on APR hash order (r1296137, r1292248)
+ * swig-rb tests: avoid FAILs on APR hash order (r1310535, r1310594)
+ * swig-pl: Improved perl detection in gen-make.py (r1291797, r1291810)
+
+
+Version 1.7.4
+(08 Mar 2012, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.4
+
+ User-visible changes:
+ * fix 'svn log --diff' for moved paths (r1210147, et al)
+ * fix ra_serf problem with reading directory entries via HTTPv2 (r1238121)
+ * prepend "sqlite:" to error messages from SQLite (r1245738, -817)
+ * fix randomly missing "Merged via" notifications in 'svn log -g' (r1293229)
+ * fix spurious conflict when merging deleted symbolic link (issue #4052)
+ * fix URL-to-WC copy of externals on Windows (issue #4123)
+ * improve an FSFS sanity-check error message (r1294470)
+ * fix regressions with symlinks pointing at externals (issue #4102)
+ * fix 'svn log --diff' output ordering issue on Windows (r1295671)
+
+ Developer-visible changes:
+ * don't build mod_dontdothat if not building with httpd (r1243976)
+ * fix the testsuite to avoid FAILs on APR hash order (r1230714, et al)
+
+
+Version 1.7.3
+(14 Feb 2012, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.3
+
+ General:
+ * ship mod_dontdothat with the standard release
+
+ User-visible changes:
+ * fix segfault on 'svn rm $ROOT_URL' (issue #4074)
+ * replace a couple of assertions in favor of errors (r1207858, -949)
+ * fix a server assert after being upgraded (r1210195)
+ * fix segfault on 'svn mkdir svn://localhost' (r1211483)
+ * make 'svnadmin recover' prune the rep cache (r1213331, et al)
+ * make svnmucc use values from --config-dir option
+ * update and clarify the merge help text (r1154121, et al)
+ * replace wc assertion with informative error (r1222521, -693)
+ * copy permissions correctly for FSFS dirs (r1229252)
+ * improve 'svn log --with-all-revprops' over ra-dav (issue #4082)
+ * fix segfault when remapping a file external (issue #4093)
+ * fix segfault caused by obstructing unversioned dir (r1229677)
+ * fix regression on first update of external dir with '-r' (issue #4053)
+ * fix various EOL-handling problems in 'svn patch' (issues #3814, #3991)
+ * fix segfault in 'svn revert' (r1229303)
+ * improve correctness of 'svn patch --dry-run' (r1231944, -5)
+ * enforce revisions given in 'svn:externals' (issue #4053)
+ * fix potential corruption on 32-bit FSFS with large files (r1230212)
+ * make 'svn status --xml' show new files (issue #4097)
+ * fix 'svn mergeinfo' correctness (issue #4050)
+ * return the correct status for non-present nodes (r1232202, -07, -21, -22)
+ * improve SASL error messages (r1236343, et al)
+ * improve server cert error code for ra_serf (r1232413)
+ * fix SVNParentPath listings for parent path symlinks (r1221767, -80)
+ * fix mod_dav_svn's handling of POST errors (issue #4086)
+ * log some mod_dav_svn errors, rather than ignoring them (r1237720, -9596)
+ * relax requirements for canonicalization in mod_dav_svn (r1236173)
+ * fix a rare source of FSFS corruption (r1240752)
+ * allow committing the result of some copy operations (issue #4059)
+ * prevent one-byte buffer overflow in base64 decoding (r1242337)
+
+ Developer-visible changes:
+ * JavaHL: Add missing notify action, fixing an exception (r1221793)
+ * fix swig-py memory leak (r1235264, -296, -302, -736)
+ * fix spurious test suite failure (r1220742, -50)
+ * allow running tests on UNC shares (r1225491)
+ * bindings: see platform-specific password providers (r1242660, -1)
+ * skip 'svnrdump dump' tests over ra_serf (r1242537)
+ * convert a few ra_serf assertions to errors (r1242607)
+
+
+Version 1.7.2
+(02 Dec 2011, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.2
+
+ User-visible changes:
+ * fix working copy corruption after interrupted update/switch (issue #4040)
+ * avoid segfaults against pre-1.5 servers (r1186928)
+ * improve configure error message if apr-util uses old or no bdb (r1186784)
+ * make 'svn patch' ignore '/dev/null' targets for compat with git (r1197998)
+ * fix 'svn patch' segfault on patch that skips and deletes files (r1199950)
+ * omit "Committed revision N." output from 'svn commit --quiet' (r1200837)
+ * fix authz denial when svnserve root is a repository (issue #4060)
+ * fix uninitialized memory read in client diff code (r1201002)
+ * avoid potential segfault during merges (r1202807)
+ * fix an assertion failure when a symlink is updated (r1186944, -81, -83)
+ * make working copy operations fail if nodes have no base checksum (r1202630)
+ * fix nested <Location>s when using v2 protocol (r1203546, -651, -653)
+ * make mod_dav_svn ignore non-Subversion POST requests (r1187695)
+ * avoid reading freed memory (r1204478)
+ * recognize empty (only byte order mark) UTF-8 files as text (issue #4064)
+ * fix 1.7 client regression when operating against a 1.0.x server (r1199876)
+ * remove empty parent dirs of removed externals on update (issue #4044)
+ * make 'svn diff -c N' work for files added in rN (issue #2873)
+ * plug a memory leak in the bdb backend (r1205726)
+ * fix 'svn import' with native eol-style and inconsistent EOLs (r1205193)
+ * fix reading beyond the end of a string in bdb backend (r1205839, -48)
+ * don't assert when committing an incomplete directory (issue #4042)
+
+ Developer-visible changes:
+ * JavaHL: allow 'status -u' to function properly (r1189190, -395)
+ * don't put '\r' characters in our generate sql headers (r1189580)
+ * properly define WIN64 on Windows x64 builds (r1188609)
+ * better adherence to C89 in enum definitions (r1189665)
+ * bump copyright year in Windows DLLs (r1189261)
+ * log a better error when opening rep-cache.db fails (r1204610, -73)
+
+
+Version 1.7.1
+(24 Oct 2011, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.1
+
+ User-visible changes:
+ * improve performance of 'svn info' (r1164386)
+ * improve hash table sorting performance (r1167659)
+ * update bash completion for 1.7 (r1177001)
+ * make 'svn ls' continue to work with 1.0-1.3 repos (r1154278, -379, -82)
+ * improve handling of error messages generated by Cyrus SASL (r1179767)
+ * update INSTALL documentation file (r1182115, and others)
+ * error instead of assert when upgrading corrupt WCs (r1182904, -9)
+ * improve memory usage in merge (r1176915)
+ * fix an invalid assertion in merge (r1149103, -35)
+ * improve performance of 'merge --reintegrate' in edge-case (r1167681)
+ * fixed: 'svn mergeinfo' shows wrong revisions for added nodes (issue #3791)
+ * make 'svn add --parents D/file' work if D is deleted (r1185222)
+ * improve performance of trivial text file merges (issue #4009)
+ * add FSFS sanity check to prevent corruption seen in the wild (r1178280)
+ * improve correctness/performance of recursive info and proplist (r1164426)
+ * fix memory leak in 'merge --reintegrate' (r1180154)
+ * fix handling of directories after 'update --set-depth=empty' (r1185911)
+ * fix 'checksum != NULL' assertions in some upgraded WCs (r1177732)
+ * fix upgrading of WCs containing authz-restricted dirs (r1185738)
+ * make the server tolerate svn:mergeinfo with malformed paths (r1182771)
+ * fix some erroneous warning messages from the svn client (r1185746)
+ * fix WC upgrade with replaced nodes in edge-case (issue #4033)
+
+ Developer-visible changes:
+ * fix object lifetime issues in the JavaHL bindings (r1175888)
+ * fix org.tigris JavaHL wrappers to avoid double finalize (r1179680)
+ * don't write to const memory (r1177492)
+ * improve zlib configuration (r1174761, -98, -806)
+ * improve SQLite runtime init for OS X 10.7 compat (r1181666)
+ * improve test suite correctness (r1174111)
+ * fix potential segfault seen by TSVN (r1183263)
+ * fix backward compat crashes in JavaHL (r1183054, -347)
+ * fill in repos_* fields of svn_wc_status3_t for repos-only nodes (r1181609)
+ * disable the SQLite shared process cache (r1185242, r1185280)
+
+
+Version 1.7.0
+(11 Oct 2011, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.0
+
+See the 1.7 release notes for a more verbose overview of the changes since
+the 1.6 release: http://subversion.apache.org/docs/release-notes/1.7.html
+
+ User-visible changes:
+ - General:
+ * No longer including contrib/ in the release tarballs (r877798)
+
+ - Major new features:
+ * Less verbose HTTP-based repository access protocol (issue #1161, #3371)
+ * Rewritten working copy metadata storage (issue #3357)
+ * New 'svn patch' subcommand (issue #511)
+ * Rewritten FSFS in-memory caching for better performance
+ * New remote repository dump/load client 'svnrdump'
+
+ - Minor new features and improvements:
+ * Better handling of HTTP redirects (issue #2779)
+ * Improved and much more consistent path handling (issue #2028, and others)
+ * 'svnadmin load' rewrites changed revnums in mergeinfo (issue #3020)
+ * Error message and help text improvements
+ * 'svn log' can print unidiff of changes made in a revision (issue #2909)
+ * 'svn diff' can print git-style unidiff annotations
+ * svnsync can now steal locks on a mirror repository (issue #3309)
+ * display the wc root in the output of 'svn info' (issue #3355)
+ * add 'svnlook filesize' (issue #3509)
+ * add 'svn upgrade' command for upgrading working copies (r877675)
+ * add 'svnsync --disable-locking' (issue #3545)
+ * subtree merges don't unconditionally stop reintegrate merge (issue #3577)
+ * 'svn relocate' replaces 'svn switch --relocate' (r1026475)
+ * 'svn relocate' updates relative externals (issue #3597)
+ * allow svnsync users to specify the source repo (issue #3637)
+ * remove redundant mergeinfo notifications for 2-URL merges (issue #3671)
+ * 'svn export' into the current directory (issue #3727)
+ * added '--parents' to 'svn update' (issue #3748)
+ * allow configurable connection timeout in ra_serf (r876161)
+ * add digest authentication in ra_serf (r876405)
+ * add extensive caching support to servers (r1067669, -75, -72302)
+ * add configurable caching to svnadmin (r1078357)
+ * make server-side network data compression rate configurable (r1072288)
+ * added support for auto-detecting mime-types with libmagic (r1131120)
+ * 'svn rm url1 url2 url3' uses single txn per repo (issue #1199)
+ * don't leave unversioned files when reverting copies (issue #3101)
+
+ - Client-side bugfixes:
+ * 'svn cp A B; svn mv B C' is equivalent to 'svn cp A C' (issue #756)
+ * revert fetches missing directories from the server (issue #1040)
+ * allow subdirs of moved dirs to be moved and committed (issue #1259)
+ * improved performance of 'svn mv' with whole directories (issue #1284)
+ * 'svn rm B; svn cp A B' now works (issue #1516)
+ * 'svn diff URL1 URL2' now reverse of 'svn diff URL2 URL1' (issue #2333)
+ * error if relocating to an unused URL (issue #2531)
+ * 'svn blame -rWORKING' is now supported (issue #2544)
+ * improve correctness of commit on a relocated wc over ra_dav (issue #2578)
+ * add early error to 'svn add --auto-props' with mixed eols (issue #2713)
+ * allow 'svn diff' to accept symlinks as targets (issue #2716)
+ * don't lose props for replaced items (issue #2743)
+ * handle mergeinfo for subtrees removed outside of svn (issue #2915)
+ * add ability to force 'svn diff' to use internal diff (issue #3701)
+ * correctly recover a schedule-for-delete rm'd outside of svn (issue #3106)
+ * don't create self-referential mergeinfo from own history (issue #3157)
+ * improve 'svn log -g' handling of bad mergeinfo source paths (issue #3270)
+ * better conflict stat printing (issue #3342, issue #3594)
+ * 'svn update' restores excluded files (issue #3544)
+ * allow reintegrate merges into WCs with missing subtrees (issue #3603)
+ * more gracefully error when given back cmdline input (issue #3620)
+ * update exit codes to reflect command failure (issue #3622)
+ * don't double-update file externals (issue #3665)
+ * improve output of multi-target update (issue #3693, #3746)
+ * make 'svn up --set-depth=exclude FILE' work (issue #3736)
+ * return correct error code for 'svn cat' on nonexisting file (issue #3713)
+ * support svn:externals on locally added directories (issue #2267)
+ * use installed GSSAPI lib for Kerberos in ra_serf (r877381)
+ * allow 'svn info' to run on an excluded item (issue #3792)
+ * improve 'log -g' output with reverse merges (issue #3176)
+ * don't print error message if stdout is a pipe and is closed (issue #3014)
+ * removed special copy-handling during updates added in 1.5.0 (issue #3711)
+ * fix warning about copies committed with non-infinity depth (issue #3752)
+ * can now commit multiple wc paths lacking a common parent (issue #2381)
+ * 'svn export --depth $WC' now works correctly (issue #3800)
+ * added support for case-only renames on Windows (issue #3702)
+ * 'svn delete --force' removes tree conflicts (issue #3805)
+ * don't throw an error when skipping tree conflicts in update (issue #3329)
+ * don't break commits of wc->wc copies with file externals (issue #3589)
+ * allow 'svn info' to work on symlinks to working copies (issue #2305)
+ * allow 'svn st --show-updates' to work across symlinks (issue #3117)
+ * 'svn revert' shouldn't loop on symlinks (issue #3972)
+ * fixed: wc-to-wc copy of a switch source (issue #1802)
+ * fixed: 'svn st' reports symlinks as obstructed items (issue #2284)
+ * fixed: 'cd e:\; svn up e:\' fails (issue #2556)
+ * fixed: svn aborts on commiting from root dir on windows (issue #3346)
+ * fixed: removing a dir scheduled for deletion corrupts wc (issue #2741)
+ * fixed: 'svn cleanup' fails on obstructed paths (issue #2867)
+ * fixed: case-only renames resulting from merges don't work (issue #3115)
+ * fixed: 'svn mergeinfo' ignores peg rev for wc target (issue #3180)
+ * fixed: unable to merge to wc of deleted branch (issue #3221)
+ * fixed: move via merge leaves behind versioned move source (issue #3324)
+ * fixed: ra_serf does not honor http-proxy-exceptions (issue #3428)
+ * fixed: 'svn mv A B; svn mv B A' loses history (issue #3429)
+ * fixed: ra_serf doesn't support http-auth-types config (issue #3435)
+ * fixed: merge sets incorrect mergeinfo on skipped paths (issue #3440)
+ * fixed: ra_serf inconsistent handling of cached authn creds (issue #3450)
+ * fixed: ra_serf sefault with using NTLM or Negotiate auth (r876910)
+ * fixed: excluded subtrees are not detected by svnversion (issue #3461)
+ * fixed: submitting a changelist while obstructed item exists (issue #3484)
+ * fixed: crash when changing an external's URL (issue #3530)
+ * fixed: target moved after branching breaks reintegrate (issue #3640)
+ * fixed: potential race condition in svnsync (issue #3546)
+ * fixed: spurious merge conflicts with pre-1.7 mod_dav_svn (issue #3657)
+ * fixed: repeat merge is not a no-op (issue #3564)
+ * fixed: inheritance results in self-referential mergeinfo (issue #3668)
+ * fixed: inheritance results in nonexistent mergeinfo sources (issue #3669)
+ * fixed: memory leaks in ra_serf (issue #3684)
+ * fixed: corruption of 'svn pg' output for large properties (issue #3721)
+ * fixed: 'svnsync copy-revprops' doesn't sync revprop dels (issue #3728)
+ * fixed: executable flag not correctly set on merge (issue #3686)
+ * fixed: 'svn rm' fails on multiple URLs with encoded spaces (issue #3839)
+ * fixed: children of replaced dirs cannot be deleted (issue #3468)
+ * fixed: executable flag of binary file lost during merge (issue #3686)
+ * fixed: merging a symlink-turned-regular-file breaks the wc (issue #2530)
+ * fixed: can't remove file externals (issue #3351)
+ * fixed: 'svn unlock' attempts to unlock wrong token on DAV (issue #3794)
+ * fixed: forced DAV 'svn unlock' results in 403, not warning (issue #3801)
+ * fixed: rm -> ci -> cp = missing directory (issue #2763)
+ * fixed: 'svn info' returns parent info on missing dirs (issue #3178)
+ * fixed: spurious prop conflict with 'merge --reintegrate' (issue #3919)
+ * fixed: 'svn --version' fails with non-existant $HOME (issue #3947)
+ * fixed: unforced export silently overwites existing file (issue #3799)
+ * fixed: reverse merge which adds subtree mergeinfo fails (issue #3978)
+ * fixed: 'svn up -r{R>HEAD}' hangs client over ra_svn (issue #3963)
+ * fixed: 'svn up' updates file externals in target siblings (issue #3819)
+ * many other minor bugfixes, optimizations, plugs of memory leaks, etc
+
+ - Server-side bugfixes:
+ * mod_dav_svn is less strict about auto-merging for commits (issue #1704)
+ * allow SVNListParentPath to be used with authz (issue #2753)
+ * allow nav to repo list from repo top with SVNListParentPath (issue #3159)
+ * allow repositories in the root of a drive on windows (issue #3535)
+ * don't destroy mergeinfo with 'svnadmin load --parent-dir' (issue #3547)
+ * fixed: 'svnadmin hotcopy' does not duplicate symlinks (issue #2591)
+ * fixed: post-revprop-change errors cancel commit (issue #2990)
+ * fixed: mod_dav_svn runs pre-revprop-change hook twice (issue #3085)
+ * fixed: mod_dav_svn doesn't return stderr to user on failure (issue #3112)
+ * fixed: hotcopy may corrupt target rep-cache.db (issue #3596)
+ * fixed: mod_dav_svn can cause spurious merge conflicts (issue #3657)
+ * fixed: DAV can overwrite directories during copy (issue #3314)
+ * fixed: 'svn log' returns log of unrelated path (issue #3931)
+ * match paths against authz rules in case sensitive way (issue #3781)
+ * svnserve can now force usernames to upper/lower case (issue #3726)
+ * reduce duplicate log messages in 'log -g' (issue #3650)
+ * svnserve: don't crash on shutdown with SASL in inetd mode (issue #3664)
+ * disallow arbitrary HTTP headers from committers (issue #2872)
+ * limit FSFS memory consumption (issue #3478, #3593)
+ * many other minor bugfixes too numerous to list here
+
+ - Other tool improvements and bugfixes:
+ * svnsync now takes the '--config-option' argument (issue #2027)
+ * svnsync can translate non-UTF-8 properties to UTF-8 (issue #3817)
+ * svnadmin now errors on non-UTF-8 revision properties (issue #3755)
+ * svnadmin verify now errors on non-UTF-8 paths (r1129641)
+
+ Developer-visible changes:
+ - General:
+ * improved output of 'make check'
+ * introduce scratch_pool/result_pool parameter paradigm
+ * improved error tracing (r877208, -736)
+ * improve building with sqlite on Windows (issue #3364)
+ * allow mod_dav_svn to compile against Apache 2.4 (issue #3548)
+ * support running tests against older servers (r876016)
+ * notification of unversioned obstructions (r877344)
+ * removed virtually all abort() calls (issue #2780)
+ * don't include client-specific suggestions in error msgs (issue #3887)
+
+ - API changes:
+ * don't crash svn_client_copy if ctx->log_msg_func is NULL (issue #3234)
+ * much improved ra_serf error handling (issue #3375)
+ * provide clients with old and new revision on update (r876515)
+ * close both files, even on error in svn_stream_copy3() (r887262)
+ * added 'work-in-progress' XFail test status (r876549)
+ * notifications sent when mergeinfo changes (r877588)
+ * add information on text and property mods in log APIs (r877688)
+ * fixed: svn_ra_local__get_file() leaks file descriptors (issue #3290)
+ * svn_ra_neon__get_dir() returns correct dir set for URLs (issue #3093)
+ * swig-py: always set ChangedPath.path (also for deletes) (issue #2630)
+ * improve conflict resolver API for a specific direction (issue #3049)
+
+ - Bindings:
+ * New JavaHL package: org.apache.subversion
+ * Deprecate the SVNClientSynchronized class in JavaHL (issue #2755)
+ * fixed setting binary properties in JavaHL (issue #3770)
+ * fix type mapping of svn_txdelta_window_t in python bindings (issue #3688)
+
+
+Version 1.6.23
+(30 May 2013, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.23
+
+ User-visible changes
+ - Server-side bugfixes:
+ * fix FSFS repository corruption due to newline in filename (issue #4340)
+ * fix svnserve exiting when a client connection is aborted (r1482759)
+
+ - Other tool improvements and bugfixes:
+ * fix argument processing in contrib hook scripts (r1485350)
+
+
+Version 1.6.22
+(Not released, see changes for 1.6.23.)
+
+
+Version 1.6.21
+(04 Apr 2013, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.21
+
+ User-visible changes
+ - Server-side bugfixes:
+ * mod_dav_svn will omit some property values for activity urls (r1453780)
+ * improve memory usage when committing properties in mod_dav_svn (r1443929)
+ * fix mod_dav_svn runs pre-revprop-change twice (issue #3085)
+ * fixed: post-revprop-change errors cancel commit (issue #2990)
+ * improved logic in mod_dav_svn's implementation of lock. (r1455352)
+
+ Developer-visible changes:
+ - General:
+ * fix a compatibility issue with g++ 4.7 (r1345740)
+
+
+Version 1.6.20
+(04 Jan 2013, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.20
+
+ User-visible changes
+ - Client- and server-side bugfixes:
+ * Fix typos in pt_BR, es and zh_TW translations (r1402417)
+
+ - Server-side bugfixes:
+ * add Vary: header to GET responses to improve cacheability (r1390653)
+ * fix fs_fs to cleanup after failed rep transmission (r1403964, et al)
+ * fix an assert with SVNAutoVersioning in mod_dav_svn (issue #4231)
+
+
+Version 1.6.19
+(10 Sep 2012, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.19
+
+ - Client-side bugfixes:
+ * handle missing svn:date reported by svnserve gracefully (r1306111)
+
+ - Server-side bugfixes:
+ * fix possible server hang if a hook script fails to start (r1330410)
+ * fix write-through proxy commit regression introduced in 1.6.17 (r1088602)
+ * partial sync drops properties when converting to adds (issue #4184)
+
+ - Developer-visible changes:
+ * fix the testsuite to avoid FAILs on APR hash order (r1230714, et al)
+
+
+Version 1.6.18
+(29 Mar 2012, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.18
+
+ User-visible changes:
+ * reject invalid svn:mergeinfo at commit time over DAV (issue #3953)
+ * fix reintegrate merge regression introduced in 1.6.13 (issue #3957)
+ * make the stderr output of the post-commit hook XML-safe (r893478)
+ * fix a rare source of FSFS corruption (r1240752)
+ * plug a memory leak in the bdb backend (r1205726)
+ * server-side performance fix for "log -g" (r1152282)
+ * fix description of svndumpfilter's --targets option (r1151911)
+ * fix datastream corruption during resumed transfer in ra_serf (r1154733)
+ * fix a crash in ra_svn SASL authentication (r1166555, -678)
+ * fix potential corruption on 32-bit FSFS with large files (r1230212)
+ * make website links point to subversion.apache.org (r896893, -901, r915036)
+ * fix non-fatal FSFS corruption bug with concurrent commits (issue #4129)
+
+ Developer-visible changes:
+ * fix sqlite distfile retrieval in get-deps.sh (r1134734)
+ * fix swig-py memory leak (r1235264, -296, -302, -736)
+ * allow passing --with-jdk to gen-make.py on Windows (r966167)
+
+
+Version 1.6.17
+(01 Jun 2011, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.17
+
+ User-visible changes:
+ * improve checkout speed on Windows (issue #3719)
+ * make 'blame -g' more efficient with large mergeinfo (r1094692)
+ * avoid some invalid handle exceptions on Windows (r1095654)
+ * preserve log message with a non-zero editor exit (r1072084)
+ * fix FSFS cache performance on 64-bit platforms (r1103665)
+ * make svn cleanup tolerate obstructed directories (r1091881)
+ * fix deadlock in multithreaded servers serving FSFS repositories (r1104093)
+ * detect very occasional corruption and abort commit (issue #3845)
+ * fixed: file externals cause non-inheritable mergeinfo (issue #3843)
+ * fixed: file externals cause mixed-revision working copies (issue #3816)
+ * fix crash in mod_dav_svn with GETs of baselined resources (r1104126)
+ See CVE-2011-1752, and descriptive advisory at
+ http://subversion.apache.org/security/CVE-2011-1752-advisory.txt
+ * fixed: write-through proxy could direcly commit to slave (r917523)
+ * detect a particular corruption condition in FSFS (r1100213)
+ * improve error message when clients refer to unkown revisions (r939000)
+ * bugfixes and optimizations to the DAV mirroring code (r878607)
+ * fixed: locked and deleted file causes tree conflict (issue #3525)
+ * fixed: update touches locked file with svn:keywords property (issue #3471)
+ * fix svnsync handling of directory copyfrom (issue #3641)
+ * fix 'log -g' excessive duplicate output (issue #3650)
+ * fix svnsync copyfrom handling bug with BDB (r1036429)
+ * server-side validation of svn:mergeinfo syntax during commit (issue #3895)
+ * fix remotely triggerable mod_dav_svn DoS (r1130303)
+ See CVE-2011-1783, and descriptive advisory at
+ http://subversion.apache.org/security/CVE-2011-1783-advisory.txt
+ * fix potential leak of authz-protected file contents (r1130303)
+ See CVE-2011-1921, and descriptive advisory at
+ http://subversion.apache.org/security/CVE-2011-1921-advisory.txt
+
+ Developer-visible changes:
+ * fix reporting FS-level post-commit processing errors (r1104098)
+ * fix JVM recognition on OS X Snow Leopard (10.6) (r1028084)
+ * allow building on Windows with recent Expat (r1074572)
+
+
+Version 1.6.16
+(02 Mar 2011, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.16
+
+ User-visible changes:
+ * more improvement to the 'blame -g' memory leak from 1.6.15 (r1041438)
+ * avoid a crash in mod_dav_svn when using locks (r1071239, -307)
+ See CVE-2011-0715, and descriptive advisory at
+ http://subversion.apache.org/security/CVE-2011-0715-advisory.txt
+ * avoid unnecessary globbing for performance (r1068988)
+ * don't add tree conflicts when one already exists (issue #3486)
+ * fix potential crash when requesting mergeinfo (r902467)
+ * don't attempt to resolve prop conflicts in 'merge --dry-run' (r880146)
+ * more fixes for issue #3270.
+
+ Developer-visible changes:
+ * ensure report_info_t is properly initialized by ra_serf (r1058722)
+ * locate errors properly on a malfunction (r1053208)
+ * fix output param timing of svn_fs_commit_txn() on fsfs (r1051751)
+ * for svn_fs_commit_txn(), set invalid rev on failed commit (r1051632, -8)
+ * fix sporadic Ruby bindings test failures (r1038792)
+ * fix JavaHL JVM object leak when dumping large revisions (r947006)
+ * use Perl to resolve symlinks when building swig-pl (r1039040)
+ * allow Perl bindings to build within a symlinked working copy (r1036534)
+ * don't overwrite the LD_LIBRARY_PATH during make check-swig-pl (r946355)
+ * improve unit tests for some fs functions (r1051744, -5, -3185, -241)
+
+
+Version 1.6.15
+(26 Nov 2010, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.15
+
+ User-visible changes:
+ * hide unreadable dirs in mod_dav_svn's GET response (r996884)
+ * make 'svnmucc propsetf' actually work (r1005446)
+ * limit memory fragmentation in svnserve (r1022675)
+ * fix 'svn export' regression from 1.6.13 (r1032970)
+ * fix 'svn export' mistakenly uri-encodes paths (issue #3745)
+ * fix server-side memory leaks triggered by 'blame -g' (r1032808)
+ This has been tracked as CVE-2010-4644
+ * prevent crash in mod_dav_svn when using SVNParentPath (r1033166)
+ This has been tracked as CVE-2010-4539
+ * allow 'log -g' to continue in the face of invalid mergeinfo (issue #3270)
+ * filter unreadable paths for 'svn ls' and 'svn co' (r997026, -070, -474)
+ * fix abort in 'svn blame -g' (issue #3666)
+ * fix file handle leak in ruby bindings (issue #3512)
+ * remove check for 1.7-style working copies (issue #3729)
+
+ Developer-visible changes:
+ * improve some swig parameter mapping (r984565, r1035745)
+ * improve test accuracy over dav (r991534, r877814)
+ * create fails.log for test runs (r964349)
+ * improve detection of 'svnversion' when building (r877219, et al)
+ * don't violate API layering in dumpstream logic (issue #3733)
+ * don't report working copy installs as switched (r1033921)
+
+
+Version 1.6.14
+(Not released, see changes for 1.6.15.)
+
+
+Version 1.6.13
+(01 Oct 2010, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.13
+
+ User-visible changes:
+ * don't drop properties during foreign-repo merges (issue #3623)
+ * improve auto-props failure error message (r961970)
+ * improve error message for 403 status with ra_neon (r876615)
+ * don't allow 'merge --reintegrate' for 2-url merges (r959004)
+ * improve handling of missing fsfs.conf during hotcopy (r980811, -1449)
+ * escape unsafe characters in a URL during export (issue #3683)
+ * don't leak stale locks in FSFS (r959760)
+ * better detect broken working copies during update over ra_neon (r979045)
+ * fsfs: make rev files read-only (r981921)
+ * properly canonicalize a URL (r984928, -31)
+ * fix wc corruption with 'commit --depth=empty' (issue #3700)
+ * permissions fixes when doing reintegrate merges (related to issue #3242)
+ * fix mergeinfo miscalculation during 2-url merges (issue #3648)
+ * fix error transmission problems in svnserve (r997457, -66)
+ * fixed: record-only merges create self-referential mergeinfo (issue #3646)
+ * fixed: 'SVNPathAuthz short_circuit' unsolicited read access (issue #3695)
+ See CVE-2010-3315, and descriptive advisory at
+ http://subversion.apache.org/security/CVE-2010-3315-advisory.txt
+ * make 'svnmucc propset' handle existing and non-existing URLs (r1000607)
+ * add new 'propsetf' subcommand to svnmucc (r1000612)
+ * warn about copied dirs during 'svn ci' with limited depth (r1002094)
+
+ Developer-visible changes:
+ * make ruby bindings compatible with Ruby 1.9 (r957507)
+ * use the repos verify API in JavaHL (r948916)
+ * teach ra_serf to parse md5 checksums with update editors (r979429)
+ * let ra_serf work with current serf releases (r879757, r880320, r943796)
+
+
+Version 1.6.12
+(21 Jun 2010, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.12
+
+ User-visible changes:
+ * further improvements for issue #3242
+ * allow deletion of uris which need character escaping (issue #3636)
+ * fix errors with 'svn mkdir --parents' (issue #3649)
+ * update address to which crash reports are sent (r901304)
+ * check for server certificate revocation on Windows (r898048)
+ * disable custom file mutexes on Windows (r879902, -16)
+ * fix handling of peg revision'd copy targets (issue #3651)
+ * more improvements to 'svn merge --reintegrate' (r935631)
+ * allow copying of broken symlinks (issue #3303)
+ * improve rep-sharing performance on high-concurrency repos (issue #3506)
+ * fixed: added subtrees with mergeinfo break reintegrate (issue #3654)
+ * fixed: assertion triggered by tree-conflicted externals (issue #3469)
+
+ Developer-visible changes:
+ * give windows devs more flexibility with sqlite versions (r944635)
+ * allow the pack tests to work with low file descriptor limits (r937610)
+ * improve exception handling on Windows Vista and 7 (r878447, -910, -916)
+
+
+Version 1.6.11
+(19 Apr 2010, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.11
+
+ User-visible changes:
+ * fix for repositories mounted via NFS (issue #3501)
+ * enable TCP keep-alives in svnserve (r880552)
+ * tighten restrictions on revprops for 'svnadmin verify' (r904594)
+ * make ra_serf give better out-of-date information (issue #3561)
+ * improve error message upon connection failure with svn+ssh:// (r922516)
+ * allow 'svn log' on an uncommitted copy/move destination (r901752)
+ * make 'svnadmin hotcopy' copy the fsfs config file (r905303)
+ * mergeinfo improvements with non-inheritable mergeinfo (issue #3573)
+ * make mergeinfo queries not require access to the repo root (issue #3242)
+ * update URLs to refer the new apache.org repository (r904301, -94)
+ * update relative externals during a switch (issue #3390)
+ * fix 'merge --reintegrate' with self-referential mergeinfo (r892050, -85)
+ * improve wc-ng working copy detection (r929382)
+ * improve handling of mergeinfo when using serf (r880461)
+ * fixed: 'svnlook plist --revprop' with '-t TXN_NAME' (r917640, -8211)
+ * fixed: file external from URL cannot overwrite existing item (issue #3552)
+ * fixed: potential memory error in 'svn status' (r923674, -9)
+ * fixed: merge records mergeinfo from natural history gaps (issue #3432)
+ * fixed: theoretical possibility of DB corruption (r926151, -67)
+
+ Developer-visible changes:
+ * disable checks for wc-ng working copies when running the test suite
+ * on Windows, don't ignore move operation error codes (r896915)
+ * more precise reporting of errors occuring with sqlite init (r927323, -8)
+ * ensure rangelist APIs are commutative (r923389, -91)
+
+
+Version 1.6.10
+(Not released, see changes for 1.6.11.)
+
+
+Version 1.6.9
+(25 Jan 2010, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.9
+
+ User-visible changes:
+ * allow multiple external updates over ra_svn (issue #3487)
+ * fix a segmentation fault when using FSFS (r881905)
+ * support Berkeley DB 4.8 (r879688)
+ * various autoprop improvements (r880274, -5)
+ * improve usage of svn+ssh:// on Windows (issue #2580)
+ * teach 1.6.x to recognize 1.7 working copies (1.6.x-future-proof branch)
+ * update help text for 'svn update' and 'svn switch' (r886164, -97)
+ * make 'svnadmin load --parent-dir' create valid mergeinfo (r888979, -9081)
+ * tolerate relative merge source paths in mergeinfo (r889840)
+ * teach mod_dav_svn to support the Label header (issue #3519)
+ * fixed: svnsync leaves stale sync-locks on mirrors (r884842)
+ * fix applicability of 'svn resolve --accept=theirs-conflict' (r880525, -6)
+ * fixed: segfault in 'svn resolve' (r896522, -47)
+ * fix commit failure against an out-of-date mirror (r900797)
+
+ Developer-visible changes:
+ * update ruby bindings test expectation (r880162)
+ * don't allow rangelist and mergeinfo API to modify input args (r879093)
+
+
+Version 1.6.8
+(Not released, see changes for 1.6.9.)
+
+
+Version 1.6.7
+(Not released, see changes for 1.6.9.)
+
+
+[ Note: All revision numbers for versions prior to 1.6.7 reference the
+ original repository on svn.collab.net. For more information see:
+ http://svn.apache.org/repos/asf/subversion/README ]
+
+
+Version 1.6.6
+(22 Oct 2009, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.6
+
+ User-visible changes:
+ * fix crash during 'svn update' (r39673)
+ * respect Apache's ServerSignature directive (r40008, -21, -31)
+ * don't add a file with mixed line endings, and then abort (issue #2713)
+ * support Neon 0.29.
+ * fix a crash in 'svn rm --force' (r37953)
+ * handle tree conflicts involving replacements (issue #3486)
+ * allow non-threadsafe sqlite if APR has no threads (r39301)
+ * print newline before plaintext SSL cert / password prompts (r38982, r39302)
+ * improve merge performance with implicit subtree mergeinfo (issue #3443)
+ * fix "libsvn_ra_svn/marshal.c assertion failed (opt || cstr)" (issue #3485)
+ * make file externals work for binary files (issue #3368)
+ * perform MIME type matching case-insensitively (issue #3479)
+ * do not treat non-existent revisions as HEAD in 'svn export' (issue #3400)
+ * revert r36720's default MIME type change back to "text/plain" (issue #3508)
+ * improve "tree conflict already exists" error message (r38872)
+ * fix failure to commit replacement of a directory (issue #3281)
+ * fix mod_dav_svn parent dir links to preserve peg revisions (issue #3425)
+
+ Developer-visible changes:
+ * fix 2 failing tests in ruby bindings (r38886)
+ * do not require GNU grep for build (issue #3453)
+ * use '$SED' instead of 'sed' in build scripts (issue #3458)
+ * add svn.client.{log5,merge_peg3} to python bindings (r39635, -6, -7)
+ * include the time of a test run in tests.log (r39887)
+
+
+Version 1.6.5
+(22 Aug 2009, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.5
+
+ User-visible changes:
+ * fix mod_dav_svn directory view links to preserve peg revisions (r38021)
+ * do not error on Windows when ALLUSERPROFILE dir nonexistent (r38053, -5, -7)
+ * properly escape lock comments over ra_neon (r38101, -2)
+ * allow syncing copies of '/' over ra_neon and ra_serf (issue #3438)
+ * make 'svnlook diff' show empty added or deleted files (r38458)
+ * fix building with Apache 2.4 (r36720)
+ * fix possible data loss on ext4 and GPFS filesystems (issue #3442)
+ * resolve symlinks when checking for ~/.subversion (r36023)
+ * don't let svn+ssh SIGKILL ssh processes (issue #2580)
+ * allow PLAIN and LOGIN mechanisms with SASL in svnserve (r38205)
+ * fix peg revision parsing in filenames like 'dir/@file.txt' (issue #3416)
+ * fix detection of Apache <2.0.56 (r38290, -3, -4)
+ * don't pretend to do tree conflict resolution (r38799, -801, -805)
+ * fix data corruption when syncing from svnserve to mod_dav_svn (r38686, -7)
+ * fix GNOME Keyring with '--non-interactive' option (r38222, -3, -61, -410)
+ * fixed: false "File '...' already exists" error during commit (issue #3119)
+
+ Developer-visible changes:
+ * avoid referencing uninitialized variables (r38388)
+ * plug a couple of error leaks (r38572)
+ * improve windows test output (r38616, -7, -9, -49)
+
+
+Version 1.6.4
+(06 Aug 2009, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.4
+
+ User-visible changes:
+ * fixed: heap overflow vulnerability on server and client
+ See CVE-2009-2411, and descriptive advisory at
+ http://subversion.apache.org/security/CVE-2009-2411-advisory.txt
+
+
+Version 1.6.3
+(22 Jun 2009, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.3
+
+ User-visible changes:
+ * fix segfault in WC->URL copy (r37646, -56)
+ * let 'svnadmin load' tolerate mergeinfo with "\r\n" (r37768)
+ * make svnsync normalize svn:* props to LF line endings (issue #3404)
+ * better integration with external merge tools (r36178)
+ * return a friendly error message for 'svn diff' (r37735)
+ * update dsvn.el for 1.6 (r37774)
+ * don't allow setting of props on out-of-date dirs under neon (r37745)
+ * improve BASH completion (r36450, -52, -70, -79, -538)
+ * always show tree conflicts with 'svn st' (issue #3382)
+ * improve correctness of 'svn mergeinfo' (issue #3126)
+ * decrease the amount of memory needed for large commits (r37894, -6)
+ * work around an APR buffer overflow seen by svnsync (r37622)
+ * ra_svn clients now use TCP keep-alives if available (issue #3347)
+ * improve 'svn merge' perf by reducing server contact (r37491, -593, -618)
+ * stop propagating self-referential mergeinfo in reintegrate merges (r37931)
+ * fix NLS detection where -liconv is required for bindtextdomain() (r37827)
+ * don't delete unversioned files with 'rm --keep-local' (r38015, -17, -19)
+ * bump apr and apr-util versions included in deps to latest. (r37941)
+ * avoid temp file name collisions with ra_serf, ra_neon (r37972)
+ * fixed: potential segfault with noop file merges (r37779)
+ * fixed: incorrect output with 'svn blame -g' (r37719, -23, -41)
+ * fixed: bindings don't load FS libs when module search enabled (issue #3413)
+ * fixed: DAV RA layers not properly handling update/switch working copy
+ directory to revision/place in which it doesn't exist (issue #3414)
+ * fixed: potential abort() in the working copy library (r37857)
+ * fixed: memory leak in hash reading functions (r37868, -979)
+
+ Developer-visible changes:
+ * improve memory usage in file-to-stringbuf APIs (r37907)
+ * reduce memory usage for temp string manipulation (r38010)
+
+
+Version 1.6.2
+(11 May 2009, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.2
+
+ User-visible changes:
+ * vastly improve memory usage with 'svn merge' (issue #3393)
+ * make default depth for merge 'infinity' (r37156)
+ * make 'status --quiet' show tree conflicts (issue #3396)
+ * allow '--set-depth infinity' to expand shallow subtrees (r37169)
+ * return an error if attempting to reintegrate from/to the repo root (r37385)
+ * don't store bogus mergeinfo for '--ignore-ancestry', foreign merges (r37333)
+ * don't allow merge of difference between two repos (r37519)
+ * avoid potential segfault with subtree mergeinfo (r36613, -15, -31, -41)
+ * recommend sqlite 3.6.13 (r37245)
+ * avoid unnecessary server query for implicit mergeinfo (r36509)
+ * avoid unnecessary server query during reverse merges (r36527)
+ * set depth=infinity on 'svn add' items with restricted depth (r37607)
+ * fixed: commit log message template missing paths (issue #3399)
+ * fixed: segfault on merge with servers < 1.6 (r37363, -67, -68, -79)
+ * fixed: repeat merge failures with non-inheritable mergeinfo (issue #3392)
+ * fixed: another memory leak when performing mergeinfo-aware merges (r37398)
+ * fixed: incorrect mergeinfo on children of shallow merges (issue #3407)
+ * fixed: pool lifetime issues in the BDB backend (r37137)
+
+ Developer-visible changes:
+ * don't fail if an embedding app has already initialized SQLite (issue #3387)
+ * resolve naming collisions with static stat() function in svnserve (r37527)
+ * fix an expectation for a failing dirent windows test (r37121)
+
+
+Version 1.6.1
+(9 Apr 2009, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.1
+
+ User-visible changes:
+ * recommend Neon 0.28.4. (r36388)
+ * improve performance of 'svn merge --ignore-ancestry' (r36256)
+ * improve 'svn merge' performance with subtree mergeinfo (r36444)
+ * correctly proxy LOCK and UNLOCK requests (r36159)
+ * prevent a crash when updating old working copies (r36751)
+ * don't let svnmerge.py delete a nonexistent property (r36086, -767, -769)
+ * don't fail when upgrading pre-1.2 repositories (r36851, -7)
+ * allow escaping of separator characters in autoprops (r36763, -84)
+ * improve tempfile creation robustness on Windows (r36442, -3)
+ * fix change-svn-wc-format.py for 1.6.x working copies (r36874, -5)
+ * improve configure's detection of Berkeley DB (r36741, -2)
+ * don't allow foreign merges to add foreign mergeinfo (issue #3383)
+ * improve performance of 'svn update' on large files (r36389, et. al.)
+ * fixed: error leak and potential crash (r36860)
+ * fixed: parent directory handling on Windows (r36049, -50, -51, -131)
+ * fixed: unintialized memory errors (r36252, -3)
+ * fixed: potential working copy corruption (r36714)
+ * fixed: working copy upgrade error (r36302)
+ * fixed: pointer dereference error (r36783)
+ * fixed: error diff'ing large data with ignored whitespace (r36816)
+ * fixed: potential hang in ra_serf (r36913)
+ * fixed: problem with merge and non-inheritable mergeinfo (r36879)
+ * fixed: repeated merging of conflicted properties fails (issue #3250)
+ * fixed: excluding an absent directory segfaults (issue #3391)
+
+ Developer-visible changes:
+ * ensure svn_subst_translate_cstring2() properly flushes data (r36747)
+ * make serf report a base checksum to apply_textdelta (r36890)
+ * syntax updates for strict C89 compilers (r36799)
+ * update RPM scripts for RHEL4 (r36834)
+ * allow tests to be run with Python 2.6.1 on Windows (r36149, -50, -51, -56)
+ * allow building JavaHL with Visual Studio 2008 (r36954)
+ * stop setting default translation domain in JavaHL (r36955)
+ * fixed: warning with Python 2.6 and ctypes bindings (r36559)
+ * fixed: undefined references to svn_fs_path_change2_create() (r36823)
+
+
+Version 1.6.0
+(20 Mar 2009, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.0
+
+ User-visible changes:
+ - General:
+ * Now require Windows 2000 or newer on Windows (r33170)
+
+ - Major new features:
+ * identical files share storage space in repository (issue #2286)
+ * file-externals support for intra-repository files (issue #937)
+ * "tree" conflicts now handled more gracefully (issue #2282, #2908)
+ * repository root relative URL support on most commands (issue #3193)
+
+ - Minor new features and improvements:
+ * pre-lock hook can now specify lock tokens via stdout (r32778)
+ * svnmucc: support '--with-revprop' (r29492)
+ * merge: log include-descendants in operational log (r30426, r30428)
+ * improved operational logging for 'svn switch' (r30517)
+ * new 'Header' keyword, similar to 'Id' but with full URL (r35386)
+ * warn/disallow when storing plain-text passwords (r31046)
+ * support KWallet and GNOME keyring for password storage (r31241, -337)
+ * client now caches SSL client cert passphrases (issue #2489)
+ * add '--prefix-file' option to 'svndumpfilter' (issue #2697)
+ * add '--ignore-externals' option to 'svn cp' (issue #3365)
+ * add '--with-no-revprops' to 'svn log' (issue #3286)
+ * new 'svnadmin pack' command to compress FSFS filesystems
+ * new SVNAllowBulkUpdates mod_dav_svn directive (issue #3121)
+ * new public mod_dav_svn URI syntax: path?[p=PEG][&r=REV] (r34076)
+ * new 'svnsync info' command to show synchronization information (r35053)
+ * conflict resolver supports display-conflict, mine-conflict and theirs-conflict
+
+ - Client-side bugfixes:
+ * faulty reflexive merges (issue #2897)
+ * buffer overflow on a 0 byte string buffer (r35968, -74)
+ * conflict resolver needed more useful 'diff' option (issue #3048)
+ * disable username assumption (issue #2324)
+ * more accurate usage message for 'svn log' (r30449)
+ * do not repeat merge if target has explicit mergeinfo (issue #2821)
+ * corruption when filtering self-referential mergeinfo (r30467)
+ * filter empty mergeinfo with self-referential mergeinfo (r30510)
+ * pay attention to partial replay from the server in svnsync (r30440)
+ * improved property name handling in svnsync (r30480)
+ * properly recognize the file:/// in repository with svnsync (r30482)
+ * svn+ssh SIGKILLs ssh processes (issue #2580)
+ * 'svn up'/'svn co' early abort with svn:externals (issue #3148)
+ * improve tempfile names for conflict resolver (issue #3166)
+ * ra_serf: 'svn merge' aborts (issue #3212)
+ * 'svn cleanup' failed on non-ASCII characters (issue #3313)
+ * 'svn update' fails on moved, modified file with local mods (issue #3354)
+ * easier use of NTLM for proxy with ra_neon (r29874)
+ * 2-url merge from DAV-accessed foreign repo makes bad wcprops (issue #3118)
+ * can't add .svn (and children) to your wc via '--parents' (r35819)
+ * improved performance removing unversioned directories (r36111)
+ * 'svn cp --parents' had path URL encoding issues (issue #3374)
+ * support shell quoting rules in externals definitions (issue #2461)
+ * new SVN_LOCALE_DIR environment variable for localization (issue #2879)
+ * scheme and domain name in urls handled case insensitive (issue #2475)
+ * merge: pick default revisions with peg revision in single url (r30455)
+ * many other minor bugfixes, optimizations, plugs of memory leaks, etc
+
+ - Server-side bugfixes:
+ * mod_dav_svn runs pre-revprop-change twice (issue #3085)
+ * mod_dav_svn ignores pre-revprop-change failure on delete (issue #3086)
+ * mod_dav_svn prevented lock breaks from being propagated to client (r29914)
+ * non-UTF8 filenames could enter repository (issue #2748)
+ * 'svnlook proplist' xml output (issue #2809)
+ * don't let mod_dav_svn hide errors from client (issue #3102)
+ * ra_serf failure during update (issue #3113)
+ * ra_serf comply with RFC 2617 in handling authentication headers (r35981)
+ * use both SHA1 and MD5 in the FS backends (r34388)
+ * many other minor bugfixes too numerous to list here
+
+ - Contributed tools improvements and bugfixes:
+ * commit-email.pl: Deprecated; use mailer.py instead (r31755, -67)
+ * svnmerge.py migration tool munged svn:mergeinfo ordering (issue #3302)
+ * And other random sundry stuff
+
+ Developer-visible changes:
+ - General:
+ * serf 0.3.0 required, when building with serf (r35586)
+ * require SQLite 3.4.0 or newer (r33520)
+ * allow the use of an in-tree SQLite amalgamation (r35263)
+ * svn_log_changed_path_t now includes a 'kind' field (issue #1967)
+ * BDB `changes' table inconsistency when APIs are misused (issue #3349)
+ * configure should prefer apr-1 over apr-0 if both are present (issue #2671)
+ * make 'Not Found' errors consistent between RA layers (issue #3137)
+ * fix a potential buffer overrun (r34374)
+ * many bug fixes and improvements to the test suite
+
+ - API changes:
+ * notification system for properties and revision properties (issue #783)
+ * make ra_svn's merge commit-revprops public (r30462, r30453)
+ * mod_dav_svn operational logging compatible with svnserve logging (r30518)
+ * improve speed of svn_client__get_copy_source() (issue #3356)
+ * if fsfs commit fails return SVN_INVALID_REVNUM (r35950)
+
+ - Bindings:
+ * new: ctypes python bindings
+ * many improvements to all bindings (Java, Perl, Python, and Ruby)
+ * respect CFLAGS in SWIG bindings (r35879)
+ * fix building Ruby bindings with Ruby 1.9 (r35852, r35883)
+
+
+Version 1.5.9
+(06 Dec 2010, from /branches/1.5.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.5.9
+
+ User-visible changes:
+ * fix proxying of LOCK and UNLOCK requests with WebDAV proxies (r36159)
+ * improve performance of --ignore-ancestry merges (r36256)
+ * avoid crash with when using subtree mergeinfo (r36613, -13, -31, -41)
+ * improve merge correctness with non-inheritable mergeinfo (r36789)
+ * fixed: repeated mergeinfo of conflicting properties fails (issue #3250)
+ * fix segfault in wc->URL copy (r37646, -56)
+ * make 'svn up --set-depth infinity' expand shallow subtrees (r37169)
+ * resolve symlinks when checking for ~/.subversion (r36023)
+ * make default depth of 'svn merge' infinity (r37156)
+ * don't allow foreign merges to add foreign mergeinfo (issue #3383)
+ * error if attempting to reintegrate to/from the repo root (r37385)
+ * let 'svnadmin load' tolerate mergeinfo with "\r\n" (r37768)
+ * improve memory performance in 'svn merge' (issue #3393)
+ * fixed: 'SVNPathAuthz short_circuit' unsolicited read access (issue #3695)
+ See CVE-2010-3315, and descriptive advisory at
+ http://subversion.apache.org/security/CVE-2010-3315-advisory.txt
+ * prevent crash in mod_dav_svn when using SVNParentPath (r1033166)
+ * limit memory fragmentation in svnserve (r1022675)
+ * fix server-side memory leaks triggered by 'blame -g' (r1032808)
+ * perform MIME type matching case-insensitively (issue #3479)
+ * respect Apache's ServerSignature directive (r880082)
+ * error early if attempting to use Serf >= 0.4.0 (r1041545)
+
+ Developer-visible changes:
+ * fix pointer dereference (r36783)
+ * fix error leak (r36860)
+ * make basic_tests 12 compatible with Windows and Python 2.5+ (r35930)
+
+
+Version 1.5.8
+(Not released, see changes for 1.5.9.)
+
+
+Version 1.5.7
+(06 Aug 2009, from /branches/1.5.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.5.7
+
+ User-visible changes:
+ * fixed: heap overflow vulnerability on server and client
+ See CVE-2009-2411, and descriptive advisory at
+ http://subversion.apache.org/security/CVE-2009-2411-advisory.txt
+
+
+Version 1.5.6
+(26 Feb 2009, from /branches/1.5.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.5.6
+
+ User-visible changes:
+ * allow colons within mergeinfo path names (r35040)
+ * make it impossible to add .svn to wc via 'svn add --parents' (r35143, -5)
+ * copy properties of added but uncommitted files (r32448)
+ * speedup JavaHL bindings on Windows (r35733)
+ * improve performance of log operation on < 1.5 servers (r35566)
+ * allow commits over Neon of files >2GB (POSIX only) (r34919, -24)
+ * allow serf from behind MS ISA proxy servers (r35981)
+ * prevent svnmerge-migrate-history.py from committing bogus mergeinfo (r35516)
+
+ Developer-visible changes:
+ * fix error handling in mod_dav_svn (r35250, -86)
+ * support --server-minor-version in windows testsuite (r31393)
+ * fix depth_tests.py 23 on Windows with a BDB repo (r34875)
+ * allow svn_mergeinfo_parse() to tolerate unordered mergeinfo (r35297, -367)
+ * allow overlapping rangelists into svn_mergeinfo_parse() (r35466, -712, -713)
+
+
+Version 1.5.5
+(22 Dec 2008, from /branches/1.5.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.5.5
+
+ User-visible changes:
+ * allow prop commits on dirs with modified children (r34487, -92, -94)
+ * make Cyrus auth implementation always prefer EXTERNAL to ANONYMOUS (r33866)
+ * do not create mergeinfo for wc-wc moves or copies (r34184, -585)
+ * do not autoupgrade old BDB filesystems to 1.5 or 1.4 format (r34653, -6)
+ * return mergeinfo to prior state during reverse merges (r30257, r33024, -6)
+ * remove mergeinfo deleted by merge (issue #3323)
+ * make proxy slaves pass through txn GET and PROPFIND requests (issue #3275)
+ * merge can now use targets with inconsistent newlines (issue #3262)
+ * don't allow empty-string changelists (issue #3344)
+ * remove false positive ra_neon mergeinfo errors (r34822)
+ * improve performance of 'svn merge --reintegrate' (r34091, -4, and others)
+ * fixed: foreign merges keep UUID of foreign repository (r34050, -1, -3)
+ * fixed: properly encode diff headers used in conflict resolution (r34171)
+ * fixed: segfault in 'svn cp --parents' (r31311, -4)
+ * fixed: mergeinfo for '...' maps to empty revision range (issue #3312)
+ * fixed: segfault in BDB backend node-origins cache (r34506)
+ * fixed: broken merge if target's history includes resurrections (r34385, -93)
+ * fixed: invalid mergeinfo created on a subtree during merge (r34560, -2)
+
+ Developer-visible changes:
+ * fixed: svn_repos_get_logs() chokes on some revision arguments (r33873, -4)
+
+
+Version 1.5.4
+(24 Oct 2008, from /branches/1.5.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.5.4
+
+ User-visible changes:
+ * Properly handle explicit mergeinfo added in merge source (r32968, -75)
+ * fixed: merging of paths containing spaces (r33641, -44)
+ * fixed: regression in mergeinfo-aware merges against 1.5.3 (r33693, -704)
+
+
+Version 1.5.3
+(10 Oct 2008, from /branches/1.5.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.5.3
+
+ User-visible changes:
+ * Allow switch to continue after deleting locally modified dirs (issue #2505)
+ * Update bash_completion to be compatible with 1.5 (r32900, -11, -12)
+ * Improve 'svn merge' execution time by 30% on Windows (r33447)
+ * Reuse network sessions during 'svn merge', improving performance (r33476)
+ * Improve temp file creation time on Windows (r33464)
+ * Greatly improve merge performance (r29969, r32463, r33013, -016, -022, -112)
+ * Improve file IO performance on Windows (r33178, -85)
+ * fixed: merging files with spaces in name (r33109, -121, -369)
+ * fixed: incorrect relative externals expansion (r33109, -121, -369)
+ * fixed: 'svn mv' hangs and consumes infinite memory (r33201, -12)
+ * fixed: correctness regression in 'svn log -g' (issue #3285)
+ * fixed: current early bailout of 'svn log -g' (r32977)
+
+ Developer-visible changes:
+ * Allow the tests to run as non-administrator on Windows Vista (r31203)
+ * Allow out-of-tree build of bindings on BSD (r32409)
+ * Translate messages in svn_fs_util.h (r32771)
+ * fixed: bindings test for Perl 5.10 (r31546)
+ * fixed: building bindings and C API tests with VS2008 (r32012)
+ * fixed: svn_ra_replay API over ra_serf (r33173)
+
+
+Version 1.5.2
+(30 Aug 2008, from /branches/1.5.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.5.2
+
+ User-visible changes:
+ * Set correct permissions on created fsfs shards (r32355, -7)
+ * Pass client capabilities to start-commit hook (issue #3255)
+ * Disallow creating nested repositories (issue #3269)
+ * Support Neon 0.28.3
+ * Properly canonicalize URIs with an empty hostname (issue #2116)
+ * Improved merge performance for superfluous ranges (r32643)
+ * Better error message for 'Malformed URL for repository' (r31867, r32365)
+ * Improved svn:externals parsing (r32672, -673, -674, -739)
+ * fixed: improper ordering in 'svnlook diff' output (r32019)
+ * fixed: mod_dav_svn memory leak with 'SVNPathAuthz short_circuit' (r32360)
+ * fixed: duplicate svn:externals targets fail on co/up (issue #3246)
+ * fixed: 'svn merge --depth' inconsistencies (issue #2825)
+ * fixed: ra_serf test failures (1.5.x-ra_serf-backports branch)
+ * fixed: memory leak and crashes in FS (r32545, -58, -82)
+ * fixed: core dump with relative externals (issue #3237)
+ * fixed: 'svn copy' working copy corruption (r32467, -70)
+ * fixed: perl bindings errors in non-English locale (issue #3258)
+ * fixed: 'svn merge' incorrectly reverses previous merges (r32494, -522, -523)
+ * fixed: 'svn merge' errors with subtree mergeinfo (issue #3067)
+
+ Developer-visible changes:
+ * make libsvn_ra_neon initialization thread-safe (r32497, r32510)
+ * respect LDFLAGS in SWIG bindings (r32416, r32421, r32442)
+ * fixed: test failures in non-English locales (r32491)
+
+
+Version 1.5.1
+(26 Jul 2008, from /branches/1.5.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.5.1
+
+ User-visible changes:
+ * mergeinfo on switched subtrees should elide in repos (issue #3188)
+ * Add support for --config-dir to svnmerge.py (r31727)
+ * improve performance of bdb post-commit deltification (r31820, -59)
+ * return faster when there is nothing to be merged (r30748)
+ * don't commit an add of a missing item (issue #3198)
+ * don't create unneeded self-referential mergeinfo (issue #3157)
+ * support 'http-library' (if --enable-runtime-module-search) (r31425, -722)
+ * support Berkeley DB 4.7 (r32017, -29)
+ * fixed: make serf usable with root-level authz (r31464)
+ * fixed: 'svndumpfilter' partial-path matching bug (r31833)
+ * fixed: crash on invalid dates in 'log' and 'blame' (issue #2721)
+ * fixed: 'svn status --xml' outputting invalid XML (issue #2887)
+ * fixed: 'svn merge' prints incorrect range (r30746, -47)
+ * fixed: using neon/serf, can not replace branch (issue #2939)
+ * fixed: 'file not found' error when merging to a broken symlink (r31159, -79)
+ * fixed: using serf, crash or endless loop fetching authn data (r31619)
+ * fixed: ArrayIndexOutOfBoundsException in JavaHL bindings (r31719, -806)
+ * fixed: authn password lookup used wrong username (issue #2242)
+ * fixed: unbounded memory usage in wc-to-wc copy and move (r31868)
+ * fixed: subtree merges broken for non-intersecting ranges (issue #3199)
+ * fixed: invalid XML from 'svn log --xml' against pre-1.2 servers (r31875)
+ * fixed: 'svnlook diff' ignores --diff-copy-from for properties (issue #3248)
+ * fixed: 'svnlook diff' doesn't report that binary files differ (issue #3249)
+ * fixed: bogus results from commits to subtrees added by merge (issue #3240)
+ * fixed: non-existent subtree in destination breaks the merge (issue #3067)
+ * fixed: serf merge bug too complex to describe here (r32056)
+ * fixed: 'svn log -g' correctness and speed (issue #3220, issue #3235)
+ * fixed: merge chokes on renamed subtrees (issue #3174)
+
+ Developer-visible changes:
+ * export svn_path_is_url() to the bindings (r31603)
+ * don't clobber LDFLAGS in configure when given '--with-zlib' (r31825)
+ * make libsvn_ra depend on libsvn_delta unconditionally (r31852)
+ * correctly set the peg revision for copy in JavaHL (r31994)
+ * 'svn mergeinfo' handles wc paths (r31023, -873, -874, -929, -930, -038)
+ * fixed: crash when when svn_ra_open3() is passed a bogus URL (r31223)
+ * fixed: JavaHL compilation on Windows (r31737)
+ * fixed: crash in calling apr_pstrcat (affects TortoiseSVN) (r32080)
+
+
+Version 1.5.0
+(19 Jun 2008, from /branches/1.5.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.5.0
+
+ User-visible changes:
+ - Major new features:
+ * Merge Tracking [foundational] (issue #820)
+ * Sparse checkouts (see new '--depth' option) (issue #695)
+ * Interactive conflict resolution (r25670 et al)
+ * svn:externals handles relative URLs (issue #1336) and peg URLs
+ * Changelist support
+ * WebDAV transparent write-through proxy
+ * Better support for large FSFS deployments (via sharding & partitioning)
+ * Cyrus SASL support for ra_svn and svnserve (issue #1144)
+
+ - Minor new features and improvements:
+ * 'svn resolve' (with '--accept' option) replaces "resolved" (issue #2784)
+ * 'svn move file1 file2 ... dir' now moves the files into dir (issue #747)
+ * 'svn mkdir' and 'svn copy' now take '--parents' option (issue #1776)
+ * 'svn delete' now takes '--keep-local' to not remove working copy files
+ * 'svn copy', 'move' now support peg revisions (issue #2546; also r26484)
+ * 'svn copy A B ; svn move B C' now the same as 'svn copy A C' (issue #756)
+ * 'svn copy -rBASE' now works in a working copy (issue #1643)
+ * 'svn import' now takes '--force' (issue #2806)
+ * 'svn status -u' now shows of locally deleted directories (issue #2420)
+ * 'svn switch' now takes '--force' (issue #2392)
+ * 'svn switch' now takes '--ignore-externals' option (issue #2189)
+ * 'svn switch' now supports peg revisions (issue #2545)
+ * 'svn checkout' now takes '--force' option (issue #1328)
+ * 'svn proplist' and 'svn propget' now support peg revisions (issue #3070)
+ * 'svn propget' now takes '--xml' option (issue #2696)
+ * 'svn propedit' now support URLs (issue #2238, but see issue #2923)
+ * 'svn proplist --quiet' no longer prints extra info (issue #1547)
+ * 'svn diff --summarize' now takes '--xml' option (issue #2967)
+ * 'svn diff -x' now takes '-p' extension option (issue #2995)
+ * 'svn log' now takes '-c' option (r27933)
+ * 'svn log' now takes '-l' as short form of '--limit' (r25829)
+ * 'svn log --xml' now takes '--with-revprop' option (issue #2850)
+ * 'svn diff'/'svnlook diff' now show property actions better (issue #3019)
+ * 'svn merge' now has informative messages on reverse merges (issue #2848)
+ * 'svn merge FILE' now honors '--ignore-ancestry' (issue #2853, r25891)
+ * 'svn merge' handles multiple notifications for single items (issue #2828)
+ * 'svn merge' handles skipped path better (issue #2829)
+ * 'svn merge' handles merges from foreign repositories more completely
+ * 'update', 'checkout', 'switch' now handle obstructions gracefully (r22257)
+ * 'svn update' now takes '--force' (issue #2392)
+ * 'svn update' now sometimes copies or moves local files, for efficiency
+ * 'svnadmin lslocks' now accepts path within repository (issue #2965)
+ * 'svnadmin recover' now supports FSFS repositories (issue #2992)
+ * 'svnadmin verify' now has '-q' and '-r' options (r22103)
+ * 'svnadmin setrevprop' command added (r21736)
+ * 'svnadmin setuuid' command added (r28511)
+ * 'svnsync sync' now shows commit progress like 'svn commit'
+ * 'svnsync' now takes '-q, --quiet' option (r26465)
+ * 'svnsync' now supports separate authn for source/target (issue #2717)
+ * 'svnsync copy-revprops' now supports revision ranges (r23498)
+ * 'svnsync copy-revprops' now supports "HEAD" revision alias (r23500)
+ * 'svnmucc' is new name for contrib tool formerly called 'mucc'
+ * 'svnmucc' now has propset and propdel subcommands (issue #2758)
+ * 'svnmucc' now has more authentication options
+ * 'svnmucc' now now takes '--non-interactive' option (r25977)
+ * 'svnmucc' now takes a global base revision, for extra safety (r23764)
+ * 'svnlook' now takes '--extensions' option (issue #2912)
+ * 'svnlook' now takes '-N' option (issue #2663)
+ * 'svnlook history' now takes '-l' / '--limit' option (r25843)
+ * 'svnserve' now takes '--config-file' option (r24119)
+ * 'mod_dav_svn' now uses Apache default mime-type for files (issue #2304)
+ * new '--with-revprop' option on all commands that commit (issue #1976)
+ * now accept "peg dates" (URL@{DATE}), behaving like peg revs (issue #2602)
+ * easier to try out experimental ra_serf http:// access module
+ * select ra_neon vs ra_serf on a site-by-site basis in config (r25535)
+ * client-side post-commit processing now more efficient (issue #2607)
+ * windows binaries now use a custom crash handler (issue #1628)
+ * add vim swap file patterns to default global-ignores (r24348)
+ * add "*.pyc" and "*.pyo" patterns to default global-ignores (issue #2415)
+ * add unix/libtool library patterns to default global-ignores (issue #2415)
+ * naming scheme for conflict files is now configurable (issue #2474)
+ * removed svn-ref.tex as it's extremely out of date (issue #2762)
+ * improved cancellation response in many situations
+ * support Neon up to 0.28
+ * character set conversion now uses native API on Windows (r25650)
+ * HTTP authn protocol now configurable (for Neon 0.26 and higher) (r21531)
+ * http:// (over Neon) supports HTTP redirection / relocation (issue #660)
+ * support PKCS#11-provided (smartcard) SSL client certs with Neon (r29421)
+ * authz now supports aliases (r21982)
+ * authz token rules for authenticated-only, anonymous, and inverse (r23750)
+ * mailer.py now supports properties in commit messages (r21684)
+ * ra_serf now supports NTLM/SSPI authentication (issue #2900)
+ * warn if try to turn off boolean property via propset/propedit (r25486)
+ * display repository basename in XML and HTML index views (r25837, r25838)
+ * config 'http-auth-type' can be overridden to force BASIC auth (r23900)
+ * translation updates for all languages, as usual
+ * Revamp mod_dav_svn logging; see tools/server-side/svn_dav_log_parse.py
+ * misleading configure arg --enable-dso now --enable-runtime-module-search
+
+ - Client-side bugfixes:
+ * 'svn revert' of missing scheduled addition broke wc (issue #2425)
+ * 'svn export' should export svn:externals from local copies (issue #2429)
+ * 'svn status -uN' should show status of files (issue #2468)
+ * 'svn update' overwrote if local timestamp unchanged (issue #2746)
+ * 'svn update -N' errored when receiving a deletion (issue #3039)
+ * 'svn merge' would delete locally modified props (issue #2857)
+ * 'svn log --xml' could output invalid XML (issue #2866)
+ * 'svn copy' on URL with spaces made wrong WC file name (issue #2955)
+ * 'svn diff' was failing w/ large diffs on Windows (issue #1789)
+ * 'svn delete' no longer deletes locally-modified files (issue #1808)
+ * 'svn move' moved files to wrong directory on Windows (issue #1869)
+ * 'svn revert' mistakenly used leftover .svn-revert files (issue #2927)
+ * 'svn diff' output now shows relative paths (issue #2723)
+ * 'svn diff' wasn't ignoring all EOLs (issue #2920)
+ * 'svn cleanup' no longer fails on a missing .svn/tmp dir (r23370)
+ * infinite loop in UTF conversion in non-C locale (issue #2577)
+ * interrupting "svn status" could make svn crash (issue #2623)
+ * commit-email.pl date header output now RFC2822-compliant (issue #2633)
+ * authz write access to folder wasn't permitting locking (issue #2700)
+ * stop complaining just because $HOME is unreadable (issue #2363)
+ * do not display unescaped characters in error message (issue #2471)
+ * propchange received on subdir merge causes conflict (issue #2969)
+ * revert replaced-with-history files should restore checksum (issue #2928)
+ * catch improper arguments to diff (issue #2996)
+ * handle URLs like http://hostname (i.e. no path part) (issue #1851)
+ * config autoprops honored regardless of case of entry (issue #2036)
+ * "Cannot replace a directory from within" error now rarer (issue #2047)
+ * handle _svn/.svn as part of a path (issue #3026)
+ * make permissions changes on symlinks a no-op (issue #2581)
+ * error usefully if asked to update a URL (r22296)
+ * fixed infinite loop on Windows if fail to find repository root (r22483)
+ * 'svn info $REPO_ROOT' now supports pre-1.2 svn:// servers (r26264)
+ * be more resilient in the face of faulty .svn/entries files (r26482)
+ * 'svn diff -x --ignore-eol-style' failed to ignore all EOLs (r27094)
+ * rare property dataloss bug now fixed (issue #2986, see also r29538)
+ * fixed faulty status reporting for some missing directories (issue #2804)
+ * 'svn diff --summarize' showed wrong output paths (issue #2765)
+ * propset and move interaction could cause property weirdness (r25833)
+ * 'svn propget <propname> .@HEAD' now works (issue #3012)
+ * 'svnsync' had bug with replaced+modified rev over serf (issue #2904)
+ * 'svnsync --config-dir' sometimes ignored, thus tunnel agent bug (r27056)
+ * update/merge safely receives file on top of schedule-add file (r23506)
+ * http:// (over Neon) reports progress while disk-spooling delta (r26271)
+ * print "Out of memory" before dying from memory shortage (issue #2167)
+ * warn when used on old checkout without a repository root entry (r25168)
+ * merge to missing file target wrongly appeared to succeed (issue #2782)
+ * 'svn merge URL PATH -cX' could cause property corruption (issue #2781)
+ * URL parsing now consistently checks for error earlier (issue #2207)
+ * security hole: files could be created above cwd (r26047, CVE-2007-3846)
+ * local property mods to replaced-with-history file could be lost (r26364)
+ * revert of replaced-with-history path left copyfrom info (r23452)
+ * character encoding translation could hang (r23492)
+ * un-substituting keywords was buggy ($Id$ vs. $Id:$) (issue #2640)
+ * ra_neon and ra_serf lost pre-revprop-change hook output (issue #443)
+ * merge of non-empty subdir could be committed incorrectly (issue #1962)
+ * many other minor bugfixes, optimizations, plugs of memory leaks, etc
+
+ - Server-side bugfixes:
+ * segfault in svnserve and svnversion commands fixed (issue #2757)
+ * segfault when stopping httpd (if BDB repository) fixed (issue #2732)
+ * 'svnadmin dump' had a path ordering bug (issue #2641)
+ * better FSFS support for NFS v3 and lower (r24470)
+ * better FSFS support for some buggy NFS clients (r29448)
+ * authentication and authz bugs w.r.t. anonymous access (issue #2712)
+ * inconclusive authz result should deny, not allow (r23815)
+ * better reporting of problems parsing authz files (r22329)
+ * set svn:date revprop even if dumpstream does not (issue #2729)
+ * http:// commit can now create empty files properly (r25471, r25474)
+ * squelch not-a-directory errors in both FS backends (issue #2549)
+ * segfault on update-report response without base revision (issue #3023)
+ * 'svnserve --root PATH' checks that PATH exists (r22580, r22701)
+ * 'svnlook propget -t TXN_NAME' reports errors better (r22772)
+ * make location of mod_dav_svn activity database configurable (r24873)
+ * select only paths that are proper children of requested path (r25231)
+ * http:// commit error could leave empty transactions behind (r23594)
+ * 'svn switch --relocate' now works against unreadable repos root (r23848)
+ * many other minor bugfixes too numerous to list here
+
+ - Contributed tools improvements and bugfixes:
+ * svn_load_dirs.pl:
+ - Support global-ignores list (issue #2470)
+ - Allow "@" in filenames (r22203, Debian bug 359145)
+ - Add -no_auto_exe option (r26399)
+ * svnmerge.py:
+ - fixed: Always get end_rev from source instead of target (issue #2863)
+ - fixed: 'init' now chooses a better default revision range (issue #2810)
+ - fixed: Consider revs changing blocking status as reflected (issue #2814)
+ - Performance inmprovement (issue #2812)
+ - initialized revisions can be excluded (issue #2851)
+ * new 'svn-populate-node-origins-index' tool (issue #3024)
+ * new 'svn-merge-vendor.py' to assist in merging vendor branches (r23030)
+ * 'svn2rss.py' is now called 'svn2feed.py'
+ * svn2cl: New release 0.9 (r24498)
+ * commit-email.pl: various improvements (r22971, r22589)
+ * commit-email.rb: various improvements
+ * psvn.el: too many improvements and new features to list them all here
+ * dsvn.el: improve XEmacs compatibility (r24337)
+ * svn-tweak-author.py: make NEWAUTHOR argument optional (r24387)
+ * And more stuff that we just didn't have time to list. Enjoy.
+
+ Developer-visible changes:
+ * General:
+ - libsvn_ra_neon is new name for libsvn_ra_dav (to accommodate ra_serf)
+ - many abort() calls removed, replaced with error returns
+ - client and server now do capabilities exchange (r29358 et al)
+ - gen_win.py: auto-detect the path to the JDK on Windows (r24333)
+ * API changes:
+ - many, many new APIs and types as part of the new features in 1.5.0
+ - APIs to allow retrieving multiple revprops in one fetch (issue #2850)
+ - basic progress reporting for ra_svn (issue #901)
+ - new APIs for creating and using iterators (r26533)
+ - svn_fs_node_origin_rev finds line of history origin (issue #3017, #3024)
+ - svn_revnum_parse for parsing revision numbers (r26195)
+ - svn_path_is_canonical for validating paths (r26481)
+ - new API svn_fs_txn_root_base_revision() (r22610)
+ - pass individual arguments rather than config objects (r25182, r25190)
+ - clients can now extend HTTP User-Agent header (r28613)
+ - SVN_ERR_RA_DAV_PATH_NOT_FOUND is deprecated and no longer raised
+ * Bindings:
+ - Many improvements to all bindings (Java, Perl, Python, and Ruby)
+
+
+Version 1.4.6
+(21 Dec 2007, from /branches/1.4.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.4.6
+
+ User-visible changes:
+ - Client:
+ * fixed: unbounded memory use in "svn cat" over ra_svn (r26964, -8)
+ * fixed: 'svn diff --summarize file' displays erroneous output (issue #2765)
+ * fixed: 'svn status' wrong on previously-reverted deleted dir (issue #2804)
+ * fixed: 'svn up' can delete unversioned symlinks (issue #1808)
+ * fixed: use correct properties for locally replaced files (issue #2743)
+ * fixed: 'svn info -R $REPO_ROOT' w/ pre-1.2 svnserve broken (r26264)
+ * fixed: svnsync ignores '--config-dir' (r27056)
+ * datestamps can be localized (r26156)
+ * fixed: text base not updated when merging a replaced file (issue #2698)
+ * fixed: inverted 'switch --relocate' error message (r22355)
+ * fixed: sporadically failing file and directory removal on Windows (r25520)
+ * fixed: property file handling for schedule-delete files (r25833)
+ * fixed: allow invalid svn:eol-style values (r28331)
+ * fixed: 'svnadmin rmlocks' should error when no path provided (r28431)
+ * support neon 0.26.4 (r26077)
+
+ - Server:
+ * fixed: authz granted if calculation inconclusive (r23815)
+ * fixed: svndumpfilter crashes on Windows (r23494)
+ * fixed: wrong pointer type used for memset (r27263)
+ * fixed: invalid FSFS directory cache can corrupt repository (r27256)
+ * fixed: dir props on FSFS filesystem root never conflict (issue #2608)
+
+ - Client and Server:
+ * fixed: "No newline at end of file" message translated (issue #2906)
+ * use compressed delta encoding for 'svn blame' in svnserve (r26115)
+ * translation updates for Simplified Chinese
+
+ Developer-visible changes:
+ * svnserveautocheck.sh script is executable (r23942)
+ * add RHEL5 RPM (r25593)
+ * test suite passes with trunk servers (forwards-compatibility) (r25607)
+ * javahl bindings:
+ - improve error reporting from native code (r25208)
+
+
+Version 1.4.5
+(27 Aug 2007, from /branches/1.4.5)
+http://svn.apache.org/repos/asf/subversion/tags/1.4.5
+
+ User-visible changes:
+ * fixed: file placement vulnerability (Win32 clients only)
+ See CVE-2007-3846, and descriptive advisory at
+ http://subversion.apache.org/security/CVE-2007-3846-advisory.txt
+
+
+Version 1.4.4
+(30 May 2007, from /branches/1.4.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.4.4
+
+ User-visible changes:
+ - Client:
+ * fixed: 'svn up' of replaced file without history fails (issue #2618)
+ * fixed: 'svn export' succeeds on non-existent URL (r23191, -3, -5, -200)
+ * fixed: 'svn diff' fails writing large hunks to Win console (issue #1789)
+ * fixed: 'svn merge' shows 'G' notifications for unchanged files (r24483)
+ * fixed: svnsync cannot sync unreadable modified dir copies (issue #2705)
+ * fixed: ra_dav litters empty transactions if initial setup fails (r23594)
+ * fixed: inconsistent expansion of revision number keywords (issue #1743)
+
+ - Server:
+ * fixed: rare dirprop dataloss leading to BDB repo corruption (issue #2751)
+ * fixed: race condition when changing FSFS revprops (r23439, r23440)
+ * fixed: 'svnadmin load' invents svn:date if none exists (issue #2729)
+ * fixed: svnserve can't commit locked file if root unwritable (issue #2700)
+ * fixed: 'svnadmin dump' output invalid for non-ASCII paths (issue #2641)
+ * fixed: security flaw in 'svn prop*' commands [CVE-2007-2448]
+ (r25095, -099, -104, -105, -10)
+
+ - Client and Server:
+ * fixed: hang during character translation (r23491, r23492)
+ * translation updates for Simplified Chinese, Japanese, and Norwegian
+
+ Developer-visible changes:
+ * new "make svnserveautocheck" testing target (r23558)
+ * fixed: ra_serf fails checkout if access to repos root is forbidden (r23846)
+ * fixed: svn_client_cat2() doesn't accept WORKING as a revision (r23556)
+ * javahl bindings:
+ - fixed: potential segfault in initialisation (r23383)
+ - fixed: SVNClientSynchronized.logMessages() isn't synchronised (r23978)
+ - fixed: SVNClient.info2() misreports itself as unlock in errors (r24219)
+ * SWIG/perl bindings:
+ - fixed: ra_do_{update,switch,status} don't work with Perl delta editors
+ (r20667, r22311)
+ * SWIG/python bindings:
+ - fixed: memory leak whenever C APIs returned errors (r23521)
+ * SWIG/ruby bindings:
+ - fixed: typos in method Svn::Wc#merge_prop_diffs and docs (r23405, -6)
+
+
+Version 1.4.3
+(18 January 2007, from /branches/1.4.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.4.3
+
+ User-visible changes:
+ - Client:
+ * fixed: crash using automatic auth protocols with Neon 0.26 (r22440, -61)
+ * fixed: svn_load_dirs.pl cannot import file names containing '@' (r22203)
+ * fixed: error when committing replaced directories (r22991, -8)
+ * fixed: inability to change file perms due to existing file perms (r23018)
+ * include newest version of svn-graph.pl (r22969)
+
+ - Server:
+ * fixed: incorrectly reporting authz circular dependencies (issue #2684)
+ * fixed: potential filesystem memory leak in commit finalisation (r22729)
+
+ - Client and Server:
+ * fixed: crash in character translation, particularly on Windows (r22417)
+ * fixed: potential string corruption when resizing string buffers (r22689)
+ * translation updates for Korean, Spanish, Italian, Simplified Chinese,
+ and Japanese (fixing issues #2649 and #2681)
+
+ Developer-visible changes:
+ * support Neon 0.26.2 (issue #2666)
+ * update (experimental) ra_serf repository access module for DAV (r22872)
+ * Windows installer improvements (r21516, r22155, r22224)
+ * fixed: svn_{ra,repos}_replay() doesn't send checksums (r22346, -51, -52)
+ * fixed: error when calling svn_repos_replay2() with a txn root (r22609)
+ * fixed: Solaris packaging script broken (issue #2669)
+ * javahl bindings:
+ - fixed: auth cache is created in the current directory (r22780)
+ - fixed: SVNAdmin's setLog() method always fails (r22387)
+ - fixed: target dependency order in generated build scripts (r22209)
+ * SWIG/perl bindings:
+ - fixed: memory leak when calling methods on a Perl commit editor (r22332)
+
+
+Version 1.4.2
+(2 November 2006, from /branches/1.4.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.4.2
+
+ User-visible changes:
+ - Client:
+ * new "notes/svnsync.txt" file explains common svnsync usage
+ * install a manpage for svnsync (r21403)
+ * install/package svnsync on Windows (r21387, r21424)
+ * translation updates for all languages
+ * dramatically speed up commit of wc-to-wc copy (r21471)
+ * fixed: support 'svn co URL@{DATE}' (issue #2602)
+ * fixed: cannot access repositories with spaces via svn:// (issue #2612)
+ * fixed: passing full URL in some DAV requests, breaking proxies (r21526)
+ * fixed: history-tracing can fail for renamed directories (issue #2600)
+ * fixed: crash if interrupted while opening a working copy (r21792)
+ * fixed: 'svn merge' should notify about conflicted files (issue #2584)
+ * fixed: 'svn revert' should notify about prop-only reverts (issue #2517)
+ * fixed: 'svn status -u' not showing props changed on wc root (issue #2533)
+ * fixed: 'svn status -u' fails in a read-only working copy (r21904, -19)
+ * fixed: 'svn up' failing with checksum mismatch error (issue #2618)
+ * fixed: 'svnsync sync' copying missing implicit revprops (issue #2613)
+ * fixed: svnsync unable to synchronise copies of URL-unsafe paths (r22092)
+ * svnshell tool: support "setrev head" (r20992)
+ * include newest version of svnmerge.py
+
+ - Server:
+ * FSFS: improve detection of disk write errors (r21346)
+ * FSFS: prevent API violation from corrupting repository (issue #2467)
+ * improved error checking when running hook scripts, etc (r21483)
+ * mailer.py: new commit_url option links to web page for a commit (r21333)
+
+ Developer-visible changes:
+ * support Neon 0.26.0 and 0.26.1 (r21289, r21293, r21956)
+ * support current CVS versions of libtool (post-1.5.22) (r22120)
+ * now compiles on architectures without APR_HAS_DSO (e.g. RISC OS) (r21473)
+ * fixed: build error on FreeBSD due to missing svnsync manpage (r21403)
+ * RHEL3 RPM package requires correct version of Apache httpd (r21974)
+ * numerous improvements to coverage of the test suite
+ * javahl bindings:
+ - compile Java bytecode for Java 1.2 VM (r21765, -7, r21814)
+ - fixed: crash if using 1.4.x bindings with older libraries (r21316, -429)
+ - fixed: crash when empty destination path passed to checkout (r21770)
+ * SWIG/ruby bindings:
+ - fixed: accept nil for Svn::Repos#load_fs's parent_dir argument (r21793)
+ * SWIG/python bindings:
+ - fixed: crash when using an apr_hash_t typemap (issue #2606)
+ - fixed: in tests, use URLs that work on Windows (r21392)
+ * SWIG/perl bindings:
+ - fixed: ra_replay works with Perl delta editors (r20666)
+
+
+Version 1.4.1
+(Not released, see changes for 1.4.2.)
+
+
+Version 1.4.0
+(10 September 2006, from /branches/1.4.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.4.0
+
+ User-visible changes:
+ - Client:
+ * new 'svnsync' commandline tool for repository replication
+ * numerous working copy improvements (WARNING! upgrades to new format!):
+ - improved performance when detecting modified files (r18628 -56)
+ - new property storage is faster and uses less disk space (r17583)
+ - internal wcprops take up less space (r19433 -37)
+ - large file commit speedups (r17861 -73 18867 -918 -29 -44 -45 -48 -49)
+ - reduce memory usage for large working copies (r19183 -538)
+ - increased working copy stability with merge, copy and move:
+ (fixes issues #845, #1516, #1553, #2135, #2144, #2148)
+ * new switches added:
+ - 'svn blame --force' (issue #2509)
+ - 'svn diff/merge -c/--change' (r17054 -6 -68 18568 -741)
+ - 'svn diff --summarize' (issue #2015)
+ - 'svn merge/blame -x' (r18716 -20) (r18602 -857)
+ * 'svn log' now supports peg revisions (issue #2287)
+ * 'svn export' now creates intermediate directories if needed (r20030)
+ * use switch/relocate when svn:externals updated (issue #2209)
+ * internal diff can ignore whitespace and eol style changes (issue #2121)
+ * conflict markers now match the file's eol style (issue #1325)
+ * new svn2cl, svn-viewdiff and svn-resolve contrib scripts
+ * numerous improvements to svnmerge.py, vc-svn and psvn
+ * translation updates for all languages
+ * Mac OS X: store cached passwords encrypted in Keychain (r17619 -43)
+ * fixed: 'svn ls' slow over ra_dav (issue #2151)
+ * fixed: 'svn import' not handling eol-style correctly (issue #2433)
+ * fixed: 'svn blame' should default operative rev range to peg rev (r18400)
+ * fixed: 'svn blame' ignores eol-style (issue #2431)
+ * fixed: 'svn checkout' should default operative rev to peg rev (r18422)
+ * fixed: 'svn diff' supports all eol styles (r17624 -8 -61 18195 -392)
+ * fixed: 'svn diff' multi-target memory leak (r17518)
+ * fixed: 'svn merge' showing wrong status with external diff3 (issue #1914)
+ * fixed: 'svn merge' not merging added dir into deleted dir (issue #2515)
+ * fixed: 'svn rm' of non-existent item should fail (issue #2440)
+ * fixed: 'svn status' should skip unversioned files (issue #2030)
+ * fixed: 'svn status' shows added and conflicted files as added (r20382)
+ * fixed: 'svn switch --relocate' may set wrong repos root (r17031)
+ * fixed: 'svn switch --relocate' memory leak (r19535)
+ * fixed: 'svn switch --relocate' not caching passwords (issue #2360)
+ * fixed: 'svn info' not showing locks sometimes (r19777)
+ * fixed: incorrect merge of add of binary file already in WC (issue #2403)
+ * fixed: possible dataloss if editing immediately after merge (r20609 -12)
+ * fixed: lots of diff wc<->repos bugs
+ * fixed: unfriendly error message on propget on nonexistent path (r19399)
+ * fixed: spurious revert report after manual conflict removal (issue #2517)
+ * fixed: don't allow -rPREV on schedule add path (issue #2315)
+ * fixed: keywords with dollar signs cause badness (issue #1780)
+ * fixed: really revert file with locally modified keywords (issue #1663)
+ * fixed: deleting schedule add file leaves working props file (issue #2419)
+ * fixed: svn:needs-lock and read-only-ness not always in sync (issue #2306)
+ * fixed: post-commit error output not sent to the client (issue #443)
+ * fixed: not locked error on commit of switched path (issue #2353)
+ * fixed: svn_apply_autoprops.py should trim whitespace from props (r20790)
+ * fixed: show locking notifications in local path style (r20927)
+ * fixed: encoding error on error messages from invalid options (r20883)
+
+ - Server:
+ * support for new 'svnsync' repository mirroring utility
+ * support for BDB 4.4, including automatic recovery (issue #2449)
+ * new contrib hook scripts:
+ - enforcer
+ - detect-merge-conflict.sh
+ - case-insensitive.py
+ * new tools script svn-backup-dumps.py
+ * new tools hook script log-police.py
+ * svnserve improvements:
+ - can now run as a native Windows service (r18855)
+ - new option --pid-file (r17836)
+ - allow the password database to be read-only (r16840)
+ * mod_dav_svn improvements:
+ - fixed: error conversion crash (r19516)
+ - fixed: unfriendly error when locking already locked path (issue #2275)
+ - fixed: xml escaping bugs (r19760 -85 -86)
+ * authorization improvements:
+ - new mod_dontdothat apache module (r19531)
+ - new mod_authz_svn directive AuthzSVNNoAuthWhenAnonymousAllowed (r18680)
+ - error out when authz rules contain unexpected characters (r19471)
+ * support .wsf hook scripts on Windows (r18972, 19076)
+ * lots of improvements to mailer.py and commit-email.pl
+ * FSFS back-end performance improvements (r17125 19119 -456 -58 -59)
+ * fixed: 'svnadmin verify' output not in native encoding (issue #1997)
+ * fixed: uuid file in FSFS could be destroyed on write error (issue #2193)
+ * fixed: FSFS path encoding bug (r17774)
+ * fixed: don't crash on corrupt repositories (r17625)
+ * fixed: expect error output from hook scripts in native encoding (r17101)
+ * fixed: catch errors starting hook scripts (r16891 17041 -81)
+ * fixed: svnserve and authz can cause broken WCs (issue #2566)
+ * fixed: the default hook script templates should be vanilla sh (r20796)
+
+ - Both:
+ * delta compression improvements:
+ - new delta encoding reduces size (r18363 -94 -66 -78 -98 -99 -457 -950)
+ - xdelta algorithm speed improvements (r18986, 19047)
+ * don't bail on invalid locale (r19445)
+ * improve speed of non-verbose svn ls (r17067 -71)
+ * fixed: delta combiner reading past EOF (r17743)
+
+ Developer-visible changes:
+ * require APR >= 0.9.7 to improve error detection for FSFS repos (r19915)
+ * require zlib, for svndiff1 delta encoding (r18363)
+ * support SWIG 1.3.29 (r19968)
+ * support autoconf 2.60-dev (r19919 20632 -36)
+ * removed no-longer-supported Red Hat 7.x RPMs (r20462)
+ * add support for building RPMs for x86-64 architecture (r20548 -552)
+ * numerous improvements to gen-make.py build system, especially on win32
+ * removed Visual Studio.NET APR 0.9 project files (r20170)
+ * numerous improvements to the test suite
+ * new public APIs:
+ - keyword / eol translation helpers and generic streams (see svn_subst.h)
+ - new generic stream helpers (see svn_io.h)
+ - authn providers made available to other clients (see svn_auth.h)
+ - svn_cmdline_setup_auth_baton
+ - svn_dso_initialize, svn_dso_load
+ - svn_client_diff_summarize and svn_client_diff_summarize_peg
+ - svn_client_list
+ - svn_config_has_section
+ - svn_txdelta_compose_windows and svn_txdelta_apply_instructions
+ - svn_txdelta_stream_create
+ - svn_diff_file_options_create and svn_diff_file_options_parse
+ - svn_err_best_message
+ - svn_compat_wrap_commit_callback
+ - svn_uuid_generate
+ - svn_user_get_name and svn_user_get_homedir
+ - svn_io_get_dir_filenames
+ - svn_ra_reparent
+ - svn_ra_replay
+ - svn_wc_revision_status
+ - several rev'd APIs, see doxygen docs
+ * flush stdout after each status/notification line (r19476 -656)
+ * new (experimental) ra_serf repository access module for pipelined DAV
+ * .svn/entries use a less verbose non-xml format (r19420)
+ * make recursive 'svn ls' streamy (issue #1809)
+ * remove svn-config script
+ * empty-file and README.txt removed from WC admin areas (r17181 -268 -364)
+ * replace cmdline client XML DTDs with RNG schemas (r16379 -80 -93 -571 17248)
+ * fixed: log --limit against old svnserve leaves unusable session (r19638)
+ * fixed: Solaris build problems (r19636)
+ * fixed: blame of WORKING revision shouldn't give BASE (r19558)
+ * fixed: svn_client_copy and _move should fail if target exists (issue #2188)
+ * fixed: svn_io_file_rename and readonlyness on Windows and UNIX (r17366 -69)
+ * fixed: ra_dav memory leak when reusing session (issue #2247)
+ * fixed: console character encoding problems when built with VS2005 (r20108)
+ * fixed: various problems with --enable-dso and global pools (r20996, r20999)
+ * fixed: installer file syntax error in new versions of Inno Setup (r21022)
+ * SWIG bindings:
+ - SWIG/python bindings:
+ - new support for svn_client_info (r19413)
+ - SWIG/ruby bindings:
+ - full support for Subversion 1.4 APIs, including :
+ svn_ra_replay and svn_diff_summarize
+ - numerous bug fixes
+ - add ruby documentation (make install-swig-rb-doc) (r20166)
+ - add APIs for adding a provider (r21079)
+ - SWIG/perl bindings:
+ - new support for svn_client_info (r18758)
+ - minor corrections to SVN::Fs (r19312)
+ * javahl bindings:
+ - APIs to get version info for the native libraries (r17604 -07)
+ - API for path validation (r18989, r19079)
+ - C++/Java code refactoring, cleanup, and consolidation
+ - fixed: handle possible errors from date/time conversions (r17213)
+ - fixed: SVNClient username/password JVM crash on null input (r19803 -13)
+ - fixed: specify default UUID load action (r18030)
+ - fixed: compile error on Visual Studio 2005 (r18054)
+
+
+Version 1.3.2
+(23 May 2006, from /branches/1.3.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.3.2
+
+ User-visible changes:
+ - Client:
+ * fixed: 'svn st -u' crash on missing subdirs (r19348, -71, issue #2551)
+ * fixed: leaving stray working copy locks on cancellation (r18893)
+ * fixed: svn_load_dirs.pl trying to import .svn and _svn dirs (r18549)
+ * svn_load_dirs.pl symlink support (issue #2478)
+ * translation updates to Japanese, Traditional Chinese.
+
+ - Server:
+ * fixed: mod_dav_svn memory leak when listing large dirs (r19528)
+ * fixed: mod_dav_svn crash on valid request (r19520)
+ * fixed: svnserve protocol error in lock, causing client hang (issue #2548)
+ * mailer.py: add Content-Transfer-Encoding header (r19319)
+ * mailer.py: fixed: named substitutions incorrectly ignored (r18114, -681)
+ * fixed: authz requires read access for root for writes (issue #2486)
+ * svnauthz-validate: add config file validation tool (r18504, -09)
+
+ Developer-visible changes:
+ * fixed: tests don't catch repository creation failure properly (r19149,-51)
+ * support SWIG 1.3.28
+ * support APR 0.9.x >= 0.9.10 (r19039, -57, -60)
+ * python bindings:
+ - fixed: link error on OpenBSD (r18983)
+ * ruby bindings:
+ - fixed: memory leak (r19493)
+ - fixed: NULL argument conversion bug (r19543)
+
+
+Version 1.3.1
+(25 March 2006, from /branches/1.3.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.3.1
+
+ User-visible changes:
+ - Client:
+ * fixed: segfault moving unversioned files (issue #2436)
+ * fixed: verbose list broken over ra_dav (issue #2442)
+ * fixed: 'svn ci -m path_name' not requiring '--force-log' (r17956)
+ * fixed: crash on mixed-case https URL scheme (r18042)
+ * fixed: crash in status with ignored directories (r18291)
+ * fixed: strip peg rev from default checkout directory (r18416)
+ * fixed: diff crash with non-recursive checkout (r17231, 18539, -41)
+ * fixed: 'svn ls' URL encoding bug with locks (r18665, -68)
+ * fixed: unlock circumvents lock token check (r18691, -94)
+ * fixed: repos-to-repos copy crash (r18451)
+ * fixed: 'svnmerge' utility improvements (r18811)
+ * translation updates for German, Swedish and Norwegian
+
+ - Server:
+ * fixed: set svn:date at the end of commit in fsfs (r18078)
+ * fixed: don't wait for hook script background jobs (r18146)
+ * fixed: mod_dav_svn should log the whole error chain (r18211)
+ * fixed: uncomment section headers in repos config files (r18247, -50)
+ * fixed: log scalability issues with many paths (r18395, -404)
+ * fixed: better path input validation in mod_dav_svn (r18660)
+ * fixed: assert in copy in fsfs and bdb (issue #2398)
+ * fixed: RPM package bad interaction with NFS servers (issue #1456)
+
+ - Both:
+ * fixed: copyright years updated to include 2006 (r18021, -127)
+
+ Developer-visible changes:
+ * fixed: missing #include (r18065)
+ * fixed: allow building with Neon 0.25.5 (r18215)
+ * fixed: error leaks (18196, -249)
+ * javahl bindings:
+ - fixed: compile error on Visual Studio 2005 (r18054, -55)
+ * python bindings:
+ - fixed: libsvn_swig_py link problem on Solaris 10 (r17910)
+ - fixed: pool lifetime bug (r17992)
+ - fixed: memory leak (r18230)
+ - fixed: race condition during application pool initialization (r18721)
+ - fixed: Make pool parameters optional (issue #2444)
+ * ruby bindings:
+ - fixed: pool management issue (r17795, -811)
+ - fixed: protect baton from garbage collection (r17627)
+ - fixed: conversion bug (r17726, -925)
+ - fixed: compile errors with SWIG 1.3.24 (r18456, -58)
+
+
+Version 1.3.0
+(30 December 2005, from /branches/1.3.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.3.0
+
+ User-visible changes:
+ - Client:
+ * 'svn ls -v' now shows remote locks (issue #2291)
+ * 'svn status' speedup (r15061, r15103)
+ * 'svn blame' speedup on files with long history (issue #1970)
+ * 'svnversion' now assumes default argument of '.' (r14892)
+ * support for neon 0.25.x, which fixes http:// control-c bug (issue #2297)
+ * support for more ISO-8601 date formats, compatible with GNU date (r14428)
+ * support for single-digit date components (r15459)
+ * on Windows, '_svn' admin dir now toggled by runtime env. variable (r16244)
+ * working copy size with empty propfiles reduced (r16855, see releasenotes)
+ * new switches added:
+ - 'svn blame --xml [--incremental]' (r14690)
+ - 'svn status --xml [--incremental]' (issue #2069)
+ - 'svn info --xml [--incremental]'
+ - 'svn add/import --no-ignore' (issue #2105)
+ - 'svnlook tree --full-paths' (r13976)
+ - 'svnlook diff --diff-copy-from' (r14855)
+ - 'svnlook changed --copy-info' (r16681)
+ * fixed: 'svn copy wc URL' might include deleted items (issue #2153)
+ * fixed: 'svn copy wc wc' allows cross-repository copies (issue #2404)
+ * fixed: 'svn up/merge' major property-merging bugs (issue #2035)
+ * fixed: 'svn merge' insisting on write access to '.' (issue #2411)
+ * fixed: 'svn merge' cross-device move problems (r16293, -329, -330)
+ * fixed: 'svn diff' outputs headers in wrong encoding (issue #1533)
+ * fixed: 'svn proplist/add/cat' dies on unversioned items (issue #2030)
+ * fixed: 'svn add' not honoring svn:ignore property (issue #2243)
+ * fixed: 'svn log -rN:M --limit X' error over http:// (issue #2396)
+ * fixed: 'svn switch --relocate' failure on 'deleted' dir (r16673)
+ * fixed: 'svn info' not always showing repos lock (issue #2276)
+ * fixed: 'svn info' might show lock on wrong path (r16626)
+ * fixed: 'svnlook' chokes on logs with inconsistent newlines (r14573)
+ * fixed: 'svnlook propget --revprop -t' failure (r15203)
+ * fixed: 'svnversion' wrongly traverses into externals (r15161)
+ * fixed: incorrect URI encoding passed to svn+ssh:// (issue #2406)
+ * fixed: properly handle filenames containing '@' (issue #2317)
+ * fixed: '--non-interactive' now suppresses launch of $EDITOR (r15277)
+ * fixed: conflict markers not in current encoding (r14621)
+ * fixed: commands ignoring extraneous -m or -F switches (issue #2285)
+ * fixed: poor error-checking when using revprops (r15542)
+ * fixed: stack-smashing bugs (r15948, r16037)
+ * fixed: incorrect parsing of mod_dav_svn XML responses (r17589)
+ * translation updates for all languages
+
+ - Server:
+ * svnserve improvements:
+ - can now restrict read/write access by path (see releasenotes)
+ - undeprecation of the --read-only (-R) option (r17614)
+ * mod_dav_svn improvements:
+ - 'SVNListParentPath on' shows all repositories in web browser (r16158)
+ - ability to log high-level client operations (see releasenotes)
+ - sets svn:mime-type on autoversioning commits (r14359)
+ * 'svn log' performance improvement (r14722)
+ * fixed: fs history algorithm might return wrong objects (issue #1970)
+ * fixed: repos deadlock when hooks output too much (issue #2078)
+ * fixed: mod_dav_svn displays errors with sensitive paths (r14792)
+ * fixed: anonymous reader could create empty commits (issue #2388)
+ * fixed: possible segfault to callers of trace_node_locations() (r16188)
+ * fixed: BDB-style locking actions on FSFS repositories (r16295, r16297)
+ * fixed: numerous bugs running BDB commands on FSFS (issue #2361, r16388)
+ * fixed: svndumpfilter incorrectly remapping dropped revs (issue #1911)
+
+ - Both:
+ * faster multiple (un)locks in a single svn:// request (issue #2264)
+ * the Subversion Book is no longer bundled (r17466)
+
+ Developer-visible changes:
+ * reorganization of automated tests, including ability to run on ramdisk
+ * lots of Doxygen/API documentation cleanup
+ * numerous improvements to gen-make.py build system, especially on win32
+ * working copy is now storing repos_root as separate field (issue #960)
+ * keywords are now stored in an internal hash (issue #890)
+ * client status APIs now makes more server-side info available (r16344)
+ * new public APIs:
+ - new transfer progress callback for DAV (r15948)
+ - svn_ra_initialize(), svn_client_open_ra_session()
+ - svn_fs_closest_copy(), svn_fs_type()
+ - several rev'd APIs, see doxygen docs
+ * SWIG bindings: No more compile-time or runtime SWIG dependencies
+ - SWIG/python bindings:
+ - automatic memory management: APIs no longer require pool arguments!
+ - improved stability, as shown by our new testsuite
+ - better error messages
+ - SWIG/ruby bindings:
+ - complete API coverage!
+ - automatic memory management
+ - greatly expanded test suite
+ - SWIG/perl bindings:
+ - new accessors for svn_lock_t, svn_fs_access_t
+ - a number of bugfixes
+ * javahl bindings:
+ - add streamy API for fetching file contents (r15584)
+ - fixed: let tests run before bindings are installed (issue #2040)
+ - fixed: lock command not raising errors properly (issue #2394)
+ - fixed: ignored errors from svn_client_blame2() (r16434)
+
+
+Version 1.2.3
+(19 August 2005, from /branches/1.2.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.2.3
+
+ User-visible changes:
+ - Client:
+ * fixed: 'svn status -u' fails against pre-1.2 mod_dav_svn (r15359, r15423)
+ * fixed: 'svn export' segfault (r15516)
+ * fixed: 'svn merge' memory leak (r15233)
+ * fixed: horrible rename-tracing performance against 1.0 servers (r15315)
+ * fixed: 'svn cat' over file:// -- small leak (r15253)
+ * fixed: crash with "svn lock" and authentication (r15703)
+ * improvements to 'svnmerge' utility (r14008,-458,-587,-632, r15329,-340)
+ * translation updates for French, German, Polish, Norwegian, Swedish,
+ Korean
+
+ - Server:
+ * fixed: mod_authz_svn being overly restrictive (r15463)
+ * fixed: fsfs directory caching bug (r15705, r15742)
+
+ - Both:
+ * fixed: crash when >50 options passed to any commandline app (r15251)
+ * fixed: memory leak in character translation handle caching (r15379,-398)
+
+ Developer-visible changes:
+ * fixed: crash when calling svn_client_(un)lock with no targets (r15734)
+ * rhel-4 RPM bugfix for python bindings (r15616)
+ * missing #include in SWIG bindings (r15683)
+ * javahl bindings:
+ - fixed: JNI library loading bug (r15552)
+ - fixed: JNI stack-name cut and paste error (r15337)
+ - fixed: crash when revisions have no dates (r15737)
+ * perl bindings:
+ - now compatible with SWIG 1.3.25 (r15248)
+ - allow SVN::Pool to be used as pool parameter (r15450)
+ - make SVN::Delta::Editor friendlier for debugging (r15609)
+ - fixed: wrap svn_ra_stat properly (r15713)
+ - fixed: bug in SVN::Core::Stream's read function (r15698, r15700)
+ * ruby bindings:
+ - now compatible with SWIG 1.3.25 (r14980, r15361)
+
+
+Version 1.2.2
+(Not released, see changes for 1.2.3.)
+
+
+Version 1.2.1
+(5 July 2005, from /branches/1.2.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.2.1
+
+ User-visible changes:
+ - Client:
+ * fixed: 'svn lock' on switched file locks wrong thing (issue #2307)
+ * fixed: 'svn (un)lock' errors on multiple targets (r14736, 14775)
+ * fixed: 'svn (un)lock' problems with URI-unsafe names (issue #2314)
+ * fixed: 'svn (un)lock' not caching authentication (r15088)
+ * fixed: 'svn unlock' loses executable bit (r14859, r14923, r14939)
+ * fixed: 'svn unlock URL' segfault (r14893)
+ * fixed: 'svn commit' failure on XML-unsafe locked paths (issue #2335)
+ * fixed: recursive directory copy bug (issue #2343)
+ * fixed: don't initialize RA library in 'svnversion' (r14755)
+ * fixed: svn-push segfault (r14732)
+ * various translation updates for localized client messages
+
+ - Server:
+ * fixed: 'svn log' performance regression, general (r14116, 14772, 14759)
+ * fixed: 'svn log -v' performance regression, FSFS-specific (r15016)
+ * fixed: mod_dav_svn bug sets content-type incorrectly (r15046)
+
+ Developer-visible changes:
+ * fixed: win32 innosetup's add/repair/remove features (r14830)
+ * fixed: OBOE with 'limit' parameter of svn_repos_get_logs3(). (r15119)
+ * redhat RPM fixes (r15050)
+ * perl bindings:
+ - accessors for svn_lock_t (r15082)
+ - call utf_initialize, adjust global pool usage (r15076, r15080,
+ r15081, r15117)
+
+
+Version 1.2.0
+(21 May 2005, from /branches/1.2.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.2.0
+
+See the 1.2 release notes for a more verbose overview of the changes since
+the 1.1 release: http://subversion.apache.org/docs/release-notes/1.2.html
+
+ User-visible changes:
+ - Client:
+ * add peg-rev syntax to co/blame/cat/ls/pget/plist/export (issue #1093)
+ * 'svn info' now works on URLs (r13123, 13144)
+ * 'svn* --version' now shows available repository back-ends (r13761)
+ * new fixed-length keywords (for placement in binary files) (issue #2095)
+ * on Windows, disk-cached passwords are now encrypted (r13888)
+ * performance improvements:
+ - 'svn status' does much less disk parsing (r11677, 11704)
+ - 'svn st -u' no longer asks server to generate textdeltas (issue #2259)
+ - 'svn revert -R' doing much less work (r13883)
+ - utf8<->native conversions are faster now (issue #2016)
+ * new switches added:
+ - 'svn commit --no-unlock - retain lock in wc upon commit
+ - 'svn log --limit N' - show only first N log messages
+ - 'svn info --revision' - show info on older object (r13265)
+ - 'svn list --xml' - output listing in XML
+ - 'svn propset --force' - allow unusual propsets (#2065)
+ - 'svn diff --force' - show diffs on binary files (#2099)
+ - 'svn co/up/st --ignore-externals' - skip over externals (#2189)
+ - 'svn export --non-recursive' - don't export subdirs (issue #2228)
+ - 'svnversion --help' - show help (r13128)
+ * fixed: 'svn merge' fails to add symlinks or expand keywords (issue #2064)
+ * fixed: 'svn merge --dry-run' shows spurious 'skip' messages (issue #1943)
+ * fixed: 'svn merge' file-not-found' error (issue #1673)
+ * fixed: 'svn merge' of propchanges into deleted file (issue #2132)
+ * fixed: 'svn merge' on implicit target with space (r13010)
+ * fixed: 'svn merge/diff URL URL' can cause httpd timeout (issue #2048)
+ * fixed: 'svn switch/update' failure might corrupt wc (issue #1825)
+ * fixed: 'svn up' should rm before add, helps case-insensitivity (r12616)
+ * fixed: 'svn up -rX' causes file to be unrestorable (issue #2250)
+ * fixed: 'svn copy wc wc' should keep .svn/ hidden (issue #1739)
+ * fixed: 'svn copy wc wc' of deleted=true doesn't delete (issue #2101)
+ * fixed: 'svn copy' shouldn't copy into schedule-delete area (issue #2020)
+ * fixed: 'svn copy dir dir' infinite recursion (issue #2224)
+ * fixed: 'svn log' throws error on unversioned target (issue #1551)
+ * fixed: 'svn log' in r0 working copy shows r1 log msg (issue #1950)
+ * fixed: 'svn export' bugs on deleted dirs or nonexistents (#2226, r13226)
+ * fixed: 'svn export' on single file from working copy (issue #1708)
+ * fixed: 'svn import' creating an empty revision (r14293)
+ * fixed: 'svn commit' ignores --encoding when editing externally (#2244)
+ * fixed: 'svn commit' log message lost if utf8-conversion failure (r13230)
+ * fixed: 'svn diff' output encoding bug (r11461)
+ * fixed: 'svn diff' showing prop-diffs on repos root dir (r13381-2)
+ * fixed: 'svn diff' label reversal (issue #2033)
+ * fixed: 'svn propget' prints extra newline in --strict mode (r14505)
+ * fixed: 'svn propset' should skip unversioned files (#2030)
+ * fixed: 'svn rm URL1 URL2 URL3...' huge memory usage (issue #2218)
+ * fixed: 'svn mkdir' cleanup after failure (r11883)
+ * fixed: 'svn status -u' crash in non-recursive wc's (issue #2122)
+ * fixed: 'svn revert' should skip unversioned items (issues #2030, 2133)
+ * fixed: 'svn revert' should suggest --recursive (issue #2114)
+ * fixed: 'svn add/import' better detects invalid paths (issue #1954)
+ * fixed: 'svn cleanup' should repair timestamps (r12012)
+ * fixed: 'svn cat -rBASE' contacts repository (issue #1361)
+ * fixed: fuzzily escape control-characters when sending over dav (#2147)
+ * fixed: prevent client from manipulating svn:wc:* properties (r12523)
+ * fixed: allow portnumber in svn+ssh://user@host:port/ URLs (r14373)
+ * fixed: xml-escaping bugs over dav (r11090)
+ * fixed: store symlinks as utf8, always work in non-utf8 locale (r11358-9)
+ * fixed: bug in special-file detranslation (r11441)
+ * fixed: show paths in local-style where we weren't (issue #1538)
+ * fixed: detect invalid propnames better (issue #1832)
+ * fixed: entire error stack not being printed (issue #1822)
+ * fixed: improper utf8 conversion of revision strings (issue #1999)
+ * fixed: use-commit-times timestamp bug (r12906)
+ * fixed: don't comment out section-names in default config file (r11771)
+ * more support for user-cancellation (r13083-4, 13086)
+ * improved error messages (r12920, 11392, 11599, 11913, #2154, #2214)
+
+ - Server:
+ * mod_dav_svn autoversioning feature now complete (see release notes)
+ * 'svnadmin create' now creates FSFS repositories by default (r13624)
+ * new pre/post-revprop hook argument to describe propchange (r12162)
+ * mod_authz_svn groups can now contain other groups (issue #2085)
+ * 'svnadmin recover' now creates default svnserve passwd file (r11589)
+ * increase default BDB cache size in DB_CONFIG (r13030)
+ * new switches added:
+ - 'svnlook diff --no-diff-added' - suppress added files (#2180)
+ - 'svnlook propget/proplist --revprop' - show revision props (#2181)
+ - 'svnadmin load --use-pre-commit-hook'
+ 'svnadmin load --use-post-commit-hook'- invoke hooks when loading
+ * fixed: FSFS race condition on posix platforms (issue #2265)
+ * fixed: change FSFS revprops atomically and safely (issue #2193)
+ * fixed: FSFS should verify checksums (issue #2253)
+ * fixed: FSFS crash bug (r14333)
+ * fixed: 'svnadmin create' should clean up when it fails (r13200)
+ * fixed: 'svnadmin load' compatibility on pre-0.14 dumpfiles (r12075)
+ * fixed: 'svnadmin load' crashes on contentful rev 0 (issue #1674)
+ * fixed: 'svnadmin dump' should write in console encoding (issue #1997)
+ * fixed: check for null-streams in dump/load code (r10510)
+ * fixed: hook script ignored when symlink is broken (issue #1700)
+ * fixed: hook script may inherit server's stdin stream (r12155)
+ * fixed: potential svnserve segfault (r13199)
+ * fixed: svnserve handling mutually-exclusive options (issue #2251)
+ * fixed: mod_authz_svn should log errors to httpd errorlog (issue #2182)
+ * fixed: 'svnadmin hotcopy' failed to copy format files (r14678, r14683)
+ * mailer.py: add win32 compatibility, plus other bugfixes
+
+ - Both:
+ * new 'locking' feature (issue #1478, see release notes for details):
+ - new: 'svn lock/unlock', 'svnadmin lslocks/rmlocks', 'svnlook lock'
+ - new: 'svn:needs-lock' property to enable communication
+ - 'svn st [-u]' shows local or remote lock overview
+ - 'svn info wc | URL' shows local or remote lock details
+ - 'svn commit' sends locks, 'svn up' removes stale locks
+ - new hook scripts: pre-lock, pre-unlock, post-lock, post-unlock
+ * speedups for 'svn blame' and other commands (see xdelta in release notes)
+ * fixed: make both svnserve and svn:// urls work with IPv6 (r13235-6)
+ * fixed: updating xml-unsafe dirname over http (issue #2268)
+ * new translation of localized messages: French
+ * continued improvement of localized message translations:
+ - German, Spanish, Polish, Brazilian Portuguese, Norwegian Bokmål,
+ Swedish, Traditional Chinese, Simplified Chinese, Korean, Japanese
+ - more localized messages in all svn-related binaries
+
+ Developer-visible changes:
+ * binary diff algorithm now defaults to xdelta instead of vdelta
+ * huge number of new APIs:
+ - new locking APIs in svn_client.h, svn_ra.h, svn_repos.h, svn_fs.h
+ - new 'flattened' svn_ra.h API, which imitates svn_fs.h (issue #1931)
+ - new notification API in svn_client.h, svn_wc.h
+ - http://svn.haxx.se/dev/archive-2005-04/0319.shtml has all API changes
+ * fs now has its own 'format' file, independent of repos 'format' (r13387)
+ * improve efficiency of delta combining algorithm (r13016, r13063)
+ * make all BDB apis take explicit pool parameters (r13198, r13205)
+ * remove libsvn_fs_base caching of node revisions (r13299)
+ * libsvn_repos commit editor can now take incoming txn (r13733)
+ * fixed: mod_dav_svn sending illegal editor-drive (issue #2258)
+ * pool usage improvements (r12954, 12852, r13386, issue #1310)
+ * SWIG bindings: better API coverage overall.
+ - new ruby bindings!
+ - remove bitrotting swig-java bindings
+ - perl and python bindings: numerous improvements, see their own logs.
+ - bindings tests now within svntest framework
+ * javahl bindings: numerous improvements, see its own logs.
+ * many improvements to mailer.py and commit-email.pl
+ * rewrite/improvements to gen-make build system, including VS.NET support
+ * many improvements to the automated python testsuite (issue #2257)
+ * book moved to separate repository (http://svn.red-bean.com/svnbook)
+
+
+Version 1.1.4
+(1 April 2005, from /branches/1.1.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.1.4
+
+ User-visible changes:
+ - Client:
+ * fixed: win32 not ignoring versioned symlinks (issue #2173)
+ * fixed: 'svn merge' can cause broken working copy (issue #2222)
+ * fixed: 'svn commit' fails when schedule-delete dir has local mod (r11980)
+ * fixed: 'svn st -u nonexistent_file' segfault (issue #2127)
+ * fixed: 'svn cp wc wc' utf8 conversion error (r13111)
+ * fixed: confusing error message about "wc not locked" (issue #2174)
+ * many translation updates for localized client messages
+
+ - Server:
+ * fixed: nasty (though unusual) performance bug in FSFS commits (r13222-3)
+ * fixed: FSFS memory leak when auto-merging large tree (r13193)
+ * fixed: FSFS memory leak in 'svnadmin hotcopy' (r13218, 13465, 13468)
+ * fixed: FSFS segfault when encountering empty data reps (r13683)
+ * fixed: two dataloss bugs in svndumpfilter (r12630, r12636)
+ * fixed: wasteful memory usage in svndumpfilter (r12637, r12640)
+ * fixed: mod_dav_svn segfaults when client sends bogus paths (issue #2199)
+ * make mailer.py work on win32 (r12499, r12542, r12670)
+
+ - Both:
+ * fixed: (win32) retry file operation if sharing violation (r12983, r12986)
+
+ Developer-visible changes:
+ * add SWIG 1.3.24 and .25 compatibility (r12551, r12717-9, r12722, r13504)
+ * fixed: JavaHL run-time link error (r12576), path/url cleanups (r13090)
+ * fixed: python bindings log_receiver failure with SWIG 1.3.24 (r13487)
+ * build system tweaks: add install dependencies for fs & fs_base (r11050)
+
+
+Version 1.1.3
+(14 January 2005, from /branches/1.1.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.1.3
+
+ User-visible changes:
+ - Client:
+ * translation updates for localized client messages.
+
+ Developer-visible changes:
+ * Fix a compile error in the Perl bindings.
+
+
+Version 1.1.2
+(20 December 2004, from /branches/1.1.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.1.2
+
+ User-visible changes:
+ - Client:
+ * fixed: 'svn switch' interruption can break working copy (issue #1826)
+ * fixed: 'svn switch' memleak over ra_dav (issue #2106)
+ * fixed: 'svn blame' algorithm bug (r11527)
+ * fixed: invoke external diff/diff3 with local-style paths (r11689)
+ * fixed: 'svn status' handling of missing subdirs (r11936)
+ * fixed: 'svn ls -v' encoding bug (r11740)
+ * fixed: 'svn ls "file with space"' bug (r12273, r12393)
+ * fixed: 'svn merge' should URI-encode copyfrom URLs (issue #1905)
+ * fixed: 'svn merge' deletion output formatting (r12100, r12111, r12114)
+ * fixed: 'svnversion --version .' crash (r11438)
+ * fixed: UNC paths on Cygwin (issue #2108)
+ * fixed: win98 iconv bug -- uninitialized variable (issue #2091)
+ * improved 'svn status' performance:
+ - do fewer check_path calls (r11592)
+ - 'svn status file' shouldn't recursively lock tree (r11439, r11669)
+ * translation updates for localized client messages.
+
+ - Server:
+ * fixed: 'svnadmin load' race condition (r12327)
+ * fixed: fsfs memleak in commit finalization (r11706)
+ * fixed: fsfs memleak in inefficient directory removal (r11701)
+ * fixed: fsfs commits use insert-only perms on db/revs/ (r11665)
+ * fixed: fsfs creates lockfile at creation time, not at 1st commit (r12172)
+ * fixed: svndumpfilter mislabeling output as version 3 (issue #2142)
+ * fixed: 'svnserve -h' encoding bug (part of issue #1997)
+ * fixed: prevent cross-repository copies (r12003)
+ * fixed: increase log-region max size in default DB_CONFIG (issue #2159)
+
+ - Both:
+ * fixed: 'svn switch' quietly corrupting working copy (issue #2124)
+ * fixed: canonicalize paths sent by ra_svn/svnserve (issue #2119)
+ * fixed: memleak into UTF8 translation routines (r11689)
+
+ Developer-visible changes:
+ * add support for BerkeleyDB 4.3 (if using a compatible apr-util)
+ * add support for any apr/apr-util 1.X
+ * disallow incompatible SWIG versions (r12450)
+ * fixed: slight API/ABI incompatibility between 1.0.9 and 1.1.x (r12102)
+ * fixed: perl bindings pool usage & object refcounts (r11451, r11630)
+ * fixed: perl bindings pool usage and potential memleak (r12397)
+ * fixed: javahl crash trying to fetch nonexistent property (r12184)
+ * fixed: javahl build can fail due to missing dirs (issue #2032)
+ * fixed: RPM build breakage (issue #2111)
+ * fixed: i18n issues for windows installer (r11685)
+ * allow build system to update single .po file (r11763)
+
+
+Version 1.1.1
+(22 October 2004, from /branches/1.1.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.1.1
+
+ User-visible changes:
+ - Client:
+ * fixed: 'svn status' win32 performance regression (issue #2016)
+ * fixed: 'svn ls' dying on non-ascii paths over DAV (issue #2060)
+ * fixed: allow URI-encoded colon or pipe on win32 (issue #2012)
+ * fixed: broken win32 UNC paths (issue #2011)
+ * fixed: memory bloat when committing many files over DAV (r11284, -321)
+ * fixed: eol-style translation error for 'svn propget' (r11202, -243)
+ * fixed: 'svn propedit' does EOL conversion properly (issue #2063)
+ * fixed: 'svn log --xml' shouldn't be locale-dependent. (r11181)
+ * fixed: 'svn export' of symlinks with 'use-commit-times' (r11224)
+ * fixed: 'svn export -rBASE' when WC has added items (r11296, -415)
+ * many translation updates for localized client messages.
+
+ - Server:
+ * fixed: 'svn ls' HTTP performance regression (r11211, -232, -285)
+ * fixed: make it possible to set "SVNPathAuthz off" in httpd.conf (r11190)
+ * fixed: fsfs validating revisions when accessing revprops (issue #2076)
+ * fixed: 'svn log -v' hiding too much info on 'empty' revisions. (r11137)
+ * fixed: encoding bug with 'svnlook log'/'svnlook author' (r11172)
+ * fixed: allow mod_authz_svn to return '403 Forbidden', not 500 (r11064)
+ * fixed: XML-escape author and date strings before sending (issue #2071)
+ * fixed: invalid XML being sent over DAV (issue #2090)
+
+ Developer-visible changes:
+ * fixed: IRIX compile error (issue #2082)
+ * fixed: error in perl bindings (r11290)
+ * fixed: error leaks in mod_dav_svn (r11458)
+ * fixed: javahl should use default config directory (r11394)
+
+
+Version 1.0.9
+(13 October 2004, from /branches/1.0.9)
+http://svn.apache.org/repos/asf/subversion/tags/1.0.9
+
+ User-visible changes:
+ - Server:
+ * fixed: 'svn ls' HTTP performance regression (r11211, -232, -285)
+ * fixed: 'svn log -v' hiding too much info on 'empty' revisions. (r11137)
+
+ Developer-visible changes:
+ * fixed: make redhat 7/8 rpm scripts build the book correctly (11143)
+
+
+Version 1.1.0
+(29 September 2004, from /branches/1.1.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.1.0
+
+See the 1.1 release notes for a more verbose overview of the changes since
+1.0.x: http://subversion.apache.org/docs/release-notes/1.1.html
+
+ User-visible changes:
+ * new non-database repository back-end (libsvn_fs_fs)
+ * symlinks can now be placed under version control (unix systems only)
+ * cmdline client now supports psuedo-IRIs and autoescapes chars (issue #1910)
+ * 'svnadmin recover' no longer waits forever for a lock (new '--wait' option)
+ * new $Revision$ synonym for $Rev$ and $LastChangedRevision$
+ * new runtime option 'store-passwords = ' gives finer control (r10794)x
+ * fixed: working copies now shareable by multiple users (issue #1509)
+ * fixed: diff and other subcommands correctly follow renames (issue #1093)
+ - new 'peg' syntax for diff/merge: 'svn diff -r X:Y TARGET@REV'
+ - now able to compare working copy with URL: 'svn diff --old WC --new URL'
+ * new framework for localized error/info/help messages, initial translations:
+ - German, Polish, Swedish, Norwegian Bokmål, Traditional Chinese,
+ Japanese, Brazilian Portuguese.
+ * speed improvements:
+ - faster 'svn up' on complex working copies -- no more repos txns (r8840)
+ - faster 'svn status' -- fewer stat() calls (r9182)
+ - faster 'svn checkout' -- fewer sleep() calls (r9123)
+ - faster 'svn blame' -- new RA->get_file_revs() func (issue #1715)
+ * new switches added:
+ - 'svn blame --verbose' - show extra annotation info
+ - 'svn export --native-eol TYPE' - export using TYPE line-endings
+ - 'svn add --force' - recurse into version-controlled dirs
+ - 'svnadmin dump --deltas' - include binary diffs in dumpfile
+ - 'svnadmin create --fs-type fsfs' - create fs_fs repos (default is bdb)
+ - 'svnserve --tunnel-user=NAME' - assume authenticated NAME over tunnel
+ - 'svndumpfilter [cmd] --quiet' - less chatty dumpfiltering
+ - 'svnserve --version' - show program's version
+ 'svnversion --version'
+ 'svndumpfilter --version'
+ * svnadmin dump/deltify now understand -r{DATE} (r9805)
+ * allow update of non-existent target entry (partial issue #1902 fix)
+ * 'svnadmin create' now sets sgid bit on repos/db/ (unix systems only)
+ * increase default neon (ra_dav) timeout from 120 to 3600 seconds (r9568)
+ * print verbose BDB error messages (r10557, r10566)
+ * fixed: don't bail when 'svn up' refuses to delete local mods (issue #1806)
+ * fixed: process svn:externals in defined order (issue #1788)
+ * fixed: pass new propval to stdin of pre-revprop-change hook (issue #952)
+ * fixed: svndumpfilter logic/memory/display bugs (r8691, 8831, 9061)
+ * fixed: 'svnadmin hotcopy PATH .' (r8659)
+ * fixed: copy crash bug (r8863)
+ * fixed: 'svn st -u' crash bug (r10841)
+ * fixed: 'svn commit' segfault (r10676)
+ * fixed: allow cleanup on .svn/ dirs containing KILLME file (r8891)
+ * fixed: 'svn revert' detects corrupted text-base (r8897)
+ * fixed: 'svn status -N' no longer locks entire tree (r8906)
+ * fixed: several different 'svn switch' bugs (r9192, 9203, 9238, 9698)
+ * fixed: some 'svn copy' bugs (r9193, 9274)
+ * fixed: obscure update-deletion bug (r8976)
+ * fixed: utf8 conversion 'hang' (r9233)
+ * fixed: missing UTF8->native recoding in 'svn log' output (r10652, 10673)
+ * fixed: 'svn blame' now defaults to rev (r9440)
+ * fixed: 'svn blame' closing files before deleting them (issue #1969)
+ * fixed: 'svn diff' shows truncated paths (r9693)
+ * fixed: 'svn diff --notice-ancestry' bug (r9699)
+ * fixed: 'svn subcommand -r{DATE} URL' works if URL not in HEAD (issue #1840)
+ * fixed: 'svn blame' on non-ascii path truncation (issue #1770)
+ * fixed: svn:external 'wc not locked' bug (issue #1897)
+ * fixed: proper mod_dav_svn html/xml escaping (issue #1209)
+ * fixed: memleak in 'svn propset -R URL' (issue #1928)
+ * fixed: stop 'svn up' from deleting schedule-add target dir (issue #1793)
+ * fixed: 'svn merge' adding a directory already 'deleted' (issue #1769)
+ * fixed: excessive memory use when fs deltifies revision 2^N (r10070)
+ * fixed: disallow non-recursive directory commit (issue #1797)
+ * fixed: allow propget of props with colon in name (issue #1807)
+ * fixed: 'svnadmin load' computation of copyfrom-rev (issue #1795)
+ * fixed: runtime config files created with proper line-endings (issue #1803)
+ * fixed: make svnserve's authn work on usernames with spaces (r10385)
+ * fixed: have svnserve use repos UUID as default authn realm (r10394)
+ * fixed: segfault when history-following hits 'empty' revision (r10368)
+ * fixed: overzealous out-of-dateness checks in 'svn cp wc URL' (issue 1994)
+ * fixed: don't URI-encode path in mod_dav_svn XML listings (r10461)
+ * fixed: 'svn info' should refuse URL targets (r10760)
+ * fixed: incomplete-directory handling bug (r10956)
+ * fixed: allow cancellation between files during recursive dir add (r10894)
+ * general improvement and normalization of error messages
+ * many improvements to contributed tools: mailer.py, psvn.el, etc.
+
+ Developer-visible changes:
+ * libsvn_fs now loads either bdb (libsvn_fs_base) or fsfs (libsvn_fs_fs)
+ * new console-printing API: svn_cmdline_printf() family checks for errors.
+ * new library-version querying API:
+ - new svn_[libname]_version() in each library
+ - svn_ver_*() family of functions
+ * 2nd generation APIs, from svn_foo() --> svn_foo2(). old APIs deprecated.
+ - svn_wc_adm_open2() & friends, svn_wc_export2(), svn_client_add2()
+ svn_wc_parse_externals_description2(), svn_hash_read/write2(),
+ svn_repos_dump/load_fs2() & friends, svn_wc_diff2(),
+ svn_subst_copy_and_translate2()
+ * other new APIs:
+ - svn_stream_copy(), svn_txdelta_target_push(), svn_opt_parse_path(),
+ svn_io_file_flush_to_disk, svn_repos_trace_node_locations(),
+ svn_repos_get_file_revs(), RA->get_locations(), RA->get_file_revs,
+ RA->get_version(), svn_sort_compare_paths(), svn_utf_initialize()
+ * SVN_REVNUM_FMT_T usage replaced with %ld (r9691)
+ * cache mod_authz_svn authz file per connection (r8867)
+ * validate hex digits in % escape (issue #1947)
+ * hashes now written to disk in sorted order (r9910)
+ * do cancellation checks before loops, not after (r8918)
+ * fixed: bug in svn_repos_dir_delta replacement logic (r8078)
+ * fixed: tiny memory access bugs (r8229, 8230, 8313)
+ * fixed: several commit buglets (r8955, 9658, 9757, 9855)
+ * fixed: don't recursively lock all prop commands (r9172)
+ * fixed: svnserve memory usage on many-file commits (r9185)
+ * fixed: close svnserve child's listen-socket after forking (r10050)
+ * fixed: 'svnadmin hotcopy' integrity improvements (issues #1817, #1818)
+ * fixed: only verify media type of svn:mime-type, not encoding (r10126)
+ * fixed: handle '//' and '..' in svn_path_canonicalize (issue #1779)
+ * fixed: double URI escaping (issue #1814)
+ * fixed: editor-driver bug (don't delete before every copy) (r10851)
+ * fixed: potential mod_dav_svn crashes/memleaks (r10478)
+ * fixed: better 'svnadmin verify verification (r10508, r10509)
+ * fixed: encoding of get_repos_url_result (r10353, 10375)
+ * fixed: prevent canonicalized URIs from ending in '/' (r10317)
+ * stop using -std=c89 gcc flag (r11054)
+ * sync with apr 1.0's find_apr.m4 and find_apu.m4 files (r10560)
+ * win32 installer improvements (r10978)
+ * huge improvements to python, perl, java bindings
+ * huge changes to win32 build system
+
+
+Version 1.0.8
+(22 September 2004, from /branches/1.0.8)
+http://svn.apache.org/repos/asf/subversion/tags/1.0.8
+
+ User-visible changes:
+ * fixed: mod_authz_svn path and log-message metadata leaks.
+ See CAN-2004-0749, and descriptive advisory at
+ http://subversion.apache.org/security/CAN-2004-0749-advisory.txt
+
+
+Version 1.0.7
+(17 September 2004, from /branches/1.0.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.0.7
+
+ User-visible changes:
+ * fixed: win32 'file not found' error [issue #1862]
+ * fixed: 'svn st -u' crash (r10841)
+ * fixed: potential repos corruption; ensure stdin/out/err always open (r10819)
+ * fixed: allow propnames containing ":" to be fetched via http:// (r10190)
+ * fixed: allow user to interrupt between authentication prompts (see r11014)
+ * fixed: work around +t directory-creation bug in APR (r10616, 10638, 10642)
+ * various small fixes to Book
+
+ Developer-visible changes:
+ * fix library dependencies for bindings (r9338, 9340)
+ * java bindings: fix a crash and other bugs (r9883, 9905, 8027)
+ * perl bindings: various fixes (see r11023)
+
+
+Version 1.0.6
+(19 July 2004, from /branches/1.0.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.0.6
+
+ User-visible changes:
+ * fixed: crash in status command, caused by race (r10144)
+ * fixed: crashes when deleting a revision-prop (r10148, r10185, r10192)
+ * fixed: mod_authz_svn allows COPY method on repos with space in name (#1837)
+ * fixed: mod_authz_svn COPY security hole: authorize whole tree (issue #1949)
+
+ Developer-visible changes:
+ * neon 0.24.7 now required (fixes wire compression bugs) (r10159, 10176)
+
+
+Version 1.0.5
+(10 Jun 2004, from /branches/1.0.5)
+http://svn.apache.org/repos/asf/subversion/tags/1.0.5
+
+ User-visible changes:
+ * fixed: security bug in svn protocol string parsing. (CAN-2004-0413)
+
+
+Version 1.0.4
+(21 May 2004, from /branches/1.0.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.0.4
+
+ User-visible changes:
+ * fixed: 'svn up' can delete unversioned data on win32 fs (issue #1854)
+ * fixed: pool leaks in 'svnlook diff/changed/dirs-changed'
+ * fixed: insecure script example in pre-commit-hook template
+ * fixed: inability to do a checkout to '/'
+ * officially recommend neon 0.24.6 in all docs.
+
+ Developer-visible changes:
+ * fixed: RPM build for Fedora & WBEL3/RHEL3
+ * fixed: SWIG-java building problem
+ * fixed: javahl bug which can crash JVM
+ * fixed: change formatting codes in svn_swig_pl_callback_thunk
+ * fixed: properly wrap svn_txdelta_parse_svndiff for perl
+
+
+Version 1.0.3
+(19 May 2004, from /branches/1.0.3)
+http://svn.apache.org/repos/asf/subversion/tags/1.0.3
+
+ User-visible changes:
+ * fixed: security bug in date parsing. (CAN-2004-0397)
+
+
+Version 1.0.2
+(15 April 2004, from /branches/1.0.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.0.2
+
+ User-visible changes:
+ * fixed: segfault when remotely deleting svn:author property.
+ * fixed: mod_dav_svn accepting too many authors. (issue #1786)
+ * fixed: create runtime config files with native EOLs. (Issue #1802)
+ * fixed: recursive propset can corrupt .svn/entries (issue #1794)
+ * fixed: allow shared working copies [mostly working now] (issue #1509)
+ * fixed: mod_authz_svn should ignore uri on MERGE request (partial #1821)
+ * fixed: svnserve assertion failure on empty error messages
+ * fixed: commit/update memory leaks when working on many targets (issue #1635)
+ * fixed: don't display repos-paths or URLs with '\' on win32.
+ * new example script: svnserve 'sgid' wrapper.
+ * minor book fixes, new 'best-practices' doc.
+
+ Developer-visible changes:
+ * fixed: deprecation warning from SWIG 1.3.20_
+ * fixed: broken win32 python-swig bindings compilation.
+ * fixed: bug in libsvn_fs changes-table change-folding code.
+ * fixed: perl bindings: wrap root->paths_changed, apply_txdelta return values
+ * added VC7 support and defines for including debug symbol files.
+
+
+Version 1.0.1
+(12 March 2004, from /branches/1.0.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.0.1
+
+ User-visible changes:
+ * allow anonymous access checking in mod_authz_svn
+ * fixed: mod_authz_svn now works with SVNParentPath (issue #1588)
+ * fixed: potential segfault in mod_dav_svn.
+ * fixed: improper BDB cursor shutdown in libsvn_fs, which can wedge repos.
+ * fixed: allow checkout of repository with space in path. (issue #1694)
+ * fixed: make 'svn propget URL' work correctly over svn://. (issue #1752)
+ * fixed: failed 'svn merge URL' when URL contains user@host. (issue #1759)
+ * fixed: invalid REPORT response when updating a deleted wc. (issue #1721)
+ * fixed: allow deletes below copied wc dirs.
+ * fixed: merge --dry-run bug on added-files with props. (issue #1738)
+ * fixed: svnlook no longer requires write access to '.'
+ * fixed: ensure 'svn blame' fails on files marked as binary. (issue #1733)
+ * fixed: make failed direct-URL commits clean up their fs txns. (issue #1726)
+ * fixed: obscure bugs in time/date string formatting. (issue #1692)
+ * fixed: svn export doesn't export svn:externals. (issue #1750)
+ * fixed: svn import doesn't handle EOL or keyword translation. (issue #1756)
+ * fixed: svn status -v shows unwanted status of externals (issue #1741)
+ * fixed: allow revert of schedule-replace file that has no props (issue #1775)
+ * fixed: svnserve segfault on invalid --listen-host argument.
+ * fixed: switch bug which caused wrong URL to be left in wc.
+ * detect invalid UTF8 filenames when native locale is UTF8.
+ * improve presentation of directory property conflicts.
+ * improve presentation of errors from svnadmin & svnlook.
+ * clarify output of 'svnadmin help deltify'.
+ * augment copyright notice to --version output.
+ * more book updates.
+
+ Developer-visible changes:
+ * remove obsolete auth provider examples.
+ * prevent potential ra_dav commit race-condition.
+ * fix svn_io_dir_walk 'dot-first' ordering required by 'svnadmin hotcopy'.
+ * fix error leaks in dav_svn_convert_err()
+ * upgrade win32 innosettup tools and redhat RPMs.
+ * fix compile warning: compressed streams on LP64 architecture.
+ * use cpio to generate tarballs instead of GNU tar.
+ * tweaks to dist.sh.
+ * fix bindings on win32.
+ * fix perl bindings build on OS X.
+ * fix perl bindings: bug which rejects string revnums.
+
+
+Version 1.0.0
+(branching 23 February 2004, from /branches/1.0.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.0.0
+
+ User-visible changes:
+ * fixes to the shbang lines in tools/hook-scripts/.
+ * vast improvements to cvs2svn.py (NOTE: now a separate project!)
+ * general documentation cleanup:
+ - clarify built-in help text for 'svn switch' and 'svn status'.
+ - fix docs within the hook templates.
+ - cleanups to README, INSTALL, HACKING, svn-ref.tex, bash_completion.
+ - bring www/ pages up-to-date for 1.0.
+ - many changes to the Book
+
+ Developer-visible changes:
+ * updates to the win32 installer packaging code.
+ * cleanups to SWIG bindings:
+ - disable svn_io_* functions.
+ - svn_filesize_t and apr_time_t fixes.
+ - remove debugging print statements and various warnings.
+ - make svn_repos_dir_delta() function correctly
+ - add support for repos authz callback.
+
+
+Version 0.37.0 [Beta Interim 2]
+(branching 24 January 2004, from /branches/1.0-stabilization)
+http://svn.apache.org/repos/asf/subversion/tags/0.37.0
+
+ User-visible changes:
+ * bugfix: buffer overflow for AIX client
+ * 'svn merge' now notices ancestry by default. (r8390)
+ * bugfix: double Ctrl-C on windows no longer wedges repository.
+ * New date formats (see API change: Rewrite of date parser below)
+ * bugfix: Errors in authentication when --no-interactive is turned on (r8139)
+ * bugfix: Fix some 'access denied' errors on Windows (r8341, r8352)
+
+ Developer-visible changes:
+ * API change: Rewrite of date parser (r8327, r8328, r8329) (issue #408)
+ * bugfix: svn_fs__bdb_changes_fetch() fouls up change ordering (issue #1695)
+ * require SWIG >=1.3.19 (issue #1690)
+ * numerous changes to language bindings, to keep up with C API.
+ * fix: apr build issues (r8279, r8280, r8318) (issue #1666)
+ * changed the auth-provider C API to use 'realmstring' on all funcs
+ * check the ra plugin ABI versions.
+ * fix: ABI problem with blame. (r8494) (issue #1705)
+ * remove svn_io_file_printf from public API. (r8492) (issue #1653)
+ * extensive changes in the perl client bindings. (r8270)
+ * too many big and small internal code cleanups and fixes to mention here
+
+
+
+Version 0.36.0 [Beta Interim 1]
+(branching 13 January 2004, from /branches/1.0-stabilization)
+http://svn.apache.org/repos/asf/subversion/tags/0.36.0
+
+ User-visible changes:
+ * add cancellation support to svnadmin and svnlook (r8222)
+ * runtime 'store-password' option renamed to 'store-auth-creds' (r8014)
+ * 'svn blame' changes:
+ - now shows correct revision info (r8035-6)
+ - responds to cancellation better (r8129)
+ * svnserve changes:
+ - added '--inetd' option; now required to speak with stdin/stdout (r8205)
+ - added '--listen-port' and '--listen-host' options (r8001-2)
+ - removed '-u' option (r8003)
+ - ignore SIGPIPE (no more repos lockups when you terminate a pipe) (r8140)
+ * lots of Book work (many newly-documented Apache and svnserve topics)
+
+ Developer-visible changes:
+ * bugfix: svnserve network crash (r8142)
+ * bugfix: return result_rev from svn_client_checkout correctly (r8096)
+ * bugfix: fs history harvesting code (r8154)
+ * bugfix: memory leak in mod_dav_svn (r8223)
+ * bugfixes in edge-cases of status and update (r8114-5)
+ * make 'svn blame' work with 18n and uri-escaped filenames (r8023, 8030, 8040)
+ * small bugfixes to authentication system (r8006, r8235)
+ * standardize error message formatting (r8218)
+ * load RA modules as foo.so.0, not foo.so (r8098)
+ * various core API changes:
+ - use constructor for svn_client_cxt_t (r8053-4)
+ - anchor/target may use NULL for target (r8216)
+ - stop using apr_ symbols (r8219)
+ - rename to 'svn_repos_authz_func_t' (r8213)
+ - add pool parameter to finish_report and abort_report (r8215)
+ * numerous changes to Perl and Java bindings, to keep up with C API.
+
+
+
+Version 0.35.1 [Beta] (branching 19 December 2003, from /tags/0.35.0)
+http://svn.apache.org/repos/asf/subversion/tags/0.35.1
+
+ NOTICES:
+
+ This release is to correct for the problems in the 0.35.0
+ release and affects Windows users only:
+
+ * fix: file handle leak (r8048)
+ * fix: UTF-8 path problem (issue #1660)
+
+
+Version 0.35.0 (branching 12 December 2003, from revision 7994)
+http://svn.apache.org/repos/asf/subversion/branches/0.35.0
+
+ NOTICES:
+
+ 1. As of this release, Subversion once again does deltification
+ automatically. This means that the deltification step most
+ repositories introduced into their post-commit hooks as of
+ release 0.33.0 should now be reverted. Look for a line with
+ "svnadmin deltify" in hooks/post-commit, and remove it.
+
+ 2. We now recommend using Berkeley DB 4.2.52 or higher for SVN
+ repositories. See http://sleepycat.com/download/index.shtml.
+
+ User-visible changes:
+ * BDB log files are automatically pruned, with BDB 4.2.50 and higher (#1615)
+ * deltification is automatic again (issue #1601)
+ * fix: svn diff -rX:Y wcpath' may lie (issue #1616)
+ * fix: URI-decoding problem on 'svn import' (issue #1622)
+ * many other enhancements, minor features, and bugfixes not listed here
+
+
+ Developer-visible changes:
+ * misc. improvements on Perl and Java bindings
+ * improved diff handling (r7985)
+ * many other changes not listed here
+
+
+ Merged revisions after release branching:
+ * r8009, r8010 and r8011 - Java bindings
+ * r8041 - typo/bugfix
+
+
+Version 0.34.0 (released 3 December 2003, from revision r7859)
+http://svn.apache.org/repos/asf/subversion/tags/0.34.0
+
+#####################################################################
+## WARNING WARNING WARNING WARNING WARNING WARNING WARNING ##
+#####################################################################
+## ##
+## This release makes an incompatible change to the Subversion ##
+## database. Repositories created with versions of Subversion ##
+## prior to 0.34 will not work with Subversion 0.34. ##
+## To upgrade, first use 'svnadmin dump' with your existing ##
+## Subversion binaries. Then upgrade your binaries to 0.34, and ##
+## use 'svnadmin load' to create a new repository from your ##
+## dumpfile. ##
+## Don't forget to copy any custom configuration/hooks from the ##
+## old to the new repository. ##
+## ##
+#####################################################################
+
+ Please see notes/repos_upgrade_HOWTO for documentation on migrating
+ pre-0.34.0 repos to 0.34.0.
+
+ That document is also located here:
+ http://svn.apache.org/repos/asf/subversion/trunk/notes/repos_upgrade_HOWTO
+
+ User-visible changes:
+ * fs schema change (#1578, #1595) **NOTE: repos dump/load cycle required!**
+ * Berkeley DB 4.2.50 is now the recommended Berkeley version
+ * Fix: 'svn status' thought replaced items were unversioned (#1609)
+ * SSL server cert error prompt improvement (r7849)
+ * many error message improvements (r7745, r7763, r7824 and 7827 - #897)
+ * don't show update-completion message until all wc work completes (#1556)
+ * many other enhancements, minor features, and bugfixes not listed here
+
+ Developer-visible changes:
+ * public client APIs changes (r7799) after fixing #1556
+ * many improvements and fixes on Perl bindings (perl => 5.8.0 are required)
+ * improvements, fixes on misc. test scripts
+ * many other changes not listed here
+
+ Merged revisions after release branching:
+ * r7868 - Java bindings
+ * r7888 - Security fix for svnserve
+
+
+Version 0.33.1 (released 17 November 2003, revision r7782)
+http://svn.apache.org/repos/asf/subversion/tags/0.33.1
+
+ NOTICE: This is a bugfix release. The bug is fixed if *either*
+ the client or server uses the new code.
+
+User-visible changes:
+* major performance fix for updates
+
+
+Version 0.33.0 (released 13 November 2003, revision r7737)
+http://svn.apache.org/repos/asf/subversion/tags/0.33.0
+
+ NOTICES:
+
+ 1. This client may be incompatible with ra_dav servers <= 0.31.
+
+ 2. In order to make commits more responsive, repository
+ deltification is no longer automatic. However, you may want
+ to run deltification as a background process in your repository
+ post-commit hook. For example, the new post-commit.tmpl file
+ recommends 'nice -2 svnadmin deltify "$REPOS" -r "$REV" &'.
+
+ User-visible changes:
+ * now require APR/APU 0.9.5 (ships in Apache 2.0.48)
+ * lose automatic deltification, but recommend it in post-commit (r7695, #1573)
+ * new configuration and authn/authz support in ra_svn (r7604, r7601)
+ * much faster checkouts and updates, over both svn:// and http:// (#1429)
+ * new partial-authz feature: checkouts/updates just skip unauthorized items
+ * new 'use-commit-times = yes' config option to use commit-time timestamps
+ * new 'svnadmin hotcopy' command, like hot-backup.py (#1567)
+ * fix Win32 "access denied" error in renames (r7598, #1576)
+ * unnecessary working copy tree locks now avoided, to save time (#1245)
+ * Compatibility changes:
+ - lose ra_dav compatibility with servers 0.31 and earlier
+ - lose support for working copy format "1" (not created for over a year)
+ * 'svn diff' and other read-only actions now work in read-only working copies
+ * 'svn blame -rX' now does the intuitive thing
+ * 'svn log' output headers now say "rXXXX | " instead of "rev XXXX: "
+ * 'svnversion' no longer stymied by svn:externals
+ * new 'svn pd' alias for 'svn propdel'
+ * '-rCOMMITTED' keyword now works on more commands
+ * minor changes to output of 'svn ls -v' and 'svn st -v' (r7530)
+ * 'svn log --xml' now obeys the '-q' flag (r7555)
+ * cvs2svn.py bugfixes, especially issue #1440
+ * book and documentation updates
+ * removed server config options ssl-ignore-invalid-date and
+ ssl-override-cert-hostname (r7644)
+ * many other enhancements, minor features, and bugfixes not listed here
+
+ Developer-visible changes:
+ * repair text- and prop-time in .svn/entries if spuriously wrong (r7565)
+ * speed up keyword translation (r7502)
+ * two new editor functions, absent_file() and absent_directory()
+ * ra_dav checkouts/updates no longer do O(n) number of GET, PROPFIND requests
+ * new svn_io_temp_dir function, will morph to apr_temp_dir_get soon
+ * new svn_io_file_close wrapper for apr_file_close
+ * tools/test-scripts/svntest/ scripts now support ra_dav and ramdisk
+ * many other changes not listed here
+
+
+Version 0.32.1 (released 23 October 2003, revision 7497)
+http://svn.apache.org/repos/asf/subversion/tags/0.32.1
+
+ NOTICE: This release is to correct for the problems in the 0.32.0
+ release. There are no user or developer changes in this release
+ other than the subversion/include/svn_version.h now reflects
+ the correct version number.
+
+ NOTICE: This release of Subversion causes an ra_dav client/server
+ compatibility break with Subversions older than 0.28.0.
+
+Version 0.32.0 (released 22 October 2003, revision 7480)
+http://svn.apache.org/repos/asf/subversion/tags/0.32.0
+
+ NOTICE: This release of Subversion causes an ra_dav client/server
+ compatibility break with Subversions older than 0.28.0.
+
+ User-visible changes:
+ * new 'svn blame' subcommand. (r7389, 7438, #508)
+ * fix huge ra_dav 'svn import' memory leak. (r7381)
+ * other bugfixes: proper line endings in diff headers (r7450, #1533),
+ stop auto-props from removing all whitespace (r7358), 'svn st' UI
+ consistency fix (r7364), various 'svn switch' fixes (r7366),
+ mini-manpages for svnadmin, svnserve, svnversion (r7421), remove
+ 'P' field from 'svn ls -v' (r7432), 'svn merge' double-notification
+ bug (r7447), prevent 'svn:externals' infinite loop (r7459), 'svn
+ merge' segfault (r7458).
+
+ Developer-visible changes:
+ * 'svn diff' is now reasonably streamy. (r7393, 7439, #1481)
+ * fix many ra_dav pool abuses. (r7370-3, 7380, 7368, 7400, ...)
+ * fix mini leaks: clear unused svn_error_t's. (r7378-9, 7405, 7408, 7429)
+ * tons of code, doc, API cleanup. (from julianfoad!)
+ * new RA->get_repos_root() API. (r7428)
+ * swig/python, swig/perl and native JNI updates and improvements.
+ * more work on build depenedency generator. (r7412-8)
+ * svn_repos_finish_report() now aborts txns on error. (r7424)
+ * remove crufty old ra_dav compatibility code (r7466, 7468)
+ * other changes: new SVN_DEBUG_ERROR tool macro, new 'davautocheck'
+ and 'contrib' makefile targets, new --enable-gprof configure option
+ (r7437), new scramble-tree.py testing tool, auth provider
+ reorganization, make RA->get_dir fetch props correctly over ra_dav
+ (r7431), notice permission error when creating unique tmpfile (r7434).
+
+
+Version 0.31.0 (released 08 October 2003, revision 7355)
+http://svn.apache.org/repos/asf/subversion/tags/0.31.0
+
+ User-visible changes:
+ * new 'svnlook history' command (and removal of 'svnadmin lscr').
+ * new 'auto-props' feature can set file properties during 'svn add/import'
+ * win32 client now properly converts UTF8 to console-locale. (r7168, #872)
+ * 'svn up' now notices when svn:externals value changes. (r7256, #1519)
+ * authentication changes:
+ - client caches auth-creds in memory for a single session (r7292, #1526)
+ - SSL cert caches keyed on host+port, not any SSL connection. (r7174)
+ * faster 'svn log' (see new fs-history algorithm) (#1499)
+ * faster repos read-operations (caching gives ~20% speedup) (rXXXX, #1499)
+ * faster updates (fewer entries-file writes gives ~20% speedup) (r7170, #1490)
+ * more work on psvn.el and svn_load_dirs.pl
+ * more cvs2svn bugfixes
+ * obsolete manpages truncated to point to 'help' and book URLs. (r7340, #1508)
+ * other bugfixes: no more revision keywords "FIRST" or "CHANGED" (r7250),
+ fix 'svn cp URL URL' $EDITOR msg generation (r7264), fix regression
+ bug in 'svnadmin load' (r7273), 'svnadmin setlog' now triggers
+ repository hooks (r7322), 'svn cp -rHEAD wc' now works correctly (r7331),
+ post-commit-hook failures correctly ignored by client (r7342, #906)
+
+ Developer-visible changes:
+ * tons of filesystem improvements (#1499):
+ - new fast fs-history algorithm: allows stable VR urls (r7283, #1499)
+ - new dag-node caching (r7163)
+ - skip-deltas now run in individual trails (r7138)
+ - no-op svn_fs_copy()s don't write to the database (r7158)
+ * mod_dav_svn MERGE response is faster (using svn_repos_replay()) (r7191)
+ * ensure consistent wc 'dead entry' cleanup (r7197, r7204, #1075)
+ * lots of work on gen_win.py, gen_make.py, gen_base.py tools
+ * lots of work on making SWIG-java bindings build.
+ * updates/improvements to javahl bindings and SWIG-perl bindings
+ * updates/improvements to Mandrake RPM builds
+ * other bugfixes: python testsuite now uses local path separators (r7224),
+ svn:externals no longer keeps connections open (r7312, #1448),
+ UTF8-to-local date conversion (r7316, #1534), API consistification
+ changes (r7298, r7302, r7304, r7307).
+
+
+Version 0.30.0 (released 24 Sep 2003, revision 7178)
+http://svn.apache.org/repos/asf/subversion/tags/0.30.0
+
+ User-visible changes:
+ * SSL changes: (r7134, #1330)
+ - client now prompts to cache server certificates
+ - no more 'ssl-ignore-unknown-ca' option
+ - 'ssl-ignore-host-mismatch' is renamed to 'ssl-override-cert-hostname'
+ - new 'ssl-trust-default-ca' option to trust 'default' openssl CAs
+ * 'svn log' no longer dies on unversioned args (r6989, #777)
+ * local mods now obstruct 'svn up' deletions (r7050, #1196)
+ * 'svnserve' now notices (unauthenticated) --username arg (r7060)
+ * no more 'svnadmin createtxn' subcommand. (r7062)
+ * 'svn ls -v' shows years when appropriate
+ * document some new things in Book (r7014), plus minor technical fixes
+ * website changes: new sidebar, new 'svn links' page, new tigris.org!!
+ * other bugfixes: hooks use proper stdout handles on win32 (r7001),
+ prevent copies of copies in wc (r7077, #1259), display failed
+ revprop change over ra_dav (r7081, #1520), 'svn st -u' throws RA
+ error properly (r7094, #1506)
+
+ Developer-visible changes:
+ * ra_dav now requires neon-0.24.X
+ * many gen_make.py/gen-base.py improvements, especially for win32 builds
+ * many improvements to swig/perl bindings
+ * improvements to contrib/: psvn.el, and new svn-push program.
+ * more cvs2svn bugfixes: issue #1504, #1421, #1514, and new --username arg.
+ * python testsuite only raises exceptions, never status codes. (#1192)
+ * various libsvn_fs re-org (prepwork) for issue #1499.
+ * other bugfixes: code-complete timestamp feature (r6983, #1445), add
+ op-counting features to trails (r6984, #655), fs UUID caching
+ (r7037), almost finish win32 iconv issues (#872), restored-file
+ entry-timestamp bugfix (r7090, #1523), always print CWD as '.' (r7097)
+
+
+Version 0.29.0 (released 05 Sep 2003, revision 6976)
+http://svn.apache.org/repos/asf/subversion/tags/0.29.0
+
+ User-visible changes:
+ * 'svn status' now streams its response. (r6913, #1426)
+ * 'svn status' now recurses into externals (r6913, #1428)
+ * new 'svnadmin verify' command to verify repository data (r6851, #1074)
+ * SSL changes: (r6958, #1371)
+ - dropped support for PEM-encoded client certs, only accept PKCS12 now.
+ - 'ssl-authority-files' is now a list of CA files
+ - no more 'ssl-client-cert-type' and 'ssl-client-key-file' variables.
+ * new svndumpfilter option: '--preserve-revprops' to keep props on empty revs
+ * mailer.py improvement: handle multiple match groups (r6940)
+ * remove in-repos/on-disk repository template features, till post-1.0 (r6965)
+ * various cleanups to the Book
+ * other bugfixes: switch deletion bug (r6890, #1496), status
+ repos-delete bug (r6913, #1469), reversion of '.' (r6953, #854).
+
+ Developer-visible changes:
+ * GUI developers take note: prompting API changed (r6928, #1214)
+ * now compile against neon-0.24; 0.23.9 support to be dropped soon. (r6958)
+ * various improvements to Perl/SWIG bindings
+ * tree re-org: non-core utilities split into 'tools' and 'contrib' areas.
+ * some gen_make.py/gen-base.py improvements
+ * configure.in CFLAGS bugfix (r6963)
+ * stop calling deprecated APIs in APR, in preparation for upcoming APR-1.0.
+
+
+Version 0.28.2 (released 29 Aug 2003, revision 6946)
+http://svn.apache.org/repos/asf/subversion/tags/0.28.2
+
+ User-visible changes:
+ * MAJOR BUGFIX: revert revision 6764.
+
+ The new history-searching code was over-stressing our use
+ of BerkeleyDB transactions, causing checkouts to go
+ twice as slow and lose all concurrent-client scalability.
+
+ This is a temporary fix for a larger design problem. See issue
+ http://subversion.tigris.org/issues/show_bug.cgi?id=1499
+
+
+Version 0.28.1
+(released 28 Aug 2003,http://svn.apache.org/repos/asf/subversion/tags/0.28.1)
+
+ There are no changes in this release.
+ It is strictly an updated release, build with the correct version
+ of autoconf; autconf-2.57
+
+
+Version 0.28.0 (released 27 August 2003, rev 6894, branches/release-0.28)
+(http://svn.apache.org/repos/asf/subversion/tags/0.28)
+
+#####################################################################
+## WARNING WARNING WARNING WARNING WARNING WARNING WARNING ##
+#####################################################################
+## ##
+## This release makes an incompatible change to the Subversion ##
+## repository filesystem schema. Repositories created with ##
+## Subversion code prior to this release will unable to operate ##
+## with this new code. To maintain the ability to use said ##
+## repositories, you must use a version 'svnadmin dump' prior to ##
+## this change to dump your repository to a dumpfile, then use ##
+## this new Subversion code to create, and load your dumpfile ##
+## a new repository using 'svnadmin load'. And don't forget to ##
+## copy over any custom configuration/hooks from the old to the ##
+## new repository. ##
+## ##
+#####################################################################
+
+ Please see notes/repos_upgrade_HOWTO for documentation on migrating
+ pre-0.28.0 repos to 0.28.0.
+
+ That document is also located here:
+ http://svn.apache.org/repos/asf/subversion/trunk/notes/repos_upgrade_HOWTO
+
+ User-visible changes:
+ * fs schema change, see issue #1003 **NOTE: repos dump/load cycle required!**
+ * command-line options
+ - changed 'lsdblogs' is now 2 commands 'list-dblogs', 'list-unused-dblogs'
+ - removed '--only-unread' option
+ - new 'list-unused-dblogs'
+ - new '--config-dir' allows svn config to live outside ~/.subversion
+ - new (r6811)
+ - svn-status-get-specific-revision (interface to svn cat)
+ - svn-ediff-with-revision (run ediff w/ a file wc and a specified rev)
+ * fixed 'mod_dav_svn' segfault bug caused by foreign DeltaV requests (r6725)
+ * fixed 'svn switch' bug which could result in corrupted repo (#1316,r6746)
+ * fixed items now marked as 'deleted' if they no longer exist (#919,r6748)
+ * fixed 'merge' no longer adds file/dir if scheme differs from wc. (#1321)
+ * fixed Handle \r correctly when prompting on Windows. (r6792,#1307)
+ * 'svn merge' now 'skip's when it hits unversioned obstructions (r6810,#1425)
+ * fixed repos->wc of file w/ svn:keywords set caused segfault (r6818,#1473)
+ * fixed 'svn diff -r PREV:HEAD' failed if tmp/ exists in cwd (r6838,#1487)
+
+ Developer-visible changes:
+ * database schema changed (see warning above!) (r6752,#1003,#1248,#1438)
+ * svn Perl bindings are ready - see swig/INSTALL
+ * internal changes to treat swig libraries more like normal libraries (r6761)
+ * improved handling of errors opening a repository over ra_svn (r6841)
+
+
+
+Version 0.27.0 (released 12 August 2003, rev 6707, branches/release-0.27.0)
+
+ User-visible changes:
+ * fixed ra_svn:
+ - (r6588) avoid hangs due to ra_svn tunnel creation errors
+ - (r6696, r6697, #1465) svnserve crash due to pre-commit hook failure
+ * fixed 'svn log':
+ - (r6642, #1423) log on a deleted path over ra-dav
+ - (r6684, #1438) log performance bug
+ * fixed 'svn diff' and 'svn merge':
+ - (r6604, #1311) diff URL URL on files now works
+ - (r6668, #1142) diff comparing wc to repos branch
+ - (r6687, #1297) diff/merge interaction in file adds
+ - (r6703, #1319) merge problem with adding subtrees
+ - (#6607) new default ancestry-following behavior for diff, merge
+ * fixed 'svn status':
+ - (r6688, r6691, r6692, #1289) status on nodes deleted in repos
+ - (r6637) status now always uses "I" for ignored directories
+ * fixed 'svn copy':
+ - (r6704, #1313) copy between 2 repositories errors cleanly now
+ - (r6649, #1444) seg fault when copying empty dir from repos to wc
+ * fixed 'svn export':
+ - (r6652, #1461) exporting an empty directory
+ - (r6664, #1296) path->path exports
+ * fixed - gracefully handle failure to get uid on Win98 (r6695, #1470)
+ * fixed - avoid spurious conflicts when merging binary files (r6621, #1319)
+ * fixed - merge of a single file into implicit '.' (r6630, #1150)
+ * fixed - various Win32 innosetup improvements/fixes (r6693, r6656, #1133)
+ * fixed - disallow ".." in svn:externals target paths (r6639, #1449)
+ * fixed - use 'env' instead of hard-coded paths in scripts (r6626, #1413)
+ * fixed - bug in loading incremental dumpfiles (r6595)
+ * fixed - performance issue in svn_load_dirs.pl fixed (r6623, r6627, #1455)
+ * fixed - handle IPv6 addresses in URLs (r6638, r6654, #1338)
+ * changed - 'svn resolve' renamed to 'svn resolved' (r6597)
+ * changed - 'svnlook tree' takes new optional path-in-repos argument (r6583)
+ * changed - renamed 'svnadmin archive' to 'svnadmin lsdblogs'; offer
+ either all logs, or just the unused ones (r6661)
+ * changed - now offer full-text search in Windows documentation file (r6658)
+ * changed - much documentation updated, especially the book
+ * Many other fixes and changes, too numerous to mention individually.
+
+ Developer-visible changes:
+ * fixed - many improvements to Perl and Python bindings, including some
+ Win32-specific improvements (r6619, r6686, r6590, r6580, r6579)
+ * All other important dev changes are implied by the user-visible changes.
+
+
+Version 0.26.0 (released 24 July 2003, revision 6550, branches/release-0.26.0)
+
+ User-visible changes:
+ * fixed - --parent-dir option to svnadmin load (r6436)
+ * fixed - 'svnlook diff' now properly displays diffs (r6408, #1241)
+ * fixed - 'svn cat' no longer expands values from the wrong revision.
+ * fixed 'svn merge':
+ - (r6447,#1402) -r FOO:PREV works correctly
+ - (r6452,#1379) no longer prints confusing no-op lines
+ - (r6500/6503,#1399) warn user when a tree-delta chunk can't be applied
+ * turn compression off to work around to mod_deflate timeouts (r6509)
+
+ Developer-visible changes:
+ * cvs2svn.py: lots of bugfixing related to branch/tag support
+ * diff code refactored to allow use by other tools (r6407)
+ * make 'svn export' set commit-timestamps (not ready yet) (r6420)
+ * fixed - memory leaks in libsvn_ra_dav commits (r6422)
+ * fixed - cvs2svn.py handles branch files rooted in dead revs (r6482,#1417)
+ * fixed - new lines now detected in svn:author property (r6497,#1401)
+ * fixed - svn_load_dirs works w/ absolute paths (r6507, Debian bug #187331)
+ * changed - build infrastructure now supports Perl SWIG bindings (r6441)
+ * removed - PORTING document no longer necessary (r6472)
+
+
+Version 0.25.0 (released 11 July 2003, revision 6394, branches/release-0.25.0)
+
+ User-visible changes:
+ * command line options:
+ - new --force option for svn export (r6327,#1296)
+ - new --force-log for commit, copy, delete, import, mkdir, move (r6294)
+ - no longer need --force for commit
+ * commands
+ - new - svnadmin archive (r6310)
+ - changed - svn import syntax now 'svn import [PATH] URL' (r6288,#933,#735)
+ - fixed - Search PATH for external diff commands (r6373)
+ - fixed - 'svn switch' memory bug (r6296)
+ - fixed - 'svn mkdir' coredump (r6388,#1369)
+ * python bindings now in -tools rpm for Mandrake 9.1 (r6374)
+ * allow parent-into-child copies, provided they are not WC->WC. (r6348,#1367)
+ * fixed - Apache module installation order (r6382-6,#1381)
+ * now require apache 2.0.47 (and apr 0.9.4)
+ * fix 2 commit leaks
+ * fix mod_dav_svn path-escaping bug
+
+ Developer-visible changes:
+ * Win32 build system
+ - new - .vcproj files for svn_config project and APR (r6311)
+ - fixed - SWIG bindings for Win32 (r6304)
+ - vcproj generator now works (r6316)
+ - swig's generated .c files now dependent on headers in .i files (r6379)
+ - refactored code common to dsp & vcproj into gen_win.py (r6328)
+ * fixed
+ - SEGFAULTs in SWIG bindings (r6339)
+ - potential SEGFAULTs in 'REPORT vcc' backward-compatibility code (r6377)
+ - mod_dav_svn's autoversioning failure on PUT (r6312)
+ - 'svn switch' memory bug (r6296)
+ * changed - mailer.py now uses svn_repos_replay()
+
+
+Version 0.24.2 (released 18 June 2003, revision 6284, branches/release-0.24.2)
+
+ User-visible changes:
+ * fix 'svn export' potential segfault
+ * fix occasional diff test failures
+ * fix 'svnadmin dump' memory hog
+ * fix new-dir-with-spaces bug
+
+ Developer-visible changes:
+ * none
+
+
+Version 0.24.1 (released 16 June 2003, revision 6249, branches/release-0.24.1)
+
+ User-visible changes:
+ * Fix bug in 'svn log'.
+
+ Developer-visible changes:
+ * none
+
+
+Version 0.24.0 (released 15 June 2003, revision 6234, branches/release-0.24.0)
+
+ User-visible changes:
+ * new 'svn diff [--old OLD] [--new NEW]' syntax (#1142)
+ * new --relocate option for svn switch (#951)
+ * new --version option for svnadmin and svnlook
+ * new path-based authorization module for apache
+ * make 'svn checkout' and not just 'svn update' resume a checkout
+ * .svn directories now hidden on Windows
+ * config variable 'store-password = no' now actually works
+ * fix 'svn merge --dry-run'
+ * fix 'properties hanging after a rename' issue (#1303)
+ * fix odd behavior of 'svn up -r PREV filename' (#1304)
+ * fix delete-tracking bug (#1348)
+ * fix dump and load corner cases (#1290)
+ * ra_dav server more resilient for foreign deltaV clients
+ * numerous ra_svn changes - must update clients and servers
+ * fix export over ra_svn (#1318)
+ * fix ra_svn error transmission bug (#1146)
+ * fix ra_svn corruption in tunnel mode (#1145)
+ * make svnserve multi-threaded on non-fork platforms (now works on Windows)
+ * remove svnserve -F and -S options
+ * various memory use improvements (#1323)
+ * various performance improvements for all protocols
+ * various performance improvements for 'svnadmin dump' and svnlook
+ * various subversion book updates (you have read the book right?)
+ * more cvs2svn.py improvements (and more to follow)
+ * new debugging script normalize-dump.py to normalize dump output
+
+ Developer-visible changes:
+ * path-based editor drivers
+ * no more RA->do_checkout()
+ * update python and java bindings
+ * various windows build fixes
+
+
+Version 0.23.0 (released 16 May 2003, revision 5962, branches/release-0.23.0)
+
+ User-visible changes:
+ * 'svn cat' now performs keyword expansion (#399)
+ * 'svn export' keyword expansion fixed
+ * checkouts are now restartable (#730)
+ * ssh ra_svn tunnel agent specified with svn+ssh://hostname/path syntax.
+ * remove dependency on external diff program
+ * don't error out early on unversioned files (#774)
+ * fix commands where REPORT fails if item isn't in HEAD (#891)
+ * updates now receive checksums like checkouts (#1101)
+ * 'svn revert dir' now resets property timestamp (#1163)
+ * fix instances of client showing help instead of error message (#1265)
+ * fix incorrect path in 'not a working copy' error messages (#1291)
+ * fix cvs2svn.py file added on branch problem (#1302)
+ * fix various vc-svn.el problems (#1257, #1268)
+ * fix various psvn problems (#1270)
+ * various Win32 build fixes
+
+ Developer-visible changes:
+ * fix various gcc 3.3 warnings (#1031)
+ * fix various memory errors/leaks
+ * remove java/jni bindings
+
+
+Version 0.22.2 (released 13 May 2003, revision 5918, branches/release-0.22.2)
+
+ User-visible changes:
+ * fix Win32 build
+ * properly handle on-disk template errors
+ * fix bogus uuid bug in cvs2svn.py
+
+ Developer-visible changes:
+ * none
+
+
+Version 0.22.1 (released 9 May 2003, revision 5874, branches/release-0.22.1)
+
+ User-visible changes:
+ * fix shared library installation problem
+ * update cvs2svn.py script
+
+ Developer-visible changes:
+ * none
+
+
+Version 0.22.0 (released 7 May 2003, revision 5842, branches/release-0.22)
+
+ User-visible changes:
+ * svn diff -r BASE:HEAD and other edge cases fixed (#977)
+ * svn diff and merge now have --ignore-ancestry option (#1034)
+ * svn ci -N DIR no longer errors during post-commit (#1239)
+ * ra_dav now optional (#617)
+ * update vn-svn.el (#1250, #1253)
+ * improvements to svn_load_dirs.pl (#1223, #1215)
+ * misc ra_svn bug fixes and protocol change
+ * log-encoding option now properly only applied to logs
+ * fix mmap failures on HP-UX
+ * fix some client memory leaks
+
+ Developer-visible changes:
+ * finish transition to new xml prop namespaces for mod_dav_svn (#840)
+ * minimize full tree locks and number of system calls (#1245)
+ * auto-generated .dsp files (#850)
+ * fix ETag of directory (#1251)
+ * added export editor (#1230)
+
+
+Version 0.21.0 (released 15 Apr 2003, revision 5639, branches/release-0.21)
+
+ User-visible changes:
+ * SSL client and server certificate verification
+ * authentication info now stored in ~/.subversion/auth/
+ * svn diff on a copied file only shows local mods, not the whole file
+ * svn propget now takes a --strict option to control output
+ * svnadmin load now takes a --parent-dir option
+ * added the new 'svndumpfilter' program
+ * svnlook now has 'cat', 'propget', and 'proplist' commands to enable
+ viewing this information on transactions
+ * 'svn copy' from another repository now adds without history
+ * tag/branch conversion disabled in cvs2svn until it gets fixed
+ * the 'anonymous' user is no longer used; we simply avoid attaching an
+ author property when an author is not available
+ * improvements to ignored-file handling
+ * Python ConfigParser-style variable expansion for config file (#1165)
+
+ Developer-visible changes:
+ * introduced the svn_filesize_t type (#639)
+ * realmstring added to the svn_auth framework
+ * the "result checksum" moved to the editor.close_file function
+ * more checksumming here and there
+ * initial work to enable binary properties via ra_dav
+ * initial, internal support for compressed streams
+ * test framework shifting to exception-based failure recording (#1193)
+ * improved options and handling in the C test framework
+ * java and python binding work
+ * libsvn_auth folded into libsvn_subr
+ * bug fixes: 'svnadmin load' parse bug; ra_svn crashes (#1160); 'svn
+ log' on a switched wc (#1108); 'svn ci -N' on named files (#1195)
+
+
+Version 0.20.1 (released 26 Mar 2003, revision 5467, branches/release-0.20.1)
+
+ User-visible changes:
+ * fix svnadmin load bug so that property deletions actually occur
+ * fix checksum compatibility issue for older repositories
+
+ Developer-visible changes:
+ * none
+
+
+Version 0.20.0 (released 20 Mar 2003, revision 5410, branches/release-0.20)
+
+ User-visible changes:
+ * new compatibility rule: require only that each interim release be
+ compatible with the one before it (see HACKING)
+ * ra_svn is still new so above rule doesn't yet apply
+ (i.e. 0.20 over ra_svn is NOT compatible with previous releases)
+ * merge infers the target path (see book chapters 4 & 8)
+ * merge continues in presence of missing target file
+ * merge's add notifications are no longer duplicated
+ * commands can be safely interrupted (Ctrl-C)
+ * --encoding global default in ~/.subversion/config
+ * new option --editor-cmd
+ * begin multi-release transition to escape binary properties over DAV
+ * misc performance improvements
+
+ Developer-visible changes:
+ * RA vtable functions take pool argument
+ * svn-config --includes path fixed
+ * uuid at creation now complete
+ * start having test failures throw exceptions rather than return errors
+ * test suite option --cleanup with --verbose being default mode
+ * continued diff library development
+ * minor revprop hook changes
+ * bug fixes: no diff on binary files (#1019), consistent error messages
+ (#1181), version numbers in hook scripts (#1182), win98 codepage (#1186)
+
+
+Version 0.19.1 (released 12 Mar 2003, revision 5303, branches/release-0.19.1)
+
+ User-visible changes:
+ * fix svnserve tunnel mode pipe close bug
+
+ Developer-visible changes:
+ * none
+
+
+Version 0.19.0 (released 10 Mar 2003, revision 5262, branches/release-0.19)
+
+ User-visible changes:
+ * svn ls works on wc paths (#1130)
+ * new cvs2svn.py features and bug fixes (1105)
+ * new svnlook subcommand 'uuid'
+ * new svnadmin create option '--bdb-txn-nosync' (use with care)
+ * fix svnserve help output
+ * SVN_EDITOR now overrides svn-editor in ~/.subversion/config
+ * miscellaneous performance improvements (memory and speed)
+ * more work on the Book
+
+ Developer-visible changes:
+ * start implementing cancellation of long-running functions
+ * misc windows build fixes and features (DSP generator)
+ * -W and -P options to stress.pl
+ * start adding support for multiple fs backends
+ * work on bindings and bindings build system (#1132, #1149)
+ * bug fixes: ra_dav import/checkout memory usage (#995), control chars
+ in commit messages (#1025), svn merge memory usage (#1069, #1077),
+ pre-existing ~/.subversion (#1121), keyword expansion (#1151), line
+ number in config error message (#1157), svn-tunnel-agent in [default]
+ (#1158), RA->close RIP (#1164), config-test non-source (#1172)
+
+
+Version 0.18.1 (released 26 Feb 2003, revision 5118, branches/release-0.18.1)
+
+ User-visible changes:
+ * editor environment variables no longer incorrectly required
+ * 'svn help import' now displays correct usage
+ * fix crashes in the internal diff library and ra_dav
+ * fix Win9x/Me console issue
+ * cvs2svn.py api fix
+ * hot_backup.py now correctly removes old backups
+
+ Developer-visible changes:
+ * various rpm package fixes
+
+
+Version 0.18.0 (released 19 Feb 2003, revision 4968, branches/release-0.18)
+
+ User-visible changes:
+ * renamed the [default] section to [global] in the servers config file
+ * compression option is now http-compression and lives in servers file
+ * use internal diff by default rather than external program (#405 in progress)
+ * symlinked hook scripts now run
+ * read-only access flag (-R) for svnserve
+ * quiet flag (--quiet) for svnadmin dump
+ * --ignore-uuid and --force-uuid for svnadmin load
+ * miscellaneous performance improvements
+ * more work on the Book
+
+ Developer-visible changes:
+ * new authentication library libsvn_auth (#724)
+ * new bdb table uuids
+ * client context object in libsvn_client
+ * more work on java and other language bindings
+ * test framework now has a quiet option (-q)
+ * miscellaneous small code cleanups
+ * bug fixes: more valgrind memory bugs, apr xlate i18n mess (#872),
+ non-existent URL checkout (#946), props on to-be-deleted files (#1066),
+ ra_svn move/copy (#1084), eol translation (#1085), ra_svn
+ checksumming (#1099), cat command corrupt output (#1104), cvs2svn
+ memory consumption (#1107), merge of property add (#1109),
+ '..' relative path (#1111), commit/cleanup/diff3 (#1119),
+ .svn/entries checksum (#1120), svn commit in / (#1122),
+ status on uncontrolled directory (#1124), commit message eol
+ characters (#1126), cat -r PREV (#1134), ra_dav wcprops (#1136)
+ split XML cdata/attribute encoding (#1118)
+
+
+Version 0.17.1 (released 22 Jan 2003, revision 4503, branches/0.17.1)
+
+ User-visible changes:
+ * changed non-baseline build version number display.
+ * compatibility change: make sure old clients can talk to newest servers.
+ * some changes to the Book
+
+ Developer-visible changes:
+ * dumper/loader now use checksums (#1102)
+ * miscellaneous small code cleanups
+ * bug fixes: eol-style timestamp changes (#1086), valgrind mem bug,
+ better checksum error reporting,
+
+
+Version 0.17.0 (released 20 Jan 2003, revision 4468, branches/0.17)
+
+ User-visible changes:
+ * 'svn add' is now recursive by default, -N to disable (#1073)
+ * new 'svnversion' program summarizes mixed-revs of a working copy
+ * huge improvements to the mailer.py tool
+ * more work on the Book and man page
+ * default global-ignores now built-in, new runtime-config file commented out
+
+ Developer-visible changes:
+ * checksums, checksums everywhere (issues #649, #689):
+ - filesystem stores them, and verifies them when reading/writing
+ - working copy stores them, and verifies them when reading/writing
+ - checksums transferred both ways over network, RA layers verify them
+ * finish draft of internal diff/diff3 library -- ready for testing/optimizing
+ * more utf8<->apr conversion work (#872)
+ * more work on swig/python and ruby bindings
+ * improvements to win32-innosetup package
+ * 'svnserve' now has an official IANA-assigned portnumber.
+ * mod_dav_svn now only sends/understands new xml prop namespaces (#840)
+ * bug fixes: stop needless fs rep data copies (#1067), wc auth
+ caching bugs (#1064), use APR_BUFFERED to open files (#1071), lots
+ of wc 'missing dir' edge-case bugs (#962), prevent wc from
+ receiving '.svn' (#1068), don't commit symlinks (#1081), better
+ diff labels (#1080), better fulltext tmpfile names in conflicts (#1079),
+ prevent ra_dav from deleting out-of-date items (#1017), segfault (#1092),
+ don't attempt checksum on missing tmp textbase (#1091), allow diffs
+ during update again (yikes!)
+
+
+Version 0.16.1 (released 6 Jan 2003, revision 4276)
+
+ User-visible changes:
+ * ra_svn network layer (apache alternative) now tested & ssh-tunnelable
+ * new (experimental) mod_dav_svn autoversioning feature (SVNAutoversioning)
+ * reorganization of the ~/.subversion/ run-time config files.
+ * more entry caching: approx. 3x speedup on checkouts & updates.
+ * option rename: --non-recursive instead of --nonrecursive
+ * option rename: --no-diff-deleted instead of --no-diff-on-delete
+ * new 'svn log --quiet'
+ * new 'svn diff --no-diff-deleted'
+ * fix keyword expansion behaviors ($keyword:$ / $keyword$ / $keyword: $)
+ * handle win32 non-ascii config-file paths (#968, #1048, part of #872)
+
+ Developer-visible changes:
+ * most public header files now using doxygen markup
+ * new (untested) internal difflib (#405)
+ * neon debugging now tweakable via run-time config file
+ * more progress on Subversion Book rewrite.
+ * new ./configure --with-diffutils
+ * begin work on client/server checksums (#649)
+ * regression tests now depend on svnadmin dump/load
+ * lose src_err field of svn_error_t
+ * many fs function renames: begins fs database back-end abstraction.
+ * new libsvn_repos prop-validating wrappers
+ * lots of work on build-system dependency graph generation (for SWIG building)
+ * swig binding work:
+ - python svn calls can now run as independent threads
+ - new java-binding build system
+ - improved swig building features: --prefix, LDFLAGS behaviors
+ * many, many bug fixes: wc->repos copies (#1029), #943 followup
+ (#1023), copies of copies (#830), 'svn resolve' cleans up entries
+ file (#1021), prop merging (#1012), segfault fixes (#1027, #1055),
+ autoconf 2.5X (#886), O(1) copies (#717), new 'failed revert'
+ signal (#714), detect missing schedule-add conflicts (#899, #863),
+ begin dav namespace switchover (#840), status bugs, url auth
+ inference (#1038), log bug (#1028), newline prompt (#1039),
+ svnadmin errorchecking, url syntax errors (#1057, #1058), apr/utf8
+ work (start #872), and many more.
+
+
+Version 0.16 (released 4 Dec 2002, revision 3987)
+
+ User-visible changes:
+ * new 'svn cat' subcommand
+ * new --revprop flag to access revision props, -r for versioned props (#943)
+ * new "compression" runtime option in ~/.subversion/config
+ * svnadmin/svnlook now use help system, and some subcommands deleted or moved.
+ * tool changes:
+ - new svnshell.py tool
+ - new mirror_dir_through_svn.cgi script
+ - new svn_load_dirs.pl features
+ - updates to vc-svn.el
+ * --message-encoding is now just --encoding, and affects svn: propvals too.
+ * major rewrites of chapters 3, 4, 5 of the Subversion Book.
+
+ Developer-visible changes:
+ * new network layer, libsvn_ra_svn! still experimental.
+ * all svn_error_t's now allocated in subpool of global pool.
+ * reorganize svnlook/svnadmin subcommands & option-parsing (#540, #915, #910)
+ * all log messages and svn: props now stored as UTF8/LF endings (#896)
+ * huge cleanup/reorg of all svn_path_* routines
+ * svn_client_status sends feedback, distinguishes unversioned vs. ignored
+ * improvements to swig typemappings and build processes
+ * fixes to pool cleanup handlers
+ * begin abstraction of gen_make.py
+ * entry-caching improvements
+ * stop using global apr_xlate objects
+ * win32-innosetup code added to packages/
+ * new work on ruby bindings and swig-java bindings
+ * many, many bug fixes: various small coredumps, svn_error_t leaks,
+ copy props correctly (#976), copy executable bits correctly (#982),
+ test-system fix (#882), accidentally imported tmpfile (#964),
+ ra_local checkout memleak (#985), accidental wc deletion (#988),
+ better text vs. binary detection (#994), dav log-report error
+ handling, bad 'svn switch' dav caching (#1000), don't call NULL
+ callbacks (#1005), bogus switch feedback (#1007), eol-style file
+ corruption (#920), getdate.y fix (#1009), ra_local error reporting (#900),
+ start of work on issues #830 and #869.
+
+
+Version 0.15 (released 7 Nov 2002, revision 3687)
+
+ User-visible changes:
+ * New 'S' indicator in 'svn status' shows switched subdirs
+ * New --dry-run option added for 'svn merge' (issue #953)
+ * Fix 'svn update .' to handle svn:externals correctly
+ * Memory usage of 'svn import' reduced (issue #860)
+ * Allow 'svn revert' on missing directories scheduled for deletion
+ * Assorted bug fixes in several exciting flavors
+ * Documentation improvements
+
+ Developer-visible changes:
+ * #911 (apr and apr-util version at build time)
+ * Fixed issues #851, #894,
+ * Testing scripts accept --url=URL and BASE_URL=URL
+ * Issue #881 (--enable-all-static)
+ * Delta editors all converted to new-style, and editor composition is gone
+ * Improve libsvn_wc wcprop handling (issue #806)
+ * SWIG binding improvements
+ * Various pool usage improvements
+
+
+Version 0.14.5 [Alpha Interim 5] (released 30 Oct 2002, revision 3578)
+
+ User-visible changes:
+ * allow --incremental option for 'svn log' xml output
+
+ Developer-visible changes:
+ * autoconf bugfix for berkeley-db detection
+ * clean up property interface mess (part of #806)
+ * dish.sh bugfix: build the new docbook docs correctly
+ * python tests now log commands
+ * gen-make.py now assumes 'build.conf'
+
+
+Version 0.14.4 [Alpha Interim 4] (released 29 Oct 2002, revision 3553)
+
+ User-visible changes:
+ * new working-copy entry-caching: speeds many ops up to 5x (#749)
+ * new 'svnadmin recover', instead of db_recover
+ * client can now view & change server-side revision props (e.g. log messages)
+ * new --non-interactive switch for commandline client
+ * new --incremental option to 'svn log'
+ * new -r {date} syntax for specifying dated revs; works over network too.
+ * automatically set svn:executable prop when adding or importing (#870)
+ * initial $EDITOR text now ignores all log data below special token
+ * consistify behavior of text & prop columns in 'svn status' output.
+ * .svn/auth/* files now chmod 700, to stop scaring people. :-)
+ * improved labels in 'svn diff' output (#936)
+ * run-time adjustable neon timeout in newly renamed 'servers' config file
+ * big improvements to cvs2svn script: bugfixes and basic branch/tag support
+ * new python access-control hook script
+ * no more implicit dot-target for 'svn propedit' or 'svn propset' (#924)
+ * Win32 improvements:
+ - use system-wide config-file/registry
+ - run-time configurable diff/diff3 binary locations (#668)
+ * remove obsolete --xml-file support
+ * Handbook is now ported to Docbook, 2 new chapters.
+
+ Developer-visible changes:
+ * abstracted option/help-parsing code, now shared between svn and svnadmin
+ * require apache 2.0.42
+ * use neon 0.23.5: fix XML entity derefs, SSL server certs, HP-UX build, etc.
+ * support Berkeley DB 4.0 *or* 4.1
+ * many SWIG binding improvements:
+ - better overall coverage of apr and libsvn_* library symbols
+ - new 'make swig-py-ext' and 'make install-swig-py-ext' targets
+ * finish conversion of all editor/drivers to "new" style (#737)
+ * removed xml-delta editors and editor drivers and related tests
+ * new predicate-logic system added to automated-test system ("skip" support)
+ * more work on mailer.py
+ * no more lost commit messages (#761)
+ * eradication of misused stringbufs, obsolete code removal (#909)
+ * mem-leak fixes in libsvn_fs (#860)
+ * improved atomicity of working-file translations (#914)
+ * improve ./configure --help output (#949)
+ * MANY bugfixes, especially for entry-locks (#931, #932, #847, #938),
+ merges (#880, ), auth storage (#934); also #921 (svnadmin
+ segfault), #907 (xml quoting), #918 (post-commit processing), #935
+ (path canonicalization), #779 (diff errors)
+
+
+Version 0.14.3 [Alpha Interim 3] (released 20 Sept 2002, revision 3200)
+
+ User-visible changes:
+ * new ~/.subversion/config file
+ * new $Id$ keyword
+ * new client --no-auth-cache option
+ * empty values in the Windows Registry are no longer ignored (issue #671)
+ * report details of repository start-commit or pre-commit hook errors
+ * fix locking behaviour when using current directory as a target
+ * updated man page
+ * new front-page logo. :-)
+
+ Developer-visible changes:
+ * continuing work on python SWIG bindings
+ * continuing work on new access-baton system for libsvn_wc
+ * upgrade to neon 0.23.4 to fix Windows build issues and seg faults
+ * add XFAIL to the C testing framework
+ * prevent setting of certain svn: props on incorrect file types
+ * cleanup libsvn_subr's path library behavior
+ * new 'fast-clean' vs. 'clean' Makefile targets
+ * various bugfixes, tweaks, cleanups.
+
+
+Version 0.14.2 [Alpha Interim 2] (released 22 Aug 2002, revision 3033)
+
+ User-visible changes:
+ * fs schema change, see issue #842. **NOTE: repos dump/load cycle required!**
+ * new 'svn ls -R' option
+ * new status code `~', for type changes
+ * add --username and --password options to 'svn ls'
+ * new script tools/client-side/svn_all_diffs.pl
+ * new script tools/examples/blame.py (draft)
+
+ Developer-visible changes:
+ * test suite now does XFAIL and XPASS
+ * test suite over DAV now uses SVNParentPath, no longer depends on symlinks
+ * DAV tests now work on Windows
+ * upgrade to neon 0.22.0
+ * 'make install' notices the $(DESTDIR) parameter
+ * new dav prop namespaces, but old still sent for compat; see issue #840
+ * error code space reorganized, see issue #702
+ * many cleanups to path handling
+ * more use of access batons in libsvn_wc, see issue #749
+ * working props now stored with ".svn-work" extension, see issue #618
+ * the usual round of bug fixes, new regression tests, etc
+
+
+Version 0.14.1 [Alpha Interim 1] (released 9 August 2002, revision 2927)
+
+ User-visible changes:
+ * show copy-ancestry in 'svn log -v'
+ * 'svn co' can take multiple URLs now
+ * new 'svn ls' command
+ * new 'svn st --no-ignore' option
+ * new 'svn --version --quiet' option
+ * more conservative 'svn help' usage error-message
+ * more graceful degradation from charset conversion failure
+ * standardize policy of -q switch behavior
+ * less intimidating error output
+ * new SVNParentPath directive for mod_dav_svn <Location>s
+ * svnlook now correctly displays copied subtrees
+ * Handbook: additions, tweaks, cleanups, and new French Translation :-)
+ * svn_load_dirs.pl: auto propset on files matching specified regex, bug fixes
+
+ Developer-visible changes:
+ * integrated the delta-combiner! (issue #531)
+ * integration of libsvn_wc-baton-locking branch (issue #749)
+ * new "skip-deltas" added to delta-combiner
+ * properly URI-encode/decode path components throughout our code
+ * RA->do_diff() made independent from RA->do_switch().
+ * stricter setting/parsing of svn:mime-type property in client and server.
+ * new 'install-static' make target
+ * extend SWIG bindings to libsvn_wc and libsvn_client
+ * BerkeleyDB usage tweaking: in preparation for auto-recovery features.
+ * work on #850 (.dsp generator)
+ * Better support for incremental dumps (see revision 2920)
+ * started fs branch work on #842 (copyID inheritance), #830 (copies of
+ copies), #790 (copy table uses txnID), #815 (custom sorting)
+ * numerous bugfixes: #709 (better error handling), #813/814
+ (apr_filepath_merge), #685 (showing dir propdiffs), OS X dumper
+ bugfix, #561 (property conflict detection), mod_dav_svn path bugs,
+ svn_wc_status() bugs, path canonicalization bugs, #816 (svn log -r),
+ #843 (URL keyword), #846 (kind-change replacement), #809 ($EDITOR dir),
+ #855 (module updates not cooperating with new wc access batons),
+ improvements to test suite sensitivity,
+
+
+Version 0.14.0 [Alpha] (released 23 July 2002, revision 2667)
+
+ User-visible changes:
+ * finally some documentation: The Subversion Handbook
+ * i18n support for paths, prop names, and log messages; (not on Win32 yet)
+ * support for URI-escaped paths
+ * "-R" is now short for --recursive, and "-N" replaces "-n"
+ * add the -R option to 'svn info' and 'svn resolve'
+ * new syntax for 'svn switch' and 'svn co'
+ * new 'svn-config' file installed
+ * new commit-access-control.pl utility (feature #775)
+ * new vc-svn.el, first pass at Emacs VC support for Subversion
+ * lots of work on svn_load_dirs.pl (provides vendor-branch-like features)
+ * new --message-encoding option for logfiles given by -F
+ * support win32 drive-letters in file:/// urls
+ * improved date output syntax: ISO-8601 prefix, then human-friendly suffix
+ * the usual round of bug fixes
+
+ Developer-visible changes:
+ * UTF-8 changes
+ - all libraries now assume UTF-8 input paths and log msgs
+ - many apr calls are now abstracted into new svn_io_* wrappers
+ * fs schema change
+ - cache each revision's changed-paths in a new 'changes' table
+ - another repository dump/load is required
+ * a number of fs-dumper bugfixes and redesigns
+ * test suite is now all python, so it can run on win32
+ * reduce huge memory consumption of mod_dav_svn during checkouts
+ * memory optimizations for prop-reading and 'svn diff'
+ * bugfixes for commit-email.pl and tweak-log.cgi
+ * lots of branch work on the delta-combiner and on libsvn_wc rewrite
+ * numerous bugfixes: 'svn merge .' bug (#748), bug #764, two new
+ ghudson-dirversioning bugs, #756, #675, #783, #796, wc-root bugs,
+ #799, #800, #797, directory-removal bugs (#611, #687)
+
+
+Version 0.13.2 [Pre-Alpha] (released 28 June 2002, revision 2376)
+
+ User-visible changes:
+ * fixed various buggy commandline outputs
+ * allow global/local config-files on win32
+ * prevent overwrites with 'svn cp URL URL'
+ * improvements to svn_load_dirs.pl
+ * mod_dav_svn can generate xml output for directory GETs
+ * new svnadmin(1) man page
+
+ Developer-visible changes:
+ * finished notification callback system, no more buggy output
+ * fs-changes:
+ - revisions table nothing but an index to txns table
+ - branch work-in-progress: new 'changes' table to store changed paths
+ * more work on svn_time_* funcs and formats (moving towards ISO8601)
+ * property reversion bugs fixed, dumper bug fixed
+ * add version number to svndiff database storage
+ * new regression tests for 'svn merge'
+ * fix 'svn diff -rX:Y' server bug
+ * fix bugs in python test system
+ * bring win32 build up-to-date, get most python tests working on win32
+
+
+Version 0.13.1 [Pre-Alpha] (released 20 June 2002, revision 2291)
+
+ User-visible changes:
+ * "modules" are now implemented
+ * new 'svn export' command
+ * 'svn log' now traverses copy history and can print changed paths
+ * 'svn merge' now (temporarily) only merges into '.'
+ * 'svnadmin lscr' now traverses copy history
+ * changes to the 'svn:executable' prop take effect immediately now
+ * server is more tolerant of wc's with old-style version resource URLs
+ * new Handbook started
+ * commit-email.pl fixes/improvements -- now shows prop mods and copy history
+ * bug fixes to cp, rm, merge, revert, admin dump and load, svnlook
+
+ Developer-visible changes:
+ * headers now install in subdir and libs are named libsvn_FOO-1.so
+ * improvements to the Python test suite
+ * delta combiner implemented (unused for now, though)
+ * Python SWIG binding improvements: ability to write an editor in Python
+ * new example: tools/examples/svnlook.py
+ * start moving libsvn_client to new notification system (no composed editors!)
+ * upgrade to neon 0.21.2, fixing deflated communication with apache
+ * Moved Berkeley-specific code to libsvn_fs/bdb/, skels into libsvn_fs/util/
+ * changes to the RPM packaging
+
+
+Version 0.13.0 [Pre-Alpha] (released 10 June 2002, revision 2140)
+
+ User-visible changes:
+ * repositories have a new database schema; existing ones must be upgraded!
+ - new svnadmin 'dump'/'load' commands to migrate repositories
+ - read http://svn.apache.org/repos/asf/subversion/trunk/notes/repos_upgrade_HOWTO
+
+ Developer-visible changes:
+ * complete rewrite of filesystem schema!
+ - skels are abstracted away, opening the door to SQL backends
+ - node-ids now have copy IDs
+ * huge progress on module system [only checkouts work at the moment]
+ * massive conversion of stringbufs to char* in our public APIs
+ * vsn-rsc-urls are now based on created-rev/path instead of fs_id_t's.
+ * reinstate 'deleted' flag on entries, to ensure accurate update reports
+ * dir_delta learns how to send copy history
+ - svnlook no longer sends 10MB emails when we make a branch
+ - dumpfiles get much smaller
+ * memory consumption reduced via new apr-pool code that reuses/frees mem
+ * client can now parse ISO-8601 timestamps (start of issue 614)
+ * added script for stress-testing concurrent repository access
+ * auto-locate apache's apr libraries at build-time
+ * beginnings of ra_pipe library
+ * progress on delta combiner code
+ * many memleaks fixed, thanks to valgrind!
+ * upgrade to newest neon, allow deflated communication with apache
+ * many bugfixes to merge, switch, checkout, rm; tackling of issues 704,
+ 705, 698, 711, 713, 721, 718 and many others
+
+
+Version 0.12.0 (released 3 May 2002, revision 1868)
+
+ User-visible changes:
+ * 'svn diff' can now compare two arbitrary URLs
+ * 'svn diff' now displays property changes
+ * 'svn rm' requires --force for unversioned and/or modified items
+ * 'svn rm' immediately removes files & uncommitted dirs
+ * 'svn mv' for WC->WC behaves like 'svn rm' with respect to the source
+ * checkouts, updates, switches now print received revision on final line.
+ * new 'svn info' command prints information about a versioned resource.
+ * switch to 2-part conflict markers (diff3 -E) instead of 3-part (diff3 -A)
+ * new bash programmable completion file
+ * file's executable bit can be versioned (svn:executable prop)
+ * commits and imports now support --nonrecursive option
+ * new --xml option for 'svn log'
+ * new 'svnadmin dump' command
+
+ Developer-visible changes:
+ * updates correctly deal with disjoint urls.
+ * libsvn_wc now checksums text-bases, to detect working copy corruption
+ * cached wcprops (vsn-rsc-urls) now auto-regenerate if invalid
+ * python testsuite now runs on Win32.
+ * new switch_tests.py added to testsuite
+ * NEW internalized diff/diff3 library. Not yet integrated/tested.
+ * dir_delta sends entry props; pipe-editor removed.
+ * no more expat/ tree; use apr-util's expat instead.
+ * fs deltificaton happens outside commit process, using fewer db locks
+ * privatize svn_fs_id_t structure
+ * start abstracting skels out of libsvn_fs
+ * new docs: secure coding tips, quickref card
+ * memory bugfixes for import/commit/mass removals
+ * many bugfixes: issues 644, 646, 691, 693, 694, 543, 684
+
+
+Version 0.11.1 (released 12 April 2002, revision 1692, branches/0.11.0)
+
+ User-visible changes:
+ * completion of 'svn merge' (issue 504)
+ * added SVNReposName directive to mod_dav_svn
+ * insist on a diff binary that supports "-u"
+ * fix and unify pop-up $EDITOR behaviors (issues 638, 633, 615)
+
+ Developer-visible changes:
+ * finish rewrite of commit system to handle disjoint urls (issue 575)
+ * finish proxy support via config files (esp. on win32) (issue 579)
+ * fix svn_ra_dav__get_baseline_info and related bugs (issue 581)
+ * reorganization of libsvn_wc header files & API
+ * new getopt_tests.py to test commandline option processing
+ * 'make check' now more portable -- tests invoked via python, not sh
+ * miscellaneous bugfixes in imports, svndiff, db linkage.
+
+
+Version 0.11.0 (unreleased)
+
+
+Version 0.10.2 (released 25 Mar 2002, revision 1587)
+
+ User-visible changes:
+ * new ~/.subversion configuration directory
+ * proxy support via ~/.subversion/proxies file
+
+ Developer-visible changes:
+ * rewrite of client-side commit process partially done
+ * beginnings of 'svn merge'
+ * mod_dav_svn now generates "streamy" report responses
+ * stringbuf cleanups and bugfixes
+ * interface to svn_wc_entry_t cleaned up
+ * tweaks to build system and freebsd port
+ * miscellaneous bugfixes in path escaping, pool usage, hp-ux compilation
+
+
+Version 0.10.1 (released 17 Mar 2002, revision 1537)
+
+ User-visible changes:
+ * New --targets command-line option for some commands.
+ * conflicts now create conflict-markers in files, and 3 fulltext backups.
+ * new 'svn resolve' command removes conflicted state (by removing backups)
+
+ Developer-visible changes:
+ * no more dependency on 'patch'; only on GNU diff3 and some version of 'diff'
+ * complete rewrite of svn_wc_entry_t interface
+ * begin abstracting svn_fs API by hiding implementation details
+ * consolidate RA layer callbacks
+ * start work on commit-driver rewrite
+ * start work on ~/.subversion/ configuration directory, and proxy support
+ * move a lot of svn_wc.h into private wc.h
+ * bugfixes relating to commits, network prop xfers, 'svn log', 'svn co -q'
+ * major deletion bug fixed
+ (see email WARNING:
+ http://subversion.tigris.org/servlets/ReadMsg?msgId=64442&listName=dev)
+
+
+Version 0.10.0 (released 08 Mar 2002, revision 1467)
+
+ User-visible changes:
+ * fewer out-of-memory errors: (see "memory consumption" below)
+ * clearer user errors:
+ - detailed marshalling of server errors to client
+ - better errors from ra_dav
+ - better commandline-client-specific error messages
+ * 'svn log' now works on single paths correctly
+ * show locked directories in 'svn status'
+ * 'svnadmin lstxns' improvements, and new --long switch
+ * commits show "Replacing" instead of "Deleting/Adding" (#571)
+ * commits show progress on postfix txdeltas.
+ * WARNING: existing repositories need to be upgraded;
+ read tools/enable-dupkeys.sh.
+
+ Developer-visible changes:
+ * reduced memory consumption
+ - new Editor interface that manages pools automatically
+ - conversion of most existing editors to new system
+ - have libsvn_fs write data to DB streamily
+ - reduce DB logfile growth via 'duplicate keys'
+ - stop using one pool for post-commit processing
+ - stop using one pool for sending all textdeltas
+ - many, many other pool-usage improvements in libsvn_wc, ra_dav, etc.
+ * start of work on 'svn merge": issue 504, and diff3 integration
+ * start of work on disjoint-url detection: issue 575
+ * start removing stringbuf path library funcs; use new const char * funcs
+ * better python 2.X detection in test suite
+ * svnlook uses single tempdir
+ * build system evolution
+ - upgrade to neon 0.19.[2-3]
+ - lots of work on FreeBSD port
+ * many small bugfixes:
+ - propedit, file merges, revert, dir_delta, keywords
+ - memory leaks in 'svn add', 'svn import/commit', and svnlook
+ - date-parsing and readonly bugs
+
+
+Version 0.9 (released 15 Feb 2002, revision 1302)
+
+ User-visible changes:
+ * 'svn switch', for switching part of a working copy to a branch
+ * 'svn status -v' now shows created-rev and last-author info
+ * 'svn help <subcommand>' now shows proper switches
+ * if no log message passed to commit, $EDITOR pops up
+ * greatly improved/re-organized README, INSTALL, and HACKING docs
+ * big progress on cvs2svn repository converter
+ * faster retrieval of old revisions: turn off fs directory deltification
+ * fixed broken behaviors in 'svn diff' and 'svn log'
+
+ Developer-visible changes:
+ * new fs code for detecting differences and relatedness
+ * new cancellation editor, for event-driven users of libsvn_client
+ * make .svn/ area readonly
+ * continued development of ruby, java, and python (swig) bindings
+ * new config-file parser
+ * code reorganization and cleanup
+ - huge conversion of svn_stringbuf_t --> char *
+ - standardized on commit_info return structure
+ - no more 'path styles' in path library
+ - rewrite bootstrapping code for python test framework
+ - rewrite commandline app's help-system and alias-system
+ - feedback table replaced with notfication callback
+ - rewrite sorting of hashes
+ - svnadmin internal rewrite
+ - faster post-update processing
+ - using SVN_ERR macros where they weren't
+ - new svn_client_revision_t mechanism
+ - txdelta windows are readonly now
+ - pool debugging code moved to APR
+ - various pool-usage fixes
+ * build system evolution
+ - apr-util now required
+ - upgrade to neon 0.18.5
+ - much apr m4 macro churn
+ - win32 updates, no longer needs precompiled neon
+ - 'make check' when builddir != srcdir
+ * fixes for many issues, including #624, 627, 580, 598, 591,
+ 607. 609, 590, 565
+
+
+[Versions 0.8 and older are only brief summaries]
+
+Version 0.8 (released 15 Jan 2002, revision 909)
+
+ * newline conversion and keyword substitution (#524)
+ * rewrite ra_local commit system to commit against HEAD (#463)
+ * mod_dav_svn sends svndiffs now (#518)
+ * code migration from libsvn_fs to libsvn_repos (#428)
+
+
+Version 0.7 (released 03 Dec 2001, revision 587)
+
+ * 'svn cp/mv' completed:
+ - can copy from wc/repos to wc/repos
+ - This how we create branches/tags
+ * 'svn mkdir' [WC_PATH|REPOS_URL]
+ * 'svn delete' [REPOS_URL]
+
+
+Version 0.6 (released 12 Nov 2001, revision 444)
+
+ * 'svn log'
+ * 'svn cp/mv' from wc to wc
+
+
+Milestones M4/M5 (released 19 Oct 2001, revision 271)
+
+ * network layer bugfixes
+ * filesystem deltification
+
+
+Milestone M3 (released 30 Aug 2001, revision 1)
+
+ * self-hosting begins, all history left behind in CVS repository.
+
+
+Milestone M2 (released 15 May 2001, from CVS, "milestone-2" tag)
+
+ * filesystem library (libsvn_fs)
+ * network layer (libsvn_ra_dav and mod_dav_svn)
+
+
+Milestone M1 (released 20 Oct 2000, from CVS, "milestone-1" tag)
+
+ * working-copy library (libsvn_wc), using XML files
+
+
+Birth (05 June 2000)
+
+ * CVS repository created.
diff --git a/contrib/subversion/COMMITTERS b/contrib/subversion/COMMITTERS
new file mode 100644
index 0000000..f0a8739
--- /dev/null
+++ b/contrib/subversion/COMMITTERS
@@ -0,0 +1,234 @@
+The following people have commit access to the Subversion sources.
+Note that this is not a full list of Subversion's authors, however --
+for that, you'd need to look over the log messages to see all the
+patch contributors.
+
+If you have a question or comment, it's probably best to mail
+dev@subversion.apache.org, rather than mailing any of these people
+directly.
+
+Blanket commit access:
+
+ jimb Jim Blandy <jimb@red-bean.com>
+ sussman Ben Collins-Sussman <sussman@red-bean.com>
+ kfogel Karl Fogel <kfogel@red-bean.com>
+ gstein Greg Stein <gstein@gmail.com>
+ brane Branko Čibej <brane@apache.org>
+ jorton Joe Orton <joe@manyfish.co.uk>
+ ghudson Greg Hudson <ghudson@mit.edu>
+ fitz Brian W. Fitzpatrick <fitz@red-bean.com>
+ daniel Daniel Stenberg <daniel@haxx.se>
+ cmpilato C. Michael Pilato <cmpilato@collab.net>
+ philip Philip Martin <philip.martin@wandisco.com>
+ jerenkrantz Justin Erenkrantz <justin@erenkrantz.com>
+ rooneg Garrett Rooney <rooneg@electricjellyfish.net>
+ blair Blair Zajac <blair@orcaware.com>
+ striker Sander Striker <striker@apache.org>
+ dlr Daniel Rall <dlr@finemaltcoding.com>
+ mbk Mark Benedetto King <mbk@lowlatency.com>
+ jaa Jani Averbach <jaa@iki.fi>
+ julianfoad Julian Foad <julian.foad@wandisco.com>
+ jszakmeister John Szakmeister <john@szakmeister.net>
+ ehu Erik Hülsmann <ehuels@gmail.com>
+ breser Ben Reser <ben@reser.org>
+ maxb Max Bowsher <maxb1@ukf.net>
+ dberlin Daniel Berlin <dberlin@dberlin.org>
+ danderson David Anderson <david.anderson@natulte.net>
+ ivan Ivan Zhakov <chemodax@gmail.com>
+ djames David James <james@cs.toronto.edu>
+ pburba Paul Burba <pburba@collab.net>
+ glasser David Glasser <glasser@davidglasser.net>
+ lgo Lieven Govaerts <lgo@mobsol.be>
+ hwright Hyrum Wright <hyrum@hyrumwright.org>
+ vgeorgescu Vlad Georgescu <vgeorgescu@gmail.com>
+ kameshj Kamesh Jayachandran <kamesh.jayachandran@gmail.com>
+ markphip Mark Phippard <mphippard@collab.net>
+ arfrever Arfrever Frehtes Taifersar Arahesis <arfrever.fta@gmail.com>
+ stsp Stefan Sperling <stsp@elego.de>
+ kou Kouhei Sutou <kou@cozmixng.org>
+ danielsh Daniel Shahaf <d.s@daniel.shahaf.name>
+ peters Peter Samuelson <peter@p12n.org>
+ rhuijben Bert Huijben <rhuijben@collab.net>
+ stylesen Senthil Kumaran S <stylesen@gmail.com>
+ steveking Stefan Küng <tortoisesvn@gmail.com>
+ neels Neels J. Hofmeyr <neels@elego.de>
+ jwhitlock Jeremy Whitlock <jcscoobyrs@gmail.com>
+ sbutler Stephen Butler <sbutler@elego.de>
+ dannas Daniel Näslund <dannas@dannas.name>
+ stefan2 Stefan Fuhrmann <stefan.fuhrmann@wandisco.com>
+ jcorvel Johan Corveleyn <jcorvel@gmail.com>
+ trent Trent Nelson <trent@snakebite.org>
+
+[[END ACTIVE FULL COMMITTERS. LEAVE THIS LINE HERE; SCRIPTS LOOK FOR IT.]]
+
+Full committers who have asked to be listed as dormant:
+
+ bdenny Brian Estlin <brian@implementality.com>
+ epg Eric Gillespie <epg@pretzelnet.org>
+ kraai Matt Kraai <kraai@alumni.cmu.edu>
+ bcollins Ben Collins <bcollins@debian.org>
+ djh D.J. Heap <djheap@gmail.com>
+ dwhedon David Kimdon <dwhedon@debian.org>
+ jpieper Josh Pieper <jjp@pobox.com>
+ kevin Kevin Pilch-Bisson <kevin@pilch-bisson.net>
+ lundblad Peter N. Lundblad <peter@famlundblad.se>
+ malcolm Malcolm Rowe <malcolm-svn-dev@farside.org.uk>
+ naked Nuutti Kotivuori <naked@iki.fi>
+ ringstrom Tobias Ringström <tobias@ringstrom.mine.nu>
+
+
+Partial committers who have asked to be listed as dormant:
+
+ kon Kalle Olavi Niemitalo <kon@iki.fi> (psvn.el)
+ rassilon Bill Tutt <bill@tutts.org> (Win32, COM, issue-1003-dev br.)
+ pll Paul lussier <p.lussier@comcast.net> (releases)
+ rdonch Роман Донченко <dpb@corrigendum.ru> (Swig-Python b.)
+
+
+Commit access for specific areas:
+
+ Bindings:
+
+ pmayweg Patrick Mayweg <mayweg@qint.de> (JavaHL bindings)
+ rey4 Russell Yanofsky <rey4@columbia.edu> (Swig bindings)
+ clkao Chia-liang Kao <clkao@clkao.org> (Swig-Perl b.)
+ joeswatosh Joe Swatosh <joe.swatosh@gmail.com> (Swig-Ruby b.)
+ jrvernooij Jelmer Vernooij <jelmer@samba.org> (Python bindings)
+ sage Sage LaTorra <sagelt@gmail.com> (Ctypes-Python b.)
+ vmpn Vladimir Berezniker <vmpn@hitechman.com> (JavaHL bindings)
+
+ Packages:
+
+ dws David Summers <david@summersoft.fay.ar.us> (RPMs)
+ ebswift Troy Simpson <troy@ebswift.com> (windows-innosetup)
+
+ Miscellaneous:
+
+ kbohling Kirby C. Bohling <kbohling@birddog.com> (tools/dev) [EMAIL
+ IS BOUNCING]
+ nsd Nick Duffek <nick@duffek.com> (doc)
+ xsteve Stefan Reichör <stefan@xsteve.at> (psvn.el)
+ josander Jostein Andersen <jostein@vait.se> (various)
+ niemeyer Gustavo Niemeyer <niemeyer@conectiva.com> (svnperms.py)
+ [EMAIL IS BOUNCING]
+ zbrown Zack Brown <zbrown@tumblerings.org> (doc) [EMAIL IS
+ BOUNCING]
+ mprice Michael Price <ectospheno@gmail.com> (releases)
+ jrepenning Jack Repenning <jrepenning@collab.net> (tools/dev)
+ jlonestar Martin Maurer <martin.maurer@email.de> (svnshow) [EMAIL
+ IS BOUNCING]
+ shlomif Shlomi Fish <shlomif@shlomifish.org> (svn-push)
+ mthelen Michael W Thelen <mike@pietdepsi.com> (doc)
+ jeremybettis Jeremy Bettis <jeremy@deadbeef.com> (case-insensitive)
+ martinto Martin Tomes <lists@tomes.org> (case-insensitive)
+ danpat Daniel Patterson <danpat@danpat.net> (svn-graph.pl)
+ archiecobbs Archie Cobbs <archie@awarix.com> (svnmerge) [EMAIL
+ IS BOUNCING]
+ giovannibajo Giovanni Bajo <rasky@develer.com> (svnmerge)
+ offby1 Eric Hanchrow <offby1@blarg.net> (doc)
+ nomis80 Simon Perreault <nomis80@nomis80.org> (svn-clean)
+ jlvarner Joshua Varner <jlvarner@gmail.com> (doc)
+ nori Kobayashi Noritada <nori1@dolphin.c.u-tokyo.ac.jp> (Ruby tools,
+ po: ja) [EMAIL IS
+ BOUNCING]
+ mf Martin Furter <mf@apache.org> (svnmirror.sh
+ svn-backup-dumps.py)
+ adejong Arthur de Jong <arthur@ch.tudelft.nl> (svn2cl)
+ wsanchez Wilfredo Sánchez <wsanchez@wsanchez.net> (various contrib)
+ mhagger Michael Haggerty <mhagger@alum.mit.edu> (svntest)
+ madanus Madan U S <madan@collab.net> (svnmerge) [EMAIL
+ IS BOUNCING]
+ wein Mathias Weinert <wein@mccw.de> (mailer)
+ bhuvan Bhuvaneswaran A <bhuvan@apache.org> (svn2feed.py,
+ build/hudson)
+ aogier Anthony Ogier <aogier@iorga.com> (svn-merge-vendor.py)
+ dkagedal David Kågedal <davidk@lysator.liu.se> (dsvn.el)
+ mattiase Mattias Engdegård <mattiase@acm.org> (dsvn.el)
+ dustin Dustin J. Mitchell <dustin@zmanda.com> (svnmerge)
+ rocketraman Raman Gupta <rocketraman@fastmail.fm> (svnmerge)
+ rhansen Richard Hansen <rhansen@bbn.com> (svnstsw)
+ larrys Larry Shatzer, Jr. <larrys@gmail.com> (svn-keyword-check.pl)
+ nmiyo MIYOKAWA, Nobuyoshi <n-miyo@tempus.org> (www: ja)
+ rocksun Rock Sun <daijun@gmail.com> (www: zh)
+ kmradke Kevin Radke <kmradke@gmail.com> (add-needs-lock.py)
+ esr Eric S. Raymond <esr@thyrsus.com> (svncutter)
+ gmcdonald Gavin McDonald <gavin@16degrees.com.au> (build/hudson,
+ tools/buildbot)
+ artagnon Ramkumar Ramachandra <artagnon@gmail.com> (svnrdump, svntest)
+ arwin Arwin Arni <arwin@collab.net> (svn-bisect)
+ joes Joe Schaefer <joe_schaefer@yahoo.com> (svnpubsub)
+ prabhugs Prabhu Gnana Sundar <prabhugs@collab.net> (verify-keep-going)
+
+
+ Translation of message files:
+
+ niqueco Nicolás Lichtmaier <nick@reloco.com.ar> (po: es)
+ luebbe Lübbe Onken <luebbe@tigris.org> (po: de)
+ jensseidel Jens Seidel <jensseidel@users.sf.net> (po: de)
+ astieger Andreas Stieger <andreas.stieger@gmx.de> (po: de)
+ oyvindmo Øyvind Møll <svn@moll.no> (po: nb)
+ sunny256 Øyvind A. Holm <sunny@sunbase.org> (po: nb)
+ jzgoda Jaroslaw Zgoda <jzgoda@o2.pl> (po: pl)
+ karolszk Karol Szkudlarek <karol@mikronika.com.pl> (po: pl)
+ plasma Wei-Hon Chen <plasma@ms9.hinet.net> (po: zh_TW)
+ jihuang June-Yen Huang <jihuang@iis.sinica.edu.tw> (po: zh_TW) [EMAIL
+ IS BOUNCING]
+ marcosc Marcos Chaves <marcos.nospam@gmail.com> (po: pt_BR)
+ pynoos Hojin Choi <hojin.choi@gmail.com> (po: ko)
+ blueboh Jeong Seolin <blueboh@gmail.com> (po: ko)
+ dongsheng Dongsheng Song <songdongsheng@live.cn> (po: zh_CN)
+ hynnet YingNing Huang <hyn@bao.hynnet.com> (po: zh_CN) [EMAIL
+ IS BOUNCING]
+ lark Wang Jian <lark@linux.net.cn> (po: zh_CN) [EMAIL
+ IS BOUNCING]
+giorgio_valoti Giorgio Valoti <giorgio_v@mac.com> (po: it)
+ nebiac Federico Nebiacolombo <cint1@amsjv.it> (po: it) [EMAIL
+ IS BOUNCING]
+ fabien Fabien Coelho <fabien@coelho.net> (po: fr)
+ marcelg Marcel Gosselin <marcel.gosselin@polymtl.ca> (po: fr)
+
+ Experimental branches:
+
+ ashod Ashod Nakashian <ashod@apache.org> (compressed-
+ pristines br.)
+ gthompson Glenn A. Thompson <gthompson@cdr.net> (pluggable-db br.)
+ sigfred Sigfred Håversen <bsdlist@mumak.com> (svnserve-ssl br.)
+ [EMAIL IS BOUNCING]
+ pmarek Ph. Marek <philipp@marek.priv.at> (meta-data-v br.)
+ jpeacock John Peacock <jpeacock@rowman.com> (perl-bindings-
+ improvements br.)
+ nikclayton Nik Clayton <nik@ngo.org.uk> (perl-bindings-
+ improvements br.)
+ cacknin Charles Acknin <charlesacknin@gmail.com> (svnpatch-diff
+ br.)
+ holden Holden Karau <holden@pigscanfly.ca> (scheme-bindings br.)
+ moklo Morten Kloster <morklo@gmail.com> (diff-improvements br.)
+ vmpn Vladimir Berezniker <vmpn@hitechman.com> (javahl-ra br.)
+
+ Subprojects that are complete, abandoned or have moved elsewhere:
+
+ xela Alexander Müller <alex@littleblue.de> (Java JNI b.)
+ yoshiki Yoshiki Hayashi <yoshiki@xemacs.org> (Non-SWIG Ruby b.)
+ mmacek Marko Maček <Marko.Macek@gmx.net> (cvs2svn branch)
+ mass David Waite <mass@akuma.org> (certs branch)
+ sergeyli Sergey A. Lipnevich <sergey@optimaltec.com> (neon-0.24 port)
+ ballbach Michael Ballbach <ballbach@rten.net> (Old Mandrake RPM)
+ morten Morten Ludvigsen <morten@2ps.dk> (Swig-Java b.)
+ jespersm Jesper Steen Møller <jesper@selskabet.org> (Swig-Java b.)
+ knacke Kai Nacke <kai.nacke@redstar.de> (Swig-Java b.)
+ fmatias Féliciano Matias <feliciano.matias@free.fr> (doc: fr)
+ dimentiy Dmitriy O. Popkov <dimentiy@dimentiy.info> (doc: ru)
+ khmarbaise Karl Heinz Marbaise <khmarbaise@gmx.de> (doc: de)
+ gerhardoettl Gerhard Oettl <gerhard.oettl.ml@oesoft.at> (doc: de)
+ beerfrick Ariel Arjona <beerfrick@gmail.com> (doc: es)
+ gradha Grzegorz A. Hankiewicz <gradha@titanium.sabren.com> (doc: es)
+ ruben Rubén Gómez <rugoli@euskalnet.net> (doc: es)
+ dbrouard Diego Brouard <dbrouard@gmail.com> (doc: es)
+ firemeteor Guo Rui <timmyguo@mail.ustc.edu.cn> (issue-2843-dev
+ br.)
+
+
+## Local Variables:
+## coding:utf-8
+## End:
+## vim:fileencoding=utf8
diff --git a/contrib/subversion/INSTALL b/contrib/subversion/INSTALL
new file mode 100644
index 0000000..55ca344
--- /dev/null
+++ b/contrib/subversion/INSTALL
@@ -0,0 +1,1466 @@
+ ======================================
+ INSTALLING SUBVERSION
+ A Quick Guide
+ ======================================
+
+$LastChangedDate: 2013-06-05 04:00:25 +0000 (Wed, 05 Jun 2013) $
+
+
+Contents:
+
+ I. INTRODUCTION
+ A. Audience
+ B. Dependency Overview
+ C. Dependencies in Detail
+ D. Documentation
+
+ II. INSTALLATION
+ A. Building from a Tarball or RPM
+ B. Building the Latest Source under Unix
+ C. Building under Unix in Different Directories
+ D. Installing from a Zip or Installer File under Windows
+ E. Building the Latest Source under Windows
+
+ III. BUILDING A SUBVERSION SERVER
+ A. Setting Up Apache
+ B. Making and Installing the Subversion Server
+ C. Configuring Apache for Subversion
+ D. Running and Testing
+ E. Alternative: 'svnserve' and ra_svn
+
+ IV. PLATFORM-SPECIFIC ISSUES
+ A. Windows XP
+ B. Mac OS X
+
+ V. PROGRAMMING LANGUAGE BINDINGS (PYTHON, PERL, RUBY, JAVA)
+
+
+
+I. INTRODUCTION
+ ============
+
+ A. Audience
+
+ This document is written for people who intend to build
+ Subversion from source code. Normally, the only people who do
+ this are Subversion developers and package maintainers.
+
+ If neither of these labels fits you, we recommend you find an
+ appropriate binary package of Subversion and install that.
+ While the Subversion project doesn't officially release binary
+ packages, a number of volunteers have made such packages
+ available for different operating systems. Most Linux and BSD
+ distributions already have Subversion packages ready to go via
+ standard packaging channels, and other volunteers have built
+ 'installers' for both Windows and OS X. Visit this page for
+ package links:
+
+ http://subversion.apache.org/packages.html
+
+ For those of you who still wish to build from source, Subversion
+ follows the Unix convention of "./configure && make", but it has
+ a number of dependencies.
+
+
+ B. Dependency Overview
+
+ You'll need the following build tools to compile Subversion:
+
+ * autoconf 2.59 or later (Unix only)
+ * libtool 1.4 or later (Unix only)
+ * a reasonable C compiler (gcc, Visual Studio, etc.)
+
+
+ Subversion also depends on the following third-party libraries:
+
+ * libapr and libapr-util (REQUIRED for client and server)
+
+ The Apache Portable Runtime (APR) library provides an
+ abstraction of operating-system level services such as file
+ and network I/O, memory management, and so on. It also
+ provides convenience routines for things like hashtables,
+ checksums, and argument processing. While it was originally
+ developed for the Apache HTTP server, APR is a standalone
+ library used by Subversion and other products. It is a
+ critical dependency for all of Subversion; it's the layer
+ that allows Subversion clients and servers to run on
+ different operating systems.
+
+ * SQLite (REQUIRED for client and server)
+
+ Subversion uses SQLite to manage some internal databases.
+
+ * libz (REQUIRED for client and server)
+
+ Subversion uses zlib for compressing binary differences.
+ These diff streams are used everywhere -- over the network,
+ in the repository, and in the client's working copy.
+
+ * libserf (OPTIONAL for client)
+
+ The Serf library allows the Subversion client to send HTTP
+ requests. This is necessary if you want your client to access
+ a repository served by the Apache HTTP server. There is an
+ alternate 'svnserve' server as well, though, and clients
+ automatically know how to speak the svnserve protocol.
+ Thus it's not strictly necessary for your client to be able
+ to speak HTTP... though we still recommend that your client
+ be built to speak both HTTP and svnserve protocols.
+
+ * OpenSSL (OPTIONAL for client and server)
+
+ OpenSSL enables your client to access SSL-encrypted https://
+ URLs (using libserf) in addition to unencrypted http:// URLs.
+ To use SSL with Subversion's WebDAV server, Apache needs to be
+ compiled with OpenSSL as well.
+
+ * Berkeley DB (OPTIONAL for client and server)
+
+ There are two different repository 'back-end'
+ implementations. One implementation stores data in a flat
+ filesystem (known as FSFS); the other implementation stores
+ data in a Berkeley DB database (known as BDB). When you
+ create a repository, you have the option of specifying a
+ storage back-end. The Berkeley DB back-end will only be
+ available if the BDB libraries are discovered at compile
+ time.
+
+ * libsasl (OPTIONAL for client and server)
+
+ If the Cyrus SASL library is detected at compile time, then
+ the svn client (and svnserve server) will be able to utilize
+ SASL to do various forms of authentication when speaking the
+ svnserve protocol.
+
+ * Python, Perl, Java, Ruby (OPTIONAL)
+
+ Subversion is mostly a collection of C libraries with
+ well-defined APIs, with a small collection of programs that
+ use the APIs. If you want to build Subversion API bindings
+ for other languages, you need to have those languages
+ available at build time.
+
+ * KDELibs, GNOME Keyring (OPTIONAL for client)
+
+ Subversion contains optional support for storing passwords in
+ KWallet (KDE 4) or GNOME Keyring.
+
+ * libmagic
+
+ If the libmagic library is detected at compile time,
+ it will be used to determine mime-types of binary files
+ which are added to version control. Note that mime-types
+ configured via auto-props or the mime-types-file option
+ take precedence.
+
+ C. Dependencies in Detail
+
+ Subversion depends on a number of third party tools and libraries.
+ Some of them are only required to run a Subversion server; others
+ are necessary just for a Subversion client. This section explains
+ what other tools and libraries will be required so that Subversion
+ can be built with the set of features you want.
+
+ On Unix systems, the './configure' script will tell you if you are
+ missing the correct version of any of the required libraries or
+ tools, so if you are in a real hurry to get building, you can skip
+ straight to section II. If you want to gather the pieces you will
+ need before starting out, however, you should read the following.
+
+ If you're just installing a Subversion client, the Subversion
+ team has created a script that downloads the minimal prerequisite
+ libraries (Apache Portable Runtime, Sqlite, and Zlib). The script,
+ 'get-deps.sh', is available in the same directory as this file.
+ When run, it will place 'apr', 'apr-util', 'serf', 'zlib', and
+ 'sqlite-amalgamation' directories directly into your unpacked Subversion
+ distribution. With the exception of sqlite-amalgamation, they will
+ still need to be configured, built and installed explicitly, and
+ Subversion's own configure script may need to be told where to find
+ them, if they were not installed in standard system locations.
+
+ Note: there are optional dependencies (such as openssl, swig, and httpd)
+ which get-deps.sh does not download.
+
+ Note: Because previous builds of Subversion may have installed older
+ versions of these libraries, you may want to run some of the cleanup
+ commands described in section II.B before installing the following.
+
+
+ 1. Apache Portable Runtime 0.9.7 or 1.X.X (REQUIRED)
+
+ Whenever you want to build any part of Subversion, you need the
+ Apache Portable Runtime (APR) and the APR Utility (APR-util)
+ libraries.
+
+
+ ****************************************************************
+ ** IMPORTANT ISSUE ABOUT APR VERSIONS: READ THIS. **
+ ** **
+ ****************************************************************
+ | |
+ | APR 0.9.X and 1.X are binary-incompatible. |
+ | |
+ | This means: |
+ | |
+ | - if you are already using Subversion with APR 0.9.X, and |
+ | then upgrade your libapr to 1.X without rebuilding |
+ | Subversion, things will break and segfault. |
+ | |
+ | - if your Subversion server libraries are linked to one |
+ | version of APR, but your Apache server is linked to a |
+ | different version, things will break and segfault. |
+ | |
+ | Subversion distribution dependencies: |
+ | ------------------------------------- |
+ | |
+ | For a long time, Subversion's main distribution contained |
+ | APR and APR-UTIL (both 0.9.x), plus a few other things that |
+ | we couldn't count on the installation system having. But |
+ | nowadays, Subversion's requirements are no longer exotic, |
+ | and so our main distribution contains just the Subversion |
+ | source code itself -- people compiling Subversion are |
+ | expected to either have the APR libraries already installed |
+ | on their system, or to be capable of fetching them easily. |
+ | |
+ | Note that it's *perfectly* safe to use APR 1.X from the |
+ | beginning. In fact, we recommend it. If you're building |
+ | Subversion for the first time, there's no compatibility |
+ | issue to worry about, so grab the latest version of APR. |
+ | |
+ | If you already have a Subversion installation using APR |
+ | 0.9.x, it's still possible to move to APR 1.X safely. Just |
+ | be sure to recompile Subversion (and Apache httpd if |
+ | necessary) after upgrading APR! |
+ |______________________________________________________________|
+
+
+ If you do not have a pre-installed APR and APR-util, you will need
+ to get these yourself:
+
+ http://apr.apache.org/download.cgi
+
+ On Unix systems, if you already have the APR libraries compiled and do
+ not wish to regenerate them from source code, then Subversion needs to
+ be able to find them.
+
+ There are a couple of options to "./configure" that tell it where
+ to look for the APR and APR-util libraries. By default it will try
+ to locate the libraries using apr-config and apu-config scripts.
+ These scripts provide all the relevant information for the APR and
+ APR-util installations.
+
+ If you want to specify the location of the APR library, you can use
+ the "--with-apr=" option of "./configure". It should be able to find
+ the apr-config script in the standard location under that directory
+ (e.g. ${prefix}/bin).
+
+ Similarly, you can specify the location of APR-util using the
+ "--with-apr-util=" option to "./configure". It will look for the
+ apu-config script relative to that directory.
+
+ For example, if you want to use the APR libraries you built
+ with the Apache httpd server, you could run:
+
+ $ ./configure --with-apr=/usr/local/apache2 \
+ --with-apr-util=/usr/local/apache2 ...
+
+ Be sure to use a native Windows SVN client (as opposed to
+ Cygwin's version) so that the .dsp files get carriage-returns at
+ the ends of their lines. Otherwise Visual Studio will complain
+ that it doesn't recognize the .dsp files.
+
+ If you use APR libraries checked out from svn in an Unix
+ environment, you need to run the 'buildconf' script in each
+ library's directory, to regenerate the configure scripts and
+ other files required for compiling the libraries:
+
+ $ cd apr; ./buildconf; ./configure ...; make; make install; cd ..
+
+ $ cd apr-util; ./buildconf; ./configure ...; make; make install; cd ..
+
+ Configure build and install both libraries before running Subversion's
+ configure script.
+
+
+ 2. Zlib (REQUIRED)
+
+ Subversion's binary-differencing engine depends on zlib for
+ compression. Most Unix systems have libz pre-installed, but
+ if you need it, you can get it from
+
+ http://www.zlib.net
+
+
+ 3. autoconf 2.59 or newer (Unix only)
+
+ This is required only if you plan to build from the latest source
+ (see section II.B). Generally only developers would be doing this.
+
+
+ 4. libtool 1.4 or newer (Unix only)
+
+ This is required only if you plan to build from the latest source
+ (see section II.B).
+
+ Note: Some systems (Solaris, for example) require libtool 1.4.3 or
+ newer. The autogen.sh script knows about that.
+
+
+ 5. Serf library 1.2.1 or newer (OPTIONAL)
+
+ If you want your client to be able to speak to an Apache
+ server (via a http:// or https:// URL), you must link against
+ serf. Though optional, we strongly recommend this.
+
+ In order to use ra_serf, you must install serf, and run Subversion's
+ ./configure with the argument --with-serf. If serf is installed in a
+ non-standard place, you should use
+
+ --with-serf=/path/to/serf/install
+
+ instead.
+
+ Serf can be obtained via your system's package distribution
+ system or directly from http://code.google.com/p/serf/.
+
+ For more information on serf and Subversion's ra_serf, see the file
+ subversion/libsvn_ra_serf/README.
+
+ 6. OpenSSL (OPTIONAL)
+
+ ### needs some updates. I think serf automagically handles
+ ### finding OpenSSL, but we may need more docco here. and w.r.t
+ ### zlib.
+
+ The Serf library has support for SSL encryption by relying on the
+ OpenSSL library.
+
+ a. Using OpenSSL on the client through Serf
+
+ On Unix systems, to build Serf with OpenSSL, you need OpenSSL
+ installed on your system, and you must add "--with-ssl" as a
+ "./configure" parameter. If your OpenSSL installation is hard
+ for Serf to find, you may need to use "--with-libs=/path/to/lib"
+ in addition. In particular, on Red Hat (but not Fedora Core) it
+ is necessary to specify "--with-libs=/usr/kerberos" for OpenSSL
+ to be found. You can also specify a path to the zlib library
+ using "--with-libs".
+
+ Under Windows, you can specify the paths to these libraries by
+ passing the options --with-zlib and --with-openssl to gen-make.py.
+
+ ### Is that right? In-tree build of Neon was disabled in r875974.
+ This may now apply to Serf, or else gen-make.py should be
+ updated to remove such options.
+
+ c. Using OpenSSL on the Apache server
+
+ You can also add support for these features to an Apache httpd
+ server to be used for Subversion using the same support libraries.
+ The Subversion build system will not provide them, however. You
+ add them by specifying parameters to the "./configure" script of
+ the Apache Server instead.
+
+ For getting SSL on your server, you would add the "--enable-ssl"
+ or "--with-ssl=/path/to/lib" option to Apache's "./configure"
+ script. Apache enables zlib support by default, but you can
+ specify a nonstandard location for the library with the
+ "--with-z=/path/to/dir" option. Consult the Apache documentation
+ for more details, and for other modules you may wish to install
+ to enhance your Subversion server.
+
+ If you don't already have it, you can get a copy of OpenSSL,
+ including instructions for building and packaging on both Unix
+ systems and Windows, at:
+
+ http://www.openssl.org/
+
+
+ 7. Berkeley DB 4.X (OPTIONAL)
+
+ Berkeley DB is needed to build a Subversion server that supports
+ the BDB repository filesystem, or to access a BDB repository on
+ local disk. If you will only use the FSFS repository filesystem,
+ or if you are building a Subversion client that will only speak
+ to remote (networked) repositories, you don't need it.
+
+ The current recommended version is 4.4.20 or newer, which brings
+ auto-recovery functionality to the Berkeley DB database
+ environment.
+
+ If you must use an older version of Berkeley DB, we *strongly*
+ recommend using 4.3 or 4.2 over the 4.1 or 4.0 versions. Not
+ only are these significantly faster and more stable, but they
+ also enable Subversion repositories to automatically clean up
+ database journal files to save disk space.
+
+ You'll need Berkeley DB installed on your system. You can
+ get it from:
+
+ http://www.oracle.com/technology/software/products/berkeley-db/index.html
+
+ If you have Berkeley DB installed in a place not searched by default
+ for includes and libraries, add something like this:
+
+ --with-berkeley-db=db.h:/usr/local/include/db4.7:/usr/local/lib/db4.7:db-4.7
+
+ to your `configure' switches, and the build process will use the
+ Berkeley DB header and library in the named directories. You may
+ need to use a different path, of course. Note that in order for
+ the detection to succeed, the dynamic linker must be able to find
+ the libraries at configure time.
+
+ If you are on the Windows platform and want to build Subversion,
+ a precompiled version of the Berkeley DB library is available for
+ download at the Subversion web site "Documents & files" area:
+
+ http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=688
+
+ Look in the "Releases > Windows > Windows BDB" section.
+
+
+ 8. Cyrus SASL library (OPTIONAL)
+
+ If the Simple Authentication and Security Layer (SASL) library
+ is detected on your system, then the Subversion client and
+ svnserve server can utilize its abilities for various forms of
+ authentication. To learn more about SASL or to get the source
+ code, visit:
+
+ http://freshmeat.net/projects/cyrussasl/
+
+
+ 9. Apache Web Server 2.X (OPTIONAL)
+
+ (http://httpd.apache.org/download.cgi)
+
+ The Apache httpd server is one of two methods to make your Subversion
+ repository available over a network - the other is a custom server
+ program called svnserve, which requires no extra software packages.
+ Building Subversion, the Apache server, and the modules that Apache
+ needs to communicate with Subversion are complicated enough that there
+ is a whole section at the end of this document that describes how it
+ is done: See section III for details.
+
+
+ 10. Python 2.5 or newer (http://www.python.org/) (OPTIONAL)
+
+ If you want to run "make check" or build from the latest source
+ under Unix as described in section II.B and III.D, install
+ Python 2.5 or higher on your system. The majority of the test
+ suite is written in Python, as is part of Subversion's build
+ system.
+
+
+ 11. Perl 5.8 or newer (Windows only) (OPTIONAL)
+
+ To build Subversion under any of the MS Windows platforms, you
+ will also need Perl 5.8 or newer to run apr-util's w32locatedb.pl
+ script.
+
+
+ 12. MASM 6 or newer (Windows only, OPTIONAL)
+
+ The Windows build scripts for Subversion can use the Microsoft
+ Macro Assembler (MASM) to build an optimized version of the ZLib
+ library. Make sure that the version of MASM you use is compatible
+ with the C compiler. If you're using MSVC 6, and don't have MASM 6,
+ a free MASM-compatible assembler is available here:
+
+ http://www.masm32.com/
+
+ You only need ML.EXE and ML.ERR from this distribution.
+
+ The VS.NET installation already contains MASM (but note, that
+ version if MASM is not compatible with MSVC 6).
+
+
+ 13. SQLite (REQUIRED)
+
+ Subversion 1.7 requires SQLite version 3.6.18 or above. You can meet
+ this dependency several ways:
+ * Use an SQLite amalgamation file.
+ * Specify an SQLite installation to use.
+ * Let Subversion find an installed SQLite.
+
+ To use an SQLite-provided amalgamation, just drop sqlite3.c into
+ Subversion's sqlite-amalgamation/ directory, or point to it with the
+ --with-sqlite configure option. This file also ships with the Subversion
+ dependencies distribution, or you can download it from SQLite:
+
+ http://www.sqlite.org/download.html
+
+
+ 14. pkg-config (Unix only, OPTIONAL)
+
+ Subversion uses pkg-config to find appropriate options used
+ at build time.
+
+
+ 15. D-Bus (Unix only, OPTIONAL)
+
+ D-Bus is a message bus system. D-Bus is required for support for KWallet
+ and GNOME Keyring. pkg-config is needed to find D-Bus headers and library.
+
+
+ 16. Qt 4 (Unix only, OPTIONAL)
+
+ Qt is a cross-platform application framework. QtCore, QtDBus and QtGui
+ modules are required for support for KWallet. pkg-config is needed
+ to find Qt headers and libraries.
+
+
+ 17. KDELibs 4 (Unix only, OPTIONAL)
+
+ Subversion contains optional support for storing passwords in KWallet.
+ KDELibs contains core KDE libraries. Subversion uses libkdecore and libkdeui
+ libraries when support for KWallet is enabled. kde4-config is used to get
+ some necessary options. pkg-config, D-Bus and Qt 4 are also required.
+ If you want to build support for KWallet, then pass the '--with-kwallet'
+ option to `configure`. If KDE is installed in a non-standard prefix, then
+ use:
+
+ --with-kwallet=/path/to/KDE/prefix
+
+ 18. GLib 2 (Unix only, OPTIONAL)
+
+ GLib is a general-purpose utility library. GLib is required for support
+ for GNOME Keyring. pkg-config is needed to find GLib headers and library.
+
+
+ 19. GNOME Keyring (Unix only, OPTIONAL)
+
+ Subversion contains optional support for storing passwords in GNOME Keyring.
+ pkg-config is needed to find GNOME Keyring headers and library. D-Bus and
+ GLib are also required. If you want to build support for GNOME Keyring,
+ then pass the '--with-gnome-keyring' option to `configure`.
+
+
+ 20. Ctypesgen (OPTIONAL)
+
+ Ctypesgen is Python wrapper generator for ctypes. It is used to generate
+ a part of Subversion Ctypes Python bindings (CSVN). If you want to build
+ CSVN, then pass the '--with-ctypesgen' option to `configure`. If ctypesgen.py
+ is installed in a non-standard place, then use:
+
+ --with-ctypesgen=/path/to/ctypesgen.py
+
+ For more information on CSVN, see subversion/bindings/ctypes-python/README.
+
+ 21. libmagic (OPTIONAL)
+
+ Subversion's configure script attempts to find libmagic automatically.
+ If it is installed in a non-standard location, then use:
+
+ --with-libmagic=/path/to/libmagic/prefix
+
+ The files include/magic.h and lib/libmagic.so.1.0 (or similar)
+ are expected beneath this prefix directory. If they cannot be
+ found Subversion will be compiled without support for libmagic.
+
+ If libmagic is installed but support for it should not be compiled
+ in, then use:
+
+ --with-libmagic=no
+
+ If configure should fail when libmagic is not present, but only
+ the default locations should be searched, then use:
+
+ --with-libmagic
+
+ D. Documentation
+
+ The primary documentation for Subversion is the free book
+ "Version Control with Subversion", a.k.a. "The Subversion Book",
+ obtainable from http://svnbook.red-bean.com/.
+
+ Various additional documentation exists in the doc/ subdirectory of
+ the Subversion source. See the file doc/README for more information.
+
+
+
+II. INSTALLATION
+ ============
+
+ A. Building from a Tarball or RPM
+ ------------------------------
+
+ 1. Building from a Tarball
+
+ Download the most recent distribution tarball from:
+
+ http://subversion.apache.org/download/
+
+ Unpack it, and use the standard GNU procedure to compile:
+
+ $ ./configure
+ $ make
+ # make install
+
+ You can also run the full test suite by running 'make check'.
+
+
+ 2. Building from an RPM
+
+ If you are using Linux (or any OS that can use RPM) then another
+ possibility is to download the binary RPM from the
+ http://summersoft.fay.ar.us/pub/subversion directory.
+
+ Currently only Linux on the i386 platform is supported
+ using this method. You might also require additional RPMS
+ (which can be found in the above mentioned directory) to use the
+ subversion RPM depending on what packages you already have installed:
+
+ subversion*.i386.rpm
+ apache*.i386.rpm (Version 2.0.49 or greater)
+ db*.i386.rpm (Version 4.0.14 or greater; version 4.3.27 or
+ 4.2.52 is preferred however)
+ expat (Comes with RedHat)
+
+ After downloading, install it (as root user):
+
+ # rpm -ivh subversion*.386.rpm (add other packages as necessary)
+
+ Note: For an easy way to generate a new version of the RPM
+ source and binary package from the latest source code you
+ just checked out, see the packages/rpm/README file for a
+ one-line build procedure.
+
+
+ B. Building the Latest Source under Unix
+ -------------------------------------
+
+ These instructions assume you have already installed Subversion
+ and checked out a working copy of Subversion's own code --
+ either the latest /trunk code, or some branch or tag. You also
+ need to have already installed whatever prerequisites that
+ version of Subversion requires (if you haven't, the ./configure
+ step should complain).
+
+ You can discard the directory created by the tarball; you're
+ about to build the latest, greatest Subversion client. This is
+ the procedure Subversion developers use.
+
+ First off, if you have any Subversion libraries lying around
+ from previous 'make installs', clean them up first!
+
+ # rm -f /usr/local/lib/libsvn*
+ # rm -f /usr/local/lib/libapr*
+ # rm -f /usr/local/lib/libexpat*
+ # rm -f /usr/local/lib/libserf*
+
+ Start the process by running "autogen.sh":
+
+ $ sh ./autogen.sh
+
+ This script will make sure you have all the necessary components
+ available to build Subversion. If any are missing, you will be
+ told where to get them from. (See the 'Build Requirements' in
+ section I.)
+
+ Note: if the command "autoconf" on your machine does not run
+ autoconf 2.59 or later, but you do have a new enough autoconf
+ available, then you can specify the correct one with the
+ AUTOCONF variable. (The AUTOHEADER variable is similar.) This
+ may be required on Debian GNU/Linux, where "autoconf" is
+ actually a Perl script that attempts to guess which version is
+ required -- because of the interaction between Subversion's and
+ APR's configuration systems, the Perl script may get it wrong.
+ So for example, you might need to do:
+
+ $ AUTOCONF=autoconf2.59 sh ./autogen.sh
+
+ Once you've prepared the working copy by running autogen.sh,
+ just follow the usual configuration and build procedure:
+
+ $ ./configure
+ $ make
+ # make install
+
+ (Optionally, you might want to pass --enable-maintainer-mode to
+ the ./configure script. This enables debugging symbols in your
+ binaries (among other things) and most Subversion developers use it.)
+
+ Since the resulting binary depends on shared libraries, the
+ destination library directory must be identified in your
+ operating system's library search path. That is in either
+ /etc/ld.so.conf or $LD_LIBRARY_PATH for Linux systems and in
+ /etc/rc.conf for FreeBSD, followed by a run of the 'ldconfig'
+ program. Check your system documentation for details. By
+ identifying the destination directory, Subversion will be able
+ to dynamically load repository access plugins. If you try to do
+ a checkout and see an error like:
+
+ subversion/libsvn_ra/ra_loader.c:209: (apr_err=170000)
+ svn: Unrecognized URL scheme 'https://svn.apache.org/repos/asf/subversion/trunk'
+
+ It probably means that the dynamic loader/linker can't find all
+ of the libsvn_* libraries.
+
+
+ C. Building under Unix in Different Directories
+ --------------------------------------------
+
+ It is possible to configure and build Subversion on Unix in a
+ directory other than the working copy. For example
+
+ $ svn co https://svn.apache.org/repos/asf/subversion/trunk svn
+ $ cd svn
+ $ # get SQLite amalgamation if required
+ $ chmod +x autogen.sh
+ $ ./autogen.sh
+ $ mkdir ../obj
+ $ cd ../obj
+ $ ../svn/configure [...with options as appropriate...]
+ $ make
+
+ puts the Subversion working copy in the directory svn and builds
+ it in a separate, parallel directory obj.
+
+ Why would you want to do this? Well there are a number of
+ reasons...
+
+ * You may prefer to avoid "polluting" the working copy with
+ files generated during the build.
+
+ * You may want to put the build directory and the working
+ copy on different physical disks to improve performance.
+
+ * You may want to separate source and object code and only
+ backup the source.
+
+ * You may want to remote mount the working copy on multiple
+ machines, and build for different machines from the same
+ working copy.
+
+ * You may want to build multiple configurations from the
+ same working copy.
+
+ The last reason above is possibly the most useful. For instance
+ you can have separate debug and optimized builds each using the
+ same working copy. Or you may want a client-only build and a
+ client-server build. Using multiple build directories you can
+ rebuild any or all configurations after an edit without the need
+ to either clean and reconfigure, or identify and copy changes
+ into another working copy.
+
+
+ D. Installing from a Zip or Installer File under Windows
+ --------------------------------------------------------
+
+ Of all the ways of getting a Subversion client, this is the
+ easiest. Download a Zip (*.zip) or self-extracting installer
+ (*-setup.exe) file from:
+
+ http://subversion.apache.org/packages#windows
+
+ For a Zip file, run your unzipping utility (WinZIP, ZipGenius,
+ UltimateZIP, FreeZIP, whatever) and extract the DLLs and EXEs to
+ a directory of your choice. Included in the download is the SVN
+ client, the SVNADMIN administration tool, and the SVNLOOK
+ reporting tool.
+
+ Note that if you need support for non-English locales you'll have
+ to set the APR_ICONV_PATH environment variable to the path of the
+ iconv directory in the folder that contains the Subversion install.
+
+ You may also want to add the bin directory in the Subversion folder
+ to your PATH environment variable so as to not have to use the full
+ path when running Subversion commands.
+
+ To test the installation, open a DOS box (run either "cmd" or
+ "command" from the Start menu's "Run..." menu option), change to
+ the directory you installed the executables into, and run:
+
+ C:\test>svn co https://svn.apache.org/repos/asf/subversion/trunk svn
+
+ This will get the latest Subversion sources and put them into the
+ "svn" subdirectory.
+
+ If using a self-extracting .exe file, just run it instead of
+ unzipping it, to install Subversion.
+
+ E. Building the Latest Source under Windows
+ ----------------------------------------
+
+ E.1 Prerequisites
+
+ * Visual Studio 6 and service pack. It can be built with later versions
+ of Visual Studio (Visual Studio.NET 2002, 2003, 2005, 2008 and Visual
+ C++ Express 2005, 2008) but these instructions assume VS6.
+ * A recent Windows SDK. (Not needed with Visual Studio 2005 and later)
+ If you are using Visual Studio 6, you need the latest SDK which
+ is compatible with VC6, which is the one from february 2003.
+ You can get it from MSDN:
+ http://www.microsoft.com/msdownload/platformsdk/sdkupdate/psdk-full.htm
+ * Python 2.5 or higher, downloaded from http://www.python.org/ which is
+ used to generate the project files.
+ * Perl 5.8 or higher from http://www.activestate.com/
+ * Awk (from http://www.cs.princeton.edu/~bwk/btl.mirror/awk95.exe) is
+ needed to compile Apache or APR. Note that this is the actual awk
+ program, not an installer - just rename it to awk.exe and it is
+ ready to use.
+ * Apache apr, apr-util, and optionally apr-iconv libraries, version
+ 0.9.12 or later. Included in both the Subversion dependencies ZIP file
+ and the Apache 2 source zip. If you are building from a Subversion
+ checkout and have not downloaded Apache 2, then get these 3 libraries
+ from http://www.apache.org/dist/apr/.
+ * ZLib 1.2 or higher is required and is included in the Subversion
+ dependencies zip file or can be obtained from http://www.zlib.org
+ * Either a Subversion client binary from http://subversion.apache.org/ to
+ do the initial checkout of the Subversion source or the zip file
+ source distribution. See the section "Bootstrapping from a Zip or
+ Installer File under Windows" above for more.
+ * A means of unpacking the files, e.g., WinZIP or similar.
+
+ Additional Options
+
+ * [Optional] Apache 2 source, downloaded from
+ http://httpd.apache.org/download.cgi, these instructions assume
+ version 2.0.58. This is only needed for building the Subversion
+ server Apache modules. Note that although Subversion will compile
+ against Apache 2.2.3 and APR 1.2.7, there is a bug that causes
+ runtime failures with Subversion on Windows. The fix is included in
+ APR 1.2.8 and will be bundled in the next HTTP Server release
+ (likely to be 2.2.4).
+ * [Optional] Apache 2 msi install file, also from
+ http://httpd.apache.org/download.cgi (required for running the
+ tests). Only needed for testing the server dso modules and if
+ you are using Visual Studio 6.
+ Note that if you are not using Visual Studio 6 (and you want to
+ run and test the server modules) then you must rebuild Apache
+ from source -- do not use the stock MSI since mixing C runtime
+ libraries is not supported.
+ * [Optional] Berkeley DB for backend support of the server
+ components -- versions 4.3.27 and 4.4.20 are available from
+ http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=688
+ as db-4.3.27-win32.zip and db-4.4.20-win32.zip.
+ For more information see Section I.5.
+ * [Optional] Openssl 0.9.7f or higher can be obtained from
+ http://www.openssl.org/source/openssl-0.9.7f.tar.gz
+ * [Optional] A modified version of GNU libintl, called
+ svn-win32-libintl.zip, can be used for displaying localized
+ messages. Available at:
+ http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=2627
+ * [Optional] GNU gettext for generating message catalog (.mo)
+ files from message translations. You can get the latest
+ binaries from http://gnuwin32.sourceforge.net/. You'll need the
+ binaries (gettext-0.14.1-bin.zip) and dependencies
+ (gettext-0.14.1-dep.zip).
+ * [Optional] An assembler, e.g., MASM32 from http://www.masm32.com/
+ or nasm which is available from
+ http://www.nasm.us/pub/nasm/releasebuilds/?C=M;O=D
+
+ E.2 Notes
+
+ The Serf library supports secure connections with OpenSSL and
+ on-the-wire compression with zlib. If you want to use the
+ secure connections feature, you should pass the option
+ "--with-openssl" to the gen-make.py script. See Section I.11 for
+ more details.
+
+ E.3 Preparation
+
+ This section describes how to unpack the files to make a build tree.
+
+ * Make a directory SVN and cd into it.
+ * Either checkout Subversion:
+
+ svn co https://svn.apache.org/repos/asf/subversion/trunk src-trunk
+
+ or unpack the zip file distribution and rename the directory to
+ src-trunk.
+
+ * Install Visual Studio Environment. You either have to tell the
+ installer to register environment variables or run VCVARS32.BAT
+ before building anything. If you are using a newer Visual Studio,
+ use the 'Visual Studio 200x Command Prompt' on the Start menu.
+ * Install and register a recent Windows Core SDK if you are using
+ Visual Studio 6. This is a quote from the Microsoft February 2003
+ SDK documentation:
+
+ "To register the SDK bin, include, and library directories with
+ Microsoft Visual Studio® version 6.0 and Visual Studio .NET,
+ click Start, point to All Programs, point to Microsoft Platform
+ SDK February 2003, point to Visual Studio Registration, and then
+ click Register PSDK Directories with Visual Studio. This
+ registration process places the SDK bin, include, and library
+ directories at the beginning of the search paths, which ensures
+ that the latest headers and libraries are used when building
+ applications in the IDE. Note that for Visual Studio 6.0
+ integration to succeed, Visual Studio 6.0 must run at least once
+ before you select Register PSDK Directories with Visual
+ Studio. Also note that when this option is run, the IDEs should
+ not be running."
+
+ * Install Python and add it to your path
+ * Install Perl (it should add itself to the path)
+ * Copy AWK (awk95.exe) to awk.exe (e.g. SVN\awk\awk.exe) and add
+ the directory containing it (e.g. SVN\awk) to the path.
+ * Install Apache 2 using the msi file if you are going to test the
+ server dso modules and are using Visual Studio 6. You must build
+ and install it from source if you are not using Visual Studio 6 and
+ want to build and/or test the server modules.
+ * If you checked out Subversion from the repository then install the serf
+ sources into SVN\src-trunk\serf.
+ * If you want BDB backend support, extract the Berkeley DB files
+ into SVN\src-trunk\db4-win32. It's a good idea to add
+ SVN\src-trunk\db4-win32\bin to your PATH, so that Subversion can find
+ the Berkeley DB DLLs.
+
+ [NOTE: This binary package of Berkeley DB is provided for
+ convenience only. Please don't address questions about
+ Berkeley DB that aren't directly related to using Subversion
+ to the project mailing list.]
+
+ If you build Berkeley DB from the source, you will have to copy
+ the file db-x.x.x\build_win32\db.h to
+ SVN\src-trunk\db4-win32\include, and all the import libraries to
+ SVN\src-trunk\db4-win32\lib. Again, the DLLs should be somewhere in
+ your path.
+
+ * If you want to build the server modules, extract Apache source into
+ SVN\httpd-2.x.x.
+ * If you are building from a checkout of Subversion, and you are NOT
+ building Apache, then you will need the APR libraries. Depending
+ on how you got your version of APR, either:
+ - Extract the APR, APR-util and APR-iconv source distributions into
+ SVN\apr, SVN\apr-util, and SVN\apr-iconv respectively.
+ Or:
+ - Extract the apr, apr-util and apr-iconv directories from the
+ srclib folder in the Apache httpd source into SVN\apr,
+ SVN\apr-util, and SVN\apr-iconv respectively.
+ * Extract the ZLib sources into SVN\zlib if you are not using the zlib
+ included in the dependencies zip file.
+ * If you want secure connection (https) client support, extract openssl
+ into SVN\openssl-x.x.x
+ * If you want localized message support, extract svn-win32-libintl.zip
+ into SVN\svn-win32-libintl and extract gettext-x.x.x-bin.zip and
+ gettext-x.x.x-dep.zip into SVN\gettext-x.x.x-bin.
+ Add SVN\gettext-x.x.x-bin\bin to your path.
+ * [Optional] Extract MASM32 (only the ML.EXE and ML.ERR files) into
+ SVN\asm (or extract nasm into SVN\asm) and put it in your path.
+
+ E.4 Building the Binaries
+
+ To build the binaries either follow the instructions here or use
+ build\win32\vc6-build.bat.in after editing its default paths to match
+ yours and saving it as vc6-build.bat. The vc6-build.bat does a full build
+ using all options so it requires Apache 2 source and the other optional
+ components.
+
+ Start in the SVN directory you created.
+
+ Set up the environment (commands should be one line even if wrapped here).
+
+ C:>set VER=trunk
+ C:>set DIR=trunk
+ C:>set DRIVE=C
+ C:>set PYTHONDIR=C:\Python22
+ C:>set AWKDIR=C:\SVN\Awk
+ C:>set ASMDIR=C:\SVN\asm
+ C:>set SDKINC=C:\Program Files\Microsoft SDK\include
+ C:>set SDKLIB=C:\Program Files\Microsoft SDK\lib
+ C:>set GETTEXTBIN=C:\SVN\gettext-0.14.1-bin\bin
+ C:>PATH=%PATH%;%DRIVE%:\SVN\src-%DIR%\db4-win32;%ASMDIR%;
+ %PYTHONDIR%;%AWKDIR%;%GETTEXTBIN%
+ C:>set INCLUDE=%SDKINC%;%INCLUDE%
+ C:>set LIB=%SDKLIB%;%LIB%
+
+ OpenSSL
+
+ C:>cd openssl-0.9.7f
+ C:>perl Configure VC-WIN32
+ [*] C:>call ms\do_masm
+ C:>nmake -f ms\ntdll.mak
+ C:>cd out32dll
+ C:>call ..\ms\test
+ C:>cd ..\..
+
+ *Note: Use "call ms\do_nasm" if you have nasm instead of MASM, or
+ "call ms\do_ms" if you don't have an assembler.
+
+ Apache 2
+
+ This step is only required for building the server dso modules.
+
+ The Subversion gen-make.py script must be run before building Apache or
+ Apache and Subversion will be running incompatible versions of apr.
+
+ C:>cd src-%DIR%
+ C:>python gen-make.py -t dsp --with-httpd=..\httpd-2.0.58
+ --with-berkeley-db=db4-win32 --with-openssl=..\openssl-0.9.7f
+ --with-zlib=..\zlib --with-libintl=..\svn-win32-libintl
+ C:>cd ..
+ C:>set APACHEDIR=C:\Program Files\Apache Group\Apache2
+ C:>msdev httpd-2.0.58\apache.dsw /MAKE "BuildBin - Win32 Release"
+
+ Subversion
+
+ Things to note:
+
+ * If you don't want to build mod_dav_svn, omit the --with-httpd
+ option. The zip file source distribution contains apr, apr-util and
+ apr-iconv in the default build location. If you have downloaded the
+ apr files yourself you will have to tell the generator where to find
+ the APR libraries; the options are --with-apr, --with-apr-util and
+ --with-apr-iconv.
+ * If you would like a debug build substitute Debug for Release in
+ the msdev commands.
+ * There have been rumors that Subversion on Win32 can be built
+ using the latest cygwin, you probably don't want the zip file source
+ distribution though. ymmv.
+ * The /USEENV switch to msdev makes it take notice of the INCLUDE and
+ LIB environment variables, it also makes it ignore its own lib and
+ include settings so you need to have the Windows SDK lib and include
+ directories in the LIB and INCLUDE environment variables. Do *not*
+ use this switch when starting up the msdev Visual environment. If you
+ wish to build in the Visual environment the SDK lib and include
+ directories must be in the Tools/Options/Directories settings (if you
+ followed the 'Register the SDK with Visual Studio 6' instructions
+ above this has been done for you).
+ * If you are using Visual Studio .NET change -t dsw into -t vcproj and
+ add the --vsnet-version=200x option on the gen-make.py command.
+ In this case you will also have to distribute the C runtime dll with
+ the binaries. Also, since Apache/APR do not provide .vcproj files,
+ you will need to convert the Apache/APR .dsp files to .vcproj files
+ with Visual Studio before building -- just open the Apache .dsw file
+ and answer 'Yes To All' when the conversion dialog pops up, or you
+ can open the individual .dsp files and convert them one at a time.
+ The Apache/APR projects required by Subversion are:
+ apr-util\libaprutil.dsp, apr\libapr.dsp,
+ apr-iconv\libapriconv.dsp, apr-util\xml\expat\lib\xml.dsp,
+ apr-util\uri\gen_uri_delims.dsp (for APR 0.9.x),
+ apr-iconv\ccs\libapriconv_ccs_modules.dsp, and
+ apr-iconv\ces\libapriconv_ces_modules.dsp.
+ * If the server dso modules are being built and tested Apache must not
+ be running or the copy of the dso modules will fail.
+
+ C:>cd src-%DIR%
+
+ If Apache 2 has been built and the server modules are required then
+ gen-make.py will already have been run. If the source is from the zip
+ file, Apache 2 has not been built so gen-make.py must be run:
+
+ C:>python gen-make.py -t dsp --with-berkeley-db=db4-win32
+ --with-openssl=..\openssl-0.9.7f --with-zlib=..\zlib
+ --with-libintl=..\svn-win32-libintl
+
+ Then build subversion:
+
+ C:>msdev subversion_msvc.dsw /USEENV /MAKE "__ALL_TESTS__ - Win32 Release"
+ C:>cd ..
+
+ Or, with Visual C++.NET 2002, 2003, 2005:
+
+ C:>devenv subversion_vcnet.sln /build "Release" /project "__ALL_TESTS__"
+ C:>cd ..
+
+ Or, with Visual C++ Express 2005:
+
+ C:>msbuild subversion_vcnet.sln /t:__ALL_TESTS__ /p:Configuration=Release
+ C:>cd ..
+
+ The binaries have now been built.
+
+ E.5 Packaging the binaries
+
+ You now need to copy the binaries ready to make the release zip
+ file. You also need to do this to run the tests as the new binaries
+ need to be in your path. You can use the build/win32/make_dist.py
+ script in the Subversion source directory to do that.
+
+ [TBD: Describe how to do this. Note dependencies on zip, jar, doxygen.]
+
+ E.6 Testing the Binaries
+ [TBD: It's been a long, long while since it was necessary to move
+ binaries around for testing. win-tests.py does that automagically.
+ Fix this section accordingly, and probably reorder, putting
+ the packaging at the end.]
+
+ The build process creates the binary test programs but it does not
+ copy the client tests into the release test area.
+
+ C:>cd src-%DIR%
+ C:>mkdir Release\subversion\tests\cmdline
+ C:>xcopy /S /Y subversion\tests\cmdline Release\subversion\tests\cmdline
+
+ If the server dso modules have been built then copy the dso files and
+ dlls into the Apache modules directory.
+
+ C:>copy Release\subversion\mod_dav_svn\mod_dav_svn.so "%APACHEDIR%"\modules
+ C:>copy Release\subversion\mod_authz_svn\mod_authz_svn.so
+ "%APACHEDIR%"\modules
+ C:>copy svn-win32-%VER%\bin\intl.dll "%APACHEDIR%\bin"
+ C:>copy svn-win32-%VER%\bin\iconv.dll "%APACHEDIR%\bin"
+ C:>copy svn-win32-%VER%\bin\libdb42.dll "%APACHEDIR%\bin"
+ C:>cd ..
+
+ Put the svn-win32-trunk\bin directory at the start of your path so
+ you run the newly built binaries and not another version you might
+ have installed.
+
+ Then run the client tests:
+
+ C:>PATH=%DRIVE%:\SVN\svn-win32-%VER%\bin;%PATH%
+ C:>cd src-%DIR%
+ C:>python win-tests.py -c -r -v
+
+ If the server dso modules were built configure Apache to use the
+ mod_dav_svn and mod_authz_svn modules by making sure these lines appear
+ uncommented in httpd.conf:
+
+ LoadModule dav_module modules/mod_dav.so
+ LoadModule dav_fs_module modules/mod_dav_fs.so
+ LoadModule dav_svn_module modules/mod_dav_svn.so
+ LoadModule authz_svn_module modules/mod_authz_svn.so
+
+ And further down the file add location directives to point to the
+ test repositories. Change the paths to the SVN directory you created
+ (paths should be on one line even if wrapped here):
+
+ <Location /svn-test-work/repositories>
+ DAV svn
+ SVNParentPath C:/SVN/src-trunk/Release/subversion/tests/cmdline/
+ svn-test-work/repositories
+ </Location>
+
+ <Location /svn-test-work/local_tmp/repos>
+ DAV svn
+ SVNPath c:/SVN/src-trunk/Release/subversion/tests/cmdline/
+ svn-test-work/local_tmp/repos
+ </Location>
+
+ Then restart Apache and run the tests:
+
+ C:>python win-tests.py -c -r -v -u http://localhost
+ C:>cd ..
+
+III. BUILDING A SUBVERSION SERVER
+ ============================
+
+ Subversion has two servers you can choose from: svnserve and
+ Apache. svnserve is a small, lightweight server program that is
+ automatically compiled when you build Subversion's source. Apache
+ is a more heavyweight HTTP server, but tends to have more features.
+
+ This section primarily focuses on how to build Apache and the
+ accompanying mod_dav_svn server module for it. If you plan to use
+ svnserve instead, jump right to section E for a quick explanation.
+
+
+ A. Setting Up Apache
+ -----------------
+
+ (Following the BOOTSTRAPPING FROM RPM procedures above will install and
+ build the latest Subversion server for Linux RedHat 7.1, 7.2, and PPC
+ Linux systems *IF* the apache-devel-2.0.41 or greater package is already
+ installed when the SUBVERSION RPM is built.)
+
+
+ 1. Obtaining and Installing Apache 2
+
+ Subversion tries to compile against the latest released version
+ of Apache httpd 2.X. The easiest thing for you to do is download
+ a source tarball of the latest release and unpack that.
+
+
+ ****************************************************************
+ ** IMPORTANT ISSUE ABOUT APACHE VERSIONS: READ THIS. **
+ ** **
+ ****************************************************************
+ | |
+ | First, be sure to read the APR version warning box, back in |
+ | section I.C.1, which explains that APR 0.9.x and 1.X are |
+ | binary-incompatible. |
+ | |
+ | Apache HTTPD 2.0 uses APR 0.9.x. |
+ | Apache HTTPD 2.2 uses APR 1.2.x. |
+ | |
+ | We recommend using the latest Apache. However, whatever |
+ | version you choose, you *must* ensure that Subversion |
+ | and Apache are using the same version of APR. If you don't, |
+ | things will segfault and break. |
+ |______________________________________________________________|
+
+
+ If you have questions about the Apache httpd 2.0 build, please consult
+ the httpd install documentation:
+
+ http://httpd.apache.org/docs-2.0/install.html
+
+ At the top of the httpd tree:
+
+ $ ./buildconf
+ $ ./configure --enable-dav --enable-so --enable-maintainer-mode
+
+ The first arg says to build mod_dav.
+
+ The second arg says to enable shared module support which is needed
+ for a typical compile of mod_dav_svn (see below).
+
+ The third arg says to include debugging information. If you
+ built Subversion with --enable-maintainer-mode, then you should
+ do the same for Apache; there can be problems if one was
+ compiled with debugging and the other without.
+
+ Note: if you have multiple db versions installed on your system,
+ Apache might link to a different one than Subversion, causing
+ failures when accessing the repository through Apache. To prevent
+ this from happening, you have to tell Apache which db version to
+ use and where to find db. Add --with-dbm=db4 and
+ --with-berkeley-db=/usr/local/BerkeleyDB.4.2 to the configure
+ line. Make sure this is the same db as the one Subversion uses.
+ This note assumes you have installed Berkeley DB 4.2.52
+ at its default locations. For more info about the db requirement,
+ see section I.5.
+
+ You may also want to include other modules in your build. Add
+ --enable-ssl to turn on SSL support, and --enable-deflate to turn on
+ compression support, for example. Consult the Apache documentation
+ for more details.
+
+ All instructions below assume you configured Apache to install
+ in its default location, /usr/local/apache2/; substitute
+ appropriately if you chose some other location.
+
+ Compile and install apache:
+
+ $ make && make install
+
+
+ B. Making and Installing the Subversion Apache Server Module
+ ---------------------------------------------------------
+
+ Go back into your subversion working copy and run ./autogen.sh if
+ you need to. Then, assuming Apache httpd 2.0 is installed in the
+ standard location, run:
+
+ $ ./configure
+
+ Note: do *not* configure subversion with "--disable-shared"!
+ mod_dav_svn *must* be built as a shared library, and it will
+ look for other libsvn_*.so libraries on your system.
+
+ If you see a warning message that the build of mod_dav_svn is
+ being skipped, this may be because you have Apache httpd 2.X
+ installed in a non-standard location. You can use the
+ "--with-apxs=" option to locate the apxs script:
+
+ $ ./configure --with-apxs=/usr/local/apache2/bin/apxs
+
+ Note: it *is* possible to build mod_dav_svn as a static library
+ and link it directly into Apache. Possible, but painful. Stick
+ with the shared library for now; if you can't, then ask.
+
+ $ rm /usr/local/lib/libsvn*
+
+ If you have old subversion libraries sitting on your system,
+ libtool will link them instead of the `fresh' ones in your tree.
+ Remove them before building subversion.
+
+ $ make clean && make && make install
+
+ After the make install, the Subversion shared libraries are in
+ /usr/local/lib/. mod_dav_svn.so should be installed in
+ /usr/local/apache2/modules/.
+
+
+ Section II.E explains how to build the server on Windows.
+
+
+ C. Configuring Apache for Subversion
+ ---------------------------------
+
+ The following section is an abbreviated version of the
+ information in the Subversion Book
+ (http://svnbook.red-bean.com). Please read chapter 6 for more
+ details.
+
+ The following assumes you have already created a repository.
+ For documentation on how to do that, see README.
+
+ The following also assumes that you have modified
+ /usr/local/apache2/conf/httpd.conf to reflect your setup.
+ At a minimum you should look at the User, Group and ServerName
+ directives. Full details on setting up apache can be found at:
+ http://httpd.apache.org/docs-2.0/
+
+ First, your httpd.conf needs to load the mod_dav_svn module.
+ Subversion's 'make install' target should automatically add this
+ line for you. But if apache gives you an error like "Unknown
+ DAV provider: svn", then you may want to verify that this line
+ exists in your httpd.conf:
+
+ LoadModule dav_svn_module modules/mod_dav_svn.so
+
+ NOTE: if you built mod_dav as a dynamic module as well, make sure
+ the above line appears after the one that loads mod_dav.so.
+
+ Next, add this to the *bottom* of your httpd.conf:
+
+ <Location /svn/repos>
+ DAV svn
+ SVNPath /absolute/path/to/repository
+ </Location>
+
+ This will give anyone unrestricted access to the repository. If
+ you want limited access, read or write, you add these lines to
+ the Location block:
+
+ AuthType Basic
+ AuthName "Subversion repository"
+ AuthUserFile /my/svn/user/passwd/file
+
+ And:
+
+ a) For a read/write restricted repository:
+
+ Require valid-user
+
+ b) For a write restricted repository:
+
+ <LimitExcept GET PROPFIND OPTIONS REPORT>
+ Require valid-user
+ </LimitExcept>
+
+ c) For separate restricted read and write access:
+
+ AuthGroupFile /my/svn/group/file
+
+ <LimitExcept GET PROPFIND OPTIONS REPORT>
+ Require group svn_committers
+ </LimitExcept>
+
+ <Limit GET PROPFIND OPTIONS REPORT>
+ Require group svn_committers
+ Require group svn_readers
+ </Limit>
+
+ These are only a few simple examples. For a complete tutorial
+ on Apache access control, please consider taking a look at the
+ tutorials found under "Security" on the following page:
+ http://httpd.apache.org/docs-2.0/misc/tutorials.html
+
+ In order for 'svn cp' to work (which is actually implemented as a
+ DAV COPY command), mod_dav needs to be able to determine the
+ hostname of the server. A standard way of doing this is to use
+ Apache's ServerName directive to set the server's hostname. Edit
+ your /usr/local/apache2/conf/httpd.conf to include:
+
+ ServerName svn.myserver.org
+
+ If you are using virtual hosting through Apache's NameVirtualHost
+ directive, you may need to use the ServerAlias directive to specify
+ additional names that your server is known by.
+
+ If you have configured mod_deflate to be in the server, you can enable
+ compression support for your repository by adding the following line
+ to your Location block:
+
+ SetOutputFilter DEFLATE
+
+
+ NOTE: If you are unfamiliar with an Apache directive, or not exactly
+ sure about what it does, don't hesitate to look it up in the
+ documentation: http://httpd.apache.org/docs-2.0/mod/directives.html.
+
+ NOTE: Make sure that the user 'nobody' (or whatever UID the
+ httpd process runs as) has permission to read and write the
+ Berkeley DB files! This is a very common problem.
+
+
+ D. Running and Testing
+ -------------------
+
+ Fire up apache 2:
+
+ $ /usr/local/apache2/bin/apachectl stop
+ $ /usr/local/apache2/bin/apachectl start
+
+ Check /usr/local/apache2/logs/error_log to make sure it started
+ up okay.
+
+ Try doing a network checkout from the repository:
+
+ $ svn co http://localhost/svn/repos wc
+
+ The most common reason this might fail is permission problems
+ reading the repository db files. If the checkout fails, make
+ sure that the httpd process has permission to read and write to
+ the repository. You can see all of mod_dav_svn's complaints in
+ the Apache error logfile, /usr/local/apache2/logs/error_log.
+
+ To run the regression test suite for networked Subversion, see
+ the instructions in subversion/tests/cmdline/README.
+ For advice about tracing problems, see "Debugging the server" in
+ http://subversion.apache.org/docs/community-guide/.
+
+
+ E. Alternative: 'svnserve' and ra_svn
+ -----------------------------------
+
+ An alternative network layer is libsvn_ra_svn (on the client
+ side) and the 'svnserve' process on the server. This is a
+ simple network layer that speaks a custom protocol over plain
+ TCP (documented in libsvn_ra_svn/protocol):
+
+ $ svnserve -d # becomes a background daemon
+ $ svn checkout svn://localhost/usr/local/svn/repository
+
+ You can use the "-r" option to svnserve to set a logical root
+ for repositories, and the "-R" option to restrict connections to
+ read-only access. ("Read-only" is a logical term here; svnserve
+ still needs write access to the database in this mode, but will
+ not allow commits or revprop changes.)
+
+ 'svnserve' has built-in CRAM-MD5 authentication (so you can use
+ non-system accounts), and can also be tunneled over SSH (so you
+ can use existing system accounts). It's also capable of using
+ Cyrus SASL if libsasl2 is detected at ./configure time. Please
+ read chapter 6 in the Subversion Book
+ (http://svnbook.red-bean.com) for details on these features.
+
+
+
+IV. PLATFORM-SPECIFIC ISSUES
+ ========================
+
+ A. Windows XP
+ ----------
+
+ There is an error in the Windows XP TCP/IP stack which causes
+ corruption in certain cases. This problem is exposed only
+ through ra_dav.
+
+ The root of the matter is caused by duplicating file handles
+ between parent and child processes. The httpd Apache group
+ explains this a lot better:
+
+ http://www.apache.org/dist/httpd/binaries/win32/#xpbug
+
+ And there's an item about this in the Subversion FAQ:
+
+ http://subversion.apache.org/faq.html#windows-xp-server
+
+ The only known workaround for now is to update to Windows XP
+ SP1 (or higher).
+
+
+ B. Mac OS X
+ --------
+
+ [TBD: Describe BDB 4.0.x problem]
+
+
+
+V. PROGRAMMING LANGUAGE BINDINGS (PYTHON, PERL, RUBY, JAVA)
+ ========================================================
+
+ For Python, Perl and Ruby bindings, see the file
+
+ ./subversion/bindings/swig/INSTALL
+
+ For Java bindings, see the file
+
+ ./subversion/bindings/javahl/README
diff --git a/contrib/subversion/LICENSE b/contrib/subversion/LICENSE
new file mode 100644
index 0000000..fd5f6ba
--- /dev/null
+++ b/contrib/subversion/LICENSE
@@ -0,0 +1,270 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+SUBVERSION SUBCOMPONENTS:
+
+Subversion includes a number of subcomponents with separate copyright
+notices and license terms. Your use of the source code for the these
+subcomponents is subject to the terms and conditions of the following
+licenses.
+
+For portions of the Python bindings test suite at
+subversion/bindings/swig/python/tests/trac/:
+
+ I. Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+ II. Copyright (C) 2003, 2004, 2005 Edgewall Software
+ Copyright (C) 2003, 2004, 2005 Jonas Borgström <jonas@edgewall.com>
+ Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ the documentation and/or other materials provided with the
+ distribution.
+ 3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+For the file subversion/libsvn_subr/utf_width.c
+
+ * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
diff --git a/contrib/subversion/Makefile.in b/contrib/subversion/Makefile.in
new file mode 100644
index 0000000..fdcd544
--- /dev/null
+++ b/contrib/subversion/Makefile.in
@@ -0,0 +1,907 @@
+#
+# Makefile.in: template Makefile for Subversion
+#
+# ====================================================================
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ====================================================================
+#
+
+top_builddir = .
+top_srcdir = @top_srcdir@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+VPATH = @top_srcdir@
+
+SVN_RA_LIB_DEPS = @SVN_RA_LIB_DEPS@
+SVN_RA_LIB_INSTALL_DEPS = @SVN_RA_LIB_INSTALL_DEPS@
+SVN_RA_LIB_LINK = @SVN_RA_LIB_LINK@
+
+SVN_FS_LIB_DEPS = @SVN_FS_LIB_DEPS@
+SVN_FS_LIB_INSTALL_DEPS = @SVN_FS_LIB_INSTALL_DEPS@
+SVN_FS_LIB_LINK = @SVN_FS_LIB_LINK@
+
+SWIG_SRC_DIR = $(abs_srcdir)/subversion/bindings/swig
+SWIG_BUILD_DIR = $(abs_builddir)/subversion/bindings/swig
+
+SCHEMA_DIR = subversion/svn/schema
+
+SVN_APR_LIBS = @SVN_APR_LIBS@
+SVN_APRUTIL_LIBS = @SVN_APRUTIL_LIBS@
+SVN_APR_MEMCACHE_LIBS = @SVN_APR_MEMCACHE_LIBS@
+SVN_DB_LIBS = @SVN_DB_LIBS@
+SVN_GPG_AGENT_LIBS = @SVN_GPG_AGENT_LIBS@
+SVN_GNOME_KEYRING_LIBS = @SVN_GNOME_KEYRING_LIBS@
+SVN_KWALLET_LIBS = @SVN_KWALLET_LIBS@
+SVN_MAGIC_LIBS = @SVN_MAGIC_LIBS@
+SVN_SASL_LIBS = @SVN_SASL_LIBS@
+SVN_SERF_LIBS = @SVN_SERF_LIBS@
+SVN_SQLITE_LIBS = @SVN_SQLITE_LIBS@
+SVN_XML_LIBS = @SVN_XML_LIBS@
+SVN_ZLIB_LIBS = @SVN_ZLIB_LIBS@
+
+LIBS = @LIBS@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+libdir = @libdir@
+fsmod_libdir = @libdir@
+ramod_libdir = @libdir@
+bdb_libdir = @libdir@
+gnome_keyring_libdir = @libdir@
+gpg_agent_libdir = @libdir@
+kwallet_libdir = @libdir@
+serf_libdir = @libdir@
+bindir = @bindir@
+includedir = @includedir@
+mandir = @mandir@
+srcdir = @srcdir@
+canonicalized_srcdir = @canonicalized_srcdir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+localedir = @localedir@
+
+# where to install libsvn_swig_*
+swig_py_libdir = @libdir@
+swig_pl_libdir = @libdir@
+swig_rb_libdir = @libdir@
+
+### these possibly need further discussion
+swig_pydir = @libdir@/svn-python/libsvn
+swig_pydir_extra = @libdir@/svn-python/svn
+swig_pldir = @libdir@/svn-perl
+swig_rbdir = $(SWIG_RB_SITE_ARCH_DIR)/svn/ext
+toolsdir = @bindir@/svn-tools
+
+javahl_javadir = @libdir@/svn-javahl
+javahl_javahdir = @libdir@/svn-javahl/include
+javahl_libdir = @libdir@
+javahl_test_rootdir=$(abs_builddir)/subversion/bindings/javahl/test-work
+javahl_test_srcdir=$(abs_srcdir)/subversion/bindings/javahl
+
+gnome_auth_dir = $(abs_builddir)/subversion/libsvn_auth_gnome_keyring/.libs
+kwallet_auth_dir = $(abs_builddir)/subversion/libsvn_auth_kwallet/.libs
+auth_plugin_dirs = $(gnome_auth_dir):$(kwallet_auth_dir)
+
+MSGFMT = @MSGFMT@
+MSGFMTFLAGS = @MSGFMTFLAGS@
+MSGMERGE = @MSGMERGE@
+XGETTEXT = @XGETTEXT@
+TRANG = @TRANG@
+
+PACKAGE_NAME=@PACKAGE_NAME@
+PACKAGE_VERSION=@PACKAGE_VERSION@
+
+CC = @CC@
+CXX = @CXX@
+EXEEXT = @EXEEXT@
+
+SHELL = @SHELL@
+LIBTOOL = @SVN_LIBTOOL@
+LTFLAGS = --tag=CC --silent
+LTCXXFLAGS = --tag=CXX --silent
+LT_CFLAGS = @LT_CFLAGS@
+LT_LDFLAGS = @LT_LDFLAGS@
+LT_SO_VERSION = @SVN_LT_SOVERSION@
+LT_NO_UNDEFINED = @LT_NO_UNDEFINED@
+LT_CXX_LIBADD = @LT_CXX_LIBADD@
+
+INCLUDES = -I$(top_srcdir)/subversion/include -I$(top_builddir)/subversion \
+ @SVN_APR_INCLUDES@ @SVN_APRUTIL_INCLUDES@ @SVN_APR_MEMCACHE_INCLUDES@ \
+ @SVN_DB_INCLUDES@ @SVN_GNOME_KEYRING_INCLUDES@ \
+ @SVN_KWALLET_INCLUDES@ @SVN_MAGIC_INCLUDES@ \
+ @SVN_SASL_INCLUDES@ @SVN_SERF_INCLUDES@ @SVN_SQLITE_INCLUDES@ \
+ @SVN_XML_INCLUDES@ @SVN_ZLIB_INCLUDES@
+
+APACHE_INCLUDES = @APACHE_INCLUDES@
+APACHE_LIBEXECDIR = $(DESTDIR)@APACHE_LIBEXECDIR@
+APACHE_LDFLAGS = @APACHE_LDFLAGS@
+
+SWIG = @SWIG@
+SWIG_PY_INCLUDES = @SWIG_PY_INCLUDES@ -I$(SWIG_SRC_DIR)/python/libsvn_swig_py
+SWIG_PY_COMPILE = @SWIG_PY_COMPILE@
+SWIG_PY_LINK = @SWIG_PY_LINK@
+SWIG_PY_LIBS = @SWIG_PY_LIBS@
+SWIG_PL_INCLUDES = @SWIG_PL_INCLUDES@
+SWIG_RB_INCLUDES = @SWIG_RB_INCLUDES@ -I$(SWIG_SRC_DIR)/ruby/libsvn_swig_ruby
+SWIG_RB_COMPILE = @SWIG_RB_COMPILE@
+SWIG_RB_LINK = @SWIG_RB_LINK@
+SWIG_RB_LIBS = @SWIG_RB_LIBS@
+SWIG_RB_SITE_LIB_DIR = @SWIG_RB_SITE_LIB_DIR@
+SWIG_RB_SITE_ARCH_DIR = @SWIG_RB_SITE_ARCH_DIR@
+SWIG_RB_TEST_VERBOSE = @SWIG_RB_TEST_VERBOSE@
+SWIG_RB_RI_DATADIR = $(DESTDIR)$(datadir)/ri/$(RUBY_MAJOR).$(RUBY_MINOR)/site
+
+CTYPESGEN = @CTYPESGEN@
+CTYPES_PYTHON_SRC_DIR = $(abs_srcdir)/subversion/bindings/ctypes-python
+
+JAVAHL_JAR=subversion/bindings/javahl/svn-javahl.jar
+JAVAHL_INCLUDES= @JNI_INCLUDES@ -I$(abs_builddir)/subversion/bindings/javahl/include
+
+CXXHL_INCLUDES = -I$(abs_srcdir)/subversion/bindings/cxxhl/include
+
+SVN_APR_CONFIG = @SVN_APR_CONFIG@
+SVN_APR_INCLUDES = @SVN_APR_INCLUDES@
+SVN_APRUTIL_CONFIG = @SVN_APRUTIL_CONFIG@
+SVN_APRUTIL_INCLUDES = @SVN_APRUTIL_INCLUDES@
+
+MKDIR = @MKDIR@
+
+DOXYGEN = @DOXYGEN@
+
+# The EXTRA_ parameters can be used to pass extra flags at 'make' time.
+CFLAGS = @CFLAGS@ $(EXTRA_CFLAGS)
+CMODEFLAGS = @CMODEFLAGS@
+CMAINTAINERFLAGS = @CMAINTAINERFLAGS@
+CXXFLAGS = @CXXFLAGS@ $(EXTRA_CXXFLAGS)
+CXXMODEFLAGS = @CXXMODEFLAGS@
+CXXMAINTAINERFLAGS = @CXXMAINTAINERFLAGS@
+### A few of the CFLAGS (e.g. -Wmissing-prototypes, -Wstrict-prototypes,
+### -Wmissing-declarations) are not valid for C++, and should be somehow
+### suppressed (but they may come from httpd or APR).
+CPPFLAGS = @CPPFLAGS@ $(EXTRA_CPPFLAGS)
+LDFLAGS = @LDFLAGS@ $(EXTRA_LDFLAGS)
+SWIG_LDFLAGS = @SWIG_LDFLAGS@ $(EXTRA_SWIG_LDFLAGS)
+
+COMPILE = $(CC) $(CMODEFLAGS) $(CPPFLAGS) $(CMAINTAINERFLAGS) $(CFLAGS) $(INCLUDES)
+COMPILE_CXX = $(CXX) $(CXXMODEFLAGS) $(CPPFLAGS) $(CXXMAINTAINERFLAGS) $(CXXFLAGS) $(INCLUDES)
+LT_COMPILE = $(LIBTOOL) $(LTFLAGS) --mode=compile $(COMPILE) $(LT_CFLAGS)
+LT_COMPILE_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=compile $(COMPILE_CXX) $(LT_CFLAGS)
+
+# Execute a command that loads libraries from the build dir
+LT_EXECUTE = $(LIBTOOL) $(LTFLAGS) --mode=execute `for f in $(abs_builddir)/subversion/*/*.la; do echo -dlopen $$f; done`
+
+# special compilation for files destined for mod_dav_svn
+COMPILE_APACHE_MOD = $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) $(CMODEFLAGS) $(CPPFLAGS) $(CFLAGS) $(CMAINTAINERFLAGS) $(LT_CFLAGS) $(APACHE_INCLUDES) $(INCLUDES) -o $@ -c
+
+# special compilation for files destined for libsvn_swig_* (e.g. swigutil_*.c)
+COMPILE_SWIG_PY = $(LIBTOOL) $(LTFLAGS) --mode=compile $(SWIG_PY_COMPILE) $(CPPFLAGS) $(LT_CFLAGS) -DSWIGPYTHON $(SWIG_PY_INCLUDES) $(INCLUDES) -o $@ -c
+COMPILE_SWIG_PL = $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) $(CPPFLAGS) $(CFLAGS) $(LT_CFLAGS) $(SWIG_PL_INCLUDES) $(INCLUDES) -o $@ -c
+COMPILE_SWIG_RB = $(LIBTOOL) $(LTFLAGS) --mode=compile $(SWIG_RB_COMPILE) $(CPPFLAGS) $(LT_CFLAGS) $(SWIG_RB_INCLUDES) $(INCLUDES) -o $@ -c
+
+# special compilation for files destined for javahl (i.e. C++)
+COMPILE_JAVAHL_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=compile $(COMPILE_CXX) $(LT_CFLAGS) $(JAVAHL_INCLUDES) -o $@ -c
+COMPILE_JAVAHL_JAVAC = $(JAVAC) $(JAVAC_FLAGS)
+COMPILE_JAVAHL_JAVAH = $(JAVAH)
+
+# special compilation for files destined for cxxhl
+COMPILE_CXXHL_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=compile $(COMPILE_CXX) $(LT_CFLAGS) $(CXXHL_INCLUDES) -o $@ -c
+
+LINK = $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LT_LDFLAGS) $(CFLAGS) $(LDFLAGS) -rpath $(libdir)
+LINK_LIB = $(LINK) $(LT_SO_VERSION)
+LINK_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=link $(CXX) $(LT_LDFLAGS) $(CXXFLAGS) $(LDFLAGS) -rpath $(libdir)
+LINK_CXX_LIB = $(LINK_CXX) $(LT_SO_VERSION)
+
+# special link rule for mod_dav_svn
+LINK_APACHE_MOD = $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LT_LDFLAGS) $(CFLAGS) $(LDFLAGS) -rpath $(APACHE_LIBEXECDIR) -avoid-version -module $(APACHE_LDFLAGS)
+
+# Special LDFLAGS for some libraries
+libsvn_auth_gnome_keyring_LDFLAGS = @libsvn_auth_gnome_keyring_LDFLAGS@
+libsvn_auth_kwallet_LDFLAGS = @libsvn_auth_kwallet_LDFLAGS@
+libsvn_client_LDFLAGS = @libsvn_client_LDFLAGS@
+libsvn_delta_LDFLAGS = @libsvn_delta_LDFLAGS@
+libsvn_diff_LDFLAGS = @libsvn_diff_LDFLAGS@
+libsvn_fs_LDFLAGS = @libsvn_fs_LDFLAGS@
+libsvn_fs_base_LDFLAGS = @libsvn_fs_base_LDFLAGS@
+libsvn_fs_fs_LDFLAGS = @libsvn_fs_fs_LDFLAGS@
+libsvn_fs_util_LDFLAGS = @libsvn_fs_util_LDFLAGS@
+libsvn_ra_LDFLAGS = @libsvn_ra_LDFLAGS@
+libsvn_ra_local_LDFLAGS = @libsvn_ra_local_LDFLAGS@
+libsvn_ra_serf_LDFLAGS = @libsvn_ra_serf_LDFLAGS@
+libsvn_ra_svn_LDFLAGS = @libsvn_ra_svn_LDFLAGS@
+libsvn_repos_LDFLAGS = @libsvn_repos_LDFLAGS@
+libsvn_subr_LDFLAGS = @libsvn_subr_LDFLAGS@
+libsvn_wc_LDFLAGS = @libsvn_wc_LDFLAGS@
+
+# Compilation of SWIG-generated C source code
+COMPILE_PY_WRAPPER = $(LIBTOOL) $(LTFLAGS) --mode=compile $(SWIG_PY_COMPILE) $(LT_CFLAGS) $(CPPFLAGS) $(SWIG_PY_INCLUDES) -prefer-pic -c -o $@
+COMPILE_RB_WRAPPER = $(LIBTOOL) $(LTFLAGS) --mode=compile $(SWIG_RB_COMPILE) $(LT_CFLAGS) $(CPPFLAGS) $(SWIG_RB_INCLUDES) -prefer-pic -c -o $@
+
+# these commands link the wrapper objects into an extension library/module
+LINK_PY_WRAPPER = $(LIBTOOL) $(LTFLAGS) --mode=link $(SWIG_PY_LINK) $(SWIG_LDFLAGS) -rpath $(swig_pydir) -avoid-version -module
+LINK_RB_WRAPPER = $(LIBTOOL) $(LTFLAGS) --mode=link $(SWIG_RB_LINK) $(SWIG_LDFLAGS) -rpath $(swig_rbdir) -avoid-version -module
+
+LINK_JAVAHL_CXX = $(LIBTOOL) $(LTCXXFLAGS) --mode=link $(CXX) $(LT_LDFLAGS) $(CXXFLAGS) $(LDFLAGS) $(LT_CXX_LIBADD) -rpath $(libdir)
+
+INSTALL = @INSTALL@
+INSTALL_LIB = $(LIBTOOL) --mode=install $(INSTALL)
+INSTALL_FSMOD_LIB = $(INSTALL_LIB)
+INSTALL_RAMOD_LIB = $(INSTALL_LIB)
+INSTALL_APR_MEMCACHE_LIB = $(INSTALL_LIB)
+INSTALL_BDB_LIB = $(INSTALL_LIB)
+INSTALL_GPG_AGENT_LIB = $(INSTALL_LIB)
+INSTALL_GNOME_KEYRING_LIB = $(INSTALL_LIB)
+INSTALL_KWALLET_LIB = $(INSTALL_LIB)
+INSTALL_SERF_LIB = $(INSTALL_LIB)
+INSTALL_BIN = $(LIBTOOL) --mode=install $(INSTALL)
+INSTALL_CONTRIB = $(LIBTOOL) --mode=install $(INSTALL)
+INSTALL_TOOLS = $(LIBTOOL) --mode=install $(INSTALL)
+INSTALL_INCLUDE = $(INSTALL) -m 644
+INSTALL_MOD_SHARED = @APXS@ -i -S LIBEXECDIR="$(APACHE_LIBEXECDIR)" @MOD_ACTIVATION@
+INSTALL_DATA = $(INSTALL) -m 644
+INSTALL_LOCALE = $(INSTALL_DATA)
+INSTALL_APACHE_MODS = @INSTALL_APACHE_MODS@
+
+### this isn't correct yet
+INSTALL_SWIG_PY = $(INSTALL_LIB)
+INSTALL_SWIG_PY_LIB = $(INSTALL_LIB)
+INSTALL_SWIG_PL_LIB = $(INSTALL_LIB)
+INSTALL_SWIG_RB = $(INSTALL_LIB)
+INSTALL_SWIG_RB_LIB = $(INSTALL_LIB)
+
+INSTALL_JAVAHL_LIB = $(INSTALL_LIB)
+
+# additional installation rules for the SWIG wrappers
+INSTALL_EXTRA_SWIG_PY=\
+ $(MKDIR) $(DESTDIR)$(swig_pydir); \
+ $(MKDIR) $(DESTDIR)$(swig_pydir_extra); \
+ for i in $(abs_srcdir)/subversion/bindings/swig/python/svn/*.py; do \
+ $(INSTALL_DATA) "$$i" $(DESTDIR)$(swig_pydir_extra); \
+ done; \
+ for i in $(abs_srcdir)/subversion/bindings/swig/python/*.py; do \
+ $(INSTALL_DATA) "$$i" $(DESTDIR)$(swig_pydir); \
+ done; \
+ if test "$(abs_srcdir)" != "$(abs_builddir)"; then \
+ for i in $(abs_builddir)/subversion/bindings/swig/python/*.py; do \
+ $(INSTALL_DATA) "$$i" $(DESTDIR)$(swig_pydir); \
+ done; \
+ fi; \
+ $(PYTHON) -c 'import compileall; \
+ compileall.compile_dir("$(DESTDIR)$(swig_pydir)", 1, "$(swig_pydir)"); \
+ compileall.compile_dir("$(DESTDIR)$(swig_pydir_extra)", 1, \
+ "$(swig_pydir_extra)");'
+
+# export an env variable so that the tests can run without being installed
+TEST_SHLIB_VAR_SWIG_PY=\
+ if [ "@SVN_APR_SHLIB_PATH_VAR@" = "DYLD_LIBRARY_PATH" ]; then \
+ for d in $(SWIG_PY_DIR)/libsvn_swig_py $(SWIG_PY_DIR)/../../../libsvn_*; do \
+ if [ -n "$$DYLD_LIBRARY_PATH" ]; then \
+ @SVN_APR_SHLIB_PATH_VAR@="$$@SVN_APR_SHLIB_PATH_VAR@:$$d/.libs"; \
+ else \
+ @SVN_APR_SHLIB_PATH_VAR@="$$d/.libs"; \
+ fi; \
+ done; \
+ export @SVN_APR_SHLIB_PATH_VAR@; \
+ fi;
+
+# The path to generated and complementary source files for the SWIG
+# bindings.
+SWIG_PL_DIR = $(abs_builddir)/subversion/bindings/swig/perl
+SWIG_PY_DIR = $(abs_builddir)/subversion/bindings/swig/python
+SWIG_RB_DIR = $(abs_builddir)/subversion/bindings/swig/ruby
+
+# The path to the source files for the SWIG bindings
+SWIG_PL_SRC_DIR = $(abs_srcdir)/subversion/bindings/swig/perl
+SWIG_PY_SRC_DIR = $(abs_srcdir)/subversion/bindings/swig/python
+SWIG_RB_SRC_DIR = $(abs_srcdir)/subversion/bindings/swig/ruby
+
+### Automate JAR creation using Makefile generator's javahl-java.jar
+### property. Enhance generator to support JAR installation.
+JAVAHL_MANIFEST_IN = $(abs_srcdir)/subversion/bindings/javahl/Manifest.in
+JAVAHL_MANIFEST = subversion/bindings/javahl/Manifest
+INSTALL_EXTRA_JAVAHL_JAVA=\
+ sed s/%bundleVersion/$(PACKAGE_VERSION)/ $(JAVAHL_MANIFEST_IN) > $(JAVAHL_MANIFEST); \
+ $(JAR) cfm $(JAVAHL_JAR) $(JAVAHL_MANIFEST) -C subversion/bindings/javahl/classes org; \
+ $(INSTALL_DATA) $(JAVAHL_JAR) $(DESTDIR)$(javahl_javadir);
+
+INSTALL_EXTRA_JAVAHL_LIB=@INSTALL_EXTRA_JAVAHL_LIB@
+
+INSTALL_EXTRA_SWIG_RB=\
+ @echo $(MKDIR) $(DESTDIR)$(SWIG_RB_SITE_LIB_DIR)/svn; \
+ $(MKDIR) $(DESTDIR)$(SWIG_RB_SITE_LIB_DIR)/svn; \
+ for i in $(abs_srcdir)/subversion/bindings/swig/ruby/svn/*.rb; do \
+ echo $(INSTALL_DATA) "$$i" $(DESTDIR)$(SWIG_RB_SITE_LIB_DIR)/svn; \
+ $(INSTALL_DATA) "$$i" $(DESTDIR)$(SWIG_RB_SITE_LIB_DIR)/svn; \
+ done
+
+# export an env variable so that the tests can run without being installed
+TEST_SHLIB_VAR_SWIG_RB=\
+ if [ "@SVN_APR_SHLIB_PATH_VAR@" = "DYLD_LIBRARY_PATH" ]; then \
+ for d in $(SWIG_PY_DIR)/libsvn_swig_rb $(SWIG_PY_DIR)/../../../libsvn_*; do \
+ if [ -n "$$DYLD_LIBRARY_PATH" ]; then \
+ @SVN_APR_SHLIB_PATH_VAR@="$$@SVN_APR_SHLIB_PATH_VAR@:$$d/.libs"; \
+ else \
+ @SVN_APR_SHLIB_PATH_VAR@="$$d/.libs"; \
+ fi; \
+ done; \
+ export @SVN_APR_SHLIB_PATH_VAR@; \
+ fi;
+
+APXS = @APXS@
+
+PYTHON = @PYTHON@
+PERL = @PERL@
+
+JDK = @JDK@
+JAVA = @JAVA@
+JAVAC = @JAVAC@
+JAVADOC = @JAVADOC@
+JAVAC_FLAGS = @JAVAC_FLAGS@
+JAVAH = @JAVAH@
+JAR = @JAR@
+
+JAVA_CLASSPATH=$(abs_srcdir)/subversion/bindings/javahl/src:@JAVA_CLASSPATH@
+javahl_java_CLASSPATH=$(JAVA_CLASSPATH)
+javahl_compat_CLASSPATH=$(JAVA_CLASSPATH)
+javahl_tests_CLASSPATH=$(JAVA_CLASSPATH)
+javahl_compat_tests_CLASSPATH=$(JAVA_CLASSPATH)
+
+RUBY = @RUBY@
+RUBY_MAJOR = @RUBY_MAJOR@
+RUBY_MINOR = @RUBY_MINOR@
+RDOC = @RDOC@
+
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+
+TESTS = $(TEST_PROGRAMS) @BDB_TEST_PROGRAMS@
+
+all: mkdir-init local-all
+clean: local-clean
+distclean: local-distclean
+extraclean: local-extraclean
+install: local-install revision-install
+
+@INCLUDE_OUTPUTS@
+
+local-all: @BUILD_RULES@ @TRANSFORM_LIBTOOL_SCRIPTS@
+
+transform-libtool-scripts: @BUILD_RULES@
+ @$(SHELL) $(top_srcdir)/build/transform_libtool_scripts.sh
+
+locale-gnu-pot:
+ cd $(abs_srcdir) && XGETTEXT="$(XGETTEXT)" MSGMERGE="$(MSGMERGE)" \
+ $(SHELL) tools/po/po-update.sh pot
+
+# "make locale-gnu-po-update" updates all translations.
+# "make locale-gnu-po-update PO=ll" updates only the ll.po file.
+locale-gnu-po-update:
+ cd $(abs_srcdir) && XGETTEXT="$(XGETTEXT)" MSGMERGE="$(MSGMERGE)" \
+ $(SHELL) tools/po/po-update.sh $(PO)
+
+# clean everything but the bulky test output, returning the system back
+# to before 'make' was run.
+fast-clean: doc-clean
+ @list='$(BUILD_DIRS)'; for i in $$list; do \
+ if [ -d $$i ]; then \
+ (cd $$i && rm -f *.o *.lo *.la *.la-a *.spo *.mo && \
+ rm -rf .libs); \
+ fi \
+ done
+ echo $(CLEAN_FILES) | xargs rm -f --
+ find $(CTYPES_PYTHON_SRC_DIR) $(SWIG_PY_SRC_DIR) $(SWIG_PY_DIR) \
+ $(abs_srcdir)/build $(top_srcdir)/subversion/tests/cmdline/svntest \
+ -name "*.pyc" -exec rm {} ';'
+
+# clean everything, returning to before './configure' was run.
+SVN_CONFIG_SCRIPT_FILES = @SVN_CONFIG_SCRIPT_FILES@
+local-distclean: local-clean
+ rm -fr config.cache config.log config.nice config.status \
+ libtool mkmf.log subversion/svn_private_config.h \
+ subversion/bindings/javahl/classes \
+ subversion/bindings/javahl/include \
+ $(SVN_CONFIG_SCRIPT_FILES)
+ rm -f Makefile
+
+# clean everything out, returning to before './autogen.sh' was run.
+local-extraclean: extraclean-bindings local-distclean
+ rm -f $(top_srcdir)/build-outputs.mk \
+ $(top_srcdir)/subversion/svn_private_config.h.in \
+ $(top_srcdir)/configure \
+ $(top_srcdir)/gen-make.opts \
+ $(top_srcdir)/build/config.guess \
+ $(top_srcdir)/build/config.sub \
+ $(top_srcdir)/build/libtool.m4 \
+ $(top_srcdir)/build/ltoptions.m4 \
+ $(top_srcdir)/build/ltsugar.m4 \
+ $(top_srcdir)/build/ltversion.m4 \
+ $(top_srcdir)/build/lt~obsolete.m4 \
+ $(top_srcdir)/build/ltmain.sh \
+ $(top_srcdir)/build/transform_libtool_scripts.sh \
+ $(EXTRACLEAN_FILES)
+
+
+# clean everything, including test output.
+local-clean: check-clean clean-bindings fast-clean
+
+local-install: @INSTALL_RULES@
+
+revision-install:
+ test -d $(DESTDIR)$(includedir)/subversion-1 || \
+ $(MKDIR) $(DESTDIR)$(includedir)/subversion-1
+ (subversion/svnversion/svnversion $(top_srcdir) 2> /dev/null || \
+ svnversion $(top_srcdir) 2> /dev/null || \
+ echo "unknown"; \
+ ) > $(DESTDIR)$(includedir)/subversion-1/svn-revision.txt
+
+install-static: @INSTALL_STATIC_RULES@
+
+# JavaHL target aliases
+javahl: mkdir-init javahl-java javahl-javah javahl-callback-javah javahl-types-javah javahl-lib @JAVAHL_TESTS_TARGET@ javahl-compat
+install-javahl: javahl install-javahl-java install-javahl-javah install-javahl-lib
+javahl-compat: javahl-compat-java @JAVAHL_COMPAT_TESTS_TARGET@
+
+clean-javahl:
+ rm -rf $(javahl_java_PATH) $(javahl_javah_PATH) @JAVAHL_OBJDIR@
+ rm -fr $(javahl_test_rootdir)
+ rm -f $(libsvnjavahl_PATH)/*.la $(JAVAHL_JAR)
+ rm -f $(libsvnjavahl_PATH)/*.lo
+ rm -f $(libsvnjavahl_PATH)/*.o
+
+check-tigris-javahl: javahl-compat
+ @FIX_JAVAHL_LIB@
+ $(JAVA) "-Dtest.rootdir=$(javahl_test_rootdir)" "-Dtest.srcdir=$(javahl_test_srcdir)" "-Dtest.rooturl=$(BASE_URL)" "-Dtest.fstype=$(FS_TYPE)" -Djava.library.path=@JAVAHL_OBJDIR@:$(libdir) -classpath $(javahl_compat_tests_PATH):$(javahl_tests_CLASSPATH) "-Dtest.tests=$(JAVAHL_TESTS)" org.tigris.subversion.javahl.RunTests
+
+check-apache-javahl: javahl
+ @FIX_JAVAHL_LIB@
+ $(JAVA) "-Dtest.rootdir=$(javahl_test_rootdir)" "-Dtest.srcdir=$(javahl_test_srcdir)" "-Dtest.rooturl=$(BASE_URL)" "-Dtest.fstype=$(FS_TYPE)" -Djava.library.path=@JAVAHL_OBJDIR@:$(libdir) -classpath $(javahl_tests_PATH):$(javahl_tests_CLASSPATH) "-Dtest.tests=$(JAVAHL_TESTS)" org.apache.subversion.javahl.RunTests
+
+check-javahl: check-apache-javahl
+
+check-all-javahl: check-apache-javahl check-tigris-javahl
+
+# "make check CLEANUP=true" will clean up directories for successful tests.
+# "make check TESTS=subversion/tests/cmdline/basic_tests.py"
+# will perform only basic tests (likewise for other tests).
+check: bin @TRANSFORM_LIBTOOL_SCRIPTS@ $(TEST_DEPS) @BDB_TEST_DEPS@
+ @if test "$(PYTHON)" != "none"; then \
+ flags="--verbose"; \
+ if test "$(CLEANUP)" != ""; then \
+ flags="--cleanup $$flags"; \
+ fi; \
+ if test "$(BASE_URL)" != ""; then \
+ flags="--url $(BASE_URL) $$flags"; \
+ fi; \
+ if test "$(FS_TYPE)" != ""; then \
+ flags="--fs-type $(FS_TYPE) $$flags"; \
+ fi; \
+ if test "$(HTTP_LIBRARY)" != ""; then \
+ flags="--http-library $(HTTP_LIBRARY) $$flags"; \
+ fi; \
+ if test "$(SERVER_MINOR_VERSION)" != ""; then \
+ flags="--server-minor-version $(SERVER_MINOR_VERSION) $$flags"; \
+ fi; \
+ if test "$(ENABLE_SASL)" != ""; then \
+ flags="--enable-sasl $$flags"; \
+ fi; \
+ if test "$(FSFS_SHARDING)" != ""; then \
+ flags="--fsfs-sharding $(FSFS_SHARDING) $$flags"; \
+ fi; \
+ if test "$(FSFS_PACKING)" != ""; then \
+ flags="--fsfs-packing $$flags"; \
+ fi; \
+ if test "$(PARALLEL)" != ""; then \
+ flags="--parallel $$flags"; \
+ fi; \
+ if test "$(LOG_TO_STDOUT)" != ""; then \
+ flags="--log-to-stdout $$flags"; \
+ fi; \
+ if test "$(MILESTONE_FILTER)" != ""; then \
+ flags="--list --milestone-filter=$(MILESTONE_FILTER) \
+ --mode-filter=$(MODE_FILTER) --log-to-stdout $$flags"; \
+ fi; \
+ if test "$(SET_LOG_LEVEL)" != ""; then \
+ flags="--set-log-level $(SET_LOG_LEVEL) $$flags"; \
+ fi; \
+ if test "$(SSL_CERT)" != ""; then \
+ flags="--ssl-cert $(SSL_CERT) $$flags"; \
+ fi; \
+ if test "$(HTTP_PROXY)" != ""; then \
+ flags="--http-proxy $(HTTP_PROXY) $$flags"; \
+ fi; \
+ LD_LIBRARY_PATH='$(auth_plugin_dirs):$(LD_LIBRARY_PATH)' \
+ $(PYTHON) $(top_srcdir)/build/run_tests.py \
+ --config-file $(top_srcdir)/subversion/tests/tests.conf \
+ $$flags \
+ '$(abs_srcdir)' '$(abs_builddir)' $(TESTS); \
+ else \
+ echo "make check: Python 2.5 or greater is required,"; \
+ echo " but was not detected during configure"; \
+ exit 1; \
+ fi;
+
+# First, set up Apache as documented in
+# subversion/tests/cmdline/README.
+davcheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@ apache-mod
+ @$(MAKE) check BASE_URL=http://localhost
+
+# Automatically configure and run Apache httpd on a random port, and then
+# run make check.
+davautocheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@ apache-mod
+ @# Takes MODULE_PATH, USE_HTTPV1 and SVN_PATH_AUTHZ in the environment.
+ @APXS=$(APXS) $(top_srcdir)/subversion/tests/cmdline/davautocheck.sh
+
+# First, run:
+# subversion/svnserve/svnserve -d -r `pwd`/subversion/tests/cmdline
+svncheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@
+ @$(MAKE) check BASE_URL=svn://127.0.0.1
+
+# 'make svnserveautocheck' runs svnserve for you and kills it.
+svnserveautocheck: svnserve bin $(TEST_DEPS) @BDB_TEST_DEPS@
+ @env PYTHON=$(PYTHON) THREADED=$(THREADED) \
+ $(top_srcdir)/subversion/tests/cmdline/svnserveautocheck.sh
+
+# First, run:
+# subversion/svnserve/svnserve --listen-host "::1" -d -r `pwd`/subversion/tests/cmdline
+
+svncheck6: bin $(TEST_DEPS) @BDB_TEST_DEPS@
+ @$(MAKE) check BASE_URL=svn://\[::1\]
+
+# First make sure you can ssh to localhost and that "svnserve" is in
+# the path of the resulting shell.
+svnsshcheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@
+ @$(MAKE) check \
+ BASE_URL=svn+ssh://localhost`pwd`/subversion/tests/cmdline
+
+bdbcheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@
+ @$(MAKE) check FS_TYPE=bdb
+
+# Create an execution coverage report from the data collected during
+# all execution since the last reset.
+gcov:
+ lcov --capture -d . -b . -o gcov-lcov.dat > gcov-lcov.log
+ genhtml gcov-lcov.dat -o gcov-report > gcov-genhtml.log
+
+# Reset all execution coverage counters to zero.
+gcov-reset:
+ lcov --zerocounters -d .
+
+# Remove the execution coverage data and the report.
+gcov-clean:
+ rm -f gcov-lcov.dat gcov-lcov.log gcov-genhtml.log
+ rm -rf gcov-report
+ find . -name "*.gcda" -o -name "*.gcno" -print0 | xargs -0 rm -f --
+
+check-clean: gcov-clean
+ if [ -d subversion/tests/cmdline/svn-test-work ]; then \
+ find subversion/tests/cmdline/svn-test-work -mindepth 1 -maxdepth 1 \
+ -print0 | xargs -0 rm -rf --; \
+ fi
+ rm -rf subversion/tests/libsvn_fs/test-repo-* \
+ subversion/tests/libsvn_fs_base/test-repo-* \
+ subversion/tests/libsvn_fs_fs/test-repo-* \
+ subversion/tests/libsvn_ra_local/test-repo-* \
+ subversion/tests/libsvn_repos/test-repo-* \
+ subversion/tests/libsvn_subr/z \
+ subversion/tests/libsvn_wc/fake-wc \
+ subversion/tests/libsvn_client/test-patch* \
+ subversion/tests/libsvn_client/test-*/ \
+ subversion/tests/libsvn_diff/B2 \
+ subversion/tests/libsvn_diff/T1 \
+ subversion/tests/libsvn_diff/T2 \
+ subversion/tests/libsvn_diff/T3 \
+ subversion/tests/svnserveautocheck.pid \
+ tests.log fails.log
+
+mkdir-init:
+ @list='$(BUILD_DIRS) $(SCHEMA_DIR) doc'; \
+ for i in $$list; do \
+ if [ ! -d $$i ]; then \
+ $(MKDIR) $$i ; \
+ fi; \
+ done
+
+# DOCUMENTATION RULES
+
+# Every single document in every format.
+doc: doc-api doc-javahl
+
+# Generate API documentation for the C libraries.
+### This could also generate POD for swig-perl, etc.
+doc-api: mkdir-init
+ ( cd $(top_srcdir) && \
+ sed "s,\(OUTPUT_DIRECTORY *= *\),\1$(abs_builddir)/," \
+ doc/doxygen.conf | $(DOXYGEN) - )
+
+# Generate API documentation for the JavaHL package.
+doc-javahl:
+ $(JAVADOC) -d $(abs_builddir)/doc/javadoc \
+ -sourcepath $(top_srcdir)/subversion/bindings/javahl/src \
+ -link http://java.sun.com/javase/6/docs/api/ \
+ org.tigris.subversion.javahl \
+ org.apache.subversion.javahl \
+ org.apache.subversion.javahl.callback \
+ org.apache.subversion.javahl.types
+
+doc-clean:
+ rm -rf $(top_srcdir)/doc/doxygen
+ rm -rf $(top_srcdir)/doc/javadoc
+
+# Converting from the .rnc XML shcemas to various other schema formats.
+SCHEMAS_DTD = $(SCHEMA_DIR)/blame.dtd $(SCHEMA_DIR)/info.dtd \
+ $(SCHEMA_DIR)/list.dtd $(SCHEMA_DIR)/log.dtd \
+ $(SCHEMA_DIR)/status.dtd $(SCHEMA_DIR)/props.dtd
+
+SCHEMAS_RNG = $(SCHEMA_DIR)/blame.rng $(SCHEMA_DIR)/info.rng \
+ $(SCHEMA_DIR)/list.rng $(SCHEMA_DIR)/log.rng \
+ $(SCHEMA_DIR)/status.rng $(SCHEMA_DIR)/props.rng
+
+SCHEMAS_XSD = $(SCHEMA_DIR)/blame.xsd $(SCHEMA_DIR)/info.xsd \
+ $(SCHEMA_DIR)/list.xsd $(SCHEMA_DIR)/log.xsd \
+ $(SCHEMA_DIR)/status.xsd $(SCHEMA_DIR)/props.xsd
+
+schema: schema-rng schema-dtd schema-xsd
+
+schema-rng: $(SCHEMAS_RNG)
+schema-dtd: $(SCHEMAS_DTD)
+schema-xsd: $(SCHEMAS_XSD)
+
+$(SCHEMAS_RNG) $(SCHEMAS_DTD) $(SCHEMAS_XSD): $(SCHEMA_DIR)/common.rnc
+
+schema-clean:
+ (cd $(SCHEMA_DIR) && rm -f *.rng *.dtd *.xsd)
+
+#
+# Implicit rules for creating outputs from input files
+#
+.SUFFIXES:
+.SUFFIXES: .c .cpp .lo .o .la-a .la \
+ .po .spo .mo .rnc .rng .dtd .xsd .sql .h
+
+.sql.h:
+ $(PYTHON) $(top_srcdir)/build/transform_sql.py $< $(top_srcdir)/$@
+
+.c.o:
+ $(COMPILE) -o $@ -c $<
+
+.cpp.o:
+ $(COMPILE_CXX) -o $@ -c $<
+
+.c.lo:
+ $(LT_COMPILE) -o $@ -c $<
+
+.cpp.lo:
+ $(LT_COMPILE_CXX) -o $@ -c $<
+
+.la.la-a:
+ sed "/library_names/s/'.*'/''/" $< > $@
+
+
+# Strip the Content-Type: header from the po file if we don't have a
+# gettext that supports bind_textdomain_codeset, so it doesn't try
+# to convert our UTF-8 .po files to the locale encoding.
+@NO_GETTEXT_CODESET@.po.spo:
+@NO_GETTEXT_CODESET@ sed \
+@NO_GETTEXT_CODESET@ '/^"Content-Type: text\/plain; charset=UTF-8\\n"$$/d' \
+@NO_GETTEXT_CODESET@ $< > $@
+
+@NO_GETTEXT_CODESET@.spo.mo:
+@NO_GETTEXT_CODESET@ $(MSGFMT) $(MSGFMTFLAGS) -o $@ $<
+
+# For systems with bind_textdomain_codeset, just leave the Content-Type:
+# header alone.
+@GETTEXT_CODESET@.po.mo:
+@GETTEXT_CODESET@ $(MSGFMT) $(MSGFMTFLAGS) -o $@ $<
+
+.rnc.rng:
+ @TRANG@ $< $@
+
+.rnc.dtd:
+ @TRANG@ $< $@
+
+.rnc.xsd:
+ @TRANG@ $< $@
+
+install-docs: install-man
+
+manroot = $(mandir)/man
+install-man:
+ @list='$(MANPAGES)'; \
+ for i in $$list; do \
+ if test -f $(srcdir)/$$i; then file=$(srcdir)/$$i; \
+ else file=$$i; fi; \
+ ext=`echo $$i | sed -e 's/^.*\\.//'`; \
+ $(MKDIR) $(DESTDIR)$(manroot)$$ext; \
+ inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \
+ inst=`echo $$inst | sed -e 's/^.*\///'`; \
+ inst=`echo $$inst`.$$ext; \
+ echo "$(INSTALL_DATA) $$file $(DESTDIR)$(manroot)$$ext/$$inst"; \
+ $(INSTALL_DATA) $$file $(DESTDIR)$(manroot)$$ext/$$inst; \
+ done
+
+install-swig-py: install-swig-py-lib
+install-swig-rb: install-swig-rb-lib
+
+clean-bindings: clean-swig clean-ctypes-python clean-javahl
+
+extraclean-bindings: clean-swig extraclean-swig-headers \
+ extraclean-swig-py extraclean-swig-rb \
+ extraclean-swig-pl \
+ clean-ctypes-python clean-javahl \
+
+clean-swig: clean-swig-headers clean-swig-py clean-swig-rb clean-swig-pl
+ @rm -f .swig_checked
+
+EXTRACLEAN_SWIG_HEADERS=rm -f $(SWIG_SRC_DIR)/proxy/*.swg
+
+clean-swig-headers:
+ if test -z "$(RELEASE_MODE)"; then \
+ $(EXTRACLEAN_SWIG_HEADERS); \
+ fi
+
+extraclean-swig-headers: clean-swig-headers
+ $(EXTRACLEAN_SWIG_HEADERS)
+
+$(SWIG_PL_DIR)/native/Makefile.PL: $(SWIG_SRC_DIR)/perl/native/Makefile.PL.in
+ ./config.status subversion/bindings/swig/perl/native/Makefile.PL
+
+$(SWIG_PL_DIR)/native/Makefile: $(SWIG_PL_DIR)/native/Makefile.PL
+ cd $(SWIG_PL_DIR)/native; $(PERL) Makefile.PL
+
+# There is a "readlink -f" command on some systems for the same purpose,
+# but it's not as portable (e.g. Mac OS X doesn't have it). These should
+# only be used where Python/Perl are known to be available.
+READLINK_PY=$(PYTHON) -c 'import sys,os; print(os.path.realpath(sys.argv[1]))'
+READLINK_PL=$(PERL) -e 'use Cwd; print Cwd::realpath(shift)'
+
+swig-pl_DEPS = autogen-swig-pl libsvn_swig_perl \
+ $(SWIG_PL_DIR)/native/Makefile
+swig-pl: $(swig-pl_DEPS)
+ if test "`$(READLINK_PL) $(SWIG_PL_DIR)`" != "`$(READLINK_PL) $(SWIG_PL_SRC_DIR)`"; then \
+ ln -sf $(SWIG_PL_SRC_DIR)/native/*.c $(SWIG_PL_DIR)/native; \
+ fi
+ cd $(SWIG_PL_DIR)/native; $(MAKE) OPTIMIZE="" OTHERLDFLAGS="$(SWIG_LDFLAGS)"
+
+check-swig-pl: swig-pl swig-pl-lib
+ cd $(SWIG_PL_DIR)/native; $(MAKE) test
+
+install-swig-pl: swig-pl install-swig-pl-lib
+ cd $(SWIG_PL_DIR)/native; $(MAKE) install
+
+EXTRACLEAN_SWIG_PL=rm -f $(SWIG_PL_SRC_DIR)/native/svn_*.c \
+ $(SWIG_PL_SRC_DIR)/native/core.c
+
+# Running Makefile.PL at this point *fails* (cannot find ..../.libs) so if the
+# Makefile does not exist, DO NOT try to make it. But, if it doesn't exist,
+# then the directory is probably clean anyway.
+clean-swig-pl:
+ if test -z "$(RELEASE_MODE)"; then \
+ $(EXTRACLEAN_SWIG_PL); \
+ fi
+ for d in $(SWIG_PL_DIR)/libsvn_swig_perl; \
+ do \
+ cd $$d; \
+ rm -rf *.lo *.la *.o .libs; \
+ done
+ if [ -f "$(SWIG_PL_DIR)/native/Makefile" ]; then \
+ cd $(SWIG_PL_DIR)/native; $(MAKE) clean; \
+ fi
+
+extraclean-swig-pl: clean-swig-pl
+ $(EXTRACLEAN_SWIG_PL)
+
+$(SWIG_PY_DIR)/libsvn:
+ mkdir $(SWIG_PY_DIR)/libsvn
+
+copy-swig-py: autogen-swig-py $(SWIG_PY_DIR)/libsvn
+ @for f in $(SWIG_PY_SRC_DIR)/*.py $(SWIG_PY_DIR)/*.py; do \
+ ! [ -f "$$f" ] || cp -pf $$f $(SWIG_PY_DIR)/libsvn; \
+ done
+ @touch $(SWIG_PY_DIR)/libsvn/__init__.py
+
+swig-py: autogen-swig-py copy-swig-py
+
+check-swig-py: swig-py
+ $(TEST_SHLIB_VAR_SWIG_PY) \
+ cd $(SWIG_PY_DIR); \
+ $(PYTHON) $(SWIG_PY_SRC_DIR)/tests/run_all.py
+
+EXTRACLEAN_SWIG_PY=rm -rf $(SWIG_PY_SRC_DIR)/svn_*.c $(SWIG_PY_SRC_DIR)/core.c \
+ $(SWIG_PY_SRC_DIR)/[a-z]*.py
+clean-swig-py:
+ rm -rf $(SWIG_PY_DIR)/libsvn
+ if test -z "$(RELEASE_MODE)"; then \
+ $(EXTRACLEAN_SWIG_PY); \
+ fi
+ for d in $(SWIG_PY_DIR) $(SWIG_PY_DIR)/libsvn_swig_py; \
+ do \
+ cd $$d && rm -rf *.lo *.la *.o *.pyc .libs; \
+ done
+ find $(SWIG_PY_SRC_DIR) $(SWIG_PY_DIR) -name "*.pyc" -exec rm {} ';'
+
+extraclean-swig-py: clean-swig-py
+ $(EXTRACLEAN_SWIG_PY)
+
+swig-rb: autogen-swig-rb
+
+check-swig-rb: swig-rb svnserve
+ $(TEST_SHLIB_VAR_SWIG_RB) \
+ cd $(SWIG_RB_DIR); \
+ if [ "$(RUBY_MAJOR)" -eq 1 -a "$(RUBY_MINOR)" -lt 9 ] ; then \
+ $(RUBY) -I $(SWIG_RB_SRC_DIR) \
+ $(SWIG_RB_SRC_DIR)/test/run-test.rb \
+ --verbose=$(SWIG_RB_TEST_VERBOSE); \
+ else \
+ $(RUBY) -I $(SWIG_RB_SRC_DIR) \
+ $(SWIG_RB_SRC_DIR)/test/run-test.rb; \
+ fi
+
+EXTRACLEAN_SWIG_RB=rm -f $(SWIG_RB_SRC_DIR)/svn_*.c $(SWIG_RB_SRC_DIR)/core.c
+
+clean-swig-rb:
+ rm -rf $(SWIG_RB_DIR)/test/repos $(SWIG_RB_DIR)/test/wc
+ if test -z "$(RELEASE_MODE)"; then \
+ $(EXTRACLEAN_SWIG_RB); \
+ fi
+ for d in $(SWIG_RB_DIR) $(SWIG_RB_DIR)/libsvn_swig_ruby; \
+ do \
+ cd $$d; \
+ rm -rf *.lo *.la *.o .libs; \
+ done
+
+extraclean-swig-rb: clean-swig-rb
+ $(EXTRACLEAN_SWIG_RB)
+
+install-swig-rb-doc:
+ $(RDOC) --all --ri --op "$(SWIG_RB_RI_DATADIR)" "$(SWIG_RB_SRC_DIR)/svn"
+
+# ctypes-python make targets
+ctypes-python: local-all
+ $(SHELL) $(abs_srcdir)/build/run_ctypesgen.sh "$(LT_EXECUTE)" "$(CPPFLAGS)" "$(EXTRA_CTYPES_LDFLAGS)" "$(PYTHON)" "$(CTYPESGEN)" "$(abs_srcdir)" "$(abs_builddir)" "$(libdir)" "$(SVN_APR_CONFIG)" "$(SVN_APRUTIL_CONFIG)"
+
+install-ctypes-python: ctypes-python
+ cd $(CTYPES_PYTHON_SRC_DIR); \
+ $(PYTHON) setup.py install --prefix="$(DESTDIR)$(prefix)"
+
+check-ctypes-python: ctypes-python
+ cd $(CTYPES_PYTHON_SRC_DIR); \
+ $(LT_EXECUTE) $(PYTHON) test/run_all.py
+
+# If any of those files exists, run ctypes' 'setup.py clean'. We don't run
+# it otherwise because it spams stdout+stderr; see r1479326.
+clean-ctypes-python:
+ cd $(CTYPES_PYTHON_SRC_DIR); \
+ for b in build csvn/core/functions.py svn_all.py svn_all2.py ; do \
+ if [ -e "$$b" ] ; then \
+ $(PYTHON) setup.py clean --all; \
+ break; \
+ fi; \
+ done
+
+# manually describe a dependency, which we won't otherwise detect
+subversion/libsvn_wc/wc-queries.h: $(abs_srcdir)/subversion/libsvn_wc/wc-metadata.sql
+subversion/libsvn_wc/wc-queries.h: $(abs_srcdir)/subversion/libsvn_wc/wc-checks.sql
+
+# Compatibility symlink.
+# This runs after the target of the same name in build-outputs.mk.
+INSTALL_EXTRA_TOOLS=\
+ $(MKDIR) $(DESTDIR)$(bindir); \
+ test -n "$$SVN_SVNMUCC_IS_SVNSYITF" && \
+ ln -sf svnmucc$(EXEEXT) $(DESTDIR)$(bindir)/svnsyitf$(EXEEXT); \
+ if test "$(DESTDIR)$(bindir)" != "$(DESTDIR)$(toolsdir)"; then \
+ ln -sf $(DESTDIR)$(bindir)/svnmucc$(EXEEXT) $(DESTDIR)$(toolsdir)/svnmucc$(EXEEXT); \
+ fi
diff --git a/contrib/subversion/NOTICE b/contrib/subversion/NOTICE
new file mode 100644
index 0000000..4450bc3f
--- /dev/null
+++ b/contrib/subversion/NOTICE
@@ -0,0 +1,23 @@
+Subversion
+Copyright 2010 The Apache Software Foundation
+
+This product includes software developed by many people, and distributed
+under Contributor License Agreements to The Apache Software Foundation
+(http://www.apache.org/). See the accompanying COMMITTERS file and the
+revision logs for an exact contribution history.
+
+Portions of the test suite for Subversion's Python bindings are copyrighted
+by Edgewall Software, Jonas Borgström and Christopher Lenz.
+For more information, see LICENSE.
+
+This product includes software developed under the X Consortium License
+see: build/install-sh
+
+This product includes software developed by Markus Kuhn under a permissive
+license, see LICENSE.
+
+This software contains code derived from the RSA Data Security
+Inc. MD5 Message-Digest Algorithm, including various
+modifications by Spyglass Inc., Carnegie Mellon University, and
+Bell Communications Research, Inc (Bellcore).
+
diff --git a/contrib/subversion/README b/contrib/subversion/README
new file mode 100644
index 0000000..ae91c90
--- /dev/null
+++ b/contrib/subversion/README
@@ -0,0 +1,84 @@
+
+ Subversion, a version control system.
+ =====================================
+
+$LastChangedDate: 2012-02-10 14:58:53 +0000 (Fri, 10 Feb 2012) $
+
+Contents:
+
+ I. A FEW POINTERS
+ II. DOCUMENTATION
+ III. PARTICIPATING IN THE SUBVERSION COMMUNITY
+ IV. QUICKSTART GUIDE
+ V. CONVERTING FROM CVS
+
+
+
+I. A FEW POINTERS
+
+ For an overview of the Subversion project, visit
+
+ http://subversion.apache.org/
+
+ Once you have a Subversion client you can get the latest version
+ of the code with the command:
+
+ $ svn co http://svn.apache.org/repos/asf/subversion/trunk subversion
+
+
+
+II. DOCUMENTATION
+
+ The main documentation is the Subversion Book - an on-line version
+ can be found at:
+
+ http://svnbook.red-bean.com/
+
+ It is written in DocBook XML, and the sources can be found at:
+
+ http://svnbook.googlecode.com/svn/trunk/
+
+ If you wish to build the documentation from source, read the
+ src/en/README file within the book source.
+
+
+
+III. PARTICIPATING IN THE SUBVERSION COMMUNITY
+
+ First, read http://subversion.apache.org/docs/community-guide/
+ It describes Subversion coding and log message standards, as well
+ as how to join discussion lists.
+
+ Talk on IRC with developers: irc.freenode.net, channel #svn-dev.
+
+ Read the FAQ: http://subversion.apache.org/faq.html
+
+
+
+IV. QUICKSTART GUIDE
+
+ See the final section of the first chapter of the Subversion Book.
+
+
+
+V. CONVERTING FROM CVS
+
+ If you're a CVS user trying to move your CVS history over to
+ Subversion, then be sure to visit the 'cvs2svn' project:
+
+ http://cvs2svn.tigris.org
+
+ You can get the latest released version of the cvs2svn converter
+ from the project downloads area:
+
+ http://cvs2svn.tigris.org/servlets/ProjectDocumentList?folderID=2976
+
+ Please note that the cvs2svn project is a *separate* project from
+ Subversion. If you have problems with cvs2svn or are confused,
+ please email the cvs2svn project's mailing lists, not the
+ Subversion lists.
+
+ Finally, be sure to see Appendix B in the Subversion Book. It
+ contains a very quick overview of the major differences between
+ CVS and Subversion.
+
diff --git a/contrib/subversion/aclocal.m4 b/contrib/subversion/aclocal.m4
new file mode 100644
index 0000000..d92e4a2
--- /dev/null
+++ b/contrib/subversion/aclocal.m4
@@ -0,0 +1,55 @@
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# aclocal.m4: Supplementary macros used by Subversion's configure.ac
+#
+# These are here rather than directly in configure.ac, since this prevents
+# comments in the macro files being copied into configure.ac, producing
+# useless bloat. (This is significant - a 12kB reduction in size!)
+
+# Include macros distributed by the APR project
+sinclude(build/ac-macros/find_apr.m4)
+sinclude(build/ac-macros/find_apu.m4)
+
+# Include Subversion's own custom macros
+sinclude(build/ac-macros/svn-macros.m4)
+
+sinclude(build/ac-macros/apache.m4)
+sinclude(build/ac-macros/apr.m4)
+sinclude(build/ac-macros/aprutil.m4)
+sinclude(build/ac-macros/apr_memcache.m4)
+sinclude(build/ac-macros/berkeley-db.m4)
+sinclude(build/ac-macros/compiler.m4)
+sinclude(build/ac-macros/ctypesgen.m4)
+sinclude(build/ac-macros/java.m4)
+sinclude(build/ac-macros/sasl.m4)
+sinclude(build/ac-macros/serf.m4)
+sinclude(build/ac-macros/sqlite.m4)
+sinclude(build/ac-macros/swig.m4)
+sinclude(build/ac-macros/zlib.m4)
+sinclude(build/ac-macros/kwallet.m4)
+sinclude(build/ac-macros/macosx.m4)
+
+# Include the libtool macros
+sinclude(build/libtool.m4)
+sinclude(build/ltoptions.m4)
+sinclude(build/ltsugar.m4)
+sinclude(build/ltversion.m4)
+sinclude(build/lt~obsolete.m4)
diff --git a/contrib/subversion/autogen.sh b/contrib/subversion/autogen.sh
new file mode 100755
index 0000000..b8ba406
--- /dev/null
+++ b/contrib/subversion/autogen.sh
@@ -0,0 +1,210 @@
+#!/bin/sh
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+
+### Run this to produce everything needed for configuration. ###
+
+
+# Run tests to ensure that our build requirements are met
+RELEASE_MODE=""
+RELEASE_ARGS=""
+SKIP_DEPS=""
+while test $# != 0; do
+ case "$1" in
+ --release)
+ RELEASE_MODE="$1"
+ RELEASE_ARGS="--release"
+ shift
+ ;;
+ -s)
+ SKIP_DEPS="yes"
+ shift
+ ;;
+ --) # end of option parsing
+ break
+ ;;
+ *)
+ echo "invalid parameter: '$1'"
+ exit 1
+ ;;
+ esac
+done
+# ### The order of parameters is important; buildcheck.sh depends on it and
+# ### we don't want to copy the fancy option parsing loop there. For the
+# ### same reason, all parameters should be quoted, so that buildcheck.sh
+# ### sees an empty arg rather than missing one.
+./build/buildcheck.sh "$RELEASE_MODE" || exit 1
+
+# Handle some libtool helper files
+#
+# ### eventually, we can/should toss this in favor of simply using
+# ### APR's libtool. deferring to a second round of change...
+#
+
+libtoolize="`./build/PrintPath glibtoolize libtoolize libtoolize15`"
+lt_major_version=`$libtoolize --version 2>/dev/null | sed -e 's/^[^0-9]*//' -e 's/\..*//' -e '/^$/d' -e 1q`
+
+if [ "x$libtoolize" = "x" ]; then
+ echo "libtoolize not found in path"
+ exit 1
+fi
+
+rm -f build/config.guess build/config.sub
+$libtoolize --copy --automake --force
+
+ltpath="`dirname $libtoolize`"
+ltfile=${LIBTOOL_M4-`cd $ltpath/../share/aclocal ; pwd`/libtool.m4}
+
+if [ ! -f $ltfile ]; then
+ echo "$ltfile not found (try setting the LIBTOOL_M4 environment variable)"
+ exit 1
+fi
+
+echo "Copying libtool helper: $ltfile"
+# An ancient helper might already be present from previous builds,
+# and it might be write-protected (e.g. mode 444, seen on FreeBSD).
+# This would cause cp to fail and print an error message, but leave
+# behind a potentially outdated libtool helper. So, remove before
+# copying:
+rm -f build/libtool.m4
+cp $ltfile build/libtool.m4
+
+for file in ltoptions.m4 ltsugar.m4 ltversion.m4 lt~obsolete.m4; do
+ rm -f build/$file
+
+ if [ $lt_major_version -ge 2 ]; then
+ ltfile=${LIBTOOL_M4-`cd $ltpath/../share/aclocal ; pwd`/$file}
+
+ if [ ! -f $ltfile ]; then
+ echo "$ltfile not found (try setting the LIBTOOL_M4 environment variable)"
+ exit 1
+ fi
+
+ echo "Copying libtool helper: $ltfile"
+ cp $ltfile build/$file
+ fi
+done
+
+if [ $lt_major_version -ge 2 ]; then
+ for file in config.guess config.sub; do
+ configfile=${LIBTOOL_CONFIG-`cd $ltpath/../share/libtool/config ; pwd`/$file}
+
+ if [ ! -f $configfile ]; then
+ echo "$configfile not found (try setting the LIBTOOL_CONFIG environment variable)"
+ exit 1
+ fi
+
+ cp $configfile build/$file
+ done
+fi
+
+# Create the file detailing all of the build outputs for SVN.
+#
+# Note: this dependency on Python is fine: only SVN developers use autogen.sh
+# and we can state that dev people need Python on their machine. Note
+# that running gen-make.py requires Python 2.5 or newer.
+
+PYTHON="`./build/find_python.sh`"
+if test -z "$PYTHON"; then
+ echo "Python 2.5 or later is required to run autogen.sh"
+ echo "If you have a suitable Python installed, but not on the"
+ echo "PATH, set the environment variable PYTHON to the full path"
+ echo "to the Python executable, and re-run autogen.sh"
+ exit 1
+fi
+
+# Compile SWIG headers into standalone C files if we are in release mode
+if test -n "$RELEASE_MODE"; then
+ echo "Generating SWIG code..."
+ # Generate build-outputs.mk in non-release-mode, so that we can
+ # build the SWIG-related files
+ "$PYTHON" ./gen-make.py build.conf || gen_failed=1
+
+ # Build the SWIG-related files
+ make -f autogen-standalone.mk autogen-swig
+
+ # Remove the .swig_checked file
+ rm -f .swig_checked
+fi
+
+if test -n "$SKIP_DEPS"; then
+ echo "Creating build-outputs.mk (no dependencies)..."
+ "$PYTHON" ./gen-make.py $RELEASE_ARGS -s build.conf || gen_failed=1
+else
+ echo "Creating build-outputs.mk..."
+ "$PYTHON" ./gen-make.py $RELEASE_ARGS build.conf || gen_failed=1
+fi
+
+if test -n "$RELEASE_MODE"; then
+ find build/ -name '*.pyc' -exec rm {} \;
+fi
+
+rm autogen-standalone.mk
+
+if test -n "$gen_failed"; then
+ echo "ERROR: gen-make.py failed"
+ exit 1
+fi
+
+# Produce config.h.in
+echo "Creating svn_private_config.h.in..."
+${AUTOHEADER:-autoheader}
+
+# If there's a config.cache file, we may need to delete it.
+# If we have an existing configure script, save a copy for comparison.
+if [ -f config.cache ] && [ -f configure ]; then
+ cp configure configure.$$.tmp
+fi
+
+# Produce ./configure
+echo "Creating configure..."
+${AUTOCONF:-autoconf}
+
+# If we have a config.cache file, toss it if the configure script has
+# changed, or if we just built it for the first time.
+if [ -f config.cache ]; then
+ (
+ [ -f configure.$$.tmp ] && cmp configure configure.$$.tmp > /dev/null 2>&1
+ ) || (
+ echo "Tossing config.cache, since configure has changed."
+ rm config.cache
+ )
+ rm -f configure.$$.tmp
+fi
+
+# Remove autoconf 2.5x's cache directory
+rm -rf autom4te*.cache
+
+echo ""
+echo "You can run ./configure now."
+echo ""
+echo "Running autogen.sh implies you are a maintainer. You may prefer"
+echo "to run configure in one of the following ways:"
+echo ""
+echo "./configure --enable-maintainer-mode"
+echo "./configure --disable-shared"
+echo "./configure --enable-maintainer-mode --disable-shared"
+echo "./configure --disable-optimize --enable-debug"
+echo "./configure CUSERFLAGS='--flags-for-C' CXXUSERFLAGS='--flags-for-C++'"
+echo ""
+echo "Note: If you wish to run a Subversion HTTP server, you will need"
+echo "Apache 2.x. See the INSTALL file for details."
+echo ""
diff --git a/contrib/subversion/build-outputs.mk b/contrib/subversion/build-outputs.mk
new file mode 100644
index 0000000..7bee70d
--- /dev/null
+++ b/contrib/subversion/build-outputs.mk
@@ -0,0 +1,2894 @@
+# DO NOT EDIT -- AUTOMATICALLY GENERATED BY build/generator/gen_make.py
+# FROM build/generator/templates/build-outputs.mk.ezt
+
+########################################
+# Section 1: Global make variables
+########################################
+
+FS_BASE_DEPS = subversion/libsvn_fs_base/libsvn_fs_base-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la
+FS_BASE_LINK = ../../subversion/libsvn_fs_base/libsvn_fs_base-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la ../../subversion/libsvn_fs_util/libsvn_fs_util-1.la
+
+FS_FS_DEPS = subversion/libsvn_fs_fs/libsvn_fs_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la
+FS_FS_LINK = ../../subversion/libsvn_fs_fs/libsvn_fs_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la ../../subversion/libsvn_fs_util/libsvn_fs_util-1.la
+
+RA_LOCAL_DEPS = subversion/libsvn_ra_local/libsvn_ra_local-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+RA_LOCAL_LINK = ../../subversion/libsvn_ra_local/libsvn_ra_local-1.la ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la
+
+RA_SERF_DEPS = subversion/libsvn_ra_serf/libsvn_ra_serf-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+RA_SERF_LINK = ../../subversion/libsvn_ra_serf/libsvn_ra_serf-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la
+
+RA_SVN_DEPS = subversion/libsvn_ra_svn/libsvn_ra_svn-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+RA_SVN_LINK = ../../subversion/libsvn_ra_svn/libsvn_ra_svn-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la
+
+BUILD_DIRS = subversion/tests/cmdline subversion/tests/libsvn_subr subversion/tests/libsvn_fs_base subversion/tests/libsvn_client subversion/tests/libsvn_wc subversion/bindings/cxxhl subversion/bindings/cxxhl/tests tools/diff subversion/tests/libsvn_diff subversion/tests/libsvn_fs_fs subversion/tests/libsvn_fs tools/dev tools/server-side subversion/bindings/javahl/src/org/apache/subversion/javahl/callback subversion/bindings/javahl/classes subversion/bindings/javahl/include subversion/bindings/javahl/src/org/tigris/subversion/javahl subversion/bindings/javahl/tests/org/tigris/subversion/javahl subversion/bindings/javahl/src/org/apache/subversion/javahl subversion/bindings/javahl/src/org/apache/subversion/javahl/types subversion/bindings/javahl/tests/org/apache/subversion/javahl subversion/libsvn_auth_gnome_keyring subversion/libsvn_auth_kwallet subversion/libsvn_client subversion/libsvn_delta subversion/libsvn_diff subversion/libsvn_fs subversion/libsvn_fs_base subversion/libsvn_fs_base/bdb subversion/libsvn_fs_base/util subversion/libsvn_fs_fs subversion/libsvn_fs_util subversion/libsvn_ra subversion/libsvn_ra_local subversion/libsvn_ra_serf subversion/libsvn_ra_svn subversion/libsvn_repos subversion/libsvn_subr subversion/bindings/swig/perl/libsvn_swig_perl subversion/bindings/swig/python/libsvn_swig_py subversion/bindings/swig/ruby/libsvn_swig_ruby subversion/tests subversion/libsvn_wc subversion/bindings/cxxhl/src subversion/bindings/javahl/native subversion/po subversion/mod_authz_svn subversion/mod_dav_svn subversion/mod_dav_svn/reports subversion/mod_dav_svn/posts tools/server-side/mod_dontdothat subversion/tests/libsvn_ra_local subversion/tests/libsvn_ra subversion/tests/libsvn_delta subversion/tests/libsvn_repos subversion/svn tools/client-side/svn-bench subversion/svnadmin subversion/svndumpfilter subversion/svnlook subversion/svnmucc tools/dev/svnraisetreeconflict subversion/svnrdump subversion/svnserve subversion/svnsync subversion/svnversion subversion/bindings/swig subversion/bindings/swig/python subversion/bindings/swig/perl subversion/bindings/swig/ruby subversion/bindings/swig/proxy
+
+BDB_TEST_DEPS = subversion/tests/libsvn_fs_base/changes-test$(EXEEXT) subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT) subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT)
+
+BDB_TEST_PROGRAMS = subversion/tests/libsvn_fs_base/changes-test$(EXEEXT) subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT) subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT)
+
+TEST_DEPS = subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT) subversion/tests/libsvn_subr/auth-test$(EXEEXT) subversion/tests/libsvn_subr/cache-test$(EXEEXT) subversion/tests/libsvn_subr/checksum-test$(EXEEXT) subversion/tests/libsvn_client/client-test$(EXEEXT) subversion/tests/libsvn_subr/compat-test$(EXEEXT) subversion/tests/libsvn_subr/config-test$(EXEEXT) subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT) subversion/tests/libsvn_subr/crypto-test$(EXEEXT) subversion/tests/libsvn_wc/db-test$(EXEEXT) subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT) subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT) subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT) subversion/tests/cmdline/entries-dump$(EXEEXT) subversion/tests/libsvn_subr/error-code-test$(EXEEXT) subversion/tests/libsvn_subr/error-test$(EXEEXT) subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT) subversion/tests/libsvn_fs/fs-test$(EXEEXT) subversion/tests/libsvn_subr/hashdump-test$(EXEEXT) subversion/tests/libsvn_subr/io-test$(EXEEXT) subversion/tests/libsvn_fs/locks-test$(EXEEXT) subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT) subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT) subversion/tests/libsvn_wc/op-depth-test$(EXEEXT) subversion/tests/libsvn_subr/opt-test$(EXEEXT) subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT) subversion/tests/libsvn_subr/path-test$(EXEEXT) subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT) subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT) subversion/tests/libsvn_ra/ra-test$(EXEEXT) subversion/tests/libsvn_delta/random-test$(EXEEXT) subversion/tests/libsvn_repos/repos-test$(EXEEXT) subversion/tests/libsvn_subr/revision-test$(EXEEXT) subversion/tests/libsvn_subr/skel-test$(EXEEXT) subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT) subversion/tests/libsvn_subr/stream-test$(EXEEXT) subversion/tests/libsvn_subr/string-test$(EXEEXT) subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT) tools/server-side/svnauthz$(EXEEXT) tools/server-side/svnauthz-validate$(EXEEXT) subversion/tests/libsvn_delta/svndiff-test$(EXEEXT) subversion/tests/libsvn_subr/time-test$(EXEEXT) subversion/tests/libsvn_subr/translate-test$(EXEEXT) subversion/tests/libsvn_subr/utf-test$(EXEEXT) subversion/tests/libsvn_delta/vdelta-test$(EXEEXT) subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT) subversion/tests/libsvn_wc/wc-test$(EXEEXT) subversion/tests/libsvn_delta/window-test$(EXEEXT) subversion/tests/cmdline/authz_tests.py subversion/tests/cmdline/autoprop_tests.py subversion/tests/cmdline/basic_tests.py subversion/tests/cmdline/blame_tests.py subversion/tests/cmdline/cat_tests.py subversion/tests/cmdline/changelist_tests.py subversion/tests/cmdline/checkout_tests.py subversion/tests/cmdline/commit_tests.py subversion/tests/cmdline/copy_tests.py subversion/tests/cmdline/depth_tests.py subversion/tests/cmdline/diff_tests.py subversion/tests/cmdline/entries_tests.py subversion/tests/cmdline/export_tests.py subversion/tests/cmdline/externals_tests.py subversion/tests/cmdline/getopt_tests.py subversion/tests/cmdline/history_tests.py subversion/tests/cmdline/import_tests.py subversion/tests/cmdline/info_tests.py subversion/tests/cmdline/input_validation_tests.py subversion/tests/cmdline/iprop_authz_tests.py subversion/tests/cmdline/iprop_tests.py subversion/tests/cmdline/lock_tests.py subversion/tests/cmdline/log_tests.py subversion/tests/cmdline/merge_authz_tests.py subversion/tests/cmdline/merge_automatic_tests.py subversion/tests/cmdline/merge_reintegrate_tests.py subversion/tests/cmdline/merge_tests.py subversion/tests/cmdline/merge_tree_conflict_tests.py subversion/tests/cmdline/mergeinfo_tests.py subversion/tests/cmdline/move_tests.py subversion/tests/cmdline/patch_tests.py subversion/tests/cmdline/prop_tests.py subversion/tests/cmdline/redirect_tests.py subversion/tests/cmdline/relocate_tests.py subversion/tests/cmdline/resolve_tests.py subversion/tests/cmdline/revert_tests.py subversion/tests/cmdline/schedule_tests.py subversion/tests/cmdline/special_tests.py subversion/tests/cmdline/stat_tests.py subversion/tests/cmdline/svnadmin_tests.py subversion/tests/cmdline/svnauthz_tests.py subversion/tests/cmdline/svndumpfilter_tests.py subversion/tests/cmdline/svnlook_tests.py subversion/tests/cmdline/svnmucc_tests.py subversion/tests/cmdline/svnrdump_tests.py subversion/tests/cmdline/svnsync_authz_tests.py subversion/tests/cmdline/svnsync_tests.py subversion/tests/cmdline/svnversion_tests.py subversion/tests/cmdline/switch_tests.py subversion/tests/cmdline/trans_tests.py subversion/tests/cmdline/tree_conflict_tests.py subversion/tests/cmdline/update_tests.py subversion/tests/cmdline/upgrade_tests.py subversion/tests/cmdline/wc_tests.py
+
+TEST_PROGRAMS = subversion/tests/libsvn_subr/auth-test$(EXEEXT) subversion/tests/libsvn_subr/cache-test$(EXEEXT) subversion/tests/libsvn_subr/checksum-test$(EXEEXT) subversion/tests/libsvn_client/client-test$(EXEEXT) subversion/tests/libsvn_subr/compat-test$(EXEEXT) subversion/tests/libsvn_subr/config-test$(EXEEXT) subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT) subversion/tests/libsvn_subr/crypto-test$(EXEEXT) subversion/tests/libsvn_wc/db-test$(EXEEXT) subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT) subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT) subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT) subversion/tests/libsvn_subr/error-code-test$(EXEEXT) subversion/tests/libsvn_subr/error-test$(EXEEXT) subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT) subversion/tests/libsvn_fs/fs-test$(EXEEXT) subversion/tests/libsvn_subr/hashdump-test$(EXEEXT) subversion/tests/libsvn_subr/io-test$(EXEEXT) subversion/tests/libsvn_fs/locks-test$(EXEEXT) subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT) subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT) subversion/tests/libsvn_wc/op-depth-test$(EXEEXT) subversion/tests/libsvn_subr/opt-test$(EXEEXT) subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT) subversion/tests/libsvn_subr/path-test$(EXEEXT) subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT) subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT) subversion/tests/libsvn_ra/ra-test$(EXEEXT) subversion/tests/libsvn_delta/random-test$(EXEEXT) subversion/tests/libsvn_repos/repos-test$(EXEEXT) subversion/tests/libsvn_subr/revision-test$(EXEEXT) subversion/tests/libsvn_subr/skel-test$(EXEEXT) subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT) subversion/tests/libsvn_subr/stream-test$(EXEEXT) subversion/tests/libsvn_subr/string-test$(EXEEXT) subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT) subversion/tests/libsvn_subr/time-test$(EXEEXT) subversion/tests/libsvn_subr/translate-test$(EXEEXT) subversion/tests/libsvn_subr/utf-test$(EXEEXT) subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT) subversion/tests/libsvn_wc/wc-test$(EXEEXT) subversion/tests/libsvn_delta/window-test$(EXEEXT) subversion/tests/cmdline/authz_tests.py subversion/tests/cmdline/autoprop_tests.py subversion/tests/cmdline/basic_tests.py subversion/tests/cmdline/blame_tests.py subversion/tests/cmdline/cat_tests.py subversion/tests/cmdline/changelist_tests.py subversion/tests/cmdline/checkout_tests.py subversion/tests/cmdline/commit_tests.py subversion/tests/cmdline/copy_tests.py subversion/tests/cmdline/depth_tests.py subversion/tests/cmdline/diff_tests.py subversion/tests/cmdline/entries_tests.py subversion/tests/cmdline/export_tests.py subversion/tests/cmdline/externals_tests.py subversion/tests/cmdline/getopt_tests.py subversion/tests/cmdline/history_tests.py subversion/tests/cmdline/import_tests.py subversion/tests/cmdline/info_tests.py subversion/tests/cmdline/input_validation_tests.py subversion/tests/cmdline/iprop_authz_tests.py subversion/tests/cmdline/iprop_tests.py subversion/tests/cmdline/lock_tests.py subversion/tests/cmdline/log_tests.py subversion/tests/cmdline/merge_authz_tests.py subversion/tests/cmdline/merge_automatic_tests.py subversion/tests/cmdline/merge_reintegrate_tests.py subversion/tests/cmdline/merge_tests.py subversion/tests/cmdline/merge_tree_conflict_tests.py subversion/tests/cmdline/mergeinfo_tests.py subversion/tests/cmdline/move_tests.py subversion/tests/cmdline/patch_tests.py subversion/tests/cmdline/prop_tests.py subversion/tests/cmdline/redirect_tests.py subversion/tests/cmdline/relocate_tests.py subversion/tests/cmdline/resolve_tests.py subversion/tests/cmdline/revert_tests.py subversion/tests/cmdline/schedule_tests.py subversion/tests/cmdline/special_tests.py subversion/tests/cmdline/stat_tests.py subversion/tests/cmdline/svnadmin_tests.py subversion/tests/cmdline/svnauthz_tests.py subversion/tests/cmdline/svndumpfilter_tests.py subversion/tests/cmdline/svnlook_tests.py subversion/tests/cmdline/svnmucc_tests.py subversion/tests/cmdline/svnrdump_tests.py subversion/tests/cmdline/svnsync_authz_tests.py subversion/tests/cmdline/svnsync_tests.py subversion/tests/cmdline/svnversion_tests.py subversion/tests/cmdline/switch_tests.py subversion/tests/cmdline/trans_tests.py subversion/tests/cmdline/tree_conflict_tests.py subversion/tests/cmdline/update_tests.py subversion/tests/cmdline/upgrade_tests.py subversion/tests/cmdline/wc_tests.py
+
+check-deps test-deps: subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT) subversion/tests/cmdline/entries-dump$(EXEEXT) tools/server-side/svnauthz$(EXEEXT) tools/server-side/svnauthz-validate$(EXEEXT) subversion/tests/libsvn_delta/svndiff-test$(EXEEXT) subversion/tests/libsvn_delta/vdelta-test$(EXEEXT) subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT)
+
+MANPAGES = subversion/svn/svn.1 subversion/svnadmin/svnadmin.1 subversion/svndumpfilter/svndumpfilter.1 subversion/svnlook/svnlook.1 subversion/svnmucc/svnmucc.1 subversion/svnrdump/svnrdump.1 subversion/svnserve/svnserve.8 subversion/svnserve/svnserve.conf.5 subversion/svnsync/svnsync.1 subversion/svnversion/svnversion.1
+
+CLEAN_FILES = subversion/bindings/cxxhl/cxxhl-tests$(EXEEXT) subversion/svn/svn$(EXEEXT) subversion/svnadmin/svnadmin$(EXEEXT) subversion/svndumpfilter/svndumpfilter$(EXEEXT) subversion/svnlook/svnlook$(EXEEXT) subversion/svnmucc/svnmucc$(EXEEXT) subversion/svnrdump/svnrdump$(EXEEXT) subversion/svnserve/svnserve$(EXEEXT) subversion/svnsync/svnsync$(EXEEXT) subversion/svnversion/svnversion$(EXEEXT) subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT) subversion/tests/cmdline/authz_tests.pyc subversion/tests/cmdline/autoprop_tests.pyc subversion/tests/cmdline/basic_tests.pyc subversion/tests/cmdline/blame_tests.pyc subversion/tests/cmdline/cat_tests.pyc subversion/tests/cmdline/changelist_tests.pyc subversion/tests/cmdline/checkout_tests.pyc subversion/tests/cmdline/commit_tests.pyc subversion/tests/cmdline/copy_tests.pyc subversion/tests/cmdline/depth_tests.pyc subversion/tests/cmdline/diff_tests.pyc subversion/tests/cmdline/entries-dump$(EXEEXT) subversion/tests/cmdline/entries_tests.pyc subversion/tests/cmdline/export_tests.pyc subversion/tests/cmdline/externals_tests.pyc subversion/tests/cmdline/getopt_tests.pyc subversion/tests/cmdline/history_tests.pyc subversion/tests/cmdline/import_tests.pyc subversion/tests/cmdline/info_tests.pyc subversion/tests/cmdline/input_validation_tests.pyc subversion/tests/cmdline/iprop_authz_tests.pyc subversion/tests/cmdline/iprop_tests.pyc subversion/tests/cmdline/lock_tests.pyc subversion/tests/cmdline/log_tests.pyc subversion/tests/cmdline/merge_authz_tests.pyc subversion/tests/cmdline/merge_automatic_tests.pyc subversion/tests/cmdline/merge_reintegrate_tests.pyc subversion/tests/cmdline/merge_tests.pyc subversion/tests/cmdline/merge_tree_conflict_tests.pyc subversion/tests/cmdline/mergeinfo_tests.pyc subversion/tests/cmdline/move_tests.pyc subversion/tests/cmdline/patch_tests.pyc subversion/tests/cmdline/prop_tests.pyc subversion/tests/cmdline/redirect_tests.pyc subversion/tests/cmdline/relocate_tests.pyc subversion/tests/cmdline/resolve_tests.pyc subversion/tests/cmdline/revert_tests.pyc subversion/tests/cmdline/schedule_tests.pyc subversion/tests/cmdline/special_tests.pyc subversion/tests/cmdline/stat_tests.pyc subversion/tests/cmdline/svnadmin_tests.pyc subversion/tests/cmdline/svnauthz_tests.pyc subversion/tests/cmdline/svndumpfilter_tests.pyc subversion/tests/cmdline/svnlook_tests.pyc subversion/tests/cmdline/svnmucc_tests.pyc subversion/tests/cmdline/svnrdump_tests.pyc subversion/tests/cmdline/svnsync_authz_tests.pyc subversion/tests/cmdline/svnsync_tests.pyc subversion/tests/cmdline/svnversion_tests.pyc subversion/tests/cmdline/switch_tests.pyc subversion/tests/cmdline/trans_tests.pyc subversion/tests/cmdline/tree_conflict_tests.pyc subversion/tests/cmdline/update_tests.pyc subversion/tests/cmdline/upgrade_tests.pyc subversion/tests/cmdline/wc_tests.pyc subversion/tests/libsvn_client/client-test$(EXEEXT) subversion/tests/libsvn_delta/random-test$(EXEEXT) subversion/tests/libsvn_delta/svndiff-test$(EXEEXT) subversion/tests/libsvn_delta/vdelta-test$(EXEEXT) subversion/tests/libsvn_delta/window-test$(EXEEXT) subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT) subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT) subversion/tests/libsvn_fs/fs-test$(EXEEXT) subversion/tests/libsvn_fs/locks-test$(EXEEXT) subversion/tests/libsvn_fs_base/changes-test$(EXEEXT) subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT) subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT) subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT) subversion/tests/libsvn_ra/ra-test$(EXEEXT) subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT) subversion/tests/libsvn_repos/repos-test$(EXEEXT) subversion/tests/libsvn_subr/auth-test$(EXEEXT) subversion/tests/libsvn_subr/cache-test$(EXEEXT) subversion/tests/libsvn_subr/checksum-test$(EXEEXT) subversion/tests/libsvn_subr/compat-test$(EXEEXT) subversion/tests/libsvn_subr/config-test$(EXEEXT) subversion/tests/libsvn_subr/crypto-test$(EXEEXT) subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT) subversion/tests/libsvn_subr/error-code-test$(EXEEXT) subversion/tests/libsvn_subr/error-test$(EXEEXT) subversion/tests/libsvn_subr/hashdump-test$(EXEEXT) subversion/tests/libsvn_subr/io-test$(EXEEXT) subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT) subversion/tests/libsvn_subr/named_atomic-proc-test$(EXEEXT) subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT) subversion/tests/libsvn_subr/opt-test$(EXEEXT) subversion/tests/libsvn_subr/path-test$(EXEEXT) subversion/tests/libsvn_subr/revision-test$(EXEEXT) subversion/tests/libsvn_subr/skel-test$(EXEEXT) subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT) subversion/tests/libsvn_subr/stream-test$(EXEEXT) subversion/tests/libsvn_subr/string-test$(EXEEXT) subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT) subversion/tests/libsvn_subr/time-test$(EXEEXT) subversion/tests/libsvn_subr/translate-test$(EXEEXT) subversion/tests/libsvn_subr/utf-test$(EXEEXT) subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT) subversion/tests/libsvn_wc/db-test$(EXEEXT) subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT) subversion/tests/libsvn_wc/op-depth-test$(EXEEXT) subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT) subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT) subversion/tests/libsvn_wc/wc-test$(EXEEXT) tools/client-side/svn-bench/svn-bench$(EXEEXT) tools/dev/fsfs-access-map$(EXEEXT) tools/dev/fsfs-reorg$(EXEEXT) tools/dev/svnraisetreeconflict/svnraisetreeconflict$(EXEEXT) tools/diff/diff$(EXEEXT) tools/diff/diff3$(EXEEXT) tools/diff/diff4$(EXEEXT) tools/server-side/fsfs-stats$(EXEEXT) tools/server-side/svn-populate-node-origins-index$(EXEEXT) tools/server-side/svn-rep-sharing-stats$(EXEEXT) tools/server-side/svnauthz$(EXEEXT) tools/server-side/svnauthz-validate$(EXEEXT)
+EXTRACLEAN_FILES = subversion/libsvn_fs_fs/rep-cache-db.h subversion/libsvn_subr/internal_statements.h subversion/libsvn_wc/wc-queries.h
+
+SWIG_INCLUDES = -I$(abs_builddir)/subversion \
+ -I$(abs_srcdir)/subversion/include \
+ -I$(abs_srcdir)/subversion/bindings/swig \
+ -I$(abs_srcdir)/subversion/bindings/swig/include \
+ -I$(abs_srcdir)/subversion/bindings/swig/proxy \
+ -I$(abs_builddir)/subversion/bindings/swig/proxy \
+ $(SVN_APR_INCLUDES) $(SVN_APRUTIL_INCLUDES)
+
+RELEASE_MODE = 1
+
+
+########################################
+# Section 2: SWIG headers (wrappers and external runtimes)
+########################################
+
+
+########################################
+# Section 3: SWIG autogen rules
+########################################
+
+autogen-swig-py: subversion/bindings/swig/python/core.c subversion/bindings/swig/python/svn_client.c subversion/bindings/swig/python/svn_delta.c subversion/bindings/swig/python/svn_diff.c subversion/bindings/swig/python/svn_fs.c subversion/bindings/swig/python/svn_ra.c subversion/bindings/swig/python/svn_repos.c subversion/bindings/swig/python/svn_wc.c
+autogen-swig: autogen-swig-py
+
+autogen-swig-pl: subversion/bindings/swig/perl/native/core.c subversion/bindings/swig/perl/native/svn_client.c subversion/bindings/swig/perl/native/svn_delta.c subversion/bindings/swig/perl/native/svn_diff.c subversion/bindings/swig/perl/native/svn_fs.c subversion/bindings/swig/perl/native/svn_ra.c subversion/bindings/swig/perl/native/svn_repos.c subversion/bindings/swig/perl/native/svn_wc.c
+autogen-swig: autogen-swig-pl
+
+autogen-swig-rb: subversion/bindings/swig/ruby/core.c subversion/bindings/swig/ruby/svn_client.c subversion/bindings/swig/ruby/svn_delta.c subversion/bindings/swig/ruby/svn_diff.c subversion/bindings/swig/ruby/svn_fs.c subversion/bindings/swig/ruby/svn_ra.c subversion/bindings/swig/ruby/svn_repos.c subversion/bindings/swig/ruby/svn_wc.c
+autogen-swig: autogen-swig-rb
+
+
+
+########################################
+# Section 4: Rules to build SWIG .c files from .i files
+########################################
+
+
+# This needs to be here, rather than in Makefile.in, else
+# './autogen.sh --release' doesn't find it.
+.swig_checked:
+ @if [ "$(SWIG)" = "none" ]; then \
+ echo "SWIG disabled at configure time" >&2; \
+ exit 1; \
+ fi
+ @touch .swig_checked
+
+
+########################################
+# Section 5: Individual target build rules
+########################################
+
+atomic_ra_revprop_change_PATH = subversion/tests/cmdline
+atomic_ra_revprop_change_DEPS = subversion/tests/cmdline/atomic-ra-revprop-change.lo subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la
+atomic_ra_revprop_change_OBJECTS = atomic-ra-revprop-change.lo
+subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT): $(atomic_ra_revprop_change_DEPS)
+ cd subversion/tests/cmdline && $(LINK) $(atomic_ra_revprop_change_LDFLAGS) -o atomic-ra-revprop-change$(EXEEXT) $(atomic_ra_revprop_change_OBJECTS) ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+auth_test_PATH = subversion/tests/libsvn_subr
+auth_test_DEPS = subversion/tests/libsvn_subr/auth-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+auth_test_OBJECTS = auth-test.lo
+subversion/tests/libsvn_subr/auth-test$(EXEEXT): $(auth_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(auth_test_LDFLAGS) -o auth-test$(EXEEXT) $(auth_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+cache_test_PATH = subversion/tests/libsvn_subr
+cache_test_DEPS = subversion/tests/libsvn_subr/cache-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+cache_test_OBJECTS = cache-test.lo
+subversion/tests/libsvn_subr/cache-test$(EXEEXT): $(cache_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(cache_test_LDFLAGS) -o cache-test$(EXEEXT) $(cache_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+changes_test_PATH = subversion/tests/libsvn_fs_base
+changes_test_DEPS = subversion/tests/libsvn_fs_base/changes-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_fs_base/libsvn_fs_base-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+changes_test_OBJECTS = changes-test.lo
+subversion/tests/libsvn_fs_base/changes-test$(EXEEXT): $(changes_test_DEPS)
+ cd subversion/tests/libsvn_fs_base && $(LINK) $(changes_test_LDFLAGS) -o changes-test$(EXEEXT) $(changes_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_fs_base/libsvn_fs_base-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+checksum_test_PATH = subversion/tests/libsvn_subr
+checksum_test_DEPS = subversion/tests/libsvn_subr/checksum-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+checksum_test_OBJECTS = checksum-test.lo
+subversion/tests/libsvn_subr/checksum-test$(EXEEXT): $(checksum_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(checksum_test_LDFLAGS) -o checksum-test$(EXEEXT) $(checksum_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+client_test_PATH = subversion/tests/libsvn_client
+client_test_DEPS = subversion/tests/libsvn_client/client-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+client_test_OBJECTS = client-test.lo
+subversion/tests/libsvn_client/client-test$(EXEEXT): $(client_test_DEPS)
+ cd subversion/tests/libsvn_client && $(LINK) $(client_test_LDFLAGS) -o client-test$(EXEEXT) $(client_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+compat_test_PATH = subversion/tests/libsvn_subr
+compat_test_DEPS = subversion/tests/libsvn_subr/compat-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+compat_test_OBJECTS = compat-test.lo
+subversion/tests/libsvn_subr/compat-test$(EXEEXT): $(compat_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(compat_test_LDFLAGS) -o compat-test$(EXEEXT) $(compat_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+config_test_PATH = subversion/tests/libsvn_subr
+config_test_DEPS = subversion/tests/libsvn_subr/config-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+config_test_OBJECTS = config-test.lo
+subversion/tests/libsvn_subr/config-test$(EXEEXT): $(config_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(config_test_LDFLAGS) -o config-test$(EXEEXT) $(config_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+conflict_data_test_PATH = subversion/tests/libsvn_wc
+conflict_data_test_DEPS = subversion/tests/libsvn_wc/conflict-data-test.lo subversion/tests/libsvn_wc/utils.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la
+conflict_data_test_OBJECTS = conflict-data-test.lo utils.lo
+subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT): $(conflict_data_test_DEPS)
+ cd subversion/tests/libsvn_wc && $(LINK) $(conflict_data_test_LDFLAGS) -o conflict-data-test$(EXEEXT) $(conflict_data_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+crypto_test_PATH = subversion/tests/libsvn_subr
+crypto_test_DEPS = subversion/tests/libsvn_subr/crypto-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+crypto_test_OBJECTS = crypto-test.lo
+subversion/tests/libsvn_subr/crypto-test$(EXEEXT): $(crypto_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(crypto_test_LDFLAGS) -o crypto-test$(EXEEXT) $(crypto_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+cxxhl_tests_PATH = subversion/bindings/cxxhl
+cxxhl_tests_DEPS = subversion/bindings/cxxhl/tests/test_exception.lo subversion/bindings/cxxhl/libsvncxxhl-1.la subversion/libsvn_subr/libsvn_subr-1.la
+cxxhl_tests_OBJECTS = tests/test_exception.lo
+subversion/bindings/cxxhl/cxxhl-tests$(EXEEXT): $(cxxhl_tests_DEPS)
+ cd subversion/bindings/cxxhl && $(LINK_CXX) $(cxxhl_tests_LDFLAGS) -o cxxhl-tests$(EXEEXT) $(cxxhl_tests_OBJECTS) ../../../subversion/bindings/cxxhl/libsvncxxhl-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(LIBS)
+
+db_test_PATH = subversion/tests/libsvn_wc
+db_test_DEPS = subversion/tests/libsvn_wc/db-test.lo subversion/tests/libsvn_wc/utils.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la
+db_test_OBJECTS = db-test.lo utils.lo
+subversion/tests/libsvn_wc/db-test$(EXEEXT): $(db_test_DEPS)
+ cd subversion/tests/libsvn_wc && $(LINK) $(db_test_LDFLAGS) -o db-test$(EXEEXT) $(db_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+diff_PATH = tools/diff
+diff_DEPS = tools/diff/diff.lo subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+diff_OBJECTS = diff.lo
+tools/diff/diff$(EXEEXT): $(diff_DEPS)
+ cd tools/diff && $(LINK) $(diff_LDFLAGS) -o diff$(EXEEXT) $(diff_OBJECTS) ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+diff_diff3_test_PATH = subversion/tests/libsvn_diff
+diff_diff3_test_DEPS = subversion/tests/libsvn_diff/diff-diff3-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+diff_diff3_test_OBJECTS = diff-diff3-test.lo
+subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT): $(diff_diff3_test_DEPS)
+ cd subversion/tests/libsvn_diff && $(LINK) $(diff_diff3_test_LDFLAGS) -o diff-diff3-test$(EXEEXT) $(diff_diff3_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+diff3_PATH = tools/diff
+diff3_DEPS = tools/diff/diff3.lo subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+diff3_OBJECTS = diff3.lo
+tools/diff/diff3$(EXEEXT): $(diff3_DEPS)
+ cd tools/diff && $(LINK) $(diff3_LDFLAGS) -o diff3$(EXEEXT) $(diff3_OBJECTS) ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+diff4_PATH = tools/diff
+diff4_DEPS = tools/diff/diff4.lo subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+diff4_OBJECTS = diff4.lo
+tools/diff/diff4$(EXEEXT): $(diff4_DEPS)
+ cd tools/diff && $(LINK) $(diff4_LDFLAGS) -o diff4$(EXEEXT) $(diff4_OBJECTS) ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+dirent_uri_test_PATH = subversion/tests/libsvn_subr
+dirent_uri_test_DEPS = subversion/tests/libsvn_subr/dirent_uri-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+dirent_uri_test_OBJECTS = dirent_uri-test.lo
+subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT): $(dirent_uri_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(dirent_uri_test_LDFLAGS) -o dirent_uri-test$(EXEEXT) $(dirent_uri_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+entries_compat_test_PATH = subversion/tests/libsvn_wc
+entries_compat_test_DEPS = subversion/tests/libsvn_wc/entries-compat.lo subversion/tests/libsvn_wc/utils.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la
+entries_compat_test_OBJECTS = entries-compat.lo utils.lo
+subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT): $(entries_compat_test_DEPS)
+ cd subversion/tests/libsvn_wc && $(LINK) $(entries_compat_test_LDFLAGS) -o entries-compat-test$(EXEEXT) $(entries_compat_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+entries_dump_PATH = subversion/tests/cmdline
+entries_dump_DEPS = subversion/tests/cmdline/entries-dump.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la
+entries_dump_OBJECTS = entries-dump.lo
+subversion/tests/cmdline/entries-dump$(EXEEXT): $(entries_dump_DEPS)
+ cd subversion/tests/cmdline && $(LINK) $(entries_dump_LDFLAGS) -o entries-dump$(EXEEXT) $(entries_dump_OBJECTS) ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+error_code_test_PATH = subversion/tests/libsvn_subr
+error_code_test_DEPS = subversion/tests/libsvn_subr/error-code-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+error_code_test_OBJECTS = error-code-test.lo
+subversion/tests/libsvn_subr/error-code-test$(EXEEXT): $(error_code_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(error_code_test_LDFLAGS) -o error-code-test$(EXEEXT) $(error_code_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+error_test_PATH = subversion/tests/libsvn_subr
+error_test_DEPS = subversion/tests/libsvn_subr/error-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+error_test_OBJECTS = error-test.lo
+subversion/tests/libsvn_subr/error-test$(EXEEXT): $(error_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(error_test_LDFLAGS) -o error-test$(EXEEXT) $(error_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+fs_base_test_PATH = subversion/tests/libsvn_fs_base
+fs_base_test_DEPS = subversion/tests/libsvn_fs_base/fs-base-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_fs_base/libsvn_fs_base-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la subversion/libsvn_subr/libsvn_subr-1.la
+fs_base_test_OBJECTS = fs-base-test.lo
+subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT): $(fs_base_test_DEPS)
+ cd subversion/tests/libsvn_fs_base && $(LINK) $(fs_base_test_LDFLAGS) -o fs-base-test$(EXEEXT) $(fs_base_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_fs_base/libsvn_fs_base-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_fs_util/libsvn_fs_util-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+fs_pack_test_PATH = subversion/tests/libsvn_fs_fs
+fs_pack_test_DEPS = subversion/tests/libsvn_fs_fs/fs-pack-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_fs_fs/libsvn_fs_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+fs_pack_test_OBJECTS = fs-pack-test.lo
+subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT): $(fs_pack_test_DEPS)
+ cd subversion/tests/libsvn_fs_fs && $(LINK) $(fs_pack_test_LDFLAGS) -o fs-pack-test$(EXEEXT) $(fs_pack_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_fs_fs/libsvn_fs_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+fs_test_PATH = subversion/tests/libsvn_fs
+fs_test_DEPS = subversion/tests/libsvn_fs/fs-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+fs_test_OBJECTS = fs-test.lo
+subversion/tests/libsvn_fs/fs-test$(EXEEXT): $(fs_test_DEPS)
+ cd subversion/tests/libsvn_fs && $(LINK) $(fs_test_LDFLAGS) -o fs-test$(EXEEXT) $(fs_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+fsfs_access_map_PATH = tools/dev
+fsfs_access_map_DEPS = tools/dev/fsfs-access-map.lo subversion/libsvn_subr/libsvn_subr-1.la
+fsfs_access_map_OBJECTS = fsfs-access-map.lo
+tools/dev/fsfs-access-map$(EXEEXT): $(fsfs_access_map_DEPS)
+ cd tools/dev && $(LINK) $(fsfs_access_map_LDFLAGS) -o fsfs-access-map$(EXEEXT) $(fsfs_access_map_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+fsfs_reorg_PATH = tools/dev
+fsfs_reorg_DEPS = tools/dev/fsfs-reorg.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+fsfs_reorg_OBJECTS = fsfs-reorg.lo
+tools/dev/fsfs-reorg$(EXEEXT): $(fsfs_reorg_DEPS)
+ cd tools/dev && $(LINK) $(fsfs_reorg_LDFLAGS) -o fsfs-reorg$(EXEEXT) $(fsfs_reorg_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+fsfs_stats_PATH = tools/server-side
+fsfs_stats_DEPS = tools/server-side/fsfs-stats.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+fsfs_stats_OBJECTS = fsfs-stats.lo
+tools/server-side/fsfs-stats$(EXEEXT): $(fsfs_stats_DEPS)
+ cd tools/server-side && $(LINK) $(fsfs_stats_LDFLAGS) -o fsfs-stats$(EXEEXT) $(fsfs_stats_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+hashdump_test_PATH = subversion/tests/libsvn_subr
+hashdump_test_DEPS = subversion/tests/libsvn_subr/hashdump-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+hashdump_test_OBJECTS = hashdump-test.lo
+subversion/tests/libsvn_subr/hashdump-test$(EXEEXT): $(hashdump_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(hashdump_test_LDFLAGS) -o hashdump-test$(EXEEXT) $(hashdump_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+io_test_PATH = subversion/tests/libsvn_subr
+io_test_DEPS = subversion/tests/libsvn_subr/io-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+io_test_OBJECTS = io-test.lo
+subversion/tests/libsvn_subr/io-test$(EXEEXT): $(io_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(io_test_LDFLAGS) -o io-test$(EXEEXT) $(io_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+javahl_callback_javah_PATH = subversion/bindings/javahl/include
+javahl_callback_javah_HEADERS = subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_BlameCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ChangelistCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ClientNotifyCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_CommitCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_CommitMessageCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ConflictResolverCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_DiffSummaryCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ImportFilterCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_InfoCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_InheritedProplistCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ListCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_LogMessageCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_PatchCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ProgressCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ProplistCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ReposFreezeAction.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_ReposNotifyCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_StatusCallback.h subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_UserPasswordCallback.h
+javahl_callback_javah_OBJECTS =
+javahl_callback_javah_DEPS = $(javahl_callback_javah_HEADERS) $(javahl_callback_javah_OBJECTS) $(javahl_java_DEPS)
+javahl-callback-javah: $(javahl_callback_javah_DEPS)
+javahl_callback_javah_CLASS_FILENAMES = subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/BlameCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ChangelistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ClientNotifyCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitMessageCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ConflictResolverCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/DiffSummaryCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ImportFilterCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InfoCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InheritedProplistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ListCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/LogMessageCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/PatchCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProgressCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProplistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposFreezeAction.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposNotifyCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/StatusCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/UserPasswordCallback.class
+javahl_callback_javah_CLASSES = org.apache.subversion.javahl.callback.BlameCallback org.apache.subversion.javahl.callback.ChangelistCallback org.apache.subversion.javahl.callback.ClientNotifyCallback org.apache.subversion.javahl.callback.CommitCallback org.apache.subversion.javahl.callback.CommitMessageCallback org.apache.subversion.javahl.callback.ConflictResolverCallback org.apache.subversion.javahl.callback.DiffSummaryCallback org.apache.subversion.javahl.callback.ImportFilterCallback org.apache.subversion.javahl.callback.InfoCallback org.apache.subversion.javahl.callback.InheritedProplistCallback org.apache.subversion.javahl.callback.ListCallback org.apache.subversion.javahl.callback.LogMessageCallback org.apache.subversion.javahl.callback.PatchCallback org.apache.subversion.javahl.callback.ProgressCallback org.apache.subversion.javahl.callback.ProplistCallback org.apache.subversion.javahl.callback.ReposFreezeAction org.apache.subversion.javahl.callback.ReposNotifyCallback org.apache.subversion.javahl.callback.StatusCallback org.apache.subversion.javahl.callback.UserPasswordCallback
+$(javahl_callback_javah_HEADERS): $(javahl_callback_javah_CLASS_FILENAMES)
+ $(COMPILE_JAVAHL_JAVAH) -force -d subversion/bindings/javahl/include -classpath subversion/bindings/javahl/classes:$(javahl_callback_javah_CLASSPATH) $(javahl_callback_javah_CLASSES)
+
+
+javahl_compat_java_PATH = subversion/bindings/javahl/classes
+javahl_compat_java_HEADERS =
+javahl_compat_java_OBJECTS = subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback2.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback3.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallbackImpl.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ChangePath.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ChangelistCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ClientException.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitItem.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitItemStateFlags.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitMessage.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictDescriptor.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictResolverCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictResult.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictVersion.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CopySource.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Depth.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DiffSummary.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DiffSummaryReceiver.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DirEntry.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ErrorCodes.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Info.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Info2.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/InfoCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/InputInterface.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ListCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Lock.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LockStatus.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogDate.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogMessage.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogMessageCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Mergeinfo.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/MergeinfoLogKind.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NativeException.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NodeKind.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Notify.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Notify2.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyAction.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyInformation.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyStatus.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Operation.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/OutputInterface.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Path.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProgressEvent.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProgressListener.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword2.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword3.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PropertyData.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProplistCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProplistCallbackImpl.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Revision.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RevisionKind.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RevisionRange.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNAdmin.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClient.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientInterface.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientLogLevel.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientSynchronized.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNInputStream.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNOutputStream.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ScheduleKind.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Status.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/StatusCallback.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/StatusKind.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SubversionException.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Version.class
+javahl_compat_java_DEPS = $(javahl_compat_java_HEADERS) $(javahl_compat_java_OBJECTS) $(javahl_java_DEPS)
+javahl-compat-java: $(javahl_compat_java_DEPS)
+javahl_compat_java_SRC = $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback2.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback3.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallbackImpl.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ChangePath.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ChangelistCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ClientException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitItem.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitItemStateFlags.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitMessage.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictDescriptor.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictResolverCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictResult.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictVersion.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/CopySource.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Depth.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/DiffSummary.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/DiffSummaryReceiver.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/DirEntry.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ErrorCodes.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Info.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Info2.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/InfoCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/InputInterface.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ListCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Lock.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/LockStatus.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogDate.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogMessage.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogMessageCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Mergeinfo.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/MergeinfoLogKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NativeException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NodeKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Notify.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Notify2.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyAction.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyInformation.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyStatus.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Operation.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/OutputInterface.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Path.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProgressEvent.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProgressListener.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword2.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword3.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/PropertyData.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallbackImpl.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Revision.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/RevisionKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/RevisionRange.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNAdmin.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientLogLevel.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNInputStream.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNOutputStream.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/ScheduleKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Status.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/StatusCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/StatusKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SubversionException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/tigris/subversion/javahl/Version.java
+$(javahl_compat_java_OBJECTS): $(javahl_compat_java_SRC)
+ $(COMPILE_JAVAHL_JAVAC) -d subversion/bindings/javahl/classes -classpath subversion/bindings/javahl/classes:$(javahl_compat_java_CLASSPATH) $(javahl_compat_java_SRC)
+
+
+javahl_compat_tests_PATH = subversion/bindings/javahl/classes
+javahl_compat_tests_HEADERS =
+javahl_compat_tests_OBJECTS = subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BasicTests.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RunTests.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNAdminTests.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNTests.class subversion/bindings/javahl/classes/org/tigris/subversion/javahl/WC.class
+javahl_compat_tests_DEPS = $(javahl_compat_tests_HEADERS) $(javahl_compat_tests_OBJECTS) $(javahl_compat_java_DEPS)
+javahl-compat-tests: $(javahl_compat_tests_DEPS)
+javahl_compat_tests_SRC = $(abs_srcdir)/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/BasicTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/RunTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/SVNAdminTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/SVNTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/WC.java
+$(javahl_compat_tests_OBJECTS): $(javahl_compat_tests_SRC)
+ $(COMPILE_JAVAHL_JAVAC) -d subversion/bindings/javahl/classes -classpath subversion/bindings/javahl/classes:$(javahl_compat_tests_CLASSPATH) $(javahl_compat_tests_SRC)
+
+
+javahl_java_PATH = subversion/bindings/javahl/classes
+javahl_java_HEADERS =
+javahl_java_OBJECTS = subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientException.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientNotifyInformation.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitInfo.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItem.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItemStateFlags.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictDescriptor.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictResult.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/DiffSummary.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNClient.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNRepos.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/JNIError.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeException.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeResources.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ProgressEvent.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ReposNotifyInformation.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNClient.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNRepos.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SubversionException.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/BlameCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ChangelistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ClientNotifyCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitMessageCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ConflictResolverCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/DiffSummaryCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ImportFilterCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InfoCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InheritedProplistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ListCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/LogMessageCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/PatchCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProgressCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProplistCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposFreezeAction.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposNotifyCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/StatusCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/UserPasswordCallback.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ChangePath.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Checksum.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ConflictVersion.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/CopySource.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Depth.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DiffOptions.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DirEntry.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Info.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Lock.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/LogDate.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Mergeinfo.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/NodeKind.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Property.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Revision.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/RevisionRange.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Status.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Tristate.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Version.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/VersionExtended.class
+javahl_java_DEPS = $(javahl_java_HEADERS) $(javahl_java_OBJECTS)
+javahl-java: $(javahl_java_DEPS)
+javahl_java_SRC = $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientNotifyInformation.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitInfo.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitItem.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitItemStateFlags.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictDescriptor.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictResult.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/DiffSummary.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNClient.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNRepos.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/JNIError.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeResources.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ProgressEvent.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/ReposNotifyInformation.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNClient.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNRepos.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/SubversionException.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/BlameCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ChangelistCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ClientNotifyCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/CommitCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/CommitMessageCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ConflictResolverCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/DiffSummaryCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ImportFilterCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/InfoCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/InheritedProplistCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ListCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/LogMessageCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/PatchCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProgressCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProplistCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ReposFreezeAction.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ReposNotifyCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/StatusCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/UserPasswordCallback.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/ChangePath.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Checksum.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/ConflictVersion.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/CopySource.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Depth.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/DiffOptions.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/DirEntry.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Info.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Lock.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/LogDate.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Mergeinfo.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/NodeKind.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Property.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Revision.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/RevisionRange.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Status.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Tristate.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Version.java $(abs_srcdir)/subversion/bindings/javahl/src/org/apache/subversion/javahl/types/VersionExtended.java
+$(javahl_java_OBJECTS): $(javahl_java_SRC)
+ $(COMPILE_JAVAHL_JAVAC) -d subversion/bindings/javahl/classes -classpath subversion/bindings/javahl/classes:$(javahl_java_CLASSPATH) $(javahl_java_SRC)
+
+
+javahl_javah_PATH = subversion/bindings/javahl/include
+javahl_javah_HEADERS = subversion/bindings/javahl/include/org_apache_subversion_javahl_ClientException.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ClientNotifyInformation.h subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitInfo.h subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitItem.h subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitItemStateFlags.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ConflictDescriptor.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ConflictResult.h subversion/bindings/javahl/include/org_apache_subversion_javahl_DiffSummary.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ISVNClient.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ISVNRepos.h subversion/bindings/javahl/include/org_apache_subversion_javahl_JNIError.h subversion/bindings/javahl/include/org_apache_subversion_javahl_NativeException.h subversion/bindings/javahl/include/org_apache_subversion_javahl_NativeResources.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ProgressEvent.h subversion/bindings/javahl/include/org_apache_subversion_javahl_ReposNotifyInformation.h subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNClient.h subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNRepos.h subversion/bindings/javahl/include/org_apache_subversion_javahl_SubversionException.h
+javahl_javah_OBJECTS =
+javahl_javah_DEPS = $(javahl_javah_HEADERS) $(javahl_javah_OBJECTS) $(javahl_java_DEPS)
+javahl-javah: $(javahl_javah_DEPS)
+javahl_javah_CLASS_FILENAMES = subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientException.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientNotifyInformation.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitInfo.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItem.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItemStateFlags.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictDescriptor.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictResult.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/DiffSummary.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNClient.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNRepos.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/JNIError.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeException.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeResources.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ProgressEvent.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/ReposNotifyInformation.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNClient.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNRepos.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SubversionException.class
+javahl_javah_CLASSES = org.apache.subversion.javahl.ClientException org.apache.subversion.javahl.ClientNotifyInformation org.apache.subversion.javahl.CommitInfo org.apache.subversion.javahl.CommitItem org.apache.subversion.javahl.CommitItemStateFlags org.apache.subversion.javahl.ConflictDescriptor org.apache.subversion.javahl.ConflictResult org.apache.subversion.javahl.DiffSummary org.apache.subversion.javahl.ISVNClient org.apache.subversion.javahl.ISVNRepos org.apache.subversion.javahl.JNIError org.apache.subversion.javahl.NativeException org.apache.subversion.javahl.NativeResources org.apache.subversion.javahl.ProgressEvent org.apache.subversion.javahl.ReposNotifyInformation org.apache.subversion.javahl.SVNClient org.apache.subversion.javahl.SVNRepos org.apache.subversion.javahl.SubversionException
+$(javahl_javah_HEADERS): $(javahl_javah_CLASS_FILENAMES)
+ $(COMPILE_JAVAHL_JAVAH) -force -d subversion/bindings/javahl/include -classpath subversion/bindings/javahl/classes:$(javahl_javah_CLASSPATH) $(javahl_javah_CLASSES)
+
+
+javahl_tests_PATH = subversion/bindings/javahl/classes
+javahl_tests_HEADERS =
+javahl_tests_OBJECTS = subversion/bindings/javahl/classes/org/apache/subversion/javahl/BasicTests.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/RunTests.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNReposTests.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNTests.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/WC.class
+javahl_tests_DEPS = $(javahl_tests_HEADERS) $(javahl_tests_OBJECTS) $(javahl_java_DEPS)
+javahl-tests: $(javahl_tests_DEPS)
+javahl_tests_SRC = $(abs_srcdir)/subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/apache/subversion/javahl/RunTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/apache/subversion/javahl/SVNReposTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/apache/subversion/javahl/SVNTests.java $(abs_srcdir)/subversion/bindings/javahl/tests/org/apache/subversion/javahl/WC.java
+$(javahl_tests_OBJECTS): $(javahl_tests_SRC)
+ $(COMPILE_JAVAHL_JAVAC) -d subversion/bindings/javahl/classes -classpath subversion/bindings/javahl/classes:$(javahl_tests_CLASSPATH) $(javahl_tests_SRC)
+
+
+javahl_types_javah_PATH = subversion/bindings/javahl/include
+javahl_types_javah_HEADERS = subversion/bindings/javahl/include/org_apache_subversion_javahl_types_ChangePath.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Checksum.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_ConflictVersion.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_CopySource.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Depth.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_DiffOptions.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_DirEntry.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Info.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Lock.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_LogDate.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Mergeinfo.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_NodeKind.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Property.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Revision.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_RevisionRange.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Status.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Tristate.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Version.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended.h
+javahl_types_javah_OBJECTS =
+javahl_types_javah_DEPS = $(javahl_types_javah_HEADERS) $(javahl_types_javah_OBJECTS) $(javahl_java_DEPS)
+javahl-types-javah: $(javahl_types_javah_DEPS)
+javahl_types_javah_CLASS_FILENAMES = subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ChangePath.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Checksum.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ConflictVersion.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/CopySource.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Depth.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DiffOptions.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DirEntry.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Info.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Lock.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/LogDate.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Mergeinfo.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/NodeKind.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Property.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Revision.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/RevisionRange.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Status.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Tristate.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Version.class subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/VersionExtended.class
+javahl_types_javah_CLASSES = org.apache.subversion.javahl.types.ChangePath org.apache.subversion.javahl.types.Checksum org.apache.subversion.javahl.types.ConflictVersion org.apache.subversion.javahl.types.CopySource org.apache.subversion.javahl.types.Depth org.apache.subversion.javahl.types.DiffOptions org.apache.subversion.javahl.types.DirEntry org.apache.subversion.javahl.types.Info org.apache.subversion.javahl.types.Lock org.apache.subversion.javahl.types.LogDate org.apache.subversion.javahl.types.Mergeinfo org.apache.subversion.javahl.types.NodeKind org.apache.subversion.javahl.types.Property org.apache.subversion.javahl.types.Revision org.apache.subversion.javahl.types.RevisionRange org.apache.subversion.javahl.types.Status org.apache.subversion.javahl.types.Tristate org.apache.subversion.javahl.types.Version org.apache.subversion.javahl.types.VersionExtended
+$(javahl_types_javah_HEADERS): $(javahl_types_javah_CLASS_FILENAMES)
+ $(COMPILE_JAVAHL_JAVAH) -force -d subversion/bindings/javahl/include -classpath subversion/bindings/javahl/classes:$(javahl_types_javah_CLASSPATH) $(javahl_types_javah_CLASSES)
+
+
+libsvn_auth_gnome_keyring_PATH = subversion/libsvn_auth_gnome_keyring
+libsvn_auth_gnome_keyring_DEPS = subversion/libsvn_auth_gnome_keyring/gnome_keyring.lo subversion/libsvn_auth_gnome_keyring/version.lo subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_auth_gnome_keyring_OBJECTS = gnome_keyring.lo version.lo
+subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring-1.la: $(libsvn_auth_gnome_keyring_DEPS)
+ cd subversion/libsvn_auth_gnome_keyring && $(LINK_LIB) $(libsvn_auth_gnome_keyring_LDFLAGS) -o libsvn_auth_gnome_keyring-1.la $(LT_NO_UNDEFINED) $(libsvn_auth_gnome_keyring_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(SVN_GNOME_KEYRING_LIBS) $(LIBS)
+
+libsvn_auth_kwallet_PATH = subversion/libsvn_auth_kwallet
+libsvn_auth_kwallet_DEPS = subversion/libsvn_auth_kwallet/kwallet.lo subversion/libsvn_auth_kwallet/version.lo subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_auth_kwallet_OBJECTS = kwallet.lo version.lo
+subversion/libsvn_auth_kwallet/libsvn_auth_kwallet-1.la: $(libsvn_auth_kwallet_DEPS)
+ cd subversion/libsvn_auth_kwallet && $(LINK_CXX_LIB) $(libsvn_auth_kwallet_LDFLAGS) -o libsvn_auth_kwallet-1.la $(LT_NO_UNDEFINED) $(libsvn_auth_kwallet_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(SVN_KWALLET_LIBS) $(LIBS)
+
+libsvn_client_PATH = subversion/libsvn_client
+libsvn_client_DEPS = subversion/libsvn_client/add.lo subversion/libsvn_client/blame.lo subversion/libsvn_client/cat.lo subversion/libsvn_client/changelist.lo subversion/libsvn_client/checkout.lo subversion/libsvn_client/cleanup.lo subversion/libsvn_client/cmdline.lo subversion/libsvn_client/commit.lo subversion/libsvn_client/commit_util.lo subversion/libsvn_client/compat_providers.lo subversion/libsvn_client/copy.lo subversion/libsvn_client/copy_foreign.lo subversion/libsvn_client/ctx.lo subversion/libsvn_client/delete.lo subversion/libsvn_client/deprecated.lo subversion/libsvn_client/diff.lo subversion/libsvn_client/diff_local.lo subversion/libsvn_client/diff_summarize.lo subversion/libsvn_client/export.lo subversion/libsvn_client/externals.lo subversion/libsvn_client/import.lo subversion/libsvn_client/info.lo subversion/libsvn_client/iprops.lo subversion/libsvn_client/list.lo subversion/libsvn_client/locking_commands.lo subversion/libsvn_client/log.lo subversion/libsvn_client/merge.lo subversion/libsvn_client/mergeinfo.lo subversion/libsvn_client/patch.lo subversion/libsvn_client/prop_commands.lo subversion/libsvn_client/ra.lo subversion/libsvn_client/relocate.lo subversion/libsvn_client/repos_diff.lo subversion/libsvn_client/resolved.lo subversion/libsvn_client/revert.lo subversion/libsvn_client/revisions.lo subversion/libsvn_client/status.lo subversion/libsvn_client/switch.lo subversion/libsvn_client/update.lo subversion/libsvn_client/upgrade.lo subversion/libsvn_client/url.lo subversion/libsvn_client/util.lo subversion/libsvn_client/version.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_client_OBJECTS = add.lo blame.lo cat.lo changelist.lo checkout.lo cleanup.lo cmdline.lo commit.lo commit_util.lo compat_providers.lo copy.lo copy_foreign.lo ctx.lo delete.lo deprecated.lo diff.lo diff_local.lo diff_summarize.lo export.lo externals.lo import.lo info.lo iprops.lo list.lo locking_commands.lo log.lo merge.lo mergeinfo.lo patch.lo prop_commands.lo ra.lo relocate.lo repos_diff.lo resolved.lo revert.lo revisions.lo status.lo switch.lo update.lo upgrade.lo url.lo util.lo version.lo
+subversion/libsvn_client/libsvn_client-1.la: $(libsvn_client_DEPS)
+ cd subversion/libsvn_client && $(LINK_LIB) $(libsvn_client_LDFLAGS) -o libsvn_client-1.la $(LT_NO_UNDEFINED) $(libsvn_client_OBJECTS) ../../subversion/libsvn_wc/libsvn_wc-1.la ../../subversion/libsvn_ra/libsvn_ra-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvn_delta_PATH = subversion/libsvn_delta
+libsvn_delta_DEPS = subversion/libsvn_delta/cancel.lo subversion/libsvn_delta/compat.lo subversion/libsvn_delta/compose_delta.lo subversion/libsvn_delta/debug_editor.lo subversion/libsvn_delta/default_editor.lo subversion/libsvn_delta/deprecated.lo subversion/libsvn_delta/depth_filter_editor.lo subversion/libsvn_delta/editor.lo subversion/libsvn_delta/path_driver.lo subversion/libsvn_delta/svndiff.lo subversion/libsvn_delta/text_delta.lo subversion/libsvn_delta/version.lo subversion/libsvn_delta/xdelta.lo subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_delta_OBJECTS = cancel.lo compat.lo compose_delta.lo debug_editor.lo default_editor.lo deprecated.lo depth_filter_editor.lo editor.lo path_driver.lo svndiff.lo text_delta.lo version.lo xdelta.lo
+subversion/libsvn_delta/libsvn_delta-1.la: $(libsvn_delta_DEPS)
+ cd subversion/libsvn_delta && $(LINK_LIB) $(libsvn_delta_LDFLAGS) -o libsvn_delta-1.la $(LT_NO_UNDEFINED) $(libsvn_delta_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_ZLIB_LIBS) $(LIBS)
+
+libsvn_diff_PATH = subversion/libsvn_diff
+libsvn_diff_DEPS = subversion/libsvn_diff/deprecated.lo subversion/libsvn_diff/diff.lo subversion/libsvn_diff/diff3.lo subversion/libsvn_diff/diff4.lo subversion/libsvn_diff/diff_file.lo subversion/libsvn_diff/diff_memory.lo subversion/libsvn_diff/diff_tree.lo subversion/libsvn_diff/lcs.lo subversion/libsvn_diff/parse-diff.lo subversion/libsvn_diff/token.lo subversion/libsvn_diff/util.lo subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_diff_OBJECTS = deprecated.lo diff.lo diff3.lo diff4.lo diff_file.lo diff_memory.lo diff_tree.lo lcs.lo parse-diff.lo token.lo util.lo
+subversion/libsvn_diff/libsvn_diff-1.la: $(libsvn_diff_DEPS)
+ cd subversion/libsvn_diff && $(LINK_LIB) $(libsvn_diff_LDFLAGS) -o libsvn_diff-1.la $(LT_NO_UNDEFINED) $(libsvn_diff_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_ZLIB_LIBS) $(LIBS)
+
+libsvn_fs_PATH = subversion/libsvn_fs
+install-ramod-lib: $(SVN_FS_LIB_INSTALL_DEPS)
+libsvn_fs_DEPS = $(SVN_FS_LIB_DEPS) subversion/libsvn_fs/access.lo subversion/libsvn_fs/editor.lo subversion/libsvn_fs/fs-loader.lo subversion/libsvn_fs_util/libsvn_fs_util-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_fs_OBJECTS = access.lo editor.lo fs-loader.lo
+subversion/libsvn_fs/libsvn_fs-1.la: $(libsvn_fs_DEPS)
+ cd subversion/libsvn_fs && $(LINK_LIB) $(libsvn_fs_LDFLAGS) -o libsvn_fs-1.la $(LT_NO_UNDEFINED) $(libsvn_fs_OBJECTS) ../../subversion/libsvn_fs_util/libsvn_fs_util-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_FS_LIB_LINK) $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvn_fs_base_PATH = subversion/libsvn_fs_base
+libsvn_fs_base_DEPS = subversion/libsvn_fs_base/bdb/bdb-err.lo subversion/libsvn_fs_base/bdb/bdb_compat.lo subversion/libsvn_fs_base/bdb/changes-table.lo subversion/libsvn_fs_base/bdb/checksum-reps-table.lo subversion/libsvn_fs_base/bdb/copies-table.lo subversion/libsvn_fs_base/bdb/dbt.lo subversion/libsvn_fs_base/bdb/env.lo subversion/libsvn_fs_base/bdb/lock-tokens-table.lo subversion/libsvn_fs_base/bdb/locks-table.lo subversion/libsvn_fs_base/bdb/miscellaneous-table.lo subversion/libsvn_fs_base/bdb/node-origins-table.lo subversion/libsvn_fs_base/bdb/nodes-table.lo subversion/libsvn_fs_base/bdb/reps-table.lo subversion/libsvn_fs_base/bdb/rev-table.lo subversion/libsvn_fs_base/bdb/strings-table.lo subversion/libsvn_fs_base/bdb/txn-table.lo subversion/libsvn_fs_base/bdb/uuids-table.lo subversion/libsvn_fs_base/dag.lo subversion/libsvn_fs_base/err.lo subversion/libsvn_fs_base/fs.lo subversion/libsvn_fs_base/id.lo subversion/libsvn_fs_base/key-gen.lo subversion/libsvn_fs_base/lock.lo subversion/libsvn_fs_base/node-rev.lo subversion/libsvn_fs_base/reps-strings.lo subversion/libsvn_fs_base/revs-txns.lo subversion/libsvn_fs_base/trail.lo subversion/libsvn_fs_base/tree.lo subversion/libsvn_fs_base/util/fs_skels.lo subversion/libsvn_fs_base/uuid.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la
+libsvn_fs_base_OBJECTS = bdb/bdb-err.lo bdb/bdb_compat.lo bdb/changes-table.lo bdb/checksum-reps-table.lo bdb/copies-table.lo bdb/dbt.lo bdb/env.lo bdb/lock-tokens-table.lo bdb/locks-table.lo bdb/miscellaneous-table.lo bdb/node-origins-table.lo bdb/nodes-table.lo bdb/reps-table.lo bdb/rev-table.lo bdb/strings-table.lo bdb/txn-table.lo bdb/uuids-table.lo dag.lo err.lo fs.lo id.lo key-gen.lo lock.lo node-rev.lo reps-strings.lo revs-txns.lo trail.lo tree.lo util/fs_skels.lo uuid.lo
+subversion/libsvn_fs_base/libsvn_fs_base-1.la: $(libsvn_fs_base_DEPS)
+ cd subversion/libsvn_fs_base && $(LINK_LIB) $(libsvn_fs_base_LDFLAGS) -o libsvn_fs_base-1.la $(LT_NO_UNDEFINED) $(libsvn_fs_base_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_DB_LIBS) ../../subversion/libsvn_fs_util/libsvn_fs_util-1.la $(LIBS)
+
+libsvn_fs_fs_PATH = subversion/libsvn_fs_fs
+libsvn_fs_fs_DEPS = subversion/libsvn_fs_fs/caching.lo subversion/libsvn_fs_fs/dag.lo subversion/libsvn_fs_fs/fs.lo subversion/libsvn_fs_fs/fs_fs.lo subversion/libsvn_fs_fs/id.lo subversion/libsvn_fs_fs/key-gen.lo subversion/libsvn_fs_fs/lock.lo subversion/libsvn_fs_fs/rep-cache.lo subversion/libsvn_fs_fs/temp_serializer.lo subversion/libsvn_fs_fs/tree.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la
+libsvn_fs_fs_OBJECTS = caching.lo dag.lo fs.lo fs_fs.lo id.lo key-gen.lo lock.lo rep-cache.lo temp_serializer.lo tree.lo
+subversion/libsvn_fs_fs/libsvn_fs_fs-1.la: $(libsvn_fs_fs_DEPS)
+ cd subversion/libsvn_fs_fs && $(LINK_LIB) $(libsvn_fs_fs_LDFLAGS) -o libsvn_fs_fs-1.la $(LT_NO_UNDEFINED) $(libsvn_fs_fs_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) ../../subversion/libsvn_fs_util/libsvn_fs_util-1.la $(LIBS)
+
+libsvn_fs_util_PATH = subversion/libsvn_fs_util
+libsvn_fs_util_DEPS = subversion/libsvn_fs_util/fs-util.lo subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_fs_util_OBJECTS = fs-util.lo
+subversion/libsvn_fs_util/libsvn_fs_util-1.la: $(libsvn_fs_util_DEPS)
+ cd subversion/libsvn_fs_util && $(LINK_LIB) $(libsvn_fs_util_LDFLAGS) -o libsvn_fs_util-1.la $(LT_NO_UNDEFINED) $(libsvn_fs_util_OBJECTS) ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvn_ra_PATH = subversion/libsvn_ra
+install-lib: $(SVN_RA_LIB_INSTALL_DEPS)
+libsvn_ra_DEPS = $(SVN_RA_LIB_DEPS) subversion/libsvn_ra/compat.lo subversion/libsvn_ra/debug_reporter.lo subversion/libsvn_ra/deprecated.lo subversion/libsvn_ra/editor.lo subversion/libsvn_ra/ra_loader.lo subversion/libsvn_ra/util.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_ra_OBJECTS = compat.lo debug_reporter.lo deprecated.lo editor.lo ra_loader.lo util.lo
+subversion/libsvn_ra/libsvn_ra-1.la: $(libsvn_ra_DEPS)
+ cd subversion/libsvn_ra && $(LINK_LIB) $(libsvn_ra_LDFLAGS) -o libsvn_ra-1.la $(LT_NO_UNDEFINED) $(libsvn_ra_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_RA_LIB_LINK) $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvn_ra_local_PATH = subversion/libsvn_ra_local
+libsvn_ra_local_DEPS = subversion/libsvn_ra_local/ra_plugin.lo subversion/libsvn_ra_local/split_url.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_ra_local_OBJECTS = ra_plugin.lo split_url.lo
+subversion/libsvn_ra_local/libsvn_ra_local-1.la: $(libsvn_ra_local_DEPS)
+ cd subversion/libsvn_ra_local && $(LINK_LIB) $(libsvn_ra_local_LDFLAGS) -o libsvn_ra_local-1.la $(LT_NO_UNDEFINED) $(libsvn_ra_local_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvn_ra_serf_PATH = subversion/libsvn_ra_serf
+libsvn_ra_serf_DEPS = subversion/libsvn_ra_serf/blame.lo subversion/libsvn_ra_serf/blncache.lo subversion/libsvn_ra_serf/commit.lo subversion/libsvn_ra_serf/get_deleted_rev.lo subversion/libsvn_ra_serf/getdate.lo subversion/libsvn_ra_serf/getlocations.lo subversion/libsvn_ra_serf/getlocationsegments.lo subversion/libsvn_ra_serf/getlocks.lo subversion/libsvn_ra_serf/inherited_props.lo subversion/libsvn_ra_serf/locks.lo subversion/libsvn_ra_serf/log.lo subversion/libsvn_ra_serf/merge.lo subversion/libsvn_ra_serf/mergeinfo.lo subversion/libsvn_ra_serf/options.lo subversion/libsvn_ra_serf/property.lo subversion/libsvn_ra_serf/replay.lo subversion/libsvn_ra_serf/sb_bucket.lo subversion/libsvn_ra_serf/serf.lo subversion/libsvn_ra_serf/update.lo subversion/libsvn_ra_serf/util.lo subversion/libsvn_ra_serf/util_error.lo subversion/libsvn_ra_serf/xml.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_ra_serf_OBJECTS = blame.lo blncache.lo commit.lo get_deleted_rev.lo getdate.lo getlocations.lo getlocationsegments.lo getlocks.lo inherited_props.lo locks.lo log.lo merge.lo mergeinfo.lo options.lo property.lo replay.lo sb_bucket.lo serf.lo update.lo util.lo util_error.lo xml.lo
+subversion/libsvn_ra_serf/libsvn_ra_serf-1.la: $(libsvn_ra_serf_DEPS)
+ cd subversion/libsvn_ra_serf && $(LINK_LIB) $(libsvn_ra_serf_LDFLAGS) -o libsvn_ra_serf-1.la $(LT_NO_UNDEFINED) $(libsvn_ra_serf_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_SERF_LIBS) $(SVN_XML_LIBS) $(LIBS)
+
+libsvn_ra_svn_PATH = subversion/libsvn_ra_svn
+libsvn_ra_svn_DEPS = subversion/libsvn_ra_svn/client.lo subversion/libsvn_ra_svn/cram.lo subversion/libsvn_ra_svn/cyrus_auth.lo subversion/libsvn_ra_svn/deprecated.lo subversion/libsvn_ra_svn/editorp.lo subversion/libsvn_ra_svn/internal_auth.lo subversion/libsvn_ra_svn/marshal.lo subversion/libsvn_ra_svn/streams.lo subversion/libsvn_ra_svn/version.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_ra_svn_OBJECTS = client.lo cram.lo cyrus_auth.lo deprecated.lo editorp.lo internal_auth.lo marshal.lo streams.lo version.lo
+subversion/libsvn_ra_svn/libsvn_ra_svn-1.la: $(libsvn_ra_svn_DEPS)
+ cd subversion/libsvn_ra_svn && $(LINK_LIB) $(libsvn_ra_svn_LDFLAGS) -o libsvn_ra_svn-1.la $(LT_NO_UNDEFINED) $(libsvn_ra_svn_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_SASL_LIBS) $(LIBS)
+
+libsvn_repos_PATH = subversion/libsvn_repos
+libsvn_repos_DEPS = subversion/libsvn_repos/authz.lo subversion/libsvn_repos/commit.lo subversion/libsvn_repos/delta.lo subversion/libsvn_repos/deprecated.lo subversion/libsvn_repos/dump.lo subversion/libsvn_repos/fs-wrap.lo subversion/libsvn_repos/hooks.lo subversion/libsvn_repos/load-fs-vtable.lo subversion/libsvn_repos/load.lo subversion/libsvn_repos/log.lo subversion/libsvn_repos/node_tree.lo subversion/libsvn_repos/notify.lo subversion/libsvn_repos/replay.lo subversion/libsvn_repos/reporter.lo subversion/libsvn_repos/repos.lo subversion/libsvn_repos/rev_hunt.lo subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_repos_OBJECTS = authz.lo commit.lo delta.lo deprecated.lo dump.lo fs-wrap.lo hooks.lo load-fs-vtable.lo load.lo log.lo node_tree.lo notify.lo replay.lo reporter.lo repos.lo rev_hunt.lo
+subversion/libsvn_repos/libsvn_repos-1.la: $(libsvn_repos_DEPS)
+ cd subversion/libsvn_repos && $(LINK_LIB) $(libsvn_repos_LDFLAGS) -o libsvn_repos-1.la $(LT_NO_UNDEFINED) $(libsvn_repos_OBJECTS) ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvn_subr_PATH = subversion/libsvn_subr
+libsvn_subr_DEPS = subversion/libsvn_subr/adler32.lo subversion/libsvn_subr/atomic.lo subversion/libsvn_subr/auth.lo subversion/libsvn_subr/base64.lo subversion/libsvn_subr/cache-inprocess.lo subversion/libsvn_subr/cache-membuffer.lo subversion/libsvn_subr/cache-memcache.lo subversion/libsvn_subr/cache.lo subversion/libsvn_subr/cache_config.lo subversion/libsvn_subr/checksum.lo subversion/libsvn_subr/cmdline.lo subversion/libsvn_subr/compat.lo subversion/libsvn_subr/config.lo subversion/libsvn_subr/config_auth.lo subversion/libsvn_subr/config_file.lo subversion/libsvn_subr/config_win.lo subversion/libsvn_subr/crypto.lo subversion/libsvn_subr/ctype.lo subversion/libsvn_subr/date.lo subversion/libsvn_subr/debug.lo subversion/libsvn_subr/deprecated.lo subversion/libsvn_subr/dirent_uri.lo subversion/libsvn_subr/dso.lo subversion/libsvn_subr/eol.lo subversion/libsvn_subr/error.lo subversion/libsvn_subr/gpg_agent.lo subversion/libsvn_subr/hash.lo subversion/libsvn_subr/io.lo subversion/libsvn_subr/iter.lo subversion/libsvn_subr/lock.lo subversion/libsvn_subr/log.lo subversion/libsvn_subr/macos_keychain.lo subversion/libsvn_subr/magic.lo subversion/libsvn_subr/md5.lo subversion/libsvn_subr/mergeinfo.lo subversion/libsvn_subr/mutex.lo subversion/libsvn_subr/named_atomic.lo subversion/libsvn_subr/nls.lo subversion/libsvn_subr/opt.lo subversion/libsvn_subr/path.lo subversion/libsvn_subr/pool.lo subversion/libsvn_subr/prompt.lo subversion/libsvn_subr/properties.lo subversion/libsvn_subr/pseudo_md5.lo subversion/libsvn_subr/quoprint.lo subversion/libsvn_subr/sha1.lo subversion/libsvn_subr/simple_providers.lo subversion/libsvn_subr/skel.lo subversion/libsvn_subr/sorts.lo subversion/libsvn_subr/spillbuf.lo subversion/libsvn_subr/sqlite.lo subversion/libsvn_subr/sqlite3wrapper.lo subversion/libsvn_subr/ssl_client_cert_providers.lo subversion/libsvn_subr/ssl_client_cert_pw_providers.lo subversion/libsvn_subr/ssl_server_trust_providers.lo subversion/libsvn_subr/stream.lo subversion/libsvn_subr/string.lo subversion/libsvn_subr/subst.lo subversion/libsvn_subr/sysinfo.lo subversion/libsvn_subr/target.lo subversion/libsvn_subr/temp_serializer.lo subversion/libsvn_subr/time.lo subversion/libsvn_subr/token.lo subversion/libsvn_subr/types.lo subversion/libsvn_subr/user.lo subversion/libsvn_subr/username_providers.lo subversion/libsvn_subr/utf.lo subversion/libsvn_subr/utf_validate.lo subversion/libsvn_subr/utf_width.lo subversion/libsvn_subr/validate.lo subversion/libsvn_subr/version.lo subversion/libsvn_subr/win32_crashrpt.lo subversion/libsvn_subr/win32_crypto.lo subversion/libsvn_subr/win32_xlate.lo subversion/libsvn_subr/xml.lo
+libsvn_subr_OBJECTS = adler32.lo atomic.lo auth.lo base64.lo cache-inprocess.lo cache-membuffer.lo cache-memcache.lo cache.lo cache_config.lo checksum.lo cmdline.lo compat.lo config.lo config_auth.lo config_file.lo config_win.lo crypto.lo ctype.lo date.lo debug.lo deprecated.lo dirent_uri.lo dso.lo eol.lo error.lo gpg_agent.lo hash.lo io.lo iter.lo lock.lo log.lo macos_keychain.lo magic.lo md5.lo mergeinfo.lo mutex.lo named_atomic.lo nls.lo opt.lo path.lo pool.lo prompt.lo properties.lo pseudo_md5.lo quoprint.lo sha1.lo simple_providers.lo skel.lo sorts.lo spillbuf.lo sqlite.lo sqlite3wrapper.lo ssl_client_cert_providers.lo ssl_client_cert_pw_providers.lo ssl_server_trust_providers.lo stream.lo string.lo subst.lo sysinfo.lo target.lo temp_serializer.lo time.lo token.lo types.lo user.lo username_providers.lo utf.lo utf_validate.lo utf_width.lo validate.lo version.lo win32_crashrpt.lo win32_crypto.lo win32_xlate.lo xml.lo
+subversion/libsvn_subr/libsvn_subr-1.la: $(libsvn_subr_DEPS)
+ cd subversion/libsvn_subr && $(LINK_LIB) $(libsvn_subr_LDFLAGS) -o libsvn_subr-1.la $(LT_NO_UNDEFINED) $(libsvn_subr_OBJECTS) $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_XML_LIBS) $(SVN_ZLIB_LIBS) $(SVN_APR_MEMCACHE_LIBS) $(SVN_SQLITE_LIBS) $(SVN_MAGIC_LIBS) $(LIBS)
+
+libsvn_swig_perl_PATH = subversion/bindings/swig/perl/libsvn_swig_perl
+libsvn_swig_perl_DEPS = subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_swig_perl_OBJECTS = swigutil_pl.lo
+subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la: $(libsvn_swig_perl_DEPS)
+ cd subversion/bindings/swig/perl/libsvn_swig_perl && $(LINK_LIB) $(libsvn_swig_perl_LDFLAGS) -o libsvn_swig_perl-1.la $(LT_NO_UNDEFINED) $(libsvn_swig_perl_OBJECTS) ../../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvn_swig_py_PATH = subversion/bindings/swig/python/libsvn_swig_py
+libsvn_swig_py_DEPS = subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_swig_py_OBJECTS = swigutil_py.lo
+subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la: $(libsvn_swig_py_DEPS)
+ cd subversion/bindings/swig/python/libsvn_swig_py && $(LINK) $(libsvn_swig_py_LDFLAGS) -o libsvn_swig_py-1.la $(LT_NO_UNDEFINED) $(libsvn_swig_py_OBJECTS) ../../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvn_swig_ruby_PATH = subversion/bindings/swig/ruby/libsvn_swig_ruby
+libsvn_swig_ruby_DEPS = subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_swig_ruby_OBJECTS = swigutil_rb.lo
+subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la: $(libsvn_swig_ruby_DEPS)
+ cd subversion/bindings/swig/ruby/libsvn_swig_ruby && $(LINK) $(SWIG_RB_LIBS) $(libsvn_swig_ruby_LDFLAGS) -o libsvn_swig_ruby-1.la $(LT_NO_UNDEFINED) $(libsvn_swig_ruby_OBJECTS) ../../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvn_test_PATH = subversion/tests
+libsvn_test_DEPS = subversion/tests/svn_test_fs.lo subversion/tests/svn_test_main.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_test_OBJECTS = svn_test_fs.lo svn_test_main.lo
+subversion/tests/libsvn_test-1.la: $(libsvn_test_DEPS)
+ cd subversion/tests && $(LINK_LIB) $(libsvn_test_LDFLAGS) -o libsvn_test-1.la $(libsvn_test_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvn_wc_PATH = subversion/libsvn_wc
+libsvn_wc_DEPS = subversion/libsvn_wc/adm_crawler.lo subversion/libsvn_wc/adm_files.lo subversion/libsvn_wc/adm_ops.lo subversion/libsvn_wc/ambient_depth_filter_editor.lo subversion/libsvn_wc/cleanup.lo subversion/libsvn_wc/conflicts.lo subversion/libsvn_wc/context.lo subversion/libsvn_wc/copy.lo subversion/libsvn_wc/crop.lo subversion/libsvn_wc/delete.lo subversion/libsvn_wc/deprecated.lo subversion/libsvn_wc/diff_editor.lo subversion/libsvn_wc/diff_local.lo subversion/libsvn_wc/entries.lo subversion/libsvn_wc/externals.lo subversion/libsvn_wc/info.lo subversion/libsvn_wc/lock.lo subversion/libsvn_wc/merge.lo subversion/libsvn_wc/node.lo subversion/libsvn_wc/old-and-busted.lo subversion/libsvn_wc/props.lo subversion/libsvn_wc/questions.lo subversion/libsvn_wc/relocate.lo subversion/libsvn_wc/revert.lo subversion/libsvn_wc/revision_status.lo subversion/libsvn_wc/status.lo subversion/libsvn_wc/translate.lo subversion/libsvn_wc/tree_conflicts.lo subversion/libsvn_wc/update_editor.lo subversion/libsvn_wc/upgrade.lo subversion/libsvn_wc/util.lo subversion/libsvn_wc/wc_db.lo subversion/libsvn_wc/wc_db_pristine.lo subversion/libsvn_wc/wc_db_update_move.lo subversion/libsvn_wc/wc_db_util.lo subversion/libsvn_wc/wc_db_wcroot.lo subversion/libsvn_wc/wcroot_anchor.lo subversion/libsvn_wc/workqueue.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_wc_OBJECTS = adm_crawler.lo adm_files.lo adm_ops.lo ambient_depth_filter_editor.lo cleanup.lo conflicts.lo context.lo copy.lo crop.lo delete.lo deprecated.lo diff_editor.lo diff_local.lo entries.lo externals.lo info.lo lock.lo merge.lo node.lo old-and-busted.lo props.lo questions.lo relocate.lo revert.lo revision_status.lo status.lo translate.lo tree_conflicts.lo update_editor.lo upgrade.lo util.lo wc_db.lo wc_db_pristine.lo wc_db_update_move.lo wc_db_util.lo wc_db_wcroot.lo wcroot_anchor.lo workqueue.lo
+subversion/libsvn_wc/libsvn_wc-1.la: $(libsvn_wc_DEPS)
+ cd subversion/libsvn_wc && $(LINK_LIB) $(libsvn_wc_LDFLAGS) -o libsvn_wc-1.la $(LT_NO_UNDEFINED) $(libsvn_wc_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvncxxhl_PATH = subversion/bindings/cxxhl
+libsvncxxhl_DEPS = subversion/bindings/cxxhl/src/exception.lo subversion/bindings/cxxhl/src/tristate.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs/libsvn_fs-1.la
+libsvncxxhl_OBJECTS = src/exception.lo src/tristate.lo
+subversion/bindings/cxxhl/libsvncxxhl-1.la: $(libsvncxxhl_DEPS)
+ cd subversion/bindings/cxxhl && $(LINK_CXX_LIB) $(libsvncxxhl_LDFLAGS) -o libsvncxxhl-1.la $(LT_NO_UNDEFINED) $(libsvncxxhl_OBJECTS) ../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+libsvnjavahl_PATH = subversion/bindings/javahl/native
+libsvnjavahl_DEPS = $(javahl_javah_DEPS) $(javahl_java_DEPS) $(javahl_callback_javah_DEPS) $(javahl_types_javah_DEPS) subversion/bindings/javahl/native/Array.lo subversion/bindings/javahl/native/BlameCallback.lo subversion/bindings/javahl/native/ChangelistCallback.lo subversion/bindings/javahl/native/ClientContext.lo subversion/bindings/javahl/native/CommitCallback.lo subversion/bindings/javahl/native/CommitMessage.lo subversion/bindings/javahl/native/CopySources.lo subversion/bindings/javahl/native/CreateJ.lo subversion/bindings/javahl/native/DiffOptions.lo subversion/bindings/javahl/native/DiffSummaryReceiver.lo subversion/bindings/javahl/native/EnumMapper.lo subversion/bindings/javahl/native/File.lo subversion/bindings/javahl/native/ImportFilterCallback.lo subversion/bindings/javahl/native/InfoCallback.lo subversion/bindings/javahl/native/InputStream.lo subversion/bindings/javahl/native/JNIByteArray.lo subversion/bindings/javahl/native/JNICriticalSection.lo subversion/bindings/javahl/native/JNIMutex.lo subversion/bindings/javahl/native/JNIStackElement.lo subversion/bindings/javahl/native/JNIStringHolder.lo subversion/bindings/javahl/native/JNIThreadData.lo subversion/bindings/javahl/native/JNIUtil.lo subversion/bindings/javahl/native/ListCallback.lo subversion/bindings/javahl/native/LogMessageCallback.lo subversion/bindings/javahl/native/MessageReceiver.lo subversion/bindings/javahl/native/OutputStream.lo subversion/bindings/javahl/native/PatchCallback.lo subversion/bindings/javahl/native/Path.lo subversion/bindings/javahl/native/Pool.lo subversion/bindings/javahl/native/Prompter.lo subversion/bindings/javahl/native/ProplistCallback.lo subversion/bindings/javahl/native/ReposFreezeAction.lo subversion/bindings/javahl/native/ReposNotifyCallback.lo subversion/bindings/javahl/native/Revision.lo subversion/bindings/javahl/native/RevisionRange.lo subversion/bindings/javahl/native/RevpropTable.lo subversion/bindings/javahl/native/SVNBase.lo subversion/bindings/javahl/native/SVNClient.lo subversion/bindings/javahl/native/SVNRepos.lo subversion/bindings/javahl/native/StatusCallback.lo subversion/bindings/javahl/native/StringArray.lo subversion/bindings/javahl/native/Targets.lo subversion/bindings/javahl/native/VersionExtended.lo subversion/bindings/javahl/native/libsvnjavahl.la.lo subversion/bindings/javahl/native/org_apache_subversion_javahl_NativeResources.lo subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.lo subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.lo subversion/bindings/javahl/native/org_apache_subversion_javahl_types_Version.lo subversion/bindings/javahl/native/org_apache_subversion_javahl_types_VersionExtended.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_fs/libsvn_fs-1.la
+libsvnjavahl_OBJECTS = Array.lo BlameCallback.lo ChangelistCallback.lo ClientContext.lo CommitCallback.lo CommitMessage.lo CopySources.lo CreateJ.lo DiffOptions.lo DiffSummaryReceiver.lo EnumMapper.lo File.lo ImportFilterCallback.lo InfoCallback.lo InputStream.lo JNIByteArray.lo JNICriticalSection.lo JNIMutex.lo JNIStackElement.lo JNIStringHolder.lo JNIThreadData.lo JNIUtil.lo ListCallback.lo LogMessageCallback.lo MessageReceiver.lo OutputStream.lo PatchCallback.lo Path.lo Pool.lo Prompter.lo ProplistCallback.lo ReposFreezeAction.lo ReposNotifyCallback.lo Revision.lo RevisionRange.lo RevpropTable.lo SVNBase.lo SVNClient.lo SVNRepos.lo StatusCallback.lo StringArray.lo Targets.lo VersionExtended.lo libsvnjavahl.la.lo org_apache_subversion_javahl_NativeResources.lo org_apache_subversion_javahl_SVNClient.lo org_apache_subversion_javahl_SVNRepos.lo org_apache_subversion_javahl_types_Version.lo org_apache_subversion_javahl_types_VersionExtended.lo
+subversion/bindings/javahl/native/libsvnjavahl-1.la: $(libsvnjavahl_DEPS)
+ cd subversion/bindings/javahl/native && $(LINK_JAVAHL_CXX) $(libsvnjavahl_LDFLAGS) -o libsvnjavahl-1.la $(LT_NO_UNDEFINED) $(libsvnjavahl_OBJECTS) ../../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la ../../../../subversion/libsvn_fs/libsvn_fs-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+locale_PATH = subversion/po
+locale_DEPS = subversion/po/de.mo subversion/po/es.mo subversion/po/fr.mo subversion/po/it.mo subversion/po/ja.mo subversion/po/ko.mo subversion/po/nb.mo subversion/po/pl.mo subversion/po/pt_BR.mo subversion/po/sv.mo subversion/po/zh_CN.mo subversion/po/zh_TW.mo
+locale: $(locale_DEPS)
+
+locks_test_PATH = subversion/tests/libsvn_fs
+locks_test_DEPS = subversion/tests/libsvn_fs/locks-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+locks_test_OBJECTS = locks-test.lo
+subversion/tests/libsvn_fs/locks-test$(EXEEXT): $(locks_test_DEPS)
+ cd subversion/tests/libsvn_fs && $(LINK) $(locks_test_LDFLAGS) -o locks-test$(EXEEXT) $(locks_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+mergeinfo_test_PATH = subversion/tests/libsvn_subr
+mergeinfo_test_DEPS = subversion/tests/libsvn_subr/mergeinfo-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+mergeinfo_test_OBJECTS = mergeinfo-test.lo
+subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT): $(mergeinfo_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(mergeinfo_test_LDFLAGS) -o mergeinfo-test$(EXEEXT) $(mergeinfo_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+mod_authz_svn_PATH = subversion/mod_authz_svn
+mod_authz_svn_DEPS = subversion/mod_authz_svn/mod_authz_svn.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/mod_dav_svn/mod_dav_svn.la
+mod_authz_svn_OBJECTS = mod_authz_svn.lo
+subversion/mod_authz_svn/mod_authz_svn.la: $(mod_authz_svn_DEPS)
+ if $(INSTALL_APACHE_MODS) ; then cd subversion/mod_authz_svn && $(LINK_APACHE_MOD) $(mod_authz_svn_LDFLAGS) -o mod_authz_svn.la $(LT_NO_UNDEFINED) $(mod_authz_svn_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(LIBS) ; else echo "fake" > subversion/mod_authz_svn/mod_authz_svn.la ; fi
+
+mod_dav_svn_PATH = subversion/mod_dav_svn
+mod_dav_svn_DEPS = subversion/mod_dav_svn/activity.lo subversion/mod_dav_svn/authz.lo subversion/mod_dav_svn/deadprops.lo subversion/mod_dav_svn/liveprops.lo subversion/mod_dav_svn/lock.lo subversion/mod_dav_svn/merge.lo subversion/mod_dav_svn/mirror.lo subversion/mod_dav_svn/mod_dav_svn.lo subversion/mod_dav_svn/posts/create_txn.lo subversion/mod_dav_svn/reports/dated-rev.lo subversion/mod_dav_svn/reports/deleted-rev.lo subversion/mod_dav_svn/reports/file-revs.lo subversion/mod_dav_svn/reports/get-location-segments.lo subversion/mod_dav_svn/reports/get-locations.lo subversion/mod_dav_svn/reports/get-locks.lo subversion/mod_dav_svn/reports/inherited-props.lo subversion/mod_dav_svn/reports/log.lo subversion/mod_dav_svn/reports/mergeinfo.lo subversion/mod_dav_svn/reports/replay.lo subversion/mod_dav_svn/reports/update.lo subversion/mod_dav_svn/repos.lo subversion/mod_dav_svn/util.lo subversion/mod_dav_svn/version.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+mod_dav_svn_OBJECTS = activity.lo authz.lo deadprops.lo liveprops.lo lock.lo merge.lo mirror.lo mod_dav_svn.lo posts/create_txn.lo reports/dated-rev.lo reports/deleted-rev.lo reports/file-revs.lo reports/get-location-segments.lo reports/get-locations.lo reports/get-locks.lo reports/inherited-props.lo reports/log.lo reports/mergeinfo.lo reports/replay.lo reports/update.lo repos.lo util.lo version.lo
+subversion/mod_dav_svn/mod_dav_svn.la: $(mod_dav_svn_DEPS)
+ if $(INSTALL_APACHE_MODS) ; then cd subversion/mod_dav_svn && $(LINK_APACHE_MOD) $(mod_dav_svn_LDFLAGS) -o mod_dav_svn.la $(LT_NO_UNDEFINED) $(mod_dav_svn_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(LIBS) ; else echo "fake" > subversion/mod_dav_svn/mod_dav_svn.la ; fi
+
+mod_dontdothat_PATH = tools/server-side/mod_dontdothat
+mod_dontdothat_DEPS = tools/server-side/mod_dontdothat/mod_dontdothat.lo subversion/libsvn_subr/libsvn_subr-1.la subversion/mod_dav_svn/mod_dav_svn.la
+mod_dontdothat_OBJECTS = mod_dontdothat.lo
+tools/server-side/mod_dontdothat/mod_dontdothat.la: $(mod_dontdothat_DEPS)
+ if $(INSTALL_APACHE_MODS) ; then cd tools/server-side/mod_dontdothat && $(LINK_APACHE_MOD) $(mod_dontdothat_LDFLAGS) -o mod_dontdothat.la $(LT_NO_UNDEFINED) $(mod_dontdothat_OBJECTS) ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_XML_LIBS) $(LIBS) ; else echo "fake" > tools/server-side/mod_dontdothat/mod_dontdothat.la ; fi
+
+named_atomic_proc_test_PATH = subversion/tests/libsvn_subr
+named_atomic_proc_test_DEPS = subversion/tests/libsvn_subr/named_atomic-test-proc.lo subversion/libsvn_subr/libsvn_subr-1.la
+named_atomic_proc_test_OBJECTS = named_atomic-test-proc.lo
+subversion/tests/libsvn_subr/named_atomic-proc-test$(EXEEXT): $(named_atomic_proc_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(named_atomic_proc_test_LDFLAGS) -o named_atomic-proc-test$(EXEEXT) $(named_atomic_proc_test_OBJECTS) ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+named_atomic_test_PATH = subversion/tests/libsvn_subr
+named_atomic_test_DEPS = subversion/tests/libsvn_subr/named_atomic-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+named_atomic_test_OBJECTS = named_atomic-test.lo
+subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT): $(named_atomic_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(named_atomic_test_LDFLAGS) -o named_atomic-test$(EXEEXT) $(named_atomic_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+op_depth_test_PATH = subversion/tests/libsvn_wc
+op_depth_test_DEPS = subversion/tests/libsvn_wc/op-depth-test.lo subversion/tests/libsvn_wc/utils.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la
+op_depth_test_OBJECTS = op-depth-test.lo utils.lo
+subversion/tests/libsvn_wc/op-depth-test$(EXEEXT): $(op_depth_test_DEPS)
+ cd subversion/tests/libsvn_wc && $(LINK) $(op_depth_test_LDFLAGS) -o op-depth-test$(EXEEXT) $(op_depth_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+opt_test_PATH = subversion/tests/libsvn_subr
+opt_test_DEPS = subversion/tests/libsvn_subr/opt-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+opt_test_OBJECTS = opt-test.lo
+subversion/tests/libsvn_subr/opt-test$(EXEEXT): $(opt_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(opt_test_LDFLAGS) -o opt-test$(EXEEXT) $(opt_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+parse_diff_test_PATH = subversion/tests/libsvn_diff
+parse_diff_test_DEPS = subversion/tests/libsvn_diff/parse-diff-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+parse_diff_test_OBJECTS = parse-diff-test.lo
+subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT): $(parse_diff_test_DEPS)
+ cd subversion/tests/libsvn_diff && $(LINK) $(parse_diff_test_LDFLAGS) -o parse-diff-test$(EXEEXT) $(parse_diff_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+path_test_PATH = subversion/tests/libsvn_subr
+path_test_DEPS = subversion/tests/libsvn_subr/path-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+path_test_OBJECTS = path-test.lo
+subversion/tests/libsvn_subr/path-test$(EXEEXT): $(path_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(path_test_LDFLAGS) -o path-test$(EXEEXT) $(path_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+perl_client_PATH = subversion/bindings/swig/perl/native
+perl_client_DEPS = subversion/bindings/swig/perl/native/svn_client.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la
+perl_client_OBJECTS = svn_client.lo
+subversion/bindings/swig/perl/native/_Client.la: $(perl_client_DEPS)
+ cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_client_LDFLAGS) -o _Client.la $(LT_NO_UNDEFINED) $(perl_client_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+perl_core_PATH = subversion/bindings/swig/perl/native
+perl_core_DEPS = subversion/bindings/swig/perl/native/core.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+perl_core_OBJECTS = core.lo
+subversion/bindings/swig/perl/native/_Core.la: $(perl_core_DEPS)
+ cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_core_LDFLAGS) -o _Core.la $(LT_NO_UNDEFINED) $(perl_core_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+perl_delta_PATH = subversion/bindings/swig/perl/native
+perl_delta_DEPS = subversion/bindings/swig/perl/native/svn_delta.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la
+perl_delta_OBJECTS = svn_delta.lo
+subversion/bindings/swig/perl/native/_Delta.la: $(perl_delta_DEPS)
+ cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_delta_LDFLAGS) -o _Delta.la $(LT_NO_UNDEFINED) $(perl_delta_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+perl_diff_PATH = subversion/bindings/swig/perl/native
+perl_diff_DEPS = subversion/bindings/swig/perl/native/svn_diff.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la
+perl_diff_OBJECTS = svn_diff.lo
+subversion/bindings/swig/perl/native/_Diff.la: $(perl_diff_DEPS)
+ cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_diff_LDFLAGS) -o _Diff.la $(LT_NO_UNDEFINED) $(perl_diff_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+perl_fs_PATH = subversion/bindings/swig/perl/native
+perl_fs_DEPS = subversion/bindings/swig/perl/native/svn_fs.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la
+perl_fs_OBJECTS = svn_fs.lo
+subversion/bindings/swig/perl/native/_Fs.la: $(perl_fs_DEPS)
+ cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_fs_LDFLAGS) -o _Fs.la $(LT_NO_UNDEFINED) $(perl_fs_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+perl_ra_PATH = subversion/bindings/swig/perl/native
+perl_ra_DEPS = subversion/bindings/swig/perl/native/svn_ra.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la
+perl_ra_OBJECTS = svn_ra.lo
+subversion/bindings/swig/perl/native/_Ra.la: $(perl_ra_DEPS)
+ cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_ra_LDFLAGS) -o _Ra.la $(LT_NO_UNDEFINED) $(perl_ra_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+perl_repos_PATH = subversion/bindings/swig/perl/native
+perl_repos_DEPS = subversion/bindings/swig/perl/native/svn_repos.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la
+perl_repos_OBJECTS = svn_repos.lo
+subversion/bindings/swig/perl/native/_Repos.la: $(perl_repos_DEPS)
+ cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_repos_LDFLAGS) -o _Repos.la $(LT_NO_UNDEFINED) $(perl_repos_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+perl_wc_PATH = subversion/bindings/swig/perl/native
+perl_wc_DEPS = subversion/bindings/swig/perl/native/svn_wc.lo subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/perl/native/_Core.la
+perl_wc_OBJECTS = svn_wc.lo
+subversion/bindings/swig/perl/native/_Wc.la: $(perl_wc_DEPS)
+ cd subversion/bindings/swig/perl/native && $(LINK_PL_WRAPPER) $(perl_wc_LDFLAGS) -o _Wc.la $(LT_NO_UNDEFINED) $(perl_wc_OBJECTS) ../../../../../subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la ../../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+pristine_store_test_PATH = subversion/tests/libsvn_wc
+pristine_store_test_DEPS = subversion/tests/libsvn_wc/pristine-store-test.lo subversion/tests/libsvn_wc/utils.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la
+pristine_store_test_OBJECTS = pristine-store-test.lo utils.lo
+subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT): $(pristine_store_test_DEPS)
+ cd subversion/tests/libsvn_wc && $(LINK) $(pristine_store_test_LDFLAGS) -o pristine-store-test$(EXEEXT) $(pristine_store_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+python_client_PATH = subversion/bindings/swig/python
+python_client_DEPS = subversion/bindings/swig/python/svn_client.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la
+python_client_OBJECTS = svn_client.lo
+subversion/bindings/swig/python/_client.la: $(python_client_DEPS)
+ cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_client_LDFLAGS) -o _client.la $(LT_NO_UNDEFINED) $(python_client_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+python_core_PATH = subversion/bindings/swig/python
+python_core_DEPS = subversion/bindings/swig/python/core.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+python_core_OBJECTS = core.lo
+subversion/bindings/swig/python/_core.la: $(python_core_DEPS)
+ cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_core_LDFLAGS) -o _core.la $(LT_NO_UNDEFINED) $(python_core_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+python_delta_PATH = subversion/bindings/swig/python
+python_delta_DEPS = subversion/bindings/swig/python/svn_delta.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la
+python_delta_OBJECTS = svn_delta.lo
+subversion/bindings/swig/python/_delta.la: $(python_delta_DEPS)
+ cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_delta_LDFLAGS) -o _delta.la $(LT_NO_UNDEFINED) $(python_delta_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+python_diff_PATH = subversion/bindings/swig/python
+python_diff_DEPS = subversion/bindings/swig/python/svn_diff.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la
+python_diff_OBJECTS = svn_diff.lo
+subversion/bindings/swig/python/_diff.la: $(python_diff_DEPS)
+ cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_diff_LDFLAGS) -o _diff.la $(LT_NO_UNDEFINED) $(python_diff_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+python_fs_PATH = subversion/bindings/swig/python
+python_fs_DEPS = subversion/bindings/swig/python/svn_fs.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la
+python_fs_OBJECTS = svn_fs.lo
+subversion/bindings/swig/python/_fs.la: $(python_fs_DEPS)
+ cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_fs_LDFLAGS) -o _fs.la $(LT_NO_UNDEFINED) $(python_fs_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+python_ra_PATH = subversion/bindings/swig/python
+python_ra_DEPS = subversion/bindings/swig/python/svn_ra.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la
+python_ra_OBJECTS = svn_ra.lo
+subversion/bindings/swig/python/_ra.la: $(python_ra_DEPS)
+ cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_ra_LDFLAGS) -o _ra.la $(LT_NO_UNDEFINED) $(python_ra_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+python_repos_PATH = subversion/bindings/swig/python
+python_repos_DEPS = subversion/bindings/swig/python/svn_repos.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la
+python_repos_OBJECTS = svn_repos.lo
+subversion/bindings/swig/python/_repos.la: $(python_repos_DEPS)
+ cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_repos_LDFLAGS) -o _repos.la $(LT_NO_UNDEFINED) $(python_repos_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+python_wc_PATH = subversion/bindings/swig/python
+python_wc_DEPS = subversion/bindings/swig/python/svn_wc.lo subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/python/_core.la
+python_wc_OBJECTS = svn_wc.lo
+subversion/bindings/swig/python/_wc.la: $(python_wc_DEPS)
+ cd subversion/bindings/swig/python && $(LINK_PY_WRAPPER) $(python_wc_LDFLAGS) -o _wc.la $(LT_NO_UNDEFINED) $(python_wc_OBJECTS) ../../../../subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la ../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+ra_local_test_PATH = subversion/tests/libsvn_ra_local
+ra_local_test_DEPS = subversion/tests/libsvn_ra_local/ra-local-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_ra_local/libsvn_ra_local-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+ra_local_test_OBJECTS = ra-local-test.lo
+subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT): $(ra_local_test_DEPS)
+ cd subversion/tests/libsvn_ra_local && $(LINK) $(ra_local_test_LDFLAGS) -o ra-local-test$(EXEEXT) $(ra_local_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_ra_local/libsvn_ra_local-1.la ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+ra_test_PATH = subversion/tests/libsvn_ra
+ra_test_DEPS = subversion/tests/libsvn_ra/ra-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+ra_test_OBJECTS = ra-test.lo
+subversion/tests/libsvn_ra/ra-test$(EXEEXT): $(ra_test_DEPS)
+ cd subversion/tests/libsvn_ra && $(LINK) $(ra_test_LDFLAGS) -o ra-test$(EXEEXT) $(ra_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+random_test_PATH = subversion/tests/libsvn_delta
+random_test_DEPS = subversion/tests/libsvn_delta/random-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+random_test_OBJECTS = random-test.lo
+subversion/tests/libsvn_delta/random-test$(EXEEXT): $(random_test_DEPS)
+ cd subversion/tests/libsvn_delta && $(LINK) $(random_test_LDFLAGS) -o random-test$(EXEEXT) $(random_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+repos_test_PATH = subversion/tests/libsvn_repos
+repos_test_DEPS = subversion/tests/libsvn_repos/dir-delta-editor.lo subversion/tests/libsvn_repos/repos-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+repos_test_OBJECTS = dir-delta-editor.lo repos-test.lo
+subversion/tests/libsvn_repos/repos-test$(EXEEXT): $(repos_test_DEPS)
+ cd subversion/tests/libsvn_repos && $(LINK) $(repos_test_LDFLAGS) -o repos-test$(EXEEXT) $(repos_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+revision_test_PATH = subversion/tests/libsvn_subr
+revision_test_DEPS = subversion/tests/libsvn_subr/revision-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+revision_test_OBJECTS = revision-test.lo
+subversion/tests/libsvn_subr/revision-test$(EXEEXT): $(revision_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(revision_test_LDFLAGS) -o revision-test$(EXEEXT) $(revision_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+ruby_client_PATH = subversion/bindings/swig/ruby
+ruby_client_DEPS = subversion/bindings/swig/ruby/svn_client.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la
+ruby_client_OBJECTS = svn_client.lo
+subversion/bindings/swig/ruby/client.la: $(ruby_client_DEPS)
+ cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_client_LDFLAGS) -o client.la $(LT_NO_UNDEFINED) $(ruby_client_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_client/libsvn_client-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+ruby_core_PATH = subversion/bindings/swig/ruby
+ruby_core_DEPS = subversion/bindings/swig/ruby/core.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+ruby_core_OBJECTS = core.lo
+subversion/bindings/swig/ruby/core.la: $(ruby_core_DEPS)
+ cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_core_LDFLAGS) -o core.la $(LT_NO_UNDEFINED) $(ruby_core_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+ruby_delta_PATH = subversion/bindings/swig/ruby
+ruby_delta_DEPS = subversion/bindings/swig/ruby/svn_delta.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la
+ruby_delta_OBJECTS = svn_delta.lo
+subversion/bindings/swig/ruby/delta.la: $(ruby_delta_DEPS)
+ cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_delta_LDFLAGS) -o delta.la $(LT_NO_UNDEFINED) $(ruby_delta_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+ruby_diff_PATH = subversion/bindings/swig/ruby
+ruby_diff_DEPS = subversion/bindings/swig/ruby/svn_diff.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la
+ruby_diff_OBJECTS = svn_diff.lo
+subversion/bindings/swig/ruby/diff.la: $(ruby_diff_DEPS)
+ cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_diff_LDFLAGS) -o diff.la $(LT_NO_UNDEFINED) $(ruby_diff_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_diff/libsvn_diff-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+ruby_fs_PATH = subversion/bindings/swig/ruby
+ruby_fs_DEPS = subversion/bindings/swig/ruby/svn_fs.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la
+ruby_fs_OBJECTS = svn_fs.lo
+subversion/bindings/swig/ruby/fs.la: $(ruby_fs_DEPS)
+ cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_fs_LDFLAGS) -o fs.la $(LT_NO_UNDEFINED) $(ruby_fs_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+ruby_ra_PATH = subversion/bindings/swig/ruby
+ruby_ra_DEPS = subversion/bindings/swig/ruby/svn_ra.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la
+ruby_ra_OBJECTS = svn_ra.lo
+subversion/bindings/swig/ruby/ra.la: $(ruby_ra_DEPS)
+ cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_ra_LDFLAGS) -o ra.la $(LT_NO_UNDEFINED) $(ruby_ra_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+ruby_repos_PATH = subversion/bindings/swig/ruby
+ruby_repos_DEPS = subversion/bindings/swig/ruby/svn_repos.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la
+ruby_repos_OBJECTS = svn_repos.lo
+subversion/bindings/swig/ruby/repos.la: $(ruby_repos_DEPS)
+ cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_repos_LDFLAGS) -o repos.la $(LT_NO_UNDEFINED) $(ruby_repos_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_repos/libsvn_repos-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+ruby_wc_PATH = subversion/bindings/swig/ruby
+ruby_wc_DEPS = subversion/bindings/swig/ruby/svn_wc.lo subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/bindings/swig/ruby/core.la
+ruby_wc_OBJECTS = svn_wc.lo
+subversion/bindings/swig/ruby/wc.la: $(ruby_wc_DEPS)
+ cd subversion/bindings/swig/ruby && $(LINK_RB_WRAPPER) $(ruby_wc_LDFLAGS) -o wc.la $(LT_NO_UNDEFINED) $(ruby_wc_OBJECTS) ../../../../subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la ../../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+skel_test_PATH = subversion/tests/libsvn_subr
+skel_test_DEPS = subversion/tests/libsvn_subr/skel-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+skel_test_OBJECTS = skel-test.lo
+subversion/tests/libsvn_subr/skel-test$(EXEEXT): $(skel_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(skel_test_LDFLAGS) -o skel-test$(EXEEXT) $(skel_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+spillbuf_test_PATH = subversion/tests/libsvn_subr
+spillbuf_test_DEPS = subversion/tests/libsvn_subr/spillbuf-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+spillbuf_test_OBJECTS = spillbuf-test.lo
+subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT): $(spillbuf_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(spillbuf_test_LDFLAGS) -o spillbuf-test$(EXEEXT) $(spillbuf_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+stream_test_PATH = subversion/tests/libsvn_subr
+stream_test_DEPS = subversion/tests/libsvn_subr/stream-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+stream_test_OBJECTS = stream-test.lo
+subversion/tests/libsvn_subr/stream-test$(EXEEXT): $(stream_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(stream_test_LDFLAGS) -o stream-test$(EXEEXT) $(stream_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+string_test_PATH = subversion/tests/libsvn_subr
+string_test_DEPS = subversion/tests/libsvn_subr/string-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+string_test_OBJECTS = string-test.lo
+subversion/tests/libsvn_subr/string-test$(EXEEXT): $(string_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(string_test_LDFLAGS) -o string-test$(EXEEXT) $(string_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+strings_reps_test_PATH = subversion/tests/libsvn_fs_base
+strings_reps_test_DEPS = subversion/tests/libsvn_fs_base/strings-reps-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_fs_base/libsvn_fs_base-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+strings_reps_test_OBJECTS = strings-reps-test.lo
+subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT): $(strings_reps_test_DEPS)
+ cd subversion/tests/libsvn_fs_base && $(LINK) $(strings_reps_test_LDFLAGS) -o strings-reps-test$(EXEEXT) $(strings_reps_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_fs/libsvn_fs-1.la ../../../subversion/libsvn_fs_base/libsvn_fs_base-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+subst_translate_test_PATH = subversion/tests/libsvn_subr
+subst_translate_test_DEPS = subversion/tests/libsvn_subr/subst_translate-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+subst_translate_test_OBJECTS = subst_translate-test.lo
+subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT): $(subst_translate_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(subst_translate_test_LDFLAGS) -o subst_translate-test$(EXEEXT) $(subst_translate_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+svn_PATH = subversion/svn
+svn_DEPS = subversion/svn/add-cmd.lo subversion/svn/blame-cmd.lo subversion/svn/cat-cmd.lo subversion/svn/changelist-cmd.lo subversion/svn/checkout-cmd.lo subversion/svn/cl-conflicts.lo subversion/svn/cleanup-cmd.lo subversion/svn/commit-cmd.lo subversion/svn/conflict-callbacks.lo subversion/svn/copy-cmd.lo subversion/svn/delete-cmd.lo subversion/svn/deprecated.lo subversion/svn/diff-cmd.lo subversion/svn/export-cmd.lo subversion/svn/file-merge.lo subversion/svn/help-cmd.lo subversion/svn/import-cmd.lo subversion/svn/info-cmd.lo subversion/svn/list-cmd.lo subversion/svn/lock-cmd.lo subversion/svn/log-cmd.lo subversion/svn/merge-cmd.lo subversion/svn/mergeinfo-cmd.lo subversion/svn/mkdir-cmd.lo subversion/svn/move-cmd.lo subversion/svn/notify.lo subversion/svn/patch-cmd.lo subversion/svn/propdel-cmd.lo subversion/svn/propedit-cmd.lo subversion/svn/propget-cmd.lo subversion/svn/proplist-cmd.lo subversion/svn/props.lo subversion/svn/propset-cmd.lo subversion/svn/relocate-cmd.lo subversion/svn/resolve-cmd.lo subversion/svn/resolved-cmd.lo subversion/svn/revert-cmd.lo subversion/svn/status-cmd.lo subversion/svn/status.lo subversion/svn/svn.lo subversion/svn/switch-cmd.lo subversion/svn/unlock-cmd.lo subversion/svn/update-cmd.lo subversion/svn/upgrade-cmd.lo subversion/svn/util.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svn_OBJECTS = add-cmd.lo blame-cmd.lo cat-cmd.lo changelist-cmd.lo checkout-cmd.lo cl-conflicts.lo cleanup-cmd.lo commit-cmd.lo conflict-callbacks.lo copy-cmd.lo delete-cmd.lo deprecated.lo diff-cmd.lo export-cmd.lo file-merge.lo help-cmd.lo import-cmd.lo info-cmd.lo list-cmd.lo lock-cmd.lo log-cmd.lo merge-cmd.lo mergeinfo-cmd.lo mkdir-cmd.lo move-cmd.lo notify.lo patch-cmd.lo propdel-cmd.lo propedit-cmd.lo propget-cmd.lo proplist-cmd.lo props.lo propset-cmd.lo relocate-cmd.lo resolve-cmd.lo resolved-cmd.lo revert-cmd.lo status-cmd.lo status.lo svn.lo switch-cmd.lo unlock-cmd.lo update-cmd.lo upgrade-cmd.lo util.lo
+subversion/svn/svn$(EXEEXT): $(svn_DEPS)
+ cd subversion/svn && $(LINK) $(svn_LDFLAGS) -o svn$(EXEEXT) $(svn_OBJECTS) ../../subversion/libsvn_client/libsvn_client-1.la ../../subversion/libsvn_wc/libsvn_wc-1.la ../../subversion/libsvn_ra/libsvn_ra-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+svn_bench_PATH = tools/client-side/svn-bench
+svn_bench_DEPS = tools/client-side/svn-bench/help-cmd.lo tools/client-side/svn-bench/notify.lo tools/client-side/svn-bench/null-export-cmd.lo tools/client-side/svn-bench/null-list-cmd.lo tools/client-side/svn-bench/null-log-cmd.lo tools/client-side/svn-bench/svn-bench.lo tools/client-side/svn-bench/util.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_delta/libsvn_delta-1.la
+svn_bench_OBJECTS = help-cmd.lo notify.lo null-export-cmd.lo null-list-cmd.lo null-log-cmd.lo svn-bench.lo util.lo
+tools/client-side/svn-bench/svn-bench$(EXEEXT): $(svn_bench_DEPS)
+ cd tools/client-side/svn-bench && $(LINK) $(svn_bench_LDFLAGS) -o svn-bench$(EXEEXT) $(svn_bench_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_ra/libsvn_ra-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+svn_populate_node_origins_index_PATH = tools/server-side
+svn_populate_node_origins_index_DEPS = tools/server-side/svn-populate-node-origins-index.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svn_populate_node_origins_index_OBJECTS = svn-populate-node-origins-index.lo
+tools/server-side/svn-populate-node-origins-index$(EXEEXT): $(svn_populate_node_origins_index_DEPS)
+ cd tools/server-side && $(LINK) $(svn_populate_node_origins_index_LDFLAGS) -o svn-populate-node-origins-index$(EXEEXT) $(svn_populate_node_origins_index_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+svn_rep_sharing_stats_PATH = tools/server-side
+svn_rep_sharing_stats_DEPS = tools/server-side/svn-rep-sharing-stats.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_fs_fs/libsvn_fs_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svn_rep_sharing_stats_OBJECTS = svn-rep-sharing-stats.lo
+tools/server-side/svn-rep-sharing-stats$(EXEEXT): $(svn_rep_sharing_stats_DEPS)
+ cd tools/server-side && $(LINK) $(svn_rep_sharing_stats_LDFLAGS) -o svn-rep-sharing-stats$(EXEEXT) $(svn_rep_sharing_stats_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_fs_fs/libsvn_fs_fs-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+svnadmin_PATH = subversion/svnadmin
+svnadmin_DEPS = subversion/svnadmin/svnadmin.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svnadmin_OBJECTS = svnadmin.lo
+subversion/svnadmin/svnadmin$(EXEEXT): $(svnadmin_DEPS)
+ cd subversion/svnadmin && $(LINK) $(svnadmin_LDFLAGS) -o svnadmin$(EXEEXT) $(svnadmin_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+svnauthz_PATH = tools/server-side
+svnauthz_DEPS = tools/server-side/svnauthz.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svnauthz_OBJECTS = svnauthz.lo
+tools/server-side/svnauthz$(EXEEXT): $(svnauthz_DEPS)
+ cd tools/server-side && $(LINK) $(svnauthz_LDFLAGS) -o svnauthz$(EXEEXT) $(svnauthz_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+svnauthz_validate_PATH = tools/server-side
+svnauthz_validate_DEPS = tools/server-side/svnauthz.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svnauthz_validate_OBJECTS = svnauthz.lo
+tools/server-side/svnauthz-validate$(EXEEXT): $(svnauthz_validate_DEPS)
+ cd tools/server-side && $(LINK) $(svnauthz_validate_LDFLAGS) -o svnauthz-validate$(EXEEXT) $(svnauthz_validate_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+svndiff_test_PATH = subversion/tests/libsvn_delta
+svndiff_test_DEPS = subversion/tests/libsvn_delta/svndiff-test.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svndiff_test_OBJECTS = svndiff-test.lo
+subversion/tests/libsvn_delta/svndiff-test$(EXEEXT): $(svndiff_test_DEPS)
+ cd subversion/tests/libsvn_delta && $(LINK) $(svndiff_test_LDFLAGS) -o svndiff-test$(EXEEXT) $(svndiff_test_OBJECTS) ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+svndumpfilter_PATH = subversion/svndumpfilter
+svndumpfilter_DEPS = subversion/svndumpfilter/svndumpfilter.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svndumpfilter_OBJECTS = svndumpfilter.lo
+subversion/svndumpfilter/svndumpfilter$(EXEEXT): $(svndumpfilter_DEPS)
+ cd subversion/svndumpfilter && $(LINK) $(svndumpfilter_LDFLAGS) -o svndumpfilter$(EXEEXT) $(svndumpfilter_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+svnlook_PATH = subversion/svnlook
+svnlook_DEPS = subversion/svnlook/svnlook.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svnlook_OBJECTS = svnlook.lo
+subversion/svnlook/svnlook$(EXEEXT): $(svnlook_DEPS)
+ cd subversion/svnlook && $(LINK) $(svnlook_LDFLAGS) -o svnlook$(EXEEXT) $(svnlook_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_diff/libsvn_diff-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+svnmucc_PATH = subversion/svnmucc
+svnmucc_DEPS = subversion/svnmucc/svnmucc.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_delta/libsvn_delta-1.la
+svnmucc_OBJECTS = svnmucc.lo
+subversion/svnmucc/svnmucc$(EXEEXT): $(svnmucc_DEPS)
+ cd subversion/svnmucc && $(LINK) $(svnmucc_LDFLAGS) -o svnmucc$(EXEEXT) $(svnmucc_OBJECTS) ../../subversion/libsvn_client/libsvn_client-1.la ../../subversion/libsvn_ra/libsvn_ra-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+svnraisetreeconflict_PATH = tools/dev/svnraisetreeconflict
+svnraisetreeconflict_DEPS = tools/dev/svnraisetreeconflict/svnraisetreeconflict.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svnraisetreeconflict_OBJECTS = svnraisetreeconflict.lo
+tools/dev/svnraisetreeconflict/svnraisetreeconflict$(EXEEXT): $(svnraisetreeconflict_DEPS)
+ cd tools/dev/svnraisetreeconflict && $(LINK) $(svnraisetreeconflict_LDFLAGS) -o svnraisetreeconflict$(EXEEXT) $(svnraisetreeconflict_OBJECTS) ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+svnrdump_PATH = subversion/svnrdump
+svnrdump_DEPS = subversion/svnrdump/dump_editor.lo subversion/svnrdump/load_editor.lo subversion/svnrdump/svnrdump.lo subversion/svnrdump/util.lo subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svnrdump_OBJECTS = dump_editor.lo load_editor.lo svnrdump.lo util.lo
+subversion/svnrdump/svnrdump$(EXEEXT): $(svnrdump_DEPS)
+ cd subversion/svnrdump && $(LINK) $(svnrdump_LDFLAGS) -o svnrdump$(EXEEXT) $(svnrdump_OBJECTS) ../../subversion/libsvn_client/libsvn_client-1.la ../../subversion/libsvn_ra/libsvn_ra-1.la ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+svnserve_PATH = subversion/svnserve
+svnserve_DEPS = subversion/svnserve/cyrus_auth.lo subversion/svnserve/log-escape.lo subversion/svnserve/serve.lo subversion/svnserve/svnserve.lo subversion/svnserve/winservice.lo subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_ra_svn/libsvn_ra_svn-1.la
+svnserve_OBJECTS = cyrus_auth.lo log-escape.lo serve.lo svnserve.lo winservice.lo
+subversion/svnserve/svnserve$(EXEEXT): $(svnserve_DEPS)
+ cd subversion/svnserve && $(LINK) $(svnserve_LDFLAGS) -o svnserve$(EXEEXT) $(svnserve_OBJECTS) ../../subversion/libsvn_repos/libsvn_repos-1.la ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la ../../subversion/libsvn_ra_svn/libsvn_ra_svn-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_SASL_LIBS) $(LIBS)
+
+svnsync_PATH = subversion/svnsync
+svnsync_DEPS = subversion/svnsync/svnsync.lo subversion/svnsync/sync.lo subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svnsync_OBJECTS = svnsync.lo sync.lo
+subversion/svnsync/svnsync$(EXEEXT): $(svnsync_DEPS)
+ cd subversion/svnsync && $(LINK) $(svnsync_LDFLAGS) -o svnsync$(EXEEXT) $(svnsync_OBJECTS) ../../subversion/libsvn_ra/libsvn_ra-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APR_LIBS) $(LIBS)
+
+svnversion_PATH = subversion/svnversion
+svnversion_DEPS = subversion/svnversion/svnversion.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la
+svnversion_OBJECTS = svnversion.lo
+subversion/svnversion/svnversion$(EXEEXT): $(svnversion_DEPS)
+ cd subversion/svnversion && $(LINK) $(svnversion_LDFLAGS) -o svnversion$(EXEEXT) $(svnversion_OBJECTS) ../../subversion/libsvn_wc/libsvn_wc-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+time_test_PATH = subversion/tests/libsvn_subr
+time_test_DEPS = subversion/tests/libsvn_subr/time-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+time_test_OBJECTS = time-test.lo
+subversion/tests/libsvn_subr/time-test$(EXEEXT): $(time_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(time_test_LDFLAGS) -o time-test$(EXEEXT) $(time_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+translate_test_PATH = subversion/tests/libsvn_subr
+translate_test_DEPS = subversion/tests/libsvn_subr/translate-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+translate_test_OBJECTS = translate-test.lo
+subversion/tests/libsvn_subr/translate-test$(EXEEXT): $(translate_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(translate_test_LDFLAGS) -o translate-test$(EXEEXT) $(translate_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+utf_test_PATH = subversion/tests/libsvn_subr
+utf_test_DEPS = subversion/tests/libsvn_subr/utf-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+utf_test_OBJECTS = utf-test.lo
+subversion/tests/libsvn_subr/utf-test$(EXEEXT): $(utf_test_DEPS)
+ cd subversion/tests/libsvn_subr && $(LINK) $(utf_test_LDFLAGS) -o utf-test$(EXEEXT) $(utf_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+vdelta_test_PATH = subversion/tests/libsvn_delta
+vdelta_test_DEPS = subversion/tests/libsvn_delta/vdelta-test.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+vdelta_test_OBJECTS = vdelta-test.lo
+subversion/tests/libsvn_delta/vdelta-test$(EXEEXT): $(vdelta_test_DEPS)
+ cd subversion/tests/libsvn_delta && $(LINK) $(vdelta_test_LDFLAGS) -o vdelta-test$(EXEEXT) $(vdelta_test_OBJECTS) ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+wc_incomplete_tester_PATH = subversion/tests/libsvn_wc
+wc_incomplete_tester_DEPS = subversion/tests/libsvn_wc/wc-incomplete-tester.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la
+wc_incomplete_tester_OBJECTS = wc-incomplete-tester.lo
+subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT): $(wc_incomplete_tester_DEPS)
+ cd subversion/tests/libsvn_wc && $(LINK) $(wc_incomplete_tester_LDFLAGS) -o wc-incomplete-tester$(EXEEXT) $(wc_incomplete_tester_OBJECTS) ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+wc_lock_tester_PATH = subversion/tests/libsvn_wc
+wc_lock_tester_DEPS = subversion/tests/libsvn_wc/wc-lock-tester.lo subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la
+wc_lock_tester_OBJECTS = wc-lock-tester.lo
+subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT): $(wc_lock_tester_DEPS)
+ cd subversion/tests/libsvn_wc && $(LINK) $(wc_lock_tester_LDFLAGS) -o wc-lock-tester$(EXEEXT) $(wc_lock_tester_OBJECTS) ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+wc_queries_test_PATH = subversion/tests/libsvn_wc
+wc_queries_test_DEPS = subversion/tests/libsvn_wc/wc-queries-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_subr/libsvn_subr-1.la
+wc_queries_test_OBJECTS = wc-queries-test.lo
+subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT): $(wc_queries_test_DEPS)
+ cd subversion/tests/libsvn_wc && $(LINK) $(wc_queries_test_LDFLAGS) -o wc-queries-test$(EXEEXT) $(wc_queries_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_SQLITE_LIBS) $(LIBS)
+
+wc_test_PATH = subversion/tests/libsvn_wc
+wc_test_DEPS = subversion/tests/libsvn_wc/utils.lo subversion/tests/libsvn_wc/wc-test.lo subversion/libsvn_client/libsvn_client-1.la subversion/tests/libsvn_test-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_subr/libsvn_subr-1.la
+wc_test_OBJECTS = utils.lo wc-test.lo
+subversion/tests/libsvn_wc/wc-test$(EXEEXT): $(wc_test_DEPS)
+ cd subversion/tests/libsvn_wc && $(LINK) $(wc_test_LDFLAGS) -o wc-test$(EXEEXT) $(wc_test_OBJECTS) ../../../subversion/libsvn_client/libsvn_client-1.la ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_wc/libsvn_wc-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+window_test_PATH = subversion/tests/libsvn_delta
+window_test_DEPS = subversion/tests/libsvn_delta/window-test.lo subversion/tests/libsvn_test-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
+window_test_OBJECTS = window-test.lo
+subversion/tests/libsvn_delta/window-test$(EXEEXT): $(window_test_DEPS)
+ cd subversion/tests/libsvn_delta && $(LINK) $(window_test_LDFLAGS) -o window-test$(EXEEXT) $(window_test_OBJECTS) ../../../subversion/tests/libsvn_test-1.la ../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(LIBS)
+
+
+########################################
+# Section 6: Install-Group build targets
+########################################
+
+apache-mod: subversion/mod_authz_svn/mod_authz_svn.la subversion/mod_dav_svn/mod_dav_svn.la
+
+bdb-lib: subversion/libsvn_fs_base/libsvn_fs_base-1.la
+
+bdb-test: subversion/tests/libsvn_fs_base/changes-test$(EXEEXT) subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT) subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT)
+
+bin: subversion/svn/svn$(EXEEXT) subversion/svnadmin/svnadmin$(EXEEXT) subversion/svndumpfilter/svndumpfilter$(EXEEXT) subversion/svnlook/svnlook$(EXEEXT) subversion/svnmucc/svnmucc$(EXEEXT) subversion/svnrdump/svnrdump$(EXEEXT) subversion/svnserve/svnserve$(EXEEXT) subversion/svnsync/svnsync$(EXEEXT) subversion/svnversion/svnversion$(EXEEXT)
+
+cxxhl-lib: subversion/bindings/cxxhl/libsvncxxhl-1.la
+
+fsmod-lib: subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_fs_fs/libsvn_fs_fs-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la subversion/libsvn_subr/libsvn_subr-1.la
+
+gnome-keyring-lib: subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring-1.la
+
+javahl-callback-javah:
+
+javahl-compat-java:
+
+javahl-compat-tests:
+
+javahl-java:
+
+javahl-javah:
+
+javahl-lib: subversion/bindings/javahl/native/libsvnjavahl-1.la
+
+javahl-tests:
+
+javahl-types-javah:
+
+kwallet-lib: subversion/libsvn_auth_kwallet/libsvn_auth_kwallet-1.la
+
+lib: subversion/libsvn_client/libsvn_client-1.la subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_wc/libsvn_wc-1.la
+
+locale:
+
+ramod-lib: subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_ra_local/libsvn_ra_local-1.la subversion/libsvn_ra_svn/libsvn_ra_svn-1.la subversion/libsvn_repos/libsvn_repos-1.la
+
+serf-lib: subversion/libsvn_ra_serf/libsvn_ra_serf-1.la
+
+sub-test: subversion/tests/libsvn_subr/named_atomic-proc-test$(EXEEXT)
+
+swig-pl-lib: subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la
+
+swig-py: subversion/bindings/swig/python/_client.la subversion/bindings/swig/python/_core.la subversion/bindings/swig/python/_delta.la subversion/bindings/swig/python/_diff.la subversion/bindings/swig/python/_fs.la subversion/bindings/swig/python/_ra.la subversion/bindings/swig/python/_repos.la subversion/bindings/swig/python/_wc.la
+
+swig-py-lib: subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la
+
+swig-rb: subversion/bindings/swig/ruby/client.la subversion/bindings/swig/ruby/core.la subversion/bindings/swig/ruby/delta.la subversion/bindings/swig/ruby/diff.la subversion/bindings/swig/ruby/fs.la subversion/bindings/swig/ruby/ra.la subversion/bindings/swig/ruby/repos.la subversion/bindings/swig/ruby/wc.la
+
+swig-rb-lib: subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la
+
+test: subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT) subversion/tests/libsvn_subr/auth-test$(EXEEXT) subversion/tests/libsvn_subr/cache-test$(EXEEXT) subversion/tests/libsvn_subr/checksum-test$(EXEEXT) subversion/tests/libsvn_client/client-test$(EXEEXT) subversion/tests/libsvn_subr/compat-test$(EXEEXT) subversion/tests/libsvn_subr/config-test$(EXEEXT) subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT) subversion/tests/libsvn_subr/crypto-test$(EXEEXT) subversion/tests/libsvn_wc/db-test$(EXEEXT) subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT) subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT) subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT) subversion/tests/cmdline/entries-dump$(EXEEXT) subversion/tests/libsvn_subr/error-code-test$(EXEEXT) subversion/tests/libsvn_subr/error-test$(EXEEXT) subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT) subversion/tests/libsvn_fs/fs-test$(EXEEXT) subversion/tests/libsvn_subr/hashdump-test$(EXEEXT) subversion/tests/libsvn_subr/io-test$(EXEEXT) subversion/tests/libsvn_test-1.la subversion/tests/libsvn_fs/locks-test$(EXEEXT) subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT) subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT) subversion/tests/libsvn_wc/op-depth-test$(EXEEXT) subversion/tests/libsvn_subr/opt-test$(EXEEXT) subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT) subversion/tests/libsvn_subr/path-test$(EXEEXT) subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT) subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT) subversion/tests/libsvn_ra/ra-test$(EXEEXT) subversion/tests/libsvn_delta/random-test$(EXEEXT) subversion/tests/libsvn_repos/repos-test$(EXEEXT) subversion/tests/libsvn_subr/revision-test$(EXEEXT) subversion/tests/libsvn_subr/skel-test$(EXEEXT) subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT) subversion/tests/libsvn_subr/stream-test$(EXEEXT) subversion/tests/libsvn_subr/string-test$(EXEEXT) subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT) subversion/tests/libsvn_delta/svndiff-test$(EXEEXT) subversion/tests/libsvn_subr/time-test$(EXEEXT) subversion/tests/libsvn_subr/translate-test$(EXEEXT) subversion/tests/libsvn_subr/utf-test$(EXEEXT) subversion/tests/libsvn_delta/vdelta-test$(EXEEXT) subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT) subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT) subversion/tests/libsvn_wc/wc-test$(EXEEXT) subversion/tests/libsvn_delta/window-test$(EXEEXT)
+
+tests: subversion/bindings/cxxhl/cxxhl-tests$(EXEEXT)
+
+tools: tools/diff/diff$(EXEEXT) tools/diff/diff3$(EXEEXT) tools/diff/diff4$(EXEEXT) tools/dev/fsfs-access-map$(EXEEXT) tools/dev/fsfs-reorg$(EXEEXT) tools/server-side/fsfs-stats$(EXEEXT) tools/server-side/mod_dontdothat/mod_dontdothat.la tools/client-side/svn-bench/svn-bench$(EXEEXT) tools/server-side/svn-populate-node-origins-index$(EXEEXT) tools/server-side/svn-rep-sharing-stats$(EXEEXT) tools/server-side/svnauthz$(EXEEXT) tools/server-side/svnauthz-validate$(EXEEXT) tools/dev/svnraisetreeconflict/svnraisetreeconflict$(EXEEXT)
+
+
+########################################
+# Section 7: Install-Group install targets
+########################################
+
+install-mods-shared: subversion/mod_dav_svn/mod_dav_svn.la subversion/mod_authz_svn/mod_authz_svn.la
+ if $(INSTALL_APACHE_MODS) ; then cd subversion/mod_dav_svn ; $(MKDIR) "$(APACHE_LIBEXECDIR)" ; $(INSTALL_MOD_SHARED) -n dav_svn mod_dav_svn.la ; fi
+ if $(INSTALL_APACHE_MODS) ; then cd subversion/mod_authz_svn ; $(MKDIR) "$(APACHE_LIBEXECDIR)" ; $(INSTALL_MOD_SHARED) -n authz_svn mod_authz_svn.la ; fi
+
+install-bdb-lib: subversion/libsvn_fs_base/libsvn_fs_base-1.la
+ $(MKDIR) $(DESTDIR)$(bdb_libdir)
+ cd subversion/libsvn_fs_base ; $(INSTALL_BDB_LIB) libsvn_fs_base-1.la $(DESTDIR)$(bdb_libdir)/libsvn_fs_base-1.la
+
+install-bin: subversion/svn/svn$(EXEEXT) subversion/svnadmin/svnadmin$(EXEEXT) subversion/svndumpfilter/svndumpfilter$(EXEEXT) subversion/svnlook/svnlook$(EXEEXT) subversion/svnmucc/svnmucc$(EXEEXT) subversion/svnrdump/svnrdump$(EXEEXT) subversion/svnserve/svnserve$(EXEEXT) subversion/svnsync/svnsync$(EXEEXT) subversion/svnversion/svnversion$(EXEEXT)
+ $(MKDIR) $(DESTDIR)$(bindir)
+ cd subversion/svn ; $(INSTALL_BIN) svn$(EXEEXT) $(DESTDIR)$(bindir)/svn$(EXEEXT)
+ cd subversion/svnadmin ; $(INSTALL_BIN) svnadmin$(EXEEXT) $(DESTDIR)$(bindir)/svnadmin$(EXEEXT)
+ cd subversion/svndumpfilter ; $(INSTALL_BIN) svndumpfilter$(EXEEXT) $(DESTDIR)$(bindir)/svndumpfilter$(EXEEXT)
+ cd subversion/svnlook ; $(INSTALL_BIN) svnlook$(EXEEXT) $(DESTDIR)$(bindir)/svnlook$(EXEEXT)
+ cd subversion/svnmucc ; $(INSTALL_BIN) svnmucc$(EXEEXT) $(DESTDIR)$(bindir)/svnmucc$(EXEEXT)
+ cd subversion/svnrdump ; $(INSTALL_BIN) svnrdump$(EXEEXT) $(DESTDIR)$(bindir)/svnrdump$(EXEEXT)
+ cd subversion/svnserve ; $(INSTALL_BIN) svnserve$(EXEEXT) $(DESTDIR)$(bindir)/svnserve$(EXEEXT)
+ cd subversion/svnsync ; $(INSTALL_BIN) svnsync$(EXEEXT) $(DESTDIR)$(bindir)/svnsync$(EXEEXT)
+ cd subversion/svnversion ; $(INSTALL_BIN) svnversion$(EXEEXT) $(DESTDIR)$(bindir)/svnversion$(EXEEXT)
+
+install-cxxhl-lib: subversion/bindings/cxxhl/libsvncxxhl-1.la
+ $(MKDIR) $(DESTDIR)$(cxxhl_libdir)
+ cd subversion/bindings/cxxhl ; $(INSTALL_CXXHL_LIB) libsvncxxhl-1.la $(DESTDIR)$(cxxhl_libdir)/libsvncxxhl-1.la
+ $(INSTALL_EXTRA_CXXHL_LIB)
+
+install-fsmod-lib: subversion/libsvn_subr/libsvn_subr-1.la subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_fs_util/libsvn_fs_util-1.la subversion/libsvn_fs_fs/libsvn_fs_fs-1.la
+ $(MKDIR) $(DESTDIR)$(fsmod_libdir)
+ cd subversion/libsvn_subr ; $(INSTALL_FSMOD_LIB) libsvn_subr-1.la $(DESTDIR)$(fsmod_libdir)/libsvn_subr-1.la
+ cd subversion/libsvn_delta ; $(INSTALL_FSMOD_LIB) libsvn_delta-1.la $(DESTDIR)$(fsmod_libdir)/libsvn_delta-1.la
+ cd subversion/libsvn_fs_util ; $(INSTALL_FSMOD_LIB) libsvn_fs_util-1.la $(DESTDIR)$(fsmod_libdir)/libsvn_fs_util-1.la
+ cd subversion/libsvn_fs_fs ; $(INSTALL_FSMOD_LIB) libsvn_fs_fs-1.la $(DESTDIR)$(fsmod_libdir)/libsvn_fs_fs-1.la
+
+install-gnome-keyring-lib: subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring-1.la
+ $(MKDIR) $(DESTDIR)$(gnome_keyring_libdir)
+ cd subversion/libsvn_auth_gnome_keyring ; $(INSTALL_GNOME_KEYRING_LIB) libsvn_auth_gnome_keyring-1.la $(DESTDIR)$(gnome_keyring_libdir)/libsvn_auth_gnome_keyring-1.la
+
+install-javahl-callback-javah:
+ $(MKDIR) $(DESTDIR)$(javahl_callback_javahdir)
+ $(INSTALL_EXTRA_JAVAHL_CALLBACK_JAVAH)
+
+install-javahl-compat-java:
+ $(MKDIR) $(DESTDIR)$(javahl_compat_javadir)
+ $(INSTALL_EXTRA_JAVAHL_COMPAT_JAVA)
+
+install-javahl-compat-tests:
+ $(MKDIR) $(DESTDIR)$(javahl_compat_testsdir)
+ $(INSTALL_EXTRA_JAVAHL_COMPAT_TESTS)
+
+install-javahl-java:
+ $(MKDIR) $(DESTDIR)$(javahl_javadir)
+ $(INSTALL_EXTRA_JAVAHL_JAVA)
+
+install-javahl-javah:
+ $(MKDIR) $(DESTDIR)$(javahl_javahdir)
+ $(INSTALL_EXTRA_JAVAHL_JAVAH)
+
+install-javahl-lib: subversion/bindings/javahl/native/libsvnjavahl-1.la
+ $(MKDIR) $(DESTDIR)$(javahl_libdir)
+ cd subversion/bindings/javahl/native ; $(INSTALL_JAVAHL_LIB) libsvnjavahl-1.la $(DESTDIR)$(javahl_libdir)/libsvnjavahl-1.la
+ $(INSTALL_EXTRA_JAVAHL_LIB)
+
+install-javahl-tests:
+ $(MKDIR) $(DESTDIR)$(javahl_testsdir)
+ $(INSTALL_EXTRA_JAVAHL_TESTS)
+
+install-javahl-types-javah:
+ $(MKDIR) $(DESTDIR)$(javahl_types_javahdir)
+ $(INSTALL_EXTRA_JAVAHL_TYPES_JAVAH)
+
+install-kwallet-lib: subversion/libsvn_auth_kwallet/libsvn_auth_kwallet-1.la
+ $(MKDIR) $(DESTDIR)$(kwallet_libdir)
+ cd subversion/libsvn_auth_kwallet ; $(INSTALL_KWALLET_LIB) libsvn_auth_kwallet-1.la $(DESTDIR)$(kwallet_libdir)/libsvn_auth_kwallet-1.la
+
+install-lib: subversion/libsvn_diff/libsvn_diff-1.la subversion/libsvn_ra/libsvn_ra-1.la subversion/libsvn_wc/libsvn_wc-1.la subversion/libsvn_client/libsvn_client-1.la
+ $(MKDIR) $(DESTDIR)$(libdir)
+ cd subversion/libsvn_diff ; $(INSTALL_LIB) libsvn_diff-1.la $(DESTDIR)$(libdir)/libsvn_diff-1.la
+ cd subversion/libsvn_ra ; $(INSTALL_LIB) libsvn_ra-1.la $(DESTDIR)$(libdir)/libsvn_ra-1.la
+ cd subversion/libsvn_wc ; $(INSTALL_LIB) libsvn_wc-1.la $(DESTDIR)$(libdir)/libsvn_wc-1.la
+ cd subversion/libsvn_client ; $(INSTALL_LIB) libsvn_client-1.la $(DESTDIR)$(libdir)/libsvn_client-1.la
+
+install-locale: subversion/po/de.mo subversion/po/es.mo subversion/po/fr.mo subversion/po/it.mo subversion/po/ja.mo subversion/po/ko.mo subversion/po/nb.mo subversion/po/pl.mo subversion/po/pt_BR.mo subversion/po/sv.mo subversion/po/zh_CN.mo subversion/po/zh_TW.mo
+ $(MKDIR) $(DESTDIR)$(localedir)
+ $(MKDIR) $(DESTDIR)$(localedir)/de/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) de.mo $(DESTDIR)$(localedir)/de/LC_MESSAGES/$(PACKAGE_NAME).mo
+ $(MKDIR) $(DESTDIR)$(localedir)/es/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) es.mo $(DESTDIR)$(localedir)/es/LC_MESSAGES/$(PACKAGE_NAME).mo
+ $(MKDIR) $(DESTDIR)$(localedir)/fr/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) fr.mo $(DESTDIR)$(localedir)/fr/LC_MESSAGES/$(PACKAGE_NAME).mo
+ $(MKDIR) $(DESTDIR)$(localedir)/it/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) it.mo $(DESTDIR)$(localedir)/it/LC_MESSAGES/$(PACKAGE_NAME).mo
+ $(MKDIR) $(DESTDIR)$(localedir)/ja/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) ja.mo $(DESTDIR)$(localedir)/ja/LC_MESSAGES/$(PACKAGE_NAME).mo
+ $(MKDIR) $(DESTDIR)$(localedir)/ko/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) ko.mo $(DESTDIR)$(localedir)/ko/LC_MESSAGES/$(PACKAGE_NAME).mo
+ $(MKDIR) $(DESTDIR)$(localedir)/nb/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) nb.mo $(DESTDIR)$(localedir)/nb/LC_MESSAGES/$(PACKAGE_NAME).mo
+ $(MKDIR) $(DESTDIR)$(localedir)/pl/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) pl.mo $(DESTDIR)$(localedir)/pl/LC_MESSAGES/$(PACKAGE_NAME).mo
+ $(MKDIR) $(DESTDIR)$(localedir)/pt_BR/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) pt_BR.mo $(DESTDIR)$(localedir)/pt_BR/LC_MESSAGES/$(PACKAGE_NAME).mo
+ $(MKDIR) $(DESTDIR)$(localedir)/sv/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) sv.mo $(DESTDIR)$(localedir)/sv/LC_MESSAGES/$(PACKAGE_NAME).mo
+ $(MKDIR) $(DESTDIR)$(localedir)/zh_CN/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) zh_CN.mo $(DESTDIR)$(localedir)/zh_CN/LC_MESSAGES/$(PACKAGE_NAME).mo
+ $(MKDIR) $(DESTDIR)$(localedir)/zh_TW/LC_MESSAGES
+ cd subversion/po ; $(INSTALL_LOCALE) zh_TW.mo $(DESTDIR)$(localedir)/zh_TW/LC_MESSAGES/$(PACKAGE_NAME).mo
+
+install-ramod-lib: subversion/libsvn_fs/libsvn_fs-1.la subversion/libsvn_ra_svn/libsvn_ra_svn-1.la subversion/libsvn_repos/libsvn_repos-1.la subversion/libsvn_ra_local/libsvn_ra_local-1.la
+ $(MKDIR) $(DESTDIR)$(ramod_libdir)
+ cd subversion/libsvn_fs ; $(INSTALL_RAMOD_LIB) libsvn_fs-1.la $(DESTDIR)$(ramod_libdir)/libsvn_fs-1.la
+ cd subversion/libsvn_ra_svn ; $(INSTALL_RAMOD_LIB) libsvn_ra_svn-1.la $(DESTDIR)$(ramod_libdir)/libsvn_ra_svn-1.la
+ cd subversion/libsvn_repos ; $(INSTALL_RAMOD_LIB) libsvn_repos-1.la $(DESTDIR)$(ramod_libdir)/libsvn_repos-1.la
+ cd subversion/libsvn_ra_local ; $(INSTALL_RAMOD_LIB) libsvn_ra_local-1.la $(DESTDIR)$(ramod_libdir)/libsvn_ra_local-1.la
+
+install-serf-lib: subversion/libsvn_ra_serf/libsvn_ra_serf-1.la
+ $(MKDIR) $(DESTDIR)$(serf_libdir)
+ cd subversion/libsvn_ra_serf ; $(INSTALL_SERF_LIB) libsvn_ra_serf-1.la $(DESTDIR)$(serf_libdir)/libsvn_ra_serf-1.la
+
+install-sub-test: subversion/tests/libsvn_subr/named_atomic-proc-test$(EXEEXT)
+ $(MKDIR) $(DESTDIR)$(sub_testdir)
+ cd subversion/tests/libsvn_subr ; $(INSTALL_SUB_TEST) named_atomic-proc-test$(EXEEXT) $(DESTDIR)$(sub_testdir)/named_atomic-proc-test$(EXEEXT)
+
+install-swig-pl-lib: subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la
+ $(MKDIR) $(DESTDIR)$(swig_pl_libdir)
+ cd subversion/bindings/swig/perl/libsvn_swig_perl ; $(INSTALL_SWIG_PL_LIB) libsvn_swig_perl-1.la $(DESTDIR)$(swig_pl_libdir)/libsvn_swig_perl-1.la
+
+install-swig-py: subversion/bindings/swig/python/_core.la subversion/bindings/swig/python/_client.la subversion/bindings/swig/python/_delta.la subversion/bindings/swig/python/_diff.la subversion/bindings/swig/python/_fs.la subversion/bindings/swig/python/_ra.la subversion/bindings/swig/python/_repos.la subversion/bindings/swig/python/_wc.la
+ $(MKDIR) $(DESTDIR)$(swig_pydir)
+ cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _core.la $(DESTDIR)$(swig_pydir)/_core.la
+ cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _client.la $(DESTDIR)$(swig_pydir)/_client.la
+ cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _delta.la $(DESTDIR)$(swig_pydir)/_delta.la
+ cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _diff.la $(DESTDIR)$(swig_pydir)/_diff.la
+ cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _fs.la $(DESTDIR)$(swig_pydir)/_fs.la
+ cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _ra.la $(DESTDIR)$(swig_pydir)/_ra.la
+ cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _repos.la $(DESTDIR)$(swig_pydir)/_repos.la
+ cd subversion/bindings/swig/python ; $(INSTALL_SWIG_PY) _wc.la $(DESTDIR)$(swig_pydir)/_wc.la
+ $(INSTALL_EXTRA_SWIG_PY)
+
+install-swig-py-lib: subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la
+ $(MKDIR) $(DESTDIR)$(swig_py_libdir)
+ cd subversion/bindings/swig/python/libsvn_swig_py ; $(INSTALL_SWIG_PY_LIB) libsvn_swig_py-1.la $(DESTDIR)$(swig_py_libdir)/libsvn_swig_py-1.la
+
+install-swig-rb: subversion/bindings/swig/ruby/core.la subversion/bindings/swig/ruby/client.la subversion/bindings/swig/ruby/delta.la subversion/bindings/swig/ruby/diff.la subversion/bindings/swig/ruby/fs.la subversion/bindings/swig/ruby/ra.la subversion/bindings/swig/ruby/repos.la subversion/bindings/swig/ruby/wc.la
+ $(MKDIR) $(DESTDIR)$(swig_rbdir)
+ cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) core.la $(DESTDIR)$(swig_rbdir)/core.la
+ cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) client.la $(DESTDIR)$(swig_rbdir)/client.la
+ cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) delta.la $(DESTDIR)$(swig_rbdir)/delta.la
+ cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) diff.la $(DESTDIR)$(swig_rbdir)/diff.la
+ cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) fs.la $(DESTDIR)$(swig_rbdir)/fs.la
+ cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) ra.la $(DESTDIR)$(swig_rbdir)/ra.la
+ cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) repos.la $(DESTDIR)$(swig_rbdir)/repos.la
+ cd subversion/bindings/swig/ruby ; $(INSTALL_SWIG_RB) wc.la $(DESTDIR)$(swig_rbdir)/wc.la
+ $(INSTALL_EXTRA_SWIG_RB)
+
+install-swig-rb-lib: subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la
+ $(MKDIR) $(DESTDIR)$(swig_rb_libdir)
+ cd subversion/bindings/swig/ruby/libsvn_swig_ruby ; $(INSTALL_SWIG_RB_LIB) libsvn_swig_ruby-1.la $(DESTDIR)$(swig_rb_libdir)/libsvn_swig_ruby-1.la
+
+install-tests: subversion/bindings/cxxhl/cxxhl-tests$(EXEEXT)
+ $(MKDIR) $(DESTDIR)$(testsdir)
+ cd subversion/bindings/cxxhl ; $(INSTALL_TESTS) cxxhl-tests$(EXEEXT) $(DESTDIR)$(testsdir)/cxxhl-tests$(EXEEXT)
+
+install-tools: tools/diff/diff$(EXEEXT) tools/diff/diff3$(EXEEXT) tools/diff/diff4$(EXEEXT) tools/dev/fsfs-access-map$(EXEEXT) tools/dev/fsfs-reorg$(EXEEXT) tools/server-side/fsfs-stats$(EXEEXT) tools/server-side/mod_dontdothat/mod_dontdothat.la tools/client-side/svn-bench/svn-bench$(EXEEXT) tools/server-side/svn-populate-node-origins-index$(EXEEXT) tools/server-side/svn-rep-sharing-stats$(EXEEXT) tools/server-side/svnauthz$(EXEEXT) tools/server-side/svnauthz-validate$(EXEEXT) tools/dev/svnraisetreeconflict/svnraisetreeconflict$(EXEEXT)
+ $(MKDIR) $(DESTDIR)$(toolsdir)
+ cd tools/diff ; $(INSTALL_TOOLS) diff$(EXEEXT) $(DESTDIR)$(toolsdir)/diff$(EXEEXT)
+ cd tools/diff ; $(INSTALL_TOOLS) diff3$(EXEEXT) $(DESTDIR)$(toolsdir)/diff3$(EXEEXT)
+ cd tools/diff ; $(INSTALL_TOOLS) diff4$(EXEEXT) $(DESTDIR)$(toolsdir)/diff4$(EXEEXT)
+ cd tools/dev ; $(INSTALL_TOOLS) fsfs-access-map$(EXEEXT) $(DESTDIR)$(toolsdir)/fsfs-access-map$(EXEEXT)
+ cd tools/dev ; $(INSTALL_TOOLS) fsfs-reorg$(EXEEXT) $(DESTDIR)$(toolsdir)/fsfs-reorg$(EXEEXT)
+ cd tools/server-side ; $(INSTALL_TOOLS) fsfs-stats$(EXEEXT) $(DESTDIR)$(toolsdir)/fsfs-stats$(EXEEXT)
+ if $(INSTALL_APACHE_MODS) ; then cd tools/server-side/mod_dontdothat ; $(MKDIR) "$(APACHE_LIBEXECDIR)" ; $(INSTALL_MOD_SHARED) -n dontdothat mod_dontdothat.la ; fi
+ cd tools/client-side/svn-bench ; $(INSTALL_TOOLS) svn-bench$(EXEEXT) $(DESTDIR)$(toolsdir)/svn-bench$(EXEEXT)
+ cd tools/server-side ; $(INSTALL_TOOLS) svn-populate-node-origins-index$(EXEEXT) $(DESTDIR)$(toolsdir)/svn-populate-node-origins-index$(EXEEXT)
+ cd tools/server-side ; $(INSTALL_TOOLS) svn-rep-sharing-stats$(EXEEXT) $(DESTDIR)$(toolsdir)/svn-rep-sharing-stats$(EXEEXT)
+ cd tools/server-side ; $(INSTALL_TOOLS) svnauthz$(EXEEXT) $(DESTDIR)$(toolsdir)/svnauthz$(EXEEXT)
+ cd tools/server-side ; $(INSTALL_TOOLS) svnauthz-validate$(EXEEXT) $(DESTDIR)$(toolsdir)/svnauthz-validate$(EXEEXT)
+ cd tools/dev/svnraisetreeconflict ; $(INSTALL_TOOLS) svnraisetreeconflict$(EXEEXT) $(DESTDIR)$(toolsdir)/svnraisetreeconflict$(EXEEXT)
+ $(INSTALL_EXTRA_TOOLS)
+
+
+########################################
+# Section 8: The install-include rule
+########################################
+
+install-include: subversion/include/mod_authz_svn.h subversion/include/mod_dav_svn.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_md5.h subversion/include/svn_mergeinfo.h subversion/include/svn_nls.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_quoprint.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/include/svn_xml.h
+ $(MKDIR) $(DESTDIR)$(includedir)/subversion-1
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/mod_authz_svn.h $(DESTDIR)$(includedir)/subversion-1/mod_authz_svn.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/mod_dav_svn.h $(DESTDIR)$(includedir)/subversion-1/mod_dav_svn.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_auth.h $(DESTDIR)$(includedir)/subversion-1/svn_auth.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_base64.h $(DESTDIR)$(includedir)/subversion-1/svn_base64.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_cache_config.h $(DESTDIR)$(includedir)/subversion-1/svn_cache_config.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_checksum.h $(DESTDIR)$(includedir)/subversion-1/svn_checksum.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_client.h $(DESTDIR)$(includedir)/subversion-1/svn_client.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_cmdline.h $(DESTDIR)$(includedir)/subversion-1/svn_cmdline.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_compat.h $(DESTDIR)$(includedir)/subversion-1/svn_compat.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_config.h $(DESTDIR)$(includedir)/subversion-1/svn_config.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_ctype.h $(DESTDIR)$(includedir)/subversion-1/svn_ctype.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_dav.h $(DESTDIR)$(includedir)/subversion-1/svn_dav.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_delta.h $(DESTDIR)$(includedir)/subversion-1/svn_delta.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_diff.h $(DESTDIR)$(includedir)/subversion-1/svn_diff.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_dirent_uri.h $(DESTDIR)$(includedir)/subversion-1/svn_dirent_uri.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_dso.h $(DESTDIR)$(includedir)/subversion-1/svn_dso.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_error.h $(DESTDIR)$(includedir)/subversion-1/svn_error.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_error_codes.h $(DESTDIR)$(includedir)/subversion-1/svn_error_codes.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_fs.h $(DESTDIR)$(includedir)/subversion-1/svn_fs.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_hash.h $(DESTDIR)$(includedir)/subversion-1/svn_hash.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_io.h $(DESTDIR)$(includedir)/subversion-1/svn_io.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_iter.h $(DESTDIR)$(includedir)/subversion-1/svn_iter.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_md5.h $(DESTDIR)$(includedir)/subversion-1/svn_md5.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_mergeinfo.h $(DESTDIR)$(includedir)/subversion-1/svn_mergeinfo.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_nls.h $(DESTDIR)$(includedir)/subversion-1/svn_nls.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_opt.h $(DESTDIR)$(includedir)/subversion-1/svn_opt.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_path.h $(DESTDIR)$(includedir)/subversion-1/svn_path.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_pools.h $(DESTDIR)$(includedir)/subversion-1/svn_pools.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_props.h $(DESTDIR)$(includedir)/subversion-1/svn_props.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_quoprint.h $(DESTDIR)$(includedir)/subversion-1/svn_quoprint.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_ra.h $(DESTDIR)$(includedir)/subversion-1/svn_ra.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_ra_svn.h $(DESTDIR)$(includedir)/subversion-1/svn_ra_svn.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_repos.h $(DESTDIR)$(includedir)/subversion-1/svn_repos.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_sorts.h $(DESTDIR)$(includedir)/subversion-1/svn_sorts.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_string.h $(DESTDIR)$(includedir)/subversion-1/svn_string.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_subst.h $(DESTDIR)$(includedir)/subversion-1/svn_subst.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_time.h $(DESTDIR)$(includedir)/subversion-1/svn_time.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_types.h $(DESTDIR)$(includedir)/subversion-1/svn_types.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_user.h $(DESTDIR)$(includedir)/subversion-1/svn_user.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_utf.h $(DESTDIR)$(includedir)/subversion-1/svn_utf.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_version.h $(DESTDIR)$(includedir)/subversion-1/svn_version.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_wc.h $(DESTDIR)$(includedir)/subversion-1/svn_wc.h
+ $(INSTALL_INCLUDE) $(abs_srcdir)/subversion/include/svn_xml.h $(DESTDIR)$(includedir)/subversion-1/svn_xml.h
+
+########################################
+# Section 9: Shortcut targets for manual builds of specific items
+########################################
+
+atomic-ra-revprop-change: subversion/tests/cmdline/atomic-ra-revprop-change$(EXEEXT)
+auth-test: subversion/tests/libsvn_subr/auth-test$(EXEEXT)
+cache-test: subversion/tests/libsvn_subr/cache-test$(EXEEXT)
+changes-test: subversion/tests/libsvn_fs_base/changes-test$(EXEEXT)
+checksum-test: subversion/tests/libsvn_subr/checksum-test$(EXEEXT)
+client-test: subversion/tests/libsvn_client/client-test$(EXEEXT)
+compat-test: subversion/tests/libsvn_subr/compat-test$(EXEEXT)
+config-test: subversion/tests/libsvn_subr/config-test$(EXEEXT)
+conflict-data-test: subversion/tests/libsvn_wc/conflict-data-test$(EXEEXT)
+crypto-test: subversion/tests/libsvn_subr/crypto-test$(EXEEXT)
+cxxhl-tests: subversion/bindings/cxxhl/cxxhl-tests$(EXEEXT)
+db-test: subversion/tests/libsvn_wc/db-test$(EXEEXT)
+diff: tools/diff/diff$(EXEEXT)
+diff-diff3-test: subversion/tests/libsvn_diff/diff-diff3-test$(EXEEXT)
+diff3: tools/diff/diff3$(EXEEXT)
+diff4: tools/diff/diff4$(EXEEXT)
+dirent_uri-test: subversion/tests/libsvn_subr/dirent_uri-test$(EXEEXT)
+entries-compat-test: subversion/tests/libsvn_wc/entries-compat-test$(EXEEXT)
+entries-dump: subversion/tests/cmdline/entries-dump$(EXEEXT)
+error-code-test: subversion/tests/libsvn_subr/error-code-test$(EXEEXT)
+error-test: subversion/tests/libsvn_subr/error-test$(EXEEXT)
+fs-base-test: subversion/tests/libsvn_fs_base/fs-base-test$(EXEEXT)
+fs-pack-test: subversion/tests/libsvn_fs_fs/fs-pack-test$(EXEEXT)
+fs-test: subversion/tests/libsvn_fs/fs-test$(EXEEXT)
+fsfs-access-map: tools/dev/fsfs-access-map$(EXEEXT)
+fsfs-reorg: tools/dev/fsfs-reorg$(EXEEXT)
+fsfs-stats: tools/server-side/fsfs-stats$(EXEEXT)
+hashdump-test: subversion/tests/libsvn_subr/hashdump-test$(EXEEXT)
+io-test: subversion/tests/libsvn_subr/io-test$(EXEEXT)
+libsvn_auth_gnome_keyring: subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring-1.la
+libsvn_auth_kwallet: subversion/libsvn_auth_kwallet/libsvn_auth_kwallet-1.la
+libsvn_client: subversion/libsvn_client/libsvn_client-1.la
+libsvn_delta: subversion/libsvn_delta/libsvn_delta-1.la
+libsvn_diff: subversion/libsvn_diff/libsvn_diff-1.la
+libsvn_fs: subversion/libsvn_fs/libsvn_fs-1.la
+libsvn_fs_base: subversion/libsvn_fs_base/libsvn_fs_base-1.la
+libsvn_fs_fs: subversion/libsvn_fs_fs/libsvn_fs_fs-1.la
+libsvn_fs_util: subversion/libsvn_fs_util/libsvn_fs_util-1.la
+libsvn_ra: subversion/libsvn_ra/libsvn_ra-1.la
+libsvn_ra_local: subversion/libsvn_ra_local/libsvn_ra_local-1.la
+libsvn_ra_serf: subversion/libsvn_ra_serf/libsvn_ra_serf-1.la
+libsvn_ra_svn: subversion/libsvn_ra_svn/libsvn_ra_svn-1.la
+libsvn_repos: subversion/libsvn_repos/libsvn_repos-1.la
+libsvn_subr: subversion/libsvn_subr/libsvn_subr-1.la
+libsvn_swig_perl: subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la
+libsvn_swig_py: subversion/bindings/swig/python/libsvn_swig_py/libsvn_swig_py-1.la
+libsvn_swig_ruby: subversion/bindings/swig/ruby/libsvn_swig_ruby/libsvn_swig_ruby-1.la
+libsvn_test: subversion/tests/libsvn_test-1.la
+libsvn_wc: subversion/libsvn_wc/libsvn_wc-1.la
+libsvncxxhl: subversion/bindings/cxxhl/libsvncxxhl-1.la
+libsvnjavahl: subversion/bindings/javahl/native/libsvnjavahl-1.la
+locks-test: subversion/tests/libsvn_fs/locks-test$(EXEEXT)
+mergeinfo-test: subversion/tests/libsvn_subr/mergeinfo-test$(EXEEXT)
+mod_authz_svn: subversion/mod_authz_svn/mod_authz_svn.la
+mod_dav_svn: subversion/mod_dav_svn/mod_dav_svn.la
+mod_dontdothat: tools/server-side/mod_dontdothat/mod_dontdothat.la
+named_atomic-proc-test: subversion/tests/libsvn_subr/named_atomic-proc-test$(EXEEXT)
+named_atomic-test: subversion/tests/libsvn_subr/named_atomic-test$(EXEEXT)
+op-depth-test: subversion/tests/libsvn_wc/op-depth-test$(EXEEXT)
+opt-test: subversion/tests/libsvn_subr/opt-test$(EXEEXT)
+parse-diff-test: subversion/tests/libsvn_diff/parse-diff-test$(EXEEXT)
+path-test: subversion/tests/libsvn_subr/path-test$(EXEEXT)
+perl_client: subversion/bindings/swig/perl/native/_Client.la
+perl_core: subversion/bindings/swig/perl/native/_Core.la
+perl_delta: subversion/bindings/swig/perl/native/_Delta.la
+perl_diff: subversion/bindings/swig/perl/native/_Diff.la
+perl_fs: subversion/bindings/swig/perl/native/_Fs.la
+perl_ra: subversion/bindings/swig/perl/native/_Ra.la
+perl_repos: subversion/bindings/swig/perl/native/_Repos.la
+perl_wc: subversion/bindings/swig/perl/native/_Wc.la
+pristine-store-test: subversion/tests/libsvn_wc/pristine-store-test$(EXEEXT)
+python_client: subversion/bindings/swig/python/_client.la
+python_core: subversion/bindings/swig/python/_core.la
+python_delta: subversion/bindings/swig/python/_delta.la
+python_diff: subversion/bindings/swig/python/_diff.la
+python_fs: subversion/bindings/swig/python/_fs.la
+python_ra: subversion/bindings/swig/python/_ra.la
+python_repos: subversion/bindings/swig/python/_repos.la
+python_wc: subversion/bindings/swig/python/_wc.la
+ra-local-test: subversion/tests/libsvn_ra_local/ra-local-test$(EXEEXT)
+ra-test: subversion/tests/libsvn_ra/ra-test$(EXEEXT)
+random-test: subversion/tests/libsvn_delta/random-test$(EXEEXT)
+repos-test: subversion/tests/libsvn_repos/repos-test$(EXEEXT)
+revision-test: subversion/tests/libsvn_subr/revision-test$(EXEEXT)
+ruby_client: subversion/bindings/swig/ruby/client.la
+ruby_core: subversion/bindings/swig/ruby/core.la
+ruby_delta: subversion/bindings/swig/ruby/delta.la
+ruby_diff: subversion/bindings/swig/ruby/diff.la
+ruby_fs: subversion/bindings/swig/ruby/fs.la
+ruby_ra: subversion/bindings/swig/ruby/ra.la
+ruby_repos: subversion/bindings/swig/ruby/repos.la
+ruby_wc: subversion/bindings/swig/ruby/wc.la
+skel-test: subversion/tests/libsvn_subr/skel-test$(EXEEXT)
+spillbuf-test: subversion/tests/libsvn_subr/spillbuf-test$(EXEEXT)
+stream-test: subversion/tests/libsvn_subr/stream-test$(EXEEXT)
+string-test: subversion/tests/libsvn_subr/string-test$(EXEEXT)
+strings-reps-test: subversion/tests/libsvn_fs_base/strings-reps-test$(EXEEXT)
+subst_translate-test: subversion/tests/libsvn_subr/subst_translate-test$(EXEEXT)
+svn: subversion/svn/svn$(EXEEXT)
+svn-bench: tools/client-side/svn-bench/svn-bench$(EXEEXT)
+svn-populate-node-origins-index: tools/server-side/svn-populate-node-origins-index$(EXEEXT)
+svn-rep-sharing-stats: tools/server-side/svn-rep-sharing-stats$(EXEEXT)
+svnadmin: subversion/svnadmin/svnadmin$(EXEEXT)
+svnauthz: tools/server-side/svnauthz$(EXEEXT)
+svnauthz-validate: tools/server-side/svnauthz-validate$(EXEEXT)
+svndiff-test: subversion/tests/libsvn_delta/svndiff-test$(EXEEXT)
+svndumpfilter: subversion/svndumpfilter/svndumpfilter$(EXEEXT)
+svnlook: subversion/svnlook/svnlook$(EXEEXT)
+svnmucc: subversion/svnmucc/svnmucc$(EXEEXT)
+svnraisetreeconflict: tools/dev/svnraisetreeconflict/svnraisetreeconflict$(EXEEXT)
+svnrdump: subversion/svnrdump/svnrdump$(EXEEXT)
+svnserve: subversion/svnserve/svnserve$(EXEEXT)
+svnsync: subversion/svnsync/svnsync$(EXEEXT)
+svnversion: subversion/svnversion/svnversion$(EXEEXT)
+time-test: subversion/tests/libsvn_subr/time-test$(EXEEXT)
+translate-test: subversion/tests/libsvn_subr/translate-test$(EXEEXT)
+utf-test: subversion/tests/libsvn_subr/utf-test$(EXEEXT)
+vdelta-test: subversion/tests/libsvn_delta/vdelta-test$(EXEEXT)
+wc-incomplete-tester: subversion/tests/libsvn_wc/wc-incomplete-tester$(EXEEXT)
+wc-lock-tester: subversion/tests/libsvn_wc/wc-lock-tester$(EXEEXT)
+wc-queries-test: subversion/tests/libsvn_wc/wc-queries-test$(EXEEXT)
+wc-test: subversion/tests/libsvn_wc/wc-test$(EXEEXT)
+window-test: subversion/tests/libsvn_delta/window-test$(EXEEXT)
+
+########################################
+# Section 10: Rules to build all other kinds of object-like files
+########################################
+
+subversion/bindings/cxxhl/src/exception.lo: subversion/bindings/cxxhl/src/exception.cpp subversion/bindings/cxxhl/include/svncxxhl/_compat.hpp subversion/bindings/cxxhl/include/svncxxhl/exception.hpp subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_error_private.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h
+ $(COMPILE_CXXHL_CXX) $(canonicalized_srcdir)subversion/bindings/cxxhl/src/exception.cpp
+
+subversion/bindings/cxxhl/src/tristate.lo: subversion/bindings/cxxhl/src/tristate.cpp subversion/bindings/cxxhl/include/svncxxhl/tristate.hpp subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h
+ $(COMPILE_CXXHL_CXX) $(canonicalized_srcdir)subversion/bindings/cxxhl/src/tristate.cpp
+
+subversion/bindings/cxxhl/tests/test_exception.lo: subversion/bindings/cxxhl/tests/test_exception.cpp subversion/bindings/cxxhl/include/svncxxhl.hpp subversion/bindings/cxxhl/include/svncxxhl/_compat.hpp subversion/bindings/cxxhl/include/svncxxhl/exception.hpp subversion/bindings/cxxhl/include/svncxxhl/tristate.hpp subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h
+ $(COMPILE_CXXHL_CXX) $(canonicalized_srcdir)subversion/bindings/cxxhl/tests/test_exception.cpp
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/BasicTests.class: subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientException.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientException.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientNotifyInformation.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientNotifyInformation.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitInfo.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitInfo.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItem.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitItem.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItemStateFlags.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/CommitItemStateFlags.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictDescriptor.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictDescriptor.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictResult.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictResult.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/DiffSummary.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/DiffSummary.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNClient.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNClient.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNRepos.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNRepos.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/JNIError.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/JNIError.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeException.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeException.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeResources.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeResources.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/ProgressEvent.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ProgressEvent.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/ReposNotifyInformation.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/ReposNotifyInformation.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/RunTests.class: subversion/bindings/javahl/tests/org/apache/subversion/javahl/RunTests.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNClient.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNClient.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNRepos.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNRepos.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNReposTests.class: subversion/bindings/javahl/tests/org/apache/subversion/javahl/SVNReposTests.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNTests.class: subversion/bindings/javahl/tests/org/apache/subversion/javahl/SVNTests.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/SubversionException.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/SubversionException.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/WC.class: subversion/bindings/javahl/tests/org/apache/subversion/javahl/WC.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/BlameCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/BlameCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ChangelistCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ChangelistCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ClientNotifyCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ClientNotifyCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/CommitCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitMessageCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/CommitMessageCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ConflictResolverCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ConflictResolverCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/DiffSummaryCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/DiffSummaryCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ImportFilterCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ImportFilterCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InfoCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/InfoCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InheritedProplistCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/InheritedProplistCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ListCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ListCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/LogMessageCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/LogMessageCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/PatchCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/PatchCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProgressCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProgressCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProplistCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ProplistCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposFreezeAction.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ReposFreezeAction.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposNotifyCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ReposNotifyCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/StatusCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/StatusCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/UserPasswordCallback.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/UserPasswordCallback.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ChangePath.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/ChangePath.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Checksum.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Checksum.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ConflictVersion.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/ConflictVersion.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/CopySource.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/CopySource.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Depth.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Depth.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DiffOptions.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/DiffOptions.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DirEntry.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/DirEntry.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Info.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Info.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Lock.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Lock.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/LogDate.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/LogDate.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Mergeinfo.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Mergeinfo.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/NodeKind.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/NodeKind.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Property.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Property.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Revision.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Revision.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/RevisionRange.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/RevisionRange.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Status.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Status.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Tristate.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Tristate.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Version.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/Version.java
+
+subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/VersionExtended.class: subversion/bindings/javahl/src/org/apache/subversion/javahl/types/VersionExtended.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BasicTests.class: subversion/bindings/javahl/tests/org/tigris/subversion/javahl/BasicTests.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback2.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback2.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallback3.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallback3.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/BlameCallbackImpl.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/BlameCallbackImpl.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ChangePath.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ChangePath.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ChangelistCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ChangelistCallback.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ClientException.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ClientException.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitItem.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitItem.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitItemStateFlags.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitItemStateFlags.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CommitMessage.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/CommitMessage.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictDescriptor.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictDescriptor.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictResolverCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictResolverCallback.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictResult.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictResult.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ConflictVersion.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ConflictVersion.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/CopySource.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/CopySource.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Depth.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Depth.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DiffSummary.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/DiffSummary.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DiffSummaryReceiver.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/DiffSummaryReceiver.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/DirEntry.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/DirEntry.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ErrorCodes.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ErrorCodes.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Info.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Info.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Info2.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Info2.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/InfoCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/InfoCallback.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/InputInterface.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/InputInterface.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ListCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ListCallback.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Lock.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Lock.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LockStatus.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/LockStatus.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogDate.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogDate.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogMessage.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogMessage.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/LogMessageCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/LogMessageCallback.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Mergeinfo.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Mergeinfo.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/MergeinfoLogKind.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/MergeinfoLogKind.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NativeException.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/NativeException.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NodeKind.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/NodeKind.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Notify.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Notify.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Notify2.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Notify2.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyAction.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyAction.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyInformation.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyInformation.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/NotifyStatus.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/NotifyStatus.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Operation.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Operation.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/OutputInterface.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/OutputInterface.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Path.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Path.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProgressEvent.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProgressEvent.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProgressListener.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProgressListener.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword2.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword2.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PromptUserPassword3.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/PromptUserPassword3.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/PropertyData.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/PropertyData.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProplistCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallback.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ProplistCallbackImpl.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ProplistCallbackImpl.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Revision.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Revision.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RevisionKind.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/RevisionKind.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RevisionRange.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/RevisionRange.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/RunTests.class: subversion/bindings/javahl/tests/org/tigris/subversion/javahl/RunTests.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNAdmin.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNAdmin.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNAdminTests.class: subversion/bindings/javahl/tests/org/tigris/subversion/javahl/SVNAdminTests.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClient.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientInterface.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientLogLevel.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientLogLevel.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNClientSynchronized.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNInputStream.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNInputStream.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNOutputStream.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNOutputStream.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SVNTests.class: subversion/bindings/javahl/tests/org/tigris/subversion/javahl/SVNTests.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/ScheduleKind.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/ScheduleKind.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Status.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Status.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/StatusCallback.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/StatusCallback.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/StatusKind.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/StatusKind.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/SubversionException.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/SubversionException.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/Version.class: subversion/bindings/javahl/src/org/tigris/subversion/javahl/Version.java
+
+subversion/bindings/javahl/classes/org/tigris/subversion/javahl/WC.class: subversion/bindings/javahl/tests/org/tigris/subversion/javahl/WC.java
+
+subversion/bindings/javahl/include/BlameCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/BlameCallback.class
+
+subversion/bindings/javahl/include/ChangePath.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ChangePath.class
+
+subversion/bindings/javahl/include/ChangelistCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ChangelistCallback.class
+
+subversion/bindings/javahl/include/Checksum.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Checksum.class
+
+subversion/bindings/javahl/include/ClientException.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientException.class
+
+subversion/bindings/javahl/include/ClientNotifyCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ClientNotifyCallback.class
+
+subversion/bindings/javahl/include/ClientNotifyInformation.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ClientNotifyInformation.class
+
+subversion/bindings/javahl/include/CommitCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitCallback.class
+
+subversion/bindings/javahl/include/CommitInfo.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitInfo.class
+
+subversion/bindings/javahl/include/CommitItem.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItem.class
+
+subversion/bindings/javahl/include/CommitItemStateFlags.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/CommitItemStateFlags.class
+
+subversion/bindings/javahl/include/CommitMessageCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/CommitMessageCallback.class
+
+subversion/bindings/javahl/include/ConflictDescriptor.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictDescriptor.class
+
+subversion/bindings/javahl/include/ConflictResolverCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ConflictResolverCallback.class
+
+subversion/bindings/javahl/include/ConflictResult.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ConflictResult.class
+
+subversion/bindings/javahl/include/ConflictVersion.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/ConflictVersion.class
+
+subversion/bindings/javahl/include/CopySource.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/CopySource.class
+
+subversion/bindings/javahl/include/Depth.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Depth.class
+
+subversion/bindings/javahl/include/DiffOptions.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DiffOptions.class
+
+subversion/bindings/javahl/include/DiffSummary.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/DiffSummary.class
+
+subversion/bindings/javahl/include/DiffSummaryCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/DiffSummaryCallback.class
+
+subversion/bindings/javahl/include/DirEntry.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/DirEntry.class
+
+subversion/bindings/javahl/include/ISVNClient.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNClient.class
+
+subversion/bindings/javahl/include/ISVNRepos.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ISVNRepos.class
+
+subversion/bindings/javahl/include/ImportFilterCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ImportFilterCallback.class
+
+subversion/bindings/javahl/include/Info.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Info.class
+
+subversion/bindings/javahl/include/InfoCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InfoCallback.class
+
+subversion/bindings/javahl/include/InheritedProplistCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/InheritedProplistCallback.class
+
+subversion/bindings/javahl/include/JNIError.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/JNIError.class
+
+subversion/bindings/javahl/include/ListCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ListCallback.class
+
+subversion/bindings/javahl/include/Lock.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Lock.class
+
+subversion/bindings/javahl/include/LogDate.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/LogDate.class
+
+subversion/bindings/javahl/include/LogMessageCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/LogMessageCallback.class
+
+subversion/bindings/javahl/include/Mergeinfo.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Mergeinfo.class
+
+subversion/bindings/javahl/include/NativeException.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeException.class
+
+subversion/bindings/javahl/include/NativeResources.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/NativeResources.class
+
+subversion/bindings/javahl/include/NodeKind.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/NodeKind.class
+
+subversion/bindings/javahl/include/PatchCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/PatchCallback.class
+
+subversion/bindings/javahl/include/ProgressCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProgressCallback.class
+
+subversion/bindings/javahl/include/ProgressEvent.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ProgressEvent.class
+
+subversion/bindings/javahl/include/Property.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Property.class
+
+subversion/bindings/javahl/include/ProplistCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ProplistCallback.class
+
+subversion/bindings/javahl/include/ReposFreezeAction.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposFreezeAction.class
+
+subversion/bindings/javahl/include/ReposNotifyCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/ReposNotifyCallback.class
+
+subversion/bindings/javahl/include/ReposNotifyInformation.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/ReposNotifyInformation.class
+
+subversion/bindings/javahl/include/Revision.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Revision.class
+
+subversion/bindings/javahl/include/RevisionRange.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/RevisionRange.class
+
+subversion/bindings/javahl/include/SVNClient.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNClient.class
+
+subversion/bindings/javahl/include/SVNRepos.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/SVNRepos.class
+
+subversion/bindings/javahl/include/Status.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Status.class
+
+subversion/bindings/javahl/include/StatusCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/StatusCallback.class
+
+subversion/bindings/javahl/include/SubversionException.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/SubversionException.class
+
+subversion/bindings/javahl/include/Tristate.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Tristate.class
+
+subversion/bindings/javahl/include/UserPasswordCallback.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/callback/UserPasswordCallback.class
+
+subversion/bindings/javahl/include/Version.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/Version.class
+
+subversion/bindings/javahl/include/VersionExtended.h: subversion/bindings/javahl/classes/org/apache/subversion/javahl/types/VersionExtended.class
+
+subversion/bindings/javahl/native/Array.lo: subversion/bindings/javahl/native/Array.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Array.cpp
+
+subversion/bindings/javahl/native/BlameCallback.lo: subversion/bindings/javahl/native/BlameCallback.cpp subversion/bindings/javahl/native/BlameCallback.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/BlameCallback.cpp
+
+subversion/bindings/javahl/native/ChangelistCallback.lo: subversion/bindings/javahl/native/ChangelistCallback.cpp subversion/bindings/javahl/native/ChangelistCallback.h subversion/bindings/javahl/native/ClientContext.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/SVNClient.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ChangelistCallback.cpp
+
+subversion/bindings/javahl/native/ClientContext.lo: subversion/bindings/javahl/native/ClientContext.cpp subversion/bindings/javahl/native/ClientContext.h subversion/bindings/javahl/native/CommitMessage.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNICriticalSection.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Prompter.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ClientContext.cpp
+
+subversion/bindings/javahl/native/CommitCallback.lo: subversion/bindings/javahl/native/CommitCallback.cpp subversion/bindings/javahl/native/CommitCallback.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/CommitCallback.cpp
+
+subversion/bindings/javahl/native/CommitMessage.lo: subversion/bindings/javahl/native/CommitMessage.cpp subversion/bindings/javahl/native/CommitMessage.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/CommitMessage.cpp
+
+subversion/bindings/javahl/native/CopySources.lo: subversion/bindings/javahl/native/CopySources.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/CopySources.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Revision.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/CopySources.cpp
+
+subversion/bindings/javahl/native/CreateJ.lo: subversion/bindings/javahl/native/CreateJ.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitItemStateFlags.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Revision.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/RevisionRange.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/CreateJ.cpp
+
+subversion/bindings/javahl/native/DiffOptions.lo: subversion/bindings/javahl/native/DiffOptions.cpp subversion/bindings/javahl/native/DiffOptions.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/DiffOptions.cpp
+
+subversion/bindings/javahl/native/DiffSummaryReceiver.lo: subversion/bindings/javahl/native/DiffSummaryReceiver.cpp subversion/bindings/javahl/native/DiffSummaryReceiver.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/DiffSummaryReceiver.cpp
+
+subversion/bindings/javahl/native/EnumMapper.lo: subversion/bindings/javahl/native/EnumMapper.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitItemStateFlags.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/EnumMapper.cpp
+
+subversion/bindings/javahl/native/File.lo: subversion/bindings/javahl/native/File.cpp subversion/bindings/javahl/native/File.h subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/File.cpp
+
+subversion/bindings/javahl/native/ImportFilterCallback.lo: subversion/bindings/javahl/native/ImportFilterCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/ImportFilterCallback.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ImportFilterCallback.cpp
+
+subversion/bindings/javahl/native/InfoCallback.lo: subversion/bindings/javahl/native/InfoCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/InfoCallback.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/InfoCallback.cpp
+
+subversion/bindings/javahl/native/InputStream.lo: subversion/bindings/javahl/native/InputStream.cpp subversion/bindings/javahl/native/InputStream.h subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/InputStream.cpp
+
+subversion/bindings/javahl/native/JNIByteArray.lo: subversion/bindings/javahl/native/JNIByteArray.cpp subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIByteArray.cpp
+
+subversion/bindings/javahl/native/JNICriticalSection.lo: subversion/bindings/javahl/native/JNICriticalSection.cpp subversion/bindings/javahl/native/JNICriticalSection.h subversion/bindings/javahl/native/JNIMutex.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNICriticalSection.cpp
+
+subversion/bindings/javahl/native/JNIMutex.lo: subversion/bindings/javahl/native/JNIMutex.cpp subversion/bindings/javahl/native/JNIMutex.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIMutex.cpp
+
+subversion/bindings/javahl/native/JNIStackElement.lo: subversion/bindings/javahl/native/JNIStackElement.cpp subversion/bindings/javahl/native/JNIStackElement.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIThreadData.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIStackElement.cpp
+
+subversion/bindings/javahl/native/JNIStringHolder.lo: subversion/bindings/javahl/native/JNIStringHolder.cpp subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIStringHolder.cpp
+
+subversion/bindings/javahl/native/JNIThreadData.lo: subversion/bindings/javahl/native/JNIThreadData.cpp subversion/bindings/javahl/native/JNIThreadData.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIThreadData.cpp
+
+subversion/bindings/javahl/native/JNIUtil.lo: subversion/bindings/javahl/native/JNIUtil.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/JNICriticalSection.h subversion/bindings/javahl/native/JNIMutex.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIThreadData.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/SVNBase.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/JNIUtil.cpp
+
+subversion/bindings/javahl/native/ListCallback.lo: subversion/bindings/javahl/native/ListCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/ListCallback.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ListCallback.cpp
+
+subversion/bindings/javahl/native/LogMessageCallback.lo: subversion/bindings/javahl/native/LogMessageCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/LogMessageCallback.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/LogMessageCallback.cpp
+
+subversion/bindings/javahl/native/MessageReceiver.lo: subversion/bindings/javahl/native/MessageReceiver.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/MessageReceiver.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/MessageReceiver.cpp
+
+subversion/bindings/javahl/native/OutputStream.lo: subversion/bindings/javahl/native/OutputStream.cpp subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/OutputStream.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/OutputStream.cpp
+
+subversion/bindings/javahl/native/PatchCallback.lo: subversion/bindings/javahl/native/PatchCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/PatchCallback.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/PatchCallback.cpp
+
+subversion/bindings/javahl/native/Path.lo: subversion/bindings/javahl/native/Path.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Path.cpp
+
+subversion/bindings/javahl/native/Pool.lo: subversion/bindings/javahl/native/Pool.cpp subversion/bindings/javahl/native/JNICriticalSection.h subversion/bindings/javahl/native/JNIMutex.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Pool.cpp
+
+subversion/bindings/javahl/native/Prompter.lo: subversion/bindings/javahl/native/Prompter.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_UserPasswordCallback.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Prompter.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Prompter.cpp
+
+subversion/bindings/javahl/native/ProplistCallback.lo: subversion/bindings/javahl/native/ProplistCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/ProplistCallback.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ProplistCallback.cpp
+
+subversion/bindings/javahl/native/ReposFreezeAction.lo: subversion/bindings/javahl/native/ReposFreezeAction.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/ReposFreezeAction.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ReposFreezeAction.cpp
+
+subversion/bindings/javahl/native/ReposNotifyCallback.lo: subversion/bindings/javahl/native/ReposNotifyCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/ReposNotifyCallback.h subversion/bindings/javahl/native/RevisionRange.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/ReposNotifyCallback.cpp
+
+subversion/bindings/javahl/native/Revision.lo: subversion/bindings/javahl/native/Revision.cpp subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Revision.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Revision.cpp
+
+subversion/bindings/javahl/native/RevisionRange.lo: subversion/bindings/javahl/native/RevisionRange.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Revision.h subversion/bindings/javahl/native/RevisionRange.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/RevisionRange.cpp
+
+subversion/bindings/javahl/native/RevpropTable.lo: subversion/bindings/javahl/native/RevpropTable.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/RevpropTable.h subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/RevpropTable.cpp
+
+subversion/bindings/javahl/native/SVNBase.lo: subversion/bindings/javahl/native/SVNBase.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/SVNBase.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/SVNBase.cpp
+
+subversion/bindings/javahl/native/SVNClient.lo: subversion/bindings/javahl/native/SVNClient.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/BlameCallback.h subversion/bindings/javahl/native/ChangelistCallback.h subversion/bindings/javahl/native/ClientContext.h subversion/bindings/javahl/native/CommitCallback.h subversion/bindings/javahl/native/CommitMessage.h subversion/bindings/javahl/native/CopySources.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/DiffOptions.h subversion/bindings/javahl/native/DiffSummaryReceiver.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/ImportFilterCallback.h subversion/bindings/javahl/native/InfoCallback.h subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/ListCallback.h subversion/bindings/javahl/native/LogMessageCallback.h subversion/bindings/javahl/native/OutputStream.h subversion/bindings/javahl/native/PatchCallback.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Prompter.h subversion/bindings/javahl/native/ProplistCallback.h subversion/bindings/javahl/native/Revision.h subversion/bindings/javahl/native/RevisionRange.h subversion/bindings/javahl/native/RevpropTable.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/SVNClient.h subversion/bindings/javahl/native/StatusCallback.h subversion/bindings/javahl/native/StringArray.h subversion/bindings/javahl/native/Targets.h subversion/bindings/javahl/native/VersionExtended.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/SVNClient.cpp
+
+subversion/bindings/javahl/native/SVNRepos.lo: subversion/bindings/javahl/native/SVNRepos.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/File.h subversion/bindings/javahl/native/InputStream.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/MessageReceiver.h subversion/bindings/javahl/native/OutputStream.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/ReposFreezeAction.h subversion/bindings/javahl/native/ReposNotifyCallback.h subversion/bindings/javahl/native/Revision.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/SVNRepos.h subversion/bindings/javahl/native/StringArray.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/SVNRepos.cpp
+
+subversion/bindings/javahl/native/StatusCallback.lo: subversion/bindings/javahl/native/StatusCallback.cpp subversion/bindings/javahl/native/CreateJ.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/StatusCallback.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/StatusCallback.cpp
+
+subversion/bindings/javahl/native/StringArray.lo: subversion/bindings/javahl/native/StringArray.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/StringArray.h subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/StringArray.cpp
+
+subversion/bindings/javahl/native/Targets.lo: subversion/bindings/javahl/native/Targets.cpp subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/StringArray.h subversion/bindings/javahl/native/Targets.h subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/Targets.cpp
+
+subversion/bindings/javahl/native/VersionExtended.lo: subversion/bindings/javahl/native/VersionExtended.cpp subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/VersionExtended.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h subversion/include/svn_version.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/VersionExtended.cpp
+
+subversion/bindings/javahl/native/libsvnjavahl.la.lo: subversion/bindings/javahl/native/libsvnjavahl.la.c
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/libsvnjavahl.la.c
+
+subversion/bindings/javahl/native/org_apache_subversion_javahl_NativeResources.lo: subversion/bindings/javahl/native/org_apache_subversion_javahl_NativeResources.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_NativeResources.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/org_apache_subversion_javahl_NativeResources.cpp
+
+subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.lo: subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNClient.h subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/BlameCallback.h subversion/bindings/javahl/native/ChangelistCallback.h subversion/bindings/javahl/native/ClientContext.h subversion/bindings/javahl/native/CommitCallback.h subversion/bindings/javahl/native/CommitMessage.h subversion/bindings/javahl/native/CopySources.h subversion/bindings/javahl/native/DiffOptions.h subversion/bindings/javahl/native/DiffSummaryReceiver.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/ImportFilterCallback.h subversion/bindings/javahl/native/InfoCallback.h subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIStackElement.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/ListCallback.h subversion/bindings/javahl/native/LogMessageCallback.h subversion/bindings/javahl/native/OutputStream.h subversion/bindings/javahl/native/PatchCallback.h subversion/bindings/javahl/native/Path.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/Prompter.h subversion/bindings/javahl/native/ProplistCallback.h subversion/bindings/javahl/native/Revision.h subversion/bindings/javahl/native/RevisionRange.h subversion/bindings/javahl/native/RevpropTable.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/SVNClient.h subversion/bindings/javahl/native/StatusCallback.h subversion/bindings/javahl/native/StringArray.h subversion/bindings/javahl/native/Targets.h subversion/bindings/javahl/native/VersionExtended.h subversion/bindings/javahl/native/version.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.cpp
+
+subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.lo: subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNRepos.h subversion/bindings/javahl/native/Array.h subversion/bindings/javahl/native/EnumMapper.h subversion/bindings/javahl/native/File.h subversion/bindings/javahl/native/InputStream.h subversion/bindings/javahl/native/JNIByteArray.h subversion/bindings/javahl/native/JNIStackElement.h subversion/bindings/javahl/native/JNIStringHolder.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/MessageReceiver.h subversion/bindings/javahl/native/OutputStream.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/ReposFreezeAction.h subversion/bindings/javahl/native/ReposNotifyCallback.h subversion/bindings/javahl/native/Revision.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/SVNRepos.h subversion/bindings/javahl/native/StringArray.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.cpp
+
+subversion/bindings/javahl/native/org_apache_subversion_javahl_types_Version.lo: subversion/bindings/javahl/native/org_apache_subversion_javahl_types_Version.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Version.h subversion/bindings/javahl/native/JNIStackElement.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h subversion/include/svn_version.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/org_apache_subversion_javahl_types_Version.cpp
+
+subversion/bindings/javahl/native/org_apache_subversion_javahl_types_VersionExtended.lo: subversion/bindings/javahl/native/org_apache_subversion_javahl_types_VersionExtended.cpp subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LinkedLib.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LinkedLibIterator.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LoadedLib.h subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LoadedLibIterator.h subversion/bindings/javahl/native/JNIStackElement.h subversion/bindings/javahl/native/JNIUtil.h subversion/bindings/javahl/native/Pool.h subversion/bindings/javahl/native/SVNBase.h subversion/bindings/javahl/native/VersionExtended.h subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h subversion/include/svn_version.h
+ $(COMPILE_JAVAHL_CXX) $(canonicalized_srcdir)subversion/bindings/javahl/native/org_apache_subversion_javahl_types_VersionExtended.cpp
+
+subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.lo: subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/proxy/swig_perl_external_runtime.swg subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_SWIG_PL) $(canonicalized_srcdir)subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.c
+
+subversion/bindings/swig/perl/native/core.lo: subversion/bindings/swig/perl/native/core.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/core.c
+
+subversion/bindings/swig/perl/native/svn_client.lo: subversion/bindings/swig/perl/native/svn_client.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h
+ $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_client.c
+
+subversion/bindings/swig/perl/native/svn_delta.lo: subversion/bindings/swig/perl/native/svn_delta.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_delta.c
+
+subversion/bindings/swig/perl/native/svn_diff.lo: subversion/bindings/swig/perl/native/svn_diff.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_diff.c
+
+subversion/bindings/swig/perl/native/svn_fs.lo: subversion/bindings/swig/perl/native/svn_fs.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_fs.c
+
+subversion/bindings/swig/perl/native/svn_ra.lo: subversion/bindings/swig/perl/native/svn_ra.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_ra.c
+
+subversion/bindings/swig/perl/native/svn_repos.lo: subversion/bindings/swig/perl/native/svn_repos.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_repos.c
+
+subversion/bindings/swig/perl/native/svn_wc.lo: subversion/bindings/swig/perl/native/svn_wc.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h
+ $(COMPILE_PL_WRAPPER) subversion/bindings/swig/perl/native/svn_wc.c
+
+subversion/bindings/swig/python/core.lo: subversion/bindings/swig/python/core.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/core.c
+
+subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.lo: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c subversion/bindings/swig/proxy/swig_python_external_runtime.swg subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_SWIG_PY) $(canonicalized_srcdir)subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
+
+subversion/bindings/swig/python/svn_client.lo: subversion/bindings/swig/python/svn_client.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h
+ $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_client.c
+
+subversion/bindings/swig/python/svn_delta.lo: subversion/bindings/swig/python/svn_delta.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_delta.c
+
+subversion/bindings/swig/python/svn_diff.lo: subversion/bindings/swig/python/svn_diff.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_diff.c
+
+subversion/bindings/swig/python/svn_fs.lo: subversion/bindings/swig/python/svn_fs.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_fs.c
+
+subversion/bindings/swig/python/svn_ra.lo: subversion/bindings/swig/python/svn_ra.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_ra.c
+
+subversion/bindings/swig/python/svn_repos.lo: subversion/bindings/swig/python/svn_repos.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_repos.c
+
+subversion/bindings/swig/python/svn_wc.lo: subversion/bindings/swig/python/svn_wc.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h
+ $(COMPILE_PY_WRAPPER) subversion/bindings/swig/python/svn_wc.c
+
+subversion/bindings/swig/ruby/core.lo: subversion/bindings/swig/ruby/core.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/core.c
+
+subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.lo: subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.c subversion/bindings/swig/proxy/swig_ruby_external_runtime.swg subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_nls.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_SWIG_RB) $(canonicalized_srcdir)subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.c
+
+subversion/bindings/swig/ruby/svn_client.lo: subversion/bindings/swig/ruby/svn_client.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h
+ $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_client.c
+
+subversion/bindings/swig/ruby/svn_delta.lo: subversion/bindings/swig/ruby/svn_delta.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_delta.c
+
+subversion/bindings/swig/ruby/svn_diff.lo: subversion/bindings/swig/ruby/svn_diff.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_delta.h subversion/include/svn_fs.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_diff.c
+
+subversion/bindings/swig/ruby/svn_fs.lo: subversion/bindings/swig/ruby/svn_fs.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_fs.c
+
+subversion/bindings/swig/ruby/svn_ra.lo: subversion/bindings/swig/ruby/svn_ra.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_ra.c
+
+subversion/bindings/swig/ruby/svn_repos.lo: subversion/bindings/swig/ruby/svn_repos.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_ra.h subversion/include/svn_wc.h subversion/svn_private_config.h
+ $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_repos.c
+
+subversion/bindings/swig/ruby/svn_wc.lo: subversion/bindings/swig/ruby/svn_wc.c subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.h subversion/include/svn_client.h subversion/include/svn_fs.h subversion/include/svn_repos.h subversion/svn_private_config.h
+ $(COMPILE_RB_WRAPPER) subversion/bindings/swig/ruby/svn_wc.c
+
+subversion/libsvn_auth_gnome_keyring/gnome_keyring.lo: subversion/libsvn_auth_gnome_keyring/gnome_keyring.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_auth_gnome_keyring/version.lo: subversion/libsvn_auth_gnome_keyring/version.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h
+
+subversion/libsvn_auth_kwallet/kwallet.lo: subversion/libsvn_auth_kwallet/kwallet.cpp subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/svn_private_config.h
+
+subversion/libsvn_auth_kwallet/version.lo: subversion/libsvn_auth_kwallet/version.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h
+
+subversion/libsvn_client/add.lo: subversion/libsvn_client/add.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/blame.lo: subversion/libsvn_client/blame.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/cat.lo: subversion/libsvn_client/cat.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/changelist.lo: subversion/libsvn_client/changelist.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/checkout.lo: subversion/libsvn_client/checkout.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/cleanup.lo: subversion/libsvn_client/cleanup.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/cmdline.lo: subversion/libsvn_client/cmdline.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/commit.lo: subversion/libsvn_client/commit.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/commit_util.lo: subversion/libsvn_client/commit_util.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/compat_providers.lo: subversion/libsvn_client/compat_providers.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h
+
+subversion/libsvn_client/copy.lo: subversion/libsvn_client/copy.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/svn_private_config.h
+
+subversion/libsvn_client/copy_foreign.lo: subversion/libsvn_client/copy_foreign.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/ctx.lo: subversion/libsvn_client/ctx.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h
+
+subversion/libsvn_client/delete.lo: subversion/libsvn_client/delete.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/deprecated.lo: subversion/libsvn_client/deprecated.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/svn_private_config.h
+
+subversion/libsvn_client/diff.lo: subversion/libsvn_client/diff.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_private.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/diff_local.lo: subversion/libsvn_client/diff_local.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/diff_summarize.lo: subversion/libsvn_client/diff_summarize.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h
+
+subversion/libsvn_client/export.lo: subversion/libsvn_client/export.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/externals.lo: subversion/libsvn_client/externals.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/import.lo: subversion/libsvn_client/import.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/info.lo: subversion/libsvn_client/info.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/iprops.lo: subversion/libsvn_client/iprops.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/list.lo: subversion/libsvn_client/list.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/locking_commands.lo: subversion/libsvn_client/locking_commands.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/log.lo: subversion/libsvn_client/log.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/merge.lo: subversion/libsvn_client/merge.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_magic.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/svn_private_config.h
+
+subversion/libsvn_client/mergeinfo.lo: subversion/libsvn_client/mergeinfo.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_magic.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/svn_private_config.h
+
+subversion/libsvn_client/patch.lo: subversion/libsvn_client/patch.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_eol_private.h subversion/include/private/svn_magic.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/prop_commands.lo: subversion/libsvn_client/prop_commands.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/ra.lo: subversion/libsvn_client/ra.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/svn_private_config.h
+
+subversion/libsvn_client/relocate.lo: subversion/libsvn_client/relocate.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/repos_diff.lo: subversion/libsvn_client/repos_diff.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/resolved.lo: subversion/libsvn_client/resolved.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/revert.lo: subversion/libsvn_client/revert.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/revisions.lo: subversion/libsvn_client/revisions.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/status.lo: subversion/libsvn_client/status.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/switch.lo: subversion/libsvn_client/switch.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/update.lo: subversion/libsvn_client/update.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/upgrade.lo: subversion/libsvn_client/upgrade.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/url.lo: subversion/libsvn_client/url.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/util.lo: subversion/libsvn_client/util.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/svn_private_config.h
+
+subversion/libsvn_client/version.lo: subversion/libsvn_client/version.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h
+
+subversion/libsvn_delta/cancel.lo: subversion/libsvn_delta/cancel.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_delta/compat.lo: subversion/libsvn_delta/compat.c subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_editor.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_delta/compose_delta.lo: subversion/libsvn_delta/compose_delta.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h
+
+subversion/libsvn_delta/debug_editor.lo: subversion/libsvn_delta/debug_editor.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/debug_editor.h
+
+subversion/libsvn_delta/default_editor.lo: subversion/libsvn_delta/default_editor.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_delta/deprecated.lo: subversion/libsvn_delta/deprecated.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_delta/depth_filter_editor.lo: subversion/libsvn_delta/depth_filter_editor.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_delta/editor.lo: subversion/libsvn_delta/editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_delta/path_driver.lo: subversion/libsvn_delta/path_driver.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_delta/svndiff.lo: subversion/libsvn_delta/svndiff.c subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_editor.h subversion/include/private/svn_error_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h subversion/svn_private_config.h
+
+subversion/libsvn_delta/text_delta.lo: subversion/libsvn_delta/text_delta.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h
+
+subversion/libsvn_delta/version.lo: subversion/libsvn_delta/version.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h
+
+subversion/libsvn_delta/xdelta.lo: subversion/libsvn_delta/xdelta.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h
+
+subversion/libsvn_diff/deprecated.lo: subversion/libsvn_diff/deprecated.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h
+
+subversion/libsvn_diff/diff.lo: subversion/libsvn_diff/diff.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_diff/diff.h
+
+subversion/libsvn_diff/diff3.lo: subversion/libsvn_diff/diff3.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_diff/diff.h
+
+subversion/libsvn_diff/diff4.lo: subversion/libsvn_diff/diff4.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_diff/diff.h
+
+subversion/libsvn_diff/diff_file.lo: subversion/libsvn_diff/diff_file.c subversion/include/private/svn_adler32.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_diff_private.h subversion/include/private/svn_eol_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_diff/diff.h subversion/svn_private_config.h
+
+subversion/libsvn_diff/diff_memory.lo: subversion/libsvn_diff/diff_memory.c subversion/include/private/svn_adler32.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_private.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_diff/diff.h subversion/svn_private_config.h
+
+subversion/libsvn_diff/diff_tree.lo: subversion/libsvn_diff/diff_tree.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_diff/lcs.lo: subversion/libsvn_diff/lcs.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_diff/diff.h
+
+subversion/libsvn_diff/parse-diff.lo: subversion/libsvn_diff/parse-diff.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_eol_private.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h
+
+subversion/libsvn_diff/token.lo: subversion/libsvn_diff/token.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_diff/diff.h
+
+subversion/libsvn_diff/util.lo: subversion/libsvn_diff/util.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_diff/diff.h subversion/svn_private_config.h
+
+subversion/libsvn_fs/access.lo: subversion/libsvn_fs/access.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h
+
+subversion/libsvn_fs/editor.lo: subversion/libsvn_fs/editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/svn_private_config.h
+
+subversion/libsvn_fs/fs-loader.lo: subversion/libsvn_fs/fs-loader.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_mutex.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_fs/fs-loader.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/bdb-err.lo: subversion/libsvn_fs_base/bdb/bdb-err.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/bdb_compat.lo: subversion/libsvn_fs_base/bdb/bdb_compat.c subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/changes-table.lo: subversion/libsvn_fs_base/bdb/changes-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/checksum-reps-table.lo: subversion/libsvn_fs_base/bdb/checksum-reps-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/checksum-reps-table.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/copies-table.lo: subversion/libsvn_fs_base/bdb/copies-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/copies-table.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/dbt.lo: subversion/libsvn_fs_base/bdb/dbt.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/id.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/env.lo: subversion/libsvn_fs_base/bdb/env.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_mutex.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/lock-tokens-table.lo: subversion/libsvn_fs_base/bdb/lock-tokens-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/lock-tokens-table.h subversion/libsvn_fs_base/bdb/locks-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/locks-table.lo: subversion/libsvn_fs_base/bdb/locks-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/lock-tokens-table.h subversion/libsvn_fs_base/bdb/locks-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/miscellaneous-table.lo: subversion/libsvn_fs_base/bdb/miscellaneous-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/miscellaneous-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/node-origins-table.lo: subversion/libsvn_fs_base/bdb/node-origins-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/node-origins-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/nodes-table.lo: subversion/libsvn_fs_base/bdb/nodes-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/reps-table.lo: subversion/libsvn_fs_base/bdb/reps-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/reps-table.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/rev-table.lo: subversion/libsvn_fs_base/bdb/rev-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/strings-table.lo: subversion/libsvn_fs_base/bdb/strings-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/txn-table.lo: subversion/libsvn_fs_base/bdb/txn-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/bdb/uuids-table.lo: subversion/libsvn_fs_base/bdb/uuids-table.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/dbt.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/uuids-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/dag.lo: subversion/libsvn_fs_base/dag.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/checksum-reps-table.h subversion/libsvn_fs_base/bdb/copies-table.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/node-origins-table.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/bdb/reps-table.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/dag.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/node-rev.h subversion/libsvn_fs_base/reps-strings.h subversion/libsvn_fs_base/revs-txns.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/err.lo: subversion/libsvn_fs_base/err.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/id.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/fs.lo: subversion/libsvn_fs_base/fs.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/checksum-reps-table.h subversion/libsvn_fs_base/bdb/copies-table.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/lock-tokens-table.h subversion/libsvn_fs_base/bdb/locks-table.h subversion/libsvn_fs_base/bdb/miscellaneous-table.h subversion/libsvn_fs_base/bdb/node-origins-table.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/bdb/reps-table.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/bdb/uuids-table.h subversion/libsvn_fs_base/dag.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/lock.h subversion/libsvn_fs_base/revs-txns.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/tree.h subversion/libsvn_fs_base/uuid.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/id.lo: subversion/libsvn_fs_base/id.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/id.h
+
+subversion/libsvn_fs_base/key-gen.lo: subversion/libsvn_fs_base/key-gen.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_base/key-gen.h
+
+subversion/libsvn_fs_base/lock.lo: subversion/libsvn_fs_base/lock.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/lock-tokens-table.h subversion/libsvn_fs_base/bdb/locks-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/lock.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/tree.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/node-rev.lo: subversion/libsvn_fs_base/node-rev.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/node-origins-table.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/node-rev.h subversion/libsvn_fs_base/reps-strings.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/reps-strings.lo: subversion/libsvn_fs_base/reps-strings.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/reps-table.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/reps-strings.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/revs-txns.lo: subversion/libsvn_fs_base/revs-txns.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/copies-table.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/dag.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/revs-txns.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/tree.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/trail.lo: subversion/libsvn_fs_base/trail.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb-err.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/tree.lo: subversion/libsvn_fs_base/tree.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/copies-table.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/miscellaneous-table.h subversion/libsvn_fs_base/bdb/node-origins-table.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/bdb/rev-table.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/dag.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/lock.h subversion/libsvn_fs_base/node-rev.h subversion/libsvn_fs_base/revs-txns.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/tree.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/util/fs_skels.lo: subversion/libsvn_fs_base/util/fs_skels.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_skel.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_base/uuid.lo: subversion/libsvn_fs_base/uuid.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/uuids-table.h subversion/libsvn_fs_base/err.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/uuid.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_fs/caching.lo: subversion/libsvn_fs_fs/caching.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/dag.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/temp_serializer.h subversion/libsvn_fs_fs/tree.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_fs/dag.lo: subversion/libsvn_fs_fs/dag.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_temp_serializer.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/dag.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/key-gen.h subversion/libsvn_fs_fs/temp_serializer.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_fs/fs.lo: subversion/libsvn_fs_fs/fs.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/lock.h subversion/libsvn_fs_fs/rep-cache.h subversion/libsvn_fs_fs/tree.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_fs/fs_fs.lo: subversion/libsvn_fs_fs/fs_fs.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/key-gen.h subversion/libsvn_fs_fs/lock.h subversion/libsvn_fs_fs/rep-cache.h subversion/libsvn_fs_fs/temp_serializer.h subversion/libsvn_fs_fs/tree.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_fs/id.lo: subversion/libsvn_fs_fs/id.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/private/svn_temp_serializer.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/id.h
+
+subversion/libsvn_fs_fs/key-gen.lo: subversion/libsvn_fs_fs/key-gen.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_fs/key-gen.h
+
+subversion/libsvn_fs_fs/lock.lo: subversion/libsvn_fs_fs/lock.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/lock.h subversion/libsvn_fs_fs/tree.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_fs/rep-cache.lo: subversion/libsvn_fs_fs/rep-cache.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/rep-cache-db.h subversion/libsvn_fs_fs/rep-cache.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_fs/temp_serializer.lo: subversion/libsvn_fs_fs/temp_serializer.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_temp_serializer.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/temp_serializer.h
+
+subversion/libsvn_fs_fs/tree.lo: subversion/libsvn_fs_fs/tree.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_fs/dag.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/libsvn_fs_fs/key-gen.h subversion/libsvn_fs_fs/lock.h subversion/libsvn_fs_fs/temp_serializer.h subversion/libsvn_fs_fs/tree.h subversion/svn_private_config.h
+
+subversion/libsvn_fs_util/fs-util.lo: subversion/libsvn_fs_util/fs-util.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/svn_private_config.h
+
+subversion/libsvn_ra/compat.lo: subversion/libsvn_ra/compat.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra/ra_loader.h subversion/svn_private_config.h
+
+subversion/libsvn_ra/debug_reporter.lo: subversion/libsvn_ra/debug_reporter.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra/debug_reporter.h
+
+subversion/libsvn_ra/deprecated.lo: subversion/libsvn_ra/deprecated.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra/deprecated.h subversion/libsvn_ra/ra_loader.h subversion/svn_private_config.h
+
+subversion/libsvn_ra/editor.lo: subversion/libsvn_ra/editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra/ra_loader.h subversion/svn_private_config.h
+
+subversion/libsvn_ra/ra_loader.lo: subversion/libsvn_ra/ra_loader.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/deprecated.h subversion/libsvn_ra/ra_loader.h subversion/svn_private_config.h
+
+subversion/libsvn_ra/util.lo: subversion/libsvn_ra/util.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_local/ra_plugin.lo: subversion/libsvn_ra_local/ra_plugin.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_repos_private.h subversion/include/svn_auth.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra/wrapper_template.h subversion/libsvn_ra_local/ra_local.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_local/split_url.lo: subversion/libsvn_ra_local/split_url.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_local/ra_local.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/blame.lo: subversion/libsvn_ra_serf/blame.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/blncache.lo: subversion/libsvn_ra_serf/blncache.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_serf/blncache.h
+
+subversion/libsvn_ra_serf/commit.lo: subversion/libsvn_ra_serf/commit.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_skel.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/get_deleted_rev.lo: subversion/libsvn_ra_serf/get_deleted_rev.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/getdate.lo: subversion/libsvn_ra_serf/getdate.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/getlocations.lo: subversion/libsvn_ra_serf/getlocations.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/getlocationsegments.lo: subversion/libsvn_ra_serf/getlocationsegments.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/getlocks.lo: subversion/libsvn_ra_serf/getlocks.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/inherited_props.lo: subversion/libsvn_ra_serf/inherited_props.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/locks.lo: subversion/libsvn_ra_serf/locks.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/log.lo: subversion/libsvn_ra_serf/log.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/merge.lo: subversion/libsvn_ra_serf/merge.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/mergeinfo.lo: subversion/libsvn_ra_serf/mergeinfo.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/options.lo: subversion/libsvn_ra_serf/options.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/property.lo: subversion/libsvn_ra_serf/property.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/replay.lo: subversion/libsvn_ra_serf/replay.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/sb_bucket.lo: subversion/libsvn_ra_serf/sb_bucket.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/serf.lo: subversion/libsvn_ra_serf/serf.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra/wrapper_template.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/update.lo: subversion/libsvn_ra_serf/update.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/util.lo: subversion/libsvn_ra_serf/util.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_serf/util_error.lo: subversion/libsvn_ra_serf/util_error.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_error_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h
+
+subversion/libsvn_ra_serf/xml.lo: subversion/libsvn_ra_serf/xml.c subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra_serf/blncache.h subversion/libsvn_ra_serf/ra_serf.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_svn/client.lo: subversion/libsvn_ra_svn/client.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_ra/ra_loader.h subversion/libsvn_ra/wrapper_template.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_svn/cram.lo: subversion/libsvn_ra_svn/cram.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_svn/cyrus_auth.lo: subversion/libsvn_ra_svn/cyrus_auth.c subversion/include/private/ra_svn_sasl.h subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_mutex.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_svn/deprecated.lo: subversion/libsvn_ra_svn/deprecated.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_ra_svn/editorp.lo: subversion/libsvn_ra_svn/editorp.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_svn/internal_auth.lo: subversion/libsvn_ra_svn/internal_auth.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_svn/marshal.lo: subversion/libsvn_ra_svn/marshal.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_error_private.h subversion/include/private/svn_ra_svn_private.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_svn/streams.lo: subversion/libsvn_ra_svn/streams.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_svn/ra_svn.h subversion/svn_private_config.h
+
+subversion/libsvn_ra_svn/version.lo: subversion/libsvn_ra_svn/version.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_ra_svn.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h
+
+subversion/libsvn_repos/authz.lo: subversion/libsvn_repos/authz.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h
+
+subversion/libsvn_repos/commit.lo: subversion/libsvn_repos/commit.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_fspath.h subversion/include/private/svn_repos_private.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/delta.lo: subversion/libsvn_repos/delta.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/deprecated.lo: subversion/libsvn_repos/deprecated.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/dump.lo: subversion/libsvn_repos/dump.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_mergeinfo_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/fs-wrap.lo: subversion/libsvn_repos/fs-wrap.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_repos_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/hooks.lo: subversion/libsvn_repos/hooks.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_repos_private.h subversion/include/private/svn_string_private.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/load-fs-vtable.lo: subversion/libsvn_repos/load-fs-vtable.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/load.lo: subversion/libsvn_repos/load.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_mergeinfo_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/log.lo: subversion/libsvn_repos/log.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/node_tree.lo: subversion/libsvn_repos/node_tree.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/notify.lo: subversion/libsvn_repos/notify.c subversion/include/private/svn_debug.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/replay.lo: subversion/libsvn_repos/replay.c subversion/include/private/svn_debug.h subversion/include/private/svn_delta_private.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_repos_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/reporter.lo: subversion/libsvn_repos/reporter.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_fspath.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/repos.lo: subversion/libsvn_repos/repos.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_repos_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_repos/rev_hunt.lo: subversion/libsvn_repos/rev_hunt.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_repos/repos.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/adler32.lo: subversion/libsvn_subr/adler32.c subversion/include/private/svn_adler32.h
+
+subversion/libsvn_subr/atomic.lo: subversion/libsvn_subr/atomic.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/auth.lo: subversion/libsvn_subr/auth.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_subr/auth.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/base64.lo: subversion/libsvn_subr/base64.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/cache-inprocess.lo: subversion/libsvn_subr/cache-inprocess.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_mutex.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/cache.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/cache-membuffer.lo: subversion/libsvn_subr/cache-membuffer.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_mutex.h subversion/include/private/svn_pseudo_md5.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/cache.h subversion/libsvn_subr/md5.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/cache-memcache.lo: subversion/libsvn_subr/cache-memcache.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/cache.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/cache.lo: subversion/libsvn_subr/cache.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/cache.h
+
+subversion/libsvn_subr/cache_config.lo: subversion/libsvn_subr/cache_config.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/checksum.lo: subversion/libsvn_subr/checksum.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/md5.h subversion/libsvn_subr/sha1.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/cmdline.lo: subversion/libsvn_subr/cmdline.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_nls.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_xml.h subversion/libsvn_subr/win32_crashrpt.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/compat.lo: subversion/libsvn_subr/compat.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/config.lo: subversion/libsvn_subr/config.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/config_impl.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/config_auth.lo: subversion/libsvn_subr/config_auth.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/auth.h subversion/libsvn_subr/config_impl.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/config_file.lo: subversion/libsvn_subr/config_file.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/libsvn_subr/config_impl.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/config_win.lo: subversion/libsvn_subr/config_win.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_subr/config_impl.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/crypto.lo: subversion/libsvn_subr/crypto.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/crypto.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/ctype.lo: subversion/libsvn_subr/ctype.c subversion/include/svn_ctype.h
+
+subversion/libsvn_subr/date.lo: subversion/libsvn_subr/date.c subversion/include/private/svn_debug.h subversion/include/private/svn_token.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/debug.lo: subversion/libsvn_subr/debug.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/deprecated.lo: subversion/libsvn_subr/deprecated.c subversion/include/private/svn_debug.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/libsvn_subr/opt.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/dirent_uri.lo: subversion/libsvn_subr/dirent_uri.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/dirent_uri.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/dso.lo: subversion/libsvn_subr/dso.c subversion/include/private/svn_debug.h subversion/include/private/svn_mutex.h subversion/include/svn_checksum.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/eol.lo: subversion/libsvn_subr/eol.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_eol_private.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/error.lo: subversion/libsvn_subr/error.c subversion/include/private/svn_debug.h subversion/include/private/svn_error_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/gpg_agent.lo: subversion/libsvn_subr/gpg_agent.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/hash.lo: subversion/libsvn_subr/hash.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/io.lo: subversion/libsvn_subr/io.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_io_private.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/iter.lo: subversion/libsvn_subr/iter.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_iter.h subversion/include/svn_pools.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/lock.lo: subversion/libsvn_subr/lock.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/log.lo: subversion/libsvn_subr/log.c subversion/include/private/svn_debug.h subversion/include/private/svn_log.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/macos_keychain.lo: subversion/libsvn_subr/macos_keychain.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/magic.lo: subversion/libsvn_subr/magic.c subversion/include/private/svn_debug.h subversion/include/private/svn_magic.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/md5.lo: subversion/libsvn_subr/md5.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_md5.h subversion/include/svn_types.h subversion/libsvn_subr/md5.h
+
+subversion/libsvn_subr/mergeinfo.lo: subversion/libsvn_subr/mergeinfo.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/mutex.lo: subversion/libsvn_subr/mutex.c subversion/include/private/svn_debug.h subversion/include/private/svn_mutex.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/named_atomic.lo: subversion/libsvn_subr/named_atomic.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/nls.lo: subversion/libsvn_subr/nls.c subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_nls.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/opt.lo: subversion/libsvn_subr/opt.c subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_subr/opt.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/path.lo: subversion/libsvn_subr/path.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_subr/dirent_uri.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/pool.lo: subversion/libsvn_subr/pool.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/prompt.lo: subversion/libsvn_subr/prompt.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/properties.lo: subversion/libsvn_subr/properties.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/pseudo_md5.lo: subversion/libsvn_subr/pseudo_md5.c subversion/include/private/svn_pseudo_md5.h
+
+subversion/libsvn_subr/quoprint.lo: subversion/libsvn_subr/quoprint.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_quoprint.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/sha1.lo: subversion/libsvn_subr/sha1.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/libsvn_subr/sha1.h
+
+subversion/libsvn_subr/simple_providers.lo: subversion/libsvn_subr/simple_providers.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/libsvn_subr/auth.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/skel.lo: subversion/libsvn_subr/skel.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/private/svn_string_private.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/sorts.lo: subversion/libsvn_subr/sorts.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/spillbuf.lo: subversion/libsvn_subr/spillbuf.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/sqlite.lo: subversion/libsvn_subr/sqlite.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/internal_statements.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/sqlite3wrapper.lo: subversion/libsvn_subr/sqlite3wrapper.c subversion/svn_private_config.h
+
+subversion/libsvn_subr/ssl_client_cert_providers.lo: subversion/libsvn_subr/ssl_client_cert_providers.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/ssl_client_cert_pw_providers.lo: subversion/libsvn_subr/ssl_client_cert_pw_providers.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/ssl_server_trust_providers.lo: subversion/libsvn_subr/ssl_server_trust_providers.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/stream.lo: subversion/libsvn_subr/stream.c subversion/include/private/svn_debug.h subversion/include/private/svn_eol_private.h subversion/include/private/svn_error_private.h subversion/include/private/svn_io_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/string.lo: subversion/libsvn_subr/string.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_string_private.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/subst.lo: subversion/libsvn_subr/subst.c subversion/include/private/svn_debug.h subversion/include/private/svn_io_private.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/sysinfo.lo: subversion/libsvn_subr/sysinfo.c subversion/include/private/svn_debug.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_subr/sysinfo.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/target.lo: subversion/libsvn_subr/target.c subversion/include/private/svn_debug.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/temp_serializer.lo: subversion/libsvn_subr/temp_serializer.c subversion/include/private/svn_debug.h subversion/include/private/svn_temp_serializer.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/time.lo: subversion/libsvn_subr/time.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/token.lo: subversion/libsvn_subr/token.c subversion/include/private/svn_debug.h subversion/include/private/svn_token.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/types.lo: subversion/libsvn_subr/types.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_props.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/user.lo: subversion/libsvn_subr/user.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h
+
+subversion/libsvn_subr/username_providers.lo: subversion/libsvn_subr/username_providers.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h
+
+subversion/libsvn_subr/utf.lo: subversion/libsvn_subr/utf.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_mutex.h subversion/include/private/svn_string_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_subr/win32_xlate.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/utf_validate.lo: subversion/libsvn_subr/utf_validate.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_eol_private.h subversion/include/private/svn_utf_private.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h
+
+subversion/libsvn_subr/utf_width.lo: subversion/libsvn_subr/utf_width.c subversion/include/private/svn_debug.h subversion/include/private/svn_utf_private.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/validate.lo: subversion/libsvn_subr/validate.c subversion/include/private/svn_debug.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/version.lo: subversion/libsvn_subr/version.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_subr/sysinfo.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/win32_crashrpt.lo: subversion/libsvn_subr/win32_crashrpt.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_subr/win32_crashrpt.h subversion/libsvn_subr/win32_crashrpt_dll.h
+
+subversion/libsvn_subr/win32_crypto.lo: subversion/libsvn_subr/win32_crypto.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/svn_private_config.h
+
+subversion/libsvn_subr/win32_xlate.lo: subversion/libsvn_subr/win32_xlate.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/libsvn_subr/win32_xlate.h
+
+subversion/libsvn_subr/xml.lo: subversion/libsvn_subr/xml.c subversion/include/private/svn_debug.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/adm_crawler.lo: subversion/libsvn_wc/adm_crawler.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/adm_files.lo: subversion/libsvn_wc/adm_files.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/adm_ops.lo: subversion/libsvn_wc/adm_ops.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/ambient_depth_filter_editor.lo: subversion/libsvn_wc/ambient_depth_filter_editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/cleanup.lo: subversion/libsvn_wc/cleanup.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/conflicts.lo: subversion/libsvn_wc/conflicts.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_string_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/context.lo: subversion/libsvn_wc/context.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/copy.lo: subversion/libsvn_wc/copy.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/crop.lo: subversion/libsvn_wc/crop.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/delete.lo: subversion/libsvn_wc/delete.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/deprecated.lo: subversion/libsvn_wc/deprecated.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/diff_editor.lo: subversion/libsvn_wc/diff_editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/diff.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/diff_local.lo: subversion/libsvn_wc/diff_local.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/diff.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/entries.lo: subversion/libsvn_wc/entries.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/tree_conflicts.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/externals.lo: subversion/libsvn_wc/externals.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/info.lo: subversion/libsvn_wc/info.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/lock.lo: subversion/libsvn_wc/lock.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/merge.lo: subversion/libsvn_wc/merge.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/node.lo: subversion/libsvn_wc/node.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/old-and-busted.lo: subversion/libsvn_wc/old-and-busted.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/props.lo: subversion/libsvn_wc/props.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/questions.lo: subversion/libsvn_wc/questions.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/relocate.lo: subversion/libsvn_wc/relocate.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/revert.lo: subversion/libsvn_wc/revert.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_io_private.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/revision_status.lo: subversion/libsvn_wc/revision_status.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/status.lo: subversion/libsvn_wc/status.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/tree_conflicts.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/translate.lo: subversion/libsvn_wc/translate.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/tree_conflicts.lo: subversion/libsvn_wc/tree_conflicts.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/tree_conflicts.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/update_editor.lo: subversion/libsvn_wc/update_editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_subr_private.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/upgrade.lo: subversion/libsvn_wc/upgrade.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/props.h subversion/libsvn_wc/tree_conflicts.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/util.lo: subversion/libsvn_wc/util.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/wc_db.lo: subversion/libsvn_wc/wc_db.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/entries.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/props.h subversion/libsvn_wc/token-map.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/wc_db_pristine.lo: subversion/libsvn_wc/wc_db_pristine.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/wc_db_update_move.lo: subversion/libsvn_wc/wc_db_update_move.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/token-map.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/wc_db_util.lo: subversion/libsvn_wc/wc_db_util.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/wc_db_wcroot.lo: subversion/libsvn_wc/wc_db_wcroot.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/props.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/wcroot_anchor.lo: subversion/libsvn_wc/wcroot_anchor.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/libsvn_wc/workqueue.lo: subversion/libsvn_wc/workqueue.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/adm_files.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/props.h subversion/libsvn_wc/translate.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h
+
+subversion/mod_authz_svn/mod_authz_svn.lo: subversion/mod_authz_svn/mod_authz_svn.c subversion/include/mod_authz_svn.h subversion/include/mod_dav_svn.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_authz_svn/mod_authz_svn.c ; else echo "fake" > subversion/mod_authz_svn/mod_authz_svn.lo ; fi
+
+subversion/mod_dav_svn/activity.lo: subversion/mod_dav_svn/activity.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/activity.c ; else echo "fake" > subversion/mod_dav_svn/activity.lo ; fi
+
+subversion/mod_dav_svn/authz.lo: subversion/mod_dav_svn/authz.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/authz.c ; else echo "fake" > subversion/mod_dav_svn/authz.lo ; fi
+
+subversion/mod_dav_svn/deadprops.lo: subversion/mod_dav_svn/deadprops.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/deadprops.c ; else echo "fake" > subversion/mod_dav_svn/deadprops.lo ; fi
+
+subversion/mod_dav_svn/liveprops.lo: subversion/mod_dav_svn/liveprops.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/liveprops.c ; else echo "fake" > subversion/mod_dav_svn/liveprops.lo ; fi
+
+subversion/mod_dav_svn/lock.lo: subversion/mod_dav_svn/lock.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/lock.c ; else echo "fake" > subversion/mod_dav_svn/lock.lo ; fi
+
+subversion/mod_dav_svn/merge.lo: subversion/mod_dav_svn/merge.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/merge.c ; else echo "fake" > subversion/mod_dav_svn/merge.lo ; fi
+
+subversion/mod_dav_svn/mirror.lo: subversion/mod_dav_svn/mirror.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/mirror.c ; else echo "fake" > subversion/mod_dav_svn/mirror.lo ; fi
+
+subversion/mod_dav_svn/mod_dav_svn.lo: subversion/mod_dav_svn/mod_dav_svn.c subversion/include/mod_authz_svn.h subversion/include/mod_dav_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/private/svn_subr_private.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/mod_dav_svn.c ; else echo "fake" > subversion/mod_dav_svn/mod_dav_svn.lo ; fi
+
+subversion/mod_dav_svn/posts/create_txn.lo: subversion/mod_dav_svn/posts/create_txn.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/posts/create_txn.c ; else echo "fake" > subversion/mod_dav_svn/posts/create_txn.lo ; fi
+
+subversion/mod_dav_svn/reports/dated-rev.lo: subversion/mod_dav_svn/reports/dated-rev.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/dated-rev.c ; else echo "fake" > subversion/mod_dav_svn/reports/dated-rev.lo ; fi
+
+subversion/mod_dav_svn/reports/deleted-rev.lo: subversion/mod_dav_svn/reports/deleted-rev.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/deleted-rev.c ; else echo "fake" > subversion/mod_dav_svn/reports/deleted-rev.lo ; fi
+
+subversion/mod_dav_svn/reports/file-revs.lo: subversion/mod_dav_svn/reports/file-revs.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/file-revs.c ; else echo "fake" > subversion/mod_dav_svn/reports/file-revs.lo ; fi
+
+subversion/mod_dav_svn/reports/get-location-segments.lo: subversion/mod_dav_svn/reports/get-location-segments.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/get-location-segments.c ; else echo "fake" > subversion/mod_dav_svn/reports/get-location-segments.lo ; fi
+
+subversion/mod_dav_svn/reports/get-locations.lo: subversion/mod_dav_svn/reports/get-locations.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/get-locations.c ; else echo "fake" > subversion/mod_dav_svn/reports/get-locations.lo ; fi
+
+subversion/mod_dav_svn/reports/get-locks.lo: subversion/mod_dav_svn/reports/get-locks.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/get-locks.c ; else echo "fake" > subversion/mod_dav_svn/reports/get-locks.lo ; fi
+
+subversion/mod_dav_svn/reports/inherited-props.lo: subversion/mod_dav_svn/reports/inherited-props.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/inherited-props.c ; else echo "fake" > subversion/mod_dav_svn/reports/inherited-props.lo ; fi
+
+subversion/mod_dav_svn/reports/log.lo: subversion/mod_dav_svn/reports/log.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/log.c ; else echo "fake" > subversion/mod_dav_svn/reports/log.lo ; fi
+
+subversion/mod_dav_svn/reports/mergeinfo.lo: subversion/mod_dav_svn/reports/mergeinfo.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/mergeinfo.c ; else echo "fake" > subversion/mod_dav_svn/reports/mergeinfo.lo ; fi
+
+subversion/mod_dav_svn/reports/replay.lo: subversion/mod_dav_svn/reports/replay.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/replay.c ; else echo "fake" > subversion/mod_dav_svn/reports/replay.lo ; fi
+
+subversion/mod_dav_svn/reports/update.lo: subversion/mod_dav_svn/reports/update.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_skel.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/reports/update.c ; else echo "fake" > subversion/mod_dav_svn/reports/update.lo ; fi
+
+subversion/mod_dav_svn/repos.lo: subversion/mod_dav_svn/repos.c subversion/include/mod_authz_svn.h subversion/include/mod_dav_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_repos_private.h subversion/include/private/svn_skel.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/repos.c ; else echo "fake" > subversion/mod_dav_svn/repos.lo ; fi
+
+subversion/mod_dav_svn/util.lo: subversion/mod_dav_svn/util.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/private/svn_skel.h subversion/include/private/svn_string_private.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/util.c ; else echo "fake" > subversion/mod_dav_svn/util.lo ; fi
+
+subversion/mod_dav_svn/version.lo: subversion/mod_dav_svn/version.c subversion/include/mod_authz_svn.h subversion/include/private/svn_dav_protocol.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_repos_private.h subversion/include/private/svn_skel.h subversion/include/private/svn_subr_private.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_dav.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/mod_dav_svn/dav_svn.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)subversion/mod_dav_svn/version.c ; else echo "fake" > subversion/mod_dav_svn/version.lo ; fi
+
+subversion/po/de.mo: subversion/po/de.po
+
+subversion/po/es.mo: subversion/po/es.po
+
+subversion/po/fr.mo: subversion/po/fr.po
+
+subversion/po/it.mo: subversion/po/it.po
+
+subversion/po/ja.mo: subversion/po/ja.po
+
+subversion/po/ko.mo: subversion/po/ko.po
+
+subversion/po/nb.mo: subversion/po/nb.po
+
+subversion/po/pl.mo: subversion/po/pl.po
+
+subversion/po/pt_BR.mo: subversion/po/pt_BR.po
+
+subversion/po/sv.mo: subversion/po/sv.po
+
+subversion/po/zh_CN.mo: subversion/po/zh_CN.po
+
+subversion/po/zh_TW.mo: subversion/po/zh_TW.po
+
+subversion/svn/add-cmd.lo: subversion/svn/add-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/blame-cmd.lo: subversion/svn/blame-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/cat-cmd.lo: subversion/svn/cat-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/changelist-cmd.lo: subversion/svn/changelist-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/checkout-cmd.lo: subversion/svn/checkout-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/cl-conflicts.lo: subversion/svn/cl-conflicts.c subversion/include/private/svn_debug.h subversion/include/private/svn_token.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl-conflicts.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/cleanup-cmd.lo: subversion/svn/cleanup-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/commit-cmd.lo: subversion/svn/commit-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/conflict-callbacks.lo: subversion/svn/conflict-callbacks.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn/cl-conflicts.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/copy-cmd.lo: subversion/svn/copy-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/delete-cmd.lo: subversion/svn/delete-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/deprecated.lo: subversion/svn/deprecated.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h
+
+subversion/svn/diff-cmd.lo: subversion/svn/diff-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/export-cmd.lo: subversion/svn/export-cmd.c subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/file-merge.lo: subversion/svn/file-merge.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_utf_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/help-cmd.lo: subversion/svn/help-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/import-cmd.lo: subversion/svn/import-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/info-cmd.lo: subversion/svn/info-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl-conflicts.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/list-cmd.lo: subversion/svn/list-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/lock-cmd.lo: subversion/svn/lock-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/log-cmd.lo: subversion/svn/log-cmd.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/merge-cmd.lo: subversion/svn/merge-cmd.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/mergeinfo-cmd.lo: subversion/svn/mergeinfo-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/mkdir-cmd.lo: subversion/svn/mkdir-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/move-cmd.lo: subversion/svn/move-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/notify.lo: subversion/svn/notify.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/patch-cmd.lo: subversion/svn/patch-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/propdel-cmd.lo: subversion/svn/propdel-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/propedit-cmd.lo: subversion/svn/propedit-cmd.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/propget-cmd.lo: subversion/svn/propget-cmd.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/proplist-cmd.lo: subversion/svn/proplist-cmd.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/props.lo: subversion/svn/props.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/propset-cmd.lo: subversion/svn/propset-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/relocate-cmd.lo: subversion/svn/relocate-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/resolve-cmd.lo: subversion/svn/resolve-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/resolved-cmd.lo: subversion/svn/resolved-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/revert-cmd.lo: subversion/svn/revert-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/status-cmd.lo: subversion/svn/status-cmd.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/status.lo: subversion/svn/status.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl-conflicts.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/svn.lo: subversion/svn/svn.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/switch-cmd.lo: subversion/svn/switch-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/unlock-cmd.lo: subversion/svn/unlock-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/update-cmd.lo: subversion/svn/update-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/upgrade-cmd.lo: subversion/svn/upgrade-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svn/util.lo: subversion/svn/util.c subversion/include/private/svn_client_private.h subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_string_private.h subversion/include/private/svn_token.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn/cl.h subversion/svn_private_config.h
+
+subversion/svnadmin/svnadmin.lo: subversion/svnadmin/svnadmin.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/svn_private_config.h
+
+subversion/svndumpfilter/svndumpfilter.lo: subversion/svndumpfilter/svndumpfilter.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_mergeinfo_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/svn_private_config.h
+
+subversion/svnlook/svnlook.lo: subversion/svnlook/svnlook.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_private.h subversion/include/private/svn_fspath.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_xml.h subversion/svn_private_config.h
+
+subversion/svnmucc/svnmucc.lo: subversion/svnmucc/svnmucc.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h
+
+subversion/svnrdump/dump_editor.lo: subversion/svnrdump/dump_editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_subr_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/svnrdump/svnrdump.h
+
+subversion/svnrdump/load_editor.lo: subversion/svnrdump/load_editor.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_ra_private.h subversion/include/private/svn_repos_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/svnrdump/svnrdump.h
+
+subversion/svnrdump/svnrdump.lo: subversion/svnrdump/svnrdump.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/svn_private_config.h subversion/svnrdump/svnrdump.h
+
+subversion/svnrdump/util.lo: subversion/svnrdump/util.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/svnrdump/svnrdump.h
+
+subversion/svnserve/cyrus_auth.lo: subversion/svnserve/cyrus_auth.c subversion/include/private/ra_svn_sasl.h subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra_svn.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/svnserve/server.h
+
+subversion/svnserve/log-escape.lo: subversion/svnserve/log-escape.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_ra_svn.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svnserve/server.h
+
+subversion/svnserve/serve.lo: subversion/svnserve/serve.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fspath.h subversion/include/private/svn_log.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_ra_svn_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_ra_svn.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_user.h subversion/svn_private_config.h subversion/svnserve/server.h
+
+subversion/svnserve/svnserve.lo: subversion/svnserve/svnserve.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_auth.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra_svn.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/svn_private_config.h subversion/svnserve/server.h subversion/svnserve/winservice.h
+
+subversion/svnserve/winservice.lo: subversion/svnserve/winservice.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/svnserve/winservice.h
+
+subversion/svnsync/svnsync.lo: subversion/svnsync/svnsync.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_ra_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/svn_private_config.h subversion/svnsync/sync.h
+
+subversion/svnsync/sync.lo: subversion/svnsync/sync.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/svn_private_config.h subversion/svnsync/sync.h
+
+subversion/svnversion/svnversion.lo: subversion/svnversion/svnversion.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h
+
+subversion/tests/cmdline/atomic-ra-revprop-change.lo: subversion/tests/cmdline/atomic-ra-revprop-change.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h
+
+subversion/tests/cmdline/entries-dump.lo: subversion/tests/cmdline/entries-dump.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/lock.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/tests/libsvn_client/client-test.lo: subversion/tests/libsvn_client/client-test.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_editor.h subversion/include/private/svn_magic.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_client/client.h subversion/libsvn_client/mergeinfo.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_delta/random-test.lo: subversion/tests/libsvn_delta/random-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/compose_delta.c subversion/libsvn_delta/delta.h subversion/tests/libsvn_delta/delta-window-test.h subversion/tests/libsvn_delta/range-index-test.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_delta/svndiff-test.lo: subversion/tests/libsvn_delta/svndiff-test.c subversion/include/private/svn_debug.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_quoprint.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_delta/vdelta-test.lo: subversion/tests/libsvn_delta/vdelta-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h subversion/tests/libsvn_delta/delta-window-test.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_delta/window-test.lo: subversion/tests/libsvn_delta/window-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_diff/diff-diff3-test.lo: subversion/tests/libsvn_diff/diff-diff3-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_diff/parse-diff-test.lo: subversion/tests/libsvn_diff/parse-diff-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_fs/fs-test.lo: subversion/tests/libsvn_fs/fs-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/libsvn_delta/delta.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_fs/locks-test.lo: subversion/tests/libsvn_fs/locks-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_fs_base/changes-test.lo: subversion/tests/libsvn_fs_base/changes-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/changes-table.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_fs_base/fs-base-test.lo: subversion/tests/libsvn_fs_base/fs-base-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_fs_util.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/libsvn_delta/delta.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/nodes-table.h subversion/libsvn_fs_base/bdb/txn-table.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/id.h subversion/libsvn_fs_base/key-gen.h subversion/libsvn_fs_base/trail.h subversion/svn_private_config.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_fs_base/strings-reps-test.lo: subversion/tests/libsvn_fs_base/strings-reps-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs/fs-loader.h subversion/libsvn_fs_base/bdb/bdb_compat.h subversion/libsvn_fs_base/bdb/env.h subversion/libsvn_fs_base/bdb/reps-table.h subversion/libsvn_fs_base/bdb/strings-table.h subversion/libsvn_fs_base/fs.h subversion/libsvn_fs_base/trail.h subversion/libsvn_fs_base/util/fs_skels.h subversion/svn_private_config.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_fs_fs/fs-pack-test.lo: subversion/tests/libsvn_fs_fs/fs-pack-test.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_string_private.h subversion/include/private/svn_token.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_fs_fs/fs.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_ra/ra-test.lo: subversion/tests/libsvn_ra/ra-test.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_ra_local/ra_local.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_ra_local/ra-local-test.lo: subversion/tests/libsvn_ra_local/ra-local-test.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_ra_local/ra_local.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_repos/dir-delta-editor.lo: subversion/tests/libsvn_repos/dir-delta-editor.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/libsvn_repos/dir-delta-editor.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_repos/repos-test.lo: subversion/tests/libsvn_repos/repos-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/tests/libsvn_repos/dir-delta-editor.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_subr/auth-test.lo: subversion/tests/libsvn_subr/auth-test.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/cache-test.lo: subversion/tests/libsvn_subr/cache-test.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/checksum-test.lo: subversion/tests/libsvn_subr/checksum-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_pseudo_md5.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/compat-test.lo: subversion/tests/libsvn_subr/compat-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/svn_private_config.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/config-test.lo: subversion/tests/libsvn_subr/config-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/crypto-test.lo: subversion/tests/libsvn_subr/crypto-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_subr/crypto.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/dirent_uri-test.lo: subversion/tests/libsvn_subr/dirent_uri-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/error-code-test.lo: subversion/tests/libsvn_subr/error-code-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/error-test.lo: subversion/tests/libsvn_subr/error-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_error_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/hashdump-test.lo: subversion/tests/libsvn_subr/hashdump-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/io-test.lo: subversion/tests/libsvn_subr/io-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_subr/mergeinfo-test.lo: subversion/tests/libsvn_subr/mergeinfo-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_mergeinfo_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/named_atomic-test-proc.lo: subversion/tests/libsvn_subr/named_atomic-test-proc.c subversion/include/private/svn_debug.h subversion/include/private/svn_named_atomic.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/libsvn_subr/named_atomic-test-common.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/named_atomic-test.lo: subversion/tests/libsvn_subr/named_atomic-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_named_atomic.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/libsvn_subr/named_atomic-test-common.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/opt-test.lo: subversion/tests/libsvn_subr/opt-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/path-test.lo: subversion/tests/libsvn_subr/path-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/revision-test.lo: subversion/tests/libsvn_subr/revision-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/skel-test.lo: subversion/tests/libsvn_subr/skel-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_skel.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_subr/spillbuf-test.lo: subversion/tests/libsvn_subr/spillbuf-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/stream-test.lo: subversion/tests/libsvn_subr/stream-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_io_private.h subversion/include/svn_base64.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/string-test.lo: subversion/tests/libsvn_subr/string-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/subst_translate-test.lo: subversion/tests/libsvn_subr/subst_translate-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/time-test.lo: subversion/tests/libsvn_subr/time-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/translate-test.lo: subversion/tests/libsvn_subr/translate-test.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_subst.h subversion/include/svn_types.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_subr/utf-test.lo: subversion/tests/libsvn_subr/utf-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_utf_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_wc/conflict-data-test.lo: subversion/tests/libsvn_wc/conflict-data-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/tree_conflicts.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_wc/db-test.lo: subversion/tests/libsvn_wc/db-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_wc/entries-compat.lo: subversion/tests/libsvn_wc/entries-compat.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_wc/op-depth-test.lo: subversion/tests/libsvn_wc/op-depth-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/conflicts.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_wc/pristine-store-test.lo: subversion/tests/libsvn_wc/pristine-store-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/workqueue.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_wc/utils.lo: subversion/tests/libsvn_wc/utils.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc-queries.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/libsvn_wc/wc-incomplete-tester.lo: subversion/tests/libsvn_wc/wc-incomplete-tester.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/tests/libsvn_wc/wc-lock-tester.lo: subversion/tests/libsvn_wc/wc-lock-tester.c subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/svn_private_config.h
+
+subversion/tests/libsvn_wc/wc-queries-test.lo: subversion/tests/libsvn_wc/wc-queries-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_checksum.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/libsvn_wc/wc-queries.h subversion/svn_private_config.h subversion/tests/svn_test.h
+
+subversion/tests/libsvn_wc/wc-test.lo: subversion/tests/libsvn_wc/wc-test.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_skel.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/libsvn_wc/wc.h subversion/libsvn_wc/wc_db.h subversion/libsvn_wc/wc_db_private.h subversion/svn_private_config.h subversion/tests/libsvn_wc/utils.h subversion/tests/svn_test.h
+
+subversion/tests/svn_test_fs.lo: subversion/tests/svn_test_fs.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/tests/svn_test.h subversion/tests/svn_test_fs.h
+
+subversion/tests/svn_test_main.lo: subversion/tests/svn_test_main.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_ctype.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/svn_private_config.h subversion/tests/svn_test.h
+
+tools/client-side/svn-bench/help-cmd.lo: tools/client-side/svn-bench/help-cmd.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h
+
+tools/client-side/svn-bench/notify.lo: tools/client-side/svn-bench/notify.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h
+
+tools/client-side/svn-bench/null-export-cmd.lo: tools/client-side/svn-bench/null-export-cmd.c subversion/include/private/svn_client_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h
+
+tools/client-side/svn-bench/null-list-cmd.lo: tools/client-side/svn-bench/null-list-cmd.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_time.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_wc.h subversion/include/svn_xml.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h
+
+tools/client-side/svn-bench/null-log-cmd.lo: tools/client-side/svn-bench/null-log-cmd.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_compat.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_props.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h
+
+tools/client-side/svn-bench/svn-bench.lo: tools/client-side/svn-bench/svn-bench.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_opt_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h
+
+tools/client-side/svn-bench/util.lo: tools/client-side/svn-bench/util.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_client.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_wc.h subversion/svn_private_config.h tools/client-side/svn-bench/cl.h
+
+tools/dev/fsfs-access-map.lo: tools/dev/fsfs-access-map.c subversion/include/private/svn_debug.h subversion/include/private/svn_string_private.h subversion/include/svn_checksum.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+tools/dev/fsfs-reorg.lo: tools/dev/fsfs-reorg.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h
+
+tools/dev/svnraisetreeconflict/svnraisetreeconflict.lo: tools/dev/svnraisetreeconflict/svnraisetreeconflict.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_diff_tree.h subversion/include/private/svn_wc_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_ra.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/include/svn_wc.h subversion/svn_private_config.h
+
+tools/diff/diff.lo: tools/diff/diff.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h
+
+tools/diff/diff3.lo: tools/diff/diff3.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+tools/diff/diff4.lo: tools/diff/diff4.c subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_diff.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_pools.h subversion/include/svn_string.h subversion/include/svn_types.h
+
+tools/server-side/fsfs-stats.lo: tools/server-side/fsfs-stats.c subversion/include/private/svn_cache.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_string_private.h subversion/include/private/svn_subr_private.h subversion/include/svn_cache_config.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_diff.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_hash.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_pools.h subversion/include/svn_sorts.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h
+
+tools/server-side/mod_dontdothat/mod_dontdothat.lo: tools/server-side/mod_dontdothat/mod_dontdothat.c subversion/include/mod_dav_svn.h subversion/include/private/svn_debug.h subversion/include/svn_checksum.h subversion/include/svn_config.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_io.h subversion/include/svn_string.h subversion/include/svn_types.h
+ if $(INSTALL_APACHE_MODS) ; then $(COMPILE_APACHE_MOD) $(canonicalized_srcdir)tools/server-side/mod_dontdothat/mod_dontdothat.c ; else echo "fake" > tools/server-side/mod_dontdothat/mod_dontdothat.lo ; fi
+
+tools/server-side/svn-populate-node-origins-index.lo: tools/server-side/svn-populate-node-origins-index.c subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h
+
+tools/server-side/svn-rep-sharing-stats.lo: tools/server-side/svn-rep-sharing-stats.c subversion/include/private/svn_atomic.h subversion/include/private/svn_cache.h subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_editor.h subversion/include/private/svn_fs_private.h subversion/include/private/svn_mutex.h subversion/include/private/svn_named_atomic.h subversion/include/private/svn_sqlite.h subversion/include/private/svn_token.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_iter.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h subversion/include/svn_version.h subversion/libsvn_fs_fs/fs.h subversion/libsvn_fs_fs/fs_fs.h subversion/libsvn_fs_fs/id.h subversion/svn_private_config.h
+
+tools/server-side/svnauthz.lo: tools/server-side/svnauthz.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h
+
+tools/server-side/svnauthz.lo: tools/server-side/svnauthz.c subversion/include/private/svn_cmdline_private.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h subversion/include/svn_config.h subversion/include/svn_delta.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_fs.h subversion/include/svn_io.h subversion/include/svn_mergeinfo.h subversion/include/svn_opt.h subversion/include/svn_path.h subversion/include/svn_pools.h subversion/include/svn_repos.h subversion/include/svn_string.h subversion/include/svn_types.h subversion/include/svn_utf.h
+
diff --git a/contrib/subversion/build.conf b/contrib/subversion/build.conf
new file mode 100644
index 0000000..fd62014
--- /dev/null
+++ b/contrib/subversion/build.conf
@@ -0,0 +1,1377 @@
+#
+# build.conf -- configuration information for building Subversion
+#
+######################################################################
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+######################################################################
+#
+# This file is processed by gen-make.py, creating build-outputs.mk
+#
+
+# ----------------------------------------------------------------------------
+#
+# PREDEFINED SECTION
+#
+# This [options] section is global in scope, providing information to the
+# process, rather than defining a build target, as all other sections do.
+#
+
+[options]
+includes = subversion/include/*.h
+include-wildcards = *.h *.i *.swg
+private-includes =
+ subversion/include/private/*.h
+ subversion/bindings/swig/include/*.swg
+ subversion/libsvn_delta/compose_delta.c
+ subversion/bindings/cxxhl/include/*.hpp
+ subversion/bindings/cxxhl/include/svncxxhl/*.hpp
+private-built-includes =
+ subversion/svn_private_config.h
+ subversion/libsvn_fs_fs/rep-cache-db.h
+ subversion/libsvn_wc/wc-metadata.h
+ subversion/libsvn_wc/wc-queries.h
+ subversion/libsvn_wc/wc-checks.h
+ subversion/libsvn_subr/internal_statements.h
+ subversion/bindings/swig/proxy/swig_python_external_runtime.swg
+ subversion/bindings/swig/proxy/swig_perl_external_runtime.swg
+ subversion/bindings/swig/proxy/swig_ruby_external_runtime.swg
+ subversion/bindings/swig/proxy/rubyhead.swg
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_CommitItemStateFlags.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_NativeResources.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_Path.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNRepos.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_SVNClient.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Version.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LinkedLib.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LinkedLibIterator.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LoadedLib.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_types_VersionExtended_LoadedLibIterator.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_types_Revision.h
+ subversion/bindings/javahl/include/org_apache_subversion_javahl_callback_UserPasswordCallback.h
+
+
+test-scripts =
+ subversion/tests/cmdline/*_tests.py
+
+bdb-test-scripts =
+
+swig-python-opts = $(CPPFLAGS) -python -classic
+swig-perl-opts = $(CPPFLAGS) -perl -nopm -noproxy
+swig-ruby-opts = $(CPPFLAGS) -ruby
+swig-languages = python perl ruby
+swig-dirs =
+ subversion/bindings/swig/python
+ subversion/bindings/swig/perl
+ subversion/bindings/swig/ruby
+ subversion/bindings/swig/proxy
+
+swig-proxy-dir = subversion/bindings/swig/proxy
+swig-checkout-files = common.swg swigrun.swg runtime.swg
+ ruby/rubydef.swg ruby/rubyhead.swg ruby/rubytracking.swg
+ perl5/perlrun.swg python/pyrun.swg python/python.swg
+
+# ----------------------------------------------------------------------------
+#
+# BUILD TARGETS
+#
+# Target parameters:
+# description - optional build target description
+# type - the target type, defines how to build it
+# when - the name of an autoconf-substed variable that muset be
+# defined to either "true" or "false", that determines
+# whether this target should be built and installed.
+# path - relative path to target sources
+# sources - explicit list of target sources
+# install - the installation group/type
+# manpages - the man pages associated with this target
+# libs - libraries that this target depends on
+# nonlibs - dependencies that are not linked into the target
+# lang - bindings for language $(lang)
+# msvc-libs - additional libraries to link with on Windows
+# msvc-export - additional list of files to expose in dsp/vc(x)proj
+# msvc-static - visual studio target produces only a static lib
+# add-deps - expands to additional autoconf-defined dependencies
+# add-install-deps - like add-deps, but for the install step
+# external-lib - expands to additional autoconf-defined libs
+# external-project - visual studio project to depend on
+#
+
+# The subversion command-line client
+[svn]
+description = Subversion Client
+type = exe
+path = subversion/svn
+libs = libsvn_client libsvn_wc libsvn_ra libsvn_delta libsvn_diff libsvn_subr
+ apriconv apr
+manpages = subversion/svn/svn.1
+install = bin
+
+# The subversion repository administration tool
+[svnadmin]
+description = Subversion Repository Administrator
+type = exe
+path = subversion/svnadmin
+install = bin
+manpages = subversion/svnadmin/svnadmin.1
+libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr apriconv apr
+
+# The subversion repository dump filtering tool
+[svndumpfilter]
+description = Subversion Dumpfile Filter
+type = exe
+path = subversion/svndumpfilter
+install = bin
+manpages = subversion/svndumpfilter/svndumpfilter.1
+libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr apriconv apr
+
+# The subversion repository inspection tool
+[svnlook]
+description = Subversion Repository Browser
+type = exe
+path = subversion/svnlook
+install = bin
+manpages = subversion/svnlook/svnlook.1
+libs = libsvn_repos libsvn_fs libsvn_delta libsvn_diff libsvn_subr apriconv apr
+
+[svnserve]
+description = Subversion Server
+type = exe
+path = subversion/svnserve
+install = bin
+manpages = subversion/svnserve/svnserve.8 subversion/svnserve/svnserve.conf.5
+libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr libsvn_ra_svn
+ apriconv apr sasl
+msvc-libs = advapi32.lib ws2_32.lib
+
+[svnsync]
+description = Subversion repository replicator
+type = exe
+path = subversion/svnsync
+libs = libsvn_ra libsvn_delta libsvn_subr apr
+install = bin
+manpages = subversion/svnsync/svnsync.1
+
+[svnversion]
+description = Subversion Revision Extractor
+type = exe
+path = subversion/svnversion
+libs = libsvn_wc libsvn_subr apriconv apr
+install = bin
+manpages = subversion/svnversion/svnversion.1
+
+[svnrdump]
+description = Subversion remote repository dumper and loader
+type = exe
+path = subversion/svnrdump
+libs = libsvn_client libsvn_ra libsvn_repos libsvn_delta libsvn_subr aprutil apr
+install = bin
+manpages = subversion/svnrdump/svnrdump.1
+
+[svnmucc]
+description = Subversion Multiple URL Command Client
+type = exe
+path = subversion/svnmucc
+libs = libsvn_client libsvn_ra libsvn_subr libsvn_delta apriconv apr
+install = bin
+manpages = subversion/svnmucc/svnmucc.1
+
+# Support for GNOME Keyring
+[libsvn_auth_gnome_keyring]
+description = Subversion GNOME Keyring Library
+type = lib
+install = gnome-keyring-lib
+path = subversion/libsvn_auth_gnome_keyring
+libs = libsvn_subr apr gnome-keyring
+
+# Support for KWallet
+[libsvn_auth_kwallet]
+description = Subversion KWallet Library
+type = lib
+install = kwallet-lib
+path = subversion/libsvn_auth_kwallet
+libs = libsvn_subr apr kwallet
+link-cmd = $(LINK_CXX_LIB)
+
+# Library needed by all subversion clients
+[libsvn_client]
+description = Subversion Client Library
+type = lib
+path = subversion/libsvn_client
+libs = libsvn_wc libsvn_ra libsvn_delta libsvn_diff libsvn_subr apriconv apr
+install = lib
+msvc-export = svn_client.h private/svn_client_private.h
+
+# Routines for binary diffing and tree-deltas
+[libsvn_delta]
+description = Subversion Delta Library
+type = lib
+install = fsmod-lib
+path = subversion/libsvn_delta
+libs = libsvn_subr aprutil apriconv apr zlib
+msvc-export = svn_delta.h private/svn_editor.h private/svn_delta_private.h
+
+# Routines for diffing
+[libsvn_diff]
+description = Subversion Diff Library
+type = lib
+path = subversion/libsvn_diff
+libs = libsvn_subr apriconv apr zlib
+install = lib
+msvc-export = svn_diff.h private/svn_diff_private.h private/svn_diff_tree.h
+
+# The repository filesystem library
+[libsvn_fs]
+description = Subversion Repository Filesystem Library
+type = lib
+path = subversion/libsvn_fs
+install = ramod-lib
+libs = libsvn_fs_util libsvn_delta libsvn_subr fs-libs aprutil apr
+# conditionally add more dependencies
+add-deps = $(SVN_FS_LIB_DEPS)
+add-install-deps = $(SVN_FS_LIB_INSTALL_DEPS)
+msvc-export = svn_fs.h private/svn_fs_private.h
+
+[libsvn_fs_base]
+type = fs-module
+path = subversion/libsvn_fs_base
+sources = *.c bdb/*.c util/*.c
+install = bdb-lib
+libs = libsvn_delta libsvn_subr aprutil apriconv apr bdb libsvn_fs_util
+msvc-static = yes
+
+[libsvn_fs_fs]
+type = fs-module
+path = subversion/libsvn_fs_fs
+install = fsmod-lib
+libs = libsvn_delta libsvn_subr aprutil apriconv apr libsvn_fs_util
+msvc-static = yes
+
+# Low-level grab bag of utilities
+[libsvn_fs_util]
+type = lib
+install = fsmod-lib
+path = subversion/libsvn_fs_util
+libs = libsvn_subr aprutil apriconv apr
+msvc-libs = advapi32.lib shfolder.lib
+msvc-static = yes
+
+# General API for accessing repositories
+[libsvn_ra]
+description = Subversion Repository Access Library
+type = lib
+path = subversion/libsvn_ra
+libs = libsvn_delta libsvn_subr ra-libs apriconv apr
+# conditionally add more dependencies
+add-deps = $(SVN_RA_LIB_DEPS)
+add-install-deps = $(SVN_RA_LIB_INSTALL_DEPS)
+install = lib
+msvc-export = svn_ra.h private\svn_ra_private.h
+
+# Accessing repositories via DAV through serf
+[libsvn_ra_serf]
+type = ra-module
+path = subversion/libsvn_ra_serf
+install = serf-lib
+libs = libsvn_delta libsvn_subr aprutil apriconv apr serf xml
+msvc-libs = secur32.lib
+msvc-static = yes
+
+# Accessing repositories via SVN
+[libsvn_ra_svn]
+type = ra-module
+path = subversion/libsvn_ra_svn
+install = ramod-lib
+libs = libsvn_delta libsvn_subr aprutil apriconv apr sasl
+msvc-static = yes
+
+# Accessing repositories via direct libsvn_fs
+[libsvn_ra_local]
+type = ra-module
+path = subversion/libsvn_ra_local
+install = ramod-lib
+libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr apriconv apr
+msvc-static = yes
+
+# Routines built on top of libsvn_fs
+[libsvn_repos]
+description = Subversion Repository Library
+type = lib
+path = subversion/libsvn_repos
+install = ramod-lib
+libs = libsvn_fs libsvn_delta libsvn_subr apriconv apr
+msvc-export = svn_repos.h private/svn_repos_private.h
+
+# Low-level grab bag of utilities
+[libsvn_subr]
+description = Subversion General Utility Library
+type = lib
+install = fsmod-lib
+path = subversion/libsvn_subr
+libs = aprutil apriconv apr xml zlib apr_memcache sqlite magic
+msvc-libs = kernel32.lib advapi32.lib shfolder.lib ole32.lib
+ crypt32.lib version.lib psapi.lib
+msvc-export =
+ svn_auth.h svn_base64.h svn_cache_config.h svn_checksum.h svn_cmdline.h
+ svn_compat.h svn_config.h svn_ctype.h svn_dirent_uri.h svn_dso.h
+ svn_error.h svn_hash.h svn_io.h svn_iter.h svn_md5.h svn_mergeinfo.h
+ svn_nls.h svn_opt.h svn_path.h svn_pools.h svn_props.h svn_quoprint.h
+ svn_sorts.h svn_string.h svn_subst.h svn_time.h svn_types.h svn_user.h
+ svn_utf.h svn_version.h svn_xml.h
+ private\svn_atomic.h private\svn_cache.h private\svn_cmdline_private.h
+ private\svn_debug.h private\svn_error_private.h private\svn_fspath.h
+ private\svn_log.h private\svn_mergeinfo_private.h
+ private\svn_opt_private.h private\svn_skel.h private\svn_sqlite.h
+ private\svn_utf_private.h private\svn_eol_private.h
+ private\svn_token.h private\svn_adler32.h
+ private\svn_temp_serializer.h private\svn_io_private.h
+ private\svn_string_private.h private\svn_magic.h
+ private\svn_subr_private.h private\svn_mutex.h private\svn_named_atomic.h
+
+# Working copy management lib
+[libsvn_wc]
+description = Subversion Working Copy Library
+type = lib
+path = subversion/libsvn_wc
+libs = libsvn_delta libsvn_diff libsvn_subr aprutil apriconv apr
+install = lib
+msvc-export = svn_wc.h private\svn_wc_private.h
+
+# Subversion plugin for Apache's mod_dav
+[mod_dav_svn]
+description = Subversion plug-in for the Apache DAV module
+when = INSTALL_APACHE_MODS
+type = apache-mod
+path = subversion/mod_dav_svn
+sources = *.c reports/*.c posts/*.c
+libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr
+nonlibs = apr aprutil
+install = apache-mod
+msvc-libs = mod_dav.lib libhttpd.lib
+
+[mod_authz_svn]
+description = Subversion path-based authorization module for Apache
+when = INSTALL_APACHE_MODS
+type = apache-mod
+path = subversion/mod_authz_svn
+nonlibs = mod_dav_svn apr aprutil
+libs = libsvn_repos libsvn_subr
+install = apache-mod
+msvc-libs = libhttpd.lib
+
+[mod_dontdothat]
+description = Apache Httpd module to block certain kinds of Apache Subversion requests
+when = INSTALL_APACHE_MODS
+type = apache-mod
+path = tools/server-side/mod_dontdothat
+nonlibs = mod_dav_svn apr aprutil
+libs = libsvn_subr xml
+install = tools
+msvc-libs = libhttpd.lib
+
+# ----------------------------------------------------------------------------
+#
+# CONSTRUCTED HEADERS
+#
+
+[rep_cache]
+description = Schema for the rep-sharing feature
+type = sql-header
+path = subversion/libsvn_fs_fs
+sources = rep-cache-db.sql
+
+[wc_queries]
+desription = Queries on the WC database
+type = sql-header
+path = subversion/libsvn_wc
+sources = wc-queries.sql
+
+[subr_sqlite]
+description = Internal statements for SQLite interface
+type = sql-header
+path = subversion/libsvn_subr
+sources = internal_statements.sql
+
+
+# ----------------------------------------------------------------------------
+#
+# TARGETS FOR I18N SUPPORT
+#
+[locale]
+type = i18n
+path = subversion/po
+install = locale
+external-project = svn_locale
+
+# ----------------------------------------------------------------------------
+#
+# TARGETS FOR SWIG SUPPORT
+#
+
+[swig_core]
+type = swig
+path = subversion/bindings/swig
+sources = core.i
+libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby
+ libsvn_diff libsvn_subr apr
+description = Subversion core library bindings
+include-runtime = yes
+
+[swig_client]
+type = swig
+path = subversion/bindings/swig
+sources = svn_client.i
+libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby
+ libsvn_client libsvn_subr apr
+nonlibs = swig_core
+description = Subversion client library bindings
+
+[swig_delta]
+type = swig
+path = subversion/bindings/swig
+sources = svn_delta.i
+libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby
+ libsvn_delta libsvn_subr apr
+nonlibs = swig_core
+description = Subversion delta library bindings
+
+[swig_diff]
+type = swig
+path = subversion/bindings/swig
+sources = svn_diff.i
+libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby
+ libsvn_diff libsvn_subr apr
+nonlibs = swig_core
+description = Subversion diff library bindings
+
+[swig_fs]
+type = swig
+path = subversion/bindings/swig
+sources = svn_fs.i
+libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby
+ libsvn_fs libsvn_subr apr
+nonlibs = swig_core
+description = Subversion FS library bindings
+
+[swig_ra]
+type = swig
+path = subversion/bindings/swig
+sources = svn_ra.i
+libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby
+ libsvn_ra libsvn_subr apr
+nonlibs = swig_core
+description = Subversion RA library bindings
+
+[swig_repos]
+type = swig
+path = subversion/bindings/swig
+sources = svn_repos.i
+libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby
+ libsvn_repos libsvn_subr apr
+nonlibs = swig_core
+description = Subversion repository library bindings
+
+[swig_wc]
+type = swig
+path = subversion/bindings/swig
+sources = svn_wc.i
+libs = libsvn_swig_py libsvn_swig_perl libsvn_swig_ruby
+ libsvn_wc libsvn_subr apr
+nonlibs = swig_core
+description = Subversion WC library bindings
+
+# SWIG utility library for Python modules
+[libsvn_swig_py]
+type = swig_lib
+lang = python
+path = subversion/bindings/swig/python/libsvn_swig_py
+libs = libsvn_client libsvn_wc libsvn_ra libsvn_delta libsvn_subr apriconv apr
+link-cmd = $(LINK)
+install = swig-py-lib
+# need special build rule to include -DSWIGPYTHON
+compile-cmd = $(COMPILE_SWIG_PY)
+msvc-static = no
+
+# SWIG utility library for Perl modules
+[libsvn_swig_perl]
+type = swig_lib
+lang = perl
+path = subversion/bindings/swig/perl/libsvn_swig_perl
+libs = libsvn_delta libsvn_subr apriconv apr
+install = swig-pl-lib
+# need special build rule to include
+compile-cmd = $(COMPILE_SWIG_PL)
+msvc-static = yes
+
+# SWIG utility library for Ruby modules
+[libsvn_swig_ruby]
+type = swig_lib
+lang = ruby
+path = subversion/bindings/swig/ruby/libsvn_swig_ruby
+libs = libsvn_client libsvn_wc libsvn_delta libsvn_subr apriconv apr
+link-cmd = $(LINK) $(SWIG_RB_LIBS)
+install = swig-rb-lib
+# need special build rule to include
+compile-cmd = $(COMPILE_SWIG_RB)
+msvc-static = no
+
+# ----------------------------------------------------------------------------
+#
+# JavaHL targets
+#
+[javahl-java]
+type = java
+path = subversion/bindings/javahl/src/org/apache/subversion/javahl
+ subversion/bindings/javahl/src/org/apache/subversion/javahl/callback
+ subversion/bindings/javahl/src/org/apache/subversion/javahl/types
+src-root = subversion/bindings/javahl/src
+sources = *.java
+install = javahl-java
+link-cmd = $(COMPILE_JAVAHL_JAVAC)
+classes = subversion/bindings/javahl/classes
+package-roots = org
+
+[javahl-compat-java]
+type = java
+path = subversion/bindings/javahl/src/org/tigris/subversion/javahl
+sources = *.java
+install = javahl-java
+link-cmd = $(COMPILE_JAVAHL_JAVAC)
+classes = subversion/bindings/javahl/classes
+add-deps = $(javahl_java_DEPS)
+### Replace JAR call in INSTALL_EXTRA_JAVAHL_JAVA macro Makefile.in.
+#jar = svn-javahl.jar
+package-roots = org
+
+[javahl-tests]
+type = java
+path = subversion/bindings/javahl/tests/org/apache/subversion/javahl
+sources = *.java
+install = javahl-java
+link-cmd = $(COMPILE_JAVAHL_JAVAC)
+classes = subversion/bindings/javahl/classes
+package-roots = org
+### Java targets don't do up-to-date checks yet.
+#add-deps = javahl-java
+add-deps = $(javahl_java_DEPS)
+
+[javahl-compat-tests]
+type = java
+path = subversion/bindings/javahl/tests/org/tigris/subversion/javahl
+sources = *.java
+install = javahl-java
+link-cmd = $(COMPILE_JAVAHL_JAVAC)
+classes = subversion/bindings/javahl/classes
+package-roots = org
+### Java targets don't do up-to-date checks yet.
+#add-deps = javahl-compat-java
+add-deps = $(javahl_compat_java_DEPS)
+
+[javahl-types-javah]
+type = javah
+path = subversion/bindings/javahl/src/org/apache/subversion/javahl/types
+classes = subversion/bindings/javahl/classes
+headers = subversion/bindings/javahl/include
+package = org.apache.subversion.javahl.types
+sources = *.java
+add-deps = $(javahl_java_DEPS)
+install = javahl-javah
+link-cmd = $(COMPILE_JAVAHL_JAVAH) -force
+
+[javahl-callback-javah]
+type = javah
+path = subversion/bindings/javahl/src/org/apache/subversion/javahl/callback
+classes = subversion/bindings/javahl/classes
+headers = subversion/bindings/javahl/include
+package = org.apache.subversion.javahl.callback
+sources = *.java
+add-deps = $(javahl_java_DEPS)
+install = javahl-javah
+link-cmd = $(COMPILE_JAVAHL_JAVAH) -force
+
+[javahl-javah]
+type = javah
+path = subversion/bindings/javahl/src/org/apache/subversion/javahl
+classes = subversion/bindings/javahl/classes
+headers = subversion/bindings/javahl/include
+package = org.apache.subversion.javahl
+sources = *.java
+add-deps = $(javahl_java_DEPS)
+install = javahl-javah
+link-cmd = $(COMPILE_JAVAHL_JAVAH) -force
+
+[libsvnjavahl]
+description = Subversion Java HighLevel binding
+type = lib
+path = subversion/bindings/javahl/native
+libs = libsvn_repos libsvn_client libsvn_wc libsvn_ra libsvn_delta libsvn_diff
+ libsvn_subr libsvn_fs aprutil apriconv apr
+sources = *.cpp *.c
+add-deps = $(javahl_javah_DEPS) $(javahl_java_DEPS) $(javahl_callback_javah_DEPS) $(javahl_types_javah_DEPS)
+install = javahl-lib
+# need special build rule to include -I$(JDK)/include/jni.h
+compile-cmd = $(COMPILE_JAVAHL_CXX)
+link-cmd = $(LINK_JAVAHL_CXX)
+
+# ----------------------------------------------------------------------------
+#
+# C++HL targets
+#
+
+[libsvncxxhl]
+description = Subversion C++ HighLevel bindings
+type = lib
+path = subversion/bindings/cxxhl
+libs = libsvn_repos libsvn_client libsvn_wc libsvn_ra libsvn_delta libsvn_diff
+ libsvn_subr libsvn_fs aprutil apriconv apr
+sources = src/*.cpp
+install = cxxhl-lib
+msvc-static = yes
+compile-cmd = $(COMPILE_CXXHL_CXX)
+link-cmd = $(LINK_CXX_LIB)
+
+[cxxhl-tests]
+description = Unit tests for Subversion C++ HighLevel bindings
+type = exe
+path = subversion/bindings/cxxhl
+libs = libsvncxxhl libsvn_subr
+sources = tests/*.cpp
+install = tests
+compile-cmd = $(COMPILE_CXXHL_CXX)
+link-cmd = $(LINK_CXX)
+
+# ----------------------------------------------------------------------------
+#
+# TESTING TARGETS
+#
+
+# general library: our C testing framework
+[libsvn_test]
+type = lib
+path = subversion/tests
+install = test
+libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr aprutil apriconv apr
+msvc-static = yes
+undefined-lib-symbols = yes
+
+# ----------------------------------------------------------------------------
+# Tests for libsvn_fs_base
+
+[fs-base-test]
+description = Tests for *public* fs API (svn_fs.h)
+type = exe
+path = subversion/tests/libsvn_fs_base
+sources = fs-base-test.c
+install = bdb-test
+libs = libsvn_test libsvn_fs libsvn_fs_base libsvn_delta
+ libsvn_fs_util libsvn_subr apriconv apr
+
+[strings-reps-test]
+description = Test strings/reps in libsvn_fs_base
+type = exe
+path = subversion/tests/libsvn_fs_base
+sources = strings-reps-test.c
+install = bdb-test
+libs = libsvn_test libsvn_fs libsvn_fs_base libsvn_delta
+ libsvn_subr apriconv apr
+
+[changes-test]
+description = Test changes in libsvn_fs_base
+type = exe
+path = subversion/tests/libsvn_fs_base
+sources = changes-test.c
+install = bdb-test
+libs = libsvn_test libsvn_fs libsvn_fs_base libsvn_delta
+ libsvn_subr apriconv apr
+
+# ----------------------------------------------------------------------------
+# Tests for libsvn_fs_fs
+[fs-pack-test]
+description = Test fsfs packing in libsvn_fs_fs
+type = exe
+path = subversion/tests/libsvn_fs_fs
+sources = fs-pack-test.c
+install = test
+libs = libsvn_test libsvn_fs libsvn_fs_fs libsvn_delta
+ libsvn_subr apriconv apr
+
+# ----------------------------------------------------------------------------
+# Tests for libsvn_fs
+
+[locks-test]
+description = Test locks in libsvn_fs
+type = exe
+path = subversion/tests/libsvn_fs
+sources = locks-test.c
+install = test
+libs = libsvn_test libsvn_fs libsvn_delta libsvn_subr apriconv apr
+
+[fs-test]
+description = Test locks in libsvn_fs
+type = exe
+path = subversion/tests/libsvn_fs
+sources = fs-test.c
+install = test
+libs = libsvn_test libsvn_fs libsvn_delta
+ libsvn_subr aprutil apriconv apr
+
+# ----------------------------------------------------------------------------
+# Tests for libsvn_repos
+
+[repos-test]
+description = Test delta editor in libsvn_repos
+type = exe
+path = subversion/tests/libsvn_repos
+sources = repos-test.c dir-delta-editor.c
+install = test
+libs = libsvn_test libsvn_repos libsvn_fs libsvn_delta libsvn_subr apriconv apr
+
+# ----------------------------------------------------------------------------
+# Tests for libsvn_subr
+
+[auth-test]
+description = Test platform-specific auth provider access
+type = exe
+path = subversion/tests/libsvn_subr
+sources = auth-test.c
+install = test
+libs = libsvn_test libsvn_subr apr
+
+[cache-test]
+description = Test in-memory cache
+type = exe
+path = subversion/tests/libsvn_subr
+sources = cache-test.c
+install = test
+libs = libsvn_test libsvn_subr apr
+
+[checksum-test]
+description = Test checksum functions
+type = exe
+path = subversion/tests/libsvn_subr
+sources = checksum-test.c
+install = test
+libs = libsvn_test libsvn_subr apr
+
+[compat-test]
+description = Test compatibility functions
+type = exe
+path = subversion/tests/libsvn_subr
+sources = compat-test.c
+install = test
+libs = libsvn_test libsvn_subr apr
+
+[config-test]
+description = Test svn_config utilities
+type = exe
+path = subversion/tests/libsvn_subr
+sources = config-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[crypto-test]
+description = Test svn_crypto utilities
+type = exe
+path = subversion/tests/libsvn_subr
+sources = crypto-test.c
+install = test
+libs = libsvn_test libsvn_subr aprutil apr
+
+[dirent_uri-test]
+description = Test dirent_uri library
+type = exe
+path = subversion/tests/libsvn_subr
+sources = dirent_uri-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[error-test]
+description = Test error library
+type = exe
+path = subversion/tests/libsvn_subr
+sources = error-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[error-code-test]
+description = Test error library
+type = exe
+path = subversion/tests/libsvn_subr
+sources = error-code-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[hashdump-test]
+description = Test hashfile format for props
+type = exe
+path = subversion/tests/libsvn_subr
+sources = hashdump-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[io-test]
+description = Test I/O Operations
+type = exe
+path = subversion/tests/libsvn_subr
+sources = io-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[opt-test]
+description = Test options library
+type = exe
+path = subversion/tests/libsvn_subr
+sources = opt-test.c
+install = test
+libs = libsvn_test libsvn_subr apr
+
+[mergeinfo-test]
+description = Test mergeinfo library
+type = exe
+path = subversion/tests/libsvn_subr
+sources = mergeinfo-test.c
+install = test
+libs = libsvn_test libsvn_subr apr
+
+[named_atomic-test]
+description = Test named atomics
+type = exe
+path = subversion/tests/libsvn_subr
+sources = named_atomic-test.c
+install = test
+libs = libsvn_test libsvn_subr apr
+
+[named_atomic-proc-test]
+description = Sub-process for named atomics
+type = exe
+path = subversion/tests/libsvn_subr
+sources = named_atomic-test-proc.c
+install = sub-test
+libs = libsvn_subr apr
+
+[path-test]
+description = Test path library
+type = exe
+path = subversion/tests/libsvn_subr
+sources = path-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[revision-test]
+description = Test revision library
+type = exe
+path = subversion/tests/libsvn_subr
+sources = revision-test.c
+install = test
+libs = libsvn_test libsvn_subr apr
+
+[skel-test]
+description = Test skels in libsvn_subr
+type = exe
+path = subversion/tests/libsvn_subr
+sources = skel-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[spillbuf-test]
+description = Test spillbuf in libsvn_subr
+type = exe
+path = subversion/tests/libsvn_subr
+sources = spillbuf-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[stream-test]
+description = Test stream library
+type = exe
+path = subversion/tests/libsvn_subr
+sources = stream-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[string-test]
+description = Test svn_stringbuf_t utilities
+type = exe
+path = subversion/tests/libsvn_subr
+sources = string-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[time-test]
+description = Test time functions
+type = exe
+path = subversion/tests/libsvn_subr
+sources = time-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[utf-test]
+description = Test UTF-8 functions
+type = exe
+path = subversion/tests/libsvn_subr
+sources = utf-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[subst_translate-test]
+description = Test the svn_subst_translate* functions
+type = exe
+path = subversion/tests/libsvn_subr
+sources = subst_translate-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+[translate-test]
+description = Test eol conversion and keyword substitution routines
+type = exe
+path = subversion/tests/libsvn_subr
+sources = translate-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr
+
+# ----------------------------------------------------------------------------
+# Tests for libsvn_delta
+
+[random-test]
+description = Use random data to test delta processing
+type = exe
+path = subversion/tests/libsvn_delta
+sources = random-test.c
+install = test
+libs = libsvn_test libsvn_delta libsvn_subr apriconv apr
+
+[window-test]
+description = Test delta window generation
+type = exe
+path = subversion/tests/libsvn_delta
+sources = window-test.c
+install = test
+libs = libsvn_test libsvn_delta libsvn_subr apriconv apr
+
+# ----------------------------------------------------------------------------
+# Tests for libsvn_client
+
+[client-test]
+description = Test low-level functionality in libsvn_client
+type = exe
+path = subversion/tests/libsvn_client
+sources = client-test.c
+install = test
+libs = libsvn_test libsvn_client libsvn_wc libsvn_repos libsvn_ra libsvn_fs libsvn_delta libsvn_subr apriconv apr
+
+# ----------------------------------------------------------------------------
+# Tests for libsvn_diff
+
+[diff-diff3-test]
+description = Test the diff/diff3 library
+type = exe
+path = subversion/tests/libsvn_diff
+sources = diff-diff3-test.c
+install = test
+libs = libsvn_test libsvn_diff libsvn_subr apriconv apr
+
+[parse-diff-test]
+description = Test unidiff parsing
+type = exe
+path = subversion/tests/libsvn_diff
+sources = parse-diff-test.c
+install = test
+libs = libsvn_test libsvn_diff libsvn_subr apriconv apr
+
+# ----------------------------------------------------------------------------
+# Tests for libsvn_ra
+
+[ra-test]
+description = Test a few things in libsvn_ra
+type = exe
+path = subversion/tests/libsvn_ra
+sources = ra-test.c
+install = test
+libs = libsvn_test libsvn_ra libsvn_fs libsvn_delta libsvn_subr
+ apriconv apr
+
+# ----------------------------------------------------------------------------
+# Tests for libsvn_ra_local
+
+[ra-local-test]
+description = Test a few things in libsvn_ra_local
+type = exe
+path = subversion/tests/libsvn_ra_local
+sources = ra-local-test.c
+install = test
+libs = libsvn_test libsvn_ra_local libsvn_ra libsvn_fs libsvn_delta libsvn_subr
+ apriconv apr
+
+# ----------------------------------------------------------------------------
+# Tests for libsvn_wc
+
+[conflict-data-test]
+description = Test the storage of tree conflict data
+type = exe
+path = subversion/tests/libsvn_wc
+sources = conflict-data-test.c utils.c
+install = test
+libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr
+
+[db-test]
+description = Test the wc-ng database subsystem
+type = exe
+path = subversion/tests/libsvn_wc
+sources = db-test.c utils.c
+install = test
+libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr
+
+[pristine-store-test]
+description = Test the wc-ng pristine text storage subsystem
+type = exe
+path = subversion/tests/libsvn_wc
+sources = pristine-store-test.c utils.c
+install = test
+libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr
+
+[entries-compat-test]
+description = Test backwards compat for the entry interface
+type = exe
+path = subversion/tests/libsvn_wc
+sources = entries-compat.c utils.c
+install = test
+libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr
+
+[op-depth-test]
+description = Test layered tree changes
+type = exe
+path = subversion/tests/libsvn_wc
+sources = op-depth-test.c utils.c
+install = test
+libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr
+
+[wc-queries-test]
+description = Test Sqlite query evaluation
+type = exe
+path = subversion/tests/libsvn_wc
+sources = wc-queries-test.c
+install = test
+libs = libsvn_test libsvn_subr apriconv apr sqlite
+
+[wc-test]
+description = Test the main WC API functions
+type = exe
+path = subversion/tests/libsvn_wc
+sources = wc-test.c utils.c
+install = test
+libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr
+
+# ----------------------------------------------------------------------------
+# These are not unit tests at all, they are small programs that exercise
+# parts of the libsvn_delta API from the command line. They are stuck here
+# because of some historical association with the test-suite, but should
+# really be put somewhere else.
+
+# test our textdelta encoding
+[svndiff-test]
+type = exe
+path = subversion/tests/libsvn_delta
+sources = svndiff-test.c
+install = test
+libs = libsvn_delta libsvn_subr apriconv apr
+testing = skip
+
+# compare two files, print txdelta windows
+[vdelta-test]
+type = exe
+path = subversion/tests/libsvn_delta
+sources = vdelta-test.c
+install = test
+libs = libsvn_delta libsvn_subr apriconv apr
+testing = skip
+
+[entries-dump]
+type = exe
+path = subversion/tests/cmdline
+sources = entries-dump.c
+install = test
+libs = libsvn_wc libsvn_subr apriconv apr
+testing = skip
+
+[atomic-ra-revprop-change]
+type = exe
+path = subversion/tests/cmdline
+sources = atomic-ra-revprop-change.c
+install = test
+libs = libsvn_ra libsvn_subr apriconv apr
+testing = skip
+
+[wc-lock-tester]
+type = exe
+path = subversion/tests/libsvn_wc
+sources = wc-lock-tester.c
+install = test
+libs = libsvn_wc libsvn_subr apriconv apr
+testing = skip
+
+[wc-incomplete-tester]
+type = exe
+path = subversion/tests/libsvn_wc
+sources = wc-incomplete-tester.c
+install = test
+libs = libsvn_wc libsvn_subr apriconv apr
+testing = skip
+
+# ----------------------------------------------------------------------------
+#
+# EXTERNAL TARGETS (NO BUILD NEEDED)
+#
+
+[apr]
+type = lib
+external-lib = $(SVN_APR_LIBS)
+msvc-libs = ws2_32.lib rpcrt4.lib mswsock.lib
+
+[aprutil]
+type = lib
+external-lib = $(SVN_APRUTIL_LIBS)
+
+[apriconv]
+type = lib
+external-lib = $(SVN_APRUTIL_LIBS)
+
+[bdb]
+type = lib
+external-lib = $(SVN_DB_LIBS)
+
+[gnome-keyring]
+type = lib
+external-lib = $(SVN_GNOME_KEYRING_LIBS)
+
+[kwallet]
+type = lib
+external-lib = $(SVN_KWALLET_LIBS)
+
+[magic]
+type = lib
+external-lib = $(SVN_MAGIC_LIBS)
+
+[sasl]
+type = lib
+external-lib = $(SVN_SASL_LIBS)
+
+[zlib]
+type = lib
+external-lib = $(SVN_ZLIB_LIBS)
+external-project = zlib
+msvc-static = yes
+
+[apr_memcache]
+type = lib
+external-lib = $(SVN_APR_MEMCACHE_LIBS)
+
+[serf]
+type = lib
+external-lib = $(SVN_SERF_LIBS)
+external-project = serf/serf
+libs = apr aprutil xml
+msvc-static = yes
+
+[sqlite]
+type = lib
+external-lib = $(SVN_SQLITE_LIBS)
+
+[xml]
+type = lib
+external-lib = $(SVN_XML_LIBS)
+
+[ra-libs]
+type = lib
+external-lib = $(SVN_RA_LIB_LINK)
+libs = libsvn_ra_serf libsvn_ra_local libsvn_ra_svn
+
+[fs-libs]
+type = lib
+external-lib = $(SVN_FS_LIB_LINK)
+libs = libsvn_fs_base libsvn_fs_fs
+
+[__ALL__]
+type = project
+path = build/win32
+libs = svn svnadmin svndumpfilter svnlook svnmucc svnserve svnrdump svnsync
+ svnversion
+ mod_authz_svn mod_dav_svn mod_dontdothat
+ svnauthz svnauthz-validate svnraisetreeconflict
+
+[__ALL_TESTS__]
+type = project
+path = build/win32
+libs = __ALL__
+ fs-test fs-base-test fs-fsfs-test fs-pack-test skel-test
+ strings-reps-test changes-test locks-test repos-test
+ checksum-test compat-test config-test hashdump-test mergeinfo-test
+ opt-test path-test stream-test string-test time-test utf-test
+ error-test error-code-test cache-test spillbuf-test crypto-test
+ named_atomic-test named_atomic-proc-test revision-test
+ subst_translate-test io-test
+ translate-test
+ random-test window-test
+ diff-diff3-test
+ ra-test
+ ra-local-test
+ svndiff-test vdelta-test
+ entries-dump atomic-ra-revprop-change wc-lock-tester wc-incomplete-tester
+ client-test
+ conflict-data-test db-test pristine-store-test entries-compat-test
+ op-depth-test dirent_uri-test wc-queries-test wc-test
+ auth-test
+ parse-diff-test
+
+[__MORE__]
+type = project
+path = build/win32
+libs = __ALL_TESTS__
+ diff diff3 diff4 fsfs-reorg fsfs-stats fsfs-access-map svn-bench
+ svn-rep-sharing-stats svn-populate-node-origins-index
+
+[__LIBS__]
+type = project
+path = build/win32
+libs = fs-libs ra-libs libsvn_client libsvn_subr libsvn_wc
+ aprutil apriconv apr
+
+[__CONFIG__]
+type = lib
+external-project = svn_config
+
+[__SWIG_PYTHON__]
+type = swig_project
+path = build/win32
+libs = swig_client swig_delta swig_diff swig_fs swig_ra swig_repos swig_wc swig_core
+lang = python
+
+[__SWIG_PERL__]
+type = swig_project
+path = build/win32
+libs = swig_client swig_delta swig_fs swig_ra swig_repos swig_wc swig_core
+lang = perl
+
+[__SWIG_RUBY__]
+type = swig_project
+path = build/win32
+libs = swig_client swig_delta swig_fs swig_ra swig_repos swig_wc swig_core
+lang = ruby
+
+[__JAVAHL__]
+type = project
+path = build/win32
+libs = javahl-java javahl-javah libsvnjavahl
+
+[__JAVAHL_TESTS__]
+type = project
+path = build/win32
+libs = __JAVAHL__ javahl-tests javahl-compat-tests
+
+# ----------------------------------------------------------------------------
+# Contrib and tools
+
+[fsfs-reorg]
+type = exe
+path = tools/dev
+sources = fsfs-reorg.c
+install = tools
+libs = libsvn_delta libsvn_subr apr
+
+[fsfs-stats]
+type = exe
+path = tools/server-side
+sources = fsfs-stats.c
+install = tools
+libs = libsvn_delta libsvn_subr apr
+
+[fsfs-access-map]
+type = exe
+path = tools/dev
+sources = fsfs-access-map.c
+install = tools
+libs = libsvn_subr apr
+
+[diff]
+type = exe
+path = tools/diff
+sources = diff.c
+install = tools
+libs = libsvn_diff libsvn_subr apriconv apr
+
+[diff3]
+type = exe
+path = tools/diff
+sources = diff3.c
+install = tools
+libs = libsvn_diff libsvn_subr apriconv apr
+
+[diff4]
+type = exe
+path = tools/diff
+sources = diff4.c
+install = tools
+libs = libsvn_diff libsvn_subr apriconv apr
+
+[svn-bench]
+type = exe
+path = tools/client-side/svn-bench
+install = tools
+libs = libsvn_client libsvn_wc libsvn_ra libsvn_subr libsvn_delta
+ apriconv apr
+
+[svnauthz]
+description = Authz config file tool
+type = exe
+path = tools/server-side
+sources = svnauthz.c
+install = tools
+libs = libsvn_repos libsvn_fs libsvn_subr apr
+
+# svnauthz-validate is the compat mode of the new svnauthz tool. It is
+# exactly the same code as svnauthz. This duplicated target is needed
+# in order to easily test both commands as part of the build since libtool
+# does not provide a way to set argv[0] different from the commands actual
+# name in the wrapper script.
+[svnauthz-validate]
+description = Authz config file validator
+type = exe
+path = tools/server-side
+sources = svnauthz.c
+install = tools
+libs = libsvn_repos libsvn_fs libsvn_subr apr
+
+[svn-populate-node-origins-index]
+type = exe
+path = tools/server-side
+sources = svn-populate-node-origins-index.c
+install = tools
+libs = libsvn_repos libsvn_fs libsvn_subr apr
+
+[svnraisetreeconflict]
+description = Tool to Flag a Tree Conflict
+type = exe
+path = tools/dev/svnraisetreeconflict
+libs = libsvn_wc libsvn_subr apriconv apr
+install = tools
+
+[svn-rep-sharing-stats]
+type = exe
+path = tools/server-side
+sources = svn-rep-sharing-stats.c
+install = tools
+libs = libsvn_repos libsvn_fs libsvn_fs_fs libsvn_subr apriconv apr
diff --git a/contrib/subversion/configure b/contrib/subversion/configure
new file mode 100755
index 0000000..57dadd6
--- /dev/null
+++ b/contrib/subversion/configure
@@ -0,0 +1,27324 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.69 for subversion 1.8.0.
+#
+# Report bugs to <http://subversion.apache.org/>.
+#
+#
+# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
+#
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='print -r --'
+ as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='printf %s\n'
+ as_echo_n='printf %s'
+else
+ if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+ as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+ as_echo_n='/usr/ucb/echo -n'
+ else
+ as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+ as_echo_n_body='eval
+ arg=$1;
+ case $arg in #(
+ *"$as_nl"*)
+ expr "X$arg" : "X\\(.*\\)$as_nl";
+ arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+ esac;
+ expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+ '
+ export as_echo_n_body
+ as_echo_n='sh -c $as_echo_n_body as_echo'
+ fi
+ export as_echo_body
+ as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ PATH_SEPARATOR=:
+ (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+ (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+ PATH_SEPARATOR=';'
+ }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order. Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" "" $as_nl"
+
+# Find who we are. Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+ as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+ $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+ exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there. '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+# Use a proper internal environment variable to ensure we don't fall
+ # into an infinite loop, continuously re-executing ourselves.
+ if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then
+ _as_can_reexec=no; export _as_can_reexec;
+ # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+ *v*x* | *x*v* ) as_opts=-vx ;;
+ *v* ) as_opts=-v ;;
+ *x* ) as_opts=-x ;;
+ * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+as_fn_exit 255
+ fi
+ # We don't want this to propagate to other subprocesses.
+ { _as_can_reexec=; unset _as_can_reexec;}
+if test "x$CONFIG_SHELL" = x; then
+ as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '\${1+\"\$@\"}'='\"\$@\"'
+ setopt NO_GLOB_SUBST
+else
+ case \`(set -o) 2>/dev/null\` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+"
+ as_required="as_fn_return () { (exit \$1); }
+as_fn_success () { as_fn_return 0; }
+as_fn_failure () { as_fn_return 1; }
+as_fn_ret_success () { return 0; }
+as_fn_ret_failure () { return 1; }
+
+exitcode=0
+as_fn_success || { exitcode=1; echo as_fn_success failed.; }
+as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
+as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
+as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
+if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
+
+else
+ exitcode=1; echo positional parameters were not saved.
+fi
+test x\$exitcode = x0 || exit 1
+test -x / || exit 1"
+ as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
+ as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
+ eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
+ test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
+test \$(( 1 + 1 )) = 2 || exit 1
+
+ test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || (
+ ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+ ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO
+ ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO
+ PATH=/empty FPATH=/empty; export PATH FPATH
+ test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\
+ || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1"
+ if (eval "$as_required") 2>/dev/null; then :
+ as_have_required=yes
+else
+ as_have_required=no
+fi
+ if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
+
+else
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ as_found=:
+ case $as_dir in #(
+ /*)
+ for as_base in sh bash ksh sh5; do
+ # Try only shells that exist, to save several forks.
+ as_shell=$as_dir/$as_base
+ if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+ { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
+ CONFIG_SHELL=$as_shell as_have_required=yes
+ if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
+ break 2
+fi
+fi
+ done;;
+ esac
+ as_found=false
+done
+$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+ { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
+ CONFIG_SHELL=$SHELL as_have_required=yes
+fi; }
+IFS=$as_save_IFS
+
+
+ if test "x$CONFIG_SHELL" != x; then :
+ export CONFIG_SHELL
+ # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+ *v*x* | *x*v* ) as_opts=-vx ;;
+ *v* ) as_opts=-v ;;
+ *x* ) as_opts=-x ;;
+ * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+exit 255
+fi
+
+ if test x$as_have_required = xno; then :
+ $as_echo "$0: This script requires a shell more modern than all"
+ $as_echo "$0: the shells that I found on your system."
+ if test x${ZSH_VERSION+set} = xset ; then
+ $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+ $as_echo "$0: be upgraded to zsh 4.3.4 or later."
+ else
+ $as_echo "$0: Please tell bug-autoconf@gnu.org and
+$0: http://subversion.apache.org/ about your system,
+$0: including any error possibly output before this
+$0: message. Then install a modern shell, or manually run
+$0: the script under such a shell if you do have one."
+ fi
+ exit 1
+fi
+fi
+fi
+SHELL=${CONFIG_SHELL-/bin/sh}
+export SHELL
+# Unset more variables known to interfere with behavior of common tools.
+CLICOLOR_FORCE= GREP_OPTIONS=
+unset CLICOLOR_FORCE GREP_OPTIONS
+
+## --------------------- ##
+## M4sh Shell Functions. ##
+## --------------------- ##
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+ { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+ return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+ set +e
+ as_fn_set_status $1
+ exit $1
+} # as_fn_exit
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+ case $as_dir in #(
+ -*) as_dir=./$as_dir;;
+ esac
+ test -d "$as_dir" || eval $as_mkdir_p || {
+ as_dirs=
+ while :; do
+ case $as_dir in #(
+ *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+ *) as_qdir=$as_dir;;
+ esac
+ as_dirs="'$as_qdir' $as_dirs"
+ as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_dir" : 'X\(//\)[^/]' \| \
+ X"$as_dir" : 'X\(//\)$' \| \
+ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ test -d "$as_dir" && break
+ done
+ test -z "$as_dirs" || eval "mkdir $as_dirs"
+ } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+ test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+ eval 'as_fn_append ()
+ {
+ eval $1+=\$2
+ }'
+else
+ as_fn_append ()
+ {
+ eval $1=\$$1\$2
+ }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+ eval 'as_fn_arith ()
+ {
+ as_val=$(( $* ))
+ }'
+else
+ as_fn_arith ()
+ {
+ as_val=`expr "$@" || test $? -eq 1`
+ }
+fi # as_fn_arith
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+ as_status=$1; test $as_status -eq 0 && as_status=1
+ if test "$4"; then
+ as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+ fi
+ $as_echo "$as_me: error: $2" >&2
+ as_fn_exit $as_status
+} # as_fn_error
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+ test "X`expr 00001 : '.*\(...\)'`" = X001; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+ as_dirname=dirname
+else
+ as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+ as_lineno_1=$LINENO as_lineno_1a=$LINENO
+ as_lineno_2=$LINENO as_lineno_2a=$LINENO
+ eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
+ test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
+ # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-)
+ sed -n '
+ p
+ /[$]LINENO/=
+ ' <$as_myself |
+ sed '
+ s/[$]LINENO.*/&-/
+ t lineno
+ b
+ :lineno
+ N
+ :loop
+ s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+ t loop
+ s/-\n.*//
+ ' >$as_me.lineno &&
+ chmod +x "$as_me.lineno" ||
+ { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+
+ # If we had to re-execute with $CONFIG_SHELL, we're ensured to have
+ # already done that, so ensure we don't try to do so again and fall
+ # in an infinite loop. This has already happened in practice.
+ _as_can_reexec=no; export _as_can_reexec
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensitive to this).
+ . "./$as_me.lineno"
+ # Exit status is that of the last command.
+ exit
+}
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+ case `echo 'xy\c'` in
+ *c*) ECHO_T=' ';; # ECHO_T is single tab character.
+ xy) ECHO_C='\c';;
+ *) echo `echo ksh88 bug on AIX 6.1` > /dev/null
+ ECHO_T=' ';;
+ esac;;
+*)
+ ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+ rm -f conf$$.dir/conf$$.file
+else
+ rm -f conf$$.dir
+ mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+ if ln -s conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s='ln -s'
+ # ... but there are two gotchas:
+ # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+ # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+ # In both cases, we have to default to `cp -pR'.
+ ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+ as_ln_s='cp -pR'
+ elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+ else
+ as_ln_s='cp -pR'
+ fi
+else
+ as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p='mkdir -p "$as_dir"'
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+
+test -n "$DJDIR" || exec 7<&0 </dev/null
+exec 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+
+# Identity of this package.
+PACKAGE_NAME='subversion'
+PACKAGE_TARNAME='subversion'
+PACKAGE_VERSION='1.8.0'
+PACKAGE_STRING='subversion 1.8.0'
+PACKAGE_BUGREPORT='http://subversion.apache.org/'
+PACKAGE_URL=''
+
+ac_unique_file="subversion/include/svn_types.h"
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_STRING_H
+# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='LTLIBOBJS
+SVN_CONFIG_SCRIPT_FILES
+INCLUDE_OUTPUTS
+JAVAHL_COMPAT_TESTS_TARGET
+JAVAHL_TESTS_TARGET
+JAVA_CLASSPATH
+LT_CXX_LIBADD
+FIX_JAVAHL_LIB
+JAVAHL_OBJDIR
+INSTALL_EXTRA_JAVAHL_LIB
+SVN_FS_LIB_LINK
+SVN_FS_LIB_INSTALL_DEPS
+SVN_FS_LIB_DEPS
+SVN_RA_LIB_LINK
+SVN_RA_LIB_INSTALL_DEPS
+SVN_RA_LIB_DEPS
+CTYPESGEN
+SWIG_RB_TEST_VERBOSE
+SWIG_RB_SITE_ARCH_DIR
+SWIG_RB_SITE_LIB_DIR
+SWIG_RB_COMPILE
+SWIG_RB_INCLUDES
+SWIG_RB_LIBS
+SWIG_RB_LINK
+SWIG_PL_INCLUDES
+SWIG_PY_LIBS
+SWIG_PY_LINK
+SWIG_PY_COMPILE
+SWIG_PY_INCLUDES
+SWIG
+RUBY_TEENY
+RUBY_MINOR
+RUBY_MAJOR
+RDOC
+RUBY
+PERL
+JNI_INCLUDES
+JAR
+JAVAH
+JAVADOC
+JAVAC_FLAGS
+JAVAC
+JAVA
+JDK
+PYTHON
+MOD_ACTIVATION
+SVN_ZLIB_LIBS
+SVN_ZLIB_INCLUDES
+libsvn_wc_LDFLAGS
+libsvn_subr_LDFLAGS
+libsvn_repos_LDFLAGS
+libsvn_ra_svn_LDFLAGS
+libsvn_ra_serf_LDFLAGS
+libsvn_ra_local_LDFLAGS
+libsvn_ra_LDFLAGS
+libsvn_fs_util_LDFLAGS
+libsvn_fs_fs_LDFLAGS
+libsvn_fs_base_LDFLAGS
+libsvn_fs_LDFLAGS
+libsvn_diff_LDFLAGS
+libsvn_delta_LDFLAGS
+libsvn_client_LDFLAGS
+libsvn_auth_kwallet_LDFLAGS
+libsvn_auth_gnome_keyring_LDFLAGS
+LIBOBJS
+BDB_TEST_PROGRAMS
+BDB_TEST_DEPS
+INSTALL_RULES
+INSTALL_STATIC_RULES
+BUILD_RULES
+SVN_KWALLET_LIBS
+SVN_KWALLET_INCLUDES
+KDE4_CONFIG
+SVN_MAGIC_LIBS
+SVN_MAGIC_INCLUDES
+MSGFMTFLAGS
+NO_GETTEXT_CODESET
+GETTEXT_CODESET
+XGETTEXT
+MSGMERGE
+MSGFMT
+SVN_GNOME_KEYRING_LIBS
+SVN_GNOME_KEYRING_INCLUDES
+SVN_HAVE_GPG_AGENT
+SVN_SASL_LIBS
+SVN_SASL_INCLUDES
+SVN_DB_LIBS
+SVN_DB_INCLUDES
+SVN_XML_LIBS
+SVN_XML_INCLUDES
+DOXYGEN
+TRANG
+LT_NO_UNDEFINED
+TRANSFORM_LIBTOOL_SCRIPTS
+LT_LDFLAGS
+LT_CFLAGS
+SVN_LIBTOOL
+CXXCPP
+OTOOL64
+OTOOL
+LIPO
+NMEDIT
+DSYMUTIL
+MANIFEST_TOOL
+AWK
+RANLIB
+STRIP
+ac_ct_AR
+AR
+DLLTOOL
+OBJDUMP
+NM
+ac_ct_DUMPBIN
+DUMPBIN
+LD
+FGREP
+LIBTOOL
+SVN_BINDIR
+SVN_SQLITE_LIBS
+SVN_SQLITE_INCLUDES
+INSTALL_APACHE_MODS
+APACHE_LIBEXECDIR
+APACHE_INCLUDES
+APACHE_LDFLAGS
+APXS
+SVN_APR_MEMCACHE_LIBS
+SVN_APR_MEMCACHE_INCLUDES
+SVN_SERF_LIBS
+SVN_SERF_INCLUDES
+PKG_CONFIG
+SVN_LT_SOVERSION
+SVN_APRUTIL_LIBS
+SVN_APRUTIL_CONFIG
+SVN_APRUTIL_INCLUDES
+SVN_APR_SHLIB_PATH_VAR
+SVN_APR_LIBS
+SVN_APR_INCLUDES
+SVN_APR_CONFIG
+MKDIR
+INSTALL_DATA
+INSTALL_SCRIPT
+INSTALL_PROGRAM
+LN_S
+EGREP
+GREP
+target_os
+target_vendor
+target_cpu
+target
+host_os
+host_vendor
+host_cpu
+host
+build_os
+build_vendor
+build_cpu
+build
+SED
+CPP
+CXXMAINTAINERFLAGS
+CXXMODEFLAGS
+ac_ct_CXX
+CXXFLAGS
+CXX
+CMAINTAINERFLAGS
+CMODEFLAGS
+OBJEXT
+EXEEXT
+ac_ct_CC
+CPPFLAGS
+LDFLAGS
+CFLAGS
+CC
+SWIG_LDFLAGS
+canonicalized_srcdir
+abs_builddir
+abs_srcdir
+target_alias
+host_alias
+build_alias
+LIBS
+ECHO_T
+ECHO_N
+ECHO_C
+DEFS
+mandir
+localedir
+libdir
+psdir
+pdfdir
+dvidir
+htmldir
+infodir
+docdir
+oldincludedir
+includedir
+localstatedir
+sharedstatedir
+sysconfdir
+datadir
+datarootdir
+libexecdir
+sbindir
+bindir
+program_transform_name
+prefix
+exec_prefix
+PACKAGE_URL
+PACKAGE_BUGREPORT
+PACKAGE_STRING
+PACKAGE_VERSION
+PACKAGE_TARNAME
+PACKAGE_NAME
+PATH_SEPARATOR
+SHELL'
+ac_subst_files=''
+ac_user_opts='
+enable_option_checking
+with_apr
+with_apr_util
+with_serf
+with_apr_memcache
+with_apxs
+with_apache_libexecdir
+with_sqlite
+enable_sqlite_compatibility_version
+enable_shared
+enable_static
+with_pic
+enable_fast_install
+with_gnu_ld
+with_sysroot
+enable_libtool_lock
+enable_experimental_libtool
+enable_all_static
+enable_local_library_preloading
+with_trang
+with_doxygen
+with_expat
+with_berkeley_db
+with_sasl
+enable_keychain
+with_gpg_agent
+with_gnome_keyring
+enable_ev2_impl
+enable_nls
+with_libmagic
+with_kwallet
+enable_plaintext_password_storage
+with_openssl
+enable_debug
+enable_optimize
+enable_disallowing_of_undefined_references
+enable_maintainer_mode
+enable_full_version_match
+with_editor
+with_zlib
+enable_mod_activation
+enable_gcov
+enable_gprof
+with_jdk
+with_jikes
+with_swig
+with_ruby_sitedir
+with_ruby_test_verbose
+with_ctypesgen
+enable_runtime_module_search
+enable_javahl
+with_junit
+'
+ ac_precious_vars='build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+CXX
+CXXFLAGS
+CCC
+CPP
+CXXCPP'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+ac_unrecognized_opts=
+ac_unrecognized_sep=
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+ # If the previous option needs an argument, assign it.
+ if test -n "$ac_prev"; then
+ eval $ac_prev=\$ac_option
+ ac_prev=
+ continue
+ fi
+
+ case $ac_option in
+ *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+ *=) ac_optarg= ;;
+ *) ac_optarg=yes ;;
+ esac
+
+ # Accept the important Cygnus configure options, so we can diagnose typos.
+
+ case $ac_dashdash$ac_option in
+ --)
+ ac_dashdash=yes ;;
+
+ -bindir | --bindir | --bindi | --bind | --bin | --bi)
+ ac_prev=bindir ;;
+ -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+ bindir=$ac_optarg ;;
+
+ -build | --build | --buil | --bui | --bu)
+ ac_prev=build_alias ;;
+ -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+ build_alias=$ac_optarg ;;
+
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ac_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+ cache_file=$ac_optarg ;;
+
+ --config-cache | -C)
+ cache_file=config.cache ;;
+
+ -datadir | --datadir | --datadi | --datad)
+ ac_prev=datadir ;;
+ -datadir=* | --datadir=* | --datadi=* | --datad=*)
+ datadir=$ac_optarg ;;
+
+ -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+ | --dataroo | --dataro | --datar)
+ ac_prev=datarootdir ;;
+ -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+ datarootdir=$ac_optarg ;;
+
+ -disable-* | --disable-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid feature name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"enable_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval enable_$ac_useropt=no ;;
+
+ -docdir | --docdir | --docdi | --doc | --do)
+ ac_prev=docdir ;;
+ -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+ docdir=$ac_optarg ;;
+
+ -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+ ac_prev=dvidir ;;
+ -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+ dvidir=$ac_optarg ;;
+
+ -enable-* | --enable-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid feature name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"enable_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval enable_$ac_useropt=\$ac_optarg ;;
+
+ -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+ | --exec | --exe | --ex)
+ ac_prev=exec_prefix ;;
+ -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+ | --exec=* | --exe=* | --ex=*)
+ exec_prefix=$ac_optarg ;;
+
+ -gas | --gas | --ga | --g)
+ # Obsolete; use --with-gas.
+ with_gas=yes ;;
+
+ -help | --help | --hel | --he | -h)
+ ac_init_help=long ;;
+ -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+ ac_init_help=recursive ;;
+ -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+ ac_init_help=short ;;
+
+ -host | --host | --hos | --ho)
+ ac_prev=host_alias ;;
+ -host=* | --host=* | --hos=* | --ho=*)
+ host_alias=$ac_optarg ;;
+
+ -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+ ac_prev=htmldir ;;
+ -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+ | --ht=*)
+ htmldir=$ac_optarg ;;
+
+ -includedir | --includedir | --includedi | --included | --include \
+ | --includ | --inclu | --incl | --inc)
+ ac_prev=includedir ;;
+ -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+ | --includ=* | --inclu=* | --incl=* | --inc=*)
+ includedir=$ac_optarg ;;
+
+ -infodir | --infodir | --infodi | --infod | --info | --inf)
+ ac_prev=infodir ;;
+ -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+ infodir=$ac_optarg ;;
+
+ -libdir | --libdir | --libdi | --libd)
+ ac_prev=libdir ;;
+ -libdir=* | --libdir=* | --libdi=* | --libd=*)
+ libdir=$ac_optarg ;;
+
+ -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+ | --libexe | --libex | --libe)
+ ac_prev=libexecdir ;;
+ -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+ | --libexe=* | --libex=* | --libe=*)
+ libexecdir=$ac_optarg ;;
+
+ -localedir | --localedir | --localedi | --localed | --locale)
+ ac_prev=localedir ;;
+ -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+ localedir=$ac_optarg ;;
+
+ -localstatedir | --localstatedir | --localstatedi | --localstated \
+ | --localstate | --localstat | --localsta | --localst | --locals)
+ ac_prev=localstatedir ;;
+ -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+ localstatedir=$ac_optarg ;;
+
+ -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+ ac_prev=mandir ;;
+ -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+ mandir=$ac_optarg ;;
+
+ -nfp | --nfp | --nf)
+ # Obsolete; use --without-fp.
+ with_fp=no ;;
+
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c | -n)
+ no_create=yes ;;
+
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ no_recursion=yes ;;
+
+ -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+ | --oldin | --oldi | --old | --ol | --o)
+ ac_prev=oldincludedir ;;
+ -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+ oldincludedir=$ac_optarg ;;
+
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ac_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ prefix=$ac_optarg ;;
+
+ -program-prefix | --program-prefix | --program-prefi | --program-pref \
+ | --program-pre | --program-pr | --program-p)
+ ac_prev=program_prefix ;;
+ -program-prefix=* | --program-prefix=* | --program-prefi=* \
+ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+ program_prefix=$ac_optarg ;;
+
+ -program-suffix | --program-suffix | --program-suffi | --program-suff \
+ | --program-suf | --program-su | --program-s)
+ ac_prev=program_suffix ;;
+ -program-suffix=* | --program-suffix=* | --program-suffi=* \
+ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+ program_suffix=$ac_optarg ;;
+
+ -program-transform-name | --program-transform-name \
+ | --program-transform-nam | --program-transform-na \
+ | --program-transform-n | --program-transform- \
+ | --program-transform | --program-transfor \
+ | --program-transfo | --program-transf \
+ | --program-trans | --program-tran \
+ | --progr-tra | --program-tr | --program-t)
+ ac_prev=program_transform_name ;;
+ -program-transform-name=* | --program-transform-name=* \
+ | --program-transform-nam=* | --program-transform-na=* \
+ | --program-transform-n=* | --program-transform-=* \
+ | --program-transform=* | --program-transfor=* \
+ | --program-transfo=* | --program-transf=* \
+ | --program-trans=* | --program-tran=* \
+ | --progr-tra=* | --program-tr=* | --program-t=*)
+ program_transform_name=$ac_optarg ;;
+
+ -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+ ac_prev=pdfdir ;;
+ -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+ pdfdir=$ac_optarg ;;
+
+ -psdir | --psdir | --psdi | --psd | --ps)
+ ac_prev=psdir ;;
+ -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+ psdir=$ac_optarg ;;
+
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ silent=yes ;;
+
+ -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+ ac_prev=sbindir ;;
+ -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+ | --sbi=* | --sb=*)
+ sbindir=$ac_optarg ;;
+
+ -sharedstatedir | --sharedstatedir | --sharedstatedi \
+ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+ | --sharedst | --shareds | --shared | --share | --shar \
+ | --sha | --sh)
+ ac_prev=sharedstatedir ;;
+ -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+ | --sha=* | --sh=*)
+ sharedstatedir=$ac_optarg ;;
+
+ -site | --site | --sit)
+ ac_prev=site ;;
+ -site=* | --site=* | --sit=*)
+ site=$ac_optarg ;;
+
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ac_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ srcdir=$ac_optarg ;;
+
+ -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+ | --syscon | --sysco | --sysc | --sys | --sy)
+ ac_prev=sysconfdir ;;
+ -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+ sysconfdir=$ac_optarg ;;
+
+ -target | --target | --targe | --targ | --tar | --ta | --t)
+ ac_prev=target_alias ;;
+ -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+ target_alias=$ac_optarg ;;
+
+ -v | -verbose | --verbose | --verbos | --verbo | --verb)
+ verbose=yes ;;
+
+ -version | --version | --versio | --versi | --vers | -V)
+ ac_init_version=: ;;
+
+ -with-* | --with-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid package name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"with_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval with_$ac_useropt=\$ac_optarg ;;
+
+ -without-* | --without-*)
+ ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+ as_fn_error $? "invalid package name: $ac_useropt"
+ ac_useropt_orig=$ac_useropt
+ ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+ case $ac_user_opts in
+ *"
+"with_$ac_useropt"
+"*) ;;
+ *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
+ ac_unrecognized_sep=', ';;
+ esac
+ eval with_$ac_useropt=no ;;
+
+ --x)
+ # Obsolete; use --with-x.
+ with_x=yes ;;
+
+ -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+ | --x-incl | --x-inc | --x-in | --x-i)
+ ac_prev=x_includes ;;
+ -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+ x_includes=$ac_optarg ;;
+
+ -x-libraries | --x-libraries | --x-librarie | --x-librari \
+ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+ ac_prev=x_libraries ;;
+ -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+ x_libraries=$ac_optarg ;;
+
+ -*) as_fn_error $? "unrecognized option: \`$ac_option'
+Try \`$0 --help' for more information"
+ ;;
+
+ *=*)
+ ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+ # Reject names that are not valid shell variable names.
+ case $ac_envvar in #(
+ '' | [0-9]* | *[!_$as_cr_alnum]* )
+ as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
+ esac
+ eval $ac_envvar=\$ac_optarg
+ export $ac_envvar ;;
+
+ *)
+ # FIXME: should be removed in autoconf 3.0.
+ $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+ expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+ $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+ : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+ ;;
+
+ esac
+done
+
+if test -n "$ac_prev"; then
+ ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+ as_fn_error $? "missing argument to $ac_option"
+fi
+
+if test -n "$ac_unrecognized_opts"; then
+ case $enable_option_checking in
+ no) ;;
+ fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
+ *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+ esac
+fi
+
+# Check all directory arguments for consistency.
+for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
+ datadir sysconfdir sharedstatedir localstatedir includedir \
+ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+ libdir localedir mandir
+do
+ eval ac_val=\$$ac_var
+ # Remove trailing slashes.
+ case $ac_val in
+ */ )
+ ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
+ eval $ac_var=\$ac_val;;
+ esac
+ # Be sure to have absolute directory names.
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* ) continue;;
+ NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+ esac
+ as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+ if test "x$build_alias" = x; then
+ cross_compiling=maybe
+ elif test "x$build_alias" != "x$host_alias"; then
+ cross_compiling=yes
+ fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+ as_fn_error $? "working directory cannot be determined"
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+ as_fn_error $? "pwd does not report name of working directory"
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+ ac_srcdir_defaulted=yes
+ # Try the directory containing this script, then the parent directory.
+ ac_confdir=`$as_dirname -- "$as_myself" ||
+$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_myself" : 'X\(//\)[^/]' \| \
+ X"$as_myself" : 'X\(//\)$' \| \
+ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_myself" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ srcdir=$ac_confdir
+ if test ! -r "$srcdir/$ac_unique_file"; then
+ srcdir=..
+ fi
+else
+ ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+ test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+ as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+ cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
+ pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+ srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+ eval ac_env_${ac_var}_set=\${${ac_var}+set}
+ eval ac_env_${ac_var}_value=\$${ac_var}
+ eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+ eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat <<_ACEOF
+\`configure' configures subversion 1.8.0 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE. See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+ -h, --help display this help and exit
+ --help=short display options specific to this package
+ --help=recursive display the short help of all the included packages
+ -V, --version display version information and exit
+ -q, --quiet, --silent do not print \`checking ...' messages
+ --cache-file=FILE cache test results in FILE [disabled]
+ -C, --config-cache alias for \`--cache-file=config.cache'
+ -n, --no-create do not create output files
+ --srcdir=DIR find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [$ac_default_prefix]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --libexecdir=DIR program executables [EPREFIX/libexec]
+ --sysconfdir=DIR read-only single-machine data [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --libdir=DIR object code libraries [EPREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --oldincludedir=DIR C header files for non-gcc [/usr/include]
+ --datarootdir=DIR read-only arch.-independent data root [PREFIX/share]
+ --datadir=DIR read-only architecture-independent data [DATAROOTDIR]
+ --infodir=DIR info documentation [DATAROOTDIR/info]
+ --localedir=DIR locale-dependent data [DATAROOTDIR/locale]
+ --mandir=DIR man documentation [DATAROOTDIR/man]
+ --docdir=DIR documentation root [DATAROOTDIR/doc/subversion]
+ --htmldir=DIR html documentation [DOCDIR]
+ --dvidir=DIR dvi documentation [DOCDIR]
+ --pdfdir=DIR pdf documentation [DOCDIR]
+ --psdir=DIR ps documentation [DOCDIR]
+_ACEOF
+
+ cat <<\_ACEOF
+
+System types:
+ --build=BUILD configure for building on BUILD [guessed]
+ --host=HOST cross-compile to build programs to run on HOST [BUILD]
+ --target=TARGET configure for building compilers for TARGET [HOST]
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+ case $ac_init_help in
+ short | recursive ) echo "Configuration of subversion 1.8.0:";;
+ esac
+ cat <<\_ACEOF
+
+Optional Features:
+ --disable-option-checking ignore unrecognized --enable/--with options
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --enable-sqlite-compatibility-version=X.Y.Z
+ Allow binary to run against SQLite as old as ARG
+ --enable-shared[=PKGS] build shared libraries [default=yes]
+ --enable-static[=PKGS] build static libraries [default=yes]
+ --enable-fast-install[=PKGS]
+ optimize for fast installation [default=yes]
+ --disable-libtool-lock avoid locking (might break parallel builds)
+ --enable-experimental-libtool
+ Use APR's libtool
+ --enable-static Build static libraries
+ --enable-shared Build shared libraries
+ --enable-all-static Build completely static (standalone) binaries.
+ --enable-local-library-preloading
+ Enable preloading of locally built libraries in
+ locally built executables. This may be necessary for
+ testing prior to installation on some platforms. It
+ does not work on some platforms (Darwin, OpenBSD,
+ ...).
+ --disable-keychain Disable use of Mac OS KeyChain for auth credentials
+ --enable-ev2-impl Use Ev2 implementations, where available
+ [EXPERIMENTAL]
+ --disable-nls Disable gettext functionality
+ --disable-plaintext-password-storage
+ Disable on-disk caching of plaintext passwords and
+ passphrases. (Leaving this functionality enabled
+ will not force Subversion to store passwords in
+ plaintext, but does permit users to explicitly allow
+ that behavior via runtime configuration.)
+ --enable-debug Turn on debugging
+ --enable-optimize Turn on optimizations
+ --enable-disallowing-of-undefined-references
+ Use -Wl,--no-undefined flag during linking of some
+ libraries to disallow undefined references
+ --enable-maintainer-mode
+ Turn on debugging and very strict compile-time
+ warnings
+ --disable-full-version-match
+ Disable the full version match rules when checking
+ Subversion library compatibility.
+ --enable-mod-activation Enable mod_dav_svn in httpd.conf
+ --enable-gcov Turn on gcov coverage testing (GCC only).
+ --enable-gprof Produce gprof profiling data in 'gmon.out' (GCC
+ only).
+ --enable-runtime-module-search
+ Turn on dynamic loading of RA/FS libraries including
+ third-party FS libraries
+ --enable-javahl Enable compilation of Java high-level bindings
+ (requires C++)
+
+Optional Packages:
+ --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
+ --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
+ --with-apr=PATH prefix for installed APR, path to APR build tree,
+ or the full path to apr-config
+ --with-apr-util=PATH prefix for installed APU, path to APU build tree,
+ or the full path to apu-config
+ --with-serf=PREFIX Serf HTTP client library (enabled by default if
+ found)
+ --with-apr_memcache=PREFIX
+ Standalone apr_memcache client library
+ --with-apxs[=FILE] Build shared Apache modules. FILE is the optional
+ pathname to the Apache apxs tool; defaults to
+ "apxs".
+ --with-apache-libexecdir[=PATH]
+ Install Apache modules to Apache's configured
+ modules directory instead of LIBEXECDIR; if PATH is
+ given, install to PATH.
+ --with-sqlite=PREFIX Use installed SQLite library or amalgamation file.
+ --with-pic[=PKGS] try to use only PIC/non-PIC objects [default=use
+ both]
+ --with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-sysroot=DIR Search for dependent libraries within DIR
+ (or the compiler's sysroot if not specified).
+ --with-trang=PATH Specify the command to run the trang schema
+ converter
+ --with-doxygen=PATH Specify the command to run doxygen
+ --with-expat=INCLUDES:LIB_SEARCH_DIRS:LIBS
+ Specify location of Expat
+ --with-berkeley-db[=HEADER:INCLUDES:LIB_SEARCH_DIRS:LIBS]
+ The Subversion Berkeley DB based filesystem library
+ requires Berkeley DB $db_version or newer. If you
+ specify `--without-berkeley-db', that library will
+ not be built. If you omit the argument of this
+ option completely, the configure script will use
+ Berkeley DB used by APR-UTIL.
+ --with-sasl=PATH Compile with libsasl2 in PATH
+ --without-gpg-agent Disable support for GPG-Agent
+ --with-gnome-keyring Enable use of GNOME Keyring for auth credentials
+ (enabled by default if found)
+ --with-libmagic=PREFIX libmagic filetype detection library
+ --with-kwallet[=PATH] Enable use of KWallet (KDE 4) for auth credentials
+ --with-openssl This option does NOT affect the Subversion build
+ process in any way. It tells an integrated Serf HTTP
+ client library build process where to locate the
+ OpenSSL library when (and only when) building Serf
+ as an integrated part of the Subversion build
+ process. When linking to a previously installed
+ version of Serf instead, you do not need to use this
+ option.
+ --with-editor=PATH Specify a default editor for the subversion client.
+ --with-zlib=PREFIX zlib compression library
+ --with-jdk=PATH Try to use 'PATH/include' to find the JNI headers.
+ If PATH is not specified, look for a Java
+ Development Kit at JAVA_HOME.
+ --with-jikes=PATH Specify the path to a jikes binary to use it as your
+ Java compiler. The default is to look for jikes
+ (PATH optional). This behavior can be switched off
+ by supplying 'no'.
+ --with-swig=PATH Try to use 'PATH/bin/swig' to build the swig
+ bindings. If PATH is not specified, look for a
+ 'swig' binary in your PATH.
+ --with-ruby-sitedir=SITEDIR
+ install Ruby bindings in SITEDIR (default is same as
+ ruby's one)
+ --with-ruby-test-verbose=LEVEL
+ how to use output level for Ruby bindings tests
+ (default is normal)
+ --with-ctypesgen=PATH Specify the path to ctypesgen. This can either be
+ the full path to a ctypesgen installation, the full
+ path to a ctypesgen source tree or the full path to
+ ctypesgen.py.
+ --with-junit=PATH Specify a path to the junit JAR file.
+
+Some influential environment variables:
+ CC C compiler command
+ CFLAGS C compiler flags
+ LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
+ nonstandard directory <lib dir>
+ LIBS libraries to pass to the linker, e.g. -l<library>
+ CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+ you have headers in a nonstandard directory <include dir>
+ CXX C++ compiler command
+ CXXFLAGS C++ compiler flags
+ CPP C preprocessor
+ CXXCPP C++ preprocessor
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to <http://subversion.apache.org/>.
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+ # If there are subdirs, report their specific --help.
+ for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+ test -d "$ac_dir" ||
+ { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
+ continue
+ ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+ ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+ # A ".." for each directory in $ac_dir_suffix.
+ ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+ case $ac_top_builddir_sub in
+ "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+ *) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+ esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+ .) # We are building in place.
+ ac_srcdir=.
+ ac_top_srcdir=$ac_top_builddir_sub
+ ac_abs_top_srcdir=$ac_pwd ;;
+ [\\/]* | ?:[\\/]* ) # Absolute name.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir
+ ac_abs_top_srcdir=$srcdir ;;
+ *) # Relative name.
+ ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_build_prefix$srcdir
+ ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+ cd "$ac_dir" || { ac_status=$?; continue; }
+ # Check for guested configure.
+ if test -f "$ac_srcdir/configure.gnu"; then
+ echo &&
+ $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+ elif test -f "$ac_srcdir/configure"; then
+ echo &&
+ $SHELL "$ac_srcdir/configure" --help=recursive
+ else
+ $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+ fi || ac_status=$?
+ cd "$ac_pwd" || { ac_status=$?; break; }
+ done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+ cat <<\_ACEOF
+subversion configure 1.8.0
+generated by GNU Autoconf 2.69
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+ exit
+fi
+
+## ------------------------ ##
+## Autoconf initialization. ##
+## ------------------------ ##
+
+# ac_fn_c_try_compile LINENO
+# --------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_compile ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext
+ if { { ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compile") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest.$ac_objext; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_compile
+
+# ac_fn_cxx_try_compile LINENO
+# ----------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_cxx_try_compile ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext
+ if { { ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compile") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_cxx_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest.$ac_objext; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_cxx_try_compile
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } > conftest.i && {
+ test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+
+# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists, giving a warning if it cannot be compiled using
+# the include files in INCLUDES and setting the cache variable VAR
+# accordingly.
+ac_fn_c_check_header_mongrel ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if eval \${$3+:} false; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+else
+ # Is the header compilable?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5
+$as_echo_n "checking $2 usability... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_header_compiler=yes
+else
+ ac_header_compiler=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5
+$as_echo "$ac_header_compiler" >&6; }
+
+# Is the header present?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5
+$as_echo_n "checking $2 presence... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <$2>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ ac_header_preproc=yes
+else
+ ac_header_preproc=no
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
+$as_echo "$ac_header_preproc" >&6; }
+
+# So? What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #((
+ yes:no: )
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5
+$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+ ;;
+ no:yes:* )
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5
+$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5
+$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5
+$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5
+$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+( $as_echo "## -------------------------------------------- ##
+## Report this to http://subversion.apache.org/ ##
+## -------------------------------------------- ##"
+ ) | sed "s/^/$as_me: WARNING: /" >&2
+ ;;
+esac
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ eval "$3=\$ac_header_compiler"
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_mongrel
+
+# ac_fn_c_try_run LINENO
+# ----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
+# that executables *can* be run.
+ac_fn_c_try_run ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
+ { { case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_try") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: program exited with status $ac_status" >&5
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=$ac_status
+fi
+ rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_run
+
+# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists and can be compiled using the include files in
+# INCLUDES, setting the cache variable VAR accordingly.
+ac_fn_c_check_header_compile ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ eval "$3=yes"
+else
+ eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_compile
+
+# ac_fn_c_try_link LINENO
+# -----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_link ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext conftest$ac_exeext
+ if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest$ac_exeext && {
+ test "$cross_compiling" = yes ||
+ test -x conftest$ac_exeext
+ }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+ # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+ # interfere with the next link command; also delete a directory that is
+ # left behind by Apple's compiler. We do this before executing the actions.
+ rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_link
+
+# ac_fn_c_check_func LINENO FUNC VAR
+# ----------------------------------
+# Tests whether FUNC exists, setting the cache variable VAR accordingly
+ac_fn_c_check_func ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
+ For example, HP-UX 11i <limits.h> declares gettimeofday. */
+#define $2 innocuous_$2
+
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $2 (); below.
+ Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ <limits.h> exists even on freestanding compilers. */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $2
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $2 ();
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined __stub_$2 || defined __stub___$2
+choke me
+#endif
+
+int
+main ()
+{
+return $2 ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ eval "$3=yes"
+else
+ eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_func
+
+# ac_fn_cxx_try_cpp LINENO
+# ------------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_cxx_try_cpp ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } > conftest.i && {
+ test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" ||
+ test ! -s conftest.err
+ }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_cxx_try_cpp
+
+# ac_fn_cxx_try_link LINENO
+# -------------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_cxx_try_link ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext conftest$ac_exeext
+ if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_cxx_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest$ac_exeext && {
+ test "$cross_compiling" = yes ||
+ test -x conftest$ac_exeext
+ }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+ # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+ # interfere with the next link command; also delete a directory that is
+ # left behind by Apple's compiler. We do this before executing the actions.
+ rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_cxx_try_link
+
+# ac_fn_c_check_type LINENO TYPE VAR INCLUDES
+# -------------------------------------------
+# Tests whether TYPE exists after having included INCLUDES, setting cache
+# variable VAR accordingly.
+ac_fn_c_check_type ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ eval "$3=no"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+int
+main ()
+{
+if (sizeof ($2))
+ return 0;
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$4
+int
+main ()
+{
+if (sizeof (($2)))
+ return 0;
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ eval "$3=yes"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_type
+cat >config.log <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by subversion $as_me 1.8.0, which was
+generated by GNU Autoconf 2.69. Invocation command line was
+
+ $ $0 $@
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown`
+
+/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown`
+/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown`
+/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ $as_echo "PATH: $as_dir"
+ done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+ for ac_arg
+ do
+ case $ac_arg in
+ -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ continue ;;
+ *\'*)
+ ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ case $ac_pass in
+ 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
+ 2)
+ as_fn_append ac_configure_args1 " '$ac_arg'"
+ if test $ac_must_keep_next = true; then
+ ac_must_keep_next=false # Got value, back to normal.
+ else
+ case $ac_arg in
+ *=* | --config-cache | -C | -disable-* | --disable-* \
+ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+ | -with-* | --with-* | -without-* | --without-* | --x)
+ case "$ac_configure_args0 " in
+ "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+ esac
+ ;;
+ -* ) ac_must_keep_next=true ;;
+ esac
+ fi
+ as_fn_append ac_configure_args " '$ac_arg'"
+ ;;
+ esac
+ done
+done
+{ ac_configure_args0=; unset ac_configure_args0;}
+{ ac_configure_args1=; unset ac_configure_args1;}
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log. We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+ # Save into config.log some information that might help in debugging.
+ {
+ echo
+
+ $as_echo "## ---------------- ##
+## Cache variables. ##
+## ---------------- ##"
+ echo
+ # The following way of writing the cache mishandles newlines in values,
+(
+ for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+ eval ac_val=\$$ac_var
+ case $ac_val in #(
+ *${as_nl}*)
+ case $ac_var in #(
+ *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+ esac
+ case $ac_var in #(
+ _ | IFS | as_nl) ;; #(
+ BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+ *) { eval $ac_var=; unset $ac_var;} ;;
+ esac ;;
+ esac
+ done
+ (set) 2>&1 |
+ case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+ *${as_nl}ac_space=\ *)
+ sed -n \
+ "s/'\''/'\''\\\\'\'''\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+ ;; #(
+ *)
+ sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+ ;;
+ esac |
+ sort
+)
+ echo
+
+ $as_echo "## ----------------- ##
+## Output variables. ##
+## ----------------- ##"
+ echo
+ for ac_var in $ac_subst_vars
+ do
+ eval ac_val=\$$ac_var
+ case $ac_val in
+ *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+ esac
+ $as_echo "$ac_var='\''$ac_val'\''"
+ done | sort
+ echo
+
+ if test -n "$ac_subst_files"; then
+ $as_echo "## ------------------- ##
+## File substitutions. ##
+## ------------------- ##"
+ echo
+ for ac_var in $ac_subst_files
+ do
+ eval ac_val=\$$ac_var
+ case $ac_val in
+ *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+ esac
+ $as_echo "$ac_var='\''$ac_val'\''"
+ done | sort
+ echo
+ fi
+
+ if test -s confdefs.h; then
+ $as_echo "## ----------- ##
+## confdefs.h. ##
+## ----------- ##"
+ echo
+ cat confdefs.h
+ echo
+ fi
+ test "$ac_signal" != 0 &&
+ $as_echo "$as_me: caught signal $ac_signal"
+ $as_echo "$as_me: exit $exit_status"
+ } >&5
+ rm -f core *.core core.conftest.* &&
+ rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+ exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+ trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+$as_echo "/* confdefs.h */" > confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_URL "$PACKAGE_URL"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer an explicitly selected file to automatically selected ones.
+ac_site_file1=NONE
+ac_site_file2=NONE
+if test -n "$CONFIG_SITE"; then
+ # We do not want a PATH search for config.site.
+ case $CONFIG_SITE in #((
+ -*) ac_site_file1=./$CONFIG_SITE;;
+ */*) ac_site_file1=$CONFIG_SITE;;
+ *) ac_site_file1=./$CONFIG_SITE;;
+ esac
+elif test "x$prefix" != xNONE; then
+ ac_site_file1=$prefix/share/config.site
+ ac_site_file2=$prefix/etc/config.site
+else
+ ac_site_file1=$ac_default_prefix/share/config.site
+ ac_site_file2=$ac_default_prefix/etc/config.site
+fi
+for ac_site_file in "$ac_site_file1" "$ac_site_file2"
+do
+ test "x$ac_site_file" = xNONE && continue
+ if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+$as_echo "$as_me: loading site script $ac_site_file" >&6;}
+ sed 's/^/| /' "$ac_site_file" >&5
+ . "$ac_site_file" \
+ || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "failed to load site script $ac_site_file
+See \`config.log' for more details" "$LINENO" 5; }
+ fi
+done
+
+if test -r "$cache_file"; then
+ # Some versions of bash will fail to source /dev/null (special files
+ # actually), so we avoid doing that. DJGPP emulates it as a regular file.
+ if test /dev/null != "$cache_file" && test -f "$cache_file"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+$as_echo "$as_me: loading cache $cache_file" >&6;}
+ case $cache_file in
+ [\\/]* | ?:[\\/]* ) . "$cache_file";;
+ *) . "./$cache_file";;
+ esac
+ fi
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+$as_echo "$as_me: creating cache $cache_file" >&6;}
+ >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+ eval ac_old_set=\$ac_cv_env_${ac_var}_set
+ eval ac_new_set=\$ac_env_${ac_var}_set
+ eval ac_old_val=\$ac_cv_env_${ac_var}_value
+ eval ac_new_val=\$ac_env_${ac_var}_value
+ case $ac_old_set,$ac_new_set in
+ set,)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,set)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,);;
+ *)
+ if test "x$ac_old_val" != "x$ac_new_val"; then
+ # differences in whitespace do not lead to failure.
+ ac_old_val_w=`echo x $ac_old_val`
+ ac_new_val_w=`echo x $ac_new_val`
+ if test "$ac_old_val_w" != "$ac_new_val_w"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+ ac_cache_corrupted=:
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+ eval $ac_var=\$ac_old_val
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5
+$as_echo "$as_me: former value: \`$ac_old_val'" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5
+$as_echo "$as_me: current value: \`$ac_new_val'" >&2;}
+ fi;;
+ esac
+ # Pass precious variables to config.status.
+ if test "$ac_new_set" = set; then
+ case $ac_new_val in
+ *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+ *) ac_arg=$ac_var=$ac_new_val ;;
+ esac
+ case " $ac_configure_args " in
+ *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
+ *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+ esac
+ fi
+done
+if $ac_cache_corrupted; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+ as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+ac_aux_dir=
+for ac_dir in build "$srcdir"/build; do
+ if test -f "$ac_dir/install-sh"; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install-sh -c"
+ break
+ elif test -f "$ac_dir/install.sh"; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install.sh -c"
+ break
+ elif test -f "$ac_dir/shtool"; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/shtool install -c"
+ break
+ fi
+done
+if test -z "$ac_aux_dir"; then
+ as_fn_error $? "cannot find install-sh, install.sh, or shtool in build \"$srcdir\"/build" "$LINENO" 5
+fi
+
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var.
+ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var.
+ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var.
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: Configuring Subversion 1.8.0" >&5
+$as_echo "$as_me: Configuring Subversion 1.8.0" >&6;}
+
+abs_srcdir="`cd $srcdir && pwd`"
+
+abs_builddir="`pwd`"
+
+if test "$abs_srcdir" = "$abs_builddir"; then
+ canonicalized_srcdir=""
+else
+ canonicalized_srcdir="$srcdir/"
+fi
+
+
+SWIG_LDFLAGS="$LDFLAGS"
+
+
+# Generate config.nice early (before the arguments are munged)
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: creating config.nice" >&5
+$as_echo "$as_me: creating config.nice" >&6;}
+ # This little dance satisfies Cygwin, which cannot overwrite in-use files.
+ if test -f "config.nice"; then
+ mv "config.nice" "config.nice.old"
+ fi
+
+ cat >"config.nice" <<EOF
+#! /bin/sh
+#
+# Created by configure
+
+'$0' $ac_configure_args "\$@"
+EOF
+
+ chmod +x "config.nice"
+ rm -f "config.nice.old"
+
+
+# ==== Check for programs ====================================================
+
+# Look for a C compiler (before anything can set CFLAGS)
+CMAINTAINERFLAGS="$CUSERFLAGS"
+CUSERFLAGS="$CFLAGS"
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="${ac_tool_prefix}gcc"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+ ac_ct_CC=$CC
+ # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="gcc"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_CC" = x; then
+ CC=""
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ CC=$ac_ct_CC
+ fi
+else
+ CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="${ac_tool_prefix}cc"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ fi
+fi
+if test -z "$CC"; then
+ # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+ ac_prog_rejected=yes
+ continue
+ fi
+ ac_cv_prog_CC="cc"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+ # We found a bogon in the path, so make sure we never use it.
+ set dummy $ac_cv_prog_CC
+ shift
+ if test $# != 0; then
+ # We chose a different compiler from the bogus one.
+ # However, it has the same basename, so the bogon will be chosen
+ # first if we set CC to just the basename; use the full file name.
+ shift
+ ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+ fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ for ac_prog in cl.exe
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$CC" && break
+ done
+fi
+if test -z "$CC"; then
+ ac_ct_CC=$CC
+ for ac_prog in cl.exe
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$ac_ct_CC" && break
+done
+
+ if test "x$ac_ct_CC" = x; then
+ CC=""
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ CC=$ac_ct_CC
+ fi
+fi
+
+fi
+
+
+test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5; }
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+ { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ sed '10a\
+... rest of stderr output deleted ...
+ 10q' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ fi
+ rm -f conftest.er1 conftest.err
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+done
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+$as_echo_n "checking whether the C compiler works... " >&6; }
+ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+
+# The possible output files:
+ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
+
+ac_rmfiles=
+for ac_file in $ac_files
+do
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+ * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+ esac
+done
+rm -f $ac_rmfiles
+
+if { { ac_try="$ac_link_default"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link_default") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then :
+ # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile. We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
+ ;;
+ [ab].out )
+ # We found the default executable, but exeext='' is most
+ # certainly right.
+ break;;
+ *.* )
+ if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+ then :; else
+ ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ fi
+ # We set ac_cv_exeext here because the later test for it is not
+ # safe: cross compilers may not add the suffix if given an `-o'
+ # argument, so we may need to know it at that point already.
+ # Even if this section looks crufty: it has the advantage of
+ # actually working.
+ break;;
+ * )
+ break;;
+ esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else
+ ac_file=''
+fi
+if test -z "$ac_file"; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+$as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+$as_echo_n "checking for C compiler default output file name... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+$as_echo "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
+
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+$as_echo_n "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then :
+ # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+ *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ break;;
+ * ) break;;
+ esac
+done
+else
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+$as_echo "$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdio.h>
+int
+main ()
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run. If not, either
+# the compiler is broken, or we cross compile.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+$as_echo_n "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+ { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ if { ac_try='./conftest$ac_cv_exeext'
+ { { case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_try") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; }; then
+ cross_compiling=no
+ else
+ if test "$cross_compiling" = maybe; then
+ cross_compiling=yes
+ else
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5; }
+ fi
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+$as_echo "$cross_compiling" >&6; }
+
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+$as_echo_n "checking for suffix of object files... " >&6; }
+if ${ac_cv_objext+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compile") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then :
+ for ac_file in conftest.o conftest.obj conftest.*; do
+ test -f "$ac_file" || continue;
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+ *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+ break;;
+ esac
+done
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+$as_echo "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+if ${ac_cv_c_compiler_gnu+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+#ifndef __GNUC__
+ choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_compiler_gnu=yes
+else
+ ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+ GCC=yes
+else
+ GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+$as_echo_n "checking whether $CC accepts -g... " >&6; }
+if ${ac_cv_prog_cc_g+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_save_c_werror_flag=$ac_c_werror_flag
+ ac_c_werror_flag=yes
+ ac_cv_prog_cc_g=no
+ CFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_prog_cc_g=yes
+else
+ CFLAGS=""
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+ ac_c_werror_flag=$ac_save_c_werror_flag
+ CFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+$as_echo "$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+ CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+ if test "$GCC" = yes; then
+ CFLAGS="-g -O2"
+ else
+ CFLAGS="-g"
+ fi
+else
+ if test "$GCC" = yes; then
+ CFLAGS="-O2"
+ else
+ CFLAGS=
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+if ${ac_cv_prog_cc_c89+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdarg.h>
+#include <stdio.h>
+struct stat;
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+ char **p;
+ int i;
+{
+ return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+ char *s;
+ va_list v;
+ va_start (v,p);
+ s = g (p, va_arg (v,int));
+ va_end (v);
+ return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has
+ function prototypes and stuff, but not '\xHH' hex character constants.
+ These don't provoke an error unfortunately, instead are silently treated
+ as 'x'. The following induces an error, until -std is added to get
+ proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an
+ array size at least. It's necessary to write '\x00'==0 to get something
+ that's true only with -std. */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+ inside strings and character constants. */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1];
+ ;
+ return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+ -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+ CC="$ac_save_CC $ac_arg"
+ if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+ test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+ x)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+ xno)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+ *)
+ CC="$CC $ac_cv_prog_cc_c89"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c89" != xno; then :
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ CFLAGS_KEEP="$CFLAGS"
+ CFLAGS=""
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -std=c90" >&5
+$as_echo_n "checking if $CC accepts -std=c90... " >&6; }
+ CFLAGS="-std=c90 $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -std=c89" >&5
+$as_echo_n "checking if $CC accepts -std=c89... " >&6; }
+ CFLAGS="-std=c89 $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -ansi" >&5
+$as_echo_n "checking if $CC accepts -ansi... " >&6; }
+ CFLAGS="-ansi $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ CMODEFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS_KEEP"
+
+
+
+
+# Look for a C++ compiler (before anything can set CXXFLAGS)
+CXXMAINTAINERFLAGS="$CXXUSERFLAGS"
+CXXUSERFLAGS="$CXXFLAGS"
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+if test -z "$CXX"; then
+ if test -n "$CCC"; then
+ CXX=$CCC
+ else
+ if test -n "$ac_tool_prefix"; then
+ for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CXX"; then
+ ac_cv_prog_CXX="$CXX" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CXX="$ac_tool_prefix$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CXX=$ac_cv_prog_CXX
+if test -n "$CXX"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5
+$as_echo "$CXX" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$CXX" && break
+ done
+fi
+if test -z "$CXX"; then
+ ac_ct_CXX=$CXX
+ for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_CXX"; then
+ ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CXX="$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CXX=$ac_cv_prog_ac_ct_CXX
+if test -n "$ac_ct_CXX"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5
+$as_echo "$ac_ct_CXX" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$ac_ct_CXX" && break
+done
+
+ if test "x$ac_ct_CXX" = x; then
+ CXX="g++"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ CXX=$ac_ct_CXX
+ fi
+fi
+
+ fi
+fi
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+ { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ sed '10a\
+... rest of stderr output deleted ...
+ 10q' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ fi
+ rm -f conftest.er1 conftest.err
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+done
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C++ compiler" >&5
+$as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; }
+if ${ac_cv_cxx_compiler_gnu+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+#ifndef __GNUC__
+ choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ ac_compiler_gnu=yes
+else
+ ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_cxx_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5
+$as_echo "$ac_cv_cxx_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+ GXX=yes
+else
+ GXX=
+fi
+ac_test_CXXFLAGS=${CXXFLAGS+set}
+ac_save_CXXFLAGS=$CXXFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5
+$as_echo_n "checking whether $CXX accepts -g... " >&6; }
+if ${ac_cv_prog_cxx_g+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_save_cxx_werror_flag=$ac_cxx_werror_flag
+ ac_cxx_werror_flag=yes
+ ac_cv_prog_cxx_g=no
+ CXXFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ ac_cv_prog_cxx_g=yes
+else
+ CXXFLAGS=""
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+else
+ ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+ CXXFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ ac_cv_prog_cxx_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5
+$as_echo "$ac_cv_prog_cxx_g" >&6; }
+if test "$ac_test_CXXFLAGS" = set; then
+ CXXFLAGS=$ac_save_CXXFLAGS
+elif test $ac_cv_prog_cxx_g = yes; then
+ if test "$GXX" = yes; then
+ CXXFLAGS="-g -O2"
+ else
+ CXXFLAGS="-g"
+ fi
+else
+ if test "$GXX" = yes; then
+ CXXFLAGS="-O2"
+ else
+ CXXFLAGS=
+ fi
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ CXXFLAGS_KEEP="$CXXFLAGS"
+ CXXFLAGS=""
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -std=c++98" >&5
+$as_echo_n "checking if $CXX accepts -std=c++98... " >&6; }
+ CXXFLAGS="-std=c++98 $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ CXXMODEFLAGS="$CXXFLAGS"
+ CXXFLAGS="$CXXFLAGS_KEEP"
+
+
+
+
+# Look for a C pre-processor
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+$as_echo_n "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+ CPP=
+fi
+if test -z "$CPP"; then
+ if ${ac_cv_prog_CPP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ # Double quotes because CPP needs to be expanded
+ for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+ do
+ ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ # <limits.h> exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+ Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ # Broken: success on invalid input.
+continue
+else
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+ break
+fi
+
+ done
+ ac_cv_prog_CPP=$CPP
+
+fi
+ CPP=$ac_cv_prog_CPP
+else
+ ac_cv_prog_CPP=$CPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+$as_echo "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ # <limits.h> exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+ Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ # Broken: success on invalid input.
+continue
+else
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+# Look for a good sed
+# AC_PROG_SED was introduced in Autoconf 2.59b
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5
+$as_echo_n "checking for a sed that does not truncate output... " >&6; }
+if ${ac_cv_path_SED+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/
+ for ac_i in 1 2 3 4 5 6 7; do
+ ac_script="$ac_script$as_nl$ac_script"
+ done
+ echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed
+ { ac_script=; unset ac_script;}
+ if test -z "$SED"; then
+ ac_path_SED_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in sed gsed; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_SED="$as_dir/$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_SED" || continue
+# Check for GNU ac_path_SED and select it if it is found.
+ # Check for GNU $ac_path_SED
+case `"$ac_path_SED" --version 2>&1` in
+*GNU*)
+ ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;;
+*)
+ ac_count=0
+ $as_echo_n 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ $as_echo '' >> "conftest.nl"
+ "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_SED_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_SED="$ac_path_SED"
+ ac_path_SED_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_SED_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_SED"; then
+ as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5
+ fi
+else
+ ac_cv_path_SED=$SED
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5
+$as_echo "$ac_cv_path_SED" >&6; }
+ SED="$ac_cv_path_SED"
+ rm -f conftest.sed
+
+
+# Grab target_cpu, so we can use it in the Solaris pkginfo file
+# Make sure we can run config.sub.
+$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
+ as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
+$as_echo_n "checking build system type... " >&6; }
+if ${ac_cv_build+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_build_alias=$build_alias
+test "x$ac_build_alias" = x &&
+ ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
+test "x$ac_build_alias" = x &&
+ as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5
+ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` ||
+ as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
+$as_echo "$ac_cv_build" >&6; }
+case $ac_cv_build in
+*-*-*) ;;
+*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;;
+esac
+build=$ac_cv_build
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_build
+shift
+build_cpu=$1
+build_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+build_os=$*
+IFS=$ac_save_IFS
+case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5
+$as_echo_n "checking host system type... " >&6; }
+if ${ac_cv_host+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test "x$host_alias" = x; then
+ ac_cv_host=$ac_cv_build
+else
+ ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` ||
+ as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5
+$as_echo "$ac_cv_host" >&6; }
+case $ac_cv_host in
+*-*-*) ;;
+*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;;
+esac
+host=$ac_cv_host
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_host
+shift
+host_cpu=$1
+host_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+host_os=$*
+IFS=$ac_save_IFS
+case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking target system type" >&5
+$as_echo_n "checking target system type... " >&6; }
+if ${ac_cv_target+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test "x$target_alias" = x; then
+ ac_cv_target=$ac_cv_host
+else
+ ac_cv_target=`$SHELL "$ac_aux_dir/config.sub" $target_alias` ||
+ as_fn_error $? "$SHELL $ac_aux_dir/config.sub $target_alias failed" "$LINENO" 5
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_target" >&5
+$as_echo "$ac_cv_target" >&6; }
+case $ac_cv_target in
+*-*-*) ;;
+*) as_fn_error $? "invalid value of canonical target" "$LINENO" 5;;
+esac
+target=$ac_cv_target
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_target
+shift
+target_cpu=$1
+target_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+target_os=$*
+IFS=$ac_save_IFS
+case $target_os in *\ *) target_os=`echo "$target_os" | sed 's/ /-/g'`;; esac
+
+
+# The aliases save the names the user supplied, while $host etc.
+# will get canonicalized.
+test -n "$target_alias" &&
+ test "$program_prefix$program_suffix$program_transform_name" = \
+ NONENONEs,x,x, &&
+ program_prefix=${target_alias}-
+
+# Look for an extended grep
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
+if ${ac_cv_path_GREP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -z "$GREP"; then
+ ac_path_GREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in grep ggrep; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_GREP" || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+ # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+ ac_count=0
+ $as_echo_n 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ $as_echo 'GREP' >> "conftest.nl"
+ "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_GREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_GREP="$ac_path_GREP"
+ ac_path_GREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_GREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_GREP"; then
+ as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+$as_echo "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+$as_echo_n "checking for egrep... " >&6; }
+if ${ac_cv_path_EGREP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+ then ac_cv_path_EGREP="$GREP -E"
+ else
+ if test -z "$EGREP"; then
+ ac_path_EGREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in egrep; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_EGREP" || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+ # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+ ac_count=0
+ $as_echo_n 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ $as_echo 'EGREP' >> "conftest.nl"
+ "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_EGREP="$ac_path_EGREP"
+ ac_path_EGREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_EGREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_EGREP"; then
+ as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_EGREP=$EGREP
+fi
+
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+$as_echo "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5
+$as_echo_n "checking whether ln -s works... " >&6; }
+LN_S=$as_ln_s
+if test "$LN_S" = "ln -s"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5
+$as_echo "no, using $LN_S" >&6; }
+fi
+
+
+# Find a good install program. We prefer a C program (faster),
+# so one script is as good as another. But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# OS/2's system install, which has a completely different semantic
+# ./install, which can be erroneously created by make from ./install.sh.
+# Reject install programs that cannot install multiple files.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
+$as_echo_n "checking for a BSD-compatible install... " >&6; }
+if test -z "$INSTALL"; then
+if ${ac_cv_path_install+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ # Account for people who put trailing slashes in PATH elements.
+case $as_dir/ in #((
+ ./ | .// | /[cC]/* | \
+ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \
+ /usr/ucb/* ) ;;
+ *)
+ # OSF1 and SCO ODT 3.0 have their own names for install.
+ # Don't use installbsd from OSF since it installs stuff as root
+ # by default.
+ for ac_prog in ginstall scoinst install; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then
+ if test $ac_prog = install &&
+ grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # AIX install. It has an incompatible calling convention.
+ :
+ elif test $ac_prog = install &&
+ grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+ # program-specific install script used by HP pwplus--don't use.
+ :
+ else
+ rm -rf conftest.one conftest.two conftest.dir
+ echo one > conftest.one
+ echo two > conftest.two
+ mkdir conftest.dir
+ if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" &&
+ test -s conftest.one && test -s conftest.two &&
+ test -s conftest.dir/conftest.one &&
+ test -s conftest.dir/conftest.two
+ then
+ ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c"
+ break 3
+ fi
+ fi
+ fi
+ done
+ done
+ ;;
+esac
+
+ done
+IFS=$as_save_IFS
+
+rm -rf conftest.one conftest.two conftest.dir
+
+fi
+ if test "${ac_cv_path_install+set}" = set; then
+ INSTALL=$ac_cv_path_install
+ else
+ # As a last resort, use the slow shell script. Don't cache a
+ # value for INSTALL within a source directory, because that will
+ # break other packages using the cache if that directory is
+ # removed, or if the value is a relative name.
+ INSTALL=$ac_install_sh
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5
+$as_echo "$INSTALL" >&6; }
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+# If $INSTALL is relative path to our fallback install-sh, then convert
+# to an absolute path, as in some cases (e.g. Solaris VPATH build), libtool
+# may try to use it from a changed working directory.
+if test "$INSTALL" = "build/install-sh -c"; then
+ INSTALL="$abs_srcdir/$INSTALL"
+fi
+
+if test -z "$MKDIR"; then
+ MKDIR="$INSTALL -d"
+fi
+
+
+# ==== Libraries, for which we may have source to build ======================
+
+
+APR_VER_REGEXES="0\.9\.[7-9] 0\.9\.1[0-9] 1\. 2\."
+
+
+ APR_WANTED_REGEXES="$APR_VER_REGEXES"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Apache Portable Runtime (APR) library configuration" >&5
+$as_echo "$as_me: Apache Portable Runtime (APR) library configuration" >&6;}
+
+
+ apr_found="no"
+
+ if test "$ac_cv_emxos2" = "yes"; then
+ # Scripts don't pass test -x on OS/2
+ TEST_X="test -f"
+ else
+ TEST_X="test -x"
+ fi
+
+ acceptable_majors="1 0"
+
+ apr_temp_acceptable_apr_config=""
+ for apr_temp_major in $acceptable_majors
+ do
+ case $apr_temp_major in
+ 0)
+ apr_temp_acceptable_apr_config="$apr_temp_acceptable_apr_config apr-config"
+ ;;
+ *)
+ apr_temp_acceptable_apr_config="$apr_temp_acceptable_apr_config apr-$apr_temp_major-config"
+ ;;
+ esac
+ done
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for APR" >&5
+$as_echo_n "checking for APR... " >&6; }
+
+# Check whether --with-apr was given.
+if test "${with_apr+set}" = set; then :
+ withval=$with_apr;
+ if test "$withval" = "no" || test "$withval" = "yes"; then
+ as_fn_error $? "--with-apr requires a directory or file to be provided" "$LINENO" 5
+ fi
+
+ for apr_temp_apr_config_file in $apr_temp_acceptable_apr_config
+ do
+ for lookdir in "$withval/bin" "$withval"
+ do
+ if $TEST_X "$lookdir/$apr_temp_apr_config_file"; then
+ apr_found="yes"
+ apr_config="$lookdir/$apr_temp_apr_config_file"
+ break 2
+ fi
+ done
+ done
+
+ if test "$apr_found" != "yes" && $TEST_X "$withval" && $withval --help > /dev/null 2>&1 ; then
+ apr_found="yes"
+ apr_config="$withval"
+ fi
+
+ if test "$apr_found" != "yes"; then
+ as_fn_error $? "the --with-apr parameter is incorrect. It must specify an install prefix, a build directory, or an apr-config file." "$LINENO" 5
+ fi
+
+else
+
+ if test -d """"; then
+ apr_temp_abs_srcdir="`cd "" && pwd`"
+ apr_found="reconfig"
+ apr_bundled_major="`sed -n '/#define.*APR_MAJOR_VERSION/s/^[^0-9]*\([0-9]*\).*$/\1/p' \"""/include/apr_version.h\"`"
+ case $apr_bundled_major in
+ "")
+ as_fn_error $? "failed to find major version of bundled APR" "$LINENO" 5
+ ;;
+ 0)
+ apr_temp_apr_config_file="apr-config"
+ ;;
+ *)
+ apr_temp_apr_config_file="apr-$apr_bundled_major-config"
+ ;;
+ esac
+ if test -n """"; then
+ apr_config="""/$apr_temp_apr_config_file"
+ else
+ apr_config="""/$apr_temp_apr_config_file"
+ fi
+ fi
+ if test "$apr_found" = "no" && test -n "1" && test "1" = "1"; then
+ for apr_temp_apr_config_file in $apr_temp_acceptable_apr_config
+ do
+ if $apr_temp_apr_config_file --help > /dev/null 2>&1 ; then
+ apr_found="yes"
+ apr_config="$apr_temp_apr_config_file"
+ break
+ else
+ for lookdir in /usr /usr/local /opt/apr /usr/local/apache2 ; do
+ if $TEST_X "$lookdir/bin/$apr_temp_apr_config_file"; then
+ apr_found="yes"
+ apr_config="$lookdir/bin/$apr_temp_apr_config_file"
+ break 2
+ fi
+ done
+ fi
+ done
+ fi
+
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $apr_found" >&5
+$as_echo "$apr_found" >&6; }
+
+
+ if test $apr_found = "no"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: APR not found" >&5
+$as_echo "$as_me: WARNING: APR not found" >&2;}
+
+ echo "The Apache Portable Runtime (APR) library cannot be found."
+ echo "Please install APR on this system and configure Subversion"
+ echo "with the appropriate --with-apr option."
+ echo ""
+ echo "You probably need to do something similar with the Apache"
+ echo "Portable Runtime Utility (APRUTIL) library and then configure"
+ echo "Subversion with both the --with-apr and --with-apr-util options."
+ echo ""
+ as_fn_error $? "no suitable APR found" "$LINENO" 5
+
+ fi
+
+ if test $apr_found = "reconfig"; then
+ as_fn_error $? "Unexpected APR reconfig" "$LINENO" 5
+ fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking APR version" >&5
+$as_echo_n "checking APR version... " >&6; }
+ apr_version="`$apr_config --version`"
+ if test $? -ne 0; then
+ as_fn_error $? "apr-config --version failed" "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $apr_version" >&5
+$as_echo "$apr_version" >&6; }
+
+ APR_WANTED_REGEX_MATCH=0
+ for apr_wanted_regex in $APR_WANTED_REGEXES; do
+ if test `expr $apr_version : $apr_wanted_regex` -ne 0; then
+ APR_WANTED_REGEX_MATCH=1
+ break
+ fi
+ done
+
+ if test $APR_WANTED_REGEX_MATCH -eq 0; then
+ echo "wanted regexes are $APR_WANTED_REGEXES"
+ as_fn_error $? "invalid apr version found" "$LINENO" 5
+ fi
+
+
+ CPPFLAGS="$CPPFLAGS `$apr_config --cppflags`"
+ if test $? -ne 0; then
+ as_fn_error $? "apr-config --cppflags failed" "$LINENO" 5
+ fi
+
+ CFLAGS="$CFLAGS `$apr_config --cflags`"
+ if test $? -ne 0; then
+ as_fn_error $? "apr-config --cflags failed" "$LINENO" 5
+ fi
+
+ apr_ldflags="`$apr_config --ldflags`"
+ if test $? -ne 0; then
+ as_fn_error $? "apr-config --ldflags failed" "$LINENO" 5
+ fi
+ LDFLAGS="$LDFLAGS `
+ input_flags="$apr_ldflags"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ SVN_APR_INCLUDES="`$apr_config --includes`"
+ if test $? -ne 0; then
+ as_fn_error $? "apr-config --includes failed" "$LINENO" 5
+ fi
+
+ if test "$enable_all_static" = "yes"; then
+ SVN_APR_LIBS="`$apr_config --link-ld --libs`"
+ if test $? -ne 0; then
+ as_fn_error $? "apr-config --link-ld --libs failed" "$LINENO" 5
+ fi
+ else
+ SVN_APR_LIBS="`$apr_config --link-ld`"
+ if test $? -ne 0; then
+ as_fn_error $? "apr-config --link-ld failed" "$LINENO" 5
+ fi
+ fi
+ SVN_APR_LIBS="`
+ input_flags="$SVN_APR_LIBS"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ SVN_APR_SHLIB_PATH_VAR="`$apr_config --shlib-path-var`"
+ if test $? -ne 0; then
+ as_fn_error $? "apr-config --shlib-path-var failed" "$LINENO" 5
+ fi
+
+ SVN_APR_CONFIG="$apr_config"
+
+
+
+
+
+
+if test `expr $apr_version : 2` -ne 0; then
+ svn_lib_ver=2
+ apu_config=$apr_config
+
+ SVN_APRUTIL_CONFIG="$apu_config"
+
+
+else
+ svn_lib_ver=0
+ APU_VER_REGEXES="0\.9\.[7-9] 0\.9\.1[0-9] 1\."
+
+ APRUTIL_WANTED_REGEXES="$APU_VER_REGEXES"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Apache Portable Runtime Utility (APRUTIL) library configuration" >&5
+$as_echo "$as_me: Apache Portable Runtime Utility (APRUTIL) library configuration" >&6;}
+
+
+ apu_found="no"
+
+ if test "$ac_cv_emxos2" = "yes"; then
+ # Scripts don't pass test -x on OS/2
+ TEST_X="test -f"
+ else
+ TEST_X="test -x"
+ fi
+
+ acceptable_majors="1 0"
+
+ apu_temp_acceptable_apu_config=""
+ for apu_temp_major in $acceptable_majors
+ do
+ case $apu_temp_major in
+ 0)
+ apu_temp_acceptable_apu_config="$apu_temp_acceptable_apu_config apu-config"
+ ;;
+ *)
+ apu_temp_acceptable_apu_config="$apu_temp_acceptable_apu_config apu-$apu_temp_major-config"
+ ;;
+ esac
+ done
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for APR-util" >&5
+$as_echo_n "checking for APR-util... " >&6; }
+
+# Check whether --with-apr-util was given.
+if test "${with_apr_util+set}" = set; then :
+ withval=$with_apr_util;
+ if test "$withval" = "no" || test "$withval" = "yes"; then
+ as_fn_error $? "--with-apr-util requires a directory or file to be provided" "$LINENO" 5
+ fi
+
+ for apu_temp_apu_config_file in $apu_temp_acceptable_apu_config
+ do
+ for lookdir in "$withval/bin" "$withval"
+ do
+ if $TEST_X "$lookdir/$apu_temp_apu_config_file"; then
+ apu_found="yes"
+ apu_config="$lookdir/$apu_temp_apu_config_file"
+ break 2
+ fi
+ done
+ done
+
+ if test "$apu_found" != "yes" && $TEST_X "$withval" && $withval --help > /dev/null 2>&1 ; then
+ apu_found="yes"
+ apu_config="$withval"
+ fi
+
+ if test "$apu_found" != "yes"; then
+ as_fn_error $? "the --with-apr-util parameter is incorrect. It must specify an install prefix, a build directory, or an apu-config file." "$LINENO" 5
+ fi
+
+else
+
+ if test -d """"; then
+ apu_temp_abs_srcdir="`cd "" && pwd`"
+ apu_found="reconfig"
+ apu_bundled_major="`sed -n '/#define.*APU_MAJOR_VERSION/s/^[^0-9]*\([0-9]*\).*$/\1/p' \"""/include/apu_version.h\"`"
+ case $apu_bundled_major in
+ "")
+ as_fn_error $? "failed to find major version of bundled APU" "$LINENO" 5
+ ;;
+ 0)
+ apu_temp_apu_config_file="apu-config"
+ ;;
+ *)
+ apu_temp_apu_config_file="apu-$apu_bundled_major-config"
+ ;;
+ esac
+ if test -n """"; then
+ apu_config="""/$apu_temp_apu_config_file"
+ else
+ apu_config="""/$apu_temp_apu_config_file"
+ fi
+ fi
+ if test "$apu_found" = "no" && test -n "1" && test "1" = "1"; then
+ for apu_temp_apu_config_file in $apu_temp_acceptable_apu_config
+ do
+ if $apu_temp_apu_config_file --help > /dev/null 2>&1 ; then
+ apu_found="yes"
+ apu_config="$apu_temp_apu_config_file"
+ break
+ else
+ for lookdir in /usr /usr/local /opt/apr /usr/local/apache2 ; do
+ if $TEST_X "$lookdir/bin/$apu_temp_apu_config_file"; then
+ apu_found="yes"
+ apu_config="$lookdir/bin/$apu_temp_apu_config_file"
+ break 2
+ fi
+ done
+ fi
+ done
+ fi
+
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $apu_found" >&5
+$as_echo "$apu_found" >&6; }
+
+
+ if test $apu_found = "no"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: APRUTIL not found" >&5
+$as_echo "$as_me: WARNING: APRUTIL not found" >&2;}
+
+ echo "The Apache Portable Runtime Utility (APRUTIL) library cannot be found."
+ echo "Install APRUTIL on this system and configure Subversion with the"
+ echo " appropriate --with-apr-util option."
+ echo ""
+ as_fn_error $? "no suitable APRUTIL found" "$LINENO" 5
+
+ fi
+
+ if test $apu_found = "reconfig"; then
+ as_fn_error $? "Unexpected APRUTIL reconfig" "$LINENO" 5
+ fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking APRUTIL version" >&5
+$as_echo_n "checking APRUTIL version... " >&6; }
+ apu_version="`$apu_config --version`"
+ if test $? -ne 0; then
+ # This is a hack as suggested by Ben Collins-Sussman. It can be
+ # removed after apache 2.0.44 has been released. (The apu-config
+ # shipped in 2.0.43 contains a correct version number, but
+ # stupidly doesn't understand the --version switch.)
+ apu_version=`grep "APRUTIL_DOTTED_VERSION=" $(which $apu_config) | tr -d "APRUTIL_DOTTED_VERSION="| tr -d '"'`
+ #AC_MSG_ERROR([
+ # apu-config --version failed.
+ # Your apu-config doesn't support the --version switch, please upgrade
+ # to APR-UTIL more recent than 2002-Nov-05.])
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $apu_version" >&5
+$as_echo "$apu_version" >&6; }
+
+ APU_WANTED_REGEX_MATCH=0
+ for apu_wanted_regex in $APRUTIL_WANTED_REGEXES; do
+ if test `expr $apu_version : $apu_wanted_regex` -ne 0; then
+ APU_WANTED_REGEX_MATCH=1
+ break
+ fi
+ done
+
+ if test $APU_WANTED_REGEX_MATCH -eq 0; then
+ echo "wanted regexes are $APRUTIL_WANTED_REGEXES"
+ as_fn_error $? "invalid APRUTIL version found" "$LINENO" 5
+ fi
+
+
+ apu_ldflags="`$apu_config --ldflags`"
+ if test $? -ne 0; then
+ as_fn_error $? "apu-config --ldflags failed" "$LINENO" 5
+ fi
+ LDFLAGS="$LDFLAGS `
+ input_flags="$apu_ldflags"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ SVN_APRUTIL_INCLUDES="`$apu_config --includes`"
+ if test $? -ne 0; then
+ as_fn_error $? "apu-config --includes failed" "$LINENO" 5
+ fi
+
+ if test "$enable_all_static" = "yes"; then
+ SVN_APRUTIL_LIBS="`$apu_config --link-ld --libs`"
+ if test $? -ne 0; then
+ as_fn_error $? "apu-config --link-ld --libs failed" "$LINENO" 5
+ fi
+ else
+ SVN_APRUTIL_LIBS="`$apu_config --link-ld`"
+ if test $? -ne 0; then
+ as_fn_error $? "apu-config --link-ld failed" "$LINENO" 5
+ fi
+ fi
+ SVN_APRUTIL_LIBS="`
+ input_flags="$SVN_APRUTIL_LIBS"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+
+ SVN_APRUTIL_CONFIG="$apu_config"
+
+
+
+ SVN_HAVE_OLD_EXPAT="`$apu_config --old-expat`"
+ if test "$SVN_HAVE_OLD_EXPAT" = "yes"; then
+
+$as_echo "#define SVN_HAVE_OLD_EXPAT 1" >>confdefs.h
+
+ fi
+
+fi
+SVN_LT_SOVERSION="-version-info $svn_lib_ver"
+
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_SOVERSION $svn_lib_ver
+_ACEOF
+
+
+# Extract the first word of "pkg-config", so it can be a program name with args.
+set dummy pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PKG_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $PKG_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+PKG_CONFIG=$ac_cv_path_PKG_CONFIG
+if test -n "$PKG_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
+$as_echo "$PKG_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
+$as_echo_n "checking for ANSI C header files... " >&6; }
+if ${ac_cv_header_stdc+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_header_stdc=yes
+else
+ ac_cv_header_stdc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+ # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "memchr" >/dev/null 2>&1; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "free" >/dev/null 2>&1; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+ if test "$cross_compiling" = yes; then :
+ :
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ctype.h>
+#include <stdlib.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+ (('a' <= (c) && (c) <= 'i') \
+ || ('j' <= (c) && (c) <= 'r') \
+ || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ if (XOR (islower (i), ISLOWER (i))
+ || toupper (i) != TOUPPER (i))
+ return 2;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
+$as_echo "$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+$as_echo "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+ inttypes.h stdint.h unistd.h
+do :
+ as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
+"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+
+ serf_found=no
+ serf_required=no
+ serf_skip=no
+
+ serf_check_major="1"
+ serf_check_minor="2"
+ serf_check_patch="1"
+ serf_check_version="1.2.1"
+
+
+# Check whether --with-serf was given.
+if test "${with_serf+set}" = set; then :
+ withval=$with_serf;
+ if test "$withval" = "yes" ; then
+ serf_required=yes
+ elif test "$withval" = "no" ; then
+ serf_skip=yes
+ else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: serf library configuration via prefix" >&5
+$as_echo "$as_me: serf library configuration via prefix" >&6;}
+ serf_required=yes
+ serf_prefix=$withval
+ for serf_major in serf-2 serf-1; do
+ if ! test -d $serf_prefix/include/$serf_major; then continue; fi
+ save_cppflags="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES -I$serf_prefix/include/$serf_major"
+ for ac_header in serf.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "serf.h" "ac_cv_header_serf_h" "$ac_includes_default"
+if test "x$ac_cv_header_serf_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_SERF_H 1
+_ACEOF
+
+ save_ldflags="$LDFLAGS"
+ LDFLAGS="$LDFLAGS -L$serf_prefix/lib"
+ as_ac_Lib=`$as_echo "ac_cv_lib_$serf_major''_serf_context_create" | $as_tr_sh`
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for serf_context_create in -l$serf_major" >&5
+$as_echo_n "checking for serf_context_create in -l$serf_major... " >&6; }
+if eval \${$as_ac_Lib+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-l$serf_major $SVN_APRUTIL_LIBS $SVN_APR_LIBS -lz $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char serf_context_create ();
+int
+main ()
+{
+return serf_context_create ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ eval "$as_ac_Lib=yes"
+else
+ eval "$as_ac_Lib=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+eval ac_res=\$$as_ac_Lib
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <stdlib.h>
+#include "serf.h"
+
+int
+main ()
+{
+
+#if ! SERF_VERSION_AT_LEAST($serf_check_major, $serf_check_minor, $serf_check_patch)
+#error Serf version too old: need $serf_check_version
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ serf_found=yes
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Serf version too old: need $serf_check_version" >&5
+$as_echo "$as_me: WARNING: Serf version too old: need $serf_check_version" >&2;}
+ serf_found=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
+ LDFLAGS="$save_ldflags"
+fi
+
+done
+
+ CPPFLAGS="$save_cppflags"
+ test $serf_found = yes && break
+ done
+
+ if test $serf_found = "yes"; then
+ SVN_SERF_INCLUDES="-I$serf_prefix/include/$serf_major"
+ if test -e "$serf_prefix/lib/lib$serf_major.la"; then
+ SVN_SERF_LIBS="$serf_prefix/lib/lib$serf_major.la"
+ else
+ SVN_SERF_LIBS="-l$serf_major"
+ LDFLAGS="$LDFLAGS -L$serf_prefix/lib"
+ fi
+ fi
+
+ fi
+
+fi
+
+
+ if test "$serf_skip" = "no" ; then
+ if test "$serf_found" = "no" ; then
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: serf library configuration via pkg-config" >&5
+$as_echo "$as_me: serf library configuration via pkg-config" >&6;}
+ if test -n "$PKG_CONFIG"; then
+ for serf_major in serf-2 serf-1; do
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $serf_major library" >&5
+$as_echo_n "checking for $serf_major library... " >&6; }
+ if $PKG_CONFIG $serf_major --exists; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking serf library version" >&5
+$as_echo_n "checking serf library version... " >&6; }
+ SERF_VERSION=`$PKG_CONFIG $serf_major --modversion`
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SERF_VERSION" >&5
+$as_echo "$SERF_VERSION" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking serf version is suitable" >&5
+$as_echo_n "checking serf version is suitable... " >&6; }
+ if $PKG_CONFIG $serf_major --atleast-version=$serf_check_version; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ serf_found=yes
+ SVN_SERF_INCLUDES=`$PKG_CONFIG $serf_major --cflags | $SED -e 's/-D[^ ]*//g'`
+ SVN_SERF_LIBS=`$PKG_CONFIG $serf_major --libs`
+ break
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Serf version too old: need $serf_check_version" >&5
+$as_echo "$as_me: WARNING: Serf version too old: need $serf_check_version" >&2;}
+ fi
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ fi
+ done
+ fi
+
+ fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking was serf enabled" >&5
+$as_echo_n "checking was serf enabled... " >&6; }
+ if test "$serf_found" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+ echo ""
+ echo "An appropriate version of serf could not be found, so libsvn_ra_serf"
+ echo "will not be built. If you want to build libsvn_ra_serf, please"
+ echo "install serf $serf_check_version or newer."
+ echo ""
+
+ if test "$serf_required" = "yes"; then
+ as_fn_error $? "Serf was explicitly enabled but an appropriate version was not found." "$LINENO" 5
+ fi
+ fi
+ fi
+
+ svn_lib_serf=$serf_found
+
+
+
+
+
+if test "$svn_lib_serf" = "yes"; then
+
+$as_echo "#define SVN_HAVE_SERF 1" >>confdefs.h
+
+fi
+
+
+ apr_memcache_found=no
+
+
+# Check whether --with-apr_memcache was given.
+if test "${with_apr_memcache+set}" = set; then :
+ withval=$with_apr_memcache;
+ if test "$withval" = "yes" ; then
+ as_fn_error $? "--with-apr_memcache requires an argument." "$LINENO" 5
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: looking for separate apr_memcache package" >&5
+$as_echo "$as_me: looking for separate apr_memcache package" >&6;}
+ apr_memcache_prefix=$withval
+ save_cppflags="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES -I$apr_memcache_prefix/include/apr_memcache-0"
+ ac_fn_c_check_header_mongrel "$LINENO" "apr_memcache.h" "ac_cv_header_apr_memcache_h" "$ac_includes_default"
+if test "x$ac_cv_header_apr_memcache_h" = xyes; then :
+
+ save_ldflags="$LDFLAGS"
+ LDFLAGS="$LDFLAGS -L$apr_memcache_prefix/lib"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_memcache_create in -lapr_memcache" >&5
+$as_echo_n "checking for apr_memcache_create in -lapr_memcache... " >&6; }
+if ${ac_cv_lib_apr_memcache_apr_memcache_create+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lapr_memcache $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char apr_memcache_create ();
+int
+main ()
+{
+return apr_memcache_create ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_apr_memcache_apr_memcache_create=yes
+else
+ ac_cv_lib_apr_memcache_apr_memcache_create=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_apr_memcache_apr_memcache_create" >&5
+$as_echo "$ac_cv_lib_apr_memcache_apr_memcache_create" >&6; }
+if test "x$ac_cv_lib_apr_memcache_apr_memcache_create" = xyes; then :
+ apr_memcache_found="standalone"
+fi
+
+ LDFLAGS="$save_ldflags"
+fi
+
+
+ CPPFLAGS="$save_cppflags"
+ fi
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: looking for apr_memcache as part of apr-util" >&5
+$as_echo "$as_me: looking for apr_memcache as part of apr-util" >&6;}
+ save_cppflags="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES"
+ ac_fn_c_check_header_mongrel "$LINENO" "apr_memcache.h" "ac_cv_header_apr_memcache_h" "$ac_includes_default"
+if test "x$ac_cv_header_apr_memcache_h" = xyes; then :
+
+ save_ldflags="$LDFLAGS"
+ LDFLAGS="$LDFLAGS $SVN_APRUTIL_LIBS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_memcache_create in -laprutil-1" >&5
+$as_echo_n "checking for apr_memcache_create in -laprutil-1... " >&6; }
+if ${ac_cv_lib_aprutil_1_apr_memcache_create+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-laprutil-1 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char apr_memcache_create ();
+int
+main ()
+{
+return apr_memcache_create ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_aprutil_1_apr_memcache_create=yes
+else
+ ac_cv_lib_aprutil_1_apr_memcache_create=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_aprutil_1_apr_memcache_create" >&5
+$as_echo "$ac_cv_lib_aprutil_1_apr_memcache_create" >&6; }
+if test "x$ac_cv_lib_aprutil_1_apr_memcache_create" = xyes; then :
+ apr_memcache_found="aprutil"
+fi
+
+ LDFLAGS="$save_ldflags"
+fi
+
+
+ CPPFLAGS="$save_cppflags"
+
+fi
+
+
+
+ if test $apr_memcache_found = "standalone"; then
+ SVN_APR_MEMCACHE_INCLUDES="-I$apr_memcache_prefix/include/apr_memcache-0"
+ SVN_APR_MEMCACHE_LIBS="$apr_memcache_prefix/lib/libapr_memcache.la"
+ svn_lib_apr_memcache=yes
+ elif test $apr_memcache_found = "aprutil"; then
+ SVN_APR_MEMCACHE_INCLUDES=""
+ SVN_APR_MEMCACHE_LIBS=""
+ svn_lib_apr_memcache=yes
+ elif test $apr_memcache_found = "reconfig"; then
+ svn_lib_apr_memcache=yes
+ else
+ svn_lib_apr_memcache=no
+ fi
+
+
+
+
+
+if test "$svn_lib_apr_memcache" = "yes"; then
+
+$as_echo "#define SVN_HAVE_MEMCACHE 1" >>confdefs.h
+
+fi
+
+
+
+
+
+HTTPD_WANTED_MMN="20020903"
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Apache module support via DSO through APXS" >&5
+$as_echo_n "checking for Apache module support via DSO through APXS... " >&6; }
+
+# Check whether --with-apxs was given.
+if test "${with_apxs+set}" = set; then :
+ withval=$with_apxs;
+ if test "$withval" = "yes"; then
+ APXS=apxs
+ else
+ APXS="$withval"
+ fi
+ APXS_EXPLICIT=1
+
+fi
+
+
+if test -z "$APXS"; then
+ for i in /usr/sbin /usr/local/apache/bin /usr/local/apache2/bin /usr/bin ; do
+ if test -f "$i/apxs2"; then
+ APXS="$i/apxs2"
+ break
+ fi
+ if test -f "$i/apxs"; then
+ APXS="$i/apxs"
+ break
+ fi
+ done
+fi
+
+if test -n "$APXS" && test "$APXS" != "no"; then
+ APXS_INCLUDE="`$APXS -q INCLUDEDIR`"
+ if test -r $APXS_INCLUDE/mod_dav.h; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: found at $APXS" >&5
+$as_echo "found at $APXS" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking httpd version" >&5
+$as_echo_n "checking httpd version... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include "$APXS_INCLUDE/ap_mmn.h"
+#if AP_MODULE_MAGIC_AT_LEAST($HTTPD_WANTED_MMN,0)
+VERSION_OKAY
+#endif
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "VERSION_OKAY" >/dev/null 2>&1; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: recent enough" >&5
+$as_echo "recent enough" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: apache too old: mmn must be at least $HTTPD_WANTED_MMN" >&5
+$as_echo "apache too old: mmn must be at least $HTTPD_WANTED_MMN" >&6; }
+ if test "$APXS_EXPLICIT" != ""; then
+ as_fn_error $? "Apache APXS build explicitly requested, but apache version is too old" "$LINENO" 5
+ fi
+ APXS=""
+
+fi
+rm -f conftest*
+
+
+ elif test "$APXS_EXPLICIT" != ""; then
+ as_fn_error $? "no - APXS refers to an old version of Apache
+ Unable to locate $APXS_INCLUDE/mod_dav.h" "$LINENO" 5
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no - Unable to locate $APXS_INCLUDE/mod_dav.h" >&5
+$as_echo "no - Unable to locate $APXS_INCLUDE/mod_dav.h" >&6; }
+ APXS=""
+ fi
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+if test -n "$APXS" && test "$APXS" != "no"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether Apache version is compatible with APR version" >&5
+$as_echo_n "checking whether Apache version is compatible with APR version... " >&6; }
+ apr_major_version="${apr_version%%.*}"
+ case "$apr_major_version" in
+ 0)
+ apache_minor_version_wanted_regex="0"
+ ;;
+ 1)
+ apache_minor_version_wanted_regex="[1-5]"
+ ;;
+ 2)
+ apache_minor_version_wanted_regex="[3-5]"
+ ;;
+ *)
+ as_fn_error $? "unknown APR version" "$LINENO" 5
+ ;;
+ esac
+ old_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include "$APXS_INCLUDE/ap_release.h"
+apache_minor_version=AP_SERVER_MINORVERSION
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "apache_minor_version= *\"$apache_minor_version_wanted_regex\"" >/dev/null 2>&1; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ as_fn_error $? "Apache version incompatible with APR version" "$LINENO" 5
+fi
+rm -f conftest*
+
+ CPPFLAGS="$old_CPPFLAGS"
+fi
+
+
+# Check whether --with-apache-libexecdir was given.
+if test "${with_apache_libexecdir+set}" = set; then :
+ withval=$with_apache_libexecdir; APACHE_LIBEXECDIR="$withval"
+else
+ APACHE_LIBEXECDIR='no'
+fi
+
+
+INSTALL_APACHE_MODS=false
+if test -n "$APXS" && test "$APXS" != "no"; then
+ APXS_CC="`$APXS -q CC`"
+ APACHE_INCLUDES="$APACHE_INCLUDES -I$APXS_INCLUDE"
+
+ if test "$APACHE_LIBEXECDIR" = 'no'; then
+ APACHE_LIBEXECDIR="$libexecdir"
+ elif test "$APACHE_LIBEXECDIR" = 'yes'; then
+ APACHE_LIBEXECDIR="`$APXS -q libexecdir`"
+ fi
+
+ BUILD_APACHE_RULE=apache-mod
+ INSTALL_APACHE_RULE=install-mods-shared
+ INSTALL_APACHE_MODS=true
+
+ case $host in
+ *-*-cygwin*)
+ APACHE_LDFLAGS="-shrext .so"
+ ;;
+ esac
+elif test x"$APXS" != x"no"; then
+ echo "=================================================================="
+ echo "WARNING: skipping the build of mod_dav_svn"
+ echo " try using --with-apxs"
+ echo "=================================================================="
+fi
+
+
+
+
+
+
+
+# there aren't any flags that interest us ...
+#if test -n "$APXS" && test "$APXS" != "no"; then
+# CFLAGS="$CFLAGS `$APXS -q CFLAGS CFLAGS_SHLIB`"
+#fi
+
+if test -n "$APXS_CC" && test "$APXS_CC" != "$CC" ; then
+ echo "=================================================================="
+ echo "WARNING: You have chosen to compile Subversion with a different"
+ echo " compiler than the one used to compile Apache."
+ echo ""
+ echo " Current compiler: $CC"
+ echo " Apache's compiler: $APXS_CC"
+ echo ""
+ echo "This could cause some problems."
+ echo "=================================================================="
+fi
+
+
+
+SQLITE_MINIMUM_VER="3.7.12"
+SQLITE_RECOMMENDED_VER="3.7.15.1"
+SQLITE_URL="http://www.sqlite.org/sqlite-amalgamation-$(printf %d%02d%02d%02d $(echo ${SQLITE_RECOMMENDED_VER} | sed -e 's/\./ /g')).zip"
+
+
+ SQLITE_MINIMUM_VER="${SQLITE_MINIMUM_VER}"
+ SQLITE_RECOMMENDED_VER="${SQLITE_RECOMMENDED_VER}"
+ SQLITE_URL="${SQLITE_URL}"
+ SQLITE_PKGNAME="sqlite3"
+
+
+
+ version_string="$SQLITE_MINIMUM_VER"
+
+ major=`expr $version_string : '\([0-9]*\)'`
+ minor=`expr $version_string : '[0-9]*\.\([0-9]*\)'`
+ micro=`expr $version_string : '[0-9]*\.[0-9]*\.\([0-9]*\)'`
+ if test -z "$micro"; then
+ micro=0
+ fi
+ sqlite_min_ver_num=`expr $major \* 1000000 \
+ \+ $minor \* 1000 \
+ \+ $micro`
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite library" >&5
+$as_echo "$as_me: checking sqlite library" >&6;}
+
+
+# Check whether --with-sqlite was given.
+if test "${with_sqlite+set}" = set; then :
+ withval=$with_sqlite;
+ if test "$withval" = "yes" ; then
+ as_fn_error $? "--with-sqlite requires an argument." "$LINENO" 5
+ else
+ sqlite_dir="$withval"
+ fi
+
+ if test -d $sqlite_dir; then
+
+ if test -z "$sqlite_dir"; then
+ sqlite_dir=""
+ sqlite_include="sqlite3.h"
+ else
+ sqlite_dir="$sqlite_dir"
+ sqlite_include="$sqlite_dir/include/sqlite3.h"
+ fi
+
+ save_CPPFLAGS="$CPPFLAGS"
+ save_LDFLAGS="$LDFLAGS"
+
+ if test ! -z "$sqlite_dir"; then
+ CPPFLAGS="$CPPFLAGS -I$sqlite_dir/include"
+ LDFLAGS="$LDFLAGS -L$sqlite_dir/lib"
+ fi
+
+ ac_fn_c_check_header_mongrel "$LINENO" "sqlite3.h" "ac_cv_header_sqlite3_h" "$ac_includes_default"
+if test "x$ac_cv_header_sqlite3_h" = xyes; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite library version (via header)" >&5
+$as_echo_n "checking sqlite library version (via header)... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include "$sqlite_include"
+#if SQLITE_VERSION_NUMBER >= $sqlite_min_ver_num
+SQLITE_VERSION_OKAY
+#endif
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "SQLITE_VERSION_OKAY" >/dev/null 2>&1; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: okay" >&5
+$as_echo "okay" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_close in -lsqlite3" >&5
+$as_echo_n "checking for sqlite3_close in -lsqlite3... " >&6; }
+if ${ac_cv_lib_sqlite3_sqlite3_close+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsqlite3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char sqlite3_close ();
+int
+main ()
+{
+return sqlite3_close ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_sqlite3_sqlite3_close=yes
+else
+ ac_cv_lib_sqlite3_sqlite3_close=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sqlite3_sqlite3_close" >&5
+$as_echo "$ac_cv_lib_sqlite3_sqlite3_close" >&6; }
+if test "x$ac_cv_lib_sqlite3_sqlite3_close" = xyes; then :
+
+ svn_lib_sqlite="yes"
+ if test -z "$sqlite_dir" -o ! -d "$sqlite_dir"; then
+ SVN_SQLITE_LIBS="-lsqlite3"
+ else
+ SVN_SQLITE_INCLUDES="-I$sqlite_dir/include"
+ SVN_SQLITE_LIBS="`
+ input_flags="-L$sqlite_dir/lib -lsqlite3"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+ fi
+
+fi
+
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported SQLite version" >&5
+$as_echo "unsupported SQLite version" >&6; }
+fi
+rm -f conftest*
+
+
+fi
+
+
+
+ CPPFLAGS="$save_CPPFLAGS"
+ LDFLAGS="$save_LDFLAGS"
+
+ else
+
+ sqlite_amalg="$sqlite_dir"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite amalgamation" >&5
+$as_echo_n "checking sqlite amalgamation... " >&6; }
+ if test ! -e $sqlite_amalg; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite amalgamation file version" >&5
+$as_echo_n "checking sqlite amalgamation file version... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include "$sqlite_amalg"
+#if SQLITE_VERSION_NUMBER >= $sqlite_min_ver_num
+SQLITE_VERSION_OKAY
+#endif
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "SQLITE_VERSION_OKAY" >/dev/null 2>&1; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: amalgamation found and is okay" >&5
+$as_echo "amalgamation found and is okay" >&6; }
+
+ case $host_os in
+ beos* | mingw* | pw32* | cegcc* | cygwin*)
+ svn_sqlite_dso_ldflags=
+ ;;
+
+ darwin*)
+ # if libdl is installed we need to link against it
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+$as_echo_n "checking for dlopen in -ldl... " >&6; }
+if ${ac_cv_lib_dl_dlopen+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dl_dlopen=yes
+else
+ ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+$as_echo "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes; then :
+ lt_cv_dlopen="dlopen" svn_sqlite_dso_ldflags="-ldl"
+else
+
+ svn_sqlite_dso_ldflags=
+
+fi
+
+ ;;
+
+ *)
+ ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load"
+if test "x$ac_cv_func_shl_load" = xyes; then :
+ svn_sqlite_dso_ldflags=
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5
+$as_echo_n "checking for shl_load in -ldld... " >&6; }
+if ${ac_cv_lib_dld_shl_load+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char shl_load ();
+int
+main ()
+{
+return shl_load ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dld_shl_load=yes
+else
+ ac_cv_lib_dld_shl_load=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5
+$as_echo "$ac_cv_lib_dld_shl_load" >&6; }
+if test "x$ac_cv_lib_dld_shl_load" = xyes; then :
+ svn_sqlite_dso_ldflags="-ldld"
+else
+ ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen"
+if test "x$ac_cv_func_dlopen" = xyes; then :
+ svn_sqlite_dso_ldflags=
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+$as_echo_n "checking for dlopen in -ldl... " >&6; }
+if ${ac_cv_lib_dl_dlopen+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dl_dlopen=yes
+else
+ ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+$as_echo "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes; then :
+ svn_sqlite_dso_ldflags="-ldl"
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5
+$as_echo_n "checking for dlopen in -lsvld... " >&6; }
+if ${ac_cv_lib_svld_dlopen+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsvld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_svld_dlopen=yes
+else
+ ac_cv_lib_svld_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5
+$as_echo "$ac_cv_lib_svld_dlopen" >&6; }
+if test "x$ac_cv_lib_svld_dlopen" = xyes; then :
+ svn_sqlite_dso_ldflags="-lsvld"
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5
+$as_echo_n "checking for dld_link in -ldld... " >&6; }
+if ${ac_cv_lib_dld_dld_link+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dld_link ();
+int
+main ()
+{
+return dld_link ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dld_dld_link=yes
+else
+ ac_cv_lib_dld_dld_link=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5
+$as_echo "$ac_cv_lib_dld_dld_link" >&6; }
+if test "x$ac_cv_lib_dld_dld_link" = xyes; then :
+ svn_sqlite_dso_ldflags="-ldld"
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+ ;;
+ esac
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking additional libraries for sqlite" >&5
+$as_echo_n "checking additional libraries for sqlite... " >&6; }
+ if test -n "$svn_sqlite_dso_ldflags"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${svn_sqlite_dso_ldflags}" >&5
+$as_echo "${svn_sqlite_dso_ldflags}" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5
+$as_echo "none" >&6; }
+ fi
+
+
+$as_echo "#define SVN_SQLITE_INLINE 1" >>confdefs.h
+
+ SVN_SQLITE_INCLUDES="-I`dirname $sqlite_amalg`"
+ if test -n "$svn_sqlite_dso_ldflags"; then
+ SVN_SQLITE_LIBS="$svn_sqlite_dso_ldflags -lpthread"
+ else
+ SVN_SQLITE_LIBS="-lpthread"
+ fi
+ svn_lib_sqlite="yes"
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported amalgamation SQLite version" >&5
+$as_echo "unsupported amalgamation SQLite version" >&6; }
+fi
+rm -f conftest*
+
+ fi
+
+ fi
+
+ if test -z "$svn_lib_sqlite"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: no suitable sqlite found in $sqlite_dir" >&5
+$as_echo "$as_me: WARNING: no suitable sqlite found in $sqlite_dir" >&2;}
+
+ echo ""
+ echo "An appropriate version of sqlite could not be found. We recommmend"
+ echo "${SQLITE_RECOMMENDED_VER}, but require at least ${SQLITE_MINIMUM_VER}."
+ echo "Please either install a newer sqlite on this system"
+ echo ""
+ echo "or"
+ echo ""
+ echo "get the sqlite ${SQLITE_RECOMMENDED_VER} amalgamation from:"
+ echo " ${SQLITE_URL}"
+ echo "unpack the archive using unzip and rename the resulting"
+ echo "directory to:"
+ echo "$abs_srcdir/sqlite-amalgamation"
+ echo ""
+ as_fn_error $? "Subversion requires SQLite" "$LINENO" 5
+
+ fi
+
+else
+
+
+ sqlite_amalg="$abs_srcdir/sqlite-amalgamation/sqlite3.c"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite amalgamation" >&5
+$as_echo_n "checking sqlite amalgamation... " >&6; }
+ if test ! -e $sqlite_amalg; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite amalgamation file version" >&5
+$as_echo_n "checking sqlite amalgamation file version... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include "$sqlite_amalg"
+#if SQLITE_VERSION_NUMBER >= $sqlite_min_ver_num
+SQLITE_VERSION_OKAY
+#endif
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "SQLITE_VERSION_OKAY" >/dev/null 2>&1; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: amalgamation found and is okay" >&5
+$as_echo "amalgamation found and is okay" >&6; }
+
+ case $host_os in
+ beos* | mingw* | pw32* | cegcc* | cygwin*)
+ svn_sqlite_dso_ldflags=
+ ;;
+
+ darwin*)
+ # if libdl is installed we need to link against it
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+$as_echo_n "checking for dlopen in -ldl... " >&6; }
+if ${ac_cv_lib_dl_dlopen+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dl_dlopen=yes
+else
+ ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+$as_echo "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes; then :
+ lt_cv_dlopen="dlopen" svn_sqlite_dso_ldflags="-ldl"
+else
+
+ svn_sqlite_dso_ldflags=
+
+fi
+
+ ;;
+
+ *)
+ ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load"
+if test "x$ac_cv_func_shl_load" = xyes; then :
+ svn_sqlite_dso_ldflags=
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5
+$as_echo_n "checking for shl_load in -ldld... " >&6; }
+if ${ac_cv_lib_dld_shl_load+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char shl_load ();
+int
+main ()
+{
+return shl_load ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dld_shl_load=yes
+else
+ ac_cv_lib_dld_shl_load=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5
+$as_echo "$ac_cv_lib_dld_shl_load" >&6; }
+if test "x$ac_cv_lib_dld_shl_load" = xyes; then :
+ svn_sqlite_dso_ldflags="-ldld"
+else
+ ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen"
+if test "x$ac_cv_func_dlopen" = xyes; then :
+ svn_sqlite_dso_ldflags=
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+$as_echo_n "checking for dlopen in -ldl... " >&6; }
+if ${ac_cv_lib_dl_dlopen+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dl_dlopen=yes
+else
+ ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+$as_echo "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes; then :
+ svn_sqlite_dso_ldflags="-ldl"
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5
+$as_echo_n "checking for dlopen in -lsvld... " >&6; }
+if ${ac_cv_lib_svld_dlopen+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsvld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_svld_dlopen=yes
+else
+ ac_cv_lib_svld_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5
+$as_echo "$ac_cv_lib_svld_dlopen" >&6; }
+if test "x$ac_cv_lib_svld_dlopen" = xyes; then :
+ svn_sqlite_dso_ldflags="-lsvld"
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5
+$as_echo_n "checking for dld_link in -ldld... " >&6; }
+if ${ac_cv_lib_dld_dld_link+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dld_link ();
+int
+main ()
+{
+return dld_link ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dld_dld_link=yes
+else
+ ac_cv_lib_dld_dld_link=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5
+$as_echo "$ac_cv_lib_dld_dld_link" >&6; }
+if test "x$ac_cv_lib_dld_dld_link" = xyes; then :
+ svn_sqlite_dso_ldflags="-ldld"
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+ ;;
+ esac
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking additional libraries for sqlite" >&5
+$as_echo_n "checking additional libraries for sqlite... " >&6; }
+ if test -n "$svn_sqlite_dso_ldflags"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${svn_sqlite_dso_ldflags}" >&5
+$as_echo "${svn_sqlite_dso_ldflags}" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5
+$as_echo "none" >&6; }
+ fi
+
+
+$as_echo "#define SVN_SQLITE_INLINE 1" >>confdefs.h
+
+ SVN_SQLITE_INCLUDES="-I`dirname $sqlite_amalg`"
+ if test -n "$svn_sqlite_dso_ldflags"; then
+ SVN_SQLITE_LIBS="$svn_sqlite_dso_ldflags -lpthread"
+ else
+ SVN_SQLITE_LIBS="-lpthread"
+ fi
+ svn_lib_sqlite="yes"
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported amalgamation SQLite version" >&5
+$as_echo "unsupported amalgamation SQLite version" >&6; }
+fi
+rm -f conftest*
+
+ fi
+
+
+ if test -z "$svn_lib_sqlite"; then
+
+ if test -z ""; then
+ sqlite_dir=""
+ sqlite_include="sqlite3.h"
+ else
+ sqlite_dir=""
+ sqlite_include="/include/sqlite3.h"
+ fi
+
+ save_CPPFLAGS="$CPPFLAGS"
+ save_LDFLAGS="$LDFLAGS"
+
+ if test ! -z ""; then
+ CPPFLAGS="$CPPFLAGS -I$sqlite_dir/include"
+ LDFLAGS="$LDFLAGS -L$sqlite_dir/lib"
+ fi
+
+ ac_fn_c_check_header_mongrel "$LINENO" "sqlite3.h" "ac_cv_header_sqlite3_h" "$ac_includes_default"
+if test "x$ac_cv_header_sqlite3_h" = xyes; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite library version (via header)" >&5
+$as_echo_n "checking sqlite library version (via header)... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include "$sqlite_include"
+#if SQLITE_VERSION_NUMBER >= $sqlite_min_ver_num
+SQLITE_VERSION_OKAY
+#endif
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "SQLITE_VERSION_OKAY" >/dev/null 2>&1; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: okay" >&5
+$as_echo "okay" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite3_close in -lsqlite3" >&5
+$as_echo_n "checking for sqlite3_close in -lsqlite3... " >&6; }
+if ${ac_cv_lib_sqlite3_sqlite3_close+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsqlite3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char sqlite3_close ();
+int
+main ()
+{
+return sqlite3_close ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_sqlite3_sqlite3_close=yes
+else
+ ac_cv_lib_sqlite3_sqlite3_close=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sqlite3_sqlite3_close" >&5
+$as_echo "$ac_cv_lib_sqlite3_sqlite3_close" >&6; }
+if test "x$ac_cv_lib_sqlite3_sqlite3_close" = xyes; then :
+
+ svn_lib_sqlite="yes"
+ if test -z "$sqlite_dir" -o ! -d "$sqlite_dir"; then
+ SVN_SQLITE_LIBS="-lsqlite3"
+ else
+ SVN_SQLITE_INCLUDES="-I$sqlite_dir/include"
+ SVN_SQLITE_LIBS="`
+ input_flags="-L$sqlite_dir/lib -lsqlite3"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+ fi
+
+fi
+
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported SQLite version" >&5
+$as_echo "unsupported SQLite version" >&6; }
+fi
+rm -f conftest*
+
+
+fi
+
+
+
+ CPPFLAGS="$save_CPPFLAGS"
+ LDFLAGS="$save_LDFLAGS"
+
+ fi
+
+ if test -z "$svn_lib_sqlite"; then
+
+ if test -n "$PKG_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking sqlite library version (via pkg-config)" >&5
+$as_echo_n "checking sqlite library version (via pkg-config)... " >&6; }
+ sqlite_version=`$PKG_CONFIG $SQLITE_PKGNAME --modversion --silence-errors`
+
+ if test -n "$sqlite_version"; then
+
+ version_string="$sqlite_version"
+
+ major=`expr $version_string : '\([0-9]*\)'`
+ minor=`expr $version_string : '[0-9]*\.\([0-9]*\)'`
+ micro=`expr $version_string : '[0-9]*\.[0-9]*\.\([0-9]*\)'`
+ if test -z "$micro"; then
+ micro=0
+ fi
+ sqlite_ver_num=`expr $major \* 1000000 \
+ \+ $minor \* 1000 \
+ \+ $micro`
+
+
+ if test "$sqlite_ver_num" -ge "$sqlite_min_ver_num"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $sqlite_version" >&5
+$as_echo "$sqlite_version" >&6; }
+ svn_lib_sqlite="yes"
+ SVN_SQLITE_INCLUDES="`$PKG_CONFIG $SQLITE_PKGNAME --cflags`"
+ SVN_SQLITE_LIBS="`$PKG_CONFIG $SQLITE_PKGNAME --libs`"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none or unsupported $sqlite_version" >&5
+$as_echo "none or unsupported $sqlite_version" >&6; }
+ fi
+ fi
+ fi
+
+ if test -z "$svn_lib_sqlite"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ fi
+
+ fi
+
+ if test -z "$svn_lib_sqlite"; then
+
+ echo ""
+ echo "An appropriate version of sqlite could not be found. We recommmend"
+ echo "${SQLITE_RECOMMENDED_VER}, but require at least ${SQLITE_MINIMUM_VER}."
+ echo "Please either install a newer sqlite on this system"
+ echo ""
+ echo "or"
+ echo ""
+ echo "get the sqlite ${SQLITE_RECOMMENDED_VER} amalgamation from:"
+ echo " ${SQLITE_URL}"
+ echo "unpack the archive using unzip and rename the resulting"
+ echo "directory to:"
+ echo "$abs_srcdir/sqlite-amalgamation"
+ echo ""
+ as_fn_error $? "Subversion requires SQLite" "$LINENO" 5
+
+ fi
+
+fi
+
+
+
+
+
+
+# Check whether --enable-sqlite-compatibility-version was given.
+if test "${enable_sqlite_compatibility_version+set}" = set; then :
+ enableval=$enable_sqlite_compatibility_version; sqlite_compat_ver=$enableval
+else
+ sqlite_compat_ver=no
+fi
+
+
+if test -n "$sqlite_compat_ver" && test "$sqlite_compat_ver" != no; then
+
+ version_string="$sqlite_compat_ver"
+
+ major=`expr $version_string : '\([0-9]*\)'`
+ minor=`expr $version_string : '[0-9]*\.\([0-9]*\)'`
+ micro=`expr $version_string : '[0-9]*\.[0-9]*\.\([0-9]*\)'`
+ if test -z "$micro"; then
+ micro=0
+ fi
+ sqlite_compat_ver_num=`expr $major \* 1000000 \
+ \+ $minor \* 1000 \
+ \+ $micro`
+
+ CFLAGS="-DSVN_SQLITE_MIN_VERSION='\"$sqlite_compat_ver\"' $CFLAGS"
+ CFLAGS="-DSVN_SQLITE_MIN_VERSION_NUMBER=$sqlite_compat_ver_num $CFLAGS"
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the compiler provides atomic builtins" >&5
+$as_echo_n "checking whether the compiler provides atomic builtins... " >&6; }
+if ${svn_cv_atomic_builtins+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test "$cross_compiling" = yes; then :
+ svn_cv_atomic_builtins=no
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ int main()
+ {
+ unsigned long long val = 1010, tmp, *mem = &val;
+
+ if (__sync_fetch_and_add(&val, 1010) != 1010 || val != 2020)
+ return 1;
+
+ tmp = val;
+
+ if (__sync_fetch_and_sub(mem, 1010) != tmp || val != 1010)
+ return 1;
+
+ if (__sync_sub_and_fetch(&val, 1010) != 0 || val != 0)
+ return 1;
+
+ tmp = 3030;
+
+ if (__sync_val_compare_and_swap(mem, 0, tmp) != 0 || val != tmp)
+ return 1;
+
+ if (__sync_lock_test_and_set(&val, 4040) != 3030)
+ return 1;
+
+ mem = &tmp;
+
+ if (__sync_val_compare_and_swap(&mem, &tmp, &val) != &tmp)
+ return 1;
+
+ __sync_synchronize();
+
+ if (mem != &val)
+ return 1;
+
+ return 0;
+ }
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ svn_cv_atomic_builtins=yes
+else
+ svn_cv_atomic_builtins=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_atomic_builtins" >&5
+$as_echo "$svn_cv_atomic_builtins" >&6; }
+
+
+if test "$svn_cv_atomic_builtins" = "yes"; then
+
+$as_echo "#define SVN_HAS_ATOMIC_BUILTINS 1" >>confdefs.h
+
+fi
+
+
+if test "${bindir}" = '${exec_prefix}/bin'; then
+ if test "${exec_prefix}" = "NONE"; then
+ if test "${prefix}" = "NONE"; then
+ SVN_BINDIR="${ac_default_prefix}/bin"
+ else
+ SVN_BINDIR="${prefix}/bin"
+ fi
+ else
+ SVN_BINDIR="${exec_prefix}/bin"
+ fi
+else
+ SVN_BINDIR="${bindir}"
+fi
+
+SVN_BINDIR="`eval echo ${SVN_BINDIR}`"
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_BINDIR "${SVN_BINDIR}"
+_ACEOF
+
+
+localedir='${datadir}/locale'
+
+
+if test "${datadir}" = '${prefix}/share' && test "${prefix}" = "NONE"; then
+ exp_localedir='${ac_default_prefix}/share/locale'
+else
+ exp_localedir=$localedir
+fi
+
+svn_last=
+svn_cur=""${exp_localedir}""
+while test "x${svn_cur}" != "x${svn_last}";
+do
+ svn_last="${svn_cur}"
+ svn_cur=`eval "echo ${svn_cur}"`
+done
+svn_localedir="${svn_cur}"
+
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_LOCALE_DIR "${svn_localedir}"
+_ACEOF
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: configuring libtool now" >&5
+$as_echo "$as_me: configuring libtool now" >&6;}
+case `pwd` in
+ *\ * | *\ *)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5
+$as_echo "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;;
+esac
+
+
+
+macro_version='2.4.2'
+macro_revision='1.3337'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ltmain="$ac_aux_dir/ltmain.sh"
+
+# Backslashify metacharacters that are still active within
+# double-quoted strings.
+sed_quote_subst='s/\(["`$\\]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\(["`\\]\)/\\\1/g'
+
+# Sed substitution to delay expansion of an escaped shell variable in a
+# double_quote_subst'ed string.
+delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g'
+
+# Sed substitution to delay expansion of an escaped single quote.
+delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g'
+
+# Sed substitution to avoid accidental globbing in evaled expressions
+no_glob_subst='s/\*/\\\*/g'
+
+ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5
+$as_echo_n "checking how to print strings... " >&6; }
+# Test print first, because it will be a builtin if present.
+if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \
+ test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then
+ ECHO='print -r --'
+elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then
+ ECHO='printf %s\n'
+else
+ # Use this function as a fallback that always works.
+ func_fallback_echo ()
+ {
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+ }
+ ECHO='func_fallback_echo'
+fi
+
+# func_echo_all arg...
+# Invoke $ECHO with all args, space-separated.
+func_echo_all ()
+{
+ $ECHO ""
+}
+
+case "$ECHO" in
+ printf*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: printf" >&5
+$as_echo "printf" >&6; } ;;
+ print*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: print -r" >&5
+$as_echo "print -r" >&6; } ;;
+ *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: cat" >&5
+$as_echo "cat" >&6; } ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5
+$as_echo_n "checking for a sed that does not truncate output... " >&6; }
+if ${ac_cv_path_SED+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/
+ for ac_i in 1 2 3 4 5 6 7; do
+ ac_script="$ac_script$as_nl$ac_script"
+ done
+ echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed
+ { ac_script=; unset ac_script;}
+ if test -z "$SED"; then
+ ac_path_SED_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in sed gsed; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_SED="$as_dir/$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_SED" || continue
+# Check for GNU ac_path_SED and select it if it is found.
+ # Check for GNU $ac_path_SED
+case `"$ac_path_SED" --version 2>&1` in
+*GNU*)
+ ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;;
+*)
+ ac_count=0
+ $as_echo_n 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ $as_echo '' >> "conftest.nl"
+ "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_SED_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_SED="$ac_path_SED"
+ ac_path_SED_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_SED_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_SED"; then
+ as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5
+ fi
+else
+ ac_cv_path_SED=$SED
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5
+$as_echo "$ac_cv_path_SED" >&6; }
+ SED="$ac_cv_path_SED"
+ rm -f conftest.sed
+
+test -z "$SED" && SED=sed
+Xsed="$SED -e 1s/^X//"
+
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5
+$as_echo_n "checking for fgrep... " >&6; }
+if ${ac_cv_path_FGREP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1
+ then ac_cv_path_FGREP="$GREP -F"
+ else
+ if test -z "$FGREP"; then
+ ac_path_FGREP_found=false
+ # Loop through the user's path and test for each of PROGNAME-LIST
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_prog in fgrep; do
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ ac_path_FGREP="$as_dir/$ac_prog$ac_exec_ext"
+ as_fn_executable_p "$ac_path_FGREP" || continue
+# Check for GNU ac_path_FGREP and select it if it is found.
+ # Check for GNU $ac_path_FGREP
+case `"$ac_path_FGREP" --version 2>&1` in
+*GNU*)
+ ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;;
+*)
+ ac_count=0
+ $as_echo_n 0123456789 >"conftest.in"
+ while :
+ do
+ cat "conftest.in" "conftest.in" >"conftest.tmp"
+ mv "conftest.tmp" "conftest.in"
+ cp "conftest.in" "conftest.nl"
+ $as_echo 'FGREP' >> "conftest.nl"
+ "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break
+ diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+ as_fn_arith $ac_count + 1 && ac_count=$as_val
+ if test $ac_count -gt ${ac_path_FGREP_max-0}; then
+ # Best one so far, save it but keep looking for a better one
+ ac_cv_path_FGREP="$ac_path_FGREP"
+ ac_path_FGREP_max=$ac_count
+ fi
+ # 10*(2^10) chars as input seems more than enough
+ test $ac_count -gt 10 && break
+ done
+ rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+ $ac_path_FGREP_found && break 3
+ done
+ done
+ done
+IFS=$as_save_IFS
+ if test -z "$ac_cv_path_FGREP"; then
+ as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+ fi
+else
+ ac_cv_path_FGREP=$FGREP
+fi
+
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5
+$as_echo "$ac_cv_path_FGREP" >&6; }
+ FGREP="$ac_cv_path_FGREP"
+
+
+test -z "$GREP" && GREP=grep
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Check whether --with-gnu-ld was given.
+if test "${with_gnu_ld+set}" = set; then :
+ withval=$with_gnu_ld; test "$withval" = no || with_gnu_ld=yes
+else
+ with_gnu_ld=no
+fi
+
+ac_prog=ld
+if test "$GCC" = yes; then
+ # Check if gcc -print-prog-name=ld gives a path.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5
+$as_echo_n "checking for ld used by $CC... " >&6; }
+ case $host in
+ *-*-mingw*)
+ # gcc leaves a trailing carriage return which upsets mingw
+ ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+ *)
+ ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+ esac
+ case $ac_prog in
+ # Accept absolute paths.
+ [\\/]* | ?:[\\/]*)
+ re_direlt='/[^/][^/]*/\.\./'
+ # Canonicalize the pathname of ld
+ ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'`
+ while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do
+ ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"`
+ done
+ test -z "$LD" && LD="$ac_prog"
+ ;;
+ "")
+ # If it fails, then pretend we aren't using GCC.
+ ac_prog=ld
+ ;;
+ *)
+ # If it is relative, then search for the first ld in PATH.
+ with_gnu_ld=unknown
+ ;;
+ esac
+elif test "$with_gnu_ld" = yes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5
+$as_echo_n "checking for GNU ld... " >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5
+$as_echo_n "checking for non-GNU ld... " >&6; }
+fi
+if ${lt_cv_path_LD+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -z "$LD"; then
+ lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH; do
+ IFS="$lt_save_ifs"
+ test -z "$ac_dir" && ac_dir=.
+ if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+ lt_cv_path_LD="$ac_dir/$ac_prog"
+ # Check to see if the program is GNU ld. I'd rather use --version,
+ # but apparently some variants of GNU ld only accept -v.
+ # Break only if it was the GNU/non-GNU ld that we prefer.
+ case `"$lt_cv_path_LD" -v 2>&1 </dev/null` in
+ *GNU* | *'with BFD'*)
+ test "$with_gnu_ld" != no && break
+ ;;
+ *)
+ test "$with_gnu_ld" != yes && break
+ ;;
+ esac
+ fi
+ done
+ IFS="$lt_save_ifs"
+else
+ lt_cv_path_LD="$LD" # Let the user override the test with a path.
+fi
+fi
+
+LD="$lt_cv_path_LD"
+if test -n "$LD"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LD" >&5
+$as_echo "$LD" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5
+$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; }
+if ${lt_cv_prog_gnu_ld+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ # I'd rather use --version here, but apparently some GNU lds only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+ lt_cv_prog_gnu_ld=yes
+ ;;
+*)
+ lt_cv_prog_gnu_ld=no
+ ;;
+esac
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_gnu_ld" >&5
+$as_echo "$lt_cv_prog_gnu_ld" >&6; }
+with_gnu_ld=$lt_cv_prog_gnu_ld
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5
+$as_echo_n "checking for BSD- or MS-compatible name lister (nm)... " >&6; }
+if ${lt_cv_path_NM+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$NM"; then
+ # Let the user override the test.
+ lt_cv_path_NM="$NM"
+else
+ lt_nm_to_check="${ac_tool_prefix}nm"
+ if test -n "$ac_tool_prefix" && test "$build" = "$host"; then
+ lt_nm_to_check="$lt_nm_to_check nm"
+ fi
+ for lt_tmp_nm in $lt_nm_to_check; do
+ lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do
+ IFS="$lt_save_ifs"
+ test -z "$ac_dir" && ac_dir=.
+ tmp_nm="$ac_dir/$lt_tmp_nm"
+ if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then
+ # Check to see if the nm accepts a BSD-compat flag.
+ # Adding the `sed 1q' prevents false positives on HP-UX, which says:
+ # nm: unknown option "B" ignored
+ # Tru64's nm complains that /dev/null is an invalid object file
+ case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in
+ */dev/null* | *'Invalid file or object type'*)
+ lt_cv_path_NM="$tmp_nm -B"
+ break
+ ;;
+ *)
+ case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in
+ */dev/null*)
+ lt_cv_path_NM="$tmp_nm -p"
+ break
+ ;;
+ *)
+ lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but
+ continue # so that we can try to find one that supports BSD flags
+ ;;
+ esac
+ ;;
+ esac
+ fi
+ done
+ IFS="$lt_save_ifs"
+ done
+ : ${lt_cv_path_NM=no}
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5
+$as_echo "$lt_cv_path_NM" >&6; }
+if test "$lt_cv_path_NM" != "no"; then
+ NM="$lt_cv_path_NM"
+else
+ # Didn't find any BSD compatible name lister, look for dumpbin.
+ if test -n "$DUMPBIN"; then :
+ # Let the user override the test.
+ else
+ if test -n "$ac_tool_prefix"; then
+ for ac_prog in dumpbin "link -dump"
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_DUMPBIN+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$DUMPBIN"; then
+ ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+DUMPBIN=$ac_cv_prog_DUMPBIN
+if test -n "$DUMPBIN"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5
+$as_echo "$DUMPBIN" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$DUMPBIN" && break
+ done
+fi
+if test -z "$DUMPBIN"; then
+ ac_ct_DUMPBIN=$DUMPBIN
+ for ac_prog in dumpbin "link -dump"
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_DUMPBIN+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_DUMPBIN"; then
+ ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_DUMPBIN="$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN
+if test -n "$ac_ct_DUMPBIN"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5
+$as_echo "$ac_ct_DUMPBIN" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$ac_ct_DUMPBIN" && break
+done
+
+ if test "x$ac_ct_DUMPBIN" = x; then
+ DUMPBIN=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ DUMPBIN=$ac_ct_DUMPBIN
+ fi
+fi
+
+ case `$DUMPBIN -symbols /dev/null 2>&1 | sed '1q'` in
+ *COFF*)
+ DUMPBIN="$DUMPBIN -symbols"
+ ;;
+ *)
+ DUMPBIN=:
+ ;;
+ esac
+ fi
+
+ if test "$DUMPBIN" != ":"; then
+ NM="$DUMPBIN"
+ fi
+fi
+test -z "$NM" && NM=nm
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5
+$as_echo_n "checking the name lister ($NM) interface... " >&6; }
+if ${lt_cv_nm_interface+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_nm_interface="BSD nm"
+ echo "int some_variable = 0;" > conftest.$ac_ext
+ (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5)
+ (eval "$ac_compile" 2>conftest.err)
+ cat conftest.err >&5
+ (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
+ (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
+ cat conftest.err >&5
+ (eval echo "\"\$as_me:$LINENO: output\"" >&5)
+ cat conftest.out >&5
+ if $GREP 'External.*some_variable' conftest.out > /dev/null; then
+ lt_cv_nm_interface="MS dumpbin"
+ fi
+ rm -f conftest*
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5
+$as_echo "$lt_cv_nm_interface" >&6; }
+
+# find the maximum length of command line arguments
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5
+$as_echo_n "checking the maximum length of command line arguments... " >&6; }
+if ${lt_cv_sys_max_cmd_len+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ i=0
+ teststring="ABCD"
+
+ case $build_os in
+ msdosdjgpp*)
+ # On DJGPP, this test can blow up pretty badly due to problems in libc
+ # (any single argument exceeding 2000 bytes causes a buffer overrun
+ # during glob expansion). Even if it were fixed, the result of this
+ # check would be larger than it should be.
+ lt_cv_sys_max_cmd_len=12288; # 12K is about right
+ ;;
+
+ gnu*)
+ # Under GNU Hurd, this test is not required because there is
+ # no limit to the length of command line arguments.
+ # Libtool will interpret -1 as no limit whatsoever
+ lt_cv_sys_max_cmd_len=-1;
+ ;;
+
+ cygwin* | mingw* | cegcc*)
+ # On Win9x/ME, this test blows up -- it succeeds, but takes
+ # about 5 minutes as the teststring grows exponentially.
+ # Worse, since 9x/ME are not pre-emptively multitasking,
+ # you end up with a "frozen" computer, even though with patience
+ # the test eventually succeeds (with a max line length of 256k).
+ # Instead, let's just punt: use the minimum linelength reported by
+ # all of the supported platforms: 8192 (on NT/2K/XP).
+ lt_cv_sys_max_cmd_len=8192;
+ ;;
+
+ mint*)
+ # On MiNT this can take a long time and run out of memory.
+ lt_cv_sys_max_cmd_len=8192;
+ ;;
+
+ amigaos*)
+ # On AmigaOS with pdksh, this test takes hours, literally.
+ # So we just punt and use a minimum line length of 8192.
+ lt_cv_sys_max_cmd_len=8192;
+ ;;
+
+ netbsd* | freebsd* | openbsd* | darwin* | dragonfly*)
+ # This has been around since 386BSD, at least. Likely further.
+ if test -x /sbin/sysctl; then
+ lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax`
+ elif test -x /usr/sbin/sysctl; then
+ lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax`
+ else
+ lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs
+ fi
+ # And add a safety zone
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+ ;;
+
+ interix*)
+ # We know the value 262144 and hardcode it with a safety zone (like BSD)
+ lt_cv_sys_max_cmd_len=196608
+ ;;
+
+ os2*)
+ # The test takes a long time on OS/2.
+ lt_cv_sys_max_cmd_len=8192
+ ;;
+
+ osf*)
+ # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure
+ # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not
+ # nice to cause kernel panics so lets avoid the loop below.
+ # First set a reasonable default.
+ lt_cv_sys_max_cmd_len=16384
+ #
+ if test -x /sbin/sysconfig; then
+ case `/sbin/sysconfig -q proc exec_disable_arg_limit` in
+ *1*) lt_cv_sys_max_cmd_len=-1 ;;
+ esac
+ fi
+ ;;
+ sco3.2v5*)
+ lt_cv_sys_max_cmd_len=102400
+ ;;
+ sysv5* | sco5v6* | sysv4.2uw2*)
+ kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null`
+ if test -n "$kargmax"; then
+ lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[ ]//'`
+ else
+ lt_cv_sys_max_cmd_len=32768
+ fi
+ ;;
+ *)
+ lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null`
+ if test -n "$lt_cv_sys_max_cmd_len"; then
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+ else
+ # Make teststring a little bigger before we do anything with it.
+ # a 1K string should be a reasonable start.
+ for i in 1 2 3 4 5 6 7 8 ; do
+ teststring=$teststring$teststring
+ done
+ SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}}
+ # If test is not a shell built-in, we'll probably end up computing a
+ # maximum length that is only half of the actual maximum length, but
+ # we can't tell.
+ while { test "X"`env echo "$teststring$teststring" 2>/dev/null` \
+ = "X$teststring$teststring"; } >/dev/null 2>&1 &&
+ test $i != 17 # 1/2 MB should be enough
+ do
+ i=`expr $i + 1`
+ teststring=$teststring$teststring
+ done
+ # Only check the string length outside the loop.
+ lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1`
+ teststring=
+ # Add a significant safety factor because C++ compilers can tack on
+ # massive amounts of additional arguments before passing them to the
+ # linker. It appears as though 1/2 is a usable value.
+ lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2`
+ fi
+ ;;
+ esac
+
+fi
+
+if test -n $lt_cv_sys_max_cmd_len ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5
+$as_echo "$lt_cv_sys_max_cmd_len" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5
+$as_echo "none" >&6; }
+fi
+max_cmd_len=$lt_cv_sys_max_cmd_len
+
+
+
+
+
+
+: ${CP="cp -f"}
+: ${MV="mv -f"}
+: ${RM="rm -f"}
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the shell understands some XSI constructs" >&5
+$as_echo_n "checking whether the shell understands some XSI constructs... " >&6; }
+# Try some XSI features
+xsi_shell=no
+( _lt_dummy="a/b/c"
+ test "${_lt_dummy##*/},${_lt_dummy%/*},${_lt_dummy#??}"${_lt_dummy%"$_lt_dummy"}, \
+ = c,a/b,b/c, \
+ && eval 'test $(( 1 + 1 )) -eq 2 \
+ && test "${#_lt_dummy}" -eq 5' ) >/dev/null 2>&1 \
+ && xsi_shell=yes
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $xsi_shell" >&5
+$as_echo "$xsi_shell" >&6; }
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the shell understands \"+=\"" >&5
+$as_echo_n "checking whether the shell understands \"+=\"... " >&6; }
+lt_shell_append=no
+( foo=bar; set foo baz; eval "$1+=\$2" && test "$foo" = barbaz ) \
+ >/dev/null 2>&1 \
+ && lt_shell_append=yes
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_shell_append" >&5
+$as_echo "$lt_shell_append" >&6; }
+
+
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+ lt_unset=unset
+else
+ lt_unset=false
+fi
+
+
+
+
+
+# test EBCDIC or ASCII
+case `echo X|tr X '\101'` in
+ A) # ASCII based system
+ # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr
+ lt_SP2NL='tr \040 \012'
+ lt_NL2SP='tr \015\012 \040\040'
+ ;;
+ *) # EBCDIC based system
+ lt_SP2NL='tr \100 \n'
+ lt_NL2SP='tr \r\n \100\100'
+ ;;
+esac
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5
+$as_echo_n "checking how to convert $build file names to $host format... " >&6; }
+if ${lt_cv_to_host_file_cmd+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $host in
+ *-*-mingw* )
+ case $build in
+ *-*-mingw* ) # actually msys
+ lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32
+ ;;
+ *-*-cygwin* )
+ lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32
+ ;;
+ * ) # otherwise, assume *nix
+ lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32
+ ;;
+ esac
+ ;;
+ *-*-cygwin* )
+ case $build in
+ *-*-mingw* ) # actually msys
+ lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin
+ ;;
+ *-*-cygwin* )
+ lt_cv_to_host_file_cmd=func_convert_file_noop
+ ;;
+ * ) # otherwise, assume *nix
+ lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin
+ ;;
+ esac
+ ;;
+ * ) # unhandled hosts (and "normal" native builds)
+ lt_cv_to_host_file_cmd=func_convert_file_noop
+ ;;
+esac
+
+fi
+
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5
+$as_echo "$lt_cv_to_host_file_cmd" >&6; }
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5
+$as_echo_n "checking how to convert $build file names to toolchain format... " >&6; }
+if ${lt_cv_to_tool_file_cmd+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ #assume ordinary cross tools, or native build.
+lt_cv_to_tool_file_cmd=func_convert_file_noop
+case $host in
+ *-*-mingw* )
+ case $build in
+ *-*-mingw* ) # actually msys
+ lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32
+ ;;
+ esac
+ ;;
+esac
+
+fi
+
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5
+$as_echo "$lt_cv_to_tool_file_cmd" >&6; }
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5
+$as_echo_n "checking for $LD option to reload object files... " >&6; }
+if ${lt_cv_ld_reload_flag+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_ld_reload_flag='-r'
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5
+$as_echo "$lt_cv_ld_reload_flag" >&6; }
+reload_flag=$lt_cv_ld_reload_flag
+case $reload_flag in
+"" | " "*) ;;
+*) reload_flag=" $reload_flag" ;;
+esac
+reload_cmds='$LD$reload_flag -o $output$reload_objs'
+case $host_os in
+ cygwin* | mingw* | pw32* | cegcc*)
+ if test "$GCC" != yes; then
+ reload_cmds=false
+ fi
+ ;;
+ darwin*)
+ if test "$GCC" = yes; then
+ reload_cmds='$LTCC $LTCFLAGS -nostdlib ${wl}-r -o $output$reload_objs'
+ else
+ reload_cmds='$LD$reload_flag -o $output$reload_objs'
+ fi
+ ;;
+esac
+
+
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args.
+set dummy ${ac_tool_prefix}objdump; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_OBJDUMP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$OBJDUMP"; then
+ ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+OBJDUMP=$ac_cv_prog_OBJDUMP
+if test -n "$OBJDUMP"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5
+$as_echo "$OBJDUMP" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OBJDUMP"; then
+ ac_ct_OBJDUMP=$OBJDUMP
+ # Extract the first word of "objdump", so it can be a program name with args.
+set dummy objdump; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_OBJDUMP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_OBJDUMP"; then
+ ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_OBJDUMP="objdump"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP
+if test -n "$ac_ct_OBJDUMP"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5
+$as_echo "$ac_ct_OBJDUMP" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_OBJDUMP" = x; then
+ OBJDUMP="false"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ OBJDUMP=$ac_ct_OBJDUMP
+ fi
+else
+ OBJDUMP="$ac_cv_prog_OBJDUMP"
+fi
+
+test -z "$OBJDUMP" && OBJDUMP=objdump
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5
+$as_echo_n "checking how to recognize dependent libraries... " >&6; }
+if ${lt_cv_deplibs_check_method+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_file_magic_cmd='$MAGIC_CMD'
+lt_cv_file_magic_test_file=
+lt_cv_deplibs_check_method='unknown'
+# Need to set the preceding variable on all platforms that support
+# interlibrary dependencies.
+# 'none' -- dependencies not supported.
+# `unknown' -- same as none, but documents that we really don't know.
+# 'pass_all' -- all dependencies passed with no checks.
+# 'test_compile' -- check by making test program.
+# 'file_magic [[regex]]' -- check by looking for files in library path
+# which responds to the $file_magic_cmd with a given extended regex.
+# If you have `file' or equivalent on your system and you're not sure
+# whether `pass_all' will *always* work, you probably want this one.
+
+case $host_os in
+aix[4-9]*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+beos*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+bsdi[45]*)
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)'
+ lt_cv_file_magic_cmd='/usr/bin/file -L'
+ lt_cv_file_magic_test_file=/shlib/libc.so
+ ;;
+
+cygwin*)
+ # func_win32_libid is a shell function defined in ltmain.sh
+ lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+ lt_cv_file_magic_cmd='func_win32_libid'
+ ;;
+
+mingw* | pw32*)
+ # Base MSYS/MinGW do not provide the 'file' command needed by
+ # func_win32_libid shell function, so use a weaker test based on 'objdump',
+ # unless we find 'file', for example because we are cross-compiling.
+ # func_win32_libid assumes BSD nm, so disallow it if using MS dumpbin.
+ if ( test "$lt_cv_nm_interface" = "BSD nm" && file / ) >/dev/null 2>&1; then
+ lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+ lt_cv_file_magic_cmd='func_win32_libid'
+ else
+ # Keep this pattern in sync with the one in func_win32_libid.
+ lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)'
+ lt_cv_file_magic_cmd='$OBJDUMP -f'
+ fi
+ ;;
+
+cegcc*)
+ # use the weaker test based on 'objdump'. See mingw*.
+ lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?'
+ lt_cv_file_magic_cmd='$OBJDUMP -f'
+ ;;
+
+darwin* | rhapsody*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+freebsd* | dragonfly*)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+ case $host_cpu in
+ i*86 )
+ # Not sure whether the presence of OpenBSD here was a mistake.
+ # Let's accept both of them until this is cleared up.
+ lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library'
+ lt_cv_file_magic_cmd=/usr/bin/file
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+ ;;
+ esac
+ else
+ lt_cv_deplibs_check_method=pass_all
+ fi
+ ;;
+
+gnu*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+haiku*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+hpux10.20* | hpux11*)
+ lt_cv_file_magic_cmd=/usr/bin/file
+ case $host_cpu in
+ ia64*)
+ lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64'
+ lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so
+ ;;
+ hppa*64*)
+ lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'
+ lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl
+ ;;
+ *)
+ lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library'
+ lt_cv_file_magic_test_file=/usr/lib/libc.sl
+ ;;
+ esac
+ ;;
+
+interix[3-9]*)
+ # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here
+ lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$'
+ ;;
+
+irix5* | irix6* | nonstopux*)
+ case $LD in
+ *-32|*"-32 ") libmagic=32-bit;;
+ *-n32|*"-n32 ") libmagic=N32;;
+ *-64|*"-64 ") libmagic=64-bit;;
+ *) libmagic=never-match;;
+ esac
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+netbsd*)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+ lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$'
+ else
+ lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$'
+ fi
+ ;;
+
+newos6*)
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)'
+ lt_cv_file_magic_cmd=/usr/bin/file
+ lt_cv_file_magic_test_file=/usr/lib/libnls.so
+ ;;
+
+*nto* | *qnx*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+openbsd*)
+ if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$'
+ else
+ lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$'
+ fi
+ ;;
+
+osf3* | osf4* | osf5*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+rdos*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+solaris*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+sysv4 | sysv4.3*)
+ case $host_vendor in
+ motorola)
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]'
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*`
+ ;;
+ ncr)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+ sequent)
+ lt_cv_file_magic_cmd='/bin/file'
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )'
+ ;;
+ sni)
+ lt_cv_file_magic_cmd='/bin/file'
+ lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib"
+ lt_cv_file_magic_test_file=/lib/libc.so
+ ;;
+ siemens)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+ pc)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+ esac
+ ;;
+
+tpf*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+esac
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5
+$as_echo "$lt_cv_deplibs_check_method" >&6; }
+
+file_magic_glob=
+want_nocaseglob=no
+if test "$build" = "$host"; then
+ case $host_os in
+ mingw* | pw32*)
+ if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then
+ want_nocaseglob=yes
+ else
+ file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"`
+ fi
+ ;;
+ esac
+fi
+
+file_magic_cmd=$lt_cv_file_magic_cmd
+deplibs_check_method=$lt_cv_deplibs_check_method
+test -z "$deplibs_check_method" && deplibs_check_method=unknown
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args.
+set dummy ${ac_tool_prefix}dlltool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_DLLTOOL+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$DLLTOOL"; then
+ ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+DLLTOOL=$ac_cv_prog_DLLTOOL
+if test -n "$DLLTOOL"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5
+$as_echo "$DLLTOOL" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_DLLTOOL"; then
+ ac_ct_DLLTOOL=$DLLTOOL
+ # Extract the first word of "dlltool", so it can be a program name with args.
+set dummy dlltool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_DLLTOOL+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_DLLTOOL"; then
+ ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_DLLTOOL="dlltool"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL
+if test -n "$ac_ct_DLLTOOL"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5
+$as_echo "$ac_ct_DLLTOOL" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_DLLTOOL" = x; then
+ DLLTOOL="false"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ DLLTOOL=$ac_ct_DLLTOOL
+ fi
+else
+ DLLTOOL="$ac_cv_prog_DLLTOOL"
+fi
+
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5
+$as_echo_n "checking how to associate runtime and link libraries... " >&6; }
+if ${lt_cv_sharedlib_from_linklib_cmd+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_sharedlib_from_linklib_cmd='unknown'
+
+case $host_os in
+cygwin* | mingw* | pw32* | cegcc*)
+ # two different shell functions defined in ltmain.sh
+ # decide which to use based on capabilities of $DLLTOOL
+ case `$DLLTOOL --help 2>&1` in
+ *--identify-strict*)
+ lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib
+ ;;
+ *)
+ lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback
+ ;;
+ esac
+ ;;
+*)
+ # fallback: assume linklib IS sharedlib
+ lt_cv_sharedlib_from_linklib_cmd="$ECHO"
+ ;;
+esac
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5
+$as_echo "$lt_cv_sharedlib_from_linklib_cmd" >&6; }
+sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd
+test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+ for ac_prog in ar
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_AR+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$AR"; then
+ ac_cv_prog_AR="$AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_AR="$ac_tool_prefix$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+AR=$ac_cv_prog_AR
+if test -n "$AR"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5
+$as_echo "$AR" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$AR" && break
+ done
+fi
+if test -z "$AR"; then
+ ac_ct_AR=$AR
+ for ac_prog in ar
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_AR+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_AR"; then
+ ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_AR="$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_AR=$ac_cv_prog_ac_ct_AR
+if test -n "$ac_ct_AR"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5
+$as_echo "$ac_ct_AR" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$ac_ct_AR" && break
+done
+
+ if test "x$ac_ct_AR" = x; then
+ AR="false"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ AR=$ac_ct_AR
+ fi
+fi
+
+: ${AR=ar}
+: ${AR_FLAGS=cru}
+
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5
+$as_echo_n "checking for archiver @FILE support... " >&6; }
+if ${lt_cv_ar_at_file+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_ar_at_file=no
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ echo conftest.$ac_objext > conftest.lst
+ lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5'
+ { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5
+ (eval $lt_ar_try) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ if test "$ac_status" -eq 0; then
+ # Ensure the archiver fails upon bogus file names.
+ rm -f conftest.$ac_objext libconftest.a
+ { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5
+ (eval $lt_ar_try) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ if test "$ac_status" -ne 0; then
+ lt_cv_ar_at_file=@
+ fi
+ fi
+ rm -f conftest.* libconftest.a
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5
+$as_echo "$lt_cv_ar_at_file" >&6; }
+
+if test "x$lt_cv_ar_at_file" = xno; then
+ archiver_list_spec=
+else
+ archiver_list_spec=$lt_cv_ar_at_file
+fi
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_STRIP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$STRIP"; then
+ ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_STRIP="${ac_tool_prefix}strip"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+STRIP=$ac_cv_prog_STRIP
+if test -n "$STRIP"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5
+$as_echo "$STRIP" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_STRIP"; then
+ ac_ct_STRIP=$STRIP
+ # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_STRIP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_STRIP"; then
+ ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_STRIP="strip"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP
+if test -n "$ac_ct_STRIP"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5
+$as_echo "$ac_ct_STRIP" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_STRIP" = x; then
+ STRIP=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ STRIP=$ac_ct_STRIP
+ fi
+else
+ STRIP="$ac_cv_prog_STRIP"
+fi
+
+test -z "$STRIP" && STRIP=:
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args.
+set dummy ${ac_tool_prefix}ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_RANLIB+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$RANLIB"; then
+ ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+RANLIB=$ac_cv_prog_RANLIB
+if test -n "$RANLIB"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5
+$as_echo "$RANLIB" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_RANLIB"; then
+ ac_ct_RANLIB=$RANLIB
+ # Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_RANLIB+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_RANLIB"; then
+ ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_RANLIB="ranlib"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB
+if test -n "$ac_ct_RANLIB"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5
+$as_echo "$ac_ct_RANLIB" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_RANLIB" = x; then
+ RANLIB=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ RANLIB=$ac_ct_RANLIB
+ fi
+else
+ RANLIB="$ac_cv_prog_RANLIB"
+fi
+
+test -z "$RANLIB" && RANLIB=:
+
+
+
+
+
+
+# Determine commands to create old-style static archives.
+old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs'
+old_postinstall_cmds='chmod 644 $oldlib'
+old_postuninstall_cmds=
+
+if test -n "$RANLIB"; then
+ case $host_os in
+ openbsd*)
+ old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib"
+ ;;
+ *)
+ old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib"
+ ;;
+ esac
+ old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib"
+fi
+
+case $host_os in
+ darwin*)
+ lock_old_archive_extraction=yes ;;
+ *)
+ lock_old_archive_extraction=no ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+for ac_prog in gawk mawk nawk awk
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_AWK+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$AWK"; then
+ ac_cv_prog_AWK="$AWK" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_AWK="$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+AWK=$ac_cv_prog_AWK
+if test -n "$AWK"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5
+$as_echo "$AWK" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$AWK" && break
+done
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+
+
+# Check for command to grab the raw symbol name followed by C symbol from nm.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5
+$as_echo_n "checking command to parse $NM output from $compiler object... " >&6; }
+if ${lt_cv_sys_global_symbol_pipe+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+# These are sane defaults that work on at least a few old systems.
+# [They come from Ultrix. What could be older than Ultrix?!! ;)]
+
+# Character class describing NM global symbol codes.
+symcode='[BCDEGRST]'
+
+# Regexp to match symbols that can be accessed directly from C.
+sympat='\([_A-Za-z][_A-Za-z0-9]*\)'
+
+# Define system-specific variables.
+case $host_os in
+aix*)
+ symcode='[BCDT]'
+ ;;
+cygwin* | mingw* | pw32* | cegcc*)
+ symcode='[ABCDGISTW]'
+ ;;
+hpux*)
+ if test "$host_cpu" = ia64; then
+ symcode='[ABCDEGRST]'
+ fi
+ ;;
+irix* | nonstopux*)
+ symcode='[BCDEGRST]'
+ ;;
+osf*)
+ symcode='[BCDEGQRST]'
+ ;;
+solaris*)
+ symcode='[BDRT]'
+ ;;
+sco3.2v5*)
+ symcode='[DT]'
+ ;;
+sysv4.2uw2*)
+ symcode='[DT]'
+ ;;
+sysv5* | sco5v6* | unixware* | OpenUNIX*)
+ symcode='[ABDT]'
+ ;;
+sysv4)
+ symcode='[DFNSTU]'
+ ;;
+esac
+
+# If we're using GNU nm, then use its standard symbol codes.
+case `$NM -V 2>&1` in
+*GNU* | *'with BFD'*)
+ symcode='[ABCDGIRSTW]' ;;
+esac
+
+# Transform an extracted symbol line into a proper C declaration.
+# Some systems (esp. on ia64) link data and code symbols differently,
+# so use this general approach.
+lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'"
+
+# Transform an extracted symbol line into symbol name and symbol address
+lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([^ ]*\)[ ]*$/ {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([^ ]*\) \([^ ]*\)$/ {\"\2\", (void *) \&\2},/p'"
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n -e 's/^: \([^ ]*\)[ ]*$/ {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([^ ]*\) \(lib[^ ]*\)$/ {\"\2\", (void *) \&\2},/p' -e 's/^$symcode* \([^ ]*\) \([^ ]*\)$/ {\"lib\2\", (void *) \&\2},/p'"
+
+# Handle CRLF in mingw tool chain
+opt_cr=
+case $build_os in
+mingw*)
+ opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp
+ ;;
+esac
+
+# Try without a prefix underscore, then with it.
+for ac_symprfx in "" "_"; do
+
+ # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol.
+ symxfrm="\\1 $ac_symprfx\\2 \\2"
+
+ # Write the raw and C identifiers.
+ if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+ # Fake it for dumpbin and say T for any non-static function
+ # and D for any global variable.
+ # Also find C++ and __fastcall symbols from MSVC++,
+ # which start with @ or ?.
+ lt_cv_sys_global_symbol_pipe="$AWK '"\
+" {last_section=section; section=\$ 3};"\
+" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\
+" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\
+" \$ 0!~/External *\|/{next};"\
+" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\
+" {if(hide[section]) next};"\
+" {f=0}; \$ 0~/\(\).*\|/{f=1}; {printf f ? \"T \" : \"D \"};"\
+" {split(\$ 0, a, /\||\r/); split(a[2], s)};"\
+" s[1]~/^[@?]/{print s[1], s[1]; next};"\
+" s[1]~prfx {split(s[1],t,\"@\"); print t[1], substr(t[1],length(prfx))}"\
+" ' prfx=^$ac_symprfx"
+ else
+ lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'"
+ fi
+ lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'"
+
+ # Check to see that the pipe works correctly.
+ pipe_works=no
+
+ rm -f conftest*
+ cat > conftest.$ac_ext <<_LT_EOF
+#ifdef __cplusplus
+extern "C" {
+#endif
+char nm_test_var;
+void nm_test_func(void);
+void nm_test_func(void){}
+#ifdef __cplusplus
+}
+#endif
+int main(){nm_test_var='a';nm_test_func();return(0);}
+_LT_EOF
+
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ # Now try to grab the symbols.
+ nlist=conftest.nm
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist\""; } >&5
+ (eval $NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && test -s "$nlist"; then
+ # Try sorting and uniquifying the output.
+ if sort "$nlist" | uniq > "$nlist"T; then
+ mv -f "$nlist"T "$nlist"
+ else
+ rm -f "$nlist"T
+ fi
+
+ # Make sure that we snagged all the symbols we need.
+ if $GREP ' nm_test_var$' "$nlist" >/dev/null; then
+ if $GREP ' nm_test_func$' "$nlist" >/dev/null; then
+ cat <<_LT_EOF > conftest.$ac_ext
+/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */
+#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE)
+/* DATA imports from DLLs on WIN32 con't be const, because runtime
+ relocations are performed -- see ld's documentation on pseudo-relocs. */
+# define LT_DLSYM_CONST
+#elif defined(__osf__)
+/* This system does not cope well with relocations in const data. */
+# define LT_DLSYM_CONST
+#else
+# define LT_DLSYM_CONST const
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+_LT_EOF
+ # Now generate the symbol file.
+ eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext'
+
+ cat <<_LT_EOF >> conftest.$ac_ext
+
+/* The mapping between symbol names and symbols. */
+LT_DLSYM_CONST struct {
+ const char *name;
+ void *address;
+}
+lt__PROGRAM__LTX_preloaded_symbols[] =
+{
+ { "@PROGRAM@", (void *) 0 },
+_LT_EOF
+ $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/ {\"\2\", (void *) \&\2},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext
+ cat <<\_LT_EOF >> conftest.$ac_ext
+ {0, (void *) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+ return lt__PROGRAM__LTX_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+_LT_EOF
+ # Now try linking the two files.
+ mv conftest.$ac_objext conftstm.$ac_objext
+ lt_globsym_save_LIBS=$LIBS
+ lt_globsym_save_CFLAGS=$CFLAGS
+ LIBS="conftstm.$ac_objext"
+ CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag"
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+ (eval $ac_link) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && test -s conftest${ac_exeext}; then
+ pipe_works=yes
+ fi
+ LIBS=$lt_globsym_save_LIBS
+ CFLAGS=$lt_globsym_save_CFLAGS
+ else
+ echo "cannot find nm_test_func in $nlist" >&5
+ fi
+ else
+ echo "cannot find nm_test_var in $nlist" >&5
+ fi
+ else
+ echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5
+ fi
+ else
+ echo "$progname: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ fi
+ rm -rf conftest* conftst*
+
+ # Do not use the global_symbol_pipe unless it works.
+ if test "$pipe_works" = yes; then
+ break
+ else
+ lt_cv_sys_global_symbol_pipe=
+ fi
+done
+
+fi
+
+if test -z "$lt_cv_sys_global_symbol_pipe"; then
+ lt_cv_sys_global_symbol_to_cdecl=
+fi
+if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5
+$as_echo "failed" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok" >&5
+$as_echo "ok" >&6; }
+fi
+
+# Response file support.
+if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+ nm_file_list_spec='@'
+elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then
+ nm_file_list_spec='@'
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5
+$as_echo_n "checking for sysroot... " >&6; }
+
+# Check whether --with-sysroot was given.
+if test "${with_sysroot+set}" = set; then :
+ withval=$with_sysroot;
+else
+ with_sysroot=no
+fi
+
+
+lt_sysroot=
+case ${with_sysroot} in #(
+ yes)
+ if test "$GCC" = yes; then
+ lt_sysroot=`$CC --print-sysroot 2>/dev/null`
+ fi
+ ;; #(
+ /*)
+ lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"`
+ ;; #(
+ no|'')
+ ;; #(
+ *)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${with_sysroot}" >&5
+$as_echo "${with_sysroot}" >&6; }
+ as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5
+ ;;
+esac
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5
+$as_echo "${lt_sysroot:-no}" >&6; }
+
+
+
+
+
+# Check whether --enable-libtool-lock was given.
+if test "${enable_libtool_lock+set}" = set; then :
+ enableval=$enable_libtool_lock;
+fi
+
+test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes
+
+# Some flags need to be propagated to the compiler or linker for good
+# libtool support.
+case $host in
+ia64-*-hpux*)
+ # Find out which ABI we are using.
+ echo 'int i;' > conftest.$ac_ext
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ case `/usr/bin/file conftest.$ac_objext` in
+ *ELF-32*)
+ HPUX_IA64_MODE="32"
+ ;;
+ *ELF-64*)
+ HPUX_IA64_MODE="64"
+ ;;
+ esac
+ fi
+ rm -rf conftest*
+ ;;
+*-*-irix6*)
+ # Find out which ABI we are using.
+ echo '#line '$LINENO' "configure"' > conftest.$ac_ext
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ if test "$lt_cv_prog_gnu_ld" = yes; then
+ case `/usr/bin/file conftest.$ac_objext` in
+ *32-bit*)
+ LD="${LD-ld} -melf32bsmip"
+ ;;
+ *N32*)
+ LD="${LD-ld} -melf32bmipn32"
+ ;;
+ *64-bit*)
+ LD="${LD-ld} -melf64bmip"
+ ;;
+ esac
+ else
+ case `/usr/bin/file conftest.$ac_objext` in
+ *32-bit*)
+ LD="${LD-ld} -32"
+ ;;
+ *N32*)
+ LD="${LD-ld} -n32"
+ ;;
+ *64-bit*)
+ LD="${LD-ld} -64"
+ ;;
+ esac
+ fi
+ fi
+ rm -rf conftest*
+ ;;
+
+x86_64-*kfreebsd*-gnu|x86_64-*linux*|ppc*-*linux*|powerpc*-*linux*| \
+s390*-*linux*|s390*-*tpf*|sparc*-*linux*)
+ # Find out which ABI we are using.
+ echo 'int i;' > conftest.$ac_ext
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ case `/usr/bin/file conftest.o` in
+ *32-bit*)
+ case $host in
+ x86_64-*kfreebsd*-gnu)
+ LD="${LD-ld} -m elf_i386_fbsd"
+ ;;
+ x86_64-*linux*)
+ LD="${LD-ld} -m elf_i386"
+ ;;
+ ppc64-*linux*|powerpc64-*linux*)
+ LD="${LD-ld} -m elf32ppclinux"
+ ;;
+ s390x-*linux*)
+ LD="${LD-ld} -m elf_s390"
+ ;;
+ sparc64-*linux*)
+ LD="${LD-ld} -m elf32_sparc"
+ ;;
+ esac
+ ;;
+ *64-bit*)
+ case $host in
+ x86_64-*kfreebsd*-gnu)
+ LD="${LD-ld} -m elf_x86_64_fbsd"
+ ;;
+ x86_64-*linux*)
+ LD="${LD-ld} -m elf_x86_64"
+ ;;
+ ppc*-*linux*|powerpc*-*linux*)
+ LD="${LD-ld} -m elf64ppc"
+ ;;
+ s390*-*linux*|s390*-*tpf*)
+ LD="${LD-ld} -m elf64_s390"
+ ;;
+ sparc*-*linux*)
+ LD="${LD-ld} -m elf64_sparc"
+ ;;
+ esac
+ ;;
+ esac
+ fi
+ rm -rf conftest*
+ ;;
+
+*-*-sco3.2v5*)
+ # On SCO OpenServer 5, we need -belf to get full-featured binaries.
+ SAVE_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -belf"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5
+$as_echo_n "checking whether the C compiler needs -belf... " >&6; }
+if ${lt_cv_cc_needs_belf+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ lt_cv_cc_needs_belf=yes
+else
+ lt_cv_cc_needs_belf=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5
+$as_echo "$lt_cv_cc_needs_belf" >&6; }
+ if test x"$lt_cv_cc_needs_belf" != x"yes"; then
+ # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf
+ CFLAGS="$SAVE_CFLAGS"
+ fi
+ ;;
+*-*solaris*)
+ # Find out which ABI we are using.
+ echo 'int i;' > conftest.$ac_ext
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ case `/usr/bin/file conftest.o` in
+ *64-bit*)
+ case $lt_cv_prog_gnu_ld in
+ yes*)
+ case $host in
+ i?86-*-solaris*)
+ LD="${LD-ld} -m elf_x86_64"
+ ;;
+ sparc*-*-solaris*)
+ LD="${LD-ld} -m elf64_sparc"
+ ;;
+ esac
+ # GNU ld 2.21 introduced _sol2 emulations. Use them if available.
+ if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then
+ LD="${LD-ld}_sol2"
+ fi
+ ;;
+ *)
+ if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then
+ LD="${LD-ld} -64"
+ fi
+ ;;
+ esac
+ ;;
+ esac
+ fi
+ rm -rf conftest*
+ ;;
+esac
+
+need_locks="$enable_libtool_lock"
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args.
+set dummy ${ac_tool_prefix}mt; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_MANIFEST_TOOL+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$MANIFEST_TOOL"; then
+ ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL
+if test -n "$MANIFEST_TOOL"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5
+$as_echo "$MANIFEST_TOOL" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_MANIFEST_TOOL"; then
+ ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL
+ # Extract the first word of "mt", so it can be a program name with args.
+set dummy mt; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_MANIFEST_TOOL+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_MANIFEST_TOOL"; then
+ ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_MANIFEST_TOOL="mt"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL
+if test -n "$ac_ct_MANIFEST_TOOL"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5
+$as_echo "$ac_ct_MANIFEST_TOOL" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_MANIFEST_TOOL" = x; then
+ MANIFEST_TOOL=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL
+ fi
+else
+ MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL"
+fi
+
+test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5
+$as_echo_n "checking if $MANIFEST_TOOL is a manifest tool... " >&6; }
+if ${lt_cv_path_mainfest_tool+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_path_mainfest_tool=no
+ echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5
+ $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out
+ cat conftest.err >&5
+ if $GREP 'Manifest Tool' conftest.out > /dev/null; then
+ lt_cv_path_mainfest_tool=yes
+ fi
+ rm -f conftest*
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_mainfest_tool" >&5
+$as_echo "$lt_cv_path_mainfest_tool" >&6; }
+if test "x$lt_cv_path_mainfest_tool" != xyes; then
+ MANIFEST_TOOL=:
+fi
+
+
+
+
+
+
+ case $host_os in
+ rhapsody* | darwin*)
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args.
+set dummy ${ac_tool_prefix}dsymutil; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_DSYMUTIL+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$DSYMUTIL"; then
+ ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+DSYMUTIL=$ac_cv_prog_DSYMUTIL
+if test -n "$DSYMUTIL"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5
+$as_echo "$DSYMUTIL" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_DSYMUTIL"; then
+ ac_ct_DSYMUTIL=$DSYMUTIL
+ # Extract the first word of "dsymutil", so it can be a program name with args.
+set dummy dsymutil; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_DSYMUTIL+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_DSYMUTIL"; then
+ ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_DSYMUTIL="dsymutil"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL
+if test -n "$ac_ct_DSYMUTIL"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5
+$as_echo "$ac_ct_DSYMUTIL" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_DSYMUTIL" = x; then
+ DSYMUTIL=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ DSYMUTIL=$ac_ct_DSYMUTIL
+ fi
+else
+ DSYMUTIL="$ac_cv_prog_DSYMUTIL"
+fi
+
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args.
+set dummy ${ac_tool_prefix}nmedit; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_NMEDIT+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$NMEDIT"; then
+ ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+NMEDIT=$ac_cv_prog_NMEDIT
+if test -n "$NMEDIT"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5
+$as_echo "$NMEDIT" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_NMEDIT"; then
+ ac_ct_NMEDIT=$NMEDIT
+ # Extract the first word of "nmedit", so it can be a program name with args.
+set dummy nmedit; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_NMEDIT+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_NMEDIT"; then
+ ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_NMEDIT="nmedit"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT
+if test -n "$ac_ct_NMEDIT"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5
+$as_echo "$ac_ct_NMEDIT" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_NMEDIT" = x; then
+ NMEDIT=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ NMEDIT=$ac_ct_NMEDIT
+ fi
+else
+ NMEDIT="$ac_cv_prog_NMEDIT"
+fi
+
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args.
+set dummy ${ac_tool_prefix}lipo; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_LIPO+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$LIPO"; then
+ ac_cv_prog_LIPO="$LIPO" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_LIPO="${ac_tool_prefix}lipo"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+LIPO=$ac_cv_prog_LIPO
+if test -n "$LIPO"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5
+$as_echo "$LIPO" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_LIPO"; then
+ ac_ct_LIPO=$LIPO
+ # Extract the first word of "lipo", so it can be a program name with args.
+set dummy lipo; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_LIPO+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_LIPO"; then
+ ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_LIPO="lipo"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO
+if test -n "$ac_ct_LIPO"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5
+$as_echo "$ac_ct_LIPO" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_LIPO" = x; then
+ LIPO=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ LIPO=$ac_ct_LIPO
+ fi
+else
+ LIPO="$ac_cv_prog_LIPO"
+fi
+
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args.
+set dummy ${ac_tool_prefix}otool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_OTOOL+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$OTOOL"; then
+ ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_OTOOL="${ac_tool_prefix}otool"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+OTOOL=$ac_cv_prog_OTOOL
+if test -n "$OTOOL"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5
+$as_echo "$OTOOL" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OTOOL"; then
+ ac_ct_OTOOL=$OTOOL
+ # Extract the first word of "otool", so it can be a program name with args.
+set dummy otool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_OTOOL+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_OTOOL"; then
+ ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_OTOOL="otool"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL
+if test -n "$ac_ct_OTOOL"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5
+$as_echo "$ac_ct_OTOOL" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_OTOOL" = x; then
+ OTOOL=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ OTOOL=$ac_ct_OTOOL
+ fi
+else
+ OTOOL="$ac_cv_prog_OTOOL"
+fi
+
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args.
+set dummy ${ac_tool_prefix}otool64; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_OTOOL64+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$OTOOL64"; then
+ ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+OTOOL64=$ac_cv_prog_OTOOL64
+if test -n "$OTOOL64"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5
+$as_echo "$OTOOL64" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OTOOL64"; then
+ ac_ct_OTOOL64=$OTOOL64
+ # Extract the first word of "otool64", so it can be a program name with args.
+set dummy otool64; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_OTOOL64+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_OTOOL64"; then
+ ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_OTOOL64="otool64"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64
+if test -n "$ac_ct_OTOOL64"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5
+$as_echo "$ac_ct_OTOOL64" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_OTOOL64" = x; then
+ OTOOL64=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ OTOOL64=$ac_ct_OTOOL64
+ fi
+else
+ OTOOL64="$ac_cv_prog_OTOOL64"
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5
+$as_echo_n "checking for -single_module linker flag... " >&6; }
+if ${lt_cv_apple_cc_single_mod+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_apple_cc_single_mod=no
+ if test -z "${LT_MULTI_MODULE}"; then
+ # By default we will add the -single_module flag. You can override
+ # by either setting the environment variable LT_MULTI_MODULE
+ # non-empty at configure time, or by adding -multi_module to the
+ # link flags.
+ rm -rf libconftest.dylib*
+ echo "int foo(void){return 1;}" > conftest.c
+ echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+-dynamiclib -Wl,-single_module conftest.c" >&5
+ $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+ -dynamiclib -Wl,-single_module conftest.c 2>conftest.err
+ _lt_result=$?
+ # If there is a non-empty error log, and "single_module"
+ # appears in it, assume the flag caused a linker warning
+ if test -s conftest.err && $GREP single_module conftest.err; then
+ cat conftest.err >&5
+ # Otherwise, if the output was created with a 0 exit code from
+ # the compiler, it worked.
+ elif test -f libconftest.dylib && test $_lt_result -eq 0; then
+ lt_cv_apple_cc_single_mod=yes
+ else
+ cat conftest.err >&5
+ fi
+ rm -rf libconftest.dylib*
+ rm -f conftest.*
+ fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5
+$as_echo "$lt_cv_apple_cc_single_mod" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5
+$as_echo_n "checking for -exported_symbols_list linker flag... " >&6; }
+if ${lt_cv_ld_exported_symbols_list+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_ld_exported_symbols_list=no
+ save_LDFLAGS=$LDFLAGS
+ echo "_main" > conftest.sym
+ LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ lt_cv_ld_exported_symbols_list=yes
+else
+ lt_cv_ld_exported_symbols_list=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ LDFLAGS="$save_LDFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5
+$as_echo "$lt_cv_ld_exported_symbols_list" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5
+$as_echo_n "checking for -force_load linker flag... " >&6; }
+if ${lt_cv_ld_force_load+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_ld_force_load=no
+ cat > conftest.c << _LT_EOF
+int forced_loaded() { return 2;}
+_LT_EOF
+ echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5
+ $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5
+ echo "$AR cru libconftest.a conftest.o" >&5
+ $AR cru libconftest.a conftest.o 2>&5
+ echo "$RANLIB libconftest.a" >&5
+ $RANLIB libconftest.a 2>&5
+ cat > conftest.c << _LT_EOF
+int main() { return 0;}
+_LT_EOF
+ echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5
+ $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err
+ _lt_result=$?
+ if test -s conftest.err && $GREP force_load conftest.err; then
+ cat conftest.err >&5
+ elif test -f conftest && test $_lt_result -eq 0 && $GREP forced_load conftest >/dev/null 2>&1 ; then
+ lt_cv_ld_force_load=yes
+ else
+ cat conftest.err >&5
+ fi
+ rm -f conftest.err libconftest.a conftest conftest.c
+ rm -rf conftest.dSYM
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5
+$as_echo "$lt_cv_ld_force_load" >&6; }
+ case $host_os in
+ rhapsody* | darwin1.[012])
+ _lt_dar_allow_undefined='${wl}-undefined ${wl}suppress' ;;
+ darwin1.*)
+ _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+ darwin*) # darwin 5.x on
+ # if running on 10.5 or later, the deployment target defaults
+ # to the OS version, if on x86, and 10.4, the deployment
+ # target defaults to 10.4. Don't you love it?
+ case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in
+ 10.0,*86*-darwin8*|10.0,*-darwin[91]*)
+ _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+ 10.[012]*)
+ _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+ 10.*)
+ _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+ esac
+ ;;
+ esac
+ if test "$lt_cv_apple_cc_single_mod" = "yes"; then
+ _lt_dar_single_mod='$single_module'
+ fi
+ if test "$lt_cv_ld_exported_symbols_list" = "yes"; then
+ _lt_dar_export_syms=' ${wl}-exported_symbols_list,$output_objdir/${libname}-symbols.expsym'
+ else
+ _lt_dar_export_syms='~$NMEDIT -s $output_objdir/${libname}-symbols.expsym ${lib}'
+ fi
+ if test "$DSYMUTIL" != ":" && test "$lt_cv_ld_force_load" = "no"; then
+ _lt_dsymutil='~$DSYMUTIL $lib || :'
+ else
+ _lt_dsymutil=
+ fi
+ ;;
+ esac
+
+for ac_header in dlfcn.h
+do :
+ ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default
+"
+if test "x$ac_cv_header_dlfcn_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_DLFCN_H 1
+_ACEOF
+
+fi
+
+done
+
+
+
+func_stripname_cnf ()
+{
+ case ${2} in
+ .*) func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%\\\\${2}\$%%"`;;
+ *) func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%${2}\$%%"`;;
+ esac
+} # func_stripname_cnf
+
+
+
+
+
+# Set options
+
+
+
+ enable_dlopen=no
+
+
+ enable_win32_dll=no
+
+
+ # Check whether --enable-shared was given.
+if test "${enable_shared+set}" = set; then :
+ enableval=$enable_shared; p=${PACKAGE-default}
+ case $enableval in
+ yes) enable_shared=yes ;;
+ no) enable_shared=no ;;
+ *)
+ enable_shared=no
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+ for pkg in $enableval; do
+ IFS="$lt_save_ifs"
+ if test "X$pkg" = "X$p"; then
+ enable_shared=yes
+ fi
+ done
+ IFS="$lt_save_ifs"
+ ;;
+ esac
+else
+ enable_shared=yes
+fi
+
+
+
+
+
+
+
+
+
+ # Check whether --enable-static was given.
+if test "${enable_static+set}" = set; then :
+ enableval=$enable_static; p=${PACKAGE-default}
+ case $enableval in
+ yes) enable_static=yes ;;
+ no) enable_static=no ;;
+ *)
+ enable_static=no
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+ for pkg in $enableval; do
+ IFS="$lt_save_ifs"
+ if test "X$pkg" = "X$p"; then
+ enable_static=yes
+ fi
+ done
+ IFS="$lt_save_ifs"
+ ;;
+ esac
+else
+ enable_static=yes
+fi
+
+
+
+
+
+
+
+
+
+
+# Check whether --with-pic was given.
+if test "${with_pic+set}" = set; then :
+ withval=$with_pic; lt_p=${PACKAGE-default}
+ case $withval in
+ yes|no) pic_mode=$withval ;;
+ *)
+ pic_mode=default
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+ for lt_pkg in $withval; do
+ IFS="$lt_save_ifs"
+ if test "X$lt_pkg" = "X$lt_p"; then
+ pic_mode=yes
+ fi
+ done
+ IFS="$lt_save_ifs"
+ ;;
+ esac
+else
+ pic_mode=default
+fi
+
+
+test -z "$pic_mode" && pic_mode=default
+
+
+
+
+
+
+
+ # Check whether --enable-fast-install was given.
+if test "${enable_fast_install+set}" = set; then :
+ enableval=$enable_fast_install; p=${PACKAGE-default}
+ case $enableval in
+ yes) enable_fast_install=yes ;;
+ no) enable_fast_install=no ;;
+ *)
+ enable_fast_install=no
+ # Look at the argument we got. We use all the common list separators.
+ lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+ for pkg in $enableval; do
+ IFS="$lt_save_ifs"
+ if test "X$pkg" = "X$p"; then
+ enable_fast_install=yes
+ fi
+ done
+ IFS="$lt_save_ifs"
+ ;;
+ esac
+else
+ enable_fast_install=yes
+fi
+
+
+
+
+
+
+
+
+
+
+
+# This can be used to rebuild libtool when needed
+LIBTOOL_DEPS="$ltmain"
+
+# Always use our own libtool.
+LIBTOOL='$(SHELL) $(top_builddir)/libtool'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+test -z "$LN_S" && LN_S="ln -s"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+if test -n "${ZSH_VERSION+set}" ; then
+ setopt NO_GLOB_SUBST
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5
+$as_echo_n "checking for objdir... " >&6; }
+if ${lt_cv_objdir+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ rm -f .libs 2>/dev/null
+mkdir .libs 2>/dev/null
+if test -d .libs; then
+ lt_cv_objdir=.libs
+else
+ # MS-DOS does not allow filenames that begin with a dot.
+ lt_cv_objdir=_libs
+fi
+rmdir .libs 2>/dev/null
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5
+$as_echo "$lt_cv_objdir" >&6; }
+objdir=$lt_cv_objdir
+
+
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define LT_OBJDIR "$lt_cv_objdir/"
+_ACEOF
+
+
+
+
+case $host_os in
+aix3*)
+ # AIX sometimes has problems with the GCC collect2 program. For some
+ # reason, if we set the COLLECT_NAMES environment variable, the problems
+ # vanish in a puff of smoke.
+ if test "X${COLLECT_NAMES+set}" != Xset; then
+ COLLECT_NAMES=
+ export COLLECT_NAMES
+ fi
+ ;;
+esac
+
+# Global variables:
+ofile=libtool
+can_build_shared=yes
+
+# All known linkers require a `.a' archive for static linking (except MSVC,
+# which needs '.lib').
+libext=a
+
+with_gnu_ld="$lt_cv_prog_gnu_ld"
+
+old_CC="$CC"
+old_CFLAGS="$CFLAGS"
+
+# Set sane defaults for various variables
+test -z "$CC" && CC=cc
+test -z "$LTCC" && LTCC=$CC
+test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS
+test -z "$LD" && LD=ld
+test -z "$ac_objext" && ac_objext=o
+
+for cc_temp in $compiler""; do
+ case $cc_temp in
+ compile | *[\\/]compile | ccache | *[\\/]ccache ) ;;
+ distcc | *[\\/]distcc | purify | *[\\/]purify ) ;;
+ \-*) ;;
+ *) break;;
+ esac
+done
+cc_basename=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"`
+
+
+# Only perform the check for file, if the check method requires it
+test -z "$MAGIC_CMD" && MAGIC_CMD=file
+case $deplibs_check_method in
+file_magic*)
+ if test "$file_magic_cmd" = '$MAGIC_CMD'; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5
+$as_echo_n "checking for ${ac_tool_prefix}file... " >&6; }
+if ${lt_cv_path_MAGIC_CMD+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $MAGIC_CMD in
+[\\/*] | ?:[\\/]*)
+ lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+ ;;
+*)
+ lt_save_MAGIC_CMD="$MAGIC_CMD"
+ lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ ac_dummy="/usr/bin$PATH_SEPARATOR$PATH"
+ for ac_dir in $ac_dummy; do
+ IFS="$lt_save_ifs"
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/${ac_tool_prefix}file; then
+ lt_cv_path_MAGIC_CMD="$ac_dir/${ac_tool_prefix}file"
+ if test -n "$file_magic_test_file"; then
+ case $deplibs_check_method in
+ "file_magic "*)
+ file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+ MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+ if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+ $EGREP "$file_magic_regex" > /dev/null; then
+ :
+ else
+ cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such. This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem. Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool@gnu.org
+
+_LT_EOF
+ fi ;;
+ esac
+ fi
+ break
+ fi
+ done
+ IFS="$lt_save_ifs"
+ MAGIC_CMD="$lt_save_MAGIC_CMD"
+ ;;
+esac
+fi
+
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5
+$as_echo "$MAGIC_CMD" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+
+
+if test -z "$lt_cv_path_MAGIC_CMD"; then
+ if test -n "$ac_tool_prefix"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for file" >&5
+$as_echo_n "checking for file... " >&6; }
+if ${lt_cv_path_MAGIC_CMD+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $MAGIC_CMD in
+[\\/*] | ?:[\\/]*)
+ lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+ ;;
+*)
+ lt_save_MAGIC_CMD="$MAGIC_CMD"
+ lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ ac_dummy="/usr/bin$PATH_SEPARATOR$PATH"
+ for ac_dir in $ac_dummy; do
+ IFS="$lt_save_ifs"
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/file; then
+ lt_cv_path_MAGIC_CMD="$ac_dir/file"
+ if test -n "$file_magic_test_file"; then
+ case $deplibs_check_method in
+ "file_magic "*)
+ file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+ MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+ if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+ $EGREP "$file_magic_regex" > /dev/null; then
+ :
+ else
+ cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such. This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem. Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool@gnu.org
+
+_LT_EOF
+ fi ;;
+ esac
+ fi
+ break
+ fi
+ done
+ IFS="$lt_save_ifs"
+ MAGIC_CMD="$lt_save_MAGIC_CMD"
+ ;;
+esac
+fi
+
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5
+$as_echo "$MAGIC_CMD" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ else
+ MAGIC_CMD=:
+ fi
+fi
+
+ fi
+ ;;
+esac
+
+# Use C for the default configuration in the libtool script
+
+lt_save_CC="$CC"
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+# Source file extension for C test sources.
+ac_ext=c
+
+# Object file extension for compiled C test sources.
+objext=o
+objext=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="int some_variable = 0;"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='int main(){return(0);}'
+
+
+
+
+
+
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+
+# Save the default compiler, since it gets overwritten when the other
+# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP.
+compiler_DEFAULT=$CC
+
+# save warnings/boilerplate of simple test code
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_compile_test_code" >conftest.$ac_ext
+eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_compiler_boilerplate=`cat conftest.err`
+$RM conftest*
+
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_link_test_code" >conftest.$ac_ext
+eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_linker_boilerplate=`cat conftest.err`
+$RM -r conftest*
+
+
+## CAVEAT EMPTOR:
+## There is no encapsulation within the following macros, do not change
+## the running order or otherwise move them around unless you know exactly
+## what you are doing...
+if test -n "$compiler"; then
+
+lt_prog_compiler_no_builtin_flag=
+
+if test "$GCC" = yes; then
+ case $cc_basename in
+ nvcc*)
+ lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;;
+ *)
+ lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;;
+ esac
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5
+$as_echo_n "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; }
+if ${lt_cv_prog_compiler_rtti_exceptions+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler_rtti_exceptions=no
+ ac_outfile=conftest.$ac_objext
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+ lt_compiler_flag="-fno-rtti -fno-exceptions"
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ # The option is referenced via a variable to avoid confusing sed.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+ (eval "$lt_compile" 2>conftest.err)
+ ac_status=$?
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ if (exit $ac_status) && test -s "$ac_outfile"; then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings other than the usual output.
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_rtti_exceptions=yes
+ fi
+ fi
+ $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5
+$as_echo "$lt_cv_prog_compiler_rtti_exceptions" >&6; }
+
+if test x"$lt_cv_prog_compiler_rtti_exceptions" = xyes; then
+ lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions"
+else
+ :
+fi
+
+fi
+
+
+
+
+
+
+ lt_prog_compiler_wl=
+lt_prog_compiler_pic=
+lt_prog_compiler_static=
+
+
+ if test "$GCC" = yes; then
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_static='-static'
+
+ case $host_os in
+ aix*)
+ # All AIX code is PIC.
+ if test "$host_cpu" = ia64; then
+ # AIX 5 now supports IA64 processor
+ lt_prog_compiler_static='-Bstatic'
+ fi
+ ;;
+
+ amigaos*)
+ case $host_cpu in
+ powerpc)
+ # see comment about AmigaOS4 .so support
+ lt_prog_compiler_pic='-fPIC'
+ ;;
+ m68k)
+ # FIXME: we need at least 68020 code to build shared libraries, but
+ # adding the `-m68020' flag to GCC prevents building anything better,
+ # like `-m68040'.
+ lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4'
+ ;;
+ esac
+ ;;
+
+ beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+ # PIC is the default for these OSes.
+ ;;
+
+ mingw* | cygwin* | pw32* | os2* | cegcc*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ # Although the cygwin gcc ignores -fPIC, still need this for old-style
+ # (--disable-auto-import) libraries
+ lt_prog_compiler_pic='-DDLL_EXPORT'
+ ;;
+
+ darwin* | rhapsody*)
+ # PIC is the default on this platform
+ # Common symbols not allowed in MH_DYLIB files
+ lt_prog_compiler_pic='-fno-common'
+ ;;
+
+ haiku*)
+ # PIC is the default for Haiku.
+ # The "-static" flag exists, but is broken.
+ lt_prog_compiler_static=
+ ;;
+
+ hpux*)
+ # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+ # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag
+ # sets the default TLS model and affects inlining.
+ case $host_cpu in
+ hppa*64*)
+ # +Z the default
+ ;;
+ *)
+ lt_prog_compiler_pic='-fPIC'
+ ;;
+ esac
+ ;;
+
+ interix[3-9]*)
+ # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+ # Instead, we relocate shared libraries at runtime.
+ ;;
+
+ msdosdjgpp*)
+ # Just because we use GCC doesn't mean we suddenly get shared libraries
+ # on systems that don't support them.
+ lt_prog_compiler_can_build_shared=no
+ enable_shared=no
+ ;;
+
+ *nto* | *qnx*)
+ # QNX uses GNU C++, but need to define -shared option too, otherwise
+ # it will coredump.
+ lt_prog_compiler_pic='-fPIC -shared'
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ lt_prog_compiler_pic=-Kconform_pic
+ fi
+ ;;
+
+ *)
+ lt_prog_compiler_pic='-fPIC'
+ ;;
+ esac
+
+ case $cc_basename in
+ nvcc*) # Cuda Compiler Driver 2.2
+ lt_prog_compiler_wl='-Xlinker '
+ if test -n "$lt_prog_compiler_pic"; then
+ lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic"
+ fi
+ ;;
+ esac
+ else
+ # PORTME Check for flag to pass linker flags through the system compiler.
+ case $host_os in
+ aix*)
+ lt_prog_compiler_wl='-Wl,'
+ if test "$host_cpu" = ia64; then
+ # AIX 5 now supports IA64 processor
+ lt_prog_compiler_static='-Bstatic'
+ else
+ lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp'
+ fi
+ ;;
+
+ mingw* | cygwin* | pw32* | os2* | cegcc*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ lt_prog_compiler_pic='-DDLL_EXPORT'
+ ;;
+
+ hpux9* | hpux10* | hpux11*)
+ lt_prog_compiler_wl='-Wl,'
+ # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but
+ # not for PA HP-UX.
+ case $host_cpu in
+ hppa*64*|ia64*)
+ # +Z the default
+ ;;
+ *)
+ lt_prog_compiler_pic='+Z'
+ ;;
+ esac
+ # Is there a better lt_prog_compiler_static that works with the bundled CC?
+ lt_prog_compiler_static='${wl}-a ${wl}archive'
+ ;;
+
+ irix5* | irix6* | nonstopux*)
+ lt_prog_compiler_wl='-Wl,'
+ # PIC (with -KPIC) is the default.
+ lt_prog_compiler_static='-non_shared'
+ ;;
+
+ linux* | k*bsd*-gnu | kopensolaris*-gnu)
+ case $cc_basename in
+ # old Intel for x86_64 which still supported -KPIC.
+ ecc*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-static'
+ ;;
+ # icc used to be incompatible with GCC.
+ # ICC 10 doesn't accept -KPIC any more.
+ icc* | ifort*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-fPIC'
+ lt_prog_compiler_static='-static'
+ ;;
+ # Lahey Fortran 8.1.
+ lf95*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='--shared'
+ lt_prog_compiler_static='--static'
+ ;;
+ nagfor*)
+ # NAG Fortran compiler
+ lt_prog_compiler_wl='-Wl,-Wl,,'
+ lt_prog_compiler_pic='-PIC'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+ pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*)
+ # Portland Group compilers (*not* the Pentium gcc compiler,
+ # which looks to be a dead project)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-fpic'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+ ccc*)
+ lt_prog_compiler_wl='-Wl,'
+ # All Alpha code is PIC.
+ lt_prog_compiler_static='-non_shared'
+ ;;
+ xl* | bgxl* | bgf* | mpixl*)
+ # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-qpic'
+ lt_prog_compiler_static='-qstaticlink'
+ ;;
+ *)
+ case `$CC -V 2>&1 | sed 5q` in
+ *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*)
+ # Sun Fortran 8.3 passes all unrecognized flags to the linker
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ lt_prog_compiler_wl=''
+ ;;
+ *Sun\ F* | *Sun*Fortran*)
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ lt_prog_compiler_wl='-Qoption ld '
+ ;;
+ *Sun\ C*)
+ # Sun C 5.9
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ lt_prog_compiler_wl='-Wl,'
+ ;;
+ *Intel*\ [CF]*Compiler*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-fPIC'
+ lt_prog_compiler_static='-static'
+ ;;
+ *Portland\ Group*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-fpic'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+
+ newsos6)
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+
+ *nto* | *qnx*)
+ # QNX uses GNU C++, but need to define -shared option too, otherwise
+ # it will coredump.
+ lt_prog_compiler_pic='-fPIC -shared'
+ ;;
+
+ osf3* | osf4* | osf5*)
+ lt_prog_compiler_wl='-Wl,'
+ # All OSF/1 code is PIC.
+ lt_prog_compiler_static='-non_shared'
+ ;;
+
+ rdos*)
+ lt_prog_compiler_static='-non_shared'
+ ;;
+
+ solaris*)
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ case $cc_basename in
+ f77* | f90* | f95* | sunf77* | sunf90* | sunf95*)
+ lt_prog_compiler_wl='-Qoption ld ';;
+ *)
+ lt_prog_compiler_wl='-Wl,';;
+ esac
+ ;;
+
+ sunos4*)
+ lt_prog_compiler_wl='-Qoption ld '
+ lt_prog_compiler_pic='-PIC'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+
+ sysv4 | sysv4.2uw2* | sysv4.3*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec ;then
+ lt_prog_compiler_pic='-Kconform_pic'
+ lt_prog_compiler_static='-Bstatic'
+ fi
+ ;;
+
+ sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_pic='-KPIC'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+
+ unicos*)
+ lt_prog_compiler_wl='-Wl,'
+ lt_prog_compiler_can_build_shared=no
+ ;;
+
+ uts4*)
+ lt_prog_compiler_pic='-pic'
+ lt_prog_compiler_static='-Bstatic'
+ ;;
+
+ *)
+ lt_prog_compiler_can_build_shared=no
+ ;;
+ esac
+ fi
+
+case $host_os in
+ # For platforms which do not support PIC, -DPIC is meaningless:
+ *djgpp*)
+ lt_prog_compiler_pic=
+ ;;
+ *)
+ lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC"
+ ;;
+esac
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5
+$as_echo_n "checking for $compiler option to produce PIC... " >&6; }
+if ${lt_cv_prog_compiler_pic+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler_pic=$lt_prog_compiler_pic
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5
+$as_echo "$lt_cv_prog_compiler_pic" >&6; }
+lt_prog_compiler_pic=$lt_cv_prog_compiler_pic
+
+#
+# Check to make sure the PIC flag actually works.
+#
+if test -n "$lt_prog_compiler_pic"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5
+$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; }
+if ${lt_cv_prog_compiler_pic_works+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler_pic_works=no
+ ac_outfile=conftest.$ac_objext
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+ lt_compiler_flag="$lt_prog_compiler_pic -DPIC"
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ # The option is referenced via a variable to avoid confusing sed.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+ (eval "$lt_compile" 2>conftest.err)
+ ac_status=$?
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ if (exit $ac_status) && test -s "$ac_outfile"; then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings other than the usual output.
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_pic_works=yes
+ fi
+ fi
+ $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5
+$as_echo "$lt_cv_prog_compiler_pic_works" >&6; }
+
+if test x"$lt_cv_prog_compiler_pic_works" = xyes; then
+ case $lt_prog_compiler_pic in
+ "" | " "*) ;;
+ *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;;
+ esac
+else
+ lt_prog_compiler_pic=
+ lt_prog_compiler_can_build_shared=no
+fi
+
+fi
+
+
+
+
+
+
+
+
+
+
+
+#
+# Check to make sure the static flag actually works.
+#
+wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5
+$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; }
+if ${lt_cv_prog_compiler_static_works+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler_static_works=no
+ save_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS $lt_tmp_static_flag"
+ echo "$lt_simple_link_test_code" > conftest.$ac_ext
+ if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+ # The linker can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s conftest.err; then
+ # Append any errors to the config.log.
+ cat conftest.err 1>&5
+ $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if diff conftest.exp conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_static_works=yes
+ fi
+ else
+ lt_cv_prog_compiler_static_works=yes
+ fi
+ fi
+ $RM -r conftest*
+ LDFLAGS="$save_LDFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5
+$as_echo "$lt_cv_prog_compiler_static_works" >&6; }
+
+if test x"$lt_cv_prog_compiler_static_works" = xyes; then
+ :
+else
+ lt_prog_compiler_static=
+fi
+
+
+
+
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if ${lt_cv_prog_compiler_c_o+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler_c_o=no
+ $RM -r conftest 2>/dev/null
+ mkdir conftest
+ cd conftest
+ mkdir out
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+ lt_compiler_flag="-o out/conftest2.$ac_objext"
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+ (eval "$lt_compile" 2>out/conftest.err)
+ ac_status=$?
+ cat out/conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ if (exit $ac_status) && test -s out/conftest2.$ac_objext
+ then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+ $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+ if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_c_o=yes
+ fi
+ fi
+ chmod u+w . 2>&5
+ $RM conftest*
+ # SGI C++ compiler will create directory out/ii_files/ for
+ # template instantiation
+ test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+ $RM out/* && rmdir out
+ cd ..
+ $RM -r conftest
+ $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5
+$as_echo "$lt_cv_prog_compiler_c_o" >&6; }
+
+
+
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if ${lt_cv_prog_compiler_c_o+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler_c_o=no
+ $RM -r conftest 2>/dev/null
+ mkdir conftest
+ cd conftest
+ mkdir out
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+ lt_compiler_flag="-o out/conftest2.$ac_objext"
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+ (eval "$lt_compile" 2>out/conftest.err)
+ ac_status=$?
+ cat out/conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ if (exit $ac_status) && test -s out/conftest2.$ac_objext
+ then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+ $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+ if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_c_o=yes
+ fi
+ fi
+ chmod u+w . 2>&5
+ $RM conftest*
+ # SGI C++ compiler will create directory out/ii_files/ for
+ # template instantiation
+ test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+ $RM out/* && rmdir out
+ cd ..
+ $RM -r conftest
+ $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5
+$as_echo "$lt_cv_prog_compiler_c_o" >&6; }
+
+
+
+
+hard_links="nottested"
+if test "$lt_cv_prog_compiler_c_o" = no && test "$need_locks" != no; then
+ # do not overwrite the value of need_locks provided by the user
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5
+$as_echo_n "checking if we can lock with hard links... " >&6; }
+ hard_links=yes
+ $RM conftest*
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ touch conftest.a
+ ln conftest.a conftest.b 2>&5 || hard_links=no
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5
+$as_echo "$hard_links" >&6; }
+ if test "$hard_links" = no; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&5
+$as_echo "$as_me: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2;}
+ need_locks=warn
+ fi
+else
+ need_locks=no
+fi
+
+
+
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5
+$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; }
+
+ runpath_var=
+ allow_undefined_flag=
+ always_export_symbols=no
+ archive_cmds=
+ archive_expsym_cmds=
+ compiler_needs_object=no
+ enable_shared_with_static_runtimes=no
+ export_dynamic_flag_spec=
+ export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+ hardcode_automatic=no
+ hardcode_direct=no
+ hardcode_direct_absolute=no
+ hardcode_libdir_flag_spec=
+ hardcode_libdir_separator=
+ hardcode_minus_L=no
+ hardcode_shlibpath_var=unsupported
+ inherit_rpath=no
+ link_all_deplibs=unknown
+ module_cmds=
+ module_expsym_cmds=
+ old_archive_from_new_cmds=
+ old_archive_from_expsyms_cmds=
+ thread_safe_flag_spec=
+ whole_archive_flag_spec=
+ # include_expsyms should be a list of space-separated symbols to be *always*
+ # included in the symbol list
+ include_expsyms=
+ # exclude_expsyms can be an extended regexp of symbols to exclude
+ # it will be wrapped by ` (' and `)$', so one must not match beginning or
+ # end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc',
+ # as well as any symbol that contains `d'.
+ exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'
+ # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out
+ # platforms (ab)use it in PIC code, but their linkers get confused if
+ # the symbol is explicitly referenced. Since portable code cannot
+ # rely on this symbol name, it's probably fine to never include it in
+ # preloaded symbol tables.
+ # Exclude shared library initialization/finalization symbols.
+ extract_expsyms_cmds=
+
+ case $host_os in
+ cygwin* | mingw* | pw32* | cegcc*)
+ # FIXME: the MSVC++ port hasn't been tested in a loooong time
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++.
+ if test "$GCC" != yes; then
+ with_gnu_ld=no
+ fi
+ ;;
+ interix*)
+ # we just hope/assume this is gcc and not c89 (= MSVC++)
+ with_gnu_ld=yes
+ ;;
+ openbsd*)
+ with_gnu_ld=no
+ ;;
+ esac
+
+ ld_shlibs=yes
+
+ # On some targets, GNU ld is compatible enough with the native linker
+ # that we're better off using the native interface for both.
+ lt_use_gnu_ld_interface=no
+ if test "$with_gnu_ld" = yes; then
+ case $host_os in
+ aix*)
+ # The AIX port of GNU ld has always aspired to compatibility
+ # with the native linker. However, as the warning in the GNU ld
+ # block says, versions before 2.19.5* couldn't really create working
+ # shared libraries, regardless of the interface used.
+ case `$LD -v 2>&1` in
+ *\ \(GNU\ Binutils\)\ 2.19.5*) ;;
+ *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;;
+ *\ \(GNU\ Binutils\)\ [3-9]*) ;;
+ *)
+ lt_use_gnu_ld_interface=yes
+ ;;
+ esac
+ ;;
+ *)
+ lt_use_gnu_ld_interface=yes
+ ;;
+ esac
+ fi
+
+ if test "$lt_use_gnu_ld_interface" = yes; then
+ # If archive_cmds runs LD, not CC, wlarc should be empty
+ wlarc='${wl}'
+
+ # Set some defaults for GNU ld with shared library support. These
+ # are reset later if shared libraries are not supported. Putting them
+ # here allows them to be overridden if necessary.
+ runpath_var=LD_RUN_PATH
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ export_dynamic_flag_spec='${wl}--export-dynamic'
+ # ancient GNU ld didn't support --whole-archive et. al.
+ if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then
+ whole_archive_flag_spec="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+ else
+ whole_archive_flag_spec=
+ fi
+ supports_anon_versioning=no
+ case `$LD -v 2>&1` in
+ *GNU\ gold*) supports_anon_versioning=yes ;;
+ *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11
+ *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ...
+ *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ...
+ *\ 2.11.*) ;; # other 2.11 versions
+ *) supports_anon_versioning=yes ;;
+ esac
+
+ # See if GNU ld supports shared libraries.
+ case $host_os in
+ aix[3-9]*)
+ # On AIX/PPC, the GNU linker is very broken
+ if test "$host_cpu" != ia64; then
+ ld_shlibs=no
+ cat <<_LT_EOF 1>&2
+
+*** Warning: the GNU linker, at least up to release 2.19, is reported
+*** to be unable to reliably create shared libraries on AIX.
+*** Therefore, libtool is disabling shared libraries support. If you
+*** really care for shared libraries, you may want to install binutils
+*** 2.20 or above, or modify your PATH so that a non-GNU linker is found.
+*** You will then need to restart the configuration process.
+
+_LT_EOF
+ fi
+ ;;
+
+ amigaos*)
+ case $host_cpu in
+ powerpc)
+ # see comment about AmigaOS4 .so support
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds=''
+ ;;
+ m68k)
+ archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ ;;
+ esac
+ ;;
+
+ beos*)
+ if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ allow_undefined_flag=unsupported
+ # Joseph Beckenbach <jrb3@best.com> says some releases of gcc
+ # support --undefined. This deserves some investigation. FIXME
+ archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ cygwin* | mingw* | pw32* | cegcc*)
+ # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless,
+ # as there is no search path for DLLs.
+ hardcode_libdir_flag_spec='-L$libdir'
+ export_dynamic_flag_spec='${wl}--export-all-symbols'
+ allow_undefined_flag=unsupported
+ always_export_symbols=no
+ enable_shared_with_static_runtimes=yes
+ export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols'
+ exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'
+
+ if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+ # If the export-symbols file already is a .def file (1st line
+ # is EXPORTS), use it as is; otherwise, prepend...
+ archive_expsym_cmds='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+ cp $export_symbols $output_objdir/$soname.def;
+ else
+ echo EXPORTS > $output_objdir/$soname.def;
+ cat $export_symbols >> $output_objdir/$soname.def;
+ fi~
+ $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ haiku*)
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ link_all_deplibs=yes
+ ;;
+
+ interix[3-9]*)
+ hardcode_direct=no
+ hardcode_shlibpath_var=no
+ hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+ export_dynamic_flag_spec='${wl}-E'
+ # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+ # Instead, shared libraries are loaded at an image base (0x10000000 by
+ # default) and relocated if they conflict, which is a slow very memory
+ # consuming and fragmenting process. To avoid this, we pick a random,
+ # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+ # time. Moving up from 0x10000000 also allows more sbrk(2) space.
+ archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+ archive_expsym_cmds='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+ ;;
+
+ gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu)
+ tmp_diet=no
+ if test "$host_os" = linux-dietlibc; then
+ case $cc_basename in
+ diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn)
+ esac
+ fi
+ if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \
+ && test "$tmp_diet" = no
+ then
+ tmp_addflag=' $pic_flag'
+ tmp_sharedflag='-shared'
+ case $cc_basename,$host_cpu in
+ pgcc*) # Portland Group C compiler
+ whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+ tmp_addflag=' $pic_flag'
+ ;;
+ pgf77* | pgf90* | pgf95* | pgfortran*)
+ # Portland Group f77 and f90 compilers
+ whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+ tmp_addflag=' $pic_flag -Mnomain' ;;
+ ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64
+ tmp_addflag=' -i_dynamic' ;;
+ efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64
+ tmp_addflag=' -i_dynamic -nofor_main' ;;
+ ifc* | ifort*) # Intel Fortran compiler
+ tmp_addflag=' -nofor_main' ;;
+ lf95*) # Lahey Fortran 8.1
+ whole_archive_flag_spec=
+ tmp_sharedflag='--shared' ;;
+ xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below)
+ tmp_sharedflag='-qmkshrobj'
+ tmp_addflag= ;;
+ nvcc*) # Cuda Compiler Driver 2.2
+ whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+ compiler_needs_object=yes
+ ;;
+ esac
+ case `$CC -V 2>&1 | sed 5q` in
+ *Sun\ C*) # Sun C 5.9
+ whole_archive_flag_spec='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+ compiler_needs_object=yes
+ tmp_sharedflag='-G' ;;
+ *Sun\ F*) # Sun Fortran 8.3
+ tmp_sharedflag='-G' ;;
+ esac
+ archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+
+ if test "x$supports_anon_versioning" = xyes; then
+ archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~
+ cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+ echo "local: *; };" >> $output_objdir/$libname.ver~
+ $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib'
+ fi
+
+ case $cc_basename in
+ xlf* | bgf* | bgxlf* | mpixlf*)
+ # IBM XL Fortran 10.1 on PPC cannot create shared libs itself
+ whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive'
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib'
+ if test "x$supports_anon_versioning" = xyes; then
+ archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~
+ cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+ echo "local: *; };" >> $output_objdir/$libname.ver~
+ $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib'
+ fi
+ ;;
+ esac
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ netbsd*)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib'
+ wlarc=
+ else
+ archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ fi
+ ;;
+
+ solaris*)
+ if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then
+ ld_shlibs=no
+ cat <<_LT_EOF 1>&2
+
+*** Warning: The releases 2.8.* of the GNU linker cannot reliably
+*** create shared libraries on Solaris systems. Therefore, libtool
+*** is disabling shared libraries support. We urge you to upgrade GNU
+*** binutils to release 2.9.1 or newer. Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+ elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)
+ case `$LD -v 2>&1` in
+ *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*)
+ ld_shlibs=no
+ cat <<_LT_EOF 1>&2
+
+*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not
+*** reliably create shared libraries on SCO systems. Therefore, libtool
+*** is disabling shared libraries support. We urge you to upgrade GNU
+*** binutils to release 2.16.91.0.3 or newer. Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+ ;;
+ *)
+ # For security reasons, it is highly recommended that you always
+ # use absolute paths for naming shared libraries, and exclude the
+ # DT_RUNPATH tag from executables and libraries. But doing so
+ # requires that you compile everything twice, which is a pain.
+ if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ esac
+ ;;
+
+ sunos4*)
+ archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ wlarc=
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ *)
+ if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ esac
+
+ if test "$ld_shlibs" = no; then
+ runpath_var=
+ hardcode_libdir_flag_spec=
+ export_dynamic_flag_spec=
+ whole_archive_flag_spec=
+ fi
+ else
+ # PORTME fill in a description of your system's linker (not GNU ld)
+ case $host_os in
+ aix3*)
+ allow_undefined_flag=unsupported
+ always_export_symbols=yes
+ archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname'
+ # Note: this linker hardcodes the directories in LIBPATH if there
+ # are no directories specified by -L.
+ hardcode_minus_L=yes
+ if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then
+ # Neither direct hardcoding nor static linking is supported with a
+ # broken collect2.
+ hardcode_direct=unsupported
+ fi
+ ;;
+
+ aix[4-9]*)
+ if test "$host_cpu" = ia64; then
+ # On IA64, the linker does run time linking by default, so we don't
+ # have to do anything special.
+ aix_use_runtimelinking=no
+ exp_sym_flag='-Bexport'
+ no_entry_flag=""
+ else
+ # If we're using GNU nm, then we don't want the "-C" option.
+ # -C means demangle to AIX nm, but means don't demangle with GNU nm
+ # Also, AIX nm treats weak defined symbols like other global
+ # defined symbols, whereas GNU nm marks them as "W".
+ if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+ export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+ else
+ export_symbols_cmds='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+ fi
+ aix_use_runtimelinking=no
+
+ # Test if we are trying to use run time linking or normal
+ # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+ # need to do runtime linking.
+ case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*)
+ for ld_flag in $LDFLAGS; do
+ if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then
+ aix_use_runtimelinking=yes
+ break
+ fi
+ done
+ ;;
+ esac
+
+ exp_sym_flag='-bexport'
+ no_entry_flag='-bnoentry'
+ fi
+
+ # When large executables or shared objects are built, AIX ld can
+ # have problems creating the table of contents. If linking a library
+ # or program results in "error TOC overflow" add -mminimal-toc to
+ # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not
+ # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+ archive_cmds=''
+ hardcode_direct=yes
+ hardcode_direct_absolute=yes
+ hardcode_libdir_separator=':'
+ link_all_deplibs=yes
+ file_list_spec='${wl}-f,'
+
+ if test "$GCC" = yes; then
+ case $host_os in aix4.[012]|aix4.[012].*)
+ # We only want to do this on AIX 4.2 and lower, the check
+ # below for broken collect2 doesn't work under 4.3+
+ collect2name=`${CC} -print-prog-name=collect2`
+ if test -f "$collect2name" &&
+ strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+ then
+ # We have reworked collect2
+ :
+ else
+ # We have old collect2
+ hardcode_direct=unsupported
+ # It fails to find uninstalled libraries when the uninstalled
+ # path is not listed in the libpath. Setting hardcode_minus_L
+ # to unsupported forces relinking
+ hardcode_minus_L=yes
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_libdir_separator=
+ fi
+ ;;
+ esac
+ shared_flag='-shared'
+ if test "$aix_use_runtimelinking" = yes; then
+ shared_flag="$shared_flag "'${wl}-G'
+ fi
+ else
+ # not using gcc
+ if test "$host_cpu" = ia64; then
+ # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+ # chokes on -Wl,-G. The following line is correct:
+ shared_flag='-G'
+ else
+ if test "$aix_use_runtimelinking" = yes; then
+ shared_flag='${wl}-G'
+ else
+ shared_flag='${wl}-bM:SRE'
+ fi
+ fi
+ fi
+
+ export_dynamic_flag_spec='${wl}-bexpall'
+ # It seems that -bexpall does not export symbols beginning with
+ # underscore (_), so it is better to generate a list of symbols to export.
+ always_export_symbols=yes
+ if test "$aix_use_runtimelinking" = yes; then
+ # Warning - without using the other runtime loading flags (-brtl),
+ # -berok will link without error, but may produce a broken library.
+ allow_undefined_flag='-berok'
+ # Determine the default libpath from the value encoded in an
+ # empty executable.
+ if test "${lt_cv_aix_libpath+set}" = set; then
+ aix_libpath=$lt_cv_aix_libpath
+else
+ if ${lt_cv_aix_libpath_+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+
+ lt_aix_libpath_sed='
+ /Import File Strings/,/^$/ {
+ /^0/ {
+ s/^0 *\([^ ]*\) *$/\1/
+ p
+ }
+ }'
+ lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ # Check for a 64-bit object if we didn't find anything.
+ if test -z "$lt_cv_aix_libpath_"; then
+ lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ if test -z "$lt_cv_aix_libpath_"; then
+ lt_cv_aix_libpath_="/usr/lib:/lib"
+ fi
+
+fi
+
+ aix_libpath=$lt_cv_aix_libpath_
+fi
+
+ hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath"
+ archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+ else
+ if test "$host_cpu" = ia64; then
+ hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib'
+ allow_undefined_flag="-z nodefs"
+ archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols"
+ else
+ # Determine the default libpath from the value encoded in an
+ # empty executable.
+ if test "${lt_cv_aix_libpath+set}" = set; then
+ aix_libpath=$lt_cv_aix_libpath
+else
+ if ${lt_cv_aix_libpath_+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+
+ lt_aix_libpath_sed='
+ /Import File Strings/,/^$/ {
+ /^0/ {
+ s/^0 *\([^ ]*\) *$/\1/
+ p
+ }
+ }'
+ lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ # Check for a 64-bit object if we didn't find anything.
+ if test -z "$lt_cv_aix_libpath_"; then
+ lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ if test -z "$lt_cv_aix_libpath_"; then
+ lt_cv_aix_libpath_="/usr/lib:/lib"
+ fi
+
+fi
+
+ aix_libpath=$lt_cv_aix_libpath_
+fi
+
+ hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath"
+ # Warning - without using the other run time loading flags,
+ # -berok will link without error, but may produce a broken library.
+ no_undefined_flag=' ${wl}-bernotok'
+ allow_undefined_flag=' ${wl}-berok'
+ if test "$with_gnu_ld" = yes; then
+ # We only use this code for GNU lds that support --whole-archive.
+ whole_archive_flag_spec='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+ else
+ # Exported symbols can be pulled into shared objects from archives
+ whole_archive_flag_spec='$convenience'
+ fi
+ archive_cmds_need_lc=yes
+ # This is similar to how AIX traditionally builds its shared libraries.
+ archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname'
+ fi
+ fi
+ ;;
+
+ amigaos*)
+ case $host_cpu in
+ powerpc)
+ # see comment about AmigaOS4 .so support
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds=''
+ ;;
+ m68k)
+ archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ ;;
+ esac
+ ;;
+
+ bsdi[45]*)
+ export_dynamic_flag_spec=-rdynamic
+ ;;
+
+ cygwin* | mingw* | pw32* | cegcc*)
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++.
+ # hardcode_libdir_flag_spec is actually meaningless, as there is
+ # no search path for DLLs.
+ case $cc_basename in
+ cl*)
+ # Native MSVC
+ hardcode_libdir_flag_spec=' '
+ allow_undefined_flag=unsupported
+ always_export_symbols=yes
+ file_list_spec='@'
+ # Tell ltmain to make .lib files, not .a files.
+ libext=lib
+ # Tell ltmain to make .dll files, not .so files.
+ shrext_cmds=".dll"
+ # FIXME: Setting linknames here is a bad hack.
+ archive_cmds='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames='
+ archive_expsym_cmds='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+ sed -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp;
+ else
+ sed -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp;
+ fi~
+ $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+ linknames='
+ # The linker will not automatically build a static lib if we build a DLL.
+ # _LT_TAGVAR(old_archive_from_new_cmds, )='true'
+ enable_shared_with_static_runtimes=yes
+ exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+ export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols'
+ # Don't use ranlib
+ old_postinstall_cmds='chmod 644 $oldlib'
+ postlink_cmds='lt_outputfile="@OUTPUT@"~
+ lt_tool_outputfile="@TOOL_OUTPUT@"~
+ case $lt_outputfile in
+ *.exe|*.EXE) ;;
+ *)
+ lt_outputfile="$lt_outputfile.exe"
+ lt_tool_outputfile="$lt_tool_outputfile.exe"
+ ;;
+ esac~
+ if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then
+ $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+ $RM "$lt_outputfile.manifest";
+ fi'
+ ;;
+ *)
+ # Assume MSVC wrapper
+ hardcode_libdir_flag_spec=' '
+ allow_undefined_flag=unsupported
+ # Tell ltmain to make .lib files, not .a files.
+ libext=lib
+ # Tell ltmain to make .dll files, not .so files.
+ shrext_cmds=".dll"
+ # FIXME: Setting linknames here is a bad hack.
+ archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames='
+ # The linker will automatically build a .lib file if we build a DLL.
+ old_archive_from_new_cmds='true'
+ # FIXME: Should let the user specify the lib program.
+ old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs'
+ enable_shared_with_static_runtimes=yes
+ ;;
+ esac
+ ;;
+
+ darwin* | rhapsody*)
+
+
+ archive_cmds_need_lc=no
+ hardcode_direct=no
+ hardcode_automatic=yes
+ hardcode_shlibpath_var=unsupported
+ if test "$lt_cv_ld_force_load" = "yes"; then
+ whole_archive_flag_spec='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience ${wl}-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`'
+
+ else
+ whole_archive_flag_spec=''
+ fi
+ link_all_deplibs=yes
+ allow_undefined_flag="$_lt_dar_allow_undefined"
+ case $cc_basename in
+ ifort*) _lt_dar_can_shared=yes ;;
+ *) _lt_dar_can_shared=$GCC ;;
+ esac
+ if test "$_lt_dar_can_shared" = "yes"; then
+ output_verbose_link_cmd=func_echo_all
+ archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}"
+ module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}"
+ archive_expsym_cmds="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}"
+ module_expsym_cmds="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}"
+
+ else
+ ld_shlibs=no
+ fi
+
+ ;;
+
+ dgux*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_shlibpath_var=no
+ ;;
+
+ # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor
+ # support. Future versions do this automatically, but an explicit c++rt0.o
+ # does not break anything, and helps significantly (at the cost of a little
+ # extra space).
+ freebsd2.2*)
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o'
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ # Unfortunately, older versions of FreeBSD 2 do not have this feature.
+ freebsd2.*)
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes
+ hardcode_minus_L=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ # FreeBSD 3 and greater uses gcc -shared to do shared libraries.
+ freebsd* | dragonfly*)
+ archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ hpux9*)
+ if test "$GCC" = yes; then
+ archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+ else
+ archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+ fi
+ hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+ hardcode_libdir_separator=:
+ hardcode_direct=yes
+
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ hardcode_minus_L=yes
+ export_dynamic_flag_spec='${wl}-E'
+ ;;
+
+ hpux10*)
+ if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+ archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+ fi
+ if test "$with_gnu_ld" = no; then
+ hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+ hardcode_libdir_separator=:
+ hardcode_direct=yes
+ hardcode_direct_absolute=yes
+ export_dynamic_flag_spec='${wl}-E'
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ hardcode_minus_L=yes
+ fi
+ ;;
+
+ hpux11*)
+ if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+ case $host_cpu in
+ hppa*64*)
+ archive_cmds='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ ia64*)
+ archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ *)
+ archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ esac
+ else
+ case $host_cpu in
+ hppa*64*)
+ archive_cmds='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ ia64*)
+ archive_cmds='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ *)
+
+ # Older versions of the 11.00 compiler do not understand -b yet
+ # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5
+$as_echo_n "checking if $CC understands -b... " >&6; }
+if ${lt_cv_prog_compiler__b+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler__b=no
+ save_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS -b"
+ echo "$lt_simple_link_test_code" > conftest.$ac_ext
+ if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+ # The linker can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s conftest.err; then
+ # Append any errors to the config.log.
+ cat conftest.err 1>&5
+ $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if diff conftest.exp conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler__b=yes
+ fi
+ else
+ lt_cv_prog_compiler__b=yes
+ fi
+ fi
+ $RM -r conftest*
+ LDFLAGS="$save_LDFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5
+$as_echo "$lt_cv_prog_compiler__b" >&6; }
+
+if test x"$lt_cv_prog_compiler__b" = xyes; then
+ archive_cmds='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+else
+ archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+fi
+
+ ;;
+ esac
+ fi
+ if test "$with_gnu_ld" = no; then
+ hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+ hardcode_libdir_separator=:
+
+ case $host_cpu in
+ hppa*64*|ia64*)
+ hardcode_direct=no
+ hardcode_shlibpath_var=no
+ ;;
+ *)
+ hardcode_direct=yes
+ hardcode_direct_absolute=yes
+ export_dynamic_flag_spec='${wl}-E'
+
+ # hardcode_minus_L: Not really in the search PATH,
+ # but as the default location of the library.
+ hardcode_minus_L=yes
+ ;;
+ esac
+ fi
+ ;;
+
+ irix5* | irix6* | nonstopux*)
+ if test "$GCC" = yes; then
+ archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ # Try to use the -exported_symbol ld option, if it does not
+ # work, assume that -exports_file does not work either and
+ # implicitly export all symbols.
+ # This should be the same for all languages, so no per-tag cache variable.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5
+$as_echo_n "checking whether the $host_os linker accepts -exported_symbol... " >&6; }
+if ${lt_cv_irix_exported_symbol+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ save_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS -shared ${wl}-exported_symbol ${wl}foo ${wl}-update_registry ${wl}/dev/null"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+int foo (void) { return 0; }
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ lt_cv_irix_exported_symbol=yes
+else
+ lt_cv_irix_exported_symbol=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ LDFLAGS="$save_LDFLAGS"
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5
+$as_echo "$lt_cv_irix_exported_symbol" >&6; }
+ if test "$lt_cv_irix_exported_symbol" = yes; then
+ archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations ${wl}-exports_file ${wl}$export_symbols -o $lib'
+ fi
+ else
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+ archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -exports_file $export_symbols -o $lib'
+ fi
+ archive_cmds_need_lc='no'
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ inherit_rpath=yes
+ link_all_deplibs=yes
+ ;;
+
+ netbsd*)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out
+ else
+ archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF
+ fi
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ newsos6)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ hardcode_shlibpath_var=no
+ ;;
+
+ *nto* | *qnx*)
+ ;;
+
+ openbsd*)
+ if test -f /usr/libexec/ld.so; then
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ hardcode_direct_absolute=yes
+ if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols'
+ hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+ export_dynamic_flag_spec='${wl}-E'
+ else
+ case $host_os in
+ openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*)
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='-R$libdir'
+ ;;
+ *)
+ archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+ hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+ ;;
+ esac
+ fi
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ os2*)
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ allow_undefined_flag=unsupported
+ archive_cmds='$ECHO "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~echo DATA >> $output_objdir/$libname.def~echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def'
+ old_archive_from_new_cmds='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def'
+ ;;
+
+ osf3*)
+ if test "$GCC" = yes; then
+ allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*'
+ archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ else
+ allow_undefined_flag=' -expect_unresolved \*'
+ archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+ fi
+ archive_cmds_need_lc='no'
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ ;;
+
+ osf4* | osf5*) # as osf3* with the addition of -msym flag
+ if test "$GCC" = yes; then
+ allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*'
+ archive_cmds='$CC -shared${allow_undefined_flag} $pic_flag $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ else
+ allow_undefined_flag=' -expect_unresolved \*'
+ archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+ archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~
+ $CC -shared${allow_undefined_flag} ${wl}-input ${wl}$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~$RM $lib.exp'
+
+ # Both c and cxx compiler support -rpath directly
+ hardcode_libdir_flag_spec='-rpath $libdir'
+ fi
+ archive_cmds_need_lc='no'
+ hardcode_libdir_separator=:
+ ;;
+
+ solaris*)
+ no_undefined_flag=' -z defs'
+ if test "$GCC" = yes; then
+ wlarc='${wl}'
+ archive_cmds='$CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+ else
+ case `$CC -V 2>&1` in
+ *"Compilers 5.0"*)
+ wlarc=''
+ archive_cmds='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp'
+ ;;
+ *)
+ wlarc='${wl}'
+ archive_cmds='$CC -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+ ;;
+ esac
+ fi
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_shlibpath_var=no
+ case $host_os in
+ solaris2.[0-5] | solaris2.[0-5].*) ;;
+ *)
+ # The compiler driver will combine and reorder linker options,
+ # but understands `-z linker_flag'. GCC discards it without `$wl',
+ # but is careful enough not to reorder.
+ # Supported since Solaris 2.6 (maybe 2.5.1?)
+ if test "$GCC" = yes; then
+ whole_archive_flag_spec='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract'
+ else
+ whole_archive_flag_spec='-z allextract$convenience -z defaultextract'
+ fi
+ ;;
+ esac
+ link_all_deplibs=yes
+ ;;
+
+ sunos4*)
+ if test "x$host_vendor" = xsequent; then
+ # Use $CC to link under sequent, because it throws in some extra .o
+ # files that make .init and .fini sections work.
+ archive_cmds='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags'
+ fi
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_direct=yes
+ hardcode_minus_L=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ sysv4)
+ case $host_vendor in
+ sni)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes # is this really true???
+ ;;
+ siemens)
+ ## LD is ld it makes a PLAMLIB
+ ## CC just makes a GrossModule.
+ archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags'
+ reload_cmds='$CC -r -o $output$reload_objs'
+ hardcode_direct=no
+ ;;
+ motorola)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=no #Motorola manual says yes, but my tests say they lie
+ ;;
+ esac
+ runpath_var='LD_RUN_PATH'
+ hardcode_shlibpath_var=no
+ ;;
+
+ sysv4.3*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_shlibpath_var=no
+ export_dynamic_flag_spec='-Bexport'
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_shlibpath_var=no
+ runpath_var=LD_RUN_PATH
+ hardcode_runpath_var=yes
+ ld_shlibs=yes
+ fi
+ ;;
+
+ sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*)
+ no_undefined_flag='${wl}-z,text'
+ archive_cmds_need_lc=no
+ hardcode_shlibpath_var=no
+ runpath_var='LD_RUN_PATH'
+
+ if test "$GCC" = yes; then
+ archive_cmds='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ fi
+ ;;
+
+ sysv5* | sco3.2v5* | sco5v6*)
+ # Note: We can NOT use -z defs as we might desire, because we do not
+ # link with -lc, and that would cause any symbols used from libc to
+ # always be unresolved, which means just about no library would
+ # ever link correctly. If we're not using GNU ld we use -z text
+ # though, which does catch some bad symbols but isn't as heavy-handed
+ # as -z defs.
+ no_undefined_flag='${wl}-z,text'
+ allow_undefined_flag='${wl}-z,nodefs'
+ archive_cmds_need_lc=no
+ hardcode_shlibpath_var=no
+ hardcode_libdir_flag_spec='${wl}-R,$libdir'
+ hardcode_libdir_separator=':'
+ link_all_deplibs=yes
+ export_dynamic_flag_spec='${wl}-Bexport'
+ runpath_var='LD_RUN_PATH'
+
+ if test "$GCC" = yes; then
+ archive_cmds='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ fi
+ ;;
+
+ uts4*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_shlibpath_var=no
+ ;;
+
+ *)
+ ld_shlibs=no
+ ;;
+ esac
+
+ if test x$host_vendor = xsni; then
+ case $host in
+ sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+ export_dynamic_flag_spec='${wl}-Blargedynsym'
+ ;;
+ esac
+ fi
+ fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5
+$as_echo "$ld_shlibs" >&6; }
+test "$ld_shlibs" = no && can_build_shared=no
+
+with_gnu_ld=$with_gnu_ld
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#
+# Do we need to explicitly link libc?
+#
+case "x$archive_cmds_need_lc" in
+x|xyes)
+ # Assume -lc should be added
+ archive_cmds_need_lc=yes
+
+ if test "$enable_shared" = yes && test "$GCC" = yes; then
+ case $archive_cmds in
+ *'~'*)
+ # FIXME: we may have to deal with multi-command sequences.
+ ;;
+ '$CC '*)
+ # Test whether the compiler implicitly links with -lc since on some
+ # systems, -lgcc has to come before -lc. If gcc already passes -lc
+ # to ld, don't add -lc before -lgcc.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5
+$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; }
+if ${lt_cv_archive_cmds_need_lc+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ $RM conftest*
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } 2>conftest.err; then
+ soname=conftest
+ lib=conftest
+ libobjs=conftest.$ac_objext
+ deplibs=
+ wl=$lt_prog_compiler_wl
+ pic_flag=$lt_prog_compiler_pic
+ compiler_flags=-v
+ linker_flags=-v
+ verstring=
+ output_objdir=.
+ libname=conftest
+ lt_save_allow_undefined_flag=$allow_undefined_flag
+ allow_undefined_flag=
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5
+ (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ then
+ lt_cv_archive_cmds_need_lc=no
+ else
+ lt_cv_archive_cmds_need_lc=yes
+ fi
+ allow_undefined_flag=$lt_save_allow_undefined_flag
+ else
+ cat conftest.err 1>&5
+ fi
+ $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5
+$as_echo "$lt_cv_archive_cmds_need_lc" >&6; }
+ archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc
+ ;;
+ esac
+ fi
+ ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5
+$as_echo_n "checking dynamic linker characteristics... " >&6; }
+
+if test "$GCC" = yes; then
+ case $host_os in
+ darwin*) lt_awk_arg="/^libraries:/,/LR/" ;;
+ *) lt_awk_arg="/^libraries:/" ;;
+ esac
+ case $host_os in
+ mingw* | cegcc*) lt_sed_strip_eq="s,=\([A-Za-z]:\),\1,g" ;;
+ *) lt_sed_strip_eq="s,=/,/,g" ;;
+ esac
+ lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq`
+ case $lt_search_path_spec in
+ *\;*)
+ # if the path contains ";" then we assume it to be the separator
+ # otherwise default to the standard path separator (i.e. ":") - it is
+ # assumed that no part of a normal pathname contains ";" but that should
+ # okay in the real world where ";" in dirpaths is itself problematic.
+ lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'`
+ ;;
+ *)
+ lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"`
+ ;;
+ esac
+ # Ok, now we have the path, separated by spaces, we can step through it
+ # and add multilib dir if necessary.
+ lt_tmp_lt_search_path_spec=
+ lt_multi_os_dir=`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null`
+ for lt_sys_path in $lt_search_path_spec; do
+ if test -d "$lt_sys_path/$lt_multi_os_dir"; then
+ lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path/$lt_multi_os_dir"
+ else
+ test -d "$lt_sys_path" && \
+ lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path"
+ fi
+ done
+ lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk '
+BEGIN {RS=" "; FS="/|\n";} {
+ lt_foo="";
+ lt_count=0;
+ for (lt_i = NF; lt_i > 0; lt_i--) {
+ if ($lt_i != "" && $lt_i != ".") {
+ if ($lt_i == "..") {
+ lt_count++;
+ } else {
+ if (lt_count == 0) {
+ lt_foo="/" $lt_i lt_foo;
+ } else {
+ lt_count--;
+ }
+ }
+ }
+ }
+ if (lt_foo != "") { lt_freq[lt_foo]++; }
+ if (lt_freq[lt_foo] == 1) { print lt_foo; }
+}'`
+ # AWK program above erroneously prepends '/' to C:/dos/paths
+ # for these hosts.
+ case $host_os in
+ mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\
+ $SED 's,/\([A-Za-z]:\),\1,g'` ;;
+ esac
+ sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP`
+else
+ sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+fi
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+shrext_cmds=".so"
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+need_lib_prefix=unknown
+hardcode_into_libs=no
+
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+need_version=unknown
+
+case $host_os in
+aix3*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a'
+ shlibpath_var=LIBPATH
+
+ # AIX 3 has no versioning support, so we append a major version to the name.
+ soname_spec='${libname}${release}${shared_ext}$major'
+ ;;
+
+aix[4-9]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ hardcode_into_libs=yes
+ if test "$host_cpu" = ia64; then
+ # AIX 5 supports IA64
+ library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}'
+ shlibpath_var=LD_LIBRARY_PATH
+ else
+ # With GCC up to 2.95.x, collect2 would create an import file
+ # for dependence libraries. The import file would start with
+ # the line `#! .'. This would cause the generated library to
+ # depend on `.', always an invalid library. This was fixed in
+ # development snapshots of GCC prior to 3.0.
+ case $host_os in
+ aix4 | aix4.[01] | aix4.[01].*)
+ if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+ echo ' yes '
+ echo '#endif'; } | ${CC} -E - | $GREP yes > /dev/null; then
+ :
+ else
+ can_build_shared=no
+ fi
+ ;;
+ esac
+ # AIX (on Power*) has no versioning support, so currently we can not hardcode correct
+ # soname into executable. Probably we can add versioning support to
+ # collect2, so additional links can be useful in future.
+ if test "$aix_use_runtimelinking" = yes; then
+ # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+ # instead of lib<name>.a to let people know that these are not
+ # typical AIX shared libraries.
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ else
+ # We preserve .a as extension for shared libraries through AIX4.2
+ # and later when we are not doing run time linking.
+ library_names_spec='${libname}${release}.a $libname.a'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ fi
+ shlibpath_var=LIBPATH
+ fi
+ ;;
+
+amigaos*)
+ case $host_cpu in
+ powerpc)
+ # Since July 2007 AmigaOS4 officially supports .so libraries.
+ # When compiling the executable, add -use-dynld -Lsobjs: to the compileline.
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ ;;
+ m68k)
+ library_names_spec='$libname.ixlibrary $libname.a'
+ # Create ${libname}_ixlibrary.a entries in /sys/libs.
+ finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done'
+ ;;
+ esac
+ ;;
+
+beos*)
+ library_names_spec='${libname}${shared_ext}'
+ dynamic_linker="$host_os ld.so"
+ shlibpath_var=LIBRARY_PATH
+ ;;
+
+bsdi[45]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+ sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+ # the default ld.so.conf also contains /usr/contrib/lib and
+ # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+ # libtool to hard-code these into programs
+ ;;
+
+cygwin* | mingw* | pw32* | cegcc*)
+ version_type=windows
+ shrext_cmds=".dll"
+ need_version=no
+ need_lib_prefix=no
+
+ case $GCC,$cc_basename in
+ yes,*)
+ # gcc
+ library_names_spec='$libname.dll.a'
+ # DLL is installed to $(libdir)/../bin by postinstall_cmds
+ postinstall_cmds='base_file=`basename \${file}`~
+ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog $dir/$dlname \$dldir/$dlname~
+ chmod a+x \$dldir/$dlname~
+ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+ eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+ fi'
+ postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $RM \$dlpath'
+ shlibpath_overrides_runpath=yes
+
+ case $host_os in
+ cygwin*)
+ # Cygwin DLLs use 'cyg' prefix rather than 'lib'
+ soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+
+ sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"
+ ;;
+ mingw* | cegcc*)
+ # MinGW DLLs use traditional 'lib' prefix
+ soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+ ;;
+ pw32*)
+ # pw32 DLLs use 'pw' prefix rather than 'lib'
+ library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+ ;;
+ esac
+ dynamic_linker='Win32 ld.exe'
+ ;;
+
+ *,cl*)
+ # Native MSVC
+ libname_spec='$name'
+ soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+ library_names_spec='${libname}.dll.lib'
+
+ case $build_os in
+ mingw*)
+ sys_lib_search_path_spec=
+ lt_save_ifs=$IFS
+ IFS=';'
+ for lt_path in $LIB
+ do
+ IFS=$lt_save_ifs
+ # Let DOS variable expansion print the short 8.3 style file name.
+ lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"`
+ sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path"
+ done
+ IFS=$lt_save_ifs
+ # Convert to MSYS style.
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'`
+ ;;
+ cygwin*)
+ # Convert to unix form, then to dos form, then back to unix form
+ # but this time dos style (no spaces!) so that the unix form looks
+ # like /cygdrive/c/PROGRA~1:/cygdr...
+ sys_lib_search_path_spec=`cygpath --path --unix "$LIB"`
+ sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null`
+ sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+ ;;
+ *)
+ sys_lib_search_path_spec="$LIB"
+ if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then
+ # It is most probably a Windows format PATH.
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'`
+ else
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+ fi
+ # FIXME: find the short name or the path components, as spaces are
+ # common. (e.g. "Program Files" -> "PROGRA~1")
+ ;;
+ esac
+
+ # DLL is installed to $(libdir)/../bin by postinstall_cmds
+ postinstall_cmds='base_file=`basename \${file}`~
+ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog $dir/$dlname \$dldir/$dlname'
+ postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $RM \$dlpath'
+ shlibpath_overrides_runpath=yes
+ dynamic_linker='Win32 link.exe'
+ ;;
+
+ *)
+ # Assume MSVC wrapper
+ library_names_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext} $libname.lib'
+ dynamic_linker='Win32 ld.exe'
+ ;;
+ esac
+ # FIXME: first we should search . and the directory the executable is in
+ shlibpath_var=PATH
+ ;;
+
+darwin* | rhapsody*)
+ dynamic_linker="$host_os dyld"
+ version_type=darwin
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${major}$shared_ext ${libname}$shared_ext'
+ soname_spec='${libname}${release}${major}$shared_ext'
+ shlibpath_overrides_runpath=yes
+ shlibpath_var=DYLD_LIBRARY_PATH
+ shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`'
+
+ sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"
+ sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib'
+ ;;
+
+dgux*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+freebsd* | dragonfly*)
+ # DragonFly does not have aout. When/if they implement a new
+ # versioning mechanism, adjust this.
+ if test -x /usr/bin/objformat; then
+ objformat=`/usr/bin/objformat`
+ else
+ case $host_os in
+ freebsd[23].*) objformat=aout ;;
+ *) objformat=elf ;;
+ esac
+ fi
+ version_type=freebsd-$objformat
+ case $version_type in
+ freebsd-elf*)
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+ need_version=no
+ need_lib_prefix=no
+ ;;
+ freebsd-*)
+ library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix'
+ need_version=yes
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_os in
+ freebsd2.*)
+ shlibpath_overrides_runpath=yes
+ ;;
+ freebsd3.[01]* | freebsdelf3.[01]*)
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+ freebsd3.[2-9]* | freebsdelf3.[2-9]* | \
+ freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1)
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+ *) # from 4.6 on, and DragonFly
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+ esac
+ ;;
+
+gnu*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+
+haiku*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ dynamic_linker="$host_os runtime_loader"
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib'
+ hardcode_into_libs=yes
+ ;;
+
+hpux9* | hpux10* | hpux11*)
+ # Give a soname corresponding to the major version so that dld.sl refuses to
+ # link against other versions.
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ case $host_cpu in
+ ia64*)
+ shrext_cmds='.so'
+ hardcode_into_libs=yes
+ dynamic_linker="$host_os dld.so"
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ if test "X$HPUX_IA64_MODE" = X32; then
+ sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib"
+ else
+ sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64"
+ fi
+ sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+ ;;
+ hppa*64*)
+ shrext_cmds='.sl'
+ hardcode_into_libs=yes
+ dynamic_linker="$host_os dld.sl"
+ shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH
+ shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64"
+ sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+ ;;
+ *)
+ shrext_cmds='.sl'
+ dynamic_linker="$host_os dld.sl"
+ shlibpath_var=SHLIB_PATH
+ shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ ;;
+ esac
+ # HP-UX runs *really* slowly unless shared libraries are mode 555, ...
+ postinstall_cmds='chmod 555 $lib'
+ # or fails outright, so override atomically:
+ install_override_mode=555
+ ;;
+
+interix[3-9]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+
+irix5* | irix6* | nonstopux*)
+ case $host_os in
+ nonstopux*) version_type=nonstopux ;;
+ *)
+ if test "$lt_cv_prog_gnu_ld" = yes; then
+ version_type=linux # correct to gnu/linux during the next big refactor
+ else
+ version_type=irix
+ fi ;;
+ esac
+ need_lib_prefix=no
+ need_version=no
+ soname_spec='${libname}${release}${shared_ext}$major'
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}'
+ case $host_os in
+ irix5* | nonstopux*)
+ libsuff= shlibsuff=
+ ;;
+ *)
+ case $LD in # libtool.m4 will add one of these switches to LD
+ *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ")
+ libsuff= shlibsuff= libmagic=32-bit;;
+ *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ")
+ libsuff=32 shlibsuff=N32 libmagic=N32;;
+ *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ")
+ libsuff=64 shlibsuff=64 libmagic=64-bit;;
+ *) libsuff= shlibsuff= libmagic=never-match;;
+ esac
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}"
+ sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}"
+ hardcode_into_libs=yes
+ ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux*oldld* | linux*aout* | linux*coff*)
+ dynamic_linker=no
+ ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+
+ # Some binutils ld are patched to set DT_RUNPATH
+ if ${lt_cv_shlibpath_overrides_runpath+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_shlibpath_overrides_runpath=no
+ save_LDFLAGS=$LDFLAGS
+ save_libdir=$libdir
+ eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \
+ LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\""
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then :
+ lt_cv_shlibpath_overrides_runpath=yes
+fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ LDFLAGS=$save_LDFLAGS
+ libdir=$save_libdir
+
+fi
+
+ shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath
+
+ # This implies no fast_install, which is unacceptable.
+ # Some rework will be needed to allow for fast_install
+ # before this can be enabled.
+ hardcode_into_libs=yes
+
+ # Append ld.so.conf contents to the search path
+ if test -f /etc/ld.so.conf; then
+ lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '`
+ sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra"
+ fi
+
+ # We used to test for /lib/ld.so.1 and disable shared libraries on
+ # powerpc, because MkLinux only supported shared libraries with the
+ # GNU dynamic linker. Since this was broken with cross compilers,
+ # most powerpc-linux boxes support dynamic linking these days and
+ # people can always --disable-shared, the test was removed, and we
+ # assume the GNU/Linux dynamic linker is in use.
+ dynamic_linker='GNU/Linux ld.so'
+ ;;
+
+netbsd*)
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ dynamic_linker='NetBSD (a.out) ld.so'
+ else
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ dynamic_linker='NetBSD ld.elf_so'
+ fi
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+
+newsos6)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ ;;
+
+*nto* | *qnx*)
+ version_type=qnx
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ dynamic_linker='ldqnx.so'
+ ;;
+
+openbsd*)
+ version_type=sunos
+ sys_lib_dlsearch_path_spec="/usr/lib"
+ need_lib_prefix=no
+ # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs.
+ case $host_os in
+ openbsd3.3 | openbsd3.3.*) need_version=yes ;;
+ *) need_version=no ;;
+ esac
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ case $host_os in
+ openbsd2.[89] | openbsd2.[89].*)
+ shlibpath_overrides_runpath=no
+ ;;
+ *)
+ shlibpath_overrides_runpath=yes
+ ;;
+ esac
+ else
+ shlibpath_overrides_runpath=yes
+ fi
+ ;;
+
+os2*)
+ libname_spec='$name'
+ shrext_cmds=".dll"
+ need_lib_prefix=no
+ library_names_spec='$libname${shared_ext} $libname.a'
+ dynamic_linker='OS/2 ld.exe'
+ shlibpath_var=LIBPATH
+ ;;
+
+osf3* | osf4* | osf5*)
+ version_type=osf
+ need_lib_prefix=no
+ need_version=no
+ soname_spec='${libname}${release}${shared_ext}$major'
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+ sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec"
+ ;;
+
+rdos*)
+ dynamic_linker=no
+ ;;
+
+solaris*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ # ldd complains unless libraries are executable
+ postinstall_cmds='chmod +x $lib'
+ ;;
+
+sunos4*)
+ version_type=sunos
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+ finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ if test "$with_gnu_ld" = yes; then
+ need_lib_prefix=no
+ fi
+ need_version=yes
+ ;;
+
+sysv4 | sysv4.3*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_vendor in
+ sni)
+ shlibpath_overrides_runpath=no
+ need_lib_prefix=no
+ runpath_var=LD_RUN_PATH
+ ;;
+ siemens)
+ need_lib_prefix=no
+ ;;
+ motorola)
+ need_lib_prefix=no
+ need_version=no
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+ ;;
+ esac
+ ;;
+
+sysv4*MP*)
+ if test -d /usr/nec ;then
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}'
+ soname_spec='$libname${shared_ext}.$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ fi
+ ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+ version_type=freebsd-elf
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ if test "$with_gnu_ld" = yes; then
+ sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib'
+ else
+ sys_lib_search_path_spec='/usr/ccs/lib /usr/lib'
+ case $host_os in
+ sco3.2v5*)
+ sys_lib_search_path_spec="$sys_lib_search_path_spec /lib"
+ ;;
+ esac
+ fi
+ sys_lib_dlsearch_path_spec='/usr/lib'
+ ;;
+
+tpf*)
+ # TPF is a cross-target only. Preferred cross-host = GNU/Linux.
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+
+uts4*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+*)
+ dynamic_linker=no
+ ;;
+esac
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5
+$as_echo "$dynamic_linker" >&6; }
+test "$dynamic_linker" = no && can_build_shared=no
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test "$GCC" = yes; then
+ variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then
+ sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec"
+fi
+if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then
+ sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec"
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5
+$as_echo_n "checking how to hardcode library paths into programs... " >&6; }
+hardcode_action=
+if test -n "$hardcode_libdir_flag_spec" ||
+ test -n "$runpath_var" ||
+ test "X$hardcode_automatic" = "Xyes" ; then
+
+ # We can hardcode non-existent directories.
+ if test "$hardcode_direct" != no &&
+ # If the only mechanism to avoid hardcoding is shlibpath_var, we
+ # have to relink, otherwise we might link with an installed library
+ # when we should be linking with a yet-to-be-installed one
+ ## test "$_LT_TAGVAR(hardcode_shlibpath_var, )" != no &&
+ test "$hardcode_minus_L" != no; then
+ # Linking always hardcodes the temporary library directory.
+ hardcode_action=relink
+ else
+ # We can link without hardcoding, and we can hardcode nonexisting dirs.
+ hardcode_action=immediate
+ fi
+else
+ # We cannot hardcode anything, or else we can only hardcode existing
+ # directories.
+ hardcode_action=unsupported
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5
+$as_echo "$hardcode_action" >&6; }
+
+if test "$hardcode_action" = relink ||
+ test "$inherit_rpath" = yes; then
+ # Fast installation is not supported
+ enable_fast_install=no
+elif test "$shlibpath_overrides_runpath" = yes ||
+ test "$enable_shared" = no; then
+ # Fast installation is not necessary
+ enable_fast_install=needless
+fi
+
+
+
+
+
+
+ if test "x$enable_dlopen" != xyes; then
+ enable_dlopen=unknown
+ enable_dlopen_self=unknown
+ enable_dlopen_self_static=unknown
+else
+ lt_cv_dlopen=no
+ lt_cv_dlopen_libs=
+
+ case $host_os in
+ beos*)
+ lt_cv_dlopen="load_add_on"
+ lt_cv_dlopen_libs=
+ lt_cv_dlopen_self=yes
+ ;;
+
+ mingw* | pw32* | cegcc*)
+ lt_cv_dlopen="LoadLibrary"
+ lt_cv_dlopen_libs=
+ ;;
+
+ cygwin*)
+ lt_cv_dlopen="dlopen"
+ lt_cv_dlopen_libs=
+ ;;
+
+ darwin*)
+ # if libdl is installed we need to link against it
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+$as_echo_n "checking for dlopen in -ldl... " >&6; }
+if ${ac_cv_lib_dl_dlopen+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dl_dlopen=yes
+else
+ ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+$as_echo "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes; then :
+ lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"
+else
+
+ lt_cv_dlopen="dyld"
+ lt_cv_dlopen_libs=
+ lt_cv_dlopen_self=yes
+
+fi
+
+ ;;
+
+ *)
+ ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load"
+if test "x$ac_cv_func_shl_load" = xyes; then :
+ lt_cv_dlopen="shl_load"
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5
+$as_echo_n "checking for shl_load in -ldld... " >&6; }
+if ${ac_cv_lib_dld_shl_load+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char shl_load ();
+int
+main ()
+{
+return shl_load ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dld_shl_load=yes
+else
+ ac_cv_lib_dld_shl_load=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5
+$as_echo "$ac_cv_lib_dld_shl_load" >&6; }
+if test "x$ac_cv_lib_dld_shl_load" = xyes; then :
+ lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-ldld"
+else
+ ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen"
+if test "x$ac_cv_func_dlopen" = xyes; then :
+ lt_cv_dlopen="dlopen"
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+$as_echo_n "checking for dlopen in -ldl... " >&6; }
+if ${ac_cv_lib_dl_dlopen+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dl_dlopen=yes
+else
+ ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+$as_echo "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes; then :
+ lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5
+$as_echo_n "checking for dlopen in -lsvld... " >&6; }
+if ${ac_cv_lib_svld_dlopen+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsvld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_svld_dlopen=yes
+else
+ ac_cv_lib_svld_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5
+$as_echo "$ac_cv_lib_svld_dlopen" >&6; }
+if test "x$ac_cv_lib_svld_dlopen" = xyes; then :
+ lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5
+$as_echo_n "checking for dld_link in -ldld... " >&6; }
+if ${ac_cv_lib_dld_dld_link+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dld_link ();
+int
+main ()
+{
+return dld_link ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dld_dld_link=yes
+else
+ ac_cv_lib_dld_dld_link=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5
+$as_echo "$ac_cv_lib_dld_dld_link" >&6; }
+if test "x$ac_cv_lib_dld_dld_link" = xyes; then :
+ lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-ldld"
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+ ;;
+ esac
+
+ if test "x$lt_cv_dlopen" != xno; then
+ enable_dlopen=yes
+ else
+ enable_dlopen=no
+ fi
+
+ case $lt_cv_dlopen in
+ dlopen)
+ save_CPPFLAGS="$CPPFLAGS"
+ test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H"
+
+ save_LDFLAGS="$LDFLAGS"
+ wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\"
+
+ save_LIBS="$LIBS"
+ LIBS="$lt_cv_dlopen_libs $LIBS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5
+$as_echo_n "checking whether a program can dlopen itself... " >&6; }
+if ${lt_cv_dlopen_self+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test "$cross_compiling" = yes; then :
+ lt_cv_dlopen_self=cross
+else
+ lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+ lt_status=$lt_dlunknown
+ cat > conftest.$ac_ext <<_LT_EOF
+#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+# define LT_DLGLOBAL RTLD_GLOBAL
+#else
+# ifdef DL_GLOBAL
+# define LT_DLGLOBAL DL_GLOBAL
+# else
+# define LT_DLGLOBAL 0
+# endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+ find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+# ifdef RTLD_LAZY
+# define LT_DLLAZY_OR_NOW RTLD_LAZY
+# else
+# ifdef DL_LAZY
+# define LT_DLLAZY_OR_NOW DL_LAZY
+# else
+# ifdef RTLD_NOW
+# define LT_DLLAZY_OR_NOW RTLD_NOW
+# else
+# ifdef DL_NOW
+# define LT_DLLAZY_OR_NOW DL_NOW
+# else
+# define LT_DLLAZY_OR_NOW 0
+# endif
+# endif
+# endif
+# endif
+#endif
+
+/* When -fvisbility=hidden is used, assume the code has been annotated
+ correspondingly for the symbols needed. */
+#if defined(__GNUC__) && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+ void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+ int status = $lt_dlunknown;
+
+ if (self)
+ {
+ if (dlsym (self,"fnord")) status = $lt_dlno_uscore;
+ else
+ {
+ if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore;
+ else puts (dlerror ());
+ }
+ /* dlclose (self); */
+ }
+ else
+ puts (dlerror ());
+
+ return status;
+}
+_LT_EOF
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+ (eval $ac_link) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && test -s conftest${ac_exeext} 2>/dev/null; then
+ (./conftest; exit; ) >&5 2>/dev/null
+ lt_status=$?
+ case x$lt_status in
+ x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;;
+ x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;;
+ x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;;
+ esac
+ else :
+ # compilation failed
+ lt_cv_dlopen_self=no
+ fi
+fi
+rm -fr conftest*
+
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5
+$as_echo "$lt_cv_dlopen_self" >&6; }
+
+ if test "x$lt_cv_dlopen_self" = xyes; then
+ wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5
+$as_echo_n "checking whether a statically linked program can dlopen itself... " >&6; }
+if ${lt_cv_dlopen_self_static+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test "$cross_compiling" = yes; then :
+ lt_cv_dlopen_self_static=cross
+else
+ lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+ lt_status=$lt_dlunknown
+ cat > conftest.$ac_ext <<_LT_EOF
+#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+# define LT_DLGLOBAL RTLD_GLOBAL
+#else
+# ifdef DL_GLOBAL
+# define LT_DLGLOBAL DL_GLOBAL
+# else
+# define LT_DLGLOBAL 0
+# endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+ find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+# ifdef RTLD_LAZY
+# define LT_DLLAZY_OR_NOW RTLD_LAZY
+# else
+# ifdef DL_LAZY
+# define LT_DLLAZY_OR_NOW DL_LAZY
+# else
+# ifdef RTLD_NOW
+# define LT_DLLAZY_OR_NOW RTLD_NOW
+# else
+# ifdef DL_NOW
+# define LT_DLLAZY_OR_NOW DL_NOW
+# else
+# define LT_DLLAZY_OR_NOW 0
+# endif
+# endif
+# endif
+# endif
+#endif
+
+/* When -fvisbility=hidden is used, assume the code has been annotated
+ correspondingly for the symbols needed. */
+#if defined(__GNUC__) && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+ void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+ int status = $lt_dlunknown;
+
+ if (self)
+ {
+ if (dlsym (self,"fnord")) status = $lt_dlno_uscore;
+ else
+ {
+ if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore;
+ else puts (dlerror ());
+ }
+ /* dlclose (self); */
+ }
+ else
+ puts (dlerror ());
+
+ return status;
+}
+_LT_EOF
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+ (eval $ac_link) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && test -s conftest${ac_exeext} 2>/dev/null; then
+ (./conftest; exit; ) >&5 2>/dev/null
+ lt_status=$?
+ case x$lt_status in
+ x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;;
+ x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;;
+ x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;;
+ esac
+ else :
+ # compilation failed
+ lt_cv_dlopen_self_static=no
+ fi
+fi
+rm -fr conftest*
+
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5
+$as_echo "$lt_cv_dlopen_self_static" >&6; }
+ fi
+
+ CPPFLAGS="$save_CPPFLAGS"
+ LDFLAGS="$save_LDFLAGS"
+ LIBS="$save_LIBS"
+ ;;
+ esac
+
+ case $lt_cv_dlopen_self in
+ yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;;
+ *) enable_dlopen_self=unknown ;;
+ esac
+
+ case $lt_cv_dlopen_self_static in
+ yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;;
+ *) enable_dlopen_self_static=unknown ;;
+ esac
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+striplib=
+old_striplib=
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5
+$as_echo_n "checking whether stripping libraries is possible... " >&6; }
+if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then
+ test -z "$old_striplib" && old_striplib="$STRIP --strip-debug"
+ test -z "$striplib" && striplib="$STRIP --strip-unneeded"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+# FIXME - insert some real tests, host_os isn't really good enough
+ case $host_os in
+ darwin*)
+ if test -n "$STRIP" ; then
+ striplib="$STRIP -x"
+ old_striplib="$STRIP -S"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ fi
+ ;;
+ *)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ ;;
+ esac
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+ # Report which library types will actually be built
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5
+$as_echo_n "checking if libtool supports shared libraries... " >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5
+$as_echo "$can_build_shared" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5
+$as_echo_n "checking whether to build shared libraries... " >&6; }
+ test "$can_build_shared" = "no" && enable_shared=no
+
+ # On AIX, shared libraries and static libraries use the same namespace, and
+ # are all built from PIC.
+ case $host_os in
+ aix3*)
+ test "$enable_shared" = yes && enable_static=no
+ if test -n "$RANLIB"; then
+ archive_cmds="$archive_cmds~\$RANLIB \$lib"
+ postinstall_cmds='$RANLIB $lib'
+ fi
+ ;;
+
+ aix[4-9]*)
+ if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+ test "$enable_shared" = yes && enable_static=no
+ fi
+ ;;
+ esac
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5
+$as_echo "$enable_shared" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5
+$as_echo_n "checking whether to build static libraries... " >&6; }
+ # Make sure either enable_shared or enable_static is yes.
+ test "$enable_shared" = yes || enable_static=yes
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5
+$as_echo "$enable_static" >&6; }
+
+
+
+
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+CC="$lt_save_CC"
+
+ if test -n "$CXX" && ( test "X$CXX" != "Xno" &&
+ ( (test "X$CXX" = "Xg++" && `g++ -v >/dev/null 2>&1` ) ||
+ (test "X$CXX" != "Xg++"))) ; then
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C++ preprocessor" >&5
+$as_echo_n "checking how to run the C++ preprocessor... " >&6; }
+if test -z "$CXXCPP"; then
+ if ${ac_cv_prog_CXXCPP+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ # Double quotes because CXXCPP needs to be expanded
+ for CXXCPP in "$CXX -E" "/lib/cpp"
+ do
+ ac_preproc_ok=false
+for ac_cxx_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ # <limits.h> exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+ Syntax error
+_ACEOF
+if ac_fn_cxx_try_cpp "$LINENO"; then :
+
+else
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_cxx_try_cpp "$LINENO"; then :
+ # Broken: success on invalid input.
+continue
+else
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+ break
+fi
+
+ done
+ ac_cv_prog_CXXCPP=$CXXCPP
+
+fi
+ CXXCPP=$ac_cv_prog_CXXCPP
+else
+ ac_cv_prog_CXXCPP=$CXXCPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXXCPP" >&5
+$as_echo "$CXXCPP" >&6; }
+ac_preproc_ok=false
+for ac_cxx_preproc_warn_flag in '' yes
+do
+ # Use a header file that comes with gcc, so configuring glibc
+ # with a fresh cross-compiler works.
+ # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+ # <limits.h> exists even on freestanding compilers.
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp. "Syntax error" is here to catch this case.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+ Syntax error
+_ACEOF
+if ac_fn_cxx_try_cpp "$LINENO"; then :
+
+else
+ # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+ # OK, works on sane cases. Now check whether nonexistent headers
+ # can be detected and how.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_cxx_try_cpp "$LINENO"; then :
+ # Broken: success on invalid input.
+continue
+else
+ # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C++ preprocessor \"$CXXCPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+else
+ _lt_caught_CXX_error=yes
+fi
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+archive_cmds_need_lc_CXX=no
+allow_undefined_flag_CXX=
+always_export_symbols_CXX=no
+archive_expsym_cmds_CXX=
+compiler_needs_object_CXX=no
+export_dynamic_flag_spec_CXX=
+hardcode_direct_CXX=no
+hardcode_direct_absolute_CXX=no
+hardcode_libdir_flag_spec_CXX=
+hardcode_libdir_separator_CXX=
+hardcode_minus_L_CXX=no
+hardcode_shlibpath_var_CXX=unsupported
+hardcode_automatic_CXX=no
+inherit_rpath_CXX=no
+module_cmds_CXX=
+module_expsym_cmds_CXX=
+link_all_deplibs_CXX=unknown
+old_archive_cmds_CXX=$old_archive_cmds
+reload_flag_CXX=$reload_flag
+reload_cmds_CXX=$reload_cmds
+no_undefined_flag_CXX=
+whole_archive_flag_spec_CXX=
+enable_shared_with_static_runtimes_CXX=no
+
+# Source file extension for C++ test sources.
+ac_ext=cpp
+
+# Object file extension for compiled C++ test sources.
+objext=o
+objext_CXX=$objext
+
+# No sense in running all these tests if we already determined that
+# the CXX compiler isn't working. Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test "$_lt_caught_CXX_error" != yes; then
+ # Code to be used in simple compile tests
+ lt_simple_compile_test_code="int some_variable = 0;"
+
+ # Code to be used in simple link tests
+ lt_simple_link_test_code='int main(int, char *[]) { return(0); }'
+
+ # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+
+
+
+
+
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+
+
+ # save warnings/boilerplate of simple test code
+ ac_outfile=conftest.$ac_objext
+echo "$lt_simple_compile_test_code" >conftest.$ac_ext
+eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_compiler_boilerplate=`cat conftest.err`
+$RM conftest*
+
+ ac_outfile=conftest.$ac_objext
+echo "$lt_simple_link_test_code" >conftest.$ac_ext
+eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_linker_boilerplate=`cat conftest.err`
+$RM -r conftest*
+
+
+ # Allow CC to be a program name with arguments.
+ lt_save_CC=$CC
+ lt_save_CFLAGS=$CFLAGS
+ lt_save_LD=$LD
+ lt_save_GCC=$GCC
+ GCC=$GXX
+ lt_save_with_gnu_ld=$with_gnu_ld
+ lt_save_path_LD=$lt_cv_path_LD
+ if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then
+ lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx
+ else
+ $as_unset lt_cv_prog_gnu_ld
+ fi
+ if test -n "${lt_cv_path_LDCXX+set}"; then
+ lt_cv_path_LD=$lt_cv_path_LDCXX
+ else
+ $as_unset lt_cv_path_LD
+ fi
+ test -z "${LDCXX+set}" || LD=$LDCXX
+ CC=${CXX-"c++"}
+ CFLAGS=$CXXFLAGS
+ compiler=$CC
+ compiler_CXX=$CC
+ for cc_temp in $compiler""; do
+ case $cc_temp in
+ compile | *[\\/]compile | ccache | *[\\/]ccache ) ;;
+ distcc | *[\\/]distcc | purify | *[\\/]purify ) ;;
+ \-*) ;;
+ *) break;;
+ esac
+done
+cc_basename=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"`
+
+
+ if test -n "$compiler"; then
+ # We don't want -fno-exception when compiling C++ code, so set the
+ # no_builtin_flag separately
+ if test "$GXX" = yes; then
+ lt_prog_compiler_no_builtin_flag_CXX=' -fno-builtin'
+ else
+ lt_prog_compiler_no_builtin_flag_CXX=
+ fi
+
+ if test "$GXX" = yes; then
+ # Set up default GNU C++ configuration
+
+
+
+# Check whether --with-gnu-ld was given.
+if test "${with_gnu_ld+set}" = set; then :
+ withval=$with_gnu_ld; test "$withval" = no || with_gnu_ld=yes
+else
+ with_gnu_ld=no
+fi
+
+ac_prog=ld
+if test "$GCC" = yes; then
+ # Check if gcc -print-prog-name=ld gives a path.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5
+$as_echo_n "checking for ld used by $CC... " >&6; }
+ case $host in
+ *-*-mingw*)
+ # gcc leaves a trailing carriage return which upsets mingw
+ ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+ *)
+ ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+ esac
+ case $ac_prog in
+ # Accept absolute paths.
+ [\\/]* | ?:[\\/]*)
+ re_direlt='/[^/][^/]*/\.\./'
+ # Canonicalize the pathname of ld
+ ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'`
+ while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do
+ ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"`
+ done
+ test -z "$LD" && LD="$ac_prog"
+ ;;
+ "")
+ # If it fails, then pretend we aren't using GCC.
+ ac_prog=ld
+ ;;
+ *)
+ # If it is relative, then search for the first ld in PATH.
+ with_gnu_ld=unknown
+ ;;
+ esac
+elif test "$with_gnu_ld" = yes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5
+$as_echo_n "checking for GNU ld... " >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5
+$as_echo_n "checking for non-GNU ld... " >&6; }
+fi
+if ${lt_cv_path_LD+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -z "$LD"; then
+ lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH; do
+ IFS="$lt_save_ifs"
+ test -z "$ac_dir" && ac_dir=.
+ if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+ lt_cv_path_LD="$ac_dir/$ac_prog"
+ # Check to see if the program is GNU ld. I'd rather use --version,
+ # but apparently some variants of GNU ld only accept -v.
+ # Break only if it was the GNU/non-GNU ld that we prefer.
+ case `"$lt_cv_path_LD" -v 2>&1 </dev/null` in
+ *GNU* | *'with BFD'*)
+ test "$with_gnu_ld" != no && break
+ ;;
+ *)
+ test "$with_gnu_ld" != yes && break
+ ;;
+ esac
+ fi
+ done
+ IFS="$lt_save_ifs"
+else
+ lt_cv_path_LD="$LD" # Let the user override the test with a path.
+fi
+fi
+
+LD="$lt_cv_path_LD"
+if test -n "$LD"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LD" >&5
+$as_echo "$LD" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5
+$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; }
+if ${lt_cv_prog_gnu_ld+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ # I'd rather use --version here, but apparently some GNU lds only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+ lt_cv_prog_gnu_ld=yes
+ ;;
+*)
+ lt_cv_prog_gnu_ld=no
+ ;;
+esac
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_gnu_ld" >&5
+$as_echo "$lt_cv_prog_gnu_ld" >&6; }
+with_gnu_ld=$lt_cv_prog_gnu_ld
+
+
+
+
+
+
+
+ # Check if GNU C++ uses GNU ld as the underlying linker, since the
+ # archiving commands below assume that GNU ld is being used.
+ if test "$with_gnu_ld" = yes; then
+ archive_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+
+ hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir'
+ export_dynamic_flag_spec_CXX='${wl}--export-dynamic'
+
+ # If archive_cmds runs LD, not CC, wlarc should be empty
+ # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to
+ # investigate it a little bit more. (MM)
+ wlarc='${wl}'
+
+ # ancient GNU ld didn't support --whole-archive et. al.
+ if eval "`$CC -print-prog-name=ld` --help 2>&1" |
+ $GREP 'no-whole-archive' > /dev/null; then
+ whole_archive_flag_spec_CXX="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+ else
+ whole_archive_flag_spec_CXX=
+ fi
+ else
+ with_gnu_ld=no
+ wlarc=
+
+ # A generic and very simple default shared library creation
+ # command for GNU C++ for the case where it uses the native
+ # linker, instead of GNU ld. If possible, this setting should
+ # overridden to take advantage of the native linker features on
+ # the platform it is being used on.
+ archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib'
+ fi
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+
+ else
+ GXX=no
+ with_gnu_ld=no
+ wlarc=
+ fi
+
+ # PORTME: fill in a description of your system's C++ link characteristics
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5
+$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; }
+ ld_shlibs_CXX=yes
+ case $host_os in
+ aix3*)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ aix[4-9]*)
+ if test "$host_cpu" = ia64; then
+ # On IA64, the linker does run time linking by default, so we don't
+ # have to do anything special.
+ aix_use_runtimelinking=no
+ exp_sym_flag='-Bexport'
+ no_entry_flag=""
+ else
+ aix_use_runtimelinking=no
+
+ # Test if we are trying to use run time linking or normal
+ # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+ # need to do runtime linking.
+ case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*)
+ for ld_flag in $LDFLAGS; do
+ case $ld_flag in
+ *-brtl*)
+ aix_use_runtimelinking=yes
+ break
+ ;;
+ esac
+ done
+ ;;
+ esac
+
+ exp_sym_flag='-bexport'
+ no_entry_flag='-bnoentry'
+ fi
+
+ # When large executables or shared objects are built, AIX ld can
+ # have problems creating the table of contents. If linking a library
+ # or program results in "error TOC overflow" add -mminimal-toc to
+ # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not
+ # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+ archive_cmds_CXX=''
+ hardcode_direct_CXX=yes
+ hardcode_direct_absolute_CXX=yes
+ hardcode_libdir_separator_CXX=':'
+ link_all_deplibs_CXX=yes
+ file_list_spec_CXX='${wl}-f,'
+
+ if test "$GXX" = yes; then
+ case $host_os in aix4.[012]|aix4.[012].*)
+ # We only want to do this on AIX 4.2 and lower, the check
+ # below for broken collect2 doesn't work under 4.3+
+ collect2name=`${CC} -print-prog-name=collect2`
+ if test -f "$collect2name" &&
+ strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+ then
+ # We have reworked collect2
+ :
+ else
+ # We have old collect2
+ hardcode_direct_CXX=unsupported
+ # It fails to find uninstalled libraries when the uninstalled
+ # path is not listed in the libpath. Setting hardcode_minus_L
+ # to unsupported forces relinking
+ hardcode_minus_L_CXX=yes
+ hardcode_libdir_flag_spec_CXX='-L$libdir'
+ hardcode_libdir_separator_CXX=
+ fi
+ esac
+ shared_flag='-shared'
+ if test "$aix_use_runtimelinking" = yes; then
+ shared_flag="$shared_flag "'${wl}-G'
+ fi
+ else
+ # not using gcc
+ if test "$host_cpu" = ia64; then
+ # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+ # chokes on -Wl,-G. The following line is correct:
+ shared_flag='-G'
+ else
+ if test "$aix_use_runtimelinking" = yes; then
+ shared_flag='${wl}-G'
+ else
+ shared_flag='${wl}-bM:SRE'
+ fi
+ fi
+ fi
+
+ export_dynamic_flag_spec_CXX='${wl}-bexpall'
+ # It seems that -bexpall does not export symbols beginning with
+ # underscore (_), so it is better to generate a list of symbols to
+ # export.
+ always_export_symbols_CXX=yes
+ if test "$aix_use_runtimelinking" = yes; then
+ # Warning - without using the other runtime loading flags (-brtl),
+ # -berok will link without error, but may produce a broken library.
+ allow_undefined_flag_CXX='-berok'
+ # Determine the default libpath from the value encoded in an empty
+ # executable.
+ if test "${lt_cv_aix_libpath+set}" = set; then
+ aix_libpath=$lt_cv_aix_libpath
+else
+ if ${lt_cv_aix_libpath__CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_link "$LINENO"; then :
+
+ lt_aix_libpath_sed='
+ /Import File Strings/,/^$/ {
+ /^0/ {
+ s/^0 *\([^ ]*\) *$/\1/
+ p
+ }
+ }'
+ lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ # Check for a 64-bit object if we didn't find anything.
+ if test -z "$lt_cv_aix_libpath__CXX"; then
+ lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ if test -z "$lt_cv_aix_libpath__CXX"; then
+ lt_cv_aix_libpath__CXX="/usr/lib:/lib"
+ fi
+
+fi
+
+ aix_libpath=$lt_cv_aix_libpath__CXX
+fi
+
+ hardcode_libdir_flag_spec_CXX='${wl}-blibpath:$libdir:'"$aix_libpath"
+
+ archive_expsym_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+ else
+ if test "$host_cpu" = ia64; then
+ hardcode_libdir_flag_spec_CXX='${wl}-R $libdir:/usr/lib:/lib'
+ allow_undefined_flag_CXX="-z nodefs"
+ archive_expsym_cmds_CXX="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols"
+ else
+ # Determine the default libpath from the value encoded in an
+ # empty executable.
+ if test "${lt_cv_aix_libpath+set}" = set; then
+ aix_libpath=$lt_cv_aix_libpath
+else
+ if ${lt_cv_aix_libpath__CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_link "$LINENO"; then :
+
+ lt_aix_libpath_sed='
+ /Import File Strings/,/^$/ {
+ /^0/ {
+ s/^0 *\([^ ]*\) *$/\1/
+ p
+ }
+ }'
+ lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ # Check for a 64-bit object if we didn't find anything.
+ if test -z "$lt_cv_aix_libpath__CXX"; then
+ lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+ fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ if test -z "$lt_cv_aix_libpath__CXX"; then
+ lt_cv_aix_libpath__CXX="/usr/lib:/lib"
+ fi
+
+fi
+
+ aix_libpath=$lt_cv_aix_libpath__CXX
+fi
+
+ hardcode_libdir_flag_spec_CXX='${wl}-blibpath:$libdir:'"$aix_libpath"
+ # Warning - without using the other run time loading flags,
+ # -berok will link without error, but may produce a broken library.
+ no_undefined_flag_CXX=' ${wl}-bernotok'
+ allow_undefined_flag_CXX=' ${wl}-berok'
+ if test "$with_gnu_ld" = yes; then
+ # We only use this code for GNU lds that support --whole-archive.
+ whole_archive_flag_spec_CXX='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+ else
+ # Exported symbols can be pulled into shared objects from archives
+ whole_archive_flag_spec_CXX='$convenience'
+ fi
+ archive_cmds_need_lc_CXX=yes
+ # This is similar to how AIX traditionally builds its shared
+ # libraries.
+ archive_expsym_cmds_CXX="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname'
+ fi
+ fi
+ ;;
+
+ beos*)
+ if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+ allow_undefined_flag_CXX=unsupported
+ # Joseph Beckenbach <jrb3@best.com> says some releases of gcc
+ # support --undefined. This deserves some investigation. FIXME
+ archive_cmds_CXX='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ else
+ ld_shlibs_CXX=no
+ fi
+ ;;
+
+ chorus*)
+ case $cc_basename in
+ *)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ esac
+ ;;
+
+ cygwin* | mingw* | pw32* | cegcc*)
+ case $GXX,$cc_basename in
+ ,cl* | no,cl*)
+ # Native MSVC
+ # hardcode_libdir_flag_spec is actually meaningless, as there is
+ # no search path for DLLs.
+ hardcode_libdir_flag_spec_CXX=' '
+ allow_undefined_flag_CXX=unsupported
+ always_export_symbols_CXX=yes
+ file_list_spec_CXX='@'
+ # Tell ltmain to make .lib files, not .a files.
+ libext=lib
+ # Tell ltmain to make .dll files, not .so files.
+ shrext_cmds=".dll"
+ # FIXME: Setting linknames here is a bad hack.
+ archive_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames='
+ archive_expsym_cmds_CXX='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+ $SED -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp;
+ else
+ $SED -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp;
+ fi~
+ $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+ linknames='
+ # The linker will not automatically build a static lib if we build a DLL.
+ # _LT_TAGVAR(old_archive_from_new_cmds, CXX)='true'
+ enable_shared_with_static_runtimes_CXX=yes
+ # Don't use ranlib
+ old_postinstall_cmds_CXX='chmod 644 $oldlib'
+ postlink_cmds_CXX='lt_outputfile="@OUTPUT@"~
+ lt_tool_outputfile="@TOOL_OUTPUT@"~
+ case $lt_outputfile in
+ *.exe|*.EXE) ;;
+ *)
+ lt_outputfile="$lt_outputfile.exe"
+ lt_tool_outputfile="$lt_tool_outputfile.exe"
+ ;;
+ esac~
+ func_to_tool_file "$lt_outputfile"~
+ if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then
+ $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+ $RM "$lt_outputfile.manifest";
+ fi'
+ ;;
+ *)
+ # g++
+ # _LT_TAGVAR(hardcode_libdir_flag_spec, CXX) is actually meaningless,
+ # as there is no search path for DLLs.
+ hardcode_libdir_flag_spec_CXX='-L$libdir'
+ export_dynamic_flag_spec_CXX='${wl}--export-all-symbols'
+ allow_undefined_flag_CXX=unsupported
+ always_export_symbols_CXX=no
+ enable_shared_with_static_runtimes_CXX=yes
+
+ if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+ archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+ # If the export-symbols file already is a .def file (1st line
+ # is EXPORTS), use it as is; otherwise, prepend...
+ archive_expsym_cmds_CXX='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+ cp $export_symbols $output_objdir/$soname.def;
+ else
+ echo EXPORTS > $output_objdir/$soname.def;
+ cat $export_symbols >> $output_objdir/$soname.def;
+ fi~
+ $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+ else
+ ld_shlibs_CXX=no
+ fi
+ ;;
+ esac
+ ;;
+ darwin* | rhapsody*)
+
+
+ archive_cmds_need_lc_CXX=no
+ hardcode_direct_CXX=no
+ hardcode_automatic_CXX=yes
+ hardcode_shlibpath_var_CXX=unsupported
+ if test "$lt_cv_ld_force_load" = "yes"; then
+ whole_archive_flag_spec_CXX='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience ${wl}-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`'
+
+ else
+ whole_archive_flag_spec_CXX=''
+ fi
+ link_all_deplibs_CXX=yes
+ allow_undefined_flag_CXX="$_lt_dar_allow_undefined"
+ case $cc_basename in
+ ifort*) _lt_dar_can_shared=yes ;;
+ *) _lt_dar_can_shared=$GCC ;;
+ esac
+ if test "$_lt_dar_can_shared" = "yes"; then
+ output_verbose_link_cmd=func_echo_all
+ archive_cmds_CXX="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}"
+ module_cmds_CXX="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}"
+ archive_expsym_cmds_CXX="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}"
+ module_expsym_cmds_CXX="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}"
+ if test "$lt_cv_apple_cc_single_mod" != "yes"; then
+ archive_cmds_CXX="\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dsymutil}"
+ archive_expsym_cmds_CXX="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dar_export_syms}${_lt_dsymutil}"
+ fi
+
+ else
+ ld_shlibs_CXX=no
+ fi
+
+ ;;
+
+ dgux*)
+ case $cc_basename in
+ ec++*)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ ghcx*)
+ # Green Hills C++ Compiler
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ *)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ esac
+ ;;
+
+ freebsd2.*)
+ # C++ shared libraries reported to be fairly broken before
+ # switch to ELF
+ ld_shlibs_CXX=no
+ ;;
+
+ freebsd-elf*)
+ archive_cmds_need_lc_CXX=no
+ ;;
+
+ freebsd* | dragonfly*)
+ # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF
+ # conventions
+ ld_shlibs_CXX=yes
+ ;;
+
+ gnu*)
+ ;;
+
+ haiku*)
+ archive_cmds_CXX='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ link_all_deplibs_CXX=yes
+ ;;
+
+ hpux9*)
+ hardcode_libdir_flag_spec_CXX='${wl}+b ${wl}$libdir'
+ hardcode_libdir_separator_CXX=:
+ export_dynamic_flag_spec_CXX='${wl}-E'
+ hardcode_direct_CXX=yes
+ hardcode_minus_L_CXX=yes # Not in the search PATH,
+ # but as the default
+ # location of the library.
+
+ case $cc_basename in
+ CC*)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ aCC*)
+ archive_cmds_CXX='$RM $output_objdir/$soname~$CC -b ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ #
+ # There doesn't appear to be a way to prevent this compiler from
+ # explicitly linking system object files so we need to strip them
+ # from the output so that they don't get included in the library
+ # dependencies.
+ output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+ ;;
+ *)
+ if test "$GXX" = yes; then
+ archive_cmds_CXX='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+ else
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ fi
+ ;;
+ esac
+ ;;
+
+ hpux10*|hpux11*)
+ if test $with_gnu_ld = no; then
+ hardcode_libdir_flag_spec_CXX='${wl}+b ${wl}$libdir'
+ hardcode_libdir_separator_CXX=:
+
+ case $host_cpu in
+ hppa*64*|ia64*)
+ ;;
+ *)
+ export_dynamic_flag_spec_CXX='${wl}-E'
+ ;;
+ esac
+ fi
+ case $host_cpu in
+ hppa*64*|ia64*)
+ hardcode_direct_CXX=no
+ hardcode_shlibpath_var_CXX=no
+ ;;
+ *)
+ hardcode_direct_CXX=yes
+ hardcode_direct_absolute_CXX=yes
+ hardcode_minus_L_CXX=yes # Not in the search PATH,
+ # but as the default
+ # location of the library.
+ ;;
+ esac
+
+ case $cc_basename in
+ CC*)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ aCC*)
+ case $host_cpu in
+ hppa*64*)
+ archive_cmds_CXX='$CC -b ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ ia64*)
+ archive_cmds_CXX='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ *)
+ archive_cmds_CXX='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ esac
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ #
+ # There doesn't appear to be a way to prevent this compiler from
+ # explicitly linking system object files so we need to strip them
+ # from the output so that they don't get included in the library
+ # dependencies.
+ output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+ ;;
+ *)
+ if test "$GXX" = yes; then
+ if test $with_gnu_ld = no; then
+ case $host_cpu in
+ hppa*64*)
+ archive_cmds_CXX='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ ia64*)
+ archive_cmds_CXX='$CC -shared -nostdlib $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ *)
+ archive_cmds_CXX='$CC -shared -nostdlib $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ ;;
+ esac
+ fi
+ else
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ fi
+ ;;
+ esac
+ ;;
+
+ interix[3-9]*)
+ hardcode_direct_CXX=no
+ hardcode_shlibpath_var_CXX=no
+ hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir'
+ export_dynamic_flag_spec_CXX='${wl}-E'
+ # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+ # Instead, shared libraries are loaded at an image base (0x10000000 by
+ # default) and relocated if they conflict, which is a slow very memory
+ # consuming and fragmenting process. To avoid this, we pick a random,
+ # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+ # time. Moving up from 0x10000000 also allows more sbrk(2) space.
+ archive_cmds_CXX='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+ archive_expsym_cmds_CXX='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+ ;;
+ irix5* | irix6*)
+ case $cc_basename in
+ CC*)
+ # SGI C++
+ archive_cmds_CXX='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+
+ # Archives containing C++ object files must be created using
+ # "CC -ar", where "CC" is the IRIX C++ compiler. This is
+ # necessary to make sure instantiated templates are included
+ # in the archive.
+ old_archive_cmds_CXX='$CC -ar -WR,-u -o $oldlib $oldobjs'
+ ;;
+ *)
+ if test "$GXX" = yes; then
+ if test "$with_gnu_ld" = no; then
+ archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ else
+ archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` -o $lib'
+ fi
+ fi
+ link_all_deplibs_CXX=yes
+ ;;
+ esac
+ hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator_CXX=:
+ inherit_rpath_CXX=yes
+ ;;
+
+ linux* | k*bsd*-gnu | kopensolaris*-gnu)
+ case $cc_basename in
+ KCC*)
+ # Kuck and Associates, Inc. (KAI) C++ Compiler
+
+ # KCC will only create a shared library if the output file
+ # ends with ".so" (or ".sl" for HP-UX), so rename the library
+ # to its proper name (with version) after linking.
+ archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib'
+ archive_expsym_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib ${wl}-retain-symbols-file,$export_symbols; mv \$templib $lib'
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ #
+ # There doesn't appear to be a way to prevent this compiler from
+ # explicitly linking system object files so we need to strip them
+ # from the output so that they don't get included in the library
+ # dependencies.
+ output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+
+ hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir'
+ export_dynamic_flag_spec_CXX='${wl}--export-dynamic'
+
+ # Archives containing C++ object files must be created using
+ # "CC -Bstatic", where "CC" is the KAI C++ compiler.
+ old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs'
+ ;;
+ icpc* | ecpc* )
+ # Intel C++
+ with_gnu_ld=yes
+ # version 8.0 and above of icpc choke on multiply defined symbols
+ # if we add $predep_objects and $postdep_objects, however 7.1 and
+ # earlier do not add the objects themselves.
+ case `$CC -V 2>&1` in
+ *"Version 7."*)
+ archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ ;;
+ *) # Version 8.0 or newer
+ tmp_idyn=
+ case $host_cpu in
+ ia64*) tmp_idyn=' -i_dynamic';;
+ esac
+ archive_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ ;;
+ esac
+ archive_cmds_need_lc_CXX=no
+ hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir'
+ export_dynamic_flag_spec_CXX='${wl}--export-dynamic'
+ whole_archive_flag_spec_CXX='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+ ;;
+ pgCC* | pgcpp*)
+ # Portland Group C++ compiler
+ case `$CC -V` in
+ *pgCC\ [1-5].* | *pgcpp\ [1-5].*)
+ prelink_cmds_CXX='tpldir=Template.dir~
+ rm -rf $tpldir~
+ $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~
+ compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"'
+ old_archive_cmds_CXX='tpldir=Template.dir~
+ rm -rf $tpldir~
+ $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~
+ $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~
+ $RANLIB $oldlib'
+ archive_cmds_CXX='tpldir=Template.dir~
+ rm -rf $tpldir~
+ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~
+ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib'
+ archive_expsym_cmds_CXX='tpldir=Template.dir~
+ rm -rf $tpldir~
+ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~
+ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib'
+ ;;
+ *) # Version 6 and above use weak symbols
+ archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib'
+ archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib'
+ ;;
+ esac
+
+ hardcode_libdir_flag_spec_CXX='${wl}--rpath ${wl}$libdir'
+ export_dynamic_flag_spec_CXX='${wl}--export-dynamic'
+ whole_archive_flag_spec_CXX='${wl}--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+ ;;
+ cxx*)
+ # Compaq C++
+ archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib ${wl}-retain-symbols-file $wl$export_symbols'
+
+ runpath_var=LD_RUN_PATH
+ hardcode_libdir_flag_spec_CXX='-rpath $libdir'
+ hardcode_libdir_separator_CXX=:
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ #
+ # There doesn't appear to be a way to prevent this compiler from
+ # explicitly linking system object files so we need to strip them
+ # from the output so that they don't get included in the library
+ # dependencies.
+ output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed'
+ ;;
+ xl* | mpixl* | bgxl*)
+ # IBM XL 8.0 on PPC, with GNU ld
+ hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir'
+ export_dynamic_flag_spec_CXX='${wl}--export-dynamic'
+ archive_cmds_CXX='$CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ if test "x$supports_anon_versioning" = xyes; then
+ archive_expsym_cmds_CXX='echo "{ global:" > $output_objdir/$libname.ver~
+ cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+ echo "local: *; };" >> $output_objdir/$libname.ver~
+ $CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib'
+ fi
+ ;;
+ *)
+ case `$CC -V 2>&1 | sed 5q` in
+ *Sun\ C*)
+ # Sun C++ 5.9
+ no_undefined_flag_CXX=' -zdefs'
+ archive_cmds_CXX='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ archive_expsym_cmds_CXX='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file ${wl}$export_symbols'
+ hardcode_libdir_flag_spec_CXX='-R$libdir'
+ whole_archive_flag_spec_CXX='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+ compiler_needs_object_CXX=yes
+
+ # Not sure whether something based on
+ # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1
+ # would be better.
+ output_verbose_link_cmd='func_echo_all'
+
+ # Archives containing C++ object files must be created using
+ # "CC -xar", where "CC" is the Sun C++ compiler. This is
+ # necessary to make sure instantiated templates are included
+ # in the archive.
+ old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs'
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+
+ lynxos*)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+
+ m88k*)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+
+ mvs*)
+ case $cc_basename in
+ cxx*)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ *)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ esac
+ ;;
+
+ netbsd*)
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ archive_cmds_CXX='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags'
+ wlarc=
+ hardcode_libdir_flag_spec_CXX='-R$libdir'
+ hardcode_direct_CXX=yes
+ hardcode_shlibpath_var_CXX=no
+ fi
+ # Workaround some broken pre-1.5 toolchains
+ output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"'
+ ;;
+
+ *nto* | *qnx*)
+ ld_shlibs_CXX=yes
+ ;;
+
+ openbsd2*)
+ # C++ shared libraries are fairly broken
+ ld_shlibs_CXX=no
+ ;;
+
+ openbsd*)
+ if test -f /usr/libexec/ld.so; then
+ hardcode_direct_CXX=yes
+ hardcode_shlibpath_var_CXX=no
+ hardcode_direct_absolute_CXX=yes
+ archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib'
+ hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir'
+ if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file,$export_symbols -o $lib'
+ export_dynamic_flag_spec_CXX='${wl}-E'
+ whole_archive_flag_spec_CXX="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+ fi
+ output_verbose_link_cmd=func_echo_all
+ else
+ ld_shlibs_CXX=no
+ fi
+ ;;
+
+ osf3* | osf4* | osf5*)
+ case $cc_basename in
+ KCC*)
+ # Kuck and Associates, Inc. (KAI) C++ Compiler
+
+ # KCC will only create a shared library if the output file
+ # ends with ".so" (or ".sl" for HP-UX), so rename the library
+ # to its proper name (with version) after linking.
+ archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib'
+
+ hardcode_libdir_flag_spec_CXX='${wl}-rpath,$libdir'
+ hardcode_libdir_separator_CXX=:
+
+ # Archives containing C++ object files must be created using
+ # the KAI C++ compiler.
+ case $host in
+ osf3*) old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' ;;
+ *) old_archive_cmds_CXX='$CC -o $oldlib $oldobjs' ;;
+ esac
+ ;;
+ RCC*)
+ # Rational C++ 2.4.1
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ cxx*)
+ case $host in
+ osf3*)
+ allow_undefined_flag_CXX=' ${wl}-expect_unresolved ${wl}\*'
+ archive_cmds_CXX='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $soname `test -n "$verstring" && func_echo_all "${wl}-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+ hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir'
+ ;;
+ *)
+ allow_undefined_flag_CXX=' -expect_unresolved \*'
+ archive_cmds_CXX='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+ archive_expsym_cmds_CXX='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~
+ echo "-hidden">> $lib.exp~
+ $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname ${wl}-input ${wl}$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~
+ $RM $lib.exp'
+ hardcode_libdir_flag_spec_CXX='-rpath $libdir'
+ ;;
+ esac
+
+ hardcode_libdir_separator_CXX=:
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ #
+ # There doesn't appear to be a way to prevent this compiler from
+ # explicitly linking system object files so we need to strip them
+ # from the output so that they don't get included in the library
+ # dependencies.
+ output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+ ;;
+ *)
+ if test "$GXX" = yes && test "$with_gnu_ld" = no; then
+ allow_undefined_flag_CXX=' ${wl}-expect_unresolved ${wl}\*'
+ case $host in
+ osf3*)
+ archive_cmds_CXX='$CC -shared -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ ;;
+ *)
+ archive_cmds_CXX='$CC -shared $pic_flag -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ ;;
+ esac
+
+ hardcode_libdir_flag_spec_CXX='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator_CXX=:
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+
+ else
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ fi
+ ;;
+ esac
+ ;;
+
+ psos*)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+
+ sunos4*)
+ case $cc_basename in
+ CC*)
+ # Sun C++ 4.x
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ lcc*)
+ # Lucid
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ *)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ esac
+ ;;
+
+ solaris*)
+ case $cc_basename in
+ CC* | sunCC*)
+ # Sun C++ 4.2, 5.x and Centerline C++
+ archive_cmds_need_lc_CXX=yes
+ no_undefined_flag_CXX=' -zdefs'
+ archive_cmds_CXX='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+ archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -G${allow_undefined_flag} ${wl}-M ${wl}$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+ hardcode_libdir_flag_spec_CXX='-R$libdir'
+ hardcode_shlibpath_var_CXX=no
+ case $host_os in
+ solaris2.[0-5] | solaris2.[0-5].*) ;;
+ *)
+ # The compiler driver will combine and reorder linker options,
+ # but understands `-z linker_flag'.
+ # Supported since Solaris 2.6 (maybe 2.5.1?)
+ whole_archive_flag_spec_CXX='-z allextract$convenience -z defaultextract'
+ ;;
+ esac
+ link_all_deplibs_CXX=yes
+
+ output_verbose_link_cmd='func_echo_all'
+
+ # Archives containing C++ object files must be created using
+ # "CC -xar", where "CC" is the Sun C++ compiler. This is
+ # necessary to make sure instantiated templates are included
+ # in the archive.
+ old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs'
+ ;;
+ gcx*)
+ # Green Hills C++ Compiler
+ archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+
+ # The C++ compiler must be used to create the archive.
+ old_archive_cmds_CXX='$CC $LDFLAGS -archive -o $oldlib $oldobjs'
+ ;;
+ *)
+ # GNU C++ compiler with Solaris linker
+ if test "$GXX" = yes && test "$with_gnu_ld" = no; then
+ no_undefined_flag_CXX=' ${wl}-z ${wl}defs'
+ if $CC --version | $GREP -v '^2\.7' > /dev/null; then
+ archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+ archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -shared $pic_flag -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+ else
+ # g++ 2.7 appears to require `-G' NOT `-shared' on this
+ # platform.
+ archive_cmds_CXX='$CC -G -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+ archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+ $CC -G -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+ # Commands to make compiler produce verbose output that lists
+ # what "hidden" libraries, object files and flags are used when
+ # linking a shared library.
+ output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+ fi
+
+ hardcode_libdir_flag_spec_CXX='${wl}-R $wl$libdir'
+ case $host_os in
+ solaris2.[0-5] | solaris2.[0-5].*) ;;
+ *)
+ whole_archive_flag_spec_CXX='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract'
+ ;;
+ esac
+ fi
+ ;;
+ esac
+ ;;
+
+ sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*)
+ no_undefined_flag_CXX='${wl}-z,text'
+ archive_cmds_need_lc_CXX=no
+ hardcode_shlibpath_var_CXX=no
+ runpath_var='LD_RUN_PATH'
+
+ case $cc_basename in
+ CC*)
+ archive_cmds_CXX='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds_CXX='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ *)
+ archive_cmds_CXX='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds_CXX='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ esac
+ ;;
+
+ sysv5* | sco3.2v5* | sco5v6*)
+ # Note: We can NOT use -z defs as we might desire, because we do not
+ # link with -lc, and that would cause any symbols used from libc to
+ # always be unresolved, which means just about no library would
+ # ever link correctly. If we're not using GNU ld we use -z text
+ # though, which does catch some bad symbols but isn't as heavy-handed
+ # as -z defs.
+ no_undefined_flag_CXX='${wl}-z,text'
+ allow_undefined_flag_CXX='${wl}-z,nodefs'
+ archive_cmds_need_lc_CXX=no
+ hardcode_shlibpath_var_CXX=no
+ hardcode_libdir_flag_spec_CXX='${wl}-R,$libdir'
+ hardcode_libdir_separator_CXX=':'
+ link_all_deplibs_CXX=yes
+ export_dynamic_flag_spec_CXX='${wl}-Bexport'
+ runpath_var='LD_RUN_PATH'
+
+ case $cc_basename in
+ CC*)
+ archive_cmds_CXX='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds_CXX='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ old_archive_cmds_CXX='$CC -Tprelink_objects $oldobjs~
+ '"$old_archive_cmds_CXX"
+ reload_cmds_CXX='$CC -Tprelink_objects $reload_objs~
+ '"$reload_cmds_CXX"
+ ;;
+ *)
+ archive_cmds_CXX='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ archive_expsym_cmds_CXX='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+ ;;
+ esac
+ ;;
+
+ tandem*)
+ case $cc_basename in
+ NCC*)
+ # NonStop-UX NCC 3.20
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ *)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ esac
+ ;;
+
+ vxworks*)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+
+ *)
+ # FIXME: insert proper C++ library support
+ ld_shlibs_CXX=no
+ ;;
+ esac
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5
+$as_echo "$ld_shlibs_CXX" >&6; }
+ test "$ld_shlibs_CXX" = no && can_build_shared=no
+
+ GCC_CXX="$GXX"
+ LD_CXX="$LD"
+
+ ## CAVEAT EMPTOR:
+ ## There is no encapsulation within the following macros, do not change
+ ## the running order or otherwise move them around unless you know exactly
+ ## what you are doing...
+ # Dependencies to place before and after the object being linked:
+predep_objects_CXX=
+postdep_objects_CXX=
+predeps_CXX=
+postdeps_CXX=
+compiler_lib_search_path_CXX=
+
+cat > conftest.$ac_ext <<_LT_EOF
+class Foo
+{
+public:
+ Foo (void) { a = 0; }
+private:
+ int a;
+};
+_LT_EOF
+
+
+_lt_libdeps_save_CFLAGS=$CFLAGS
+case "$CC $CFLAGS " in #(
+*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;;
+*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;;
+*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;;
+esac
+
+if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ # Parse the compiler output and extract the necessary
+ # objects, libraries and library flags.
+
+ # Sentinel used to keep track of whether or not we are before
+ # the conftest object file.
+ pre_test_object_deps_done=no
+
+ for p in `eval "$output_verbose_link_cmd"`; do
+ case ${prev}${p} in
+
+ -L* | -R* | -l*)
+ # Some compilers place space between "-{L,R}" and the path.
+ # Remove the space.
+ if test $p = "-L" ||
+ test $p = "-R"; then
+ prev=$p
+ continue
+ fi
+
+ # Expand the sysroot to ease extracting the directories later.
+ if test -z "$prev"; then
+ case $p in
+ -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;;
+ -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;;
+ -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;;
+ esac
+ fi
+ case $p in
+ =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;;
+ esac
+ if test "$pre_test_object_deps_done" = no; then
+ case ${prev} in
+ -L | -R)
+ # Internal compiler library paths should come after those
+ # provided the user. The postdeps already come after the
+ # user supplied libs so there is no need to process them.
+ if test -z "$compiler_lib_search_path_CXX"; then
+ compiler_lib_search_path_CXX="${prev}${p}"
+ else
+ compiler_lib_search_path_CXX="${compiler_lib_search_path_CXX} ${prev}${p}"
+ fi
+ ;;
+ # The "-l" case would never come before the object being
+ # linked, so don't bother handling this case.
+ esac
+ else
+ if test -z "$postdeps_CXX"; then
+ postdeps_CXX="${prev}${p}"
+ else
+ postdeps_CXX="${postdeps_CXX} ${prev}${p}"
+ fi
+ fi
+ prev=
+ ;;
+
+ *.lto.$objext) ;; # Ignore GCC LTO objects
+ *.$objext)
+ # This assumes that the test object file only shows up
+ # once in the compiler output.
+ if test "$p" = "conftest.$objext"; then
+ pre_test_object_deps_done=yes
+ continue
+ fi
+
+ if test "$pre_test_object_deps_done" = no; then
+ if test -z "$predep_objects_CXX"; then
+ predep_objects_CXX="$p"
+ else
+ predep_objects_CXX="$predep_objects_CXX $p"
+ fi
+ else
+ if test -z "$postdep_objects_CXX"; then
+ postdep_objects_CXX="$p"
+ else
+ postdep_objects_CXX="$postdep_objects_CXX $p"
+ fi
+ fi
+ ;;
+
+ *) ;; # Ignore the rest.
+
+ esac
+ done
+
+ # Clean up.
+ rm -f a.out a.exe
+else
+ echo "libtool.m4: error: problem compiling CXX test program"
+fi
+
+$RM -f confest.$objext
+CFLAGS=$_lt_libdeps_save_CFLAGS
+
+# PORTME: override above test on systems where it is broken
+case $host_os in
+interix[3-9]*)
+ # Interix 3.5 installs completely hosed .la files for C++, so rather than
+ # hack all around it, let's just trust "g++" to DTRT.
+ predep_objects_CXX=
+ postdep_objects_CXX=
+ postdeps_CXX=
+ ;;
+
+linux*)
+ case `$CC -V 2>&1 | sed 5q` in
+ *Sun\ C*)
+ # Sun C++ 5.9
+
+ # The more standards-conforming stlport4 library is
+ # incompatible with the Cstd library. Avoid specifying
+ # it if it's in CXXFLAGS. Ignore libCrun as
+ # -library=stlport4 depends on it.
+ case " $CXX $CXXFLAGS " in
+ *" -library=stlport4 "*)
+ solaris_use_stlport4=yes
+ ;;
+ esac
+
+ if test "$solaris_use_stlport4" != yes; then
+ postdeps_CXX='-library=Cstd -library=Crun'
+ fi
+ ;;
+ esac
+ ;;
+
+solaris*)
+ case $cc_basename in
+ CC* | sunCC*)
+ # The more standards-conforming stlport4 library is
+ # incompatible with the Cstd library. Avoid specifying
+ # it if it's in CXXFLAGS. Ignore libCrun as
+ # -library=stlport4 depends on it.
+ case " $CXX $CXXFLAGS " in
+ *" -library=stlport4 "*)
+ solaris_use_stlport4=yes
+ ;;
+ esac
+
+ # Adding this requires a known-good setup of shared libraries for
+ # Sun compiler versions before 5.6, else PIC objects from an old
+ # archive will be linked into the output, leading to subtle bugs.
+ if test "$solaris_use_stlport4" != yes; then
+ postdeps_CXX='-library=Cstd -library=Crun'
+ fi
+ ;;
+ esac
+ ;;
+esac
+
+
+case " $postdeps_CXX " in
+*" -lc "*) archive_cmds_need_lc_CXX=no ;;
+esac
+ compiler_lib_search_dirs_CXX=
+if test -n "${compiler_lib_search_path_CXX}"; then
+ compiler_lib_search_dirs_CXX=`echo " ${compiler_lib_search_path_CXX}" | ${SED} -e 's! -L! !g' -e 's!^ !!'`
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ lt_prog_compiler_wl_CXX=
+lt_prog_compiler_pic_CXX=
+lt_prog_compiler_static_CXX=
+
+
+ # C++ specific cases for pic, static, wl, etc.
+ if test "$GXX" = yes; then
+ lt_prog_compiler_wl_CXX='-Wl,'
+ lt_prog_compiler_static_CXX='-static'
+
+ case $host_os in
+ aix*)
+ # All AIX code is PIC.
+ if test "$host_cpu" = ia64; then
+ # AIX 5 now supports IA64 processor
+ lt_prog_compiler_static_CXX='-Bstatic'
+ fi
+ ;;
+
+ amigaos*)
+ case $host_cpu in
+ powerpc)
+ # see comment about AmigaOS4 .so support
+ lt_prog_compiler_pic_CXX='-fPIC'
+ ;;
+ m68k)
+ # FIXME: we need at least 68020 code to build shared libraries, but
+ # adding the `-m68020' flag to GCC prevents building anything better,
+ # like `-m68040'.
+ lt_prog_compiler_pic_CXX='-m68020 -resident32 -malways-restore-a4'
+ ;;
+ esac
+ ;;
+
+ beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+ # PIC is the default for these OSes.
+ ;;
+ mingw* | cygwin* | os2* | pw32* | cegcc*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ # Although the cygwin gcc ignores -fPIC, still need this for old-style
+ # (--disable-auto-import) libraries
+ lt_prog_compiler_pic_CXX='-DDLL_EXPORT'
+ ;;
+ darwin* | rhapsody*)
+ # PIC is the default on this platform
+ # Common symbols not allowed in MH_DYLIB files
+ lt_prog_compiler_pic_CXX='-fno-common'
+ ;;
+ *djgpp*)
+ # DJGPP does not support shared libraries at all
+ lt_prog_compiler_pic_CXX=
+ ;;
+ haiku*)
+ # PIC is the default for Haiku.
+ # The "-static" flag exists, but is broken.
+ lt_prog_compiler_static_CXX=
+ ;;
+ interix[3-9]*)
+ # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+ # Instead, we relocate shared libraries at runtime.
+ ;;
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ lt_prog_compiler_pic_CXX=-Kconform_pic
+ fi
+ ;;
+ hpux*)
+ # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+ # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag
+ # sets the default TLS model and affects inlining.
+ case $host_cpu in
+ hppa*64*)
+ ;;
+ *)
+ lt_prog_compiler_pic_CXX='-fPIC'
+ ;;
+ esac
+ ;;
+ *qnx* | *nto*)
+ # QNX uses GNU C++, but need to define -shared option too, otherwise
+ # it will coredump.
+ lt_prog_compiler_pic_CXX='-fPIC -shared'
+ ;;
+ *)
+ lt_prog_compiler_pic_CXX='-fPIC'
+ ;;
+ esac
+ else
+ case $host_os in
+ aix[4-9]*)
+ # All AIX code is PIC.
+ if test "$host_cpu" = ia64; then
+ # AIX 5 now supports IA64 processor
+ lt_prog_compiler_static_CXX='-Bstatic'
+ else
+ lt_prog_compiler_static_CXX='-bnso -bI:/lib/syscalls.exp'
+ fi
+ ;;
+ chorus*)
+ case $cc_basename in
+ cxch68*)
+ # Green Hills C++ Compiler
+ # _LT_TAGVAR(lt_prog_compiler_static, CXX)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a"
+ ;;
+ esac
+ ;;
+ mingw* | cygwin* | os2* | pw32* | cegcc*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ lt_prog_compiler_pic_CXX='-DDLL_EXPORT'
+ ;;
+ dgux*)
+ case $cc_basename in
+ ec++*)
+ lt_prog_compiler_pic_CXX='-KPIC'
+ ;;
+ ghcx*)
+ # Green Hills C++ Compiler
+ lt_prog_compiler_pic_CXX='-pic'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ freebsd* | dragonfly*)
+ # FreeBSD uses GNU C++
+ ;;
+ hpux9* | hpux10* | hpux11*)
+ case $cc_basename in
+ CC*)
+ lt_prog_compiler_wl_CXX='-Wl,'
+ lt_prog_compiler_static_CXX='${wl}-a ${wl}archive'
+ if test "$host_cpu" != ia64; then
+ lt_prog_compiler_pic_CXX='+Z'
+ fi
+ ;;
+ aCC*)
+ lt_prog_compiler_wl_CXX='-Wl,'
+ lt_prog_compiler_static_CXX='${wl}-a ${wl}archive'
+ case $host_cpu in
+ hppa*64*|ia64*)
+ # +Z the default
+ ;;
+ *)
+ lt_prog_compiler_pic_CXX='+Z'
+ ;;
+ esac
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ interix*)
+ # This is c89, which is MS Visual C++ (no shared libs)
+ # Anyone wants to do a port?
+ ;;
+ irix5* | irix6* | nonstopux*)
+ case $cc_basename in
+ CC*)
+ lt_prog_compiler_wl_CXX='-Wl,'
+ lt_prog_compiler_static_CXX='-non_shared'
+ # CC pic flag -KPIC is the default.
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ linux* | k*bsd*-gnu | kopensolaris*-gnu)
+ case $cc_basename in
+ KCC*)
+ # KAI C++ Compiler
+ lt_prog_compiler_wl_CXX='--backend -Wl,'
+ lt_prog_compiler_pic_CXX='-fPIC'
+ ;;
+ ecpc* )
+ # old Intel C++ for x86_64 which still supported -KPIC.
+ lt_prog_compiler_wl_CXX='-Wl,'
+ lt_prog_compiler_pic_CXX='-KPIC'
+ lt_prog_compiler_static_CXX='-static'
+ ;;
+ icpc* )
+ # Intel C++, used to be incompatible with GCC.
+ # ICC 10 doesn't accept -KPIC any more.
+ lt_prog_compiler_wl_CXX='-Wl,'
+ lt_prog_compiler_pic_CXX='-fPIC'
+ lt_prog_compiler_static_CXX='-static'
+ ;;
+ pgCC* | pgcpp*)
+ # Portland Group C++ compiler
+ lt_prog_compiler_wl_CXX='-Wl,'
+ lt_prog_compiler_pic_CXX='-fpic'
+ lt_prog_compiler_static_CXX='-Bstatic'
+ ;;
+ cxx*)
+ # Compaq C++
+ # Make sure the PIC flag is empty. It appears that all Alpha
+ # Linux and Compaq Tru64 Unix objects are PIC.
+ lt_prog_compiler_pic_CXX=
+ lt_prog_compiler_static_CXX='-non_shared'
+ ;;
+ xlc* | xlC* | bgxl[cC]* | mpixl[cC]*)
+ # IBM XL 8.0, 9.0 on PPC and BlueGene
+ lt_prog_compiler_wl_CXX='-Wl,'
+ lt_prog_compiler_pic_CXX='-qpic'
+ lt_prog_compiler_static_CXX='-qstaticlink'
+ ;;
+ *)
+ case `$CC -V 2>&1 | sed 5q` in
+ *Sun\ C*)
+ # Sun C++ 5.9
+ lt_prog_compiler_pic_CXX='-KPIC'
+ lt_prog_compiler_static_CXX='-Bstatic'
+ lt_prog_compiler_wl_CXX='-Qoption ld '
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+ lynxos*)
+ ;;
+ m88k*)
+ ;;
+ mvs*)
+ case $cc_basename in
+ cxx*)
+ lt_prog_compiler_pic_CXX='-W c,exportall'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ netbsd*)
+ ;;
+ *qnx* | *nto*)
+ # QNX uses GNU C++, but need to define -shared option too, otherwise
+ # it will coredump.
+ lt_prog_compiler_pic_CXX='-fPIC -shared'
+ ;;
+ osf3* | osf4* | osf5*)
+ case $cc_basename in
+ KCC*)
+ lt_prog_compiler_wl_CXX='--backend -Wl,'
+ ;;
+ RCC*)
+ # Rational C++ 2.4.1
+ lt_prog_compiler_pic_CXX='-pic'
+ ;;
+ cxx*)
+ # Digital/Compaq C++
+ lt_prog_compiler_wl_CXX='-Wl,'
+ # Make sure the PIC flag is empty. It appears that all Alpha
+ # Linux and Compaq Tru64 Unix objects are PIC.
+ lt_prog_compiler_pic_CXX=
+ lt_prog_compiler_static_CXX='-non_shared'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ psos*)
+ ;;
+ solaris*)
+ case $cc_basename in
+ CC* | sunCC*)
+ # Sun C++ 4.2, 5.x and Centerline C++
+ lt_prog_compiler_pic_CXX='-KPIC'
+ lt_prog_compiler_static_CXX='-Bstatic'
+ lt_prog_compiler_wl_CXX='-Qoption ld '
+ ;;
+ gcx*)
+ # Green Hills C++ Compiler
+ lt_prog_compiler_pic_CXX='-PIC'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ sunos4*)
+ case $cc_basename in
+ CC*)
+ # Sun C++ 4.x
+ lt_prog_compiler_pic_CXX='-pic'
+ lt_prog_compiler_static_CXX='-Bstatic'
+ ;;
+ lcc*)
+ # Lucid
+ lt_prog_compiler_pic_CXX='-pic'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+ case $cc_basename in
+ CC*)
+ lt_prog_compiler_wl_CXX='-Wl,'
+ lt_prog_compiler_pic_CXX='-KPIC'
+ lt_prog_compiler_static_CXX='-Bstatic'
+ ;;
+ esac
+ ;;
+ tandem*)
+ case $cc_basename in
+ NCC*)
+ # NonStop-UX NCC 3.20
+ lt_prog_compiler_pic_CXX='-KPIC'
+ ;;
+ *)
+ ;;
+ esac
+ ;;
+ vxworks*)
+ ;;
+ *)
+ lt_prog_compiler_can_build_shared_CXX=no
+ ;;
+ esac
+ fi
+
+case $host_os in
+ # For platforms which do not support PIC, -DPIC is meaningless:
+ *djgpp*)
+ lt_prog_compiler_pic_CXX=
+ ;;
+ *)
+ lt_prog_compiler_pic_CXX="$lt_prog_compiler_pic_CXX -DPIC"
+ ;;
+esac
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5
+$as_echo_n "checking for $compiler option to produce PIC... " >&6; }
+if ${lt_cv_prog_compiler_pic_CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler_pic_CXX=$lt_prog_compiler_pic_CXX
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_CXX" >&5
+$as_echo "$lt_cv_prog_compiler_pic_CXX" >&6; }
+lt_prog_compiler_pic_CXX=$lt_cv_prog_compiler_pic_CXX
+
+#
+# Check to make sure the PIC flag actually works.
+#
+if test -n "$lt_prog_compiler_pic_CXX"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works" >&5
+$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works... " >&6; }
+if ${lt_cv_prog_compiler_pic_works_CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler_pic_works_CXX=no
+ ac_outfile=conftest.$ac_objext
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+ lt_compiler_flag="$lt_prog_compiler_pic_CXX -DPIC"
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ # The option is referenced via a variable to avoid confusing sed.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+ (eval "$lt_compile" 2>conftest.err)
+ ac_status=$?
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ if (exit $ac_status) && test -s "$ac_outfile"; then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings other than the usual output.
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_pic_works_CXX=yes
+ fi
+ fi
+ $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works_CXX" >&5
+$as_echo "$lt_cv_prog_compiler_pic_works_CXX" >&6; }
+
+if test x"$lt_cv_prog_compiler_pic_works_CXX" = xyes; then
+ case $lt_prog_compiler_pic_CXX in
+ "" | " "*) ;;
+ *) lt_prog_compiler_pic_CXX=" $lt_prog_compiler_pic_CXX" ;;
+ esac
+else
+ lt_prog_compiler_pic_CXX=
+ lt_prog_compiler_can_build_shared_CXX=no
+fi
+
+fi
+
+
+
+
+
+#
+# Check to make sure the static flag actually works.
+#
+wl=$lt_prog_compiler_wl_CXX eval lt_tmp_static_flag=\"$lt_prog_compiler_static_CXX\"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5
+$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; }
+if ${lt_cv_prog_compiler_static_works_CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler_static_works_CXX=no
+ save_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS $lt_tmp_static_flag"
+ echo "$lt_simple_link_test_code" > conftest.$ac_ext
+ if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+ # The linker can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s conftest.err; then
+ # Append any errors to the config.log.
+ cat conftest.err 1>&5
+ $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+ $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+ if diff conftest.exp conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_static_works_CXX=yes
+ fi
+ else
+ lt_cv_prog_compiler_static_works_CXX=yes
+ fi
+ fi
+ $RM -r conftest*
+ LDFLAGS="$save_LDFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works_CXX" >&5
+$as_echo "$lt_cv_prog_compiler_static_works_CXX" >&6; }
+
+if test x"$lt_cv_prog_compiler_static_works_CXX" = xyes; then
+ :
+else
+ lt_prog_compiler_static_CXX=
+fi
+
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if ${lt_cv_prog_compiler_c_o_CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler_c_o_CXX=no
+ $RM -r conftest 2>/dev/null
+ mkdir conftest
+ cd conftest
+ mkdir out
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+ lt_compiler_flag="-o out/conftest2.$ac_objext"
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+ (eval "$lt_compile" 2>out/conftest.err)
+ ac_status=$?
+ cat out/conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ if (exit $ac_status) && test -s out/conftest2.$ac_objext
+ then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+ $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+ if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_c_o_CXX=yes
+ fi
+ fi
+ chmod u+w . 2>&5
+ $RM conftest*
+ # SGI C++ compiler will create directory out/ii_files/ for
+ # template instantiation
+ test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+ $RM out/* && rmdir out
+ cd ..
+ $RM -r conftest
+ $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5
+$as_echo "$lt_cv_prog_compiler_c_o_CXX" >&6; }
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if ${lt_cv_prog_compiler_c_o_CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_prog_compiler_c_o_CXX=no
+ $RM -r conftest 2>/dev/null
+ mkdir conftest
+ cd conftest
+ mkdir out
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+ lt_compiler_flag="-o out/conftest2.$ac_objext"
+ # Insert the option either (1) after the last *FLAGS variable, or
+ # (2) before a word containing "conftest.", or (3) at the end.
+ # Note that $ac_compile itself does not contain backslashes and begins
+ # with a dollar sign (not a hyphen), so the echo should work correctly.
+ lt_compile=`echo "$ac_compile" | $SED \
+ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+ -e 's:$: $lt_compiler_flag:'`
+ (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+ (eval "$lt_compile" 2>out/conftest.err)
+ ac_status=$?
+ cat out/conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ if (exit $ac_status) && test -s out/conftest2.$ac_objext
+ then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+ $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+ if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+ lt_cv_prog_compiler_c_o_CXX=yes
+ fi
+ fi
+ chmod u+w . 2>&5
+ $RM conftest*
+ # SGI C++ compiler will create directory out/ii_files/ for
+ # template instantiation
+ test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+ $RM out/* && rmdir out
+ cd ..
+ $RM -r conftest
+ $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5
+$as_echo "$lt_cv_prog_compiler_c_o_CXX" >&6; }
+
+
+
+
+hard_links="nottested"
+if test "$lt_cv_prog_compiler_c_o_CXX" = no && test "$need_locks" != no; then
+ # do not overwrite the value of need_locks provided by the user
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5
+$as_echo_n "checking if we can lock with hard links... " >&6; }
+ hard_links=yes
+ $RM conftest*
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ touch conftest.a
+ ln conftest.a conftest.b 2>&5 || hard_links=no
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5
+$as_echo "$hard_links" >&6; }
+ if test "$hard_links" = no; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&5
+$as_echo "$as_me: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2;}
+ need_locks=warn
+ fi
+else
+ need_locks=no
+fi
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5
+$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; }
+
+ export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+ exclude_expsyms_CXX='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'
+ case $host_os in
+ aix[4-9]*)
+ # If we're using GNU nm, then we don't want the "-C" option.
+ # -C means demangle to AIX nm, but means don't demangle with GNU nm
+ # Also, AIX nm treats weak defined symbols like other global defined
+ # symbols, whereas GNU nm marks them as "W".
+ if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+ export_symbols_cmds_CXX='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+ else
+ export_symbols_cmds_CXX='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+ fi
+ ;;
+ pw32*)
+ export_symbols_cmds_CXX="$ltdll_cmds"
+ ;;
+ cygwin* | mingw* | cegcc*)
+ case $cc_basename in
+ cl*)
+ exclude_expsyms_CXX='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+ ;;
+ *)
+ export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols'
+ exclude_expsyms_CXX='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'
+ ;;
+ esac
+ ;;
+ *)
+ export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+ ;;
+ esac
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5
+$as_echo "$ld_shlibs_CXX" >&6; }
+test "$ld_shlibs_CXX" = no && can_build_shared=no
+
+with_gnu_ld_CXX=$with_gnu_ld
+
+
+
+
+
+
+#
+# Do we need to explicitly link libc?
+#
+case "x$archive_cmds_need_lc_CXX" in
+x|xyes)
+ # Assume -lc should be added
+ archive_cmds_need_lc_CXX=yes
+
+ if test "$enable_shared" = yes && test "$GCC" = yes; then
+ case $archive_cmds_CXX in
+ *'~'*)
+ # FIXME: we may have to deal with multi-command sequences.
+ ;;
+ '$CC '*)
+ # Test whether the compiler implicitly links with -lc since on some
+ # systems, -lgcc has to come before -lc. If gcc already passes -lc
+ # to ld, don't add -lc before -lgcc.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5
+$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; }
+if ${lt_cv_archive_cmds_need_lc_CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ $RM conftest*
+ echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } 2>conftest.err; then
+ soname=conftest
+ lib=conftest
+ libobjs=conftest.$ac_objext
+ deplibs=
+ wl=$lt_prog_compiler_wl_CXX
+ pic_flag=$lt_prog_compiler_pic_CXX
+ compiler_flags=-v
+ linker_flags=-v
+ verstring=
+ output_objdir=.
+ libname=conftest
+ lt_save_allow_undefined_flag=$allow_undefined_flag_CXX
+ allow_undefined_flag_CXX=
+ if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5
+ (eval $archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+ then
+ lt_cv_archive_cmds_need_lc_CXX=no
+ else
+ lt_cv_archive_cmds_need_lc_CXX=yes
+ fi
+ allow_undefined_flag_CXX=$lt_save_allow_undefined_flag
+ else
+ cat conftest.err 1>&5
+ fi
+ $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc_CXX" >&5
+$as_echo "$lt_cv_archive_cmds_need_lc_CXX" >&6; }
+ archive_cmds_need_lc_CXX=$lt_cv_archive_cmds_need_lc_CXX
+ ;;
+ esac
+ fi
+ ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5
+$as_echo_n "checking dynamic linker characteristics... " >&6; }
+
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+shrext_cmds=".so"
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+need_lib_prefix=unknown
+hardcode_into_libs=no
+
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+need_version=unknown
+
+case $host_os in
+aix3*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a'
+ shlibpath_var=LIBPATH
+
+ # AIX 3 has no versioning support, so we append a major version to the name.
+ soname_spec='${libname}${release}${shared_ext}$major'
+ ;;
+
+aix[4-9]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ hardcode_into_libs=yes
+ if test "$host_cpu" = ia64; then
+ # AIX 5 supports IA64
+ library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}'
+ shlibpath_var=LD_LIBRARY_PATH
+ else
+ # With GCC up to 2.95.x, collect2 would create an import file
+ # for dependence libraries. The import file would start with
+ # the line `#! .'. This would cause the generated library to
+ # depend on `.', always an invalid library. This was fixed in
+ # development snapshots of GCC prior to 3.0.
+ case $host_os in
+ aix4 | aix4.[01] | aix4.[01].*)
+ if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+ echo ' yes '
+ echo '#endif'; } | ${CC} -E - | $GREP yes > /dev/null; then
+ :
+ else
+ can_build_shared=no
+ fi
+ ;;
+ esac
+ # AIX (on Power*) has no versioning support, so currently we can not hardcode correct
+ # soname into executable. Probably we can add versioning support to
+ # collect2, so additional links can be useful in future.
+ if test "$aix_use_runtimelinking" = yes; then
+ # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+ # instead of lib<name>.a to let people know that these are not
+ # typical AIX shared libraries.
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ else
+ # We preserve .a as extension for shared libraries through AIX4.2
+ # and later when we are not doing run time linking.
+ library_names_spec='${libname}${release}.a $libname.a'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ fi
+ shlibpath_var=LIBPATH
+ fi
+ ;;
+
+amigaos*)
+ case $host_cpu in
+ powerpc)
+ # Since July 2007 AmigaOS4 officially supports .so libraries.
+ # When compiling the executable, add -use-dynld -Lsobjs: to the compileline.
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ ;;
+ m68k)
+ library_names_spec='$libname.ixlibrary $libname.a'
+ # Create ${libname}_ixlibrary.a entries in /sys/libs.
+ finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done'
+ ;;
+ esac
+ ;;
+
+beos*)
+ library_names_spec='${libname}${shared_ext}'
+ dynamic_linker="$host_os ld.so"
+ shlibpath_var=LIBRARY_PATH
+ ;;
+
+bsdi[45]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+ sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+ # the default ld.so.conf also contains /usr/contrib/lib and
+ # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+ # libtool to hard-code these into programs
+ ;;
+
+cygwin* | mingw* | pw32* | cegcc*)
+ version_type=windows
+ shrext_cmds=".dll"
+ need_version=no
+ need_lib_prefix=no
+
+ case $GCC,$cc_basename in
+ yes,*)
+ # gcc
+ library_names_spec='$libname.dll.a'
+ # DLL is installed to $(libdir)/../bin by postinstall_cmds
+ postinstall_cmds='base_file=`basename \${file}`~
+ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog $dir/$dlname \$dldir/$dlname~
+ chmod a+x \$dldir/$dlname~
+ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+ eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+ fi'
+ postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $RM \$dlpath'
+ shlibpath_overrides_runpath=yes
+
+ case $host_os in
+ cygwin*)
+ # Cygwin DLLs use 'cyg' prefix rather than 'lib'
+ soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+
+ ;;
+ mingw* | cegcc*)
+ # MinGW DLLs use traditional 'lib' prefix
+ soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+ ;;
+ pw32*)
+ # pw32 DLLs use 'pw' prefix rather than 'lib'
+ library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+ ;;
+ esac
+ dynamic_linker='Win32 ld.exe'
+ ;;
+
+ *,cl*)
+ # Native MSVC
+ libname_spec='$name'
+ soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+ library_names_spec='${libname}.dll.lib'
+
+ case $build_os in
+ mingw*)
+ sys_lib_search_path_spec=
+ lt_save_ifs=$IFS
+ IFS=';'
+ for lt_path in $LIB
+ do
+ IFS=$lt_save_ifs
+ # Let DOS variable expansion print the short 8.3 style file name.
+ lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"`
+ sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path"
+ done
+ IFS=$lt_save_ifs
+ # Convert to MSYS style.
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'`
+ ;;
+ cygwin*)
+ # Convert to unix form, then to dos form, then back to unix form
+ # but this time dos style (no spaces!) so that the unix form looks
+ # like /cygdrive/c/PROGRA~1:/cygdr...
+ sys_lib_search_path_spec=`cygpath --path --unix "$LIB"`
+ sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null`
+ sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+ ;;
+ *)
+ sys_lib_search_path_spec="$LIB"
+ if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then
+ # It is most probably a Windows format PATH.
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'`
+ else
+ sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+ fi
+ # FIXME: find the short name or the path components, as spaces are
+ # common. (e.g. "Program Files" -> "PROGRA~1")
+ ;;
+ esac
+
+ # DLL is installed to $(libdir)/../bin by postinstall_cmds
+ postinstall_cmds='base_file=`basename \${file}`~
+ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog $dir/$dlname \$dldir/$dlname'
+ postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $RM \$dlpath'
+ shlibpath_overrides_runpath=yes
+ dynamic_linker='Win32 link.exe'
+ ;;
+
+ *)
+ # Assume MSVC wrapper
+ library_names_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext} $libname.lib'
+ dynamic_linker='Win32 ld.exe'
+ ;;
+ esac
+ # FIXME: first we should search . and the directory the executable is in
+ shlibpath_var=PATH
+ ;;
+
+darwin* | rhapsody*)
+ dynamic_linker="$host_os dyld"
+ version_type=darwin
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${major}$shared_ext ${libname}$shared_ext'
+ soname_spec='${libname}${release}${major}$shared_ext'
+ shlibpath_overrides_runpath=yes
+ shlibpath_var=DYLD_LIBRARY_PATH
+ shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`'
+
+ sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib'
+ ;;
+
+dgux*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+freebsd* | dragonfly*)
+ # DragonFly does not have aout. When/if they implement a new
+ # versioning mechanism, adjust this.
+ if test -x /usr/bin/objformat; then
+ objformat=`/usr/bin/objformat`
+ else
+ case $host_os in
+ freebsd[23].*) objformat=aout ;;
+ *) objformat=elf ;;
+ esac
+ fi
+ version_type=freebsd-$objformat
+ case $version_type in
+ freebsd-elf*)
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+ need_version=no
+ need_lib_prefix=no
+ ;;
+ freebsd-*)
+ library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix'
+ need_version=yes
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_os in
+ freebsd2.*)
+ shlibpath_overrides_runpath=yes
+ ;;
+ freebsd3.[01]* | freebsdelf3.[01]*)
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+ freebsd3.[2-9]* | freebsdelf3.[2-9]* | \
+ freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1)
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+ *) # from 4.6 on, and DragonFly
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+ esac
+ ;;
+
+gnu*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+
+haiku*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ dynamic_linker="$host_os runtime_loader"
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib'
+ hardcode_into_libs=yes
+ ;;
+
+hpux9* | hpux10* | hpux11*)
+ # Give a soname corresponding to the major version so that dld.sl refuses to
+ # link against other versions.
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ case $host_cpu in
+ ia64*)
+ shrext_cmds='.so'
+ hardcode_into_libs=yes
+ dynamic_linker="$host_os dld.so"
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ if test "X$HPUX_IA64_MODE" = X32; then
+ sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib"
+ else
+ sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64"
+ fi
+ sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+ ;;
+ hppa*64*)
+ shrext_cmds='.sl'
+ hardcode_into_libs=yes
+ dynamic_linker="$host_os dld.sl"
+ shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH
+ shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64"
+ sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+ ;;
+ *)
+ shrext_cmds='.sl'
+ dynamic_linker="$host_os dld.sl"
+ shlibpath_var=SHLIB_PATH
+ shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ ;;
+ esac
+ # HP-UX runs *really* slowly unless shared libraries are mode 555, ...
+ postinstall_cmds='chmod 555 $lib'
+ # or fails outright, so override atomically:
+ install_override_mode=555
+ ;;
+
+interix[3-9]*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+
+irix5* | irix6* | nonstopux*)
+ case $host_os in
+ nonstopux*) version_type=nonstopux ;;
+ *)
+ if test "$lt_cv_prog_gnu_ld" = yes; then
+ version_type=linux # correct to gnu/linux during the next big refactor
+ else
+ version_type=irix
+ fi ;;
+ esac
+ need_lib_prefix=no
+ need_version=no
+ soname_spec='${libname}${release}${shared_ext}$major'
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}'
+ case $host_os in
+ irix5* | nonstopux*)
+ libsuff= shlibsuff=
+ ;;
+ *)
+ case $LD in # libtool.m4 will add one of these switches to LD
+ *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ")
+ libsuff= shlibsuff= libmagic=32-bit;;
+ *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ")
+ libsuff=32 shlibsuff=N32 libmagic=N32;;
+ *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ")
+ libsuff=64 shlibsuff=64 libmagic=64-bit;;
+ *) libsuff= shlibsuff= libmagic=never-match;;
+ esac
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}"
+ sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}"
+ hardcode_into_libs=yes
+ ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux*oldld* | linux*aout* | linux*coff*)
+ dynamic_linker=no
+ ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+
+ # Some binutils ld are patched to set DT_RUNPATH
+ if ${lt_cv_shlibpath_overrides_runpath+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ lt_cv_shlibpath_overrides_runpath=no
+ save_LDFLAGS=$LDFLAGS
+ save_libdir=$libdir
+ eval "libdir=/foo; wl=\"$lt_prog_compiler_wl_CXX\"; \
+ LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec_CXX\""
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_link "$LINENO"; then :
+ if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then :
+ lt_cv_shlibpath_overrides_runpath=yes
+fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ LDFLAGS=$save_LDFLAGS
+ libdir=$save_libdir
+
+fi
+
+ shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath
+
+ # This implies no fast_install, which is unacceptable.
+ # Some rework will be needed to allow for fast_install
+ # before this can be enabled.
+ hardcode_into_libs=yes
+
+ # Append ld.so.conf contents to the search path
+ if test -f /etc/ld.so.conf; then
+ lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '`
+ sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra"
+ fi
+
+ # We used to test for /lib/ld.so.1 and disable shared libraries on
+ # powerpc, because MkLinux only supported shared libraries with the
+ # GNU dynamic linker. Since this was broken with cross compilers,
+ # most powerpc-linux boxes support dynamic linking these days and
+ # people can always --disable-shared, the test was removed, and we
+ # assume the GNU/Linux dynamic linker is in use.
+ dynamic_linker='GNU/Linux ld.so'
+ ;;
+
+netbsd*)
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ dynamic_linker='NetBSD (a.out) ld.so'
+ else
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ dynamic_linker='NetBSD ld.elf_so'
+ fi
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+
+newsos6)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ ;;
+
+*nto* | *qnx*)
+ version_type=qnx
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ dynamic_linker='ldqnx.so'
+ ;;
+
+openbsd*)
+ version_type=sunos
+ sys_lib_dlsearch_path_spec="/usr/lib"
+ need_lib_prefix=no
+ # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs.
+ case $host_os in
+ openbsd3.3 | openbsd3.3.*) need_version=yes ;;
+ *) need_version=no ;;
+ esac
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ case $host_os in
+ openbsd2.[89] | openbsd2.[89].*)
+ shlibpath_overrides_runpath=no
+ ;;
+ *)
+ shlibpath_overrides_runpath=yes
+ ;;
+ esac
+ else
+ shlibpath_overrides_runpath=yes
+ fi
+ ;;
+
+os2*)
+ libname_spec='$name'
+ shrext_cmds=".dll"
+ need_lib_prefix=no
+ library_names_spec='$libname${shared_ext} $libname.a'
+ dynamic_linker='OS/2 ld.exe'
+ shlibpath_var=LIBPATH
+ ;;
+
+osf3* | osf4* | osf5*)
+ version_type=osf
+ need_lib_prefix=no
+ need_version=no
+ soname_spec='${libname}${release}${shared_ext}$major'
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+ sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec"
+ ;;
+
+rdos*)
+ dynamic_linker=no
+ ;;
+
+solaris*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ # ldd complains unless libraries are executable
+ postinstall_cmds='chmod +x $lib'
+ ;;
+
+sunos4*)
+ version_type=sunos
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+ finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ if test "$with_gnu_ld" = yes; then
+ need_lib_prefix=no
+ fi
+ need_version=yes
+ ;;
+
+sysv4 | sysv4.3*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_vendor in
+ sni)
+ shlibpath_overrides_runpath=no
+ need_lib_prefix=no
+ runpath_var=LD_RUN_PATH
+ ;;
+ siemens)
+ need_lib_prefix=no
+ ;;
+ motorola)
+ need_lib_prefix=no
+ need_version=no
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+ ;;
+ esac
+ ;;
+
+sysv4*MP*)
+ if test -d /usr/nec ;then
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}'
+ soname_spec='$libname${shared_ext}.$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ fi
+ ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+ version_type=freebsd-elf
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ if test "$with_gnu_ld" = yes; then
+ sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib'
+ else
+ sys_lib_search_path_spec='/usr/ccs/lib /usr/lib'
+ case $host_os in
+ sco3.2v5*)
+ sys_lib_search_path_spec="$sys_lib_search_path_spec /lib"
+ ;;
+ esac
+ fi
+ sys_lib_dlsearch_path_spec='/usr/lib'
+ ;;
+
+tpf*)
+ # TPF is a cross-target only. Preferred cross-host = GNU/Linux.
+ version_type=linux # correct to gnu/linux during the next big refactor
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+
+uts4*)
+ version_type=linux # correct to gnu/linux during the next big refactor
+ library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+ soname_spec='${libname}${release}${shared_ext}$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+*)
+ dynamic_linker=no
+ ;;
+esac
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5
+$as_echo "$dynamic_linker" >&6; }
+test "$dynamic_linker" = no && can_build_shared=no
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test "$GCC" = yes; then
+ variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then
+ sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec"
+fi
+if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then
+ sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec"
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5
+$as_echo_n "checking how to hardcode library paths into programs... " >&6; }
+hardcode_action_CXX=
+if test -n "$hardcode_libdir_flag_spec_CXX" ||
+ test -n "$runpath_var_CXX" ||
+ test "X$hardcode_automatic_CXX" = "Xyes" ; then
+
+ # We can hardcode non-existent directories.
+ if test "$hardcode_direct_CXX" != no &&
+ # If the only mechanism to avoid hardcoding is shlibpath_var, we
+ # have to relink, otherwise we might link with an installed library
+ # when we should be linking with a yet-to-be-installed one
+ ## test "$_LT_TAGVAR(hardcode_shlibpath_var, CXX)" != no &&
+ test "$hardcode_minus_L_CXX" != no; then
+ # Linking always hardcodes the temporary library directory.
+ hardcode_action_CXX=relink
+ else
+ # We can link without hardcoding, and we can hardcode nonexisting dirs.
+ hardcode_action_CXX=immediate
+ fi
+else
+ # We cannot hardcode anything, or else we can only hardcode existing
+ # directories.
+ hardcode_action_CXX=unsupported
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action_CXX" >&5
+$as_echo "$hardcode_action_CXX" >&6; }
+
+if test "$hardcode_action_CXX" = relink ||
+ test "$inherit_rpath_CXX" = yes; then
+ # Fast installation is not supported
+ enable_fast_install=no
+elif test "$shlibpath_overrides_runpath" = yes ||
+ test "$enable_shared" = no; then
+ # Fast installation is not necessary
+ enable_fast_install=needless
+fi
+
+
+
+
+
+
+
+ fi # test -n "$compiler"
+
+ CC=$lt_save_CC
+ CFLAGS=$lt_save_CFLAGS
+ LDCXX=$LD
+ LD=$lt_save_LD
+ GCC=$lt_save_GCC
+ with_gnu_ld=$lt_save_with_gnu_ld
+ lt_cv_path_LDCXX=$lt_cv_path_LD
+ lt_cv_path_LD=$lt_save_path_LD
+ lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld
+ lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld
+fi # test "$_lt_caught_CXX_error" != yes
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ac_config_commands="$ac_config_commands libtool"
+
+
+
+
+# Only expand once:
+
+
+# Check whether --enable-experimental-libtool was given.
+if test "${enable_experimental_libtool+set}" = set; then :
+ enableval=$enable_experimental_libtool; experimental_libtool=$enableval
+else
+ experimental_libtool=no
+fi
+
+
+if test "$experimental_libtool" = "yes"; then
+ echo "using APR's libtool"
+ sh_libtool="`$apr_config --apr-libtool`"
+ LIBTOOL="$sh_libtool"
+ SVN_LIBTOOL="$sh_libtool"
+else
+ sh_libtool="$abs_builddir/libtool"
+ SVN_LIBTOOL="\$(SHELL) $sh_libtool"
+fi
+
+
+lt_pversion=`$LIBTOOL --version 2>/dev/null|$SED -e 's/([^)]*)//g;s/^[^0-9]*//;s/[- ].*//g;q'`
+lt_version=`echo $lt_pversion|$SED -e 's/\([a-z]*\)$/.\1/'`
+lt_major_version=`echo $lt_version | cut -d'.' -f 1`
+
+svn_enable_static=yes
+svn_enable_shared=yes
+
+# Check whether --enable-static was given.
+if test "${enable_static+set}" = set; then :
+ enableval=$enable_static; svn_enable_static="$enableval"
+else
+ svn_enable_static="yes"
+fi
+
+
+# Check whether --enable-shared was given.
+if test "${enable_shared+set}" = set; then :
+ enableval=$enable_shared; svn_enable_shared="$enableval"
+else
+ svn_enable_shared="yes"
+fi
+
+
+if test "$svn_enable_static" = "yes" && test "$svn_enable_shared" = "yes" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: building both shared and static libraries" >&5
+$as_echo "$as_me: building both shared and static libraries" >&6;}
+elif test "$svn_enable_static" = "yes" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: building static libraries only" >&5
+$as_echo "$as_me: building static libraries only" >&6;}
+ LT_CFLAGS="-static $LT_CFLAGS"
+ LT_LDFLAGS="-static $LT_LDFLAGS"
+elif test "$svn_enable_shared" = "yes" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: building shared libraries only" >&5
+$as_echo "$as_me: building shared libraries only" >&6;}
+ if test "$lt_major_version" = "1" ; then
+ LT_CFLAGS="-prefer-pic $LT_CFLAGS"
+ elif test "$lt_major_version" = "2" ; then
+ LT_CFLAGS="-shared $LT_CFLAGS"
+ fi
+ LT_LDFLAGS="-shared $LT_LDFLAGS"
+else
+ as_fn_error $? "cannot disable both shared and static libraries" "$LINENO" 5
+fi
+
+# Check whether --enable-all-static was given.
+if test "${enable_all_static+set}" = set; then :
+ enableval=$enable_all_static;
+ if test "$enableval" = "yes" ; then
+ LT_LDFLAGS="-all-static $LT_LDFLAGS"
+ elif test "$enableval" != "no" ; then
+ as_fn_error $? "--enable-all-static doesn't accept argument" "$LINENO" 5
+ fi
+
+fi
+
+
+
+
+
+# Check whether --enable-local-library-preloading was given.
+if test "${enable_local_library_preloading+set}" = set; then :
+ enableval=$enable_local_library_preloading;
+ if test "$enableval" != "no"; then
+ if test "$svn_enable_shared" = "yes"; then
+ TRANSFORM_LIBTOOL_SCRIPTS="transform-libtool-scripts"
+ else
+ as_fn_error $? "--enable-local-library-preloading conflicts with --disable-shared" "$LINENO" 5
+ fi
+ else
+ TRANSFORM_LIBTOOL_SCRIPTS=""
+ fi
+
+else
+
+ TRANSFORM_LIBTOOL_SCRIPTS=""
+
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether libtool needs -no-undefined" >&5
+$as_echo_n "checking whether libtool needs -no-undefined... " >&6; }
+case $host in
+ *-*-cygwin*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ LT_NO_UNDEFINED="-no-undefined"
+ ;;
+ *)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ LT_NO_UNDEFINED=""
+ ;;
+esac
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to avoid circular linkage at all costs" >&5
+$as_echo_n "checking whether to avoid circular linkage at all costs... " >&6; }
+case $host in
+ *-*-cygwin*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+$as_echo "#define SVN_AVOID_CIRCULAR_LINKAGE_AT_ALL_COSTS_HACK 1" >>confdefs.h
+
+ ;;
+ *)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ ;;
+esac
+
+trang=yes
+
+# Check whether --with-trang was given.
+if test "${with_trang+set}" = set; then :
+ withval=$with_trang;
+ trang="$withval"
+
+fi
+
+if test "$trang" = "yes"; then
+ # Extract the first word of "trang", so it can be a program name with args.
+set dummy trang; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_TRANG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $TRANG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_TRANG="$TRANG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_TRANG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_TRANG" && ac_cv_path_TRANG="none"
+ ;;
+esac
+fi
+TRANG=$ac_cv_path_TRANG
+if test -n "$TRANG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TRANG" >&5
+$as_echo "$TRANG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+else
+ TRANG="$trang"
+
+fi
+
+doxygen=yes
+
+# Check whether --with-doxygen was given.
+if test "${with_doxygen+set}" = set; then :
+ withval=$with_doxygen;
+ doxygen="$withval"
+
+fi
+
+if test "$doxygen" = "yes"; then
+ # Extract the first word of "doxygen", so it can be a program name with args.
+set dummy doxygen; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_DOXYGEN+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $DOXYGEN in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_DOXYGEN="$DOXYGEN" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_DOXYGEN="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_DOXYGEN" && ac_cv_path_DOXYGEN="none"
+ ;;
+esac
+fi
+DOXYGEN=$ac_cv_path_DOXYGEN
+if test -n "$DOXYGEN"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DOXYGEN" >&5
+$as_echo "$DOXYGEN" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+else
+ DOXYGEN="$doxygen"
+
+fi
+
+
+
+
+
+# Check whether --with-expat was given.
+if test "${with_expat+set}" = set; then :
+ withval=$with_expat; svn_lib_expat="$withval"
+else
+ svn_lib_expat="::expat"
+fi
+
+
+# APR-util accepts "builtin" as an argument to this option so if the user
+# passed "builtin" pretend the user didn't specify the --with-expat option
+# at all. Expat will (hopefully) be found in apr-util.
+test "_$svn_lib_expat" = "_builtin" && svn_lib_expat="::expat"
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Expat" >&5
+$as_echo_n "checking for Expat... " >&6; }
+if test -n "`echo "$svn_lib_expat" | $EGREP ":.*:"`"; then
+ SVN_XML_INCLUDES=""
+ for i in `echo "$svn_lib_expat" | $SED -e "s/\([^:]*\):.*/\1/"`; do
+ SVN_XML_INCLUDES="$SVN_XML_INCLUDES -I$i"
+ done
+ SVN_XML_INCLUDES="${SVN_XML_INCLUDES## }"
+ for l in `echo "$svn_lib_expat" | $SED -e "s/.*:\([^:]*\):.*/\1/"`; do
+ LDFLAGS="$LDFLAGS -L$l"
+ done
+ for l in `echo "$svn_lib_expat" | $SED -e "s/.*:\([^:]*\)/\1/"`; do
+ SVN_XML_LIBS="$SVN_XML_LIBS -l$l"
+ done
+ SVN_XML_LIBS="${SVN_XML_LIBS## }"
+ old_CPPFLAGS="$CPPFLAGS"
+ old_LIBS="$LIBS"
+ CPPFLAGS="$CPPFLAGS $SVN_XML_INCLUDES"
+ LIBS="$LIBS $SVN_XML_LIBS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <expat.h>
+int main()
+{XML_ParserCreate(NULL);}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ svn_lib_expat="yes"
+else
+ svn_lib_expat="no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ LIBS="$old_LIBS"
+ if test "$svn_lib_expat" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ else
+ SVN_XML_INCLUDES=""
+ SVN_XML_LIBS=""
+ CPPFLAGS="$CPPFLAGS $SVN_APRUTIL_INCLUDES"
+ if test "$enable_all_static" != "yes"; then
+ SVN_APRUTIL_LIBS="$SVN_APRUTIL_LIBS `$apu_config --libs`"
+ fi
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <expat.h>
+int main()
+{XML_ParserCreate(NULL);}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ svn_lib_expat="yes"
+else
+ svn_lib_expat="no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ if test "$svn_lib_expat" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Expat found amongst libraries used by APR-Util, but Subversion libraries might be needlessly linked against additional unused libraries. It can be avoided by specifying exact location of Expat in argument of --with-expat option." >&5
+$as_echo "$as_me: WARNING: Expat found amongst libraries used by APR-Util, but Subversion libraries might be needlessly linked against additional unused libraries. It can be avoided by specifying exact location of Expat in argument of --with-expat option." >&2;}
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ as_fn_error $? "Expat not found" "$LINENO" 5
+ fi
+ fi
+ CPPFLAGS="$old_CPPFLAGS"
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ if test "$svn_lib_expat" = "yes"; then
+ as_fn_error $? "--with-expat option requires argument" "$LINENO" 5
+ elif test "$svn_lib_expat" = "no"; then
+ as_fn_error $? "Expat is required" "$LINENO" 5
+ else
+ as_fn_error $? "Invalid syntax of argument of --with-expat option" "$LINENO" 5
+ fi
+fi
+
+
+
+
+# Berkeley DB on SCO OpenServer needs -lsocket
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for socket in -lsocket" >&5
+$as_echo_n "checking for socket in -lsocket... " >&6; }
+if ${ac_cv_lib_socket_socket+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsocket $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char socket ();
+int
+main ()
+{
+return socket ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_socket_socket=yes
+else
+ ac_cv_lib_socket_socket=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_socket" >&5
+$as_echo "$ac_cv_lib_socket_socket" >&6; }
+if test "x$ac_cv_lib_socket_socket" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSOCKET 1
+_ACEOF
+
+ LIBS="-lsocket $LIBS"
+
+fi
+
+
+# Build the BDB filesystem library only if we have an appropriate
+# version of Berkeley DB.
+case "$host" in
+powerpc-apple-darwin*)
+ # Berkeley DB 4.0 does not work on OS X.
+ SVN_FS_WANT_DB_MAJOR=4
+ SVN_FS_WANT_DB_MINOR=1
+ SVN_FS_WANT_DB_PATCH=25
+ ;;
+*)
+ SVN_FS_WANT_DB_MAJOR=4
+ SVN_FS_WANT_DB_MINOR=0
+ SVN_FS_WANT_DB_PATCH=14
+ ;;
+esac
+# Look for libdb4.so first:
+
+ db_version=$SVN_FS_WANT_DB_MAJOR.$SVN_FS_WANT_DB_MINOR.$SVN_FS_WANT_DB_PATCH
+
+
+# Check whether --with-berkeley-db was given.
+if test "${with_berkeley_db+set}" = set; then :
+ withval=$with_berkeley_db;
+ if test "$withval" = "no"; then
+ bdb_status=skip
+ elif test "$withval" = "yes"; then
+ apu_db_version="`$apu_config --db-version`"
+ if test $? -ne 0; then
+ as_fn_error $? "Can't determine whether apr-util is linked against a
+ proper version of Berkeley DB." "$LINENO" 5
+ fi
+
+ if test "$withval" = "yes"; then
+ if test "$apu_db_version" -lt "4"; then
+ as_fn_error $? "APR-UTIL was linked against Berkeley DB version $apu_db_version,
+ while version 4 or higher is required. Reinstall
+ APR-UTIL with the appropriate options." "$LINENO" 5
+ fi
+
+ bdb_status=required
+
+ elif test "$apu_found" != "reconfig"; then
+ if test "$apu_db_version" -lt 4; then
+ as_fn_error $? "APR-UTIL was installed independently, it won't be
+ possible to use the specified Berkeley DB: $withval" "$LINENO" 5
+ fi
+
+ bdb_status=required
+ fi
+ else
+ if echo "$withval" | $EGREP ":.*:.*:" > /dev/null; then
+ svn_berkeley_db_header="`echo "$withval" | $SED -e "s/\([^:]*\):.*/\1/"`"
+ SVN_DB_INCLUDES=""
+ for i in `echo "$withval" | $SED -e "s/.*:\([^:]*\):[^:]*:.*/\1/"`; do
+ SVN_DB_INCLUDES="$SVN_DB_INCLUDES -I$i"
+ done
+ SVN_DB_INCLUDES="${SVN_DB_INCLUDES## }"
+ for l in `echo "$withval" | $SED -e "s/.*:[^:]*:\([^:]*\):.*/\1/"`; do
+ LDFLAGS="$LDFLAGS -L$l"
+ done
+ SVN_DB_LIBS=""
+ for l in `echo "$withval" | $SED -e "s/.*:\([^:]*\)/\1/"`; do
+ SVN_DB_LIBS="$SVN_DB_LIBS -l$l"
+ done
+ SVN_DB_LIBS="${SVN_DB_LIBS## }"
+
+ bdb_status=required
+ else
+ as_fn_error $? "Invalid syntax of argument of --with-berkeley-db option" "$LINENO" 5
+ fi
+ fi
+
+else
+
+ # No --with-berkeley-db option:
+ #
+ # Check if APR-UTIL is providing the correct Berkeley DB version
+ # for us.
+ #
+ apu_db_version="`$apu_config --db-version`"
+ if test $? -ne 0; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Detected older version of APR-UTIL, trying to determine
+ whether apr-util is linked against Berkeley DB
+ $db_version" >&5
+$as_echo "$as_me: WARNING: Detected older version of APR-UTIL, trying to determine
+ whether apr-util is linked against Berkeley DB
+ $db_version" >&2;}
+ bdb_status=try-link
+ elif test "$apu_db_version" -lt "4"; then
+ bdb_status=skip
+ else
+ bdb_status=try-link
+ fi
+
+fi
+
+
+ if test "$bdb_status" = "skip"; then
+ svn_lib_berkeley_db=no
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for availability of Berkeley DB" >&5
+$as_echo_n "checking for availability of Berkeley DB... " >&6; }
+
+ svn_lib_berkeley_db_try_save_cppflags="$CPPFLAGS"
+ svn_lib_berkeley_db_try_save_libs="$LIBS"
+
+ svn_check_berkeley_db_major=$SVN_FS_WANT_DB_MAJOR
+ svn_check_berkeley_db_minor=$SVN_FS_WANT_DB_MINOR
+ svn_check_berkeley_db_patch=$SVN_FS_WANT_DB_PATCH
+
+ if test -z "$SVN_DB_LIBS"; then
+ # We pass --dbm-libs here since Debian has modified apu-config not
+ # to return -ldb unless --dbm-libs is passed. This may also produce
+ # extra output beyond -ldb but since we're only filtering for -ldb
+ # it won't matter to us. However, --dbm-libs was added to apu-config
+ # in 1.3.8 so it's possible the version we have doesn't support it
+ # so fallback without it if we get an error.
+ svn_db_libs_prefiltered="`$apu_config --libs --dbm-libs`"
+ if test $? -ne 0; then
+ svn_db_libs_prefiltered="`$apu_config --libs`"
+ fi
+
+ # Extract only the -ldb.* flag from the libs supplied by apu-config
+ # Otherwise we get bit by the fact that expat might not be built yet
+ # Or that it resides in a non-standard location which we would have
+ # to compensate with using something like -R`$apu_config --prefix`/lib.
+ #
+ SVN_DB_LIBS="`echo \"$svn_db_libs_prefiltered\" | $SED -e 's/.*\(-ldb[^[:space:]]*\).*/\1/' | $EGREP -- '-ldb[^[:space:]]*'`"
+ fi
+
+ CPPFLAGS="$SVN_DB_INCLUDES $SVN_APRUTIL_INCLUDES $CPPFLAGS"
+ LIBS="`$apu_config --ldflags` $SVN_DB_LIBS $LIBS"
+
+ if test -n "$svn_berkeley_db_header"; then
+ SVN_DB_HEADER="#include <$svn_berkeley_db_header>"
+ svn_db_header="#include <$svn_berkeley_db_header>"
+ else
+ SVN_DB_HEADER="#include <apu_want.h>"
+ svn_db_header="#define APU_WANT_DB
+#include <apu_want.h>"
+ fi
+
+
+
+ if test "$cross_compiling" = yes; then :
+ svn_have_berkeley_db=yes
+
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <stdlib.h>
+$svn_db_header
+
+int main ()
+{
+ int major, minor, patch;
+
+ db_version (&major, &minor, &patch);
+
+ /* Sanity check: ensure that db.h constants actually match the db library */
+ if (major != DB_VERSION_MAJOR
+ || minor != DB_VERSION_MINOR
+ || patch != DB_VERSION_PATCH)
+ exit (1);
+
+ /* Run-time check: ensure the library claims to be the correct version. */
+
+ if (major < $svn_check_berkeley_db_major)
+ exit (1);
+ if (major > $svn_check_berkeley_db_major)
+ exit (0);
+
+ if (minor < $svn_check_berkeley_db_minor)
+ exit (1);
+ if (minor > $svn_check_berkeley_db_minor)
+ exit (0);
+
+ if (patch >= $svn_check_berkeley_db_patch)
+ exit (0);
+ else
+ exit (1);
+}
+
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ svn_have_berkeley_db=yes
+else
+ svn_have_berkeley_db=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+
+ CPPFLAGS="$svn_lib_berkeley_db_try_save_cppflags"
+ LIBS="$svn_lib_berkeley_db_try_save_libs"
+
+
+ if test "$svn_have_berkeley_db" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ svn_lib_berkeley_db=yes
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ svn_lib_berkeley_db=no
+ if test "$bdb_status" = "required"; then
+ as_fn_error $? "Berkeley DB $db_version or newer wasn't found." "$LINENO" 5
+ fi
+ fi
+ fi
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_FS_WANT_DB_MAJOR $SVN_FS_WANT_DB_MAJOR
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_FS_WANT_DB_MINOR $SVN_FS_WANT_DB_MINOR
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_FS_WANT_DB_PATCH $SVN_FS_WANT_DB_PATCH
+_ACEOF
+
+
+
+
+
+
+
+# Check whether --with-sasl was given.
+if test "${with_sasl+set}" = set; then :
+ withval=$with_sasl;
+ with_sasl="$withval"
+ required="yes"
+
+else
+
+ with_sasl="yes"
+ required="no"
+
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to look for SASL" >&5
+$as_echo_n "checking whether to look for SASL... " >&6; }
+
+ if test "${with_sasl}" = "no"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ svn_lib_sasl=no
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ saved_LDFLAGS="$LDFLAGS"
+ saved_CPPFLAGS="$CPPFLAGS"
+
+ if test "$with_sasl" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Looking in default locations" >&5
+$as_echo "$as_me: Looking in default locations" >&6;}
+ ac_fn_c_check_header_mongrel "$LINENO" "sasl/sasl.h" "ac_cv_header_sasl_sasl_h" "$ac_includes_default"
+if test "x$ac_cv_header_sasl_sasl_h" = xyes; then :
+ ac_fn_c_check_header_mongrel "$LINENO" "sasl/saslutil.h" "ac_cv_header_sasl_saslutil_h" "$ac_includes_default"
+if test "x$ac_cv_header_sasl_saslutil_h" = xyes; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for prop_get in -lsasl2" >&5
+$as_echo_n "checking for prop_get in -lsasl2... " >&6; }
+if ${ac_cv_lib_sasl2_prop_get+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsasl2 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char prop_get ();
+int
+main ()
+{
+return prop_get ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_sasl2_prop_get=yes
+else
+ ac_cv_lib_sasl2_prop_get=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sasl2_prop_get" >&5
+$as_echo "$ac_cv_lib_sasl2_prop_get" >&6; }
+if test "x$ac_cv_lib_sasl2_prop_get" = xyes; then :
+ svn_lib_sasl=yes
+else
+ svn_lib_sasl=no
+fi
+
+else
+ svn_lib_sasl=no
+fi
+
+
+else
+ svn_lib_sasl=no
+fi
+
+
+ if test "$svn_lib_sasl" = "no"; then
+ with_sasl="/usr/local"
+ fi
+ else
+ svn_lib_sasl=no
+ fi
+
+ if test "$svn_lib_sasl" = "no"; then
+ SVN_SASL_INCLUDES="-I${with_sasl}/include"
+ CPPFLAGS="$CPPFLAGS $SVN_SASL_INCLUDES"
+ LDFLAGS="$LDFLAGS `
+ input_flags="-L${with_sasl}/lib"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ ac_fn_c_check_header_mongrel "$LINENO" "sasl/sasl.h" "ac_cv_header_sasl_sasl_h" "$ac_includes_default"
+if test "x$ac_cv_header_sasl_sasl_h" = xyes; then :
+ ac_fn_c_check_header_mongrel "$LINENO" "sasl/saslutil.h" "ac_cv_header_sasl_saslutil_h" "$ac_includes_default"
+if test "x$ac_cv_header_sasl_saslutil_h" = xyes; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for prop_get in -lsasl2" >&5
+$as_echo_n "checking for prop_get in -lsasl2... " >&6; }
+if ${ac_cv_lib_sasl2_prop_get+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsasl2 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char prop_get ();
+int
+main ()
+{
+return prop_get ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_sasl2_prop_get=yes
+else
+ ac_cv_lib_sasl2_prop_get=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sasl2_prop_get" >&5
+$as_echo "$ac_cv_lib_sasl2_prop_get" >&6; }
+if test "x$ac_cv_lib_sasl2_prop_get" = xyes; then :
+ svn_lib_sasl=yes
+else
+ svn_lib_sasl=no
+fi
+
+else
+ svn_lib_sasl=no
+fi
+
+
+else
+ svn_lib_sasl=no
+fi
+
+
+ fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for availability of Cyrus SASL v2" >&5
+$as_echo_n "checking for availability of Cyrus SASL v2... " >&6; }
+ if test "$svn_lib_sasl" = "yes"; then
+ SVN_SASL_LIBS="-lsasl2"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+ if test "$required" = "yes"; then
+ as_fn_error $? "Could not find Cyrus SASL v2" "$LINENO" 5
+ fi
+
+ SVN_SASL_INCLUDES=""
+ LDFLAGS="$saved_LDFLAGS"
+ fi
+
+ CPPFLAGS="$saved_CPPFLAGS"
+ fi
+
+
+
+
+
+if test "$svn_lib_sasl" = "yes"; then
+
+$as_echo "#define SVN_HAVE_SASL 1" >>confdefs.h
+
+fi
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Mach-O dynamic module iteration functions" >&5
+$as_echo_n "checking for Mach-O dynamic module iteration functions... " >&6; }
+ if test "$cross_compiling" = yes; then :
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot run test program while cross compiling
+See \`config.log' for more details" "$LINENO" 5; }
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <mach-o/dyld.h>
+ #include <mach-o/loader.h>
+
+int
+main ()
+{
+
+ const struct mach_header *header = _dyld_get_image_header(0);
+ const char *name = _dyld_get_image_name(0);
+ if (name && header) return 0;
+ return 1;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+
+$as_echo "#define SVN_HAVE_MACHO_ITERATE 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Mac OS property list utilities" >&5
+$as_echo_n "checking for Mac OS property list utilities... " >&6; }
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <AvailabilityMacros.h>
+ #if !defined(MAC_OS_X_VERSION_MAX_ALLOWED) \
+ || !defined(MAC_OS_X_VERSION_10_0) \
+ || (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_0)
+ #error ProperyList API unavailable.
+ #endif
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+
+ LIBS="$LIBS -framework CoreFoundation"
+
+$as_echo "#define SVN_HAVE_MACOS_PLIST 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+
+ # Check whether --enable-keychain was given.
+if test "${enable_keychain+set}" = set; then :
+ enableval=$enable_keychain; enable_keychain=$enableval
+else
+ enable_keychain=yes
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Mac OS KeyChain Services" >&5
+$as_echo_n "checking for Mac OS KeyChain Services... " >&6; }
+
+ if test "$enable_keychain" = "yes"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+ #include <AvailabilityMacros.h>
+ #if !defined(MAC_OS_X_VERSION_MAX_ALLOWED) \
+ || !defined(MAC_OS_X_VERSION_10_2) \
+ || (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_2)
+ #error KeyChain API unavailable.
+ #endif
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ LIBS="$LIBS -framework Security"
+ LIBS="$LIBS -framework CoreServices"
+
+$as_echo "#define SVN_HAVE_KEYCHAIN_SERVICES 1" >>confdefs.h
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+else
+
+ enable_keychain=no
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether APR has support for DSOs" >&5
+$as_echo_n "checking whether APR has support for DSOs... " >&6; }
+old_CPPFLAGS="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <apr.h>
+#if !APR_HAS_DSO
+#error
+#endif
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+ APR_HAS_DSO="yes"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ APR_HAS_DSO="no"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+CPPFLAGS="$old_CPPFLAGS"
+
+
+
+if test -n "$PKG_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for D-Bus .pc file" >&5
+$as_echo_n "checking for D-Bus .pc file... " >&6; }
+ if $PKG_CONFIG --exists dbus-1; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ old_CPPFLAGS="$CPPFLAGS"
+ old_LIBS="$LIBS"
+ DBUS_CPPFLAGS="`$PKG_CONFIG --cflags dbus-1`"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking D-Bus version" >&5
+$as_echo_n "checking D-Bus version... " >&6; }
+ DBUS_VERSION="`$PKG_CONFIG --modversion dbus-1`"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DBUS_VERSION" >&5
+$as_echo "$DBUS_VERSION" >&6; }
+ # D-Bus 0.* requires DBUS_API_SUBJECT_TO_CHANGE
+ if test -n "`echo "$DBUS_VERSION" | $EGREP '^0\.[[:digit:]]+'`"; then
+ DBUS_CPPFLAGS="$DBUS_CPPFLAGS -DDBUS_API_SUBJECT_TO_CHANGE"
+ fi
+ DBUS_LIBS="`$PKG_CONFIG --libs dbus-1`"
+ CPPFLAGS="$CPPFLAGS $DBUS_CPPFLAGS"
+ LIBS="$LIBS $DBUS_LIBS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for D-Bus" >&5
+$as_echo_n "checking for D-Bus... " >&6; }
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <dbus/dbus.h>
+int main()
+{dbus_bus_get(DBUS_BUS_SESSION, NULL);}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ HAVE_DBUS="yes"
+else
+ HAVE_DBUS="no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ if test "$HAVE_DBUS" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ fi
+ CPPFLAGS="$old_CPPFLAGS"
+ LIBS="$old_LIBS"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ fi
+fi
+
+
+
+# Check whether --with-gpg_agent was given.
+if test "${with_gpg_agent+set}" = set; then :
+ withval=$with_gpg_agent;
+else
+ with_gpg_agent=yes
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support GPG-Agent" >&5
+$as_echo_n "checking whether to support GPG-Agent... " >&6; }
+if test "$with_gpg_agent" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+$as_echo "#define SVN_HAVE_GPG_AGENT 1" >>confdefs.h
+
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+
+
+# Check whether --with-gnome_keyring was given.
+if test "${with_gnome_keyring+set}" = set; then :
+ withval=$with_gnome_keyring; with_gnome_keyring="$withval"
+else
+ with_gnome_keyring=auto
+fi
+
+
+found_gnome_keyring=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to look for GNOME Keyring" >&5
+$as_echo_n "checking whether to look for GNOME Keyring... " >&6; }
+if test "$with_gnome_keyring" != "no"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ if test "$svn_enable_shared" = "yes"; then
+ if test "$APR_HAS_DSO" = "yes"; then
+ if test -n "$PKG_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GLib and GNOME Keyring .pc files" >&5
+$as_echo_n "checking for GLib and GNOME Keyring .pc files... " >&6; }
+ if $PKG_CONFIG --exists glib-2.0 gnome-keyring-1; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ old_CPPFLAGS="$CPPFLAGS"
+ SVN_GNOME_KEYRING_INCLUDES="`$PKG_CONFIG --cflags glib-2.0 gnome-keyring-1`"
+ CPPFLAGS="$CPPFLAGS $SVN_GNOME_KEYRING_INCLUDES"
+ ac_fn_c_check_header_mongrel "$LINENO" "gnome-keyring.h" "ac_cv_header_gnome_keyring_h" "$ac_includes_default"
+if test "x$ac_cv_header_gnome_keyring_h" = xyes; then :
+ found_gnome_keyring=yes
+else
+ found_gnome_keyring=no
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNOME Keyring" >&5
+$as_echo_n "checking for GNOME Keyring... " >&6; }
+ if test "$found_gnome_keyring" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+$as_echo "#define SVN_HAVE_GNOME_KEYRING 1" >>confdefs.h
+
+ CPPFLAGS="$old_CPPFLAGS"
+ SVN_GNOME_KEYRING_LIBS="`$PKG_CONFIG --libs glib-2.0 gnome-keyring-1`"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ if test "$with_gnome_keyring" = "yes"; then
+ as_fn_error $? "cannot find GNOME Keyring" "$LINENO" 5
+ fi
+ fi
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ if test "$with_gnome_keyring" = "yes"; then
+ as_fn_error $? "cannot find GLib and GNOME Keyring .pc files." "$LINENO" 5
+ else
+ with_gnome_keyring=no
+ fi
+ fi
+ else
+ if test "$with_gnome_keyring" = "yes"; then
+ as_fn_error $? "cannot find pkg-config. GNOME Keyring requires this." "$LINENO" 5
+ else
+ with_gnome_keyring=no
+ fi
+ fi
+ else
+ if test "$with_gnome_keyring" = "yes"; then
+ as_fn_error $? "APR does not have support for DSOs. GNOME Keyring requires this." "$LINENO" 5
+ else
+ with_gnome_keyring=no
+ fi
+ fi
+ else
+ if test "$with_gnome_keyring" = "yes"; then
+ as_fn_error $? "--with-gnome-keyring conflicts with --disable-shared" "$LINENO" 5
+ else
+ with_gnome_keyring=no
+ fi
+ fi
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+
+# Check whether --enable-ev2-impl was given.
+if test "${enable_ev2_impl+set}" = set; then :
+ enableval=$enable_ev2_impl; enable_ev2_impl=$enableval
+else
+ enable_ev2_impl=no
+fi
+
+if test "$enable_ev2_impl" = "yes"; then
+
+$as_echo "#define ENABLE_EV2_IMPL 1" >>confdefs.h
+
+fi
+
+
+
+# Check whether --enable-nls was given.
+if test "${enable_nls+set}" = set; then :
+ enableval=$enable_nls; enable_nls=$enableval
+else
+ enable_nls=yes
+fi
+
+
+USE_NLS="no"
+if test "$enable_nls" = "yes"; then
+ # Extract the first word of "msgfmt", so it can be a program name with args.
+set dummy msgfmt; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_MSGFMT+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $MSGFMT in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_MSGFMT="$MSGFMT" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_MSGFMT="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_MSGFMT" && ac_cv_path_MSGFMT="none"
+ ;;
+esac
+fi
+MSGFMT=$ac_cv_path_MSGFMT
+if test -n "$MSGFMT"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MSGFMT" >&5
+$as_echo "$MSGFMT" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ # Extract the first word of "msgmerge", so it can be a program name with args.
+set dummy msgmerge; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_MSGMERGE+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $MSGMERGE in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_MSGMERGE="$MSGMERGE" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_MSGMERGE="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_MSGMERGE" && ac_cv_path_MSGMERGE="none"
+ ;;
+esac
+fi
+MSGMERGE=$ac_cv_path_MSGMERGE
+if test -n "$MSGMERGE"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MSGMERGE" >&5
+$as_echo "$MSGMERGE" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ # Extract the first word of "xgettext", so it can be a program name with args.
+set dummy xgettext; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_XGETTEXT+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $XGETTEXT in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_XGETTEXT="$XGETTEXT" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_XGETTEXT="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_XGETTEXT" && ac_cv_path_XGETTEXT="none"
+ ;;
+esac
+fi
+XGETTEXT=$ac_cv_path_XGETTEXT
+if test -n "$XGETTEXT"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $XGETTEXT" >&5
+$as_echo "$XGETTEXT" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ if test "$MSGFMT" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing bindtextdomain" >&5
+$as_echo_n "checking for library containing bindtextdomain... " >&6; }
+if ${ac_cv_search_bindtextdomain+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char bindtextdomain ();
+int
+main ()
+{
+return bindtextdomain ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' intl; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_bindtextdomain=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_bindtextdomain+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_bindtextdomain+:} false; then :
+
+else
+ ac_cv_search_bindtextdomain=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_bindtextdomain" >&5
+$as_echo "$ac_cv_search_bindtextdomain" >&6; }
+ac_res=$ac_cv_search_bindtextdomain
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+else
+
+ enable_nls="no"
+
+fi
+
+ if test "$enable_nls" = "no"; then
+ # Destroy the cached result so we can test again
+ unset ac_cv_search_bindtextdomain
+ # On some systems, libintl needs libiconv to link properly,
+ # so try again with -liconv.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing bindtextdomain" >&5
+$as_echo_n "checking for library containing bindtextdomain... " >&6; }
+if ${ac_cv_search_bindtextdomain+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char bindtextdomain ();
+int
+main ()
+{
+return bindtextdomain ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' intl; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib -liconv $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_bindtextdomain=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_bindtextdomain+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_bindtextdomain+:} false; then :
+
+else
+ ac_cv_search_bindtextdomain=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_bindtextdomain" >&5
+$as_echo "$ac_cv_search_bindtextdomain" >&6; }
+ac_res=$ac_cv_search_bindtextdomain
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+ enable_nls="yes"
+ # This is here so that -liconv ends up in LIBS
+ # if it worked with -liconv.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for libiconv_open in -liconv" >&5
+$as_echo_n "checking for libiconv_open in -liconv... " >&6; }
+if ${ac_cv_lib_iconv_libiconv_open+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-liconv $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char libiconv_open ();
+int
+main ()
+{
+return libiconv_open ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_iconv_libiconv_open=yes
+else
+ ac_cv_lib_iconv_libiconv_open=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_iconv_libiconv_open" >&5
+$as_echo "$ac_cv_lib_iconv_libiconv_open" >&6; }
+if test "x$ac_cv_lib_iconv_libiconv_open" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBICONV 1
+_ACEOF
+
+ LIBS="-liconv $LIBS"
+
+fi
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: bindtextdomain() not found. Disabling NLS." >&5
+$as_echo "$as_me: WARNING: bindtextdomain() not found. Disabling NLS." >&2;}
+ enable_nls="no"
+
+fi
+
+ fi
+ if test "$enable_nls" = "yes"; then
+
+$as_echo "#define ENABLE_NLS 1" >>confdefs.h
+
+ USE_NLS="yes"
+ fi
+ fi
+fi
+
+
+
+GETTEXT_CODESET=\#
+NO_GETTEXT_CODESET=\#
+if test $USE_NLS = "yes"; then
+ for ac_func in bind_textdomain_codeset
+do :
+ ac_fn_c_check_func "$LINENO" "bind_textdomain_codeset" "ac_cv_func_bind_textdomain_codeset"
+if test "x$ac_cv_func_bind_textdomain_codeset" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_BIND_TEXTDOMAIN_CODESET 1
+_ACEOF
+ GETTEXT_CODESET=""
+else
+ NO_GETTEXT_CODESET=""
+fi
+done
+
+fi
+
+
+
+# Check if we are using GNU gettext.
+GNU_GETTEXT=no
+MSGFMTFLAGS=''
+if test $USE_NLS = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we are using GNU gettext" >&5
+$as_echo_n "checking if we are using GNU gettext... " >&6; }
+ if $MSGFMT --version 2>&1 | $EGREP GNU > /dev/null; then
+ GNU_GETTEXT=yes
+ MSGFMTFLAGS='-c'
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GNU_GETTEXT" >&5
+$as_echo "$GNU_GETTEXT" >&6; }
+fi
+
+
+
+libmagic_found=no
+
+
+# Check whether --with-libmagic was given.
+if test "${with_libmagic+set}" = set; then :
+ withval=$with_libmagic;
+ if test "$withval" = "yes" ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "magic.h" "ac_cv_header_magic_h" "$ac_includes_default"
+if test "x$ac_cv_header_magic_h" = xyes; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for magic_open in -lmagic" >&5
+$as_echo_n "checking for magic_open in -lmagic... " >&6; }
+if ${ac_cv_lib_magic_magic_open+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lmagic $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char magic_open ();
+int
+main ()
+{
+return magic_open ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_magic_magic_open=yes
+else
+ ac_cv_lib_magic_magic_open=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_magic_magic_open" >&5
+$as_echo "$ac_cv_lib_magic_magic_open" >&6; }
+if test "x$ac_cv_lib_magic_magic_open" = xyes; then :
+ libmagic_found="builtin"
+fi
+
+
+fi
+
+
+ libmagic_prefix="the default locations"
+ elif test "$withval" != "no"; then
+ libmagic_prefix=$withval
+ save_cppflags="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS -I$libmagic_prefix/include"
+ for ac_header in magic.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "magic.h" "ac_cv_header_magic_h" "$ac_includes_default"
+if test "x$ac_cv_header_magic_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_MAGIC_H 1
+_ACEOF
+
+ save_ldflags="$LDFLAGS"
+ LDFLAGS="-L$libmagic_prefix/lib"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for magic_open in -lmagic" >&5
+$as_echo_n "checking for magic_open in -lmagic... " >&6; }
+if ${ac_cv_lib_magic_magic_open+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lmagic $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char magic_open ();
+int
+main ()
+{
+return magic_open ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_magic_magic_open=yes
+else
+ ac_cv_lib_magic_magic_open=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_magic_magic_open" >&5
+$as_echo "$ac_cv_lib_magic_magic_open" >&6; }
+if test "x$ac_cv_lib_magic_magic_open" = xyes; then :
+ libmagic_found="yes"
+fi
+
+ LDFLAGS="$save_ldflags"
+
+fi
+
+done
+
+ CPPFLAGS="$save_cppflags"
+ fi
+ if test "$withval" != "no" && test "$libmagic_found" = "no"; then
+ as_fn_error $? "--with-libmagic requested, but libmagic not found at $libmagic_prefix" "$LINENO" 5
+ fi
+
+else
+
+ ac_fn_c_check_header_mongrel "$LINENO" "magic.h" "ac_cv_header_magic_h" "$ac_includes_default"
+if test "x$ac_cv_header_magic_h" = xyes; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for magic_open in -lmagic" >&5
+$as_echo_n "checking for magic_open in -lmagic... " >&6; }
+if ${ac_cv_lib_magic_magic_open+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lmagic $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char magic_open ();
+int
+main ()
+{
+return magic_open ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_magic_magic_open=yes
+else
+ ac_cv_lib_magic_magic_open=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_magic_magic_open" >&5
+$as_echo "$ac_cv_lib_magic_magic_open" >&6; }
+if test "x$ac_cv_lib_magic_magic_open" = xyes; then :
+ libmagic_found="builtin"
+fi
+
+
+fi
+
+
+
+fi
+
+
+if test "$libmagic_found" != "no"; then
+
+$as_echo "#define SVN_HAVE_LIBMAGIC 1" >>confdefs.h
+
+ SVN_MAGIC_LIBS="-lmagic"
+fi
+
+if test "$libmagic_found" = "yes"; then
+ SVN_MAGIC_INCLUDES="-I$libmagic_prefix/include"
+ LDFLAGS="$LDFLAGS `
+ input_flags="-L$libmagic_prefix/lib"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+fi
+
+
+
+
+
+
+# Check whether --with-kwallet was given.
+if test "${with_kwallet+set}" = set; then :
+ withval=$with_kwallet; svn_lib_kwallet="$withval"
+else
+ svn_lib_kwallet=no
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to look for KWallet" >&5
+$as_echo_n "checking whether to look for KWallet... " >&6; }
+ if test "$svn_lib_kwallet" != "no"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ if test "$svn_enable_shared" = "yes"; then
+ if test "$APR_HAS_DSO" = "yes"; then
+ if test -n "$PKG_CONFIG"; then
+ if test "$HAVE_DBUS" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for QtCore, QtDBus, QtGui" >&5
+$as_echo_n "checking for QtCore, QtDBus, QtGui... " >&6; }
+ if $PKG_CONFIG --exists QtCore QtDBus QtGui; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ if test "$svn_lib_kwallet" != "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for kde4-config" >&5
+$as_echo_n "checking for kde4-config... " >&6; }
+ KDE4_CONFIG="$svn_lib_kwallet/bin/kde4-config"
+ if test -f "$KDE4_CONFIG" && test -x "$KDE4_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ else
+ KDE4_CONFIG=""
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ fi
+ else
+ # Extract the first word of "kde4-config", so it can be a program name with args.
+set dummy kde4-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_KDE4_CONFIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $KDE4_CONFIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_KDE4_CONFIG="$KDE4_CONFIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_KDE4_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+KDE4_CONFIG=$ac_cv_path_KDE4_CONFIG
+if test -n "$KDE4_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $KDE4_CONFIG" >&5
+$as_echo "$KDE4_CONFIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ fi
+ if test -n "$KDE4_CONFIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for KWallet" >&5
+$as_echo_n "checking for KWallet... " >&6; }
+ old_CXXFLAGS="$CXXFLAGS"
+ old_LDFLAGS="$LDFLAGS"
+ old_LIBS="$LIBS"
+ for d in `$PKG_CONFIG --cflags QtCore QtDBus QtGui`; do
+ if test -n "`echo "$d" | $EGREP -- '^-D[^[:space:]]*'`"; then
+ CPPFLAGS="$CPPFLAGS $d"
+ fi
+ done
+ qt_include_dirs="`$PKG_CONFIG --cflags-only-I QtCore QtDBus QtGui`"
+ kde_dir="`$KDE4_CONFIG --prefix`"
+ SVN_KWALLET_INCLUDES="$DBUS_CPPFLAGS $qt_include_dirs -I$kde_dir/include"
+ qt_libs_other_options="`$PKG_CONFIG --libs-only-other QtCore QtDBus QtGui`"
+ SVN_KWALLET_LIBS="$DBUS_LIBS -lQtCore -lQtDBus -lQtGui -lkdecore -lkdeui $qt_libs_other_options"
+ CXXFLAGS="$CXXFLAGS $SVN_KWALLET_INCLUDES"
+ LIBS="$LIBS $SVN_KWALLET_LIBS"
+ qt_lib_dirs="`$PKG_CONFIG --libs-only-L QtCore QtDBus QtGui`"
+ kde_lib_suffix="`$KDE4_CONFIG --libsuffix`"
+ LDFLAGS="$old_LDFLAGS `
+ input_flags="$qt_lib_dirs -L$kde_dir/lib$kde_lib_suffix"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <kwallet.h>
+int main()
+{KWallet::Wallet::walletList();}
+_ACEOF
+if ac_fn_cxx_try_link "$LINENO"; then :
+ svn_lib_kwallet="yes"
+else
+ svn_lib_kwallet="no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ if test "$svn_lib_kwallet" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ CXXFLAGS="$old_CXXFLAGS"
+ LIBS="$old_LIBS"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ as_fn_error $? "cannot find KWallet" "$LINENO" 5
+ fi
+ else
+ as_fn_error $? "cannot find kde4-config" "$LINENO" 5
+ fi
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ as_fn_error $? "cannot find QtCore, QtDBus, QtGui" "$LINENO" 5
+ fi
+ else
+ as_fn_error $? "cannot find D-Bus" "$LINENO" 5
+ fi
+ else
+ as_fn_error $? "cannot find pkg-config" "$LINENO" 5
+ fi
+ else
+ as_fn_error $? "APR does not have support for DSOs" "$LINENO" 5
+ fi
+ else
+ as_fn_error $? "--with-kwallet conflicts with --disable-shared" "$LINENO" 5
+ fi
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ fi
+
+
+
+
+if test "$svn_lib_kwallet" = "yes"; then
+
+$as_echo "#define SVN_HAVE_KWALLET 1" >>confdefs.h
+
+fi
+
+# Check whether --enable-plaintext-password-storage was given.
+if test "${enable_plaintext_password_storage+set}" = set; then :
+ enableval=$enable_plaintext_password_storage;
+ if test "$enableval" = "no"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling plaintext password/passphrase storage" >&5
+$as_echo "$as_me: Disabling plaintext password/passphrase storage" >&6;}
+
+$as_echo "#define SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE 1" >>confdefs.h
+
+ fi
+
+fi
+
+
+
+INSTALL_STATIC_RULES="install-bin install-docs"
+INSTALL_RULES="install-fsmod-lib install-ramod-lib install-lib install-include install-static"
+INSTALL_RULES="$INSTALL_RULES $INSTALL_APACHE_RULE"
+BUILD_RULES="fsmod-lib ramod-lib lib bin test sub-test $BUILD_APACHE_RULE tools"
+
+if test "$svn_lib_berkeley_db" = "yes"; then
+ BUILD_RULES="$BUILD_RULES bdb-lib bdb-test"
+ INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-fsmod-lib/install-fsmod-lib install-bdb-lib/'`"
+ INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-bdb-lib"
+ BDB_TEST_DEPS="\$(BDB_TEST_DEPS)"
+ BDB_TEST_PROGRAMS="\$(BDB_TEST_PROGRAMS)"
+fi
+
+if test "$svn_lib_serf" = "yes"; then
+ BUILD_RULES="$BUILD_RULES serf-lib"
+ INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-ramod-lib/install-ramod-lib install-serf-lib/'`"
+ INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-serf-lib"
+fi
+
+if test "$svn_lib_kwallet" = "yes"; then
+ BUILD_RULES="$BUILD_RULES kwallet-lib"
+ INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-lib/install-lib install-kwallet-lib/'`"
+ INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-kwallet-lib"
+fi
+
+if test "$found_gnome_keyring" = "yes"; then
+ BUILD_RULES="$BUILD_RULES gnome-keyring-lib"
+ INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-lib/install-lib install-gnome-keyring-lib/'`"
+ INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-gnome-keyring-lib"
+fi
+
+if test "$USE_NLS" = "yes"; then
+ BUILD_RULES="$BUILD_RULES locale"
+ INSTALL_RULES="$INSTALL_RULES install-locale"
+fi
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
+$as_echo_n "checking for ANSI C header files... " >&6; }
+if ${ac_cv_header_stdc+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_header_stdc=yes
+else
+ ac_cv_header_stdc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+ # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "memchr" >/dev/null 2>&1; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "free" >/dev/null 2>&1; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+ if test "$cross_compiling" = yes; then :
+ :
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <ctype.h>
+#include <stdlib.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+ (('a' <= (c) && (c) <= 'i') \
+ || ('j' <= (c) && (c) <= 'r') \
+ || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ if (XOR (islower (i), ISLOWER (i))
+ || toupper (i) != TOUPPER (i))
+ return 2;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+ ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
+$as_echo "$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+$as_echo "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for an ANSI C-conforming const" >&5
+$as_echo_n "checking for an ANSI C-conforming const... " >&6; }
+if ${ac_cv_c_const+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+#ifndef __cplusplus
+ /* Ultrix mips cc rejects this sort of thing. */
+ typedef int charset[2];
+ const charset cs = { 0, 0 };
+ /* SunOS 4.1.1 cc rejects this. */
+ char const *const *pcpcc;
+ char **ppc;
+ /* NEC SVR4.0.2 mips cc rejects this. */
+ struct point {int x, y;};
+ static struct point const zero = {0,0};
+ /* AIX XL C 1.02.0.0 rejects this.
+ It does not let you subtract one const X* pointer from another in
+ an arm of an if-expression whose if-part is not a constant
+ expression */
+ const char *g = "string";
+ pcpcc = &g + (g ? g-g : 0);
+ /* HPUX 7.0 cc rejects these. */
+ ++pcpcc;
+ ppc = (char**) pcpcc;
+ pcpcc = (char const *const *) ppc;
+ { /* SCO 3.2v4 cc rejects this sort of thing. */
+ char tx;
+ char *t = &tx;
+ char const *s = 0 ? (char *) 0 : (char const *) 0;
+
+ *t++ = 0;
+ if (s) return 0;
+ }
+ { /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */
+ int x[] = {25, 17};
+ const int *foo = &x[0];
+ ++foo;
+ }
+ { /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */
+ typedef const int *iptr;
+ iptr p = 0;
+ ++p;
+ }
+ { /* AIX XL C 1.02.0.0 rejects this sort of thing, saying
+ "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */
+ struct s { int j; const int *ap[3]; } bx;
+ struct s *b = &bx; b->j = 5;
+ }
+ { /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */
+ const int foo = 10;
+ if (!foo) return 0;
+ }
+ return !cs[0] && !zero.x;
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ ac_cv_c_const=yes
+else
+ ac_cv_c_const=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_const" >&5
+$as_echo "$ac_cv_c_const" >&6; }
+if test $ac_cv_c_const = no; then
+
+$as_echo "#define const /**/" >>confdefs.h
+
+fi
+
+
+ac_fn_c_check_type "$LINENO" "size_t" "ac_cv_type_size_t" "$ac_includes_default"
+if test "x$ac_cv_type_size_t" = xyes; then :
+
+else
+
+cat >>confdefs.h <<_ACEOF
+#define size_t unsigned int
+_ACEOF
+
+fi
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for working memcmp" >&5
+$as_echo_n "checking for working memcmp... " >&6; }
+if ${ac_cv_func_memcmp_working+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test "$cross_compiling" = yes; then :
+ ac_cv_func_memcmp_working=no
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+$ac_includes_default
+int
+main ()
+{
+
+ /* Some versions of memcmp are not 8-bit clean. */
+ char c0 = '\100', c1 = '\200', c2 = '\201';
+ if (memcmp(&c0, &c2, 1) >= 0 || memcmp(&c1, &c2, 1) >= 0)
+ return 1;
+
+ /* The Next x86 OpenStep bug shows up only when comparing 16 bytes
+ or more and with at least one buffer not starting on a 4-byte boundary.
+ William Lewis provided this test program. */
+ {
+ char foo[21];
+ char bar[21];
+ int i;
+ for (i = 0; i < 4; i++)
+ {
+ char *a = foo + i;
+ char *b = bar + i;
+ strcpy (a, "--------01111111");
+ strcpy (b, "--------10000000");
+ if (memcmp (a, b, 16) >= 0)
+ return 1;
+ }
+ return 0;
+ }
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ ac_cv_func_memcmp_working=yes
+else
+ ac_cv_func_memcmp_working=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_memcmp_working" >&5
+$as_echo "$ac_cv_func_memcmp_working" >&6; }
+test $ac_cv_func_memcmp_working = no && case " $LIBOBJS " in
+ *" memcmp.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS memcmp.$ac_objext"
+ ;;
+esac
+
+
+
+for ac_func in vprintf
+do :
+ ac_fn_c_check_func "$LINENO" "vprintf" "ac_cv_func_vprintf"
+if test "x$ac_cv_func_vprintf" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_VPRINTF 1
+_ACEOF
+
+ac_fn_c_check_func "$LINENO" "_doprnt" "ac_cv_func__doprnt"
+if test "x$ac_cv_func__doprnt" = xyes; then :
+
+$as_echo "#define HAVE_DOPRNT 1" >>confdefs.h
+
+fi
+
+fi
+done
+
+
+
+for ac_func in symlink readlink
+do :
+ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
+if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+done
+
+
+for ac_header in sys/utsname.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "sys/utsname.h" "ac_cv_header_sys_utsname_h" "$ac_includes_default"
+if test "x$ac_cv_header_sys_utsname_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_SYS_UTSNAME_H 1
+_ACEOF
+ for ac_func in uname
+do :
+ ac_fn_c_check_func "$LINENO" "uname" "ac_cv_func_uname"
+if test "x$ac_cv_func_uname" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_UNAME 1
+_ACEOF
+
+fi
+done
+
+fi
+
+done
+
+
+ac_fn_c_check_header_mongrel "$LINENO" "termios.h" "ac_cv_header_termios_h" "$ac_includes_default"
+if test "x$ac_cv_header_termios_h" = xyes; then :
+
+ for ac_func in tcgetattr tcsetattr
+do :
+ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
+if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+
+
+$as_echo "#define HAVE_TERMIOS_H 1" >>confdefs.h
+
+
+fi
+done
+
+
+fi
+
+
+
+
+
+# Check whether --with-openssl was given.
+if test "${with_openssl+set}" = set; then :
+ withval=$with_openssl;
+fi
+
+
+# Check whether --enable-debug was given.
+if test "${enable_debug+set}" = set; then :
+ enableval=$enable_debug;
+ if test "$enableval" = "yes" ; then
+ enable_debugging="yes"
+ else
+ enable_debugging="no"
+ fi
+
+else
+
+ # Neither --enable-debug nor --disable-debug was passed.
+ enable_debugging="maybe"
+
+fi
+
+
+# Check whether --enable-optimize was given.
+if test "${enable_optimize+set}" = set; then :
+ enableval=$enable_optimize;
+ if test "$enableval" = "yes" ; then
+ enable_optimization="yes"
+ else
+ enable_optimization="no"
+ fi
+
+else
+
+ # Neither --enable-optimize nor --disable-optimize was passed.
+ enable_optimization="maybe"
+
+fi
+
+
+# Check whether --enable-disallowing-of-undefined-references was given.
+if test "${enable_disallowing_of_undefined_references+set}" = set; then :
+ enableval=$enable_disallowing_of_undefined_references;
+fi
+
+if test "$enable_disallowing_of_undefined_references" != "yes" && test "`uname`" != "Linux"; then
+ enable_disallowing_of_undefined_references="no"
+fi
+if test "$enable_disallowing_of_undefined_references" != "no"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -Wl,--no-undefined" >&5
+$as_echo_n "checking for -Wl,--no-undefined... " >&6; }
+ old_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS -Wl,--no-undefined"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+int main(){;}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ svn_wl_no_undefined="yes"
+else
+ svn_wl_no_undefined="no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ LDFLAGS="$old_LDFLAGS"
+ if test "$svn_wl_no_undefined" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ for library_dir in "$abs_srcdir/subversion/libsvn_"*; do
+ eval "`basename $library_dir`_LDFLAGS=-Wl,--no-undefined"
+ done
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ if test "$enable_disallowing_of_undefined_references" = "yes"; then
+ as_fn_error $? "--enable-disallowing-of-undefined-references explicitly requested, but -Wl,--no-undefined not supported" "$LINENO" 5
+ fi
+ fi
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Check whether --enable-maintainer-mode was given.
+if test "${enable_maintainer_mode+set}" = set; then :
+ enableval=$enable_maintainer_mode;
+ if test "$enableval" = "yes" ; then
+ if test "$enable_debugging" = "no" ; then
+ as_fn_error $? "Can't have --disable-debug and --enable-maintainer-mode" "$LINENO" 5
+ fi
+ enable_debugging=yes
+
+ if test "$GCC" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: maintainer-mode: adding GCC warning flags" >&5
+$as_echo "$as_me: maintainer-mode: adding GCC warning flags" >&6;}
+
+
+ CFLAGS_KEEP="$CFLAGS"
+ CFLAGS=""
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Werror=implicit-function-declaration" >&5
+$as_echo_n "checking if $CC accepts -Werror=implicit-function-declaration... " >&6; }
+ CFLAGS="-Werror=implicit-function-declaration $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Werror=declaration-after-statement" >&5
+$as_echo_n "checking if $CC accepts -Werror=declaration-after-statement... " >&6; }
+ CFLAGS="-Werror=declaration-after-statement $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wextra-tokens" >&5
+$as_echo_n "checking if $CC accepts -Wextra-tokens... " >&6; }
+ CFLAGS="-Wextra-tokens $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wnewline-eof" >&5
+$as_echo_n "checking if $CC accepts -Wnewline-eof... " >&6; }
+ CFLAGS="-Wnewline-eof $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wshorten-64-to-32" >&5
+$as_echo_n "checking if $CC accepts -Wshorten-64-to-32... " >&6; }
+ CFLAGS="-Wshorten-64-to-32 $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wold-style-definition" >&5
+$as_echo_n "checking if $CC accepts -Wold-style-definition... " >&6; }
+ CFLAGS="-Wold-style-definition $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wno-system-headers" >&5
+$as_echo_n "checking if $CC accepts -Wno-system-headers... " >&6; }
+ CFLAGS="-Wno-system-headers $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wno-format-nonliteral" >&5
+$as_echo_n "checking if $CC accepts -Wno-format-nonliteral... " >&6; }
+ CFLAGS="-Wno-format-nonliteral $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ CMAINTAINERFLAGS="$CFLAGS $CMAINTAINERFLAGS"
+ CFLAGS="$CFLAGS_KEEP"
+
+ CMAINTAINERFLAGS="-Wall -Wpointer-arith -Wwrite-strings -Wshadow -Wformat=2 -Wunused -Waggregate-return -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wno-multichar -Wredundant-decls -Wnested-externs -Winline -Wno-long-long $CMAINTAINERFLAGS"
+ fi
+ if test "$GXX" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: maintainer-mode: adding G++ warning flags" >&5
+$as_echo "$as_me: maintainer-mode: adding G++ warning flags" >&6;}
+
+ CXXFLAGS_KEEP="$CXXFLAGS"
+ CXXFLAGS=""
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -Wextra-tokens" >&5
+$as_echo_n "checking if $CXX accepts -Wextra-tokens... " >&6; }
+ CXXFLAGS="-Wextra-tokens $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -Wnewline-eof" >&5
+$as_echo_n "checking if $CXX accepts -Wnewline-eof... " >&6; }
+ CXXFLAGS="-Wnewline-eof $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -Wshorten-64-to-32" >&5
+$as_echo_n "checking if $CXX accepts -Wshorten-64-to-32... " >&6; }
+ CXXFLAGS="-Wshorten-64-to-32 $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -Wno-system-headers" >&5
+$as_echo_n "checking if $CXX accepts -Wno-system-headers... " >&6; }
+ CXXFLAGS="-Wno-system-headers $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ CXXMAINTAINERFLAGS="$CXXFLAGS $CXXMAINTAINERFLAGS"
+ CXXFLAGS="$CXXFLAGS_KEEP"
+
+ CXXMAINTAINERFLAGS="-Wall -Wpointer-arith -Wwrite-strings -Wshadow -Wunused -Wunreachable-code $CXXMAINTAINERFLAGS"
+ fi
+ fi
+
+fi
+
+
+if test "$enable_debugging" = "yes" ; then
+ if test "$enable_optimization" != "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling optimizations for debugging" >&5
+$as_echo "$as_me: Disabling optimizations for debugging" >&6;}
+ CFLAGS="`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"
+ CXXFLAGS="`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"
+ fi
+ if test -z "`echo $CUSERFLAGS' ' | $EGREP -- '-g[0-9]? '`"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling debugging for C" >&5
+$as_echo "$as_me: Enabling debugging for C" >&6;}
+ CFLAGS="`echo $CFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -fno-inline" >&5
+$as_echo_n "checking if $CC accepts -fno-inline... " >&6; }
+ CFLAGS="-fno-inline $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -fno-omit-frame-pointer" >&5
+$as_echo_n "checking if $CC accepts -fno-omit-frame-pointer... " >&6; }
+ CFLAGS="-fno-omit-frame-pointer $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -g3" >&5
+$as_echo_n "checking if $CC accepts -g3... " >&6; }
+ CFLAGS="-g3 $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -g2" >&5
+$as_echo_n "checking if $CC accepts -g2... " >&6; }
+ CFLAGS="-g2 $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -g" >&5
+$as_echo_n "checking if $CC accepts -g... " >&6; }
+ CFLAGS="-g $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ fi
+ if test -z "`echo $CXXUSERFLAGS' ' | $EGREP -- '-g[0-9]? '`"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling debugging for C++" >&5
+$as_echo "$as_me: Enabling debugging for C++" >&6;}
+ CXXFLAGS="`echo $CXXFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -fno-inline" >&5
+$as_echo_n "checking if $CXX accepts -fno-inline... " >&6; }
+ CXXFLAGS="-fno-inline $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -fno-omit-frame-pointer" >&5
+$as_echo_n "checking if $CXX accepts -fno-omit-frame-pointer... " >&6; }
+ CXXFLAGS="-fno-omit-frame-pointer $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -g3" >&5
+$as_echo_n "checking if $CXX accepts -g3... " >&6; }
+ CXXFLAGS="-g3 $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -g2" >&5
+$as_echo_n "checking if $CXX accepts -g2... " >&6; }
+ CXXFLAGS="-g2 $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -g" >&5
+$as_echo_n "checking if $CXX accepts -g... " >&6; }
+ CXXFLAGS="-g $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ fi
+ CFLAGS="$CFLAGS -DSVN_DEBUG -DAP_DEBUG"
+ CXXFLAGS="$CXXFLAGS -DSVN_DEBUG -DAP_DEBUG"
+elif test "$enable_debugging" = "no" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling debugging" >&5
+$as_echo "$as_me: Disabling debugging" >&6;}
+ CFLAGS="`echo $CFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"
+ CXXFLAGS="`echo $CXXFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"
+ CFLAGS="$CFLAGS -DNDEBUG"
+ CXXFLAGS="$CXXFLAGS -DNDEBUG"
+# elif test "$enable_debugging" = "maybe" ; then
+# # do nothing
+fi
+
+if test "$enable_optimization" = "yes"; then
+ if test -z "`echo $CUSERFLAGS' ' | $EGREP -- '-O[^ ]* '`"; then
+ CFLAGS="`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"
+ if test "$enable_debugging" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling optimizations for C (with debugging enabled)" >&5
+$as_echo "$as_me: Enabling optimizations for C (with debugging enabled)" >&6;}
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O1" >&5
+$as_echo_n "checking if $CC accepts -O1... " >&6; }
+ CFLAGS="-O1 $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O" >&5
+$as_echo_n "checking if $CC accepts -O... " >&6; }
+ CFLAGS="-O $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling optimizations for C" >&5
+$as_echo "$as_me: Enabling optimizations for C" >&6;}
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O3" >&5
+$as_echo_n "checking if $CC accepts -O3... " >&6; }
+ CFLAGS="-O3 $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O2" >&5
+$as_echo_n "checking if $CC accepts -O2... " >&6; }
+ CFLAGS="-O2 $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O1" >&5
+$as_echo_n "checking if $CC accepts -O1... " >&6; }
+ CFLAGS="-O1 $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -O" >&5
+$as_echo_n "checking if $CC accepts -O... " >&6; }
+ CFLAGS="-O $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -Wno-clobbered" >&5
+$as_echo_n "checking if $CC accepts -Wno-clobbered... " >&6; }
+ CFLAGS="-Wno-clobbered $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -flto" >&5
+$as_echo_n "checking if $CC accepts -flto... " >&6; }
+ CFLAGS="-flto $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CFLAGS"
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC accepts -fwhole-program" >&5
+$as_echo_n "checking if $CC accepts -fwhole-program... " >&6; }
+ CFLAGS="-fwhole-program $CFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ fi
+ fi
+ if test -z "`echo $CXXUSERFLAGS' ' | $EGREP -- '-O[^ ]* '`"; then
+ CXXFLAGS="`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"
+ if test "$enable_debugging" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling optimizations for C++ (with debugging enabled)" >&5
+$as_echo "$as_me: Enabling optimizations for C++ (with debugging enabled)" >&6;}
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O1" >&5
+$as_echo_n "checking if $CXX accepts -O1... " >&6; }
+ CXXFLAGS="-O1 $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O" >&5
+$as_echo_n "checking if $CXX accepts -O... " >&6; }
+ CXXFLAGS="-O $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling optimizations for C++" >&5
+$as_echo "$as_me: Enabling optimizations for C++" >&6;}
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O3" >&5
+$as_echo_n "checking if $CXX accepts -O3... " >&6; }
+ CXXFLAGS="-O3 $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O2" >&5
+$as_echo_n "checking if $CXX accepts -O2... " >&6; }
+ CXXFLAGS="-O2 $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O1" >&5
+$as_echo_n "checking if $CXX accepts -O1... " >&6; }
+ CXXFLAGS="-O1 $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -O" >&5
+$as_echo_n "checking if $CXX accepts -O... " >&6; }
+ CXXFLAGS="-O $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -Wno-clobbered" >&5
+$as_echo_n "checking if $CXX accepts -Wno-clobbered... " >&6; }
+ CXXFLAGS="-Wno-clobbered $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -flto" >&5
+$as_echo_n "checking if $CXX accepts -flto... " >&6; }
+ CXXFLAGS="-flto $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+ _svn_xxflags__save="$CXXFLAGS"
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CXX accepts -fwhole-program" >&5
+$as_echo_n "checking if $CXX accepts -fwhole-program... " >&6; }
+ CXXFLAGS="-fwhole-program $CXXFLAGS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+else
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CXXFLAGS="$_svn_xxflags__save"
+
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ fi
+ fi
+elif test "$enable_optimization" = "no"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling optimizations" >&5
+$as_echo "$as_me: Disabling optimizations" >&6;}
+ CFLAGS="`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"
+ CXXFLAGS="`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"
+# elif test "$enable_optimization" = "maybe" ; then
+# # do nothing
+fi
+
+
+# Check whether --enable-full-version-match was given.
+if test "${enable_full_version_match+set}" = set; then :
+ enableval=$enable_full_version_match;
+ if test "$enableval" = "no" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling svn full version matching" >&5
+$as_echo "$as_me: Disabling svn full version matching" >&6;}
+
+$as_echo "#define SVN_DISABLE_FULL_VERSION_MATCH 1" >>confdefs.h
+
+ fi
+
+fi
+
+
+
+# Check whether --with-editor was given.
+if test "${with_editor+set}" = set; then :
+ withval=$with_editor;
+
+ if test "$withval" = "yes" ; then
+ as_fn_error $? "--with-editor requires an argument." "$LINENO" 5
+ else
+ SVN_CLIENT_EDITOR=$withval
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_CLIENT_EDITOR "$SVN_CLIENT_EDITOR"
+_ACEOF
+
+
+ fi
+
+
+fi
+
+
+
+ zlib_found=no
+
+
+# Check whether --with-zlib was given.
+if test "${with_zlib+set}" = set; then :
+ withval=$with_zlib;
+ if test "$withval" = "yes" ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default"
+if test "x$ac_cv_header_zlib_h" = xyes; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for inflate in -lz" >&5
+$as_echo_n "checking for inflate in -lz... " >&6; }
+if ${ac_cv_lib_z_inflate+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lz $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char inflate ();
+int
+main ()
+{
+return inflate ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_z_inflate=yes
+else
+ ac_cv_lib_z_inflate=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_inflate" >&5
+$as_echo "$ac_cv_lib_z_inflate" >&6; }
+if test "x$ac_cv_lib_z_inflate" = xyes; then :
+ zlib_found="builtin"
+fi
+
+
+fi
+
+
+ elif test "$withval" = "no" ; then
+ as_fn_error $? "cannot compile without zlib." "$LINENO" 5
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: zlib library configuration" >&5
+$as_echo "$as_me: zlib library configuration" >&6;}
+ zlib_prefix=$withval
+ save_cppflags="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS -I$zlib_prefix/include"
+ for ac_header in zlib.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default"
+if test "x$ac_cv_header_zlib_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_ZLIB_H 1
+_ACEOF
+
+ save_ldflags="$LDFLAGS"
+ LDFLAGS="$LDFLAGS -L$zlib_prefix/lib"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for inflate in -lz" >&5
+$as_echo_n "checking for inflate in -lz... " >&6; }
+if ${ac_cv_lib_z_inflate+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lz $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char inflate ();
+int
+main ()
+{
+return inflate ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_z_inflate=yes
+else
+ ac_cv_lib_z_inflate=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_inflate" >&5
+$as_echo "$ac_cv_lib_z_inflate" >&6; }
+if test "x$ac_cv_lib_z_inflate" = xyes; then :
+ zlib_found="yes"
+fi
+
+ LDFLAGS="$save_ldflags"
+
+fi
+
+done
+
+ CPPFLAGS="$save_cppflags"
+ fi
+
+else
+
+ ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default"
+if test "x$ac_cv_header_zlib_h" = xyes; then :
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for inflate in -lz" >&5
+$as_echo_n "checking for inflate in -lz... " >&6; }
+if ${ac_cv_lib_z_inflate+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lz $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char inflate ();
+int
+main ()
+{
+return inflate ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_z_inflate=yes
+else
+ ac_cv_lib_z_inflate=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_inflate" >&5
+$as_echo "$ac_cv_lib_z_inflate" >&6; }
+if test "x$ac_cv_lib_z_inflate" = xyes; then :
+ zlib_found="builtin"
+fi
+
+
+fi
+
+
+
+fi
+
+
+ if test "$zlib_found" = "no"; then
+ as_fn_error $? "subversion requires zlib" "$LINENO" 5
+ fi
+
+ if test "$zlib_found" = "yes"; then
+ SVN_ZLIB_INCLUDES="-I$zlib_prefix/include"
+ LDFLAGS="$LDFLAGS `
+ input_flags="-L$zlib_prefix/lib"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+ fi
+
+ SVN_ZLIB_LIBS="-lz"
+
+
+
+
+
+MOD_ACTIVATION=""
+# Check whether --enable-mod-activation was given.
+if test "${enable_mod_activation+set}" = set; then :
+ enableval=$enable_mod_activation;
+ if test "$enableval" = "yes" ; then
+ MOD_ACTIVATION="-a"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling apache module activation" >&5
+$as_echo "$as_me: Enabling apache module activation" >&6;}
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Disabling apache module activation" >&5
+$as_echo "$as_me: Disabling apache module activation" >&6;}
+ fi
+
+fi
+
+
+
+
+
+# Check whether --enable-gcov was given.
+if test "${enable_gcov+set}" = set; then :
+ enableval=$enable_gcov;
+ if test "$enableval" = "yes" ; then
+ if test "$GCC" = "yes"; then
+ if test "$svn_enable_shared" = "yes" ; then
+ as_fn_error $? "Can't have --enable-gcov without --disable-shared (we
+ recommend also using --enable-all-static)." "$LINENO" 5
+ fi
+ if test ! "$enable_all_static" = "yes" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: We recommend --enable-all-static with --enable-gcov." >&5
+$as_echo "$as_me: WARNING: We recommend --enable-all-static with --enable-gcov." >&2;}
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling gcov coverage testing." >&5
+$as_echo "$as_me: Enabling gcov coverage testing." >&6;}
+ CFLAGS="$CFLAGS -fprofile-arcs -ftest-coverage"
+ CXXFLAGS="$CXXFLAGS -fprofile-arcs -ftest-coverage"
+ else
+ as_fn_error $? "We only support --enable-gcov with GCC right now." "$LINENO" 5
+ fi
+ fi
+
+fi
+
+
+# Check whether --enable-gprof was given.
+if test "${enable_gprof+set}" = set; then :
+ enableval=$enable_gprof;
+ if test "$enableval" = "yes" ; then
+ if test "$GCC" = "yes"; then
+ if test "$svn_enable_shared" = "yes" ; then
+ as_fn_error $? "Can't have --enable-gprof without --disable-shared (we
+ recommend also using --enable-all-static)." "$LINENO" 5
+ fi
+ if test ! "$enable_all_static" = "yes" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: We recommend --enable-all-static with --enable-gprof." >&5
+$as_echo "$as_me: WARNING: We recommend --enable-all-static with --enable-gprof." >&2;}
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Enabling gprof profiling data (to gmon.out)." >&5
+$as_echo "$as_me: Enabling gprof profiling data (to gmon.out)." >&6;}
+ CFLAGS="$CFLAGS -pg"
+ CXXFLAGS="$CXXFLAGS -pg"
+ LT_LDFLAGS="$LT_LDFLAGS -pg"
+ else
+ as_fn_error $? "We only support --enable-gprof with GCC right now." "$LINENO" 5
+ fi
+ fi
+
+fi
+
+
+
+# Scripting and Bindings languages
+
+# Python: Used for testsuite, and bindings
+
+
+PYTHON="`$abs_srcdir/build/find_python.sh`"
+if test -z "$PYTHON"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Python 2.5 or later is required to run the testsuite" >&5
+$as_echo "$as_me: WARNING: Python 2.5 or later is required to run the testsuite" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: or to use the Subversion Python bindings" >&5
+$as_echo "$as_me: WARNING: or to use the Subversion Python bindings" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: " >&5
+$as_echo "$as_me: WARNING: " >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: If you have a suitable Python installed, but not on the" >&5
+$as_echo "$as_me: WARNING: If you have a suitable Python installed, but not on the" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: PATH, set the environment variable PYTHON to the full path" >&5
+$as_echo "$as_me: WARNING: PATH, set the environment variable PYTHON to the full path" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: to the Python executable, and re-run configure" >&5
+$as_echo "$as_me: WARNING: to the Python executable, and re-run configure" >&2;}
+fi
+for ac_prog in "$PYTHON"
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PYTHON+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $PYTHON in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_PYTHON="$PYTHON" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_PYTHON="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+PYTHON=$ac_cv_path_PYTHON
+if test -n "$PYTHON"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5
+$as_echo "$PYTHON" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$PYTHON" && break
+done
+test -n "$PYTHON" || PYTHON="none"
+
+
+# The minimum version for the JVM runtime for our Java bytecode.
+JAVA_OLDEST_WORKING_VER='1.5'
+# SVN_CHECK_JDK sets $JAVA_CLASSPATH
+
+ JAVA_OLDEST_WORKING_VER="$JAVA_OLDEST_WORKING_VER"
+
+# Check whether --with-jdk was given.
+if test "${with_jdk+set}" = set; then :
+ withval=$with_jdk;
+ case "$withval" in
+ "no")
+ JDK_SUITABLE=no
+ ;;
+ "yes")
+
+ where=check
+ JAVA_OLDEST_WORKING_VER="$JAVA_OLDEST_WORKING_VER"
+
+ JDK=none
+ JAVA_BIN=none
+ JAVADOC=none
+ JAVAC=none
+ JAVAH=none
+ JAR=none
+ JNI_INCLUDES=none
+
+ JDK_SUITABLE=no
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for JDK" >&5
+$as_echo_n "checking for JDK... " >&6; }
+ if test $where = check; then
+ if test -x "$JAVA_HOME/bin/java"; then
+ JDK="$JAVA_HOME"
+ elif test -x "/Library/Java/Home/bin/java"; then
+ JDK="/Library/Java/Home"
+ elif test -x "/usr/bin/java"; then
+ JDK="/usr"
+ elif test -x "/usr/local/bin/java"; then
+ JDK="/usr/local"
+ fi
+ else
+ JDK=$where
+ fi
+
+ os_arch="`uname`"
+ if test "$os_arch" = "Darwin"; then
+ OSX_VER=`/usr/bin/sw_vers | grep ProductVersion | cut -f2 | cut -d"." -f1,2`
+
+ if test "$OSX_VER" = "10.4"; then
+ OSX_VER="10.4u"
+ fi
+
+ OSX_SYS_JAVA_FRAMEWORK="/System/Library/Frameworks/JavaVM.framework"
+ OSX_SDK_JAVA_FRAMEWORK="/Developer/SDKs/MacOSX$OSX_VER.sdk/System/Library"
+ OSX_SDK_JAVA_FRAMEWORK="$OSX_SDK_JAVA_FRAMEWORK/Frameworks/JavaVM.framework"
+ fi
+
+ if test "$os_arch" = "Darwin" && test "$JDK" = "/usr" &&
+ test -d "/Library/Java/Home"; then
+ JDK="/Library/Java/Home"
+ fi
+
+ if test "$os_arch" = "Darwin" && test "$JDK" = "/Library/Java/Home"; then
+ JRE_LIB_DIR="$OSX_SYS_JAVA_FRAMEWORK/Classes"
+ else
+ JRE_LIB_DIR="$JDK/jre/lib"
+ fi
+
+ if test -f "$JDK/include/jni.h"; then
+ JNI_INCLUDEDIR="$JDK/include"
+ JDK_SUITABLE=yes
+ elif test "$os_arch" = "Darwin" && test -e "$JDK/Headers/jni.h"; then
+ JNI_INCLUDEDIR="$JDK/Headers"
+ JDK_SUITABLE=yes
+ elif test "$os_arch" = "Darwin" &&
+ test -e "$OSX_SYS_JAVA_FRAMEWORK/Headers/jni.h"; then
+ JNI_INCLUDEDIR="$OSX_SYS_JAVA_FRAMEWORK/Headers"
+ JDK_SUITABLE=yes
+ elif test "$os_arch" = "Darwin" &&
+ test -e "$OSX_SDK_JAVA_FRAMEWORK/Headers/jni.h"; then
+ JNI_INCLUDEDIR="$OSX_SDK_JAVA_FRAMEWORK/Headers"
+ JDK_SUITABLE=yes
+ else
+ JDK_SUITABLE=no
+ fi
+ if test "$JDK_SUITABLE" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $JNI_INCLUDEDIR/jni.h" >&5
+$as_echo "$JNI_INCLUDEDIR/jni.h" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ if test "$where" != "check"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: no JNI header files found." >&5
+$as_echo "$as_me: WARNING: no JNI header files found." >&2;}
+ if test "$os_arch" = "Darwin"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&5
+$as_echo "$as_me: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&2;}
+ fi
+ fi
+ fi
+
+ if test "$JDK_SUITABLE" = "yes"; then
+ JAVA_BIN='$(JDK)/bin'
+
+ JAVA="$JAVA_BIN/java"
+ JAVAC="$JAVA_BIN/javac"
+ JAVAH="$JAVA_BIN/javah"
+ JAVADOC="$JAVA_BIN/javadoc"
+ JAR="$JAVA_BIN/jar"
+
+ jikes_options="/usr/local/bin/jikes /usr/bin/jikes"
+
+# Check whether --with-jikes was given.
+if test "${with_jikes+set}" = set; then :
+ withval=$with_jikes;
+ if test "$withval" != "no" && test "$withval" != "yes"; then
+ jikes_options="$withval $jikes_options"
+ fi
+ requested_jikes="$withval" # will be 'yes' if path unspecified
+
+fi
+
+ if test "$requested_jikes" != "no"; then
+ for jikes in $jikes_options; do
+ if test -z "$jikes_found" && test -x "$jikes"; then
+ jikes_found="yes"
+ JAVAC="$jikes"
+ JAVA_CLASSPATH="$JRE_LIB_DIR"
+ for jar in $JRE_LIB_DIR/*.jar; do
+ JAVA_CLASSPATH="$JAVA_CLASSPATH:$jar"
+ done
+ fi
+ done
+ fi
+ if test -n "$requested_jikes" && test "$requested_jikes" != "no"; then
+ if test -z "$jikes_found"; then
+ as_fn_error $? "Could not find a usable version of Jikes" "$LINENO" 5
+ elif test -n "$jikes_found" && test "$requested_jikes" != "yes" &&
+ test "$JAVAC" != "$requested_jikes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --with-jikes PATH was invalid, substitute found" >&5
+$as_echo "$as_me: WARNING: --with-jikes PATH was invalid, substitute found" >&2;}
+ fi
+ fi
+
+ # The release for "-source" could actually be greater than that
+ # of "-target", if we want to cross-compile for lesser JVMs.
+ if test -z "$JAVAC_FLAGS"; then
+ JAVAC_FLAGS="-target $JAVA_OLDEST_WORKING_VER -source 1.5"
+ if test "$enable_debugging" = "yes"; then
+ JAVAC_FLAGS="-g -Xlint:unchecked $JAVAC_FLAGS"
+ fi
+ fi
+
+ JNI_INCLUDES="-I$JNI_INCLUDEDIR"
+ list="`find "$JNI_INCLUDEDIR" -type d -print`"
+ for dir in $list; do
+ JNI_INCLUDES="$JNI_INCLUDES -I$dir"
+ done
+ fi
+
+
+
+
+
+
+
+
+
+
+ ;;
+ *)
+
+ where=$withval
+ JAVA_OLDEST_WORKING_VER="$JAVA_OLDEST_WORKING_VER"
+
+ JDK=none
+ JAVA_BIN=none
+ JAVADOC=none
+ JAVAC=none
+ JAVAH=none
+ JAR=none
+ JNI_INCLUDES=none
+
+ JDK_SUITABLE=no
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for JDK" >&5
+$as_echo_n "checking for JDK... " >&6; }
+ if test $where = check; then
+ if test -x "$JAVA_HOME/bin/java"; then
+ JDK="$JAVA_HOME"
+ elif test -x "/Library/Java/Home/bin/java"; then
+ JDK="/Library/Java/Home"
+ elif test -x "/usr/bin/java"; then
+ JDK="/usr"
+ elif test -x "/usr/local/bin/java"; then
+ JDK="/usr/local"
+ fi
+ else
+ JDK=$where
+ fi
+
+ os_arch="`uname`"
+ if test "$os_arch" = "Darwin"; then
+ OSX_VER=`/usr/bin/sw_vers | grep ProductVersion | cut -f2 | cut -d"." -f1,2`
+
+ if test "$OSX_VER" = "10.4"; then
+ OSX_VER="10.4u"
+ fi
+
+ OSX_SYS_JAVA_FRAMEWORK="/System/Library/Frameworks/JavaVM.framework"
+ OSX_SDK_JAVA_FRAMEWORK="/Developer/SDKs/MacOSX$OSX_VER.sdk/System/Library"
+ OSX_SDK_JAVA_FRAMEWORK="$OSX_SDK_JAVA_FRAMEWORK/Frameworks/JavaVM.framework"
+ fi
+
+ if test "$os_arch" = "Darwin" && test "$JDK" = "/usr" &&
+ test -d "/Library/Java/Home"; then
+ JDK="/Library/Java/Home"
+ fi
+
+ if test "$os_arch" = "Darwin" && test "$JDK" = "/Library/Java/Home"; then
+ JRE_LIB_DIR="$OSX_SYS_JAVA_FRAMEWORK/Classes"
+ else
+ JRE_LIB_DIR="$JDK/jre/lib"
+ fi
+
+ if test -f "$JDK/include/jni.h"; then
+ JNI_INCLUDEDIR="$JDK/include"
+ JDK_SUITABLE=yes
+ elif test "$os_arch" = "Darwin" && test -e "$JDK/Headers/jni.h"; then
+ JNI_INCLUDEDIR="$JDK/Headers"
+ JDK_SUITABLE=yes
+ elif test "$os_arch" = "Darwin" &&
+ test -e "$OSX_SYS_JAVA_FRAMEWORK/Headers/jni.h"; then
+ JNI_INCLUDEDIR="$OSX_SYS_JAVA_FRAMEWORK/Headers"
+ JDK_SUITABLE=yes
+ elif test "$os_arch" = "Darwin" &&
+ test -e "$OSX_SDK_JAVA_FRAMEWORK/Headers/jni.h"; then
+ JNI_INCLUDEDIR="$OSX_SDK_JAVA_FRAMEWORK/Headers"
+ JDK_SUITABLE=yes
+ else
+ JDK_SUITABLE=no
+ fi
+ if test "$JDK_SUITABLE" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $JNI_INCLUDEDIR/jni.h" >&5
+$as_echo "$JNI_INCLUDEDIR/jni.h" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ if test "$where" != "check"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: no JNI header files found." >&5
+$as_echo "$as_me: WARNING: no JNI header files found." >&2;}
+ if test "$os_arch" = "Darwin"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&5
+$as_echo "$as_me: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&2;}
+ fi
+ fi
+ fi
+
+ if test "$JDK_SUITABLE" = "yes"; then
+ JAVA_BIN='$(JDK)/bin'
+
+ JAVA="$JAVA_BIN/java"
+ JAVAC="$JAVA_BIN/javac"
+ JAVAH="$JAVA_BIN/javah"
+ JAVADOC="$JAVA_BIN/javadoc"
+ JAR="$JAVA_BIN/jar"
+
+ jikes_options="/usr/local/bin/jikes /usr/bin/jikes"
+
+# Check whether --with-jikes was given.
+if test "${with_jikes+set}" = set; then :
+ withval=$with_jikes;
+ if test "$withval" != "no" && test "$withval" != "yes"; then
+ jikes_options="$withval $jikes_options"
+ fi
+ requested_jikes="$withval" # will be 'yes' if path unspecified
+
+fi
+
+ if test "$requested_jikes" != "no"; then
+ for jikes in $jikes_options; do
+ if test -z "$jikes_found" && test -x "$jikes"; then
+ jikes_found="yes"
+ JAVAC="$jikes"
+ JAVA_CLASSPATH="$JRE_LIB_DIR"
+ for jar in $JRE_LIB_DIR/*.jar; do
+ JAVA_CLASSPATH="$JAVA_CLASSPATH:$jar"
+ done
+ fi
+ done
+ fi
+ if test -n "$requested_jikes" && test "$requested_jikes" != "no"; then
+ if test -z "$jikes_found"; then
+ as_fn_error $? "Could not find a usable version of Jikes" "$LINENO" 5
+ elif test -n "$jikes_found" && test "$requested_jikes" != "yes" &&
+ test "$JAVAC" != "$requested_jikes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --with-jikes PATH was invalid, substitute found" >&5
+$as_echo "$as_me: WARNING: --with-jikes PATH was invalid, substitute found" >&2;}
+ fi
+ fi
+
+ # The release for "-source" could actually be greater than that
+ # of "-target", if we want to cross-compile for lesser JVMs.
+ if test -z "$JAVAC_FLAGS"; then
+ JAVAC_FLAGS="-target $JAVA_OLDEST_WORKING_VER -source 1.5"
+ if test "$enable_debugging" = "yes"; then
+ JAVAC_FLAGS="-g -Xlint:unchecked $JAVAC_FLAGS"
+ fi
+ fi
+
+ JNI_INCLUDES="-I$JNI_INCLUDEDIR"
+ list="`find "$JNI_INCLUDEDIR" -type d -print`"
+ for dir in $list; do
+ JNI_INCLUDES="$JNI_INCLUDES -I$dir"
+ done
+ fi
+
+
+
+
+
+
+
+
+
+
+ ;;
+ esac
+
+else
+
+
+ where=check
+ JAVA_OLDEST_WORKING_VER="$JAVA_OLDEST_WORKING_VER"
+
+ JDK=none
+ JAVA_BIN=none
+ JAVADOC=none
+ JAVAC=none
+ JAVAH=none
+ JAR=none
+ JNI_INCLUDES=none
+
+ JDK_SUITABLE=no
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for JDK" >&5
+$as_echo_n "checking for JDK... " >&6; }
+ if test $where = check; then
+ if test -x "$JAVA_HOME/bin/java"; then
+ JDK="$JAVA_HOME"
+ elif test -x "/Library/Java/Home/bin/java"; then
+ JDK="/Library/Java/Home"
+ elif test -x "/usr/bin/java"; then
+ JDK="/usr"
+ elif test -x "/usr/local/bin/java"; then
+ JDK="/usr/local"
+ fi
+ else
+ JDK=$where
+ fi
+
+ os_arch="`uname`"
+ if test "$os_arch" = "Darwin"; then
+ OSX_VER=`/usr/bin/sw_vers | grep ProductVersion | cut -f2 | cut -d"." -f1,2`
+
+ if test "$OSX_VER" = "10.4"; then
+ OSX_VER="10.4u"
+ fi
+
+ OSX_SYS_JAVA_FRAMEWORK="/System/Library/Frameworks/JavaVM.framework"
+ OSX_SDK_JAVA_FRAMEWORK="/Developer/SDKs/MacOSX$OSX_VER.sdk/System/Library"
+ OSX_SDK_JAVA_FRAMEWORK="$OSX_SDK_JAVA_FRAMEWORK/Frameworks/JavaVM.framework"
+ fi
+
+ if test "$os_arch" = "Darwin" && test "$JDK" = "/usr" &&
+ test -d "/Library/Java/Home"; then
+ JDK="/Library/Java/Home"
+ fi
+
+ if test "$os_arch" = "Darwin" && test "$JDK" = "/Library/Java/Home"; then
+ JRE_LIB_DIR="$OSX_SYS_JAVA_FRAMEWORK/Classes"
+ else
+ JRE_LIB_DIR="$JDK/jre/lib"
+ fi
+
+ if test -f "$JDK/include/jni.h"; then
+ JNI_INCLUDEDIR="$JDK/include"
+ JDK_SUITABLE=yes
+ elif test "$os_arch" = "Darwin" && test -e "$JDK/Headers/jni.h"; then
+ JNI_INCLUDEDIR="$JDK/Headers"
+ JDK_SUITABLE=yes
+ elif test "$os_arch" = "Darwin" &&
+ test -e "$OSX_SYS_JAVA_FRAMEWORK/Headers/jni.h"; then
+ JNI_INCLUDEDIR="$OSX_SYS_JAVA_FRAMEWORK/Headers"
+ JDK_SUITABLE=yes
+ elif test "$os_arch" = "Darwin" &&
+ test -e "$OSX_SDK_JAVA_FRAMEWORK/Headers/jni.h"; then
+ JNI_INCLUDEDIR="$OSX_SDK_JAVA_FRAMEWORK/Headers"
+ JDK_SUITABLE=yes
+ else
+ JDK_SUITABLE=no
+ fi
+ if test "$JDK_SUITABLE" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $JNI_INCLUDEDIR/jni.h" >&5
+$as_echo "$JNI_INCLUDEDIR/jni.h" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ if test "$where" != "check"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: no JNI header files found." >&5
+$as_echo "$as_me: WARNING: no JNI header files found." >&2;}
+ if test "$os_arch" = "Darwin"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&5
+$as_echo "$as_me: WARNING: You may need to install the latest Java Development package from http://connect.apple.com/. Apple no longer includes the JNI header files by default on Java updates." >&2;}
+ fi
+ fi
+ fi
+
+ if test "$JDK_SUITABLE" = "yes"; then
+ JAVA_BIN='$(JDK)/bin'
+
+ JAVA="$JAVA_BIN/java"
+ JAVAC="$JAVA_BIN/javac"
+ JAVAH="$JAVA_BIN/javah"
+ JAVADOC="$JAVA_BIN/javadoc"
+ JAR="$JAVA_BIN/jar"
+
+ jikes_options="/usr/local/bin/jikes /usr/bin/jikes"
+
+# Check whether --with-jikes was given.
+if test "${with_jikes+set}" = set; then :
+ withval=$with_jikes;
+ if test "$withval" != "no" && test "$withval" != "yes"; then
+ jikes_options="$withval $jikes_options"
+ fi
+ requested_jikes="$withval" # will be 'yes' if path unspecified
+
+fi
+
+ if test "$requested_jikes" != "no"; then
+ for jikes in $jikes_options; do
+ if test -z "$jikes_found" && test -x "$jikes"; then
+ jikes_found="yes"
+ JAVAC="$jikes"
+ JAVA_CLASSPATH="$JRE_LIB_DIR"
+ for jar in $JRE_LIB_DIR/*.jar; do
+ JAVA_CLASSPATH="$JAVA_CLASSPATH:$jar"
+ done
+ fi
+ done
+ fi
+ if test -n "$requested_jikes" && test "$requested_jikes" != "no"; then
+ if test -z "$jikes_found"; then
+ as_fn_error $? "Could not find a usable version of Jikes" "$LINENO" 5
+ elif test -n "$jikes_found" && test "$requested_jikes" != "yes" &&
+ test "$JAVAC" != "$requested_jikes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --with-jikes PATH was invalid, substitute found" >&5
+$as_echo "$as_me: WARNING: --with-jikes PATH was invalid, substitute found" >&2;}
+ fi
+ fi
+
+ # The release for "-source" could actually be greater than that
+ # of "-target", if we want to cross-compile for lesser JVMs.
+ if test -z "$JAVAC_FLAGS"; then
+ JAVAC_FLAGS="-target $JAVA_OLDEST_WORKING_VER -source 1.5"
+ if test "$enable_debugging" = "yes"; then
+ JAVAC_FLAGS="-g -Xlint:unchecked $JAVAC_FLAGS"
+ fi
+ fi
+
+ JNI_INCLUDES="-I$JNI_INCLUDEDIR"
+ list="`find "$JNI_INCLUDEDIR" -type d -print`"
+ for dir in $list; do
+ JNI_INCLUDES="$JNI_INCLUDES -I$dir"
+ done
+ fi
+
+
+
+
+
+
+
+
+
+
+
+fi
+
+
+
+# Extract the first word of "perl", so it can be a program name with args.
+set dummy perl; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PERL+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $PERL in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_PERL="$PERL" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_PERL="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_PERL" && ac_cv_path_PERL="none"
+ ;;
+esac
+fi
+PERL=$ac_cv_path_PERL
+if test -n "$PERL"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL" >&5
+$as_echo "$PERL" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+if test -n "$RUBY"; then
+ # Extract the first word of ""$RUBY"", so it can be a program name with args.
+set dummy "$RUBY"; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_RUBY+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $RUBY in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_RUBY="$RUBY" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_RUBY="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_RUBY" && ac_cv_path_RUBY="none"
+ ;;
+esac
+fi
+RUBY=$ac_cv_path_RUBY
+if test -n "$RUBY"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RUBY" >&5
+$as_echo "$RUBY" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+else
+ for ac_prog in ruby ruby1.8 ruby18 ruby1.9 ruby1 ruby1.9.3 ruby193
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_RUBY+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $RUBY in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_RUBY="$RUBY" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_RUBY="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+RUBY=$ac_cv_path_RUBY
+if test -n "$RUBY"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RUBY" >&5
+$as_echo "$RUBY" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$RUBY" && break
+done
+test -n "$RUBY" || RUBY="none"
+
+fi
+if test "$RUBY" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking rb_hash_foreach" >&5
+$as_echo_n "checking rb_hash_foreach... " >&6; }
+ if "$RUBY" -r mkmf -e 'exit(have_func("rb_hash_foreach") ? 0 : 1)' >/dev/null; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ if test -n "$RDOC"; then
+ # Extract the first word of ""$RDOC"", so it can be a program name with args.
+set dummy "$RDOC"; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_RDOC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $RDOC in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_RDOC="$RDOC" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_RDOC="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_RDOC" && ac_cv_path_RDOC="none"
+ ;;
+esac
+fi
+RDOC=$ac_cv_path_RDOC
+if test -n "$RDOC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RDOC" >&5
+$as_echo "$RDOC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ else
+ for ac_prog in rdoc rdoc1.8 rdoc18 rdoc1.9 rdoc19 rdoc1.9.3 rdoc193
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_RDOC+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $RDOC in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_RDOC="$RDOC" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_RDOC="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+RDOC=$ac_cv_path_RDOC
+if test -n "$RDOC"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RDOC" >&5
+$as_echo "$RDOC" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$RDOC" && break
+done
+test -n "$RDOC" || RDOC="none"
+
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby major version" >&5
+$as_echo_n "checking for Ruby major version... " >&6; }
+if ${svn_cv_ruby_major+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_major="`$RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MAJOR))'`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_major" >&5
+$as_echo "$svn_cv_ruby_major" >&6; }
+ RUBY_MAJOR="$svn_cv_ruby_major"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby minor version" >&5
+$as_echo_n "checking for Ruby minor version... " >&6; }
+if ${svn_cv_ruby_minor+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_minor="`$RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MINOR))'`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_minor" >&5
+$as_echo "$svn_cv_ruby_minor" >&6; }
+ RUBY_MINOR="$svn_cv_ruby_minor"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby teeny version" >&5
+$as_echo_n "checking for Ruby teeny version... " >&6; }
+if ${svn_cv_ruby_teeny+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_teeny="`$RUBY -rrbconfig -e 'major, minor, teeny = RUBY_VERSION.split("."); print teeny;'`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_teeny" >&5
+$as_echo "$svn_cv_ruby_teeny" >&6; }
+ RUBY_TEENY="$svn_cv_ruby_teeny"
+
+
+
+
+ if test \( "$RUBY_MAJOR" -eq "1" -a "$RUBY_MINOR" -gt "8" -a "$RUBY_TEENY" -lt "3" \); then
+ # Disallow Ruby between 1.8.7 and 1.9.3
+ RUBY="none"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: The detected Ruby is between 1.9 and 1.9.3" >&5
+$as_echo "$as_me: WARNING: The detected Ruby is between 1.9 and 1.9.3" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Only 1.8.x and 1.9.3 releases are supported at this time" >&5
+$as_echo "$as_me: WARNING: Only 1.8.x and 1.9.3 releases are supported at this time" >&2;}
+ elif test \( "$RUBY_MAJOR" -eq "1" -a "$RUBY_MINOR" -eq "9" -a "$RUBY_TEENY" -eq "3" \); then
+ #Warn about 1.9.3 support
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: WARNING: The detected Ruby is 1.9.3" >&5
+$as_echo "$as_me: WARNING: WARNING: The detected Ruby is 1.9.3" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: WARNING: Only 1.8.x releases are fully supported, 1.9.3 support is new" >&5
+$as_echo "$as_me: WARNING: WARNING: Only 1.8.x releases are fully supported, 1.9.3 support is new" >&2;}
+ fi
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ RUBY="none"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: The detected Ruby is too old for Subversion to use" >&5
+$as_echo "$as_me: WARNING: The detected Ruby is too old for Subversion to use" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: A Ruby which has rb_hash_foreach is required to use the" >&5
+$as_echo "$as_me: WARNING: A Ruby which has rb_hash_foreach is required to use the" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Subversion Ruby bindings" >&5
+$as_echo "$as_me: WARNING: Subversion Ruby bindings" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Upgrade to the official 1.8.2 release, or later" >&5
+$as_echo "$as_me: WARNING: Upgrade to the official 1.8.2 release, or later" >&2;}
+ fi
+fi
+
+
+
+# Check whether --with-swig was given.
+if test "${with_swig+set}" = set; then :
+ withval=$with_swig;
+ case "$withval" in
+ "no")
+ SWIG_SUITABLE=no
+
+ where=no
+
+ if test $where = no; then
+ SWIG=none
+ elif test $where = check; then
+ # Extract the first word of "swig", so it can be a program name with args.
+set dummy swig; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_SWIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $SWIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_SWIG="$SWIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_SWIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_SWIG" && ac_cv_path_SWIG="none"
+ ;;
+esac
+fi
+SWIG=$ac_cv_path_SWIG
+if test -n "$SWIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG" >&5
+$as_echo "$SWIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ else
+ if test -f "$where"; then
+ SWIG="$where"
+ else
+ SWIG="$where/bin/swig"
+ fi
+ if test ! -f "$SWIG" || test ! -x "$SWIG"; then
+ as_fn_error $? "Could not find swig binary at $SWIG" "$LINENO" 5
+ fi
+ fi
+
+ if test "$SWIG" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking swig version" >&5
+$as_echo_n "checking swig version... " >&6; }
+ SWIG_VERSION_RAW="`$SWIG -version 2>&1 | \
+ $SED -ne 's/^.*Version \(.*\)$/\1/p'`"
+ # We want the version as an integer so we can test against
+ # which version we're using. SWIG doesn't provide this
+ # to us so we have to come up with it on our own.
+ # The major is passed straight through,
+ # the minor is zero padded to two places,
+ # and the patch level is zero padded to three places.
+ # e.g. 1.3.24 becomes 103024
+ SWIG_VERSION="`echo \"$SWIG_VERSION_RAW\" | \
+ $SED -e 's/[^0-9\.].*$//' \
+ -e 's/\.\([0-9]\)$/.0\1/' \
+ -e 's/\.\([0-9][0-9]\)$/.0\1/' \
+ -e 's/\.\([0-9]\)\./0\1/; s/\.//g;'`"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_VERSION_RAW" >&5
+$as_echo "$SWIG_VERSION_RAW" >&6; }
+ # If you change the required swig version number, don't forget to update:
+ # subversion/bindings/swig/INSTALL
+ # packages/rpm/redhat-8+/subversion.spec
+ # packages/rpm/redhat-7.x/subversion.spec
+ # packages/rpm/rhel-3/subversion.spec
+ # packages/rpm/rhel-4/subversion.spec
+ if test -n "$SWIG_VERSION" && test "$SWIG_VERSION" -ge "103024"; then
+ SWIG_SUITABLE=yes
+ else
+ SWIG_SUITABLE=no
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&5
+$as_echo "$as_me: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Subversion requires SWIG 1.3.24 or later" >&5
+$as_echo "$as_me: WARNING: Subversion requires SWIG 1.3.24 or later" >&2;}
+ fi
+ fi
+
+ SWIG_PY_COMPILE="none"
+ SWIG_PY_LINK="none"
+ if test "$PYTHON" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring python swig binding" >&5
+$as_echo "$as_me: Configuring python swig binding" >&6;}
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python includes" >&5
+$as_echo_n "checking for Python includes... " >&6; }
+if ${ac_cv_python_includes+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_includes="`$PYTHON ${abs_srcdir}/build/get-py-info.py --includes`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_includes" >&5
+$as_echo "$ac_cv_python_includes" >&6; }
+ SWIG_PY_INCLUDES="\$(SWIG_INCLUDES) $ac_cv_python_includes"
+
+ if test "$ac_cv_python_includes" = "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: python bindings cannot be built without distutils module" >&5
+$as_echo "$as_me: WARNING: python bindings cannot be built without distutils module" >&2;}
+ fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiling Python extensions" >&5
+$as_echo_n "checking for compiling Python extensions... " >&6; }
+if ${ac_cv_python_compile+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_compile="`$PYTHON ${abs_srcdir}/build/get-py-info.py --compile`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_compile" >&5
+$as_echo "$ac_cv_python_compile" >&6; }
+ SWIG_PY_COMPILE="$ac_cv_python_compile $CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python extensions" >&5
+$as_echo_n "checking for linking Python extensions... " >&6; }
+if ${ac_cv_python_link+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_link="`$PYTHON ${abs_srcdir}/build/get-py-info.py --link`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_link" >&5
+$as_echo "$ac_cv_python_link" >&6; }
+ SWIG_PY_LINK="$ac_cv_python_link"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python libraries" >&5
+$as_echo_n "checking for linking Python libraries... " >&6; }
+if ${ac_cv_python_libs+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_libs="`$PYTHON ${abs_srcdir}/build/get-py-info.py --libs`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_libs" >&5
+$as_echo "$ac_cv_python_libs" >&6; }
+ SWIG_PY_LIBS="`
+ input_flags="$ac_cv_python_libs"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ SVN_PYCFMT_SAVE_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_int64_t Python/C API format string" >&5
+$as_echo_n "checking for apr_int64_t Python/C API format string... " >&6; }
+if ${svn_cv_pycfmt_apr_int64_t+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"lld\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="L"
+fi
+rm -f conftest*
+
+ fi
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+r
+ #include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"ld\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="l"
+fi
+rm -f conftest*
+
+ fi
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"d\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="i"
+fi
+rm -f conftest*
+
+ fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_pycfmt_apr_int64_t" >&5
+$as_echo "$svn_cv_pycfmt_apr_int64_t" >&6; }
+ CPPFLAGS="$SVN_PYCFMT_SAVE_CPPFLAGS"
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ as_fn_error $? "failed to recognize APR_INT64_T_FMT on this platform" "$LINENO" 5
+ fi
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_APR_INT64_T_PYCFMT "$svn_cv_pycfmt_apr_int64_t"
+_ACEOF
+
+ fi
+
+ if test "$PERL" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking perl version" >&5
+$as_echo_n "checking perl version... " >&6; }
+ PERL_VERSION="`$PERL -e 'q([); print $] * 1000000,$/;'`"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL_VERSION" >&5
+$as_echo "$PERL_VERSION" >&6; }
+ if test "$PERL_VERSION" -ge "5008000"; then
+ SWIG_PL_INCLUDES="\$(SWIG_INCLUDES) `$PERL -MExtUtils::Embed -e ccopts`"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: perl bindings require perl 5.8.0 or newer." >&5
+$as_echo "$as_me: WARNING: perl bindings require perl 5.8.0 or newer." >&2;}
+ fi
+ fi
+
+ SWIG_RB_COMPILE="none"
+ SWIG_RB_LINK="none"
+ if test "$RUBY" != "none"; then
+ rbconfig="$RUBY -rrbconfig -e "
+
+ for var_name in arch archdir CC LDSHARED DLEXT LIBS LIBRUBYARG \
+ rubyhdrdir sitedir sitelibdir sitearchdir libdir
+ do
+ rbconfig_tmp=`$rbconfig "print RbConfig::CONFIG['$var_name']"`
+ eval "rbconfig_$var_name=\"$rbconfig_tmp\""
+ done
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring Ruby SWIG binding" >&5
+$as_echo "$as_me: Configuring Ruby SWIG binding" >&6;}
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby include path" >&5
+$as_echo_n "checking for Ruby include path... " >&6; }
+if ${svn_cv_ruby_includes+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ if test -d "$rbconfig_rubyhdrdir"; then
+ svn_cv_ruby_includes="-I. -I$rbconfig_rubyhdrdir -I$rbconfig_rubyhdrdir/ruby -I$rbconfig_rubyhdrdir/ruby/backward -I$rbconfig_rubyhdrdir/$rbconfig_arch"
+ else
+ svn_cv_ruby_includes="-I. -I$rbconfig_archdir"
+ fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_includes" >&5
+$as_echo "$svn_cv_ruby_includes" >&6; }
+ SWIG_RB_INCLUDES="\$(SWIG_INCLUDES) $svn_cv_ruby_includes"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to compile Ruby extensions" >&5
+$as_echo_n "checking how to compile Ruby extensions... " >&6; }
+if ${svn_cv_ruby_compile+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_compile="$rbconfig_CC $CFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_compile" >&5
+$as_echo "$svn_cv_ruby_compile" >&6; }
+ SWIG_RB_COMPILE="$svn_cv_ruby_compile"
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-ansi//'`
+
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c89//'`
+
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c90//'`
+
+ SWIG_RB_COMPILE="$SWIG_RB_COMPILE -Wno-int-to-pointer-cast"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby extensions" >&5
+$as_echo_n "checking how to link Ruby extensions... " >&6; }
+if ${svn_cv_ruby_link+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_link="`$RUBY -e 'ARGV.shift; print ARGV.join(%q( ))' \
+ $rbconfig_LDSHARED`"
+ svn_cv_ruby_link="$rbconfig_CC $svn_cv_ruby_link"
+ svn_cv_ruby_link="$svn_cv_ruby_link -shrext .$rbconfig_DLEXT"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_link" >&5
+$as_echo "$svn_cv_ruby_link" >&6; }
+ SWIG_RB_LINK="$svn_cv_ruby_link"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby libraries" >&5
+$as_echo_n "checking how to link Ruby libraries... " >&6; }
+if ${ac_cv_ruby_libs+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_ruby_libs="$rbconfig_LIBRUBYARG $rbconfig_LIBS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_ruby_libs" >&5
+$as_echo "$ac_cv_ruby_libs" >&6; }
+ SWIG_RB_LIBS="`
+ input_flags="$ac_cv_ruby_libs"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for rb_errinfo" >&5
+$as_echo_n "checking for rb_errinfo... " >&6; }
+ old_CFLAGS="$CFLAGS"
+ old_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $svn_cv_ruby_includes"
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-ansi//'`
+
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c89//'`
+
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c90//'`
+
+ LIBS="$SWIG_RB_LIBS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <ruby.h>
+int main()
+{rb_errinfo();}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ have_rb_errinfo="yes"
+else
+ have_rb_errinfo="no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ if test "$have_rb_errinfo" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+$as_echo "#define HAVE_RB_ERRINFO 1" >>confdefs.h
+
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ fi
+ CFLAGS="$old_CFLAGS"
+ LIBS="$old_LIBS"
+
+ if ${svn_cv_ruby_sitedir+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir="$rbconfig_sitedir"
+
+fi
+
+
+# Check whether --with-ruby-sitedir was given.
+if test "${with_ruby_sitedir+set}" = set; then :
+ withval=$with_ruby_sitedir; svn_ruby_installdir="$withval"
+else
+ svn_ruby_installdir="$svn_cv_ruby_sitedir"
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby scripts" >&5
+$as_echo_n "checking where to install Ruby scripts... " >&6; }
+ if ${svn_cv_ruby_sitedir_libsuffix+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir_libsuffix="`echo "$rbconfig_sitelibdir" | \
+ $SED -e "s,^$rbconfig_sitedir,,"`"
+
+fi
+
+ SWIG_RB_SITE_LIB_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_libsuffix}"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_LIB_DIR" >&5
+$as_echo "$SWIG_RB_SITE_LIB_DIR" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby extensions" >&5
+$as_echo_n "checking where to install Ruby extensions... " >&6; }
+ if ${svn_cv_ruby_sitedir_archsuffix+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir_archsuffix="`echo "$rbconfig_sitearchdir" | \
+ $SED -e "s,^$rbconfig_sitedir,,"`"
+
+fi
+
+ SWIG_RB_SITE_ARCH_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_archsuffix}"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_ARCH_DIR" >&5
+$as_echo "$SWIG_RB_SITE_ARCH_DIR" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to use output level for Ruby bindings tests" >&5
+$as_echo_n "checking how to use output level for Ruby bindings tests... " >&6; }
+ if ${svn_cv_ruby_test_verbose+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_test_verbose="normal"
+
+fi
+
+
+# Check whether --with-ruby-test-verbose was given.
+if test "${with_ruby_test_verbose+set}" = set; then :
+ withval=$with_ruby_test_verbose; svn_ruby_test_verbose="$withval"
+else
+ svn_ruby_test_verbose="$svn_cv_ruby_test_verbose"
+fi
+
+ SWIG_RB_TEST_VERBOSE="$svn_ruby_test_verbose"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_TEST_VERBOSE" >&5
+$as_echo "$SWIG_RB_TEST_VERBOSE" >&6; }
+ fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ;;
+ "yes")
+
+ where=check
+
+ if test $where = no; then
+ SWIG=none
+ elif test $where = check; then
+ # Extract the first word of "swig", so it can be a program name with args.
+set dummy swig; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_SWIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $SWIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_SWIG="$SWIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_SWIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_SWIG" && ac_cv_path_SWIG="none"
+ ;;
+esac
+fi
+SWIG=$ac_cv_path_SWIG
+if test -n "$SWIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG" >&5
+$as_echo "$SWIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ else
+ if test -f "$where"; then
+ SWIG="$where"
+ else
+ SWIG="$where/bin/swig"
+ fi
+ if test ! -f "$SWIG" || test ! -x "$SWIG"; then
+ as_fn_error $? "Could not find swig binary at $SWIG" "$LINENO" 5
+ fi
+ fi
+
+ if test "$SWIG" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking swig version" >&5
+$as_echo_n "checking swig version... " >&6; }
+ SWIG_VERSION_RAW="`$SWIG -version 2>&1 | \
+ $SED -ne 's/^.*Version \(.*\)$/\1/p'`"
+ # We want the version as an integer so we can test against
+ # which version we're using. SWIG doesn't provide this
+ # to us so we have to come up with it on our own.
+ # The major is passed straight through,
+ # the minor is zero padded to two places,
+ # and the patch level is zero padded to three places.
+ # e.g. 1.3.24 becomes 103024
+ SWIG_VERSION="`echo \"$SWIG_VERSION_RAW\" | \
+ $SED -e 's/[^0-9\.].*$//' \
+ -e 's/\.\([0-9]\)$/.0\1/' \
+ -e 's/\.\([0-9][0-9]\)$/.0\1/' \
+ -e 's/\.\([0-9]\)\./0\1/; s/\.//g;'`"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_VERSION_RAW" >&5
+$as_echo "$SWIG_VERSION_RAW" >&6; }
+ # If you change the required swig version number, don't forget to update:
+ # subversion/bindings/swig/INSTALL
+ # packages/rpm/redhat-8+/subversion.spec
+ # packages/rpm/redhat-7.x/subversion.spec
+ # packages/rpm/rhel-3/subversion.spec
+ # packages/rpm/rhel-4/subversion.spec
+ if test -n "$SWIG_VERSION" && test "$SWIG_VERSION" -ge "103024"; then
+ SWIG_SUITABLE=yes
+ else
+ SWIG_SUITABLE=no
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&5
+$as_echo "$as_me: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Subversion requires SWIG 1.3.24 or later" >&5
+$as_echo "$as_me: WARNING: Subversion requires SWIG 1.3.24 or later" >&2;}
+ fi
+ fi
+
+ SWIG_PY_COMPILE="none"
+ SWIG_PY_LINK="none"
+ if test "$PYTHON" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring python swig binding" >&5
+$as_echo "$as_me: Configuring python swig binding" >&6;}
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python includes" >&5
+$as_echo_n "checking for Python includes... " >&6; }
+if ${ac_cv_python_includes+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_includes="`$PYTHON ${abs_srcdir}/build/get-py-info.py --includes`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_includes" >&5
+$as_echo "$ac_cv_python_includes" >&6; }
+ SWIG_PY_INCLUDES="\$(SWIG_INCLUDES) $ac_cv_python_includes"
+
+ if test "$ac_cv_python_includes" = "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: python bindings cannot be built without distutils module" >&5
+$as_echo "$as_me: WARNING: python bindings cannot be built without distutils module" >&2;}
+ fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiling Python extensions" >&5
+$as_echo_n "checking for compiling Python extensions... " >&6; }
+if ${ac_cv_python_compile+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_compile="`$PYTHON ${abs_srcdir}/build/get-py-info.py --compile`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_compile" >&5
+$as_echo "$ac_cv_python_compile" >&6; }
+ SWIG_PY_COMPILE="$ac_cv_python_compile $CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python extensions" >&5
+$as_echo_n "checking for linking Python extensions... " >&6; }
+if ${ac_cv_python_link+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_link="`$PYTHON ${abs_srcdir}/build/get-py-info.py --link`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_link" >&5
+$as_echo "$ac_cv_python_link" >&6; }
+ SWIG_PY_LINK="$ac_cv_python_link"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python libraries" >&5
+$as_echo_n "checking for linking Python libraries... " >&6; }
+if ${ac_cv_python_libs+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_libs="`$PYTHON ${abs_srcdir}/build/get-py-info.py --libs`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_libs" >&5
+$as_echo "$ac_cv_python_libs" >&6; }
+ SWIG_PY_LIBS="`
+ input_flags="$ac_cv_python_libs"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ SVN_PYCFMT_SAVE_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_int64_t Python/C API format string" >&5
+$as_echo_n "checking for apr_int64_t Python/C API format string... " >&6; }
+if ${svn_cv_pycfmt_apr_int64_t+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"lld\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="L"
+fi
+rm -f conftest*
+
+ fi
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+r
+ #include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"ld\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="l"
+fi
+rm -f conftest*
+
+ fi
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"d\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="i"
+fi
+rm -f conftest*
+
+ fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_pycfmt_apr_int64_t" >&5
+$as_echo "$svn_cv_pycfmt_apr_int64_t" >&6; }
+ CPPFLAGS="$SVN_PYCFMT_SAVE_CPPFLAGS"
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ as_fn_error $? "failed to recognize APR_INT64_T_FMT on this platform" "$LINENO" 5
+ fi
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_APR_INT64_T_PYCFMT "$svn_cv_pycfmt_apr_int64_t"
+_ACEOF
+
+ fi
+
+ if test "$PERL" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking perl version" >&5
+$as_echo_n "checking perl version... " >&6; }
+ PERL_VERSION="`$PERL -e 'q([); print $] * 1000000,$/;'`"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL_VERSION" >&5
+$as_echo "$PERL_VERSION" >&6; }
+ if test "$PERL_VERSION" -ge "5008000"; then
+ SWIG_PL_INCLUDES="\$(SWIG_INCLUDES) `$PERL -MExtUtils::Embed -e ccopts`"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: perl bindings require perl 5.8.0 or newer." >&5
+$as_echo "$as_me: WARNING: perl bindings require perl 5.8.0 or newer." >&2;}
+ fi
+ fi
+
+ SWIG_RB_COMPILE="none"
+ SWIG_RB_LINK="none"
+ if test "$RUBY" != "none"; then
+ rbconfig="$RUBY -rrbconfig -e "
+
+ for var_name in arch archdir CC LDSHARED DLEXT LIBS LIBRUBYARG \
+ rubyhdrdir sitedir sitelibdir sitearchdir libdir
+ do
+ rbconfig_tmp=`$rbconfig "print RbConfig::CONFIG['$var_name']"`
+ eval "rbconfig_$var_name=\"$rbconfig_tmp\""
+ done
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring Ruby SWIG binding" >&5
+$as_echo "$as_me: Configuring Ruby SWIG binding" >&6;}
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby include path" >&5
+$as_echo_n "checking for Ruby include path... " >&6; }
+if ${svn_cv_ruby_includes+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ if test -d "$rbconfig_rubyhdrdir"; then
+ svn_cv_ruby_includes="-I. -I$rbconfig_rubyhdrdir -I$rbconfig_rubyhdrdir/ruby -I$rbconfig_rubyhdrdir/ruby/backward -I$rbconfig_rubyhdrdir/$rbconfig_arch"
+ else
+ svn_cv_ruby_includes="-I. -I$rbconfig_archdir"
+ fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_includes" >&5
+$as_echo "$svn_cv_ruby_includes" >&6; }
+ SWIG_RB_INCLUDES="\$(SWIG_INCLUDES) $svn_cv_ruby_includes"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to compile Ruby extensions" >&5
+$as_echo_n "checking how to compile Ruby extensions... " >&6; }
+if ${svn_cv_ruby_compile+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_compile="$rbconfig_CC $CFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_compile" >&5
+$as_echo "$svn_cv_ruby_compile" >&6; }
+ SWIG_RB_COMPILE="$svn_cv_ruby_compile"
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-ansi//'`
+
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c89//'`
+
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c90//'`
+
+ SWIG_RB_COMPILE="$SWIG_RB_COMPILE -Wno-int-to-pointer-cast"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby extensions" >&5
+$as_echo_n "checking how to link Ruby extensions... " >&6; }
+if ${svn_cv_ruby_link+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_link="`$RUBY -e 'ARGV.shift; print ARGV.join(%q( ))' \
+ $rbconfig_LDSHARED`"
+ svn_cv_ruby_link="$rbconfig_CC $svn_cv_ruby_link"
+ svn_cv_ruby_link="$svn_cv_ruby_link -shrext .$rbconfig_DLEXT"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_link" >&5
+$as_echo "$svn_cv_ruby_link" >&6; }
+ SWIG_RB_LINK="$svn_cv_ruby_link"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby libraries" >&5
+$as_echo_n "checking how to link Ruby libraries... " >&6; }
+if ${ac_cv_ruby_libs+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_ruby_libs="$rbconfig_LIBRUBYARG $rbconfig_LIBS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_ruby_libs" >&5
+$as_echo "$ac_cv_ruby_libs" >&6; }
+ SWIG_RB_LIBS="`
+ input_flags="$ac_cv_ruby_libs"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for rb_errinfo" >&5
+$as_echo_n "checking for rb_errinfo... " >&6; }
+ old_CFLAGS="$CFLAGS"
+ old_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $svn_cv_ruby_includes"
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-ansi//'`
+
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c89//'`
+
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c90//'`
+
+ LIBS="$SWIG_RB_LIBS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <ruby.h>
+int main()
+{rb_errinfo();}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ have_rb_errinfo="yes"
+else
+ have_rb_errinfo="no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ if test "$have_rb_errinfo" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+$as_echo "#define HAVE_RB_ERRINFO 1" >>confdefs.h
+
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ fi
+ CFLAGS="$old_CFLAGS"
+ LIBS="$old_LIBS"
+
+ if ${svn_cv_ruby_sitedir+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir="$rbconfig_sitedir"
+
+fi
+
+
+# Check whether --with-ruby-sitedir was given.
+if test "${with_ruby_sitedir+set}" = set; then :
+ withval=$with_ruby_sitedir; svn_ruby_installdir="$withval"
+else
+ svn_ruby_installdir="$svn_cv_ruby_sitedir"
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby scripts" >&5
+$as_echo_n "checking where to install Ruby scripts... " >&6; }
+ if ${svn_cv_ruby_sitedir_libsuffix+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir_libsuffix="`echo "$rbconfig_sitelibdir" | \
+ $SED -e "s,^$rbconfig_sitedir,,"`"
+
+fi
+
+ SWIG_RB_SITE_LIB_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_libsuffix}"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_LIB_DIR" >&5
+$as_echo "$SWIG_RB_SITE_LIB_DIR" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby extensions" >&5
+$as_echo_n "checking where to install Ruby extensions... " >&6; }
+ if ${svn_cv_ruby_sitedir_archsuffix+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir_archsuffix="`echo "$rbconfig_sitearchdir" | \
+ $SED -e "s,^$rbconfig_sitedir,,"`"
+
+fi
+
+ SWIG_RB_SITE_ARCH_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_archsuffix}"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_ARCH_DIR" >&5
+$as_echo "$SWIG_RB_SITE_ARCH_DIR" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to use output level for Ruby bindings tests" >&5
+$as_echo_n "checking how to use output level for Ruby bindings tests... " >&6; }
+ if ${svn_cv_ruby_test_verbose+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_test_verbose="normal"
+
+fi
+
+
+# Check whether --with-ruby-test-verbose was given.
+if test "${with_ruby_test_verbose+set}" = set; then :
+ withval=$with_ruby_test_verbose; svn_ruby_test_verbose="$withval"
+else
+ svn_ruby_test_verbose="$svn_cv_ruby_test_verbose"
+fi
+
+ SWIG_RB_TEST_VERBOSE="$svn_ruby_test_verbose"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_TEST_VERBOSE" >&5
+$as_echo "$SWIG_RB_TEST_VERBOSE" >&6; }
+ fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ;;
+ *)
+
+ where=$withval
+
+ if test $where = no; then
+ SWIG=none
+ elif test $where = check; then
+ # Extract the first word of "swig", so it can be a program name with args.
+set dummy swig; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_SWIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $SWIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_SWIG="$SWIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_SWIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_SWIG" && ac_cv_path_SWIG="none"
+ ;;
+esac
+fi
+SWIG=$ac_cv_path_SWIG
+if test -n "$SWIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG" >&5
+$as_echo "$SWIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ else
+ if test -f "$where"; then
+ SWIG="$where"
+ else
+ SWIG="$where/bin/swig"
+ fi
+ if test ! -f "$SWIG" || test ! -x "$SWIG"; then
+ as_fn_error $? "Could not find swig binary at $SWIG" "$LINENO" 5
+ fi
+ fi
+
+ if test "$SWIG" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking swig version" >&5
+$as_echo_n "checking swig version... " >&6; }
+ SWIG_VERSION_RAW="`$SWIG -version 2>&1 | \
+ $SED -ne 's/^.*Version \(.*\)$/\1/p'`"
+ # We want the version as an integer so we can test against
+ # which version we're using. SWIG doesn't provide this
+ # to us so we have to come up with it on our own.
+ # The major is passed straight through,
+ # the minor is zero padded to two places,
+ # and the patch level is zero padded to three places.
+ # e.g. 1.3.24 becomes 103024
+ SWIG_VERSION="`echo \"$SWIG_VERSION_RAW\" | \
+ $SED -e 's/[^0-9\.].*$//' \
+ -e 's/\.\([0-9]\)$/.0\1/' \
+ -e 's/\.\([0-9][0-9]\)$/.0\1/' \
+ -e 's/\.\([0-9]\)\./0\1/; s/\.//g;'`"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_VERSION_RAW" >&5
+$as_echo "$SWIG_VERSION_RAW" >&6; }
+ # If you change the required swig version number, don't forget to update:
+ # subversion/bindings/swig/INSTALL
+ # packages/rpm/redhat-8+/subversion.spec
+ # packages/rpm/redhat-7.x/subversion.spec
+ # packages/rpm/rhel-3/subversion.spec
+ # packages/rpm/rhel-4/subversion.spec
+ if test -n "$SWIG_VERSION" && test "$SWIG_VERSION" -ge "103024"; then
+ SWIG_SUITABLE=yes
+ else
+ SWIG_SUITABLE=no
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&5
+$as_echo "$as_me: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Subversion requires SWIG 1.3.24 or later" >&5
+$as_echo "$as_me: WARNING: Subversion requires SWIG 1.3.24 or later" >&2;}
+ fi
+ fi
+
+ SWIG_PY_COMPILE="none"
+ SWIG_PY_LINK="none"
+ if test "$PYTHON" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring python swig binding" >&5
+$as_echo "$as_me: Configuring python swig binding" >&6;}
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python includes" >&5
+$as_echo_n "checking for Python includes... " >&6; }
+if ${ac_cv_python_includes+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_includes="`$PYTHON ${abs_srcdir}/build/get-py-info.py --includes`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_includes" >&5
+$as_echo "$ac_cv_python_includes" >&6; }
+ SWIG_PY_INCLUDES="\$(SWIG_INCLUDES) $ac_cv_python_includes"
+
+ if test "$ac_cv_python_includes" = "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: python bindings cannot be built without distutils module" >&5
+$as_echo "$as_me: WARNING: python bindings cannot be built without distutils module" >&2;}
+ fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiling Python extensions" >&5
+$as_echo_n "checking for compiling Python extensions... " >&6; }
+if ${ac_cv_python_compile+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_compile="`$PYTHON ${abs_srcdir}/build/get-py-info.py --compile`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_compile" >&5
+$as_echo "$ac_cv_python_compile" >&6; }
+ SWIG_PY_COMPILE="$ac_cv_python_compile $CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python extensions" >&5
+$as_echo_n "checking for linking Python extensions... " >&6; }
+if ${ac_cv_python_link+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_link="`$PYTHON ${abs_srcdir}/build/get-py-info.py --link`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_link" >&5
+$as_echo "$ac_cv_python_link" >&6; }
+ SWIG_PY_LINK="$ac_cv_python_link"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python libraries" >&5
+$as_echo_n "checking for linking Python libraries... " >&6; }
+if ${ac_cv_python_libs+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_libs="`$PYTHON ${abs_srcdir}/build/get-py-info.py --libs`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_libs" >&5
+$as_echo "$ac_cv_python_libs" >&6; }
+ SWIG_PY_LIBS="`
+ input_flags="$ac_cv_python_libs"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ SVN_PYCFMT_SAVE_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_int64_t Python/C API format string" >&5
+$as_echo_n "checking for apr_int64_t Python/C API format string... " >&6; }
+if ${svn_cv_pycfmt_apr_int64_t+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"lld\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="L"
+fi
+rm -f conftest*
+
+ fi
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+r
+ #include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"ld\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="l"
+fi
+rm -f conftest*
+
+ fi
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"d\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="i"
+fi
+rm -f conftest*
+
+ fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_pycfmt_apr_int64_t" >&5
+$as_echo "$svn_cv_pycfmt_apr_int64_t" >&6; }
+ CPPFLAGS="$SVN_PYCFMT_SAVE_CPPFLAGS"
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ as_fn_error $? "failed to recognize APR_INT64_T_FMT on this platform" "$LINENO" 5
+ fi
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_APR_INT64_T_PYCFMT "$svn_cv_pycfmt_apr_int64_t"
+_ACEOF
+
+ fi
+
+ if test "$PERL" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking perl version" >&5
+$as_echo_n "checking perl version... " >&6; }
+ PERL_VERSION="`$PERL -e 'q([); print $] * 1000000,$/;'`"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL_VERSION" >&5
+$as_echo "$PERL_VERSION" >&6; }
+ if test "$PERL_VERSION" -ge "5008000"; then
+ SWIG_PL_INCLUDES="\$(SWIG_INCLUDES) `$PERL -MExtUtils::Embed -e ccopts`"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: perl bindings require perl 5.8.0 or newer." >&5
+$as_echo "$as_me: WARNING: perl bindings require perl 5.8.0 or newer." >&2;}
+ fi
+ fi
+
+ SWIG_RB_COMPILE="none"
+ SWIG_RB_LINK="none"
+ if test "$RUBY" != "none"; then
+ rbconfig="$RUBY -rrbconfig -e "
+
+ for var_name in arch archdir CC LDSHARED DLEXT LIBS LIBRUBYARG \
+ rubyhdrdir sitedir sitelibdir sitearchdir libdir
+ do
+ rbconfig_tmp=`$rbconfig "print RbConfig::CONFIG['$var_name']"`
+ eval "rbconfig_$var_name=\"$rbconfig_tmp\""
+ done
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring Ruby SWIG binding" >&5
+$as_echo "$as_me: Configuring Ruby SWIG binding" >&6;}
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby include path" >&5
+$as_echo_n "checking for Ruby include path... " >&6; }
+if ${svn_cv_ruby_includes+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ if test -d "$rbconfig_rubyhdrdir"; then
+ svn_cv_ruby_includes="-I. -I$rbconfig_rubyhdrdir -I$rbconfig_rubyhdrdir/ruby -I$rbconfig_rubyhdrdir/ruby/backward -I$rbconfig_rubyhdrdir/$rbconfig_arch"
+ else
+ svn_cv_ruby_includes="-I. -I$rbconfig_archdir"
+ fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_includes" >&5
+$as_echo "$svn_cv_ruby_includes" >&6; }
+ SWIG_RB_INCLUDES="\$(SWIG_INCLUDES) $svn_cv_ruby_includes"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to compile Ruby extensions" >&5
+$as_echo_n "checking how to compile Ruby extensions... " >&6; }
+if ${svn_cv_ruby_compile+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_compile="$rbconfig_CC $CFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_compile" >&5
+$as_echo "$svn_cv_ruby_compile" >&6; }
+ SWIG_RB_COMPILE="$svn_cv_ruby_compile"
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-ansi//'`
+
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c89//'`
+
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c90//'`
+
+ SWIG_RB_COMPILE="$SWIG_RB_COMPILE -Wno-int-to-pointer-cast"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby extensions" >&5
+$as_echo_n "checking how to link Ruby extensions... " >&6; }
+if ${svn_cv_ruby_link+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_link="`$RUBY -e 'ARGV.shift; print ARGV.join(%q( ))' \
+ $rbconfig_LDSHARED`"
+ svn_cv_ruby_link="$rbconfig_CC $svn_cv_ruby_link"
+ svn_cv_ruby_link="$svn_cv_ruby_link -shrext .$rbconfig_DLEXT"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_link" >&5
+$as_echo "$svn_cv_ruby_link" >&6; }
+ SWIG_RB_LINK="$svn_cv_ruby_link"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby libraries" >&5
+$as_echo_n "checking how to link Ruby libraries... " >&6; }
+if ${ac_cv_ruby_libs+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_ruby_libs="$rbconfig_LIBRUBYARG $rbconfig_LIBS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_ruby_libs" >&5
+$as_echo "$ac_cv_ruby_libs" >&6; }
+ SWIG_RB_LIBS="`
+ input_flags="$ac_cv_ruby_libs"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for rb_errinfo" >&5
+$as_echo_n "checking for rb_errinfo... " >&6; }
+ old_CFLAGS="$CFLAGS"
+ old_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $svn_cv_ruby_includes"
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-ansi//'`
+
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c89//'`
+
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c90//'`
+
+ LIBS="$SWIG_RB_LIBS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <ruby.h>
+int main()
+{rb_errinfo();}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ have_rb_errinfo="yes"
+else
+ have_rb_errinfo="no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ if test "$have_rb_errinfo" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+$as_echo "#define HAVE_RB_ERRINFO 1" >>confdefs.h
+
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ fi
+ CFLAGS="$old_CFLAGS"
+ LIBS="$old_LIBS"
+
+ if ${svn_cv_ruby_sitedir+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir="$rbconfig_sitedir"
+
+fi
+
+
+# Check whether --with-ruby-sitedir was given.
+if test "${with_ruby_sitedir+set}" = set; then :
+ withval=$with_ruby_sitedir; svn_ruby_installdir="$withval"
+else
+ svn_ruby_installdir="$svn_cv_ruby_sitedir"
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby scripts" >&5
+$as_echo_n "checking where to install Ruby scripts... " >&6; }
+ if ${svn_cv_ruby_sitedir_libsuffix+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir_libsuffix="`echo "$rbconfig_sitelibdir" | \
+ $SED -e "s,^$rbconfig_sitedir,,"`"
+
+fi
+
+ SWIG_RB_SITE_LIB_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_libsuffix}"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_LIB_DIR" >&5
+$as_echo "$SWIG_RB_SITE_LIB_DIR" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby extensions" >&5
+$as_echo_n "checking where to install Ruby extensions... " >&6; }
+ if ${svn_cv_ruby_sitedir_archsuffix+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir_archsuffix="`echo "$rbconfig_sitearchdir" | \
+ $SED -e "s,^$rbconfig_sitedir,,"`"
+
+fi
+
+ SWIG_RB_SITE_ARCH_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_archsuffix}"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_ARCH_DIR" >&5
+$as_echo "$SWIG_RB_SITE_ARCH_DIR" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to use output level for Ruby bindings tests" >&5
+$as_echo_n "checking how to use output level for Ruby bindings tests... " >&6; }
+ if ${svn_cv_ruby_test_verbose+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_test_verbose="normal"
+
+fi
+
+
+# Check whether --with-ruby-test-verbose was given.
+if test "${with_ruby_test_verbose+set}" = set; then :
+ withval=$with_ruby_test_verbose; svn_ruby_test_verbose="$withval"
+else
+ svn_ruby_test_verbose="$svn_cv_ruby_test_verbose"
+fi
+
+ SWIG_RB_TEST_VERBOSE="$svn_ruby_test_verbose"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_TEST_VERBOSE" >&5
+$as_echo "$SWIG_RB_TEST_VERBOSE" >&6; }
+ fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ;;
+ esac
+
+else
+
+
+ where=check
+
+ if test $where = no; then
+ SWIG=none
+ elif test $where = check; then
+ # Extract the first word of "swig", so it can be a program name with args.
+set dummy swig; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_SWIG+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $SWIG in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_SWIG="$SWIG" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_SWIG="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_SWIG" && ac_cv_path_SWIG="none"
+ ;;
+esac
+fi
+SWIG=$ac_cv_path_SWIG
+if test -n "$SWIG"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG" >&5
+$as_echo "$SWIG" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ else
+ if test -f "$where"; then
+ SWIG="$where"
+ else
+ SWIG="$where/bin/swig"
+ fi
+ if test ! -f "$SWIG" || test ! -x "$SWIG"; then
+ as_fn_error $? "Could not find swig binary at $SWIG" "$LINENO" 5
+ fi
+ fi
+
+ if test "$SWIG" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking swig version" >&5
+$as_echo_n "checking swig version... " >&6; }
+ SWIG_VERSION_RAW="`$SWIG -version 2>&1 | \
+ $SED -ne 's/^.*Version \(.*\)$/\1/p'`"
+ # We want the version as an integer so we can test against
+ # which version we're using. SWIG doesn't provide this
+ # to us so we have to come up with it on our own.
+ # The major is passed straight through,
+ # the minor is zero padded to two places,
+ # and the patch level is zero padded to three places.
+ # e.g. 1.3.24 becomes 103024
+ SWIG_VERSION="`echo \"$SWIG_VERSION_RAW\" | \
+ $SED -e 's/[^0-9\.].*$//' \
+ -e 's/\.\([0-9]\)$/.0\1/' \
+ -e 's/\.\([0-9][0-9]\)$/.0\1/' \
+ -e 's/\.\([0-9]\)\./0\1/; s/\.//g;'`"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_VERSION_RAW" >&5
+$as_echo "$SWIG_VERSION_RAW" >&6; }
+ # If you change the required swig version number, don't forget to update:
+ # subversion/bindings/swig/INSTALL
+ # packages/rpm/redhat-8+/subversion.spec
+ # packages/rpm/redhat-7.x/subversion.spec
+ # packages/rpm/rhel-3/subversion.spec
+ # packages/rpm/rhel-4/subversion.spec
+ if test -n "$SWIG_VERSION" && test "$SWIG_VERSION" -ge "103024"; then
+ SWIG_SUITABLE=yes
+ else
+ SWIG_SUITABLE=no
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&5
+$as_echo "$as_me: WARNING: Detected SWIG version $SWIG_VERSION_RAW" >&2;}
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Subversion requires SWIG 1.3.24 or later" >&5
+$as_echo "$as_me: WARNING: Subversion requires SWIG 1.3.24 or later" >&2;}
+ fi
+ fi
+
+ SWIG_PY_COMPILE="none"
+ SWIG_PY_LINK="none"
+ if test "$PYTHON" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring python swig binding" >&5
+$as_echo "$as_me: Configuring python swig binding" >&6;}
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python includes" >&5
+$as_echo_n "checking for Python includes... " >&6; }
+if ${ac_cv_python_includes+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_includes="`$PYTHON ${abs_srcdir}/build/get-py-info.py --includes`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_includes" >&5
+$as_echo "$ac_cv_python_includes" >&6; }
+ SWIG_PY_INCLUDES="\$(SWIG_INCLUDES) $ac_cv_python_includes"
+
+ if test "$ac_cv_python_includes" = "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: python bindings cannot be built without distutils module" >&5
+$as_echo "$as_me: WARNING: python bindings cannot be built without distutils module" >&2;}
+ fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiling Python extensions" >&5
+$as_echo_n "checking for compiling Python extensions... " >&6; }
+if ${ac_cv_python_compile+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_compile="`$PYTHON ${abs_srcdir}/build/get-py-info.py --compile`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_compile" >&5
+$as_echo "$ac_cv_python_compile" >&6; }
+ SWIG_PY_COMPILE="$ac_cv_python_compile $CFLAGS"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python extensions" >&5
+$as_echo_n "checking for linking Python extensions... " >&6; }
+if ${ac_cv_python_link+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_link="`$PYTHON ${abs_srcdir}/build/get-py-info.py --link`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_link" >&5
+$as_echo "$ac_cv_python_link" >&6; }
+ SWIG_PY_LINK="$ac_cv_python_link"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for linking Python libraries" >&5
+$as_echo_n "checking for linking Python libraries... " >&6; }
+if ${ac_cv_python_libs+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_python_libs="`$PYTHON ${abs_srcdir}/build/get-py-info.py --libs`"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_python_libs" >&5
+$as_echo "$ac_cv_python_libs" >&6; }
+ SWIG_PY_LIBS="`
+ input_flags="$ac_cv_python_libs"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ SVN_PYCFMT_SAVE_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr_int64_t Python/C API format string" >&5
+$as_echo_n "checking for apr_int64_t Python/C API format string... " >&6; }
+if ${svn_cv_pycfmt_apr_int64_t+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"lld\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="L"
+fi
+rm -f conftest*
+
+ fi
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+r
+ #include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"ld\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="l"
+fi
+rm -f conftest*
+
+ fi
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <apr.h>
+ MaTcHtHiS APR_INT64_T_FMT EnDeNd
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ $EGREP "MaTcHtHiS +\"d\" +EnDeNd" >/dev/null 2>&1; then :
+ svn_cv_pycfmt_apr_int64_t="i"
+fi
+rm -f conftest*
+
+ fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_pycfmt_apr_int64_t" >&5
+$as_echo "$svn_cv_pycfmt_apr_int64_t" >&6; }
+ CPPFLAGS="$SVN_PYCFMT_SAVE_CPPFLAGS"
+ if test "x$svn_cv_pycfmt_apr_int64_t" = "x"; then
+ as_fn_error $? "failed to recognize APR_INT64_T_FMT on this platform" "$LINENO" 5
+ fi
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_APR_INT64_T_PYCFMT "$svn_cv_pycfmt_apr_int64_t"
+_ACEOF
+
+ fi
+
+ if test "$PERL" != "none"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking perl version" >&5
+$as_echo_n "checking perl version... " >&6; }
+ PERL_VERSION="`$PERL -e 'q([); print $] * 1000000,$/;'`"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL_VERSION" >&5
+$as_echo "$PERL_VERSION" >&6; }
+ if test "$PERL_VERSION" -ge "5008000"; then
+ SWIG_PL_INCLUDES="\$(SWIG_INCLUDES) `$PERL -MExtUtils::Embed -e ccopts`"
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: perl bindings require perl 5.8.0 or newer." >&5
+$as_echo "$as_me: WARNING: perl bindings require perl 5.8.0 or newer." >&2;}
+ fi
+ fi
+
+ SWIG_RB_COMPILE="none"
+ SWIG_RB_LINK="none"
+ if test "$RUBY" != "none"; then
+ rbconfig="$RUBY -rrbconfig -e "
+
+ for var_name in arch archdir CC LDSHARED DLEXT LIBS LIBRUBYARG \
+ rubyhdrdir sitedir sitelibdir sitearchdir libdir
+ do
+ rbconfig_tmp=`$rbconfig "print RbConfig::CONFIG['$var_name']"`
+ eval "rbconfig_$var_name=\"$rbconfig_tmp\""
+ done
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: Configuring Ruby SWIG binding" >&5
+$as_echo "$as_me: Configuring Ruby SWIG binding" >&6;}
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Ruby include path" >&5
+$as_echo_n "checking for Ruby include path... " >&6; }
+if ${svn_cv_ruby_includes+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ if test -d "$rbconfig_rubyhdrdir"; then
+ svn_cv_ruby_includes="-I. -I$rbconfig_rubyhdrdir -I$rbconfig_rubyhdrdir/ruby -I$rbconfig_rubyhdrdir/ruby/backward -I$rbconfig_rubyhdrdir/$rbconfig_arch"
+ else
+ svn_cv_ruby_includes="-I. -I$rbconfig_archdir"
+ fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_includes" >&5
+$as_echo "$svn_cv_ruby_includes" >&6; }
+ SWIG_RB_INCLUDES="\$(SWIG_INCLUDES) $svn_cv_ruby_includes"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to compile Ruby extensions" >&5
+$as_echo_n "checking how to compile Ruby extensions... " >&6; }
+if ${svn_cv_ruby_compile+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_compile="$rbconfig_CC $CFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_compile" >&5
+$as_echo "$svn_cv_ruby_compile" >&6; }
+ SWIG_RB_COMPILE="$svn_cv_ruby_compile"
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-ansi//'`
+
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c89//'`
+
+
+ SWIG_RB_COMPILE=`echo "$SWIG_RB_COMPILE" | $SED -e 's/-std=c90//'`
+
+ SWIG_RB_COMPILE="$SWIG_RB_COMPILE -Wno-int-to-pointer-cast"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby extensions" >&5
+$as_echo_n "checking how to link Ruby extensions... " >&6; }
+if ${svn_cv_ruby_link+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_link="`$RUBY -e 'ARGV.shift; print ARGV.join(%q( ))' \
+ $rbconfig_LDSHARED`"
+ svn_cv_ruby_link="$rbconfig_CC $svn_cv_ruby_link"
+ svn_cv_ruby_link="$svn_cv_ruby_link -shrext .$rbconfig_DLEXT"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $svn_cv_ruby_link" >&5
+$as_echo "$svn_cv_ruby_link" >&6; }
+ SWIG_RB_LINK="$svn_cv_ruby_link"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link Ruby libraries" >&5
+$as_echo_n "checking how to link Ruby libraries... " >&6; }
+if ${ac_cv_ruby_libs+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ ac_cv_ruby_libs="$rbconfig_LIBRUBYARG $rbconfig_LIBS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_ruby_libs" >&5
+$as_echo "$ac_cv_ruby_libs" >&6; }
+ SWIG_RB_LIBS="`
+ input_flags="$ac_cv_ruby_libs"
+ output_flags=""
+ filtered_dirs="/lib /lib64 /usr/lib /usr/lib64"
+ for flag in $input_flags; do
+ filter="no"
+ for dir in $filtered_dirs; do
+ if test "$flag" = "-L$dir" || test "$flag" = "-L$dir/"; then
+ filter="yes"
+ break
+ fi
+ done
+ if test "$filter" = "no"; then
+ output_flags="$output_flags $flag"
+ fi
+ done
+ if test -n "$output_flags"; then
+ printf "%s" "${output_flags# }"
+ fi
+`"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for rb_errinfo" >&5
+$as_echo_n "checking for rb_errinfo... " >&6; }
+ old_CFLAGS="$CFLAGS"
+ old_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $svn_cv_ruby_includes"
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-ansi//'`
+
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c89//'`
+
+
+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-std=c90//'`
+
+ LIBS="$SWIG_RB_LIBS"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <ruby.h>
+int main()
+{rb_errinfo();}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ have_rb_errinfo="yes"
+else
+ have_rb_errinfo="no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+ if test "$have_rb_errinfo" = "yes"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+$as_echo "#define HAVE_RB_ERRINFO 1" >>confdefs.h
+
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ fi
+ CFLAGS="$old_CFLAGS"
+ LIBS="$old_LIBS"
+
+ if ${svn_cv_ruby_sitedir+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir="$rbconfig_sitedir"
+
+fi
+
+
+# Check whether --with-ruby-sitedir was given.
+if test "${with_ruby_sitedir+set}" = set; then :
+ withval=$with_ruby_sitedir; svn_ruby_installdir="$withval"
+else
+ svn_ruby_installdir="$svn_cv_ruby_sitedir"
+fi
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby scripts" >&5
+$as_echo_n "checking where to install Ruby scripts... " >&6; }
+ if ${svn_cv_ruby_sitedir_libsuffix+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir_libsuffix="`echo "$rbconfig_sitelibdir" | \
+ $SED -e "s,^$rbconfig_sitedir,,"`"
+
+fi
+
+ SWIG_RB_SITE_LIB_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_libsuffix}"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_LIB_DIR" >&5
+$as_echo "$SWIG_RB_SITE_LIB_DIR" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking where to install Ruby extensions" >&5
+$as_echo_n "checking where to install Ruby extensions... " >&6; }
+ if ${svn_cv_ruby_sitedir_archsuffix+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_sitedir_archsuffix="`echo "$rbconfig_sitearchdir" | \
+ $SED -e "s,^$rbconfig_sitedir,,"`"
+
+fi
+
+ SWIG_RB_SITE_ARCH_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_archsuffix}"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_SITE_ARCH_DIR" >&5
+$as_echo "$SWIG_RB_SITE_ARCH_DIR" >&6; }
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to use output level for Ruby bindings tests" >&5
+$as_echo_n "checking how to use output level for Ruby bindings tests... " >&6; }
+ if ${svn_cv_ruby_test_verbose+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+ svn_cv_ruby_test_verbose="normal"
+
+fi
+
+
+# Check whether --with-ruby-test-verbose was given.
+if test "${with_ruby_test_verbose+set}" = set; then :
+ withval=$with_ruby_test_verbose; svn_ruby_test_verbose="$withval"
+else
+ svn_ruby_test_verbose="$svn_cv_ruby_test_verbose"
+fi
+
+ SWIG_RB_TEST_VERBOSE="$svn_ruby_test_verbose"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SWIG_RB_TEST_VERBOSE" >&5
+$as_echo "$SWIG_RB_TEST_VERBOSE" >&6; }
+ fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+fi
+
+
+
+
+
+# Check whether --with-ctypesgen was given.
+if test "${with_ctypesgen+set}" = set; then :
+ withval=$with_ctypesgen;
+ case "$withval" in
+ "no")
+
+ where=no
+
+ CTYPESGEN=none
+
+ if test $where = check; then
+ # Extract the first word of ""ctypesgen.py"", so it can be a program name with args.
+set dummy "ctypesgen.py"; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_CTYPESGEN+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $CTYPESGEN in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_CTYPESGEN="$CTYPESGEN" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_CTYPESGEN="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_CTYPESGEN" && ac_cv_path_CTYPESGEN="none"
+ ;;
+esac
+fi
+CTYPESGEN=$ac_cv_path_CTYPESGEN
+if test -n "$CTYPESGEN"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5
+$as_echo "$CTYPESGEN" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ elif test $where != no; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ctypesgen.py" >&5
+$as_echo_n "checking for ctypesgen.py... " >&6; }
+
+ if test -f "$where"; then
+ CTYPESGEN="$where"
+ elif test -f "$where/bin/ctypesgen.py"; then
+ CTYPESGEN="$where/bin/ctypesgen.py"
+ else
+ CTYPESGEN="$where/ctypesgen.py"
+ fi
+
+ if test ! -f "$CTYPESGEN" || test ! -x "$CTYPESGEN"; then
+ as_fn_error $? "Could not find ctypesgen at $where/ctypesgen.py or at
+ $where/bin/ctypesgen.py" "$LINENO" 5
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5
+$as_echo "$CTYPESGEN" >&6; }
+ fi
+ fi
+
+
+
+ ;;
+ "yes")
+
+ where=check
+
+ CTYPESGEN=none
+
+ if test $where = check; then
+ # Extract the first word of ""ctypesgen.py"", so it can be a program name with args.
+set dummy "ctypesgen.py"; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_CTYPESGEN+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $CTYPESGEN in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_CTYPESGEN="$CTYPESGEN" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_CTYPESGEN="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_CTYPESGEN" && ac_cv_path_CTYPESGEN="none"
+ ;;
+esac
+fi
+CTYPESGEN=$ac_cv_path_CTYPESGEN
+if test -n "$CTYPESGEN"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5
+$as_echo "$CTYPESGEN" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ elif test $where != no; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ctypesgen.py" >&5
+$as_echo_n "checking for ctypesgen.py... " >&6; }
+
+ if test -f "$where"; then
+ CTYPESGEN="$where"
+ elif test -f "$where/bin/ctypesgen.py"; then
+ CTYPESGEN="$where/bin/ctypesgen.py"
+ else
+ CTYPESGEN="$where/ctypesgen.py"
+ fi
+
+ if test ! -f "$CTYPESGEN" || test ! -x "$CTYPESGEN"; then
+ as_fn_error $? "Could not find ctypesgen at $where/ctypesgen.py or at
+ $where/bin/ctypesgen.py" "$LINENO" 5
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5
+$as_echo "$CTYPESGEN" >&6; }
+ fi
+ fi
+
+
+
+ ;;
+ *)
+
+ where=$withval
+
+ CTYPESGEN=none
+
+ if test $where = check; then
+ # Extract the first word of ""ctypesgen.py"", so it can be a program name with args.
+set dummy "ctypesgen.py"; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_CTYPESGEN+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $CTYPESGEN in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_CTYPESGEN="$CTYPESGEN" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_CTYPESGEN="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_CTYPESGEN" && ac_cv_path_CTYPESGEN="none"
+ ;;
+esac
+fi
+CTYPESGEN=$ac_cv_path_CTYPESGEN
+if test -n "$CTYPESGEN"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5
+$as_echo "$CTYPESGEN" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ elif test $where != no; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ctypesgen.py" >&5
+$as_echo_n "checking for ctypesgen.py... " >&6; }
+
+ if test -f "$where"; then
+ CTYPESGEN="$where"
+ elif test -f "$where/bin/ctypesgen.py"; then
+ CTYPESGEN="$where/bin/ctypesgen.py"
+ else
+ CTYPESGEN="$where/ctypesgen.py"
+ fi
+
+ if test ! -f "$CTYPESGEN" || test ! -x "$CTYPESGEN"; then
+ as_fn_error $? "Could not find ctypesgen at $where/ctypesgen.py or at
+ $where/bin/ctypesgen.py" "$LINENO" 5
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5
+$as_echo "$CTYPESGEN" >&6; }
+ fi
+ fi
+
+
+
+ ;;
+ esac
+
+else
+
+
+ where=check
+
+ CTYPESGEN=none
+
+ if test $where = check; then
+ # Extract the first word of ""ctypesgen.py"", so it can be a program name with args.
+set dummy "ctypesgen.py"; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_CTYPESGEN+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $CTYPESGEN in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_CTYPESGEN="$CTYPESGEN" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_CTYPESGEN="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_CTYPESGEN" && ac_cv_path_CTYPESGEN="none"
+ ;;
+esac
+fi
+CTYPESGEN=$ac_cv_path_CTYPESGEN
+if test -n "$CTYPESGEN"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5
+$as_echo "$CTYPESGEN" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ elif test $where != no; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ctypesgen.py" >&5
+$as_echo_n "checking for ctypesgen.py... " >&6; }
+
+ if test -f "$where"; then
+ CTYPESGEN="$where"
+ elif test -f "$where/bin/ctypesgen.py"; then
+ CTYPESGEN="$where/bin/ctypesgen.py"
+ else
+ CTYPESGEN="$where/ctypesgen.py"
+ fi
+
+ if test ! -f "$CTYPESGEN" || test ! -x "$CTYPESGEN"; then
+ as_fn_error $? "Could not find ctypesgen at $where/ctypesgen.py or at
+ $where/bin/ctypesgen.py" "$LINENO" 5
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CTYPESGEN" >&5
+$as_echo "$CTYPESGEN" >&6; }
+ fi
+ fi
+
+
+
+
+fi
+
+
+
+# Check whether --enable-runtime-module-search was given.
+if test "${enable_runtime_module_search+set}" = set; then :
+ enableval=$enable_runtime_module_search;
+ if test "$enableval" = "yes"; then
+ use_dso=yes
+ if test "$svn_enable_shared" = "no"; then
+ as_fn_error $? "--enable-runtime-module-search conflicts with --disable-shared" "$LINENO" 5
+ fi
+
+$as_echo "#define SVN_USE_DSO 1" >>confdefs.h
+
+ fi
+
+fi
+
+
+if test "$svn_enable_shared" = "no" || test "$use_dso" != "yes"; then
+
+$as_echo "#define SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL 1" >>confdefs.h
+
+ svn_ra_lib_deps="\$(RA_LOCAL_DEPS)"
+ svn_ra_lib_install_deps="install-ramod-lib"
+ svn_ra_lib_link="\$(RA_LOCAL_LINK)"
+
+
+$as_echo "#define SVN_LIBSVN_CLIENT_LINKS_RA_SVN 1" >>confdefs.h
+
+ svn_ra_lib_deps="$svn_ra_lib_deps \$(RA_SVN_DEPS)"
+ svn_ra_lib_link="$svn_ra_lib_link \$(RA_SVN_LINK)"
+
+ if test "$svn_lib_serf" = "yes"; then
+
+$as_echo "#define SVN_LIBSVN_CLIENT_LINKS_RA_SERF 1" >>confdefs.h
+
+ svn_ra_lib_deps="$svn_ra_lib_deps \$(RA_SERF_DEPS)"
+ svn_ra_lib_install_deps="$svn_ra_lib_install_deps install-serf-lib"
+ svn_ra_lib_link="$svn_ra_lib_link \$(RA_SERF_LINK)"
+ fi
+
+ SVN_RA_LIB_DEPS=$svn_ra_lib_deps
+ SVN_RA_LIB_INSTALL_DEPS=$svn_ra_lib_install_deps
+ SVN_RA_LIB_LINK=$svn_ra_lib_link
+
+
+$as_echo "#define SVN_LIBSVN_FS_LINKS_FS_FS 1" >>confdefs.h
+
+ svn_fs_lib_deps="\$(FS_FS_DEPS)"
+ svn_fs_lib_install_deps="install-fsmod-lib"
+ svn_fs_lib_link="\$(FS_FS_LINK)"
+
+ if test "$svn_lib_berkeley_db" = "yes"; then
+
+$as_echo "#define SVN_LIBSVN_FS_LINKS_FS_BASE 1" >>confdefs.h
+
+ svn_fs_lib_deps="$svn_fs_lib_deps \$(FS_BASE_DEPS)"
+ svn_fs_lib_install_deps="$svn_fs_lib_install_deps install-bdb-lib"
+ svn_fs_lib_link="$svn_fs_lib_link \$(FS_BASE_LINK)"
+ fi
+
+ SVN_FS_LIB_DEPS=$svn_fs_lib_deps
+ SVN_FS_LIB_INSTALL_DEPS=$svn_fs_lib_install_deps
+ SVN_FS_LIB_LINK=$svn_fs_lib_link
+fi
+
+
+
+
+
+
+
+
+# ==== JavaHL ================================================================
+
+do_javahl_build=no
+# Check whether --enable-javahl was given.
+if test "${enable_javahl+set}" = set; then :
+ enableval=$enable_javahl; if test "$enableval" = "yes" ; then
+ do_javahl_build="yes"
+ fi
+
+fi
+
+
+JAVAHL_OBJDIR=""
+INSTALL_EXTRA_JAVAHL_LIB=""
+FIX_JAVAHL_LIB=""
+JAVAHL_TESTS_TARGET=""
+JAVAHL_COMPAT_TESTS_TARGET=""
+LT_CXX_LIBADD=""
+if test "$do_javahl_build" = "yes"; then
+ if test "$JDK_SUITABLE" = "no"; then
+ as_fn_error $? "Cannot compile JavaHL without a suitable JDK.
+ Please specify a suitable JDK using the --with-jdk option." "$LINENO" 5
+ fi
+
+ JAVAHL_OBJDIR='$(libsvnjavahl_PATH)/.libs'
+
+ os_arch=`uname`
+ if test "$os_arch" = "Darwin"; then
+ INSTALL_EXTRA_JAVAHL_LIB='ln -sf $(libdir)/libsvnjavahl-1.dylib $(libdir)/libsvnjavahl-1.jnilib'
+ FIX_JAVAHL_LIB="ln -sf libsvnjavahl-1.dylib $JAVAHL_OBJDIR/libsvnjavahl-1.jnilib"
+ fi
+ # This segment (and the rest of r10800) is very likely unnecessary
+ # with libtool 1.5, which automatically adds libstdc++ as a
+ # dependency to the C++ libraries it builds. So at some future time
+ # when autogen.sh requires libtool 1.5 or higher, we can get rid of
+ # it.
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for additional flags to link C++ libraries" >&5
+$as_echo_n "checking for additional flags to link C++ libraries... " >&6; }
+ if test "x$ac_compiler_gnu" = "xyes"; then
+ LT_CXX_LIBADD="-lstdc++"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LT_CXX_LIBADD" >&5
+$as_echo "$LT_CXX_LIBADD" >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; }
+ fi
+fi
+
+
+
+
+
+
+# Check whether --with-junit was given.
+if test "${with_junit+set}" = set; then :
+ withval=$with_junit;
+ if test "$withval" != "no"; then
+ if test -n "$JAVA_CLASSPATH"; then
+ JAVA_CLASSPATH="$withval:$JAVA_CLASSPATH"
+ else
+ JAVA_CLASSPATH="$withval"
+ fi
+ JAVAHL_TESTS_TARGET="javahl-tests"
+ JAVAHL_COMPAT_TESTS_TARGET="javahl-compat-tests"
+ fi
+
+fi
+
+
+
+
+
+# ==== Miscellaneous bits ====================================================
+
+# Strip '-no-cpp-precomp' from CPPFLAGS for the clang compiler
+### I think we get this flag from APR, so the fix probably belongs there
+if test "$CC" = "clang"; then
+
+ CPPFLAGS=`echo "$CPPFLAGS" | $SED -e 's/-no-cpp-precomp //'`
+
+fi
+
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_PATH_LOCAL_SEPARATOR '/'
+_ACEOF
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_NULL_DEVICE_NAME "/dev/null"
+_ACEOF
+
+
+DEFAULT_FS_TYPE="fsfs"
+
+cat >>confdefs.h <<_ACEOF
+#define DEFAULT_FS_TYPE "$DEFAULT_FS_TYPE"
+_ACEOF
+
+
+DEFAULT_HTTP_LIBRARY="serf"
+
+cat >>confdefs.h <<_ACEOF
+#define DEFAULT_HTTP_LIBRARY "$DEFAULT_HTTP_LIBRARY"
+_ACEOF
+
+
+# BSD/OS (BSDi) needs to use a different include syntax in Makefile
+INCLUDE_OUTPUTS="include \$(top_srcdir)/build-outputs.mk"
+case "$host" in
+ *bsdi*)
+ # Check whether they've installed GNU make
+ if ! make --version > /dev/null 2>&1; then
+ # BSDi make
+ INCLUDE_OUTPUTS=".include \"\$(top_srcdir)/build-outputs.mk\""
+ fi
+ ;;
+esac
+
+
+# ==== Detection complete - output and run config.status =====================
+
+ac_config_headers="$ac_config_headers subversion/svn_private_config.h.tmp:subversion/svn_private_config.h.in"
+
+ac_config_commands="$ac_config_commands svn_private_config.h.tmp"
+
+ac_config_files="$ac_config_files Makefile"
+
+
+
+ SVN_CONFIG_SCRIPT_FILES="$SVN_CONFIG_SCRIPT_FILES tools/backup/hot-backup.py"
+ ac_config_files="$ac_config_files tools/backup/hot-backup.py"
+
+
+ SVN_CONFIG_SCRIPT_FILES="$SVN_CONFIG_SCRIPT_FILES tools/hook-scripts/commit-access-control.pl"
+ ac_config_files="$ac_config_files tools/hook-scripts/commit-access-control.pl"
+
+
+ SVN_CONFIG_SCRIPT_FILES="$SVN_CONFIG_SCRIPT_FILES subversion/bindings/swig/perl/native/Makefile.PL"
+ ac_config_files="$ac_config_files subversion/bindings/swig/perl/native/Makefile.PL"
+
+if test -e packages/solaris/pkginfo.in; then
+
+ SVN_CONFIG_SCRIPT_FILES="$SVN_CONFIG_SCRIPT_FILES packages/solaris/pkginfo"
+ ac_config_files="$ac_config_files packages/solaris/pkginfo"
+
+fi
+
+
+# Ensure that SWIG is checked after reconfiguration.
+rm -f .swig_checked
+
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_BUILD_HOST "${host}"
+_ACEOF
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SVN_BUILD_TARGET "${target}"
+_ACEOF
+
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems. If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+ for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+ eval ac_val=\$$ac_var
+ case $ac_val in #(
+ *${as_nl}*)
+ case $ac_var in #(
+ *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+ esac
+ case $ac_var in #(
+ _ | IFS | as_nl) ;; #(
+ BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+ *) { eval $ac_var=; unset $ac_var;} ;;
+ esac ;;
+ esac
+ done
+
+ (set) 2>&1 |
+ case $as_nl`(ac_space=' '; set) 2>&1` in #(
+ *${as_nl}ac_space=\ *)
+ # `set' does not quote correctly, so add quotes: double-quote
+ # substitution turns \\\\ into \\, and sed turns \\ into \.
+ sed -n \
+ "s/'/'\\\\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+ ;; #(
+ *)
+ # `set' quotes correctly as required by POSIX, so do not add quotes.
+ sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+ ;;
+ esac |
+ sort
+) |
+ sed '
+ /^ac_cv_env_/b end
+ t clear
+ :clear
+ s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+ t end
+ s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+ :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+ if test -w "$cache_file"; then
+ if test "x$cache_file" != "x/dev/null"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+$as_echo "$as_me: updating cache $cache_file" >&6;}
+ if test ! -f "$cache_file" || test -h "$cache_file"; then
+ cat confcache >"$cache_file"
+ else
+ case $cache_file in #(
+ */* | ?:*)
+ mv -f confcache "$cache_file"$$ &&
+ mv -f "$cache_file"$$ "$cache_file" ;; #(
+ *)
+ mv -f confcache "$cache_file" ;;
+ esac
+ fi
+ fi
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+ fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+DEFS=-DHAVE_CONFIG_H
+
+ac_libobjs=
+ac_ltlibobjs=
+U=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+ # 1. Remove the extension, and $U if already installed.
+ ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+ ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+ # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR
+ # will be set to the directory where LIBOBJS objects are built.
+ as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+ as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+
+: "${CONFIG_STATUS=./config.status}"
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+ emulate sh
+ NULLCMD=:
+ # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in #(
+ *posix*) :
+ set -o posix ;; #(
+ *) :
+ ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='print -r --'
+ as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+ as_echo='printf %s\n'
+ as_echo_n='printf %s'
+else
+ if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+ as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+ as_echo_n='/usr/ucb/echo -n'
+ else
+ as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+ as_echo_n_body='eval
+ arg=$1;
+ case $arg in #(
+ *"$as_nl"*)
+ expr "X$arg" : "X\\(.*\\)$as_nl";
+ arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+ esac;
+ expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+ '
+ export as_echo_n_body
+ as_echo_n='sh -c $as_echo_n_body as_echo'
+ fi
+ export as_echo_body
+ as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ PATH_SEPARATOR=:
+ (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+ (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+ PATH_SEPARATOR=';'
+ }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order. Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" "" $as_nl"
+
+# Find who we are. Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+ done
+IFS=$as_save_IFS
+
+ ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+ as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+ $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+ exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there. '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+ as_status=$1; test $as_status -eq 0 && as_status=1
+ if test "$4"; then
+ as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+ fi
+ $as_echo "$as_me: error: $2" >&2
+ as_fn_exit $as_status
+} # as_fn_error
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+ return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+ set +e
+ as_fn_set_status $1
+ exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+ { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+ eval 'as_fn_append ()
+ {
+ eval $1+=\$2
+ }'
+else
+ as_fn_append ()
+ {
+ eval $1=\$$1\$2
+ }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+ eval 'as_fn_arith ()
+ {
+ as_val=$(( $* ))
+ }'
+else
+ as_fn_arith ()
+ {
+ as_val=`expr "$@" || test $? -eq 1`
+ }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+ test "X`expr 00001 : '.*\(...\)'`" = X001; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+ as_dirname=dirname
+else
+ as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\/\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+ case `echo 'xy\c'` in
+ *c*) ECHO_T=' ';; # ECHO_T is single tab character.
+ xy) ECHO_C='\c';;
+ *) echo `echo ksh88 bug on AIX 6.1` > /dev/null
+ ECHO_T=' ';;
+ esac;;
+*)
+ ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+ rm -f conf$$.dir/conf$$.file
+else
+ rm -f conf$$.dir
+ mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+ if ln -s conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s='ln -s'
+ # ... but there are two gotchas:
+ # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+ # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+ # In both cases, we have to default to `cp -pR'.
+ ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+ as_ln_s='cp -pR'
+ elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+ else
+ as_ln_s='cp -pR'
+ fi
+else
+ as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+ case $as_dir in #(
+ -*) as_dir=./$as_dir;;
+ esac
+ test -d "$as_dir" || eval $as_mkdir_p || {
+ as_dirs=
+ while :; do
+ case $as_dir in #(
+ *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+ *) as_qdir=$as_dir;;
+ esac
+ as_dirs="'$as_qdir' $as_dirs"
+ as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_dir" : 'X\(//\)[^/]' \| \
+ X"$as_dir" : 'X\(//\)$' \| \
+ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ test -d "$as_dir" && break
+ done
+ test -z "$as_dirs" || eval "mkdir $as_dirs"
+ } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p='mkdir -p "$as_dir"'
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+ test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by subversion $as_me 1.8.0, which was
+generated by GNU Autoconf 2.69. Invocation command line was
+
+ CONFIG_FILES = $CONFIG_FILES
+ CONFIG_HEADERS = $CONFIG_HEADERS
+ CONFIG_LINKS = $CONFIG_LINKS
+ CONFIG_COMMANDS = $CONFIG_COMMANDS
+ $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+case $ac_config_headers in *"
+"*) set x $ac_config_headers; shift; ac_config_headers=$*;;
+esac
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+config_headers="$ac_config_headers"
+config_commands="$ac_config_commands"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration. Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+ -h, --help print this help, then exit
+ -V, --version print version number and configuration settings, then exit
+ --config print configuration, then exit
+ -q, --quiet, --silent
+ do not print progress messages
+ -d, --debug don't remove temporary files
+ --recheck update $as_me by reconfiguring in the same conditions
+ --file=FILE[:TEMPLATE]
+ instantiate the configuration file FILE
+ --header=FILE[:TEMPLATE]
+ instantiate the configuration header FILE
+
+Configuration files:
+$config_files
+
+Configuration headers:
+$config_headers
+
+Configuration commands:
+$config_commands
+
+Report bugs to <http://subversion.apache.org/>."
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ac_cs_version="\\
+subversion config.status 1.8.0
+configured by $0, generated by GNU Autoconf 2.69,
+ with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+INSTALL='$INSTALL'
+AWK='$AWK'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+ case $1 in
+ --*=?*)
+ ac_option=`expr "X$1" : 'X\([^=]*\)='`
+ ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+ ac_shift=:
+ ;;
+ --*=)
+ ac_option=`expr "X$1" : 'X\([^=]*\)='`
+ ac_optarg=
+ ac_shift=:
+ ;;
+ *)
+ ac_option=$1
+ ac_optarg=$2
+ ac_shift=shift
+ ;;
+ esac
+
+ case $ac_option in
+ # Handling of the options.
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ ac_cs_recheck=: ;;
+ --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+ $as_echo "$ac_cs_version"; exit ;;
+ --config | --confi | --conf | --con | --co | --c )
+ $as_echo "$ac_cs_config"; exit ;;
+ --debug | --debu | --deb | --de | --d | -d )
+ debug=: ;;
+ --file | --fil | --fi | --f )
+ $ac_shift
+ case $ac_optarg in
+ *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ '') as_fn_error $? "missing file argument" ;;
+ esac
+ as_fn_append CONFIG_FILES " '$ac_optarg'"
+ ac_need_defaults=false;;
+ --header | --heade | --head | --hea )
+ $ac_shift
+ case $ac_optarg in
+ *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ as_fn_append CONFIG_HEADERS " '$ac_optarg'"
+ ac_need_defaults=false;;
+ --he | --h)
+ # Conflict between --help and --header
+ as_fn_error $? "ambiguous option: \`$1'
+Try \`$0 --help' for more information.";;
+ --help | --hel | -h )
+ $as_echo "$ac_cs_usage"; exit ;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil | --si | --s)
+ ac_cs_silent=: ;;
+
+ # This is an error.
+ -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+ *) as_fn_append ac_config_targets " $1"
+ ac_need_defaults=false ;;
+
+ esac
+ shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+ exec 6>/dev/null
+ ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+ set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+ shift
+ \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+ CONFIG_SHELL='$SHELL'
+ export CONFIG_SHELL
+ exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+ echo
+ sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+ $as_echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+#
+# INIT-COMMANDS
+#
+
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+sed_quote_subst='$sed_quote_subst'
+double_quote_subst='$double_quote_subst'
+delay_variable_subst='$delay_variable_subst'
+macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`'
+macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`'
+enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`'
+enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`'
+pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`'
+enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`'
+SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`'
+ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`'
+PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`'
+host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`'
+host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`'
+host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`'
+build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`'
+build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`'
+build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`'
+SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`'
+Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`'
+GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`'
+EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`'
+FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`'
+LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`'
+NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`'
+LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`'
+max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`'
+ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`'
+exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`'
+lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`'
+lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`'
+lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`'
+lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`'
+lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`'
+reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`'
+reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`'
+OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`'
+deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`'
+file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`'
+file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`'
+want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`'
+DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`'
+sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`'
+AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`'
+AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`'
+archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`'
+STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`'
+RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`'
+old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`'
+old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`'
+old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`'
+lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`'
+CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`'
+CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`'
+compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`'
+GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`'
+nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`'
+lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`'
+objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`'
+MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`'
+lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`'
+need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`'
+MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`'
+DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`'
+NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`'
+LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`'
+OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`'
+OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`'
+libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`'
+shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`'
+extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`'
+archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`'
+enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`'
+export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`'
+whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`'
+compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`'
+old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`'
+old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`'
+archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`'
+archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`'
+module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`'
+module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`'
+with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`'
+allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`'
+no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`'
+hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`'
+hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`'
+hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`'
+hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`'
+hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`'
+inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`'
+link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`'
+always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`'
+export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`'
+exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`'
+include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`'
+prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`'
+postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`'
+file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`'
+variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`'
+need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`'
+need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`'
+version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`'
+runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`'
+shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`'
+shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`'
+libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`'
+library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`'
+soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`'
+install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`'
+postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`'
+postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`'
+finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`'
+finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`'
+hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`'
+sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`'
+sys_lib_dlsearch_path_spec='`$ECHO "$sys_lib_dlsearch_path_spec" | $SED "$delay_single_quote_subst"`'
+hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`'
+enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`'
+enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`'
+enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`'
+old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`'
+striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`'
+compiler_lib_search_dirs='`$ECHO "$compiler_lib_search_dirs" | $SED "$delay_single_quote_subst"`'
+predep_objects='`$ECHO "$predep_objects" | $SED "$delay_single_quote_subst"`'
+postdep_objects='`$ECHO "$postdep_objects" | $SED "$delay_single_quote_subst"`'
+predeps='`$ECHO "$predeps" | $SED "$delay_single_quote_subst"`'
+postdeps='`$ECHO "$postdeps" | $SED "$delay_single_quote_subst"`'
+compiler_lib_search_path='`$ECHO "$compiler_lib_search_path" | $SED "$delay_single_quote_subst"`'
+LD_CXX='`$ECHO "$LD_CXX" | $SED "$delay_single_quote_subst"`'
+reload_flag_CXX='`$ECHO "$reload_flag_CXX" | $SED "$delay_single_quote_subst"`'
+reload_cmds_CXX='`$ECHO "$reload_cmds_CXX" | $SED "$delay_single_quote_subst"`'
+old_archive_cmds_CXX='`$ECHO "$old_archive_cmds_CXX" | $SED "$delay_single_quote_subst"`'
+compiler_CXX='`$ECHO "$compiler_CXX" | $SED "$delay_single_quote_subst"`'
+GCC_CXX='`$ECHO "$GCC_CXX" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_no_builtin_flag_CXX='`$ECHO "$lt_prog_compiler_no_builtin_flag_CXX" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_pic_CXX='`$ECHO "$lt_prog_compiler_pic_CXX" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_wl_CXX='`$ECHO "$lt_prog_compiler_wl_CXX" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_static_CXX='`$ECHO "$lt_prog_compiler_static_CXX" | $SED "$delay_single_quote_subst"`'
+lt_cv_prog_compiler_c_o_CXX='`$ECHO "$lt_cv_prog_compiler_c_o_CXX" | $SED "$delay_single_quote_subst"`'
+archive_cmds_need_lc_CXX='`$ECHO "$archive_cmds_need_lc_CXX" | $SED "$delay_single_quote_subst"`'
+enable_shared_with_static_runtimes_CXX='`$ECHO "$enable_shared_with_static_runtimes_CXX" | $SED "$delay_single_quote_subst"`'
+export_dynamic_flag_spec_CXX='`$ECHO "$export_dynamic_flag_spec_CXX" | $SED "$delay_single_quote_subst"`'
+whole_archive_flag_spec_CXX='`$ECHO "$whole_archive_flag_spec_CXX" | $SED "$delay_single_quote_subst"`'
+compiler_needs_object_CXX='`$ECHO "$compiler_needs_object_CXX" | $SED "$delay_single_quote_subst"`'
+old_archive_from_new_cmds_CXX='`$ECHO "$old_archive_from_new_cmds_CXX" | $SED "$delay_single_quote_subst"`'
+old_archive_from_expsyms_cmds_CXX='`$ECHO "$old_archive_from_expsyms_cmds_CXX" | $SED "$delay_single_quote_subst"`'
+archive_cmds_CXX='`$ECHO "$archive_cmds_CXX" | $SED "$delay_single_quote_subst"`'
+archive_expsym_cmds_CXX='`$ECHO "$archive_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`'
+module_cmds_CXX='`$ECHO "$module_cmds_CXX" | $SED "$delay_single_quote_subst"`'
+module_expsym_cmds_CXX='`$ECHO "$module_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`'
+with_gnu_ld_CXX='`$ECHO "$with_gnu_ld_CXX" | $SED "$delay_single_quote_subst"`'
+allow_undefined_flag_CXX='`$ECHO "$allow_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`'
+no_undefined_flag_CXX='`$ECHO "$no_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_flag_spec_CXX='`$ECHO "$hardcode_libdir_flag_spec_CXX" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_separator_CXX='`$ECHO "$hardcode_libdir_separator_CXX" | $SED "$delay_single_quote_subst"`'
+hardcode_direct_CXX='`$ECHO "$hardcode_direct_CXX" | $SED "$delay_single_quote_subst"`'
+hardcode_direct_absolute_CXX='`$ECHO "$hardcode_direct_absolute_CXX" | $SED "$delay_single_quote_subst"`'
+hardcode_minus_L_CXX='`$ECHO "$hardcode_minus_L_CXX" | $SED "$delay_single_quote_subst"`'
+hardcode_shlibpath_var_CXX='`$ECHO "$hardcode_shlibpath_var_CXX" | $SED "$delay_single_quote_subst"`'
+hardcode_automatic_CXX='`$ECHO "$hardcode_automatic_CXX" | $SED "$delay_single_quote_subst"`'
+inherit_rpath_CXX='`$ECHO "$inherit_rpath_CXX" | $SED "$delay_single_quote_subst"`'
+link_all_deplibs_CXX='`$ECHO "$link_all_deplibs_CXX" | $SED "$delay_single_quote_subst"`'
+always_export_symbols_CXX='`$ECHO "$always_export_symbols_CXX" | $SED "$delay_single_quote_subst"`'
+export_symbols_cmds_CXX='`$ECHO "$export_symbols_cmds_CXX" | $SED "$delay_single_quote_subst"`'
+exclude_expsyms_CXX='`$ECHO "$exclude_expsyms_CXX" | $SED "$delay_single_quote_subst"`'
+include_expsyms_CXX='`$ECHO "$include_expsyms_CXX" | $SED "$delay_single_quote_subst"`'
+prelink_cmds_CXX='`$ECHO "$prelink_cmds_CXX" | $SED "$delay_single_quote_subst"`'
+postlink_cmds_CXX='`$ECHO "$postlink_cmds_CXX" | $SED "$delay_single_quote_subst"`'
+file_list_spec_CXX='`$ECHO "$file_list_spec_CXX" | $SED "$delay_single_quote_subst"`'
+hardcode_action_CXX='`$ECHO "$hardcode_action_CXX" | $SED "$delay_single_quote_subst"`'
+compiler_lib_search_dirs_CXX='`$ECHO "$compiler_lib_search_dirs_CXX" | $SED "$delay_single_quote_subst"`'
+predep_objects_CXX='`$ECHO "$predep_objects_CXX" | $SED "$delay_single_quote_subst"`'
+postdep_objects_CXX='`$ECHO "$postdep_objects_CXX" | $SED "$delay_single_quote_subst"`'
+predeps_CXX='`$ECHO "$predeps_CXX" | $SED "$delay_single_quote_subst"`'
+postdeps_CXX='`$ECHO "$postdeps_CXX" | $SED "$delay_single_quote_subst"`'
+compiler_lib_search_path_CXX='`$ECHO "$compiler_lib_search_path_CXX" | $SED "$delay_single_quote_subst"`'
+
+LTCC='$LTCC'
+LTCFLAGS='$LTCFLAGS'
+compiler='$compiler_DEFAULT'
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+\$1
+_LTECHO_EOF'
+}
+
+# Quote evaled strings.
+for var in SHELL \
+ECHO \
+PATH_SEPARATOR \
+SED \
+GREP \
+EGREP \
+FGREP \
+LD \
+NM \
+LN_S \
+lt_SP2NL \
+lt_NL2SP \
+reload_flag \
+OBJDUMP \
+deplibs_check_method \
+file_magic_cmd \
+file_magic_glob \
+want_nocaseglob \
+DLLTOOL \
+sharedlib_from_linklib_cmd \
+AR \
+AR_FLAGS \
+archiver_list_spec \
+STRIP \
+RANLIB \
+CC \
+CFLAGS \
+compiler \
+lt_cv_sys_global_symbol_pipe \
+lt_cv_sys_global_symbol_to_cdecl \
+lt_cv_sys_global_symbol_to_c_name_address \
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \
+nm_file_list_spec \
+lt_prog_compiler_no_builtin_flag \
+lt_prog_compiler_pic \
+lt_prog_compiler_wl \
+lt_prog_compiler_static \
+lt_cv_prog_compiler_c_o \
+need_locks \
+MANIFEST_TOOL \
+DSYMUTIL \
+NMEDIT \
+LIPO \
+OTOOL \
+OTOOL64 \
+shrext_cmds \
+export_dynamic_flag_spec \
+whole_archive_flag_spec \
+compiler_needs_object \
+with_gnu_ld \
+allow_undefined_flag \
+no_undefined_flag \
+hardcode_libdir_flag_spec \
+hardcode_libdir_separator \
+exclude_expsyms \
+include_expsyms \
+file_list_spec \
+variables_saved_for_relink \
+libname_spec \
+library_names_spec \
+soname_spec \
+install_override_mode \
+finish_eval \
+old_striplib \
+striplib \
+compiler_lib_search_dirs \
+predep_objects \
+postdep_objects \
+predeps \
+postdeps \
+compiler_lib_search_path \
+LD_CXX \
+reload_flag_CXX \
+compiler_CXX \
+lt_prog_compiler_no_builtin_flag_CXX \
+lt_prog_compiler_pic_CXX \
+lt_prog_compiler_wl_CXX \
+lt_prog_compiler_static_CXX \
+lt_cv_prog_compiler_c_o_CXX \
+export_dynamic_flag_spec_CXX \
+whole_archive_flag_spec_CXX \
+compiler_needs_object_CXX \
+with_gnu_ld_CXX \
+allow_undefined_flag_CXX \
+no_undefined_flag_CXX \
+hardcode_libdir_flag_spec_CXX \
+hardcode_libdir_separator_CXX \
+exclude_expsyms_CXX \
+include_expsyms_CXX \
+file_list_spec_CXX \
+compiler_lib_search_dirs_CXX \
+predep_objects_CXX \
+postdep_objects_CXX \
+predeps_CXX \
+postdeps_CXX \
+compiler_lib_search_path_CXX; do
+ case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+ *[\\\\\\\`\\"\\\$]*)
+ eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\""
+ ;;
+ *)
+ eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+ ;;
+ esac
+done
+
+# Double-quote double-evaled strings.
+for var in reload_cmds \
+old_postinstall_cmds \
+old_postuninstall_cmds \
+old_archive_cmds \
+extract_expsyms_cmds \
+old_archive_from_new_cmds \
+old_archive_from_expsyms_cmds \
+archive_cmds \
+archive_expsym_cmds \
+module_cmds \
+module_expsym_cmds \
+export_symbols_cmds \
+prelink_cmds \
+postlink_cmds \
+postinstall_cmds \
+postuninstall_cmds \
+finish_cmds \
+sys_lib_search_path_spec \
+sys_lib_dlsearch_path_spec \
+reload_cmds_CXX \
+old_archive_cmds_CXX \
+old_archive_from_new_cmds_CXX \
+old_archive_from_expsyms_cmds_CXX \
+archive_cmds_CXX \
+archive_expsym_cmds_CXX \
+module_cmds_CXX \
+module_expsym_cmds_CXX \
+export_symbols_cmds_CXX \
+prelink_cmds_CXX \
+postlink_cmds_CXX; do
+ case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+ *[\\\\\\\`\\"\\\$]*)
+ eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\""
+ ;;
+ *)
+ eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+ ;;
+ esac
+done
+
+ac_aux_dir='$ac_aux_dir'
+xsi_shell='$xsi_shell'
+lt_shell_append='$lt_shell_append'
+
+# See if we are running on zsh, and set the options which allow our
+# commands through without removal of \ escapes INIT.
+if test -n "\${ZSH_VERSION+set}" ; then
+ setopt NO_GLOB_SUBST
+fi
+
+
+ PACKAGE='$PACKAGE'
+ VERSION='$VERSION'
+ TIMESTAMP='$TIMESTAMP'
+ RM='$RM'
+ ofile='$ofile'
+
+
+
+
+
+SED="$SED"
+ SVN_DB_HEADER="$SVN_DB_HEADER"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+ case $ac_config_target in
+ "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;;
+ "subversion/svn_private_config.h.tmp") CONFIG_HEADERS="$CONFIG_HEADERS subversion/svn_private_config.h.tmp:subversion/svn_private_config.h.in" ;;
+ "svn_private_config.h.tmp") CONFIG_COMMANDS="$CONFIG_COMMANDS svn_private_config.h.tmp" ;;
+ "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+ "tools/backup/hot-backup.py") CONFIG_FILES="$CONFIG_FILES tools/backup/hot-backup.py" ;;
+ "tools/hook-scripts/commit-access-control.pl") CONFIG_FILES="$CONFIG_FILES tools/hook-scripts/commit-access-control.pl" ;;
+ "subversion/bindings/swig/perl/native/Makefile.PL") CONFIG_FILES="$CONFIG_FILES subversion/bindings/swig/perl/native/Makefile.PL" ;;
+ "packages/solaris/pkginfo") CONFIG_FILES="$CONFIG_FILES packages/solaris/pkginfo" ;;
+
+ *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+ esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used. Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+ test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+ test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers
+ test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands
+fi
+
+# Have a temporary directory for convenience. Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+ tmp= ac_tmp=
+ trap 'exit_status=$?
+ : "${ac_tmp:=$tmp}"
+ { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+' 0
+ trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+ tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+ test -d "$tmp"
+} ||
+{
+ tmp=./conf$$-$RANDOM
+ (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+ eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+ ac_cs_awk_cr='\\r'
+else
+ ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+ echo "cat >conf$$subs.awk <<_ACEOF" &&
+ echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+ echo "_ACEOF"
+} >conf$$subs.sh ||
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+ . ./conf$$subs.sh ||
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+ ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+ if test $ac_delim_n = $ac_delim_num; then
+ break
+ elif $ac_last_try; then
+ as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ else
+ ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+ fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+ N
+ s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+ for (key in S) S_is_set[key] = 1
+ FS = ""
+
+}
+{
+ line = $ 0
+ nfields = split(line, field, "@")
+ substed = 0
+ len = length(field[1])
+ for (i = 2; i < nfields; i++) {
+ key = field[i]
+ keylen = length(key)
+ if (S_is_set[key]) {
+ value = S[key]
+ line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+ len += length(value) + length(field[++i])
+ substed = 1
+ } else
+ len += 1 + keylen
+ }
+
+ print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+ sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+ cat
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+ ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{
+h
+s///
+s/^/:/
+s/[ ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[ ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[ ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+# Set up the scripts for CONFIG_HEADERS section.
+# No need to generate them if there are no CONFIG_HEADERS.
+# This happens for instance with `./config.status Makefile'.
+if test -n "$CONFIG_HEADERS"; then
+cat >"$ac_tmp/defines.awk" <<\_ACAWK ||
+BEGIN {
+_ACEOF
+
+# Transform confdefs.h into an awk script `defines.awk', embedded as
+# here-document in config.status, that substitutes the proper values into
+# config.h.in to produce config.h.
+
+# Create a delimiter string that does not exist in confdefs.h, to ease
+# handling of long lines.
+ac_delim='%!_!# '
+for ac_last_try in false false :; do
+ ac_tt=`sed -n "/$ac_delim/p" confdefs.h`
+ if test -z "$ac_tt"; then
+ break
+ elif $ac_last_try; then
+ as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5
+ else
+ ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+ fi
+done
+
+# For the awk script, D is an array of macro values keyed by name,
+# likewise P contains macro parameters if any. Preserve backslash
+# newline sequences.
+
+ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]*
+sed -n '
+s/.\{148\}/&'"$ac_delim"'/g
+t rset
+:rset
+s/^[ ]*#[ ]*define[ ][ ]*/ /
+t def
+d
+:def
+s/\\$//
+t bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3"/p
+s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p
+d
+:bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3\\\\\\n"\\/p
+t cont
+s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p
+t cont
+d
+:cont
+n
+s/.\{148\}/&'"$ac_delim"'/g
+t clear
+:clear
+s/\\$//
+t bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/"/p
+d
+:bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p
+b cont
+' <confdefs.h | sed '
+s/'"$ac_delim"'/"\\\
+"/g' >>$CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ for (key in D) D_is_set[key] = 1
+ FS = ""
+}
+/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ {
+ line = \$ 0
+ split(line, arg, " ")
+ if (arg[1] == "#") {
+ defundef = arg[2]
+ mac1 = arg[3]
+ } else {
+ defundef = substr(arg[1], 2)
+ mac1 = arg[2]
+ }
+ split(mac1, mac2, "(") #)
+ macro = mac2[1]
+ prefix = substr(line, 1, index(line, defundef) - 1)
+ if (D_is_set[macro]) {
+ # Preserve the white space surrounding the "#".
+ print prefix "define", macro P[macro] D[macro]
+ next
+ } else {
+ # Replace #undef with comments. This is necessary, for example,
+ # in the case of _POSIX_SOURCE, which is predefined and required
+ # on some systems where configure will not decide to define it.
+ if (defundef == "undef") {
+ print "/*", prefix defundef, macro, "*/"
+ next
+ }
+ }
+}
+{ print }
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ as_fn_error $? "could not setup config headers machinery" "$LINENO" 5
+fi # test -n "$CONFIG_HEADERS"
+
+
+eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS"
+shift
+for ac_tag
+do
+ case $ac_tag in
+ :[FHLC]) ac_mode=$ac_tag; continue;;
+ esac
+ case $ac_mode$ac_tag in
+ :[FHL]*:*);;
+ :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+ :[FH]-) ac_tag=-:-;;
+ :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+ esac
+ ac_save_IFS=$IFS
+ IFS=:
+ set x $ac_tag
+ IFS=$ac_save_IFS
+ shift
+ ac_file=$1
+ shift
+
+ case $ac_mode in
+ :L) ac_source=$1;;
+ :[FH])
+ ac_file_inputs=
+ for ac_f
+ do
+ case $ac_f in
+ -) ac_f="$ac_tmp/stdin";;
+ *) # Look for the file first in the build tree, then in the source tree
+ # (if the path is not absolute). The absolute path cannot be DOS-style,
+ # because $ac_f cannot contain `:'.
+ test -f "$ac_f" ||
+ case $ac_f in
+ [\\/$]*) false;;
+ *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+ esac ||
+ as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+ esac
+ case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+ as_fn_append ac_file_inputs " '$ac_f'"
+ done
+
+ # Let's still pretend it is `configure' which instantiates (i.e., don't
+ # use $as_me), people would be surprised to read:
+ # /* config.h. Generated by config.status. */
+ configure_input='Generated from '`
+ $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+ `' by configure.'
+ if test x"$ac_file" != x-; then
+ configure_input="$ac_file. $configure_input"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+$as_echo "$as_me: creating $ac_file" >&6;}
+ fi
+ # Neutralize special characters interpreted by sed in replacement strings.
+ case $configure_input in #(
+ *\&* | *\|* | *\\* )
+ ac_sed_conf_input=`$as_echo "$configure_input" |
+ sed 's/[\\\\&|]/\\\\&/g'`;; #(
+ *) ac_sed_conf_input=$configure_input;;
+ esac
+
+ case $ac_tag in
+ *:-:* | *:-) cat >"$ac_tmp/stdin" \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+ esac
+ ;;
+ esac
+
+ ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$ac_file" : 'X\(//\)[^/]' \| \
+ X"$ac_file" : 'X\(//\)$' \| \
+ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$ac_file" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)[^/].*/{
+ s//\1/
+ q
+ }
+ /^X\(\/\/\)$/{
+ s//\1/
+ q
+ }
+ /^X\(\/\).*/{
+ s//\1/
+ q
+ }
+ s/.*/./; q'`
+ as_dir="$ac_dir"; as_fn_mkdir_p
+ ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+ ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+ # A ".." for each directory in $ac_dir_suffix.
+ ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+ case $ac_top_builddir_sub in
+ "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+ *) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+ esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+ .) # We are building in place.
+ ac_srcdir=.
+ ac_top_srcdir=$ac_top_builddir_sub
+ ac_abs_top_srcdir=$ac_pwd ;;
+ [\\/]* | ?:[\\/]* ) # Absolute name.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir
+ ac_abs_top_srcdir=$srcdir ;;
+ *) # Relative name.
+ ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_build_prefix$srcdir
+ ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+ case $ac_mode in
+ :F)
+ #
+ # CONFIG_FILE
+ #
+
+ case $INSTALL in
+ [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+ *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;;
+ esac
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+ p
+ q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ ac_datarootdir_hack='
+ s&@datadir@&$datadir&g
+ s&@docdir@&$docdir&g
+ s&@infodir@&$infodir&g
+ s&@localedir@&$localedir&g
+ s&@mandir@&$mandir&g
+ s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+s&@INSTALL@&$ac_INSTALL&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+ { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+ { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \
+ "$ac_tmp/out"`; test -z "$ac_out"; } &&
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined. Please make sure it is defined" >&5
+$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined. Please make sure it is defined" >&2;}
+
+ rm -f "$ac_tmp/stdin"
+ case $ac_file in
+ -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+ *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+ esac \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+ :H)
+ #
+ # CONFIG_HEADER
+ #
+ if test x"$ac_file" != x-; then
+ {
+ $as_echo "/* $configure_input */" \
+ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs"
+ } >"$ac_tmp/config.h" \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
+$as_echo "$as_me: $ac_file is unchanged" >&6;}
+ else
+ rm -f "$ac_file"
+ mv "$ac_tmp/config.h" "$ac_file" \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ fi
+ else
+ $as_echo "/* $configure_input */" \
+ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \
+ || as_fn_error $? "could not create -" "$LINENO" 5
+ fi
+ ;;
+
+ :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5
+$as_echo "$as_me: executing $ac_file commands" >&6;}
+ ;;
+ esac
+
+
+ case $ac_file$ac_mode in
+ "libtool":C)
+
+ # See if we are running on zsh, and set the options which allow our
+ # commands through without removal of \ escapes.
+ if test -n "${ZSH_VERSION+set}" ; then
+ setopt NO_GLOB_SUBST
+ fi
+
+ cfgfile="${ofile}T"
+ trap "$RM \"$cfgfile\"; exit 1" 1 2 15
+ $RM "$cfgfile"
+
+ cat <<_LT_EOF >> "$cfgfile"
+#! $SHELL
+
+# `$ECHO "$ofile" | sed 's%^.*/%%'` - Provide generalized library-building support services.
+# Generated automatically by $as_me ($PACKAGE$TIMESTAMP) $VERSION
+# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+# NOTE: Changes made to this file will be lost: look at ltmain.sh.
+#
+# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
+# 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+# Foundation, Inc.
+# Written by Gordon Matzigkeit, 1996
+#
+# This file is part of GNU Libtool.
+#
+# GNU Libtool is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+#
+# As a special exception to the GNU General Public License,
+# if you distribute this file as part of a program or library that
+# is built using GNU Libtool, you may include this file under the
+# same distribution terms that you use for the rest of that program.
+#
+# GNU Libtool is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Libtool; see the file COPYING. If not, a copy
+# can be downloaded from http://www.gnu.org/licenses/gpl.html, or
+# obtained by writing to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+
+# The names of the tagged configurations supported by this script.
+available_tags="CXX "
+
+# ### BEGIN LIBTOOL CONFIG
+
+# Which release of libtool.m4 was used?
+macro_version=$macro_version
+macro_revision=$macro_revision
+
+# Whether or not to build shared libraries.
+build_libtool_libs=$enable_shared
+
+# Whether or not to build static libraries.
+build_old_libs=$enable_static
+
+# What type of objects to build.
+pic_mode=$pic_mode
+
+# Whether or not to optimize for fast installation.
+fast_install=$enable_fast_install
+
+# Shell to use when invoking shell scripts.
+SHELL=$lt_SHELL
+
+# An echo program that protects backslashes.
+ECHO=$lt_ECHO
+
+# The PATH separator for the build system.
+PATH_SEPARATOR=$lt_PATH_SEPARATOR
+
+# The host system.
+host_alias=$host_alias
+host=$host
+host_os=$host_os
+
+# The build system.
+build_alias=$build_alias
+build=$build
+build_os=$build_os
+
+# A sed program that does not truncate output.
+SED=$lt_SED
+
+# Sed that helps us avoid accidentally triggering echo(1) options like -n.
+Xsed="\$SED -e 1s/^X//"
+
+# A grep program that handles long lines.
+GREP=$lt_GREP
+
+# An ERE matcher.
+EGREP=$lt_EGREP
+
+# A literal string matcher.
+FGREP=$lt_FGREP
+
+# A BSD- or MS-compatible name lister.
+NM=$lt_NM
+
+# Whether we need soft or hard links.
+LN_S=$lt_LN_S
+
+# What is the maximum length of a command?
+max_cmd_len=$max_cmd_len
+
+# Object file suffix (normally "o").
+objext=$ac_objext
+
+# Executable file suffix (normally "").
+exeext=$exeext
+
+# whether the shell understands "unset".
+lt_unset=$lt_unset
+
+# turn spaces into newlines.
+SP2NL=$lt_lt_SP2NL
+
+# turn newlines into spaces.
+NL2SP=$lt_lt_NL2SP
+
+# convert \$build file names to \$host format.
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+
+# convert \$build files to toolchain format.
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+
+# An object symbol dumper.
+OBJDUMP=$lt_OBJDUMP
+
+# Method to check whether dependent libraries are shared objects.
+deplibs_check_method=$lt_deplibs_check_method
+
+# Command to use when deplibs_check_method = "file_magic".
+file_magic_cmd=$lt_file_magic_cmd
+
+# How to find potential files when deplibs_check_method = "file_magic".
+file_magic_glob=$lt_file_magic_glob
+
+# Find potential files using nocaseglob when deplibs_check_method = "file_magic".
+want_nocaseglob=$lt_want_nocaseglob
+
+# DLL creation program.
+DLLTOOL=$lt_DLLTOOL
+
+# Command to associate shared and link libraries.
+sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd
+
+# The archiver.
+AR=$lt_AR
+
+# Flags to create an archive.
+AR_FLAGS=$lt_AR_FLAGS
+
+# How to feed a file listing to the archiver.
+archiver_list_spec=$lt_archiver_list_spec
+
+# A symbol stripping program.
+STRIP=$lt_STRIP
+
+# Commands used to install an old-style archive.
+RANLIB=$lt_RANLIB
+old_postinstall_cmds=$lt_old_postinstall_cmds
+old_postuninstall_cmds=$lt_old_postuninstall_cmds
+
+# Whether to use a lock for old archive extraction.
+lock_old_archive_extraction=$lock_old_archive_extraction
+
+# A C compiler.
+LTCC=$lt_CC
+
+# LTCC compiler flags.
+LTCFLAGS=$lt_CFLAGS
+
+# Take the output of nm and produce a listing of raw symbols and C names.
+global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe
+
+# Transform the output of nm in a proper C declaration.
+global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl
+
+# Transform the output of nm in a C name address pair.
+global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address
+
+# Transform the output of nm in a C name address pair when lib prefix is needed.
+global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix
+
+# Specify filename containing input files for \$NM.
+nm_file_list_spec=$lt_nm_file_list_spec
+
+# The root where to search for dependent libraries,and in which our libraries should be installed.
+lt_sysroot=$lt_sysroot
+
+# The name of the directory that contains temporary libtool files.
+objdir=$objdir
+
+# Used to examine libraries when file_magic_cmd begins with "file".
+MAGIC_CMD=$MAGIC_CMD
+
+# Must we lock files when doing compilation?
+need_locks=$lt_need_locks
+
+# Manifest tool.
+MANIFEST_TOOL=$lt_MANIFEST_TOOL
+
+# Tool to manipulate archived DWARF debug symbol files on Mac OS X.
+DSYMUTIL=$lt_DSYMUTIL
+
+# Tool to change global to local symbols on Mac OS X.
+NMEDIT=$lt_NMEDIT
+
+# Tool to manipulate fat objects and archives on Mac OS X.
+LIPO=$lt_LIPO
+
+# ldd/readelf like tool for Mach-O binaries on Mac OS X.
+OTOOL=$lt_OTOOL
+
+# ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4.
+OTOOL64=$lt_OTOOL64
+
+# Old archive suffix (normally "a").
+libext=$libext
+
+# Shared library suffix (normally ".so").
+shrext_cmds=$lt_shrext_cmds
+
+# The commands to extract the exported symbol list from a shared archive.
+extract_expsyms_cmds=$lt_extract_expsyms_cmds
+
+# Variables whose values should be saved in libtool wrapper scripts and
+# restored at link time.
+variables_saved_for_relink=$lt_variables_saved_for_relink
+
+# Do we need the "lib" prefix for modules?
+need_lib_prefix=$need_lib_prefix
+
+# Do we need a version for libraries?
+need_version=$need_version
+
+# Library versioning type.
+version_type=$version_type
+
+# Shared library runtime path variable.
+runpath_var=$runpath_var
+
+# Shared library path variable.
+shlibpath_var=$shlibpath_var
+
+# Is shlibpath searched before the hard-coded library search path?
+shlibpath_overrides_runpath=$shlibpath_overrides_runpath
+
+# Format of library name prefix.
+libname_spec=$lt_libname_spec
+
+# List of archive names. First name is the real one, the rest are links.
+# The last name is the one that the linker finds with -lNAME
+library_names_spec=$lt_library_names_spec
+
+# The coded name of the library, if different from the real name.
+soname_spec=$lt_soname_spec
+
+# Permission mode override for installation of shared libraries.
+install_override_mode=$lt_install_override_mode
+
+# Command to use after installation of a shared archive.
+postinstall_cmds=$lt_postinstall_cmds
+
+# Command to use after uninstallation of a shared archive.
+postuninstall_cmds=$lt_postuninstall_cmds
+
+# Commands used to finish a libtool library installation in a directory.
+finish_cmds=$lt_finish_cmds
+
+# As "finish_cmds", except a single script fragment to be evaled but
+# not shown.
+finish_eval=$lt_finish_eval
+
+# Whether we should hardcode library paths into libraries.
+hardcode_into_libs=$hardcode_into_libs
+
+# Compile-time system search path for libraries.
+sys_lib_search_path_spec=$lt_sys_lib_search_path_spec
+
+# Run-time system search path for libraries.
+sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec
+
+# Whether dlopen is supported.
+dlopen_support=$enable_dlopen
+
+# Whether dlopen of programs is supported.
+dlopen_self=$enable_dlopen_self
+
+# Whether dlopen of statically linked programs is supported.
+dlopen_self_static=$enable_dlopen_self_static
+
+# Commands to strip libraries.
+old_striplib=$lt_old_striplib
+striplib=$lt_striplib
+
+
+# The linker used to build libraries.
+LD=$lt_LD
+
+# How to create reloadable object files.
+reload_flag=$lt_reload_flag
+reload_cmds=$lt_reload_cmds
+
+# Commands used to build an old-style archive.
+old_archive_cmds=$lt_old_archive_cmds
+
+# A language specific compiler.
+CC=$lt_compiler
+
+# Is the compiler the GNU compiler?
+with_gcc=$GCC
+
+# Compiler flag to turn off builtin functions.
+no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag
+
+# Additional compiler flags for building library objects.
+pic_flag=$lt_lt_prog_compiler_pic
+
+# How to pass a linker flag through the compiler.
+wl=$lt_lt_prog_compiler_wl
+
+# Compiler flag to prevent dynamic linking.
+link_static_flag=$lt_lt_prog_compiler_static
+
+# Does compiler simultaneously support -c and -o options?
+compiler_c_o=$lt_lt_cv_prog_compiler_c_o
+
+# Whether or not to add -lc for building shared libraries.
+build_libtool_need_lc=$archive_cmds_need_lc
+
+# Whether or not to disallow shared libs when runtime libs are static.
+allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes
+
+# Compiler flag to allow reflexive dlopens.
+export_dynamic_flag_spec=$lt_export_dynamic_flag_spec
+
+# Compiler flag to generate shared objects directly from archives.
+whole_archive_flag_spec=$lt_whole_archive_flag_spec
+
+# Whether the compiler copes with passing no objects directly.
+compiler_needs_object=$lt_compiler_needs_object
+
+# Create an old-style archive from a shared archive.
+old_archive_from_new_cmds=$lt_old_archive_from_new_cmds
+
+# Create a temporary old-style archive to link instead of a shared archive.
+old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds
+
+# Commands used to build a shared archive.
+archive_cmds=$lt_archive_cmds
+archive_expsym_cmds=$lt_archive_expsym_cmds
+
+# Commands used to build a loadable module if different from building
+# a shared archive.
+module_cmds=$lt_module_cmds
+module_expsym_cmds=$lt_module_expsym_cmds
+
+# Whether we are building with GNU ld or not.
+with_gnu_ld=$lt_with_gnu_ld
+
+# Flag that allows shared libraries with undefined symbols to be built.
+allow_undefined_flag=$lt_allow_undefined_flag
+
+# Flag that enforces no undefined symbols.
+no_undefined_flag=$lt_no_undefined_flag
+
+# Flag to hardcode \$libdir into a binary during linking.
+# This must work even if \$libdir does not exist
+hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec
+
+# Whether we need a single "-rpath" flag with a separated argument.
+hardcode_libdir_separator=$lt_hardcode_libdir_separator
+
+# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes
+# DIR into the resulting binary.
+hardcode_direct=$hardcode_direct
+
+# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes
+# DIR into the resulting binary and the resulting library dependency is
+# "absolute",i.e impossible to change by setting \${shlibpath_var} if the
+# library is relocated.
+hardcode_direct_absolute=$hardcode_direct_absolute
+
+# Set to "yes" if using the -LDIR flag during linking hardcodes DIR
+# into the resulting binary.
+hardcode_minus_L=$hardcode_minus_L
+
+# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR
+# into the resulting binary.
+hardcode_shlibpath_var=$hardcode_shlibpath_var
+
+# Set to "yes" if building a shared library automatically hardcodes DIR
+# into the library and all subsequent libraries and executables linked
+# against it.
+hardcode_automatic=$hardcode_automatic
+
+# Set to yes if linker adds runtime paths of dependent libraries
+# to runtime path list.
+inherit_rpath=$inherit_rpath
+
+# Whether libtool must link a program against all its dependency libraries.
+link_all_deplibs=$link_all_deplibs
+
+# Set to "yes" if exported symbols are required.
+always_export_symbols=$always_export_symbols
+
+# The commands to list exported symbols.
+export_symbols_cmds=$lt_export_symbols_cmds
+
+# Symbols that should not be listed in the preloaded symbols.
+exclude_expsyms=$lt_exclude_expsyms
+
+# Symbols that must always be exported.
+include_expsyms=$lt_include_expsyms
+
+# Commands necessary for linking programs (against libraries) with templates.
+prelink_cmds=$lt_prelink_cmds
+
+# Commands necessary for finishing linking programs.
+postlink_cmds=$lt_postlink_cmds
+
+# Specify filename containing input files.
+file_list_spec=$lt_file_list_spec
+
+# How to hardcode a shared library path into an executable.
+hardcode_action=$hardcode_action
+
+# The directories searched by this compiler when creating a shared library.
+compiler_lib_search_dirs=$lt_compiler_lib_search_dirs
+
+# Dependencies to place before and after the objects being linked to
+# create a shared library.
+predep_objects=$lt_predep_objects
+postdep_objects=$lt_postdep_objects
+predeps=$lt_predeps
+postdeps=$lt_postdeps
+
+# The library search path used internally by the compiler when linking
+# a shared library.
+compiler_lib_search_path=$lt_compiler_lib_search_path
+
+# ### END LIBTOOL CONFIG
+
+_LT_EOF
+
+ case $host_os in
+ aix3*)
+ cat <<\_LT_EOF >> "$cfgfile"
+# AIX sometimes has problems with the GCC collect2 program. For some
+# reason, if we set the COLLECT_NAMES environment variable, the problems
+# vanish in a puff of smoke.
+if test "X${COLLECT_NAMES+set}" != Xset; then
+ COLLECT_NAMES=
+ export COLLECT_NAMES
+fi
+_LT_EOF
+ ;;
+ esac
+
+
+ltmain="$ac_aux_dir/ltmain.sh"
+
+
+ # We use sed instead of cat because bash on DJGPP gets confused if
+ # if finds mixed CR/LF and LF-only lines. Since sed operates in
+ # text mode, it properly converts lines to CR/LF. This bash problem
+ # is reportedly fixed, but why not run on old versions too?
+ sed '$q' "$ltmain" >> "$cfgfile" \
+ || (rm -f "$cfgfile"; exit 1)
+
+ if test x"$xsi_shell" = xyes; then
+ sed -e '/^func_dirname ()$/,/^} # func_dirname /c\
+func_dirname ()\
+{\
+\ case ${1} in\
+\ */*) func_dirname_result="${1%/*}${2}" ;;\
+\ * ) func_dirname_result="${3}" ;;\
+\ esac\
+} # Extended-shell func_dirname implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+ sed -e '/^func_basename ()$/,/^} # func_basename /c\
+func_basename ()\
+{\
+\ func_basename_result="${1##*/}"\
+} # Extended-shell func_basename implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+ sed -e '/^func_dirname_and_basename ()$/,/^} # func_dirname_and_basename /c\
+func_dirname_and_basename ()\
+{\
+\ case ${1} in\
+\ */*) func_dirname_result="${1%/*}${2}" ;;\
+\ * ) func_dirname_result="${3}" ;;\
+\ esac\
+\ func_basename_result="${1##*/}"\
+} # Extended-shell func_dirname_and_basename implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+ sed -e '/^func_stripname ()$/,/^} # func_stripname /c\
+func_stripname ()\
+{\
+\ # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are\
+\ # positional parameters, so assign one to ordinary parameter first.\
+\ func_stripname_result=${3}\
+\ func_stripname_result=${func_stripname_result#"${1}"}\
+\ func_stripname_result=${func_stripname_result%"${2}"}\
+} # Extended-shell func_stripname implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+ sed -e '/^func_split_long_opt ()$/,/^} # func_split_long_opt /c\
+func_split_long_opt ()\
+{\
+\ func_split_long_opt_name=${1%%=*}\
+\ func_split_long_opt_arg=${1#*=}\
+} # Extended-shell func_split_long_opt implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+ sed -e '/^func_split_short_opt ()$/,/^} # func_split_short_opt /c\
+func_split_short_opt ()\
+{\
+\ func_split_short_opt_arg=${1#??}\
+\ func_split_short_opt_name=${1%"$func_split_short_opt_arg"}\
+} # Extended-shell func_split_short_opt implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+ sed -e '/^func_lo2o ()$/,/^} # func_lo2o /c\
+func_lo2o ()\
+{\
+\ case ${1} in\
+\ *.lo) func_lo2o_result=${1%.lo}.${objext} ;;\
+\ *) func_lo2o_result=${1} ;;\
+\ esac\
+} # Extended-shell func_lo2o implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+ sed -e '/^func_xform ()$/,/^} # func_xform /c\
+func_xform ()\
+{\
+ func_xform_result=${1%.*}.lo\
+} # Extended-shell func_xform implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+ sed -e '/^func_arith ()$/,/^} # func_arith /c\
+func_arith ()\
+{\
+ func_arith_result=$(( $* ))\
+} # Extended-shell func_arith implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+ sed -e '/^func_len ()$/,/^} # func_len /c\
+func_len ()\
+{\
+ func_len_result=${#1}\
+} # Extended-shell func_len implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+fi
+
+if test x"$lt_shell_append" = xyes; then
+ sed -e '/^func_append ()$/,/^} # func_append /c\
+func_append ()\
+{\
+ eval "${1}+=\\${2}"\
+} # Extended-shell func_append implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+ sed -e '/^func_append_quoted ()$/,/^} # func_append_quoted /c\
+func_append_quoted ()\
+{\
+\ func_quote_for_eval "${2}"\
+\ eval "${1}+=\\\\ \\$func_quote_for_eval_result"\
+} # Extended-shell func_append_quoted implementation' "$cfgfile" > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+ # Save a `func_append' function call where possible by direct use of '+='
+ sed -e 's%func_append \([a-zA-Z_]\{1,\}\) "%\1+="%g' $cfgfile > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+ test 0 -eq $? || _lt_function_replace_fail=:
+else
+ # Save a `func_append' function call even when '+=' is not available
+ sed -e 's%func_append \([a-zA-Z_]\{1,\}\) "%\1="$\1%g' $cfgfile > $cfgfile.tmp \
+ && mv -f "$cfgfile.tmp" "$cfgfile" \
+ || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+ test 0 -eq $? || _lt_function_replace_fail=:
+fi
+
+if test x"$_lt_function_replace_fail" = x":"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Unable to substitute extended shell functions in $ofile" >&5
+$as_echo "$as_me: WARNING: Unable to substitute extended shell functions in $ofile" >&2;}
+fi
+
+
+ mv -f "$cfgfile" "$ofile" ||
+ (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile")
+ chmod +x "$ofile"
+
+
+ cat <<_LT_EOF >> "$ofile"
+
+# ### BEGIN LIBTOOL TAG CONFIG: CXX
+
+# The linker used to build libraries.
+LD=$lt_LD_CXX
+
+# How to create reloadable object files.
+reload_flag=$lt_reload_flag_CXX
+reload_cmds=$lt_reload_cmds_CXX
+
+# Commands used to build an old-style archive.
+old_archive_cmds=$lt_old_archive_cmds_CXX
+
+# A language specific compiler.
+CC=$lt_compiler_CXX
+
+# Is the compiler the GNU compiler?
+with_gcc=$GCC_CXX
+
+# Compiler flag to turn off builtin functions.
+no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag_CXX
+
+# Additional compiler flags for building library objects.
+pic_flag=$lt_lt_prog_compiler_pic_CXX
+
+# How to pass a linker flag through the compiler.
+wl=$lt_lt_prog_compiler_wl_CXX
+
+# Compiler flag to prevent dynamic linking.
+link_static_flag=$lt_lt_prog_compiler_static_CXX
+
+# Does compiler simultaneously support -c and -o options?
+compiler_c_o=$lt_lt_cv_prog_compiler_c_o_CXX
+
+# Whether or not to add -lc for building shared libraries.
+build_libtool_need_lc=$archive_cmds_need_lc_CXX
+
+# Whether or not to disallow shared libs when runtime libs are static.
+allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes_CXX
+
+# Compiler flag to allow reflexive dlopens.
+export_dynamic_flag_spec=$lt_export_dynamic_flag_spec_CXX
+
+# Compiler flag to generate shared objects directly from archives.
+whole_archive_flag_spec=$lt_whole_archive_flag_spec_CXX
+
+# Whether the compiler copes with passing no objects directly.
+compiler_needs_object=$lt_compiler_needs_object_CXX
+
+# Create an old-style archive from a shared archive.
+old_archive_from_new_cmds=$lt_old_archive_from_new_cmds_CXX
+
+# Create a temporary old-style archive to link instead of a shared archive.
+old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds_CXX
+
+# Commands used to build a shared archive.
+archive_cmds=$lt_archive_cmds_CXX
+archive_expsym_cmds=$lt_archive_expsym_cmds_CXX
+
+# Commands used to build a loadable module if different from building
+# a shared archive.
+module_cmds=$lt_module_cmds_CXX
+module_expsym_cmds=$lt_module_expsym_cmds_CXX
+
+# Whether we are building with GNU ld or not.
+with_gnu_ld=$lt_with_gnu_ld_CXX
+
+# Flag that allows shared libraries with undefined symbols to be built.
+allow_undefined_flag=$lt_allow_undefined_flag_CXX
+
+# Flag that enforces no undefined symbols.
+no_undefined_flag=$lt_no_undefined_flag_CXX
+
+# Flag to hardcode \$libdir into a binary during linking.
+# This must work even if \$libdir does not exist
+hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec_CXX
+
+# Whether we need a single "-rpath" flag with a separated argument.
+hardcode_libdir_separator=$lt_hardcode_libdir_separator_CXX
+
+# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes
+# DIR into the resulting binary.
+hardcode_direct=$hardcode_direct_CXX
+
+# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes
+# DIR into the resulting binary and the resulting library dependency is
+# "absolute",i.e impossible to change by setting \${shlibpath_var} if the
+# library is relocated.
+hardcode_direct_absolute=$hardcode_direct_absolute_CXX
+
+# Set to "yes" if using the -LDIR flag during linking hardcodes DIR
+# into the resulting binary.
+hardcode_minus_L=$hardcode_minus_L_CXX
+
+# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR
+# into the resulting binary.
+hardcode_shlibpath_var=$hardcode_shlibpath_var_CXX
+
+# Set to "yes" if building a shared library automatically hardcodes DIR
+# into the library and all subsequent libraries and executables linked
+# against it.
+hardcode_automatic=$hardcode_automatic_CXX
+
+# Set to yes if linker adds runtime paths of dependent libraries
+# to runtime path list.
+inherit_rpath=$inherit_rpath_CXX
+
+# Whether libtool must link a program against all its dependency libraries.
+link_all_deplibs=$link_all_deplibs_CXX
+
+# Set to "yes" if exported symbols are required.
+always_export_symbols=$always_export_symbols_CXX
+
+# The commands to list exported symbols.
+export_symbols_cmds=$lt_export_symbols_cmds_CXX
+
+# Symbols that should not be listed in the preloaded symbols.
+exclude_expsyms=$lt_exclude_expsyms_CXX
+
+# Symbols that must always be exported.
+include_expsyms=$lt_include_expsyms_CXX
+
+# Commands necessary for linking programs (against libraries) with templates.
+prelink_cmds=$lt_prelink_cmds_CXX
+
+# Commands necessary for finishing linking programs.
+postlink_cmds=$lt_postlink_cmds_CXX
+
+# Specify filename containing input files.
+file_list_spec=$lt_file_list_spec_CXX
+
+# How to hardcode a shared library path into an executable.
+hardcode_action=$hardcode_action_CXX
+
+# The directories searched by this compiler when creating a shared library.
+compiler_lib_search_dirs=$lt_compiler_lib_search_dirs_CXX
+
+# Dependencies to place before and after the objects being linked to
+# create a shared library.
+predep_objects=$lt_predep_objects_CXX
+postdep_objects=$lt_postdep_objects_CXX
+predeps=$lt_predeps_CXX
+postdeps=$lt_postdeps_CXX
+
+# The library search path used internally by the compiler when linking
+# a shared library.
+compiler_lib_search_path=$lt_compiler_lib_search_path_CXX
+
+# ### END LIBTOOL TAG CONFIG: CXX
+_LT_EOF
+
+ ;;
+ "svn_private_config.h.tmp":C) svn_cf=subversion/svn_private_config.h;
+ $SED -e "s/@SVN_DB_HEADER@/$SVN_DB_HEADER/" $svn_cf.tmp > $svn_cf.tmp.new
+ cmp -s $svn_cf.tmp.new $svn_cf || mv -f $svn_cf.tmp.new $svn_cf
+ rm -f $svn_cf.tmp.new $svn_cf.tmp ;;
+ "tools/backup/hot-backup.py":F) chmod +x tools/backup/hot-backup.py ;;
+ "tools/hook-scripts/commit-access-control.pl":F) chmod +x tools/hook-scripts/commit-access-control.pl ;;
+ "subversion/bindings/swig/perl/native/Makefile.PL":F) chmod +x subversion/bindings/swig/perl/native/Makefile.PL ;;
+ "packages/solaris/pkginfo":F) chmod +x packages/solaris/pkginfo ;;
+
+ esac
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+ as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded. So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status. When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+ ac_cs_success=:
+ ac_config_status_args=
+ test "$silent" = yes &&
+ ac_config_status_args="$ac_config_status_args --quiet"
+ exec 5>/dev/null
+ $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+ exec 5>>config.log
+ # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+ # would make configure fail if this is the last instruction.
+ $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
+
+# ==== Print final messages to user ==========================================
+
+
+if test "$svn_lib_berkeley_db" = "no" && test "$with_berkeley_db" != "no"; then
+ db_version="$SVN_FS_WANT_DB_MAJOR.$SVN_FS_WANT_DB_MINOR.$SVN_FS_WANT_DB_PATCH"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: we have configured without BDB filesystem support
+
+
+You don't seem to have Berkeley DB version $db_version or newer
+installed and linked to APR-UTIL. We have created a Makefile which will build
+Subversion without support for the Berkeley DB back-end. You can find the
+latest version of Berkeley DB here:
+
+ http://www.oracle.com/technetwork/products/berkeleydb/downloads/index.html
+
+or explicitly specify --without-berkeley-db to silence this warning.
+" >&5
+$as_echo "$as_me: WARNING: we have configured without BDB filesystem support
+
+
+You don't seem to have Berkeley DB version $db_version or newer
+installed and linked to APR-UTIL. We have created a Makefile which will build
+Subversion without support for the Berkeley DB back-end. You can find the
+latest version of Berkeley DB here:
+
+ http://www.oracle.com/technetwork/products/berkeleydb/downloads/index.html
+
+or explicitly specify --without-berkeley-db to silence this warning.
+" >&2;}
+fi
diff --git a/contrib/subversion/configure.ac b/contrib/subversion/configure.ac
new file mode 100644
index 0000000..b1748ce
--- /dev/null
+++ b/contrib/subversion/configure.ac
@@ -0,0 +1,1521 @@
+dnl Licensed to the Apache Software Foundation (ASF) under one
+dnl or more contributor license agreements. See the NOTICE file
+dnl distributed with this work for additional information
+dnl regarding copyright ownership. The ASF licenses this file
+dnl to you under the Apache License, Version 2.0 (the
+dnl "License"); you may not use this file except in compliance
+dnl with the License. You may obtain a copy of the License at
+dnl
+dnl http://www.apache.org/licenses/LICENSE-2.0
+dnl
+dnl Unless required by applicable law or agreed to in writing,
+dnl software distributed under the License is distributed on an
+dnl "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+dnl KIND, either express or implied. See the License for the
+dnl specific language governing permissions and limitations
+dnl under the License.
+
+dnl configure.ac: Autoconfiscation for Subversion
+dnl Process this file with autoconf to produce a configure script.
+
+AC_PREREQ(2.59)
+
+dnl Get the version of Subversion, using m4's esyscmd() command to do this
+dnl at m4-time, since AC_INIT() requires it then.
+AC_INIT([subversion],
+ [esyscmd(python build/getversion.py SVN subversion/include/svn_version.h)],
+ [http://subversion.apache.org/])
+
+AC_CONFIG_SRCDIR(subversion/include/svn_types.h)
+AC_CONFIG_AUX_DIR([build])
+
+AC_MSG_NOTICE([Configuring Subversion ]AC_PACKAGE_VERSION)
+
+AC_SUBST([abs_srcdir], ["`cd $srcdir && pwd`"])
+AC_SUBST([abs_builddir], ["`pwd`"])
+if test "$abs_srcdir" = "$abs_builddir"; then
+ canonicalized_srcdir=""
+else
+ canonicalized_srcdir="$srcdir/"
+fi
+AC_SUBST([canonicalized_srcdir])
+
+SWIG_LDFLAGS="$LDFLAGS"
+AC_SUBST([SWIG_LDFLAGS])
+
+# Generate config.nice early (before the arguments are munged)
+SVN_CONFIG_NICE(config.nice)
+
+# ==== Check for programs ====================================================
+
+# Look for a C compiler (before anything can set CFLAGS)
+CMAINTAINERFLAGS="$CUSERFLAGS"
+CUSERFLAGS="$CFLAGS"
+AC_PROG_CC
+SVN_CC_MODE_SETUP
+
+# Look for a C++ compiler (before anything can set CXXFLAGS)
+CXXMAINTAINERFLAGS="$CXXUSERFLAGS"
+CXXUSERFLAGS="$CXXFLAGS"
+AC_PROG_CXX
+SVN_CXX_MODE_SETUP
+
+# Look for a C pre-processor
+AC_PROG_CPP
+
+# Look for a good sed
+# AC_PROG_SED was introduced in Autoconf 2.59b
+m4_ifdef([AC_PROG_SED], [AC_PROG_SED], [SED="${SED:-sed}"])
+
+# Grab target_cpu, so we can use it in the Solaris pkginfo file
+AC_CANONICAL_TARGET
+
+# Look for an extended grep
+AC_PROG_EGREP
+
+AC_PROG_LN_S
+
+AC_PROG_INSTALL
+# If $INSTALL is relative path to our fallback install-sh, then convert
+# to an absolute path, as in some cases (e.g. Solaris VPATH build), libtool
+# may try to use it from a changed working directory.
+if test "$INSTALL" = "build/install-sh -c"; then
+ INSTALL="$abs_srcdir/$INSTALL"
+fi
+
+if test -z "$MKDIR"; then
+ MKDIR="$INSTALL -d"
+fi
+AC_SUBST([MKDIR])
+
+# ==== Libraries, for which we may have source to build ======================
+
+dnl verify apr version and set apr flags
+dnl These regular expressions should not contain "\(" and "\)".
+dnl The specific reason we require APR 0.9.7 is:
+dnl It contains fixes to its file writing routines
+dnl now generating errors instead of silently ignoring
+dnl them. Only .7 and later can guarantee repository
+dnl integrity with FSFS.
+
+APR_VER_REGEXES=["0\.9\.[7-9] 0\.9\.1[0-9] 1\. 2\."]
+
+SVN_LIB_APR($APR_VER_REGEXES)
+
+if test `expr $apr_version : 2` -ne 0; then
+ dnl Bump the library so-version to 2 if using APR-2
+ dnl (Debian uses so-version 1 for APR-1-with-largefile)
+ svn_lib_ver=2
+ dnl APR-2 provides APRUTIL
+ apu_config=$apr_config
+ AC_SUBST(SVN_APRUTIL_INCLUDES)
+ AC_SUBST(SVN_APRUTIL_CONFIG, ["$apu_config"])
+ AC_SUBST(SVN_APRUTIL_LIBS)
+else
+ svn_lib_ver=0
+ APU_VER_REGEXES=["0\.9\.[7-9] 0\.9\.1[0-9] 1\."]
+ SVN_LIB_APRUTIL($APU_VER_REGEXES)
+fi
+SVN_LT_SOVERSION="-version-info $svn_lib_ver"
+AC_SUBST(SVN_LT_SOVERSION)
+AC_DEFINE_UNQUOTED(SVN_SOVERSION, $svn_lib_ver,
+ [Subversion library major verson])
+
+dnl Search for pkg-config
+AC_PATH_PROG(PKG_CONFIG, pkg-config)
+
+dnl Search for serf
+SVN_LIB_SERF(1,2,1)
+
+if test "$svn_lib_serf" = "yes"; then
+ AC_DEFINE([SVN_HAVE_SERF], 1,
+ [Defined if support for Serf is enabled])
+fi
+
+dnl Search for apr_memcache (only affects fs_fs)
+SVN_LIB_APR_MEMCACHE
+
+if test "$svn_lib_apr_memcache" = "yes"; then
+ AC_DEFINE(SVN_HAVE_MEMCACHE, 1,
+ [Defined if apr_memcache (standalone or in apr-util) is present])
+fi
+
+
+dnl Find Apache with a recent-enough magic module number
+SVN_FIND_APACHE(20020903)
+
+dnl Search for SQLite. If you change SQLITE_URL from a .zip to
+dnl something else also update build/ac-macros/sqlite.m4 to reflect
+dnl the correct command to unpack the downloaded file.
+SQLITE_MINIMUM_VER="3.7.12"
+SQLITE_RECOMMENDED_VER="3.7.15.1"
+SQLITE_URL="http://www.sqlite.org/sqlite-amalgamation-$(printf %d%02d%02d%02d $(echo ${SQLITE_RECOMMENDED_VER} | sed -e 's/\./ /g')).zip"
+
+SVN_LIB_SQLITE(${SQLITE_MINIMUM_VER}, ${SQLITE_RECOMMENDED_VER},
+ ${SQLITE_URL})
+
+AC_ARG_ENABLE(sqlite-compatibility-version,
+ AS_HELP_STRING([--enable-sqlite-compatibility-version=X.Y.Z],
+ [Allow binary to run against SQLite as old as ARG]),
+ [sqlite_compat_ver=$enableval],[sqlite_compat_ver=no])
+
+if test -n "$sqlite_compat_ver" && test "$sqlite_compat_ver" != no; then
+ SVN_SQLITE_VERNUM_PARSE([$sqlite_compat_ver],
+ [sqlite_compat_ver_num])
+ CFLAGS="-DSVN_SQLITE_MIN_VERSION='\"$sqlite_compat_ver\"' $CFLAGS"
+ CFLAGS="-DSVN_SQLITE_MIN_VERSION_NUMBER=$sqlite_compat_ver_num $CFLAGS"
+fi
+
+SVN_CHECK_FOR_ATOMIC_BUILTINS
+
+if test "$svn_cv_atomic_builtins" = "yes"; then
+ AC_DEFINE(SVN_HAS_ATOMIC_BUILTINS, 1, [Define if compiler provides atomic builtins])
+fi
+
+dnl Set up a number of directories ---------------------
+
+dnl Create SVN_BINDIR for proper substitution
+if test "${bindir}" = '${exec_prefix}/bin'; then
+ if test "${exec_prefix}" = "NONE"; then
+ if test "${prefix}" = "NONE"; then
+ SVN_BINDIR="${ac_default_prefix}/bin"
+ else
+ SVN_BINDIR="${prefix}/bin"
+ fi
+ else
+ SVN_BINDIR="${exec_prefix}/bin"
+ fi
+else
+ SVN_BINDIR="${bindir}"
+fi
+
+dnl fully evaluate this value. when we substitute it into our tool scripts,
+dnl they will not have things such as ${bindir} available
+SVN_BINDIR="`eval echo ${SVN_BINDIR}`"
+AC_SUBST(SVN_BINDIR)
+
+dnl provide ${bindir} in svn_private_config.h for use in compiled code
+AC_DEFINE_UNQUOTED(SVN_BINDIR, "${SVN_BINDIR}",
+ [Defined to be the path to the installed binaries])
+
+dnl This purposely does *not* allow for multiple parallel installs.
+dnl However, it is compatible with most gettext usages.
+localedir='${datadir}/locale'
+AC_SUBST(localedir)
+
+dnl For SVN_LOCALE_DIR, we have to expand it to something. See SVN_BINDIR.
+if test "${datadir}" = '${prefix}/share' && test "${prefix}" = "NONE"; then
+ exp_localedir='${ac_default_prefix}/share/locale'
+else
+ exp_localedir=$localedir
+fi
+SVN_EXPAND_VAR(svn_localedir, "${exp_localedir}")
+AC_DEFINE_UNQUOTED(SVN_LOCALE_DIR, "${svn_localedir}",
+ [Defined to be the path to the installed locale dirs])
+
+dnl Check for libtool -- we'll definitely need it for all our shared libs!
+AC_MSG_NOTICE([configuring libtool now])
+ifdef([LT_INIT], [LT_INIT], [AC_PROG_LIBTOOL])
+AC_ARG_ENABLE(experimental-libtool,
+ AS_HELP_STRING([--enable-experimental-libtool],[Use APR's libtool]),
+ [experimental_libtool=$enableval],[experimental_libtool=no])
+
+if test "$experimental_libtool" = "yes"; then
+ echo "using APR's libtool"
+ sh_libtool="`$apr_config --apr-libtool`"
+ LIBTOOL="$sh_libtool"
+ SVN_LIBTOOL="$sh_libtool"
+else
+ sh_libtool="$abs_builddir/libtool"
+ SVN_LIBTOOL="\$(SHELL) $sh_libtool"
+fi
+AC_SUBST(SVN_LIBTOOL)
+
+dnl Determine the libtool version
+changequote(, )dnl
+lt_pversion=`$LIBTOOL --version 2>/dev/null|$SED -e 's/([^)]*)//g;s/^[^0-9]*//;s/[- ].*//g;q'`
+lt_version=`echo $lt_pversion|$SED -e 's/\([a-z]*\)$/.\1/'`
+lt_major_version=`echo $lt_version | cut -d'.' -f 1`
+changequote([, ])dnl
+
+dnl set the default parameters
+svn_enable_static=yes
+svn_enable_shared=yes
+
+dnl check for --enable-static option
+AC_ARG_ENABLE(static,
+ AS_HELP_STRING([--enable-static],
+ [Build static libraries]),
+ [svn_enable_static="$enableval"], [svn_enable_static="yes"])
+
+dnl check for --enable-shared option
+AC_ARG_ENABLE(shared,
+ AS_HELP_STRING([--enable-shared],
+ [Build shared libraries]),
+ [svn_enable_shared="$enableval"], [svn_enable_shared="yes"])
+
+if test "$svn_enable_static" = "yes" && test "$svn_enable_shared" = "yes" ; then
+ AC_MSG_NOTICE([building both shared and static libraries])
+elif test "$svn_enable_static" = "yes" ; then
+ AC_MSG_NOTICE([building static libraries only])
+ LT_CFLAGS="-static $LT_CFLAGS"
+ LT_LDFLAGS="-static $LT_LDFLAGS"
+elif test "$svn_enable_shared" = "yes" ; then
+ AC_MSG_NOTICE([building shared libraries only])
+ if test "$lt_major_version" = "1" ; then
+ LT_CFLAGS="-prefer-pic $LT_CFLAGS"
+ elif test "$lt_major_version" = "2" ; then
+ LT_CFLAGS="-shared $LT_CFLAGS"
+ fi
+ LT_LDFLAGS="-shared $LT_LDFLAGS"
+else
+ AC_MSG_ERROR([cannot disable both shared and static libraries])
+fi
+
+dnl Check for --enable-all-static option
+AC_ARG_ENABLE(all-static,
+ AS_HELP_STRING([--enable-all-static],
+ [Build completely static (standalone) binaries.]),
+ [
+ if test "$enableval" = "yes" ; then
+ LT_LDFLAGS="-all-static $LT_LDFLAGS"
+ elif test "$enableval" != "no" ; then
+ AC_MSG_ERROR([--enable-all-static doesn't accept argument])
+ fi
+])
+
+AC_SUBST(LT_CFLAGS)
+AC_SUBST(LT_LDFLAGS)
+
+AC_ARG_ENABLE(local-library-preloading,
+ AS_HELP_STRING([--enable-local-library-preloading],
+ [Enable preloading of locally built libraries in locally
+ built executables. This may be necessary for testing
+ prior to installation on some platforms. It does not
+ work on some platforms (Darwin, OpenBSD, ...).]),
+ [
+ if test "$enableval" != "no"; then
+ if test "$svn_enable_shared" = "yes"; then
+ TRANSFORM_LIBTOOL_SCRIPTS="transform-libtool-scripts"
+ else
+ AC_MSG_ERROR([--enable-local-library-preloading conflicts with --disable-shared])
+ fi
+ else
+ TRANSFORM_LIBTOOL_SCRIPTS=""
+ fi
+ ], [
+ TRANSFORM_LIBTOOL_SCRIPTS=""
+])
+AC_SUBST(TRANSFORM_LIBTOOL_SCRIPTS)
+
+dnl Check if -no-undefined is needed for the platform.
+dnl It should always work but with libtool 1.4.3 on OS X it breaks the build.
+dnl So we only turn it on for platforms where we know we really need it.
+AC_MSG_CHECKING([whether libtool needs -no-undefined])
+case $host in
+ *-*-cygwin*)
+ AC_MSG_RESULT([yes])
+ LT_NO_UNDEFINED="-no-undefined"
+ ;;
+ *)
+ AC_MSG_RESULT([no])
+ LT_NO_UNDEFINED=""
+ ;;
+esac
+AC_SUBST(LT_NO_UNDEFINED)
+
+AC_MSG_CHECKING([whether to avoid circular linkage at all costs])
+case $host in
+ *-*-cygwin*)
+ AC_MSG_RESULT([yes])
+ AC_DEFINE([SVN_AVOID_CIRCULAR_LINKAGE_AT_ALL_COSTS_HACK], 1,
+ [Define if circular linkage is not possible on this platform.])
+ ;;
+ *)
+ AC_MSG_RESULT([no])
+ ;;
+esac
+
+dnl Check for trang.
+trang=yes
+AC_ARG_WITH(trang,
+AS_HELP_STRING([--with-trang=PATH],
+ [Specify the command to run the trang schema converter]),
+[
+ trang="$withval"
+])
+if test "$trang" = "yes"; then
+ AC_PATH_PROG(TRANG, trang, none)
+else
+ TRANG="$trang"
+ AC_SUBST(TRANG)
+fi
+
+dnl Check for doxygen
+doxygen=yes
+AC_ARG_WITH(doxygen,
+AC_HELP_STRING([--with-doxygen=PATH],
+ [Specify the command to run doxygen]),
+[
+ doxygen="$withval"
+])
+if test "$doxygen" = "yes"; then
+ AC_PATH_PROG(DOXYGEN, doxygen, none)
+else
+ DOXYGEN="$doxygen"
+ AC_SUBST(DOXYGEN)
+fi
+
+
+dnl Check for libraries --------------------
+
+dnl Expat -------------------
+
+AC_ARG_WITH(expat,
+ AS_HELP_STRING([--with-expat=INCLUDES:LIB_SEARCH_DIRS:LIBS],
+ [Specify location of Expat]),
+ [svn_lib_expat="$withval"],
+ [svn_lib_expat="::expat"])
+
+# APR-util accepts "builtin" as an argument to this option so if the user
+# passed "builtin" pretend the user didn't specify the --with-expat option
+# at all. Expat will (hopefully) be found in apr-util.
+test "_$svn_lib_expat" = "_builtin" && svn_lib_expat="::expat"
+
+AC_MSG_CHECKING([for Expat])
+if test -n "`echo "$svn_lib_expat" | $EGREP ":.*:"`"; then
+ SVN_XML_INCLUDES=""
+ for i in [`echo "$svn_lib_expat" | $SED -e "s/\([^:]*\):.*/\1/"`]; do
+ SVN_XML_INCLUDES="$SVN_XML_INCLUDES -I$i"
+ done
+ SVN_XML_INCLUDES="${SVN_XML_INCLUDES## }"
+ for l in [`echo "$svn_lib_expat" | $SED -e "s/.*:\([^:]*\):.*/\1/"`]; do
+ LDFLAGS="$LDFLAGS -L$l"
+ done
+ for l in [`echo "$svn_lib_expat" | $SED -e "s/.*:\([^:]*\)/\1/"`]; do
+ SVN_XML_LIBS="$SVN_XML_LIBS -l$l"
+ done
+ SVN_XML_LIBS="${SVN_XML_LIBS## }"
+ old_CPPFLAGS="$CPPFLAGS"
+ old_LIBS="$LIBS"
+ CPPFLAGS="$CPPFLAGS $SVN_XML_INCLUDES"
+ LIBS="$LIBS $SVN_XML_LIBS"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[
+#include <expat.h>
+int main()
+{XML_ParserCreate(NULL);}]])], svn_lib_expat="yes", svn_lib_expat="no")
+ LIBS="$old_LIBS"
+ if test "$svn_lib_expat" = "yes"; then
+ AC_MSG_RESULT([yes])
+ else
+ SVN_XML_INCLUDES=""
+ SVN_XML_LIBS=""
+ CPPFLAGS="$CPPFLAGS $SVN_APRUTIL_INCLUDES"
+ if test "$enable_all_static" != "yes"; then
+ SVN_APRUTIL_LIBS="$SVN_APRUTIL_LIBS `$apu_config --libs`"
+ fi
+ AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
+#include <expat.h>
+int main()
+{XML_ParserCreate(NULL);}]])], svn_lib_expat="yes", svn_lib_expat="no")
+ if test "$svn_lib_expat" = "yes"; then
+ AC_MSG_RESULT([yes])
+ AC_MSG_WARN([Expat found amongst libraries used by APR-Util, but Subversion libraries might be needlessly linked against additional unused libraries. It can be avoided by specifying exact location of Expat in argument of --with-expat option.])
+ else
+ AC_MSG_RESULT([no])
+ AC_MSG_ERROR([Expat not found])
+ fi
+ fi
+ CPPFLAGS="$old_CPPFLAGS"
+else
+ AC_MSG_RESULT([no])
+ if test "$svn_lib_expat" = "yes"; then
+ AC_MSG_ERROR([--with-expat option requires argument])
+ elif test "$svn_lib_expat" = "no"; then
+ AC_MSG_ERROR([Expat is required])
+ else
+ AC_MSG_ERROR([Invalid syntax of argument of --with-expat option])
+ fi
+fi
+AC_SUBST(SVN_XML_INCLUDES)
+AC_SUBST(SVN_XML_LIBS)
+
+dnl Berkeley DB -------------------
+
+# Berkeley DB on SCO OpenServer needs -lsocket
+AC_CHECK_LIB(socket, socket)
+
+# Build the BDB filesystem library only if we have an appropriate
+# version of Berkeley DB.
+case "$host" in
+powerpc-apple-darwin*)
+ # Berkeley DB 4.0 does not work on OS X.
+ SVN_FS_WANT_DB_MAJOR=4
+ SVN_FS_WANT_DB_MINOR=1
+ SVN_FS_WANT_DB_PATCH=25
+ ;;
+*)
+ SVN_FS_WANT_DB_MAJOR=4
+ SVN_FS_WANT_DB_MINOR=0
+ SVN_FS_WANT_DB_PATCH=14
+ ;;
+esac
+# Look for libdb4.so first:
+SVN_LIB_BERKELEY_DB($SVN_FS_WANT_DB_MAJOR, $SVN_FS_WANT_DB_MINOR,
+ $SVN_FS_WANT_DB_PATCH, [db4 db])
+
+AC_DEFINE_UNQUOTED(SVN_FS_WANT_DB_MAJOR, $SVN_FS_WANT_DB_MAJOR,
+ [The desired major version for the Berkeley DB])
+AC_DEFINE_UNQUOTED(SVN_FS_WANT_DB_MINOR, $SVN_FS_WANT_DB_MINOR,
+ [The desired minor version for the Berkeley DB])
+AC_DEFINE_UNQUOTED(SVN_FS_WANT_DB_PATCH, $SVN_FS_WANT_DB_PATCH,
+ [The desired patch version for the Berkeley DB])
+
+AC_SUBST(SVN_DB_INCLUDES)
+AC_SUBST(SVN_DB_LIBS)
+
+SVN_LIB_SASL
+
+if test "$svn_lib_sasl" = "yes"; then
+ AC_DEFINE(SVN_HAVE_SASL, 1,
+ [Defined if Cyrus SASL v2 is present on the system])
+fi
+
+dnl Mac OS specific features -------------------
+
+SVN_LIB_MACHO_ITERATE
+SVN_LIB_MACOS_PLIST
+SVN_LIB_MACOS_KEYCHAIN
+
+dnl APR_HAS_DSO -------------------
+
+AC_MSG_CHECKING([whether APR has support for DSOs])
+old_CPPFLAGS="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES"
+AC_PREPROC_IFELSE([AC_LANG_SOURCE([[
+#include <apr.h>
+#if !APR_HAS_DSO
+#error
+#endif]])],
+ APR_HAS_DSO="yes"
+ AC_MSG_RESULT([yes]),
+ APR_HAS_DSO="no"
+ AC_MSG_RESULT([no]))
+CPPFLAGS="$old_CPPFLAGS"
+
+
+dnl D-Bus (required for support for KWallet) -------------------
+
+if test -n "$PKG_CONFIG"; then
+ AC_MSG_CHECKING([for D-Bus .pc file])
+ if $PKG_CONFIG --exists dbus-1; then
+ AC_MSG_RESULT([yes])
+ old_CPPFLAGS="$CPPFLAGS"
+ old_LIBS="$LIBS"
+ DBUS_CPPFLAGS="`$PKG_CONFIG --cflags dbus-1`"
+ AC_MSG_CHECKING([D-Bus version])
+ DBUS_VERSION="`$PKG_CONFIG --modversion dbus-1`"
+ AC_MSG_RESULT([$DBUS_VERSION])
+ # D-Bus 0.* requires DBUS_API_SUBJECT_TO_CHANGE
+ if test -n ["`echo "$DBUS_VERSION" | $EGREP '^0\.[[:digit:]]+'`"]; then
+ DBUS_CPPFLAGS="$DBUS_CPPFLAGS -DDBUS_API_SUBJECT_TO_CHANGE"
+ fi
+ DBUS_LIBS="`$PKG_CONFIG --libs dbus-1`"
+ CPPFLAGS="$CPPFLAGS $DBUS_CPPFLAGS"
+ LIBS="$LIBS $DBUS_LIBS"
+ AC_MSG_CHECKING([for D-Bus])
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[
+#include <dbus/dbus.h>
+int main()
+{dbus_bus_get(DBUS_BUS_SESSION, NULL);}]])], HAVE_DBUS="yes", HAVE_DBUS="no")
+ if test "$HAVE_DBUS" = "yes"; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ fi
+ CPPFLAGS="$old_CPPFLAGS"
+ LIBS="$old_LIBS"
+ else
+ AC_MSG_RESULT([no])
+ fi
+fi
+
+dnl GPG Agent -------------------
+
+AC_ARG_WITH(gpg_agent,
+AS_HELP_STRING([--without-gpg-agent],
+ [Disable support for GPG-Agent]),
+ [], [with_gpg_agent=yes])
+AC_MSG_CHECKING([whether to support GPG-Agent])
+if test "$with_gpg_agent" = "yes"; then
+ AC_MSG_RESULT([yes])
+ AC_DEFINE([SVN_HAVE_GPG_AGENT], [1],
+ [Is GPG Agent support enabled?])
+else
+ AC_MSG_RESULT([no])
+fi
+
+AC_SUBST(SVN_HAVE_GPG_AGENT)
+
+dnl GNOME Keyring -------------------
+
+AC_ARG_WITH(gnome_keyring,
+ AS_HELP_STRING([--with-gnome-keyring],
+ [Enable use of GNOME Keyring for auth credentials (enabled by default if found)]),
+ [with_gnome_keyring="$withval"],
+ [with_gnome_keyring=auto])
+
+found_gnome_keyring=no
+AC_MSG_CHECKING([whether to look for GNOME Keyring])
+if test "$with_gnome_keyring" != "no"; then
+ AC_MSG_RESULT([yes])
+ if test "$svn_enable_shared" = "yes"; then
+ if test "$APR_HAS_DSO" = "yes"; then
+ if test -n "$PKG_CONFIG"; then
+ AC_MSG_CHECKING([for GLib and GNOME Keyring .pc files])
+ if $PKG_CONFIG --exists glib-2.0 gnome-keyring-1; then
+ AC_MSG_RESULT([yes])
+ old_CPPFLAGS="$CPPFLAGS"
+ SVN_GNOME_KEYRING_INCLUDES="`$PKG_CONFIG --cflags glib-2.0 gnome-keyring-1`"
+ CPPFLAGS="$CPPFLAGS $SVN_GNOME_KEYRING_INCLUDES"
+ AC_CHECK_HEADER(gnome-keyring.h, found_gnome_keyring=yes, found_gnome_keyring=no)
+ AC_MSG_CHECKING([for GNOME Keyring])
+ if test "$found_gnome_keyring" = "yes"; then
+ AC_MSG_RESULT([yes])
+ AC_DEFINE([SVN_HAVE_GNOME_KEYRING], [1],
+ [Is GNOME Keyring support enabled?])
+ CPPFLAGS="$old_CPPFLAGS"
+ SVN_GNOME_KEYRING_LIBS="`$PKG_CONFIG --libs glib-2.0 gnome-keyring-1`"
+ else
+ AC_MSG_RESULT([no])
+ if test "$with_gnome_keyring" = "yes"; then
+ AC_MSG_ERROR([cannot find GNOME Keyring])
+ fi
+ fi
+ else
+ AC_MSG_RESULT([no])
+ if test "$with_gnome_keyring" = "yes"; then
+ AC_MSG_ERROR([cannot find GLib and GNOME Keyring .pc files.])
+ else
+ with_gnome_keyring=no
+ fi
+ fi
+ else
+ if test "$with_gnome_keyring" = "yes"; then
+ AC_MSG_ERROR([cannot find pkg-config. GNOME Keyring requires this.])
+ else
+ with_gnome_keyring=no
+ fi
+ fi
+ else
+ if test "$with_gnome_keyring" = "yes"; then
+ AC_MSG_ERROR([APR does not have support for DSOs. GNOME Keyring requires this.])
+ else
+ with_gnome_keyring=no
+ fi
+ fi
+ else
+ if test "$with_gnome_keyring" = "yes"; then
+ AC_MSG_ERROR([--with-gnome-keyring conflicts with --disable-shared])
+ else
+ with_gnome_keyring=no
+ fi
+ fi
+else
+ AC_MSG_RESULT([no])
+fi
+AC_SUBST(SVN_GNOME_KEYRING_INCLUDES)
+AC_SUBST(SVN_GNOME_KEYRING_LIBS)
+
+
+dnl Ev2 experimental features ----------------------
+dnl Note: The Ev2 implementations will be built unconditionally, but by
+dnl providing this flag, users can choose to use the currently-shimmed Ev2
+dnl editor implementations for various operations. This will probably
+dnl negatively impact performance, but is useful for testing.
+AC_ARG_ENABLE(ev2-impl,
+ AS_HELP_STRING([--enable-ev2-impl],
+ [Use Ev2 implementations, where available [EXPERIMENTAL]]),
+ [enable_ev2_impl=$enableval],[enable_ev2_impl=no])
+if test "$enable_ev2_impl" = "yes"; then
+ AC_DEFINE(ENABLE_EV2_IMPL, 1,
+ [Define to 1 if Ev2 implementations should be used.])
+fi
+
+
+dnl I18n -------------------
+
+AC_ARG_ENABLE(nls,
+ AS_HELP_STRING([--disable-nls],[Disable gettext functionality]),
+ [enable_nls=$enableval],[enable_nls=yes])
+
+USE_NLS="no"
+if test "$enable_nls" = "yes"; then
+ dnl First, check to see if there is a working msgfmt.
+ AC_PATH_PROG(MSGFMT, msgfmt, none)
+ AC_PATH_PROG(MSGMERGE, msgmerge, none)
+ AC_PATH_PROG(XGETTEXT, xgettext, none)
+ if test "$MSGFMT" != "none"; then
+ AC_SEARCH_LIBS(bindtextdomain, [intl], [],
+ [
+ enable_nls="no"
+ ])
+ if test "$enable_nls" = "no"; then
+ # Destroy the cached result so we can test again
+ unset ac_cv_search_bindtextdomain
+ # On some systems, libintl needs libiconv to link properly,
+ # so try again with -liconv.
+ AC_SEARCH_LIBS(bindtextdomain, [intl],
+ [
+ enable_nls="yes"
+ # This is here so that -liconv ends up in LIBS
+ # if it worked with -liconv.
+ AC_CHECK_LIB(iconv, libiconv_open)
+ ],
+ [
+ AC_MSG_WARN([bindtextdomain() not found. Disabling NLS.])
+ enable_nls="no"
+ ], -liconv)
+ fi
+ if test "$enable_nls" = "yes"; then
+ AC_DEFINE(ENABLE_NLS, 1,
+ [Define to 1 if translation of program messages to the user's
+ native language is requested.])
+ USE_NLS="yes"
+ fi
+ fi
+fi
+
+AH_BOTTOM([
+/* Indicate to translators that string X should be translated. Do not look
+ up the translation at run time; just expand to X. This macro is suitable
+ for use where a constant string is required at compile time. */
+#define N_(x) x
+/* Indicate to translators that we have decided the string X should not be
+ translated. Expand to X. */
+#define U_(x) x
+#ifdef ENABLE_NLS
+#include <locale.h>
+#include <libintl.h>
+/* Indicate to translators that string X should be translated. At run time,
+ look up and return the translation of X. */
+#define _(x) dgettext(PACKAGE_NAME, x)
+/* Indicate to translators that strings X1 and X2 are singular and plural
+ forms of the same message, and should be translated. At run time, return
+ an appropriate translation depending on the number N. */
+#define Q_(x1, x2, n) dngettext(PACKAGE_NAME, x1, x2, n)
+#else
+#define _(x) (x)
+#define Q_(x1, x2, n) (((n) == 1) ? x1 : x2)
+#define gettext(x) (x)
+#define dgettext(domain, x) (x)
+#endif
+])
+
+dnl Used to simulate makefile conditionals.
+GETTEXT_CODESET=\#
+NO_GETTEXT_CODESET=\#
+if test $USE_NLS = "yes"; then
+ AC_CHECK_FUNCS(bind_textdomain_codeset,
+ [ GETTEXT_CODESET="" ],
+ [ NO_GETTEXT_CODESET="" ])
+fi
+AC_SUBST(GETTEXT_CODESET)
+AC_SUBST(NO_GETTEXT_CODESET)
+
+# Check if we are using GNU gettext.
+GNU_GETTEXT=no
+MSGFMTFLAGS=''
+if test $USE_NLS = "yes"; then
+ AC_MSG_CHECKING(if we are using GNU gettext)
+ if $MSGFMT --version 2>&1 | $EGREP GNU > /dev/null; then
+ GNU_GETTEXT=yes
+ MSGFMTFLAGS='-c'
+ fi
+ AC_MSG_RESULT($GNU_GETTEXT)
+fi
+AC_SUBST(MSGFMTFLAGS)
+
+dnl libmagic -------------------
+
+libmagic_found=no
+
+AC_ARG_WITH(libmagic,AS_HELP_STRING([--with-libmagic=PREFIX],
+ [libmagic filetype detection library]),
+[
+ if test "$withval" = "yes" ; then
+ AC_CHECK_HEADER(magic.h, [
+ AC_CHECK_LIB(magic, magic_open, [libmagic_found="builtin"])
+ ])
+ libmagic_prefix="the default locations"
+ elif test "$withval" != "no"; then
+ libmagic_prefix=$withval
+ save_cppflags="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS -I$libmagic_prefix/include"
+ AC_CHECK_HEADERS(magic.h,[
+ save_ldflags="$LDFLAGS"
+ LDFLAGS="-L$libmagic_prefix/lib"
+ AC_CHECK_LIB(magic, magic_open, [libmagic_found="yes"])
+ LDFLAGS="$save_ldflags"
+ ])
+ CPPFLAGS="$save_cppflags"
+ fi
+ if test "$withval" != "no" && test "$libmagic_found" = "no"; then
+ AC_MSG_ERROR([[--with-libmagic requested, but libmagic not found at $libmagic_prefix]])
+ fi
+],
+[
+ AC_CHECK_HEADER(magic.h, [
+ AC_CHECK_LIB(magic, magic_open, [libmagic_found="builtin"])
+ ])
+])
+
+if test "$libmagic_found" != "no"; then
+ AC_DEFINE([SVN_HAVE_LIBMAGIC], [1], [Defined if libmagic support is enabled])
+ SVN_MAGIC_LIBS="-lmagic"
+fi
+
+if test "$libmagic_found" = "yes"; then
+ SVN_MAGIC_INCLUDES="-I$libmagic_prefix/include"
+ LDFLAGS="$LDFLAGS `SVN_REMOVE_STANDARD_LIB_DIRS(-L$libmagic_prefix/lib)`"
+fi
+
+AC_SUBST(SVN_MAGIC_INCLUDES)
+AC_SUBST(SVN_MAGIC_LIBS)
+
+dnl KWallet -------------------
+SVN_LIB_KWALLET
+
+if test "$svn_lib_kwallet" = "yes"; then
+ AC_DEFINE([SVN_HAVE_KWALLET], 1,
+ [Defined if KWallet support is enabled])
+fi
+
+dnl plaintext passwords -------------------
+AC_ARG_ENABLE(plaintext-password-storage,
+AS_HELP_STRING([--disable-plaintext-password-storage],
+ [Disable on-disk caching of plaintext passwords and passphrases.
+ (Leaving this functionality enabled will not force Subversion
+ to store passwords in plaintext, but does permit users to
+ explicitly allow that behavior via runtime configuration.)]),
+[
+ if test "$enableval" = "no"; then
+ AC_MSG_NOTICE([Disabling plaintext password/passphrase storage])
+ AC_DEFINE(SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE, 1,
+ [Defined if plaintext password/passphrase storage is disabled])
+ fi
+])
+
+dnl Build and install rules -------------------
+
+INSTALL_STATIC_RULES="install-bin install-docs"
+INSTALL_RULES="install-fsmod-lib install-ramod-lib install-lib install-include install-static"
+INSTALL_RULES="$INSTALL_RULES $INSTALL_APACHE_RULE"
+BUILD_RULES="fsmod-lib ramod-lib lib bin test sub-test $BUILD_APACHE_RULE tools"
+
+if test "$svn_lib_berkeley_db" = "yes"; then
+ BUILD_RULES="$BUILD_RULES bdb-lib bdb-test"
+ INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-fsmod-lib/install-fsmod-lib install-bdb-lib/'`"
+ INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-bdb-lib"
+ BDB_TEST_DEPS="\$(BDB_TEST_DEPS)"
+ BDB_TEST_PROGRAMS="\$(BDB_TEST_PROGRAMS)"
+fi
+
+if test "$svn_lib_serf" = "yes"; then
+ BUILD_RULES="$BUILD_RULES serf-lib"
+ INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-ramod-lib/install-ramod-lib install-serf-lib/'`"
+ INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-serf-lib"
+fi
+
+if test "$svn_lib_kwallet" = "yes"; then
+ BUILD_RULES="$BUILD_RULES kwallet-lib"
+ INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-lib/install-lib install-kwallet-lib/'`"
+ INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-kwallet-lib"
+fi
+
+if test "$found_gnome_keyring" = "yes"; then
+ BUILD_RULES="$BUILD_RULES gnome-keyring-lib"
+ INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-lib/install-lib install-gnome-keyring-lib/'`"
+ INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-gnome-keyring-lib"
+fi
+
+if test "$USE_NLS" = "yes"; then
+ BUILD_RULES="$BUILD_RULES locale"
+ INSTALL_RULES="$INSTALL_RULES install-locale"
+fi
+
+AC_SUBST(BUILD_RULES)
+AC_SUBST(INSTALL_STATIC_RULES)
+AC_SUBST(INSTALL_RULES)
+AC_SUBST(BDB_TEST_DEPS)
+AC_SUBST(BDB_TEST_PROGRAMS)
+
+dnl Check for header files ----------------
+
+dnl Standard C headers
+AC_HEADER_STDC
+
+dnl Check for typedefs, structures, and compiler characteristics ----------
+
+dnl if compiler doesn't understand `const', then define it empty
+AC_C_CONST
+
+dnl if non-existent, define size_t to be `unsigned'
+AC_TYPE_SIZE_T
+
+
+dnl Check for library functions ----------
+
+AC_FUNC_MEMCMP
+
+dnl svn_error's default warning handler uses vfprintf()
+AC_FUNC_VPRINTF
+
+dnl check for functions needed in special file handling
+AC_CHECK_FUNCS(symlink readlink)
+
+dnl check for uname
+AC_CHECK_HEADERS(sys/utsname.h, [AC_CHECK_FUNCS(uname)], [])
+
+dnl check for termios
+AC_CHECK_HEADER(termios.h,[
+ AC_CHECK_FUNCS(tcgetattr tcsetattr,[
+ AC_DEFINE(HAVE_TERMIOS_H,1,[Defined if we have a usable termios library.])
+ ])
+])
+
+dnl Process some configuration options ----------
+
+AC_ARG_WITH(openssl,
+AS_HELP_STRING([--with-openssl],
+ [This option does NOT affect the Subversion build process in any
+ way. It tells an integrated Serf HTTP client library build
+ process where to locate the OpenSSL library when (and only when)
+ building Serf as an integrated part of the Subversion build
+ process. When linking to a previously installed version of Serf
+ instead, you do not need to use this option.]),
+[])
+
+AC_ARG_ENABLE(debug,
+AS_HELP_STRING([--enable-debug],
+ [Turn on debugging]),
+[
+ if test "$enableval" = "yes" ; then
+ enable_debugging="yes"
+ else
+ enable_debugging="no"
+ fi
+],
+[
+ # Neither --enable-debug nor --disable-debug was passed.
+ enable_debugging="maybe"
+])
+
+AC_ARG_ENABLE(optimize,
+AS_HELP_STRING([--enable-optimize],
+ [Turn on optimizations]),
+[
+ if test "$enableval" = "yes" ; then
+ enable_optimization="yes"
+ else
+ enable_optimization="no"
+ fi
+],
+[
+ # Neither --enable-optimize nor --disable-optimize was passed.
+ enable_optimization="maybe"
+])
+
+dnl Use -Wl,--no-undefined during linking of some libraries
+AC_ARG_ENABLE(disallowing-of-undefined-references,
+ [AS_HELP_STRING([--enable-disallowing-of-undefined-references],
+ [Use -Wl,--no-undefined flag during linking of some libraries to disallow undefined references])])
+if test "$enable_disallowing_of_undefined_references" != "yes" && test "`uname`" != "Linux"; then
+ enable_disallowing_of_undefined_references="no"
+fi
+if test "$enable_disallowing_of_undefined_references" != "no"; then
+ AC_MSG_CHECKING([for -Wl,--no-undefined])
+ old_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS -Wl,--no-undefined"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(){;}]])], [svn_wl_no_undefined="yes"], [svn_wl_no_undefined="no"])
+ LDFLAGS="$old_LDFLAGS"
+ if test "$svn_wl_no_undefined" = "yes"; then
+ AC_MSG_RESULT([yes])
+ for library_dir in "$abs_srcdir/subversion/libsvn_"*; do
+ eval "`basename $library_dir`_LDFLAGS=-Wl,--no-undefined"
+ done
+ else
+ AC_MSG_RESULT([no])
+ if test "$enable_disallowing_of_undefined_references" = "yes"; then
+ AC_MSG_ERROR([--enable-disallowing-of-undefined-references explicitly requested, but -Wl,--no-undefined not supported])
+ fi
+ fi
+fi
+AC_SUBST([libsvn_auth_gnome_keyring_LDFLAGS])
+AC_SUBST([libsvn_auth_kwallet_LDFLAGS])
+AC_SUBST([libsvn_client_LDFLAGS])
+AC_SUBST([libsvn_delta_LDFLAGS])
+AC_SUBST([libsvn_diff_LDFLAGS])
+AC_SUBST([libsvn_fs_LDFLAGS])
+AC_SUBST([libsvn_fs_base_LDFLAGS])
+AC_SUBST([libsvn_fs_fs_LDFLAGS])
+AC_SUBST([libsvn_fs_util_LDFLAGS])
+AC_SUBST([libsvn_ra_LDFLAGS])
+AC_SUBST([libsvn_ra_local_LDFLAGS])
+AC_SUBST([libsvn_ra_serf_LDFLAGS])
+AC_SUBST([libsvn_ra_svn_LDFLAGS])
+AC_SUBST([libsvn_repos_LDFLAGS])
+AC_SUBST([libsvn_subr_LDFLAGS])
+AC_SUBST([libsvn_wc_LDFLAGS])
+
+
+AC_ARG_ENABLE(maintainer-mode,
+AS_HELP_STRING([--enable-maintainer-mode],
+ [Turn on debugging and very strict compile-time warnings]),
+[
+ if test "$enableval" = "yes" ; then
+ if test "$enable_debugging" = "no" ; then
+ AC_MSG_ERROR([Can't have --disable-debug and --enable-maintainer-mode])
+ fi
+ enable_debugging=yes
+
+ dnl Enable some extra warnings. Put these before the user's flags
+ dnl so the user can specify flags that override these.
+ if test "$GCC" = "yes"; then
+ AC_MSG_NOTICE([maintainer-mode: adding GCC warning flags])
+
+ dnl some additional flags that can be handy for an occasional review,
+ dnl but throw too many warnings in svn code, of too little importance,
+ dnl to keep these enabled. Remove the "dnl" to do a run with these
+ dnl switches enabled.
+ dnl ./configure CUSERFLAGS="-Wswitch-enum -Wswitch-default"
+
+ dnl Add each of the following flags only if the C compiler accepts it.
+ CFLAGS_KEEP="$CFLAGS"
+ CFLAGS=""
+
+ SVN_CFLAGS_ADD_IFELSE([-Werror=implicit-function-declaration])
+ SVN_CFLAGS_ADD_IFELSE([-Werror=declaration-after-statement])
+ SVN_CFLAGS_ADD_IFELSE([-Wextra-tokens])
+ SVN_CFLAGS_ADD_IFELSE([-Wnewline-eof])
+ SVN_CFLAGS_ADD_IFELSE([-Wshorten-64-to-32])
+ SVN_CFLAGS_ADD_IFELSE([-Wold-style-definition])
+ SVN_CFLAGS_ADD_IFELSE([-Wno-system-headers])
+ SVN_CFLAGS_ADD_IFELSE([-Wno-format-nonliteral])
+
+ CMAINTAINERFLAGS="$CFLAGS $CMAINTAINERFLAGS"
+ CFLAGS="$CFLAGS_KEEP"
+
+ dnl Add flags that all versions of GCC (should) support
+ CMAINTAINERFLAGS="-Wall -Wpointer-arith -Wwrite-strings -Wshadow -Wformat=2 -Wunused -Waggregate-return -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wno-multichar -Wredundant-decls -Wnested-externs -Winline -Wno-long-long $CMAINTAINERFLAGS"
+ fi
+ if test "$GXX" = "yes"; then
+ AC_MSG_NOTICE([maintainer-mode: adding G++ warning flags])
+
+ dnl Add each of the following flags only if the C++ compiler accepts it.
+ CXXFLAGS_KEEP="$CXXFLAGS"
+ CXXFLAGS=""
+
+ SVN_CXXFLAGS_ADD_IFELSE([-Wextra-tokens])
+ SVN_CXXFLAGS_ADD_IFELSE([-Wnewline-eof])
+ SVN_CXXFLAGS_ADD_IFELSE([-Wshorten-64-to-32])
+ SVN_CXXFLAGS_ADD_IFELSE([-Wno-system-headers])
+
+ CXXMAINTAINERFLAGS="$CXXFLAGS $CXXMAINTAINERFLAGS"
+ CXXFLAGS="$CXXFLAGS_KEEP"
+
+ dnl Add flags that all versions of G++ (should) support
+ CXXMAINTAINERFLAGS="-Wall -Wpointer-arith -Wwrite-strings -Wshadow -Wunused -Wunreachable-code $CXXMAINTAINERFLAGS"
+ fi
+ fi
+])
+
+if test "$enable_debugging" = "yes" ; then
+ dnl At the moment, we don't want optimization, because we're
+ dnl debugging. Unless optiization was explicitly enabled.
+ if test "$enable_optimization" != "yes"; then
+ AC_MSG_NOTICE([Disabling optimizations for debugging])
+ CFLAGS=["`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"]
+ CXXFLAGS=["`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"]
+ fi
+ dnl Add debugging flags, unless they were set by the user
+ if test -z ["`echo $CUSERFLAGS' ' | $EGREP -- '-g[0-9]? '`"]; then
+ AC_MSG_NOTICE([Enabling debugging for C])
+ CFLAGS=["`echo $CFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"]
+ SVN_CFLAGS_ADD_IFELSE([-fno-inline])
+ SVN_CFLAGS_ADD_IFELSE([-fno-omit-frame-pointer])
+ SVN_CFLAGS_ADD_IFELSE([-g3],[],[
+ SVN_CFLAGS_ADD_IFELSE([-g2],[],[
+ SVN_CFLAGS_ADD_IFELSE([-g])])])
+ fi
+ if test -z ["`echo $CXXUSERFLAGS' ' | $EGREP -- '-g[0-9]? '`"]; then
+ AC_MSG_NOTICE([Enabling debugging for C++])
+ CXXFLAGS=["`echo $CXXFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"]
+ SVN_CXXFLAGS_ADD_IFELSE([-fno-inline])
+ SVN_CXXFLAGS_ADD_IFELSE([-fno-omit-frame-pointer])
+ SVN_CXXFLAGS_ADD_IFELSE([-g3],[],[
+ SVN_CXXFLAGS_ADD_IFELSE([-g2],[],[
+ SVN_CXXFLAGS_ADD_IFELSE([-g])])])
+ fi
+ dnl SVN_DEBUG enables specific features for developer builds
+ dnl AP_DEBUG enables specific (Apache) features for developer builds
+ CFLAGS="$CFLAGS -DSVN_DEBUG -DAP_DEBUG"
+ CXXFLAGS="$CXXFLAGS -DSVN_DEBUG -DAP_DEBUG"
+elif test "$enable_debugging" = "no" ; then
+ AC_MSG_NOTICE([Disabling debugging])
+ CFLAGS=["`echo $CFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"]
+ CXXFLAGS=["`echo $CXXFLAGS' ' | $SED -e 's/-g[0-9] //g' -e 's/-g //g'`"]
+ dnl Compile with NDEBUG to get rid of assertions
+ CFLAGS="$CFLAGS -DNDEBUG"
+ CXXFLAGS="$CXXFLAGS -DNDEBUG"
+# elif test "$enable_debugging" = "maybe" ; then
+# # do nothing
+fi
+
+if test "$enable_optimization" = "yes"; then
+ dnl Add optimization flags, unless they were set by the user
+ if test -z ["`echo $CUSERFLAGS' ' | $EGREP -- '-O[^ ]* '`"]; then
+ CFLAGS=["`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"]
+ if test "$enable_debugging" = "yes"; then
+ AC_MSG_NOTICE([Enabling optimizations for C (with debugging enabled)])
+ SVN_CFLAGS_ADD_IFELSE([-O1],[],[
+ SVN_CFLAGS_ADD_IFELSE([-O])])
+ else
+ AC_MSG_NOTICE([Enabling optimizations for C])
+ SVN_CFLAGS_ADD_IFELSE([-O3],[],[
+ SVN_CFLAGS_ADD_IFELSE([-O2],[],[
+ SVN_CFLAGS_ADD_IFELSE([-O1],[],[
+ SVN_CFLAGS_ADD_IFELSE([-O])])])])
+ SVN_CFLAGS_ADD_IFELSE([-Wno-clobbered])
+ SVN_CFLAGS_ADD_IFELSE([-flto])
+ SVN_CFLAGS_ADD_IFELSE([-fwhole-program])
+ fi
+ fi
+ if test -z ["`echo $CXXUSERFLAGS' ' | $EGREP -- '-O[^ ]* '`"]; then
+ CXXFLAGS=["`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"]
+ if test "$enable_debugging" = "yes"; then
+ AC_MSG_NOTICE([Enabling optimizations for C++ (with debugging enabled)])
+ SVN_CXXFLAGS_ADD_IFELSE([-O1],[],[
+ SVN_CXXFLAGS_ADD_IFELSE([-O])])
+ else
+ AC_MSG_NOTICE([Enabling optimizations for C++])
+ SVN_CXXFLAGS_ADD_IFELSE([-O3],[],[
+ SVN_CXXFLAGS_ADD_IFELSE([-O2],[],[
+ SVN_CXXFLAGS_ADD_IFELSE([-O1],[],[
+ SVN_CXXFLAGS_ADD_IFELSE([-O])])])])
+ SVN_CXXFLAGS_ADD_IFELSE([-Wno-clobbered])
+ SVN_CXXFLAGS_ADD_IFELSE([-flto])
+ SVN_CXXFLAGS_ADD_IFELSE([-fwhole-program])
+ fi
+ fi
+elif test "$enable_optimization" = "no"; then
+ dnl Remove all optimization flags
+ AC_MSG_NOTICE([Disabling optimizations])
+ CFLAGS=["`echo $CFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"]
+ CXXFLAGS=["`echo $CXXFLAGS' ' | $SED -e 's/-O[^ ]* //g'`"]
+# elif test "$enable_optimization" = "maybe" ; then
+# # do nothing
+fi
+
+
+AC_ARG_ENABLE(full-version-match,
+AS_HELP_STRING([--disable-full-version-match],
+ [Disable the full version match rules when checking
+ Subversion library compatibility.]),
+[
+ if test "$enableval" = "no" ; then
+ AC_MSG_NOTICE([Disabling svn full version matching])
+ AC_DEFINE(SVN_DISABLE_FULL_VERSION_MATCH, 1,
+ [Defined if the full version matching rules are disabled])
+ fi
+])
+
+AC_ARG_WITH(editor,
+AS_HELP_STRING([--with-editor=PATH],
+ [Specify a default editor for the subversion client.]),
+[
+
+ if test "$withval" = "yes" ; then
+ AC_MSG_ERROR([--with-editor requires an argument.])
+ else
+ SVN_CLIENT_EDITOR=$withval
+ AC_DEFINE_UNQUOTED(SVN_CLIENT_EDITOR, "$SVN_CLIENT_EDITOR",
+ [The path of a default editor for the client.])
+
+ fi
+
+])
+
+SVN_LIB_Z
+
+MOD_ACTIVATION=""
+AC_ARG_ENABLE(mod-activation,
+AS_HELP_STRING([--enable-mod-activation],
+ [Enable mod_dav_svn in httpd.conf]),
+[
+ if test "$enableval" = "yes" ; then
+ MOD_ACTIVATION="-a"
+ AC_MSG_NOTICE([Enabling apache module activation])
+ else
+ AC_MSG_NOTICE([Disabling apache module activation])
+ fi
+])
+AC_SUBST(MOD_ACTIVATION)
+
+
+
+AC_ARG_ENABLE(gcov,
+AC_HELP_STRING([--enable-gcov],
+ [Turn on gcov coverage testing (GCC only).]),
+[
+ if test "$enableval" = "yes" ; then
+ dnl Probably other compilers support something similar;
+ dnl feel free to extend this to include them.
+ if test "$GCC" = "yes"; then
+ if test "$svn_enable_shared" = "yes" ; then
+ AC_MSG_ERROR([Can't have --enable-gcov without --disable-shared (we
+ recommend also using --enable-all-static).])
+ fi
+ if test ! "$enable_all_static" = "yes" ; then
+ AC_MSG_WARN(We recommend --enable-all-static with --enable-gcov.)
+ fi
+ AC_MSG_NOTICE([Enabling gcov coverage testing.])
+ CFLAGS="$CFLAGS -fprofile-arcs -ftest-coverage"
+ CXXFLAGS="$CXXFLAGS -fprofile-arcs -ftest-coverage"
+ else
+ AC_MSG_ERROR([We only support --enable-gcov with GCC right now.])
+ fi
+ fi
+])
+
+AC_ARG_ENABLE(gprof,
+AS_HELP_STRING([--enable-gprof],
+ [Produce gprof profiling data in 'gmon.out' (GCC only).]),
+[
+ if test "$enableval" = "yes" ; then
+ dnl Probably other compilers support -pg or something similar;
+ dnl feel free to extend this to include them.
+ if test "$GCC" = "yes"; then
+ if test "$svn_enable_shared" = "yes" ; then
+ AC_MSG_ERROR([Can't have --enable-gprof without --disable-shared (we
+ recommend also using --enable-all-static).])
+ fi
+ if test ! "$enable_all_static" = "yes" ; then
+ AC_MSG_WARN(We recommend --enable-all-static with --enable-gprof.)
+ fi
+ AC_MSG_NOTICE([Enabling gprof profiling data (to gmon.out).])
+ CFLAGS="$CFLAGS -pg"
+ CXXFLAGS="$CXXFLAGS -pg"
+ LT_LDFLAGS="$LT_LDFLAGS -pg"
+ else
+ AC_MSG_ERROR([We only support --enable-gprof with GCC right now.])
+ fi
+ fi
+])
+
+
+# Scripting and Bindings languages
+
+# Python: Used for testsuite, and bindings
+
+
+PYTHON="`$abs_srcdir/build/find_python.sh`"
+if test -z "$PYTHON"; then
+ AC_MSG_WARN([Python 2.5 or later is required to run the testsuite])
+ AC_MSG_WARN([or to use the Subversion Python bindings])
+ AC_MSG_WARN([])
+ AC_MSG_WARN([If you have a suitable Python installed, but not on the])
+ AC_MSG_WARN([PATH, set the environment variable PYTHON to the full path])
+ AC_MSG_WARN([to the Python executable, and re-run configure])
+fi
+AC_PATH_PROGS(PYTHON, "$PYTHON", none)
+
+# The minimum version for the JVM runtime for our Java bytecode.
+JAVA_OLDEST_WORKING_VER='1.5'
+# SVN_CHECK_JDK sets $JAVA_CLASSPATH
+SVN_CHECK_JDK($JAVA_OLDEST_WORKING_VER)
+
+AC_PATH_PROG(PERL, perl, none)
+
+if test -n "$RUBY"; then
+ AC_PATH_PROG(RUBY, "$RUBY", none)
+else
+ AC_PATH_PROGS(RUBY, ruby ruby1.8 ruby18 ruby1.9 ruby1 ruby1.9.3 ruby193, none)
+fi
+if test "$RUBY" != "none"; then
+ AC_MSG_CHECKING([rb_hash_foreach])
+ if "$RUBY" -r mkmf -e 'exit(have_func("rb_hash_foreach") ? 0 : 1)' >/dev/null; then
+ AC_MSG_RESULT([yes])
+ if test -n "$RDOC"; then
+ AC_PATH_PROG(RDOC, "$RDOC", none)
+ else
+ AC_PATH_PROGS(RDOC, rdoc rdoc1.8 rdoc18 rdoc1.9 rdoc19 rdoc1.9.3 rdoc193, none)
+ fi
+ AC_CACHE_CHECK([for Ruby major version], [svn_cv_ruby_major],[
+ svn_cv_ruby_major="`$RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MAJOR))'`"
+ ])
+ RUBY_MAJOR="$svn_cv_ruby_major"
+
+ AC_CACHE_CHECK([for Ruby minor version], [svn_cv_ruby_minor],[
+ svn_cv_ruby_minor="`$RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MINOR))'`"
+ ])
+ RUBY_MINOR="$svn_cv_ruby_minor"
+
+ AC_CACHE_CHECK([for Ruby teeny version], [svn_cv_ruby_teeny],[
+ svn_cv_ruby_teeny="`$RUBY -rrbconfig -e 'major, minor, teeny = RUBY_VERSION.split("."); print teeny;'`"
+ ])
+ RUBY_TEENY="$svn_cv_ruby_teeny"
+
+ AC_SUBST(RUBY_MAJOR)
+ AC_SUBST(RUBY_MINOR)
+ AC_SUBST(RUBY_TEENY)
+ if test \( "$RUBY_MAJOR" -eq "1" -a "$RUBY_MINOR" -gt "8" -a "$RUBY_TEENY" -lt "3" \); then
+ # Disallow Ruby between 1.8.7 and 1.9.3
+ RUBY="none"
+ AC_MSG_WARN([The detected Ruby is between 1.9 and 1.9.3])
+ AC_MSG_WARN([Only 1.8.x and 1.9.3 releases are supported at this time])
+ elif test \( "$RUBY_MAJOR" -eq "1" -a "$RUBY_MINOR" -eq "9" -a "$RUBY_TEENY" -eq "3" \); then
+ #Warn about 1.9.3 support
+ AC_MSG_WARN([WARNING: The detected Ruby is 1.9.3])
+ AC_MSG_WARN([WARNING: Only 1.8.x releases are fully supported, 1.9.3 support is new])
+ fi
+ else
+ AC_MSG_RESULT([no])
+ RUBY="none"
+ AC_MSG_WARN([The detected Ruby is too old for Subversion to use])
+ AC_MSG_WARN([A Ruby which has rb_hash_foreach is required to use the])
+ AC_MSG_WARN([Subversion Ruby bindings])
+ AC_MSG_WARN([Upgrade to the official 1.8.2 release, or later])
+ fi
+fi
+
+SVN_CHECK_SWIG
+
+SVN_CHECK_CTYPESGEN
+
+dnl decide whether we want to link against the RA/FS libraries
+AC_ARG_ENABLE(runtime-module-search,
+AS_HELP_STRING([--enable-runtime-module-search],
+ [Turn on dynamic loading of RA/FS libraries including
+ third-party FS libraries]),
+[
+ if test "$enableval" = "yes"; then
+ use_dso=yes
+ if test "$svn_enable_shared" = "no"; then
+ AC_MSG_ERROR([--enable-runtime-module-search conflicts with --disable-shared])
+ fi
+ AC_DEFINE(SVN_USE_DSO, 1,
+ [Defined if svn should try to load DSOs])
+ fi
+])
+
+if test "$svn_enable_shared" = "no" || test "$use_dso" != "yes"; then
+ AC_DEFINE(SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL, 1,
+ [Defined if libsvn_client should link against libsvn_ra_local])
+ svn_ra_lib_deps="\$(RA_LOCAL_DEPS)"
+ svn_ra_lib_install_deps="install-ramod-lib"
+ svn_ra_lib_link="\$(RA_LOCAL_LINK)"
+
+ AC_DEFINE(SVN_LIBSVN_CLIENT_LINKS_RA_SVN, 1,
+ [Defined if libsvn_client should link against libsvn_ra_svn])
+ svn_ra_lib_deps="$svn_ra_lib_deps \$(RA_SVN_DEPS)"
+ svn_ra_lib_link="$svn_ra_lib_link \$(RA_SVN_LINK)"
+
+ if test "$svn_lib_serf" = "yes"; then
+ AC_DEFINE(SVN_LIBSVN_CLIENT_LINKS_RA_SERF, 1,
+ [Defined if libsvn_client should link against libsvn_ra_serf])
+ svn_ra_lib_deps="$svn_ra_lib_deps \$(RA_SERF_DEPS)"
+ svn_ra_lib_install_deps="$svn_ra_lib_install_deps install-serf-lib"
+ svn_ra_lib_link="$svn_ra_lib_link \$(RA_SERF_LINK)"
+ fi
+
+ SVN_RA_LIB_DEPS=$svn_ra_lib_deps
+ SVN_RA_LIB_INSTALL_DEPS=$svn_ra_lib_install_deps
+ SVN_RA_LIB_LINK=$svn_ra_lib_link
+
+ AC_DEFINE(SVN_LIBSVN_FS_LINKS_FS_FS, 1,
+ [Defined if libsvn_fs should link against libsvn_fs_fs])
+ svn_fs_lib_deps="\$(FS_FS_DEPS)"
+ svn_fs_lib_install_deps="install-fsmod-lib"
+ svn_fs_lib_link="\$(FS_FS_LINK)"
+
+ if test "$svn_lib_berkeley_db" = "yes"; then
+ AC_DEFINE(SVN_LIBSVN_FS_LINKS_FS_BASE, 1,
+ [Defined if libsvn_fs should link against libsvn_fs_base])
+ svn_fs_lib_deps="$svn_fs_lib_deps \$(FS_BASE_DEPS)"
+ svn_fs_lib_install_deps="$svn_fs_lib_install_deps install-bdb-lib"
+ svn_fs_lib_link="$svn_fs_lib_link \$(FS_BASE_LINK)"
+ fi
+
+ SVN_FS_LIB_DEPS=$svn_fs_lib_deps
+ SVN_FS_LIB_INSTALL_DEPS=$svn_fs_lib_install_deps
+ SVN_FS_LIB_LINK=$svn_fs_lib_link
+fi
+
+AC_SUBST(SVN_RA_LIB_DEPS)
+AC_SUBST(SVN_RA_LIB_INSTALL_DEPS)
+AC_SUBST(SVN_RA_LIB_LINK)
+AC_SUBST(SVN_FS_LIB_DEPS)
+AC_SUBST(SVN_FS_LIB_INSTALL_DEPS)
+AC_SUBST(SVN_FS_LIB_LINK)
+
+# ==== JavaHL ================================================================
+
+dnl Possibly compile JavaHL
+do_javahl_build=no
+AC_ARG_ENABLE(javahl,
+ AS_HELP_STRING([--enable-javahl],
+ [Enable compilation of Java high-level bindings (requires C++)]),
+ [ if test "$enableval" = "yes" ; then
+ do_javahl_build="yes"
+ fi
+ ])
+
+JAVAHL_OBJDIR=""
+INSTALL_EXTRA_JAVAHL_LIB=""
+FIX_JAVAHL_LIB=""
+JAVAHL_TESTS_TARGET=""
+JAVAHL_COMPAT_TESTS_TARGET=""
+LT_CXX_LIBADD=""
+if test "$do_javahl_build" = "yes"; then
+ dnl Check for suitable JDK
+ if test "$JDK_SUITABLE" = "no"; then
+ AC_MSG_ERROR([Cannot compile JavaHL without a suitable JDK.
+ Please specify a suitable JDK using the --with-jdk option.])
+ fi
+
+ dnl The temporary directory where libtool compiles libsvnjavahl.
+ JAVAHL_OBJDIR='$(libsvnjavahl_PATH)/.libs'
+
+ os_arch=`uname`
+ if test "$os_arch" = "Darwin"; then
+ dnl On Darwin, JNI libs must be installed as .jnilib
+ INSTALL_EXTRA_JAVAHL_LIB='ln -sf $(libdir)/libsvnjavahl-1.dylib $(libdir)/libsvnjavahl-1.jnilib'
+ FIX_JAVAHL_LIB="ln -sf libsvnjavahl-1.dylib $JAVAHL_OBJDIR/libsvnjavahl-1.jnilib"
+ fi
+ # This segment (and the rest of r10800) is very likely unnecessary
+ # with libtool 1.5, which automatically adds libstdc++ as a
+ # dependency to the C++ libraries it builds. So at some future time
+ # when autogen.sh requires libtool 1.5 or higher, we can get rid of
+ # it.
+ AC_MSG_CHECKING([for additional flags to link C++ libraries])
+ if test "x$ac_compiler_gnu" = "xyes"; then
+ LT_CXX_LIBADD="-lstdc++"
+ AC_MSG_RESULT([$LT_CXX_LIBADD])
+ else
+ AC_MSG_RESULT([none needed])
+ fi
+fi
+AC_SUBST(INSTALL_EXTRA_JAVAHL_LIB)
+AC_SUBST(JAVAHL_OBJDIR)
+AC_SUBST(FIX_JAVAHL_LIB)
+AC_SUBST(LT_CXX_LIBADD)
+
+AC_ARG_WITH(junit,
+AS_HELP_STRING([--with-junit=PATH],
+ [Specify a path to the junit JAR file.]),
+[
+ if test "$withval" != "no"; then
+ if test -n "$JAVA_CLASSPATH"; then
+ JAVA_CLASSPATH="$withval:$JAVA_CLASSPATH"
+ else
+ JAVA_CLASSPATH="$withval"
+ fi
+ JAVAHL_TESTS_TARGET="javahl-tests"
+ JAVAHL_COMPAT_TESTS_TARGET="javahl-compat-tests"
+ fi
+])
+AC_SUBST(JAVA_CLASSPATH)
+AC_SUBST(JAVAHL_TESTS_TARGET)
+AC_SUBST(JAVAHL_COMPAT_TESTS_TARGET)
+
+# ==== Miscellaneous bits ====================================================
+
+# Strip '-no-cpp-precomp' from CPPFLAGS for the clang compiler
+### I think we get this flag from APR, so the fix probably belongs there
+if test "$CC" = "clang"; then
+ SVN_STRIP_FLAG(CPPFLAGS, [-no-cpp-precomp ])
+fi
+
+dnl Since this is used only on Unix-y systems, define the path separator as '/'
+AC_DEFINE_UNQUOTED(SVN_PATH_LOCAL_SEPARATOR, '/',
+ [Defined to be the path separator used on your local filesystem])
+
+AC_DEFINE_UNQUOTED(SVN_NULL_DEVICE_NAME, "/dev/null",
+ [Defined to be the null device for the system])
+
+DEFAULT_FS_TYPE="fsfs"
+AC_DEFINE_UNQUOTED(DEFAULT_FS_TYPE, "$DEFAULT_FS_TYPE",
+ [The fs type to use by default])
+
+DEFAULT_HTTP_LIBRARY="serf"
+AC_DEFINE_UNQUOTED(DEFAULT_HTTP_LIBRARY, "$DEFAULT_HTTP_LIBRARY",
+ [The http library to use by default])
+
+# BSD/OS (BSDi) needs to use a different include syntax in Makefile
+INCLUDE_OUTPUTS="include \$(top_srcdir)/build-outputs.mk"
+case "$host" in
+ *bsdi*)
+ # Check whether they've installed GNU make
+ if ! make --version > /dev/null 2>&1; then
+ # BSDi make
+ INCLUDE_OUTPUTS=".include \"\$(top_srcdir)/build-outputs.mk\""
+ fi
+ ;;
+esac
+AC_SUBST(INCLUDE_OUTPUTS)
+
+# ==== Detection complete - output and run config.status =====================
+
+AC_CONFIG_HEADERS(subversion/svn_private_config.h.tmp:subversion/svn_private_config.h.in)
+AC_CONFIG_COMMANDS([svn_private_config.h.tmp],
+ [svn_cf=subversion/svn_private_config.h;
+ $SED -e "s/@SVN_DB_HEADER@/$SVN_DB_HEADER/" $svn_cf.tmp > $svn_cf.tmp.new
+ cmp -s $svn_cf.tmp.new $svn_cf || mv -f $svn_cf.tmp.new $svn_cf
+ rm -f $svn_cf.tmp.new $svn_cf.tmp],
+ [SED="$SED"
+ SVN_DB_HEADER="$SVN_DB_HEADER"])
+AC_CONFIG_FILES([Makefile])
+
+SVN_CONFIG_SCRIPT(tools/backup/hot-backup.py)
+SVN_CONFIG_SCRIPT(tools/hook-scripts/commit-access-control.pl)
+SVN_CONFIG_SCRIPT(subversion/bindings/swig/perl/native/Makefile.PL)
+if test -e packages/solaris/pkginfo.in; then
+ SVN_CONFIG_SCRIPT(packages/solaris/pkginfo)
+fi
+AC_SUBST(SVN_CONFIG_SCRIPT_FILES)
+
+# Ensure that SWIG is checked after reconfiguration.
+rm -f .swig_checked
+
+dnl Provide ${host} for use in compiled code (for svn --version)
+AC_DEFINE_UNQUOTED([SVN_BUILD_HOST], "${host}",
+ [Defined to the config.guess name of the build system])
+
+dnl Provide ${target} for use in compiled code (for user-agent string)
+AC_DEFINE_UNQUOTED([SVN_BUILD_TARGET], "${target}",
+ [Defined to the config.guess name of the build target])
+
+AC_OUTPUT
+
+# ==== Print final messages to user ==========================================
+
+dnl Configure is long - users tend to miss warnings printed during it.
+dnl Hence, print a warnings about what we did and didn't configure at the
+dnl end, where people will actually see them.
+
+if test "$svn_lib_berkeley_db" = "no" && test "$with_berkeley_db" != "no"; then
+ db_version="$SVN_FS_WANT_DB_MAJOR.$SVN_FS_WANT_DB_MINOR.$SVN_FS_WANT_DB_PATCH"
+ AC_MSG_WARN([we have configured without BDB filesystem support
+
+
+You don't seem to have Berkeley DB version $db_version or newer
+installed and linked to APR-UTIL. We have created a Makefile which will build
+Subversion without support for the Berkeley DB back-end. You can find the
+latest version of Berkeley DB here:
+
+ http://www.oracle.com/technetwork/products/berkeleydb/downloads/index.html
+
+or explicitly specify --without-berkeley-db to silence this warning.
+])
+fi
diff --git a/contrib/subversion/doc/README b/contrib/subversion/doc/README
new file mode 100644
index 0000000..3710d73
--- /dev/null
+++ b/contrib/subversion/doc/README
@@ -0,0 +1,28 @@
+ Documentation for Subversion
+ ============================
+
+A rough guide:
+
+ http://svnbook.red-bean.com/
+ "Version Control with Subversion"
+ (a.k.a. "The Subversion Book", "The Svnbook",
+ and formerly entitled
+ "Subversion: The Definitive Guide".)
+ This is the book that has been published by
+ O'Reilly & Associates. For both newbies and
+ experts alike. Written in DocBook Lite,
+ and now maintained in a separate repository
+ of its own.
+
+ programmer/ Documents for Subversion programmers.
+
+ programmer/WritingChangeLogs.txt
+ A longer version of the info in
+ www/hacking.html.
+
+ user/ Documents for Subversion users.
+
+ user/lj_article.txt An introductory article from Linux Journal.
+
+ user/*.html Some documentation that should probably be
+ migrated to DocBook Lite and misc-docs/.
diff --git a/contrib/subversion/doc/doxygen.conf b/contrib/subversion/doc/doxygen.conf
new file mode 100644
index 0000000..6c17d1b
--- /dev/null
+++ b/contrib/subversion/doc/doxygen.conf
@@ -0,0 +1,1522 @@
+# Doxyfile 1.6.1
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = Subversion
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc/doxygen
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES = "copyright=@if copyrights " \
+ endcopyright=@endif
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it parses.
+# With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this tag.
+# The format is ext=language, where ext is a file extension, and language is one of
+# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
+# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penality.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will rougly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
+# doxygen. The layout file controls the global structure of the generated output files
+# in an output format independent way. The create the layout file that represents
+# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
+# file name after the option, if omitted DoxygenLayout.xml will be used as the name
+# of the layout file.
+
+LAYOUT_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text "
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = subversion/include \
+ subversion/include/private/svn_doxygen.h
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = NO
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX = svn_ SVN_
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
+# are set, an additional index file will be generated that can be used as input for
+# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
+# HTML documentation.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
+# For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION =
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 1
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = NO
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP)
+# there is already a search function so this one should typically
+# be disabled.
+
+SEARCHENGINE = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED = DOXYGEN \
+ DOXYGEN_SHOULD_SKIP_THIS \
+ __attribute__(x)= \
+ AP_MODULE_DECLARE(x)=x \
+ "SVN_ERROR_START=typedef enum svn_errno_t { SVN_WARNING = APR_OS_START_USERERR + 1, " \
+ "SVN_ERRDEF(num,offset,str)=/** str */ num = offset, " \
+ "SVN_ERROR_END=SVN_ERR_LAST } svn_errno_t; "
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
diff --git a/contrib/subversion/doc/programmer/WritingChangeLogs.txt b/contrib/subversion/doc/programmer/WritingChangeLogs.txt
new file mode 100644
index 0000000..c5f50cd
--- /dev/null
+++ b/contrib/subversion/doc/programmer/WritingChangeLogs.txt
@@ -0,0 +1,220 @@
+This is an essay by Jim Blandy <jimb@redhat.com> on maintaining
+ChangeLog entries.
+
+Although Subversion generates its ChangeLogs from svn log data,
+instead of keeping independent ChangeLog files, most of the advice
+below is as applicable to cvs log messages as to ChangeLog entries.
+
+
+Maintaining the ChangeLog
+=========================
+
+A project's ChangeLog provides a history of development. Comments in
+the code should explain the code's present state, but ChangeLog
+entries should explain how and when it got that way. The ChangeLog
+must show:
+
+* the relative order in which changes entered the code, so you can
+ see the context in which a change was made, and
+
+* the date at which the change entered the code, so you can relate the
+ change to outside events, like branch cuts, code freezes, and
+ releases.
+
+In the case of CVS, these refer to when the change was committed,
+because that is the context in which other developers will see the
+change.
+
+Every change to the sources should have a ChangeLog entry. The value
+of the ChangeLog becomes much less if developers cannot rely on its
+completeness. Even if you've only changed comments, write an entry
+that says, "Doc fix." The only changes you needn't log are small
+changes that have no effect on the source, like formatting tweaks.
+
+In order to keep the ChangeLog a manageable size, at the beginning of
+each year, the ChangeLog should be renamed to "ChangeLog-YYYY", and a
+fresh ChangeLog file started.
+
+
+How to write ChangeLog entries
+------------------------------
+
+ChangeLog entries should be full sentences, not sentence fragments.
+Fragments are more often ambiguous, and it takes only a few more
+seconds to write out what you mean. Fragments like `New file' or `New
+function' are acceptable, because they are standard idioms, and all
+further details should appear in the source code.
+
+The log entry should mention every file changed. It should also
+mention by name every function, variable, macro, makefile target,
+grammar rule, etc. you changed. However, there are common-sense
+exceptions:
+
+* If you have made a change which requires trivial changes throughout
+ the rest of the program (e.g., renaming a variable), you needn't
+ name all the functions affected.
+
+* If you have rewritten a file completely, the reader understands that
+ everything in it has changed, so your log entry may simply give the
+ file name, and say "Rewritten".
+
+In general, there is a tension between making entries easy to find by
+searching for identifiers, and wasting time or producing unreadable
+entries by being exhaustive. Use your best judgement --- and be
+considerate of your fellow developers.
+
+Group ChangeLog entries into "paragraphs", separated by blank lines.
+Each paragraph should be a set of changes that accomplish a single
+goal. Independent changes should be in separate paragraphs. For
+example:
+
+ 1999-03-24 Stan Shebs <shebs@andros.cygnus.com>
+
+ * configure.host (mips-dec-mach3*): Use mipsm3, not mach3.
+
+ Attempt to sort out SCO-related configs.
+ * configure.host (i[3456]86-*-sysv4.2*): Use this instead of
+ i[3456]86-*-sysv4.2MP and i[3456]86-*-sysv4.2uw2*.
+ (i[3456]86-*-sysv5*): Recognize this.
+ * configure.tgt (i[3456]86-*-sco3.2v5*, i[3456]86-*-sco3.2v4*):
+ Recognize these.
+
+Even though this entry describes two changes to `configure.host',
+they're in separate paragraphs, because they're unrelated changes.
+The second change to `configure.host' is grouped with another change
+to `configure.tgt', because they both serve the same purpose.
+
+Also note that the author has kindly recorded his overall motivation
+for the paragraph, so we don't have to glean it from the individual
+changes.
+
+The header line for the ChangeLog entry should have the format shown
+above. If you are using an old version of Emacs (before 20.1) that
+generates entries with more verbose dates, consider using
+`etc/add-log.el', from the GDB source tree. If you are using vi,
+consider using the macro in `etc/add-log.vi'. Both of these generate
+entries in the newer, terser format.
+
+One should never need the ChangeLog to understand the current code.
+If you find yourself writing a significant explanation in the
+ChangeLog, you should consider carefully whether your text doesn't
+actually belong in a comment, alongside the code it explains. Here's
+an example of doing it right:
+
+ 1999-02-23 Tom Tromey <tromey@cygnus.com>
+
+ * cplus-dem.c (consume_count): If `count' is unreasonable,
+ return 0 and don't advance input pointer.
+
+And then, in `consume_count' in `cplus-dem.c':
+
+ while (isdigit ((unsigned char)**type))
+ {
+ count *= 10;
+ count += **type - '0';
+ /* A sanity check. Otherwise a symbol like
+ `_Utf390_1__1_9223372036854775807__9223372036854775'
+ can cause this function to return a negative value.
+ In this case we just consume until the end of the string. */
+ if (count > strlen (*type))
+ {
+ *type = save;
+ return 0;
+ }
+
+This is why a new function, for example, needs only a log entry saying
+"New Function" --- all the details should be in the source.
+
+Avoid the temptation to abbreviate filenames or function names, as in
+this example (mostly real, but slightly exaggerated):
+
+ * gdbarch.[ch] (gdbarch_tdep, gdbarch_bfd_arch_info,
+ gdbarch_byte_order, {set,}gdbarch_long_bit,
+ {set,}gdbarch_long_long_bit, {set,}gdbarch_ptr_bit): Corresponding
+ functions.
+
+This makes it difficult for others to search the ChangeLog for changes
+to the file or function they are interested in. For example, if you
+searched for `set_gdbarch_long_bit', you would not find the above
+entry, because the writer used CSH-style globbing to abbreviate the
+list of functions. If you gave up, and made a second pass looking for
+gdbarch.c, you wouldn't find that either. Consider your poor readers,
+and write out the names.
+
+
+ChangeLogs and the CVS log
+--------------------------
+
+CVS maintains its own logs, which you can access using the `cvs log'
+command. This duplicates the information present in the ChangeLog,
+but binds each entry to a specific revision, which can be helpful at
+times.
+
+However, the CVS log is no substitute for the ChangeLog files.
+
+* CVS provides no easy way to see the changes made to a set of files
+ in chronological order. They're sorted first by filename, not by date.
+
+* Unless you put full ChangeLog paragraphs in your CVS log entries, it's
+ difficult to pull together changes that cross several files.
+
+* CVS doesn't segregate log entries for branches from those for the
+ trunk in any useful way.
+
+In some circumstances, though, the CVS log is more useful than the
+ChangeLog, so we maintain both. When you commit a change, you should
+provide appropriate text in both the ChangeLog and the CVS log.
+
+It is not necessary to provide CVS log entries for ChangeLog changes,
+since it would simply duplicate the contents of the file itself.
+
+
+Writing ChangeLog entries for merges
+------------------------------------
+
+Revision management software like CVS can introduce some confusion
+when writing ChangeLog entries. For example, one might write a change
+on a branch, and then merge it into the trunk months later. In that
+case, what position and date should the developer use for the
+ChangeLog entry --- that of the original change, or the date of the
+merge?
+
+The principles described at the top need to hold for both the original
+change and the merged change. That is:
+
+* On the branch (or trunk) where the change is first committed, the
+ ChangeLog entry should be written as normal, inserted at the top of
+ the ChangeLog and reflecting the date the change was committed to
+ the branch (or trunk).
+
+* When the change is then merged (to the trunk, or to another branch),
+ the ChangeLog entry should have the following form:
+
+ 1999-03-26 Jim Blandy <jimb@zwingli.cygnus.com>
+
+ Merged change from foobar_20010401_branch:
+
+ 1999-03-16 Keith Seitz <keiths@cygnus.com>
+ [...]
+
+ In this case, "Jim Blandy" is doing the merge on March 26; "Keith
+ Seitz" is the original author of the change, who committed it to
+ `foobar_20010401_branch' on March 16.
+
+ As shown here, the entry for the merge should be like any other
+ change --- inserted at the top of the ChangeLog, and stamped with
+ the date the merge was committed. It should indicate the origin of
+ the change, and provide the full text of the original entry,
+ indented to avoid being confused with a true log entry. Remember
+ that people looking for the merge will search for the original
+ changelog text, so it's important to preserve it unchanged.
+
+ For the merge entry, we use the merge date, and not the original
+ date, because this is when the change appears on the trunk or branch
+ this ChangeLog documents. Its impact on these sources is
+ independent of when or where it originated.
+
+This approach preserves the structure of the ChangeLog (entries appear
+in order, and dates reflect when they appeared), but also provides
+full information about changes' origins.
+
diff --git a/contrib/subversion/doc/user/cvs-crossover-guide.html b/contrib/subversion/doc/user/cvs-crossover-guide.html
new file mode 100644
index 0000000..d038a39
--- /dev/null
+++ b/contrib/subversion/doc/user/cvs-crossover-guide.html
@@ -0,0 +1,906 @@
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+<title>CVS to SVN Crossover Guide</title>
+<style type="text/css">
+body {
+ font-family: sans-serif;
+}
+h1 {
+ text-align: center;
+}
+h2 {
+ background: #b0c0f0;
+ margin: 0;
+}
+.h2 {
+ border-left: 4px #b0c0f0 solid;
+ margin-bottom: 2em;
+}
+hr {
+ height: 1px;
+ width: 80%;
+}
+p, h3, dl {
+ padding-left: 1em;
+}
+dd {
+ margin-left: 2em;
+}
+.sidebyside {
+ padding: 0 2em;
+ width: 100%;
+ font-size: 80%;
+}
+.sidebyside th, .sidebyside td {
+ width: 50%;
+ border-width: 0 1px 2px 0;
+ border-style: solid;
+ border-color: black;
+ background: #b0c0f0;
+ vertical-align: top;
+}
+.sidebyside th {
+ text-align: center;
+ background: #90a0d0;
+}
+.bookref {
+ font-size: 80%;
+}
+</style>
+</head>
+
+<body>
+
+<h1>CVS to SVN Crossover Guide</h1>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2>Purpose</h2>
+
+<p>This document provides an alternate method of learning Subversion.
+ Many users dislike learning new technology via a theoretical "top
+ down" approach, as provided by the <a
+ href="http://svnbook.red-bean.com">Subversion Book</a>. Instead,
+ this document presents Subversion from the "bottom up": it shows a
+ CVS command or task, and then shows the equivalent task in
+ Subversion (along with relevant book links.) It's essentially a
+ re-indexing of topics covered by the book, keyed on CVS tasks.</p>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2>Table of Contents</h2>
+
+<h3>Setup</h3>
+<ul>
+ <li><a href="#repos_creation">Repository creation</a></li>
+ <li><a href="#import">Importing data</a></li>
+ <li><a href="#installing">Installing a server</a></li>
+ <li><a href="#authenticating">Authenticating to a server</a></li>
+ <li><a href="#browsing">Browsing a repository</a></li>
+ <li><a href="#checkingout">Checking out a working copy</a></li>
+</ul>
+
+<h3>Basic Work Cycle</h3>
+<ul>
+ <li><a href="#changeditems">Seeing locally changed items</a></li>
+ <li><a href="#outofdate">Seeing out-of-date items</a></li>
+ <li><a href="#scheduling">Scheduling additions or deletions</a></li>
+ <li><a href="#copying">Copying and moving</a></li>
+ <li>Undoing local changes</li>
+ <li>Updating and committing</li>
+ <li>Resolving conflicts</li>
+ <li>Adding a binary file</li>
+ <li>Using native line-endings</li>
+</ul>
+
+<h3>Examining history</h3>
+<ul>
+ <li>Seeing history of an item</li>
+ <li>Comparing two versions of an item</li>
+</ul>
+
+<h3>Branching/Tagging/Merging</h3>
+<ul>
+ <li>Creating a branch</li>
+ <li>Moving a working copy to a branch</li>
+ <li>Finding the beginning of a branch</li>
+ <li>Porting a single change</li>
+ <li>Merging a whole branch</li>
+ <li>Reverting a committed change</li>
+ <li>Resurrecting deleted items</li>
+ <li>Creating a tag</li>
+ <li>Tweaking a tag</li>
+ <li>Seeing all tags</li>
+ <li>Comparing two tags</li>
+ <li>Seeing logs between two tags</li>
+</ul>
+
+<h3>Other tasks</h3>
+<ul>
+ <li>Using modules</li>
+ <li>Line endings and keywords</li>
+</ul>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2 id="repos_creation">Repository creation</h2>
+
+<p>Create a new repository for holding versioned data.</p>
+
+<table class="sidebyside">
+<tr>
+ <th>CVS</th>
+ <th>Subversion</th>
+</tr>
+<tr>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;cvs&nbsp;-d&nbsp;/usr/local/repos&nbsp;init</tt></dd>
+
+ <dt>Explanation:</dt>
+ <dd>Creates a new directory <tt>repos</tt> ready to hold RCS
+ files and config scripts.</dd>
+ </dl>
+ </td>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;svnadmin&nbsp;create&nbsp;/usr/local/repos</tt></dd>
+
+ <dt>Explanation:</dt>
+ <dd>Creates a new directory <tt>repos</tt> containing BerkeleyDB
+ files and config scripts.</dd>
+ </dl>
+ </td>
+</tr>
+</table>
+
+<dl class="bookref">
+ <dt>Book References:</dt>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/ch05s02.html">Repository Creation and Configuration</a></dd>
+</dl>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2 id="import">Importing data</h2>
+
+<p>Populate a new repository with initial data. Assuming that you
+ have a tree of code in the local directory <tt>myproj/</tt>, and
+ you want to move this tree into the repository.</p>
+
+<table class="sidebyside">
+<tr>
+ <th>CVS</th>
+ <th>Subversion</th>
+</tr>
+<tr>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;cd&nbsp;myproj</tt></dd>
+ <dd><tt>$&nbsp;cvs&nbsp;-d&nbsp;/usr/local/repos&nbsp;import&nbsp;myproj/&nbsp;none&nbsp;start</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>This copies the contents of the current working directory to
+ a new directory (<tt>myproj</tt>) in the CVS repository. The
+ CVS repository now contains a directory <tt>/myproj/</tt> at the
+ top level.</dd>
+
+ </dl>
+ </td>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;svn&nbsp;mkdir&nbsp;file:///usr/local/repos/tags</tt></dd>
+ <dd><tt>$&nbsp;svn&nbsp;mkdir&nbsp;file:///usr/local/repos/branches</tt></dd>
+ <dd><tt>$&nbsp;svn&nbsp;import&nbsp;myproj/&nbsp;file:///usr/local/repos/trunk</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>Though not strictly required, we deliberately create
+ <tt>/tags</tt> and <tt>/branches</tt> top-level directories in
+ the repository, to hold tags and branches later on. Then we
+ import the contents of the local <tt>myproj/</tt> directory into
+ a newly created <tt>/trunk</tt> directory in the
+ repository.</dd>
+ </dl>
+ </td>
+</tr>
+</table>
+
+<dl class="bookref">
+ <dt>Book References:</dt>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/ch05s04.html#svn-ch-5-sect-6.1">Choosing a repository layout</a></dd>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/re12.html">svn import</a></dd>
+</dl>
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2 id="installing">Installing a server</h2>
+
+<p>Make the repository available to clients via a network.</p>
+
+<table class="sidebyside">
+<tr>
+ <th>CVS</th>
+ <th>Subversion</th>
+</tr>
+<tr>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd>(too complex to demonstrate here)</dd>
+
+ <dt>Explanation:</dt>
+ <dd>Export the repository via the cvs <em>pserver</em> program.
+ It can be launched by either <strong>inetd</strong> or a
+ client's <strong>ssh</strong> remote request.</dd>
+
+ </dl>
+ </td>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd>(too complex to demonstrate here)</dd>
+
+ <dt>Explanation:</dt>
+ <dd>Export the repository with the <em>Apache 2.0.x</em> server,
+ or via the <em>svnserve</em> program. The latter can run as a
+ standalone daemon, can be launched by <strong>inetd</strong>, or
+ invoked by a client's <strong>ssh</strong> remote request.</dd>
+
+ </dl>
+ </td>
+</tr>
+</table>
+
+<dl class="bookref">
+ <dt>Book References:</dt>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/ch06.html">Server configuration</a></dd>
+</dl>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2 id="authenticating">Authenticating to a server</h2>
+
+<p>Have a network client prove its identity to a version
+ control server.</p>
+
+<table class="sidebyside">
+<tr>
+ <th>CVS</th>
+ <th>Subversion</th>
+</tr>
+<tr>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;cvs&nbsp;-d&nbsp;:pserver:user@host:/repos&nbsp;<em>command</em>&hellip;</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>When contacting a repository, the client pre-emptively
+ "pushes" its authentication credentials at the server.</dd>
+
+ </dl>
+ </td>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;svn&nbsp;<em>command</em>&nbsp;<em>URL</em>&hellip;</tt></dd>
+ <dd><tt>Password&nbsp;for&nbsp;'user':&nbsp;&nbsp;XXXXXXX</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>The client's authentication credentials are "pulled" from
+ the user interactively, and only when the server deems that a
+ challenge needs to be made. (And contrary to popular belief,
+ the <tt>--username</tt> and <tt>--password</tt> options are
+ merely values to be used <em>if</em> the server issues a
+ challenge; they do not "push" the credentials at the
+ server.)</dd>
+
+ </dl>
+ </td>
+</tr>
+</table>
+
+<dl class="bookref">
+ <dt>Book References:</dt>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/ch06s02.html">Network Model</a></dd>
+</dl>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2 id="browsing">Browsing a repository</h2>
+
+<p>Browse the repository as a filesystem, perusing file
+ contents and history as well (older versions of files or
+ trees.)</p>
+
+<table class="sidebyside">
+<tr>
+ <th>CVS</th>
+ <th>Subversion</th>
+</tr>
+<tr>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd>(not possible with commandline client)</dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>Not possible with commandline client. A third-party web
+ server tool such as ViewCVS must be used.</dd>
+
+ </dl>
+ </td>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;svn&nbsp;list&nbsp;<em>URL</em>&nbsp;[-r&nbsp;<em>rev</em>]&nbsp;[-v]</tt></dd>
+ <dd><tt>$&nbsp;svn&nbsp;cat&nbsp;<em>URL</em>&nbsp;[-r&nbsp;<em>rev</em>]</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>The <tt>svn list</tt> and <tt>svn cat</tt> commands allow
+ interactive browsing of a repository (and all previous states of
+ a repository) from the commandline. (The <tt>--verbose [-v]</tt>
+ switch displays full listing information.) If Apache is being
+ used as a Subversion server process (i.e. clients access via
+ <strong>http://</strong>), then the latest version of the
+ repository can be directly browsed by entering <em>URL</em> into
+ any web browser. Additionally, a third-party web server tool
+ (such as ViewCVS) can be used with Subversion.</dd>
+
+ </dl>
+ </td>
+</tr>
+</table>
+
+<dl class="bookref">
+ <dt>Book References:</dt>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/re14.html">svn list</a></dd>
+</dl>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2 id="checkingout">Checking out a working copy</h2>
+
+<p>Create a workspace on local disk which mirrors a directory
+ in the repository.</p>
+
+<table class="sidebyside">
+<tr>
+ <th>CVS</th>
+ <th>Subversion</th>
+</tr>
+<tr>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;cvs&nbsp;-d&nbsp;/usr/local/repos&nbsp;checkout&nbsp;myproj</tt></dd>
+ <dd><tt>U&nbsp;myproj/foo.c</tt></dd>
+ <dd><tt>U&nbsp;myproj/bar.c</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>Creates a local directory <tt>myproj</tt> which is a mirror
+ of the repository directory <tt>/myproj</tt>.</dd>
+
+ </dl>
+ </td>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;svn&nbsp;checkout&nbsp;file:///usr/local/repos/trunk&nbsp;myproj</tt></dd>
+ <dd><tt>A&nbsp;&nbsp;myproj/foo.c</tt></dd>
+ <dd><tt>A&nbsp;&nbsp;myproj/bar.c</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>Assuming that the original project data was imported into
+ the repository <tt>/trunk</tt> directory, this creates a local
+ directory <tt>myproj</tt> which is a mirror of the repository
+ directory <tt>/trunk</tt>. Standard Subversion convention is to
+ do "mainline" development in <tt>/trunk</tt>. See branching and
+ tagging sections for more details.</dd>
+
+ </dl>
+ </td>
+</tr>
+</table>
+
+<dl class="bookref">
+ <dt>Book References:</dt>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/ch03s04.html">Initial Checkout</a></dd>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/re04.html">svn checkout</a></dd>
+</dl>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2 id="changeditems">Seeing locally changed items</h2>
+
+<p>Discover which items in the working copy have local
+ modifications or are scheduled for addition/deletion.</p>
+
+<table class="sidebyside">
+<tr>
+ <th>CVS</th>
+ <th>Subversion</th>
+</tr>
+<tr>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;cvs&nbsp;status</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+ <dd><tt>File: baz.c&nbsp;&nbsp;&nbsp;Status:&nbsp;Up-to-date</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+ <dd><tt>$&nbsp;cvs&nbsp;update</tt></dd>
+ <dd><tt>M foo.c</tt></dd>
+ <dd><tt>U bar.c</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>The <tt>cvs status</tt> command shows whether a file is
+ locally modified or out of date, including information about
+ working revision and branch info. Unfortunately, because the
+ output is so verbose and hard to read, many users run <tt>cvs
+ update</tt> instead, which shows a more compact listing of
+ modified files (and of course, it also causes the server to
+ merge changes into your working copy.)</dd>
+
+ </dl>
+ </td>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;svn&nbsp;status</tt></dd>
+ <dd><tt>M&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foo.c</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>Shows modified files only. Very fast, as it does not use
+ the network. Does not update your working copy, yet still shows
+ a single-line display, much like <tt>svn update</tt>. To see
+ working revision and branch information, run <tt>svn info</tt>.</dd>
+
+ </dl>
+ </td>
+</tr>
+</table>
+
+<dl class="bookref">
+ <dt>Book References:</dt>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/ch03s05.html#svn-ch-3-sect-4.3.1">Examine Your Changes</a></dd>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/re26.html">svn status</a></dd>
+</dl>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2 id="outofdate">Seeing out-of-date items</h2>
+
+<p>Discover which items in the working copy are out-of-date
+ (i.e. newer versions exist in the repository.)</p>
+
+<table class="sidebyside">
+<tr>
+ <th>CVS</th>
+ <th>Subversion</th>
+</tr>
+<tr>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;cvs&nbsp;status</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+ <dd><tt>File: baz.c&nbsp;&nbsp;&nbsp;Status:&nbsp;Needs&nbsp;Patch</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+ <dd><tt>$&nbsp;cvs&nbsp;-n&nbsp;update</tt></dd>
+ <dd><tt>M foo.c</tt></dd>
+ <dd><tt>U bar.c</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>The <tt>cvs status</tt> command shows whether a file is
+ locally modified or out of date, including information about
+ working revision and branch info. A less verbose option is to
+ run <tt>cvs -n update</tt> instead, which shows a compact
+ listing of both out-of-date and locally modified files, without
+ actually updating the working copy.</dd>
+
+ </dl>
+ </td>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;svn&nbsp;status&nbsp;-u</tt></dd>
+ <dd><tt>M&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;46&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foo.c</tt></dd>
+ <dd><tt>M&nbsp;&nbsp;*&nbsp;&nbsp;46&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bar.c</tt></dd>
+ <dd><tt>&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;46&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;baz.c</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>Shows modified files (<tt>M</tt>) as well as out-of-date
+ files (<tt>*</tt>). Contacts repository, but doesn't modify the
+ working copy. To see working revision and branch information,
+ run <tt>svn info</tt>.</dd>
+
+ </dl>
+ </td>
+</tr>
+</table>
+
+<dl class="bookref">
+ <dt>Book References:</dt>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/ch03s05.html#svn-ch-3-sect-4.3.1">Examine Your Changes</a></dd>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/re26.html">svn status</a></dd>
+</dl>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2 id="scheduling">Scheduling additions or deletions</h2>
+
+<p>Schedule a working-copy file or directory to be added or
+ removed from the repository.</p>
+
+<table class="sidebyside">
+<tr>
+ <th>CVS</th>
+ <th>Subversion</th>
+</tr>
+<tr>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;touch&nbsp;foo.c</tt></dd>
+ <dd><tt>$&nbsp;cvs&nbsp;add&nbsp;foo.c</tt></dd>
+ <dd><tt>cvs&nbsp;server:&nbsp;scheduling&nbsp;file&nbsp;`blah'&nbsp;for&nbsp;addition</tt></dd>
+ <dd><tt>cvs&nbsp;server:&nbsp;use&nbsp;'cvs&nbsp;commit'&nbsp;to&nbsp;add&nbsp;this&nbsp;file&nbsp;permanently</tt></dd>
+ <dd><tt>&nbsp;</tt></dd>
+ <dd><tt>$&nbsp;mkdir&nbsp;new-dir</tt></dd>
+ <dd><tt>$&nbsp;cvs&nbsp;add&nbsp;new-dir</tt></dd>
+ <dd><tt>Directory&nbsp;new-dir&nbsp;added&nbsp;to&nbsp;the&nbsp;repository</tt></dd>
+ <dd><tt>&nbsp;</tt></dd>
+ <dd><tt>$&nbsp;rm&nbsp;bar.c</tt></dd>
+ <dd><tt>$&nbsp;cvs&nbsp;rm&nbsp;bar.c</tt></dd>
+ <dd><tt>cvs&nbsp;remove:&nbsp;scheduling&nbsp;`bar.c'&nbsp;for&nbsp;removal</tt></dd>
+ <dd><tt>cvs&nbsp;remove:&nbsp;use&nbsp;'cvs&nbsp;commit'&nbsp;to&nbsp;remove&nbsp;this&nbsp;file&nbsp;permanently</tt></dd>
+ <dd><tt>&nbsp;</tt></dd>
+ <dd><tt>$&nbsp;rm&nbsp;-rf&nbsp;old-dir/*</tt></dd>
+ <dd><tt>$&nbsp;cvs&nbsp;rm&nbsp;old-dir</tt></dd>
+ <dd><tt>cvs&nbsp;remove:&nbsp;Removing&nbsp;3bits</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+
+
+ <dt>Explanation:</dt>
+
+ <dd>Schedules a file or directory for addition or removal
+ to/from the repository. The repository will not be changed
+ until the user runs <tt>cvs commit</tt>, except for the case of
+ adding a directory, which immediately changes the repository.
+ Also, directories cannot be truly removed from the repository,
+ just emptied out. (<tt>cvs update -P</tt> will prune empty
+ directories from your working copy.)</dd>
+
+ </dl>
+ </td>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;touch&nbsp;foo.c</tt></dd>
+ <dd><tt>$&nbsp;svn&nbsp;add&nbsp;foo.c</tt></dd>
+ <dd><tt>A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foo.c</tt></dd>
+ <dd><tt>&nbsp;</tt></dd>
+ <dd><tt>$&nbsp;mkdir&nbsp;new-dir</tt></dd>
+ <dd><tt>$&nbsp;svn&nbsp;add&nbsp;new-dir</tt></dd>
+ <dd><tt>A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new-dir</tt></dd>
+ <dd><tt>&nbsp;</tt></dd>
+ <dd><tt>$&nbsp;svn&nbsp;rm&nbsp;bar.c</tt></dd>
+ <dd><tt>D&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bar.c</tt></dd>
+ <dd><tt>&nbsp;</tt></dd>
+ <dd><tt>$&nbsp;svn&nbsp;rm&nbsp;old-dir</tt></dd>
+ <dd><tt>D&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;old-dir/file1</tt></dd>
+ <dd><tt>D&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;old-dir/file2</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>Schedules a file or directory for addition or removal
+ to/from the repository. The repository will not be changed
+ until the user runs <tt>svn commit</tt>. The scheduled
+ operations are shown as <tt>A</tt> or <tt>D</tt> by <tt>svn
+ status</tt>, and <tt>svn revert</tt> can un-do the scheduling.
+ Directories really can be deleted (though as with all deleted
+ items, continues to exist in history.)</dd>
+
+ </dl>
+ </td>
+</tr>
+</table>
+
+<dl class="bookref">
+ <dt>Book References:</dt>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/ch03s05.html#svn-ch-3-sect-4.2">Make Changes to Your Working Copy</a></dd>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/re01.html">svn add</a></dd>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/re08.html">svn delete</a></dd>
+</dl>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2 id="copying">Copying and moving</h2>
+
+<p>Copy or move/rename a file or directory.</p>
+
+<table class="sidebyside">
+<tr>
+ <th>CVS</th>
+ <th>Subversion</th>
+</tr>
+<tr>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd>(not possible.)</dd>
+
+
+ <dt>Explanation:</dt>
+
+ <dd>Not possible, unless an administrator directly mucks with
+ RCS files in the repository. (And in that case, no history
+ records the act of copying or renaming.)</dd>
+
+ </dl>
+ </td>
+ <td>
+ <dl>
+ <dt>Commands:</dt>
+ <dd><tt>$&nbsp;svn&nbsp;copy&nbsp;foo.c&nbsp;foo2.c</tt></dd>
+ <dd><tt>A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foo2.c</tt></dd>
+ <dd><tt>&nbsp;</tt></dd>
+ <dd><tt>$&nbsp;svn&nbsp;copy&nbsp;dir&nbsp;dir2</tt></dd>
+ <dd><tt>A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dir2</tt></dd>
+ <dd><tt>&nbsp;</tt></dd>
+ <dd><tt>$&nbsp;svn&nbsp;move&nbsp;bar.c&nbsp;baz.c</tt></dd>
+ <dd><tt>A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;baz.c</tt></dd>
+ <dd><tt>D&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bar.c</tt></dd>
+ <dd><tt>&nbsp;</tt></dd>
+ <dd><tt>$&nbsp;svn&nbsp;move&nbsp;dirA&nbsp;dirB</tt></dd>
+ <dd><tt>A&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dirB</tt></dd>
+ <dd><tt>D&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dirA/file1</tt></dd>
+ <dd><tt>D&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dirA/file2</tt></dd>
+ <dd><tt>&hellip;</tt></dd>
+
+ <dt>Explanation:</dt>
+
+ <dd>The <tt>svn copy</tt> command schedules a file or directory
+ for addition to the repository, recording the "source" of the
+ copy. After committing, <tt>svn log</tt> on the copied item
+ will trace history back through the original copy-source. The
+ <tt>svn move</tt> command is exactly equivalent to running
+ <tt>svn copy</tt>, followed by an <tt>svn delete</tt> on the
+ copy-source: the result is a new item scheduled for addition
+ (with copy-history attached) and the original item scheduled for
+ deletion.</dd>
+
+ </dl>
+ </td>
+</tr>
+</table>
+
+<dl class="bookref">
+ <dt>Book References:</dt>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/ch03s05.html#svn-ch-3-sect-4.2">Make Changes to Your Working Copy</a></dd>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/re07.html">svn copy</a></dd>
+ <dd><a href="http://svnbook.red-bean.com/svnbook/re18.html">svn move</a></dd>
+</dl>
+
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2>Finding the beginning of a branch</h2>
+
+<p>If you're attempting to merge an entire branch into another, you
+need to compare the "root" and "tip" of the source branch, and then
+merge those differences into a working copy of the target branch.
+Obviously the "tip" of the branch can be represented by using the
+<tt>HEAD</tt> keyword. But how do you find the "birth" revision of
+the source branch?</p>
+
+<p>The easiest solution is to run</p>
+
+<pre>
+ $ svn log -v --stop-on-copy source-branch-URL
+ &hellip;
+</pre>
+
+<p>This command will display every change ever made to the branch, but
+<tt>--stop-on-copy</tt> option will cause the output to stop as soon
+as detects a copy operation in the branch's history. By definition,
+then, the very last log entry printed will show the copy being made.
+It will look something like:</p>
+
+<pre>
+r9189 | joe | 2004-03-22 10:10:47 -0600 (Mon, 22 Mar 2004) | 1 line
+Changed paths:
+ A /branches/mybranch (from /trunk:9188)
+</pre>
+
+<p>In this case, you would then know to compare revisions 9189 and
+HEAD of the branch in order to perform the merge:</p>
+
+<pre>
+ $ svn merge -r9189:HEAD source-branch-URL target-branch-WC
+ &hellip;
+</pre>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2>Seeing all of a project's tags</h2>
+
+<p>Assuming you've been following a consistent policy for creating
+tag-copies, then this is just a matter of running <tt>svn ls</tt> on a
+directory containing your tags. Typically you would run it on the
+<tt>/tags</tt> directory in your repository, although you're certainly
+free to organize this directory in a more complex way, or invent a
+different convention altogether.</p>
+
+<p>As an example, you can see all of Subversion's tags by running:</p>
+
+<pre>
+ $ svn ls --verbose http://svn.apache.org/repos/asf/subversion/tags
+ &hellip;
+ 7739 kfogel Nov 13 22:05 0.33.0/
+ 7796 josander Nov 18 12:15 0.33.1/
+ 7932 josander Dec 03 17:54 0.34.0/
+ 8045 josander Dec 19 15:13 0.35.0/
+ 8063 josander Dec 20 11:20 0.35.1/
+ 8282 josander Jan 13 14:15 0.36.0/
+ 8512 josander Jan 24 17:31 0.37.0/
+ 8810 kfogel Feb 23 03:44 1.0.0/
+ &hellip;
+</pre>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2>Seeing the differences between two tags</h2>
+
+<p>Just use <tt>svn diff</tt> in its fully expanded form, which
+compares any two URLs:</p>
+
+<pre>
+ $ svn diff tagURL1 tagURL2
+ &hellip;
+</pre>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2>Seeing logs between two tags</h2>
+
+<p>This is a somewhat common practice in CVS, and is doable in Subversion,
+but requires a little bit more work. Assuming that you've made two
+tags of <tt>/trunk</tt> at different points in time, the ultimate goal
+here is to run </p>
+
+<pre>
+ $ svn log -rX:Y trunkURL
+</pre>
+
+<p>&hellip;where X and Y are the revisions from which the two tags were
+copied. To discover X and Y, you can use the same technique
+described in the previous section ("finding the beginning of a
+branch".) Just use the <tt>--stop-on-copy</tt> option when logging the
+history of each tag. No commits happen on tag directories, so the
+following commands should each produce exactly <em>one</em> log
+entry:</p>
+
+<pre>
+ $ svn log -v --stop-on-copy tag1-URL
+
+ r3520 | joe | 2004-03-12 15:28:43 -0600 (Fri, 12 Mar 2004) | 1 line
+ &hellip;
+
+ $ svn log -v --stop-on-copy tag2-URL
+ a
+ r4177 | joe | 2004-03-12 15:28:43 -0600 (Fri, 12 Mar 2004) | 1 line
+ &hellip;
+</pre>
+
+<p>So in this example, the values of X and Y are 3520 and 4177. Now
+you can view all <tt>/trunk</tt> changes between those two points in time:</p>
+
+<pre>
+ $ svn log -r3520:4177 trunkURL
+ &hellip;
+</pre>
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2>Fixing an incorrect tag</h2>
+
+<p>If your tag is a bit off, you can "adjust" it just as people often
+do in CVS. Simply check out a working copy of the tag directory, make
+any changes you wish, and commit.</p>
+
+<p>Remember, because branches and tags are directories, they can also
+be deleted when they're no longer of any use to your project. They'll
+continue to exist in the repository's history.</p>
+
+
+</div>
+
+<!-- ==================================================================== -->
+<div class="h2">
+<h2>Creating/using "modules"</h2>
+
+<p>Compare CVS Modules vs. svn:externals.</p>
+
+</div>
+
+<!-- ==================================================================== -->
+</body>
+</html>
diff --git a/contrib/subversion/doc/user/lj_article.txt b/contrib/subversion/doc/user/lj_article.txt
new file mode 100644
index 0000000..dca776d
--- /dev/null
+++ b/contrib/subversion/doc/user/lj_article.txt
@@ -0,0 +1,323 @@
+
+ The Subversion Project: Building a Better CVS
+ ==============================================
+
+ Ben Collins-Sussman <sussman@collab.net>
+
+ Written in August 2001
+ Published in Linux Journal, January 2002
+
+Abstract
+--------
+
+This article discusses the history, goals, features and design of
+Subversion (http://subversion.tigris.org), an open-source project that
+aims to produce a compelling replacement for CVS.
+
+
+Introduction
+------------
+
+If you work on any kind of open-source project, you've probably worked
+with CVS. You probably remember the first time you learned to do an
+anonymous checkout of a source tree over the net -- or your first
+commit, or learning how to look at CVS diffs. And then the fateful
+day came: you asked your friend how to rename a file.
+
+"You can't", was the reply.
+
+What? What do you mean?
+
+"Well, you can delete the file from the repository and then re-add it
+under a new name."
+
+Yes, but then nobody would know it had been renamed...
+
+"Let's call the CVS administrator. She can hand-edit the repository's
+RCS files for us and possibly make things work."
+
+What?
+
+"And by the way, don't try to delete a directory either."
+
+You rolled your eyes and groaned. How could such simple tasks be
+difficult?
+
+
+The Legacy of CVS
+-----------------
+
+No doubt about it, CVS has evolved into the standard Software
+Configuration Management (SCM) system of the open source community.
+And rightly so! CVS itself is Free software, and its wonderful "non
+locking" development model -- whereby dozens of far-flung programmers
+collaborate -- fits the open-source world very well. In fact, one
+might argue that without CVS, it's doubtful whether sites like
+Freshmeat or Sourceforge would ever have flourished as they do now.
+CVS and its semi-chaotic development model have become an essential
+part of open source culture.
+
+So what's wrong with CVS?
+
+Because it uses the RCS storage-system under the hood, CVS can only
+track file contents, not tree structures. As a result, the user has
+no way to copy, move, or rename items without losing history. Tree
+rearrangements are always ugly server-side tweaks.
+
+The RCS back-end cannot store binary files efficiently, and branching
+and tagging operations can grow to be very slow. CVS also uses the
+network inefficiently; many users are annoyed by long waits, because
+file differeces are sent in only one direction (from server to client,
+but not from client to server), and binary files are always
+transmitted in their entirety.
+
+From a developer's standpoint, the CVS codebase is the result of
+layers upon layers of historical "hacks". (Remember that CVS began
+life as a collection of shell-scripts to drive RCS.) This makes the
+code difficult to understand, maintain, or extend. For example: CVS's
+networking ability was essentially "stapled on". It was never
+designed to be a native client-server system.
+
+Rectifying CVS's problems is a huge task -- and we've only listed just
+a few of the many common complaints here.
+
+
+Enter Subversion
+----------------
+
+In 1995, Karl Fogel and Jim Blandy founded Cyclic Software, a company
+for commercially supporting and improving CVS. Cyclic made the first
+public release of a network-enabled CVS (contributed by Cygnus
+software.) In 1999, Karl Fogel published a book about CVS and the
+open-source development model it enables (cvsbook.red-bean.com). Karl
+and Jim had long talked about writing a replacement for CVS; Jim had
+even drafted a new, theoretical repository design. Finally, in
+February of 2000, Brian Behlendorf of CollabNet (www.collab.net)
+offered Karl a full-time job to write a CVS replacement. Karl
+gathered a team together and work began in May.
+
+The team settled on a few simple goals: it was decided that Subversion
+would be designed as a functional replacement for CVS. It would do
+everything that CVS does -- preserving the same development model
+while fixing the flaws in CVS's (lack-of) design. Existing CVS users
+would be the target audience: any CVS user should be able to start
+using Subversion with little effort. Any other SCM "bonus features"
+were decided to be of secondary importance (at least before a 1.0
+release.)
+
+At the time of writing, the original team has been coding for a little
+over a year, and we have a number of excellent volunteer contributors.
+(Subversion, like CVS, is a open-source project!)
+
+
+Subversion's Features
+----------------------
+
+Here's a quick run-down of some of the reasons you should be excited
+about Subversion:
+
+ * Real copies and renames. The Subversion repository doesn't use
+ RCS files at all; instead, it implements a 'virtual' versioned
+ filesystem that tracks tree-structures over time (described
+ below). Files *and* directories are versioned. At last, there
+ are real client-side `mv' and `cp' commands that behave just as
+ you think.
+
+ * Atomic commits. A commit either goes into the repository
+ completely, or not all.
+
+ * Advanced network layer. The Subversion network server is Apache,
+ and client and server speak WebDAV(2) to one another. (See the
+ 'design' section below.)
+
+ * Faster network access. A binary diffing algorithm is used to
+ store and transmit deltas in *both* directions, regardless of
+ whether a file is of text or binary type.
+
+ * Filesystem "properties". Each file or directory has an invisible
+ hashtable attached. You can invent and store any arbitrary
+ key/value pairs you wish: owner, perms, icons, app-creator,
+ mime-type, personal notes, etc. This is a general-purpose feature
+ for users. Properties are versioned, just like file contents.
+ And some properties are auto-detected, like the mime-type of a
+ file (no more remembering to use the '-kb' switch!)
+
+ * Extensible and hackable. Subversion has no historical baggage; it
+ was designed and then implemented as a collection of shared C
+ libraries with well-defined APIs. This makes Subversion extremely
+ maintainable and usable by other applications and languages.
+
+ * Easy migration. The Subversion command-line client is very
+ similar to CVS; the development model is the same, so CVS users
+ should have little trouble making the switch. Development of a
+ 'cvs2svn' repository converter is in progress.
+
+ * It's Free. Subversion is released under a Apache/BSD-style
+ open-source license.
+
+
+Subversion's Design
+-------------------
+
+Subversion has a modular design; it's implemented as a collection of C
+libraries. Each layer has a well-defined purpose and interface. In
+general, code flow begins at the top of the diagram and flows
+"downward" -- each layer provides an interface to the layer above it.
+
+ <<insert diagram here: svn.tiff>>
+
+
+Let's take a short tour of these layers, starting at the bottom.
+
+
+--> The Subversion filesystem.
+
+The Subversion Filesystem is *not* a kernel-level filesystem that one
+would install in an operating system (like the Linux ext2 fs.)
+Instead, it refers to the design of Subversion's repository. The
+repository is built on top of a database -- currently Berkeley DB --
+and thus is a collection of .db files. However, a library accesses
+these files and exports a C API that simulates a filesystem --
+specifically, a "versioned" filesystem.
+
+This means that writing a program to access the repository is like
+writing against other filesystem APIs: you can open files and
+directories for reading and writing as usual. The main difference is
+that this particular filesystem never loses data when written to; old
+versions of files and directories are always saved as historical
+artifacts.
+
+Whereas CVS's backend (RCS) stores revision numbers on a per-file
+basis, Subversion numbers entire trees. Each atomic 'commit' to the
+repository creates a completely new filesystem tree, and is
+individually labeled with a single, global revision number. Files and
+directories which have changed are rewritten (and older versions are
+backed up and stored as differences against the latest version), while
+unchanged entries are pointed to via a shared-storage mechanism. This
+is how the repository is able to version tree structures, not just
+file contents.
+
+Finally, it should be mentioned that using a database like Berkeley DB
+immediately provides other nice features that Subversion needs: data
+integrity, atomic writes, recoverability, and hot backups. (See
+www.sleepycat.com for more information.)
+
+
+--> The network layer.
+
+Subversion has the mark of Apache all over it. At its very core, the
+client uses the Apache Portable Runtime (APR) library. (In fact, this
+means that Subversion client should compile and run anywhere Apache
+httpd does -- right now, this list includes all flavors of Unix,
+Win32, BeOS, OS/2, Mac OS X, and possibly Netware.)
+
+However, Subversion depends on more than just APR -- the Subversion
+"server" is Apache httpd itself.
+
+Why was Apache chosen? Ultimately, the decision was about not
+reinventing the wheel. Apache is a time-tested, open-source server
+process that ready for serious use, yet is still extensible. It can
+sustain a high network load. It runs on many platforms and can
+operate through firewalls. It's able to use a number of different
+authentication protocols. It can do network pipelining and caching.
+By using Apache as a server, Subversion gets all these features for
+free. Why start from scratch?
+
+Subversion uses WebDAV as its network protocol. DAV (Distributed
+Authoring and Versioning) is a whole discussion in itself (see
+www.webdav.org) -- but in short, it's an extension to HTTP that allows
+reads/writes and "versioning" of files over the web. The Subversion
+project is hoping to ride a slowly rising tide of support for this
+protocol: all of the latest file-browsers for Win32, MacOS, and GNOME
+speak this protocol already. Interoperability will (hopefully) become
+more and more of a bonus over time.
+
+For users who simply wish to access Subversion repositories on local
+disk, the client can do this too; no network is required. The
+"Repository Access" layer (RA) is an abstract API implemented by both
+the DAV and local-access RA libraries. This is a specific benefit of
+writing a "librarized" version control system; it's a big win over
+CVS, which has two very different, difficult-to-maintain codepaths for
+local vs. network repository-access. Feel like writing a new network
+protocol for Subversion? Just write a new library that implements the
+RA API!
+
+
+--> The client libraries.
+
+On the client side, the Subversion "working copy" library maintains
+administrative information within special SVN/ subdirectories, similar
+in purpose to the CVS/ administrative directories found in CVS working
+copies.
+
+A glance inside the typical SVN/ directory turns up a bit more than
+usual, however. The `entries' file contains XML which describes the
+current state of the working copy directory (and which basically
+serves the purposes of CVS's Entries, Root, and Repository files
+combined). But other items present (and not found in CVS/) include
+storage locations for the versioned "properties" (the metadata
+mentioned in 'Subversion Features' above) and private caches of
+pristine versions of each file. This latter feature provides the
+ability to report local modifications -- and do reversions --
+*without* network access. Authentication data is also stored within
+SVN/, rather than in a single .cvspass-like file.
+
+The Subversion "client" library has the broadest responsibility; its
+job is to mingle the functionality of the working-copy library with
+that of the repository-access library, and then to provide a
+highest-level API to any application that wishes to perform general
+version control actions.
+
+For example: the C routine `svn_client_checkout()' takes a URL as an
+argument. It passes this URL to the repository-access library and
+opens an authenticated session with a particular repository. It then
+asks the repository for a certain tree, and sends this tree into the
+working-copy library, which then writes a full working copy to disk
+(SVN/ directories and all.)
+
+The client library is designed to be used by any application. While
+the Subversion source code includes a standard command-line client, it
+should be very easy to write any number of GUI clients on top of the
+client library. Hopefully, these GUIs should someday prove to be much
+better than the current crop of CVS GUI applications (the majority of
+which are no more than fragile "wrappers" around the CVS command-line
+client.)
+
+In addition, proper SWIG bindings (www.swig.org) should make
+the Subversion API available to any number of languages: java, perl,
+python, guile, and so on. In order to Subvert CVS, it helps to be
+ubiquitous!
+
+
+Subversion's Future
+-------------------
+
+The release of Subversion 1.0 is currently planned for early 2002.
+After the release of 1.0, Subversion is slated for additions such as
+i18n support, "intelligent" merging, better "changeset" manipulation,
+client-side plugins, and improved features for server administration.
+(Also on the wishlist is an eclectic collection of ideas, such as
+distributed, replicating repositories.)
+
+A final thought from Subversion's FAQ:
+
+ "We aren't (yet) attempting to break new ground in SCM systems, nor
+ are we attempting to imitate all the best features of every SCM
+ system out there. We're trying to replace CVS."
+
+If, in three years, Subversion is widely presumed to be the "standard"
+SCM system in the open-source community, then the project will have
+succeeded. But the future is still hazy: ultimately, Subversion
+will have to win this position on its own technical merits.
+
+Patches are welcome.
+
+
+For More Information
+--------------------
+
+Please visit the Subversion project website at
+http://subversion.tigris.org. There are discussion lists to join, and
+the source code is available via anonymous CVS -- and soon through
+Subversion itself.
+
diff --git a/contrib/subversion/doc/user/svn-best-practices.html b/contrib/subversion/doc/user/svn-best-practices.html
new file mode 100644
index 0000000..61ba1c6
--- /dev/null
+++ b/contrib/subversion/doc/user/svn-best-practices.html
@@ -0,0 +1,350 @@
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+<title>Subversion Best Practices</title>
+<style type="text/css">
+h1 {
+ text-align: center;
+}
+</style>
+</head>
+
+<body>
+
+<h1>Subversion Best Practices</h1>
+
+<p>This is a quick set of guidelines for making the best use of
+Subversion in your day-to-day software development work.</p>
+
+
+<h2>Use a sane repository layout</h2>
+
+<p>There are many ways to lay out your repository. Because branches
+and tags are ordinary directories, you'll need to account for them in
+your repository structure.</p>
+
+<p>The Subversion project officially recommends the idea of a "project
+root", which represents an anchoring point for a project. A "project
+root" contains exactly three subdirectories: <tt>/trunk</tt>,
+<tt>/branches</tt>, and <tt>/tags</tt>. A repository may contain
+only one project root, or it may contain a number of them.</p>
+
+<p><em>Book reference:</em> <a
+ href="http://svnbook.red-bean.com/svnbook/ch05s04.html#svn-ch-5-sect-6.1">Choosing
+ a Repository Layout</a>.</p>
+
+
+
+<!-- =================================================== -->
+
+<h2>Commit logical changesets</h2>
+
+<p>When you commit a change to the repository, make sure your change
+reflects a single purpose: the fixing of a specific bug, the addition
+of a new feature, or some particular task. Your commit will create a
+new revision number which can forever be used as a "name" for the
+change. You can mention this revision number in bug databases, or use
+it as an argument to <tt>svn merge</tt> should you want to undo the
+change or port it to another branch.</p>
+
+<p><em>Book reference:</em> "Subversion and Changesets" sidebar,
+ within <a
+ href="http://svnbook.red-bean.com/svnbook/ch04s03.html">chapter
+ 4</a>.</p>
+
+<!-- =================================================== -->
+
+<h2>Use the issue-tracker wisely</h2>
+
+<p>Try to create as many two-way links between Subversion changesets
+and your issue-tracking database as possible:</p>
+
+<ul>
+<li>If possible, refer to a specific issue ID in every commit log message.</li>
+<li>When appending information to an issue (to describe progress, or
+ to close the issue) name the revision number(s) responsible
+ for the change.</li>
+</ul>
+
+<!-- =================================================== -->
+
+<h2>Track merges manually</h2>
+
+<p>When committing the result of a merge, be sure to write a
+descriptive log message that explains what was merged, something
+like:</p>
+
+ <pre>Merged revisions 3490:4120 of /branches/foobranch to /trunk.</pre>
+
+<p><em>Book reference:</em> <a
+ href="http://svnbook.red-bean.com/svnbook/ch04s03.html#svn-ch-4-sect-3.2">Tracking
+ merges manually</a>, and <a
+ href="http://svnbook.red-bean.com/svnbook/ch04s04.html#svn-ch-4-sect-4.1">Merging a whole branch to another</a>.</p>
+
+<!-- =================================================== -->
+
+<h2>Understand mixed-revision working copies</h2>
+
+<p>Your working copy's directories and files can be at different
+"working" revisions: this is a deliberate feature which allows you to
+mix and match older versions of things with newer ones. But there are
+few facts you must be aware of:</p>
+
+<ol>
+
+<li>After every <tt>svn commit</tt>, your working copy has mixed
+revisions. The things you just committed are now at the HEAD
+revision, and everything else is at an older revision.</li>
+
+<li>Certain commits are disallowed:
+ <ul>
+ <li>You cannot commit the deletion of a file or directory which
+ doesn't have a working revision of HEAD.</li>
+ <li>You cannot commit a property change to a directory which
+ doesn't have a working revision of HEAD.</li>
+ </ul>
+</li>
+
+<li><tt>svn update</tt> will bring your entire working copy to one
+ working revision, and is the typical solution to the
+ problems mentioned in point #2.</li>
+</ol>
+
+<p><em>Book reference:</em> <a
+href="http://svnbook.red-bean.com/svnbook/ch02s03.html#svn-ch-2-sect-3.4">The
+ limitation of mixed revisions</a>.</p>
+
+
+<!-- =================================================== -->
+
+<h2>Be patient with large files</h2>
+
+<p>A nice feature of Subversion is that by design, there is no limit
+to the size of files it can handle. Files are sent "streamily" in
+both directions between Subversion client and server, using a small,
+constant amount of memory on each side of the network.</p>
+
+<p>Of course, there are a number of practical issues to consider.
+While there's no need to worry about files in the kilobyte-sized range
+(e.g. typical source-code files), committing larger files can take a
+tremendous amount of both time and space (e.g. files that are dozens
+or hundreds of megabytes large.)</p>
+
+<p>To begin with, remember that your Subversion working copy stores
+pristine copies of all version-controlled files in the
+<tt>.svn/text-base/</tt> area. This means that your working copy
+takes up at least twice as much disk space as the original dataset.
+Beyond that, the Subversion client follows a (currently unadjustable)
+algorithm for committing files:</p>
+
+ <ul>
+ <li>Copies the file to <tt>.svn/tmp/</tt> <em>(can take a while,
+ and temporarily uses extra disk space)</em>)</li>
+
+ <li>Performs a binary diff between the tmpfile and the pristine
+ copy, or between the tmpfile and an empty-file if newly
+ added. <em>(can take a very long time to compute, even
+ though only a small amount of data might ultimately be sent
+ over the network)</em></li>
+
+ <li>Sends the diff to the server, then moves the tmpfile into
+ <tt>.svn/text-base/</tt></li>
+ </ul>
+
+<p>So while there's no theoretical limit to the size of your files,
+you'll need to be aware that very large files may require quite a bit
+of patient waiting while your client chugs away. You can rest
+assured, however, that unlike CVS, your large files won't incapacitate
+the server or affect other users.</p>
+
+<!-- =================================================== -->
+
+<h2>Work around commands that don't understand copies/renames</h2>
+
+<p>When a file or directory is copied or renamed, the Subversion repository
+tracks that history. Unfortunately in Subversion 1.0, the only client
+subcommand which actually takes advantage of this feature is <tt>svn
+log</tt>. A number of other commands (such as <tt>svn diff</tt> and
+<tt>svn cat</tt>) ought to be automatically following rename-history,
+but aren't doing so yet.</p>
+
+<p>In all of these cases, a basic workaround is to use <tt>'svn log
+-v'</tt> to discover the proper path within the older revision.</p>
+
+<p>For example, suppose you copied <tt>/trunk</tt> to
+<tt>/branches/mybranch</tt> in revision 200, and then committed some
+changes to <tt>/branches/mybranch/foo.c</tt> in subsequent revisions.
+Now you'd like to compare revisions 80 and 250 of the file.</p>
+
+<p>If you have a working copy of the branch and run <tt>svn diff
+-r80:250 foo.c</tt>, you'll see an error about
+<tt>/branches/mybranch/foo.c</tt> not existing in revision 80. To
+remedy, you would run <tt>svn log -v</tt> on your branch or file to
+discover that it was named <tt>/trunk/foo.c</tt> prior to revision 200,
+and then compare the two URLs directly:</p>
+
+<pre>
+ $ svn diff http://.../trunk/foo.c@80 \
+ http://.../branches/mybranch/foo.c@200
+</pre>
+
+
+
+<!-- =================================================== -->
+
+<h2>Know when to create branches</h2>
+
+<p>This is a hotly debated question, and it really depends on the
+culture of your software project. Rather than prescribe a universal
+policy, we'll describe three common ones here.</p>
+
+<h3>The Never-Branch system</h3>
+
+<p>(Often used by nascent projects that don't yet have runnable code.)</p>
+
+<ul>
+<li>Users commit their day-to-day work on <tt>/trunk</tt>.</li>
+<li>Occasionally <tt>/trunk</tt> "breaks" (doesn't compile, or fails
+functional tests) when a user begins to commit a series of complicated
+changes.</li>
+</ul>
+
+<p><em>Pros:</em> Very easy policy to follow. New developers have low
+ barrier to entry. Nobody needs to learn how to branch or merge.</p>
+
+<p><em>Cons:</em> Chaotic development, code could be unstable at any
+ time.</p>
+
+<p>A side note: this sort of development is a bit less risky in
+Subversion than in CVS. Because Subversion commits are atomic, it's
+not possible for a checkout or update to receive a "partial" commit
+while somebody else is in the process of committing.</p>
+
+
+<h3>The Always-Branch system</h3>
+
+<p>(Often used by projects that favor heavy management and supervision.)</p>
+
+<ul>
+<li>Each user creates/works on a private branch for <em>every</em> coding task.
+ </li>
+<li>When coding is complete, someone (original coder, peer, or
+ manager) reviews all private branch changes and merges them to
+ <tt>/trunk</tt>.</li>
+</ul>
+
+<p><em>Pros:</em> <tt>/trunk</tt> is guaranteed to be
+ <em>extremely</em> stable at all times. </p>
+
+<p><em>Cons:</em> Coders are artificially isolated from each other,
+ possibly creating more merge conflicts than necessary.
+ Requires users to do lots of extra merging.</p>
+
+
+<h3>The Branch-When-Needed system</h3>
+
+<p>(This is the system used by the Subversion project.)
+
+<ul>
+<li>Users commit their day-to-day work on <tt>/trunk</tt>.</li>
+
+<li>Rule #1: <tt>/trunk</tt> must compile and pass regression tests at
+all times. Committers who violate this rule are publically
+humiliated.</li>
+
+<li>Rule #2: a single commit (changeset) must not be so large
+so as to discourage peer-review.</li>
+
+<li>Rule #3: if rules #1 and #2 come into conflict (i.e. it's
+impossible to make a series of small commits without disrupting the
+trunk), then the user should create a branch and commit a series of
+smaller changesets there. This allows peer-review without disrupting
+the stability of <tt>/trunk</tt>.</li>
+
+</ul>
+
+<p><em>Pros:</em> <tt>/trunk</tt> is guaranteed to be stable at all
+ times. The hassle of branching/merging is somewhat rare.</p>
+
+<p><em>Cons:</em> Adds a bit of burden to users' daily work:
+ they must compile and test before every commit.</p>
+
+
+<!--
+
+
+Mapping CVS tasks to SVN tasks
+==============================
+
+This section is just a re-indexing of topics covered in the book,
+for people who prefer to learn from the "bottom up" rather than "top down".
+It shows some common CVS operations, and then the equivalent SVN operation,
+followed by a link to the book which explains more.
+
+
+* Importing data.
+
+* Checking out a working copy.
+
+* Seeing your changes.
+
+* Undoing your changes.
+
+* Resolving a conflict.
+
+* Adding binary files.
+
+* Activating keyword expansion and/or EOL translation.
+
+
+TAGS:
+
+* Creating a tag from a working copy
+
+* Creating a remote tag
+
+* Seeing all of a project's tags
+
+* Comparing two tags
+
+* Seeing the logs between two tags
+
+* Tweaking a tag
+
+
+BRANCHES:
+
+* Creating a branch and switching to it
+
+* Finding the beginning of a branch
+
+* Merging a branch to trunk, or vice versa
+
+-->
+
+
+</body>
+</html>
diff --git a/contrib/subversion/gen-make.opts b/contrib/subversion/gen-make.opts
new file mode 100644
index 0000000..96d052e
--- /dev/null
+++ b/contrib/subversion/gen-make.opts
@@ -0,0 +1,2 @@
+[options]
+--release =
diff --git a/contrib/subversion/gen-make.py b/contrib/subversion/gen-make.py
new file mode 100755
index 0000000..2b49b5e
--- /dev/null
+++ b/contrib/subversion/gen-make.py
@@ -0,0 +1,326 @@
+#!/usr/bin/env python
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+#
+# gen-make.py -- generate makefiles for building Subversion
+#
+
+
+import os
+import sys
+import getopt
+try:
+ my_getopt = getopt.gnu_getopt
+except AttributeError:
+ my_getopt = getopt.getopt
+try:
+ # Python >=3.0
+ import configparser
+except ImportError:
+ # Python <3.0
+ import ConfigParser as configparser
+
+# for the generator modules
+sys.path.insert(0, os.path.join('build', 'generator'))
+
+# for getversion
+sys.path.insert(1, 'build')
+
+gen_modules = {
+ 'make' : ('gen_make', 'Makefiles for POSIX systems'),
+ 'dsp' : ('gen_msvc_dsp', 'MSVC 6.x project files'),
+ 'vcproj' : ('gen_vcnet_vcproj', 'VC.Net project files'),
+ }
+
+def main(fname, gentype, verfname=None,
+ skip_depends=0, other_options=None):
+ if verfname is None:
+ verfname = os.path.join('subversion', 'include', 'svn_version.h')
+
+ gen_module = __import__(gen_modules[gentype][0])
+
+ generator = gen_module.Generator(fname, verfname, other_options)
+
+ if not skip_depends:
+ generator.compute_hdr_deps()
+
+ generator.write()
+ generator.write_sqlite_headers()
+
+ if ('--debug', '') in other_options:
+ for dep_type, target_dict in generator.graph.deps.items():
+ sorted_targets = list(target_dict.keys()); sorted_targets.sort()
+ for target in sorted_targets:
+ print(dep_type + ": " + _objinfo(target))
+ for source in target_dict[target]:
+ print(" " + _objinfo(source))
+ print("=" * 72)
+ gen_keys = sorted(generator.__dict__.keys())
+ for name in gen_keys:
+ value = generator.__dict__[name]
+ if isinstance(value, list):
+ print(name + ": ")
+ for i in value:
+ print(" " + _objinfo(i))
+ print("=" * 72)
+
+
+def _objinfo(o):
+ if isinstance(o, str):
+ return repr(o)
+ else:
+ t = o.__class__.__name__
+ n = getattr(o, 'name', '-')
+ f = getattr(o, 'filename', '-')
+ return "%s: %s %s" % (t,n,f)
+
+
+def _usage_exit(err=None):
+ "print ERR (if any), print usage, then exit the script"
+ if err:
+ print("ERROR: %s\n" % (err))
+ print("USAGE: gen-make.py [options...] [conf-file]")
+ print(" -s skip dependency generation")
+ print(" --debug print lots of stuff only developers care about")
+ print(" --release release mode")
+ print(" --reload reuse all options from the previous invocation")
+ print(" of the script, except -s, -t, --debug and --reload")
+ print(" -t TYPE use the TYPE generator; can be one of:")
+ items = sorted(gen_modules.items())
+ for name, (module, desc) in items:
+ print(' %-12s %s' % (name, desc))
+ print("")
+ print(" The default generator type is 'make'")
+ print("")
+ print(" Makefile-specific options:")
+ print("")
+ print(" --assume-shared-libs")
+ print(" omit dependencies on libraries, on the assumption that")
+ print(" shared libraries will be built, so that it is unnecessary")
+ print(" to relink executables when the libraries that they depend")
+ print(" on change. This is an option for developers who want to")
+ print(" increase the speed of frequent rebuilds.")
+ print(" *** Do not use unless you understand the consequences. ***")
+ print("")
+ print(" UNIX-specific options:")
+ print("")
+ print(" --installed-libs")
+ print(" Comma-separated list of Subversion libraries to find")
+ print(" pre-installed instead of building (probably only")
+ print(" useful for packagers)")
+ print("")
+ print(" Windows-specific options:")
+ print("")
+ print(" --with-apr=DIR")
+ print(" the APR sources are in DIR")
+ print("")
+ print(" --with-apr-util=DIR")
+ print(" the APR-Util sources are in DIR")
+ print("")
+ print(" --with-apr-iconv=DIR")
+ print(" the APR-Iconv sources are in DIR")
+ print("")
+ print(" --with-berkeley-db=DIR")
+ print(" look for Berkeley DB headers and libs in")
+ print(" DIR")
+ print("")
+ print(" --with-serf=DIR")
+ print(" the Serf sources are in DIR")
+ print("")
+ print(" --with-httpd=DIR")
+ print(" the httpd sources and binaries required")
+ print(" for building mod_dav_svn are in DIR;")
+ print(" implies --with-apr{-util, -iconv}, but")
+ print(" you can override them")
+ print("")
+ print(" --with-libintl=DIR")
+ print(" look for GNU libintl headers and libs in DIR;")
+ print(" implies --enable-nls")
+ print("")
+ print(" --with-openssl=DIR")
+ print(" tell serf to look for OpenSSL headers")
+ print(" and libs in DIR")
+ print("")
+ print(" --with-zlib=DIR")
+ print(" tell Subversion to look for ZLib headers and")
+ print(" libs in DIR")
+ print("")
+ print(" --with-jdk=DIR")
+ print(" look for the java development kit here")
+ print("")
+ print(" --with-junit=DIR")
+ print(" look for the junit jar here")
+ print(" junit is for testing the java bindings")
+ print("")
+ print(" --with-swig=DIR")
+ print(" look for the swig program in DIR")
+ print("")
+ print(" --with-sqlite=DIR")
+ print(" look for sqlite in DIR")
+ print("")
+ print(" --with-sasl=DIR")
+ print(" look for the sasl headers and libs in DIR")
+ print("")
+ print(" --enable-pool-debug")
+ print(" turn on APR pool debugging")
+ print("")
+ print(" --enable-purify")
+ print(" add support for Purify instrumentation;")
+ print(" implies --enable-pool-debug")
+ print("")
+ print(" --enable-quantify")
+ print(" add support for Quantify instrumentation")
+ print("")
+ print(" --enable-nls")
+ print(" add support for gettext localization")
+ print("")
+ print(" --enable-bdb-in-apr-util")
+ print(" configure APR-Util to use Berkeley DB")
+ print("")
+ print(" --enable-ml")
+ print(" enable use of ML assembler with zlib")
+ print("")
+ print(" --disable-shared")
+ print(" only build static libraries")
+ print("")
+ print(" --with-static-apr")
+ print(" Use static apr and apr-util")
+ print("")
+ print(" --with-static-openssl")
+ print(" Use static openssl")
+ print("")
+ print(" --vsnet-version=VER")
+ print(" generate for VS.NET version VER (2002, 2003, 2005, 2008, 2010 or 2012)")
+ print(" [only valid in combination with '-t vcproj']")
+ print("")
+ print(" --with-apr_memcache=DIR")
+ print(" the apr_memcache sources are in DIR")
+ sys.exit(1)
+
+
+class Options:
+ def __init__(self):
+ self.list = []
+ self.dict = {}
+
+ def add(self, opt, val):
+ if opt in self.dict:
+ self.list[self.dict[opt]] = (opt, val)
+ else:
+ self.dict[opt] = len(self.list)
+ self.list.append((opt, val))
+
+if __name__ == '__main__':
+ try:
+ opts, args = my_getopt(sys.argv[1:], 'st:',
+ ['debug',
+ 'release',
+ 'reload',
+ 'assume-shared-libs',
+ 'with-apr=',
+ 'with-apr-util=',
+ 'with-apr-iconv=',
+ 'with-berkeley-db=',
+ 'with-serf=',
+ 'with-httpd=',
+ 'with-libintl=',
+ 'with-openssl=',
+ 'with-zlib=',
+ 'with-jdk=',
+ 'with-junit=',
+ 'with-swig=',
+ 'with-sqlite=',
+ 'with-sasl=',
+ 'with-apr_memcache=',
+ 'with-static-apr',
+ 'with-static-openssl',
+ 'enable-pool-debug',
+ 'enable-purify',
+ 'enable-quantify',
+ 'enable-nls',
+ 'enable-bdb-in-apr-util',
+ 'enable-ml',
+ 'disable-shared',
+ 'installed-libs=',
+ 'vsnet-version=',
+
+ # Keep distributions that help by adding a path
+ # working. On unix this would be filtered by
+ # configure, but on Windows gen-make.py is used
+ # directly.
+ 'with-neon=',
+ 'without-neon',
+ ])
+ if len(args) > 1:
+ _usage_exit("Too many arguments")
+ except getopt.GetoptError, e:
+ _usage_exit(str(e))
+
+ conf = 'build.conf'
+ skip = 0
+ gentype = 'make'
+ rest = Options()
+
+ if args:
+ conf = args[0]
+
+ # First merge options with previously saved to gen-make.opts if --reload
+ # options used
+ for opt, val in opts:
+ if opt == '--reload':
+ prev_conf = configparser.ConfigParser()
+ prev_conf.read('gen-make.opts')
+ for opt, val in prev_conf.items('options'):
+ if opt != '--debug':
+ rest.add(opt, val)
+ del prev_conf
+ elif opt == '--with-neon' or opt == '--without-neon':
+ # Provide a warning that we ignored these arguments
+ print("Ignoring no longer supported argument '%s'" % opt)
+ else:
+ rest.add(opt, val)
+
+ # Parse options list
+ for opt, val in rest.list:
+ if opt == '-s':
+ skip = 1
+ elif opt == '-t':
+ gentype = val
+ else:
+ if opt == '--with-httpd':
+ rest.add('--with-apr', os.path.join(val, 'srclib', 'apr'))
+ rest.add('--with-apr-util', os.path.join(val, 'srclib', 'apr-util'))
+ rest.add('--with-apr-iconv', os.path.join(val, 'srclib', 'apr-iconv'))
+
+ # Remember all options so that --reload and other scripts can use them
+ opt_conf = open('gen-make.opts', 'w')
+ opt_conf.write('[options]\n')
+ for opt, val in rest.list:
+ opt_conf.write(opt + ' = ' + val + '\n')
+ opt_conf.close()
+
+ if gentype not in gen_modules.keys():
+ _usage_exit("Unknown module type '%s'" % (gentype))
+
+ main(conf, gentype, skip_depends=skip, other_options=rest.list)
+
+
+### End of file.
diff --git a/contrib/subversion/get-deps.sh b/contrib/subversion/get-deps.sh
new file mode 100755
index 0000000..5d8f49c
--- /dev/null
+++ b/contrib/subversion/get-deps.sh
@@ -0,0 +1,173 @@
+#!/bin/sh
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+#
+# get-deps.sh -- download the dependencies useful for building Subversion
+#
+
+# If changing this file please take care to try to make your changes as
+# portable as possible. That means at a minimum only use POSIX supported
+# features and functions. However, it may be desirable to use an even
+# more narrow set of features than POSIX, e.g. Solaris /bin/sh only has
+# a subset of the POSIX shell features. If in doubt, limit yourself to
+# features already used in the file. Reviewing the history of changes
+# may be useful as well.
+
+APR_VERSION=${APR_VERSION:-"1.4.6"}
+APU_VERSION=${APU_VERSION:-"1.5.1"}
+SERF_VERSION=${SERF_VERSION:-"1.2.1"}
+ZLIB_VERSION=${ZLIB_VERSION:-"1.2.8"}
+SQLITE_VERSION=${SQLITE_VERSION:-"3.7.15.1"}
+GTEST_VERSION=${GTEST_VERSION:-"1.6.0"}
+HTTPD_VERSION=${HTTPD_VERSION:-"2.4.3"}
+APR_ICONV_VERSION=${APR_ICONV_VERSION:-"1.2.1"}
+
+APR=apr-${APR_VERSION}
+APR_UTIL=apr-util-${APU_VERSION}
+SERF=serf-${SERF_VERSION}
+ZLIB=zlib-${ZLIB_VERSION}
+SQLITE_VERSION_LIST=`echo $SQLITE_VERSION | sed -e 's/\./ /g'`
+SQLITE=sqlite-amalgamation-`printf %d%02d%02d%02d $SQLITE_VERSION_LIST`
+GTEST=gtest-${GTEST_VERSION}
+GTEST_URL=http://googletest.googlecode.com/files/
+
+HTTPD=httpd-${HTTPD_VERSION}
+APR_ICONV=apr-iconv-${APR_ICONV_VERSION}
+
+BASEDIR=`pwd`
+TEMPDIR=$BASEDIR/temp
+
+HTTP_FETCH=
+[ -z "$HTTP_FETCH" ] && type wget >/dev/null 2>&1 && HTTP_FETCH="wget -q -nc"
+[ -z "$HTTP_FETCH" ] && type curl >/dev/null 2>&1 && HTTP_FETCH="curl -sO"
+[ -z "$HTTP_FETCH" ] && type fetch >/dev/null 2>&1 && HTTP_FETCH="fetch -q"
+
+# Need this uncommented if any of the specific versions of the ASF tarballs to
+# be downloaded are no longer available on the general mirrors.
+APACHE_MIRROR=http://archive.apache.org/dist
+
+# helpers
+usage() {
+ echo "Usage: $0"
+ echo "Usage: $0 [ apr | serf | zlib | sqlite | gtest ] ..."
+ exit $1
+}
+
+# getters
+get_apr() {
+ cd $TEMPDIR
+ test -d $BASEDIR/apr || $HTTP_FETCH $APACHE_MIRROR/apr/$APR.tar.bz2
+ test -d $BASEDIR/apr-util || $HTTP_FETCH $APACHE_MIRROR/apr/$APR_UTIL.tar.bz2
+ cd $BASEDIR
+
+ test -d $BASEDIR/apr || bzip2 -dc $TEMPDIR/$APR.tar.bz2 | tar -xf -
+ test -d $BASEDIR/apr-util || bzip2 -dc $TEMPDIR/$APR_UTIL.tar.bz2 | tar -xf -
+
+ test -d $BASEDIR/apr || mv $APR apr
+ test -d $BASEDIR/apr-util || mv $APR_UTIL apr-util
+}
+
+get_serf() {
+ test -d $BASEDIR/serf && return
+
+ cd $TEMPDIR
+ $HTTP_FETCH http://serf.googlecode.com/files/$SERF.tar.bz2
+ cd $BASEDIR
+
+ bzip2 -dc $TEMPDIR/$SERF.tar.bz2 | tar -xf -
+
+ mv $SERF serf
+}
+
+get_zlib() {
+ test -d $BASEDIR/zlib && return
+
+ cd $TEMPDIR
+ $HTTP_FETCH http://www.zlib.net/$ZLIB.tar.gz
+ cd $BASEDIR
+
+ gzip -dc $TEMPDIR/$ZLIB.tar.gz | tar -xf -
+
+ mv $ZLIB zlib
+}
+
+get_sqlite() {
+ test -d $BASEDIR/sqlite-amalgamation && return
+
+ cd $TEMPDIR
+ $HTTP_FETCH http://www.sqlite.org/$SQLITE.zip
+ cd $BASEDIR
+
+ unzip -q $TEMPDIR/$SQLITE.zip
+
+ mv $SQLITE sqlite-amalgamation
+
+}
+
+get_gtest() {
+ test -d $BASEDIR/gtest && return
+
+ cd $TEMPDIR
+ $HTTP_FETCH ${GTEST_URL}/${GTEST}.zip
+ cd $BASEDIR
+
+ unzip -q $TEMPDIR/$GTEST.zip
+
+ mv $GTEST gtest
+}
+
+# main()
+get_deps() {
+ mkdir -p $TEMPDIR
+
+ for i in zlib serf sqlite-amalgamation apr apr-util gtest; do
+ if [ -d $i ]; then
+ echo "Local directory '$i' already exists; the downloaded copy won't be used" >&2
+ fi
+ done
+
+ if [ $# -gt 0 ]; then
+ for target in "$@"; do
+ if [ "$target" != "deps" ]; then
+ get_$target || usage
+ else
+ usage
+ fi
+ done
+ else
+ get_apr
+ get_serf
+ get_zlib
+ get_sqlite
+
+ echo
+ echo "If you require mod_dav_svn, the recommended version of httpd is:"
+ echo " $APACHE_MIRROR/httpd/$HTTPD.tar.bz2"
+
+ echo
+ echo "If you require apr-iconv, its recommended version is:"
+ echo " $APACHE_MIRROR/apr/$APR_ICONV.tar.bz2"
+ fi
+
+ rm -rf $TEMPDIR
+}
+
+get_deps "$@"
diff --git a/contrib/subversion/subversion/include/mod_authz_svn.h b/contrib/subversion/subversion/include/mod_authz_svn.h
new file mode 100644
index 0000000..2cf1464
--- /dev/null
+++ b/contrib/subversion/subversion/include/mod_authz_svn.h
@@ -0,0 +1,61 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file mod_authz_svn.h
+ * @brief Subversion authorization extensions for mod_dav_svn
+ */
+
+#ifndef MOD_AUTHZ_SVN_H
+#define MOD_AUTHZ_SVN_H
+
+#include <httpd.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * mod_dav_svn to mod_authz_svn bypass mechanism
+ */
+/** Provider group for subrequest bypass */
+#define AUTHZ_SVN__SUBREQ_BYPASS_PROV_GRP "dav2authz_subreq_bypass"
+/** Provider name for subrequest bypass */
+#define AUTHZ_SVN__SUBREQ_BYPASS_PROV_NAME "mod_authz_svn_subreq_bypass"
+/** Provider version for subrequest bypass */
+#define AUTHZ_SVN__SUBREQ_BYPASS_PROV_VER "00.00a"
+/** Provider to allow mod_dav_svn to bypass the generation of an apache
+ * request when checking GET access from "mod_dav_svn/auth.c".
+ *
+ * Uses @a r @a repos_path and @a repos_name to determine if the user
+ * making the request is authorized.
+ *
+ * If the access is allowed returns @c OK or @c HTTP_FORBIDDEN if it is not.
+ */
+typedef int (*authz_svn__subreq_bypass_func_t)(request_rec *r,
+ const char *repos_path,
+ const char *repos_name);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/contrib/subversion/subversion/include/mod_dav_svn.h b/contrib/subversion/subversion/include/mod_dav_svn.h
new file mode 100644
index 0000000..c498c5e
--- /dev/null
+++ b/contrib/subversion/subversion/include/mod_dav_svn.h
@@ -0,0 +1,99 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file mod_dav_svn.h
+ * @brief Subversion's backend for Apache's mod_dav module
+ */
+
+
+#ifndef MOD_DAV_SVN_H
+#define MOD_DAV_SVN_H
+
+#include <httpd.h>
+#include <mod_dav.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/**
+ Given an apache request @a r, a @a uri, and a @a root_path to the svn
+ location block, process @a uri and return many things, allocated in
+ @a r->pool:
+
+ - @a cleaned_uri: The uri with duplicate and trailing slashes removed.
+
+ - @a trailing_slash: Whether the uri had a trailing slash on it.
+
+ Three special substrings of the uri are returned for convenience:
+
+ - @a repos_basename: The single path component that is the directory
+ which contains the repository. (Don't confuse
+ this with the "repository name" as optionally
+ defined via the SVNReposName directive!)
+
+ - @a relative_path: The remaining imaginary path components.
+
+ - @a repos_path: The actual path within the repository filesystem, or
+ NULL if no part of the uri refers to a path in
+ the repository (e.g. "!svn/vcc/default" or
+ "!svn/bln/25").
+
+
+ For example, consider the uri
+
+ /svn/repos/proj1/!svn/blah/13//A/B/alpha
+
+ In the SVNPath case, this function would receive a @a root_path of
+ '/svn/repos/proj1', and in the SVNParentPath case would receive a
+ @a root_path of '/svn/repos'. But either way, we would get back:
+
+ - @a cleaned_uri: /svn/repos/proj1/!svn/blah/13/A/B/alpha
+ - @a repos_basename: proj1
+ - @a relative_path: /!svn/blah/13/A/B/alpha
+ - @a repos_path: A/B/alpha
+ - @a trailing_slash: FALSE
+*/
+AP_MODULE_DECLARE(dav_error *) dav_svn_split_uri(request_rec *r,
+ const char *uri,
+ const char *root_path,
+ const char **cleaned_uri,
+ int *trailing_slash,
+ const char **repos_basename,
+ const char **relative_path,
+ const char **repos_path);
+
+
+/**
+ * Given an apache request @a r and a @a root_path to the svn location
+ * block, set @a *repos_path to the path of the repository on disk. */
+AP_MODULE_DECLARE(dav_error *) dav_svn_get_repos_path(request_rec *r,
+ const char *root_path,
+ const char **repos_path);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* MOD_DAV_SVN_H */
diff --git a/contrib/subversion/subversion/include/private/README b/contrib/subversion/subversion/include/private/README
new file mode 100644
index 0000000..05527e2
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/README
@@ -0,0 +1,4 @@
+Header files in this private/ directory are for internal APIs shared
+across Subversion's implementation. They are not part of the public
+API, nor are they ever copied into or under the include/ directory
+(e.g. by the installation process).
diff --git a/contrib/subversion/subversion/include/private/ra_svn_sasl.h b/contrib/subversion/subversion/include/private/ra_svn_sasl.h
new file mode 100644
index 0000000..428e20e
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/ra_svn_sasl.h
@@ -0,0 +1,86 @@
+/*
+ * ra_svn_sasl.h : SASL-related declarations shared between the
+ * ra_svn and svnserve module
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#ifndef RA_SVN_SASL_H
+#define RA_SVN_SASL_H
+
+#ifdef WIN32
+/* This prevents sasl.h from redefining iovec, which is always defined by APR
+ on win32. */
+#define STRUCT_IOVEC_DEFINED
+#include <sasl.h>
+#else
+#include <sasl/sasl.h>
+#endif
+
+#include <apr_errno.h>
+#include <apr_pools.h>
+
+#include "svn_error.h"
+#include "svn_ra_svn.h"
+
+#include "private/svn_atomic.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** The application and service name used for sasl_client_new,
+ * sasl_server_init, and sasl_server_new. */
+#define SVN_RA_SVN_SASL_NAME "svn"
+
+extern volatile svn_atomic_t svn_ra_svn__sasl_status;
+
+/* Initialize secprops with default values. */
+void
+svn_ra_svn__default_secprops(sasl_security_properties_t *secprops);
+
+/* This function is called by the client and the server before
+ calling sasl_{client, server}_init, pool is used for allocations. */
+svn_error_t *
+svn_ra_svn__sasl_common_init(apr_pool_t *pool);
+
+/* Sets local_addrport and remote_addrport to a string containing the
+ remote and local IP address and port, formatted like this: a.b.c.d;port. */
+svn_error_t *
+svn_ra_svn__get_addresses(const char **local_addrport,
+ const char **remote_addrport,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/* If a security layer was negotiated during the authentication exchange,
+ create an encrypted stream for conn. */
+svn_error_t *
+svn_ra_svn__enable_sasl_encryption(svn_ra_svn_conn_t *conn,
+ sasl_conn_t *sasl_ctx,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* RA_SVN_SASL_H */
diff --git a/contrib/subversion/subversion/include/private/svn_adler32.h b/contrib/subversion/subversion/include/private/svn_adler32.h
new file mode 100644
index 0000000..8c9bbd2
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_adler32.h
@@ -0,0 +1,52 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_adler32.h
+ * @brief Subversion's take on Adler-32 calculation
+ */
+
+#ifndef SVN_ADLER32_H
+#define SVN_ADLER32_H
+
+#include <apr.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/**
+ * Return an adler32 checksum based on CHECKSUM, updated with
+ * DATA of size LEN.
+ *
+ * @since New in 1.7.
+ */
+apr_uint32_t
+svn__adler32(apr_uint32_t checksum, const char *data, apr_off_t len);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* SVN_ADLER32_H */
diff --git a/contrib/subversion/subversion/include/private/svn_atomic.h b/contrib/subversion/subversion/include/private/svn_atomic.h
new file mode 100644
index 0000000..187703b
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_atomic.h
@@ -0,0 +1,123 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_atomic.h
+ * @brief Macros and functions for atomic operations
+ */
+
+#ifndef SVN_ATOMIC_H
+#define SVN_ATOMIC_H
+
+#include <apr_version.h>
+#include <apr_atomic.h>
+
+#include "svn_error.h"
+#include "private/svn_dep_compat.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * @name Macro definitions for atomic types and operations
+ *
+ * @note These are necessary because the apr_atomic API changed somewhat
+ * between apr-0.x and apr-1.x.
+ * @{
+ */
+
+/** The type used by all the other atomic operations. */
+#if APR_VERSION_AT_LEAST(1, 0, 0)
+#define svn_atomic_t apr_uint32_t
+#else
+#define svn_atomic_t apr_atomic_t
+#endif
+
+/** Atomically read an #svn_atomic_t from memory. */
+#if APR_VERSION_AT_LEAST(1, 0, 0)
+#define svn_atomic_read(mem) apr_atomic_read32((mem))
+#else
+#define svn_atomic_read(mem) apr_atomic_read((mem))
+#endif
+
+/** Atomically set an #svn_atomic_t in memory. */
+#if APR_VERSION_AT_LEAST(1, 0, 0)
+#define svn_atomic_set(mem, val) apr_atomic_set32((mem), (val))
+#else
+#define svn_atomic_set(mem, val) apr_atomic_set((mem), (val))
+#endif
+
+/** Atomically increment an #svn_atomic_t. */
+#if APR_VERSION_AT_LEAST(1, 0, 0)
+#define svn_atomic_inc(mem) apr_atomic_inc32(mem)
+#else
+#define svn_atomic_inc(mem) apr_atomic_inc(mem)
+#endif
+
+/** Atomically decrement an #svn_atomic_t. */
+#if APR_VERSION_AT_LEAST(1, 0, 0)
+#define svn_atomic_dec(mem) apr_atomic_dec32(mem)
+#else
+#define svn_atomic_dec(mem) apr_atomic_dec(mem)
+#endif
+
+/**
+ * Atomic compare-and-swap.
+ *
+ * Compare the value that @a mem points to with @a cmp. If they are
+ * the same swap the value with @a with.
+ *
+ * @note svn_atomic_cas should not be combined with the other
+ * svn_atomic operations. A comment in apr_atomic.h explains
+ * that on some platforms, the CAS function is implemented in a
+ * way that is incompatible with the other atomic operations.
+ */
+#if APR_VERSION_AT_LEAST(1, 0, 0)
+#define svn_atomic_cas(mem, with, cmp) \
+ apr_atomic_cas32((mem), (with), (cmp))
+#else
+#define svn_atomic_cas(mem, with, cmp) \
+ apr_atomic_cas((mem), (with), (cmp))
+#endif
+/** @} */
+
+/**
+ * Call an initialization function in a thread-safe manner.
+ *
+ * @a global_status must be a pointer to a global, zero-initialized
+ * #svn_atomic_t. @a init_func is a pointer to the function that performs
+ * the actual initialization. @a baton and and @a pool are passed on to the
+ * init_func for its use.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_atomic__init_once(volatile svn_atomic_t *global_status,
+ svn_error_t *(*init_func)(void*,apr_pool_t*),
+ void *baton,
+ apr_pool_t* pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_ATOMIC_H */
diff --git a/contrib/subversion/subversion/include/private/svn_auth_private.h b/contrib/subversion/subversion/include/private/svn_auth_private.h
new file mode 100644
index 0000000..7a1c716
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_auth_private.h
@@ -0,0 +1,220 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_auth_private.h
+ * @brief Subversion's authentication system - Internal routines
+ */
+
+#ifndef SVN_AUTH_PRIVATE_H
+#define SVN_AUTH_PRIVATE_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* If you add a password type for a provider which stores
+ * passwords on disk in encrypted form, remember to update
+ * svn_auth__simple_save_creds_helper. Otherwise it will be
+ * assumed that your provider stores passwords in plaintext. */
+#define SVN_AUTH__SIMPLE_PASSWORD_TYPE "simple"
+#define SVN_AUTH__WINCRYPT_PASSWORD_TYPE "wincrypt"
+#define SVN_AUTH__KEYCHAIN_PASSWORD_TYPE "keychain"
+#define SVN_AUTH__KWALLET_PASSWORD_TYPE "kwallet"
+#define SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE "gnome-keyring"
+#define SVN_AUTH__GPG_AGENT_PASSWORD_TYPE "gpg-agent"
+
+/* A function that stores in *PASSWORD (potentially after decrypting it)
+ the user's password. It might be obtained directly from CREDS, or
+ from an external store, using REALMSTRING and USERNAME as keys.
+ (The behavior is undefined if REALMSTRING or USERNAME are NULL.)
+ If NON_INTERACTIVE is set, the user must not be involved in the
+ retrieval process. Set *DONE to TRUE if a password was stored
+ in *PASSWORD, to FALSE otherwise. POOL is used for any necessary
+ allocation. */
+typedef svn_error_t * (*svn_auth__password_get_t)
+ (svn_boolean_t *done,
+ const char **password,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool);
+
+/* A function that stores PASSWORD (or some encrypted version thereof)
+ either directly in CREDS, or externally using REALMSTRING and USERNAME
+ as keys into the external store. If NON_INTERACTIVE is set, the user
+ must not be involved in the storage process. Set *DONE to TRUE if the
+ password was store, to FALSE otherwise. POOL is used for any necessary
+ allocation. */
+typedef svn_error_t * (*svn_auth__password_set_t)
+ (svn_boolean_t *done,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ const char *password,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool);
+
+/* Use PARAMETERS and REALMSTRING to set *CREDENTIALS to a set of
+ pre-cached authentication credentials pulled from the simple
+ credential cache store identified by PASSTYPE. PASSWORD_GET is
+ used to obtain the password value. Allocate *CREDENTIALS from
+ POOL.
+
+ NOTE: This function is a common implementation of code used by
+ several of the simple credential providers (the default disk cache
+ mechanism, Windows CryptoAPI, GNOME Keyring, etc.), typically in
+ their "first_creds" implementation. */
+svn_error_t *
+svn_auth__simple_creds_cache_get(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ svn_auth__password_get_t password_get,
+ const char *passtype,
+ apr_pool_t *pool);
+
+/* Use PARAMETERS and REALMSTRING to save CREDENTIALS in the simple
+ credential cache store identified by PASSTYPE. PASSWORD_SET is
+ used to do the actual storage. Use POOL for necessary allocations.
+ Set *SAVED according to whether or not the credentials were
+ successfully stored.
+
+ NOTE: This function is a common implementation of code used by
+ several of the simple credential providers (the default disk cache
+ mechanism, Windows CryptoAPI, GNOME Keyring, etc.) typically in
+ their "save_creds" implementation. */
+svn_error_t *
+svn_auth__simple_creds_cache_set(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ svn_auth__password_set_t password_set,
+ const char *passtype,
+ apr_pool_t *pool);
+
+/* Implementation of svn_auth__password_get_t that retrieves
+ the plaintext password from CREDS when USERNAME matches the stored
+ credentials. */
+svn_error_t *
+svn_auth__simple_password_get(svn_boolean_t *done,
+ const char **password,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool);
+
+/* Implementation of svn_auth__password_set_t that stores
+ the plaintext password in CREDS. */
+svn_error_t *
+svn_auth__simple_password_set(svn_boolean_t *done,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ const char *password,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool);
+
+
+/* Use PARAMETERS and REALMSTRING to set *CREDENTIALS to a set of
+ pre-cached authentication credentials pulled from the SSL client
+ certificate passphrase credential cache store identified by
+ PASSTYPE. PASSPHRASE_GET is used to obtain the passphrase value.
+ Allocate *CREDENTIALS from POOL.
+
+ NOTE: This function is a common implementation of code used by
+ several of the ssl client passphrase credential providers (the
+ default disk cache mechanism, Windows CryptoAPI, GNOME Keyring,
+ etc.), typically in their "first_creds" implementation. */
+svn_error_t *
+svn_auth__ssl_client_cert_pw_cache_get(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ svn_auth__password_get_t passphrase_get,
+ const char *passtype,
+ apr_pool_t *pool);
+
+/* Use PARAMETERS and REALMSTRING to save CREDENTIALS in the SSL
+ client certificate passphrase credential cache store identified by
+ PASSTYPE. PASSPHRASE_SET is used to do the actual storage. Use
+ POOL for necessary allocations. Set *SAVED according to whether or
+ not the credentials were successfully stored.
+
+ NOTE: This function is a common implementation of code used by
+ several of the simple credential providers (the default disk cache
+ mechanism, Windows CryptoAPI, GNOME Keyring, etc.) typically in
+ their "save_creds" implementation. */
+svn_error_t *
+svn_auth__ssl_client_cert_pw_cache_set(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ svn_auth__password_set_t passphrase_set,
+ const char *passtype,
+ apr_pool_t *pool);
+
+/* This implements the svn_auth__password_get_t interface.
+ Set **PASSPHRASE to the plaintext passphrase retrieved from CREDS;
+ ignore other parameters. */
+svn_error_t *
+svn_auth__ssl_client_cert_pw_get(svn_boolean_t *done,
+ const char **passphrase,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool);
+
+/* This implements the svn_auth__password_set_t interface.
+ Store PASSPHRASE in CREDS; ignore other parameters. */
+svn_error_t *
+svn_auth__ssl_client_cert_pw_set(svn_boolean_t *done,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ const char *passphrase,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_AUTH_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_cache.h b/contrib/subversion/subversion/include/private/svn_cache.h
new file mode 100644
index 0000000..df40f7e
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_cache.h
@@ -0,0 +1,486 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_cache.h
+ * @brief In-memory cache implementation.
+ */
+
+
+#ifndef SVN_CACHE_H
+#define SVN_CACHE_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_iter.h"
+#include "svn_config.h"
+#include "svn_string.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/**
+ * @defgroup svn_cache__support In-memory caching
+ * @{
+ */
+
+/**
+ * A function type for deserializing an object @a *out from the string
+ * @a data of length @a data_len into @a result_pool. It is legal and
+ * generally suggested that the deserialization will be done in-place,
+ * i.e. modify @a data directly and return it in @a *out.
+ */
+typedef svn_error_t *(*svn_cache__deserialize_func_t)(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *result_pool);
+
+/**
+ * A function type for deserializing an object @a *out from the string
+ * @a data of length @a data_len into @a result_pool. The extra information
+ * @a baton passed into can be used to deserialize only a specific part or
+ * sub-structure or to perform any other non-modifying operation that may
+ * not require the whole structure to be processed.
+ */
+typedef svn_error_t *(*svn_cache__partial_getter_func_t)(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *result_pool);
+
+/**
+ * A function type for modifying an already deserialized in the @a *data
+ * buffer of length @a *data_len. Additional information of the modification
+ * to do will be provided in @a baton. The function may change the size of
+ * data buffer and may re-allocate it if necessary. In that case, the new
+ * values must be passed back in @a *data_len and @a *data, respectively.
+ * Allocations will be done from @a result_pool.
+ */
+typedef svn_error_t *(*svn_cache__partial_setter_func_t)(void **data,
+ apr_size_t *data_len,
+ void *baton,
+ apr_pool_t *result_pool);
+
+/**
+ * A function type for serializing an object @a in into bytes. The
+ * function should allocate the serialized value in @a result_pool, set
+ * @a *data to the serialized value, and set @a *data_len to its length.
+ */
+typedef svn_error_t *(*svn_cache__serialize_func_t)(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *result_pool);
+
+/**
+ * A function type for transforming or ignoring errors. @a scratch_pool may
+ * be used for temporary allocations.
+ */
+typedef svn_error_t *(*svn_cache__error_handler_t)(svn_error_t *err,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * A wrapper around apr_memcache_t, provided essentially so that the
+ * Subversion public API doesn't depend on whether or not you have
+ * access to the APR memcache libraries.
+ */
+typedef struct svn_memcache_t svn_memcache_t;
+
+/**
+ * An opaque structure representing a membuffer cache object.
+ */
+typedef struct svn_membuffer_t svn_membuffer_t;
+
+/**
+ * Opaque type for an in-memory cache.
+ */
+typedef struct svn_cache__t svn_cache__t;
+
+/**
+ * A structure containing typical statistics about a given cache instance.
+ * Use svn_cache__get_info() to get this data. Note that not all types
+ * of caches will be able to report complete and correct information.
+ */
+typedef struct svn_cache__info_t
+{
+ /** A string identifying the cache instance. Usually a copy of the @a id
+ * or @a prefix parameter passed to the cache constructor.
+ */
+ const char* id;
+
+ /** Number of getter calls (svn_cache__get() or svn_cache__get()).
+ */
+ apr_uint64_t gets;
+
+ /** Number of getter calls that return data.
+ */
+ apr_uint64_t hits;
+
+ /** Number of setter calls (svn_cache__set()).
+ */
+ apr_uint64_t sets;
+
+ /** Number of function calls that returned an error.
+ */
+ apr_uint64_t failures;
+
+ /** Size of the data currently stored in the cache.
+ * May be 0 if that information is not available.
+ */
+ apr_uint64_t used_size;
+
+ /** Amount of memory currently reserved for cached data.
+ * Will be equal to @a used_size if no precise information is available.
+ */
+ apr_uint64_t data_size;
+
+ /** Lower threshold of the total size of memory allocated to the cache and
+ * its index as well as management structures. The actual memory allocated
+ * by the cache may be larger.
+ */
+ apr_uint64_t total_size;
+
+ /** Number of cache entries.
+ * May be 0 if that information is not available.
+ */
+ apr_uint64_t used_entries;
+
+ /** Maximum numbers of cache entries.
+ * May be 0 if that information is not available.
+ */
+ apr_uint64_t total_entries;
+} svn_cache__info_t;
+
+/**
+ * Creates a new cache in @a *cache_p. This cache will use @a pool
+ * for all of its storage needs. The elements in the cache will be
+ * indexed by keys of length @a klen, which may be APR_HASH_KEY_STRING
+ * if they are strings. Cached values will be copied in and out of
+ * the cache using @a serialize_func and @a deserialize_func, respectively.
+ *
+ * The cache stores up to @a pages * @a items_per_page items at a
+ * time. The exact cache invalidation strategy is not defined here,
+ * but in general, a lower value for @a items_per_page means more
+ * memory overhead for the same number of items, but a higher value
+ * for @a items_per_page means more items are cleared at once. Both
+ * @a pages and @a items_per_page must be positive (though they both
+ * may certainly be 1).
+ *
+ * If @a thread_safe is true, and APR is compiled with threads, all
+ * accesses to the cache will be protected with a mutex. The @a id
+ * is a purely user-visible information that will allow coders to
+ * identify this cache instance in a #svn_cache__info_t struct.
+ * It does not influence the behavior of the cache itself.
+ *
+ * Note that NULL is a legitimate value for cache entries (and
+ * @a serialize_func will not be called on it).
+ *
+ * It is not safe for @a serialize_func nor @a deserialize_func to
+ * interact with the cache itself.
+ */
+svn_error_t *
+svn_cache__create_inprocess(svn_cache__t **cache_p,
+ svn_cache__serialize_func_t serialize_func,
+ svn_cache__deserialize_func_t deserialize_func,
+ apr_ssize_t klen,
+ apr_int64_t pages,
+ apr_int64_t items_per_page,
+ svn_boolean_t thread_safe,
+ const char *id,
+ apr_pool_t *pool);
+
+/**
+ * Creates a new cache in @a *cache_p, communicating to a memcached
+ * process via @a memcache. The elements in the cache will be indexed
+ * by keys of length @a klen, which may be APR_HASH_KEY_STRING if they
+ * are strings. Values will be serialized for memcached using @a
+ * serialize_func and deserialized using @a deserialize_func. Because
+ * the same memcached server may cache many different kinds of values,
+ * @a prefix should be specified to differentiate this cache from
+ * other caches. @a *cache_p will be allocated in @a result_pool.
+ *
+ * If @a deserialize_func is NULL, then the data is returned as an
+ * svn_string_t; if @a serialize_func is NULL, then the data is
+ * assumed to be an svn_stringbuf_t.
+ *
+ * These caches are always thread safe.
+ *
+ * These caches do not support svn_cache__iter.
+ *
+ * If Subversion was not built with apr_memcache support, always
+ * raises SVN_ERR_NO_APR_MEMCACHE.
+ */
+svn_error_t *
+svn_cache__create_memcache(svn_cache__t **cache_p,
+ svn_memcache_t *memcache,
+ svn_cache__serialize_func_t serialize_func,
+ svn_cache__deserialize_func_t deserialize_func,
+ apr_ssize_t klen,
+ const char *prefix,
+ apr_pool_t *result_pool);
+
+/**
+ * Given @a config, returns an APR memcached interface in @a
+ * *memcache_p allocated in @a result_pool if @a config contains entries in
+ * the SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS section describing
+ * memcached servers; otherwise, sets @a *memcache_p to NULL.
+ *
+ * If Subversion was not built with apr_memcache_support, then raises
+ * SVN_ERR_NO_APR_MEMCACHE if and only if @a config is configured to
+ * use memcache.
+ */
+svn_error_t *
+svn_cache__make_memcache_from_config(svn_memcache_t **memcache_p,
+ svn_config_t *config,
+ apr_pool_t *result_pool);
+
+/**
+ * Creates a new membuffer cache object in @a *cache. It will contain
+ * up to @a total_size bytes of data, using @a directory_size bytes
+ * for index information and the remainder for serialized objects.
+ *
+ * Since each index entry is about 50 bytes long, 1 to 10 percent of
+ * the @a total_size should be allocated to the @a directory_size,
+ * depending on the average serialized object size. Higher percentages
+ * will generally result in higher hit rates and reduced conflict
+ * resolution overhead.
+ *
+ * The cache will be split into @a segment_count segments of equal size.
+ * A higher number reduces lock contention but also limits the maximum
+ * cachable item size. If it is not a power of two, it will be rounded
+ * down to next lower power of two. Also, there is an implementation
+ * specific upper limit and the setting will be capped there automatically.
+ * If the number is 0, a default will be derived from @a total_size.
+ *
+ * If access to the resulting cache object is guaranteed to be serialized,
+ * @a thread_safe may be set to @c FALSE for maximum performance.
+ *
+ * There is no limit on the number of threads reading a given cache segment
+ * concurrently. Writes, however, need an exclusive lock on the respective
+ * segment. @a allow_blocking_writes controls contention is handled here.
+ * If set to TRUE, writes will wait until the lock becomes available, i.e.
+ * reads should be short. If set to FALSE, write attempts will be ignored
+ * (no data being written to the cache) if some reader or another writer
+ * currently holds the segment lock.
+ *
+ * Allocations will be made in @a result_pool, in particular the data buffers.
+ */
+svn_error_t *
+svn_cache__membuffer_cache_create(svn_membuffer_t **cache,
+ apr_size_t total_size,
+ apr_size_t directory_size,
+ apr_size_t segment_count,
+ svn_boolean_t thread_safe,
+ svn_boolean_t allow_blocking_writes,
+ apr_pool_t *result_pool);
+
+/**
+ * Creates a new cache in @a *cache_p, storing the data in a potentially
+ * shared @a membuffer object. The elements in the cache will be indexed
+ * by keys of length @a klen, which may be APR_HASH_KEY_STRING if they
+ * are strings. Values will be serialized for the memcache using @a
+ * serialize_func and deserialized using @a deserialize_func. Because
+ * the same memcache object may cache many different kinds of values
+ * form multiple caches, @a prefix should be specified to differentiate
+ * this cache from other caches. @a *cache_p will be allocated in @a result_pool.
+ *
+ * If @a deserialize_func is NULL, then the data is returned as an
+ * svn_string_t; if @a serialize_func is NULL, then the data is
+ * assumed to be an svn_stringbuf_t.
+ *
+ * If @a thread_safe is true, and APR is compiled with threads, all
+ * accesses to the cache will be protected with a mutex, if the shared
+ * @a memcache has also been created with thread_safe flag set.
+ *
+ * These caches do not support svn_cache__iter.
+ */
+svn_error_t *
+svn_cache__create_membuffer_cache(svn_cache__t **cache_p,
+ svn_membuffer_t *membuffer,
+ svn_cache__serialize_func_t serialize,
+ svn_cache__deserialize_func_t deserialize,
+ apr_ssize_t klen,
+ const char *prefix,
+ svn_boolean_t thread_safe,
+ apr_pool_t *result_pool);
+
+/**
+ * Sets @a handler to be @a cache's error handling routine. If any
+ * error is returned from a call to svn_cache__get or svn_cache__set, @a
+ * handler will be called with @a baton and the error, and the
+ * original function will return whatever error @a handler returns
+ * instead (possibly SVN_NO_ERROR); @a handler will receive the pool
+ * passed to the svn_cache_* function. @a scratch_pool is used for temporary
+ * allocations.
+ */
+svn_error_t *
+svn_cache__set_error_handler(svn_cache__t *cache,
+ svn_cache__error_handler_t handler,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Returns @c TRUE if the @a cache supports objects of the given @a size.
+ * There is no guarantee, that svn_cache__set() will actually store the
+ * respective object in that case. However, a @c FALSE return value indicates
+ * that an attempt to cache the item will either fail or impair the overall
+ * cache performance. @c FALSE will also be returned if @a cache is @c NULL.
+ */
+svn_boolean_t
+svn_cache__is_cachable(svn_cache__t *cache,
+ apr_size_t size);
+
+#define SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "memcached-servers"
+
+/**
+ * Fetches a value indexed by @a key from @a cache into @a *value,
+ * setting @a *found to TRUE iff it is in the cache and FALSE if it is
+ * not found. @a key may be NULL in which case @a *found will be
+ * FALSE. The value is copied into @a result_pool using the deserialize
+ * function provided to the cache's constructor.
+ */
+svn_error_t *
+svn_cache__get(void **value,
+ svn_boolean_t *found,
+ svn_cache__t *cache,
+ const void *key,
+ apr_pool_t *result_pool);
+
+/**
+ * Stores the value @a value under the key @a key in @a cache. Uses @a
+ * scratch_pool for temporary allocations. The cache makes copies of
+ * @a key and @a value if necessary (that is, @a key and @a value may
+ * have shorter lifetimes than the cache). @a key may be NULL in which
+ * case the cache will remain unchanged.
+ *
+ * If there is already a value for @a key, this will replace it. Bear
+ * in mind that in some circumstances this may leak memory (that is,
+ * the cache's copy of the previous value may not be immediately
+ * cleared); it is only guaranteed to not leak for caches created with
+ * @a items_per_page equal to 1.
+ */
+svn_error_t *
+svn_cache__set(svn_cache__t *cache,
+ const void *key,
+ void *value,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Iterates over the elements currently in @a cache, calling @a func
+ * for each one until there are no more elements or @a func returns an
+ * error. Uses @a scratch_pool for temporary allocations.
+ *
+ * If @a completed is not NULL, then on return - if @a func returns no
+ * errors - @a *completed will be set to @c TRUE.
+ *
+ * If @a func returns an error other than @c SVN_ERR_ITER_BREAK, that
+ * error is returned. When @a func returns @c SVN_ERR_ITER_BREAK,
+ * iteration is interrupted, but no error is returned and @a
+ * *completed is set to @c FALSE. (The error handler set by
+ * svn_cache__set_error_handler is not used for svn_cache__iter.)
+ *
+ * It is not legal to perform any other cache operations on @a cache
+ * inside @a func.
+ *
+ * svn_cache__iter is not supported by all cache implementations; see
+ * the svn_cache__create_* function for details.
+ */
+svn_error_t *
+svn_cache__iter(svn_boolean_t *completed,
+ svn_cache__t *cache,
+ svn_iter_apr_hash_cb_t func,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_cache__get() but will call a specific de-serialization
+ * function @a func. @a found will be set depending on whether the @a key
+ * has been found. Even if that reports @c TRUE, @a value may still return
+ * a @c NULL pointer depending on the logic inside @a func. For a @a NULL
+ * @a key, no data will be found. @a value will be allocated in
+ * @a result_pool.
+ */
+svn_error_t *
+svn_cache__get_partial(void **value,
+ svn_boolean_t *found,
+ svn_cache__t *cache,
+ const void *key,
+ svn_cache__partial_getter_func_t func,
+ void *baton,
+ apr_pool_t *result_pool);
+
+/**
+ * Find the item identified by @a key in the @a cache. If it has been found,
+ * call @a func for it and @a baton to potentially modify the data. Changed
+ * data will be written back to the cache. If the item cannot be found,
+ * or if @a key is NULL, @a func does not get called. @a scratch_pool is
+ * used for temporary allocations.
+ */
+svn_error_t *
+svn_cache__set_partial(svn_cache__t *cache,
+ const void *key,
+ svn_cache__partial_setter_func_t func,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Collect all available usage statistics on the cache instance @a cache
+ * and write the data into @a info. If @a reset has been set, access
+ * counters will be reset right after copying the statistics info.
+ * @a result_pool will be used for allocations.
+ */
+svn_error_t *
+svn_cache__get_info(svn_cache__t *cache,
+ svn_cache__info_t *info,
+ svn_boolean_t reset,
+ apr_pool_t *result_pool);
+
+/**
+ * Return the information given in @a info formatted as a multi-line string.
+ * Allocations take place in @a result_pool.
+ */
+svn_string_t *
+svn_cache__format_info(const svn_cache__info_t *info,
+ apr_pool_t *result_pool);
+
+/* Access the process-global (singleton) membuffer cache. The first call
+ * will automatically allocate the cache using the current cache config.
+ * NULL will be returned if the desired cache size is 0.
+ *
+ * @since New in 1.7.
+ */
+struct svn_membuffer_t *
+svn_cache__get_global_membuffer_cache(void);
+
+/** @} */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CACHE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_client_private.h b/contrib/subversion/subversion/include/private/svn_client_private.h
new file mode 100644
index 0000000..9eebc18
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_client_private.h
@@ -0,0 +1,299 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_client_private.h
+ * @brief Subversion-internal client APIs.
+ */
+
+#ifndef SVN_CLIENT_PRIVATE_H
+#define SVN_CLIENT_PRIVATE_H
+
+#include <apr_pools.h>
+
+#include "svn_ra.h"
+#include "svn_client.h"
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Return true if KIND is a revision kind that is dependent on the working
+ * copy. Otherwise, return false. */
+#define SVN_CLIENT__REVKIND_NEEDS_WC(kind) \
+ ((kind) == svn_opt_revision_base || \
+ (kind) == svn_opt_revision_previous || \
+ (kind) == svn_opt_revision_working || \
+ (kind) == svn_opt_revision_committed) \
+
+/* Return true if KIND is a revision kind that the WC can supply without
+ * contacting the repository. Otherwise, return false. */
+#define SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(kind) \
+ ((kind) == svn_opt_revision_base || \
+ (kind) == svn_opt_revision_working || \
+ (kind) == svn_opt_revision_committed)
+
+/* A location in a repository. */
+typedef struct svn_client__pathrev_t
+{
+ const char *repos_root_url;
+ const char *repos_uuid;
+ svn_revnum_t rev;
+ const char *url;
+} svn_client__pathrev_t;
+
+/* Return a new path-rev structure, allocated in RESULT_POOL,
+ * initialized with deep copies of REPOS_ROOT_URL, REPOS_UUID, REV and URL. */
+svn_client__pathrev_t *
+svn_client__pathrev_create(const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t rev,
+ const char *url,
+ apr_pool_t *result_pool);
+
+/* Return a new path-rev structure, allocated in RESULT_POOL,
+ * initialized with deep copies of REPOS_ROOT_URL, REPOS_UUID, and REV,
+ * and using the repository-relative RELPATH to construct the URL. */
+svn_client__pathrev_t *
+svn_client__pathrev_create_with_relpath(const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t rev,
+ const char *relpath,
+ apr_pool_t *result_pool);
+
+/* Set *PATHREV_P to a new path-rev structure, allocated in RESULT_POOL,
+ * initialized with deep copies of the repository root URL and UUID from
+ * RA_SESSION, and of REV and URL. */
+svn_error_t *
+svn_client__pathrev_create_with_session(svn_client__pathrev_t **pathrev_p,
+ svn_ra_session_t *ra_session,
+ svn_revnum_t rev,
+ const char *url,
+ apr_pool_t *result_pool);
+
+/* Return a deep copy of PATHREV, allocated in RESULT_POOL. */
+svn_client__pathrev_t *
+svn_client__pathrev_dup(const svn_client__pathrev_t *pathrev,
+ apr_pool_t *result_pool);
+
+/* Return a deep copy of PATHREV, with a URI-encoded representation of
+ * RELPATH joined on to the URL. Allocate the result in RESULT_POOL. */
+svn_client__pathrev_t *
+svn_client__pathrev_join_relpath(const svn_client__pathrev_t *pathrev,
+ const char *relpath,
+ apr_pool_t *result_pool);
+
+/* Return the repository-relative relpath of PATHREV. */
+const char *
+svn_client__pathrev_relpath(const svn_client__pathrev_t *pathrev,
+ apr_pool_t *result_pool);
+
+/* Return the repository-relative fspath of PATHREV. */
+const char *
+svn_client__pathrev_fspath(const svn_client__pathrev_t *pathrev,
+ apr_pool_t *result_pool);
+
+/* Given PATH_OR_URL, which contains either a working copy path or an
+ absolute URL, a peg revision PEG_REVISION, and a desired revision
+ REVISION, create an RA connection to that object as it exists in
+ that revision, following copy history if necessary. If REVISION is
+ younger than PEG_REVISION, then PATH_OR_URL will be checked to see
+ that it is the same node in both PEG_REVISION and REVISION. If it
+ is not, then @c SVN_ERR_CLIENT_UNRELATED_RESOURCES is returned.
+
+ BASE_DIR_ABSPATH is the working copy path the ra_session corresponds
+ to. If provided it will be used to read and dav props. So if provided
+ this directory MUST match the session anchor.
+
+ If PEG_REVISION->kind is 'unspecified', the peg revision is 'head'
+ for a URL or 'working' for a WC path. If REVISION->kind is
+ 'unspecified', the operative revision is the peg revision.
+
+ Store the resulting ra_session in *RA_SESSION_P. Store the final
+ resolved location of the object in *RESOLVED_LOC_P. RESOLVED_LOC_P
+ may be NULL if not wanted.
+
+ Use authentication baton cached in CTX to authenticate against the
+ repository.
+
+ Use POOL for all allocations. */
+svn_error_t *
+svn_client__ra_session_from_path2(svn_ra_session_t **ra_session_p,
+ svn_client__pathrev_t **resolved_loc_p,
+ const char *path_or_url,
+ const char *base_dir_abspath,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Given PATH_OR_URL, which contains either a working copy path or an
+ absolute URL, a peg revision PEG_REVISION, and a desired revision
+ REVISION, find the path at which that object exists in REVISION,
+ following copy history if necessary. If REVISION is younger than
+ PEG_REVISION, then check that PATH_OR_URL is the same node in both
+ PEG_REVISION and REVISION, and return @c
+ SVN_ERR_CLIENT_UNRELATED_RESOURCES if it is not the same node.
+
+ If PEG_REVISION->kind is 'unspecified', the peg revision is 'head'
+ for a URL or 'working' for a WC path. If REVISION->kind is
+ 'unspecified', the operative revision is the peg revision.
+
+ Store the actual location of the object in *RESOLVED_LOC_P.
+
+ RA_SESSION should be an open RA session pointing at the URL of
+ PATH_OR_URL, or NULL, in which case this function will open its own
+ temporary session.
+
+ Use authentication baton cached in CTX to authenticate against the
+ repository.
+
+ Use POOL for all allocations. */
+svn_error_t *
+svn_client__resolve_rev_and_url(svn_client__pathrev_t **resolved_loc_p,
+ svn_ra_session_t *ra_session,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** Return @c SVN_ERR_ILLEGAL_TARGET if TARGETS contains a mixture of
+ * URLs and paths; otherwise return SVN_NO_ERROR.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client__assert_homogeneous_target_type(const apr_array_header_t *targets);
+
+
+/* Create a svn_client_status_t structure *CST for LOCAL_ABSPATH, shallow
+ * copying data from *STATUS wherever possible and retrieving the other values
+ * where needed. Perform temporary allocations in SCRATCH_POOL and allocate the
+ * result in RESULT_POOL
+ */
+svn_error_t *
+svn_client__create_status(svn_client_status_t **cst,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *ANCESTOR_URL and *ANCESTOR_REVISION to the URL and revision,
+ * respectively, of the youngest common ancestor of the two locations
+ * PATH_OR_URL1@REV1 and PATH_OR_URL2@REV2. Set *ANCESTOR_RELPATH to
+ * NULL and *ANCESTOR_REVISION to SVN_INVALID_REVNUM if they have no
+ * common ancestor. This function assumes that PATH_OR_URL1@REV1 and
+ * PATH_OR_URL2@REV2 both refer to the same repository.
+ *
+ * Use the authentication baton cached in CTX to authenticate against
+ * the repository.
+ *
+ * See also svn_client__get_youngest_common_ancestor().
+ */
+svn_error_t *
+svn_client__youngest_common_ancestor(const char **ancestor_url,
+ svn_revnum_t *ancestor_rev,
+ const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Get the repository location of the base node at LOCAL_ABSPATH.
+ *
+ * A pathrev_t wrapper around svn_wc__node_get_base().
+ *
+ * Set *BASE_P to the location that this node was checked out at or last
+ * updated/switched to, regardless of any uncommitted changes (delete,
+ * replace and/or copy-here/move-here).
+ *
+ * If there is no base node at LOCAL_ABSPATH (such as when there is a
+ * locally added/copied/moved-here node that is not part of a replace),
+ * set *BASE_P to NULL.
+ */
+svn_error_t *
+svn_client__wc_node_get_base(svn_client__pathrev_t **base_p,
+ const char *wc_abspath,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Get the original location of the WC node at LOCAL_ABSPATH.
+ *
+ * A pathrev_t wrapper around svn_wc__node_get_origin().
+ *
+ * Set *ORIGIN_P to the origin of the WC node at WC_ABSPATH. If the node
+ * is a local copy, give the copy-from location. If the node is locally
+ * added or deleted, set *ORIGIN_P to NULL.
+ */
+svn_error_t *
+svn_client__wc_node_get_origin(svn_client__pathrev_t **origin_p,
+ const char *wc_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Produce a diff with depth DEPTH between two files or two directories at
+ * LOCAL_ABSPATH1 and LOCAL_ABSPATH2, using the provided diff callbacks to
+ * show changes in files. The files and directories involved may be part of
+ * a working copy or they may be unversioned. For versioned files, show
+ * property changes, too. */
+svn_error_t *
+svn_client__arbitrary_nodes_diff(const char *local_abspath1,
+ const char *local_abspath2,
+ svn_depth_t depth,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/* Copy the file or directory on URL in some repository to DST_ABSPATH,
+ * copying node information and properties. Resolve URL using PEG_REV and
+ * REVISION.
+ *
+ * If URL specifies a directory, create the copy using depth DEPTH.
+ *
+ * If MAKE_PARENTS is TRUE and DST_ABSPATH doesn't have an added parent
+ * create missing parent directories
+ */
+svn_error_t *
+svn_client__copy_foreign(const char *url,
+ const char *dst_abspath,
+ svn_opt_revision_t *peg_revision,
+ svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t make_parents,
+ svn_boolean_t already_locked,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CLIENT_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_cmdline_private.h b/contrib/subversion/subversion/include/private/svn_cmdline_private.h
new file mode 100644
index 0000000..ad16b66
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_cmdline_private.h
@@ -0,0 +1,228 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_cmdline_private.h
+ * @brief Private functions for Subversion cmdline.
+ */
+
+#ifndef SVN_CMDLINE_PRIVATE_H
+#define SVN_CMDLINE_PRIVATE_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_io.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** Write a property as an XML element into @a *outstr.
+ *
+ * If @a outstr is NULL, allocate @a *outstr in @a pool; else append to
+ * @a *outstr, allocating in @a outstr's pool
+ *
+ * @a propname is the property name. @a propval is the property value, which
+ * will be encoded if it contains unsafe bytes.
+ *
+ * If @a inherited_prop is TRUE then @a propname is an inherited property,
+ * otherwise @a propname is an explicit property.
+ */
+void
+svn_cmdline__print_xml_prop(svn_stringbuf_t **outstr,
+ const char *propname,
+ svn_string_t *propval,
+ svn_boolean_t inherited_prop,
+ apr_pool_t *pool);
+
+
+/** An implementation of @c svn_auth_gnome_keyring_unlock_prompt_func_t that
+ * prompts the user for default GNOME Keyring password.
+ *
+ * Expects a @c svn_cmdline_prompt_baton2_t to be passed as @a baton.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_cmdline__auth_gnome_keyring_unlock_prompt(char **keyring_password,
+ const char *keyring_name,
+ void *baton,
+ apr_pool_t *pool);
+
+/** Container for config options parsed with svn_cmdline__parse_config_option
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_cmdline__config_argument_t
+{
+ const char *file;
+ const char *section;
+ const char *option;
+ const char *value;
+} svn_cmdline__config_argument_t;
+
+/** Parser for 'FILE:SECTION:OPTION=[VALUE]'-style option arguments.
+ *
+ * Parses @a opt_arg and places its value in @a config_options, an apr array
+ * containing svn_cmdline__config_argument_t* elements, allocating the option
+ * data in @a pool
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_cmdline__parse_config_option(apr_array_header_t *config_options,
+ const char *opt_arg,
+ apr_pool_t *pool);
+
+/** Sets the config options in @a config_options, an apr array containing
+ * @c svn_cmdline__config_argument_t* elements, to the configuration in @a cfg,
+ * a hash mapping of <tt>const char *</tt> configuration file names to
+ * @c svn_config_t *'s. Write warnings to stderr.
+ *
+ * Use @a prefix as prefix and @a argument_name in warning messages.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_cmdline__apply_config_options(apr_hash_t *config,
+ const apr_array_header_t *config_options,
+ const char *prefix,
+ const char *argument_name);
+
+/* Return a string allocated in POOL that is a copy of STR but with each
+ * line prefixed with INDENT. A line is all characters up to the first
+ * CR-LF, LF-CR, CR or LF, or the end of STR if sooner. */
+const char *
+svn_cmdline__indent_string(const char *str,
+ const char *indent,
+ apr_pool_t *pool);
+
+/* Print to stdout a hash PROP_HASH that maps property names (char *) to
+ property values (svn_string_t *). The names are assumed to be in UTF-8
+ format; the values are either in UTF-8 (the special Subversion props) or
+ plain binary values.
+
+ If OUT is not NULL, then write to it rather than stdout.
+
+ If NAMES_ONLY is true, print just names, else print names and
+ values. */
+svn_error_t *
+svn_cmdline__print_prop_hash(svn_stream_t *out,
+ apr_hash_t *prop_hash,
+ svn_boolean_t names_only,
+ apr_pool_t *pool);
+
+/* Similar to svn_cmdline__print_prop_hash(), only output xml to *OUTSTR.
+ If INHERITED_PROPS is true, then PROP_HASH contains inherited properties,
+ otherwise PROP_HASH contains explicit properties. If *OUTSTR is NULL,
+ allocate it first from POOL, otherwise append to it. */
+svn_error_t *
+svn_cmdline__print_xml_prop_hash(svn_stringbuf_t **outstr,
+ apr_hash_t *prop_hash,
+ svn_boolean_t names_only,
+ svn_boolean_t inherited_props,
+ apr_pool_t *pool);
+
+
+/* Search for a text editor command in standard environment variables,
+ and invoke it to edit PATH. Use POOL for all allocations.
+
+ If EDITOR_CMD is not NULL, it is the name of the external editor
+ command to use, overriding anything else that might determine the
+ editor.
+
+ CONFIG is a hash of svn_config_t * items keyed on a configuration
+ category (SVN_CONFIG_CATEGORY_CONFIG et al), and may be NULL. */
+svn_error_t *
+svn_cmdline__edit_file_externally(const char *path,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ apr_pool_t *pool);
+
+/* Search for a text editor command in standard environment variables,
+ and invoke it to edit CONTENTS (using a temporary file created in
+ directory BASE_DIR). Return the new contents in *EDITED_CONTENTS,
+ or set *EDITED_CONTENTS to NULL if no edit was performed.
+
+ If EDITOR_CMD is not NULL, it is the name of the external editor
+ command to use, overriding anything else that might determine the
+ editor.
+
+ If TMPFILE_LEFT is NULL, the temporary file will be destroyed.
+ Else, the file will be left on disk, and its path returned in
+ *TMPFILE_LEFT.
+
+ CONFIG is a hash of svn_config_t * items keyed on a configuration
+ category (SVN_CONFIG_CATEGORY_CONFIG et al), and may be NULL.
+
+ If AS_TEXT is TRUE, recode CONTENTS and convert to native eol-style before
+ editing and back again afterwards. In this case, ENCODING determines the
+ encoding used during editing. If non-NULL, use the named encoding, else
+ use the system encoding. If AS_TEXT is FALSE, don't do any translation.
+ In that case, ENCODING is ignored.
+
+ Use POOL for all allocations. Use PREFIX as the prefix for the
+ temporary file used by the editor.
+
+ If return error, *EDITED_CONTENTS is not touched. */
+svn_error_t *
+svn_cmdline__edit_string_externally(svn_string_t **edited_contents,
+ const char **tmpfile_left,
+ const char *editor_cmd,
+ const char *base_dir,
+ const svn_string_t *contents,
+ const char *prefix,
+ apr_hash_t *config,
+ svn_boolean_t as_text,
+ const char *encoding,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_getopt_init(), which see.
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_cmdline__getopt_init(apr_getopt_t **os,
+ int argc,
+ const char *argv[],
+ apr_pool_t *pool);
+
+/* Determine whether interactive mode should be enabled, based on whether
+ * the user passed the --non-interactive or --force-interactive options.
+ * If neither option was passed, interactivity is enabled if standard
+ * input is connected to a terminal device.
+ *
+ * @since New in 1.8.
+ */
+svn_boolean_t
+svn_cmdline__be_interactive(svn_boolean_t non_interactive,
+ svn_boolean_t force_interactive);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CMDLINE_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_dav_protocol.h b/contrib/subversion/subversion/include/private/svn_dav_protocol.h
new file mode 100644
index 0000000..94cf069
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_dav_protocol.h
@@ -0,0 +1,68 @@
+/*
+ * svn_dav_protocol.h: Declarations of the protocol shared by the
+ * mod_dav_svn backend for httpd's mod_dav and its ra_serf RA DAV clients.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_DAV_PROTOCOL_H
+#define SVN_DAV_PROTOCOL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** Names for the custom HTTP REPORTs understood by mod_dav_svn, sans
+ namespace. */
+#define SVN_DAV__MERGEINFO_REPORT "mergeinfo-report"
+#define SVN_DAV__INHERITED_PROPS_REPORT "inherited-props-report"
+
+/** Names for XML child elements of the custom HTTP REPORTs understood
+ by mod_dav_svn, sans namespace. */
+#define SVN_DAV__CREATIONDATE "creationdate"
+#define SVN_DAV__MERGEINFO_ITEM "mergeinfo-item"
+#define SVN_DAV__MERGEINFO_PATH "mergeinfo-path"
+#define SVN_DAV__MERGEINFO_INFO "mergeinfo-info"
+#define SVN_DAV__PATH "path"
+#define SVN_DAV__INHERIT "inherit"
+#define SVN_DAV__REVISION "revision"
+#define SVN_DAV__INCLUDE_DESCENDANTS "include-descendants"
+#define SVN_DAV__VERSION_NAME "version-name"
+#define SVN_DAV__IPROP_ITEM "iprop-item"
+#define SVN_DAV__IPROP_PATH "iprop-path"
+#define SVN_DAV__IPROP_PROPNAME "iprop-propname"
+#define SVN_DAV__IPROP_PROPVAL "iprop-propval"
+
+/** Names of XML elements attributes and tags for svn_ra_change_rev_prop2()'s
+ extension of PROPPATCH. */
+#define SVN_DAV__OLD_VALUE "old-value"
+#define SVN_DAV__OLD_VALUE__ABSENT "absent"
+
+/** Helper typedef for svn_ra_change_rev_prop2() implementation. */
+typedef struct svn_dav__two_props_t {
+ const svn_string_t *const *old_value_p;
+ const svn_string_t *new_value;
+} svn_dav__two_props_t;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DAV_PROTOCOL_H */
diff --git a/contrib/subversion/subversion/include/private/svn_debug.h b/contrib/subversion/subversion/include/private/svn_debug.h
new file mode 100644
index 0000000..a596ba1
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_debug.h
@@ -0,0 +1,107 @@
+/* svn_debug.h : handy little debug tools for the SVN developers
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_DEBUG_H
+#define SVN_DEBUG_H
+
+#ifdef SVN_DEBUG
+#define SVN_DBG__PROTOTYPES
+#endif
+
+#ifdef SVN_DBG__PROTOTYPES
+#define APR_WANT_STDIO
+#include <apr_want.h>
+#include <apr_hash.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef SVN_DBG__PROTOTYPES
+/* A few helper functions for the macros below. */
+void
+svn_dbg__preamble(const char *file, long line, FILE *output);
+void
+svn_dbg__printf(const char *fmt, ...)
+ __attribute__((format(printf, 1, 2)));
+void
+svn_dbg__print_props(apr_hash_t *props,
+ const char *header_fmt,
+ ...)
+ __attribute__((format(printf, 2, 3)));
+#endif
+
+/* Only available when SVN_DEBUG is defined (ie. svn developers). Note that
+ we do *not* provide replacement macros/functions for proper releases.
+ The debug stuff should be removed before a commit.
+
+ ### maybe we will eventually decide to allow certain debug stuff to
+ ### remain in the code. at that point, we can rejigger this header. */
+#ifdef SVN_DEBUG
+
+/* Print to stdout. Edit this line if you need stderr. */
+#define SVN_DBG_OUTPUT stdout
+
+
+/* Defining this symbol in the source file, BEFORE INCLUDING THIS HEADER,
+ will switch off the output. Calls will still be made to svn_dbg__preamble()
+ for breakpoints. */
+#ifdef SVN_DBG_QUIET
+
+#define SVN_DBG(ARGS) svn_dbg__preamble(__FILE__, __LINE__, NULL)
+#define SVN_DBG_PROPS(ARGS) svn_dbg__preamble(__FILE__, __LINE__, NULL)
+
+#else
+
+/** Debug aid macro that prints the file:line of the call and printf-like
+ * arguments to the #SVN_DBG_OUTPUT stdio stream (#stdout by default). Typical
+ * usage:
+ *
+ * <pre>
+ * SVN_DBG(("rev=%ld kind=%s\n", revnum, svn_node_kind_to_word(kind)));
+ * </pre>
+ *
+ * outputs:
+ *
+ * <pre>
+ * DBG: kitchensink.c: 42: rev=3141592 kind=file
+ * </pre>
+ *
+ * Note that these output lines are filtered by our test suite automatically,
+ * so you don't have to worry about throwing off expected output.
+ */
+#define SVN_DBG(ARGS) (svn_dbg__preamble(__FILE__, __LINE__, SVN_DBG_OUTPUT), \
+ svn_dbg__printf ARGS)
+#define SVN_DBG_PROPS(ARGS) (svn_dbg__preamble(__FILE__, __LINE__, \
+ SVN_DBG_OUTPUT), \
+ svn_dbg__print_props ARGS)
+
+#endif
+
+#endif /* SVN_DEBUG */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DEBUG_H */
diff --git a/contrib/subversion/subversion/include/private/svn_delta_private.h b/contrib/subversion/subversion/include/private/svn_delta_private.h
new file mode 100644
index 0000000..4de85a9
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_delta_private.h
@@ -0,0 +1,128 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_delta_private.h
+ * @brief The Subversion delta/diff/editor library - Internal routines
+ */
+
+#ifndef SVN_DELTA_PRIVATE_H
+#define SVN_DELTA_PRIVATE_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_delta.h"
+#include "svn_editor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+typedef svn_error_t *(*svn_delta__start_edit_func_t)(
+ void *baton,
+ svn_revnum_t base_revision);
+
+typedef svn_error_t *(*svn_delta__target_revision_func_t)(
+ void *baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *scratch_pool);
+
+typedef svn_error_t *(*svn_delta__unlock_func_t)(
+ void *baton,
+ const char *path,
+ apr_pool_t *scratch_pool);
+
+
+/* See svn_editor__insert_shims() for more information. */
+struct svn_delta__extra_baton
+{
+ svn_delta__start_edit_func_t start_edit;
+ svn_delta__target_revision_func_t target_revision;
+ void *baton;
+};
+
+
+/** A temporary API to convert from a delta editor to an Ev2 editor. */
+svn_error_t *
+svn_delta__editor_from_delta(svn_editor_t **editor_p,
+ struct svn_delta__extra_baton **exb,
+ svn_delta__unlock_func_t *unlock_func,
+ void **unlock_baton,
+ const svn_delta_editor_t *deditor,
+ void *dedit_baton,
+ svn_boolean_t *send_abs_paths,
+ const char *repos_root,
+ const char *base_relpath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_delta_fetch_kind_func_t fetch_kind_func,
+ void *fetch_kind_baton,
+ svn_delta_fetch_props_func_t fetch_props_func,
+ void *fetch_props_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** A temporary API to convert from an Ev2 editor to a delta editor. */
+svn_error_t *
+svn_delta__delta_from_editor(const svn_delta_editor_t **deditor,
+ void **dedit_baton,
+ svn_editor_t *editor,
+ svn_delta__unlock_func_t unlock_func,
+ void *unlock_baton,
+ svn_boolean_t *found_abs_paths,
+ const char *repos_root,
+ const char *base_relpath,
+ svn_delta_fetch_props_func_t fetch_props_func,
+ void *fetch_props_baton,
+ svn_delta_fetch_base_func_t fetch_base_func,
+ void *fetch_base_baton,
+ struct svn_delta__extra_baton *exb,
+ apr_pool_t *pool);
+
+/**
+ * Get the data from IN, compress it according to the specified
+ * COMPRESSION_LEVEL and write the result to OUT.
+ * SVN_DELTA_COMPRESSION_LEVEL_NONE is valid for COMPRESSION_LEVEL.
+ */
+svn_error_t *
+svn__compress(svn_string_t *in,
+ svn_stringbuf_t *out,
+ int compression_level);
+
+/**
+ * Get the compressed data from IN, decompress it and write the result to
+ * OUT. Return an error if the decompressed size is larger than LIMIT.
+ */
+svn_error_t *
+svn__decompress(svn_string_t *in,
+ svn_stringbuf_t *out,
+ apr_size_t limit);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DELTA_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_dep_compat.h b/contrib/subversion/subversion/include/private/svn_dep_compat.h
new file mode 100644
index 0000000..71c0b9a
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_dep_compat.h
@@ -0,0 +1,184 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_compat.h
+ * @brief Compatibility macros and functions.
+ * @since New in 1.5.0.
+ */
+
+#ifndef SVN_DEP_COMPAT_H
+#define SVN_DEP_COMPAT_H
+
+#include <apr_version.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Check at compile time if the APR version is at least a certain
+ * level.
+ * @param major The major version component of the version checked
+ * for (e.g., the "1" of "1.3.0").
+ * @param minor The minor version component of the version checked
+ * for (e.g., the "3" of "1.3.0").
+ * @param patch The patch level component of the version checked
+ * for (e.g., the "0" of "1.3.0").
+ *
+ * @since New in 1.5.
+ */
+#ifndef APR_VERSION_AT_LEAST /* Introduced in APR 1.3.0 */
+#define APR_VERSION_AT_LEAST(major,minor,patch) \
+(((major) < APR_MAJOR_VERSION) \
+ || ((major) == APR_MAJOR_VERSION && (minor) < APR_MINOR_VERSION) \
+ || ((major) == APR_MAJOR_VERSION && (minor) == APR_MINOR_VERSION && \
+ (patch) <= APR_PATCH_VERSION))
+#endif /* APR_VERSION_AT_LEAST */
+
+/**
+ * If we don't have a recent enough APR, emulate the behavior of the
+ * apr_array_clear() API.
+ */
+#if !APR_VERSION_AT_LEAST(1,3,0)
+#define apr_array_clear(arr) (arr)->nelts = 0
+#endif
+
+#if !APR_VERSION_AT_LEAST(1,3,0)
+/* Equivalent to the apr_hash_clear() function in APR >= 1.3.0. Used to
+ * implement the 'apr_hash_clear' macro if the version of APR that
+ * we build against does not provide the apr_hash_clear() function. */
+void svn_hash__clear(struct apr_hash_t *ht);
+
+/**
+ * If we don't have a recent enough APR, emulate the behavior of the
+ * apr_hash_clear() API.
+ */
+#define apr_hash_clear(ht) svn_hash__clear(ht)
+#endif
+
+#if !APR_VERSION_AT_LEAST(1,0,0)
+#define APR_UINT64_C(val) UINT64_C(val)
+#define APR_FPROT_OS_DEFAULT APR_OS_DEFAULT
+#endif
+
+#if !APR_VERSION_AT_LEAST(1,3,0)
+#define APR_UINT16_MAX 0xFFFFU
+#define APR_INT16_MAX 0x7FFF
+#define APR_INT16_MIN (-APR_INT16_MAX-1)
+#define APR_UINT32_MAX 0xFFFFFFFFU
+#define APR_INT32_MAX 0x7FFFFFFF
+#define APR_INT32_MIN (-APR_INT32_MAX-1)
+#define APR_UINT64_MAX APR_UINT64_C(0xFFFFFFFFFFFFFFFF)
+#define APR_INT64_MAX APR_INT64_C(0x7FFFFFFFFFFFFFFF)
+#define APR_INT64_MIN (-APR_INT64_MAX-1)
+#define APR_SIZE_MAX (~(apr_size_t)0)
+
+#if APR_SIZEOF_VOIDP == 8
+typedef apr_uint64_t apr_uintptr_t;
+#else
+typedef apr_uint32_t apr_uintptr_t;
+#endif
+#endif /* !APR_VERSION_AT_LEAST(1,3,0) */
+
+/**
+ * Work around a platform dependency issue. apr_thread_rwlock_trywrlock()
+ * will make APR_STATUS_IS_EBUSY() return TRUE if the lock could not be
+ * acquired under Unix. Under Windows, this will not work. So, provide
+ * a more portable substitute.
+ *
+ * @since New in 1.8.
+ */
+#ifdef WIN32
+#define SVN_LOCK_IS_BUSY(x) \
+ (APR_STATUS_IS_EBUSY(x) || (x) == APR_FROM_OS_ERROR(WAIT_TIMEOUT))
+#else
+#define SVN_LOCK_IS_BUSY(x) APR_STATUS_IS_EBUSY(x)
+#endif
+
+/**
+ * Check at compile time if the Serf version is at least a certain
+ * level.
+ * @param major The major version component of the version checked
+ * for (e.g., the "1" of "1.3.0").
+ * @param minor The minor version component of the version checked
+ * for (e.g., the "3" of "1.3.0").
+ * @param patch The patch level component of the version checked
+ * for (e.g., the "0" of "1.3.0").
+ *
+ * @since New in 1.5.
+ */
+#ifndef SERF_VERSION_AT_LEAST /* Introduced in Serf 0.1.1 */
+#define SERF_VERSION_AT_LEAST(major,minor,patch) \
+(((major) < SERF_MAJOR_VERSION) \
+ || ((major) == SERF_MAJOR_VERSION && (minor) < SERF_MINOR_VERSION) \
+ || ((major) == SERF_MAJOR_VERSION && (minor) == SERF_MINOR_VERSION && \
+ (patch) <= SERF_PATCH_VERSION))
+#endif /* SERF_VERSION_AT_LEAST */
+
+/**
+ * By default, if libsvn is built against one version of SQLite
+ * and then run using an older version, svn will error out:
+ *
+ * svn: Couldn't perform atomic initialization
+ * svn: SQLite compiled for 3.7.4, but running with 3.7.3
+ *
+ * That can be annoying when building on a modern system in order
+ * to deploy on a less modern one. So these constants allow one
+ * to specify how old the system being deployed on might be.
+ * For example,
+ *
+ * EXTRA_CFLAGS += -DSVN_SQLITE_MIN_VERSION_NUMBER=3007003
+ * EXTRA_CFLAGS += '-DSVN_SQLITE_MIN_VERSION="3.7.3"'
+ *
+ * turns on code that works around infelicities in older versions
+ * as far back as 3.7.3 and relaxes the check at initialization time
+ * to permit them.
+ *
+ * @since New in 1.8.
+ */
+#ifndef SVN_SQLITE_MIN_VERSION_NUMBER
+#define SVN_SQLITE_MIN_VERSION_NUMBER SQLITE_VERSION_NUMBER
+#define SVN_SQLITE_MIN_VERSION SQLITE_VERSION
+#endif /* SVN_SQLITE_MIN_VERSION_NUMBER */
+
+/**
+ * Check at compile time if the SQLite version is at least a certain
+ * level.
+ * @param major The major version component of the version checked
+ * for (e.g., the "1" of "1.3.0").
+ * @param minor The minor version component of the version checked
+ * for (e.g., the "3" of "1.3.0").
+ * @param patch The patch level component of the version checked
+ * for (e.g., the "0" of "1.3.0").
+ *
+ * @since New in 1.6.
+ */
+#ifndef SQLITE_VERSION_AT_LEAST
+#define SQLITE_VERSION_AT_LEAST(major,minor,patch) \
+((major*1000000 + minor*1000 + patch) <= SVN_SQLITE_MIN_VERSION_NUMBER)
+#endif /* SQLITE_VERSION_AT_LEAST */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DEP_COMPAT_H */
diff --git a/contrib/subversion/subversion/include/private/svn_diff_private.h b/contrib/subversion/subversion/include/private/svn_diff_private.h
new file mode 100644
index 0000000..3b6e591
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_diff_private.h
@@ -0,0 +1,115 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ */
+
+
+#ifndef SVN_DIFF_PRIVATE_H
+#define SVN_DIFF_PRIVATE_H
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_types.h"
+#include "svn_io.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* The separator string used below the "Index:" or similar line of
+ * Subversion's Unidiff-like diff format. */
+#define SVN_DIFF__EQUAL_STRING \
+ "==================================================================="
+
+/* The separator string used below the "Properties on ..." line of
+ * Subversion's Unidiff-like diff format. */
+#define SVN_DIFF__UNDER_STRING \
+ "___________________________________________________________________"
+
+/* The string used to mark a line in a hunk that doesn't end with a newline,
+ * when diffing a file. Intentionally not marked for translation, for wider
+ * interoperability with patch(1) programs. */
+#define SVN_DIFF__NO_NEWLINE_AT_END_OF_FILE \
+ "\\ No newline at end of file"
+
+/* The string used to mark a line in a hunk that doesn't end with a newline,
+ * when diffing a Subversion property. */
+#define SVN_DIFF__NO_NEWLINE_AT_END_OF_PROPERTY \
+ "\\ No newline at end of property"
+
+/* Write a unidiff "---" and "+++" header to OUTPUT_STREAM.
+ *
+ * Write "---" followed by a space and OLD_HEADER and a newline,
+ * then "+++" followed by a space and NEW_HEADER and a newline.
+ *
+ * The text will be encoded into HEADER_ENCODING.
+ */
+svn_error_t *
+svn_diff__unidiff_write_header(svn_stream_t *output_stream,
+ const char *header_encoding,
+ const char *old_header,
+ const char *new_header,
+ apr_pool_t *scratch_pool);
+
+/* Display property changes in pseudo-Unidiff format.
+ *
+ * Write to @a outstream the changes described by @a propchanges based on
+ * original properties @a original_props.
+ *
+ * Write all mark-up text (headers and so on) using the character encoding
+ * @a encoding.
+ *
+ * ### I think the idea is: we want the output to use @a encoding, and
+ * we will assume the text of the user's files and the values of any
+ * user-defined properties are already using @a encoding, so we don't
+ * want to re-code the *whole* output.
+ * So, shouldn't we also convert all prop names and all 'svn:*' prop
+ * values to @a encoding, since we know those are stored in UTF-8?
+ *
+ * @a original_props is a hash mapping (const char *) property names to
+ * (svn_string_t *) values. @a propchanges is an array of svn_prop_t
+ * representing the new values for any of the properties that changed, with
+ * a NULL value to represent deletion.
+ *
+ * If @a pretty_print_mergeinfo is true, then describe 'svn:mergeinfo'
+ * property changes in a human-readable form that says what changes were
+ * merged or reverse merged; otherwise (or if the mergeinfo property values
+ * don't parse correctly) display them just like any other property.
+ *
+ * Use @a pool for temporary allocations.
+ */
+svn_error_t *
+svn_diff__display_prop_diffs(svn_stream_t *outstream,
+ const char *encoding,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ svn_boolean_t pretty_print_mergeinfo,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DIFF_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_diff_tree.h b/contrib/subversion/subversion/include/private/svn_diff_tree.h
new file mode 100644
index 0000000..597a59b
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_diff_tree.h
@@ -0,0 +1,357 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_wc.h
+ * @brief Generic diff handler. Replacing the old svn_wc_diff_callbacks4_t
+ * infrastructure
+ */
+
+#ifndef SVN_DIFF_PROCESSOR_H
+#define SVN_DIFF_PROCESSOR_H
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * About the diff tree processor.
+ *
+ * Subversion uses two kinds of editors to describe changes. One to
+ * describe changes on how to *exactly* transform one tree to another tree,
+ * as efficiently as possible and one to describe the difference between trees
+ * in order to review the changes, or to allow applying them on a third tree
+ * which is similar to those other trees.
+ *
+ * The first case was originally handled by svn_delta_editor_t and might be
+ * replaced by svn_editor_t in a future version. This diff processor handles
+ * the other case and as such forms the layer below our diff and merge
+ * handling.
+ *
+ * The major difference between this and the other editors is that this diff
+ * always provides access to the full text and/or properties in the left and
+ * right tree when applicable to allow processor implementers to decide how
+ * to interpret changes.
+ *
+ * Originally this diff processor was not formalized explicitly, but
+ * informally handled by the working copy diff callbacks. These callbacks just
+ * provided the information to drive a unified diff and a textual merge. To go
+ * one step further and allow full tree conflict detection we needed a better
+ * defined diff handling. Instead of adding yet a few more functions and
+ * arguments to the already overloaded diff callbacks the api was completely
+ * redesigned with a few points in mind.
+ *
+ * * It must be able to drive the old callbacks interface without users
+ * noticing the difference (100% compatible).
+ * (Implemented as svn_wc__wrap_diff_callbacks())
+ *
+ * * It should provide the information that was missing in the old interface,
+ * but required to close existing issues.
+ *
+ * E.g. - properties and children on deleted directories.
+ * - revision numbers and copyfrom information on directories.
+ *
+ * To cleanup the implementation and make it easier on diff processors to
+ * handle the results I also added the following constraints.
+ *
+ * * Diffs should be fully reversable: anything that is deleted should be
+ * available, just like something that is added.
+ * (Proven via svn_diff__tree_processor_reverse_create)
+ * ### Still in doubt if *_deleted() needs a copy_to argument, for the
+ * ### 99% -> 100%.
+ *
+ * * Diff processors should have an easy way to communicate that they are
+ * not interrested in certain expensive to obtain results.
+ *
+ * * Directories should have clear open and close events to allow adding them
+ * before their children, but still allowing property changes to have
+ * defined behavior.
+ *
+ * * Files and directories should be handled as similar as possible as in
+ * many cases they are just nodes in a tree.
+ *
+ * * It should be easy to create diff wrappers to apply certain transforms.
+ *
+ * During the creation an additional requirement of knowing about 'some
+ * absent' nodes was added, to allow the merge to work on just this processor
+ * api.
+ *
+ * The api describes a clean open-close walk through a tree, depending on the
+ * driver multiple siblings can be described at the same time, but when a
+ * directory is closed all descendants are done.
+ *
+ * Note that it is possible for nodes to be described as a delete followed by
+ * an add at the same place within one parent. (Iff the diff is reversed you
+ * can see an add followed by a delete!)
+ *
+ * The directory batons live between the open and close events of a directory
+ * and are thereby guaranteed to outlive the batons of their descendants.
+ */
+
+/* Describes the source of a merge */
+typedef struct svn_diff_source_t
+{
+ /* Always available */
+ svn_revnum_t revision;
+
+ /* Depending on the driver available for copyfrom */
+ const char *repos_relpath;
+} svn_diff_source_t;
+
+/**
+ * A callback vtable invoked by our diff-editors, as they receive diffs
+ * from the server. 'svn diff' and 'svn merge' implement their own versions
+ * of this vtable.
+ *
+ * All callbacks receive the processor and at least a parent baton. Forwarding
+ * the processor allows future extensions to call into the old functions without
+ * revving the entire API.
+ *
+ * Users must call svn_diff__tree_processor_create() to allow adding new
+ * callbacks later. (E.g. when we decide how to add move support) These
+ * extensions can then just call into other callbacks.
+ *
+ * @since New in 1.8.
+ */
+typedef struct svn_diff_tree_processor_t
+{
+ /** The value passed to svn_diff__tree_processor_create() as BATON.
+ */
+ void *baton; /* To avoid an additional in some places */
+
+ /* Called before a directories children are processed.
+ *
+ * Set *SKIP_CHILDREN to TRUE, to skip calling callbacks for all
+ * children.
+ *
+ * Set *SKIP to TRUE to skip calling the added, deleted, changed
+ * or closed callback for this node only.
+ */
+ svn_error_t *
+ (*dir_opened)(void **new_dir_baton,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *parent_dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+ /* Called after a directory and all its children are added
+ */
+ svn_error_t *
+ (*dir_added)(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool);
+
+ /* Called after all children of this node are reported as deleted.
+ *
+ * The default implementation calls dir_closed().
+ */
+ svn_error_t *
+ (*dir_deleted)(const char *relpath,
+ const svn_diff_source_t *left_source,
+ /*const*/ apr_hash_t *left_props,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool);
+
+ /* Called instead of dir_closed() if the properties on the directory
+ * were modified.
+ *
+ * The default implementation calls dir_closed().
+ */
+ svn_error_t *
+ (*dir_changed)(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ const apr_array_header_t *prop_changes,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool);
+
+ /* Called when a directory is closed without applying changes to
+ * the directory itself.
+ *
+ * When dir_changed or dir_deleted are handled by the default implementation
+ * they call dir_closed()
+ */
+ svn_error_t *
+ (*dir_closed)(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool);
+
+ /* Called before file_added(), file_deleted(), file_changed() and
+ file_closed()
+ */
+ svn_error_t *
+ (*file_opened)(void **new_file_baton,
+ svn_boolean_t *skip,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+ /* Called after file_opened() for newly added and copied files */
+ svn_error_t *
+ (*file_added)(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ const char *copyfrom_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *file_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool);
+
+ /* Called after file_opened() for deleted or moved away files */
+ svn_error_t *
+ (*file_deleted)(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const char *left_file,
+ /*const*/ apr_hash_t *left_props,
+ void *file_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool);
+
+ /* Called after file_opened() for changed files */
+ svn_error_t *
+ (*file_changed)(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const char *left_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_boolean_t file_modified,
+ const apr_array_header_t *prop_changes,
+ void *file_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool);
+
+ /* Called after file_opened() for unmodified files */
+ svn_error_t *
+ (*file_closed)(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *file_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool);
+
+ /* Called when encountering a marker for an absent file or directory */
+ svn_error_t *
+ (*node_absent)(const char *relpath,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool);
+} svn_diff_tree_processor_t;
+
+/**
+ * Create a new svn_diff_tree_processor_t instance with all functions
+ * set to a callback doing nothing but copying the parent baton to
+ * the new baton.
+ *
+ * @since New in 1.8.
+ */
+svn_diff_tree_processor_t *
+svn_diff__tree_processor_create(void *baton,
+ apr_pool_t *result_pool);
+
+/**
+ * Create a new svn_diff_tree_processor_t instance with all functions setup
+ * to call into another svn_diff_tree_processor_t processor, but with all
+ * adds and deletes inverted.
+ *
+ * @since New in 1.8.
+ */ /* Used by libsvn clients repository diff */
+const svn_diff_tree_processor_t *
+svn_diff__tree_processor_reverse_create(const svn_diff_tree_processor_t * processor,
+ const char *prefix_relpath,
+ apr_pool_t *result_pool);
+
+/**
+ * Create a new svn_diff_tree_processor_t instance with all functions setup
+ * to call into processor for all paths equal to and below prefix_relpath.
+ *
+ * @since New in 1.8.
+ */ /* Used by libsvn clients repository diff */
+const svn_diff_tree_processor_t *
+svn_diff__tree_processor_filter_create(const svn_diff_tree_processor_t *processor,
+ const char *prefix_relpath,
+ apr_pool_t *result_pool);
+
+/**
+ * Create a new svn_diff_tree_processor_t instace with all function setup
+ * to call into processor with all adds with copyfrom information transformed
+ * to simple node changes.
+ *
+ * @since New in 1.8.
+ */ /* Used by libsvn_wc diff editor */
+const svn_diff_tree_processor_t *
+svn_diff__tree_processor_copy_as_changed_create(
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool);
+
+
+/**
+ * Create a new svn_diff_tree_processor_t instance with all functions setup
+ * to first call into processor1 and then processor2.
+ *
+ * This function is mostly a debug and migration helper.
+ *
+ * @since New in 1.8.
+ */ /* Used by libsvn clients repository diff */
+const svn_diff_tree_processor_t *
+svn_diff__tree_processor_tee_create(const svn_diff_tree_processor_t *processor1,
+ const svn_diff_tree_processor_t *processor2,
+ apr_pool_t *result_pool);
+
+
+svn_diff_source_t *
+svn_diff__source_create(svn_revnum_t revision,
+ apr_pool_t *result_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DIFF_PROCESSOR_H */
+
diff --git a/contrib/subversion/subversion/include/private/svn_doxygen.h b/contrib/subversion/subversion/include/private/svn_doxygen.h
new file mode 100644
index 0000000..426e3f7
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_doxygen.h
@@ -0,0 +1,32 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * @mainpage Subversion Documentation
+ *
+ * This documentation covers the public APIs provided by the Subversion
+ * libraries. It is intended mainly for programmers, both those working
+ * on Subversion itself, as well as developers of 3rd-party applications
+ * intending to use these APIs. For more information about using Subversion,
+ * see the Subversion Book at http://svnbook.red-bean.com/.
+ *
+ * To learn more about Subversion, please visit http://subversion.apache.org/.
+ */
diff --git a/contrib/subversion/subversion/include/private/svn_editor.h b/contrib/subversion/subversion/include/private/svn_editor.h
new file mode 100644
index 0000000..d714bb1
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_editor.h
@@ -0,0 +1,1194 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_editor.h
+ * @brief Tree editing functions and structures
+ */
+
+#ifndef SVN_EDITOR_H
+#define SVN_EDITOR_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_io.h" /* for svn_stream_t */
+#include "svn_delta.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Temporarily private stuff (should move to svn_delta.h when Editor
+ V2 is made public) ***/
+
+/** Callback to retrieve a node's entire set of properties. This is
+ * needed by the various editor shims in order to effect backwards
+ * compatibility.
+ *
+ * Implementations should set @a *props to the hash of properties
+ * associated with @a path in @a base_revision, allocating that hash
+ * and its contents in @a result_pool, and should use @a scratch_pool
+ * for temporary allocations.
+ *
+ * @a baton is an implementation-specific closure.
+ */
+typedef svn_error_t *(*svn_delta_fetch_props_func_t)(
+ apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool
+ );
+
+/** Callback to retrieve a node's kind. This is needed by the various
+ * editor shims in order to effect backwards compatibility.
+ *
+ * Implementations should set @a *kind to the node kind of @a path in
+ * @a base_revision, using @a scratch_pool for temporary allocations.
+ *
+ * @a baton is an implementation-specific closure.
+ */
+typedef svn_error_t *(*svn_delta_fetch_kind_func_t)(
+ svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool
+ );
+
+/** Callback to fetch the name of a file to use as a delta base.
+ *
+ * Implementations should set @a *filename to the name of a file
+ * suitable for use as a delta base for @a path in @a base_revision
+ * (allocating @a *filename from @a result_pool), or to @c NULL if the
+ * base stream is empty. @a scratch_pool is provided for temporary
+ * allocations.
+ *
+ * @a baton is an implementation-specific closure.
+ */
+typedef svn_error_t *(*svn_delta_fetch_base_func_t)(
+ const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool
+ );
+
+/** Collection of callbacks used for the shim code. This structure
+ * may grow additional fields in the future. Therefore, always use
+ * svn_delta_shim_callbacks_default() to allocate new instances of it.
+ */
+typedef struct svn_delta_shim_callbacks_t
+{
+ svn_delta_fetch_props_func_t fetch_props_func;
+ svn_delta_fetch_kind_func_t fetch_kind_func;
+ svn_delta_fetch_base_func_t fetch_base_func;
+ void *fetch_baton;
+} svn_delta_shim_callbacks_t;
+
+/** Return a collection of default shim functions in @a result_pool.
+ */
+svn_delta_shim_callbacks_t *
+svn_delta_shim_callbacks_default(apr_pool_t *result_pool);
+
+
+
+/** Transforming trees ("editing").
+ *
+ * In Subversion, we have a number of occasions where we transform a tree
+ * from one state into another. This process is called "editing" a tree.
+ *
+ * In processing a `commit' command:
+ * - The client examines its working copy data to determine the set of
+ * changes necessary to transform its base tree into the desired target.
+ * - The client networking library delivers that set of changes/operations
+ * across the wire as an equivalent series of network requests (for
+ * example, to svnserve as an ra_svn protocol stream, or to an
+ * Apache httpd server as WebDAV commands)
+ * - The server receives those requests and applies the sequence of
+ * operations on a revision, producing a transaction representing the
+ * desired target.
+ * - The Subversion server then commits the transaction to the filesystem.
+ *
+ * In processing an `update' command, the process is reversed:
+ * - The Subversion server module talks to the filesystem and computes a
+ * set of changes necessary to bring the client's working copy up to date.
+ * - The server serializes this description of changes, and delivers it to
+ * the client.
+ * - The client networking library receives that reply, producing a set
+ * of changes/operations to alter the working copy into the revision
+ * requested by the update command.
+ * - The working copy library applies those operations to the working copy
+ * to align it with the requested update target.
+ *
+ * The series of changes (or operations) necessary to transform a tree from
+ * one state into another is passed between subsystems using this "editor"
+ * interface. The "receiver" edits its tree according to the operations
+ * described by the "driver".
+ *
+ * Note that the driver must have a perfect understanding of the tree which
+ * the receiver will be applying edits upon. There is no room for error here,
+ * and the interface embodies assumptions/requirements that the driver has
+ * about the targeted tree. As a result, this interface is a standardized
+ * mechanism of *describing* those change operations, but the intimate
+ * knowledge between the driver and the receiver implies some level of
+ * coupling between those subsystems.
+ *
+ * The set of changes, and the data necessary to describe it entirely, is
+ * completely unbounded. An addition of one simple 20 GB file might be well
+ * past the available memory of a machine processing these operations.
+ * As a result, the API to describe the changes is designed to be applied
+ * in a sequential (and relatively random-access) model. The operations
+ * can be streamed from the driver to the receiver, resulting in the
+ * receiver editing its tree to the target state defined by the driver.
+ *
+ *
+ * <h3>History</h3>
+ *
+ * Classically, Subversion had a notion of a "tree delta" which could be
+ * passed around as an independent entity. Theory implied this delta was an
+ * entity in its own right, to be used when and where necessary.
+ * Unfortunately, this theory did not work well in practice. The producer
+ * and consumer of these tree deltas were (and are) tightly coupled. As noted
+ * above, the tree delta producer needed to be *totally* aware of the tree
+ * that it needed to edit. So rather than telling the delta consumer how to
+ * edit its tree, the classic #svn_delta_editor_t interface focused
+ * entirely on the tree delta, an intermediate (logical) data structure
+ * which was unusable outside of the particular, coupled pairing of producer
+ * and consumer. This generation of the API forgoes the logical tree delta
+ * entity and directly passes the necessary edits/changes/operations from
+ * the producer to the consumer. In our new parlance, one subsystem "drives"
+ * a set of operations describing the change, and a "receiver" accepts and
+ * applies them to its tree.
+ *
+ * The classic interface was named #svn_delta_editor_t and was described
+ * idiomatically as the "editor interface". This generation of the interface
+ * retains the "editor" name for that reason. All notions of a "tree delta"
+ * structure are no longer part of this interface.
+ *
+ * The old interface was purely vtable-based and used a number of special
+ * editors which could be interposed between the driver and receiver. Those
+ * editors provided cancellation, debugging, and other various operations.
+ * While the "interposition" pattern is still possible with this interface,
+ * the most common functionality (cancellation and debugging) have been
+ * integrated directly into this new editor system.
+ *
+ *
+ * <h3>Implementation Plan</h3>
+ * @note This section can be removed after Ev2 is fully implemented.
+ *
+ * The delta editor is pretty engrained throughout Subversion, so attempting
+ * to replace it in situ is somewhat akin to performing open heart surgery
+ * while the patient is running a marathon. However, a viable plan should
+ * make things a bit easier, and help parallelize the work.
+ *
+ * In short, the following items need to be done:
+ * -# Implement backward compatibility wrappers ("shims")
+ * -# Use shims to update editor consumers to Ev2
+ * -# Update editor producers to drive Ev2
+ * - This will largely involve rewriting the RA layers to accept and
+ * send Ev2 commands
+ * -# Optimize consumers and producers to leverage the features of Ev2
+ *
+ * The shims are largely self-contained, and as of this writing, are almost
+ * complete. They can be released without much ado. However, they do add
+ * <em>significant</em> performance regressions, which make releasing code
+ * which is half-delta-editor and half-Ev2 inadvisable. As such, the updating
+ * of producers and consumers to Ev2 will probably need to wait until 1.9,
+ * though it could be largely parallelized.
+ *
+ *
+ * @defgroup svn_editor The editor interface
+ * @{
+ */
+
+/** An abstract object that edits a target tree.
+ *
+ * @note The term "follow" means at any later time in the editor drive.
+ * Terms such as "must", "must not", "required", "shall", "shall not",
+ * "should", "should not", "recommended", "may", and "optional" in this
+ * document are to be interpreted as described in RFC 2119.
+ *
+ * @note The editor objects are *not* reentrant. The receiver should not
+ * directly or indirectly invoke an editor API with the same object unless
+ * it has been marked as explicitly supporting reentrancy during a
+ * receiver's callback. This limitation extends to the cancellation
+ * callback, too. (This limitation is due to the scratch_pool shared by
+ * all callbacks, and cleared after each callback; a reentrant call could
+ * clear the outer call's pool). Note that the code itself is reentrant, so
+ * there is no problem using the APIs on different editor objects.
+ *
+ * \n
+ * <h3>Life-Cycle</h3>
+ *
+ * - @b Create: A receiver uses svn_editor_create() to create an
+ * "empty" svn_editor_t. It cannot be used yet, since it still lacks
+ * actual callback functions. svn_editor_create() sets the
+ * #svn_editor_t's callback baton and scratch pool that the callback
+ * functions receive, as well as a cancellation callback and baton
+ * (see "Cancellation" below).
+ *
+ * - @b Set callbacks: The receiver calls svn_editor_setcb_many() or a
+ * succession of the other svn_editor_setcb_*() functions to tell
+ * #svn_editor_t which functions to call when driven by the various
+ * operations. Callback functions are implemented by the receiver and must
+ * adhere to the @c svn_editor_cb_*_t function types as expected by the
+ * svn_editor_setcb_*() functions. See: \n
+ * svn_editor_cb_many_t \n
+ * svn_editor_setcb_many() \n
+ * or \n
+ * svn_editor_setcb_add_directory() \n
+ * svn_editor_setcb_add_file() \n
+ * svn_editor_setcb_add_symlink() \n
+ * svn_editor_setcb_add_absent() \n
+ * svn_editor_setcb_alter_directory() \n
+ * svn_editor_setcb_alter_file() \n
+ * svn_editor_setcb_alter_symlink() \n
+ * svn_editor_setcb_delete() \n
+ * svn_editor_setcb_copy() \n
+ * svn_editor_setcb_move() \n
+ * svn_editor_setcb_rotate() \n
+ * svn_editor_setcb_complete() \n
+ * svn_editor_setcb_abort()
+ *
+ * - @b Drive: The driver is provided with the completed #svn_editor_t
+ * instance. (It is typically passed to a generic driving
+ * API, which could receive the driving editor calls over the network
+ * by providing a proxy #svn_editor_t on the remote side.)
+ * The driver invokes the #svn_editor_t instance's callback functions
+ * according to the restrictions defined below, in order to describe the
+ * entire set of operations necessary to transform the receiver's tree
+ * into the desired target. The callbacks can be invoked using the
+ * svn_editor_*() functions, i.e.: \n
+ * svn_editor_add_directory() \n
+ * svn_editor_add_file() \n
+ * svn_editor_add_symlink() \n
+ * svn_editor_add_absent() \n
+ * svn_editor_alter_directory() \n
+ * svn_editor_alter_file() \n
+ * svn_editor_alter_symlink() \n
+ * svn_editor_delete() \n
+ * svn_editor_copy() \n
+ * svn_editor_move() \n
+ * svn_editor_rotate()
+ * \n\n
+ * Just before each callback invocation is carried out, the @a cancel_func
+ * that was passed to svn_editor_create() is invoked to poll any
+ * external reasons to cancel the sequence of operations. Unless it
+ * overrides the cancellation (denoted by #SVN_ERR_CANCELLED), the driver
+ * aborts the transmission by invoking the svn_editor_abort() callback.
+ * Exceptions to this are calls to svn_editor_complete() and
+ * svn_editor_abort(), which cannot be canceled externally.
+ *
+ * - @b Receive: While the driver invokes operations upon the editor, the
+ * receiver finds its callback functions called with the information
+ * to operate on its tree. Each actual callback function receives those
+ * arguments that the driver passed to the "driving" functions, plus these:
+ * - @a baton: This is the @a editor_baton pointer originally passed to
+ * svn_editor_create(). It may be freely used by the callback
+ * implementation to store information across all callbacks.
+ * - @a scratch_pool: This temporary pool is cleared directly after
+ * each callback returns. See "Pool Usage".
+ * \n\n
+ * If the receiver encounters an error within a callback, it returns an
+ * #svn_error_t*. The driver receives this and aborts transmission.
+ *
+ * - @b Complete/Abort: The driver will end transmission by calling \n
+ * svn_editor_complete() if successful, or \n
+ * svn_editor_abort() if an error or cancellation occurred.
+ * \n\n
+ *
+ * <h3>Driving Order Restrictions</h3>
+ * In order to reduce complexity of callback receivers, the editor callbacks
+ * must be driven in adherence to these rules:
+ *
+ * - If any path is added (with add_*) or deleted/moved/rotated, then
+ * an svn_editor_alter_directory() call must be made for its parent
+ * directory with the target/eventual set of children.
+ *
+ * - svn_editor_add_directory() -- Another svn_editor_add_*() call must
+ * follow for each child mentioned in the @a children argument of any
+ * svn_editor_add_directory() call.
+ *
+ * - For each node created with add_*, if its parent was created using
+ * svn_editor_add_directory(), then the new child node MUST have been
+ * mentioned in the @a children parameter of the parent's creation.
+ * This allows the parent directory to properly mark the child as
+ * "incomplete" until the child's add_* call arrives.
+ *
+ * - A path should never be referenced more than once by the add_*, alter_*,
+ * and delete operations (the "Once Rule"). The source path of a copy (and
+ * its children, if a directory) may be copied many times, and are
+ * otherwise subject to the Once Rule. The destination path of a copy
+ * or move may have alter_* operations applied, but not add_* or delete.
+ * If the destination path of a copy, move, or rotate is a directory,
+ * then its children are subject to the Once Rule. The source path of
+ * a move (and its child paths) may be referenced in add_*, or as the
+ * destination of a copy (where these new or copied nodes are subject
+ * to the Once Rule). Paths listed in a rotation are both sources and
+ * destinations, so they may not be referenced again in an add_* or a
+ * deletion; these paths may have alter_* operations applied.
+ *
+ * - The ancestor of an added, copied-here, moved-here, rotated, or
+ * modified node may not be deleted. The ancestor may not be moved
+ * (instead: perform the move, *then* the edits).
+ *
+ * - svn_editor_delete() must not be used to replace a path -- i.e.
+ * svn_editor_delete() must not be followed by an svn_editor_add_*() on
+ * the same path, nor by an svn_editor_copy() or svn_editor_move() with
+ * the same path as the copy/move target.
+ *
+ * Instead of a prior delete call, the add/copy/move callbacks should be
+ * called with the @a replaces_rev argument set to the revision number of
+ * the node at this path that is being replaced. Note that the path and
+ * revision number are the key to finding any other information about the
+ * replaced node, like node kind, etc.
+ * @todo say which function(s) to use.
+ *
+ * - svn_editor_delete() must not be used to move a path -- i.e.
+ * svn_editor_delete() must not delete the source path of a previous
+ * svn_editor_copy() call. Instead, svn_editor_move() must be used.
+ * Note: if the desired semantics is one (or more) copies, followed
+ * by a delete... that is fine. It is simply that svn_editor_move()
+ * should be used to describe a semantic move.
+ *
+ * - Paths mentioned in svn_editor_rotate() may have their properties
+ * and contents edited (via alter_* calls) by a previous or later call,
+ * but they may not be subject to a later move, rotate, or deletion.
+ *
+ * - One of svn_editor_complete() or svn_editor_abort() must be called
+ * exactly once, which must be the final call the driver invokes.
+ * Invoking svn_editor_complete() must imply that the set of changes has
+ * been transmitted completely and without errors, and invoking
+ * svn_editor_abort() must imply that the transformation was not completed
+ * successfully.
+ *
+ * - If any callback invocation (besides svn_editor_complete()) returns
+ * with an error, the driver must invoke svn_editor_abort() and stop
+ * transmitting operations.
+ * \n\n
+ *
+ * <h3>Receiving Restrictions</h3>
+ *
+ * All callbacks must complete their handling of a path before they return.
+ * Since future callbacks will never reference this path again (due to the
+ * Once Rule), the changes can and should be completed.
+ *
+ * This restriction is not recursive -- a directory's children may remain
+ * incomplete until later callback calls are received.
+ *
+ * For example, an svn_editor_add_directory() call during an 'update'
+ * operation will create the directory itself, including its properties,
+ * and will complete any client notification for the directory itself.
+ * The immediate children of the added directory, given in @a children,
+ * will be recorded in the WC as 'incomplete' and will be completed in the
+ * course of the same operation sequence, when the corresponding callbacks
+ * for these items are invoked.
+ * \n\n
+ *
+ * <h3>Timing and State</h3>
+ * The calls made by the driver to alter the state in the receiver are
+ * based on the receiver's *current* state, which includes all prior changes
+ * made during the edit.
+ *
+ * Example: copy A to B; set-props on A; copy A to C. The props on C
+ * should reflect the updated properties of A.
+ *
+ * Example: mv A@N to B; mv C@M to A. The second move cannot be marked as
+ * a "replacing" move since it is not replacing A. The node at A was moved
+ * away. The second operation is simply moving C to the now-empty path
+ * known as A.
+ *
+ * <h3>Paths</h3>
+ * Each driver/receiver implementation of this editor interface must
+ * establish the expected root for all the paths sent and received via
+ * the callbacks' @a relpath arguments.
+ *
+ * For example, during an "update", the driver is the repository, as a
+ * whole. The receiver may have just a portion of that repository. Here,
+ * the receiver could tell the driver which repository URL the working
+ * copy refers to, and thus the driver could send @a relpath arguments
+ * that are relative to the receiver's working copy.
+ *
+ * @note Because the source of a copy may be located *anywhere* in the
+ * repository, editor drives should typically use the repository root
+ * as the negotiated root. This allows the @a src_relpath argument in
+ * svn_editor_copy() to specify any possible source.
+ * \n\n
+ *
+ * <h3>Pool Usage</h3>
+ * The @a result_pool passed to svn_editor_create() is used to allocate
+ * the #svn_editor_t instance, and thus it must not be cleared before the
+ * driver has finished driving the editor.
+ *
+ * The @a scratch_pool passed to each callback invocation is derived from
+ * the @a result_pool that was passed to svn_editor_create(). It is
+ * cleared directly after each single callback invocation.
+ * To allocate memory with a longer lifetime from within a callback
+ * function, you may use your own pool kept in the @a editor_baton.
+ *
+ * The @a scratch_pool passed to svn_editor_create() may be used to help
+ * during construction of the #svn_editor_t instance, but it is assumed to
+ * live only until svn_editor_create() returns.
+ * \n\n
+ *
+ * <h3>Cancellation</h3>
+ * To allow graceful interruption by external events (like a user abort),
+ * svn_editor_create() can be passed an #svn_cancel_func_t that is
+ * polled every time the driver invokes a callback, just before the
+ * actual editor callback implementation is invoked. If this function
+ * decides to return with an error, the driver will receive this error
+ * as if the callback function had returned it, i.e. as the result from
+ * calling any of the driving functions (e.g. svn_editor_add_directory()).
+ * As with any other error, the driver must then invoke svn_editor_abort()
+ * and abort the transformation sequence. See #svn_cancel_func_t.
+ *
+ * The @a cancel_baton argument to svn_editor_create() is passed
+ * unchanged to each poll of @a cancel_func.
+ *
+ * The cancellation function and baton are typically provided by the client
+ * context.
+ *
+ *
+ * @todo ### TODO anything missing?
+ *
+ * @since New in 1.8.
+ */
+typedef struct svn_editor_t svn_editor_t;
+
+/** The kind of the checksum to be used throughout the #svn_editor_t APIs.
+ *
+ * @note ### This may change before Ev2 is official released, so just like
+ * everything else in this file, please don't rely upon it until then.
+ */
+#define SVN_EDITOR_CHECKSUM_KIND svn_checksum_sha1
+
+
+/** These function types define the callback functions a tree delta consumer
+ * implements.
+ *
+ * Each of these "receiving" function types matches a "driving" function,
+ * which has the same arguments with these differences:
+ *
+ * - These "receiving" functions have a @a baton argument, which is the
+ * @a editor_baton originally passed to svn_editor_create(), as well as
+ * a @a scratch_pool argument.
+ *
+ * - The "driving" functions have an #svn_editor_t* argument, in order to
+ * call the implementations of the function types defined here that are
+ * registered with the given #svn_editor_t instance.
+ *
+ * Note that any remaining arguments for these function types are explained
+ * in the comment for the "driving" functions. Each function type links to
+ * its corresponding "driver".
+ *
+ * @see svn_editor_t, svn_editor_cb_many_t.
+ *
+ * @defgroup svn_editor_callbacks Editor callback definitions
+ * @{
+ */
+
+/** @see svn_editor_add_directory(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_add_directory_t)(
+ void *baton,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_add_file(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_add_file_t)(
+ void *baton,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_add_symlink(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_add_symlink_t)(
+ void *baton,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_add_absent(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_add_absent_t)(
+ void *baton,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_alter_directory(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_alter_directory_t)(
+ void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_alter_file(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_alter_file_t)(
+ void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_alter_symlink(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_alter_symlink_t)(
+ void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_delete(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_delete_t)(
+ void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_copy(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_copy_t)(
+ void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_move(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_move_t)(
+ void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_rotate(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_rotate_t)(
+ void *baton,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_complete(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_complete_t)(
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_editor_abort(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_editor_cb_abort_t)(
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+/** @} */
+
+
+/** These functions create an editor instance so that it can be driven.
+ *
+ * @defgroup svn_editor_create Editor creation
+ * @{
+ */
+
+/** Allocate an #svn_editor_t instance from @a result_pool, store
+ * @a editor_baton, @a cancel_func and @a cancel_baton in the new instance
+ * and return it in @a editor.
+ * @a scratch_pool is used for temporary allocations (if any). Note that
+ * this is NOT the same @a scratch_pool that is passed to callback functions.
+ * @see svn_editor_t
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_create(svn_editor_t **editor,
+ void *editor_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Return an editor's private baton.
+ *
+ * In some cases, the baton is required outside of the callbacks. This
+ * function returns the private baton for use.
+ *
+ * @since New in 1.8.
+ */
+void *
+svn_editor_get_baton(const svn_editor_t *editor);
+
+
+/** Sets the #svn_editor_cb_add_directory_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_add_directory(svn_editor_t *editor,
+ svn_editor_cb_add_directory_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_add_file_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_add_file(svn_editor_t *editor,
+ svn_editor_cb_add_file_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_add_symlink_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_add_symlink(svn_editor_t *editor,
+ svn_editor_cb_add_symlink_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_add_absent_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_add_absent(svn_editor_t *editor,
+ svn_editor_cb_add_absent_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_alter_directory_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_alter_directory(svn_editor_t *editor,
+ svn_editor_cb_alter_directory_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_alter_file_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_alter_file(svn_editor_t *editor,
+ svn_editor_cb_alter_file_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_alter_symlink_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_alter_symlink(svn_editor_t *editor,
+ svn_editor_cb_alter_symlink_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_delete_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_delete(svn_editor_t *editor,
+ svn_editor_cb_delete_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_copy_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_copy(svn_editor_t *editor,
+ svn_editor_cb_copy_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_move_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_move(svn_editor_t *editor,
+ svn_editor_cb_move_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_rotate_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_rotate(svn_editor_t *editor,
+ svn_editor_cb_rotate_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_complete_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_complete(svn_editor_t *editor,
+ svn_editor_cb_complete_t callback,
+ apr_pool_t *scratch_pool);
+
+/** Sets the #svn_editor_cb_abort_t callback in @a editor
+ * to @a callback.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @see also svn_editor_setcb_many().
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_abort(svn_editor_t *editor,
+ svn_editor_cb_abort_t callback,
+ apr_pool_t *scratch_pool);
+
+
+/** Lists a complete set of editor callbacks.
+ * This is a convenience structure.
+ * @see svn_editor_setcb_many(), svn_editor_create(), svn_editor_t.
+ * @since New in 1.8.
+ */
+typedef struct svn_editor_cb_many_t
+{
+ svn_editor_cb_add_directory_t cb_add_directory;
+ svn_editor_cb_add_file_t cb_add_file;
+ svn_editor_cb_add_symlink_t cb_add_symlink;
+ svn_editor_cb_add_absent_t cb_add_absent;
+ svn_editor_cb_alter_directory_t cb_alter_directory;
+ svn_editor_cb_alter_file_t cb_alter_file;
+ svn_editor_cb_alter_symlink_t cb_alter_symlink;
+ svn_editor_cb_delete_t cb_delete;
+ svn_editor_cb_copy_t cb_copy;
+ svn_editor_cb_move_t cb_move;
+ svn_editor_cb_rotate_t cb_rotate;
+ svn_editor_cb_complete_t cb_complete;
+ svn_editor_cb_abort_t cb_abort;
+
+} svn_editor_cb_many_t;
+
+/** Sets all the callback functions in @a editor at once, according to the
+ * callback functions stored in @a many.
+ * @a scratch_pool is used for temporary allocations (if any).
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_setcb_many(svn_editor_t *editor,
+ const svn_editor_cb_many_t *many,
+ apr_pool_t *scratch_pool);
+
+/** @} */
+
+
+/** These functions are called by the tree delta driver to edit the target.
+ *
+ * @see svn_editor_t.
+ *
+ * @defgroup svn_editor_drive Driving the editor
+ * @{
+ */
+
+/** Drive @a editor's #svn_editor_cb_add_directory_t callback.
+ *
+ * Create a new directory at @a relpath. The immediate parent of @a relpath
+ * is expected to exist.
+ *
+ * For descriptions of @a props and @a replaces_rev, see
+ * svn_editor_add_file().
+ *
+ * A complete listing of the immediate children of @a relpath that will be
+ * added subsequently is given in @a children. @a children is an array of
+ * const char*s, each giving the basename of an immediate child. It is an
+ * error to pass NULL for @a children; use an empty array to indicate
+ * the new directory will have no children.
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ */
+svn_error_t *
+svn_editor_add_directory(svn_editor_t *editor,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev);
+
+/** Drive @a editor's #svn_editor_cb_add_file_t callback.
+ *
+ * Create a new file at @a relpath. The immediate parent of @a relpath
+ * is expected to exist.
+ *
+ * The file's contents are specified in @a contents which has a checksum
+ * matching @a checksum. Both values must be non-NULL.
+ *
+ * Set the properties of the new file to @a props, which is an
+ * apr_hash_t holding key-value pairs. Each key is a const char* of a
+ * property name, each value is a const svn_string_t*. If no properties are
+ * being set on the new file, @a props must be the empty hash. It is an
+ * error to pass NULL for @a props.
+ *
+ * If this add is expected to replace a previously existing file, symlink or
+ * directory at @a relpath, the revision number of the node to be replaced
+ * must be given in @a replaces_rev. Otherwise, @a replaces_rev must be
+ * #SVN_INVALID_REVNUM. Note: it is not allowed to call a "delete" followed
+ * by an "add" on the same path. Instead, an "add" with @a replaces_rev set
+ * accordingly MUST be used.
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_add_file(svn_editor_t *editor,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev);
+
+/** Drive @a editor's #svn_editor_cb_add_symlink_t callback.
+ *
+ * Create a new symbolic link at @a relpath, with a link target of @a
+ * target. The immediate parent of @a relpath is expected to exist.
+ *
+ * For descriptions of @a props and @a replaces_rev, see
+ * svn_editor_add_file().
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_add_symlink(svn_editor_t *editor,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev);
+
+/** Drive @a editor's #svn_editor_cb_add_absent_t callback.
+ *
+ * Create an "absent" node of kind @a kind at @a relpath. The immediate
+ * parent of @a relpath is expected to exist.
+ * ### TODO @todo explain "absent".
+ * ### JAF: What are the allowed values of 'kind'?
+ *
+ * For a description of @a replaces_rev, see svn_editor_add_file().
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_add_absent(svn_editor_t *editor,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t replaces_rev);
+
+/** Drive @a editor's #svn_editor_cb_alter_directory_t callback.
+ *
+ * Alter the properties of the directory at @a relpath.
+ *
+ * @a revision specifies the revision at which the receiver should
+ * expect to find this node. That is, @a relpath at the start of the
+ * whole edit and @a relpath at @a revision must lie within the same
+ * node-rev (aka location history segment). This information may be used
+ * to catch an attempt to alter and out-of-date directory. If the
+ * directory does not have a corresponding revision in the repository
+ * (e.g. it has not yet been committed), then @a revision should be
+ * #SVN_INVALID_REVNUM.
+ *
+ * If any changes to the set of children will be made in the future of
+ * the edit drive, then @a children MUST specify the resulting set of
+ * children. See svn_editor_add_directory() for the format of @a children.
+ * If not changes will be made, then NULL may be specified.
+ *
+ * For a description of @a props, see svn_editor_add_file(). If no changes
+ * to the properties will be made (ie. only future changes to the set of
+ * children), then @a props may be NULL.
+ *
+ * It is an error to pass NULL for both @a children and @a props.
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_alter_directory(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ apr_hash_t *props);
+
+/** Drive @a editor's #svn_editor_cb_alter_file_t callback.
+ *
+ * Alter the properties and/or the contents of the file at @a relpath
+ * with @a revision as its expected revision. See svn_editor_alter_directory()
+ * for more information about @a revision.
+ *
+ * If @a props is non-NULL, then the properties will be applied.
+ *
+ * If @a contents is non-NULL, then the stream will be copied to
+ * the file, and its checksum must match @a checksum (which must also
+ * be non-NULL). If @a contents is NULL, then @a checksum must also
+ * be NULL, and no change will be applied to the file's contents.
+ *
+ * The properties and/or the contents must be changed. It is an error to
+ * pass NULL for @a props, @a checksum, and @a contents.
+ *
+ * For a description of @a checksum and @a contents see
+ * svn_editor_add_file(). This function allows @a props to be NULL, but
+ * the parameter is otherwise described by svn_editor_add_file().
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_alter_file(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents);
+
+/** Drive @a editor's #svn_editor_cb_alter_symlink_t callback.
+ *
+ * Alter the properties and/or the target of the symlink at @a relpath
+ * with @a revision as its expected revision. See svn_editor_alter_directory()
+ * for more information about @a revision.
+ *
+ * If @a props is non-NULL, then the properties will be applied.
+ *
+ * If @a target is non-NULL, then the symlink's target will be updated.
+ *
+ * The properties and/or the target must be changed. It is an error to
+ * pass NULL for @a props and @a target.
+ *
+ * This function allows @a props to be NULL, but the parameter is
+ * otherwise described by svn_editor_add_file().
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_alter_symlink(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target);
+
+/** Drive @a editor's #svn_editor_cb_delete_t callback.
+ *
+ * Delete the existing node at @a relpath, expected to be identical to
+ * revision @a revision of that path.
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_delete(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision);
+
+/** Drive @a editor's #svn_editor_cb_copy_t callback.
+ *
+ * Copy the node at @a src_relpath, expected to be identical to revision @a
+ * src_revision of that path, to @a dst_relpath.
+ *
+ * For a description of @a replaces_rev, see svn_editor_add_file().
+ *
+ * @note See the general instructions on paths for this API. Since the
+ * @a src_relpath argument must generally be able to reference any node
+ * in the repository, the implication is that the editor's root must be
+ * the repository root.
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_copy(svn_editor_t *editor,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev);
+
+/** Drive @a editor's #svn_editor_cb_move_t callback.
+ *
+ * Move the node at @a src_relpath to @a dst_relpath.
+ *
+ * @a src_revision specifies the revision at which the receiver should
+ * expect to find this node. That is, @a src_relpath at the start of
+ * the whole edit and @a src_relpath at @a src_revision must lie within
+ * the same node-rev (aka history-segment). This is just like the
+ * revisions specified to svn_editor_delete() and svn_editor_rotate().
+ *
+ * For a description of @a replaces_rev, see svn_editor_add_file().
+ *
+ * ### what happens if one side of this move is not "within" the receiver's
+ * ### set of paths?
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_move(svn_editor_t *editor,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev);
+
+/** Drive @a editor's #svn_editor_cb_rotate_t callback.
+ *
+ * Perform a rotation among multiple nodes in the target tree.
+ *
+ * The @a relpaths and @a revisions arrays (pair-wise) specify nodes in the
+ * tree which are located at a path and expected to be at a specific
+ * revision. These nodes are simultaneously moved in a rotation pattern.
+ * For example, the node at index 0 of @a relpaths and @a revisions will
+ * be moved to the relpath specified at index 1 of @a relpaths. The node
+ * at index 1 will be moved to the location at index 2. The node at index
+ * N-1 will be moved to the relpath specified at index 0.
+ *
+ * The simplest form of this operation is to swap nodes A and B. One may
+ * think to move A to a temporary location T, then move B to A, then move
+ * T to B. However, this last move violations the Once Rule by moving T
+ * (which had already by edited by the move from A). In order to keep the
+ * restrictions against multiple moves of a single node, the rotation
+ * operation is needed for certain types of tree edits.
+ *
+ * ### what happens if one of the paths of the rotation is not "within" the
+ * ### receiver's set of paths?
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_rotate(svn_editor_t *editor,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions);
+
+/** Drive @a editor's #svn_editor_cb_complete_t callback.
+ *
+ * Send word that the edit has been completed successfully.
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_complete(svn_editor_t *editor);
+
+/** Drive @a editor's #svn_editor_cb_abort_t callback.
+ *
+ * Notify that the edit transmission was not successful.
+ * ### TODO @todo Shouldn't we add a reason-for-aborting argument?
+ *
+ * For all restrictions on driving the editor, see #svn_editor_t.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_editor_abort(svn_editor_t *editor);
+
+/** @} */
+
+/** @} */
+
+/** A temporary API which conditionally inserts a double editor shim
+ * into the chain of delta editors. Used for testing Editor v2.
+ *
+ * Whether or not the shims are inserted is controlled by a compile-time
+ * option in libsvn_delta/compat.c.
+ *
+ * @note The use of these shims and this API will likely cause all kinds
+ * of performance degredation. (Which is actually a moot point since they
+ * don't even work properly yet anyway.)
+ */
+svn_error_t *
+svn_editor__insert_shims(const svn_delta_editor_t **deditor_out,
+ void **dedit_baton_out,
+ const svn_delta_editor_t *deditor_in,
+ void *dedit_baton_in,
+ const char *repos_root,
+ const char *base_dir,
+ svn_delta_shim_callbacks_t *shim_callbacks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_EDITOR_H */
diff --git a/contrib/subversion/subversion/include/private/svn_eol_private.h b/contrib/subversion/subversion/include/private/svn_eol_private.h
new file mode 100644
index 0000000..d2cce5c
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_eol_private.h
@@ -0,0 +1,93 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_eol_private.h
+ * @brief Subversion's EOL functions - Internal routines
+ */
+
+#ifndef SVN_EOL_PRIVATE_H
+#define SVN_EOL_PRIVATE_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Constants used by various chunky string processing functions.
+ */
+#if APR_SIZEOF_VOIDP == 8
+# define SVN__LOWER_7BITS_SET 0x7f7f7f7f7f7f7f7f
+# define SVN__BIT_7_SET 0x8080808080808080
+# define SVN__R_MASK 0x0a0a0a0a0a0a0a0a
+# define SVN__N_MASK 0x0d0d0d0d0d0d0d0d
+#else
+# define SVN__LOWER_7BITS_SET 0x7f7f7f7f
+# define SVN__BIT_7_SET 0x80808080
+# define SVN__R_MASK 0x0a0a0a0a
+# define SVN__N_MASK 0x0d0d0d0d
+#endif
+
+/* Generic EOL character helper routines */
+
+/* Look for the start of an end-of-line sequence (i.e. CR or LF)
+ * in the array pointed to by @a buf , of length @a len.
+ * If such a byte is found, return the pointer to it, else return NULL.
+ *
+ * @since New in 1.7
+ */
+char *
+svn_eol__find_eol_start(char *buf, apr_size_t len);
+
+/* Return the first eol marker found in buffer @a buf as a NUL-terminated
+ * string, or NULL if no eol marker is found. Do not examine more than
+ * @a len bytes in @a buf.
+ *
+ * If the last valid character of @a buf is the first byte of a
+ * potentially two-byte eol sequence, just return that single-character
+ * sequence, that is, assume @a buf represents a CR-only or LF-only file.
+ * This is correct for callers that pass an entire file at once, and is
+ * no more likely to be incorrect than correct for any caller that doesn't.
+ *
+ * The returned string is statically allocated, i.e. it is NOT a pointer
+ * to an address within @a buf.
+ *
+ * If an eol marker is found and @a eolp is not NULL, store in @a *eolp
+ * the address within @a buf of the first byte of the eol marker.
+ * This allows callers to tell whether there might be more than one eol
+ * sequence in @a buf, as well as detect two-byte eol sequences that
+ * span buffer boundaries.
+ *
+ * @since New in 1.7
+ */
+const char *
+svn_eol__detect_eol(char *buf, apr_size_t len, char **eolp);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_EOL_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_error_private.h b/contrib/subversion/subversion/include/private/svn_error_private.h
new file mode 100644
index 0000000..f8bd2bc
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_error_private.h
@@ -0,0 +1,54 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_error_private.h
+ * @brief Subversion-internal error APIs.
+ */
+
+#ifndef SVN_ERROR_PRIVATE_H
+#define SVN_ERROR_PRIVATE_H
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Returns if @a err is a "tracing" error.
+ */
+svn_boolean_t
+svn_error__is_tracing_link(svn_error_t *err);
+
+/**
+ * Converts a zlib error to an svn_error_t. zerr is the error code,
+ * function is the function name, message is an optional extra part
+ * of the error message and may be NULL.
+ */
+svn_error_t *
+svn_error__wrap_zlib(int zerr, const char *function, const char *message);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_ERROR_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_fs_private.h b/contrib/subversion/subversion/include/private/svn_fs_private.h
new file mode 100644
index 0000000..20b70cd
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_fs_private.h
@@ -0,0 +1,189 @@
+/*
+ * svn_fs_private.h: Private declarations for the filesystem layer to
+ * be consumed by libsvn_fs* and non-libsvn_fs* modules.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_FS_PRIVATE_H
+#define SVN_FS_PRIVATE_H
+
+#include "svn_fs.h"
+#include "private/svn_editor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* The maximum length of a transaction name. The Berkeley DB backend
+ generates transaction names from a sequence expressed as a base 36
+ number with a maximum of MAX_KEY_SIZE (currently 200) bytes. The
+ FSFS backend generates transaction names of the form
+ <rev>-<base 36-number> where the base 36 number is a sequence value
+ with a maximum length of MAX_KEY_SIZE bytes. The maximum length is
+ 212, but use 220 just to have some extra space:
+ 10 -> 32 bit revision number
+ 1 -> '-'
+ 200 -> 200 digit base 36 number
+ 1 -> '\0'
+ */
+#define SVN_FS__TXN_MAX_LEN 220
+
+/** Retrieve the lock-tokens associated in the context @a access_ctx.
+ * The tokens are in a hash keyed with <tt>const char *</tt> tokens,
+ * and with <tt>const char *</tt> values for the paths associated.
+ *
+ * You should always use svn_fs_access_add_lock_token2() if you intend
+ * to use this function. The result of the function is not guaranteed
+ * if you use it with the deprecated svn_fs_access_add_lock_token()
+ * API.
+ *
+ * @since New in 1.6. */
+apr_hash_t *
+svn_fs__access_get_lock_tokens(svn_fs_access_t *access_ctx);
+
+
+/* Check whether PATH is valid for a filesystem, following (most of) the
+ * requirements in svn_fs.h:"Directory entry names and directory paths".
+ *
+ * Return SVN_ERR_FS_PATH_SYNTAX if PATH is not valid.
+ */
+svn_error_t *
+svn_fs__path_valid(const char *path, apr_pool_t *pool);
+
+
+
+/** Editors
+ *
+ * ### docco
+ *
+ * @defgroup svn_fs_editor Transaction editors
+ * @{
+ */
+
+/**
+ * Create a new filesystem transaction, based on based on the youngest
+ * revision of @a fs, and return its name @a *txn_name and an @a *editor
+ * that can be used to make changes into it.
+ *
+ * @a flags determines transaction enforcement behaviors, and is composed
+ * from the constants SVN_FS_TXN_* (#SVN_FS_TXN_CHECK_OOD etc.). It is a
+ * property of the underlying transaction, and will not change if multiple
+ * editors are used to refer to that transaction (see @a autocommit, below).
+ *
+ * @note If you're building a txn for committing, you probably don't want
+ * to call this directly. Instead, call svn_repos__get_commit_ev2(), which
+ * honors the repository's hook configurations.
+ *
+ * When svn_editor_complete() is called for @a editor, internal resources
+ * will be cleaned and nothing more will happen. If you wish to commit the
+ * transaction, call svn_fs_editor_commit() instead. It is illegal to call
+ * both; the second call will return #SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION.
+ *
+ * @see svn_fs_commit_txn()
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_fs__editor_create(svn_editor_t **editor,
+ const char **txn_name,
+ svn_fs_t *fs,
+ apr_uint32_t flags,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Like svn_fs__editor_create(), but open an existing transaction
+ * @a txn_name and continue editing it.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_fs__editor_create_for(svn_editor_t **editor,
+ svn_fs_t *fs,
+ const char *txn_name,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Commit the transaction represented by @a editor.
+ *
+ * If the commit to the filesystem succeeds, then @a *revision will be set
+ * to the resulting revision number. Note that further errors may occur,
+ * as described below. If the commit process does not succeed, for whatever
+ * reason, then @a *revision will be set to #SVN_INVALID_REVNUM.
+ *
+ * If a conflict occurs during the commit, then @a *conflict_path will
+ * be set to a path that caused the conflict. #SVN_NO_ERROR will be returned.
+ * Callers may want to construct an #SVN_ERR_FS_CONFLICT error with a
+ * message that incorporates @a *conflict_path.
+ *
+ * If a non-conflict error occurs during the commit, then that error will
+ * be returned.
+ * As is standard with any Subversion API, @a revision, @a post_commit_err,
+ * and @a conflict_path (the OUT parameters) have an indeterminate value if
+ * an error is returned.
+ *
+ * If the commit completes (and a revision is returned in @a *revision), then
+ * it is still possible for an error to occur during the cleanup process.
+ * Any such error will be returned in @a *post_commit_err. The caller must
+ * properly use or clear that error.
+ *
+ * If svn_editor_complete() has already been called on @a editor, then
+ * #SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION will be returned.
+ *
+ * @note After calling this function, @a editor will be marked as completed
+ * and no further operations may be performed on it. The underlying
+ * transaction will either be committed or aborted once this function is
+ * called. It cannot be recovered for additional work.
+ *
+ * @a result_pool will be used to allocate space for @a conflict_path.
+ * @a scratch_pool will be used for all temporary allocations.
+ *
+ * @note To summarize, there are three possible outcomes of this function:
+ * successful commit (with or without an associated @a *post_commit_err);
+ * failed commit due to a conflict (reported via @a *conflict_path); and
+ * failed commit for some other reason (reported via the returned error.)
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_fs__editor_commit(svn_revnum_t *revision,
+ svn_error_t **post_commit_err,
+ const char **conflict_path,
+ svn_editor_t *editor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** @} */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_FS_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_fs_util.h b/contrib/subversion/subversion/include/private/svn_fs_util.h
new file mode 100644
index 0000000..eb0f024
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_fs_util.h
@@ -0,0 +1,217 @@
+/*
+ * svn_fs_util.h: Declarations for the APIs of libsvn_fs_util to be
+ * consumed by only fs_* libs.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_FS_UTIL_H
+#define SVN_FS_UTIL_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Returns whether PATH is in canonical form as defined by
+ svn_fs__canonicalize_abspath().
+ */
+svn_boolean_t
+svn_fs__is_canonical_abspath(const char *path);
+
+/* Return a canonicalized version of a filesystem PATH, allocated in POOL.
+
+ While the filesystem API is pretty flexible about the incoming paths
+ (they must be UTF-8 with '/' as separators, but they don't have to
+ begin with '/', and multiple contiguous '/'s are ignored) we want any
+ paths that are physically stored in the underlying database to look
+ consistent. Specifically, absolute filesystem paths should begin with
+ '/', and all redundant and trailing '/' characters be removed.
+
+ This is similar to svn_fspath__canonicalize() but doesn't treat "."
+ segments as special.
+*/
+const char *
+svn_fs__canonicalize_abspath(const char *path, apr_pool_t *pool);
+
+/* If EXPECT_OPEN, verify that FS refers to an open database;
+ otherwise, verify that FS refers to an unopened database. Return
+ an appropriate error if the expectation fails to match the
+ reality. */
+svn_error_t *
+svn_fs__check_fs(svn_fs_t *fs, svn_boolean_t expect_open);
+
+/* An identifier for FS to be used in the text of error messages.
+ (Not used anywhere but in this header.)
+
+ Note: we log the UUID, rather than (fs)->path, since some of these
+ errors are marshalled to the client. */
+#define svn_fs__identifier(fs) ((fs)->uuid)
+
+/* Constructing nice error messages for roots. */
+
+/* Build an SVN_ERR_FS_NOT_FOUND error, with a detailed error text,
+ for PATH in ROOT. ROOT is of type svn_fs_root_t *. */
+#define SVN_FS__NOT_FOUND(root, path) ( \
+ root->is_txn_root ? \
+ svn_error_createf \
+ (SVN_ERR_FS_NOT_FOUND, 0, \
+ _("File not found: transaction '%s', path '%s'"), \
+ root->txn, path) \
+ : \
+ svn_error_createf \
+ (SVN_ERR_FS_NOT_FOUND, 0, \
+ _("File not found: revision %ld, path '%s'"), \
+ root->rev, path) \
+ )
+
+
+/* Build a detailed `file already exists' message for PATH in ROOT.
+ ROOT is of type svn_fs_root_t *. */
+#define SVN_FS__ALREADY_EXISTS(root, path_str) ( \
+ root->is_txn_root ? \
+ svn_error_createf \
+ (SVN_ERR_FS_ALREADY_EXISTS, 0, \
+ _("File already exists: filesystem '%s', transaction '%s', path '%s'"), \
+ svn_fs__identifier(root->fs), root->txn, path_str) \
+ : \
+ svn_error_createf \
+ (SVN_ERR_FS_ALREADY_EXISTS, 0, \
+ _("File already exists: filesystem '%s', revision %ld, path '%s'"), \
+ svn_fs__identifier(root->fs), root->rev, path_str) \
+ )
+
+/* ROOT is of type svn_fs_root_t *. */
+#define SVN_FS__NOT_TXN(root) \
+ svn_error_create \
+ (SVN_ERR_FS_NOT_TXN_ROOT, NULL, \
+ _("Root object must be a transaction root"))
+
+/* SVN_FS__ERR_NOT_MUTABLE: the caller attempted to change a node
+ outside of a transaction. FS is of type "svn_fs_t *". */
+#define SVN_FS__ERR_NOT_MUTABLE(fs, rev, path_in_repo) \
+ svn_error_createf( \
+ SVN_ERR_FS_NOT_MUTABLE, 0, \
+ _("File is not mutable: filesystem '%s', revision %ld, path '%s'"), \
+ svn_fs__identifier(fs), rev, path_in_repo)
+
+/* FS is of type "svn_fs_t *".*/
+#define SVN_FS__ERR_NOT_DIRECTORY(fs, path_in_repo) \
+ svn_error_createf( \
+ SVN_ERR_FS_NOT_DIRECTORY, 0, \
+ _("'%s' is not a directory in filesystem '%s'"), \
+ path_in_repo, svn_fs__identifier(fs))
+
+/* FS is of type "svn_fs_t *". */
+#define SVN_FS__ERR_NOT_FILE(fs, path_in_repo) \
+ svn_error_createf( \
+ SVN_ERR_FS_NOT_FILE, 0, \
+ _("'%s' is not a file in filesystem '%s'"), \
+ path_in_repo, svn_fs__identifier(fs))
+
+
+/* FS is of type "svn_fs_t *", LOCK is of type "svn_lock_t *". */
+#define SVN_FS__ERR_PATH_ALREADY_LOCKED(fs, lock) \
+ svn_error_createf( \
+ SVN_ERR_FS_PATH_ALREADY_LOCKED, 0, \
+ _("Path '%s' is already locked by user '%s' in filesystem '%s'"), \
+ (lock)->path, (lock)->owner, svn_fs__identifier(fs))
+
+/* FS is of type "svn_fs_t *". */
+#define SVN_FS__ERR_NO_SUCH_LOCK(fs, path_in_repo) \
+ svn_error_createf( \
+ SVN_ERR_FS_NO_SUCH_LOCK, 0, \
+ _("No lock on path '%s' in filesystem '%s'"), \
+ path_in_repo, svn_fs__identifier(fs))
+
+/* FS is of type "svn_fs_t *". */
+#define SVN_FS__ERR_LOCK_EXPIRED(fs, token) \
+ svn_error_createf( \
+ SVN_ERR_FS_LOCK_EXPIRED, 0, \
+ _("Lock has expired: lock-token '%s' in filesystem '%s'"), \
+ token, svn_fs__identifier(fs))
+
+/* FS is of type "svn_fs_t *". */
+#define SVN_FS__ERR_NO_USER(fs) \
+ svn_error_createf( \
+ SVN_ERR_FS_NO_USER, 0, \
+ _("No username is currently associated with filesystem '%s'"), \
+ svn_fs__identifier(fs))
+
+/* SVN_FS__ERR_LOCK_OWNER_MISMATCH: trying to use a lock whose
+ LOCK_OWNER doesn't match the USERNAME associated with FS.
+ FS is of type "svn_fs_t *". */
+#define SVN_FS__ERR_LOCK_OWNER_MISMATCH(fs, username, lock_owner) \
+ svn_error_createf( \
+ SVN_ERR_FS_LOCK_OWNER_MISMATCH, 0, \
+ _("User '%s' is trying to use a lock owned by '%s' in " \
+ "filesystem '%s'"), \
+ username, lock_owner, svn_fs__identifier(fs))
+
+/* Return a NULL-terminated copy of the first component of PATH,
+ allocated in POOL. If path is empty, or consists entirely of
+ slashes, return the empty string.
+
+ If the component is followed by one or more slashes, we set *NEXT_P
+ to point after the slashes. If the component ends PATH, we set
+ *NEXT_P to zero. This means:
+ - If *NEXT_P is zero, then the component ends the PATH, and there
+ are no trailing slashes in the path.
+ - If *NEXT_P points at PATH's terminating NULL character, then
+ the component returned was the last, and PATH ends with one or more
+ slash characters.
+ - Otherwise, *NEXT_P points to the beginning of the next component
+ of PATH. You can pass this value to next_entry_name to extract
+ the next component. */
+char *
+svn_fs__next_entry_name(const char **next_p,
+ const char *path,
+ apr_pool_t *pool);
+
+/* Allocate an svn_fs_path_change2_t structure in POOL, initialize and
+ return it.
+
+ Set the node_rev_id field of the created struct to NODE_REV_ID, and
+ change_kind to CHANGE_KIND. Set all other fields to their _unknown,
+ NULL or invalid value, respectively. */
+svn_fs_path_change2_t *
+svn_fs__path_change_create_internal(const svn_fs_id_t *node_rev_id,
+ svn_fs_path_change_kind_t change_kind,
+ apr_pool_t *pool);
+
+/* Append REL_PATH (which may contain slashes) to each path that exists in
+ the mergeinfo INPUT, and return a new mergeinfo in *OUTPUT. Deep
+ copies the values. Perform all allocations in POOL. */
+svn_error_t *
+svn_fs__append_to_merged_froms(svn_mergeinfo_t *output,
+ svn_mergeinfo_t input,
+ const char *rel_path,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_FS_UTIL_H */
diff --git a/contrib/subversion/subversion/include/private/svn_fspath.h b/contrib/subversion/subversion/include/private/svn_fspath.h
new file mode 100644
index 0000000..01679b9
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_fspath.h
@@ -0,0 +1,175 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_fspath.h
+ * @brief Implementation of path manipulation functions similar to
+ * those in svn_dirent_uri.h (which see for details) but for
+ * the private fspath class of paths.
+ */
+
+#ifndef SVN_FSPATH_H
+#define SVN_FSPATH_H
+
+#include <apr.h>
+#include <apr_pools.h>
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** Return TRUE iff @a fspath is canonical.
+ * @a fspath need not be canonical, of course.
+ *
+ * @since New in 1.7.
+ */
+svn_boolean_t
+svn_fspath__is_canonical(const char *fspath);
+
+
+/** This function is similar to svn_relpath_canonicalize(), except
+ * that it returns an fspath (which is essentially just a relpath
+ * tacked onto a leading forward slash).
+ *
+ * The returned fspath may be statically allocated or allocated from
+ * @a pool.
+ *
+ * This is similar to svn_fs__canonicalize_abspath() but also treats "."
+ * segments as special.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_fspath__canonicalize(const char *fspath,
+ apr_pool_t *pool);
+
+/** Return the dirname of @a fspath, defined as the path with its basename
+ * removed. If @a fspath is "/", return "/".
+ *
+ * Allocate the result in @a pool.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_fspath__dirname(const char *fspath,
+ apr_pool_t *pool);
+
+/** Return the last component of @a fspath. The returned value will have no
+ * slashes in it. If @a fspath is "/", return "".
+ *
+ * If @a pool is NULL, return a pointer to within @a fspath, else allocate
+ * the result in @a pool.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_fspath__basename(const char *fspath,
+ apr_pool_t *pool);
+
+/** Divide the canonical @a fspath into @a *dirpath and @a
+ * *base_name, allocated in @a pool.
+ *
+ * If @a dirpath or @a base_name is NULL, then don't set that one.
+ *
+ * Either @a dirpath or @a base_name may be @a fspath's own address, but they
+ * may not both be the same address, or the results are undefined.
+ *
+ * If @a fspath has two or more components, the separator between @a dirpath
+ * and @a base_name is not included in either of the new names.
+ *
+ * @since New in 1.7.
+ */
+void
+svn_fspath__split(const char **dirpath,
+ const char **base_name,
+ const char *fspath,
+ apr_pool_t *result_pool);
+
+/** Return the fspath composed of @a fspath with @a relpath appended.
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+char *
+svn_fspath__join(const char *fspath,
+ const char *relpath,
+ apr_pool_t *result_pool);
+
+
+/** Return TRUE if @a fspath (with length @a len) is the root
+ * directory; return FALSE otherwise.
+ *
+ * @since New in 1.7.
+ */
+svn_boolean_t
+svn_fspath__is_root(const char *fspath,
+ apr_size_t len);
+
+/** Return the relative path part of @a child_fspath that is below
+ * @a parent_fspath, or just "" if @a parent_fspath is equal to
+ * @a child_fspath. If @a child_fspath is not below @a parent_fspath
+ * or equal to it, return @c NULL.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_fspath__skip_ancestor(const char *parent_fspath,
+ const char *child_fspath);
+
+/** Return the longest common path shared by two fspaths, @a fspath1 and
+ * @a fspath2. If there's no common ancestor, return "/".
+ *
+ * @since New in 1.7.
+ */
+char *
+svn_fspath__get_longest_ancestor(const char *fspath1,
+ const char *fspath2,
+ apr_pool_t *result_pool);
+
+
+
+
+/** A faux fspath API used by the DAV modules to help us distinguish
+ * between real URI-decoded fspaths and URI-encoded URL path-portions.
+ */
+#define svn_urlpath__basename svn_fspath__basename
+#define svn_urlpath__dirname svn_fspath__dirname
+#define svn_urlpath__get_longest_ancestor svn_fspath__get_longest_ancestor
+#define svn_urlpath__is_canonical svn_fspath__is_canonical
+#define svn_urlpath__is_root svn_fspath__is_root
+#define svn_urlpath__join svn_fspath__join
+#define svn_urlpath__skip_ancestor svn_fspath__skip_ancestor
+#define svn_urlpath__split svn_fspath__split
+
+/* Like svn_fspath__canonicalize(), but this one accepts both full
+ URLs and URL path-portions. */
+const char *
+svn_urlpath__canonicalize(const char *uri, apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_FSPATH_H */
diff --git a/contrib/subversion/subversion/include/private/svn_io_private.h b/contrib/subversion/subversion/include/private/svn_io_private.h
new file mode 100644
index 0000000..2fb4fa5
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_io_private.h
@@ -0,0 +1,99 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_io_private.h
+ * @brief Private IO API
+ */
+
+#ifndef SVN_IO_PRIVATE_H
+#define SVN_IO_PRIVATE_H
+
+#include <apr.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* The flags to pass to apr_stat to check for executable and/or readonly */
+#if defined(WIN32) || defined(__OS2__)
+#define SVN__APR_FINFO_EXECUTABLE (0)
+#define SVN__APR_FINFO_READONLY (0)
+#define SVN__APR_FINFO_MASK_OUT (APR_FINFO_PROT | APR_FINFO_OWNER)
+#else
+#define SVN__APR_FINFO_EXECUTABLE (APR_FINFO_PROT)
+#define SVN__APR_FINFO_READONLY (APR_FINFO_PROT | APR_FINFO_OWNER)
+#define SVN__APR_FINFO_MASK_OUT (0)
+#endif
+
+
+/** Set @a *executable TRUE if @a file_info is executable for the
+ * user, FALSE otherwise.
+ *
+ * Always returns FALSE on Windows or platforms without user support.
+ */
+svn_error_t *
+svn_io__is_finfo_executable(svn_boolean_t *executable,
+ apr_finfo_t *file_info,
+ apr_pool_t *pool);
+
+/** Set @a *read_only TRUE if @a file_info is read-only for the user,
+ * FALSE otherwise.
+ */
+svn_error_t *
+svn_io__is_finfo_read_only(svn_boolean_t *read_only,
+ apr_finfo_t *file_info,
+ apr_pool_t *pool);
+
+
+/** Buffer test handler function for a generic stream. @see svn_stream_t
+ * and svn_stream__is_buffered().
+ *
+ * @since New in 1.7.
+ */
+typedef svn_boolean_t (*svn_stream__is_buffered_fn_t)(void *baton);
+
+/** Set @a stream's buffer test function to @a is_buffered_fn
+ *
+ * @since New in 1.7.
+ */
+void
+svn_stream__set_is_buffered(svn_stream_t *stream,
+ svn_stream__is_buffered_fn_t is_buffered_fn);
+
+/** Return whether this generic @a stream uses internal buffering.
+ * This may be used to work around subtle differences between buffered
+ * an non-buffered APR files. A lazy-open stream cannot report the
+ * true buffering state until after the lazy open: a stream that
+ * initially reports as non-buffered may report as buffered later.
+ *
+ * @since New in 1.7.
+ */
+svn_boolean_t
+svn_stream__is_buffered(svn_stream_t *stream);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* SVN_IO_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_log.h b/contrib/subversion/subversion/include/private/svn_log.h
new file mode 100644
index 0000000..bdcf32f
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_log.h
@@ -0,0 +1,260 @@
+/*
+ * svn_log.h: Functions for assembling entries for server-side logs.
+ * See also tools/server-side/svn_server_log_parse.py .
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LOG_H
+#define SVN_LOG_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_types.h"
+#include "svn_mergeinfo.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Return a log string for a reparent action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__reparent(const char *path, apr_pool_t *pool);
+
+/**
+ * Return a log string for a change-rev-prop action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__change_rev_prop(svn_revnum_t rev, const char *name, apr_pool_t *pool);
+
+/**
+ * Return a log string for a rev-proplist action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__rev_proplist(svn_revnum_t rev, apr_pool_t *pool);
+
+/**
+ * Return a log string for a rev-prop action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__rev_prop(svn_revnum_t rev, const char *name, apr_pool_t *pool);
+
+/**
+ * Return a log string for a commit action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__commit(svn_revnum_t rev, apr_pool_t *pool);
+
+/**
+ * Return a log string for a get-file action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__get_file(const char *path, svn_revnum_t rev,
+ svn_boolean_t want_contents, svn_boolean_t want_props,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for a get-dir action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__get_dir(const char *path, svn_revnum_t rev,
+ svn_boolean_t want_contents, svn_boolean_t want_props,
+ apr_uint64_t dirent_fields,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for a get-mergeinfo action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__get_mergeinfo(const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for a checkout action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__checkout(const char *path, svn_revnum_t rev, svn_depth_t depth,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for an update action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__update(const char *path, svn_revnum_t rev, svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for a switch action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__switch(const char *path, const char *dst_path, svn_revnum_t revnum,
+ svn_depth_t depth, apr_pool_t *pool);
+
+/**
+ * Return a log string for a status action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__status(const char *path, svn_revnum_t rev, svn_depth_t depth,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for a diff action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__diff(const char *path, svn_revnum_t from_revnum,
+ const char *dst_path, svn_revnum_t revnum,
+ svn_depth_t depth, svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for a log action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__log(const apr_array_header_t *paths,
+ svn_revnum_t start, svn_revnum_t end,
+ int limit, svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops, apr_pool_t *pool);
+
+/**
+ * Return a log string for a get-locations action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__get_locations(const char *path, svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for a get-location-segments action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__get_location_segments(const char *path, svn_revnum_t peg_revision,
+ svn_revnum_t start, svn_revnum_t end,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for a get-file-revs action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__get_file_revs(const char *path, svn_revnum_t start, svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for a lock action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__lock(const apr_array_header_t *paths, svn_boolean_t steal,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for an unlock action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__unlock(const apr_array_header_t *paths, svn_boolean_t break_lock,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for a lock action on only one path; this is
+ * just a convenience wrapper around svn_log__lock().
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__lock_one_path(const char *path, svn_boolean_t steal,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for an unlock action on only one path; this is
+ * just a convenience wrapper around svn_log__unlock().
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__unlock_one_path(const char *path, svn_boolean_t break_lock,
+ apr_pool_t *pool);
+
+/**
+ * Return a log string for a replay action.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_log__replay(const char *path, svn_revnum_t rev, apr_pool_t *pool);
+
+/**
+ * Return a log string for a get-inherited-props action.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_log__get_inherited_props(const char *path,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LOG_H */
diff --git a/contrib/subversion/subversion/include/private/svn_magic.h b/contrib/subversion/subversion/include/private/svn_magic.h
new file mode 100644
index 0000000..b057e56
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_magic.h
@@ -0,0 +1,55 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_magic.h
+ * @brief Subversion interface to libmagic.
+ */
+
+#ifndef SVN_MAGIC_H
+#define SVN_MAGIC_H
+
+/* An opaque struct that wraps a libmagic cookie. */
+typedef struct svn_magic__cookie_t svn_magic__cookie_t;
+
+/* This routine initialises libmagic.
+ * Upon success a new *MAGIC_COOKIE is allocated in RESULT_POOL.
+ * On failure *MAGIC_COOKIE is set to NULL.
+ * All resources used by libmagic are freed by a cleanup handler
+ * installed on RESULT_POOL, i.e. *MAGIC_COOKIE becomes invalid when
+ * the pool is cleared! */
+void
+svn_magic__init(svn_magic__cookie_t **magic_cookie,
+ apr_pool_t *result_pool);
+
+/* Detect the mime-type of the file at LOCAL_ABSPATH using MAGIC_COOKIE.
+ * If the mime-type is binary return the result in *MIMETYPE.
+ * If the file is not a binary file or if its mime-type cannot be determined
+ * set *MIMETYPE to NULL. Allocate *MIMETYPE in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_magic__detect_binary_mimetype(const char **mimetype,
+ const char *local_abspath,
+ svn_magic__cookie_t *magic_cookie,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+#endif /* SVN_MAGIC_H */
diff --git a/contrib/subversion/subversion/include/private/svn_mergeinfo_private.h b/contrib/subversion/subversion/include/private/svn_mergeinfo_private.h
new file mode 100644
index 0000000..287515a
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_mergeinfo_private.h
@@ -0,0 +1,270 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_mergeinfo_private.h
+ * @brief Subversion-internal mergeinfo APIs.
+ */
+
+#ifndef SVN_MERGEINFO_PRIVATE_H
+#define SVN_MERGEINFO_PRIVATE_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_mergeinfo.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Set inheritability of all ranges in RANGELIST to INHERITABLE.
+ If RANGELIST is NULL do nothing. */
+void
+svn_rangelist__set_inheritance(svn_rangelist_t *rangelist,
+ svn_boolean_t inheritable);
+
+/* Parse a rangelist from the string STR. Set *RANGELIST to the result,
+ * allocated in RESULT_POOL. Return an error if the rangelist is not
+ * well-formed (for example, if it contains invalid characters or if
+ * R1 >= R2 in a "R1-R2" range element).
+ *
+ * Unlike svn_mergeinfo_parse(), this does not sort the ranges into order
+ * or combine adjacent and overlapping ranges.
+ *
+ * The compaction can be done with svn_rangelist__combine_adjacent_ranges().
+ */
+svn_error_t *
+svn_rangelist__parse(svn_rangelist_t **rangelist,
+ const char *str,
+ apr_pool_t *result_pool);
+
+/* In-place combines adjacent ranges in a rangelist.
+ SCRATCH_POOL is just used for providing error messages. */
+svn_error_t *
+svn_rangelist__combine_adjacent_ranges(svn_rangelist_t *rangelist,
+ apr_pool_t *scratch_pool);
+
+/* Set inheritability of all rangelists in MERGEINFO to INHERITABLE.
+ If MERGEINFO is NULL do nothing. If a rangelist in MERGEINFO is
+ NULL leave it alone. */
+void
+svn_mergeinfo__set_inheritance(svn_mergeinfo_t mergeinfo,
+ svn_boolean_t inheritable,
+ apr_pool_t *scratch_pool);
+
+/* Return whether INFO1 and INFO2 are equal in *IS_EQUAL.
+
+ CONSIDER_INHERITANCE determines how the rangelists in the two
+ hashes are compared for equality. If CONSIDER_INHERITANCE is FALSE,
+ then the start and end revisions of the svn_merge_range_t's being
+ compared are the only factors considered when determining equality.
+
+ e.g. '/trunk: 1,3-4*,5' == '/trunk: 1,3-5'
+
+ If CONSIDER_INHERITANCE is TRUE, then the inheritability of the
+ svn_merge_range_t's is also considered and must be the same for two
+ otherwise identical ranges to be judged equal.
+
+ e.g. '/trunk: 1,3-4*,5' != '/trunk: 1,3-5'
+ '/trunk: 1,3-4*,5' == '/trunk: 1,3-4*,5'
+ '/trunk: 1,3-4,5' == '/trunk: 1,3-4,5'
+
+ Use POOL for temporary allocations. */
+svn_error_t *
+svn_mergeinfo__equals(svn_boolean_t *is_equal,
+ svn_mergeinfo_t info1,
+ svn_mergeinfo_t info2,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *pool);
+
+/* Examine MERGEINFO, removing all paths from the hash which map to
+ empty rangelists. POOL is used only to allocate the apr_hash_index_t
+ iterator. Returns TRUE if any paths were removed and FALSE if none were
+ removed or MERGEINFO is NULL. */
+svn_boolean_t
+svn_mergeinfo__remove_empty_rangelists(svn_mergeinfo_t mergeinfo,
+ apr_pool_t *pool);
+
+/* Make a shallow (ie, mergeinfos are not duped, or altered at all;
+ keys share storage) copy of IN_CATALOG in *OUT_CATALOG, removing
+ PREFIX_PATH from the beginning of each key in the catalog.
+ PREFIX_PATH and the keys of IN_CATALOG are absolute 'fspaths',
+ starting with '/'. It is illegal for any key to not start with
+ PREFIX_PATH. The keys of *OUT_CATALOG are relpaths. The new hash
+ and temporary values are allocated in POOL. (This is useful for
+ making the return value from svn_ra_get_mergeinfo relative to the
+ session root, say.) */
+svn_error_t *
+svn_mergeinfo__remove_prefix_from_catalog(svn_mergeinfo_catalog_t *out_catalog,
+ svn_mergeinfo_catalog_t in_catalog,
+ const char *prefix_path,
+ apr_pool_t *pool);
+
+/* Make a shallow (ie, mergeinfos are not duped, or altered at all;
+ though keys are reallocated) copy of IN_CATALOG in *OUT_CATALOG,
+ adding PREFIX_PATH to the beginning of each key in the catalog.
+
+ The new hash keys are allocated in RESULT_POOL. SCRATCH_POOL
+ is used for any temporary allocations.*/
+svn_error_t *
+svn_mergeinfo__add_prefix_to_catalog(svn_mergeinfo_catalog_t *out_catalog,
+ svn_mergeinfo_catalog_t in_catalog,
+ const char *prefix_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *OUT_MERGEINFO to a shallow copy of MERGEINFO with the relpath
+ SUFFIX_RELPATH added to the end of each key path.
+
+ Allocate *OUT_MERGEINFO and the new keys in RESULT_POOL. Use
+ SCRATCH_POOL for any temporary allocations. */
+svn_error_t *
+svn_mergeinfo__add_suffix_to_mergeinfo(svn_mergeinfo_t *out_mergeinfo,
+ svn_mergeinfo_t mergeinfo,
+ const char *suffix_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Create a string representation of CATALOG in *OUTPUT, allocated in POOL.
+ The hash keys of CATALOG and the merge source paths of each key's mergeinfo
+ are represented in sorted order as per svn_sort_compare_items_as_paths.
+ If CATALOG is empty or NULL then *OUTPUT->DATA is set to "\n". If SVN_DEBUG
+ is true, then a NULL or empty CATALOG causes *OUTPUT to be set to an
+ appropriate newline terminated string. If KEY_PREFIX is not NULL then
+ prepend KEY_PREFIX to each key (path) in *OUTPUT. if VAL_PREFIX is not
+ NULL then prepend VAL_PREFIX to each merge source:rangelist line in
+ *OUTPUT.
+
+ Any relative merge source paths in the mergeinfo in CATALOG are converted
+ to absolute paths in *OUTPUT. */
+svn_error_t *
+svn_mergeinfo__catalog_to_formatted_string(svn_string_t **output,
+ svn_mergeinfo_catalog_t catalog,
+ const char *key_prefix,
+ const char *val_prefix,
+ apr_pool_t *pool);
+
+/* Set *YOUNGEST_REV and *OLDEST_REV to the youngest and oldest revisions
+ found in the rangelists within MERGEINFO. Note that *OLDEST_REV is
+ exclusive and *YOUNGEST_REV is inclusive. If MERGEINFO is NULL or empty
+ set *YOUNGEST_REV and *OLDEST_REV to SVN_INVALID_REVNUM. */
+svn_error_t *
+svn_mergeinfo__get_range_endpoints(svn_revnum_t *youngest_rev,
+ svn_revnum_t *oldest_rev,
+ svn_mergeinfo_t mergeinfo,
+ apr_pool_t *pool);
+
+/* Set *FILTERED_MERGEINFO to a deep copy of MERGEINFO, allocated in
+ RESULT_POOL, less any revision ranges that fall outside of the range
+ OLDEST_REV:YOUNGEST_REV (exclusive:inclusive) if INCLUDE_RANGE is true,
+ or less any ranges within OLDEST_REV:YOUNGEST_REV if INCLUDE_RANGE
+ is false. If all the rangelists mapped to a given path are filtered
+ then filter that path as well. If all paths are filtered or MERGEINFO is
+ empty or NULL then *FILTERED_MERGEINFO is set to an empty hash.
+
+ Use SCRATCH_POOL for any temporary allocations. */
+svn_error_t *
+svn_mergeinfo__filter_mergeinfo_by_ranges(svn_mergeinfo_t *filtered_mergeinfo,
+ svn_mergeinfo_t mergeinfo,
+ svn_revnum_t youngest_rev,
+ svn_revnum_t oldest_rev,
+ svn_boolean_t include_range,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Filter each mergeinfo in CATALOG as per
+ svn_mergeinfo__filter_mergeinfo_by_ranges() and put a deep copy of the
+ result in *FILTERED_CATALOG, allocated in RESULT_POOL. If any mergeinfo
+ is filtered to an empty hash then filter that path/mergeinfo as well.
+ If all mergeinfo is filtered or CATALOG is NULL then set *FILTERED_CATALOG
+ to an empty hash.
+
+ Use SCRATCH_POOL for any temporary allocations. */
+svn_error_t*
+svn_mergeinfo__filter_catalog_by_ranges(
+ svn_mergeinfo_catalog_t *filtered_catalog,
+ svn_mergeinfo_catalog_t catalog,
+ svn_revnum_t youngest_rev,
+ svn_revnum_t oldest_rev,
+ svn_boolean_t include_range,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* If MERGEINFO is non-inheritable return TRUE, return FALSE otherwise.
+ MERGEINFO may be NULL or empty. */
+svn_boolean_t
+svn_mergeinfo__is_noninheritable(svn_mergeinfo_t mergeinfo,
+ apr_pool_t *scratch_pool);
+
+/* Return a rangelist with one svn_merge_range_t * element defined by START,
+ END, and INHERITABLE. The rangelist and its contents are allocated in
+ RESULT_POOL. */
+svn_rangelist_t *
+svn_rangelist__initialize(svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t inheritable,
+ apr_pool_t *result_pool);
+
+/* Adjust in-place MERGEINFO's rangelists by OFFSET. If OFFSET is negative
+ and would adjust any part of MERGEINFO's source revisions to 0 or less,
+ then those revisions are dropped. If all the source revisions for a merge
+ source path are dropped, then the path itself is dropped. If all merge
+ source paths are dropped, then *ADJUSTED_MERGEINFO is set to an empty
+ hash. *ADJUSTED_MERGEINFO is allocated in RESULT_POOL. SCRATCH_POOL is
+ used for any temporary allocations. */
+svn_error_t *
+svn_mergeinfo__adjust_mergeinfo_rangelists(svn_mergeinfo_t *adjusted_mergeinfo,
+ svn_mergeinfo_t mergeinfo,
+ svn_revnum_t offset,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Translates an array SEGMENTS (of svn_location_segment_t *), like the one
+ returned from svn_client__repos_location_segments, into a mergeinfo
+ *MERGEINFO_P, allocated in POOL.
+
+ Note: A svn_location_segment_t segment may legitimately describe only revision 0,
+ but there is no way to describe that using svn_mergeinfo_t. Any such
+ segment in SEGMENTS are ignored. */
+svn_error_t *
+svn_mergeinfo__mergeinfo_from_segments(svn_mergeinfo_t *mergeinfo_p,
+ const apr_array_header_t *segments,
+ apr_pool_t *pool);
+
+/* Merge every rangelist in MERGEINFO into the given MERGED_RANGELIST,
+ * ignoring the source paths of MERGEINFO. MERGED_RANGELIST may
+ * initially be empty. New elements added to RANGELIST are allocated in
+ * RESULT_POOL. See svn_rangelist_merge2() for details of inheritability
+ * etc. */
+svn_error_t *
+svn_rangelist__merge_many(svn_rangelist_t *merged_rangelist,
+ svn_mergeinfo_t mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_MERGEINFO_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_mutex.h b/contrib/subversion/subversion/include/private/svn_mutex.h
new file mode 100644
index 0000000..85583d3
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_mutex.h
@@ -0,0 +1,117 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_mutex.h
+ * @brief Strutures and functions for mutual exclusion
+ */
+
+#ifndef SVN_MUTEX_H
+#define SVN_MUTEX_H
+
+#include <apr_thread_mutex.h>
+
+#include "svn_error.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * This is a simple wrapper around @c apr_thread_mutex_t and will be a
+ * valid identifier even if APR does not support threading.
+ */
+#if APR_HAS_THREADS
+
+/** A mutex for synchronization between threads. It may be NULL, in
+ * which case no synchronization will take place. The latter is useful
+ * when implementing some functionality with optional synchronization.
+ */
+typedef apr_thread_mutex_t svn_mutex__t;
+
+#else
+
+/** Dummy definition. The content will never be actually accessed.
+ */
+typedef void svn_mutex__t;
+
+#endif
+
+/** Initialize the @a *mutex. If @a mutex_required is TRUE, the mutex will
+ * actually be created with a lifetime defined by @a result_pool. Otherwise,
+ * the pointer will be set to @c NULL and svn_mutex__lock() as well as
+ * svn_mutex__unlock() will be no-ops.
+ *
+ * If threading is not supported by APR, this function is a no-op.
+ */
+svn_error_t *
+svn_mutex__init(svn_mutex__t **mutex,
+ svn_boolean_t mutex_required,
+ apr_pool_t *result_pool);
+
+/** Acquire the @a mutex, if that has been enabled in svn_mutex__init().
+ * Make sure to call svn_mutex__unlock() some time later in the same
+ * thread to release the mutex again. Recursive locking are not supported.
+ *
+ * @note You should use #SVN_MUTEX__WITH_LOCK instead of explicit lock
+ * aquisition and release.
+ */
+svn_error_t *
+svn_mutex__lock(svn_mutex__t *mutex);
+
+/** Release the @a mutex, previously acquired using svn_mutex__lock()
+ * that has been enabled in svn_mutex__init().
+ *
+ * Since this is often used as part of the calling function's exit
+ * sequence, we accept that function's current return code in @a err.
+ * If it is not #SVN_NO_ERROR, it will be used as the return value -
+ * irrespective of the possible internal failures during unlock. If @a err
+ * is #SVN_NO_ERROR, internal failures of this function will be
+ * reported in the return value.
+ *
+ * @note You should use #SVN_MUTEX__WITH_LOCK instead of explicit lock
+ * aquisition and release.
+ */
+svn_error_t *
+svn_mutex__unlock(svn_mutex__t *mutex,
+ svn_error_t *err);
+
+/** Aquires the @a mutex, executes the expression @a expr and finally
+ * releases the @a mutex. If any of these steps fail, the function using
+ * this macro will return an #svn_error_t. This macro guarantees that
+ * the @a mutex will always be unlocked again if it got locked successfully
+ * by the first step.
+ *
+ * @note Prefer using this macro instead of explicit lock aquisition and
+ * release.
+ */
+#define SVN_MUTEX__WITH_LOCK(mutex, expr) \
+do { \
+ svn_mutex__t *svn_mutex__m = (mutex); \
+ SVN_ERR(svn_mutex__lock(svn_mutex__m)); \
+ SVN_ERR(svn_mutex__unlock(svn_mutex__m, (expr))); \
+} while (0)
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_MUTEX_H */
diff --git a/contrib/subversion/subversion/include/private/svn_named_atomic.h b/contrib/subversion/subversion/include/private/svn_named_atomic.h
new file mode 100644
index 0000000..4efa255
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_named_atomic.h
@@ -0,0 +1,162 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_named_atomics.h
+ * @brief Structures and functions for machine-wide named atomics.
+ * These atomics store 64 bit signed integer values and provide
+ * a number of basic operations on them. Instead of an address,
+ * these atomics are identified by strings / names. We also support
+ * namespaces - mainly to separate debug from production data.
+ */
+
+#ifndef SVN_NAMED_ATOMICS_H
+#define SVN_NAMED_ATOMICS_H
+
+#include "svn_error.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** An opaque structure that represents a namespace, i.e. a container
+ * for named atomics.
+ */
+typedef struct svn_atomic_namespace__t svn_atomic_namespace__t;
+
+/** An opaque structure that represents a named, system-wide visible
+ * 64 bit integer with atomic access routines.
+ */
+typedef struct svn_named_atomic__t svn_named_atomic__t;
+
+/** Maximum length of the name of any atomic (excluding the terminal NUL).
+ */
+#define SVN_NAMED_ATOMIC__MAX_NAME_LENGTH 30
+
+/** Returns #FALSE when named atomics are not available to our process
+ * and svn_atomic_namespace__create is likely to fail.
+ *
+ * @note The actual check will be performed only once and later
+ * changes in process privileges will not reflect in the outcome of future
+ * calls to this function.
+ */
+svn_boolean_t
+svn_named_atomic__is_supported(void);
+
+/** Returns #TRUE on platforms that don't need expensive synchronization
+ * objects to serialize access to named atomics. If this returns #FALSE,
+ * reading from or modifying a #svn_named_atomic__t may be as expensive
+ * as a file system operation.
+ */
+svn_boolean_t
+svn_named_atomic__is_efficient(void);
+
+/** Create a namespace (i.e. access object) with the given @a name and
+ * return it in @a *ns.
+ *
+ * Multiple access objects with the same name may be created. They access
+ * the same shared memory region but have independent lifetimes.
+ *
+ * The access object will be allocated in @a result_pool and atomics gotten
+ * from this object will become invalid when the pool is being cleared.
+ */
+svn_error_t *
+svn_atomic_namespace__create(svn_atomic_namespace__t **ns,
+ const char *name,
+ apr_pool_t *result_pool);
+
+/** Removes persistent data structures (files in particular) that got
+ * created for the namespace given by @a name. Use @a pool for temporary
+ * allocations.
+ *
+ * @note You must not call this while the respective namespace is still
+ * in use. Calling this multiple times for the same namespace is safe.
+ */
+svn_error_t *
+svn_atomic_namespace__cleanup(const char *name,
+ apr_pool_t *pool);
+
+/** Find the atomic with the specified @a name in namespace @a ns and
+ * return it in @a *atomic. If no object with that name can be found, the
+ * behavior depends on @a auto_create. If it is @c FALSE, @a *atomic will
+ * be set to @c NULL. Otherwise, a new atomic will be created, its value
+ * set to 0 and the access structure be returned in @a *atomic.
+ *
+ * Note that @a name must not exceed #SVN_NAMED_ATOMIC__MAX_NAME_LENGTH
+ * characters and an error will be returned if the specified name is longer
+ * than supported.
+ *
+ * @note The lifetime of the atomic object is bound to the lifetime
+ * of the @a ns object, i.e. the pool the latter was created in.
+ * The data in the namespace persists as long as at least one process
+ * holds an #svn_atomic_namespace__t object corresponding to it.
+ */
+svn_error_t *
+svn_named_atomic__get(svn_named_atomic__t **atomic,
+ svn_atomic_namespace__t *ns,
+ const char *name,
+ svn_boolean_t auto_create);
+
+/** Read the @a atomic and return its current @a *value.
+ * An error will be returned if @a atomic is @c NULL.
+ */
+svn_error_t *
+svn_named_atomic__read(apr_int64_t *value,
+ svn_named_atomic__t *atomic);
+
+/** Set the data in @a atomic to @a new_value and return its old content
+ * in @a *old_value. @a old_value may be NULL.
+ *
+ * An error will be returned if @a atomic is @c NULL.
+ */
+svn_error_t *
+svn_named_atomic__write(apr_int64_t *old_value,
+ apr_int64_t new_value,
+ svn_named_atomic__t *atomic);
+
+/** Add @a delta to the data in @a atomic and return its new value in
+ * @a *new_value. @a new_value may be null.
+ *
+ * An error will be returned if @a atomic is @c NULL.
+ */
+svn_error_t *
+svn_named_atomic__add(apr_int64_t *new_value,
+ apr_int64_t delta,
+ svn_named_atomic__t *atomic);
+
+/** If the current data in @a atomic equals @a comperand, set it to
+ * @a new_value. Return the initial value in @a *old_value.
+ * @a old_value may be NULL.
+ *
+ * An error will be returned if @a atomic is @c NULL.
+ */
+svn_error_t *
+svn_named_atomic__cmpxchg(apr_int64_t *old_value,
+ apr_int64_t new_value,
+ apr_int64_t comperand,
+ svn_named_atomic__t *atomic);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_NAMED_ATOMICS_H */
diff --git a/contrib/subversion/subversion/include/private/svn_opt_private.h b/contrib/subversion/subversion/include/private/svn_opt_private.h
new file mode 100644
index 0000000..6ae67a5
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_opt_private.h
@@ -0,0 +1,156 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_opt_private.h
+ * @brief Subversion-internal option parsing APIs.
+ */
+
+#ifndef SVN_OPT_PRIVATE_H
+#define SVN_OPT_PRIVATE_H
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+#include <apr_getopt.h>
+
+#include "svn_error.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Extract the peg revision, if any, from UTF8_TARGET.
+ *
+ * If PEG_REVISION is not NULL, return the peg revision in *PEG_REVISION.
+ * *PEG_REVISION will be an empty string if no peg revision is found.
+ * Return the true target portion in *TRUE_TARGET.
+ *
+ * UTF8_TARGET need not be canonical. *TRUE_TARGET will not be canonical
+ * unless UTF8_TARGET is.
+ *
+ * It is an error if *TRUE_TARGET results in the empty string after the
+ * split, which happens in case UTF8_TARGET has a leading '@' character
+ * with no additional '@' characters to escape the first '@'.
+ *
+ * Note that *PEG_REVISION will still contain the '@' symbol as the first
+ * character if a peg revision was found. If a trailing '@' symbol was
+ * used to escape other '@' characters in UTF8_TARGET, *PEG_REVISION will
+ * point to the string "@", containing only a single character.
+ *
+ * All allocations are done in POOL.
+ */
+svn_error_t *
+svn_opt__split_arg_at_peg_revision(const char **true_target,
+ const char **peg_revision,
+ const char *utf8_target,
+ apr_pool_t *pool);
+
+/* Attempt to transform URL_IN, which is a URL-like user input, into a
+ * valid URL:
+ * - escape IRI characters and some other non-URI characters
+ * - check that no back-path ("..") components are present
+ * - call svn_uri_canonicalize()
+ * URL_IN is in UTF-8 encoding and has no peg revision specifier.
+ * Set *URL_OUT to the result, allocated from POOL.
+ */
+svn_error_t *
+svn_opt__arg_canonicalize_url(const char **url_out,
+ const char *url_in,
+ apr_pool_t *pool);
+
+/*
+ * Attempt to transform PATH_IN, which is a local path-like user input, into a
+ * valid local path:
+ * - Attempt to get the correct capitalization by trying to actually find
+ * the path specified.
+ * - If the path does not exist (which is valid) the given capitalization
+ * is used.
+ * - canonicalize the separator ("/") characters
+ * - call svn_dirent_canonicalize()
+ * PATH_IN is in UTF-8 encoding and has no peg revision specifier.
+ * Set *PATH_OUT to the result, allocated from POOL.
+ */
+svn_error_t *
+svn_opt__arg_canonicalize_path(const char **path_out,
+ const char *path_in,
+ apr_pool_t *pool);
+
+/*
+ * Pull remaining target arguments from OS into *TARGETS_P,
+ * converting them to UTF-8, followed by targets from KNOWN_TARGETS
+ * (which might come from, for example, the "--targets" command line
+ * option), which are already in UTF-8.
+ *
+ * On each URL target, do some IRI-to-URI encoding and some
+ * auto-escaping. On each local path, canonicalize case and path
+ * separators.
+ *
+ * Allocate *TARGETS_P and its elements in POOL.
+ *
+ * If a path has the same name as a Subversion working copy
+ * administrative directory, return SVN_ERR_RESERVED_FILENAME_SPECIFIED;
+ * if multiple reserved paths are encountered, return a chain of
+ * errors, all of which are SVN_ERR_RESERVED_FILENAME_SPECIFIED. Do
+ * not return this type of error in a chain with any other type of
+ * error, and if this is the only type of error encountered, complete
+ * the operation before returning the error(s).
+ */
+svn_error_t *
+svn_opt__args_to_target_array(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ apr_pool_t *pool);
+
+/**
+ * Return a human-readable description of @a revision. The result
+ * will be allocated statically or from @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_opt__revision_to_string(const svn_opt_revision_t *revision,
+ apr_pool_t *result_pool);
+
+/**
+ * Create a revision range structure from two revisions. Return a new range
+ * allocated in @a result_pool with the start and end initialized to
+ * (deep copies of) @a *start_revision and @a *end_revision.
+ */
+svn_opt_revision_range_t *
+svn_opt__revision_range_create(const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ apr_pool_t *result_pool);
+
+/**
+ * Create a revision range structure from two revnums. Return a new range
+ * allocated in @a result_pool with the start and end kinds initialized to
+ * #svn_opt_revision_number and values @a start_revnum and @a end_revnum.
+ */
+svn_opt_revision_range_t *
+svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,
+ svn_revnum_t end_revnum,
+ apr_pool_t *result_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_OPT_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_pseudo_md5.h b/contrib/subversion/subversion/include/private/svn_pseudo_md5.h
new file mode 100644
index 0000000..34d5929
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_pseudo_md5.h
@@ -0,0 +1,83 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_pseudo_md5.h
+ * @brief Subversion hash sum calculation for runtime data (only)
+ */
+
+#ifndef SVN_PSEUDO_MD5_H
+#define SVN_PSEUDO_MD5_H
+
+#include <apr.h> /* for apr_uint32_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/**
+ * Calculates a hash sum for 15 bytes in @a x and returns it in @a digest.
+ * The most significant byte in @a x must be 0 (independent of being on a
+ * little or big endian machine).
+ *
+ * @note Use for runtime data hashing only.
+ *
+ * @note The output is NOT an MD5 digest shares has the same basic
+ * cryptographic properties. Collisions with proper MD5 on the same
+ * or other input data is equally unlikely as any MD5 collision.
+ */
+void svn__pseudo_md5_15(apr_uint32_t digest[4],
+ const apr_uint32_t x[4]);
+
+/**
+ * Calculates a hash sum for 31 bytes in @a x and returns it in @a digest.
+ * The most significant byte in @a x must be 0 (independent of being on a
+ * little or big endian machine).
+ *
+ * @note Use for runtime data hashing only.
+ *
+ * @note The output is NOT an MD5 digest shares has the same basic
+ * cryptographic properties. Collisions with proper MD5 on the same
+ * or other input data is equally unlikely as any MD5 collision.
+ */
+void svn__pseudo_md5_31(apr_uint32_t digest[4],
+ const apr_uint32_t x[8]);
+
+/**
+ * Calculates a hash sum for 63 bytes in @a x and returns it in @a digest.
+ * The most significant byte in @a x must be 0 (independent of being on a
+ * little or big endian machine).
+ *
+ * @note Use for runtime data hashing only.
+ *
+ * @note The output is NOT an MD5 digest shares has the same basic
+ * cryptographic properties. Collisions with proper MD5 on the same
+ * or other input data is equally unlikely as any MD5 collision.
+ */
+void svn__pseudo_md5_63(apr_uint32_t digest[4],
+ const apr_uint32_t x[16]);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_PSEUDO_MD5_H */
diff --git a/contrib/subversion/subversion/include/private/svn_ra_private.h b/contrib/subversion/subversion/include/private/svn_ra_private.h
new file mode 100644
index 0000000..4531bcb
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_ra_private.h
@@ -0,0 +1,280 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_ra_private.h
+ * @brief The Subversion repository access library - Internal routines
+ */
+
+#ifndef SVN_RA_PRIVATE_H
+#define SVN_RA_PRIVATE_H
+
+#include <apr_pools.h>
+
+#include "svn_error.h"
+#include "svn_ra.h"
+#include "svn_delta.h"
+#include "svn_editor.h"
+#include "svn_io.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Return an error with code SVN_ERR_UNSUPPORTED_FEATURE, and an error
+ message referencing PATH_OR_URL, if the "server" pointed to by
+ RA_SESSION doesn't support Merge Tracking (e.g. is pre-1.5).
+ Perform temporary allocations in POOL. */
+svn_error_t *
+svn_ra__assert_mergeinfo_capable_server(svn_ra_session_t *ra_session,
+ const char *path_or_url,
+ apr_pool_t *pool);
+
+
+/*** Operational Locks ***/
+
+/** This is a function type which allows svn_ra__get_operational_lock()
+ * to report lock attempt failures. If non-NULL, @a locktoken is the
+ * preexisting lock which prevented lock acquisition.
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_ra__lock_retry_func_t)(void *baton,
+ const svn_string_t *locktoken,
+ apr_pool_t *pool);
+
+/** Acquire a lock (of sorts) on the repository associated with the
+ * given RA @a session, retrying as necessary up to @a num_retries
+ * times, and set @a *lock_string_p to the value of the acquired lock
+ * token. Allocate the returned token from @a pool. (See this
+ * function's counterpart svn_ra__release_operational_lock() for your
+ * lock removal needs.)
+ *
+ * @a lock_revprop_name is the name of the revision-0 property used to
+ * store the lock.
+ *
+ * If @a steal_lock is set, then replace any pre-existing lock on the
+ * repository with our own. Iff such a theft occurs and
+ * @a stolen_lock_p is non-NULL, set @a *stolen_lock_p to the token of
+ * the lock we stole.
+ *
+ * Call @a retry_func with @a retry_baton each time the retry loop
+ * fails to acquire a lock.
+ *
+ * Use @a cancel_func and @a cancel_baton to check for early
+ * cancellation.
+ *
+ * @note If the server does not support #SVN_RA_CAPABILITY_ATOMIC_REVPROPS
+ * (i.e., is a pre-1.7 server), then this function makes a "best effort"
+ * attempt to obtain the lock, but is susceptible to a race condition; see
+ * issue #3546.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_ra__get_operational_lock(const svn_string_t **lock_string_p,
+ const svn_string_t **stolen_lock_p,
+ svn_ra_session_t *session,
+ const char *lock_revprop_name,
+ svn_boolean_t steal_lock,
+ int num_retries,
+ svn_ra__lock_retry_func_t retry_func,
+ void *retry_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/** Release an operational lock (whose value is @a mylocktoken) on the
+ * repository associated with RA @a session. (This is the counterpart
+ * to svn_ra__get_operational_lock().)
+ *
+ * @a lock_revprop_name is the name of the revision-0 property used to
+ * store the lock.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_ra__release_operational_lock(svn_ra_session_t *session,
+ const char *lock_revprop_name,
+ const svn_string_t *mylocktoken,
+ apr_pool_t *scratch_pool);
+
+/** Register CALLBACKS to be used with the Ev2 shims in RA_SESSION. */
+svn_error_t *
+svn_ra__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
+ svn_delta_shim_callbacks_t *callbacks);
+
+
+/* Using information from BATON, provide the (file's) pristine contents
+ for REPOS_RELPATH. They are returned in *CONTENTS, and correspond to
+ *REVISION.
+
+ If a pristine is not available (ie. a locally-added node), then set
+ *CONTENTS to NULL; *REVISION will not be examined in this case.
+
+ These are allocated in RESULT_POOL. SCRATCH_POOL can be used
+ for temporary allocations. */
+typedef svn_error_t *(*svn_ra__provide_base_cb_t)(
+ svn_stream_t **contents,
+ svn_revnum_t *revision,
+ void *baton,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Using information from BATON, provide the pristine properties for
+ REPOS_RELPATH. They are returned in *PROPS, and correspond to *REVISION.
+
+ If properties are not available (ie. a locally-added node), then set
+ *PROPS to NULL; *REVISION will not be examined in this case.
+
+ The properties are allocated in RESULT_POOL. SCRATCH_POOL can be used
+ for temporary allocations. */
+typedef svn_error_t *(*svn_ra__provide_props_cb_t)(
+ apr_hash_t **props,
+ svn_revnum_t *revision,
+ void *baton,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Using information from BATON, fetch the kind of REPOS_RELPATH at revision
+ SRC_REVISION, returning it in *KIND.
+
+ If the kind cannot be determined, then set *KIND to svn_node_unknown.
+
+ Temporary allocations can be made in SCRATCH_POOL. */
+typedef svn_error_t *(*svn_ra__get_copysrc_kind_cb_t)(
+ svn_node_kind_t *kind,
+ void *baton,
+ const char *repos_relpath,
+ svn_revnum_t src_revision,
+ apr_pool_t *scratch_pool);
+
+
+/* Return an Ev2-based editor for performing commits.
+
+ The editor is associated with the given SESSION, and its implied target
+ repository.
+
+ REVPROPS contains all the revision properties that should be stored onto
+ the newly-committed revision. SVN_PROP_REVISION_AUTHOR will be set to
+ the username as determined by the session; overwriting any prior value
+ that may be present in REVPROPS.
+
+ COMMIT_CB/BATON contain the callback to receive post-commit information.
+
+ LOCK_TOKENS should contain all lock tokens necessary to modify paths
+ within the commit. If KEEP_LOCKS is FALSE, then the paths associated
+ with these tokens will be unlocked.
+ ### today, LOCK_TOKENS is session_relpath:token_value. in the future,
+ ### it should be repos_relpath:token_value.
+
+ PROVIDE_BASE_CB is a callback to fetch pristine contents, used to send
+ an svndiff over the wire to the server. This may be NULL, indicating
+ pristine contents are not available (eg. URL-based operations or import).
+
+ PROVIDE_PROPS_CB is a callback to fetch pristine properties, used to
+ send property deltas over the wire to the server. This may be NULL,
+ indicating pristine properties are not available (eg. URL-based operations
+ or an import).
+
+ GET_COPYSRC_KIND_CB is a callback to determine the kind of a copy-source.
+ This is necessary when an Ev2/Ev1 shim is required by the RA provider,
+ in order to determine whether to use delta->add_directory() or the
+ delta->add_file() vtable entry to perform the copy.
+ ### unclear on impact if this is NULL.
+ ### this callback will disappear when "everything" is running Ev2
+
+ CB_BATON is the baton used/shared by the above three callbacks.
+
+ Cancellation is handled through the callbacks provided when SESSION
+ is initially opened.
+
+ *EDITOR will be allocated in RESULT_POOL, and all temporary allocations
+ will be performed in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_ra__get_commit_ev2(svn_editor_t **editor,
+ svn_ra_session_t *session,
+ apr_hash_t *revprops,
+ svn_commit_callback2_t commit_cb,
+ void *commit_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
+ void *cb_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Similar to #svn_ra_replay_revstart_callback_t, but with an Ev2 editor. */
+typedef svn_error_t *(*svn_ra__replay_revstart_ev2_callback_t)(
+ svn_revnum_t revision,
+ void *replay_baton,
+ svn_editor_t **editor,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool);
+
+/* Similar to #svn_ra_replay_revfinish_callback_t, but with an Ev2 editor. */
+typedef svn_error_t *(*svn_ra__replay_revfinish_ev2_callback_t)(
+ svn_revnum_t revision,
+ void *replay_baton,
+ svn_editor_t *editor,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool);
+
+/* Similar to svn_ra_replay_range(), but uses Ev2 versions of the callback
+ functions. */
+svn_error_t *
+svn_ra__replay_range_ev2(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra__replay_revstart_ev2_callback_t revstart_func,
+ svn_ra__replay_revfinish_ev2_callback_t revfinish_func,
+ void *replay_baton,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
+ void *cb_baton,
+ apr_pool_t *scratch_pool);
+
+/* Similar to svn_ra_replay(), but with an Ev2 editor. */
+svn_error_t *
+svn_ra__replay_ev2(svn_ra_session_t *session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_editor_t *editor,
+ apr_pool_t *scratch_pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_RA_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_ra_svn_private.h b/contrib/subversion/subversion/include/private/svn_ra_svn_private.h
new file mode 100644
index 0000000..b4294d0
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_ra_svn_private.h
@@ -0,0 +1,826 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_ra_svn_private.h
+ * @brief Functions used by the server - Internal routines
+ */
+
+#ifndef SVN_RA_SVN_PRIVATE_H
+#define SVN_RA_SVN_PRIVATE_H
+
+#include "svn_ra_svn.h"
+#include "svn_editor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/**
+ * Set the shim callbacks to be used by @a conn to @a shim_callbacks.
+ */
+svn_error_t *
+svn_ra_svn__set_shim_callbacks(svn_ra_svn_conn_t *conn,
+ svn_delta_shim_callbacks_t *shim_callbacks);
+
+/**
+ * @defgroup ra_svn_deprecated ra_svn low-level functions
+ * @{
+ */
+
+/** Write a number over the net.
+ *
+ * Writes will be buffered until the next read or flush.
+ */
+svn_error_t *
+svn_ra_svn__write_number(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_uint64_t number);
+
+/** Write a string over the net.
+ *
+ * Writes will be buffered until the next read or flush.
+ */
+svn_error_t *
+svn_ra_svn__write_string(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_string_t *str);
+
+/** Write a cstring over the net.
+ *
+ * Writes will be buffered until the next read or flush.
+ */
+svn_error_t *
+svn_ra_svn__write_cstring(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *s);
+
+/** Write a word over the net.
+ *
+ * Writes will be buffered until the next read or flush.
+ */
+svn_error_t *
+svn_ra_svn__write_word(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *word);
+
+/** Write a list of properties over the net. @a props is allowed to be NULL,
+ * in which case an empty list will be written out.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_svn__write_proplist(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_hash_t *props);
+
+/** Begin a list. Writes will be buffered until the next read or flush. */
+svn_error_t *
+svn_ra_svn__start_list(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** End a list. Writes will be buffered until the next read or flush. */
+svn_error_t *
+svn_ra_svn__end_list(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** Flush the write buffer.
+ *
+ * Normally this shouldn't be necessary, since the write buffer is flushed
+ * when a read is attempted.
+ */
+svn_error_t *
+svn_ra_svn__flush(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** Write a tuple, using a printf-like interface.
+ *
+ * The format string @a fmt may contain:
+ *
+ *@verbatim
+ Spec Argument type Item type
+ ---- -------------------- ---------
+ n apr_uint64_t Number
+ r svn_revnum_t Number
+ s const svn_string_t * String
+ c const char * String
+ w const char * Word
+ b svn_boolean_t Word ("true" or "false")
+ ( Begin tuple
+ ) End tuple
+ ? Remaining elements optional
+ ! (at beginning or end) Suppress opening or closing of tuple
+ @endverbatim
+ *
+ * Inside the optional part of a tuple, 'r' values may be @c
+ * SVN_INVALID_REVNUM, 'n' values may be
+ * SVN_RA_SVN_UNSPECIFIED_NUMBER, and 's', 'c', and 'w' values may be
+ * @c NULL; in these cases no data will be written. 'b' and '(' may
+ * not appear in the optional part of a tuple. Either all or none of
+ * the optional values should be valid.
+ *
+ * (If we ever have a need for an optional boolean value, we should
+ * invent a 'B' specifier which stores a boolean into an int, using -1
+ * for unspecified. Right now there is no need for such a thing.)
+ *
+ * Use the '!' format specifier to write partial tuples when you have
+ * to transmit an array or other unusual data. For example, to write
+ * a tuple containing a revision, an array of words, and a boolean:
+ * @code
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(!", rev));
+ for (i = 0; i < n; i++)
+ SVN_ERR(svn_ra_svn_write_word(conn, pool, words[i]));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)b", flag)); @endcode
+ */
+svn_error_t *
+svn_ra_svn__write_tuple(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...);
+
+/** Read an item from the network into @a *item. */
+svn_error_t *
+svn_ra_svn__read_item(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_ra_svn_item_t **item);
+
+/** Scan data on @a conn until we find something which looks like the
+ * beginning of an svn server greeting (an open paren followed by a
+ * whitespace character). This function is appropriate for beginning
+ * a client connection opened in tunnel mode, since people's dotfiles
+ * sometimes write output to stdout. It may only be called at the
+ * beginning of a client connection.
+ */
+svn_error_t *
+svn_ra_svn__skip_leading_garbage(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** Parse an array of @c svn_sort__item_t structures as a tuple, using a
+ * printf-like interface. The format string @a fmt may contain:
+ *
+ *@verbatim
+ Spec Argument type Item type
+ ---- -------------------- ---------
+ n apr_uint64_t * Number
+ r svn_revnum_t * Number
+ s svn_string_t ** String
+ c const char ** String
+ w const char ** Word
+ b svn_boolean_t * Word ("true" or "false")
+ B apr_uint64_t * Word ("true" or "false")
+ l apr_array_header_t ** List
+ ( Begin tuple
+ ) End tuple
+ ? Tuple is allowed to end here
+ @endverbatim
+ *
+ * Note that a tuple is only allowed to end precisely at a '?', or at
+ * the end of the specification. So if @a fmt is "c?cc" and @a list
+ * contains two elements, an error will result.
+ *
+ * 'B' is similar to 'b', but may be used in the optional tuple specification.
+ * It returns TRUE, FALSE, or SVN_RA_SVN_UNSPECIFIED_NUMBER.
+ *
+ * If an optional part of a tuple contains no data, 'r' values will be
+ * set to @c SVN_INVALID_REVNUM, 'n' and 'B' values will be set to
+ * SVN_RA_SVN_UNSPECIFIED_NUMBER, and 's', 'c', 'w', and 'l' values
+ * will be set to @c NULL. 'b' may not appear inside an optional
+ * tuple specification; use 'B' instead.
+ */
+svn_error_t *
+svn_ra_svn__parse_tuple(const apr_array_header_t *list,
+ apr_pool_t *pool,
+ const char *fmt, ...);
+
+/** Read a tuple from the network and parse it as a tuple, using the
+ * format string notation from svn_ra_svn_parse_tuple().
+ */
+svn_error_t *
+svn_ra_svn__read_tuple(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...);
+
+/** Parse an array of @c svn_ra_svn_item_t structures as a list of
+ * properties, storing the properties in a hash table.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_svn__parse_proplist(const apr_array_header_t *list,
+ apr_pool_t *pool,
+ apr_hash_t **props);
+
+/** Read a command response from the network and parse it as a tuple, using
+ * the format string notation from svn_ra_svn_parse_tuple().
+ */
+svn_error_t *
+svn_ra_svn__read_cmd_response(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...);
+
+/** Accept commands over the network and handle them according to @a
+ * commands. Command handlers will be passed @a conn, a subpool of @a
+ * pool (cleared after each command is handled), the parameters of the
+ * command, and @a baton. Commands will be accepted until a
+ * terminating command is received (a command with "terminate" set in
+ * the command table). If a command handler returns an error wrapped
+ * in SVN_RA_SVN_CMD_ERR (see the @c SVN_CMD_ERR macro), the error
+ * will be reported to the other side of the connection and the
+ * command loop will continue; any other kind of error (typically a
+ * network or protocol error) is passed through to the caller.
+ *
+ * @since New in 1.6.
+ *
+ */
+svn_error_t *
+svn_ra_svn__handle_commands2(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_ra_svn_cmd_entry_t *commands,
+ void *baton,
+ svn_boolean_t error_on_disconnect);
+
+/** Write a successful command response over the network, using the
+ * same format string notation as svn_ra_svn_write_tuple(). Do not use
+ * partial tuples with this function; if you need to use partial
+ * tuples, just write out the "success" and argument tuple by hand.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_response(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...);
+
+/** Write an unsuccessful command response over the network. */
+svn_error_t *
+svn_ra_svn__write_cmd_failure(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_error_t *err);
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup svn_commands sending ra_svn commands
+ * @{
+ */
+
+/** Sets the target revision of connection @a conn to @a rev. Use @a pool
+ * for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_target_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev);
+
+/** Send a "open-root" command over connection @a conn. Open the
+ * repository root at revision @a rev and associate it with @a token.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_open_root(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *token);
+
+/** Send a "delete-entry" command over connection @a conn. Delete the
+ * @a path at optional revision @a rev below @a parent_token.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_delete_entry(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t rev,
+ const char *parent_token);
+
+/** Send a "add-dir" command over connection @a conn. Add a new directory
+ * node named @a path under the directory identified by @a parent_token.
+ * Associate the new directory with the given @a token. * @a copy_path
+ * and @a copy_rev are optional and describe the copy source.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_add_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token,
+ const char *token,
+ const char *copy_path,
+ svn_revnum_t copy_rev);
+
+/** Send a "open-dir" command over connection @a conn. Associate to
+ * @a token the directory node named @a path under the directory
+ * identified by @a parent_token in revision @a rev.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_open_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token,
+ const char *token,
+ svn_revnum_t rev);
+
+/** Send a "change-dir-prop" command over connection @a conn. Set the
+ * property @a name to the optional @a value on the directory identified
+ * to @a token. Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_change_dir_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token,
+ const char *name,
+ const svn_string_t *value);
+
+/** Send a "close-dir" command over connection @a conn. Identify the node
+ * to close with @a token. The latter will then no longer be associated
+ * with that node. Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_close_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token);
+
+/** Send a "absent-dir" command over connection @a conn. Directory node
+ * named @a path under the directory identified by @a parent_token is
+ * absent. Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_absent_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token);
+
+/** Send a "add-file" command over connection @a conn. Add a new file
+ * node named @a path under the directory identified by @a parent_token.
+ * Associate the new file with the given @a token. * @a copy_path and
+ * @a copy_rev are optional and describe the copy source.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_add_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token,
+ const char *token,
+ const char *copy_path,
+ svn_revnum_t copy_rev);
+
+/** Send a "open-file" command over connection @a conn. Associate to
+ * @a token the file node named @a path under the directory identified by
+ * @a parent_token in revision @a rev.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_open_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token,
+ const char *token,
+ svn_revnum_t rev);
+
+/** Send a "change-file-prop" command over connection @a conn. Set the
+ * property @a name to the optional @a value on the file identified to
+ * @a token. Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_change_file_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token,
+ const char *name,
+ const svn_string_t *value);
+
+/** Send a "close-dir" command over connection @a conn. Identify the node
+ * to close with @a token and provide an optional @a check_sum. The token
+ * will then no longer be associated with that node.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_close_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token,
+ const char *text_checksum);
+
+/** Send a "absent-file" command over connection @a conn. File node
+ * named @a path in the directory identified by @a parent_token is
+ * absent. Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_absent_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token);
+
+/** Send a "apply-textdelta" command over connection @a conn. Starts a
+ * series of text deltas to be applied to the file identified by @a token.
+ * Optionally, specify the file's current checksum in @a base_checksum.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_apply_textdelta(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token,
+ const char *base_checksum);
+
+/** Send a "textdelta-chunk" command over connection @a conn. Apply
+ * textdelta @a chunk to the file identified by @a token.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_textdelta_chunk(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token,
+ const svn_string_t *chunk);
+
+/** Send a "textdelta-end" command over connection @a conn. Ends the
+ * series of text deltas to be applied to the file identified by @a token.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_textdelta_end(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token);
+
+/** Send a "close-edit" command over connection @a conn. Ends the editor
+ * drive (successfully). Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_close_edit(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** Send a "abort-edit" command over connection @a conn. Prematurely ends
+ * the editor drive, e.g. due to some problem on the other side.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_abort_edit(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** Send a "set-path" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see set_path() in #svn_ra_reporter3_t for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_set_path(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t rev,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ svn_depth_t depth);
+
+/** Send a "delete-path" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see delete_path() in #svn_ra_reporter3_t for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_delete_path(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path);
+
+/** Send a "link-path" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see link_path() in #svn_ra_reporter3_t for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_link_path(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *url,
+ svn_revnum_t rev,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ svn_depth_t depth);
+
+/** Send a "finish-report" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see finish_report() in #svn_ra_reporter3_t for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_finish_report(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** Send a "abort-report" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see abort_report() in #svn_ra_reporter3_t for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_abort_report(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** Send a "reparent" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_reparent for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_reparent(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *url);
+
+/** Send a "get-latest-rev" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_get_latest_revnum for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_get_latest_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** Send a "get-dated-rev" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_get_dated_revision for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_get_dated_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_time_t tm);
+
+/** Send a "change-rev-prop2" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_change_rev_prop2 for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_change_rev_prop2(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *value,
+ svn_boolean_t dont_care,
+ const svn_string_t *old_value);
+
+/** Send a "change-rev-prop" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_change_rev_prop for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_change_rev_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *value);
+
+/** Send a "rev-proplist" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_rev_proplist for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_rev_proplist(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev);
+
+/** Send a "rev-prop" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_rev_prop for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_rev_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *name);
+
+/** Send a "get-file" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_get_file for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_get_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t rev,
+ svn_boolean_t props,
+ svn_boolean_t stream);
+
+/** Send a "update" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_do_update3 for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_update(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *target,
+ svn_boolean_t recurse,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry);
+
+/** Send a "switch" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_do_switch3 for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_switch(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *target,
+ svn_boolean_t recurse,
+ const char *switch_url,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry);
+
+/** Send a "status" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_do_status2 for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_status(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *target,
+ svn_boolean_t recurse,
+ svn_revnum_t rev,
+ svn_depth_t depth);
+
+/** Send a "diff" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_do_diff3 for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_diff(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *target,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ const char *versus_url,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth);
+
+/** Send a "check-path" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_check_path for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_check_path(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t rev);
+
+/** Send a "stat" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_stat for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_stat(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t rev);
+
+/** Send a "get-file-revs" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_get_file_revs2 for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_get_file_revs(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions);
+
+/** Send a "lock" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_lock for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_lock(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ svn_revnum_t revnum);
+
+/** Send a "unlock" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_unlock for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_unlock(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock);
+
+/** Send a "get-lock" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_get_lock for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_get_lock(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path);
+
+/** Send a "get-locks" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_get_locks2 for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_get_locks(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_depth_t depth);
+
+/** Send a "replay" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_replay for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_replay(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas);
+
+/** Send a "replay-range" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_replay_range for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_replay_range(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas);
+
+/** Send a "get-deleted-rev" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_get_deleted_rev for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_get_deleted_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision);
+
+/** Send a "get-iprops" command over connection @a conn.
+ * Use @a pool for allocations.
+ *
+ * @see #svn_ra_get_inherited_props for a description.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_get_iprops(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t revision);
+
+/** Send a "finish-replay" command over connection @a conn.
+ * Use @a pool for allocations.
+ */
+svn_error_t *
+svn_ra_svn__write_cmd_finish_replay(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/**
+ * @}
+ */
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_RA_SVN_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_repos_private.h b/contrib/subversion/subversion/include/private/svn_repos_private.h
new file mode 100644
index 0000000..8e943ae
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_repos_private.h
@@ -0,0 +1,121 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_repos_private.h
+ * @brief Subversion-internal repos APIs.
+ */
+
+#ifndef SVN_REPOS_PRIVATE_H
+#define SVN_REPOS_PRIVATE_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_repos.h"
+#include "svn_editor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** Validate that property @a name is valid for use in a Subversion
+ * repository; return @c SVN_ERR_REPOS_BAD_ARGS if it isn't. For some
+ * "svn:" properties, also validate the @a value, and return
+ * @c SVN_ERR_BAD_PROPERTY_VALUE if it is not valid.
+ *
+ * Use @a pool for temporary allocations.
+ *
+ * @note This function is used to implement server-side validation.
+ * Consequently, if you make this function stricter in what it accepts, you
+ * (a) break svnsync'ing of existing repositories that contain now-invalid
+ * properties, (b) do not preclude such invalid values from entering the
+ * repository via tools that use the svn_fs_* API directly (possibly
+ * including svnadmin and svnlook). This has happened before and there
+ * are known (documented, but unsupported) upgrade paths in some cases.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_repos__validate_prop(const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/**
+ * Given the error @a err from svn_repos_fs_commit_txn(), return an
+ * string containing either or both of the svn_fs_commit_txn() error
+ * and the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped error from
+ * the post-commit hook. Any error tracing placeholders in the error
+ * chain are skipped over.
+ *
+ * This function does not modify @a err.
+ *
+ * ### This method should not be necessary, but there are a few
+ * ### places, e.g. mod_dav_svn, where only a single error message
+ * ### string is returned to the caller and it is useful to have both
+ * ### error messages included in the message.
+ *
+ * Use @a pool to do any allocations in.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_repos__post_commit_error_str(svn_error_t *err,
+ apr_pool_t *pool);
+
+/* A repos version of svn_fs_type */
+svn_error_t *
+svn_repos__fs_type(const char **fs_type,
+ const char *repos_path,
+ apr_pool_t *pool);
+
+
+/* Create a commit editor for REPOS, based on REVISION. */
+svn_error_t *
+svn_repos__get_commit_ev2(svn_editor_t **editor,
+ svn_repos_t *repos,
+ svn_authz_t *authz,
+ const char *authz_repos_name,
+ const char *authz_user,
+ apr_hash_t *revprops,
+ svn_commit_callback2_t commit_cb,
+ void *commit_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_repos__replay_ev2(svn_fs_root_t *root,
+ const char *base_dir,
+ svn_revnum_t low_water_mark,
+ svn_editor_t *editor,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *scratch_pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_REPOS_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_skel.h b/contrib/subversion/subversion/include/private/svn_skel.h
new file mode 100644
index 0000000..5b17b21
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_skel.h
@@ -0,0 +1,236 @@
+/* svn_skel.h : interface to `skeleton' functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_SKEL_H
+#define SVN_SKEL_H
+
+#include <apr_pools.h>
+
+#include "svn_string.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* What is a skel? */
+
+/* Subversion needs to read a lot of structured data from database
+ records. Instead of writing a half-dozen parsers and getting lazy
+ about error-checking, we define a reasonably dense, open-ended
+ syntax for strings and lists, and then use that for the concrete
+ representation of files, directories, property lists, etc. This
+ lets us handle all the fussy character-by-character testing and
+ sanity checks all in one place, allowing the users of this library
+ to focus on higher-level consistency.
+
+ A `skeleton' (or `skel') is either an atom, or a list. A list may
+ contain zero or more elements, each of which may be an atom or a
+ list.
+
+ Here's a description of the syntax of a skel:
+
+ A "whitespace" byte is 9, 10, 12, 13, or 32 (ASCII tab, newline,
+ form feed, carriage return, or space).
+
+ A "digit" byte is 48 -- 57 (ASCII digits).
+
+ A "name" byte is 65 -- 90, or 97 -- 122 (ASCII upper- and
+ lower-case characters).
+
+ An atom has one the following two forms:
+ - any string of bytes whose first byte is a name character, and
+ which contains no whitespace characters, bytes 40 (ASCII '(') or
+ bytes 41 (ASCII ')') (`implicit-length form'), or
+ - a string of digit bytes, followed by exactly one whitespace
+ character, followed by N bytes, where N is the value of the digit
+ bytes as a decimal number (`explicit-length form').
+
+ In the first case, the `contents' of the atom are the entire string
+ of characters. In the second case, the contents of the atom are
+ the N bytes after the count and whitespace.
+
+ A list consists of a byte 40 (ASCII '('), followed by a series of
+ atoms or lists, followed by a byte 41 (ASCII ')'). There may be
+ zero or more whitespace characters after the '(' and before the
+ ')', and between any pair of elements. If two consecutive elements
+ are atoms, they must be separated by at least one whitespace
+ character. */
+
+
+/* The `skel' structure. */
+
+/* A structure representing the results of parsing an array of bytes
+ as a skel. */
+struct svn_skel_t {
+
+ /* True if the string was an atom, false if it was a list.
+
+ If the string is an atom, DATA points to the beginning of its
+ contents, and LEN gives the content length, in bytes.
+
+ If the string is a list, DATA and LEN delimit the entire body of
+ the list. */
+ svn_boolean_t is_atom;
+
+ const char *data;
+ apr_size_t len;
+
+ /* If the string is a list, CHILDREN is a pointer to a
+ null-terminated linked list of skel objects representing the
+ elements of the list, linked through their NEXT pointers. */
+ struct svn_skel_t *children;
+ struct svn_skel_t *next;
+};
+typedef struct svn_skel_t svn_skel_t;
+
+
+
+/* Operations on skels. */
+
+
+/* Parse the LEN bytes at DATA as the concrete representation of a
+ skel, and return a skel object allocated from POOL describing its
+ contents. If the data is not a properly-formed SKEL object, return
+ zero.
+
+ The returned skel objects point into the block indicated by DATA
+ and LEN; we don't copy the contents. */
+svn_skel_t *svn_skel__parse(const char *data, apr_size_t len,
+ apr_pool_t *pool);
+
+
+/* Create an atom skel whose contents are the C string STR, allocated
+ from POOL. */
+svn_skel_t *svn_skel__str_atom(const char *str, apr_pool_t *pool);
+
+
+/* Create an atom skel whose contents are the LEN bytes at ADDR,
+ allocated from POOL. */
+svn_skel_t *svn_skel__mem_atom(const void *addr, apr_size_t len,
+ apr_pool_t *pool);
+
+
+/* Create an empty list skel, allocated from POOL. */
+svn_skel_t *svn_skel__make_empty_list(apr_pool_t *pool);
+
+/* Duplicates the skel structure SRC_SKEL and if DUP_DATA is true also the
+ data it references in RESULT_POOL */
+svn_skel_t *svn_skel__dup(const svn_skel_t *src_skel, svn_boolean_t dup_data,
+ apr_pool_t *result_pool);
+
+
+/* Prepend SKEL to LIST. */
+void svn_skel__prepend(svn_skel_t *skel, svn_skel_t *list);
+
+
+/* Append SKEL to LIST. Note: this must traverse the LIST, so you
+ generally want to use svn_skel__prepend().
+
+ NOTE: careful of the argument order here. */
+void svn_skel__append(svn_skel_t *list, svn_skel_t *skel);
+
+
+/* Create an atom skel whose contents are the string representation
+ of the integer VALUE, allocated in RESULT_POOL, and then prepend
+ it to SKEL. */
+void svn_skel__prepend_int(apr_int64_t value,
+ svn_skel_t *skel,
+ apr_pool_t *result_pool);
+
+
+/* Create an atom skel (allocated from RESULT_POOL) whose contents refer
+ to the string VALUE, then prepend it to SKEL.
+
+ NOTE: VALUE must have a lifetime *at least* that of RESULT_POOL. This
+ function does NOT copy it into RESULT_POOL. */
+void svn_skel__prepend_str(const char *value,
+ svn_skel_t *skel,
+ apr_pool_t *result_pool);
+
+
+/* Parse SKEL as an integer and return the result in *N.
+ * SCRATCH_POOL is used for temporary memory. */
+svn_error_t *
+svn_skel__parse_int(apr_int64_t *n, const svn_skel_t *skel,
+ apr_pool_t *scratch_pool);
+
+
+/* Return a string whose contents are a concrete representation of
+ SKEL. Allocate the string from POOL. */
+svn_stringbuf_t *svn_skel__unparse(const svn_skel_t *skel, apr_pool_t *pool);
+
+
+/* Return true iff SKEL is an atom whose data is the same as STR. */
+svn_boolean_t svn_skel__matches_atom(const svn_skel_t *skel, const char *str);
+
+
+/* Return the length of the list skel SKEL. Atoms have a length of -1. */
+int svn_skel__list_length(const svn_skel_t *skel);
+
+
+/* Parse a `PROPLIST' SKEL into a regular hash of properties,
+ *PROPLIST_P, which has const char * property names, and
+ svn_string_t * values. Use RESULT_POOL for all allocations. */
+svn_error_t *
+svn_skel__parse_proplist(apr_hash_t **proplist_p,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool);
+
+/* Parse a `IPROPS' SKEL into a depth-first ordered array of
+ svn_prop_inherited_item_t * structures *IPROPS. Use RESULT_POOL
+ for all allocations. */
+svn_error_t *
+svn_skel__parse_iprops(apr_array_header_t **iprops,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool);
+
+/* Parse a `PROPLIST' SKEL looking for PROPNAME. If PROPNAME is found
+ then return its value in *PROVAL, allocated in RESULT_POOL. */
+svn_error_t *
+svn_skel__parse_prop(svn_string_t **propval,
+ const svn_skel_t *skel,
+ const char *propname,
+ apr_pool_t *result_pool);
+
+/* Unparse a PROPLIST hash (which has const char * property names and
+ svn_string_t * values) into a `PROPLIST' skel *SKEL_P. Use POOL
+ for all allocations. */
+svn_error_t *
+svn_skel__unparse_proplist(svn_skel_t **skel_p,
+ const apr_hash_t *proplist,
+ apr_pool_t *pool);
+
+/* Unparse INHERITED_PROPS, a depth-first ordered array of
+ svn_prop_inherited_item_t * structures, into a `IPROPS' skel *SKEL_P.
+ Use RESULT_POOL for all allocations. */
+svn_error_t *
+svn_skel__unparse_iproplist(svn_skel_t **skel_p,
+ const apr_array_header_t *inherited_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_SKEL_H */
diff --git a/contrib/subversion/subversion/include/private/svn_sqlite.h b/contrib/subversion/subversion/include/private/svn_sqlite.h
new file mode 100644
index 0000000..c1d640b
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_sqlite.h
@@ -0,0 +1,519 @@
+/* svn_sqlite.h
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_SQLITE_H
+#define SVN_SQLITE_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_checksum.h"
+#include "svn_error.h"
+
+#include "private/svn_token.h" /* for svn_token_map_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Because the SQLite code can be inlined into libsvn_subre/sqlite.c,
+ we define accessors to its compile-time and run-time version
+ numbers here. */
+
+/* Return the value that SQLITE_VERSION had at compile time. */
+const char *svn_sqlite__compiled_version(void);
+
+/* Return the value of sqlite3_libversion() at run time. */
+const char *svn_sqlite__runtime_version(void);
+
+
+typedef struct svn_sqlite__db_t svn_sqlite__db_t;
+typedef struct svn_sqlite__stmt_t svn_sqlite__stmt_t;
+typedef struct svn_sqlite__context_t svn_sqlite__context_t;
+typedef struct svn_sqlite__value_t svn_sqlite__value_t;
+
+typedef enum svn_sqlite__mode_e {
+ svn_sqlite__mode_readonly, /* open the database read-only */
+ svn_sqlite__mode_readwrite, /* open the database read-write */
+ svn_sqlite__mode_rwcreate /* open/create the database read-write */
+} svn_sqlite__mode_t;
+
+/* The type used for callback functions. */
+typedef svn_error_t *(*svn_sqlite__func_t)(svn_sqlite__context_t *sctx,
+ int argc,
+ svn_sqlite__value_t *values[],
+ apr_pool_t *scatch_pool);
+
+
+/* Step the given statement; if it returns SQLITE_DONE, reset the statement.
+ Otherwise, raise an SVN error. */
+svn_error_t *
+svn_sqlite__step_done(svn_sqlite__stmt_t *stmt);
+
+/* Step the given statement; raise an SVN error (and reset the
+ statement) if it doesn't return SQLITE_ROW. */
+svn_error_t *
+svn_sqlite__step_row(svn_sqlite__stmt_t *stmt);
+
+/* Step the given statement; raise an SVN error (and reset the
+ statement) if it doesn't return SQLITE_DONE or SQLITE_ROW. Set
+ *GOT_ROW to true iff it got SQLITE_ROW.
+*/
+svn_error_t *
+svn_sqlite__step(svn_boolean_t *got_row, svn_sqlite__stmt_t *stmt);
+
+/* Perform an insert as given by the prepared and bound STMT, and set
+ *ROW_ID to the id of the inserted row if ROW_ID is non-NULL.
+ STMT will be reset prior to returning. */
+svn_error_t *
+svn_sqlite__insert(apr_int64_t *row_id, svn_sqlite__stmt_t *stmt);
+
+/* Perform an update/delete and then return the number of affected rows.
+ If AFFECTED_ROWS is not NULL, then set *AFFECTED_ROWS to the
+ number of rows changed.
+ STMT will be reset prior to returning. */
+svn_error_t *
+svn_sqlite__update(int *affected_rows, svn_sqlite__stmt_t *stmt);
+
+/* Return in *VERSION the version of the schema in DB. Use SCRATCH_POOL
+ for temporary allocations. */
+svn_error_t *
+svn_sqlite__read_schema_version(int *version,
+ svn_sqlite__db_t *db,
+ apr_pool_t *scratch_pool);
+
+
+
+/* Open a connection in *DB to the database at PATH. Validate the schema,
+ creating/upgrading to LATEST_SCHEMA if needed using the instructions
+ in UPGRADE_SQL. The resulting DB is allocated in RESULT_POOL, and any
+ temporary allocations are made in SCRATCH_POOL.
+
+ STATEMENTS is an array of strings which may eventually be executed, the
+ last element of which should be NULL. These strings and the array itself
+ are not duplicated internally, and should have a lifetime at least as long
+ as RESULT_POOL.
+ STATEMENTS itself may be NULL, in which case it has no impact.
+ See svn_sqlite__get_statement() for how these strings are used.
+
+ The statements will be finalized and the SQLite database will be closed
+ when RESULT_POOL is cleaned up. */
+svn_error_t *
+svn_sqlite__open(svn_sqlite__db_t **db, const char *repos_path,
+ svn_sqlite__mode_t mode, const char * const statements[],
+ int latest_schema, const char * const *upgrade_sql,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool);
+
+/* Explicitly close the connection in DB. */
+svn_error_t *
+svn_sqlite__close(svn_sqlite__db_t *db);
+
+/* Add a custom function to be used with this database connection. The data
+ in BATON should live at least as long as the connection in DB. */
+svn_error_t *
+svn_sqlite__create_scalar_function(svn_sqlite__db_t *db,
+ const char *func_name,
+ int argc,
+ svn_sqlite__func_t func,
+ void *baton);
+
+/* Execute the (multiple) statements in the STATEMENTS[STMT_IDX] string. */
+svn_error_t *
+svn_sqlite__exec_statements(svn_sqlite__db_t *db, int stmt_idx);
+
+/* Return the statement in *STMT which has been prepared from the
+ STATEMENTS[STMT_IDX] string, where STATEMENTS is the array that was
+ passed to svn_sqlite__open(). This statement is allocated in the same
+ pool as the DB, and will be cleaned up when DB is closed. */
+svn_error_t *
+svn_sqlite__get_statement(svn_sqlite__stmt_t **stmt, svn_sqlite__db_t *db,
+ int stmt_idx);
+
+
+/* ---------------------------------------------------------------------
+
+ BINDING VALUES
+
+*/
+
+/* Bind values to SQL parameters in STMT, according to FMT. FMT may contain:
+
+ Spec Argument type Item type
+ ---- ----------------- ---------
+ n <none, absent> Column assignment skip
+ d int Number
+ L apr_int64_t Number
+ i apr_int64_t Number (deprecated format spec)
+ s const char * String
+ b const void * Blob data
+ apr_size_t Blob length
+ r svn_revnum_t Revision number
+ t const svn_token_map_t * Token mapping table
+ int Token value
+
+ Each character in FMT maps to one SQL parameter, and one or two function
+ parameters, in the order they appear.
+*/
+svn_error_t *
+svn_sqlite__bindf(svn_sqlite__stmt_t *stmt, const char *fmt, ...);
+
+/* Error-handling wrapper around sqlite3_bind_int. */
+svn_error_t *
+svn_sqlite__bind_int(svn_sqlite__stmt_t *stmt, int slot, int val);
+
+/* Error-handling wrapper around sqlite3_bind_int64. */
+svn_error_t *
+svn_sqlite__bind_int64(svn_sqlite__stmt_t *stmt, int slot,
+ apr_int64_t val);
+
+/* Error-handling wrapper around sqlite3_bind_text. VAL cannot contain
+ zero bytes; we always pass SQLITE_TRANSIENT. */
+svn_error_t *
+svn_sqlite__bind_text(svn_sqlite__stmt_t *stmt, int slot,
+ const char *val);
+
+/* Error-handling wrapper around sqlite3_bind_blob. */
+svn_error_t *
+svn_sqlite__bind_blob(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const void *val,
+ apr_size_t len);
+
+/* Look up VALUE in MAP, and bind the resulting token word at SLOT. */
+svn_error_t *
+svn_sqlite__bind_token(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const svn_token_map_t *map,
+ int value);
+
+/* Bind the value to SLOT, unless SVN_IS_VALID_REVNUM(value) is false,
+ in which case it binds NULL. */
+svn_error_t *
+svn_sqlite__bind_revnum(svn_sqlite__stmt_t *stmt, int slot,
+ svn_revnum_t value);
+
+/* Bind a set of properties to the given slot. If PROPS is NULL, then no
+ binding will occur. PROPS will be stored as a serialized skel. */
+svn_error_t *
+svn_sqlite__bind_properties(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const apr_hash_t *props,
+ apr_pool_t *scratch_pool);
+
+/* Bind a set of inherited properties to the given slot. If INHERITED_PROPS
+ is NULL, then no binding will occur. INHERITED_PROPS will be stored as a
+ serialized skel. */
+svn_error_t *
+svn_sqlite__bind_iprops(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const apr_array_header_t *inherited_props,
+ apr_pool_t *scratch_pool);
+
+/* Bind a checksum's value to the given slot. If CHECKSUM is NULL, then no
+ binding will occur. */
+svn_error_t *
+svn_sqlite__bind_checksum(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const svn_checksum_t *checksum,
+ apr_pool_t *scratch_pool);
+
+
+/* ---------------------------------------------------------------------
+
+ FETCHING VALUES
+
+*/
+
+/* Wrapper around sqlite3_column_blob and sqlite3_column_bytes. The return
+ value will be NULL if the column is null.
+
+ If RESULT_POOL is not NULL, allocate the return value (if any) in it.
+ If RESULT_POOL is NULL, the return value will be valid until an
+ invocation of svn_sqlite__column_* performs a data type conversion (as
+ described in the SQLite documentation) or the statement is stepped or
+ reset or finalized. */
+const void *
+svn_sqlite__column_blob(svn_sqlite__stmt_t *stmt, int column,
+ apr_size_t *len, apr_pool_t *result_pool);
+
+/* Wrapper around sqlite3_column_text. If the column is null, then the
+ return value will be NULL.
+
+ If RESULT_POOL is not NULL, allocate the return value (if any) in it.
+ If RESULT_POOL is NULL, the return value will be valid until an
+ invocation of svn_sqlite__column_* performs a data type conversion (as
+ described in the SQLite documentation) or the statement is stepped or
+ reset or finalized. */
+const char *
+svn_sqlite__column_text(svn_sqlite__stmt_t *stmt, int column,
+ apr_pool_t *result_pool);
+
+/* Wrapper around sqlite3_column_int64. If the column is null, then the
+ return value will be SVN_INVALID_REVNUM. */
+svn_revnum_t
+svn_sqlite__column_revnum(svn_sqlite__stmt_t *stmt, int column);
+
+/* Wrapper around sqlite3_column_int64. If the column is null, then the
+ return value will be FALSE. */
+svn_boolean_t
+svn_sqlite__column_boolean(svn_sqlite__stmt_t *stmt, int column);
+
+/* Wrapper around sqlite3_column_int. If the column is null, then the
+ return value will be 0. */
+int
+svn_sqlite__column_int(svn_sqlite__stmt_t *stmt, int column);
+
+/* Wrapper around sqlite3_column_int64. If the column is null, then the
+ return value will be 0. */
+apr_int64_t
+svn_sqlite__column_int64(svn_sqlite__stmt_t *stmt, int column);
+
+/* Fetch the word at COLUMN, look it up in the MAP, and return its value.
+ MALFUNCTION is thrown if the column is null or contains an unknown word. */
+int
+svn_sqlite__column_token(svn_sqlite__stmt_t *stmt,
+ int column,
+ const svn_token_map_t *map);
+
+/* Fetch the word at COLUMN, look it up in the MAP, and return its value.
+ Returns NULL_VAL if the column is null. MALFUNCTION is thrown if the
+ column contains an unknown word. */
+int
+svn_sqlite__column_token_null(svn_sqlite__stmt_t *stmt,
+ int column,
+ const svn_token_map_t *map,
+ int null_val);
+
+/* Return the column as a hash of const char * => const svn_string_t *.
+ If the column is null, then set *PROPS to NULL. The
+ results will be allocated in RESULT_POOL, and any temporary allocations
+ will be made in SCRATCH_POOL. */
+svn_error_t *
+svn_sqlite__column_properties(apr_hash_t **props,
+ svn_sqlite__stmt_t *stmt,
+ int column,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Return the column as an array of depth-first ordered array of
+ svn_prop_inherited_item_t * structures. If the column is null, then
+ set *IPROPS to NULL. The results will be allocated in RESULT_POOL,
+ and any temporary allocations will be made in SCRATCH_POOL. */
+svn_error_t *
+svn_sqlite__column_iprops(apr_array_header_t **iprops,
+ svn_sqlite__stmt_t *stmt,
+ int column,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Return the column as a checksum. If the column is null, then NULL will
+ be stored into *CHECKSUM. The result will be allocated in RESULT_POOL. */
+svn_error_t *
+svn_sqlite__column_checksum(const svn_checksum_t **checksum,
+ svn_sqlite__stmt_t *stmt,
+ int column,
+ apr_pool_t *result_pool);
+
+/* Return TRUE if the result of selecting the column is null,
+ FALSE otherwise */
+svn_boolean_t
+svn_sqlite__column_is_null(svn_sqlite__stmt_t *stmt, int column);
+
+/* Return the number of bytes the column uses in a text or blob representation.
+ 0 for NULL columns. */
+int
+svn_sqlite__column_bytes(svn_sqlite__stmt_t *stmt, int column);
+
+
+/* --------------------------------------------------------------------- */
+
+#define SVN_SQLITE__INTEGER 1
+#define SVN_SQLITE__FLOAT 2
+#define SVN_SQLITE__TEXT 3
+#define SVN_SQLITE__BLOB 4
+#define SVN_SQLITE__NULL 5
+
+/* */
+int
+svn_sqlite__value_type(svn_sqlite__value_t *val);
+
+/* */
+const char *
+svn_sqlite__value_text(svn_sqlite__value_t *val);
+
+
+/* --------------------------------------------------------------------- */
+
+/* */
+void
+svn_sqlite__result_null(svn_sqlite__context_t *sctx);
+
+void
+svn_sqlite__result_int64(svn_sqlite__context_t *sctx, apr_int64_t val);
+
+
+/* --------------------------------------------------------------------- */
+
+
+/* Error-handling wrapper around sqlite3_finalize. */
+svn_error_t *
+svn_sqlite__finalize(svn_sqlite__stmt_t *stmt);
+
+/* Reset STMT by calling sqlite3_reset(), and also clear any bindings to it.
+
+ Note: svn_sqlite__get_statement() calls this function automatically if
+ the requested statement has been used and has not yet been reset. */
+svn_error_t *
+svn_sqlite__reset(svn_sqlite__stmt_t *stmt);
+
+
+/* Begin a transaction in DB. */
+svn_error_t *
+svn_sqlite__begin_transaction(svn_sqlite__db_t *db);
+
+/* Like svn_sqlite__begin_transaction(), but takes out a 'RESERVED' lock
+ immediately, instead of using the default deferred locking scheme. */
+svn_error_t *
+svn_sqlite__begin_immediate_transaction(svn_sqlite__db_t *db);
+
+/* Begin a savepoint in DB. */
+svn_error_t *
+svn_sqlite__begin_savepoint(svn_sqlite__db_t *db);
+
+/* Commit the current transaction in DB if ERR is SVN_NO_ERROR, otherwise
+ * roll back the transaction. Return a composition of ERR and any error
+ * that may occur during the commit or roll-back. */
+svn_error_t *
+svn_sqlite__finish_transaction(svn_sqlite__db_t *db,
+ svn_error_t *err);
+
+/* Release the current savepoint in DB if EXPR is SVN_NO_ERROR, otherwise
+ * roll back to the savepoint and then release it. Return a composition of
+ * ERR and any error that may occur during the release or roll-back. */
+svn_error_t *
+svn_sqlite__finish_savepoint(svn_sqlite__db_t *db,
+ svn_error_t *err);
+
+/* Evaluate the expression EXPR within a transaction.
+ *
+ * Begin a transaction in DB; evaluate the expression EXPR, which would
+ * typically be a function call that does some work in DB; finally commit
+ * the transaction if EXPR evaluated to SVN_NO_ERROR, otherwise roll back
+ * the transaction.
+ */
+#define SVN_SQLITE__WITH_TXN(expr, db) \
+ do { \
+ svn_sqlite__db_t *svn_sqlite__db = (db); \
+ svn_error_t *svn_sqlite__err; \
+ \
+ SVN_ERR(svn_sqlite__begin_transaction(svn_sqlite__db)); \
+ svn_sqlite__err = (expr); \
+ SVN_ERR(svn_sqlite__finish_transaction(svn_sqlite__db, svn_sqlite__err)); \
+ } while (0)
+
+/* Callback function to for use with svn_sqlite__with_transaction(). */
+typedef svn_error_t *(*svn_sqlite__transaction_callback_t)(
+ void *baton, svn_sqlite__db_t *db, apr_pool_t *scratch_pool);
+
+/* Helper function to handle SQLite transactions. All the work done inside
+ CB_FUNC will be wrapped in an SQLite transaction, which will be committed
+ if CB_FUNC does not return an error. If any error is returned from CB_FUNC,
+ the transaction will be rolled back. DB and CB_BATON will be passed to
+ CB_FUNC. SCRATCH_POOL will be passed to the callback (NULL is valid). */
+svn_error_t *
+svn_sqlite__with_transaction(svn_sqlite__db_t *db,
+ svn_sqlite__transaction_callback_t cb_func,
+ void *cb_baton, apr_pool_t *scratch_pool);
+
+/* Like SVN_SQLITE__WITH_TXN(), but takes out a 'RESERVED' lock
+ immediately, instead of using the default deferred locking scheme. */
+#define SVN_SQLITE__WITH_IMMEDIATE_TXN(expr, db) \
+ do { \
+ svn_sqlite__db_t *svn_sqlite__db = (db); \
+ svn_error_t *svn_sqlite__err; \
+ \
+ SVN_ERR(svn_sqlite__begin_immediate_transaction(svn_sqlite__db)); \
+ svn_sqlite__err = (expr); \
+ SVN_ERR(svn_sqlite__finish_transaction(svn_sqlite__db, svn_sqlite__err)); \
+ } while (0)
+
+/* Like svn_sqlite__with_transaction(), but takes out a 'RESERVED' lock
+ immediately, instead of using the default deferred locking scheme. */
+svn_error_t *
+svn_sqlite__with_immediate_transaction(svn_sqlite__db_t *db,
+ svn_sqlite__transaction_callback_t cb_func,
+ void *cb_baton,
+ apr_pool_t *scratch_pool);
+
+/* Evaluate the expression EXPR within a 'savepoint'. Savepoints can be
+ * nested.
+ *
+ * Begin a savepoint in DB; evaluate the expression EXPR, which would
+ * typically be a function call that does some work in DB; finally release
+ * the savepoint if EXPR evaluated to SVN_NO_ERROR, otherwise roll back
+ * to the savepoint and then release it.
+ */
+#define SVN_SQLITE__WITH_LOCK(expr, db) \
+ do { \
+ svn_sqlite__db_t *svn_sqlite__db = (db); \
+ svn_error_t *svn_sqlite__err; \
+ \
+ SVN_ERR(svn_sqlite__begin_savepoint(svn_sqlite__db)); \
+ svn_sqlite__err = (expr); \
+ SVN_ERR(svn_sqlite__finish_savepoint(svn_sqlite__db, svn_sqlite__err)); \
+ } while (0)
+
+/* Helper function to handle several SQLite operations inside a shared lock.
+ This callback is similar to svn_sqlite__with_transaction(), but can be
+ nested (even with a transaction).
+
+ Using this function as a wrapper around a group of operations can give a
+ *huge* performance boost as the shared-read lock will be shared over
+ multiple statements, instead of being reobtained every time, which may
+ require disk and/or network io, depending on SQLite's locking strategy.
+
+ SCRATCH_POOL will be passed to the callback (NULL is valid).
+
+ ### Since we now require SQLite >= 3.6.18, this function has the effect of
+ always behaving like a defered transaction. Can it be combined with
+ svn_sqlite__with_transaction()?
+ */
+svn_error_t *
+svn_sqlite__with_lock(svn_sqlite__db_t *db,
+ svn_sqlite__transaction_callback_t cb_func,
+ void *cb_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Hotcopy an SQLite database from SRC_PATH to DST_PATH. */
+svn_error_t *
+svn_sqlite__hotcopy(const char *src_path,
+ const char *dst_path,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_SQLITE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_string_private.h b/contrib/subversion/subversion/include/private/svn_string_private.h
new file mode 100644
index 0000000..8579f4d
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_string_private.h
@@ -0,0 +1,222 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_string_private.h
+ * @brief Non-public string utility functions.
+ */
+
+
+#ifndef SVN_STRING_PRIVATE_H
+#define SVN_STRING_PRIVATE_H
+
+#include "svn_string.h" /* for svn_boolean_t, svn_error_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * @defgroup svn_string String handling
+ * @{
+ */
+
+
+/** Private functions.
+ *
+ * @defgroup svn_string_private Private functions
+ * @{
+ */
+
+
+/** A self-contained memory buffer of known size.
+ *
+ * Intended to be used where a single variable-sized buffer is needed
+ * within an iteration, a scratch pool is available and we want to
+ * avoid the cost of creating another pool just for the iteration.
+ */
+typedef struct svn_membuf_t
+{
+ /** The a pool from which this buffer was originally allocated, and is not
+ * necessarily specific to this buffer. This is used only for allocating
+ * more memory from when the buffer needs to grow.
+ */
+ apr_pool_t *pool;
+
+ /** pointer to the memory */
+ void *data;
+
+ /** total size of buffer allocated */
+ apr_size_t size;
+} svn_membuf_t;
+
+
+/* Initialize a memory buffer of the given size */
+void
+svn_membuf__create(svn_membuf_t *membuf, apr_size_t size, apr_pool_t *pool);
+
+/* Ensure that the given memory buffer has at least the given size */
+void
+svn_membuf__ensure(svn_membuf_t *membuf, apr_size_t size);
+
+/* Resize the given memory buffer, preserving its contents. */
+void
+svn_membuf__resize(svn_membuf_t *membuf, apr_size_t size);
+
+/* Zero-fill the given memory */
+void
+svn_membuf__zero(svn_membuf_t *membuf);
+
+/* Zero-fill the given memory buffer up to the smaller of SIZE and the
+ current buffer size. */
+void
+svn_membuf__nzero(svn_membuf_t *membuf, apr_size_t size);
+
+/* Inline implementation of svn_membuf__zero.
+ * Note that PMEMBUF is evaluated only once.
+ */
+#define SVN_MEMBUF__ZERO(pmembuf) \
+ do \
+ { \
+ svn_membuf_t *const _m_b_f_ = (pmembuf); \
+ memset(_m_b_f_->data, 0, _m_b_f_->size); \
+ } \
+ while(0)
+
+/* Inline implementation of svn_membuf__nzero
+ * Note that PMEMBUF and PSIZE are evaluated only once.
+ */
+#define SVN_MEMBUF__NZERO(pmembuf, psize) \
+ do \
+ { \
+ svn_membuf_t *const _m_b_f_ = (pmembuf); \
+ const apr_size_t _s_z_ = (psize); \
+ if (_s_z_ > _m_b_f_->size) \
+ memset(_m_b_f_->data, 0, _m_b_f_->size); \
+ else \
+ memset(_m_b_f_->data, 0, _s_z_); \
+ } \
+ while(0)
+
+#ifndef SVN_DEBUG
+/* In non-debug mode, just use these inlie replacements */
+#define svn_membuf__zero(B) SVN_MEMBUF__ZERO((B))
+#define svn_membuf__nzero(B, S) SVN_MEMBUF__NZERO((B), (S))
+#endif
+
+
+/** Returns the #svn_string_t information contained in the data and
+ * len members of @a strbuf. This is effectively a typecast, converting
+ * @a strbuf into an #svn_string_t. This first will become invalid and must
+ * not be accessed after this function returned.
+ */
+svn_string_t *
+svn_stringbuf__morph_into_string(svn_stringbuf_t *strbuf);
+
+/** Like apr_strtoff but provided here for backward compatibility
+ * with APR 0.9 */
+apr_status_t
+svn__strtoff(apr_off_t *offset, const char *buf, char **end, int base);
+
+/** Number of chars needed to represent signed (19 places + sign + NUL) or
+ * unsigned (20 places + NUL) integers as strings.
+ */
+#define SVN_INT64_BUFFER_SIZE 21
+
+/** Writes the @a number as string into @a dest. The latter must provide
+ * space for at least #SVN_INT64_BUFFER_SIZE characters. Returns the number
+ * chars written excluding the terminating NUL.
+ */
+apr_size_t
+svn__ui64toa(char * dest, apr_uint64_t number);
+
+/** Writes the @a number as string into @a dest. The latter must provide
+ * space for at least #SVN_INT64_BUFFER_SIZE characters. Returns the number
+ * chars written excluding the terminating NUL.
+ */
+apr_size_t
+svn__i64toa(char * dest, apr_int64_t number);
+
+/** Returns a decimal string for @a number allocated in @a pool. Put in
+ * the @a seperator at each third place.
+ */
+char *
+svn__ui64toa_sep(apr_uint64_t number, char seperator, apr_pool_t *pool);
+
+/** Returns a decimal string for @a number allocated in @a pool. Put in
+ * the @a seperator at each third place.
+ */
+char *
+svn__i64toa_sep(apr_int64_t number, char seperator, apr_pool_t *pool);
+
+/**
+ * Computes the similarity score of STRA and STRB. Returns the ratio
+ * of the length of their longest common subsequence and the average
+ * length of the strings, normalized to the range [0..1000].
+ * The result is equivalent to Python's
+ *
+ * difflib.SequenceMatcher.ratio
+ *
+ * Optionally sets *RLCS to the length of the longest common
+ * subsequence of STRA and STRB. Using BUFFER for temporary storage,
+ * requires memory proportional to the length of the shorter string.
+ *
+ * The LCS algorithm used is described in, e.g.,
+ *
+ * http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
+ *
+ * Q: Why another LCS when we already have one in libsvn_diff?
+ * A: svn_diff__lcs is too heavyweight and too generic for the
+ * purposes of similarity testing. Whilst it would be possible
+ * to use a character-based tokenizer with it, we really only need
+ * the *length* of the LCS for the similarity score, not all the
+ * other information that svn_diff__lcs produces in order to
+ * make printing diffs possible.
+ *
+ * Q: Is there a limit on the length of the string parameters?
+ * A: Only available memory. But note that the LCS algorithm used
+ * has O(strlen(STRA) * strlen(STRB)) worst-case performance,
+ * so do keep a rein on your enthusiasm.
+ */
+unsigned int
+svn_cstring__similarity(const char *stra, const char *strb,
+ svn_membuf_t *buffer, apr_size_t *rlcs);
+
+/**
+ * Like svn_cstring__similarity, but accepts svn_string_t's instead
+ * of NUL-terminated character strings.
+ */
+unsigned int
+svn_string__similarity(const svn_string_t *stringa,
+ const svn_string_t *stringb,
+ svn_membuf_t *buffer, apr_size_t *rlcs);
+
+
+/** @} */
+
+/** @} */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_STRING_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_subr_private.h b/contrib/subversion/subversion/include/private/svn_subr_private.h
new file mode 100644
index 0000000..a45e664
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_subr_private.h
@@ -0,0 +1,340 @@
+/*
+ * svn_subr_private.h : private definitions from libsvn_subr
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_SUBR_PRIVATE_H
+#define SVN_SUBR_PRIVATE_H
+
+#include "svn_types.h"
+#include "svn_io.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** Spill-to-file Buffers
+ *
+ * @defgroup svn_spillbuf_t Spill-to-file Buffers
+ * @{
+ */
+
+/** A buffer that collects blocks of content, possibly using a file.
+ *
+ * The spill-buffer is created with two basic parameters: the size of the
+ * blocks that will be written into the spill-buffer ("blocksize"), and
+ * the (approximate) maximum size that will be allowed in memory ("maxsize").
+ * Once the maxsize is reached, newly written content will be "spilled"
+ * into a temporary file.
+ *
+ * When writing, content will be buffered into memory unless a given write
+ * will cause the amount of in-memory content to exceed the specified
+ * maxsize. At that point, the file is created, and the content will be
+ * written to that file.
+ *
+ * To read information back out of a spill buffer, there are two approaches
+ * available to the application:
+ *
+ * *) reading blocks using svn_spillbuf_read() (a "pull" model)
+ * *) having blocks passed to a callback via svn_spillbuf_process()
+ * (a "push" model to your application)
+ *
+ * In both cases, the spill-buffer will provide you with a block of N bytes
+ * that you must fully consume before asking for more data. The callback
+ * style provides for a "stop" parameter to temporarily pause the reading
+ * until another read is desired. The two styles of reading may be mixed,
+ * as the caller desires. Generally, N will be the blocksize, and will be
+ * less when the end of the content is reached.
+ *
+ * For a more stream-oriented style of reading, where the caller specifies
+ * the number of bytes to read into a caller-provided buffer, please see
+ * svn_spillbuf_reader_t. That overlaid type will cause more memory copies
+ * to be performed (whereas the bare spill-buffer type hands you a buffer
+ * to consume).
+ *
+ * Writes may be interleaved with reading, and content will be returned
+ * in a FIFO manner. Thus, if content has been placed into the spill-buffer
+ * you will always read the earliest-written data, and any newly-written
+ * content will be appended to the buffer.
+ *
+ * Note: the file is created in the same pool where the spill-buffer was
+ * created. If the content is completely read from that file, it will be
+ * closed and deleted. Should writing further content cause another spill
+ * file to be created, that will increase the size of the pool. There is
+ * no bound on the amount of file-related resources that may be consumed
+ * from the pool. It is entirely related to the read/write pattern and
+ * whether spill files are repeatedly created.
+ */
+typedef struct svn_spillbuf_t svn_spillbuf_t;
+
+
+/* Create a spill buffer. */
+svn_spillbuf_t *
+svn_spillbuf__create(apr_size_t blocksize,
+ apr_size_t maxsize,
+ apr_pool_t *result_pool);
+
+
+/* Determine how much content is stored in the spill buffer. */
+svn_filesize_t
+svn_spillbuf__get_size(const svn_spillbuf_t *buf);
+
+
+/* Write some data into the spill buffer. */
+svn_error_t *
+svn_spillbuf__write(svn_spillbuf_t *buf,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool);
+
+
+/* Read a block of memory from the spill buffer. @a *data will be set to
+ NULL if no content remains. Otherwise, @a data and @a len will point to
+ data that must be fully-consumed by the caller. This data will remain
+ valid until another call to svn_spillbuf_write(), svn_spillbuf_read(),
+ or svn_spillbuf_process(), or if the spill buffer's pool is cleared. */
+svn_error_t *
+svn_spillbuf__read(const char **data,
+ apr_size_t *len,
+ svn_spillbuf_t *buf,
+ apr_pool_t *scratch_pool);
+
+
+/* Callback for reading content out of the spill buffer. Set @a stop if
+ you want to stop the processing (and will call svn_spillbuf_process
+ again, at a later time). */
+typedef svn_error_t * (*svn_spillbuf_read_t)(svn_boolean_t *stop,
+ void *baton,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool);
+
+
+/* Process the content stored in the spill buffer. @a exhausted will be
+ set to TRUE if all of the content is processed by @a read_func. This
+ function may return early if the callback returns TRUE for its 'stop'
+ parameter. */
+svn_error_t *
+svn_spillbuf__process(svn_boolean_t *exhausted,
+ svn_spillbuf_t *buf,
+ svn_spillbuf_read_t read_func,
+ void *read_baton,
+ apr_pool_t *scratch_pool);
+
+
+/** Classic stream reading layer on top of spill-buffers.
+ *
+ * This type layers upon a spill-buffer to enable a caller to read a
+ * specified number of bytes into the caller's provided buffer. This
+ * implies more memory copies than the standard spill-buffer reading
+ * interface, but is sometimes required by spill-buffer users.
+ */
+typedef struct svn_spillbuf_reader_t svn_spillbuf_reader_t;
+
+
+/* Create a spill-buffer and a reader for it. */
+svn_spillbuf_reader_t *
+svn_spillbuf__reader_create(apr_size_t blocksize,
+ apr_size_t maxsize,
+ apr_pool_t *result_pool);
+
+
+/* Read @a len bytes from @a reader into @a data. The number of bytes
+ actually read is stored in @a amt. If the content is exhausted, then
+ @a amt is set to zero. It will always be non-zero if the spill-buffer
+ contains content.
+
+ If @a len is zero, then SVN_ERR_INCORRECT_PARAMS is returned. */
+svn_error_t *
+svn_spillbuf__reader_read(apr_size_t *amt,
+ svn_spillbuf_reader_t *reader,
+ char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool);
+
+
+/* Read a single character from @a reader, and place it in @a c. If there
+ is no content in the spill-buffer, then SVN_ERR_STREAM_UNEXPECTED_EOF
+ is returned. */
+svn_error_t *
+svn_spillbuf__reader_getc(char *c,
+ svn_spillbuf_reader_t *reader,
+ apr_pool_t *scratch_pool);
+
+
+/* Write @a len bytes from @a data into the spill-buffer in @a reader. */
+svn_error_t *
+svn_spillbuf__reader_write(svn_spillbuf_reader_t *reader,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool);
+
+
+/* Return a stream built on top of a spillbuf, using the same arguments as
+ svn_spillbuf__create(). This stream can be used for reading and writing,
+ but implements the same basic sematics of a spillbuf for the underlying
+ storage. */
+svn_stream_t *
+svn_stream__from_spillbuf(apr_size_t blocksize,
+ apr_size_t maxsize,
+ apr_pool_t *result_pool);
+
+/** @} */
+
+/**
+ * Internal function for creating a MD5 checksum from a binary digest.
+ *
+ * @since New in 1.8
+ */
+svn_checksum_t *
+svn_checksum__from_digest_md5(const unsigned char *digest,
+ apr_pool_t *result_pool);
+
+/**
+ * Internal function for creating a SHA1 checksum from a binary
+ * digest.
+ *
+ * @since New in 1.8
+ */
+svn_checksum_t *
+svn_checksum__from_digest_sha1(const unsigned char *digest,
+ apr_pool_t *result_pool);
+
+
+/**
+ * @defgroup svn_hash_support Hash table serialization support
+ * @{
+ */
+
+/*----------------------------------------------------*/
+
+/**
+ * @defgroup svn_hash_misc Miscellaneous hash APIs
+ * @{
+ */
+
+/** @} */
+
+
+/**
+ * @defgroup svn_hash_getters Specialized getter APIs for hashes
+ * @{
+ */
+
+/** Find the value of a @a key in @a hash, return the value.
+ *
+ * If @a hash is @c NULL or if the @a key cannot be found, the
+ * @a default_value will be returned.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_hash__get_cstring(apr_hash_t *hash,
+ const char *key,
+ const char *default_value);
+
+/** Like svn_hash_get_cstring(), but for boolean values.
+ *
+ * Parses the value as a boolean value. The recognized representations
+ * are 'TRUE'/'FALSE', 'yes'/'no', 'on'/'off', '1'/'0'; case does not
+ * matter.
+ *
+ * @since New in 1.7.
+ */
+svn_boolean_t
+svn_hash__get_bool(apr_hash_t *hash,
+ const char *key,
+ svn_boolean_t default_value);
+
+/** @} */
+
+/**
+ * @defgroup svn_hash_create Create optimized APR hash tables
+ * @{
+ */
+
+/** Returns a hash table, allocated in @a pool, with the same ordering of
+ * elements as APR 1.4.5 or earlier (using apr_hashfunc_default) but uses
+ * a faster hash function implementation.
+ *
+ * @since New in 1.8.
+ */
+apr_hash_t *
+svn_hash__make(apr_pool_t *pool);
+
+/** @} */
+
+/** @} */
+
+
+/** Apply the changes described by @a prop_changes to @a original_props and
+ * return the result. The inverse of svn_prop_diffs().
+ *
+ * Allocate the resulting hash from @a pool, but allocate its keys and
+ * values from @a pool and/or by reference to the storage of the inputs.
+ *
+ * Note: some other APIs use an array of pointers to svn_prop_t.
+ *
+ * @since New in 1.8.
+ */
+apr_hash_t *
+svn_prop__patch(const apr_hash_t *original_props,
+ const apr_array_header_t *prop_changes,
+ apr_pool_t *pool);
+
+
+/**
+ * @defgroup svn_version Version number dotted triplet parsing
+ * @{
+ */
+
+/* Set @a *version to a version structure parsed from the version
+ * string representation in @a version_string. Return
+ * @c SVN_ERR_MALFORMED_VERSION_STRING if the string fails to parse
+ * cleanly.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_version__parse_version_string(svn_version_t **version,
+ const char *version_string,
+ apr_pool_t *result_pool);
+
+/* Return true iff @a version represents a version number of at least
+ * the level represented by @a major, @a minor, and @a patch.
+ *
+ * @since New in 1.8.
+ */
+svn_boolean_t
+svn_version__at_least(svn_version_t *version,
+ int major,
+ int minor,
+ int patch);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_SUBR_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_temp_serializer.h b/contrib/subversion/subversion/include/private/svn_temp_serializer.h
new file mode 100644
index 0000000..7a007c3
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_temp_serializer.h
@@ -0,0 +1,207 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_temp_serializer.h
+ * @brief Helper API for serializing _temporarily_ data structures.
+ *
+ * @note This API is intended for efficient serialization and duplication
+ * of temporary, e.g. cached, data structures ONLY. It is not
+ * suitable for persistent data.
+ */
+
+#ifndef SVN_TEMP_SERIALIZER_H
+#define SVN_TEMP_SERIALIZER_H
+
+#include "svn_string.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* forward declaration */
+struct svn_stringbuf_t;
+
+/**
+ * The amount of extra memory allocated by #svn_temp_serializer__init for
+ * the internal buffer in addition to its suggested_buffer_size parameter.
+ * To allocate a 512 buffer, including overhead, just specify a size of
+ * 512 - SVN_TEMP_SERIALIZER__OVERHEAD.
+ */
+#define SVN_TEMP_SERIALIZER__OVERHEAD (sizeof(svn_stringbuf_t) + 1)
+
+/**
+ * Opaque structure controlling the serialization process and holding the
+ * intermediate as well as final results.
+ */
+typedef struct svn_temp_serializer__context_t svn_temp_serializer__context_t;
+
+/**
+ * Begin the serialization process for the @a source_struct and all objects
+ * referenced from it. @a struct_size must match the result of @c sizeof()
+ * of the actual structure. Due to the generic nature of the init function
+ * we can't determine the structure size as part of the function.
+ *
+ * It is possible to specify a @c NULL source_struct in which case the first
+ * call to svn_temp_serializer__push() will provide the root struct.
+ * Alternatively, one may even call svn_temp_serializer__add_string()
+ * but there is generally no point in doing so because the result will be
+ * simple string object in a #svn_stringbuf_t.
+ *
+ * You may suggest a larger initial buffer size in @a suggested_buffer_size
+ * to minimize the number of internal buffer re-allocations during the
+ * serialization process. All allocations will be made from @a pool.
+ *
+ * Pointers within the structure will be replaced by their serialized
+ * representation when the respective strings or sub-structures get
+ * serialized. This scheme allows only for tree-like, i.e. non-circular
+ * data structures.
+ *
+ * @return the serialization context.
+ */
+svn_temp_serializer__context_t *
+svn_temp_serializer__init(const void *source_struct,
+ apr_size_t struct_size,
+ apr_size_t suggested_buffer_size,
+ apr_pool_t *pool);
+
+/**
+ * Continue the serialization process of the @a source_struct that has
+ * already been serialized to @a buffer but contains references to new
+ * objects yet to serialize. I.e. this function allows you to append
+ * data to serialized structures returned by svn_temp_serializer__get().
+ *
+ * The current size of the serialized data is given in @a currently_used.
+ * If the allocated data buffer is actually larger, you may specifiy that
+ * size in @a currently_allocated to prevent unnecessary re-allocations.
+ * Otherwise, set it to 0.
+ *
+ * All allocations will be made from @a pool.
+ *
+ * Please note that only sub-structures of @a source_struct may be added.
+ * To add item referenced from other parts of the buffer, serialize from
+ * @a source_struct first, get the result from svn_temp_serializer__get()
+ * and call svn_temp_serializer__init_append for the next part.
+ *
+ * @return the serialization context.
+ */
+svn_temp_serializer__context_t *
+svn_temp_serializer__init_append(void *buffer,
+ void *source_struct,
+ apr_size_t currently_used,
+ apr_size_t currently_allocated,
+ apr_pool_t *pool);
+
+/**
+ * Begin serialization of a referenced sub-structure within the
+ * serialization @a context. @a source_struct must be a reference to the
+ * pointer in the original parent structure so that the correspondence in
+ * the serialized structure can be established. @a struct_size must match
+ * the result of @c sizeof() of the actual structure.
+ *
+ * Only in case that svn_temp_serializer__init() has not been provided
+ * with a root structure and this is the first call after the initialization,
+ * @a source_struct will point to a reference to the root structure instead
+ * of being related to some other.
+ *
+ * Sub-structures and strings will be added in a FIFO fashion. If you need
+ * add further sub-structures on the same level, you need to call
+ * svn_serializer__pop() to realign the serialization context.
+ */
+void
+svn_temp_serializer__push(svn_temp_serializer__context_t *context,
+ const void * const * source_struct,
+ apr_size_t struct_size);
+
+/**
+ * End the serialization of the current sub-structure. The serialization
+ * @a context will be focused back on the parent structure. You may then
+ * add further sub-structures starting from that level.
+ *
+ * It is not necessary to call this function just for symmetry at the end
+ * of the serialization process.
+ */
+void
+svn_temp_serializer__pop(svn_temp_serializer__context_t *context);
+
+/**
+ * Serialize a string referenced from the current structure within the
+ * serialization @a context. @a s must be a reference to the @c char*
+ * pointer in the original structure so that the correspondence in the
+ * serialized structure can be established.
+ *
+ * Only in case that svn_temp_serializer__init() has not been provided
+ * with a root structure and this is the first call after the initialization,
+ * @a s will not be related to some struct.
+ */
+void
+svn_temp_serializer__add_string(svn_temp_serializer__context_t *context,
+ const char * const * s);
+
+/**
+ * Set the serialized representation of the pointer @a ptr inside the
+ * current structure within the serialization @a context to @c NULL.
+ * This is particularly useful if the pointer is not @c NULL in the
+ * source structure.
+ */
+void
+svn_temp_serializer__set_null(svn_temp_serializer__context_t *context,
+ const void * const * ptr);
+
+/**
+ * @return the number of bytes currently used in the serialization buffer
+ * of the given serialization @a context.
+ */
+apr_size_t
+svn_temp_serializer__get_length(svn_temp_serializer__context_t *context);
+
+/**
+ * @return a reference to the data buffer containing the data serialialized
+ * so far in the given serialization @a context.
+ */
+struct svn_stringbuf_t *
+svn_temp_serializer__get(svn_temp_serializer__context_t *context);
+
+/**
+ * Deserialization is straightforward: just copy the serialized buffer to
+ * a natively aligned memory location (APR pools will take care of that
+ * automatically) and resolve all pointers to sub-structures.
+ *
+ * To do the latter, call this function for each of these pointers, giving
+ * the start address of the copied buffer in @a buffer and a reference to
+ * the pointer to resolve in @a ptr.
+ */
+void
+svn_temp_deserializer__resolve(void *buffer, void **ptr);
+
+/**
+ * Similar to svn_temp_deserializer__resolve() but instead of modifying
+ * the buffer content, the resulting pointer is passed back to the caller
+ * as the return value.
+ */
+const void *
+svn_temp_deserializer__ptr(const void *buffer, const void *const *ptr);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_TEMP_SERIALIZER_H */
diff --git a/contrib/subversion/subversion/include/private/svn_token.h b/contrib/subversion/subversion/include/private/svn_token.h
new file mode 100644
index 0000000..c7c1c2c
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_token.h
@@ -0,0 +1,98 @@
+/* svn_token.h : value/string-token functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_TOKEN_H
+#define SVN_TOKEN_H
+
+
+#include "svn_error.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** A mapping between a string STR and an enumeration value VAL.
+ *
+ * Maps are an array of these, terminated with a struct where STR == NULL.
+ */
+typedef struct svn_token_map_t
+{
+ const char *str;
+ int val;
+} svn_token_map_t;
+
+
+/* A value used by some token functions to indicate an unrecognized token. */
+#define SVN_TOKEN_UNKNOWN (-9999)
+
+
+/* Return the string form of the given VALUE as found in MAP. If the value
+ is not recognized, then a MALFUNCTION will occur. */
+const char *
+svn_token__to_word(const svn_token_map_t *map,
+ int value);
+
+
+/* NOTE: in the following functions, if WORD is NULL, then SVN_TOKEN_UNKNOWN
+ will be returned, or will cause the appropriate MALFUNCTION or ERROR. */
+
+/* Return the integer value of the given token WORD, as found in MAP. If the
+ string is not recognized, then a MALFUNCTION will occur.
+
+ Note: this function is for persisted string values. Because this function
+ will throw a MALFUNCTION, it should not be used for network input or
+ user input. */
+int
+svn_token__from_word_strict(const svn_token_map_t *map,
+ const char *word);
+
+
+/* Store the integer value of WORD into *VALUE. If the string is not
+ recognized, then SVN_ERR_BAD_TOKEN is returned. */
+svn_error_t *
+svn_token__from_word_err(int *value,
+ const svn_token_map_t *map,
+ const char *word);
+
+
+/* Return the integer value of the given token WORD as found in MAP. If the
+ string is not recognized, then SVN_TOKEN_UNKNOWN will be returned. */
+int
+svn_token__from_word(const svn_token_map_t *map,
+ const char *word);
+
+
+/* Return the integer value of the given token WORD/LEN as found in MAP. If
+ the string is not recognized, then SVN_TOKEN_UNKNOWN will be returned. */
+int
+svn_token__from_mem(const svn_token_map_t *map,
+ const char *word,
+ apr_size_t len);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_TOKEN_H */
diff --git a/contrib/subversion/subversion/include/private/svn_utf_private.h b/contrib/subversion/subversion/include/private/svn_utf_private.h
new file mode 100644
index 0000000..9f5a4ad
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_utf_private.h
@@ -0,0 +1,87 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_utf_private.h
+ * @brief UTF validation routines
+ */
+
+#ifndef SVN_UTF_PRIVATE_H
+#define SVN_UTF_PRIVATE_H
+
+#include <apr.h>
+#include <apr_pools.h>
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Return TRUE if the string SRC of length LEN is a valid UTF-8 encoding
+ * according to the rules laid down by the Unicode 4.0 standard, FALSE
+ * otherwise. This function is faster than svn_utf__last_valid().
+ */
+svn_boolean_t
+svn_utf__is_valid(const char *src, apr_size_t len);
+
+/* As for svn_utf__is_valid but SRC is NULL terminated. */
+svn_boolean_t
+svn_utf__cstring_is_valid(const char *src);
+
+/* Return a pointer to the first character after the last valid UTF-8
+ * potentially multi-byte character in the string SRC of length LEN.
+ * Validity of bytes from SRC to SRC+LEN-1, inclusively, is checked.
+ * If SRC is a valid UTF-8, the return value will point to the byte SRC+LEN,
+ * otherwise it will point to the start of the first invalid character.
+ * In either case all the characters between SRC and the return pointer - 1,
+ * inclusively, are valid UTF-8.
+ *
+ * See also svn_utf__is_valid().
+ */
+const char *
+svn_utf__last_valid(const char *src, apr_size_t len);
+
+/* As for svn_utf__last_valid but uses a different implementation without
+ lookup tables. It avoids the table memory use (about 400 bytes) but the
+ function is longer (about 200 bytes extra) and likely to be slower when
+ the string is valid. If the string is invalid this function may be
+ faster since it returns immediately rather than continuing to the end of
+ the string. The main reason this function exists is to test the table
+ driven implementation. */
+const char *
+svn_utf__last_valid2(const char *src, apr_size_t len);
+
+const char *
+svn_utf__cstring_from_utf8_fuzzy(const char *src,
+ apr_pool_t *pool,
+ svn_error_t *(*convert_from_utf8)
+ (const char **,
+ const char *,
+ apr_pool_t *));
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_UTF_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/private/svn_wc_private.h b/contrib/subversion/subversion/include/private/svn_wc_private.h
new file mode 100644
index 0000000..fce42b0
--- /dev/null
+++ b/contrib/subversion/subversion/include/private/svn_wc_private.h
@@ -0,0 +1,1847 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_wc_private.h
+ * @brief The Subversion Working Copy Library - Internal routines
+ *
+ * Requires:
+ * - A working copy
+ *
+ * Provides:
+ * - Ability to manipulate working copy's versioned data.
+ * - Ability to manipulate working copy's administrative files.
+ *
+ * Used By:
+ * - Clients.
+ */
+
+#ifndef SVN_WC_PRIVATE_H
+#define SVN_WC_PRIVATE_H
+
+#include "svn_types.h"
+#include "svn_wc.h"
+#include "private/svn_diff_tree.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Return TRUE iff CLHASH (a hash whose keys are const char *
+ changelist names) is NULL or if LOCAL_ABSPATH is part of a changelist in
+ CLHASH. */
+svn_boolean_t
+svn_wc__changelist_match(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const apr_hash_t *clhash,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc_get_update_editorX and svn_wc_get_status_editorX, but only
+ allows updating a file external LOCAL_ABSPATH.
+
+ Since this only deals with files, the WCROOT_IPROPS argument in
+ svn_wc_get_update_editorX and svn_wc_get_status_editorX (hashes mapping
+ const char * absolute working copy paths, which are working copy roots, to
+ depth-first ordered arrays of svn_prop_inherited_item_t * structures) is
+ simply IPROPS here, a depth-first ordered arrays of
+ svn_prop_inherited_item_t * structs. */
+svn_error_t *
+svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *url,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ apr_array_header_t *iprops,
+ svn_boolean_t use_commit_times,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ const char *record_ancestor_abspath,
+ const char *recorded_url,
+ const svn_opt_revision_t *recorded_peg_rev,
+ const svn_opt_revision_t *recorded_rev,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc_crawl_revisionsX, but only supports updating a file external
+ LOCAL_ABSPATH which may or may not exist yet. */
+svn_error_t *
+svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_ra_reporter3_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/* Check if LOCAL_ABSPATH is an external in the working copy identified
+ by WRI_ABSPATH. If not return SVN_ERR_WC_PATH_NOT_FOUND.
+
+ If it is an external return more information on this external.
+
+ If IGNORE_ENOENT, then set *external_kind to svn_node_none, when
+ LOCAL_ABSPATH is not an external instead of returning an error.
+
+ Here is an overview of how DEFINING_REVISION and
+ DEFINING_OPERATIONAL_REVISION would be set for which kinds of externals
+ definitions:
+
+ svn:externals line DEFINING_REV. DEFINING_OP._REV.
+
+ ^/foo@2 bar 2 2
+ -r1 ^/foo@2 bar 1 2
+ -r1 ^/foo bar 1 SVN_INVALID_REVNUM
+ ^/foo bar SVN_INVALID_REVNUM SVN_INVALID_REVNUM
+ ^/foo@HEAD bar SVN_INVALID_REVNUM SVN_INVALID_REVNUM
+ -rHEAD ^/foo bar -- not a valid externals definition --
+*/
+svn_error_t *
+svn_wc__read_external_info(svn_node_kind_t *external_kind,
+ const char **defining_abspath,
+ const char **defining_url,
+ svn_revnum_t *defining_operational_revision,
+ svn_revnum_t *defining_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** See svn_wc__committable_externals_below(). */
+typedef struct svn_wc__committable_external_info_t {
+
+ /* The local absolute path where the external should be checked out. */
+ const char *local_abspath;
+
+ /* The relpath part of the source URL the external should be checked out
+ * from. */
+ const char *repos_relpath;
+
+ /* The root URL part of the source URL the external should be checked out
+ * from. */
+ const char *repos_root_url;
+
+ /* Set to either svn_node_file or svn_node_dir. */
+ svn_node_kind_t kind;
+
+} svn_wc__committable_external_info_t;
+
+/* Add svn_wc__committable_external_info_t* items to *EXTERNALS, describing
+ * 'committable' externals checked out below LOCAL_ABSPATH. Recursively find
+ * all nested externals (externals defined inside externals).
+ *
+ * In this context, a 'committable' external belongs to the same repository as
+ * LOCAL_ABSPATH, is not revision-pegged and is currently checked out in the
+ * WC. (Local modifications are not tested for.)
+ *
+ * *EXTERNALS must be initialized either to NULL or to a pointer created with
+ * apr_array_make(..., sizeof(svn_wc__committable_external_info_t *)). If
+ * *EXTERNALS is initialized to NULL, an array will be allocated from
+ * RESULT_POOL as necessary. If no committable externals are found,
+ * *EXTERNALS is left unchanged.
+ *
+ * DEPTH limits the recursion below LOCAL_ABSPATH.
+ *
+ * This function will not find externals defined in some parent WC above
+ * LOCAL_ABSPATH's WC-root.
+ *
+ * ###TODO: Add a WRI_ABSPATH (wc root indicator) separate from LOCAL_ABSPATH,
+ * to allow searching any wc-root for externals under LOCAL_ABSPATH, not only
+ * LOCAL_ABSPATH's most immediate wc-root. */
+svn_error_t *
+svn_wc__committable_externals_below(apr_array_header_t **externals,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gets a mapping from const char * local abspaths of externals to the const
+ char * local abspath of where they are defined for all externals defined
+ at or below LOCAL_ABSPATH.
+
+ ### Returns NULL in *EXTERNALS until we bumped to format 29.
+
+ Allocate the result in RESULT_POOL and perform temporary allocations in
+ SCRATCH_POOL. */
+svn_error_t *
+svn_wc__externals_defined_below(apr_hash_t **externals,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Registers a new external at LOCAL_ABSPATH in the working copy containing
+ DEFINING_ABSPATH.
+
+ The node is registered as defined on DEFINING_ABSPATH (must be an ancestor
+ of LOCAL_ABSPATH) of kind KIND.
+
+ The external is registered as from repository REPOS_ROOT_URL with uuid
+ REPOS_UUID and the defining relative path REPOS_RELPATH.
+
+ If the revision of the node is locked OPERATIONAL_REVISION and REVISION
+ are the peg and normal revision; otherwise their value is
+ SVN_INVALID_REVNUM.
+
+ ### Only KIND svn_node_dir is supported.
+
+ Perform temporary allocations in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__external_register(svn_wc_context_t *wc_ctx,
+ const char *defining_abspath,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ const char *repos_relpath,
+ svn_revnum_t operational_revision,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool);
+
+/* Remove the external at LOCAL_ABSPATH from the working copy identified by
+ WRI_ABSPATH using WC_CTX.
+
+ If DECLARATION_ONLY is TRUE, only remove the registration and leave the
+ on-disk structure untouched.
+
+ If not NULL, call CANCEL_FUNC with CANCEL_BATON to allow canceling while
+ removing the working copy files.
+
+ ### This function wraps svn_wc_remove_from_revision_control2().
+ */
+svn_error_t *
+svn_wc__external_remove(svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_boolean_t declaration_only,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Gather all svn:externals property values from the actual properties on
+ directories below LOCAL_ABSPATH as a mapping of const char *local_abspath
+ to const char * values.
+
+ Use DEPTH as how it would be used to limit the externals property results
+ on update. (So any depth < infinity will only read svn:externals on
+ LOCAL_ABSPATH itself)
+
+ If DEPTHS is not NULL, set *depths to an apr_hash_t* mapping the same
+ local_abspaths to the const char * ambient depth of the node.
+
+ Allocate the result in RESULT_POOL and perform temporary allocations in
+ SCRATCH_POOL. */
+svn_error_t *
+svn_wc__externals_gather_definitions(apr_hash_t **externals,
+ apr_hash_t **ambient_depths,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Close the DB for LOCAL_ABSPATH. Perform temporary allocations in
+ SCRATCH_POOL.
+
+ Wraps svn_wc__db_drop_root(). */
+svn_error_t *
+svn_wc__close_db(const char *external_abspath,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *scratch_pool);
+
+/** Set @a *tree_conflict to a newly allocated @c
+ * svn_wc_conflict_description_t structure describing the tree
+ * conflict state of @a victim_abspath, or to @c NULL if @a victim_abspath
+ * is not in a state of tree conflict. @a wc_ctx is a working copy context
+ * used to access @a victim_path. Allocate @a *tree_conflict in @a result_pool,
+ * use @a scratch_pool for temporary allocations.
+ */
+svn_error_t *
+svn_wc__get_tree_conflict(const svn_wc_conflict_description2_t **tree_conflict,
+ svn_wc_context_t *wc_ctx,
+ const char *victim_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Record the tree conflict described by @a conflict in the WC for
+ * @a conflict->local_abspath. Use @a scratch_pool for all temporary
+ * allocations.
+ *
+ * Returns an SVN_ERR_WC_PATH_UNEXPECTED_STATUS error when
+ * CONFLICT->LOCAL_ABSPATH is already tree conflicted.
+ *
+ * ### This function can't set moved_away, moved_here conflicts for
+ * any operation, except merges.
+ */
+svn_error_t *
+svn_wc__add_tree_conflict(svn_wc_context_t *wc_ctx,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *scratch_pool);
+
+/* Remove any tree conflict on victim @a victim_abspath using @a wc_ctx.
+ * (If there is no such conflict recorded, do nothing and return success.)
+ *
+ * Do all temporary allocations in @a scratch_pool.
+ */
+svn_error_t *
+svn_wc__del_tree_conflict(svn_wc_context_t *wc_ctx,
+ const char *victim_abspath,
+ apr_pool_t *scratch_pool);
+
+/** Check whether LOCAL_ABSPATH has a parent directory that knows about its
+ * existence. Set *IS_WCROOT to FALSE if a parent is found, and to TRUE
+ * if there is no such parent.
+ *
+ * Like svn_wc_is_wc_root2(), but doesn't consider switched subdirs or
+ * deleted entries as working copy roots.
+ */
+svn_error_t *
+svn_wc__is_wcroot(svn_boolean_t *is_wcroot,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/** Set @a *wcroot_abspath to the local abspath of the root of the
+ * working copy in which @a local_abspath resides.
+ */
+svn_error_t *
+svn_wc__get_wcroot(const char **wcroot_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * The following are temporary APIs to aid in the transition from wc-1 to
+ * wc-ng. Use them for new development now, but they may be disappearing
+ * before the 1.7 release.
+ */
+
+
+/*
+ * Convert from svn_wc_conflict_description2_t to
+ * svn_wc_conflict_description_t. This is needed by some backwards-compat
+ * code in libsvn_client/ctx.c
+ *
+ * Allocate the result in RESULT_POOL.
+ */
+svn_wc_conflict_description_t *
+svn_wc__cd2_to_cd(const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *result_pool);
+
+
+/*
+ * Convert from svn_wc_status3_t to svn_wc_status2_t.
+ * Allocate the result in RESULT_POOL.
+ */
+svn_error_t *
+svn_wc__status2_from_3(svn_wc_status2_t **status,
+ const svn_wc_status3_t *old_status,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Set @a *children to a new array of the immediate children of the working
+ * node at @a dir_abspath. The elements of @a *children are (const char *)
+ * absolute paths.
+ *
+ * Include children that are scheduled for deletion. Iff @a show_hidden
+ * is true, also include children that are 'excluded' or 'server-excluded' or
+ * 'not-present'.
+ *
+ * Return every path that refers to a child of the working node at
+ * @a dir_abspath. Do not include a path just because it was a child of a
+ * deleted directory that existed at @a dir_abspath if that directory is now
+ * sheduled to be replaced by the working node at @a dir_abspath.
+ *
+ * Allocate @a *children in @a result_pool. Use @a wc_ctx to access the
+ * working copy, and @a scratch_pool for all temporary allocations.
+ */
+svn_error_t *
+svn_wc__node_get_children_of_working_node(const apr_array_header_t **children,
+ svn_wc_context_t *wc_ctx,
+ const char *dir_abspath,
+ svn_boolean_t show_hidden,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Like svn_wc__node_get_children_of_working_node(), except also include any
+ * path that was a child of a deleted directory that existed at
+ * @a dir_abspath, even if that directory is now scheduled to be replaced by
+ * the working node at @a dir_abspath.
+ */
+svn_error_t *
+svn_wc__node_get_children(const apr_array_header_t **children,
+ svn_wc_context_t *wc_ctx,
+ const char *dir_abspath,
+ svn_boolean_t show_hidden,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Fetch the repository information for the working version
+ * of the node at @a local_abspath into @a *revision, @a *repos_relpath,
+ * @a *repos_root_url and @a *repos_uuid. Use @a wc_ctx to access the working
+ * copy. Allocate results in @a result_pool.
+ *
+ * @a *revision will be set to SVN_INVALID_REVNUM for any shadowed node (including
+ * added and deleted nodes). All other output values will be set to the current
+ * values or those they would have after a commit.
+ *
+ * All output argument may be NULL, indicating no interest.
+ */
+svn_error_t *
+svn_wc__node_get_repos_info(svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+
+/**
+ * Get the depth of @a local_abspath using @a wc_ctx. If @a local_abspath is
+ * not in the working copy, return @c SVN_ERR_WC_PATH_NOT_FOUND.
+ */
+svn_error_t *
+svn_wc__node_get_depth(svn_depth_t *depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Get the changed revision, date and author for @a local_abspath using @a
+ * wc_ctx. Allocate the return values in @a result_pool; use @a scratch_pool
+ * for temporary allocations. Any of the return pointers may be @c NULL, in
+ * which case they are not set.
+ *
+ * If @a local_abspath is not in the working copy, return
+ * @c SVN_ERR_WC_PATH_NOT_FOUND.
+ */
+svn_error_t *
+svn_wc__node_get_changed_info(svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Set @a *url to the corresponding url for @a local_abspath, using @a wc_ctx.
+ * If the node is added, return the url it will have in the repository.
+ *
+ * If @a local_abspath is not in the working copy, return
+ * @c SVN_ERR_WC_PATH_NOT_FOUND.
+ */
+svn_error_t *
+svn_wc__node_get_url(const char **url,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Retrieves the origin of the node as it is known in the repository. For
+ * a copied node this retrieves where the node is copied from, for an added
+ * node this returns NULL/INVALID outputs, and for any other node this
+ * retrieves the repository location.
+ *
+ * All output arguments may be NULL.
+ *
+ * If @a is_copy is not NULL, sets @a *is_copy to TRUE if the origin is a copy
+ * of the original node.
+ *
+ * If not NULL, sets @a revision, @a repos_relpath, @a repos_root_url and
+ * @a repos_uuid to the original (if a copy) or their current values.
+ *
+ * If @a copy_root_abspath is not NULL, and @a *is_copy indicates that the
+ * node was copied, set @a *copy_root_abspath to the local absolute path of
+ * the root of the copied subtree containing the node. If the copied node is
+ * a root by itself, @a *copy_root_abspath will match @a local_abspath (but
+ * won't necessarily point to the same string in memory).
+ *
+ * If @a scan_deleted is TRUE, determine the origin of the deleted node. If
+ * @a scan_deleted is FALSE, return NULL, SVN_INVALID_REVNUM or FALSE for
+ * deleted nodes.
+ *
+ * Allocate the result in @a result_pool. Perform temporary allocations in
+ * @a scratch_pool */
+svn_error_t *
+svn_wc__node_get_origin(svn_boolean_t *is_copy,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **copy_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t scan_deleted,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Set @a *is_deleted to TRUE if @a local_abspath is deleted, using
+ * @a wc_ctx. If @a local_abspath is not in the working copy, return
+ * @c SVN_ERR_WC_PATH_NOT_FOUND. Use @a scratch_pool for all temporary
+ * allocations.
+ */
+svn_error_t *
+svn_wc__node_is_status_deleted(svn_boolean_t *is_deleted,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Set @a *deleted_ancestor_abspath to the root of the delete operation
+ * that deleted @a local_abspath. If @a local_abspath itself was deleted
+ * and has no deleted ancestor, @a *deleted_ancestor_abspath will equal
+ * @a local_abspath. If @a local_abspath was not deleted,
+ * set @a *deleted_ancestor_abspath to @c NULL.
+ *
+ * A node is considered 'deleted' if it is deleted or moved-away, and is
+ * not replaced.
+ *
+ * @a *deleted_ancestor_abspath is allocated in @a result_pool.
+ * Use @a scratch_pool for all temporary allocations.
+ */
+svn_error_t *
+svn_wc__node_get_deleted_ancestor(const char **deleted_ancestor_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Set @a *not_present to TRUE when @a local_abspath has status
+ * svn_wc__db_status_not_present. Set @a *user_excluded to TRUE when
+ * @a local_abspath has status svn_wc__db_status_excluded. Set
+ * @a *server_excluded to TRUE when @a local_abspath has status
+ * svn_wc__db_status_server_excluded. Otherwise set these values to FALSE.
+ * If @a base_only is TRUE then only the base node will be examined,
+ * otherwise the current base or working node will be examined.
+ *
+ * If a value is not interesting you can pass #NULL.
+ *
+ * If @a local_abspath is not in the working copy, return
+ * @c SVN_ERR_WC_PATH_NOT_FOUND. Use @a scratch_pool for all temporary
+ * allocations.
+ */
+svn_error_t *
+svn_wc__node_is_not_present(svn_boolean_t *not_present,
+ svn_boolean_t *user_excluded,
+ svn_boolean_t *server_excluded,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t base_only,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Set @a *is_added to whether @a local_abspath is added, using
+ * @a wc_ctx. If @a local_abspath is not in the working copy, return
+ * @c SVN_ERR_WC_PATH_NOT_FOUND. Use @a scratch_pool for all temporary
+ * allocations.
+ *
+ * NOTE: "added" in this sense, means it was added, copied-here, or
+ * moved-here. This function provides NO information on whether this
+ * addition has replaced another node.
+ *
+ * To be clear, this does NOT correspond to svn_wc_schedule_add.
+ */
+svn_error_t *
+svn_wc__node_is_added(svn_boolean_t *is_added,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Set @a *has_working to whether @a local_abspath has a working node (which
+ * might shadow BASE nodes)
+ *
+ * This is a check similar to status = added or status = deleted.
+ */
+svn_error_t *
+svn_wc__node_has_working(svn_boolean_t *has_working,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Get the repository location of the base node at @a local_abspath.
+ *
+ * Set *REVISION, *REPOS_RELPATH, *REPOS_ROOT_URL *REPOS_UUID and *LOCK_TOKEN
+ * to the location that this node was checked out at or last updated/switched
+ * to, regardless of any uncommitted changes (delete, replace and/or copy-here/
+ * move-here).
+ *
+ * If there is no BASE node at @a local_abspath or if @a show_hidden is FALSE,
+ * no status 'normal' or 'incomplete' BASE node report
+ * SVN_ERR_WC_PATH_NOT_FOUND, or if @a ignore_enoent is TRUE, @a kind
+ * svn_node_unknown, @a revision SVN_INVALID_REVNUM and all other values NULL.
+ *
+ * All output arguments may be NULL.
+ *
+ * Allocate the results in @a result_pool. Perform temporary allocations in
+ * @a scratch_pool.
+ */
+svn_error_t *
+svn_wc__node_get_base(svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **lock_token,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t ignore_enoent,
+ svn_boolean_t show_hidden,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Get the working revision of @a local_abspath using @a wc_ctx. If @a
+ * local_abspath is not in the working copy, return @c
+ * SVN_ERR_WC_PATH_NOT_FOUND.
+ *
+ * This function is meant as a temporary solution for using the old-style
+ * semantics of entries. It will handle any uncommitted changes (delete,
+ * replace and/or copy-here/move-here).
+ *
+ * For a delete the @a revision is the BASE node of the operation root, e.g
+ * the path that was deleted. But if the delete is below an add, the
+ * revision is set to SVN_INVALID_REVNUM. For an add, copy or move we return
+ * SVN_INVALID_REVNUM. In case of a replacement, we return the BASE
+ * revision.
+ *
+ * The @a changed_rev is set to the latest committed change to @a
+ * local_abspath before or equal to @a revision, unless the node is
+ * copied-here or moved-here. Then it is the revision of the latest committed
+ * change before or equal to the copyfrom_rev. NOTE, that we use
+ * SVN_INVALID_REVNUM for a scheduled copy or move.
+ *
+ * The @a changed_date and @a changed_author are the ones associated with @a
+ * changed_rev.
+ */
+svn_error_t *
+svn_wc__node_get_pre_ng_status_data(svn_revnum_t *revision,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Acquire a recursive write lock for @a local_abspath. If @a lock_anchor
+ * is true, determine if @a local_abspath has an anchor that should be locked
+ * instead; otherwise, @a local_abspath must be a versioned directory.
+ * Store the obtained lock in @a wc_ctx.
+ *
+ * If @a lock_root_abspath is not NULL, store the root of the lock in
+ * @a *lock_root_abspath. If @a lock_root_abspath is NULL, then @a
+ * lock_anchor must be FALSE.
+ *
+ * Returns @c SVN_ERR_WC_LOCKED if an existing lock is encountered, in
+ * which case any locks acquired will have been released.
+ *
+ * If @a lock_anchor is TRUE and @a lock_root_abspath is not NULL, @a
+ * lock_root_abspath will be set even when SVN_ERR_WC_LOCKED is returned.
+ */
+svn_error_t *
+svn_wc__acquire_write_lock(const char **lock_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t lock_anchor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Recursively release write locks for @a local_abspath, using @a wc_ctx
+ * for working copy access. Only locks held by @a wc_ctx are released.
+ * Locks are not removed if work queue items are present.
+ *
+ * If @a local_abspath is not the root of an owned SVN_ERR_WC_NOT_LOCKED
+ * is returned.
+ */
+svn_error_t *
+svn_wc__release_write_lock(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/** A callback invoked by the svn_wc__call_with_write_lock() function. */
+typedef svn_error_t *(*svn_wc__with_write_lock_func_t)(void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Call function @a func while holding a write lock on
+ * @a local_abspath. The @a baton, and @a result_pool and
+ * @a scratch_pool, is passed @a func.
+ *
+ * If @a lock_anchor is TRUE, determine if @a local_abspath has an anchor
+ * that should be locked instead.
+ *
+ * Use @a wc_ctx for working copy access.
+ * The lock is guaranteed to be released after @a func returns.
+ */
+svn_error_t *
+svn_wc__call_with_write_lock(svn_wc__with_write_lock_func_t func,
+ void *baton,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t lock_anchor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Evaluate the expression @a expr while holding a write lock on
+ * @a local_abspath.
+ *
+ * @a expr must yield an (svn_error_t *) error code. If the error code
+ * is not #SVN_NO_ERROR, cause the function using this macro to return
+ * the error to its caller.
+ *
+ * If @a lock_anchor is TRUE, determine if @a local_abspath has an anchor
+ * that should be locked instead.
+ *
+ * Use @a wc_ctx for working copy access.
+ *
+ * The lock is guaranteed to be released after evaluating @a expr.
+ */
+#define SVN_WC__CALL_WITH_WRITE_LOCK(expr, wc_ctx, local_abspath, \
+ lock_anchor, scratch_pool) \
+ do { \
+ svn_error_t *svn_wc__err1, *svn_wc__err2; \
+ const char *svn_wc__lock_root_abspath; \
+ SVN_ERR(svn_wc__acquire_write_lock(&svn_wc__lock_root_abspath, wc_ctx, \
+ local_abspath, lock_anchor, \
+ scratch_pool, scratch_pool)); \
+ svn_wc__err1 = (expr); \
+ svn_wc__err2 = svn_wc__release_write_lock( \
+ wc_ctx, svn_wc__lock_root_abspath, scratch_pool); \
+ SVN_ERR(svn_error_compose_create(svn_wc__err1, svn_wc__err2)); \
+ } while (0)
+
+
+/**
+ * Calculates the schedule and copied status of a node as that would
+ * have been stored in an svn_wc_entry_t instance.
+ *
+ * If not @c NULL, @a schedule and @a copied are set to their calculated
+ * values.
+ */
+svn_error_t *
+svn_wc__node_get_schedule(svn_wc_schedule_t *schedule,
+ svn_boolean_t *copied,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/** A callback invoked by svn_wc__prop_list_recursive().
+ * It is equivalent to svn_proplist_receiver_t declared in svn_client.h,
+ * but kept private within the svn_wc__ namespace because it is used within
+ * the bowels of libsvn_wc which don't include svn_client.h.
+ *
+ * @since New in 1.7. */
+typedef svn_error_t *(*svn_wc__proplist_receiver_t)(void *baton,
+ const char *local_abspath,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool);
+
+/** Call @a receiver_func, passing @a receiver_baton, an absolute path, and
+ * a hash table mapping <tt>const char *</tt> names onto <tt>const
+ * svn_string_t *</tt> values for all the regular properties of the node
+ * at @a local_abspath and any node beneath @a local_abspath within the
+ * specified @a depth. @a receiver_fun must not be NULL.
+ *
+ * If @a propname is not NULL, the passed hash table will only contain
+ * the property @a propname.
+ *
+ * If @a pristine is not @c TRUE, and @a base_props is FALSE show local
+ * modifications to the properties.
+ *
+ * If a node has no properties, @a receiver_func is not called for the node.
+ *
+ * If @a changelists are non-NULL and non-empty, filter by them.
+ *
+ * Use @a wc_ctx to access the working copy, and @a scratch_pool for
+ * temporary allocations.
+ *
+ * If the node at @a local_abspath does not exist,
+ * #SVN_ERR_WC_PATH_NOT_FOUND is returned.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ svn_depth_t depth,
+ svn_boolean_t pristine,
+ const apr_array_header_t *changelists,
+ svn_wc__proplist_receiver_t receiver_func,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Set @a *inherited_props to a depth-first ordered array of
+ * #svn_prop_inherited_item_t * structures representing the properties
+ * inherited by @a local_abspath from the ACTUAL tree above
+ * @a local_abspath (looking through to the WORKING or BASE tree as
+ * required), up to and including the root of the working copy and
+ * any cached inherited properties inherited by the root.
+ *
+ * The #svn_prop_inherited_item_t->path_or_url members of the
+ * #svn_prop_inherited_item_t * structures in @a *inherited_props are
+ * paths relative to the repository root URL for cached inherited
+ * properties and absolute working copy paths otherwise.
+ *
+ * Allocate @a *inherited_props in @a result_pool. Use @a scratch_pool
+ * for temporary allocations.
+ */
+svn_error_t *
+svn_wc__get_iprops(apr_array_header_t **inherited_props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Obtain a mapping of const char * local_abspaths to const svn_string_t*
+ * property values in *VALUES, of all PROPNAME properties on LOCAL_ABSPATH
+ * and its descendants.
+ *
+ * Allocate the result in RESULT_POOL, and perform temporary allocations in
+ * SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__prop_retrieve_recursive(apr_hash_t **values,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Set @a *iprops_paths to a hash mapping const char * absolute working
+ * copy paths to the nodes repository root relative path for each path
+ * in the working copy at or below @a local_abspath, limited by @a depth,
+ * that has cached inherited properties for the base node of the path.
+ *
+ * Allocate @a *iprop_paths
+ * in @a result_pool. Use @a scratch_pool for temporary allocations.
+ */
+svn_error_t *
+svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * For use by entries.c and entries-dump.c to read old-format working copies.
+ */
+svn_error_t *
+svn_wc__read_entries_old(apr_hash_t **entries,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Recursively clear the dav cache (wcprops) in @a wc_ctx for the tree
+ * rooted at @a local_abspath.
+ */
+svn_error_t *
+svn_wc__node_clear_dav_cache_recursive(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Set @a lock_tokens to a hash mapping <tt>const char *</tt> URL
+ * to <tt>const char *</tt> lock tokens for every path at or under
+ * @a local_abspath in @a wc_ctx which has such a lock token set on it.
+ * Allocate the hash and all items therein from @a result_pool.
+ */
+svn_error_t *
+svn_wc__node_get_lock_tokens_recursive(apr_hash_t **lock_tokens,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set @a *min_revision and @a *max_revision to the lowest and highest revision
+ * numbers found within @a local_abspath, using context @a wc_ctx.
+ * If @a committed is TRUE, set @a *min_revision and @a *max_revision
+ * to the lowest and highest comitted (i.e. "last changed") revision numbers,
+ * respectively. Use @a scratch_pool for temporary allocations.
+ *
+ * Either of MIN_REVISION and MAX_REVISION may be passed as NULL if
+ * the caller doesn't care about that return value.
+ *
+ * This function provides a subset of the functionality of
+ * svn_wc_revision_status2() and is more efficient if the caller
+ * doesn't need all information returned by svn_wc_revision_status2(). */
+svn_error_t *
+svn_wc__min_max_revisions(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t committed,
+ apr_pool_t *scratch_pool);
+
+/* Indicate in @a is_switched whether any node beneath @a local_abspath
+ * is switched, using context @a wc_ctx.
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * If @a trail_url is non-NULL, use it to determine if @a local_abspath itself
+ * is switched. It should be any trailing portion of @a local_abspath's
+ * expected URL, long enough to include any parts that the caller considers
+ * might be changed by a switch. If it does not match the end of
+ * @a local_abspath's actual URL, then report a "switched" status.
+ *
+ * This function provides a subset of the functionality of
+ * svn_wc_revision_status2() and is more efficient if the caller
+ * doesn't need all information returned by svn_wc_revision_status2(). */
+svn_error_t *
+svn_wc__has_switched_subtrees(svn_boolean_t *is_switched,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *trail_url,
+ apr_pool_t *scratch_pool);
+
+/* Set @a *excluded_subtrees to a hash mapping <tt>const char *</tt>
+ * local * absolute paths to <tt>const char *</tt> local absolute paths for
+ * every path under @a local_abspath in @a wc_ctx which are excluded
+ * by the server (e.g. because of authz) or the users.
+ * If no excluded paths are found then @a *server_excluded_subtrees
+ * is set to @c NULL.
+ * Allocate the hash and all items therein from @a result_pool.
+ */
+svn_error_t *
+svn_wc__get_excluded_subtrees(apr_hash_t **server_excluded_subtrees,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Indicate in @a *is_modified whether the working copy has local
+ * modifications, using context @a wc_ctx.
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * This function provides a subset of the functionality of
+ * svn_wc_revision_status2() and is more efficient if the caller
+ * doesn't need all information returned by svn_wc_revision_status2(). */
+svn_error_t *
+svn_wc__has_local_mods(svn_boolean_t *is_modified,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Renames a working copy from @a from_abspath to @a dst_abspath and makes sure
+ open handles are closed to allow this on all platforms.
+
+ Summary: This avoids a file lock problem on wc.db on Windows, that is
+ triggered by libsvn_client'ss copy to working copy code. */
+svn_error_t *
+svn_wc__rename_wc(svn_wc_context_t *wc_ctx,
+ const char *from_abspath,
+ const char *dst_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Set *TMPDIR_ABSPATH to a directory that is suitable for temporary
+ files which may need to be moved (atomically and same-device) into
+ the working copy indicated by WRI_ABSPATH. */
+svn_error_t *
+svn_wc__get_tmpdir(const char **tmpdir_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gets information needed by the commit harvester.
+ *
+ * ### Currently this API is work in progress and is designed for just this
+ * ### caller. It is certainly possible (and likely) that this function and
+ * ### it's caller will eventually move into a wc and maybe wc_db api.
+ */
+svn_error_t *
+svn_wc__node_get_commit_status(svn_boolean_t *added,
+ svn_boolean_t *deleted,
+ svn_boolean_t *is_replace_root,
+ svn_boolean_t *is_op_root,
+ svn_revnum_t *revision,
+ svn_revnum_t *original_revision,
+ const char **original_repos_relpath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gets the md5 checksum for the pristine file identified by a sha1_checksum in the
+ working copy identified by wri_abspath.
+
+ Wraps svn_wc__db_pristine_get_md5().
+ */
+svn_error_t *
+svn_wc__node_get_md5_from_sha1(const svn_checksum_t **md5_checksum,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc_get_pristine_contents2(), but keyed on the CHECKSUM
+ rather than on the local absolute path of the working file.
+ WRI_ABSPATH is any versioned path of the working copy in whose
+ pristine database we'll be looking for these contents. */
+svn_error_t *
+svn_wc__get_pristine_contents_by_checksum(svn_stream_t **contents,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const svn_checksum_t *checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gets an array of const char *repos_relpaths of descendants of LOCAL_ABSPATH,
+ * which must be the op root of an addition, copy or move. The descendants
+ * returned are at the same op_depth, but are to be deleted by the commit
+ * processing because they are not present in the local copy.
+ */
+svn_error_t *
+svn_wc__get_not_present_descendants(const apr_array_header_t **descendants,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Checks a node LOCAL_ABSPATH in WC_CTX for several kinds of obstructions
+ * for tasks like merge processing.
+ *
+ * If a node is not obstructed it sets *OBSTRUCTION_STATE to
+ * svn_wc_notify_state_inapplicable. If a node is obstructed or when its
+ * direct parent does not exist or is deleted return _state_obstructed. When
+ * a node doesn't exist but should exist return svn_wc_notify_state_missing.
+ *
+ * A node is also obstructed if it is marked excluded or server-excluded or when
+ * an unversioned file or directory exists. And if NO_WCROOT_CHECK is FALSE,
+ * the root of a working copy is also obstructed; this to allow detecting
+ * obstructing working copies.
+ *
+ * If KIND is not NULL, set *KIND to the kind of node registered in the working
+ * copy, or SVN_NODE_NONE if the node doesn't
+ *
+ * If DELETED is not NULL, set *DELETED to TRUE if the node is marked as
+ * deleted in the working copy.
+ *
+ * If EXCLUDED is not NULL, set *EXCLUDED to TRUE if the node is marked as
+ * user or server excluded.
+ *
+ * If PARENT_DEPTH is not NULL, set *PARENT_DEPTH to the depth stored on the
+ * parent. (Set to svn_depth_unknown if LOCAL_ABSPATH itself exists as node)
+ *
+ * All output arguments except OBSTRUCTION_STATE can be NULL to ommit the
+ * result.
+ *
+ * This function performs temporary allocations in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__check_for_obstructions(svn_wc_notify_state_t *obstruction_state,
+ svn_node_kind_t *kind,
+ svn_boolean_t *deleted,
+ svn_boolean_t *excluded,
+ svn_depth_t *parent_depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t no_wcroot_check,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * A structure which describes various system-generated metadata about
+ * a working-copy path or URL.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, users shouldn't allocate structures of this
+ * type, to preserve binary compatibility.
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_wc__info2_t
+{
+ /** Where the item lives in the repository. */
+ const char *URL;
+
+ /** The root URL of the repository. */
+ const char *repos_root_URL;
+
+ /** The repository's UUID. */
+ const char *repos_UUID;
+
+ /** The revision of the object. If the target is a working-copy
+ * path, then this is its current working revision number. If the target
+ * is a URL, then this is the repository revision that it lives in. */
+ svn_revnum_t rev;
+
+ /** The node's kind. */
+ svn_node_kind_t kind;
+
+ /** The size of the file in the repository (untranslated,
+ * e.g. without adjustment of line endings and keyword
+ * expansion). Only applicable for file -- not directory -- URLs.
+ * For working copy paths, @a size will be #SVN_INVALID_FILESIZE. */
+ svn_filesize_t size;
+
+ /** The last revision in which this object changed. */
+ svn_revnum_t last_changed_rev;
+
+ /** The date of the last_changed_rev. */
+ apr_time_t last_changed_date;
+
+ /** The author of the last_changed_rev. */
+ const char *last_changed_author;
+
+ /** An exclusive lock, if present. Could be either local or remote. */
+ svn_lock_t *lock;
+
+ /* Possible information about the working copy, NULL if not valid. */
+ struct svn_wc_info_t *wc_info;
+
+} svn_wc__info2_t;
+
+/** The callback invoked by info retrievers. Each invocation
+ * describes @a local_abspath with the information present in @a info.
+ * Use @a scratch_pool for all temporary allocation.
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_wc__info_receiver2_t)(void *baton,
+ const char *local_abspath,
+ const svn_wc__info2_t *info,
+ apr_pool_t *scratch_pool);
+
+/* Walk the children of LOCAL_ABSPATH and push svn_wc__info2_t's through
+ RECEIVER/RECEIVER_BATON. Honor DEPTH while crawling children, and
+ filter the pushed items against CHANGELISTS.
+
+ If FETCH_EXCLUDED is TRUE, also fetch excluded nodes.
+ If FETCH_ACTUAL_ONLY is TRUE, also fetch actual-only nodes. */
+svn_error_t *
+svn_wc__get_info(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t fetch_excluded,
+ svn_boolean_t fetch_actual_only,
+ const apr_array_header_t *changelists,
+ svn_wc__info_receiver2_t receiver,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Alternative version of svn_wc_delete4().
+ * It can delete multiple TARGETS more efficiently (within a single sqlite
+ * transaction per working copy), but lacks support for moves.
+ *
+ * ### Inconsistency: if DELETE_UNVERSIONED_TARGET is FALSE and a target is
+ * unversioned, svn_wc__delete_many() will continue whereas
+ * svn_wc_delete4() will throw an error.
+ */
+svn_error_t *
+svn_wc__delete_many(svn_wc_context_t *wc_ctx,
+ const apr_array_header_t *targets,
+ svn_boolean_t keep_local,
+ svn_boolean_t delete_unversioned_target,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* If the node at LOCAL_ABSPATH was moved away set *MOVED_TO_ABSPATH to
+ * the absolute path of the copied move-target node, and *COPY_OP_ROOT_ABSPATH
+ * to the absolute path of the root node of the copy operation.
+ *
+ * If the node was not moved, set *MOVED_TO_ABSPATH and *COPY_OP_ROOT_ABSPATH
+ * to NULL.
+ *
+ * Either MOVED_TO_ABSPATH or OP_ROOT_ABSPATH may be NULL to indicate
+ * that the caller is not interested in the result.
+ */
+svn_error_t *
+svn_wc__node_was_moved_away(const char **moved_to_abspath,
+ const char **copy_op_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* If the node at LOCAL_ABSPATH was moved here set *MOVED_FROM_ABSPATH to
+ * the absolute path of the deleted move-source node, and set
+ * *DELETE_OP_ROOT_ABSPATH to the absolute path of the root node of the
+ * delete operation.
+ *
+ * If the node was not moved, set *MOVED_FROM_ABSPATH and
+ * *DELETE_OP_ROOT_ABSPATH to NULL.
+ *
+ * Either MOVED_FROM_ABSPATH or OP_ROOT_ABSPATH may be NULL to indicate
+ * that the caller is not interested in the result.
+ */
+svn_error_t *
+svn_wc__node_was_moved_here(const char **moved_from_abspath,
+ const char **delete_op_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* During an upgrade to wc-ng, supply known details about an existing
+ * external. The working copy will suck in and store the information supplied
+ * about the existing external at @a local_abspath. */
+svn_error_t *
+svn_wc__upgrade_add_external_info(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *def_local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t def_peg_revision,
+ svn_revnum_t def_revision,
+ apr_pool_t *scratch_pool);
+
+/* If the URL for @a item is relative, then using the repository root
+ URL @a repos_root_url and the parent directory URL @parent_dir_url,
+ resolve it into an absolute URL and save it in @a *resolved_url.
+
+ Regardless if the URL is absolute or not, if there are no errors,
+ the URL returned in @a *resolved_url will be canonicalized.
+
+ The following relative URL formats are supported:
+
+ ../ relative to the parent directory of the external
+ ^/ relative to the repository root
+ // relative to the scheme
+ / relative to the server's hostname
+
+ The ../ and ^/ relative URLs may use .. to remove path elements up
+ to the server root.
+
+ The external URL should not be canonicalized before calling this function,
+ as otherwise the scheme relative URL '//host/some/path' would have been
+ canonicalized to '/host/some/path' and we would not be able to match on
+ the leading '//'. */
+svn_error_t *
+svn_wc__resolve_relative_external_url(const char **resolved_url,
+ const svn_wc_external_item2_t *item,
+ const char *repos_root_url,
+ const char *parent_dir_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Set @a *editor and @a *edit_baton to an editor that generates
+ * #svn_wc_status3_t structures and sends them through @a status_func /
+ * @a status_baton. @a anchor_abspath is a working copy directory
+ * directory which will be used as the root of our editor. If @a
+ * target_basename is not "", it represents a node in the @a anchor_abspath
+ * which is the subject of the editor drive (otherwise, the @a
+ * anchor_abspath is the subject).
+ *
+ * If @a set_locks_baton is non-@c NULL, it will be set to a baton that can
+ * be used in a call to the svn_wc_status_set_repos_locks() function.
+ *
+ * Callers drive this editor to describe working copy out-of-dateness
+ * with respect to the repository. If this information is not
+ * available or not desired, callers should simply call the
+ * close_edit() function of the @a editor vtable.
+ *
+ * If the editor driver calls @a editor's set_target_revision() vtable
+ * function, then when the edit drive is completed, @a *edit_revision
+ * will contain the revision delivered via that interface.
+ *
+ * Assuming the target is a directory, then:
+ *
+ * - If @a get_all is FALSE, then only locally-modified entries will be
+ * returned. If TRUE, then all entries will be returned.
+ *
+ * - If @a depth is #svn_depth_empty, a status structure will
+ * be returned for the target only; if #svn_depth_files, for the
+ * target and its immediate file children; if
+ * #svn_depth_immediates, for the target and its immediate
+ * children; if #svn_depth_infinity, for the target and
+ * everything underneath it, fully recursively.
+ *
+ * If @a depth is #svn_depth_unknown, take depths from the
+ * working copy and behave as above in each directory's case.
+ *
+ * If the given @a depth is incompatible with the depth found in a
+ * working copy directory, the found depth always governs.
+ *
+ * If @a no_ignore is set, statuses that would typically be ignored
+ * will instead be reported.
+ *
+ * @a ignore_patterns is an array of file patterns matching
+ * unversioned files to ignore for the purposes of status reporting,
+ * or @c NULL if the default set of ignorable file patterns should be used.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton while building
+ * the @a statushash to determine if the client has canceled the operation.
+ *
+ * If @a depth_as_sticky is set handle @a depth like when depth_is_sticky is
+ * passed for updating. This will show excluded nodes show up as added in the
+ * repository.
+ *
+ * If @a server_performs_filtering is TRUE, assume that the server handles
+ * the ambient depth filtering, so this doesn't have to be handled in the
+ * editor.
+ *
+ * Allocate the editor itself in @a result_pool, and use @a scratch_pool
+ * for temporary allocations. The editor will do its temporary allocations
+ * in a subpool of @a result_pool.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc__get_status_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ void **set_locks_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t depth_as_sticky,
+ svn_boolean_t server_performs_filtering,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Set @a *editor and @a *edit_baton to an editor and baton for updating a
+ * working copy.
+ *
+ * @a anchor_abspath is a local working copy directory, with a fully recursive
+ * write lock in @a wc_ctx, which will be used as the root of our editor.
+ *
+ * @a target_basename is the entry in @a anchor_abspath that will actually be
+ * updated, or the empty string if all of @a anchor_abspath should be updated.
+ *
+ * The editor invokes @a notify_func with @a notify_baton as the update
+ * progresses, if @a notify_func is non-NULL.
+ *
+ * If @a cancel_func is non-NULL, the editor will invoke @a cancel_func with
+ * @a cancel_baton as the update progresses to see if it should continue.
+ *
+ * If @a conflict_func is non-NULL, then invoke it with @a
+ * conflict_baton whenever a conflict is encountered, giving the
+ * callback a chance to resolve the conflict before the editor takes
+ * more drastic measures (such as marking a file conflicted, or
+ * bailing out of the update).
+ *
+ * If @a external_func is non-NULL, then invoke it with @a external_baton
+ * whenever external changes are encountered, giving the callback a chance
+ * to store the external information for processing.
+ *
+ * If @a diff3_cmd is non-NULL, then use it as the diff3 command for
+ * any merging; otherwise, use the built-in merge code.
+ *
+ * @a preserved_exts is an array of filename patterns which, when
+ * matched against the extensions of versioned files, determine for
+ * which such files any related generated conflict files will preserve
+ * the original file's extension as their own. If a file's extension
+ * does not match any of the patterns in @a preserved_exts (which is
+ * certainly the case if @a preserved_exts is @c NULL or empty),
+ * generated conflict files will carry Subversion's custom extensions.
+ *
+ * @a target_revision is a pointer to a revision location which, after
+ * successful completion of the drive of this editor, will be
+ * populated with the revision to which the working copy was updated.
+ *
+ * @a wcroot_iprops is a hash mapping const char * absolute working copy
+ * paths which are working copy roots (at or under the target within the
+ * constraints dictated by @a depth) to depth-first ordered arrays of
+ * svn_prop_inherited_item_t * structures which represent the inherited
+ * properties for the base of those paths at @a target_revision. After a
+ * successful drive of this editor, the base nodes for these paths will
+ * have their inherited properties cache updated with the values from
+ * @a wcroot_iprops.
+ *
+ * If @a use_commit_times is TRUE, then all edited/added files will
+ * have their working timestamp set to the last-committed-time. If
+ * FALSE, the working files will be touched with the 'now' time.
+ *
+ * If @a allow_unver_obstructions is TRUE, then allow unversioned
+ * obstructions when adding a path.
+ *
+ * If @a adds_as_modification is TRUE, a local addition at the same path
+ * as an incoming addition of the same node kind results in a normal node
+ * with a possible local modification, instead of a tree conflict.
+ *
+ * If @a depth is #svn_depth_infinity, update fully recursively.
+ * Else if it is #svn_depth_immediates, update the uppermost
+ * directory, its file entries, and the presence or absence of
+ * subdirectories (but do not descend into the subdirectories).
+ * Else if it is #svn_depth_files, update the uppermost directory
+ * and its immediate file entries, but not subdirectories.
+ * Else if it is #svn_depth_empty, update exactly the uppermost
+ * target, and don't touch its entries.
+ *
+ * If @a depth_is_sticky is set and @a depth is not
+ * #svn_depth_unknown, then in addition to updating PATHS, also set
+ * their sticky ambient depth value to @a depth.
+ *
+ * If @a server_performs_filtering is TRUE, assume that the server handles
+ * the ambient depth filtering, so this doesn't have to be handled in the
+ * editor.
+ *
+ * If @a clean_checkout is TRUE, assume that we are checking out into an
+ * empty directory, and so bypass a number of conflict checks that are
+ * unnecessary in this case.
+ *
+ * If @a fetch_dirents_func is not NULL, the update editor may call this
+ * callback, when asked to perform a depth restricted update. It will do this
+ * before returning the editor to allow using the primary ra session for this.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc__get_update_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ apr_hash_t *wcroot_iprops,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t server_performs_filtering,
+ svn_boolean_t clean_checkout,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * A variant of svn_wc__get_update_editor().
+ *
+ * Set @a *editor and @a *edit_baton to an editor and baton for "switching"
+ * a working copy to a new @a switch_url. (Right now, this URL must be
+ * within the same repository that the working copy already comes
+ * from.) @a switch_url must not be @c NULL.
+ *
+ * All other parameters behave as for svn_wc__get_update_editor().
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc__get_switch_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ const char *switch_url,
+ apr_hash_t *wcroot_iprops,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t server_performs_filtering,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+
+/**
+ * Return an @a editor/@a edit_baton for diffing a working copy against the
+ * repository. The editor is allocated in @a result_pool; temporary
+ * calculations are performed in @a scratch_pool.
+ *
+ * This editor supports diffing either the actual files and properties in the
+ * working copy (when @a use_text_base is #FALSE), or the current pristine
+ * information (when @a use_text_base is #TRUE) against the editor driver.
+ *
+ * @a anchor_abspath/@a target represent the base of the hierarchy to be
+ * compared. The diff callback paths will be relative to this path.
+ *
+ * Diffs will be reported as valid relpaths, with @a anchor_abspath being
+ * the root ("").
+ *
+ * @a callbacks/@a callback_baton is the callback table to use.
+ *
+ * If @a depth is #svn_depth_empty, just diff exactly @a target or
+ * @a anchor_path if @a target is empty. If #svn_depth_files then do the same
+ * and for top-level file entries as well (if any). If
+ * #svn_depth_immediates, do the same as #svn_depth_files but also diff
+ * top-level subdirectories at #svn_depth_empty. If #svn_depth_infinity,
+ * then diff fully recursively. If @a depth is #svn_depth_unknown, then...
+ *
+ * ### ... then the @a server_performs_filtering option is meaningful.
+ * ### But what does this depth mean exactly? Something about 'ambient'
+ * ### depth? How does it compare with depth 'infinity'?
+ *
+ * @a ignore_ancestry determines whether paths that have discontinuous node
+ * ancestry are treated as delete/add or as simple modifications. If
+ * @a ignore_ancestry is @c FALSE, then any discontinuous node ancestry will
+ * result in the diff given as a full delete followed by an add.
+ *
+ * @a show_copies_as_adds determines whether paths added with history will
+ * appear as a diff against their copy source, or whether such paths will
+ * appear as if they were newly added in their entirety.
+ *
+ * If @a use_git_diff_format is TRUE, copied paths will be treated as added
+ * if they weren't modified after being copied. This allows the callbacks
+ * to generate appropriate --git diff headers for such files.
+ *
+ * Normally, the difference from repository->working_copy is shown.
+ * If @a reverse_order is TRUE, then show working_copy->repository diffs.
+ *
+ * If @a cancel_func is non-NULL, it will be used along with @a cancel_baton
+ * to periodically check if the client has canceled the operation.
+ *
+ * @a changelist_filter is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose differences are
+ * reported; that is, don't generate diffs about any item unless
+ * it's a member of one of those changelists. If @a changelist_filter is
+ * empty (or altogether @c NULL), no changelist filtering occurs.
+ *
+ * If @a server_performs_filtering is TRUE, assume that the server handles
+ * the ambient depth filtering, so this doesn't have to be handled in the
+ * editor.
+ *
+ *
+ * A diagram illustrating how this function is used.
+ *
+ * Steps 1 and 2 create the chain; step 3 drives it.
+ *
+ * 1. svn_wc__get_diff_editor(diff_cbs)
+ * | ^
+ * 2. svn_ra_do_diff3(editor) | |
+ * | ^ | |
+ * v | v |
+ * +----------+ +----------+ +----------+
+ * | | | | | |
+ * +--> | reporter | ----> | editor | ----> | diff_cbs | ----> text
+ * | | | | | | | out
+ * | +----------+ +----------+ +----------+
+ * |
+ * 3. svn_wc_crawl_revisions5(WC,reporter)
+ *
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc__get_diff_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_boolean_t server_performs_filtering,
+ const apr_array_header_t *changelist_filter,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Callback for the svn_diff_tree_processor_t wrapper, to allow handling
+ * notifications like how the repos diff in libsvn_client does.
+ *
+ * Probably only necessary while transitioning to svn_diff_tree_processor_t
+ */
+typedef svn_error_t *
+ (*svn_wc__diff_state_handle_t)(svn_boolean_t tree_conflicted,
+ svn_wc_notify_state_t *state,
+ svn_wc_notify_state_t *prop_state,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_boolean_t before_op,
+ svn_boolean_t for_add,
+ svn_boolean_t for_delete,
+ void *state_baton,
+ apr_pool_t *scratch_pool);
+
+/** Callback for the svn_diff_tree_processor_t wrapper, to allow handling
+ * notifications like how the repos diff in libsvn_client does.
+ *
+ * Probably only necessary while transitioning to svn_diff_tree_processor_t
+ */
+typedef svn_error_t *
+ (*svn_wc__diff_state_close_t)(const char *relpath,
+ svn_node_kind_t kind,
+ void *state_baton,
+ apr_pool_t *scratch_pool);
+
+/** Callback for the svn_diff_tree_processor_t wrapper, to allow handling
+ * absent nodes.
+ *
+ * Probably only necessary while transitioning to svn_diff_tree_processor_t
+ */
+typedef svn_error_t *
+ (*svn_wc__diff_state_absent_t)(const char *relpath,
+ void *state_baton,
+ apr_pool_t *scratch_pool);
+
+/** Obtains a diff processor that will drive the diff callbacks when it
+ * is invoked.
+ */
+svn_error_t *
+svn_wc__wrap_diff_callbacks(const svn_diff_tree_processor_t **diff_processor,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t walk_deleted_dirs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Assuming @a local_abspath itself or any of its children are under version
+ * control or a tree conflict victim and in a state of conflict, take these
+ * nodes out of this state.
+ *
+ * If @a resolve_text is TRUE then any text conflict is resolved,
+ * if @a resolve_tree is TRUE then any tree conflicts are resolved.
+ * If @a resolve_prop is set to "" all property conflicts are resolved,
+ * if it is set to any other string value, conflicts on that specific
+ * property are resolved and when resolve_prop is NULL, no property
+ * conflicts are resolved.
+ *
+ * If @a depth is #svn_depth_empty, act only on @a local_abspath; if
+ * #svn_depth_files, resolve @a local_abspath and its conflicted file
+ * children (if any); if #svn_depth_immediates, resolve @a local_abspath
+ * and all its immediate conflicted children (both files and directories,
+ * if any); if #svn_depth_infinity, resolve @a local_abspath and every
+ * conflicted file or directory anywhere beneath it.
+ *
+ * If @a conflict_choice is #svn_wc_conflict_choose_base, resolve the
+ * conflict with the old file contents; if
+ * #svn_wc_conflict_choose_mine_full, use the original working contents;
+ * if #svn_wc_conflict_choose_theirs_full, the new contents; and if
+ * #svn_wc_conflict_choose_merged, don't change the contents at all,
+ * just remove the conflict status, which is the pre-1.5 behavior.
+ *
+ * If @a conflict_choice is #svn_wc_conflict_choose_unspecified, invoke the
+ * @a conflict_func with the @a conflict_baton argument to obtain a
+ * resolution decision for each conflict.
+ *
+ * #svn_wc_conflict_choose_theirs_conflict and
+ * #svn_wc_conflict_choose_mine_conflict are not legal for binary
+ * files or properties.
+ *
+ * @a wc_ctx is a working copy context, with a write lock, for @a
+ * local_abspath.
+ *
+ * The implementation details are opaque, as our "conflicted" criteria
+ * might change over time. (At the moment, this routine removes the
+ * three fulltext 'backup' files and any .prej file created in a conflict,
+ * and modifies @a local_abspath's entry.)
+ *
+ * If @a local_abspath is not under version control and not a tree
+ * conflict, return #SVN_ERR_ENTRY_NOT_FOUND. If @a path isn't in a
+ * state of conflict to begin with, do nothing, and return #SVN_NO_ERROR.
+ *
+ * If @c local_abspath was successfully taken out of a state of conflict,
+ * report this information to @c notify_func (if non-@c NULL.) If only
+ * text, only property, or only tree conflict resolution was requested,
+ * and it was successful, then success gets reported.
+ *
+ * Temporary allocations will be performed in @a scratch_pool.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc__resolve_conflicts(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t resolve_text,
+ const char *resolve_prop,
+ svn_boolean_t resolve_tree,
+ svn_wc_conflict_choice_t conflict_choice,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Move @a src_abspath to @a dst_abspath, by scheduling @a dst_abspath
+ * for addition to the repository, remembering the history. Mark @a src_abspath
+ * as deleted after moving.@a wc_ctx is used for accessing the working copy and
+ * must contain a write lock for the parent directory of @a src_abspath and
+ * @a dst_abspath.
+ *
+ * If @a metadata_only is TRUE then this is a database-only operation and
+ * the working directories and files are not changed.
+ *
+ * @a src_abspath must be a file or directory under version control;
+ * the parent of @a dst_abspath must be a directory under version control
+ * in the same working copy; @a dst_abspath will be the name of the copied
+ * item, and it must not exist already if @a metadata_only is FALSE. Note that
+ * when @a src points to a versioned file, the working file doesn't
+ * necessarily exist in which case its text-base is used instead.
+ *
+ * If @a allow_mixed_revisions is @c FALSE, #SVN_ERR_WC_MIXED_REVISIONS
+ * will be raised if the move source is a mixed-revision subtree.
+ * If @a allow_mixed_revisions is TRUE, a mixed-revision move source is
+ * allowed but the move will degrade to a copy and a delete without local
+ * move tracking. This parameter should be set to FALSE except where backwards
+ * compatibility to svn_wc_move() is required.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton at
+ * various points during the operation. If it returns an error
+ * (typically #SVN_ERR_CANCELLED), return that error immediately.
+ *
+ * If @a notify_func is non-NULL, call it with @a notify_baton and the path
+ * of the root node (only) of the destination.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc__move2(svn_wc_context_t *wc_ctx,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t metadata_only,
+ svn_boolean_t allow_mixed_revisions,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* During merge when we encounter added directories, we add them using
+ svn_wc_add4(), recording its original location, etc. But at that time
+ we don't have its original properties. This function allows updating the
+ BASE properties of such a special added node, but only before it receives
+ other changes.
+
+ NEW_ORIGINAL_PROPS is a new set of properties, including entry props that
+ will be applied to LOCAL_ABSPATH as pristine properties.
+
+ The copyfrom_* arguments are used to verify (some of) the assumptions of
+ this function */
+svn_error_t *
+svn_wc__complete_directory_add(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_hash_t *new_original_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *scratch_pool);
+
+
+/* Acquire a write lock on LOCAL_ABSPATH or an ancestor that covers
+ all possible paths affected by resolving the conflicts in the tree
+ LOCAL_ABSPATH. Set *LOCK_ROOT_ABSPATH to the path of the lock
+ obtained. */
+svn_error_t *
+svn_wc__acquire_write_lock_for_resolve(const char **lock_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_WC_PRIVATE_H */
diff --git a/contrib/subversion/subversion/include/svn_auth.h b/contrib/subversion/subversion/include/svn_auth.h
new file mode 100644
index 0000000..dadc1cf
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_auth.h
@@ -0,0 +1,1282 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_auth.h
+ * @brief Subversion's authentication system
+ */
+
+#ifndef SVN_AUTH_H
+#define SVN_AUTH_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+
+#include "svn_types.h"
+#include "svn_config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** Overview of the svn authentication system.
+ *
+ * We define an authentication "provider" as a module that is able to
+ * return a specific set of credentials. (e.g. username/password,
+ * certificate, etc.) Each provider implements a vtable that
+ *
+ * - can fetch initial credentials
+ * - can retry the fetch (or try to fetch something different)
+ * - can store the credentials for future use
+ *
+ * For any given type of credentials, there can exist any number of
+ * separate providers -- each provider has a different method of
+ * fetching. (i.e. from a disk store, by prompting the user, etc.)
+ *
+ * The application begins by creating an auth baton object, and
+ * "registers" some number of providers with the auth baton, in a
+ * specific order. (For example, it may first register a
+ * username/password provider that looks in disk store, then register
+ * a username/password provider that prompts the user.)
+ *
+ * Later on, when any svn library is challenged, it asks the auth
+ * baton for the specific credentials. If the initial credentials
+ * fail to authenticate, the caller keeps requesting new credentials.
+ * Under the hood, libsvn_auth effectively "walks" over each provider
+ * (in order of registry), one at a time, until all the providers have
+ * exhausted all their retry options.
+ *
+ * This system allows an application to flexibly define authentication
+ * behaviors (by changing registration order), and very easily write
+ * new authentication providers.
+ *
+ * An auth_baton also contains an internal hashtable of run-time
+ * parameters; any provider or library layer can set these run-time
+ * parameters at any time, so that the provider has access to the
+ * data. (For example, certain run-time data may not be available
+ * until an authentication challenge is made.) Each credential type
+ * must document the run-time parameters that are made available to
+ * its providers.
+ *
+ * @defgroup auth_fns Authentication functions
+ * @{
+ */
+
+
+/** The type of a Subversion authentication object */
+typedef struct svn_auth_baton_t svn_auth_baton_t;
+
+/** The type of a Subversion authentication-iteration object */
+typedef struct svn_auth_iterstate_t svn_auth_iterstate_t;
+
+
+/** The main authentication "provider" vtable. */
+typedef struct svn_auth_provider_t
+{
+ /** The kind of credentials this provider knows how to retrieve. */
+ const char *cred_kind;
+
+ /** Get an initial set of credentials.
+ *
+ * Set @a *credentials to a set of valid credentials within @a
+ * realmstring, or NULL if no credentials are available. Set @a
+ * *iter_baton to context that allows a subsequent call to @c
+ * next_credentials, in case the first credentials fail to
+ * authenticate. @a provider_baton is general context for the
+ * vtable, @a parameters contains any run-time data that the
+ * provider may need, and @a realmstring comes from the
+ * svn_auth_first_credentials() call.
+ */
+ svn_error_t * (*first_credentials)(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool);
+
+ /** Get a different set of credentials.
+ *
+ * Set @a *credentials to another set of valid credentials (using @a
+ * iter_baton as the context from previous call to first_credentials
+ * or next_credentials). If no more credentials are available, set
+ * @a *credentials to NULL. If the provider only has one set of
+ * credentials, this function pointer should simply be NULL. @a
+ * provider_baton is general context for the vtable, @a parameters
+ * contains any run-time data that the provider may need, and @a
+ * realmstring comes from the svn_auth_first_credentials() call.
+ */
+ svn_error_t * (*next_credentials)(void **credentials,
+ void *iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool);
+
+ /** Save credentials.
+ *
+ * Store @a credentials for future use. @a provider_baton is
+ * general context for the vtable, and @a parameters contains any
+ * run-time data the provider may need. Set @a *saved to TRUE if
+ * the save happened, or FALSE if not. The provider is not required
+ * to save; if it refuses or is unable to save for non-fatal
+ * reasons, return FALSE. If the provider never saves data, then
+ * this function pointer should simply be NULL. @a realmstring comes
+ * from the svn_auth_first_credentials() call.
+ */
+ svn_error_t * (*save_credentials)(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool);
+
+} svn_auth_provider_t;
+
+
+/** A provider object, ready to be put into an array and given to
+ svn_auth_open(). */
+typedef struct svn_auth_provider_object_t
+{
+ const svn_auth_provider_t *vtable;
+ void *provider_baton;
+
+} svn_auth_provider_object_t;
+
+/** The type of function returning authentication provider. */
+typedef void (*svn_auth_simple_provider_func_t)(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+/** Specific types of credentials **/
+
+/** Simple username/password pair credential kind.
+ *
+ * The following auth parameters are available to the providers:
+ *
+ * - @c SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG (@c svn_config_t*)
+ * - @c SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS (@c svn_config_t*)
+ *
+ * The following auth parameters may be available to the providers:
+ *
+ * - @c SVN_AUTH_PARAM_NO_AUTH_CACHE (@c void*)
+ * - @c SVN_AUTH_PARAM_DEFAULT_USERNAME (@c char*)
+ * - @c SVN_AUTH_PARAM_DEFAULT_PASSWORD (@c char*)
+ */
+#define SVN_AUTH_CRED_SIMPLE "svn.simple"
+
+/** @c SVN_AUTH_CRED_SIMPLE credentials. */
+typedef struct svn_auth_cred_simple_t
+{
+ /** Username */
+ const char *username;
+ /** Password */
+ const char *password;
+ /** Indicates if the credentials may be saved (to disk). For example, a
+ * GUI prompt implementation with a remember password checkbox shall set
+ * @a may_save to TRUE if the checkbox is checked.
+ */
+ svn_boolean_t may_save;
+} svn_auth_cred_simple_t;
+
+
+/** Username credential kind.
+ *
+ * The following optional auth parameters are relevant to the providers:
+ *
+ * - @c SVN_AUTH_PARAM_NO_AUTH_CACHE (@c void*)
+ * - @c SVN_AUTH_PARAM_DEFAULT_USERNAME (@c char*)
+ */
+#define SVN_AUTH_CRED_USERNAME "svn.username"
+
+/** @c SVN_AUTH_CRED_USERNAME credentials. */
+typedef struct svn_auth_cred_username_t
+{
+ /** Username */
+ const char *username;
+ /** Indicates if the credentials may be saved (to disk). For example, a
+ * GUI prompt implementation with a remember username checkbox shall set
+ * @a may_save to TRUE if the checkbox is checked.
+ */
+ svn_boolean_t may_save;
+} svn_auth_cred_username_t;
+
+
+/** SSL client certificate credential type.
+ *
+ * The following auth parameters are available to the providers:
+ *
+ * - @c SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS (@c svn_config_t*)
+ * - @c SVN_AUTH_PARAM_SERVER_GROUP (@c char*)
+ *
+ * The following optional auth parameters are relevant to the providers:
+ *
+ * - @c SVN_AUTH_PARAM_NO_AUTH_CACHE (@c void*)
+ */
+#define SVN_AUTH_CRED_SSL_CLIENT_CERT "svn.ssl.client-cert"
+
+/** @c SVN_AUTH_CRED_SSL_CLIENT_CERT credentials. */
+typedef struct svn_auth_cred_ssl_client_cert_t
+{
+ /** Absolute path to the certificate file */
+ const char *cert_file;
+ /** Indicates if the credentials may be saved (to disk). For example, a
+ * GUI prompt implementation with a remember certificate checkbox shall
+ * set @a may_save to TRUE if the checkbox is checked.
+ */
+ svn_boolean_t may_save;
+} svn_auth_cred_ssl_client_cert_t;
+
+
+/** A function returning an SSL client certificate passphrase provider. */
+typedef void (*svn_auth_ssl_client_cert_pw_provider_func_t)(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+/** SSL client certificate passphrase credential type.
+ *
+ * @note The realmstring used with this credential type must be a name that
+ * makes it possible for the user to identify the certificate.
+ *
+ * The following auth parameters are available to the providers:
+ *
+ * - @c SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG (@c svn_config_t*)
+ * - @c SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS (@c svn_config_t*)
+ * - @c SVN_AUTH_PARAM_SERVER_GROUP (@c char*)
+ *
+ * The following optional auth parameters are relevant to the providers:
+ *
+ * - @c SVN_AUTH_PARAM_NO_AUTH_CACHE (@c void*)
+ */
+#define SVN_AUTH_CRED_SSL_CLIENT_CERT_PW "svn.ssl.client-passphrase"
+
+/** @c SVN_AUTH_CRED_SSL_CLIENT_CERT_PW credentials. */
+typedef struct svn_auth_cred_ssl_client_cert_pw_t
+{
+ /** Certificate password */
+ const char *password;
+ /** Indicates if the credentials may be saved (to disk). For example, a
+ * GUI prompt implementation with a remember password checkbox shall set
+ * @a may_save to TRUE if the checkbox is checked.
+ */
+ svn_boolean_t may_save;
+} svn_auth_cred_ssl_client_cert_pw_t;
+
+
+/** SSL server verification credential type.
+ *
+ * The following auth parameters are available to the providers:
+ *
+ * - @c SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS (@c svn_config_t*)
+ * - @c SVN_AUTH_PARAM_SERVER_GROUP (@c char*)
+ * - @c SVN_AUTH_PARAM_SSL_SERVER_FAILURES (@c apr_uint32_t*)
+ * - @c SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO
+ * (@c svn_auth_ssl_server_cert_info_t*)
+ *
+ * The following optional auth parameters are relevant to the providers:
+ *
+ * - @c SVN_AUTH_PARAM_NO_AUTH_CACHE (@c void*)
+ */
+#define SVN_AUTH_CRED_SSL_SERVER_TRUST "svn.ssl.server"
+
+/** SSL server certificate information used by @c
+ * SVN_AUTH_CRED_SSL_SERVER_TRUST providers.
+ */
+typedef struct svn_auth_ssl_server_cert_info_t
+{
+ /** Primary CN */
+ const char *hostname;
+ /** ASCII fingerprint */
+ const char *fingerprint;
+ /** ASCII date from which the certificate is valid */
+ const char *valid_from;
+ /** ASCII date until which the certificate is valid */
+ const char *valid_until;
+ /** DN of the certificate issuer */
+ const char *issuer_dname;
+ /** Base-64 encoded DER certificate representation */
+ const char *ascii_cert;
+} svn_auth_ssl_server_cert_info_t;
+
+/**
+ * Return a deep copy of @a info, allocated in @a pool.
+ *
+ * @since New in 1.3.
+ */
+svn_auth_ssl_server_cert_info_t *
+svn_auth_ssl_server_cert_info_dup(const svn_auth_ssl_server_cert_info_t *info,
+ apr_pool_t *pool);
+
+/** @c SVN_AUTH_CRED_SSL_SERVER_TRUST credentials. */
+typedef struct svn_auth_cred_ssl_server_trust_t
+{
+ /** Indicates if the credentials may be saved (to disk). For example, a
+ * GUI prompt implementation with a checkbox to accept the certificate
+ * permanently shall set @a may_save to TRUE if the checkbox is checked.
+ */
+ svn_boolean_t may_save;
+ /** Bit mask of the accepted failures */
+ apr_uint32_t accepted_failures;
+} svn_auth_cred_ssl_server_trust_t;
+
+
+
+/** Credential-constructing prompt functions. **/
+
+/** These exist so that different client applications can use
+ * different prompt mechanisms to supply the same credentials. For
+ * example, if authentication requires a username and password, a
+ * command-line client's prompting function might prompt first for the
+ * username and then for the password, whereas a GUI client's would
+ * present a single dialog box asking for both, and a telepathic
+ * client's would read all the information directly from the user's
+ * mind. All these prompting functions return the same type of
+ * credential, but the information used to construct the credential is
+ * gathered in an interface-specific way in each case.
+ */
+
+/** Set @a *cred by prompting the user, allocating @a *cred in @a pool.
+ * @a baton is an implementation-specific closure.
+ *
+ * If @a realm is non-NULL, maybe use it in the prompt string.
+ *
+ * If @a username is non-NULL, then the user might be prompted only
+ * for a password, but @a *cred would still be filled with both
+ * username and password. For example, a typical usage would be to
+ * pass @a username on the first call, but then leave it NULL for
+ * subsequent calls, on the theory that if credentials failed, it's
+ * as likely to be due to incorrect username as incorrect password.
+ *
+ * If @a may_save is FALSE, the auth system does not allow the credentials
+ * to be saved (to disk). A prompt function shall not ask the user if the
+ * credentials shall be saved if @a may_save is FALSE. For example, a GUI
+ * client with a remember password checkbox would grey out the checkbox if
+ * @a may_save is FALSE.
+ */
+typedef svn_error_t *(*svn_auth_simple_prompt_func_t)(
+ svn_auth_cred_simple_t **cred,
+ void *baton,
+ const char *realm,
+ const char *username,
+ svn_boolean_t may_save,
+ apr_pool_t *pool);
+
+
+/** Set @a *cred by prompting the user, allocating @a *cred in @a pool.
+ * @a baton is an implementation-specific closure.
+ *
+ * If @a realm is non-NULL, maybe use it in the prompt string.
+ *
+ * If @a may_save is FALSE, the auth system does not allow the credentials
+ * to be saved (to disk). A prompt function shall not ask the user if the
+ * credentials shall be saved if @a may_save is FALSE. For example, a GUI
+ * client with a remember username checkbox would grey out the checkbox if
+ * @a may_save is FALSE.
+ */
+typedef svn_error_t *(*svn_auth_username_prompt_func_t)(
+ svn_auth_cred_username_t **cred,
+ void *baton,
+ const char *realm,
+ svn_boolean_t may_save,
+ apr_pool_t *pool);
+
+
+/** @name SSL server certificate failure bits
+ *
+ * @note These values are stored in the on disk auth cache by the SSL
+ * server certificate auth provider, so the meaning of these bits must
+ * not be changed.
+ * @{
+ */
+/** Certificate is not yet valid. */
+#define SVN_AUTH_SSL_NOTYETVALID 0x00000001
+/** Certificate has expired. */
+#define SVN_AUTH_SSL_EXPIRED 0x00000002
+/** Certificate's CN (hostname) does not match the remote hostname. */
+#define SVN_AUTH_SSL_CNMISMATCH 0x00000004
+/** @brief Certificate authority is unknown (i.e. not trusted) */
+#define SVN_AUTH_SSL_UNKNOWNCA 0x00000008
+/** @brief Other failure. This can happen if an unknown failure occurs
+ * that we do not handle yet. */
+#define SVN_AUTH_SSL_OTHER 0x40000000
+/** @} */
+
+/** Set @a *cred by prompting the user, allocating @a *cred in @a pool.
+ * @a baton is an implementation-specific closure.
+ *
+ * @a cert_info is a structure describing the server cert that was
+ * presented to the client, and @a failures is a bitmask that
+ * describes exactly why the cert could not be automatically validated,
+ * composed from the constants SVN_AUTH_SSL_* (@c SVN_AUTH_SSL_NOTYETVALID
+ * etc.). @a realm is a string that can be used in the prompt string.
+ *
+ * If @a may_save is FALSE, the auth system does not allow the credentials
+ * to be saved (to disk). A prompt function shall not ask the user if the
+ * credentials shall be saved if @a may_save is FALSE. For example, a GUI
+ * client with a trust permanently checkbox would grey out the checkbox if
+ * @a may_save is FALSE.
+ */
+typedef svn_error_t *(*svn_auth_ssl_server_trust_prompt_func_t)(
+ svn_auth_cred_ssl_server_trust_t **cred,
+ void *baton,
+ const char *realm,
+ apr_uint32_t failures,
+ const svn_auth_ssl_server_cert_info_t *cert_info,
+ svn_boolean_t may_save,
+ apr_pool_t *pool);
+
+
+/** Set @a *cred by prompting the user, allocating @a *cred in @a pool.
+ * @a baton is an implementation-specific closure. @a realm is a string
+ * that can be used in the prompt string.
+ *
+ * If @a may_save is FALSE, the auth system does not allow the credentials
+ * to be saved (to disk). A prompt function shall not ask the user if the
+ * credentials shall be saved if @a may_save is FALSE. For example, a GUI
+ * client with a remember certificate checkbox would grey out the checkbox
+ * if @a may_save is FALSE.
+ */
+typedef svn_error_t *(*svn_auth_ssl_client_cert_prompt_func_t)(
+ svn_auth_cred_ssl_client_cert_t **cred,
+ void *baton,
+ const char *realm,
+ svn_boolean_t may_save,
+ apr_pool_t *pool);
+
+
+/** Set @a *cred by prompting the user, allocating @a *cred in @a pool.
+ * @a baton is an implementation-specific closure. @a realm is a string
+ * identifying the certificate, and can be used in the prompt string.
+ *
+ * If @a may_save is FALSE, the auth system does not allow the credentials
+ * to be saved (to disk). A prompt function shall not ask the user if the
+ * credentials shall be saved if @a may_save is FALSE. For example, a GUI
+ * client with a remember password checkbox would grey out the checkbox if
+ * @a may_save is FALSE.
+ */
+typedef svn_error_t *(*svn_auth_ssl_client_cert_pw_prompt_func_t)(
+ svn_auth_cred_ssl_client_cert_pw_t **cred,
+ void *baton,
+ const char *realm,
+ svn_boolean_t may_save,
+ apr_pool_t *pool);
+
+/** A type of callback function for asking whether storing a password to
+ * disk in plaintext is allowed.
+ *
+ * In this callback, the client should ask the user whether storing
+ * a password for the realm identified by @a realmstring to disk
+ * in plaintext is allowed.
+ *
+ * The answer is returned in @a *may_save_plaintext.
+ * @a baton is an implementation-specific closure.
+ * All allocations should be done in @a pool.
+ *
+ * @since New in 1.6
+ */
+typedef svn_error_t *(*svn_auth_plaintext_prompt_func_t)(
+ svn_boolean_t *may_save_plaintext,
+ const char *realmstring,
+ void *baton,
+ apr_pool_t *pool);
+
+/** A type of callback function for asking whether storing a passphrase to
+ * disk in plaintext is allowed.
+ *
+ * In this callback, the client should ask the user whether storing
+ * a passphrase for the realm identified by @a realmstring to disk
+ * in plaintext is allowed.
+ *
+ * The answer is returned in @a *may_save_plaintext.
+ * @a baton is an implementation-specific closure.
+ * All allocations should be done in @a pool.
+ *
+ * @since New in 1.6
+ */
+typedef svn_error_t *(*svn_auth_plaintext_passphrase_prompt_func_t)(
+ svn_boolean_t *may_save_plaintext,
+ const char *realmstring,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/** Initialize an authentication system.
+ *
+ * Return an authentication object in @a *auth_baton (allocated in @a
+ * pool) that represents a particular instance of the svn
+ * authentication system. @a providers is an array of @c
+ * svn_auth_provider_object_t pointers, already allocated in @a pool
+ * and intentionally ordered. These pointers will be stored within @a
+ * *auth_baton, grouped by credential type, and searched in this exact
+ * order.
+ */
+void
+svn_auth_open(svn_auth_baton_t **auth_baton,
+ const apr_array_header_t *providers,
+ apr_pool_t *pool);
+
+/** Set an authentication run-time parameter.
+ *
+ * Store @a name / @a value pair as a run-time parameter in @a
+ * auth_baton, making the data accessible to all providers. @a name
+ * and @a value will NOT be duplicated into the auth_baton's pool.
+ * To delete a run-time parameter, pass NULL for @a value.
+ */
+void
+svn_auth_set_parameter(svn_auth_baton_t *auth_baton,
+ const char *name,
+ const void *value);
+
+/** Get an authentication run-time parameter.
+ *
+ * Return a value for run-time parameter @a name from @a auth_baton.
+ * Return NULL if the parameter doesn't exist.
+ */
+const void *
+svn_auth_get_parameter(svn_auth_baton_t *auth_baton,
+ const char *name);
+
+/** Universal run-time parameters, made available to all providers.
+
+ If you are writing a new provider, then to be a "good citizen",
+ you should notice these global parameters! Note that these
+ run-time params should be treated as read-only by providers; the
+ application is responsible for placing them into the auth_baton
+ hash. */
+
+/** The auth-hash prefix indicating that the parameter is global. */
+#define SVN_AUTH_PARAM_PREFIX "svn:auth:"
+
+/**
+ * @name Default credentials defines
+ * Property values are const char *.
+ * @{ */
+/** Default username provided by the application itself (e.g. --username) */
+#define SVN_AUTH_PARAM_DEFAULT_USERNAME SVN_AUTH_PARAM_PREFIX "username"
+/** Default password provided by the application itself (e.g. --password) */
+#define SVN_AUTH_PARAM_DEFAULT_PASSWORD SVN_AUTH_PARAM_PREFIX "password"
+/** @} */
+
+/** @brief The application doesn't want any providers to prompt
+ * users. Property value is irrelevant; only property's existence
+ * matters. */
+#define SVN_AUTH_PARAM_NON_INTERACTIVE SVN_AUTH_PARAM_PREFIX "non-interactive"
+
+/** @brief The application doesn't want any providers to save passwords
+ * to disk. Property value is irrelevant; only property's existence
+ * matters. */
+#define SVN_AUTH_PARAM_DONT_STORE_PASSWORDS SVN_AUTH_PARAM_PREFIX \
+ "dont-store-passwords"
+
+/** @brief Indicates whether providers may save passwords to disk in
+ * plaintext. Property value can be either SVN_CONFIG_TRUE,
+ * SVN_CONFIG_FALSE, or SVN_CONFIG_ASK.
+ * @since New in 1.6.
+ */
+#define SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS SVN_AUTH_PARAM_PREFIX \
+ "store-plaintext-passwords"
+
+/** @brief The application doesn't want any providers to save passphrase
+ * to disk. Property value is irrelevant; only property's existence
+ * matters.
+ * @since New in 1.6.
+ */
+#define SVN_AUTH_PARAM_DONT_STORE_SSL_CLIENT_CERT_PP \
+ SVN_AUTH_PARAM_PREFIX "dont-store-ssl-client-cert-pp"
+
+/** @brief Indicates whether providers may save passphrase to disk in
+ * plaintext. Property value can be either SVN_CONFIG_TRUE,
+ * SVN_CONFIG_FALSE, or SVN_CONFIG_ASK.
+ * @since New in 1.6.
+ */
+#define SVN_AUTH_PARAM_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT \
+ SVN_AUTH_PARAM_PREFIX "store-ssl-client-cert-pp-plaintext"
+
+/** @brief The application doesn't want any providers to save credentials
+ * to disk. Property value is irrelevant; only property's existence
+ * matters. */
+#define SVN_AUTH_PARAM_NO_AUTH_CACHE SVN_AUTH_PARAM_PREFIX "no-auth-cache"
+
+/** @brief The following property is for SSL server cert providers. This
+ * provides a pointer to an @c apr_uint32_t containing the failures
+ * detected by the certificate validator. */
+#define SVN_AUTH_PARAM_SSL_SERVER_FAILURES SVN_AUTH_PARAM_PREFIX \
+ "ssl:failures"
+
+/** @brief The following property is for SSL server cert providers. This
+ * provides the cert info (svn_auth_ssl_server_cert_info_t). */
+#define SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO SVN_AUTH_PARAM_PREFIX \
+ "ssl:cert-info"
+
+/** This provides a pointer to a @c svn_config_t containting the config
+ * category. */
+#define SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG SVN_AUTH_PARAM_PREFIX \
+ "config-category-config"
+
+/** This provides a pointer to a @c svn_config_t containting the servers
+ * category. */
+#define SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS SVN_AUTH_PARAM_PREFIX \
+ "config-category-servers"
+
+/** @deprecated Provided for backward compatibility with the 1.5 API. */
+#define SVN_AUTH_PARAM_CONFIG SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS
+
+/** The current server group. */
+#define SVN_AUTH_PARAM_SERVER_GROUP SVN_AUTH_PARAM_PREFIX "server-group"
+
+/** @brief A configuration directory that overrides the default
+ * ~/.subversion. */
+#define SVN_AUTH_PARAM_CONFIG_DIR SVN_AUTH_PARAM_PREFIX "config-dir"
+
+/** Get an initial set of credentials.
+ *
+ * Ask @a auth_baton to set @a *credentials to a set of credentials
+ * defined by @a cred_kind and valid within @a realmstring, or NULL if
+ * no credentials are available. Otherwise, return an iteration state
+ * in @a *state, so that the caller can call
+ * svn_auth_next_credentials(), in case the first set of credentials
+ * fails to authenticate.
+ *
+ * Use @a pool to allocate @a *state, and for temporary allocation.
+ * Note that @a *credentials will be allocated in @a auth_baton's pool.
+ */
+svn_error_t *
+svn_auth_first_credentials(void **credentials,
+ svn_auth_iterstate_t **state,
+ const char *cred_kind,
+ const char *realmstring,
+ svn_auth_baton_t *auth_baton,
+ apr_pool_t *pool);
+
+/** Get another set of credentials, assuming previous ones failed to
+ * authenticate.
+ *
+ * Use @a state to fetch a different set of @a *credentials, as a
+ * follow-up to svn_auth_first_credentials() or
+ * svn_auth_next_credentials(). If no more credentials are available,
+ * set @a *credentials to NULL.
+ *
+ * Note that @a *credentials will be allocated in @c auth_baton's pool.
+ */
+svn_error_t *
+svn_auth_next_credentials(void **credentials,
+ svn_auth_iterstate_t *state,
+ apr_pool_t *pool);
+
+/** Save a set of credentials.
+ *
+ * Ask @a state to store the most recently returned credentials,
+ * presumably because they successfully authenticated.
+ * All allocations should be done in @a pool.
+ *
+ * If no credentials were ever returned, do nothing.
+ */
+svn_error_t *
+svn_auth_save_credentials(svn_auth_iterstate_t *state,
+ apr_pool_t *pool);
+
+/** Forget a set (or all) memory-cached credentials.
+ *
+ * Remove references (if any) in @a auth_baton to credentials cached
+ * therein. If @a cred_kind and @a realmstring are non-NULL, forget
+ * only the credentials associated with those credential types and
+ * realm. Otherwise @a cred_kind and @a realmstring must both be
+ * NULL, and this function will forget all credentials cached within
+ * @a auth_baton.
+ *
+ * @note This function does not affect persisted authentication
+ * credential storage at all. It is merely a way to cause Subversion
+ * to forget about credentials already fetched from a provider,
+ * forcing them to be fetched again later should they be required.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_auth_forget_credentials(svn_auth_baton_t *auth_baton,
+ const char *cred_kind,
+ const char *realmstring,
+ apr_pool_t *pool);
+
+/** @} */
+
+/** Set @a *provider to an authentication provider of type
+ * svn_auth_cred_simple_t that gets information by prompting the user
+ * with @a prompt_func and @a prompt_baton. Allocate @a *provider in
+ * @a pool.
+ *
+ * If both @c SVN_AUTH_PARAM_DEFAULT_USERNAME and
+ * @c SVN_AUTH_PARAM_DEFAULT_PASSWORD are defined as runtime
+ * parameters in the @c auth_baton, then @a *provider will return the
+ * default arguments when svn_auth_first_credentials() is called. If
+ * svn_auth_first_credentials() fails, then @a *provider will
+ * re-prompt @a retry_limit times (via svn_auth_next_credentials()).
+ * For infinite retries, set @a retry_limit to value less than 0.
+ *
+ * @since New in 1.4.
+ */
+void
+svn_auth_get_simple_prompt_provider(svn_auth_provider_object_t **provider,
+ svn_auth_simple_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool);
+
+
+/** Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_username_t that gets information by prompting the
+ * user with @a prompt_func and @a prompt_baton. Allocate @a *provider
+ * in @a pool.
+ *
+ * If @c SVN_AUTH_PARAM_DEFAULT_USERNAME is defined as a runtime
+ * parameter in the @c auth_baton, then @a *provider will return the
+ * default argument when svn_auth_first_credentials() is called. If
+ * svn_auth_first_credentials() fails, then @a *provider will
+ * re-prompt @a retry_limit times (via svn_auth_next_credentials()).
+ * For infinite retries, set @a retry_limit to value less than 0.
+ *
+ * @since New in 1.4.
+ */
+void
+svn_auth_get_username_prompt_provider(
+ svn_auth_provider_object_t **provider,
+ svn_auth_username_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool);
+
+
+/** Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_simple_t that gets/sets information from the user's
+ * ~/.subversion configuration directory.
+ *
+ * If the provider is going to save the password unencrypted, it calls @a
+ * plaintext_prompt_func, passing @a prompt_baton, before saving the
+ * password.
+ *
+ * If @a plaintext_prompt_func is NULL it is not called and the answer is
+ * assumed to be TRUE. This matches the deprecated behaviour of storing
+ * unencrypted passwords by default, and is only done this way for backward
+ * compatibility reasons.
+ * Client developers are highly encouraged to provide this callback
+ * to ensure their users are made aware of the fact that their password
+ * is going to be stored unencrypted. In the future, providers may
+ * default to not storing the password unencrypted if this callback is NULL.
+ *
+ * Clients can however set the callback to NULL and set
+ * SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS to SVN_CONFIG_FALSE or
+ * SVN_CONFIG_TRUE to enforce a certain behaviour.
+ *
+ * Allocate @a *provider in @a pool.
+ *
+ * If a default username or password is available, @a *provider will
+ * honor them as well, and return them when
+ * svn_auth_first_credentials() is called. (see @c
+ * SVN_AUTH_PARAM_DEFAULT_USERNAME and @c
+ * SVN_AUTH_PARAM_DEFAULT_PASSWORD).
+ *
+ * @since New in 1.6.
+ */
+void
+svn_auth_get_simple_provider2(
+ svn_auth_provider_object_t **provider,
+ svn_auth_plaintext_prompt_func_t plaintext_prompt_func,
+ void *prompt_baton,
+ apr_pool_t *pool);
+
+/** Like svn_auth_get_simple_provider2, but without the ability to
+ * call the svn_auth_plaintext_prompt_func_t callback, and the provider
+ * always assumes that it is allowed to store the password in plaintext.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.5 API.
+ * @since New in 1.4.
+ */
+SVN_DEPRECATED
+void
+svn_auth_get_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+/** Set @a *provider to an authentication provider of type @c
+ * svn_auth_provider_object_t, or return @c NULL if the provider is not
+ * available for the requested platform or the requested provider is unknown.
+ *
+ * Valid @a provider_name values are: "gnome_keyring", "keychain", "kwallet",
+ * "gpg_agent", and "windows".
+ *
+ * Valid @a provider_type values are: "simple", "ssl_client_cert_pw" and
+ * "ssl_server_trust".
+ *
+ * Allocate @a *provider in @a pool.
+ *
+ * What actually happens is we invoke the appropriate provider function to
+ * supply the @a provider, like so:
+ *
+ * svn_auth_get_<name>_<type>_provider(@a provider, @a pool);
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_auth_get_platform_specific_provider(
+ svn_auth_provider_object_t **provider,
+ const char *provider_name,
+ const char *provider_type,
+ apr_pool_t *pool);
+
+/** Set @a *providers to an array of <tt>svn_auth_provider_object_t *</tt>
+ * objects.
+ * Only client authentication providers available for the current platform are
+ * returned. Order of the platform-specific authentication providers is
+ * determined by the 'password-stores' configuration option which is retrieved
+ * from @a config. @a config can be NULL.
+ *
+ * Create and allocate @a *providers in @a pool.
+ *
+ * Default order of the platform-specific authentication providers:
+ * 1. gnome-keyring
+ * 2. kwallet
+ * 3. keychain
+ * 4. gpg-agent
+ * 5. windows-cryptoapi
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_auth_get_platform_specific_client_providers(
+ apr_array_header_t **providers,
+ svn_config_t *config,
+ apr_pool_t *pool);
+
+#if (defined(WIN32) && !defined(__MINGW32__)) || defined(DOXYGEN)
+/**
+ * Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_simple_t that gets/sets information from the user's
+ * ~/.subversion configuration directory. Allocate @a *provider in
+ * @a pool.
+ *
+ * This is like svn_auth_get_simple_provider(), except that, when
+ * running on Window 2000 or newer (or any other Windows version that
+ * includes the CryptoAPI), the provider encrypts the password before
+ * storing it to disk. On earlier versions of Windows, the provider
+ * does nothing.
+ *
+ * @since New in 1.4.
+ * @note This function is only available on Windows.
+ *
+ * @note An administrative password reset may invalidate the account's
+ * secret key. This function will detect that situation and behave as
+ * if the password were not cached at all.
+ */
+void
+svn_auth_get_windows_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_ssl_client_cert_pw_t that gets/sets information from the
+ * user's ~/.subversion configuration directory. Allocate @a *provider in
+ * @a pool.
+ *
+ * This is like svn_auth_get_ssl_client_cert_pw_file_provider(), except that
+ * when running on Window 2000 or newer, the provider encrypts the password
+ * before storing it to disk. On earlier versions of Windows, the provider
+ * does nothing.
+ *
+ * @since New in 1.6
+ * @note This function is only available on Windows.
+ *
+ * @note An administrative password reset may invalidate the account's
+ * secret key. This function will detect that situation and behave as
+ * if the password were not cached at all.
+ */
+void
+svn_auth_get_windows_ssl_client_cert_pw_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_ssl_server_trust_t, allocated in @a pool.
+ *
+ * This provider automatically validates ssl server certificates with
+ * the CryptoApi, like Internet Explorer and the Windows network API do.
+ * This allows the rollout of root certificates via Windows Domain
+ * policies, instead of Subversion specific configuration.
+ *
+ * @since New in 1.5.
+ * @note This function is only available on Windows.
+ */
+void
+svn_auth_get_windows_ssl_server_trust_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+#endif /* WIN32 && !__MINGW32__ || DOXYGEN */
+
+#if defined(DARWIN) || defined(DOXYGEN)
+/**
+ * Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_simple_t that gets/sets information from the user's
+ * ~/.subversion configuration directory. Allocate @a *provider in
+ * @a pool.
+ *
+ * This is like svn_auth_get_simple_provider(), except that the
+ * password is stored in the Mac OS KeyChain.
+ *
+ * @since New in 1.4
+ * @note This function is only available on Mac OS 10.2 and higher.
+ */
+void
+svn_auth_get_keychain_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_ssl_client_cert_pw_t that gets/sets information from the
+ * user's ~/.subversion configuration directory. Allocate @a *provider in
+ * @a pool.
+ *
+ * This is like svn_auth_get_ssl_client_cert_pw_file_provider(), except
+ * that the password is stored in the Mac OS KeyChain.
+ *
+ * @since New in 1.6
+ * @note This function is only available on Mac OS 10.2 and higher.
+ */
+void
+svn_auth_get_keychain_ssl_client_cert_pw_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+#endif /* DARWIN || DOXYGEN */
+
+#if (!defined(DARWIN) && !defined(WIN32)) || defined(DOXYGEN)
+/** A type of callback function for obtaining the GNOME Keyring password.
+ *
+ * In this callback, the client should ask the user for default keyring
+ * @a keyring_name password.
+ *
+ * The answer is returned in @a *keyring_password.
+ * @a baton is an implementation-specific closure.
+ * All allocations should be done in @a pool.
+ *
+ * @since New in 1.6
+ */
+typedef svn_error_t *(*svn_auth_gnome_keyring_unlock_prompt_func_t)(
+ char **keyring_password,
+ const char *keyring_name,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/** libsvn_auth_gnome_keyring-specific run-time parameters. */
+
+/** @brief The pointer to function which prompts user for GNOME Keyring
+ * password.
+ * The type of this pointer should be svn_auth_gnome_keyring_unlock_prompt_func_t. */
+#define SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC "gnome-keyring-unlock-prompt-func"
+
+/** @brief The baton which is passed to
+ * @c *SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC. */
+#define SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_BATON "gnome-keyring-unlock-prompt-baton"
+
+
+/**
+ * Get libsvn_auth_gnome_keyring version information.
+ *
+ * @since New in 1.6
+ */
+const svn_version_t *
+svn_auth_gnome_keyring_version(void);
+
+
+/**
+ * Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_simple_t that gets/sets information from the user's
+ * ~/.subversion configuration directory.
+ *
+ * This is like svn_client_get_simple_provider(), except that the
+ * password is stored in GNOME Keyring.
+ *
+ * If the GNOME Keyring is locked the provider calls
+ * @c *SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC in order to unlock
+ * the keyring.
+ *
+ * @c SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_BATON is passed to
+ * @c *SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC.
+ *
+ * Allocate @a *provider in @a pool.
+ *
+ * @since New in 1.6
+ * @note This function actually works only on systems with
+ * libsvn_auth_gnome_keyring and GNOME Keyring installed.
+ */
+void
+svn_auth_get_gnome_keyring_simple_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+/**
+ * Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_ssl_client_cert_pw_t that gets/sets information from the
+ * user's ~/.subversion configuration directory.
+ *
+ * This is like svn_client_get_ssl_client_cert_pw_file_provider(), except
+ * that the password is stored in GNOME Keyring.
+ *
+ * If the GNOME Keyring is locked the provider calls
+ * @c *SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC in order to unlock
+ * the keyring.
+ *
+ * @c SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_BATON is passed to
+ * @c *SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC.
+ *
+ * Allocate @a *provider in @a pool.
+ *
+ * @since New in 1.6
+ * @note This function actually works only on systems with
+ * libsvn_auth_gnome_keyring and GNOME Keyring installed.
+ */
+void
+svn_auth_get_gnome_keyring_ssl_client_cert_pw_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+/**
+ * Get libsvn_auth_kwallet version information.
+ *
+ * @since New in 1.6
+ */
+const svn_version_t *
+svn_auth_kwallet_version(void);
+
+
+/**
+ * Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_simple_t that gets/sets information from the user's
+ * ~/.subversion configuration directory. Allocate @a *provider in
+ * @a pool.
+ *
+ * This is like svn_client_get_simple_provider(), except that the
+ * password is stored in KWallet.
+ *
+ * @since New in 1.6
+ * @note This function actually works only on systems with libsvn_auth_kwallet
+ * and KWallet installed.
+ */
+void
+svn_auth_get_kwallet_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+/**
+ * Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_ssl_client_cert_pw_t that gets/sets information from the
+ * user's ~/.subversion configuration directory. Allocate @a *provider in
+ * @a pool.
+ *
+ * This is like svn_client_get_ssl_client_cert_pw_file_provider(), except
+ * that the password is stored in KWallet.
+ *
+ * @since New in 1.6
+ * @note This function actually works only on systems with libsvn_auth_kwallet
+ * and KWallet installed.
+ */
+void
+svn_auth_get_kwallet_ssl_client_cert_pw_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+#endif /* (!DARWIN && !WIN32) || DOXYGEN */
+
+#if !defined(WIN32) || defined(DOXYGEN)
+/**
+ * Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_simple_t that gets/sets information from the user's
+ * ~/.subversion configuration directory.
+ *
+ * This is like svn_client_get_simple_provider(), except that the
+ * password is obtained from gpg_agent, which will keep it in
+ * a memory cache.
+ *
+ * Allocate @a *provider in @a pool.
+ *
+ * @since New in 1.8
+ * @note This function actually works only on systems with
+ * GNU Privacy Guard installed.
+ */
+void
+svn_auth_get_gpg_agent_simple_provider
+ (svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+#endif /* !defined(WIN32) || defined(DOXYGEN) */
+
+
+/** Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_username_t that gets/sets information from a user's
+ * ~/.subversion configuration directory. Allocate @a *provider in
+ * @a pool.
+ *
+ * If a default username is available, @a *provider will honor it,
+ * and return it when svn_auth_first_credentials() is called. (See
+ * @c SVN_AUTH_PARAM_DEFAULT_USERNAME.)
+ *
+ * @since New in 1.4.
+ */
+void
+svn_auth_get_username_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+/** Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_ssl_server_trust_t, allocated in @a pool.
+ *
+ * @a *provider retrieves its credentials from the configuration
+ * mechanism. The returned credential is used to override SSL
+ * security on an error.
+ *
+ * @since New in 1.4.
+ */
+void
+svn_auth_get_ssl_server_trust_file_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+/** Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_ssl_client_cert_t, allocated in @a pool.
+ *
+ * @a *provider retrieves its credentials from the configuration
+ * mechanism. The returned credential is used to load the appropriate
+ * client certificate for authentication when requested by a server.
+ *
+ * @since New in 1.4.
+ */
+void
+svn_auth_get_ssl_client_cert_file_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+/** Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_ssl_client_cert_pw_t that gets/sets information from the user's
+ * ~/.subversion configuration directory.
+ *
+ * If the provider is going to save the passphrase unencrypted,
+ * it calls @a plaintext_passphrase_prompt_func, passing @a
+ * prompt_baton, before saving the passphrase.
+ *
+ * If @a plaintext_passphrase_prompt_func is NULL it is not called
+ * and the passphrase is not stored in plaintext.
+ * Client developers are highly encouraged to provide this callback
+ * to ensure their users are made aware of the fact that their passphrase
+ * is going to be stored unencrypted.
+ *
+ * Clients can however set the callback to NULL and set
+ * SVN_AUTH_PARAM_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT to SVN_CONFIG_FALSE or
+ * SVN_CONFIG_TRUE to enforce a certain behaviour.
+ *
+ * Allocate @a *provider in @a pool.
+ *
+ * @since New in 1.6.
+ */
+void
+svn_auth_get_ssl_client_cert_pw_file_provider2(
+ svn_auth_provider_object_t **provider,
+ svn_auth_plaintext_passphrase_prompt_func_t plaintext_passphrase_prompt_func,
+ void *prompt_baton,
+ apr_pool_t *pool);
+
+/** Like svn_auth_get_ssl_client_cert_pw_file_provider2, but without
+ * the ability to call the svn_auth_plaintext_passphrase_prompt_func_t
+ * callback, and the provider always assumes that it is not allowed
+ * to store the passphrase in plaintext.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.5 API.
+ * @since New in 1.4.
+ */
+SVN_DEPRECATED
+void
+svn_auth_get_ssl_client_cert_pw_file_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+/** Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_ssl_server_trust_t, allocated in @a pool.
+ *
+ * @a *provider retrieves its credentials by using the @a prompt_func
+ * and @a prompt_baton. The returned credential is used to override
+ * SSL security on an error.
+ *
+ * @since New in 1.4.
+ */
+void
+svn_auth_get_ssl_server_trust_prompt_provider(
+ svn_auth_provider_object_t **provider,
+ svn_auth_ssl_server_trust_prompt_func_t prompt_func,
+ void *prompt_baton,
+ apr_pool_t *pool);
+
+
+/** Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_ssl_client_cert_t, allocated in @a pool.
+ *
+ * @a *provider retrieves its credentials by using the @a prompt_func
+ * and @a prompt_baton. The returned credential is used to load the
+ * appropriate client certificate for authentication when requested by
+ * a server. The prompt will be retried @a retry_limit times. For
+ * infinite retries, set @a retry_limit to value less than 0.
+ *
+ * @since New in 1.4.
+ */
+void
+svn_auth_get_ssl_client_cert_prompt_provider(
+ svn_auth_provider_object_t **provider,
+ svn_auth_ssl_client_cert_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool);
+
+
+/** Set @a *provider to an authentication provider of type @c
+ * svn_auth_cred_ssl_client_cert_pw_t, allocated in @a pool.
+ *
+ * @a *provider retrieves its credentials by using the @a prompt_func
+ * and @a prompt_baton. The returned credential is used when a loaded
+ * client certificate is protected by a passphrase. The prompt will
+ * be retried @a retry_limit times. For infinite retries, set
+ * @a retry_limit to value less than 0.
+ *
+ * @since New in 1.4.
+ */
+void
+svn_auth_get_ssl_client_cert_pw_prompt_provider(
+ svn_auth_provider_object_t **provider,
+ svn_auth_ssl_client_cert_pw_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_AUTH_H */
diff --git a/contrib/subversion/subversion/include/svn_base64.h b/contrib/subversion/subversion/include/svn_base64.h
new file mode 100644
index 0000000..cc1820f
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_base64.h
@@ -0,0 +1,123 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_base64.h
+ * @brief Base64 encoding and decoding functions
+ */
+
+#ifndef SVN_BASE64_H
+#define SVN_BASE64_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_io.h" /* for svn_stream_t */
+#include "svn_string.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ *
+ *
+ * @defgroup base64 Base64 encoding/decoding functions
+ *
+ * @{
+ */
+
+/** Return a writable generic stream which will encode binary data in
+ * base64 format and write the encoded data to @a output. Be sure to
+ * close the stream when done writing in order to squeeze out the last
+ * bit of encoded data. The stream is allocated in @a pool.
+ */
+svn_stream_t *
+svn_base64_encode(svn_stream_t *output,
+ apr_pool_t *pool);
+
+/** Return a writable generic stream which will decode base64-encoded
+ * data and write the decoded data to @a output. The stream is allocated
+ * in @a pool.
+ */
+svn_stream_t *
+svn_base64_decode(svn_stream_t *output,
+ apr_pool_t *pool);
+
+
+/** Encode an @c svn_stringbuf_t into base64.
+ *
+ * A simple interface for encoding base64 data assuming we have all of
+ * it present at once. If @a break_lines is true, newlines will be
+ * inserted periodically; otherwise the string will only consist of
+ * base64 encoding characters. The returned string will be allocated
+ * from @a pool.
+ *
+ * @since New in 1.6.
+ */
+const svn_string_t *
+svn_base64_encode_string2(const svn_string_t *str,
+ svn_boolean_t break_lines,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_base64_encode_string2, but with @a break_lines always
+ * TRUE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+const svn_string_t *
+svn_base64_encode_string(const svn_string_t *str,
+ apr_pool_t *pool);
+
+/** Decode an @c svn_stringbuf_t from base64.
+ *
+ * A simple interface for decoding base64 data assuming we have all of
+ * it present at once. The returned string will be allocated from @c
+ * pool.
+ *
+ */
+const svn_string_t *
+svn_base64_decode_string(const svn_string_t *str,
+ apr_pool_t *pool);
+
+
+/** Return a base64-encoded checksum for finalized @a digest.
+ *
+ * @a digest contains @c APR_MD5_DIGESTSIZE bytes of finalized data.
+ * Allocate the returned checksum in @a pool.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_stringbuf_t *
+svn_base64_from_md5(unsigned char digest[],
+ apr_pool_t *pool);
+
+
+/** @} end group: Base64 encoding/decoding functions */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_BASE64_H */
diff --git a/contrib/subversion/subversion/include/svn_cache_config.h b/contrib/subversion/subversion/include/svn_cache_config.h
new file mode 100644
index 0000000..b03a079
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_cache_config.h
@@ -0,0 +1,90 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_cache_config.h
+ * @brief Configuration interface to internal Subversion caches.
+ */
+
+#ifndef SVN_CACHE_CONFIG_H
+#define SVN_CACHE_CONFIG_H
+
+#include <apr.h>
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** @defgroup svn_fs_cache_config caching configuration
+ * @{
+ * @since New in 1.7. */
+
+/** Cache resource settings. It controls what caches, in what size and
+ how they will be created. The settings apply for the whole process.
+
+ @since New in 1.7.
+ */
+typedef struct svn_cache_config_t
+{
+ /** total cache size in bytes. Please note that this is only soft limit
+ to the total application memory usage and will be exceeded due to
+ temporary objects and other program state.
+ May be 0, resulting in default caching code being used. */
+ apr_uint64_t cache_size;
+
+ /** maximum number of files kept open */
+ apr_size_t file_handle_count;
+
+ /** is this application guaranteed to be single-threaded? */
+ svn_boolean_t single_threaded;
+} svn_cache_config_t;
+
+/** Get the current cache configuration. If it has not been set,
+ this function will return the default settings.
+
+ @since New in 1.7.
+ */
+const svn_cache_config_t *
+svn_cache_config_get(void);
+
+/** Set the cache configuration. Please note that it may not change
+ the actual configuration *in use*. Therefore, call it before reading
+ data from any repo and call it only once.
+
+ This function is not thread-safe. Therefore, it should be called
+ from the processes' initialization code only.
+
+ @since New in 1.7.
+ */
+void
+svn_cache_config_set(const svn_cache_config_t *settings);
+
+/** @} */
+
+/** @} */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CACHE_CONFIG_H */
diff --git a/contrib/subversion/subversion/include/svn_checksum.h b/contrib/subversion/subversion/include/svn_checksum.h
new file mode 100644
index 0000000..d3271f5
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_checksum.h
@@ -0,0 +1,278 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_checksum.h
+ * @brief Subversion checksum routines
+ */
+
+#ifndef SVN_CHECKSUM_H
+#define SVN_CHECKSUM_H
+
+#include <apr.h> /* for apr_size_t */
+#include <apr_pools.h> /* for apr_pool_t */
+
+#include "svn_types.h" /* for svn_boolean_t, svn_error_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/**
+ * Various types of checksums.
+ *
+ * @since New in 1.6.
+ */
+typedef enum svn_checksum_kind_t
+{
+ /** The checksum is (or should be set to) an MD5 checksum. */
+ svn_checksum_md5,
+
+ /** The checksum is (or should be set to) a SHA1 checksum. */
+ svn_checksum_sha1
+} svn_checksum_kind_t;
+
+/**
+ * A generic checksum representation.
+ *
+ * @since New in 1.6.
+ */
+typedef struct svn_checksum_t
+{
+ /** The bytes of the checksum. */
+ const unsigned char *digest;
+
+ /** The type of the checksum. This should never be changed by consumers
+ of the APIs. */
+ svn_checksum_kind_t kind;
+} svn_checksum_t;
+
+/**
+ * Opaque type for creating checksums of data.
+ */
+typedef struct svn_checksum_ctx_t svn_checksum_ctx_t;
+
+/** Return a new checksum structure of type @a kind, initialized to the all-
+ * zeros value, allocated in @a pool.
+ *
+ * @since New in 1.6.
+ */
+svn_checksum_t *
+svn_checksum_create(svn_checksum_kind_t kind,
+ apr_pool_t *pool);
+
+/** Set @a checksum->digest to all zeros, which, by convention, matches
+ * all other checksums.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_checksum_clear(svn_checksum_t *checksum);
+
+/** Compare checksums @a checksum1 and @a checksum2. If their kinds do not
+ * match or if neither is all zeros, and their content does not match, then
+ * return FALSE; else return TRUE.
+ *
+ * @since New in 1.6.
+ */
+svn_boolean_t
+svn_checksum_match(const svn_checksum_t *checksum1,
+ const svn_checksum_t *checksum2);
+
+
+/**
+ * Return a deep copy of @a checksum, allocated in @a pool. If @a
+ * checksum is NULL then NULL is returned.
+ *
+ * @since New in 1.6.
+ */
+svn_checksum_t *
+svn_checksum_dup(const svn_checksum_t *checksum,
+ apr_pool_t *pool);
+
+
+/** Return the hex representation of @a checksum, allocating the string
+ * in @a pool.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_checksum_to_cstring_display(const svn_checksum_t *checksum,
+ apr_pool_t *pool);
+
+
+/** Return the hex representation of @a checksum, allocating the
+ * string in @a pool. If @a checksum->digest is all zeros (that is,
+ * 0, not '0') then return NULL. In 1.7+, @a checksum may be NULL
+ * and NULL will be returned in that case.
+ *
+ * @since New in 1.6.
+ * @note Passing NULL for @a checksum in 1.6 will cause a segfault.
+ */
+const char *
+svn_checksum_to_cstring(const svn_checksum_t *checksum,
+ apr_pool_t *pool);
+
+
+/** Return a serialized representation of @a checksum, allocated in
+ * @a result_pool. Temporary allocations are performed in @a scratch_pool.
+ *
+ * Note that @a checksum may not be NULL.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_checksum_serialize(const svn_checksum_t *checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Return @a checksum from the serialized format at @a data. The checksum
+ * will be allocated in @a result_pool, with any temporary allocations
+ * performed in @a scratch_pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_checksum_deserialize(const svn_checksum_t **checksum,
+ const char *data,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Parse the hex representation @a hex of a checksum of kind @a kind and
+ * set @a *checksum to the result, allocating in @a pool.
+ *
+ * If @a hex is @c NULL or is the all-zeros checksum, then set @a *checksum
+ * to @c NULL.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_checksum_parse_hex(svn_checksum_t **checksum,
+ svn_checksum_kind_t kind,
+ const char *hex,
+ apr_pool_t *pool);
+
+/**
+ * Return in @a *checksum the checksum of type @a kind for the bytes beginning
+ * at @a data, and going for @a len. @a *checksum is allocated in @a pool.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t kind,
+ const void *data,
+ apr_size_t len,
+ apr_pool_t *pool);
+
+
+/**
+ * Return in @a pool a newly allocated checksum populated with the checksum
+ * of type @a kind for the empty string.
+ *
+ * @since New in 1.6.
+ */
+svn_checksum_t *
+svn_checksum_empty_checksum(svn_checksum_kind_t kind,
+ apr_pool_t *pool);
+
+
+/**
+ * Create a new @c svn_checksum_ctx_t structure, allocated from @a pool for
+ * calculating checksums of type @a kind. @see svn_checksum_final()
+ *
+ * @since New in 1.6.
+ */
+svn_checksum_ctx_t *
+svn_checksum_ctx_create(svn_checksum_kind_t kind,
+ apr_pool_t *pool);
+
+/**
+ * Update the checksum represented by @a ctx, with @a len bytes starting at
+ * @a data.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_checksum_update(svn_checksum_ctx_t *ctx,
+ const void *data,
+ apr_size_t len);
+
+
+/**
+ * Finalize the checksum used when creating @a ctx, and put the resultant
+ * checksum in @a *checksum, allocated in @a pool.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_checksum_final(svn_checksum_t **checksum,
+ const svn_checksum_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Return the digest size of @a checksum.
+ *
+ * @since New in 1.6.
+ */
+apr_size_t
+svn_checksum_size(const svn_checksum_t *checksum);
+
+/**
+ * Return @c TRUE iff @a checksum matches the checksum for the empty
+ * string.
+ *
+ * @since New in 1.8.
+ */
+svn_boolean_t
+svn_checksum_is_empty_checksum(svn_checksum_t *checksum);
+
+
+/**
+ * Return an error of type #SVN_ERR_CHECKSUM_MISMATCH for @a actual and
+ * @a expected checksums which do not match. Use @a fmt, and the following
+ * parameters to populate the error message.
+ *
+ * @note This function does not actually check for the mismatch, it just
+ * constructs the error.
+ *
+ * @a scratch_pool is used for temporary allocations; the returned error
+ * will be allocated in its own pool (as is typical).
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_checksum_mismatch_err(const svn_checksum_t *expected,
+ const svn_checksum_t *actual,
+ apr_pool_t *scratch_pool,
+ const char *fmt,
+ ...)
+ __attribute__ ((format(printf, 4, 5)));
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CHECKSUM_H */
diff --git a/contrib/subversion/subversion/include/svn_client.h b/contrib/subversion/subversion/include/svn_client.h
new file mode 100644
index 0000000..d8eacdf
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_client.h
@@ -0,0 +1,6475 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_client.h
+ * @brief Subversion's client library
+ *
+ * Requires: The working copy library and repository access library.
+ * Provides: Broad wrappers around working copy library functionality.
+ * Used By: Client programs.
+ */
+
+#ifndef SVN_CLIENT_H
+#define SVN_CLIENT_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_getopt.h>
+#include <apr_file_io.h>
+#include <apr_time.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_wc.h"
+#include "svn_opt.h"
+#include "svn_ra.h"
+#include "svn_diff.h"
+#include "svn_auth.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/**
+ * Get libsvn_client version information.
+ *
+ * @since New in 1.1.
+ */
+const svn_version_t *
+svn_client_version(void);
+
+/** Client supporting functions
+ *
+ * @defgroup clnt_support Client supporting subsystem
+ *
+ * @{
+ */
+
+
+/*** Authentication stuff ***/
+
+/** The new authentication system allows the RA layer to "pull"
+ * information as needed from libsvn_client.
+ *
+ * @deprecated Replaced by the svn_auth_* functions.
+ * @see auth_fns
+ *
+ * @defgroup auth_fns_depr (deprecated) AuthZ client subsystem
+ *
+ * @{
+ */
+
+/** Create and return @a *provider, an authentication provider of type
+ * svn_auth_cred_simple_t that gets information by prompting the user
+ * with @a prompt_func and @a prompt_baton. Allocate @a *provider in
+ * @a pool.
+ *
+ * If both #SVN_AUTH_PARAM_DEFAULT_USERNAME and
+ * #SVN_AUTH_PARAM_DEFAULT_PASSWORD are defined as runtime
+ * parameters in the @c auth_baton, then @a *provider will return the
+ * default arguments when svn_auth_first_credentials() is called. If
+ * svn_auth_first_credentials() fails, then @a *provider will
+ * re-prompt @a retry_limit times (via svn_auth_next_credentials()).
+ * For infinite retries, set @a retry_limit to value less than 0.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_auth_get_simple_prompt_provider() instead.
+ */
+SVN_DEPRECATED
+void
+svn_client_get_simple_prompt_provider(
+ svn_auth_provider_object_t **provider,
+ svn_auth_simple_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool);
+
+
+/** Create and return @a *provider, an authentication provider of type
+ * #svn_auth_cred_username_t that gets information by prompting the
+ * user with @a prompt_func and @a prompt_baton. Allocate @a *provider
+ * in @a pool.
+ *
+ * If #SVN_AUTH_PARAM_DEFAULT_USERNAME is defined as a runtime
+ * parameter in the @c auth_baton, then @a *provider will return the
+ * default argument when svn_auth_first_credentials() is called. If
+ * svn_auth_first_credentials() fails, then @a *provider will
+ * re-prompt @a retry_limit times (via svn_auth_next_credentials()).
+ * For infinite retries, set @a retry_limit to value less than 0.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_auth_get_username_prompt_provider() instead.
+ */
+SVN_DEPRECATED
+void
+svn_client_get_username_prompt_provider(
+ svn_auth_provider_object_t **provider,
+ svn_auth_username_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool);
+
+
+/** Create and return @a *provider, an authentication provider of type
+ * #svn_auth_cred_simple_t that gets/sets information from the user's
+ * ~/.subversion configuration directory. Allocate @a *provider in
+ * @a pool.
+ *
+ * If a default username or password is available, @a *provider will
+ * honor them as well, and return them when
+ * svn_auth_first_credentials() is called. (see
+ * #SVN_AUTH_PARAM_DEFAULT_USERNAME and #SVN_AUTH_PARAM_DEFAULT_PASSWORD).
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_auth_get_simple_provider2() instead.
+ */
+SVN_DEPRECATED
+void
+svn_client_get_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+#if (defined(WIN32) && !defined(__MINGW32__)) || defined(DOXYGEN) || defined(CTYPESGEN) || defined(SWIG)
+/**
+ * Create and return @a *provider, an authentication provider of type
+ * #svn_auth_cred_simple_t that gets/sets information from the user's
+ * ~/.subversion configuration directory. Allocate @a *provider in
+ * @a pool.
+ *
+ * This is like svn_client_get_simple_provider(), except that, when
+ * running on Window 2000 or newer (or any other Windows version that
+ * includes the CryptoAPI), the provider encrypts the password before
+ * storing it to disk. On earlier versions of Windows, the provider
+ * does nothing.
+ *
+ * @since New in 1.2.
+ * @note This function is only available on Windows.
+ *
+ * @note An administrative password reset may invalidate the account's
+ * secret key. This function will detect that situation and behave as
+ * if the password were not cached at all.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_auth_get_windows_simple_provider() instead.
+ */
+SVN_DEPRECATED
+void
+svn_client_get_windows_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+#endif /* WIN32 && !__MINGW32__ || DOXYGEN || CTYPESGEN || SWIG */
+
+/** Create and return @a *provider, an authentication provider of type
+ * #svn_auth_cred_username_t that gets/sets information from a user's
+ * ~/.subversion configuration directory. Allocate @a *provider in
+ * @a pool.
+ *
+ * If a default username is available, @a *provider will honor it,
+ * and return it when svn_auth_first_credentials() is called. (see
+ * #SVN_AUTH_PARAM_DEFAULT_USERNAME).
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_auth_get_username_provider() instead.
+ */
+SVN_DEPRECATED
+void
+svn_client_get_username_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+/** Create and return @a *provider, an authentication provider of type
+ * #svn_auth_cred_ssl_server_trust_t, allocated in @a pool.
+ *
+ * @a *provider retrieves its credentials from the configuration
+ * mechanism. The returned credential is used to override SSL
+ * security on an error.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_auth_get_ssl_server_trust_file_provider() instead.
+ */
+SVN_DEPRECATED
+void
+svn_client_get_ssl_server_trust_file_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+/** Create and return @a *provider, an authentication provider of type
+ * #svn_auth_cred_ssl_client_cert_t, allocated in @a pool.
+ *
+ * @a *provider retrieves its credentials from the configuration
+ * mechanism. The returned credential is used to load the appropriate
+ * client certificate for authentication when requested by a server.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_auth_get_ssl_client_cert_file_provider() instead.
+ */
+SVN_DEPRECATED
+void
+svn_client_get_ssl_client_cert_file_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+/** Create and return @a *provider, an authentication provider of type
+ * #svn_auth_cred_ssl_client_cert_pw_t, allocated in @a pool.
+ *
+ * @a *provider retrieves its credentials from the configuration
+ * mechanism. The returned credential is used when a loaded client
+ * certificate is protected by a passphrase.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_auth_get_ssl_client_cert_pw_file_provider2() instead.
+ */
+SVN_DEPRECATED
+void
+svn_client_get_ssl_client_cert_pw_file_provider(
+ svn_auth_provider_object_t **provider,
+ apr_pool_t *pool);
+
+
+/** Create and return @a *provider, an authentication provider of type
+ * #svn_auth_cred_ssl_server_trust_t, allocated in @a pool.
+ *
+ * @a *provider retrieves its credentials by using the @a prompt_func
+ * and @a prompt_baton. The returned credential is used to override
+ * SSL security on an error.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_auth_get_ssl_server_trust_prompt_provider() instead.
+ */
+SVN_DEPRECATED
+void
+svn_client_get_ssl_server_trust_prompt_provider(
+ svn_auth_provider_object_t **provider,
+ svn_auth_ssl_server_trust_prompt_func_t prompt_func,
+ void *prompt_baton,
+ apr_pool_t *pool);
+
+
+/** Create and return @a *provider, an authentication provider of type
+ * #svn_auth_cred_ssl_client_cert_t, allocated in @a pool.
+ *
+ * @a *provider retrieves its credentials by using the @a prompt_func
+ * and @a prompt_baton. The returned credential is used to load the
+ * appropriate client certificate for authentication when requested by
+ * a server. The prompt will be retried @a retry_limit times.
+ * For infinite retries, set @a retry_limit to value less than 0.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_auth_get_ssl_client_cert_prompt_provider() instead.
+ */
+SVN_DEPRECATED
+void
+svn_client_get_ssl_client_cert_prompt_provider(
+ svn_auth_provider_object_t **provider,
+ svn_auth_ssl_client_cert_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool);
+
+
+/** Create and return @a *provider, an authentication provider of type
+ * #svn_auth_cred_ssl_client_cert_pw_t, allocated in @a pool.
+ *
+ * @a *provider retrieves its credentials by using the @a prompt_func
+ * and @a prompt_baton. The returned credential is used when a loaded
+ * client certificate is protected by a passphrase. The prompt will
+ * be retried @a retry_limit times. For infinite retries, set @a retry_limit
+ * to value less than 0.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_auth_get_ssl_client_cert_pw_prompt_provider() instead.
+ */
+SVN_DEPRECATED
+void
+svn_client_get_ssl_client_cert_pw_prompt_provider(
+ svn_auth_provider_object_t **provider,
+ svn_auth_ssl_client_cert_pw_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * Revisions and Peg Revisions
+ *
+ * @defgroup clnt_revisions Revisions and Peg Revisions
+ *
+ * A brief word on operative and peg revisions.
+ *
+ * If the kind of the peg revision is #svn_opt_revision_unspecified, then it
+ * defaults to #svn_opt_revision_head for URLs and #svn_opt_revision_working
+ * for local paths.
+ *
+ * For deeper insight, please see the
+ * <a href="http://svnbook.red-bean.com/nightly/en/svn.advanced.pegrevs.html">
+ * Peg and Operative Revisions</a> section of the Subversion Book.
+ */
+
+/**
+ * Commit operations
+ *
+ * @defgroup clnt_commit Client commit subsystem
+ *
+ * @{
+ */
+
+/** This is a structure which stores a filename and a hash of property
+ * names and values.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+typedef struct svn_client_proplist_item_t
+{
+ /** The name of the node on which these properties are set. */
+ svn_stringbuf_t *node_name;
+
+ /** A hash of (const char *) property names, and (svn_string_t *) property
+ * values. */
+ apr_hash_t *prop_hash;
+
+} svn_client_proplist_item_t;
+
+/**
+ * The callback invoked by svn_client_proplist4(). Each invocation
+ * provides the regular and/or inherited properties of @a path, which is
+ * either a working copy path or a URL. If @a prop_hash is not @c NULL, then
+ * it maps explicit <tt>const char *</tt> property names to
+ * <tt>svn_string_t *</tt> explicit property values. If @a inherited_props
+ * is not @c NULL, then it is a depth-first ordered array of
+ * #svn_prop_inherited_item_t * structures representing the
+ * properties inherited by @a path. Use @a scratch_pool for all temporary
+ * allocations.
+ *
+ * The #svn_prop_inherited_item_t->path_or_url members of the
+ * #svn_prop_inherited_item_t * structures in @a inherited_props are
+ * URLs if @a path is a URL or if @a path is a working copy path but the
+ * property represented by the structure is above the working copy root (i.e.
+ * the inherited property is from the cache). In all other cases the
+ * #svn_prop_inherited_item_t->path_or_url members are absolute working copy
+ * paths.
+ *
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_proplist_receiver2_t)(
+ void *baton,
+ const char *path,
+ apr_hash_t *prop_hash,
+ apr_array_header_t *inherited_props,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to #svn_proplist_receiver2_t, but doesn't return inherited
+ * properties.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_proplist_receiver_t)(
+ void *baton,
+ const char *path,
+ apr_hash_t *prop_hash,
+ apr_pool_t *pool);
+
+/**
+ * Return a duplicate of @a item, allocated in @a pool. No part of the new
+ * structure will be shared with @a item.
+ *
+ * @since New in 1.3.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_client_proplist_item_t *
+svn_client_proplist_item_dup(const svn_client_proplist_item_t *item,
+ apr_pool_t *pool);
+
+/** Information about commits passed back to client from this module.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+typedef struct svn_client_commit_info_t
+{
+ /** just-committed revision. */
+ svn_revnum_t revision;
+
+ /** server-side date of the commit. */
+ const char *date;
+
+ /** author of the commit. */
+ const char *author;
+
+} svn_client_commit_info_t;
+
+
+/**
+ * @name Commit state flags
+ * @brief State flags for use with the #svn_client_commit_item3_t structure
+ * (see the note about the namespace for that structure, which also
+ * applies to these flags).
+ * @{
+ */
+#define SVN_CLIENT_COMMIT_ITEM_ADD 0x01
+#define SVN_CLIENT_COMMIT_ITEM_DELETE 0x02
+#define SVN_CLIENT_COMMIT_ITEM_TEXT_MODS 0x04
+#define SVN_CLIENT_COMMIT_ITEM_PROP_MODS 0x08
+#define SVN_CLIENT_COMMIT_ITEM_IS_COPY 0x10
+/** @since New in 1.2. */
+#define SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN 0x20
+/** @since New in 1.8. */
+#define SVN_CLIENT_COMMIT_ITEM_MOVED_HERE 0x40
+/** @} */
+
+/** The commit candidate structure.
+ *
+ * In order to avoid backwards compatibility problems clients should use
+ * svn_client_commit_item3_create() to allocate and initialize this
+ * structure instead of doing so themselves.
+ *
+ * @since New in 1.5.
+ */
+typedef struct svn_client_commit_item3_t
+{
+ /* IMPORTANT: If you extend this structure, add new fields to the end. */
+
+ /** absolute working-copy path of item */
+ const char *path;
+
+ /** node kind (dir, file) */
+ svn_node_kind_t kind;
+
+ /** commit URL for this item */
+ const char *url;
+
+ /** revision of textbase */
+ svn_revnum_t revision;
+
+ /** copyfrom-url or NULL if not a copied item */
+ const char *copyfrom_url;
+
+ /** copyfrom-rev, valid when copyfrom_url != NULL */
+ svn_revnum_t copyfrom_rev;
+
+ /** state flags */
+ apr_byte_t state_flags;
+
+ /** An array of #svn_prop_t *'s, which are incoming changes from
+ * the repository to WC properties. These changes are applied
+ * post-commit.
+ *
+ * When adding to this array, allocate the #svn_prop_t and its
+ * contents in @c incoming_prop_changes->pool, so that it has the
+ * same lifetime as this data structure.
+ *
+ * See http://subversion.tigris.org/issues/show_bug.cgi?id=806 for a
+ * description of what would happen if the post-commit process
+ * didn't group these changes together with all other changes to the
+ * item.
+ */
+ apr_array_header_t *incoming_prop_changes;
+
+ /** An array of #svn_prop_t *'s, which are outgoing changes to
+ * make to properties in the repository. These extra property
+ * changes are declared pre-commit, and applied to the repository as
+ * part of a commit.
+ *
+ * When adding to this array, allocate the #svn_prop_t and its
+ * contents in @c outgoing_prop_changes->pool, so that it has the
+ * same lifetime as this data structure.
+ */
+ apr_array_header_t *outgoing_prop_changes;
+
+ /**
+ * When processing the commit this contains the relative path for
+ * the commit session. #NULL until the commit item is preprocessed.
+ * @since New in 1.7.
+ */
+ const char *session_relpath;
+
+ /**
+ * When committing a move, this contains the absolute path where
+ * the node was directly moved from. (If an ancestor at the original
+ * location was moved then it points to where the node itself was
+ * moved from; not the original location.)
+ * @since New in 1.8.
+ */
+ const char *moved_from_abspath;
+
+} svn_client_commit_item3_t;
+
+/** The commit candidate structure.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+typedef struct svn_client_commit_item2_t
+{
+ /** absolute working-copy path of item */
+ const char *path;
+
+ /** node kind (dir, file) */
+ svn_node_kind_t kind;
+
+ /** commit URL for this item */
+ const char *url;
+
+ /** revision of textbase */
+ svn_revnum_t revision;
+
+ /** copyfrom-url or NULL if not a copied item */
+ const char *copyfrom_url;
+
+ /** copyfrom-rev, valid when copyfrom_url != NULL */
+ svn_revnum_t copyfrom_rev;
+
+ /** state flags */
+ apr_byte_t state_flags;
+
+ /** Analogous to the #svn_client_commit_item3_t.incoming_prop_changes
+ * field.
+ */
+ apr_array_header_t *wcprop_changes;
+} svn_client_commit_item2_t;
+
+/** The commit candidate structure.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+typedef struct svn_client_commit_item_t
+{
+ /** absolute working-copy path of item */
+ const char *path;
+
+ /** node kind (dir, file) */
+ svn_node_kind_t kind;
+
+ /** commit URL for this item */
+ const char *url;
+
+ /** revision (copyfrom-rev if _IS_COPY) */
+ svn_revnum_t revision;
+
+ /** copyfrom-url */
+ const char *copyfrom_url;
+
+ /** state flags */
+ apr_byte_t state_flags;
+
+ /** Analogous to the #svn_client_commit_item3_t.incoming_prop_changes
+ * field.
+ */
+ apr_array_header_t *wcprop_changes;
+
+} svn_client_commit_item_t;
+
+/** Return a new commit item object, allocated in @a pool.
+ *
+ * In order to avoid backwards compatibility problems, this function
+ * is used to initialize and allocate the #svn_client_commit_item3_t
+ * structure rather than doing so explicitly, as the size of this
+ * structure may change in the future.
+ *
+ * @since New in 1.6.
+ */
+svn_client_commit_item3_t *
+svn_client_commit_item3_create(apr_pool_t *pool);
+
+/** Like svn_client_commit_item3_create() but with a stupid "const"
+ * qualifier on the returned structure, and it returns an error that
+ * will never happen.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_commit_item_create(const svn_client_commit_item3_t **item,
+ apr_pool_t *pool);
+
+/**
+ * Return a duplicate of @a item, allocated in @a pool. No part of the
+ * new structure will be shared with @a item, except for the adm_access
+ * member.
+ *
+ * @since New in 1.5.
+ */
+svn_client_commit_item3_t *
+svn_client_commit_item3_dup(const svn_client_commit_item3_t *item,
+ apr_pool_t *pool);
+
+/**
+ * Return a duplicate of @a item, allocated in @a pool. No part of the new
+ * structure will be shared with @a item.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_client_commit_item2_t *
+svn_client_commit_item2_dup(const svn_client_commit_item2_t *item,
+ apr_pool_t *pool);
+
+/** Callback type used by commit-y operations to get a commit log message
+ * from the caller.
+ *
+ * Set @a *log_msg to the log message for the commit, allocated in @a
+ * pool, or @c NULL if wish to abort the commit process. Set @a *tmp_file
+ * to the path of any temporary file which might be holding that log
+ * message, or @c NULL if no such file exists (though, if @a *log_msg is
+ * @c NULL, this value is undefined). The log message MUST be a UTF8
+ * string with LF line separators.
+ *
+ * @a commit_items is a read-only array of #svn_client_commit_item3_t
+ * structures, which may be fully or only partially filled-in,
+ * depending on the type of commit operation.
+ *
+ * @a baton is provided along with the callback for use by the handler.
+ *
+ * All allocations should be performed in @a pool.
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_client_get_commit_log3_t)(
+ const char **log_msg,
+ const char **tmp_file,
+ const apr_array_header_t *commit_items,
+ void *baton,
+ apr_pool_t *pool);
+
+/** Callback type used by commit-y operations to get a commit log message
+ * from the caller.
+ *
+ * Set @a *log_msg to the log message for the commit, allocated in @a
+ * pool, or @c NULL if wish to abort the commit process. Set @a *tmp_file
+ * to the path of any temporary file which might be holding that log
+ * message, or @c NULL if no such file exists (though, if @a *log_msg is
+ * @c NULL, this value is undefined). The log message MUST be a UTF8
+ * string with LF line separators.
+ *
+ * @a commit_items is a read-only array of #svn_client_commit_item2_t
+ * structures, which may be fully or only partially filled-in,
+ * depending on the type of commit operation.
+ *
+ * @a baton is provided along with the callback for use by the handler.
+ *
+ * All allocations should be performed in @a pool.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+typedef svn_error_t *(*svn_client_get_commit_log2_t)(
+ const char **log_msg,
+ const char **tmp_file,
+ const apr_array_header_t *commit_items,
+ void *baton,
+ apr_pool_t *pool);
+
+/** Callback type used by commit-y operations to get a commit log message
+ * from the caller.
+ *
+ * Set @a *log_msg to the log message for the commit, allocated in @a
+ * pool, or @c NULL if wish to abort the commit process. Set @a *tmp_file
+ * to the path of any temporary file which might be holding that log
+ * message, or @c NULL if no such file exists (though, if @a *log_msg is
+ * @c NULL, this value is undefined). The log message MUST be a UTF8
+ * string with LF line separators.
+ *
+ * @a commit_items is a read-only array of #svn_client_commit_item_t
+ * structures, which may be fully or only partially filled-in,
+ * depending on the type of commit operation.
+ *
+ * @a baton is provided along with the callback for use by the handler.
+ *
+ * All allocations should be performed in @a pool.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+typedef svn_error_t *(*svn_client_get_commit_log_t)(
+ const char **log_msg,
+ const char **tmp_file,
+ apr_array_header_t *commit_items,
+ void *baton,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * Client blame
+ *
+ * @defgroup clnt_blame Client blame functionality
+ *
+ * @{
+ */
+
+/** Callback type used by svn_client_blame5() to notify the caller
+ * that line @a line_no of the blamed file was last changed in @a revision
+ * which has the revision properties @a rev_props, and that the contents were
+ * @a line.
+ *
+ * @a start_revnum and @a end_revnum contain the start and end revision
+ * number of the entire blame operation, as determined from the repository
+ * inside svn_client_blame5(). This can be useful for the blame receiver
+ * to format the blame output.
+ *
+ * If svn_client_blame5() was called with @a include_merged_revisions set to
+ * TRUE, @a merged_revision, @a merged_rev_props and @a merged_path will be
+ * set, otherwise they will be NULL. @a merged_path will be set to the
+ * absolute repository path.
+ *
+ * All allocations should be performed in @a pool.
+ *
+ * @note If there is no blame information for this line, @a revision will be
+ * invalid and @a rev_props will be NULL. In this case @a local_change
+ * will be true if the reason there is no blame information is that the line
+ * was modified locally. In all other cases @a local_change will be false.
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_client_blame_receiver3_t)(
+ void *baton,
+ svn_revnum_t start_revnum,
+ svn_revnum_t end_revnum,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ apr_hash_t *rev_props,
+ svn_revnum_t merged_revision,
+ apr_hash_t *merged_rev_props,
+ const char *merged_path,
+ const char *line,
+ svn_boolean_t local_change,
+ apr_pool_t *pool);
+
+/**
+ * Similar to #svn_client_blame_receiver3_t, but with separate author and
+ * date revision properties instead of all revision properties, and without
+ * information about local changes.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_client_blame_receiver2_t)(
+ void *baton,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ svn_revnum_t merged_revision,
+ const char *merged_author,
+ const char *merged_date,
+ const char *merged_path,
+ const char *line,
+ apr_pool_t *pool);
+
+/**
+ * Similar to #svn_client_blame_receiver2_t, but without @a merged_revision,
+ * @a merged_author, @a merged_date, or @a merged_path members.
+ *
+ * @note New in 1.4 is that the line is defined to contain only the line
+ * content (and no [partial] EOLs; which was undefined in older versions).
+ * Using this callback with svn_client_blame() or svn_client_blame2()
+ * will still give you the old behaviour.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+typedef svn_error_t *(*svn_client_blame_receiver_t)(
+ void *baton,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ const char *line,
+ apr_pool_t *pool);
+
+
+/** @} */
+
+/**
+ * Client diff
+ *
+ * @defgroup clnt_diff Client diff functionality
+ *
+ * @{
+ */
+/** The difference type in an svn_diff_summarize_t structure.
+ *
+ * @since New in 1.4.
+ */
+typedef enum svn_client_diff_summarize_kind_t
+{
+ /** An item with no text modifications */
+ svn_client_diff_summarize_kind_normal,
+
+ /** An added item */
+ svn_client_diff_summarize_kind_added,
+
+ /** An item with text modifications */
+ svn_client_diff_summarize_kind_modified,
+
+ /** A deleted item */
+ svn_client_diff_summarize_kind_deleted
+} svn_client_diff_summarize_kind_t;
+
+
+/** A struct that describes the diff of an item. Passed to
+ * #svn_client_diff_summarize_func_t.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, users shouldn't allocate structures of this
+ * type, to preserve binary compatibility.
+ *
+ * @since New in 1.4.
+ */
+typedef struct svn_client_diff_summarize_t
+{
+ /** Path relative to the target. If the target is a file, path is
+ * the empty string. */
+ const char *path;
+
+ /** Change kind */
+ svn_client_diff_summarize_kind_t summarize_kind;
+
+ /** Properties changed? For consistency with 'svn status' output,
+ * this should be false if summarize_kind is _added or _deleted. */
+ svn_boolean_t prop_changed;
+
+ /** File or dir */
+ svn_node_kind_t node_kind;
+} svn_client_diff_summarize_t;
+
+/**
+ * Return a duplicate of @a diff, allocated in @a pool. No part of the new
+ * structure will be shared with @a diff.
+ *
+ * @since New in 1.4.
+ */
+svn_client_diff_summarize_t *
+svn_client_diff_summarize_dup(const svn_client_diff_summarize_t *diff,
+ apr_pool_t *pool);
+
+
+/** A callback used in svn_client_diff_summarize2() and
+ * svn_client_diff_summarize_peg2() for reporting a @a diff summary.
+ *
+ * All allocations should be performed in @a pool.
+ *
+ * @a baton is a closure object; it should be provided by the implementation,
+ * and passed by the caller.
+ *
+ * @since New in 1.4.
+ */
+typedef svn_error_t *(*svn_client_diff_summarize_func_t)(
+ const svn_client_diff_summarize_t *diff,
+ void *baton,
+ apr_pool_t *pool);
+
+
+
+/** @} */
+
+
+/**
+ * Client context
+ *
+ * @defgroup clnt_ctx Client context management
+ *
+ * @{
+ */
+
+/** A client context structure, which holds client specific callbacks,
+ * batons, serves as a cache for configuration options, and other various
+ * and sundry things. In order to avoid backwards compatibility problems
+ * clients should use svn_client_create_context() to allocate and
+ * initialize this structure instead of doing so themselves.
+ */
+typedef struct svn_client_ctx_t
+{
+ /** main authentication baton. */
+ svn_auth_baton_t *auth_baton;
+
+ /** notification callback function.
+ * This will be called by notify_func2() by default.
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ * Use @c notify_func2 instead. */
+ svn_wc_notify_func_t notify_func;
+
+ /** notification callback baton for notify_func()
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ * Use @c notify_baton2 instead */
+ void *notify_baton;
+
+ /** Log message callback function. NULL means that Subversion
+ * should try not attempt to fetch a log message.
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ * Use @c log_msg_func2 instead. */
+ svn_client_get_commit_log_t log_msg_func;
+
+ /** log message callback baton
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ * Use @c log_msg_baton2 instead. */
+ void *log_msg_baton;
+
+ /** a hash mapping of <tt>const char *</tt> configuration file names to
+ * #svn_config_t *'s. For example, the '~/.subversion/config' file's
+ * contents should have the key "config". May be left unset (or set to
+ * NULL) to use the built-in default settings and not use any configuration.
+ */
+ apr_hash_t *config;
+
+ /** a callback to be used to see if the client wishes to cancel the running
+ * operation. */
+ svn_cancel_func_t cancel_func;
+
+ /** a baton to pass to the cancellation callback. */
+ void *cancel_baton;
+
+ /** notification function, defaulting to a function that forwards
+ * to notify_func(). If @c NULL, it will not be invoked.
+ * @since New in 1.2. */
+ svn_wc_notify_func2_t notify_func2;
+
+ /** notification baton for notify_func2().
+ * @since New in 1.2. */
+ void *notify_baton2;
+
+ /** Log message callback function. NULL means that Subversion
+ * should try log_msg_func.
+ * @since New in 1.3. */
+ svn_client_get_commit_log2_t log_msg_func2;
+
+ /** callback baton for log_msg_func2
+ * @since New in 1.3. */
+ void *log_msg_baton2;
+
+ /** Notification callback for network progress information.
+ * May be NULL if not used.
+ * @since New in 1.3. */
+ svn_ra_progress_notify_func_t progress_func;
+
+ /** Callback baton for progress_func.
+ * @since New in 1.3. */
+ void *progress_baton;
+
+ /** Log message callback function. NULL means that Subversion
+ * should try @c log_msg_func2, then @c log_msg_func.
+ * @since New in 1.5. */
+ svn_client_get_commit_log3_t log_msg_func3;
+
+ /** The callback baton for @c log_msg_func3.
+ * @since New in 1.5. */
+ void *log_msg_baton3;
+
+ /** MIME types map.
+ * @since New in 1.5. */
+ apr_hash_t *mimetypes_map;
+
+ /** Conflict resolution callback and baton, if available.
+ * @since New in 1.5. */
+ svn_wc_conflict_resolver_func_t conflict_func;
+ void *conflict_baton;
+
+ /** Custom client name string, or @c NULL.
+ * @since New in 1.5. */
+ const char *client_name;
+
+ /** Conflict resolution callback and baton, if available. NULL means that
+ * subversion should try @c conflict_func.
+ * @since New in 1.7. */
+ svn_wc_conflict_resolver_func2_t conflict_func2;
+ void *conflict_baton2;
+
+ /** A working copy context for the client operation to use.
+ * This is initialized by svn_client_create_context() and should never
+ * be @c NULL.
+ *
+ * @since New in 1.7. */
+ svn_wc_context_t *wc_ctx;
+
+} svn_client_ctx_t;
+
+/** Initialize a client context.
+ * Set @a *ctx to a client context object, allocated in @a pool, that
+ * represents a particular instance of an svn client. @a cfg_hash is used
+ * to initialise the config member of the returned context object and should
+ * remain valid for the lifetime of the object. @a cfg_hash may be @c NULL,
+ * in which case it is ignored.
+ *
+ * In order to avoid backwards compatibility problems, clients must
+ * use this function to initialize and allocate the
+ * #svn_client_ctx_t structure rather than doing so themselves, as
+ * the size of this structure may change in the future.
+ *
+ * The current implementation never returns error, but callers should
+ * still check for error, for compatibility with future versions.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_create_context2(svn_client_ctx_t **ctx,
+ apr_hash_t *cfg_hash,
+ apr_pool_t *pool);
+
+
+/** Similar to svn_client_create_context2 but passes a NULL @a cfg_hash.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_create_context(svn_client_ctx_t **ctx,
+ apr_pool_t *pool);
+
+/** @} end group: Client context management */
+
+/**
+ * @name Authentication information file names
+ *
+ * Names of files that contain authentication information.
+ *
+ * These filenames are decided by libsvn_client, since this library
+ * implements all the auth-protocols; libsvn_wc does nothing but
+ * blindly store and retrieve these files from protected areas.
+ *
+ * @defgroup clnt_auth_filenames Client authentication file names
+ * @{
+ */
+#define SVN_CLIENT_AUTH_USERNAME "username"
+#define SVN_CLIENT_AUTH_PASSWORD "password"
+/** @} group end: Authentication information file names */
+
+/** Client argument processing
+ *
+ * @defgroup clnt_cmdline Client command-line processing
+ *
+ * @{
+ */
+
+/**
+ * Pull remaining target arguments from @a os into @a *targets_p,
+ * converting them to UTF-8, followed by targets from @a known_targets
+ * (which might come from, for example, the "--targets" command line option).
+ *
+ * Process each target in one of the following ways. For a repository-
+ * relative URL: resolve to a full URL, contacting the repository if
+ * necessary to do so, and then treat as a full URL. For a URL: do some
+ * IRI-to-URI encoding and some auto-escaping, and canonicalize. For a
+ * local path: canonicalize case and path separators.
+ *
+ * If @a keep_last_origpath_on_truepath_collision is TRUE, and there are
+ * exactly two targets which both case-canonicalize to the same path, the last
+ * target will be returned in the original non-case-canonicalized form.
+ *
+ * Allocate @a *targets_p and its elements in @a pool.
+ *
+ * @a ctx is required for possible repository authentication.
+ *
+ * If a path has the same name as a Subversion working copy
+ * administrative directory, return #SVN_ERR_RESERVED_FILENAME_SPECIFIED;
+ * if multiple reserved paths are encountered, return a chain of
+ * errors, all of which are #SVN_ERR_RESERVED_FILENAME_SPECIFIED. Do
+ * not return this type of error in a chain with any other type of
+ * error, and if this is the only type of error encountered, complete
+ * the operation before returning the error(s).
+ *
+ * @since New in 1.7
+ */
+svn_error_t *
+svn_client_args_to_target_array2(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t keep_last_origpath_on_truepath_collision,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_args_to_target_array2() but with
+ * @a keep_last_origpath_on_truepath_collision always set to FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_args_to_target_array(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} group end: Client command-line processing */
+
+/** @} */
+
+/**
+ * Client working copy management functions
+ *
+ * @defgroup clnt_wc Client working copy management
+ *
+ * @{
+ */
+
+/**
+ * @defgroup clnt_wc_checkout Checkout
+ *
+ * @{
+ */
+
+
+/**
+ * Checkout a working copy from a repository.
+ *
+ * @param[out] result_rev If non-NULL, the value of the revision checked
+ * out from the repository.
+ * @param[in] URL The repository URL of the checkout source.
+ * @param[in] path The root of the new working copy.
+ * @param[in] peg_revision The peg revision.
+ * @param[in] revision The operative revision.
+ * @param[in] depth The depth of the operation. If #svn_depth_unknown,
+ * then behave as if for #svn_depth_infinity, except in the case
+ * of resuming a previous checkout of @a path (i.e., updating),
+ * in which case use the depth of the existing working copy.
+ * @param[in] ignore_externals If @c TRUE, don't process externals
+ * definitions as part of this operation.
+ * @param[in] allow_unver_obstructions If @c TRUE, then tolerate existing
+ * unversioned items that obstruct incoming paths. Only
+ * obstructions of the same type (file or dir) as the added
+ * item are tolerated. The text of obstructing files is left
+ * as-is, effectively treating it as a user modification after
+ * the checkout. Working properties of obstructing items are
+ * set equal to the base properties. <br>
+ * If @c FALSE, then abort if there are any unversioned
+ * obstructing items.
+ * @param[in] ctx The standard client context, used for authentication and
+ * notification.
+ * @param[in] pool Used for any temporary allocation.
+ *
+ * @return A pointer to an #svn_error_t of the type (this list is not
+ * exhaustive): <br>
+ * #SVN_ERR_UNSUPPORTED_FEATURE if @a URL refers to a file rather
+ * than a directory; <br>
+ * #SVN_ERR_RA_ILLEGAL_URL if @a URL does not exist; <br>
+ * #SVN_ERR_CLIENT_BAD_REVISION if @a revision is not one of
+ * #svn_opt_revision_number, #svn_opt_revision_head, or
+ * #svn_opt_revision_date. <br>
+ * If no error occurred, return #SVN_NO_ERROR.
+ *
+ * @since New in 1.5.
+ *
+ * @see #svn_depth_t <br> #svn_client_ctx_t <br> @ref clnt_revisions for
+ * a discussion of operative and peg revisions.
+ */
+svn_error_t *
+svn_client_checkout3(svn_revnum_t *result_rev,
+ const char *URL,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_checkout3() but with @a allow_unver_obstructions
+ * always set to FALSE, and @a depth set according to @a recurse: if
+ * @a recurse is TRUE, @a depth is #svn_depth_infinity, if @a recurse
+ * is FALSE, @a depth is #svn_depth_files.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_checkout2(svn_revnum_t *result_rev,
+ const char *URL,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_externals,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_checkout2(), but with @a peg_revision
+ * always set to #svn_opt_revision_unspecified and
+ * @a ignore_externals always set to FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_checkout(svn_revnum_t *result_rev,
+ const char *URL,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+/** @} */
+
+/**
+ * @defgroup Update Bring a working copy up-to-date with a repository
+ *
+ * @{
+ *
+ */
+
+/**
+ * Update working trees @a paths to @a revision, authenticating with the
+ * authentication baton cached in @a ctx. @a paths is an array of const
+ * char * paths to be updated. Unversioned paths that are direct children
+ * of a versioned path will cause an update that attempts to add that path;
+ * other unversioned paths are skipped. If @a result_revs is not NULL,
+ * @a *result_revs will be set to an array of svn_revnum_t with each
+ * element set to the revision to which @a revision was resolved for the
+ * corresponding element of @a paths.
+ *
+ * @a revision must be of kind #svn_opt_revision_number,
+ * #svn_opt_revision_head, or #svn_opt_revision_date. If @a
+ * revision does not meet these requirements, return the error
+ * #SVN_ERR_CLIENT_BAD_REVISION.
+ *
+ * The paths in @a paths can be from multiple working copies from multiple
+ * repositories, but even if they all come from the same repository there
+ * is no guarantee that revision represented by #svn_opt_revision_head
+ * will remain the same as each path is updated.
+ *
+ * If @a ignore_externals is set, don't process externals definitions
+ * as part of this operation.
+ *
+ * If @a depth is #svn_depth_infinity, update fully recursively.
+ * Else if it is #svn_depth_immediates or #svn_depth_files, update
+ * each target and its file entries, but not its subdirectories. Else
+ * if #svn_depth_empty, update exactly each target, nonrecursively
+ * (essentially, update the target's properties).
+ *
+ * If @a depth is #svn_depth_unknown, take the working depth from
+ * @a paths and then behave as described above.
+ *
+ * If @a depth_is_sticky is set and @a depth is not
+ * #svn_depth_unknown, then in addition to updating PATHS, also set
+ * their sticky ambient depth value to @a depth.
+ *
+ * If @a allow_unver_obstructions is TRUE then the update tolerates
+ * existing unversioned items that obstruct added paths. Only
+ * obstructions of the same type (file or dir) as the added item are
+ * tolerated. The text of obstructing files is left as-is, effectively
+ * treating it as a user modification after the update. Working
+ * properties of obstructing items are set equal to the base properties.
+ * If @a allow_unver_obstructions is FALSE then the update will abort
+ * if there are any unversioned obstructing items.
+ *
+ * If @a adds_as_modification is TRUE, a local addition at the same path
+ * as an incoming addition of the same node kind results in a normal node
+ * with a possible local modification, instead of a tree conflict.
+ *
+ * If @a make_parents is TRUE, create any non-existent parent
+ * directories also by checking them out at depth=empty.
+ *
+ * If @a ctx->notify_func2 is non-NULL, invoke @a ctx->notify_func2 with
+ * @a ctx->notify_baton2 for each item handled by the update, and also for
+ * files restored from text-base. If @a ctx->cancel_func is non-NULL, invoke
+ * it passing @a ctx->cancel_baton at various places during the update.
+ *
+ * Use @a pool for any temporary allocation.
+ *
+ * @todo Multiple Targets
+ * - Up for debate: an update on multiple targets is *not* atomic.
+ * Right now, svn_client_update only takes one path. What's
+ * debatable is whether this should ever change. On the one hand,
+ * it's kind of losing to have the client application loop over
+ * targets and call svn_client_update() on each one; each call to
+ * update initializes a whole new repository session (network
+ * overhead, etc.) On the other hand, it's a very simple
+ * implementation, and allows for the possibility that different
+ * targets may come from different repositories.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_update4(apr_array_header_t **result_revs,
+ const apr_array_header_t *paths,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t make_parents,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_update4() but with @a make_parents always set
+ * to FALSE and @a adds_as_modification set to TRUE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_update3(apr_array_header_t **result_revs,
+ const apr_array_header_t *paths,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_update3() but with @a allow_unver_obstructions
+ * always set to FALSE, @a depth_is_sticky to FALSE, and @a depth set
+ * according to @a recurse: if @a recurse is TRUE, set @a depth to
+ * #svn_depth_infinity, if @a recurse is FALSE, set @a depth to
+ * #svn_depth_files.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_update2(apr_array_header_t **result_revs,
+ const apr_array_header_t *paths,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_externals,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_update2() except that it accepts only a single
+ * target in @a path, returns a single revision if @a result_rev is
+ * not NULL, and @a ignore_externals is always set to FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_update(svn_revnum_t *result_rev,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+/** @} */
+
+/**
+ * @defgroup Switch Switch a working copy to another location.
+ *
+ * @{
+ */
+
+/**
+ * Switch an existing working copy directory to a different repository
+ * location.
+ *
+ * This is normally used to switch a working copy directory over to another
+ * line of development, such as a branch or a tag. Switching an existing
+ * working copy directory is more efficient than checking out @a url from
+ * scratch.
+ *
+ * @param[out] result_rev If non-NULL, the value of the revision to which
+ * the working copy was actually switched.
+ * @param[in] path The directory to be switched. This need not be the
+ * root of a working copy.
+ * @param[in] url The repository URL to switch to.
+ * @param[in] peg_revision The peg revision.
+ * @param[in] revision The operative revision.
+ * @param[in] depth The depth of the operation. If #svn_depth_infinity,
+ * switch fully recursively. Else if #svn_depth_immediates,
+ * switch @a path and its file children (if any), and
+ * switch subdirectories but do not update them. Else if
+ * #svn_depth_files, switch just file children, ignoring
+ * subdirectories completely. Else if #svn_depth_empty,
+ * switch just @a path and touch nothing underneath it.
+ * @param[in] depth_is_sticky If @c TRUE, and @a depth is not
+ * #svn_depth_unknown, then in addition to switching @a path, also
+ * set its sticky ambient depth value to @a depth.
+ * @param[in] ignore_externals If @c TRUE, don't process externals
+ * definitions as part of this operation.
+ * @param[in] allow_unver_obstructions If @c TRUE, then tolerate existing
+ * unversioned items that obstruct incoming paths. Only
+ * obstructions of the same type (file or dir) as the added
+ * item are tolerated. The text of obstructing files is left
+ * as-is, effectively treating it as a user modification after
+ * the checkout. Working properties of obstructing items are
+ * set equal to the base properties. <br>
+ * If @c FALSE, then abort if there are any unversioned
+ * obstructing items.
+ * @param[in] ignore_ancestry If @c FALSE, then verify that the file
+ * or directory at @a path shares some common version control
+ * ancestry with the switch URL location (represented by the
+ * combination of @a url, @a peg_revision, and @a revision),
+ * and returning #SVN_ERR_CLIENT_UNRELATED_RESOURCES if they
+ * do not. If @c TRUE, no such sanity checks are performed.
+ *
+ * @param[in] ctx The standard client context, used for authentication and
+ * notification. The notifier is invoked for paths affected by
+ * the switch, and also for files which may be restored from the
+ * pristine store after being previously removed from the working
+ * copy.
+ * @param[in] pool Used for any temporary allocation.
+ *
+ * @return A pointer to an #svn_error_t of the type (this list is not
+ * exhaustive): <br>
+ * #SVN_ERR_CLIENT_BAD_REVISION if @a revision is not one of
+ * #svn_opt_revision_number, #svn_opt_revision_head, or
+ * #svn_opt_revision_date. <br>
+ * If no error occurred, return #SVN_NO_ERROR.
+ *
+ * @since New in 1.7.
+ *
+ * @see #svn_depth_t <br> #svn_client_ctx_t <br> @ref clnt_revisions for
+ * a discussion of operative and peg revisions.
+ */
+svn_error_t *
+svn_client_switch3(svn_revnum_t *result_rev,
+ const char *path,
+ const char *url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t ignore_ancestry,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_switch3() but with @a ignore_ancestry always
+ * set to TRUE.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_switch2(svn_revnum_t *result_rev,
+ const char *path,
+ const char *url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_switch2() but with @a allow_unver_obstructions,
+ * @a ignore_externals, and @a depth_is_sticky always set to FALSE,
+ * and @a depth set according to @a recurse: if @a recurse is TRUE,
+ * set @a depth to #svn_depth_infinity, if @a recurse is FALSE, set
+ * @a depth to #svn_depth_files.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_switch(svn_revnum_t *result_rev,
+ const char *path,
+ const char *url,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Add Begin versioning files/directories in a working copy.
+ *
+ * @{
+ */
+
+/**
+ * Schedule a working copy @a path for addition to the repository.
+ *
+ * If @a depth is #svn_depth_empty, add just @a path and nothing
+ * below it. If #svn_depth_files, add @a path and any file
+ * children of @a path. If #svn_depth_immediates, add @a path, any
+ * file children, and any immediate subdirectories (but nothing
+ * underneath those subdirectories). If #svn_depth_infinity, add
+ * @a path and everything under it fully recursively.
+ *
+ * @a path's parent must be under revision control already (unless
+ * @a add_parents is TRUE), but @a path is not.
+ *
+ * If @a force is not set and @a path is already under version
+ * control, return the error #SVN_ERR_ENTRY_EXISTS. If @a force is
+ * set, do not error on already-versioned items. When used on a
+ * directory in conjunction with a @a depth value greater than
+ * #svn_depth_empty, this has the effect of scheduling for addition
+ * any unversioned files and directories scattered within even a
+ * versioned tree (up to @a depth).
+ *
+ * If @a ctx->notify_func2 is non-NULL, then for each added item, call
+ * @a ctx->notify_func2 with @a ctx->notify_baton2 and the path of the
+ * added item.
+ *
+ * If @a no_ignore is FALSE, don't add any file or directory (or recurse
+ * into any directory) that is unversioned and found by recursion (as
+ * opposed to being the explicit target @a path) and whose name matches the
+ * svn:ignore property on its parent directory or the global-ignores list in
+ * @a ctx->config. If @a no_ignore is TRUE, do include such files and
+ * directories. (Note that an svn:ignore property can influence this
+ * behaviour only when recursing into an already versioned directory with @a
+ * force.)
+ *
+ * If @a no_autoprops is TRUE, don't set any autoprops on added files. If
+ * @a no_autoprops is FALSE then all added files have autprops set as per
+ * the auto-props list in @a ctx->config and the value of any
+ * @c SVN_PROP_INHERITABLE_AUTO_PROPS properties inherited by the nearest
+ * parents of @a path which are already under version control.
+ *
+ * If @a add_parents is TRUE, recurse up @a path's directory and look for
+ * a versioned directory. If found, add all intermediate paths between it
+ * and @a path. If not found, return #SVN_ERR_CLIENT_NO_VERSIONED_PARENT.
+ *
+ * @a scratch_pool is used for temporary allocations only.
+ *
+ * @par Important:
+ * This is a *scheduling* operation. No changes will
+ * happen to the repository until a commit occurs. This scheduling
+ * can be removed with svn_client_revert2().
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_add5(const char *path,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t add_parents,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_client_add5(), but with @a no_autoprops always set to
+ * FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_add4(const char *path,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t no_ignore,
+ svn_boolean_t add_parents,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_add4(), but with @a add_parents always set to
+ * FALSE and @a depth set according to @a recursive: if TRUE, then
+ * @a depth is #svn_depth_infinity, if FALSE, then #svn_depth_empty.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_add3(const char *path,
+ svn_boolean_t recursive,
+ svn_boolean_t force,
+ svn_boolean_t no_ignore,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_add3(), but with @a no_ignore always set to
+ * FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_add2(const char *path,
+ svn_boolean_t recursive,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_add2(), but with @a force always set to FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_add(const char *path,
+ svn_boolean_t recursive,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Mkdir Create directories in a working copy or repository.
+ *
+ * @{
+ */
+
+/** Create a directory, either in a repository or a working copy.
+ *
+ * @a paths is an array of (const char *) paths, either all local WC paths
+ * or all URLs.
+ *
+ * If @a paths contains URLs, use the authentication baton in @a ctx
+ * and @a message to immediately attempt to commit the creation of the
+ * directories in @a paths in the repository.
+ *
+ * Else, create the directories on disk, and attempt to schedule them
+ * for addition (using svn_client_add(), whose docstring you should
+ * read).
+ *
+ * If @a make_parents is TRUE, create any non-existent parent directories
+ * also.
+ *
+ * If non-NULL, @a revprop_table is a hash table holding additional,
+ * custom revision properties (<tt>const char *</tt> names mapped to
+ * <tt>svn_string_t *</tt> values) to be set on the new revision in
+ * the event that this is a committing operation. This table cannot
+ * contain any standard Subversion properties.
+ *
+ * @a ctx->log_msg_func3/@a ctx->log_msg_baton3 are a callback/baton
+ * combo that this function can use to query for a commit log message
+ * when one is needed.
+ *
+ * If @a ctx->notify_func2 is non-NULL, when the directory has been created
+ * (successfully) in the working copy, call @a ctx->notify_func2 with
+ * @a ctx->notify_baton2 and the path of the new directory. Note that this is
+ * only called for items added to the working copy.
+ *
+ * If @a commit_callback is non-NULL, then for each successful commit, call
+ * @a commit_callback with @a commit_baton and a #svn_commit_info_t for
+ * the commit.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_mkdir4(const apr_array_header_t *paths,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_mkdir4(), but returns the commit info in
+ * @a *commit_info_p rather than through a callback function.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_mkdir3(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Same as svn_client_mkdir3(), but with @a make_parents always FALSE,
+ * and @a revprop_table always NULL.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_mkdir2(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_client_mkdir2(), but takes the #svn_client_commit_info_t
+ * type for @a commit_info_p.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_mkdir(svn_client_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Delete Remove files/directories from a working copy or repository.
+ *
+ * @{
+ */
+
+/** Delete items from a repository or working copy.
+ *
+ * @a paths is an array of (const char *) paths, either all local WC paths
+ * or all URLs.
+ *
+ * If the paths in @a paths are URLs, use the authentication baton in
+ * @a ctx and @a ctx->log_msg_func3/@a ctx->log_msg_baton3 to
+ * immediately attempt to commit a deletion of the URLs from the
+ * repository. Every path must belong to the same repository.
+ *
+ * Else, schedule the working copy paths in @a paths for removal from
+ * the repository. Each path's parent must be under revision control.
+ * This is just a *scheduling* operation. No changes will happen to
+ * the repository until a commit occurs. This scheduling can be
+ * removed with svn_client_revert2(). If a path is a file it is
+ * immediately removed from the working copy. If the path is a
+ * directory it will remain in the working copy but all the files, and
+ * all unversioned items, it contains will be removed. If @a force is
+ * not set then this operation will fail if any path contains locally
+ * modified and/or unversioned items. If @a force is set such items
+ * will be deleted.
+ *
+ * If the paths are working copy paths and @a keep_local is TRUE then
+ * the paths will not be removed from the working copy, only scheduled
+ * for removal from the repository. Once the scheduled deletion is
+ * committed, they will appear as unversioned paths in the working copy.
+ *
+ * If non-NULL, @a revprop_table is a hash table holding additional,
+ * custom revision properties (<tt>const char *</tt> names mapped to
+ * <tt>svn_string_t *</tt> values) to be set on the new revision in
+ * the event that this is a committing operation. This table cannot
+ * contain any standard Subversion properties.
+ *
+ * @a ctx->log_msg_func3/@a ctx->log_msg_baton3 are a callback/baton
+ * combo that this function can use to query for a commit log message
+ * when one is needed.
+ *
+ * If @a ctx->notify_func2 is non-NULL, then for each item deleted, call
+ * @a ctx->notify_func2 with @a ctx->notify_baton2 and the path of the deleted
+ * item.
+ *
+ * If @a commit_callback is non-NULL, then for each successful commit, call
+ * @a commit_callback with @a commit_baton and a #svn_commit_info_t for
+ * the commit.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_delete4(const apr_array_header_t *paths,
+ svn_boolean_t force,
+ svn_boolean_t keep_local,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_delete4(), but returns the commit info in
+ * @a *commit_info_p rather than through a callback function.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_delete3(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_boolean_t force,
+ svn_boolean_t keep_local,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_delete3(), but with @a keep_local always set
+ * to FALSE, and @a revprop_table passed as NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_delete2(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_delete2(), but takes the #svn_client_commit_info_t
+ * type for @a commit_info_p.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_delete(svn_client_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/** @} */
+
+/**
+ * @defgroup Import Import files into the repository.
+ *
+ * @{
+ */
+
+/**
+ * The callback invoked by svn_client_import5() before adding a node to the
+ * list of nodes to be imported.
+ *
+ * @a baton is the value passed to @a svn_client_import5 as filter_baton.
+ *
+ * The callback receives the @a local_abspath for each node and the @a dirent
+ * for it when walking the directory tree. Only the kind of node, including
+ * special status is available in @a dirent.
+ *
+ * Implementations can set @a *filtered to TRUE, to make the import
+ * process omit the node and (if the node is a directory) all its
+ * descendants.
+ *
+ * @a scratch_pool can be used for temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_client_import_filter_func_t)(
+ void *baton,
+ svn_boolean_t *filtered,
+ const char *local_abspath,
+ const svn_io_dirent2_t *dirent,
+ apr_pool_t *scratch_pool);
+
+/** Import file or directory @a path into repository directory @a url at
+ * head, authenticating with the authentication baton cached in @a ctx,
+ * and using @a ctx->log_msg_func3/@a ctx->log_msg_baton3 to get a log message
+ * for the (implied) commit. If some components of @a url do not exist
+ * then create parent directories as necessary.
+ *
+ * This function reads an unversioned tree from disk and skips any ".svn"
+ * directories. Even if a file or directory being imported is part of an
+ * existing WC, this function sees it as unversioned and does not notice any
+ * existing Subversion properties in it.
+ *
+ * If @a path is a directory, the contents of that directory are
+ * imported directly into the directory identified by @a url. Note that the
+ * directory @a path itself is not imported -- that is, the basename of
+ * @a path is not part of the import.
+ *
+ * If @a path is a file, then the dirname of @a url is the directory
+ * receiving the import. The basename of @a url is the filename in the
+ * repository. In this case if @a url already exists, return error.
+ *
+ * If @a ctx->notify_func2 is non-NULL, then call @a ctx->notify_func2 with
+ * @a ctx->notify_baton2 as the import progresses, with any of the following
+ * actions: #svn_wc_notify_commit_added,
+ * #svn_wc_notify_commit_postfix_txdelta.
+ *
+ * Use @a scratch_pool for any temporary allocation.
+ *
+ * If non-NULL, @a revprop_table is a hash table holding additional,
+ * custom revision properties (<tt>const char *</tt> names mapped to
+ * <tt>svn_string_t *</tt> values) to be set on the new revision.
+ * This table cannot contain any standard Subversion properties.
+ *
+ * @a ctx->log_msg_func3/@a ctx->log_msg_baton3 are a callback/baton
+ * combo that this function can use to query for a commit log message
+ * when one is needed.
+ *
+ * If @a depth is #svn_depth_empty, import just @a path and nothing
+ * below it. If #svn_depth_files, import @a path and any file
+ * children of @a path. If #svn_depth_immediates, import @a path, any
+ * file children, and any immediate subdirectories (but nothing
+ * underneath those subdirectories). If #svn_depth_infinity, import
+ * @a path and everything under it fully recursively.
+ *
+ * If @a no_ignore is @c FALSE, don't import any file or directory (or
+ * recurse into any directory) that is found by recursion (as opposed to
+ * being the explicit target @a path) and whose name matches the
+ * global-ignores list in @a ctx->config. If @a no_ignore is @c TRUE, do
+ * include such files and directories. (Note that svn:ignore properties are
+ * not involved, as auto-props cannot set properties on directories and even
+ * if the target is part of a WC the import ignores any existing
+ * properties.)
+ *
+ * If @a no_autoprops is TRUE, don't set any autoprops on imported files. If
+ * @a no_autoprops is FALSE then all imported files have autprops set as per
+ * the auto-props list in @a ctx->config and the value of any
+ * @c SVN_PROP_INHERITABLE_AUTO_PROPS properties inherited by and explicitly set
+ * on @a url if @a url is already under versioned control, or the nearest parents
+ * of @a path which are already under version control if not.
+ *
+ * If @a ignore_unknown_node_types is @c FALSE, ignore files of which the
+ * node type is unknown, such as device files and pipes.
+ *
+ * If @a filter_callback is non-NULL, call it for each node that isn't ignored
+ * for other reasons with @a filter_baton, to allow third party to ignore
+ * specific nodes during importing.
+ *
+ * If @a commit_callback is non-NULL, then for each successful commit, call
+ * @a commit_callback with @a commit_baton and a #svn_commit_info_t for
+ * the commit.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_import5(const char *path,
+ const char *url,
+ svn_depth_t depth,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t ignore_unknown_node_types,
+ const apr_hash_t *revprop_table,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_client_import5(), but without support for an optional
+ * @a filter_callback and @a no_autoprops always set to FALSE.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_import4(const char *path,
+ const char *url,
+ svn_depth_t depth,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_unknown_node_types,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_import4(), but returns the commit info in
+ * @a *commit_info_p rather than through a callback function.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_import3(svn_commit_info_t **commit_info_p,
+ const char *path,
+ const char *url,
+ svn_depth_t depth,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_unknown_node_types,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_import3(), but with @a ignore_unknown_node_types
+ * always set to @c FALSE, @a revprop_table passed as NULL, and @a
+ * depth set according to @a nonrecursive: if TRUE, then @a depth is
+ * #svn_depth_files, else #svn_depth_infinity.
+ *
+ * @since New in 1.3.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_import2(svn_commit_info_t **commit_info_p,
+ const char *path,
+ const char *url,
+ svn_boolean_t nonrecursive,
+ svn_boolean_t no_ignore,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_import2(), but with @a no_ignore always set
+ * to FALSE and using the #svn_client_commit_info_t type for
+ * @a commit_info_p.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_import(svn_client_commit_info_t **commit_info_p,
+ const char *path,
+ const char *url,
+ svn_boolean_t nonrecursive,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Commit Commit local modifications to the repository.
+ *
+ * @{
+ */
+
+/**
+ * Commit files or directories into repository, authenticating with
+ * the authentication baton cached in @a ctx, and using
+ * @a ctx->log_msg_func3/@a ctx->log_msg_baton3 to obtain the log message.
+ * Set @a *commit_info_p to the results of the commit, allocated in @a pool.
+ *
+ * @a targets is an array of <tt>const char *</tt> paths to commit. They
+ * need not be canonicalized nor condensed; this function will take care of
+ * that. If @a targets has zero elements, then do nothing and return
+ * immediately without error.
+ *
+ * If non-NULL, @a revprop_table is a hash table holding additional,
+ * custom revision properties (<tt>const char *</tt> names mapped to
+ * <tt>svn_string_t *</tt> values) to be set on the new revision.
+ * This table cannot contain any standard Subversion properties.
+ *
+ * If @a ctx->notify_func2 is non-NULL, then call @a ctx->notify_func2 with
+ * @a ctx->notify_baton2 as the commit progresses, with any of the following
+ * actions: #svn_wc_notify_commit_modified, #svn_wc_notify_commit_added,
+ * #svn_wc_notify_commit_deleted, #svn_wc_notify_commit_replaced,
+ * #svn_wc_notify_commit_copied, #svn_wc_notify_commit_copied_replaced,
+ * #svn_wc_notify_commit_postfix_txdelta.
+ *
+ * If @a depth is #svn_depth_infinity, commit all changes to and
+ * below named targets. If @a depth is #svn_depth_empty, commit
+ * only named targets (that is, only property changes on named
+ * directory targets, and property and content changes for named file
+ * targets). If @a depth is #svn_depth_files, behave as above for
+ * named file targets, and for named directory targets, commit
+ * property changes on a named directory and all changes to files
+ * directly inside that directory. If #svn_depth_immediates, behave
+ * as for #svn_depth_files, and for subdirectories of any named
+ * directory target commit as though for #svn_depth_empty.
+ *
+ * Unlock paths in the repository, unless @a keep_locks is TRUE.
+ *
+ * @a changelists is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items that are committed;
+ * that is, don't commit anything unless it's a member of one of those
+ * changelists. After the commit completes successfully, remove
+ * changelist associations from the targets, unless @a
+ * keep_changelists is set. If @a changelists is
+ * empty (or altogether @c NULL), no changelist filtering occurs.
+ *
+ * If @a commit_as_operations is set to FALSE, when a copy is committed
+ * all changes below the copy are always committed at the same time
+ * (independent of the value of @a depth). If @a commit_as_operations is
+ * #TRUE, changes to descendants are only committed if they are itself
+ * included via @a depth and targets.
+ *
+ * If @a include_file_externals and/or @a include_dir_externals are #TRUE,
+ * also commit all file and/or dir externals (respectively) that are reached
+ * by recursion, except for those externals which:
+ * - have a fixed revision, or
+ * - come from a different repository root URL (dir externals).
+ * These flags affect only recursion; externals that directly appear in @a
+ * targets are always included in the commit.
+ *
+ * ### TODO: currently, file externals hidden inside an unversioned dir are
+ * skipped deliberately, because we can't commit those yet.
+ * See STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW.
+ *
+ * ### TODO: With @c depth_immediates, this function acts as if
+ * @a include_dir_externals was passed #FALSE, but caller expects
+ * immediate child dir externals to be included @c depth_empty.
+ *
+ * When @a commit_as_operations is #TRUE it is possible to delete a node and
+ * all its descendants by selecting just the root of the deletion. If it is
+ * set to #FALSE this will raise an error.
+ *
+ * If @a commit_callback is non-NULL, then for each successful commit, call
+ * @a commit_callback with @a commit_baton and a #svn_commit_info_t for
+ * the commit.
+ *
+ * @note #svn_depth_unknown and #svn_depth_exclude must not be passed
+ * for @a depth.
+ *
+ * Use @a pool for any temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_commit6(const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_boolean_t keep_locks,
+ svn_boolean_t keep_changelists,
+ svn_boolean_t commit_as_operations,
+ svn_boolean_t include_file_externals,
+ svn_boolean_t include_dir_externals,
+ const apr_array_header_t *changelists,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_commit6(), but passes @a include_file_externals as
+ * FALSE and @a include_dir_externals as FALSE.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_commit5(const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_boolean_t keep_locks,
+ svn_boolean_t keep_changelists,
+ svn_boolean_t commit_as_operations,
+ const apr_array_header_t *changelists,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_commit5(), but returns the commit info in
+ * @a *commit_info_p rather than through a callback function. Does not use
+ * #svn_wc_notify_commit_copied or #svn_wc_notify_commit_copied_replaced
+ * (preferring #svn_wc_notify_commit_added and
+ * #svn_wc_notify_commit_replaced, respectively, instead).
+ *
+ * Also, if no error is returned and @a (*commit_info_p)->revision is set to
+ * #SVN_INVALID_REVNUM, then the commit was a no-op; nothing needed to
+ * be committed.
+ *
+ * Sets @a commit_as_operations to FALSE to match Subversion 1.6's behavior.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_commit4(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_boolean_t keep_locks,
+ svn_boolean_t keep_changelists,
+ const apr_array_header_t *changelists,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_commit4(), but always with NULL for
+ * @a changelist_name, FALSE for @a keep_changelist, NULL for @a
+ * revprop_table, and @a depth set according to @a recurse: if @a
+ * recurse is TRUE, use #svn_depth_infinity, else #svn_depth_empty.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ *
+ * @since New in 1.3.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_commit3(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *targets,
+ svn_boolean_t recurse,
+ svn_boolean_t keep_locks,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_commit3(), but uses #svn_client_commit_info_t
+ * for @a commit_info_p.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ *
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_commit2(svn_client_commit_info_t **commit_info_p,
+ const apr_array_header_t *targets,
+ svn_boolean_t recurse,
+ svn_boolean_t keep_locks,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_commit2(), but with @a keep_locks set to
+ * TRUE and @a nonrecursive instead of @a recurse.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_commit(svn_client_commit_info_t **commit_info_p,
+ const apr_array_header_t *targets,
+ svn_boolean_t nonrecursive,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Status Report interesting information about paths in the \
+ * working copy.
+ *
+ * @{
+ */
+
+/**
+ * Structure for holding the "status" of a working copy item.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, to preserve binary compatibility, users
+ * should not directly allocate structures of this type.
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_client_status_t
+{
+ /** The kind of node as recorded in the working copy */
+ svn_node_kind_t kind;
+
+ /** The absolute path to the node */
+ const char *local_abspath;
+
+ /** The actual size of the working file on disk, or SVN_INVALID_FILESIZE
+ * if unknown (or if the item isn't a file at all). */
+ svn_filesize_t filesize;
+
+ /** If the path is under version control, versioned is TRUE, otherwise
+ * FALSE. */
+ svn_boolean_t versioned;
+
+ /** Set to TRUE if the node is the victim of some kind of conflict. */
+ svn_boolean_t conflicted;
+
+ /** The status of the node, based on the restructuring changes and if the
+ * node has no restructuring changes the text and prop status. */
+ enum svn_wc_status_kind node_status;
+
+ /** The status of the text of the node, not including restructuring changes.
+ * Valid values are: svn_wc_status_none, svn_wc_status_normal,
+ * svn_wc_status_modified and svn_wc_status_conflicted. */
+ enum svn_wc_status_kind text_status;
+
+ /** The status of the node's properties.
+ * Valid values are: svn_wc_status_none, svn_wc_status_normal,
+ * svn_wc_status_modified and svn_wc_status_conflicted. */
+ enum svn_wc_status_kind prop_status;
+
+ /** A node can be 'locked' if a working copy update is in progress or
+ * was interrupted. */
+ svn_boolean_t wc_is_locked;
+
+ /** A file or directory can be 'copied' if it's scheduled for
+ * addition-with-history (or part of a subtree that is scheduled as such.).
+ */
+ svn_boolean_t copied;
+
+ /** The URL of the repository root. */
+ const char *repos_root_url;
+
+ /** The UUID of the repository */
+ const char *repos_uuid;
+
+ /** The in-repository path relative to the repository root. */
+ const char *repos_relpath;
+
+ /** Base revision. */
+ svn_revnum_t revision;
+
+ /** Last revision this was changed */
+ svn_revnum_t changed_rev;
+
+ /** Date of last commit. */
+ apr_time_t changed_date;
+
+ /** Last commit author of this item */
+ const char *changed_author;
+
+ /** A file or directory can be 'switched' if the switch command has been
+ * used. If this is TRUE, then file_external will be FALSE.
+ */
+ svn_boolean_t switched;
+
+ /** If the item is a file that was added to the working copy with an
+ * svn:externals; if file_external is TRUE, then switched is always
+ * FALSE.
+ */
+ svn_boolean_t file_external;
+
+ /** The locally present lock. (Values of path, token, owner, comment and
+ * are available if a lock is present) */
+ const svn_lock_t *lock;
+
+ /** Which changelist this item is part of, or NULL if not part of any. */
+ const char *changelist;
+
+ /** The depth of the node as recorded in the working copy
+ * (#svn_depth_unknown for files or when no depth is recorded) */
+ svn_depth_t depth;
+
+ /**
+ * @defgroup svn_wc_status_ood WC out-of-date info from the repository
+ * @{
+ *
+ * When the working copy item is out-of-date compared to the
+ * repository, the following fields represent the state of the
+ * youngest revision of the item in the repository. If the working
+ * copy is not out of date, the fields are initialized as described
+ * below.
+ */
+
+ /** Set to the node kind of the youngest commit, or #svn_node_none
+ * if not out of date. */
+ svn_node_kind_t ood_kind;
+
+ /** The status of the node, based on the text status if the node has no
+ * restructuring changes */
+ enum svn_wc_status_kind repos_node_status;
+
+ /** The node's text status in the repository. */
+ enum svn_wc_status_kind repos_text_status;
+
+ /** The node's property status in the repository. */
+ enum svn_wc_status_kind repos_prop_status;
+
+ /** The node's lock in the repository, if any. */
+ const svn_lock_t *repos_lock;
+
+ /** Set to the youngest committed revision, or #SVN_INVALID_REVNUM
+ * if not out of date. */
+ svn_revnum_t ood_changed_rev;
+
+ /** Set to the most recent commit date, or @c 0 if not out of date. */
+ apr_time_t ood_changed_date;
+
+ /** Set to the user name of the youngest commit, or @c NULL if not
+ * out of date or non-existent. Because a non-existent @c
+ * svn:author property has the same behavior as an out-of-date
+ * working copy, examine @c ood_changed_rev to determine whether
+ * the working copy is out of date. */
+ const char *ood_changed_author;
+
+ /** @} */
+
+ /** Reserved for libsvn_client's internal use; this value is only to be used
+ * for libsvn_client backwards compatibility wrappers. This value may be NULL
+ * or to other data in future versions. */
+ const void *backwards_compatibility_baton;
+
+ /** Set to the local absolute path that this node was moved from, if this
+ * file or directory has been moved here locally and is the root of that
+ * move. Otherwise set to NULL.
+ *
+ * This will be NULL for moved-here nodes that are just part of a subtree
+ * that was moved along (and are not themselves a root of a different move
+ * operation).
+ *
+ * @since New in 1.8. */
+ const char *moved_from_abspath;
+
+ /** Set to the local absolute path that this node was moved to, if this file
+ * or directory has been moved away locally and corresponds to the root
+ * of the destination side of the move. Otherwise set to NULL.
+ *
+ * Note: Saying just "root" here could be misleading. For example:
+ * svn mv A AA;
+ * svn mv AA/B BB;
+ * creates a situation where A/B is moved-to BB, but one could argue that
+ * the move source's root actually was AA/B. Note that, as far as the
+ * working copy is concerned, above case is exactly identical to:
+ * svn mv A/B BB;
+ * svn mv A AA;
+ * In both situations, @a moved_to_abspath would be set for nodes A (moved
+ * to AA) and A/B (moved to BB), only.
+ *
+ * This will be NULL for moved-away nodes that were just part of a subtree
+ * that was moved along (and are not themselves a root of a different move
+ * operation).
+ *
+ * @since New in 1.8. */
+ const char *moved_to_abspath;
+
+ /* NOTE! Please update svn_client_status_dup() when adding new fields here. */
+} svn_client_status_t;
+
+/**
+ * Return a duplicate of @a status, allocated in @a result_pool. No part of the new
+ * structure will be shared with @a status.
+ *
+ * @since New in 1.7.
+ */
+svn_client_status_t *
+svn_client_status_dup(const svn_client_status_t *status,
+ apr_pool_t *result_pool);
+
+/**
+ * A callback for reporting a @a status about @a path (which may be an
+ * absolute or relative path).
+ *
+ * @a baton is a closure object; it should be provided by the
+ * implementation, and passed by the caller.
+ *
+ * @a scratch_pool will be cleared between invocations to the callback.
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_client_status_func_t)(
+ void *baton,
+ const char *path,
+ const svn_client_status_t *status,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Given @a path to a working copy directory (or single file), call
+ * @a status_func/status_baton with a set of #svn_wc_status_t *
+ * structures which describe the status of @a path, and its children
+ * (recursing according to @a depth).
+ *
+ * - If @a get_all is set, retrieve all entries; otherwise,
+ * retrieve only "interesting" entries (local mods and/or
+ * out of date).
+ *
+ * - If @a update is set, contact the repository and augment the
+ * status structures with information about out-of-dateness (with
+ * respect to @a revision). Also, if @a result_rev is not @c NULL,
+ * set @a *result_rev to the actual revision against which the
+ * working copy was compared (@a *result_rev is not meaningful unless
+ * @a update is set).
+ *
+ * If @a no_ignore is @c FALSE, don't report any file or directory (or
+ * recurse into any directory) that is found by recursion (as opposed to
+ * being the explicit target @a path) and whose name matches the
+ * svn:ignore property on its parent directory or the global-ignores
+ * list in @a ctx->config. If @a no_ignore is @c TRUE, report each such
+ * file or directory with the status code #svn_wc_status_ignored.
+ *
+ * If @a ignore_externals is not set, then recurse into externals
+ * definitions (if any exist) after handling the main target. This
+ * calls the client notification function (in @a ctx) with the
+ * #svn_wc_notify_status_external action before handling each externals
+ * definition, and with #svn_wc_notify_status_completed
+ * after each.
+ *
+ * If @a depth_as_sticky is set and @a depth is not
+ * #svn_depth_unknown, then the status is calculated as if depth_is_sticky
+ * was passed to an equivalent update command.
+ *
+ * @a changelists is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose statuses are
+ * reported; that is, don't report status about any item unless
+ * it's a member of one of those changelists. If @a changelists is
+ * empty (or altogether @c NULL), no changelist filtering occurs.
+ *
+ * If @a path is an absolute path then the @c path parameter passed in each
+ * call to @a status_func will be an absolute path.
+ *
+ * All temporary allocations are performed in @a scratch_pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_status5(svn_revnum_t *result_rev,
+ svn_client_ctx_t *ctx,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t update,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t depth_as_sticky,
+ const apr_array_header_t *changelists,
+ svn_client_status_func_t status_func,
+ void *status_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Same as svn_client_status5(), but using #svn_wc_status_func3_t
+ * instead of #svn_client_status_func_t and depth_as_sticky set to TRUE.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_status4(svn_revnum_t *result_rev,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_wc_status_func3_t status_func,
+ void *status_baton,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t update,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_externals,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_client_status4(), but using an #svn_wc_status_func2_t
+ * instead of an #svn_wc_status_func3_t.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_status3(svn_revnum_t *result_rev,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_wc_status_func2_t status_func,
+ void *status_baton,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t update,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_externals,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Like svn_client_status3(), except with @a changelists passed as @c
+ * NULL, and with @a recurse instead of @a depth. If @a recurse is
+ * TRUE, behave as if for #svn_depth_infinity; else if @a recurse is
+ * FALSE, behave as if for #svn_depth_immediates.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_status2(svn_revnum_t *result_rev,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_wc_status_func2_t status_func,
+ void *status_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t get_all,
+ svn_boolean_t update,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_externals,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_status2(), but with @a ignore_externals
+ * always set to FALSE, taking the #svn_wc_status_func_t type
+ * instead of the #svn_wc_status_func2_t type for @a status_func,
+ * and requiring @a *revision to be non-const even though it is
+ * treated as constant.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_status(svn_revnum_t *result_rev,
+ const char *path,
+ svn_opt_revision_t *revision,
+ svn_wc_status_func_t status_func,
+ void *status_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t get_all,
+ svn_boolean_t update,
+ svn_boolean_t no_ignore,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Log View information about previous revisions of an object.
+ *
+ * @{
+ */
+
+/**
+ * Invoke @a receiver with @a receiver_baton on each log message from
+ * each (#svn_opt_revision_range_t *) range in @a revision_ranges in turn,
+ * inclusive (but never invoke @a receiver on a given log message more
+ * than once).
+ *
+ * @a targets contains either a URL followed by zero or more relative
+ * paths, or 1 working copy path, as <tt>const char *</tt>, for which log
+ * messages are desired. @a receiver is invoked only on messages whose
+ * revisions involved a change to some path in @a targets. @a peg_revision
+ * indicates in which revision @a targets are valid. If @a peg_revision is
+ * #svn_opt_revision_unspecified, it defaults to #svn_opt_revision_head
+ * for URLs or #svn_opt_revision_working for WC paths.
+ *
+ * If @a limit is non-zero only invoke @a receiver on the first @a limit
+ * logs.
+ *
+ * If @a discover_changed_paths is set, then the @c changed_paths and @c
+ * changed_paths2 fields in the @c log_entry argument to @a receiver will be
+ * populated on each invocation. @note The @c text_modified and @c
+ * props_modified fields of the changed paths structure may have the value
+ * #svn_tristate_unknown if the repository does not report that information.
+ *
+ * If @a strict_node_history is set, copy history (if any exists) will
+ * not be traversed while harvesting revision logs for each target.
+ *
+ * If @a include_merged_revisions is set, log information for revisions
+ * which have been merged to @a targets will also be returned.
+ *
+ * If @a revprops is NULL, retrieve all revision properties; else, retrieve
+ * only the revision properties named by the (const char *) array elements
+ * (i.e. retrieve none if the array is empty).
+ *
+ * Use @a pool for any temporary allocation.
+ *
+ * If @a ctx->notify_func2 is non-NULL, then call @a ctx->notify_func2/baton2
+ * with a 'skip' signal on any unversioned targets.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_client_log5(const apr_array_header_t *targets,
+ const svn_opt_revision_t *peg_revision,
+ const apr_array_header_t *revision_ranges,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_log5(), but takes explicit start and end parameters
+ * instead of an array of revision ranges.
+ *
+ * @deprecated Provided for compatibility with the 1.5 API.
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_log4(const apr_array_header_t *targets,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_log4(), but using #svn_log_message_receiver_t
+ * instead of #svn_log_entry_receiver_t. Also, @a
+ * include_merged_revisions is set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log.
+ *
+ * @deprecated Provided for compatibility with the 1.4 API.
+ * @since New in 1.4.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_log3(const apr_array_header_t *targets,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_log3(), but with the @c kind field of
+ * @a peg_revision set to #svn_opt_revision_unspecified.
+ *
+ * @par Important:
+ * A special case for the revision range HEAD:1, which was present
+ * in svn_client_log(), has been removed from svn_client_log2(). Instead, it
+ * is expected that callers will specify the range HEAD:0, to avoid a
+ * #SVN_ERR_FS_NO_SUCH_REVISION error when invoked against an empty repository
+ * (i.e. one not containing a revision 1).
+ *
+ * @deprecated Provided for compatibility with the 1.3 API.
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_log2(const apr_array_header_t *targets,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_log2(), but with @a limit set to 0, and the
+ * following special case:
+ *
+ * Special case for repositories at revision 0:
+ *
+ * If @a start->kind is #svn_opt_revision_head, and @a end->kind is
+ * #svn_opt_revision_number && @a end->number is @c 1, then handle an
+ * empty (no revisions) repository specially: instead of erroring
+ * because requested revision 1 when the highest revision is 0, just
+ * invoke @a receiver on revision 0, passing @c NULL for changed paths and
+ * empty strings for the author and date. This is because that
+ * particular combination of @a start and @a end usually indicates the
+ * common case of log invocation -- the user wants to see all log
+ * messages from youngest to oldest, where the oldest commit is
+ * revision 1. That works fine, except when there are no commits in
+ * the repository, hence this special case.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_log(const apr_array_header_t *targets,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Blame Show modification information about lines in a file.
+ *
+ * @{
+ */
+
+/**
+ * Invoke @a receiver with @a receiver_baton on each line-blame item
+ * associated with revision @a end of @a path_or_url, using @a start
+ * as the default source of all blame. @a peg_revision indicates in
+ * which revision @a path_or_url is valid. If @a peg_revision->kind
+ * is #svn_opt_revision_unspecified, then it defaults to
+ * #svn_opt_revision_head for URLs or #svn_opt_revision_working for
+ * WC targets.
+ *
+ * If @a start->kind or @a end->kind is #svn_opt_revision_unspecified,
+ * return the error #SVN_ERR_CLIENT_BAD_REVISION. If either are
+ * #svn_opt_revision_working, return the error
+ * #SVN_ERR_UNSUPPORTED_FEATURE. If any of the revisions of @a
+ * path_or_url have a binary mime-type, return the error
+ * #SVN_ERR_CLIENT_IS_BINARY_FILE, unless @a ignore_mime_type is TRUE,
+ * in which case blame information will be generated regardless of the
+ * MIME types of the revisions.
+ *
+ * Use @a diff_options to determine how to compare different revisions of the
+ * target.
+ *
+ * If @a include_merged_revisions is TRUE, also return data based upon
+ * revisions which have been merged to @a path_or_url.
+ *
+ * Use @a pool for any temporary allocation.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_blame5(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ const svn_diff_file_options_t *diff_options,
+ svn_boolean_t ignore_mime_type,
+ svn_boolean_t include_merged_revisions,
+ svn_client_blame_receiver3_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_blame5(), but with #svn_client_blame_receiver3_t
+ * as the receiver.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ *
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_blame4(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ const svn_diff_file_options_t *diff_options,
+ svn_boolean_t ignore_mime_type,
+ svn_boolean_t include_merged_revisions,
+ svn_client_blame_receiver2_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_blame4(), but with @a include_merged_revisions set
+ * to FALSE, and using a #svn_client_blame_receiver2_t as the receiver.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.4 API.
+ *
+ * @since New in 1.4.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_blame3(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ const svn_diff_file_options_t *diff_options,
+ svn_boolean_t ignore_mime_type,
+ svn_client_blame_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_blame3(), but with @a diff_options set to
+ * default options as returned by svn_diff_file_options_parse() and
+ * @a ignore_mime_type set to FALSE.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API.
+ *
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_blame2(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ svn_client_blame_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_blame2() except that @a peg_revision is always
+ * the same as @a end.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_blame(const char *path_or_url,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ svn_client_blame_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Diff Generate differences between paths.
+ *
+ * @{
+ */
+
+/**
+ * Produce diff output which describes the delta between
+ * @a path_or_url1/@a revision1 and @a path_or_url2/@a revision2. Print
+ * the output of the diff to @a outstream, and any errors to @a
+ * errstream. @a path_or_url1 and @a path_or_url2 can be either
+ * working-copy paths or URLs.
+ *
+ * If @a relative_to_dir is not @c NULL, the original path and
+ * modified path will have the @a relative_to_dir stripped from the
+ * front of the respective paths. If @a relative_to_dir is @c NULL,
+ * paths will not be modified. If @a relative_to_dir is not
+ * @c NULL but @a relative_to_dir is not a parent path of the target,
+ * an error is returned. Finally, if @a relative_to_dir is a URL, an
+ * error will be returned.
+ *
+ * If either @a revision1 or @a revision2 has an `unspecified' or
+ * unrecognized `kind', return #SVN_ERR_CLIENT_BAD_REVISION.
+ *
+ * @a path_or_url1 and @a path_or_url2 must both represent the same node
+ * kind -- that is, if @a path_or_url1 is a directory, @a path_or_url2
+ * must also be, and if @a path_or_url1 is a file, @a path_or_url2 must
+ * also be.
+ *
+ * If @a depth is #svn_depth_infinity, diff fully recursively.
+ * Else if it is #svn_depth_immediates, diff the named paths and
+ * their file children (if any), and diff properties of
+ * subdirectories, but do not descend further into the subdirectories.
+ * Else if #svn_depth_files, behave as if for #svn_depth_immediates
+ * except don't diff properties of subdirectories. If
+ * #svn_depth_empty, diff exactly the named paths but nothing
+ * underneath them.
+ *
+ * Use @a ignore_ancestry to control whether or not items being
+ * diffed will be checked for relatedness first. Unrelated items
+ * are typically transmitted to the editor as a deletion of one thing
+ * and the addition of another, but if this flag is TRUE, unrelated
+ * items will be diffed as if they were related.
+ *
+ * If @a no_diff_added is TRUE, then no diff output will be generated
+ * on added files.
+ *
+ * If @a no_diff_deleted is TRUE, then no diff output will be
+ * generated on deleted files.
+ *
+ * If @a show_copies_as_adds is TRUE, then copied files will not be diffed
+ * against their copyfrom source, and will appear in the diff output
+ * in their entirety, as if they were newly added.
+ * ### BUGS: For a repos-repos diff, this is ignored. Instead, a file is
+ * diffed against its copyfrom source iff the file is the diff target
+ * and not if some parent directory is the diff target. For a repos-WC
+ * diff, this is ignored if the file is the diff target.
+ *
+ * If @a use_git_diff_format is TRUE, then the git's extended diff format
+ * will be used.
+ * ### Do we need to say more about the format? A reference perhaps?
+ *
+ * If @a ignore_properties is TRUE, do not show property differences.
+ * If @a properties_only is TRUE, show only property changes.
+ * The above two options are mutually exclusive. It is an error to set
+ * both to TRUE.
+ *
+ * Generated headers are encoded using @a header_encoding.
+ *
+ * Diff output will not be generated for binary files, unless @a
+ * ignore_content_type is TRUE, in which case diffs will be shown
+ * regardless of the content types.
+ *
+ * @a diff_options (an array of <tt>const char *</tt>) is used to pass
+ * additional command line options to the diff processes invoked to compare
+ * files. @a diff_options is allowed to be @c NULL, in which case a value
+ * for this option might still be obtained from the Subversion configuration
+ * file via client context @a ctx.
+ *
+ * The authentication baton cached in @a ctx is used to communicate with
+ * the repository.
+ *
+ * @a changelists is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose differences are
+ * reported; that is, don't generate diffs about any item unless
+ * it's a member of one of those changelists. If @a changelists is
+ * empty (or altogether @c NULL), no changelist filtering occurs.
+ *
+ * @note Changelist filtering only applies to diffs in which at least
+ * one side of the diff represents working copy data.
+ *
+ * @note @a header_encoding doesn't affect headers generated by external
+ * diff programs.
+ *
+ * @note @a relative_to_dir doesn't affect the path index generated by
+ * external diff programs.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_diff6(const apr_array_header_t *diff_options,
+ const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_added,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t ignore_properties,
+ svn_boolean_t properties_only,
+ svn_boolean_t use_git_diff_format,
+ const char *header_encoding,
+ svn_stream_t *outstream,
+ svn_stream_t *errstream,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** Similar to svn_client_diff6(), but with @a outfile and @a errfile,
+ * instead of @a outstream and @a errstream, and with @a
+ * no_diff_added, @a ignore_properties, and @a properties_only always
+ * passed as @c FALSE (which means that additions and property changes
+ * are always transmitted).
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * @since New in 1.7.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff5(const apr_array_header_t *diff_options,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t use_git_diff_format,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_diff5(), but with @a show_copies_as_adds set to
+ * @c FALSE and @a use_git_diff_format set to @c FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff4(const apr_array_header_t *diff_options,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_diff4(), but with @a changelists passed as @c
+ * NULL, and @a depth set according to @a recurse: if @a recurse is
+ * TRUE, set @a depth to #svn_depth_infinity, if @a recurse is
+ * FALSE, set @a depth to #svn_depth_empty.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ * @since New in 1.3.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff3(const apr_array_header_t *diff_options,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_diff3(), but with @a header_encoding set to
+ * @c APR_LOCALE_CHARSET.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff2(const apr_array_header_t *diff_options,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_diff2(), but with @a ignore_content_type
+ * always set to FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff(const apr_array_header_t *diff_options,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Produce diff output which describes the delta between the filesystem
+ * object @a path_or_url in peg revision @a peg_revision, as it changed
+ * between @a start_revision and @a end_revision. @a path_or_url can
+ * be either a working-copy path or URL.
+ *
+ * If @a peg_revision is #svn_opt_revision_unspecified, behave
+ * identically to svn_client_diff6(), using @a path_or_url for both of that
+ * function's @a path_or_url1 and @a path_or_url2 arguments.
+ *
+ * All other options are handled identically to svn_client_diff6().
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_diff_peg6(const apr_array_header_t *diff_options,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_added,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t ignore_properties,
+ svn_boolean_t properties_only,
+ svn_boolean_t use_git_diff_format,
+ const char *header_encoding,
+ svn_stream_t *outstream,
+ svn_stream_t *errstream,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** Similar to svn_client_diff6_peg6(), but with @a outfile and @a errfile,
+ * instead of @a outstream and @a errstream, and with @a
+ * no_diff_added, @a ignore_properties, and @a properties_only always
+ * passed as @c FALSE (which means that additions and property changes
+ * are always transmitted).
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * @since New in 1.7.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff_peg5(const apr_array_header_t *diff_options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t use_git_diff_format,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_diff_peg5(), but with @a show_copies_as_adds set to
+ * @c FALSE and @a use_git_diff_format set to @c FALSE.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff_peg4(const apr_array_header_t *diff_options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_diff_peg4(), but with @a changelists passed
+ * as @c NULL, and @a depth set according to @a recurse: if @a recurse
+ * is TRUE, set @a depth to #svn_depth_infinity, if @a recurse is
+ * FALSE, set @a depth to #svn_depth_files.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ * @since New in 1.3.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff_peg3(const apr_array_header_t *diff_options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_diff_peg3(), but with @a header_encoding set to
+ * @c APR_LOCALE_CHARSET.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff_peg2(const apr_array_header_t *diff_options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_diff_peg2(), but with @a ignore_content_type
+ * always set to FALSE.
+ *
+ * @since New in 1.1.
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff_peg(const apr_array_header_t *diff_options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Produce a diff summary which lists the changed items between
+ * @a path_or_url1/@a revision1 and @a path_or_url2/@a revision2 without
+ * creating text deltas. @a path_or_url1 and @a path_or_url2 can be
+ * either working-copy paths or URLs.
+ *
+ * The function may report false positives if @a ignore_ancestry is false,
+ * since a file might have been modified between two revisions, but still
+ * have the same contents.
+ *
+ * Calls @a summarize_func with @a summarize_baton for each difference
+ * with a #svn_client_diff_summarize_t structure describing the difference.
+ *
+ * See svn_client_diff6() for a description of the other parameters.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_client_diff_summarize2(const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_diff_summarize2(), but with @a changelists
+ * passed as @c NULL, and @a depth set according to @a recurse: if @a
+ * recurse is TRUE, set @a depth to #svn_depth_infinity, if @a
+ * recurse is FALSE, set @a depth to #svn_depth_files.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ *
+ * @since New in 1.4.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff_summarize(const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Produce a diff summary which lists the changed items between the
+ * filesystem object @a path_or_url in peg revision @a peg_revision, as it
+ * changed between @a start_revision and @a end_revision. @a path_or_url can
+ * be either a working-copy path or URL.
+ *
+ * If @a peg_revision is #svn_opt_revision_unspecified, behave
+ * identically to svn_client_diff_summarize2(), using @a path_or_url for
+ * both of that function's @a path_or_url1 and @a path_or_url2 arguments.
+ *
+ * The function may report false positives if @a ignore_ancestry is false,
+ * as described in the documentation for svn_client_diff_summarize2().
+ *
+ * Call @a summarize_func with @a summarize_baton for each difference
+ * with a #svn_client_diff_summarize_t structure describing the difference.
+ *
+ * See svn_client_diff_peg5() for a description of the other parameters.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_client_diff_summarize_peg2(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_diff_summarize_peg2(), but with @a
+ * changelists passed as @c NULL, and @a depth set according to @a
+ * recurse: if @a recurse is TRUE, set @a depth to
+ * #svn_depth_infinity, if @a recurse is FALSE, set @a depth to
+ * #svn_depth_files.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ *
+ * @since New in 1.4.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_diff_summarize_peg(const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Merge Merge changes between branches.
+ *
+ * @{
+ */
+
+/** Get information about the state of merging between two branches.
+ *
+ * The source is specified by @a source_path_or_url at @a source_revision.
+ * The target is specified by @a target_path_or_url at @a target_revision,
+ * which refers to either a WC or a repository location.
+ *
+ * Set @a *needs_reintegration to true if an automatic merge from source
+ * to target would be a reintegration merge: that is, if the last automatic
+ * merge was in the opposite direction; or to false otherwise.
+ *
+ * Set @a *yca_url, @a *yca_rev, @a *base_url, @a *base_rev, @a *right_url,
+ * @a *right_rev, @a *target_url, @a *target_rev to the repository locations
+ * of, respectively: the youngest common ancestor of the branches, the base
+ * chosen for 3-way merge, the right-hand side of the source diff, and the
+ * target.
+ *
+ * Set @a repos_root_url to the URL of the repository root. This is a
+ * common prefix of all four URL outputs.
+ *
+ * Allocate the results in @a result_pool. Any of the output pointers may
+ * be NULL if not wanted.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_get_merging_summary(svn_boolean_t *needs_reintegration,
+ const char **yca_url, svn_revnum_t *yca_rev,
+ const char **base_url, svn_revnum_t *base_rev,
+ const char **right_url, svn_revnum_t *right_rev,
+ const char **target_url, svn_revnum_t *target_rev,
+ const char **repos_root_url,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Merge changes from @a source1/@a revision1 to @a source2/@a revision2 into
+ * the working-copy path @a target_wcpath.
+ *
+ * @a source1 and @a source2 are either URLs that refer to entries in the
+ * repository, or paths to entries in the working copy.
+ *
+ * By "merging", we mean: apply file differences using
+ * svn_wc_merge(), and schedule additions & deletions when appropriate.
+ *
+ * @a source1 and @a source2 must both represent the same node kind -- that
+ * is, if @a source1 is a directory, @a source2 must also be, and if @a source1
+ * is a file, @a source2 must also be.
+ *
+ * If either @a revision1 or @a revision2 has an `unspecified' or
+ * unrecognized `kind', return #SVN_ERR_CLIENT_BAD_REVISION.
+ *
+ * If @a depth is #svn_depth_infinity, merge fully recursively.
+ * Else if #svn_depth_immediates, merge changes at most to files
+ * that are immediate children of @a target_wcpath and to directory
+ * properties of @a target_wcpath and its immediate subdirectory children.
+ * Else if #svn_depth_files, merge at most to immediate file
+ * children of @a target_wcpath and to @a target_wcpath itself.
+ * Else if #svn_depth_empty, apply changes only to @a target_wcpath
+ * (i.e., directory property changes only)
+ *
+ * If @a depth is #svn_depth_unknown, use the depth of @a target_wcpath.
+ *
+ * If @a ignore_mergeinfo is true, disable merge tracking, by treating the
+ * two sources as unrelated even if they actually have a common ancestor.
+ *
+ * If @a diff_ignore_ancestry is true, diff unrelated nodes as if related:
+ * that is, diff the 'left' and 'right' versions of a node as if they were
+ * related (if they are the same kind) even if they are not related.
+ * Otherwise, diff unrelated items as a deletion of one thing and the
+ * addition of another.
+ *
+ * If @a force_delete is false and the merge involves deleting a file whose
+ * content differs from the source-left version, or a locally modified
+ * directory, or an unversioned item, then the operation will fail. If
+ * @a force_delete is true then all such items will be deleted.
+ *
+ * @a merge_options (an array of <tt>const char *</tt>), if non-NULL,
+ * is used to pass additional command line arguments to the merge
+ * processes (internal or external). @see
+ * svn_diff_file_options_parse().
+ *
+ * If @a ctx->notify_func2 is non-NULL, then call @a ctx->notify_func2 with @a
+ * ctx->notify_baton2 once for each merged target, passing the target's local
+ * path.
+ *
+ * If @a record_only is TRUE, the merge is performed, but is limited only to
+ * mergeinfo property changes on existing paths in @a target_wcpath.
+ *
+ * If @a dry_run is TRUE, the merge is carried out, and full notification
+ * feedback is provided, but the working copy is not modified.
+ *
+ * If allow_mixed_rev is @c FALSE, and @a merge_target is a mixed-revision
+ * working copy, raise @c SVN_ERR_CLIENT_MERGE_UPDATE_REQUIRED.
+ * Because users rarely intend to merge into mixed-revision working copies,
+ * it is recommended to set this parameter to FALSE by default unless the
+ * user has explicitly requested a merge into a mixed-revision working copy.
+ *
+ * The authentication baton cached in @a ctx is used to communicate with the
+ * repository.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_merge5(const char *source1,
+ const svn_opt_revision_t *revision1,
+ const char *source2,
+ const svn_opt_revision_t *revision2,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_mergeinfo,
+ svn_boolean_t diff_ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ svn_boolean_t allow_mixed_rev,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_merge5(), but the single @a ignore_ancestry
+ * parameter maps to both @c ignore_mergeinfo and @c diff_ignore_ancestry.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * @since New in 1.7.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_merge4(const char *source1,
+ const svn_opt_revision_t *revision1,
+ const char *source2,
+ const svn_opt_revision_t *revision2,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ svn_boolean_t allow_mixed_rev,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_merge4(), but with @a allow_mixed_rev set to
+ * @c TRUE. The @a force parameter maps to the @c force_delete parameter
+ * of svn_client_merge4().
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ *
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_merge3(const char *source1,
+ const svn_opt_revision_t *revision1,
+ const char *source2,
+ const svn_opt_revision_t *revision2,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_merge3(), but with @a record_only set to @c
+ * FALSE, and @a depth set according to @a recurse: if @a recurse is
+ * TRUE, set @a depth to #svn_depth_infinity, if @a recurse is
+ * FALSE, set @a depth to #svn_depth_files.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ *
+ * @since New in 1.4.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_merge2(const char *source1,
+ const svn_opt_revision_t *revision1,
+ const char *source2,
+ const svn_opt_revision_t *revision2,
+ const char *target_wcpath,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_merge2(), but with @a merge_options set to NULL.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_merge(const char *source1,
+ const svn_opt_revision_t *revision1,
+ const char *source2,
+ const svn_opt_revision_t *revision2,
+ const char *target_wcpath,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Perform a reintegration merge of @a source_path_or_url at @a source_peg_revision
+ * into @a target_wcpath.
+ * @a target_wcpath must be a single-revision, #svn_depth_infinity,
+ * pristine, unswitched working copy -- in other words, it must
+ * reflect a single revision tree, the "target". The mergeinfo on @a
+ * source_path_or_url must reflect that all of the target has been merged into it.
+ * Then this behaves like a merge with svn_client_merge5() from the
+ * target's URL to the source.
+ *
+ * All other options are handled identically to svn_client_merge5().
+ * The depth of the merge is always #svn_depth_infinity.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backwards compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_merge_reintegrate(const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ const char *target_wcpath,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Merge changes from the source branch identified by
+ * @a source_path_or_url in peg revision @a source_peg_revision,
+ * into the target branch working copy at @a target_wcpath.
+ *
+ * If @a ranges_to_merge is NULL then perform an automatic merge of
+ * all the eligible changes up to @a source_peg_revision. If the merge
+ * required is a reintegrate merge, then return an error if the WC has
+ * mixed revisions, local modifications and/or switched subtrees; if
+ * the merge is determined to be of the non-reintegrate kind, then
+ * return an error if @a allow_mixed_rev is false and the WC contains
+ * mixed revisions.
+ *
+ * If @a ranges_to_merge is not NULL then merge the changes specified
+ * by the revision ranges in @a ranges_to_merge, or, when honouring
+ * mergeinfo, only the eligible parts of those revision ranges.
+ * @a ranges_to_merge is an array of <tt>svn_opt_revision_range_t
+ * *</tt> ranges. These ranges may describe additive and/or
+ * subtractive merge ranges, they may overlap fully or partially,
+ * and/or they may partially or fully negate each other. This
+ * rangelist is not required to be sorted. If any revision in the
+ * list of provided ranges has an `unspecified' or unrecognized
+ * `kind', return #SVN_ERR_CLIENT_BAD_REVISION.
+ *
+ * If @a ranges_to_merge is an empty array, then do nothing.
+ *
+ * All other options are handled identically to svn_client_merge5().
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_merge_peg5(const char *source_path_or_url,
+ const apr_array_header_t *ranges_to_merge,
+ const svn_opt_revision_t *source_peg_revision,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_mergeinfo,
+ svn_boolean_t diff_ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ svn_boolean_t allow_mixed_rev,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_merge_peg5(), but automatic merge is not available
+ * (@a ranges_to_merge must not be NULL), and the single @a ignore_ancestry
+ * parameter maps to both @c ignore_mergeinfo and @c diff_ignore_ancestry.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * @since New in 1.7.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_merge_peg4(const char *source_path_or_url,
+ const apr_array_header_t *ranges_to_merge,
+ const svn_opt_revision_t *source_peg_revision,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ svn_boolean_t allow_mixed_rev,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_merge_peg4(), but with @a allow_mixed_rev set to
+ * @c TRUE. The @a force parameter maps to the @c force_delete parameter
+ * of svn_client_merge_peg4().
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ *
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_merge_peg3(const char *source,
+ const apr_array_header_t *ranges_to_merge,
+ const svn_opt_revision_t *peg_revision,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_merge_peg3(), but with @a record_only set to
+ * @c FALSE, and @a depth set according to @a recurse: if @a recurse
+ * is TRUE, set @a depth to #svn_depth_infinity, if @a recurse is
+ * FALSE, set @a depth to #svn_depth_files.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.4 API.
+ *
+ * @since New in 1.4.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_merge_peg2(const char *source,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ const char *target_wcpath,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_merge_peg2(), but with @a merge_options set to
+ * NULL.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API.
+ *
+ * @since New in 1.1.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_merge_peg(const char *source,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ const char *target_wcpath,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/** Set @a suggestions to an ordered array of @c const char *
+ * potential merge sources (expressed as full repository URLs) for @a
+ * path_or_url at @a peg_revision. @a path_or_url is a working copy
+ * path or repository URL. @a ctx is a context used for
+ * authentication in the repository case. Use @a pool for all
+ * allocations.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_client_suggest_merge_sources(apr_array_header_t **suggestions,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Get the mergeinfo for a single target node (ignoring any subtrees).
+ *
+ * Set @a *mergeinfo to a hash mapping <tt>const char *</tt> merge source
+ * URLs to <tt>svn_rangelist_t *</tt> rangelists describing the ranges which
+ * have been merged into @a path_or_url as of @a peg_revision, per
+ * @a path_or_url's explicit mergeinfo or inherited mergeinfo if no
+ * explicit mergeinfo if found. If no explicit or inherited mergeinfo
+ * is found, then set @a *mergeinfo to NULL.
+ *
+ * Use @a pool for all necessary allocations.
+ *
+ * If the server doesn't support retrieval of mergeinfo (which will
+ * never happen for file:// URLs), return an
+ * #SVN_ERR_UNSUPPORTED_FEATURE error.
+ *
+ * @note Unlike most APIs which deal with mergeinfo, this one returns
+ * data where the keys of the hash are absolute repository URLs rather
+ * than repository filesystem paths.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_client_mergeinfo_get_merged(apr_hash_t **mergeinfo,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Describe the revisions that either have or have not been merged from
+ * one source branch (or subtree) into another.
+ *
+ * If @a finding_merged is TRUE, then drive log entry callbacks
+ * @a receiver / @a receiver_baton with the revisions merged from
+ * @a source_path_or_url (as of @a source_peg_revision) into
+ * @a target_path_or_url (as of @a target_peg_revision). If @a
+ * finding_merged is FALSE then find the revisions eligible for merging.
+ *
+ * If both @a source_start_revision and @a source_end_revision are
+ * unspecified (that is, of kind @c svn_opt_revision_unspecified),
+ * @a receiver will be called the requested revisions from 0 to
+ * @a source_peg_revision and in that order (that is, oldest to
+ * youngest). Otherwise, both @a source_start_revision and
+ * @a source_end_revision must be specified, which has two effects:
+ *
+ * - @a receiver will be called only with revisions which fall
+ * within range of @a source_start_revision to
+ * @a source_end_revision, inclusive, and
+ *
+ * - those revisions will be ordered in the same "direction" as the
+ * walk from @a source_start_revision to @a source_end_revision.
+ * (If @a source_start_revision is the younger of the two, @a
+ * receiver will be called with revisions in youngest-to-oldest
+ * order; otherwise, the reverse occurs.)
+ *
+ * If @a depth is #svn_depth_empty consider only the explicit or
+ * inherited mergeinfo on @a target_path_or_url when calculating merged
+ * revisions from @a source_path_or_url. If @a depth is #svn_depth_infinity
+ * then also consider the explicit subtree mergeinfo under @a
+ * target_path_or_url.
+ * If a depth other than #svn_depth_empty or #svn_depth_infinity is
+ * requested then return a #SVN_ERR_UNSUPPORTED_FEATURE error.
+ *
+ * @a discover_changed_paths and @a revprops are the same as for
+ * svn_client_log5(). Use @a scratch_pool for all temporary allocations.
+ *
+ * @a ctx is a context used for authentication.
+ *
+ * If the server doesn't support retrieval of mergeinfo, return an
+ * #SVN_ERR_UNSUPPORTED_FEATURE error.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_mergeinfo_log2(svn_boolean_t finding_merged,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_peg_revision,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ const svn_opt_revision_t *source_start_revision,
+ const svn_opt_revision_t *source_end_revision,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_boolean_t discover_changed_paths,
+ svn_depth_t depth,
+ const apr_array_header_t *revprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_client_mergeinfo_log2(), but with @a source_start_revision
+ * and @a source_end_revision always of kind @c svn_opt_revision_unspecified;
+ *
+ * @deprecated Provided for backwards compatibility with the 1.7 API.
+ * @since New in 1.7.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_mergeinfo_log(svn_boolean_t finding_merged,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_peg_revision,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_boolean_t discover_changed_paths,
+ svn_depth_t depth,
+ const apr_array_header_t *revprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_client_mergeinfo_log(), but finds only merged revisions
+ * and always operates at @a depth #svn_depth_empty.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.6 API. Use
+ * svn_client_mergeinfo_log() instead.
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_mergeinfo_log_merged(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const char *merge_source_path_or_url,
+ const svn_opt_revision_t *src_peg_revision,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_boolean_t discover_changed_paths,
+ const apr_array_header_t *revprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_mergeinfo_log(), but finds only eligible revisions
+ * and always operates at @a depth #svn_depth_empty.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.6 API. Use
+ * svn_client_mergeinfo_log() instead.
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_mergeinfo_log_eligible(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const char *merge_source_path_or_url,
+ const svn_opt_revision_t *src_peg_revision,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_boolean_t discover_changed_paths,
+ const apr_array_header_t *revprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Cleanup Cleanup an abnormally terminated working copy.
+ *
+ * @{
+ */
+
+/** Recursively cleanup a working copy directory @a dir, finishing any
+ * incomplete operations, removing lockfiles, etc.
+ *
+ * If @a ctx->cancel_func is non-NULL, invoke it with @a
+ * ctx->cancel_baton at various points during the operation. If it
+ * returns an error (typically #SVN_ERR_CANCELLED), return that error
+ * immediately.
+ *
+ * Use @a scratch_pool for any temporary allocations.
+ */
+svn_error_t *
+svn_client_cleanup(const char *dir,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+
+/** @} */
+
+/**
+ * @defgroup Upgrade Upgrade a working copy.
+ *
+ * @{
+ */
+
+/** Recursively upgrade a working copy from any older format to the current
+ * WC metadata storage format. @a wcroot_dir is the path to the WC root.
+ *
+ * Use @a scratch_pool for any temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_upgrade(const char *wcroot_dir,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+
+/** @} */
+
+/**
+ * @defgroup Relocate Switch a working copy to a different repository.
+ *
+ * @{
+ */
+
+/**
+ * Recursively modify a working copy rooted at @a wcroot_dir, changing
+ * any repository URLs that begin with @a from_prefix to begin with @a
+ * to_prefix instead.
+ *
+ * @param wcroot_dir Working copy root directory
+ * @param from_prefix Original URL
+ * @param to_prefix New URL
+ * @param ignore_externals If not set, recurse into external working
+ * copies after relocating the primary working copy
+ * @param ctx svn_client_ctx_t
+ * @param pool The pool from which to perform memory allocations
+ *
+ * @since New in 1.7
+ */
+svn_error_t *
+svn_client_relocate2(const char *wcroot_dir,
+ const char *from_prefix,
+ const char *to_prefix,
+ svn_boolean_t ignore_externals,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_relocate2(), but with @a ignore_externals
+ * always TRUE.
+ *
+ * @note As of the 1.7 API, @a dir is required to be a working copy
+ * root directory, and @a recurse is required to be TRUE.
+ *
+ * @deprecated Provided for limited backwards compatibility with the
+ * 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_relocate(const char *dir,
+ const char *from_prefix,
+ const char *to_prefix,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Revert Remove local changes in a repository.
+ *
+ * @{
+ */
+
+/**
+ * Restore the pristine version of working copy @a paths,
+ * effectively undoing any local mods. For each path in @a paths,
+ * revert it if it is a file. Else if it is a directory, revert
+ * according to @a depth:
+ *
+ * @a paths is an array of (const char *) local WC paths.
+ *
+ * If @a depth is #svn_depth_empty, revert just the properties on
+ * the directory; else if #svn_depth_files, revert the properties
+ * and any files immediately under the directory; else if
+ * #svn_depth_immediates, revert all of the preceding plus
+ * properties on immediate subdirectories; else if #svn_depth_infinity,
+ * revert path and everything under it fully recursively.
+ *
+ * @a changelists is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items reverted; that is,
+ * don't revert any item unless it's a member of one of those
+ * changelists. If @a changelists is empty (or altogether @c NULL),
+ * no changelist filtering occurs.
+ *
+ * If @a ctx->notify_func2 is non-NULL, then for each item reverted,
+ * call @a ctx->notify_func2 with @a ctx->notify_baton2 and the path of
+ * the reverted item.
+ *
+ * If an item specified for reversion is not under version control,
+ * then do not error, just invoke @a ctx->notify_func2 with @a
+ * ctx->notify_baton2, using notification code #svn_wc_notify_skip.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_client_revert2(const apr_array_header_t *paths,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_revert2(), but with @a changelists passed as
+ * @c NULL, and @a depth set according to @a recurse: if @a recurse is
+ * TRUE, @a depth is #svn_depth_infinity, else if @a recurse is
+ * FALSE, @a depth is #svn_depth_empty.
+ *
+ * @note Most APIs map @a recurse==FALSE to @a depth==svn_depth_files;
+ * revert is deliberately different.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_revert(const apr_array_header_t *paths,
+ svn_boolean_t recursive,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/** @} */
+
+/**
+ * @defgroup Resolved Mark conflicted paths as resolved.
+ *
+ * @{
+ */
+
+/**
+ * Similar to svn_client_resolve(), but without automatic conflict
+ * resolution support.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ * Use svn_client_resolve() with @a conflict_choice == @c
+ * svn_wc_conflict_choose_merged instead.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_resolved(const char *path,
+ svn_boolean_t recursive,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** Perform automatic conflict resolution on a working copy @a path.
+ *
+ * If @a conflict_choice is
+ *
+ * - #svn_wc_conflict_choose_base:
+ * resolve the conflict with the old file contents
+ *
+ * - #svn_wc_conflict_choose_mine_full:
+ * use the original working contents
+ *
+ * - #svn_wc_conflict_choose_theirs_full:
+ * use the new contents
+ *
+ * - #svn_wc_conflict_choose_merged:
+ * don't change the contents at all, just remove the conflict
+ * status, which is the pre-1.5 behavior.
+ *
+ * - #svn_wc_conflict_choose_theirs_conflict
+ * ###...
+ *
+ * - #svn_wc_conflict_choose_mine_conflict
+ * ###...
+ *
+ * - svn_wc_conflict_choose_unspecified
+ * invoke @a ctx->conflict_func2 with @a ctx->conflict_baton2 to obtain
+ * a resolution decision for each conflict. This can be used to
+ * implement interactive conflict resolution.
+ *
+ * #svn_wc_conflict_choose_theirs_conflict and
+ * #svn_wc_conflict_choose_mine_conflict are not legal for binary
+ * files or properties.
+ *
+ * If @a path is not in a state of conflict to begin with, do nothing.
+ * If @a path's conflict state is removed and @a ctx->notify_func2 is non-NULL,
+ * call @a ctx->notify_func2 with @a ctx->notify_baton2 and @a path.
+ * ### with what notification parameters?
+ *
+ * If @a depth is #svn_depth_empty, act only on @a path; if
+ * #svn_depth_files, resolve @a path and its conflicted file
+ * children (if any); if #svn_depth_immediates, resolve @a path and
+ * all its immediate conflicted children (both files and directories,
+ * if any); if #svn_depth_infinity, resolve @a path and every
+ * conflicted file or directory anywhere beneath it.
+ *
+ * Note that this operation will try to lock the parent directory of
+ * @a path in order to be able to resolve tree-conflicts on @a path.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_client_resolve(const char *path,
+ svn_depth_t depth,
+ svn_wc_conflict_choice_t conflict_choice,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/** @} */
+
+/**
+ * @defgroup Copy Copy paths in the working copy and repository.
+ *
+ * @{
+ */
+
+/**
+ * A structure which describes the source of a copy operation--its path,
+ * revision, and peg revision.
+ *
+ * @since New in 1.5.
+ */
+typedef struct svn_client_copy_source_t
+{
+ /** The source path or URL. */
+ const char *path;
+
+ /** The source operational revision. */
+ const svn_opt_revision_t *revision;
+
+ /** The source peg revision. */
+ const svn_opt_revision_t *peg_revision;
+} svn_client_copy_source_t;
+
+/** Copy each source in @a sources to @a dst_path.
+ *
+ * If multiple @a sources are given, @a dst_path must be a directory,
+ * and @a sources will be copied as children of @a dst_path.
+ *
+ * @a sources is an array of <tt>svn_client_copy_source_t *</tt> elements,
+ * either all referring to local WC items or all referring to versioned
+ * items in the repository.
+ *
+ * If @a sources has only one item, attempt to copy it to @a dst_path. If
+ * @a copy_as_child is TRUE and @a dst_path already exists, attempt to copy the
+ * item as a child of @a dst_path. If @a copy_as_child is FALSE and
+ * @a dst_path already exists, fail with #SVN_ERR_ENTRY_EXISTS if @a dst_path
+ * is a working copy path and #SVN_ERR_FS_ALREADY_EXISTS if @a dst_path is a
+ * URL.
+ *
+ * If @a sources has multiple items, and @a copy_as_child is TRUE, all
+ * @a sources are copied as children of @a dst_path. If any child of
+ * @a dst_path already exists with the same name any item in @a sources,
+ * fail with #SVN_ERR_ENTRY_EXISTS if @a dst_path is a working copy path and
+ * #SVN_ERR_FS_ALREADY_EXISTS if @a dst_path is a URL.
+ *
+ * If @a sources has multiple items, and @a copy_as_child is FALSE, fail
+ * with #SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED.
+ *
+ * If @a dst_path is a URL, use the authentication baton
+ * in @a ctx and @a ctx->log_msg_func3/@a ctx->log_msg_baton3 to immediately
+ * attempt to commit the copy action in the repository.
+ *
+ * If @a dst_path is not a URL, then this is just a variant of
+ * svn_client_add(), where the @a sources are scheduled for addition
+ * as copies. No changes will happen to the repository until a commit occurs.
+ * This scheduling can be removed with svn_client_revert2().
+ *
+ * If @a make_parents is TRUE, create any non-existent parent directories
+ * also. Otherwise the parent of @a dst_path must already exist.
+ *
+ * If @a ignore_externals is set, don't process externals definitions
+ * as part of this operation.
+ *
+ * If non-NULL, @a revprop_table is a hash table holding additional,
+ * custom revision properties (<tt>const char *</tt> names mapped to
+ * <tt>svn_string_t *</tt> values) to be set on the new revision in
+ * the event that this is a committing operation. This table cannot
+ * contain any standard Subversion properties.
+ *
+ * @a ctx->log_msg_func3/@a ctx->log_msg_baton3 are a callback/baton combo
+ * that this function can use to query for a commit log message when one is
+ * needed.
+ *
+ * If @a ctx->notify_func2 is non-NULL, invoke it with @a ctx->notify_baton2
+ * for each item added at the new location, passing the new, relative path of
+ * the added item.
+ *
+ * If @a commit_callback is non-NULL, then for each successful commit, call
+ * @a commit_callback with @a commit_baton and a #svn_commit_info_t for
+ * the commit.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_copy6(const apr_array_header_t *sources,
+ const char *dst_path,
+ svn_boolean_t copy_as_child,
+ svn_boolean_t make_parents,
+ svn_boolean_t ignore_externals,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_copy6(), but returns the commit info in
+ * @a *commit_info_p rather than through a callback function.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_copy5(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *sources,
+ const char *dst_path,
+ svn_boolean_t copy_as_child,
+ svn_boolean_t make_parents,
+ svn_boolean_t ignore_externals,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_copy5(), with @a ignore_externals set to @c FALSE.
+ *
+ * @since New in 1.5.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_copy4(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *sources,
+ const char *dst_path,
+ svn_boolean_t copy_as_child,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_copy4(), with only one @a src_path, @a
+ * copy_as_child set to @c FALSE, @a revprop_table passed as NULL, and
+ * @a make_parents set to @c FALSE. Also, use @a src_revision as both
+ * the operational and peg revision.
+ *
+ * @since New in 1.4.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_copy3(svn_commit_info_t **commit_info_p,
+ const char *src_path,
+ const svn_opt_revision_t *src_revision,
+ const char *dst_path,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_copy3(), with the difference that if @a dst_path
+ * already exists and is a directory, copy the item into that directory,
+ * keeping its name (the last component of @a src_path).
+ *
+ * @since New in 1.3.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_copy2(svn_commit_info_t **commit_info_p,
+ const char *src_path,
+ const svn_opt_revision_t *src_revision,
+ const char *dst_path,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_copy2(), but uses #svn_client_commit_info_t
+ * for @a commit_info_p.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_copy(svn_client_commit_info_t **commit_info_p,
+ const char *src_path,
+ const svn_opt_revision_t *src_revision,
+ const char *dst_path,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/** @} */
+
+/**
+ * @defgroup Move Move paths in the working copy or repository.
+ *
+ * @{
+ */
+
+/**
+ * Move @a src_paths to @a dst_path.
+ *
+ * @a src_paths is an array of (const char *) paths -- either all WC paths
+ * or all URLs -- of versioned items. If multiple @a src_paths are given,
+ * @a dst_path must be a directory and @a src_paths will be moved as
+ * children of @a dst_path.
+ *
+ * If @a src_paths are repository URLs:
+ *
+ * - @a dst_path must also be a repository URL.
+ *
+ * - The authentication baton in @a ctx and @a ctx->log_msg_func/@a
+ * ctx->log_msg_baton are used to commit the move.
+ *
+ * - The move operation will be immediately committed.
+ *
+ * If @a src_paths are working copy paths:
+ *
+ * - @a dst_path must also be a working copy path.
+ *
+ * - @a ctx->log_msg_func3 and @a ctx->log_msg_baton3 are ignored.
+ *
+ * - This is a scheduling operation. No changes will happen to the
+ * repository until a commit occurs. This scheduling can be removed
+ * with svn_client_revert2(). If one of @a src_paths is a file it is
+ * removed from the working copy immediately. If one of @a src_path
+ * is a directory it will remain in the working copy but all the files,
+ * and unversioned items, it contains will be removed.
+ *
+ * If @a src_paths has only one item, attempt to move it to @a dst_path. If
+ * @a move_as_child is TRUE and @a dst_path already exists, attempt to move the
+ * item as a child of @a dst_path. If @a move_as_child is FALSE and
+ * @a dst_path already exists, fail with #SVN_ERR_ENTRY_EXISTS if @a dst_path
+ * is a working copy path and #SVN_ERR_FS_ALREADY_EXISTS if @a dst_path is a
+ * URL.
+ *
+ * If @a src_paths has multiple items, and @a move_as_child is TRUE, all
+ * @a src_paths are moved as children of @a dst_path. If any child of
+ * @a dst_path already exists with the same name any item in @a src_paths,
+ * fail with #SVN_ERR_ENTRY_EXISTS if @a dst_path is a working copy path and
+ * #SVN_ERR_FS_ALREADY_EXISTS if @a dst_path is a URL.
+ *
+ * If @a src_paths has multiple items, and @a move_as_child is FALSE, fail
+ * with #SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED.
+ *
+ * If @a make_parents is TRUE, create any non-existent parent directories
+ * also. Otherwise, the parent of @a dst_path must already exist.
+ *
+ * If @a allow_mixed_revisions is @c FALSE, #SVN_ERR_WC_MIXED_REVISIONS
+ * will be raised if the move source is a mixed-revision subtree.
+ * If @a allow_mixed_revisions is TRUE, a mixed-revision move source is
+ * allowed but the move will degrade to a copy and a delete without local
+ * move tracking. This parameter should be set to FALSE except where backwards
+ * compatibility to svn_client_move6() is required.
+ *
+ * If @a metadata_only is @c TRUE and moving a file in a working copy,
+ * everything in the metadata is updated as if the node is moved, but the
+ * actual disk move operation is not performed. This feature is useful for
+ * clients that want to keep the working copy in sync while the actual working
+ * copy is updated by some other task.
+ *
+ * If non-NULL, @a revprop_table is a hash table holding additional,
+ * custom revision properties (<tt>const char *</tt> names mapped to
+ * <tt>svn_string_t *</tt> values) to be set on the new revision in
+ * the event that this is a committing operation. This table cannot
+ * contain any standard Subversion properties.
+ *
+ * @a ctx->log_msg_func3/@a ctx->log_msg_baton3 are a callback/baton combo that
+ * this function can use to query for a commit log message when one is needed.
+ *
+ * If @a ctx->notify_func2 is non-NULL, then for each item moved, call
+ * @a ctx->notify_func2 with the @a ctx->notify_baton2 twice, once to indicate
+ * the deletion of the moved thing, and once to indicate the addition of
+ * the new location of the thing.
+ *
+ * ### Is this really true? What about svn_wc_notify_commit_replaced()? ###
+ *
+ * If @a commit_callback is non-NULL, then for each successful commit, call
+ * @a commit_callback with @a commit_baton and a #svn_commit_info_t for
+ * the commit.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_move7(const apr_array_header_t *src_paths,
+ const char *dst_path,
+ svn_boolean_t move_as_child,
+ svn_boolean_t make_parents,
+ svn_boolean_t allow_mixed_revisions,
+ svn_boolean_t metadata_only,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_move7(), but with @a allow_mixed_revisions always
+ * set to @c TRUE and @a metadata_only always to @c FALSE.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_move6(const apr_array_header_t *src_paths,
+ const char *dst_path,
+ svn_boolean_t move_as_child,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_move6(), but returns the commit info in
+ * @a *commit_info_p rather than through a callback function.
+ *
+ * A WC-to-WC move will include any modified and/or unversioned children.
+ * @a force is ignored.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_move5(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *src_paths,
+ const char *dst_path,
+ svn_boolean_t force,
+ svn_boolean_t move_as_child,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_move5(), with only one @a src_path, @a
+ * move_as_child set to @c FALSE, @a revprop_table passed as NULL, and
+ * @a make_parents set to @c FALSE.
+ *
+ * Note: The behaviour of @a force changed in 1.5 (r860885 and r861421), when
+ * the 'move' semantics were improved to just move the source including any
+ * modified and/or unversioned items in it. Before that, @a force
+ * controlled what happened to such items, but now @a force is ignored.
+ *
+ * @since New in 1.4.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_move4(svn_commit_info_t **commit_info_p,
+ const char *src_path,
+ const char *dst_path,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_move4(), with the difference that if @a dst_path
+ * already exists and is a directory, move the item into that directory,
+ * keeping its name (the last component of @a src_path).
+ *
+ * @since New in 1.3.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_move3(svn_commit_info_t **commit_info_p,
+ const char *src_path,
+ const char *dst_path,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_move3(), but uses #svn_client_commit_info_t
+ * for @a commit_info_p.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ *
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_move2(svn_client_commit_info_t **commit_info_p,
+ const char *src_path,
+ const char *dst_path,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_move2(), but an extra argument @a src_revision
+ * must be passed. This has no effect, but must be of kind
+ * #svn_opt_revision_unspecified or #svn_opt_revision_head,
+ * otherwise error #SVN_ERR_UNSUPPORTED_FEATURE is returned.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_move(svn_client_commit_info_t **commit_info_p,
+ const char *src_path,
+ const svn_opt_revision_t *src_revision,
+ const char *dst_path,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/** Properties
+ *
+ * Note that certain svn-controlled properties must always have their
+ * values set and stored in UTF8 with LF line endings. When
+ * retrieving these properties, callers must convert the values back
+ * to native locale and native line-endings before displaying them to
+ * the user. For help with this task, see
+ * svn_prop_needs_translation(), svn_subst_translate_string(), and
+ * svn_subst_detranslate_string().
+ *
+ * @defgroup svn_client_prop_funcs Property functions
+ * @{
+ */
+
+
+/**
+ * Set @a propname to @a propval on @a url. A @a propval of @c NULL will
+ * delete the property.
+ *
+ * Immediately attempt to commit the property change in the repository,
+ * using the authentication baton in @a ctx and @a
+ * ctx->log_msg_func3/@a ctx->log_msg_baton3.
+ *
+ * If the property has changed on @a url since revision
+ * @a base_revision_for_url (which must not be #SVN_INVALID_REVNUM), no
+ * change will be made and an error will be returned.
+ *
+ * If non-NULL, @a revprop_table is a hash table holding additional,
+ * custom revision properties (<tt>const char *</tt> names mapped to
+ * <tt>svn_string_t *</tt> values) to be set on the new revision. This
+ * table cannot contain any standard Subversion properties.
+ *
+ * If @a commit_callback is non-NULL, then call @a commit_callback with
+ * @a commit_baton and a #svn_commit_info_t for the commit.
+ *
+ * If @a propname is an svn-controlled property (i.e. prefixed with
+ * #SVN_PROP_PREFIX), then the caller is responsible for ensuring that
+ * the value is UTF8-encoded and uses LF line-endings.
+ *
+ * If @a skip_checks is TRUE, do no validity checking. But if @a
+ * skip_checks is FALSE, and @a propname is not a valid property for @a
+ * url, return an error, either #SVN_ERR_ILLEGAL_TARGET (if the property is
+ * not appropriate for @a url), or * #SVN_ERR_BAD_MIME_TYPE (if @a propname
+ * is "svn:mime-type", but @a propval is not a valid mime-type).
+ *
+ * Use @a scratch_pool for all memory allocation.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_propset_remote(const char *propname,
+ const svn_string_t *propval,
+ const char *url,
+ svn_boolean_t skip_checks,
+ svn_revnum_t base_revision_for_url,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Set @a propname to @a propval on each (const char *) target in @a
+ * targets. The targets must be all working copy paths. A @a propval
+ * of @c NULL will delete the property.
+ *
+ * If @a depth is #svn_depth_empty, set the property on each member of
+ * @a targets only; if #svn_depth_files, set it on @a targets and their
+ * file children (if any); if #svn_depth_immediates, on @a targets and all
+ * of their immediate children (both files and directories); if
+ * #svn_depth_infinity, on @a targets and everything beneath them.
+ *
+ * @a changelists is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose properties are
+ * set; that is, don't set properties on any item unless it's a member
+ * of one of those changelists. If @a changelists is empty (or
+ * altogether @c NULL), no changelist filtering occurs.
+ *
+ * If @a propname is an svn-controlled property (i.e. prefixed with
+ * #SVN_PROP_PREFIX), then the caller is responsible for ensuring that
+ * the value is UTF8-encoded and uses LF line-endings.
+ *
+ * If @a skip_checks is TRUE, do no validity checking. But if @a
+ * skip_checks is FALSE, and @a propname is not a valid property for @a
+ * targets, return an error, either #SVN_ERR_ILLEGAL_TARGET (if the
+ * property is not appropriate for @a targets), or
+ * #SVN_ERR_BAD_MIME_TYPE (if @a propname is "svn:mime-type", but @a
+ * propval is not a valid mime-type).
+ *
+ * If @a ctx->cancel_func is non-NULL, invoke it passing @a
+ * ctx->cancel_baton at various places during the operation.
+ *
+ * Use @a scratch_pool for all memory allocation.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_propset_local(const char *propname,
+ const svn_string_t *propval,
+ const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_boolean_t skip_checks,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/**
+ * An amalgamation of svn_client_propset_local() and
+ * svn_client_propset_remote() that takes only a single target, and
+ * returns the commit info in @a *commit_info_p rather than through a
+ * callback function.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_propset3(svn_commit_info_t **commit_info_p,
+ const char *propname,
+ const svn_string_t *propval,
+ const char *target,
+ svn_depth_t depth,
+ svn_boolean_t skip_checks,
+ svn_revnum_t base_revision_for_url,
+ const apr_array_header_t *changelists,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Like svn_client_propset3(), but with @a base_revision_for_url
+ * always #SVN_INVALID_REVNUM; @a commit_info_p always @c NULL; @a
+ * changelists always @c NULL; @a revprop_table always @c NULL; and @a
+ * depth set according to @a recurse: if @a recurse is TRUE, @a depth
+ * is #svn_depth_infinity, else #svn_depth_empty.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_propset2(const char *propname,
+ const svn_string_t *propval,
+ const char *target,
+ svn_boolean_t recurse,
+ svn_boolean_t skip_checks,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Like svn_client_propset2(), but with @a skip_checks always FALSE and a
+ * newly created @a ctx.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_propset(const char *propname,
+ const svn_string_t *propval,
+ const char *target,
+ svn_boolean_t recurse,
+ apr_pool_t *pool);
+
+/** Set @a propname to @a propval on revision @a revision in the repository
+ * represented by @a URL. Use the authentication baton in @a ctx for
+ * authentication, and @a pool for all memory allocation. Return the actual
+ * rev affected in @a *set_rev. A @a propval of @c NULL will delete the
+ * property.
+ *
+ * If @a original_propval is non-NULL, then just before setting the
+ * new value, check that the old value matches @a original_propval;
+ * if they do not match, return the error #SVN_ERR_RA_OUT_OF_DATE.
+ * This is to help clients support interactive editing of revprops:
+ * without this check, the window during which the property may change
+ * underneath the user is as wide as the amount of time the user
+ * spends editing the property. With this check, the window is
+ * reduced to a small, constant amount of time right before we set the
+ * new value. (To check that an old value is still non-existent, set
+ * @a original_propval->data to NULL, and @a original_propval->len is
+ * ignored.)
+ * If the server advertises #SVN_RA_CAPABILITY_ATOMIC_REVPROPS, the
+ * check of @a original_propval is done atomically.
+ *
+ * Note: the representation of "property is not set" in @a
+ * original_propval differs from the representation in other APIs
+ * (such as svn_fs_change_rev_prop2() and svn_ra_change_rev_prop2()).
+ *
+ * If @a force is TRUE, allow newlines in the author property.
+ *
+ * If @a propname is an svn-controlled property (i.e. prefixed with
+ * #SVN_PROP_PREFIX), then the caller is responsible for ensuring that
+ * the value UTF8-encoded and uses LF line-endings.
+ *
+ * Note that unlike its cousin svn_client_propset3(), this routine
+ * doesn't affect the working copy at all; it's a pure network
+ * operation that changes an *unversioned* property attached to a
+ * revision. This can be used to tweak log messages, dates, authors,
+ * and the like. Be careful: it's a lossy operation.
+
+ * @a ctx->notify_func2 and @a ctx->notify_baton2 are the notification
+ * functions and baton which are called upon successful setting of the
+ * property.
+ *
+ * Also note that unless the administrator creates a
+ * pre-revprop-change hook in the repository, this feature will fail.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_client_revprop_set2(const char *propname,
+ const svn_string_t *propval,
+ const svn_string_t *original_propval,
+ const char *URL,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *set_rev,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_revprop_set2(), but with @a original_propval
+ * always @c NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_revprop_set(const char *propname,
+ const svn_string_t *propval,
+ const char *URL,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *set_rev,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *props to a hash table whose keys are absolute paths or URLs
+ * of items on which property @a propname is explicitly set, and whose
+ * values are <tt>svn_string_t *</tt> representing the property value for
+ * @a propname at that path.
+ *
+ * If @a inherited_props is not @c NULL, then set @a *inherited_props to a
+ * depth-first ordered array of #svn_prop_inherited_item_t * structures
+ * representing the properties inherited by @a target. If @a target is a
+ * working copy path, then properties inherited by @a target as far as the
+ * root of the working copy are obtained from the working copy's actual
+ * property values. Properties inherited from above the working copy root
+ * come from the inherited properties cache. If @a target is a URL, then
+ * the inherited properties come from the repository. If @a inherited_props
+ * is not @c NULL and no inheritable properties are found, then set
+ * @a *inherited_props to an empty array.
+ *
+ * The #svn_prop_inherited_item_t->path_or_url members of the
+ * #svn_prop_inherited_item_t * structures in @a *inherited_props are
+ * URLs if @a target is a URL or if @a target is a working copy path but the
+ * property represented by the structure is above the working copy root (i.e.
+ * the inherited property is from the cache). In all other cases the
+ * #svn_prop_inherited_item_t->path_or_url members are absolute working copy
+ * paths.
+ *
+ * Allocate @a *props (including keys and values) and @a *inherited_props
+ * (including its elements) in @a result_pool, use @a scratch_pool for
+ * temporary allocations.
+ *
+ * @a target is a WC absolute path or a URL.
+ *
+ * Don't store any path, not even @a target, if it does not have a
+ * property named @a propname.
+ *
+ * If @a revision->kind is #svn_opt_revision_unspecified, then: get
+ * properties from the working copy if @a target is a working copy
+ * path, or from the repository head if @a target is a URL. Else get
+ * the properties as of @a revision. The actual node revision
+ * selected is determined by the path as it exists in @a peg_revision.
+ * If @a peg_revision->kind is #svn_opt_revision_unspecified, then
+ * it defaults to #svn_opt_revision_head for URLs or
+ * #svn_opt_revision_working for WC targets. Use the authentication
+ * baton in @a ctx for authentication if contacting the repository.
+ * If @a actual_revnum is not @c NULL, the actual revision number used
+ * for the fetch is stored in @a *actual_revnum.
+ *
+ * If @a depth is #svn_depth_empty, fetch the property from
+ * @a target only; if #svn_depth_files, fetch from @a target and its
+ * file children (if any); if #svn_depth_immediates, from @a target
+ * and all of its immediate children (both files and directories); if
+ * #svn_depth_infinity, from @a target and everything beneath it.
+ *
+ * @a changelists is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose properties are
+ * gotten; that is, don't get @a propname on any item unless it's a member
+ * of one of those changelists. If @a changelists is empty (or
+ * altogether @c NULL), no changelist filtering occurs.
+ *
+ * If error, don't touch @a *props, otherwise @a *props is a hash table
+ * even if empty.
+ *
+ * This function returns SVN_ERR_UNVERSIONED_RESOURCE when it is called on
+ * unversioned nodes.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_propget5(apr_hash_t **props,
+ apr_array_header_t **inherited_props,
+ const char *propname,
+ const char *target, /* abspath or URL */
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *actual_revnum,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_client_propget5 but with @a inherited_props always
+ * passed as NULL.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_propget4(apr_hash_t **props,
+ const char *propname,
+ const char *target, /* abspath or URL */
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *actual_revnum,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_client_propget4(), but with the following change to the
+ * output hash keys: keys are `<tt>char *</tt>' paths, prefixed by
+ * @a target, which is a working copy path or a URL.
+ *
+ * This function returns SVN_ERR_ENTRY_NOT_FOUND where svn_client_propget4
+ * would return SVN_ERR_UNVERSIONED_RESOURCE.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_propget3(apr_hash_t **props,
+ const char *propname,
+ const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *actual_revnum,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_propget3(), except that @a actual_revnum and
+ * @a changelists are always @c NULL, and @a depth is set according to
+ * @a recurse: if @a recurse is TRUE, then @a depth is
+ * #svn_depth_infinity, else #svn_depth_empty.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_propget2(apr_hash_t **props,
+ const char *propname,
+ const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_propget2(), except that @a peg_revision is
+ * always the same as @a revision.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_propget(apr_hash_t **props,
+ const char *propname,
+ const char *target,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** Set @a *propval to the value of @a propname on revision @a revision
+ * in the repository represented by @a URL. Use the authentication baton
+ * in @a ctx for authentication, and @a pool for all memory allocation.
+ * Return the actual rev queried in @a *set_rev.
+ *
+ * Note that unlike its cousin svn_client_propget(), this routine
+ * doesn't affect the working copy at all; it's a pure network
+ * operation that queries an *unversioned* property attached to a
+ * revision. This can query log messages, dates, authors, and the
+ * like.
+ */
+svn_error_t *
+svn_client_revprop_get(const char *propname,
+ svn_string_t **propval,
+ const char *URL,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *set_rev,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Invoke @a receiver with @a receiver_baton to return the regular explicit, and
+ * possibly the inherited, properties of @a target, a URL or working copy path.
+ * @a receiver will be called for each path encountered.
+ *
+ * @a target is a WC path or a URL.
+ *
+ * If @a revision->kind is #svn_opt_revision_unspecified, then get the
+ * explicit (and possibly the inherited) properties from the working copy,
+ * if @a target is a working copy path, or from the repository head if
+ * @a target is a URL. Else get the properties as of @a revision.
+ * The actual node revision selected is determined by the path as it exists
+ * in @a peg_revision. If @a peg_revision->kind is
+ * #svn_opt_revision_unspecified, then it defaults to #svn_opt_revision_head
+ * for URLs or #svn_opt_revision_working for WC targets. Use the
+ * authentication baton cached in @a ctx for authentication if contacting
+ * the repository.
+ *
+ * If @a depth is #svn_depth_empty, list only the properties of
+ * @a target itself. If @a depth is #svn_depth_files, and
+ * @a target is a directory, list the properties of @a target
+ * and its file entries. If #svn_depth_immediates, list the properties
+ * of its immediate file and directory entries. If #svn_depth_infinity,
+ * list the properties of its file entries and recurse (with
+ * #svn_depth_infinity) on directory entries. #svn_depth_unknown is
+ * equivalent to #svn_depth_empty. All other values produce undefined
+ * results.
+ *
+ * @a changelists is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose properties are
+ * listed; that is, don't list properties on any item unless it's a member
+ * of one of those changelists. If @a changelists is empty (or
+ * altogether @c NULL), no changelist filtering occurs.
+ *
+ * If @a get_target_inherited_props is true, then also return any inherited
+ * properties when @a receiver is called for @a target. If @a target is a
+ * working copy path, then properties inherited by @a target as far as the
+ * root of the working copy are obtained from the working copy's actual
+ * property values. Properties inherited from above the working copy
+ * root come from the inherited properties cache. If @a target is a URL,
+ * then the inherited properties come from the repository.
+ * If @a get_target_inherited_props is false, then no inherited properties
+ * are returned to @a receiver.
+ *
+ * If @a target is not found, return the error #SVN_ERR_ENTRY_NOT_FOUND.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_proplist4(const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_boolean_t get_target_inherited_props,
+ svn_proplist_receiver2_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_client_proplist4(), except that the @a receiver type
+ * is a #svn_proplist_receiver_t, @a get_target_inherited_props is
+ * always passed NULL, and there is no separate scratch pool.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_proplist3(const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_proplist_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_proplist3(), except the properties are
+ * returned as an array of #svn_client_proplist_item_t * structures
+ * instead of by invoking the receiver function, there's no support
+ * for @a changelists filtering, and @a recurse is used instead of a
+ * #svn_depth_t parameter (FALSE corresponds to #svn_depth_empty,
+ * and TRUE to #svn_depth_infinity).
+ *
+ * @since New in 1.2.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_proplist2(apr_array_header_t **props,
+ const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_proplist2(), except that @a peg_revision is
+ * always the same as @a revision.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_proplist(apr_array_header_t **props,
+ const char *target,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** Set @a *props to a hash of the revision props attached to @a revision in
+ * the repository represented by @a URL. Use the authentication baton cached
+ * in @a ctx for authentication, and @a pool for all memory allocation.
+ * Return the actual rev queried in @a *set_rev.
+ *
+ * The allocated hash maps (<tt>const char *</tt>) property names to
+ * (#svn_string_t *) property values.
+ *
+ * Note that unlike its cousin svn_client_proplist(), this routine
+ * doesn't read a working copy at all; it's a pure network operation
+ * that reads *unversioned* properties attached to a revision.
+ */
+svn_error_t *
+svn_client_revprop_list(apr_hash_t **props,
+ const char *URL,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *set_rev,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+/** @} */
+
+
+/**
+ * @defgroup Export Export a tree from version control.
+ *
+ * @{
+ */
+
+/**
+ * Export the contents of either a subversion repository or a
+ * subversion working copy into a 'clean' directory (meaning a
+ * directory with no administrative directories). If @a result_rev
+ * is not @c NULL and the path being exported is a repository URL, set
+ * @a *result_rev to the value of the revision actually exported (set
+ * it to #SVN_INVALID_REVNUM for local exports).
+ *
+ * @a from_path_or_url is either the path the working copy on disk, or
+ * a URL to the repository you wish to export.
+ *
+ * When exporting a directory, @a to_path is the path to the directory
+ * where you wish to create the exported tree; when exporting a file, it
+ * is the path of the file that will be created. If @a to_path is the
+ * empty path, then the basename of the export file/directory in the repository
+ * will be used. If @a to_path represents an existing directory, and a
+ * file is being exported, then a file with the that basename will be
+ * created under that directory (as with 'copy' operations).
+ *
+ * @a peg_revision is the revision where the path is first looked up
+ * when exporting from a repository. If @a peg_revision->kind is
+ * #svn_opt_revision_unspecified, then it defaults to #svn_opt_revision_head
+ * for URLs or #svn_opt_revision_working for WC targets.
+ *
+ * @a revision is the revision that should be exported, which is only used
+ * when exporting from a repository.
+ *
+ * @a peg_revision and @a revision must not be @c NULL.
+ *
+ * @a ctx->notify_func2 and @a ctx->notify_baton2 are the notification
+ * functions and baton which are passed to svn_client_checkout() when
+ * exporting from a repository.
+ *
+ * @a ctx is a context used for authentication in the repository case.
+ *
+ * @a overwrite if TRUE will cause the export to overwrite files or
+ * directories.
+ *
+ * If @a ignore_externals is set, don't process externals definitions
+ * as part of this operation.
+ *
+ * If @a ignore_keywords is set, don't expand keywords as part of this
+ * operation.
+ *
+ * @a native_eol allows you to override the standard eol marker on the
+ * platform you are running on. Can be either "LF", "CR" or "CRLF" or
+ * NULL. If NULL will use the standard eol marker. Any other value
+ * will cause the #SVN_ERR_IO_UNKNOWN_EOL error to be returned.
+ *
+ * If @a depth is #svn_depth_infinity, export fully recursively. Else
+ * if it is #svn_depth_immediates, export @a from_path_or_url and its
+ * immediate children (if any), but with subdirectories empty and at
+ * #svn_depth_empty. Else if #svn_depth_files, export @a
+ * from_path_or_url and its immediate file children (if any) only. If
+ * @a depth is #svn_depth_empty, then export exactly @a
+ * from_path_or_url and none of its children.
+ *
+ * All allocations are done in @a pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_export5(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ const char *to_path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t overwrite,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t ignore_keywords,
+ svn_depth_t depth,
+ const char *native_eol,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_export5(), but with @a ignore_keywords set
+ * to FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_export4(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ const char *to_path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t overwrite,
+ svn_boolean_t ignore_externals,
+ svn_depth_t depth,
+ const char *native_eol,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_export4(), but with @a depth set according to
+ * @a recurse: if @a recurse is TRUE, set @a depth to
+ * #svn_depth_infinity, if @a recurse is FALSE, set @a depth to
+ * #svn_depth_files.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ *
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_export3(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ const char *to_path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t overwrite,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t recurse,
+ const char *native_eol,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_export3(), but with @a peg_revision
+ * always set to #svn_opt_revision_unspecified, @a overwrite set to
+ * the value of @a force, @a ignore_externals always FALSE, and
+ * @a recurse always TRUE.
+ *
+ * @since New in 1.1.
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_export2(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ const char *to_path,
+ svn_opt_revision_t *revision,
+ svn_boolean_t force,
+ const char *native_eol,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_export2(), but with @a native_eol always set
+ * to NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_export(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ const char *to_path,
+ svn_opt_revision_t *revision,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup List List / ls
+ *
+ * @{
+ */
+
+/** The type of function invoked by svn_client_list3() to report the details
+ * of each directory entry being listed.
+ *
+ * @a baton is the baton that was passed to the caller. @a path is the
+ * entry's path relative to @a abs_path; it is the empty path when reporting
+ * the top node of the list operation. @a dirent contains some or all of
+ * the directory entry's details, as determined by the caller. @a lock is
+ * the entry's lock, if it is locked and if lock information is being
+ * reported by the caller; otherwise @a lock is NULL. @a abs_path is the
+ * repository path of the top node of the list operation; it is relative to
+ * the repository root and begins with "/".
+ *
+ * If svn_client_list3() was called with @a include_externals set to TRUE,
+ * @a external_parent_url and @a external_target will be set.
+ * @a external_parent_url is url of the directory which has the
+ * externals definitions. @a external_target is the target subdirectory of
+ * externals definitions which is relative to the parent directory that holds
+ * the external item.
+ *
+ * If external_parent_url and external_target are defined, the item being
+ * listed is part of the external described by external_parent_url and
+ * external_target. Else, the item is not part of any external.
+ * Moreover, we will never mix items which are part of separate
+ * externals, and will always finish listing an external before listing
+ * the next one.
+ *
+ * @a scratch_pool may be used for temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_client_list_func2_t)(
+ void *baton,
+ const char *path,
+ const svn_dirent_t *dirent,
+ const svn_lock_t *lock,
+ const char *abs_path,
+ const char *external_parent_url,
+ const char *external_target,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to #svn_client_list_func2_t, but without any information about
+ * externals definitions.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ *
+ * @since New in 1.4
+ *
+ * */
+typedef svn_error_t *(*svn_client_list_func_t)(void *baton,
+ const char *path,
+ const svn_dirent_t *dirent,
+ const svn_lock_t *lock,
+ const char *abs_path,
+ apr_pool_t *pool);
+
+/**
+ * Report the directory entry, and possibly children, for @a
+ * path_or_url at @a revision. The actual node revision selected is
+ * determined by the path as it exists in @a peg_revision. If @a
+ * peg_revision->kind is #svn_opt_revision_unspecified, then it defaults
+ * to #svn_opt_revision_head for URLs or #svn_opt_revision_working
+ * for WC targets.
+ *
+ * Report directory entries by invoking @a list_func/@a baton with @a path
+ * relative to @a path_or_url. The dirent for @a path_or_url is reported
+ * using an empty @a path. If @a path_or_url is a directory, also report
+ * its children. If @a path_or_url is non-existent, return
+ * #SVN_ERR_FS_NOT_FOUND.
+ *
+ * If @a fetch_locks is TRUE, include locks when reporting directory entries.
+ *
+ * If @a include_externals is TRUE, also list all external items
+ * reached by recursion. @a depth value passed to the original list target
+ * applies for the externals also.
+ *
+ * Use @a pool for temporary allocations.
+ *
+ * Use authentication baton cached in @a ctx to authenticate against the
+ * repository.
+ *
+ * If @a depth is #svn_depth_empty, list just @a path_or_url itself.
+ * If @a depth is #svn_depth_files, list @a path_or_url and its file
+ * entries. If #svn_depth_immediates, list its immediate file and
+ * directory entries. If #svn_depth_infinity, list file entries and
+ * recurse (with #svn_depth_infinity) on directory entries.
+ *
+ * @a dirent_fields controls which fields in the #svn_dirent_t's are
+ * filled in. To have them totally filled in use #SVN_DIRENT_ALL,
+ * otherwise simply bitwise OR together the combination of @c SVN_DIRENT_
+ * fields you care about.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_list3(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ apr_uint32_t dirent_fields,
+ svn_boolean_t fetch_locks,
+ svn_boolean_t include_externals,
+ svn_client_list_func2_t list_func,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/** Similar to svn_client_list3(), but with @a include_externals set
+ * to FALSE, and using a #svn_client_list_func_t as callback.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.7 API.
+ *
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_list2(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ apr_uint32_t dirent_fields,
+ svn_boolean_t fetch_locks,
+ svn_client_list_func_t list_func,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_list2(), but with @a recurse instead of @a depth.
+ * If @a recurse is TRUE, pass #svn_depth_files for @a depth; else
+ * pass #svn_depth_infinity.
+ *
+ * @since New in 1.4.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_list(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ apr_uint32_t dirent_fields,
+ svn_boolean_t fetch_locks,
+ svn_client_list_func_t list_func,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_client_list(), but always passes #SVN_DIRENT_ALL for
+ * the @a dirent_fields argument and returns all information in two
+ * hash tables instead of invoking a callback.
+ *
+ * Set @a *dirents to a newly allocated hash of directory entries.
+ * The @a dirents hash maps entry names (<tt>const char *</tt>) to
+ * #svn_dirent_t *'s.
+ *
+ * If @a locks is not @c NULL, set @a *locks to a hash table mapping
+ * entry names (<tt>const char *</tt>) to #svn_lock_t *'s.
+ *
+ * @since New in 1.3.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ * Use svn_client_list2() instead.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_ls3(apr_hash_t **dirents,
+ apr_hash_t **locks,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_client_ls3(), but without the ability to get locks.
+ *
+ * @since New in 1.2.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ * Use svn_client_list2() instead.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_ls2(apr_hash_t **dirents,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_ls2() except that @a peg_revision is always
+ * the same as @a revision.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ * Use svn_client_list2() instead.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_ls(apr_hash_t **dirents,
+ const char *path_or_url,
+ svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/** @} */
+
+/**
+ * @defgroup Cat View the contents of a file in the repository.
+ *
+ * @{
+ */
+
+/**
+ * Output the content of a file.
+ *
+ * @param[in] out The stream to which the content will be written.
+ * @param[in] path_or_url The path or URL of the file.
+ * @param[in] peg_revision The peg revision.
+ * @param[in] revision The operative revision.
+ * @param[in] ctx The standard client context, used for possible
+ * authentication.
+ * @param[in] pool Used for any temporary allocation.
+ *
+ * @todo Add an expansion/translation flag?
+ *
+ * @return A pointer to an #svn_error_t of the type (this list is not
+ * exhaustive): <br>
+ * An unspecified error if @a revision is of kind
+ * #svn_opt_revision_previous (or some other kind that requires
+ * a local path), because the desired revision cannot be
+ * determined. <br>
+ * If no error occurred, return #SVN_NO_ERROR.
+ *
+ * @since New in 1.2.
+ *
+ * @see #svn_client_ctx_t <br> @ref clnt_revisions for
+ * a discussion of operative and peg revisions.
+ */
+svn_error_t *
+svn_client_cat2(svn_stream_t *out,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_client_cat2() except that the peg revision is always
+ * the same as @a revision.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_cat(svn_stream_t *out,
+ const char *path_or_url,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} end group: cat */
+
+
+
+/** Changelist commands
+ *
+ * @defgroup svn_client_changelist_funcs Client Changelist Functions
+ * @{
+ */
+
+/** Implementation note:
+ *
+ * For now, changelists are implemented by scattering the
+ * associations across multiple .svn/entries files in a working copy.
+ * However, this client API was written so that we have the option of
+ * changing the underlying implementation -- we may someday want to
+ * store changelist definitions in a centralized database.
+ */
+
+/**
+ * Add each path in @a paths (recursing to @a depth as necessary) to
+ * @a changelist. If a path is already a member of another
+ * changelist, then remove it from the other changelist and add it to
+ * @a changelist. (For now, a path cannot belong to two changelists
+ * at once.)
+ *
+ * @a paths is an array of (const char *) local WC paths.
+ *
+ * @a changelists is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose changelist
+ * assignments are adjusted; that is, don't tweak the changeset of any
+ * item unless it's currently a member of one of those changelists.
+ * If @a changelists is empty (or altogether @c NULL), no changelist
+ * filtering occurs.
+ *
+ * @note This metadata is purely a client-side "bookkeeping"
+ * convenience, and is entirely managed by the working copy.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_client_add_to_changelist(const apr_array_header_t *paths,
+ const char *changelist,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Remove each path in @a paths (recursing to @a depth as necessary)
+ * from changelists to which they are currently assigned.
+ *
+ * @a paths is an array of (const char *) local WC paths.
+ *
+ * @a changelists is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose changelist
+ * assignments are removed; that is, don't remove from a changeset any
+ * item unless it's currently a member of one of those changelists.
+ * If @a changelists is empty (or altogether @c NULL), all changelist
+ * assignments in and under each path in @a paths (to @a depth) will
+ * be removed.
+ *
+ * @note This metadata is purely a client-side "bookkeeping"
+ * convenience, and is entirely managed by the working copy.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_client_remove_from_changelists(const apr_array_header_t *paths,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/**
+ * Beginning at @a path, crawl to @a depth to discover every path in
+ * or under @a path which belongs to one of the changelists in @a
+ * changelists (an array of <tt>const char *</tt> changelist names).
+ * If @a changelists is @c NULL, discover paths with any changelist.
+ * Call @a callback_func (with @a callback_baton) each time a
+ * changelist-having path is discovered.
+ *
+ * @a path is a local WC path.
+ *
+ * If @a ctx->cancel_func is not @c NULL, invoke it passing @a
+ * ctx->cancel_baton during the recursive walk.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_client_get_changelists(const char *path,
+ const apr_array_header_t *changelists,
+ svn_depth_t depth,
+ svn_changelist_receiver_t callback_func,
+ void *callback_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+
+/** Locking commands
+ *
+ * @defgroup svn_client_locking_funcs Client Locking Functions
+ * @{
+ */
+
+/**
+ * Lock @a targets in the repository. @a targets is an array of
+ * <tt>const char *</tt> paths - either all working copy paths or all URLs.
+ * All targets must be in the same repository.
+ *
+ * If a target is already locked in the repository, no lock will be
+ * acquired unless @a steal_lock is TRUE, in which case the locks are
+ * stolen. @a comment, if non-NULL, is an xml-escapable description
+ * stored with each lock in the repository. Each acquired lock will
+ * be stored in the working copy if the targets are WC paths.
+ *
+ * For each target @a ctx->notify_func2/notify_baton2 will be used to indicate
+ * whether it was locked. An action of #svn_wc_notify_locked
+ * means that the path was locked. If the path was not locked because
+ * it was out of date or there was already a lock in the repository,
+ * the notification function will be called with
+ * #svn_wc_notify_failed_lock, and the error passed in the notification
+ * structure.
+ *
+ * Use @a pool for temporary allocations.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_client_lock(const apr_array_header_t *targets,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Unlock @a targets in the repository. @a targets is an array of
+ * <tt>const char *</tt> paths - either all working copy paths or all URLs.
+ * All targets must be in the same repository.
+ *
+ * If the targets are WC paths, and @a break_lock is FALSE, the working
+ * copy must contain a lock for each target.
+ * If this is not the case, or the working copy lock doesn't match the
+ * lock token in the repository, an error will be signaled.
+ *
+ * If the targets are URLs, the locks may be broken even if @a break_lock
+ * is FALSE, but only if the lock owner is the same as the
+ * authenticated user.
+ *
+ * If @a break_lock is TRUE, the locks will be broken in the
+ * repository. In both cases, the locks, if any, will be removed from
+ * the working copy if the targets are WC paths.
+ *
+ * The notification functions in @a ctx will be called for each
+ * target. If the target was successfully unlocked,
+ * #svn_wc_notify_unlocked will be used. Else, if the error is
+ * directly related to unlocking the path (see
+ * #SVN_ERR_IS_UNLOCK_ERROR), #svn_wc_notify_failed_unlock will be
+ * used and the error will be passed in the notification structure.
+
+ * Use @a pool for temporary allocations.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_client_unlock(const apr_array_header_t *targets,
+ svn_boolean_t break_lock,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * @defgroup Info Show repository information about a working copy.
+ *
+ * @{
+ */
+
+/** The size of the file is unknown.
+ * Used as value in fields of type @c apr_size_t in #svn_info_t.
+ *
+ * @since New in 1.5
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+#define SVN_INFO_SIZE_UNKNOWN ((apr_size_t) -1)
+
+/**
+ * A structure which describes various system-generated metadata about
+ * a working-copy path or URL.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, users shouldn't allocate structures of this
+ * type, to preserve binary compatibility.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API. The new
+ * API is #svn_client_info2_t.
+ */
+typedef struct svn_info_t
+{
+ /** Where the item lives in the repository. */
+ const char *URL;
+
+ /** The revision of the object. If path_or_url is a working-copy
+ * path, then this is its current working revnum. If path_or_url
+ * is a URL, then this is the repos revision that path_or_url lives in. */
+ svn_revnum_t rev;
+
+ /** The node's kind. */
+ svn_node_kind_t kind;
+
+ /** The root URL of the repository. */
+ const char *repos_root_URL;
+
+ /** The repository's UUID. */
+ const char *repos_UUID;
+
+ /** The last revision in which this object changed. */
+ svn_revnum_t last_changed_rev;
+
+ /** The date of the last_changed_rev. */
+ apr_time_t last_changed_date;
+
+ /** The author of the last_changed_rev. */
+ const char *last_changed_author;
+
+ /** An exclusive lock, if present. Could be either local or remote. */
+ svn_lock_t *lock;
+
+ /** Whether or not to ignore the next 10 wc-specific fields. */
+ svn_boolean_t has_wc_info;
+
+ /**
+ * @name Working-copy path fields
+ * These things only apply to a working-copy path.
+ * See svn_wc_entry_t for explanations.
+ * @{
+ */
+ svn_wc_schedule_t schedule;
+ const char *copyfrom_url;
+ svn_revnum_t copyfrom_rev;
+ apr_time_t text_time;
+ apr_time_t prop_time; /* will always be 0 for svn 1.4 and later */
+ const char *checksum;
+ const char *conflict_old;
+ const char *conflict_new;
+ const char *conflict_wrk;
+ const char *prejfile;
+ /** @since New in 1.5. */
+ const char *changelist;
+ /** @since New in 1.5. */
+ svn_depth_t depth;
+
+ /**
+ * Similar to working_size64, but will be #SVN_INFO_SIZE_UNKNOWN when
+ * its value would overflow apr_size_t (so when size >= 4 GB - 1 byte).
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+ apr_size_t working_size;
+
+ /** @} */
+
+ /**
+ * Similar to size64, but size will be #SVN_INFO_SIZE_UNKNOWN when
+ * its value would overflow apr_size_t (so when size >= 4 GB - 1 byte).
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+ apr_size_t size;
+
+ /**
+ * The size of the file in the repository (untranslated,
+ * e.g. without adjustment of line endings and keyword
+ * expansion). Only applicable for file -- not directory -- URLs.
+ * For working copy paths, size64 will be #SVN_INVALID_FILESIZE.
+ * @since New in 1.6.
+ */
+ svn_filesize_t size64;
+
+ /**
+ * The size of the file after being translated into its local
+ * representation, or #SVN_INVALID_FILESIZE if unknown.
+ * Not applicable for directories.
+ * @since New in 1.6.
+ * @name Working-copy path fields
+ * @{
+ */
+ svn_filesize_t working_size64;
+
+ /**
+ * Info on any tree conflict of which this node is a victim. Otherwise NULL.
+ * @since New in 1.6.
+ */
+ svn_wc_conflict_description_t *tree_conflict;
+
+ /** @} */
+
+} svn_info_t;
+
+
+/**
+ * The callback invoked by svn_client_info2(). Each invocation
+ * describes @a path with the information present in @a info. Note
+ * that any fields within @a info may be NULL if information is
+ * unavailable. Use @a pool for all temporary allocation.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API. The new
+ * API is #svn_client_info_receiver2_t.
+ */
+typedef svn_error_t *(*svn_info_receiver_t)(
+ void *baton,
+ const char *path,
+ const svn_info_t *info,
+ apr_pool_t *pool);
+
+/**
+ * Return a duplicate of @a info, allocated in @a pool. No part of the new
+ * structure will be shared with @a info.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backward compatibility with the 1.6 API. The new
+ * API is #svn_client_info2_dup().
+ */
+SVN_DEPRECATED
+svn_info_t *
+svn_info_dup(const svn_info_t *info,
+ apr_pool_t *pool);
+
+/**
+ * A structure which describes various system-generated metadata about
+ * a working-copy path or URL.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, users shouldn't allocate structures of this
+ * type, to preserve binary compatibility.
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_client_info2_t
+{
+ /** Where the item lives in the repository. */
+ const char *URL;
+
+ /** The revision of the object. If the target is a working-copy
+ * path, then this is its current working revnum. If the target
+ * is a URL, then this is the repos revision that it lives in. */
+ svn_revnum_t rev;
+
+ /** The root URL of the repository. */
+ const char *repos_root_URL;
+
+ /** The repository's UUID. */
+ const char *repos_UUID;
+
+ /** The node's kind. */
+ svn_node_kind_t kind;
+
+ /** The size of the file in the repository (untranslated,
+ * e.g. without adjustment of line endings and keyword
+ * expansion). Only applicable for file -- not directory -- URLs.
+ * For working copy paths, @a size will be #SVN_INVALID_FILESIZE. */
+ svn_filesize_t size;
+
+ /** The last revision in which this object changed. */
+ svn_revnum_t last_changed_rev;
+
+ /** The date of the last_changed_rev. */
+ apr_time_t last_changed_date;
+
+ /** The author of the last_changed_rev. */
+ const char *last_changed_author;
+
+ /** An exclusive lock, if present. Could be either local or remote. */
+ const svn_lock_t *lock;
+
+ /** Possible information about the working copy, NULL if not valid. */
+ const svn_wc_info_t *wc_info;
+
+} svn_client_info2_t;
+
+/**
+ * Return a duplicate of @a info, allocated in @a pool. No part of the new
+ * structure will be shared with @a info.
+ *
+ * @since New in 1.7.
+ */
+svn_client_info2_t *
+svn_client_info2_dup(const svn_client_info2_t *info,
+ apr_pool_t *pool);
+
+/**
+ * The callback invoked by info retrievers. Each invocation
+ * describes @a abspath_or_url with the information present in @a info.
+ * Use @a scratch_pool for all temporary allocation.
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_client_info_receiver2_t)(
+ void *baton,
+ const char *abspath_or_url,
+ const svn_client_info2_t *info,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Invoke @a receiver with @a receiver_baton to return information
+ * about @a abspath_or_url in @a revision. The information returned is
+ * system-generated metadata, not the sort of "property" metadata
+ * created by users. See #svn_client_info2_t.
+ *
+ * If both revision arguments are either #svn_opt_revision_unspecified
+ * or @c NULL, then information will be pulled solely from the working copy;
+ * no network connections will be made.
+ *
+ * Otherwise, information will be pulled from a repository. The
+ * actual node revision selected is determined by the @a abspath_or_url
+ * as it exists in @a peg_revision. If @a peg_revision->kind is
+ * #svn_opt_revision_unspecified, then it defaults to
+ * #svn_opt_revision_head for URLs or #svn_opt_revision_working for
+ * WC targets.
+ *
+ * If @a abspath_or_url is not a local path, then if @a revision is of
+ * kind #svn_opt_revision_previous (or some other kind that requires
+ * a local path), an error will be returned, because the desired
+ * revision cannot be determined.
+ *
+ * Use the authentication baton cached in @a ctx to authenticate
+ * against the repository.
+ *
+ * If @a abspath_or_url is a file, just invoke @a receiver on it. If it
+ * is a directory, then descend according to @a depth. If @a depth is
+ * #svn_depth_empty, invoke @a receiver on @a abspath_or_url and
+ * nothing else; if #svn_depth_files, on @a abspath_or_url and its
+ * immediate file children; if #svn_depth_immediates, the preceding
+ * plus on each immediate subdirectory; if #svn_depth_infinity, then
+ * recurse fully, invoking @a receiver on @a abspath_or_url and
+ * everything beneath it.
+ *
+ * If @a fetch_excluded is TRUE, also also send excluded nodes in the working
+ * copy to @a receiver, otherwise these are skipped. If @a fetch_actual_only
+ * is TRUE also send nodes that don't exist as versioned but are still
+ * tree conflicted.
+ *
+ * @a changelists is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose info is
+ * reported; that is, don't report info about any item unless
+ * it's a member of one of those changelists. If @a changelists is
+ * empty (or altogether @c NULL), no changelist filtering occurs.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_info3(const char *abspath_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t fetch_excluded,
+ svn_boolean_t fetch_actual_only,
+ const apr_array_header_t *changelists,
+ svn_client_info_receiver2_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_client_info3, but uses an svn_info_receiver_t instead of
+ * a #svn_client_info_receiver2_t, and @a path_or_url may be a relative path.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_info2(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_info_receiver_t receiver,
+ void *receiver_baton,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_info2() but with @a changelists passed as @c
+ * NULL, and @a depth set according to @a recurse: if @a recurse is
+ * TRUE, @a depth is #svn_depth_infinity, else #svn_depth_empty.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_info(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_info_receiver_t receiver,
+ void *receiver_baton,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *wcroot_abspath to the local abspath of the root of the
+ * working copy in which @a local_abspath resides.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_get_wc_root(const char **wcroot_abspath,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Set @a *min_revision and @a *max_revision to the lowest and highest
+ * revision numbers found within @a local_abspath. If @a committed is
+ * TRUE, set @a *min_revision and @a *max_revision to the lowest and
+ * highest comitted (i.e. "last changed") revision numbers,
+ * respectively. NULL may be passed for either of @a min_revision and
+ * @a max_revision to indicate the caller's lack of interest in the
+ * value. Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_min_max_revisions(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ const char *local_abspath,
+ svn_boolean_t committed,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/** @} */
+
+
+/**
+ * @defgroup Patch Apply a patch to the working copy
+ *
+ * @{
+ */
+
+/**
+ * The callback invoked by svn_client_patch() before attempting to patch
+ * the target file at @a canon_path_from_patchfile (the path as parsed from
+ * the patch file, but in canonicalized form). The callback can set
+ * @a *filtered to @c TRUE to prevent the file from being patched, or else
+ * must set it to @c FALSE.
+ *
+ * The callback is also provided with @a patch_abspath, the path of a
+ * temporary file containing the patched result, and with @a reject_abspath,
+ * the path to a temporary file containing the diff text of any hunks
+ * which were rejected during patching.
+ *
+ * Because the callback is invoked before the patching attempt is made,
+ * there is no guarantee that the target file will actually be patched
+ * successfully. Client implementations must pay attention to notification
+ * feedback provided by svn_client_patch() to find out which paths were
+ * patched successfully.
+ *
+ * Note also that the files at @a patch_abspath and @a reject_abspath are
+ * guaranteed to remain on disk after patching only if the
+ * @a remove_tempfiles parameter for svn_client_patch() is @c FALSE.
+ *
+ * The const char * parameters may be allocated in @a scratch_pool which
+ * will be cleared after each invocation.
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_client_patch_func_t)(
+ void *baton,
+ svn_boolean_t *filtered,
+ const char *canon_path_from_patchfile,
+ const char *patch_abspath,
+ const char *reject_abspath,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Apply a unidiff patch that's located at absolute path
+ * @a patch_abspath to the working copy directory at @a wc_dir_abspath.
+ *
+ * This function makes a best-effort attempt at applying the patch.
+ * It might skip patch targets which cannot be patched (e.g. targets
+ * that are outside of the working copy). It will also reject hunks
+ * which cannot be applied to a target in case the hunk's context
+ * does not match anywhere in the patch target.
+ *
+ * If @a dry_run is TRUE, the patching process is carried out, and full
+ * notification feedback is provided, but the working copy is not modified.
+ *
+ * @a strip_count specifies how many leading path components should be
+ * stripped from paths obtained from the patch. It is an error if a
+ * negative strip count is passed.
+ *
+ * If @a reverse is @c TRUE, apply patches in reverse, deleting lines
+ * the patch would add and adding lines the patch would delete.
+ *
+ * If @a ignore_whitespace is TRUE, allow patches to be applied if they
+ * only differ from the target by whitespace.
+ *
+ * If @a remove_tempfiles is TRUE, lifetimes of temporary files created
+ * during patching will be managed internally. Otherwise, the caller should
+ * take ownership of these files, the names of which can be obtained by
+ * passing a @a patch_func callback.
+ *
+ * If @a patch_func is non-NULL, invoke @a patch_func with @a patch_baton
+ * for each patch target processed.
+ *
+ * If @a ctx->notify_func2 is non-NULL, invoke @a ctx->notify_func2 with
+ * @a ctx->notify_baton2 as patching progresses.
+ *
+ * If @a ctx->cancel_func is non-NULL, invoke it passing @a
+ * ctx->cancel_baton at various places during the operation.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_patch(const char *patch_abspath,
+ const char *wc_dir_abspath,
+ svn_boolean_t dry_run,
+ int strip_count,
+ svn_boolean_t reverse,
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t remove_tempfiles,
+ svn_client_patch_func_t patch_func,
+ void *patch_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/** @} */
+
+/** @} end group: Client working copy management */
+
+/**
+ *
+ * @defgroup clnt_sessions Client session related functions
+ *
+ * @{
+ *
+ */
+
+
+/* Converting paths to URLs. */
+
+/** Set @a *url to the URL for @a path_or_url allocated in result_pool.
+ *
+ * If @a path_or_url is already a URL, set @a *url to @a path_or_url.
+ *
+ * If @a path_or_url is a versioned item, set @a *url to @a
+ * path_or_url's entry URL. If @a path_or_url is unversioned (has
+ * no entry), set @a *url to NULL.
+ *
+ * Use @a ctx->wc_ctx to retrieve the information. Use
+ ** @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_client_url_from_path2(const char **url,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_client_url_from_path2(), but without a context argument.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_url_from_path(const char **url,
+ const char *path_or_url,
+ apr_pool_t *pool);
+
+
+
+/* Fetching a repository's root URL and UUID. */
+
+/** Set @a *repos_root_url and @a *repos_uuid, to the root URL and UUID of
+ * the repository in which @a abspath_or_url is versioned. Use the
+ * authentication baton and working copy context cached in @a ctx as
+ * necessary. @a repos_root_url and/or @a repos_uuid may be NULL if not
+ * wanted.
+ *
+ * This function will open a temporary RA session to the repository if
+ * necessary to get the information.
+ *
+ * Allocate @a *repos_root_url and @a *repos_uuid in @a result_pool.
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_get_repos_root(const char **repos_root_url,
+ const char **repos_uuid,
+ const char *abspath_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Set @a *url to the repository root URL of the repository in which
+ * @a path_or_url is versioned (or scheduled to be versioned),
+ * allocated in @a pool. @a ctx is required for possible repository
+ * authentication.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.7 API. Use
+ * svn_client_get_repos_root() instead, with an absolute path.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_root_url_from_path(const char **url,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/** Get repository @a uuid for @a url.
+ *
+ * Use a @a pool to open a temporary RA session to @a url, discover the
+ * repository uuid, and free the session. Return the uuid in @a uuid,
+ * allocated in @a pool. @a ctx is required for possible repository
+ * authentication.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API. Use
+ * svn_client_get_repos_root() instead.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_uuid_from_url(const char **uuid,
+ const char *url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/** Return the repository @a uuid for working-copy @a local_abspath,
+ * allocated in @a result_pool. Use @a ctx->wc_ctx to retrieve the
+ * information.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API. Use
+ * svn_client_get_repos_root() instead.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_uuid_from_path2(const char **uuid,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_client_uuid_from_path2(), but with a relative path and
+ * an access baton.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_uuid_from_path(const char **uuid,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/* Opening RA sessions. */
+
+/** Open an RA session rooted at @a url, and return it in @a *session.
+ *
+ * Use the authentication baton stored in @a ctx for authentication.
+ * @a *session is allocated in @a result_pool.
+ *
+ * If @a wri_abspath is not NULL, use the working copy identified by @a
+ * wri_abspath to potentially avoid transferring unneeded data.
+ *
+ * @note This function is similar to svn_ra_open4(), but the caller avoids
+ * having to providing its own callback functions.
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_open_ra_session2(svn_ra_session_t **session,
+ const char *url,
+ const char *wri_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_client_open_ra_session(), but with @ wri_abspath
+ * always passed as NULL, and with the same pool used as both @a
+ * result_pool and @a scratch_pool.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_client_open_ra_session(svn_ra_session_t **session,
+ const char *url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/** @} end group: Client session related functions */
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CLIENT_H */
diff --git a/contrib/subversion/subversion/include/svn_cmdline.h b/contrib/subversion/subversion/include/svn_cmdline.h
new file mode 100644
index 0000000..80442e4
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_cmdline.h
@@ -0,0 +1,376 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_cmdline.h
+ * @brief Support functions for command line programs
+ */
+
+
+
+
+#ifndef SVN_CMDLINE_H
+#define SVN_CMDLINE_H
+
+#include <apr_pools.h>
+#include <apr_getopt.h>
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+#define APR_WANT_STDIO
+#endif
+#include <apr_want.h>
+
+#include "svn_types.h"
+#include "svn_auth.h"
+#include "svn_config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** Set up the locale for character conversion, and initialize APR.
+ * If @a error_stream is non-NULL, print error messages to the stream,
+ * using @a progname as the program name. Attempt to set @c stdout to
+ * line-buffered mode, and @a error_stream to unbuffered mode. Return
+ * @c EXIT_SUCCESS if successful, otherwise @c EXIT_FAILURE.
+ *
+ * @note This function should be called exactly once at program startup,
+ * before calling any other APR or Subversion functions.
+ */
+int
+svn_cmdline_init(const char *progname,
+ FILE *error_stream);
+
+
+/** Set @a *dest to an output-encoded C string from UTF-8 C string @a
+ * src; allocate @a *dest in @a pool.
+ */
+svn_error_t *
+svn_cmdline_cstring_from_utf8(const char **dest,
+ const char *src,
+ apr_pool_t *pool);
+
+/** Like svn_utf_cstring_from_utf8_fuzzy(), but converts to an
+ * output-encoded C string. */
+const char *
+svn_cmdline_cstring_from_utf8_fuzzy(const char *src,
+ apr_pool_t *pool);
+
+/** Set @a *dest to a UTF-8-encoded C string from input-encoded C
+ * string @a src; allocate @a *dest in @a pool.
+ */
+svn_error_t *
+svn_cmdline_cstring_to_utf8(const char **dest,
+ const char *src,
+ apr_pool_t *pool);
+
+/** Set @a *dest to an output-encoded natively-formatted path string
+ * from canonical path @a src; allocate @a *dest in @a pool.
+ */
+svn_error_t *
+svn_cmdline_path_local_style_from_utf8(const char **dest,
+ const char *src,
+ apr_pool_t *pool);
+
+/** Write to stdout, using a printf-like format string @a fmt, passed
+ * through apr_pvsprintf(). All string arguments are in UTF-8; the output
+ * is converted to the output encoding. Use @a pool for temporary
+ * allocation.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_cmdline_printf(apr_pool_t *pool,
+ const char *fmt,
+ ...)
+ __attribute__((format(printf, 2, 3)));
+
+/** Write to the stdio @a stream, using a printf-like format string @a fmt,
+ * passed through apr_pvsprintf(). All string arguments are in UTF-8;
+ * the output is converted to the output encoding. Use @a pool for
+ * temporary allocation.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_cmdline_fprintf(FILE *stream,
+ apr_pool_t *pool,
+ const char *fmt,
+ ...)
+ __attribute__((format(printf, 3, 4)));
+
+/** Output the @a string to the stdio @a stream, converting from UTF-8
+ * to the output encoding. Use @a pool for temporary allocation.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_cmdline_fputs(const char *string,
+ FILE *stream,
+ apr_pool_t *pool);
+
+/** Flush output buffers of the stdio @a stream, returning an error if that
+ * fails. This is just a wrapper for the standard fflush() function for
+ * consistent error handling.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_cmdline_fflush(FILE *stream);
+
+/** Return the name of the output encoding allocated in @a pool, or @c
+ * APR_LOCALE_CHARSET if the output encoding is the same as the locale
+ * encoding.
+ *
+ * @since New in 1.3.
+ */
+const char *
+svn_cmdline_output_encoding(apr_pool_t *pool);
+
+/** Handle @a error in preparation for immediate exit from a
+ * command-line client. Specifically:
+ *
+ * Call svn_handle_error2(@a error, stderr, FALSE, @a prefix), clear
+ * @a error, destroy @a pool iff it is non-NULL, and return EXIT_FAILURE.
+ *
+ * @since New in 1.3.
+ */
+int
+svn_cmdline_handle_exit_error(svn_error_t *error,
+ apr_pool_t *pool,
+ const char *prefix);
+
+/** A prompt function/baton pair, and the path to the configuration
+ * directory. To be passed as the baton argument to the
+ * @c svn_cmdline_*_prompt functions.
+ *
+ * @since New in 1.6.
+ */
+typedef struct svn_cmdline_prompt_baton2_t {
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+ const char *config_dir;
+} svn_cmdline_prompt_baton2_t;
+
+/** Like svn_cmdline_prompt_baton2_t, but without the path to the
+ * configuration directory.
+ *
+ * @since New in 1.4.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+typedef struct svn_cmdline_prompt_baton_t {
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+} svn_cmdline_prompt_baton_t;
+
+/** Prompt the user for input, using @a prompt_str for the prompt and
+ * @a baton (which may be @c NULL) for cancellation, and returning the
+ * user's response in @a result, allocated in @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_cmdline_prompt_user2(const char **result,
+ const char *prompt_str,
+ svn_cmdline_prompt_baton_t *baton,
+ apr_pool_t *pool);
+
+/** Similar to svn_cmdline_prompt_user2, but without cancellation
+ * support.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_cmdline_prompt_user(const char **result,
+ const char *prompt_str,
+ apr_pool_t *pool);
+
+/** An implementation of @c svn_auth_simple_prompt_func_t that prompts
+ * the user for keyboard input on the command line.
+ *
+ * @since New in 1.4.
+ *
+ * Expects a @c svn_cmdline_prompt_baton_t to be passed as @a baton.
+ */
+svn_error_t *
+svn_cmdline_auth_simple_prompt(svn_auth_cred_simple_t **cred_p,
+ void *baton,
+ const char *realm,
+ const char *username,
+ svn_boolean_t may_save,
+ apr_pool_t *pool);
+
+
+/** An implementation of @c svn_auth_username_prompt_func_t that prompts
+ * the user for their username via the command line.
+ *
+ * @since New in 1.4.
+ *
+ * Expects a @c svn_cmdline_prompt_baton_t to be passed as @a baton.
+ */
+svn_error_t *
+svn_cmdline_auth_username_prompt(svn_auth_cred_username_t **cred_p,
+ void *baton,
+ const char *realm,
+ svn_boolean_t may_save,
+ apr_pool_t *pool);
+
+
+/** An implementation of @c svn_auth_ssl_server_trust_prompt_func_t that
+ * asks the user if they trust a specific ssl server via the command line.
+ *
+ * @since New in 1.4.
+ *
+ * Expects a @c svn_cmdline_prompt_baton_t to be passed as @a baton.
+ */
+svn_error_t *
+svn_cmdline_auth_ssl_server_trust_prompt(
+ svn_auth_cred_ssl_server_trust_t **cred_p,
+ void *baton,
+ const char *realm,
+ apr_uint32_t failures,
+ const svn_auth_ssl_server_cert_info_t *cert_info,
+ svn_boolean_t may_save,
+ apr_pool_t *pool);
+
+
+/** An implementation of @c svn_auth_ssl_client_cert_prompt_func_t that
+ * prompts the user for the filename of their SSL client certificate via
+ * the command line.
+ *
+ * Records absolute path of the SSL client certificate file.
+ *
+ * @since New in 1.4.
+ *
+ * Expects a @c svn_cmdline_prompt_baton_t to be passed as @a baton.
+ */
+svn_error_t *
+svn_cmdline_auth_ssl_client_cert_prompt(
+ svn_auth_cred_ssl_client_cert_t **cred_p,
+ void *baton,
+ const char *realm,
+ svn_boolean_t may_save,
+ apr_pool_t *pool);
+
+
+/** An implementation of @c svn_auth_ssl_client_cert_pw_prompt_func_t that
+ * prompts the user for their SSL certificate password via the command line.
+ *
+ * @since New in 1.4.
+ *
+ * Expects a @c svn_cmdline_prompt_baton_t to be passed as @a baton.
+ */
+svn_error_t *
+svn_cmdline_auth_ssl_client_cert_pw_prompt(
+ svn_auth_cred_ssl_client_cert_pw_t **cred_p,
+ void *baton,
+ const char *realm,
+ svn_boolean_t may_save,
+ apr_pool_t *pool);
+
+/** An implementation of @c svn_auth_plaintext_prompt_func_t that
+ * prompts the user whether storing unencrypted passwords to disk is OK.
+ *
+ * Expects a @c svn_cmdline_prompt_baton2_t to be passed as @a baton.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_cmdline_auth_plaintext_prompt(svn_boolean_t *may_save_plaintext,
+ const char *realmstring,
+ void *baton,
+ apr_pool_t *pool);
+
+/** An implementation of @c svn_auth_plaintext_passphrase_prompt_func_t that
+ * prompts the user whether storing unencrypted passphrase to disk is OK.
+ *
+ * Expects a @c svn_cmdline_prompt_baton2_t to be passed as @a baton.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_cmdline_auth_plaintext_passphrase_prompt(svn_boolean_t *may_save_plaintext,
+ const char *realmstring,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/** Set @a *ab to an authentication baton allocated from @a pool and
+ * initialized with the standard set of authentication providers used
+ * by the command line client.
+ *
+ * @a non_interactive, @a username, @a password, @a config_dir,
+ * @a no_auth_cache, and @a trust_server_cert are the values of the
+ * command line options of the corresponding names.
+ *
+ * @a cfg is the @c SVN_CONFIG_CATEGORY_CONFIG configuration, and
+ * @a cancel_func and @a cancel_baton control the cancellation of the
+ * prompting providers that are initialized.
+ *
+ * Use @a pool for all allocations.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_cmdline_create_auth_baton(svn_auth_baton_t **ab,
+ svn_boolean_t non_interactive,
+ const char *username,
+ const char *password,
+ const char *config_dir,
+ svn_boolean_t no_auth_cache,
+ svn_boolean_t trust_server_cert,
+ svn_config_t *cfg,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/** Similar to svn_cmdline_create_auth_baton(), but with
+ * @a trust_server_cert always set to false.
+ *
+ * @since New in 1.4.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ * Use svn_cmdline_create_auth_baton() instead.
+ *
+ * @note This deprecation does not follow the usual pattern of putting
+ * a new number on end of the function's name. Instead, the new
+ * function name is distinguished from the old by a grammatical
+ * improvement: the verb "create" instead of the noun "setup".
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_cmdline_setup_auth_baton(svn_auth_baton_t **ab,
+ svn_boolean_t non_interactive,
+ const char *username,
+ const char *password,
+ const char *config_dir,
+ svn_boolean_t no_auth_cache,
+ svn_config_t *cfg,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CMDLINE_H */
diff --git a/contrib/subversion/subversion/include/svn_compat.h b/contrib/subversion/subversion/include/svn_compat.h
new file mode 100644
index 0000000..a127125
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_compat.h
@@ -0,0 +1,104 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_compat.h
+ * @brief Utilities to help applications provide backwards-compatibility
+ */
+
+#ifndef SVN_COMPAT_H
+#define SVN_COMPAT_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** Return, in @a *callback2 and @a *callback2_baton a function/baton that
+ * will call @a callback/@a callback_baton, allocating the @a *callback2_baton
+ * in @a pool.
+ *
+ * @note This is used by compatibility wrappers, which exist in more than
+ * Subversion core library.
+ *
+ * @since New in 1.4.
+ */
+void
+svn_compat_wrap_commit_callback(svn_commit_callback2_t *callback2,
+ void **callback2_baton,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ apr_pool_t *pool);
+
+/** Clear svn:author, svn:date, and svn:log from @a revprops if not NULL.
+ * Use this if you must handle these three properties separately for
+ * compatibility reasons.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_compat_log_revprops_clear(apr_hash_t *revprops);
+
+/** Return a list to pass to post-1.5 log-retrieval functions in order to
+ * retrieve the pre-1.5 set of revprops: svn:author, svn:date, and svn:log.
+ *
+ * @since New in 1.5.
+ */
+apr_array_header_t *
+svn_compat_log_revprops_in(apr_pool_t *pool);
+
+/** Return, in @a **author, @a **date, and @a **message, the values of the
+ * svn:author, svn:date, and svn:log revprops from @a revprops. If @a
+ * revprops is NULL, all return values are NULL. Any return value may be
+ * NULL if the corresponding property is not set in @a revprops.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_compat_log_revprops_out(const char **author, const char **date,
+ const char **message, apr_hash_t *revprops);
+
+/** Return, in @a *receiver2 and @a *receiver2_baton a function/baton that
+ * will call @a receiver/@a receiver_baton, allocating the @a *receiver2_baton
+ * in @a pool.
+ *
+ * @note This is used by compatibility wrappers, which exist in more than
+ * Subversion core library.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
+ void **receiver2_baton,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_COMPAT_H */
diff --git a/contrib/subversion/subversion/include/svn_config.h b/contrib/subversion/subversion/include/svn_config.h
new file mode 100644
index 0000000..c5d6976
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_config.h
@@ -0,0 +1,808 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_config.h
+ * @brief Accessing SVN configuration files.
+ */
+
+
+
+#ifndef SVN_CONFIG_H
+#define SVN_CONFIG_H
+
+#include <apr.h> /* for apr_int64_t */
+#include <apr_pools.h> /* for apr_pool_t */
+#include <apr_hash.h> /* for apr_hash_t */
+
+#include "svn_types.h"
+#include "svn_io.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/**************************************************************************
+ *** ***
+ *** For a description of the SVN configuration file syntax, see ***
+ *** your ~/.subversion/README, which is written out automatically by ***
+ *** svn_config_ensure(). ***
+ *** ***
+ **************************************************************************/
+
+
+/** Opaque structure describing a set of configuration options. */
+typedef struct svn_config_t svn_config_t;
+
+
+/*** Configuration Defines ***/
+
+/**
+ * @name Client configuration files strings
+ * Strings for the names of files, sections, and options in the
+ * client configuration files.
+ * @{
+ */
+
+ /* This list of #defines is intentionally presented as a nested list
+ that matches the in-config hierarchy. */
+
+#define SVN_CONFIG_CATEGORY_SERVERS "servers"
+#define SVN_CONFIG_SECTION_GROUPS "groups"
+#define SVN_CONFIG_SECTION_GLOBAL "global"
+#define SVN_CONFIG_OPTION_HTTP_PROXY_HOST "http-proxy-host"
+#define SVN_CONFIG_OPTION_HTTP_PROXY_PORT "http-proxy-port"
+#define SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME "http-proxy-username"
+#define SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD "http-proxy-password"
+#define SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS "http-proxy-exceptions"
+#define SVN_CONFIG_OPTION_HTTP_TIMEOUT "http-timeout"
+#define SVN_CONFIG_OPTION_HTTP_COMPRESSION "http-compression"
+#define SVN_CONFIG_OPTION_NEON_DEBUG_MASK "neon-debug-mask"
+#define SVN_CONFIG_OPTION_HTTP_AUTH_TYPES "http-auth-types"
+#define SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES "ssl-authority-files"
+#define SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA "ssl-trust-default-ca"
+#define SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE "ssl-client-cert-file"
+#define SVN_CONFIG_OPTION_SSL_CLIENT_CERT_PASSWORD "ssl-client-cert-password"
+#define SVN_CONFIG_OPTION_SSL_PKCS11_PROVIDER "ssl-pkcs11-provider"
+#define SVN_CONFIG_OPTION_HTTP_LIBRARY "http-library"
+#define SVN_CONFIG_OPTION_STORE_PASSWORDS "store-passwords"
+#define SVN_CONFIG_OPTION_STORE_PLAINTEXT_PASSWORDS "store-plaintext-passwords"
+#define SVN_CONFIG_OPTION_STORE_AUTH_CREDS "store-auth-creds"
+#define SVN_CONFIG_OPTION_STORE_SSL_CLIENT_CERT_PP "store-ssl-client-cert-pp"
+#define SVN_CONFIG_OPTION_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT \
+ "store-ssl-client-cert-pp-plaintext"
+#define SVN_CONFIG_OPTION_USERNAME "username"
+/** @since New in 1.8. */
+#define SVN_CONFIG_OPTION_HTTP_BULK_UPDATES "http-bulk-updates"
+/** @since New in 1.8. */
+#define SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS "http-max-connections"
+
+#define SVN_CONFIG_CATEGORY_CONFIG "config"
+#define SVN_CONFIG_SECTION_AUTH "auth"
+#define SVN_CONFIG_OPTION_PASSWORD_STORES "password-stores"
+#define SVN_CONFIG_OPTION_KWALLET_WALLET "kwallet-wallet"
+#define SVN_CONFIG_OPTION_KWALLET_SVN_APPLICATION_NAME_WITH_PID "kwallet-svn-application-name-with-pid"
+/** @since New in 1.8. */
+#define SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT "ssl-client-cert-file-prompt"
+/* The majority of options of the "auth" section
+ * has been moved to SVN_CONFIG_CATEGORY_SERVERS. */
+#define SVN_CONFIG_SECTION_HELPERS "helpers"
+#define SVN_CONFIG_OPTION_EDITOR_CMD "editor-cmd"
+#define SVN_CONFIG_OPTION_DIFF_CMD "diff-cmd"
+/** @since New in 1.7. */
+#define SVN_CONFIG_OPTION_DIFF_EXTENSIONS "diff-extensions"
+#define SVN_CONFIG_OPTION_DIFF3_CMD "diff3-cmd"
+#define SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG "diff3-has-program-arg"
+#define SVN_CONFIG_OPTION_MERGE_TOOL_CMD "merge-tool-cmd"
+#define SVN_CONFIG_SECTION_MISCELLANY "miscellany"
+#define SVN_CONFIG_OPTION_GLOBAL_IGNORES "global-ignores"
+#define SVN_CONFIG_OPTION_LOG_ENCODING "log-encoding"
+#define SVN_CONFIG_OPTION_USE_COMMIT_TIMES "use-commit-times"
+/** @deprecated Not used by Subversion since 2003/r847039 (well before 1.0) */
+#define SVN_CONFIG_OPTION_TEMPLATE_ROOT "template-root"
+#define SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS "enable-auto-props"
+#define SVN_CONFIG_OPTION_NO_UNLOCK "no-unlock"
+#define SVN_CONFIG_OPTION_MIMETYPES_FILE "mime-types-file"
+#define SVN_CONFIG_OPTION_PRESERVED_CF_EXTS "preserved-conflict-file-exts"
+#define SVN_CONFIG_OPTION_INTERACTIVE_CONFLICTS "interactive-conflicts"
+#define SVN_CONFIG_OPTION_MEMORY_CACHE_SIZE "memory-cache-size"
+#define SVN_CONFIG_SECTION_TUNNELS "tunnels"
+#define SVN_CONFIG_SECTION_AUTO_PROPS "auto-props"
+/** @since New in 1.8. */
+#define SVN_CONFIG_SECTION_WORKING_COPY "working-copy"
+/** @since New in 1.8. */
+#define SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE "exclusive-locking"
+/** @since New in 1.8. */
+#define SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE_CLIENTS "exclusive-locking-clients"
+/** @} */
+
+/** @name Repository conf directory configuration files strings
+ * Strings for the names of sections and options in the
+ * repository conf directory configuration files.
+ * @{
+ */
+/* For repository svnserve.conf files */
+#define SVN_CONFIG_SECTION_GENERAL "general"
+#define SVN_CONFIG_OPTION_ANON_ACCESS "anon-access"
+#define SVN_CONFIG_OPTION_AUTH_ACCESS "auth-access"
+#define SVN_CONFIG_OPTION_PASSWORD_DB "password-db"
+#define SVN_CONFIG_OPTION_REALM "realm"
+#define SVN_CONFIG_OPTION_AUTHZ_DB "authz-db"
+/** @since New in 1.8. */
+#define SVN_CONFIG_OPTION_GROUPS_DB "groups-db"
+/** @since New in 1.7. */
+#define SVN_CONFIG_OPTION_FORCE_USERNAME_CASE "force-username-case"
+/** @since New in 1.8. */
+#define SVN_CONFIG_OPTION_HOOKS_ENV "hooks-env"
+#define SVN_CONFIG_SECTION_SASL "sasl"
+#define SVN_CONFIG_OPTION_USE_SASL "use-sasl"
+#define SVN_CONFIG_OPTION_MIN_SSF "min-encryption"
+#define SVN_CONFIG_OPTION_MAX_SSF "max-encryption"
+
+/* For repository password database */
+#define SVN_CONFIG_SECTION_USERS "users"
+/** @} */
+
+/*** Configuration Default Values ***/
+
+/* '*' matches leading dots, e.g. '*.rej' matches '.foo.rej'. */
+/* We want this to be printed on two lines in the generated config file,
+ * but we don't want the # character to end up in the variable.
+ */
+#define SVN_CONFIG__DEFAULT_GLOBAL_IGNORES_LINE_1 \
+ "*.o *.lo *.la *.al .libs *.so *.so.[0-9]* *.a *.pyc *.pyo __pycache__"
+#define SVN_CONFIG__DEFAULT_GLOBAL_IGNORES_LINE_2 \
+ "*.rej *~ #*# .#* .*.swp .DS_Store"
+
+#define SVN_CONFIG_DEFAULT_GLOBAL_IGNORES \
+ SVN_CONFIG__DEFAULT_GLOBAL_IGNORES_LINE_1 " " \
+ SVN_CONFIG__DEFAULT_GLOBAL_IGNORES_LINE_2
+
+#define SVN_CONFIG_TRUE "TRUE"
+#define SVN_CONFIG_FALSE "FALSE"
+#define SVN_CONFIG_ASK "ASK"
+
+/* Default values for some options. Should be passed as default values
+ * to svn_config_get and friends, instead of hard-coding the defaults in
+ * multiple places. */
+#define SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS TRUE
+#define SVN_CONFIG_DEFAULT_OPTION_STORE_PLAINTEXT_PASSWORDS SVN_CONFIG_ASK
+#define SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS TRUE
+#define SVN_CONFIG_DEFAULT_OPTION_STORE_SSL_CLIENT_CERT_PP TRUE
+#define SVN_CONFIG_DEFAULT_OPTION_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT \
+ SVN_CONFIG_ASK
+#define SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS 4
+
+/** Read configuration information from the standard sources and merge it
+ * into the hash @a *cfg_hash. If @a config_dir is not NULL it specifies a
+ * directory from which to read the configuration files, overriding all
+ * other sources. Otherwise, first read any system-wide configurations
+ * (from a file or from the registry), then merge in personal
+ * configurations (again from file or registry). The hash and all its data
+ * are allocated in @a pool.
+ *
+ * @a *cfg_hash is a hash whose keys are @c const char * configuration
+ * categories (@c SVN_CONFIG_CATEGORY_SERVERS,
+ * @c SVN_CONFIG_CATEGORY_CONFIG, etc.) and whose values are the @c
+ * svn_config_t * items representing the configuration values for that
+ * category.
+ */
+svn_error_t *
+svn_config_get_config(apr_hash_t **cfg_hash,
+ const char *config_dir,
+ apr_pool_t *pool);
+
+/** Set @a *cfgp to an empty @c svn_config_t structure,
+ * allocated in @a result_pool.
+ *
+ * Pass TRUE to @a section_names_case_sensitive if
+ * section names are to be populated case sensitively.
+ *
+ * Pass TRUE to @a option_names_case_sensitive if
+ * option names are to be populated case sensitively.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_config_create2(svn_config_t **cfgp,
+ svn_boolean_t section_names_case_sensitive,
+ svn_boolean_t option_names_case_sensitive,
+ apr_pool_t *result_pool);
+
+/** Similar to svn_config_create2, but always passes @c FALSE to
+ * @a option_names_case_sensitive.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_config_create(svn_config_t **cfgp,
+ svn_boolean_t section_names_case_sensitive,
+ apr_pool_t *result_pool);
+
+/** Read configuration data from @a file (a file or registry path) into
+ * @a *cfgp, allocated in @a pool.
+ *
+ * If @a file does not exist, then if @a must_exist, return an error,
+ * otherwise return an empty @c svn_config_t.
+ *
+ * If @a section_names_case_sensitive is @c TRUE, populate section name hashes
+ * case sensitively, except for the @c "DEFAULT" section.
+ *
+ * If @a option_names_case_sensitive is @c TRUE, populate option name hashes
+ * case sensitively.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_config_read3(svn_config_t **cfgp,
+ const char *file,
+ svn_boolean_t must_exist,
+ svn_boolean_t section_names_case_sensitive,
+ svn_boolean_t option_names_case_sensitive,
+ apr_pool_t *result_pool);
+
+/** Similar to svn_config_read3, but always passes @c FALSE to
+ * @a option_names_case_sensitive.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_config_read2(svn_config_t **cfgp,
+ const char *file,
+ svn_boolean_t must_exist,
+ svn_boolean_t section_names_case_sensitive,
+ apr_pool_t *result_pool);
+
+/** Similar to svn_config_read2, but always passes @c FALSE to
+ * @a section_names_case_sensitive.
+ *
+ * @deprecated Provided for backward compatibility with 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_config_read(svn_config_t **cfgp,
+ const char *file,
+ svn_boolean_t must_exist,
+ apr_pool_t *result_pool);
+
+/** Read configuration data from @a stream into @a *cfgp, allocated in
+ * @a result_pool.
+ *
+ * If @a section_names_case_sensitive is @c TRUE, populate section name hashes
+ * case sensitively, except for the @c "DEFAULT" section.
+ *
+ * If @a option_names_case_sensitive is @c TRUE, populate option name hashes
+ * case sensitively.
+ *
+ * @since New in 1.8.
+ */
+
+svn_error_t *
+svn_config_parse(svn_config_t **cfgp,
+ svn_stream_t *stream,
+ svn_boolean_t section_names_case_sensitive,
+ svn_boolean_t option_names_case_sensitive,
+ apr_pool_t *result_pool);
+
+/** Like svn_config_read(), but merges the configuration data from @a file
+ * (a file or registry path) into @a *cfg, which was previously returned
+ * from svn_config_read(). This function invalidates all value
+ * expansions in @a cfg, so that the next svn_config_get() takes the
+ * modifications into account.
+ */
+svn_error_t *
+svn_config_merge(svn_config_t *cfg,
+ const char *file,
+ svn_boolean_t must_exist);
+
+
+/** Find the value of a (@a section, @a option) pair in @a cfg, set @a
+ * *valuep to the value.
+ *
+ * If @a cfg is @c NULL, just sets @a *valuep to @a default_value. If
+ * the value does not exist, expand and return @a default_value. @a
+ * default_value can be NULL.
+ *
+ * The returned value will be valid at least until the next call to
+ * svn_config_get(), or for the lifetime of @a default_value. It is
+ * safest to consume the returned value immediately.
+ *
+ * This function may change @a cfg by expanding option values.
+ */
+void
+svn_config_get(svn_config_t *cfg,
+ const char **valuep,
+ const char *section,
+ const char *option,
+ const char *default_value);
+
+/** Add or replace the value of a (@a section, @a option) pair in @a cfg with
+ * @a value.
+ *
+ * This function invalidates all value expansions in @a cfg.
+ *
+ * To remove an option, pass NULL for the @a value.
+ */
+void
+svn_config_set(svn_config_t *cfg,
+ const char *section,
+ const char *option,
+ const char *value);
+
+/** Like svn_config_get(), but for boolean values.
+ *
+ * Parses the option as a boolean value. The recognized representations
+ * are 'TRUE'/'FALSE', 'yes'/'no', 'on'/'off', '1'/'0'; case does not
+ * matter. Returns an error if the option doesn't contain a known string.
+ */
+svn_error_t *
+svn_config_get_bool(svn_config_t *cfg,
+ svn_boolean_t *valuep,
+ const char *section,
+ const char *option,
+ svn_boolean_t default_value);
+
+/** Like svn_config_set(), but for boolean values.
+ *
+ * Sets the option to 'TRUE'/'FALSE', depending on @a value.
+ */
+void
+svn_config_set_bool(svn_config_t *cfg,
+ const char *section,
+ const char *option,
+ svn_boolean_t value);
+
+/** Like svn_config_get(), but for 64-bit signed integers.
+ *
+ * Parses the @a option in @a section of @a cfg as an integer value,
+ * setting @a *valuep to the result. If the option is not found, sets
+ * @a *valuep to @a default_value. If the option is found but cannot
+ * be converted to an integer, returns an error.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_config_get_int64(svn_config_t *cfg,
+ apr_int64_t *valuep,
+ const char *section,
+ const char *option,
+ apr_int64_t default_value);
+
+/** Like svn_config_set(), but for 64-bit signed integers.
+ *
+ * Sets the value of @a option in @a section of @a cfg to the signed
+ * decimal @a value.
+ *
+ * @since New in 1.8.
+ */
+void
+svn_config_set_int64(svn_config_t *cfg,
+ const char *section,
+ const char *option,
+ apr_int64_t value);
+
+/** Like svn_config_get(), but only for yes/no/ask values.
+ *
+ * Parse @a option in @a section and set @a *valuep to one of
+ * SVN_CONFIG_TRUE, SVN_CONFIG_FALSE, or SVN_CONFIG_ASK. If there is
+ * no setting for @a option, then parse @a default_value and set
+ * @a *valuep accordingly. If @a default_value is NULL, the result is
+ * undefined, and may be an error; we recommend that you pass one of
+ * SVN_CONFIG_TRUE, SVN_CONFIG_FALSE, or SVN_CONFIG_ASK for @a default value.
+ *
+ * Valid representations are (at least) "true"/"false", "yes"/"no",
+ * "on"/"off", "1"/"0", and "ask"; they are case-insensitive. Return
+ * an SVN_ERR_BAD_CONFIG_VALUE error if either @a default_value or
+ * @a option's value is not a valid representation.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_config_get_yes_no_ask(svn_config_t *cfg,
+ const char **valuep,
+ const char *section,
+ const char *option,
+ const char* default_value);
+
+/** Like svn_config_get_bool(), but for tristate values.
+ *
+ * Set @a *valuep to #svn_tristate_true, #svn_tristate_false, or
+ * #svn_tristate_unknown, depending on the value of @a option in @a
+ * section of @a cfg. True and false values are the same as for
+ * svn_config_get_bool(); @a unknown_value specifies the option value
+ * allowed for third state (#svn_tristate_unknown).
+ *
+ * Use @a default_value as the default value if @a option cannot be
+ * found.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_config_get_tristate(svn_config_t *cfg,
+ svn_tristate_t *valuep,
+ const char *section,
+ const char *option,
+ const char *unknown_value,
+ svn_tristate_t default_value);
+
+/** Similar to @c svn_config_section_enumerator2_t, but is not
+ * provided with a memory pool argument.
+ *
+ * See svn_config_enumerate_sections() for the details of this type.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.2 API.
+ */
+typedef svn_boolean_t (*svn_config_section_enumerator_t)(const char *name,
+ void *baton);
+
+/** Similar to svn_config_enumerate_sections2(), but uses a memory pool of
+ * @a cfg instead of one that is explicitly provided.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+int
+svn_config_enumerate_sections(svn_config_t *cfg,
+ svn_config_section_enumerator_t callback,
+ void *baton);
+
+/** A callback function used in enumerating config sections.
+ *
+ * See svn_config_enumerate_sections2() for the details of this type.
+ *
+ * @since New in 1.3.
+ */
+typedef svn_boolean_t (*svn_config_section_enumerator2_t)(const char *name,
+ void *baton,
+ apr_pool_t *pool);
+
+/** Enumerate the sections, passing @a baton and the current section's name
+ * to @a callback. Continue the enumeration if @a callback returns @c TRUE.
+ * Return the number of times @a callback was called.
+ *
+ * ### See kff's comment to svn_config_enumerate2(). It applies to this
+ * function, too. ###
+ *
+ * @a callback's @a name parameter is only valid for the duration of the call.
+ *
+ * @since New in 1.3.
+ */
+int
+svn_config_enumerate_sections2(svn_config_t *cfg,
+ svn_config_section_enumerator2_t callback,
+ void *baton, apr_pool_t *pool);
+
+/** Similar to @c svn_config_enumerator2_t, but is not
+ * provided with a memory pool argument.
+ * See svn_config_enumerate() for the details of this type.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.2 API.
+ */
+typedef svn_boolean_t (*svn_config_enumerator_t)(const char *name,
+ const char *value,
+ void *baton);
+
+/** Similar to svn_config_enumerate2(), but uses a memory pool of
+ * @a cfg instead of one that is explicitly provided.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+int
+svn_config_enumerate(svn_config_t *cfg,
+ const char *section,
+ svn_config_enumerator_t callback,
+ void *baton);
+
+
+/** A callback function used in enumerating config options.
+ *
+ * See svn_config_enumerate2() for the details of this type.
+ *
+ * @since New in 1.3.
+ */
+typedef svn_boolean_t (*svn_config_enumerator2_t)(const char *name,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool);
+
+/** Enumerate the options in @a section, passing @a baton and the current
+ * option's name and value to @a callback. Continue the enumeration if
+ * @a callback returns @c TRUE. Return the number of times @a callback
+ * was called.
+ *
+ * ### kff asks: A more usual interface is to continue enumerating
+ * while @a callback does not return error, and if @a callback does
+ * return error, to return the same error (or a wrapping of it)
+ * from svn_config_enumerate(). What's the use case for
+ * svn_config_enumerate()? Is it more likely to need to break out
+ * of an enumeration early, with no error, than an invocation of
+ * @a callback is likely to need to return an error? ###
+ *
+ * @a callback's @a name and @a value parameters are only valid for the
+ * duration of the call.
+ *
+ * @since New in 1.3.
+ */
+int
+svn_config_enumerate2(svn_config_t *cfg,
+ const char *section,
+ svn_config_enumerator2_t callback,
+ void *baton,
+ apr_pool_t *pool);
+
+/**
+ * Return @c TRUE if @a section exists in @a cfg, @c FALSE otherwise.
+ *
+ * @since New in 1.4.
+ */
+svn_boolean_t
+svn_config_has_section(svn_config_t *cfg,
+ const char *section);
+
+/** Enumerate the group @a master_section in @a cfg. Each variable
+ * value is interpreted as a list of glob patterns (separated by comma
+ * and optional whitespace). Return the name of the first variable
+ * whose value matches @a key, or @c NULL if no variable matches.
+ */
+const char *
+svn_config_find_group(svn_config_t *cfg,
+ const char *key,
+ const char *master_section,
+ apr_pool_t *pool);
+
+/** Retrieve value corresponding to @a option_name in @a cfg, or
+ * return @a default_value if none is found.
+ *
+ * The config will first be checked for a default.
+ * If @a server_group is not @c NULL, the config will also be checked
+ * for an override in a server group,
+ *
+ */
+const char *
+svn_config_get_server_setting(svn_config_t *cfg,
+ const char* server_group,
+ const char* option_name,
+ const char* default_value);
+
+/** Retrieve value into @a result_value corresponding to @a option_name for a
+ * given @a server_group in @a cfg, or return @a default_value if none is
+ * found.
+ *
+ * The config will first be checked for a default, then will be checked for
+ * an override in a server group. If the value found is not a valid integer,
+ * a @c svn_error_t* will be returned.
+ */
+svn_error_t *
+svn_config_get_server_setting_int(svn_config_t *cfg,
+ const char *server_group,
+ const char *option_name,
+ apr_int64_t default_value,
+ apr_int64_t *result_value,
+ apr_pool_t *pool);
+
+
+/** Set @a *valuep according to @a option_name for a given
+ * @a server_group in @a cfg, or set to @a default_value if no value is
+ * specified.
+ *
+ * Check first a default, then for an override in a server group. If
+ * a value is found but is not a valid boolean, return an
+ * SVN_ERR_BAD_CONFIG_VALUE error.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_config_get_server_setting_bool(svn_config_t *cfg,
+ svn_boolean_t *valuep,
+ const char *server_group,
+ const char *option_name,
+ svn_boolean_t default_value);
+
+
+
+/** Try to ensure that the user's ~/.subversion/ area exists, and create
+ * no-op template files for any absent config files. Use @a pool for any
+ * temporary allocation. If @a config_dir is not @c NULL it specifies a
+ * directory from which to read the config overriding all other sources.
+ *
+ * Don't error if something exists but is the wrong kind (for example,
+ * ~/.subversion exists but is a file, or ~/.subversion/servers exists
+ * but is a directory).
+ *
+ * Also don't error if trying to create something and failing -- it's
+ * okay for the config area or its contents not to be created.
+ * However, if creating a config template file succeeds, return an
+ * error if unable to initialize its contents.
+ */
+svn_error_t *
+svn_config_ensure(const char *config_dir,
+ apr_pool_t *pool);
+
+
+
+
+/** Accessing cached authentication data in the user config area.
+ *
+ * @defgroup cached_authentication_data Cached authentication data
+ * @{
+ */
+
+
+/** A hash-key pointing to a realmstring. Every file containing
+ * authentication data should have this key.
+ */
+#define SVN_CONFIG_REALMSTRING_KEY "svn:realmstring"
+
+/** Use @a cred_kind and @a realmstring to locate a file within the
+ * ~/.subversion/auth/ area. If the file exists, initialize @a *hash
+ * and load the file contents into the hash, using @a pool. If the
+ * file doesn't exist, set @a *hash to NULL.
+ *
+ * If @a config_dir is not NULL it specifies a directory from which to
+ * read the config overriding all other sources.
+ *
+ * Besides containing the original credential fields, the hash will
+ * also contain @c SVN_CONFIG_REALMSTRING_KEY. The caller can examine
+ * this value as a sanity-check that the correct file was loaded.
+ *
+ * The hashtable will contain <tt>const char *</tt> keys and
+ * <tt>svn_string_t *</tt> values.
+ */
+svn_error_t *
+svn_config_read_auth_data(apr_hash_t **hash,
+ const char *cred_kind,
+ const char *realmstring,
+ const char *config_dir,
+ apr_pool_t *pool);
+
+/** Use @a cred_kind and @a realmstring to create or overwrite a file
+ * within the ~/.subversion/auth/ area. Write the contents of @a hash into
+ * the file. If @a config_dir is not NULL it specifies a directory to read
+ * the config overriding all other sources.
+ *
+ * Also, add @a realmstring to the file, with key @c
+ * SVN_CONFIG_REALMSTRING_KEY. This allows programs (or users) to
+ * verify exactly which set credentials live within the file.
+ *
+ * The hashtable must contain <tt>const char *</tt> keys and
+ * <tt>svn_string_t *</tt> values.
+ */
+svn_error_t *
+svn_config_write_auth_data(apr_hash_t *hash,
+ const char *cred_kind,
+ const char *realmstring,
+ const char *config_dir,
+ apr_pool_t *pool);
+
+
+/** Callback for svn_config_walk_auth_data().
+ *
+ * Called for each credential walked by that function (and able to be
+ * fully purged) to allow perusal and selective removal of credentials.
+ *
+ * @a cred_kind and @a realmstring specify the key of the credential.
+ * @a hash contains the hash data associated with the record.
+ *
+ * Before returning set @a *delete_cred to TRUE to remove the credential from
+ * the cache; leave @a *delete_cred unchanged or set it to FALSE to keep the
+ * credential.
+ *
+ * Implementations may return #SVN_ERR_CEASE_INVOCATION to indicate
+ * that the callback should not be called again. Note that when that
+ * error is returned, the value of @a delete_cred will still be
+ * honored and action taken if necessary. (For other returned errors,
+ * @a delete_cred is ignored by svn_config_walk_auth_data().)
+ *
+ * @since New in 1.8.
+ */
+typedef svn_error_t *
+(*svn_config_auth_walk_func_t)(svn_boolean_t *delete_cred,
+ void *cleanup_baton,
+ const char *cred_kind,
+ const char *realmstring,
+ apr_hash_t *hash,
+ apr_pool_t *scratch_pool);
+
+/** Call @a walk_func with @a walk_baton and information describing
+ * each credential cached within the Subversion auth store located
+ * under @a config_dir. If the callback sets its delete_cred return
+ * flag, delete the associated credential.
+ *
+ * @note Removing credentials from the config-based disk store will
+ * not purge them from any open svn_auth_baton_t instance. Consider
+ * using svn_auth_forget_credentials() -- from the @a cleanup_func,
+ * even -- for this purpose.
+ *
+ * @note Removing credentials from the config-based disk store will
+ * not also remove any related credentials from third-party password
+ * stores. (Implementations of @a walk_func which delete credentials
+ * may wish to consult the "passtype" element of @a hash, if any, to
+ * see if a third-party store -- such as "gnome-keyring" or "kwallet"
+ * is being used to hold the most sensitive portion of the credentials
+ * for this @a cred_kind and @a realmstring.)
+ *
+ * @see svn_auth_forget_credentials()
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_config_walk_auth_data(const char *config_dir,
+ svn_config_auth_walk_func_t walk_func,
+ void *walk_baton,
+ apr_pool_t *scratch_pool);
+
+/** Put the absolute path to the user's configuration directory,
+ * or to a file within that directory, into @a *path.
+ *
+ * If @a config_dir is not NULL, it must point to an alternative
+ * config directory location. If it is NULL, the default location
+ * is used. If @a fname is not NULL, it must specify the last
+ * component of the path to be returned. This can be used to create
+ * a path to any file in the configuration directory.
+ *
+ * Do all allocations in @a pool.
+ *
+ * Hint:
+ * To get the user configuration file, pass @c SVN_CONFIG_CATEGORY_CONFIG
+ * for @a fname. To get the servers configuration file, pass
+ * @c SVN_CONFIG_CATEGORY_SERVERS for @a fname.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_config_get_user_config_path(const char **path,
+ const char *config_dir,
+ const char *fname,
+ apr_pool_t *pool);
+
+/** Create a deep copy of the config object @a src and return
+ * it in @a cfgp, allocating the memory in @a pool.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_config_dup(svn_config_t **cfgp,
+ svn_config_t *src,
+ apr_pool_t *pool);
+
+/** Create a deep copy of the config hash @a src_hash and return
+ * it in @a cfg_hash, allocating the memory in @a pool.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_config_copy_config(apr_hash_t **cfg_hash,
+ apr_hash_t *src_hash,
+ apr_pool_t *pool);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CONFIG_H */
diff --git a/contrib/subversion/subversion/include/svn_ctype.h b/contrib/subversion/subversion/include/svn_ctype.h
new file mode 100644
index 0000000..1263552
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_ctype.h
@@ -0,0 +1,196 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_ctype.h
+ * @brief Character classification routines
+ * @since New in 1.2.
+ */
+
+
+#ifndef SVN_CTYPE_H
+#define SVN_CTYPE_H
+
+#include <apr.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** Table of flags for character classification. */
+extern const apr_uint32_t *const svn_ctype_table;
+
+
+/** Check if @a c is in the character class described by @a flags.
+ * The @a flags is a bitwise-or combination of @c SVN_CTYPE_*
+ * constants. Uses #svn_ctype_table.
+ */
+#define svn_ctype_test(c, flags) \
+ (0 != (svn_ctype_table[(unsigned char)(c)] & (flags)))
+
+
+/**
+ * @defgroup ctype_basic Basic character classification - 7-bit ASCII only
+ * @{
+ */
+
+/* Basic character classes */
+#define SVN_CTYPE_CNTRL 0x0001 /**< Control character */
+#define SVN_CTYPE_SPACE 0x0002 /**< Whitespace */
+#define SVN_CTYPE_DIGIT 0x0004 /**< Decimal digit */
+#define SVN_CTYPE_UPPER 0x0008 /**< Uppercase letter */
+#define SVN_CTYPE_LOWER 0x0010 /**< Lowercase letter */
+#define SVN_CTYPE_PUNCT 0x0020 /**< Punctuation mark */
+#define SVN_CTYPE_XALPHA 0x0040 /**< Hexadecimal digits A to F */
+#define SVN_CTYPE_ASCII 0x0080 /**< ASCII subset*/
+
+/* Derived character classes */
+/** ASCII letter */
+#define SVN_CTYPE_ALPHA (SVN_CTYPE_LOWER | SVN_CTYPE_UPPER)
+/** ASCII letter or decimal digit */
+#define SVN_CTYPE_ALNUM (SVN_CTYPE_ALPHA | SVN_CTYPE_DIGIT)
+/** ASCII hexadecimal digit */
+#define SVN_CTYPE_XDIGIT (SVN_CTYPE_DIGIT | SVN_CTYPE_XALPHA)
+/** Printable ASCII except space */
+#define SVN_CTYPE_GRAPH (SVN_CTYPE_PUNCT | SVN_CTYPE_ALNUM)
+/** All printable ASCII */
+#define SVN_CTYPE_PRINT (SVN_CTYPE_GRAPH | SVN_CTYPE_SPACE)
+
+
+/** Check if @a c is an ASCII control character. */
+#define svn_ctype_iscntrl(c) svn_ctype_test((c), SVN_CTYPE_CNTRL)
+
+/** Check if @a c is an ASCII whitespace character. */
+#define svn_ctype_isspace(c) svn_ctype_test((c), SVN_CTYPE_SPACE)
+
+/** Check if @a c is an ASCII digit. */
+#define svn_ctype_isdigit(c) svn_ctype_test((c), SVN_CTYPE_DIGIT)
+
+/** Check if @a c is an ASCII uppercase letter. */
+#define svn_ctype_isupper(c) svn_ctype_test((c), SVN_CTYPE_UPPER)
+
+/** Check if @a c is an ASCII lowercase letter. */
+#define svn_ctype_islower(c) svn_ctype_test((c), SVN_CTYPE_LOWER)
+
+/** Check if @a c is an ASCII punctuation mark. */
+#define svn_ctype_ispunct(c) svn_ctype_test((c), SVN_CTYPE_PUNCT)
+
+/** Check if @a c is an ASCII character. */
+#define svn_ctype_isascii(c) svn_ctype_test((c), SVN_CTYPE_ASCII)
+
+/** Check if @a c is an ASCII letter. */
+#define svn_ctype_isalpha(c) svn_ctype_test((c), SVN_CTYPE_ALPHA)
+
+/** Check if @a c is an ASCII letter or decimal digit. */
+#define svn_ctype_isalnum(c) svn_ctype_test((c), SVN_CTYPE_ALNUM)
+
+/** Check if @a c is an ASCII hexadecimal digit. */
+#define svn_ctype_isxdigit(c) svn_ctype_test((c), SVN_CTYPE_XDIGIT)
+
+/** Check if @a c is an ASCII graphical (visible printable) character. */
+#define svn_ctype_isgraph(c) svn_ctype_test((c), SVN_CTYPE_GRAPH)
+
+/** Check if @a c is an ASCII printable character. */
+#define svn_ctype_isprint(c) svn_ctype_test((c), SVN_CTYPE_PRINT)
+
+/** @} */
+
+/**
+ * @defgroup ctype_extra Extended character classification
+ * @{
+ */
+
+/* Basic extended character classes */
+#define SVN_CTYPE_UTF8LEAD 0x0100 /**< UTF-8 multibyte lead byte */
+#define SVN_CTYPE_UTF8CONT 0x0200 /**< UTF-8 multibyte non-lead byte */
+/* ### TBD
+#define SVN_CTYPE_XMLNAME 0x0400
+#define SVN_CTYPE_URISAFE 0x0800
+*/
+
+/* Derived extended character classes */
+/** Part of a UTF-8 multibyte character. */
+#define SVN_CTYPE_UTF8MBC (SVN_CTYPE_UTF8LEAD | SVN_CTYPE_UTF8CONT)
+/** All valid UTF-8 bytes. */
+#define SVN_CTYPE_UTF8 (SVN_CTYPE_ASCII | SVN_CTYPE_UTF8MBC)
+
+/** Check if @a c is a UTF-8 multibyte lead byte. */
+#define svn_ctype_isutf8lead(c) svn_ctype_test((c), SVN_CTYPE_UTF8LEAD)
+
+/** Check if @a c is a UTF-8 multibyte continuation (non-lead) byte. */
+#define svn_ctype_isutf8cont(c) svn_ctype_test((c), SVN_CTYLE_UTF8CONT)
+
+/** Check if @a c is part of a UTF-8 multibyte character. */
+#define svn_ctype_isutf8mbc(c) svn_ctype_test((c), SVN_CTYPE_UTF8MBC)
+
+/** Check if @a c is valid in UTF-8. */
+#define svn_ctype_isutf8(c) svn_ctype_test((c), SVN_CTYPE_UTF8)
+
+/** @} */
+
+/**
+ * @defgroup ctype_ascii ASCII character value constants
+ * @{
+ */
+
+#define SVN_CTYPE_ASCII_MINUS 45 /**< ASCII value of '-' */
+#define SVN_CTYPE_ASCII_DOT 46 /**< ASCII value of '.' */
+#define SVN_CTYPE_ASCII_COLON 58 /**< ASCII value of ':' */
+#define SVN_CTYPE_ASCII_UNDERSCORE 95 /**< ASCII value of '_' */
+#define SVN_CTYPE_ASCII_TAB 9 /**< ASCII value of a tab */
+#define SVN_CTYPE_ASCII_LINEFEED 10 /**< ASCII value of a line feed */
+#define SVN_CTYPE_ASCII_CARRIAGERETURN 13
+ /**< ASCII value of a carriage return */
+#define SVN_CTYPE_ASCII_DELETE 127
+ /**< ASCII value of a delete character */
+
+
+/** @} */
+
+/**
+ * @defgroup ctype_case ASCII-subset case folding
+ * @{
+ */
+
+/**
+ * Compare two characters @a a and @a b, treating case-equivalent
+ * unaccented Latin (ASCII subset) letters as equal.
+ *
+ * Returns in integer greater than, equal to, or less than 0,
+ * according to whether @a a is considered greater than, equal to,
+ * or less than @a b.
+ *
+ * @since New in 1.5.
+ */
+int
+svn_ctype_casecmp(int a,
+ int b);
+
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CTYPE_H */
diff --git a/contrib/subversion/subversion/include/svn_dav.h b/contrib/subversion/subversion/include/svn_dav.h
new file mode 100644
index 0000000..e9092d5
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_dav.h
@@ -0,0 +1,398 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_dav.h
+ * @brief Code related to WebDAV/DeltaV usage in Subversion.
+ */
+
+
+
+
+#ifndef SVN_DAV_H
+#define SVN_DAV_H
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** This is the MIME type that Subversion uses for its "svndiff" format.
+ *
+ * This is an application type, for the "svn" vendor. The specific subtype
+ * is "svndiff".
+ */
+#define SVN_SVNDIFF_MIME_TYPE "application/vnd.svn-svndiff"
+
+/** This is the MIME type that Subversion users for its "skel" format.
+ *
+ * This is an application type, for the "svn" vendor. The specific subtype
+ * is "skel".
+ * @since New in 1.7.
+ */
+#define SVN_SKEL_MIME_TYPE "application/vnd.svn-skel"
+
+/** This header is *TEMPORARILY* used to transmit the delta base to the
+ * server. It contains a version resource URL for what is on the client.
+ *
+ * @note The HTTP delta draft recommends an If-None-Match header
+ * holding an entity tag corresponding to the base copy that the
+ * client has. In Subversion, it is much more natural to use a version
+ * URL to specify that base. We'd like, then, to use the If: header
+ * to specify the URL. Unfortunately, mod_dav sees all "State-token"
+ * items as lock tokens. So we'll use this custom header until mod_dav
+ * and other backend APIs are taught to be less rigid, at which time
+ * we can switch to using an If: header to report our base version.
+ */
+#define SVN_DAV_DELTA_BASE_HEADER "X-SVN-VR-Base"
+
+/** This header is used when an svn client wants to trigger specific
+ * svn server behaviors. Normal WebDAV or DeltaV clients won't use it.
+ */
+#define SVN_DAV_OPTIONS_HEADER "X-SVN-Options"
+
+/**
+ * @name options-header defines
+ * Specific options that can appear in the options-header:
+ * @{
+ */
+#define SVN_DAV_OPTION_NO_MERGE_RESPONSE "no-merge-response"
+#define SVN_DAV_OPTION_LOCK_BREAK "lock-break"
+#define SVN_DAV_OPTION_LOCK_STEAL "lock-steal"
+#define SVN_DAV_OPTION_RELEASE_LOCKS "release-locks"
+#define SVN_DAV_OPTION_KEEP_LOCKS "keep-locks"
+/** @} */
+
+/** This header is used when an svn client wants to tell mod_dav_svn
+ * exactly what revision of a resource it thinks it's operating on.
+ * (For example, an svn server can use it to validate a DELETE request.)
+ * Normal WebDAV or DeltaV clients won't use it.
+ */
+#define SVN_DAV_VERSION_NAME_HEADER "X-SVN-Version-Name"
+
+/** A header generated by mod_dav_svn whenever it responds
+ successfully to a LOCK request. Only svn clients will notice it,
+ and use it to fill in svn_lock_t->creation_date. */
+#define SVN_DAV_CREATIONDATE_HEADER "X-SVN-Creation-Date"
+
+/** A header generated by mod_dav_svn whenever it responds
+ successfully to a PROPFIND for the 'DAV:lockdiscovery' property.
+ Only svn clients will notice it, and use it to fill in
+ svn_lock_t->owner. (Remember that the DAV:owner field maps to
+ svn_lock_t->comment, and that there is no analogue in the DAV
+ universe of svn_lock_t->owner.) */
+#define SVN_DAV_LOCK_OWNER_HEADER "X-SVN-Lock-Owner"
+
+/** Assuming the OPTIONS was performed against a resource within a
+ * Subversion repository, then this header indicates the youngest
+ * revision in the repository.
+ * @since New in 1.7. */
+#define SVN_DAV_YOUNGEST_REV_HEADER "SVN-Youngest-Rev"
+
+/** Assuming the OPTIONS was performed against a resource within a
+ * Subversion repository, then this header indicates the UUID of the
+ * repository.
+ * @since New in 1.7. */
+#define SVN_DAV_REPOS_UUID_HEADER "SVN-Repository-UUID"
+
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the server speaks HTTP protocol v2. This header provides an
+ * opaque URI that the client should send all custom REPORT requests
+ * against.
+ * @since New in 1.7. */
+#define SVN_DAV_ME_RESOURCE_HEADER "SVN-Me-Resource"
+
+/** This header provides the repository root URI, suitable for use in
+ * calculating the relative paths of other public URIs for this
+ * repository into . (HTTP protocol v2 only)
+ * @since New in 1.7. */
+#define SVN_DAV_ROOT_URI_HEADER "SVN-Repository-Root"
+
+/** This header provides an opaque URI that the client can append a
+ * revision to, to construct a 'revision URL'. This allows direct
+ * read/write access to revprops via PROPFIND or PROPPATCH, and is
+ * similar to libsvn_fs's revision objects (as distinct from "revision
+ * roots"). (HTTP protocol v2 only)
+ * @since New in 1.7. */
+#define SVN_DAV_REV_STUB_HEADER "SVN-Rev-Stub"
+
+/** This header provides an opaque URI that the client can append
+ * PEGREV/PATH to, in order to construct URIs of pegged objects in the
+ * repository, similar to the use of a "revision root" in the
+ * libsvn_fs API. (HTTP protocol v2 only)
+ * @since New in 1.7. */
+#define SVN_DAV_REV_ROOT_STUB_HEADER "SVN-Rev-Root-Stub"
+
+/** This header provides an opaque URI which represents a Subversion
+ * transaction (revision-in-progress) object. It is suitable for use
+ * in fetching and modifying transaction properties as part of a
+ * commit process, similar to the svn_fs_txn_t object (as distinct
+ * from a "txn root"). (HTTP protocol v2 only)
+ * @since New in 1.7. */
+#define SVN_DAV_TXN_STUB_HEADER "SVN-Txn-Stub"
+
+/** Companion to @c SVN_DAV_TXN_STUB_HEADER, used when a POST request
+ * returns @c SVN_DAV_VTXN_NAME_HEADER in response to a client
+ * supplied name. (HTTP protocol v2 only)
+ * @since New in 1.7. */
+#define SVN_DAV_VTXN_STUB_HEADER "SVN-VTxn-Stub"
+
+/** This header provides an opaque URI which represents the root
+ * directory of a Subversion transaction (revision-in-progress),
+ * similar to the concept of a "txn root" in the libsvn_fs API. The
+ * client can append additional path segments to it to access items
+ * deeper in the transaction tree as part of a commit process. (HTTP
+ * protocol v2 only)
+ * @since New in 1.7. */
+#define SVN_DAV_TXN_ROOT_STUB_HEADER "SVN-Txn-Root-Stub"
+
+/** Companion to @c SVN_DAV_TXN_ROOT_STUB_HEADER, used when a POST
+ * request returns @c SVN_DAV_VTXN_NAME_HEADER in response to a
+ * client supplied name. (HTTP protocol v2 only)
+ * @since New in 1.7. */
+#define SVN_DAV_VTXN_ROOT_STUB_HEADER "SVN-VTxn-Root-Stub"
+
+/** This header is used in the POST response to tell the client the
+ * name of the Subversion transaction created by the request. It can
+ * then be appended to the transaction stub and transaction root stub
+ * for access to the properties and paths, respectively, of the named
+ * transaction. (HTTP protocol v2 only)
+ * @since New in 1.7. */
+#define SVN_DAV_TXN_NAME_HEADER "SVN-Txn-Name"
+
+/** This header is used in the POST request, to pass a client supplied
+ * alternative transaction name to the server, and in the POST
+ * response, to tell the client that the alternative transaction
+ * resource names should be used. (HTTP protocol v2 only)
+ * @since New in 1.7. */
+#define SVN_DAV_VTXN_NAME_HEADER "SVN-VTxn-Name"
+
+/** This header is used in the OPTIONS response to identify named
+ * skel-based POST request types which the server is prepared to
+ * handle. (HTTP protocol v2 only)
+ * @since New in 1.8. */
+#define SVN_DAV_SUPPORTED_POSTS_HEADER "SVN-Supported-Posts"
+
+/** This header is used in the OPTIONS response to indicate if the server
+ * wants bulk update requests (Prefer) or only accepts skelta requests (Off).
+ * If this value is On both options are allowed.
+ * @since New in 1.8. */
+#define SVN_DAV_ALLOW_BULK_UPDATES "SVN-Allow-Bulk-Updates"
+
+/** Assuming the request target is a Subversion repository resource,
+ * this header is returned in the OPTIONS response to indicate whether
+ * the repository supports the merge tracking feature ("yes") or not
+ * ("no").
+ * @since New in 1.8. */
+#define SVN_DAV_REPOSITORY_MERGEINFO "SVN-Repository-MergeInfo"
+
+/**
+ * @name Fulltext MD5 headers
+ *
+ * These headers are for client and server to verify that the base
+ * and the result of a change transmission are the same on both
+ * sides, regardless of what transformations (svndiff deltification,
+ * gzipping, etc) the data may have gone through in between.
+ *
+ * The result md5 is always used whenever file contents are
+ * transferred, because every transmission has a resulting text.
+ *
+ * The base md5 is used to verify the base text against which svndiff
+ * data is being applied. Note that even for svndiff transmissions,
+ * base verification is not strictly necessary (and may therefore be
+ * unimplemented), as any error will be caught by the verification of
+ * the final result. However, if the problem is that the base text is
+ * corrupt, the error will be caught earlier if the base md5 is used.
+ *
+ * Normal WebDAV or DeltaV clients don't use these.
+ * @{
+ */
+#define SVN_DAV_BASE_FULLTEXT_MD5_HEADER "X-SVN-Base-Fulltext-MD5"
+#define SVN_DAV_RESULT_FULLTEXT_MD5_HEADER "X-SVN-Result-Fulltext-MD5"
+/** @} */
+
+/* ### should add strings for the various XML elements in the reports
+ ### and things. also the custom prop names. etc.
+*/
+
+/** The svn-specific object that is placed within a <D:error> response.
+ *
+ * @defgroup svn_dav_error Errors in svn_dav
+ * @{ */
+
+/** The error object's namespace */
+#define SVN_DAV_ERROR_NAMESPACE "svn:"
+
+/** The error object's tag */
+#define SVN_DAV_ERROR_TAG "error"
+
+/** @} */
+
+
+/** General property (xml) namespaces that will be used by both ra_dav
+ * and mod_dav_svn for marshalling properties.
+ *
+ * @defgroup svn_dav_property_xml_namespaces DAV property namespaces
+ * @{
+ */
+
+/** A property stored in the fs and wc, begins with 'svn:', and is
+ * interpreted either by client or server.
+ */
+#define SVN_DAV_PROP_NS_SVN "http://subversion.tigris.org/xmlns/svn/"
+
+/** A property stored in the fs and wc, but totally ignored by svn
+ * client and server.
+ *
+ * A property simply invented by the users.
+ */
+#define SVN_DAV_PROP_NS_CUSTOM "http://subversion.tigris.org/xmlns/custom/"
+
+/** A property purely generated and consumed by the network layer, not
+ * seen by either fs or wc.
+ */
+#define SVN_DAV_PROP_NS_DAV "http://subversion.tigris.org/xmlns/dav/"
+
+
+/**
+ * @name Custom (extension) values for the DAV header.
+ * Note that although these share the SVN_DAV_PROP_NS_DAV namespace
+ * prefix, they are not properties; they are header values.
+ * @{
+ */
+
+/* ##################################################################
+ *
+ * WARNING: At least some versions of Microsoft's Web Folders
+ * WebDAV client implementation are unable to handle
+ * DAV: headers with values longer than 63 characters,
+ * so please keep these strings within that limit.
+ *
+ * ##################################################################
+ */
+
+
+/** Presence of this in a DAV header in an OPTIONS request or response
+ * indicates that the transmitter supports @c svn_depth_t.
+ *
+ * @since New in 1.5.
+ */
+#define SVN_DAV_NS_DAV_SVN_DEPTH\
+ SVN_DAV_PROP_NS_DAV "svn/depth"
+
+/** Presence of this in a DAV header in an OPTIONS request or response
+ * indicates that the server knows how to handle merge-tracking
+ * information.
+ *
+ * Note that this says nothing about whether the repository can handle
+ * mergeinfo, only whether the server does. For more information, see
+ * mod_dav_svn/version.c:get_vsn_options().
+ *
+ * @since New in 1.5.
+ */
+#define SVN_DAV_NS_DAV_SVN_MERGEINFO\
+ SVN_DAV_PROP_NS_DAV "svn/mergeinfo"
+
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the transmitter (in this case, the server) knows how to send
+ * custom revprops in log responses.
+ *
+ * @since New in 1.5.
+ */
+#define SVN_DAV_NS_DAV_SVN_LOG_REVPROPS\
+ SVN_DAV_PROP_NS_DAV "svn/log-revprops"
+
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the transmitter (in this case, the server) knows how to handle
+ * a replay of a directory in the repository (not root).
+ *
+ * @since New in 1.5.
+ */
+#define SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY\
+ SVN_DAV_PROP_NS_DAV "svn/partial-replay"
+
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the transmitter (in this case, the server) knows how to enforce
+ * old-value atomicity in PROPPATCH (for editing revprops).
+ *
+ * @since New in 1.7.
+ */
+#define SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS\
+ SVN_DAV_PROP_NS_DAV "svn/atomic-revprops"
+
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the transmitter (in this case, the server) knows how to get
+ * inherited properties.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_DAV_NS_DAV_SVN_INHERITED_PROPS\
+ SVN_DAV_PROP_NS_DAV "svn/inherited-props"
+
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the transmitter (in this case, the server) knows how to
+ * properly handle ephemeral (that is, deleted-just-before-commit) FS
+ * transaction properties.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS\
+ SVN_DAV_PROP_NS_DAV "svn/ephemeral-txnprops"
+
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the transmitter (in this case, the server) supports serving
+ * properties inline in update editor when 'send-all' is 'false'.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_DAV_NS_DAV_SVN_INLINE_PROPS\
+ SVN_DAV_PROP_NS_DAV "svn/inline-props"
+
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the transmitter (in this case, the server) knows how to handle
+ * a replay of a revision resource. Transmitters must be
+ * HTTP-v2-enabled to support this feature.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE\
+ SVN_DAV_PROP_NS_DAV "svn/replay-rev-resource"
+
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the transmitter (in this case, the server) knows how to handle
+ * a reversed fetch of file versions.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS\
+ SVN_DAV_PROP_NS_DAV "svn/reverse-file-revs"
+
+
+/** @} */
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DAV_H */
diff --git a/contrib/subversion/subversion/include/svn_delta.h b/contrib/subversion/subversion/include/svn_delta.h
new file mode 100644
index 0000000..7df7f3f
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_delta.h
@@ -0,0 +1,1367 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_delta.h
+ * @brief Delta-parsing
+ */
+
+/* ==================================================================== */
+
+
+
+#ifndef SVN_DELTA_H
+#define SVN_DELTA_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_file_io.h> /* for apr_file_t */
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_io.h"
+#include "svn_checksum.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/** This compression level effectively disables data compression.
+ * However, the data pre-processing costs may still not be zero.
+ *
+ * @since New in 1.7.
+ */
+#define SVN_DELTA_COMPRESSION_LEVEL_NONE 0
+
+/** This is the maximum compression level we can pass to zlib.
+ *
+ * @since New in 1.7.
+ */
+#define SVN_DELTA_COMPRESSION_LEVEL_MAX 9
+
+/** This is the default compression level we pass to zlib. It
+ * should be between 0 and 9, with higher numbers resulting in
+ * better compression rates but slower operation.
+ *
+ * @since New in 1.7.
+ */
+#define SVN_DELTA_COMPRESSION_LEVEL_DEFAULT 5
+
+/**
+ * Get libsvn_delta version information.
+ *
+ * @since New in 1.1.
+ */
+const svn_version_t *
+svn_delta_version(void);
+
+/**
+ * @defgroup delta_support Delta generation and handling
+ *
+ * @{
+ */
+
+/** Text deltas.
+ *
+ * A text delta represents the difference between two strings of
+ * bytes, the `source' string and the `target' string. Given a source
+ * string and a target string, we can compute a text delta; given a
+ * source string and a delta, we can reconstruct the target string.
+ * However, note that deltas are not reversible: you cannot always
+ * reconstruct the source string given the target string and delta.
+ *
+ * Since text deltas can be very large, the interface here allows us
+ * to produce and consume them in pieces. Each piece, represented by
+ * an #svn_txdelta_window_t structure, describes how to produce the
+ * next section of the target string.
+ *
+ * To compute a new text delta:
+ *
+ * - We call svn_txdelta() on the streams we want to compare. That
+ * returns us an #svn_txdelta_stream_t object.
+ *
+ * - We then call svn_txdelta_next_window() on the stream object
+ * repeatedly. Each call returns a new #svn_txdelta_window_t
+ * object, which describes the next portion of the target string.
+ * When svn_txdelta_next_window() returns zero, we are done building
+ * the target string.
+ *
+ * @defgroup svn_delta_txt_delta Text deltas
+ * @{
+ */
+
+/** Action codes for text delta instructions. */
+enum svn_delta_action {
+ /* Note: The svndiff implementation relies on the values assigned in
+ * this enumeration matching the instruction encoding values. */
+
+ /** Append the @a length bytes at @a offset in the source view to the
+ * target.
+ *
+ * It must be the case that 0 <= @a offset < @a offset +
+ * @a length <= size of source view.
+ */
+ svn_txdelta_source,
+
+ /** Append the @a length bytes at @a offset in the target view, to the
+ * target.
+ *
+ * It must be the case that 0 <= @a offset < current position in the
+ * target view.
+ *
+ * However! @a offset + @a length may be *beyond* the end of the existing
+ * target data. "Where the heck does the text come from, then?"
+ * If you start at @a offset, and append @a length bytes one at a time,
+ * it'll work out --- you're adding new bytes to the end at the
+ * same rate you're reading them from the middle. Thus, if your
+ * current target text is "abcdefgh", and you get an #svn_txdelta_target
+ * instruction whose @a offset is 6 and whose @a length is 7,
+ * the resulting string is "abcdefghghghghg". This trick is actually
+ * useful in encoding long runs of consecutive characters, long runs
+ * of CR/LF pairs, etc.
+ */
+ svn_txdelta_target,
+
+ /** Append the @a length bytes at @a offset in the window's @a new string
+ * to the target.
+ *
+ * It must be the case that 0 <= @a offset < @a offset +
+ * @a length <= length of @a new. Windows MUST use new data in ascending
+ * order with no overlap at the moment; svn_txdelta_to_svndiff()
+ * depends on this.
+ */
+ svn_txdelta_new
+};
+
+/** A single text delta instruction. */
+typedef struct svn_txdelta_op_t
+{
+ /** Action code of delta instruction */
+ enum svn_delta_action action_code;
+ /** Offset of delta, see #svn_delta_action for more details. */
+ apr_size_t offset;
+ /** Number of bytes of delta, see #svn_delta_action for more details. */
+ apr_size_t length;
+} svn_txdelta_op_t;
+
+
+/** An #svn_txdelta_window_t object describes how to reconstruct a
+ * contiguous section of the target string (the "target view") using a
+ * specified contiguous region of the source string (the "source
+ * view"). It contains a series of instructions which assemble the
+ * new target string text by pulling together substrings from:
+ *
+ * - the source view,
+ *
+ * - the previously constructed portion of the target view,
+ *
+ * - a string of new data contained within the window structure
+ *
+ * The source view must always slide forward from one window to the
+ * next; that is, neither the beginning nor the end of the source view
+ * may move to the left as we read from a window stream. This
+ * property allows us to apply deltas to non-seekable source streams
+ * without making a full copy of the source stream.
+ */
+typedef struct svn_txdelta_window_t
+{
+
+ /** The offset of the source view for this window. */
+ svn_filesize_t sview_offset;
+
+ /** The length of the source view for this window. */
+ apr_size_t sview_len;
+
+ /** The length of the target view for this window, i.e. the number of
+ * bytes which will be reconstructed by the instruction stream. */
+ apr_size_t tview_len;
+
+ /** The number of instructions in this window. */
+ int num_ops;
+
+ /** The number of svn_txdelta_source instructions in this window. If
+ * this number is 0, we don't need to read the source in order to
+ * reconstruct the target view.
+ */
+ int src_ops;
+
+ /** The instructions for this window. */
+ const svn_txdelta_op_t *ops;
+
+ /** New data, for use by any `svn_txdelta_new' instructions. */
+ const svn_string_t *new_data;
+
+} svn_txdelta_window_t;
+
+/**
+ * Return a deep copy of @a window, allocated in @a pool.
+ *
+ * @since New in 1.3.
+ */
+svn_txdelta_window_t *
+svn_txdelta_window_dup(const svn_txdelta_window_t *window,
+ apr_pool_t *pool);
+
+/**
+ * Compose two delta windows, yielding a third, allocated in @a pool.
+ *
+ * @since New in 1.4
+ *
+ */
+svn_txdelta_window_t *
+svn_txdelta_compose_windows(const svn_txdelta_window_t *window_A,
+ const svn_txdelta_window_t *window_B,
+ apr_pool_t *pool);
+
+/**
+ * Apply the instructions from @a window to a source view @a sbuf to
+ * produce a target view @a tbuf.
+ *
+ * @a sbuf is assumed to have @a window->sview_len bytes of data and
+ * @a tbuf is assumed to have room for @a tlen bytes of output. @a
+ * tlen may be more than @a window->tview_len, so return the actual
+ * number of bytes written. @a sbuf is not touched and may be NULL if
+ * @a window contains no source-copy operations. This is purely a
+ * memory operation; nothing can go wrong as long as we have a valid
+ * window.
+ *
+ * @since New in 1.4
+ *
+ */
+void
+svn_txdelta_apply_instructions(svn_txdelta_window_t *window,
+ const char *sbuf, char *tbuf,
+ apr_size_t *tlen);
+
+/** A typedef for functions that consume a series of delta windows, for
+ * use in caller-pushes interfaces. Such functions will typically
+ * apply the delta windows to produce some file, or save the windows
+ * somewhere. At the end of the delta window stream, you must call
+ * this function passing zero for the @a window argument.
+ */
+typedef svn_error_t *(*svn_txdelta_window_handler_t)(
+ svn_txdelta_window_t *window, void *baton);
+
+
+/** This function will generate delta windows that turn @a source into
+ * @a target, and pushing these windows into the @a handler window handler
+ * callback (passing @a handler_baton to each invocation).
+ *
+ * If @a checksum is not NULL, then a checksum (of kind @a checksum_kind)
+ * will be computed for the target stream, and placed into *checksum.
+ *
+ * If @a cancel_func is not NULL, then it should refer to a cancellation
+ * function (along with @a cancel_baton).
+ *
+ * Results (the checksum) will be allocated from @a result_pool, and all
+ * temporary allocations will be performed in @a scratch_pool.
+ *
+ * Note: this function replaces the combination of svn_txdelta() and
+ * svn_txdelta_send_txstream().
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_txdelta_run(svn_stream_t *source,
+ svn_stream_t *target,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ svn_checksum_kind_t checksum_kind,
+ svn_checksum_t **checksum,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** A delta stream --- this is the hat from which we pull a series of
+ * svn_txdelta_window_t objects, which, taken in order, describe the
+ * entire target string. This type is defined within libsvn_delta, and
+ * opaque outside that library.
+ */
+typedef struct svn_txdelta_stream_t svn_txdelta_stream_t;
+
+
+/** A typedef for a function that will set @a *window to the next
+ * window from a #svn_txdelta_stream_t object. If there are no more
+ * delta windows, NULL will be used. The returned window, if any,
+ * will be allocated in @a pool. @a baton is the baton specified
+ * when the stream was created.
+ *
+ * @since New in 1.4.
+ */
+typedef svn_error_t *
+(*svn_txdelta_next_window_fn_t)(svn_txdelta_window_t **window,
+ void *baton,
+ apr_pool_t *pool);
+
+/** A typedef for a function that will return the md5 checksum of the
+ * fulltext deltified by a #svn_txdelta_stream_t object. Will
+ * return NULL if the final null window hasn't yet been returned by
+ * the stream. The returned value will be allocated in the same pool
+ * as the stream. @a baton is the baton specified when the stream was
+ * created.
+ *
+ * @since New in 1.4.
+ */
+typedef const unsigned char *
+(*svn_txdelta_md5_digest_fn_t)(void *baton);
+
+/** Create and return a generic text delta stream with @a baton, @a
+ * next_window and @a md5_digest. Allocate the new stream in @a
+ * pool.
+ *
+ * @since New in 1.4.
+ */
+svn_txdelta_stream_t *
+svn_txdelta_stream_create(void *baton,
+ svn_txdelta_next_window_fn_t next_window,
+ svn_txdelta_md5_digest_fn_t md5_digest,
+ apr_pool_t *pool);
+
+/** Set @a *window to a pointer to the next window from the delta stream
+ * @a stream. When we have completely reconstructed the target string,
+ * set @a *window to zero.
+ *
+ * The window will be allocated in @a pool.
+ */
+svn_error_t *
+svn_txdelta_next_window(svn_txdelta_window_t **window,
+ svn_txdelta_stream_t *stream,
+ apr_pool_t *pool);
+
+
+/** Return the md5 digest for the complete fulltext deltified by
+ * @a stream, or @c NULL if @a stream has not yet returned its final
+ * @c NULL window. The digest is allocated in the same memory as @a
+ * STREAM.
+ */
+const unsigned char *
+svn_txdelta_md5_digest(svn_txdelta_stream_t *stream);
+
+/** Set @a *stream to a pointer to a delta stream that will turn the byte
+ * string from @a source into the byte stream from @a target.
+ *
+ * @a source and @a target are both readable generic streams. When we call
+ * svn_txdelta_next_window() on @a *stream, it will read from @a source and
+ * @a target to gather as much data as it needs. If @a calculate_checksum
+ * is set, you may call svn_txdelta_md5_digest() to get an MD5 checksum
+ * for @a target.
+ *
+ * Do any necessary allocation in a sub-pool of @a pool.
+ *
+ * @since New in 1.8.
+ */
+void
+svn_txdelta2(svn_txdelta_stream_t **stream,
+ svn_stream_t *source,
+ svn_stream_t *target,
+ svn_boolean_t calculate_checksum,
+ apr_pool_t *pool);
+
+/** Similar to svn_txdelta2 but always calculating the target checksum.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+void
+svn_txdelta(svn_txdelta_stream_t **stream,
+ svn_stream_t *source,
+ svn_stream_t *target,
+ apr_pool_t *pool);
+
+
+/**
+ * Return a writable stream which, when fed target data, will send
+ * delta windows to @a handler/@a handler_baton which transform the
+ * data in @a source to the target data. As usual, the window handler
+ * will receive a NULL window to signify the end of the window stream.
+ * The stream handler functions will read data from @a source as
+ * necessary.
+ *
+ * @since New in 1.1.
+ */
+svn_stream_t *
+svn_txdelta_target_push(svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ svn_stream_t *source,
+ apr_pool_t *pool);
+
+
+/** Send the contents of @a string to window-handler @a handler/@a baton.
+ * This is effectively a 'copy' operation, resulting in delta windows that
+ * make the target equivalent to the value of @a string.
+ *
+ * All temporary allocation is performed in @a pool.
+ */
+svn_error_t *
+svn_txdelta_send_string(const svn_string_t *string,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+/** Send the contents of @a stream to window-handler @a handler/@a baton.
+ * This is effectively a 'copy' operation, resulting in delta windows that
+ * make the target equivalent to the stream.
+ *
+ * If @a digest is non-NULL, populate it with the md5 checksum for the
+ * fulltext that was deltified (@a digest must be at least
+ * @c APR_MD5_DIGESTSIZE bytes long).
+ *
+ * All temporary allocation is performed in @a pool.
+ */
+svn_error_t *
+svn_txdelta_send_stream(svn_stream_t *stream,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ unsigned char *digest,
+ apr_pool_t *pool);
+
+/** Send the contents of @a txstream to window-handler @a handler/@a baton.
+ * Windows will be extracted from the stream and delivered to the handler.
+ *
+ * All temporary allocation is performed in @a pool.
+ */
+svn_error_t *
+svn_txdelta_send_txstream(svn_txdelta_stream_t *txstream,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+
+/** Send the @a contents of length @a len as a txdelta against an empty
+ * source directly to window-handler @a handler/@a handler_baton.
+ *
+ * All temporary allocation is performed in @a pool.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_txdelta_send_contents(const unsigned char *contents,
+ apr_size_t len,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+/** Prepare to apply a text delta. @a source is a readable generic stream
+ * yielding the source data, @a target is a writable generic stream to
+ * write target data to, and allocation takes place in a sub-pool of
+ * @a pool. On return, @a *handler is set to a window handler function and
+ * @a *handler_baton is set to the value to pass as the @a baton argument to
+ * @a *handler.
+ *
+ * If @a result_digest is non-NULL, it points to APR_MD5_DIGESTSIZE bytes
+ * of storage, and the final call to @a handler populates it with the
+ * MD5 digest of the resulting fulltext.
+ *
+ * If @a error_info is non-NULL, it is inserted parenthetically into
+ * the error string for any error returned by svn_txdelta_apply() or
+ * @a *handler. (It is normally used to provide path information,
+ * since there's nothing else in the delta application's context to
+ * supply a path for error messages.)
+ *
+ * @note To avoid lifetime issues, @a error_info is copied into
+ * @a pool or a subpool thereof.
+ */
+void
+svn_txdelta_apply(svn_stream_t *source,
+ svn_stream_t *target,
+ unsigned char *result_digest,
+ const char *error_info,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton);
+
+
+
+
+/*** Producing and consuming svndiff-format text deltas. ***/
+
+/** Prepare to produce an svndiff-format diff from text delta windows.
+ * @a output is a writable generic stream to write the svndiff data to.
+ * Allocation takes place in a sub-pool of @a pool. On return, @a *handler
+ * is set to a window handler function and @a *handler_baton is set to
+ * the value to pass as the @a baton argument to @a *handler. The svndiff
+ * version is @a svndiff_version. @a compression_level is the zlib
+ * compression level from 0 (no compression) and 9 (maximum compression).
+ *
+ * @since New in 1.7.
+ */
+void
+svn_txdelta_to_svndiff3(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ svn_stream_t *output,
+ int svndiff_version,
+ int compression_level,
+ apr_pool_t *pool);
+
+/** Similar to svn_txdelta_to_svndiff3(), but always using the SVN default
+ * compression level (#SVN_DELTA_COMPRESSION_LEVEL_DEFAULT).
+ *
+ * @since New in 1.4.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+void
+svn_txdelta_to_svndiff2(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ svn_stream_t *output,
+ int svndiff_version,
+ apr_pool_t *pool);
+
+/** Similar to svn_txdelta_to_svndiff2, but always using svndiff
+ * version 0.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+void
+svn_txdelta_to_svndiff(svn_stream_t *output,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton);
+
+/** Return a writable generic stream which will parse svndiff-format
+ * data into a text delta, invoking @a handler with @a handler_baton
+ * whenever a new window is ready. If @a error_on_early_close is @c
+ * TRUE, attempting to close this stream before it has handled the entire
+ * svndiff data set will result in #SVN_ERR_SVNDIFF_UNEXPECTED_END,
+ * else this error condition will be ignored.
+ */
+svn_stream_t *
+svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ svn_boolean_t error_on_early_close,
+ apr_pool_t *pool);
+
+/**
+ * Read and parse one delta window in svndiff format from the
+ * readable stream @a stream and place it in @a *window, allocating
+ * the result in @a pool. The caller must take responsibility for
+ * stripping off the four-byte 'SVN@<ver@>' header at the beginning of
+ * the svndiff document before reading the first window, and must
+ * provide the version number (the value of the fourth byte) to each
+ * invocation of this routine with the @a svndiff_version argument.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_txdelta_read_svndiff_window(svn_txdelta_window_t **window,
+ svn_stream_t *stream,
+ int svndiff_version,
+ apr_pool_t *pool);
+
+/**
+ * Read and skip one delta window in svndiff format from the
+ * file @a file. @a pool is used for temporary allocations. The
+ * caller must take responsibility for stripping off the four-byte
+ * 'SVN@<ver@>' header at the beginning of the svndiff document before
+ * reading or skipping the first window, and must provide the version
+ * number (the value of the fourth byte) to each invocation of this
+ * routine with the @a svndiff_version argument.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_txdelta_skip_svndiff_window(apr_file_t *file,
+ int svndiff_version,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/** Traversing tree deltas.
+ *
+ * In Subversion, we've got various producers and consumers of tree
+ * deltas.
+ *
+ * In processing a `commit' command:
+ * - The client examines its working copy data, and produces a tree
+ * delta describing the changes to be committed.
+ * - The client networking library consumes that delta, and sends them
+ * across the wire as an equivalent series of network requests (for
+ * example, to svnserve as an ra_svn protocol stream, or to an
+ * Apache httpd server as WebDAV commands)
+ * - The server receives those requests and produces a tree delta ---
+ * hopefully equivalent to the one the client produced above.
+ * - The Subversion server module consumes that delta and commits an
+ * appropriate transaction to the filesystem.
+ *
+ * In processing an `update' command, the process is reversed:
+ * - The Subversion server module talks to the filesystem and produces
+ * a tree delta describing the changes necessary to bring the
+ * client's working copy up to date.
+ * - The server consumes this delta, and assembles a reply
+ * representing the appropriate changes.
+ * - The client networking library receives that reply, and produces a
+ * tree delta --- hopefully equivalent to the one the Subversion
+ * server produced above.
+ * - The working copy library consumes that delta, and makes the
+ * appropriate changes to the working copy.
+ *
+ * The simplest approach would be to represent tree deltas using the
+ * obvious data structure. To do an update, the server would
+ * construct a delta structure, and the working copy library would
+ * apply that structure to the working copy; the network layer's job
+ * would simply be to get the structure across the net intact.
+ *
+ * However, we expect that these deltas will occasionally be too large
+ * to fit in a typical workstation's swap area. For example, in
+ * checking out a 200Mb source tree, the entire source tree is
+ * represented by a single tree delta. So it's important to handle
+ * deltas that are too large to fit in swap all at once.
+ *
+ * So instead of representing the tree delta explicitly, we define a
+ * standard way for a consumer to process each piece of a tree delta
+ * as soon as the producer creates it. The #svn_delta_editor_t
+ * structure is a set of callback functions to be defined by a delta
+ * consumer, and invoked by a delta producer. Each invocation of a
+ * callback function describes a piece of the delta --- a file's
+ * contents changing, something being renamed, etc.
+ *
+ * @defgroup svn_delta_tree_deltas Tree deltas
+ * @{
+ */
+
+/** A structure full of callback functions the delta source will invoke
+ * as it produces the delta.
+ *
+ * @note Don't try to allocate one of these yourself. Instead, always
+ * use svn_delta_default_editor() or some other constructor, to ensure
+ * that unused slots are filled in with no-op functions.
+ *
+ * <h3>Function Usage</h3>
+ *
+ * Here's how to use these functions to express a tree delta.
+ *
+ * The delta consumer implements the callback functions described in
+ * this structure, and the delta producer invokes them. So the
+ * caller (producer) is pushing tree delta data at the callee
+ * (consumer).
+ *
+ * At the start of traversal, the consumer provides @a edit_baton, a
+ * baton global to the entire delta edit. If there is a target
+ * revision that needs to be set for this operation, the producer
+ * should call the @c set_target_revision function at this point.
+ *
+ * Next, if there are any tree deltas to express, the producer should
+ * pass the @a edit_baton to the @c open_root function, to get a baton
+ * representing root of the tree being edited.
+ *
+ * Most of the callbacks work in the obvious way:
+ *
+ * @c delete_entry
+ * @c add_file
+ * @c add_directory
+ * @c open_file
+ * @c open_directory
+ *
+ * Each of these takes a directory baton, indicating the directory
+ * in which the change takes place, and a @a path argument, giving the
+ * path of the file, subdirectory, or directory entry to change.
+ *
+ * The @a path argument to each of the callbacks is relative to the
+ * root of the edit. Editors will usually want to join this relative
+ * path with some base stored in the edit baton (e.g. a URL, or a
+ * location in the OS filesystem).
+ *
+ * Since every call requires a parent directory baton, including
+ * @c add_directory and @c open_directory, where do we ever get our
+ * initial directory baton, to get things started? The @c open_root
+ * function returns a baton for the top directory of the change. In
+ * general, the producer needs to invoke the editor's @c open_root
+ * function before it can get anything of interest done.
+ *
+ * While @c open_root provides a directory baton for the root of
+ * the tree being changed, the @c add_directory and @c open_directory
+ * callbacks provide batons for other directories. Like the
+ * callbacks above, they take a @a parent_baton and a relative path
+ * @a path, and then return a new baton for the subdirectory being
+ * created / modified --- @a child_baton. The producer can then use
+ * @a child_baton to make further changes in that subdirectory.
+ *
+ * So, if we already have subdirectories named `foo' and `foo/bar',
+ * then the producer can create a new file named `foo/bar/baz.c' by
+ * calling:
+ *
+ * - @c open_root () --- yielding a baton @a root for the top directory
+ *
+ * - @c open_directory (@a root, "foo") --- yielding a baton @a f for `foo'
+ *
+ * - @c open_directory (@a f, "foo/bar") --- yielding a baton @a b for
+ * `foo/bar'
+ *
+ * - @c add_file (@a b, "foo/bar/baz.c")
+ *
+ * When the producer is finished making changes to a directory, it
+ * should call @c close_directory. This lets the consumer do any
+ * necessary cleanup, and free the baton's storage.
+ *
+ * The @c add_file and @c open_file callbacks each return a baton
+ * for the file being created or changed. This baton can then be
+ * passed to @c apply_textdelta to change the file's contents, or
+ * @c change_file_prop to change the file's properties. When the
+ * producer is finished making changes to a file, it should call
+ * @c close_file, to let the consumer clean up and free the baton.
+ *
+ * The @c add_file and @c add_directory functions each take arguments
+ * @a copyfrom_path and @a copyfrom_revision. If @a copyfrom_path is
+ * non-@c NULL, then @a copyfrom_path and @a copyfrom_revision indicate where
+ * the file or directory should be copied from (to create the file
+ * or directory being added). In that case, @a copyfrom_path must be
+ * either a path relative to the root of the edit, or a URI from the
+ * repository being edited. If @a copyfrom_path is @c NULL, then @a
+ * copyfrom_revision must be #SVN_INVALID_REVNUM; it is invalid to
+ * pass a mix of valid and invalid copyfrom arguments.
+ *
+ *
+ * <h3>Function Call Ordering</h3>
+ *
+ * There are six restrictions on the order in which the producer
+ * may use the batons:
+ *
+ * 1. The producer may call @c open_directory, @c add_directory,
+ * @c open_file, @c add_file at most once on any given directory
+ * entry. @c delete_entry may be called at most once on any given
+ * directory entry and may later be followed by @c add_directory or
+ * @c add_file on the same directory entry. @c delete_entry may
+ * not be called on any directory entry after @c open_directory,
+ * @c add_directory, @c open_file or @c add_file has been called on
+ * that directory entry.
+ *
+ * 2. The producer may not close a directory baton until it has
+ * closed all batons for its subdirectories.
+ *
+ * 3. When a producer calls @c open_directory or @c add_directory,
+ * it must specify the most recently opened of the currently open
+ * directory batons. Put another way, the producer cannot have
+ * two sibling directory batons open at the same time.
+ *
+ * 4. A producer must call @c change_dir_prop on a directory either
+ * before opening any of the directory's subdirs or after closing
+ * them, but not in the middle.
+ *
+ * 5. When the producer calls @c open_file or @c add_file, either:
+ *
+ * (a) The producer must follow with any changes to the file
+ * (@c change_file_prop and/or @c apply_textdelta, as applicable),
+ * followed by a @c close_file call, before issuing any other file
+ * or directory calls, or
+ *
+ * (b) The producer must follow with a @c change_file_prop call if
+ * it is applicable, before issuing any other file or directory
+ * calls; later, after all directory batons including the root
+ * have been closed, the producer must issue @c apply_textdelta
+ * and @c close_file calls.
+ *
+ * 6. When the producer calls @c apply_textdelta, it must make all of
+ * the window handler calls (including the @c NULL window at the
+ * end) before issuing any other #svn_delta_editor_t calls.
+ *
+ * So, the producer needs to use directory and file batons as if it
+ * is doing a single depth-first traversal of the tree, with the
+ * exception that the producer may keep file batons open in order to
+ * make @c apply_textdelta calls at the end.
+ *
+ *
+ * <h3>Pool Usage</h3>
+ *
+ * Many editor functions are invoked multiple times, in a sequence
+ * determined by the editor "driver". The driver is responsible for
+ * creating a pool for use on each iteration of the editor function,
+ * and clearing that pool between each iteration. The driver passes
+ * the appropriate pool on each function invocation.
+ *
+ * Based on the requirement of calling the editor functions in a
+ * depth-first style, it is usually customary for the driver to similarly
+ * nest the pools. However, this is only a safety feature to ensure
+ * that pools associated with deeper items are always cleared when the
+ * top-level items are also cleared. The interface does not assume, nor
+ * require, any particular organization of the pools passed to these
+ * functions. In fact, if "postfix deltas" are used for files, the file
+ * pools definitely need to live outside the scope of their parent
+ * directories' pools.
+ *
+ * Note that close_directory can be called *before* a file in that
+ * directory has been closed. That is, the directory's baton is
+ * closed before the file's baton. The implication is that
+ * @c apply_textdelta and @c close_file should not refer to a parent
+ * directory baton UNLESS the editor has taken precautions to
+ * allocate it in a pool of the appropriate lifetime (the @a dir_pool
+ * passed to @c open_directory and @c add_directory definitely does not
+ * have the proper lifetime). In general, it is recommended to simply
+ * avoid keeping a parent directory baton in a file baton.
+ *
+ *
+ * <h3>Errors</h3>
+ *
+ * At least one implementation of the editor interface is
+ * asynchronous; an error from one operation may be detected some
+ * number of operations later. As a result, an editor driver must not
+ * assume that an error from an editing function resulted from the
+ * particular operation being detected. Moreover, once an editing
+ * function (including @c close_edit) returns an error, the edit is
+ * dead; the only further operation which may be called on the editor
+ * is @c abort_edit.
+ */
+typedef struct svn_delta_editor_t
+{
+ /** Set the target revision for this edit to @a target_revision. This
+ * call, if used, should precede all other editor calls.
+ *
+ * @note This is typically used only for server->client update-type
+ * operations. It doesn't really make much sense for commit-type
+ * operations, because the revision of a commit isn't known until
+ * the commit is finalized.
+ *
+ * Any temporary allocations may be performed in @a scratch_pool.
+ */
+ svn_error_t *(*set_target_revision)(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *scratch_pool);
+
+ /** Set @a *root_baton to a baton for the top directory of the change.
+ * (This is the top of the subtree being changed, not necessarily
+ * the root of the filesystem.) As with any other directory baton, the
+ * producer should call @c close_directory on @a root_baton when done.
+ * And as with other @c open_* calls, the @a base_revision here is the
+ * current revision of the directory (before getting bumped up to the
+ * new target revision set with @c set_target_revision).
+ *
+ * Allocations for the returned @a root_baton should be performed in
+ * @a result_pool. It is also typical to (possibly) save this pool for
+ * later usage by @c close_directory.
+ */
+ svn_error_t *(*open_root)(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **root_baton);
+
+
+ /** Remove the directory entry at @a path, a child of the directory
+ * represented by @a parent_baton. If @a revision is a valid
+ * revision number, it is used as a sanity check to ensure that you
+ * are really removing the revision of @a path that you think you are.
+ *
+ * Any temporary allocations may be performed in @a scratch_pool.
+ *
+ * @note The @a revision parameter is typically used only for
+ * client->server commit-type operations, allowing the server to
+ * verify that it is deleting what the client thinks it should be
+ * deleting. It only really makes sense in the opposite direction
+ * (during server->client update-type operations) when the trees
+ * whose delta is being described are ancestrally related (that is,
+ * one tree is an ancestor of the other).
+ */
+ svn_error_t *(*delete_entry)(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *scratch_pool);
+
+
+ /** We are going to add a new subdirectory at @a path, a child of
+ * the directory represented by @a parent_baton. We will use
+ * the value this callback stores in @a *child_baton as the
+ * parent baton for further changes in the new subdirectory.
+ *
+ * If @a copyfrom_path is non-@c NULL, this add has history (i.e., is a
+ * copy), and the origin of the copy may be recorded as
+ * @a copyfrom_path under @a copyfrom_revision.
+ *
+ * Allocations for the returned @a child_baton should be performed in
+ * @a result_pool. It is also typical to (possibly) save this pool for
+ * later usage by @c close_directory.
+ */
+ svn_error_t *(*add_directory)(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *result_pool,
+ void **child_baton);
+
+ /** We are going to make changes in the subdirectory at @a path, a
+ * child of the directory represented by @a parent_baton.
+ * The callback must store a value in @a *child_baton that
+ * should be used as the parent baton for subsequent changes in this
+ * subdirectory. If a valid revnum, @a base_revision is the current
+ * revision of the subdirectory.
+ *
+ * Allocations for the returned @a child_baton should be performed in
+ * @a result_pool. It is also typical to (possibly) save this pool for
+ * later usage by @c close_directory.
+ */
+ svn_error_t *(*open_directory)(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **child_baton);
+
+ /** Change the value of a directory's property.
+ * - @a dir_baton specifies the directory whose property should change.
+ * - @a name is the name of the property to change.
+ * - @a value is the new (final) value of the property, or @c NULL if the
+ * property should be removed altogether.
+ *
+ * The callback is guaranteed to be called exactly once for each property
+ * whose value differs between the start and the end of the edit.
+ *
+ * Any temporary allocations may be performed in @a scratch_pool.
+ */
+ svn_error_t *(*change_dir_prop)(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool);
+
+ /** We are done processing a subdirectory, whose baton is @a dir_baton
+ * (set by @c add_directory or @c open_directory). We won't be using
+ * the baton any more, so whatever resources it refers to may now be
+ * freed.
+ *
+ * Any temporary allocations may be performed in @a scratch_pool.
+ */
+ svn_error_t *(*close_directory)(void *dir_baton,
+ apr_pool_t *scratch_pool);
+
+
+ /** In the directory represented by @a parent_baton, indicate that
+ * @a path is present as a subdirectory in the edit source, but
+ * cannot be conveyed to the edit consumer. Currently, this would
+ * only occur because of authorization restrictions, but may change
+ * in the future.
+ *
+ * Any temporary allocations may be performed in @a scratch_pool.
+ */
+ svn_error_t *(*absent_directory)(const char *path,
+ void *parent_baton,
+ apr_pool_t *scratch_pool);
+
+ /** We are going to add a new file at @a path, a child of the
+ * directory represented by @a parent_baton. The callback can
+ * store a baton for this new file in @a **file_baton; whatever value
+ * it stores there should be passed through to @c apply_textdelta.
+ *
+ * If @a copyfrom_path is non-@c NULL, this add has history (i.e., is a
+ * copy), and the origin of the copy may be recorded as
+ * @a copyfrom_path under @a copyfrom_revision.
+ *
+ * Allocations for the returned @a file_baton should be performed in
+ * @a result_pool. It is also typical to save this pool for later usage
+ * by @c apply_textdelta and possibly @c close_file.
+ *
+ * @note Because the editor driver could be employing the "postfix
+ * deltas" paradigm, @a result_pool could potentially be relatively
+ * long-lived. Every file baton created by the editor for a given
+ * editor drive might be resident in memory similtaneously. Editor
+ * implementations should ideally keep their file batons as
+ * conservative (memory-usage-wise) as possible, and use @a result_pool
+ * only for those batons. (Consider using a subpool of @a result_pool
+ * for scratch work, destroying the subpool before exiting this
+ * function's implementation.)
+ */
+ svn_error_t *(*add_file)(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *result_pool,
+ void **file_baton);
+
+ /** We are going to make changes to a file at @a path, a child of the
+ * directory represented by @a parent_baton.
+ *
+ * The callback can store a baton for this new file in @a **file_baton;
+ * whatever value it stores there should be passed through to
+ * @c apply_textdelta. If a valid revnum, @a base_revision is the
+ * current revision of the file.
+ *
+ * Allocations for the returned @a file_baton should be performed in
+ * @a result_pool. It is also typical to save this pool for later usage
+ * by @c apply_textdelta and possibly @c close_file.
+ *
+ * @note See note about memory usage on @a add_file, which also
+ * applies here.
+ */
+ svn_error_t *(*open_file)(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **file_baton);
+
+ /** Apply a text delta, yielding the new revision of a file.
+ *
+ * @a file_baton indicates the file we're creating or updating, and the
+ * ancestor file on which it is based; it is the baton set by some
+ * prior @c add_file or @c open_file callback.
+ *
+ * The callback should set @a *handler to a text delta window
+ * handler; we will then call @a *handler on successive text
+ * delta windows as we receive them. The callback should set
+ * @a *handler_baton to the value we should pass as the @a baton
+ * argument to @a *handler. These values should be allocated within
+ * @a result_pool.
+ *
+ * @a base_checksum is the hex MD5 digest for the base text against
+ * which the delta is being applied; it is ignored if NULL, and may
+ * be ignored even if not NULL. If it is not ignored, it must match
+ * the checksum of the base text against which svndiff data is being
+ * applied; if it does not, @c apply_textdelta or the @a *handler call
+ * which detects the mismatch will return the error
+ * SVN_ERR_CHECKSUM_MISMATCH (if there is no base text, there may
+ * still be an error if @a base_checksum is neither NULL nor the hex
+ * MD5 checksum of the empty string).
+ */
+ svn_error_t *(*apply_textdelta)(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *result_pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton);
+
+ /** Change the value of a file's property.
+ * - @a file_baton specifies the file whose property should change.
+ * - @a name is the name of the property to change.
+ * - @a value is the new (final) value of the property, or @c NULL if the
+ * property should be removed altogether.
+ *
+ * The callback is guaranteed to be called exactly once for each property
+ * whose value differs between the start and the end of the edit.
+ *
+ * Any temporary allocations may be performed in @a scratch_pool.
+ */
+ svn_error_t *(*change_file_prop)(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool);
+
+ /** We are done processing a file, whose baton is @a file_baton (set by
+ * @c add_file or @c open_file). We won't be using the baton any
+ * more, so whatever resources it refers to may now be freed.
+ *
+ * @a text_checksum is the hex MD5 digest for the fulltext that
+ * resulted from a delta application, see @c apply_textdelta. The
+ * checksum is ignored if NULL. If not null, it is compared to the
+ * checksum of the new fulltext, and the error
+ * SVN_ERR_CHECKSUM_MISMATCH is returned if they do not match. If
+ * there is no new fulltext, @a text_checksum is ignored.
+ *
+ * Any temporary allocations may be performed in @a scratch_pool.
+ */
+ svn_error_t *(*close_file)(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *scratch_pool);
+
+ /** In the directory represented by @a parent_baton, indicate that
+ * @a path is present as a file in the edit source, but cannot be
+ * cannot be conveyed to the edit consumer. Currently, this would
+ * only occur because of authorization restrictions, but may change
+ * in the future.
+ *
+ * Any temporary allocations may be performed in @a scratch_pool.
+ */
+ svn_error_t *(*absent_file)(const char *path,
+ void *parent_baton,
+ apr_pool_t *scratch_pool);
+
+ /** All delta processing is done. Call this, with the @a edit_baton for
+ * the entire edit.
+ *
+ * Any temporary allocations may be performed in @a scratch_pool.
+ */
+ svn_error_t *(*close_edit)(void *edit_baton,
+ apr_pool_t *scratch_pool);
+
+ /** The editor-driver has decided to bail out. Allow the editor to
+ * gracefully clean up things if it needs to.
+ *
+ * Any temporary allocations may be performed in @a scratch_pool.
+ */
+ svn_error_t *(*abort_edit)(void *edit_baton,
+ apr_pool_t *scratch_pool);
+
+ /* Be sure to update svn_delta_get_cancellation_editor() and
+ * svn_delta_default_editor() if you add a new callback here. */
+} svn_delta_editor_t;
+
+
+/** Return a default delta editor template, allocated in @a pool.
+ *
+ * The editor functions in the template do only the most basic
+ * baton-swapping: each editor function that produces a baton does so
+ * by copying its incoming baton into the outgoing baton reference.
+ *
+ * This editor is not intended to be useful by itself, but is meant to
+ * be the basis for a useful editor. After getting a default editor,
+ * you substitute in your own implementations for the editor functions
+ * you care about. The ones you don't care about, you don't have to
+ * implement -- you can rely on the template's implementation to
+ * safely do nothing of consequence.
+ */
+svn_delta_editor_t *
+svn_delta_default_editor(apr_pool_t *pool);
+
+/** A text-delta window handler which does nothing.
+ *
+ * Editors can return this handler from @c apply_textdelta if they don't
+ * care about text delta windows.
+ */
+svn_error_t *
+svn_delta_noop_window_handler(svn_txdelta_window_t *window,
+ void *baton);
+
+/** Set @a *editor and @a *edit_baton to a cancellation editor that
+ * wraps @a wrapped_editor and @a wrapped_baton.
+ *
+ * The @a editor will call @a cancel_func with @a cancel_baton when each of
+ * its functions is called, continuing on to call the corresponding wrapped
+ * function if @a cancel_func returns #SVN_NO_ERROR.
+ *
+ * If @a cancel_func is @c NULL, set @a *editor to @a wrapped_editor and
+ * @a *edit_baton to @a wrapped_baton.
+ */
+svn_error_t *
+svn_delta_get_cancellation_editor(svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool);
+
+/** Set @a *editor and @a *edit_baton to an depth-based filtering
+ * editor that wraps @a wrapped_editor and @a wrapped_baton.
+ *
+ * The @a editor will track the depth of this drive against the @a
+ * requested_depth, taking into account whether not the edit drive is
+ * making use of a target (via @a has_target), and forward editor
+ * calls which operate "within" the request depth range through to @a
+ * wrapped_editor.
+ *
+ * @a requested_depth must be one of the following depth values:
+ * #svn_depth_infinity, #svn_depth_empty, #svn_depth_files,
+ * #svn_depth_immediates, or #svn_depth_unknown.
+ *
+ * If filtering is deemed unnecessary (or if @a requested_depth is
+ * #svn_depth_unknown), @a *editor and @a *edit_baton will be set to @a
+ * wrapped_editor and @a wrapped_baton, respectively; otherwise,
+ * they'll be set to new objects allocated from @a pool.
+ *
+ * @note Because the svn_delta_editor_t interface's @c delete_entry()
+ * function doesn't carry node kind information, a depth-based
+ * filtering editor being asked to filter for #svn_depth_files but
+ * receiving a @c delete_entry() call on an immediate child of the
+ * editor's target is unable to know if that deletion should be
+ * allowed or filtered out -- a delete of a top-level file is okay in
+ * this case, a delete of a top-level subdirectory is not. As such,
+ * this filtering editor takes a conservative approach, and ignores
+ * top-level deletion requests when filtering for #svn_depth_files.
+ * Fortunately, most non-depth-aware (pre-1.5) Subversion editor
+ * drivers can be told to drive non-recursively (where non-recursive
+ * means essentially #svn_depth_files), which means they won't
+ * transmit out-of-scope editor commands anyway.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_delta_depth_filter_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_edit_baton,
+ svn_depth_t requested_depth,
+ svn_boolean_t has_target,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/** Path-based editor drives.
+ *
+ * @defgroup svn_delta_path_delta_drivers Path-based delta drivers
+ * @{
+ */
+
+/** Callback function type for svn_delta_path_driver().
+ *
+ * The handler of this callback is given the callback baton @a
+ * callback_baton, @a path which is a relpath relative to the
+ * root of the edit, and the @a parent_baton which represents
+ * path's parent directory as created by the editor passed to
+ * svn_delta_path_driver().
+ *
+ * If @a path represents a directory, the handler must return a @a
+ * *dir_baton for @a path, generated from the same editor (so that the
+ * driver can later close that directory).
+ *
+ * If, however, @a path represents a file, the handler should NOT
+ * return any file batons. It can close any opened or added files
+ * immediately, or delay that close until the end of the edit when
+ * svn_delta_path_driver() returns.
+ *
+ * Finally, if @a parent_baton is @c NULL, then the root of the edit
+ * is also one of the paths passed to svn_delta_path_driver(). The
+ * handler of this callback must call the editor's open_root()
+ * function and return the top-level root dir baton in @a *dir_baton.
+ */
+typedef svn_error_t *(*svn_delta_path_driver_cb_func_t)(
+ void **dir_baton,
+ void *parent_baton,
+ void *callback_baton,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Drive @a editor (with its @a edit_baton) to visit each path in @a paths.
+ * As each path is hit as part of the editor drive, use
+ * @a callback_func and @a callback_baton to allow the caller to handle
+ * the portion of the editor drive related to that path.
+ *
+ * Each path in @a paths is a (const char *) relpath, relative
+ * to the root path of the @a edit. The editor drive will be
+ * performed in the same order as @a paths. The paths should be sorted
+ * using something like svn_sort_compare_paths to ensure that a depth-first
+ * pattern is observed for directory/file baton creation. If @a sort_paths
+ * is set, the function will sort the paths for you. Some callers may need
+ * further customization of the order (ie. libsvn_delta/compat.c).
+ *
+ * Use @a scratch_pool for all necessary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_delta_path_driver2(const svn_delta_editor_t *editor,
+ void *edit_baton,
+ const apr_array_header_t *paths,
+ svn_boolean_t sort_paths,
+ svn_delta_path_driver_cb_func_t callback_func,
+ void *callback_baton,
+ apr_pool_t *scratch_pool);
+
+
+/** Similar to svn_delta_path_driver2, but takes an (unused) revision,
+ * and will sort the provided @a paths using svn_sort_compare_paths.
+ *
+ * @note In versions prior to 1.8, this function would modify the order
+ * of elements in @a paths, despite the 'const' marker on the parameter.
+ * This has been fixed in 1.8.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_delta_path_driver(const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_revnum_t revision,
+ const apr_array_header_t *paths,
+ svn_delta_path_driver_cb_func_t callback_func,
+ void *callback_baton,
+ apr_pool_t *scratch_pool);
+
+/** @} */
+
+
+/*** File revision iterator types ***/
+
+/**
+ * The callback invoked by file rev loopers, such as
+ * svn_ra_plugin_t.get_file_revs2() and svn_repos_get_file_revs2().
+ *
+ * @a baton is provided by the caller, @a path is the pathname of the file
+ * in revision @a rev and @a rev_props are the revision properties.
+ *
+ * If @a delta_handler and @a delta_baton are non-NULL, they may be set to a
+ * handler/baton which will be called with the delta between the previous
+ * revision and this one after the return of this callback. They may be
+ * left as NULL/NULL.
+ *
+ * @a result_of_merge will be @c TRUE if the revision being returned was
+ * included as the result of a merge.
+ *
+ * @a prop_diffs is an array of svn_prop_t elements indicating the property
+ * delta for this and the previous revision.
+ *
+ * @a pool may be used for temporary allocations, but you can't rely
+ * on objects allocated to live outside of this particular call and
+ * the immediately following calls to @a *delta_handler if any. (Pass
+ * in a pool via @a baton if need be.)
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_file_rev_handler_t)(
+ void *baton,
+ const char *path,
+ svn_revnum_t rev,
+ apr_hash_t *rev_props,
+ svn_boolean_t result_of_merge,
+ svn_txdelta_window_handler_t *delta_handler,
+ void **delta_baton,
+ apr_array_header_t *prop_diffs,
+ apr_pool_t *pool);
+
+/**
+ * The old file rev handler interface.
+ *
+ * @note #svn_file_rev_handler_old_t is a placeholder type for both
+ * #svn_repos_file_rev_handler_t and #svn_ra_file_rev_handler_t. It is
+ * reproduced here for dependency reasons.
+ *
+ * @deprecated This type is provided for the svn_compat_wrap_file_rev_handler()
+ * compatibility wrapper, and should not be used for new development.
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_file_rev_handler_old_t)(
+ void *baton,
+ const char *path,
+ svn_revnum_t rev,
+ apr_hash_t *rev_props,
+ svn_txdelta_window_handler_t *delta_handler,
+ void **delta_baton,
+ apr_array_header_t *prop_diffs,
+ apr_pool_t *pool);
+
+/** Return, in @a *handler2 and @a *handler2_baton a function/baton that
+ * will call @a handler/@a handler_baton, allocating the @a *handler2_baton
+ * in @a pool.
+ *
+ * @note This is used by compatibility wrappers, which exist in more than
+ * Subversion core library.
+ *
+ * @note #svn_file_rev_handler_old_t is a placeholder type for both
+ * #svn_repos_file_rev_handler_t and #svn_ra_file_rev_handler_t. It is
+ * reproduced here for dependency reasons.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_compat_wrap_file_rev_handler(svn_file_rev_handler_t *handler2,
+ void **handler2_baton,
+ svn_file_rev_handler_old_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+/** @} end group: delta_support */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DELTA_H */
diff --git a/contrib/subversion/subversion/include/svn_diff.h b/contrib/subversion/subversion/include/svn_diff.h
new file mode 100644
index 0000000..23b8970
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_diff.h
@@ -0,0 +1,1118 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_diff.h
+ * @brief Contextual diffing.
+ *
+ * This is an internalized library for performing contextual diffs
+ * between sources of data.
+ *
+ * @note This is different than Subversion's binary-diffing engine.
+ * That API lives in @c svn_delta.h -- see the "text deltas" section. A
+ * "text delta" is way of representing precise binary diffs between
+ * strings of data. The Subversion client and server send text deltas
+ * to one another during updates and commits.
+ *
+ * This API, however, is (or will be) used for performing *contextual*
+ * merges between files in the working copy. During an update or
+ * merge, 3-way file merging is needed. And 'svn diff' needs to show
+ * the differences between 2 files.
+ *
+ * The nice thing about this API is that it's very general. It
+ * operates on any source of data (a "datasource") and calculates
+ * contextual differences on "tokens" within the data. In our
+ * particular usage, the datasources are files and the tokens are
+ * lines. But the possibilities are endless.
+ */
+
+
+#ifndef SVN_DIFF_H
+#define SVN_DIFF_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_tables.h> /* for apr_array_header_t */
+
+#include "svn_types.h"
+#include "svn_io.h" /* for svn_stream_t */
+#include "svn_string.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/**
+ * Get libsvn_diff version information.
+ *
+ * @since New in 1.1.
+ */
+const svn_version_t *
+svn_diff_version(void);
+
+
+/* Diffs. */
+
+/** An opaque type that represents a difference between either two or
+ * three datasources. This object is returned by svn_diff_diff(),
+ * svn_diff_diff3() and svn_diff_diff4(), and consumed by a number of
+ * other routines.
+ */
+typedef struct svn_diff_t svn_diff_t;
+
+/**
+ * There are four types of datasources. In GNU diff3 terminology,
+ * the first three types correspond to the phrases "older", "mine",
+ * and "yours".
+ */
+typedef enum svn_diff_datasource_e
+{
+ /** The oldest form of the data. */
+ svn_diff_datasource_original,
+
+ /** The same data, but potentially changed by the user. */
+ svn_diff_datasource_modified,
+
+ /** The latest version of the data, possibly different than the
+ * user's modified version.
+ */
+ svn_diff_datasource_latest,
+
+ /** The common ancestor of original and modified. */
+ svn_diff_datasource_ancestor
+
+} svn_diff_datasource_e;
+
+
+/** A vtable for reading data from the three datasources.
+ * @since New in 1.7. */
+typedef struct svn_diff_fns2_t
+{
+ /** Open the datasources of type @a datasources. */
+ svn_error_t *(*datasources_open)(void *diff_baton,
+ apr_off_t *prefix_lines,
+ apr_off_t *suffix_lines,
+ const svn_diff_datasource_e *datasources,
+ apr_size_t datasources_len);
+
+ /** Close the datasource of type @a datasource. */
+ svn_error_t *(*datasource_close)(void *diff_baton,
+ svn_diff_datasource_e datasource);
+
+ /** Get the next "token" from the datasource of type @a datasource.
+ * Return a "token" in @a *token. Return a hash of "token" in @a *hash.
+ * Leave @a token and @a hash untouched when the datasource is exhausted.
+ */
+ svn_error_t *(*datasource_get_next_token)(apr_uint32_t *hash, void **token,
+ void *diff_baton,
+ svn_diff_datasource_e datasource);
+
+ /** A function for ordering the tokens, resembling 'strcmp' in functionality.
+ * @a compare should contain the return value of the comparison:
+ * If @a ltoken and @a rtoken are "equal", return 0. If @a ltoken is
+ * "less than" @a rtoken, return a number < 0. If @a ltoken is
+ * "greater than" @a rtoken, return a number > 0.
+ */
+ svn_error_t *(*token_compare)(void *diff_baton,
+ void *ltoken,
+ void *rtoken,
+ int *compare);
+
+ /** Free @a token from memory, the diff algorithm is done with it. */
+ void (*token_discard)(void *diff_baton,
+ void *token);
+
+ /** Free *all* tokens from memory, they're no longer needed. */
+ void (*token_discard_all)(void *diff_baton);
+} svn_diff_fns2_t;
+
+
+/** Like #svn_diff_fns2_t except with datasource_open() instead of
+ * datasources_open().
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+typedef struct svn_diff_fns_t
+{
+ svn_error_t *(*datasource_open)(void *diff_baton,
+ svn_diff_datasource_e datasource);
+
+ svn_error_t *(*datasource_close)(void *diff_baton,
+ svn_diff_datasource_e datasource);
+
+ svn_error_t *(*datasource_get_next_token)(apr_uint32_t *hash, void **token,
+ void *diff_baton,
+ svn_diff_datasource_e datasource);
+
+ svn_error_t *(*token_compare)(void *diff_baton,
+ void *ltoken,
+ void *rtoken,
+ int *compare);
+
+ void (*token_discard)(void *diff_baton,
+ void *token);
+
+ void (*token_discard_all)(void *diff_baton);
+} svn_diff_fns_t;
+
+
+/* The Main Events */
+
+/** Given a vtable of @a diff_fns/@a diff_baton for reading datasources,
+ * return a diff object in @a *diff that represents a difference between
+ * an "original" and "modified" datasource. Do all allocation in @a pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_diff_diff_2(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns2_t *diff_fns,
+ apr_pool_t *pool);
+
+/** Like svn_diff_diff_2() but using #svn_diff_fns_t instead of
+ * #svn_diff_fns2_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_diff_diff(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns_t *diff_fns,
+ apr_pool_t *pool);
+
+/** Given a vtable of @a diff_fns/@a diff_baton for reading datasources,
+ * return a diff object in @a *diff that represents a difference between
+ * three datasources: "original", "modified", and "latest". Do all
+ * allocation in @a pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_diff_diff3_2(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns2_t *diff_fns,
+ apr_pool_t *pool);
+
+/** Like svn_diff_diff3_2() but using #svn_diff_fns_t instead of
+ * #svn_diff_fns2_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_diff_diff3(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns_t *diff_fns,
+ apr_pool_t *pool);
+
+/** Given a vtable of @a diff_fns/@a diff_baton for reading datasources,
+ * return a diff object in @a *diff that represents a difference between
+ * two datasources: "original" and "latest", adjusted to become a full
+ * difference between "original", "modified" and "latest" using "ancestor".
+ * Do all allocation in @a pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_diff_diff4_2(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns2_t *diff_fns,
+ apr_pool_t *pool);
+
+/** Like svn_diff_diff4_2() but using #svn_diff_fns_t instead of
+ * #svn_diff_fns2_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_diff_diff4(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns_t *diff_fns,
+ apr_pool_t *pool);
+
+
+/* Utility functions */
+
+/** Determine if a diff object contains conflicts. If it does, return
+ * @c TRUE, else return @c FALSE.
+ */
+svn_boolean_t
+svn_diff_contains_conflicts(svn_diff_t *diff);
+
+
+/** Determine if a diff object contains actual differences between the
+ * datasources. If so, return @c TRUE, else return @c FALSE.
+ */
+svn_boolean_t
+svn_diff_contains_diffs(svn_diff_t *diff);
+
+
+
+
+/* Displaying Diffs */
+
+/** A vtable for displaying (or consuming) differences between datasources.
+ *
+ * Differences, similarities, and conflicts are described by lining up
+ * "ranges" of data.
+ *
+ * Any of the function pointers in this vtable may be NULL to ignore the
+ * corresponding kinds of output.
+ *
+ * @note These callbacks describe data ranges in units of "tokens".
+ * A "token" is whatever you've defined it to be in your datasource
+ * @c svn_diff_fns_t vtable.
+ */
+typedef struct svn_diff_output_fns_t
+{
+ /* Two-way and three-way diffs both call the first two output functions: */
+
+ /**
+ * If doing a two-way diff, then an *identical* data range was found
+ * between the "original" and "modified" datasources. Specifically,
+ * the match starts at @a original_start and goes for @a original_length
+ * tokens in the original data, and at @a modified_start for
+ * @a modified_length tokens in the modified data.
+ *
+ * If doing a three-way diff, then all three datasources have
+ * matching data ranges. The range @a latest_start, @a latest_length in
+ * the "latest" datasource is identical to the range @a original_start,
+ * @a original_length in the original data, and is also identical to
+ * the range @a modified_start, @a modified_length in the modified data.
+ */
+ svn_error_t *(*output_common)(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length);
+
+ /**
+ * If doing a two-way diff, then an *conflicting* data range was found
+ * between the "original" and "modified" datasources. Specifically,
+ * the conflict starts at @a original_start and goes for @a original_length
+ * tokens in the original data, and at @a modified_start for
+ * @a modified_length tokens in the modified data.
+ *
+ * If doing a three-way diff, then an identical data range was discovered
+ * between the "original" and "latest" datasources, but this conflicts with
+ * a range in the "modified" datasource.
+ */
+ svn_error_t *(*output_diff_modified)(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length);
+
+ /* ------ The following callbacks are used by three-way diffs only --- */
+
+ /** An identical data range was discovered between the "original" and
+ * "modified" datasources, but this conflicts with a range in the
+ * "latest" datasource.
+ */
+ svn_error_t *(*output_diff_latest)(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length);
+
+ /** An identical data range was discovered between the "modified" and
+ * "latest" datasources, but this conflicts with a range in the
+ * "original" datasource.
+ */
+ svn_error_t *(*output_diff_common)(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length);
+
+ /** All three datasources have conflicting data ranges. The range
+ * @a latest_start, @a latest_length in the "latest" datasource conflicts
+ * with the range @a original_start, @a original_length in the "original"
+ * datasource, and also conflicts with the range @a modified_start,
+ * @a modified_length in the "modified" datasource.
+ * If there are common ranges in the "modified" and "latest" datasources
+ * in this conflicting range, @a resolved_diff will contain a diff
+ * which can be used to retrieve the common and conflicting ranges.
+ */
+ svn_error_t *(*output_conflict)(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length,
+ svn_diff_t *resolved_diff);
+} svn_diff_output_fns_t;
+
+/** Style for displaying conflicts during diff3 output.
+ *
+ * @since New in 1.6.
+ */
+typedef enum svn_diff_conflict_display_style_t
+{
+ /** Display modified and latest, with conflict markers. */
+ svn_diff_conflict_display_modified_latest,
+
+ /** Like svn_diff_conflict_display_modified_latest, but with an
+ extra effort to identify common sequences between modified and
+ latest. */
+ svn_diff_conflict_display_resolved_modified_latest,
+
+ /** Display modified, original, and latest, with conflict
+ markers. */
+ svn_diff_conflict_display_modified_original_latest,
+
+ /** Just display modified, with no markers. */
+ svn_diff_conflict_display_modified,
+
+ /** Just display latest, with no markers. */
+ svn_diff_conflict_display_latest,
+
+ /** Like svn_diff_conflict_display_modified_original_latest, but
+ *only* showing conflicts. */
+ svn_diff_conflict_display_only_conflicts
+} svn_diff_conflict_display_style_t;
+
+
+/** Given a vtable of @a output_fns/@a output_baton for consuming
+ * differences, output the differences in @a diff.
+ */
+svn_error_t *
+svn_diff_output(svn_diff_t *diff,
+ void *output_baton,
+ const svn_diff_output_fns_t *output_fns);
+
+
+
+/* Diffs on files */
+
+/** To what extent whitespace should be ignored when comparing lines.
+ *
+ * @since New in 1.4.
+ */
+typedef enum svn_diff_file_ignore_space_t
+{
+ /** Ignore no whitespace. */
+ svn_diff_file_ignore_space_none,
+
+ /** Ignore changes in sequences of whitespace characters, treating each
+ * sequence of whitespace characters as a single space. */
+ svn_diff_file_ignore_space_change,
+
+ /** Ignore all whitespace characters. */
+ svn_diff_file_ignore_space_all
+} svn_diff_file_ignore_space_t;
+
+/** Options to control the behaviour of the file diff routines.
+ *
+ * @since New in 1.4.
+ *
+ * @note This structure may be extended in the future, so to preserve binary
+ * compatibility, users must not allocate structs of this type themselves.
+ * @see svn_diff_file_options_create().
+ *
+ * @note Although its name suggests otherwise, this structure is used to
+ * pass options to file as well as in-memory diff functions.
+ */
+typedef struct svn_diff_file_options_t
+{
+ /** To what extent whitespace should be ignored when comparing lines.
+ * The default is @c svn_diff_file_ignore_space_none. */
+ svn_diff_file_ignore_space_t ignore_space;
+ /** Whether to treat all end-of-line markers the same when comparing lines.
+ * The default is @c FALSE. */
+ svn_boolean_t ignore_eol_style;
+ /** Whether the "@@" lines of the unified diff output should include a prefix
+ * of the nearest preceding line that starts with a character that might be
+ * the initial character of a C language identifier. The default is
+ * @c FALSE.
+ */
+ svn_boolean_t show_c_function;
+} svn_diff_file_options_t;
+
+/** Allocate a @c svn_diff_file_options_t structure in @a pool, initializing
+ * it with default values.
+ *
+ * @since New in 1.4.
+ */
+svn_diff_file_options_t *
+svn_diff_file_options_create(apr_pool_t *pool);
+
+/**
+ * Parse @a args, an array of <tt>const char *</tt> command line switches
+ * and adjust @a options accordingly. @a options is assumed to be initialized
+ * with default values. @a pool is used for temporary allocation.
+ *
+ * @since New in 1.4.
+ *
+ * The following options are supported:
+ * - --ignore-space-change, -b
+ * - --ignore-all-space, -w
+ * - --ignore-eol-style
+ * - --show-c-function, -p @since New in 1.5.
+ * - --unified, -u (for compatibility, does nothing).
+ */
+svn_error_t *
+svn_diff_file_options_parse(svn_diff_file_options_t *options,
+ const apr_array_header_t *args,
+ apr_pool_t *pool);
+
+
+/** A convenience function to produce a diff between two files.
+ *
+ * @since New in 1.4.
+ *
+ * Return a diff object in @a *diff (allocated from @a pool) that represents
+ * the difference between an @a original file and @a modified file.
+ * (The file arguments must be full paths to the files.)
+ *
+ * Compare lines according to the relevant fields of @a options.
+ */
+svn_error_t *
+svn_diff_file_diff_2(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool);
+
+/** Similar to svn_file_diff_2(), but with @a options set to a struct with
+ * default options.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_diff_file_diff(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ apr_pool_t *pool);
+
+/** A convenience function to produce a diff between three files.
+ *
+ * @since New in 1.4.
+ *
+ * Return a diff object in @a *diff (allocated from @a pool) that represents
+ * the difference between an @a original file, @a modified file, and @a latest
+ * file.
+ *
+ * Compare lines according to the relevant fields of @a options.
+ */
+svn_error_t *
+svn_diff_file_diff3_2(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ const char *latest,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool);
+
+/** Similar to svn_diff_file_diff3_2(), but with @a options set to a struct
+ * with default options.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_diff_file_diff3(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ const char *latest,
+ apr_pool_t *pool);
+
+/** A convenience function to produce a diff between four files.
+ *
+ * @since New in 1.4.
+ *
+ * Return a diff object in @a *diff (allocated from @a pool) that represents
+ * the difference between an @a original file, @a modified file, @a latest
+ * and @a ancestor file. (The file arguments must be full paths to the files.)
+ *
+ * Compare lines according to the relevant fields of @a options.
+ */
+svn_error_t *
+svn_diff_file_diff4_2(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ const char *latest,
+ const char *ancestor,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool);
+
+/** Similar to svn_file_diff4_2(), but with @a options set to a struct with
+ * default options.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_diff_file_diff4(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ const char *latest,
+ const char *ancestor,
+ apr_pool_t *pool);
+
+/** A convenience function to produce unified diff output from the
+ * diff generated by svn_diff_file_diff().
+ *
+ * @since New in 1.5.
+ *
+ * Output a @a diff between @a original_path and @a modified_path in unified
+ * context diff format to @a output_stream. Optionally supply
+ * @a original_header and/or @a modified_header to be displayed in the header
+ * of the output. If @a original_header or @a modified_header is @c NULL, a
+ * default header will be displayed, consisting of path and last modified time.
+ * Output all headers and markers in @a header_encoding. If @a relative_to_dir
+ * is not @c NULL, the @a original_path and @a modified_path will have the
+ * @a relative_to_dir stripped from the front of the respective paths. If
+ * @a relative_to_dir is @c NULL, paths will be not be modified. If
+ * @a relative_to_dir is not @c NULL but @a relative_to_dir is not a parent
+ * path of the target, an error is returned. Finally, if @a relative_to_dir
+ * is a URL, an error will be returned.
+ */
+svn_error_t *
+svn_diff_file_output_unified3(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_path,
+ const char *modified_path,
+ const char *original_header,
+ const char *modified_header,
+ const char *header_encoding,
+ const char *relative_to_dir,
+ svn_boolean_t show_c_function,
+ apr_pool_t *pool);
+
+/** Similar to svn_diff_file_output_unified3(), but with @a relative_to_dir
+ * set to NULL and @a show_c_function to false.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_diff_file_output_unified2(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_path,
+ const char *modified_path,
+ const char *original_header,
+ const char *modified_header,
+ const char *header_encoding,
+ apr_pool_t *pool);
+
+/** Similar to svn_diff_file_output_unified2(), but with @a header_encoding
+ * set to @c APR_LOCALE_CHARSET.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_diff_file_output_unified(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_path,
+ const char *modified_path,
+ const char *original_header,
+ const char *modified_header,
+ apr_pool_t *pool);
+
+
+/** A convenience function to produce diff3 output from the
+ * diff generated by svn_diff_file_diff3().
+ *
+ * Output a @a diff between @a original_path, @a modified_path and
+ * @a latest_path in merged format to @a output_stream. Optionally supply
+ * @a conflict_modified, @a conflict_original, @a conflict_separator and/or
+ * @a conflict_latest to be displayed as conflict markers in the output.
+ * If @a conflict_original, @a conflict_modified, @a conflict_latest and/or
+ * @a conflict_separator is @c NULL, a default marker will be displayed.
+ * @a conflict_style dictates how conflicts are displayed.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_diff_file_output_merge2(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_path,
+ const char *modified_path,
+ const char *latest_path,
+ const char *conflict_original,
+ const char *conflict_modified,
+ const char *conflict_latest,
+ const char *conflict_separator,
+ svn_diff_conflict_display_style_t conflict_style,
+ apr_pool_t *pool);
+
+
+/** Similar to svn_diff_file_output_merge2, but with @a
+ * display_original_in_conflict and @a display_resolved_conflicts
+ * booleans instead of the @a conflict_style enum.
+ *
+ * If both booleans are false, acts like
+ * svn_diff_conflict_display_modified_latest; if @a
+ * display_original_in_conflict is true, acts like
+ * svn_diff_conflict_display_modified_original_latest; if @a
+ * display_resolved_conflicts is true, acts like
+ * svn_diff_conflict_display_resolved_modified_latest. The booleans
+ * may not both be true.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_diff_file_output_merge(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_path,
+ const char *modified_path,
+ const char *latest_path,
+ const char *conflict_original,
+ const char *conflict_modified,
+ const char *conflict_latest,
+ const char *conflict_separator,
+ svn_boolean_t display_original_in_conflict,
+ svn_boolean_t display_resolved_conflicts,
+ apr_pool_t *pool);
+
+
+
+/* Diffs on in-memory structures */
+
+/** Generate @a diff output from the @a original and @a modified
+ * in-memory strings. @a diff will be allocated from @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_diff_mem_string_diff(svn_diff_t **diff,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool);
+
+
+/** Generate @a diff output from the @a original, @a modified and @a latest
+ * in-memory strings. @a diff will be allocated in @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_diff_mem_string_diff3(svn_diff_t **diff,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ const svn_string_t *latest,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool);
+
+
+/** Generate @a diff output from the @a original, @a modified and @a latest
+ * in-memory strings, using @a ancestor. @a diff will be allocated in @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_diff_mem_string_diff4(svn_diff_t **diff,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ const svn_string_t *latest,
+ const svn_string_t *ancestor,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool);
+
+/** Outputs the @a diff object generated by svn_diff_mem_string_diff()
+ * in unified diff format on @a output_stream, using @a original
+ * and @a modified for the text in the output.
+ *
+ * If @a with_diff_header is TRUE, write a diff header ("---" and "+++"
+ * lines), using @a original_header and @a modified_header to fill the field
+ * after the "---" and "+++" markers; otherwise @a original_header and
+ * @a modified_header are ignored and may be NULL.
+ *
+ * Outputs the header and hunk delimiters in @a header_encoding.
+ * A @a hunk_delimiter can optionally be specified.
+ * If @a hunk_delimiter is NULL, use the default hunk delimiter "@@".
+ *
+ * As a special case, if the hunk delimiter is "##", then for an incomplete
+ * final line use the text "\ No newline at end of property" instead of
+ * "\ No newline at end of file".
+ *
+ * @since New in 1.7. Hunk delimiter "##" has the special meaning since 1.8.
+ */
+svn_error_t *
+svn_diff_mem_string_output_unified2(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ svn_boolean_t with_diff_header,
+ const char *hunk_delimiter,
+ const char *original_header,
+ const char *modified_header,
+ const char *header_encoding,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ apr_pool_t *pool);
+
+/** Similar to svn_diff_mem_string_output_unified2() but with
+ * @a with_diff_header always set to TRUE and @a hunk_delimiter always
+ * set to NULL.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_diff_mem_string_output_unified(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_header,
+ const char *modified_header,
+ const char *header_encoding,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ apr_pool_t *pool);
+
+/** Output the @a diff generated by svn_diff_mem_string_diff3() in diff3
+ * format on @a output_stream, using @a original, @a modified and @a latest
+ * for content changes.
+ *
+ * Use the conflict markers @a conflict_original, @a conflict_modified,
+ * @a conflict_latest and @a conflict_separator or the default one for
+ * each of these if @c NULL is passed.
+ *
+ * @a conflict_style dictates how conflicts are displayed.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_diff_mem_string_output_merge2(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ const svn_string_t *latest,
+ const char *conflict_original,
+ const char *conflict_modified,
+ const char *conflict_latest,
+ const char *conflict_separator,
+ svn_diff_conflict_display_style_t style,
+ apr_pool_t *pool);
+
+/** Similar to svn_diff_mem_string_output_merge2, but with @a
+ * display_original_in_conflict and @a display_resolved_conflicts
+ * booleans instead of the @a conflict_style enum.
+ *
+ * If both booleans are false, acts like
+ * svn_diff_conflict_display_modified_latest; if @a
+ * display_original_in_conflict is true, acts like
+ * svn_diff_conflict_display_modified_original_latest; if @a
+ * display_resolved_conflicts is true, acts like
+ * svn_diff_conflict_display_resolved_modified_latest. The booleans
+ * may not both be true.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_diff_mem_string_output_merge(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ const svn_string_t *latest,
+ const char *conflict_original,
+ const char *conflict_modified,
+ const char *conflict_latest,
+ const char *conflict_separator,
+ svn_boolean_t display_original_in_conflict,
+ svn_boolean_t display_resolved_conflicts,
+ apr_pool_t *pool);
+
+
+
+/* Diff parsing. If you want to apply a patch to a working copy
+ * rather than parse it, see svn_client_patch(). */
+
+/**
+ * Describes what operation has been performed on a file.
+ *
+ * @since New in 1.7.
+ */
+typedef enum svn_diff_operation_kind_e
+{
+ svn_diff_op_unchanged,
+ svn_diff_op_added,
+ svn_diff_op_deleted,
+ svn_diff_op_copied,
+ svn_diff_op_moved,
+ /* There's no tree changes, just text modifications. */
+ svn_diff_op_modified
+} svn_diff_operation_kind_t;
+
+/**
+ * A single hunk inside a patch.
+ *
+ * The lines of text comprising the hunk can be interpreted in three ways:
+ * - diff text The hunk as it appears in the unidiff patch file,
+ * including the hunk header line ("@@ ... @@")
+ * - original text The text the patch was based on.
+ * - modified text The result of patching the original text.
+ *
+ * For example, consider a hunk with the following diff text:
+ *
+ * @verbatim
+ @@ -1,5 +1,5 @@
+ #include <stdio.h>
+ int main(int argc, char *argv[]) {
+ - printf("Hello World!\n");
+ + printf("I like Subversion!\n");
+ } @endverbatim
+ *
+ * The original text of this hunk is:
+ *
+ * @verbatim
+ #include <stdio.h>
+ int main(int argc, char *argv[]) {
+ printf("Hello World!\n");
+ } @endverbatim
+ *
+ * And the modified text is:
+ *
+ * @verbatim
+ #include <stdio.h>
+ int main(int argc, char *argv[]) {
+ printf("I like Subversion!\n");
+ } @endverbatim
+ *
+ * @see svn_diff_hunk_readline_diff_text()
+ * @see svn_diff_hunk_readline_original_text()
+ * @see svn_diff_hunk_readline_modified_text()
+ *
+ * @since New in 1.7. */
+typedef struct svn_diff_hunk_t svn_diff_hunk_t;
+
+/**
+ * Allocate @a *stringbuf in @a result_pool, and read into it one line
+ * of the diff text of @a hunk. The first line returned is the hunk header.
+ * Any subsequent lines are unidiff data (starting with '+', '-', or ' ').
+ * If the @a hunk is being interpreted in reverse (i.e. the reverse
+ * parameter of svn_diff_parse_next_patch() was @c TRUE), the diff
+ * text will be returned in reversed form.
+ * The line-terminator is detected automatically and stored in @a *eol
+ * if @a eol is not NULL.
+ * If EOF is reached, set @a *eof to TRUE, and set @a *eol to NULL if the
+ * hunk does not end with a newline character and @a eol is not NULL.
+ * Temporary allocations will be performed in @a scratch_pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_diff_hunk_readline_diff_text(svn_diff_hunk_t *hunk,
+ svn_stringbuf_t **stringbuf,
+ const char **eol,
+ svn_boolean_t *eof,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Allocate @a *stringbuf in @a result_pool, and read into it one line
+ * of the original text of @a hunk.
+ * The line-terminator is detected automatically and stored in @a *eol
+ * if @a eol is not NULL.
+ * If EOF is reached, set @a *eof to TRUE, and set @a *eol to NULL if the
+ * hunk text does not end with a newline character and @a eol is not NULL.
+ * Temporary allocations will be performed in @a scratch_pool.
+ *
+ * @see svn_diff_hunk_t
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_diff_hunk_readline_original_text(svn_diff_hunk_t *hunk,
+ svn_stringbuf_t **stringbuf,
+ const char **eol,
+ svn_boolean_t *eof,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Like svn_diff_hunk_readline_original_text(), but it returns lines from
+ * the modified text of the hunk.
+ *
+ * @see svn_diff_hunk_t
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_diff_hunk_readline_modified_text(svn_diff_hunk_t *hunk,
+ svn_stringbuf_t **stringbuf,
+ const char **eol,
+ svn_boolean_t *eof,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Reset the diff text of @a hunk so it can be read again from the start.
+ * @since New in 1.7. */
+void
+svn_diff_hunk_reset_diff_text(svn_diff_hunk_t *hunk);
+
+/** Reset the original text of @a hunk so it can be read again from the start.
+ * @since New in 1.7. */
+void
+svn_diff_hunk_reset_original_text(svn_diff_hunk_t *hunk);
+
+/** Reset the modified text of @a hunk so it can be read again from the start.
+ * @since New in 1.7. */
+void
+svn_diff_hunk_reset_modified_text(svn_diff_hunk_t *hunk);
+
+/** Return the line offset of the original hunk text,
+ * as parsed from the hunk header.
+ * @since New in 1.7. */
+svn_linenum_t
+svn_diff_hunk_get_original_start(const svn_diff_hunk_t *hunk);
+
+/** Return the number of lines in the original @a hunk text,
+ * as parsed from the hunk header.
+ * @since New in 1.7. */
+svn_linenum_t
+svn_diff_hunk_get_original_length(const svn_diff_hunk_t *hunk);
+
+/** Return the line offset of the modified @a hunk text,
+ * as parsed from the hunk header.
+ * @since New in 1.7. */
+svn_linenum_t
+svn_diff_hunk_get_modified_start(const svn_diff_hunk_t *hunk);
+
+/** Return the number of lines in the modified @a hunk text,
+ * as parsed from the hunk header.
+ * @since New in 1.7. */
+svn_linenum_t
+svn_diff_hunk_get_modified_length(const svn_diff_hunk_t *hunk);
+
+/** Return the number of lines of leading context of @a hunk,
+ * i.e. the number of lines starting with ' ' before the first line
+ * that starts with a '+' or '-'.
+ * @since New in 1.7. */
+svn_linenum_t
+svn_diff_hunk_get_leading_context(const svn_diff_hunk_t *hunk);
+
+/** Return the number of lines of trailing context of @a hunk,
+ * i.e. the number of lines starting with ' ' after the last line
+ * that starts with a '+' or '-'.
+ * @since New in 1.7. */
+svn_linenum_t
+svn_diff_hunk_get_trailing_context(const svn_diff_hunk_t *hunk);
+
+/**
+ * Data type to manage parsing of properties in patches.
+ * API users should not allocate structures of this type directly.
+ *
+ * @since New in 1.7. */
+typedef struct svn_prop_patch_t {
+ const char *name;
+
+ /** Represents the operation performed on the property */
+ svn_diff_operation_kind_t operation;
+
+ /**
+ * An array containing an svn_diff_hunk_t object for each hunk parsed
+ * from the patch associated with our property name */
+ apr_array_header_t *hunks;
+} svn_prop_patch_t;
+
+/**
+ * Data type to manage parsing of patches.
+ * API users should not allocate structures of this type directly.
+ *
+ * @since New in 1.7. */
+typedef struct svn_patch_t {
+ /**
+ * The old and new file names as retrieved from the patch file.
+ * These paths are UTF-8 encoded and canonicalized, but otherwise
+ * left unchanged from how they appeared in the patch file. */
+ const char *old_filename;
+ const char *new_filename;
+
+ /**
+ * An array containing an svn_diff_hunk_t * for each hunk parsed
+ * from the patch. */
+ apr_array_header_t *hunks;
+
+ /**
+ * A hash table keyed by property names containing svn_prop_patch_t
+ * object for each property parsed from the patch. */
+ apr_hash_t *prop_patches;
+
+ /**
+ * Represents the operation performed on the file. */
+ svn_diff_operation_kind_t operation;
+
+ /**
+ * Indicates whether the patch is being interpreted in reverse. */
+ svn_boolean_t reverse;
+} svn_patch_t;
+
+/** An opaque type representing an open patch file.
+ *
+ * @since New in 1.7. */
+typedef struct svn_patch_file_t svn_patch_file_t;
+
+/** Open @a patch_file at @a local_abspath.
+ * Allocate @a patch_file in @a result_pool.
+ *
+ * @since New in 1.7. */
+svn_error_t *
+svn_diff_open_patch_file(svn_patch_file_t **patch_file,
+ const char *local_abspath,
+ apr_pool_t *result_pool);
+
+/**
+ * Return the next @a *patch in @a patch_file.
+ * If no patch can be found, set @a *patch to NULL.
+ * If @a reverse is TRUE, invert the patch while parsing it.
+ * If @a ignore_whitespace is TRUE, allow patches with no leading
+ * whitespace to be parsed.
+ * Allocate results in @a result_pool.
+ * Use @a scratch_pool for all other allocations.
+ *
+ * @since New in 1.7. */
+svn_error_t *
+svn_diff_parse_next_patch(svn_patch_t **patch,
+ svn_patch_file_t *patch_file,
+ svn_boolean_t reverse,
+ svn_boolean_t ignore_whitespace,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Dispose of @a patch_file.
+ * Use @a scratch_pool for all temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_diff_close_patch_file(svn_patch_file_t *patch_file,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DIFF_H */
diff --git a/contrib/subversion/subversion/include/svn_dirent_uri.h b/contrib/subversion/subversion/include/svn_dirent_uri.h
new file mode 100644
index 0000000..8fb449d
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_dirent_uri.h
@@ -0,0 +1,805 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_dirent_uri.h
+ * @brief A library to manipulate URIs, relative paths and directory entries.
+ *
+ * This library makes a clear distinction between several path formats:
+ *
+ * - a dirent is a path on (local) disc or a UNC path (Windows) in
+ * either relative or absolute format.
+ * Examples:
+ * "/foo/bar", "X:/temp", "//server/share", "A:/" (Windows only), ""
+ * But not:
+ * "http://server"
+ *
+ * - a uri, for our purposes, is a percent-encoded, absolute path
+ * (URI) that starts with a schema definition. In practice, these
+ * tend to look like URLs, but never carry query strings.
+ * Examples:
+ * "http://server", "file:///path/to/repos",
+ * "svn+ssh://user@host:123/My%20Stuff/file.doc"
+ * But not:
+ * "file", "dir/file", "A:/dir", "/My%20Stuff/file.doc", ""
+ *
+ * - a relative path (relpath) is an unrooted path that can be joined
+ * to any other relative path, uri or dirent. A relative path is
+ * never rooted/prefixed by a '/'.
+ * Examples:
+ * "file", "dir/file", "dir/subdir/../file", ""
+ * But not:
+ * "/file", "http://server/file"
+ *
+ * This distinction is needed because on Windows we have to handle some
+ * dirents and URIs differently. Since it's not possible to determine from
+ * the path string if it's a dirent or a URI, it's up to the API user to
+ * make this choice. See also issue #2028.
+ *
+ * All incoming and outgoing paths are non-NULL unless otherwise documented.
+ *
+ * All of these functions expect paths passed into them to be in canonical
+ * form, except:
+ *
+ * - @c svn_dirent_canonicalize()
+ * - @c svn_dirent_is_canonical()
+ * - @c svn_dirent_internal_style()
+ * - @c svn_relpath_canonicalize()
+ * - @c svn_relpath_is_canonical()
+ * - @c svn_relpath__internal_style()
+ * - @c svn_uri_canonicalize()
+ * - @c svn_uri_is_canonical()
+ *
+ * The Subversion codebase also recognizes some other classes of path:
+ *
+ * - A Subversion filesystem path (fspath) -- otherwise known as a
+ * path within a repository -- is a path relative to the root of
+ * the repository filesystem, that starts with a slash ("/"). The
+ * rules for a fspath are the same as for a relpath except for the
+ * leading '/'. A fspath never ends with '/' except when the whole
+ * path is just '/'. The fspath API is private (see
+ * private/svn_fspath.h).
+ *
+ * - A URL path (urlpath) is just the path part of a URL (the part
+ * that follows the schema, username, hostname, and port). These
+ * are also like relpaths, except that they have a leading slash
+ * (like fspaths) and are URI-encoded. The urlpath API is also
+ * private (see private/svn_fspath.h)
+ * Example:
+ * "/svn/repos/trunk/README",
+ * "/svn/repos/!svn/bc/45/file%20with%20spaces.txt"
+ *
+ * So, which path API is appropriate for your use-case?
+ *
+ * - If your path refers to a local file, directory, symlink, etc. of
+ * the sort that you can examine and operate on with other software
+ * on your computer, it's a dirent.
+ *
+ * - If your path is a full URL -- with a schema, hostname (maybe),
+ * and path portion -- it's a uri.
+ *
+ * - If your path is relative, and is somewhat ambiguous unless it's
+ * joined to some other more explicit (possible absolute) base
+ * (such as a dirent or URL), it's a relpath.
+ *
+ * - If your path is the virtual path of a versioned object inside a
+ * Subversion repository, it could be one of two different types of
+ * paths. We'd prefer to use relpaths (relative to the root
+ * directory of the virtual repository filesystem) for that stuff,
+ * but some legacy code uses fspaths. You'll need to figure out if
+ * your code expects repository paths to have a leading '/' or not.
+ * If so, they are fspaths; otherwise they are relpaths.
+ *
+ * - If your path refers only to the path part of URL -- as if
+ * someone hacked off the initial schema and hostname portion --
+ * it's a urlpath. To date, the ra_dav modules are the only ones
+ * within Subversion that make use of urlpaths, and this is because
+ * WebDAV makes heavy use of that form of path specification.
+ *
+ * When translating between local paths (dirents) and uris code should
+ * always go via the relative path format, perhaps by truncating a
+ * parent portion from a path with svn_*_skip_ancestor(), or by
+ * converting portions to basenames and then joining to existing
+ * paths.
+ *
+ * SECURITY WARNING: If a path that is received from an untrusted
+ * source -- such as from the network -- is converted to a dirent it
+ * should be tested with svn_dirent_is_under_root() before you can
+ * assume the path to be a safe local path.
+ *
+ * MEMORY ALLOCATION: A function documented as allocating the result
+ * in a pool may instead return a static string such as "." or "". If
+ * the result is equal to an input, it will duplicate the input.
+ */
+
+#ifndef SVN_DIRENT_URI_H
+#define SVN_DIRENT_URI_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** Convert @a dirent from the local style to the canonical internal style.
+ * "Local style" means native path separators and "." for the empty path.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_dirent_internal_style(const char *dirent,
+ apr_pool_t *result_pool);
+
+/** Convert @a dirent from the internal style to the local style.
+ * "Local style" means native path separators and "." for the empty path.
+ * If the input is not canonical, the output may not be canonical.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_dirent_local_style(const char *dirent,
+ apr_pool_t *result_pool);
+
+/** Convert @a relpath from the local style to the canonical internal style.
+ * "Local style" means native path separators and "." for the empty path.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_relpath__internal_style(const char *relpath,
+ apr_pool_t *result_pool);
+
+
+/** Join a base dirent (@a base) with a component (@a component).
+ *
+ * If either @a base or @a component is the empty string, then the other
+ * argument will be copied and returned. If both are the empty string then
+ * empty string is returned.
+ *
+ * If the @a component is an absolute dirent, then it is copied and returned.
+ * The platform specific rules for joining paths are used to join the components.
+ *
+ * This function is NOT appropriate for native (local) file
+ * dirents. Only for "internal" canonicalized dirents, since it uses '/'
+ * for the separator.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.6.
+ */
+char *
+svn_dirent_join(const char *base,
+ const char *component,
+ apr_pool_t *result_pool);
+
+/** Join multiple components onto a @a base dirent. The components are
+ * terminated by a @c NULL.
+ *
+ * If any component is the empty string, it will be ignored.
+ *
+ * If any component is an absolute dirent, then it resets the base and
+ * further components will be appended to it.
+ *
+ * See svn_dirent_join() for further notes about joining dirents.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.6.
+ */
+char *
+svn_dirent_join_many(apr_pool_t *result_pool,
+ const char *base,
+ ...);
+
+/** Join a base relpath (@a base) with a component (@a component).
+ * @a component need not be a single component.
+ *
+ * If either @a base or @a component is the empty path, then the other
+ * argument will be copied and returned. If both are the empty path the
+ * empty path is returned.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+char *
+svn_relpath_join(const char *base,
+ const char *component,
+ apr_pool_t *result_pool);
+
+/** Gets the name of the specified canonicalized @a dirent as it is known
+ * within its parent directory. If the @a dirent is root, return "". The
+ * returned value will not have slashes in it.
+ *
+ * Example: svn_dirent_basename("/foo/bar") -> "bar"
+ *
+ * If @a result_pool is NULL, return a pointer to the basename in @a dirent,
+ * otherwise allocate the result in @a result_pool.
+ *
+ * @note If an empty string is passed, then an empty string will be returned.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_dirent_basename(const char *dirent,
+ apr_pool_t *result_pool);
+
+/** Get the dirname of the specified canonicalized @a dirent, defined as
+ * the dirent with its basename removed.
+ *
+ * If @a dirent is root ("/", "X:/", "//server/share/") or "", it is returned
+ * unchanged.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.6.
+ */
+char *
+svn_dirent_dirname(const char *dirent,
+ apr_pool_t *result_pool);
+
+/** Divide the canonicalized @a dirent into @a *dirpath and @a *base_name.
+ *
+ * If @a dirpath or @a base_name is NULL, then don't set that one.
+ *
+ * Either @a dirpath or @a base_name may be @a dirent's own address, but they
+ * may not both be the same address, or the results are undefined.
+ *
+ * If @a dirent has two or more components, the separator between @a dirpath
+ * and @a base_name is not included in either of the new names.
+ *
+ * Examples:
+ * - <pre>"/foo/bar/baz" ==> "/foo/bar" and "baz"</pre>
+ * - <pre>"/bar" ==> "/" and "bar"</pre>
+ * - <pre>"/" ==> "/" and ""</pre>
+ * - <pre>"bar" ==> "" and "bar"</pre>
+ * - <pre>"" ==> "" and ""</pre>
+ * Windows: - <pre>"X:/" ==> "X:/" and ""</pre>
+ * - <pre>"X:/foo" ==> "X:/" and "foo"</pre>
+ * - <pre>"X:foo" ==> "X:" and "foo"</pre>
+ * Posix: - <pre>"X:foo" ==> "" and "X:foo"</pre>
+ *
+ * Allocate the results in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+void
+svn_dirent_split(const char **dirpath,
+ const char **base_name,
+ const char *dirent,
+ apr_pool_t *result_pool);
+
+/** Divide the canonicalized @a relpath into @a *dirpath and @a *base_name.
+ *
+ * If @a dirpath or @a base_name is NULL, then don't set that one.
+ *
+ * Either @a dirpath or @a base_name may be @a relpaths's own address, but
+ * they may not both be the same address, or the results are undefined.
+ *
+ * If @a relpath has two or more components, the separator between @a dirpath
+ * and @a base_name is not included in either of the new names.
+ *
+ * examples:
+ * - <pre>"foo/bar/baz" ==> "foo/bar" and "baz"</pre>
+ * - <pre>"bar" ==> "" and "bar"</pre>
+ * - <pre>"" ==> "" and ""</pre>
+ *
+ * Allocate the results in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+void
+svn_relpath_split(const char **dirpath,
+ const char **base_name,
+ const char *relpath,
+ apr_pool_t *result_pool);
+
+/** Get the basename of the specified canonicalized @a relpath. The
+ * basename is defined as the last component of the relpath. If the @a
+ * relpath has only one component then that is returned. The returned
+ * value will have no slashes in it.
+ *
+ * Example: svn_relpath_basename("/trunk/foo/bar") -> "bar"
+ *
+ * If @a result_pool is NULL, return a pointer to the basename in @a relpath,
+ * otherwise allocate the result in @a result_pool.
+ *
+ * @note If an empty string is passed, then an empty string will be returned.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_relpath_basename(const char *relpath,
+ apr_pool_t *result_pool);
+
+/** Get the dirname of the specified canonicalized @a relpath, defined as
+ * the relpath with its basename removed.
+ *
+ * If @a relpath is empty, "" is returned.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+char *
+svn_relpath_dirname(const char *relpath,
+ apr_pool_t *result_pool);
+
+
+/** Divide the canonicalized @a uri into a uri @a *dirpath and a
+ * (URI-decoded) relpath @a *base_name.
+ *
+ * If @a dirpath or @a base_name is NULL, then don't set that one.
+ *
+ * Either @a dirpath or @a base_name may be @a uri's own address, but they
+ * may not both be the same address, or the results are undefined.
+ *
+ * If @a uri has two or more components, the separator between @a dirpath
+ * and @a base_name is not included in either of the new names.
+ *
+ * Examples:
+ * - <pre>"http://server/foo/bar" ==> "http://server/foo" and "bar"</pre>
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+void
+svn_uri_split(const char **dirpath,
+ const char **base_name,
+ const char *uri,
+ apr_pool_t *result_pool);
+
+/** Get the (URI-decoded) basename of the specified canonicalized @a
+ * uri. The basename is defined as the last component of the uri. If
+ * the @a uri is root, return "". The returned value will have no
+ * slashes in it.
+ *
+ * Example: svn_uri_basename("http://server/foo/bar") -> "bar"
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_uri_basename(const char *uri,
+ apr_pool_t *result_pool);
+
+/** Get the dirname of the specified canonicalized @a uri, defined as
+ * the uri with its basename removed.
+ *
+ * If @a uri is root (e.g. "http://server"), it is returned
+ * unchanged.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+char *
+svn_uri_dirname(const char *uri,
+ apr_pool_t *result_pool);
+
+/** Return TRUE if @a dirent is considered absolute on the platform at
+ * hand. E.g. '/foo' on Posix platforms or 'X:/foo', '//server/share/foo'
+ * on Windows.
+ *
+ * @since New in 1.6.
+ */
+svn_boolean_t
+svn_dirent_is_absolute(const char *dirent);
+
+/** Return TRUE if @a dirent is considered a root directory on the platform
+ * at hand.
+ * E.g.:
+ * On Posix: '/'
+ * On Windows: '/', 'X:/', '//server/share', 'X:'
+ *
+ * Note that on Windows '/' and 'X:' are roots, but paths starting with this
+ * root are not absolute.
+ *
+ * @since New in 1.5.
+ */
+svn_boolean_t
+svn_dirent_is_root(const char *dirent,
+ apr_size_t len);
+
+/** Return TRUE if @a uri is a root URL (e.g., "http://server").
+ *
+ * @since New in 1.7
+ */
+svn_boolean_t
+svn_uri_is_root(const char *uri,
+ apr_size_t len);
+
+/** Return a new dirent like @a dirent, but transformed such that some types
+ * of dirent specification redundancies are removed.
+ *
+ * This involves:
+ * - collapsing redundant "/./" elements
+ * - removing multiple adjacent separator characters
+ * - removing trailing separator characters
+ * - converting the server name of a UNC path to lower case (on Windows)
+ * - converting a drive letter to upper case (on Windows)
+ *
+ * and possibly other semantically inoperative transformations.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_dirent_canonicalize(const char *dirent,
+ apr_pool_t *result_pool);
+
+
+/** Return a new relpath like @a relpath, but transformed such that some types
+ * of relpath specification redundancies are removed.
+ *
+ * This involves:
+ * - collapsing redundant "/./" elements
+ * - removing multiple adjacent separator characters
+ * - removing trailing separator characters
+ *
+ * and possibly other semantically inoperative transformations.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_relpath_canonicalize(const char *relpath,
+ apr_pool_t *result_pool);
+
+
+/** Return a new uri like @a uri, but transformed such that some types
+ * of uri specification redundancies are removed.
+ *
+ * This involves:
+ * - collapsing redundant "/./" elements
+ * - removing multiple adjacent separator characters
+ * - removing trailing separator characters
+ * - normalizing the escaping of the path component by unescaping
+ * characters that don't need escaping and escaping characters that do
+ * need escaping but weren't
+ * - removing the port number if it is the default port number (80 for
+ * http, 443 for https, 3690 for svn)
+ *
+ * and possibly other semantically inoperative transformations.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_uri_canonicalize(const char *uri,
+ apr_pool_t *result_pool);
+
+/** Return @c TRUE iff @a dirent is canonical.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @note The test for canonicalization is currently defined as
+ * "looks exactly the same as @c svn_dirent_canonicalize() would make
+ * it look".
+ *
+ * @see svn_dirent_canonicalize()
+ * @since New in 1.6.
+ */
+svn_boolean_t
+svn_dirent_is_canonical(const char *dirent,
+ apr_pool_t *scratch_pool);
+
+/** Return @c TRUE iff @a relpath is canonical.
+ *
+ * @see svn_relpath_canonicalize()
+ * @since New in 1.7.
+ */
+svn_boolean_t
+svn_relpath_is_canonical(const char *relpath);
+
+/** Return @c TRUE iff @a uri is canonical.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @see svn_uri_canonicalize()
+ * @since New in 1.7.
+ */
+svn_boolean_t
+svn_uri_is_canonical(const char *uri,
+ apr_pool_t *scratch_pool);
+
+/** Return the longest common dirent shared by two canonicalized dirents,
+ * @a dirent1 and @a dirent2. If there's no common ancestor, return the
+ * empty path.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.6.
+ */
+char *
+svn_dirent_get_longest_ancestor(const char *dirent1,
+ const char *dirent2,
+ apr_pool_t *result_pool);
+
+/** Return the longest common path shared by two relative paths,
+ * @a relpath1 and @a relpath2. If there's no common ancestor, return the
+ * empty path.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+char *
+svn_relpath_get_longest_ancestor(const char *relpath1,
+ const char *relpath2,
+ apr_pool_t *result_pool);
+
+/** Return the longest common path shared by two canonicalized uris,
+ * @a uri1 and @a uri2. If there's no common ancestor, return the
+ * empty path. In order for two URLs to have a common ancestor, they
+ * must (a) have the same protocol (since two URLs with the same path
+ * but different protocols may point at completely different
+ * resources), and (b) share a common ancestor in their path
+ * component, i.e. 'protocol://' is not a sufficient ancestor.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+char *
+svn_uri_get_longest_ancestor(const char *uri1,
+ const char *uri2,
+ apr_pool_t *result_pool);
+
+/** Convert @a relative canonicalized dirent to an absolute dirent and
+ * return the results in @a *pabsolute.
+ * Raise SVN_ERR_BAD_FILENAME if the absolute dirent cannot be determined.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_dirent_get_absolute(const char **pabsolute,
+ const char *relative,
+ apr_pool_t *result_pool);
+
+/** Similar to svn_dirent_skip_ancestor(), except that if @a child_dirent is
+ * the same as @a parent_dirent, it is not considered a child, so the result
+ * is @c NULL; an empty string is never returned.
+ *
+ * If @a result_pool is NULL, return a pointer into @a child_dirent, otherwise
+ * allocate the result in @a result_pool.
+ *
+ * ### TODO: Deprecate, as the semantics are trivially
+ * obtainable from *_skip_ancestor().
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_dirent_is_child(const char *parent_dirent,
+ const char *child_dirent,
+ apr_pool_t *result_pool);
+
+/** Return TRUE if @a parent_dirent is an ancestor of @a child_dirent or
+ * the dirents are equal, and FALSE otherwise.
+ *
+ * ### TODO: Deprecate, as the semantics are trivially
+ * obtainable from *_skip_ancestor().
+ *
+ * @since New in 1.6.
+ */
+svn_boolean_t
+svn_dirent_is_ancestor(const char *parent_dirent,
+ const char *child_dirent);
+
+/** Return TRUE if @a parent_uri is an ancestor of @a child_uri or
+ * the uris are equal, and FALSE otherwise.
+ */
+svn_boolean_t
+svn_uri__is_ancestor(const char *parent_uri,
+ const char *child_uri);
+
+
+/** Return the relative path part of @a child_dirent that is below
+ * @a parent_dirent, or just "" if @a parent_dirent is equal to
+ * @a child_dirent. If @a child_dirent is not below or equal to
+ * @a parent_dirent, return NULL.
+ *
+ * If one of @a parent_dirent and @a child_dirent is absolute and
+ * the other relative, return NULL.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_dirent_skip_ancestor(const char *parent_dirent,
+ const char *child_dirent);
+
+/** Return the relative path part of @a child_relpath that is below
+ * @a parent_relpath, or just "" if @a parent_relpath is equal to
+ * @a child_relpath. If @a child_relpath is not below or equal to
+ * @a parent_relpath, return NULL.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_relpath_skip_ancestor(const char *parent_relpath,
+ const char *child_relpath);
+
+/** Return the URI-decoded relative path of @a child_uri that is below
+ * @a parent_uri, or just "" if @a parent_uri is equal to @a child_uri. If
+ * @a child_uri is not below or equal to @a parent_uri, return NULL.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_uri_skip_ancestor(const char *parent_uri,
+ const char *child_uri,
+ apr_pool_t *result_pool);
+
+/** Find the common prefix of the canonicalized dirents in @a targets
+ * (an array of <tt>const char *</tt>'s), and remove redundant dirents if @a
+ * remove_redundancies is TRUE.
+ *
+ * - Set @a *pcommon to the absolute dirent of the dirent common to
+ * all of the targets. If the targets have no common prefix (e.g.
+ * "C:/file" and "D:/file" on Windows), set @a *pcommon to the empty
+ * string.
+ *
+ * - If @a pcondensed_targets is non-NULL, set @a *pcondensed_targets
+ * to an array of targets relative to @a *pcommon, and if
+ * @a remove_redundancies is TRUE, omit any dirents that are
+ * descendants of another dirent in @a targets. If *pcommon
+ * is empty, @a *pcondensed_targets will contain absolute dirents;
+ * redundancies can still be removed. If @a pcondensed_targets is NULL,
+ * leave it alone.
+ *
+ * Else if there is exactly one target, then
+ *
+ * - Set @a *pcommon to that target, and
+ *
+ * - If @a pcondensed_targets is non-NULL, set @a *pcondensed_targets
+ * to an array containing zero elements. Else if
+ * @a pcondensed_targets is NULL, leave it alone.
+ *
+ * If there are no items in @a targets, set @a *pcommon and (if
+ * applicable) @a *pcondensed_targets to @c NULL.
+ *
+ * Allocate the results in @a result_pool. Use @a scratch_pool for
+ * temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_dirent_condense_targets(const char **pcommon,
+ apr_array_header_t **pcondensed_targets,
+ const apr_array_header_t *targets,
+ svn_boolean_t remove_redundancies,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Find the common prefix of the canonicalized uris in @a targets
+ * (an array of <tt>const char *</tt>'s), and remove redundant uris if @a
+ * remove_redundancies is TRUE.
+ *
+ * - Set @a *pcommon to the common base uri of all of the targets.
+ * If the targets have no common prefix (e.g. "http://srv1/file"
+ * and "http://srv2/file"), set @a *pcommon to the empty
+ * string.
+ *
+ * - If @a pcondensed_targets is non-NULL, set @a *pcondensed_targets
+ * to an array of URI-decoded targets relative to @a *pcommon, and
+ * if @a remove_redundancies is TRUE, omit any uris that are
+ * descendants of another uri in @a targets. If *pcommon is
+ * empty, @a *pcondensed_targets will contain absolute uris;
+ * redundancies can still be removed. If @a pcondensed_targets is
+ * NULL, leave it alone.
+ *
+ * Else if there is exactly one target, then
+ *
+ * - Set @a *pcommon to that target, and
+ *
+ * - If @a pcondensed_targets is non-NULL, set @a *pcondensed_targets
+ * to an array containing zero elements. Else if
+ * @a pcondensed_targets is NULL, leave it alone.
+ *
+ * If there are no items in @a targets, set @a *pcommon and (if
+ * applicable) @a *pcondensed_targets to @c NULL.
+ *
+ * Allocate the results in @a result_pool. Use @a scratch_pool for
+ * temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_uri_condense_targets(const char **pcommon,
+ apr_array_header_t **pcondensed_targets,
+ const apr_array_header_t *targets,
+ svn_boolean_t remove_redundancies,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Join @a path onto @a base_path, checking that @a path does not attempt
+ * to traverse above @a base_path. If @a path or any ".." component within
+ * it resolves to a path above @a base_path, or if @a path is an absolute
+ * path, then set @a *under_root to @c FALSE. Otherwise, set @a *under_root
+ * to @c TRUE and, if @a result_path is not @c NULL, set @a *result_path to
+ * the resulting path.
+ *
+ * @a path need not be canonical. @a base_path must be canonical and
+ * @a *result_path will be canonical.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @note Use of this function is strongly encouraged. Do not roll your own.
+ * (http://cve.mitre.org/cgi-bin/cvename.cgi?name=2007-3846)
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_dirent_is_under_root(svn_boolean_t *under_root,
+ const char **result_path,
+ const char *base_path,
+ const char *path,
+ apr_pool_t *result_pool);
+
+/** Set @a *dirent to the path corresponding to the file:// URL @a url, using
+ * the platform-specific file:// rules.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_uri_get_dirent_from_file_url(const char **dirent,
+ const char *url,
+ apr_pool_t *result_pool);
+
+/** Set @a *url to a file:// URL, corresponding to @a dirent using the
+ * platform specific dirent and file:// rules.
+ *
+ * Allocate the result in @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_uri_get_file_url_from_dirent(const char **url,
+ const char *dirent,
+ apr_pool_t *result_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DIRENT_URI_H */
diff --git a/contrib/subversion/subversion/include/svn_dso.h b/contrib/subversion/subversion/include/svn_dso.h
new file mode 100644
index 0000000..f5cfa10
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_dso.h
@@ -0,0 +1,99 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_dso.h
+ * @brief DSO loading routines
+ */
+
+
+
+#ifndef SVN_DSO_H
+#define SVN_DSO_H
+
+#include <apr_dso.h>
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Initialize the DSO loading routines.
+ *
+ * @note This should be called prior to the creation of any pool that
+ * is passed to a function that comes from a DSO, otherwise you
+ * risk having the DSO unloaded before all pool cleanup callbacks
+ * that live in the DSO have been executed. If it is not called
+ * prior to @c svn_dso_load being used for the first time there
+ * will be a best effort attempt made to initialize the subsystem,
+ * but it will not be entirely thread safe and it risks running
+ * into the previously mentioned problems with DSO unloading and
+ * pool cleanup callbacks.
+ *
+ * Returns svn_error_t object with corresponding apr_err returned by
+ * underlying calls. In case of no error returns @c SVN_NO_ERROR.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_dso_initialize2(void);
+
+/** The same as svn_dso_initialize2(), except that if there is an error this
+ * calls abort() instead of returning the error.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.5 API.
+ *
+ * @since New in 1.4.
+ */
+SVN_DEPRECATED
+void
+svn_dso_initialize(void);
+
+
+#if APR_HAS_DSO
+
+/**
+ * Attempt to load @a libname, returning it in @a *dso.
+ *
+ * If @a libname cannot be loaded set @a *dso to NULL and return
+ * @c SVN_NO_ERROR.
+ *
+ * @note Due to pool lifetime issues DSOs are all loaded into a global
+ * pool, so you must be certain that there is a bounded number of
+ * them that will ever be loaded by the system, otherwise you will
+ * leak memory.
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_dso_load(apr_dso_handle_t **dso,
+ const char *libname);
+
+#endif /* APR_HAS_DSO */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DSO_H */
diff --git a/contrib/subversion/subversion/include/svn_error.h b/contrib/subversion/subversion/include/svn_error.h
new file mode 100644
index 0000000..3a6e4c5
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_error.h
@@ -0,0 +1,662 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_error.h
+ * @brief Common exception handling for Subversion.
+ */
+
+#ifndef SVN_ERROR_H
+#define SVN_ERROR_H
+
+#include <apr.h> /* for apr_size_t */
+#include <apr_errno.h> /* APR's error system */
+#include <apr_pools.h> /* for apr_pool_t */
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+#define APR_WANT_STDIO
+#endif
+#include <apr_want.h> /* for FILE* */
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* For the Subversion developers, this #define turns on extended "stack
+ traces" of any errors that get thrown. See the SVN_ERR() macro. */
+#ifdef SVN_DEBUG
+#define SVN_ERR__TRACING
+#endif
+
+
+/** the best kind of (@c svn_error_t *) ! */
+#define SVN_NO_ERROR 0
+
+/* The actual error codes are kept in a separate file; see comments
+ there for the reasons why. */
+#include "svn_error_codes.h"
+
+/** Put an English description of @a statcode into @a buf and return @a buf,
+ * NULL-terminated. @a statcode is either an svn error or apr error.
+ */
+char *
+svn_strerror(apr_status_t statcode,
+ char *buf,
+ apr_size_t bufsize);
+
+
+/**
+ * Return the symbolic name of an error code. If the error code
+ * is in svn_error_codes.h, return the name of the macro as a string.
+ * If the error number is not recognised, return @c NULL.
+ *
+ * An error number may not be recognised because it was defined in a future
+ * version of Subversion (e.g., a 1.9.x server may transmit a defined-in-1.9.0
+ * error number to a 1.8.x client).
+ *
+ * An error number may be recognised @em incorrectly if the @c apr_status_t
+ * value originates in another library (such as libserf) which also uses APR.
+ * (This is a theoretical concern only: the @c apr_err member of #svn_error_t
+ * should never contain a "foreign" @c apr_status_t value, and
+ * in any case Subversion and Serf use non-overlapping subsets of the
+ * @c APR_OS_START_USERERR range.)
+ *
+ * Support for error codes returned by APR itself (i.e., not in the
+ * @c APR_OS_START_USERERR range, as defined in apr_errno.h) may be implemented
+ * in the future.
+ *
+ * @note In rare cases, a single numeric code has more than one symbolic name.
+ * (For example, #SVN_ERR_WC_NOT_DIRECTORY and #SVN_ERR_WC_NOT_WORKING_COPY).
+ * In those cases, it is not guaranteed which symbolic name is returned.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_error_symbolic_name(apr_status_t statcode);
+
+
+/** If @a err has a custom error message, return that, otherwise
+ * store the generic error string associated with @a err->apr_err into
+ * @a buf (terminating with NULL) and return @a buf.
+ *
+ * @since New in 1.4.
+ *
+ * @note @a buf and @a bufsize are provided in the interface so that
+ * this function is thread-safe and yet does no allocation.
+ */
+const char *svn_err_best_message(svn_error_t *err,
+ char *buf,
+ apr_size_t bufsize);
+
+
+
+/** SVN error creation and destruction.
+ *
+ * @defgroup svn_error_error_creation_destroy Error creation and destruction
+ * @{
+ */
+
+/** Create a nested exception structure.
+ *
+ * Input: an APR or SVN custom error code,
+ * a "child" error to wrap,
+ * a specific message
+ *
+ * Returns: a new error structure (containing the old one).
+ *
+ * @note Errors are always allocated in a subpool of the global pool,
+ * since an error's lifetime is generally not related to the
+ * lifetime of any convenient pool. Errors must be freed
+ * with svn_error_clear(). The specific message should be @c NULL
+ * if there is nothing to add to the general message associated
+ * with the error code.
+ *
+ * If creating the "bottommost" error in a chain, pass @c NULL for
+ * the child argument.
+ */
+svn_error_t *
+svn_error_create(apr_status_t apr_err,
+ svn_error_t *child,
+ const char *message);
+
+/** Create an error structure with the given @a apr_err and @a child,
+ * with a printf-style error message produced by passing @a fmt, using
+ * apr_psprintf().
+ */
+svn_error_t *
+svn_error_createf(apr_status_t apr_err,
+ svn_error_t *child,
+ const char *fmt,
+ ...)
+ __attribute__ ((format(printf, 3, 4)));
+
+/** Wrap a @a status from an APR function. If @a fmt is NULL, this is
+ * equivalent to svn_error_create(status,NULL,NULL). Otherwise,
+ * the error message is constructed by formatting @a fmt and the
+ * following arguments according to apr_psprintf(), and then
+ * appending ": " and the error message corresponding to @a status.
+ * (If UTF-8 translation of the APR error message fails, the ": " and
+ * APR error are not appended to the error message.)
+ */
+svn_error_t *
+svn_error_wrap_apr(apr_status_t status,
+ const char *fmt,
+ ...)
+ __attribute__((format(printf, 2, 3)));
+
+/** A quick n' easy way to create a wrapped exception with your own
+ * message, before throwing it up the stack. (It uses all of the
+ * @a child's fields.)
+ */
+svn_error_t *
+svn_error_quick_wrap(svn_error_t *child,
+ const char *new_msg);
+
+/** Compose two errors, returning the composition as a brand new error
+ * and consuming the original errors. Either or both of @a err1 and
+ * @a err2 may be @c SVN_NO_ERROR. If both are not @c SVN_NO_ERROR,
+ * @a err2 will follow @a err1 in the chain of the returned error.
+ *
+ * Either @a err1 or @a err2 can be functions that return svn_error_t*
+ * but if both are functions they can be evaluated in either order as
+ * per the C language rules.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_error_compose_create(svn_error_t *err1,
+ svn_error_t *err2);
+
+/** Add @a new_err to the end of @a chain's chain of errors. The @a new_err
+ * chain will be copied into @a chain's pool and destroyed, so @a new_err
+ * itself becomes invalid after this function.
+ *
+ * Either @a chain or @a new_err can be functions that return svn_error_t*
+ * but if both are functions they can be evaluated in either order as
+ * per the C language rules.
+ */
+void
+svn_error_compose(svn_error_t *chain,
+ svn_error_t *new_err);
+
+/** Return the root cause of @a err by finding the last error in its
+ * chain (e.g. it or its children). @a err may be @c SVN_NO_ERROR, in
+ * which case @c SVN_NO_ERROR is returned.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_error_root_cause(svn_error_t *err);
+
+/** Return the first error in @a err's chain that has an error code @a
+ * apr_err or #SVN_NO_ERROR if there is no error with that code. The
+ * returned error should @em not be cleared as it shares memory with @a err.
+ *
+ * If @a err is #SVN_NO_ERROR, return #SVN_NO_ERROR.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_error_find_cause(svn_error_t *err, apr_status_t apr_err);
+
+/** Create a new error that is a deep copy of @a err and return it.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_error_dup(svn_error_t *err);
+
+/** Free the memory used by @a error, as well as all ancestors and
+ * descendants of @a error.
+ *
+ * Unlike other Subversion objects, errors are managed explicitly; you
+ * MUST clear an error if you are ignoring it, or you are leaking memory.
+ * For convenience, @a error may be @c NULL, in which case this function does
+ * nothing; thus, svn_error_clear(svn_foo(...)) works as an idiom to
+ * ignore errors.
+ */
+void
+svn_error_clear(svn_error_t *error);
+
+
+#if defined(SVN_ERR__TRACING)
+/** Set the error location for debug mode. */
+void
+svn_error__locate(const char *file,
+ long line);
+
+/* Wrapper macros to collect file and line information */
+#define svn_error_create \
+ (svn_error__locate(__FILE__,__LINE__), (svn_error_create))
+#define svn_error_createf \
+ (svn_error__locate(__FILE__,__LINE__), (svn_error_createf))
+#define svn_error_wrap_apr \
+ (svn_error__locate(__FILE__,__LINE__), (svn_error_wrap_apr))
+#define svn_error_quick_wrap \
+ (svn_error__locate(__FILE__,__LINE__), (svn_error_quick_wrap))
+#endif
+
+
+/**
+ * Very basic default error handler: print out error stack @a error to the
+ * stdio stream @a stream, with each error prefixed by @a prefix; quit and
+ * clear @a error iff the @a fatal flag is set. Allocations are performed
+ * in the @a error's pool.
+ *
+ * If you're not sure what prefix to pass, just pass "svn: ". That's
+ * what code that used to call svn_handle_error() and now calls
+ * svn_handle_error2() does.
+ *
+ * @since New in 1.2.
+ */
+void
+svn_handle_error2(svn_error_t *error,
+ FILE *stream,
+ svn_boolean_t fatal,
+ const char *prefix);
+
+/** Like svn_handle_error2() but with @c prefix set to "svn: "
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+void
+svn_handle_error(svn_error_t *error,
+ FILE *stream,
+ svn_boolean_t fatal);
+
+/**
+ * Very basic default warning handler: print out the error @a error to the
+ * stdio stream @a stream, prefixed by @a prefix. Allocations are
+ * performed in the error's pool.
+ *
+ * @a error may not be @c NULL.
+ *
+ * @since New in 1.2.
+ */
+void
+svn_handle_warning2(FILE *stream,
+ svn_error_t *error,
+ const char *prefix);
+
+/** Like svn_handle_warning2() but with @c prefix set to "svn: "
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+void
+svn_handle_warning(FILE *stream,
+ svn_error_t *error);
+
+
+/** A statement macro for checking error values.
+ *
+ * Evaluate @a expr. If it yields an error, return that error from the
+ * current function. Otherwise, continue.
+ *
+ * The <tt>do { ... } while (0)</tt> wrapper has no semantic effect,
+ * but it makes this macro syntactically equivalent to the expression
+ * statement it resembles. Without it, statements like
+ *
+ * @code
+ * if (a)
+ * SVN_ERR(some operation);
+ * else
+ * foo;
+ * @endcode
+ *
+ * would not mean what they appear to.
+ */
+#define SVN_ERR(expr) \
+ do { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ return svn_error_trace(svn_err__temp); \
+ } while (0)
+
+/**
+ * A macro for wrapping an error in a source-location trace message.
+ *
+ * This macro can be used when directly returning an already created
+ * error (when not using SVN_ERR, svn_error_create(), etc.) to ensure
+ * that the call stack is recorded correctly.
+ *
+ * @since New in 1.7.
+ */
+#ifdef SVN_ERR__TRACING
+svn_error_t *
+svn_error__trace(const char *file, long line, svn_error_t *err);
+
+#define svn_error_trace(expr) svn_error__trace(__FILE__, __LINE__, (expr))
+#else
+#define svn_error_trace(expr) (expr)
+#endif
+
+/**
+ * Returns an error chain that is based on @a err's error chain but
+ * does not include any error tracing placeholders. @a err is not
+ * modified, except for any allocations using its pool.
+ *
+ * The returned error chain is allocated from @a err's pool and shares
+ * its message and source filename character arrays. The returned
+ * error chain should *not* be cleared because it is not a fully
+ * fledged error chain, only clearing @a err should be done to clear
+ * the returned error chain. If @a err is cleared, then the returned
+ * error chain is unusable.
+ *
+ * @a err can be #SVN_NO_ERROR. If @a err is not #SVN_NO_ERROR, then
+ * the last link in the error chain must be a non-tracing error, i.e,
+ * a real error.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *svn_error_purge_tracing(svn_error_t *err);
+
+
+/** A statement macro, very similar to @c SVN_ERR.
+ *
+ * This macro will wrap the error with the specified text before
+ * returning the error.
+ */
+#define SVN_ERR_W(expr, wrap_msg) \
+ do { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ return svn_error_quick_wrap(svn_err__temp, wrap_msg); \
+ } while (0)
+
+
+/** A statement macro, similar to @c SVN_ERR, but returns an integer.
+ *
+ * Evaluate @a expr. If it yields an error, handle that error and
+ * return @c EXIT_FAILURE.
+ */
+#define SVN_INT_ERR(expr) \
+ do { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) { \
+ svn_handle_error2(svn_err__temp, stderr, FALSE, "svn: "); \
+ svn_error_clear(svn_err__temp); \
+ return EXIT_FAILURE; } \
+ } while (0)
+
+/** @} */
+
+
+/** Error groups
+ *
+ * @defgroup svn_error_error_groups Error groups
+ * @{
+ */
+
+/**
+ * Return TRUE if @a err is an error specifically related to locking a
+ * path in the repository, FALSE otherwise.
+ *
+ * SVN_ERR_FS_OUT_OF_DATE and SVN_ERR_FS_NOT_FOUND are in here because it's a
+ * non-fatal error that can be thrown when attempting to lock an item.
+ *
+ * @since New in 1.2.
+ */
+#define SVN_ERR_IS_LOCK_ERROR(err) \
+ (err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED || \
+ err->apr_err == SVN_ERR_FS_NOT_FOUND || \
+ err->apr_err == SVN_ERR_FS_OUT_OF_DATE || \
+ err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)
+
+/**
+ * Return TRUE if @a err is an error specifically related to unlocking
+ * a path in the repository, FALSE otherwise.
+ *
+ * @since New in 1.2.
+ */
+#define SVN_ERR_IS_UNLOCK_ERROR(err) \
+ (err->apr_err == SVN_ERR_FS_PATH_NOT_LOCKED || \
+ err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN || \
+ err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH || \
+ err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK || \
+ err->apr_err == SVN_ERR_RA_NOT_LOCKED || \
+ err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+
+/** Evaluates to @c TRUE iff @a apr_err (of type apr_status_t) is in the given
+ * @a category, which should be one of the @c SVN_ERR_*_CATEGORY_START
+ * constants.
+ *
+ * @since New in 1.7.
+ */
+#define SVN_ERROR_IN_CATEGORY(apr_err, category) \
+ ((category) == ((apr_err) / SVN_ERR_CATEGORY_SIZE) * SVN_ERR_CATEGORY_SIZE)
+
+
+/** @} */
+
+
+/** Internal malfunctions and assertions
+ *
+ * @defgroup svn_error_malfunction_assertion Malfunctions and assertions
+ * @{
+ */
+
+/** Report that an internal malfunction has occurred, and possibly terminate
+ * the program.
+ *
+ * Act as determined by the current "malfunction handler" which may have
+ * been specified by a call to svn_error_set_malfunction_handler() or else
+ * is the default handler as specified in that function's documentation. If
+ * the malfunction handler returns, then cause the function using this macro
+ * to return the error object that it generated.
+ *
+ * @note The intended use of this macro is where execution reaches a point
+ * that cannot possibly be reached unless there is a bug in the program.
+ *
+ * @since New in 1.6.
+ */
+#define SVN_ERR_MALFUNCTION() \
+ do { \
+ return svn_error_trace(svn_error__malfunction( \
+ TRUE, __FILE__, __LINE__, NULL)); \
+ } while (0)
+
+/** Similar to SVN_ERR_MALFUNCTION(), but without the option of returning
+ * an error to the calling function.
+ *
+ * If possible you should use SVN_ERR_MALFUNCTION() instead.
+ *
+ * @since New in 1.6.
+ */
+#define SVN_ERR_MALFUNCTION_NO_RETURN() \
+ do { \
+ svn_error__malfunction(FALSE, __FILE__, __LINE__, NULL); \
+ abort(); \
+ } while (1)
+
+/** Like SVN_ERR_ASSERT(), but append ERR to the returned error chain.
+ *
+ * If EXPR is false, return a malfunction error whose chain includes ERR.
+ * If EXPR is true, do nothing. (In particular, this does not clear ERR.)
+ *
+ * Types: (svn_boolean_t expr, svn_error_t *err)
+ *
+ * @since New in 1.8.
+ */
+#ifdef __clang_analyzer__
+#include <assert.h>
+/* Just ignore ERR. If the assert triggers, it'll be our least concern. */
+#define SVN_ERR_ASSERT_E(expr, err) assert((expr))
+#else
+#define SVN_ERR_ASSERT_E(expr, err) \
+ do { \
+ if (!(expr)) { \
+ return svn_error_compose_create( \
+ svn_error__malfunction(TRUE, __FILE__, __LINE__, #expr), \
+ (err)); \
+ } \
+ } while (0)
+#endif
+
+
+/** Check that a condition is true: if not, report an error and possibly
+ * terminate the program.
+ *
+ * If the Boolean expression @a expr is true, do nothing. Otherwise,
+ * act as determined by the current "malfunction handler" which may have
+ * been specified by a call to svn_error_set_malfunction_handler() or else
+ * is the default handler as specified in that function's documentation. If
+ * the malfunction handler returns, then cause the function using this macro
+ * to return the error object that it generated.
+ *
+ * @note The intended use of this macro is to check a condition that cannot
+ * possibly be false unless there is a bug in the program.
+ *
+ * @note The condition to be checked should not be computationally expensive
+ * if it is reached often, as, unlike traditional "assert" statements, the
+ * evaluation of this expression is not compiled out in release-mode builds.
+ *
+ * @since New in 1.6.
+ *
+ * @see SVN_ERR_ASSERT_E()
+ */
+#ifdef __clang_analyzer__
+#include <assert.h>
+#define SVN_ERR_ASSERT(expr) assert((expr))
+#else
+#define SVN_ERR_ASSERT(expr) \
+ do { \
+ if (!(expr)) \
+ SVN_ERR(svn_error__malfunction(TRUE, __FILE__, __LINE__, #expr)); \
+ } while (0)
+#endif
+
+/** Similar to SVN_ERR_ASSERT(), but without the option of returning
+ * an error to the calling function.
+ *
+ * If possible you should use SVN_ERR_ASSERT() instead.
+ *
+ * @since New in 1.6.
+ */
+#define SVN_ERR_ASSERT_NO_RETURN(expr) \
+ do { \
+ if (!(expr)) { \
+ svn_error__malfunction(FALSE, __FILE__, __LINE__, #expr); \
+ abort(); \
+ } \
+ } while (0)
+
+/** Report a "Not implemented" malfunction. Internal use only. */
+#define SVN__NOT_IMPLEMENTED() \
+ return svn_error__malfunction(TRUE, __FILE__, __LINE__, "Not implemented.")
+
+/** A helper function for the macros that report malfunctions. Handle a
+ * malfunction by calling the current "malfunction handler" which may have
+ * been specified by a call to svn_error_set_malfunction_handler() or else
+ * is the default handler as specified in that function's documentation.
+ *
+ * Pass all of the parameters to the handler. The error occurred in the
+ * source file @a file at line @a line, and was an assertion failure of the
+ * expression @a expr, or, if @a expr is null, an unconditional error.
+ *
+ * If @a can_return is true, the handler can return an error object
+ * that is returned by the caller. If @a can_return is false the
+ * method should never return. (The caller will call abort())
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_error__malfunction(svn_boolean_t can_return,
+ const char *file,
+ int line,
+ const char *expr);
+
+/** A type of function that handles an assertion failure or other internal
+ * malfunction detected within the Subversion libraries.
+ *
+ * The error occurred in the source file @a file at line @a line, and was an
+ * assertion failure of the expression @a expr, or, if @a expr is null, an
+ * unconditional error.
+ *
+ * If @a can_return is false a function of this type must never return.
+ *
+ * If @a can_return is true a function of this type must do one of:
+ * - Return an error object describing the error, using an error code in
+ * the category SVN_ERR_MALFUNC_CATEGORY_START.
+ * - Never return.
+ *
+ * The function may alter its behaviour according to compile-time
+ * and run-time and even interactive conditions.
+ *
+ * @see SVN_ERROR_IN_CATEGORY()
+ *
+ * @since New in 1.6.
+ */
+typedef svn_error_t *(*svn_error_malfunction_handler_t)
+ (svn_boolean_t can_return, const char *file, int line, const char *expr);
+
+/** Cause subsequent malfunctions to be handled by @a func.
+ * Return the handler that was previously in effect.
+ *
+ * @a func may not be null.
+ *
+ * @note The default handler is svn_error_abort_on_malfunction().
+ *
+ * @note This function must be called in a single-threaded context.
+ *
+ * @since New in 1.6.
+ */
+svn_error_malfunction_handler_t
+svn_error_set_malfunction_handler(svn_error_malfunction_handler_t func);
+
+/** Handle a malfunction by returning an error object that describes it.
+ *
+ * When @a can_return is false, abort()
+ *
+ * This function implements @c svn_error_malfunction_handler_t.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_error_raise_on_malfunction(svn_boolean_t can_return,
+ const char *file,
+ int line,
+ const char *expr);
+
+/** Handle a malfunction by printing a message to stderr and aborting.
+ *
+ * This function implements @c svn_error_malfunction_handler_t.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_error_abort_on_malfunction(svn_boolean_t can_return,
+ const char *file,
+ int line,
+ const char *expr);
+
+/** @} */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_ERROR_H */
diff --git a/contrib/subversion/subversion/include/svn_error_codes.h b/contrib/subversion/subversion/include/svn_error_codes.h
new file mode 100644
index 0000000..222bc2b
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_error_codes.h
@@ -0,0 +1,1521 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_error_codes.h
+ * @brief Subversion error codes.
+ */
+
+/* What's going on here?
+
+ In order to define error codes and their associated description
+ strings in the same place, we overload the SVN_ERRDEF() macro with
+ two definitions below. Both take two arguments, an error code name
+ and a description string. One definition of the macro just throws
+ away the string and defines enumeration constants using the error
+ code names -- that definition is used by the header file that
+ exports error codes to the rest of Subversion. The other
+ definition creates a static table mapping the enum codes to their
+ corresponding strings -- that definition is used by the C file that
+ implements svn_strerror().
+
+ The header and C files both include this file, using #defines to
+ control which version of the macro they get.
+*/
+
+
+/* Process this file if we're building an error array, or if we have
+ not defined the enumerated constants yet. */
+#if defined(SVN_ERROR_BUILD_ARRAY) || !defined(SVN_ERROR_ENUM_DEFINED)
+
+/* Note: despite lacking double underscores in its name, the macro
+ SVN_ERROR_BUILD_ARRAY is an implementation detail of Subversion and not
+ a public API. */
+
+
+#include <apr_errno.h> /* APR's error system */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+
+#if defined(SVN_ERROR_BUILD_ARRAY)
+
+#define SVN_ERROR_START \
+ static const err_defn error_table[] = { \
+ { SVN_WARNING, "SVN_WARNING", "Warning" },
+#define SVN_ERRDEF(num, offset, str) { num, #num, str },
+#define SVN_ERROR_END { 0, NULL, NULL } };
+
+#elif !defined(SVN_ERROR_ENUM_DEFINED)
+
+#define SVN_ERROR_START \
+ typedef enum svn_errno_t { \
+ SVN_WARNING = APR_OS_START_USERERR + 1,
+#define SVN_ERRDEF(num, offset, str) /** str */ num = offset,
+#define SVN_ERROR_END SVN_ERR_LAST } svn_errno_t;
+
+#define SVN_ERROR_ENUM_DEFINED
+
+#endif
+
+/* Define custom Subversion error numbers, in the range reserved for
+ that in APR: from APR_OS_START_USERERR to APR_OS_START_SYSERR (see
+ apr_errno.h).
+
+ Error numbers are divided into categories of up to 5000 errors
+ each. Since we're dividing up the APR user error space, which has
+ room for 500,000 errors, we can have up to 100 categories.
+ Categories are fixed-size; if a category has fewer than 5000
+ errors, then it just ends with a range of unused numbers.
+
+ To maintain binary compatibility, please observe these guidelines:
+
+ - When adding a new error, always add on the end of the
+ appropriate category, so that the real values of existing
+ errors are not changed.
+
+ - When deleting an error, leave a placeholder comment indicating
+ the offset, again so that the values of other errors are not
+ perturbed.
+*/
+
+#define SVN_ERR_CATEGORY_SIZE 5000
+
+/* Leave one category of room at the beginning, for SVN_WARNING and
+ any other such beasts we might create in the future. */
+#define SVN_ERR_BAD_CATEGORY_START (APR_OS_START_USERERR \
+ + ( 1 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_XML_CATEGORY_START (APR_OS_START_USERERR \
+ + ( 2 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_IO_CATEGORY_START (APR_OS_START_USERERR \
+ + ( 3 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_STREAM_CATEGORY_START (APR_OS_START_USERERR \
+ + ( 4 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_NODE_CATEGORY_START (APR_OS_START_USERERR \
+ + ( 5 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_ENTRY_CATEGORY_START (APR_OS_START_USERERR \
+ + ( 6 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_WC_CATEGORY_START (APR_OS_START_USERERR \
+ + ( 7 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_FS_CATEGORY_START (APR_OS_START_USERERR \
+ + ( 8 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_REPOS_CATEGORY_START (APR_OS_START_USERERR \
+ + ( 9 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_RA_CATEGORY_START (APR_OS_START_USERERR \
+ + (10 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_RA_DAV_CATEGORY_START (APR_OS_START_USERERR \
+ + (11 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_RA_LOCAL_CATEGORY_START (APR_OS_START_USERERR \
+ + (12 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_SVNDIFF_CATEGORY_START (APR_OS_START_USERERR \
+ + (13 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_APMOD_CATEGORY_START (APR_OS_START_USERERR \
+ + (14 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_CLIENT_CATEGORY_START (APR_OS_START_USERERR \
+ + (15 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_MISC_CATEGORY_START (APR_OS_START_USERERR \
+ + (16 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_CL_CATEGORY_START (APR_OS_START_USERERR \
+ + (17 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_RA_SVN_CATEGORY_START (APR_OS_START_USERERR \
+ + (18 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_AUTHN_CATEGORY_START (APR_OS_START_USERERR \
+ + (19 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_AUTHZ_CATEGORY_START (APR_OS_START_USERERR \
+ + (20 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_DIFF_CATEGORY_START (APR_OS_START_USERERR \
+ + (21 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_RA_SERF_CATEGORY_START (APR_OS_START_USERERR \
+ + (22 * SVN_ERR_CATEGORY_SIZE))
+#define SVN_ERR_MALFUNC_CATEGORY_START (APR_OS_START_USERERR \
+ + (23 * SVN_ERR_CATEGORY_SIZE))
+
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+/** Collection of Subversion error code values, located within the
+ * APR user error space. */
+SVN_ERROR_START
+
+ /* validation ("BAD_FOO") errors */
+
+ SVN_ERRDEF(SVN_ERR_BAD_CONTAINING_POOL,
+ SVN_ERR_BAD_CATEGORY_START + 0,
+ "Bad parent pool passed to svn_make_pool()")
+
+ SVN_ERRDEF(SVN_ERR_BAD_FILENAME,
+ SVN_ERR_BAD_CATEGORY_START + 1,
+ "Bogus filename")
+
+ SVN_ERRDEF(SVN_ERR_BAD_URL,
+ SVN_ERR_BAD_CATEGORY_START + 2,
+ "Bogus URL")
+
+ SVN_ERRDEF(SVN_ERR_BAD_DATE,
+ SVN_ERR_BAD_CATEGORY_START + 3,
+ "Bogus date")
+
+ SVN_ERRDEF(SVN_ERR_BAD_MIME_TYPE,
+ SVN_ERR_BAD_CATEGORY_START + 4,
+ "Bogus mime-type")
+
+ /** @since New in 1.5.
+ *
+ * Note that there was an unused slot sitting here at
+ * SVN_ERR_BAD_CATEGORY_START + 5, so error codes after this aren't
+ * necessarily "New in 1.5" just because they come later.
+ */
+ SVN_ERRDEF(SVN_ERR_BAD_PROPERTY_VALUE,
+ SVN_ERR_BAD_CATEGORY_START + 5,
+ "Wrong or unexpected property value")
+
+ SVN_ERRDEF(SVN_ERR_BAD_VERSION_FILE_FORMAT,
+ SVN_ERR_BAD_CATEGORY_START + 6,
+ "Version file format not correct")
+
+ SVN_ERRDEF(SVN_ERR_BAD_RELATIVE_PATH,
+ SVN_ERR_BAD_CATEGORY_START + 7,
+ "Path is not an immediate child of the specified directory")
+
+ SVN_ERRDEF(SVN_ERR_BAD_UUID,
+ SVN_ERR_BAD_CATEGORY_START + 8,
+ "Bogus UUID")
+
+ /** @since New in 1.6. */
+ SVN_ERRDEF(SVN_ERR_BAD_CONFIG_VALUE,
+ SVN_ERR_BAD_CATEGORY_START + 9,
+ "Invalid configuration value")
+
+ SVN_ERRDEF(SVN_ERR_BAD_SERVER_SPECIFICATION,
+ SVN_ERR_BAD_CATEGORY_START + 10,
+ "Bogus server specification")
+
+ SVN_ERRDEF(SVN_ERR_BAD_CHECKSUM_KIND,
+ SVN_ERR_BAD_CATEGORY_START + 11,
+ "Unsupported checksum type")
+
+ SVN_ERRDEF(SVN_ERR_BAD_CHECKSUM_PARSE,
+ SVN_ERR_BAD_CATEGORY_START + 12,
+ "Invalid character in hex checksum")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_BAD_TOKEN,
+ SVN_ERR_BAD_CATEGORY_START + 13,
+ "Unknown string value of token")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_BAD_CHANGELIST_NAME,
+ SVN_ERR_BAD_CATEGORY_START + 14,
+ "Invalid changelist name")
+
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_BAD_ATOMIC,
+ SVN_ERR_BAD_CATEGORY_START + 15,
+ "Invalid atomic")
+
+ /* xml errors */
+
+ SVN_ERRDEF(SVN_ERR_XML_ATTRIB_NOT_FOUND,
+ SVN_ERR_XML_CATEGORY_START + 0,
+ "No such XML tag attribute")
+
+ SVN_ERRDEF(SVN_ERR_XML_MISSING_ANCESTRY,
+ SVN_ERR_XML_CATEGORY_START + 1,
+ "<delta-pkg> is missing ancestry")
+
+ SVN_ERRDEF(SVN_ERR_XML_UNKNOWN_ENCODING,
+ SVN_ERR_XML_CATEGORY_START + 2,
+ "Unrecognized binary data encoding; can't decode")
+
+ SVN_ERRDEF(SVN_ERR_XML_MALFORMED,
+ SVN_ERR_XML_CATEGORY_START + 3,
+ "XML data was not well-formed")
+
+ SVN_ERRDEF(SVN_ERR_XML_UNESCAPABLE_DATA,
+ SVN_ERR_XML_CATEGORY_START + 4,
+ "Data cannot be safely XML-escaped")
+
+ /* io errors */
+
+ SVN_ERRDEF(SVN_ERR_IO_INCONSISTENT_EOL,
+ SVN_ERR_IO_CATEGORY_START + 0,
+ "Inconsistent line ending style")
+
+ SVN_ERRDEF(SVN_ERR_IO_UNKNOWN_EOL,
+ SVN_ERR_IO_CATEGORY_START + 1,
+ "Unrecognized line ending style")
+
+ /** @deprecated Unused, slated for removal in the next major release. */
+ SVN_ERRDEF(SVN_ERR_IO_CORRUPT_EOL,
+ SVN_ERR_IO_CATEGORY_START + 2,
+ "Line endings other than expected")
+
+ SVN_ERRDEF(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
+ SVN_ERR_IO_CATEGORY_START + 3,
+ "Ran out of unique names")
+
+ /** @deprecated Unused, slated for removal in the next major release. */
+ SVN_ERRDEF(SVN_ERR_IO_PIPE_FRAME_ERROR,
+ SVN_ERR_IO_CATEGORY_START + 4,
+ "Framing error in pipe protocol")
+
+ /** @deprecated Unused, slated for removal in the next major release. */
+ SVN_ERRDEF(SVN_ERR_IO_PIPE_READ_ERROR,
+ SVN_ERR_IO_CATEGORY_START + 5,
+ "Read error in pipe")
+
+ SVN_ERRDEF(SVN_ERR_IO_WRITE_ERROR,
+ SVN_ERR_IO_CATEGORY_START + 6,
+ "Write error")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_IO_PIPE_WRITE_ERROR,
+ SVN_ERR_IO_CATEGORY_START + 7,
+ "Write error in pipe")
+
+ /* stream errors */
+
+ SVN_ERRDEF(SVN_ERR_STREAM_UNEXPECTED_EOF,
+ SVN_ERR_STREAM_CATEGORY_START + 0,
+ "Unexpected EOF on stream")
+
+ SVN_ERRDEF(SVN_ERR_STREAM_MALFORMED_DATA,
+ SVN_ERR_STREAM_CATEGORY_START + 1,
+ "Malformed stream data")
+
+ SVN_ERRDEF(SVN_ERR_STREAM_UNRECOGNIZED_DATA,
+ SVN_ERR_STREAM_CATEGORY_START + 2,
+ "Unrecognized stream data")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED,
+ SVN_ERR_STREAM_CATEGORY_START + 3,
+ "Stream doesn't support seeking")
+
+ /* node errors */
+
+ SVN_ERRDEF(SVN_ERR_NODE_UNKNOWN_KIND,
+ SVN_ERR_NODE_CATEGORY_START + 0,
+ "Unknown svn_node_kind")
+
+ SVN_ERRDEF(SVN_ERR_NODE_UNEXPECTED_KIND,
+ SVN_ERR_NODE_CATEGORY_START + 1,
+ "Unexpected node kind found")
+
+ /* entry errors */
+
+ SVN_ERRDEF(SVN_ERR_ENTRY_NOT_FOUND,
+ SVN_ERR_ENTRY_CATEGORY_START + 0,
+ "Can't find an entry")
+
+ /* UNUSED error slot: + 1 */
+
+ SVN_ERRDEF(SVN_ERR_ENTRY_EXISTS,
+ SVN_ERR_ENTRY_CATEGORY_START + 2,
+ "Entry already exists")
+
+ SVN_ERRDEF(SVN_ERR_ENTRY_MISSING_REVISION,
+ SVN_ERR_ENTRY_CATEGORY_START + 3,
+ "Entry has no revision")
+
+ SVN_ERRDEF(SVN_ERR_ENTRY_MISSING_URL,
+ SVN_ERR_ENTRY_CATEGORY_START + 4,
+ "Entry has no URL")
+
+ SVN_ERRDEF(SVN_ERR_ENTRY_ATTRIBUTE_INVALID,
+ SVN_ERR_ENTRY_CATEGORY_START + 5,
+ "Entry has an invalid attribute")
+
+ SVN_ERRDEF(SVN_ERR_ENTRY_FORBIDDEN,
+ SVN_ERR_ENTRY_CATEGORY_START + 6,
+ "Can't create an entry for a forbidden name")
+
+ /* wc errors */
+
+ SVN_ERRDEF(SVN_ERR_WC_OBSTRUCTED_UPDATE,
+ SVN_ERR_WC_CATEGORY_START + 0,
+ "Obstructed update")
+
+ /** @deprecated Unused, slated for removal in the next major release. */
+ SVN_ERRDEF(SVN_ERR_WC_UNWIND_MISMATCH,
+ SVN_ERR_WC_CATEGORY_START + 1,
+ "Mismatch popping the WC unwind stack")
+
+ /** @deprecated Unused, slated for removal in the next major release. */
+ SVN_ERRDEF(SVN_ERR_WC_UNWIND_EMPTY,
+ SVN_ERR_WC_CATEGORY_START + 2,
+ "Attempt to pop empty WC unwind stack")
+
+ /** @deprecated Unused, slated for removal in the next major release. */
+ SVN_ERRDEF(SVN_ERR_WC_UNWIND_NOT_EMPTY,
+ SVN_ERR_WC_CATEGORY_START + 3,
+ "Attempt to unlock with non-empty unwind stack")
+
+ SVN_ERRDEF(SVN_ERR_WC_LOCKED,
+ SVN_ERR_WC_CATEGORY_START + 4,
+ "Attempted to lock an already-locked dir")
+
+ SVN_ERRDEF(SVN_ERR_WC_NOT_LOCKED,
+ SVN_ERR_WC_CATEGORY_START + 5,
+ "Working copy not locked; this is probably a bug, please report")
+
+ /** @deprecated Unused, slated for removal in the next major release. */
+ SVN_ERRDEF(SVN_ERR_WC_INVALID_LOCK,
+ SVN_ERR_WC_CATEGORY_START + 6,
+ "Invalid lock")
+
+ /** @since New in 1.7. Previously this error number was used by
+ * #SVN_ERR_WC_NOT_DIRECTORY, which is now an alias for this error. */
+ SVN_ERRDEF(SVN_ERR_WC_NOT_WORKING_COPY,
+ SVN_ERR_WC_CATEGORY_START + 7,
+ "Path is not a working copy directory")
+
+ /** @deprecated Provided for backward compatibility with the 1.6 API.
+ * Use #SVN_ERR_WC_NOT_WORKING_COPY. */
+ SVN_ERRDEF(SVN_ERR_WC_NOT_DIRECTORY,
+ SVN_ERR_WC_NOT_WORKING_COPY,
+ "Path is not a working copy directory")
+
+ SVN_ERRDEF(SVN_ERR_WC_NOT_FILE,
+ SVN_ERR_WC_CATEGORY_START + 8,
+ "Path is not a working copy file")
+
+ SVN_ERRDEF(SVN_ERR_WC_BAD_ADM_LOG,
+ SVN_ERR_WC_CATEGORY_START + 9,
+ "Problem running log")
+
+ SVN_ERRDEF(SVN_ERR_WC_PATH_NOT_FOUND,
+ SVN_ERR_WC_CATEGORY_START + 10,
+ "Can't find a working copy path")
+
+ SVN_ERRDEF(SVN_ERR_WC_NOT_UP_TO_DATE,
+ SVN_ERR_WC_CATEGORY_START + 11,
+ "Working copy is not up-to-date")
+
+ SVN_ERRDEF(SVN_ERR_WC_LEFT_LOCAL_MOD,
+ SVN_ERR_WC_CATEGORY_START + 12,
+ "Left locally modified or unversioned files")
+
+ SVN_ERRDEF(SVN_ERR_WC_SCHEDULE_CONFLICT,
+ SVN_ERR_WC_CATEGORY_START + 13,
+ "Unmergeable scheduling requested on an entry")
+
+ SVN_ERRDEF(SVN_ERR_WC_PATH_FOUND,
+ SVN_ERR_WC_CATEGORY_START + 14,
+ "Found a working copy path")
+
+ SVN_ERRDEF(SVN_ERR_WC_FOUND_CONFLICT,
+ SVN_ERR_WC_CATEGORY_START + 15,
+ "A conflict in the working copy obstructs the current operation")
+
+ SVN_ERRDEF(SVN_ERR_WC_CORRUPT,
+ SVN_ERR_WC_CATEGORY_START + 16,
+ "Working copy is corrupt")
+
+ SVN_ERRDEF(SVN_ERR_WC_CORRUPT_TEXT_BASE,
+ SVN_ERR_WC_CATEGORY_START + 17,
+ "Working copy text base is corrupt")
+
+ SVN_ERRDEF(SVN_ERR_WC_NODE_KIND_CHANGE,
+ SVN_ERR_WC_CATEGORY_START + 18,
+ "Cannot change node kind")
+
+ SVN_ERRDEF(SVN_ERR_WC_INVALID_OP_ON_CWD,
+ SVN_ERR_WC_CATEGORY_START + 19,
+ "Invalid operation on the current working directory")
+
+ SVN_ERRDEF(SVN_ERR_WC_BAD_ADM_LOG_START,
+ SVN_ERR_WC_CATEGORY_START + 20,
+ "Problem on first log entry in a working copy")
+
+ SVN_ERRDEF(SVN_ERR_WC_UNSUPPORTED_FORMAT,
+ SVN_ERR_WC_CATEGORY_START + 21,
+ "Unsupported working copy format")
+
+ SVN_ERRDEF(SVN_ERR_WC_BAD_PATH,
+ SVN_ERR_WC_CATEGORY_START + 22,
+ "Path syntax not supported in this context")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_WC_INVALID_SCHEDULE,
+ SVN_ERR_WC_CATEGORY_START + 23,
+ "Invalid schedule")
+
+ /** @since New in 1.3. */
+ SVN_ERRDEF(SVN_ERR_WC_INVALID_RELOCATION,
+ SVN_ERR_WC_CATEGORY_START + 24,
+ "Invalid relocation")
+
+ /** @since New in 1.3. */
+ SVN_ERRDEF(SVN_ERR_WC_INVALID_SWITCH,
+ SVN_ERR_WC_CATEGORY_START + 25,
+ "Invalid switch")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_WC_MISMATCHED_CHANGELIST,
+ SVN_ERR_WC_CATEGORY_START + 26,
+ "Changelist doesn't match")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ SVN_ERR_WC_CATEGORY_START + 27,
+ "Conflict resolution failed")
+
+ SVN_ERRDEF(SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND,
+ SVN_ERR_WC_CATEGORY_START + 28,
+ "Failed to locate 'copyfrom' path in working copy")
+
+ /** @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * This event is not an error, and is now reported
+ * via the standard notification mechanism instead. */
+ SVN_ERRDEF(SVN_ERR_WC_CHANGELIST_MOVE,
+ SVN_ERR_WC_CATEGORY_START + 29,
+ "Moving a path from one changelist to another")
+
+ /** @since New in 1.6. */
+ SVN_ERRDEF(SVN_ERR_WC_CANNOT_DELETE_FILE_EXTERNAL,
+ SVN_ERR_WC_CATEGORY_START + 30,
+ "Cannot delete a file external")
+
+ /** @since New in 1.6. */
+ SVN_ERRDEF(SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
+ SVN_ERR_WC_CATEGORY_START + 31,
+ "Cannot move a file external")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_WC_DB_ERROR,
+ SVN_ERR_WC_CATEGORY_START + 32,
+ "Something's amiss with the wc sqlite database")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_WC_MISSING,
+ SVN_ERR_WC_CATEGORY_START + 33,
+ "The working copy is missing")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_WC_NOT_SYMLINK,
+ SVN_ERR_WC_CATEGORY_START + 34,
+ "The specified node is not a symlink")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ SVN_ERR_WC_CATEGORY_START + 35,
+ "The specified path has an unexpected status")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_WC_UPGRADE_REQUIRED,
+ SVN_ERR_WC_CATEGORY_START + 36,
+ "The working copy needs to be upgraded")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_WC_CLEANUP_REQUIRED,
+ SVN_ERR_WC_CATEGORY_START + 37,
+ "Previous operation has not finished; "
+ "run 'cleanup' if it was interrupted")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_WC_INVALID_OPERATION_DEPTH,
+ SVN_ERR_WC_CATEGORY_START + 38,
+ "The operation cannot be performed with the specified depth")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_WC_PATH_ACCESS_DENIED,
+ SVN_ERR_WC_CATEGORY_START + 39,
+ "Couldn't open a working copy file because access was denied")
+
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_WC_MIXED_REVISIONS,
+ SVN_ERR_WC_CATEGORY_START + 40,
+ "Mixed-revision working copy was found but not expected")
+
+ /** @since New in 1.8 */
+ SVN_ERRDEF(SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET,
+ SVN_ERR_WC_CATEGORY_START + 41,
+ "Duplicate targets in svn:externals property")
+
+ /* fs errors */
+
+ SVN_ERRDEF(SVN_ERR_FS_GENERAL,
+ SVN_ERR_FS_CATEGORY_START + 0,
+ "General filesystem error")
+
+ SVN_ERRDEF(SVN_ERR_FS_CLEANUP,
+ SVN_ERR_FS_CATEGORY_START + 1,
+ "Error closing filesystem")
+
+ SVN_ERRDEF(SVN_ERR_FS_ALREADY_OPEN,
+ SVN_ERR_FS_CATEGORY_START + 2,
+ "Filesystem is already open")
+
+ SVN_ERRDEF(SVN_ERR_FS_NOT_OPEN,
+ SVN_ERR_FS_CATEGORY_START + 3,
+ "Filesystem is not open")
+
+ SVN_ERRDEF(SVN_ERR_FS_CORRUPT,
+ SVN_ERR_FS_CATEGORY_START + 4,
+ "Filesystem is corrupt")
+
+ SVN_ERRDEF(SVN_ERR_FS_PATH_SYNTAX,
+ SVN_ERR_FS_CATEGORY_START + 5,
+ "Invalid filesystem path syntax")
+
+ SVN_ERRDEF(SVN_ERR_FS_NO_SUCH_REVISION,
+ SVN_ERR_FS_CATEGORY_START + 6,
+ "Invalid filesystem revision number")
+
+ SVN_ERRDEF(SVN_ERR_FS_NO_SUCH_TRANSACTION,
+ SVN_ERR_FS_CATEGORY_START + 7,
+ "Invalid filesystem transaction name")
+
+ SVN_ERRDEF(SVN_ERR_FS_NO_SUCH_ENTRY,
+ SVN_ERR_FS_CATEGORY_START + 8,
+ "Filesystem directory has no such entry")
+
+ SVN_ERRDEF(SVN_ERR_FS_NO_SUCH_REPRESENTATION,
+ SVN_ERR_FS_CATEGORY_START + 9,
+ "Filesystem has no such representation")
+
+ SVN_ERRDEF(SVN_ERR_FS_NO_SUCH_STRING,
+ SVN_ERR_FS_CATEGORY_START + 10,
+ "Filesystem has no such string")
+
+ SVN_ERRDEF(SVN_ERR_FS_NO_SUCH_COPY,
+ SVN_ERR_FS_CATEGORY_START + 11,
+ "Filesystem has no such copy")
+
+ SVN_ERRDEF(SVN_ERR_FS_TRANSACTION_NOT_MUTABLE,
+ SVN_ERR_FS_CATEGORY_START + 12,
+ "The specified transaction is not mutable")
+
+ SVN_ERRDEF(SVN_ERR_FS_NOT_FOUND,
+ SVN_ERR_FS_CATEGORY_START + 13,
+ "Filesystem has no item")
+
+ SVN_ERRDEF(SVN_ERR_FS_ID_NOT_FOUND,
+ SVN_ERR_FS_CATEGORY_START + 14,
+ "Filesystem has no such node-rev-id")
+
+ SVN_ERRDEF(SVN_ERR_FS_NOT_ID,
+ SVN_ERR_FS_CATEGORY_START + 15,
+ "String does not represent a node or node-rev-id")
+
+ SVN_ERRDEF(SVN_ERR_FS_NOT_DIRECTORY,
+ SVN_ERR_FS_CATEGORY_START + 16,
+ "Name does not refer to a filesystem directory")
+
+ SVN_ERRDEF(SVN_ERR_FS_NOT_FILE,
+ SVN_ERR_FS_CATEGORY_START + 17,
+ "Name does not refer to a filesystem file")
+
+ SVN_ERRDEF(SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT,
+ SVN_ERR_FS_CATEGORY_START + 18,
+ "Name is not a single path component")
+
+ SVN_ERRDEF(SVN_ERR_FS_NOT_MUTABLE,
+ SVN_ERR_FS_CATEGORY_START + 19,
+ "Attempt to change immutable filesystem node")
+
+ SVN_ERRDEF(SVN_ERR_FS_ALREADY_EXISTS,
+ SVN_ERR_FS_CATEGORY_START + 20,
+ "Item already exists in filesystem")
+
+ SVN_ERRDEF(SVN_ERR_FS_ROOT_DIR,
+ SVN_ERR_FS_CATEGORY_START + 21,
+ "Attempt to remove or recreate fs root dir")
+
+ SVN_ERRDEF(SVN_ERR_FS_NOT_TXN_ROOT,
+ SVN_ERR_FS_CATEGORY_START + 22,
+ "Object is not a transaction root")
+
+ SVN_ERRDEF(SVN_ERR_FS_NOT_REVISION_ROOT,
+ SVN_ERR_FS_CATEGORY_START + 23,
+ "Object is not a revision root")
+
+ SVN_ERRDEF(SVN_ERR_FS_CONFLICT,
+ SVN_ERR_FS_CATEGORY_START + 24,
+ "Merge conflict during commit")
+
+ SVN_ERRDEF(SVN_ERR_FS_REP_CHANGED,
+ SVN_ERR_FS_CATEGORY_START + 25,
+ "A representation vanished or changed between reads")
+
+ SVN_ERRDEF(SVN_ERR_FS_REP_NOT_MUTABLE,
+ SVN_ERR_FS_CATEGORY_START + 26,
+ "Tried to change an immutable representation")
+
+ SVN_ERRDEF(SVN_ERR_FS_MALFORMED_SKEL,
+ SVN_ERR_FS_CATEGORY_START + 27,
+ "Malformed skeleton data")
+
+ SVN_ERRDEF(SVN_ERR_FS_TXN_OUT_OF_DATE,
+ SVN_ERR_FS_CATEGORY_START + 28,
+ "Transaction is out of date")
+
+ SVN_ERRDEF(SVN_ERR_FS_BERKELEY_DB,
+ SVN_ERR_FS_CATEGORY_START + 29,
+ "Berkeley DB error")
+
+ SVN_ERRDEF(SVN_ERR_FS_BERKELEY_DB_DEADLOCK,
+ SVN_ERR_FS_CATEGORY_START + 30,
+ "Berkeley DB deadlock error")
+
+ SVN_ERRDEF(SVN_ERR_FS_TRANSACTION_DEAD,
+ SVN_ERR_FS_CATEGORY_START + 31,
+ "Transaction is dead")
+
+ SVN_ERRDEF(SVN_ERR_FS_TRANSACTION_NOT_DEAD,
+ SVN_ERR_FS_CATEGORY_START + 32,
+ "Transaction is not dead")
+
+ /** @since New in 1.1. */
+ SVN_ERRDEF(SVN_ERR_FS_UNKNOWN_FS_TYPE,
+ SVN_ERR_FS_CATEGORY_START + 33,
+ "Unknown FS type")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_FS_NO_USER,
+ SVN_ERR_FS_CATEGORY_START + 34,
+ "No user associated with filesystem")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_FS_PATH_ALREADY_LOCKED,
+ SVN_ERR_FS_CATEGORY_START + 35,
+ "Path is already locked")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_FS_PATH_NOT_LOCKED,
+ SVN_ERR_FS_CATEGORY_START + 36,
+ "Path is not locked")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_FS_BAD_LOCK_TOKEN,
+ SVN_ERR_FS_CATEGORY_START + 37,
+ "Lock token is incorrect")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_FS_NO_LOCK_TOKEN,
+ SVN_ERR_FS_CATEGORY_START + 38,
+ "No lock token provided")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_FS_LOCK_OWNER_MISMATCH,
+ SVN_ERR_FS_CATEGORY_START + 39,
+ "Username does not match lock owner")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_FS_NO_SUCH_LOCK,
+ SVN_ERR_FS_CATEGORY_START + 40,
+ "Filesystem has no such lock")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_FS_LOCK_EXPIRED,
+ SVN_ERR_FS_CATEGORY_START + 41,
+ "Lock has expired")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_FS_OUT_OF_DATE,
+ SVN_ERR_FS_CATEGORY_START + 42,
+ "Item is out of date")
+
+ /**@since New in 1.2.
+ *
+ * This is analogous to SVN_ERR_REPOS_UNSUPPORTED_VERSION. To avoid
+ * confusion with "versions" (i.e., releases) of Subversion, we've
+ * started calling this the "format" number instead. The old
+ * SVN_ERR_REPOS_UNSUPPORTED_VERSION error predates this and so
+ * retains its name.
+ */
+ SVN_ERRDEF(SVN_ERR_FS_UNSUPPORTED_FORMAT,
+ SVN_ERR_FS_CATEGORY_START + 43,
+ "Unsupported FS format")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_FS_REP_BEING_WRITTEN,
+ SVN_ERR_FS_CATEGORY_START + 44,
+ "Representation is being written")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_FS_TXN_NAME_TOO_LONG,
+ SVN_ERR_FS_CATEGORY_START + 45,
+ "The generated transaction name is too long")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_FS_NO_SUCH_NODE_ORIGIN,
+ SVN_ERR_FS_CATEGORY_START + 46,
+ "Filesystem has no such node origin record")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_FS_UNSUPPORTED_UPGRADE,
+ SVN_ERR_FS_CATEGORY_START + 47,
+ "Filesystem upgrade is not supported")
+
+ /** @since New in 1.6. */
+ SVN_ERRDEF(SVN_ERR_FS_NO_SUCH_CHECKSUM_REP,
+ SVN_ERR_FS_CATEGORY_START + 48,
+ "Filesystem has no such checksum-representation index record")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH,
+ SVN_ERR_FS_CATEGORY_START + 49,
+ "Property value in filesystem differs from the provided "
+ "base value")
+
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION,
+ SVN_ERR_FS_CATEGORY_START + 50,
+ "The filesystem editor completion process was not followed")
+
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE,
+ SVN_ERR_FS_CATEGORY_START + 51,
+ "A packed revprop could not be read")
+
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_FS_REVPROP_CACHE_INIT_FAILURE,
+ SVN_ERR_FS_CATEGORY_START + 52,
+ "Could not initialize the revprop caching infrastructure.")
+
+ /* repos errors */
+
+ SVN_ERRDEF(SVN_ERR_REPOS_LOCKED,
+ SVN_ERR_REPOS_CATEGORY_START + 0,
+ "The repository is locked, perhaps for db recovery")
+
+ SVN_ERRDEF(SVN_ERR_REPOS_HOOK_FAILURE,
+ SVN_ERR_REPOS_CATEGORY_START + 1,
+ "A repository hook failed")
+
+ SVN_ERRDEF(SVN_ERR_REPOS_BAD_ARGS,
+ SVN_ERR_REPOS_CATEGORY_START + 2,
+ "Incorrect arguments supplied")
+
+ SVN_ERRDEF(SVN_ERR_REPOS_NO_DATA_FOR_REPORT,
+ SVN_ERR_REPOS_CATEGORY_START + 3,
+ "A report cannot be generated because no data was supplied")
+
+ SVN_ERRDEF(SVN_ERR_REPOS_BAD_REVISION_REPORT,
+ SVN_ERR_REPOS_CATEGORY_START + 4,
+ "Bogus revision report")
+
+ /* This is analogous to SVN_ERR_FS_UNSUPPORTED_FORMAT. To avoid
+ * confusion with "versions" (i.e., releases) of Subversion, we
+ * started using the word "format" instead of "version". However,
+ * this error code's name predates that decision.
+ */
+ SVN_ERRDEF(SVN_ERR_REPOS_UNSUPPORTED_VERSION,
+ SVN_ERR_REPOS_CATEGORY_START + 5,
+ "Unsupported repository version")
+
+ SVN_ERRDEF(SVN_ERR_REPOS_DISABLED_FEATURE,
+ SVN_ERR_REPOS_CATEGORY_START + 6,
+ "Disabled repository feature")
+
+ SVN_ERRDEF(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED,
+ SVN_ERR_REPOS_CATEGORY_START + 7,
+ "Error running post-commit hook")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED,
+ SVN_ERR_REPOS_CATEGORY_START + 8,
+ "Error running post-lock hook")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED,
+ SVN_ERR_REPOS_CATEGORY_START + 9,
+ "Error running post-unlock hook")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_REPOS_UNSUPPORTED_UPGRADE,
+ SVN_ERR_REPOS_CATEGORY_START + 10,
+ "Repository upgrade is not supported")
+
+ /* generic RA errors */
+
+ SVN_ERRDEF(SVN_ERR_RA_ILLEGAL_URL,
+ SVN_ERR_RA_CATEGORY_START + 0,
+ "Bad URL passed to RA layer")
+
+ SVN_ERRDEF(SVN_ERR_RA_NOT_AUTHORIZED,
+ SVN_ERR_RA_CATEGORY_START + 1,
+ "Authorization failed")
+
+ SVN_ERRDEF(SVN_ERR_RA_UNKNOWN_AUTH,
+ SVN_ERR_RA_CATEGORY_START + 2,
+ "Unknown authorization method")
+
+ SVN_ERRDEF(SVN_ERR_RA_NOT_IMPLEMENTED,
+ SVN_ERR_RA_CATEGORY_START + 3,
+ "Repository access method not implemented")
+
+ SVN_ERRDEF(SVN_ERR_RA_OUT_OF_DATE,
+ SVN_ERR_RA_CATEGORY_START + 4,
+ "Item is out of date")
+
+ SVN_ERRDEF(SVN_ERR_RA_NO_REPOS_UUID,
+ SVN_ERR_RA_CATEGORY_START + 5,
+ "Repository has no UUID")
+
+ SVN_ERRDEF(SVN_ERR_RA_UNSUPPORTED_ABI_VERSION,
+ SVN_ERR_RA_CATEGORY_START + 6,
+ "Unsupported RA plugin ABI version")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_RA_NOT_LOCKED,
+ SVN_ERR_RA_CATEGORY_START + 7,
+ "Path is not locked")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED,
+ SVN_ERR_RA_CATEGORY_START + 8,
+ "Server can only replay from the root of a repository")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_RA_UUID_MISMATCH,
+ SVN_ERR_RA_CATEGORY_START + 9,
+ "Repository UUID does not match expected UUID")
+
+ /** @since New in 1.6. */
+ SVN_ERRDEF(SVN_ERR_RA_REPOS_ROOT_URL_MISMATCH,
+ SVN_ERR_RA_CATEGORY_START + 10,
+ "Repository root URL does not match expected root URL")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_RA_SESSION_URL_MISMATCH,
+ SVN_ERR_RA_CATEGORY_START + 11,
+ "Session URL does not match expected session URL")
+
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
+ SVN_ERR_RA_CATEGORY_START + 12,
+ "Can't create tunnel")
+
+ /* ra_dav errors */
+
+ SVN_ERRDEF(SVN_ERR_RA_DAV_SOCK_INIT,
+ SVN_ERR_RA_DAV_CATEGORY_START + 0,
+ "RA layer failed to init socket layer")
+
+ SVN_ERRDEF(SVN_ERR_RA_DAV_CREATING_REQUEST,
+ SVN_ERR_RA_DAV_CATEGORY_START + 1,
+ "RA layer failed to create HTTP request")
+
+ SVN_ERRDEF(SVN_ERR_RA_DAV_REQUEST_FAILED,
+ SVN_ERR_RA_DAV_CATEGORY_START + 2,
+ "RA layer request failed")
+
+ SVN_ERRDEF(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED,
+ SVN_ERR_RA_DAV_CATEGORY_START + 3,
+ "RA layer didn't receive requested OPTIONS info")
+
+ SVN_ERRDEF(SVN_ERR_RA_DAV_PROPS_NOT_FOUND,
+ SVN_ERR_RA_DAV_CATEGORY_START + 4,
+ "RA layer failed to fetch properties")
+
+ SVN_ERRDEF(SVN_ERR_RA_DAV_ALREADY_EXISTS,
+ SVN_ERR_RA_DAV_CATEGORY_START + 5,
+ "RA layer file already exists")
+
+ /** @deprecated To improve consistency between ra layers, this error code
+ is replaced by SVN_ERR_BAD_CONFIG_VALUE.
+ Slated for removal in the next major release. */
+ SVN_ERRDEF(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE,
+ SVN_ERR_RA_DAV_CATEGORY_START + 6,
+ "Invalid configuration value")
+
+ /** @deprecated To improve consistency between ra layers, this error code
+ is replaced in ra_serf by SVN_ERR_FS_NOT_FOUND.
+ Slated for removal in the next major release. */
+ SVN_ERRDEF(SVN_ERR_RA_DAV_PATH_NOT_FOUND,
+ SVN_ERR_RA_DAV_CATEGORY_START + 7,
+ "HTTP Path Not Found")
+
+ SVN_ERRDEF(SVN_ERR_RA_DAV_PROPPATCH_FAILED,
+ SVN_ERR_RA_DAV_CATEGORY_START + 8,
+ "Failed to execute WebDAV PROPPATCH")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ SVN_ERR_RA_DAV_CATEGORY_START + 9,
+ "Malformed network data")
+
+ /** @since New in 1.3 */
+ SVN_ERRDEF(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS,
+ SVN_ERR_RA_DAV_CATEGORY_START + 10,
+ "Unable to extract data from response header")
+
+ /** @since New in 1.5 */
+ SVN_ERRDEF(SVN_ERR_RA_DAV_RELOCATED,
+ SVN_ERR_RA_DAV_CATEGORY_START + 11,
+ "Repository has been moved")
+
+ /** @since New in 1.7 */
+ SVN_ERRDEF(SVN_ERR_RA_DAV_CONN_TIMEOUT,
+ SVN_ERR_RA_DAV_CATEGORY_START + 12,
+ "Connection timed out")
+
+ /** @since New in 1.6 */
+ SVN_ERRDEF(SVN_ERR_RA_DAV_FORBIDDEN,
+ SVN_ERR_RA_DAV_CATEGORY_START + 13,
+ "URL access forbidden for unknown reason")
+
+ /* ra_local errors */
+
+ SVN_ERRDEF(SVN_ERR_RA_LOCAL_REPOS_NOT_FOUND,
+ SVN_ERR_RA_LOCAL_CATEGORY_START + 0,
+ "Couldn't find a repository")
+
+ SVN_ERRDEF(SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED,
+ SVN_ERR_RA_LOCAL_CATEGORY_START + 1,
+ "Couldn't open a repository")
+
+ /* svndiff errors */
+
+ SVN_ERRDEF(SVN_ERR_SVNDIFF_INVALID_HEADER,
+ SVN_ERR_SVNDIFF_CATEGORY_START + 0,
+ "Svndiff data has invalid header")
+
+ SVN_ERRDEF(SVN_ERR_SVNDIFF_CORRUPT_WINDOW,
+ SVN_ERR_SVNDIFF_CATEGORY_START + 1,
+ "Svndiff data contains corrupt window")
+
+ SVN_ERRDEF(SVN_ERR_SVNDIFF_BACKWARD_VIEW,
+ SVN_ERR_SVNDIFF_CATEGORY_START + 2,
+ "Svndiff data contains backward-sliding source view")
+
+ SVN_ERRDEF(SVN_ERR_SVNDIFF_INVALID_OPS,
+ SVN_ERR_SVNDIFF_CATEGORY_START + 3,
+ "Svndiff data contains invalid instruction")
+
+ SVN_ERRDEF(SVN_ERR_SVNDIFF_UNEXPECTED_END,
+ SVN_ERR_SVNDIFF_CATEGORY_START + 4,
+ "Svndiff data ends unexpectedly")
+
+ SVN_ERRDEF(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA,
+ SVN_ERR_SVNDIFF_CATEGORY_START + 5,
+ "Svndiff compressed data is invalid")
+
+ /* mod_dav_svn errors */
+
+ SVN_ERRDEF(SVN_ERR_APMOD_MISSING_PATH_TO_FS,
+ SVN_ERR_APMOD_CATEGORY_START + 0,
+ "Apache has no path to an SVN filesystem")
+
+ SVN_ERRDEF(SVN_ERR_APMOD_MALFORMED_URI,
+ SVN_ERR_APMOD_CATEGORY_START + 1,
+ "Apache got a malformed URI")
+
+ SVN_ERRDEF(SVN_ERR_APMOD_ACTIVITY_NOT_FOUND,
+ SVN_ERR_APMOD_CATEGORY_START + 2,
+ "Activity not found")
+
+ SVN_ERRDEF(SVN_ERR_APMOD_BAD_BASELINE,
+ SVN_ERR_APMOD_CATEGORY_START + 3,
+ "Baseline incorrect")
+
+ SVN_ERRDEF(SVN_ERR_APMOD_CONNECTION_ABORTED,
+ SVN_ERR_APMOD_CATEGORY_START + 4,
+ "Input/output error")
+
+ /* libsvn_client errors */
+
+ SVN_ERRDEF(SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED,
+ SVN_ERR_CLIENT_CATEGORY_START + 0,
+ "A path under version control is needed for this operation")
+
+ SVN_ERRDEF(SVN_ERR_CLIENT_RA_ACCESS_REQUIRED,
+ SVN_ERR_CLIENT_CATEGORY_START + 1,
+ "Repository access is needed for this operation")
+
+ SVN_ERRDEF(SVN_ERR_CLIENT_BAD_REVISION,
+ SVN_ERR_CLIENT_CATEGORY_START + 2,
+ "Bogus revision information given")
+
+ SVN_ERRDEF(SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL,
+ SVN_ERR_CLIENT_CATEGORY_START + 3,
+ "Attempting to commit to a URL more than once")
+
+ SVN_ERRDEF(SVN_ERR_CLIENT_IS_BINARY_FILE,
+ SVN_ERR_CLIENT_CATEGORY_START + 4,
+ "Operation does not apply to binary file")
+
+ /*### SVN_PROP_EXTERNALS needed to be replaced with "svn:externals"
+ in order to get gettext translatable strings */
+ SVN_ERRDEF(SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION,
+ SVN_ERR_CLIENT_CATEGORY_START + 5,
+ "Format of an svn:externals property was invalid")
+
+ SVN_ERRDEF(SVN_ERR_CLIENT_MODIFIED,
+ SVN_ERR_CLIENT_CATEGORY_START + 6,
+ "Attempting restricted operation for modified resource")
+
+ SVN_ERRDEF(SVN_ERR_CLIENT_IS_DIRECTORY,
+ SVN_ERR_CLIENT_CATEGORY_START + 7,
+ "Operation does not apply to directory")
+
+ SVN_ERRDEF(SVN_ERR_CLIENT_REVISION_RANGE,
+ SVN_ERR_CLIENT_CATEGORY_START + 8,
+ "Revision range is not allowed")
+
+ SVN_ERRDEF(SVN_ERR_CLIENT_INVALID_RELOCATION,
+ SVN_ERR_CLIENT_CATEGORY_START + 9,
+ "Inter-repository relocation not allowed")
+
+ SVN_ERRDEF(SVN_ERR_CLIENT_REVISION_AUTHOR_CONTAINS_NEWLINE,
+ SVN_ERR_CLIENT_CATEGORY_START + 10,
+ "Author name cannot contain a newline")
+
+ SVN_ERRDEF(SVN_ERR_CLIENT_PROPERTY_NAME,
+ SVN_ERR_CLIENT_CATEGORY_START + 11,
+ "Bad property name")
+
+ /** @since New in 1.1. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_UNRELATED_RESOURCES,
+ SVN_ERR_CLIENT_CATEGORY_START + 12,
+ "Two versioned resources are unrelated")
+
+ /** @since New in 1.2. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_MISSING_LOCK_TOKEN,
+ SVN_ERR_CLIENT_CATEGORY_START + 13,
+ "Path has no lock token")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
+ SVN_ERR_CLIENT_CATEGORY_START + 14,
+ "Operation does not support multiple sources")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_NO_VERSIONED_PARENT,
+ SVN_ERR_CLIENT_CATEGORY_START + 15,
+ "No versioned parent directories")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_NOT_READY_TO_MERGE,
+ SVN_ERR_CLIENT_CATEGORY_START + 16,
+ "Working copy and merge source not ready for reintegration")
+
+ /** @since New in 1.6. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED,
+ SVN_ERR_CLIENT_CATEGORY_START + 17,
+ "A file external cannot overwrite an existing versioned item")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT,
+ SVN_ERR_CLIENT_CATEGORY_START + 18,
+ "Invalid path component strip count specified")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_CYCLE_DETECTED,
+ SVN_ERR_CLIENT_CATEGORY_START + 19,
+ "Detected a cycle while processing the operation")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_MERGE_UPDATE_REQUIRED,
+ SVN_ERR_CLIENT_CATEGORY_START + 20,
+ "Working copy and merge source not ready for reintegration")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING,
+ SVN_ERR_CLIENT_CATEGORY_START + 21,
+ "Invalid mergeinfo detected in merge target")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_NO_LOCK_TOKEN,
+ SVN_ERR_CLIENT_CATEGORY_START + 22,
+ "Can't perform this operation without a valid lock token")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER,
+ SVN_ERR_CLIENT_CATEGORY_START + 23,
+ "The operation is forbidden by the server")
+
+ /* misc errors */
+
+ SVN_ERRDEF(SVN_ERR_BASE,
+ SVN_ERR_MISC_CATEGORY_START + 0,
+ "A problem occurred; see other errors for details")
+
+ SVN_ERRDEF(SVN_ERR_PLUGIN_LOAD_FAILURE,
+ SVN_ERR_MISC_CATEGORY_START + 1,
+ "Failure loading plugin")
+
+ SVN_ERRDEF(SVN_ERR_MALFORMED_FILE,
+ SVN_ERR_MISC_CATEGORY_START + 2,
+ "Malformed file")
+
+ SVN_ERRDEF(SVN_ERR_INCOMPLETE_DATA,
+ SVN_ERR_MISC_CATEGORY_START + 3,
+ "Incomplete data")
+
+ SVN_ERRDEF(SVN_ERR_INCORRECT_PARAMS,
+ SVN_ERR_MISC_CATEGORY_START + 4,
+ "Incorrect parameters given")
+
+ SVN_ERRDEF(SVN_ERR_UNVERSIONED_RESOURCE,
+ SVN_ERR_MISC_CATEGORY_START + 5,
+ "Tried a versioning operation on an unversioned resource")
+
+ SVN_ERRDEF(SVN_ERR_TEST_FAILED,
+ SVN_ERR_MISC_CATEGORY_START + 6,
+ "Test failed")
+
+ SVN_ERRDEF(SVN_ERR_UNSUPPORTED_FEATURE,
+ SVN_ERR_MISC_CATEGORY_START + 7,
+ "Trying to use an unsupported feature")
+
+ SVN_ERRDEF(SVN_ERR_BAD_PROP_KIND,
+ SVN_ERR_MISC_CATEGORY_START + 8,
+ "Unexpected or unknown property kind")
+
+ SVN_ERRDEF(SVN_ERR_ILLEGAL_TARGET,
+ SVN_ERR_MISC_CATEGORY_START + 9,
+ "Illegal target for the requested operation")
+
+ SVN_ERRDEF(SVN_ERR_DELTA_MD5_CHECKSUM_ABSENT,
+ SVN_ERR_MISC_CATEGORY_START + 10,
+ "MD5 checksum is missing")
+
+ SVN_ERRDEF(SVN_ERR_DIR_NOT_EMPTY,
+ SVN_ERR_MISC_CATEGORY_START + 11,
+ "Directory needs to be empty but is not")
+
+ SVN_ERRDEF(SVN_ERR_EXTERNAL_PROGRAM,
+ SVN_ERR_MISC_CATEGORY_START + 12,
+ "Error calling external program")
+
+ SVN_ERRDEF(SVN_ERR_SWIG_PY_EXCEPTION_SET,
+ SVN_ERR_MISC_CATEGORY_START + 13,
+ "Python exception has been set with the error")
+
+ SVN_ERRDEF(SVN_ERR_CHECKSUM_MISMATCH,
+ SVN_ERR_MISC_CATEGORY_START + 14,
+ "A checksum mismatch occurred")
+
+ SVN_ERRDEF(SVN_ERR_CANCELLED,
+ SVN_ERR_MISC_CATEGORY_START + 15,
+ "The operation was interrupted")
+
+ SVN_ERRDEF(SVN_ERR_INVALID_DIFF_OPTION,
+ SVN_ERR_MISC_CATEGORY_START + 16,
+ "The specified diff option is not supported")
+
+ SVN_ERRDEF(SVN_ERR_PROPERTY_NOT_FOUND,
+ SVN_ERR_MISC_CATEGORY_START + 17,
+ "Property not found")
+
+ SVN_ERRDEF(SVN_ERR_NO_AUTH_FILE_PATH,
+ SVN_ERR_MISC_CATEGORY_START + 18,
+ "No auth file path available")
+
+ /** @since New in 1.1. */
+ SVN_ERRDEF(SVN_ERR_VERSION_MISMATCH,
+ SVN_ERR_MISC_CATEGORY_START + 19,
+ "Incompatible library version")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_MERGEINFO_PARSE_ERROR,
+ SVN_ERR_MISC_CATEGORY_START + 20,
+ "Mergeinfo parse error")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_CEASE_INVOCATION,
+ SVN_ERR_MISC_CATEGORY_START + 21,
+ "Cease invocation of this API")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_REVNUM_PARSE_FAILURE,
+ SVN_ERR_MISC_CATEGORY_START + 22,
+ "Error parsing revision number")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_ITER_BREAK,
+ SVN_ERR_MISC_CATEGORY_START + 23,
+ "Iteration terminated before completion")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_UNKNOWN_CHANGELIST,
+ SVN_ERR_MISC_CATEGORY_START + 24,
+ "Unknown changelist")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
+ SVN_ERR_MISC_CATEGORY_START + 25,
+ "Reserved directory name in command line arguments")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_UNKNOWN_CAPABILITY,
+ SVN_ERR_MISC_CATEGORY_START + 26,
+ "Inquiry about unknown capability")
+
+ /** @since New in 1.6. */
+ SVN_ERRDEF(SVN_ERR_TEST_SKIPPED,
+ SVN_ERR_MISC_CATEGORY_START + 27,
+ "Test skipped")
+
+ /** @since New in 1.6. */
+ SVN_ERRDEF(SVN_ERR_NO_APR_MEMCACHE,
+ SVN_ERR_MISC_CATEGORY_START + 28,
+ "APR memcache library not available")
+
+ /** @since New in 1.6. */
+ SVN_ERRDEF(SVN_ERR_ATOMIC_INIT_FAILURE,
+ SVN_ERR_MISC_CATEGORY_START + 29,
+ "Couldn't perform atomic initialization")
+
+ /** @since New in 1.6. */
+ SVN_ERRDEF(SVN_ERR_SQLITE_ERROR,
+ SVN_ERR_MISC_CATEGORY_START + 30,
+ "SQLite error")
+
+ /** @since New in 1.6. */
+ SVN_ERRDEF(SVN_ERR_SQLITE_READONLY,
+ SVN_ERR_MISC_CATEGORY_START + 31,
+ "Attempted to write to readonly SQLite db")
+
+ /** @since New in 1.6.
+ * @deprecated the internal sqlite support code does not manage schemas
+ * any longer. */
+ SVN_ERRDEF(SVN_ERR_SQLITE_UNSUPPORTED_SCHEMA,
+ SVN_ERR_MISC_CATEGORY_START + 32,
+ "Unsupported schema found in SQLite db")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_SQLITE_BUSY,
+ SVN_ERR_MISC_CATEGORY_START + 33,
+ "The SQLite db is busy")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_SQLITE_RESETTING_FOR_ROLLBACK,
+ SVN_ERR_MISC_CATEGORY_START + 34,
+ "SQLite busy at transaction rollback; "
+ "resetting all busy SQLite statements to allow rollback")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_SQLITE_CONSTRAINT,
+ SVN_ERR_MISC_CATEGORY_START + 35,
+ "Constraint error in SQLite db")
+
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_TOO_MANY_MEMCACHED_SERVERS,
+ SVN_ERR_MISC_CATEGORY_START + 36,
+ "Too many memcached servers configured")
+
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_MALFORMED_VERSION_STRING,
+ SVN_ERR_MISC_CATEGORY_START + 37,
+ "Failed to parse version number string")
+
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_CORRUPTED_ATOMIC_STORAGE,
+ SVN_ERR_MISC_CATEGORY_START + 38,
+ "Atomic data storage is corrupt")
+
+ /* command-line client errors */
+
+ SVN_ERRDEF(SVN_ERR_CL_ARG_PARSING_ERROR,
+ SVN_ERR_CL_CATEGORY_START + 0,
+ "Error parsing arguments")
+
+ SVN_ERRDEF(SVN_ERR_CL_INSUFFICIENT_ARGS,
+ SVN_ERR_CL_CATEGORY_START + 1,
+ "Not enough arguments provided")
+
+ SVN_ERRDEF(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS,
+ SVN_ERR_CL_CATEGORY_START + 2,
+ "Mutually exclusive arguments specified")
+
+ SVN_ERRDEF(SVN_ERR_CL_ADM_DIR_RESERVED,
+ SVN_ERR_CL_CATEGORY_START + 3,
+ "Attempted command in administrative dir")
+
+ SVN_ERRDEF(SVN_ERR_CL_LOG_MESSAGE_IS_VERSIONED_FILE,
+ SVN_ERR_CL_CATEGORY_START + 4,
+ "The log message file is under version control")
+
+ SVN_ERRDEF(SVN_ERR_CL_LOG_MESSAGE_IS_PATHNAME,
+ SVN_ERR_CL_CATEGORY_START + 5,
+ "The log message is a pathname")
+
+ SVN_ERRDEF(SVN_ERR_CL_COMMIT_IN_ADDED_DIR,
+ SVN_ERR_CL_CATEGORY_START + 6,
+ "Committing in directory scheduled for addition")
+
+ SVN_ERRDEF(SVN_ERR_CL_NO_EXTERNAL_EDITOR,
+ SVN_ERR_CL_CATEGORY_START + 7,
+ "No external editor available")
+
+ SVN_ERRDEF(SVN_ERR_CL_BAD_LOG_MESSAGE,
+ SVN_ERR_CL_CATEGORY_START + 8,
+ "Something is wrong with the log message's contents")
+
+ SVN_ERRDEF(SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE,
+ SVN_ERR_CL_CATEGORY_START + 9,
+ "A log message was given where none was necessary")
+
+ SVN_ERRDEF(SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL,
+ SVN_ERR_CL_CATEGORY_START + 10,
+ "No external merge tool available")
+
+ SVN_ERRDEF(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
+ SVN_ERR_CL_CATEGORY_START + 11,
+ "Failed processing one or more externals definitions")
+
+ /* ra_svn errors */
+
+ SVN_ERRDEF(SVN_ERR_RA_SVN_CMD_ERR,
+ SVN_ERR_RA_SVN_CATEGORY_START + 0,
+ "Special code for wrapping server errors to report to client")
+
+ SVN_ERRDEF(SVN_ERR_RA_SVN_UNKNOWN_CMD,
+ SVN_ERR_RA_SVN_CATEGORY_START + 1,
+ "Unknown svn protocol command")
+
+ SVN_ERRDEF(SVN_ERR_RA_SVN_CONNECTION_CLOSED,
+ SVN_ERR_RA_SVN_CATEGORY_START + 2,
+ "Network connection closed unexpectedly")
+
+ SVN_ERRDEF(SVN_ERR_RA_SVN_IO_ERROR,
+ SVN_ERR_RA_SVN_CATEGORY_START + 3,
+ "Network read/write error")
+
+ SVN_ERRDEF(SVN_ERR_RA_SVN_MALFORMED_DATA,
+ SVN_ERR_RA_SVN_CATEGORY_START + 4,
+ "Malformed network data")
+
+ SVN_ERRDEF(SVN_ERR_RA_SVN_REPOS_NOT_FOUND,
+ SVN_ERR_RA_SVN_CATEGORY_START + 5,
+ "Couldn't find a repository")
+
+ SVN_ERRDEF(SVN_ERR_RA_SVN_BAD_VERSION,
+ SVN_ERR_RA_SVN_CATEGORY_START + 6,
+ "Client/server version mismatch")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_RA_SVN_NO_MECHANISMS,
+ SVN_ERR_RA_SVN_CATEGORY_START + 7,
+ "Cannot negotiate authentication mechanism")
+
+ /** @since New in 1.7 */
+ SVN_ERRDEF(SVN_ERR_RA_SVN_EDIT_ABORTED,
+ SVN_ERR_RA_SVN_CATEGORY_START + 8,
+ "Editor drive was aborted")
+
+ /* libsvn_auth errors */
+
+ /* this error can be used when an auth provider doesn't have
+ the creds, but no other "real" error occurred. */
+ SVN_ERRDEF(SVN_ERR_AUTHN_CREDS_UNAVAILABLE,
+ SVN_ERR_AUTHN_CATEGORY_START + 0,
+ "Credential data unavailable")
+
+ SVN_ERRDEF(SVN_ERR_AUTHN_NO_PROVIDER,
+ SVN_ERR_AUTHN_CATEGORY_START + 1,
+ "No authentication provider available")
+
+ SVN_ERRDEF(SVN_ERR_AUTHN_PROVIDERS_EXHAUSTED,
+ SVN_ERR_AUTHN_CATEGORY_START + 2,
+ "All authentication providers exhausted")
+
+ SVN_ERRDEF(SVN_ERR_AUTHN_CREDS_NOT_SAVED,
+ SVN_ERR_AUTHN_CATEGORY_START + 3,
+ "Credentials not saved")
+
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_AUTHN_FAILED,
+ SVN_ERR_AUTHN_CATEGORY_START + 4,
+ "Authentication failed")
+
+ /* authorization errors */
+
+ SVN_ERRDEF(SVN_ERR_AUTHZ_ROOT_UNREADABLE,
+ SVN_ERR_AUTHZ_CATEGORY_START + 0,
+ "Read access denied for root of edit")
+
+ /** @since New in 1.1. */
+ SVN_ERRDEF(SVN_ERR_AUTHZ_UNREADABLE,
+ SVN_ERR_AUTHZ_CATEGORY_START + 1,
+ "Item is not readable")
+
+ /** @since New in 1.1. */
+ SVN_ERRDEF(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
+ SVN_ERR_AUTHZ_CATEGORY_START + 2,
+ "Item is partially readable")
+
+ SVN_ERRDEF(SVN_ERR_AUTHZ_INVALID_CONFIG,
+ SVN_ERR_AUTHZ_CATEGORY_START + 3,
+ "Invalid authz configuration")
+
+ /** @since New in 1.3 */
+ SVN_ERRDEF(SVN_ERR_AUTHZ_UNWRITABLE,
+ SVN_ERR_AUTHZ_CATEGORY_START + 4,
+ "Item is not writable")
+
+
+ /* libsvn_diff errors */
+
+ SVN_ERRDEF(SVN_ERR_DIFF_DATASOURCE_MODIFIED,
+ SVN_ERR_DIFF_CATEGORY_START + 0,
+ "Diff data source modified unexpectedly")
+
+ /* libsvn_ra_serf errors */
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_RA_SERF_SSPI_INITIALISATION_FAILED,
+ SVN_ERR_RA_SERF_CATEGORY_START + 0,
+ "Initialization of SSPI library failed")
+ /** @since New in 1.5. */
+ SVN_ERRDEF(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED,
+ SVN_ERR_RA_SERF_CATEGORY_START + 1,
+ "Server SSL certificate untrusted")
+ /** @since New in 1.7.
+ @deprecated GSSAPI now handled by serf rather than libsvn_ra_serf. */
+ SVN_ERRDEF(SVN_ERR_RA_SERF_GSSAPI_INITIALISATION_FAILED,
+ SVN_ERR_RA_SERF_CATEGORY_START + 2,
+ "Initialization of the GSSAPI context failed")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_RA_SERF_WRAPPED_ERROR,
+ SVN_ERR_RA_SERF_CATEGORY_START + 3,
+ "While handling serf response:")
+
+ /* malfunctions such as assertion failures */
+
+ SVN_ERRDEF(SVN_ERR_ASSERTION_FAIL,
+ SVN_ERR_MALFUNC_CATEGORY_START + 0,
+ "Assertion failure")
+
+ SVN_ERRDEF(SVN_ERR_ASSERTION_ONLY_TRACING_LINKS,
+ SVN_ERR_MALFUNC_CATEGORY_START + 1,
+ "No non-tracing links found in the error chain")
+
+SVN_ERROR_END
+
+
+#undef SVN_ERROR_START
+#undef SVN_ERRDEF
+#undef SVN_ERROR_END
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* defined(SVN_ERROR_BUILD_ARRAY) || !defined(SVN_ERROR_ENUM_DEFINED) */
diff --git a/contrib/subversion/subversion/include/svn_fs.h b/contrib/subversion/subversion/include/svn_fs.h
new file mode 100644
index 0000000..8cef9a3
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_fs.h
@@ -0,0 +1,2530 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_fs.h
+ * @brief Interface to the Subversion filesystem.
+ */
+
+#ifndef SVN_FS_H
+#define SVN_FS_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_time.h> /* for apr_time_t */
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_delta.h"
+#include "svn_io.h"
+#include "svn_mergeinfo.h"
+#include "svn_checksum.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/**
+ * Get libsvn_fs version information.
+ *
+ * @since New in 1.1.
+ */
+const svn_version_t *
+svn_fs_version(void);
+
+/**
+ * @defgroup fs_handling Filesystem interaction subsystem
+ * @{
+ */
+
+/* Opening and creating filesystems. */
+
+
+/** An object representing a Subversion filesystem. */
+typedef struct svn_fs_t svn_fs_t;
+
+
+/**
+ * @name Filesystem configuration options
+ * @{
+ */
+#define SVN_FS_CONFIG_BDB_TXN_NOSYNC "bdb-txn-nosync"
+#define SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE "bdb-log-autoremove"
+
+/** Enable / disable text delta caching for a FSFS repository.
+ *
+ * @since New in 1.7.
+ */
+#define SVN_FS_CONFIG_FSFS_CACHE_DELTAS "fsfs-cache-deltas"
+
+/** Enable / disable full-text caching for a FSFS repository.
+ *
+ * @since New in 1.7.
+ */
+#define SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS "fsfs-cache-fulltexts"
+
+/** Enable / disable revprop caching for a FSFS repository.
+ *
+ * "2" is allowed, too and means "enable if efficient",
+ * i.e. this will not create warning at runtime if there
+ * if no efficient support for revprop caching.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_FS_CONFIG_FSFS_CACHE_REVPROPS "fsfs-cache-revprops"
+
+/** Select the cache namespace. If you potentially share the cache with
+ * another FS object for the same repository, objects read through one FS
+ * will not need to be read again for the other. In most cases, that is
+ * a very desirable behavior and the default is, therefore, an empty
+ * namespace.
+ *
+ * If you want to be sure that your FS instance will actually read all
+ * requested data at least once, you need to specify a separate namespace
+ * for it. All repository verification code, for instance, should use
+ * some GUID here that is different each time you open an FS instance.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_FS_CONFIG_FSFS_CACHE_NS "fsfs-cache-namespace"
+
+/* Note to maintainers: if you add further SVN_FS_CONFIG_FSFS_CACHE_* knobs,
+ update fs_fs.c:verify_as_revision_before_current_plus_plus(). */
+
+/* See also svn_fs_type(). */
+/** @since New in 1.1. */
+#define SVN_FS_CONFIG_FS_TYPE "fs-type"
+/** @since New in 1.1. */
+#define SVN_FS_TYPE_BDB "bdb"
+/** @since New in 1.1. */
+#define SVN_FS_TYPE_FSFS "fsfs"
+
+/** Create repository format compatible with Subversion versions
+ * earlier than 1.4.
+ *
+ * @since New in 1.4.
+ */
+#define SVN_FS_CONFIG_PRE_1_4_COMPATIBLE "pre-1.4-compatible"
+
+/** Create repository format compatible with Subversion versions
+ * earlier than 1.5.
+ *
+ * @since New in 1.5.
+ */
+#define SVN_FS_CONFIG_PRE_1_5_COMPATIBLE "pre-1.5-compatible"
+
+/** Create repository format compatible with Subversion versions
+ * earlier than 1.6.
+ *
+ * @since New in 1.6.
+ */
+#define SVN_FS_CONFIG_PRE_1_6_COMPATIBLE "pre-1.6-compatible"
+
+/** Create repository format compatible with Subversion versions
+ * earlier than 1.8.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_FS_CONFIG_PRE_1_8_COMPATIBLE "pre-1.8-compatible"
+/** @} */
+
+
+/**
+ * Callers should invoke this function to initialize global state in
+ * the FS library before creating FS objects. If this function is
+ * invoked, no FS objects may be created in another thread at the same
+ * time as this invocation, and the provided @a pool must last longer
+ * than any FS object created subsequently.
+ *
+ * If this function is not called, the FS library will make a best
+ * effort to bootstrap a mutex for protecting data common to FS
+ * objects; however, there is a small window of failure. Also, a
+ * small amount of data will be leaked if the Subversion FS library is
+ * dynamically unloaded, and using the bdb FS can potentially segfault
+ * or invoke other undefined behavior if this function is not called
+ * with an appropriate pool (such as the pool the module was loaded into)
+ * when loaded dynamically.
+ *
+ * If this function is called multiple times before the pool passed to
+ * the first call is destroyed or cleared, the later calls will have
+ * no effect.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_fs_initialize(apr_pool_t *pool);
+
+
+/** The type of a warning callback function. @a baton is the value specified
+ * in the call to svn_fs_set_warning_func(); the filesystem passes it through
+ * to the callback. @a err contains the warning message.
+ *
+ * The callback function should not clear the error that is passed to it;
+ * its caller should do that.
+ */
+typedef void (*svn_fs_warning_callback_t)(void *baton, svn_error_t *err);
+
+
+/** Provide a callback function, @a warning, that @a fs should use to
+ * report (non-fatal) errors. To print an error, the filesystem will call
+ * @a warning, passing it @a warning_baton and the error.
+ *
+ * By default, this is set to a function that will crash the process.
+ * Dumping to @c stderr or <tt>/dev/tty</tt> is not acceptable default
+ * behavior for server processes, since those may both be equivalent to
+ * <tt>/dev/null</tt>.
+ */
+void
+svn_fs_set_warning_func(svn_fs_t *fs,
+ svn_fs_warning_callback_t warning,
+ void *warning_baton);
+
+
+
+/**
+ * Create a new, empty Subversion filesystem, stored in the directory
+ * @a path, and return a pointer to it in @a *fs_p. @a path must not
+ * currently exist, but its parent must exist. If @a fs_config is not
+ * @c NULL, the options it contains modify the behavior of the
+ * filesystem. The interpretation of @a fs_config is specific to the
+ * filesystem back-end. The new filesystem may be closed by
+ * destroying @a pool.
+ *
+ * @note The lifetime of @a fs_config must not be shorter than @a
+ * pool's. It's a good idea to allocate @a fs_config from @a pool or
+ * one of its ancestors.
+ *
+ * If @a fs_config contains a value for #SVN_FS_CONFIG_FS_TYPE, that
+ * value determines the filesystem type for the new filesystem.
+ * Currently defined values are:
+ *
+ * SVN_FS_TYPE_BDB Berkeley-DB implementation
+ * SVN_FS_TYPE_FSFS Native-filesystem implementation
+ *
+ * If @a fs_config is @c NULL or does not contain a value for
+ * #SVN_FS_CONFIG_FS_TYPE then the default filesystem type will be used.
+ * This will typically be BDB for version 1.1 and FSFS for later versions,
+ * though the caller should not rely upon any particular default if they
+ * wish to ensure that a filesystem of a specific type is created.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_fs_create(svn_fs_t **fs_p,
+ const char *path,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool);
+
+/**
+ * Open a Subversion filesystem located in the directory @a path, and
+ * return a pointer to it in @a *fs_p. If @a fs_config is not @c
+ * NULL, the options it contains modify the behavior of the
+ * filesystem. The interpretation of @a fs_config is specific to the
+ * filesystem back-end. The opened filesystem may be closed by
+ * destroying @a pool.
+ *
+ * @note The lifetime of @a fs_config must not be shorter than @a
+ * pool's. It's a good idea to allocate @a fs_config from @a pool or
+ * one of its ancestors.
+ *
+ * Only one thread may operate on any given filesystem object at once.
+ * Two threads may access the same filesystem simultaneously only if
+ * they open separate filesystem objects.
+ *
+ * @note You probably don't want to use this directly. Take a look at
+ * svn_repos_open2() instead.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_fs_open(svn_fs_t **fs_p,
+ const char *path,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool);
+
+/**
+ * Upgrade the Subversion filesystem located in the directory @a path
+ * to the latest version supported by this library. Return
+ * #SVN_ERR_FS_UNSUPPORTED_UPGRADE and make no changes to the
+ * filesystem if the requested upgrade is not supported. Use @a pool
+ * for necessary allocations.
+ *
+ * @note You probably don't want to use this directly. Take a look at
+ * svn_repos_upgrade() instead.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_fs_upgrade(const char *path,
+ apr_pool_t *pool);
+
+/**
+ * Callback function type for progress notification.
+ *
+ * @a revision is the number of the revision currently begin processed,
+ * #SVN_INVALID_REVNUM if the current stage is not linked to any specific
+ * revision. @a baton is the callback baton.
+ *
+ * @since New in 1.8.
+ */
+typedef void (*svn_fs_progress_notify_func_t)(svn_revnum_t revision,
+ void *baton,
+ apr_pool_t *pool);
+
+/**
+ * Return, in @a *fs_type, a string identifying the back-end type of
+ * the Subversion filesystem located in @a path. Allocate @a *fs_type
+ * in @a pool.
+ *
+ * The string should be equal to one of the @c SVN_FS_TYPE_* defined
+ * constants, unless the filesystem is a new back-end type added in
+ * a later version of Subversion.
+ *
+ * In general, the type should make no difference in the filesystem's
+ * semantics, but there are a few situations (such as backups) where
+ * it might matter.
+ *
+ * @since New in 1.3.
+ */
+svn_error_t *
+svn_fs_type(const char **fs_type,
+ const char *path,
+ apr_pool_t *pool);
+
+/**
+ * Return the path to @a fs's repository, allocated in @a pool.
+ * @note This is just what was passed to svn_fs_create() or
+ * svn_fs_open() -- might be absolute, might not.
+ *
+ * @since New in 1.1.
+ */
+const char *
+svn_fs_path(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/**
+ * Return a shallow copy of the configuration parameters used to open
+ * @a fs, allocated in @a pool. It may be @c NULL. The contents of the
+ * hash contents remains valid only for @a fs's lifetime.
+ *
+ * @note This is just what was passed to svn_fs_create() or svn_fs_open().
+ * You may not modify it.
+ *
+ * @since New in 1.8.
+ */
+apr_hash_t *
+svn_fs_config(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/**
+ * Delete the filesystem at @a path.
+ *
+ * @note: Deleting a filesystem that has an open svn_fs_t is not
+ * supported. Clear/destroy all pools used to create/open @a path.
+ * See issue 4264.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_fs_delete_fs(const char *path,
+ apr_pool_t *pool);
+
+/**
+ * Copy a possibly live Subversion filesystem from @a src_path to
+ * @a dest_path. If @a clean is @c TRUE, perform cleanup on the
+ * source filesystem as part of the copy operation; currently, this
+ * means deleting copied, unused logfiles for a Berkeley DB source
+ * filesystem.
+ *
+ * If @a incremental is TRUE, make an effort to avoid re-copying
+ * information already present in the destination where possible. If
+ * incremental hotcopy is not implemented, raise
+ * #SVN_ERR_UNSUPPORTED_FEATURE.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_fs_hotcopy2(const char *src_path,
+ const char *dest_path,
+ svn_boolean_t clean,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Like svn_fs_hotcopy2(), but with @a incremental always passed as @c
+ * TRUE and without cancellation support.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * @since New in 1.1.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_hotcopy(const char *src_path,
+ const char *dest_path,
+ svn_boolean_t clean,
+ apr_pool_t *pool);
+
+/** Perform any necessary non-catastrophic recovery on the Subversion
+ * filesystem located at @a path.
+ *
+ * If @a cancel_func is not @c NULL, it is called periodically with
+ * @a cancel_baton as argument to see if the client wishes to cancel
+ * recovery. BDB filesystems do not currently support cancellation.
+ *
+ * Do any necessary allocation within @a pool.
+ *
+ * For FSFS filesystems, recovery is currently limited to recreating
+ * the db/current file, and does not require exclusive access.
+ *
+ * For BDB filesystems, recovery requires exclusive access, and is
+ * described in detail below.
+ *
+ * After an unexpected server exit, due to a server crash or a system
+ * crash, a Subversion filesystem based on Berkeley DB needs to run
+ * recovery procedures to bring the database back into a consistent
+ * state and release any locks that were held by the deceased process.
+ * The recovery procedures require exclusive access to the database
+ * --- while they execute, no other process or thread may access the
+ * database.
+ *
+ * In a server with multiple worker processes, like Apache, if a
+ * worker process accessing the filesystem dies, you must stop the
+ * other worker processes, and run recovery. Then, the other worker
+ * processes can re-open the database and resume work.
+ *
+ * If the server exited cleanly, there is no need to run recovery, but
+ * there is no harm in it, either, and it take very little time. So
+ * it's a fine idea to run recovery when the server process starts,
+ * before it begins handling any requests.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_fs_recover(const char *path,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Callback for svn_fs_freeze().
+ *
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_fs_freeze_func_t)(void *baton, apr_pool_t *pool);
+
+/**
+ * Take an exclusive lock on @a fs to prevent commits and then invoke
+ * @a freeze_func passing @a freeze_baton.
+ *
+ * @note The BDB backend doesn't implement this feature so most
+ * callers should not call this function directly but should use the
+ * higher level svn_repos_freeze() instead.
+ *
+ * @see svn_repos_freeze()
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_fs_freeze(svn_fs_t *fs,
+ svn_fs_freeze_func_t freeze_func,
+ void *freeze_baton,
+ apr_pool_t *pool);
+
+
+/** Subversion filesystems based on Berkeley DB.
+ *
+ * The following functions are specific to Berkeley DB filesystems.
+ *
+ * @defgroup svn_fs_bdb Berkeley DB filesystems
+ * @{
+ */
+
+/** Register an error handling function for Berkeley DB error messages.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ *
+ * Despite being first declared deprecated in Subversion 1.3, this API
+ * is redundant in versions 1.1 and 1.2 as well.
+ *
+ * Berkeley DB's error codes are seldom sufficiently informative to allow
+ * adequate troubleshooting. Berkeley DB provides extra messages through
+ * a callback function - if an error occurs, the @a handler will be called
+ * with two strings: an error message prefix, which will be zero, and
+ * an error message. @a handler might print it out, log it somewhere,
+ * etc.
+ *
+ * Subversion 1.1 and later install their own handler internally, and
+ * wrap the messages from Berkeley DB into the standard svn_error_t object,
+ * making any information gained through this interface redundant.
+ *
+ * It is only worth using this function if your program will be used
+ * with Subversion 1.0.
+ *
+ * This function connects to the Berkeley DB @c DBENV->set_errcall interface.
+ * Since that interface supports only a single callback, Subversion's internal
+ * callback is registered with Berkeley DB, and will forward notifications to
+ * a user provided callback after performing its own processing.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_set_berkeley_errcall(svn_fs_t *fs,
+ void (*handler)(const char *errpfx,
+ char *msg));
+
+/** Set @a *logfiles to an array of <tt>const char *</tt> log file names
+ * of Berkeley DB-based Subversion filesystem.
+ *
+ * If @a only_unused is @c TRUE, set @a *logfiles to an array which
+ * contains only the names of Berkeley DB log files no longer in use
+ * by the filesystem. Otherwise, all log files (used and unused) are
+ * returned.
+
+ * This function wraps the Berkeley DB 'log_archive' function
+ * called by the db_archive binary. Repository administrators may
+ * want to run this function periodically and delete the unused log
+ * files, as a way of reclaiming disk space.
+ */
+svn_error_t *
+svn_fs_berkeley_logfiles(apr_array_header_t **logfiles,
+ const char *path,
+ svn_boolean_t only_unused,
+ apr_pool_t *pool);
+
+
+/**
+ * The following functions are similar to their generic counterparts.
+ *
+ * In Subversion 1.2 and earlier, they only work on Berkeley DB filesystems.
+ * In Subversion 1.3 and later, they perform largely as aliases for their
+ * generic counterparts (with the exception of recover, which only gained
+ * a generic counterpart in 1.5).
+ *
+ * @defgroup svn_fs_bdb_deprecated Berkeley DB filesystem compatibility
+ * @{
+ */
+
+/** @deprecated Provided for backward compatibility with the 1.0 API. */
+SVN_DEPRECATED
+svn_fs_t *
+svn_fs_new(apr_hash_t *fs_config,
+ apr_pool_t *pool);
+
+/** @deprecated Provided for backward compatibility with the 1.0 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_create_berkeley(svn_fs_t *fs,
+ const char *path);
+
+/** @deprecated Provided for backward compatibility with the 1.0 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_open_berkeley(svn_fs_t *fs,
+ const char *path);
+
+/** @deprecated Provided for backward compatibility with the 1.0 API. */
+SVN_DEPRECATED
+const char *
+svn_fs_berkeley_path(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/** @deprecated Provided for backward compatibility with the 1.0 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_delete_berkeley(const char *path,
+ apr_pool_t *pool);
+
+/** @deprecated Provided for backward compatibility with the 1.0 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_hotcopy_berkeley(const char *src_path,
+ const char *dest_path,
+ svn_boolean_t clean_logs,
+ apr_pool_t *pool);
+
+/** @deprecated Provided for backward compatibility with the 1.4 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_berkeley_recover(const char *path,
+ apr_pool_t *pool);
+/** @} */
+
+/** @} */
+
+
+/** Filesystem Access Contexts.
+ *
+ * @since New in 1.2.
+ *
+ * At certain times, filesystem functions need access to temporary
+ * user data. For example, which user is changing a file? If the
+ * file is locked, has an appropriate lock-token been supplied?
+ *
+ * This temporary user data is stored in an "access context" object,
+ * and the access context is then connected to the filesystem object.
+ * Whenever a filesystem function requires information, it can pull
+ * things out of the context as needed.
+ *
+ * @defgroup svn_fs_access_ctx Filesystem access contexts
+ * @{
+ */
+
+/** An opaque object representing temporary user data. */
+typedef struct svn_fs_access_t svn_fs_access_t;
+
+
+/** Set @a *access_ctx to a new #svn_fs_access_t object representing
+ * @a username, allocated in @a pool. @a username is presumed to
+ * have been authenticated by the caller.
+ *
+ * Make a deep copy of @a username.
+ */
+svn_error_t *
+svn_fs_create_access(svn_fs_access_t **access_ctx,
+ const char *username,
+ apr_pool_t *pool);
+
+
+/** Associate @a access_ctx with an open @a fs.
+ *
+ * This function can be run multiple times on the same open
+ * filesystem, in order to change the filesystem access context for
+ * different filesystem operations. Pass a NULL value for @a
+ * access_ctx to disassociate the current access context from the
+ * filesystem.
+ */
+svn_error_t *
+svn_fs_set_access(svn_fs_t *fs,
+ svn_fs_access_t *access_ctx);
+
+
+/** Set @a *access_ctx to the current @a fs access context, or NULL if
+ * there is no current fs access context.
+ */
+svn_error_t *
+svn_fs_get_access(svn_fs_access_t **access_ctx,
+ svn_fs_t *fs);
+
+
+/** Accessors for the access context: */
+
+/** Set @a *username to the name represented by @a access_ctx. */
+svn_error_t *
+svn_fs_access_get_username(const char **username,
+ svn_fs_access_t *access_ctx);
+
+
+/** Push a lock-token @a token associated with path @a path into the
+ * context @a access_ctx. The context remembers all tokens it
+ * receives, and makes them available to fs functions. The token and
+ * path are not duplicated into @a access_ctx's pool; make sure the
+ * token's lifetime is at least as long as @a access_ctx.
+ *
+ * @since New in 1.6. */
+svn_error_t *
+svn_fs_access_add_lock_token2(svn_fs_access_t *access_ctx,
+ const char *path,
+ const char *token);
+
+/**
+ * Same as svn_fs_access_add_lock_token2(), but with @a path set to value 1.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_access_add_lock_token(svn_fs_access_t *access_ctx,
+ const char *token);
+
+/** @} */
+
+
+/** Filesystem Nodes and Node-Revisions.
+ *
+ * In a Subversion filesystem, a `node' corresponds roughly to an
+ * `inode' in a Unix filesystem:
+ * - A node is either a file or a directory.
+ * - A node's contents change over time.
+ * - When you change a node's contents, it's still the same node; it's
+ * just been changed. So a node's identity isn't bound to a specific
+ * set of contents.
+ * - If you rename a node, it's still the same node, just under a
+ * different name. So a node's identity isn't bound to a particular
+ * filename.
+ *
+ * A `node revision' refers to one particular version of a node's contents,
+ * that existed over a specific period of time (one or more repository
+ * revisions). Changing a node's contents always creates a new revision of
+ * that node, which is to say creates a new `node revision'. Once created,
+ * a node revision's contents never change.
+ *
+ * When we create a node, its initial contents are the initial revision of
+ * the node. As users make changes to the node over time, we create new
+ * revisions of that same node. When a user commits a change that deletes
+ * a file from the filesystem, we don't delete the node, or any revision
+ * of it --- those stick around to allow us to recreate prior revisions of
+ * the filesystem. Instead, we just remove the reference to the node
+ * from the directory.
+ *
+ * Each node revision is a part of exactly one node, and appears only once
+ * in the history of that node. It is uniquely identified by a node
+ * revision id, #svn_fs_id_t. Its node revision id also identifies which
+ * node it is a part of.
+ *
+ * @note: Often when we talk about `the node' within the context of a single
+ * revision (or transaction), we implicitly mean `the node as it appears in
+ * this revision (or transaction)', or in other words `the node revision'.
+ *
+ * @note: Commonly, a node revision will have the same content as some other
+ * node revisions in the same node and in different nodes. The FS libraries
+ * allow different node revisions to share the same data without storing a
+ * separate copy of the data.
+ *
+ * @defgroup svn_fs_nodes Filesystem nodes
+ * @{
+ */
+
+/** An object representing a node-revision id. */
+typedef struct svn_fs_id_t svn_fs_id_t;
+
+
+/** Return -1, 0, or 1 if node revisions @a a and @a b are respectively
+ * unrelated, equivalent, or otherwise related (part of the same node).
+ */
+int
+svn_fs_compare_ids(const svn_fs_id_t *a,
+ const svn_fs_id_t *b);
+
+
+
+/** Return TRUE if node revisions @a id1 and @a id2 are related (part of the
+ * same node), else return FALSE.
+ */
+svn_boolean_t
+svn_fs_check_related(const svn_fs_id_t *id1,
+ const svn_fs_id_t *id2);
+
+
+/**
+ * @note This function is not guaranteed to work with all filesystem
+ * types. There is currently no un-deprecated equivalent; contact the
+ * Subversion developers if you have a need for it.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_fs_id_t *
+svn_fs_parse_id(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool);
+
+
+/** Return a Subversion string containing the unparsed form of the
+ * node revision id @a id. Allocate the string containing the
+ * unparsed form in @a pool.
+ */
+svn_string_t *
+svn_fs_unparse_id(const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/** Filesystem Transactions.
+ *
+ * To make a change to a Subversion filesystem:
+ * - Create a transaction object, using svn_fs_begin_txn().
+ * - Call svn_fs_txn_root(), to get the transaction's root directory.
+ * - Make whatever changes you like in that tree.
+ * - Commit the transaction, using svn_fs_commit_txn().
+ *
+ * The filesystem implementation guarantees that your commit will
+ * either:
+ * - succeed completely, so that all of the changes are committed to
+ * create a new revision of the filesystem, or
+ * - fail completely, leaving the filesystem unchanged.
+ *
+ * Until you commit the transaction, any changes you make are
+ * invisible. Only when your commit succeeds do they become visible
+ * to the outside world, as a new revision of the filesystem.
+ *
+ * If you begin a transaction, and then decide you don't want to make
+ * the change after all (say, because your net connection with the
+ * client disappeared before the change was complete), you can call
+ * svn_fs_abort_txn(), to cancel the entire transaction; this
+ * leaves the filesystem unchanged.
+ *
+ * The only way to change the contents of files or directories, or
+ * their properties, is by making a transaction and creating a new
+ * revision, as described above. Once a revision has been committed, it
+ * never changes again; the filesystem interface provides no means to
+ * go back and edit the contents of an old revision. Once history has
+ * been recorded, it is set in stone. Clients depend on this property
+ * to do updates and commits reliably; proxies depend on this property
+ * to cache changes accurately; and so on.
+ *
+ * There are two kinds of nodes in the filesystem: mutable, and
+ * immutable. Revisions in the filesystem consist entirely of
+ * immutable nodes, whose contents never change. A transaction in
+ * progress, which the user is still constructing, uses mutable nodes
+ * for those nodes which have been changed so far, and refers to
+ * immutable nodes from existing revisions for portions of the tree
+ * which haven't been changed yet in that transaction.
+ *
+ * Immutable nodes, as part of revisions, never refer to mutable
+ * nodes, which are part of uncommitted transactions. Mutable nodes
+ * may refer to immutable nodes, or other mutable nodes.
+ *
+ * Note that the terms "immutable" and "mutable" describe whether or
+ * not the nodes have been changed as part of a transaction --- not
+ * the permissions on the nodes they refer to. Even if you aren't
+ * authorized to modify the filesystem's root directory, you might be
+ * authorized to change some descendant of the root; doing so would
+ * create a new mutable copy of the root directory. Mutability refers
+ * to the role of the node: part of an existing revision, or part of a
+ * new one. This is independent of your authorization to make changes
+ * to a given node.
+ *
+ * Transactions are actually persistent objects, stored in the
+ * database. You can open a filesystem, begin a transaction, and
+ * close the filesystem, and then a separate process could open the
+ * filesystem, pick up the same transaction, and continue work on it.
+ * When a transaction is successfully committed, it is removed from
+ * the database.
+ *
+ * Every transaction is assigned a name. You can open a transaction
+ * by name, and resume work on it, or find out the name of a
+ * transaction you already have open. You can also list all the
+ * transactions currently present in the database.
+ *
+ * You may assign properties to transactions; these are name/value
+ * pairs. When you commit a transaction, all of its properties become
+ * unversioned revision properties of the new revision. (There is one
+ * exception: the svn:date property will be automatically set on new
+ * transactions to the date that the transaction was created, and will
+ * be overwritten when the transaction is committed by the current
+ * time; changes to a transaction's svn:date property will not affect
+ * its committed value.)
+ *
+ * Transaction names are guaranteed to contain only letters (upper-
+ * and lower-case), digits, `-', and `.', from the ASCII character
+ * set.
+ *
+ * The Subversion filesystem will make a best effort to not reuse
+ * transaction names. The Berkeley DB backend generates transaction
+ * names using a sequence, or a counter, which is stored in the BDB
+ * database. Each new transaction increments the counter. The
+ * current value of the counter is not serialized into a filesystem
+ * dump file, so dumping and restoring the repository will reset the
+ * sequence and reuse transaction names. The FSFS backend generates a
+ * transaction name using the hostname, process ID and current time in
+ * microseconds since 00:00:00 January 1, 1970 UTC. So it is
+ * extremely unlikely that a transaction name will be reused.
+ *
+ * @defgroup svn_fs_txns Filesystem transactions
+ * @{
+ */
+
+/** The type of a Subversion transaction object. */
+typedef struct svn_fs_txn_t svn_fs_txn_t;
+
+
+/** @defgroup svn_fs_begin_txn2_flags Bitmask flags for svn_fs_begin_txn2()
+ * @since New in 1.2.
+ * @{ */
+
+/** Do on-the-fly out-of-dateness checks. That is, an fs routine may
+ * throw error if a caller tries to edit an out-of-date item in the
+ * transaction.
+ *
+ * @warning ### Not yet implemented.
+ */
+#define SVN_FS_TXN_CHECK_OOD 0x00001
+
+/** Do on-the-fly lock checks. That is, an fs routine may throw error
+ * if a caller tries to edit a locked item without having rights to the lock.
+ */
+#define SVN_FS_TXN_CHECK_LOCKS 0x00002
+
+/** @} */
+
+/**
+ * Begin a new transaction on the filesystem @a fs, based on existing
+ * revision @a rev. Set @a *txn_p to a pointer to the new transaction.
+ * When committed, this transaction will create a new revision.
+ *
+ * Allocate the new transaction in @a pool; when @a pool is freed, the new
+ * transaction will be closed (neither committed nor aborted).
+ *
+ * @a flags determines transaction enforcement behaviors, and is composed
+ * from the constants SVN_FS_TXN_* (#SVN_FS_TXN_CHECK_OOD etc.).
+ *
+ * @note If you're building a txn for committing, you probably
+ * don't want to call this directly. Instead, call
+ * svn_repos_fs_begin_txn_for_commit(), which honors the
+ * repository's hook configurations.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_fs_begin_txn2(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_uint32_t flags,
+ apr_pool_t *pool);
+
+
+/**
+ * Same as svn_fs_begin_txn2(), but with @a flags set to 0.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_begin_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+
+
+/** Commit @a txn.
+ *
+ * @note You usually don't want to call this directly.
+ * Instead, call svn_repos_fs_commit_txn(), which honors the
+ * repository's hook configurations.
+ *
+ * If the transaction conflicts with other changes committed to the
+ * repository, return an #SVN_ERR_FS_CONFLICT error. Otherwise, create
+ * a new filesystem revision containing the changes made in @a txn,
+ * storing that new revision number in @a *new_rev, and return zero.
+ *
+ * If @a conflict_p is non-zero, use it to provide details on any
+ * conflicts encountered merging @a txn with the most recent committed
+ * revisions. If a conflict occurs, set @a *conflict_p to the path of
+ * the conflict in @a txn, allocated within @a pool;
+ * otherwise, set @a *conflict_p to NULL.
+ *
+ * If the commit succeeds, @a txn is invalid.
+ *
+ * If the commit fails for any reason, @a *new_rev is an invalid
+ * revision number, an error other than #SVN_NO_ERROR is returned and
+ * @a txn is still valid; you can make more operations to resolve the
+ * conflict, or call svn_fs_abort_txn() to abort the transaction.
+ *
+ * @note Success or failure of the commit of @a txn is determined by
+ * examining the value of @a *new_rev upon this function's return. If
+ * the value is a valid revision number, the commit was successful,
+ * even though a non-@c NULL function return value may indicate that
+ * something else went wrong in post commit FS processing.
+ *
+ * @note See api-errata/1.8/fs001.txt for information on how this
+ * function was documented in versions prior to 1.8.
+ *
+ * ### need to document this better. there are four combinations of
+ * ### return values:
+ * ### 1) err=NULL. conflict=NULL. new_rev is valid
+ * ### 2) err=SVN_ERR_FS_CONFLICT. conflict is set. new_rev=SVN_INVALID_REVNUM
+ * ### 3) err=!NULL. conflict=NULL. new_rev is valid
+ * ### 4) err=!NULL. conflict=NULL. new_rev=SVN_INVALID_REVNUM
+ * ###
+ * ### some invariants:
+ * ### *conflict_p will be non-NULL IFF SVN_ERR_FS_CONFLICT
+ * ### if *conflict_p is set (and SVN_ERR_FS_CONFLICT), then new_rev
+ * ### will always be SVN_INVALID_REVNUM
+ * ### *conflict_p will always be initialized to NULL, or to a valid
+ * ### conflict string
+ * ### *new_rev will always be initialized to SVN_INVALID_REVNUM, or
+ * ### to a valid, committed revision number
+ */
+svn_error_t *
+svn_fs_commit_txn(const char **conflict_p,
+ svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+
+/** Abort the transaction @a txn. Any changes made in @a txn are
+ * discarded, and the filesystem is left unchanged. Use @a pool for
+ * any necessary allocations.
+ *
+ * @note This function first sets the state of @a txn to "dead", and
+ * then attempts to purge it and any related data from the filesystem.
+ * If some part of the cleanup process fails, @a txn and some portion
+ * of its data may remain in the database after this function returns.
+ * Use svn_fs_purge_txn() to retry the transaction cleanup.
+ */
+svn_error_t *
+svn_fs_abort_txn(svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+
+/** Cleanup the dead transaction in @a fs whose ID is @a txn_id. Use
+ * @a pool for all allocations. If the transaction is not yet dead,
+ * the error #SVN_ERR_FS_TRANSACTION_NOT_DEAD is returned. (The
+ * caller probably forgot to abort the transaction, or the cleanup
+ * step of that abort failed for some reason.)
+ */
+svn_error_t *
+svn_fs_purge_txn(svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+
+/** Set @a *name_p to the name of the transaction @a txn, as a
+ * NULL-terminated string. Allocate the name in @a pool.
+ */
+svn_error_t *
+svn_fs_txn_name(const char **name_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+/** Return @a txn's base revision. */
+svn_revnum_t
+svn_fs_txn_base_revision(svn_fs_txn_t *txn);
+
+
+
+/** Open the transaction named @a name in the filesystem @a fs. Set @a *txn
+ * to the transaction.
+ *
+ * If there is no such transaction, #SVN_ERR_FS_NO_SUCH_TRANSACTION is
+ * the error returned.
+ *
+ * Allocate the new transaction in @a pool; when @a pool is freed, the new
+ * transaction will be closed (neither committed nor aborted).
+ */
+svn_error_t *
+svn_fs_open_txn(svn_fs_txn_t **txn,
+ svn_fs_t *fs,
+ const char *name,
+ apr_pool_t *pool);
+
+
+/** Set @a *names_p to an array of <tt>const char *</tt> ids which are the
+ * names of all the currently active transactions in the filesystem @a fs.
+ * Allocate the array in @a pool.
+ */
+svn_error_t *
+svn_fs_list_transactions(apr_array_header_t **names_p,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Transaction properties */
+
+/** Set @a *value_p to the value of the property named @a propname on
+ * transaction @a txn. If @a txn has no property by that name, set
+ * @a *value_p to zero. Allocate the result in @a pool.
+ */
+svn_error_t *
+svn_fs_txn_prop(svn_string_t **value_p,
+ svn_fs_txn_t *txn,
+ const char *propname,
+ apr_pool_t *pool);
+
+
+/** Set @a *table_p to the entire property list of transaction @a txn, as
+ * an APR hash table allocated in @a pool. The resulting table maps property
+ * names to pointers to #svn_string_t objects containing the property value.
+ */
+svn_error_t *
+svn_fs_txn_proplist(apr_hash_t **table_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+
+/** Change a transactions @a txn's property's value, or add/delete a
+ * property. @a name is the name of the property to change, and @a value
+ * is the new value of the property, or zero if the property should be
+ * removed altogether. Do any necessary temporary allocation in @a pool.
+ */
+svn_error_t *
+svn_fs_change_txn_prop(svn_fs_txn_t *txn,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+
+/** Change, add, and/or delete transaction property values in
+ * transaction @a txn. @a props is an array of <tt>svn_prop_t</tt>
+ * elements. This is equivalent to calling svn_fs_change_txn_prop()
+ * multiple times with the @c name and @c value fields of each
+ * successive <tt>svn_prop_t</tt>, but may be more efficient.
+ * (Properties not mentioned are left alone.) Do any necessary
+ * temporary allocation in @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_fs_change_txn_props(svn_fs_txn_t *txn,
+ const apr_array_header_t *props,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/** Roots.
+ *
+ * An #svn_fs_root_t object represents the root directory of some
+ * revision or transaction in a filesystem. To refer to particular
+ * node or node revision, you provide a root, and a directory path
+ * relative to that root.
+ *
+ * @defgroup svn_fs_roots Filesystem roots
+ * @{
+ */
+
+/** The Filesystem Root object. */
+typedef struct svn_fs_root_t svn_fs_root_t;
+
+
+/** Set @a *root_p to the root directory of revision @a rev in filesystem @a fs.
+ * Allocate @a *root_p in a private subpool of @a pool; the root can be
+ * destroyed earlier than @a pool by calling #svn_fs_close_root.
+ */
+svn_error_t *
+svn_fs_revision_root(svn_fs_root_t **root_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+
+/** Set @a *root_p to the root directory of @a txn. Allocate @a *root_p in a
+ * private subpool of @a pool; the root can be destroyed earlier than @a pool by
+ * calling #svn_fs_close_root.
+ */
+svn_error_t *
+svn_fs_txn_root(svn_fs_root_t **root_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+
+/** Free the root directory @a root; this only needs to be used if you want to
+ * free the memory associated with @a root earlier than the time you destroy
+ * the pool passed to the function that created it (svn_fs_revision_root() or
+ * svn_fs_txn_root()).
+ */
+void
+svn_fs_close_root(svn_fs_root_t *root);
+
+
+/** Return the filesystem to which @a root belongs. */
+svn_fs_t *
+svn_fs_root_fs(svn_fs_root_t *root);
+
+
+/** Return @c TRUE iff @a root is a transaction root. */
+svn_boolean_t
+svn_fs_is_txn_root(svn_fs_root_t *root);
+
+/** Return @c TRUE iff @a root is a revision root. */
+svn_boolean_t
+svn_fs_is_revision_root(svn_fs_root_t *root);
+
+
+/** If @a root is the root of a transaction, return the name of the
+ * transaction, allocated in @a pool; otherwise, return NULL.
+ */
+const char *
+svn_fs_txn_root_name(svn_fs_root_t *root,
+ apr_pool_t *pool);
+
+/** If @a root is the root of a transaction, return the number of the
+ * revision on which is was based when created. Otherwise, return
+ * #SVN_INVALID_REVNUM.
+ *
+ * @since New in 1.5.
+ */
+svn_revnum_t
+svn_fs_txn_root_base_revision(svn_fs_root_t *root);
+
+/** If @a root is the root of a revision, return the revision number.
+ * Otherwise, return #SVN_INVALID_REVNUM.
+ */
+svn_revnum_t
+svn_fs_revision_root_revision(svn_fs_root_t *root);
+
+/** @} */
+
+
+/** Directory entry names and directory paths.
+ *
+ * Here are the rules for directory entry names, and directory paths:
+ *
+ * A directory entry name is a Unicode string encoded in UTF-8, and
+ * may not contain the NULL character (U+0000). The name should be in
+ * Unicode canonical decomposition and ordering. No directory entry
+ * may be named '.', '..', or the empty string. Given a directory
+ * entry name which fails to meet these requirements, a filesystem
+ * function returns an SVN_ERR_FS_PATH_SYNTAX error.
+ *
+ * A directory path is a sequence of zero or more directory entry
+ * names, separated by slash characters (U+002f), and possibly ending
+ * with slash characters. Sequences of two or more consecutive slash
+ * characters are treated as if they were a single slash. If a path
+ * ends with a slash, it refers to the same node it would without the
+ * slash, but that node must be a directory, or else the function
+ * returns an SVN_ERR_FS_NOT_DIRECTORY error.
+ *
+ * A path consisting of the empty string, or a string containing only
+ * slashes, refers to the root directory.
+ *
+ * @defgroup svn_fs_directories Filesystem directories
+ * @{
+ */
+
+
+
+/** The kind of change that occurred on the path. */
+typedef enum svn_fs_path_change_kind_t
+{
+ /** path modified in txn */
+ svn_fs_path_change_modify = 0,
+
+ /** path added in txn */
+ svn_fs_path_change_add,
+
+ /** path removed in txn */
+ svn_fs_path_change_delete,
+
+ /** path removed and re-added in txn */
+ svn_fs_path_change_replace,
+
+ /** ignore all previous change items for path (internal-use only) */
+ svn_fs_path_change_reset
+
+} svn_fs_path_change_kind_t;
+
+/** Change descriptor.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, to preserve binary compatibility, users
+ * should not directly allocate structures of this type.
+ *
+ * @since New in 1.6. */
+typedef struct svn_fs_path_change2_t
+{
+ /** node revision id of changed path */
+ const svn_fs_id_t *node_rev_id;
+
+ /** kind of change */
+ svn_fs_path_change_kind_t change_kind;
+
+ /** were there text mods? */
+ svn_boolean_t text_mod;
+
+ /** were there property mods? */
+ svn_boolean_t prop_mod;
+
+ /** what node kind is the path?
+ (Note: it is legal for this to be #svn_node_unknown.) */
+ svn_node_kind_t node_kind;
+
+ /** Copyfrom revision and path; this is only valid if copyfrom_known
+ * is true. */
+ svn_boolean_t copyfrom_known;
+ svn_revnum_t copyfrom_rev;
+ const char *copyfrom_path;
+
+ /* NOTE! Please update svn_fs_path_change2_create() when adding new
+ fields here. */
+} svn_fs_path_change2_t;
+
+
+/** Similar to #svn_fs_path_change2_t, but without kind and copyfrom
+ * information.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.5 API.
+ */
+
+typedef struct svn_fs_path_change_t
+{
+ /** node revision id of changed path */
+ const svn_fs_id_t *node_rev_id;
+
+ /** kind of change */
+ svn_fs_path_change_kind_t change_kind;
+
+ /** were there text mods? */
+ svn_boolean_t text_mod;
+
+ /** were there property mods? */
+ svn_boolean_t prop_mod;
+
+} svn_fs_path_change_t;
+
+/**
+ * Allocate an #svn_fs_path_change2_t structure in @a pool, initialize and
+ * return it.
+ *
+ * Set the @c node_rev_id field of the created struct to @a node_rev_id, and
+ * @c change_kind to @a change_kind. Set all other fields to their
+ * @c _unknown, @c NULL or invalid value, respectively.
+ *
+ * @since New in 1.6.
+ */
+svn_fs_path_change2_t *
+svn_fs_path_change2_create(const svn_fs_id_t *node_rev_id,
+ svn_fs_path_change_kind_t change_kind,
+ apr_pool_t *pool);
+
+/** Determine what has changed under a @a root.
+ *
+ * Allocate and return a hash @a *changed_paths2_p containing descriptions
+ * of the paths changed under @a root. The hash is keyed with
+ * <tt>const char *</tt> paths, and has #svn_fs_path_change2_t * values.
+ *
+ * Callers can assume that this function takes time proportional to
+ * the amount of data output, and does not need to do tree crawls;
+ * however, it is possible that some of the @c node_kind fields in the
+ * #svn_fs_path_change2_t * values will be #svn_node_unknown or
+ * that and some of the @c copyfrom_known fields will be FALSE.
+ *
+ * Use @a pool for all allocations, including the hash and its values.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_fs_paths_changed2(apr_hash_t **changed_paths2_p,
+ svn_fs_root_t *root,
+ apr_pool_t *pool);
+
+
+/** Same as svn_fs_paths_changed2(), only with #svn_fs_path_change_t * values
+ * in the hash (and thus no kind or copyfrom data).
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_paths_changed(apr_hash_t **changed_paths_p,
+ svn_fs_root_t *root,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/* Operations appropriate to all kinds of nodes. */
+
+/** Set @a *kind_p to the type of node present at @a path under @a
+ * root. If @a path does not exist under @a root, set @a *kind_p to
+ * #svn_node_none. Use @a pool for temporary allocation.
+ */
+svn_error_t *
+svn_fs_check_path(svn_node_kind_t *kind_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** An opaque node history object. */
+typedef struct svn_fs_history_t svn_fs_history_t;
+
+
+/** Set @a *history_p to an opaque node history object which
+ * represents @a path under @a root. @a root must be a revision root.
+ * Use @a pool for all allocations.
+ */
+svn_error_t *
+svn_fs_node_history(svn_fs_history_t **history_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Set @a *prev_history_p to an opaque node history object which
+ * represents the previous (or "next oldest") interesting history
+ * location for the filesystem node represented by @a history, or @c
+ * NULL if no such previous history exists. If @a cross_copies is @c
+ * FALSE, also return @c NULL if stepping backwards in history to @a
+ * *prev_history_p would cross a filesystem copy operation.
+ *
+ * @note If this is the first call to svn_fs_history_prev() for the @a
+ * history object, it could return a history object whose location is
+ * the same as the original. This will happen if the original
+ * location was an interesting one (where the node was modified, or
+ * took place in a copy event). This behavior allows looping callers
+ * to avoid the calling svn_fs_history_location() on the object
+ * returned by svn_fs_node_history(), and instead go ahead and begin
+ * calling svn_fs_history_prev().
+ *
+ * @note This function uses node-id ancestry alone to determine
+ * modifiedness, and therefore does NOT claim that in any of the
+ * returned revisions file contents changed, properties changed,
+ * directory entries lists changed, etc.
+ *
+ * @note The revisions returned for @a path will be older than or
+ * the same age as the revision of that path in @a root. That is, if
+ * @a root is a revision root based on revision X, and @a path was
+ * modified in some revision(s) younger than X, those revisions
+ * younger than X will not be included for @a path. */
+svn_error_t *
+svn_fs_history_prev(svn_fs_history_t **prev_history_p,
+ svn_fs_history_t *history,
+ svn_boolean_t cross_copies,
+ apr_pool_t *pool);
+
+
+/** Set @a *path and @a *revision to the path and revision,
+ * respectively, of the @a history object. Use @a pool for all
+ * allocations.
+ */
+svn_error_t *
+svn_fs_history_location(const char **path,
+ svn_revnum_t *revision,
+ svn_fs_history_t *history,
+ apr_pool_t *pool);
+
+
+/** Set @a *is_dir to @c TRUE iff @a path in @a root is a directory.
+ * Do any necessary temporary allocation in @a pool.
+ */
+svn_error_t *
+svn_fs_is_dir(svn_boolean_t *is_dir,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Set @a *is_file to @c TRUE iff @a path in @a root is a file.
+ * Do any necessary temporary allocation in @a pool.
+ */
+svn_error_t *
+svn_fs_is_file(svn_boolean_t *is_file,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Get the id of a node.
+ *
+ * Set @a *id_p to the node revision ID of @a path in @a root, allocated in
+ * @a pool.
+ *
+ * If @a root is the root of a transaction, keep in mind that other
+ * changes to the transaction can change which node @a path refers to,
+ * and even whether the path exists at all.
+ */
+svn_error_t *
+svn_fs_node_id(const svn_fs_id_t **id_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Set @a *revision to the revision in which @a path under @a root was
+ * created. Use @a pool for any temporary allocations. @a *revision will
+ * be set to #SVN_INVALID_REVNUM for uncommitted nodes (i.e. modified nodes
+ * under a transaction root). Note that the root of an unmodified transaction
+ * is not itself considered to be modified; in that case, return the revision
+ * upon which the transaction was based.
+ */
+svn_error_t *
+svn_fs_node_created_rev(svn_revnum_t *revision,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Set @a *revision to the revision in which the line of history
+ * represented by @a path under @a root originated. Use @a pool for
+ * any temporary allocations. If @a root is a transaction root, @a
+ * *revision will be set to #SVN_INVALID_REVNUM for any nodes newly
+ * added in that transaction (brand new files or directories created
+ * using #svn_fs_make_dir or #svn_fs_make_file).
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_fs_node_origin_rev(svn_revnum_t *revision,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Set @a *created_path to the path at which @a path under @a root was
+ * created. Use @a pool for all allocations. Callers may use this
+ * function in conjunction with svn_fs_node_created_rev() to perform a
+ * reverse lookup of the mapping of (path, revision) -> node-id that
+ * svn_fs_node_id() performs.
+ */
+svn_error_t *
+svn_fs_node_created_path(const char **created_path,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Set @a *value_p to the value of the property named @a propname of
+ * @a path in @a root. If the node has no property by that name, set
+ * @a *value_p to zero. Allocate the result in @a pool.
+ */
+svn_error_t *
+svn_fs_node_prop(svn_string_t **value_p,
+ svn_fs_root_t *root,
+ const char *path,
+ const char *propname,
+ apr_pool_t *pool);
+
+
+/** Set @a *table_p to the entire property list of @a path in @a root,
+ * as an APR hash table allocated in @a pool. The resulting table maps
+ * property names to pointers to #svn_string_t objects containing the
+ * property value.
+ */
+svn_error_t *
+svn_fs_node_proplist(apr_hash_t **table_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Change a node's property's value, or add/delete a property.
+ *
+ * - @a root and @a path indicate the node whose property should change.
+ * @a root must be the root of a transaction, not the root of a revision.
+ * - @a name is the name of the property to change.
+ * - @a value is the new value of the property, or zero if the property should
+ * be removed altogether.
+ * Do any necessary temporary allocation in @a pool.
+ */
+svn_error_t *
+svn_fs_change_node_prop(svn_fs_root_t *root,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+
+/** Determine if the properties of two path/root combinations are different.
+ *
+ * Set @a *changed_p to 1 if the properties at @a path1 under @a root1 differ
+ * from those at @a path2 under @a root2, or set it to 0 if they are the
+ * same. Both paths must exist under their respective roots, and both
+ * roots must be in the same filesystem.
+ */
+svn_error_t *
+svn_fs_props_changed(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool);
+
+
+/** Discover a node's copy ancestry, if any.
+ *
+ * If the node at @a path in @a root was copied from some other node, set
+ * @a *rev_p and @a *path_p to the revision and path (expressed as an
+ * absolute filesystem path) of the other node, allocating @a *path_p
+ * in @a pool.
+ *
+ * Else if there is no copy ancestry for the node, set @a *rev_p to
+ * #SVN_INVALID_REVNUM and @a *path_p to NULL.
+ *
+ * If an error is returned, the values of @a *rev_p and @a *path_p are
+ * undefined, but otherwise, if one of them is set as described above,
+ * you may assume the other is set correspondingly.
+ *
+ * @a root may be a revision root or a transaction root.
+ *
+ * Notes:
+ * - Copy ancestry does not descend. After copying directory D to
+ * E, E will have copy ancestry referring to D, but E's children
+ * may not. See also svn_fs_copy().
+ *
+ * - Copy ancestry *under* a copy is preserved. That is, if you
+ * copy /A/D/G/pi to /A/D/G/pi2, and then copy /A/D/G to /G, then
+ * /G/pi2 will still have copy ancestry pointing to /A/D/G/pi.
+ * We don't know if this is a feature or a bug yet; if it turns
+ * out to be a bug, then the fix is to make svn_fs_copied_from()
+ * observe the following logic, which currently callers may
+ * choose to follow themselves: if node X has copy history, but
+ * its ancestor A also has copy history, then you may ignore X's
+ * history if X's revision-of-origin is earlier than A's --
+ * because that would mean that X's copy history was preserved in
+ * a copy-under-a-copy scenario. If X's revision-of-origin is
+ * the same as A's, then it was copied under A during the same
+ * transaction that created A. (X's revision-of-origin cannot be
+ * greater than A's, if X has copy history.) @todo See how
+ * people like this, it can always be hidden behind the curtain
+ * if necessary.
+ *
+ * - Copy ancestry is not stored as a regular subversion property
+ * because it is not inherited. Copying foo to bar results in a
+ * revision of bar with copy ancestry; but committing a text
+ * change to bar right after that results in a new revision of
+ * bar without copy ancestry.
+ */
+svn_error_t *
+svn_fs_copied_from(svn_revnum_t *rev_p,
+ const char **path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Set @a *root_p and @a *path_p to the revision root and path of the
+ * destination of the most recent copy event that caused @a path to
+ * exist where it does in @a root, or to NULL if no such copy exists.
+ *
+ * @a *path_p might be a parent of @a path, rather than @a path
+ * itself. However, it will always be the deepest relevant path.
+ * That is, if a copy occurs underneath another copy in the same txn,
+ * this function makes sure to set @a *path_p to the longest copy
+ * destination path that is still a parent of or equal to @a path.
+ *
+ * Values returned in @a *root_p and @a *path_p will be allocated
+ * from @a pool.
+ *
+ * @since New in 1.3.
+ */
+svn_error_t *
+svn_fs_closest_copy(svn_fs_root_t **root_p,
+ const char **path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Retrieve mergeinfo for multiple nodes.
+ *
+ * @a *catalog is a catalog for @a paths. It will never be @c NULL,
+ * but may be empty.
+ *
+ * @a root is revision root to use when looking up paths.
+ *
+ * @a paths are the paths you are requesting information for.
+ *
+ * @a inherit indicates whether to retrieve explicit,
+ * explicit-or-inherited, or only inherited mergeinfo.
+ *
+ * If @a adjust_inherited_mergeinfo is @c TRUE, then any inherited
+ * mergeinfo returned in @a *catalog is normalized to represent the
+ * inherited mergeinfo on the path which inherits it. If
+ * @a adjust_inherited_mergeinfo is @c FALSE, then any inherited
+ * mergeinfo is the raw explicit mergeinfo from the nearest parent
+ * of the path with explicit mergeinfo, unadjusted for the path-wise
+ * difference between the path and its parent. This may include
+ * non-inheritable mergeinfo.
+ *
+ * If @a include_descendants is TRUE, then additionally return the
+ * mergeinfo for any descendant of any element of @a paths which has
+ * the #SVN_PROP_MERGEINFO property explicitly set on it. (Note
+ * that inheritance is only taken into account for the elements in @a
+ * paths; descendants of the elements in @a paths which get their
+ * mergeinfo via inheritance are not included in @a *catalog.)
+ *
+ * Allocate @a *catalog in result_pool. Do any necessary temporary
+ * allocations in @a scratch_pool.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_fs_get_mergeinfo2(svn_mergeinfo_catalog_t *catalog,
+ svn_fs_root_t *root,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Same as svn_fs_get_mergeinfo2(), but with @a adjust_inherited_mergeinfo
+ * set always set to @c TRUE and with only one pool.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
+ svn_fs_root_t *root,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool);
+
+/** Merge changes between two nodes into a third node.
+ *
+ * Given nodes @a source and @a target, and a common ancestor @a ancestor,
+ * modify @a target to contain all the changes made between @a ancestor and
+ * @a source, as well as the changes made between @a ancestor and @a target.
+ * @a target_root must be the root of a transaction, not a revision.
+ *
+ * @a source, @a target, and @a ancestor are generally directories; this
+ * function recursively merges the directories' contents. If they are
+ * files, this function simply returns an error whenever @a source,
+ * @a target, and @a ancestor are all distinct node revisions.
+ *
+ * If there are differences between @a ancestor and @a source that conflict
+ * with changes between @a ancestor and @a target, this function returns an
+ * #SVN_ERR_FS_CONFLICT error.
+ *
+ * If the merge is successful, @a target is left in the merged state, and
+ * the base root of @a target's txn is set to the root node of @a source.
+ * If an error is returned (whether for conflict or otherwise), @a target
+ * is left unaffected.
+ *
+ * If @a conflict_p is non-NULL, then: a conflict error sets @a *conflict_p
+ * to the name of the node in @a target which couldn't be merged,
+ * otherwise, success sets @a *conflict_p to NULL.
+ *
+ * Do any necessary temporary allocation in @a pool.
+ */
+svn_error_t *
+svn_fs_merge(const char **conflict_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ svn_fs_root_t *ancestor_root,
+ const char *ancestor_path,
+ apr_pool_t *pool);
+
+
+
+/* Directories. */
+
+
+/** The type of a Subversion directory entry. */
+typedef struct svn_fs_dirent_t
+{
+
+ /** The name of this directory entry. */
+ const char *name;
+
+ /** The node revision ID it names. */
+ const svn_fs_id_t *id;
+
+ /** The node kind. */
+ svn_node_kind_t kind;
+
+} svn_fs_dirent_t;
+
+
+/** Set @a *entries_p to a newly allocated APR hash table containing the
+ * entries of the directory at @a path in @a root. The keys of the table
+ * are entry names, as byte strings, excluding the final NULL
+ * character; the table's values are pointers to #svn_fs_dirent_t
+ * structures. Allocate the table and its contents in @a pool.
+ */
+svn_error_t *
+svn_fs_dir_entries(apr_hash_t **entries_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Create a new directory named @a path in @a root. The new directory has
+ * no entries, and no properties. @a root must be the root of a transaction,
+ * not a revision.
+ *
+ * Do any necessary temporary allocation in @a pool.
+ */
+svn_error_t *
+svn_fs_make_dir(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Delete the node named @a path in @a root. If the node being deleted is
+ * a directory, its contents will be deleted recursively. @a root must be
+ * the root of a transaction, not of a revision. Use @a pool for
+ * temporary allocation.
+ *
+ * If return #SVN_ERR_FS_NO_SUCH_ENTRY, then the basename of @a path is
+ * missing from its parent, that is, the final target of the deletion
+ * is missing.
+ *
+ * Attempting to remove the root dir also results in an error,
+ * #SVN_ERR_FS_ROOT_DIR, even if the dir is empty.
+ */
+svn_error_t *
+svn_fs_delete(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Create a copy of @a from_path in @a from_root named @a to_path in
+ * @a to_root. If @a from_path in @a from_root is a directory, copy the
+ * tree it refers to recursively.
+ *
+ * The copy will remember its source; use svn_fs_copied_from() to
+ * access this information.
+ *
+ * @a to_root must be the root of a transaction; @a from_root must be the
+ * root of a revision. (Requiring @a from_root to be the root of a
+ * revision makes the implementation trivial: there is no detectable
+ * difference (modulo node revision ID's) between copying @a from and
+ * simply adding a reference to it. So the operation takes place in
+ * constant time. However, there's no reason not to extend this to
+ * mutable nodes --- it's just more code.) Further, @a to_root and @a
+ * from_root must represent the same filesystem.
+ *
+ * @note To do a copy without preserving copy history, use
+ * svn_fs_revision_link().
+ *
+ * Do any necessary temporary allocation in @a pool.
+ */
+svn_error_t *
+svn_fs_copy(svn_fs_root_t *from_root,
+ const char *from_path,
+ svn_fs_root_t *to_root,
+ const char *to_path,
+ apr_pool_t *pool);
+
+
+/** Like svn_fs_copy(), but doesn't record copy history, and preserves
+ * the PATH. You cannot use svn_fs_copied_from() later to find out
+ * where this copy came from.
+ *
+ * Use svn_fs_revision_link() in situations where you don't care
+ * about the copy history, and where @a to_path and @a from_path are
+ * the same, because it is cheaper than svn_fs_copy().
+ */
+svn_error_t *
+svn_fs_revision_link(svn_fs_root_t *from_root,
+ svn_fs_root_t *to_root,
+ const char *path,
+ apr_pool_t *pool);
+
+/* Files. */
+
+/** Set @a *length_p to the length of the file @a path in @a root, in bytes.
+ * Do any necessary temporary allocation in @a pool.
+ */
+svn_error_t *
+svn_fs_file_length(svn_filesize_t *length_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Set @a *checksum to the checksum of type @a kind for the file @a path.
+ * @a *checksum will be allocated out of @a pool, which will also be used
+ * for temporary allocations.
+ *
+ * If the filesystem does not have a prerecorded checksum of @a kind for
+ * @a path, and @a force is not TRUE, do not calculate a checksum
+ * dynamically, just put NULL into @a checksum. (By convention, the NULL
+ * checksum is considered to match any checksum.)
+ *
+ * Notes:
+ *
+ * You might wonder, why do we only provide this interface for file
+ * contents, and not for properties or directories?
+ *
+ * The answer is that property lists and directory entry lists are
+ * essentially data structures, not text. We serialize them for
+ * transmission, but there is no guarantee that the consumer will
+ * parse them into the same form, or even the same order, as the
+ * producer. It's difficult to find a checksumming method that
+ * reaches the same result given such variation in input. (I suppose
+ * we could calculate an independent MD5 sum for each propname and
+ * value, and XOR them together; same with directory entry names.
+ * Maybe that's the solution?) Anyway, for now we punt. The most
+ * important data, and the only data that goes through svndiff
+ * processing, is file contents, so that's what we provide
+ * checksumming for.
+ *
+ * Internally, of course, the filesystem checksums everything, because
+ * it has access to the lowest level storage forms: strings behind
+ * representations.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_fs_file_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t kind,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_boolean_t force,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_fs_file_checksum(), only always put the MD5 checksum of file
+ * @a path into @a digest, which should point to @c APR_MD5_DIGESTSIZE bytes
+ * of storage. If the checksum doesn't exist, put all 0's into @a digest.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_file_md5_checksum(unsigned char digest[],
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Set @a *contents to a readable generic stream that will yield the
+ * contents of the file @a path in @a root. Allocate the stream in
+ * @a pool. You can only use @a *contents for as long as the underlying
+ * filesystem is open. If @a path is not a file, return
+ * #SVN_ERR_FS_NOT_FILE.
+ *
+ * If @a root is the root of a transaction, it is possible that the
+ * contents of the file @a path will change between calls to
+ * svn_fs_file_contents(). In that case, the result of reading from
+ * @a *contents is undefined.
+ *
+ * ### @todo kff: I am worried about lifetime issues with this pool vs
+ * the trail created farther down the call stack. Trace this function
+ * to investigate...
+ */
+svn_error_t *
+svn_fs_file_contents(svn_stream_t **contents,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+/**
+ * Callback function type used with svn_fs_try_process_file_contents()
+ * that delivers the immutable, non-NULL @a contents of @a len bytes.
+ * @a baton is an implementation-specific closure.
+ *
+ * Use @a scratch_pool for allocations.
+ *
+ * @since New in 1.8.
+ */
+typedef svn_error_t *
+(*svn_fs_process_contents_func_t)(const unsigned char *contents,
+ apr_size_t len,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+/** Efficiently deliver the contents of the file @a path in @a root
+ * via @a processor (with @a baton), setting @a *success to @c TRUE
+ * upon doing so. Use @a pool for allocations.
+ *
+ * This function is intended to support zero copy data processing. It may
+ * not be implemented for all data backends or not applicable for certain
+ * content. In that case, @a *success will always be @c FALSE. Also, this
+ * is a best-effort function which means that there is no guarantee that
+ * @a processor gets called at all for some content.
+ *
+ * @note @a processor is expected to be relatively short function with
+ * at most O(content size) runtime.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_fs_try_process_file_contents(svn_boolean_t *success,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool);
+
+/** Create a new file named @a path in @a root. The file's initial contents
+ * are the empty string, and it has no properties. @a root must be the
+ * root of a transaction, not a revision.
+ *
+ * Do any necessary temporary allocation in @a pool.
+ */
+svn_error_t *
+svn_fs_make_file(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Apply a text delta to the file @a path in @a root. @a root must be the
+ * root of a transaction, not a revision.
+ *
+ * Set @a *contents_p to a function ready to receive text delta windows
+ * describing how to change the file's contents, relative to its
+ * current contents. Set @a *contents_baton_p to a baton to pass to
+ * @a *contents_p.
+ *
+ * If @a path does not exist in @a root, return an error. (You cannot use
+ * this routine to create new files; use svn_fs_make_file() to create
+ * an empty file first.)
+ *
+ * @a base_checksum is the hex MD5 digest for the base text against
+ * which the delta is to be applied; it is ignored if NULL, and may be
+ * ignored even if not NULL. If it is not ignored, it must match the
+ * checksum of the base text against which svndiff data is being
+ * applied; if not, svn_fs_apply_textdelta() or the @a *contents_p call
+ * which detects the mismatch will return the error
+ * #SVN_ERR_CHECKSUM_MISMATCH (if there is no base text, there may
+ * still be an error if @a base_checksum is neither NULL nor the
+ * checksum of the empty string).
+ *
+ * @a result_checksum is the hex MD5 digest for the fulltext that
+ * results from this delta application. It is ignored if NULL, but if
+ * not NULL, it must match the checksum of the result; if it does not,
+ * then the @a *contents_p call which detects the mismatch will return
+ * the error #SVN_ERR_CHECKSUM_MISMATCH.
+ *
+ * The caller must send all delta windows including the terminating
+ * NULL window to @a *contents_p before making further changes to the
+ * transaction.
+ *
+ * Do temporary allocation in @a pool.
+ */
+svn_error_t *
+svn_fs_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
+ void **contents_baton_p,
+ svn_fs_root_t *root,
+ const char *path,
+ const char *base_checksum,
+ const char *result_checksum,
+ apr_pool_t *pool);
+
+
+/** Write data directly to the file @a path in @a root. @a root must be the
+ * root of a transaction, not a revision.
+ *
+ * Set @a *contents_p to a stream ready to receive full textual data.
+ * When the caller closes this stream, the data replaces the previous
+ * contents of the file. The caller must write all file data and close
+ * the stream before making further changes to the transaction.
+ *
+ * If @a path does not exist in @a root, return an error. (You cannot use
+ * this routine to create new files; use svn_fs_make_file() to create
+ * an empty file first.)
+ *
+ * @a result_checksum is the hex MD5 digest for the final fulltext
+ * written to the stream. It is ignored if NULL, but if not null, it
+ * must match the checksum of the result; if it does not, then the @a
+ * *contents_p call which detects the mismatch will return the error
+ * #SVN_ERR_CHECKSUM_MISMATCH.
+ *
+ * Do any necessary temporary allocation in @a pool.
+ *
+ * ### This is like svn_fs_apply_textdelta(), but takes the text
+ * straight. It is currently used only by the loader, see
+ * libsvn_repos/load.c. It should accept a checksum, of course, which
+ * would come from an (optional) header in the dump file. See
+ * http://subversion.tigris.org/issues/show_bug.cgi?id=1102 for more.
+ */
+svn_error_t *
+svn_fs_apply_text(svn_stream_t **contents_p,
+ svn_fs_root_t *root,
+ const char *path,
+ const char *result_checksum,
+ apr_pool_t *pool);
+
+
+/** Check if the contents of two root/path combos have changed.
+ *
+ * Set @a *changed_p to 1 if the contents at @a path1 under @a root1 differ
+ * from those at @a path2 under @a root2, or set it to 0 if they are the
+ * same. Both paths must exist under their respective roots, and both
+ * roots must be in the same filesystem.
+ */
+svn_error_t *
+svn_fs_contents_changed(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool);
+
+
+
+/* Filesystem revisions. */
+
+
+/** Set @a *youngest_p to the number of the youngest revision in filesystem
+ * @a fs. Use @a pool for all temporary allocation.
+ *
+ * The oldest revision in any filesystem is numbered zero.
+ */
+svn_error_t *
+svn_fs_youngest_rev(svn_revnum_t *youngest_p,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+
+/** Provide filesystem @a fs the opportunity to compress storage relating to
+ * associated with @a revision in filesystem @a fs. Use @a pool for all
+ * allocations.
+ *
+ * @note This can be a time-consuming process, depending the breadth
+ * of the changes made in @a revision, and the depth of the history of
+ * those changed paths. This may also be a no op.
+ */
+svn_error_t *
+svn_fs_deltify_revision(svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *pool);
+
+
+/** Set @a *value_p to the value of the property named @a propname on
+ * revision @a rev in the filesystem @a fs. If @a rev has no property by
+ * that name, set @a *value_p to zero. Allocate the result in @a pool.
+ */
+svn_error_t *
+svn_fs_revision_prop(svn_string_t **value_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *propname,
+ apr_pool_t *pool);
+
+
+/** Set @a *table_p to the entire property list of revision @a rev in
+ * filesystem @a fs, as an APR hash table allocated in @a pool. The table
+ * maps <tt>char *</tt> property names to #svn_string_t * values; the names
+ * and values are allocated in @a pool.
+ */
+svn_error_t *
+svn_fs_revision_proplist(apr_hash_t **table_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+
+/** Change a revision's property's value, or add/delete a property.
+ *
+ * - @a fs is a filesystem, and @a rev is the revision in that filesystem
+ * whose property should change.
+ * - @a name is the name of the property to change.
+ * - if @a old_value_p is not @c NULL, then changing the property will fail with
+ * error #SVN_ERR_FS_PROP_BASEVALUE_MISMATCH if the present value of the
+ * property is not @a *old_value_p. (This is an atomic test-and-set).
+ * @a *old_value_p may be @c NULL, representing that the property must be not
+ * already set.
+ * - @a value is the new value of the property, or zero if the property should
+ * be removed altogether.
+ *
+ * Note that revision properties are non-historied --- you can change
+ * them after the revision has been committed. They are not protected
+ * via transactions.
+ *
+ * Do any necessary temporary allocation in @a pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_fs_change_rev_prop2(svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_fs_change_rev_prop2(), but with @a old_value_p passed as
+ * @c NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_change_rev_prop(svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+
+
+/* Computing deltas. */
+
+
+/** Set @a *stream_p to a pointer to a delta stream that will turn the
+ * contents of the file @a source into the contents of the file @a target.
+ * If @a source_root is zero, use a file with zero length as the source.
+ *
+ * This function does not compare the two files' properties.
+ *
+ * Allocate @a *stream_p, and do any necessary temporary allocation, in
+ * @a pool.
+ */
+svn_error_t *
+svn_fs_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ apr_pool_t *pool);
+
+
+
+/* UUID manipulation. */
+
+/** Populate @a *uuid with the UUID associated with @a fs. Allocate
+ @a *uuid in @a pool. */
+svn_error_t *
+svn_fs_get_uuid(svn_fs_t *fs,
+ const char **uuid,
+ apr_pool_t *pool);
+
+
+/** If not @c NULL, associate @a *uuid with @a fs. Otherwise (if @a
+ * uuid is @c NULL), generate a new UUID for @a fs. Use @a pool for
+ * any scratch work.
+ */
+svn_error_t *
+svn_fs_set_uuid(svn_fs_t *fs,
+ const char *uuid,
+ apr_pool_t *pool);
+
+
+/* Non-historical properties. */
+
+/* [[Yes, do tell.]] */
+
+
+
+/** @defgroup svn_fs_locks Filesystem locks
+ * @{
+ * @since New in 1.2. */
+
+/** A lock represents one user's exclusive right to modify a path in a
+ * filesystem. In order to create or destroy a lock, a username must
+ * be associated with the filesystem's access context (see
+ * #svn_fs_access_t).
+ *
+ * When a lock is created, a 'lock-token' is returned. The lock-token
+ * is a unique URI that represents the lock (treated as an opaque
+ * string by the client), and is required to make further use of the
+ * lock (including removal of the lock.) A lock-token can also be
+ * queried to return a svn_lock_t structure that describes the details
+ * of the lock. lock-tokens must not contain any newline character,
+ * mainly due to the serialization for tokens for pre-commit hook.
+ *
+ * Locks are not secret; anyone can view existing locks in a
+ * filesystem. Locks are not omnipotent: they can broken and stolen
+ * by people who don't "own" the lock. (Though admins can tailor a
+ * custom break/steal policy via libsvn_repos pre-lock hook script.)
+ *
+ * Locks can be created with an optional expiration date. If a lock
+ * has an expiration date, then the act of fetching/reading it might
+ * cause it to automatically expire, returning either nothing or an
+ * expiration error (depending on the API).
+ */
+
+
+/** Lock @a path in @a fs, and set @a *lock to a lock
+ * representing the new lock, allocated in @a pool.
+ *
+ * @warning You may prefer to use svn_repos_fs_lock() instead,
+ * which see.
+ *
+ * @a fs must have a username associated with it (see
+ * #svn_fs_access_t), else return #SVN_ERR_FS_NO_USER. Set the
+ * 'owner' field in the new lock to the fs username.
+ *
+ * @a comment is optional: it's either an xml-escapable UTF8 string
+ * which describes the lock, or it is @c NULL.
+ *
+ * @a is_dav_comment describes whether the comment was created by a
+ * generic DAV client; only mod_dav_svn's autoversioning feature needs
+ * to use it. If in doubt, pass 0.
+ *
+ * If path is already locked, then return #SVN_ERR_FS_PATH_ALREADY_LOCKED,
+ * unless @a steal_lock is TRUE, in which case "steal" the existing
+ * lock, even if the FS access-context's username does not match the
+ * current lock's owner: delete the existing lock on @a path, and
+ * create a new one.
+ *
+ * @a token is a lock token such as can be generated using
+ * svn_fs_generate_lock_token() (indicating that the caller wants to
+ * dictate the lock token used), or it is @c NULL (indicating that the
+ * caller wishes to have a new token generated by this function). If
+ * @a token is not @c NULL, and represents an existing lock, then @a
+ * path must match the path associated with that existing lock.
+ *
+ * If @a expiration_date is zero, then create a non-expiring lock.
+ * Else, the lock will expire at @a expiration_date.
+ *
+ * If @a current_rev is a valid revnum, then do an out-of-dateness
+ * check. If the revnum is less than the last-changed-revision of @a
+ * path (or if @a path doesn't exist in HEAD), return
+ * #SVN_ERR_FS_OUT_OF_DATE.
+ *
+ * @note At this time, only files can be locked.
+ */
+svn_error_t *
+svn_fs_lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_revnum_t current_rev,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool);
+
+
+/** Generate a unique lock-token using @a fs. Return in @a *token,
+ * allocated in @a pool.
+ *
+ * This can be used in to populate lock->token before calling
+ * svn_fs_attach_lock().
+ */
+svn_error_t *
+svn_fs_generate_lock_token(const char **token,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+
+/** Remove the lock on @a path represented by @a token in @a fs.
+ *
+ * If @a token doesn't point to a lock, return #SVN_ERR_FS_BAD_LOCK_TOKEN.
+ * If @a token points to an expired lock, return #SVN_ERR_FS_LOCK_EXPIRED.
+ * If @a fs has no username associated with it, return #SVN_ERR_FS_NO_USER
+ * unless @a break_lock is specified.
+ *
+ * If @a token points to a lock, but the username of @a fs's access
+ * context doesn't match the lock's owner, return
+ * #SVN_ERR_FS_LOCK_OWNER_MISMATCH. If @a break_lock is TRUE, however, don't
+ * return error; allow the lock to be "broken" in any case. In the latter
+ * case, @a token shall be @c NULL.
+ *
+ * Use @a pool for temporary allocations.
+ */
+svn_error_t *
+svn_fs_unlock(svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool);
+
+
+/** If @a path is locked in @a fs, set @a *lock to an svn_lock_t which
+ * represents the lock, allocated in @a pool.
+ *
+ * If @a path is not locked, set @a *lock to NULL.
+ */
+svn_error_t *
+svn_fs_get_lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** The type of a lock discovery callback function. @a baton is the
+ * value specified in the call to svn_fs_get_locks(); the filesystem
+ * passes it through to the callback. @a lock is a lock structure.
+ * @a pool is a temporary subpool for use by the callback
+ * implementation -- it is cleared after invocation of the callback.
+ */
+typedef svn_error_t *(*svn_fs_get_locks_callback_t)(void *baton,
+ svn_lock_t *lock,
+ apr_pool_t *pool);
+
+
+/** Report locks on or below @a path in @a fs using the @a
+ * get_locks_func / @a get_locks_baton. Use @a pool for necessary
+ * allocations.
+ *
+ * @a depth limits the reported locks to those associated with paths
+ * within the specified depth of @a path, and must be one of the
+ * following values: #svn_depth_empty, #svn_depth_files,
+ * #svn_depth_immediates, or #svn_depth_infinity.
+ *
+ * If the @a get_locks_func callback implementation returns an error,
+ * lock iteration will terminate and that error will be returned by
+ * this function.
+ *
+ * @note Over the course of this function's invocation, locks might be
+ * added, removed, or modified by concurrent processes. Callers need
+ * to anticipate and gracefully handle the transience of this
+ * information.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_fs_get_locks2(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ apr_pool_t *pool);
+
+/** Similar to svn_fs_get_locks2(), but with @a depth always passed as
+ * svn_depth_infinity, and with the following known problem (which is
+ * not present in svn_fs_get_locks2()):
+ *
+ * @note On Berkeley-DB-backed filesystems in Subversion 1.6 and
+ * prior, the @a get_locks_func callback will be invoked from within a
+ * Berkeley-DB transaction trail. Implementors of the callback are,
+ * as a result, forbidden from calling any svn_fs API functions which
+ * might themselves attempt to start a new Berkeley DB transaction
+ * (which is most of this svn_fs API). Yes, this is a nasty
+ * implementation detail to have to be aware of.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_fs_get_locks(svn_fs_t *fs,
+ const char *path,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * Append a textual list of all available FS modules to the stringbuf
+ * @a output. Third-party modules are only included if repository
+ * access has caused them to be loaded.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_fs_print_modules(svn_stringbuf_t *output,
+ apr_pool_t *pool);
+
+
+/** The kind of action being taken by 'pack'. */
+typedef enum svn_fs_pack_notify_action_t
+{
+ /** packing of the shard has commenced */
+ svn_fs_pack_notify_start = 0,
+
+ /** packing of the shard is completed */
+ svn_fs_pack_notify_end,
+
+ /** packing of the shard revprops has commenced
+ @since New in 1.7. */
+ svn_fs_pack_notify_start_revprop,
+
+ /** packing of the shard revprops has completed
+ @since New in 1.7. */
+ svn_fs_pack_notify_end_revprop
+
+} svn_fs_pack_notify_action_t;
+
+/** The type of a pack notification function. @a shard is the shard being
+ * acted upon; @a action is the type of action being performed. @a baton is
+ * the corresponding baton for the notification function, and @a pool can
+ * be used for temporary allocations, but will be cleared between invocations.
+ */
+typedef svn_error_t *(*svn_fs_pack_notify_t)(void *baton,
+ apr_int64_t shard,
+ svn_fs_pack_notify_action_t action,
+ apr_pool_t *pool);
+
+/**
+ * Possibly update the filesystem located in the directory @a path
+ * to use disk space more efficiently.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_fs_pack(const char *db_path,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Perform backend-specific data consistency and correctness validations
+ * to the Subversion filesystem (mainly the meta-data) located in the
+ * directory @a path. Use the backend-specific configuration @a fs_config
+ * when opening the filesystem. @a NULL is valid for all backends.
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @a start and @a end define the (minimum) range of revisions to check.
+ * If @a start is #SVN_INVALID_REVNUM, it defaults to @c r0. Likewise,
+ * @a end will default to the current youngest repository revision when
+ * given as #SVN_INVALID_REVNUM. Since meta data checks may have to touch
+ * other revisions as well, you may receive notifications for revisions
+ * outside the specified range. In fact, it is perfectly legal for a FS
+ * implementation to always check all revisions.
+ *
+ * Global invariants are only guaranteed to get verified when @a r0 has
+ * been included in the range of revisions to check.
+ *
+ * The optional @a notify_func callback is only a general feedback that
+ * the operation is still in process but may be called in random revisions
+ * order and more than once for the same revision, i.e. r2, r1, r2 would
+ * be a valid sequence.
+ *
+ * The optional @a cancel_func callback will be invoked as usual to allow
+ * the user to preempt this potentially lengthy operation.
+ *
+ * @note You probably don't want to use this directly. Take a look at
+ * svn_repos_verify_fs2() instead, which does non-backend-specific
+ * verifications as well.
+ *
+ * @note To ensure a full verification using all tests and covering all
+ * revisions, you must call this function *and* #svn_fs_verify_root.
+ *
+ * @note Implementors, please do tests that can be done efficiently for
+ * a single revision in #svn_fs_verify_root. This function is meant for
+ * global checks or tests that require an expensive context setup.
+ *
+ * @see svn_repos_verify_fs2()
+ * @see svn_fs_verify_root()
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_fs_verify(const char *path,
+ apr_hash_t *fs_config,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Perform backend-specific data consistency and correctness validations
+ * of @a root in the Subversion filesystem @a fs. @a root is typically
+ * a revision root (see svn_fs_revision_root()), but may be a
+ * transaction root. Use @a scratch_pool for temporary allocations.
+ *
+ * @note You probably don't want to use this directly. Take a look at
+ * svn_repos_verify_fs2() instead, which does non-backend-specific
+ * verifications as well.
+ *
+ * @note To ensure a full verification using all available tests and
+ * covering all revisions, you must call both this function and
+ * #svn_fs_verify.
+ *
+ * @note Implementors, please perform tests that cannot be done
+ * efficiently for a single revision in #svn_fs_verify. This function
+ * is intended for local checks that don't require an expensive context
+ * setup.
+ *
+ * @see svn_repos_verify_fs2()
+ * @see svn_fs_verify()
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_fs_verify_root(svn_fs_root_t *root,
+ apr_pool_t *scratch_pool);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_FS_H */
diff --git a/contrib/subversion/subversion/include/svn_hash.h b/contrib/subversion/subversion/include/svn_hash.h
new file mode 100644
index 0000000..46b4760
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_hash.h
@@ -0,0 +1,265 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_hash.h
+ * @brief Dumping and reading hash tables to/from files.
+ */
+
+
+#ifndef SVN_HASH_H
+#define SVN_HASH_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_file_io.h> /* for apr_file_t */
+
+#include "svn_types.h"
+#include "svn_io.h" /* for svn_stream_t */
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** The longest the "K <number>" line can be in one of our hashdump files. */
+#define SVN_KEYLINE_MAXLEN 100
+
+/**
+ * @defgroup svn_hash_support Hash table serialization support
+ * @{
+ */
+
+/*----------------------------------------------------*/
+
+/** Reading/writing hashtables to disk
+ *
+ * @defgroup svn_hash_read_write Reading and writing hashtables to disk
+ * @{
+ */
+
+/**
+ * The conventional terminator for hash dumps.
+ *
+ * @since New in 1.1.
+ */
+#define SVN_HASH_TERMINATOR "END"
+
+/**
+ * Read a hash table from @a stream, storing the resultants names and
+ * values in @a hash. Use a @a pool for all allocations. @a hash will
+ * have <tt>const char *</tt> keys and <tt>svn_string_t *</tt> values.
+ * If @a terminator is NULL, expect the hash to be terminated by the
+ * end of the stream; otherwise, expect the hash to be terminated by a
+ * line containing @a terminator. Pass @c SVN_HASH_TERMINATOR to use
+ * the conventional terminator "END".
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_hash_read2(apr_hash_t *hash,
+ svn_stream_t *stream,
+ const char *terminator,
+ apr_pool_t *pool);
+
+/**
+ * Dump @a hash to @a stream. Use @a pool for all allocations. @a
+ * hash has <tt>const char *</tt> keys and <tt>svn_string_t *</tt>
+ * values. If @a terminator is not NULL, terminate the hash with a
+ * line containing @a terminator.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_hash_write2(apr_hash_t *hash,
+ svn_stream_t *stream,
+ const char *terminator,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_hash_read2(), but allows @a stream to contain
+ * deletion lines which remove entries from @a hash as well as adding
+ * to it.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_hash_read_incremental(apr_hash_t *hash,
+ svn_stream_t *stream,
+ const char *terminator,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_hash_write2(), but only writes out entries for
+ * keys which differ between @a hash and @a oldhash, and also writes
+ * out deletion lines for keys which are present in @a oldhash but not
+ * in @a hash.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_hash_write_incremental(apr_hash_t *hash,
+ apr_hash_t *oldhash,
+ svn_stream_t *stream,
+ const char *terminator,
+ apr_pool_t *pool);
+
+/**
+ * This function behaves like svn_hash_read2(), but it only works
+ * on an apr_file_t input, empty files are accepted, and the hash is
+ * expected to be terminated with a line containing "END" or
+ * "PROPS-END".
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_hash_read(apr_hash_t *hash,
+ apr_file_t *srcfile,
+ apr_pool_t *pool);
+
+/**
+ * This function behaves like svn_hash_write2(), but it only works
+ * on an apr_file_t output, and the terminator is always "END".
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_hash_write(apr_hash_t *hash,
+ apr_file_t *destfile,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/** Taking the "diff" of two hash tables.
+ *
+ * @defgroup svn_hash_diff Taking the diff of two hash tables.
+ * @{
+ */
+
+/** Hash key status indicator for svn_hash_diff_func_t. */
+enum svn_hash_diff_key_status
+ {
+ /* Key is present in both hashes. */
+ svn_hash_diff_key_both,
+
+ /* Key is present in first hash only. */
+ svn_hash_diff_key_a,
+
+ /* Key is present in second hash only. */
+ svn_hash_diff_key_b
+ };
+
+
+/** Function type for expressing a key's status between two hash tables. */
+typedef svn_error_t *(*svn_hash_diff_func_t)
+ (const void *key, apr_ssize_t klen,
+ enum svn_hash_diff_key_status status,
+ void *baton);
+
+
+/** Take the diff of two hashtables.
+ *
+ * For each key in the union of @a hash_a's and @a hash_b's keys, invoke
+ * @a diff_func exactly once, passing the key, the key's length, an enum
+ * @c svn_hash_diff_key_status indicating which table(s) the key appears
+ * in, and @a diff_func_baton.
+ *
+ * Process all keys of @a hash_a first, then all remaining keys of @a hash_b.
+ *
+ * If @a diff_func returns error, return that error immediately, without
+ * applying @a diff_func to anything else.
+ *
+ * @a hash_a or @a hash_b or both may be NULL; treat a null table as though
+ * empty.
+ *
+ * Use @a pool for temporary allocation.
+ */
+svn_error_t *
+svn_hash_diff(apr_hash_t *hash_a,
+ apr_hash_t *hash_b,
+ svn_hash_diff_func_t diff_func,
+ void *diff_func_baton,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/**
+ * @defgroup svn_hash_misc Miscellaneous hash APIs
+ * @{
+ */
+
+/**
+ * Return the keys to @a hash in @a *array. The keys are assumed to be
+ * (const char *). The keys are in no particular order.
+ *
+ * @a *array itself is allocated in @a pool; however, the keys are not
+ * copied from the hash.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_hash_keys(apr_array_header_t **array,
+ apr_hash_t *hash,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *hash to a new hash whose keys come from the items in @a keys
+ * (an array of <tt>const char *</tt> items), and whose values are
+ * match their corresponding key. Use @a pool for all allocations
+ * (including @a *hash, its keys, and its values).
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_hash_from_cstring_keys(apr_hash_t **hash,
+ const apr_array_header_t *keys,
+ apr_pool_t *pool);
+
+/** Shortcut for apr_hash_get() with a const char * key.
+ *
+ * @since New in 1.8.
+ */
+#define svn_hash_gets(ht, key) \
+ apr_hash_get(ht, key, APR_HASH_KEY_STRING)
+
+/** Shortcut for apr_hash_set() with a const char * key.
+ *
+ * @since New in 1.8.
+ */
+#define svn_hash_sets(ht, key, val) \
+ apr_hash_set(ht, key, APR_HASH_KEY_STRING, val)
+
+/** @} */
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_HASH_H */
diff --git a/contrib/subversion/subversion/include/svn_io.h b/contrib/subversion/subversion/include/svn_io.h
new file mode 100644
index 0000000..92874e1
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_io.h
@@ -0,0 +1,2282 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_io.h
+ * @brief General file I/O for Subversion
+ */
+
+/* ==================================================================== */
+
+
+#ifndef SVN_IO_H
+#define SVN_IO_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_time.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_file_io.h>
+#include <apr_file_info.h>
+#include <apr_thread_proc.h> /* for apr_proc_t, apr_exit_why_e */
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_checksum.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/** Used as an argument when creating temporary files to indicate
+ * when a file should be removed.
+ *
+ * @since New in 1.4.
+ *
+ * Not specifying any of these means no removal at all. */
+typedef enum svn_io_file_del_t
+{
+ /** No deletion ever */
+ svn_io_file_del_none = 0,
+ /** Remove when the file is closed */
+ svn_io_file_del_on_close,
+ /** Remove when the associated pool is cleared */
+ svn_io_file_del_on_pool_cleanup
+} svn_io_file_del_t;
+
+/** A set of directory entry data elements as returned by svn_io_get_dirents
+ *
+ * Note that the first two fields are exactly identical to svn_io_dirent_t
+ * to allow returning a svn_io_dirent2_t as a svn_io_dirent_t.
+ *
+ * Use svn_io_dirent2_create() to create new svn_dirent2_t instances or
+ * svn_io_dirent2_dup() to duplicate an existing instance.
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_io_dirent2_t {
+ /* New fields must be added at the end to preserve binary compatibility */
+
+ /** The kind of this entry. */
+ svn_node_kind_t kind;
+
+ /** If @c kind is #svn_node_file, whether this entry is a special file;
+ * else FALSE.
+ *
+ * @see svn_io_check_special_path().
+ */
+ svn_boolean_t special;
+
+ /** The filesize of this entry or undefined for a directory */
+ svn_filesize_t filesize;
+
+ /** The time the file was last modified */
+ apr_time_t mtime;
+
+ /* Don't forget to update svn_io_dirent2_dup() when adding new fields */
+} svn_io_dirent2_t;
+
+
+/** Creates a new #svn_io_dirent2_t structure
+ *
+ * @since New in 1.7.
+ */
+svn_io_dirent2_t *
+svn_io_dirent2_create(apr_pool_t *result_pool);
+
+/** Duplicates a @c svn_io_dirent2_t structure into @a result_pool.
+ *
+ * @since New in 1.7.
+ */
+svn_io_dirent2_t *
+svn_io_dirent2_dup(const svn_io_dirent2_t *item,
+ apr_pool_t *result_pool);
+
+/** Represents the kind and special status of a directory entry.
+ *
+ * Note that the first two fields are exactly identical to svn_io_dirent2_t
+ * to allow returning a svn_io_dirent2_t as a svn_io_dirent_t.
+ *
+ * @since New in 1.3.
+ */
+typedef struct svn_io_dirent_t {
+ /** The kind of this entry. */
+ svn_node_kind_t kind;
+ /** If @c kind is #svn_node_file, whether this entry is a special file;
+ * else FALSE.
+ *
+ * @see svn_io_check_special_path().
+ */
+ svn_boolean_t special;
+} svn_io_dirent_t;
+
+/** Determine the @a kind of @a path. @a path should be UTF-8 encoded.
+ *
+ * If @a path is a file, set @a *kind to #svn_node_file.
+ *
+ * If @a path is a directory, set @a *kind to #svn_node_dir.
+ *
+ * If @a path does not exist, set @a *kind to #svn_node_none.
+ *
+ * If @a path exists but is none of the above, set @a *kind to
+ * #svn_node_unknown.
+ *
+ * If @a path is not a valid pathname, set @a *kind to #svn_node_none. If
+ * unable to determine @a path's kind for any other reason, return an error,
+ * with @a *kind's value undefined.
+ *
+ * Use @a pool for temporary allocations.
+ *
+ * @see svn_node_kind_t
+ */
+svn_error_t *
+svn_io_check_path(const char *path,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool);
+
+/**
+ * Like svn_io_check_path(), but also set *is_special to @c TRUE if
+ * the path is not a normal file.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_io_check_special_path(const char *path,
+ svn_node_kind_t *kind,
+ svn_boolean_t *is_special,
+ apr_pool_t *pool);
+
+/** Like svn_io_check_path(), but resolve symlinks. This returns the
+ same varieties of @a kind as svn_io_check_path(). */
+svn_error_t *
+svn_io_check_resolved_path(const char *path,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool);
+
+
+/** Open a new file (for reading and writing) with a unique name based on
+ * utf-8 encoded @a filename, in the directory @a dirpath. The file handle is
+ * returned in @a *file, and the name, which ends with @a suffix, is returned
+ * in @a *unique_name, also utf8-encoded. Either @a file or @a unique_name
+ * may be @c NULL. If @a file is @c NULL, the file will be created but not
+ * open.
+ *
+ * If @a delete_when is #svn_io_file_del_on_close, then the @c APR_DELONCLOSE
+ * flag will be used when opening the file. The @c APR_BUFFERED flag will
+ * always be used.
+ *
+ * The first attempt will just append @a suffix. If the result is not
+ * a unique name, then subsequent attempts will append a dot,
+ * followed by an iteration number ("2", then "3", and so on),
+ * followed by the suffix. For example, successive calls to
+ *
+ * svn_io_open_uniquely_named(&f, &u, "tests/t1/A/D/G", "pi", ".tmp", ...)
+ *
+ * will open
+ *
+ * tests/t1/A/D/G/pi.tmp
+ * tests/t1/A/D/G/pi.2.tmp
+ * tests/t1/A/D/G/pi.3.tmp
+ * tests/t1/A/D/G/pi.4.tmp
+ * tests/t1/A/D/G/pi.5.tmp
+ * ...
+ *
+ * Assuming @a suffix is non-empty, @a *unique_name will never be exactly
+ * the same as @a filename, even if @a filename does not exist.
+ *
+ * If @a dirpath is NULL, then the directory returned by svn_io_temp_dir()
+ * will be used.
+ *
+ * If @a filename is NULL, then "tempfile" will be used.
+ *
+ * If @a suffix is NULL, then ".tmp" will be used.
+ *
+ * Allocates @a *file and @a *unique_name in @a result_pool. All
+ * intermediate allocations will be performed in @a scratch_pool.
+ *
+ * If no unique name can be found, #SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED is
+ * the error returned.
+ *
+ * Claim of Historical Inevitability: this function was written
+ * because
+ *
+ * - tmpnam() is not thread-safe.
+ * - tempname() tries standard system tmp areas first.
+ *
+ * @since New in 1.6
+ */
+svn_error_t *
+svn_io_open_uniquely_named(apr_file_t **file,
+ const char **unique_name,
+ const char *dirpath,
+ const char *filename,
+ const char *suffix,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Create a writable file, with an arbitrary and unique name, in the
+ * directory @a dirpath. Set @a *temp_path to its full path, and set
+ * @a *file to the file handle, both allocated from @a result_pool. Either
+ * @a file or @a temp_path may be @c NULL. If @a file is @c NULL, the file
+ * will be created but not open.
+ *
+ * If @a dirpath is @c NULL, use the path returned from svn_io_temp_dir().
+ * (Note that when using the system-provided temp directory, it may not
+ * be possible to atomically rename the resulting file due to cross-device
+ * issues.)
+ *
+ * The file will be deleted according to @a delete_when. If @a delete_when
+ * is @c svn_io_file_del_on_close and @a file is @c NULL, the file will be
+ * deleted before this function returns.
+ *
+ * When passing @c svn_io_file_del_none please don't forget to eventually
+ * remove the temporary file to avoid filling up the system temp directory.
+ * It is often appropriate to bind the lifetime of the temporary file to
+ * the lifetime of a pool by using @c svn_io_file_del_on_pool_cleanup.
+ *
+ * Temporary allocations will be performed in @a scratch_pool.
+ *
+ * @since New in 1.6
+ * @see svn_stream_open_unique()
+ */
+svn_error_t *
+svn_io_open_unique_file3(apr_file_t **file,
+ const char **temp_path,
+ const char *dirpath,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Like svn_io_open_uniquely_named(), but takes a joined dirpath and
+ * filename, and a single pool.
+ *
+ * @since New in 1.4
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_open_unique_file2(apr_file_t **f,
+ const char **unique_name_p,
+ const char *path,
+ const char *suffix,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *pool);
+
+/** Like svn_io_open_unique_file2, but can't delete on pool cleanup.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API
+ *
+ * @note In 1.4 the API was extended to require either @a f or
+ * @a unique_name_p (the other can be NULL). Before that, both were
+ * required.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_open_unique_file(apr_file_t **f,
+ const char **unique_name_p,
+ const char *path,
+ const char *suffix,
+ svn_boolean_t delete_on_close,
+ apr_pool_t *pool);
+
+
+/**
+ * Like svn_io_open_unique_file(), except that instead of creating a
+ * file, a symlink is generated that references the path @a dest.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_io_create_unique_link(const char **unique_name_p,
+ const char *path,
+ const char *dest,
+ const char *suffix,
+ apr_pool_t *pool);
+
+
+/**
+ * Set @a *dest to the path that the symlink at @a path references.
+ * Allocate the string from @a pool.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_io_read_link(svn_string_t **dest,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Set @a *dir to a directory path (allocated in @a pool) deemed
+ * usable for the creation of temporary files and subdirectories.
+ */
+svn_error_t *
+svn_io_temp_dir(const char **dir,
+ apr_pool_t *pool);
+
+
+/** Copy @a src to @a dst atomically, in a "byte-for-byte" manner.
+ * Overwrite @a dst if it exists, else create it. Both @a src and @a dst
+ * are utf8-encoded filenames. If @a copy_perms is TRUE, set @a dst's
+ * permissions to match those of @a src.
+ */
+svn_error_t *
+svn_io_copy_file(const char *src,
+ const char *dst,
+ svn_boolean_t copy_perms,
+ apr_pool_t *pool);
+
+
+/** Copy permission flags from @a src onto the file at @a dst. Both
+ * filenames are utf8-encoded filenames.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_io_copy_perms(const char *src,
+ const char *dst,
+ apr_pool_t *pool);
+
+
+/**
+ * Copy symbolic link @a src to @a dst atomically. Overwrite @a dst
+ * if it exists, else create it. Both @a src and @a dst are
+ * utf8-encoded filenames. After copying, the @a dst link will point
+ * to the same thing @a src does.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_io_copy_link(const char *src,
+ const char *dst,
+ apr_pool_t *pool);
+
+
+/** Recursively copy directory @a src into @a dst_parent, as a new entry named
+ * @a dst_basename. If @a dst_basename already exists in @a dst_parent,
+ * return error. @a copy_perms will be passed through to svn_io_copy_file()
+ * when any files are copied. @a src, @a dst_parent, and @a dst_basename are
+ * all utf8-encoded.
+ *
+ * If @a cancel_func is non-NULL, invoke it with @a cancel_baton at
+ * various points during the operation. If it returns any error
+ * (typically #SVN_ERR_CANCELLED), return that error immediately.
+ */
+svn_error_t *
+svn_io_copy_dir_recursively(const char *src,
+ const char *dst_parent,
+ const char *dst_basename,
+ svn_boolean_t copy_perms,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/** Create directory @a path on the file system, creating intermediate
+ * directories as required, like <tt>mkdir -p</tt>. Report no error if @a
+ * path already exists. @a path is utf8-encoded.
+ *
+ * This is essentially a wrapper for apr_dir_make_recursive(), passing
+ * @c APR_OS_DEFAULT as the permissions.
+ */
+svn_error_t *
+svn_io_make_dir_recursively(const char *path,
+ apr_pool_t *pool);
+
+
+/** Set @a *is_empty_p to @c TRUE if directory @a path is empty, else to
+ * @c FALSE if it is not empty. @a path must be a directory, and is
+ * utf8-encoded. Use @a pool for temporary allocation.
+ */
+svn_error_t *
+svn_io_dir_empty(svn_boolean_t *is_empty_p,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Append @a src to @a dst. @a dst will be appended to if it exists, else it
+ * will be created. Both @a src and @a dst are utf8-encoded.
+ */
+svn_error_t *
+svn_io_append_file(const char *src,
+ const char *dst,
+ apr_pool_t *pool);
+
+
+/** Make a file as read-only as the operating system allows.
+ * @a path is the utf8-encoded path to the file. If @a ignore_enoent is
+ * @c TRUE, don't fail if the target file doesn't exist.
+ *
+ * If @a path is a symlink, do nothing.
+ *
+ * @note If @a path is a directory, act on it as though it were a
+ * file, as described above, but note that you probably don't want to
+ * call this function on directories. We have left it effective on
+ * directories for compatibility reasons, but as its name implies, it
+ * should be used only for files.
+ */
+svn_error_t *
+svn_io_set_file_read_only(const char *path,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool);
+
+
+/** Make a file as writable as the operating system allows.
+ * @a path is the utf8-encoded path to the file. If @a ignore_enoent is
+ * @c TRUE, don't fail if the target file doesn't exist.
+ * @warning On Unix this function will do the equivalent of chmod a+w path.
+ * If this is not what you want you should not use this function, but rather
+ * use apr_file_perms_set().
+ *
+ * If @a path is a symlink, do nothing.
+ *
+ * @note If @a path is a directory, act on it as though it were a
+ * file, as described above, but note that you probably don't want to
+ * call this function on directories. We have left it effective on
+ * directories for compatibility reasons, but as its name implies, it
+ * should be used only for files.
+ */
+svn_error_t *
+svn_io_set_file_read_write(const char *path,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool);
+
+
+/** Similar to svn_io_set_file_read_* functions.
+ * Change the read-write permissions of a file.
+ * @since New in 1.1.
+ *
+ * When making @a path read-write on operating systems with unix style
+ * permissions, set the permissions on @a path to the permissions that
+ * are set when a new file is created (effectively honoring the user's
+ * umask).
+ *
+ * When making the file read-only on operating systems with unix style
+ * permissions, remove all write permissions.
+ *
+ * On other operating systems, toggle the file's "writability" as much as
+ * the operating system allows.
+ *
+ * @a path is the utf8-encoded path to the file. If @a enable_write
+ * is @c TRUE, then make the file read-write. If @c FALSE, make it
+ * read-only. If @a ignore_enoent is @c TRUE, don't fail if the target
+ * file doesn't exist.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_set_file_read_write_carefully(const char *path,
+ svn_boolean_t enable_write,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool);
+
+/** Set @a path's "executability" (but do nothing if it is a symlink).
+ *
+ * @a path is the utf8-encoded path to the file. If @a executable
+ * is @c TRUE, then make the file executable. If @c FALSE, make it
+ * non-executable. If @a ignore_enoent is @c TRUE, don't fail if the target
+ * file doesn't exist.
+ *
+ * When making the file executable on operating systems with unix style
+ * permissions, never add an execute permission where there is not
+ * already a read permission: that is, only make the file executable
+ * for the user, group or world if the corresponding read permission
+ * is already set for user, group or world.
+ *
+ * When making the file non-executable on operating systems with unix style
+ * permissions, remove all execute permissions.
+ *
+ * On other operating systems, toggle the file's "executability" as much as
+ * the operating system allows.
+ *
+ * @note If @a path is a directory, act on it as though it were a
+ * file, as described above, but note that you probably don't want to
+ * call this function on directories. We have left it effective on
+ * directories for compatibility reasons, but as its name implies, it
+ * should be used only for files.
+ */
+svn_error_t *
+svn_io_set_file_executable(const char *path,
+ svn_boolean_t executable,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool);
+
+/** Determine whether a file is executable by the current user.
+ * Set @a *executable to @c TRUE if the file @a path is executable by the
+ * current user, otherwise set it to @c FALSE.
+ *
+ * On Windows and on platforms without userids, always returns @c FALSE.
+ */
+svn_error_t *
+svn_io_is_file_executable(svn_boolean_t *executable,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/** Read a line from @a file into @a buf, but not exceeding @a *limit bytes.
+ * Does not include newline, instead '\\0' is put there.
+ * Length (as in strlen) is returned in @a *limit.
+ * @a buf should be pre-allocated.
+ * @a file should be already opened.
+ *
+ * When the file is out of lines, @c APR_EOF will be returned.
+ */
+svn_error_t *
+svn_io_read_length_line(apr_file_t *file,
+ char *buf,
+ apr_size_t *limit,
+ apr_pool_t *pool);
+
+
+/** Set @a *apr_time to the time of last modification of the contents of the
+ * file @a path. @a path is utf8-encoded.
+ *
+ * @note This is the APR mtime which corresponds to the traditional mtime
+ * on Unix, and the last write time on Windows.
+ */
+svn_error_t *
+svn_io_file_affected_time(apr_time_t *apr_time,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Set the timestamp of file @a path to @a apr_time. @a path is
+ * utf8-encoded.
+ *
+ * @note This is the APR mtime which corresponds to the traditional mtime
+ * on Unix, and the last write time on Windows.
+ */
+svn_error_t *
+svn_io_set_file_affected_time(apr_time_t apr_time,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Sleep to ensure that any files modified after we exit have a different
+ * timestamp than the one we recorded. If @a path is not NULL, check if we
+ * can determine how long we should wait for a new timestamp on the filesystem
+ * containing @a path, an existing file or directory. If @a path is NULL or we
+ * can't determine the timestamp resolution, sleep until the next second.
+ *
+ * Use @a pool for any necessary allocations. @a pool can be null if @a path
+ * is NULL.
+ *
+ * Errors while retrieving the timestamp resolution will result in sleeping
+ * to the next second, to keep the working copy stable in error conditions.
+ *
+ * @since New in 1.6.
+ */
+void
+svn_io_sleep_for_timestamps(const char *path, apr_pool_t *pool);
+
+/** Set @a *different_p to TRUE if @a file1 and @a file2 have different
+ * sizes, else set to FALSE. Both @a file1 and @a file2 are utf8-encoded.
+ *
+ * Setting @a *different_p to zero does not mean the files definitely
+ * have the same size, it merely means that the sizes are not
+ * definitely different. That is, if the size of one or both files
+ * cannot be determined, then the sizes are not known to be different,
+ * so @a *different_p is set to FALSE.
+ */
+svn_error_t *
+svn_io_filesizes_different_p(svn_boolean_t *different_p,
+ const char *file1,
+ const char *file2,
+ apr_pool_t *pool);
+
+/** Set @a *different_p12 to non-zero if @a file1 and @a file2 have different
+ * sizes, else set to zero. Do the similar for @a *different_p23 with
+ * @a file2 and @a file3, and @a *different_p13 for @a file1 and @a file3.
+ * The filenames @a file1, @a file2 and @a file3 are utf8-encoded.
+ *
+ * Setting @a *different_p12 to zero does not mean the files definitely
+ * have the same size, it merely means that the sizes are not
+ * definitely different. That is, if the size of one or both files
+ * cannot be determined (due to stat() returning an error), then the sizes
+ * are not known to be different, so @a *different_p12 is set to 0.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_io_filesizes_three_different_p(svn_boolean_t *different_p12,
+ svn_boolean_t *different_p23,
+ svn_boolean_t *different_p13,
+ const char *file1,
+ const char *file2,
+ const char *file3,
+ apr_pool_t *scratch_pool);
+
+/** Return in @a *checksum the checksum of type @a kind of @a file
+ * Use @a pool for temporary allocations and to allocate @a *checksum.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_io_file_checksum2(svn_checksum_t **checksum,
+ const char *file,
+ svn_checksum_kind_t kind,
+ apr_pool_t *pool);
+
+
+/** Put the md5 checksum of @a file into @a digest.
+ * @a digest points to @c APR_MD5_DIGESTSIZE bytes of storage.
+ * Use @a pool only for temporary allocations.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_file_checksum(unsigned char digest[],
+ const char *file,
+ apr_pool_t *pool);
+
+
+/** Set @a *same to TRUE if @a file1 and @a file2 have the same
+ * contents, else set it to FALSE. Use @a pool for temporary allocations.
+ */
+svn_error_t *
+svn_io_files_contents_same_p(svn_boolean_t *same,
+ const char *file1,
+ const char *file2,
+ apr_pool_t *pool);
+
+/** Set @a *same12 to TRUE if @a file1 and @a file2 have the same
+ * contents, else set it to FALSE. Do the similar for @a *same23
+ * with @a file2 and @a file3, and @a *same13 for @a file1 and @a
+ * file3. The filenames @a file1, @a file2 and @a file3 are
+ * utf8-encoded. Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_io_files_contents_three_same_p(svn_boolean_t *same12,
+ svn_boolean_t *same23,
+ svn_boolean_t *same13,
+ const char *file1,
+ const char *file2,
+ const char *file3,
+ apr_pool_t *scratch_pool);
+
+/** Create file at utf8-encoded @a file with contents @a contents.
+ * @a file must not already exist.
+ * Use @a pool for memory allocations.
+ */
+svn_error_t *
+svn_io_file_create(const char *file,
+ const char *contents,
+ apr_pool_t *pool);
+
+/**
+ * Lock file at @a lock_file. If @a exclusive is TRUE,
+ * obtain exclusive lock, otherwise obtain shared lock.
+ * Lock will be automatically released when @a pool is cleared or destroyed.
+ * Use @a pool for memory allocations.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_file_lock(const char *lock_file,
+ svn_boolean_t exclusive,
+ apr_pool_t *pool);
+
+/**
+ * Lock file at @a lock_file. If @a exclusive is TRUE,
+ * obtain exclusive lock, otherwise obtain shared lock.
+ *
+ * If @a nonblocking is TRUE, do not wait for the lock if it
+ * is not available: throw an error instead.
+ *
+ * Lock will be automatically released when @a pool is cleared or destroyed.
+ * Use @a pool for memory allocations.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_io_file_lock2(const char *lock_file,
+ svn_boolean_t exclusive,
+ svn_boolean_t nonblocking,
+ apr_pool_t *pool);
+
+/**
+ * Lock the file @a lockfile_handle. If @a exclusive is TRUE,
+ * obtain exclusive lock, otherwise obtain shared lock.
+ *
+ * If @a nonblocking is TRUE, do not wait for the lock if it
+ * is not available: throw an error instead.
+ *
+ * Lock will be automatically released when @a pool is cleared or destroyed.
+ * You may also explicitly call svn_io_unlock_open_file().
+ * Use @a pool for memory allocations. @a pool must be the pool that
+ * @a lockfile_handle has been created in or one of its sub-pools.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_io_lock_open_file(apr_file_t *lockfile_handle,
+ svn_boolean_t exclusive,
+ svn_boolean_t nonblocking,
+ apr_pool_t *pool);
+
+/**
+ * Unlock the file @a lockfile_handle.
+ *
+ * Use @a pool for memory allocations. @a pool must be the pool that
+ * was passed to svn_io_lock_open_file().
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_io_unlock_open_file(apr_file_t *lockfile_handle,
+ apr_pool_t *pool);
+
+/**
+ * Flush any unwritten data from @a file to disk. Use @a pool for
+ * memory allocations.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_io_file_flush_to_disk(apr_file_t *file,
+ apr_pool_t *pool);
+
+/** Copy the file whose basename (or relative path) is @a file within
+ * directory @a src_path to the same basename (or relative path) within
+ * directory @a dest_path. Overwrite the destination file if it already
+ * exists. The destination directory (including any directory
+ * components in @a name) must already exist. Set the destination
+ * file's permissions to match those of the source. Use @a pool for
+ * memory allocations.
+ */
+svn_error_t *
+svn_io_dir_file_copy(const char *src_path,
+ const char *dest_path,
+ const char *file,
+ apr_pool_t *pool);
+
+
+/** Generic byte-streams
+ *
+ * @defgroup svn_io_byte_streams Generic byte streams
+ * @{
+ */
+
+/** An abstract stream of bytes--either incoming or outgoing or both.
+ *
+ * The creator of a stream sets functions to handle read and write.
+ * Both of these handlers accept a baton whose value is determined at
+ * stream creation time; this baton can point to a structure
+ * containing data associated with the stream. If a caller attempts
+ * to invoke a handler which has not been set, it will generate a
+ * runtime assertion failure. The creator can also set a handler for
+ * close requests so that it can flush buffered data or whatever;
+ * if a close handler is not specified, a close request on the stream
+ * will simply be ignored. Note that svn_stream_close() does not
+ * deallocate the memory used to allocate the stream structure; free
+ * the pool you created the stream in to free that memory.
+ *
+ * The read and write handlers accept length arguments via pointer.
+ * On entry to the handler, the pointed-to value should be the amount
+ * of data which can be read or the amount of data to write. When the
+ * handler returns, the value is reset to the amount of data actually
+ * read or written. Handlers are obliged to complete a read or write
+ * to the maximum extent possible; thus, a short read with no
+ * associated error implies the end of the input stream, and a short
+ * write should never occur without an associated error.
+ *
+ * In Subversion 1.7 reset support was added as an optional feature of
+ * streams. If a stream implements resetting it allows reading the data
+ * again after a successful call to svn_stream_reset().
+ */
+typedef struct svn_stream_t svn_stream_t;
+
+
+
+/** Read handler function for a generic stream. @see svn_stream_t. */
+typedef svn_error_t *(*svn_read_fn_t)(void *baton,
+ char *buffer,
+ apr_size_t *len);
+
+/** Skip data handler function for a generic stream. @see svn_stream_t
+ * and svn_stream_skip().
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_stream_skip_fn_t)(void *baton,
+ apr_size_t len);
+
+/** Write handler function for a generic stream. @see svn_stream_t. */
+typedef svn_error_t *(*svn_write_fn_t)(void *baton,
+ const char *data,
+ apr_size_t *len);
+
+/** Close handler function for a generic stream. @see svn_stream_t. */
+typedef svn_error_t *(*svn_close_fn_t)(void *baton);
+
+/** An opaque type which represents a mark on a stream. There is no
+ * concrete definition of this type, it is a named type for stream
+ * implementation specific baton pointers.
+ *
+ * @see svn_stream_mark().
+ * @since New in 1.7.
+ */
+typedef struct svn_stream_mark_t svn_stream_mark_t;
+
+/** Mark handler function for a generic stream. @see svn_stream_t and
+ * svn_stream_mark().
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_stream_mark_fn_t)(void *baton,
+ svn_stream_mark_t **mark,
+ apr_pool_t *pool);
+
+/** Seek handler function for a generic stream. @see svn_stream_t and
+ * svn_stream_seek().
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_stream_seek_fn_t)(void *baton,
+ const svn_stream_mark_t *mark);
+
+/** Create a generic stream. @see svn_stream_t. */
+svn_stream_t *
+svn_stream_create(void *baton,
+ apr_pool_t *pool);
+
+/** Set @a stream's baton to @a baton */
+void
+svn_stream_set_baton(svn_stream_t *stream,
+ void *baton);
+
+/** Set @a stream's read function to @a read_fn */
+void
+svn_stream_set_read(svn_stream_t *stream,
+ svn_read_fn_t read_fn);
+
+/** Set @a stream's skip function to @a skip_fn
+ *
+ * @since New in 1.7
+ */
+void
+svn_stream_set_skip(svn_stream_t *stream,
+ svn_stream_skip_fn_t skip_fn);
+
+/** Set @a stream's write function to @a write_fn */
+void
+svn_stream_set_write(svn_stream_t *stream,
+ svn_write_fn_t write_fn);
+
+/** Set @a stream's close function to @a close_fn */
+void
+svn_stream_set_close(svn_stream_t *stream,
+ svn_close_fn_t close_fn);
+
+/** Set @a stream's mark function to @a mark_fn
+ *
+ * @since New in 1.7.
+ */
+void
+svn_stream_set_mark(svn_stream_t *stream,
+ svn_stream_mark_fn_t mark_fn);
+
+/** Set @a stream's seek function to @a seek_fn
+ *
+ * @since New in 1.7.
+ */
+void
+svn_stream_set_seek(svn_stream_t *stream,
+ svn_stream_seek_fn_t seek_fn);
+
+/** Create a stream that is empty for reading and infinite for writing. */
+svn_stream_t *
+svn_stream_empty(apr_pool_t *pool);
+
+/** Return a stream allocated in @a pool which forwards all requests
+ * to @a stream. Destruction is explicitly excluded from forwarding.
+ *
+ * @see notes/destruction-of-stacked-resources
+ *
+ * @since New in 1.4.
+ */
+svn_stream_t *
+svn_stream_disown(svn_stream_t *stream,
+ apr_pool_t *pool);
+
+
+/** Create a stream to read the file at @a path. It will be opened using
+ * the APR_BUFFERED and APR_BINARY flag, and APR_OS_DEFAULT for the perms.
+ * If you'd like to use different values, then open the file yourself, and
+ * use the svn_stream_from_aprfile2() interface.
+ *
+ * The stream will be returned in @a stream, and allocated from @a result_pool.
+ * Temporary allocations will be performed in @a scratch_pool.
+ *
+ * @since New in 1.6
+ */
+svn_error_t *
+svn_stream_open_readonly(svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Create a stream to write a file at @a path. The file will be *created*
+ * using the APR_BUFFERED and APR_BINARY flag, and APR_OS_DEFAULT for the
+ * perms. The file will be created "exclusively", so if it already exists,
+ * then an error will be thrown. If you'd like to use different values, or
+ * open an existing file, then open the file yourself, and use the
+ * svn_stream_from_aprfile2() interface.
+ *
+ * The stream will be returned in @a stream, and allocated from @a result_pool.
+ * Temporary allocations will be performed in @a scratch_pool.
+ *
+ * @since New in 1.6
+ */
+svn_error_t *
+svn_stream_open_writable(svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Create a writable stream to a file in the directory @a dirpath.
+ * The file will have an arbitrary and unique name, and the full path
+ * will be returned in @a temp_path. The stream will be returned in
+ * @a stream. Both will be allocated from @a result_pool.
+ *
+ * If @a dirpath is @c NULL, use the path returned from svn_io_temp_dir().
+ * (Note that when using the system-provided temp directory, it may not
+ * be possible to atomically rename the resulting file due to cross-device
+ * issues.)
+ *
+ * The file will be deleted according to @a delete_when.
+ *
+ * Temporary allocations will be performed in @a scratch_pool.
+ *
+ * @since New in 1.6
+ * @see svn_io_open_unique_file3()
+ */
+svn_error_t *
+svn_stream_open_unique(svn_stream_t **stream,
+ const char **temp_path,
+ const char *dirpath,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Create a stream from an APR file. For convenience, if @a file is
+ * @c NULL, an empty stream created by svn_stream_empty() is returned.
+ *
+ * This function should normally be called with @a disown set to FALSE,
+ * in which case closing the stream will also close the underlying file.
+ *
+ * If @a disown is TRUE, the stream will disown the underlying file,
+ * meaning that svn_stream_close() will not close the file.
+ *
+ * @since New in 1.4.
+ */
+svn_stream_t *
+svn_stream_from_aprfile2(apr_file_t *file,
+ svn_boolean_t disown,
+ apr_pool_t *pool);
+
+/** Similar to svn_stream_from_aprfile2(), except that the file will
+ * always be disowned.
+ *
+ * @note The stream returned is not considered to "own" the underlying
+ * file, meaning that svn_stream_close() on the stream will not
+ * close the file.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_stream_t *
+svn_stream_from_aprfile(apr_file_t *file,
+ apr_pool_t *pool);
+
+/** Set @a *in to a generic stream connected to stdin, allocated in
+ * @a pool. The stream and its underlying APR handle will be closed
+ * when @a pool is cleared or destroyed.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_stream_for_stdin(svn_stream_t **in,
+ apr_pool_t *pool);
+
+/** Set @a *err to a generic stream connected to stderr, allocated in
+ * @a pool. The stream and its underlying APR handle will be closed
+ * when @a pool is cleared or destroyed.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_stream_for_stderr(svn_stream_t **err,
+ apr_pool_t *pool);
+
+/** Set @a *out to a generic stream connected to stdout, allocated in
+ * @a pool. The stream and its underlying APR handle will be closed
+ * when @a pool is cleared or destroyed.
+ */
+svn_error_t *
+svn_stream_for_stdout(svn_stream_t **out,
+ apr_pool_t *pool);
+
+/** Return a generic stream connected to stringbuf @a str. Allocate the
+ * stream in @a pool.
+ */
+svn_stream_t *
+svn_stream_from_stringbuf(svn_stringbuf_t *str,
+ apr_pool_t *pool);
+
+/** Return a generic read-only stream connected to string @a str.
+ * Allocate the stream in @a pool.
+ */
+svn_stream_t *
+svn_stream_from_string(const svn_string_t *str,
+ apr_pool_t *pool);
+
+/** Return a generic stream which implements buffered reads and writes.
+ * The stream will preferentially store data in-memory, but may use
+ * disk storage as backup if the amount of data is large.
+ * Allocate the stream in @a result_pool
+ *
+ * @since New in 1.8.
+ */
+svn_stream_t *
+svn_stream_buffered(apr_pool_t *result_pool);
+
+/** Return a stream that decompresses all data read and compresses all
+ * data written. The stream @a stream is used to read and write all
+ * compressed data. All compression data structures are allocated on
+ * @a pool. If compression support is not compiled in then
+ * svn_stream_compressed() returns @a stream unmodified. Make sure you
+ * call svn_stream_close() on the stream returned by this function,
+ * so that all data are flushed and cleaned up.
+ *
+ * @note From 1.4, compression support is always compiled in.
+ */
+svn_stream_t *
+svn_stream_compressed(svn_stream_t *stream,
+ apr_pool_t *pool);
+
+/** Return a stream that calculates checksums for all data read
+ * and written. The stream @a stream is used to read and write all data.
+ * The stream and the resulting digests are allocated in @a pool.
+ *
+ * When the stream is closed, @a *read_checksum and @a *write_checksum
+ * are set to point to the resulting checksums, of type @a read_checksum_kind
+ * and @a write_checksum_kind, respectively.
+ *
+ * Both @a read_checksum and @a write_checksum can be @c NULL, in which case
+ * the respective checksum isn't calculated.
+ *
+ * If @a read_all is TRUE, make sure that all data available on @a
+ * stream is read (and checksummed) when the stream is closed.
+ *
+ * Read and write operations can be mixed without interfering.
+ *
+ * The @a stream passed into this function is closed when the created
+ * stream is closed.
+ *
+ * @since New in 1.6.
+ */
+svn_stream_t *
+svn_stream_checksummed2(svn_stream_t *stream,
+ svn_checksum_t **read_checksum,
+ svn_checksum_t **write_checksum,
+ svn_checksum_kind_t checksum_kind,
+ svn_boolean_t read_all,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_stream_checksummed2(), but always returning the MD5
+ * checksum in @a read_digest and @a write_digest.
+ *
+ * @since New in 1.4.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_stream_t *
+svn_stream_checksummed(svn_stream_t *stream,
+ const unsigned char **read_digest,
+ const unsigned char **write_digest,
+ svn_boolean_t read_all,
+ apr_pool_t *pool);
+
+/** Read from a generic stream. @see svn_stream_t. */
+svn_error_t *
+svn_stream_read(svn_stream_t *stream,
+ char *buffer,
+ apr_size_t *len);
+
+/**
+ * Skip @a len bytes from a generic @a stream. If the stream is exhausted
+ * before @a len bytes have been read, return an error.
+ *
+ * @note No assumption can be made on the semantics of this function
+ * other than that the stream read pointer will be advanced by *len
+ * bytes. Depending on the capabilities of the underlying stream
+ * implementation, this may for instance be translated into a sequence
+ * of reads or a simple seek operation. If the stream implementation has
+ * not provided a skip function, this will read from the stream and
+ * discard the data.
+ */
+svn_error_t *
+svn_stream_skip(svn_stream_t *stream,
+ apr_size_t len);
+
+/** Write to a generic stream. @see svn_stream_t. */
+svn_error_t *
+svn_stream_write(svn_stream_t *stream,
+ const char *data,
+ apr_size_t *len);
+
+/** Close a generic stream. @see svn_stream_t. */
+svn_error_t *
+svn_stream_close(svn_stream_t *stream);
+
+/** Reset a generic stream back to its origin. (E.g. On a file this would be
+ * implemented as a seek to position 0). This function returns a
+ * #SVN_ERR_STREAM_SEEK_NOT_SUPPORTED error when the stream doesn't
+ * implement resetting.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_stream_reset(svn_stream_t *stream);
+
+/** Returns @c TRUE if the generic @a stream supports svn_stream_mark().
+ *
+ * @see svn_stream_mark()
+ * @since New in 1.7.
+ */
+svn_boolean_t
+svn_stream_supports_mark(svn_stream_t *stream);
+
+/** Set a @a mark at the current position of a generic @a stream,
+ * which can later be sought back to using svn_stream_seek().
+ * The @a mark is allocated in @a pool.
+ *
+ * This function returns the #SVN_ERR_STREAM_SEEK_NOT_SUPPORTED error
+ * if the stream doesn't implement seeking.
+ *
+ * @see svn_stream_seek()
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_stream_mark(svn_stream_t *stream,
+ svn_stream_mark_t **mark,
+ apr_pool_t *pool);
+
+/** Seek to a @a mark in a generic @a stream.
+ * This function returns the #SVN_ERR_STREAM_SEEK_NOT_SUPPORTED error
+ * if the stream doesn't implement seeking. Passing NULL as @a mark,
+ * seeks to the start of the stream.
+ *
+ * @see svn_stream_mark()
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_stream_seek(svn_stream_t *stream, const svn_stream_mark_t *mark);
+
+/** Return a writable stream which, when written to, writes to both of the
+ * underlying streams. Both of these streams will be closed upon closure of
+ * the returned stream; use svn_stream_disown() if this is not the desired
+ * behavior. One or both of @a out1 and @a out2 may be @c NULL. If both are
+ * @c NULL, @c NULL is returned.
+ *
+ * @since New in 1.7.
+ */
+svn_stream_t *
+svn_stream_tee(svn_stream_t *out1,
+ svn_stream_t *out2,
+ apr_pool_t *pool);
+
+/** Write NULL-terminated string @a str to @a stream.
+ *
+ * @since New in 1.8.
+ *
+ */
+svn_error_t *
+svn_stream_puts(svn_stream_t *stream,
+ const char *str);
+
+/** Write to @a stream using a printf-style @a fmt specifier, passed through
+ * apr_psprintf() using memory from @a pool.
+ */
+svn_error_t *
+svn_stream_printf(svn_stream_t *stream,
+ apr_pool_t *pool,
+ const char *fmt,
+ ...)
+ __attribute__((format(printf, 3, 4)));
+
+/** Write to @a stream using a printf-style @a fmt specifier, passed through
+ * apr_psprintf() using memory from @a pool. The resulting string
+ * will be translated to @a encoding before it is sent to @a stream.
+ *
+ * @note Use @c APR_LOCALE_CHARSET to translate to the encoding of the
+ * current locale.
+ *
+ * @since New in 1.3.
+ */
+svn_error_t *
+svn_stream_printf_from_utf8(svn_stream_t *stream,
+ const char *encoding,
+ apr_pool_t *pool,
+ const char *fmt,
+ ...)
+ __attribute__((format(printf, 4, 5)));
+
+/** Allocate @a *stringbuf in @a pool, and read into it one line (terminated
+ * by @a eol) from @a stream. The line-terminator is read from the stream,
+ * but is not added to the end of the stringbuf. Instead, the stringbuf
+ * ends with a usual '\\0'.
+ *
+ * If @a stream runs out of bytes before encountering a line-terminator,
+ * then set @a *eof to @c TRUE, otherwise set @a *eof to FALSE.
+ */
+svn_error_t *
+svn_stream_readline(svn_stream_t *stream,
+ svn_stringbuf_t **stringbuf,
+ const char *eol,
+ svn_boolean_t *eof,
+ apr_pool_t *pool);
+
+/**
+ * Read the contents of the readable stream @a from and write them to the
+ * writable stream @a to calling @a cancel_func before copying each chunk.
+ *
+ * @a cancel_func may be @c NULL.
+ *
+ * @note both @a from and @a to will be closed upon successful completion of
+ * the copy (but an error may still be returned, based on trying to close
+ * the two streams). If the closure is not desired, then you can use
+ * svn_stream_disown() to protect either or both of the streams from
+ * being closed.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_stream_copy3(svn_stream_t *from,
+ svn_stream_t *to,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_stream_copy3() but the streams are not closed.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_stream_copy2(svn_stream_t *from,
+ svn_stream_t *to,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_stream_copy3(), but without the cancellation function
+ * or stream closing.
+ *
+ * @since New in 1.1.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_stream_copy(svn_stream_t *from,
+ svn_stream_t *to,
+ apr_pool_t *pool);
+
+
+/** Set @a *same to TRUE if @a stream1 and @a stream2 have the same
+ * contents, else set it to FALSE.
+ *
+ * Both streams will be closed before this function returns (regardless of
+ * the result, or any possible error).
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_stream_contents_same2(svn_boolean_t *same,
+ svn_stream_t *stream1,
+ svn_stream_t *stream2,
+ apr_pool_t *pool);
+
+
+/**
+ * Same as svn_stream_contents_same2(), but the streams will not be closed.
+ *
+ * @since New in 1.4.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_stream_contents_same(svn_boolean_t *same,
+ svn_stream_t *stream1,
+ svn_stream_t *stream2,
+ apr_pool_t *pool);
+
+
+/** Read the contents of @a stream into memory, returning the data in
+ * @a result. The stream will be closed when it has been successfully and
+ * completely read.
+ *
+ * The returned memory is allocated in @a result_pool, and any temporary
+ * allocations are performed in @a scratch_pool.
+ *
+ * @note due to memory pseudo-reallocation behavior (due to pools), this
+ * can be a memory-intensive operation for large files.
+ *
+ * @since New in 1.6
+ */
+svn_error_t *
+svn_string_from_stream(svn_string_t **result,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** A function type provided for use as a callback from
+ * @c svn_stream_lazyopen_create().
+ *
+ * The callback function shall open a new stream and set @a *stream to
+ * the stream object, allocated in @a result_pool. @a baton is the
+ * callback baton that was passed to svn_stream_lazyopen_create().
+ *
+ * @a result_pool is the result pool that was passed to
+ * svn_stream_lazyopen_create(). The callback function may use
+ * @a scratch_pool for temporary allocations; the caller may clear or
+ * destroy @a scratch_pool any time after the function returns.
+ *
+ * @since New in 1.8.
+ */
+typedef svn_error_t *
+(*svn_stream_lazyopen_func_t)(svn_stream_t **stream,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Return a generic stream which wraps another primary stream,
+ * delaying the "opening" of that stream until the first time the
+ * returned stream is accessed.
+ *
+ * @a open_func and @a open_baton are a callback function/baton pair
+ * which will be invoked upon the first access of the returned
+ * stream (read, write, mark, seek, skip, or possibly close). The
+ * callback shall open the primary stream.
+ *
+ * If the only "access" the returned stream gets is to close it
+ * then @a open_func will only be called if @a open_on_close is TRUE.
+ *
+ * @since New in 1.8.
+ */
+svn_stream_t *
+svn_stream_lazyopen_create(svn_stream_lazyopen_func_t open_func,
+ void *open_baton,
+ svn_boolean_t open_on_close,
+ apr_pool_t *result_pool);
+
+/** @} */
+
+/** Set @a *result to a string containing the contents of @a
+ * filename, which is either "-" (indicating that stdin should be
+ * read) or the utf8-encoded path of a real file.
+ *
+ * @warning Callers should be aware of possible unexpected results
+ * when using this function to read from stdin where additional
+ * stdin-reading processes abound. For example, if a program tries
+ * both to invoke an external editor and to read from stdin, stdin
+ * could be trashed and the editor might act funky or die outright.
+ *
+ * @note due to memory pseudo-reallocation behavior (due to pools), this
+ * can be a memory-intensive operation for large files.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_stringbuf_from_file2(svn_stringbuf_t **result,
+ const char *filename,
+ apr_pool_t *pool);
+
+/** Similar to svn_stringbuf_from_file2(), except that if @a filename
+ * is "-", return the error #SVN_ERR_UNSUPPORTED_FEATURE and don't
+ * touch @a *result.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_stringbuf_from_file(svn_stringbuf_t **result,
+ const char *filename,
+ apr_pool_t *pool);
+
+/** Sets @a *result to a string containing the contents of the already opened
+ * @a file. Reads from the current position in file to the end. Does not
+ * close the file or reset the cursor position.
+ *
+ * @note due to memory pseudo-reallocation behavior (due to pools), this
+ * can be a memory-intensive operation for large files.
+ */
+svn_error_t *
+svn_stringbuf_from_aprfile(svn_stringbuf_t **result,
+ apr_file_t *file,
+ apr_pool_t *pool);
+
+/** Remove file @a path, a utf8-encoded path. This wraps apr_file_remove(),
+ * converting any error to a Subversion error. If @a ignore_enoent is TRUE, and
+ * the file is not present (APR_STATUS_IS_ENOENT returns TRUE), then no
+ * error will be returned.
+ *
+ * The file will be removed even if it is not writable. (On Windows and
+ * OS/2, this function first clears the file's read-only bit.)
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_io_remove_file2(const char *path,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_io_remove_file2(), except with @a ignore_enoent set to FALSE.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_remove_file(const char *path,
+ apr_pool_t *pool);
+
+/** Recursively remove directory @a path. @a path is utf8-encoded.
+ * If @a ignore_enoent is @c TRUE, don't fail if the target directory
+ * doesn't exist. Use @a pool for temporary allocations.
+ *
+ * Because recursive delete of a directory tree can be a lengthy operation,
+ * provide @a cancel_func and @a cancel_baton for interruptibility.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_io_remove_dir2(const char *path,
+ svn_boolean_t ignore_enoent,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/** Similar to svn_io_remove_dir2(), but with @a ignore_enoent set to
+ * @c FALSE and @a cancel_func and @a cancel_baton set to @c NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_remove_dir(const char *path,
+ apr_pool_t *pool);
+
+/** Read all of the disk entries in directory @a path, a utf8-encoded
+ * path. Set @a *dirents to a hash mapping dirent names (<tt>char *</tt>) to
+ * undefined non-NULL values, allocated in @a pool.
+ *
+ * @note The `.' and `..' directories normally returned by
+ * apr_dir_read() are NOT returned in the hash.
+ *
+ * @since New in 1.4.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_get_dir_filenames(apr_hash_t **dirents,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Read all of the disk entries in directory @a path, a utf8-encoded
+ * path. Set @a *dirents to a hash mapping dirent names (<tt>char *</tt>) to
+ * #svn_io_dirent2_t structures, allocated in @a pool.
+ *
+ * If @a only_check_type is set to @c TRUE, only the kind and special
+ * fields of the svn_io_dirent2_t are filled.
+ *
+ * @note The `.' and `..' directories normally returned by
+ * apr_dir_read() are NOT returned in the hash.
+ *
+ * @note The kind field in the @a dirents is set according to the mapping
+ * as documented for svn_io_check_path().
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_io_get_dirents3(apr_hash_t **dirents,
+ const char *path,
+ svn_boolean_t only_check_type,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Similar to svn_io_get_dirents3, but returns a mapping to svn_io_dirent_t
+ * structures instead of svn_io_dirent2_t and with only a single pool.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_get_dirents2(apr_hash_t **dirents,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Similar to svn_io_get_dirents2(), but @a *dirents is a hash table
+ * with #svn_node_kind_t values.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_get_dirents(apr_hash_t **dirents,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Create a svn_io_dirent2_t instance for path. Specialized variant of
+ * svn_io_stat() that directly translates node_kind and special.
+ *
+ * If @a verify_truename is @c TRUE, an additional check is performed to
+ * verify the truename of the last path component on case insensitive
+ * filesystems. This check is expensive compared to a just a stat,
+ * but certainly cheaper than a full truename calculation using
+ * apr_filepath_merge() which verifies all path components.
+ *
+ * If @a ignore_enoent is set to @c TRUE, set *dirent_p->kind to
+ * svn_node_none instead of returning an error.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_io_stat_dirent2(const svn_io_dirent2_t **dirent_p,
+ const char *path,
+ svn_boolean_t verify_truename,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Similar to svn_io_stat_dirent2, but always passes FALSE for
+ * verify_truename.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backwards compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_stat_dirent(const svn_io_dirent2_t **dirent_p,
+ const char *path,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Callback function type for svn_io_dir_walk() */
+typedef svn_error_t * (*svn_io_walk_func_t)(void *baton,
+ const char *path,
+ const apr_finfo_t *finfo,
+ apr_pool_t *pool);
+
+/** Recursively walk the directory rooted at @a dirname, a
+ * utf8-encoded path, invoking @a walk_func (with @a walk_baton) for
+ * each item in the tree. For a given directory, invoke @a walk_func
+ * on the directory itself before invoking it on any children thereof.
+ *
+ * Deliver to @a walk_func the information specified by @a wanted,
+ * which is a combination of @c APR_FINFO_* flags, plus the
+ * information specified by @c APR_FINFO_TYPE and @c APR_FINFO_NAME.
+ *
+ * Use @a pool for all allocations.
+ *
+ * @note This function does not currently pass all file types to @a
+ * walk_func -- only APR_DIR, APR_REG, and APR_LNK. We reserve the
+ * right to pass additional file types through this interface in the
+ * future, though, so implementations of this callback should
+ * explicitly test FINFO->filetype. See the APR library's
+ * apr_filetype_e enum for the various filetypes and their meanings.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_io_dir_walk2(const char *dirname,
+ apr_int32_t wanted,
+ svn_io_walk_func_t walk_func,
+ void *walk_baton,
+ apr_pool_t *pool);
+
+/** Similar to svn_io_dir_walk(), but only calls @a walk_func for
+ * files of type APR_DIR (directory) and APR_REG (regular file).
+ *
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_dir_walk(const char *dirname,
+ apr_int32_t wanted,
+ svn_io_walk_func_t walk_func,
+ void *walk_baton,
+ apr_pool_t *pool);
+
+/**
+ * Start @a cmd with @a args, using utf8-encoded @a path as working
+ * directory. Return the process handle for the invoked program in @a
+ * *cmd_proc.
+ *
+ * If @a infile_pipe is TRUE, connect @a cmd's stdin to a pipe;
+ * otherwise, connect it to @a infile (which may be NULL). If
+ * @a outfile_pipe is TRUE, connect @a cmd's stdout to a pipe; otherwise,
+ * connect it to @a outfile (which may be NULL). If @a errfile_pipe
+ * is TRUE, connect @a cmd's stderr to a pipe; otherwise, connect it
+ * to @a errfile (which may be NULL). (Callers must pass FALSE for
+ * each of these boolean values for which the corresponding file
+ * handle is non-NULL.)
+ *
+ * @a args is a list of utf8-encoded <tt>const char *</tt> arguments,
+ * terminated by @c NULL. @a args[0] is the name of the program, though it
+ * need not be the same as @a cmd.
+ *
+ * If @a inherit is TRUE, the invoked program inherits its environment from
+ * the caller and @a cmd, if not absolute, is searched for in PATH.
+ *
+ * If @a inherit is FALSE @a cmd must be an absolute path and the invoked
+ * program inherits the environment defined by @a env or runs with an empty
+ * environment in @a env is NULL.
+ *
+ * @note On some platforms, failure to execute @a cmd in the child process
+ * will result in error output being written to @a errfile, if non-NULL, and
+ * a non-zero exit status being returned to the parent process.
+ *
+ * @note An APR bug affects Windows: passing a NULL @a env does not
+ * guarantee the invoked program to run with an empty environment when
+ * @a inherits is FALSE, the program may inherit its parent's environment.
+ * Explicitly pass an empty @a env to get an empty environment.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *svn_io_start_cmd3(apr_proc_t *cmd_proc,
+ const char *path,
+ const char *cmd,
+ const char *const *args,
+ const char *const *env,
+ svn_boolean_t inherit,
+ svn_boolean_t infile_pipe,
+ apr_file_t *infile,
+ svn_boolean_t outfile_pipe,
+ apr_file_t *outfile,
+ svn_boolean_t errfile_pipe,
+ apr_file_t *errfile,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_io_start_cmd3() but with @a env always set to NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API
+ * @since New in 1.7.
+ */
+SVN_DEPRECATED
+svn_error_t *svn_io_start_cmd2(apr_proc_t *cmd_proc,
+ const char *path,
+ const char *cmd,
+ const char *const *args,
+ svn_boolean_t inherit,
+ svn_boolean_t infile_pipe,
+ apr_file_t *infile,
+ svn_boolean_t outfile_pipe,
+ apr_file_t *outfile,
+ svn_boolean_t errfile_pipe,
+ apr_file_t *errfile,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_io_start_cmd2() but with @a infile_pipe, @a
+ * outfile_pipe, and @a errfile_pipe always FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API
+ * @since New in 1.3.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_start_cmd(apr_proc_t *cmd_proc,
+ const char *path,
+ const char *cmd,
+ const char *const *args,
+ svn_boolean_t inherit,
+ apr_file_t *infile,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ apr_pool_t *pool);
+
+/**
+ * Wait for the process @a *cmd_proc to complete and optionally retrieve
+ * its exit code. @a cmd is used only in error messages.
+ *
+ * If @a exitcode is not NULL, set @a *exitcode to the exit code of the
+ * process and do not consider any exit code to be an error. If @a exitcode
+ * is NULL, then if the exit code of the process is non-zero then return an
+ * #SVN_ERR_EXTERNAL_PROGRAM error.
+ *
+ * If @a exitwhy is not NULL, set @a *exitwhy to indicate why the process
+ * terminated and do not consider any reason to be an error. If @a exitwhy
+ * is NULL, then if the termination reason is not @c APR_PROC_CHECK_EXIT()
+ * then return an #SVN_ERR_EXTERNAL_PROGRAM error.
+ *
+ * @since New in 1.3.
+ */
+svn_error_t *
+svn_io_wait_for_cmd(apr_proc_t *cmd_proc,
+ const char *cmd,
+ int *exitcode,
+ apr_exit_why_e *exitwhy,
+ apr_pool_t *pool);
+
+/** Run a command to completion, by first calling svn_io_start_cmd() and
+ * then calling svn_io_wait_for_cmd(). The parameters correspond to
+ * the same-named parameters of those two functions.
+ */
+svn_error_t *
+svn_io_run_cmd(const char *path,
+ const char *cmd,
+ const char *const *args,
+ int *exitcode,
+ apr_exit_why_e *exitwhy,
+ svn_boolean_t inherit,
+ apr_file_t *infile,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ apr_pool_t *pool);
+
+/** Invoke the configured @c diff program, with @a user_args (an array
+ * of utf8-encoded @a num_user_args arguments) if they are specified
+ * (that is, if @a user_args is non-NULL), or "-u" if they are not.
+ * If @a user_args is NULL, the value of @a num_user_args is ignored.
+ *
+ * Diff runs in utf8-encoded @a dir, and its exit status is stored in
+ * @a exitcode, if it is not @c NULL.
+ *
+ * If @a label1 and/or @a label2 are not NULL they will be passed to the diff
+ * process as the arguments of "-L" options. @a label1 and @a label2 are also
+ * in utf8, and will be converted to native charset along with the other args.
+ *
+ * @a from is the first file passed to diff, and @a to is the second. The
+ * stdout of diff will be sent to @a outfile, and the stderr to @a errfile.
+ *
+ * @a diff_cmd must be non-NULL.
+ *
+ * Do all allocation in @a pool.
+ * @since New in 1.6.0.
+ */
+svn_error_t *
+svn_io_run_diff2(const char *dir,
+ const char *const *user_args,
+ int num_user_args,
+ const char *label1,
+ const char *label2,
+ const char *from,
+ const char *to,
+ int *exitcode,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const char *diff_cmd,
+ apr_pool_t *pool);
+
+/** Similar to svn_io_run_diff2() but with @a diff_cmd encoded in internal
+ * encoding used by APR.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.5 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_run_diff(const char *dir,
+ const char *const *user_args,
+ int num_user_args,
+ const char *label1,
+ const char *label2,
+ const char *from,
+ const char *to,
+ int *exitcode,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const char *diff_cmd,
+ apr_pool_t *pool);
+
+
+
+/** Invoke the configured @c diff3 program, in utf8-encoded @a dir
+ * like this:
+ *
+ * diff3 -E -m @a mine @a older @a yours > @a merged
+ *
+ * (See the diff3 documentation for details.)
+ *
+ * If @a user_args is non-NULL, replace "-E" with the <tt>const char*</tt>
+ * elements that @a user_args contains.
+ *
+ * @a mine, @a older and @a yours are utf8-encoded paths (relative to
+ * @a dir or absolute) to three files that already exist.
+ *
+ * @a merged is an open file handle, and is left open after the merge
+ * result is written to it. (@a merged should *not* be the same file
+ * as @a mine, or nondeterministic things may happen!)
+ *
+ * @a mine_label, @a older_label, @a yours_label are utf8-encoded label
+ * parameters for diff3's -L option. Any of them may be @c NULL, in
+ * which case the corresponding @a mine, @a older, or @a yours parameter is
+ * used instead.
+ *
+ * Set @a *exitcode to diff3's exit status. If @a *exitcode is anything
+ * other than 0 or 1, then return #SVN_ERR_EXTERNAL_PROGRAM. (Note the
+ * following from the diff3 info pages: "An exit status of 0 means
+ * `diff3' was successful, 1 means some conflicts were found, and 2
+ * means trouble.")
+ *
+ * @a diff3_cmd must be non-NULL.
+ *
+ * Do all allocation in @a pool.
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_io_run_diff3_3(int *exitcode,
+ const char *dir,
+ const char *mine,
+ const char *older,
+ const char *yours,
+ const char *mine_label,
+ const char *older_label,
+ const char *yours_label,
+ apr_file_t *merged,
+ const char *diff3_cmd,
+ const apr_array_header_t *user_args,
+ apr_pool_t *pool);
+
+/** Similar to svn_io_run_diff3_3(), but with @a diff3_cmd encoded in
+ * internal encoding used by APR.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.5 API.
+ * @since New in 1.4.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_run_diff3_2(int *exitcode,
+ const char *dir,
+ const char *mine,
+ const char *older,
+ const char *yours,
+ const char *mine_label,
+ const char *older_label,
+ const char *yours_label,
+ apr_file_t *merged,
+ const char *diff3_cmd,
+ const apr_array_header_t *user_args,
+ apr_pool_t *pool);
+
+/** Similar to svn_io_run_diff3_2(), but with @a user_args set to @c NULL.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_run_diff3(const char *dir,
+ const char *mine,
+ const char *older,
+ const char *yours,
+ const char *mine_label,
+ const char *older_label,
+ const char *yours_label,
+ apr_file_t *merged,
+ int *exitcode,
+ const char *diff3_cmd,
+ apr_pool_t *pool);
+
+
+/** Parse utf8-encoded @a mimetypes_file as a MIME types file (such as
+ * is provided with Apache HTTP Server), and set @a *type_map to a
+ * hash mapping <tt>const char *</tt> filename extensions to
+ * <tt>const char *</tt> MIME types.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_io_parse_mimetypes_file(apr_hash_t **type_map,
+ const char *mimetypes_file,
+ apr_pool_t *pool);
+
+
+/** Examine utf8-encoded @a file to determine if it can be described by a
+ * known (as in, known by this function) Multipurpose Internet Mail
+ * Extension (MIME) type. If so, set @a *mimetype to a character string
+ * describing the MIME type, else set it to @c NULL.
+ *
+ * If not @c NULL, @a mimetype_map is a hash mapping <tt>const char *</tt>
+ * filename extensions to <tt>const char *</tt> MIME types, and is the
+ * first source consulted regarding @a file's MIME type.
+ *
+ * Use @a pool for any necessary allocations.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_io_detect_mimetype2(const char **mimetype,
+ const char *file,
+ apr_hash_t *mimetype_map,
+ apr_pool_t *pool);
+
+
+/** Like svn_io_detect_mimetype2, but with @a mimetypes_map set to
+ * @c NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_detect_mimetype(const char **mimetype,
+ const char *file,
+ apr_pool_t *pool);
+
+
+/** Examine up to @a len bytes of data in @a buf to determine if the
+ * can be considered binary data, in which case return TRUE.
+ * If the data can be considered plain-text data, return FALSE.
+ *
+ * @since New in 1.7.
+ */
+svn_boolean_t
+svn_io_is_binary_data(const void *buf, apr_size_t len);
+
+
+/** Wrapper for apr_file_open(). @a fname is utf8-encoded.
+ Always passed flag | APR_BINARY to apr. */
+svn_error_t *
+svn_io_file_open(apr_file_t **new_file,
+ const char *fname,
+ apr_int32_t flag,
+ apr_fileperms_t perm,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_file_close(). */
+svn_error_t *
+svn_io_file_close(apr_file_t *file,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_file_getc(). */
+svn_error_t *
+svn_io_file_getc(char *ch,
+ apr_file_t *file,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_file_putc().
+ * @since New in 1.7
+ */
+svn_error_t *
+svn_io_file_putc(char ch,
+ apr_file_t *file,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_file_info_get(). */
+svn_error_t *
+svn_io_file_info_get(apr_finfo_t *finfo,
+ apr_int32_t wanted,
+ apr_file_t *file,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_file_read(). */
+svn_error_t *
+svn_io_file_read(apr_file_t *file,
+ void *buf,
+ apr_size_t *nbytes,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_file_read_full().
+ *
+ * If @a hit_eof is not NULL, EOF will be indicated there and no
+ * svn_error_t error object will be created upon EOF.
+ *
+ * @since New in 1.7
+ */
+svn_error_t *
+svn_io_file_read_full2(apr_file_t *file,
+ void *buf,
+ apr_size_t nbytes,
+ apr_size_t *bytes_read,
+ svn_boolean_t *hit_eof,
+ apr_pool_t *pool);
+
+
+/** Similar to svn_io_file_read_full2 with hit_eof being set
+ * to @c NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_io_file_read_full(apr_file_t *file,
+ void *buf,
+ apr_size_t nbytes,
+ apr_size_t *bytes_read,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_file_seek(). */
+svn_error_t *
+svn_io_file_seek(apr_file_t *file,
+ apr_seek_where_t where,
+ apr_off_t *offset,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_file_write(). */
+svn_error_t *
+svn_io_file_write(apr_file_t *file,
+ const void *buf,
+ apr_size_t *nbytes,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_file_write_full(). */
+svn_error_t *
+svn_io_file_write_full(apr_file_t *file,
+ const void *buf,
+ apr_size_t nbytes,
+ apr_size_t *bytes_written,
+ apr_pool_t *pool);
+
+/**
+ * Open a unique file in @a dirpath, and write @a nbytes from @a buf to
+ * the file before flushing it to disk and closing it. Return the name
+ * of the newly created file in @a *tmp_path, allocated in @a pool.
+ *
+ * If @a dirpath is @c NULL, use the path returned from svn_io_temp_dir().
+ * (Note that when using the system-provided temp directory, it may not
+ * be possible to atomically rename the resulting file due to cross-device
+ * issues.)
+ *
+ * The file will be deleted according to @a delete_when.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_io_write_unique(const char **tmp_path,
+ const char *dirpath,
+ const void *buf,
+ apr_size_t nbytes,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *pool);
+
+/** Wrapper for apr_file_trunc().
+ * @since New in 1.6. */
+svn_error_t *
+svn_io_file_trunc(apr_file_t *file,
+ apr_off_t offset,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_stat(). @a fname is utf8-encoded. */
+svn_error_t *
+svn_io_stat(apr_finfo_t *finfo,
+ const char *fname,
+ apr_int32_t wanted,
+ apr_pool_t *pool);
+
+
+/** Rename and/or move the node (not necessarily a regular file) at
+ * @a from_path to a new path @a to_path within the same filesystem.
+ * In some cases, an existing node at @a to_path will be overwritten.
+ *
+ * A wrapper for apr_file_rename(). @a from_path and @a to_path are
+ * utf8-encoded.
+ */
+svn_error_t *
+svn_io_file_rename(const char *from_path,
+ const char *to_path,
+ apr_pool_t *pool);
+
+
+/** Move the file from @a from_path to @a to_path, even across device
+ * boundaries. Overwrite @a to_path if it exists.
+ *
+ * @note This function is different from svn_io_file_rename in that the
+ * latter fails in the 'across device boundaries' case.
+ *
+ * @since New in 1.3.
+ */
+svn_error_t *
+svn_io_file_move(const char *from_path,
+ const char *to_path,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_dir_make(). @a path is utf8-encoded. */
+svn_error_t *
+svn_io_dir_make(const char *path,
+ apr_fileperms_t perm,
+ apr_pool_t *pool);
+
+/** Same as svn_io_dir_make(), but sets the hidden attribute on the
+ directory on systems that support it. */
+svn_error_t *
+svn_io_dir_make_hidden(const char *path,
+ apr_fileperms_t perm,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_io_dir_make(), but attempts to set the sgid on the
+ * directory on systems that support it. Does not return an error if
+ * the attempt to set the sgid bit fails. On Unix filesystems,
+ * setting the sgid bit on a directory ensures that files and
+ * subdirectories created within inherit group ownership from the
+ * parent instead of from the primary gid.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_io_dir_make_sgid(const char *path,
+ apr_fileperms_t perm,
+ apr_pool_t *pool);
+
+/** Wrapper for apr_dir_open(). @a dirname is utf8-encoded. */
+svn_error_t *
+svn_io_dir_open(apr_dir_t **new_dir,
+ const char *dirname,
+ apr_pool_t *pool);
+
+/** Wrapper for apr_dir_close().
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_io_dir_close(apr_dir_t *thedir);
+
+/** Wrapper for apr_dir_remove(). @a dirname is utf8-encoded.
+ * @note This function has this name to avoid confusion with
+ * svn_io_remove_dir2(), which is recursive.
+ */
+svn_error_t *
+svn_io_dir_remove_nonrecursive(const char *dirname,
+ apr_pool_t *pool);
+
+
+/** Wrapper for apr_dir_read(). Ensures that @a finfo->name is
+ * utf8-encoded, which means allocating @a finfo->name in @a pool,
+ * which may or may not be the same as @a finfo's pool. Use @a pool
+ * for error allocation as well.
+ */
+svn_error_t *
+svn_io_dir_read(apr_finfo_t *finfo,
+ apr_int32_t wanted,
+ apr_dir_t *thedir,
+ apr_pool_t *pool);
+
+/** Wrapper for apr_file_name_get(). @a *filename is utf8-encoded.
+ *
+ * @note The file name may be NULL.
+ *
+ * @since New in 1.7. */
+svn_error_t *
+svn_io_file_name_get(const char **filename,
+ apr_file_t *file,
+ apr_pool_t *pool);
+
+
+
+/** Version/format files.
+ *
+ * @defgroup svn_io_format_files Version/format files
+ * @{
+ */
+
+/** Set @a *version to the integer that starts the file at @a path. If the
+ * file does not begin with a series of digits followed by a newline,
+ * return the error #SVN_ERR_BAD_VERSION_FILE_FORMAT. Use @a pool for
+ * all allocations.
+ */
+svn_error_t *
+svn_io_read_version_file(int *version,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Create (or overwrite) the file at @a path with new contents,
+ * formatted as a non-negative integer @a version followed by a single
+ * newline. On successful completion the file will be read-only. Use
+ * @a pool for all allocations.
+ */
+svn_error_t *
+svn_io_write_version_file(const char *path,
+ int version,
+ apr_pool_t *pool);
+
+/** Read a line of text from a file, up to a specified length.
+ *
+ * Allocate @a *stringbuf in @a result_pool, and read into it one line
+ * from @a file. Reading stops either after a line-terminator was found
+ * or after @a max_len bytes have been read.
+ *
+ * If end-of-file is reached or @a max_len bytes have been read, and @a eof
+ * is not NULL, then set @a *eof to @c TRUE.
+ *
+ * The line-terminator is not stored in @a *stringbuf.
+ * The line-terminator is detected automatically and stored in @a *eol
+ * if @a eol is not NULL. If EOF is reached and @a file does not end
+ * with a newline character, and @a eol is not NULL, @ *eol is set to NULL.
+ *
+ * @a scratch_pool is used for temporary allocations.
+ *
+ * Hint: To read all data until a line-terminator is hit, pass APR_SIZE_MAX
+ * for @a max_len.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_io_file_readline(apr_file_t *file,
+ svn_stringbuf_t **stringbuf,
+ const char **eol,
+ svn_boolean_t *eof,
+ apr_size_t max_len,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_IO_H */
diff --git a/contrib/subversion/subversion/include/svn_iter.h b/contrib/subversion/subversion/include/svn_iter.h
new file mode 100644
index 0000000..ab88935
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_iter.h
@@ -0,0 +1,139 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_iter.h
+ * @brief The Subversion Iteration drivers helper routines
+ *
+ */
+
+#ifndef SVN_ITER_H
+#define SVN_ITER_H
+
+#include <apr.h> /* for apr_ssize_t */
+#include <apr_pools.h> /* for apr_pool_t */
+#include <apr_hash.h> /* for apr_hash_t */
+#include <apr_tables.h> /* for apr_array_header_t */
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** Callback function for use with svn_iter_apr_hash().
+ * Use @a pool for temporary allocation, it's cleared between invocations.
+ *
+ * @a key, @a klen and @a val are the values normally retrieved with
+ * apr_hash_this().
+ *
+ * @a baton is the baton passed into svn_iter_apr_hash().
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_iter_apr_hash_cb_t)(void *baton,
+ const void *key,
+ apr_ssize_t klen,
+ void *val, apr_pool_t *pool);
+
+/** Iterate over the elements in @a hash, calling @a func for each one until
+ * there are no more elements or @a func returns an error.
+ *
+ * Uses @a pool for temporary allocations.
+ *
+ * If @a completed is not NULL, then on return - if @a func returns no
+ * errors - @a *completed will be set to @c TRUE.
+ *
+ * If @a func returns an error other than @c SVN_ERR_ITER_BREAK, that
+ * error is returned. When @a func returns @c SVN_ERR_ITER_BREAK,
+ * iteration is interrupted, but no error is returned and @a *completed is
+ * set to @c FALSE (even if this iteration was the last one).
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_iter_apr_hash(svn_boolean_t *completed,
+ apr_hash_t *hash,
+ svn_iter_apr_hash_cb_t func,
+ void *baton,
+ apr_pool_t *pool);
+
+/** Iteration callback used in conjuction with svn_iter_apr_array().
+ *
+ * Use @a pool for temporary allocation, it's cleared between invocations.
+ *
+ * @a baton is the baton passed to svn_iter_apr_array(). @a item
+ * is a pointer to the item written to the array with the APR_ARRAY_PUSH()
+ * macro.
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_iter_apr_array_cb_t)(void *baton,
+ void *item,
+ apr_pool_t *pool);
+
+/** Iterate over the elements in @a array calling @a func for each one until
+ * there are no more elements or @a func returns an error.
+ *
+ * Uses @a pool for temporary allocations.
+ *
+ * If @a completed is not NULL, then on return - if @a func returns no
+ * errors - @a *completed will be set to @c TRUE.
+ *
+ * If @a func returns an error other than @c SVN_ERR_ITER_BREAK, that
+ * error is returned. When @a func returns @c SVN_ERR_ITER_BREAK,
+ * iteration is interrupted, but no error is returned and @a *completed is
+ * set to @c FALSE (even if this iteration was the last one).
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_iter_apr_array(svn_boolean_t *completed,
+ const apr_array_header_t *array,
+ svn_iter_apr_array_cb_t func,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/** Internal routine used by svn_iter_break() macro.
+ */
+svn_error_t *
+svn_iter__break(void);
+
+
+/** Helper macro to break looping in svn_iter_apr_array() and
+ * svn_iter_apr_hash() driven loops.
+ *
+ * @note The error is just a means of communicating between
+ * driver and callback. There is no need for it to exist
+ * past the lifetime of the iterpool.
+ *
+ * @since New in 1.5.
+ */
+#define svn_iter_break(pool) return svn_iter__break()
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_ITER_H */
diff --git a/contrib/subversion/subversion/include/svn_md5.h b/contrib/subversion/subversion/include/svn_md5.h
new file mode 100644
index 0000000..e6e330d
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_md5.h
@@ -0,0 +1,91 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_md5.h
+ * @brief Converting and comparing MD5 checksums.
+ */
+
+#ifndef SVN_MD5_H
+#define SVN_MD5_H
+
+#include <apr_pools.h> /* for apr_pool_t */
+
+#include "svn_types.h" /* for svn_boolean_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/**
+ * The MD5 digest for the empty string.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ * */
+SVN_DEPRECATED
+const unsigned char *
+svn_md5_empty_string_digest(void);
+
+
+/**
+ * Return the hex representation of @a digest, which must be
+ * @c APR_MD5_DIGESTSIZE bytes long, allocating the string in @a pool.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+const char *
+svn_md5_digest_to_cstring_display(const unsigned char digest[],
+ apr_pool_t *pool);
+
+
+/**
+ * Return the hex representation of @a digest, which must be
+ * @c APR_MD5_DIGESTSIZE bytes long, allocating the string in @a pool.
+ * If @a digest is all zeros, then return NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+const char *
+svn_md5_digest_to_cstring(const unsigned char digest[],
+ apr_pool_t *pool);
+
+
+/**
+ * Compare digests @a d1 and @a d2, each @c APR_MD5_DIGESTSIZE bytes long.
+ * If neither is all zeros, and they do not match, then return FALSE;
+ * else return TRUE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_boolean_t
+svn_md5_digests_match(const unsigned char d1[],
+ const unsigned char d2[]);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_MD5_H */
diff --git a/contrib/subversion/subversion/include/svn_mergeinfo.h b/contrib/subversion/subversion/include/svn_mergeinfo.h
new file mode 100644
index 0000000..ada70a2
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_mergeinfo.h
@@ -0,0 +1,612 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_mergeinfo.h
+ * @brief mergeinfo handling and processing
+ */
+
+
+#ifndef SVN_MERGEINFO_H
+#define SVN_MERGEINFO_H
+
+#include <apr_pools.h>
+#include <apr_tables.h> /* for apr_array_header_t */
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_string.h" /* for svn_string_t */
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** Overview of the @c SVN_PROP_MERGEINFO property.
+ *
+ * Merge history is stored in the @c SVN_PROP_MERGEINFO property of files
+ * and directories. The @c SVN_PROP_MERGEINFO property on a path stores the
+ * complete list of changes merged to that path, either directly or via the
+ * path's parent, grand-parent, etc.. A path may have empty mergeinfo which
+ * means that nothing has been merged to that path or all previous merges
+ * to the path were reversed. Note that a path may have no mergeinfo, this
+ * is not the same as empty mergeinfo.
+ *
+ * Every path in a tree may have @c SVN_PROP_MERGEINFO set, but if the
+ * @c SVN_PROP_MERGEINFO for a path is equivalent to the
+ * @c SVN_PROP_MERGEINFO for its parent, then the @c SVN_PROP_MERGEINFO on
+ * the path will 'elide' (be removed) from the path as a post step to any
+ * merge. If a path's parent does not have any @c SVN_PROP_MERGEINFO set,
+ * the path's mergeinfo can elide to its nearest grand-parent,
+ * great-grand-parent, etc. that has equivalent @c SVN_PROP_MERGEINFO set
+ * on it.
+ *
+ * If a path has no @c SVN_PROP_MERGEINFO of its own, it inherits mergeinfo
+ * from its nearest parent that has @c SVN_PROP_MERGEINFO set. The
+ * exception to this is @c SVN_PROP_MERGEINFO with non-inheritable revision
+ * ranges. These non-inheritable ranges apply only to the path which they
+ * are set on.
+ *
+ * Due to Subversion's allowance for mixed revision working copies, both
+ * elision and inheritance within the working copy presume the path
+ * between a path and its nearest parent with mergeinfo is at the same
+ * working revision. If this is not the case then neither inheritance nor
+ * elision can occur.
+ *
+ * The value of the @c SVN_PROP_MERGEINFO property is either an empty string
+ * (representing empty mergeinfo) or a non-empty string consisting of
+ * a path, a colon, and comma separated revision list, containing one or more
+ * revision or revision ranges. Revision range start and end points are
+ * separated by "-". Revisions and revision ranges may have the optional
+ * @c SVN_MERGEINFO_NONINHERITABLE_STR suffix to signify a non-inheritable
+ * revision/revision range.
+ *
+ * @c SVN_PROP_MERGEINFO Value Grammar:
+ *
+ * Token Definition
+ * ----- ----------
+ * revisionrange REVISION1 "-" REVISION2
+ * revisioneelement (revisionrange | REVISION)"*"?
+ * rangelist revisioneelement (COMMA revisioneelement)*
+ * revisionline PATHNAME COLON rangelist
+ * top "" | (revisionline (NEWLINE revisionline))*
+ *
+ * The PATHNAME is the source of a merge and the rangelist the revision(s)
+ * merged to the path @c SVN_PROP_MERGEINFO is set on directly or indirectly
+ * via inheritance. PATHNAME must always exist at the specified rangelist
+ * and thus a single merge may result in multiple revisionlines if the source
+ * was renamed.
+ *
+ * Rangelists must be sorted from lowest to highest revision and cannot
+ * contain overlapping revisionlistelements. REVISION1 must be less than
+ * REVISION2. Consecutive single revisions that can be represented by a
+ * revisionrange are allowed however (e.g. '5,6,7,8,9-12' or '5-12' are
+ * both acceptable).
+ */
+
+/* Suffix for SVN_PROP_MERGEINFO revision ranges indicating a given
+ range is non-inheritable. */
+#define SVN_MERGEINFO_NONINHERITABLE_STR "*"
+
+/** Terminology for data structures that contain mergeinfo.
+ *
+ * Subversion commonly uses several data structures to represent
+ * mergeinfo in RAM:
+ *
+ * (a) Strings (@c svn_string_t *) containing "unparsed mergeinfo".
+ *
+ * (b) @c svn_rangelist_t, called a "rangelist". An array of non-
+ * overlapping merge ranges (@c svn_merge_range_t *), sorted as said by
+ * @c svn_sort_compare_ranges(). An empty range list is represented by
+ * an empty array. Unless specifically noted otherwise, all APIs require
+ * rangelists that describe only forward ranges, i.e. the range's start
+ * revision is less than its end revision.
+ *
+ * (c) @c svn_mergeinfo_t, called "mergeinfo". A hash mapping merge
+ * source paths (@c const char *, starting with slashes) to
+ * non-empty rangelist arrays. A @c NULL hash is used to represent
+ * no mergeinfo and an empty hash is used to represent empty
+ * mergeinfo.
+ *
+ * (d) @c svn_mergeinfo_catalog_t, called a "mergeinfo catalog". A hash
+ * mapping paths (@c const char *) to @c svn_mergeinfo_t.
+ *
+ * Both @c svn_mergeinfo_t and @c svn_mergeinfo_catalog_t are just
+ * typedefs for @c apr_hash_t *; there is no static type-checking, and
+ * you still use standard @c apr_hash_t functions to interact with
+ * them.
+ *
+ * Note that while the keys of mergeinfos are always absolute from the
+ * repository root, the keys of a catalog may be relative to something
+ * else, such as an RA session root.
+ */
+
+typedef apr_array_header_t svn_rangelist_t;
+typedef apr_hash_t *svn_mergeinfo_t;
+typedef apr_hash_t *svn_mergeinfo_catalog_t;
+
+/** Parse the mergeinfo from @a input into @a *mergeinfo. If no
+ * mergeinfo is available, return an empty mergeinfo (never @c NULL).
+ * Perform temporary allocations in @a pool.
+ *
+ * If @a input is not a grammatically correct @c SVN_PROP_MERGEINFO
+ * property, contains overlapping revision ranges of differing
+ * inheritability, or revision ranges with a start revision greater
+ * than or equal to its end revision, or contains paths mapped to empty
+ * revision ranges, then return @c SVN_ERR_MERGEINFO_PARSE_ERROR.
+ * Unordered revision ranges are allowed, but will be sorted when
+ * placed into @a *mergeinfo. Overlapping revision ranges of the same
+ * inheritability are also allowed, but will be combined into a single
+ * range when placed into @a *mergeinfo.
+ *
+ * @a input may contain relative merge source paths, but these are
+ * converted to absolute paths in @a *mergeinfo.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_mergeinfo_parse(svn_mergeinfo_t *mergeinfo, const char *input,
+ apr_pool_t *pool);
+
+/** Calculate the delta between two mergeinfos, @a mergefrom and @a mergeto
+ * (either or both of which may be @c NULL meaning an empty mergeinfo).
+ * Place the result in @a *deleted and @a *added (neither output argument
+ * may be @c NULL), both allocated in @a result_pool. The resulting
+ * @a *deleted and @a *added will not be null.
+ *
+ * @a consider_inheritance determines how the rangelists in the two
+ * hashes are compared for equality. If @a consider_inheritance is FALSE,
+ * then the start and end revisions of the @c svn_merge_range_t's being
+ * compared are the only factors considered when determining equality.
+ *
+ * e.g. '/trunk: 1,3-4*,5' == '/trunk: 1,3-5'
+ *
+ * If @a consider_inheritance is TRUE, then the inheritability of the
+ * @c svn_merge_range_t's is also considered and must be the same for two
+ * otherwise identical ranges to be judged equal.
+ *
+ * e.g. '/trunk: 1,3-4*,5' != '/trunk: 1,3-5'
+ * '/trunk: 1,3-4*,5' == '/trunk: 1,3-4*,5'
+ * '/trunk: 1,3-4,5' == '/trunk: 1,3-4,5'
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_mergeinfo_diff2(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
+ svn_mergeinfo_t mergefrom, svn_mergeinfo_t mergeto,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_mergeinfo_diff2(), but users only one pool.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_mergeinfo_diff(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
+ svn_mergeinfo_t mergefrom, svn_mergeinfo_t mergeto,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *pool);
+
+/** Merge a shallow copy of one mergeinfo, @a changes, into another mergeinfo
+ * @a mergeinfo.
+ *
+ * Rangelists for merge source paths common to @a changes and @a mergeinfo may
+ * result in new rangelists; these are allocated in @a result_pool.
+ * Temporary allocations are made in @a scratch_pool.
+ *
+ * When intersecting rangelists for a path are merged, the inheritability of
+ * the resulting svn_merge_range_t depends on the inheritability of the
+ * operands. If two non-inheritable ranges are merged the result is always
+ * non-inheritable, in all other cases the resulting range is inheritable.
+ *
+ * e.g. '/A: 1,3-4' merged with '/A: 1,3,4*,5' --> '/A: 1,3-5'
+ * '/A: 1,3-4*' merged with '/A: 1,3,4*,5' --> '/A: 1,3,4*,5'
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_mergeinfo_merge2(svn_mergeinfo_t mergeinfo,
+ svn_mergeinfo_t changes,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Like svn_mergeinfo_merge2, but uses only one pool.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_mergeinfo_merge(svn_mergeinfo_t mergeinfo,
+ svn_mergeinfo_t changes,
+ apr_pool_t *pool);
+
+/** Combine one mergeinfo catalog, @a changes_catalog, into another mergeinfo
+ * catalog @a mergeinfo_catalog. If both catalogs have mergeinfo for the same
+ * key, use svn_mergeinfo_merge() to combine the mergeinfos.
+ *
+ * Additions to @a mergeinfo_catalog are deep copies allocated in
+ * @a result_pool. Temporary allocations are made in @a scratch_pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_mergeinfo_catalog_merge(svn_mergeinfo_catalog_t mergeinfo_catalog,
+ svn_mergeinfo_catalog_t changes_catalog,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Like svn_mergeinfo_remove2, but always considers inheritance.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_mergeinfo_remove(svn_mergeinfo_t *mergeinfo, svn_mergeinfo_t eraser,
+ svn_mergeinfo_t whiteboard, apr_pool_t *pool);
+
+/** Removes @a eraser (the subtrahend) from @a whiteboard (the
+ * minuend), and places the resulting difference in @a *mergeinfo.
+ * Allocates @a *mergeinfo in @a result_pool. Temporary allocations
+ * will be performed in @a scratch_pool.
+ *
+ * @a consider_inheritance determines how to account for the inheritability
+ * of the two mergeinfo's ranges when calculating the range equivalence,
+ * as described for svn_mergeinfo_diff().
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_mergeinfo_remove2(svn_mergeinfo_t *mergeinfo,
+ svn_mergeinfo_t eraser,
+ svn_mergeinfo_t whiteboard,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Calculate the delta between two rangelists consisting of @c
+ * svn_merge_range_t * elements (sorted in ascending order), @a from
+ * and @a to, and place the result in @a *deleted and @a *added
+ * (neither output argument will ever be @c NULL).
+ *
+ * @a consider_inheritance determines how to account for the inheritability
+ * of the two rangelist's ranges when calculating the diff,
+ * as described for svn_mergeinfo_diff().
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_rangelist_diff(svn_rangelist_t **deleted, svn_rangelist_t **added,
+ const svn_rangelist_t *from, const svn_rangelist_t *to,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *pool);
+
+/** Merge two rangelists consisting of @c svn_merge_range_t *
+ * elements, @a rangelist and @a changes, placing the results in
+ * @a rangelist. New elements added to @a rangelist are allocated
+ * in @a result_pool. Either rangelist may be empty.
+ *
+ * When intersecting rangelists are merged, the inheritability of
+ * the resulting svn_merge_range_t depends on the inheritability of the
+ * operands: see svn_mergeinfo_merge().
+ *
+ * Note: @a rangelist and @a changes must be sorted as said by @c
+ * svn_sort_compare_ranges(). @a rangelist is guaranteed to remain
+ * in sorted order and be compacted to the minimal number of ranges
+ * needed to represent the merged result.
+ *
+ * If the original rangelist contains non-collapsed adjacent ranges,
+ * the final result is not guaranteed to be compacted either.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_rangelist_merge2(svn_rangelist_t *rangelist,
+ const svn_rangelist_t *changes,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Like svn_rangelist_merge2(), but with @a rangelist as an input/output
+ * argument. This function always allocates a new rangelist in @a pool and
+ * returns its result in @a *rangelist. It does not modify @a *rangelist
+ * in place. If not used carefully, this function can use up a lot of memory
+ * if called in a loop.
+ *
+ * It performs an extra adjacent range compaction round to make sure non
+ * collapsed input ranges are compacted in the result.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_rangelist_merge(svn_rangelist_t **rangelist,
+ const svn_rangelist_t *changes,
+ apr_pool_t *pool);
+
+/** Removes @a eraser (the subtrahend) from @a whiteboard (the
+ * minuend), and places the resulting difference in @a output.
+ *
+ * Note: @a eraser and @a whiteboard must be sorted as said by @c
+ * svn_sort_compare_ranges(). @a output is guaranteed to be in sorted
+ * order.
+ *
+ * @a consider_inheritance determines how to account for the
+ * @c svn_merge_range_t inheritable field when comparing @a whiteboard's
+ * and @a *eraser's rangelists for equality. @see svn_mergeinfo_diff().
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_rangelist_remove(svn_rangelist_t **output, const svn_rangelist_t *eraser,
+ const svn_rangelist_t *whiteboard,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *pool);
+
+/** Find the intersection of two mergeinfos, @a mergeinfo1 and @a
+ * mergeinfo2, and place the result in @a *mergeinfo, which is (deeply)
+ * allocated in @a result_pool. Temporary allocations will be performed
+ * in @a scratch_pool.
+ *
+ * @a consider_inheritance determines how to account for the inheritability
+ * of the two mergeinfo's ranges when calculating the range equivalence,
+ * @see svn_rangelist_intersect().
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_mergeinfo_intersect2(svn_mergeinfo_t *mergeinfo,
+ svn_mergeinfo_t mergeinfo1,
+ svn_mergeinfo_t mergeinfo2,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Like svn_mergeinfo_intersect2, but always considers inheritance.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_mergeinfo_intersect(svn_mergeinfo_t *mergeinfo,
+ svn_mergeinfo_t mergeinfo1,
+ svn_mergeinfo_t mergeinfo2,
+ apr_pool_t *pool);
+
+/** Find the intersection of two rangelists consisting of @c
+ * svn_merge_range_t * elements, @a rangelist1 and @a rangelist2, and
+ * place the result in @a *rangelist (which is never @c NULL).
+ *
+ * @a consider_inheritance determines how to account for the inheritability
+ * of the two rangelist's ranges when calculating the intersection,
+ * @see svn_mergeinfo_diff(). If @a consider_inheritance is FALSE then
+ * ranges with different inheritance can intersect, but the resulting
+ * @a *rangelist is non-inheritable only if the corresponding ranges from
+ * both @a rangelist1 and @a rangelist2 are non-inheritable.
+ * If @a consider_inheritance is TRUE, then ranges with different
+ * inheritance can never intersect.
+ *
+ * Note: @a rangelist1 and @a rangelist2 must be sorted as said by @c
+ * svn_sort_compare_ranges(). @a *rangelist is guaranteed to be in sorted
+ * order.
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_rangelist_intersect(svn_rangelist_t **rangelist,
+ const svn_rangelist_t *rangelist1,
+ const svn_rangelist_t *rangelist2,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *pool);
+
+/** Reverse @a rangelist, and the @c start and @c end fields of each
+ * range in @a rangelist, in place.
+ *
+ * TODO(miapi): Is this really a valid function? Rangelists that
+ * aren't sorted, or rangelists containing reverse ranges, are
+ * generally not valid in mergeinfo code. Can we rewrite the two
+ * places where this is used?
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_rangelist_reverse(svn_rangelist_t *rangelist, apr_pool_t *pool);
+
+/** Take an array of svn_merge_range_t *'s in @a rangelist, and convert it
+ * back to a text format rangelist in @a output. If @a rangelist contains
+ * no elements, sets @a output to the empty string.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_rangelist_to_string(svn_string_t **output,
+ const svn_rangelist_t *rangelist,
+ apr_pool_t *pool);
+
+/** Return a deep copy of @c svn_merge_range_t *'s in @a rangelist excluding
+ * all non-inheritable @c svn_merge_range_t if @a inheritable is TRUE or
+ * excluding all inheritable @c svn_merge_range_t otherwise. If @a start and
+ * @a end are valid revisions and @a start is less than or equal to @a end,
+ * then exclude only the non-inheritable revision ranges that intersect
+ * inclusively with the range defined by @a start and @a end. If
+ * @a rangelist contains no elements, return an empty array. Allocate the
+ * copy in @a result_pool, use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_rangelist_inheritable2(svn_rangelist_t **inheritable_rangelist,
+ const svn_rangelist_t *rangelist,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t inheritable,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Like svn_rangelist_inheritable2, but always finds inheritable ranges.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_rangelist_inheritable(svn_rangelist_t **inheritable_rangelist,
+ const svn_rangelist_t *rangelist,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ apr_pool_t *pool);
+
+/** Return a deep copy of @a mergeinfo, excluding all non-inheritable
+ * @c svn_merge_range_t if @a inheritable is TRUE or excluding all
+ * inheritable @c svn_merge_range_t otherwise. If @a start and @a end
+ * are valid revisions and @a start is less than or equal to @a end,
+ * then exclude only the non-inheritable revisions that intersect
+ * inclusively with the range defined by @a start and @a end. If @a path
+ * is not NULL remove non-inheritable ranges only for @a path. If all
+ * ranges are removed for a given path then remove that path as well.
+ * If all paths are removed or @a rangelist is empty then set
+ * @a *inheritable_rangelist to an empty array. Allocate the copy in
+ * @a result_pool, use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_mergeinfo_inheritable2(svn_mergeinfo_t *inheritable_mergeinfo,
+ svn_mergeinfo_t mergeinfo,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t inheritable,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Like svn_mergeinfo_inheritable2, but always finds inheritable mergeinfo.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_mergeinfo_inheritable(svn_mergeinfo_t *inheritable_mergeinfo,
+ svn_mergeinfo_t mergeinfo,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ apr_pool_t *pool);
+
+/** Take a mergeinfo in @a mergeinput, and convert it to unparsed
+ * mergeinfo. Set @a *output to the result, allocated in @a pool.
+ * If @a input contains no elements, set @a *output to the empty string.
+ *
+ * @a mergeinput may contain relative merge source paths, but these are
+ * converted to absolute paths in @a *output.
+ *
+ * @since New in 1.5.
+*/
+svn_error_t *
+svn_mergeinfo_to_string(svn_string_t **output,
+ svn_mergeinfo_t mergeinput,
+ apr_pool_t *pool);
+
+/** Take a hash of mergeinfo in @a mergeinfo, and sort the rangelists
+ * associated with each key (in place).
+ *
+ * TODO(miapi): mergeinfos should *always* be sorted. This should be
+ * a private function.
+ *
+ * @since New in 1.5
+ */
+svn_error_t *
+svn_mergeinfo_sort(svn_mergeinfo_t mergeinfo, apr_pool_t *pool);
+
+/** Return a deep copy of @a mergeinfo_catalog, allocated in @a pool.
+ *
+ * @since New in 1.6.
+ */
+svn_mergeinfo_catalog_t
+svn_mergeinfo_catalog_dup(svn_mergeinfo_catalog_t mergeinfo_catalog,
+ apr_pool_t *pool);
+
+/** Return a deep copy of @a mergeinfo, allocated in @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_mergeinfo_t
+svn_mergeinfo_dup(svn_mergeinfo_t mergeinfo, apr_pool_t *pool);
+
+/** Return a deep copy of @a rangelist, allocated in @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_rangelist_t *
+svn_rangelist_dup(const svn_rangelist_t *rangelist, apr_pool_t *pool);
+
+
+/**
+ * The three ways to request mergeinfo affecting a given path.
+ *
+ * @since New in 1.5.
+ */
+typedef enum svn_mergeinfo_inheritance_t
+{
+ /** Explicit mergeinfo only. */
+ svn_mergeinfo_explicit,
+
+ /** Explicit mergeinfo, or if that doesn't exist, the inherited
+ mergeinfo from a target's nearest (path-wise, not history-wise)
+ ancestor. */
+ svn_mergeinfo_inherited,
+
+ /** Mergeinfo inherited from a target's nearest (path-wise, not
+ history-wise) ancestor, regardless of whether target has explicit
+ mergeinfo. */
+ svn_mergeinfo_nearest_ancestor
+} svn_mergeinfo_inheritance_t;
+
+/** Return a constant string expressing @a inherit as an English word,
+ * i.e., "explicit" (default), "inherited", or "nearest_ancestor".
+ * The string is not localized, as it may be used for client<->server
+ * communications.
+ *
+ * @since New in 1.5.
+ */
+const char *
+svn_inheritance_to_word(svn_mergeinfo_inheritance_t inherit);
+
+
+/** Return the appropriate @c svn_mergeinfo_inheritance_t for @a word.
+ * @a word is as returned from svn_inheritance_to_word(). Defaults to
+ * @c svn_mergeinfo_explicit.
+ *
+ * @since New in 1.5.
+ */
+svn_mergeinfo_inheritance_t
+svn_inheritance_from_word(const char *word);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_MERGEINFO_H */
diff --git a/contrib/subversion/subversion/include/svn_nls.h b/contrib/subversion/subversion/include/svn_nls.h
new file mode 100644
index 0000000..6a5bc1b
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_nls.h
@@ -0,0 +1,56 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_nls.h
+ * @brief Support functions for NLS programs
+ */
+
+
+
+#ifndef SVN_NLS_H
+#define SVN_NLS_H
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** Set up the NLS.
+ * Return the error @c APR_EINVAL or @c APR_INCOMPLETE if an
+ * error occurs.
+ *
+ * @note This function is for bindings. You should usually
+ * use svn_cmdline_init() instead of calling this
+ * function directly. This function should be called
+ * after initializing APR.
+ *
+ * @since New in 1.3.
+ */
+svn_error_t *
+svn_nls_init(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_NLS_H */
diff --git a/contrib/subversion/subversion/include/svn_opt.h b/contrib/subversion/subversion/include/svn_opt.h
new file mode 100644
index 0000000..25da44f
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_opt.h
@@ -0,0 +1,779 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_opt.h
+ * @brief Option and argument parsing for Subversion command lines
+ */
+
+#ifndef SVN_OPTS_H
+#define SVN_OPTS_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_getopt.h>
+#include <apr_tables.h>
+#include <apr_hash.h>
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+#define APR_WANT_STDIO
+#endif
+#include <apr_want.h> /* for FILE* */
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/**
+ * All subcommand procedures in Subversion conform to this prototype.
+ *
+ * @a os is the apr option state after getopt processing has been run; in
+ * other words, it still contains the non-option arguments following
+ * the subcommand. See @a os->argv and @a os->ind.
+ *
+ * @a baton is anything you need it to be.
+ *
+ * @a pool is used for allocating errors, and for any other allocation
+ * unless the instance is explicitly documented to allocate from a
+ * pool in @a baton.
+ */
+typedef svn_error_t *(svn_opt_subcommand_t)(
+ apr_getopt_t *os, void *baton, apr_pool_t *pool);
+
+
+/** The maximum number of aliases a subcommand can have. */
+#define SVN_OPT_MAX_ALIASES 3
+
+/** The maximum number of options that can be accepted by a subcommand. */
+#define SVN_OPT_MAX_OPTIONS 50
+
+/** Options that have no short option char should use an identifying
+ * integer equal to or greater than this.
+ */
+#define SVN_OPT_FIRST_LONGOPT_ID 256
+
+
+/** One element of a subcommand dispatch table.
+ *
+ * @since New in 1.4.
+ */
+typedef struct svn_opt_subcommand_desc2_t
+{
+ /** The full name of this command. */
+ const char *name;
+
+ /** The function this command invokes. */
+ svn_opt_subcommand_t *cmd_func;
+
+ /** A list of alias names for this command (e.g., 'up' for 'update'). */
+ const char *aliases[SVN_OPT_MAX_ALIASES];
+
+ /** A brief string describing this command, for usage messages. */
+ const char *help;
+
+ /** A list of options accepted by this command. Each value in the
+ * array is a unique enum (the 2nd field in apr_getopt_option_t)
+ */
+ int valid_options[SVN_OPT_MAX_OPTIONS];
+
+ /** A list of option help descriptions, keyed by the option unique enum
+ * (the 2nd field in apr_getopt_option_t), which override the generic
+ * descriptions given in an apr_getopt_option_t on a per-subcommand basis.
+ */
+ struct { int optch; const char *desc; } desc_overrides[SVN_OPT_MAX_OPTIONS];
+} svn_opt_subcommand_desc2_t;
+
+
+/** One element of a subcommand dispatch table.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ *
+ * Like #svn_opt_subcommand_desc2_t but lacking the @c desc_overrides
+ * member.
+ */
+typedef struct svn_opt_subcommand_desc_t
+{
+ /** The full name of this command. */
+ const char *name;
+
+ /** The function this command invokes. */
+ svn_opt_subcommand_t *cmd_func;
+
+ /** A list of alias names for this command (e.g., 'up' for 'update'). */
+ const char *aliases[SVN_OPT_MAX_ALIASES];
+
+ /** A brief string describing this command, for usage messages. */
+ const char *help;
+
+ /** A list of options accepted by this command. Each value in the
+ * array is a unique enum (the 2nd field in apr_getopt_option_t)
+ */
+ int valid_options[SVN_OPT_MAX_OPTIONS];
+
+} svn_opt_subcommand_desc_t;
+
+
+/**
+ * Return the entry in @a table whose name matches @a cmd_name, or @c NULL if
+ * none. @a cmd_name may be an alias.
+ *
+ * @since New in 1.4.
+ */
+const svn_opt_subcommand_desc2_t *
+svn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table,
+ const char *cmd_name);
+
+
+/**
+ * Return the entry in @a table whose name matches @a cmd_name, or @c NULL if
+ * none. @a cmd_name may be an alias.
+ *
+ * Same as svn_opt_get_canonical_subcommand2(), but acts on
+ * #svn_opt_subcommand_desc_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+const svn_opt_subcommand_desc_t *
+svn_opt_get_canonical_subcommand(const svn_opt_subcommand_desc_t *table,
+ const char *cmd_name);
+
+
+/**
+ * Return pointer to an @c apr_getopt_option_t for the option whose
+ * option code is @a code, or @c NULL if no match. @a option_table must end
+ * with an element whose every field is zero. If @a command is non-NULL,
+ * then return the subcommand-specific option description instead of the
+ * generic one, if a specific description is defined.
+ *
+ * The returned value may be statically allocated, or allocated in @a pool.
+ *
+ * @since New in 1.4.
+ */
+const apr_getopt_option_t *
+svn_opt_get_option_from_code2(int code,
+ const apr_getopt_option_t *option_table,
+ const svn_opt_subcommand_desc2_t *command,
+ apr_pool_t *pool);
+
+
+/**
+ * Return the first entry from @a option_table whose option code is @a code,
+ * or @c NULL if no match. @a option_table must end with an element whose
+ * every field is zero.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+const apr_getopt_option_t *
+svn_opt_get_option_from_code(int code,
+ const apr_getopt_option_t *option_table);
+
+
+/**
+ * Return @c TRUE iff subcommand @a command supports option @a
+ * option_code, else return @c FALSE. If @a global_options is
+ * non-NULL, it is a zero-terminated array, and all subcommands take
+ * the options listed in it.
+ *
+ * @since New in 1.5.
+ */
+svn_boolean_t
+svn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command,
+ int option_code,
+ const int *global_options);
+
+/**
+ * Same as svn_opt_subcommand_takes_option3(), but with @c NULL for @a
+ * global_options.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_boolean_t
+svn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command,
+ int option_code);
+
+
+/**
+ * Return @c TRUE iff subcommand @a command supports option @a option_code,
+ * else return @c FALSE.
+ *
+ * Same as svn_opt_subcommand_takes_option2(), but acts on
+ * #svn_opt_subcommand_desc_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_boolean_t
+svn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command,
+ int option_code);
+
+
+/**
+ * Print a generic (not command-specific) usage message to @a stream.
+ *
+ * ### @todo Why is @a stream a stdio file instead of an svn stream?
+ *
+ * If @a header is non-NULL, print @a header followed by a newline. Then
+ * loop over @a cmd_table printing the usage for each command (getting
+ * option usages from @a opt_table). Then if @a footer is non-NULL, print
+ * @a footer followed by a newline.
+ *
+ * Use @a pool for temporary allocation.
+ *
+ * @since New in 1.4.
+ */
+void
+svn_opt_print_generic_help2(const char *header,
+ const svn_opt_subcommand_desc2_t *cmd_table,
+ const apr_getopt_option_t *opt_table,
+ const char *footer,
+ apr_pool_t *pool,
+ FILE *stream);
+
+
+/**
+ * Same as svn_opt_print_generic_help2(), but acts on
+ * #svn_opt_subcommand_desc_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+void
+svn_opt_print_generic_help(const char *header,
+ const svn_opt_subcommand_desc_t *cmd_table,
+ const apr_getopt_option_t *opt_table,
+ const char *footer,
+ apr_pool_t *pool,
+ FILE *stream);
+
+
+/**
+ * Print an option @a opt nicely into a @a string allocated in @a pool.
+ * If @a doc is set, include the generic documentation string of @a opt,
+ * localized to the current locale if a translation is available.
+ */
+void
+svn_opt_format_option(const char **string,
+ const apr_getopt_option_t *opt,
+ svn_boolean_t doc,
+ apr_pool_t *pool);
+
+
+
+/**
+ * Get @a subcommand's usage from @a table, and print it to @c stdout.
+ * Obtain option usage from @a options_table. If not @c NULL, @a
+ * global_options is a zero-terminated list of global options. Use @a
+ * pool for temporary allocation. @a subcommand may be a canonical
+ * command name or an alias. ### @todo Why does this only print to
+ * @c stdout, whereas svn_opt_print_generic_help() gives us a choice?
+ *
+ * When printing the description of an option, if the same option code
+ * appears a second time in @a options_table with a different name, then
+ * use that second name as an alias for the first name. This additional
+ * behaviour is new in 1.7.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_opt_subcommand_help3(const char *subcommand,
+ const svn_opt_subcommand_desc2_t *table,
+ const apr_getopt_option_t *options_table,
+ const int *global_options,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_opt_subcommand_help3(), but with @a global_options
+ * always NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+void
+svn_opt_subcommand_help2(const char *subcommand,
+ const svn_opt_subcommand_desc2_t *table,
+ const apr_getopt_option_t *options_table,
+ apr_pool_t *pool);
+
+
+/**
+ * Same as svn_opt_subcommand_help2(), but acts on
+ * #svn_opt_subcommand_desc_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+void
+svn_opt_subcommand_help(const char *subcommand,
+ const svn_opt_subcommand_desc_t *table,
+ const apr_getopt_option_t *options_table,
+ apr_pool_t *pool);
+
+
+
+/* Parsing revision and date options. */
+
+/**
+ * Various ways of specifying revisions.
+ *
+ * @note
+ * In contexts where local mods are relevant, the `working' kind
+ * refers to the uncommitted "working" revision, which may be modified
+ * with respect to its base revision. In other contexts, `working'
+ * should behave the same as `committed' or `current'.
+ */
+enum svn_opt_revision_kind {
+ /** No revision information given. */
+ svn_opt_revision_unspecified,
+
+ /** revision given as number */
+ svn_opt_revision_number,
+
+ /** revision given as date */
+ svn_opt_revision_date,
+
+ /** rev of most recent change */
+ svn_opt_revision_committed,
+
+ /** (rev of most recent change) - 1 */
+ svn_opt_revision_previous,
+
+ /** .svn/entries current revision */
+ svn_opt_revision_base,
+
+ /** current, plus local mods */
+ svn_opt_revision_working,
+
+ /** repository youngest */
+ svn_opt_revision_head
+
+ /* please update svn_opt__revision_to_string() when extending this enum */
+};
+
+/**
+ * A revision value, which can be specified as a number or a date.
+ *
+ * @note This union was formerly an anonymous inline type in
+ * @c svn_opt_revision_t, and was converted to a named type just to
+ * make things easier for SWIG.
+ *
+ * @since New in 1.3.
+ */
+typedef union svn_opt_revision_value_t
+{
+ /** The revision number */
+ svn_revnum_t number;
+
+ /** the date of the revision */
+ apr_time_t date;
+} svn_opt_revision_value_t;
+
+/** A revision, specified in one of @c svn_opt_revision_kind ways. */
+typedef struct svn_opt_revision_t
+{
+ enum svn_opt_revision_kind kind; /**< See svn_opt_revision_kind */
+ svn_opt_revision_value_t value; /**< Extra data qualifying the @c kind */
+} svn_opt_revision_t;
+
+/** A revision range, specified in one of @c svn_opt_revision_kind ways. */
+typedef struct svn_opt_revision_range_t
+{
+ /** The first revision in the range */
+ svn_opt_revision_t start;
+
+ /** The last revision in the range */
+ svn_opt_revision_t end;
+} svn_opt_revision_range_t;
+
+/**
+ * Set @a *start_revision and/or @a *end_revision according to @a arg,
+ * where @a arg is "N" or "N:M", like so:
+ *
+ * - If @a arg is "N", set @a *start_revision to represent N, and
+ * leave @a *end_revision untouched.
+ *
+ * - If @a arg is "N:M", set @a *start_revision and @a *end_revision
+ * to represent N and M respectively.
+ *
+ * N and/or M may be one of the special revision descriptors
+ * recognized by revision_from_word(), or a date in curly braces.
+ *
+ * If @a arg is invalid, return -1; else return 0.
+ * It is invalid to omit a revision (as in, ":", "N:" or ":M").
+ *
+ * @note It is typical, though not required, for @a *start_revision and
+ * @a *end_revision to be @c svn_opt_revision_unspecified kind on entry.
+ *
+ * Use @a pool for temporary allocations.
+ */
+int
+svn_opt_parse_revision(svn_opt_revision_t *start_revision,
+ svn_opt_revision_t *end_revision,
+ const char *arg,
+ apr_pool_t *pool);
+
+/**
+ * Parse @a arg, where @a arg is "N" or "N:M", into a
+ * @c svn_opt_revision_range_t and push that onto @a opt_ranges.
+ *
+ * - If @a arg is "N", set the @c start field of the
+ * @c svn_opt_revision_range_t to represent N and the @c end field
+ * to @c svn_opt_revision_unspecified.
+ *
+ * - If @a arg is "N:M", set the @c start field of the
+ * @c svn_opt_revision_range_t to represent N and the @c end field
+ * to represent M.
+ *
+ * If @a arg is invalid, return -1; else return 0. It is invalid to omit
+ * a revision (as in, ":", "N:" or ":M").
+ *
+ * Use @a pool to allocate @c svn_opt_revision_range_t pushed to the array.
+ *
+ * @since New in 1.5.
+ */
+int
+svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
+ const char *arg,
+ apr_pool_t *pool);
+
+/**
+ * Resolve peg revisions and operational revisions in the following way:
+ *
+ * - If @a is_url is set and @a peg_rev->kind is
+ * @c svn_opt_revision_unspecified, @a peg_rev->kind defaults to
+ * @c svn_opt_revision_head.
+ *
+ * - If @a is_url is not set, and @a peg_rev->kind is
+ * @c svn_opt_revision_unspecified, @a peg_rev->kind defaults to
+ * @c svn_opt_revision_base.
+ *
+ * - If @a op_rev->kind is @c svn_opt_revision_unspecified, @a op_rev
+ * defaults to @a peg_rev.
+ *
+ * Both @a peg_rev and @a op_rev may be modified as a result of this
+ * function. @a is_url should be set if the path the revisions refer to is
+ * a url, and unset otherwise.
+ *
+ * If @a notice_local_mods is set, @c svn_opt_revision_working is used,
+ * instead of @c svn_opt_revision_base.
+ *
+ * Use @a pool for allocations.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
+ svn_opt_revision_t *op_rev,
+ svn_boolean_t is_url,
+ svn_boolean_t notice_local_mods,
+ apr_pool_t *pool);
+
+
+/* Parsing arguments. */
+
+/**
+ * Pull remaining target arguments from @a os into @a *targets_p,
+ * converting them to UTF-8, followed by targets from @a known_targets
+ * (which might come from, for example, the "--targets" command line
+ * option), which are already in UTF-8.
+ *
+ * On each URL target, do some IRI-to-URI encoding and some
+ * auto-escaping. On each local path, canonicalize case and path
+ * separators.
+ *
+ * Allocate @a *targets_p and its elements in @a pool.
+ *
+ * If a path has the same name as a Subversion working copy
+ * administrative directory, return SVN_ERR_RESERVED_FILENAME_SPECIFIED;
+ * if multiple reserved paths are encountered, return a chain of
+ * errors, all of which are SVN_ERR_RESERVED_FILENAME_SPECIFIED. Do
+ * not return this type of error in a chain with any other type of
+ * error, and if this is the only type of error encountered, complete
+ * the operation before returning the error(s).
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ * @see svn_client_args_to_target_array()
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_opt_args_to_target_array3(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ apr_pool_t *pool);
+
+/**
+ * This is the same as svn_opt_args_to_target_array3() except that it
+ * silently ignores paths that have the same name as a working copy
+ * administrative directory.
+ *
+ * @since New in 1.2.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_opt_args_to_target_array2(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ apr_pool_t *pool);
+
+
+/**
+ * The same as svn_opt_args_to_target_array2() except that, in
+ * addition, if @a extract_revisions is set, then look for trailing
+ * "@rev" syntax on the first two paths. If the first target in @a
+ * *targets_p ends in "@rev", replace it with a canonicalized version of
+ * the part before "@rev" and replace @a *start_revision with the value
+ * of "rev". If the second target in @a *targets_p ends in "@rev",
+ * replace it with a canonicalized version of the part before "@rev"
+ * and replace @a *end_revision with the value of "rev". Ignore
+ * revision specifiers on any further paths. "rev" can be any form of
+ * single revision specifier, as accepted by svn_opt_parse_revision().
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_opt_args_to_target_array(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_opt_revision_t *start_revision,
+ svn_opt_revision_t *end_revision,
+ svn_boolean_t extract_revisions,
+ apr_pool_t *pool);
+
+
+/**
+ * Parse revprop key/value pair from @a revprop_spec (name[=value]) into
+ * @a revprops, making copies of both with @a pool. If @a revprops is
+ * @c NULL, allocate a new apr_hash_t in it. @a revprops maps
+ * const char * revprop names to svn_string_t * revprop values for use
+ * with svn_repos_get_commit_editor5 and other get_commit_editor APIs.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_opt_parse_revprop(apr_hash_t **revprops, const char *revprop_spec,
+ apr_pool_t *pool);
+
+
+/**
+ * If no targets exist in @a *targets, add `.' as the lone target.
+ *
+ * (Some commands take an implicit "." string argument when invoked
+ * with no arguments. Those commands make use of this function to
+ * add "." to the target array if the user passes no args.)
+ */
+void
+svn_opt_push_implicit_dot_target(apr_array_header_t *targets,
+ apr_pool_t *pool);
+
+
+/**
+ * Parse @a num_args non-target arguments from the list of arguments in
+ * @a os->argv, return them as <tt>const char *</tt> in @a *args_p, without
+ * doing any UTF-8 conversion. Allocate @a *args_p and its values in @a pool.
+ */
+svn_error_t *
+svn_opt_parse_num_args(apr_array_header_t **args_p,
+ apr_getopt_t *os,
+ int num_args,
+ apr_pool_t *pool);
+
+
+/**
+ * Parse all remaining arguments from @a os->argv, return them as
+ * <tt>const char *</tt> in @a *args_p, without doing any UTF-8 conversion.
+ * Allocate @a *args_p and its values in @a pool.
+ */
+svn_error_t *
+svn_opt_parse_all_args(apr_array_header_t **args_p,
+ apr_getopt_t *os,
+ apr_pool_t *pool);
+
+/**
+ * Parse a working-copy path or URL in @a path, extracting any trailing
+ * revision specifier of the form "@rev" from the last component of
+ * the path.
+ *
+ * Some examples would be:
+ *
+ * - "foo/bar" -> "foo/bar", (unspecified)
+ * - "foo/bar@13" -> "foo/bar", (number, 13)
+ * - "foo/bar@HEAD" -> "foo/bar", (head)
+ * - "foo/bar@{1999-12-31}" -> "foo/bar", (date, 1999-12-31)
+ * - "http://a/b@27" -> "http://a/b", (number, 27)
+ * - "http://a/b@COMMITTED" -> "http://a/b", (committed) [*]
+ * - "http://a/b@{1999-12-31}" -> "http://a/b", (date, 1999-12-31)
+ * - "http://a/b@%7B1999-12-31%7D" -> "http://a/b", (date, 1999-12-31)
+ * - "foo/bar@1:2" -> error
+ * - "foo/bar@baz" -> error
+ * - "foo/bar@" -> "foo/bar", (unspecified)
+ * - "foo/@bar@" -> "foo/@bar", (unspecified)
+ * - "foo/bar/@13" -> "foo/bar/", (number, 13)
+ * - "foo/bar@@13" -> "foo/bar@", (number, 13)
+ * - "foo/@bar@HEAD" -> "foo/@bar", (head)
+ * - "foo@/bar" -> "foo@/bar", (unspecified)
+ * - "foo@HEAD/bar" -> "foo@HEAD/bar", (unspecified)
+ * - "@foo/bar" -> "@foo/bar", (unspecified)
+ * - "@foo/bar@" -> "@foo/bar", (unspecified)
+ *
+ * [*] Syntactically valid but probably not semantically useful.
+ *
+ * If a trailing revision specifier is found, parse it into @a *rev and
+ * put the rest of the path into @a *truepath, allocating from @a pool;
+ * or return an @c SVN_ERR_CL_ARG_PARSING_ERROR (with the effect on
+ * @a *truepath undefined) if the revision specifier is invalid.
+ * If no trailing revision specifier is found, set @a *truepath to
+ * @a path and @a rev->kind to @c svn_opt_revision_unspecified.
+ *
+ * This function does not require that @a path be in canonical form.
+ * No canonicalization is done and @a *truepath will only be in
+ * canonical form if @a path is in canonical form.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_opt_parse_path(svn_opt_revision_t *rev,
+ const char **truepath,
+ const char *path,
+ apr_pool_t *pool);
+
+/**
+ * Central dispatcher function for various kinds of help message.
+ * Prints one of:
+ * * subcommand-specific help (svn_opt_subcommand_help)
+ * * generic help (svn_opt_print_generic_help)
+ * * version info
+ * * simple usage complaint: "Type '@a pgm_name help' for usage."
+ *
+ * If @a os is not @c NULL and it contains arguments, then try
+ * printing help for them as though they are subcommands, using @a
+ * cmd_table and @a option_table for option information. If not @c
+ * NULL, @a global_options is a zero-terminated array of options taken
+ * by all subcommands.
+ *
+ * Else, if @a print_version is TRUE, then print version info, in
+ * brief form if @a quiet is also TRUE; if @a quiet is FALSE, then if
+ * @a version_footer is non-NULL, print it following the version
+ * information. If @a verbose is TRUE, also print information about
+ * the running system and loaded shared libraries, where available.
+ *
+ * Else, if @a os is not @c NULL and does not contain arguments, print
+ * generic help, via svn_opt_print_generic_help2() with the @a header,
+ * @a cmd_table, @a option_table, and @a footer arguments.
+ *
+ * Else, when @a os is @c NULL, print the simple usage complaint.
+ *
+ * Use @a pool for temporary allocations.
+ *
+ * Notes: The reason this function handles both version printing and
+ * general usage help is that a confused user might put both the
+ * --version flag *and* subcommand arguments on a help command line.
+ * The logic for handling such a situation should be in one place.
+ *
+ * @since New in 1.8.
+ */
+
+svn_error_t *
+svn_opt_print_help4(apr_getopt_t *os,
+ const char *pgm_name,
+ svn_boolean_t print_version,
+ svn_boolean_t quiet,
+ svn_boolean_t verbose,
+ const char *version_footer,
+ const char *header,
+ const svn_opt_subcommand_desc2_t *cmd_table,
+ const apr_getopt_option_t *option_table,
+ const int *global_options,
+ const char *footer,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_opt_print_help4(), but with @a verbose always @c FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+
+SVN_DEPRECATED
+svn_error_t *
+svn_opt_print_help3(apr_getopt_t *os,
+ const char *pgm_name,
+ svn_boolean_t print_version,
+ svn_boolean_t quiet,
+ const char *version_footer,
+ const char *header,
+ const svn_opt_subcommand_desc2_t *cmd_table,
+ const apr_getopt_option_t *option_table,
+ const int *global_options,
+ const char *footer,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_opt_print_help3(), but with @a global_options always @c
+ * NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+
+SVN_DEPRECATED
+svn_error_t *
+svn_opt_print_help2(apr_getopt_t *os,
+ const char *pgm_name,
+ svn_boolean_t print_version,
+ svn_boolean_t quiet,
+ const char *version_footer,
+ const char *header,
+ const svn_opt_subcommand_desc2_t *cmd_table,
+ const apr_getopt_option_t *option_table,
+ const char *footer,
+ apr_pool_t *pool);
+
+
+/**
+ * Same as svn_opt_print_help2(), but acts on #svn_opt_subcommand_desc_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_opt_print_help(apr_getopt_t *os,
+ const char *pgm_name,
+ svn_boolean_t print_version,
+ svn_boolean_t quiet,
+ const char *version_footer,
+ const char *header,
+ const svn_opt_subcommand_desc_t *cmd_table,
+ const apr_getopt_option_t *option_table,
+ const char *footer,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_OPTS_H */
diff --git a/contrib/subversion/subversion/include/svn_path.h b/contrib/subversion/subversion/include/svn_path.h
new file mode 100644
index 0000000..3478003
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_path.h
@@ -0,0 +1,734 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_path.h
+ * @brief A path manipulation library
+ *
+ * All incoming and outgoing paths are non-NULL and in UTF-8, unless
+ * otherwise documented.
+ *
+ * No result path ever ends with a separator, no matter whether the
+ * path is a file or directory, because we always canonicalize() it.
+ *
+ * Nearly all the @c svn_path_xxx functions expect paths passed into
+ * them to be in canonical form as defined by the Subversion path
+ * library itself. The only functions which do *not* have such
+ * expectations are:
+ *
+ * - @c svn_path_canonicalize()
+ * - @c svn_path_is_canonical()
+ * - @c svn_path_internal_style()
+ * - @c svn_path_uri_encode()
+ *
+ * For the most part, we mean what most anyone would mean when talking
+ * about canonical paths, but to be on the safe side, you must run
+ * your paths through @c svn_path_canonicalize() before passing them to
+ * other functions in this API.
+ */
+
+#ifndef SVN_PATH_H
+#define SVN_PATH_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/** Convert @a path from the local style to the canonical internal style.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_dirent_internal_style().
+ */
+SVN_DEPRECATED
+const char *
+svn_path_internal_style(const char *path, apr_pool_t *pool);
+
+/** Convert @a path from the canonical internal style to the local style.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_dirent_local_style().
+ */
+SVN_DEPRECATED
+const char *
+svn_path_local_style(const char *path, apr_pool_t *pool);
+
+
+/** Join a base path (@a base) with a component (@a component), allocating
+ * the result in @a pool. @a component need not be a single component: it
+ * can be any path, absolute or relative to @a base.
+ *
+ * If either @a base or @a component is the empty path, then the other
+ * argument will be copied and returned. If both are the empty path the
+ * empty path is returned.
+ *
+ * If the @a component is an absolute path, then it is copied and returned.
+ * Exactly one slash character ('/') is used to join the components,
+ * accounting for any trailing slash in @a base.
+ *
+ * Note that the contents of @a base are not examined, so it is possible to
+ * use this function for constructing URLs, or for relative URLs or
+ * repository paths.
+ *
+ * This function is NOT appropriate for native (local) file
+ * paths. Only for "internal" canonicalized paths, since it uses '/'
+ * for the separator. Further, an absolute path (for @a component) is
+ * based on a leading '/' character. Thus, an "absolute URI" for the
+ * @a component won't be detected. An absolute URI can only be used
+ * for the base.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_dirent_join(), svn_relpath_join() or
+ * svn_fspath__join().
+ */
+SVN_DEPRECATED
+char *
+svn_path_join(const char *base, const char *component, apr_pool_t *pool);
+
+/** Join multiple components onto a @a base path, allocated in @a pool. The
+ * components are terminated by a @c NULL.
+ *
+ * If any component is the empty string, it will be ignored.
+ *
+ * If any component is an absolute path, then it resets the base and
+ * further components will be appended to it.
+ *
+ * This function does not support URLs.
+ *
+ * See svn_path_join() for further notes about joining paths.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * For new code, consider using svn_dirent_join_many() or a sequence of
+ * calls to one of the *_join() functions.
+ */
+SVN_DEPRECATED
+char *
+svn_path_join_many(apr_pool_t *pool, const char *base, ...);
+
+
+/** Get the basename of the specified canonicalized @a path. The
+ * basename is defined as the last component of the path (ignoring any
+ * trailing slashes). If the @a path is root ("/"), then that is
+ * returned. Otherwise, the returned value will have no slashes in
+ * it.
+ *
+ * Example: svn_path_basename("/foo/bar") -> "bar"
+ *
+ * The returned basename will be allocated in @a pool.
+ *
+ * @note If an empty string is passed, then an empty string will be returned.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_dirent_basename(), svn_uri_basename(),
+ * svn_relpath_basename() or svn_fspath__basename().
+ */
+SVN_DEPRECATED
+char *
+svn_path_basename(const char *path, apr_pool_t *pool);
+
+/** Get the dirname of the specified canonicalized @a path, defined as
+ * the path with its basename removed. If @a path is root ("/"), it is
+ * returned unchanged.
+ *
+ * The returned dirname will be allocated in @a pool.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_dirent_dirname(), svn_uri_dirname(),
+ * svn_relpath_dirname() or svn_fspath__dirname().
+ */
+SVN_DEPRECATED
+char *
+svn_path_dirname(const char *path, apr_pool_t *pool);
+
+/** Split @a path into a root portion and an extension such that
+ * the root + the extension = the original path, and where the
+ * extension contains no period (.) characters. If not @c NULL, set
+ * @a *path_root to the root portion. If not @c NULL, set
+ * @a *path_ext to the extension (or "" if there is no extension
+ * found). Allocate both @a *path_root and @a *path_ext in @a pool.
+ *
+ * @since New in 1.5.
+ */
+void
+svn_path_splitext(const char **path_root, const char **path_ext,
+ const char *path, apr_pool_t *pool);
+
+/** Return the number of components in the canonicalized @a path.
+ *
+ * @since New in 1.1.
+*/
+apr_size_t
+svn_path_component_count(const char *path);
+
+/** Add a @a component (a NULL-terminated C-string) to the
+ * canonicalized @a path. @a component is allowed to contain
+ * directory separators.
+ *
+ * If @a path is non-empty, append the appropriate directory separator
+ * character, and then @a component. If @a path is empty, simply set it to
+ * @a component; don't add any separator character.
+ *
+ * If the result ends in a separator character, then remove the separator.
+ */
+void
+svn_path_add_component(svn_stringbuf_t *path, const char *component);
+
+/** Remove one component off the end of the canonicalized @a path. */
+void
+svn_path_remove_component(svn_stringbuf_t *path);
+
+/** Remove @a n components off the end of the canonicalized @a path.
+ * Equivalent to calling svn_path_remove_component() @a n times.
+ *
+ * @since New in 1.1.
+ */
+void
+svn_path_remove_components(svn_stringbuf_t *path, apr_size_t n);
+
+/** Divide the canonicalized @a path into @a *dirpath and @a
+ * *base_name, allocated in @a pool.
+ *
+ * If @a dirpath or @a base_name is NULL, then don't set that one.
+ *
+ * Either @a dirpath or @a base_name may be @a path's own address, but they
+ * may not both be the same address, or the results are undefined.
+ *
+ * If @a path has two or more components, the separator between @a dirpath
+ * and @a base_name is not included in either of the new names.
+ *
+ * examples:
+ * - <pre>"/foo/bar/baz" ==> "/foo/bar" and "baz"</pre>
+ * - <pre>"/bar" ==> "/" and "bar"</pre>
+ * - <pre>"/" ==> "/" and "/"</pre>
+ * - <pre>"X:/" ==> "X:/" and "X:/"</pre>
+ * - <pre>"bar" ==> "" and "bar"</pre>
+ * - <pre>"" ==> "" and ""</pre>
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_dirent_split(), svn_uri_split(),
+ * svn_relpath_split() or svn_fspath__split().
+ */
+SVN_DEPRECATED
+void
+svn_path_split(const char *path,
+ const char **dirpath,
+ const char **base_name,
+ apr_pool_t *pool);
+
+
+/** Return non-zero iff @a path is empty ("") or represents the current
+ * directory -- that is, if prepending it as a component to an existing
+ * path would result in no meaningful change.
+ */
+int
+svn_path_is_empty(const char *path);
+
+
+#ifndef SVN_DIRENT_URI_H
+/* This declaration has been moved to svn_dirent_uri.h, and remains
+ here only for compatibility reasons. */
+svn_boolean_t
+svn_dirent_is_root(const char *dirent, apr_size_t len);
+#endif /* SVN_DIRENT_URI_H */
+
+
+/** Return a new path (or URL) like @a path, but transformed such that
+ * some types of path specification redundancies are removed.
+ *
+ * This involves collapsing redundant "/./" elements, removing
+ * multiple adjacent separator characters, removing trailing
+ * separator characters, and possibly other semantically inoperative
+ * transformations.
+ *
+ * Convert the scheme and hostname to lowercase (see issue #2475)
+ *
+ * The returned path may be statically allocated, equal to @a path, or
+ * allocated from @a pool.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_dirent_canonicalize(), svn_uri_canonicalize(),
+ * svn_relpath_canonicalize() or svn_fspath__canonicalize().
+ */
+SVN_DEPRECATED
+const char *
+svn_path_canonicalize(const char *path, apr_pool_t *pool);
+
+/** Return @c TRUE iff path is canonical. Use @a pool for temporary
+ * allocations.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_dirent_is_canonical(), svn_uri_is_canonical(),
+ * svn_relpath_is_canonical() or svn_fspath__is_canonical().
+ */
+SVN_DEPRECATED
+svn_boolean_t
+svn_path_is_canonical(const char *path, apr_pool_t *pool);
+
+
+/** Return an integer greater than, equal to, or less than 0, according
+ * as @a path1 is greater than, equal to, or less than @a path2.
+ *
+ * This function works like strcmp() except that it orders children in
+ * subdirectories directly after their parents. This allows using the
+ * given ordering for a depth first walk.
+ */
+int
+svn_path_compare_paths(const char *path1, const char *path2);
+
+
+/** Return the longest common path shared by two canonicalized paths,
+ * @a path1 and @a path2. If there's no common ancestor, return the
+ * empty path.
+ *
+ * @a path1 and @a path2 may be URLs. In order for two URLs to have
+ * a common ancestor, they must (a) have the same protocol (since two URLs
+ * with the same path but different protocols may point at completely
+ * different resources), and (b) share a common ancestor in their path
+ * component, i.e. 'protocol://' is not a sufficient ancestor.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_dirent_get_longest_ancestor(),
+ * svn_uri_get_longest_ancestor(), svn_relpath_get_longest_ancestor() or
+ * svn_fspath__get_longest_ancestor().
+ */
+SVN_DEPRECATED
+char *
+svn_path_get_longest_ancestor(const char *path1,
+ const char *path2,
+ apr_pool_t *pool);
+
+/** Convert @a relative canonicalized path to an absolute path and
+ * return the results in @a *pabsolute, allocated in @a pool.
+ *
+ * @a relative may be a URL, in which case no attempt is made to convert it,
+ * and a copy of the URL is returned.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_dirent_get_absolute() on a non-URL input.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_path_get_absolute(const char **pabsolute,
+ const char *relative,
+ apr_pool_t *pool);
+
+/** Return the path part of the canonicalized @a path in @a
+ * *pdirectory, and the file part in @a *pfile. If @a path is a
+ * directory, set @a *pdirectory to @a path, and @a *pfile to the
+ * empty string. If @a path does not exist it is treated as if it is
+ * a file, since directories do not normally vanish.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should implement the required logic directly; no direct
+ * replacement is provided.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_path_split_if_file(const char *path,
+ const char **pdirectory,
+ const char **pfile,
+ apr_pool_t *pool);
+
+/** Find the common prefix of the canonicalized paths in @a targets
+ * (an array of <tt>const char *</tt>'s), and remove redundant paths if @a
+ * remove_redundancies is TRUE.
+ *
+ * - Set @a *pcommon to the absolute path of the path or URL common to
+ * all of the targets. If the targets have no common prefix, or
+ * are a mix of URLs and local paths, set @a *pcommon to the
+ * empty string.
+ *
+ * - If @a pcondensed_targets is non-NULL, set @a *pcondensed_targets
+ * to an array of targets relative to @a *pcommon, and if
+ * @a remove_redundancies is TRUE, omit any paths/URLs that are
+ * descendants of another path/URL in @a targets. If *pcommon
+ * is empty, @a *pcondensed_targets will contain full URLs and/or
+ * absolute paths; redundancies can still be removed (from both URLs
+ * and paths). If @a pcondensed_targets is NULL, leave it alone.
+ *
+ * Else if there is exactly one target, then
+ *
+ * - Set @a *pcommon to that target, and
+ *
+ * - If @a pcondensed_targets is non-NULL, set @a *pcondensed_targets
+ * to an array containing zero elements. Else if
+ * @a pcondensed_targets is NULL, leave it alone.
+ *
+ * If there are no items in @a targets, set @a *pcommon and (if
+ * applicable) @a *pcondensed_targets to @c NULL.
+ *
+ * @note There is no guarantee that @a *pcommon is within a working
+ * copy.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_dirent_condense_targets() or
+ * svn_uri_condense_targets().
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_path_condense_targets(const char **pcommon,
+ apr_array_header_t **pcondensed_targets,
+ const apr_array_header_t *targets,
+ svn_boolean_t remove_redundancies,
+ apr_pool_t *pool);
+
+
+/** Copy a list of canonicalized @a targets, one at a time, into @a
+ * pcondensed_targets, omitting any targets that are found earlier in
+ * the list, or whose ancestor is found earlier in the list. Ordering
+ * of targets in the original list is preserved in the condensed list
+ * of targets. Use @a pool for any allocations.
+ *
+ * How does this differ in functionality from svn_path_condense_targets()?
+ *
+ * Here's the short version:
+ *
+ * 1. Disclaimer: if you wish to debate the following, talk to Karl. :-)
+ * Order matters for updates because a multi-arg update is not
+ * atomic, and CVS users are used to, when doing 'cvs up targetA
+ * targetB' seeing targetA get updated, then targetB. I think the
+ * idea is that if you're in a time-sensitive or flaky-network
+ * situation, a user can say, "I really *need* to update
+ * wc/A/D/G/tau, but I might as well update my whole working copy if
+ * I can." So that user will do 'svn up wc/A/D/G/tau wc', and if
+ * something dies in the middles of the 'wc' update, at least the
+ * user has 'tau' up-to-date.
+ *
+ * 2. Also, we have this notion of an anchor and a target for updates
+ * (the anchor is where the update editor is rooted, the target is
+ * the actual thing we want to update). I needed a function that
+ * would NOT screw with my input paths so that I could tell the
+ * difference between someone being in A/D and saying 'svn up G' and
+ * being in A/D/G and saying 'svn up .' -- believe it or not, these
+ * two things don't mean the same thing. svn_path_condense_targets()
+ * plays with absolute paths (which is fine, so does
+ * svn_path_remove_redundancies()), but the difference is that it
+ * actually tweaks those targets to be relative to the "grandfather
+ * path" common to all the targets. Updates don't require a
+ * "grandfather path" at all, and even if it did, the whole
+ * conversion to an absolute path drops the crucial difference
+ * between saying "i'm in foo, update bar" and "i'm in foo/bar,
+ * update '.'"
+ */
+svn_error_t *
+svn_path_remove_redundancies(apr_array_header_t **pcondensed_targets,
+ const apr_array_header_t *targets,
+ apr_pool_t *pool);
+
+
+/** Decompose the canonicalized @a path into an array of <tt>const
+ * char *</tt> components, allocated in @a pool. If @a path is
+ * absolute, the first component will be a lone dir separator (the
+ * root directory).
+ */
+apr_array_header_t *
+svn_path_decompose(const char *path, apr_pool_t *pool);
+
+/** Join an array of <tt>const char *</tt> components into a '/'
+ * separated path, allocated in @a pool. The joined path is absolute if
+ * the first component is a lone dir separator.
+ *
+ * Calling svn_path_compose() on the output of svn_path_decompose()
+ * will return the exact same path.
+ *
+ * @since New in 1.5.
+ */
+const char *
+svn_path_compose(const apr_array_header_t *components, apr_pool_t *pool);
+
+/** Test that @a name is a single path component, that is:
+ * - not @c NULL or empty.
+ * - not a `/'-separated directory path
+ * - not empty or `..'
+ */
+svn_boolean_t
+svn_path_is_single_path_component(const char *name);
+
+
+/**
+ * Test to see if a backpath, i.e. '..', is present in @a path.
+ * If not, return @c FALSE.
+ * If so, return @c TRUE.
+ *
+ * @since New in 1.1.
+ */
+svn_boolean_t
+svn_path_is_backpath_present(const char *path);
+
+
+/**
+ * Test to see if a dotpath, i.e. '.', is present in @a path.
+ * If not, return @c FALSE.
+ * If so, return @c TRUE.
+ *
+ * @since New in 1.6.
+ */
+svn_boolean_t
+svn_path_is_dotpath_present(const char *path);
+
+
+/** Test if @a path2 is a child of @a path1.
+ * If not, return @c NULL.
+ * If so, return a copy of the remainder path, allocated in @a pool.
+ * (The remainder is the component which, added to @a path1, yields
+ * @a path2. The remainder does not begin with a dir separator.)
+ *
+ * Both paths must be in canonical form, and must either be absolute,
+ * or contain no ".." components.
+ *
+ * If @a path2 is the same as @a path1, it is not considered a child, so the
+ * result is @c NULL; an empty string is never returned.
+ *
+ * @note In 1.5 this function has been extended to allow a @c NULL @a pool
+ * in which case a pointer into @a path2 will be returned to
+ * identify the remainder path.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * For replacement functionality, see svn_dirent_skip_ancestor(),
+ * svn_dirent_is_child(), svn_uri_skip_ancestor(), and
+ * svn_relpath_skip_ancestor().
+ */
+SVN_DEPRECATED
+const char *
+svn_path_is_child(const char *path1, const char *path2, apr_pool_t *pool);
+
+/** Return TRUE if @a path1 is an ancestor of @a path2 or the paths are equal
+ * and FALSE otherwise.
+ *
+ * @since New in 1.3.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * For replacement functionality, see svn_dirent_skip_ancestor(),
+ * svn_uri_skip_ancestor(), and svn_relpath_skip_ancestor().
+ */
+SVN_DEPRECATED
+svn_boolean_t
+svn_path_is_ancestor(const char *path1, const char *path2);
+
+/**
+ * Check whether @a path is a valid Subversion path.
+ *
+ * A valid Subversion pathname is a UTF-8 string without control
+ * characters. "Valid" means Subversion can store the pathname in
+ * a repository. There may be other, OS-specific, limitations on
+ * what paths can be represented in a working copy.
+ *
+ * ASSUMPTION: @a path is a valid UTF-8 string. This function does
+ * not check UTF-8 validity.
+ *
+ * Return @c SVN_NO_ERROR if valid and @c SVN_ERR_FS_PATH_SYNTAX if
+ * invalid.
+ *
+ * @note Despite returning an @c SVN_ERR_FS_* error, this function has
+ * nothing to do with the versioned filesystem's concept of validity.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_path_check_valid(const char *path, apr_pool_t *pool);
+
+
+/** URI/URL stuff
+ *
+ * @defgroup svn_path_uri_stuff URI/URL conversion
+ * @{
+ */
+
+/** Return TRUE iff @a path looks like a valid absolute URL. */
+svn_boolean_t
+svn_path_is_url(const char *path);
+
+/** Return @c TRUE iff @a path is URI-safe, @c FALSE otherwise. */
+svn_boolean_t
+svn_path_is_uri_safe(const char *path);
+
+/** Return a URI-encoded copy of @a path, allocated in @a pool. (@a
+ path can be an arbitrary UTF-8 string and does not have to be a
+ canonical path.) */
+const char *
+svn_path_uri_encode(const char *path, apr_pool_t *pool);
+
+/** Return a URI-decoded copy of @a path, allocated in @a pool. */
+const char *
+svn_path_uri_decode(const char *path, apr_pool_t *pool);
+
+/** Extend @a url by @a component, URI-encoding that @a component
+ * before adding it to the @a url; return the new @a url, allocated in
+ * @a pool. If @a component is @c NULL, just return a copy of @a url,
+ * allocated in @a pool.
+ *
+ * @a component need not be a single path segment, but if it contains
+ * multiple segments, they must be separated by '/'. @a component
+ * should not begin with '/', however; if it does, the behavior is
+ * undefined.
+ *
+ * @a url must be in canonical format; it may not have a trailing '/'.
+ *
+ * @note To add a component that is already URI-encoded, use
+ * <tt>svn_path_join(url, component, pool)</tt> instead.
+ *
+ * @note gstein suggests this for when @a component begins with '/':
+ *
+ * "replace the path entirely
+ * https://example.com:4444/base/path joined with /leading/slash,
+ * should return: https://example.com:4444/leading/slash
+ * per the RFCs on combining URIs"
+ *
+ * We may implement that someday, which is why leading '/' is
+ * merely undefined right now.
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_path_url_add_component2(const char *url,
+ const char *component,
+ apr_pool_t *pool);
+
+/** Like svn_path_url_add_component2(), but allows path components that
+ * end with a trailing '/'
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+const char *
+svn_path_url_add_component(const char *url,
+ const char *component,
+ apr_pool_t *pool);
+
+/**
+ * Convert @a iri (Internationalized URI) to an URI.
+ * The return value may be the same as @a iri if it was already
+ * a URI. Else, allocate the return value in @a pool.
+ *
+ * @since New in 1.1.
+ */
+const char *
+svn_path_uri_from_iri(const char *iri, apr_pool_t *pool);
+
+/**
+ * URI-encode certain characters in @a uri that are not valid in an URI, but
+ * doesn't have any special meaning in @a uri at their positions. If no
+ * characters need escaping, just return @a uri.
+ *
+ * @note Currently, this function escapes <, >, ", space, {, }, |, \, ^, and `.
+ * This may be extended in the future to do context-dependent escaping.
+ *
+ * @since New in 1.1.
+ */
+const char *
+svn_path_uri_autoescape(const char *uri, apr_pool_t *pool);
+
+/** @} */
+
+/** Charset conversion stuff
+ *
+ * @defgroup svn_path_charset_stuff Charset conversion
+ * @{
+ */
+
+/** Convert @a path_utf8 from UTF-8 to the internal encoding used by APR. */
+svn_error_t *
+svn_path_cstring_from_utf8(const char **path_apr,
+ const char *path_utf8,
+ apr_pool_t *pool);
+
+/** Convert @a path_apr from the internal encoding used by APR to UTF-8. */
+svn_error_t *
+svn_path_cstring_to_utf8(const char **path_utf8,
+ const char *path_apr,
+ apr_pool_t *pool);
+
+
+/** @} */
+
+
+/** Repository relative URLs
+ *
+ * @defgroup svn_path_repos_relative_urls Repository relative URLs
+ * @{
+ */
+
+/**
+ * Return @c TRUE iff @a path is a repository-relative URL: specifically
+ * that it starts with the characters "^/"
+ *
+ * @a path is in UTF-8 encoding.
+ *
+ * Does not check whether @a path is a properly URI-encoded, canonical, or
+ * valid in any other way.
+ *
+ * @since New in 1.8.
+ */
+svn_boolean_t
+svn_path_is_repos_relative_url(const char *path);
+
+/**
+ * Set @a absolute_url to the absolute URL represented by @a relative_url
+ * relative to @a repos_root_url, preserving any peg revision
+ * specifier present in @a relative_url. Allocate @a absolute_url
+ * from @a pool.
+ *
+ * @a relative_url is in repository-relative syntax: "^/[REL-URL][@PEG]"
+ *
+ * @a repos_root_url is the absolute URL of the repository root.
+ *
+ * All strings are in UTF-8 encoding.
+ *
+ * @a repos_root_url and @a relative_url do not have to be properly
+ * URI-encoded, canonical, or valid in any other way. The caller is
+ * expected to perform canonicalization on @a absolute_url after the
+ * call to the function.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_path_resolve_repos_relative_url(const char **absolute_url,
+ const char *relative_url,
+ const char *repos_root_url,
+ apr_pool_t *pool);
+
+/* Return a copy of @a path, allocated from @a pool, for which control
+ * characters have been escaped using the form \NNN (where NNN is the
+ * octal representation of the byte's ordinal value).
+ *
+ * @since New in 1.8. */
+const char *
+svn_path_illegal_path_escape(const char *path, apr_pool_t *pool);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* SVN_PATH_H */
diff --git a/contrib/subversion/subversion/include/svn_pools.h b/contrib/subversion/subversion/include/svn_pools.h
new file mode 100644
index 0000000..d4c3a53
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_pools.h
@@ -0,0 +1,114 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_pools.h
+ * @brief APR pool management for Subversion
+ */
+
+
+
+
+#ifndef SVN_POOLS_H
+#define SVN_POOLS_H
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* Wrappers around APR pools, so we get debugging. */
+
+/** The recommended maximum amount of memory (4MB) to keep in an APR
+ * allocator on the free list, conveniently defined here to share
+ * between all our applications.
+ */
+#define SVN_ALLOCATOR_RECOMMENDED_MAX_FREE (4096 * 1024)
+
+
+/** Wrapper around apr_pool_create_ex(), with a simpler interface.
+ * The return pool will have an abort function set, which will call
+ * abort() on OOM.
+ */
+apr_pool_t *
+svn_pool_create_ex(apr_pool_t *parent_pool,
+ apr_allocator_t *allocator);
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+apr_pool_t *
+svn_pool_create_ex_debug(apr_pool_t *parent_pool,
+ apr_allocator_t *allocator,
+ const char *file_line);
+
+#if APR_POOL_DEBUG
+#define svn_pool_create_ex(pool, allocator) \
+svn_pool_create_ex_debug(pool, allocator, APR_POOL__FILE_LINE__)
+
+#endif /* APR_POOL_DEBUG */
+#endif /* DOXYGEN_SHOULD_SKIP_THIS */
+
+
+/** Create a pool as a subpool of @a parent_pool */
+#define svn_pool_create(parent_pool) svn_pool_create_ex(parent_pool, NULL)
+
+/** Clear a @a pool destroying its children.
+ *
+ * This define for @c svn_pool_clear exists for completeness.
+ */
+#define svn_pool_clear apr_pool_clear
+
+
+/** Destroy a @a pool and all of its children.
+ *
+ * This define for @c svn_pool_destroy exists for symmetry and
+ * completeness.
+ */
+#define svn_pool_destroy apr_pool_destroy
+
+/** Return a new allocator. This function limits the unused memory in the
+ * new allocator to #SVN_ALLOCATOR_RECOMMENDED_MAX_FREE and ensures
+ * proper synchronization if the allocator is used by multiple threads.
+ *
+ * If your application uses multiple threads, creating a separate
+ * allocator for each of these threads may not be feasible. Set the
+ * @a thread_safe parameter to @c TRUE in that case; otherwise, set @a
+ * thread_safe to @c FALSE to maximize performance.
+ *
+ * @note Even if @a thread_safe is @c TRUE, pools themselves will
+ * still not be thread-safe and their access may require explicit
+ * serialization.
+ *
+ * To access the owner pool, which can also serve as the root pool for
+ * your sub-pools, call @c apr_allocator_get_owner().
+ *
+ * @since: New in 1.8
+ */
+apr_allocator_t *
+svn_pool_create_allocator(svn_boolean_t thread_safe);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_POOLS_H */
diff --git a/contrib/subversion/subversion/include/svn_props.h b/contrib/subversion/subversion/include/svn_props.h
new file mode 100644
index 0000000..1f2bbbf
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_props.h
@@ -0,0 +1,714 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_props.h
+ * @brief Subversion properties
+ */
+
+/* ==================================================================== */
+
+#ifndef SVN_PROPS_H
+#define SVN_PROPS_H
+
+#include <apr_pools.h> /* for apr_pool_t */
+#include <apr_tables.h> /* for apr_array_header_t */
+#include <apr_hash.h> /* for apr_hash_t */
+
+#include "svn_types.h" /* for svn_boolean_t, svn_error_t */
+#include "svn_string.h" /* for svn_string_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * @defgroup svn_props_support Properties management utilities
+ * @{
+ */
+
+
+
+/** A general in-memory representation of a single property. Most of
+ * the time, property lists will be stored completely in hashes. But
+ * sometimes it's useful to have an "ordered" collection of
+ * properties, in which case we use an array of these structures.
+ *
+ * Also: sometimes we want a list that represents a set of property
+ * *changes*, and in this case, an @c apr_hash_t won't work -- there's no
+ * way to represent a property deletion, because we can't store a @c NULL
+ * value in a hash. So instead, we use these structures.
+ */
+typedef struct svn_prop_t
+{
+ const char *name; /**< Property name */
+ const svn_string_t *value; /**< Property value */
+} svn_prop_t;
+
+
+/**
+ * Return a duplicate of @a prop, allocated in @a pool. No part of the new
+ * structure will be shared with @a prop.
+ *
+ * @since New in 1.3.
+ */
+svn_prop_t *
+svn_prop_dup(const svn_prop_t *prop,
+ apr_pool_t *pool);
+
+
+/**
+ * Duplicate an @a array of svn_prop_t items using @a pool.
+ *
+ * @since New in 1.3.
+ */
+apr_array_header_t *
+svn_prop_array_dup(const apr_array_header_t *array,
+ apr_pool_t *pool);
+
+
+/** A structure to represent inherited properties.
+ *
+ * @since New in 1.8.
+ */
+typedef struct svn_prop_inherited_item_t
+{
+ /** The absolute working copy path, relative filesystem path, or URL
+ * from which the properties in @a prop_hash are inherited. (For
+ * details about which path specification format is in use for a
+ * particular instance of this structure, consult the documentation
+ * for the API which produced it.) */
+ const char *path_or_url;
+
+ /** A hash of (<tt>const char *</tt>) inherited property names, and
+ * (<tt>svn_string_t *</tt>) property values. */
+ apr_hash_t *prop_hash;
+
+} svn_prop_inherited_item_t;
+
+
+/**
+ * Given a hash (keys <tt>const char *</tt> and values <tt>const
+ * svn_string_t</tt>) of properties, returns an array of svn_prop_t
+ * items using @a pool.
+ *
+ * @since New in 1.5.
+ */
+apr_array_header_t *
+svn_prop_hash_to_array(const apr_hash_t *hash,
+ apr_pool_t *pool);
+
+/**
+ * Given an array of svn_prop_t items, return a hash mapping const char *
+ * property names to const svn_string_t * values.
+ *
+ * @warning The behaviour on #svn_prop_t objects with a @c NULL @c
+ * svn_prop_t.value member is undefined.
+ *
+ * @since New in 1.7.
+ */
+apr_hash_t *
+svn_prop_array_to_hash(const apr_array_header_t *properties,
+ apr_pool_t *result);
+
+/**
+ * Creates a deep copy of @a hash (keys <tt>const char *</tt> and
+ * values <tt>const svn_string_t *</tt>) in @a pool.
+ *
+ * @since New in 1.6.
+ */
+apr_hash_t *
+svn_prop_hash_dup(const apr_hash_t *hash,
+ apr_pool_t *pool);
+
+/**
+ * Return the value of property @a prop_name as it is in @a properties,
+ * with values <tt>const svn_string_t</tt>. If @a prop_name is not
+ * in @a properties or @a properties is NULL, return NULL.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_prop_get_value(const apr_hash_t *properties,
+ const char *prop_name);
+
+/**
+ * Subversion distinguishes among several kinds of properties,
+ * particularly on the client-side. There is no "unknown" kind; if
+ * there's nothing special about a property name, the default category
+ * is @c svn_prop_regular_kind.
+ */
+typedef enum svn_prop_kind
+{
+ /** In .svn/entries, i.e., author, date, etc. */
+ svn_prop_entry_kind,
+
+ /** Client-side only, stored by specific RA layer. */
+ svn_prop_wc_kind,
+
+ /** Seen if user does "svn proplist"; note that this includes some "svn:"
+ * props and all user props, i.e. ones stored in the repository fs.
+ */
+ svn_prop_regular_kind
+} svn_prop_kind_t;
+
+/** Return the property kind of a property named @a prop_name.
+ *
+ * @since New in 1.8.
+ */
+svn_prop_kind_t
+svn_property_kind2(const char *prop_name);
+
+/** Return the prop kind of a property named @a prop_name, and
+ * (if @a prefix_len is non-@c NULL) set @a *prefix_len to the length of
+ * the prefix of @a prop_name that was sufficient to distinguish its kind.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_prop_kind_t
+svn_property_kind(int *prefix_len,
+ const char *prop_name);
+
+
+/** Return @c TRUE iff @a prop_name represents the name of a Subversion
+ * property. That is, any property name in Subversion's name space for
+ * versioned or unversioned properties, regardless whether the particular
+ * property name is recognized.
+ */
+svn_boolean_t
+svn_prop_is_svn_prop(const char *prop_name);
+
+
+/** Return @c TRUE iff @a props has at least one property whose name
+ * represents the name of a Subversion property, in the sense of
+ * svn_prop_is_svn_prop().
+ *
+ * @since New in 1.5.
+ */
+svn_boolean_t
+svn_prop_has_svn_prop(const apr_hash_t *props,
+ apr_pool_t *pool);
+
+/** Return @c TRUE iff @a prop_name is a Subversion property whose
+ * value is interpreted as a boolean.
+ *
+ * @since New in 1.5.
+ */
+svn_boolean_t
+svn_prop_is_boolean(const char *prop_name);
+
+/** Return @c TRUE iff @a prop_name is in the "svn:" name space and is a
+ * known revision property ("svn:log" or "svn:date", e.g.).
+ *
+ * This will return @c FALSE for any property name that is not known by this
+ * version of the library, even though the name may be known to other (for
+ * example, later) Subversion software.
+ *
+ * @since New in 1.8.
+ */
+svn_boolean_t
+svn_prop_is_known_svn_rev_prop(const char *prop_name);
+
+/** Return @c TRUE iff @a prop_name is in the "svn:" name space and is a
+ * known versioned property that is allowed on a file and/or on a
+ * directory ("svn:eol-style", "svn:ignore", or "svn:mergeinfo", e.g.).
+ *
+ * This will return @c FALSE for any property name that is not known
+ * by this version of the library, even though the name may be known
+ * to other (for example, later) Subversion software.
+ *
+ * @since New in 1.8.
+ */
+svn_boolean_t
+svn_prop_is_known_svn_node_prop(const char *prop_name);
+
+/** Return @c TRUE iff @a prop_name is in the "svn:" name space and is
+ * a known versioned property that is allowed on a file
+ * ("svn:eol-style" or "svn:mergeinfo", e.g.).
+ *
+ * This will return @c FALSE for any property name that is not known
+ * by this version of the library, even though the name may be known
+ * to other (for example, later) Subversion software.
+ *
+ * @since New in 1.8.
+ */
+svn_boolean_t
+svn_prop_is_known_svn_file_prop(const char *prop_name);
+
+/** Return @c TRUE iff @a prop_name is in the "svn:" name space and is
+ * a known versioned property that is allowed on a directory
+ * ("svn:ignore" or "svn:mergeinfo", e.g.).
+ *
+ * This will return @c FALSE for any property name that is not known
+ * by this version of the library, even though the name may be known
+ * to other (for example, later) Subversion software.
+ *
+ * @since New in 1.8.
+ */
+svn_boolean_t
+svn_prop_is_known_svn_dir_prop(const char *prop_name);
+
+/** If @a prop_name requires that its value be stored as UTF8/LF in the
+ * repository, then return @c TRUE. Else return @c FALSE. This is for
+ * users of libsvn_client or libsvn_fs, since it their responsibility
+ * to do this translation in both directions. (See
+ * svn_subst_translate_string()/svn_subst_detranslate_string() for
+ * help with this task.)
+ */
+svn_boolean_t
+svn_prop_needs_translation(const char *prop_name);
+
+
+/** Given a @a proplist array of @c svn_prop_t structures, allocate
+ * three new arrays in @a pool. Categorize each property and then
+ * create new @c svn_prop_t structures in the proper lists. Each new
+ * @c svn_prop_t structure's fields will point to the same data within
+ * @a proplist's structures.
+ *
+ * Callers may pass NULL for each of the property lists in which they
+ * are uninterested. If no props exist in a certain category, and the
+ * property list argument for that category is non-NULL, then that
+ * array will come back with <tt>->nelts == 0</tt>.
+ */
+svn_error_t *
+svn_categorize_props(const apr_array_header_t *proplist,
+ apr_array_header_t **entry_props,
+ apr_array_header_t **wc_props,
+ apr_array_header_t **regular_props,
+ apr_pool_t *pool);
+
+
+/** Given two property hashes (<tt>const char *name</tt> -> <tt>const
+ * svn_string_t *value</tt>), deduce the differences between them (from
+ * @a source_props -> @c target_props). Set @a propdiffs to a new array of
+ * @c svn_prop_t structures, with one entry for each property that differs,
+ * including properties that exist in @a source_props or @a target_props but
+ * not both. The @c value field of each entry is that property's value from
+ * @a target_props or NULL if that property only exists in @a source_props.
+ *
+ * Allocate the array from @a pool. Allocate the contents of the array from
+ * @a pool or by reference to the storage of the input hashes or both.
+ *
+ * For note, here's a quick little table describing the logic of this
+ * routine:
+ *
+ * @verbatim
+ source_props target_props event
+ ------------ ------------ -----
+ value = foo value = NULL Deletion occurred.
+ value = foo value = bar Set occurred (modification)
+ value = NULL value = baz Set occurred (creation) @endverbatim
+ */
+svn_error_t *
+svn_prop_diffs(apr_array_header_t **propdiffs,
+ const apr_hash_t *target_props,
+ const apr_hash_t *source_props,
+ apr_pool_t *pool);
+
+
+/**
+ * Return @c TRUE iff @a prop_name is a valid property name.
+ *
+ * For now, "valid" means the ASCII subset of an XML "Name".
+ * XML "Name" is defined at http://www.w3.org/TR/REC-xml#sec-common-syn
+ *
+ * @since New in 1.5.
+ */
+svn_boolean_t
+svn_prop_name_is_valid(const char *prop_name);
+
+
+
+/* Defines for reserved ("svn:") property names. */
+
+/** All Subversion property names start with this. */
+#define SVN_PROP_PREFIX "svn:"
+
+
+/** Visible properties
+ *
+ * These are regular properties that are attached to ordinary files
+ * and dirs, and are visible (and tweakable) by svn client programs
+ * and users. Adding these properties causes specific effects.
+ *
+ * @note the values of these properties are always UTF8-encoded with
+ * LF line-endings. It is the burden of svn library users to enforce
+ * this. Use svn_prop_needs_translation() to discover if a
+ * certain property needs translation, and you can use
+ * svn_subst_translate_string()/svn_subst_detranslate_string()
+ * to do the translation.
+ *
+ * @defgroup svn_prop_visible_props Visible properties
+ * @{
+ */
+
+/** Properties whose values are interpreted as booleans (such as
+ * svn:executable, svn:needs_lock, and svn:special) always fold their
+ * value to this.
+ *
+ * @since New in 1.5.
+ */
+#define SVN_PROP_BOOLEAN_TRUE "*"
+
+/** The mime-type of a given file. */
+#define SVN_PROP_MIME_TYPE SVN_PROP_PREFIX "mime-type"
+
+/** The ignore patterns for a given directory. */
+#define SVN_PROP_IGNORE SVN_PROP_PREFIX "ignore"
+
+/** The line ending style for a given file. */
+#define SVN_PROP_EOL_STYLE SVN_PROP_PREFIX "eol-style"
+
+/** The "activated" keywords (for keyword substitution) for a given file. */
+#define SVN_PROP_KEYWORDS SVN_PROP_PREFIX "keywords"
+
+/** Set to either TRUE or FALSE if we want a file to be executable or not. */
+#define SVN_PROP_EXECUTABLE SVN_PROP_PREFIX "executable"
+
+/** The value to force the executable property to when set.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ * Use @c SVN_PROP_BOOLEAN_TRUE instead.
+ */
+#define SVN_PROP_EXECUTABLE_VALUE SVN_PROP_BOOLEAN_TRUE
+
+/** Set to TRUE ('*') if we want a file to be set to read-only when
+ * not locked. FALSE is indicated by deleting the property. */
+#define SVN_PROP_NEEDS_LOCK SVN_PROP_PREFIX "needs-lock"
+
+/** The value to force the needs-lock property to when set.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ * Use @c SVN_PROP_BOOLEAN_TRUE instead.
+ */
+#define SVN_PROP_NEEDS_LOCK_VALUE SVN_PROP_BOOLEAN_TRUE
+
+/** Set if the file should be treated as a special file. */
+#define SVN_PROP_SPECIAL SVN_PROP_PREFIX "special"
+
+/** The value to force the special property to when set.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ * Use @c SVN_PROP_BOOLEAN_TRUE instead.
+ */
+#define SVN_PROP_SPECIAL_VALUE SVN_PROP_BOOLEAN_TRUE
+
+/** Describes external items to check out into this directory.
+ *
+ * The format is a series of lines, each in the following format:
+ * [-r REV] URL[@PEG] LOCALPATH
+ * LOCALPATH is relative to the directory having this property.
+ * REV pins the external to revision REV.
+ * URL may be a full URL or a relative URL starting with one of:
+ * ../ to the parent directory of the extracted external
+ * ^/ to the repository root
+ * / to the server root
+ * // to the URL scheme
+ * The following format is supported for interoperability with
+ * Subversion 1.4 and earlier clients:
+ * LOCALPATH [-r PEG] URL
+ * The ambiguous format 'relative_path relative_path' is taken as
+ * 'relative_url relative_path' with peg revision support.
+ * Lines starting with a '#' character are ignored.
+ */
+#define SVN_PROP_EXTERNALS SVN_PROP_PREFIX "externals"
+
+/** Merge info property used to record a resource's merge history.
+ *
+ * The format is a series of lines containing merge paths and revision
+ * ranges, such as:
+ *
+ * @verbatim
+ /trunk: 1-6,9,37-38
+ /trunk/foo: 10 @endverbatim
+ */
+#define SVN_PROP_MERGEINFO SVN_PROP_PREFIX "mergeinfo"
+
+/** Property used to record inheritable configuration auto-props. */
+#define SVN_PROP_INHERITABLE_AUTO_PROPS SVN_PROP_PREFIX "auto-props"
+
+/** Property used to record inheritable configuration ignores. */
+#define SVN_PROP_INHERITABLE_IGNORES SVN_PROP_PREFIX "global-ignores"
+
+/** Meta-data properties.
+ *
+ * The following properties are used for storing meta-data about
+ * individual entries in the meta-data branches of subversion,
+ * see issue #1256 or browseable at
+ * http://svn.apache.org/viewvc/subversion/branches/meta-data-versioning/ .
+ * Furthermore @c svntar (http://svn.borg.ch/svntar/) and @c FSVS
+ * (http://fsvs.tigris.org/) use these, too.
+ *
+ * Please note that these formats are very UNIX-centric currently;
+ * a bit of discussion about Windows can be read at
+ * http://article.gmane.org/gmane.comp.version-control.subversion.devel/103991
+ *
+ * @defgroup svn_prop_meta_data Meta-data properties
+ * @{ */
+
+/** The files' last modification time.
+ * This is stored as string in the form @c "2008-08-07T07:38:51.008782Z", to
+ * be converted by the functions @c svn_time_to_cstring() and
+ * @c svn_time_from_cstring(). */
+#define SVN_PROP_TEXT_TIME SVN_PROP_PREFIX "text-time"
+
+/** The files' owner.
+ * Stored as numeric ID, optionally followed by whitespace and the string:
+ * @c "1000 pmarek". Parsers @b should accept any number of whitespace,
+ * and writers @b should put exactly a single space. */
+#define SVN_PROP_OWNER SVN_PROP_PREFIX "owner"
+
+/** The files' group.
+ * The same format as for @c SVN_PROP_OWNER, the owner-property. */
+#define SVN_PROP_GROUP SVN_PROP_PREFIX "group"
+
+/** The files' unix-mode.
+ * Stored in octal, with a leading @c 0; may have 5 digits if any of @c setuid,
+ * @c setgid or @c sticky are set; an example is @c "0644". */
+#define SVN_PROP_UNIX_MODE SVN_PROP_PREFIX "unix-mode"
+
+/** @} */ /* Meta-data properties */
+
+/**
+ * This is a list of all user-visible and -settable versioned node
+ * properties.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_PROP_NODE_ALL_PROPS SVN_PROP_MIME_TYPE, \
+ SVN_PROP_IGNORE, \
+ SVN_PROP_EOL_STYLE, \
+ SVN_PROP_KEYWORDS, \
+ SVN_PROP_EXECUTABLE, \
+ SVN_PROP_NEEDS_LOCK, \
+ SVN_PROP_SPECIAL, \
+ SVN_PROP_EXTERNALS, \
+ SVN_PROP_MERGEINFO, \
+ SVN_PROP_INHERITABLE_AUTO_PROPS, \
+ SVN_PROP_INHERITABLE_IGNORES, \
+ \
+ SVN_PROP_TEXT_TIME, \
+ SVN_PROP_OWNER, \
+ SVN_PROP_GROUP, \
+ SVN_PROP_UNIX_MODE,
+
+/** @} */
+
+/** WC props are props that are invisible to users: they're generated
+ * by an RA layer, and stored in secret parts of .svn/.
+ *
+ * @defgroup svn_prop_invisible_props Invisible properties
+ * @{
+ */
+
+/** The property name *prefix* that makes a property a "WC property".
+ *
+ * For example, WebDAV RA implementations might store a versioned-resource
+ * url as a WC prop like this:
+ *
+ * <pre reason="Should use 'verbatim' instead, but Doxygen v1.6.1 & v1.7.1
+ * then doesn't recognize the #define; presumably a bug.">
+ name = svn:wc:dav_url
+ val = http://www.example.com/repos/452348/e.289 </pre>
+ *
+ * The client will try to protect WC props by warning users against
+ * changing them. The client will also send them back to the RA layer
+ * when committing.
+ */
+#define SVN_PROP_WC_PREFIX SVN_PROP_PREFIX "wc:"
+
+/** Another type of non-user-visible property. "Entry properties" are
+ * stored as fields with the administrative 'entries' file.
+ */
+#define SVN_PROP_ENTRY_PREFIX SVN_PROP_PREFIX "entry:"
+
+/** The revision this entry was last committed to on. */
+#define SVN_PROP_ENTRY_COMMITTED_REV SVN_PROP_ENTRY_PREFIX "committed-rev"
+
+/** The date this entry was last committed to on. */
+#define SVN_PROP_ENTRY_COMMITTED_DATE SVN_PROP_ENTRY_PREFIX "committed-date"
+
+/** The author who last committed to this entry. */
+#define SVN_PROP_ENTRY_LAST_AUTHOR SVN_PROP_ENTRY_PREFIX "last-author"
+
+/** The UUID of this entry's repository. */
+#define SVN_PROP_ENTRY_UUID SVN_PROP_ENTRY_PREFIX "uuid"
+
+/** The lock token for this entry.
+ * @since New in 1.2. */
+#define SVN_PROP_ENTRY_LOCK_TOKEN SVN_PROP_ENTRY_PREFIX "lock-token"
+
+/** When custom, user-defined properties are passed over the wire, they will
+ * have this prefix added to their name.
+ */
+#define SVN_PROP_CUSTOM_PREFIX SVN_PROP_PREFIX "custom:"
+
+/** @} */
+
+/**
+ * These are reserved properties attached to a "revision" object in
+ * the repository filesystem. They can be queried by using
+ * svn_fs_revision_prop().
+ *
+ * @defgroup svn_props_revision_props Revision properties
+ * @{
+ */
+
+/** The fs revision property that stores a commit's author. */
+#define SVN_PROP_REVISION_AUTHOR SVN_PROP_PREFIX "author"
+
+/** The fs revision property that stores a commit's log message. */
+#define SVN_PROP_REVISION_LOG SVN_PROP_PREFIX "log"
+
+/** The fs revision property that stores a commit's date. */
+#define SVN_PROP_REVISION_DATE SVN_PROP_PREFIX "date"
+
+/** The fs revision property that stores a commit's "original" date.
+ *
+ * The svn:date property must be monotonically increasing, along with
+ * the revision number. In certain scenarios, this may pose a problem
+ * when the revision represents a commit that occurred at a time which
+ * does not fit within the sequencing required for svn:date. This can
+ * happen, for instance, when the revision represents a commit to a
+ * foreign version control system, or possibly when two Subversion
+ * repositories are combined. This property can be used to record the
+ * TRUE, original date of the commit.
+ */
+#define SVN_PROP_REVISION_ORIG_DATE SVN_PROP_PREFIX "original-date"
+
+/** The presence of this fs revision property indicates that the
+ * revision was automatically generated by the mod_dav_svn
+ * autoversioning feature. The value is irrelevant.
+ */
+#define SVN_PROP_REVISION_AUTOVERSIONED SVN_PROP_PREFIX "autoversioned"
+
+
+/* More reserved revision props in the 'svn:' namespace, used by the
+ svnsync tool: */
+
+/** Prefix for all svnsync custom properties.
+ * @since New in 1.4.
+ */
+#define SVNSYNC_PROP_PREFIX SVN_PROP_PREFIX "sync-"
+
+/* The following revision properties are set on revision 0 of
+ * destination repositories by svnsync:
+ */
+
+/** Used to enforce mutually exclusive destination repository access.
+ * @since New in 1.4.
+ */
+#define SVNSYNC_PROP_LOCK SVNSYNC_PROP_PREFIX "lock"
+
+/** Identifies the repository's source URL.
+ * @since New in 1.4.
+ */
+#define SVNSYNC_PROP_FROM_URL SVNSYNC_PROP_PREFIX "from-url"
+/** Identifies the repository's source UUID.
+ * @since New in 1.4.
+ */
+#define SVNSYNC_PROP_FROM_UUID SVNSYNC_PROP_PREFIX "from-uuid"
+
+/** Identifies the last completely mirrored revision.
+ * @since New in 1.4.
+ */
+#define SVNSYNC_PROP_LAST_MERGED_REV SVNSYNC_PROP_PREFIX "last-merged-rev"
+
+/** Identifies the revision currently being copied.
+ * @since New in 1.4.
+ */
+#define SVNSYNC_PROP_CURRENTLY_COPYING SVNSYNC_PROP_PREFIX "currently-copying"
+
+
+/**
+ * This is a list of all revision properties.
+ */
+#define SVN_PROP_REVISION_ALL_PROPS SVN_PROP_REVISION_AUTHOR, \
+ SVN_PROP_REVISION_LOG, \
+ SVN_PROP_REVISION_DATE, \
+ SVN_PROP_REVISION_AUTOVERSIONED, \
+ SVN_PROP_REVISION_ORIG_DATE, \
+ SVNSYNC_PROP_LOCK, \
+ SVNSYNC_PROP_FROM_URL, \
+ SVNSYNC_PROP_FROM_UUID, \
+ SVNSYNC_PROP_LAST_MERGED_REV, \
+ SVNSYNC_PROP_CURRENTLY_COPYING,
+
+/** @} */
+
+/**
+ * These are reserved properties attached to a "transaction" object in
+ * the repository filesystem in advance of the pre-commit hook script
+ * running on the server, but then automatically removed from the
+ * transaction before its promotion to a new revision.
+ *
+ * @defgroup svn_props_ephemeral_txnprops Ephemeral transaction properties
+ * @{
+ */
+
+/** The prefix used for all (ephemeral) transaction properties.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_PROP_TXN_PREFIX SVN_PROP_PREFIX "txn-"
+
+/** Identifies the client version compability level. For clients
+ * compiled against Subversion libraries, this is @c SVN_VER_NUMBER.
+ * Third-party implementations are advised to use similar formatting
+ * for values of this property.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_PROP_TXN_CLIENT_COMPAT_VERSION \
+ SVN_PROP_TXN_PREFIX "client-compat-version"
+
+/** Identifies the client's user agent string, if any.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_PROP_TXN_USER_AGENT \
+ SVN_PROP_TXN_PREFIX "user-agent"
+
+/** The prefix reserved for copies of (ephemeral) transaction
+ * properties designed to outlive the transaction. Administrators may
+ * choose to, in their pre-commit hook scripts, copy the values of one
+ * or more properties named @c SVN_PROP_TXN_PREFIX + "something"
+ * to new properties named @c SVN_PROP_REVISION_PREFIX + "something",
+ * allowing that information to survive the commit-time removal of
+ * ephemeral transaction properties.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_PROP_REVISION_PREFIX SVN_PROP_PREFIX "revision-"
+
+
+/** @} */
+
+/** @} */
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_PROPS_H */
diff --git a/contrib/subversion/subversion/include/svn_quoprint.h b/contrib/subversion/subversion/include/svn_quoprint.h
new file mode 100644
index 0000000..abbbe17
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_quoprint.h
@@ -0,0 +1,77 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_quoprint.h
+ * @brief quoted-printable encoding and decoding functions.
+ */
+
+#ifndef SVN_QUOPRINT_H
+#define SVN_QUOPRINT_H
+
+#include <apr_pools.h>
+
+#include "svn_string.h" /* for svn_strinbuf_t */
+#include "svn_io.h" /* for svn_stream_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** Return a writable generic stream which will encode binary data in
+ * quoted-printable format and write the encoded data to @a output. Be
+ * sure to close the stream when done writing in order to squeeze out
+ * the last bit of encoded data.
+ */
+svn_stream_t *
+svn_quoprint_encode(svn_stream_t *output,
+ apr_pool_t *pool);
+
+/** Return a writable generic stream which will decode binary data in
+ * quoted-printable format and write the decoded data to @a output. Be
+ * sure to close the stream when done writing in order to squeeze out
+ * the last bit of encoded data.
+ */
+svn_stream_t *
+svn_quoprint_decode(svn_stream_t *output,
+ apr_pool_t *pool);
+
+
+/** Simpler interface for encoding quoted-printable data assuming we have all
+ * of it present at once. The returned string will be allocated from @a pool.
+ */
+svn_stringbuf_t *
+svn_quoprint_encode_string(const svn_stringbuf_t *str,
+ apr_pool_t *pool);
+
+/** Simpler interface for decoding quoted-printable data assuming we have all
+ * of it present at once. The returned string will be allocated from @a pool.
+ */
+svn_stringbuf_t *
+svn_quoprint_decode_string(const svn_stringbuf_t *str,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_QUOPRINT_H */
diff --git a/contrib/subversion/subversion/include/svn_ra.h b/contrib/subversion/subversion/include/svn_ra.h
new file mode 100644
index 0000000..cf6f612
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_ra.h
@@ -0,0 +1,2468 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_ra.h
+ * @brief Repository Access
+ */
+
+#ifndef SVN_RA_H
+#define SVN_RA_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_time.h>
+#include <apr_file_io.h> /* for apr_file_t */
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_delta.h"
+#include "svn_auth.h"
+#include "svn_mergeinfo.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* Misc. declarations */
+
+/**
+ * Get libsvn_ra version information.
+ *
+ * @since New in 1.1.
+ */
+const svn_version_t *
+svn_ra_version(void);
+
+
+/** This is a function type which allows the RA layer to fetch working
+ * copy (WC) properties.
+ *
+ * The @a baton is provided along with the function pointer and should
+ * be passed back in. This will be the @a callback_baton or the
+ * @a close_baton as appropriate.
+ *
+ * @a path is relative to the "root" of the session, defined by the
+ * @a repos_URL passed to svn_ra_open4() vtable call.
+ *
+ * @a name is the name of the property to fetch. If the property is present,
+ * then it is returned in @a value. Otherwise, @a *value is set to @c NULL.
+ */
+typedef svn_error_t *(*svn_ra_get_wc_prop_func_t)(void *baton,
+ const char *path,
+ const char *name,
+ const svn_string_t **value,
+ apr_pool_t *pool);
+
+/** This is a function type which allows the RA layer to store new
+ * working copy properties during update-like operations. See the
+ * comments for @c svn_ra_get_wc_prop_func_t for @a baton, @a path, and
+ * @a name. The @a value is the value that will be stored for the property;
+ * a NULL @a value means the property will be deleted.
+ */
+typedef svn_error_t *(*svn_ra_set_wc_prop_func_t)(void *baton,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/** This is a function type which allows the RA layer to store new
+ * working copy properties as part of a commit. See the comments for
+ * @c svn_ra_get_wc_prop_func_t for @a baton, @a path, and @a name.
+ * The @a value is the value that will be stored for the property; a
+ * @c NULL @a value means the property will be deleted.
+ *
+ * Note that this might not actually store the new property before
+ * returning, but instead schedule it to be changed as part of
+ * post-commit processing (in which case a successful commit means the
+ * properties got written). Thus, during the commit, it is possible
+ * to invoke this function to set a new value for a wc prop, then read
+ * the wc prop back from the working copy and get the *old* value.
+ * Callers beware.
+ */
+typedef svn_error_t *(*svn_ra_push_wc_prop_func_t)(void *baton,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/** This is a function type which allows the RA layer to invalidate
+ * (i.e., remove) wcprops recursively. See the documentation for
+ * @c svn_ra_get_wc_prop_func_t for @a baton, @a path, and @a name.
+ *
+ * Unlike @c svn_ra_push_wc_prop_func_t, this has immediate effect. If
+ * it returns success, the wcprops have been removed.
+ */
+typedef svn_error_t *(*svn_ra_invalidate_wc_props_func_t)(void *baton,
+ const char *path,
+ const char *name,
+ apr_pool_t *pool);
+
+/** This is a function type which allows the RA layer to fetch the
+ * cached pristine file contents whose checksum is @a checksum, if
+ * any. @a *contents will be a read stream containing those contents
+ * if they are found; NULL otherwise.
+ *
+ * @since New in 1.8.
+ */
+typedef svn_error_t *
+(*svn_ra_get_wc_contents_func_t)(void *baton,
+ svn_stream_t **contents,
+ const svn_checksum_t *checksum,
+ apr_pool_t *pool);
+
+
+/** A function type for retrieving the youngest revision from a repos. */
+typedef svn_error_t *(*svn_ra_get_latest_revnum_func_t)(
+ void *session_baton,
+ svn_revnum_t *latest_revnum);
+
+/** A function type which allows the RA layer to ask about any
+ * customizations to the client name string. This is primarily used
+ * by HTTP-based RA layers wishing to extend the string reported to
+ * Apache/mod_dav_svn via the User-agent HTTP header.
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_ra_get_client_string_func_t)(void *baton,
+ const char **name,
+ apr_pool_t *pool);
+
+
+
+/**
+ * A callback function type for use in @c get_file_revs.
+ * @a baton is provided by the caller, @a path is the pathname of the file
+ * in revision @a rev and @a rev_props are the revision properties.
+ * If @a delta_handler and @a delta_baton are non-NULL, they may be set to a
+ * handler/baton which will be called with the delta between the previous
+ * revision and this one after the return of this callback. They may be
+ * left as NULL/NULL.
+ * @a prop_diffs is an array of svn_prop_t elements indicating the property
+ * delta for this and the previous revision.
+ * @a pool may be used for temporary allocations, but you can't rely
+ * on objects allocated to live outside of this particular call and the
+ * immediately following calls to @a *delta_handler, if any.
+ *
+ * @since New in 1.1.
+ */
+typedef svn_error_t *(*svn_ra_file_rev_handler_t)(
+ void *baton,
+ const char *path,
+ svn_revnum_t rev,
+ apr_hash_t *rev_props,
+ svn_txdelta_window_handler_t *delta_handler,
+ void **delta_baton,
+ apr_array_header_t *prop_diffs,
+ apr_pool_t *pool);
+
+/**
+ * Callback function type for locking and unlocking actions.
+ *
+ * @since New in 1.2.
+ *
+ * @a do_lock is TRUE when locking @a path, and FALSE
+ * otherwise.
+ *
+ * @a lock is a lock for @a path or NULL if @a do_lock is FALSE or @a ra_err is
+ * non-NULL.
+ *
+ * @a ra_err is NULL unless the ra layer encounters a locking related
+ * error which it passes back for notification purposes. The caller
+ * is responsible for clearing @a ra_err after the callback is run.
+ *
+ * @a baton is a closure object; it should be provided by the
+ * implementation, and passed by the caller. @a pool may be used for
+ * temporary allocation.
+ */
+typedef svn_error_t *(*svn_ra_lock_callback_t)(void *baton,
+ const char *path,
+ svn_boolean_t do_lock,
+ const svn_lock_t *lock,
+ svn_error_t *ra_err,
+ apr_pool_t *pool);
+
+/**
+ * Callback function type for progress notification.
+ *
+ * @a progress is the number of bytes already transferred, @a total is
+ * the total number of bytes to transfer or -1 if it's not known, @a
+ * baton is the callback baton.
+ *
+ * @since New in 1.3.
+ */
+typedef void (*svn_ra_progress_notify_func_t)(apr_off_t progress,
+ apr_off_t total,
+ void *baton,
+ apr_pool_t *pool);
+
+/**
+ * Callback function type for replay_range actions.
+ *
+ * This callback function should provide replay_range with an editor which
+ * will be driven with the received replay reports from the master repository.
+ *
+ * @a revision is the target revision number of the received replay report.
+ *
+ * @a editor and @a edit_baton should provided by the callback implementation.
+ *
+ * @a replay_baton is the baton as originally passed to replay_range.
+ *
+ * @a revprops contains key/value pairs for each revision properties for this
+ * revision.
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_ra_replay_revstart_callback_t)(
+ svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool);
+
+/**
+ * Callback function type for replay_range actions.
+ *
+ * This callback function should close the editor.
+ *
+ * @a revision is the target revision number of the received replay report.
+ *
+ * @a editor and @a edit_baton should provided by the callback implementation.
+ *
+ * @a replay_baton is the baton as originally passed to replay_range.
+ *
+ * @a revprops contains key/value pairs for each revision properties for this
+ * revision.
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_ra_replay_revfinish_callback_t)(
+ svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool);
+
+
+/**
+ * The update Reporter.
+ *
+ * A vtable structure which allows a working copy to describe a subset
+ * (or possibly all) of its working-copy to an RA layer, for the
+ * purposes of an update, switch, status, or diff operation.
+ *
+ * Paths for report calls are relative to the target (not the anchor)
+ * of the operation. Report calls must be made in depth-first order:
+ * parents before children, all children of a parent before any
+ * siblings of the parent. The first report call must be a set_path
+ * with a @a path argument of "" and a valid revision. (If the target
+ * of the operation is locally deleted or missing, use the anchor's
+ * revision.) If the target of the operation is deleted or switched
+ * relative to the anchor, follow up the initial set_path call with a
+ * link_path or delete_path call with a @a path argument of "" to
+ * indicate that. In no other case may there be two report
+ * descriptions for the same path. If the target of the operation is
+ * a locally added file or directory (which previously did not exist),
+ * it may be reported as having revision 0 or as having the parent
+ * directory's revision.
+ *
+ * @since New in 1.5.
+ */
+typedef struct svn_ra_reporter3_t
+{
+ /** Describe a working copy @a path as being at a particular
+ * @a revision and having depth @a depth.
+ *
+ * @a revision may be SVN_INVALID_REVNUM if (for example) @a path
+ * represents a locally-added path with no revision number, or @a
+ * depth is @c svn_depth_exclude.
+ *
+ * @a path may not be underneath a path on which set_path() was
+ * previously called with @c svn_depth_exclude in this report.
+ *
+ * If @a start_empty is set and @a path is a directory, the
+ * implementor should assume the directory has no entries or props.
+ *
+ * This will *override* any previous set_path() calls made on parent
+ * paths. @a path is relative to the URL specified in svn_ra_open4().
+ *
+ * If @a lock_token is non-NULL, it is the lock token for @a path in the WC.
+ *
+ * All temporary allocations are done in @a pool.
+ */
+ svn_error_t *(*set_path)(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool);
+
+ /** Describing a working copy @a path as missing.
+ *
+ * @a path may not be underneath a path on which set_path() was
+ * previously called with @c svn_depth_exclude in this report.
+ *
+ * All temporary allocations are done in @a pool.
+ */
+ svn_error_t *(*delete_path)(void *report_baton,
+ const char *path,
+ apr_pool_t *pool);
+
+ /** Like set_path(), but differs in that @a path in the working copy
+ * (relative to the root of the report driver) isn't a reflection of
+ * @a path in the repository (relative to the URL specified when
+ * opening the RA layer), but is instead a reflection of a different
+ * repository @a url at @a revision, and has depth @a depth.
+ *
+ * @a path may not be underneath a path on which set_path() was
+ * previously called with @c svn_depth_exclude in this report.
+ *
+ * If @a start_empty is set and @a path is a directory,
+ * the implementor should assume the directory has no entries or props.
+ *
+ * If @a lock_token is non-NULL, it is the lock token for @a path in the WC.
+ *
+ * All temporary allocations are done in @a pool.
+ */
+ svn_error_t *(*link_path)(void *report_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool);
+
+ /** WC calls this when the state report is finished; any directories
+ * or files not explicitly `set' are assumed to be at the
+ * baseline revision originally passed into do_update(). No other
+ * reporting functions, including abort_report, should be called after
+ * calling this function.
+ */
+ svn_error_t *(*finish_report)(void *report_baton,
+ apr_pool_t *pool);
+
+ /** If an error occurs during a report, this routine should cause the
+ * filesystem transaction to be aborted & cleaned up. No other reporting
+ * functions should be called after calling this function.
+ */
+ svn_error_t *(*abort_report)(void *report_baton,
+ apr_pool_t *pool);
+
+} svn_ra_reporter3_t;
+
+/**
+ * Similar to @c svn_ra_reporter3_t, but without support for depths.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+typedef struct svn_ra_reporter2_t
+{
+ /** Similar to the corresponding field in @c svn_ra_reporter3_t, but
+ * with @a depth always set to @c svn_depth_infinity. */
+ svn_error_t *(*set_path)(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool);
+
+ /** Same as the corresponding field in @c svn_ra_reporter3_t. */
+ svn_error_t *(*delete_path)(void *report_baton,
+ const char *path,
+ apr_pool_t *pool);
+
+ /** Similar to the corresponding field in @c svn_ra_reporter3_t, but
+ * with @a depth always set to @c svn_depth_infinity. */
+ svn_error_t *(*link_path)(void *report_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool);
+
+ /** Same as the corresponding field in @c svn_ra_reporter3_t. */
+ svn_error_t *(*finish_report)(void *report_baton,
+ apr_pool_t *pool);
+
+ /** Same as the corresponding field in @c svn_ra_reporter3_t. */
+ svn_error_t *(*abort_report)(void *report_baton,
+ apr_pool_t *pool);
+
+} svn_ra_reporter2_t;
+
+/**
+ * Similar to @c svn_ra_reporter2_t, but without support for lock tokens.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+typedef struct svn_ra_reporter_t
+{
+ /** Similar to the corresponding field in @c svn_ra_reporter2_t, but
+ * with @a lock_token always set to NULL. */
+ svn_error_t *(*set_path)(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ apr_pool_t *pool);
+
+ /** Same as the corresponding field in @c svn_ra_reporter2_t. */
+ svn_error_t *(*delete_path)(void *report_baton,
+ const char *path,
+ apr_pool_t *pool);
+
+ /** Similar to the corresponding field in @c svn_ra_reporter2_t, but
+ * with @a lock_token always set to NULL. */
+ svn_error_t *(*link_path)(void *report_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ apr_pool_t *pool);
+
+ /** Same as the corresponding field in @c svn_ra_reporter2_t. */
+ svn_error_t *(*finish_report)(void *report_baton,
+ apr_pool_t *pool);
+
+ /** Same as the corresponding field in @c svn_ra_reporter2_t. */
+ svn_error_t *(*abort_report)(void *report_baton,
+ apr_pool_t *pool);
+} svn_ra_reporter_t;
+
+
+/** A collection of callbacks implemented by libsvn_client which allows
+ * an RA layer to "pull" information from the client application, or
+ * possibly store information. libsvn_client passes this vtable to
+ * svn_ra_open4().
+ *
+ * Each routine takes a @a callback_baton originally provided with the
+ * vtable.
+ *
+ * Clients must use svn_ra_create_callbacks() to allocate and
+ * initialize this structure.
+ *
+ * @since New in 1.3.
+ */
+typedef struct svn_ra_callbacks2_t
+{
+ /** Open a unique temporary file for writing in the working copy.
+ * This file will be automatically deleted when @a fp is closed.
+ *
+ * @deprecated This callback should no longer be used by RA layers.
+ */
+ svn_error_t *(*open_tmp_file)(apr_file_t **fp,
+ void *callback_baton,
+ apr_pool_t *pool);
+
+ /** An authentication baton, created by the application, which is
+ * capable of retrieving all known types of credentials.
+ */
+ svn_auth_baton_t *auth_baton;
+
+ /*** The following items may be set to NULL to disallow the RA layer
+ to perform the respective operations of the vtable functions.
+ Perhaps WC props are not defined or are in invalid for this
+ session, or perhaps the commit operation this RA session will
+ perform is a server-side only one that shouldn't do post-commit
+ processing on a working copy path. ***/
+
+ /** Fetch working copy properties.
+ *
+ *<pre> ### we might have a problem if the RA layer ever wants a property
+ * ### that corresponds to a different revision of the file than
+ * ### what is in the WC. we'll cross that bridge one day...</pre>
+ */
+ svn_ra_get_wc_prop_func_t get_wc_prop;
+
+ /** Immediately set new values for working copy properties. */
+ svn_ra_set_wc_prop_func_t set_wc_prop;
+
+ /** Schedule new values for working copy properties. */
+ svn_ra_push_wc_prop_func_t push_wc_prop;
+
+ /** Invalidate working copy properties. */
+ svn_ra_invalidate_wc_props_func_t invalidate_wc_props;
+
+ /** Notification callback used for progress information.
+ * May be NULL if not used.
+ */
+ svn_ra_progress_notify_func_t progress_func;
+
+ /** Notification callback baton, used with progress_func. */
+ void *progress_baton;
+
+ /** Cancellation function
+ *
+ * As its baton, the general callback baton is used
+ *
+ * @since New in 1.5
+ */
+ svn_cancel_func_t cancel_func;
+
+ /** Client string customization callback function
+ * @since New in 1.5
+ */
+ svn_ra_get_client_string_func_t get_client_string;
+
+ /** Working copy file content fetching function.
+ * @since New in 1.8.
+ */
+ svn_ra_get_wc_contents_func_t get_wc_contents;
+
+} svn_ra_callbacks2_t;
+
+/** Similar to svn_ra_callbacks2_t, except that the progress
+ * notification function and baton is missing.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+typedef struct svn_ra_callbacks_t
+{
+ svn_error_t *(*open_tmp_file)(apr_file_t **fp,
+ void *callback_baton,
+ apr_pool_t *pool);
+
+ svn_auth_baton_t *auth_baton;
+
+ svn_ra_get_wc_prop_func_t get_wc_prop;
+
+ svn_ra_set_wc_prop_func_t set_wc_prop;
+
+ svn_ra_push_wc_prop_func_t push_wc_prop;
+
+ svn_ra_invalidate_wc_props_func_t invalidate_wc_props;
+
+} svn_ra_callbacks_t;
+
+
+
+/*----------------------------------------------------------------------*/
+
+/* Public Interfaces. */
+
+/**
+ * Initialize the RA library. This function must be called before using
+ * any function in this header, except the deprecated APIs based on
+ * @c svn_ra_plugin_t, or svn_ra_version(). This function must not be called
+ * simultaneously in multiple threads. @a pool must live
+ * longer than any open RA sessions.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_initialize(apr_pool_t *pool);
+
+/** Initialize a callback structure.
+* Set @a *callbacks to a ra callbacks object, allocated in @a pool.
+*
+* Clients must use this function to allocate and initialize @c
+* svn_ra_callbacks2_t structures.
+*
+* @since New in 1.3.
+*/
+svn_error_t *
+svn_ra_create_callbacks(svn_ra_callbacks2_t **callbacks,
+ apr_pool_t *pool);
+
+/**
+ * A repository access session. This object is used to perform requests
+ * to a repository, identified by a URL.
+ *
+ * @since New in 1.2.
+ */
+typedef struct svn_ra_session_t svn_ra_session_t;
+
+/**
+ * Open a repository access session to the repository at @a repos_URL,
+ * or inform the caller regarding a correct URL by which to access
+ * that repository.
+ *
+ * If @a repos_URL can be used successfully to access the repository,
+ * set @a *session_p to an opaque object representing a repository
+ * session for the repository and (if @a corrected_url is non-NULL)
+ * set @a *corrected_url to NULL. If there's a better URL that the
+ * caller should try and @a corrected_url is non-NULL, set
+ * @a *session_p to NULL and @a *corrected_url to the corrected URL. If
+ * there's a better URL that the caller should try, and @a
+ * corrected_url is NULL, return an #SVN_ERR_RA_SESSION_URL_MISMATCH
+ * error. Allocate all returned items in @a pool.
+ *
+ * The @a repos_URL need not point to the root of the repository: subject
+ * to authorization, it may point to any path within the repository, even
+ * a path at which no node exists in the repository. The session will
+ * remember this URL as its "session URL" (also called "session root URL"),
+ * until changed by svn_ra_reparent(). Many RA functions take or return
+ * paths that are relative to the session URL.
+ *
+ * If a @a corrected_url is returned, it will point to the same path
+ * within the new repository root URL that @a repos_URL pointed to within
+ * the old repository root URL.
+ *
+ * Return @c SVN_ERR_RA_UUID_MISMATCH if @a uuid is non-NULL and not equal
+ * to the UUID of the repository at @c repos_URL.
+ *
+ * @a callbacks/@a callback_baton is a table of callbacks provided by the
+ * client; see @c svn_ra_callbacks2_t.
+ *
+ * @a config is a hash mapping <tt>const char *</tt> keys to
+ * @c svn_config_t * values. For example, the @c svn_config_t for the
+ * "~/.subversion/config" file is under the key "config". @a config may
+ * be NULL. This function examines some config settings under the
+ * "servers" key (if present) before loading the required RA module, and
+ * the RA module may also examine any config settings.
+ *
+ * All RA requests require a session; they will continue to
+ * use @a pool for memory allocation.
+ *
+ * @see svn_client_open_ra_session().
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_ra_open4(svn_ra_session_t **session_p,
+ const char **corrected_url,
+ const char *repos_URL,
+ const char *uuid,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool);
+
+/** Similar to svn_ra_open4(), but with @a corrected_url always passed
+ * as @c NULL.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_open3(svn_ra_session_t **session_p,
+ const char *repos_URL,
+ const char *uuid,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_ra_open3(), but with @a uuid set to @c NULL.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_open2(svn_ra_session_t **session_p,
+ const char *repos_URL,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool);
+
+/**
+ * @see svn_ra_open2().
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_open(svn_ra_session_t **session_p,
+ const char *repos_URL,
+ const svn_ra_callbacks_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool);
+
+/** Change the root URL of an open @a ra_session to point to a new path in the
+ * same repository. @a url is the new root URL. Use @a pool for
+ * temporary allocations.
+ *
+ * If @a url has a different repository root than the current session
+ * URL, return @c SVN_ERR_RA_ILLEGAL_URL.
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_ra_reparent(svn_ra_session_t *ra_session,
+ const char *url,
+ apr_pool_t *pool);
+
+/** Set @a *url to the session URL -- the URL to which @a ra_session was
+ * opened or most recently reparented.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_get_session_url(svn_ra_session_t *ra_session,
+ const char **url,
+ apr_pool_t *pool);
+
+
+/** Convert @a url into a path relative to the session URL of @a ra_session,
+ * setting @a *rel_path to that value. If @a url is not
+ * a child of the session URL, return @c SVN_ERR_RA_ILLEGAL_URL.
+ *
+ * The returned path is uri decoded to allow using it with the ra or other
+ * apis as a valid relpath.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_ra_get_path_relative_to_session(svn_ra_session_t *ra_session,
+ const char **rel_path,
+ const char *url,
+ apr_pool_t *pool);
+
+/** Convert @a url into a path relative to the repository root URL of
+ * the repository with which @a ra_session is associated, setting @a
+ * *rel_path to that value. If @a url is not a child of repository
+ * root URL, return @c SVN_ERR_RA_ILLEGAL_URL.
+ *
+ * The returned path is uri decoded to allow using it with the ra or other
+ * apis as a valid relpath.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_ra_get_path_relative_to_root(svn_ra_session_t *ra_session,
+ const char **rel_path,
+ const char *url,
+ apr_pool_t *pool);
+
+/**
+ * Get the latest revision number from the repository of @a session.
+ *
+ * Use @a pool for memory allocation.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_get_latest_revnum(svn_ra_session_t *session,
+ svn_revnum_t *latest_revnum,
+ apr_pool_t *pool);
+
+/**
+ * Get the latest revision number at time @a tm in the repository of
+ * @a session.
+ *
+ * Use @a pool for memory allocation.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_get_dated_revision(svn_ra_session_t *session,
+ svn_revnum_t *revision,
+ apr_time_t tm,
+ apr_pool_t *pool);
+
+/**
+ * Set the property @a name to @a value on revision @a rev in the repository
+ * of @a session.
+ *
+ * If @a value is @c NULL, delete the named revision property.
+ *
+ * If the server advertises the #SVN_RA_CAPABILITY_ATOMIC_REVPROPS capability
+ * and @a old_value_p is not @c NULL, then changing the property will fail with
+ * an error chain that contains #SVN_ERR_FS_PROP_BASEVALUE_MISMATCH if the
+ * present value of the property is not @a *old_value_p. (This is an atomic
+ * test-and-set).
+ * @a *old_value_p may be @c NULL, representing that the property must be not
+ * already set.
+ *
+ * If the capability is not advertised, then @a old_value_p MUST be @c NULL.
+ *
+ * Please note that properties attached to revisions are @em unversioned.
+ *
+ * Use @a pool for memory allocation.
+ *
+ * @see svn_fs_change_rev_prop2(), svn_error_find_cause().
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_ra_change_rev_prop2(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_ra_change_rev_prop2(), but with @a old_value_p set
+ * to @c NULL.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_change_rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *props to the list of unversioned properties attached to revision
+ * @a rev in the repository of @a session. The hash maps
+ * (<tt>const char *</tt>) names to (<tt>@c svn_string_t *</tt>) values.
+ *
+ * Use @a pool for memory allocation.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_rev_proplist(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ apr_hash_t **props,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *value to the value of unversioned property @a name attached to
+ * revision @a rev in the repository of @a session. If @a rev has no
+ * property by that name, set @a *value to @c NULL.
+ *
+ * Use @a pool for memory allocation.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ svn_string_t **value,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *editor and @a *edit_baton to an editor for committing
+ * changes to the repository of @a session, setting the revision
+ * properties from @a revprop_table. The revisions being committed
+ * against are passed to the editor functions, starting with the rev
+ * argument to @c open_root. The path root of the commit is the @a
+ * session's URL.
+ *
+ * @a revprop_table is a hash mapping <tt>const char *</tt> property
+ * names to @c svn_string_t property values. The commit log message
+ * is expected to be in the @c SVN_PROP_REVISION_LOG element. @a
+ * revprop_table can not contain either of @c SVN_PROP_REVISION_DATE
+ * or @c SVN_PROP_REVISION_AUTHOR.
+ *
+ * Before @c close_edit returns, but after the commit has succeeded,
+ * it will invoke @a commit_callback (if non-NULL) with filled-in
+ * #svn_commit_info_t *, @a commit_baton, and @a pool or some subpool
+ * thereof as arguments. If @a commit_callback returns an error, that error
+ * will be returned from @c * close_edit, otherwise @c close_edit will return
+ * successfully (unless it encountered an error before invoking
+ * @a commit_callback).
+ *
+ * The callback will not be called if the commit was a no-op
+ * (i.e. nothing was committed);
+ *
+ * @a lock_tokens, if non-NULL, is a hash mapping <tt>const char
+ * *</tt> paths (relative to the URL of @a session) to <tt>
+ * const char *</tt> lock tokens. The server checks that the
+ * correct token is provided for each committed, locked path. @a lock_tokens
+ * must live during the whole commit operation.
+ *
+ * If @a keep_locks is @c TRUE, then do not release locks on
+ * committed objects. Else, automatically release such locks.
+ *
+ * The caller may not perform any RA operations using @a session before
+ * finishing the edit.
+ *
+ * Use @a pool for memory allocation.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_get_commit_editor3(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_ra_get_commit_editor3(), but with @c revprop_table set
+ * to a hash containing the @c SVN_PROP_REVISION_LOG property set
+ * to the value of @a log_msg.
+ *
+ * @since New in 1.4.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_get_commit_editor2(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const char *log_msg,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_ra_get_commit_editor2(), but uses @c svn_commit_callback_t.
+ *
+ * @since New in 1.2.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_get_commit_editor(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const char *log_msg,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool);
+
+/**
+ * Fetch the contents and properties of file @a path at @a revision.
+ * @a revision may be SVN_INVALID_REVNUM, indicating that the HEAD
+ * revision should be used. Interpret @a path relative to the URL in
+ * @a session. Use @a pool for all allocations.
+ *
+ * If @a revision is @c SVN_INVALID_REVNUM and @a fetched_rev is not
+ * @c NULL, then set @a *fetched_rev to the actual revision that was
+ * retrieved.
+ *
+ * If @a stream is non @c NULL, push the contents of the file at @a
+ * stream, do not call svn_stream_close() when finished.
+ *
+ * If @a props is non @c NULL, set @a *props to contain the properties of
+ * the file. This means @em all properties: not just ones controlled by
+ * the user and stored in the repository fs, but non-tweakable ones
+ * generated by the SCM system itself (e.g. 'wcprops', 'entryprops',
+ * etc.) The keys are <tt>const char *</tt>, values are
+ * <tt>@c svn_string_t *</tt>.
+ *
+ * The stream handlers for @a stream may not perform any RA
+ * operations using @a session.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_get_file(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool);
+
+/**
+ * If @a dirents is non @c NULL, set @a *dirents to contain all the entries
+ * of directory @a path at @a revision. The keys of @a dirents will be
+ * entry names (<tt>const char *</tt>), and the values dirents
+ * (<tt>@c svn_dirent_t *</tt>). Use @a pool for all allocations.
+ *
+ * @a dirent_fields controls which portions of the <tt>@c svn_dirent_t</tt>
+ * objects are filled in. To have them completely filled in just pass
+ * @c SVN_DIRENT_ALL, otherwise pass the bitwise OR of all the @c SVN_DIRENT_
+ * fields you would like to have returned to you.
+ *
+ * @a path is interpreted relative to the URL in @a session.
+ *
+ * If @a revision is @c SVN_INVALID_REVNUM (meaning 'head') and
+ * @a *fetched_rev is not @c NULL, then this function will set
+ * @a *fetched_rev to the actual revision that was retrieved. (Some
+ * callers want to know, and some don't.)
+ *
+ * If @a props is non @c NULL, set @a *props to contain the properties of
+ * the directory. This means @em all properties: not just ones controlled by
+ * the user and stored in the repository fs, but non-tweakable ones
+ * generated by the SCM system itself (e.g. 'wcprops', 'entryprops',
+ * etc.) The keys are <tt>const char *</tt>, values are
+ * <tt>@c svn_string_t *</tt>.
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_ra_get_dir2(svn_ra_session_t *session,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ const char *path,
+ svn_revnum_t revision,
+ apr_uint32_t dirent_fields,
+ apr_pool_t *pool);
+
+/**
+ * Similar to @c svn_ra_get_dir2, but with @c SVN_DIRENT_ALL for the
+ * @a dirent_fields parameter.
+ *
+ * @since New in 1.2.
+ *
+ * @deprecated Provided for compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_get_dir(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *catalog to a mergeinfo catalog for the paths in @a paths.
+ * If no mergeinfo is available, set @a *catalog to @c NULL. The
+ * requested mergeinfo hashes are for @a paths (which are relative to
+ * @a session's URL) in @a revision. If one of the paths does not exist
+ * in that revision, return SVN_ERR_FS_NOT_FOUND.
+ *
+ * @a inherit indicates whether explicit, explicit or inherited, or
+ * only inherited mergeinfo for @a paths is retrieved.
+ *
+ * If @a include_descendants is TRUE, then additionally return the
+ * mergeinfo for any descendant of any element of @a paths which has
+ * the @c SVN_PROP_MERGEINFO property explicitly set on it. (Note
+ * that inheritance is only taken into account for the elements in @a
+ * paths; descendants of the elements in @a paths which get their
+ * mergeinfo via inheritance are not included in @a *catalog.)
+ *
+ * Allocate the returned values in @a pool.
+ *
+ * If @a revision is @c SVN_INVALID_REVNUM, it defaults to youngest.
+ *
+ * If the server doesn't support retrieval of mergeinfo (which can
+ * happen even for file:// URLs, if the repository itself hasn't been
+ * upgraded), return @c SVN_ERR_UNSUPPORTED_FEATURE in preference to
+ * any other error that might otherwise be returned.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_get_mergeinfo(svn_ra_session_t *session,
+ svn_mergeinfo_catalog_t *catalog,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool);
+
+/**
+ * Ask the RA layer to update a working copy to a new revision.
+ *
+ * The client initially provides an @a update_editor/@a update_baton to the
+ * RA layer; this editor contains knowledge of where the change will
+ * begin in the working copy (when @c open_root() is called).
+ *
+ * In return, the client receives a @a reporter/@a report_baton. The
+ * client then describes its working copy by making calls into the
+ * @a reporter.
+ *
+ * When finished, the client calls @a reporter->finish_report(). The
+ * RA layer then does a complete drive of @a update_editor, ending with
+ * @a update_editor->close_edit(), to update the working copy.
+ *
+ * @a update_target is an optional single path component to restrict
+ * the scope of the update to just that entry (in the directory
+ * represented by the @a session's URL). If @a update_target is the
+ * empty string, the entire directory is updated.
+ *
+ * Update the target only as deeply as @a depth indicates.
+ *
+ * If @a send_copyfrom_args is TRUE, then ask the server to send
+ * copyfrom arguments to add_file() and add_directory() when possible.
+ * (Note: this means that any subsequent txdeltas coming from the
+ * server are presumed to apply against the copied file!)
+ *
+ * Use @a ignore_ancestry to control whether or not items being
+ * updated will be checked for relatedness first. Unrelated items
+ * are typically transmitted to the editor as a deletion of one thing
+ * and the addition of another, but if this flag is @c TRUE,
+ * unrelated items will be diffed as if they were related.
+ *
+ * The working copy will be updated to @a revision_to_update_to, or the
+ * "latest" revision if this arg is invalid.
+ *
+ * The caller may not perform any RA operations using @a session before
+ * finishing the report, and may not perform any RA operations using
+ * @a session from within the editing operations of @a update_editor.
+ *
+ * Allocate @a *reporter and @a *report_baton in @a result_pool. Use
+ * @a scratch_pool for temporary allocations.
+ *
+ * @note The reporter provided by this function does NOT supply copy-
+ * from information to the diff editor callbacks.
+ *
+ * @note In order to prevent pre-1.5 servers from doing more work than
+ * needed, and sending too much data back, a pre-1.5 'recurse'
+ * directive may be sent to the server, based on @a depth.
+ *
+ * @note Pre Subversion 1.8 svnserve based servers never ignore ancestry.
+ *
+ * @note This differs from calling svn_ra_do_switch3() with the current
+ * URL of the target node. Update changes only the revision numbers,
+ * leaving any switched subtrees still switched, whereas switch changes
+ * every node in the tree to a child of the same URL.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_ra_do_update3(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_ra_do_update3(), but always ignoring ancestry.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_do_update2(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_ra_do_update2(), but taking @c svn_ra_reporter2_t
+ * instead of @c svn_ra_reporter3_t; if @a recurse is true, pass @c
+ * svn_depth_infinity for @a depth, else pass @c svn_depth_files; and
+ * with @a send_copyfrom_args always false.
+ *
+ * @deprecated Provided for compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_do_update(svn_ra_session_t *session,
+ const svn_ra_reporter2_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_boolean_t recurse,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Ask the RA layer to switch a working copy to a new revision and URL.
+ *
+ * This is similar to svn_ra_do_update3(), but also changes the URL of
+ * every node in the target tree to a child of the @a switch_url. In
+ * contrast, update changes only the revision numbers, leaving any
+ * switched subtrees still switched.
+ *
+ * @note Pre Subversion 1.8 svnserve based servers always ignore ancestry
+ * and never send copyfrom data.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_ra_do_switch3(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_depth_t depth,
+ const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_ra_do_switch3(), but always ignoring ancestry and
+ * never sending copyfrom_args.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_do_switch2(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_depth_t depth,
+ const char *switch_url,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_ra_do_switch2(), but taking @c svn_ra_reporter2_t
+ * instead of @c svn_ra_reporter3_t, and therefore only able to report
+ * @c svn_depth_infinity for depths. The switch itself is performed
+ * according to @a recurse: if TRUE, then use @c svn_depth_infinity
+ * for @a depth, else use @c svn_depth_files.
+ *
+ * @deprecated Provided for compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_do_switch(svn_ra_session_t *session,
+ const svn_ra_reporter2_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_boolean_t recurse,
+ const char *switch_url,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *pool);
+
+/**
+ * Ask the RA layer to describe the status of a working copy with respect
+ * to @a revision of the repository (or HEAD, if @a revision is invalid).
+ *
+ * The client initially provides a @a status_editor/@a status_baton to the RA
+ * layer; this editor contains knowledge of where the change will
+ * begin in the working copy (when open_root() is called).
+ *
+ * In return, the client receives a @a reporter/@a report_baton. The
+ * client then describes its working copy by making calls into the
+ * @a reporter.
+ *
+ * When finished, the client calls @a reporter->finish_report(). The RA
+ * layer then does a complete drive of @a status_editor, ending with
+ * close_edit(), to report, essentially, what would be modified in
+ * the working copy were the client to call do_update().
+ * @a status_target is an optional single path component will restrict
+ * the scope of the status report to an entry in the directory
+ * represented by the @a session's URL, or empty if the entire directory
+ * is meant to be examined.
+ *
+ * Get status as deeply as @a depth indicates. If @a depth is
+ * #svn_depth_unknown, get the status down to the ambient depth of the
+ * working copy. If @a depth is deeper than the working copy, include changes
+ * that would be needed to populate the working copy to that depth.
+ *
+ * The caller may not perform any RA operations using @a session
+ * before finishing the report, and may not perform any RA operations
+ * using @a session from within the editing operations of @a status_editor.
+ *
+ * Use @a pool for memory allocation.
+ *
+ * @note The reporter provided by this function does NOT supply copy-
+ * from information to the diff editor callbacks.
+ *
+ * @note In order to prevent pre-1.5 servers from doing more work than
+ * needed, and sending too much data back, a pre-1.5 'recurse'
+ * directive may be sent to the server, based on @a depth.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_do_status2(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_ra_do_status2(), but taking @c svn_ra_reporter2_t
+ * instead of @c svn_ra_reporter3_t, and therefore only able to report
+ * @c svn_depth_infinity for depths. The status operation itself is
+ * performed according to @a recurse: if TRUE, then @a depth is
+ * @c svn_depth_infinity, else it is @c svn_depth_immediates.
+ *
+ * @deprecated Provided for compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_do_status(svn_ra_session_t *session,
+ const svn_ra_reporter2_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_boolean_t recurse,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool);
+
+/**
+ * Ask the RA layer to 'diff' a working copy against @a versus_url;
+ * it's another form of svn_ra_do_update2().
+ *
+ * @note This function cannot be used to diff a single file, only a
+ * working copy directory. See the svn_ra_do_switch3() function
+ * for more details.
+ *
+ * The client initially provides a @a diff_editor/@a diff_baton to the RA
+ * layer; this editor contains knowledge of where the common diff
+ * root is in the working copy (when open_root() is called).
+ *
+ * In return, the client receives a @a reporter/@a report_baton. The
+ * client then describes its working copy by making calls into the
+ * @a reporter.
+ *
+ * When finished, the client calls @a reporter->finish_report(). The
+ * RA layer then does a complete drive of @a diff_editor, ending with
+ * close_edit(), to transmit the diff.
+ *
+ * @a diff_target is an optional single path component will restrict
+ * the scope of the diff to an entry in the directory represented by
+ * the @a session's URL, or empty if the entire directory is meant to be
+ * one of the diff paths.
+ *
+ * The working copy will be diffed against @a versus_url as it exists
+ * in revision @a revision, or as it is in head if @a revision is
+ * @c SVN_INVALID_REVNUM.
+ *
+ * Use @a ignore_ancestry to control whether or not items being
+ * diffed will be checked for relatedness first. Unrelated items
+ * are typically transmitted to the editor as a deletion of one thing
+ * and the addition of another, but if this flag is @c TRUE,
+ * unrelated items will be diffed as if they were related.
+ *
+ * Diff only as deeply as @a depth indicates.
+ *
+ * The caller may not perform any RA operations using @a session before
+ * finishing the report, and may not perform any RA operations using
+ * @a session from within the editing operations of @a diff_editor.
+ *
+ * @a text_deltas instructs the driver of the @a diff_editor to enable
+ * the generation of text deltas. If @a text_deltas is FALSE the window
+ * handler returned by apply_textdelta will be called once with a NULL
+ * @c svn_txdelta_window_t pointer.
+ *
+ * Use @a pool for memory allocation.
+ *
+ * @note The reporter provided by this function does NOT supply copy-
+ * from information to the diff editor callbacks.
+ *
+ * @note In order to prevent pre-1.5 servers from doing more work than
+ * needed, and sending too much data back, a pre-1.5 'recurse'
+ * directive may be sent to the server, based on @a depth.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_do_diff3(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_ra_do_diff3(), but taking @c svn_ra_reporter2_t
+ * instead of @c svn_ra_reporter3_t, and therefore only able to report
+ * @c svn_depth_infinity for depths. Perform the diff according to
+ * @a recurse: if TRUE, then @a depth is @c svn_depth_infinity, else
+ * it is @c svn_depth_files.
+ *
+ * @deprecated Provided for compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_do_diff2(svn_ra_session_t *session,
+ const svn_ra_reporter2_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_ra_do_diff2(), but with @a text_deltas set to @c TRUE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_do_diff(svn_ra_session_t *session,
+ const svn_ra_reporter2_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool);
+
+/**
+ * Invoke @a receiver with @a receiver_baton on each log message from
+ * @a start to @a end. @a start may be greater or less than @a end;
+ * this just controls whether the log messages are processed in descending
+ * or ascending revision number order.
+ *
+ * If @a start or @a end is @c SVN_INVALID_REVNUM, it defaults to youngest.
+ *
+ * If @a paths is non-NULL and has one or more elements, then only show
+ * revisions in which at least one of @a paths was changed (i.e., if
+ * file, text or props changed; if dir, props changed or an entry
+ * was added or deleted). Each path is an <tt>const char *</tt>, relative
+ * to the @a session's common parent.
+ *
+ * If @a limit is non-zero only invoke @a receiver on the first @a limit
+ * logs.
+ *
+ * If @a discover_changed_paths, then each call to @a receiver passes a
+ * <tt>const apr_hash_t *</tt> for the receiver's @a changed_paths argument;
+ * the hash's keys are all the paths committed in that revision, the hash's
+ * values are <tt>const svn_log_changed_path2_t *</tt> for each committed
+ * path. Otherwise, each call to receiver passes NULL for @a changed_paths.
+ *
+ * If @a strict_node_history is set, copy history will not be traversed
+ * (if any exists) when harvesting the revision logs for each path.
+ *
+ * If @a include_merged_revisions is set, log information for revisions
+ * which have been merged to @a targets will also be returned.
+ *
+ * If @a revprops is NULL, retrieve all revision properties; else, retrieve
+ * only the revision properties named by the (const char *) array elements
+ * (i.e. retrieve none if the array is empty).
+ *
+ * If any invocation of @a receiver returns error, return that error
+ * immediately and without wrapping it.
+ *
+ * If @a start or @a end is a non-existent revision, return the error
+ * @c SVN_ERR_FS_NO_SUCH_REVISION, without ever invoking @a receiver.
+ *
+ * See also the documentation for @c svn_log_message_receiver_t.
+ *
+ * The caller may not invoke any RA operations using @a session from
+ * within @a receiver.
+ *
+ * Use @a pool for memory allocation.
+ *
+ * @note If @a paths is NULL or empty, the result depends on the
+ * server. Pre-1.5 servers will send nothing; 1.5 servers will
+ * effectively perform the log operation on the root of the
+ * repository. This behavior may be changed in the future to ensure
+ * consistency across all pedigrees of server.
+ *
+ * @note Pre-1.5 servers do not support custom revprop retrieval; if @a
+ * revprops is NULL or contains a revprop other than svn:author, svn:date,
+ * or svn:log, an @c SVN_ERR_RA_NOT_IMPLEMENTED error is returned.
+ *
+ * @since New in 1.5.
+ */
+
+svn_error_t *
+svn_ra_get_log2(svn_ra_session_t *session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_ra_get_log2(), but uses @c svn_log_message_receiver_t
+ * instead of @c svn_log_entry_receiver_t. Also, @a
+ * include_merged_revisions is set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_get_log(svn_ra_session_t *session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *kind to the node kind associated with @a path at @a revision.
+ * If @a path does not exist under @a revision, set @a *kind to
+ * @c svn_node_none. @a path is relative to the @a session's parent URL.
+ *
+ * Use @a pool for memory allocation.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_check_path(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *dirent to an @c svn_dirent_t associated with @a path at @a
+ * revision. @a path is relative to the @a session's parent's URL.
+ * If @a path does not exist in @a revision, set @a *dirent to NULL.
+ *
+ * Use @a pool for memory allocation.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_stat(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_dirent_t **dirent,
+ apr_pool_t *pool);
+
+
+/**
+ * Set @a *uuid to the repository's UUID, allocated in @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_get_uuid2(svn_ra_session_t *session,
+ const char **uuid,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_ra_get_uuid2(), but returns the value allocated in
+ * @a session's pool.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_get_uuid(svn_ra_session_t *session,
+ const char **uuid,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *url to the repository's root URL, allocated in @a pool.
+ * The value will not include a trailing '/'. The returned URL is
+ * guaranteed to be a prefix of the @a session's URL.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_get_repos_root2(svn_ra_session_t *session,
+ const char **url,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_ra_get_repos_root2(), but returns the value
+ * allocated in @a session's pool.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_get_repos_root(svn_ra_session_t *session,
+ const char **url,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *locations to the locations (at the repository revisions
+ * @a location_revisions) of the file identified by @a path in
+ * @a peg_revision. @a path is relative to the URL to which
+ * @a session was opened. @a location_revisions is an array of
+ * @c svn_revnum_t's. @a *locations will be a mapping from the revisions to
+ * their appropriate absolute paths. If the file doesn't exist in a
+ * location_revision, that revision will be ignored.
+ *
+ * Use @a pool for all allocations.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_get_locations(svn_ra_session_t *session,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool);
+
+
+/**
+ * Call @a receiver (with @a receiver_baton) for each segment in the
+ * location history of @a path in @a peg_revision, working backwards in
+ * time from @a start_rev to @a end_rev.
+ *
+ * @a end_rev may be @c SVN_INVALID_REVNUM to indicate that you want
+ * to trace the history of the object to its origin.
+ *
+ * @a start_rev may be @c SVN_INVALID_REVNUM to indicate "the HEAD
+ * revision". Otherwise, @a start_rev must be younger than @a end_rev
+ * (unless @a end_rev is @c SVN_INVALID_REVNUM).
+ *
+ * @a peg_revision may be @c SVN_INVALID_REVNUM to indicate "the HEAD
+ * revision", and must evaluate to be at least as young as @a start_rev.
+ *
+ * Use @a pool for all allocations.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_get_location_segments(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+/**
+ * Retrieve a subset of the interesting revisions of a file @a path
+ * as seen in revision @a end (see svn_fs_history_prev() for a
+ * definition of "interesting revisions"). Invoke @a handler with
+ * @a handler_baton as its first argument for each such revision.
+ * @a session is an open RA session. Use @a pool for all allocations.
+ *
+ * If there is an interesting revision of the file that is less than or
+ * equal to @a start, the iteration will begin at that revision.
+ * Else, the iteration will begin at the first revision of the file in
+ * the repository, which has to be less than or equal to @a end. Note
+ * that if the function succeeds, @a handler will have been called at
+ * least once.
+ *
+ * In a series of calls to @a handler, the file contents for the first
+ * interesting revision will be provided as a text delta against the
+ * empty file. In the following calls, the delta will be against the
+ * fulltext contents for the previous call.
+ *
+ * If @a include_merged_revisions is TRUE, revisions which are
+ * included as a result of a merge between @a start and @a end will be
+ * included.
+ *
+ * @note This functionality is not available in pre-1.1 servers. If the
+ * server doesn't implement it, an alternative (but much slower)
+ * implementation based on svn_ra_get_log2() is used.
+ *
+ * On subversion 1.8 and newer servers this function has been enabled
+ * to support reversion of the revision range for @a include_merged_revision
+ * @c FALSE reporting by switching @a end with @a start.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_get_file_revs2(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_ra_get_file_revs2(), but with @a include_merged_revisions
+ * set to FALSE.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_get_file_revs(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_ra_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+/**
+ * Lock each path in @a path_revs, which is a hash whose keys are the
+ * paths to be locked, and whose values are the corresponding base
+ * revisions for each path. The keys are (const char *) and the
+ * revisions are (svn_revnum_t *).
+ *
+ * Note that locking is never anonymous, so any server implementing
+ * this function will have to "pull" a username from the client, if
+ * it hasn't done so already.
+ *
+ * @a comment is optional: it's either an xml-escapable string
+ * which describes the lock, or it is NULL.
+ *
+ * If any path is already locked by a different user, then call @a
+ * lock_func/@a lock_baton with an error. If @a steal_lock is TRUE,
+ * then "steal" the existing lock(s) anyway, even if the RA username
+ * does not match the current lock's owner. Delete any lock on the
+ * path, and unconditionally create a new lock.
+ *
+ * For each path, if its base revision (in @a path_revs) is a valid
+ * revnum, then do an out-of-dateness check. If the revnum is less
+ * than the last-changed-revision of any path (or if a path doesn't
+ * exist in HEAD), call @a lock_func/@a lock_baton with an
+ * SVN_ERR_RA_OUT_OF_DATE error.
+ *
+ * After successfully locking a file, @a lock_func is called with the
+ * @a lock_baton.
+ *
+ * Use @a pool for temporary allocations.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_lock(svn_ra_session_t *session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool);
+
+/**
+ * Remove the repository lock for each path in @a path_tokens.
+ * @a path_tokens is a hash whose keys are the paths to be locked, and
+ * whose values are the corresponding lock tokens for each path. If
+ * the path has no corresponding lock token, or if @a break_lock is TRUE,
+ * then the corresponding value shall be "".
+ *
+ * Note that unlocking is never anonymous, so any server
+ * implementing this function will have to "pull" a username from
+ * the client, if it hasn't done so already.
+ *
+ * If @a token points to a lock, but the RA username doesn't match the
+ * lock's owner, call @a lock_func/@a lock_baton with an error. If @a
+ * break_lock is TRUE, however, instead allow the lock to be "broken"
+ * by the RA user.
+ *
+ * After successfully unlocking a path, @a lock_func is called with
+ * the @a lock_baton.
+ *
+ * Use @a pool for temporary allocations.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_unlock(svn_ra_session_t *session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t break_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool);
+
+/**
+ * If @a path is locked, set @a *lock to an svn_lock_t which
+ * represents the lock, allocated in @a pool. If @a path is not
+ * locked, set @a *lock to NULL.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_get_lock(svn_ra_session_t *session,
+ svn_lock_t **lock,
+ const char *path,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *locks to a hashtable which represents all locks on or
+ * below @a path.
+ *
+ * @a depth limits the returned locks to those associated with paths
+ * within the specified depth of @a path, and must be one of the
+ * following values: #svn_depth_empty, #svn_depth_files,
+ * #svn_depth_immediates, or #svn_depth_infinity.
+ *
+ * The hashtable maps (const char *) absolute fs paths to (const
+ * svn_lock_t *) structures. The hashtable -- and all keys and
+ * values -- are allocated in @a pool.
+ *
+ * @note It is not considered an error for @a path to not exist in HEAD.
+ * Such a search will simply return no locks.
+ *
+ * @note This functionality is not available in pre-1.2 servers. If the
+ * server doesn't implement it, an @c SVN_ERR_RA_NOT_IMPLEMENTED error is
+ * returned.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_ra_get_locks2(svn_ra_session_t *session,
+ apr_hash_t **locks,
+ const char *path,
+ svn_depth_t depth,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_ra_get_locks2(), but with @a depth always passed as
+ * #svn_depth_infinity.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_get_locks(svn_ra_session_t *session,
+ apr_hash_t **locks,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/**
+ * Replay the changes from a range of revisions between @a start_revision
+ * and @a end_revision.
+ *
+ * When receiving information for one revision, a callback @a revstart_func is
+ * called; this callback will provide an editor and baton through which the
+ * revision will be replayed.
+ * When replaying the revision is finished, callback @a revfinish_func will be
+ * called so the editor can be closed.
+ *
+ * Changes will be limited to those that occur under @a session's URL, and
+ * the server will assume that the client has no knowledge of revisions
+ * prior to @a low_water_mark. These two limiting factors define the portion
+ * of the tree that the server will assume the client already has knowledge of,
+ * and thus any copies of data from outside that part of the tree will be
+ * sent in their entirety, not as simple copies or deltas against a previous
+ * version.
+ *
+ * If @a send_deltas is @c TRUE, the actual text and property changes in
+ * the revision will be sent, otherwise dummy text deltas and NULL property
+ * changes will be sent instead.
+ *
+ * @a pool is used for all allocation.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_replay_range(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *pool);
+
+/**
+ * Replay the changes from @a revision through @a editor and @a edit_baton.
+ *
+ * Changes will be limited to those that occur under @a session's URL, and
+ * the server will assume that the client has no knowledge of revisions
+ * prior to @a low_water_mark. These two limiting factors define the portion
+ * of the tree that the server will assume the client already has knowledge of,
+ * and thus any copies of data from outside that part of the tree will be
+ * sent in their entirety, not as simple copies or deltas against a previous
+ * version.
+ *
+ * If @a send_deltas is @c TRUE, the actual text and property changes in
+ * the revision will be sent, otherwise dummy text deltas and null property
+ * changes will be sent instead.
+ *
+ * @a pool is used for all allocation.
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_ra_replay(svn_ra_session_t *session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool);
+
+/**
+ * Given @a path at revision @a peg_revision, set @a *revision_deleted to the
+ * revision @a path was first deleted, within the inclusive revision range
+ * defined by @a peg_revision and @a end_revision. @a path is relative
+ * to the URL in @a session.
+ *
+ * If @a path does not exist at @a peg_revision or was not deleted within
+ * the specified range, then set @a *revision_deleted to @c SVN_INVALID_REVNUM.
+ * If @a peg_revision or @a end_revision are invalid or if @a peg_revision is
+ * greater than @a end_revision, then return @c SVN_ERR_CLIENT_BAD_REVISION.
+ *
+ * Use @a pool for all allocations.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_ra_get_deleted_rev(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *inherited_props to a depth-first ordered array of
+ * #svn_prop_inherited_item_t * structures representing the properties
+ * inherited by @a path at @a revision (or the 'head' revision if
+ * @a revision is @c SVN_INVALID_REVNUM). Interpret @a path relative to
+ * the URL in @a session. Use @a pool for all allocations. If no
+ * inheritable properties are found, then set @a *inherited_props to
+ * an empty array.
+ *
+ * The #svn_prop_inherited_item_t->path_or_url members of the
+ * #svn_prop_inherited_item_t * structures in @a *inherited_props are
+ * paths relative to the repository root URL (of the repository which
+ * @a ra_session is associated).
+ *
+ * Allocate @a *inherited_props in @a result_pool. Use @a scratch_pool
+ * for temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_ra_get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **inherited_props,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * @defgroup Capabilities Dynamically query the server's capabilities.
+ *
+ * @{
+ */
+
+/**
+ * Set @a *has to TRUE if the server represented by @a session has
+ * @a capability (one of the capabilities beginning with
+ * @c "SVN_RA_CAPABILITY_"), else set @a *has to FALSE.
+ *
+ * If @a capability isn't recognized, throw @c SVN_ERR_UNKNOWN_CAPABILITY,
+ * with the effect on @a *has undefined.
+ *
+ * Use @a pool for all allocation.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_ra_has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool);
+
+/**
+ * The capability of understanding @c svn_depth_t (e.g., the server
+ * understands what the client means when the client describes the
+ * depth of a working copy to the server.)
+ *
+ * @since New in 1.5.
+ */
+#define SVN_RA_CAPABILITY_DEPTH "depth"
+
+/**
+ * The capability of doing the right thing with merge-tracking
+ * information. This capability should be reported bidirectionally,
+ * because some repositories may want to reject clients that do not
+ * self-report as knowing how to handle merge-tracking.
+ *
+ * @since New in 1.5.
+ */
+#define SVN_RA_CAPABILITY_MERGEINFO "mergeinfo"
+
+/**
+ * The capability of retrieving arbitrary revprops in svn_ra_get_log2.
+ *
+ * @since New in 1.5.
+ */
+#define SVN_RA_CAPABILITY_LOG_REVPROPS "log-revprops"
+
+/**
+ * The capability of replaying a directory in the repository (partial replay).
+ *
+ * @since New in 1.5.
+ */
+#define SVN_RA_CAPABILITY_PARTIAL_REPLAY "partial-replay"
+
+/**
+ * The capability of including revision properties in a commit.
+ *
+ * @since New in 1.5.
+ */
+#define SVN_RA_CAPABILITY_COMMIT_REVPROPS "commit-revprops"
+
+/**
+ * The capability of specifying (and atomically verifying) expected
+ * preexisting values when modifying revprops.
+ *
+ * @since New in 1.7.
+ */
+#define SVN_RA_CAPABILITY_ATOMIC_REVPROPS "atomic-revprops"
+
+/**
+ * The capability to get inherited properties.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_RA_CAPABILITY_INHERITED_PROPS "inherited-props"
+
+/**
+ * The capability of a server to automatically remove transaction
+ * properties prefixed with SVN_PROP_EPHEMERAL_PREFIX.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS "ephemeral-txnprops"
+
+/**
+ * The capability of a server to walk revisions backwards in
+ * svn_ra_get_file_revs2
+ *
+ * @since New in 1.8.
+ */
+#define SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE "get-file-revs-reversed"
+
+
+/* *** PLEASE READ THIS IF YOU ADD A NEW CAPABILITY ***
+ *
+ * RA layers generally fetch all capabilities when asked about any
+ * capability, to save future round trips. So if you add a new
+ * capability here, make sure to update the RA layers to remember
+ * it after any capabilities query.
+ *
+ * Also note that capability strings should not include colons,
+ * because we pass a list of client capabilities to the start-commit
+ * hook as a single, colon-separated string.
+ */
+
+/** @} */
+
+
+/**
+ * Append a textual list of all available RA modules to the stringbuf
+ * @a output.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_ra_print_modules(svn_stringbuf_t *output,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_ra_print_modules().
+ * @a ra_baton is ignored.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_print_ra_libraries(svn_stringbuf_t **descriptions,
+ void *ra_baton,
+ apr_pool_t *pool);
+
+
+
+/**
+ * Using this callback struct is similar to calling the newer public
+ * interface that is based on @c svn_ra_session_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+typedef struct svn_ra_plugin_t
+{
+ /** The proper name of the RA library, (like "ra_serf" or "ra_local") */
+ const char *name;
+
+ /** Short doc string printed out by `svn --version` */
+ const char *description;
+
+ /* The vtable hooks */
+
+ /** Call svn_ra_open() and set @a session_baton to an object representing
+ * the new session. All other arguments are passed to svn_ra_open().
+ */
+ svn_error_t *(*open)(void **session_baton,
+ const char *repos_URL,
+ const svn_ra_callbacks_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_get_latest_revnum() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*get_latest_revnum)(void *session_baton,
+ svn_revnum_t *latest_revnum,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_get_dated_revision() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*get_dated_revision)(void *session_baton,
+ svn_revnum_t *revision,
+ apr_time_t tm,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_change_rev_prop() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*change_rev_prop)(void *session_baton,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_rev_proplist() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*rev_proplist)(void *session_baton,
+ svn_revnum_t rev,
+ apr_hash_t **props,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_rev_prop() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*rev_prop)(void *session_baton,
+ svn_revnum_t rev,
+ const char *name,
+ svn_string_t **value,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_get_commit_editor() with the session associated with
+ * @a session_baton and all other arguments plus @a lock_tokens set to
+ * @c NULL and @a keep_locks set to @c TRUE.
+ */
+ svn_error_t *(*get_commit_editor)(void *session_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const char *log_msg,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_get_file() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*get_file)(void *session_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_get_dir() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*get_dir)(void *session_baton,
+ const char *path,
+ svn_revnum_t revision,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_do_update() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*do_update)(void *session_baton,
+ const svn_ra_reporter_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_boolean_t recurse,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_do_switch() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*do_switch)(void *session_baton,
+ const svn_ra_reporter_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_boolean_t recurse,
+ const char *switch_url,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_do_status() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*do_status)(void *session_baton,
+ const svn_ra_reporter_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_boolean_t recurse,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_do_diff() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*do_diff)(void *session_baton,
+ const svn_ra_reporter_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_get_log() with the session associated with
+ * @a session_baton and all other arguments. @a limit is set to 0.
+ */
+ svn_error_t *(*get_log)(void *session_baton,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_check_path() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*check_path)(void *session_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_get_uuid() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*get_uuid)(void *session_baton,
+ const char **uuid,
+ apr_pool_t *pool);
+
+ /** Call svn_ra_get_repos_root() with the session associated with
+ * @a session_baton and all other arguments.
+ */
+ svn_error_t *(*get_repos_root)(void *session_baton,
+ const char **url,
+ apr_pool_t *pool);
+
+ /**
+ * Call svn_ra_get_locations() with the session associated with
+ * @a session_baton and all other arguments.
+ *
+ * @since New in 1.1.
+ */
+ svn_error_t *(*get_locations)(void *session_baton,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ apr_array_header_t *location_revisions,
+ apr_pool_t *pool);
+
+ /**
+ * Call svn_ra_get_file_revs() with the session associated with
+ * @a session_baton and all other arguments.
+ *
+ * @since New in 1.1.
+ */
+ svn_error_t *(*get_file_revs)(void *session_baton,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_ra_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+ /**
+ * Return the plugin's version information.
+ *
+ * @since New in 1.1.
+ */
+ const svn_version_t *(*get_version)(void);
+
+
+} svn_ra_plugin_t;
+
+/**
+ * All "ra_FOO" implementations *must* export a function named
+ * svn_ra_FOO_init() of type @c svn_ra_init_func_t.
+ *
+ * When called by libsvn_client, this routine adds an entry (or
+ * entries) to the hash table for any URL schemes it handles. The hash
+ * value must be of type (<tt>@c svn_ra_plugin_t *</tt>). @a pool is a
+ * pool for allocating configuration / one-time data.
+ *
+ * This type is defined to use the "C Calling Conventions" to ensure that
+ * abi_version is the first parameter. The RA plugin must check that value
+ * before accessing the other parameters.
+ *
+ * ### need to force this to be __cdecl on Windows... how??
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+typedef svn_error_t *(*svn_ra_init_func_t)(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash);
+
+/**
+ * The current ABI (Application Binary Interface) version for the
+ * RA plugin model. This version number will change when the ABI
+ * between the SVN core (e.g. libsvn_client) and the RA plugin changes.
+ *
+ * An RA plugin should verify that the passed version number is acceptable
+ * before accessing the rest of the parameters, and before returning any
+ * information.
+ *
+ * It is entirely acceptable for an RA plugin to accept multiple ABI
+ * versions. It can simply interpret the parameters based on the version,
+ * and it can return different plugin structures.
+ *
+ *
+ * <pre>
+ * VSN DATE REASON FOR CHANGE
+ * --- ---------- ------------------------------------------------
+ * 1 2001-02-17 Initial revision.
+ * 2 2004-06-29 Preparing for svn 1.1, which adds new RA vtable funcs.
+ * 2005-01-19 Rework the plugin interface and don't provide the vtable
+ * to the client. Separate ABI versions are no longer used.
+ * </pre>
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+#define SVN_RA_ABI_VERSION 2
+
+/* Public RA implementations. */
+
+/** Initialize libsvn_ra_serf.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_dav_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash);
+
+/** Initialize libsvn_ra_local.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_local_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash);
+
+/** Initialize libsvn_ra_svn.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash);
+
+/** Initialize libsvn_ra_serf.
+ *
+ * @since New in 1.4.
+ * @deprecated Provided for backward compatibility with the 1.1 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_serf_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash);
+
+
+/**
+ * Initialize the compatibility wrapper, using @a pool for any allocations.
+ * The caller must hold on to @a ra_baton as long as the RA library is used.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_init_ra_libs(void **ra_baton,
+ apr_pool_t *pool);
+
+/**
+ * Return an RA vtable-@a library which can handle URL. A number of
+ * svn_client_* routines will call this internally, but client apps might
+ * use it too. $a ra_baton is a baton obtained by a call to
+ * svn_ra_init_ra_libs().
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_get_ra_library(svn_ra_plugin_t **library,
+ void *ra_baton,
+ const char *url,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_RA_H */
+
diff --git a/contrib/subversion/subversion/include/svn_ra_svn.h b/contrib/subversion/subversion/include/svn_ra_svn.h
new file mode 100644
index 0000000..9c556b8
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_ra_svn.h
@@ -0,0 +1,668 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_ra_svn.h
+ * @brief libsvn_ra_svn functions used by the server
+ */
+
+#ifndef SVN_RA_SVN_H
+#define SVN_RA_SVN_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_file_io.h> /* for apr_file_t */
+#include <apr_network_io.h> /* for apr_socket_t */
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** The well-known svn port number. */
+#define SVN_RA_SVN_PORT 3690
+
+/** Currently-defined capabilities. */
+#define SVN_RA_SVN_CAP_EDIT_PIPELINE "edit-pipeline"
+#define SVN_RA_SVN_CAP_SVNDIFF1 "svndiff1"
+#define SVN_RA_SVN_CAP_ABSENT_ENTRIES "absent-entries"
+/* maps to SVN_RA_CAPABILITY_COMMIT_REVPROPS: */
+#define SVN_RA_SVN_CAP_COMMIT_REVPROPS "commit-revprops"
+/* maps to SVN_RA_CAPABILITY_MERGEINFO: */
+#define SVN_RA_SVN_CAP_MERGEINFO "mergeinfo"
+/* maps to SVN_RA_CAPABILITY_DEPTH: */
+#define SVN_RA_SVN_CAP_DEPTH "depth"
+/* maps to SVN_RA_CAPABILITY_LOG_REVPROPS */
+#define SVN_RA_SVN_CAP_LOG_REVPROPS "log-revprops"
+/* maps to SVN_RA_CAPABILITY_PARTIAL_REPLAY */
+#define SVN_RA_SVN_CAP_PARTIAL_REPLAY "partial-replay"
+/* maps to SVN_RA_CAPABILITY_ATOMIC_REVPROPS */
+#define SVN_RA_SVN_CAP_ATOMIC_REVPROPS "atomic-revprops"
+/* maps to SVN_RA_CAPABILITY_INHERITED_PROPERTIES: */
+#define SVN_RA_SVN_CAP_INHERITED_PROPS "inherited-props"
+/* maps to SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS */
+#define SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS "ephemeral-txnprops"
+/* maps to SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE */
+#define SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE "file-revs-reverse"
+
+
+/** ra_svn passes @c svn_dirent_t fields over the wire as a list of
+ * words, these are the values used to represent each field.
+ *
+ * @defgroup ra_svn_dirent_fields Definitions of ra_svn dirent fields
+ * @{
+ */
+
+/** The ra_svn way of saying @c SVN_DIRENT_KIND. */
+#define SVN_RA_SVN_DIRENT_KIND "kind"
+
+/** The ra_svn way of saying @c SVN_DIRENT_SIZE. */
+#define SVN_RA_SVN_DIRENT_SIZE "size"
+
+/** The ra_svn way of saying @c SVN_DIRENT_HAS_PROPS. */
+#define SVN_RA_SVN_DIRENT_HAS_PROPS "has-props"
+
+/** The ra_svn way of saying @c SVN_DIRENT_CREATED_REV. */
+#define SVN_RA_SVN_DIRENT_CREATED_REV "created-rev"
+
+/** The ra_svn way of saying @c SVN_DIRENT_TIME. */
+#define SVN_RA_SVN_DIRENT_TIME "time"
+
+/** The ra_svn way of saying @c SVN_DIRENT_LAST_AUTHOR. */
+#define SVN_RA_SVN_DIRENT_LAST_AUTHOR "last-author"
+
+/** @} */
+
+/** A value used to indicate an optional number element in a tuple that was
+ * not received.
+ */
+#define SVN_RA_SVN_UNSPECIFIED_NUMBER ~((apr_uint64_t) 0)
+
+/** A specialized form of @c SVN_ERR to deal with errors which occur in an
+ * svn_ra_svn_command_handler().
+ *
+ * An error returned with this macro will be passed back to the other side
+ * of the connection. Use this macro when performing the requested operation;
+ * use the regular @c SVN_ERR when performing I/O with the client.
+ */
+#define SVN_CMD_ERR(expr) \
+ do { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, \
+ svn_err__temp, NULL); \
+ } while (0)
+
+/** an ra_svn connection. */
+typedef struct svn_ra_svn_conn_st svn_ra_svn_conn_t;
+
+/** Command handler, used by svn_ra_svn_handle_commands(). */
+typedef svn_error_t *(*svn_ra_svn_command_handler)(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_array_header_t *params,
+ void *baton);
+
+/** Command table, used by svn_ra_svn_handle_commands().
+ */
+typedef struct svn_ra_svn_cmd_entry_t
+{
+ /** Name of the command */
+ const char *cmdname;
+
+ /** Handler for the command */
+ svn_ra_svn_command_handler handler;
+
+ /** Termination flag. If set, command-handling will cease after
+ * command is processed. */
+ svn_boolean_t terminate;
+} svn_ra_svn_cmd_entry_t;
+
+/** Memory representation of an on-the-wire data item. */
+typedef struct svn_ra_svn_item_t
+{
+ /** Variant indicator. */
+ enum {
+ SVN_RA_SVN_NUMBER,
+ SVN_RA_SVN_STRING,
+ SVN_RA_SVN_WORD,
+ SVN_RA_SVN_LIST
+ } kind;
+ /** Variant data. */
+ union {
+ apr_uint64_t number;
+ svn_string_t *string;
+ const char *word;
+
+ /** Contains @c svn_ra_svn_item_t's. */
+ apr_array_header_t *list;
+ } u;
+} svn_ra_svn_item_t;
+
+typedef svn_error_t *(*svn_ra_svn_edit_callback)(void *baton);
+
+/** Initialize a connection structure for the given socket or
+ * input/output files.
+ *
+ * Either @a sock or @a in_file/@a out_file must be set, not both.
+ * @a compression_level specifies the desired network data compression
+ * level (zlib) from 0 (no compression) to 9 (best but slowest).
+ *
+ * If @a zero_copy_limit is not 0, cached file contents smaller than the
+ * given limit may be sent directly to the network socket. Otherwise,
+ * it will be copied into a temporary buffer before being forwarded to
+ * the network stack. Since the zero-copy code path has to enforce strict
+ * time-outs, the receiver must be able to process @a zero_copy_limit
+ * bytes within one second. Even temporary failure to do so may cause
+ * the server to cancel the respective operation with a time-out error.
+ *
+ * To reduce the overhead of checking for cancellation requests from the
+ * data receiver, set @a error_check_interval to some non-zero value.
+ * It defines the number of bytes that must have been sent since the last
+ * check before the next check will be made.
+ *
+ * Allocate the result in @a pool.
+ *
+ * @since New in 1.8
+ */
+svn_ra_svn_conn_t *svn_ra_svn_create_conn3(apr_socket_t *sock,
+ apr_file_t *in_file,
+ apr_file_t *out_file,
+ int compression_level,
+ apr_size_t zero_copy_limit,
+ apr_size_t error_check_interval,
+ apr_pool_t *pool);
+
+/** Similar to svn_ra_svn_create_conn3() but disables the zero copy code
+ * path and sets the error checking interval to 0.
+ *
+ * @since New in 1.7.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_ra_svn_conn_t *
+svn_ra_svn_create_conn2(apr_socket_t *sock,
+ apr_file_t *in_file,
+ apr_file_t *out_file,
+ int compression_level,
+ apr_pool_t *pool);
+
+/** Similar to svn_ra_svn_create_conn2() but uses the default
+ * compression level (#SVN_DELTA_COMPRESSION_LEVEL_DEFAULT) for network
+ * transmissions.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_ra_svn_conn_t *
+svn_ra_svn_create_conn(apr_socket_t *sock,
+ apr_file_t *in_file,
+ apr_file_t *out_file,
+ apr_pool_t *pool);
+
+/** Add the capabilities in @a list to @a conn's capabilities.
+ * @a list contains svn_ra_svn_item_t entries (which should be of type
+ * SVN_RA_SVN_WORD; a malformed data error will result if any are not).
+ *
+ * This is idempotent: if a given capability was already set for
+ * @a conn, it remains set.
+ */
+svn_error_t *
+svn_ra_svn_set_capabilities(svn_ra_svn_conn_t *conn,
+ const apr_array_header_t *list);
+
+/** Return @c TRUE if @a conn has the capability @a capability, or
+ * @c FALSE if it does not. */
+svn_boolean_t
+svn_ra_svn_has_capability(svn_ra_svn_conn_t *conn,
+ const char *capability);
+
+/** Return the data compression level to use for network transmissions.
+ *
+ * @since New in 1.7.
+ */
+int
+svn_ra_svn_compression_level(svn_ra_svn_conn_t *conn);
+
+/** Return the zero-copy data block limit to use for network
+ * transmissions.
+ *
+ * @see http://en.wikipedia.org/wiki/Zero-copy
+ *
+ * @since New in 1.8.
+ */
+apr_size_t
+svn_ra_svn_zero_copy_limit(svn_ra_svn_conn_t *conn);
+
+/** Returns the remote address of the connection as a string, if known,
+ * or NULL if inapplicable. */
+const char *
+svn_ra_svn_conn_remote_host(svn_ra_svn_conn_t *conn);
+
+/** Set @a *editor and @a *edit_baton to an editor which will pass editing
+ * operations over the network, using @a conn and @a pool.
+ *
+ * Upon successful completion of the edit, the editor will invoke @a callback
+ * with @a callback_baton as an argument.
+ */
+void
+svn_ra_svn_get_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_ra_svn_edit_callback callback,
+ void *callback_baton);
+
+/** Receive edit commands over the network and use them to drive @a editor
+ * with @a edit_baton. On return, @a *aborted will be set if the edit was
+ * aborted. The drive can be terminated with a finish-replay command only
+ * if @a for_replay is TRUE.
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_boolean_t *aborted,
+ svn_boolean_t for_replay);
+
+/** Like svn_ra_svn_drive_editor2, but with @a for_replay always FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_boolean_t *aborted);
+
+/** This function is only intended for use by svnserve.
+ *
+ * Perform CRAM-MD5 password authentication. On success, return
+ * SVN_NO_ERROR with *user set to the username and *success set to
+ * TRUE. On an error which can be reported to the client, report the
+ * error and return SVN_NO_ERROR with *success set to FALSE. On
+ * communications failure, return an error.
+ */
+svn_error_t *
+svn_ra_svn_cram_server(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_config_t *pwdb,
+ const char **user,
+ svn_boolean_t *success);
+
+/**
+ * Get libsvn_ra_svn version information.
+ * @since New in 1.1.
+ */
+const svn_version_t *
+svn_ra_svn_version(void);
+
+/**
+ * @defgroup ra_svn_deprecated ra_svn low-level functions
+ * @{
+ */
+
+/** Write a number over the net.
+ *
+ * Writes will be buffered until the next read or flush.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_write_number(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_uint64_t number);
+
+/** Write a string over the net.
+ *
+ * Writes will be buffered until the next read or flush.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_write_string(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_string_t *str);
+
+/** Write a cstring over the net.
+ *
+ * Writes will be buffered until the next read or flush.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_write_cstring(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *s);
+
+/** Write a word over the net.
+ *
+ * Writes will be buffered until the next read or flush.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_write_word(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *word);
+
+/** Write a list of properties over the net. @a props is allowed to be NULL,
+ * in which case an empty list will be written out.
+ *
+ * @since New in 1.5.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_write_proplist(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_hash_t *props);
+
+/** Begin a list. Writes will be buffered until the next read or flush.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_start_list(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** End a list. Writes will be buffered until the next read or flush.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_end_list(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** Flush the write buffer.
+ *
+ * Normally this shouldn't be necessary, since the write buffer is flushed
+ * when a read is attempted.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_flush(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** Write a tuple, using a printf-like interface.
+ *
+ * The format string @a fmt may contain:
+ *
+ *@verbatim
+ Spec Argument type Item type
+ ---- -------------------- ---------
+ n apr_uint64_t Number
+ r svn_revnum_t Number
+ s const svn_string_t * String
+ c const char * String
+ w const char * Word
+ b svn_boolean_t Word ("true" or "false")
+ ( Begin tuple
+ ) End tuple
+ ? Remaining elements optional
+ ! (at beginning or end) Suppress opening or closing of tuple
+ @endverbatim
+ *
+ * Inside the optional part of a tuple, 'r' values may be @c
+ * SVN_INVALID_REVNUM, 'n' values may be
+ * SVN_RA_SVN_UNSPECIFIED_NUMBER, and 's', 'c', and 'w' values may be
+ * @c NULL; in these cases no data will be written. 'b' and '(' may
+ * not appear in the optional part of a tuple. Either all or none of
+ * the optional values should be valid.
+ *
+ * (If we ever have a need for an optional boolean value, we should
+ * invent a 'B' specifier which stores a boolean into an int, using -1
+ * for unspecified. Right now there is no need for such a thing.)
+ *
+ * Use the '!' format specifier to write partial tuples when you have
+ * to transmit an array or other unusual data. For example, to write
+ * a tuple containing a revision, an array of words, and a boolean:
+ * @code
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "r(!", rev));
+ for (i = 0; i < n; i++)
+ SVN_ERR(svn_ra_svn_write_word(conn, pool, words[i]));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)b", flag)); @endcode
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_write_tuple(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...);
+
+/** Read an item from the network into @a *item.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_read_item(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_ra_svn_item_t **item);
+
+/** Scan data on @a conn until we find something which looks like the
+ * beginning of an svn server greeting (an open paren followed by a
+ * whitespace character). This function is appropriate for beginning
+ * a client connection opened in tunnel mode, since people's dotfiles
+ * sometimes write output to stdout. It may only be called at the
+ * beginning of a client connection.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_skip_leading_garbage(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/** Parse an array of @c svn_sort__item_t structures as a tuple, using a
+ * printf-like interface. The format string @a fmt may contain:
+ *
+ *@verbatim
+ Spec Argument type Item type
+ ---- -------------------- ---------
+ n apr_uint64_t * Number
+ r svn_revnum_t * Number
+ s svn_string_t ** String
+ c const char ** String
+ w const char ** Word
+ b svn_boolean_t * Word ("true" or "false")
+ B apr_uint64_t * Word ("true" or "false")
+ l apr_array_header_t ** List
+ ( Begin tuple
+ ) End tuple
+ ? Tuple is allowed to end here
+ @endverbatim
+ *
+ * Note that a tuple is only allowed to end precisely at a '?', or at
+ * the end of the specification. So if @a fmt is "c?cc" and @a list
+ * contains two elements, an error will result.
+ *
+ * 'B' is similar to 'b', but may be used in the optional tuple specification.
+ * It returns TRUE, FALSE, or SVN_RA_SVN_UNSPECIFIED_NUMBER.
+ *
+ * If an optional part of a tuple contains no data, 'r' values will be
+ * set to @c SVN_INVALID_REVNUM, 'n' and 'B' values will be set to
+ * SVN_RA_SVN_UNSPECIFIED_NUMBER, and 's', 'c', 'w', and 'l' values
+ * will be set to @c NULL. 'b' may not appear inside an optional
+ * tuple specification; use 'B' instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_parse_tuple(const apr_array_header_t *list,
+ apr_pool_t *pool,
+ const char *fmt, ...);
+
+/** Read a tuple from the network and parse it as a tuple, using the
+ * format string notation from svn_ra_svn_parse_tuple().
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_read_tuple(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...);
+
+/** Parse an array of @c svn_ra_svn_item_t structures as a list of
+ * properties, storing the properties in a hash table.
+ *
+ * @since New in 1.5.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_parse_proplist(const apr_array_header_t *list,
+ apr_pool_t *pool,
+ apr_hash_t **props);
+
+/** Read a command response from the network and parse it as a tuple, using
+ * the format string notation from svn_ra_svn_parse_tuple().
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_read_cmd_response(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...);
+
+/** Accept commands over the network and handle them according to @a
+ * commands. Command handlers will be passed @a conn, a subpool of @a
+ * pool (cleared after each command is handled), the parameters of the
+ * command, and @a baton. Commands will be accepted until a
+ * terminating command is received (a command with "terminate" set in
+ * the command table). If a command handler returns an error wrapped
+ * in SVN_RA_SVN_CMD_ERR (see the @c SVN_CMD_ERR macro), the error
+ * will be reported to the other side of the connection and the
+ * command loop will continue; any other kind of error (typically a
+ * network or protocol error) is passed through to the caller.
+ *
+ * @since New in 1.6.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_handle_commands2(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_ra_svn_cmd_entry_t *commands,
+ void *baton,
+ svn_boolean_t error_on_disconnect);
+
+/** Similar to svn_ra_svn_handle_commands2 but @a error_on_disconnect
+ * is always @c FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_handle_commands(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_ra_svn_cmd_entry_t *commands,
+ void *baton);
+
+/** Write a command over the network, using the same format string notation
+ * as svn_ra_svn_write_tuple().
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_write_cmd(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *cmdname,
+ const char *fmt, ...);
+
+/** Write a successful command response over the network, using the
+ * same format string notation as svn_ra_svn_write_tuple(). Do not use
+ * partial tuples with this function; if you need to use partial
+ * tuples, just write out the "success" and argument tuple by hand.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_write_cmd_response(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...);
+
+/** Write an unsuccessful command response over the network.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * RA_SVN low-level functions are no longer considered public.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_ra_svn_write_cmd_failure(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_error_t *err);
+
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_RA_SVN_H */
diff --git a/contrib/subversion/subversion/include/svn_repos.h b/contrib/subversion/subversion/include/svn_repos.h
new file mode 100644
index 0000000..2cec6dd
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_repos.h
@@ -0,0 +1,3406 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_repos.h
+ * @brief Tools built on top of the filesystem.
+ */
+
+#ifndef SVN_REPOS_H
+#define SVN_REPOS_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_time.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_delta.h"
+#include "svn_fs.h"
+#include "svn_io.h"
+#include "svn_mergeinfo.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* ---------------------------------------------------------------*/
+
+/**
+ * Get libsvn_repos version information.
+ *
+ * @since New in 1.1.
+ */
+const svn_version_t *
+svn_repos_version(void);
+
+
+/* Some useful enums. They need to be declared here for the notification
+ system to pick them up. */
+/** The different "actions" attached to nodes in the dumpfile. */
+enum svn_node_action
+{
+ svn_node_action_change,
+ svn_node_action_add,
+ svn_node_action_delete,
+ svn_node_action_replace
+};
+
+/** The different policies for processing the UUID in the dumpfile. */
+enum svn_repos_load_uuid
+{
+ /** only update uuid if the repos has no revisions. */
+ svn_repos_load_uuid_default,
+ /** never update uuid. */
+ svn_repos_load_uuid_ignore,
+ /** always update uuid. */
+ svn_repos_load_uuid_force
+};
+
+
+/** Callback type for checking authorization on paths produced by (at
+ * least) svn_repos_dir_delta2().
+ *
+ * Set @a *allowed to TRUE to indicate that some operation is
+ * authorized for @a path in @a root, or set it to FALSE to indicate
+ * unauthorized (presumably according to state stored in @a baton).
+ *
+ * Do not assume @a pool has any lifetime beyond this call.
+ *
+ * The exact operation being authorized depends on the callback
+ * implementation. For read authorization, for example, the caller
+ * would implement an instance that does read checking, and pass it as
+ * a parameter named [perhaps] 'authz_read_func'. The receiver of
+ * that parameter might also take another parameter named
+ * 'authz_write_func', which although sharing this type, would be a
+ * different implementation.
+ *
+ * @note If someday we want more sophisticated authorization states
+ * than just yes/no, @a allowed can become an enum type.
+ */
+typedef svn_error_t *(*svn_repos_authz_func_t)(svn_boolean_t *allowed,
+ svn_fs_root_t *root,
+ const char *path,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/** An enum defining the kinds of access authz looks up.
+ *
+ * @since New in 1.3.
+ */
+typedef enum svn_repos_authz_access_t
+{
+ /** No access. */
+ svn_authz_none = 0,
+
+ /** Path can be read. */
+ svn_authz_read = 1,
+
+ /** Path can be altered. */
+ svn_authz_write = 2,
+
+ /** The other access credentials are recursive. */
+ svn_authz_recursive = 4
+} svn_repos_authz_access_t;
+
+
+/** Callback type for checking authorization on paths produced by
+ * the repository commit editor.
+ *
+ * Set @a *allowed to TRUE to indicate that the @a required access on
+ * @a path in @a root is authorized, or set it to FALSE to indicate
+ * unauthorized (presumable according to state stored in @a baton).
+ *
+ * If @a path is NULL, the callback should perform a global authz
+ * lookup for the @a required access. That is, the lookup should
+ * check if the @a required access is granted for at least one path of
+ * the repository, and set @a *allowed to TRUE if so. @a root may
+ * also be NULL if @a path is NULL.
+ *
+ * This callback is very similar to svn_repos_authz_func_t, with the
+ * exception of the addition of the @a required parameter.
+ * This is due to historical reasons: when authz was first implemented
+ * for svn_repos_dir_delta2(), it seemed there would need only checks
+ * for read and write operations, hence the svn_repos_authz_func_t
+ * callback prototype and usage scenario. But it was then realized
+ * that lookups due to copying needed to be recursive, and that
+ * brute-force recursive lookups didn't square with the O(1)
+ * performances a copy operation should have.
+ *
+ * So a special way to ask for a recursive lookup was introduced. The
+ * commit editor needs this capability to retain acceptable
+ * performance. Instead of revving the existing callback, causing
+ * unnecessary revving of functions that don't actually need the
+ * extended functionality, this second, more complete callback was
+ * introduced, for use by the commit editor.
+ *
+ * Some day, it would be nice to reunite these two callbacks and do
+ * the necessary revving anyway, but for the time being, this dual
+ * callback mechanism will do.
+ */
+typedef svn_error_t *(*svn_repos_authz_callback_t)
+ (svn_repos_authz_access_t required,
+ svn_boolean_t *allowed,
+ svn_fs_root_t *root,
+ const char *path,
+ void *baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to #svn_file_rev_handler_t, but without the @a
+ * result_of_merge parameter.
+ *
+ * @deprecated Provided for backward compatibility with 1.4 API.
+ * @since New in 1.1.
+ */
+typedef svn_error_t *(*svn_repos_file_rev_handler_t)
+ (void *baton,
+ const char *path,
+ svn_revnum_t rev,
+ apr_hash_t *rev_props,
+ svn_txdelta_window_handler_t *delta_handler,
+ void **delta_baton,
+ apr_array_header_t *prop_diffs,
+ apr_pool_t *pool);
+
+
+/* Notification system. */
+
+/** The type of action occurring.
+ *
+ * @since New in 1.7.
+ */
+typedef enum svn_repos_notify_action_t
+{
+ /** A warning message is waiting. */
+ svn_repos_notify_warning = 0,
+
+ /** A revision has finished being dumped. */
+ svn_repos_notify_dump_rev_end,
+
+ /** A revision has finished being verified. */
+ svn_repos_notify_verify_rev_end,
+
+ /** All revisions have finished being dumped. */
+ svn_repos_notify_dump_end,
+
+ /** All revisions have finished being verified. */
+ svn_repos_notify_verify_end,
+
+ /** packing of an FSFS shard has commenced */
+ svn_repos_notify_pack_shard_start,
+
+ /** packing of an FSFS shard is completed */
+ svn_repos_notify_pack_shard_end,
+
+ /** packing of the shard revprops has commenced */
+ svn_repos_notify_pack_shard_start_revprop,
+
+ /** packing of the shard revprops has completed */
+ svn_repos_notify_pack_shard_end_revprop,
+
+ /** A revision has begun loading */
+ svn_repos_notify_load_txn_start,
+
+ /** A revision has finished loading */
+ svn_repos_notify_load_txn_committed,
+
+ /** A node has begun loading */
+ svn_repos_notify_load_node_start,
+
+ /** A node has finished loading */
+ svn_repos_notify_load_node_done,
+
+ /** A copied node has been encountered */
+ svn_repos_notify_load_copied_node,
+
+ /** Mergeinfo has been normalized */
+ svn_repos_notify_load_normalized_mergeinfo,
+
+ /** The operation has acquired a mutex for the repo. */
+ svn_repos_notify_mutex_acquired,
+
+ /** Recover has started. */
+ svn_repos_notify_recover_start,
+
+ /** Upgrade has started. */
+ svn_repos_notify_upgrade_start,
+
+ /** A revision was skipped during loading. @since New in 1.8. */
+ svn_repos_notify_load_skipped_rev,
+
+ /** The structure of a revision is being verified. @since New in 1.8. */
+ svn_repos_notify_verify_rev_structure
+
+} svn_repos_notify_action_t;
+
+/** The type of error occurring.
+ *
+ * @since New in 1.7.
+ */
+typedef enum svn_repos_notify_warning_t
+{
+ /** Referencing copy source data from a revision earlier than the
+ * first revision dumped. */
+ svn_repos_notify_warning_found_old_reference,
+
+ /** An #SVN_PROP_MERGEINFO property's encoded mergeinfo references a
+ * revision earlier than the first revision dumped. */
+ svn_repos_notify_warning_found_old_mergeinfo,
+
+ /** Found an invalid path in the filesystem.
+ * @see svn_fs.h:"Directory entry names and directory paths" */
+ /* ### TODO(doxygen): make that a proper doxygen link */
+ /* See svn_fs__path_valid(). */
+ svn_repos_notify_warning_invalid_fspath
+
+} svn_repos_notify_warning_t;
+
+/**
+ * Structure used by #svn_repos_notify_func_t.
+ *
+ * The only field guaranteed to be populated is @c action. Other fields are
+ * dependent upon the @c action. (See individual fields for more information.)
+ *
+ * @note Callers of notification functions should use
+ * svn_repos_notify_create() to create structures of this type to allow for
+ * future extensibility.
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_repos_notify_t
+{
+ /** Action that describes what happened in the repository. */
+ svn_repos_notify_action_t action;
+
+ /** For #svn_repos_notify_dump_rev_end and #svn_repos_notify_verify_rev_end,
+ * the revision which just completed. */
+ svn_revnum_t revision;
+
+ /** For #svn_repos_notify_warning, the warning object. Must be cleared
+ by the consumer of the notification. */
+ const char *warning_str;
+ svn_repos_notify_warning_t warning;
+
+ /** For #svn_repos_notify_pack_shard_start,
+ #svn_repos_notify_pack_shard_end,
+ #svn_repos_notify_pack_shard_start_revprop, and
+ #svn_repos_notify_pack_shard_end_revprop, the shard processed. */
+ apr_int64_t shard;
+
+ /** For #svn_repos_notify_load_node_done, the revision committed. */
+ svn_revnum_t new_revision;
+
+ /** For #svn_repos_notify_load_node_done, the source revision, if
+ different from @a new_revision, otherwise #SVN_INVALID_REVNUM.
+ For #svn_repos_notify_load_txn_start, the source revision. */
+ svn_revnum_t old_revision;
+
+ /** For #svn_repos_notify_load_node_start, the action being taken on the
+ node. */
+ enum svn_node_action node_action;
+
+ /** For #svn_repos_notify_load_node_start, the path of the node. */
+ const char *path;
+
+ /* NOTE: Add new fields at the end to preserve binary compatibility.
+ Also, if you add fields here, you have to update
+ svn_repos_notify_create(). */
+} svn_repos_notify_t;
+
+/** Callback for providing notification from the repository.
+ * Returns @c void. Justification: success of an operation is not dependent
+ * upon successful notification of that operation.
+ *
+ * @since New in 1.7. */
+typedef void (*svn_repos_notify_func_t)(void *baton,
+ const svn_repos_notify_t *notify,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Allocate an #svn_repos_notify_t structure in @a result_pool, initialize
+ * and return it.
+ *
+ * @since New in 1.7.
+ */
+svn_repos_notify_t *
+svn_repos_notify_create(svn_repos_notify_action_t action,
+ apr_pool_t *result_pool);
+
+
+/** The repository object. */
+typedef struct svn_repos_t svn_repos_t;
+
+/* Opening and creating repositories. */
+
+
+/** Find the root path of the repository that contains @a path.
+ *
+ * If a repository was found, the path to the root of the repository
+ * is returned, else @c NULL. The pointer to the returned path may be
+ * equal to @a path.
+ */
+const char *
+svn_repos_find_root_path(const char *path,
+ apr_pool_t *pool);
+
+/** Set @a *repos_p to a repository object for the repository at @a path.
+ *
+ * Allocate @a *repos_p in @a pool.
+ *
+ * Acquires a shared lock on the repository, and attaches a cleanup
+ * function to @a pool to remove the lock. If no lock can be acquired,
+ * returns error, with undefined effect on @a *repos_p. If an exclusive
+ * lock is present, this blocks until it's gone. @a fs_config will be
+ * passed to the filesystem initialization function and may be @c NULL.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_repos_open2(svn_repos_t **repos_p,
+ const char *path,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool);
+
+/** Similar to svn_repos_open2() with @a fs_config set to NULL.
+ *
+ * @deprecated Provided for backward compatibility with 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_open(svn_repos_t **repos_p,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Create a new Subversion repository at @a path, building the necessary
+ * directory structure, creating the filesystem, and so on.
+ * Return the repository object in @a *repos_p, allocated in @a pool.
+ *
+ * @a config is a client configuration hash of #svn_config_t * items
+ * keyed on config category names, and may be NULL.
+ *
+ * @a fs_config is passed to the filesystem, and may be NULL.
+ *
+ * @a unused_1 and @a unused_2 are not used and should be NULL.
+ */
+svn_error_t *
+svn_repos_create(svn_repos_t **repos_p,
+ const char *path,
+ const char *unused_1,
+ const char *unused_2,
+ apr_hash_t *config,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool);
+
+/**
+ * Upgrade the Subversion repository (and its underlying versioned
+ * filesystem) located in the directory @a path to the latest version
+ * supported by this library. If the requested upgrade is not
+ * supported due to the current state of the repository or it
+ * underlying filesystem, return #SVN_ERR_REPOS_UNSUPPORTED_UPGRADE
+ * or #SVN_ERR_FS_UNSUPPORTED_UPGRADE (respectively) and make no
+ * changes to the repository or filesystem.
+ *
+ * Acquires an exclusive lock on the repository, upgrades the
+ * repository, and releases the lock. If an exclusive lock can't be
+ * acquired, returns error.
+ *
+ * If @a nonblocking is TRUE, an error of type EWOULDBLOCK is
+ * returned if the lock is not immediately available.
+ *
+ * If @a start_callback is not NULL, it will be called with @a
+ * start_callback_baton as argument before the upgrade starts, but
+ * after the exclusive lock has been acquired.
+ *
+ * Use @a pool for necessary allocations.
+ *
+ * @note This functionality is provided as a convenience for
+ * administrators wishing to make use of new Subversion functionality
+ * without a potentially costly full repository dump/load. As such,
+ * the operation performs only the minimum amount of work needed to
+ * accomplish this while maintaining the integrity of the repository.
+ * It does *not* guarantee the most optimized repository state as a
+ * dump and subsequent load would.
+ *
+ * @note On some platforms the exclusive lock does not exclude other
+ * threads in the same process so this function should only be called
+ * by a single threaded process, or by a multi-threaded process when
+ * no other threads are accessing the repository.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_repos_upgrade2(const char *path,
+ svn_boolean_t nonblocking,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_upgrade2(), but with @a start_callback and baton,
+ * rather than a notify_callback / baton
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_upgrade(const char *path,
+ svn_boolean_t nonblocking,
+ svn_error_t *(*start_callback)(void *baton),
+ void *start_callback_baton,
+ apr_pool_t *pool);
+
+/** Destroy the Subversion repository found at @a path, using @a pool for any
+ * necessary allocations.
+ */
+svn_error_t *
+svn_repos_delete(const char *path,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *has to TRUE if @a repos has @a capability (one of the
+ * capabilities beginning with @c "SVN_REPOS_CAPABILITY_"), else set
+ * @a *has to FALSE.
+ *
+ * If @a capability isn't recognized, throw #SVN_ERR_UNKNOWN_CAPABILITY,
+ * with the effect on @a *has undefined.
+ *
+ * Use @a pool for all allocation.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_has_capability(svn_repos_t *repos,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * The capability of doing the right thing with merge-tracking
+ * information, both storing it and responding to queries about it.
+ *
+ * @since New in 1.5.
+ */
+#define SVN_REPOS_CAPABILITY_MERGEINFO "mergeinfo"
+/* *** PLEASE READ THIS IF YOU ADD A NEW CAPABILITY ***
+ *
+ * @c SVN_REPOS_CAPABILITY_foo strings should not include colons, to
+ * be consistent with @c SVN_RA_CAPABILITY_foo strings, which forbid
+ * colons for their own reasons. While this RA limitation has no
+ * direct impact on repository capabilities, there's no reason to be
+ * gratuitously different either.
+ *
+ * If you add a capability, update svn_repos_capabilities().
+ */
+
+
+/** Return the filesystem associated with repository object @a repos. */
+svn_fs_t *
+svn_repos_fs(svn_repos_t *repos);
+
+
+/** Make a hot copy of the Subversion repository found at @a src_path
+ * to @a dst_path.
+ *
+ * Copy a possibly live Subversion repository from @a src_path to
+ * @a dst_path. If @a clean_logs is @c TRUE, perform cleanup on the
+ * source filesystem as part of the copy operation; currently, this
+ * means deleting copied, unused logfiles for a Berkeley DB source
+ * repository.
+ *
+ * If @a incremental is TRUE, make an effort to not re-copy information
+ * already present in the destination. If incremental hotcopy is not
+ * implemented by the filesystem backend, raise SVN_ERR_UNSUPPORTED_FEATURE.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_hotcopy2(const char *src_path,
+ const char *dst_path,
+ svn_boolean_t clean_logs,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Like svn_repos_hotcopy2(), but with @a incremental always passed as
+ * @c FALSE and without cancellation support.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_hotcopy(const char *src_path,
+ const char *dst_path,
+ svn_boolean_t clean_logs,
+ apr_pool_t *pool);
+
+
+/**
+ * Possibly update the repository, @a repos, to use a more efficient
+ * filesystem representation. Use @a pool for allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_repos_fs_pack2(svn_repos_t *repos,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_fs_pack2(), but with a #svn_fs_pack_notify_t instead
+ * of a #svn_repos_notify_t.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_fs_pack(svn_repos_t *repos,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Run database recovery procedures on the repository at @a path,
+ * returning the database to a consistent state. Use @a pool for all
+ * allocation.
+ *
+ * Acquires an exclusive lock on the repository, recovers the
+ * database, and releases the lock. If an exclusive lock can't be
+ * acquired, returns error.
+ *
+ * If @a nonblocking is TRUE, an error of type EWOULDBLOCK is
+ * returned if the lock is not immediately available.
+ *
+ * If @a notify_func is not NULL, it will be called with @a
+ * notify_baton as argument before the recovery starts, but
+ * after the exclusive lock has been acquired.
+ *
+ * If @a cancel_func is not @c NULL, it is called periodically with
+ * @a cancel_baton as argument to see if the client wishes to cancel
+ * the recovery.
+ *
+ * @note On some platforms the exclusive lock does not exclude other
+ * threads in the same process so this function should only be called
+ * by a single threaded process, or by a multi-threaded process when
+ * no other threads are accessing the repository.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_repos_recover4(const char *path,
+ svn_boolean_t nonblocking,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void * cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_recover4(), but with @a start callback in place of
+ * the notify_func / baton.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_recover3(const char *path,
+ svn_boolean_t nonblocking,
+ svn_error_t *(*start_callback)(void *baton),
+ void *start_callback_baton,
+ svn_cancel_func_t cancel_func,
+ void * cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_recover3(), but without cancellation support.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_recover2(const char *path,
+ svn_boolean_t nonblocking,
+ svn_error_t *(*start_callback)(void *baton),
+ void *start_callback_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_recover2(), but with nonblocking set to FALSE, and
+ * with no callbacks provided.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_recover(const char *path,
+ apr_pool_t *pool);
+
+/**
+ * Callback for svn_repos_freeze.
+ *
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_repos_freeze_func_t)(void *baton, apr_pool_t *pool);
+
+/**
+ * Take an exclusive lock on each of the repositories in @a paths to
+ * prevent commits and then while holding all the locks invoke @a
+ * freeze_func passing @a freeze_baton. Each repository may be readable by
+ * Subversion while frozen, or may be unreadable, depending on which
+ * FS backend the repository uses. Repositories are locked in the
+ * order in which they are specified in the array.
+ *
+ * @note On some platforms the exclusive lock does not exclude other
+ * threads in the same process so this function should only be called
+ * by a single threaded process, or by a multi-threaded process when
+ * no other threads are accessing the repositories.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_freeze(apr_array_header_t *paths,
+ svn_repos_freeze_func_t freeze_func,
+ void *freeze_baton,
+ apr_pool_t *pool);
+
+/** This function is a wrapper around svn_fs_berkeley_logfiles(),
+ * returning log file paths relative to the root of the repository.
+ *
+ * @copydoc svn_fs_berkeley_logfiles()
+ */
+svn_error_t *
+svn_repos_db_logfiles(apr_array_header_t **logfiles,
+ const char *path,
+ svn_boolean_t only_unused,
+ apr_pool_t *pool);
+
+
+
+/* Repository Paths */
+
+/** Return the top-level repository path allocated in @a pool. */
+const char *
+svn_repos_path(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return the path to @a repos's filesystem directory, allocated in
+ * @a pool.
+ */
+const char *
+svn_repos_db_env(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return path to @a repos's config directory, allocated in @a pool. */
+const char *
+svn_repos_conf_dir(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return path to @a repos's svnserve.conf, allocated in @a pool. */
+const char *
+svn_repos_svnserve_conf(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return path to @a repos's lock directory, allocated in @a pool. */
+const char *
+svn_repos_lock_dir(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return path to @a repos's db lockfile, allocated in @a pool. */
+const char *
+svn_repos_db_lockfile(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return path to @a repos's db logs lockfile, allocated in @a pool. */
+const char *
+svn_repos_db_logs_lockfile(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return the path to @a repos's hook directory, allocated in @a pool. */
+const char *
+svn_repos_hook_dir(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return the path to @a repos's start-commit hook, allocated in @a pool. */
+const char *
+svn_repos_start_commit_hook(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return the path to @a repos's pre-commit hook, allocated in @a pool. */
+const char *
+svn_repos_pre_commit_hook(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return the path to @a repos's post-commit hook, allocated in @a pool. */
+const char *
+svn_repos_post_commit_hook(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return the path to @a repos's pre-revprop-change hook, allocated in
+ * @a pool.
+ */
+const char *
+svn_repos_pre_revprop_change_hook(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return the path to @a repos's post-revprop-change hook, allocated in
+ * @a pool.
+ */
+const char *
+svn_repos_post_revprop_change_hook(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+
+/** @defgroup svn_repos_lock_hooks Paths to lock hooks
+ * @{
+ * @since New in 1.2. */
+
+/** Return the path to @a repos's pre-lock hook, allocated in @a pool. */
+const char *
+svn_repos_pre_lock_hook(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return the path to @a repos's post-lock hook, allocated in @a pool. */
+const char *
+svn_repos_post_lock_hook(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return the path to @a repos's pre-unlock hook, allocated in @a pool. */
+const char *
+svn_repos_pre_unlock_hook(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Return the path to @a repos's post-unlock hook, allocated in @a pool. */
+const char *
+svn_repos_post_unlock_hook(svn_repos_t *repos,
+ apr_pool_t *pool);
+
+/** Specify that Subversion should consult the configuration file
+ * located at @a hooks_env_path to determine how to setup the
+ * environment for hook scripts invoked for the repository @a repos.
+ * As a special case, if @a hooks_env_path is @c NULL, look for the
+ * file in its default location within the repository disk structure.
+ * If @a hooks_env_path is not absolute, it specifies a path relative
+ * to the parent of the file's default location.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * If this function is not called, or if the specified configuration
+ * file does not define any environment variables, hooks will run in
+ * an empty environment.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_hooks_setenv(svn_repos_t *repos,
+ const char *hooks_env_path,
+ apr_pool_t *scratch_pool);
+
+/** @} */
+
+/* ---------------------------------------------------------------*/
+
+/* Reporting the state of a working copy, for updates. */
+
+
+/**
+ * Construct and return a @a report_baton that will be passed to the
+ * other functions in this section to describe the state of a pre-existing
+ * tree (typically, a working copy). When the report is finished,
+ * @a editor/@a edit_baton will be driven in such a way as to transform the
+ * existing tree to @a revnum and, if @a tgt_path is non-NULL, switch the
+ * reported hierarchy to @a tgt_path.
+ *
+ * @a fs_base is the absolute path of the node in the filesystem at which
+ * the comparison should be rooted. @a target is a single path component,
+ * used to limit the scope of the report to a single entry of @a fs_base,
+ * or "" if all of @a fs_base itself is the main subject of the report.
+ *
+ * @a tgt_path and @a revnum is the fs path/revision pair that is the
+ * "target" of the delta. @a tgt_path should be provided only when
+ * the source and target paths of the report differ. That is, @a tgt_path
+ * should *only* be specified when specifying that the resultant editor
+ * drive be one that transforms the reported hierarchy into a pristine tree
+ * of @a tgt_path at revision @a revnum. A @c NULL value for @a tgt_path
+ * will indicate that the editor should be driven in such a way as to
+ * transform the reported hierarchy to revision @a revnum, preserving the
+ * reported hierarchy.
+ *
+ * @a text_deltas instructs the driver of the @a editor to enable
+ * the generation of text deltas.
+ *
+ * @a ignore_ancestry instructs the driver to ignore node ancestry
+ * when determining how to transmit differences.
+ *
+ * @a send_copyfrom_args instructs the driver to send 'copyfrom'
+ * arguments to the editor's add_file() and add_directory() methods,
+ * whenever it deems feasible.
+ *
+ * Use @a authz_read_func and @a authz_read_baton (if not @c NULL) to
+ * avoid sending data through @a editor/@a edit_baton which is not
+ * authorized for transmission.
+ *
+ * @a zero_copy_limit controls the maximum size (in bytes) at which
+ * data blocks may be sent using the zero-copy code path. On that
+ * path, a number of in-memory copy operations have been eliminated to
+ * maximize throughput. However, until the whole block has been
+ * pushed to the network stack, other clients block, so be careful
+ * when using larger values here. Pass 0 for @a zero_copy_limit to
+ * disable this optimization altogether.
+ *
+ * @a note Never activate this optimization if @a editor might access
+ * any FSFS data structures (and, hence, caches). So, it is basically
+ * safe for networked editors only.
+ *
+ * All allocation for the context and collected state will occur in
+ * @a pool.
+ *
+ * @a depth is the requested depth of the editor drive.
+ *
+ * If @a depth is #svn_depth_unknown, the editor will affect only the
+ * paths reported by the individual calls to svn_repos_set_path3() and
+ * svn_repos_link_path3().
+ *
+ * For example, if the reported tree is the @c A subdir of the Greek Tree
+ * (see Subversion's test suite), at depth #svn_depth_empty, but the
+ * @c A/B subdir is reported at depth #svn_depth_infinity, then
+ * repository-side changes to @c A/mu, or underneath @c A/C and @c
+ * A/D, would not be reflected in the editor drive, but changes
+ * underneath @c A/B would be.
+ *
+ * Additionally, the editor driver will call @c add_directory and
+ * and @c add_file for directories with an appropriate depth. For
+ * example, a directory reported at #svn_depth_files will receive
+ * file (but not directory) additions. A directory at #svn_depth_empty
+ * will receive neither.
+ *
+ * If @a depth is #svn_depth_files, #svn_depth_immediates or
+ * #svn_depth_infinity and @a depth is greater than the reported depth
+ * of the working copy, then the editor driver will emit editor
+ * operations so as to upgrade the working copy to this depth.
+ *
+ * If @a depth is #svn_depth_empty, #svn_depth_files,
+ * #svn_depth_immediates and @a depth is lower
+ * than or equal to the depth of the working copy, then the editor
+ * operations will affect only paths at or above @a depth.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_begin_report3(void **report_baton,
+ svn_revnum_t revnum,
+ svn_repos_t *repos,
+ const char *fs_base,
+ const char *target,
+ const char *tgt_path,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_size_t zero_copy_limit,
+ apr_pool_t *pool);
+
+/**
+ * The same as svn_repos_begin_report3(), but with @a zero_copy_limit
+ * always passed as 0.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_begin_report2(void **report_baton,
+ svn_revnum_t revnum,
+ svn_repos_t *repos,
+ const char *fs_base,
+ const char *target,
+ const char *tgt_path,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+/**
+ * The same as svn_repos_begin_report2(), but taking a boolean
+ * @a recurse flag, and sending FALSE for @a send_copyfrom_args.
+ *
+ * If @a recurse is TRUE, the editor driver will drive the editor with
+ * a depth of #svn_depth_infinity; if FALSE, then with a depth of
+ * #svn_depth_files.
+ *
+ * @note @a username is ignored, and has been removed in a revised
+ * version of this API.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_begin_report(void **report_baton,
+ svn_revnum_t revnum,
+ const char *username,
+ svn_repos_t *repos,
+ const char *fs_base,
+ const char *target,
+ const char *tgt_path,
+ svn_boolean_t text_deltas,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Given a @a report_baton constructed by svn_repos_begin_report3(),
+ * record the presence of @a path, at @a revision with depth @a depth,
+ * in the current tree.
+ *
+ * @a path is relative to the anchor/target used in the creation of the
+ * @a report_baton.
+ *
+ * @a revision may be SVN_INVALID_REVNUM if (for example) @a path
+ * represents a locally-added path with no revision number, or @a
+ * depth is #svn_depth_exclude.
+ *
+ * @a path may not be underneath a path on which svn_repos_set_path3()
+ * was previously called with #svn_depth_exclude in this report.
+ *
+ * The first call of this in a given report usually passes an empty
+ * @a path; this is used to set up the correct root revision for the editor
+ * drive.
+ *
+ * A depth of #svn_depth_unknown is not allowed, and results in an
+ * error.
+ *
+ * If @a start_empty is TRUE and @a path is a directory, then require the
+ * caller to explicitly provide all the children of @a path - do not assume
+ * that the tree also contains all the children of @a path at @a revision.
+ * This is for 'low confidence' client reporting.
+ *
+ * If the caller has a lock token for @a path, then @a lock_token should
+ * be set to that token. Else, @a lock_token should be NULL.
+ *
+ * All temporary allocations are done in @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_set_path3(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_set_path3(), but with @a depth set to
+ * #svn_depth_infinity.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_set_path2(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_set_path2(), but with @a lock_token set to @c NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_set_path(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ apr_pool_t *pool);
+
+/**
+ * Given a @a report_baton constructed by svn_repos_begin_report3(),
+ * record the presence of @a path in the current tree, containing the contents
+ * of @a link_path at @a revision with depth @a depth.
+ *
+ * A depth of #svn_depth_unknown is not allowed, and results in an
+ * error.
+ *
+ * @a path may not be underneath a path on which svn_repos_set_path3()
+ * was previously called with #svn_depth_exclude in this report.
+ *
+ * Note that while @a path is relative to the anchor/target used in the
+ * creation of the @a report_baton, @a link_path is an absolute filesystem
+ * path!
+ *
+ * If @a start_empty is TRUE and @a path is a directory, then require the
+ * caller to explicitly provide all the children of @a path - do not assume
+ * that the tree also contains all the children of @a link_path at
+ * @a revision. This is for 'low confidence' client reporting.
+ *
+ * If the caller has a lock token for @a link_path, then @a lock_token
+ * should be set to that token. Else, @a lock_token should be NULL.
+ *
+ * All temporary allocations are done in @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_link_path3(void *report_baton,
+ const char *path,
+ const char *link_path,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_link_path3(), but with @a depth set to
+ * #svn_depth_infinity.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_link_path2(void *report_baton,
+ const char *path,
+ const char *link_path,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_link_path2(), but with @a lock_token set to @c NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_link_path(void *report_baton,
+ const char *path,
+ const char *link_path,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ apr_pool_t *pool);
+
+/** Given a @a report_baton constructed by svn_repos_begin_report3(),
+ * record the non-existence of @a path in the current tree.
+ *
+ * @a path may not be underneath a path on which svn_repos_set_path3()
+ * was previously called with #svn_depth_exclude in this report.
+ *
+ * (This allows the reporter's driver to describe missing pieces of a
+ * working copy, so that 'svn up' can recreate them.)
+ *
+ * All temporary allocations are done in @a pool.
+ */
+svn_error_t *
+svn_repos_delete_path(void *report_baton,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Given a @a report_baton constructed by svn_repos_begin_report3(),
+ * finish the report and drive the editor as specified when the report
+ * baton was constructed.
+ *
+ * If an error occurs during the driving of the editor, do NOT abort the
+ * edit; that responsibility belongs to the caller of this function, if
+ * it happens at all.
+ *
+ * After the call to this function, @a report_baton is no longer valid;
+ * it should not be passed to any other reporting functions, including
+ * svn_repos_abort_report(), even if this function returns an error.
+ */
+svn_error_t *
+svn_repos_finish_report(void *report_baton,
+ apr_pool_t *pool);
+
+
+/** Given a @a report_baton constructed by svn_repos_begin_report3(),
+ * abort the report. This function can be called anytime before
+ * svn_repos_finish_report() is called.
+ *
+ * After the call to this function, @a report_baton is no longer valid;
+ * it should not be passed to any other reporting functions.
+ */
+svn_error_t *
+svn_repos_abort_report(void *report_baton,
+ apr_pool_t *pool);
+
+
+/* ---------------------------------------------------------------*/
+
+/* The magical dir_delta update routines. */
+
+/** Use the provided @a editor and @a edit_baton to describe the changes
+ * necessary for making a given node (and its descendants, if it is a
+ * directory) under @a src_root look exactly like @a tgt_path under
+ * @a tgt_root. @a src_entry is the node to update. If @a src_entry
+ * is empty, then compute the difference between the entire tree
+ * anchored at @a src_parent_dir under @a src_root and @a tgt_path
+ * under @a tgt_root. Else, describe the changes needed to update
+ * only that entry in @a src_parent_dir. Typically, callers of this
+ * function will use a @a tgt_path that is the concatenation of @a
+ * src_parent_dir and @a src_entry.
+ *
+ * @a src_root and @a tgt_root can both be either revision or transaction
+ * roots. If @a tgt_root is a revision, @a editor's set_target_revision()
+ * will be called with the @a tgt_root's revision number, else it will
+ * not be called at all.
+ *
+ * If @a authz_read_func is non-NULL, invoke it before any call to
+ *
+ * @a editor->open_root
+ * @a editor->add_directory
+ * @a editor->open_directory
+ * @a editor->add_file
+ * @a editor->open_file
+ *
+ * passing @a tgt_root, the same path that would be passed to the
+ * editor function in question, and @a authz_read_baton. If the
+ * @a *allowed parameter comes back TRUE, then proceed with the planned
+ * editor call; else if FALSE, then invoke @a editor->absent_file or
+ * @a editor->absent_directory as appropriate, except if the planned
+ * editor call was open_root, throw SVN_ERR_AUTHZ_ROOT_UNREADABLE.
+ *
+ * If @a text_deltas is @c FALSE, send a single @c NULL txdelta window to
+ * the window handler returned by @a editor->apply_textdelta().
+ *
+ * If @a depth is #svn_depth_empty, invoke @a editor calls only on
+ * @a src_entry (or @a src_parent_dir, if @a src_entry is empty).
+ * If @a depth is #svn_depth_files, also invoke the editor on file
+ * children, if any; if #svn_depth_immediates, invoke it on
+ * immediate subdirectories as well as files; if #svn_depth_infinity,
+ * recurse fully.
+ *
+ * If @a entry_props is @c TRUE, accompany each opened/added entry with
+ * propchange editor calls that relay special "entry props" (this
+ * is typically used only for working copy updates).
+ *
+ * @a ignore_ancestry instructs the function to ignore node ancestry
+ * when determining how to transmit differences.
+ *
+ * Before completing successfully, this function calls @a editor's
+ * close_edit(), so the caller should expect its @a edit_baton to be
+ * invalid after its use with this function.
+ *
+ * Do any allocation necessary for the delta computation in @a pool.
+ * This function's maximum memory consumption is at most roughly
+ * proportional to the greatest depth of the tree under @a tgt_root, not
+ * the total size of the delta.
+ *
+ * ### svn_repos_dir_delta2 is mostly superseded by the reporter
+ * ### functionality (svn_repos_begin_report3 and friends).
+ * ### svn_repos_dir_delta2 does allow the roots to be transaction
+ * ### roots rather than just revision roots, and it has the
+ * ### entry_props flag. Almost all of Subversion's own code uses the
+ * ### reporter instead; there are some stray references to the
+ * ### svn_repos_dir_delta[2] in comments which should probably
+ * ### actually refer to the reporter.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_dir_delta2(svn_fs_root_t *src_root,
+ const char *src_parent_dir,
+ const char *src_entry,
+ svn_fs_root_t *tgt_root,
+ const char *tgt_path,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t entry_props,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_dir_delta2(), but if @a recurse is TRUE, pass
+ * #svn_depth_infinity for @a depth, and if @a recurse is FALSE,
+ * pass #svn_depth_files for @a depth.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_dir_delta(svn_fs_root_t *src_root,
+ const char *src_parent_dir,
+ const char *src_entry,
+ svn_fs_root_t *tgt_root,
+ const char *tgt_path,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_boolean_t text_deltas,
+ svn_boolean_t recurse,
+ svn_boolean_t entry_props,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool);
+
+
+/** Use the provided @a editor and @a edit_baton to describe the
+ * skeletal changes made in a particular filesystem @a root
+ * (revision or transaction).
+ *
+ * Changes will be limited to those within @a base_dir, and if
+ * @a low_water_mark is set to something other than #SVN_INVALID_REVNUM
+ * it is assumed that the client has no knowledge of revisions prior to
+ * @a low_water_mark. Together, these two arguments define the portion of
+ * the tree that the client is assumed to have knowledge of, and thus any
+ * copies of data from outside that part of the tree will be sent in their
+ * entirety, not as simple copies or deltas against a previous version.
+ *
+ * The @a editor passed to this function should be aware of the fact
+ * that, if @a send_deltas is FALSE, calls to its change_dir_prop(),
+ * change_file_prop(), and apply_textdelta() functions will not
+ * contain meaningful data, and merely serve as indications that
+ * properties or textual contents were changed.
+ *
+ * If @a send_deltas is @c TRUE, the text and property deltas for changes
+ * will be sent, otherwise NULL text deltas and empty prop changes will be
+ * used.
+ *
+ * If @a authz_read_func is non-NULL, it will be used to determine if the
+ * user has read access to the data being accessed. Data that the user
+ * cannot access will be skipped.
+ *
+ * @note This editor driver passes SVN_INVALID_REVNUM for all
+ * revision parameters in the editor interface except the copyfrom
+ * parameter of the add_file() and add_directory() editor functions.
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_repos_replay2(svn_fs_root_t *root,
+ const char *base_dir,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_replay2(), but with @a base_dir set to @c "",
+ * @a low_water_mark set to #SVN_INVALID_REVNUM, @a send_deltas
+ * set to @c FALSE, and @a authz_read_func and @a authz_read_baton
+ * set to @c NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_replay(svn_fs_root_t *root,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool);
+
+/* ---------------------------------------------------------------*/
+
+/* Making commits. */
+
+/**
+ * Return an @a editor and @a edit_baton to commit changes to the
+ * filesystem of @a repos, beginning at location 'rev:@a base_path',
+ * where "rev" is the argument given to open_root().
+ *
+ * @a repos is a previously opened repository. @a repos_url is the
+ * decoded URL to the base of the repository, and is used to check
+ * copyfrom paths. copyfrom paths passed to the editor must be full,
+ * URI-encoded, URLs. @a txn is a filesystem transaction object to use
+ * during the commit, or @c NULL to indicate that this function should
+ * create (and fully manage) a new transaction.
+ *
+ * Store the contents of @a revprop_table, a hash mapping <tt>const
+ * char *</tt> property names to #svn_string_t values, as properties
+ * of the commit transaction, including author and log message if
+ * present.
+ *
+ * @note #SVN_PROP_REVISION_DATE may be present in @a revprop_table, but
+ * it will be overwritten when the transaction is committed.
+ *
+ * Iff @a authz_callback is provided, check read/write authorizations
+ * on paths accessed by editor operations. An operation which fails
+ * due to authz will return SVN_ERR_AUTHZ_UNREADABLE or
+ * SVN_ERR_AUTHZ_UNWRITABLE.
+ *
+ * Calling @a (*editor)->close_edit completes the commit.
+ *
+ * If @a commit_callback is non-NULL, then before @c close_edit returns (but
+ * after the commit has succeeded) @c close_edit will invoke
+ * @a commit_callback with a filled-in #svn_commit_info_t *, @a commit_baton,
+ * and @a pool or some subpool thereof as arguments. If @a commit_callback
+ * returns an error, that error will be returned from @c close_edit,
+ * otherwise if there was a post-commit hook failure, then that error
+ * will be returned with code SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED.
+ * (Note that prior to Subversion 1.6, @a commit_callback cannot be NULL; if
+ * you don't need a callback, pass a dummy function.)
+ *
+ * Calling @a (*editor)->abort_edit aborts the commit, and will also
+ * abort the commit transaction unless @a txn was supplied (not @c
+ * NULL). Callers who supply their own transactions are responsible
+ * for cleaning them up (either by committing them, or aborting them).
+ *
+ * @since New in 1.5.
+ *
+ * @note Yes, @a repos_url is a <em>decoded</em> URL. We realize
+ * that's sorta wonky. Sorry about that.
+ */
+svn_error_t *
+svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url,
+ const char *base_path,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_repos_authz_callback_t authz_callback,
+ void *authz_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_get_commit_editor5(), but with @a revprop_table
+ * set to a hash containing @a user and @a log_msg as the
+ * #SVN_PROP_REVISION_AUTHOR and #SVN_PROP_REVISION_LOG properties,
+ * respectively. @a user and @a log_msg may both be @c NULL.
+ *
+ * @since New in 1.4.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_get_commit_editor4(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url,
+ const char *base_path,
+ const char *user,
+ const char *log_msg,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_repos_authz_callback_t authz_callback,
+ void *authz_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_get_commit_editor4(), but
+ * uses the svn_commit_callback_t type.
+ *
+ * @since New in 1.3.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_get_commit_editor3(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url,
+ const char *base_path,
+ const char *user,
+ const char *log_msg,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ svn_repos_authz_callback_t authz_callback,
+ void *authz_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_get_commit_editor3(), but with @a
+ * authz_callback and @a authz_baton set to @c NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_get_commit_editor2(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url,
+ const char *base_path,
+ const char *user,
+ const char *log_msg,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_repos_get_commit_editor2(), but with @a txn always
+ * set to @c NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_get_commit_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ const char *repos_url,
+ const char *base_path,
+ const char *user,
+ const char *log_msg,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ apr_pool_t *pool);
+
+/* ---------------------------------------------------------------*/
+
+/* Finding particular revisions. */
+
+/** Set @a *revision to the revision number in @a repos's filesystem that was
+ * youngest at time @a tm.
+ */
+svn_error_t *
+svn_repos_dated_revision(svn_revnum_t *revision,
+ svn_repos_t *repos,
+ apr_time_t tm,
+ apr_pool_t *pool);
+
+
+/** Given a @a root/@a path within some filesystem, return three pieces of
+ * information allocated in @a pool:
+ *
+ * - set @a *committed_rev to the revision in which the object was
+ * last modified. (In fs parlance, this is the revision in which
+ * the particular node-rev-id was 'created'.)
+ *
+ * - set @a *committed_date to the date of said revision, or @c NULL
+ * if not available.
+ *
+ * - set @a *last_author to the author of said revision, or @c NULL
+ * if not available.
+ */
+svn_error_t *
+svn_repos_get_committed_info(svn_revnum_t *committed_rev,
+ const char **committed_date,
+ const char **last_author,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/**
+ * Set @a *dirent to an #svn_dirent_t associated with @a path in @a
+ * root. If @a path does not exist in @a root, set @a *dirent to
+ * NULL. Use @a pool for memory allocation.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_repos_stat(svn_dirent_t **dirent,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/**
+ * Given @a path which exists at revision @a start in @a fs, set
+ * @a *deleted to the revision @a path was first deleted, within the
+ * inclusive revision range bounded by @a start and @a end. If @a path
+ * does not exist at revision @a start or was not deleted within the
+ * specified range, then set @a *deleted to SVN_INVALID_REVNUM.
+ * Use @a pool for memory allocation.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_deleted_rev(svn_fs_t *fs,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_revnum_t *deleted,
+ apr_pool_t *pool);
+
+
+/** Callback type for use with svn_repos_history(). @a path and @a
+ * revision represent interesting history locations in the lifetime
+ * of the path passed to svn_repos_history(). @a baton is the same
+ * baton given to svn_repos_history(). @a pool is provided for the
+ * convenience of the implementor, who should not expect it to live
+ * longer than a single callback call.
+ *
+ * Signal to callback driver to stop processing/invoking this callback
+ * by returning the #SVN_ERR_CEASE_INVOCATION error code.
+ *
+ * @note SVN_ERR_CEASE_INVOCATION is new in 1.5.
+ */
+typedef svn_error_t *(*svn_repos_history_func_t)(void *baton,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *pool);
+
+/**
+ * Call @a history_func (with @a history_baton) for each interesting
+ * history location in the lifetime of @a path in @a fs, from the
+ * youngest of @a end and @a start to the oldest. Stop processing if
+ * @a history_func returns #SVN_ERR_CEASE_INVOCATION. Only cross
+ * filesystem copy history if @a cross_copies is @c TRUE. And do all
+ * of this in @a pool.
+ *
+ * If @a authz_read_func is non-NULL, then use it (and @a
+ * authz_read_baton) to verify that @a path in @a end is readable; if
+ * not, return SVN_ERR_AUTHZ_UNREADABLE. Also verify the readability
+ * of every ancestral path/revision pair before pushing them at @a
+ * history_func. If a pair is deemed unreadable, then do not send
+ * them; instead, immediately stop traversing history and return
+ * SVN_NO_ERROR.
+ *
+ * @since New in 1.1.
+ *
+ * @note SVN_ERR_CEASE_INVOCATION is new in 1.5.
+ */
+svn_error_t *
+svn_repos_history2(svn_fs_t *fs,
+ const char *path,
+ svn_repos_history_func_t history_func,
+ void *history_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t cross_copies,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_history2(), but with @a authz_read_func
+ * and @a authz_read_baton always set to NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_history(svn_fs_t *fs,
+ const char *path,
+ svn_repos_history_func_t history_func,
+ void *history_baton,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t cross_copies,
+ apr_pool_t *pool);
+
+
+/**
+ * Set @a *locations to be a mapping of the revisions to the paths of
+ * the file @a fs_path present at the repository in revision
+ * @a peg_revision, where the revisions are taken out of the array
+ * @a location_revisions.
+ *
+ * @a location_revisions is an array of svn_revnum_t's and @a *locations
+ * maps 'svn_revnum_t *' to 'const char *'.
+ *
+ * If optional @a authz_read_func is non-NULL, then use it (and @a
+ * authz_read_baton) to verify that the peg-object is readable. If not,
+ * return SVN_ERR_AUTHZ_UNREADABLE. Also use the @a authz_read_func
+ * to check that every path returned in the hash is readable. If an
+ * unreadable path is encountered, stop tracing and return
+ * SVN_NO_ERROR.
+ *
+ * @a pool is used for all allocations.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_repos_trace_node_locations(svn_fs_t *fs,
+ apr_hash_t **locations,
+ const char *fs_path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Call @a receiver and @a receiver_baton to report successive
+ * location segments in revisions between @a start_rev and @a end_rev
+ * (inclusive) for the line of history identified by the peg-object @a
+ * path in @a peg_revision (and in @a repos).
+ *
+ * @a end_rev may be #SVN_INVALID_REVNUM to indicate that you want
+ * to trace the history of the object to its origin.
+ *
+ * @a start_rev may be #SVN_INVALID_REVNUM to indicate "the HEAD
+ * revision". Otherwise, @a start_rev must be younger than @a end_rev
+ * (unless @a end_rev is #SVN_INVALID_REVNUM).
+ *
+ * @a peg_revision may be #SVN_INVALID_REVNUM to indicate "the HEAD
+ * revision", and must evaluate to be at least as young as @a start_rev.
+ *
+ * If optional @a authz_read_func is not @c NULL, then use it (and @a
+ * authz_read_baton) to verify that the peg-object is readable. If
+ * not, return #SVN_ERR_AUTHZ_UNREADABLE. Also use the @a
+ * authz_read_func to check that every path reported in a location
+ * segment is readable. If an unreadable path is encountered, report
+ * a final (possibly truncated) location segment (if any), stop
+ * tracing history, and return #SVN_NO_ERROR.
+ *
+ * @a pool is used for all allocations.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_node_location_segments(svn_repos_t *repos,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+
+/* ### other queries we can do someday --
+
+ * fetch the last revision created by <user>
+ (once usernames become revision properties!)
+ * fetch the last revision where <path> was modified
+
+*/
+
+
+
+/* ---------------------------------------------------------------*/
+
+/* Retrieving log messages. */
+
+
+/**
+ * Invoke @a receiver with @a receiver_baton on each log message from
+ * @a start to @a end in @a repos's filesystem. @a start may be greater
+ * or less than @a end; this just controls whether the log messages are
+ * processed in descending or ascending revision number order.
+ *
+ * If @a start or @a end is #SVN_INVALID_REVNUM, it defaults to youngest.
+ *
+ * If @a paths is non-NULL and has one or more elements, then only show
+ * revisions in which at least one of @a paths was changed (i.e., if
+ * file, text or props changed; if dir, props or entries changed or any node
+ * changed below it). Each path is a <tt>const char *</tt> representing
+ * an absolute path in the repository. If @a paths is NULL or empty,
+ * show all revisions regardless of what paths were changed in those
+ * revisions.
+ *
+ * If @a limit is non-zero then only invoke @a receiver on the first
+ * @a limit logs.
+ *
+ * If @a discover_changed_paths, then each call to @a receiver passes a
+ * hash mapping paths committed in that revision to information about them
+ * as the receiver's @a changed_paths argument.
+ * Otherwise, each call to @a receiver passes NULL for @a changed_paths.
+ *
+ * If @a strict_node_history is set, copy history (if any exists) will
+ * not be traversed while harvesting revision logs for each path.
+ *
+ * If @a include_merged_revisions is set, log information for revisions
+ * which have been merged to @a paths will also be returned, unless these
+ * revisions are already part of @a start to @a end in @a repos's
+ * filesystem, as limited by @a paths. In the latter case those revisions
+ * are skipped and @a receiver is not invoked.
+ *
+ * If @a revprops is NULL, retrieve all revision properties; else, retrieve
+ * only the revision properties named by the (const char *) array elements
+ * (i.e. retrieve none if the array is empty).
+ *
+ * If any invocation of @a receiver returns error, return that error
+ * immediately and without wrapping it.
+ *
+ * If @a start or @a end is a non-existent revision, return the error
+ * #SVN_ERR_FS_NO_SUCH_REVISION, without ever invoking @a receiver.
+ *
+ * If optional @a authz_read_func is non-NULL, then use this function
+ * (along with optional @a authz_read_baton) to check the readability
+ * of each changed-path in each revision about to be "pushed" at
+ * @a receiver. If a revision has some changed-paths readable and
+ * others unreadable, unreadable paths are omitted from the
+ * changed_paths field and only svn:author and svn:date will be
+ * available in the revprops field. If a revision has no
+ * changed-paths readable at all, then all paths are omitted and no
+ * revprops are available.
+ *
+ * See also the documentation for #svn_log_entry_receiver_t.
+ *
+ * Use @a pool for temporary allocations.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_get_logs4(svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_repos_get_logs4(), but with @a receiver being
+ * #svn_log_message_receiver_t instead of #svn_log_entry_receiver_t.
+ * Also, @a include_merged_revisions is set to @c FALSE and @a revprops is
+ * svn:author, svn:date, and svn:log. If @a paths is empty, nothing
+ * is returned.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_get_logs3(svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Same as svn_repos_get_logs3(), but with @a limit always set to 0.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_get_logs2(svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_repos_get_logs2(), but with @a authz_read_func and
+ * @a authz_read_baton always set to NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_get_logs(svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+
+
+/* ---------------------------------------------------------------*/
+
+/* Retrieving mergeinfo. */
+
+/**
+ * Fetch the mergeinfo for @a paths at @a revision in @a repos, and
+ * set @a *catalog to a catalog of this mergeinfo. @a *catalog will
+ * never be @c NULL but may be empty.
+ *
+ * The paths in @a paths, and the keys of @a catalog, start with '/'.
+ *
+ * @a inherit indicates whether explicit, explicit or inherited, or
+ * only inherited mergeinfo for @a paths is fetched.
+ *
+ * If @a revision is #SVN_INVALID_REVNUM, it defaults to youngest.
+ *
+ * If @a include_descendants is TRUE, then additionally return the
+ * mergeinfo for any descendant of any element of @a paths which has
+ * the #SVN_PROP_MERGEINFO property explicitly set on it. (Note
+ * that inheritance is only taken into account for the elements in @a
+ * paths; descendants of the elements in @a paths which get their
+ * mergeinfo via inheritance are not included in @a *catalog.)
+ *
+ * If optional @a authz_read_func is non-NULL, then use this function
+ * (along with optional @a authz_read_baton) to check the readability
+ * of each path which mergeinfo was requested for (from @a paths).
+ * Silently omit unreadable paths from the request for mergeinfo.
+ *
+ * Use @a pool for all allocations.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
+ svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+
+/* ---------------------------------------------------------------*/
+
+/* Retrieving multiple revisions of a file. */
+
+/**
+ * Retrieve a subset of the interesting revisions of a file @a path in
+ * @a repos as seen in revision @a end. Invoke @a handler with
+ * @a handler_baton as its first argument for each such revision.
+ * @a pool is used for all allocations. See svn_fs_history_prev() for
+ * a discussion of interesting revisions.
+ *
+ * If optional @a authz_read_func is non-NULL, then use this function
+ * (along with optional @a authz_read_baton) to check the readability
+ * of the rev-path in each interesting revision encountered.
+ *
+ * Revision discovery happens from @a end to @a start, and if an
+ * unreadable revision is encountered before @a start is reached, then
+ * revision discovery stops and only the revisions from @a end to the
+ * oldest readable revision are returned (So it will appear that @a
+ * path was added without history in the latter revision).
+ *
+ * If there is an interesting revision of the file that is less than or
+ * equal to start, the iteration will start at that revision. Else, the
+ * iteration will start at the first revision of the file in the repository,
+ * which has to be less than or equal to end. Note that if the function
+ * succeeds, @a handler will have been called at least once.
+ *
+ * In a series of calls, the file contents for the first interesting revision
+ * will be provided as a text delta against the empty file. In the following
+ * calls, the delta will be against the contents for the previous call.
+ *
+ * If @a include_merged_revisions is TRUE, revisions which a included as a
+ * result of a merge between @a start and @a end will be included.
+ *
+ * Since Subversion 1.8 this function has been enabled to support reversion
+ * the revision range for @a include_merged_revision @c FALSE reporting by
+ * switching @a start with @a end.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_get_file_revs2(svn_repos_t *repos,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_get_file_revs2(), with @a include_merged_revisions
+ * set to FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ * @since New in 1.1.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_get_file_revs(svn_repos_t *repos,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_repos_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+
+/* ---------------------------------------------------------------*/
+
+/**
+ * @defgroup svn_repos_hook_wrappers Hook-sensitive wrappers for libsvn_fs \
+ * routines.
+ * @{
+ */
+
+/** Like svn_fs_commit_txn(), but invoke the @a repos' pre- and
+ * post-commit hooks around the commit. Use @a pool for any necessary
+ * allocations.
+ *
+ * If the pre-commit hook fails, do not attempt to commit the
+ * transaction and throw the original error to the caller.
+ *
+ * A successful commit is indicated by a valid revision value in @a
+ * *new_rev, not if svn_fs_commit_txn() returns an error, which can
+ * occur during its post commit FS processing. If the transaction was
+ * not committed, then return the associated error and do not execute
+ * the post-commit hook.
+ *
+ * If the commit succeeds the post-commit hook is executed. If the
+ * post-commit hook returns an error, always wrap it with
+ * SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED; this allows the caller to
+ * find the post-commit hook error in the returned error chain. If
+ * both svn_fs_commit_txn() and the post-commit hook return errors,
+ * then svn_fs_commit_txn()'s error is the parent error and the
+ * SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped error is the child
+ * error.
+ *
+ * @a conflict_p, @a new_rev, and @a txn are as in svn_fs_commit_txn().
+ */
+svn_error_t *
+svn_repos_fs_commit_txn(const char **conflict_p,
+ svn_repos_t *repos,
+ svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+/** Like svn_fs_begin_txn(), but use @a revprop_table, a hash mapping
+ * <tt>const char *</tt> property names to #svn_string_t values, to
+ * set the properties on transaction @a *txn_p. @a repos is the
+ * repository object which contains the filesystem. @a rev, @a
+ * *txn_p, and @a pool are as in svn_fs_begin_txn().
+ *
+ * Before a txn is created, the repository's start-commit hooks are
+ * run; if any of them fail, no txn is created, @a *txn_p is unaffected,
+ * and #SVN_ERR_REPOS_HOOK_FAILURE is returned.
+ *
+ * @note @a revprop_table may contain an #SVN_PROP_REVISION_DATE property,
+ * which will be set on the transaction, but that will be overwritten
+ * when the transaction is committed.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ apr_hash_t *revprop_table,
+ apr_pool_t *pool);
+
+
+/**
+ * Same as svn_repos_fs_begin_txn_for_commit2(), but with @a revprop_table
+ * set to a hash containing @a author and @a log_msg as the
+ * #SVN_PROP_REVISION_AUTHOR and #SVN_PROP_REVISION_LOG properties,
+ * respectively. @a author and @a log_msg may both be @c NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *log_msg,
+ apr_pool_t *pool);
+
+
+/** Like svn_fs_begin_txn(), but use @a author to set the corresponding
+ * property on transaction @a *txn_p. @a repos is the repository object
+ * which contains the filesystem. @a rev, @a *txn_p, and @a pool are as in
+ * svn_fs_begin_txn().
+ *
+ * ### Someday: before a txn is created, some kind of read-hook could
+ * be called here.
+ *
+ * @note This function was never fully implemented, nor used. Ignore it.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_fs_begin_txn_for_update(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ apr_pool_t *pool);
+
+
+/** @} */
+
+/** @defgroup svn_repos_fs_locks Repository lock wrappers
+ * @{
+ */
+
+/** Like svn_fs_lock(), but invoke the @a repos's pre- and
+ * post-lock hooks before and after the locking action. Use @a pool
+ * for any necessary allocations.
+ *
+ * If the pre-lock hook or svn_fs_lock() fails, throw the original
+ * error to caller. If an error occurs when running the post-lock
+ * hook, return the original error wrapped with
+ * SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED. If the caller sees this
+ * error, it knows that the lock succeeded anyway.
+ *
+ * The pre-lock hook may cause a different token to be used for the
+ * lock, instead of @a token; see the pre-lock-hook documentation for
+ * more.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_repos_fs_lock(svn_lock_t **lock,
+ svn_repos_t *repos,
+ const char *path,
+ const char *token,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_revnum_t current_rev,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool);
+
+
+/** Like svn_fs_unlock(), but invoke the @a repos's pre- and
+ * post-unlock hooks before and after the unlocking action. Use @a
+ * pool for any necessary allocations.
+ *
+ * If the pre-unlock hook or svn_fs_unlock() fails, throw the original
+ * error to caller. If an error occurs when running the post-unlock
+ * hook, return the original error wrapped with
+ * SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED. If the caller sees this
+ * error, it knows that the unlock succeeded anyway.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_repos_fs_unlock(svn_repos_t *repos,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool);
+
+
+
+/** Look up all the locks in and under @a path in @a repos, setting @a
+ * *locks to a hash which maps <tt>const char *</tt> paths to the
+ * #svn_lock_t locks associated with those paths. Use @a
+ * authz_read_func and @a authz_read_baton to "screen" all returned
+ * locks. That is: do not return any locks on any paths that are
+ * unreadable in HEAD, just silently omit them.
+ *
+ * @a depth limits the returned locks to those associated with paths
+ * within the specified depth of @a path, and must be one of the
+ * following values: #svn_depth_empty, #svn_depth_files,
+ * #svn_depth_immediates, or #svn_depth_infinity.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_repos_fs_get_locks2(apr_hash_t **locks,
+ svn_repos_t *repos,
+ const char *path,
+ svn_depth_t depth,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_fs_get_locks2(), but with @a depth always
+ * passed as svn_depth_infinity.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_fs_get_locks(apr_hash_t **locks,
+ svn_repos_t *repos,
+ const char *path,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+/** @} */
+
+/**
+ * Like svn_fs_change_rev_prop2(), but validate the name and value of the
+ * property and invoke the @a repos's pre- and post-revprop-change hooks
+ * around the change as specified by @a use_pre_revprop_change_hook and
+ * @a use_post_revprop_change_hook (respectively).
+ *
+ * @a rev is the revision whose property to change, @a name is the
+ * name of the property, and @a new_value is the new value of the
+ * property. If @a old_value_p is not @c NULL, then @a *old_value_p
+ * is the expected current (preexisting) value of the property (or @c NULL
+ * for "unset"). @a author is the authenticated username of the person
+ * changing the property value, or NULL if not available.
+ *
+ * If @a authz_read_func is non-NULL, then use it (with @a
+ * authz_read_baton) to validate the changed-paths associated with @a
+ * rev. If the revision contains any unreadable changed paths, then
+ * return #SVN_ERR_AUTHZ_UNREADABLE.
+ *
+ * Validate @a name and @a new_value like the same way
+ * svn_repos_fs_change_node_prop() does.
+ *
+ * Use @a pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_repos_fs_change_rev_prop4(svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *new_value,
+ svn_boolean_t
+ use_pre_revprop_change_hook,
+ svn_boolean_t
+ use_post_revprop_change_hook,
+ svn_repos_authz_func_t
+ authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_fs_change_rev_prop4(), but with @a old_value_p always
+ * set to @c NULL. (In other words, it is similar to
+ * svn_fs_change_rev_prop().)
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_fs_change_rev_prop3(svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *new_value,
+ svn_boolean_t
+ use_pre_revprop_change_hook,
+ svn_boolean_t
+ use_post_revprop_change_hook,
+ svn_repos_authz_func_t
+ authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_fs_change_rev_prop3(), but with the @a
+ * use_pre_revprop_change_hook and @a use_post_revprop_change_hook
+ * always set to @c TRUE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_fs_change_rev_prop2(svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *new_value,
+ svn_repos_authz_func_t
+ authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_fs_change_rev_prop2(), but with the
+ * @a authz_read_func parameter always NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_fs_change_rev_prop(svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *new_value,
+ apr_pool_t *pool);
+
+
+
+/**
+ * Set @a *value_p to the value of the property named @a propname on
+ * revision @a rev in the filesystem opened in @a repos. If @a rev
+ * has no property by that name, set @a *value_p to zero. Allocate
+ * the result in @a pool.
+ *
+ * If @a authz_read_func is non-NULL, then use it (with @a
+ * authz_read_baton) to validate the changed-paths associated with @a
+ * rev. If the changed-paths are all unreadable, then set @a *value_p
+ * to zero unconditionally. If only some of the changed-paths are
+ * unreadable, then allow 'svn:author' and 'svn:date' propvalues to be
+ * fetched, but return 0 for any other property.
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_repos_fs_revision_prop(svn_string_t **value_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *propname,
+ svn_repos_authz_func_t
+ authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Set @a *table_p to the entire property list of revision @a rev in
+ * filesystem opened in @a repos, as a hash table allocated in @a
+ * pool. The table maps <tt>char *</tt> property names to
+ * #svn_string_t * values; the names and values are allocated in @a
+ * pool.
+ *
+ * If @a authz_read_func is non-NULL, then use it (with @a
+ * authz_read_baton) to validate the changed-paths associated with @a
+ * rev. If the changed-paths are all unreadable, then return an empty
+ * hash. If only some of the changed-paths are unreadable, then return
+ * an empty hash, except for 'svn:author' and 'svn:date' properties
+ * (assuming those properties exist).
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_repos_fs_revision_proplist(apr_hash_t **table_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ svn_repos_authz_func_t
+ authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+
+
+/* ---------------------------------------------------------------*/
+
+/* Prop-changing wrappers for libsvn_fs routines. */
+
+/* NOTE: svn_repos_fs_change_rev_prop() also exists, but is located
+ above with the hook-related functions. */
+
+
+/** Validating wrapper for svn_fs_change_node_prop() (which see for
+ * argument descriptions).
+ *
+ * If @a name's kind is not #svn_prop_regular_kind, return
+ * #SVN_ERR_REPOS_BAD_ARGS. If @a name is an "svn:" property, validate its
+ * @a value and return SVN_ERR_BAD_PROPERTY_VALUE if it is invalid for the
+ * property.
+ *
+ * @note Currently, the only properties validated are the "svn:" properties
+ * #SVN_PROP_REVISION_LOG and #SVN_PROP_REVISION_DATE. This may change
+ * in future releases.
+ */
+svn_error_t *
+svn_repos_fs_change_node_prop(svn_fs_root_t *root,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/** Validating wrapper for svn_fs_change_txn_prop() (which see for
+ * argument descriptions). See svn_repos_fs_change_txn_props() for more
+ * information.
+ */
+svn_error_t *
+svn_repos_fs_change_txn_prop(svn_fs_txn_t *txn,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/** Validating wrapper for svn_fs_change_txn_props() (which see for
+ * argument descriptions). Validate properties and their values the
+ * same way svn_repos_fs_change_node_prop() does.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_fs_change_txn_props(svn_fs_txn_t *txn,
+ const apr_array_header_t *props,
+ apr_pool_t *pool);
+
+
+/* ---------------------------------------------------------------*/
+
+/**
+ * @defgroup svn_repos_inspection Data structures and editor things for \
+ * repository inspection.
+ * @{
+ *
+ * As it turns out, the svn_repos_replay2(), svn_repos_dir_delta2() and
+ * svn_repos_begin_report3() interfaces can be extremely useful for
+ * examining the repository, or more exactly, changes to the repository.
+ * These drivers allows for differences between two trees to be
+ * described using an editor.
+ *
+ * By using the editor obtained from svn_repos_node_editor() with one of
+ * the drivers mentioned above, the description of how to transform one
+ * tree into another can be used to build an in-memory linked-list tree,
+ * which each node representing a repository node that was changed.
+ */
+
+/** A node in the repository. */
+typedef struct svn_repos_node_t
+{
+ /** Node type (file, dir, etc.) */
+ svn_node_kind_t kind;
+
+ /** How this node entered the node tree: 'A'dd, 'D'elete, 'R'eplace */
+ char action;
+
+ /** Were there any textual mods? (files only) */
+ svn_boolean_t text_mod;
+
+ /** Where there any property mods? */
+ svn_boolean_t prop_mod;
+
+ /** The name of this node as it appears in its parent's entries list */
+ const char *name;
+
+ /** The filesystem revision where this was copied from (if any) */
+ svn_revnum_t copyfrom_rev;
+
+ /** The filesystem path where this was copied from (if any) */
+ const char *copyfrom_path;
+
+ /** Pointer to the next sibling of this node */
+ struct svn_repos_node_t *sibling;
+
+ /** Pointer to the first child of this node */
+ struct svn_repos_node_t *child;
+
+ /** Pointer to the parent of this node */
+ struct svn_repos_node_t *parent;
+
+} svn_repos_node_t;
+
+
+/** Set @a *editor and @a *edit_baton to an editor that, when driven by
+ * a driver such as svn_repos_replay2(), builds an <tt>svn_repos_node_t *</tt>
+ * tree representing the delta from @a base_root to @a root in @a
+ * repos's filesystem.
+ *
+ * The editor can also be driven by svn_repos_dir_delta2() or
+ * svn_repos_begin_report3(), but unless you have special needs,
+ * svn_repos_replay2() is preferred.
+ *
+ * Invoke svn_repos_node_from_baton() on @a edit_baton to obtain the root
+ * node afterwards.
+ *
+ * Note that the delta includes "bubbled-up" directories; that is,
+ * many of the directory nodes will have no prop_mods.
+ *
+ * Allocate the tree and its contents in @a node_pool; do all other
+ * allocation in @a pool.
+ */
+svn_error_t *
+svn_repos_node_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_root_t *base_root,
+ svn_fs_root_t *root,
+ apr_pool_t *node_pool,
+ apr_pool_t *pool);
+
+/** Return the root node of the linked-list tree generated by driving the
+ * editor (associated with @a edit_baton) created by svn_repos_node_editor().
+ * This is only really useful if used *after* the editor drive is completed.
+ */
+svn_repos_node_t *
+svn_repos_node_from_baton(void *edit_baton);
+
+/** @} */
+
+/* ---------------------------------------------------------------*/
+
+/**
+ * @defgroup svn_repos_dump_load Dumping and loading filesystem data
+ * @{
+ *
+ * The filesystem 'dump' format contains nothing but the abstract
+ * structure of the filesystem -- independent of any internal node-id
+ * schema or database back-end. All of the data in the dumpfile is
+ * acquired by public function calls into svn_fs.h. Similarly, the
+ * parser which reads the dumpfile is able to reconstruct the
+ * filesystem using only public svn_fs.h routines.
+ *
+ * Thus the dump/load feature's main purpose is for *migrating* data
+ * from one svn filesystem to another -- presumably two filesystems
+ * which have different internal implementations.
+ *
+ * If you simply want to backup your filesystem, you're probably
+ * better off using the built-in facilities of the DB backend (using
+ * Berkeley DB's hot-backup feature, for example.)
+ *
+ * For a description of the dumpfile format, see
+ * /trunk/notes/fs_dumprestore.txt.
+ */
+
+/* The RFC822-style headers in our dumpfile format. */
+#define SVN_REPOS_DUMPFILE_MAGIC_HEADER "SVN-fs-dump-format-version"
+#define SVN_REPOS_DUMPFILE_FORMAT_VERSION 3
+#define SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS 3
+#define SVN_REPOS_DUMPFILE_UUID "UUID"
+#define SVN_REPOS_DUMPFILE_CONTENT_LENGTH "Content-length"
+
+#define SVN_REPOS_DUMPFILE_REVISION_NUMBER "Revision-number"
+
+#define SVN_REPOS_DUMPFILE_NODE_PATH "Node-path"
+#define SVN_REPOS_DUMPFILE_NODE_KIND "Node-kind"
+#define SVN_REPOS_DUMPFILE_NODE_ACTION "Node-action"
+#define SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH "Node-copyfrom-path"
+#define SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV "Node-copyfrom-rev"
+/** @since New in 1.6. */
+#define SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5 "Text-copy-source-md5"
+/** @since New in 1.6. */
+#define SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1 "Text-copy-source-sha1"
+#define SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM \
+ SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5
+/** @since New in 1.6. */
+#define SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 "Text-content-md5"
+/** @since New in 1.6. */
+#define SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1 "Text-content-sha1"
+#define SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM \
+ SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
+
+#define SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH "Prop-content-length"
+#define SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH "Text-content-length"
+
+/** @since New in 1.1. */
+#define SVN_REPOS_DUMPFILE_PROP_DELTA "Prop-delta"
+/** @since New in 1.1. */
+#define SVN_REPOS_DUMPFILE_TEXT_DELTA "Text-delta"
+/** @since New in 1.6. */
+#define SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5 "Text-delta-base-md5"
+/** @since New in 1.6. */
+#define SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1 "Text-delta-base-sha1"
+/** @since New in 1.5. */
+#define SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM \
+ SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
+
+/**
+ * Verify the contents of the file system in @a repos.
+ *
+ * If @a feedback_stream is not @c NULL, write feedback to it (lines of
+ * the form "* Verified revision %ld\n").
+ *
+ * If @a start_rev is #SVN_INVALID_REVNUM, then start verifying at
+ * revision 0. If @a end_rev is #SVN_INVALID_REVNUM, then verify
+ * through the @c HEAD revision.
+ *
+ * For every verified revision call @a notify_func with @a rev set to
+ * the verified revision and @a warning_text @c NULL. For warnings call @a
+ * notify_func with @a warning_text set.
+ *
+ * If @a cancel_func is not @c NULL, call it periodically with @a
+ * cancel_baton as argument to see if the caller wishes to cancel the
+ * verification.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_repos_verify_fs2(svn_repos_t *repos,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_repos_verify_fs2(), but with a feedback_stream instead of
+ * handling feedback via the notify_func handler
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_verify_fs(svn_repos_t *repos,
+ svn_stream_t *feedback_stream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Dump the contents of the filesystem within already-open @a repos into
+ * writable @a dumpstream. Begin at revision @a start_rev, and dump every
+ * revision up through @a end_rev. Use @a pool for all allocation. If
+ * non-@c NULL, send feedback to @a feedback_stream. If @a dumpstream is
+ * @c NULL, this is effectively a primitive verify. It is not complete,
+ * however; svn_repos_verify_fs2() and svn_fs_verify().
+ *
+ * If @a start_rev is #SVN_INVALID_REVNUM, then start dumping at revision
+ * 0. If @a end_rev is #SVN_INVALID_REVNUM, then dump through the @c HEAD
+ * revision.
+ *
+ * If @a incremental is @c TRUE, the first revision dumped will be a diff
+ * against the previous revision (usually it looks like a full dump of
+ * the tree).
+ *
+ * If @a use_deltas is @c TRUE, output only node properties which have
+ * changed relative to the previous contents, and output text contents
+ * as svndiff data against the previous contents. Regardless of how
+ * this flag is set, the first revision of a non-incremental dump will
+ * be done with full plain text. A dump with @a use_deltas set cannot
+ * be loaded by Subversion 1.0.x.
+ *
+ * If @a notify_func is not @c NULL, then for every dumped revision call
+ * @a notify_func with @a rev set to the dumped revision and @a warning_text
+ * @c NULL. For warnings call @a notify_func with @a warning_text.
+ *
+ * If @a cancel_func is not @c NULL, it is called periodically with
+ * @a cancel_baton as argument to see if the client wishes to cancel
+ * the dump.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_repos_dump_fs3(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t incremental,
+ svn_boolean_t use_deltas,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_repos_dump_fs3(), but with a feedback_stream instead of
+ * handling feedback via the notify_func handler
+ *
+ * @since New in 1.1.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_dump_fs2(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_stream_t *feedback_stream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t incremental,
+ svn_boolean_t use_deltas,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_dump_fs2(), but with the @a use_deltas
+ * parameter always set to @c FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_dump_fs(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_stream_t *feedback_stream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Read and parse dumpfile-formatted @a dumpstream, reconstructing
+ * filesystem revisions in already-open @a repos, handling uuids in
+ * accordance with @a uuid_action. Use @a pool for all allocation.
+ *
+ * If the dumpstream contains copy history that is unavailable in the
+ * repository, an error will be thrown.
+ *
+ * The repository's UUID will be updated iff
+ * the dumpstream contains a UUID and
+ * @a uuid_action is not equal to #svn_repos_load_uuid_ignore and
+ * either the repository contains no revisions or
+ * @a uuid_action is equal to #svn_repos_load_uuid_force.
+ *
+ * If the dumpstream contains no UUID, then @a uuid_action is
+ * ignored and the repository UUID is not touched.
+ *
+ * @a start_rev and @a end_rev act as filters, the lower and upper
+ * (inclusive) range values of revisions in @a dumpstream which will
+ * be loaded. Either both of these values are #SVN_INVALID_REVNUM (in
+ * which case no revision-based filtering occurs at all), or both are
+ * valid revisions (where @a start_rev is older than or equivalent to
+ * @a end_rev).
+ *
+ * If @a parent_dir is not NULL, then the parser will reparent all the
+ * loaded nodes, from root to @a parent_dir. The directory @a parent_dir
+ * must be an existing directory in the repository.
+ *
+ * If @a use_pre_commit_hook is set, call the repository's pre-commit
+ * hook before committing each loaded revision.
+ *
+ * If @a use_post_commit_hook is set, call the repository's
+ * post-commit hook after committing each loaded revision.
+ *
+ * If @a validate_props is set, then validate Subversion revision and
+ * node properties (those in the svn: namespace) against established
+ * rules for those things.
+ *
+ * If non-NULL, use @a notify_func and @a notify_baton to send notification
+ * of events to the caller.
+ *
+ * If @a cancel_func is not @c NULL, it is called periodically with
+ * @a cancel_baton as argument to see if the client wishes to cancel
+ * the load.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_load_fs4(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_boolean_t use_pre_commit_hook,
+ svn_boolean_t use_post_commit_hook,
+ svn_boolean_t validate_props,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/** Similar to svn_repos_load_fs4(), but with @a start_rev and @a
+ * end_rev always passed as #SVN_INVALID_REVNUM.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_load_fs3(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_boolean_t use_pre_commit_hook,
+ svn_boolean_t use_post_commit_hook,
+ svn_boolean_t validate_props,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_load_fs3(), but with @a feedback_stream in
+ * place of the #svn_repos_notify_func_t and baton and with
+ * @a validate_props always FALSE.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_load_fs2(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_stream_t *feedback_stream,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_boolean_t use_pre_commit_hook,
+ svn_boolean_t use_post_commit_hook,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_load_fs2(), but with @a use_pre_commit_hook and
+ * @a use_post_commit_hook always @c FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_load_fs(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_stream_t *feedback_stream,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * A vtable that is driven by svn_repos_parse_dumpstream3().
+ *
+ * @since New in 1.8.
+ */
+typedef struct svn_repos_parse_fns3_t
+{
+ /** The parser has discovered a new "magic header" record within the
+ * parsing session represented by @a parse_baton. The dump-format
+ * version number is @a version.
+ */
+ svn_error_t *(*magic_header_record)(int version,
+ void *parse_baton,
+ apr_pool_t *pool);
+
+ /** The parser has discovered a new uuid record within the parsing
+ * session represented by @a parse_baton. The uuid's value is
+ * @a uuid, and it is allocated in @a pool.
+ */
+ svn_error_t *(*uuid_record)(const char *uuid,
+ void *parse_baton,
+ apr_pool_t *pool);
+
+ /** The parser has discovered a new revision record within the
+ * parsing session represented by @a parse_baton. All the headers are
+ * placed in @a headers (allocated in @a pool), which maps <tt>const
+ * char *</tt> header-name ==> <tt>const char *</tt> header-value.
+ * The @a revision_baton received back (also allocated in @a pool)
+ * represents the revision.
+ */
+ svn_error_t *(*new_revision_record)(void **revision_baton,
+ apr_hash_t *headers,
+ void *parse_baton,
+ apr_pool_t *pool);
+
+ /** The parser has discovered a new node record within the current
+ * revision represented by @a revision_baton. All the headers are
+ * placed in @a headers (as with @c new_revision_record), allocated in
+ * @a pool. The @a node_baton received back is allocated in @a pool
+ * and represents the node.
+ */
+ svn_error_t *(*new_node_record)(void **node_baton,
+ apr_hash_t *headers,
+ void *revision_baton,
+ apr_pool_t *pool);
+
+ /** For a given @a revision_baton, set a property @a name to @a value. */
+ svn_error_t *(*set_revision_property)(void *revision_baton,
+ const char *name,
+ const svn_string_t *value);
+
+ /** For a given @a node_baton, set a property @a name to @a value. */
+ svn_error_t *(*set_node_property)(void *node_baton,
+ const char *name,
+ const svn_string_t *value);
+
+ /** For a given @a node_baton, delete property @a name. */
+ svn_error_t *(*delete_node_property)(void *node_baton, const char *name);
+
+ /** For a given @a node_baton, remove all properties. */
+ svn_error_t *(*remove_node_props)(void *node_baton);
+
+ /** For a given @a node_baton, receive a writable @a stream capable of
+ * receiving the node's fulltext. After writing the fulltext, call
+ * the stream's close() function.
+ *
+ * If a @c NULL is returned instead of a stream, the vtable is
+ * indicating that no text is desired, and the parser will not
+ * attempt to send it.
+ */
+ svn_error_t *(*set_fulltext)(svn_stream_t **stream,
+ void *node_baton);
+
+ /** For a given @a node_baton, set @a handler and @a handler_baton
+ * to a window handler and baton capable of receiving a delta
+ * against the node's previous contents. A NULL window will be
+ * sent to the handler after all the windows are sent.
+ *
+ * If a @c NULL is returned instead of a handler, the vtable is
+ * indicating that no delta is desired, and the parser will not
+ * attempt to send it.
+ */
+ svn_error_t *(*apply_textdelta)(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ void *node_baton);
+
+ /** The parser has reached the end of the current node represented by
+ * @a node_baton, it can be freed.
+ */
+ svn_error_t *(*close_node)(void *node_baton);
+
+ /** The parser has reached the end of the current revision
+ * represented by @a revision_baton. In other words, there are no more
+ * changed nodes within the revision. The baton can be freed.
+ */
+ svn_error_t *(*close_revision)(void *revision_baton);
+
+} svn_repos_parse_fns3_t;
+
+
+/**
+ * Read and parse dumpfile-formatted @a stream, calling callbacks in
+ * @a parse_fns/@a parse_baton, and using @a pool for allocations.
+ *
+ * If @a deltas_are_text is @c TRUE, handle text-deltas with the @a
+ * set_fulltext callback. This is useful when manipulating a dump
+ * stream without loading it. Otherwise handle text-deltas with the
+ * @a apply_textdelta callback.
+ *
+ * If @a cancel_func is not @c NULL, it is called periodically with
+ * @a cancel_baton as argument to see if the client wishes to cancel
+ * the dump.
+ *
+ * This parser has built-in knowledge of the dumpfile format, but only
+ * in a limited sense:
+ *
+ * * it recognizes the "magic" format-version header.
+ *
+ * * it recognizes the UUID header.
+ *
+ * * it recognizes revision and node records by looking for either
+ * a REVISION_NUMBER or NODE_PATH headers.
+ *
+ * * it recognizes the CONTENT-LENGTH headers, so it knows if and
+ * how to suck up the content body.
+ *
+ * * it knows how to parse a content body into two parts: props
+ * and text, and pass the pieces to the vtable.
+ *
+ * This is enough knowledge to make it easy on vtable implementors,
+ * but still allow expansion of the format: most headers do not have
+ * to be handled explicitly.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_parse_dumpstream3(svn_stream_t *stream,
+ const svn_repos_parse_fns3_t *parse_fns,
+ void *parse_baton,
+ svn_boolean_t deltas_are_text,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Set @a *parser and @a *parse_baton to a vtable parser which commits new
+ * revisions to the fs in @a repos. The constructed parser will treat
+ * UUID records in a manner consistent with @a uuid_action. Use @a pool
+ * to operate on the fs.
+ *
+ * @a start_rev and @a end_rev act as filters, the lower and upper
+ * (inclusive) range values of revisions in @a dumpstream which will
+ * be loaded. Either both of these values are #SVN_INVALID_REVNUM (in
+ * which case no revision-based filtering occurs at all), or both are
+ * valid revisions (where @a start_rev is older than or equivalent to
+ * @a end_rev).
+ *
+ * If @a use_history is set, then the parser will require relative
+ * 'copyfrom' history to exist in the repository when it encounters
+ * nodes that are added-with-history.
+ *
+ * If @a validate_props is set, then validate Subversion revision and
+ * node properties (those in the svn: namespace) against established
+ * rules for those things.
+ *
+ * If @a parent_dir is not NULL, then the parser will reparent all the
+ * loaded nodes, from root to @a parent_dir. The directory @a parent_dir
+ * must be an existing directory in the repository.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **parser,
+ void **parse_baton,
+ svn_repos_t *repos,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t use_history,
+ svn_boolean_t validate_props,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * A vtable that is driven by svn_repos_parse_dumpstream2().
+ * Similar to #svn_repos_parse_fns3_t except that it lacks
+ * the delete_node_property and apply_textdelta callbacks.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+typedef struct svn_repos_parse_fns2_t
+{
+ /** Same as #svn_repos_parse_fns3_t.new_revision_record. */
+ svn_error_t *(*new_revision_record)(void **revision_baton,
+ apr_hash_t *headers,
+ void *parse_baton,
+ apr_pool_t *pool);
+ /** Same as #svn_repos_parse_fns3_t.uuid_record. */
+ svn_error_t *(*uuid_record)(const char *uuid,
+ void *parse_baton,
+ apr_pool_t *pool);
+ /** Same as #svn_repos_parse_fns3_t.new_node_record. */
+ svn_error_t *(*new_node_record)(void **node_baton,
+ apr_hash_t *headers,
+ void *revision_baton,
+ apr_pool_t *pool);
+ /** Same as #svn_repos_parse_fns3_t.set_revision_property. */
+ svn_error_t *(*set_revision_property)(void *revision_baton,
+ const char *name,
+ const svn_string_t *value);
+ /** Same as #svn_repos_parse_fns3_t.set_node_property. */
+ svn_error_t *(*set_node_property)(void *node_baton,
+ const char *name,
+ const svn_string_t *value);
+ /** Same as #svn_repos_parse_fns3_t.delete_node_property. */
+ svn_error_t *(*delete_node_property)(void *node_baton,
+ const char *name);
+ /** Same as #svn_repos_parse_fns3_t.remove_node_props. */
+ svn_error_t *(*remove_node_props)(void *node_baton);
+ /** Same as #svn_repos_parse_fns3_t.set_fulltext. */
+ svn_error_t *(*set_fulltext)(svn_stream_t **stream,
+ void *node_baton);
+ /** Same as #svn_repos_parse_fns3_t.apply_textdelta. */
+ svn_error_t *(*apply_textdelta)(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ void *node_baton);
+ /** Same as #svn_repos_parse_fns3_t.close_node. */
+ svn_error_t *(*close_node)(void *node_baton);
+ /** Same as #svn_repos_parse_fns3_t.close_revision. */
+ svn_error_t *(*close_revision)(void *revision_baton);
+} svn_repos_parse_fns2_t;
+
+/** @deprecated Provided for backward compatibility with the 1.7 API. */
+typedef svn_repos_parse_fns2_t svn_repos_parser_fns2_t;
+
+
+/**
+ * A vtable that is driven by svn_repos_parse_dumpstream().
+ * Similar to #svn_repos_parse_fns2_t except that it lacks
+ * the delete_node_property and apply_textdelta callbacks.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+typedef struct svn_repos_parse_fns_t
+{
+ /** Same as #svn_repos_parse_fns2_t.new_revision_record. */
+ svn_error_t *(*new_revision_record)(void **revision_baton,
+ apr_hash_t *headers,
+ void *parse_baton,
+ apr_pool_t *pool);
+ /** Same as #svn_repos_parse_fns2_t.uuid_record. */
+ svn_error_t *(*uuid_record)(const char *uuid,
+ void *parse_baton,
+ apr_pool_t *pool);
+ /** Same as #svn_repos_parse_fns2_t.new_node_record. */
+ svn_error_t *(*new_node_record)(void **node_baton,
+ apr_hash_t *headers,
+ void *revision_baton,
+ apr_pool_t *pool);
+ /** Same as #svn_repos_parse_fns2_t.set_revision_property. */
+ svn_error_t *(*set_revision_property)(void *revision_baton,
+ const char *name,
+ const svn_string_t *value);
+ /** Same as #svn_repos_parse_fns2_t.set_node_property. */
+ svn_error_t *(*set_node_property)(void *node_baton,
+ const char *name,
+ const svn_string_t *value);
+ /** Same as #svn_repos_parse_fns2_t.remove_node_props. */
+ svn_error_t *(*remove_node_props)(void *node_baton);
+ /** Same as #svn_repos_parse_fns2_t.set_fulltext. */
+ svn_error_t *(*set_fulltext)(svn_stream_t **stream,
+ void *node_baton);
+ /** Same as #svn_repos_parse_fns2_t.close_node. */
+ svn_error_t *(*close_node)(void *node_baton);
+ /** Same as #svn_repos_parse_fns2_t.close_revision. */
+ svn_error_t *(*close_revision)(void *revision_baton);
+} svn_repos_parser_fns_t;
+
+
+/**
+ * Similar to svn_repos_parse_dumpstream3(), but uses the more limited
+ * #svn_repos_parser_fns2_t vtable type.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_parse_dumpstream2(svn_stream_t *stream,
+ const svn_repos_parser_fns2_t *parse_fns,
+ void *parse_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_parse_dumpstream2(), but uses the more limited
+ * #svn_repos_parser_fns_t vtable type.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_parse_dumpstream(svn_stream_t *stream,
+ const svn_repos_parser_fns_t *parse_fns,
+ void *parse_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_get_fs_build_parser4(), but with @a start_rev
+ * and @a end_rev always passed as #SVN_INVALID_REVNUM, and yielding
+ * the more limited svn_repos_parse_fns2_t.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_get_fs_build_parser3(const svn_repos_parse_fns2_t **parser,
+ void **parse_baton,
+ svn_repos_t *repos,
+ svn_boolean_t use_history,
+ svn_boolean_t validate_props,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_get_fs_build_parser3(), but with @a outstream
+ * in place if a #svn_repos_notify_func_t and baton and with
+ * @a validate_props always FALSE.
+ *
+ * @since New in 1.1.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_get_fs_build_parser2(const svn_repos_parse_fns2_t **parser,
+ void **parse_baton,
+ svn_repos_t *repos,
+ svn_boolean_t use_history,
+ enum svn_repos_load_uuid uuid_action,
+ svn_stream_t *outstream,
+ const char *parent_dir,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_get_fs_build_parser2(), but yields the more
+ * limited svn_repos_parser_fns_t vtable type.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_get_fs_build_parser(const svn_repos_parser_fns_t **parser,
+ void **parse_baton,
+ svn_repos_t *repos,
+ svn_boolean_t use_history,
+ enum svn_repos_load_uuid uuid_action,
+ svn_stream_t *outstream,
+ const char *parent_dir,
+ apr_pool_t *pool);
+
+
+/** @} */
+
+/** A data type which stores the authz information.
+ *
+ * @since New in 1.3.
+ */
+typedef struct svn_authz_t svn_authz_t;
+
+/**
+ * Read authz configuration data from @a path (a dirent, an absolute file url
+ * or a registry path) into @a *authz_p, allocated in @a pool.
+ *
+ * If @a groups_path (a dirent, an absolute file url, or a registry path) is
+ * set, use the global groups parsed from it.
+ *
+ * If @a path or @a groups_path is not a valid authz rule file, then return
+ * #SVN_ERR_AUTHZ_INVALID_CONFIG. The contents of @a *authz_p is then
+ * undefined. If @a must_exist is TRUE, a missing authz or groups file
+ * is also an error other than #SVN_ERR_AUTHZ_INVALID_CONFIG (exact error
+ * depends on the access type).
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_authz_read2(svn_authz_t **authz_p,
+ const char *path,
+ const char *groups_path,
+ svn_boolean_t must_exist,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_repos_authz_read2(), but with @a groups_path and @a
+ * repos_root always passed as @c NULL.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_repos_authz_read(svn_authz_t **authz_p,
+ const char *file,
+ svn_boolean_t must_exist,
+ apr_pool_t *pool);
+
+/**
+ * Read authz configuration data from @a stream into @a *authz_p,
+ * allocated in @a pool.
+ *
+ * If @a groups_stream is set, use the global groups parsed from it.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_authz_parse(svn_authz_t **authz_p,
+ svn_stream_t *stream,
+ svn_stream_t *groups_stream,
+ apr_pool_t *pool);
+
+/**
+ * Check whether @a user can access @a path in the repository @a
+ * repos_name with the @a required_access. @a authz lists the ACLs to
+ * check against. Set @a *access_granted to indicate if the requested
+ * access is granted.
+ *
+ * If @a path is NULL, then check whether @a user has the @a
+ * required_access anywhere in the repository. Set @a *access_granted
+ * to TRUE if at least one path is accessible with the @a
+ * required_access.
+ *
+ * For compatibility with 1.6, and earlier, @a repos_name can be NULL
+ * in which case it is equivalent to a @a repos_name of "".
+ *
+ * @note Presently, @a repos_name must byte-for-byte match the repos_name
+ * specified in the authz file; it is treated as an opaque string, and not
+ * as a dirent.
+ *
+ * @since New in 1.3.
+ */
+svn_error_t *
+svn_repos_authz_check_access(svn_authz_t *authz,
+ const char *repos_name,
+ const char *path,
+ const char *user,
+ svn_repos_authz_access_t required_access,
+ svn_boolean_t *access_granted,
+ apr_pool_t *pool);
+
+
+
+/** Revision Access Levels
+ *
+ * Like most version control systems, access to versioned objects in
+ * Subversion is determined on primarily path-based system. Users either
+ * do or don't have the ability to read a given path.
+ *
+ * However, unlike many version control systems where versioned objects
+ * maintain their own distinct version information (revision numbers,
+ * authors, log messages, change timestamps, etc.), Subversion binds
+ * multiple paths changed as part of a single commit operation into a
+ * set, calls the whole thing a revision, and hangs commit metadata
+ * (author, date, log message, etc.) off of that revision. So, commit
+ * metadata is shared across all the paths changed as part of a given
+ * commit operation.
+ *
+ * It is common (or, at least, we hope it is) for log messages to give
+ * detailed information about changes made in the commit to which the log
+ * message is attached. Such information might include a mention of all
+ * the files changed, what was changed in them, and so on. But this
+ * causes a problem when presenting information to readers who aren't
+ * authorized to read every path in the repository. Simply knowing that
+ * a given path exists may be a security leak, even if the user can't see
+ * the contents of the data located at that path.
+ *
+ * So Subversion does what it reasonably can to prevent the leak of this
+ * information, and does so via a staged revision access policy. A
+ * reader can be said to have one of three levels of access to a given
+ * revision's metadata, based solely on the reader's access rights to the
+ * paths changed or copied in that revision:
+ *
+ * 'full access' -- Granted when the reader has access to all paths
+ * changed or copied in the revision, or when no paths were
+ * changed in the revision at all, this access level permits
+ * full visibility of all revision property names and values,
+ * and the full changed-paths information.
+ *
+ * 'no access' -- Granted when the reader does not have access to any
+ * paths changed or copied in the revision, this access level
+ * denies the reader access to all revision properties and all
+ * changed-paths information.
+ *
+ * 'partial access' -- Granted when the reader has access to at least
+ * one, but not all, of the paths changed or copied in the revision,
+ * this access level permits visibility of the svn:date and
+ * svn:author revision properties and only the paths of the
+ * changed-paths information to which the reader has access.
+ *
+ */
+
+
+/** An enum defining levels of revision access.
+ *
+ * @since New in 1.5.
+ */
+typedef enum svn_repos_revision_access_level_t
+{
+ /** no access allowed to the revision properties and all changed-paths
+ * information. */
+ svn_repos_revision_access_none,
+ /** access granted to some (svn:date and svn:author) revision properties and
+ * changed-paths information on paths the read has access to. */
+ svn_repos_revision_access_partial,
+ /** access granted to all revision properites and changed-paths
+ * information. */
+ svn_repos_revision_access_full
+}
+svn_repos_revision_access_level_t;
+
+
+/**
+ * Set @a access to the access level granted for @a revision in @a
+ * repos, as determined by consulting the @a authz_read_func callback
+ * function and its associated @a authz_read_baton.
+ *
+ * @a authz_read_func may be @c NULL, in which case @a access will be
+ * set to #svn_repos_revision_access_full.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
+ svn_repos_t *repos,
+ svn_revnum_t revision,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *inherited_values to a depth-first ordered array of
+ * #svn_prop_inherited_item_t * structures (the path_or_url members of
+ * which are relative filesystem paths) representing the properties
+ * inherited by @a path in @a root. If no properties are inherited,
+ * then set @a *inherited_values to an empty array.
+ *
+ * if @a propname is NULL then retrieve all explicit and/or inherited
+ * properties. Otherwise retrieve only the properties named @a propname.
+ *
+ * If optional @a authz_read_func is non-NULL, then use this function
+ * (along with optional @a authz_read_baton) to check the readability
+ * of each parent path from which properties are inherited. Silently omit
+ * properties for unreadable parent paths.
+ *
+ * Allocate @a *inherited_props in @a result_pool. Use @a scratch_pool for
+ * temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props,
+ svn_fs_root_t *root,
+ const char *path,
+ const char *propname,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Capabilities **/
+
+/**
+ * Store in @a repos the client-reported capabilities @a capabilities,
+ * which must be allocated in memory at least as long-lived as @a repos.
+ *
+ * The elements of @a capabilities are 'const char *', a subset of
+ * the constants beginning with @c SVN_RA_CAPABILITY_.
+ * @a capabilities is not copied, so changing it later will affect
+ * what is remembered by @a repos.
+ *
+ * @note The capabilities are passed along to the start-commit hook;
+ * see that hook's template for details.
+ *
+ * @note As of Subversion 1.5, there are no error conditions defined,
+ * so this always returns SVN_NO_ERROR. In future releases it may
+ * return error, however, so callers should check.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_repos_remember_client_capabilities(svn_repos_t *repos,
+ const apr_array_header_t *capabilities);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_REPOS_H */
diff --git a/contrib/subversion/subversion/include/svn_sorts.h b/contrib/subversion/subversion/include/svn_sorts.h
new file mode 100644
index 0000000..b9e05e5
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_sorts.h
@@ -0,0 +1,223 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_sorts.h
+ * @brief all sorts of sorts.
+ */
+
+
+#ifndef SVN_SORTS_H
+#define SVN_SORTS_H
+
+#include <apr.h> /* for apr_ssize_t */
+#include <apr_pools.h> /* for apr_pool_t */
+#include <apr_tables.h> /* for apr_array_header_t */
+#include <apr_hash.h> /* for apr_hash_t */
+
+/* Define a MAX macro if we don't already have one */
+#ifndef MAX
+#define MAX(a, b) ((a) < (b) ? (b) : (a))
+#endif
+
+/* Define a MIN macro if we don't already have one */
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/** This structure is used to hold a key/value from a hash table.
+ * @note Private. For use by Subversion's own code only. See issue #1644.
+ */
+typedef struct svn_sort__item_t {
+ /** pointer to the key */
+ const void *key;
+
+ /** size of the key */
+ apr_ssize_t klen;
+
+ /** pointer to the value */
+ void *value;
+} svn_sort__item_t;
+
+
+/** Compare two @c svn_sort__item_t's, returning an integer greater than,
+ * equal to, or less than 0, according to whether the key of @a a is
+ * greater than, equal to, or less than the key of @a b as determined
+ * by comparing them with svn_path_compare_paths().
+ *
+ * The key strings must be NULL-terminated, even though klen does not
+ * include the terminator.
+ *
+ * This is useful for converting a hash into a sorted
+ * @c apr_array_header_t. For example, to convert hash @a hsh to a sorted
+ * array, do this:
+ *
+ * @code
+ apr_array_header_t *array;
+ array = svn_sort__hash(hsh, svn_sort_compare_items_as_paths, pool);
+ @endcode
+ *
+ * This function works like svn_sort_compare_items_lexically() except that it
+ * orders children in subdirectories directly after their parents. This allows
+ * using the given ordering for a depth first walk, but at a performance
+ * penalty. Code that doesn't need this special behavior for children, e.g. when
+ * sorting files at a single directory level should use
+ * svn_sort_compare_items_lexically() instead.
+ */
+int
+svn_sort_compare_items_as_paths(const svn_sort__item_t *a,
+ const svn_sort__item_t *b);
+
+
+/** Compare two @c svn_sort__item_t's, returning an integer greater than,
+ * equal to, or less than 0, according as @a a is greater than, equal to,
+ * or less than @a b according to a lexical key comparison. The keys are
+ * not required to be zero-terminated.
+ */
+int
+svn_sort_compare_items_lexically(const svn_sort__item_t *a,
+ const svn_sort__item_t *b);
+
+/** Compare two @c svn_revnum_t's, returning an integer greater than, equal
+ * to, or less than 0, according as @a b is greater than, equal to, or less
+ * than @a a. Note that this sorts newest revision to oldest (IOW, descending
+ * order).
+ *
+ * This function is compatible for use with qsort().
+ *
+ * This is useful for converting an array of revisions into a sorted
+ * @c apr_array_header_t. You are responsible for detecting, preventing or
+ * removing duplicates.
+ */
+int
+svn_sort_compare_revisions(const void *a,
+ const void *b);
+
+
+/**
+ * Compare two @c const char * paths, @a *a and @a *b, returning an
+ * integer greater than, equal to, or less than 0, using the same
+ * comparison rules as are used by svn_path_compare_paths().
+ *
+ * This function is compatible for use with qsort().
+ *
+ * @since New in 1.1.
+ */
+int
+svn_sort_compare_paths(const void *a,
+ const void *b);
+
+/**
+ * Compare two @c svn_merge_range_t *'s, @a *a and @a *b, returning an
+ * integer greater than, equal to, or less than 0 if the first range is
+ * greater than, equal to, or less than, the second range.
+ *
+ * Both @c svn_merge_range_t *'s must describe forward merge ranges.
+ *
+ * If @a *a and @a *b intersect then the range with the lower start revision
+ * is considered the lesser range. If the ranges' start revisions are
+ * equal then the range with the lower end revision is considered the
+ * lesser range.
+ *
+ * @since New in 1.5
+ */
+int
+svn_sort_compare_ranges(const void *a,
+ const void *b);
+
+/** Sort @a ht according to its keys, return an @c apr_array_header_t
+ * containing @c svn_sort__item_t structures holding those keys and values
+ * (i.e. for each @c svn_sort__item_t @a item in the returned array,
+ * @a item->key and @a item->size are the hash key, and @a item->value points to
+ * the hash value).
+ *
+ * Storage is shared with the original hash, not copied.
+ *
+ * @a comparison_func should take two @c svn_sort__item_t's and return an
+ * integer greater than, equal to, or less than 0, according as the first item
+ * is greater than, equal to, or less than the second.
+ *
+ * @note Private. For use by Subversion's own code only. See issue #1644.
+ *
+ * @note This function and the @c svn_sort__item_t should go over to APR.
+ */
+apr_array_header_t *
+svn_sort__hash(apr_hash_t *ht,
+ int (*comparison_func)(const svn_sort__item_t *,
+ const svn_sort__item_t *),
+ apr_pool_t *pool);
+
+/* Return the lowest index at which the element @a *key should be inserted into
+ * the array @a array, according to the ordering defined by @a compare_func.
+ * The array must already be sorted in the ordering defined by @a compare_func.
+ * @a compare_func is defined as for the C stdlib function bsearch().
+ *
+ * @note Private. For use by Subversion's own code only.
+ */
+int
+svn_sort__bsearch_lower_bound(const void *key,
+ const apr_array_header_t *array,
+ int (*compare_func)(const void *, const void *));
+
+/* Insert a shallow copy of @a *new_element into the array @a array at the index
+ * @a insert_index, growing the array and shuffling existing elements along to
+ * make room.
+ *
+ * @note Private. For use by Subversion's own code only.
+ */
+void
+svn_sort__array_insert(const void *new_element,
+ apr_array_header_t *array,
+ int insert_index);
+
+
+/* Remove @a elements_to_delete elements starting at @a delete_index from the
+ * array @a arr. If @a delete_index is not a valid element of @a arr,
+ * @a elements_to_delete is not greater than zero, or
+ * @a delete_index + @a elements_to_delete is greater than @a arr->nelts,
+ * then do nothing.
+ *
+ * @note Private. For use by Subversion's own code only.
+ */
+void
+svn_sort__array_delete(apr_array_header_t *arr,
+ int delete_index,
+ int elements_to_delete);
+
+/* Reverse the order of elements in @a array, in place.
+ *
+ * @note Private. For use by Subversion's own code only.
+ */
+void
+svn_sort__array_reverse(apr_array_header_t *array,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_SORTS_H */
diff --git a/contrib/subversion/subversion/include/svn_string.h b/contrib/subversion/subversion/include/svn_string.h
new file mode 100644
index 0000000..d8ce02b
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_string.h
@@ -0,0 +1,577 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_string.h
+ * @brief Counted-length strings for Subversion, plus some C string goodies.
+ *
+ * There are two string datatypes: @c svn_string_t and @c svn_stringbuf_t.
+ * The former is a simple pointer/length pair useful for passing around
+ * strings (or arbitrary bytes) with a counted length. @c svn_stringbuf_t is
+ * buffered to enable efficient appending of strings without an allocation
+ * and copy for each append operation.
+ *
+ * @c svn_string_t contains a <tt>const char *</tt> for its data, so it is
+ * most appropriate for constant data and for functions which expect constant,
+ * counted data. Functions should generally use <tt>const @c svn_string_t
+ * *</tt> as their parameter to indicate they are expecting a constant,
+ * counted string.
+ *
+ * @c svn_stringbuf_t uses a plain <tt>char *</tt> for its data, so it is
+ * most appropriate for modifiable data.
+ *
+ * <h3>Invariants</h3>
+ *
+ * 1. Null termination:
+ *
+ * Both structures maintain a significant invariant:
+ *
+ * <tt>s->data[s->len] == '\\0'</tt>
+ *
+ * The functions defined within this header file will maintain
+ * the invariant (which does imply that memory is
+ * allocated/defined as @c len+1 bytes). If code outside of the
+ * @c svn_string.h functions manually builds these structures,
+ * then they must enforce this invariant.
+ *
+ * Note that an @c svn_string(buf)_t may contain binary data,
+ * which means that strlen(s->data) does not have to equal @c
+ * s->len. The null terminator is provided to make it easier to
+ * pass @c s->data to C string interfaces.
+ *
+ *
+ * 2. Non-NULL input:
+ *
+ * All the functions assume their input data pointer is non-NULL,
+ * unless otherwise documented, and may seg fault if passed
+ * NULL. The input data may *contain* null bytes, of course, just
+ * the data pointer itself must not be NULL.
+ *
+ * <h3>Memory allocation</h3>
+ *
+ * All the functions make a deep copy of all input data, and never store
+ * a pointer to the original input data.
+ */
+
+
+#ifndef SVN_STRING_H
+#define SVN_STRING_H
+
+#include <apr.h> /* for apr_size_t */
+#include <apr_pools.h> /* for apr_pool_t */
+#include <apr_tables.h> /* for apr_array_header_t */
+
+#include "svn_types.h" /* for svn_boolean_t, svn_error_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * @defgroup svn_string String handling
+ * @{
+ */
+
+
+
+/** A simple counted string. */
+typedef struct svn_string_t
+{
+ const char *data; /**< pointer to the bytestring */
+ apr_size_t len; /**< length of bytestring */
+} svn_string_t;
+
+/** A buffered string, capable of appending without an allocation and copy
+ * for each append. */
+typedef struct svn_stringbuf_t
+{
+ /** a pool from which this string was originally allocated, and is not
+ * necessarily specific to this string. This is used only for allocating
+ * more memory from when the string needs to grow.
+ */
+ apr_pool_t *pool;
+
+ /** pointer to the bytestring */
+ char *data;
+
+ /** length of bytestring */
+ apr_size_t len;
+
+ /** total size of buffer allocated */
+ apr_size_t blocksize;
+} svn_stringbuf_t;
+
+
+/**
+ * @defgroup svn_string_svn_string_t svn_string_t functions
+ * @{
+ */
+
+/** Create a new string copied from the null-terminated C string @a cstring.
+ */
+svn_string_t *
+svn_string_create(const char *cstring, apr_pool_t *pool);
+
+/** Create a new, empty string.
+ *
+ * @since New in 1.8.
+ */
+svn_string_t *
+svn_string_create_empty(apr_pool_t *pool);
+
+/** Create a new string copied from a generic string of bytes, @a bytes, of
+ * length @a size bytes. @a bytes is NOT assumed to be null-terminated, but
+ * the new string will be.
+ */
+svn_string_t *
+svn_string_ncreate(const char *bytes, apr_size_t size, apr_pool_t *pool);
+
+/** Create a new string copied from the stringbuf @a strbuf.
+ */
+svn_string_t *
+svn_string_create_from_buf(const svn_stringbuf_t *strbuf, apr_pool_t *pool);
+
+/** Create a new string by printf-style formatting using @a fmt and the
+ * variable arguments, which are as appropriate for apr_psprintf().
+ */
+svn_string_t *
+svn_string_createf(apr_pool_t *pool, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+
+/** Create a new string by printf-style formatting using @c fmt and @a ap.
+ * This is the same as svn_string_createf() except for the different
+ * way of passing the variable arguments.
+ */
+svn_string_t *
+svn_string_createv(apr_pool_t *pool, const char *fmt, va_list ap)
+ __attribute__((format(printf, 2, 0)));
+
+/** Return TRUE if @a str is empty (has length zero). */
+svn_boolean_t
+svn_string_isempty(const svn_string_t *str);
+
+/** Return a duplicate of @a original_string. */
+svn_string_t *
+svn_string_dup(const svn_string_t *original_string, apr_pool_t *pool);
+
+/** Return @c TRUE iff @a str1 and @a str2 have identical length and data. */
+svn_boolean_t
+svn_string_compare(const svn_string_t *str1, const svn_string_t *str2);
+
+/** Return offset of first non-whitespace character in @a str, or return
+ * @a str->len if none.
+ */
+apr_size_t
+svn_string_first_non_whitespace(const svn_string_t *str);
+
+/** Return position of last occurrence of @a ch in @a str, or return
+ * @a str->len if no occurrence.
+ */
+apr_size_t
+svn_string_find_char_backward(const svn_string_t *str, char ch);
+
+/** @} */
+
+
+/**
+ * @defgroup svn_string_svn_stringbuf_t svn_stringbuf_t functions
+ * @{
+ */
+
+/** Create a new stringbuf copied from the null-terminated C string
+ * @a cstring.
+ */
+svn_stringbuf_t *
+svn_stringbuf_create(const char *cstring, apr_pool_t *pool);
+
+/** Create a new stringbuf copied from the generic string of bytes, @a bytes,
+ * of length @a size bytes. @a bytes is NOT assumed to be null-terminated,
+ * but the new stringbuf will be.
+ */
+svn_stringbuf_t *
+svn_stringbuf_ncreate(const char *bytes, apr_size_t size, apr_pool_t *pool);
+
+/** Create a new, empty stringbuf.
+ *
+ * @since New in 1.8.
+ */
+svn_stringbuf_t *
+svn_stringbuf_create_empty(apr_pool_t *pool);
+
+/** Create a new, empty stringbuf with at least @a minimum_size bytes of
+ * space available in the memory block.
+ *
+ * The allocated string buffer will be at least one byte larger than
+ * @a minimum_size to account for a final '\\0'.
+ *
+ * @since New in 1.6.
+ */
+svn_stringbuf_t *
+svn_stringbuf_create_ensure(apr_size_t minimum_size, apr_pool_t *pool);
+
+/** Create a new stringbuf copied from the string @a str.
+ */
+svn_stringbuf_t *
+svn_stringbuf_create_from_string(const svn_string_t *str, apr_pool_t *pool);
+
+/** Create a new stringbuf by printf-style formatting using @a fmt and the
+ * variable arguments, which are as appropriate for apr_psprintf().
+ */
+svn_stringbuf_t *
+svn_stringbuf_createf(apr_pool_t *pool, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+
+/** Create a new stringbuf by printf-style formatting using @c fmt and @a ap.
+ * This is the same as svn_stringbuf_createf() except for the different
+ * way of passing the variable arguments.
+ */
+svn_stringbuf_t *
+svn_stringbuf_createv(apr_pool_t *pool, const char *fmt, va_list ap)
+ __attribute__((format(printf, 2, 0)));
+
+/** Make sure that @a str has at least @a minimum_size
+ * bytes of space available in the memory block.
+ *
+ * The allocated string buffer will be at least one byte larger than
+ * @a minimum_size to account for a final '\\0'.
+ *
+ * @note: Before Subversion 1.8 this function did not ensure space for
+ * one byte more than @a minimum_size. If compatibility with pre-1.8
+ * behaviour is required callers must assume space for only
+ * @a minimum_size-1 data bytes plus a final '\\0'.
+ */
+void
+svn_stringbuf_ensure(svn_stringbuf_t *str, apr_size_t minimum_size);
+
+/** Set @a str to a copy of the null-terminated C string @a value. */
+void
+svn_stringbuf_set(svn_stringbuf_t *str, const char *value);
+
+/** Set @a str to empty (zero length). */
+void
+svn_stringbuf_setempty(svn_stringbuf_t *str);
+
+/** Return @c TRUE if @a str is empty (has length zero). */
+svn_boolean_t
+svn_stringbuf_isempty(const svn_stringbuf_t *str);
+
+/** Chop @a nbytes bytes off end of @a str, but not more than @a str->len. */
+void
+svn_stringbuf_chop(svn_stringbuf_t *str, apr_size_t nbytes);
+
+/** Fill @a str with character @a c. */
+void
+svn_stringbuf_fillchar(svn_stringbuf_t *str, unsigned char c);
+
+/** Append the single character @a byte onto @a targetstr.
+ *
+ * This is an optimized version of svn_stringbuf_appendbytes()
+ * that is much faster to call and execute. Gains vary with the ABI.
+ * The advantages extend beyond the actual call because the reduced
+ * register pressure allows for more optimization within the caller.
+ *
+ * reallocs if necessary. @a targetstr is affected, nothing else is.
+ * @since New in 1.7.
+ */
+void
+svn_stringbuf_appendbyte(svn_stringbuf_t *targetstr,
+ char byte);
+
+/** Append an array of bytes onto @a targetstr.
+ *
+ * reallocs if necessary. @a targetstr is affected, nothing else is.
+ */
+void
+svn_stringbuf_appendbytes(svn_stringbuf_t *targetstr,
+ const char *bytes,
+ apr_size_t count);
+
+/** Append the stringbuf @c appendstr onto @a targetstr.
+ *
+ * reallocs if necessary. @a targetstr is affected, nothing else is.
+ */
+void
+svn_stringbuf_appendstr(svn_stringbuf_t *targetstr,
+ const svn_stringbuf_t *appendstr);
+
+/** Append the C string @a cstr onto @a targetstr.
+ *
+ * reallocs if necessary. @a targetstr is affected, nothing else is.
+ */
+void
+svn_stringbuf_appendcstr(svn_stringbuf_t *targetstr,
+ const char *cstr);
+
+/** Read @a count bytes from @a bytes and insert them into @a str at
+ * position @a pos and following. The resulting string will be
+ * @c count+str->len bytes long. If @c pos is larger or equal to the
+ * number of bytes currently used in @a str, simply append @a bytes.
+ *
+ * Reallocs if necessary. @a str is affected, nothing else is.
+ *
+ * @note The inserted string may be a sub-range if @a str.
+ *
+ * @since New in 1.8.
+ */
+void
+svn_stringbuf_insert(svn_stringbuf_t *str,
+ apr_size_t pos,
+ const char *bytes,
+ apr_size_t count);
+
+/** Removes @a count bytes from @a str, starting at position @a pos.
+ * If that range exceeds the current string data, @a str gets truncated
+ * at @a pos. If the latter is larger or equal to @c str->pos, this will
+ * be a no-op. Otherwise, the resulting string will be @c str->len-count
+ * bytes long.
+ *
+ * @since New in 1.8.
+ */
+void
+svn_stringbuf_remove(svn_stringbuf_t *str,
+ apr_size_t pos,
+ apr_size_t count);
+
+/** Replace in @a str the substring which starts at @a pos and is @a
+ * old_count bytes long with a new substring @a bytes (which is @a
+ * new_count bytes long).
+ *
+ * This is faster but functionally equivalent to the following sequence:
+ * @code
+ svn_stringbuf_remove(str, pos, old_count);
+ svn_stringbuf_insert(str, pos, bytes, new_count);
+ * @endcode
+ *
+ * @since New in 1.8.
+ */
+void
+svn_stringbuf_replace(svn_stringbuf_t *str,
+ apr_size_t pos,
+ apr_size_t old_count,
+ const char *bytes,
+ apr_size_t new_count);
+
+/** Return a duplicate of @a original_string. */
+svn_stringbuf_t *
+svn_stringbuf_dup(const svn_stringbuf_t *original_string, apr_pool_t *pool);
+
+/** Return @c TRUE iff @a str1 and @a str2 have identical length and data. */
+svn_boolean_t
+svn_stringbuf_compare(const svn_stringbuf_t *str1,
+ const svn_stringbuf_t *str2);
+
+/** Return offset of first non-whitespace character in @a str, or return
+ * @a str->len if none.
+ */
+apr_size_t
+svn_stringbuf_first_non_whitespace(const svn_stringbuf_t *str);
+
+/** Strip whitespace from both sides of @a str (modified in place). */
+void
+svn_stringbuf_strip_whitespace(svn_stringbuf_t *str);
+
+/** Return position of last occurrence of @a ch in @a str, or return
+ * @a str->len if no occurrence.
+ */
+apr_size_t
+svn_stringbuf_find_char_backward(const svn_stringbuf_t *str, char ch);
+
+/** Return @c TRUE iff @a str1 and @a str2 have identical length and data. */
+svn_boolean_t
+svn_string_compare_stringbuf(const svn_string_t *str1,
+ const svn_stringbuf_t *str2);
+
+/** @} */
+
+
+/**
+ * @defgroup svn_string_cstrings C string functions
+ * @{
+ */
+
+/** Divide @a input into substrings along @a sep_chars boundaries, return an
+ * array of copies of those substrings (plain const char*), allocating both
+ * the array and the copies in @a pool.
+ *
+ * None of the elements added to the array contain any of the
+ * characters in @a sep_chars, and none of the new elements are empty
+ * (thus, it is possible that the returned array will have length
+ * zero).
+ *
+ * If @a chop_whitespace is TRUE, then remove leading and trailing
+ * whitespace from the returned strings.
+ */
+apr_array_header_t *
+svn_cstring_split(const char *input,
+ const char *sep_chars,
+ svn_boolean_t chop_whitespace,
+ apr_pool_t *pool);
+
+/** Like svn_cstring_split(), but append to existing @a array instead of
+ * creating a new one. Allocate the copied substrings in @a pool
+ * (i.e., caller decides whether or not to pass @a array->pool as @a pool).
+ */
+void
+svn_cstring_split_append(apr_array_header_t *array,
+ const char *input,
+ const char *sep_chars,
+ svn_boolean_t chop_whitespace,
+ apr_pool_t *pool);
+
+
+/** Return @c TRUE iff @a str matches any of the elements of @a list, a list
+ * of zero or more glob patterns.
+ */
+svn_boolean_t
+svn_cstring_match_glob_list(const char *str, const apr_array_header_t *list);
+
+/** Return @c TRUE iff @a str exactly matches any of the elements of @a list.
+ *
+ * @since new in 1.7
+ */
+svn_boolean_t
+svn_cstring_match_list(const char *str, const apr_array_header_t *list);
+
+/**
+ * Get the next token from @a *str interpreting any char from @a sep as a
+ * token separator. Separators at the beginning of @a str will be skipped.
+ * Returns a pointer to the beginning of the first token in @a *str or NULL
+ * if no token is left. Modifies @a str such that the next call will return
+ * the next token.
+ *
+ * @note The content of @a *str may be modified by this function.
+ *
+ * @since New in 1.8.
+ */
+char *
+svn_cstring_tokenize(const char *sep, char **str);
+
+/**
+ * Return the number of line breaks in @a msg, allowing any kind of newline
+ * termination (CR, LF, CRLF, or LFCR), even inconsistent.
+ *
+ * @since New in 1.2.
+ */
+int
+svn_cstring_count_newlines(const char *msg);
+
+/**
+ * Return a cstring which is the concatenation of @a strings (an array
+ * of char *) each followed by @a separator (that is, @a separator
+ * will also end the resulting string). Allocate the result in @a pool.
+ * If @a strings is empty, then return the empty string.
+ *
+ * @since New in 1.2.
+ */
+char *
+svn_cstring_join(const apr_array_header_t *strings,
+ const char *separator,
+ apr_pool_t *pool);
+
+/**
+ * Compare two strings @a atr1 and @a atr2, treating case-equivalent
+ * unaccented Latin (ASCII subset) letters as equal.
+ *
+ * Returns in integer greater than, equal to, or less than 0,
+ * according to whether @a str1 is considered greater than, equal to,
+ * or less than @a str2.
+ *
+ * @since New in 1.5.
+ */
+int
+svn_cstring_casecmp(const char *str1, const char *str2);
+
+/**
+ * Parse the C string @a str into a 64 bit number, and return it in @a *n.
+ * Assume that the number is represented in base @a base.
+ * Raise an error if conversion fails (e.g. due to overflow), or if the
+ * converted number is smaller than @a minval or larger than @a maxval.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_cstring_strtoi64(apr_int64_t *n, const char *str,
+ apr_int64_t minval, apr_int64_t maxval,
+ int base);
+
+/**
+ * Parse the C string @a str into a 64 bit number, and return it in @a *n.
+ * Assume that the number is represented in base 10.
+ * Raise an error if conversion fails (e.g. due to overflow).
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_cstring_atoi64(apr_int64_t *n, const char *str);
+
+/**
+ * Parse the C string @a str into a 32 bit number, and return it in @a *n.
+ * Assume that the number is represented in base 10.
+ * Raise an error if conversion fails (e.g. due to overflow).
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_cstring_atoi(int *n, const char *str);
+
+/**
+ * Parse the C string @a str into an unsigned 64 bit number, and return
+ * it in @a *n. Assume that the number is represented in base @a base.
+ * Raise an error if conversion fails (e.g. due to overflow), or if the
+ * converted number is smaller than @a minval or larger than @a maxval.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_cstring_strtoui64(apr_uint64_t *n, const char *str,
+ apr_uint64_t minval, apr_uint64_t maxval,
+ int base);
+
+/**
+ * Parse the C string @a str into an unsigned 64 bit number, and return
+ * it in @a *n. Assume that the number is represented in base 10.
+ * Raise an error if conversion fails (e.g. due to overflow).
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_cstring_atoui64(apr_uint64_t *n, const char *str);
+
+/**
+ * Parse the C string @a str into an unsigned 32 bit number, and return
+ * it in @a *n. Assume that the number is represented in base 10.
+ * Raise an error if conversion fails (e.g. due to overflow).
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_cstring_atoui(unsigned int *n, const char *str);
+
+/** @} */
+
+/** @} */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_STRING_H */
diff --git a/contrib/subversion/subversion/include/svn_subst.h b/contrib/subversion/subversion/include/svn_subst.h
new file mode 100644
index 0000000..98aaf3e
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_subst.h
@@ -0,0 +1,708 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_subst.h
+ * @brief Data substitution (keywords and EOL style)
+ */
+
+
+
+#ifndef SVN_SUBST_H
+#define SVN_SUBST_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_time.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_io.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* EOL conversion and keyword expansion. */
+
+/** The EOL used in the Repository for "native" files */
+#define SVN_SUBST_NATIVE_EOL_STR "\n"
+
+/** Valid states for 'svn:eol-style' property.
+ *
+ * Property nonexistence is equivalent to 'none'.
+ */
+typedef enum svn_subst_eol_style
+{
+ /** An unrecognized style */
+ svn_subst_eol_style_unknown,
+
+ /** EOL translation is "off" or ignored value */
+ svn_subst_eol_style_none,
+
+ /** Translation is set to client's native eol */
+ svn_subst_eol_style_native,
+
+ /** Translation is set to one of LF, CR, CRLF */
+ svn_subst_eol_style_fixed
+
+} svn_subst_eol_style_t;
+
+/** Set @a *style to the appropriate @c svn_subst_eol_style_t and @a *eol to
+ * the appropriate cstring for a given svn:eol-style property value.
+ *
+ * Set @a *eol to
+ *
+ * - @c NULL for @c svn_subst_eol_style_none, or
+ *
+ * - a NULL-terminated C string containing the native eol marker
+ * for this platform, for @c svn_subst_eol_style_native, or
+ *
+ * - a NULL-terminated C string containing the eol marker indicated
+ * by the property value, for @c svn_subst_eol_style_fixed.
+ *
+ * If @a *style is NULL, it is ignored.
+ */
+void
+svn_subst_eol_style_from_value(svn_subst_eol_style_t *style,
+ const char **eol,
+ const char *value);
+
+/** Indicates whether the working copy and normalized versions of a file
+ * with the given the parameters differ. If @a force_eol_check is TRUE,
+ * the routine also accounts for all translations required due to repairing
+ * fixed eol styles.
+ *
+ * @since New in 1.4
+ *
+ */
+svn_boolean_t
+svn_subst_translation_required(svn_subst_eol_style_t style,
+ const char *eol,
+ apr_hash_t *keywords,
+ svn_boolean_t special,
+ svn_boolean_t force_eol_check);
+
+
+/** Values used in keyword expansion.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+typedef struct svn_subst_keywords_t
+{
+ /**
+ * @name svn_subst_keywords_t fields
+ * String expansion of the like-named keyword, or NULL if the keyword
+ * was not selected in the svn:keywords property.
+ * @{
+ */
+ const svn_string_t *revision;
+ const svn_string_t *date;
+ const svn_string_t *author;
+ const svn_string_t *url;
+ const svn_string_t *id;
+ /** @} */
+} svn_subst_keywords_t;
+
+
+/**
+ * Set @a *kw to a new keywords hash filled with the appropriate contents
+ * given a @a keywords_string (the contents of the svn:keywords
+ * property for the file in question), the revision @a rev, the @a url,
+ * the @a date the file was committed on, the @a author of the last
+ * commit, and the URL of the repository root @a repos_root_url.
+ *
+ * Custom keywords defined in svn:keywords properties are expanded
+ * using the provided parameters and in accordance with the following
+ * format substitutions in the @a keywords_string:
+ * %a - The author.
+ * %b - The basename of the URL.
+ * %d - Short format of the date.
+ * %D - Long format of the date.
+ * %P - The file's path, relative to the repository root URL.
+ * %r - The revision.
+ * %R - The URL to the root of the repository.
+ * %u - The URL of the file.
+ * %_ - A space (keyword definitions cannot contain a literal space).
+ * %% - A literal '%'.
+ * %H - Equivalent to %P%_%r%_%d%_%a.
+ * %I - Equivalent to %b%_%r%_%d%_%a.
+ *
+ * Custom keywords are defined by appending '=' to the keyword name, followed
+ * by a string containing any combination of the format substitutions.
+ *
+ * Any of the inputs @a rev, @a url, @a date, @a author, and @a repos_root_url
+ * can be @c NULL, or @c 0 for @a date, to indicate that the information is
+ * not present. Each piece of information that is not present expands to the
+ * empty string wherever it appears in an expanded keyword value. (This can
+ * result in multiple adjacent spaces in the expansion of a multi-valued
+ * keyword such as "Id".)
+ *
+ * Hash keys are of type <tt>const char *</tt>.
+ * Hash values are of type <tt>svn_string_t *</tt>.
+ *
+ * All memory is allocated out of @a pool.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_subst_build_keywords3(apr_hash_t **kw,
+ const char *keywords_string,
+ const char *rev,
+ const char *url,
+ const char *repos_root_url,
+ apr_time_t date,
+ const char *author,
+ apr_pool_t *pool);
+
+/** Similar to svn_subst_build_keywords3() except that it does not accept
+ * the @a repos_root_url parameter and hence supports less substitutions,
+ * and also does not support custom keyword definitions.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_build_keywords2(apr_hash_t **kw,
+ const char *keywords_string,
+ const char *rev,
+ const char *url,
+ apr_time_t date,
+ const char *author,
+ apr_pool_t *pool);
+
+/** Similar to svn_subst_build_keywords2() except that it populates
+ * an existing structure @a *kw instead of creating a keywords hash.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_build_keywords(svn_subst_keywords_t *kw,
+ const char *keywords_string,
+ const char *rev,
+ const char *url,
+ apr_time_t date,
+ const char *author,
+ apr_pool_t *pool);
+
+
+/** Return @c TRUE if @a a and @a b do not hold the same keywords.
+ *
+ * @a a and @a b are hashes of the form produced by
+ * svn_subst_build_keywords2().
+ *
+ * @since New in 1.3.
+ *
+ * If @a compare_values is @c TRUE, "same" means that the @a a and @a b
+ * contain exactly the same set of keywords, and the values of corresponding
+ * keywords match as well. Else if @a compare_values is @c FALSE, then
+ * "same" merely means that @a a and @a b hold the same set of keywords,
+ * although those keywords' values might differ.
+ *
+ * @a a and/or @a b may be @c NULL; for purposes of comparison, @c NULL is
+ * equivalent to holding no keywords.
+ */
+svn_boolean_t
+svn_subst_keywords_differ2(apr_hash_t *a,
+ apr_hash_t *b,
+ svn_boolean_t compare_values,
+ apr_pool_t *pool);
+
+/** Similar to svn_subst_keywords_differ2() except that it compares
+ * two @c svn_subst_keywords_t structs instead of keyword hashes.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_boolean_t
+svn_subst_keywords_differ(const svn_subst_keywords_t *a,
+ const svn_subst_keywords_t *b,
+ svn_boolean_t compare_values);
+
+
+/**
+ * Copy and translate the data in @a src_stream into @a dst_stream. It is
+ * assumed that @a src_stream is a readable stream and @a dst_stream is a
+ * writable stream.
+ *
+ * If @a eol_str is non-@c NULL, replace whatever bytestring @a src_stream
+ * uses to denote line endings with @a eol_str in the output. If
+ * @a src_stream has an inconsistent line ending style, then: if @a repair
+ * is @c FALSE, return @c SVN_ERR_IO_INCONSISTENT_EOL, else if @a repair is
+ * @c TRUE, convert any line ending in @a src_stream to @a eol_str in
+ * @a dst_stream. Recognized line endings are: "\n", "\r", and "\r\n".
+ *
+ * See svn_subst_stream_translated() for details of the keyword substitution
+ * which is controlled by the @a expand and @a keywords parameters.
+ *
+ * Note that a translation request is *required*: one of @a eol_str or
+ * @a keywords must be non-@c NULL.
+ *
+ * Notes:
+ *
+ * See svn_wc__get_keywords() and svn_wc__get_eol_style() for a
+ * convenient way to get @a eol_str and @a keywords if in libsvn_wc.
+ *
+ * @since New in 1.3.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ * Callers should use svn_subst_stream_translated() instead.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_translate_stream3(svn_stream_t *src_stream,
+ svn_stream_t *dst_stream,
+ const char *eol_str,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *scratch_pool);
+
+
+/** Similar to svn_subst_translate_stream3() except relies upon a
+ * @c svn_subst_keywords_t struct instead of a hash for the keywords.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_translate_stream2(svn_stream_t *src_stream,
+ svn_stream_t *dst_stream,
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Same as svn_subst_translate_stream2(), but does not take a @a pool
+ * argument, instead creates a temporary subpool of the global pool, and
+ * destroys it before returning.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_translate_stream(svn_stream_t *src_stream,
+ svn_stream_t *dst_stream,
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand);
+
+
+/** Return a stream which performs eol translation and keyword
+ * expansion when read from or written to. The stream @a stream
+ * is used to read and write all data.
+ *
+ * Make sure you call svn_stream_close() on the returned stream to
+ * ensure all data is flushed and cleaned up (this will also close
+ * the provided @a stream).
+ *
+ * Read operations from and write operations to the stream
+ * perform the same operation: if @a expand is @c FALSE, both
+ * contract keywords. One stream supports both read and write
+ * operations. Reads and writes may be mixed.
+ *
+ * If @a eol_str is non-@c NULL, replace whatever bytestring the input uses
+ * to denote line endings with @a eol_str in the output. If the input has
+ * an inconsistent line ending style, then: if @a repair is @c FALSE, then a
+ * subsequent read, write or other operation on the stream will return
+ * @c SVN_ERR_IO_INCONSISTENT_EOL when the inconsistency is detected, else
+ * if @a repair is @c TRUE, convert any line ending to @a eol_str.
+ * Recognized line endings are: "\n", "\r", and "\r\n".
+ *
+ * Expand and contract keywords using the contents of @a keywords as the
+ * new values. If @a expand is @c TRUE, expand contracted keywords and
+ * re-expand expanded keywords. If @a expand is @c FALSE, contract expanded
+ * keywords and ignore contracted ones. Keywords not found in the hash are
+ * ignored (not contracted or expanded). If the @a keywords hash
+ * itself is @c NULL, keyword substitution will be altogether ignored.
+ *
+ * Detect only keywords that are no longer than @c SVN_KEYWORD_MAX_LEN
+ * bytes, including the delimiters and the keyword itself.
+ *
+ * Recommendation: if @a expand is FALSE, then you don't care about the
+ * keyword values, so use empty strings as non-NULL signifiers when you
+ * build the keywords hash.
+ *
+ * The stream returned is allocated in @a result_pool.
+ *
+ * If the inner stream implements resetting via svn_stream_reset(),
+ * or marking and seeking via svn_stream_mark() and svn_stream_seek(),
+ * the translated stream will too.
+ *
+ * @since New in 1.4.
+ */
+svn_stream_t *
+svn_subst_stream_translated(svn_stream_t *stream,
+ const char *eol_str,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *result_pool);
+
+
+/** Set @a *stream to a stream which performs eol translation and keyword
+ * expansion when read from or written to. The stream @a source
+ * is used to read and write all data. Make sure you call
+ * svn_stream_close() on @a stream to make sure all data are flushed
+ * and cleaned up.
+ *
+ * When @a stream is closed, then @a source will be closed.
+ *
+ * Read and write operations perform the same transformation:
+ * all data is translated to normal form.
+ *
+ * @see svn_subst_translate_to_normal_form()
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_stream_translated_to_normal_form(svn_stream_t **stream,
+ svn_stream_t *source,
+ svn_subst_eol_style_t eol_style,
+ const char *eol_str,
+ svn_boolean_t always_repair_eols,
+ apr_hash_t *keywords,
+ apr_pool_t *pool);
+
+
+/** Set @a *stream to a readable stream containing the "normal form"
+ * of the special file located at @a path. The stream will be allocated
+ * in @a result_pool, and any temporary allocations will be made in
+ * @a scratch_pool.
+ *
+ * If the file at @a path is in fact a regular file, just read its content,
+ * which should be in the "normal form" for a special file. This enables
+ * special files to be written and read on platforms that do not treat them
+ * as special.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_subst_read_specialfile(svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Set @a *stream to a writable stream that accepts content in
+ * the "normal form" for a special file, to be located at @a path, and
+ * will create that file when the stream is closed. The stream will be
+ * allocated in @a result_pool, and any temporary allocations will be
+ * made in @a scratch_pool.
+ *
+ * If the platform does not support the semantics of the special file, write
+ * a regular file containing the "normal form" text. This enables special
+ * files to be written and read on platforms that do not treat them as
+ * special.
+ *
+ * Note: the target file is created in a temporary location, then renamed
+ * into position, so the creation can be considered "atomic".
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_subst_create_specialfile(svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Set @a *stream to a stream which translates the special file at @a path
+ * to the internal representation for special files when read from. When
+ * written to, it does the reverse: creating a special file when the
+ * stream is closed.
+ *
+ * @since New in 1.5.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ * Callers should use svn_subst_read_specialfile or
+ * svn_subst_create_specialfile as appropriate.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_stream_from_specialfile(svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/**
+ * Copy the contents of file-path @a src to file-path @a dst atomically,
+ * either creating @a dst or overwriting @a dst if it exists, possibly
+ * performing line ending and keyword translations.
+ *
+ * The parameters @a *eol_str, @a repair, @a *keywords and @a expand are
+ * defined the same as in svn_subst_translate_stream3().
+ *
+ * In addition, it will create a special file from normal form or
+ * translate one to normal form if @a special is @c TRUE.
+ *
+ * If anything goes wrong during the copy, attempt to delete @a dst (if
+ * it exists).
+ *
+ * If @a eol_str and @a keywords are @c NULL, behavior is just a byte-for-byte
+ * copy.
+ *
+ * @a cancel_func and @a cancel_baton will be called (if not NULL)
+ * periodically to check for cancellation.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_subst_copy_and_translate4(const char *src,
+ const char *dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ svn_boolean_t special,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_subst_copy_and_translate4() but without a cancellation
+ * function and baton.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_copy_and_translate3(const char *src,
+ const char *dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ svn_boolean_t special,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_subst_copy_and_translate3() except that @a keywords is a
+ * @c svn_subst_keywords_t struct instead of a keywords hash.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ * @since New in 1.1.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_copy_and_translate2(const char *src,
+ const char *dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand,
+ svn_boolean_t special,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_subst_copy_and_translate2() except that @a special is
+ * always set to @c FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_copy_and_translate(const char *src,
+ const char *dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *pool);
+
+
+/**
+ * Set @a *dst to a copy of the string @a src, possibly performing line
+ * ending and keyword translations.
+ *
+ * This is a variant of svn_subst_translate_stream3() that operates on
+ * cstrings. @see svn_subst_stream_translated() for details of the
+ * translation and of @a eol_str, @a repair, @a keywords and @a expand.
+ *
+ * If @a eol_str and @a keywords are @c NULL, behavior is just a byte-for-byte
+ * copy.
+ *
+ * Allocate @a *dst in @a pool.
+ *
+ * @since New in 1.3.
+ */
+svn_error_t *
+svn_subst_translate_cstring2(const char *src,
+ const char **dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_subst_translate_cstring2() except that @a keywords is a
+ * @c svn_subst_keywords_t struct instead of a keywords hash.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_translate_cstring(const char *src,
+ const char **dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *pool);
+
+/**
+ * Translate the file @a src in working copy form to a file @a dst in
+ * normal form.
+ *
+ * The values specified for @a eol_style, @a *eol_str, @a keywords and
+ * @a special, should be the ones used to translate the file to its
+ * working copy form. Usually, these are the values specified by the
+ * user in the files' properties.
+ *
+ * Inconsistent line endings in the file will be automatically repaired
+ * (made consistent) for some eol styles. For all others, an error is
+ * returned. By setting @a always_repair_eols to @c TRUE, eols will be
+ * made consistent even for those styles which don't have it by default.
+ *
+ * @note To translate a file FROM normal form, use
+ * svn_subst_copy_and_translate3().
+ *
+ * @since New in 1.4
+ * @deprecated Provided for backward compatibility with the 1.5 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_translate_to_normal_form(const char *src,
+ const char *dst,
+ svn_subst_eol_style_t eol_style,
+ const char *eol_str,
+ svn_boolean_t always_repair_eols,
+ apr_hash_t *keywords,
+ svn_boolean_t special,
+ apr_pool_t *pool);
+
+/**
+ * Set @a *stream_p to a stream that detranslates the file @a src from
+ * working copy form to normal form, allocated in @a pool.
+ *
+ * The values specified for @a eol_style, @a *eol_str, @a keywords and
+ * @a special, should be the ones used to translate the file to its
+ * working copy form. Usually, these are the values specified by the
+ * user in the files' properties.
+ *
+ * Inconsistent line endings in the file will be automatically repaired
+ * (made consistent) for some eol styles. For all others, an error is
+ * returned. By setting @a always_repair_eols to @c TRUE, eols will be
+ * made consistent even for those styles which don't have it by default.
+ *
+ * @since New in 1.4.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ * Use svn_subst_stream_from_specialfile if the source is special;
+ * otherwise, use svn_subst_stream_translated_to_normal_form.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_subst_stream_detranslated(svn_stream_t **stream_p,
+ const char *src,
+ svn_subst_eol_style_t eol_style,
+ const char *eol_str,
+ svn_boolean_t always_repair_eols,
+ apr_hash_t *keywords,
+ svn_boolean_t special,
+ apr_pool_t *pool);
+
+
+/* EOL conversion and character encodings */
+
+/** Translate the string @a value from character encoding @a encoding to
+ * UTF8, and also from its current line-ending style to LF line-endings. If
+ * @a encoding is @c NULL, translate from the system-default encoding.
+ *
+ * If @a translated_to_utf8 is not @c NULL, then set @a *translated_to_utf8
+ * to @c TRUE if at least one character of @a value in the source character
+ * encoding was translated to UTF-8, or to @c FALSE otherwise.
+ *
+ * If @a translated_line_endings is not @c NULL, then set @a
+ * *translated_line_endings to @c TRUE if at least one line ending was
+ * changed to LF, or to @c FALSE otherwise.
+ *
+ * If @a value has an inconsistent line ending style, then: if @a repair
+ * is @c FALSE, return @c SVN_ERR_IO_INCONSISTENT_EOL, else if @a repair is
+ * @c TRUE, convert any line ending in @a value to "\n" in
+ * @a *new_value. Recognized line endings are: "\n", "\r", and "\r\n".
+ *
+ * Set @a *new_value to the translated string, allocated in @a result_pool.
+ *
+ * @a scratch_pool is used for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_subst_translate_string2(svn_string_t **new_value,
+ svn_boolean_t *translated_to_utf8,
+ svn_boolean_t *translated_line_endings,
+ const svn_string_t *value,
+ const char *encoding,
+ svn_boolean_t repair,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_subst_translate_string2(), except that the information about
+ * whether re-encoding or line ending translation were performed is discarded.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *svn_subst_translate_string(svn_string_t **new_value,
+ const svn_string_t *value,
+ const char *encoding,
+ apr_pool_t *pool);
+
+/** Translate the string @a value from UTF8 and LF line-endings into native
+ * character encoding and native line-endings. If @a for_output is TRUE,
+ * translate to the character encoding of the output locale, else to that of
+ * the default locale.
+ *
+ * Set @a *new_value to the translated string, allocated in @a pool.
+ */
+svn_error_t *svn_subst_detranslate_string(svn_string_t **new_value,
+ const svn_string_t *value,
+ svn_boolean_t for_output,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_SUBST_H */
diff --git a/contrib/subversion/subversion/include/svn_time.h b/contrib/subversion/subversion/include/svn_time.h
new file mode 100644
index 0000000..76517ca
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_time.h
@@ -0,0 +1,94 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_time.h
+ * @brief Time/date utilities
+ */
+
+#ifndef SVN_TIME_H
+#define SVN_TIME_H
+
+#include <apr_pools.h>
+#include <apr_time.h>
+
+#include "svn_error.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** Convert @a when to a <tt>const char *</tt> representation allocated
+ * in @a pool. Use svn_time_from_cstring() for the reverse
+ * conversion.
+ */
+const char *
+svn_time_to_cstring(apr_time_t when,
+ apr_pool_t *pool);
+
+/** Convert @a data to an @c apr_time_t @a when.
+ * Use @a pool for temporary memory allocation.
+ */
+svn_error_t *
+svn_time_from_cstring(apr_time_t *when,
+ const char *data,
+ apr_pool_t *pool);
+
+/** Convert @a when to a <tt>const char *</tt> representation allocated
+ * in @a pool, suitable for human display in UTF8.
+ */
+const char *
+svn_time_to_human_cstring(apr_time_t when,
+ apr_pool_t *pool);
+
+
+/** Convert a human-readable date @a text into an @c apr_time_t, using
+ * @a now as the current time and storing the result in @a result.
+ * The local time zone will be used to compute the appropriate GMT
+ * offset if @a text contains a local time specification. Set @a
+ * matched to indicate whether or not @a text was parsed successfully.
+ * Perform any allocation in @a pool. Return an error iff an internal
+ * error (rather than a simple parse error) occurs.
+ */
+svn_error_t *
+svn_parse_date(svn_boolean_t *matched,
+ apr_time_t *result,
+ const char *text,
+ apr_time_t now,
+ apr_pool_t *pool);
+
+
+/** Sleep until the next second, to ensure that any files modified
+ * after we exit have a different timestamp than the one we recorded.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ * Use svn_io_sleep_for_timestamps() instead.
+ */
+SVN_DEPRECATED
+void
+svn_sleep_for_timestamps(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_TIME_H */
diff --git a/contrib/subversion/subversion/include/svn_types.h b/contrib/subversion/subversion/include/svn_types.h
new file mode 100644
index 0000000..6b3a087
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_types.h
@@ -0,0 +1,1287 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_types.h
+ * @brief Subversion's data types
+ */
+
+#ifndef SVN_TYPES_H
+#define SVN_TYPES_H
+
+/* ### this should go away, but it causes too much breakage right now */
+#include <stdlib.h>
+#include <limits.h> /* for ULONG_MAX */
+
+#include <apr.h> /* for apr_size_t, apr_int64_t, ... */
+#include <apr_errno.h> /* for apr_status_t */
+#include <apr_pools.h> /* for apr_pool_t */
+#include <apr_hash.h> /* for apr_hash_t */
+#include <apr_tables.h> /* for apr_array_push() */
+#include <apr_time.h> /* for apr_time_t */
+#include <apr_strings.h> /* for apr_atoi64() */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/** Macro used to mark deprecated functions.
+ *
+ * @since New in 1.6.
+ */
+#ifndef SVN_DEPRECATED
+# if !defined(SWIGPERL) && !defined(SWIGPYTHON) && !defined(SWIGRUBY)
+# if defined(__GNUC__) && (__GNUC__ >= 4 || (__GNUC__==3 && __GNUC_MINOR__>=1))
+# define SVN_DEPRECATED __attribute__((deprecated))
+# elif defined(_MSC_VER) && _MSC_VER >= 1300
+# define SVN_DEPRECATED __declspec(deprecated)
+# else
+# define SVN_DEPRECATED
+# endif
+# else
+# define SVN_DEPRECATED
+# endif
+#endif
+
+
+/** Indicate whether the current platform supports unaligned data access.
+ *
+ * On the majority of machines running SVN (x86 / x64), unaligned access
+ * is much cheaper than repeated aligned access. Define this macro to 1
+ * on those machines.
+ * Unaligned access on other machines (e.g. IA64) will trigger memory
+ * access faults or simply misbehave.
+ *
+ * Note: Some platforms may only support unaligned access for integers
+ * (PowerPC). As a result this macro should only be used to determine
+ * if unaligned access is supported for integers.
+ *
+ * @since New in 1.7.
+ */
+#ifndef SVN_UNALIGNED_ACCESS_IS_OK
+# if defined(_M_IX86) || defined(i386) \
+ || defined(_M_X64) || defined(__x86_64) \
+ || defined(__powerpc__) || defined(__ppc__)
+# define SVN_UNALIGNED_ACCESS_IS_OK 1
+# else
+# define SVN_UNALIGNED_ACCESS_IS_OK 0
+# endif
+#endif
+
+
+
+/** YABT: Yet Another Boolean Type */
+typedef int svn_boolean_t;
+
+#ifndef TRUE
+/** uhh... true */
+#define TRUE 1
+#endif /* TRUE */
+
+#ifndef FALSE
+/** uhh... false */
+#define FALSE 0
+#endif /* FALSE */
+
+
+
+/** Subversion error object.
+ *
+ * Defined here, rather than in svn_error.h, to avoid a recursive @#include
+ * situation.
+ */
+typedef struct svn_error_t
+{
+ /** APR error value; possibly an SVN_ custom error code (see
+ * `svn_error_codes.h' for a full listing).
+ */
+ apr_status_t apr_err;
+
+ /** Details from the producer of error.
+ *
+ * Note that if this error was generated by Subversion's API, you'll
+ * probably want to use svn_err_best_message() to get a single
+ * descriptive string for this error chain (see the @a child member)
+ * or svn_handle_error2() to print the error chain in full. This is
+ * because Subversion's API functions sometimes add many links to
+ * the error chain that lack details (used only to produce virtual
+ * stack traces). (Use svn_error_purge_tracing() to remove those
+ * trace-only links from the error chain.)
+ */
+ const char *message;
+
+ /** Pointer to the error we "wrap" (may be @c NULL). Via this
+ * member, individual error object can be strung together into an
+ * "error chain".
+ */
+ struct svn_error_t *child;
+
+ /** The pool in which this error object is allocated. (If
+ * Subversion's APIs are used to manage error chains, then this pool
+ * will contain the whole error chain of which this object is a
+ * member.) */
+ apr_pool_t *pool;
+
+ /** Source file where the error originated (iff @c SVN_DEBUG;
+ * undefined otherwise).
+ */
+ const char *file;
+
+ /** Source line where the error originated (iff @c SVN_DEBUG;
+ * undefined otherwise).
+ */
+ long line;
+
+} svn_error_t;
+
+
+
+/* See svn_version.h.
+ Defined here to avoid including svn_version.h from all public headers. */
+typedef struct svn_version_t svn_version_t;
+
+
+
+/** @defgroup APR_ARRAY_compat_macros APR Array Compatibility Helper Macros
+ * These macros are provided by APR itself from version 1.3.
+ * Definitions are provided here for when using older versions of APR.
+ * @{
+ */
+
+/** index into an apr_array_header_t */
+#ifndef APR_ARRAY_IDX
+#define APR_ARRAY_IDX(ary,i,type) (((type *)(ary)->elts)[i])
+#endif
+
+/** easier array-pushing syntax */
+#ifndef APR_ARRAY_PUSH
+#define APR_ARRAY_PUSH(ary,type) (*((type *)apr_array_push(ary)))
+#endif
+
+/** @} */
+
+
+
+/** @defgroup apr_hash_utilities APR Hash Table Helpers
+ * These functions enable the caller to dereference an APR hash table index
+ * without type casts or temporary variables.
+ *
+ * ### These are private, and may go away when APR implements them natively.
+ * @{
+ */
+
+/** Return the key of the hash table entry indexed by @a hi. */
+const void *
+svn__apr_hash_index_key(const apr_hash_index_t *hi);
+
+/** Return the key length of the hash table entry indexed by @a hi. */
+apr_ssize_t
+svn__apr_hash_index_klen(const apr_hash_index_t *hi);
+
+/** Return the value of the hash table entry indexed by @a hi. */
+void *
+svn__apr_hash_index_val(const apr_hash_index_t *hi);
+
+/** @} */
+
+
+
+/** On Windows, APR_STATUS_IS_ENOTDIR includes several kinds of
+ * invalid-pathname error but not ERROR_INVALID_NAME, so we include it.
+ * We also include ERROR_DIRECTORY as that was not included in apr versions
+ * before 1.4.0 and this fix is not backported */
+/* ### These fixes should go into APR. */
+#ifndef WIN32
+#define SVN__APR_STATUS_IS_ENOTDIR(s) APR_STATUS_IS_ENOTDIR(s)
+#else
+#define SVN__APR_STATUS_IS_ENOTDIR(s) (APR_STATUS_IS_ENOTDIR(s) \
+ || ((s) == APR_OS_START_SYSERR + ERROR_DIRECTORY) \
+ || ((s) == APR_OS_START_SYSERR + ERROR_INVALID_NAME))
+#endif
+
+/** @} */
+
+
+
+/** The various types of nodes in the Subversion filesystem. */
+typedef enum svn_node_kind_t
+{
+ /** absent */
+ svn_node_none,
+
+ /** regular file */
+ svn_node_file,
+
+ /** directory */
+ svn_node_dir,
+
+ /** something's here, but we don't know what */
+ svn_node_unknown,
+
+ /**
+ * symbolic link
+ * @note This value is not currently used by the public API.
+ * @since New in 1.8.
+ */
+ svn_node_symlink
+} svn_node_kind_t;
+
+/** Return a constant string expressing @a kind as an English word, e.g.,
+ * "file", "dir", etc. The string is not localized, as it may be used for
+ * client<->server communications. If the kind is not recognized, return
+ * "unknown".
+ *
+ * @since New in 1.6.
+ */
+const char *
+svn_node_kind_to_word(svn_node_kind_t kind);
+
+/** Return the appropriate node_kind for @a word. @a word is as
+ * returned from svn_node_kind_to_word(). If @a word does not
+ * represent a recognized kind or is @c NULL, return #svn_node_unknown.
+ *
+ * @since New in 1.6.
+ */
+svn_node_kind_t
+svn_node_kind_from_word(const char *word);
+
+
+/** Generic three-state property to represent an unknown value for values
+ * that are just like booleans. The values have been set deliberately to
+ * make tristates disjoint from #svn_boolean_t.
+ *
+ * @note It is unsafe to use apr_pcalloc() to allocate these, since '0' is
+ * not a valid value.
+ *
+ * @since New in 1.7. */
+typedef enum svn_tristate_t
+{
+ /** state known to be false (the constant does not evaulate to false) */
+ svn_tristate_false = 2,
+ /** state known to be true */
+ svn_tristate_true,
+ /** state could be true or false */
+ svn_tristate_unknown
+} svn_tristate_t;
+
+/** Return a constant string "true", "false" or NULL representing the value of
+ * @a tristate.
+ *
+ * @since New in 1.7.
+ */
+const char *
+svn_tristate__to_word(svn_tristate_t tristate);
+
+/** Return the appropriate tristate for @a word. If @a word is "true", returns
+ * #svn_tristate_true; if @a word is "false", returns #svn_tristate_false,
+ * for all other values (including NULL) returns #svn_tristate_unknown.
+ *
+ * @since New in 1.7.
+ */
+svn_tristate_t
+svn_tristate__from_word(const char * word);
+
+
+
+/** About Special Files in Subversion
+ *
+ * Subversion denotes files that cannot be portably created or
+ * modified as "special" files (svn_node_special). It stores these
+ * files in the repository as a plain text file with the svn:special
+ * property set. The file contents contain: a platform-specific type
+ * string, a space character, then any information necessary to create
+ * the file on a supported platform. For example, if a symbolic link
+ * were being represented, the repository file would have the
+ * following contents:
+ *
+ * "link /path/to/link/target"
+ *
+ * Where 'link' is the identifier string showing that this special
+ * file should be a symbolic link and '/path/to/link/target' is the
+ * destination of the symbolic link.
+ *
+ * Special files are stored in the text-base exactly as they are
+ * stored in the repository. The platform specific files are created
+ * in the working copy at EOL/keyword translation time using
+ * svn_subst_copy_and_translate2(). If the current platform does not
+ * support a specific special file type, the file is copied into the
+ * working copy as it is seen in the repository. Because of this,
+ * users of other platforms can still view and modify the special
+ * files, even if they do not have their unique properties.
+ *
+ * New types of special files can be added by:
+ * 1. Implementing a platform-dependent routine to create a uniquely
+ * named special file and one to read the special file in
+ * libsvn_subr/io.c.
+ * 2. Creating a new textual name similar to
+ * SVN_SUBST__SPECIAL_LINK_STR in libsvn_subr/subst.c.
+ * 3. Handling the translation/detranslation case for the new type in
+ * create_special_file and detranslate_special_file, using the
+ * routines from 1.
+ */
+
+
+
+/** A revision number. */
+typedef long int svn_revnum_t;
+
+/** Valid revision numbers begin at 0 */
+#define SVN_IS_VALID_REVNUM(n) ((n) >= 0)
+
+/** The 'official' invalid revision num */
+#define SVN_INVALID_REVNUM ((svn_revnum_t) -1)
+
+/** Not really invalid...just unimportant -- one day, this can be its
+ * own unique value, for now, just make it the same as
+ * #SVN_INVALID_REVNUM.
+ */
+#define SVN_IGNORED_REVNUM ((svn_revnum_t) -1)
+
+/** Convert NULL-terminated C string @a str to a revision number. */
+#define SVN_STR_TO_REV(str) ((svn_revnum_t) atol(str))
+
+/**
+ * Parse NULL-terminated C string @a str as a revision number and
+ * store its value in @a rev. If @a endptr is non-NULL, then the
+ * address of the first non-numeric character in @a str is stored in
+ * it. If there are no digits in @a str, then @a endptr is set (if
+ * non-NULL), and the error #SVN_ERR_REVNUM_PARSE_FAILURE error is
+ * returned. Negative numbers parsed from @a str are considered
+ * invalid, and result in the same error.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_revnum_parse(svn_revnum_t *rev,
+ const char *str,
+ const char **endptr);
+
+/** Originally intended to be used in printf()-style functions to format
+ * revision numbers. Deprecated due to incompatibilities with language
+ * translation tools (e.g. gettext).
+ *
+ * New code should use a bare "%ld" format specifier for formatting revision
+ * numbers.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+#define SVN_REVNUM_T_FMT "ld"
+
+
+
+/** The size of a file in the Subversion FS. */
+typedef apr_int64_t svn_filesize_t;
+
+/** The 'official' invalid file size constant. */
+#define SVN_INVALID_FILESIZE ((svn_filesize_t) -1)
+
+/** In printf()-style functions, format file sizes using this. */
+#define SVN_FILESIZE_T_FMT APR_INT64_T_FMT
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+/* Parse a base-10 numeric string into a 64-bit unsigned numeric value. */
+/* NOTE: Private. For use by Subversion's own code only. See issue #1644. */
+/* FIXME: APR should supply a function to do this, such as "apr_atoui64". */
+#define svn__atoui64(X) ((apr_uint64_t) apr_atoi64(X))
+#endif
+
+
+
+/** An enum to indicate whether recursion is needed. */
+enum svn_recurse_kind
+{
+ svn_nonrecursive = 1,
+ svn_recursive
+};
+
+/** The concept of depth for directories.
+ *
+ * @note This is similar to, but not exactly the same as, the WebDAV
+ * and LDAP concepts of depth.
+ *
+ * @since New in 1.5.
+ */
+typedef enum svn_depth_t
+{
+ /* The order of these depths is important: the higher the number,
+ the deeper it descends. This allows us to compare two depths
+ numerically to decide which should govern. */
+
+ /** Depth undetermined or ignored. In some contexts, this means the
+ client should choose an appropriate default depth. The server
+ will generally treat it as #svn_depth_infinity. */
+ svn_depth_unknown = -2,
+
+ /** Exclude (i.e., don't descend into) directory D.
+ @note In Subversion 1.5, svn_depth_exclude is *not* supported
+ anywhere in the client-side (libsvn_wc/libsvn_client/etc) code;
+ it is only supported as an argument to set_path functions in the
+ ra and repos reporters. (This will enable future versions of
+ Subversion to run updates, etc, against 1.5 servers with proper
+ svn_depth_exclude behavior, once we get a chance to implement
+ client-side support for svn_depth_exclude.)
+ */
+ svn_depth_exclude = -1,
+
+ /** Just the named directory D, no entries. Updates will not pull in
+ any files or subdirectories not already present. */
+ svn_depth_empty = 0,
+
+ /** D + its file children, but not subdirs. Updates will pull in any
+ files not already present, but not subdirectories. */
+ svn_depth_files = 1,
+
+ /** D + immediate children (D and its entries). Updates will pull in
+ any files or subdirectories not already present; those
+ subdirectories' this_dir entries will have depth-empty. */
+ svn_depth_immediates = 2,
+
+ /** D + all descendants (full recursion from D). Updates will pull
+ in any files or subdirectories not already present; those
+ subdirectories' this_dir entries will have depth-infinity.
+ Equivalent to the pre-1.5 default update behavior. */
+ svn_depth_infinity = 3
+
+} svn_depth_t;
+
+/** Return a constant string expressing @a depth as an English word,
+ * e.g., "infinity", "immediates", etc. The string is not localized,
+ * as it may be used for client<->server communications.
+ *
+ * @since New in 1.5.
+ */
+const char *
+svn_depth_to_word(svn_depth_t depth);
+
+/** Return the appropriate depth for @a depth_str. @a word is as
+ * returned from svn_depth_to_word(). If @a depth_str does not
+ * represent a recognized depth, return #svn_depth_unknown.
+ *
+ * @since New in 1.5.
+ */
+svn_depth_t
+svn_depth_from_word(const char *word);
+
+/** Return #svn_depth_infinity if boolean @a recurse is TRUE, else
+ * return #svn_depth_files.
+ *
+ * @note New code should never need to use this, it is called only
+ * from pre-depth APIs, for compatibility.
+ *
+ * @since New in 1.5.
+ */
+#define SVN_DEPTH_INFINITY_OR_FILES(recurse) \
+ ((recurse) ? svn_depth_infinity : svn_depth_files)
+
+/** Return #svn_depth_infinity if boolean @a recurse is TRUE, else
+ * return #svn_depth_immediates.
+ *
+ * @note New code should never need to use this, it is called only
+ * from pre-depth APIs, for compatibility.
+ *
+ * @since New in 1.5.
+ */
+#define SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse) \
+ ((recurse) ? svn_depth_infinity : svn_depth_immediates)
+
+/** Return #svn_depth_infinity if boolean @a recurse is TRUE, else
+ * return #svn_depth_empty.
+ *
+ * @note New code should never need to use this, it is called only
+ * from pre-depth APIs, for compatibility.
+ *
+ * @since New in 1.5.
+ */
+#define SVN_DEPTH_INFINITY_OR_EMPTY(recurse) \
+ ((recurse) ? svn_depth_infinity : svn_depth_empty)
+
+/** Return a recursion boolean based on @a depth.
+ *
+ * Although much code has been converted to use depth, some code still
+ * takes a recurse boolean. In most cases, it makes sense to treat
+ * unknown or infinite depth as recursive, and any other depth as
+ * non-recursive (which in turn usually translates to #svn_depth_files).
+ */
+#define SVN_DEPTH_IS_RECURSIVE(depth) \
+ ((depth) == svn_depth_infinity || (depth) == svn_depth_unknown)
+
+
+
+/**
+ * It is sometimes convenient to indicate which parts of an #svn_dirent_t
+ * object you are actually interested in, so that calculating and sending
+ * the data corresponding to the other fields can be avoided. These values
+ * can be used for that purpose.
+ *
+ * @defgroup svn_dirent_fields Dirent fields
+ * @{
+ */
+
+/** An indication that you are interested in the @c kind field */
+#define SVN_DIRENT_KIND 0x00001
+
+/** An indication that you are interested in the @c size field */
+#define SVN_DIRENT_SIZE 0x00002
+
+/** An indication that you are interested in the @c has_props field */
+#define SVN_DIRENT_HAS_PROPS 0x00004
+
+/** An indication that you are interested in the @c created_rev field */
+#define SVN_DIRENT_CREATED_REV 0x00008
+
+/** An indication that you are interested in the @c time field */
+#define SVN_DIRENT_TIME 0x00010
+
+/** An indication that you are interested in the @c last_author field */
+#define SVN_DIRENT_LAST_AUTHOR 0x00020
+
+/** A combination of all the dirent fields */
+#define SVN_DIRENT_ALL ~((apr_uint32_t ) 0)
+
+/** @} */
+
+/** A general subversion directory entry.
+ *
+ * @note To allow for extending the #svn_dirent_t structure in future
+ * releases, always use svn_dirent_create() to allocate the stucture.
+ *
+ * @since New in 1.6.
+ */
+typedef struct svn_dirent_t
+{
+ /** node kind */
+ svn_node_kind_t kind;
+
+ /** length of file text, or 0 for directories */
+ svn_filesize_t size;
+
+ /** does the node have props? */
+ svn_boolean_t has_props;
+
+ /** last rev in which this node changed */
+ svn_revnum_t created_rev;
+
+ /** time of created_rev (mod-time) */
+ apr_time_t time;
+
+ /** author of created_rev */
+ const char *last_author;
+
+ /* IMPORTANT: If you extend this struct, check svn_dirent_dup(). */
+} svn_dirent_t;
+
+/** Return a deep copy of @a dirent, allocated in @a pool.
+ *
+ * @since New in 1.4.
+ */
+svn_dirent_t *
+svn_dirent_dup(const svn_dirent_t *dirent,
+ apr_pool_t *pool);
+
+/**
+ * Create a new svn_dirent_t instance with all values initialized to their
+ * not-available values.
+ *
+ * @since New in 1.8.
+ */
+svn_dirent_t *
+svn_dirent_create(apr_pool_t *result_pool);
+
+
+/** Keyword substitution.
+ *
+ * All the keywords Subversion recognizes.
+ *
+ * Note that there is a better, more general proposal out there, which
+ * would take care of both internationalization issues and custom
+ * keywords (e.g., $NetBSD$). See
+ *
+ * @verbatim
+ http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=8921
+ =====
+ From: "Jonathan M. Manning" <jmanning@alisa-jon.net>
+ To: dev@subversion.tigris.org
+ Date: Fri, 14 Dec 2001 11:56:54 -0500
+ Message-ID: <87970000.1008349014@bdldevel.bl.bdx.com>
+ Subject: Re: keywords @endverbatim
+ *
+ * and Eric Gillespie's support of same:
+ *
+ * @verbatim
+ http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=8757
+ =====
+ From: "Eric Gillespie, Jr." <epg@pretzelnet.org>
+ To: dev@subversion.tigris.org
+ Date: Wed, 12 Dec 2001 09:48:42 -0500
+ Message-ID: <87k7vsebp1.fsf@vger.pretzelnet.org>
+ Subject: Re: Customizable Keywords @endverbatim
+ *
+ * However, it is considerably more complex than the scheme below.
+ * For now we're going with simplicity, hopefully the more general
+ * solution can be done post-1.0.
+ *
+ * @defgroup svn_types_keywords Keyword definitions
+ * @{
+ */
+
+/** The maximum size of an expanded or un-expanded keyword. */
+#define SVN_KEYWORD_MAX_LEN 255
+
+/** The most recent revision in which this file was changed. */
+#define SVN_KEYWORD_REVISION_LONG "LastChangedRevision"
+
+/** Short version of LastChangedRevision */
+#define SVN_KEYWORD_REVISION_SHORT "Rev"
+
+/** Medium version of LastChangedRevision, matching the one CVS uses.
+ * @since New in 1.1. */
+#define SVN_KEYWORD_REVISION_MEDIUM "Revision"
+
+/** The most recent date (repository time) when this file was changed. */
+#define SVN_KEYWORD_DATE_LONG "LastChangedDate"
+
+/** Short version of LastChangedDate */
+#define SVN_KEYWORD_DATE_SHORT "Date"
+
+/** Who most recently committed to this file. */
+#define SVN_KEYWORD_AUTHOR_LONG "LastChangedBy"
+
+/** Short version of LastChangedBy */
+#define SVN_KEYWORD_AUTHOR_SHORT "Author"
+
+/** The URL for the head revision of this file. */
+#define SVN_KEYWORD_URL_LONG "HeadURL"
+
+/** Short version of HeadURL */
+#define SVN_KEYWORD_URL_SHORT "URL"
+
+/** A compressed combination of the other four keywords. */
+#define SVN_KEYWORD_ID "Id"
+
+/** A full combination of the first four keywords.
+ * @since New in 1.6. */
+#define SVN_KEYWORD_HEADER "Header"
+
+/** @} */
+
+
+
+/** All information about a commit.
+ *
+ * @note Objects of this type should always be created using the
+ * svn_create_commit_info() function.
+ *
+ * @since New in 1.3.
+ */
+typedef struct svn_commit_info_t
+{
+ /** just-committed revision. */
+ svn_revnum_t revision;
+
+ /** server-side date of the commit. */
+ const char *date;
+
+ /** author of the commit. */
+ const char *author;
+
+ /** error message from post-commit hook, or NULL. */
+ const char *post_commit_err;
+
+ /** repository root, may be @c NULL if unknown.
+ @since New in 1.7. */
+ const char *repos_root;
+
+} svn_commit_info_t;
+
+/**
+ * Allocate an object of type #svn_commit_info_t in @a pool and
+ * return it.
+ *
+ * The @c revision field of the new struct is set to #SVN_INVALID_REVNUM.
+ * All other fields are initialized to @c NULL.
+ *
+ * @note Any object of the type #svn_commit_info_t should
+ * be created using this function.
+ * This is to provide for extending the svn_commit_info_t in
+ * the future.
+ *
+ * @since New in 1.3.
+ */
+svn_commit_info_t *
+svn_create_commit_info(apr_pool_t *pool);
+
+/**
+ * Return a deep copy @a src_commit_info allocated in @a pool.
+ *
+ * @since New in 1.4.
+ */
+svn_commit_info_t *
+svn_commit_info_dup(const svn_commit_info_t *src_commit_info,
+ apr_pool_t *pool);
+
+
+
+/**
+ * A structure to represent a path that changed for a log entry.
+ *
+ * @note To allow for extending the #svn_log_changed_path2_t structure in
+ * future releases, always use svn_log_changed_path2_create() to allocate
+ * the structure.
+ *
+ * @since New in 1.6.
+ */
+typedef struct svn_log_changed_path2_t
+{
+ /** 'A'dd, 'D'elete, 'R'eplace, 'M'odify */
+ char action;
+
+ /** Source path of copy (if any). */
+ const char *copyfrom_path;
+
+ /** Source revision of copy (if any). */
+ svn_revnum_t copyfrom_rev;
+
+ /** The type of the node, may be svn_node_unknown. */
+ svn_node_kind_t node_kind;
+
+ /** Is the text modified, may be svn_tristate_unknown.
+ * @since New in 1.7. */
+ svn_tristate_t text_modified;
+
+ /** Are properties modified, may be svn_tristate_unknown.
+ * @since New in 1.7. */
+ svn_tristate_t props_modified;
+
+ /* NOTE: Add new fields at the end to preserve binary compatibility.
+ Also, if you add fields here, you have to update
+ svn_log_changed_path2_dup(). */
+} svn_log_changed_path2_t;
+
+/**
+ * Returns an #svn_log_changed_path2_t, allocated in @a pool with all fields
+ * initialized to NULL, None or empty values.
+ *
+ * @note To allow for extending the #svn_log_changed_path2_t structure in
+ * future releases, this function should always be used to allocate the
+ * structure.
+ *
+ * @since New in 1.6.
+ */
+svn_log_changed_path2_t *
+svn_log_changed_path2_create(apr_pool_t *pool);
+
+/**
+ * Return a deep copy of @a changed_path, allocated in @a pool.
+ *
+ * @since New in 1.6.
+ */
+svn_log_changed_path2_t *
+svn_log_changed_path2_dup(const svn_log_changed_path2_t *changed_path,
+ apr_pool_t *pool);
+
+/**
+ * A structure to represent a path that changed for a log entry. Same as
+ * the first three fields of #svn_log_changed_path2_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+typedef struct svn_log_changed_path_t
+{
+ /** 'A'dd, 'D'elete, 'R'eplace, 'M'odify */
+ char action;
+
+ /** Source path of copy (if any). */
+ const char *copyfrom_path;
+
+ /** Source revision of copy (if any). */
+ svn_revnum_t copyfrom_rev;
+
+} svn_log_changed_path_t;
+
+/**
+ * Return a deep copy of @a changed_path, allocated in @a pool.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_log_changed_path_t *
+svn_log_changed_path_dup(const svn_log_changed_path_t *changed_path,
+ apr_pool_t *pool);
+
+/**
+ * A structure to represent all the information about a particular log entry.
+ *
+ * @note To allow for extending the #svn_log_entry_t structure in future
+ * releases, always use svn_log_entry_create() to allocate the structure.
+ *
+ * @since New in 1.5.
+ */
+typedef struct svn_log_entry_t
+{
+ /** A hash containing as keys every path committed in @a revision; the
+ * values are (#svn_log_changed_path_t *) structures.
+ *
+ * The subversion core libraries will always set this field to the same
+ * value as changed_paths2 for compatibility reasons.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+ apr_hash_t *changed_paths;
+
+ /** The revision of the commit. */
+ svn_revnum_t revision;
+
+ /** The hash of requested revision properties, which may be NULL if it
+ * would contain no revprops. Maps (const char *) property name to
+ * (svn_string_t *) property value. */
+ apr_hash_t *revprops;
+
+ /**
+ * Whether or not this message has children.
+ *
+ * When a log operation requests additional merge information, extra log
+ * entries may be returned as a result of this entry. The new entries, are
+ * considered children of the original entry, and will follow it. When
+ * the HAS_CHILDREN flag is set, the receiver should increment its stack
+ * depth, and wait until an entry is provided with SVN_INVALID_REVNUM which
+ * indicates the end of the children.
+ *
+ * For log operations which do not request additional merge information, the
+ * HAS_CHILDREN flag is always FALSE.
+ *
+ * For more information see:
+ * https://svn.apache.org/repos/asf/subversion/trunk/notes/merge-tracking/design.html#commutative-reporting
+ */
+ svn_boolean_t has_children;
+
+ /** A hash containing as keys every path committed in @a revision; the
+ * values are (#svn_log_changed_path2_t *) structures.
+ *
+ * If this value is not @c NULL, it MUST have the same value as
+ * changed_paths or svn_log_entry_dup() will not create an identical copy.
+ *
+ * The subversion core libraries will always set this field to the same
+ * value as changed_paths for compatibility with users assuming an older
+ * version.
+ *
+ * @note See http://svn.haxx.se/dev/archive-2010-08/0362.shtml for
+ * further explanation.
+ *
+ * @since New in 1.6.
+ */
+ apr_hash_t *changed_paths2;
+
+ /**
+ * Whether @a revision should be interpreted as non-inheritable in the
+ * same sense of #svn_merge_range_t.
+ *
+ * Only set when this #svn_log_entry_t instance is returned by the
+ * libsvn_client mergeinfo apis. Currently always FALSE when the
+ * #svn_log_entry_t instance is reported by the ra layer.
+ *
+ * @since New in 1.7.
+ */
+ svn_boolean_t non_inheritable;
+
+ /**
+ * Whether @a revision is a merged revision resulting from a reverse merge.
+ *
+ * @since New in 1.7.
+ */
+ svn_boolean_t subtractive_merge;
+
+ /* NOTE: Add new fields at the end to preserve binary compatibility.
+ Also, if you add fields here, you have to update
+ svn_log_entry_dup(). */
+} svn_log_entry_t;
+
+/**
+ * Returns an #svn_log_entry_t, allocated in @a pool with all fields
+ * initialized to NULL values.
+ *
+ * @note To allow for extending the #svn_log_entry_t structure in future
+ * releases, this function should always be used to allocate the structure.
+ *
+ * @since New in 1.5.
+ */
+svn_log_entry_t *
+svn_log_entry_create(apr_pool_t *pool);
+
+/** Return a deep copy of @a log_entry, allocated in @a pool.
+ *
+ * The resulting svn_log_entry_t has @c changed_paths set to the same
+ * value as @c changed_path2. @c changed_paths will be @c NULL if
+ * @c changed_paths2 was @c NULL.
+ *
+ * @since New in 1.6.
+ */
+svn_log_entry_t *
+svn_log_entry_dup(const svn_log_entry_t *log_entry, apr_pool_t *pool);
+
+/** The callback invoked by log message loopers, such as
+ * #svn_ra_plugin_t.get_log() and svn_repos_get_logs().
+ *
+ * This function is invoked once on each log message, in the order
+ * determined by the caller (see above-mentioned functions).
+ *
+ * @a baton is what you think it is, and @a log_entry contains relevant
+ * information for the log message. Any of @a log_entry->author,
+ * @a log_entry->date, or @a log_entry->message may be @c NULL.
+ *
+ * If @a log_entry->date is neither NULL nor the empty string, it was
+ * generated by svn_time_to_cstring() and can be converted to
+ * @c apr_time_t with svn_time_from_cstring().
+ *
+ * If @a log_entry->changed_paths is non-@c NULL, then it contains as keys
+ * every path committed in @a log_entry->revision; the values are
+ * (#svn_log_changed_path_t *) structures.
+ *
+ * If @a log_entry->has_children is @c TRUE, the message will be followed
+ * immediately by any number of merged revisions (child messages), which are
+ * terminated by an invocation with SVN_INVALID_REVNUM. This usage may
+ * be recursive.
+ *
+ * Use @a pool for temporary allocation. If the caller is iterating
+ * over log messages, invoking this receiver on each, we recommend the
+ * standard pool loop recipe: create a subpool, pass it as @a pool to
+ * each call, clear it after each iteration, destroy it after the loop
+ * is done. (For allocation that must last beyond the lifetime of a
+ * given receiver call, use a pool in @a baton.)
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_log_entry_receiver_t)(
+ void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool);
+
+/**
+ * Similar to #svn_log_entry_receiver_t, except this uses separate
+ * parameters for each part of the log entry.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+typedef svn_error_t *(*svn_log_message_receiver_t)(
+ void *baton,
+ apr_hash_t *changed_paths,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date, /* use svn_time_from_cstring() if need apr_time_t */
+ const char *message,
+ apr_pool_t *pool);
+
+
+
+/** Callback function type for commits.
+ *
+ * When a commit succeeds, an instance of this is invoked with the
+ * @a commit_info, along with the @a baton closure.
+ * @a pool can be used for temporary allocations.
+ *
+ * @since New in 1.4.
+ */
+typedef svn_error_t *(*svn_commit_callback2_t)(
+ const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool);
+
+/** Same as #svn_commit_callback2_t, but uses individual
+ * data elements instead of the #svn_commit_info_t structure
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+typedef svn_error_t *(*svn_commit_callback_t)(
+ svn_revnum_t new_revision,
+ const char *date,
+ const char *author,
+ void *baton);
+
+
+
+/** A buffer size that may be used when processing a stream of data.
+ *
+ * @note We don't use this constant any longer, since it is considered to be
+ * unnecessarily large.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API.
+ */
+#define SVN_STREAM_CHUNK_SIZE 102400
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+/*
+ * The maximum amount we (ideally) hold in memory at a time when
+ * processing a stream of data.
+ *
+ * For example, when copying data from one stream to another, do it in
+ * blocks of this size.
+ *
+ * NOTE: This is an internal macro, put here for convenience.
+ * No public API may depend on the particular value of this macro.
+ */
+#define SVN__STREAM_CHUNK_SIZE 16384
+#endif
+
+/** The maximum amount we can ever hold in memory. */
+/* FIXME: Should this be the same as SVN_STREAM_CHUNK_SIZE? */
+#define SVN_MAX_OBJECT_SIZE (((apr_size_t) -1) / 2)
+
+
+
+/* ### Note: despite being about mime-TYPES, these probably don't
+ * ### belong in svn_types.h. However, no other header is more
+ * ### appropriate, and didn't feel like creating svn_validate.h for
+ * ### so little.
+ */
+
+/** Validate @a mime_type.
+ *
+ * If @a mime_type does not contain a "/", or ends with non-alphanumeric
+ * data, return #SVN_ERR_BAD_MIME_TYPE, else return success.
+ *
+ * Use @a pool only to find error allocation.
+ *
+ * Goal: to match both "foo/bar" and "foo/bar; charset=blah", without
+ * being too strict about it, but to disallow mime types that have
+ * quotes, newlines, or other garbage on the end, such as might be
+ * unsafe in an HTTP header.
+ */
+svn_error_t *
+svn_mime_type_validate(const char *mime_type,
+ apr_pool_t *pool);
+
+/** Return FALSE iff @a mime_type is a textual type.
+ *
+ * All mime types that start with "text/" are textual, plus some special
+ * cases (for example, "image/x-xbitmap").
+ */
+svn_boolean_t
+svn_mime_type_is_binary(const char *mime_type);
+
+
+
+/** A user defined callback that subversion will call with a user defined
+ * baton to see if the current operation should be continued. If the operation
+ * should continue, the function should return #SVN_NO_ERROR, if not, it
+ * should return #SVN_ERR_CANCELLED.
+ */
+typedef svn_error_t *(*svn_cancel_func_t)(void *cancel_baton);
+
+
+
+/**
+ * A lock object, for client & server to share.
+ *
+ * A lock represents the exclusive right to add, delete, or modify a
+ * path. A lock is created in a repository, wholly controlled by the
+ * repository. A "lock-token" is the lock's UUID, and can be used to
+ * learn more about a lock's fields, and or/make use of the lock.
+ * Because a lock is immutable, a client is free to not only cache the
+ * lock-token, but the lock's fields too, for convenience.
+ *
+ * Note that the 'is_dav_comment' field is wholly ignored by every
+ * library except for mod_dav_svn. The field isn't even marshalled
+ * over the network to the client. Assuming lock structures are
+ * created with apr_pcalloc(), a default value of 0 is universally safe.
+ *
+ * @note in the current implementation, only files are lockable.
+ *
+ * @since New in 1.2.
+ */
+typedef struct svn_lock_t
+{
+ const char *path; /**< the path this lock applies to */
+ const char *token; /**< unique URI representing lock */
+ const char *owner; /**< the username which owns the lock */
+ const char *comment; /**< (optional) description of lock */
+ svn_boolean_t is_dav_comment; /**< was comment made by generic DAV client? */
+ apr_time_t creation_date; /**< when lock was made */
+ apr_time_t expiration_date; /**< (optional) when lock will expire;
+ If value is 0, lock will never expire. */
+} svn_lock_t;
+
+/**
+ * Returns an #svn_lock_t, allocated in @a pool with all fields initialized
+ * to NULL values.
+ *
+ * @note To allow for extending the #svn_lock_t structure in the future
+ * releases, this function should always be used to allocate the structure.
+ *
+ * @since New in 1.2.
+ */
+svn_lock_t *
+svn_lock_create(apr_pool_t *pool);
+
+/**
+ * Return a deep copy of @a lock, allocated in @a pool.
+ *
+ * @since New in 1.2.
+ */
+svn_lock_t *
+svn_lock_dup(const svn_lock_t *lock, apr_pool_t *pool);
+
+
+
+/**
+ * Return a formatted Universal Unique IDentifier (UUID) string.
+ *
+ * @since New in 1.4.
+ */
+const char *
+svn_uuid_generate(apr_pool_t *pool);
+
+
+
+/**
+ * Mergeinfo representing a merge of a range of revisions.
+ *
+ * @since New in 1.5
+ */
+typedef struct svn_merge_range_t
+{
+ /**
+ * If the 'start' field is less than the 'end' field then 'start' is
+ * exclusive and 'end' inclusive of the range described. This is termed
+ * a forward merge range. If 'start' is greater than 'end' then the
+ * opposite is true. This is termed a reverse merge range. If 'start'
+ * equals 'end' the meaning of the range is not defined.
+ */
+ svn_revnum_t start;
+ svn_revnum_t end;
+
+ /**
+ * Whether this merge range should be inherited by treewise
+ * descendants of the path to which the range applies. */
+ svn_boolean_t inheritable;
+} svn_merge_range_t;
+
+/**
+ * Return a copy of @a range, allocated in @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_merge_range_t *
+svn_merge_range_dup(const svn_merge_range_t *range, apr_pool_t *pool);
+
+/**
+ * Returns true if the changeset committed in revision @a rev is one
+ * of the changesets in the range @a range.
+ *
+ * @since New in 1.5.
+ */
+svn_boolean_t
+svn_merge_range_contains_rev(const svn_merge_range_t *range, svn_revnum_t rev);
+
+
+
+/** @defgroup node_location_seg_reporting Node location segment reporting.
+ * @{ */
+
+/**
+ * A representation of a segment of an object's version history with an
+ * emphasis on the object's location in the repository as of various
+ * revisions.
+ *
+ * @since New in 1.5.
+ */
+typedef struct svn_location_segment_t
+{
+ /** The beginning (oldest) and ending (youngest) revisions for this
+ segment, both inclusive. */
+ svn_revnum_t range_start;
+ svn_revnum_t range_end;
+
+ /** The absolute (sans leading slash) path for this segment. May be
+ NULL to indicate gaps in an object's history. */
+ const char *path;
+
+} svn_location_segment_t;
+
+/**
+ * A callback invoked by generators of #svn_location_segment_t
+ * objects, used to report information about a versioned object's
+ * history in terms of its location in the repository filesystem over
+ * time.
+ */
+typedef svn_error_t *(*svn_location_segment_receiver_t)(
+ svn_location_segment_t *segment,
+ void *baton,
+ apr_pool_t *pool);
+
+/**
+ * Return a deep copy of @a segment, allocated in @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_location_segment_t *
+svn_location_segment_dup(const svn_location_segment_t *segment,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+
+/** A line number, such as in a file or a stream.
+ *
+ * @since New in 1.7.
+ */
+typedef unsigned long svn_linenum_t;
+
+/** The maximum value of an svn_linenum_t.
+ *
+ * @since New in 1.7.
+ */
+#define SVN_LINENUM_MAX_VALUE ULONG_MAX
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+/*
+ * Everybody and their brother needs to deal with svn_error_t, the error
+ * codes, and whatever else. While they *should* go and include svn_error.h
+ * in order to do that... bah. Let's just help everybody out and include
+ * that header whenever somebody grabs svn_types.h.
+ *
+ * Note that we do this at the END of this header so that its contents
+ * are available to svn_error.h (our guards will prevent the circular
+ * include). We also need to do the include *outside* of the cplusplus
+ * guard.
+ */
+#include "svn_error.h"
+
+
+/*
+ * Subversion developers may want to use some additional debugging facilities
+ * while working on the code. We'll pull that in here, so individual source
+ * files don't have to include this header manually.
+ */
+#ifdef SVN_DEBUG
+#include "private/svn_debug.h"
+#endif
+
+
+#endif /* SVN_TYPES_H */
diff --git a/contrib/subversion/subversion/include/svn_user.h b/contrib/subversion/subversion/include/svn_user.h
new file mode 100644
index 0000000..65e2820
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_user.h
@@ -0,0 +1,56 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_user.h
+ * @brief Subversion's wrapper around APR's user information interface.
+ */
+
+#ifndef SVN_USER_H
+#define SVN_USER_H
+
+#include <apr_pools.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** Get the name of the current user, using @a pool for any necessary
+ * allocation, returning NULL on error.
+ *
+ * @since New in 1.4.
+ */
+const char *
+svn_user_get_name(apr_pool_t *pool);
+
+/** Get the path of the current user's home directory, using @a pool for
+ * any necessary allocation, returning NULL on error.
+ *
+ * @since New in 1.4.
+ */
+const char *
+svn_user_get_homedir(apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_USER_H */
diff --git a/contrib/subversion/subversion/include/svn_utf.h b/contrib/subversion/subversion/include/svn_utf.h
new file mode 100644
index 0000000..4a2c137
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_utf.h
@@ -0,0 +1,252 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_utf.h
+ * @brief UTF-8 conversion routines
+ *
+ * Whenever a conversion routine cannot convert to or from UTF-8, the
+ * error returned has code @c APR_EINVAL.
+ */
+
+
+
+#ifndef SVN_UTF_H
+#define SVN_UTF_H
+
+#include <apr_pools.h>
+#include <apr_xlate.h> /* for APR_*_CHARSET */
+
+#include "svn_types.h"
+#include "svn_string.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define SVN_APR_LOCALE_CHARSET APR_LOCALE_CHARSET
+#define SVN_APR_DEFAULT_CHARSET APR_DEFAULT_CHARSET
+
+/**
+ * Initialize the UTF-8 encoding/decoding routines.
+ * Allocate cached translation handles in a subpool of @a pool.
+ *
+ * If @a assume_native_utf8 is TRUE, the native character set is
+ * assumed to be UTF-8, i.e. conversion is a no-op. This is useful
+ * in contexts where the native character set is ASCII but UTF-8
+ * should be used regardless (e.g. for mod_dav_svn which runs within
+ * httpd and always uses the "C" locale).
+ *
+ * @note It is optional to call this function, but if it is used, no other
+ * svn function may be in use in other threads during the call of this
+ * function or when @a pool is cleared or destroyed.
+ * Initializing the UTF-8 routines will improve performance.
+ *
+ * @since New in 1.8.
+ */
+void
+svn_utf_initialize2(svn_boolean_t assume_native_utf8,
+ apr_pool_t *pool);
+
+/**
+ * Like svn_utf_initialize2() but without the ability to force the
+ * native encoding to UTF-8.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+void
+svn_utf_initialize(apr_pool_t *pool);
+
+/** Set @a *dest to a utf8-encoded stringbuf from native stringbuf @a src;
+ * allocate @a *dest in @a pool.
+ */
+svn_error_t *
+svn_utf_stringbuf_to_utf8(svn_stringbuf_t **dest,
+ const svn_stringbuf_t *src,
+ apr_pool_t *pool);
+
+
+/** Set @a *dest to a utf8-encoded string from native string @a src; allocate
+ * @a *dest in @a pool.
+ */
+svn_error_t *
+svn_utf_string_to_utf8(const svn_string_t **dest,
+ const svn_string_t *src,
+ apr_pool_t *pool);
+
+
+/** Set @a *dest to a utf8-encoded C string from native C string @a src;
+ * allocate @a *dest in @a pool.
+ */
+svn_error_t *
+svn_utf_cstring_to_utf8(const char **dest,
+ const char *src,
+ apr_pool_t *pool);
+
+
+/** Set @a *dest to a utf8 encoded C string from @a frompage encoded C
+ * string @a src; allocate @a *dest in @a pool.
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_utf_cstring_to_utf8_ex2(const char **dest,
+ const char *src,
+ const char *frompage,
+ apr_pool_t *pool);
+
+
+/** Like svn_utf_cstring_to_utf8_ex2() but with @a convset_key which is
+ * ignored.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_utf_cstring_to_utf8_ex(const char **dest,
+ const char *src,
+ const char *frompage,
+ const char *convset_key,
+ apr_pool_t *pool);
+
+
+/** Set @a *dest to a natively-encoded stringbuf from utf8 stringbuf @a src;
+ * allocate @a *dest in @a pool.
+ */
+svn_error_t *
+svn_utf_stringbuf_from_utf8(svn_stringbuf_t **dest,
+ const svn_stringbuf_t *src,
+ apr_pool_t *pool);
+
+
+/** Set @a *dest to a natively-encoded string from utf8 string @a src;
+ * allocate @a *dest in @a pool.
+ */
+svn_error_t *
+svn_utf_string_from_utf8(const svn_string_t **dest,
+ const svn_string_t *src,
+ apr_pool_t *pool);
+
+
+/** Set @a *dest to a natively-encoded C string from utf8 C string @a src;
+ * allocate @a *dest in @a pool.
+ */
+svn_error_t *
+svn_utf_cstring_from_utf8(const char **dest,
+ const char *src,
+ apr_pool_t *pool);
+
+
+/** Set @a *dest to a @a topage encoded C string from utf8 encoded C string
+ * @a src; allocate @a *dest in @a pool.
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_utf_cstring_from_utf8_ex2(const char **dest,
+ const char *src,
+ const char *topage,
+ apr_pool_t *pool);
+
+
+/** Like svn_utf_cstring_from_utf8_ex2() but with @a convset_key which is
+ * ignored.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_utf_cstring_from_utf8_ex(const char **dest,
+ const char *src,
+ const char *topage,
+ const char *convset_key,
+ apr_pool_t *pool);
+
+
+/** Return a fuzzily native-encoded C string from utf8 C string @a src,
+ * allocated in @a pool. A fuzzy recoding leaves all 7-bit ascii
+ * characters the same, and substitutes "?\\XXX" for others, where XXX
+ * is the unsigned decimal code for that character.
+ *
+ * This function cannot error; it is guaranteed to return something.
+ * First it will recode as described above and then attempt to convert
+ * the (new) 7-bit UTF-8 string to native encoding. If that fails, it
+ * will return the raw fuzzily recoded string, which may or may not be
+ * meaningful in the client's locale, but is (presumably) better than
+ * nothing.
+ *
+ * ### Notes:
+ *
+ * Improvement is possible, even imminent. The original problem was
+ * that if you converted a UTF-8 string (say, a log message) into a
+ * locale that couldn't represent all the characters, you'd just get a
+ * static placeholder saying "[unconvertible log message]". Then
+ * Justin Erenkrantz pointed out how on platforms that didn't support
+ * conversion at all, "svn log" would still fail completely when it
+ * encountered unconvertible data.
+ *
+ * Now for both cases, the caller can at least fall back on this
+ * function, which converts the message as best it can, substituting
+ * "?\\XXX" escape codes for the non-ascii characters.
+ *
+ * Ultimately, some callers may prefer the iconv "//TRANSLIT" option,
+ * so when we can detect that at configure time, things will change.
+ * Also, this should (?) be moved to apr/apu eventually.
+ *
+ * See http://subversion.tigris.org/issues/show_bug.cgi?id=807 for
+ * details.
+ */
+const char *
+svn_utf_cstring_from_utf8_fuzzy(const char *src,
+ apr_pool_t *pool);
+
+
+/** Set @a *dest to a natively-encoded C string from utf8 stringbuf @a src;
+ * allocate @a *dest in @a pool.
+ */
+svn_error_t *
+svn_utf_cstring_from_utf8_stringbuf(const char **dest,
+ const svn_stringbuf_t *src,
+ apr_pool_t *pool);
+
+
+/** Set @a *dest to a natively-encoded C string from utf8 string @a src;
+ * allocate @a *dest in @a pool.
+ */
+svn_error_t *
+svn_utf_cstring_from_utf8_string(const char **dest,
+ const svn_string_t *src,
+ apr_pool_t *pool);
+
+/** Return the display width of UTF-8-encoded C string @a cstr.
+ * If the string is not printable or invalid UTF-8, return -1.
+ *
+ * @since New in 1.8.
+ */
+int
+svn_utf_cstring_utf8_width(const char *cstr);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_UTF_H */
diff --git a/contrib/subversion/subversion/include/svn_version.h b/contrib/subversion/subversion/include/svn_version.h
new file mode 100644
index 0000000..f8ce7c3
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_version.h
@@ -0,0 +1,411 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_version.h
+ * @brief Version information.
+ */
+
+#ifndef SVN_VERSION_H
+#define SVN_VERSION_H
+
+/* Hack to prevent the resource compiler from including
+ apr_general.h. It doesn't resolve the include paths
+ correctly and blows up without this.
+ */
+#ifndef APR_STRINGIFY
+#include <apr_general.h>
+#endif
+#include <apr_tables.h>
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Symbols that define the version number. */
+
+/* Version numbers: <major>.<minor>.<micro>
+ *
+ * The version numbers in this file follow the rules established by:
+ *
+ * http://apr.apache.org/versioning.html
+ */
+
+/** Major version number.
+ *
+ * Modify when incompatible changes are made to published interfaces.
+ */
+#define SVN_VER_MAJOR 1
+
+/** Minor version number.
+ *
+ * Modify when new functionality is added or new interfaces are
+ * defined, but all changes are backward compatible.
+ */
+#define SVN_VER_MINOR 8
+
+/**
+ * Patch number.
+ *
+ * Modify for every released patch.
+ *
+ * @since New in 1.1.
+ */
+#define SVN_VER_PATCH 0
+
+
+/** @deprecated Provided for backward compatibility with the 1.0 API. */
+#define SVN_VER_MICRO SVN_VER_PATCH
+
+/** @deprecated Provided for backward compatibility with the 1.0 API. */
+#define SVN_VER_LIBRARY SVN_VER_MAJOR
+
+
+/** Version tag: a string describing the version.
+ *
+ * This tag remains " (dev build)" in the repository so that we can
+ * always see from "svn --version" that the software has been built
+ * from the repository rather than a "blessed" distribution.
+ *
+ * When rolling a tarball, we automatically replace this text with " (r1234)"
+ * (where 1234 is the last revision on the branch prior to the release)
+ * for final releases; in prereleases, it becomes " (Alpha 1)",
+ * " (Beta 1)", etc., as appropriate.
+ *
+ * Always change this at the same time as SVN_VER_NUMTAG.
+ */
+#define SVN_VER_TAG " (Release Candidate 3)"
+
+
+/** Number tag: a string describing the version.
+ *
+ * This tag is used to generate a version number string to identify
+ * the client and server in HTTP requests, for example. It must not
+ * contain any spaces. This value remains "-dev" in the repository.
+ *
+ * When rolling a tarball, we automatically replace this text with ""
+ * for final releases; in prereleases, it becomes "-alpha1, "-beta1",
+ * etc., as appropriate.
+ *
+ * Always change this at the same time as SVN_VER_TAG.
+ */
+#define SVN_VER_NUMTAG "-rc3"
+
+
+/** Revision number: The repository revision number of this release.
+ *
+ * This constant is used to generate the build number part of the Windows
+ * file version. Its value remains 0 in the repository.
+ *
+ * When rolling a tarball, we automatically replace it with what we
+ * guess to be the correct revision number.
+ */
+#define SVN_VER_REVISION 1490375
+
+
+/* Version strings composed from the above definitions. */
+
+/** Version number */
+#define SVN_VER_NUM APR_STRINGIFY(SVN_VER_MAJOR) \
+ "." APR_STRINGIFY(SVN_VER_MINOR) \
+ "." APR_STRINGIFY(SVN_VER_PATCH)
+
+/** Version number with tag (contains no whitespace) */
+#define SVN_VER_NUMBER SVN_VER_NUM SVN_VER_NUMTAG
+
+/** Complete version string */
+#define SVN_VERSION SVN_VER_NUMBER SVN_VER_TAG
+
+
+
+/* Version queries and compatibility checks */
+
+/**
+ * Version information. Each library contains a function called
+ * svn_<i>libname</i>_version() that returns a pointer to a statically
+ * allocated object of this type.
+ *
+ * @since New in 1.1.
+ */
+struct svn_version_t
+{
+ int major; /**< Major version number */
+ int minor; /**< Minor version number */
+ int patch; /**< Patch number */
+
+ /**
+ * The version tag (#SVN_VER_NUMTAG). Must always point to a
+ * statically allocated string.
+ */
+ const char *tag;
+};
+
+/**
+ * Define a static svn_version_t object.
+ *
+ * @since New in 1.1.
+ */
+#define SVN_VERSION_DEFINE(name) \
+ static const svn_version_t name = \
+ { \
+ SVN_VER_MAJOR, \
+ SVN_VER_MINOR, \
+ SVN_VER_PATCH, \
+ SVN_VER_NUMTAG \
+ } \
+
+/**
+ * Generate the implementation of a version query function.
+ *
+ * @since New in 1.1.
+ */
+#define SVN_VERSION_BODY \
+ SVN_VERSION_DEFINE(versioninfo); \
+ return &versioninfo
+
+/**
+ * Check library version compatibility. Return #TRUE if the client's
+ * version, given in @a my_version, is compatible with the library
+ * version, provided in @a lib_version.
+ *
+ * This function checks for version compatibility as per our
+ * guarantees, but requires an exact match when linking to an
+ * unreleased library. A development client is always compatible with
+ * a previous released library.
+ *
+ * @since New in 1.1.
+ */
+svn_boolean_t
+svn_ver_compatible(const svn_version_t *my_version,
+ const svn_version_t *lib_version);
+
+/**
+ * Check if @a my_version and @a lib_version encode the same version number.
+ *
+ * @since New in 1.2.
+ */
+svn_boolean_t
+svn_ver_equal(const svn_version_t *my_version,
+ const svn_version_t *lib_version);
+
+
+/**
+ * An entry in the compatibility checklist.
+ * @see svn_ver_check_list()
+ *
+ * @since New in 1.1.
+ */
+typedef struct svn_version_checklist_t
+{
+ const char *label; /**< Entry label */
+
+ /** Version query function for this entry */
+ const svn_version_t *(*version_query)(void);
+} svn_version_checklist_t;
+
+
+/**
+ * Perform a series of version compatibility checks. Checks if @a
+ * my_version is compatible with each entry in @a checklist. @a
+ * checklist must end with an entry whose label is @c NULL.
+ *
+ * @see svn_ver_compatible()
+ *
+ * @since New in 1.1.
+ */
+svn_error_t *
+svn_ver_check_list(const svn_version_t *my_version,
+ const svn_version_checklist_t *checklist);
+
+
+/**
+ * Type of function returning library version.
+ *
+ * @since New in 1.6.
+ */
+typedef const svn_version_t *(*svn_version_func_t)(void);
+
+
+/* libsvn_subr doesn't have an svn_subr header, so put the prototype here. */
+/**
+ * Get libsvn_subr version information.
+ *
+ * @since New in 1.1.
+ */
+const svn_version_t *
+svn_subr_version(void);
+
+
+/**
+ * Extended version information, including info about the running system.
+ *
+ * @since New in 1.8.
+ */
+typedef struct svn_version_extended_t svn_version_extended_t;
+
+/**
+ * Return version information for the running program. If @a verbose
+ * is #TRUE, collect extra information that may be expensive to
+ * retrieve (for example, the OS release name, list of shared
+ * libraries, etc.). Use @a pool for all allocations.
+ *
+ * @since New in 1.8.
+ */
+const svn_version_extended_t *
+svn_version_extended(svn_boolean_t verbose,
+ apr_pool_t *pool);
+
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The date when the libsvn_subr library was compiled, in the
+ * format defined by the C standard macro @c __DATE__.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_build_date(const svn_version_extended_t *ext_info);
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The time when the libsvn_subr library was compiled, in the
+ * format defined by the C standard macro @c __TIME__.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_build_time(const svn_version_extended_t *ext_info);
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The canonical host triplet (arch-vendor-osname) of the
+ * system where libsvn_subr was compiled.
+ *
+ * @note On Unix-like systems (includng Mac OS X), this string is the
+ * same as the output of the config.guess script.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_build_host(const svn_version_extended_t *ext_info);
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The localized copyright notice.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_copyright(const svn_version_extended_t *ext_info);
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The canonical host triplet (arch-vendor-osname) of the
+ * system where the current process is running.
+ *
+ * @note This string may not be the same as the output of config.guess
+ * on the same system.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_runtime_host(const svn_version_extended_t *ext_info);
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The "commercial" release name of the running operating
+ * system, if available. Not to be confused with, e.g., the output of
+ * "uname -v" or "uname -r". The returned value may be @c NULL.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_runtime_osname(const svn_version_extended_t *ext_info);
+
+/**
+ * Dependent library information.
+ * Describes the name and versions of known dependencies
+ * used by libsvn_subr.
+ *
+ * @since New in 1.8.
+ */
+typedef struct svn_version_ext_linked_lib_t
+{
+ const char *name; /**< Library name */
+ const char *compiled_version; /**< Compile-time version string */
+ const char *runtime_version; /**< Run-time version string (optional) */
+} svn_version_ext_linked_lib_t;
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return Array of svn_version_ext_linked_lib_t describing dependent
+ * libraries. The returned value may be @c NULL.
+ *
+ * @since New in 1.8.
+ */
+const apr_array_header_t *
+svn_version_ext_linked_libs(const svn_version_extended_t *ext_info);
+
+
+/**
+ * Loaded shared library information.
+ * Describes the name and, where available, version of the shared libraries
+ * loaded by the running program.
+ *
+ * @since New in 1.8.
+ */
+typedef struct svn_version_ext_loaded_lib_t
+{
+ const char *name; /**< Library name */
+ const char *version; /**< Library version (optional) */
+} svn_version_ext_loaded_lib_t;
+
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return Array of svn_version_ext_loaded_lib_t describing loaded
+ * shared libraries. The returned value may be @c NULL.
+ *
+ * @note On Mac OS X, the loaded frameworks, private frameworks and
+ * system libraries will not be listed.
+ *
+ * @since New in 1.8.
+ */
+const apr_array_header_t *
+svn_version_ext_loaded_libs(const svn_version_extended_t *ext_info);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_VERSION_H */
diff --git a/contrib/subversion/subversion/include/svn_wc.h b/contrib/subversion/subversion/include/svn_wc.h
new file mode 100644
index 0000000..2a9741d
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_wc.h
@@ -0,0 +1,8182 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_wc.h
+ * @brief Subversion's working copy library
+ *
+ * Requires:
+ * - A working copy
+ *
+ * Provides:
+ * - Ability to manipulate working copy's versioned data.
+ * - Ability to manipulate working copy's administrative files.
+ *
+ * Used By:
+ * - Clients.
+ *
+ * Notes:
+ * The 'path' parameters to most of the older functions can be
+ * absolute or relative (relative to current working
+ * directory). If there are any cases where they are
+ * relative to the path associated with the
+ * 'svn_wc_adm_access_t *adm_access' baton passed along with the
+ * path, those cases should be explicitly documented, and if they
+ * are not, please fix it. All new functions introduced since
+ * Subversion 1.7 require absolute paths, unless explicitly
+ * documented otherwise.
+ *
+ * Starting with Subversion 1.7, several arguments are re-ordered
+ * to be more consistent through the api. The common ordering used
+ * is:
+ *
+ * Firsts:
+ * - Output arguments
+ * Then:
+ * - Working copy context
+ * - Local abspath
+ * Followed by:
+ * - Function specific arguments
+ * - Specific callbacks with their batons
+ * Finally:
+ * - Generic callbacks (with baton) from directly functional to
+ * just observing:
+ * - svn_wc_conflict_resolver_func2_t
+ * - svn_wc_external_update_t
+ * - svn_cancel_func_t
+ * - svn_wc_notify_func2_t
+ * - Result pool
+ * - Scratch pool.
+ */
+
+#ifndef SVN_WC_H
+#define SVN_WC_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_tables.h>
+#include <apr_hash.h>
+#include <apr_time.h>
+#include <apr_file_io.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_checksum.h"
+#include "svn_io.h"
+#include "svn_delta.h" /* for svn_stream_t */
+#include "svn_opt.h"
+#include "svn_ra.h" /* for svn_ra_reporter_t type */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/**
+ * Get libsvn_wc version information.
+ *
+ * @since New in 1.1.
+ */
+const svn_version_t *
+svn_wc_version(void);
+
+
+/**
+ * @defgroup svn_wc Working copy management
+ * @{
+ */
+
+
+/** Flags for use with svn_wc_translated_file2() and svn_wc_translated_stream().
+ *
+ * @defgroup translate_flags Translation flags
+ * @{
+ */
+
+ /** Translate from Normal Form.
+ *
+ * The working copy text bases and repository files are stored
+ * in normal form. Some files' contents - or ever representation -
+ * differs between the working copy and the normal form. This flag
+ * specifies to take the latter form as input and transform it
+ * to the former.
+ *
+ * Either this flag or #SVN_WC_TRANSLATE_TO_NF should be specified,
+ * but not both.
+ */
+#define SVN_WC_TRANSLATE_FROM_NF 0x00000000
+
+ /** Translate to Normal Form.
+ *
+ * Either this flag or #SVN_WC_TRANSLATE_FROM_NF should be specified,
+ * but not both.
+ */
+#define SVN_WC_TRANSLATE_TO_NF 0x00000001
+
+ /** Force repair of eol styles, making sure the output file consistently
+ * contains the one eol style as specified by the svn:eol-style
+ * property and the required translation direction.
+ *
+ */
+#define SVN_WC_TRANSLATE_FORCE_EOL_REPAIR 0x00000002
+
+ /** Don't register a pool cleanup to delete the output file */
+#define SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP 0x00000004
+
+ /** Guarantee a new file is created on successful return.
+ * The default shortcuts translation by returning the path
+ * of the untranslated file when no translation is required.
+ */
+#define SVN_WC_TRANSLATE_FORCE_COPY 0x00000008
+
+ /** Use a non-wc-local tmp directory for creating output files,
+ * instead of in the working copy admin tmp area which is the default.
+ *
+ * @since New in 1.4.
+ */
+#define SVN_WC_TRANSLATE_USE_GLOBAL_TMP 0x00000010
+
+/** @} */
+
+
+/**
+ * @defgroup svn_wc_context Working copy context
+ * @{
+ */
+
+/** The context for all working copy interactions.
+ *
+ * This is the client-facing datastructure API consumers are required
+ * to create and use when interacting with a working copy. Multiple
+ * contexts can be created for the same working copy simultaneously, within
+ * the same process or different processes. Context mutexing will be handled
+ * internally by the working copy library.
+ *
+ * @note: #svn_wc_context_t should be passed by non-const pointer in all
+ * APIs, even for read-only operations, as it contains mutable data (caching,
+ * etc.).
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_wc_context_t svn_wc_context_t;
+
+/** Create a context for the working copy, and return it in @a *wc_ctx. This
+ * context is not associated with a particular working copy, but as operations
+ * are performed, will load the appropriate working copy information.
+ *
+ * @a config should hold the various configuration options that may apply to
+ * this context. It should live at least as long as @a result_pool. It may
+ * be @c NULL.
+ *
+ * The context will be allocated in @a result_pool, and will use @a
+ * result_pool for any internal allocations requiring the same longevity as
+ * the context. The context will be automatically destroyed, and its
+ * resources released, when @a result_pool is cleared, or it may be manually
+ * destroyed by invoking svn_wc_context_destroy().
+ *
+ * Use @a scratch_pool for temporary allocations. It may be cleared
+ * immediately upon returning from this function.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_context_create(svn_wc_context_t **wc_ctx,
+ const svn_config_t *config,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Destroy the working copy context described by @a wc_ctx, releasing any
+ * acquired resources.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_context_destroy(svn_wc_context_t *wc_ctx);
+
+
+/** @} */
+
+
+/**
+ * Locking/Opening/Closing using adm access batons.
+ *
+ * @defgroup svn_wc_adm_access Adm access batons (deprecated)
+ * @{
+ */
+
+/** Baton for access to a working copy administrative area.
+ *
+ * Access batons can be grouped into sets, by passing an existing open
+ * baton when opening a new baton. Given one baton in a set, other batons
+ * may be retrieved. This allows an entire hierarchy to be locked, and
+ * then the set of batons can be passed around by passing a single baton.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use a #svn_wc_context_t object to access the working
+ * copy.
+ */
+typedef struct svn_wc_adm_access_t svn_wc_adm_access_t;
+
+
+/**
+ * Return, in @a *adm_access, a pointer to a new access baton for the working
+ * copy administrative area associated with the directory @a path. If
+ * @a write_lock is TRUE the baton will include a write lock, otherwise the
+ * baton can only be used for read access. If @a path refers to a directory
+ * that is already write locked then the error #SVN_ERR_WC_LOCKED will be
+ * returned. The error #SVN_ERR_WC_NOT_DIRECTORY will be returned if
+ * @a path is not a versioned directory.
+ *
+ * If @a associated is an open access baton then @a adm_access will be added
+ * to the set containing @a associated. @a associated can be @c NULL, in
+ * which case @a adm_access is the start of a new set.
+ *
+ * @a levels_to_lock specifies how far to lock. Zero means just the specified
+ * directory. Any negative value means to lock the entire working copy
+ * directory hierarchy under @a path. A positive value indicates the number of
+ * levels of directories to lock -- 1 means just immediate subdirectories, 2
+ * means immediate subdirectories and their subdirectories, etc. All the
+ * access batons will become part of the set containing @a adm_access. This
+ * is an all-or-nothing option, if it is not possible to lock all the
+ * requested directories then an error will be returned and @a adm_access will
+ * be invalid, with the exception that subdirectories of @a path that are
+ * missing from the physical filesystem will not be locked and will not cause
+ * an error. The error #SVN_ERR_WC_LOCKED will be returned if a
+ * subdirectory of @a path is already write locked.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton to determine
+ * if the client has canceled the operation.
+ *
+ * @a pool will be used to allocate memory for the baton and any subsequently
+ * cached items. If @a adm_access has not been closed when the pool is
+ * cleared, it will be closed automatically at that point, and removed from
+ * its set. A baton closed in this way will not remove physical locks from
+ * the working copy if cleanup is required.
+ *
+ * The first baton in a set, with @a associated passed as @c NULL, must have
+ * the longest lifetime of all the batons in the set. This implies it must be
+ * the root of the hierarchy.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * Callers should use a #svn_wc_context_t object to access the working
+ * copy.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_open3(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_adm_open3(), but without cancellation support.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_open2(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_adm_open2(), but with @a tree_lock instead of
+ * @a levels_to_lock. @a levels_to_lock is set to -1 if @a tree_lock
+ * is @c TRUE, else 0.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_open(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ svn_boolean_t tree_lock,
+ apr_pool_t *pool);
+
+/**
+ * Checks the working copy to determine the node type of @a path. If
+ * @a path is a versioned directory then the behaviour is like that of
+ * svn_wc_adm_open3(), otherwise, if @a path is a file or does not
+ * exist, then the behaviour is like that of svn_wc_adm_open3() with
+ * @a path replaced by the parent directory of @a path. If @a path is
+ * an unversioned directory, the behaviour is also like that of
+ * svn_wc_adm_open3() on the parent, except that if the open fails,
+ * then the returned #SVN_ERR_WC_NOT_DIRECTORY error refers to @a path,
+ * not to @a path's parent.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * Callers should use a #svn_wc_context_t object to access the working
+ * copy.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_probe_open3(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_adm_probe_open3() without the cancel
+ * functionality.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_probe_open2(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_adm_probe_open2(), but with @a tree_lock instead of
+ * @a levels_to_lock. @a levels_to_lock is set to -1 if @a tree_lock
+ * is @c TRUE, else 0.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_probe_open(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ svn_boolean_t tree_lock,
+ apr_pool_t *pool);
+
+/**
+ * Open access batons for @a path and return in @a *anchor_access and
+ * @a *target the anchor and target required to drive an editor. Return
+ * in @a *target_access the access baton for the target, which may be the
+ * same as @a *anchor_access (in which case @a *target is the empty
+ * string, never NULL). All the access batons will be in the
+ * @a *anchor_access set.
+ *
+ * @a levels_to_lock determines the levels_to_lock used when opening
+ * @a path if @a path is a versioned directory, @a levels_to_lock is
+ * ignored otherwise. If @a write_lock is @c TRUE the access batons
+ * will hold write locks.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton to determine
+ * if the client has canceled the operation.
+ *
+ * This function is essentially a combination of svn_wc_adm_open3() and
+ * svn_wc_get_actual_target(), with the emphasis on reducing physical IO.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * Callers should use a #svn_wc_context_t object to access the working
+ * copy.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_open_anchor(svn_wc_adm_access_t **anchor_access,
+ svn_wc_adm_access_t **target_access,
+ const char **target,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/** Return, in @a *adm_access, a pointer to an existing access baton associated
+ * with @a path. @a path must be a directory that is locked as part of the
+ * set containing the @a associated access baton.
+ *
+ * If the requested access baton is marked as missing in, or is simply
+ * absent from, @a associated, return #SVN_ERR_WC_NOT_LOCKED.
+ *
+ * @a pool is used only for local processing, it is not used for the batons.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_retrieve(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ apr_pool_t *pool);
+
+/** Check the working copy to determine the node type of @a path. If
+ * @a path is a versioned directory then the behaviour is like that of
+ * svn_wc_adm_retrieve(), otherwise, if @a path is a file, an unversioned
+ * directory, or does not exist, then the behaviour is like that of
+ * svn_wc_adm_retrieve() with @a path replaced by the parent directory of
+ * @a path.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_probe_retrieve(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ apr_pool_t *pool);
+
+/**
+ * Try various ways to obtain an access baton for @a path.
+ *
+ * First, try to obtain @a *adm_access via svn_wc_adm_probe_retrieve(),
+ * but if this fails because @a associated can't give a baton for
+ * @a path or @a path's parent, then try svn_wc_adm_probe_open3(),
+ * this time passing @a write_lock and @a levels_to_lock. If there is
+ * still no access because @a path is not a versioned directory, then
+ * just set @a *adm_access to NULL and return success. But if it is
+ * because @a path is locked, then return the error #SVN_ERR_WC_LOCKED,
+ * and the effect on @a *adm_access is undefined. (Or if the attempt
+ * fails for any other reason, return the corresponding error, and the
+ * effect on @a *adm_access is also undefined.)
+ *
+ * If svn_wc_adm_probe_open3() succeeds, then add @a *adm_access to
+ * @a associated.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton to determine
+ * if the client has canceled the operation.
+ *
+ * Use @a pool only for local processing, not to allocate @a *adm_access.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_probe_try3(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_adm_probe_try3() without the cancel
+ * functionality.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_probe_try2(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_adm_probe_try2(), but with @a tree_lock instead of
+ * @a levels_to_lock. @a levels_to_lock is set to -1 if @a tree_lock
+ * is @c TRUE, else 0.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_probe_try(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ svn_boolean_t tree_lock,
+ apr_pool_t *pool);
+
+
+/** Give up the access baton @a adm_access, and its lock if any. This will
+ * recursively close any batons in the same set that are direct
+ * subdirectories of @a adm_access. Any physical locks will be removed from
+ * the working copy. Lock removal is unconditional, there is no check to
+ * determine if cleanup is required.
+ *
+ * Any temporary allocations are performed using @a scratch_pool.
+ *
+ * @since New in 1.6
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_close2(svn_wc_adm_access_t *adm_access,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_wc_adm_close2(), but with the internal pool of @a adm_access
+ * used for temporary allocations.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_adm_close(svn_wc_adm_access_t *adm_access);
+
+/** Return the path used to open the access baton @a adm_access.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+const char *
+svn_wc_adm_access_path(const svn_wc_adm_access_t *adm_access);
+
+/** Return the pool used by access baton @a adm_access.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+apr_pool_t *
+svn_wc_adm_access_pool(const svn_wc_adm_access_t *adm_access);
+
+/** Return @c TRUE is the access baton @a adm_access has a write lock,
+ * @c FALSE otherwise. Compared to svn_wc_locked() this is a cheap, fast
+ * function that doesn't access the filesystem.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * New code should use svn_wc_locked2() instead.
+ */
+SVN_DEPRECATED
+svn_boolean_t
+svn_wc_adm_locked(const svn_wc_adm_access_t *adm_access);
+
+/** @} */
+
+
+/** Gets up to two booleans indicating whether a path is locked for
+ * writing.
+ *
+ * @a locked_here is set to TRUE when a write lock on @a local_abspath
+ * exists in @a wc_ctx. @a locked is set to TRUE when there is a
+ * write_lock on @a local_abspath
+ *
+ * @a locked_here and/or @a locked can be NULL when you are not
+ * interested in a specific value
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_locked2(svn_boolean_t *locked_here,
+ svn_boolean_t *locked,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/** Set @a *locked to non-zero if @a path is locked, else set it to zero.
+ *
+ * New code should use svn_wc_locked2() instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_locked(svn_boolean_t *locked,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/**
+ * @defgroup svn_wc_adm_dir_name Name of Subversion's admin dir
+ * @{
+ */
+
+/** The default name of the administrative subdirectory.
+ *
+ * Ideally, this would be completely private to wc internals (in fact,
+ * it used to be that adm_subdir() in adm_files.c was the only function
+ * who knew the adm subdir's name). However, import wants to protect
+ * against importing administrative subdirs, so now the name is a
+ * matter of public record.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+#define SVN_WC_ADM_DIR_NAME ".svn"
+
+
+/**
+ * Return @c TRUE if @a name is the name of the WC administrative
+ * directory. Use @a pool for any temporary allocations. Only works
+ * with base directory names, not paths or URIs.
+ *
+ * For compatibility, the default name (.svn) will always be treated
+ * as an admin dir name, even if the working copy is actually using an
+ * alternative name.
+ *
+ * @since New in 1.3.
+ */
+svn_boolean_t
+svn_wc_is_adm_dir(const char *name, apr_pool_t *pool);
+
+
+/**
+ * Return the name of the administrative directory.
+ * Use @a pool for any temporary allocations.
+ *
+ * The returned pointer will refer to either a statically allocated
+ * string, or to a string allocated in @a pool.
+ *
+ * @since New in 1.3.
+ */
+const char *
+svn_wc_get_adm_dir(apr_pool_t *pool);
+
+
+/**
+ * Use @a name for the administrative directory in the working copy.
+ * Use @a pool for any temporary allocations.
+ *
+ * The list of valid names is limited. Currently only ".svn" (the
+ * default) and "_svn" are allowed.
+ *
+ * @note This function changes global (per-process) state and must be
+ * called in a single-threaded context during the initialization of a
+ * Subversion client.
+ *
+ * @since New in 1.3.
+ */
+svn_error_t *
+svn_wc_set_adm_dir(const char *name,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/**
+ * @defgroup svn_wc_externals Externals
+ * @{
+ */
+
+/** Callback for external definitions updates
+ *
+ * @a local_abspath is the path on which the external definition was found.
+ * @a old_val and @a new_val are the before and after values of the
+ * SVN_PROP_EXTERNALS property. @a depth is the ambient depth of the
+ * working copy directory at @a local_abspath.
+ *
+ * @since New in 1.7. */
+typedef svn_error_t *(*svn_wc_external_update_t)(void *baton,
+ const char *local_abspath,
+ const svn_string_t *old_val,
+ const svn_string_t *new_val,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool);
+
+/** Traversal information is information gathered by a working copy
+ * crawl or update. For example, the before and after values of the
+ * svn:externals property are important after an update, and since
+ * we're traversing the working tree anyway (a complete traversal
+ * during the initial crawl, and a traversal of changed paths during
+ * the checkout/update/switch), it makes sense to gather the
+ * property's values then instead of making a second pass.
+ *
+ * New code should use the svn_wc_external_update_t callback instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+typedef struct svn_wc_traversal_info_t svn_wc_traversal_info_t;
+
+
+/** Return a new, empty traversal info object, allocated in @a pool.
+ *
+ * New code should use the svn_wc_external_update_t callback instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_wc_traversal_info_t *
+svn_wc_init_traversal_info(apr_pool_t *pool);
+
+/** Set @a *externals_old and @a *externals_new to hash tables representing
+ * changes to values of the svn:externals property on directories
+ * traversed by @a traversal_info.
+ *
+ * @a traversal_info is obtained from svn_wc_init_traversal_info(), but is
+ * only useful after it has been passed through another function, such
+ * as svn_wc_crawl_revisions(), svn_wc_get_update_editor(),
+ * svn_wc_get_switch_editor(), etc.
+ *
+ * Each hash maps <tt>const char *</tt> directory names onto
+ * <tt>const char *</tt> values of the externals property for that directory.
+ * The dir names are full paths -- that is, anchor plus target, not target
+ * alone. The values are not parsed, they are simply copied raw, and are
+ * never NULL: directories that acquired or lost the property are
+ * simply omitted from the appropriate table. Directories whose value
+ * of the property did not change show the same value in each hash.
+ *
+ * The hashes, keys, and values have the same lifetime as @a traversal_info.
+ *
+ * New code should use the svn_wc_external_update_t callback instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+void
+svn_wc_edited_externals(apr_hash_t **externals_old,
+ apr_hash_t **externals_new,
+ svn_wc_traversal_info_t *traversal_info);
+
+
+/** Set @a *depths to a hash table mapping <tt>const char *</tt>
+ * directory names (directories traversed by @a traversal_info) to
+ * <tt>const char *</tt> values (the depths of those directories, as
+ * converted by svn_depth_to_word()).
+ *
+ * @a traversal_info is obtained from svn_wc_init_traversal_info(), but is
+ * only useful after it has been passed through another function, such
+ * as svn_wc_crawl_revisions(), svn_wc_get_update_editor(),
+ * svn_wc_get_switch_editor(), etc.
+ *
+ * The dir names are full paths -- that is, anchor plus target, not target
+ * alone. The values are not allocated, they are static constant strings.
+ * Although the values are never NULL, not all directories traversed
+ * are necessarily listed. For example, directories which did not
+ * have an svn:externals property set or modified are not included.
+ *
+ * The hashes and keys have the same lifetime as @a traversal_info.
+ *
+ * New code should use the svn_wc_external_update_t callback instead.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+void
+svn_wc_traversed_depths(apr_hash_t **depths,
+ svn_wc_traversal_info_t *traversal_info);
+
+
+/** One external item. This usually represents one line from an
+ * svn:externals description but with the path and URL
+ * canonicalized.
+ *
+ * In order to avoid backwards compatibility problems clients should use
+ * svn_wc_external_item2_create() to allocate and initialize this structure
+ * instead of doing so themselves.
+ *
+ * @since New in 1.5.
+ */
+typedef struct svn_wc_external_item2_t
+{
+ /** The name of the subdirectory into which this external should be
+ checked out. This is relative to the parent directory that
+ holds this external item. (Note that these structs are often
+ stored in hash tables with the target dirs as keys, so this
+ field will often be redundant.) */
+ const char *target_dir;
+
+ /** Where to check out from. This is possibly a relative external URL, as
+ * allowed in externals definitions, but without the peg revision. */
+ const char *url;
+
+ /** What revision to check out. The only valid kinds for this are
+ svn_opt_revision_number, svn_opt_revision_date, and
+ svn_opt_revision_head. */
+ svn_opt_revision_t revision;
+
+ /** The peg revision to use when checking out. The only valid kinds are
+ svn_opt_revision_number, svn_opt_revision_date, and
+ svn_opt_revision_head. */
+ svn_opt_revision_t peg_revision;
+
+} svn_wc_external_item2_t;
+
+/**
+ * Initialize an external item.
+ * Set @a *item to an external item object, allocated in @a pool.
+ *
+ * In order to avoid backwards compatibility problems, this function
+ * is used to initialize and allocate the #svn_wc_external_item2_t
+ * structure rather than doing so explicitly, as the size of this
+ * structure may change in the future.
+ *
+ * The current implementation never returns error, but callers should
+ * still check for error, for compatibility with future versions.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc_external_item2_create(svn_wc_external_item2_t **item,
+ apr_pool_t *pool);
+
+/** Same as svn_wc_external_item2_create() except the pointer to the new
+ * empty item is 'const' which is stupid since the next thing you need to do
+ * is fill in its fields.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * @since New in 1.5.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_external_item_create(const svn_wc_external_item2_t **item,
+ apr_pool_t *pool);
+
+/**
+ * Return a duplicate of @a item, allocated in @a pool. No part of the new
+ * item will be shared with @a item.
+ *
+ * @since New in 1.5.
+ */
+svn_wc_external_item2_t *
+svn_wc_external_item2_dup(const svn_wc_external_item2_t *item,
+ apr_pool_t *pool);
+
+/**
+ * One external item. Similar to svn_wc_external_item2_t, except
+ * @a revision is interpreted as both the operational revision and the
+ * peg revision.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+typedef struct svn_wc_external_item_t
+{
+ /** Same as #svn_wc_external_item2_t.target_dir */
+ const char *target_dir;
+
+ /** Same as #svn_wc_external_item2_t.url */
+ const char *url;
+
+ /** Same as #svn_wc_external_item2_t.revision */
+ svn_opt_revision_t revision;
+
+} svn_wc_external_item_t;
+
+/**
+ * Return a duplicate of @a item, allocated in @a pool. No part of the new
+ * item will be shared with @a item.
+ *
+ * @since New in 1.3.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_wc_external_item_t *
+svn_wc_external_item_dup(const svn_wc_external_item_t *item,
+ apr_pool_t *pool);
+
+/**
+ * If @a externals_p is non-NULL, set @a *externals_p to an array of
+ * #svn_wc_external_item2_t * objects based on @a desc.
+ *
+ * If the format of @a desc is invalid, don't touch @a *externals_p and
+ * return #SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION. Thus, if
+ * you just want to check the validity of an externals description,
+ * and don't care about the parsed result, pass NULL for @a externals_p.
+ *
+ * The format of @a desc is the same as for values of the directory
+ * property #SVN_PROP_EXTERNALS. Look there for more details.
+ *
+ * If @a canonicalize_url is @c TRUE, canonicalize the @a url member
+ * of those objects. If the @a url member refers to an absolute URL,
+ * it will be canonicalized as URL consistent with the way URLs are
+ * canonicalized throughout the Subversion API. If, however, the
+ * @a url member makes use of the recognized (SVN-specific) relative
+ * URL syntax for svn:externals, "canonicalization" is an ill-defined
+ * concept which may even result in munging the relative URL syntax
+ * beyond recognition. You've been warned.
+ *
+ * Allocate the table, keys, and values in @a pool.
+ *
+ * Use @a parent_directory only in constructing error strings.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
+ const char *parent_directory,
+ const char *desc,
+ svn_boolean_t canonicalize_url,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_parse_externals_description3() with @a
+ * canonicalize_url set to @c TRUE, but returns an array of
+ * #svn_wc_external_item_t * objects instead of
+ * #svn_wc_external_item2_t * objects
+ *
+ * @since New in 1.1.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_parse_externals_description2(apr_array_header_t **externals_p,
+ const char *parent_directory,
+ const char *desc,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_parse_externals_description2(), but returns the
+ * parsed externals in a hash instead of an array. This function
+ * should not be used, as storing the externals in a hash causes their
+ * order of evaluation to be not easily identifiable.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_parse_externals_description(apr_hash_t **externals_p,
+ const char *parent_directory,
+ const char *desc,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/**
+ * @defgroup svn_wc_notifications Notification callback handling
+ * @{
+ *
+ * In many cases, the WC library will scan a working copy and make
+ * changes. The caller usually wants to know when each of these changes
+ * has been made, so that it can display some kind of notification to
+ * the user.
+ *
+ * These notifications have a standard callback function type, which
+ * takes the path of the file that was affected, and a caller-
+ * supplied baton.
+ *
+ * @note The callback is a 'void' return -- this is a simple
+ * reporting mechanism, rather than an opportunity for the caller to
+ * alter the operation of the WC library.
+ *
+ * @note Some of the actions are used across several
+ * different Subversion commands. For example, the update actions are
+ * also used for checkouts, switches, and merges.
+ */
+
+/** The type of action occurring. */
+typedef enum svn_wc_notify_action_t
+{
+ /** Adding a path to revision control. */
+ svn_wc_notify_add = 0,
+
+ /** Copying a versioned path. */
+ svn_wc_notify_copy,
+
+ /** Deleting a versioned path. */
+ svn_wc_notify_delete,
+
+ /** Restoring a missing path from the pristine text-base. */
+ svn_wc_notify_restore,
+
+ /** Reverting a modified path. */
+ svn_wc_notify_revert,
+
+ /** A revert operation has failed. */
+ svn_wc_notify_failed_revert,
+
+ /** Resolving a conflict. */
+ svn_wc_notify_resolved,
+
+ /** Skipping a path. */
+ svn_wc_notify_skip,
+
+ /** Got a delete in an update. */
+ svn_wc_notify_update_delete,
+
+ /** Got an add in an update. */
+ svn_wc_notify_update_add,
+
+ /** Got any other action in an update. */
+ svn_wc_notify_update_update,
+
+ /** The last notification in an update (including updates of externals). */
+ svn_wc_notify_update_completed,
+
+ /** Updating an external module. */
+ svn_wc_notify_update_external,
+
+ /** The last notification in a status (including status on externals). */
+ svn_wc_notify_status_completed,
+
+ /** Running status on an external module. */
+ svn_wc_notify_status_external,
+
+ /** Committing a modification. */
+ svn_wc_notify_commit_modified,
+
+ /** Committing an addition. */
+ svn_wc_notify_commit_added,
+
+ /** Committing a deletion. */
+ svn_wc_notify_commit_deleted,
+
+ /** Committing a replacement. */
+ svn_wc_notify_commit_replaced,
+
+ /** Transmitting post-fix text-delta data for a file. */
+ svn_wc_notify_commit_postfix_txdelta,
+
+ /** Processed a single revision's blame. */
+ svn_wc_notify_blame_revision,
+
+ /** Locking a path. @since New in 1.2. */
+ svn_wc_notify_locked,
+
+ /** Unlocking a path. @since New in 1.2. */
+ svn_wc_notify_unlocked,
+
+ /** Failed to lock a path. @since New in 1.2. */
+ svn_wc_notify_failed_lock,
+
+ /** Failed to unlock a path. @since New in 1.2. */
+ svn_wc_notify_failed_unlock,
+
+ /** Tried adding a path that already exists. @since New in 1.5. */
+ svn_wc_notify_exists,
+
+ /** Changelist name set. @since New in 1.5. */
+ svn_wc_notify_changelist_set,
+
+ /** Changelist name cleared. @since New in 1.5. */
+ svn_wc_notify_changelist_clear,
+
+ /** Warn user that a path has moved from one changelist to another.
+ @since New in 1.5.
+ @deprecated As of 1.7, separate clear and set notifications are sent. */
+ svn_wc_notify_changelist_moved,
+
+ /** A merge operation (to path) has begun. See #svn_wc_notify_t.merge_range.
+ @since New in 1.5. */
+ svn_wc_notify_merge_begin,
+
+ /** A merge operation (to path) from a foreign repository has begun.
+ See #svn_wc_notify_t.merge_range. @since New in 1.5. */
+ svn_wc_notify_foreign_merge_begin,
+
+ /** Replace notification. @since New in 1.5. */
+ svn_wc_notify_update_replace,
+
+ /** Property added. @since New in 1.6. */
+ svn_wc_notify_property_added,
+
+ /** Property updated. @since New in 1.6. */
+ svn_wc_notify_property_modified,
+
+ /** Property deleted. @since New in 1.6. */
+ svn_wc_notify_property_deleted,
+
+ /** Nonexistent property deleted. @since New in 1.6. */
+ svn_wc_notify_property_deleted_nonexistent,
+
+ /** Revprop set. @since New in 1.6. */
+ svn_wc_notify_revprop_set,
+
+ /** Revprop deleted. @since New in 1.6. */
+ svn_wc_notify_revprop_deleted,
+
+ /** The last notification in a merge. @since New in 1.6. */
+ svn_wc_notify_merge_completed,
+
+ /** The path is a tree-conflict victim of the intended action (*not*
+ * a persistent tree-conflict from an earlier operation, but *this*
+ * operation caused the tree-conflict). @since New in 1.6. */
+ svn_wc_notify_tree_conflict,
+
+ /** The path is a subdirectory referenced in an externals definition
+ * which is unable to be operated on. @since New in 1.6. */
+ svn_wc_notify_failed_external,
+
+ /** Starting an update operation. @since New in 1.7. */
+ svn_wc_notify_update_started,
+
+ /** An update tried to add a file or directory at a path where
+ * a separate working copy was found. @since New in 1.7. */
+ svn_wc_notify_update_skip_obstruction,
+
+ /** An explicit update tried to update a file or directory that
+ * doesn't live in the repository and can't be brought in.
+ * @since New in 1.7. */
+ svn_wc_notify_update_skip_working_only,
+
+ /** An update tried to update a file or directory to which access could
+ * not be obtained. @since New in 1.7. */
+ svn_wc_notify_update_skip_access_denied,
+
+ /** An update operation removed an external working copy.
+ * @since New in 1.7. */
+ svn_wc_notify_update_external_removed,
+
+ /** A node below an existing node was added during update.
+ * @since New in 1.7. */
+ svn_wc_notify_update_shadowed_add,
+
+ /** A node below an existing node was updated during update.
+ * @since New in 1.7. */
+ svn_wc_notify_update_shadowed_update,
+
+ /** A node below an existing node was deleted during update.
+ * @since New in 1.7. */
+ svn_wc_notify_update_shadowed_delete,
+
+ /** The mergeinfo on path was updated. @since New in 1.7. */
+ svn_wc_notify_merge_record_info,
+
+ /** A working copy directory was upgraded to the latest format.
+ * @since New in 1.7. */
+ svn_wc_notify_upgraded_path,
+
+ /** Mergeinfo describing a merge was recorded.
+ * @since New in 1.7. */
+ svn_wc_notify_merge_record_info_begin,
+
+ /** Mergeinfo was removed due to elision.
+ * @since New in 1.7. */
+ svn_wc_notify_merge_elide_info,
+
+ /** A file in the working copy was patched.
+ * @since New in 1.7. */
+ svn_wc_notify_patch,
+
+ /** A hunk from a patch was applied.
+ * @since New in 1.7. */
+ svn_wc_notify_patch_applied_hunk,
+
+ /** A hunk from a patch was rejected.
+ * @since New in 1.7. */
+ svn_wc_notify_patch_rejected_hunk,
+
+ /** A hunk from a patch was found to already be applied.
+ * @since New in 1.7. */
+ svn_wc_notify_patch_hunk_already_applied,
+
+ /** Committing a non-overwriting copy (path is the target of the
+ * copy, not the source).
+ * @since New in 1.7. */
+ svn_wc_notify_commit_copied,
+
+ /** Committing an overwriting (replace) copy (path is the target of
+ * the copy, not the source).
+ * @since New in 1.7. */
+ svn_wc_notify_commit_copied_replaced,
+
+ /** The server has instructed the client to follow a URL
+ * redirection.
+ * @since New in 1.7. */
+ svn_wc_notify_url_redirect,
+
+ /** The operation was attempted on a path which doesn't exist.
+ * @since New in 1.7. */
+ svn_wc_notify_path_nonexistent,
+
+ /** Removing a path by excluding it.
+ * @since New in 1.7. */
+ svn_wc_notify_exclude,
+
+ /** Operation failed because the node remains in conflict
+ * @since New in 1.7. */
+ svn_wc_notify_failed_conflict,
+
+ /** Operation failed because an added node is missing
+ * @since New in 1.7. */
+ svn_wc_notify_failed_missing,
+
+ /** Operation failed because a node is out of date
+ * @since New in 1.7. */
+ svn_wc_notify_failed_out_of_date,
+
+ /** Operation failed because an added parent is not selected
+ * @since New in 1.7. */
+ svn_wc_notify_failed_no_parent,
+
+ /** Operation failed because a node is locked by another user and/or
+ * working copy. @since New in 1.7. */
+ svn_wc_notify_failed_locked,
+
+ /** Operation failed because the operation was forbidden by the server.
+ * @since New in 1.7. */
+ svn_wc_notify_failed_forbidden_by_server,
+
+ /** The operation skipped the path because it was conflicted.
+ * @since New in 1.7. */
+ svn_wc_notify_skip_conflicted,
+
+ /** Just the lock on a file was removed during update.
+ * @since New in 1.8. */
+ svn_wc_notify_update_broken_lock,
+
+ /** Operation failed because a node is obstructed.
+ * @since New in 1.8. */
+ svn_wc_notify_failed_obstruction,
+
+ /** Conflict resolver is starting.
+ * This can be used by clients to detect when to display conflict summary
+ * information, for example.
+ * @since New in 1.8. */
+ svn_wc_notify_conflict_resolver_starting,
+
+ /** Conflict resolver is done.
+ * This can be used by clients to detect when to display conflict summary
+ * information, for example.
+ * @since New in 1.8. */
+ svn_wc_notify_conflict_resolver_done,
+
+ /** The current operation left local changes of something that was deleted
+ * The changes are available on (and below) the notified path
+ * @since New in 1.8. */
+ svn_wc_notify_left_local_modifications,
+
+ /** A copy from a foreign repository has started
+ * @since New in 1.8. */
+ svn_wc_notify_foreign_copy_begin,
+
+ /** A move in the working copy has been broken, i.e. degraded into a
+ * copy + delete. The notified path is the move source (the deleted path).
+ * ### TODO: Provide path to move destination as well?
+ * @since New in 1.8. */
+ svn_wc_notify_move_broken
+
+} svn_wc_notify_action_t;
+
+
+/** The type of notification that is occurring. */
+typedef enum svn_wc_notify_state_t
+{
+ svn_wc_notify_state_inapplicable = 0,
+
+ /** Notifier doesn't know or isn't saying. */
+ svn_wc_notify_state_unknown,
+
+ /** The state did not change. */
+ svn_wc_notify_state_unchanged,
+
+ /** The item wasn't present. */
+ svn_wc_notify_state_missing,
+
+ /** An unversioned item obstructed work. */
+ svn_wc_notify_state_obstructed,
+
+ /** Pristine state was modified. */
+ svn_wc_notify_state_changed,
+
+ /** Modified state had mods merged in. */
+ svn_wc_notify_state_merged,
+
+ /** Modified state got conflicting mods. */
+ svn_wc_notify_state_conflicted,
+
+ /** The source to copy the file from is missing. */
+ svn_wc_notify_state_source_missing
+
+} svn_wc_notify_state_t;
+
+/**
+ * What happened to a lock during an operation.
+ *
+ * @since New in 1.2.
+ */
+typedef enum svn_wc_notify_lock_state_t
+{
+ svn_wc_notify_lock_state_inapplicable = 0,
+
+ svn_wc_notify_lock_state_unknown,
+
+ /** The lock wasn't changed. */
+ svn_wc_notify_lock_state_unchanged,
+
+ /** The item was locked. */
+ svn_wc_notify_lock_state_locked,
+
+ /** The item was unlocked. */
+ svn_wc_notify_lock_state_unlocked
+
+} svn_wc_notify_lock_state_t;
+
+/**
+ * Structure used in the #svn_wc_notify_func2_t function.
+ *
+ * @c kind, @c content_state, @c prop_state and @c lock_state are from
+ * after @c action, not before.
+ *
+ * @note If @c action is #svn_wc_notify_update_completed, then @c path has
+ * already been installed, so it is legitimate for an implementation of
+ * #svn_wc_notify_func2_t to examine @c path in the working copy.
+ *
+ * @note The purpose of the @c kind, @c mime_type, @c content_state, and
+ * @c prop_state fields is to provide "for free" information that an
+ * implementation is likely to want, and which it would otherwise be
+ * forced to deduce via expensive operations such as reading entries
+ * and properties. However, if the caller does not have this
+ * information, it will simply pass the corresponding `*_unknown'
+ * values, and it is up to the implementation how to handle that
+ * (i.e., whether to attempt deduction, or just to punt and
+ * give a less informative notification).
+ *
+ * @note Callers of notification functions should use svn_wc_create_notify()
+ * or svn_wc_create_notify_url() to create structures of this type to allow
+ * for extensibility.
+ *
+ * @since New in 1.2.
+ */
+typedef struct svn_wc_notify_t {
+
+ /** Path, either absolute or relative to the current working directory
+ * (i.e., not relative to an anchor). @c path is "." or another valid path
+ * value for compatibility reasons when the real target is a url that
+ * is available in @c url. */
+ const char *path;
+
+ /** Action that describes what happened to #svn_wc_notify_t.path. */
+ svn_wc_notify_action_t action;
+
+ /** Node kind of @c path. */
+ svn_node_kind_t kind;
+
+ /** If non-NULL, indicates the mime-type of @c path.
+ * It is always @c NULL for directories. */
+ const char *mime_type;
+
+ /** Points to the lock structure received from the repository when
+ * @c action is #svn_wc_notify_locked. For other actions, it is
+ * @c NULL. */
+ const svn_lock_t *lock;
+
+ /** Points to an error describing the reason for the failure when @c
+ * action is one of the following: #svn_wc_notify_failed_lock,
+ * #svn_wc_notify_failed_unlock, #svn_wc_notify_failed_external.
+ * Is @c NULL otherwise. */
+ svn_error_t *err;
+
+ /** The type of notification that is occurring about node content. */
+ svn_wc_notify_state_t content_state;
+
+ /** The type of notification that is occurring about node properties. */
+ svn_wc_notify_state_t prop_state;
+
+ /** Reflects the addition or removal of a lock token in the working copy. */
+ svn_wc_notify_lock_state_t lock_state;
+
+ /** When @c action is #svn_wc_notify_update_completed, target revision
+ * of the update, or #SVN_INVALID_REVNUM if not available; when @c
+ * action is #svn_wc_notify_blame_revision, processed revision; Since
+ * Subversion 1.7 when action is #svn_wc_notify_update_update or
+ * #svn_wc_notify_update_add, the target revision.
+ * In all other cases, it is #SVN_INVALID_REVNUM.
+ */
+ svn_revnum_t revision;
+
+ /** If @c action pertains to a changelist, this is the changelist name.
+ * In all other cases, it is @c NULL. @since New in 1.5 */
+ const char *changelist_name;
+
+ /** When @c action is #svn_wc_notify_merge_begin or
+ * #svn_wc_notify_foreign_merge_begin or
+ * #svn_wc_notify_merge_record_info_begin, and both the
+ * left and right sides of the merge are from the same URL. In all
+ * other cases, it is @c NULL. @since New in 1.5 */
+ svn_merge_range_t *merge_range;
+
+ /** Similar to @c path, but if non-NULL the notification is about a url.
+ * @since New in 1.6 */
+ const char *url;
+
+ /** If non-NULL, specifies an absolute path prefix that can be subtracted
+ * from the start of the absolute path in @c path or @c url. Its purpose
+ * is to allow notification to remove a common prefix from all the paths
+ * displayed for an operation. @since New in 1.6 */
+ const char *path_prefix;
+
+ /** If @c action relates to properties, specifies the name of the property.
+ * @since New in 1.6 */
+ const char *prop_name;
+
+ /** If @c action is #svn_wc_notify_blame_revision, contains a list of
+ * revision properties for the specified revision
+ * @since New in 1.6 */
+ apr_hash_t *rev_props;
+
+ /** If @c action is #svn_wc_notify_update_update or
+ * #svn_wc_notify_update_add, contains the revision before the update.
+ * In all other cases, it is #SVN_INVALID_REVNUM.
+ * @since New in 1.7 */
+ svn_revnum_t old_revision;
+
+ /** These fields are used by svn patch to identify the
+ * hunk the notification is for. They are line-based
+ * offsets and lengths parsed from the unidiff hunk header.
+ * @since New in 1.7. */
+ /* @{ */
+ svn_linenum_t hunk_original_start;
+ svn_linenum_t hunk_original_length;
+ svn_linenum_t hunk_modified_start;
+ svn_linenum_t hunk_modified_length;
+ /* @} */
+
+ /** The line at which a hunk was matched (and applied).
+ * @since New in 1.7. */
+ svn_linenum_t hunk_matched_line;
+
+ /** The fuzz factor the hunk was applied with.
+ * @since New in 1.7 */
+ svn_linenum_t hunk_fuzz;
+
+ /* NOTE: Add new fields at the end to preserve binary compatibility.
+ Also, if you add fields here, you have to update svn_wc_create_notify
+ and svn_wc_dup_notify. */
+} svn_wc_notify_t;
+
+/**
+ * Allocate an #svn_wc_notify_t structure in @a pool, initialize and return
+ * it.
+ *
+ * Set the @c path field of the created struct to @a path, and @c action to
+ * @a action. Set all other fields to their @c _unknown, @c NULL or
+ * invalid value, respectively. Make only a shallow copy of the pointer
+ * @a path.
+ *
+ * @since New in 1.2.
+ */
+svn_wc_notify_t *
+svn_wc_create_notify(const char *path,
+ svn_wc_notify_action_t action,
+ apr_pool_t *pool);
+
+/**
+ * Allocate an #svn_wc_notify_t structure in @a pool, initialize and return
+ * it.
+ *
+ * Set the @c url field of the created struct to @a url, @c path to "." and @c
+ * action to @a action. Set all other fields to their @c _unknown, @c NULL or
+ * invalid value, respectively. Make only a shallow copy of the pointer
+ * @a url.
+ *
+ * @since New in 1.6.
+ */
+svn_wc_notify_t *
+svn_wc_create_notify_url(const char *url,
+ svn_wc_notify_action_t action,
+ apr_pool_t *pool);
+
+/**
+ * Return a deep copy of @a notify, allocated in @a pool.
+ *
+ * @since New in 1.2.
+ */
+svn_wc_notify_t *
+svn_wc_dup_notify(const svn_wc_notify_t *notify,
+ apr_pool_t *pool);
+
+/**
+ * Notify the world that @a notify->action has happened to @a notify->path.
+ *
+ * Recommendation: callers of #svn_wc_notify_func2_t should avoid
+ * invoking it multiple times on the same path within a given
+ * operation, and implementations should not bother checking for such
+ * duplicate calls. For example, in an update, the caller should not
+ * invoke the notify func on receiving a prop change and then again
+ * on receiving a text change. Instead, wait until all changes have
+ * been received, and then invoke the notify func once (from within
+ * an #svn_delta_editor_t's close_file(), for example), passing
+ * the appropriate @a notify->content_state and @a notify->prop_state flags.
+ *
+ * @since New in 1.2.
+ */
+typedef void (*svn_wc_notify_func2_t)(void *baton,
+ const svn_wc_notify_t *notify,
+ apr_pool_t *pool);
+
+/**
+ * Similar to #svn_wc_notify_func2_t, but takes the information as arguments
+ * instead of struct fields.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+typedef void (*svn_wc_notify_func_t)(void *baton,
+ const char *path,
+ svn_wc_notify_action_t action,
+ svn_node_kind_t kind,
+ const char *mime_type,
+ svn_wc_notify_state_t content_state,
+ svn_wc_notify_state_t prop_state,
+ svn_revnum_t revision);
+
+/** @} */
+
+
+/**
+ * Interactive conflict handling
+ *
+ * @defgroup svn_wc_conflict Conflict callback functionality
+ * @{
+ *
+ * This API gives a Subversion client application the opportunity to
+ * define a callback that allows the user to resolve conflicts
+ * interactively during updates and merges.
+ *
+ * If a conflict is discovered, libsvn_wc invokes the callback with an
+ * #svn_wc_conflict_description_t. This structure describes the
+ * path in conflict, whether it's a text or property conflict, and may
+ * also present up to three files that can be used to resolve the
+ * conflict (perhaps by launching an editor or 3rd-party merging
+ * tool). The structure also provides a possible fourth file (@c
+ * merged_file) which, if not NULL, represents libsvn_wc's attempt to
+ * contextually merge the first three files. (Note that libsvn_wc
+ * will not attempt to merge a file that it believes is binary, and it
+ * will only attempt to merge property values it believes to be a
+ * series of multi-line text.)
+ *
+ * When the callback is finished interacting with the user, it
+ * responds by returning a #svn_wc_conflict_result_t. This
+ * structure indicates whether the user wants to postpone the conflict
+ * for later (allowing libsvn_wc to mark the path "conflicted" as
+ * usual), or whether the user wants libsvn_wc to use one of the four
+ * files as the "final" state for resolving the conflict immediately.
+ *
+ * Note that the callback is at liberty (and encouraged) to merge the
+ * three files itself. If it does so, it signals this to libsvn_wc by
+ * returning a choice of #svn_wc_conflict_choose_merged. To return
+ * the 'final' merged file to libsvn_wc, the callback has the option of
+ * either:
+ *
+ * - editing the original @c merged_file in-place
+ *
+ * or, if libsvn_wc never supplied a merged_file in the
+ * description structure (i.e. passed NULL for that field),
+ *
+ * - return the merged file in the #svn_wc_conflict_result_t.
+ *
+ */
+
+/** The type of action being attempted on an object.
+ *
+ * @since New in 1.5.
+ */
+typedef enum svn_wc_conflict_action_t
+{
+ svn_wc_conflict_action_edit, /**< attempting to change text or props */
+ svn_wc_conflict_action_add, /**< attempting to add object */
+ svn_wc_conflict_action_delete, /**< attempting to delete object */
+ svn_wc_conflict_action_replace /**< attempting to replace object,
+ @since New in 1.7 */
+} svn_wc_conflict_action_t;
+
+
+/** The pre-existing condition which is causing a state of conflict.
+ *
+ * @since New in 1.5.
+ */
+typedef enum svn_wc_conflict_reason_t
+{
+ /** Local edits are already present */
+ svn_wc_conflict_reason_edited,
+ /** Another object is in the way */
+ svn_wc_conflict_reason_obstructed,
+ /** Object is already schedule-delete */
+ svn_wc_conflict_reason_deleted,
+ /** Object is unknown or missing */
+ svn_wc_conflict_reason_missing,
+ /** Object is unversioned */
+ svn_wc_conflict_reason_unversioned,
+ /** Object is already added or schedule-add. @since New in 1.6. */
+ svn_wc_conflict_reason_added,
+ /** Object is already replaced. @since New in 1.7. */
+ svn_wc_conflict_reason_replaced,
+ /** Object is moved away. @since New in 1.8. */
+ svn_wc_conflict_reason_moved_away,
+ /** Object is moved here. @since New in 1.8. */
+ svn_wc_conflict_reason_moved_here
+
+} svn_wc_conflict_reason_t;
+
+
+/** The type of conflict being described by an
+ * #svn_wc_conflict_description2_t (see below).
+ *
+ * @since New in 1.5.
+ */
+typedef enum svn_wc_conflict_kind_t
+{
+ /** textual conflict (on a file) */
+ svn_wc_conflict_kind_text,
+ /** property conflict (on a file or dir) */
+ svn_wc_conflict_kind_property,
+ /** tree conflict (on a dir) @since New in 1.6. */
+ svn_wc_conflict_kind_tree
+} svn_wc_conflict_kind_t;
+
+
+/** The user operation that exposed a conflict.
+ *
+ * @since New in 1.6.
+ */
+typedef enum svn_wc_operation_t
+{
+ svn_wc_operation_none = 0,
+ svn_wc_operation_update,
+ svn_wc_operation_switch,
+ svn_wc_operation_merge
+
+} svn_wc_operation_t;
+
+
+/** Info about one of the conflicting versions of a node. Each field may
+ * have its respective null/invalid/unknown value if the corresponding
+ * information is not relevant or not available.
+ *
+ * @todo Consider making some or all of the info mandatory, to reduce
+ * complexity.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, to preserve binary compatibility, users
+ * should not directly allocate structures of this type.
+ *
+ * @see svn_wc_conflict_version_create()
+ * @see svn_wc_conflict_version_dup()
+ *
+ * @since New in 1.6.
+*/
+typedef struct svn_wc_conflict_version_t
+{
+ /** @name Where to find this node version in a repository */
+ /**@{*/
+
+ /** URL of repository root */
+ const char *repos_url;
+
+ /** revision at which to look up path_in_repos */
+ svn_revnum_t peg_rev;
+
+ /** path within repos; must not start with '/' */
+ /* ### should have been called repos_relpath, but we can't change this. */
+ const char *path_in_repos;
+ /** @} */
+
+ /** The node kind. Can be any kind, including 'none' or 'unknown'. */
+ svn_node_kind_t node_kind;
+
+ /** UUID of the repository (or NULL if unknown.)
+ * @since New in 1.8. */
+ const char *repos_uuid;
+
+ /* @todo Add metadata about a local copy of the node, if and when
+ * we store one. */
+
+ /* Remember to update svn_wc_conflict_version_create() and
+ * svn_wc_conflict_version_dup() in case you add fields to this struct. */
+} svn_wc_conflict_version_t;
+
+/**
+ * Allocate an #svn_wc_conflict_version_t structure in @a pool,
+ * initialize to contain a conflict origin, and return it.
+ *
+ * Set the @c repos_url field of the created struct to @a repos_root_url,
+ * the @c path_in_repos field to @a repos_relpath, the @c peg_rev field to
+ * @a revision and the @c node_kind to @a kind. Make only shallow
+ * copies of the pointer arguments.
+ *
+ * @a repos_root_url, @a repos_relpath and @a revision must be valid,
+ * non-null values. @a repos_uuid should be a valid UUID, but can be
+ * NULL if unknown. @a kind can be any kind, even 'none' or 'unknown'.
+ *
+ * @since New in 1.8.
+ */
+svn_wc_conflict_version_t *
+svn_wc_conflict_version_create2(const char *repos_root_url,
+ const char *repos_uuid,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool);
+
+/** Similar to svn_wc_conflict_version_create2(), but doesn't set all
+ * required values.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_wc_conflict_version_t *
+svn_wc_conflict_version_create(const char *repos_url,
+ const char *path_in_repos,
+ svn_revnum_t peg_rev,
+ svn_node_kind_t node_kind,
+ apr_pool_t *pool);
+
+/** Return a duplicate of @a version, allocated in @a pool.
+ * No part of the new version will be shared with @a version.
+ *
+ * @since New in 1.6.
+ */
+svn_wc_conflict_version_t *
+svn_wc_conflict_version_dup(const svn_wc_conflict_version_t *version,
+ apr_pool_t *pool);
+
+/** A struct that describes a conflict that has occurred in the
+ * working copy.
+ *
+ * The conflict described by this structure is one of:
+ * - a conflict on the content of the file node @a local_abspath
+ * - a conflict on the property @a property_name of @a local_abspath
+ * - a tree conflict, of which @a local_abspath is the victim
+ * Be aware that the victim of a tree conflict can be a non-existent node.
+ * The three kinds of conflict are distinguished by @a kind.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, to preserve binary compatibility, users
+ * should not directly allocate structures of this type but should use
+ * svn_wc_conflict_description_create_text2() or
+ * svn_wc_conflict_description_create_prop2() or
+ * svn_wc_conflict_description_create_tree2() instead.
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_wc_conflict_description2_t
+{
+ /** The path that is in conflict (for a tree conflict, it is the victim) */
+ const char *local_abspath;
+
+ /** The node type of the path being operated on (for a tree conflict,
+ * ### which version?) */
+ svn_node_kind_t node_kind;
+
+ /** What sort of conflict are we describing? */
+ svn_wc_conflict_kind_t kind;
+
+ /** The name of the property whose conflict is being described.
+ * (Only if @a kind is 'property'; else undefined.) */
+ const char *property_name;
+
+ /** Whether svn thinks ('my' version of) @c path is a 'binary' file.
+ * (Only if @c kind is 'text', else undefined.) */
+ svn_boolean_t is_binary;
+
+ /** The svn:mime-type property of ('my' version of) @c path, if available,
+ * else NULL.
+ * (Only if @c kind is 'text', else undefined.) */
+ const char *mime_type;
+
+ /** The action being attempted on the conflicted node or property.
+ * (When @c kind is 'text', this action must be 'edit'.) */
+ svn_wc_conflict_action_t action;
+
+ /** The state of the target node or property, relative to its merge-left
+ * source, that is the reason for the conflict.
+ * (When @c kind is 'text', this reason must be 'edited'.) */
+ svn_wc_conflict_reason_t reason;
+
+ /** If this is text-conflict and involves the merging of two files
+ * descended from a common ancestor, here are the paths of up to
+ * four fulltext files that can be used to interactively resolve the
+ * conflict.
+ *
+ * @a base_abspath, @a their_abspath and @a my_abspath are absolute
+ * paths.
+ *
+ * ### Is @a merged_file relative to some directory, or absolute?
+ *
+ * All four files will be in repository-normal form -- LF
+ * line endings and contracted keywords. (If any of these files are
+ * not available, they default to NULL.)
+ *
+ * On the other hand, if this is a property-conflict, then these
+ * paths represent temporary files that contain the three different
+ * property-values in conflict. The fourth path (@c merged_file)
+ * may or may not be NULL; if set, it represents libsvn_wc's
+ * attempt to merge the property values together. (Remember that
+ * property values are technically binary values, and thus can't
+ * always be merged.)
+ */
+ const char *base_abspath; /* common ancestor of the two files being merged */
+
+ /** their version of the file */
+ /* ### BH: For properties this field contains the reference to
+ the property rejection (.prej) file */
+ const char *their_abspath;
+
+ /** my locally-edited version of the file */
+ const char *my_abspath;
+
+ /** merged version; may contain conflict markers */
+ const char *merged_file;
+
+ /** The operation that exposed the conflict.
+ * Used only for tree conflicts.
+ */
+ svn_wc_operation_t operation;
+
+ /** Info on the "merge-left source" or "older" version of incoming change. */
+ const svn_wc_conflict_version_t *src_left_version;
+
+ /** Info on the "merge-right source" or "their" version of incoming change. */
+ const svn_wc_conflict_version_t *src_right_version;
+
+ /* Remember to adjust svn_wc__conflict_description2_dup()
+ * if you add new fields to this struct. */
+} svn_wc_conflict_description2_t;
+
+
+/** Similar to #svn_wc_conflict_description2_t, but with relative paths and
+ * adm_access batons. Passed to #svn_wc_conflict_resolver_func_t.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+typedef struct svn_wc_conflict_description_t
+{
+ /** The path that is in conflict (for a tree conflict, it is the victim) */
+ const char *path;
+
+ /** The node type of the path being operated on (for a tree conflict,
+ * ### which version?) */
+ svn_node_kind_t node_kind;
+
+ /** What sort of conflict are we describing? */
+ svn_wc_conflict_kind_t kind;
+
+ /** The name of the property whose conflict is being described.
+ * (Only if @a kind is 'property'; else undefined.) */
+ const char *property_name;
+
+ /** Whether svn thinks ('my' version of) @c path is a 'binary' file.
+ * (Only if @c kind is 'text', else undefined.) */
+ svn_boolean_t is_binary;
+
+ /** The svn:mime-type property of ('my' version of) @c path, if available,
+ * else NULL.
+ * (Only if @c kind is 'text', else undefined.) */
+ const char *mime_type;
+
+ /** If not NULL, an open working copy access baton to either the
+ * path itself (if @c path is a directory), or to the parent
+ * directory (if @c path is a file.)
+ * For a tree conflict, this will always be an access baton
+ * to the parent directory of the path, even if the path is
+ * a directory. */
+ svn_wc_adm_access_t *access;
+
+ /** The action being attempted on the conflicted node or property.
+ * (When @c kind is 'text', this action must be 'edit'.) */
+ svn_wc_conflict_action_t action;
+
+ /** The state of the target node or property, relative to its merge-left
+ * source, that is the reason for the conflict.
+ * (When @c kind is 'text', this reason must be 'edited'.) */
+ svn_wc_conflict_reason_t reason;
+
+ /** If this is text-conflict and involves the merging of two files
+ * descended from a common ancestor, here are the paths of up to
+ * four fulltext files that can be used to interactively resolve the
+ * conflict. All four files will be in repository-normal form -- LF
+ * line endings and contracted keywords. (If any of these files are
+ * not available, they default to NULL.)
+ *
+ * On the other hand, if this is a property-conflict, then these
+ * paths represent temporary files that contain the three different
+ * property-values in conflict. The fourth path (@c merged_file)
+ * may or may not be NULL; if set, it represents libsvn_wc's
+ * attempt to merge the property values together. (Remember that
+ * property values are technically binary values, and thus can't
+ * always be merged.)
+ */
+ const char *base_file; /* common ancestor of the two files being merged */
+
+ /** their version of the file */
+ const char *their_file;
+
+ /** my locally-edited version of the file */
+ const char *my_file;
+
+ /** merged version; may contain conflict markers */
+ const char *merged_file;
+
+ /** The operation that exposed the conflict.
+ * Used only for tree conflicts.
+ *
+ * @since New in 1.6.
+ */
+ svn_wc_operation_t operation;
+
+ /** Info on the "merge-left source" or "older" version of incoming change.
+ * @since New in 1.6. */
+ svn_wc_conflict_version_t *src_left_version;
+
+ /** Info on the "merge-right source" or "their" version of incoming change.
+ * @since New in 1.6. */
+ svn_wc_conflict_version_t *src_right_version;
+
+} svn_wc_conflict_description_t;
+
+/**
+ * Allocate an #svn_wc_conflict_description_t structure in @a result_pool,
+ * initialize to represent a text conflict, and return it.
+ *
+ * Set the @c local_abspath field of the created struct to @a local_abspath
+ * (which must be an absolute path), the @c kind field to
+ * #svn_wc_conflict_kind_text, the @c node_kind to #svn_node_file,
+ * the @c action to #svn_wc_conflict_action_edit, and the @c reason to
+ * #svn_wc_conflict_reason_edited.
+ *
+ * @note It is the caller's responsibility to set the other required fields
+ * (such as the four file names and @c mime_type and @c is_binary).
+ *
+ * @since New in 1.7.
+ */
+svn_wc_conflict_description2_t *
+svn_wc_conflict_description_create_text2(const char *local_abspath,
+ apr_pool_t *result_pool);
+
+
+/** Similar to svn_wc_conflict_description_create_text2(), but returns
+ * a #svn_wc_conflict_description_t *.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_wc_conflict_description_t *
+svn_wc_conflict_description_create_text(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+/**
+ * Allocate an #svn_wc_conflict_description_t structure in @a result_pool,
+ * initialize to represent a property conflict, and return it.
+ *
+ * Set the @c local_abspath field of the created struct to @a local_abspath
+ * (which must be an absolute path), the @c kind field
+ * to #svn_wc_conflict_kind_property, the @c node_kind to @a node_kind, and
+ * the @c property_name to @a property_name.
+ *
+ * @note: It is the caller's responsibility to set the other required fields
+ * (such as the four file names and @c action and @c reason).
+ *
+ * @since New in 1.7.
+ */
+svn_wc_conflict_description2_t *
+svn_wc_conflict_description_create_prop2(const char *local_abspath,
+ svn_node_kind_t node_kind,
+ const char *property_name,
+ apr_pool_t *result_pool);
+
+/** Similar to svn_wc_conflict_descriptor_create_prop(), but returns
+ * a #svn_wc_conflict_description_t *.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_wc_conflict_description_t *
+svn_wc_conflict_description_create_prop(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_node_kind_t node_kind,
+ const char *property_name,
+ apr_pool_t *pool);
+
+/**
+ * Allocate an #svn_wc_conflict_description_t structure in @a pool,
+ * initialize to represent a tree conflict, and return it.
+ *
+ * Set the @c local_abspath field of the created struct to @a local_abspath
+ * (which must be an absolute path), the @c kind field to
+ * #svn_wc_conflict_kind_tree, the @c node_kind to @a node_kind, the @c
+ * operation to @a operation, the @c src_left_version field to
+ * @a src_left_version, and the @c src_right_version field to
+ * @a src_right_version.
+ *
+ * @note: It is the caller's responsibility to set the other required fields
+ * (such as the four file names and @c action and @c reason).
+ *
+ * @since New in 1.7.
+ */
+svn_wc_conflict_description2_t *
+svn_wc_conflict_description_create_tree2(
+ const char *local_abspath,
+ svn_node_kind_t node_kind,
+ svn_wc_operation_t operation,
+ const svn_wc_conflict_version_t *src_left_version,
+ const svn_wc_conflict_version_t *src_right_version,
+ apr_pool_t *result_pool);
+
+
+/** Similar to svn_wc_conflict_description_create_tree(), but returns
+ * a #svn_wc_conflict_description_t *.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_wc_conflict_description_t *
+svn_wc_conflict_description_create_tree(
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_node_kind_t node_kind,
+ svn_wc_operation_t operation,
+ /* non-const */ svn_wc_conflict_version_t *src_left_version,
+ /* non-const */ svn_wc_conflict_version_t *src_right_version,
+ apr_pool_t *pool);
+
+
+/** Return a duplicate of @a conflict, allocated in @a result_pool.
+ * A deep copy of all members will be made.
+ *
+ * @since New in 1.7.
+ */
+svn_wc_conflict_description2_t *
+svn_wc__conflict_description2_dup(
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *result_pool);
+
+
+/** The way in which the conflict callback chooses a course of action.
+ *
+ * @since New in 1.5.
+ */
+typedef enum svn_wc_conflict_choice_t
+{
+ /** Don't resolve the conflict now. Let libsvn_wc mark the path
+ 'conflicted', so user can run 'svn resolved' later. */
+ svn_wc_conflict_choose_postpone,
+
+ /** If there were files to choose from, select one as a way of
+ resolving the conflict here and now. libsvn_wc will then do the
+ work of "installing" the chosen file.
+ */
+ svn_wc_conflict_choose_base, /**< original version */
+ svn_wc_conflict_choose_theirs_full, /**< incoming version */
+ svn_wc_conflict_choose_mine_full, /**< own version */
+ svn_wc_conflict_choose_theirs_conflict, /**< incoming (for conflicted hunks) */
+ svn_wc_conflict_choose_mine_conflict, /**< own (for conflicted hunks) */
+ svn_wc_conflict_choose_merged, /**< merged version */
+
+ /* @since New in 1.8. */
+ svn_wc_conflict_choose_unspecified /**< undecided */
+
+} svn_wc_conflict_choice_t;
+
+
+/** The final result returned by #svn_wc_conflict_resolver_func_t.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, to preserve binary compatibility, users
+ * should not directly allocate structures of this type. Instead,
+ * construct this structure using svn_wc_create_conflict_result()
+ * below.
+ *
+ * @since New in 1.5.
+ */
+typedef struct svn_wc_conflict_result_t
+{
+ /** A choice to either delay the conflict resolution or select a
+ particular file to resolve the conflict. */
+ svn_wc_conflict_choice_t choice;
+
+ /** If not NULL, this is a path to a file which contains the client's
+ (or more likely, the user's) merging of the three values in
+ conflict. libsvn_wc accepts this file if (and only if) @c choice
+ is set to #svn_wc_conflict_choose_merged.*/
+ const char *merged_file;
+
+ /** If true, save a backup copy of merged_file (or the original
+ merged_file from the conflict description, if merged_file is
+ NULL) in the user's working copy. */
+ svn_boolean_t save_merged;
+
+} svn_wc_conflict_result_t;
+
+
+/**
+ * Allocate an #svn_wc_conflict_result_t structure in @a pool,
+ * initialize and return it.
+ *
+ * Set the @c choice field of the structure to @a choice, and @c
+ * merged_file to @a merged_file. Set all other fields to their @c
+ * _unknown, @c NULL or invalid value, respectively. Make only a shallow
+ * copy of the pointer argument @a merged_file.
+ *
+ * @since New in 1.5.
+ */
+svn_wc_conflict_result_t *
+svn_wc_create_conflict_result(svn_wc_conflict_choice_t choice,
+ const char *merged_file,
+ apr_pool_t *pool);
+
+
+/** A callback used in merge, update and switch for resolving conflicts
+ * during the application of a tree delta to a working copy.
+ *
+ * @a description describes the exact nature of the conflict, and
+ * provides information to help resolve it. @a baton is a closure
+ * object; it should be provided by the implementation, and passed by
+ * the caller. When finished, the callback signals its resolution by
+ * returning a structure in @a *result, which should be allocated in
+ * @a result_pool. (See #svn_wc_conflict_result_t.) @a scratch_pool
+ * should be used for any temporary allocations.
+ *
+ * The values #svn_wc_conflict_choose_mine_conflict and
+ * #svn_wc_conflict_choose_theirs_conflict are not legal for conflicts
+ * in binary files or binary properties.
+ *
+ * Implementations of this callback are free to present the conflict
+ * using any user interface. This may include simple contextual
+ * conflicts in a file's text or properties, or more complex
+ * 'tree'-based conflicts related to obstructed additions, deletions,
+ * and edits. The callback implementation is free to decide which
+ * sorts of conflicts to handle; it's also free to decide which types
+ * of conflicts are automatically resolvable and which require user
+ * interaction.
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_wc_conflict_resolver_func2_t)(
+ svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *description,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Similar to #svn_wc_conflict_resolver_func2_t, but using
+ * #svn_wc_conflict_description_t instead of
+ * #svn_wc_conflict_description2_t
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+typedef svn_error_t *(*svn_wc_conflict_resolver_func_t)(
+ svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description_t *description,
+ void *baton,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+
+/**
+ * A callback vtable invoked by our diff-editors, as they receive diffs
+ * from the server. 'svn diff' and 'svn merge' implement their own versions
+ * of this vtable.
+ *
+ * Common parameters:
+ *
+ * If @a state is non-NULL, set @a *state to the state of the item
+ * after the operation has been performed. (In practice, this is only
+ * useful with merge, not diff; diff callbacks will probably set
+ * @a *state to #svn_wc_notify_state_unknown, since they do not change
+ * the state and therefore do not bother to know the state after the
+ * operation.) By default, @a state refers to the item's content
+ * state. Functions concerned with property state have separate
+ * @a contentstate and @a propstate arguments.
+ *
+ * If @a tree_conflicted is non-NULL, set @a *tree_conflicted to true if
+ * this operation caused a tree conflict, else to false. (Like with @a
+ * state, this is only useful with merge, not diff; diff callbacks
+ * should set this to false.)
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_wc_diff_callbacks4_t
+{
+ /**
+ * This function is called before @a file_changed to allow callbacks to
+ * skip the most expensive processing of retrieving the file data.
+ *
+ */
+ svn_error_t *(*file_opened)(svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton,
+ apr_pool_t *scratch_pool);
+
+ /**
+ * A file @a path has changed. If @a tmpfile2 is non-NULL, the
+ * contents have changed and those changes can be seen by comparing
+ * @a tmpfile1 and @a tmpfile2, which represent @a rev1 and @a rev2 of
+ * the file, respectively.
+ *
+ * If known, the @c svn:mime-type value of each file is passed into
+ * @a mimetype1 and @a mimetype2; either or both of the values can
+ * be NULL. The implementor can use this information to decide if
+ * (or how) to generate differences.
+ *
+ * @a propchanges is an array of (#svn_prop_t) structures. If it contains
+ * any elements, the original list of properties is provided in
+ * @a originalprops, which is a hash of #svn_string_t values, keyed on the
+ * property name.
+ *
+ */
+ svn_error_t *(*file_changed)(svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton,
+ apr_pool_t *scratch_pool);
+
+ /**
+ * A file @a path was added. The contents can be seen by comparing
+ * @a tmpfile1 and @a tmpfile2, which represent @a rev1 and @a rev2
+ * of the file, respectively. (If either file is empty, the rev
+ * will be 0.)
+ *
+ * If known, the @c svn:mime-type value of each file is passed into
+ * @a mimetype1 and @a mimetype2; either or both of the values can
+ * be NULL. The implementor can use this information to decide if
+ * (or how) to generate differences.
+ *
+ * @a propchanges is an array of (#svn_prop_t) structures. If it contains
+ * any elements, the original list of properties is provided in
+ * @a originalprops, which is a hash of #svn_string_t values, keyed on the
+ * property name.
+ * If @a copyfrom_path is non-@c NULL, this add has history (i.e., is a
+ * copy), and the origin of the copy may be recorded as
+ * @a copyfrom_path under @a copyfrom_revision.
+ */
+ svn_error_t *(*file_added)(svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton,
+ apr_pool_t *scratch_pool);
+
+ /**
+ * A file @a path was deleted. The [loss of] contents can be seen by
+ * comparing @a tmpfile1 and @a tmpfile2. @a originalprops provides
+ * the properties of the file.
+ * ### Some existing callers include WC "entry props" in @a originalprops.
+ *
+ * If known, the @c svn:mime-type value of each file is passed into
+ * @a mimetype1 and @a mimetype2; either or both of the values can
+ * be NULL. The implementor can use this information to decide if
+ * (or how) to generate differences.
+ */
+ svn_error_t *(*file_deleted)(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ const char *mimetype1,
+ const char *mimetype2,
+ apr_hash_t *originalprops,
+ void *diff_baton,
+ apr_pool_t *scratch_pool);
+
+ /**
+ * A directory @a path was deleted.
+ */
+ svn_error_t *(*dir_deleted)(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ void *diff_baton,
+ apr_pool_t *scratch_pool);
+ /**
+ * A directory @a path has been opened. @a rev is the revision that the
+ * directory came from.
+ *
+ * This function is called for any existing directory @a path before any
+ * of the callbacks are called for a child of @a path.
+ *
+ * If the callback returns @c TRUE in @a *skip_children, children
+ * of this directory will be skipped.
+ */
+ svn_error_t *(*dir_opened)(svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton,
+ apr_pool_t *scratch_pool);
+
+ /**
+ * A directory @a path was added. @a rev is the revision that the
+ * directory came from.
+ *
+ * This function is called for any new directory @a path before any
+ * of the callbacks are called for a child of @a path.
+ *
+ * If @a copyfrom_path is non-@c NULL, this add has history (i.e., is a
+ * copy), and the origin of the copy may be recorded as
+ * @a copyfrom_path under @a copyfrom_revision.
+ */
+ svn_error_t *(*dir_added)(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *path,
+ svn_revnum_t rev,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ void *diff_baton,
+ apr_pool_t *scratch_pool);
+
+ /**
+ * A list of property changes (@a propchanges) was applied to the
+ * directory @a path.
+ *
+ * The array is a list of (#svn_prop_t) structures.
+ *
+ * @a dir_was_added is set to #TRUE if the directory was added, and
+ * to #FALSE if the directory pre-existed.
+ */
+ svn_error_t *(*dir_props_changed)(svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ svn_boolean_t dir_was_added,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ void *diff_baton,
+ apr_pool_t *scratch_pool);
+
+ /**
+ * A directory @a path which has been opened with @a dir_opened or @a
+ * dir_added has been closed.
+ *
+ * @a dir_was_added is set to #TRUE if the directory was added, and
+ * to #FALSE if the directory pre-existed.
+ */
+ svn_error_t *(*dir_closed)(svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ svn_boolean_t dir_was_added,
+ void *diff_baton,
+ apr_pool_t *scratch_pool);
+
+} svn_wc_diff_callbacks4_t;
+
+
+/**
+ * Similar to #svn_wc_diff_callbacks4_t, but without @a copyfrom_path and
+ * @a copyfrom_revision arguments to @c file_added and @c dir_added functions.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+typedef struct svn_wc_diff_callbacks3_t
+{
+ /** The same as #svn_wc_diff_callbacks4_t.file_changed. */
+ svn_error_t *(*file_changed)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton);
+
+ /** Similar to #svn_wc_diff_callbacks4_t.file_added but without
+ * @a copyfrom_path and @a copyfrom_revision arguments. */
+ svn_error_t *(*file_added)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton);
+
+ /** The same as #svn_wc_diff_callbacks4_t.file_deleted. */
+ svn_error_t *(*file_deleted)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ const char *mimetype1,
+ const char *mimetype2,
+ apr_hash_t *originalprops,
+ void *diff_baton);
+
+ /** Similar to #svn_wc_diff_callbacks4_t.dir_added but without
+ * @a copyfrom_path and @a copyfrom_revision arguments. */
+ svn_error_t *(*dir_added)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton);
+
+ /** The same as #svn_wc_diff_callbacks4_t.dir_deleted. */
+ svn_error_t *(*dir_deleted)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ void *diff_baton);
+
+ /** The same as #svn_wc_diff_callbacks4_t.dir_props_changed. */
+ svn_error_t *(*dir_props_changed)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ void *diff_baton);
+
+ /** The same as #svn_wc_diff_callbacks4_t.dir_opened. */
+ svn_error_t *(*dir_opened)(svn_wc_adm_access_t *adm_access,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton);
+
+ /** The same as #svn_wc_diff_callbacks4_t.dir_closed. */
+ svn_error_t *(*dir_closed)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ void *diff_baton);
+
+} svn_wc_diff_callbacks3_t;
+
+/**
+ * Similar to #svn_wc_diff_callbacks3_t, but without the @c dir_opened
+ * and @c dir_closed functions, and without the @a tree_conflicted argument
+ * to the functions.
+ *
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ */
+typedef struct svn_wc_diff_callbacks2_t
+{
+ /** The same as @c file_changed in #svn_wc_diff_callbacks3_t. */
+ svn_error_t *(*file_changed)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton);
+
+ /** The same as @c file_added in #svn_wc_diff_callbacks3_t. */
+ svn_error_t *(*file_added)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton);
+
+ /** The same as @c file_deleted in #svn_wc_diff_callbacks3_t. */
+ svn_error_t *(*file_deleted)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ const char *mimetype1,
+ const char *mimetype2,
+ apr_hash_t *originalprops,
+ void *diff_baton);
+
+ /** The same as @c dir_added in #svn_wc_diff_callbacks3_t. */
+ svn_error_t *(*dir_added)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton);
+
+ /** The same as @c dir_deleted in #svn_wc_diff_callbacks3_t. */
+ svn_error_t *(*dir_deleted)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ const char *path,
+ void *diff_baton);
+
+ /** The same as @c dir_props_changed in #svn_wc_diff_callbacks3_t. */
+ svn_error_t *(*dir_props_changed)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ const char *path,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ void *diff_baton);
+
+} svn_wc_diff_callbacks2_t;
+
+/**
+ * Similar to #svn_wc_diff_callbacks2_t, but with file additions/content
+ * changes and property changes split into different functions.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+typedef struct svn_wc_diff_callbacks_t
+{
+ /** Similar to @c file_changed in #svn_wc_diff_callbacks2_t, but without
+ * property change information. @a tmpfile2 is never NULL. @a state applies
+ * to the file contents. */
+ svn_error_t *(*file_changed)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ void *diff_baton);
+
+ /** Similar to @c file_added in #svn_wc_diff_callbacks2_t, but without
+ * property change information. @a *state applies to the file contents. */
+ svn_error_t *(*file_added)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ void *diff_baton);
+
+ /** Similar to @c file_deleted in #svn_wc_diff_callbacks2_t, but without
+ * the properties. */
+ svn_error_t *(*file_deleted)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ const char *mimetype1,
+ const char *mimetype2,
+ void *diff_baton);
+
+ /** The same as @c dir_added in #svn_wc_diff_callbacks2_t. */
+ svn_error_t *(*dir_added)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton);
+
+ /** The same as @c dir_deleted in #svn_wc_diff_callbacks2_t. */
+ svn_error_t *(*dir_deleted)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ const char *path,
+ void *diff_baton);
+
+ /** Similar to @c dir_props_changed in #svn_wc_diff_callbacks2_t, but this
+ * function is called for files as well as directories. */
+ svn_error_t *(*props_changed)(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ const char *path,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ void *diff_baton);
+
+} svn_wc_diff_callbacks_t;
+
+
+/* Asking questions about a working copy. */
+
+/** Set @a *wc_format to @a local_abspath's working copy format version
+ * number if @a local_abspath is a valid working copy directory, else set it
+ * to 0.
+ *
+ * Return error @c APR_ENOENT if @a local_abspath does not exist at all.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_check_wc2(int *wc_format,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_wc_check_wc2(), but with a relative path and no supplied
+ * working copy context.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_check_wc(const char *path,
+ int *wc_format,
+ apr_pool_t *pool);
+
+
+/** Set @a *has_binary_prop to @c TRUE iff @a path has been marked
+ * with a property indicating that it is non-text (in other words, binary).
+ * @a adm_access is an access baton set that contains @a path.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API. As a
+ * replacement for this functionality, @see svn_mime_type_is_binary and
+ * #SVN_PROP_MIME_TYPE.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_has_binary_prop(svn_boolean_t *has_binary_prop,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+
+/* Detecting modification. */
+
+/** Set @a *modified_p to non-zero if @a local_abspath's text is modified
+ * with regard to the base revision, else set @a *modified_p to zero.
+ * @a local_abspath is the absolute path to the file.
+ *
+ * This function uses some heuristics to avoid byte-by-byte comparisons
+ * against the base text (eg. file size and its modification time).
+ *
+ * If @a local_abspath does not exist, consider it unmodified. If it exists
+ * but is not under revision control (not even scheduled for
+ * addition), return the error #SVN_ERR_ENTRY_NOT_FOUND.
+ *
+ * @a unused is ignored.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_text_modified_p2(svn_boolean_t *modified_p,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t unused,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_text_modified_p2(), but with a relative path and
+ * adm_access baton?
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_text_modified_p(svn_boolean_t *modified_p,
+ const char *filename,
+ svn_boolean_t force_comparison,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+/** Set @a *modified_p to non-zero if @a path's properties are modified
+ * with regard to the base revision, else set @a modified_p to zero.
+ * @a adm_access must be an access baton for @a path.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_props_modified_p2(svn_boolean_t *modified_p,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_props_modified_p2(), but with a relative path and
+ * adm_access baton.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_props_modified_p(svn_boolean_t *modified_p,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+
+/**
+ * @defgroup svn_wc_entries Entries and status (deprecated)
+ * @{
+ */
+
+/** The schedule states an entry can be in.
+ * @deprecated Provided for backward compatibility with the 1.6 API. */
+typedef enum svn_wc_schedule_t
+{
+ /** Nothing special here */
+ svn_wc_schedule_normal,
+
+ /** Slated for addition */
+ svn_wc_schedule_add,
+
+ /** Slated for deletion */
+ svn_wc_schedule_delete,
+
+ /** Slated for replacement (delete + add) */
+ svn_wc_schedule_replace
+
+} svn_wc_schedule_t;
+
+
+/**
+ * Values for the working_size field in svn_wc_entry_t
+ * when it isn't set to the actual size value of the unchanged
+ * working file.
+ *
+ * The value of the working size is unknown (hasn't been
+ * calculated and stored in the past for whatever reason).
+ *
+ * @since New in 1.5
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+#define SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN (-1)
+
+/** A working copy entry -- that is, revision control information about
+ * one versioned entity.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+/* SVN_DEPRECATED */
+typedef struct svn_wc_entry_t
+{
+ /* IMPORTANT: If you extend this structure, add new fields to the end. */
+
+ /* General Attributes */
+
+ /** entry's name */
+ const char *name;
+
+ /** base revision */
+ svn_revnum_t revision;
+
+ /** url in repository */
+ const char *url;
+
+ /** canonical repository URL or NULL if not known */
+ const char *repos;
+
+ /** repository uuid */
+ const char *uuid;
+
+ /** node kind (file, dir, ...) */
+ svn_node_kind_t kind;
+
+ /* State information */
+
+ /** scheduling (add, delete, replace ...) */
+ svn_wc_schedule_t schedule;
+
+ /** in a copied state (possibly because the entry is a child of a
+ * path that is #svn_wc_schedule_add or #svn_wc_schedule_replace,
+ * when the entry itself is #svn_wc_schedule_normal).
+ * COPIED is true for nodes under a directory that was copied, but
+ * COPYFROM_URL is null there. They are both set for the root
+ * destination of the copy.
+ */
+ svn_boolean_t copied;
+
+ /** The directory containing this entry had a versioned child of this
+ * name, but this entry represents a different revision or a switched
+ * path at which no item exists in the repository. This typically
+ * arises from committing or updating to a deletion of this entry
+ * without committing or updating the parent directory.
+ *
+ * The schedule can be 'normal' or 'add'. */
+ svn_boolean_t deleted;
+
+ /** absent -- we know an entry of this name exists, but that's all
+ (usually this happens because of authz restrictions) */
+ svn_boolean_t absent;
+
+ /** for THIS_DIR entry, implies whole entries file is incomplete */
+ svn_boolean_t incomplete;
+
+ /** copyfrom location */
+ const char *copyfrom_url;
+
+ /** copyfrom revision */
+ svn_revnum_t copyfrom_rev;
+
+ /** old version of conflicted file. A file basename, relative to the
+ * user's directory that the THIS_DIR entry refers to. */
+ const char *conflict_old;
+
+ /** new version of conflicted file. A file basename, relative to the
+ * user's directory that the THIS_DIR entry refers to. */
+ const char *conflict_new;
+
+ /** working version of conflicted file. A file basename, relative to the
+ * user's directory that the THIS_DIR entry refers to. */
+ const char *conflict_wrk;
+
+ /** property reject file. A file basename, relative to the user's
+ * directory that the THIS_DIR entry refers to. */
+ const char *prejfile;
+
+ /** last up-to-date time for text contents (0 means no information available)
+ */
+ apr_time_t text_time;
+
+ /** last up-to-date time for properties (0 means no information available)
+ *
+ * @deprecated This value will always be 0 in version 1.4 and later.
+ */
+ apr_time_t prop_time;
+
+ /** Hex MD5 checksum for the untranslated text base file,
+ * can be @c NULL for backwards compatibility.
+ */
+ const char *checksum;
+
+ /* "Entry props" */
+
+ /** last revision this was changed */
+ svn_revnum_t cmt_rev;
+
+ /** last date this was changed */
+ apr_time_t cmt_date;
+
+ /** last commit author of this item */
+ const char *cmt_author;
+
+ /** lock token or NULL if path not locked in this WC
+ * @since New in 1.2.
+ */
+ const char *lock_token;
+
+ /** lock owner, or NULL if not locked in this WC
+ * @since New in 1.2.
+ */
+ const char *lock_owner;
+
+ /** lock comment or NULL if not locked in this WC or no comment
+ * @since New in 1.2.
+ */
+ const char *lock_comment;
+
+ /** Lock creation date or 0 if not locked in this WC
+ * @since New in 1.2.
+ */
+ apr_time_t lock_creation_date;
+
+ /** Whether this entry has any working properties.
+ * False if this information is not stored in the entry.
+ *
+ * @since New in 1.4. */
+ svn_boolean_t has_props;
+
+ /** Whether this entry has property modifications.
+ *
+ * @note For working copies in older formats, this flag is not valid.
+ *
+ * @see svn_wc_props_modified_p().
+ *
+ * @since New in 1.4. */
+ svn_boolean_t has_prop_mods;
+
+ /** A space-separated list of all properties whose presence/absence is cached
+ * in this entry.
+ *
+ * @see @c present_props.
+ *
+ * @since New in 1.4.
+ * @deprecated This value will always be "" in version 1.7 and later. */
+ const char *cachable_props;
+
+ /** Cached property existence for this entry.
+ * This is a space-separated list of property names. If a name exists in
+ * @c cachable_props but not in this list, this entry does not have that
+ * property. If a name exists in both lists, the property is present on this
+ * entry.
+ *
+ * @since New in 1.4.
+ * @deprecated This value will always be "" in version 1.7 and later. */
+ const char *present_props;
+
+ /** which changelist this item is part of, or NULL if not part of any.
+ * @since New in 1.5.
+ */
+ const char *changelist;
+
+ /** Size of the file after being translated into local
+ * representation, or #SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN if
+ * unknown.
+ *
+ * @since New in 1.5.
+ */
+ apr_off_t working_size;
+
+ /** Whether a local copy of this entry should be kept in the working copy
+ * after a deletion has been committed, Only valid for the this-dir entry
+ * when it is scheduled for deletion.
+ *
+ * @since New in 1.5. */
+ svn_boolean_t keep_local;
+
+ /** The depth of this entry.
+ *
+ * ### It's a bit annoying that we only use this on this_dir
+ * ### entries, yet it will exist (with value svn_depth_infinity) on
+ * ### all entries. Maybe some future extensibility would make this
+ * ### field meaningful on entries besides this_dir.
+ *
+ * @since New in 1.5. */
+ svn_depth_t depth;
+
+ /** Serialized data for all of the tree conflicts detected in this_dir.
+ *
+ * @since New in 1.6. */
+ const char *tree_conflict_data;
+
+ /** The entry is a intra-repository file external and this is the
+ * repository root relative path to the file specified in the
+ * externals definition, otherwise NULL if the entry is not a file
+ * external.
+ *
+ * @since New in 1.6. */
+ const char *file_external_path;
+
+ /** The entry is a intra-repository file external and this is the
+ * peg revision number specified in the externals definition. This
+ * field is only valid when the file_external_path field is
+ * non-NULL. The only permissible values are
+ * svn_opt_revision_unspecified if the entry is not an external,
+ * svn_opt_revision_head if the external revision is unspecified or
+ * specified with -r HEAD or svn_opt_revision_number for a specific
+ * revision number.
+ *
+ * @since New in 1.6. */
+ svn_opt_revision_t file_external_peg_rev;
+
+ /** The entry is an intra-repository file external and this is the
+ * operative revision number specified in the externals definition.
+ * This field is only valid when the file_external_path field is
+ * non-NULL. The only permissible values are
+ * svn_opt_revision_unspecified if the entry is not an external,
+ * svn_opt_revision_head if the external revision is unspecified or
+ * specified with -r HEAD or svn_opt_revision_number for a specific
+ * revision number.
+ *
+ * @since New in 1.6. */
+ svn_opt_revision_t file_external_rev;
+
+ /* IMPORTANT: If you extend this structure, check the following functions in
+ * subversion/libsvn_wc/entries.c, to see if you need to extend them as well.
+ *
+ * svn_wc__atts_to_entry()
+ * svn_wc_entry_dup()
+ * alloc_entry()
+ * read_entry()
+ * write_entry()
+ * fold_entry()
+ */
+} svn_wc_entry_t;
+
+
+/** How an entries file's owner dir is named in the entries file.
+ * @deprecated Provided for backward compatibility with the 1.6 API. */
+#define SVN_WC_ENTRY_THIS_DIR ""
+
+
+/** Set @a *entry to an entry for @a path, allocated in the access baton pool.
+ * If @a show_hidden is TRUE, return the entry even if it's in 'excluded',
+ * 'deleted' or 'absent' state. Excluded entries are those with their depth
+ * set to #svn_depth_exclude. If @a path is not under revision control, or
+ * if entry is hidden, not scheduled for re-addition, and @a show_hidden is @c
+ * FALSE, then set @a *entry to @c NULL.
+ *
+ * @a *entry should not be modified, since doing so modifies the entries
+ * cache in @a adm_access without changing the entries file on disk.
+ *
+ * If @a path is not a directory then @a adm_access must be an access baton
+ * for the parent directory of @a path. To avoid needing to know whether
+ * @a path is a directory or not, if @a path is a directory @a adm_access
+ * can still be an access baton for the parent of @a path so long as the
+ * access baton for @a path itself is in the same access baton set.
+ *
+ * @a path can be relative or absolute but must share the same base used
+ * to open @a adm_access.
+ *
+ * Note that it is possible for @a path to be absent from disk but still
+ * under revision control; and conversely, it is possible for @a path to
+ * be present, but not under revision control.
+ *
+ * Use @a pool only for local processing.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_entry(const svn_wc_entry_t **entry,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool);
+
+
+/** Parse the `entries' file for @a adm_access and return a hash @a entries,
+ * whose keys are (<tt>const char *</tt>) entry names and values are
+ * (<tt>svn_wc_entry_t *</tt>). The hash @a entries, and its keys and
+ * values, are allocated from the pool used to open the @a adm_access
+ * baton (that's how the entries caching works). @a pool is used for
+ * transient allocations.
+ *
+ * Entries that are in a 'excluded', 'deleted' or 'absent' state (and not
+ * scheduled for re-addition) are not returned in the hash, unless
+ * @a show_hidden is TRUE. Excluded entries are those with their depth set to
+ * #svn_depth_exclude.
+ *
+ * @par Important:
+ * The @a entries hash is the entries cache in @a adm_access
+ * and so usually the hash itself, the keys and the values should be treated
+ * as read-only. If any of these are modified then it is the caller's
+ * responsibility to ensure that the entries file on disk is updated. Treat
+ * the hash values as type (<tt>const svn_wc_entry_t *</tt>) if you wish to
+ * avoid accidental modification. Modifying the schedule member is a
+ * particularly bad idea, as the entries writing process relies on having
+ * access to the original schedule. Use a duplicate entry to modify the
+ * schedule.
+ *
+ * @par Important:
+ * Only the entry structures representing files and
+ * #SVN_WC_ENTRY_THIS_DIR contain complete information. The entry
+ * structures representing subdirs have only the `kind' and `state'
+ * fields filled in. If you want info on a subdir, you must use this
+ * routine to open its @a path and read the #SVN_WC_ENTRY_THIS_DIR
+ * structure, or call svn_wc_entry() on its @a path.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_entries_read(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool);
+
+
+/** Return a duplicate of @a entry, allocated in @a pool. No part of the new
+ * entry will be shared with @a entry.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_wc_entry_t *
+svn_wc_entry_dup(const svn_wc_entry_t *entry,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/**
+ * This struct contains information about a working copy node.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, users shouldn't allocate structures of this
+ * type, to preserve binary compatibility.
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_wc_info_t
+{
+ /** The schedule of this item
+ * ### Do we still need schedule? */
+ svn_wc_schedule_t schedule;
+
+ /** If copied, the URL from which the copy was made, else @c NULL. */
+ const char *copyfrom_url;
+
+ /** If copied, the revision from which the copy was made,
+ * else #SVN_INVALID_REVNUM. */
+ svn_revnum_t copyfrom_rev;
+
+ /** The checksum of the node, if it is a file. */
+ const svn_checksum_t *checksum;
+
+ /** A changelist the item is in, @c NULL if this node is not in a
+ * changelist. */
+ const char *changelist;
+
+ /** The depth of the item, see #svn_depth_t */
+ svn_depth_t depth;
+
+ /**
+ * The size of the file after being translated into its local
+ * representation, or #SVN_INVALID_FILESIZE if unknown.
+ * Not applicable for directories.
+ */
+ svn_filesize_t recorded_size;
+
+ /**
+ * The time at which the file had the recorded size recorded_size and was
+ * considered unmodified. */
+ apr_time_t recorded_time;
+
+ /** Array of const svn_wc_conflict_description2_t * which contains info
+ * on any conflict of which this node is a victim. Otherwise NULL. */
+ const apr_array_header_t *conflicts;
+
+ /** The local absolute path of the working copy root. */
+ const char *wcroot_abspath;
+
+ /** The path the node was moved from, if it was moved here. Else NULL.
+ * @since New in 1.8. */
+ const char *moved_from_abspath;
+
+ /** The path the node was moved to, if it was moved away. Else NULL.
+ * @since New in 1.8. */
+ const char *moved_to_abspath;
+} svn_wc_info_t;
+
+/**
+ * Return a duplicate of @a info, allocated in @a pool. No part of the new
+ * structure will be shared with @a info.
+ *
+ * @since New in 1.7.
+ */
+svn_wc_info_t *
+svn_wc_info_dup(const svn_wc_info_t *info,
+ apr_pool_t *pool);
+
+
+/** Given @a local_abspath in a dir under version control, decide if it is
+ * in a state of conflict; return the answers in @a *text_conflicted_p, @a
+ * *prop_conflicted_p, and @a *tree_conflicted_p. If one or two of the
+ * answers are uninteresting, simply pass @c NULL pointers for those.
+ *
+ * If @a local_abspath is unversioned or does not exist, return
+ * #SVN_ERR_WC_PATH_NOT_FOUND.
+ *
+ * If the @a local_abspath has corresponding text conflict files (with suffix
+ * .mine, .theirs, etc.) that cannot be found, assume that the text conflict
+ * has been resolved by the user and return @c FALSE in @a
+ * *text_conflicted_p.
+ *
+ * Similarly, if a property conflicts file (.prej suffix) is said to exist,
+ * but it cannot be found, assume that the property conflicts have been
+ * resolved by the user and return @c FALSE in @a *prop_conflicted_p.
+ *
+ * @a *tree_conflicted_p can't be auto-resolved in this fashion. An
+ * explicit `resolved' is needed.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_conflicted_p3(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ svn_boolean_t *tree_conflicted_p,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_conflicted_p3(), but with a path/adm_access parameter
+ * pair in place of a wc_ctx/local_abspath pair.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_conflicted_p2(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ svn_boolean_t *tree_conflicted_p,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+/** Given a @a dir_path under version control, decide if one of its entries
+ * (@a entry) is in a state of conflict; return the answers in @a
+ * text_conflicted_p and @a prop_conflicted_p. These pointers must not be
+ * null.
+ *
+ * If the @a entry mentions that text conflict files (with suffix .mine,
+ * .theirs, etc.) exist, but they cannot be found, assume the text conflict
+ * has been resolved by the user and return FALSE in @a *text_conflicted_p.
+ *
+ * Similarly, if the @a entry mentions that a property conflicts file (.prej
+ * suffix) exists, but it cannot be found, assume the property conflicts
+ * have been resolved by the user and return FALSE in @a *prop_conflicted_p.
+ *
+ * The @a entry is not updated.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_conflicted_p(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ const char *dir_path,
+ const svn_wc_entry_t *entry,
+ apr_pool_t *pool);
+
+
+/** Set @a *url and @a *rev to the ancestor URL and revision for @a path,
+ * allocating in @a pool. @a adm_access must be an access baton for @a path.
+ *
+ * If @a url or @a rev is NULL, then ignore it (just don't return the
+ * corresponding information).
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_ancestry(char **url,
+ svn_revnum_t *rev,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+
+/** A callback vtable invoked by the generic entry-walker function.
+ * @since New in 1.5.
+ */
+typedef struct svn_wc_entry_callbacks2_t
+{
+ /** An @a entry was found at @a path. */
+ svn_error_t *(*found_entry)(const char *path,
+ const svn_wc_entry_t *entry,
+ void *walk_baton,
+ apr_pool_t *pool);
+
+ /** Handle the error @a err encountered while processing @a path.
+ * Wrap or squelch @a err as desired, and return an #svn_error_t
+ * *, or #SVN_NO_ERROR.
+ */
+ svn_error_t *(*handle_error)(const char *path,
+ svn_error_t *err,
+ void *walk_baton,
+ apr_pool_t *pool);
+
+} svn_wc_entry_callbacks2_t;
+
+/** @deprecated Provided for backward compatibility with the 1.4 API. */
+typedef struct svn_wc_entry_callbacks_t
+{
+ /** An @a entry was found at @a path. */
+ svn_error_t *(*found_entry)(const char *path,
+ const svn_wc_entry_t *entry,
+ void *walk_baton,
+ apr_pool_t *pool);
+
+} svn_wc_entry_callbacks_t;
+
+/**
+ * A generic entry-walker.
+ *
+ * Do a potentially recursive depth-first entry-walk beginning on
+ * @a path, which can be a file or dir. Call callbacks in
+ * @a walk_callbacks, passing @a walk_baton to each. Use @a pool for
+ * looping, recursion, and to allocate all entries returned.
+ * @a adm_access must be an access baton for @a path. The pool
+ * passed to @a walk_callbacks is a temporary subpool of @a pool.
+ *
+ * If @a depth is #svn_depth_empty, invoke the callbacks on @a path
+ * and return without recursing further. If #svn_depth_files, do
+ * the same and invoke the callbacks on file children (if any) of
+ * @a path, then return. If #svn_depth_immediates, do the preceding
+ * but also invoke callbacks on immediate subdirectories, then return.
+ * If #svn_depth_infinity, recurse fully starting from @a path.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton to determine
+ * if the client has canceled the operation.
+ *
+ * Like our other entries interfaces, entries that are in a 'excluded',
+ * 'deleted' or 'absent' state (and not scheduled for re-addition) are not
+ * discovered, unless @a show_hidden is TRUE. Excluded entries are those with
+ * their depth set to #svn_depth_exclude.
+ *
+ * When a new directory is entered, #SVN_WC_ENTRY_THIS_DIR will always
+ * be returned first.
+ *
+ * @note Callers should be aware that each directory will be
+ * returned *twice*: first as an entry within its parent, and
+ * subsequently as the '.' entry within itself. The two calls can be
+ * distinguished by looking for #SVN_WC_ENTRY_THIS_DIR in the 'name'
+ * field of the entry.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_walk_entries3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_callbacks2_t *walk_callbacks,
+ void *walk_baton,
+ svn_depth_t depth,
+ svn_boolean_t show_hidden,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_walk_entries3(), but without cancellation support
+ * or error handling from @a walk_callbacks, and with @a depth always
+ * set to #svn_depth_infinity.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_walk_entries2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_callbacks_t *walk_callbacks,
+ void *walk_baton,
+ svn_boolean_t show_hidden,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_walk_entries2(), but without cancellation support.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_walk_entries(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_callbacks_t *walk_callbacks,
+ void *walk_baton,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool);
+
+
+/** Mark missing @a path as 'deleted' in its @a parent's list of
+ * entries. @a path should be a directory that is both deleted (via
+ * svn_wc_delete4) and removed (via a system call). This function
+ * should only be called during post-commit processing following a
+ * successful commit editor drive.
+ *
+ * Return #SVN_ERR_WC_PATH_FOUND if @a path isn't actually missing.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_mark_missing_deleted(const char *path,
+ svn_wc_adm_access_t *parent,
+ apr_pool_t *pool);
+
+
+/** Ensure that an administrative area exists for @a local_abspath, so
+ * that @a local_abspath is a working copy subdir based on @a url at @a
+ * revision, with depth @a depth, and with repository UUID @a repos_uuid
+ * and repository root URL @a repos_root_url.
+ *
+ * @a depth must be a definite depth, it cannot be #svn_depth_unknown.
+ * @a repos_uuid and @a repos_root_url MUST NOT be @c NULL, and
+ * @a repos_root_url must be a prefix of @a url.
+ *
+ * If the administrative area does not exist, then create it and
+ * initialize it to an unlocked state.
+ *
+ * If the administrative area already exists then the given @a url
+ * must match the URL in the administrative area and the given
+ * @a revision must match the BASE of the working copy dir unless
+ * the admin directory is scheduled for deletion or the
+ * #SVN_ERR_WC_OBSTRUCTED_UPDATE error will be returned.
+ *
+ * Do not ensure existence of @a local_abspath itself; if @a local_abspath
+ * does not exist, return error.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_ensure_adm4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *url,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_wc_ensure_adm4(), but without the wc context parameter.
+ *
+ * @note the @a uuid and @a repos parameters were documented as allowing
+ * @c NULL to be passed. Beginning with 1.7, this will return an error,
+ * contrary to prior documented behavior: see 'notes/api-errata/1.7/wc005.txt'.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_ensure_adm3(const char *path,
+ const char *uuid,
+ const char *url,
+ const char *repos,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_wc_ensure_adm3(), but with @a depth set to
+ * #svn_depth_infinity.
+ *
+ * See the note on svn_wc_ensure_adm3() regarding the @a repos and @a uuid
+ * parameters.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backwards compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_ensure_adm2(const char *path,
+ const char *uuid,
+ const char *url,
+ const char *repos,
+ svn_revnum_t revision,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_wc_ensure_adm2(), but with @a repos set to @c NULL.
+ *
+ * @note as of 1.7, this function always returns #SVN_ERR_BAD_URL since
+ * the @a repos parameter may not be @c NULL.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.2 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_ensure_adm(const char *path,
+ const char *uuid,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *pool);
+
+
+/** Set the repository root URL of @a path to @a repos, if possible.
+ *
+ * Before Subversion 1.7 there could be working copy directories that
+ * didn't have a stored repository root in some specific circumstances.
+ * This function allowed setting this root later.
+ *
+ * Since Subversion 1.7 this function just returns #SVN_NO_ERROR.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_maybe_set_repos_root(svn_wc_adm_access_t *adm_access,
+ const char *path,
+ const char *repos,
+ apr_pool_t *pool);
+
+
+/**
+ * @defgroup svn_wc_status Working copy status.
+ * @{
+ *
+ * We have three functions for getting working copy status: one function
+ * for getting the status of exactly one thing, another for
+ * getting the statuses of (potentially) multiple things and a third for
+ * getting the working copy out-of-dateness with respect to the repository.
+ *
+ * Why do we have two different functions for getting working copy status?
+ * The concept of depth, as explained in the documentation for
+ * svn_depth_t, may be useful in understanding this. Suppose we're
+ * getting the status of directory D:
+ *
+ * To offer all three levels, we could have one unified function,
+ * taking a `depth' parameter. Unfortunately, because this function
+ * would have to handle multiple return values as well as the single
+ * return value case, getting the status of just one entity would
+ * become cumbersome: you'd have to roll through a hash to find one
+ * lone status.
+ *
+ * So we have svn_wc_status3() for depth-empty (just D itself), and
+ * svn_wc_walk_status() for depth-immediates and depth-infinity,
+ * since the latter two involve multiple return values. And for
+ * out-of-dateness information we have svn_wc_get_status_editor5().
+ */
+
+/** The type of status for the working copy. */
+enum svn_wc_status_kind
+{
+ /** does not exist */
+ svn_wc_status_none = 1,
+
+ /** is not a versioned thing in this wc */
+ svn_wc_status_unversioned,
+
+ /** exists, but uninteresting */
+ svn_wc_status_normal,
+
+ /** is scheduled for addition */
+ svn_wc_status_added,
+
+ /** under v.c., but is missing */
+ svn_wc_status_missing,
+
+ /** scheduled for deletion */
+ svn_wc_status_deleted,
+
+ /** was deleted and then re-added */
+ svn_wc_status_replaced,
+
+ /** text or props have been modified */
+ svn_wc_status_modified,
+
+ /** local mods received repos mods (### unused) */
+ svn_wc_status_merged,
+
+ /** local mods received conflicting repos mods */
+ svn_wc_status_conflicted,
+
+ /** is unversioned but configured to be ignored */
+ svn_wc_status_ignored,
+
+ /** an unversioned resource is in the way of the versioned resource */
+ svn_wc_status_obstructed,
+
+ /** an unversioned directory path populated by an svn:externals
+ property; this status is not used for file externals */
+ svn_wc_status_external,
+
+ /** a directory doesn't contain a complete entries list */
+ svn_wc_status_incomplete
+};
+
+/**
+ * Structure for holding the "status" of a working copy item.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, to preserve binary compatibility, users
+ * should not directly allocate structures of this type.
+ *
+ * @since New in 1.7.
+ */
+typedef struct svn_wc_status3_t
+{
+ /** The kind of node as recorded in the working copy */
+ svn_node_kind_t kind;
+
+ /** The depth of the node as recorded in the working copy
+ * (#svn_depth_unknown for files or when no depth is set) */
+ svn_depth_t depth;
+
+ /** The actual size of the working file on disk, or SVN_INVALID_FILESIZE
+ * if unknown (or if the item isn't a file at all). */
+ svn_filesize_t filesize;
+
+ /** If the path is under version control, versioned is TRUE, otherwise
+ * FALSE. */
+ svn_boolean_t versioned;
+
+ /** Set to TRUE if the item is the victim of a conflict. */
+ svn_boolean_t conflicted;
+
+ /** The status of the node itself. In order of precedence: Obstructions,
+ * structural changes, text changes. */
+ enum svn_wc_status_kind node_status;
+
+ /** The status of the entry's text. */
+ enum svn_wc_status_kind text_status;
+
+ /** The status of the entry's properties. */
+ enum svn_wc_status_kind prop_status;
+
+ /** a file or directory can be 'copied' if it's scheduled for
+ * addition-with-history (or part of a subtree that is scheduled as such.).
+ */
+ svn_boolean_t copied;
+
+ /** Base revision. */
+ svn_revnum_t revision;
+
+ /** Last revision this was changed */
+ svn_revnum_t changed_rev;
+
+ /** Date of last commit. */
+ apr_time_t changed_date;
+
+ /** Last commit author of this item */
+ const char *changed_author;
+
+ /** The URL of the repository */
+ const char *repos_root_url;
+
+ /** The UUID of the repository */
+ const char *repos_uuid;
+
+ /** The in-repository path relative to the repository root. */
+ const char *repos_relpath;
+
+ /** a file or directory can be 'switched' if the switch command has been
+ * used. If this is TRUE, then file_external will be FALSE.
+ */
+ svn_boolean_t switched;
+
+ /** This directory has a working copy lock */
+ svn_boolean_t locked;
+
+ /** The repository file lock. (Values of path, token, owner, comment
+ * and are available if a lock is present) */
+ const svn_lock_t *lock;
+
+ /** Which changelist this item is part of, or NULL if not part of any. */
+ const char *changelist;
+
+ /**
+ * @defgroup svn_wc_status_ood WC out-of-date info from the repository
+ * @{
+ *
+ * When the working copy item is out-of-date compared to the
+ * repository, the following fields represent the state of the
+ * youngest revision of the item in the repository. If the working
+ * copy is not out of date, the fields are initialized as described
+ * below.
+ */
+
+ /** Set to the node kind of the youngest commit, or #svn_node_none
+ * if not out of date. */
+ svn_node_kind_t ood_kind;
+
+ /** The status of the node, based on the text status if the node has no
+ * restructuring changes */
+ enum svn_wc_status_kind repos_node_status;
+
+ /** The entry's text status in the repository. */
+ enum svn_wc_status_kind repos_text_status;
+
+ /** The entry's property status in the repository. */
+ enum svn_wc_status_kind repos_prop_status;
+
+ /** The entry's lock in the repository, if any. */
+ const svn_lock_t *repos_lock;
+
+ /** Set to the youngest committed revision, or #SVN_INVALID_REVNUM
+ * if not out of date. */
+ svn_revnum_t ood_changed_rev;
+
+ /** Set to the most recent commit date, or @c 0 if not out of date. */
+ apr_time_t ood_changed_date;
+
+ /** Set to the user name of the youngest commit, or @c NULL if not
+ * out of date or non-existent. Because a non-existent @c
+ * svn:author property has the same behavior as an out-of-date
+ * working copy, examine @c ood_last_cmt_rev to determine whether
+ * the working copy is out of date. */
+ const char *ood_changed_author;
+
+ /** @} */
+
+ /** Set to the local absolute path that this node was moved from, if this
+ * file or directory has been moved here locally and is the root of that
+ * move. Otherwise set to NULL.
+ *
+ * This will be NULL for moved-here nodes that are just part of a subtree
+ * that was moved along (and are not themselves a root of a different move
+ * operation).
+ *
+ * @since New in 1.8. */
+ const char *moved_from_abspath;
+
+ /** Set to the local absolute path that this node was moved to, if this file
+ * or directory has been moved away locally and corresponds to the root
+ * of the destination side of the move. Otherwise set to NULL.
+ *
+ * Note: Saying just "root" here could be misleading. For example:
+ * svn mv A AA;
+ * svn mv AA/B BB;
+ * creates a situation where A/B is moved-to BB, but one could argue that
+ * the move source's root actually was AA/B. Note that, as far as the
+ * working copy is concerned, above case is exactly identical to:
+ * svn mv A/B BB;
+ * svn mv A AA;
+ * In both situations, @a moved_to_abspath would be set for nodes A (moved
+ * to AA) and A/B (moved to BB), only.
+ *
+ * This will be NULL for moved-away nodes that were just part of a subtree
+ * that was moved along (and are not themselves a root of a different move
+ * operation).
+ *
+ * @since New in 1.8. */
+ const char *moved_to_abspath;
+
+ /** @c TRUE iff the item is a file brought in by an svn:externals definition.
+ * @since New in 1.8. */
+ svn_boolean_t file_external;
+
+ /* NOTE! Please update svn_wc_dup_status3() when adding new fields here. */
+} svn_wc_status3_t;
+
+/**
+ * ### All diffs are not yet known.
+ * Same as svn_wc_status3_t, but without the #svn_boolean_t 'versioned'
+ * field. Instead an item that is not versioned has the 'entry' field set to
+ * @c NULL.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+typedef struct svn_wc_status2_t
+{
+ /** Can be @c NULL if not under version control. */
+ const svn_wc_entry_t *entry;
+
+ /** The status of the entry itself, including its text if it is a file. */
+ enum svn_wc_status_kind text_status;
+
+ /** The status of the entry's properties. */
+ enum svn_wc_status_kind prop_status;
+
+ /** a directory can be 'locked' if a working copy update was interrupted. */
+ svn_boolean_t locked;
+
+ /** a file or directory can be 'copied' if it's scheduled for
+ * addition-with-history (or part of a subtree that is scheduled as such.).
+ */
+ svn_boolean_t copied;
+
+ /** a file or directory can be 'switched' if the switch command has been
+ * used. If this is TRUE, then file_external will be FALSE.
+ */
+ svn_boolean_t switched;
+
+ /** The entry's text status in the repository. */
+ enum svn_wc_status_kind repos_text_status;
+
+ /** The entry's property status in the repository. */
+ enum svn_wc_status_kind repos_prop_status;
+
+ /** The entry's lock in the repository, if any. */
+ svn_lock_t *repos_lock;
+
+ /** Set to the URI (actual or expected) of the item.
+ * @since New in 1.3
+ */
+ const char *url;
+
+ /**
+ * @defgroup svn_wc_status_ood WC out-of-date info from the repository
+ * @{
+ *
+ * When the working copy item is out-of-date compared to the
+ * repository, the following fields represent the state of the
+ * youngest revision of the item in the repository. If the working
+ * copy is not out of date, the fields are initialized as described
+ * below.
+ */
+
+ /** Set to the youngest committed revision, or #SVN_INVALID_REVNUM
+ * if not out of date.
+ * @since New in 1.3
+ */
+ svn_revnum_t ood_last_cmt_rev;
+
+ /** Set to the most recent commit date, or @c 0 if not out of date.
+ * @since New in 1.3
+ */
+ apr_time_t ood_last_cmt_date;
+
+ /** Set to the node kind of the youngest commit, or #svn_node_none
+ * if not out of date.
+ * @since New in 1.3
+ */
+ svn_node_kind_t ood_kind;
+
+ /** Set to the user name of the youngest commit, or @c NULL if not
+ * out of date or non-existent. Because a non-existent @c
+ * svn:author property has the same behavior as an out-of-date
+ * working copy, examine @c ood_last_cmt_rev to determine whether
+ * the working copy is out of date.
+ * @since New in 1.3
+ */
+ const char *ood_last_cmt_author;
+
+ /** @} */
+
+ /** Non-NULL if the entry is the victim of a tree conflict.
+ * @since New in 1.6
+ */
+ svn_wc_conflict_description_t *tree_conflict;
+
+ /** If the item is a file that was added to the working copy with an
+ * svn:externals; if file_external is TRUE, then switched is always
+ * FALSE.
+ * @since New in 1.6
+ */
+ svn_boolean_t file_external;
+
+ /** The actual status of the text compared to the pristine base of the
+ * file. This value isn't masked by other working copy statuses.
+ * @c pristine_text_status is #svn_wc_status_none if this value was
+ * not calculated during the status walk.
+ * @since New in 1.6
+ */
+ enum svn_wc_status_kind pristine_text_status;
+
+ /** The actual status of the properties compared to the pristine base of
+ * the node. This value isn't masked by other working copy statuses.
+ * @c pristine_prop_status is #svn_wc_status_none if this value was
+ * not calculated during the status walk.
+ * @since New in 1.6
+ */
+ enum svn_wc_status_kind pristine_prop_status;
+
+} svn_wc_status2_t;
+
+
+
+/**
+ * Same as #svn_wc_status2_t, but without the #svn_lock_t 'repos_lock', const char 'url', #svn_revnum_t 'ood_last_cmt_rev', apr_time_t 'ood_last_cmt_date', #svn_node_kind_t 'ood_kind', const char 'ood_last_cmt_author', #svn_wc_conflict_description_t 'tree_conflict', #svn_boolean_t 'file_external', #svn_wc_status_kind 'pristine_text_status', and #svn_wc_status_kind 'pristine_prop_status' fields.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+typedef struct svn_wc_status_t
+{
+ /** Can be @c NULL if not under version control. */
+ const svn_wc_entry_t *entry;
+
+ /** The status of the entries text. */
+ enum svn_wc_status_kind text_status;
+
+ /** The status of the entries properties. */
+ enum svn_wc_status_kind prop_status;
+
+ /** a directory can be 'locked' if a working copy update was interrupted. */
+ svn_boolean_t locked;
+
+ /** a file or directory can be 'copied' if it's scheduled for
+ * addition-with-history (or part of a subtree that is scheduled as such.).
+ */
+ svn_boolean_t copied;
+
+ /** a file or directory can be 'switched' if the switch command has been
+ * used.
+ */
+ svn_boolean_t switched;
+
+ /** The entry's text status in the repository. */
+ enum svn_wc_status_kind repos_text_status;
+
+ /** The entry's property status in the repository. */
+ enum svn_wc_status_kind repos_prop_status;
+
+} svn_wc_status_t;
+
+
+/**
+ * Return a deep copy of the @a orig_stat status structure, allocated
+ * in @a pool.
+ *
+ * @since New in 1.7.
+ */
+svn_wc_status3_t *
+svn_wc_dup_status3(const svn_wc_status3_t *orig_stat,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_wc_dup_status3(), but for older svn_wc_status_t structures.
+ *
+ * @since New in 1.2
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_wc_status2_t *
+svn_wc_dup_status2(const svn_wc_status2_t *orig_stat,
+ apr_pool_t *pool);
+
+
+/**
+ * Same as svn_wc_dup_status2(), but for older svn_wc_status_t structures.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_wc_status_t *
+svn_wc_dup_status(const svn_wc_status_t *orig_stat,
+ apr_pool_t *pool);
+
+
+/**
+ * Fill @a *status for @a local_abspath, allocating in @a result_pool.
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * Here are some things to note about the returned structure. A quick
+ * examination of the @c status->text_status after a successful return of
+ * this function can reveal the following things:
+ *
+ * - #svn_wc_status_none : @a local_abspath is not versioned, and is
+ * not present on disk
+ *
+ * - #svn_wc_status_missing : @a local_abspath is versioned, but is
+ * missing from the working copy.
+ *
+ * - #svn_wc_status_unversioned : @a local_abspath is not versioned,
+ * but is present on disk and not being
+ * ignored (see above).
+ *
+ * The other available results for the @c text_status field are more
+ * straightforward in their meanings. See the comments on the
+ * #svn_wc_status_kind structure for some hints.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_status3(svn_wc_status3_t **status,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_status3(), but with a adm_access baton and absolute
+ * path.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_status2(svn_wc_status2_t **status,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+
+/**
+ * Same as svn_wc_status2(), but for older svn_wc_status_t structures.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_status(svn_wc_status_t **status,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+
+
+
+/**
+ * A callback for reporting a @a status about @a local_abspath.
+ *
+ * @a baton is a closure object; it should be provided by the
+ * implementation, and passed by the caller.
+ *
+ * @a scratch_pool will be cleared between invocations to the callback.
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_wc_status_func4_t)(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Same as svn_wc_status_func4_t, but with a non-const status and a relative
+ * path.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+typedef svn_error_t *(*svn_wc_status_func3_t)(void *baton,
+ const char *path,
+ svn_wc_status2_t *status,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_wc_status_func3_t, but without a provided pool or
+ * the ability to propagate errors.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+typedef void (*svn_wc_status_func2_t)(void *baton,
+ const char *path,
+ svn_wc_status2_t *status);
+
+/**
+ * Same as svn_wc_status_func2_t, but for older svn_wc_status_t structures.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+typedef void (*svn_wc_status_func_t)(void *baton,
+ const char *path,
+ svn_wc_status_t *status);
+
+/**
+ * Walk the working copy status of @a local_abspath using @a wc_ctx, by
+ * creating #svn_wc_status3_t structures and sending these through
+ * @a status_func / @a status_baton.
+ *
+ * * Assuming the target is a directory, then:
+ *
+ * - If @a get_all is FALSE, then only locally-modified entries will be
+ * returned. If TRUE, then all entries will be returned.
+ *
+ * - If @a ignore_text_mods is TRUE, then the walk will not check for
+ * modified files. Any #svn_wc_status3_t structures returned for files
+ * will always have a text_status field set to svn_wc_status_normal.
+ * If @a ignore_text_mods is FALSE, the walk checks for text changes
+ * and returns #svn_wc_status3_t structures describing any changes.
+ *
+ * - If @a depth is #svn_depth_empty, a status structure will
+ * be returned for the target only; if #svn_depth_files, for the
+ * target and its immediate file children; if
+ * #svn_depth_immediates, for the target and its immediate
+ * children; if #svn_depth_infinity, for the target and
+ * everything underneath it, fully recursively.
+ *
+ * If @a depth is #svn_depth_unknown, take depths from the
+ * working copy and behave as above in each directory's case.
+ *
+ * If the given @a depth is incompatible with the depth found in a
+ * working copy directory, the found depth always governs.
+ *
+ * If @a no_ignore is set, statuses that would typically be ignored
+ * will instead be reported.
+ *
+ * @a ignore_patterns is an array of file patterns matching
+ * unversioned files to ignore for the purposes of status reporting,
+ * or @c NULL if the default set of ignorable file patterns should be used.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton while walking
+ * to determine if the client has canceled the operation.
+ *
+ * This function uses @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_walk_status(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_text_mods,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * DEPRECATED -- please use APIs from svn_client.h
+ *
+ * ---
+ *
+ * Set @a *editor and @a *edit_baton to an editor that generates
+ * #svn_wc_status3_t structures and sends them through @a status_func /
+ * @a status_baton. @a anchor_abspath is a working copy directory
+ * directory which will be used as the root of our editor. If @a
+ * target_basename is not "", it represents a node in the @a anchor_abspath
+ * which is the subject of the editor drive (otherwise, the @a
+ * anchor_abspath is the subject).
+ *
+ * If @a set_locks_baton is non-@c NULL, it will be set to a baton that can
+ * be used in a call to the svn_wc_status_set_repos_locks() function.
+ *
+ * Callers drive this editor to describe working copy out-of-dateness
+ * with respect to the repository. If this information is not
+ * available or not desired, callers should simply call the
+ * close_edit() function of the @a editor vtable.
+ *
+ * If the editor driver calls @a editor's set_target_revision() vtable
+ * function, then when the edit drive is completed, @a *edit_revision
+ * will contain the revision delivered via that interface.
+ *
+ * Assuming the target is a directory, then:
+ *
+ * - If @a get_all is FALSE, then only locally-modified entries will be
+ * returned. If TRUE, then all entries will be returned.
+ *
+ * - If @a depth is #svn_depth_empty, a status structure will
+ * be returned for the target only; if #svn_depth_files, for the
+ * target and its immediate file children; if
+ * #svn_depth_immediates, for the target and its immediate
+ * children; if #svn_depth_infinity, for the target and
+ * everything underneath it, fully recursively.
+ *
+ * If @a depth is #svn_depth_unknown, take depths from the
+ * working copy and behave as above in each directory's case.
+ *
+ * If the given @a depth is incompatible with the depth found in a
+ * working copy directory, the found depth always governs.
+ *
+ * If @a no_ignore is set, statuses that would typically be ignored
+ * will instead be reported.
+ *
+ * @a ignore_patterns is an array of file patterns matching
+ * unversioned files to ignore for the purposes of status reporting,
+ * or @c NULL if the default set of ignorable file patterns should be used.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton while building
+ * the @a statushash to determine if the client has canceled the operation.
+ *
+ * If @a depth_as_sticky is set handle @a depth like when depth_is_sticky is
+ * passed for updating. This will show excluded nodes show up as added in the
+ * repository.
+ *
+ * If @a server_performs_filtering is TRUE, assume that the server handles
+ * the ambient depth filtering, so this doesn't have to be handled in the
+ * editor.
+ *
+ * Allocate the editor itself in @a result_pool, and use @a scratch_pool
+ * for temporary allocations. The editor will do its temporary allocations
+ * in a subpool of @a result_pool.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_status_editor5(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ void **set_locks_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t depth_as_sticky,
+ svn_boolean_t server_performs_filtering,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Same as svn_wc_get_status_editor5, but using #svn_wc_status_func3_t
+ * instead of #svn_wc_status_func4_t. And @a server_performs_filtering
+ * always set to #TRUE.
+ *
+ * This also uses a single pool parameter, stating that all temporary
+ * allocations are performed in manually constructed/destroyed subpool.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_status_editor4(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ void **set_locks_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func3_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_wc_get_status_editor4(), but using #svn_wc_status_func2_t
+ * instead of #svn_wc_status_func3_t.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_status_editor3(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ void **set_locks_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func2_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool);
+
+/**
+ * Like svn_wc_get_status_editor3(), but with @a ignore_patterns
+ * provided from the corresponding value in @a config, and @a recurse
+ * instead of @a depth. If @a recurse is TRUE, behave as if for
+ * #svn_depth_infinity; else if @a recurse is FALSE, behave as if for
+ * #svn_depth_immediates.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_status_editor2(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ void **set_locks_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ apr_hash_t *config,
+ svn_boolean_t recurse,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func2_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool);
+
+/**
+ * Same as svn_wc_get_status_editor2(), but with @a set_locks_baton set
+ * to @c NULL, and taking a deprecated svn_wc_status_func_t argument.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_status_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ apr_hash_t *config,
+ svn_boolean_t recurse,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool);
+
+
+/**
+ * Associate @a locks, a hash table mapping <tt>const char*</tt>
+ * absolute repository paths to <tt>svn_lock_t</tt> objects, with a
+ * @a set_locks_baton returned by an earlier call to
+ * svn_wc_get_status_editor3(). @a repos_root is the repository root URL.
+ * Perform all allocations in @a pool.
+ *
+ * @note @a locks will not be copied, so it must be valid throughout the
+ * edit. @a pool must also not be destroyed or cleared before the edit is
+ * finished.
+ *
+ * @since New in 1.2.
+ */
+svn_error_t *
+svn_wc_status_set_repos_locks(void *set_locks_baton,
+ apr_hash_t *locks,
+ const char *repos_root,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/**
+ * Copy @a src_abspath to @a dst_abspath, and schedule @a dst_abspath
+ * for addition to the repository, remembering the copy history. @a wc_ctx
+ * is used for accessing the working copy and must contain a write lock for
+ * the parent directory of @a dst_abspath,
+ *
+ * If @a metadata_only is TRUE then this is a database-only operation and
+ * the working directories and files are not copied.
+ *
+ * @a src_abspath must be a file or directory under version control;
+ * the parent of @a dst_abspath must be a directory under version control
+ * in the same working copy; @a dst_abspath will be the name of the copied
+ * item, and it must not exist already if @a metadata_only is FALSE. Note that
+ * when @a src points to a versioned file, the working file doesn't
+ * necessarily exist in which case its text-base is used instead.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton at
+ * various points during the operation. If it returns an error
+ * (typically #SVN_ERR_CANCELLED), return that error immediately.
+ *
+ * If @a notify_func is non-NULL, call it with @a notify_baton and the path
+ * of the root node (only) of the destination.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_copy3(svn_wc_context_t *wc_ctx,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t metadata_only,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_copy3(), but takes access batons and a relative path
+ * and a basename instead of absolute paths and a working copy context.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_copy2(const char *src,
+ svn_wc_adm_access_t *dst_parent,
+ const char *dst_basename,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_copy2(), but takes an #svn_wc_notify_func_t instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_copy(const char *src,
+ svn_wc_adm_access_t *dst_parent,
+ const char *dst_basename,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+/**
+ * Move @a src_abspath to @a dst_abspath, by scheduling @a dst_abspath
+ * for addition to the repository, remembering the history. Mark @a src_abspath
+ * as deleted after moving.@a wc_ctx is used for accessing the working copy and
+ * must contain a write lock for the parent directory of @a src_abspath and
+ * @a dst_abspath.
+ *
+ * If @a metadata_only is TRUE then this is a database-only operation and
+ * the working directories and files are not changed.
+ *
+ * @a src_abspath must be a file or directory under version control;
+ * the parent of @a dst_abspath must be a directory under version control
+ * in the same working copy; @a dst_abspath will be the name of the copied
+ * item, and it must not exist already if @a metadata_only is FALSE. Note that
+ * when @a src points to a versioned file, the working file doesn't
+ * necessarily exist in which case its text-base is used instead.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton at
+ * various points during the operation. If it returns an error
+ * (typically #SVN_ERR_CANCELLED), return that error immediately.
+ *
+ * If @a notify_func is non-NULL, call it with @a notify_baton and the path
+ * of the root node (only) of the destination.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * @see svn_client_move7()
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_move(svn_wc_context_t *wc_ctx,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t metadata_only,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Schedule @a local_abspath for deletion. It will be deleted from the
+ * repository on the next commit. If @a local_abspath refers to a
+ * directory, then a recursive deletion will occur. @a wc_ctx must hold
+ * a write lock for the parent of @a local_abspath, @a local_abspath itself
+ * and everything below @a local_abspath.
+ *
+ * If @a keep_local is FALSE, this function immediately deletes all files,
+ * modified and unmodified, versioned and of @a delete_unversioned is TRUE,
+ * unversioned from the working copy.
+ * It also immediately deletes unversioned directories and directories that
+ * are scheduled to be added below @a local_abspath. Only versioned may
+ * remain in the working copy, these get deleted by the update following
+ * the commit.
+ *
+ * If @a keep_local is TRUE, all files and directories will be kept in the
+ * working copy (and will become unversioned on the next commit).
+ *
+ * If @a delete_unversioned_target is TRUE and @a local_abspath is not
+ * versioned, @a local_abspath will be handled as an added files without
+ * history. So it will be deleted if @a keep_local is FALSE. If @a
+ * delete_unversioned is FALSE and @a local_abspath is not versioned a
+ * #SVN_ERR_WC_PATH_NOT_FOUND error will be returned.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton at
+ * various points during the operation. If it returns an error
+ * (typically #SVN_ERR_CANCELLED), return that error immediately.
+ *
+ * For each path marked for deletion, @a notify_func will be called with
+ * the @a notify_baton and that path. The @a notify_func callback may be
+ * @c NULL if notification is not needed.
+ *
+ * Use @a scratch_pool for temporary allocations. It may be cleared
+ * immediately upon returning from this function.
+ *
+ * @since New in 1.7.
+ */
+ /* ### BH: Maybe add a delete_switched flag that allows deny switched
+ nodes like file externals? */
+svn_error_t *
+svn_wc_delete4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t keep_local,
+ svn_boolean_t delete_unversioned_target,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_wc_delete4, but uses an access baton and relative path
+ * instead of a working copy context and absolute path. @a adm_access
+ * must hold a write lock for the parent of @a path.
+ *
+ * @c delete_unversioned_target will always be set to TRUE.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_delete3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_boolean_t keep_local,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_delete3(), but with @a keep_local always set to FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_delete2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_delete2(), but takes an #svn_wc_notify_func_t instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_delete(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Schedule the single node that exists on disk at @a local_abspath for
+ * addition to the working copy. The added node will have the properties
+ * provided in @a props, or none if that is NULL.
+ *
+ * Check and canonicalize the properties in the same way as
+ * svn_wc_prop_set4(). Return an error and don't add the node if the
+ * properties are not valid on this node. Unlike svn_wc_prop_set4()
+ * there is no option to skip some of the checks and canonicalizations.
+ *
+ * ### The error code on validity check failure should be specified, and
+ * preferably should be a single code.
+ *
+ * The versioned state of the parent path must be a modifiable directory,
+ * and the versioned state of @a local_abspath must be either nonexistent or
+ * deleted; if deleted, the new node will be a replacement.
+ *
+ * If @a local_abspath does not exist as file, directory or symlink, return
+ * #SVN_ERR_WC_PATH_NOT_FOUND.
+ *
+ * ### TODO: Split into add_dir, add_file, add_symlink?
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc_add_from_disk2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Similar to svn_wc_add_from_disk2(), but always passes NULL for @a
+ * props.
+ *
+ * This is a replacement for svn_wc_add4() case 2a (which see for
+ * details).
+
+ * @see svn_wc_add4()
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_add_from_disk(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Put @a local_abspath under version control by registering it as addition
+ * or copy in the database containing its parent. The new node is scheduled
+ * for addition to the repository below its parent node.
+ *
+ * 1) If the node is already versioned, it MUST BE the root of a separate
+ * working copy from the same repository as the parent WC. The new node
+ * and anything below it will be scheduled for addition inside the parent
+ * working copy as a copy of the original location. The separate working
+ * copy will be integrated by this step. In this case, which is only used
+ * by code like that of "svn cp URL@rev path" @a copyfrom_url and
+ * @a copyfrom_rev MUST BE the url and revision of @a local_abspath
+ * in the separate working copy.
+ *
+ * 2a) If the node was not versioned before it will be scheduled as a local
+ * addition or 2b) if @a copyfrom_url and @a copyfrom_rev are set as a copy
+ * of that location. In this last case the function doesn't set the pristine
+ * version (of a file) and/or pristine properties, which callers should
+ * handle via different APIs. Usually it is easier to call
+ * svn_wc_add_repos_file4() (### or a possible svn_wc_add_repos_dir()) than
+ * using this variant.
+ *
+ * If @a local_abspath does not exist as file, directory or symlink, return
+ * #SVN_ERR_WC_PATH_NOT_FOUND.
+ *
+ * If @a local_abspath is an unversioned directory, record @a depth on it;
+ * otherwise, ignore @a depth. (Use #svn_depth_infinity unless you exactly
+ * know what you are doing, or you may create an unexpected sparse working
+ * copy)
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton at
+ * various points during the operation. If it returns an error
+ * (typically #SVN_ERR_CANCELLED), return that error immediately.
+ *
+ * When the @a local_abspath has been added, then @a notify_func will be
+ * called (if it is not @c NULL) with the @a notify_baton and the path.
+ *
+ * @note Case 1 is deprecated. Consider doing a WC-to-WC copy instead.
+ * @note For case 2a, prefer svn_wc_add_from_disk().
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_add4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_wc_add4(), but with an access baton
+ * and relative path instead of a context and absolute path.
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_add3(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ svn_depth_t depth,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_add3(), but with the @a depth parameter always
+ * #svn_depth_infinity.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_add2(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_add2(), but takes an #svn_wc_notify_func_t instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_add(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+/** Add a file to a working copy at @a local_abspath, obtaining the
+ * text-base's contents from @a new_base_contents, the wc file's
+ * content from @a new_contents, its unmodified properties from @a
+ * new_base_props and its actual properties from @a new_props. Use
+ * @a wc_ctx for accessing the working copy.
+ *
+ * The unmodified text and props normally come from the repository
+ * file represented by the copyfrom args, see below. The new file
+ * will be marked as copy.
+ *
+ * @a new_contents and @a new_props may be NULL, in which case
+ * the working copy text and props are taken from the base files with
+ * appropriate translation of the file's content.
+ *
+ * @a new_contents must be provided in Normal Form. This is required
+ * in order to pass both special and non-special files through a stream.
+ *
+ * @a wc_ctx must contain a write lock for the parent of @a local_abspath.
+ *
+ * If @a copyfrom_url is non-NULL, then @a copyfrom_rev must be a
+ * valid revision number, and together they are the copyfrom history
+ * for the new file.
+ *
+ * The @a cancel_func and @a cancel_baton are a standard cancellation
+ * callback, or NULL if no callback is needed. @a notify_func and
+ * @a notify_baton are a notification callback, and (if not NULL)
+ * will be notified of the addition of this file.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * ### This function is very redundant with svn_wc_add(). Ideally,
+ * we'd merge them, so that svn_wc_add() would just take optional
+ * new_props and optional copyfrom information. That way it could be
+ * used for both 'svn add somefilesittingonmydisk' and for adding
+ * files from repositories, with or without copyfrom history.
+ *
+ * The problem with this Ideal Plan is that svn_wc_add() also takes
+ * care of recursive URL-rewriting. There's a whole comment in its
+ * doc string about how that's really weird, outside its core mission,
+ * etc, etc. So another part of the Ideal Plan is that that
+ * functionality of svn_wc_add() would move into a separate function.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_stream_t *new_base_contents,
+ svn_stream_t *new_contents,
+ apr_hash_t *new_base_props,
+ apr_hash_t *new_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_add_repos_file4, but uses access batons and a
+ * relative path instead of a working copy context and absolute path.
+ *
+ * ### NOTE: the notification callback/baton is not yet used.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_add_repos_file3(const char *dst_path,
+ svn_wc_adm_access_t *adm_access,
+ svn_stream_t *new_base_contents,
+ svn_stream_t *new_contents,
+ apr_hash_t *new_base_props,
+ apr_hash_t *new_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/** Same as svn_wc_add_repos_file3(), except that it has pathnames rather
+ * than streams for the text base, and actual text, and has no cancellation.
+ *
+ * @since New in 1.4.
+ * @deprecated Provided for compatibility with the 1.5 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_add_repos_file2(const char *dst_path,
+ svn_wc_adm_access_t *adm_access,
+ const char *new_text_base_path,
+ const char *new_text_path,
+ apr_hash_t *new_base_props,
+ apr_hash_t *new_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool);
+
+/** Same as svn_wc_add_repos_file3(), except that it doesn't have the
+ * BASE arguments or cancellation.
+ *
+ * @deprecated Provided for compatibility with the 1.3 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_add_repos_file(const char *dst_path,
+ svn_wc_adm_access_t *adm_access,
+ const char *new_text_path,
+ apr_hash_t *new_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool);
+
+
+/** Remove @a local_abspath from revision control. @a wc_ctx must
+ * hold a write lock on the parent of @a local_abspath, or if that is a
+ * WC root then on @a local_abspath itself.
+ *
+ * If @a local_abspath is a file, all its info will be removed from the
+ * administrative area. If @a local_abspath is a directory, then the
+ * administrative area will be deleted, along with *all* the administrative
+ * areas anywhere in the tree below @a adm_access.
+ *
+ * Normally, only administrative data is removed. However, if
+ * @a destroy_wf is TRUE, then all working file(s) and dirs are deleted
+ * from disk as well. When called with @a destroy_wf, any locally
+ * modified files will *not* be deleted, and the special error
+ * #SVN_ERR_WC_LEFT_LOCAL_MOD might be returned. (Callers only need to
+ * check for this special return value if @a destroy_wf is TRUE.)
+ *
+ * If @a instant_error is TRUE, then return
+ * #SVN_ERR_WC_LEFT_LOCAL_MOD the instant a locally modified file is
+ * encountered. Otherwise, leave locally modified files in place and
+ * return the error only after all the recursion is complete.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton at
+ * various points during the removal. If it returns an error
+ * (typically #SVN_ERR_CANCELLED), return that error immediately.
+ *
+ * WARNING: This routine is exported for careful, measured use by
+ * libsvn_client. Do *not* call this routine unless you really
+ * understand what the heck you're doing.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_remove_from_revision_control2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t destroy_wf,
+ svn_boolean_t instant_error,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_remove_from_revision_control2() but with a name
+ * and access baton.
+ *
+ * WARNING: This routine was exported for careful, measured use by
+ * libsvn_client. Do *not* call this routine unless you really
+ * understand what the heck you're doing.
+ *
+ * @deprecated Provided for compatibility with the 1.6 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_remove_from_revision_control(svn_wc_adm_access_t *adm_access,
+ const char *name,
+ svn_boolean_t destroy_wf,
+ svn_boolean_t instant_error,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Assuming @a local_abspath is under version control or a tree conflict
+ * victim and in a state of conflict, then take @a local_abspath *out*
+ * of this state. If @a resolve_text is TRUE then any text conflict is
+ * resolved, if @a resolve_tree is TRUE then any tree conflicts are
+ * resolved. If @a resolve_prop is set to "" all property conflicts are
+ * resolved, if it is set to any other string value, conflicts on that
+ * specific property are resolved and when resolve_prop is NULL, no
+ * property conflicts are resolved.
+ *
+ * If @a depth is #svn_depth_empty, act only on @a local_abspath; if
+ * #svn_depth_files, resolve @a local_abspath and its conflicted file
+ * children (if any); if #svn_depth_immediates, resolve @a local_abspath
+ * and all its immediate conflicted children (both files and directories,
+ * if any); if #svn_depth_infinity, resolve @a local_abspath and every
+ * conflicted file or directory anywhere beneath it.
+ *
+ * If @a conflict_choice is #svn_wc_conflict_choose_base, resolve the
+ * conflict with the old file contents; if
+ * #svn_wc_conflict_choose_mine_full, use the original working contents;
+ * if #svn_wc_conflict_choose_theirs_full, the new contents; and if
+ * #svn_wc_conflict_choose_merged, don't change the contents at all,
+ * just remove the conflict status, which is the pre-1.5 behavior.
+ *
+ * #svn_wc_conflict_choose_theirs_conflict and
+ * #svn_wc_conflict_choose_mine_conflict are not legal for binary
+ * files or properties.
+ *
+ * @a wc_ctx is a working copy context, with a write lock, for @a
+ * local_abspath.
+ *
+ * Needless to say, this function doesn't touch conflict markers or
+ * anything of that sort -- only a human can semantically resolve a
+ * conflict. Instead, this function simply marks a file as "having
+ * been resolved", clearing the way for a commit.
+ *
+ * The implementation details are opaque, as our "conflicted" criteria
+ * might change over time. (At the moment, this routine removes the
+ * three fulltext 'backup' files and any .prej file created in a conflict,
+ * and modifies @a local_abspath's entry.)
+ *
+ * If @a local_abspath is not under version control and not a tree
+ * conflict, return #SVN_ERR_ENTRY_NOT_FOUND. If @a path isn't in a
+ * state of conflict to begin with, do nothing, and return #SVN_NO_ERROR.
+ *
+ * If @c local_abspath was successfully taken out of a state of conflict,
+ * report this information to @c notify_func (if non-@c NULL.) If only
+ * text, only property, or only tree conflict resolution was requested,
+ * and it was successful, then success gets reported.
+ *
+ * Temporary allocations will be performed in @a scratch_pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_resolved_conflict5(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t resolve_text,
+ const char *resolve_prop,
+ svn_boolean_t resolve_tree,
+ svn_wc_conflict_choice_t conflict_choice,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_resolved_conflict5, but takes an absolute path
+ * and an access baton. This version doesn't support resolving a specific
+ * property.conflict.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_resolved_conflict4(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t resolve_text,
+ svn_boolean_t resolve_props,
+ svn_boolean_t resolve_tree,
+ svn_depth_t depth,
+ svn_wc_conflict_choice_t conflict_choice,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_wc_resolved_conflict4(), but without tree-conflict
+ * resolution support.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_resolved_conflict3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t resolve_text,
+ svn_boolean_t resolve_props,
+ svn_depth_t depth,
+ svn_wc_conflict_choice_t conflict_choice,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_wc_resolved_conflict3(), but without automatic conflict
+ * resolution support, and with @a depth set according to @a recurse:
+ * if @a recurse is TRUE, @a depth is #svn_depth_infinity, else it is
+ * #svn_depth_files.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_resolved_conflict2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t resolve_text,
+ svn_boolean_t resolve_props,
+ svn_boolean_t recurse,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_resolved_conflict2(), but takes an
+ * svn_wc_notify_func_t and doesn't have cancellation support.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_resolved_conflict(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t resolve_text,
+ svn_boolean_t resolve_props,
+ svn_boolean_t recurse,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+
+/* Commits. */
+
+
+/**
+ * Storage type for queued post-commit data.
+ *
+ * @since New in 1.5.
+ */
+typedef struct svn_wc_committed_queue_t svn_wc_committed_queue_t;
+
+
+/**
+ * Create a queue for use with svn_wc_queue_committed() and
+ * svn_wc_process_committed_queue().
+ *
+ * The returned queue and all further allocations required for queuing
+ * new items will also be done from @a pool.
+ *
+ * @since New in 1.5.
+ */
+svn_wc_committed_queue_t *
+svn_wc_committed_queue_create(apr_pool_t *pool);
+
+
+/**
+ * Queue committed items to be processed later by
+ * svn_wc_process_committed_queue2().
+ *
+ * Record in @a queue that @a local_abspath will need to be bumped
+ * after a commit succeeds.
+ *
+ * If non-NULL, @a wcprop_changes is an array of <tt>svn_prop_t *</tt>
+ * changes to wc properties; if an #svn_prop_t->value is NULL, then
+ * that property is deleted.
+ * ### [JAF] No, a prop whose value is NULL is ignored, not deleted. This
+ * ### seems to be not a set of changes but rather the new complete set of
+ * ### props. And it's renamed to 'new_dav_cache' inside; why?
+ *
+ * If @a remove_lock is @c TRUE, any entryprops related to a repository
+ * lock will be removed.
+ *
+ * If @a remove_changelist is @c TRUE, any association with a
+ * changelist will be removed.
+ *
+ *
+ * If @a sha1_checksum is non-NULL, use it to identify the node's pristine
+ * text.
+ *
+ * If @a recurse is TRUE and @a local_abspath is a directory, then bump every
+ * versioned object at or under @a local_abspath. This is usually done for
+ * copied trees.
+ *
+ * ### In the present implementation, if a recursive directory item is in
+ * the queue, then any children (at any depth) of that directory that
+ * are also in the queue as separate items will get:
+ * 'wcprop_changes' = NULL;
+ * 'remove_lock' = FALSE;
+ * 'remove_changelist' from the recursive parent item;
+ * and any children (at any depth) of that directory that are NOT in
+ * the queue as separate items will get:
+ * 'wcprop_changes' = NULL;
+ * 'remove_lock' = FALSE;
+ * 'remove_changelist' from the recursive parent item;
+ *
+ * @note the @a recurse parameter should be used with extreme care since
+ * it will bump ALL nodes under the directory, regardless of their
+ * actual inclusion in the new revision.
+ *
+ * All pointer data passed to this function (@a local_abspath,
+ * @a wcprop_changes and the checksums) should remain valid until the
+ * queue has been processed by svn_wc_process_committed_queue2().
+ *
+ * Temporary allocations will be performed in @a scratch_pool, and persistent
+ * allocations will use the same pool as @a queue used when it was created.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_queue_committed3(svn_wc_committed_queue_t *queue,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t recurse,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ svn_boolean_t remove_changelist,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool);
+
+/** Same as svn_wc_queue_committed3() except @a path doesn't have to be an
+ * abspath and @a adm_access is unused and a SHA-1 checksum cannot be
+ * specified.
+ *
+ * @since New in 1.6.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_queue_committed2(svn_wc_committed_queue_t *queue,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ svn_boolean_t remove_changelist,
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *scratch_pool);
+
+
+/** Same as svn_wc_queue_committed2() but the @a queue parameter has an
+ * extra indirection and @a digest is supplied instead of a checksum type.
+ *
+ * @note despite the extra indirection, this function does NOT allocate
+ * the queue for you. svn_wc_committed_queue_create() must be called.
+ *
+ * @since New in 1.5
+ *
+ * @deprecated Provided for backwards compatibility with 1.5
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_queue_committed(svn_wc_committed_queue_t **queue,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ svn_boolean_t remove_changelist,
+ const unsigned char *digest,
+ apr_pool_t *pool);
+
+
+/**
+ * Bump all items in @a queue to @a new_revnum after a commit succeeds.
+ * @a rev_date and @a rev_author are the (server-side) date and author
+ * of the new revision; one or both may be @c NULL.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton to determine
+ * if the client wants to cancel the operation.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_process_committed_queue2(svn_wc_committed_queue_t *queue,
+ svn_wc_context_t *wc_ctx,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/** @see svn_wc_process_committed_queue2()
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_process_committed_queue(svn_wc_committed_queue_t *queue,
+ svn_wc_adm_access_t *adm_access,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ apr_pool_t *pool);
+
+
+/**
+ * @note this function has improper expectations around the operation and
+ * execution of other parts of the Subversion WC library. The resulting
+ * coupling makes this interface near-impossible to support. Documentation
+ * has been removed, as a result.
+ *
+ * @deprecated Use the svn_wc_committed_queue_* functions instead. Provided
+ * for backwards compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_process_committed4(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ svn_boolean_t remove_changelist,
+ const unsigned char *digest,
+ apr_pool_t *pool);
+
+/** @see svn_wc_process_committed4()
+ *
+ * @deprecated Use the svn_wc_committed_queue_* functions instead. Provided
+ * for backwards compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_process_committed3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ const unsigned char *digest,
+ apr_pool_t *pool);
+
+/** @see svn_wc_process_committed4()
+ *
+ * @deprecated Use the svn_wc_committed_queue_* functions instead. Provided
+ * for backwards compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_process_committed2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ apr_pool_t *pool);
+
+/** @see svn_wc_process_committed4()
+ *
+ * @deprecated Use the svn_wc_committed_queue_* functions instead. Provided
+ * for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_process_committed(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ const apr_array_header_t *wcprop_changes,
+ apr_pool_t *pool);
+
+
+
+
+
+/**
+ * Do a depth-first crawl in a working copy, beginning at @a local_abspath,
+ * using @a wc_ctx for accessing the working copy.
+ *
+ * Communicate the `state' of the working copy's revisions and depths
+ * to @a reporter/@a report_baton. Obviously, if @a local_abspath is a
+ * file instead of a directory, this depth-first crawl will be a short one.
+ *
+ * No locks or logs are created, nor are any animals harmed in the
+ * process unless @a restore_files is TRUE. No cleanup is necessary.
+ *
+ * After all revisions are reported, @a reporter->finish_report() is
+ * called, which immediately causes the RA layer to update the working
+ * copy. Thus the return value may very well reflect the result of
+ * the update!
+ *
+ * If @a depth is #svn_depth_empty, then report state only for
+ * @a path itself. If #svn_depth_files, do the same and include
+ * immediate file children of @a path. If #svn_depth_immediates,
+ * then behave as if for #svn_depth_files but also report the
+ * property states of immediate subdirectories. If @a depth is
+ * #svn_depth_infinity, then report state fully recursively. All
+ * descents are only as deep as @a path's own depth permits, of
+ * course. If @a depth is #svn_depth_unknown, then just use
+ * #svn_depth_infinity, which in practice means depth of @a path.
+ *
+ * Iff @a honor_depth_exclude is TRUE, the crawler will report paths
+ * whose ambient depth is #svn_depth_exclude as being excluded, and
+ * thus prevent the server from pushing update data for those paths;
+ * therefore, don't set this flag if you wish to pull in excluded paths.
+ * Note that #svn_depth_exclude on the target @a path is never
+ * honored, even if @a honor_depth_exclude is TRUE, because we need to
+ * be able to explicitly pull in a target. For example, if this is
+ * the working copy...
+ *
+ * svn co greek_tree_repos wc_dir
+ * svn up --set-depth exclude wc_dir/A/B/E # now A/B/E is excluded
+ *
+ * ...then 'svn up wc_dir/A/B' would report E as excluded (assuming
+ * @a honor_depth_exclude is TRUE), but 'svn up wc_dir/A/B/E' would
+ * not, because the latter is trying to explicitly pull in E. In
+ * general, we never report the update target as excluded.
+ *
+ * Iff @a depth_compatibility_trick is TRUE, then set the @c start_empty
+ * flag on @a reporter->set_path() and @a reporter->link_path() calls
+ * as necessary to trick a pre-1.5 (i.e., depth-unaware) server into
+ * sending back all the items the client might need to upgrade a
+ * working copy from a shallower depth to a deeper one.
+ *
+ * If @a restore_files is TRUE, then unexpectedly missing working files
+ * will be restored from the administrative directory's cache. For each
+ * file restored, the @a notify_func function will be called with the
+ * @a notify_baton and the path of the restored file. @a notify_func may
+ * be @c NULL if this notification is not required. If @a
+ * use_commit_times is TRUE, then set restored files' timestamps to
+ * their last-commit-times.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_ra_reporter3_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_depth_t depth,
+ svn_boolean_t honor_depth_exclude,
+ svn_boolean_t depth_compatibility_trick,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_wc_crawl_revisions5, but with a relative path and
+ * access baton instead of an absolute path and wc_ctx.
+ *
+ * Passes NULL for @a cancel_func and @a cancel_baton.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_crawl_revisions4(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_ra_reporter3_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_depth_t depth,
+ svn_boolean_t honor_depth_exclude,
+ svn_boolean_t depth_compatibility_trick,
+ svn_boolean_t use_commit_times,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_wc_crawl_revisions4, but with @a honor_depth_exclude always
+ * set to false.
+ *
+ * @deprecated Provided for compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_crawl_revisions3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_ra_reporter3_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_depth_t depth,
+ svn_boolean_t depth_compatibility_trick,
+ svn_boolean_t use_commit_times,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_crawl_revisions3, but taking svn_ra_reporter2_t
+ * instead of svn_ra_reporter3_t, and therefore only able to report
+ * #svn_depth_infinity for depths; and taking @a recurse instead of @a
+ * depth; and with @a depth_compatibility_trick always false.
+ *
+ * @deprecated Provided for compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_crawl_revisions2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_ra_reporter2_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_boolean_t recurse,
+ svn_boolean_t use_commit_times,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_crawl_revisions2(), but takes an #svn_wc_notify_func_t
+ * and a #svn_ra_reporter_t instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_crawl_revisions(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_ra_reporter_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_boolean_t recurse,
+ svn_boolean_t use_commit_times,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool);
+
+
+/**
+ * @defgroup svn_wc_roots Working copy roots
+ * @{
+ */
+
+/** If @a is_wcroot is not @c NULL, set @a *is_wcroot to @c TRUE if @a
+ * local_abspath is the root of the working copy, otherwise to @c FALSE.
+ *
+ * If @a is_switched is not @c NULL, set @a *is_switched to @c TRUE if @a
+ * local_abspath is not the root of the working copy, and switched against its
+ * parent.
+ *
+ * If @a kind is not @c NULL, set @a *kind to the node kind of @a
+ * local_abspath.
+ *
+ * Use @a scratch_pool for any temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc_check_root(svn_boolean_t *is_wcroot,
+ svn_boolean_t *is_switched,
+ svn_node_kind_t *kind,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/** Set @a *wc_root to @c TRUE if @a local_abspath represents a "working copy
+ * root", @c FALSE otherwise. Here, @a local_abspath is a "working copy root"
+ * if its parent directory is not a WC or if it is switched. Also, a deleted
+ * tree-conflict victim is considered a "working copy root" because it has no
+ * URL.
+ *
+ * If @a local_abspath is not found, return the error #SVN_ERR_ENTRY_NOT_FOUND.
+ *
+ * Use @a scratch_pool for any temporary allocations.
+ *
+ * @note For legacy reasons only a directory can be a wc-root. However, this
+ * function will also set wc_root to @c TRUE for a switched file.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API. Consider
+ * using svn_wc_check_root() instead.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_is_wc_root2(svn_boolean_t *wc_root,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Similar to svn_wc_is_wc_root2(), but with an access baton and relative
+ * path.
+ *
+ * @note If @a path is '', this function will always return @c TRUE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_is_wc_root(svn_boolean_t *wc_root,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/* Updates. */
+
+/** Conditionally split @a path into an @a anchor and @a target for the
+ * purpose of updating and committing.
+ *
+ * @a anchor is the directory at which the update or commit editor
+ * should be rooted.
+ *
+ * @a target is the actual subject (relative to the @a anchor) of the
+ * update/commit, or "" if the @a anchor itself is the subject.
+ *
+ * Allocate @a anchor and @a target in @a result_pool; @a scratch_pool
+ * is used for temporary allocations.
+ *
+ * @note Even though this API uses a #svn_wc_context_t, it accepts a
+ * (possibly) relative path and returns a (possibly) relative path in
+ * @a *anchor. The reason being that the outputs are generally used to
+ * open access batons, and such opening currently requires relative paths.
+ * In the long-run, I expect this API to be removed from 1.7, due to the
+ * remove of access batons, but for the time being, the #svn_wc_context_t
+ * parameter allows us to avoid opening a duplicate database, just for this
+ * function.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_get_actual_target2(const char **anchor,
+ const char **target,
+ svn_wc_context_t *wc_ctx,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Similar to svn_wc_get_actual_target2(), but without the wc context, and
+ * with a absolute path.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_actual_target(const char *path,
+ const char **anchor,
+ const char **target,
+ apr_pool_t *pool);
+
+
+/**
+ * @defgroup svn_wc_update_switch Update and switch (update-like functionality)
+ * @{
+ */
+
+/**
+ * A simple callback type to wrap svn_ra_get_file(); see that
+ * docstring for more information.
+ *
+ * This technique allows libsvn_client to 'wrap' svn_ra_get_file() and
+ * pass it down into libsvn_wc functions, thus allowing the WC layer
+ * to legally call the RA function via (blind) callback.
+ *
+ * @since New in 1.5
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+typedef svn_error_t *(*svn_wc_get_file_t)(void *baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool);
+
+/**
+ * A simple callback type to wrap svn_ra_get_dir2() for avoiding issue #3569,
+ * where a directory is updated to a revision without some of its children
+ * recorded in the working copy. A future update won't bring these files in
+ * because the repository assumes they are already there.
+ *
+ * We really only need the names of the dirents for a not-present marking,
+ * but we also store the node-kind if we receive one.
+ *
+ * @a *dirents should be set to a hash mapping <tt>const char *</tt> child
+ * names, to <tt>const svn_dirent_t *</tt> instances.
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_wc_dirents_func_t)(void *baton,
+ apr_hash_t **dirents,
+ const char *repos_root_url,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * DEPRECATED -- please use APIs from svn_client.h
+ *
+ * ---
+ *
+ * Set @a *editor and @a *edit_baton to an editor and baton for updating a
+ * working copy.
+ *
+ * @a anchor_abspath is a local working copy directory, with a fully recursive
+ * write lock in @a wc_ctx, which will be used as the root of our editor.
+ *
+ * @a target_basename is the entry in @a anchor_abspath that will actually be
+ * updated, or the empty string if all of @a anchor_abspath should be updated.
+ *
+ * The editor invokes @a notify_func with @a notify_baton as the update
+ * progresses, if @a notify_func is non-NULL.
+ *
+ * If @a cancel_func is non-NULL, the editor will invoke @a cancel_func with
+ * @a cancel_baton as the update progresses to see if it should continue.
+ *
+ * If @a conflict_func is non-NULL, then invoke it with @a
+ * conflict_baton whenever a conflict is encountered, giving the
+ * callback a chance to resolve the conflict before the editor takes
+ * more drastic measures (such as marking a file conflicted, or
+ * bailing out of the update).
+ *
+ * If @a external_func is non-NULL, then invoke it with @a external_baton
+ * whenever external changes are encountered, giving the callback a chance
+ * to store the external information for processing.
+ *
+ * If @a diff3_cmd is non-NULL, then use it as the diff3 command for
+ * any merging; otherwise, use the built-in merge code.
+ *
+ * @a preserved_exts is an array of filename patterns which, when
+ * matched against the extensions of versioned files, determine for
+ * which such files any related generated conflict files will preserve
+ * the original file's extension as their own. If a file's extension
+ * does not match any of the patterns in @a preserved_exts (which is
+ * certainly the case if @a preserved_exts is @c NULL or empty),
+ * generated conflict files will carry Subversion's custom extensions.
+ *
+ * @a target_revision is a pointer to a revision location which, after
+ * successful completion of the drive of this editor, will be
+ * populated with the revision to which the working copy was updated.
+ *
+ * If @a use_commit_times is TRUE, then all edited/added files will
+ * have their working timestamp set to the last-committed-time. If
+ * FALSE, the working files will be touched with the 'now' time.
+ *
+ * If @a allow_unver_obstructions is TRUE, then allow unversioned
+ * obstructions when adding a path.
+ *
+ * If @a adds_as_modification is TRUE, a local addition at the same path
+ * as an incoming addition of the same node kind results in a normal node
+ * with a possible local modification, instead of a tree conflict.
+ *
+ * If @a depth is #svn_depth_infinity, update fully recursively.
+ * Else if it is #svn_depth_immediates, update the uppermost
+ * directory, its file entries, and the presence or absence of
+ * subdirectories (but do not descend into the subdirectories).
+ * Else if it is #svn_depth_files, update the uppermost directory
+ * and its immediate file entries, but not subdirectories.
+ * Else if it is #svn_depth_empty, update exactly the uppermost
+ * target, and don't touch its entries.
+ *
+ * If @a depth_is_sticky is set and @a depth is not
+ * #svn_depth_unknown, then in addition to updating PATHS, also set
+ * their sticky ambient depth value to @a depth.
+ *
+ * If @a server_performs_filtering is TRUE, assume that the server handles
+ * the ambient depth filtering, so this doesn't have to be handled in the
+ * editor.
+ *
+ * If @a clean_checkout is TRUE, assume that we are checking out into an
+ * empty directory, and so bypass a number of conflict checks that are
+ * unnecessary in this case.
+ *
+ * If @a fetch_dirents_func is not NULL, the update editor may call this
+ * callback, when asked to perform a depth restricted update. It will do this
+ * before returning the editor to allow using the primary ra session for this.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_update_editor4(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t server_performs_filtering,
+ svn_boolean_t clean_checkout,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_get_update_editor4, but uses access batons and relative
+ * path instead of a working copy context-abspath pair and
+ * svn_wc_traversal_info_t instead of an externals callback. Also,
+ * @a fetch_func and @a fetch_baton are ignored.
+ *
+ * If @a ti is non-NULL, record traversal info in @a ti, for use by
+ * post-traversal accessors such as svn_wc_edited_externals().
+ *
+ * All locks, both those in @a anchor and newly acquired ones, will be
+ * released when the editor driver calls @c close_edit.
+ *
+ * Always sets @a adds_as_modification to TRUE, @a server_performs_filtering
+ * and @a clean_checkout to FALSE.
+ *
+ * Uses a svn_wc_conflict_resolver_func_t conflict resolver instead of a
+ * svn_wc_conflict_resolver_func2_t.
+ *
+ * This function assumes that @a diff3_cmd is path encoded. Later versions
+ * assume utf-8.
+ *
+ * Always passes a null dirent function.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_update_editor3(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_conflict_resolver_func_t conflict_func,
+ void *conflict_baton,
+ svn_wc_get_file_t fetch_func,
+ void *fetch_baton,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *ti,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_wc_get_update_editor3() but with the @a
+ * allow_unver_obstructions parameter always set to FALSE, @a
+ * conflict_func and baton set to NULL, @a fetch_func and baton set to
+ * NULL, @a preserved_exts set to NULL, @a depth_is_sticky set to
+ * FALSE, and @a depth set according to @a recurse: if @a recurse is
+ * TRUE, pass #svn_depth_infinity, if FALSE, pass #svn_depth_files.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_update_editor2(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t recurse,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const char *diff3_cmd,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *ti,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_get_update_editor2(), but takes an svn_wc_notify_func_t
+ * instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_update_editor(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t recurse,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const char *diff3_cmd,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *ti,
+ apr_pool_t *pool);
+
+/**
+ * DEPRECATED -- please use APIs from svn_client.h
+ *
+ * ---
+ *
+ * A variant of svn_wc_get_update_editor4().
+ *
+ * Set @a *editor and @a *edit_baton to an editor and baton for "switching"
+ * a working copy to a new @a switch_url. (Right now, this URL must be
+ * within the same repository that the working copy already comes
+ * from.) @a switch_url must not be @c NULL.
+ *
+ * All other parameters behave as for svn_wc_get_update_editor4().
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_switch_editor4(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ const char *switch_url,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t server_performs_filtering,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_get_switch_editor4, but uses access batons and relative
+ * path instead of a working copy context and svn_wc_traversal_info_t instead
+ * of an externals callback.
+ *
+ * If @a ti is non-NULL, record traversal info in @a ti, for use by
+ * post-traversal accessors such as svn_wc_edited_externals().
+ *
+ * All locks, both those in @a anchor and newly acquired ones, will be
+ * released when the editor driver calls @c close_edit.
+ *
+ * Always sets @a server_performs_filtering to FALSE.
+ *
+ * Uses a svn_wc_conflict_resolver_func_t conflict resolver instead of a
+ * svn_wc_conflict_resolver_func2_t.
+ *
+ * This function assumes that @a diff3_cmd is path encoded. Later versions
+ * assume utf-8.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_switch_editor3(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ const char *switch_url,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_conflict_resolver_func_t conflict_func,
+ void *conflict_baton,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *ti,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_get_switch_editor3() but with the
+ * @a allow_unver_obstructions parameter always set to FALSE,
+ * @a preserved_exts set to NULL, @a conflict_func and baton set to NULL,
+ * @a depth_is_sticky set to FALSE, and @a depth set according to @a
+ * recurse: if @a recurse is TRUE, pass #svn_depth_infinity, if
+ * FALSE, pass #svn_depth_files.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_switch_editor2(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ const char *switch_url,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t recurse,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const char *diff3_cmd,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *ti,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_get_switch_editor2(), but takes an
+ * #svn_wc_notify_func_t instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_switch_editor(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ const char *switch_url,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t recurse,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const char *diff3_cmd,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *ti,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/**
+ * @defgroup svn_wc_properties Properties
+ * @{
+ */
+
+/** Set @a *props to a hash table mapping <tt>char *</tt> names onto
+ * <tt>svn_string_t *</tt> values for all the regular properties of
+ * @a local_abspath. Allocate the table, names, and values in
+ * @a result_pool. If the node has no properties, then an empty hash
+ * is returned. Use @a wc_ctx to access the working copy, and @a
+ * scratch_pool for temporary allocations.
+ *
+ * If the node does not exist, #SVN_ERR_WC_PATH_NOT_FOUND is returned.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_prop_list2(apr_hash_t **props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_prop_list2() but with a #svn_wc_adm_access_t /
+ * relative path parameter pair.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_prop_list(apr_hash_t **props,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+
+/** Return the set of "pristine" properties for @a local_abspath.
+ *
+ * There are node states where properties do not make sense. For these
+ * cases, NULL will be returned in @a *props. Otherwise, a hash table
+ * will always be returned (but may be empty, indicating no properties).
+ *
+ * If the node is locally-added, then @a *props will be set to NULL since
+ * pristine properties are undefined. Note: if this addition is replacing a
+ * previously-deleted node, then the replaced node's properties are not
+ * available until the addition is reverted.
+ *
+ * If the node has been copied (from another node in the repository), then
+ * the pristine properties will correspond to those original properties.
+ *
+ * If the node is locally-deleted, these properties will correspond to
+ * the BASE node's properties, as checked-out from the repository. Note: if
+ * this deletion is a child of a copy, then the pristine properties will
+ * correspond to that copy's properties, not any potential BASE node. The
+ * BASE node's properties will not be accessible until the copy is reverted.
+ *
+ * Nodes that are incomplete, excluded, absent, or not present at the
+ * node's revision will return NULL in @a props.
+ *
+ * If the node is not versioned, SVN_ERR_WC_PATH_NOT_FOUND will be returned.
+ *
+ * @a props will be allocated in @a result_pool, and all temporary
+ * allocations will be performed in @a scratch_pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_get_pristine_props(apr_hash_t **props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Set @a *value to the value of property @a name for @a local_abspath,
+ * allocating @a *value in @a result_pool. If no such prop, set @a *value
+ * to @c NULL. @a name may be a regular or wc property; if it is an
+ * entry property, return the error #SVN_ERR_BAD_PROP_KIND. @a wc_ctx
+ * is used to access the working copy.
+ *
+ * If @a local_abspath is not a versioned path, return
+ * #SVN_ERR_WC_PATH_NOT_FOUND
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_prop_get2(const svn_string_t **value,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_prop_get2(), but with a #svn_wc_adm_access_t /
+ * relative path parameter pair.
+ *
+ * When @a path is not versioned, set @a *value to NULL.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_prop_get(const svn_string_t **value,
+ const char *name,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+/**
+ * Set property @a name to @a value for @a local_abspath, or if @a value is
+ * NULL, remove property @a name from @a local_abspath. Use @a wc_ctx to
+ * access @a local_abspath.
+ *
+ * @a name may be a regular property or a "wc property". If @a name is
+ * an "entry property", return the error #SVN_ERR_BAD_PROP_KIND (even if
+ * @a skip_checks is TRUE).
+ *
+ * If @a name is a "wc property", then just update the WC DAV cache for
+ * @a local_abspath with @a name and @a value. In this case, @a depth
+ * must be #svn_depth_empty.
+ *
+ * The rest of this description applies when @a name is a regular property.
+ *
+ * If @a name is a name in the reserved "svn:" name space, and @a value is
+ * non-null, then canonicalize the property value and check the property
+ * name and value as documented for svn_wc_canonicalize_svn_prop().
+ * @a skip_checks controls the level of checking as documented there.
+ *
+ * Return an error if the canonicalization or the check fails.
+ * The error will be either #SVN_ERR_ILLEGAL_TARGET (if the
+ * property is not appropriate for @a path), or
+ * #SVN_ERR_BAD_MIME_TYPE (if @a name is "svn:mime-type", but @a value
+ * is not a valid mime-type).
+ * ### That is not currently right -- several other errors can be raised.
+ *
+ * @a depth follows the usual semantics for depth.
+ *
+ * @a changelist_filter is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose properties are
+ * set; that is, don't set properties on any item unless it's a member
+ * of one of those changelists. If @a changelist_filter is empty (or
+ * altogether @c NULL), no changelist filtering occurs.
+ *
+ * If @a cancel_func is non-NULL, then it will be invoked (with the
+ * @a cancel_baton value passed) during the processing of the property
+ * set (i.e. when @a depth indicates some amount of recursion).
+ *
+ * For each file or directory operated on, @a notify_func will be called
+ * with its path and the @a notify_baton. @a notify_func may be @c NULL
+ * if you are not interested in this information.
+ *
+ * Use @a scratch_pool for temporary allocation.
+ *
+ * @note If the caller is setting both svn:mime-type and svn:eol-style in
+ * separate calls, and @a skip_checks is false, there is an ordering
+ * dependency between them, as the validity check for svn:eol-style makes
+ * use of the current value of svn:mime-type.
+ *
+ * ### The error code on validity check failure should be specified, and
+ * should be a single code or a very small set of possibilities.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_prop_set4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *name,
+ const svn_string_t *value,
+ svn_depth_t depth,
+ svn_boolean_t skip_checks,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_prop_set4(), but with a #svn_wc_adm_access_t /
+ * relative path parameter pair, no @a depth parameter, no changelist
+ * filtering (for the depth-based property setting), and no cancellation.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_prop_set3(const char *name,
+ const svn_string_t *value,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t skip_checks,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Like svn_wc_prop_set3(), but without the notification callbacks.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backwards compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_prop_set2(const char *name,
+ const svn_string_t *value,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t skip_checks,
+ apr_pool_t *pool);
+
+
+/**
+ * Like svn_wc_prop_set2(), but with @a skip_checks always FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_prop_set(const char *name,
+ const svn_string_t *value,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+
+/** Return TRUE iff @a name is a 'normal' property name. 'Normal' is
+ * defined as a user-visible and user-tweakable property that shows up
+ * when you fetch a proplist.
+ *
+ * The function currently parses the namespace like so:
+ *
+ * - 'svn:wc:' ==> a wcprop, stored/accessed separately via different API.
+ *
+ * - 'svn:entry:' ==> an "entry" prop, shunted into the 'entries' file.
+ *
+ * If these patterns aren't found, then the property is assumed to be
+ * Normal.
+ */
+svn_boolean_t
+svn_wc_is_normal_prop(const char *name);
+
+
+
+/** Return TRUE iff @a name is a 'wc' property name. */
+svn_boolean_t
+svn_wc_is_wc_prop(const char *name);
+
+/** Return TRUE iff @a name is a 'entry' property name. */
+svn_boolean_t
+svn_wc_is_entry_prop(const char *name);
+
+/** Callback type used by #svn_wc_canonicalize_svn_prop.
+ *
+ * If @a mime_type is non-null, it sets @a *mime_type to the value of
+ * #SVN_PROP_MIME_TYPE for the path passed to
+ * #svn_wc_canonicalize_svn_prop (allocated from @a pool). If @a
+ * stream is non-null, it writes the contents of the file to @a
+ * stream.
+ *
+ * (Currently, this is used if you are attempting to set the
+ * #SVN_PROP_EOL_STYLE property, to make sure that the value matches
+ * the mime type and contents.)
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_wc_canonicalize_svn_prop_get_file_t)(
+ const svn_string_t **mime_type,
+ svn_stream_t *stream,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/** Canonicalize the value of an svn:* property @a propname with
+ * value @a propval.
+ *
+ * If the property is not appropriate for a node of kind @a kind, or
+ * is otherwise invalid, throw an error. Otherwise, set @a *propval_p
+ * to a canonicalized version of the property value.
+ *
+ * The exact set of canonicalizations and checks may vary across different
+ * versions of this API. Currently:
+ *
+ * - svn:executable
+ * - svn:needs-lock
+ * - svn:special
+ * - set the value to '*'
+ *
+ * - svn:keywords
+ * - strip leading and trailing white space
+ *
+ * - svn:ignore
+ * - svn:global-ignores
+ * - svn:auto-props
+ * - add a final a newline character if missing
+ *
+ * - svn:externals
+ * - add a final a newline character if missing
+ * - check for valid syntax
+ * - check for no duplicate entries
+ *
+ * - svn:mergeinfo
+ * - canonicalize
+ * - check for validity
+ *
+ * Also, unless @a skip_some_checks is TRUE:
+ *
+ * - svn:eol-style
+ * - strip leading and trailing white space
+ * - check value is recognized
+ * - check file content has a self-consistent EOL style
+ * (but not necessarily that it matches @a propval)
+ *
+ * - svn:mime-type
+ * - strip white space
+ * - check for reasonable syntax
+ *
+ * The EOL-style check (if not skipped) requires access to the contents and
+ * MIME type of the target if it is a file. It will call @a prop_getter with
+ * @a getter_baton. The callback must set the MIME type and/or write the
+ * contents of the file to the given stream. If @a skip_some_checks is true,
+ * then @a prop_getter is not used and may be NULL.
+ *
+ * @a path should be the path of the file in question; it is only used
+ * for error messages.
+ *
+ * ### The error code on validity check failure should be specified, and
+ * should be a single code or a very small set of possibilities.
+ *
+ * ### This is not actually related to the WC, but it does need to call
+ * ### svn_wc_parse_externals_description3.
+ *
+ * @since New in 1.5.
+ */
+svn_error_t *
+svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p,
+ const char *propname,
+ const svn_string_t *propval,
+ const char *path,
+ svn_node_kind_t kind,
+ svn_boolean_t skip_some_checks,
+ svn_wc_canonicalize_svn_prop_get_file_t prop_getter,
+ void *getter_baton,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/**
+ * @defgroup svn_wc_diffs Diffs
+ * @{
+ */
+
+/**
+ * DEPRECATED -- please use APIs from svn_client.h
+ *
+ * ---
+ *
+ * Return an @a editor/@a edit_baton for diffing a working copy against the
+ * repository. The editor is allocated in @a result_pool; temporary
+ * calculations are performed in @a scratch_pool.
+ *
+ * This editor supports diffing either the actual files and properties in the
+ * working copy (when @a use_text_base is #FALSE), or the current pristine
+ * information (when @a use_text_base is #TRUE) against the editor driver.
+ *
+ * @a anchor_abspath/@a target represent the base of the hierarchy to be
+ * compared. The diff callback paths will be relative to this path.
+ *
+ * Diffs will be reported as valid relpaths, with @a anchor_abspath being
+ * the root ("").
+ *
+ * @a callbacks/@a callback_baton is the callback table to use.
+ *
+ * If @a depth is #svn_depth_empty, just diff exactly @a target or
+ * @a anchor_path if @a target is empty. If #svn_depth_files then do the same
+ * and for top-level file entries as well (if any). If
+ * #svn_depth_immediates, do the same as #svn_depth_files but also diff
+ * top-level subdirectories at #svn_depth_empty. If #svn_depth_infinity,
+ * then diff fully recursively.
+ *
+ * @a ignore_ancestry determines whether paths that have discontinuous node
+ * ancestry are treated as delete/add or as simple modifications. If
+ * @a ignore_ancestry is @c FALSE, then any discontinuous node ancestry will
+ * result in the diff given as a full delete followed by an add.
+ *
+ * @a show_copies_as_adds determines whether paths added with history will
+ * appear as a diff against their copy source, or whether such paths will
+ * appear as if they were newly added in their entirety.
+ * @a show_copies_as_adds implies not @a ignore_ancestry.
+ *
+ * If @a use_git_diff_format is TRUE, copied paths will be treated as added
+ * if they weren't modified after being copied. This allows the callbacks
+ * to generate appropriate --git diff headers for such files.
+ * @a use_git_diff_format implies @a show_copies_as_adds, and as such implies
+ * not @a ignore_ancestry.
+ *
+ * Normally, the difference from repository->working_copy is shown.
+ * If @a reverse_order is TRUE, then show working_copy->repository diffs.
+ *
+ * If @a cancel_func is non-NULL, it will be used along with @a cancel_baton
+ * to periodically check if the client has canceled the operation.
+ *
+ * @a changelist_filter is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose differences are
+ * reported; that is, don't generate diffs about any item unless
+ * it's a member of one of those changelists. If @a changelist_filter is
+ * empty (or altogether @c NULL), no changelist filtering occurs.
+ *
+ * If @a server_performs_filtering is TRUE, assume that the server handles
+ * the ambient depth filtering, so this doesn't have to be handled in the
+ * editor.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_diff_editor6(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_boolean_t server_performs_filtering,
+ const apr_array_header_t *changelist_filter,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_wc_get_diff_editor6(), but with an access baton and relative
+ * path. @a server_performs_filtering always true and with a
+ * #svn_wc_diff_callbacks3_t instead of #svn_wc_diff_callbacks4_t,
+ * @a show_copies_as_adds, and @a use_git_diff_format set to @c FALSE.
+ *
+ * Diffs will be reported as below the relative path stored in @a anchor.
+ *
+ * @since New in 1.6.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_diff_editor5(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks3_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const apr_array_header_t *changelist_filter,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_get_diff_editor5(), but with an
+ * #svn_wc_diff_callbacks2_t instead of #svn_wc_diff_callbacks3_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_diff_editor4(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks2_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const apr_array_header_t *changelist_filter,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_get_diff_editor4(), but with @a changelist_filter
+ * passed as @c NULL, and @a depth set to #svn_depth_infinity if @a
+ * recurse is TRUE, or #svn_depth_files if @a recurse is FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_diff_editor3(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks2_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_wc_get_diff_editor3(), but with an
+ * #svn_wc_diff_callbacks_t instead of #svn_wc_diff_callbacks2_t.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_diff_editor2(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_wc_get_diff_editor2(), but with @a ignore_ancestry
+ * always set to @c FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_diff_editor(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Compare working copy against the text-base.
+ *
+ * @a target_abspath represents the base of the hierarchy to be compared.
+ *
+ * @a callbacks/@a callback_baton is the callback table to use when two
+ * files are to be compared.
+ *
+ * If @a depth is #svn_depth_empty, just diff exactly @a target_path.
+ * If #svn_depth_files then do the same
+ * and for top-level file entries as well (if any). If
+ * #svn_depth_immediates, do the same as #svn_depth_files but also diff
+ * top-level subdirectories at #svn_depth_empty. If #svn_depth_infinity,
+ * then diff fully recursively.
+ *
+ * @a ignore_ancestry determines whether paths that have discontinuous node
+ * ancestry are treated as delete/add or as simple modifications. If
+ * @a ignore_ancestry is @c FALSE, then any discontinuous node ancestry will
+ * result in the diff given as a full delete followed by an add.
+ *
+ * @a show_copies_as_adds determines whether paths added with history will
+ * appear as a diff against their copy source, or whether such paths will
+ * appear as if they were newly added in their entirety.
+ *
+ * If @a use_git_diff_format is TRUE, copied paths will be treated as added
+ * if they weren't modified after being copied. This allows the callbacks
+ * to generate appropriate --git diff headers for such files.
+ *
+ * @a changelist_filter is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose differences are
+ * reported; that is, don't generate diffs about any item unless
+ * it's a member of one of those changelists. If @a changelist_filter is
+ * empty (or altogether @c NULL), no changelist filtering occurs.
+ *
+ * If @a cancel_func is non-NULL, invoke it with @a cancel_baton at various
+ * points during the operation. If it returns an error (typically
+ * #SVN_ERR_CANCELLED), return that error immediately.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_diff6(svn_wc_context_t *wc_ctx,
+ const char *target_abspath,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_wc_diff6(), but with a #svn_wc_diff_callbacks3_t argument
+ * instead of #svn_wc_diff_callbacks4_t, @a show_copies_as_adds,
+ * and @a use_git_diff_format set to * @c FALSE.
+ * It also doesn't allow specifying a cancel function.
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_diff5(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks3_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelist_filter,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_diff5(), but with a #svn_wc_diff_callbacks2_t argument
+ * instead of #svn_wc_diff_callbacks3_t.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.5 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_diff4(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks2_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelist_filter,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_diff4(), but with @a changelist_filter passed @c NULL,
+ * and @a depth set to #svn_depth_infinity if @a recurse is TRUE, or
+ * #svn_depth_files if @a recurse is FALSE.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_diff3(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks2_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_diff3(), but with a #svn_wc_diff_callbacks_t argument
+ * instead of #svn_wc_diff_callbacks2_t.
+ *
+ * @since New in 1.1.
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_diff2(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_diff2(), but with @a ignore_ancestry always set
+ * to @c FALSE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.0 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_diff(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ apr_pool_t *pool);
+
+
+/** Given a @a local_abspath to a file or directory under version control,
+ * discover any local changes made to properties and/or the set of 'pristine'
+ * properties. @a wc_ctx will be used to access the working copy.
+ *
+ * If @a propchanges is non-@c NULL, return these changes as an array of
+ * #svn_prop_t structures stored in @a *propchanges. The structures and
+ * array will be allocated in @a result_pool. If there are no local property
+ * modifications on @a local_abspath, then set @a *propchanges will be empty.
+ *
+ * If @a original_props is non-@c NULL, then set @a *original_props to
+ * hashtable (<tt>const char *name</tt> -> <tt>const svn_string_t *value</tt>)
+ * that represents the 'pristine' property list of @a path. This hashtable is
+ * allocated in @a result_pool.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ */
+svn_error_t *
+svn_wc_get_prop_diffs2(apr_array_header_t **propchanges,
+ apr_hash_t **original_props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_get_prop_diffs2(), but with a #svn_wc_adm_access_t /
+ * relative path parameter pair.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_prop_diffs(apr_array_header_t **propchanges,
+ apr_hash_t **original_props,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/**
+ * @defgroup svn_wc_merging Merging
+ * @{
+ */
+
+/** The outcome of a merge carried out (or tried as a dry-run) by
+ * svn_wc_merge()
+ */
+typedef enum svn_wc_merge_outcome_t
+{
+ /** The working copy is (or would be) unchanged. The changes to be
+ * merged were already present in the working copy
+ */
+ svn_wc_merge_unchanged,
+
+ /** The working copy has been (or would be) changed. */
+ svn_wc_merge_merged,
+
+ /** The working copy has been (or would be) changed, but there was (or
+ * would be) a conflict
+ */
+ svn_wc_merge_conflict,
+
+ /** No merge was performed, probably because the target file was
+ * either absent or not under version control.
+ */
+ svn_wc_merge_no_merge
+
+} svn_wc_merge_outcome_t;
+
+/** Given absolute paths to three fulltexts, merge the differences between
+ * @a left_abspath and @a right_abspath into @a target_abspath.
+ * It may help to know that @a left_abspath, @a right_abspath and @a
+ * target_abspath correspond to "OLDER", "YOURS", and "MINE",
+ * respectively, in the diff3 documentation.
+ *
+ * @a wc_ctx should contain a write lock for the directory containing @a
+ * target_abspath.
+ *
+ * This function assumes that @a left_abspath and @a right_abspath are
+ * in repository-normal form (linefeeds, with keywords contracted); if
+ * necessary, @a target_abspath is temporarily converted to this form to
+ * receive the changes, then translated back again.
+ *
+ * If @a target_abspath is absent, or present but not under version
+ * control, then set @a *merge_content_outcome to #svn_wc_merge_no_merge and
+ * return success without merging anything. (The reasoning is that if
+ * the file is not versioned, then it is probably unrelated to the
+ * changes being considered, so they should not be merged into it.
+ * Furthermore, merging into an unversioned file is a lossy operation.)
+ *
+ * @a dry_run determines whether the working copy is modified. When it
+ * is @c FALSE the merge will cause @a target_abspath to be modified, when
+ * it is @c TRUE the merge will be carried out to determine the result but
+ * @a target_abspath will not be modified.
+ *
+ * If @a diff3_cmd is non-NULL, then use it as the diff3 command for
+ * any merging; otherwise, use the built-in merge code. If @a
+ * merge_options is non-NULL, either pass its elements to @a diff3_cmd or
+ * parse it and use as options to the internal merge code (see
+ * svn_diff_file_options_parse()). @a merge_options must contain
+ * <tt>const char *</tt> elements.
+ *
+ * If @a merge_props_state is non-NULL, merge @a prop_diff into the
+ * working properties before merging the text. (If @a merge_props_state
+ * is NULL, do not merge any property changes; in this case, @a prop_diff
+ * is only used to help determine the text merge result.) Handle any
+ * conflicts as described for svn_wc_merge_props3(), with the parameters
+ * @a dry_run, @a conflict_func and @a conflict_baton. Return the
+ * outcome of the property merge in @a *merge_props_state.
+ *
+ * The outcome of the text merge is returned in @a *merge_content_outcome. If
+ * there is a conflict and @a dry_run is @c FALSE, then attempt to call @a
+ * conflict_func with @a conflict_baton (if non-NULL). If the
+ * conflict callback cannot resolve the conflict, then:
+ *
+ * * Put conflict markers around the conflicting regions in
+ * @a target_abspath, labeled with @a left_label, @a right_label, and
+ * @a target_label. (If any of these labels are @c NULL, default
+ * values will be used.)
+ *
+ * * Copy @a left_abspath, @a right_abspath, and the original @a
+ * target_abspath to unique names in the same directory as @a
+ * target_abspath, ending with the suffixes ".LEFT_LABEL", ".RIGHT_LABEL",
+ * and ".TARGET_LABEL" respectively.
+ *
+ * * Mark @a target_abspath as "text-conflicted", and track the above
+ * mentioned backup files as well.
+ *
+ * * If @a left_version and/or @a right_version are not NULL, provide
+ * these values to the conflict handler and track these while the conflict
+ * exists.
+ *
+ * Binary case:
+ *
+ * If @a target_abspath is a binary file, then no merging is attempted,
+ * the merge is deemed to be a conflict. If @a dry_run is @c FALSE the
+ * working @a target_abspath is untouched, and copies of @a left_abspath and
+ * @a right_abspath are created next to it using @a left_label and
+ * @a right_label. @a target_abspath is marked as "text-conflicted", and
+ * begins tracking the two backup files and the version information.
+ *
+ * If @a dry_run is @c TRUE no files are changed. The outcome of the merge
+ * is returned in @a *merge_content_outcome.
+ * ### (and what about @a *merge_props_state?)
+ *
+ * ### BH: Two kinds of outcome is not how it should be.
+ *
+ * ### For text, we report the outcome as 'merged' if there was some
+ * incoming change that we dealt with (even if we decided to no-op?)
+ * but the callers then convert this outcome into a notification
+ * of 'merged' only if there was already a local modification;
+ * otherwise they notify it as simply 'updated'. But for props
+ * we report a notify state of 'merged' here if there was an
+ * incoming change regardless of the local-mod state. Inconsistent.
+ *
+ * Use @a scratch_pool for any temporary allocation.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc_merge5(enum svn_wc_merge_outcome_t *merge_content_outcome,
+ enum svn_wc_notify_state_t *merge_props_state,
+ svn_wc_context_t *wc_ctx,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ apr_hash_t *original_props,
+ const apr_array_header_t *prop_diff,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_merge5() but with @a merge_props_state and @a
+ * original_props always passed as NULL.
+ *
+ * Unlike svn_wc_merge5(), this function doesn't merge property
+ * changes. Callers of this function must first use
+ * svn_wc_merge_props3() to get this functionality.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backwards compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_merge4(enum svn_wc_merge_outcome_t *merge_outcome,
+ svn_wc_context_t *wc_ctx,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ const apr_array_header_t *prop_diff,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+/** Similar to svn_wc_merge4() but takes relative paths and an access
+ * baton. It doesn't support a cancel function or tracking origin version
+ * information.
+ *
+ * Uses a svn_wc_conflict_resolver_func_t conflict resolver instead of a
+ * svn_wc_conflict_resolver_func2_t.
+ *
+ * This function assumes that @a diff3_cmd is path encoded. Later versions
+ * assume utf-8.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_merge3(enum svn_wc_merge_outcome_t *merge_outcome,
+ const char *left,
+ const char *right,
+ const char *merge_target,
+ svn_wc_adm_access_t *adm_access,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ const apr_array_header_t *prop_diff,
+ svn_wc_conflict_resolver_func_t conflict_func,
+ void *conflict_baton,
+ apr_pool_t *pool);
+
+
+/** Similar to svn_wc_merge3(), but with @a prop_diff, @a
+ * conflict_func, @a conflict_baton set to NULL.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_merge2(enum svn_wc_merge_outcome_t *merge_outcome,
+ const char *left,
+ const char *right,
+ const char *merge_target,
+ svn_wc_adm_access_t *adm_access,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ apr_pool_t *pool);
+
+
+/** Similar to svn_wc_merge2(), but with @a merge_options set to NULL.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_merge(const char *left,
+ const char *right,
+ const char *merge_target,
+ svn_wc_adm_access_t *adm_access,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ svn_boolean_t dry_run,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ const char *diff3_cmd,
+ apr_pool_t *pool);
+
+
+/** Given a @a local_abspath under version control, merge an array of @a
+ * propchanges into the path's existing properties. @a propchanges is
+ * an array of #svn_prop_t objects, and @a baseprops is a hash
+ * representing the original set of properties that @a propchanges is
+ * working against. @a wc_ctx contains a lock for @a local_abspath.
+ *
+ * Only the working properties will be changed.
+ *
+ * If @a state is non-NULL, set @a *state to the state of the properties
+ * after the merge.
+ *
+ * If a conflict is found when merging a property, and @a dry_run is
+ * false and @a conflict_func is not null, then call @a conflict_func
+ * with @a conflict_baton and a description of the conflict. If any
+ * conflicts are not resolved by such callbacks, describe the unresolved
+ * conflicts in a temporary .prej file (or append to an already-existing
+ * .prej file) and mark the path as conflicted in the WC DB.
+ *
+ * If @a cancel_func is non-NULL, invoke it with @a cancel_baton at various
+ * points during the operation. If it returns an error (typically
+ * #SVN_ERR_CANCELLED), return that error immediately.
+ *
+ * If @a local_abspath is not under version control, return the error
+ * #SVN_ERR_WC_PATH_NOT_FOUND and don't touch anyone's properties.
+ *
+ * If @a local_abspath has a status in which it doesn't have properties
+ * (E.g. deleted) return the error SVN_ERR_WC_PATH_UNEXPECTED_STATUS.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_merge_props3(svn_wc_notify_state_t *state,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ apr_hash_t *baseprops,
+ const apr_array_header_t *propchanges,
+ svn_boolean_t dry_run,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+/** Similar to svn_wc_merge_props3, but takes an access baton and relative
+ * path, no cancel_function, and no left and right version.
+ *
+ * This function has the @a base_merge parameter which (when TRUE) will
+ * apply @a propchanges to this node's pristine set of properties. This
+ * functionality is not supported since API version 1.7 and will give an
+ * error if requested (unless @a dry_run is TRUE). For details see
+ * 'notes/api-errata/1.7/wc006.txt'.
+ *
+ * Uses a svn_wc_conflict_resolver_func_t conflict resolver instead of a
+ * svn_wc_conflict_resolver_func2_t.
+ *
+ * For compatibility reasons this function returns
+ * #SVN_ERR_UNVERSIONED_RESOURCE, when svn_wc_merge_props3 would return either
+ * #SVN_ERR_WC_PATH_NOT_FOUND or #SVN_ERR_WC_PATH_UNEXPECTED_STATUS.
+ *
+ * @since New in 1.5. The base_merge option is not supported since 1.7.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_merge_props2(svn_wc_notify_state_t *state,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_hash_t *baseprops,
+ const apr_array_header_t *propchanges,
+ svn_boolean_t base_merge,
+ svn_boolean_t dry_run,
+ svn_wc_conflict_resolver_func_t conflict_func,
+ void *conflict_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Same as svn_wc_merge_props2(), but with a @a conflict_func (and
+ * baton) of NULL.
+ *
+ * @since New in 1.3. The base_merge option is not supported since 1.7.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_merge_props(svn_wc_notify_state_t *state,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_hash_t *baseprops,
+ const apr_array_header_t *propchanges,
+ svn_boolean_t base_merge,
+ svn_boolean_t dry_run,
+ apr_pool_t *pool);
+
+
+/**
+ * Similar to svn_wc_merge_props(), but no baseprops are given.
+ * Instead, it's assumed that the incoming propchanges are based
+ * against the working copy's own baseprops. While this assumption is
+ * correct for 'svn update', it's incorrect for 'svn merge', and can
+ * cause flawed behavior. (See issue #2035.)
+ *
+ * @since The base_merge option is not supported since 1.7.
+ * @deprecated Provided for backward compatibility with the 1.2 API.
+ * Replaced by svn_wc_merge_props().
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_merge_prop_diffs(svn_wc_notify_state_t *state,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const apr_array_header_t *propchanges,
+ svn_boolean_t base_merge,
+ svn_boolean_t dry_run,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/** Given a @a path to a wc file, return in @a *contents a readonly stream to
+ * the pristine contents of the file that would serve as base content for the
+ * next commit. That means:
+ *
+ * When there is no change in node history scheduled, i.e. when there are only
+ * local text-mods, prop-mods or a delete, return the last checked-out or
+ * updated-/switched-to contents of the file.
+ *
+ * If the file is simply added or replaced (no copy-/move-here involved),
+ * set @a *contents to @c NULL.
+ *
+ * When the file has been locally copied-/moved-here, return the contents of
+ * the copy/move source (even if the copy-/move-here replaces a locally
+ * deleted file).
+ *
+ * If @a local_abspath refers to an unversioned or non-existing path, return
+ * @c SVN_ERR_WC_PATH_NOT_FOUND. Use @a wc_ctx to access the working copy.
+ * @a contents may not be @c NULL (unlike @a *contents).
+ *
+ * @since New in 1.7. */
+svn_error_t *
+svn_wc_get_pristine_contents2(svn_stream_t **contents,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_get_pristine_contents2, but takes no working copy
+ * context and a path that can be relative
+ *
+ * @since New in 1.6.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_pristine_contents(svn_stream_t **contents,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Set @a *pristine_path to the path of the "normal" pristine text file for
+ * the versioned file @a path.
+ *
+ * If @a path does not have a pristine text, set @a *pristine_path to a path where
+ * nothing exists on disk (in a directory that does exist).
+ *
+ * @note: Before version 1.7, the behaviour in that case was to provide the
+ * path where the pristine text *would be* if it were present. The new
+ * behaviour is intended to provide backward compatibility for callers that
+ * open or test the provided path immediately, and not for callers that
+ * store the path for later use.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.5 API.
+ * Callers should use svn_wc_get_pristine_contents() instead.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_pristine_copy_path(const char *path,
+ const char **pristine_path,
+ apr_pool_t *pool);
+
+
+/**
+ * Recurse from @a local_abspath, cleaning up unfinished log business. Perform
+ * any temporary allocations in @a scratch_pool. Any working copy locks under
+ * @a local_abspath will be taken over and then cleared by this function.
+ *
+ * WARNING: there is no mechanism that will protect locks that are still being
+ * used.
+ *
+ * If @a cancel_func is non-NULL, invoke it with @a cancel_baton at various
+ * points during the operation. If it returns an error (typically
+ * #SVN_ERR_CANCELLED), return that error immediately.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_cleanup3(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_wc_cleanup3() but uses relative paths and creates its own
+ * #svn_wc_context_t.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_cleanup2(const char *path,
+ const char *diff3_cmd,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_cleanup2(). @a optional_adm_access is an historic
+ * relic and not used, it may be NULL.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_cleanup(const char *path,
+ svn_wc_adm_access_t *optional_adm_access,
+ const char *diff3_cmd,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/** Callback for retrieving a repository root for a url from upgrade.
+ *
+ * Called by svn_wc_upgrade() when no repository root and/or repository
+ * uuid are recorded in the working copy. For normal Subversion 1.5 and
+ * later working copies, this callback will not be used.
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t * (*svn_wc_upgrade_get_repos_info_t)(
+ const char **repos_root,
+ const char **repos_uuid,
+ void *baton,
+ const char *url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Upgrade the working copy at @a local_abspath to the latest metadata
+ * storage format. @a local_abspath should be an absolute path to the
+ * root of the working copy.
+ *
+ * If @a cancel_func is non-NULL, invoke it with @a cancel_baton at
+ * various points during the operation. If it returns an error
+ * (typically #SVN_ERR_CANCELLED), return that error immediately.
+ *
+ * For each directory converted, @a notify_func will be called with
+ * in @a notify_baton action #svn_wc_notify_upgraded_path and as path
+ * the path of the upgraded directory. @a notify_func may be @c NULL
+ * if this notification is not needed.
+ *
+ * If the old working copy doesn't contain a repository root and/or
+ * repository uuid, @a repos_info_func (if non-NULL) will be called
+ * with @a repos_info_baton to provide the missing information.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_upgrade(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_wc_upgrade_get_repos_info_t repos_info_func,
+ void *repos_info_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/** Relocation validation callback typedef.
+ *
+ * Called for each relocated file/directory. @a uuid, if non-NULL, contains
+ * the expected repository UUID, @a url contains the tentative URL.
+ *
+ * @a baton is a closure object; it should be provided by the
+ * implementation, and passed by the caller.
+ *
+ * If @a root_url is passed, then the implementation should make sure that
+ * @a url is the repository root.
+ * @a pool may be used for temporary allocations.
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_wc_relocation_validator3_t)(void *baton,
+ const char *uuid,
+ const char *url,
+ const char *root_url,
+ apr_pool_t *pool);
+
+/** Similar to #svn_wc_relocation_validator3_t, but with
+ * the @a root argument.
+ *
+ * If @a root is TRUE, then the implementation should make sure that @a url
+ * is the repository root. Else, it can be a URL inside the repository.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.4 API.
+ */
+typedef svn_error_t *(*svn_wc_relocation_validator2_t)(void *baton,
+ const char *uuid,
+ const char *url,
+ svn_boolean_t root,
+ apr_pool_t *pool);
+
+/** Similar to #svn_wc_relocation_validator2_t, but without
+ * the @a root and @a pool arguments. @a uuid will not be NULL in this version
+ * of the function.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API.
+ */
+typedef svn_error_t *(*svn_wc_relocation_validator_t)(void *baton,
+ const char *uuid,
+ const char *url);
+
+/** Recursively change repository references at @a wcroot_abspath
+ * (which is the root directory of a working copy). The pre-change
+ * URL should begin with @a from, and the post-change URL will begin
+ * with @a to. @a validator (and its baton, @a validator_baton), will
+ * be called for the newly generated base URL and calculated repo
+ * root.
+ *
+ * @a wc_ctx is an working copy context.
+ *
+ * @a scratch_pool will be used for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_relocate4(svn_wc_context_t *wc_ctx,
+ const char *wcroot_abspath,
+ const char *from,
+ const char *to,
+ svn_wc_relocation_validator3_t validator,
+ void *validator_baton,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_relocate4(), but with a #svn_wc_adm_access_t /
+ * relative path parameter pair.
+ *
+ * @note As of the 1.7 API, @a path is required to be a working copy
+ * root directory, and @a recurse is required to be TRUE.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for limited backwards compatibility with the
+ * 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_relocate3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const char *from,
+ const char *to,
+ svn_boolean_t recurse,
+ svn_wc_relocation_validator3_t validator,
+ void *validator_baton,
+ apr_pool_t *pool);
+
+/** Similar to svn_wc_relocate3(), but uses #svn_wc_relocation_validator2_t.
+ *
+ * @since New in 1.4.
+ * @deprecated Provided for backwards compatibility with the 1.4 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_relocate2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const char *from,
+ const char *to,
+ svn_boolean_t recurse,
+ svn_wc_relocation_validator2_t validator,
+ void *validator_baton,
+ apr_pool_t *pool);
+
+/** Similar to svn_wc_relocate2(), but uses #svn_wc_relocation_validator_t.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API. */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_relocate(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const char *from,
+ const char *to,
+ svn_boolean_t recurse,
+ svn_wc_relocation_validator_t validator,
+ void *validator_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Revert changes to @a local_abspath. Perform necessary allocations in
+ * @a scratch_pool.
+ *
+ * @a wc_ctx contains the necessary locks required for performing the
+ * operation.
+ *
+ * If @a depth is #svn_depth_empty, revert just @a path (if a
+ * directory, then revert just the properties on that directory).
+ * Else if #svn_depth_files, revert @a path and any files
+ * directly under @a path if it is directory. Else if
+ * #svn_depth_immediates, revert all of the preceding plus
+ * properties on immediate subdirectories; else if #svn_depth_infinity,
+ * revert path and everything under it fully recursively.
+ *
+ * @a changelist_filter is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items reverted; that is,
+ * don't revert any item unless it's a member of one of those
+ * changelists. If @a changelist_filter is empty (or altogether @c NULL),
+ * no changelist filtering occurs.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton at
+ * various points during the reversion process. If it returns an
+ * error (typically #SVN_ERR_CANCELLED), return that error
+ * immediately.
+ *
+ * If @a use_commit_times is TRUE, then all reverted working-files
+ * will have their timestamp set to the last-committed-time. If
+ * FALSE, the reverted working-files will be touched with the 'now' time.
+ *
+ * For each item reverted, @a notify_func will be called with @a notify_baton
+ * and the path of the reverted item. @a notify_func may be @c NULL if this
+ * notification is not needed.
+ *
+ * If @a path is not under version control, return the error
+ * #SVN_ERR_UNVERSIONED_RESOURCE.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_revert4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_revert4() but takes a relative path and access baton.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_revert3(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_revert3(), but with @a changelist_filter passed as @c
+ * NULL, and @a depth set according to @a recursive: if @a recursive
+ * is TRUE, @a depth is #svn_depth_infinity; if FALSE, @a depth is
+ * #svn_depth_empty.
+ *
+ * @note Most APIs map @a recurse==FALSE to @a depth==svn_depth_files;
+ * revert is deliberately different.
+ *
+ * @since New in 1.2.
+ * @deprecated Provided for backward compatibility with the 1.4 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_revert2(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ svn_boolean_t recursive,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_wc_revert2(), but takes an #svn_wc_notify_func_t instead.
+ *
+ * @deprecated Provided for backward compatibility with the 1.1 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_revert(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ svn_boolean_t recursive,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+/**
+ * Restores a missing node, @a local_abspath using the @a wc_ctx. Records
+ * the new last modified time of the file for status processing.
+ *
+ * If @a use_commit_times is TRUE, then set restored files' timestamps
+ * to their last-commit-times.
+ *
+ * Returns SVN_ERROR_WC_PATH_NOT_FOUND if LOCAL_ABSPATH is not versioned and
+ * SVN_ERROR_WC_PATH_UNEXPECTED_STATUS if LOCAL_ABSPATH is in a status where
+ * it can't be restored.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_restore(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t use_commit_times,
+ apr_pool_t *scratch_pool);
+
+
+/* Tmp files */
+
+/** Create a unique temporary file in administrative tmp/ area of
+ * directory @a path. Return a handle in @a *fp and the path
+ * in @a *new_name. Either @a fp or @a new_name can be NULL.
+ *
+ * The flags will be <tt>APR_WRITE | APR_CREATE | APR_EXCL</tt> and
+ * optionally @c APR_DELONCLOSE (if the @a delete_when argument is
+ * set to #svn_io_file_del_on_close).
+ *
+ * This means that as soon as @a fp is closed, the tmp file will vanish.
+ *
+ * @since New in 1.4
+ * @deprecated For compatibility with 1.6 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_create_tmp_file2(apr_file_t **fp,
+ const char **new_name,
+ const char *path,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *pool);
+
+
+/** Same as svn_wc_create_tmp_file2(), but with @a new_name set to @c NULL,
+ * and without the ability to delete the file on pool cleanup.
+ *
+ * @deprecated For compatibility with 1.3 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_create_tmp_file(apr_file_t **fp,
+ const char *path,
+ svn_boolean_t delete_on_close,
+ apr_pool_t *pool);
+
+
+/**
+ * @defgroup svn_wc_translate EOL conversion and keyword expansion
+ * @{
+ */
+
+
+/** Set @a xlated_path to a translated copy of @a src
+ * or to @a src itself if no translation is necessary.
+ * That is, if @a versioned_file's properties indicate newline conversion or
+ * keyword expansion, point @a *xlated_path to a copy of @a src
+ * whose newlines and keywords are converted using the translation
+ * as requested by @a flags.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton to determine
+ * if the client has canceled the operation.
+ *
+ * When translating to the normal form, inconsistent eol styles will be
+ * repaired when appropriate for the given setting. When translating
+ * from normal form, no EOL repair is performed (consistency is assumed).
+ * This behaviour can be overridden by specifying
+ * #SVN_WC_TRANSLATE_FORCE_EOL_REPAIR.
+ *
+ * The caller can explicitly request a new file to be returned by setting the
+ * #SVN_WC_TRANSLATE_FORCE_COPY flag in @a flags.
+ *
+ * This function is generally used to get a file that can be compared
+ * meaningfully against @a versioned_file's text base, if
+ * @c SVN_WC_TRANSLATE_TO_NF is specified, against @a versioned_file itself
+ * if @c SVN_WC_TRANSLATE_FROM_NF is specified.
+ *
+ * If a new output file is created, it is created in the temp file area
+ * belonging to @a versioned_file. By default it will be deleted at pool
+ * cleanup. If @c SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP is specified, the
+ * default pool cleanup handler to remove @a *xlated_path is not registered.
+ * If the input file is returned as the output, its lifetime is not
+ * specified.
+ *
+ * If an error is returned, the effect on @a *xlated_path is undefined.
+ *
+ * @since New in 1.4
+ * @deprecated Provided for compatibility with the 1.6 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_translated_file2(const char **xlated_path,
+ const char *src,
+ const char *versioned_file,
+ svn_wc_adm_access_t *adm_access,
+ apr_uint32_t flags,
+ apr_pool_t *pool);
+
+
+/** Same as svn_wc_translated_file2, but will never clean up
+ * temporary files.
+ *
+ * @deprecated Provided for compatibility with the 1.3 API
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_translated_file(const char **xlated_p,
+ const char *vfile,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t force_repair,
+ apr_pool_t *pool);
+
+
+/** Returns a @a stream allocated in @a pool with access to the given
+ * @a path taking the file properties from @a versioned_file using
+ * @a adm_access.
+ *
+ * If @a flags includes #SVN_WC_TRANSLATE_FROM_NF, the stream will
+ * translate from Normal Form to working copy form while writing to
+ * @a path; stream read operations are not supported.
+ * Conversely, if @a flags includes #SVN_WC_TRANSLATE_TO_NF, the stream will
+ * translate from working copy form to Normal Form while reading from
+ * @a path; stream write operations are not supported.
+ *
+ * The @a flags are the same constants as those used for
+ * svn_wc_translated_file2().
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_translated_stream(svn_stream_t **stream,
+ const char *path,
+ const char *versioned_file,
+ svn_wc_adm_access_t *adm_access,
+ apr_uint32_t flags,
+ apr_pool_t *pool);
+
+ /** @} */
+
+
+/**
+ * @defgroup svn_wc_deltas Text/Prop Deltas Using an Editor
+ * @{
+ */
+
+/** Send the local modifications for versioned file @a local_abspath (with
+ * matching @a file_baton) through @a editor, then close @a file_baton
+ * afterwards. Use @a scratch_pool for any temporary allocation.
+ *
+ * If @a new_text_base_md5_checksum is non-NULL, set
+ * @a *new_text_base_md5_checksum to the MD5 checksum of (@a local_abspath
+ * translated to repository-normal form), allocated in @a result_pool.
+ *
+ * If @a new_text_base_sha1_checksum in non-NULL, store a copy of (@a
+ * local_abspath translated to repository-normal form) in the pristine text
+ * store, and set @a *new_text_base_sha1_checksum to its SHA-1 checksum.
+ *
+ * If @a fulltext, send the untranslated copy of @a local_abspath through
+ * @a editor as full-text; else send it as svndiff against the current text
+ * base.
+ *
+ * If sending a diff, and the recorded checksum for @a local_abspath's
+ * text-base does not match the current actual checksum, then remove the tmp
+ * copy (and set @a *tempfile to NULL if appropriate), and return the
+ * error #SVN_ERR_WC_CORRUPT_TEXT_BASE.
+ *
+ * @note This is intended for use with both infix and postfix
+ * text-delta styled editor drivers.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_transmit_text_deltas3(const svn_checksum_t **new_text_base_md5_checksum,
+ const svn_checksum_t **new_text_base_sha1_checksum,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t fulltext,
+ const svn_delta_editor_t *editor,
+ void *file_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_transmit_text_deltas3(), but with a relative path
+ * and adm_access baton, and the checksum output is an MD5 digest instead of
+ * two svn_checksum_t objects.
+ *
+ * If @a tempfile is non-NULL, make a copy of @a path with keywords
+ * and eol translated to repository-normal form, and set @a *tempfile to the
+ * absolute path to this copy, allocated in @a result_pool. The copy will
+ * be in the temporary-text-base directory. Do not clean up the copy;
+ * caller can do that. (The purpose of handing back the tmp copy is that it
+ * is usually about to become the new text base anyway, but the installation
+ * of the new text base is outside the scope of this function.)
+ *
+ * @since New in 1.4.
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_transmit_text_deltas2(const char **tempfile,
+ unsigned char digest[],
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t fulltext,
+ const svn_delta_editor_t *editor,
+ void *file_baton,
+ apr_pool_t *pool);
+
+/** Similar to svn_wc_transmit_text_deltas2(), but with @a digest set to NULL.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.3 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_transmit_text_deltas(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t fulltext,
+ const svn_delta_editor_t *editor,
+ void *file_baton,
+ const char **tempfile,
+ apr_pool_t *pool);
+
+
+/** Given a @a local_abspath, transmit all local property
+ * modifications using the appropriate @a editor method (in conjunction
+ * with @a baton). Use @a scratch_pool for any temporary allocation.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_delta_editor_t *editor,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+
+/** Similar to svn_wc_transmit_prop_deltas2(), but with a relative path,
+ * adm_access baton and tempfile.
+ *
+ * If a temporary file remains after this function is finished, the
+ * path to that file is returned in @a *tempfile (so the caller can
+ * clean this up if it wishes to do so).
+ *
+ * @note Starting version 1.5, no tempfile will ever be returned
+ * anymore. If @a *tempfile is passed, its value is set to @c NULL.
+ *
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_transmit_prop_deltas(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_t *entry,
+ const svn_delta_editor_t *editor,
+ void *baton,
+ const char **tempfile,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/**
+ * @defgroup svn_wc_ignore Ignoring unversioned files and directories
+ * @{
+ */
+
+/** Get the run-time configured list of ignore patterns from the
+ * #svn_config_t's in the @a config hash, and store them in @a *patterns.
+ * Allocate @a *patterns and its contents in @a pool.
+ */
+svn_error_t *
+svn_wc_get_default_ignores(apr_array_header_t **patterns,
+ apr_hash_t *config,
+ apr_pool_t *pool);
+
+/** Get the list of ignore patterns from the #svn_config_t's in the
+ * @a config hash and the local ignore patterns from the directory
+ * at @a local_abspath, using @a wc_ctx, and store them in @a *patterns.
+ * Allocate @a *patterns and its contents in @a result_pool, use @a
+ * scratch_pool for temporary allocations.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_get_ignores2(apr_array_header_t **patterns,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_hash_t *config,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_get_ignores2(), but with a #svn_wc_adm_access_t
+ * parameter in place of #svn_wc_context_t and @c local_abspath parameters.
+ *
+ * @since New in 1.3.
+ * @deprecated Provided for backwards compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_get_ignores(apr_array_header_t **patterns,
+ apr_hash_t *config,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+/** Return TRUE iff @a str matches any of the elements of @a list, a
+ * list of zero or more ignore patterns.
+ *
+ * @since New in 1.5.
+ */
+svn_boolean_t
+svn_wc_match_ignore_list(const char *str,
+ const apr_array_header_t *list,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/**
+ * @defgroup svn_wc_repos_locks Repository locks
+ * @{
+ */
+
+/** Add @a lock to the working copy for @a local_abspath. If @a
+ * local_abspath is read-only, due to locking properties, make it writable.
+ * Perform temporary allocations in @a scratch_pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_add_lock2(svn_wc_context_t *wc_ctx,
+ const char *abspath,
+ const svn_lock_t *lock,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_wc_add_lock2(), but with a #svn_wc_adm_access_t /
+ * relative path parameter pair.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_add_lock(const char *path,
+ const svn_lock_t *lock,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+/** Remove any lock from @a local_abspath. If @a local_abspath has a
+ * lock and the locking so specifies, make the file read-only. Don't
+ * return an error if @a local_abspath didn't have a lock. Perform temporary
+ * allocations in @a scratch_pool.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_remove_lock2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_wc_remove_lock2(), but with a #svn_wc_adm_access_t /
+ * relative path parameter pair.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ * @since New in 1.2.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_remove_lock(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
+/** @} */
+
+
+/** A structure to report a summary of a working copy, including the
+ * mix of revisions found within it, whether any parts are switched or
+ * locally modified, and whether it is a sparse checkout.
+ *
+ * @note Fields may be added to the end of this structure in future
+ * versions. Therefore, to preserve binary compatibility, users
+ * should not directly allocate structures of this type.
+ *
+ * @since New in 1.4
+ */
+typedef struct svn_wc_revision_status_t
+{
+ svn_revnum_t min_rev; /**< Lowest revision found */
+ svn_revnum_t max_rev; /**< Highest revision found */
+
+ svn_boolean_t switched; /**< Is anything switched? */
+ svn_boolean_t modified; /**< Is anything modified? */
+
+ /** Whether any WC paths are at a depth other than #svn_depth_infinity.
+ * @since New in 1.5.
+ */
+ svn_boolean_t sparse_checkout;
+} svn_wc_revision_status_t;
+
+/** Set @a *result_p to point to a new #svn_wc_revision_status_t structure
+ * containing a summary of the revision range and status of the working copy
+ * at @a local_abspath (not including "externals"). @a local_abspath must
+ * be absolute. Return SVN_ERR_WC_PATH_NOT_FOUND if @a local_abspath is not
+ * a working copy path.
+ *
+ * Set @a (*result_p)->min_rev and @a (*result_p)->max_rev respectively to the
+ * lowest and highest revision numbers in the working copy. If @a committed
+ * is TRUE, summarize the last-changed revisions, else the base revisions.
+ *
+ * Set @a (*result_p)->switched to indicate whether any item in the WC is
+ * switched relative to its parent. If @a trail_url is non-NULL, use it to
+ * determine if @a local_abspath itself is switched. It should be any trailing
+ * portion of @a local_abspath's expected URL, long enough to include any parts
+ * that the caller considers might be changed by a switch. If it does not
+ * match the end of @a local_abspath's actual URL, then report a "switched"
+ * status.
+ *
+ * Set @a (*result_p)->modified to indicate whether any item is locally
+ * modified.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton to determine
+ * if the client has canceled the operation.
+ *
+ * Allocate *result_p in @a result_pool, use @a scratch_pool for temporary
+ * allocations.
+ *
+ * @a wc_ctx should be a valid working copy context.
+ *
+ * @since New in 1.7
+ */
+svn_error_t *
+svn_wc_revision_status2(svn_wc_revision_status_t **result_p,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *trail_url,
+ svn_boolean_t committed,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** Similar to svn_wc_revision_status2(), but with a (possibly) local
+ * path and no wc_ctx parameter.
+ *
+ * @since New in 1.4.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_revision_status(svn_wc_revision_status_t **result_p,
+ const char *wc_path,
+ const char *trail_url,
+ svn_boolean_t committed,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Set @a local_abspath's 'changelist' attribute to @a changelist iff
+ * @a changelist is not @c NULL; otherwise, remove any current
+ * changelist assignment from @a local_abspath. @a changelist may not
+ * be the empty string. Recurse to @a depth.
+ *
+ * @a changelist_filter is an array of <tt>const char *</tt> changelist
+ * names, used as a restrictive filter on items whose changelist
+ * assignments are adjusted; that is, don't tweak the changeset of any
+ * item unless it's currently a member of one of those changelists.
+ * If @a changelist_filter is empty (or altogether @c NULL), no changelist
+ * filtering occurs.
+ *
+ * If @a cancel_func is not @c NULL, call it with @a cancel_baton to
+ * determine if the client has canceled the operation.
+ *
+ * If @a notify_func is not @c NULL, call it with @a notify_baton to
+ * report the change (using notification types
+ * #svn_wc_notify_changelist_set and #svn_wc_notify_changelist_clear).
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @note For now, directories are NOT allowed to be associated with
+ * changelists; there is confusion about whether they should behave
+ * as depth-0 or depth-infinity objects. If @a local_abspath is a directory,
+ * return an error.
+ *
+ * @note This metadata is purely a client-side "bookkeeping"
+ * convenience, and is entirely managed by the working copy.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_set_changelist2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *changelist,
+ svn_depth_t depth,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_set_changelist2(), but with an access baton and
+ * relative path.
+ *
+ * @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_set_changelist(const char *path,
+ const char *changelist,
+ svn_wc_adm_access_t *adm_access,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool);
+
+
+
+/**
+ * The callback type used by svn_wc_get_changelists() and
+ * svn_client_get_changelists().
+ *
+ * On each invocation, @a path is a newly discovered member of the
+ * changelist, and @a baton is a private function closure.
+ *
+ * @since New in 1.5.
+ */
+typedef svn_error_t *(*svn_changelist_receiver_t) (void *baton,
+ const char *path,
+ const char *changelist,
+ apr_pool_t *pool);
+
+
+/**
+ * ### TODO: Doc string, please.
+ *
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_wc_get_changelists(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ const apr_array_header_t *changelist_filter,
+ svn_changelist_receiver_t callback_func,
+ void *callback_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+/** Crop @a local_abspath according to @a depth.
+ *
+ * Remove any item that exceeds the boundary of @a depth (relative to
+ * @a local_abspath) from revision control. Leave modified items behind
+ * (unversioned), while removing unmodified ones completely.
+ *
+ * @a depth can be svn_depth_empty, svn_depth_files or svn_depth_immediates.
+ * Excluding nodes is handled by svn_wc_exclude().
+ *
+ * If @a local_abspath starts out with a shallower depth than @a depth,
+ * do not upgrade it to @a depth (that would not be cropping); however, do
+ * check children and crop them appropriately according to @a depth.
+ *
+ * Returns immediately with an #SVN_ERR_UNSUPPORTED_FEATURE error if @a
+ * local_abspath is not a directory, or if @a depth is not restrictive
+ * (e.g., #svn_depth_infinity).
+ *
+ * @a wc_ctx contains a tree lock, for the local path to the working copy
+ * which will be used as the root of this operation.
+ *
+ * If @a cancel_func is not @c NULL, call it with @a cancel_baton at
+ * various points to determine if the client has canceled the operation.
+ *
+ * If @a notify_func is not @c NULL, call it with @a notify_baton to
+ * report changes as they are made.
+ *
+ * @since New in 1.7
+ */
+svn_error_t *
+svn_wc_crop_tree2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_crop_tree2(), but uses an access baton and target.
+ *
+ * svn_wc_crop_tree() also allows #svn_depth_exclude, which is now
+ * handled via svn_wc_exclude()
+ *
+ * @a target is a basename in @a anchor or "" for @a anchor itself.
+ *
+ * @since New in 1.6
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_crop_tree(svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_depth_t depth,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/** Remove the local node for @a local_abspath from the working copy and
+ * add an excluded node placeholder in its place.
+ *
+ * This feature is only supported for unmodified nodes. An
+ * #SVN_ERR_UNSUPPORTED_FEATURE error is returned if the node can't be
+ * excluded in its current state.
+ *
+ * @a wc_ctx contains a tree lock, for the local path to the working copy
+ * which will be used as the root of this operation
+ *
+ * If @a notify_func is not @c NULL, call it with @a notify_baton to
+ * report changes as they are made.
+ *
+ * If @a cancel_func is not @c NULL, call it with @a cancel_baton at
+ * various points to determine if the client has canceled the operation.
+ *
+ *
+ * @since New in 1.7
+ */
+svn_error_t *
+svn_wc_exclude(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/** @} */
+
+/**
+ * Set @a kind to the #svn_node_kind_t of @a abspath. Use @a wc_ctx to access
+ * the working copy, and @a scratch_pool for all temporary allocations.
+ *
+ * If @a abspath is not under version control, set @a kind to #svn_node_none.
+ *
+ * If @a show_hidden and @a show_deleted are both @c FALSE, the kind of
+ * scheduled for delete, administrative only 'not present' and excluded
+ * nodes is reported as #svn_node_none. This is recommended as a check
+ * for 'is there a versioned file or directory here?'
+ *
+ * If @a show_deleted is FALSE, but @a show_hidden is @c TRUE then only
+ * scheduled for delete and administrative only 'not present' nodes are
+ * reported as #svn_node_none. This is recommended as check for
+ * 'Can I add a node here?'
+ *
+ * If @a show_deleted is TRUE, but @a show_hidden is FALSE, then only
+ * administrative only 'not present' nodes and excluded nodes are reported as
+ * #svn_node_none. This behavior is the behavior bescribed as 'hidden'
+ * before Subversion 1.7.
+ *
+ * If @a show_hidden and @a show_deleted are both @c TRUE all nodes are
+ * reported.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc_read_kind2(svn_node_kind_t *kind,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t show_deleted,
+ svn_boolean_t show_hidden,
+ apr_pool_t *scratch_pool);
+
+/** Similar to svn_wc_read_kind2() but with @a show_deleted always
+ * passed as TRUE.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
+svn_wc_read_kind(svn_node_kind_t *kind,
+ svn_wc_context_t *wc_ctx,
+ const char *abspath,
+ svn_boolean_t show_hidden,
+ apr_pool_t *scratch_pool);
+
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_WC_H */
diff --git a/contrib/subversion/subversion/include/svn_xml.h b/contrib/subversion/subversion/include/svn_xml.h
new file mode 100644
index 0000000..90969be
--- /dev/null
+++ b/contrib/subversion/subversion/include/svn_xml.h
@@ -0,0 +1,381 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_xml.h
+ * @brief XML code shared by various Subversion libraries.
+ */
+
+#ifndef SVN_XML_H
+#define SVN_XML_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** The namespace all Subversion XML uses. */
+#define SVN_XML_NAMESPACE "svn:"
+
+/** Used as style argument to svn_xml_make_open_tag() and friends. */
+enum svn_xml_open_tag_style {
+ /** <tag ...> */
+ svn_xml_normal = 1,
+
+ /** <tag ...>, no cosmetic newline */
+ svn_xml_protect_pcdata,
+
+ /** <tag .../> */
+ svn_xml_self_closing
+};
+
+
+
+/** Determine if a string of character @a data of length @a len is a
+ * safe bet for use with the svn_xml_escape_* functions found in this
+ * header.
+ *
+ * Return @c TRUE if it is, @c FALSE otherwise.
+ *
+ * Essentially, this function exists to determine whether or not
+ * simply running a string of bytes through the Subversion XML escape
+ * routines will produce legitimate XML. It should only be necessary
+ * for data which might contain bytes that cannot be safely encoded
+ * into XML (certain control characters, for example).
+ */
+svn_boolean_t
+svn_xml_is_xml_safe(const char *data,
+ apr_size_t len);
+
+/** Create or append in @a *outstr an xml-escaped version of @a string,
+ * suitable for output as character data.
+ *
+ * If @a *outstr is @c NULL, set @a *outstr to a new stringbuf allocated
+ * in @a pool, else append to the existing stringbuf there.
+ */
+void
+svn_xml_escape_cdata_stringbuf(svn_stringbuf_t **outstr,
+ const svn_stringbuf_t *string,
+ apr_pool_t *pool);
+
+/** Same as svn_xml_escape_cdata_stringbuf(), but @a string is an
+ * @c svn_string_t.
+ */
+void
+svn_xml_escape_cdata_string(svn_stringbuf_t **outstr,
+ const svn_string_t *string,
+ apr_pool_t *pool);
+
+/** Same as svn_xml_escape_cdata_stringbuf(), but @a string is a
+ * NULL-terminated C string.
+ */
+void
+svn_xml_escape_cdata_cstring(svn_stringbuf_t **outstr,
+ const char *string,
+ apr_pool_t *pool);
+
+
+/** Create or append in @a *outstr an xml-escaped version of @a string,
+ * suitable for output as an attribute value.
+ *
+ * If @a *outstr is @c NULL, set @a *outstr to a new stringbuf allocated
+ * in @a pool, else append to the existing stringbuf there.
+ */
+void
+svn_xml_escape_attr_stringbuf(svn_stringbuf_t **outstr,
+ const svn_stringbuf_t *string,
+ apr_pool_t *pool);
+
+/** Same as svn_xml_escape_attr_stringbuf(), but @a string is an
+ * @c svn_string_t.
+ */
+void
+svn_xml_escape_attr_string(svn_stringbuf_t **outstr,
+ const svn_string_t *string,
+ apr_pool_t *pool);
+
+/** Same as svn_xml_escape_attr_stringbuf(), but @a string is a
+ * NULL-terminated C string.
+ */
+void
+svn_xml_escape_attr_cstring(svn_stringbuf_t **outstr,
+ const char *string,
+ apr_pool_t *pool);
+
+/**
+ * Return UTF-8 string @a string if it contains no characters that are
+ * unrepresentable in XML. Else, return a copy of @a string,
+ * allocated in @a pool, with each unrepresentable character replaced
+ * by "?\uuu", where "uuu" is the three-digit unsigned decimal value
+ * of that character.
+ *
+ * Neither the input nor the output need be valid XML; however, the
+ * output can always be safely XML-escaped.
+ *
+ * @note The current implementation treats all Unicode characters as
+ * representable, except for most ASCII control characters (the
+ * exceptions being CR, LF, and TAB, which are valid in XML). There
+ * may be other UTF-8 characters that are invalid in XML; see
+ * http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=90591
+ * and its thread for details.
+ *
+ * @since New in 1.2.
+ */
+const char *
+svn_xml_fuzzy_escape(const char *string,
+ apr_pool_t *pool);
+
+
+/*---------------------------------------------------------------*/
+
+/* Generalized Subversion XML Parsing */
+
+/** A generalized Subversion XML parser object */
+typedef struct svn_xml_parser_t svn_xml_parser_t;
+
+typedef void (*svn_xml_start_elem)(void *baton,
+ const char *name,
+ const char **atts);
+
+typedef void (*svn_xml_end_elem)(void *baton, const char *name);
+
+/* data is not NULL-terminated. */
+typedef void (*svn_xml_char_data)(void *baton,
+ const char *data,
+ apr_size_t len);
+
+
+/** Create a general Subversion XML parser */
+svn_xml_parser_t *
+svn_xml_make_parser(void *baton,
+ svn_xml_start_elem start_handler,
+ svn_xml_end_elem end_handler,
+ svn_xml_char_data data_handler,
+ apr_pool_t *pool);
+
+
+/** Free a general Subversion XML parser */
+void
+svn_xml_free_parser(svn_xml_parser_t *svn_parser);
+
+
+/** Push @a len bytes of xml data in @a buf at @a svn_parser.
+ *
+ * If this is the final push, @a is_final must be set.
+ *
+ * An error will be returned if there was a syntax problem in the XML,
+ * or if any of the callbacks set an error using
+ * svn_xml_signal_bailout().
+ *
+ * If an error is returned, the @c svn_xml_parser_t will have been freed
+ * automatically, so the caller should not call svn_xml_free_parser().
+ */
+svn_error_t *
+svn_xml_parse(svn_xml_parser_t *svn_parser,
+ const char *buf,
+ apr_size_t len,
+ svn_boolean_t is_final);
+
+
+
+/** The way to officially bail out of xml parsing.
+ *
+ * Store @a error in @a svn_parser and set all expat callbacks to @c NULL.
+ */
+void
+svn_xml_signal_bailout(svn_error_t *error,
+ svn_xml_parser_t *svn_parser);
+
+
+
+
+
+/*** Helpers for dealing with the data Expat gives us. ***/
+
+/** Return the value associated with @a name in expat attribute array @a atts,
+ * else return @c NULL.
+ *
+ * (There could never be a @c NULL attribute value in the XML,
+ * although the empty string is possible.)
+ *
+ * @a atts is an array of c-strings: even-numbered indexes are names,
+ * odd-numbers hold values. If all is right, it should end on an
+ * even-numbered index pointing to @c NULL.
+ */
+const char *
+svn_xml_get_attr_value(const char *name,
+ const char *const *atts);
+
+
+
+/* Converting between Expat attribute lists and APR hash tables. */
+
+
+/** Create an attribute hash from @c va_list @a ap.
+ *
+ * The contents of @a ap are alternating <tt>char *</tt> keys and
+ * <tt>char *</tt> vals, terminated by a final @c NULL falling on an
+ * even index (zero-based).
+ */
+apr_hash_t *
+svn_xml_ap_to_hash(va_list ap,
+ apr_pool_t *pool);
+
+/** Create a hash that corresponds to Expat xml attribute list @a atts.
+ *
+ * The hash's keys and values are <tt>char *</tt>'s.
+ *
+ * @a atts may be NULL, in which case you just get an empty hash back
+ * (this makes life more convenient for some callers).
+ */
+apr_hash_t *
+svn_xml_make_att_hash(const char **atts,
+ apr_pool_t *pool);
+
+
+/** Like svn_xml_make_att_hash(), but takes a hash and preserves any
+ * key/value pairs already in it.
+ */
+void
+svn_xml_hash_atts_preserving(const char **atts,
+ apr_hash_t *ht,
+ apr_pool_t *pool);
+
+/** Like svn_xml_make_att_hash(), but takes a hash and overwrites
+ * key/value pairs already in it that also appear in @a atts.
+ */
+void
+svn_xml_hash_atts_overlaying(const char **atts,
+ apr_hash_t *ht,
+ apr_pool_t *pool);
+
+
+
+/* Printing XML */
+
+/** Create an XML header and return it in @a *str.
+ *
+ * Fully-formed XML documents should start out with a header,
+ * something like <pre>
+ * \<?xml version="1.0" encoding="UTF-8"?\>
+ * </pre>
+ *
+ * This function returns such a header. @a *str must either be @c NULL, in
+ * which case a new string is created, or it must point to an existing
+ * string to be appended to. @a encoding must either be NULL, in which case
+ * encoding information is omitted from the header, or must be the name of
+ * the encoding of the XML document, such as "UTF-8".
+ *
+ * @since New in 1.7.
+ */
+void
+svn_xml_make_header2(svn_stringbuf_t **str,
+ const char *encoding,
+ apr_pool_t *pool);
+
+/** Like svn_xml_make_header2(), but does not emit encoding information.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
+SVN_DEPRECATED
+void
+svn_xml_make_header(svn_stringbuf_t **str,
+ apr_pool_t *pool);
+
+
+/** Store a new xml tag @a tagname in @a *str.
+ *
+ * If @a *str is @c NULL, set @a *str to a new stringbuf allocated
+ * in @a pool, else append to the existing stringbuf there.
+ *
+ * Take the tag's attributes from varargs, a NULL-terminated list of
+ * alternating <tt>char *</tt> key and <tt>char *</tt> val. Do xml-escaping
+ * on each val.
+ *
+ * @a style is one of the enumerated styles in @c svn_xml_open_tag_style.
+ */
+void
+svn_xml_make_open_tag(svn_stringbuf_t **str,
+ apr_pool_t *pool,
+ enum svn_xml_open_tag_style style,
+ const char *tagname,
+ ...);
+
+
+/** Like svn_xml_make_open_tag(), but takes a @c va_list instead of being
+ * variadic.
+ */
+void
+svn_xml_make_open_tag_v(svn_stringbuf_t **str,
+ apr_pool_t *pool,
+ enum svn_xml_open_tag_style style,
+ const char *tagname,
+ va_list ap);
+
+
+/** Like svn_xml_make_open_tag(), but takes a hash table of attributes
+ * (<tt>char *</tt> keys mapping to <tt>char *</tt> values).
+ *
+ * You might ask, why not just provide svn_xml_make_tag_atts()?
+ *
+ * The reason is that a hash table is the most natural interface to an
+ * attribute list; the fact that Expat uses <tt>char **</tt> atts instead is
+ * certainly a defensible implementation decision, but since we'd have
+ * to have special code to support such lists throughout Subversion
+ * anyway, we might as well write that code for the natural interface
+ * (hashes) and then convert in the few cases where conversion is
+ * needed. Someday it might even be nice to change expat-lite to work
+ * with apr hashes.
+ *
+ * See conversion functions svn_xml_make_att_hash() and
+ * svn_xml_make_att_hash_overlaying(). Callers should use those to
+ * convert Expat attr lists into hashes when necessary.
+ */
+void
+svn_xml_make_open_tag_hash(svn_stringbuf_t **str,
+ apr_pool_t *pool,
+ enum svn_xml_open_tag_style style,
+ const char *tagname,
+ apr_hash_t *attributes);
+
+
+/** Store an xml close tag @a tagname in @a str.
+ *
+ * If @a *str is @c NULL, set @a *str to a new stringbuf allocated
+ * in @a pool, else append to the existing stringbuf there.
+ */
+void
+svn_xml_make_close_tag(svn_stringbuf_t **str,
+ apr_pool_t *pool,
+ const char *tagname);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_XML_H */
diff --git a/contrib/subversion/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c b/contrib/subversion/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c
new file mode 100644
index 0000000..48dfa35
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c
@@ -0,0 +1,517 @@
+/*
+ * gnome_keyring.c: GNOME Keyring provider for SVN_AUTH_CRED_*
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+#include <glib.h>
+#include <gnome-keyring.h>
+
+#include "svn_auth.h"
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+
+#include "private/svn_auth_private.h"
+
+#include "svn_private_config.h"
+
+
+
+/*-----------------------------------------------------------------------*/
+/* GNOME Keyring simple provider, puts passwords in GNOME Keyring */
+/*-----------------------------------------------------------------------*/
+
+
+struct gnome_keyring_baton
+{
+ const char *keyring_name;
+ GnomeKeyringInfo *info;
+ GMainLoop *loop;
+};
+
+
+/* Callback function to destroy gnome_keyring_baton. */
+static void
+callback_destroy_data_keyring(void *data)
+{
+ struct gnome_keyring_baton *key_info = data;
+
+ if (data == NULL)
+ return;
+
+ free((void*)key_info->keyring_name);
+ key_info->keyring_name = NULL;
+
+ if (key_info->info)
+ {
+ gnome_keyring_info_free(key_info->info);
+ key_info->info = NULL;
+ }
+
+ return;
+}
+
+
+/* Callback function to complete the keyring operation. */
+static void
+callback_done(GnomeKeyringResult result,
+ gpointer data)
+{
+ struct gnome_keyring_baton *key_info = data;
+
+ g_main_loop_quit(key_info->loop);
+ return;
+}
+
+
+/* Callback function to get the keyring info. */
+static void
+callback_get_info_keyring(GnomeKeyringResult result,
+ GnomeKeyringInfo *info,
+ void *data)
+{
+ struct gnome_keyring_baton *key_info = data;
+
+ if (result == GNOME_KEYRING_RESULT_OK && info != NULL)
+ {
+ key_info->info = gnome_keyring_info_copy(info);
+ }
+ else
+ {
+ if (key_info->info != NULL)
+ gnome_keyring_info_free(key_info->info);
+
+ key_info->info = NULL;
+ }
+
+ g_main_loop_quit(key_info->loop);
+
+ return;
+}
+
+
+/* Callback function to get the default keyring string name. */
+static void
+callback_default_keyring(GnomeKeyringResult result,
+ const char *string,
+ void *data)
+{
+ struct gnome_keyring_baton *key_info = data;
+
+ if (result == GNOME_KEYRING_RESULT_OK && string != NULL)
+ {
+ key_info->keyring_name = strdup(string);
+ }
+ else
+ {
+ free((void*)key_info->keyring_name);
+ key_info->keyring_name = NULL;
+ }
+
+ g_main_loop_quit(key_info->loop);
+
+ return;
+}
+
+/* Returns the default keyring name, allocated in RESULT_POOL. */
+static char*
+get_default_keyring_name(apr_pool_t *result_pool)
+{
+ char *def = NULL;
+ struct gnome_keyring_baton key_info;
+
+ key_info.info = NULL;
+ key_info.keyring_name = NULL;
+
+ /* Finds default keyring. */
+ key_info.loop = g_main_loop_new(NULL, FALSE);
+ gnome_keyring_get_default_keyring(callback_default_keyring, &key_info, NULL);
+ g_main_loop_run(key_info.loop);
+
+ if (key_info.keyring_name == NULL)
+ {
+ callback_destroy_data_keyring(&key_info);
+ return NULL;
+ }
+
+ def = apr_pstrdup(result_pool, key_info.keyring_name);
+ callback_destroy_data_keyring(&key_info);
+
+ return def;
+}
+
+/* Returns TRUE if the KEYRING_NAME is locked. */
+static svn_boolean_t
+check_keyring_is_locked(const char *keyring_name)
+{
+ struct gnome_keyring_baton key_info;
+
+ key_info.info = NULL;
+ key_info.keyring_name = NULL;
+
+ /* Get details about the default keyring. */
+ key_info.loop = g_main_loop_new(NULL, FALSE);
+ gnome_keyring_get_info(keyring_name, callback_get_info_keyring, &key_info,
+ NULL);
+ g_main_loop_run(key_info.loop);
+
+ if (key_info.info == NULL)
+ {
+ callback_destroy_data_keyring(&key_info);
+ return FALSE;
+ }
+
+ /* Check if keyring is locked. */
+ if (gnome_keyring_info_get_is_locked(key_info.info))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+/* Unlock the KEYRING_NAME with the KEYRING_PASSWORD. If KEYRING was
+ successfully unlocked return TRUE. */
+static svn_boolean_t
+unlock_gnome_keyring(const char *keyring_name,
+ const char *keyring_password,
+ apr_pool_t *pool)
+{
+ struct gnome_keyring_baton key_info;
+
+ key_info.info = NULL;
+ key_info.keyring_name = NULL;
+
+ /* Get details about the default keyring. */
+ key_info.loop = g_main_loop_new(NULL, FALSE);
+ gnome_keyring_get_info(keyring_name, callback_get_info_keyring,
+ &key_info, NULL);
+ g_main_loop_run(key_info.loop);
+
+ if (key_info.info == NULL)
+ {
+ callback_destroy_data_keyring(&key_info);
+ return FALSE;
+ }
+ else
+ {
+ key_info.loop = g_main_loop_new(NULL, FALSE);
+ gnome_keyring_unlock(keyring_name, keyring_password,
+ callback_done, &key_info, NULL);
+ g_main_loop_run(key_info.loop);
+ }
+ callback_destroy_data_keyring(&key_info);
+ if (check_keyring_is_locked(keyring_name))
+ return FALSE;
+
+ return TRUE;
+}
+
+
+/* There is a race here: this ensures keyring is unlocked just now,
+ but will it still be unlocked when we use it? */
+static svn_error_t *
+ensure_gnome_keyring_is_unlocked(svn_boolean_t non_interactive,
+ apr_hash_t *parameters,
+ apr_pool_t *scratch_pool)
+{
+ const char *default_keyring = get_default_keyring_name(scratch_pool);
+
+ if (! non_interactive)
+ {
+ svn_auth_gnome_keyring_unlock_prompt_func_t unlock_prompt_func =
+ svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC);
+ void *unlock_prompt_baton =
+ svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_BATON);
+
+ char *keyring_password;
+
+ if (unlock_prompt_func && check_keyring_is_locked(default_keyring))
+ {
+ SVN_ERR((*unlock_prompt_func)(&keyring_password,
+ default_keyring,
+ unlock_prompt_baton,
+ scratch_pool));
+
+ /* If keyring is locked give up and try the next provider. */
+ if (! unlock_gnome_keyring(default_keyring, keyring_password,
+ scratch_pool))
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ {
+ if (check_keyring_is_locked(default_keyring))
+ {
+ return svn_error_create(SVN_ERR_AUTHN_CREDS_UNAVAILABLE, NULL,
+ _("GNOME Keyring is locked and "
+ "we are non-interactive"));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implementation of svn_auth__password_get_t that retrieves the password
+ from GNOME Keyring. */
+static svn_error_t *
+password_get_gnome_keyring(svn_boolean_t *done,
+ const char **password,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ GnomeKeyringResult result;
+ GList *items;
+
+ *done = FALSE;
+
+ SVN_ERR(ensure_gnome_keyring_is_unlocked(non_interactive, parameters, pool));
+
+ if (! svn_hash_gets(parameters, "gnome-keyring-opening-failed"))
+ {
+ result = gnome_keyring_find_network_password_sync(username, realmstring,
+ NULL, NULL, NULL, NULL,
+ 0, &items);
+ }
+ else
+ {
+ result = GNOME_KEYRING_RESULT_DENIED;
+ }
+
+ if (result == GNOME_KEYRING_RESULT_OK)
+ {
+ if (items && items->data)
+ {
+ GnomeKeyringNetworkPasswordData *item = items->data;
+ if (item->password)
+ {
+ size_t len = strlen(item->password);
+ if (len > 0)
+ {
+ *password = apr_pstrmemdup(pool, item->password, len);
+ *done = TRUE;
+ }
+ }
+ gnome_keyring_network_password_list_free(items);
+ }
+ }
+ else
+ {
+ svn_hash_sets(parameters, "gnome-keyring-opening-failed", "");
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implementation of svn_auth__password_set_t that stores the password in
+ GNOME Keyring. */
+static svn_error_t *
+password_set_gnome_keyring(svn_boolean_t *done,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ const char *password,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ GnomeKeyringResult result;
+ guint32 item_id;
+
+ *done = FALSE;
+
+ SVN_ERR(ensure_gnome_keyring_is_unlocked(non_interactive, parameters, pool));
+
+ if (! svn_hash_gets(parameters, "gnome-keyring-opening-failed"))
+ {
+ result = gnome_keyring_set_network_password_sync(NULL, /* default keyring */
+ username, realmstring,
+ NULL, NULL, NULL, NULL,
+ 0, password,
+ &item_id);
+ }
+ else
+ {
+ result = GNOME_KEYRING_RESULT_DENIED;
+ }
+ if (result != GNOME_KEYRING_RESULT_OK)
+ {
+ svn_hash_sets(parameters, "gnome-keyring-opening-failed", "");
+ }
+
+ *done = (result == GNOME_KEYRING_RESULT_OK);
+ return SVN_NO_ERROR;
+}
+
+/* Get cached encrypted credentials from the simple provider's cache. */
+static svn_error_t *
+simple_gnome_keyring_first_creds(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_get(credentials,
+ iter_baton, provider_baton,
+ parameters, realmstring,
+ password_get_gnome_keyring,
+ SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE,
+ pool);
+}
+
+/* Save encrypted credentials to the simple provider's cache. */
+static svn_error_t *
+simple_gnome_keyring_save_creds(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_set(saved, credentials,
+ provider_baton, parameters,
+ realmstring,
+ password_set_gnome_keyring,
+ SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE,
+ pool);
+}
+
+#if GLIB_CHECK_VERSION(2,6,0)
+static void
+log_noop(const gchar *log_domain, GLogLevelFlags log_level,
+ const gchar *message, gpointer user_data)
+{
+ /* do nothing */
+}
+#endif
+
+static void
+init_gnome_keyring(void)
+{
+ const char *application_name = NULL;
+ application_name = g_get_application_name();
+ if (!application_name)
+ g_set_application_name("Subversion");
+
+ /* Ideally we call g_log_set_handler() with a log_domain specific to
+ libgnome-keyring. Unfortunately, at least as of gnome-keyring
+ 2.22.3, it doesn't have its own log_domain. As a result, we
+ suppress stderr spam for not only libgnome-keyring, but for
+ anything else the app is linked to that uses glib logging and
+ doesn't specify a log_domain. */
+#if GLIB_CHECK_VERSION(2,6,0)
+ g_log_set_default_handler(log_noop, NULL);
+#endif
+}
+
+static const svn_auth_provider_t gnome_keyring_simple_provider = {
+ SVN_AUTH_CRED_SIMPLE,
+ simple_gnome_keyring_first_creds,
+ NULL,
+ simple_gnome_keyring_save_creds
+};
+
+/* Public API */
+void
+svn_auth_get_gnome_keyring_simple_provider
+ (svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+
+ po->vtable = &gnome_keyring_simple_provider;
+ *provider = po;
+
+ init_gnome_keyring();
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* GNOME Keyring SSL client certificate passphrase provider, */
+/* puts passphrases in GNOME Keyring */
+/*-----------------------------------------------------------------------*/
+
+/* Get cached encrypted credentials from the ssl client cert password
+ provider's cache. */
+static svn_error_t *
+ssl_client_cert_pw_gnome_keyring_first_creds(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__ssl_client_cert_pw_cache_get(
+ credentials, iter_baton, provider_baton, parameters, realmstring,
+ password_get_gnome_keyring, SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE,
+ pool);
+}
+
+/* Save encrypted credentials to the ssl client cert password provider's
+ cache. */
+static svn_error_t *
+ssl_client_cert_pw_gnome_keyring_save_creds(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__ssl_client_cert_pw_cache_set(
+ saved, credentials, provider_baton, parameters, realmstring,
+ password_set_gnome_keyring, SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE,
+ pool);
+}
+
+static const svn_auth_provider_t gnome_keyring_ssl_client_cert_pw_provider = {
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ ssl_client_cert_pw_gnome_keyring_first_creds,
+ NULL,
+ ssl_client_cert_pw_gnome_keyring_save_creds
+};
+
+/* Public API */
+void
+svn_auth_get_gnome_keyring_ssl_client_cert_pw_provider
+ (svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+
+ po->vtable = &gnome_keyring_ssl_client_cert_pw_provider;
+ *provider = po;
+
+ init_gnome_keyring();
+}
diff --git a/contrib/subversion/subversion/libsvn_auth_gnome_keyring/version.c b/contrib/subversion/subversion/libsvn_auth_gnome_keyring/version.c
new file mode 100644
index 0000000..361fe01
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_auth_gnome_keyring/version.c
@@ -0,0 +1,35 @@
+/*
+ * version.c: libsvn_auth_gnome_keyring version number
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#include "svn_auth.h"
+#include "svn_version.h"
+
+const svn_version_t *
+svn_auth_gnome_keyring_version(void)
+{
+ SVN_VERSION_BODY;
+}
diff --git a/contrib/subversion/subversion/libsvn_auth_kwallet/kwallet.cpp b/contrib/subversion/subversion/libsvn_auth_kwallet/kwallet.cpp
new file mode 100644
index 0000000..e1a345e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_auth_kwallet/kwallet.cpp
@@ -0,0 +1,458 @@
+/*
+ * kwallet.cpp: KWallet provider for SVN_AUTH_CRED_*
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+
+#include <dbus/dbus.h>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QString>
+
+#include <kaboutdata.h>
+#include <kcmdlineargs.h>
+#include <kcomponentdata.h>
+#include <klocalizedstring.h>
+#include <kwallet.h>
+
+#include "svn_auth.h"
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_version.h"
+
+#include "private/svn_auth_private.h"
+
+#include "svn_private_config.h"
+
+
+/*-----------------------------------------------------------------------*/
+/* KWallet simple provider, puts passwords in KWallet */
+/*-----------------------------------------------------------------------*/
+
+static int q_argc = 1;
+static char q_argv0[] = "svn"; // Build non-const char * from string constant
+static char *q_argv[] = { q_argv0 };
+
+static const char *
+get_application_name(apr_hash_t *parameters,
+ apr_pool_t *pool)
+{
+ svn_config_t *config =
+ static_cast<svn_config_t *> (apr_hash_get(parameters,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
+ APR_HASH_KEY_STRING));
+ svn_boolean_t svn_application_name_with_pid;
+ svn_config_get_bool(config,
+ &svn_application_name_with_pid,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_KWALLET_SVN_APPLICATION_NAME_WITH_PID,
+ FALSE);
+ const char *svn_application_name;
+ if (svn_application_name_with_pid)
+ {
+ svn_application_name = apr_psprintf(pool, "Subversion [%ld]", long(getpid()));
+ }
+ else
+ {
+ svn_application_name = "Subversion";
+ }
+ return svn_application_name;
+}
+
+static QString
+get_wallet_name(apr_hash_t *parameters)
+{
+ svn_config_t *config =
+ static_cast<svn_config_t *> (apr_hash_get(parameters,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
+ APR_HASH_KEY_STRING));
+ const char *wallet_name;
+ svn_config_get(config,
+ &wallet_name,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_KWALLET_WALLET,
+ "");
+ if (strcmp(wallet_name, "") == 0)
+ {
+ return KWallet::Wallet::NetworkWallet();
+ }
+ else
+ {
+ return QString::fromUtf8(wallet_name);
+ }
+}
+
+static WId
+get_wid(void)
+{
+ WId wid = 1;
+ const char *wid_env_string = getenv("WINDOWID");
+
+ if (wid_env_string)
+ {
+ apr_int64_t wid_env;
+ svn_error_t *err;
+
+ err = svn_cstring_atoi64(&wid_env, wid_env_string);
+ if (err)
+ svn_error_clear(err);
+ else
+ wid = (WId)wid_env;
+ }
+
+ return wid;
+}
+
+static KWallet::Wallet *
+get_wallet(QString wallet_name,
+ apr_hash_t *parameters)
+{
+ KWallet::Wallet *wallet =
+ static_cast<KWallet::Wallet *> (apr_hash_get(parameters,
+ "kwallet-wallet",
+ APR_HASH_KEY_STRING));
+ if (! wallet && ! apr_hash_get(parameters,
+ "kwallet-opening-failed",
+ APR_HASH_KEY_STRING))
+ {
+ wallet = KWallet::Wallet::openWallet(wallet_name, get_wid(),
+ KWallet::Wallet::Synchronous);
+ }
+ if (wallet)
+ {
+ apr_hash_set(parameters,
+ "kwallet-wallet",
+ APR_HASH_KEY_STRING,
+ wallet);
+ }
+ else
+ {
+ apr_hash_set(parameters,
+ "kwallet-opening-failed",
+ APR_HASH_KEY_STRING,
+ "");
+ }
+ return wallet;
+}
+
+static apr_status_t
+kwallet_terminate(void *data)
+{
+ apr_hash_t *parameters = static_cast<apr_hash_t *> (data);
+ if (apr_hash_get(parameters, "kwallet-initialized", APR_HASH_KEY_STRING))
+ {
+ KWallet::Wallet *wallet = get_wallet(NULL, parameters);
+ delete wallet;
+ apr_hash_set(parameters,
+ "kwallet-initialized",
+ APR_HASH_KEY_STRING,
+ NULL);
+ }
+ return APR_SUCCESS;
+}
+
+/* Implementation of svn_auth__password_get_t that retrieves
+ the password from KWallet. */
+static svn_error_t *
+kwallet_password_get(svn_boolean_t *done,
+ const char **password,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ QString wallet_name = get_wallet_name(parameters);
+
+ *done = FALSE;
+
+ if (! dbus_bus_get(DBUS_BUS_SESSION, NULL))
+ {
+ return SVN_NO_ERROR;
+ }
+
+ if (non_interactive)
+ {
+ if (!KWallet::Wallet::isOpen(wallet_name))
+ return SVN_NO_ERROR;
+
+ /* There is a race here: the wallet was open just now, but will
+ it still be open when we come to use it below? */
+ }
+
+ QCoreApplication *app;
+ if (! qApp)
+ {
+ int argc = q_argc;
+ app = new QCoreApplication(argc, q_argv);
+ }
+
+ KCmdLineArgs::init(q_argc, q_argv,
+ get_application_name(parameters, pool),
+ "subversion",
+ ki18n(get_application_name(parameters, pool)),
+ SVN_VER_NUMBER,
+ ki18n("Version control system"),
+ KCmdLineArgs::CmdLineArgKDE);
+ KComponentData component_data(KCmdLineArgs::aboutData());
+ QString folder = QString::fromUtf8("Subversion");
+ QString key =
+ QString::fromUtf8(username) + "@" + QString::fromUtf8(realmstring);
+ if (! KWallet::Wallet::keyDoesNotExist(wallet_name, folder, key))
+ {
+ KWallet::Wallet *wallet = get_wallet(wallet_name, parameters);
+ if (wallet)
+ {
+ apr_hash_set(parameters,
+ "kwallet-initialized",
+ APR_HASH_KEY_STRING,
+ "");
+ if (wallet->setFolder(folder))
+ {
+ QString q_password;
+ if (wallet->readPassword(key, q_password) == 0)
+ {
+ *password = apr_pstrmemdup(pool,
+ q_password.toUtf8().data(),
+ q_password.size());
+ *done = TRUE;
+ }
+ }
+ }
+ }
+
+ apr_pool_cleanup_register(pool, parameters, kwallet_terminate,
+ apr_pool_cleanup_null);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implementation of svn_auth__password_set_t that stores
+ the password in KWallet. */
+static svn_error_t *
+kwallet_password_set(svn_boolean_t *done,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ const char *password,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ QString wallet_name = get_wallet_name(parameters);
+
+ *done = FALSE;
+
+ if (! dbus_bus_get(DBUS_BUS_SESSION, NULL))
+ {
+ return SVN_NO_ERROR;
+ }
+
+ if (non_interactive)
+ {
+ if (!KWallet::Wallet::isOpen(wallet_name))
+ return SVN_NO_ERROR;
+
+ /* There is a race here: the wallet was open just now, but will
+ it still be open when we come to use it below? */
+ }
+
+ QCoreApplication *app;
+ if (! qApp)
+ {
+ int argc = q_argc;
+ app = new QCoreApplication(argc, q_argv);
+ }
+
+ KCmdLineArgs::init(q_argc, q_argv,
+ get_application_name(parameters, pool),
+ "subversion",
+ ki18n(get_application_name(parameters, pool)),
+ SVN_VER_NUMBER,
+ ki18n("Version control system"),
+ KCmdLineArgs::CmdLineArgKDE);
+ KComponentData component_data(KCmdLineArgs::aboutData());
+ QString q_password = QString::fromUtf8(password);
+ QString folder = QString::fromUtf8("Subversion");
+ KWallet::Wallet *wallet = get_wallet(wallet_name, parameters);
+ if (wallet)
+ {
+ apr_hash_set(parameters,
+ "kwallet-initialized",
+ APR_HASH_KEY_STRING,
+ "");
+ if (! wallet->hasFolder(folder))
+ {
+ wallet->createFolder(folder);
+ }
+ if (wallet->setFolder(folder))
+ {
+ QString key = QString::fromUtf8(username) + "@"
+ + QString::fromUtf8(realmstring);
+ if (wallet->writePassword(key, q_password) == 0)
+ {
+ *done = TRUE;
+ }
+ }
+ }
+
+ apr_pool_cleanup_register(pool, parameters, kwallet_terminate,
+ apr_pool_cleanup_null);
+
+ return SVN_NO_ERROR;
+}
+
+/* Get cached encrypted credentials from the simple provider's cache. */
+static svn_error_t *
+kwallet_simple_first_creds(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_get(credentials,
+ iter_baton,
+ provider_baton,
+ parameters,
+ realmstring,
+ kwallet_password_get,
+ SVN_AUTH__KWALLET_PASSWORD_TYPE,
+ pool);
+}
+
+/* Save encrypted credentials to the simple provider's cache. */
+static svn_error_t *
+kwallet_simple_save_creds(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_set(saved, credentials,
+ provider_baton,
+ parameters,
+ realmstring,
+ kwallet_password_set,
+ SVN_AUTH__KWALLET_PASSWORD_TYPE,
+ pool);
+}
+
+static const svn_auth_provider_t kwallet_simple_provider = {
+ SVN_AUTH_CRED_SIMPLE,
+ kwallet_simple_first_creds,
+ NULL,
+ kwallet_simple_save_creds
+};
+
+/* Public API */
+extern "C" {
+void
+svn_auth_get_kwallet_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po =
+ static_cast<svn_auth_provider_object_t *> (apr_pcalloc(pool, sizeof(*po)));
+
+ po->vtable = &kwallet_simple_provider;
+ *provider = po;
+}
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* KWallet SSL client certificate passphrase provider, */
+/* puts passphrases in KWallet */
+/*-----------------------------------------------------------------------*/
+
+/* Get cached encrypted credentials from the ssl client cert password
+ provider's cache. */
+static svn_error_t *
+kwallet_ssl_client_cert_pw_first_creds(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__ssl_client_cert_pw_cache_get(credentials,
+ iter_baton, provider_baton,
+ parameters, realmstring,
+ kwallet_password_get,
+ SVN_AUTH__KWALLET_PASSWORD_TYPE,
+ pool);
+}
+
+/* Save encrypted credentials to the ssl client cert password provider's
+ cache. */
+static svn_error_t *
+kwallet_ssl_client_cert_pw_save_creds(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__ssl_client_cert_pw_cache_set(saved, credentials,
+ provider_baton, parameters,
+ realmstring,
+ kwallet_password_set,
+ SVN_AUTH__KWALLET_PASSWORD_TYPE,
+ pool);
+}
+
+static const svn_auth_provider_t kwallet_ssl_client_cert_pw_provider = {
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ kwallet_ssl_client_cert_pw_first_creds,
+ NULL,
+ kwallet_ssl_client_cert_pw_save_creds
+};
+
+/* Public API */
+extern "C" {
+void
+svn_auth_get_kwallet_ssl_client_cert_pw_provider
+ (svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po =
+ static_cast<svn_auth_provider_object_t *> (apr_pcalloc(pool, sizeof(*po)));
+
+ po->vtable = &kwallet_ssl_client_cert_pw_provider;
+ *provider = po;
+}
+}
diff --git a/contrib/subversion/subversion/libsvn_auth_kwallet/version.c b/contrib/subversion/subversion/libsvn_auth_kwallet/version.c
new file mode 100644
index 0000000..0a46e41
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_auth_kwallet/version.c
@@ -0,0 +1,35 @@
+/*
+ * version.c: libsvn_auth_kwallet version number
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#include "svn_auth.h"
+#include "svn_version.h"
+
+const svn_version_t *
+svn_auth_kwallet_version(void)
+{
+ SVN_VERSION_BODY;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/add.c b/contrib/subversion/subversion/libsvn_client/add.c
new file mode 100644
index 0000000..f121bc8
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/add.c
@@ -0,0 +1,1326 @@
+/*
+ * add.c: wrappers around wc add/mkdir functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <apr_lib.h>
+#include <apr_fnmatch.h>
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_io.h"
+#include "svn_config.h"
+#include "svn_props.h"
+#include "svn_hash.h"
+#include "svn_sorts.h"
+#include "client.h"
+#include "svn_ctype.h"
+
+#include "private/svn_client_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_magic.h"
+
+#include "svn_private_config.h"
+
+
+
+/*** Code. ***/
+
+/* Remove leading and trailing white space from a C string, in place. */
+static void
+trim_string(char **pstr)
+{
+ char *str = *pstr;
+ size_t i;
+
+ while (svn_ctype_isspace(*str))
+ str++;
+ *pstr = str;
+ i = strlen(str);
+ while ((i > 0) && svn_ctype_isspace(str[i-1]))
+ i--;
+ str[i] = '\0';
+}
+
+/* Remove leading and trailing single- or double quotes from a C string,
+ * in place. */
+static void
+unquote_string(char **pstr)
+{
+ char *str = *pstr;
+ size_t i = strlen(str);
+
+ if (i > 0 && ((*str == '"' && str[i - 1] == '"') ||
+ (*str == '\'' && str[i - 1] == '\'')))
+ {
+ str[i - 1] = '\0';
+ str++;
+ }
+ *pstr = str;
+}
+
+/* Split PROPERTY and store each individual value in PROPS.
+ Allocates from POOL. */
+static void
+split_props(apr_array_header_t **props,
+ const char *property,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *temp_props;
+ char *new_prop;
+ int i = 0;
+ int j = 0;
+
+ temp_props = apr_array_make(pool, 4, sizeof(char *));
+ new_prop = apr_palloc(pool, strlen(property)+1);
+
+ for (i = 0; property[i] != '\0'; i++)
+ {
+ if (property[i] != ';')
+ {
+ new_prop[j] = property[i];
+ j++;
+ }
+ else if (property[i] == ';')
+ {
+ /* ";;" becomes ";" */
+ if (property[i+1] == ';')
+ {
+ new_prop[j] = ';';
+ j++;
+ i++;
+ }
+ else
+ {
+ new_prop[j] = '\0';
+ APR_ARRAY_PUSH(temp_props, char *) = new_prop;
+ new_prop += j + 1;
+ j = 0;
+ }
+ }
+ }
+ new_prop[j] = '\0';
+ APR_ARRAY_PUSH(temp_props, char *) = new_prop;
+ *props = temp_props;
+}
+
+/* PROPVALS is a hash mapping char * property names to const char * property
+ values. PROPERTIES can be empty but not NULL.
+
+ If FILENAME doesn't match the filename pattern PATTERN case insensitively,
+ the do nothing. Otherwise for each 'name':'value' pair in PROPVALS, add
+ a new entry mappying 'name' to a svn_string_t * wrapping the 'value' in
+ PROPERTIES. The svn_string_t is allocated in the pool used to allocate
+ PROPERTIES, but the char *'s from PROPVALS are re-used in PROPERTIES.
+ If PROPVALS contains a 'svn:mime-type' mapping, then set *MIMETYPE to
+ the mapped value. Likewise if PROPVALS contains a mapping for
+ svn:executable, then set *HAVE_EXECUTABLE to TRUE.
+
+ Use SCRATCH_POOL for temporary allocations.
+*/
+static void
+get_auto_props_for_pattern(apr_hash_t *properties,
+ const char **mimetype,
+ svn_boolean_t *have_executable,
+ const char *filename,
+ const char *pattern,
+ apr_hash_t *propvals,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ /* check if filename matches and return if it doesn't */
+ if (apr_fnmatch(pattern, filename,
+ APR_FNM_CASE_BLIND) == APR_FNM_NOMATCH)
+ return;
+
+ for (hi = apr_hash_first(scratch_pool, propvals);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ const char *propval = svn__apr_hash_index_val(hi);
+ svn_string_t *propval_str =
+ svn_string_create_empty(apr_hash_pool_get(properties));
+
+ propval_str->data = propval;
+ propval_str->len = strlen(propval);
+
+ svn_hash_sets(properties, propname, propval_str);
+ if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
+ *mimetype = propval;
+ else if (strcmp(propname, SVN_PROP_EXECUTABLE) == 0)
+ *have_executable = TRUE;
+ }
+}
+
+svn_error_t *
+svn_client__get_paths_auto_props(apr_hash_t **properties,
+ const char **mimetype,
+ const char *path,
+ svn_magic__cookie_t *magic_cookie,
+ apr_hash_t *autoprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ svn_boolean_t have_executable = FALSE;
+
+ *properties = apr_hash_make(result_pool);
+ *mimetype = NULL;
+
+ if (autoprops)
+ {
+ for (hi = apr_hash_first(scratch_pool, autoprops);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ const char *pattern = svn__apr_hash_index_key(hi);
+ apr_hash_t *propvals = svn__apr_hash_index_val(hi);
+
+ get_auto_props_for_pattern(*properties, mimetype, &have_executable,
+ svn_dirent_basename(path, scratch_pool),
+ pattern, propvals, scratch_pool);
+ }
+ }
+
+ /* if mimetype has not been set check the file */
+ if (! *mimetype)
+ {
+ SVN_ERR(svn_io_detect_mimetype2(mimetype, path, ctx->mimetypes_map,
+ result_pool));
+
+ /* If we got no mime-type, or if it is "application/octet-stream",
+ * try to get the mime-type from libmagic. */
+ if (magic_cookie &&
+ (!*mimetype ||
+ strcmp(*mimetype, "application/octet-stream") == 0))
+ {
+ const char *magic_mimetype;
+
+ /* Since libmagic usually treats UTF-16 files as "text/plain",
+ * svn_magic__detect_binary_mimetype() will return NULL for such
+ * files. This is fine for now since we currently don't support
+ * UTF-16-encoded text files (issue #2194).
+ * Once we do support UTF-16 this code path will fail to detect
+ * them as text unless the svn_io_detect_mimetype2() call above
+ * returns "text/plain" for them. */
+ SVN_ERR(svn_magic__detect_binary_mimetype(&magic_mimetype,
+ path, magic_cookie,
+ result_pool,
+ scratch_pool));
+ if (magic_mimetype)
+ *mimetype = magic_mimetype;
+ }
+
+ if (*mimetype)
+ apr_hash_set(*properties, SVN_PROP_MIME_TYPE,
+ strlen(SVN_PROP_MIME_TYPE),
+ svn_string_create(*mimetype, result_pool));
+ }
+
+ /* if executable has not been set check the file */
+ if (! have_executable)
+ {
+ svn_boolean_t executable = FALSE;
+ SVN_ERR(svn_io_is_file_executable(&executable, path, scratch_pool));
+ if (executable)
+ apr_hash_set(*properties, SVN_PROP_EXECUTABLE,
+ strlen(SVN_PROP_EXECUTABLE),
+ svn_string_create_empty(result_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Only call this if the on-disk node kind is a file. */
+static svn_error_t *
+add_file(const char *local_abspath,
+ svn_magic__cookie_t *magic_cookie,
+ apr_hash_t *autoprops,
+ svn_boolean_t no_autoprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_hash_t *properties;
+ const char *mimetype;
+ svn_node_kind_t kind;
+ svn_boolean_t is_special;
+
+ /* Check to see if this is a special file. */
+ SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special, pool));
+
+ /* Determine the properties that the file should have */
+ if (is_special)
+ {
+ mimetype = NULL;
+ properties = apr_hash_make(pool);
+ svn_hash_sets(properties, SVN_PROP_SPECIAL,
+ svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool));
+ }
+ else
+ {
+ apr_hash_t *file_autoprops = NULL;
+
+ /* Get automatic properties */
+ /* If we are setting autoprops grab the inherited svn:auto-props and
+ config file auto-props for this file if we haven't already got them
+ when iterating over the file's unversioned parents. */
+ if (!no_autoprops)
+ {
+ if (autoprops == NULL)
+ SVN_ERR(svn_client__get_all_auto_props(
+ &file_autoprops, svn_dirent_dirname(local_abspath,pool),
+ ctx, pool, pool));
+ else
+ file_autoprops = autoprops;
+ }
+
+ /* This may fail on write-only files:
+ we open them to estimate file type. */
+ SVN_ERR(svn_client__get_paths_auto_props(&properties, &mimetype,
+ local_abspath, magic_cookie,
+ file_autoprops, ctx, pool,
+ pool));
+ }
+
+ /* Add the file */
+ SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath, properties,
+ ctx->notify_func2, ctx->notify_baton2, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Schedule directory DIR_ABSPATH, and some of the tree under it, for
+ * addition. DEPTH is the depth at this point in the descent (it may
+ * be changed for recursive calls).
+ *
+ * If DIR_ABSPATH (or any item below DIR_ABSPATH) is already scheduled for
+ * addition, add will fail and return an error unless FORCE is TRUE.
+ *
+ * Use MAGIC_COOKIE (which may be NULL) to detect the mime-type of files
+ * if necessary.
+ *
+ * If not NULL, CONFIG_AUTOPROPS is a hash representing the config file and
+ * svn:auto-props autoprops which apply to DIR_ABSPATH. It maps
+ * const char * file patterns to another hash which maps const char *
+ * property names to const char *property values. If CONFIG_AUTOPROPS is
+ * NULL and the config file and svn:auto-props autoprops are required by this
+ * function, then such will be obtained.
+ *
+ * If IGNORES is not NULL, then it is an array of const char * ignore patterns
+ * that apply to any children of DIR_ABSPATH. If REFRESH_IGNORES is TRUE, then
+ * the passed in value of IGNORES (if any) is itself ignored and this function
+ * will gather all ignore patterns applicable to DIR_ABSPATH itself (allocated in
+ * RESULT_POOL). Any recursive calls to this function get the refreshed ignore
+ * patterns. If IGNORES is NULL and REFRESH_IGNORES is FALSE, then all children of DIR_ABSPATH
+ * are unconditionally added.
+ *
+ * If CTX->CANCEL_FUNC is non-null, call it with CTX->CANCEL_BATON to allow
+ * the user to cancel the operation.
+ *
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+add_dir_recursive(const char *dir_abspath,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t no_autoprops,
+ svn_magic__cookie_t *magic_cookie,
+ apr_hash_t *config_autoprops,
+ svn_boolean_t refresh_ignores,
+ apr_array_header_t *ignores,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ apr_pool_t *iterpool;
+ apr_hash_t *dirents;
+ apr_hash_index_t *hi;
+ svn_boolean_t entry_exists = FALSE;
+
+ /* Check cancellation; note that this catches recursive calls too. */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* Add this directory to revision control. */
+ err = svn_wc_add_from_disk2(ctx->wc_ctx, dir_abspath, NULL /*props*/,
+ ctx->notify_func2, ctx->notify_baton2,
+ iterpool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
+ {
+ svn_error_clear(err);
+ entry_exists = TRUE;
+ }
+ else if (err)
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ /* Fetch ignores after adding to handle ignores on the directory itself
+ and ancestors via the single db optimization in libsvn_wc */
+ if (refresh_ignores)
+ SVN_ERR(svn_wc_get_ignores2(&ignores, ctx->wc_ctx, dir_abspath,
+ ctx->config, result_pool, iterpool));
+
+ /* If DIR_ABSPATH is the root of an unversioned subtree then get the
+ following "autoprops":
+
+ 1) Explicit and inherited svn:auto-props properties on
+ DIR_ABSPATH
+ 2) auto-props from the CTX->CONFIG hash
+
+ Since this set of autoprops applies to all unversioned children of
+ DIR_ABSPATH, we will pass these along to any recursive calls to
+ add_dir_recursive() and calls to add_file() below. Thus sparing
+ these callees from looking up the same information. */
+ if (!entry_exists && config_autoprops == NULL)
+ {
+ SVN_ERR(svn_client__get_all_auto_props(&config_autoprops, dir_abspath,
+ ctx, scratch_pool, iterpool));
+ }
+
+ SVN_ERR(svn_io_get_dirents3(&dirents, dir_abspath, TRUE, scratch_pool,
+ iterpool));
+
+ /* Read the directory entries one by one and add those things to
+ version control. */
+ for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi);
+ const char *abspath;
+
+ svn_pool_clear(iterpool);
+
+ /* Check cancellation so you can cancel during an
+ * add of a directory with lots of files. */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ /* Skip over SVN admin directories. */
+ if (svn_wc_is_adm_dir(name, iterpool))
+ continue;
+
+ if (ignores
+ && svn_wc_match_ignore_list(name, ignores, iterpool))
+ continue;
+
+ /* Construct the full path of the entry. */
+ abspath = svn_dirent_join(dir_abspath, name, iterpool);
+
+ /* Recurse on directories; add files; ignore the rest. */
+ if (dirent->kind == svn_node_dir && depth >= svn_depth_immediates)
+ {
+ svn_depth_t depth_below_here = depth;
+ if (depth == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ /* When DIR_ABSPATH is the root of an unversioned subtree then
+ it and all of its children have the same set of ignores. So
+ save any recursive calls the extra work of finding the same
+ set of ignores. */
+ if (refresh_ignores && !entry_exists)
+ refresh_ignores = FALSE;
+
+ SVN_ERR(add_dir_recursive(abspath, depth_below_here,
+ force, no_autoprops,
+ magic_cookie, config_autoprops,
+ refresh_ignores, ignores, ctx,
+ result_pool, iterpool));
+ }
+ else if ((dirent->kind == svn_node_file || dirent->special)
+ && depth >= svn_depth_files)
+ {
+ err = add_file(abspath, magic_cookie, config_autoprops,
+ no_autoprops, ctx, iterpool);
+ if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+ }
+ }
+
+ /* Destroy the per-iteration pool. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* This structure is used as baton for collecting the config entries
+ in the auto-props section and any inherited svn:auto-props
+ properties.
+*/
+typedef struct collect_auto_props_baton_t
+{
+ /* the hash table for storing the property name/value pairs */
+ apr_hash_t *autoprops;
+
+ /* a pool used for allocating memory */
+ apr_pool_t *result_pool;
+} collect_auto_props_baton_t;
+
+/* Implements svn_config_enumerator2_t callback.
+
+ For one auto-props config entry (NAME, VALUE), stash a copy of
+ NAME and VALUE, allocated in BATON->POOL, in BATON->AUTOPROP.
+ BATON must point to an collect_auto_props_baton_t.
+*/
+static svn_boolean_t
+all_auto_props_collector(const char *name,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ collect_auto_props_baton_t *autoprops_baton = baton;
+ apr_array_header_t *autoprops;
+ int i;
+
+ /* nothing to do here without a value */
+ if (*value == 0)
+ return TRUE;
+
+ split_props(&autoprops, value, pool);
+
+ for (i = 0; i < autoprops->nelts; i ++)
+ {
+ size_t len;
+ const char *this_value;
+ char *property = APR_ARRAY_IDX(autoprops, i, char *);
+ char *equal_sign = strchr(property, '=');
+
+ if (equal_sign)
+ {
+ *equal_sign = '\0';
+ equal_sign++;
+ trim_string(&equal_sign);
+ unquote_string(&equal_sign);
+ this_value = equal_sign;
+ }
+ else
+ {
+ this_value = "";
+ }
+ trim_string(&property);
+ len = strlen(property);
+
+ if (len > 0)
+ {
+ apr_hash_t *pattern_hash = svn_hash_gets(autoprops_baton->autoprops,
+ name);
+ svn_string_t *propval;
+
+ /* Force reserved boolean property values to '*'. */
+ if (svn_prop_is_boolean(property))
+ {
+ /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
+ propval = svn_string_create("*", autoprops_baton->result_pool);
+ }
+ else
+ {
+ propval = svn_string_create(this_value,
+ autoprops_baton->result_pool);
+ }
+
+ if (!pattern_hash)
+ {
+ pattern_hash = apr_hash_make(autoprops_baton->result_pool);
+ svn_hash_sets(autoprops_baton->autoprops,
+ apr_pstrdup(autoprops_baton->result_pool, name),
+ pattern_hash);
+ }
+ svn_hash_sets(pattern_hash,
+ apr_pstrdup(autoprops_baton->result_pool, property),
+ propval->data);
+ }
+ }
+ return TRUE;
+}
+
+/* Go up the directory tree from LOCAL_ABSPATH, looking for a versioned
+ * directory. If found, return its path in *EXISTING_PARENT_ABSPATH.
+ * Otherwise, return SVN_ERR_CLIENT_NO_VERSIONED_PARENT. */
+static svn_error_t *
+find_existing_parent(const char **existing_parent_abspath,
+ svn_client_ctx_t *ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ const char *parent_abspath;
+ svn_wc_context_t *wc_ctx = ctx->wc_ctx;
+
+ SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, local_abspath,
+ FALSE, FALSE, scratch_pool));
+
+ if (kind == svn_node_dir)
+ {
+ *existing_parent_abspath = apr_pstrdup(result_pool, local_abspath);
+ return SVN_NO_ERROR;
+ }
+
+ if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+ return svn_error_create(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL, NULL);
+
+ if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, scratch_pool),
+ scratch_pool))
+ return svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, NULL,
+ _("'%s' ends in a reserved name"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ SVN_ERR(find_existing_parent(existing_parent_abspath, ctx, parent_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__get_all_auto_props(apr_hash_t **autoprops,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_array_header_t *inherited_config_auto_props;
+ apr_hash_t *props;
+ svn_opt_revision_t rev;
+ svn_string_t *config_auto_prop;
+ svn_boolean_t use_autoprops;
+ collect_auto_props_baton_t autoprops_baton;
+ svn_error_t *err = NULL;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_boolean_t target_is_url = svn_path_is_url(path_or_url);
+ svn_config_t *cfg = ctx->config ? svn_hash_gets(ctx->config,
+ SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+ *autoprops = apr_hash_make(result_pool);
+ autoprops_baton.result_pool = result_pool;
+ autoprops_baton.autoprops = *autoprops;
+
+
+ /* Are "traditional" auto-props enabled? If so grab them from the
+ config. This is our starting set auto-props, which may be overriden
+ by svn:auto-props. */
+ SVN_ERR(svn_config_get_bool(cfg, &use_autoprops,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS, FALSE));
+ if (use_autoprops)
+ svn_config_enumerate2(cfg, SVN_CONFIG_SECTION_AUTO_PROPS,
+ all_auto_props_collector, &autoprops_baton,
+ scratch_pool);
+
+ /* Convert the config file setting (if any) into a hash mapping file
+ patterns to as hash of prop-->val mappings. */
+ if (svn_path_is_url(path_or_url))
+ rev.kind = svn_opt_revision_head;
+ else
+ rev.kind = svn_opt_revision_working;
+
+ /* If PATH_OR_URL is a WC path, then it might be unversioned, in which case
+ we find it's nearest versioned parent. */
+ do
+ {
+ err = svn_client_propget5(&props, &inherited_config_auto_props,
+ SVN_PROP_INHERITABLE_AUTO_PROPS, path_or_url,
+ &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
+ scratch_pool, iterpool);
+ if (err)
+ {
+ if (target_is_url || err->apr_err != SVN_ERR_UNVERSIONED_RESOURCE)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ err = NULL;
+ SVN_ERR(find_existing_parent(&path_or_url, ctx, path_or_url,
+ scratch_pool, iterpool));
+ }
+ else
+ {
+ break;
+ }
+ }
+ while (err == NULL);
+
+ /* Stash any explicit PROPS for PARENT_PATH into the inherited props array,
+ since these are actually inherited props for LOCAL_ABSPATH. */
+ config_auto_prop = svn_hash_gets(props, path_or_url);
+
+ if (config_auto_prop)
+ {
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(scratch_pool, sizeof(*new_iprop));
+ new_iprop->path_or_url = path_or_url;
+ new_iprop->prop_hash = apr_hash_make(scratch_pool);
+ svn_hash_sets(new_iprop->prop_hash, SVN_PROP_INHERITABLE_AUTO_PROPS,
+ config_auto_prop);
+ APR_ARRAY_PUSH(inherited_config_auto_props,
+ svn_prop_inherited_item_t *) = new_iprop;
+ }
+
+ for (i = 0; i < inherited_config_auto_props->nelts; i++)
+ {
+ apr_hash_index_t *hi;
+ svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
+ inherited_config_auto_props, i, svn_prop_inherited_item_t *);
+
+ for (hi = apr_hash_first(scratch_pool, elt->prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const svn_string_t *propval = svn__apr_hash_index_val(hi);
+ const char *ch = propval->data;
+ svn_stringbuf_t *config_auto_prop_pattern;
+ svn_stringbuf_t *config_auto_prop_val;
+
+ svn_pool_clear(iterpool);
+
+ config_auto_prop_pattern = svn_stringbuf_create_empty(iterpool);
+ config_auto_prop_val = svn_stringbuf_create_empty(iterpool);
+
+ /* Parse svn:auto-props value. */
+ while (*ch != '\0')
+ {
+ svn_stringbuf_setempty(config_auto_prop_pattern);
+ svn_stringbuf_setempty(config_auto_prop_val);
+
+ /* Parse the file pattern. */
+ while (*ch != '\0' && *ch != '=' && *ch != '\n')
+ {
+ svn_stringbuf_appendbyte(config_auto_prop_pattern, *ch);
+ ch++;
+ }
+
+ svn_stringbuf_strip_whitespace(config_auto_prop_pattern);
+
+ /* Parse the auto-prop group. */
+ while (*ch != '\0' && *ch != '\n')
+ {
+ svn_stringbuf_appendbyte(config_auto_prop_val, *ch);
+ ch++;
+ }
+
+ /* Strip leading '=' and whitespace from auto-prop group. */
+ if (config_auto_prop_val->data[0] == '=')
+ svn_stringbuf_remove(config_auto_prop_val, 0, 1);
+ svn_stringbuf_strip_whitespace(config_auto_prop_val);
+
+ all_auto_props_collector(config_auto_prop_pattern->data,
+ config_auto_prop_val->data,
+ &autoprops_baton,
+ scratch_pool);
+
+ /* Skip to next line if any. */
+ while (*ch != '\0' && *ch != '\n')
+ ch++;
+ if (*ch == '\n')
+ ch++;
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_client__get_inherited_ignores(apr_array_header_t **ignores,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_opt_revision_t rev;
+ apr_hash_t *explicit_ignores;
+ apr_array_header_t *inherited_ignores;
+ svn_boolean_t target_is_url = svn_path_is_url(path_or_url);
+ svn_string_t *explicit_prop;
+ int i;
+
+ if (target_is_url)
+ rev.kind = svn_opt_revision_head;
+ else
+ rev.kind = svn_opt_revision_working;
+
+ SVN_ERR(svn_client_propget5(&explicit_ignores, &inherited_ignores,
+ SVN_PROP_INHERITABLE_IGNORES, path_or_url,
+ &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
+ scratch_pool, scratch_pool));
+
+ explicit_prop = svn_hash_gets(explicit_ignores, path_or_url);
+
+ if (explicit_prop)
+ {
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(scratch_pool, sizeof(*new_iprop));
+ new_iprop->path_or_url = path_or_url;
+ new_iprop->prop_hash = apr_hash_make(scratch_pool);
+ svn_hash_sets(new_iprop->prop_hash, SVN_PROP_INHERITABLE_IGNORES,
+ explicit_prop);
+ APR_ARRAY_PUSH(inherited_ignores,
+ svn_prop_inherited_item_t *) = new_iprop;
+ }
+
+ *ignores = apr_array_make(result_pool, 16, sizeof(const char *));
+
+ for (i = 0; i < inherited_ignores->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
+ inherited_ignores, i, svn_prop_inherited_item_t *);
+ svn_string_t *ignore_val = svn_hash_gets(elt->prop_hash,
+ SVN_PROP_INHERITABLE_IGNORES);
+ if (ignore_val)
+ svn_cstring_split_append(*ignores, ignore_val->data, "\n\r\t\v ",
+ FALSE, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* The main logic of the public svn_client_add5.
+ *
+ * EXISTING_PARENT_ABSPATH is the absolute path to the first existing
+ * parent directory of local_abspath. If not NULL, all missing parents
+ * of LOCAL_ABSPATH must be created before LOCAL_ABSPATH can be added. */
+static svn_error_t *
+add(const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ const char *existing_parent_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ svn_error_t *err;
+ svn_magic__cookie_t *magic_cookie;
+ apr_array_header_t *ignores = NULL;
+
+ svn_magic__init(&magic_cookie, scratch_pool);
+
+ if (existing_parent_abspath)
+ {
+ const char *parent_abspath;
+ const char *child_relpath;
+ apr_array_header_t *components;
+ int i;
+ apr_pool_t *iterpool;
+
+ parent_abspath = existing_parent_abspath;
+ child_relpath = svn_dirent_is_child(existing_parent_abspath,
+ local_abspath, NULL);
+ components = svn_path_decompose(child_relpath, scratch_pool);
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < components->nelts - 1; i++)
+ {
+ const char *component;
+ svn_node_kind_t disk_kind;
+
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ component = APR_ARRAY_IDX(components, i, const char *);
+ parent_abspath = svn_dirent_join(parent_abspath, component,
+ scratch_pool);
+ SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, iterpool));
+ if (disk_kind != svn_node_none && disk_kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL,
+ _("'%s' prevents creating parent of '%s'"),
+ parent_abspath, local_abspath);
+
+ SVN_ERR(svn_io_make_dir_recursively(parent_abspath, scratch_pool));
+ SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, parent_abspath,
+ NULL /*props*/,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
+ if (kind == svn_node_dir)
+ {
+ /* We use add_dir_recursive for all directory targets
+ and pass depth along no matter what it is, so that the
+ target's depth will be set correctly. */
+ err = add_dir_recursive(local_abspath, depth, force,
+ no_autoprops, magic_cookie, NULL,
+ !no_ignore, ignores, ctx,
+ scratch_pool, scratch_pool);
+ }
+ else if (kind == svn_node_file)
+ err = add_file(local_abspath, magic_cookie, NULL,
+ no_autoprops, ctx, scratch_pool);
+ else if (kind == svn_node_none)
+ {
+ svn_boolean_t tree_conflicted;
+
+ /* Provide a meaningful error message if the node does not exist
+ * on disk but is a tree conflict victim. */
+ err = svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
+ ctx->wc_ctx, local_abspath,
+ scratch_pool);
+ if (err)
+ svn_error_clear(err);
+ else if (tree_conflicted)
+ return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
+ _("'%s' is an existing item in conflict; "
+ "please mark the conflict as resolved "
+ "before adding a new item here"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' not found"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ else
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Unsupported node kind for path '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ /* Ignore SVN_ERR_ENTRY_EXISTS when FORCE is set. */
+ if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+}
+
+
+
+svn_error_t *
+svn_client_add5(const char *path,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t add_parents,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *parent_abspath;
+ const char *local_abspath;
+ const char *existing_parent_abspath;
+ svn_boolean_t is_wc_root;
+ svn_error_t *err;
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+
+ /* See if we're being asked to add a wc-root. That's typically not
+ okay, unless we're in "force" mode. svn_wc__is_wcroot()
+ will return TRUE even if LOCAL_ABSPATH is a *symlink* to a working
+ copy root, which is a scenario we want to treat differently. */
+ err = svn_wc__is_wcroot(&is_wc_root, ctx->wc_ctx, local_abspath,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ {
+ return svn_error_trace(err);
+ }
+
+ svn_error_clear(err);
+ err = NULL; /* SVN_NO_ERROR */
+ is_wc_root = FALSE;
+ }
+ if (is_wc_root)
+ {
+#ifdef HAVE_SYMLINK
+ svn_node_kind_t disk_kind;
+ svn_boolean_t is_special;
+
+ SVN_ERR(svn_io_check_special_path(local_abspath, &disk_kind, &is_special,
+ scratch_pool));
+
+ /* A symlink can be an unversioned target and a wcroot. Lets try to add
+ the symlink, which can't be a wcroot. */
+ if (is_special)
+ is_wc_root = FALSE;
+ else
+#endif
+ {
+ if (! force)
+ return svn_error_createf(
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("'%s' is already under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ }
+
+ if (is_wc_root)
+ parent_abspath = local_abspath; /* We will only add children */
+ else
+ parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ existing_parent_abspath = NULL;
+ if (add_parents && !is_wc_root)
+ {
+ apr_pool_t *subpool;
+ const char *existing_parent_abspath2;
+
+ subpool = svn_pool_create(scratch_pool);
+ SVN_ERR(find_existing_parent(&existing_parent_abspath2, ctx,
+ parent_abspath, scratch_pool, subpool));
+ if (strcmp(existing_parent_abspath2, parent_abspath) != 0)
+ existing_parent_abspath = existing_parent_abspath2;
+ svn_pool_destroy(subpool);
+ }
+
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ add(local_abspath, depth, force, no_ignore, no_autoprops,
+ existing_parent_abspath, ctx, scratch_pool),
+ ctx->wc_ctx, (existing_parent_abspath ? existing_parent_abspath
+ : parent_abspath),
+ FALSE /* lock_anchor */, scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+path_driver_cb_func(void **dir_baton,
+ void *parent_baton,
+ void *callback_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *editor = callback_baton;
+ SVN_ERR(svn_path_check_valid(path, pool));
+ return editor->add_directory(path, parent_baton, NULL,
+ SVN_INVALID_REVNUM, pool, dir_baton);
+}
+
+/* Append URL, and all it's non-existent parent directories, to TARGETS.
+ Use TEMPPOOL for temporary allocations and POOL for any additions to
+ TARGETS. */
+static svn_error_t *
+add_url_parents(svn_ra_session_t *ra_session,
+ const char *url,
+ apr_array_header_t *targets,
+ apr_pool_t *temppool,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ const char *parent_url = svn_uri_dirname(url, pool);
+
+ SVN_ERR(svn_ra_reparent(ra_session, parent_url, temppool));
+ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
+ temppool));
+
+ if (kind == svn_node_none)
+ SVN_ERR(add_url_parents(ra_session, parent_url, targets, temppool, pool));
+
+ APR_ARRAY_PUSH(targets, const char *) = url;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+mkdir_urls(const apr_array_header_t *urls,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session = NULL;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ const char *log_msg;
+ apr_array_header_t *targets;
+ apr_hash_t *targets_hash;
+ apr_hash_t *commit_revprops;
+ svn_error_t *err;
+ const char *common;
+ int i;
+
+ /* Find any non-existent parent directories */
+ if (make_parents)
+ {
+ apr_array_header_t *all_urls = apr_array_make(pool, urls->nelts,
+ sizeof(const char *));
+ const char *first_url = APR_ARRAY_IDX(urls, 0, const char *);
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, first_url, NULL,
+ ctx, pool, iterpool));
+
+ for (i = 0; i < urls->nelts; i++)
+ {
+ const char *url = APR_ARRAY_IDX(urls, i, const char *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(add_url_parents(ra_session, url, all_urls, iterpool, pool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ urls = all_urls;
+ }
+
+ /* Condense our list of mkdir targets. */
+ SVN_ERR(svn_uri_condense_targets(&common, &targets, urls, FALSE,
+ pool, pool));
+
+ /*Remove duplicate targets introduced by make_parents with more targets. */
+ SVN_ERR(svn_hash_from_cstring_keys(&targets_hash, targets, pool));
+ SVN_ERR(svn_hash_keys(&targets, targets_hash, pool));
+
+ if (! targets->nelts)
+ {
+ const char *bname;
+ svn_uri_split(&common, &bname, common, pool);
+ APR_ARRAY_PUSH(targets, const char *) = bname;
+
+ if (*bname == '\0')
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("There is no valid URI above '%s'"),
+ common);
+ }
+ else
+ {
+ svn_boolean_t resplit = FALSE;
+
+ /* We can't "mkdir" the root of an editor drive, so if one of
+ our targets is the empty string, we need to back everything
+ up by a path component. */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(targets, i, const char *);
+ if (! *path)
+ {
+ resplit = TRUE;
+ break;
+ }
+ }
+ if (resplit)
+ {
+ const char *bname;
+
+ svn_uri_split(&common, &bname, common, pool);
+
+ if (*bname == '\0')
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("There is no valid URI above '%s'"),
+ common);
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(targets, i, const char *);
+ path = svn_relpath_join(bname, path, pool);
+ APR_ARRAY_IDX(targets, i, const char *) = path;
+ }
+ }
+ }
+ qsort(targets->elts, targets->nelts, targets->elt_size,
+ svn_sort_compare_paths);
+
+ /* ### This reparent may be problematic in limited-authz-to-common-parent
+ ### scenarios (compare issue #3242). See also issue #3649. */
+ if (ra_session)
+ SVN_ERR(svn_ra_reparent(ra_session, common, pool));
+
+ /* Create new commit items and add them to the array. */
+ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
+ {
+ svn_client_commit_item3_t *item;
+ const char *tmp_file;
+ apr_array_header_t *commit_items
+ = apr_array_make(pool, targets->nelts, sizeof(item));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(targets, i, const char *);
+
+ item = svn_client_commit_item3_create(pool);
+ item->url = svn_path_url_add_component2(common, path, pool);
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+ }
+
+ SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
+ ctx, pool));
+
+ if (! log_msg)
+ return SVN_NO_ERROR;
+ }
+ else
+ log_msg = "";
+
+ SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
+ log_msg, ctx, pool));
+
+ /* Open an RA session for the URL. Note that we don't have a local
+ directory, nor a place to put temp files. */
+ if (!ra_session)
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, common, NULL, ctx,
+ pool, pool));
+ else
+ SVN_ERR(svn_ra_reparent(ra_session, common, pool));
+
+
+ /* Fetch RA commit editor */
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
+ svn_client__get_shim_callbacks(ctx->wc_ctx, NULL,
+ pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ commit_revprops,
+ commit_callback,
+ commit_baton,
+ NULL, TRUE, /* No lock tokens */
+ pool));
+
+ /* Call the path-based editor driver. */
+ err = svn_delta_path_driver2(editor, edit_baton, targets, TRUE,
+ path_driver_cb_func, (void *)editor, pool);
+
+ if (err)
+ {
+ /* At least try to abort the edit (and fs txn) before throwing err. */
+ return svn_error_compose_create(
+ err,
+ editor->abort_edit(edit_baton, pool));
+ }
+
+ /* Close the edit. */
+ return editor->close_edit(edit_baton, pool);
+}
+
+
+
+svn_error_t *
+svn_client__make_local_parents(const char *path,
+ svn_boolean_t make_parents,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_node_kind_t orig_kind;
+ SVN_ERR(svn_io_check_path(path, &orig_kind, pool));
+ if (make_parents)
+ SVN_ERR(svn_io_make_dir_recursively(path, pool));
+ else
+ SVN_ERR(svn_io_dir_make(path, APR_OS_DEFAULT, pool));
+
+ /* Should no longer use svn_depth_empty to indicate that only the directory
+ itself is added, since it not only constraints the operation depth, but
+ also defines the depth of the target directory now. Moreover, the new
+ directory will have no children at all.*/
+ err = svn_client_add5(path, svn_depth_infinity, FALSE, FALSE, FALSE,
+ make_parents, ctx, pool);
+
+ /* If we created a new directory, but couldn't add it to version
+ control, then delete it. */
+ if (err && (orig_kind == svn_node_none))
+ {
+ /* ### If this returns an error, should we link it onto
+ err instead, so that the user is warned that we just
+ created an unversioned directory? */
+
+ svn_error_clear(svn_io_remove_dir2(path, FALSE, NULL, NULL, pool));
+ }
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_client_mkdir4(const apr_array_header_t *paths,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ if (! paths->nelts)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_client__assert_homogeneous_target_type(paths));
+
+ if (svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *)))
+ {
+ SVN_ERR(mkdir_urls(paths, make_parents, revprop_table, commit_callback,
+ commit_baton, ctx, pool));
+ }
+ else
+ {
+ /* This is a regular "mkdir" + "svn add" */
+ apr_pool_t *subpool = svn_pool_create(pool);
+ int i;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+ svn_pool_clear(subpool);
+
+ /* See if the user wants us to stop. */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ SVN_ERR(svn_client__make_local_parents(path, make_parents, ctx,
+ subpool));
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/blame.c b/contrib/subversion/subversion/libsvn_client/blame.c
new file mode 100644
index 0000000..188fdd2
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/blame.c
@@ -0,0 +1,837 @@
+/*
+ * blame.c: return blame messages
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "client.h"
+
+#include "svn_client.h"
+#include "svn_subst.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_diff.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_sorts.h"
+
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+#include <assert.h>
+
+/* The metadata associated with a particular revision. */
+struct rev
+{
+ svn_revnum_t revision; /* the revision number */
+ apr_hash_t *rev_props; /* the revision properties */
+ /* Used for merge reporting. */
+ const char *path; /* the absolute repository path */
+};
+
+/* One chunk of blame */
+struct blame
+{
+ const struct rev *rev; /* the responsible revision */
+ apr_off_t start; /* the starting diff-token (line) */
+ struct blame *next; /* the next chunk */
+};
+
+/* A chain of blame chunks */
+struct blame_chain
+{
+ struct blame *blame; /* linked list of blame chunks */
+ struct blame *avail; /* linked list of free blame chunks */
+ struct apr_pool_t *pool; /* Allocate members from this pool. */
+};
+
+/* The baton use for the diff output routine. */
+struct diff_baton {
+ struct blame_chain *chain;
+ const struct rev *rev;
+};
+
+/* The baton used for a file revision. */
+struct file_rev_baton {
+ svn_revnum_t start_rev, end_rev;
+ const char *target;
+ svn_client_ctx_t *ctx;
+ const svn_diff_file_options_t *diff_options;
+ /* name of file containing the previous revision of the file */
+ const char *last_filename;
+ struct rev *rev; /* the rev for which blame is being assigned
+ during a diff */
+ struct blame_chain *chain; /* the original blame chain. */
+ const char *repos_root_url; /* To construct a url */
+ apr_pool_t *mainpool; /* lives during the whole sequence of calls */
+ apr_pool_t *lastpool; /* pool used during previous call */
+ apr_pool_t *currpool; /* pool used during this call */
+
+ /* These are used for tracking merged revisions. */
+ svn_boolean_t include_merged_revisions;
+ svn_boolean_t merged_revision;
+ struct blame_chain *merged_chain; /* the merged blame chain. */
+ /* name of file containing the previous merged revision of the file */
+ const char *last_original_filename;
+ /* pools for files which may need to persist for more than one rev. */
+ apr_pool_t *filepool;
+ apr_pool_t *prevfilepool;
+};
+
+/* The baton used by the txdelta window handler. */
+struct delta_baton {
+ /* Our underlying handler/baton that we wrap */
+ svn_txdelta_window_handler_t wrapped_handler;
+ void *wrapped_baton;
+ struct file_rev_baton *file_rev_baton;
+ const char *filename;
+};
+
+
+
+
+/* Return a blame chunk associated with REV for a change starting
+ at token START, and allocated in CHAIN->mainpool. */
+static struct blame *
+blame_create(struct blame_chain *chain,
+ const struct rev *rev,
+ apr_off_t start)
+{
+ struct blame *blame;
+ if (chain->avail)
+ {
+ blame = chain->avail;
+ chain->avail = blame->next;
+ }
+ else
+ blame = apr_palloc(chain->pool, sizeof(*blame));
+ blame->rev = rev;
+ blame->start = start;
+ blame->next = NULL;
+ return blame;
+}
+
+/* Destroy a blame chunk. */
+static void
+blame_destroy(struct blame_chain *chain,
+ struct blame *blame)
+{
+ blame->next = chain->avail;
+ chain->avail = blame;
+}
+
+/* Return the blame chunk that contains token OFF, starting the search at
+ BLAME. */
+static struct blame *
+blame_find(struct blame *blame, apr_off_t off)
+{
+ struct blame *prev = NULL;
+ while (blame)
+ {
+ if (blame->start > off) break;
+ prev = blame;
+ blame = blame->next;
+ }
+ return prev;
+}
+
+/* Shift the start-point of BLAME and all subsequence blame-chunks
+ by ADJUST tokens */
+static void
+blame_adjust(struct blame *blame, apr_off_t adjust)
+{
+ while (blame)
+ {
+ blame->start += adjust;
+ blame = blame->next;
+ }
+}
+
+/* Delete the blame associated with the region from token START to
+ START + LENGTH */
+static svn_error_t *
+blame_delete_range(struct blame_chain *chain,
+ apr_off_t start,
+ apr_off_t length)
+{
+ struct blame *first = blame_find(chain->blame, start);
+ struct blame *last = blame_find(chain->blame, start + length);
+ struct blame *tail = last->next;
+
+ if (first != last)
+ {
+ struct blame *walk = first->next;
+ while (walk != last)
+ {
+ struct blame *next = walk->next;
+ blame_destroy(chain, walk);
+ walk = next;
+ }
+ first->next = last;
+ last->start = start;
+ if (first->start == start)
+ {
+ *first = *last;
+ blame_destroy(chain, last);
+ last = first;
+ }
+ }
+
+ if (tail && tail->start == last->start + length)
+ {
+ *last = *tail;
+ blame_destroy(chain, tail);
+ tail = last->next;
+ }
+
+ blame_adjust(tail, -length);
+
+ return SVN_NO_ERROR;
+}
+
+/* Insert a chunk of blame associated with REV starting
+ at token START and continuing for LENGTH tokens */
+static svn_error_t *
+blame_insert_range(struct blame_chain *chain,
+ const struct rev *rev,
+ apr_off_t start,
+ apr_off_t length)
+{
+ struct blame *head = chain->blame;
+ struct blame *point = blame_find(head, start);
+ struct blame *insert;
+
+ if (point->start == start)
+ {
+ insert = blame_create(chain, point->rev, point->start + length);
+ point->rev = rev;
+ insert->next = point->next;
+ point->next = insert;
+ }
+ else
+ {
+ struct blame *middle;
+ middle = blame_create(chain, rev, start);
+ insert = blame_create(chain, point->rev, start + length);
+ middle->next = insert;
+ insert->next = point->next;
+ point->next = middle;
+ }
+ blame_adjust(insert->next, length);
+
+ return SVN_NO_ERROR;
+}
+
+/* Callback for diff between subsequent revisions */
+static svn_error_t *
+output_diff_modified(void *baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length)
+{
+ struct diff_baton *db = baton;
+
+ if (original_length)
+ SVN_ERR(blame_delete_range(db->chain, modified_start, original_length));
+
+ if (modified_length)
+ SVN_ERR(blame_insert_range(db->chain, db->rev, modified_start,
+ modified_length));
+
+ return SVN_NO_ERROR;
+}
+
+static const svn_diff_output_fns_t output_fns = {
+ NULL,
+ output_diff_modified
+};
+
+/* Add the blame for the diffs between LAST_FILE and CUR_FILE to CHAIN,
+ for revision REV. LAST_FILE may be NULL in which
+ case blame is added for every line of CUR_FILE. */
+static svn_error_t *
+add_file_blame(const char *last_file,
+ const char *cur_file,
+ struct blame_chain *chain,
+ struct rev *rev,
+ const svn_diff_file_options_t *diff_options,
+ apr_pool_t *pool)
+{
+ if (!last_file)
+ {
+ SVN_ERR_ASSERT(chain->blame == NULL);
+ chain->blame = blame_create(chain, rev, 0);
+ }
+ else
+ {
+ svn_diff_t *diff;
+ struct diff_baton diff_baton;
+
+ diff_baton.chain = chain;
+ diff_baton.rev = rev;
+
+ /* We have a previous file. Get the diff and adjust blame info. */
+ SVN_ERR(svn_diff_file_diff_2(&diff, last_file, cur_file,
+ diff_options, pool));
+ SVN_ERR(svn_diff_output(diff, &diff_baton, &output_fns));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* The delta window handler for the text delta between the previously seen
+ * revision and the revision currently being handled.
+ *
+ * Record the blame information for this revision in BATON->file_rev_baton.
+ *
+ * Implements svn_txdelta_window_handler_t.
+ */
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct delta_baton *dbaton = baton;
+ struct file_rev_baton *frb = dbaton->file_rev_baton;
+ struct blame_chain *chain;
+
+ /* Call the wrapped handler first. */
+ SVN_ERR(dbaton->wrapped_handler(window, dbaton->wrapped_baton));
+
+ /* We patiently wait for the NULL window marking the end. */
+ if (window)
+ return SVN_NO_ERROR;
+
+ /* If we are including merged revisions, we need to add each rev to the
+ merged chain. */
+ if (frb->include_merged_revisions)
+ chain = frb->merged_chain;
+ else
+ chain = frb->chain;
+
+ /* Process this file. */
+ SVN_ERR(add_file_blame(frb->last_filename,
+ dbaton->filename, chain, frb->rev,
+ frb->diff_options, frb->currpool));
+
+ /* If we are including merged revisions, and the current revision is not a
+ merged one, we need to add its blame info to the chain for the original
+ line of history. */
+ if (frb->include_merged_revisions && ! frb->merged_revision)
+ {
+ apr_pool_t *tmppool;
+
+ SVN_ERR(add_file_blame(frb->last_original_filename,
+ dbaton->filename, frb->chain, frb->rev,
+ frb->diff_options, frb->currpool));
+
+ /* This filename could be around for a while, potentially, so
+ use the longer lifetime pool, and switch it with the previous one*/
+ svn_pool_clear(frb->prevfilepool);
+ tmppool = frb->filepool;
+ frb->filepool = frb->prevfilepool;
+ frb->prevfilepool = tmppool;
+
+ frb->last_original_filename = apr_pstrdup(frb->filepool,
+ dbaton->filename);
+ }
+
+ /* Prepare for next revision. */
+
+ /* Remember the file name so we can diff it with the next revision. */
+ frb->last_filename = dbaton->filename;
+
+ /* Switch pools. */
+ {
+ apr_pool_t *tmp_pool = frb->lastpool;
+ frb->lastpool = frb->currpool;
+ frb->currpool = tmp_pool;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Calculate and record blame information for one revision of the file,
+ * by comparing the file content against the previously seen revision.
+ *
+ * This handler is called once for each interesting revision of the file.
+ *
+ * Record the blame information for this revision in (file_rev_baton) BATON.
+ *
+ * Implements svn_file_rev_handler_t.
+ */
+static svn_error_t *
+file_rev_handler(void *baton, const char *path, svn_revnum_t revnum,
+ apr_hash_t *rev_props,
+ svn_boolean_t merged_revision,
+ svn_txdelta_window_handler_t *content_delta_handler,
+ void **content_delta_baton,
+ apr_array_header_t *prop_diffs,
+ apr_pool_t *pool)
+{
+ struct file_rev_baton *frb = baton;
+ svn_stream_t *last_stream;
+ svn_stream_t *cur_stream;
+ struct delta_baton *delta_baton;
+ apr_pool_t *filepool;
+
+ /* Clear the current pool. */
+ svn_pool_clear(frb->currpool);
+
+ if (frb->ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify_url(
+ svn_path_url_add_component2(frb->repos_root_url,
+ path+1, pool),
+ svn_wc_notify_blame_revision, pool);
+ notify->path = path;
+ notify->kind = svn_node_none;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ notify->revision = revnum;
+ notify->rev_props = rev_props;
+ frb->ctx->notify_func2(frb->ctx->notify_baton2, notify, pool);
+ }
+
+ if (frb->ctx->cancel_func)
+ SVN_ERR(frb->ctx->cancel_func(frb->ctx->cancel_baton));
+
+ /* If there were no content changes, we couldn't care less about this
+ revision now. Note that we checked the mime type above, so things
+ work if the user just changes the mime type in a commit.
+ Also note that we don't switch the pools in this case. This is important,
+ since the tempfile will be removed by the pool and we need the tempfile
+ from the last revision with content changes. */
+ if (!content_delta_handler)
+ return SVN_NO_ERROR;
+
+ frb->merged_revision = merged_revision;
+
+ /* Create delta baton. */
+ delta_baton = apr_palloc(frb->currpool, sizeof(*delta_baton));
+
+ /* Prepare the text delta window handler. */
+ if (frb->last_filename)
+ SVN_ERR(svn_stream_open_readonly(&last_stream, frb->last_filename,
+ frb->currpool, pool));
+ else
+ last_stream = svn_stream_empty(frb->currpool);
+
+ if (frb->include_merged_revisions && !frb->merged_revision)
+ filepool = frb->filepool;
+ else
+ filepool = frb->currpool;
+
+ SVN_ERR(svn_stream_open_unique(&cur_stream, &delta_baton->filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ filepool, pool));
+
+ /* Get window handler for applying delta. */
+ svn_txdelta_apply(last_stream, cur_stream, NULL, NULL,
+ frb->currpool,
+ &delta_baton->wrapped_handler,
+ &delta_baton->wrapped_baton);
+
+ /* Wrap the window handler with our own. */
+ delta_baton->file_rev_baton = frb;
+ *content_delta_handler = window_handler;
+ *content_delta_baton = delta_baton;
+
+ /* Create the rev structure. */
+ frb->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev));
+
+ if (revnum < frb->start_rev)
+ {
+ /* We shouldn't get more than one revision before the starting
+ revision (unless of including merged revisions). */
+ SVN_ERR_ASSERT((frb->last_filename == NULL)
+ || frb->include_merged_revisions);
+
+ /* The file existed before start_rev; generate no blame info for
+ lines from this revision (or before). */
+ frb->rev->revision = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ SVN_ERR_ASSERT(revnum <= frb->end_rev);
+
+ /* Set values from revision props. */
+ frb->rev->revision = revnum;
+ frb->rev->rev_props = svn_prop_hash_dup(rev_props, frb->mainpool);
+ }
+
+ if (frb->include_merged_revisions)
+ frb->rev->path = apr_pstrdup(frb->mainpool, path);
+
+ return SVN_NO_ERROR;
+}
+
+/* Ensure that CHAIN_ORIG and CHAIN_MERGED have the same number of chunks,
+ and that for every chunk C, CHAIN_ORIG[C] and CHAIN_MERGED[C] have the
+ same starting value. Both CHAIN_ORIG and CHAIN_MERGED should not be
+ NULL. */
+static void
+normalize_blames(struct blame_chain *chain,
+ struct blame_chain *chain_merged,
+ apr_pool_t *pool)
+{
+ struct blame *walk, *walk_merged;
+
+ /* Walk over the CHAIN's blame chunks and CHAIN_MERGED's blame chunks,
+ creating new chunks as needed. */
+ for (walk = chain->blame, walk_merged = chain_merged->blame;
+ walk->next && walk_merged->next;
+ walk = walk->next, walk_merged = walk_merged->next)
+ {
+ /* The current chunks should always be starting at the same offset. */
+ assert(walk->start == walk_merged->start);
+
+ if (walk->next->start < walk_merged->next->start)
+ {
+ /* insert a new chunk in CHAIN_MERGED. */
+ struct blame *tmp = blame_create(chain_merged, walk_merged->rev,
+ walk->next->start);
+ tmp->next = walk_merged->next;
+ walk_merged->next = tmp;
+ }
+
+ if (walk->next->start > walk_merged->next->start)
+ {
+ /* insert a new chunk in CHAIN. */
+ struct blame *tmp = blame_create(chain, walk->rev,
+ walk_merged->next->start);
+ tmp->next = walk->next;
+ walk->next = tmp;
+ }
+ }
+
+ /* If both NEXT pointers are null, the lists are equally long, otherwise
+ we need to extend one of them. If CHAIN is longer, append new chunks
+ to CHAIN_MERGED until its length matches that of CHAIN. */
+ while (walk->next != NULL)
+ {
+ struct blame *tmp = blame_create(chain_merged, walk_merged->rev,
+ walk->next->start);
+ walk_merged->next = tmp;
+
+ walk_merged = walk_merged->next;
+ walk = walk->next;
+ }
+
+ /* Same as above, only extend CHAIN to match CHAIN_MERGED. */
+ while (walk_merged->next != NULL)
+ {
+ struct blame *tmp = blame_create(chain, walk->rev,
+ walk_merged->next->start);
+ walk->next = tmp;
+
+ walk = walk->next;
+ walk_merged = walk_merged->next;
+ }
+}
+
+svn_error_t *
+svn_client_blame5(const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ const svn_diff_file_options_t *diff_options,
+ svn_boolean_t ignore_mime_type,
+ svn_boolean_t include_merged_revisions,
+ svn_client_blame_receiver3_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct file_rev_baton frb;
+ svn_ra_session_t *ra_session;
+ svn_revnum_t start_revnum, end_revnum;
+ svn_client__pathrev_t *end_loc;
+ struct blame *walk, *walk_merged = NULL;
+ apr_pool_t *iterpool;
+ svn_stream_t *last_stream;
+ svn_stream_t *stream;
+ const char *target_abspath_or_url;
+
+ if (start->kind == svn_opt_revision_unspecified
+ || end->kind == svn_opt_revision_unspecified)
+ return svn_error_create
+ (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
+
+ if (svn_path_is_url(target))
+ target_abspath_or_url = target;
+ else
+ SVN_ERR(svn_dirent_get_absolute(&target_abspath_or_url, target, pool));
+
+ /* Get an RA plugin for this filesystem object. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &end_loc,
+ target, NULL, peg_revision, end,
+ ctx, pool));
+ end_revnum = end_loc->rev;
+
+ SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx,
+ target_abspath_or_url, ra_session,
+ start, pool));
+
+ if (end_revnum < start_revnum)
+ return svn_error_create
+ (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Start revision must precede end revision"));
+
+ /* We check the mime-type of the yougest revision before getting all
+ the older revisions. */
+ if (!ignore_mime_type)
+ {
+ apr_hash_t *props;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_client_propget5(&props, NULL, SVN_PROP_MIME_TYPE,
+ target_abspath_or_url, peg_revision,
+ end, NULL, svn_depth_empty, NULL, ctx,
+ pool, pool));
+
+ /* props could be keyed on URLs or paths depending on the
+ peg_revision and end values so avoid using the key. */
+ hi = apr_hash_first(pool, props);
+ if (hi)
+ {
+ svn_string_t *value;
+
+ /* Should only be one value */
+ SVN_ERR_ASSERT(apr_hash_count(props) == 1);
+
+ value = svn__apr_hash_index_val(hi);
+ if (value && svn_mime_type_is_binary(value->data))
+ return svn_error_createf
+ (SVN_ERR_CLIENT_IS_BINARY_FILE, 0,
+ _("Cannot calculate blame information for binary file '%s'"),
+ (svn_path_is_url(target)
+ ? target : svn_dirent_local_style(target, pool)));
+ }
+ }
+
+ frb.start_rev = start_revnum;
+ frb.end_rev = end_revnum;
+ frb.target = target;
+ frb.ctx = ctx;
+ frb.diff_options = diff_options;
+ frb.include_merged_revisions = include_merged_revisions;
+ frb.last_filename = NULL;
+ frb.last_original_filename = NULL;
+ frb.chain = apr_palloc(pool, sizeof(*frb.chain));
+ frb.chain->blame = NULL;
+ frb.chain->avail = NULL;
+ frb.chain->pool = pool;
+ if (include_merged_revisions)
+ {
+ frb.merged_chain = apr_palloc(pool, sizeof(*frb.merged_chain));
+ frb.merged_chain->blame = NULL;
+ frb.merged_chain->avail = NULL;
+ frb.merged_chain->pool = pool;
+ }
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &frb.repos_root_url, pool));
+
+ frb.mainpool = pool;
+ /* The callback will flip the following two pools, because it needs
+ information from the previous call. Obviously, it can't rely on
+ the lifetime of the pool provided by get_file_revs. */
+ frb.lastpool = svn_pool_create(pool);
+ frb.currpool = svn_pool_create(pool);
+ if (include_merged_revisions)
+ {
+ frb.filepool = svn_pool_create(pool);
+ frb.prevfilepool = svn_pool_create(pool);
+ }
+
+ /* Collect all blame information.
+ We need to ensure that we get one revision before the start_rev,
+ if available so that we can know what was actually changed in the start
+ revision. */
+ SVN_ERR(svn_ra_get_file_revs2(ra_session, "",
+ start_revnum - (start_revnum > 0 ? 1 : 0),
+ end_revnum, include_merged_revisions,
+ file_rev_handler, &frb, pool));
+
+ if (end->kind == svn_opt_revision_working)
+ {
+ /* If the local file is modified we have to call the handler on the
+ working copy file with keywords unexpanded */
+ svn_wc_status3_t *status;
+
+ SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, target_abspath_or_url, pool,
+ pool));
+
+ if (status->text_status != svn_wc_status_normal
+ || (status->prop_status != svn_wc_status_normal
+ && status->prop_status != svn_wc_status_none))
+ {
+ svn_stream_t *wcfile;
+ svn_stream_t *tempfile;
+ svn_opt_revision_t rev;
+ svn_boolean_t normalize_eols = FALSE;
+ const char *temppath;
+
+ if (status->prop_status != svn_wc_status_none)
+ {
+ const svn_string_t *eol_style;
+ SVN_ERR(svn_wc_prop_get2(&eol_style, ctx->wc_ctx,
+ target_abspath_or_url,
+ SVN_PROP_EOL_STYLE,
+ pool, pool));
+
+ if (eol_style)
+ {
+ svn_subst_eol_style_t style;
+ const char *eol;
+ svn_subst_eol_style_from_value(&style, &eol, eol_style->data);
+
+ normalize_eols = (style == svn_subst_eol_style_native);
+ }
+ }
+
+ rev.kind = svn_opt_revision_working;
+ SVN_ERR(svn_client__get_normalized_stream(&wcfile, ctx->wc_ctx,
+ target_abspath_or_url, &rev,
+ FALSE, normalize_eols,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ pool, pool));
+
+ SVN_ERR(svn_stream_open_unique(&tempfile, &temppath, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ pool, pool));
+
+ SVN_ERR(svn_stream_copy3(wcfile, tempfile, ctx->cancel_func,
+ ctx->cancel_baton, pool));
+
+ SVN_ERR(add_file_blame(frb.last_filename, temppath, frb.chain, NULL,
+ frb.diff_options, pool));
+
+ frb.last_filename = temppath;
+ }
+ }
+
+ /* Report the blame to the caller. */
+
+ /* The callback has to have been called at least once. */
+ SVN_ERR_ASSERT(frb.last_filename != NULL);
+
+ /* Create a pool for the iteration below. */
+ iterpool = svn_pool_create(pool);
+
+ /* Open the last file and get a stream. */
+ SVN_ERR(svn_stream_open_readonly(&last_stream, frb.last_filename,
+ pool, pool));
+ stream = svn_subst_stream_translated(last_stream,
+ "\n", TRUE, NULL, FALSE, pool);
+
+ /* Perform optional merged chain normalization. */
+ if (include_merged_revisions)
+ {
+ /* If we never created any blame for the original chain, create it now,
+ with the most recent changed revision. This could occur if a file
+ was created on a branch and them merged to another branch. This is
+ semanticly a copy, and we want to use the revision on the branch as
+ the most recently changed revision. ### Is this really what we want
+ to do here? Do the sematics of copy change? */
+ if (!frb.chain->blame)
+ frb.chain->blame = blame_create(frb.chain, frb.rev, 0);
+
+ normalize_blames(frb.chain, frb.merged_chain, pool);
+ walk_merged = frb.merged_chain->blame;
+ }
+
+ /* Process each blame item. */
+ for (walk = frb.chain->blame; walk; walk = walk->next)
+ {
+ apr_off_t line_no;
+ svn_revnum_t merged_rev;
+ const char *merged_path;
+ apr_hash_t *merged_rev_props;
+
+ if (walk_merged)
+ {
+ merged_rev = walk_merged->rev->revision;
+ merged_rev_props = walk_merged->rev->rev_props;
+ merged_path = walk_merged->rev->path;
+ }
+ else
+ {
+ merged_rev = SVN_INVALID_REVNUM;
+ merged_rev_props = NULL;
+ merged_path = NULL;
+ }
+
+ for (line_no = walk->start;
+ !walk->next || line_no < walk->next->start;
+ ++line_no)
+ {
+ svn_boolean_t eof;
+ svn_stringbuf_t *sb;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, iterpool));
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+ if (!eof || sb->len)
+ {
+ if (walk->rev)
+ SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum,
+ line_no, walk->rev->revision,
+ walk->rev->rev_props, merged_rev,
+ merged_rev_props, merged_path,
+ sb->data, FALSE, iterpool));
+ else
+ SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum,
+ line_no, SVN_INVALID_REVNUM,
+ NULL, SVN_INVALID_REVNUM,
+ NULL, NULL,
+ sb->data, TRUE, iterpool));
+ }
+ if (eof) break;
+ }
+
+ if (walk_merged)
+ walk_merged = walk_merged->next;
+ }
+
+ SVN_ERR(svn_stream_close(stream));
+
+ svn_pool_destroy(frb.lastpool);
+ svn_pool_destroy(frb.currpool);
+ if (include_merged_revisions)
+ {
+ svn_pool_destroy(frb.filepool);
+ svn_pool_destroy(frb.prevfilepool);
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/cat.c b/contrib/subversion/subversion/libsvn_client/cat.c
new file mode 100644
index 0000000..7c58f88
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/cat.c
@@ -0,0 +1,308 @@
+/*
+ * cat.c: implementation of the 'cat' command
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_hash.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_subst.h"
+#include "svn_io.h"
+#include "svn_time.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "client.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+/*** Code. ***/
+
+svn_error_t *
+svn_client__get_normalized_stream(svn_stream_t **normal_stream,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t expand_keywords,
+ svn_boolean_t normalize_eols,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *kw = NULL;
+ svn_subst_eol_style_t style;
+ apr_hash_t *props;
+ svn_string_t *eol_style, *keywords, *special;
+ const char *eol = NULL;
+ svn_boolean_t local_mod = FALSE;
+ svn_stream_t *input;
+ svn_node_kind_t kind;
+
+ SVN_ERR_ASSERT(SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind));
+
+ SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, local_abspath,
+ (revision->kind != svn_opt_revision_working),
+ FALSE, scratch_pool));
+
+ if (kind == svn_node_unknown || kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, NULL,
+ _("'%s' refers to a directory"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ if (revision->kind != svn_opt_revision_working)
+ {
+ SVN_ERR(svn_wc_get_pristine_contents2(&input, wc_ctx, local_abspath,
+ result_pool, scratch_pool));
+ if (input == NULL)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' has no pristine version until it is committed"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+
+ SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ svn_wc_status3_t *status;
+
+ SVN_ERR(svn_stream_open_readonly(&input, local_abspath, scratch_pool,
+ result_pool));
+
+ SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_wc_status3(&status, wc_ctx, local_abspath, scratch_pool,
+ scratch_pool));
+ if (status->node_status != svn_wc_status_normal)
+ local_mod = TRUE;
+ }
+
+ eol_style = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
+ keywords = svn_hash_gets(props, SVN_PROP_KEYWORDS);
+ special = svn_hash_gets(props, SVN_PROP_SPECIAL);
+
+ if (eol_style)
+ svn_subst_eol_style_from_value(&style, &eol, eol_style->data);
+
+ if (keywords)
+ {
+ svn_revnum_t changed_rev;
+ const char *rev_str;
+ const char *author;
+ const char *url;
+ apr_time_t tm;
+ const char *repos_root_url;
+ const char *repos_relpath;
+
+ SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, &tm, &author, wc_ctx,
+ local_abspath, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url,
+ NULL,
+ wc_ctx, local_abspath, scratch_pool,
+ scratch_pool));
+ url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ scratch_pool);
+
+ if (local_mod)
+ {
+ /* For locally modified files, we'll append an 'M'
+ to the revision number, and set the author to
+ "(local)" since we can't always determine the
+ current user's username */
+ rev_str = apr_psprintf(scratch_pool, "%ldM", changed_rev);
+ author = _("(local)");
+
+ if (! special)
+ {
+ /* Use the modified time from the working copy for files */
+ SVN_ERR(svn_io_file_affected_time(&tm, local_abspath,
+ scratch_pool));
+ }
+ }
+ else
+ {
+ rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
+ }
+
+ SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data, rev_str, url,
+ repos_root_url, tm, author,
+ scratch_pool));
+ }
+
+ /* Wrap the output stream if translation is needed. */
+ if (eol != NULL || kw != NULL)
+ input = svn_subst_stream_translated(
+ input,
+ (eol_style && normalize_eols) ? SVN_SUBST_NATIVE_EOL_STR : eol,
+ FALSE, kw, expand_keywords, result_pool);
+
+ *normal_stream = input;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_cat2(svn_stream_t *out,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session;
+ svn_client__pathrev_t *loc;
+ svn_string_t *eol_style;
+ svn_string_t *keywords;
+ apr_hash_t *props;
+ const char *repos_root_url;
+ svn_stream_t *output = out;
+ svn_error_t *err;
+
+ /* ### Inconsistent default revision logic in this command. */
+ if (peg_revision->kind == svn_opt_revision_unspecified)
+ {
+ peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
+ path_or_url);
+ revision = svn_cl__rev_default_to_head_or_base(revision, path_or_url);
+ }
+ else
+ {
+ peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
+ path_or_url);
+ revision = svn_cl__rev_default_to_peg(revision, peg_revision);
+ }
+
+ if (! svn_path_is_url(path_or_url)
+ && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
+ && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
+ {
+ const char *local_abspath;
+ svn_stream_t *normal_stream;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url, pool));
+ SVN_ERR(svn_client__get_normalized_stream(&normal_stream, ctx->wc_ctx,
+ local_abspath, revision, TRUE, FALSE,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool, pool));
+
+ /* We don't promise to close output, so disown it to ensure we don't. */
+ output = svn_stream_disown(output, pool);
+
+ return svn_error_trace(svn_stream_copy3(normal_stream, output,
+ ctx->cancel_func,
+ ctx->cancel_baton, pool));
+ }
+
+ /* Get an RA plugin for this filesystem object. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
+ path_or_url, NULL,
+ peg_revision,
+ revision, ctx, pool));
+
+ /* Find the repos root URL */
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
+
+ /* Grab some properties we need to know in order to figure out if anything
+ special needs to be done with this file. */
+ err = svn_ra_get_file(ra_session, "", loc->rev, NULL, NULL, &props, pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FILE)
+ {
+ return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, err,
+ _("URL '%s' refers to a directory"),
+ loc->url);
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ eol_style = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
+ keywords = svn_hash_gets(props, SVN_PROP_KEYWORDS);
+
+ if (eol_style || keywords)
+ {
+ /* It's a file with no special eol style or keywords. */
+ svn_subst_eol_style_t eol;
+ const char *eol_str;
+ apr_hash_t *kw;
+
+ if (eol_style)
+ svn_subst_eol_style_from_value(&eol, &eol_str, eol_style->data);
+ else
+ {
+ eol = svn_subst_eol_style_none;
+ eol_str = NULL;
+ }
+
+
+ if (keywords)
+ {
+ svn_string_t *cmt_rev, *cmt_date, *cmt_author;
+ apr_time_t when = 0;
+
+ cmt_rev = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_REV);
+ cmt_date = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_DATE);
+ cmt_author = svn_hash_gets(props, SVN_PROP_ENTRY_LAST_AUTHOR);
+ if (cmt_date)
+ SVN_ERR(svn_time_from_cstring(&when, cmt_date->data, pool));
+
+ SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data,
+ cmt_rev->data, loc->url,
+ repos_root_url, when,
+ cmt_author ?
+ cmt_author->data : NULL,
+ pool));
+ }
+ else
+ kw = NULL;
+
+ /* Interject a translating stream */
+ output = svn_subst_stream_translated(svn_stream_disown(out, pool),
+ eol_str, FALSE, kw, TRUE, pool);
+ }
+
+ SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, output, NULL, NULL, pool));
+
+ if (out != output)
+ /* Close the interjected stream */
+ SVN_ERR(svn_stream_close(output));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/changelist.c b/contrib/subversion/subversion/libsvn_client/changelist.c
new file mode 100644
index 0000000..fc4d987
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/changelist.c
@@ -0,0 +1,144 @@
+/*
+ * changelist.c: implementation of the 'changelist' command
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_wc.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+
+#include "client.h"
+#include "private/svn_wc_private.h"
+#include "svn_private_config.h"
+
+
+
+
+svn_error_t *
+svn_client_add_to_changelist(const apr_array_header_t *paths,
+ const char *changelist,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ if (changelist[0] == '\0')
+ return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
+ _("Target changelist name must not be empty"));
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+ }
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *local_abspath;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, iterpool));
+
+ SVN_ERR(svn_wc_set_changelist2(ctx->wc_ctx, local_abspath, changelist,
+ depth, changelists,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client_remove_from_changelists(const apr_array_header_t *paths,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+ }
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *local_abspath;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, iterpool));
+
+ SVN_ERR(svn_wc_set_changelist2(ctx->wc_ctx, local_abspath, NULL,
+ depth, changelists,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client_get_changelists(const char *path,
+ const apr_array_header_t *changelists,
+ svn_depth_t depth,
+ svn_changelist_receiver_t callback_func,
+ void *callback_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ SVN_ERR(svn_wc_get_changelists(ctx->wc_ctx, local_abspath, depth, changelists,
+ callback_func, callback_baton,
+ ctx->cancel_func, ctx->cancel_baton, pool));
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/checkout.c b/contrib/subversion/subversion/libsvn_client/checkout.c
new file mode 100644
index 0000000..41be776
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/checkout.c
@@ -0,0 +1,198 @@
+/*
+ * checkout.c: wrappers around wc checkout functionality
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_pools.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_ra.h"
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_io.h"
+#include "svn_opt.h"
+#include "svn_time.h"
+#include "client.h"
+
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Public Interfaces. ***/
+
+static svn_error_t *
+initialize_area(const char *local_abspath,
+ const svn_client__pathrev_t *pathrev,
+ svn_depth_t depth,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_infinity;
+
+ /* Make the unversioned directory into a versioned one. */
+ SVN_ERR(svn_wc_ensure_adm4(ctx->wc_ctx, local_abspath, pathrev->url,
+ pathrev->repos_root_url, pathrev->repos_uuid,
+ pathrev->rev, depth, pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__checkout_internal(svn_revnum_t *result_rev,
+ const char *url,
+ const char *local_abspath,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t *timestamp_sleep,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ apr_pool_t *session_pool = svn_pool_create(pool);
+ svn_ra_session_t *ra_session;
+ svn_client__pathrev_t *pathrev;
+
+ /* Sanity check. Without these, the checkout is meaningless. */
+ SVN_ERR_ASSERT(local_abspath != NULL);
+ SVN_ERR_ASSERT(svn_uri_is_canonical(url, pool));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Fulfill the docstring promise of svn_client_checkout: */
+ if ((revision->kind != svn_opt_revision_number)
+ && (revision->kind != svn_opt_revision_date)
+ && (revision->kind != svn_opt_revision_head))
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
+
+ /* Get the RA connection. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &pathrev,
+ url, NULL, peg_revision, revision,
+ ctx, session_pool));
+
+ pathrev = svn_client__pathrev_dup(pathrev, pool);
+ SVN_ERR(svn_ra_check_path(ra_session, "", pathrev->rev, &kind, pool));
+
+ svn_pool_destroy(session_pool);
+
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' doesn't exist"), pathrev->url);
+ else if (kind == svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE , NULL,
+ _("URL '%s' refers to a file, not a directory"), pathrev->url);
+
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, pool));
+
+ if (kind == svn_node_none)
+ {
+ /* Bootstrap: create an incomplete working-copy root dir. Its
+ entries file should only have an entry for THIS_DIR with a
+ URL, revnum, and an 'incomplete' flag. */
+ SVN_ERR(svn_io_make_dir_recursively(local_abspath, pool));
+ SVN_ERR(initialize_area(local_abspath, pathrev, depth, ctx, pool));
+ }
+ else if (kind == svn_node_dir)
+ {
+ int wc_format;
+ const char *entry_url;
+
+ SVN_ERR(svn_wc_check_wc2(&wc_format, ctx->wc_ctx, local_abspath, pool));
+ if (! wc_format)
+ {
+ SVN_ERR(initialize_area(local_abspath, pathrev, depth, ctx, pool));
+ }
+ else
+ {
+ /* Get PATH's URL. */
+ SVN_ERR(svn_wc__node_get_url(&entry_url, ctx->wc_ctx, local_abspath,
+ pool, pool));
+
+ /* If PATH's existing URL matches the incoming one, then
+ just update. This allows 'svn co' to restart an
+ interrupted checkout. Otherwise bail out. */
+ if (strcmp(entry_url, pathrev->url) != 0)
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("'%s' is already a working copy for a"
+ " different URL"),
+ svn_dirent_local_style(local_abspath, pool));
+ }
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_WC_NODE_KIND_CHANGE, NULL,
+ _("'%s' already exists and is not a directory"),
+ svn_dirent_local_style(local_abspath, pool));
+ }
+
+ /* Have update fix the incompleteness. */
+ SVN_ERR(svn_client__update_internal(result_rev, local_abspath,
+ revision, depth, TRUE,
+ ignore_externals,
+ allow_unver_obstructions,
+ TRUE /* adds_as_modification */,
+ FALSE, FALSE,
+ timestamp_sleep, ctx, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_checkout3(svn_revnum_t *result_rev,
+ const char *URL,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_error_t *err;
+ svn_boolean_t sleep_here = FALSE;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ err = svn_client__checkout_internal(result_rev, URL, local_abspath,
+ peg_revision, revision, depth,
+ ignore_externals,
+ allow_unver_obstructions, &sleep_here,
+ ctx, pool);
+ if (sleep_here)
+ svn_io_sleep_for_timestamps(local_abspath, pool);
+
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/cleanup.c b/contrib/subversion/subversion/libsvn_client/cleanup.c
new file mode 100644
index 0000000..b15e824
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/cleanup.c
@@ -0,0 +1,63 @@
+/*
+ * cleanup.c: wrapper around wc cleanup functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_time.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_config.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "client.h"
+#include "svn_props.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+svn_error_t *
+svn_client_cleanup(const char *path,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ svn_error_t *err;
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+
+ err = svn_wc_cleanup3(ctx->wc_ctx, local_abspath, ctx->cancel_func,
+ ctx->cancel_baton, scratch_pool);
+ svn_io_sleep_for_timestamps(path, scratch_pool);
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/client.h b/contrib/subversion/subversion/libsvn_client/client.h
new file mode 100644
index 0000000..9ea25f2
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/client.h
@@ -0,0 +1,1124 @@
+/*
+ * client.h : shared stuff internal to the client library.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_CLIENT_H
+#define SVN_LIBSVN_CLIENT_H
+
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_opt.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_ra.h"
+#include "svn_client.h"
+
+#include "private/svn_magic.h"
+#include "private/svn_client_private.h"
+#include "private/svn_diff_tree.h"
+#include "private/svn_editor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Set *REVNUM to the revision number identified by REVISION.
+
+ If REVISION->kind is svn_opt_revision_number, just use
+ REVISION->value.number, ignoring LOCAL_ABSPATH and RA_SESSION.
+
+ Else if REVISION->kind is svn_opt_revision_committed,
+ svn_opt_revision_previous, or svn_opt_revision_base, or
+ svn_opt_revision_working, then the revision can be identified
+ purely based on the working copy's administrative information for
+ LOCAL_ABSPATH, so RA_SESSION is ignored. If LOCAL_ABSPATH is not
+ under revision control, return SVN_ERR_UNVERSIONED_RESOURCE, or if
+ LOCAL_ABSPATH is null, return SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED.
+
+ Else if REVISION->kind is svn_opt_revision_date or
+ svn_opt_revision_head, then RA_SESSION is used to retrieve the
+ revision from the repository (using REVISION->value.date in the
+ former case), and LOCAL_ABSPATH is ignored. If RA_SESSION is null,
+ return SVN_ERR_CLIENT_RA_ACCESS_REQUIRED.
+
+ Else if REVISION->kind is svn_opt_revision_unspecified, set
+ *REVNUM to SVN_INVALID_REVNUM.
+
+ If YOUNGEST_REV is non-NULL, it is an in/out parameter. If
+ *YOUNGEST_REV is valid, use it as the youngest revision in the
+ repository (regardless of reality) -- don't bother to lookup the
+ true value for HEAD, and don't return any value in *REVNUM greater
+ than *YOUNGEST_REV. If *YOUNGEST_REV is not valid, and a HEAD
+ lookup is required to populate *REVNUM, then also populate
+ *YOUNGEST_REV with the result. This is useful for making multiple
+ serialized calls to this function with a basically static view of
+ the repository, avoiding race conditions which could occur between
+ multiple invocations with HEAD lookup requests.
+
+ Else return SVN_ERR_CLIENT_BAD_REVISION.
+
+ Use SCRATCH_POOL for any temporary allocation. */
+svn_error_t *
+svn_client__get_revision_number(svn_revnum_t *revnum,
+ svn_revnum_t *youngest_rev,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_ra_session_t *ra_session,
+ const svn_opt_revision_t *revision,
+ apr_pool_t *scratch_pool);
+
+/* Set *ORIGINAL_REPOS_RELPATH and *ORIGINAL_REVISION to the original location
+ that served as the source of the copy from which PATH_OR_URL at REVISION was
+ created, or NULL and SVN_INVALID_REVNUM (respectively) if PATH_OR_URL at
+ REVISION was not the result of a copy operation. */
+svn_error_t *
+svn_client__get_copy_source(const char **original_repos_relpath,
+ svn_revnum_t *original_revision,
+ const char *path_or_url,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *START_URL and *START_REVISION (and maybe *END_URL
+ and *END_REVISION) to the revisions and repository URLs of one
+ (or two) points of interest along a particular versioned resource's
+ line of history. PATH as it exists in "peg revision"
+ REVISION identifies that line of history, and START and END
+ specify the point(s) of interest (typically the revisions referred
+ to as the "operative range" for a given operation) along that history.
+
+ START_REVISION and/or END_REVISION may be NULL if not wanted.
+ END may be NULL or of kind svn_opt_revision_unspecified (in either case
+ END_URL and END_REVISION are not touched by the function);
+ START and REVISION may not.
+
+ If PATH is a WC path and REVISION is of kind svn_opt_revision_working,
+ then look at the PATH's copy-from URL instead of its base URL.
+
+ RA_SESSION should be an open RA session pointing at the URL of PATH,
+ or NULL, in which case this function will open its own temporary session.
+
+ A NOTE ABOUT FUTURE REPORTING:
+
+ If either START or END are greater than REVISION, then do a
+ sanity check (since we cannot search future history yet): verify
+ that PATH in the future revision(s) is the "same object" as the
+ one pegged by REVISION. In other words, all three objects must
+ be connected by a single line of history which exactly passes
+ through PATH at REVISION. If this sanity check fails, return
+ SVN_ERR_CLIENT_UNRELATED_RESOURCES. If PATH doesn't exist in the future
+ revision, SVN_ERR_FS_NOT_FOUND may also be returned.
+
+ CTX is the client context baton.
+
+ Use POOL for all allocations. */
+svn_error_t *
+svn_client__repos_locations(const char **start_url,
+ svn_revnum_t *start_revision,
+ const char **end_url,
+ svn_revnum_t *end_revision,
+ svn_ra_session_t *ra_session,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Trace a line of history of a particular versioned resource back to a
+ * specific revision.
+ *
+ * Set *OP_LOC_P to the location that the object PEG_LOC had in
+ * revision OP_REVNUM.
+ *
+ * RA_SESSION is an open RA session to the correct repository; it may be
+ * temporarily reparented inside this function. */
+svn_error_t *
+svn_client__repos_location(svn_client__pathrev_t **op_loc_p,
+ svn_ra_session_t *ra_session,
+ const svn_client__pathrev_t *peg_loc,
+ svn_revnum_t op_revnum,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *SEGMENTS to an array of svn_location_segment_t * objects, each
+ representing a reposition location segment for the history of URL
+ in PEG_REVISION
+ between END_REVISION and START_REVISION, ordered from oldest
+ segment to youngest. *SEGMENTS may be empty but it will never
+ be NULL.
+
+ This is basically a thin de-stream-ifying wrapper around the
+ svn_ra_get_location_segments() interface, which see for the rules
+ governing PEG_REVISION, START_REVISION, and END_REVISION.
+
+ RA_SESSION is an RA session open to the repository of URL; it may be
+ temporarily reparented within this function.
+
+ CTX is the client context baton.
+
+ Use POOL for all allocations. */
+svn_error_t *
+svn_client__repos_location_segments(apr_array_header_t **segments,
+ svn_ra_session_t *ra_session,
+ const char *url,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/* Find the common ancestor of two locations in a repository.
+ Ancestry is determined by the 'copy-from' relationship and the normal
+ successor relationship.
+
+ Set *ANCESTOR_P to the location of the youngest common ancestor of
+ LOC1 and LOC2. If the locations have no common ancestor (including if
+ they don't have the same repository root URL), set *ANCESTOR_P to NULL.
+
+ If SESSION is not NULL, use it for retrieving the common ancestor instead
+ of creating a new session.
+
+ Use the authentication baton cached in CTX to authenticate against
+ the repository. Use POOL for all allocations.
+
+ See also svn_client__youngest_common_ancestor().
+*/
+svn_error_t *
+svn_client__get_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p,
+ const svn_client__pathrev_t *loc1,
+ const svn_client__pathrev_t *loc2,
+ svn_ra_session_t *session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Ensure that RA_SESSION's session URL matches SESSION_URL,
+ reparenting that session if necessary.
+ Store the previous session URL in *OLD_SESSION_URL (so that if the
+ reparenting is meant to be temporary, the caller can reparent the
+ session back to where it was).
+
+ If SESSION_URL is NULL, treat this as a magic value meaning "point
+ the RA session to the root of the repository".
+
+ NOTE: The typical usage pattern for this functions is:
+
+ const char *old_session_url;
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url,
+ ra_session,
+ new_session_url,
+ pool);
+
+ [...]
+
+ SVN_ERR(svn_ra_reparent(ra_session, old_session_url, pool));
+*/
+svn_error_t *
+svn_client__ensure_ra_session_url(const char **old_session_url,
+ svn_ra_session_t *ra_session,
+ const char *session_url,
+ apr_pool_t *pool);
+
+/* ---------------------------------------------------------------- */
+
+/*** RA callbacks ***/
+
+
+/* CTX is of type "svn_client_ctx_t *". */
+#define SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx) \
+ ((ctx)->log_msg_func3 || (ctx)->log_msg_func2 || (ctx)->log_msg_func)
+
+/* Open an RA session, returning it in *RA_SESSION or a corrected URL
+ in *CORRECTED_URL. (This function mirrors svn_ra_open4(), which
+ see, regarding the interpretation and handling of these two parameters.)
+
+ The root of the session is specified by BASE_URL and BASE_DIR_ABSPATH.
+
+ Additional control parameters:
+
+ - COMMIT_ITEMS is an array of svn_client_commit_item_t *
+ structures, present only for working copy commits, NULL otherwise.
+
+ - WRITE_DAV_PROPS indicates that the RA layer can clear and write
+ the DAV properties in the working copy of BASE_DIR_ABSPATH.
+
+ - READ_DAV_PROPS indicates that the RA layer should not attempt to
+ modify the WC props directly, but is still allowed to read them.
+
+ BASE_DIR_ABSPATH may be NULL if the RA operation does not correspond to a
+ working copy (in which case, WRITE_DAV_PROPS and READ_DAV_PROPS must be
+ FALSE.
+
+ If WRITE_DAV_PROPS and READ_DAV_PROPS are both FALSE the working copy may
+ still be used for locating pristine files via their SHA1.
+
+ The calling application's authentication baton is provided in CTX,
+ and allocations related to this session are performed in POOL.
+
+ NOTE: The reason for the _internal suffix of this function's name is to
+ avoid confusion with the public API svn_client_open_ra_session(). */
+svn_error_t *
+svn_client__open_ra_session_internal(svn_ra_session_t **ra_session,
+ const char **corrected_url,
+ const char *base_url,
+ const char *base_dir_abspath,
+ const apr_array_header_t *commit_items,
+ svn_boolean_t write_dav_props,
+ svn_boolean_t read_dav_props,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+svn_error_t *
+svn_client__ra_provide_base(svn_stream_t **contents,
+ svn_revnum_t *revision,
+ void *baton,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+svn_error_t *
+svn_client__ra_provide_props(apr_hash_t **props,
+ svn_revnum_t *revision,
+ void *baton,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+svn_error_t *
+svn_client__ra_get_copysrc_kind(svn_node_kind_t *kind,
+ void *baton,
+ const char *repos_relpath,
+ svn_revnum_t src_revision,
+ apr_pool_t *scratch_pool);
+
+
+void *
+svn_client__ra_make_cb_baton(svn_wc_context_t *wc_ctx,
+ apr_hash_t *relpath_map,
+ apr_pool_t *result_pool);
+
+/* ---------------------------------------------------------------- */
+
+/*** Add/delete ***/
+
+/* If AUTOPROPS is not null: Then read automatic properties matching PATH
+ from AUTOPROPS. AUTOPROPS is is a hash as per
+ svn_client__get_all_auto_props. Set *PROPERTIES to a hash containing
+ propname/value pairs (const char * keys mapping to svn_string_t * values).
+
+ If AUTOPROPS is null then set *PROPERTIES to an empty hash.
+
+ If *MIMETYPE is null or "application/octet-stream" then check AUTOPROPS
+ for a matching svn:mime-type. If AUTOPROPS is null or no match is found
+ and MAGIC_COOKIE is not NULL, then then try to detect the mime-type with
+ libmagic. If a mimetype is found then add it to *PROPERTIES and set
+ *MIMETYPE to the mimetype value or NULL otherwise.
+
+ Allocate the *PROPERTIES and its contents as well as *MIMETYPE, in
+ RESULT_POOL. Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *svn_client__get_paths_auto_props(
+ apr_hash_t **properties,
+ const char **mimetype,
+ const char *path,
+ svn_magic__cookie_t *magic_cookie,
+ apr_hash_t *autoprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gather all auto-props from CTX->config (or none if auto-props are
+ disabled) and all svn:auto-props explicitly set on or inherited
+ by PATH_OR_URL.
+
+ If PATH_OR_URL is an unversioned WC path then gather the
+ svn:auto-props inherited by PATH_OR_URL's nearest versioned
+ parent.
+
+ If PATH_OR_URL is a URL ask for the properties @HEAD, if it is a WC
+ path as sfor the working properties.
+
+ Store both types of auto-props in *AUTOPROPS, a hash mapping const
+ char * file patterns to another hash which maps const char * property
+ names to const char *property values.
+
+ If a given property name exists for the same pattern in both the config
+ file and in an a svn:auto-props property, the latter overrides the
+ former. If a given property name exists for the same pattern in two
+ different inherited svn:auto-props, then the closer path-wise
+ property overrides the more distant. svn:auto-props explicitly set
+ on PATH_OR_URL have the highest precedence and override inherited props
+ and config file settings.
+
+ Allocate *AUTOPROPS in RESULT_POOL. Use SCRATCH_POOL for temporary
+ allocations. */
+svn_error_t *svn_client__get_all_auto_props(apr_hash_t **autoprops,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Get a list of ignore patterns defined by the svn:global-ignores
+ properties set on, or inherited by, PATH_OR_URL. Store the collected
+ patterns as const char * elements in the array *IGNORES. Allocate
+ *IGNORES and its contents in RESULT_POOL. Use SCRATCH_POOL for
+ temporary allocations. */
+svn_error_t *svn_client__get_inherited_ignores(apr_array_header_t **ignores,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* The main logic for client deletion from a working copy. Deletes PATH
+ from CTX->WC_CTX. If PATH (or any item below a directory PATH) is
+ modified the delete will fail and return an error unless FORCE or KEEP_LOCAL
+ is TRUE.
+
+ If KEEP_LOCAL is TRUE then PATH is only scheduled from deletion from the
+ repository and a local copy of PATH will be kept in the working copy.
+
+ If DRY_RUN is TRUE all the checks are made to ensure that the delete can
+ occur, but the working copy is not modified. If NOTIFY_FUNC is not
+ null, it is called with NOTIFY_BATON for each file or directory deleted. */
+svn_error_t *
+svn_client__wc_delete(const char *local_abspath,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ svn_boolean_t keep_local,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/* Like svn_client__wc_delete(), but deletes multiple TARGETS efficiently. */
+svn_error_t *
+svn_client__wc_delete_many(const apr_array_header_t *targets,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ svn_boolean_t keep_local,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/* Make PATH and add it to the working copy, optionally making all the
+ intermediate parent directories if MAKE_PARENTS is TRUE. */
+svn_error_t *
+svn_client__make_local_parents(const char *path,
+ svn_boolean_t make_parents,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* ---------------------------------------------------------------- */
+
+/*** Checkout, update and switch ***/
+
+/* Update a working copy LOCAL_ABSPATH to REVISION, and (if not NULL) set
+ RESULT_REV to the update revision.
+
+ If DEPTH is svn_depth_unknown, then use whatever depth is already
+ set for LOCAL_ABSPATH, or @c svn_depth_infinity if LOCAL_ABSPATH does
+ not exist.
+
+ Else if DEPTH is svn_depth_infinity, then update fully recursively
+ (resetting the existing depth of the working copy if necessary).
+ Else if DEPTH is svn_depth_files, update all files under LOCAL_ABSPATH (if
+ any), but exclude any subdirectories. Else if DEPTH is
+ svn_depth_immediates, update all files and include immediate
+ subdirectories (at svn_depth_empty). Else if DEPTH is
+ svn_depth_empty, just update LOCAL_ABSPATH; if LOCAL_ABSPATH is a
+ directory, that means touching only its properties not its entries.
+
+ If DEPTH_IS_STICKY is set and DEPTH is not svn_depth_unknown, then
+ in addition to updating LOCAL_ABSPATH, also set its sticky ambient depth
+ value to DEPTH.
+
+ If IGNORE_EXTERNALS is true, do no externals processing.
+
+ Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
+ change *TIMESTAMP_SLEEP. The output will be valid even if the function
+ returns an error.
+
+ If ALLOW_UNVER_OBSTRUCTIONS is TRUE, unversioned children of LOCAL_ABSPATH
+ that obstruct items added from the repos are tolerated; if FALSE,
+ these obstructions cause the update to fail.
+
+ If ADDS_AS_MODIFICATION is TRUE, local additions are handled as
+ modifications on added nodes.
+
+ If INNERUPDATE is true, no anchor check is performed on the update target.
+
+ If MAKE_PARENTS is true, allow the update to calculate and checkout
+ (with depth=empty) any parent directories of the requested update
+ target which are missing from the working copy.
+
+ NOTE: You may not specify both INNERUPDATE and MAKE_PARENTS as true.
+*/
+svn_error_t *
+svn_client__update_internal(svn_revnum_t *result_rev,
+ const char *local_abspath,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t make_parents,
+ svn_boolean_t innerupdate,
+ svn_boolean_t *timestamp_sleep,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Checkout into LOCAL_ABSPATH a working copy of URL at REVISION, and (if not
+ NULL) set RESULT_REV to the checked out revision.
+
+ If DEPTH is svn_depth_infinity, then check out fully recursively.
+ Else if DEPTH is svn_depth_files, checkout all files under LOCAL_ABSPATH (if
+ any), but not subdirectories. Else if DEPTH is
+ svn_depth_immediates, check out all files and include immediate
+ subdirectories (at svn_depth_empty). Else if DEPTH is
+ svn_depth_empty, just check out LOCAL_ABSPATH, with none of its entries.
+
+ DEPTH must be a definite depth, not (e.g.) svn_depth_unknown.
+
+ RA_CACHE is a pointer to a cache of information for the URL at
+ REVISION based on the PEG_REVISION. Any information not in
+ *RA_CACHE is retrieved by a round-trip to the repository. RA_CACHE
+ may be NULL which indicates that no cache information is available.
+
+ If IGNORE_EXTERNALS is true, do no externals processing.
+
+ Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
+ change *TIMESTAMP_SLEEP. The output will be valid even if the function
+ returns an error.
+
+ If ALLOW_UNVER_OBSTRUCTIONS is TRUE,
+ unversioned children of LOCAL_ABSPATH that obstruct items added from
+ the repos are tolerated; if FALSE, these obstructions cause the checkout
+ to fail.
+
+ If INNERCHECKOUT is true, no anchor check is performed on the target.
+ */
+svn_error_t *
+svn_client__checkout_internal(svn_revnum_t *result_rev,
+ const char *URL,
+ const char *local_abspath,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t *timestamp_sleep,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Switch a working copy PATH to URL@PEG_REVISION at REVISION, and (if not
+ NULL) set RESULT_REV to the switch revision. A write lock will be
+ acquired and released if not held. Only switch as deeply as DEPTH
+ indicates.
+
+ Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
+ change *TIMESTAMP_SLEEP. The output will be valid even if the function
+ returns an error.
+
+ If IGNORE_EXTERNALS is true, don't process externals.
+
+ If ALLOW_UNVER_OBSTRUCTIONS is TRUE, unversioned children of PATH
+ that obstruct items added from the repos are tolerated; if FALSE,
+ these obstructions cause the switch to fail.
+
+ DEPTH and DEPTH_IS_STICKY behave as for svn_client__update_internal().
+
+ If IGNORE_ANCESTRY is true, don't perform a common ancestry check
+ between the PATH and URL; otherwise, do, and return
+ SVN_ERR_CLIENT_UNRELATED_RESOURCES if they aren't related.
+*/
+svn_error_t *
+svn_client__switch_internal(svn_revnum_t *result_rev,
+ const char *path,
+ const char *url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t *timestamp_sleep,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* ---------------------------------------------------------------- */
+
+/*** Inheritable Properties ***/
+
+/* Convert any svn_prop_inherited_item_t elements in INHERITED_PROPS which
+ have repository root relative path PATH_OR_URL structure members to URLs
+ using REPOS_ROOT_URL. Changes to the contents of INHERITED_PROPS are
+ allocated in RESULT_POOL. SCRATCH_POOL is used for temporary
+ allocations. */
+svn_error_t *
+svn_client__iprop_relpaths_to_urls(apr_array_header_t *inherited_props,
+ const char *repos_root_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Fetch the inherited properties for the base of LOCAL_ABSPATH as well
+ as any WC roots under LOCAL_ABSPATH (as limited by DEPTH) using
+ RA_SESSION. Store the results in *WCROOT_IPROPS, a hash mapping
+ const char * absolute working copy paths to depth-first ordered arrays
+ of svn_prop_inherited_item_t * structures.
+
+ Any svn_prop_inherited_item_t->path_or_url members returned in
+ *WCROOT_IPROPS are repository relative paths.
+
+ If LOCAL_ABSPATH has no base then do nothing.
+
+ RA_SESSION should be an open RA session pointing at the URL of PATH,
+ or NULL, in which case this function will use its own temporary session.
+
+ Allocate *WCROOT_IPROPS in RESULT_POOL, use SCRATCH_POOL for temporary
+ allocations.
+
+ If one or more of the paths are not available in the repository at the
+ specified revision, these paths will not be added to the hashtable.
+*/
+svn_error_t *
+svn_client__get_inheritable_props(apr_hash_t **wcroot_iprops,
+ const char *local_abspath,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* ---------------------------------------------------------------- */
+
+/*** Editor for repository diff ***/
+
+/* Create an editor for a pure repository comparison, i.e. comparing one
+ repository version against the other.
+
+ DIFF_CALLBACKS/DIFF_CMD_BATON represent the callback that implements
+ the comparison.
+
+ DEPTH is the depth to recurse.
+
+ RA_SESSION is an RA session through which this editor may fetch
+ properties, file contents and directory listings of the 'old' side of the
+ diff. It is a separate RA session from the one through which this editor
+ is being driven. REVISION is the revision number of the 'old' side of
+ the diff.
+
+ If TEXT_DELTAS is FALSE, then do not expect text deltas from the edit
+ drive, nor send the 'before' and 'after' texts to the diff callbacks;
+ instead, send empty files to the diff callbacks if there was a change.
+ This must be FALSE if the edit producer is not sending text deltas,
+ otherwise the file content checksum comparisons will fail.
+
+ EDITOR/EDIT_BATON return the newly created editor and baton.
+
+ @since New in 1.8.
+ */
+svn_error_t *
+svn_client__get_diff_editor2(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_ra_session_t *ra_session,
+ svn_depth_t depth,
+ svn_revnum_t revision,
+ svn_boolean_t text_deltas,
+ const svn_diff_tree_processor_t *processor,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool);
+
+/* ---------------------------------------------------------------- */
+
+/*** Editor for diff summary ***/
+
+/* Set *CALLBACKS and *CALLBACK_BATON to a set of diff callbacks that will
+ report a diff summary, i.e. only providing information about the changed
+ items without the text deltas.
+
+ TARGET is the target path, relative to the anchor, of the diff.
+
+ SUMMARIZE_FUNC is called with SUMMARIZE_BATON as parameter by the
+ created callbacks for each changed item.
+*/
+svn_error_t *
+svn_client__get_diff_summarize_callbacks(
+ svn_wc_diff_callbacks4_t **callbacks,
+ void **callback_baton,
+ const char *target,
+ svn_boolean_t reversed,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ apr_pool_t *pool);
+
+/* ---------------------------------------------------------------- */
+
+/*** Copy Stuff ***/
+
+/* This structure is used to associate a specific copy or move SRC with a
+ specific copy or move destination. It also contains information which
+ various helper functions may need. Not every copy function uses every
+ field.
+*/
+typedef struct svn_client__copy_pair_t
+{
+ /* The absolute source path or url. */
+ const char *src_abspath_or_url;
+
+ /* The base name of the object. It should be the same for both src
+ and dst. */
+ const char *base_name;
+
+ /* The node kind of the source */
+ svn_node_kind_t src_kind;
+
+ /* The original source name. (Used when the source gets overwritten by a
+ peg revision lookup.) */
+ const char *src_original;
+
+ /* The source operational revision. */
+ svn_opt_revision_t src_op_revision;
+
+ /* The source peg revision. */
+ svn_opt_revision_t src_peg_revision;
+
+ /* The source revision number. */
+ svn_revnum_t src_revnum;
+
+ /* The absolute destination path or url */
+ const char *dst_abspath_or_url;
+
+ /* The absolute source path or url of the destination's parent. */
+ const char *dst_parent_abspath;
+} svn_client__copy_pair_t;
+
+/* ---------------------------------------------------------------- */
+
+/*** Commit Stuff ***/
+
+/* WARNING: This is all new, untested, un-peer-reviewed conceptual
+ stuff.
+
+ The day that 'svn switch' came into existence, our old commit
+ crawler (svn_wc_crawl_local_mods) became obsolete. It relied far
+ too heavily on the on-disk hierarchy of files and directories, and
+ simply had no way to support disjoint working copy trees or nest
+ working copies. The primary reason for this is that commit
+ process, in order to guarantee atomicity, is a single drive of a
+ commit editor which is based not on working copy paths, but on
+ URLs. With the completion of 'svn switch', it became all too
+ likely that the on-disk working copy hierarchy would no longer be
+ guaranteed to map to a similar in-repository hierarchy.
+
+ Aside from this new brokenness of the old system, an unrelated
+ feature request had cropped up -- the ability to know in advance of
+ your commit, exactly what would be committed (so that log messages
+ could be initially populated with this information). Since the old
+ crawler discovered commit candidates while in the process of
+ committing, it was impossible to harvest this information upfront.
+ As a workaround, svn_wc_statuses() was used to stat the whole
+ working copy for changes before the commit started...and then the
+ commit would again stat the whole tree for changes.
+
+ Enter the new system.
+
+ The primary goal of this system is very straightforward: harvest
+ all commit candidate information up front, and cache enough info in
+ the process to use this to drive a URL-sorted commit.
+
+ *** END-OF-KNOWLEDGE ***
+
+ The prototypes below are still in development. In general, the
+ idea is that commit-y processes ('svn mkdir URL', 'svn delete URL',
+ 'svn commit', 'svn copy WC_PATH URL', 'svn copy URL1 URL2', 'svn
+ move URL1 URL2', others?) generate the cached commit candidate
+ information, and hand this information off to a consumer which is
+ responsible for driving the RA layer's commit editor in a
+ URL-depth-first fashion and reporting back the post-commit
+ information.
+
+*/
+
+/* Structure that contains an apr_hash_t * hash of apr_array_header_t *
+ arrays of svn_client_commit_item3_t * structures; keyed by the
+ canonical repository URLs. For faster lookup, it also provides
+ an hash index keyed by the local absolute path. */
+typedef struct svn_client__committables_t
+{
+ /* apr_array_header_t array of svn_client_commit_item3_t structures
+ keyed by canonical repository URL */
+ apr_hash_t *by_repository;
+
+ /* svn_client_commit_item3_t structures keyed by local absolute path
+ (path member in the respective structures).
+
+ This member is for fast lookup only, i.e. whether there is an
+ entry for the given path or not, but it will only allow for one
+ entry per absolute path (in case of duplicate entries in the
+ above arrays). The "canonical" data storage containing all item
+ is by_repository. */
+ apr_hash_t *by_path;
+
+} svn_client__committables_t;
+
+/* Callback for the commit harvester to check if a node exists at the specified
+ url */
+typedef svn_error_t *(*svn_client__check_url_kind_t)(void *baton,
+ svn_node_kind_t *kind,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool);
+
+/* Recursively crawl a set of working copy paths (BASE_DIR_ABSPATH + each
+ item in the TARGETS array) looking for commit candidates, locking
+ working copy directories as the crawl progresses. For each
+ candidate found:
+
+ - create svn_client_commit_item3_t for the candidate.
+
+ - add the structure to an apr_array_header_t array of commit
+ items that are in the same repository, creating a new array if
+ necessary.
+
+ - add (or update) a reference to this array to the by_repository
+ hash within COMMITTABLES and update the by_path member as well-
+
+ - if the candidate has a lock token, add it to the LOCK_TOKENS hash.
+
+ - if the candidate is a directory scheduled for deletion, crawl
+ the directories children recursively for any lock tokens and
+ add them to the LOCK_TOKENS array.
+
+ At the successful return of this function, COMMITTABLES will point
+ a new svn_client__committables_t*. LOCK_TOKENS will point to a hash
+ table with const char * lock tokens, keyed on const char * URLs.
+
+ If DEPTH is specified, descend (or not) into each target in TARGETS
+ as specified by DEPTH; the behavior is the same as that described
+ for svn_client_commit4().
+
+ If DEPTH_EMPTY_START is >= 0, all targets after index DEPTH_EMPTY_START
+ in TARGETS are handled as having svn_depth_empty.
+
+ If JUST_LOCKED is TRUE, treat unmodified items with lock tokens as
+ commit candidates.
+
+ If CHANGELISTS is non-NULL, it is an array of const char *
+ changelist names used as a restrictive filter
+ when harvesting committables; that is, don't add a path to
+ COMMITTABLES unless it's a member of one of those changelists.
+
+ If CTX->CANCEL_FUNC is non-null, it will be called with
+ CTX->CANCEL_BATON while harvesting to determine if the client has
+ cancelled the operation. */
+svn_error_t *
+svn_client__harvest_committables(svn_client__committables_t **committables,
+ apr_hash_t **lock_tokens,
+ const char *base_dir_abspath,
+ const apr_array_header_t *targets,
+ int depth_empty_start,
+ svn_depth_t depth,
+ svn_boolean_t just_locked,
+ const apr_array_header_t *changelists,
+ svn_client__check_url_kind_t check_url_func,
+ void *check_url_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Recursively crawl each absolute working copy path SRC in COPY_PAIRS,
+ harvesting commit_items into a COMMITABLES structure as if every entry
+ at or below the SRC was to be committed as a set of adds (mostly with
+ history) to a new repository URL (DST in COPY_PAIRS).
+
+ If CTX->CANCEL_FUNC is non-null, it will be called with
+ CTX->CANCEL_BATON while harvesting to determine if the client has
+ cancelled the operation. */
+svn_error_t *
+svn_client__get_copy_committables(svn_client__committables_t **committables,
+ const apr_array_header_t *copy_pairs,
+ svn_client__check_url_kind_t check_url_func,
+ void *check_url_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* A qsort()-compatible sort routine for sorting an array of
+ svn_client_commit_item_t *'s by their URL member. */
+int svn_client__sort_commit_item_urls(const void *a, const void *b);
+
+
+/* Rewrite the COMMIT_ITEMS array to be sorted by URL. Also, discover
+ a common *BASE_URL for the items in the array, and rewrite those
+ items' URLs to be relative to that *BASE_URL.
+
+ COMMIT_ITEMS is an array of (svn_client_commit_item3_t *) items.
+
+ Afterwards, some of the items in COMMIT_ITEMS may contain data
+ allocated in POOL. */
+svn_error_t *
+svn_client__condense_commit_items(const char **base_url,
+ apr_array_header_t *commit_items,
+ apr_pool_t *pool);
+
+
+/* Like svn_ra_stat() on the ra session root, but with a compatibility
+ hack for pre-1.2 svnserve that don't support this api. */
+svn_error_t *
+svn_client__ra_stat_compatible(svn_ra_session_t *ra_session,
+ svn_revnum_t rev,
+ svn_dirent_t **dirent_p,
+ apr_uint32_t dirent_fields,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool);
+
+
+/* Commit the items in the COMMIT_ITEMS array using EDITOR/EDIT_BATON
+ to describe the committed local mods. Prior to this call,
+ COMMIT_ITEMS should have been run through (and BASE_URL generated
+ by) svn_client__condense_commit_items().
+
+ COMMIT_ITEMS is an array of (svn_client_commit_item3_t *) items.
+
+ CTX->NOTIFY_FUNC/CTX->BATON will be called as the commit progresses, as
+ a way of describing actions to the application layer (if non NULL).
+
+ NOTIFY_PATH_PREFIX will be passed to CTX->notify_func2() as the
+ common absolute path prefix of the committed paths. It can be NULL.
+
+ If SHA1_CHECKSUMS is not NULL, set *SHA1_CHECKSUMS to a hash containing,
+ for each file transmitted, a mapping from the commit-item's (const
+ char *) path to the (const svn_checksum_t *) SHA1 checksum of its new text
+ base.
+
+ Use RESULT_POOL for all allocating the resulting hashes and SCRATCH_POOL
+ for temporary allocations.
+ */
+svn_error_t *
+svn_client__do_commit(const char *base_url,
+ const apr_array_header_t *commit_items,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ const char *notify_path_prefix,
+ apr_hash_t **sha1_checksums,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+
+/*** Externals (Modules) ***/
+
+/* Handle changes to the svn:externals property described by EXTERNALS_NEW,
+ and AMBIENT_DEPTHS. The tree's top level directory
+ is at TARGET_ABSPATH which has a root URL of REPOS_ROOT_URL.
+ A write lock should be held.
+
+ For each changed value of the property, discover the nature of the
+ change and behave appropriately -- either check a new "external"
+ subdir, or call svn_wc_remove_from_revision_control() on an
+ existing one, or both.
+
+ TARGET_ABSPATH is the root of the driving operation and
+ REQUESTED_DEPTH is the requested depth of the driving operation
+ (e.g., update, switch, etc). If it is neither svn_depth_infinity
+ nor svn_depth_unknown, then changes to svn:externals will have no
+ effect. If REQUESTED_DEPTH is svn_depth_unknown, then the ambient
+ depth of each working copy directory holding an svn:externals value
+ will determine whether that value is interpreted there (the ambient
+ depth must be svn_depth_infinity). If REQUESTED_DEPTH is
+ svn_depth_infinity, then it is presumed to be expanding any
+ shallower ambient depth, so changes to svn:externals values will be
+ interpreted.
+
+ Pass NOTIFY_FUNC with NOTIFY_BATON along to svn_client_checkout().
+
+ Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
+ change *TIMESTAMP_SLEEP. The output will be valid even if the function
+ returns an error.
+
+ Use POOL for temporary allocation. */
+svn_error_t *
+svn_client__handle_externals(apr_hash_t *externals_new,
+ apr_hash_t *ambient_depths,
+ const char *repos_root_url,
+ const char *target_abspath,
+ svn_depth_t requested_depth,
+ svn_boolean_t *timestamp_sleep,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/* Export externals definitions described by EXTERNALS, a hash of the
+ form returned by svn_wc_edited_externals() (which see). The external
+ items will be exported instead of checked out -- they will have no
+ administrative subdirectories.
+
+ The checked out or exported tree's top level directory is at
+ TO_ABSPATH and corresponds to FROM_URL URL in the repository, which
+ has a root URL of REPOS_ROOT_URL.
+
+ REQUESTED_DEPTH is the requested_depth of the driving operation; it
+ behaves as for svn_client__handle_externals(), except that ambient
+ depths are presumed to be svn_depth_infinity.
+
+ NATIVE_EOL is the value passed as NATIVE_EOL when exporting.
+
+ Use POOL for temporary allocation. */
+svn_error_t *
+svn_client__export_externals(apr_hash_t *externals,
+ const char *from_url,
+ const char *to_abspath,
+ const char *repos_root_url,
+ svn_depth_t requested_depth,
+ const char *native_eol,
+ svn_boolean_t ignore_keywords,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Baton for svn_client__dirent_fetcher */
+struct svn_client__dirent_fetcher_baton_t
+{
+ svn_ra_session_t *ra_session;
+ svn_revnum_t target_revision;
+ const char *anchor_url;
+};
+
+/* Implements svn_wc_dirents_func_t for update and switch handling. Assumes
+ a struct svn_client__dirent_fetcher_baton_t * baton */
+svn_error_t *
+svn_client__dirent_fetcher(void *baton,
+ apr_hash_t **dirents,
+ const char *repos_root_url,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Retrieve log messages using the first provided (non-NULL) callback
+ in the set of *CTX->log_msg_func3, CTX->log_msg_func2, or
+ CTX->log_msg_func. Other arguments same as
+ svn_client_get_commit_log3_t. */
+svn_error_t *
+svn_client__get_log_msg(const char **log_msg,
+ const char **tmp_file,
+ const apr_array_header_t *commit_items,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Return the revision properties stored in REVPROP_TABLE_IN, adding
+ LOG_MSG as SVN_PROP_REVISION_LOG in *REVPROP_TABLE_OUT, allocated in
+ POOL. *REVPROP_TABLE_OUT will map const char * property names to
+ svn_string_t values. If REVPROP_TABLE_IN is non-NULL, check that
+ it doesn't contain any of the standard Subversion properties. In
+ that case, return SVN_ERR_CLIENT_PROPERTY_NAME. */
+svn_error_t *
+svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out,
+ const apr_hash_t *revprop_table_in,
+ const char *log_msg,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Return a potentially translated version of local file LOCAL_ABSPATH
+ in NORMAL_STREAM. REVISION must be one of the following: BASE, COMMITTED,
+ WORKING.
+
+ EXPAND_KEYWORDS operates as per the EXPAND argument to
+ svn_subst_stream_translated, which see. If NORMALIZE_EOLS is TRUE and
+ LOCAL_ABSPATH requires translation, then normalize the line endings in
+ *NORMAL_STREAM.
+
+ Uses SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_client__get_normalized_stream(svn_stream_t **normal_stream,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t expand_keywords,
+ svn_boolean_t normalize_eols,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Return a set of callbacks to use with the Ev2 shims. */
+svn_delta_shim_callbacks_t *
+svn_client__get_shim_callbacks(svn_wc_context_t *wc_ctx,
+ apr_hash_t *relpath_map,
+ apr_pool_t *result_pool);
+
+/* Return REVISION unless its kind is 'unspecified' in which case return
+ * a pointer to a statically allocated revision structure of kind 'head'
+ * if PATH_OR_URL is a URL or 'base' if it is a WC path. */
+const svn_opt_revision_t *
+svn_cl__rev_default_to_head_or_base(const svn_opt_revision_t *revision,
+ const char *path_or_url);
+
+/* Return REVISION unless its kind is 'unspecified' in which case return
+ * a pointer to a statically allocated revision structure of kind 'head'
+ * if PATH_OR_URL is a URL or 'working' if it is a WC path. */
+const svn_opt_revision_t *
+svn_cl__rev_default_to_head_or_working(const svn_opt_revision_t *revision,
+ const char *path_or_url);
+
+/* Return REVISION unless its kind is 'unspecified' in which case return
+ * PEG_REVISION. */
+const svn_opt_revision_t *
+svn_cl__rev_default_to_peg(const svn_opt_revision_t *revision,
+ const svn_opt_revision_t *peg_revision);
+
+/* Call the conflict resolver callback in CTX for each conflict recorded
+ * in CONFLICTED_PATHS (const char *abspath keys; ignored values). If
+ * CONFLICTS_REMAIN is not NULL, then set *CONFLICTS_REMAIN to true if
+ * there are any conflicts among CONFLICTED_PATHS remaining unresolved
+ * at the end of this operation, else set it to false.
+ */
+svn_error_t *
+svn_client__resolve_conflicts(svn_boolean_t *conflicts_remain,
+ apr_hash_t *conflicted_paths,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_CLIENT_H */
diff --git a/contrib/subversion/subversion/libsvn_client/cmdline.c b/contrib/subversion/subversion/libsvn_client/cmdline.c
new file mode 100644
index 0000000..a17f8c4
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/cmdline.c
@@ -0,0 +1,363 @@
+/*
+ * cmdline.c: command-line processing
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+/*** Includes. ***/
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_opt.h"
+#include "svn_utf.h"
+
+#include "client.h"
+
+#include "private/svn_opt_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+#define DEFAULT_ARRAY_SIZE 5
+
+
+/* Attempt to find the repository root url for TARGET, possibly using CTX for
+ * authentication. If one is found and *ROOT_URL is not NULL, then just check
+ * that the root url for TARGET matches the value given in *ROOT_URL and
+ * return an error if it does not. If one is found and *ROOT_URL is NULL then
+ * set *ROOT_URL to the root url for TARGET, allocated from POOL.
+ * If a root url is not found for TARGET because it does not exist in the
+ * repository, then return with no error.
+ *
+ * TARGET is a UTF-8 encoded string that is fully canonicalized and escaped.
+ */
+static svn_error_t *
+check_root_url_of_target(const char **root_url,
+ const char *target,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ const char *tmp_root_url;
+ const char *truepath;
+ svn_opt_revision_t opt_rev;
+
+ SVN_ERR(svn_opt_parse_path(&opt_rev, &truepath, target, pool));
+ if (!svn_path_is_url(truepath))
+ SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, pool));
+
+ err = svn_client_get_repos_root(&tmp_root_url, NULL, truepath,
+ ctx, pool, pool);
+
+ if (err)
+ {
+ /* It is OK if the given target does not exist, it just means
+ * we will not be able to determine the root url from this particular
+ * argument.
+ *
+ * If the target itself is a URL to a repository that does not exist,
+ * that's fine, too. The callers will deal with this argument in an
+ * appropriate manner if it does not make any sense.
+ *
+ * Also tolerate locally added targets ("bad revision" error).
+ */
+ if ((err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
+ || (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ || (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
+ || (err->apr_err == SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED)
+ || (err->apr_err == SVN_ERR_CLIENT_BAD_REVISION))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ if (*root_url && tmp_root_url)
+ {
+ if (strcmp(*root_url, tmp_root_url) != 0)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("All non-relative targets must have "
+ "the same root URL"));
+ }
+ else
+ *root_url = tmp_root_url;
+
+ return SVN_NO_ERROR;
+}
+
+/* Note: This is substantially copied from svn_opt__args_to_target_array() in
+ * order to move to libsvn_client while maintaining backward compatibility. */
+svn_error_t *
+svn_client_args_to_target_array2(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t keep_last_origpath_on_truepath_collision,
+ apr_pool_t *pool)
+{
+ int i;
+ svn_boolean_t rel_url_found = FALSE;
+ const char *root_url = NULL;
+ apr_array_header_t *input_targets =
+ apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
+ apr_array_header_t *output_targets =
+ apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
+ apr_array_header_t *reserved_names = NULL;
+
+ /* Step 1: create a master array of targets that are in UTF-8
+ encoding, and come from concatenating the targets left by apr_getopt,
+ plus any extra targets (e.g., from the --targets switch.)
+ If any of the targets are relative urls, then set the rel_url_found
+ flag.*/
+
+ for (; os->ind < os->argc; os->ind++)
+ {
+ /* The apr_getopt targets are still in native encoding. */
+ const char *raw_target = os->argv[os->ind];
+ const char *utf8_target;
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&utf8_target,
+ raw_target, pool));
+
+ if (svn_path_is_repos_relative_url(utf8_target))
+ rel_url_found = TRUE;
+
+ APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
+ }
+
+ if (known_targets)
+ {
+ for (i = 0; i < known_targets->nelts; i++)
+ {
+ /* The --targets array have already been converted to UTF-8,
+ because we needed to split up the list with svn_cstring_split. */
+ const char *utf8_target = APR_ARRAY_IDX(known_targets,
+ i, const char *);
+
+ if (svn_path_is_repos_relative_url(utf8_target))
+ rel_url_found = TRUE;
+
+ APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
+ }
+ }
+
+ /* Step 2: process each target. */
+
+ for (i = 0; i < input_targets->nelts; i++)
+ {
+ const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
+
+ /* Relative urls will be canonicalized when they are resolved later in
+ * the function
+ */
+ if (svn_path_is_repos_relative_url(utf8_target))
+ {
+ APR_ARRAY_PUSH(output_targets, const char *) = utf8_target;
+ }
+ else
+ {
+ const char *true_target;
+ const char *peg_rev;
+ const char *target;
+
+ /*
+ * This is needed so that the target can be properly canonicalized,
+ * otherwise the canonicalization does not treat a ".@BASE" as a "."
+ * with a BASE peg revision, and it is not canonicalized to "@BASE".
+ * If any peg revision exists, it is appended to the final
+ * canonicalized path or URL. Do not use svn_opt_parse_path()
+ * because the resulting peg revision is a structure that would have
+ * to be converted back into a string. Converting from a string date
+ * to the apr_time_t field in the svn_opt_revision_value_t and back to
+ * a string would not necessarily preserve the exact bytes of the
+ * input date, so its easier just to keep it in string form.
+ */
+ SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
+ utf8_target, pool));
+
+ /* URLs and wc-paths get treated differently. */
+ if (svn_path_is_url(true_target))
+ {
+ SVN_ERR(svn_opt__arg_canonicalize_url(&true_target,
+ true_target, pool));
+ }
+ else /* not a url, so treat as a path */
+ {
+ const char *base_name;
+ const char *original_target;
+
+ original_target = svn_dirent_internal_style(true_target, pool);
+ SVN_ERR(svn_opt__arg_canonicalize_path(&true_target,
+ true_target, pool));
+
+ /* There are two situations in which a 'truepath-conversion'
+ (case-canonicalization to on-disk path on case-insensitive
+ filesystem) needs to be undone:
+
+ 1. If KEEP_LAST_ORIGPATH_ON_TRUEPATH_COLLISION is TRUE, and
+ this is the last target of a 2-element target list, and
+ both targets have the same truepath. */
+ if (keep_last_origpath_on_truepath_collision
+ && input_targets->nelts == 2 && i == 1
+ && strcmp(original_target, true_target) != 0)
+ {
+ const char *src_truepath = APR_ARRAY_IDX(output_targets,
+ 0,
+ const char *);
+ if (strcmp(src_truepath, true_target) == 0)
+ true_target = original_target;
+ }
+
+ /* 2. If there is an exact match in the wc-db without a
+ corresponding on-disk path (e.g. a scheduled-for-delete
+ file only differing in case from an on-disk file). */
+ if (strcmp(original_target, true_target) != 0)
+ {
+ const char *target_abspath;
+ svn_node_kind_t kind;
+ svn_error_t *err2;
+
+ SVN_ERR(svn_dirent_get_absolute(&target_abspath,
+ original_target, pool));
+ err2 = svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath,
+ TRUE, FALSE, pool);
+ if (err2
+ && (err2->apr_err == SVN_ERR_WC_NOT_WORKING_COPY
+ || err2->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED))
+ {
+ svn_error_clear(err2);
+ }
+ else
+ {
+ SVN_ERR(err2);
+ /* We successfully did a lookup in the wc-db. Now see
+ if it's something interesting. */
+ if (kind == svn_node_file || kind == svn_node_dir)
+ true_target = original_target;
+ }
+ }
+
+ /* If the target has the same name as a Subversion
+ working copy administrative dir, skip it. */
+ base_name = svn_dirent_basename(true_target, pool);
+
+ if (svn_wc_is_adm_dir(base_name, pool))
+ {
+ if (!reserved_names)
+ reserved_names = apr_array_make(pool, DEFAULT_ARRAY_SIZE,
+ sizeof(const char *));
+
+ APR_ARRAY_PUSH(reserved_names, const char *) = utf8_target;
+
+ continue;
+ }
+ }
+
+ target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL);
+
+ if (rel_url_found)
+ {
+ /* Later targets have priority over earlier target, I
+ don't know why, see basic_relative_url_multi_repo. */
+ SVN_ERR(check_root_url_of_target(&root_url, target,
+ ctx, pool));
+ }
+
+ APR_ARRAY_PUSH(output_targets, const char *) = target;
+ }
+ }
+
+ /* Only resolve relative urls if there were some actually found earlier. */
+ if (rel_url_found)
+ {
+ /*
+ * Use the current directory's root url if one wasn't found using the
+ * arguments.
+ */
+ if (root_url == NULL)
+ {
+ const char *current_abspath;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&current_abspath, "", pool));
+ err = svn_client_get_repos_root(&root_url, NULL /* uuid */,
+ current_abspath, ctx, pool, pool);
+ if (err || root_url == NULL)
+ return svn_error_create(SVN_ERR_WC_NOT_WORKING_COPY, err,
+ _("Resolving '^/': no repository root "
+ "found in the target arguments or "
+ "in the current directory"));
+ }
+
+ *targets_p = apr_array_make(pool, output_targets->nelts,
+ sizeof(const char *));
+
+ for (i = 0; i < output_targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(output_targets, i,
+ const char *);
+
+ if (svn_path_is_repos_relative_url(target))
+ {
+ const char *abs_target;
+ const char *true_target;
+ const char *peg_rev;
+
+ SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
+ target, pool));
+
+ SVN_ERR(svn_path_resolve_repos_relative_url(&abs_target,
+ true_target,
+ root_url, pool));
+
+ SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, abs_target,
+ pool));
+
+ target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL);
+ }
+
+ APR_ARRAY_PUSH(*targets_p, const char *) = target;
+ }
+ }
+ else
+ *targets_p = output_targets;
+
+ if (reserved_names)
+ {
+ svn_error_t *err = SVN_NO_ERROR;
+
+ for (i = 0; i < reserved_names->nelts; ++i)
+ err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, err,
+ _("'%s' ends in a reserved name"),
+ APR_ARRAY_IDX(reserved_names, i,
+ const char *));
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/commit.c b/contrib/subversion/subversion/libsvn_client/commit.c
new file mode 100644
index 0000000..3f6bfef
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/commit.c
@@ -0,0 +1,1031 @@
+/*
+ * commit.c: wrappers around wc commit functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <apr_strings.h>
+#include <apr_hash.h>
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_ra.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_sorts.h"
+
+#include "client.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_ra_private.h"
+
+#include "svn_private_config.h"
+
+struct capture_baton_t {
+ svn_commit_callback2_t original_callback;
+ void *original_baton;
+
+ svn_commit_info_t **info;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+capture_commit_info(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct capture_baton_t *cb = baton;
+
+ *(cb->info) = svn_commit_info_dup(commit_info, cb->pool);
+
+ if (cb->original_callback)
+ SVN_ERR((cb->original_callback)(commit_info, cb->original_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+get_ra_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ const char *log_msg,
+ const apr_array_header_t *commit_items,
+ const apr_hash_t *revprop_table,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ apr_pool_t *pool)
+{
+ apr_hash_t *commit_revprops;
+ apr_hash_t *relpath_map = NULL;
+
+ SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
+ log_msg, ctx, pool));
+
+#ifdef ENABLE_EV2_SHIMS
+ if (commit_items)
+ {
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ relpath_map = apr_hash_make(pool);
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
+ svn_client_commit_item3_t *);
+ const char *relpath;
+
+ if (!item->path)
+ continue;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL,
+ ctx->wc_ctx, item->path, FALSE, pool,
+ iterpool));
+ if (relpath)
+ svn_hash_sets(relpath_map, relpath, item->path);
+ }
+ svn_pool_destroy(iterpool);
+ }
+#endif
+
+ /* Fetch RA commit editor. */
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
+ svn_client__get_shim_callbacks(ctx->wc_ctx,
+ relpath_map, pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, editor, edit_baton,
+ commit_revprops, commit_callback,
+ commit_baton, lock_tokens, keep_locks,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Public Interfaces. ***/
+
+static svn_error_t *
+reconcile_errors(svn_error_t *commit_err,
+ svn_error_t *unlock_err,
+ svn_error_t *bump_err,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ /* Early release (for good behavior). */
+ if (! (commit_err || unlock_err || bump_err))
+ return SVN_NO_ERROR;
+
+ /* If there was a commit error, start off our error chain with
+ that. */
+ if (commit_err)
+ {
+ commit_err = svn_error_quick_wrap
+ (commit_err, _("Commit failed (details follow):"));
+ err = commit_err;
+ }
+
+ /* Else, create a new "general" error that will lead off the errors
+ that follow. */
+ else
+ err = svn_error_create(SVN_ERR_BASE, NULL,
+ _("Commit succeeded, but other errors follow:"));
+
+ /* If there was an unlock error... */
+ if (unlock_err)
+ {
+ /* Wrap the error with some headers. */
+ unlock_err = svn_error_quick_wrap
+ (unlock_err, _("Error unlocking locked dirs (details follow):"));
+
+ /* Append this error to the chain. */
+ svn_error_compose(err, unlock_err);
+ }
+
+ /* If there was a bumping error... */
+ if (bump_err)
+ {
+ /* Wrap the error with some headers. */
+ bump_err = svn_error_quick_wrap
+ (bump_err, _("Error bumping revisions post-commit (details follow):"));
+
+ /* Append this error to the chain. */
+ svn_error_compose(err, bump_err);
+ }
+
+ return err;
+}
+
+/* For all lock tokens in ALL_TOKENS for URLs under BASE_URL, add them
+ to a new hashtable allocated in POOL. *RESULT is set to point to this
+ new hash table. *RESULT will be keyed on const char * URI-decoded paths
+ relative to BASE_URL. The lock tokens will not be duplicated. */
+static svn_error_t *
+collect_lock_tokens(apr_hash_t **result,
+ apr_hash_t *all_tokens,
+ const char *base_url,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ *result = apr_hash_make(pool);
+
+ for (hi = apr_hash_first(pool, all_tokens); hi; hi = apr_hash_next(hi))
+ {
+ const char *url = svn__apr_hash_index_key(hi);
+ const char *token = svn__apr_hash_index_val(hi);
+ const char *relpath = svn_uri_skip_ancestor(base_url, url, pool);
+
+ if (relpath)
+ {
+ svn_hash_sets(*result, relpath, token);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Put ITEM onto QUEUE, allocating it in QUEUE's pool...
+ * If a checksum is provided, it can be the MD5 and/or the SHA1. */
+static svn_error_t *
+post_process_commit_item(svn_wc_committed_queue_t *queue,
+ const svn_client_commit_item3_t *item,
+ svn_wc_context_t *wc_ctx,
+ svn_boolean_t keep_changelists,
+ svn_boolean_t keep_locks,
+ svn_boolean_t commit_as_operations,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t loop_recurse = FALSE;
+ svn_boolean_t remove_lock;
+
+ if (! commit_as_operations
+ && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
+ && (item->kind == svn_node_dir)
+ && (item->copyfrom_url))
+ loop_recurse = TRUE;
+
+ remove_lock = (! keep_locks && (item->state_flags
+ & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN));
+
+ return svn_wc_queue_committed3(queue, wc_ctx, item->path,
+ loop_recurse, item->incoming_prop_changes,
+ remove_lock, !keep_changelists,
+ sha1_checksum, scratch_pool);
+}
+
+
+static svn_error_t *
+check_nonrecursive_dir_delete(svn_wc_context_t *wc_ctx,
+ const char *target_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR_ASSERT(depth != svn_depth_infinity);
+
+ SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, target_abspath,
+ TRUE, FALSE, scratch_pool));
+
+
+ /* ### TODO(sd): This check is slightly too strict. It should be
+ ### possible to:
+ ###
+ ### * delete a directory containing only files when
+ ### depth==svn_depth_files;
+ ###
+ ### * delete a directory containing only files and empty
+ ### subdirs when depth==svn_depth_immediates.
+ ###
+ ### But for now, we insist on svn_depth_infinity if you're
+ ### going to delete a directory, because we're lazy and
+ ### trying to get depthy commits working in the first place.
+ ###
+ ### This would be fairly easy to fix, though: just, well,
+ ### check the above conditions!
+ ###
+ ### GJS: I think there may be some confusion here. there is
+ ### the depth of the commit, and the depth of a checked-out
+ ### directory in the working copy. Delete, by its nature, will
+ ### always delete all of its children, so it seems a bit
+ ### strange to worry about what is in the working copy.
+ */
+ if (kind == svn_node_dir)
+ {
+ svn_wc_schedule_t schedule;
+
+ /* ### Looking at schedule is probably enough, no need for
+ pristine compare etc. */
+ SVN_ERR(svn_wc__node_get_schedule(&schedule, NULL,
+ wc_ctx, target_abspath,
+ scratch_pool));
+
+ if (schedule == svn_wc_schedule_delete
+ || schedule == svn_wc_schedule_replace)
+ {
+ const apr_array_header_t *children;
+
+ SVN_ERR(svn_wc__node_get_children(&children, wc_ctx,
+ target_abspath, TRUE,
+ scratch_pool, scratch_pool));
+
+ if (children->nelts > 0)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot delete the directory '%s' "
+ "in a non-recursive commit "
+ "because it has children"),
+ svn_dirent_local_style(target_abspath,
+ scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Given a list of committables described by their common base abspath
+ BASE_ABSPATH and a list of relative dirents TARGET_RELPATHS determine
+ which absolute paths must be locked to commit all these targets and
+ return this as a const char * array in LOCK_TARGETS
+
+ Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporary
+ storage */
+static svn_error_t *
+determine_lock_targets(apr_array_header_t **lock_targets,
+ svn_wc_context_t *wc_ctx,
+ const char *base_abspath,
+ const apr_array_header_t *target_relpaths,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *wc_items; /* const char *wcroot -> apr_array_header_t */
+ apr_hash_index_t *hi;
+ int i;
+
+ wc_items = apr_hash_make(scratch_pool);
+
+ /* Create an array of targets for each working copy used */
+ for (i = 0; i < target_relpaths->nelts; i++)
+ {
+ const char *target_abspath;
+ const char *wcroot_abspath;
+ apr_array_header_t *wc_targets;
+ svn_error_t *err;
+ const char *target_relpath = APR_ARRAY_IDX(target_relpaths, i,
+ const char *);
+
+ svn_pool_clear(iterpool);
+ target_abspath = svn_dirent_join(base_abspath, target_relpath,
+ scratch_pool);
+
+ err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
+ iterpool, iterpool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ continue;
+ }
+ return svn_error_trace(err);
+ }
+
+ wc_targets = svn_hash_gets(wc_items, wcroot_abspath);
+
+ if (! wc_targets)
+ {
+ wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *));
+ svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath),
+ wc_targets);
+ }
+
+ APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
+ }
+
+ *lock_targets = apr_array_make(result_pool, apr_hash_count(wc_items),
+ sizeof(const char *));
+
+ /* For each working copy determine where to lock */
+ for (hi = apr_hash_first(scratch_pool, wc_items);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *common;
+ const char *wcroot_abspath = svn__apr_hash_index_key(hi);
+ apr_array_header_t *wc_targets = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ if (wc_targets->nelts == 1)
+ {
+ const char *target_abspath;
+ target_abspath = APR_ARRAY_IDX(wc_targets, 0, const char *);
+
+ if (! strcmp(wcroot_abspath, target_abspath))
+ {
+ APR_ARRAY_PUSH(*lock_targets, const char *)
+ = apr_pstrdup(result_pool, target_abspath);
+ }
+ else
+ {
+ /* Lock the parent to allow deleting the target */
+ APR_ARRAY_PUSH(*lock_targets, const char *)
+ = svn_dirent_dirname(target_abspath, result_pool);
+ }
+ }
+ else if (wc_targets->nelts > 1)
+ {
+ SVN_ERR(svn_dirent_condense_targets(&common, &wc_targets, wc_targets,
+ FALSE, iterpool, iterpool));
+
+ qsort(wc_targets->elts, wc_targets->nelts, wc_targets->elt_size,
+ svn_sort_compare_paths);
+
+ if (wc_targets->nelts == 0
+ || !svn_path_is_empty(APR_ARRAY_IDX(wc_targets, 0, const char*))
+ || !strcmp(common, wcroot_abspath))
+ {
+ APR_ARRAY_PUSH(*lock_targets, const char *)
+ = apr_pstrdup(result_pool, common);
+ }
+ else
+ {
+ /* Lock the parent to allow deleting the target */
+ APR_ARRAY_PUSH(*lock_targets, const char *)
+ = svn_dirent_dirname(common, result_pool);
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Baton for check_url_kind */
+struct check_url_kind_baton
+{
+ apr_pool_t *pool;
+ svn_ra_session_t *session;
+ const char *repos_root_url;
+ svn_client_ctx_t *ctx;
+};
+
+/* Implements svn_client__check_url_kind_t for svn_client_commit5 */
+static svn_error_t *
+check_url_kind(void *baton,
+ svn_node_kind_t *kind,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ struct check_url_kind_baton *cukb = baton;
+
+ /* If we don't have a session or can't use the session, get one */
+ if (!cukb->session || !svn_uri__is_ancestor(cukb->repos_root_url, url))
+ {
+ SVN_ERR(svn_client_open_ra_session2(&cukb->session, url, NULL, cukb->ctx,
+ cukb->pool, scratch_pool));
+ SVN_ERR(svn_ra_get_repos_root2(cukb->session, &cukb->repos_root_url,
+ cukb->pool));
+ }
+ else
+ SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
+
+ return svn_error_trace(
+ svn_ra_check_path(cukb->session, "", revision,
+ kind, scratch_pool));
+}
+
+/* Recurse into every target in REL_TARGETS, finding committable externals
+ * nested within. Append these to REL_TARGETS itself. The paths in REL_TARGETS
+ * are assumed to be / will be created relative to BASE_ABSPATH. The remaining
+ * arguments correspond to those of svn_client_commit6(). */
+static svn_error_t*
+append_externals_as_explicit_targets(apr_array_header_t *rel_targets,
+ const char *base_abspath,
+ svn_boolean_t include_file_externals,
+ svn_boolean_t include_dir_externals,
+ svn_depth_t depth,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int rel_targets_nelts_fixed;
+ int i;
+ apr_pool_t *iterpool;
+
+ if (! (include_file_externals || include_dir_externals))
+ return SVN_NO_ERROR;
+
+ /* Easy part of applying DEPTH to externals. */
+ if (depth == svn_depth_empty)
+ {
+ /* Don't recurse. */
+ return SVN_NO_ERROR;
+ }
+
+ /* Iterate *and* grow REL_TARGETS at the same time. */
+ rel_targets_nelts_fixed = rel_targets->nelts;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < rel_targets_nelts_fixed; i++)
+ {
+ int j;
+ const char *target;
+ apr_array_header_t *externals = NULL;
+
+ svn_pool_clear(iterpool);
+
+ target = svn_dirent_join(base_abspath,
+ APR_ARRAY_IDX(rel_targets, i, const char *),
+ iterpool);
+
+ /* ### TODO: Possible optimization: No need to do this for file targets.
+ * ### But what's cheaper, stat'ing the file system or querying the db?
+ * ### --> future. */
+
+ SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx,
+ target, depth,
+ iterpool, iterpool));
+
+ if (externals != NULL)
+ {
+ const char *rel_target;
+
+ for (j = 0; j < externals->nelts; j++)
+ {
+ svn_wc__committable_external_info_t *xinfo =
+ APR_ARRAY_IDX(externals, j,
+ svn_wc__committable_external_info_t *);
+
+ if ((xinfo->kind == svn_node_file && ! include_file_externals)
+ || (xinfo->kind == svn_node_dir && ! include_dir_externals))
+ continue;
+
+ rel_target = svn_dirent_skip_ancestor(base_abspath,
+ xinfo->local_abspath);
+
+ SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0');
+
+ APR_ARRAY_PUSH(rel_targets, const char *) =
+ apr_pstrdup(result_pool, rel_target);
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_commit6(const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_boolean_t keep_locks,
+ svn_boolean_t keep_changelists,
+ svn_boolean_t commit_as_operations,
+ svn_boolean_t include_file_externals,
+ svn_boolean_t include_dir_externals,
+ const apr_array_header_t *changelists,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ struct capture_baton_t cb;
+ svn_ra_session_t *ra_session;
+ const char *log_msg;
+ const char *base_abspath;
+ const char *base_url;
+ apr_array_header_t *rel_targets;
+ apr_array_header_t *lock_targets;
+ apr_array_header_t *locks_obtained;
+ svn_client__committables_t *committables;
+ apr_hash_t *lock_tokens;
+ apr_hash_t *sha1_checksums;
+ apr_array_header_t *commit_items;
+ svn_error_t *cmt_err = SVN_NO_ERROR;
+ svn_error_t *bump_err = SVN_NO_ERROR;
+ svn_error_t *unlock_err = SVN_NO_ERROR;
+ svn_boolean_t commit_in_progress = FALSE;
+ svn_boolean_t timestamp_sleep = FALSE;
+ svn_commit_info_t *commit_info = NULL;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ const char *current_abspath;
+ const char *notify_prefix;
+ int depth_empty_after = -1;
+ int i;
+
+ SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude);
+
+ /* Committing URLs doesn't make sense, so error if it's tried. */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf
+ (SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is a URL, but URLs cannot be commit targets"), target);
+ }
+
+ /* Condense the target list. This makes all targets absolute. */
+ SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
+ FALSE, pool, iterpool));
+
+ /* No targets means nothing to commit, so just return. */
+ if (base_abspath == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(rel_targets != NULL);
+
+ /* If we calculated only a base and no relative targets, this
+ must mean that we are being asked to commit (effectively) a
+ single path. */
+ if (rel_targets->nelts == 0)
+ APR_ARRAY_PUSH(rel_targets, const char *) = "";
+
+ if (include_file_externals || include_dir_externals)
+ {
+ if (depth != svn_depth_unknown && depth != svn_depth_infinity)
+ {
+ /* All targets after this will be handled as depth empty */
+ depth_empty_after = rel_targets->nelts;
+ }
+
+ SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath,
+ include_file_externals,
+ include_dir_externals,
+ depth, ctx,
+ pool, pool));
+ }
+
+ SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath,
+ rel_targets, pool, iterpool));
+
+ locks_obtained = apr_array_make(pool, lock_targets->nelts,
+ sizeof(const char *));
+
+ for (i = 0; i < lock_targets->nelts; i++)
+ {
+ const char *lock_root;
+ const char *target = APR_ARRAY_IDX(lock_targets, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ cmt_err = svn_error_trace(
+ svn_wc__acquire_write_lock(&lock_root, ctx->wc_ctx, target,
+ FALSE, pool, iterpool));
+
+ if (cmt_err)
+ goto cleanup;
+
+ APR_ARRAY_PUSH(locks_obtained, const char *) = lock_root;
+ }
+
+ /* Determine prefix to strip from the commit notify messages */
+ SVN_ERR(svn_dirent_get_absolute(&current_abspath, "", pool));
+ notify_prefix = svn_dirent_get_longest_ancestor(current_abspath,
+ base_abspath,
+ pool);
+
+ /* If a non-recursive commit is desired, do not allow a deleted directory
+ as one of the targets. */
+ if (depth != svn_depth_infinity && ! commit_as_operations)
+ for (i = 0; i < rel_targets->nelts; i++)
+ {
+ const char *relpath = APR_ARRAY_IDX(rel_targets, i, const char *);
+ const char *target_abspath;
+
+ svn_pool_clear(iterpool);
+
+ target_abspath = svn_dirent_join(base_abspath, relpath, iterpool);
+
+ cmt_err = svn_error_trace(
+ check_nonrecursive_dir_delete(ctx->wc_ctx, target_abspath,
+ depth, iterpool));
+
+ if (cmt_err)
+ goto cleanup;
+ }
+
+ /* Crawl the working copy for commit items. */
+ {
+ struct check_url_kind_baton cukb;
+
+ /* Prepare for when we have a copy containing not-present nodes. */
+ cukb.pool = iterpool;
+ cukb.session = NULL; /* ### Can we somehow reuse session? */
+ cukb.repos_root_url = NULL;
+ cukb.ctx = ctx;
+
+ cmt_err = svn_error_trace(
+ svn_client__harvest_committables(&committables,
+ &lock_tokens,
+ base_abspath,
+ rel_targets,
+ depth_empty_after,
+ depth,
+ ! keep_locks,
+ changelists,
+ check_url_kind,
+ &cukb,
+ ctx,
+ pool,
+ iterpool));
+
+ svn_pool_clear(iterpool);
+ }
+
+ if (cmt_err)
+ goto cleanup;
+
+ if (apr_hash_count(committables->by_repository) == 0)
+ {
+ goto cleanup; /* Nothing to do */
+ }
+ else if (apr_hash_count(committables->by_repository) > 1)
+ {
+ cmt_err = svn_error_create(
+ SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Commit can only commit to a single repository at a time.\n"
+ "Are all targets part of the same working copy?"));
+ goto cleanup;
+ }
+
+ {
+ apr_hash_index_t *hi = apr_hash_first(iterpool,
+ committables->by_repository);
+
+ commit_items = svn__apr_hash_index_val(hi);
+ }
+
+ /* If our array of targets contains only locks (and no actual file
+ or prop modifications), then we return here to avoid committing a
+ revision with no changes. */
+ {
+ svn_boolean_t found_changed_path = FALSE;
+
+ for (i = 0; i < commit_items->nelts; ++i)
+ {
+ svn_client_commit_item3_t *item =
+ APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+
+ if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
+ {
+ found_changed_path = TRUE;
+ break;
+ }
+ }
+
+ if (!found_changed_path)
+ goto cleanup;
+ }
+
+ /* For every target that was moved verify that both halves of the
+ * move are part of the commit. */
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item =
+ APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+
+ svn_pool_clear(iterpool);
+
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE)
+ {
+ /* ### item->moved_from_abspath contains the move origin */
+ const char *moved_from_abspath;
+ const char *delete_op_root_abspath;
+
+ cmt_err = svn_error_trace(svn_wc__node_was_moved_here(
+ &moved_from_abspath,
+ &delete_op_root_abspath,
+ ctx->wc_ctx, item->path,
+ iterpool, iterpool));
+ if (cmt_err)
+ goto cleanup;
+
+ if (moved_from_abspath && delete_op_root_abspath &&
+ strcmp(moved_from_abspath, delete_op_root_abspath) == 0)
+
+ {
+ svn_boolean_t found_delete_half =
+ (svn_hash_gets(committables->by_path, delete_op_root_abspath)
+ != NULL);
+
+ if (!found_delete_half)
+ {
+ const char *delete_half_parent_abspath;
+
+ /* The delete-half isn't in the commit target list.
+ * However, it might itself be the child of a deleted node,
+ * either because of another move or a deletion.
+ *
+ * For example, consider: mv A/B B; mv B/C C; commit;
+ * C's moved-from A/B/C is a child of the deleted A/B.
+ * A/B/C does not appear in the commit target list, but
+ * A/B does appear.
+ * (Note that moved-from information is always stored
+ * relative to the BASE tree, so we have 'C moved-from
+ * A/B/C', not 'C moved-from B/C'.)
+ *
+ * An example involving a move and a delete would be:
+ * mv A/B C; rm A; commit;
+ * Now C is moved-from A/B which does not appear in the
+ * commit target list, but A does appear.
+ */
+
+ /* Scan upwards for a deletion op-root from the
+ * delete-half's parent directory. */
+ delete_half_parent_abspath =
+ svn_dirent_dirname(delete_op_root_abspath, iterpool);
+ if (strcmp(delete_op_root_abspath,
+ delete_half_parent_abspath) != 0)
+ {
+ const char *parent_delete_op_root_abspath;
+
+ cmt_err = svn_error_trace(
+ svn_wc__node_get_deleted_ancestor(
+ &parent_delete_op_root_abspath,
+ ctx->wc_ctx, delete_half_parent_abspath,
+ iterpool, iterpool));
+ if (cmt_err)
+ goto cleanup;
+
+ if (parent_delete_op_root_abspath)
+ found_delete_half =
+ (svn_hash_gets(committables->by_path,
+ parent_delete_op_root_abspath)
+ != NULL);
+ }
+ }
+
+ if (!found_delete_half)
+ {
+ cmt_err = svn_error_createf(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Cannot commit '%s' because it was moved from "
+ "'%s' which is not part of the commit; both "
+ "sides of the move must be committed together"),
+ svn_dirent_local_style(item->path, iterpool),
+ svn_dirent_local_style(delete_op_root_abspath,
+ iterpool));
+ goto cleanup;
+ }
+ }
+ }
+
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ {
+ const char *moved_to_abspath;
+ const char *copy_op_root_abspath;
+
+ cmt_err = svn_error_trace(svn_wc__node_was_moved_away(
+ &moved_to_abspath,
+ &copy_op_root_abspath,
+ ctx->wc_ctx, item->path,
+ iterpool, iterpool));
+ if (cmt_err)
+ goto cleanup;
+
+ if (moved_to_abspath && copy_op_root_abspath &&
+ strcmp(moved_to_abspath, copy_op_root_abspath) == 0 &&
+ svn_hash_gets(committables->by_path, copy_op_root_abspath)
+ == NULL)
+ {
+ cmt_err = svn_error_createf(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Cannot commit '%s' because it was moved to '%s' "
+ "which is not part of the commit; both sides of "
+ "the move must be committed together"),
+ svn_dirent_local_style(item->path, iterpool),
+ svn_dirent_local_style(copy_op_root_abspath,
+ iterpool));
+ goto cleanup;
+ }
+ }
+ }
+
+ /* Go get a log message. If an error occurs, or no log message is
+ specified, abort the operation. */
+ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
+ {
+ const char *tmp_file;
+ cmt_err = svn_error_trace(
+ svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
+ ctx, pool));
+
+ if (cmt_err || (! log_msg))
+ goto cleanup;
+ }
+ else
+ log_msg = "";
+
+ /* Sort and condense our COMMIT_ITEMS. */
+ cmt_err = svn_error_trace(svn_client__condense_commit_items(&base_url,
+ commit_items,
+ pool));
+
+ if (cmt_err)
+ goto cleanup;
+
+ /* Collect our lock tokens with paths relative to base_url. */
+ cmt_err = svn_error_trace(collect_lock_tokens(&lock_tokens, lock_tokens,
+ base_url, pool));
+
+ if (cmt_err)
+ goto cleanup;
+
+ cb.original_callback = commit_callback;
+ cb.original_baton = commit_baton;
+ cb.info = &commit_info;
+ cb.pool = pool;
+
+ /* Get the RA editor from the first lock target, rather than BASE_ABSPATH.
+ * When committing from multiple WCs, BASE_ABSPATH might be an unrelated
+ * parent of nested working copies. We don't support commits to multiple
+ * repositories so using the first WC to get the RA session is safe. */
+ cmt_err = svn_error_trace(
+ svn_client__open_ra_session_internal(&ra_session, NULL, base_url,
+ APR_ARRAY_IDX(lock_targets,
+ 0,
+ const char *),
+ commit_items,
+ TRUE, TRUE, ctx,
+ pool, pool));
+
+ if (cmt_err)
+ goto cleanup;
+
+ cmt_err = svn_error_trace(
+ get_ra_editor(&editor, &edit_baton, ra_session, ctx,
+ log_msg, commit_items, revprop_table,
+ lock_tokens, keep_locks, capture_commit_info,
+ &cb, pool));
+
+ if (cmt_err)
+ goto cleanup;
+
+ /* Make a note that we have a commit-in-progress. */
+ commit_in_progress = TRUE;
+
+ /* We'll assume that, once we pass this point, we are going to need to
+ * sleep for timestamps. Really, we may not need to do unless and until
+ * we reach the point where we post-commit 'bump' the WC metadata. */
+ timestamp_sleep = TRUE;
+
+ /* Perform the commit. */
+ cmt_err = svn_error_trace(
+ svn_client__do_commit(base_url, commit_items, editor, edit_baton,
+ notify_prefix, &sha1_checksums, ctx, pool,
+ iterpool));
+
+ /* Handle a successful commit. */
+ if ((! cmt_err)
+ || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
+ {
+ svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool);
+
+ /* Make a note that our commit is finished. */
+ commit_in_progress = FALSE;
+
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item
+ = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+
+ svn_pool_clear(iterpool);
+ bump_err = post_process_commit_item(
+ queue, item, ctx->wc_ctx,
+ keep_changelists, keep_locks, commit_as_operations,
+ svn_hash_gets(sha1_checksums, item->path),
+ iterpool);
+ if (bump_err)
+ goto cleanup;
+ }
+
+ SVN_ERR_ASSERT(commit_info);
+ bump_err = svn_wc_process_committed_queue2(
+ queue, ctx->wc_ctx,
+ commit_info->revision,
+ commit_info->date,
+ commit_info->author,
+ ctx->cancel_func, ctx->cancel_baton,
+ iterpool);
+ }
+
+ cleanup:
+ /* Sleep to ensure timestamp integrity. */
+ if (timestamp_sleep)
+ svn_io_sleep_for_timestamps(base_abspath, pool);
+
+ /* Abort the commit if it is still in progress. */
+ svn_pool_clear(iterpool); /* Close open handles before aborting */
+ if (commit_in_progress)
+ cmt_err = svn_error_compose_create(cmt_err,
+ editor->abort_edit(edit_baton, pool));
+
+ /* A bump error is likely to occur while running a working copy log file,
+ explicitly unlocking and removing temporary files would be wrong in
+ that case. A commit error (cmt_err) should only occur before any
+ attempt to modify the working copy, so it doesn't prevent explicit
+ clean-up. */
+ if (! bump_err)
+ {
+ /* Release all locks we obtained */
+ for (i = 0; i < locks_obtained->nelts; i++)
+ {
+ const char *lock_root = APR_ARRAY_IDX(locks_obtained, i,
+ const char *);
+
+ svn_pool_clear(iterpool);
+
+ unlock_err = svn_error_compose_create(
+ svn_wc__release_write_lock(ctx->wc_ctx, lock_root,
+ iterpool),
+ unlock_err);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err,
+ pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_client/commit_util.c b/contrib/subversion/subversion/libsvn_client/commit_util.c
new file mode 100644
index 0000000..1e2c50c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/commit_util.c
@@ -0,0 +1,1981 @@
+/*
+ * commit_util.c: Driver for the WC commit process.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_md5.h>
+
+#include "client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_iter.h"
+#include "svn_hash.h"
+
+#include <assert.h>
+#include <stdlib.h> /* for qsort() */
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_client_private.h"
+
+/*** Uncomment this to turn on commit driver debugging. ***/
+/*
+#define SVN_CLIENT_COMMIT_DEBUG
+*/
+
+/* Wrap an RA error in a nicer error if one is available. */
+static svn_error_t *
+fixup_commit_error(const char *local_abspath,
+ const char *base_url,
+ const char *path,
+ svn_node_kind_t kind,
+ svn_error_t *err,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND
+ || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS
+ || err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE
+ || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND
+ || err->apr_err == SVN_ERR_RA_DAV_ALREADY_EXISTS
+ || svn_error_find_cause(err, SVN_ERR_RA_OUT_OF_DATE))
+ {
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ if (local_abspath)
+ notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_failed_out_of_date,
+ scratch_pool);
+ else
+ notify = svn_wc_create_notify_url(
+ svn_path_url_add_component2(base_url, path,
+ scratch_pool),
+ svn_wc_notify_failed_out_of_date,
+ scratch_pool);
+
+ notify->kind = kind;
+ notify->err = err;
+
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ return svn_error_createf(SVN_ERR_WC_NOT_UP_TO_DATE, err,
+ (kind == svn_node_dir
+ ? _("Directory '%s' is out of date")
+ : _("File '%s' is out of date")),
+ local_abspath
+ ? svn_dirent_local_style(local_abspath,
+ scratch_pool)
+ : svn_path_url_add_component2(base_url,
+ path,
+ scratch_pool));
+ }
+ else if (svn_error_find_cause(err, SVN_ERR_FS_NO_LOCK_TOKEN)
+ || err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH
+ || err->apr_err == SVN_ERR_RA_NOT_LOCKED)
+ {
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ if (local_abspath)
+ notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_failed_locked,
+ scratch_pool);
+ else
+ notify = svn_wc_create_notify_url(
+ svn_path_url_add_component2(base_url, path,
+ scratch_pool),
+ svn_wc_notify_failed_locked,
+ scratch_pool);
+
+ notify->kind = kind;
+ notify->err = err;
+
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ return svn_error_createf(SVN_ERR_CLIENT_NO_LOCK_TOKEN, err,
+ (kind == svn_node_dir
+ ? _("Directory '%s' is locked in another working copy")
+ : _("File '%s' is locked in another working copy")),
+ local_abspath
+ ? svn_dirent_local_style(local_abspath,
+ scratch_pool)
+ : svn_path_url_add_component2(base_url,
+ path,
+ scratch_pool));
+ }
+ else if (svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN)
+ || err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE)
+ {
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ if (local_abspath)
+ notify = svn_wc_create_notify(
+ local_abspath,
+ svn_wc_notify_failed_forbidden_by_server,
+ scratch_pool);
+ else
+ notify = svn_wc_create_notify_url(
+ svn_path_url_add_component2(base_url, path,
+ scratch_pool),
+ svn_wc_notify_failed_forbidden_by_server,
+ scratch_pool);
+
+ notify->kind = kind;
+ notify->err = err;
+
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ return svn_error_createf(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER, err,
+ (kind == svn_node_dir
+ ? _("Changing directory '%s' is forbidden by the server")
+ : _("Changing file '%s' is forbidden by the server")),
+ local_abspath
+ ? svn_dirent_local_style(local_abspath,
+ scratch_pool)
+ : svn_path_url_add_component2(base_url,
+ path,
+ scratch_pool));
+ }
+ else
+ return err;
+}
+
+
+/*** Harvesting Commit Candidates ***/
+
+
+/* Add a new commit candidate (described by all parameters except
+ `COMMITTABLES') to the COMMITTABLES hash. All of the commit item's
+ members are allocated out of RESULT_POOL.
+
+ If the state flag specifies that a lock must be used, store the token in LOCK
+ in lock_tokens.
+ */
+static svn_error_t *
+add_committable(svn_client__committables_t *committables,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *repos_root_url,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ const char *copyfrom_relpath,
+ svn_revnum_t copyfrom_rev,
+ const char *moved_from_abspath,
+ apr_byte_t state_flags,
+ apr_hash_t *lock_tokens,
+ const svn_lock_t *lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *array;
+ svn_client_commit_item3_t *new_item;
+
+ /* Sanity checks. */
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(repos_root_url && repos_relpath);
+
+ /* ### todo: Get the canonical repository for this item, which will
+ be the real key for the COMMITTABLES hash, instead of the above
+ bogosity. */
+ array = svn_hash_gets(committables->by_repository, repos_root_url);
+
+ /* E-gads! There is no array for this repository yet! Oh, no
+ problem, we'll just create (and add to the hash) one. */
+ if (array == NULL)
+ {
+ array = apr_array_make(result_pool, 1, sizeof(new_item));
+ svn_hash_sets(committables->by_repository,
+ apr_pstrdup(result_pool, repos_root_url), array);
+ }
+
+ /* Now update pointer values, ensuring that their allocations live
+ in POOL. */
+ new_item = svn_client_commit_item3_create(result_pool);
+ new_item->path = apr_pstrdup(result_pool, local_abspath);
+ new_item->kind = kind;
+ new_item->url = svn_path_url_add_component2(repos_root_url,
+ repos_relpath,
+ result_pool);
+ new_item->revision = revision;
+ new_item->copyfrom_url = copyfrom_relpath
+ ? svn_path_url_add_component2(repos_root_url,
+ copyfrom_relpath,
+ result_pool)
+ : NULL;
+ new_item->copyfrom_rev = copyfrom_rev;
+ new_item->state_flags = state_flags;
+ new_item->incoming_prop_changes = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_t *));
+
+ if (moved_from_abspath)
+ new_item->moved_from_abspath = apr_pstrdup(result_pool,
+ moved_from_abspath);
+
+ /* Now, add the commit item to the array. */
+ APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item;
+
+ /* ... and to the hash. */
+ svn_hash_sets(committables->by_path, new_item->path, new_item);
+
+ if (lock
+ && lock_tokens
+ && (state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN))
+ {
+ svn_hash_sets(lock_tokens, new_item->url,
+ apr_pstrdup(result_pool, lock->token));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* If there is a commit item for PATH in COMMITTABLES, return it, else
+ return NULL. Use POOL for temporary allocation only. */
+static svn_client_commit_item3_t *
+look_up_committable(svn_client__committables_t *committables,
+ const char *path,
+ apr_pool_t *pool)
+{
+ return (svn_client_commit_item3_t *)
+ svn_hash_gets(committables->by_path, path);
+}
+
+/* Helper function for svn_client__harvest_committables().
+ * Determine whether we are within a tree-conflicted subtree of the
+ * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */
+static svn_error_t *
+bail_on_tree_conflicted_ancestor(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+
+ local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ while(svn_dirent_is_ancestor(wcroot_abspath, local_abspath))
+ {
+ svn_boolean_t tree_conflicted;
+
+ /* Check if the parent has tree conflicts */
+ SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
+ wc_ctx, local_abspath, scratch_pool));
+ if (tree_conflicted)
+ {
+ if (notify_func != NULL)
+ {
+ notify_func(notify_baton,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_failed_conflict,
+ scratch_pool),
+ scratch_pool);
+ }
+
+ return svn_error_createf(
+ SVN_ERR_WC_FOUND_CONFLICT, NULL,
+ _("Aborting commit: '%s' remains in tree-conflict"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+
+ /* Step outwards */
+ if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+ break;
+ else
+ local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Recursively search for commit candidates in (and under) LOCAL_ABSPATH using
+ WC_CTX and add those candidates to COMMITTABLES. If in ADDS_ONLY modes,
+ only new additions are recognized.
+
+ DEPTH indicates how to treat files and subdirectories of LOCAL_ABSPATH
+ when LOCAL_ABSPATH is itself a directory; see
+ svn_client__harvest_committables() for its behavior.
+
+ Lock tokens of candidates will be added to LOCK_TOKENS, if
+ non-NULL. JUST_LOCKED indicates whether to treat non-modified items with
+ lock tokens as commit candidates.
+
+ If COMMIT_RELPATH is not NULL, treat not-added nodes as if it is destined to
+ be added as COMMIT_RELPATH, and add 'deleted' entries to COMMITTABLES as
+ items to delete in the copy destination. COPY_MODE_ROOT should be set TRUE
+ for the first call for which COPY_MODE is TRUE, i.e. not for the
+ recursive calls, and FALSE otherwise.
+
+ If CHANGELISTS is non-NULL, it is a hash whose keys are const char *
+ changelist names used as a restrictive filter
+ when harvesting committables; that is, don't add a path to
+ COMMITTABLES unless it's a member of one of those changelists.
+
+ IS_EXPLICIT_TARGET should always be passed as TRUE, except when
+ harvest_committables() calls itself in recursion. This provides a way to
+ tell whether LOCAL_ABSPATH was an original target or whether it was reached
+ by recursing deeper into a dir target. (This is used to skip all file
+ externals that aren't explicit commit targets.)
+
+ DANGLERS is a hash table mapping const char* absolute paths of a parent
+ to a const char * absolute path of a child. See the comment about
+ danglers at the top of svn_client__harvest_committables().
+
+ If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see
+ if the user has cancelled the operation.
+
+ Any items added to COMMITTABLES are allocated from the COMITTABLES
+ hash pool, not POOL. SCRATCH_POOL is used for temporary allocations. */
+
+struct harvest_baton
+{
+ /* Static data */
+ const char *root_abspath;
+ svn_client__committables_t *committables;
+ apr_hash_t *lock_tokens;
+ const char *commit_relpath; /* Valid for the harvest root */
+ svn_depth_t depth;
+ svn_boolean_t just_locked;
+ apr_hash_t *changelists;
+ apr_hash_t *danglers;
+ svn_client__check_url_kind_t check_url_func;
+ void *check_url_baton;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+ svn_wc_context_t *wc_ctx;
+ apr_pool_t *result_pool;
+
+ /* Harvester state */
+ const char *skip_below_abspath; /* If non-NULL, skip everything below */
+};
+
+static svn_error_t *
+harvest_status_callback(void *status_baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+harvest_committables(const char *local_abspath,
+ svn_client__committables_t *committables,
+ apr_hash_t *lock_tokens,
+ const char *copy_mode_relpath,
+ svn_depth_t depth,
+ svn_boolean_t just_locked,
+ apr_hash_t *changelists,
+ apr_hash_t *danglers,
+ svn_client__check_url_kind_t check_url_func,
+ void *check_url_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct harvest_baton baton;
+
+ SVN_ERR_ASSERT((just_locked && lock_tokens) || !just_locked);
+
+ baton.root_abspath = local_abspath;
+ baton.committables = committables;
+ baton.lock_tokens = lock_tokens;
+ baton.commit_relpath = copy_mode_relpath;
+ baton.depth = depth;
+ baton.just_locked = just_locked;
+ baton.changelists = changelists;
+ baton.danglers = danglers;
+ baton.check_url_func = check_url_func;
+ baton.check_url_baton = check_url_baton;
+ baton.notify_func = notify_func;
+ baton.notify_baton = notify_baton;
+ baton.wc_ctx = wc_ctx;
+ baton.result_pool = result_pool;
+
+ baton.skip_below_abspath = NULL;
+
+ SVN_ERR(svn_wc_walk_status(wc_ctx,
+ local_abspath,
+ depth,
+ (copy_mode_relpath != NULL) /* get_all */,
+ FALSE /* no_ignore */,
+ FALSE /* ignore_text_mods */,
+ NULL /* ignore_patterns */,
+ harvest_status_callback,
+ &baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+harvest_not_present_for_copy(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_client__committables_t *committables,
+ const char *repos_root_url,
+ const char *commit_relpath,
+ svn_client__check_url_kind_t check_url_func,
+ void *check_url_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ /* A function to retrieve not present children would be nice to have */
+ SVN_ERR(svn_wc__node_get_children_of_working_node(
+ &children, wc_ctx, local_abspath, TRUE,
+ scratch_pool, iterpool));
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *this_abspath = APR_ARRAY_IDX(children, i, const char *);
+ const char *name = svn_dirent_basename(this_abspath, NULL);
+ const char *this_commit_relpath;
+ svn_boolean_t not_present;
+ svn_node_kind_t kind;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc__node_is_not_present(&not_present, NULL, NULL, wc_ctx,
+ this_abspath, FALSE, scratch_pool));
+
+ if (!not_present)
+ continue;
+
+ if (commit_relpath == NULL)
+ this_commit_relpath = NULL;
+ else
+ this_commit_relpath = svn_relpath_join(commit_relpath, name,
+ iterpool);
+
+ /* We should check if we should really add a delete operation */
+ if (check_url_func)
+ {
+ svn_revnum_t parent_rev;
+ const char *parent_repos_relpath;
+ const char *parent_repos_root_url;
+ const char *node_url;
+
+ /* Determine from what parent we would be the deleted child */
+ SVN_ERR(svn_wc__node_get_origin(
+ NULL, &parent_rev, &parent_repos_relpath,
+ &parent_repos_root_url, NULL, NULL,
+ wc_ctx,
+ svn_dirent_dirname(this_abspath,
+ scratch_pool),
+ FALSE, scratch_pool, scratch_pool));
+
+ node_url = svn_path_url_add_component2(
+ svn_path_url_add_component2(parent_repos_root_url,
+ parent_repos_relpath,
+ scratch_pool),
+ svn_dirent_basename(this_abspath, NULL),
+ iterpool);
+
+ SVN_ERR(check_url_func(check_url_baton, &kind,
+ node_url, parent_rev, iterpool));
+
+ if (kind == svn_node_none)
+ continue; /* This node can't be deleted */
+ }
+ else
+ SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, this_abspath,
+ TRUE, TRUE, scratch_pool));
+
+ SVN_ERR(add_committable(committables, this_abspath, kind,
+ repos_root_url,
+ this_commit_relpath,
+ SVN_INVALID_REVNUM,
+ NULL /* copyfrom_relpath */,
+ SVN_INVALID_REVNUM /* copyfrom_rev */,
+ NULL /* moved_from_abspath */,
+ SVN_CLIENT_COMMIT_ITEM_DELETE,
+ NULL, NULL,
+ result_pool, scratch_pool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_wc_status_func4_t */
+static svn_error_t *
+harvest_status_callback(void *status_baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ apr_byte_t state_flags = 0;
+ svn_revnum_t node_rev;
+ const char *cf_relpath = NULL;
+ svn_revnum_t cf_rev = SVN_INVALID_REVNUM;
+ svn_boolean_t matches_changelists;
+ svn_boolean_t is_added;
+ svn_boolean_t is_deleted;
+ svn_boolean_t is_replaced;
+ svn_boolean_t is_op_root;
+ svn_revnum_t original_rev;
+ const char *original_relpath;
+ svn_boolean_t copy_mode;
+
+ struct harvest_baton *baton = status_baton;
+ svn_boolean_t is_harvest_root =
+ (strcmp(baton->root_abspath, local_abspath) == 0);
+ svn_client__committables_t *committables = baton->committables;
+ const char *repos_root_url = status->repos_root_url;
+ const char *commit_relpath = NULL;
+ svn_boolean_t copy_mode_root = (baton->commit_relpath && is_harvest_root);
+ svn_boolean_t just_locked = baton->just_locked;
+ apr_hash_t *changelists = baton->changelists;
+ svn_wc_notify_func2_t notify_func = baton->notify_func;
+ void *notify_baton = baton->notify_baton;
+ svn_wc_context_t *wc_ctx = baton->wc_ctx;
+ apr_pool_t *result_pool = baton->result_pool;
+ const char *moved_from_abspath = NULL;
+
+ if (baton->commit_relpath)
+ commit_relpath = svn_relpath_join(
+ baton->commit_relpath,
+ svn_dirent_skip_ancestor(baton->root_abspath,
+ local_abspath),
+ scratch_pool);
+
+ copy_mode = (commit_relpath != NULL);
+
+ if (baton->skip_below_abspath
+ && svn_dirent_is_ancestor(baton->skip_below_abspath, local_abspath))
+ {
+ return SVN_NO_ERROR;
+ }
+ else
+ baton->skip_below_abspath = NULL; /* We have left the skip tree */
+
+ /* Return early for nodes that don't have a committable status */
+ switch (status->node_status)
+ {
+ case svn_wc_status_unversioned:
+ case svn_wc_status_ignored:
+ case svn_wc_status_external:
+ case svn_wc_status_none:
+ /* Unversioned nodes aren't committable, but are reported by the status
+ walker.
+ But if the unversioned node is the root of the walk, we have a user
+ error */
+ if (is_harvest_root)
+ return svn_error_createf(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+ case svn_wc_status_normal:
+ /* Status normal nodes aren't modified, so we don't have to commit them
+ when we perform a normal commit. But if a node is conflicted we want
+ to stop the commit and if we are collecting lock tokens we want to
+ look further anyway.
+
+ When in copy mode we need to compare the revision of the node against
+ the parent node to copy mixed-revision base nodes properly */
+ if (!copy_mode && !status->conflicted
+ && !(just_locked && status->lock))
+ return SVN_NO_ERROR;
+ break;
+ default:
+ /* Fall through */
+ break;
+ }
+
+ /* Early out if the item is already marked as committable. */
+ if (look_up_committable(committables, local_abspath, scratch_pool))
+ return SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT((copy_mode && commit_relpath)
+ || (! copy_mode && ! commit_relpath));
+ SVN_ERR_ASSERT((copy_mode_root && copy_mode) || ! copy_mode_root);
+
+ /* Save the result for reuse. */
+ matches_changelists = ((changelists == NULL)
+ || (status->changelist != NULL
+ && svn_hash_gets(changelists, status->changelist)
+ != NULL));
+
+ /* Early exit. */
+ if (status->kind != svn_node_dir && ! matches_changelists)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* If NODE is in our changelist, then examine it for conflicts. We
+ need to bail out if any conflicts exist.
+ The status walker checked for conflict marker removal. */
+ if (status->conflicted && matches_changelists)
+ {
+ if (notify_func != NULL)
+ {
+ notify_func(notify_baton,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_failed_conflict,
+ scratch_pool),
+ scratch_pool);
+ }
+
+ return svn_error_createf(
+ SVN_ERR_WC_FOUND_CONFLICT, NULL,
+ _("Aborting commit: '%s' remains in conflict"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else if (status->node_status == svn_wc_status_obstructed)
+ {
+ /* A node's type has changed before attempting to commit.
+ This also catches symlink vs non symlink changes */
+
+ if (notify_func != NULL)
+ {
+ notify_func(notify_baton,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_failed_obstruction,
+ scratch_pool),
+ scratch_pool);
+ }
+
+ return svn_error_createf(
+ SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Node '%s' has unexpectedly changed kind"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+
+ if (status->conflicted && status->kind == svn_node_unknown)
+ return SVN_NO_ERROR; /* Ignore delete-delete conflict */
+
+ /* Return error on unknown path kinds. We check both the entry and
+ the node itself, since a path might have changed kind since its
+ entry was written. */
+ SVN_ERR(svn_wc__node_get_commit_status(&is_added, &is_deleted,
+ &is_replaced,
+ &is_op_root,
+ &node_rev,
+ &original_rev, &original_relpath,
+ wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Hande file externals only when passed as explicit target. Note that
+ * svn_client_commit6() passes all committable externals in as explicit
+ * targets iff they count. */
+ if (status->file_external && !is_harvest_root)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ if (status->node_status == svn_wc_status_missing && matches_changelists)
+ {
+ /* Added files and directories must exist. See issue #3198. */
+ if (is_added && is_op_root)
+ {
+ if (notify_func != NULL)
+ {
+ notify_func(notify_baton,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_failed_missing,
+ scratch_pool),
+ scratch_pool);
+ }
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' is scheduled for addition, but is missing"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ if (is_deleted && !is_op_root /* && !is_added */)
+ return SVN_NO_ERROR; /* Not an operational delete and not an add. */
+
+ /* Check for the deletion case.
+ * We delete explicitly deleted nodes (duh!)
+ * We delete not-present children of copies
+ * We delete nodes that directly replace a node in its ancestor
+ */
+
+ if (is_deleted || is_replaced)
+ state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
+
+ /* Check for adds and copies */
+ if (is_added && is_op_root)
+ {
+ /* Root of local add or copy */
+ state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD;
+
+ if (original_relpath)
+ {
+ /* Root of copy */
+ state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
+ cf_relpath = original_relpath;
+ cf_rev = original_rev;
+
+ if (status->moved_from_abspath && !copy_mode)
+ {
+ state_flags |= SVN_CLIENT_COMMIT_ITEM_MOVED_HERE;
+ moved_from_abspath = status->moved_from_abspath;
+ }
+ }
+ }
+
+ /* Further copies may occur in copy mode. */
+ else if (copy_mode
+ && !(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
+ {
+ svn_revnum_t dir_rev = SVN_INVALID_REVNUM;
+
+ if (!copy_mode_root && !status->switched && !is_added)
+ SVN_ERR(svn_wc__node_get_base(NULL, &dir_rev, NULL, NULL, NULL, NULL,
+ wc_ctx, svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ FALSE /* ignore_enoent */,
+ FALSE /* show_hidden */,
+ scratch_pool, scratch_pool));
+
+ if (copy_mode_root || status->switched || node_rev != dir_rev)
+ {
+ state_flags |= (SVN_CLIENT_COMMIT_ITEM_ADD
+ | SVN_CLIENT_COMMIT_ITEM_IS_COPY);
+
+ if (status->copied)
+ {
+ /* Copy from original location */
+ cf_rev = original_rev;
+ cf_relpath = original_relpath;
+ }
+ else
+ {
+ /* Copy BASE location, to represent a mixed-rev or switch copy */
+ cf_rev = status->revision;
+ cf_relpath = status->repos_relpath;
+ }
+ }
+ }
+
+ if (!(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
+ {
+ svn_boolean_t text_mod = FALSE;
+ svn_boolean_t prop_mod = FALSE;
+
+ if (status->kind == svn_node_file)
+ {
+ /* Check for text modifications on files */
+ if ((state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
+ && ! (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
+ {
+ text_mod = TRUE; /* Local added files are always modified */
+ }
+ else
+ text_mod = (status->text_status != svn_wc_status_normal);
+ }
+
+ prop_mod = (status->prop_status != svn_wc_status_normal
+ && status->prop_status != svn_wc_status_none);
+
+ /* Set text/prop modification flags accordingly. */
+ if (text_mod)
+ state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
+ if (prop_mod)
+ state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
+ }
+
+ /* If the entry has a lock token and it is already a commit candidate,
+ or the caller wants unmodified locked items to be treated as
+ such, note this fact. */
+ if (status->lock && baton->lock_tokens && (state_flags || just_locked))
+ {
+ state_flags |= SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN;
+ }
+
+ /* Now, if this is something to commit, add it to our list. */
+ if (matches_changelists
+ && state_flags)
+ {
+ /* Finally, add the committable item. */
+ SVN_ERR(add_committable(committables, local_abspath,
+ status->kind,
+ repos_root_url,
+ copy_mode
+ ? commit_relpath
+ : status->repos_relpath,
+ copy_mode
+ ? SVN_INVALID_REVNUM
+ : node_rev,
+ cf_relpath,
+ cf_rev,
+ moved_from_abspath,
+ state_flags,
+ baton->lock_tokens, status->lock,
+ result_pool, scratch_pool));
+ }
+
+ /* Fetch lock tokens for descendants of deleted BASE nodes. */
+ if (matches_changelists
+ && (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ && !copy_mode
+ && SVN_IS_VALID_REVNUM(node_rev) /* && BASE-kind = dir */
+ && baton->lock_tokens)
+ {
+ apr_hash_t *local_relpath_tokens;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_wc__node_get_lock_tokens_recursive(
+ &local_relpath_tokens, wc_ctx, local_abspath,
+ result_pool, scratch_pool));
+
+ /* Add tokens to existing hash. */
+ for (hi = apr_hash_first(scratch_pool, local_relpath_tokens);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void * val;
+
+ apr_hash_this(hi, &key, &klen, &val);
+
+ apr_hash_set(baton->lock_tokens, key, klen, val);
+ }
+ }
+
+ /* Make sure we check for dangling children on additions
+
+ We perform this operation on the harvest root, and on roots caused by
+ changelist filtering.
+ */
+ if (matches_changelists
+ && (is_harvest_root || baton->changelists)
+ && state_flags
+ && is_added
+ && baton->danglers)
+ {
+ /* If a node is added, its parent must exist in the repository at the
+ time of committing */
+ apr_hash_t *danglers = baton->danglers;
+ svn_boolean_t parent_added;
+ const char *parent_abspath = svn_dirent_dirname(local_abspath,
+ scratch_pool);
+
+ /* First check if parent is already in the list of commits
+ (Common case for GUI clients that provide a list of commit targets) */
+ if (look_up_committable(committables, parent_abspath, scratch_pool))
+ parent_added = FALSE; /* Skip all expensive checks */
+ else
+ SVN_ERR(svn_wc__node_is_added(&parent_added, wc_ctx, parent_abspath,
+ scratch_pool));
+
+ if (parent_added)
+ {
+ const char *copy_root_abspath;
+ svn_boolean_t parent_is_copy;
+
+ /* The parent is added, so either it is a copy, or a locally added
+ * directory. In either case, we require the op-root of the parent
+ * to be part of the commit. See issue #4059. */
+ SVN_ERR(svn_wc__node_get_origin(&parent_is_copy, NULL, NULL, NULL,
+ NULL, &copy_root_abspath,
+ wc_ctx, parent_abspath,
+ FALSE, scratch_pool, scratch_pool));
+
+ if (parent_is_copy)
+ parent_abspath = copy_root_abspath;
+
+ if (!svn_hash_gets(danglers, parent_abspath))
+ {
+ svn_hash_sets(danglers, apr_pstrdup(result_pool, parent_abspath),
+ apr_pstrdup(result_pool, local_abspath));
+ }
+ }
+ }
+
+ if (is_deleted && !is_added)
+ {
+ /* Skip all descendants */
+ if (status->kind == svn_node_dir)
+ baton->skip_below_abspath = apr_pstrdup(baton->result_pool,
+ local_abspath);
+ return SVN_NO_ERROR;
+ }
+
+ /* Recursively handle each node according to depth, except when the
+ node is only being deleted, or is in an added tree (as added trees
+ use the normal commit handling). */
+ if (copy_mode && !is_added && !is_deleted && status->kind == svn_node_dir)
+ {
+ SVN_ERR(harvest_not_present_for_copy(wc_ctx, local_abspath, committables,
+ repos_root_url, commit_relpath,
+ baton->check_url_func,
+ baton->check_url_baton,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for handle_descendants */
+struct handle_descendants_baton
+{
+ svn_wc_context_t *wc_ctx;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+ svn_client__check_url_kind_t check_url_func;
+ void *check_url_baton;
+};
+
+/* Helper for the commit harvesters */
+static svn_error_t *
+handle_descendants(void *baton,
+ const void *key, apr_ssize_t klen, void *val,
+ apr_pool_t *pool)
+{
+ struct handle_descendants_baton *hdb = baton;
+ apr_array_header_t *commit_items = val;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item =
+ APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+ const apr_array_header_t *absent_descendants;
+ int j;
+
+ /* Is this a copy operation? */
+ if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
+ || ! item->copyfrom_url)
+ continue;
+
+ if (hdb->cancel_func)
+ SVN_ERR(hdb->cancel_func(hdb->cancel_baton));
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants,
+ hdb->wc_ctx, item->path,
+ iterpool, iterpool));
+
+ for (j = 0; j < absent_descendants->nelts; j++)
+ {
+ int k;
+ svn_boolean_t found_item = FALSE;
+ svn_node_kind_t kind;
+ const char *relpath = APR_ARRAY_IDX(absent_descendants, j,
+ const char *);
+ const char *local_abspath = svn_dirent_join(item->path, relpath,
+ iterpool);
+
+ /* If the path has a commit operation, we do nothing.
+ (It will be deleted by the operation) */
+ for (k = 0; k < commit_items->nelts; k++)
+ {
+ svn_client_commit_item3_t *cmt_item =
+ APR_ARRAY_IDX(commit_items, k, svn_client_commit_item3_t *);
+
+ if (! strcmp(cmt_item->path, local_abspath))
+ {
+ found_item = TRUE;
+ break;
+ }
+ }
+
+ if (found_item)
+ continue; /* We have an explicit delete or replace for this path */
+
+ /* ### Need a sub-iterpool? */
+
+ if (hdb->check_url_func)
+ {
+ const char *from_url = svn_path_url_add_component2(
+ item->copyfrom_url, relpath,
+ iterpool);
+
+ SVN_ERR(hdb->check_url_func(hdb->check_url_baton,
+ &kind, from_url, item->copyfrom_rev,
+ iterpool));
+
+ if (kind == svn_node_none)
+ continue; /* This node is already deleted */
+ }
+ else
+ kind = svn_node_unknown; /* 'Ok' for a delete of something */
+
+ {
+ /* Add a new commit item that describes the delete */
+ apr_pool_t *result_pool = commit_items->pool;
+ svn_client_commit_item3_t *new_item
+ = svn_client_commit_item3_create(result_pool);
+
+ new_item->path = svn_dirent_join(item->path, relpath,
+ result_pool);
+ new_item->kind = kind;
+ new_item->url = svn_path_url_add_component2(item->url, relpath,
+ result_pool);
+ new_item->revision = SVN_INVALID_REVNUM;
+ new_item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
+ new_item->incoming_prop_changes = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_t *));
+
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *)
+ = new_item;
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Allocate and initialize the COMMITTABLES structure from POOL.
+ */
+static void
+create_committables(svn_client__committables_t **committables,
+ apr_pool_t *pool)
+{
+ *committables = apr_palloc(pool, sizeof(**committables));
+
+ (*committables)->by_repository = apr_hash_make(pool);
+ (*committables)->by_path = apr_hash_make(pool);
+}
+
+svn_error_t *
+svn_client__harvest_committables(svn_client__committables_t **committables,
+ apr_hash_t **lock_tokens,
+ const char *base_dir_abspath,
+ const apr_array_header_t *targets,
+ int depth_empty_start,
+ svn_depth_t depth,
+ svn_boolean_t just_locked,
+ const apr_array_header_t *changelists,
+ svn_client__check_url_kind_t check_url_func,
+ void *check_url_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *changelist_hash = NULL;
+ struct handle_descendants_baton hdb;
+ apr_hash_index_t *hi;
+
+ /* It's possible that one of the named targets has a parent that is
+ * itself scheduled for addition or replacement -- that is, the
+ * parent is not yet versioned in the repository. This is okay, as
+ * long as the parent itself is part of this same commit, either
+ * directly, or by virtue of a grandparent, great-grandparent, etc,
+ * being part of the commit.
+ *
+ * Since we don't know what's included in the commit until we've
+ * harvested all the targets, we can't reliably check this as we
+ * go. So in `danglers', we record named targets whose parents
+ * do not yet exist in the repository. Then after harvesting the total
+ * commit group, we check to make sure those parents are included.
+ *
+ * Each key of danglers is a parent which does not exist in the
+ * repository. The (const char *) value is one of that parent's
+ * children which is named as part of the commit; the child is
+ * included only to make a better error message.
+ *
+ * (The reason we don't bother to check unnamed -- i.e, implicit --
+ * targets is that they can only join the commit if their parents
+ * did too, so this situation can't arise for them.)
+ */
+ apr_hash_t *danglers = apr_hash_make(scratch_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath));
+
+ /* Create the COMMITTABLES structure. */
+ create_committables(committables, result_pool);
+
+ /* And the LOCK_TOKENS dito. */
+ *lock_tokens = apr_hash_make(result_pool);
+
+ /* If we have a list of changelists, convert that into a hash with
+ changelist keys. */
+ if (changelists && changelists->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists,
+ scratch_pool));
+
+ for (i = 0; i < targets->nelts; ++i)
+ {
+ const char *target_abspath;
+
+ svn_pool_clear(iterpool);
+
+ /* Add the relative portion to the base abspath. */
+ target_abspath = svn_dirent_join(base_dir_abspath,
+ APR_ARRAY_IDX(targets, i, const char *),
+ iterpool);
+
+ /* Handle our TARGET. */
+ /* Make sure this isn't inside a working copy subtree that is
+ * marked as tree-conflicted. */
+ SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath,
+ ctx->notify_func2,
+ ctx->notify_baton2,
+ iterpool));
+
+ /* Are the remaining items externals with depth empty? */
+ if (i == depth_empty_start)
+ depth = svn_depth_empty;
+
+ SVN_ERR(harvest_committables(target_abspath,
+ *committables, *lock_tokens,
+ NULL /* COPY_MODE_RELPATH */,
+ depth, just_locked, changelist_hash,
+ danglers,
+ check_url_func, check_url_baton,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ ctx->wc_ctx, result_pool, iterpool));
+ }
+
+ hdb.wc_ctx = ctx->wc_ctx;
+ hdb.cancel_func = ctx->cancel_func;
+ hdb.cancel_baton = ctx->cancel_baton;
+ hdb.check_url_func = check_url_func;
+ hdb.check_url_baton = check_url_baton;
+
+ SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository,
+ handle_descendants, &hdb, iterpool));
+
+ /* Make sure that every path in danglers is part of the commit. */
+ for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi))
+ {
+ const char *dangling_parent = svn__apr_hash_index_key(hi);
+
+ svn_pool_clear(iterpool);
+
+ if (! look_up_committable(*committables, dangling_parent, iterpool))
+ {
+ const char *dangling_child = svn__apr_hash_index_val(hi);
+
+ if (ctx->notify_func2 != NULL)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(dangling_child,
+ svn_wc_notify_failed_no_parent,
+ scratch_pool);
+
+ ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
+ }
+
+ return svn_error_createf(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not known to exist in the repository "
+ "and is not part of the commit, "
+ "yet its child '%s' is part of the commit"),
+ /* Probably one or both of these is an entry, but
+ safest to local_stylize just in case. */
+ svn_dirent_local_style(dangling_parent, iterpool),
+ svn_dirent_local_style(dangling_child, iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+struct copy_committables_baton
+{
+ svn_client_ctx_t *ctx;
+ svn_client__committables_t *committables;
+ apr_pool_t *result_pool;
+ svn_client__check_url_kind_t check_url_func;
+ void *check_url_baton;
+};
+
+static svn_error_t *
+harvest_copy_committables(void *baton, void *item, apr_pool_t *pool)
+{
+ struct copy_committables_baton *btn = baton;
+ svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item;
+ const char *repos_root_url;
+ const char *commit_relpath;
+ struct handle_descendants_baton hdb;
+
+ /* Read the entry for this SRC. */
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
+
+ SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL,
+ btn->ctx->wc_ctx,
+ pair->src_abspath_or_url,
+ pool, pool));
+
+ commit_relpath = svn_uri_skip_ancestor(repos_root_url,
+ pair->dst_abspath_or_url, pool);
+
+ /* Handle this SRC. */
+ SVN_ERR(harvest_committables(pair->src_abspath_or_url,
+ btn->committables, NULL,
+ commit_relpath,
+ svn_depth_infinity,
+ FALSE, /* JUST_LOCKED */
+ NULL /* changelists */,
+ NULL,
+ btn->check_url_func,
+ btn->check_url_baton,
+ btn->ctx->cancel_func,
+ btn->ctx->cancel_baton,
+ btn->ctx->notify_func2,
+ btn->ctx->notify_baton2,
+ btn->ctx->wc_ctx, btn->result_pool, pool));
+
+ hdb.wc_ctx = btn->ctx->wc_ctx;
+ hdb.cancel_func = btn->ctx->cancel_func;
+ hdb.cancel_baton = btn->ctx->cancel_baton;
+ hdb.check_url_func = btn->check_url_func;
+ hdb.check_url_baton = btn->check_url_baton;
+
+ SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository,
+ handle_descendants, &hdb, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_client__get_copy_committables(svn_client__committables_t **committables,
+ const apr_array_header_t *copy_pairs,
+ svn_client__check_url_kind_t check_url_func,
+ void *check_url_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_committables_baton btn;
+
+ /* Create the COMMITTABLES structure. */
+ create_committables(committables, result_pool);
+
+ btn.ctx = ctx;
+ btn.committables = *committables;
+ btn.result_pool = result_pool;
+
+ btn.check_url_func = check_url_func;
+ btn.check_url_baton = check_url_baton;
+
+ /* For each copy pair, harvest the committables for that pair into the
+ committables hash. */
+ return svn_iter_apr_array(NULL, copy_pairs,
+ harvest_copy_committables, &btn, scratch_pool);
+}
+
+
+int svn_client__sort_commit_item_urls(const void *a, const void *b)
+{
+ const svn_client_commit_item3_t *item1
+ = *((const svn_client_commit_item3_t * const *) a);
+ const svn_client_commit_item3_t *item2
+ = *((const svn_client_commit_item3_t * const *) b);
+ return svn_path_compare_paths(item1->url, item2->url);
+}
+
+
+
+svn_error_t *
+svn_client__condense_commit_items(const char **base_url,
+ apr_array_header_t *commit_items,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *ci = commit_items; /* convenience */
+ const char *url;
+ svn_client_commit_item3_t *item, *last_item = NULL;
+ int i;
+
+ SVN_ERR_ASSERT(ci && ci->nelts);
+
+ /* Sort our commit items by their URLs. */
+ qsort(ci->elts, ci->nelts,
+ ci->elt_size, svn_client__sort_commit_item_urls);
+
+ /* Loop through the URLs, finding the longest usable ancestor common
+ to all of them, and making sure there are no duplicate URLs. */
+ for (i = 0; i < ci->nelts; i++)
+ {
+ item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
+ url = item->url;
+
+ if ((last_item) && (strcmp(last_item->url, url) == 0))
+ return svn_error_createf
+ (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL,
+ _("Cannot commit both '%s' and '%s' as they refer to the same URL"),
+ svn_dirent_local_style(item->path, pool),
+ svn_dirent_local_style(last_item->path, pool));
+
+ /* In the first iteration, our BASE_URL is just our only
+ encountered commit URL to date. After that, we find the
+ longest ancestor between the current BASE_URL and the current
+ commit URL. */
+ if (i == 0)
+ *base_url = apr_pstrdup(pool, url);
+ else
+ *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool);
+
+ /* If our BASE_URL is itself a to-be-committed item, and it is
+ anything other than an already-versioned directory with
+ property mods, we'll call its parent directory URL the
+ BASE_URL. Why? Because we can't have a file URL as our base
+ -- period -- and all other directory operations (removal,
+ addition, etc.) require that we open that directory's parent
+ dir first. */
+ /* ### I don't understand the strlen()s here, hmmm. -kff */
+ if ((strlen(*base_url) == strlen(url))
+ && (! ((item->kind == svn_node_dir)
+ && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS)))
+ *base_url = svn_uri_dirname(*base_url, pool);
+
+ /* Stash our item here for the next iteration. */
+ last_item = item;
+ }
+
+ /* Now that we've settled on a *BASE_URL, go hack that base off
+ of all of our URLs and store it as session_relpath. */
+ for (i = 0; i < ci->nelts; i++)
+ {
+ svn_client_commit_item3_t *this_item
+ = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
+
+ this_item->session_relpath = svn_uri_skip_ancestor(*base_url,
+ this_item->url, pool);
+ }
+#ifdef SVN_CLIENT_COMMIT_DEBUG
+ /* ### TEMPORARY CODE ### */
+ SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url));
+ SVN_DBG((" FLAGS REV REL-URL (COPY-URL)\n"));
+ for (i = 0; i < ci->nelts; i++)
+ {
+ svn_client_commit_item3_t *this_item
+ = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
+ char flags[6];
+ flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
+ ? 'a' : '-';
+ flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ ? 'd' : '-';
+ flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
+ ? 't' : '-';
+ flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
+ ? 'p' : '-';
+ flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
+ ? 'c' : '-';
+ flags[5] = '\0';
+ SVN_DBG((" %s %6ld '%s' (%s)\n",
+ flags,
+ this_item->revision,
+ this_item->url ? this_item->url : "",
+ this_item->copyfrom_url ? this_item->copyfrom_url : "none"));
+ }
+#endif /* SVN_CLIENT_COMMIT_DEBUG */
+
+ return SVN_NO_ERROR;
+}
+
+
+struct file_mod_t
+{
+ const svn_client_commit_item3_t *item;
+ void *file_baton;
+};
+
+
+/* A baton for use while driving a path-based editor driver for commit */
+struct item_commit_baton
+{
+ const svn_delta_editor_t *editor; /* commit editor */
+ void *edit_baton; /* commit editor's baton */
+ apr_hash_t *file_mods; /* hash: path->file_mod_t */
+ const char *notify_path_prefix; /* notification path prefix
+ (NULL is okay, else abs path) */
+ svn_client_ctx_t *ctx; /* client context baton */
+ apr_hash_t *commit_items; /* the committables */
+ const char *base_url; /* The session url for the commit */
+};
+
+
+/* Drive CALLBACK_BATON->editor with the change described by the item in
+ * CALLBACK_BATON->commit_items that is keyed by PATH. If the change
+ * includes a text mod, however, call the editor's file_open() function
+ * but do not send the text mod to the editor; instead, add a mapping of
+ * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods.
+ *
+ * Before driving the editor, call the cancellation and notification
+ * callbacks in CALLBACK_BATON->ctx, if present.
+ *
+ * This implements svn_delta_path_driver_cb_func_t. */
+static svn_error_t *
+do_item_commit(void **dir_baton,
+ void *parent_baton,
+ void *callback_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct item_commit_baton *icb = callback_baton;
+ const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items,
+ path);
+ svn_node_kind_t kind = item->kind;
+ void *file_baton = NULL;
+ apr_pool_t *file_pool = NULL;
+ const svn_delta_editor_t *editor = icb->editor;
+ apr_hash_t *file_mods = icb->file_mods;
+ svn_client_ctx_t *ctx = icb->ctx;
+ svn_error_t *err;
+ const char *local_abspath = NULL;
+
+ /* Do some initializations. */
+ *dir_baton = NULL;
+ if (item->kind != svn_node_none && item->path)
+ {
+ /* We always get an absolute path, see svn_client_commit_item3_t. */
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
+ local_abspath = item->path;
+ }
+
+ /* If this is a file with textual mods, we'll be keeping its baton
+ around until the end of the commit. So just lump its memory into
+ a single, big, all-the-file-batons-in-here pool. Otherwise, we
+ can just use POOL, and trust our caller to clean that mess up. */
+ if ((kind == svn_node_file)
+ && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
+ file_pool = apr_hash_pool_get(file_mods);
+ else
+ file_pool = pool;
+
+ /* Call the cancellation function. */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ /* Validation. */
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
+ {
+ if (! item->copyfrom_url)
+ return svn_error_createf
+ (SVN_ERR_BAD_URL, NULL,
+ _("Commit item '%s' has copy flag but no copyfrom URL"),
+ svn_dirent_local_style(path, pool));
+ if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev))
+ return svn_error_createf
+ (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Commit item '%s' has copy flag but an invalid revision"),
+ svn_dirent_local_style(path, pool));
+ }
+
+ /* If a feedback table was supplied by the application layer,
+ describe what we're about to do to this item. */
+ if (ctx->notify_func2 && item->path)
+ {
+ const char *npath = item->path;
+ svn_wc_notify_t *notify;
+
+ if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
+ {
+ /* We don't print the "(bin)" notice for binary files when
+ replacing, only when adding. So we don't bother to get
+ the mime-type here. */
+ if (item->copyfrom_url)
+ notify = svn_wc_create_notify(npath,
+ svn_wc_notify_commit_copied_replaced,
+ pool);
+ else
+ notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced,
+ pool);
+
+ }
+ else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ {
+ notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted,
+ pool);
+ }
+ else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
+ {
+ if (item->copyfrom_url)
+ notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied,
+ pool);
+ else
+ notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added,
+ pool);
+
+ if (item->kind == svn_node_file)
+ {
+ const svn_string_t *propval;
+
+ SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath,
+ SVN_PROP_MIME_TYPE, pool, pool));
+
+ if (propval)
+ notify->mime_type = propval->data;
+ }
+ }
+ else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
+ || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS))
+ {
+ notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified,
+ pool);
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
+ notify->content_state = svn_wc_notify_state_changed;
+ else
+ notify->content_state = svn_wc_notify_state_unchanged;
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
+ notify->prop_state = svn_wc_notify_state_changed;
+ else
+ notify->prop_state = svn_wc_notify_state_unchanged;
+ }
+ else
+ notify = NULL;
+
+ if (notify)
+ {
+ notify->kind = item->kind;
+ notify->path_prefix = icb->notify_path_prefix;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+ }
+
+ /* If this item is supposed to be deleted, do so. */
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ {
+ SVN_ERR_ASSERT(parent_baton);
+ err = editor->delete_entry(path, item->revision,
+ parent_baton, pool);
+
+ if (err)
+ goto fixup_error;
+ }
+
+ /* If this item is supposed to be added, do so. */
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
+ {
+ if (kind == svn_node_file)
+ {
+ SVN_ERR_ASSERT(parent_baton);
+ err = editor->add_file(
+ path, parent_baton, item->copyfrom_url,
+ item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
+ file_pool, &file_baton);
+ }
+ else /* May be svn_node_none when adding parent dirs for a copy. */
+ {
+ SVN_ERR_ASSERT(parent_baton);
+ err = editor->add_directory(
+ path, parent_baton, item->copyfrom_url,
+ item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
+ pool, dir_baton);
+ }
+
+ if (err)
+ goto fixup_error;
+
+ /* Set other prop-changes, if available in the baton */
+ if (item->outgoing_prop_changes)
+ {
+ svn_prop_t *prop;
+ apr_array_header_t *prop_changes = item->outgoing_prop_changes;
+ int ctr;
+ for (ctr = 0; ctr < prop_changes->nelts; ctr++)
+ {
+ prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *);
+ if (kind == svn_node_file)
+ {
+ err = editor->change_file_prop(file_baton, prop->name,
+ prop->value, pool);
+ }
+ else
+ {
+ err = editor->change_dir_prop(*dir_baton, prop->name,
+ prop->value, pool);
+ }
+
+ if (err)
+ goto fixup_error;
+ }
+ }
+ }
+
+ /* Now handle property mods. */
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
+ {
+ if (kind == svn_node_file)
+ {
+ if (! file_baton)
+ {
+ SVN_ERR_ASSERT(parent_baton);
+ err = editor->open_file(path, parent_baton,
+ item->revision,
+ file_pool, &file_baton);
+
+ if (err)
+ goto fixup_error;
+ }
+ }
+ else
+ {
+ if (! *dir_baton)
+ {
+ if (! parent_baton)
+ {
+ err = editor->open_root(icb->edit_baton, item->revision,
+ pool, dir_baton);
+ }
+ else
+ {
+ err = editor->open_directory(path, parent_baton,
+ item->revision,
+ pool, dir_baton);
+ }
+
+ if (err)
+ goto fixup_error;
+ }
+ }
+
+ /* When committing a directory that no longer exists in the
+ repository, a "not found" error does not occur immediately
+ upon opening the directory. It appears here during the delta
+ transmisssion. */
+ err = svn_wc_transmit_prop_deltas2(
+ ctx->wc_ctx, local_abspath, editor,
+ (kind == svn_node_dir) ? *dir_baton : file_baton, pool);
+
+ if (err)
+ goto fixup_error;
+
+ /* Make any additional client -> repository prop changes. */
+ if (item->outgoing_prop_changes)
+ {
+ svn_prop_t *prop;
+ int i;
+
+ for (i = 0; i < item->outgoing_prop_changes->nelts; i++)
+ {
+ prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i,
+ svn_prop_t *);
+ if (kind == svn_node_file)
+ {
+ err = editor->change_file_prop(file_baton, prop->name,
+ prop->value, pool);
+ }
+ else
+ {
+ err = editor->change_dir_prop(*dir_baton, prop->name,
+ prop->value, pool);
+ }
+
+ if (err)
+ goto fixup_error;
+ }
+ }
+ }
+
+ /* Finally, handle text mods (in that we need to open a file if it
+ hasn't already been opened, and we need to put the file baton in
+ our FILES hash). */
+ if ((kind == svn_node_file)
+ && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
+ {
+ struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod));
+
+ if (! file_baton)
+ {
+ SVN_ERR_ASSERT(parent_baton);
+ err = editor->open_file(path, parent_baton,
+ item->revision,
+ file_pool, &file_baton);
+
+ if (err)
+ goto fixup_error;
+ }
+
+ /* Add this file mod to the FILE_MODS hash. */
+ mod->item = item;
+ mod->file_baton = file_baton;
+ svn_hash_sets(file_mods, item->session_relpath, mod);
+ }
+ else if (file_baton)
+ {
+ /* Close any outstanding file batons that didn't get caught by
+ the "has local mods" conditional above. */
+ err = editor->close_file(file_baton, NULL, file_pool);
+
+ if (err)
+ goto fixup_error;
+ }
+
+ return SVN_NO_ERROR;
+
+fixup_error:
+ return svn_error_trace(fixup_commit_error(local_abspath,
+ icb->base_url,
+ path, kind,
+ err, ctx, pool));
+}
+
+svn_error_t *
+svn_client__do_commit(const char *base_url,
+ const apr_array_header_t *commit_items,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ const char *notify_path_prefix,
+ apr_hash_t **sha1_checksums,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *file_mods = apr_hash_make(scratch_pool);
+ apr_hash_t *items_hash = apr_hash_make(scratch_pool);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ int i;
+ struct item_commit_baton cb_baton;
+ apr_array_header_t *paths =
+ apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *));
+
+ /* Ditto for the checksums. */
+ if (sha1_checksums)
+ *sha1_checksums = apr_hash_make(result_pool);
+
+ /* Build a hash from our COMMIT_ITEMS array, keyed on the
+ relative paths (which come from the item URLs). And
+ keep an array of those decoded paths, too. */
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item =
+ APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+ const char *path = item->session_relpath;
+ svn_hash_sets(items_hash, path, item);
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ }
+
+ /* Setup the callback baton. */
+ cb_baton.editor = editor;
+ cb_baton.edit_baton = edit_baton;
+ cb_baton.file_mods = file_mods;
+ cb_baton.notify_path_prefix = notify_path_prefix;
+ cb_baton.ctx = ctx;
+ cb_baton.commit_items = items_hash;
+ cb_baton.base_url = base_url;
+
+ /* Drive the commit editor! */
+ SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
+ do_item_commit, &cb_baton, scratch_pool));
+
+ /* Transmit outstanding text deltas. */
+ for (hi = apr_hash_first(scratch_pool, file_mods);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ struct file_mod_t *mod = svn__apr_hash_index_val(hi);
+ const svn_client_commit_item3_t *item = mod->item;
+ const svn_checksum_t *new_text_base_md5_checksum;
+ const svn_checksum_t *new_text_base_sha1_checksum;
+ svn_boolean_t fulltext = FALSE;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ /* Transmit the entry. */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+ notify = svn_wc_create_notify(item->path,
+ svn_wc_notify_commit_postfix_txdelta,
+ iterpool);
+ notify->kind = svn_node_file;
+ notify->path_prefix = notify_path_prefix;
+ ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
+ }
+
+ /* If the node has no history, transmit full text */
+ if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
+ && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
+ fulltext = TRUE;
+
+ err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum,
+ &new_text_base_sha1_checksum,
+ ctx->wc_ctx, item->path,
+ fulltext, editor, mod->file_baton,
+ result_pool, iterpool);
+
+ if (err)
+ {
+ svn_pool_destroy(iterpool); /* Close tempfiles */
+ return svn_error_trace(fixup_commit_error(item->path,
+ base_url,
+ item->session_relpath,
+ svn_node_file,
+ err, ctx, scratch_pool));
+ }
+
+ if (sha1_checksums)
+ svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ /* Close the edit. */
+ return svn_error_trace(editor->close_edit(edit_baton, scratch_pool));
+}
+
+
+svn_error_t *
+svn_client__get_log_msg(const char **log_msg,
+ const char **tmp_file,
+ const apr_array_header_t *commit_items,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ if (ctx->log_msg_func3)
+ {
+ /* The client provided a callback function for the current API.
+ Forward the call to it directly. */
+ return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items,
+ ctx->log_msg_baton3, pool);
+ }
+ else if (ctx->log_msg_func2 || ctx->log_msg_func)
+ {
+ /* The client provided a pre-1.5 (or pre-1.3) API callback
+ function. Convert the commit_items list to the appropriate
+ type, and forward call to it. */
+ svn_error_t *err;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ apr_array_header_t *old_commit_items =
+ apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*));
+
+ int i;
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item =
+ APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+
+ if (ctx->log_msg_func2)
+ {
+ svn_client_commit_item2_t *old_item =
+ apr_pcalloc(scratch_pool, sizeof(*old_item));
+
+ old_item->path = item->path;
+ old_item->kind = item->kind;
+ old_item->url = item->url;
+ old_item->revision = item->revision;
+ old_item->copyfrom_url = item->copyfrom_url;
+ old_item->copyfrom_rev = item->copyfrom_rev;
+ old_item->state_flags = item->state_flags;
+ old_item->wcprop_changes = item->incoming_prop_changes;
+
+ APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) =
+ old_item;
+ }
+ else /* ctx->log_msg_func */
+ {
+ svn_client_commit_item_t *old_item =
+ apr_pcalloc(scratch_pool, sizeof(*old_item));
+
+ old_item->path = item->path;
+ old_item->kind = item->kind;
+ old_item->url = item->url;
+ /* The pre-1.3 API used the revision field for copyfrom_rev
+ and revision depeding of copyfrom_url. */
+ old_item->revision = item->copyfrom_url ?
+ item->copyfrom_rev : item->revision;
+ old_item->copyfrom_url = item->copyfrom_url;
+ old_item->state_flags = item->state_flags;
+ old_item->wcprop_changes = item->incoming_prop_changes;
+
+ APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) =
+ old_item;
+ }
+ }
+
+ if (ctx->log_msg_func2)
+ err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items,
+ ctx->log_msg_baton2, pool);
+ else
+ err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items,
+ ctx->log_msg_baton, pool);
+ svn_pool_destroy(scratch_pool);
+ return err;
+ }
+ else
+ {
+ /* No log message callback was provided by the client. */
+ *log_msg = "";
+ *tmp_file = NULL;
+ return SVN_NO_ERROR;
+ }
+}
+
+svn_error_t *
+svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out,
+ const apr_hash_t *revprop_table_in,
+ const char *log_msg,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_hash_t *new_revprop_table;
+ if (revprop_table_in)
+ {
+ if (svn_prop_has_svn_prop(revprop_table_in, pool))
+ return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("Standard properties can't be set "
+ "explicitly as revision properties"));
+ new_revprop_table = apr_hash_copy(pool, revprop_table_in);
+ }
+ else
+ {
+ new_revprop_table = apr_hash_make(pool);
+ }
+ svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG,
+ svn_string_create(log_msg, pool));
+ *revprop_table_out = new_revprop_table;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/compat_providers.c b/contrib/subversion/subversion/libsvn_client/compat_providers.c
new file mode 100644
index 0000000..ae53a15
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/compat_providers.c
@@ -0,0 +1,136 @@
+/*
+ * compat_providers.c: wrapper providers backwards compatibility
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_auth.h"
+#include "svn_client.h"
+
+void
+svn_client_get_simple_prompt_provider
+ (svn_auth_provider_object_t **provider,
+ svn_auth_simple_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool)
+{
+ svn_auth_get_simple_prompt_provider(provider, prompt_func, prompt_baton,
+ retry_limit, pool);
+}
+
+void
+svn_client_get_username_prompt_provider
+ (svn_auth_provider_object_t **provider,
+ svn_auth_username_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool)
+{
+ svn_auth_get_username_prompt_provider(provider, prompt_func, prompt_baton,
+ retry_limit, pool);
+}
+
+
+
+void svn_client_get_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_get_simple_provider2(provider, NULL, NULL, pool);
+}
+
+#if defined(WIN32) && !defined(__MINGW32__)
+void
+svn_client_get_windows_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_get_windows_simple_provider(provider, pool);
+}
+#endif /* WIN32 */
+
+void svn_client_get_username_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_get_username_provider(provider, pool);
+}
+
+void
+svn_client_get_ssl_server_trust_file_provider
+ (svn_auth_provider_object_t **provider, apr_pool_t *pool)
+{
+ svn_auth_get_ssl_server_trust_file_provider(provider, pool);
+}
+
+void
+svn_client_get_ssl_client_cert_file_provider
+ (svn_auth_provider_object_t **provider, apr_pool_t *pool)
+{
+ svn_auth_get_ssl_client_cert_file_provider(provider, pool);
+}
+
+void
+svn_client_get_ssl_client_cert_pw_file_provider
+ (svn_auth_provider_object_t **provider, apr_pool_t *pool)
+{
+ svn_auth_get_ssl_client_cert_pw_file_provider2(provider, NULL, NULL, pool);
+}
+
+void
+svn_client_get_ssl_server_trust_prompt_provider
+ (svn_auth_provider_object_t **provider,
+ svn_auth_ssl_server_trust_prompt_func_t prompt_func,
+ void *prompt_baton,
+ apr_pool_t *pool)
+{
+ svn_auth_get_ssl_server_trust_prompt_provider(provider, prompt_func,
+ prompt_baton, pool);
+}
+
+void
+svn_client_get_ssl_client_cert_prompt_provider
+ (svn_auth_provider_object_t **provider,
+ svn_auth_ssl_client_cert_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool)
+{
+ svn_auth_get_ssl_client_cert_prompt_provider(provider, prompt_func,
+ prompt_baton, retry_limit,
+ pool);
+}
+
+void
+svn_client_get_ssl_client_cert_pw_prompt_provider
+ (svn_auth_provider_object_t **provider,
+ svn_auth_ssl_client_cert_pw_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool)
+{
+ svn_auth_get_ssl_client_cert_pw_prompt_provider(provider, prompt_func,
+ prompt_baton, retry_limit,
+ pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/copy.c b/contrib/subversion/subversion/libsvn_client/copy.c
new file mode 100644
index 0000000..c0501b9
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/copy.c
@@ -0,0 +1,2422 @@
+/*
+ * copy.c: copy/move wrappers around wc 'copy' functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include "svn_hash.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_opt.h"
+#include "svn_time.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "svn_pools.h"
+
+#include "client.h"
+#include "mergeinfo.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_client_private.h"
+
+
+/*
+ * OUR BASIC APPROACH TO COPIES
+ * ============================
+ *
+ * for each source/destination pair
+ * if (not exist src_path)
+ * return ERR_BAD_SRC error
+ *
+ * if (exist dst_path)
+ * return ERR_OBSTRUCTION error
+ * else
+ * copy src_path into parent_of_dst_path as basename (dst_path)
+ *
+ * if (this is a move)
+ * delete src_path
+ */
+
+
+
+/*** Code. ***/
+
+/* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding
+ MERGEINFO to any mergeinfo pre-existing in the WC. */
+static svn_error_t *
+extend_wc_mergeinfo(const char *target_abspath,
+ apr_hash_t *mergeinfo,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_hash_t *wc_mergeinfo;
+
+ /* Get a fresh copy of the pre-existing state of the WC's mergeinfo
+ updating it. */
+ SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
+ target_abspath, pool, pool));
+
+ /* Combine the provided mergeinfo with any mergeinfo from the WC. */
+ if (wc_mergeinfo && mergeinfo)
+ SVN_ERR(svn_mergeinfo_merge2(wc_mergeinfo, mergeinfo, pool, pool));
+ else if (! wc_mergeinfo)
+ wc_mergeinfo = mergeinfo;
+
+ return svn_error_trace(
+ svn_client__record_wc_mergeinfo(target_abspath, wc_mergeinfo,
+ FALSE, ctx, pool));
+}
+
+/* Find the longest common ancestor of paths in COPY_PAIRS. If
+ SRC_ANCESTOR is NULL, ignore source paths in this calculation. If
+ DST_ANCESTOR is NULL, ignore destination paths in this calculation.
+ COMMON_ANCESTOR will be the common ancestor of both the
+ SRC_ANCESTOR and DST_ANCESTOR, and will only be set if it is not
+ NULL.
+ */
+static svn_error_t *
+get_copy_pair_ancestors(const apr_array_header_t *copy_pairs,
+ const char **src_ancestor,
+ const char **dst_ancestor,
+ const char **common_ancestor,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_client__copy_pair_t *first;
+ const char *first_dst;
+ const char *first_src;
+ const char *top_dst;
+ svn_boolean_t src_is_url;
+ svn_boolean_t dst_is_url;
+ char *top_src;
+ int i;
+
+ first = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
+
+ /* Because all the destinations are in the same directory, we can easily
+ determine their common ancestor. */
+ first_dst = first->dst_abspath_or_url;
+ dst_is_url = svn_path_is_url(first_dst);
+
+ if (copy_pairs->nelts == 1)
+ top_dst = apr_pstrdup(subpool, first_dst);
+ else
+ top_dst = dst_is_url ? svn_uri_dirname(first_dst, subpool)
+ : svn_dirent_dirname(first_dst, subpool);
+
+ /* Sources can came from anywhere, so we have to actually do some
+ work for them. */
+ first_src = first->src_abspath_or_url;
+ src_is_url = svn_path_is_url(first_src);
+ top_src = apr_pstrdup(subpool, first_src);
+ for (i = 1; i < copy_pairs->nelts; i++)
+ {
+ /* We don't need to clear the subpool here for several reasons:
+ 1) If we do, we can't use it to allocate the initial versions of
+ top_src and top_dst (above).
+ 2) We don't return any errors in the following loop, so we
+ are guanteed to destroy the subpool at the end of this function.
+ 3) The number of iterations is likely to be few, and the loop will
+ be through quickly, so memory leakage will not be significant,
+ in time or space.
+ */
+ const svn_client__copy_pair_t *pair =
+ APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
+
+ top_src = src_is_url
+ ? svn_uri_get_longest_ancestor(top_src, pair->src_abspath_or_url,
+ subpool)
+ : svn_dirent_get_longest_ancestor(top_src, pair->src_abspath_or_url,
+ subpool);
+ }
+
+ if (src_ancestor)
+ *src_ancestor = apr_pstrdup(pool, top_src);
+
+ if (dst_ancestor)
+ *dst_ancestor = apr_pstrdup(pool, top_dst);
+
+ if (common_ancestor)
+ *common_ancestor =
+ src_is_url
+ ? svn_uri_get_longest_ancestor(top_src, top_dst, pool)
+ : svn_dirent_get_longest_ancestor(top_src, top_dst, pool);
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The guts of do_wc_to_wc_copies */
+static svn_error_t *
+do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
+ const apr_array_header_t *copy_pairs,
+ const char *dst_parent,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_error_t *err = SVN_NO_ERROR;
+
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ const char *dst_abspath;
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ svn_pool_clear(iterpool);
+
+ /* Check for cancellation */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ /* Perform the copy */
+ dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name,
+ iterpool);
+ *timestamp_sleep = TRUE;
+ err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath,
+ FALSE /* metadata_only */,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2, iterpool);
+ if (err)
+ break;
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(err);
+ return SVN_NO_ERROR;
+}
+
+/* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary
+ allocations. */
+static svn_error_t *
+do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
+ const apr_array_header_t *copy_pairs,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *dst_parent, *dst_parent_abspath;
+
+ SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool));
+ if (copy_pairs->nelts == 1)
+ dst_parent = svn_dirent_dirname(dst_parent, pool);
+
+ SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool));
+
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent,
+ ctx, pool),
+ ctx->wc_ctx, dst_parent_abspath, FALSE, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* The locked bit of do_wc_to_wc_moves. */
+static svn_error_t *
+do_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair,
+ const char *dst_parent_abspath,
+ svn_boolean_t lock_src,
+ svn_boolean_t lock_dst,
+ svn_boolean_t allow_mixed_revisions,
+ svn_boolean_t metadata_only,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *dst_abspath;
+
+ dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url,
+ dst_abspath, metadata_only,
+ allow_mixed_revisions,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Wrapper to add an optional second lock */
+static svn_error_t *
+do_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair,
+ const char *dst_parent_abspath,
+ svn_boolean_t lock_src,
+ svn_boolean_t lock_dst,
+ svn_boolean_t allow_mixed_revisions,
+ svn_boolean_t metadata_only,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ if (lock_dst)
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
+ lock_dst, allow_mixed_revisions,
+ metadata_only,
+ ctx, scratch_pool),
+ ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool);
+ else
+ SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
+ lock_dst, allow_mixed_revisions,
+ metadata_only,
+ ctx, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
+ afterwards. Use POOL for temporary allocations. */
+static svn_error_t *
+do_wc_to_wc_moves(svn_boolean_t *timestamp_sleep,
+ const apr_array_header_t *copy_pairs,
+ const char *dst_path,
+ svn_boolean_t allow_mixed_revisions,
+ svn_boolean_t metadata_only,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_error_t *err = SVN_NO_ERROR;
+
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ const char *src_parent_abspath;
+ svn_boolean_t lock_src, lock_dst;
+
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ svn_pool_clear(iterpool);
+
+ /* Check for cancellation */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url,
+ iterpool);
+
+ /* We now need to lock the right combination of batons.
+ Four cases:
+ 1) src_parent == dst_parent
+ 2) src_parent is parent of dst_parent
+ 3) dst_parent is parent of src_parent
+ 4) src_parent and dst_parent are disjoint
+ We can handle 1) as either 2) or 3) */
+ if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0
+ || svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath,
+ iterpool))
+ {
+ lock_src = TRUE;
+ lock_dst = FALSE;
+ }
+ else if (svn_dirent_is_child(pair->dst_parent_abspath,
+ src_parent_abspath,
+ iterpool))
+ {
+ lock_src = FALSE;
+ lock_dst = TRUE;
+ }
+ else
+ {
+ lock_src = TRUE;
+ lock_dst = TRUE;
+ }
+
+ *timestamp_sleep = TRUE;
+
+ /* Perform the copy and then the delete. */
+ if (lock_src)
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
+ lock_src, lock_dst,
+ allow_mixed_revisions,
+ metadata_only,
+ ctx, iterpool),
+ ctx->wc_ctx, src_parent_abspath,
+ FALSE, iterpool);
+ else
+ SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
+ lock_src, lock_dst,
+ allow_mixed_revisions,
+ metadata_only,
+ ctx, iterpool));
+
+ }
+ svn_pool_destroy(iterpool);
+
+ return svn_error_trace(err);
+}
+
+/* Verify that the destinations stored in COPY_PAIRS are valid working copy
+ destinations and set pair->dst_parent_abspath and pair->base_name for each
+ item to the resulting location if they do */
+static svn_error_t *
+verify_wc_dsts(const apr_array_header_t *copy_pairs,
+ svn_boolean_t make_parents,
+ svn_boolean_t is_move,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Check that DST does not exist, but its parent does */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ svn_node_kind_t dst_kind, dst_parent_kind;
+
+ svn_pool_clear(iterpool);
+
+ /* If DST_PATH does not exist, then its basename will become a new
+ file or dir added to its parent (possibly an implicit '.').
+ Else, just error out. */
+ SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx,
+ pair->dst_abspath_or_url,
+ FALSE /* show_deleted */,
+ TRUE /* show_hidden */,
+ iterpool));
+ if (dst_kind != svn_node_none)
+ {
+ svn_boolean_t is_excluded;
+ svn_boolean_t is_server_excluded;
+
+ SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded,
+ &is_server_excluded, ctx->wc_ctx,
+ pair->dst_abspath_or_url, FALSE,
+ iterpool));
+
+ if (is_excluded || is_server_excluded)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE,
+ NULL, _("Path '%s' exists, but is excluded"),
+ svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
+ }
+ else
+ return svn_error_createf(
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("Path '%s' already exists"),
+ svn_dirent_local_style(pair->dst_abspath_or_url,
+ scratch_pool));
+ }
+
+ /* Check that there is no unversioned obstruction */
+ SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
+ iterpool));
+
+ if (dst_kind != svn_node_none)
+ {
+ if (is_move
+ && copy_pairs->nelts == 1
+ && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool),
+ svn_dirent_dirname(pair->dst_abspath_or_url,
+ iterpool)) == 0)
+ {
+ const char *dst;
+ char *dst_apr;
+ apr_status_t apr_err;
+ /* We have a rename inside a directory, which might collide
+ just because the case insensivity of the filesystem makes
+ the source match the destination. */
+
+ SVN_ERR(svn_path_cstring_from_utf8(&dst,
+ pair->dst_abspath_or_url,
+ scratch_pool));
+
+ apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
+ APR_FILEPATH_TRUENAME, iterpool);
+
+ if (!apr_err)
+ {
+ /* And now bring it back to our canonical format */
+ SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool));
+ dst = svn_dirent_canonicalize(dst, iterpool);
+ }
+ /* else: Don't report this error; just report the normal error */
+
+ if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0)
+ {
+ /* Ok, we have a single case only rename. Get out of here */
+ svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
+ pair->dst_abspath_or_url, result_pool);
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return svn_error_createf(
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("Path '%s' already exists as unversioned node"),
+ svn_dirent_local_style(pair->dst_abspath_or_url,
+ scratch_pool));
+ }
+
+ svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
+ pair->dst_abspath_or_url, result_pool);
+
+ /* Make sure the destination parent is a directory and produce a clear
+ error message if it is not. */
+ SVN_ERR(svn_wc_read_kind2(&dst_parent_kind,
+ ctx->wc_ctx, pair->dst_parent_abspath,
+ FALSE, TRUE,
+ iterpool));
+ if (make_parents && dst_parent_kind == svn_node_none)
+ {
+ SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
+ TRUE, ctx, iterpool));
+ }
+ else if (dst_parent_kind != svn_node_dir)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Path '%s' is not a directory"),
+ svn_dirent_local_style(
+ pair->dst_parent_abspath, scratch_pool));
+ }
+
+ SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
+ &dst_parent_kind, scratch_pool));
+
+ if (dst_parent_kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
+ _("Path '%s' is not a directory"),
+ svn_dirent_local_style(
+ pair->dst_parent_abspath, scratch_pool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
+ svn_boolean_t make_parents,
+ svn_boolean_t is_move,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Check that all of our SRCs exist. */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_boolean_t deleted_ok;
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ svn_pool_clear(iterpool);
+
+ deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
+ || pair->src_op_revision.kind == svn_opt_revision_base);
+
+ /* Verify that SRC_PATH exists. */
+ SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
+ pair->src_abspath_or_url,
+ deleted_ok, FALSE, iterpool));
+ if (pair->src_kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Path '%s' does not exist"),
+ svn_dirent_local_style(
+ pair->src_abspath_or_url,
+ scratch_pool));
+ }
+
+ SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, ctx,
+ result_pool, iterpool));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Path-specific state used as part of path_driver_cb_baton. */
+typedef struct path_driver_info_t
+{
+ const char *src_url;
+ const char *src_path;
+ const char *dst_path;
+ svn_node_kind_t src_kind;
+ svn_revnum_t src_revnum;
+ svn_boolean_t resurrection;
+ svn_boolean_t dir_add;
+ svn_string_t *mergeinfo; /* the new mergeinfo for the target */
+} path_driver_info_t;
+
+
+/* The baton used with the path_driver_cb_func() callback for a copy
+ or move operation. */
+struct path_driver_cb_baton
+{
+ /* The editor (and its state) used to perform the operation. */
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+
+ /* A hash of path -> path_driver_info_t *'s. */
+ apr_hash_t *action_hash;
+
+ /* Whether the operation is a move or copy. */
+ svn_boolean_t is_move;
+};
+
+static svn_error_t *
+path_driver_cb_func(void **dir_baton,
+ void *parent_baton,
+ void *callback_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct path_driver_cb_baton *cb_baton = callback_baton;
+ svn_boolean_t do_delete = FALSE, do_add = FALSE;
+ path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
+
+ /* Initialize return value. */
+ *dir_baton = NULL;
+
+ /* This function should never get an empty PATH. We can neither
+ create nor delete the empty PATH, so if someone is calling us
+ with such, the code is just plain wrong. */
+ SVN_ERR_ASSERT(! svn_path_is_empty(path));
+
+ /* Check to see if we need to add the path as a directory. */
+ if (path_info->dir_add)
+ {
+ return cb_baton->editor->add_directory(path, parent_baton, NULL,
+ SVN_INVALID_REVNUM, pool,
+ dir_baton);
+ }
+
+ /* If this is a resurrection, we know the source and dest paths are
+ the same, and that our driver will only be calling us once. */
+ if (path_info->resurrection)
+ {
+ /* If this is a move, we do nothing. Otherwise, we do the copy. */
+ if (! cb_baton->is_move)
+ do_add = TRUE;
+ }
+ /* Not a resurrection. */
+ else
+ {
+ /* If this is a move, we check PATH to see if it is the source
+ or the destination of the move. */
+ if (cb_baton->is_move)
+ {
+ if (strcmp(path_info->src_path, path) == 0)
+ do_delete = TRUE;
+ else
+ do_add = TRUE;
+ }
+ /* Not a move? This must just be the copy addition. */
+ else
+ {
+ do_add = TRUE;
+ }
+ }
+
+ if (do_delete)
+ {
+ SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
+ parent_baton, pool));
+ }
+ if (do_add)
+ {
+ SVN_ERR(svn_path_check_valid(path, pool));
+
+ if (path_info->src_kind == svn_node_file)
+ {
+ void *file_baton;
+ SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
+ path_info->src_url,
+ path_info->src_revnum,
+ pool, &file_baton));
+ if (path_info->mergeinfo)
+ SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
+ SVN_PROP_MERGEINFO,
+ path_info->mergeinfo,
+ pool));
+ SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
+ }
+ else
+ {
+ SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
+ path_info->src_url,
+ path_info->src_revnum,
+ pool, dir_baton));
+ if (path_info->mergeinfo)
+ SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
+ SVN_PROP_MERGEINFO,
+ path_info->mergeinfo,
+ pool));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* Starting with the path DIR relative to the RA_SESSION's session
+ URL, work up through DIR's parents until an existing node is found.
+ Push each nonexistent path onto the array NEW_DIRS, allocating in
+ POOL. Raise an error if the existing node is not a directory.
+
+ ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
+ ### implementation susceptible to race conditions. */
+static svn_error_t *
+find_absent_parents1(svn_ra_session_t *ra_session,
+ const char *dir,
+ apr_array_header_t *new_dirs,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
+ iterpool));
+
+ while (kind == svn_node_none)
+ {
+ svn_pool_clear(iterpool);
+
+ APR_ARRAY_PUSH(new_dirs, const char *) = dir;
+ dir = svn_dirent_dirname(dir, pool);
+
+ SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
+ &kind, iterpool));
+ }
+
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Path '%s' already exists, but is not a "
+ "directory"), dir);
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Starting with the URL *TOP_DST_URL which is also the root of
+ RA_SESSION, work up through its parents until an existing node is
+ found. Push each nonexistent URL onto the array NEW_DIRS,
+ allocating in POOL. Raise an error if the existing node is not a
+ directory.
+
+ Set *TOP_DST_URL and the RA session's root to the existing node's URL.
+
+ ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
+ ### implementation susceptible to race conditions. */
+static svn_error_t *
+find_absent_parents2(svn_ra_session_t *ra_session,
+ const char **top_dst_url,
+ apr_array_header_t *new_dirs,
+ apr_pool_t *pool)
+{
+ const char *root_url = *top_dst_url;
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
+ pool));
+
+ while (kind == svn_node_none)
+ {
+ APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
+ root_url = svn_uri_dirname(root_url, pool);
+
+ SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
+ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
+ pool));
+ }
+
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Path '%s' already exists, but is not a directory"),
+ root_url);
+
+ *top_dst_url = root_url;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+repos_to_repos_copy(const apr_array_header_t *copy_pairs,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t is_move,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
+ sizeof(const char *));
+ apr_hash_t *action_hash = apr_hash_make(pool);
+ apr_array_header_t *path_infos;
+ const char *top_url, *top_url_all, *top_url_dst;
+ const char *message, *repos_root;
+ svn_ra_session_t *ra_session = NULL;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ struct path_driver_cb_baton cb_baton;
+ apr_array_header_t *new_dirs = NULL;
+ apr_hash_t *commit_revprops;
+ int i;
+ svn_client__copy_pair_t *first_pair =
+ APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
+
+ /* Open an RA session to the first copy pair's destination. We'll
+ be verifying that every one of our copy source and destination
+ URLs is or is beneath this sucker's repository root URL as a form
+ of a cheap(ish) sanity check. */
+ SVN_ERR(svn_client_open_ra_session2(&ra_session,
+ first_pair->src_abspath_or_url, NULL,
+ ctx, pool, pool));
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
+
+ /* Verify that sources and destinations are all at or under
+ REPOS_ROOT. While here, create a path_info struct for each
+ src/dst pair and initialize portions of it with normalized source
+ location information. */
+ path_infos = apr_array_make(pool, copy_pairs->nelts,
+ sizeof(path_driver_info_t *));
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ apr_hash_t *mergeinfo;
+
+ /* Are the source and destination URLs at or under REPOS_ROOT? */
+ if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
+ && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Source and destination URLs appear not to point to the "
+ "same repository."));
+
+ /* Run the history function to get the source's URL and revnum in the
+ operational revision. */
+ SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
+ SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
+ &pair->src_revnum,
+ NULL, NULL,
+ ra_session,
+ pair->src_abspath_or_url,
+ &pair->src_peg_revision,
+ &pair->src_op_revision, NULL,
+ ctx, pool));
+
+ /* Go ahead and grab mergeinfo from the source, too. */
+ SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
+ SVN_ERR(svn_client__get_repos_mergeinfo(
+ &mergeinfo, ra_session,
+ pair->src_abspath_or_url, pair->src_revnum,
+ svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
+ if (mergeinfo)
+ SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
+
+ /* Plop an INFO structure onto our array thereof. */
+ info->src_url = pair->src_abspath_or_url;
+ info->src_revnum = pair->src_revnum;
+ info->resurrection = FALSE;
+ APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
+ }
+
+ /* If this is a move, we have to open our session to the longest
+ path common to all SRC_URLS and DST_URLS in the repository so we
+ can do existence checks on all paths, and so we can operate on
+ all paths in the case of a move. But if this is *not* a move,
+ then opening our session at the longest path common to sources
+ *and* destinations might be an optimization when the user is
+ authorized to access all that stuff, but could cause the
+ operation to fail altogether otherwise. See issue #3242. */
+ SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
+ pool));
+ top_url = is_move ? top_url_all : top_url_dst;
+
+ /* Check each src/dst pair for resurrection, and verify that TOP_URL
+ is anchored high enough to cover all the editor_t activities
+ required for this operation. */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
+ path_driver_info_t *);
+
+ /* Source and destination are the same? It's a resurrection. */
+ if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
+ info->resurrection = TRUE;
+
+ /* We need to add each dst_URL, and (in a move) we'll need to
+ delete each src_URL. Our selection of TOP_URL so far ensures
+ that all our destination URLs (and source URLs, for moves)
+ are at least as deep as TOP_URL, but we need to make sure
+ that TOP_URL is an *ancestor* of all our to-be-edited paths.
+
+ Issue #683 is demonstrates this scenario. If you're
+ resurrecting a deleted item like this: 'svn cp -rN src_URL
+ dst_URL', then src_URL == dst_URL == top_url. In this
+ situation, we want to open an RA session to be at least the
+ *parent* of all three. */
+ if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
+ && (strcmp(top_url, repos_root) != 0))
+ {
+ top_url = svn_uri_dirname(top_url, pool);
+ }
+ if (is_move
+ && (strcmp(top_url, pair->src_abspath_or_url) == 0)
+ && (strcmp(top_url, repos_root) != 0))
+ {
+ top_url = svn_uri_dirname(top_url, pool);
+ }
+ }
+
+ /* Point the RA session to our current TOP_URL. */
+ SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
+
+ /* If we're allowed to create nonexistent parent directories of our
+ destinations, then make a list in NEW_DIRS of the parent
+ directories of the destination that don't yet exist. */
+ if (make_parents)
+ {
+ new_dirs = apr_array_make(pool, 0, sizeof(const char *));
+
+ /* If this is a move, TOP_URL is at least the common ancestor of
+ all the paths (sources and destinations) involved. Assuming
+ the sources exist (which is fair, because if they don't, this
+ whole operation will fail anyway), TOP_URL must also exist.
+ So it's the paths between TOP_URL and the destinations which
+ we have to check for existence. But here, we take advantage
+ of the knowledge of our caller. We know that if there are
+ multiple copy/move operations being requested, then the
+ destinations of the copies/moves will all be siblings of one
+ another. Therefore, we need only to check for the
+ nonexistent paths between TOP_URL and *one* of our
+ destinations to find nonexistent parents of all of them. */
+ if (is_move)
+ {
+ /* Imagine a situation where the user tries to copy an
+ existing source directory to nonexistent directory with
+ --parents options specified:
+
+ svn copy --parents URL/src URL/dst
+
+ where src exists and dst does not. If the dirname of the
+ destination path is equal to TOP_URL,
+ do not try to add dst to the NEW_DIRS list since it
+ will be added to the commit items array later in this
+ function. */
+ const char *dir = svn_uri_skip_ancestor(
+ top_url,
+ svn_uri_dirname(first_pair->dst_abspath_or_url,
+ pool),
+ pool);
+ if (dir && *dir)
+ SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
+ }
+ /* If, however, this is *not* a move, TOP_URL only points to the
+ common ancestor of our destination path(s), or possibly one
+ level higher. We'll need to do an existence crawl toward the
+ root of the repository, starting with one of our destinations
+ (see "... take advantage of the knowledge of our caller ..."
+ above), and possibly adjusting TOP_URL as we go. */
+ else
+ {
+ apr_array_header_t *new_urls =
+ apr_array_make(pool, 0, sizeof(const char *));
+ SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
+
+ /* Convert absolute URLs into relpaths relative to TOP_URL. */
+ for (i = 0; i < new_urls->nelts; i++)
+ {
+ const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
+ const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
+
+ APR_ARRAY_PUSH(new_dirs, const char *) = dir;
+ }
+ }
+ }
+
+ /* For each src/dst pair, check to see if that SRC_URL is a child of
+ the DST_URL (excepting the case where DST_URL is the repo root).
+ If it is, and the parent of DST_URL is the current TOP_URL, then we
+ need to reparent the session one directory higher, the parent of
+ the DST_URL. */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
+ path_driver_info_t *);
+ const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
+ pair->src_abspath_or_url,
+ pool);
+
+ if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
+ && (relpath != NULL && *relpath != '\0'))
+ {
+ info->resurrection = TRUE;
+ top_url = svn_uri_dirname(top_url, pool);
+ SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
+ }
+ }
+
+ /* Get the portions of the SRC and DST URLs that are relative to
+ TOP_URL (URI-decoding them while we're at it), verify that the
+ source exists and the proposed destination does not, and toss
+ what we've learned into the INFO array. (For copies -- that is,
+ non-moves -- the relative source URL NULL because it isn't a
+ child of the TOP_URL at all. That's okay, we'll deal with
+ it.) */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair =
+ APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
+ path_driver_info_t *info =
+ APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
+ svn_node_kind_t dst_kind;
+ const char *src_rel, *dst_rel;
+
+ src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
+ if (src_rel)
+ {
+ SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
+ &info->src_kind, pool));
+ }
+ else
+ {
+ const char *old_url;
+
+ src_rel = NULL;
+ SVN_ERR_ASSERT(! is_move);
+
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
+ pair->src_abspath_or_url,
+ pool));
+ SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
+ &info->src_kind, pool));
+ SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
+ }
+ if (info->src_kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' does not exist in revision %ld"),
+ pair->src_abspath_or_url, pair->src_revnum);
+
+ /* Figure out the basename that will result from this operation,
+ and ensure that we aren't trying to overwrite existing paths. */
+ dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
+ SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
+ &dst_kind, pool));
+ if (dst_kind != svn_node_none)
+ return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Path '%s' already exists"), dst_rel);
+
+ /* More info for our INFO structure. */
+ info->src_path = src_rel;
+ info->dst_path = dst_rel;
+
+ svn_hash_sets(action_hash, info->dst_path, info);
+ if (is_move && (! info->resurrection))
+ svn_hash_sets(action_hash, info->src_path, info);
+ }
+
+ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
+ {
+ /* Produce a list of new paths to add, and provide it to the
+ mechanism used to acquire a log message. */
+ svn_client_commit_item3_t *item;
+ const char *tmp_file;
+ apr_array_header_t *commit_items
+ = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
+
+ /* Add any intermediate directories to the message */
+ if (make_parents)
+ {
+ for (i = 0; i < new_dirs->nelts; i++)
+ {
+ const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
+
+ item = svn_client_commit_item3_create(pool);
+ item->url = svn_path_url_add_component2(top_url, relpath, pool);
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+ }
+ }
+
+ for (i = 0; i < path_infos->nelts; i++)
+ {
+ path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
+ path_driver_info_t *);
+
+ item = svn_client_commit_item3_create(pool);
+ item->url = svn_path_url_add_component2(top_url, info->dst_path,
+ pool);
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+
+ if (is_move && (! info->resurrection))
+ {
+ item = apr_pcalloc(pool, sizeof(*item));
+ item->url = svn_path_url_add_component2(top_url, info->src_path,
+ pool);
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+ }
+ }
+
+ SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
+ ctx, pool));
+ if (! message)
+ return SVN_NO_ERROR;
+ }
+ else
+ message = "";
+
+ /* Setup our PATHS for the path-based editor drive. */
+ /* First any intermediate directories. */
+ if (make_parents)
+ {
+ for (i = 0; i < new_dirs->nelts; i++)
+ {
+ const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
+ path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
+
+ info->dst_path = relpath;
+ info->dir_add = TRUE;
+
+ APR_ARRAY_PUSH(paths, const char *) = relpath;
+ svn_hash_sets(action_hash, relpath, info);
+ }
+ }
+
+ /* Then our copy destinations and move sources (if any). */
+ for (i = 0; i < path_infos->nelts; i++)
+ {
+ path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
+ path_driver_info_t *);
+
+ APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
+ if (is_move && (! info->resurrection))
+ APR_ARRAY_PUSH(paths, const char *) = info->src_path;
+ }
+
+ SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
+ message, ctx, pool));
+
+ /* Fetch RA commit editor. */
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
+ svn_client__get_shim_callbacks(ctx->wc_ctx,
+ NULL, pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ commit_revprops,
+ commit_callback,
+ commit_baton,
+ NULL, TRUE, /* No lock tokens */
+ pool));
+
+ /* Setup the callback baton. */
+ cb_baton.editor = editor;
+ cb_baton.edit_baton = edit_baton;
+ cb_baton.action_hash = action_hash;
+ cb_baton.is_move = is_move;
+
+ /* Call the path-based editor driver. */
+ err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
+ path_driver_cb_func, &cb_baton, pool);
+ if (err)
+ {
+ /* At least try to abort the edit (and fs txn) before throwing err. */
+ return svn_error_compose_create(
+ err,
+ editor->abort_edit(edit_baton, pool));
+ }
+
+ /* Close the edit. */
+ return svn_error_trace(editor->close_edit(edit_baton, pool));
+}
+
+/* Baton for check_url_kind */
+struct check_url_kind_baton
+{
+ svn_ra_session_t *session;
+ const char *repos_root_url;
+ svn_boolean_t should_reparent;
+};
+
+/* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
+static svn_error_t *
+check_url_kind(void *baton,
+ svn_node_kind_t *kind,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ struct check_url_kind_baton *cukb = baton;
+
+ /* If we don't have a session or can't use the session, get one */
+ if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
+ *kind = svn_node_none;
+ else
+ {
+ cukb->should_reparent = TRUE;
+
+ SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
+
+ SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
+ kind, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* ### Copy ...
+ * COMMIT_INFO_P is ...
+ * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
+ * and each 'dst_abspath_or_url' is a URL.
+ * MAKE_PARENTS is ...
+ * REVPROP_TABLE is ...
+ * CTX is ... */
+static svn_error_t *
+wc_to_repos_copy(const apr_array_header_t *copy_pairs,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *message;
+ const char *top_src_path, *top_dst_url;
+ struct check_url_kind_baton cukb;
+ const char *top_src_abspath;
+ svn_ra_session_t *ra_session;
+ const svn_delta_editor_t *editor;
+ apr_hash_t *relpath_map = NULL;
+ void *edit_baton;
+ svn_client__committables_t *committables;
+ apr_array_header_t *commit_items;
+ apr_pool_t *iterpool;
+ apr_array_header_t *new_dirs = NULL;
+ apr_hash_t *commit_revprops;
+ svn_client__copy_pair_t *first_pair;
+ apr_pool_t *session_pool = svn_pool_create(scratch_pool);
+ int i;
+
+ /* Find the common root of all the source paths */
+ SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
+ scratch_pool));
+
+ /* Do we need to lock the working copy? 1.6 didn't take a write
+ lock, but what happens if the working copy changes during the copy
+ operation? */
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* Determine the longest common ancestor for the destinations, and open an RA
+ session to that location. */
+ /* ### But why start by getting the _parent_ of the first one? */
+ /* --- That works because multiple destinations always point to the same
+ * directory. I'm rather wondering why we need to find a common
+ * destination parent here at all, instead of simply getting
+ * top_dst_url from get_copy_pair_ancestors() above?
+ * It looks like the entire block of code hanging off this comment
+ * is redundant. */
+ first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
+ top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
+ for (i = 1; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
+ pair->dst_abspath_or_url,
+ scratch_pool);
+ }
+
+ SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
+
+ /* Open a session to help while determining the exact targets */
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
+ top_src_abspath, NULL,
+ FALSE /* write_dav_props */,
+ TRUE /* read_dav_props */,
+ ctx,
+ session_pool, session_pool));
+
+ /* If requested, determine the nearest existing parent of the destination,
+ and reparent the ra session there. */
+ if (make_parents)
+ {
+ new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
+ SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
+ scratch_pool));
+ }
+
+ /* Figure out the basename that will result from each copy and check to make
+ sure it doesn't exist already. */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_node_kind_t dst_kind;
+ const char *dst_rel;
+ svn_client__copy_pair_t *pair =
+ APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
+
+ svn_pool_clear(iterpool);
+ dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
+ iterpool);
+ SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
+ &dst_kind, iterpool));
+ if (dst_kind != svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Path '%s' already exists"),
+ pair->dst_abspath_or_url);
+ }
+ }
+
+ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
+ {
+ /* Produce a list of new paths to add, and provide it to the
+ mechanism used to acquire a log message. */
+ svn_client_commit_item3_t *item;
+ const char *tmp_file;
+ commit_items = apr_array_make(scratch_pool, copy_pairs->nelts,
+ sizeof(item));
+
+ /* Add any intermediate directories to the message */
+ if (make_parents)
+ {
+ for (i = 0; i < new_dirs->nelts; i++)
+ {
+ const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
+
+ item = svn_client_commit_item3_create(scratch_pool);
+ item->url = url;
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+ }
+ }
+
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+
+ item = svn_client_commit_item3_create(scratch_pool);
+ item->url = pair->dst_abspath_or_url;
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+ }
+
+ SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
+ ctx, scratch_pool));
+ if (! message)
+ {
+ svn_pool_destroy(iterpool);
+ svn_pool_destroy(session_pool);
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ message = "";
+
+ cukb.session = ra_session;
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
+ cukb.should_reparent = FALSE;
+
+ /* Crawl the working copy for commit items. */
+ /* ### TODO: Pass check_url_func for issue #3314 handling */
+ SVN_ERR(svn_client__get_copy_committables(&committables,
+ copy_pairs,
+ check_url_kind, &cukb,
+ ctx, scratch_pool, iterpool));
+
+ /* The committables are keyed by the repository root */
+ commit_items = svn_hash_gets(committables->by_repository,
+ cukb.repos_root_url);
+ SVN_ERR_ASSERT(commit_items != NULL);
+
+ if (cukb.should_reparent)
+ SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
+
+ /* If we are creating intermediate directories, tack them onto the list
+ of committables. */
+ if (make_parents)
+ {
+ for (i = 0; i < new_dirs->nelts; i++)
+ {
+ const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
+ svn_client_commit_item3_t *item;
+
+ item = svn_client_commit_item3_create(scratch_pool);
+ item->url = url;
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
+ item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
+ sizeof(svn_prop_t *));
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+ }
+ }
+
+ /* ### TODO: This extra loop would be unnecessary if this code lived
+ ### in svn_client__get_copy_committables(), which is incidentally
+ ### only used above (so should really be in this source file). */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ apr_hash_t *mergeinfo, *wc_mergeinfo;
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ svn_client_commit_item3_t *item =
+ APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+ svn_client__pathrev_t *src_origin;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
+ pair->src_abspath_or_url,
+ ctx, iterpool, iterpool));
+
+ /* Set the mergeinfo for the destination to the combined merge
+ info known to the WC and the repository. */
+ item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
+ sizeof(svn_prop_t *));
+ /* Repository mergeinfo (or NULL if it's locally added)... */
+ if (src_origin)
+ SVN_ERR(svn_client__get_repos_mergeinfo(
+ &mergeinfo, ra_session, src_origin->url, src_origin->rev,
+ svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
+ else
+ mergeinfo = NULL;
+ /* ... and WC mergeinfo. */
+ SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
+ pair->src_abspath_or_url,
+ iterpool, iterpool));
+ if (wc_mergeinfo && mergeinfo)
+ SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
+ iterpool));
+ else if (! mergeinfo)
+ mergeinfo = wc_mergeinfo;
+ if (mergeinfo)
+ {
+ /* Push a mergeinfo prop representing MERGEINFO onto the
+ * OUTGOING_PROP_CHANGES array. */
+
+ svn_prop_t *mergeinfo_prop
+ = apr_palloc(item->outgoing_prop_changes->pool,
+ sizeof(svn_prop_t));
+ svn_string_t *prop_value;
+
+ SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
+ item->outgoing_prop_changes->pool));
+
+ mergeinfo_prop->name = SVN_PROP_MERGEINFO;
+ mergeinfo_prop->value = prop_value;
+ APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
+ = mergeinfo_prop;
+ }
+ }
+
+ /* Sort and condense our COMMIT_ITEMS. */
+ SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
+ commit_items, scratch_pool));
+
+#ifdef ENABLE_EV2_SHIMS
+ if (commit_items)
+ {
+ relpath_map = apr_hash_make(pool);
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
+ svn_client_commit_item3_t *);
+ const char *relpath;
+
+ if (!item->path)
+ continue;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL,
+ ctx->wc_ctx, item->path, FALSE,
+ scratch_pool, iterpool));
+ if (relpath)
+ svn_hash_sets(relpath_map, relpath, item->path);
+ }
+ }
+#endif
+
+ /* Close the initial session, to reopen a new session with commit handling */
+ svn_pool_clear(session_pool);
+
+ /* Open a new RA session to DST_URL. */
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
+ NULL, commit_items,
+ FALSE, FALSE, ctx,
+ session_pool, session_pool));
+
+ SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
+ message, ctx, session_pool));
+
+ /* Fetch RA commit editor. */
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
+ svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
+ session_pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ commit_revprops,
+ commit_callback,
+ commit_baton, NULL,
+ TRUE, /* No lock tokens */
+ session_pool));
+
+ /* Perform the commit. */
+ SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
+ editor, edit_baton,
+ 0, /* ### any notify_path_offset needed? */
+ NULL, ctx, session_pool, session_pool),
+ _("Commit failed (details follow):"));
+
+ svn_pool_destroy(iterpool);
+ svn_pool_destroy(session_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* A baton for notification_adjust_func(). */
+struct notification_adjust_baton
+{
+ svn_wc_notify_func2_t inner_func;
+ void *inner_baton;
+ const char *checkout_abspath;
+ const char *final_abspath;
+};
+
+/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
+ * baton is BATON->inner_baton) and adjusts the notification paths that
+ * start with BATON->checkout_abspath to start instead with
+ * BATON->final_abspath. */
+static void
+notification_adjust_func(void *baton,
+ const svn_wc_notify_t *notify,
+ apr_pool_t *pool)
+{
+ struct notification_adjust_baton *nb = baton;
+ svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
+ const char *relpath;
+
+ relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
+ inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
+
+ if (nb->inner_func)
+ nb->inner_func(nb->inner_baton, inner_notify, pool);
+}
+
+/* Peform each individual copy operation for a repos -> wc copy. A
+ helper for repos_to_wc_copy().
+
+ Resolve PAIR->src_revnum to a real revision number if it isn't already. */
+static svn_error_t *
+repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
+ svn_client__copy_pair_t *pair,
+ svn_boolean_t same_repositories,
+ svn_boolean_t ignore_externals,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_hash_t *src_mergeinfo;
+ const char *dst_abspath = pair->dst_abspath_or_url;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+ if (!same_repositories && ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+ notify = svn_wc_create_notify_url(
+ pair->src_abspath_or_url,
+ svn_wc_notify_foreign_copy_begin,
+ pool);
+ notify->kind = pair->src_kind;
+ ctx->notify_func2(ctx->notify_baton2, notify, pool);
+
+ /* Allow a theoretical cancel to get through. */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+ }
+
+ if (pair->src_kind == svn_node_dir)
+ {
+ if (same_repositories)
+ {
+ svn_boolean_t sleep_needed = FALSE;
+ const char *tmpdir_abspath, *tmp_abspath;
+
+ /* Find a temporary location in which to check out the copy source. */
+ SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
+ pool, pool));
+
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
+ svn_io_file_del_on_close, pool, pool));
+
+ /* Make a new checkout of the requested source. While doing so,
+ * resolve pair->src_revnum to an actual revision number in case it
+ * was until now 'invalid' meaning 'head'. Ask this function not to
+ * sleep for timestamps, by passing a sleep_needed output param.
+ * Send notifications for all nodes except the root node, and adjust
+ * them to refer to the destination rather than this temporary path. */
+ {
+ svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
+ void *old_notify_baton2 = ctx->notify_baton2;
+ struct notification_adjust_baton nb;
+ svn_error_t *err;
+
+ nb.inner_func = ctx->notify_func2;
+ nb.inner_baton = ctx->notify_baton2;
+ nb.checkout_abspath = tmp_abspath;
+ nb.final_abspath = dst_abspath;
+ ctx->notify_func2 = notification_adjust_func;
+ ctx->notify_baton2 = &nb;
+
+ err = svn_client__checkout_internal(&pair->src_revnum,
+ pair->src_original,
+ tmp_abspath,
+ &pair->src_peg_revision,
+ &pair->src_op_revision,
+ svn_depth_infinity,
+ ignore_externals, FALSE,
+ &sleep_needed, ctx, pool);
+
+ ctx->notify_func2 = old_notify_func2;
+ ctx->notify_baton2 = old_notify_baton2;
+
+ SVN_ERR(err);
+ }
+
+ *timestamp_sleep = TRUE;
+
+ /* Schedule dst_path for addition in parent, with copy history.
+ Don't send any notification here.
+ Then remove the temporary checkout's .svn dir in preparation for
+ moving the rest of it into the final destination. */
+ SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
+ TRUE /* metadata_only */,
+ ctx->cancel_func, ctx->cancel_baton,
+ NULL, NULL, pool));
+ SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
+ FALSE, pool, pool));
+ SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
+ tmp_abspath,
+ FALSE, FALSE,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ pool));
+
+ /* Move the temporary disk tree into place. */
+ SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool));
+ }
+ else
+ {
+ *timestamp_sleep = TRUE;
+
+ SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
+ dst_abspath,
+ &pair->src_peg_revision,
+ &pair->src_op_revision,
+ svn_depth_infinity,
+ FALSE /* make_parents */,
+ TRUE /* already_locked */,
+ ctx, pool));
+
+ return SVN_NO_ERROR;
+ }
+ } /* end directory case */
+
+ else if (pair->src_kind == svn_node_file)
+ {
+ apr_hash_t *new_props;
+ const char *src_rel;
+ svn_stream_t *new_base_contents = svn_stream_buffered(pool);
+
+ SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
+ pair->src_abspath_or_url,
+ pool));
+ /* Fetch the file content. While doing so, resolve pair->src_revnum
+ * to an actual revision number if it's 'invalid' meaning 'head'. */
+ SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
+ new_base_contents,
+ &pair->src_revnum, &new_props, pool));
+
+ if (new_props && ! same_repositories)
+ svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
+
+ *timestamp_sleep = TRUE;
+
+ SVN_ERR(svn_wc_add_repos_file4(
+ ctx->wc_ctx, dst_abspath,
+ new_base_contents, NULL, new_props, NULL,
+ same_repositories ? pair->src_abspath_or_url : NULL,
+ same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+ }
+
+ /* Record the implied mergeinfo (before the notification callback
+ is invoked for the root node). */
+ SVN_ERR(svn_client__get_repos_mergeinfo(
+ &src_mergeinfo, ra_session,
+ pair->src_abspath_or_url, pair->src_revnum,
+ svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
+ SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
+
+ /* Do our own notification for the root node, even if we could possibly
+ have delegated it. See also issue #1552.
+
+ ### Maybe this notification should mention the mergeinfo change. */
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(
+ dst_abspath, svn_wc_notify_add, pool);
+ notify->kind = pair->src_kind;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
+ const apr_array_header_t *copy_pairs,
+ const char *top_dst_path,
+ svn_boolean_t ignore_externals,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_boolean_t same_repositories;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* We've already checked for physical obstruction by a working file.
+ But there could also be logical obstruction by an entry whose
+ working file happens to be missing.*/
+ SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, ctx,
+ scratch_pool, iterpool));
+
+ /* Decide whether the two repositories are the same or not. */
+ {
+ svn_error_t *src_err, *dst_err;
+ const char *parent;
+ const char *parent_abspath;
+ const char *src_uuid, *dst_uuid;
+
+ /* Get the repository uuid of SRC_URL */
+ src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool);
+ if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
+ return svn_error_trace(src_err);
+
+ /* Get repository uuid of dst's parent directory, since dst may
+ not exist. ### TODO: we should probably walk up the wc here,
+ in case the parent dir has an imaginary URL. */
+ if (copy_pairs->nelts == 1)
+ parent = svn_dirent_dirname(top_dst_path, scratch_pool);
+ else
+ parent = top_dst_path;
+
+ SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool));
+ dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
+ parent_abspath, ctx,
+ iterpool, iterpool);
+ if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
+ return dst_err;
+
+ /* If either of the UUIDs are nonexistent, then at least one of
+ the repositories must be very old. Rather than punish the
+ user, just assume the repositories are different, so no
+ copy-history is attempted. */
+ if (src_err || dst_err || (! src_uuid) || (! dst_uuid))
+ same_repositories = FALSE;
+ else
+ same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
+ }
+
+ /* Perform the move for each of the copy_pairs. */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ /* Check for cancellation */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
+ APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *),
+ same_repositories,
+ ignore_externals,
+ ra_session, ctx, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
+ const apr_array_header_t *copy_pairs,
+ svn_boolean_t make_parents,
+ svn_boolean_t ignore_externals,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session;
+ const char *top_src_url, *top_dst_path;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ const char *lock_abspath;
+ int i;
+
+ /* Get the real path for the source, based upon its peg revision. */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ const char *src;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
+ NULL,
+ pair->src_abspath_or_url,
+ &pair->src_peg_revision,
+ &pair->src_op_revision, NULL,
+ ctx, iterpool));
+
+ pair->src_original = pair->src_abspath_or_url;
+ pair->src_abspath_or_url = apr_pstrdup(pool, src);
+ }
+
+ SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path,
+ NULL, pool));
+ lock_abspath = top_dst_path;
+ if (copy_pairs->nelts == 1)
+ {
+ top_src_url = svn_uri_dirname(top_src_url, pool);
+ lock_abspath = svn_dirent_dirname(top_dst_path, pool);
+ }
+
+ /* Open a repository session to the longest common src ancestor. We do not
+ (yet) have a working copy, so we don't have a corresponding path and
+ tempfiles cannot go into the admin area. */
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
+ ctx, pool, pool));
+
+ /* Get the correct src path for the peg revision used, and verify that we
+ aren't overwriting an existing path. */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ svn_node_kind_t dst_parent_kind, dst_kind;
+ const char *dst_parent;
+ const char *src_rel;
+
+ svn_pool_clear(iterpool);
+
+ /* Next, make sure that the path exists in the repository. */
+ SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
+ pair->src_abspath_or_url,
+ iterpool));
+ SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
+ &pair->src_kind, pool));
+ if (pair->src_kind == svn_node_none)
+ {
+ if (SVN_IS_VALID_REVNUM(pair->src_revnum))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' not found in revision %ld"),
+ pair->src_abspath_or_url, pair->src_revnum);
+ else
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' not found in head revision"),
+ pair->src_abspath_or_url);
+ }
+
+ /* Figure out about dst. */
+ SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
+ iterpool));
+ if (dst_kind != svn_node_none)
+ {
+ return svn_error_createf(
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("Path '%s' already exists"),
+ svn_dirent_local_style(pair->dst_abspath_or_url, pool));
+ }
+
+ /* Make sure the destination parent is a directory and produce a clear
+ error message if it is not. */
+ dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
+ SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
+ if (make_parents && dst_parent_kind == svn_node_none)
+ {
+ SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
+ iterpool));
+ }
+ else if (dst_parent_kind != svn_node_dir)
+ {
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("Path '%s' is not a directory"),
+ svn_dirent_local_style(dst_parent, pool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ repos_to_wc_copy_locked(timestamp_sleep,
+ copy_pairs, top_dst_path, ignore_externals,
+ ra_session, ctx, pool),
+ ctx->wc_ctx, lock_abspath, FALSE, pool);
+ return SVN_NO_ERROR;
+}
+
+#define NEED_REPOS_REVNUM(revision) \
+ ((revision.kind != svn_opt_revision_unspecified) \
+ && (revision.kind != svn_opt_revision_working))
+
+/* ...
+ *
+ * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
+ * change *TIMESTAMP_SLEEP. This output will be valid even if the
+ * function returns an error.
+ *
+ * Perform all allocations in POOL.
+ */
+static svn_error_t *
+try_copy(svn_boolean_t *timestamp_sleep,
+ const apr_array_header_t *sources,
+ const char *dst_path_in,
+ svn_boolean_t is_move,
+ svn_boolean_t allow_mixed_revisions,
+ svn_boolean_t metadata_only,
+ svn_boolean_t make_parents,
+ svn_boolean_t ignore_externals,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *copy_pairs =
+ apr_array_make(pool, sources->nelts,
+ sizeof(svn_client__copy_pair_t *));
+ svn_boolean_t srcs_are_urls, dst_is_url;
+ int i;
+
+ /* Are either of our paths URLs? Just check the first src_path. If
+ there are more than one, we'll check for homogeneity among them
+ down below. */
+ srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
+ svn_client_copy_source_t *)->path);
+ dst_is_url = svn_path_is_url(dst_path_in);
+ if (!dst_is_url)
+ SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
+
+ /* If we have multiple source paths, it implies the dst_path is a
+ directory we are moving or copying into. Populate the COPY_PAIRS
+ array to contain a destination path for each of the source paths. */
+ if (sources->nelts > 1)
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < sources->nelts; i++)
+ {
+ svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
+ svn_client_copy_source_t *);
+ svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
+ const char *src_basename;
+ svn_boolean_t src_is_url = svn_path_is_url(source->path);
+
+ svn_pool_clear(iterpool);
+
+ if (src_is_url)
+ {
+ pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
+ src_basename = svn_uri_basename(pair->src_abspath_or_url,
+ iterpool);
+ }
+ else
+ {
+ SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
+ source->path, pool));
+ src_basename = svn_dirent_basename(pair->src_abspath_or_url,
+ iterpool);
+ }
+
+ pair->src_op_revision = *source->revision;
+ pair->src_peg_revision = *source->peg_revision;
+
+ SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
+ &pair->src_op_revision,
+ src_is_url,
+ TRUE,
+ iterpool));
+
+ /* Check to see if all the sources are urls or all working copy
+ * paths. */
+ if (src_is_url != srcs_are_urls)
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot mix repository and working copy sources"));
+
+ if (dst_is_url)
+ pair->dst_abspath_or_url =
+ svn_path_url_add_component2(dst_path_in, src_basename, pool);
+ else
+ pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
+ src_basename, pool);
+ APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+ else
+ {
+ /* Only one source path. */
+ svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
+ svn_client_copy_source_t *source =
+ APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
+ svn_boolean_t src_is_url = svn_path_is_url(source->path);
+
+ if (src_is_url)
+ pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
+ else
+ SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
+ source->path, pool));
+ pair->src_op_revision = *source->revision;
+ pair->src_peg_revision = *source->peg_revision;
+
+ SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
+ &pair->src_op_revision,
+ src_is_url, TRUE, pool));
+
+ pair->dst_abspath_or_url = dst_path_in;
+ APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
+ }
+
+ if (!srcs_are_urls && !dst_is_url)
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+
+ svn_pool_clear(iterpool);
+
+ if (svn_dirent_is_child(pair->src_abspath_or_url,
+ pair->dst_abspath_or_url, iterpool))
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot copy path '%s' into its own child '%s'"),
+ svn_dirent_local_style(pair->src_abspath_or_url, pool),
+ svn_dirent_local_style(pair->dst_abspath_or_url, pool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ /* A file external should not be moved since the file external is
+ implemented as a switched file and it would delete the file the
+ file external is switched to, which is not the behavior the user
+ would probably want. */
+ if (is_move && !srcs_are_urls)
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair =
+ APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
+ svn_node_kind_t external_kind;
+ const char *defining_abspath;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
+ SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
+ NULL, NULL, NULL, ctx->wc_ctx,
+ pair->src_abspath_or_url,
+ pair->src_abspath_or_url, TRUE,
+ iterpool, iterpool));
+
+ if (external_kind != svn_node_none)
+ return svn_error_createf(
+ SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
+ NULL,
+ _("Cannot move the external at '%s'; please "
+ "edit the svn:externals property on '%s'."),
+ svn_dirent_local_style(pair->src_abspath_or_url, pool),
+ svn_dirent_local_style(defining_abspath, pool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ if (is_move)
+ {
+ /* Disallow moves between the working copy and the repository. */
+ if (srcs_are_urls != dst_is_url)
+ {
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Moves between the working copy and the repository are not "
+ "supported"));
+ }
+
+ /* Disallow moving any path/URL onto or into itself. */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+
+ if (strcmp(pair->src_abspath_or_url,
+ pair->dst_abspath_or_url) == 0)
+ return svn_error_createf(
+ SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ srcs_are_urls ?
+ _("Cannot move URL '%s' into itself") :
+ _("Cannot move path '%s' into itself"),
+ srcs_are_urls ?
+ pair->src_abspath_or_url :
+ svn_dirent_local_style(pair->src_abspath_or_url, pool));
+ }
+ }
+ else
+ {
+ if (!srcs_are_urls)
+ {
+ /* If we are doing a wc->* copy, but with an operational revision
+ other than the working copy revision, we are really doing a
+ repo->* copy, because we're going to need to get the rev from the
+ repo. */
+
+ svn_boolean_t need_repos_op_rev = FALSE;
+ svn_boolean_t need_repos_peg_rev = FALSE;
+
+ /* Check to see if any revision is something other than
+ svn_opt_revision_unspecified or svn_opt_revision_working. */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+
+ if (NEED_REPOS_REVNUM(pair->src_op_revision))
+ need_repos_op_rev = TRUE;
+
+ if (NEED_REPOS_REVNUM(pair->src_peg_revision))
+ need_repos_peg_rev = TRUE;
+
+ if (need_repos_op_rev || need_repos_peg_rev)
+ break;
+ }
+
+ if (need_repos_op_rev || need_repos_peg_rev)
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ const char *copyfrom_repos_root_url;
+ const char *copyfrom_repos_relpath;
+ const char *url;
+ svn_revnum_t copyfrom_rev;
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
+
+ SVN_ERR(svn_wc__node_get_origin(NULL, &copyfrom_rev,
+ &copyfrom_repos_relpath,
+ &copyfrom_repos_root_url,
+ NULL, NULL,
+ ctx->wc_ctx,
+ pair->src_abspath_or_url,
+ TRUE, iterpool, iterpool));
+
+ if (copyfrom_repos_relpath)
+ url = svn_path_url_add_component2(copyfrom_repos_root_url,
+ copyfrom_repos_relpath,
+ pool);
+ else
+ return svn_error_createf
+ (SVN_ERR_ENTRY_MISSING_URL, NULL,
+ _("'%s' does not have a URL associated with it"),
+ svn_dirent_local_style(pair->src_abspath_or_url, pool));
+
+ pair->src_abspath_or_url = url;
+
+ if (!need_repos_peg_rev
+ || pair->src_peg_revision.kind == svn_opt_revision_base)
+ {
+ /* Default the peg revision to that of the WC entry. */
+ pair->src_peg_revision.kind = svn_opt_revision_number;
+ pair->src_peg_revision.value.number = copyfrom_rev;
+ }
+
+ if (pair->src_op_revision.kind == svn_opt_revision_base)
+ {
+ /* Use the entry's revision as the operational rev. */
+ pair->src_op_revision.kind = svn_opt_revision_number;
+ pair->src_op_revision.value.number = copyfrom_rev;
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ srcs_are_urls = TRUE;
+ }
+ }
+ }
+
+ /* Now, call the right handler for the operation. */
+ if ((! srcs_are_urls) && (! dst_is_url))
+ {
+ SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
+ ctx, pool, pool));
+
+ /* Copy or move all targets. */
+ if (is_move)
+ return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
+ copy_pairs, dst_path_in,
+ allow_mixed_revisions,
+ metadata_only,
+ ctx, pool));
+ else
+ {
+ /* We ignore these values, so assert the default value */
+ SVN_ERR_ASSERT(allow_mixed_revisions && !metadata_only);
+ return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
+ copy_pairs, ctx, pool));
+ }
+ }
+ else if ((! srcs_are_urls) && (dst_is_url))
+ {
+ return svn_error_trace(
+ wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
+ commit_callback, commit_baton, ctx, pool));
+ }
+ else if ((srcs_are_urls) && (! dst_is_url))
+ {
+ return svn_error_trace(
+ repos_to_wc_copy(timestamp_sleep,
+ copy_pairs, make_parents, ignore_externals,
+ ctx, pool));
+ }
+ else
+ {
+ return svn_error_trace(
+ repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
+ commit_callback, commit_baton, ctx, is_move,
+ pool));
+ }
+}
+
+
+
+/* Public Interfaces */
+svn_error_t *
+svn_client_copy6(const apr_array_header_t *sources,
+ const char *dst_path,
+ svn_boolean_t copy_as_child,
+ svn_boolean_t make_parents,
+ svn_boolean_t ignore_externals,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_boolean_t timestamp_sleep = FALSE;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ if (sources->nelts > 1 && !copy_as_child)
+ return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
+ NULL, NULL);
+
+ err = try_copy(&timestamp_sleep,
+ sources, dst_path,
+ FALSE /* is_move */,
+ TRUE /* allow_mixed_revisions */,
+ FALSE /* metadata_only */,
+ make_parents,
+ ignore_externals,
+ revprop_table,
+ commit_callback, commit_baton,
+ ctx,
+ subpool);
+
+ /* If the destination exists, try to copy the sources as children of the
+ destination. */
+ if (copy_as_child && err && (sources->nelts == 1)
+ && (err->apr_err == SVN_ERR_ENTRY_EXISTS
+ || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
+ {
+ const char *src_path = APR_ARRAY_IDX(sources, 0,
+ svn_client_copy_source_t *)->path;
+ const char *src_basename;
+ svn_boolean_t src_is_url = svn_path_is_url(src_path);
+ svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
+
+ svn_error_clear(err);
+ svn_pool_clear(subpool);
+
+ src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
+ : svn_dirent_basename(src_path, subpool);
+ dst_path
+ = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
+ subpool)
+ : svn_dirent_join(dst_path, src_basename, subpool);
+
+ err = try_copy(&timestamp_sleep,
+ sources, dst_path,
+ FALSE /* is_move */,
+ TRUE /* allow_mixed_revisions */,
+ FALSE /* metadata_only */,
+ make_parents,
+ ignore_externals,
+ revprop_table,
+ commit_callback, commit_baton,
+ ctx,
+ subpool);
+ }
+
+ /* Sleep if required. DST_PATH is not a URL in these cases. */
+ if (timestamp_sleep)
+ svn_io_sleep_for_timestamps(dst_path, subpool);
+
+ svn_pool_destroy(subpool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_client_move7(const apr_array_header_t *src_paths,
+ const char *dst_path,
+ svn_boolean_t move_as_child,
+ svn_boolean_t make_parents,
+ svn_boolean_t allow_mixed_revisions,
+ svn_boolean_t metadata_only,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const svn_opt_revision_t head_revision
+ = { svn_opt_revision_head, { 0 } };
+ svn_error_t *err;
+ svn_boolean_t timestamp_sleep = FALSE;
+ int i;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
+ sizeof(const svn_client_copy_source_t *));
+
+ if (src_paths->nelts > 1 && !move_as_child)
+ return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
+ NULL, NULL);
+
+ for (i = 0; i < src_paths->nelts; i++)
+ {
+ const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
+ svn_client_copy_source_t *copy_source = apr_palloc(pool,
+ sizeof(*copy_source));
+
+ copy_source->path = src_path;
+ copy_source->revision = &head_revision;
+ copy_source->peg_revision = &head_revision;
+
+ APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
+ }
+
+ err = try_copy(&timestamp_sleep,
+ sources, dst_path,
+ TRUE /* is_move */,
+ allow_mixed_revisions,
+ metadata_only,
+ make_parents,
+ FALSE /* ignore_externals */,
+ revprop_table,
+ commit_callback, commit_baton,
+ ctx,
+ subpool);
+
+ /* If the destination exists, try to move the sources as children of the
+ destination. */
+ if (move_as_child && err && (src_paths->nelts == 1)
+ && (err->apr_err == SVN_ERR_ENTRY_EXISTS
+ || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
+ {
+ const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
+ const char *src_basename;
+ svn_boolean_t src_is_url = svn_path_is_url(src_path);
+ svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
+
+ svn_error_clear(err);
+ svn_pool_clear(subpool);
+
+ src_basename = src_is_url ? svn_uri_basename(src_path, pool)
+ : svn_dirent_basename(src_path, pool);
+ dst_path
+ = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
+ subpool)
+ : svn_dirent_join(dst_path, src_basename, subpool);
+
+ err = try_copy(&timestamp_sleep,
+ sources, dst_path,
+ TRUE /* is_move */,
+ allow_mixed_revisions,
+ metadata_only,
+ make_parents,
+ FALSE /* ignore_externals */,
+ revprop_table,
+ commit_callback, commit_baton,
+ ctx,
+ subpool);
+ }
+
+ /* Sleep if required. DST_PATH is not a URL in these cases. */
+ if (timestamp_sleep)
+ svn_io_sleep_for_timestamps(dst_path, subpool);
+
+ svn_pool_destroy(subpool);
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/copy_foreign.c b/contrib/subversion/subversion/libsvn_client/copy_foreign.c
new file mode 100644
index 0000000..8de8a5d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/copy_foreign.c
@@ -0,0 +1,571 @@
+/*
+ * copy_foreign.c: copy from other repository support.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+/*** Includes. ***/
+
+#include <string.h>
+#include "svn_hash.h"
+#include "svn_client.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_ra.h"
+#include "svn_wc.h"
+
+#include <apr_md5.h>
+
+#include "client.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_wc_private.h"
+#include "svn_private_config.h"
+
+struct edit_baton_t
+{
+ apr_pool_t *pool;
+ const char *anchor_abspath;
+
+ svn_wc_context_t *wc_ctx;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+};
+
+struct dir_baton_t
+{
+ apr_pool_t *pool;
+
+ struct dir_baton_t *pb;
+ struct edit_baton_t *eb;
+
+ const char *local_abspath;
+
+ svn_boolean_t created;
+ apr_hash_t *properties;
+
+ int users;
+};
+
+/* svn_delta_editor_t function */
+static svn_error_t *
+edit_open(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **root_baton)
+{
+ struct edit_baton_t *eb = edit_baton;
+ apr_pool_t *dir_pool = svn_pool_create(eb->pool);
+ struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
+
+ db->pool = dir_pool;
+ db->eb = eb;
+ db->users = 1;
+ db->local_abspath = eb->anchor_abspath;
+
+ SVN_ERR(svn_io_make_dir_recursively(eb->anchor_abspath, dir_pool));
+
+ *root_baton = db;
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function */
+static svn_error_t *
+edit_close(void *edit_baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dir_add(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *result_pool,
+ void **child_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ apr_pool_t *dir_pool = svn_pool_create(pb->pool);
+ struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
+ svn_boolean_t under_root;
+
+ pb->users++;
+
+ db->pb = pb;
+ db->eb = pb->eb;
+ db->pool = dir_pool;
+ db->users = 1;
+
+ SVN_ERR(svn_dirent_is_under_root(&under_root, &db->local_abspath,
+ eb->anchor_abspath, path, db->pool));
+ if (! under_root)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Path '%s' is not in the working copy"),
+ svn_dirent_local_style(path, db->pool));
+ }
+
+ SVN_ERR(svn_io_make_dir_recursively(db->local_abspath, db->pool));
+
+ *child_baton = db;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dir_change_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct dir_baton_t *db = dir_baton;
+ struct edit_baton_t *eb = db->eb;
+ svn_prop_kind_t prop_kind;
+
+ prop_kind = svn_property_kind2(name);
+
+ if (prop_kind != svn_prop_regular_kind
+ || ! strcmp(name, SVN_PROP_MERGEINFO))
+ {
+ /* We can't handle DAV, ENTRY and merge specific props here */
+ return SVN_NO_ERROR;
+ }
+
+ if (! db->created)
+ {
+ /* We can still store them in the hash for immediate addition
+ with the svn_wc_add_from_disk2() call */
+ if (! db->properties)
+ db->properties = apr_hash_make(db->pool);
+
+ if (value != NULL)
+ svn_hash_sets(db->properties, apr_pstrdup(db->pool, name),
+ svn_string_dup(value, db->pool));
+ }
+ else
+ {
+ /* We have already notified for this directory, so don't do that again */
+ SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value,
+ svn_depth_empty, FALSE, NULL,
+ NULL, NULL, /* Cancelation */
+ NULL, NULL, /* Notification */
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Releases the directory baton if there are no more users */
+static svn_error_t *
+maybe_done(struct dir_baton_t *db)
+{
+ db->users--;
+
+ if (db->users == 0)
+ {
+ struct dir_baton_t *pb = db->pb;
+
+ svn_pool_clear(db->pool);
+
+ if (pb)
+ SVN_ERR(maybe_done(pb));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ensure_added(struct dir_baton_t *db,
+ apr_pool_t *scratch_pool)
+{
+ if (db->created)
+ return SVN_NO_ERROR;
+
+ if (db->pb)
+ SVN_ERR(ensure_added(db->pb, scratch_pool));
+
+ db->created = TRUE;
+
+ /* Add the directory with all the already collected properties */
+ SVN_ERR(svn_wc_add_from_disk2(db->eb->wc_ctx,
+ db->local_abspath,
+ db->properties,
+ db->eb->notify_func,
+ db->eb->notify_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dir_close(void *dir_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct dir_baton_t *db = dir_baton;
+ /*struct edit_baton_t *eb = db->eb;*/
+
+ SVN_ERR(ensure_added(db, scratch_pool));
+
+ SVN_ERR(maybe_done(db));
+
+ return SVN_NO_ERROR;
+}
+
+struct file_baton_t
+{
+ apr_pool_t *pool;
+
+ struct dir_baton_t *pb;
+ struct edit_baton_t *eb;
+
+ const char *local_abspath;
+ apr_hash_t *properties;
+
+ svn_boolean_t writing;
+ unsigned char digest[APR_MD5_DIGESTSIZE];
+
+ const char *tmp_path;
+};
+
+static svn_error_t *
+file_add(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *result_pool,
+ void **file_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ apr_pool_t *file_pool = svn_pool_create(pb->pool);
+ struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
+ svn_boolean_t under_root;
+
+ pb->users++;
+
+ fb->pool = file_pool;
+ fb->eb = eb;
+ fb->pb = pb;
+
+ SVN_ERR(svn_dirent_is_under_root(&under_root, &fb->local_abspath,
+ eb->anchor_abspath, path, fb->pool));
+ if (! under_root)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Path '%s' is not in the working copy"),
+ svn_dirent_local_style(path, fb->pool));
+ }
+
+ *file_baton = fb;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+file_change_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct file_baton_t *fb = file_baton;
+ svn_prop_kind_t prop_kind;
+
+ prop_kind = svn_property_kind2(name);
+
+ if (prop_kind != svn_prop_regular_kind
+ || ! strcmp(name, SVN_PROP_MERGEINFO))
+ {
+ /* We can't handle DAV, ENTRY and merge specific props here */
+ return SVN_NO_ERROR;
+ }
+
+ /* We store all properties in the hash for immediate addition
+ with the svn_wc_add_from_disk2() call */
+ if (! fb->properties)
+ fb->properties = apr_hash_make(fb->pool);
+
+ if (value != NULL)
+ svn_hash_sets(fb->properties, apr_pstrdup(fb->pool, name),
+ svn_string_dup(value, fb->pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+file_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *result_pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton_t *fb = file_baton;
+ svn_stream_t *target;
+
+ SVN_ERR_ASSERT(! fb->writing);
+
+ SVN_ERR(svn_stream_open_writable(&target, fb->local_abspath, fb->pool,
+ fb->pool));
+
+ fb->writing = TRUE;
+ svn_txdelta_apply(svn_stream_empty(fb->pool) /* source */,
+ target,
+ fb->digest,
+ fb->local_abspath,
+ fb->pool,
+ /* Provide the handler directly */
+ handler, handler_baton);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+file_close(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *scratch_pool)
+{
+ struct file_baton_t *fb = file_baton;
+ struct edit_baton_t *eb = fb->eb;
+ struct dir_baton_t *pb = fb->pb;
+
+ SVN_ERR(ensure_added(pb, fb->pool));
+
+ if (text_checksum)
+ {
+ svn_checksum_t *expected_checksum;
+ svn_checksum_t *actual_checksum;
+
+ SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
+ text_checksum, fb->pool));
+ actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool);
+
+ if (! svn_checksum_match(expected_checksum, actual_checksum))
+ return svn_error_trace(
+ svn_checksum_mismatch_err(expected_checksum,
+ actual_checksum,
+ fb->pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(
+ fb->local_abspath,
+ fb->pool)));
+ }
+
+ SVN_ERR(svn_wc_add_from_disk2(eb->wc_ctx, fb->local_abspath, fb->properties,
+ eb->notify_func, eb->notify_baton,
+ fb->pool));
+
+ svn_pool_destroy(fb->pool);
+ SVN_ERR(maybe_done(pb));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+copy_foreign_dir(svn_ra_session_t *ra_session,
+ svn_client__pathrev_t *location,
+ svn_wc_context_t *wc_ctx,
+ const char *dst_abspath,
+ svn_depth_t depth,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton_t eb;
+ svn_delta_editor_t *editor = svn_delta_default_editor(scratch_pool);
+ const svn_delta_editor_t *wrapped_editor;
+ void *wrapped_baton;
+ const svn_ra_reporter3_t *reporter;
+ void *reporter_baton;
+
+ eb.pool = scratch_pool;
+ eb.anchor_abspath = dst_abspath;
+
+ eb.wc_ctx = wc_ctx;
+ eb.notify_func = notify_func;
+ eb.notify_baton = notify_baton;
+
+ editor->open_root = edit_open;
+ editor->close_edit = edit_close;
+
+ editor->add_directory = dir_add;
+ editor->change_dir_prop = dir_change_prop;
+ editor->close_directory = dir_close;
+
+ editor->add_file = file_add;
+ editor->change_file_prop = file_change_prop;
+ editor->apply_textdelta = file_textdelta;
+ editor->close_file = file_close;
+
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
+ editor, &eb,
+ &wrapped_editor, &wrapped_baton,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton,
+ location->rev, "", svn_depth_infinity,
+ FALSE, FALSE, wrapped_editor, wrapped_baton,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(reporter->set_path(reporter_baton, "", location->rev, depth,
+ TRUE /* incomplete */,
+ NULL, scratch_pool));
+
+ SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__copy_foreign(const char *url,
+ const char *dst_abspath,
+ svn_opt_revision_t *peg_revision,
+ svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t make_parents,
+ svn_boolean_t already_locked,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_session_t *ra_session;
+ svn_client__pathrev_t *loc;
+ svn_node_kind_t kind;
+ svn_node_kind_t wc_kind;
+ const char *dir_abspath;
+
+ SVN_ERR_ASSERT(svn_path_is_url(url));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+ /* Do we need to validate/update revisions? */
+
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
+ url, NULL,
+ peg_revision,
+ revision, ctx,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, scratch_pool));
+
+ if (kind != svn_node_file && kind != svn_node_dir)
+ return svn_error_createf(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a valid location inside a repository"),
+ url);
+
+ SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dst_abspath, FALSE, TRUE,
+ scratch_pool));
+
+ if (wc_kind != svn_node_none)
+ {
+ return svn_error_createf(
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("'%s' is already under version control"),
+ svn_dirent_local_style(dst_abspath, scratch_pool));
+ }
+
+ dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
+ SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath,
+ FALSE, FALSE, scratch_pool));
+
+ if (wc_kind == svn_node_none)
+ {
+ if (make_parents)
+ SVN_ERR(svn_client__make_local_parents(dir_abspath, make_parents, ctx,
+ scratch_pool));
+
+ SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath,
+ FALSE, FALSE, scratch_pool));
+ }
+
+ if (wc_kind != svn_node_dir)
+ return svn_error_createf(
+ SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("Can't add '%s', because no parent directory is found"),
+ svn_dirent_local_style(dst_abspath, scratch_pool));
+
+
+ if (kind == svn_node_file)
+ {
+ svn_stream_t *target;
+ apr_hash_t *props;
+ apr_hash_index_t *hi;
+ SVN_ERR(svn_stream_open_writable(&target, dst_abspath, scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, target, NULL, &props,
+ scratch_pool));
+
+ if (props != NULL)
+ for (hi = apr_hash_first(scratch_pool, props); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+
+ if (svn_property_kind2(name) != svn_prop_regular_kind
+ || ! strcmp(name, SVN_PROP_MERGEINFO))
+ {
+ /* We can't handle DAV, ENTRY and merge specific props here */
+ svn_hash_sets(props, name, NULL);
+ }
+ }
+
+ if (!already_locked)
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ svn_wc_add_from_disk2(ctx->wc_ctx, dst_abspath, props,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool),
+ ctx->wc_ctx, dir_abspath, FALSE, scratch_pool);
+ else
+ SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, dst_abspath, props,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool));
+ }
+ else
+ {
+ if (!already_locked)
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ copy_foreign_dir(ra_session, loc,
+ ctx->wc_ctx, dst_abspath,
+ depth,
+ ctx->notify_func2, ctx->notify_baton2,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool),
+ ctx->wc_ctx, dir_abspath, FALSE, scratch_pool);
+ else
+ SVN_ERR(copy_foreign_dir(ra_session, loc,
+ ctx->wc_ctx, dst_abspath,
+ depth,
+ ctx->notify_func2, ctx->notify_baton2,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/ctx.c b/contrib/subversion/subversion/libsvn_client/ctx.c
new file mode 100644
index 0000000..185b91e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/ctx.c
@@ -0,0 +1,112 @@
+/*
+ * ctx.c: initialization function for client context
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_pools.h>
+#include "svn_hash.h"
+#include "svn_client.h"
+#include "svn_error.h"
+
+#include "private/svn_wc_private.h"
+
+
+/*** Code. ***/
+
+/* Call the notify_func of the context provided by BATON, if non-NULL. */
+static void
+call_notify_func(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
+{
+ const svn_client_ctx_t *ctx = baton;
+
+ if (ctx->notify_func)
+ ctx->notify_func(ctx->notify_baton, n->path, n->action, n->kind,
+ n->mime_type, n->content_state, n->prop_state,
+ n->revision);
+}
+
+static svn_error_t *
+call_conflict_func(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *conflict,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_ctx_t *ctx = baton;
+
+ if (ctx->conflict_func)
+ {
+ const svn_wc_conflict_description_t *cd;
+
+ cd = svn_wc__cd2_to_cd(conflict, scratch_pool);
+ SVN_ERR(ctx->conflict_func(result, cd, ctx->conflict_baton,
+ result_pool));
+ }
+ else
+ {
+ /* We have to set a result; so we postpone */
+ *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
+ NULL, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_create_context2(svn_client_ctx_t **ctx,
+ apr_hash_t *cfg_hash,
+ apr_pool_t *pool)
+{
+ svn_config_t *cfg_config;
+
+ *ctx = apr_pcalloc(pool, sizeof(svn_client_ctx_t));
+
+ (*ctx)->notify_func2 = call_notify_func;
+ (*ctx)->notify_baton2 = *ctx;
+
+ (*ctx)->conflict_func2 = call_conflict_func;
+ (*ctx)->conflict_baton2 = *ctx;
+
+ (*ctx)->config = cfg_hash;
+
+ if (cfg_hash)
+ cfg_config = svn_hash_gets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG);
+ else
+ cfg_config = NULL;
+
+ SVN_ERR(svn_wc_context_create(&(*ctx)->wc_ctx, cfg_config, pool,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_create_context(svn_client_ctx_t **ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_create_context2(ctx, NULL, pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/delete.c b/contrib/subversion/subversion/libsvn_client/delete.c
new file mode 100644
index 0000000..2f4ee66
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/delete.c
@@ -0,0 +1,595 @@
+/*
+ * delete.c: wrappers around wc delete functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_file_io.h>
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "client.h"
+
+#include "private/svn_client_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_ra_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* Baton for find_undeletables */
+struct can_delete_baton_t
+{
+ const char *root_abspath;
+ svn_boolean_t target_missing;
+};
+
+/* An svn_wc_status_func4_t callback function for finding
+ status structures which are not safely deletable. */
+static svn_error_t *
+find_undeletables(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *pool)
+{
+ if (status->node_status == svn_wc_status_missing)
+ {
+ struct can_delete_baton_t *cdt = baton;
+
+ if (strcmp(cdt->root_abspath, local_abspath) == 0)
+ cdt->target_missing = TRUE;
+ }
+
+ /* Check for error-ful states. */
+ if (status->node_status == svn_wc_status_obstructed)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' is in the way of the resource "
+ "actually under version control"),
+ svn_dirent_local_style(local_abspath, pool));
+ else if (! status->versioned)
+ return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath, pool));
+ else if ((status->node_status == svn_wc_status_added
+ || status->node_status == svn_wc_status_replaced)
+ && status->text_status == svn_wc_status_normal
+ && (status->prop_status == svn_wc_status_normal
+ || status->prop_status == svn_wc_status_none))
+ {
+ /* Unmodified copy. Go ahead, remove it */
+ }
+ else if (status->node_status != svn_wc_status_normal
+ && status->node_status != svn_wc_status_deleted
+ && status->node_status != svn_wc_status_missing)
+ return svn_error_createf(SVN_ERR_CLIENT_MODIFIED, NULL,
+ _("'%s' has local modifications -- commit or "
+ "revert them first"),
+ svn_dirent_local_style(local_abspath, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Check whether LOCAL_ABSPATH is an external and raise an error if it is.
+
+ A file external should not be deleted since the file external is
+ implemented as a switched file and it would delete the file the
+ file external is switched to, which is not the behavior the user
+ would probably want.
+
+ A directory external should not be deleted since it is the root
+ of a different working copy. */
+static svn_error_t *
+check_external(const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t external_kind;
+ const char *defining_abspath;
+
+ SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, NULL,
+ NULL, NULL,
+ ctx->wc_ctx, local_abspath,
+ local_abspath, TRUE,
+ scratch_pool, scratch_pool));
+
+ if (external_kind != svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_CANNOT_DELETE_FILE_EXTERNAL, NULL,
+ _("Cannot remove the external at '%s'; "
+ "please edit or delete the svn:externals "
+ "property on '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool),
+ svn_dirent_local_style(defining_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the path can be deleted without losing stuff,
+ i.e. ensure that there are no modified or unversioned resources
+ under PATH. This is similar to checking the output of the status
+ command. CTX is used for the client's config options. POOL is
+ used for all temporary allocations. */
+static svn_error_t *
+can_delete_node(svn_boolean_t *target_missing,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *ignores;
+ struct can_delete_baton_t cdt;
+
+ /* Use an infinite-depth status check to see if there's anything in
+ or under PATH which would make it unsafe for deletion. The
+ status callback function find_undeletables() makes the
+ determination, returning an error if it finds anything that shouldn't
+ be deleted. */
+
+ SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool));
+
+ cdt.root_abspath = local_abspath;
+ cdt.target_missing = FALSE;
+
+ SVN_ERR(svn_wc_walk_status(ctx->wc_ctx,
+ local_abspath,
+ svn_depth_infinity,
+ FALSE /* get_all */,
+ FALSE /* no_ignore */,
+ FALSE /* ignore_text_mod */,
+ ignores,
+ find_undeletables, &cdt,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ scratch_pool));
+
+ if (target_missing)
+ *target_missing = cdt.target_missing;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+path_driver_cb_func(void **dir_baton,
+ void *parent_baton,
+ void *callback_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *editor = callback_baton;
+ *dir_baton = NULL;
+ return editor->delete_entry(path, SVN_INVALID_REVNUM, parent_baton, pool);
+}
+
+static svn_error_t *
+single_repos_delete(svn_ra_session_t *ra_session,
+ const char *repos_root,
+ const apr_array_header_t *relpaths,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *editor;
+ apr_hash_t *commit_revprops;
+ void *edit_baton;
+ const char *log_msg;
+ int i;
+ svn_error_t *err;
+
+ /* Create new commit items and add them to the array. */
+ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
+ {
+ svn_client_commit_item3_t *item;
+ const char *tmp_file;
+ apr_array_header_t *commit_items
+ = apr_array_make(pool, relpaths->nelts, sizeof(item));
+
+ for (i = 0; i < relpaths->nelts; i++)
+ {
+ const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *);
+
+ item = svn_client_commit_item3_create(pool);
+ item->url = svn_path_url_add_component2(repos_root, relpath, pool);
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+ }
+ SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
+ ctx, pool));
+ if (! log_msg)
+ return SVN_NO_ERROR;
+ }
+ else
+ log_msg = "";
+
+ SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
+ log_msg, ctx, pool));
+
+ /* Fetch RA commit editor */
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
+ svn_client__get_shim_callbacks(ctx->wc_ctx,
+ NULL, pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ commit_revprops,
+ commit_callback,
+ commit_baton,
+ NULL, TRUE, /* No lock tokens */
+ pool));
+
+ /* Call the path-based editor driver. */
+ err = svn_delta_path_driver2(editor, edit_baton, relpaths, TRUE,
+ path_driver_cb_func, (void *)editor, pool);
+
+ if (err)
+ {
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ editor->abort_edit(edit_baton, pool)));
+ }
+
+ /* Close the edit. */
+ return svn_error_trace(editor->close_edit(edit_baton, pool));
+}
+
+
+/* Structure for tracking remote delete targets associated with a
+ specific repository. */
+struct repos_deletables_t
+{
+ svn_ra_session_t *ra_session;
+ apr_array_header_t *target_uris;
+};
+
+
+static svn_error_t *
+delete_urls_multi_repos(const apr_array_header_t *uris,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_hash_t *deletables = apr_hash_make(pool);
+ apr_pool_t *iterpool;
+ apr_hash_index_t *hi;
+ int i;
+
+ /* Create a hash mapping repository root URLs -> repos_deletables_t *
+ structures. */
+ for (i = 0; i < uris->nelts; i++)
+ {
+ const char *uri = APR_ARRAY_IDX(uris, i, const char *);
+ struct repos_deletables_t *repos_deletables = NULL;
+ const char *repos_relpath;
+ svn_node_kind_t kind;
+
+ for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
+ {
+ const char *repos_root = svn__apr_hash_index_key(hi);
+
+ repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
+ if (repos_relpath)
+ {
+ /* Great! We've found another URI underneath this
+ session. We'll pick out the related RA session for
+ use later, store the new target, and move on. */
+ repos_deletables = svn__apr_hash_index_val(hi);
+ APR_ARRAY_PUSH(repos_deletables->target_uris, const char *) =
+ apr_pstrdup(pool, uri);
+ break;
+ }
+ }
+
+ /* If we haven't created a repos_deletable structure for this
+ delete target, we need to do. That means opening up an RA
+ session and initializing its targets list. */
+ if (!repos_deletables)
+ {
+ svn_ra_session_t *ra_session = NULL;
+ const char *repos_root;
+ apr_array_header_t *target_uris;
+
+ /* Open an RA session to (ultimately) the root of the
+ repository in which URI is found. */
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, uri, NULL,
+ ctx, pool, pool));
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
+ SVN_ERR(svn_ra_reparent(ra_session, repos_root, pool));
+ repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
+
+ /* Make a new relpaths list for this repository, and add
+ this URI's relpath to it. */
+ target_uris = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(target_uris, const char *) = apr_pstrdup(pool, uri);
+
+ /* Build our repos_deletables_t item and stash it in the
+ hash. */
+ repos_deletables = apr_pcalloc(pool, sizeof(*repos_deletables));
+ repos_deletables->ra_session = ra_session;
+ repos_deletables->target_uris = target_uris;
+ svn_hash_sets(deletables, repos_root, repos_deletables);
+ }
+
+ /* If we get here, we should have been able to calculate a
+ repos_relpath for this URI. Let's make sure. (We return an
+ RA error code otherwise for 1.6 compatibility.) */
+ if (!repos_relpath || !*repos_relpath)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ "URL '%s' not within a repository", uri);
+
+ /* Now, test to see if the thing actually exists in HEAD. */
+ SVN_ERR(svn_ra_check_path(repos_deletables->ra_session, repos_relpath,
+ SVN_INVALID_REVNUM, &kind, pool));
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ "URL '%s' does not exist", uri);
+ }
+
+ /* Now we iterate over the DELETABLES hash, issuing a commit for
+ each repository with its associated collected targets. */
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
+ {
+ const char *repos_root = svn__apr_hash_index_key(hi);
+ struct repos_deletables_t *repos_deletables = svn__apr_hash_index_val(hi);
+ const char *base_uri;
+ apr_array_header_t *target_relpaths;
+
+ svn_pool_clear(iterpool);
+
+ /* We want to anchor the commit on the longest common path
+ across the targets for this one repository. If, however, one
+ of our targets is that longest common path, we need instead
+ anchor the commit on that path's immediate parent. Because
+ we're asking svn_uri_condense_targets() to remove
+ redundancies, this situation should be detectable by their
+ being returned either a) only a single, empty-path, target
+ relpath, or b) no target relpaths at all. */
+ SVN_ERR(svn_uri_condense_targets(&base_uri, &target_relpaths,
+ repos_deletables->target_uris,
+ TRUE, iterpool, iterpool));
+ SVN_ERR_ASSERT(!svn_path_is_empty(base_uri));
+ if (target_relpaths->nelts == 0)
+ {
+ const char *target_relpath;
+
+ svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
+ APR_ARRAY_PUSH(target_relpaths, const char *) = target_relpath;
+ }
+ else if ((target_relpaths->nelts == 1)
+ && (svn_path_is_empty(APR_ARRAY_IDX(target_relpaths, 0,
+ const char *))))
+ {
+ const char *target_relpath;
+
+ svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
+ APR_ARRAY_IDX(target_relpaths, 0, const char *) = target_relpath;
+ }
+
+ SVN_ERR(svn_ra_reparent(repos_deletables->ra_session, base_uri, pool));
+ SVN_ERR(single_repos_delete(repos_deletables->ra_session, repos_root,
+ target_relpaths,
+ revprop_table, commit_callback,
+ commit_baton, ctx, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__wc_delete(const char *local_abspath,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ svn_boolean_t keep_local,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_boolean_t target_missing = FALSE;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(check_external(local_abspath, ctx, pool));
+
+ if (!force && !keep_local)
+ /* Verify that there are no "awkward" files */
+ SVN_ERR(can_delete_node(&target_missing, local_abspath, ctx, pool));
+
+ if (!dry_run)
+ /* Mark the entry for commit deletion and perform wc deletion */
+ return svn_error_trace(svn_wc_delete4(ctx->wc_ctx, local_abspath,
+ keep_local || target_missing
+ /*keep_local */,
+ TRUE /* delete_unversioned */,
+ ctx->cancel_func, ctx->cancel_baton,
+ notify_func, notify_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__wc_delete_many(const apr_array_header_t *targets,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ svn_boolean_t keep_local,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ int i;
+ svn_boolean_t has_non_missing = FALSE;
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *local_abspath = APR_ARRAY_IDX(targets, i, const char *);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(check_external(local_abspath, ctx, pool));
+
+ if (!force && !keep_local)
+ {
+ svn_boolean_t missing;
+ /* Verify that there are no "awkward" files */
+
+ SVN_ERR(can_delete_node(&missing, local_abspath, ctx, pool));
+
+ if (! missing)
+ has_non_missing = TRUE;
+ }
+ else
+ has_non_missing = TRUE;
+ }
+
+ if (!dry_run)
+ {
+ /* Mark the entry for commit deletion and perform wc deletion */
+
+ /* If none of the targets exists, pass keep local TRUE, to avoid
+ deleting case-different files. Detecting this in the generic case
+ from the delete code is expensive */
+ return svn_error_trace(svn_wc__delete_many(ctx->wc_ctx, targets,
+ keep_local || !has_non_missing,
+ TRUE /* delete_unversioned_target */,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ notify_func, notify_baton,
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_delete4(const apr_array_header_t *paths,
+ svn_boolean_t force,
+ svn_boolean_t keep_local,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_boolean_t is_url;
+
+ if (! paths->nelts)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_client__assert_homogeneous_target_type(paths));
+ is_url = svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *));
+
+ if (is_url)
+ {
+ SVN_ERR(delete_urls_multi_repos(paths, revprop_table, commit_callback,
+ commit_baton, ctx, pool));
+ }
+ else
+ {
+ const char *local_abspath;
+ apr_hash_t *wcroots;
+ apr_hash_index_t *hi;
+ int i;
+ int j;
+ apr_pool_t *iterpool;
+ svn_boolean_t is_new_target;
+
+ /* Build a map of wcroots and targets within them. */
+ wcroots = apr_hash_make(pool);
+ iterpool = svn_pool_create(pool);
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *wcroot_abspath;
+ apr_array_header_t *targets;
+
+ svn_pool_clear(iterpool);
+
+ /* See if the user wants us to stop. */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath,
+ APR_ARRAY_IDX(paths, i,
+ const char *),
+ pool));
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ local_abspath, pool, iterpool));
+ targets = svn_hash_gets(wcroots, wcroot_abspath);
+ if (targets == NULL)
+ {
+ targets = apr_array_make(pool, 1, sizeof(const char *));
+ svn_hash_sets(wcroots, wcroot_abspath, targets);
+ }
+
+ /* Make sure targets are unique. */
+ is_new_target = TRUE;
+ for (j = 0; j < targets->nelts; j++)
+ {
+ if (strcmp(APR_ARRAY_IDX(targets, j, const char *),
+ local_abspath) == 0)
+ {
+ is_new_target = FALSE;
+ break;
+ }
+ }
+
+ if (is_new_target)
+ APR_ARRAY_PUSH(targets, const char *) = local_abspath;
+ }
+
+ /* Delete the targets from each working copy in turn. */
+ for (hi = apr_hash_first(pool, wcroots); hi; hi = apr_hash_next(hi))
+ {
+ const char *root_abspath;
+ const apr_array_header_t *targets = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_dirent_condense_targets(&root_abspath, NULL, targets,
+ FALSE, iterpool, iterpool));
+
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ svn_client__wc_delete_many(targets, force, FALSE, keep_local,
+ ctx->notify_func2, ctx->notify_baton2,
+ ctx, iterpool),
+ ctx->wc_ctx, root_abspath, TRUE /* lock_anchor */,
+ iterpool);
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/deprecated.c b/contrib/subversion/subversion/libsvn_client/deprecated.c
new file mode 100644
index 0000000..a67a69b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/deprecated.c
@@ -0,0 +1,2966 @@
+/*
+ * deprecated.c: holding file for all deprecated APIs.
+ * "we can't lose 'em, but we can shun 'em!"
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+/* We define this here to remove any further warnings about the usage of
+ deprecated functions in this file. */
+#define SVN_DEPRECATED
+
+#include <string.h>
+#include "svn_client.h"
+#include "svn_path.h"
+#include "svn_compat.h"
+#include "svn_hash.h"
+#include "svn_props.h"
+#include "svn_utf.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+
+#include "client.h"
+#include "mergeinfo.h"
+
+#include "private/svn_opt_private.h"
+#include "private/svn_wc_private.h"
+#include "svn_private_config.h"
+
+
+
+
+/*** Code. ***/
+
+
+/* Baton for capture_commit_info() */
+struct capture_baton_t {
+ svn_commit_info_t **info;
+ apr_pool_t *pool;
+};
+
+
+/* Callback which implements svn_commit_callback2_t for use with some
+ backward compat functions. */
+static svn_error_t *
+capture_commit_info(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct capture_baton_t *cb = baton;
+
+ *(cb->info) = svn_commit_info_dup(commit_info, cb->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** From add.c ***/
+svn_error_t *
+svn_client_add4(const char *path,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t no_ignore,
+ svn_boolean_t add_parents,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_add5(path, depth, force, no_ignore, FALSE, add_parents,
+ ctx, pool);
+}
+
+svn_error_t *
+svn_client_add3(const char *path,
+ svn_boolean_t recursive,
+ svn_boolean_t force,
+ svn_boolean_t no_ignore,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_add4(path, SVN_DEPTH_INFINITY_OR_EMPTY(recursive),
+ force, no_ignore, FALSE, ctx,
+ pool);
+}
+
+svn_error_t *
+svn_client_add2(const char *path,
+ svn_boolean_t recursive,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_add3(path, recursive, force, FALSE, ctx, pool);
+}
+
+svn_error_t *
+svn_client_add(const char *path,
+ svn_boolean_t recursive,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_add3(path, recursive, FALSE, FALSE, ctx, pool);
+}
+
+svn_error_t *
+svn_client_mkdir3(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct capture_baton_t cb;
+
+ cb.info = commit_info_p;
+ cb.pool = pool;
+
+ return svn_client_mkdir4(paths, make_parents, revprop_table,
+ capture_commit_info, &cb, ctx, pool);
+}
+
+svn_error_t *
+svn_client_mkdir2(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_mkdir3(commit_info_p, paths, FALSE, NULL, ctx, pool);
+}
+
+
+svn_error_t *
+svn_client_mkdir(svn_client_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_commit_info_t *commit_info = NULL;
+ svn_error_t *err;
+
+ err = svn_client_mkdir2(&commit_info, paths, ctx, pool);
+ /* These structs have the same layout for the common fields. */
+ *commit_info_p = (svn_client_commit_info_t *) commit_info;
+ return svn_error_trace(err);
+}
+
+/*** From blame.c ***/
+
+struct blame_receiver_wrapper_baton2 {
+ void *baton;
+ svn_client_blame_receiver2_t receiver;
+};
+
+static svn_error_t *
+blame_wrapper_receiver2(void *baton,
+ svn_revnum_t start_revnum,
+ svn_revnum_t end_revnum,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ apr_hash_t *rev_props,
+ svn_revnum_t merged_revision,
+ apr_hash_t *merged_rev_props,
+ const char *merged_path,
+ const char *line,
+ svn_boolean_t local_change,
+ apr_pool_t *pool)
+{
+ struct blame_receiver_wrapper_baton2 *brwb = baton;
+ const char *author = NULL;
+ const char *date = NULL;
+ const char *merged_author = NULL;
+ const char *merged_date = NULL;
+
+ if (rev_props != NULL)
+ {
+ author = svn_prop_get_value(rev_props, SVN_PROP_REVISION_AUTHOR);
+ date = svn_prop_get_value(rev_props, SVN_PROP_REVISION_DATE);
+ }
+ if (merged_rev_props != NULL)
+ {
+ merged_author = svn_prop_get_value(merged_rev_props,
+ SVN_PROP_REVISION_AUTHOR);
+ merged_date = svn_prop_get_value(merged_rev_props,
+ SVN_PROP_REVISION_DATE);
+ }
+
+ if (brwb->receiver)
+ return brwb->receiver(brwb->baton, line_no, revision, author, date,
+ merged_revision, merged_author, merged_date,
+ merged_path, line, pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_blame4(const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ const svn_diff_file_options_t *diff_options,
+ svn_boolean_t ignore_mime_type,
+ svn_boolean_t include_merged_revisions,
+ svn_client_blame_receiver2_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct blame_receiver_wrapper_baton2 baton;
+
+ baton.receiver = receiver;
+ baton.baton = receiver_baton;
+
+ return svn_client_blame5(target, peg_revision, start, end, diff_options,
+ ignore_mime_type, include_merged_revisions,
+ blame_wrapper_receiver2, &baton, ctx, pool);
+}
+
+
+/* Baton for use with wrap_blame_receiver */
+struct blame_receiver_wrapper_baton {
+ void *baton;
+ svn_client_blame_receiver_t receiver;
+};
+
+/* This implements svn_client_blame_receiver2_t */
+static svn_error_t *
+blame_wrapper_receiver(void *baton,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ svn_revnum_t merged_revision,
+ const char *merged_author,
+ const char *merged_date,
+ const char *merged_path,
+ const char *line,
+ apr_pool_t *pool)
+{
+ struct blame_receiver_wrapper_baton *brwb = baton;
+
+ if (brwb->receiver)
+ return brwb->receiver(brwb->baton,
+ line_no, revision, author, date, line, pool);
+
+ return SVN_NO_ERROR;
+}
+
+static void
+wrap_blame_receiver(svn_client_blame_receiver2_t *receiver2,
+ void **receiver2_baton,
+ svn_client_blame_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ struct blame_receiver_wrapper_baton *brwb = apr_palloc(pool, sizeof(*brwb));
+
+ /* Set the user provided old format callback in the baton. */
+ brwb->baton = receiver_baton;
+ brwb->receiver = receiver;
+
+ *receiver2_baton = brwb;
+ *receiver2 = blame_wrapper_receiver;
+}
+
+svn_error_t *
+svn_client_blame3(const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ const svn_diff_file_options_t *diff_options,
+ svn_boolean_t ignore_mime_type,
+ svn_client_blame_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_client_blame_receiver2_t receiver2;
+ void *receiver2_baton;
+
+ wrap_blame_receiver(&receiver2, &receiver2_baton, receiver, receiver_baton,
+ pool);
+
+ return svn_client_blame4(target, peg_revision, start, end, diff_options,
+ ignore_mime_type, FALSE, receiver2, receiver2_baton,
+ ctx, pool);
+}
+
+/* svn_client_blame3 guarantees 'no EOL chars' as part of the receiver
+ LINE argument. Older versions depend on the fact that if a CR is
+ required, that CR is already part of the LINE data.
+
+ Because of this difference, we need to trap old receivers and append
+ a CR to LINE before passing it on to the actual receiver on platforms
+ which want CRLF line termination.
+
+*/
+
+struct wrapped_receiver_baton_s
+{
+ svn_client_blame_receiver_t orig_receiver;
+ void *orig_baton;
+};
+
+static svn_error_t *
+wrapped_receiver(void *baton,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ const char *line,
+ apr_pool_t *pool)
+{
+ struct wrapped_receiver_baton_s *b = baton;
+ svn_stringbuf_t *expanded_line = svn_stringbuf_create(line, pool);
+
+ svn_stringbuf_appendbyte(expanded_line, '\r');
+
+ return b->orig_receiver(b->orig_baton, line_no, revision, author,
+ date, expanded_line->data, pool);
+}
+
+static void
+wrap_pre_blame3_receiver(svn_client_blame_receiver_t *receiver,
+ void **receiver_baton,
+ apr_pool_t *pool)
+{
+ if (sizeof(APR_EOL_STR) == 3)
+ {
+ struct wrapped_receiver_baton_s *b = apr_palloc(pool,sizeof(*b));
+
+ b->orig_receiver = *receiver;
+ b->orig_baton = *receiver_baton;
+
+ *receiver_baton = b;
+ *receiver = wrapped_receiver;
+ }
+}
+
+svn_error_t *
+svn_client_blame2(const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ svn_client_blame_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ wrap_pre_blame3_receiver(&receiver, &receiver_baton, pool);
+ return svn_client_blame3(target, peg_revision, start, end,
+ svn_diff_file_options_create(pool), FALSE,
+ receiver, receiver_baton, ctx, pool);
+}
+svn_error_t *
+svn_client_blame(const char *target,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ svn_client_blame_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ wrap_pre_blame3_receiver(&receiver, &receiver_baton, pool);
+ return svn_client_blame2(target, end, start, end,
+ receiver, receiver_baton, ctx, pool);
+}
+
+/*** From cmdline.c ***/
+svn_error_t *
+svn_client_args_to_target_array(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_args_to_target_array2(targets_p, os, known_targets, ctx,
+ FALSE, pool);
+}
+
+/*** From commit.c ***/
+svn_error_t *
+svn_client_import4(const char *path,
+ const char *url,
+ svn_depth_t depth,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_unknown_node_types,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_client_import5(path, url, depth, no_ignore,
+ FALSE, ignore_unknown_node_types,
+ revprop_table,
+ NULL, NULL,
+ commit_callback, commit_baton,
+ ctx, pool));
+}
+
+
+svn_error_t *
+svn_client_import3(svn_commit_info_t **commit_info_p,
+ const char *path,
+ const char *url,
+ svn_depth_t depth,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_unknown_node_types,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct capture_baton_t cb;
+
+ cb.info = commit_info_p;
+ cb.pool = pool;
+
+ return svn_client_import4(path, url, depth, no_ignore,
+ ignore_unknown_node_types, revprop_table,
+ capture_commit_info, &cb, ctx, pool);
+}
+
+svn_error_t *
+svn_client_import2(svn_commit_info_t **commit_info_p,
+ const char *path,
+ const char *url,
+ svn_boolean_t nonrecursive,
+ svn_boolean_t no_ignore,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_import3(commit_info_p,
+ path, url,
+ SVN_DEPTH_INFINITY_OR_FILES(! nonrecursive),
+ no_ignore, FALSE, NULL, ctx, pool);
+}
+
+svn_error_t *
+svn_client_import(svn_client_commit_info_t **commit_info_p,
+ const char *path,
+ const char *url,
+ svn_boolean_t nonrecursive,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_commit_info_t *commit_info = NULL;
+ svn_error_t *err;
+
+ err = svn_client_import2(&commit_info,
+ path, url, nonrecursive,
+ FALSE, ctx, pool);
+ /* These structs have the same layout for the common fields. */
+ *commit_info_p = (svn_client_commit_info_t *) commit_info;
+ return svn_error_trace(err);
+}
+
+
+/* Wrapper notify_func2 function and baton for downgrading
+ svn_wc_notify_commit_copied and svn_wc_notify_commit_copied_replaced
+ to svn_wc_notify_commit_added and svn_wc_notify_commit_replaced,
+ respectively. */
+struct downgrade_commit_copied_notify_baton
+{
+ svn_wc_notify_func2_t orig_notify_func2;
+ void *orig_notify_baton2;
+};
+
+static void
+downgrade_commit_copied_notify_func(void *baton,
+ const svn_wc_notify_t *notify,
+ apr_pool_t *pool)
+{
+ struct downgrade_commit_copied_notify_baton *b = baton;
+
+ if (notify->action == svn_wc_notify_commit_copied)
+ {
+ svn_wc_notify_t *my_notify = svn_wc_dup_notify(notify, pool);
+ my_notify->action = svn_wc_notify_commit_added;
+ notify = my_notify;
+ }
+ else if (notify->action == svn_wc_notify_commit_copied_replaced)
+ {
+ svn_wc_notify_t *my_notify = svn_wc_dup_notify(notify, pool);
+ my_notify->action = svn_wc_notify_commit_replaced;
+ notify = my_notify;
+ }
+
+ /* Call the wrapped notification system (if any) with MY_NOTIFY,
+ which is either the original NOTIFY object, or a tweaked deep
+ copy thereof. */
+ if (b->orig_notify_func2)
+ b->orig_notify_func2(b->orig_notify_baton2, notify, pool);
+}
+
+svn_error_t *
+svn_client_commit5(const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_boolean_t keep_locks,
+ svn_boolean_t keep_changelists,
+ svn_boolean_t commit_as_operations,
+ const apr_array_header_t *changelists,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_commit6(targets, depth, keep_locks, keep_changelists,
+ commit_as_operations,
+ FALSE, /* include_file_externals */
+ FALSE, /* include_dir_externals */
+ changelists, revprop_table, commit_callback,
+ commit_baton, ctx, pool);
+}
+
+svn_error_t *
+svn_client_commit4(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_boolean_t keep_locks,
+ svn_boolean_t keep_changelists,
+ const apr_array_header_t *changelists,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct capture_baton_t cb;
+ struct downgrade_commit_copied_notify_baton notify_baton;
+ svn_error_t *err;
+
+ notify_baton.orig_notify_func2 = ctx->notify_func2;
+ notify_baton.orig_notify_baton2 = ctx->notify_baton2;
+
+ *commit_info_p = NULL;
+ cb.info = commit_info_p;
+ cb.pool = pool;
+
+ /* Swap out the notification system (if any) with a thin filtering
+ wrapper. */
+ if (ctx->notify_func2)
+ {
+ ctx->notify_func2 = downgrade_commit_copied_notify_func;
+ ctx->notify_baton2 = &notify_baton;
+ }
+
+ err = svn_client_commit5(targets, depth, keep_locks, keep_changelists, FALSE,
+ changelists, revprop_table,
+ capture_commit_info, &cb, ctx, pool);
+
+ /* Ensure that the original notification system is in place. */
+ ctx->notify_func2 = notify_baton.orig_notify_func2;
+ ctx->notify_baton2 = notify_baton.orig_notify_baton2;
+
+ SVN_ERR(err);
+
+ if (! *commit_info_p)
+ *commit_info_p = svn_create_commit_info(pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_commit3(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *targets,
+ svn_boolean_t recurse,
+ svn_boolean_t keep_locks,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_depth_t depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse);
+
+ return svn_client_commit4(commit_info_p, targets, depth, keep_locks,
+ FALSE, NULL, NULL, ctx, pool);
+}
+
+svn_error_t *
+svn_client_commit2(svn_client_commit_info_t **commit_info_p,
+ const apr_array_header_t *targets,
+ svn_boolean_t recurse,
+ svn_boolean_t keep_locks,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_commit_info_t *commit_info = NULL;
+ svn_error_t *err;
+
+ err = svn_client_commit3(&commit_info, targets, recurse, keep_locks,
+ ctx, pool);
+ /* These structs have the same layout for the common fields. */
+ *commit_info_p = (svn_client_commit_info_t *) commit_info;
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_client_commit(svn_client_commit_info_t **commit_info_p,
+ const apr_array_header_t *targets,
+ svn_boolean_t nonrecursive,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_commit2(commit_info_p, targets,
+ ! nonrecursive,
+ TRUE,
+ ctx, pool);
+}
+
+/*** From copy.c ***/
+svn_error_t *
+svn_client_copy5(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *sources,
+ const char *dst_path,
+ svn_boolean_t copy_as_child,
+ svn_boolean_t make_parents,
+ svn_boolean_t ignore_externals,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct capture_baton_t cb;
+
+ cb.info = commit_info_p;
+ cb.pool = pool;
+
+ return svn_client_copy6(sources, dst_path, copy_as_child, make_parents,
+ ignore_externals, revprop_table,
+ capture_commit_info, &cb, ctx, pool);
+}
+
+svn_error_t *
+svn_client_copy4(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *sources,
+ const char *dst_path,
+ svn_boolean_t copy_as_child,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_copy5(commit_info_p, sources, dst_path, copy_as_child,
+ make_parents, FALSE, revprop_table, ctx, pool);
+}
+
+svn_error_t *
+svn_client_copy3(svn_commit_info_t **commit_info_p,
+ const char *src_path,
+ const svn_opt_revision_t *src_revision,
+ const char *dst_path,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *sources = apr_array_make(pool, 1,
+ sizeof(const svn_client_copy_source_t *));
+ svn_client_copy_source_t copy_source;
+
+ copy_source.path = src_path;
+ copy_source.revision = src_revision;
+ copy_source.peg_revision = src_revision;
+
+ APR_ARRAY_PUSH(sources, const svn_client_copy_source_t *) = &copy_source;
+
+ return svn_client_copy4(commit_info_p, sources, dst_path, FALSE, FALSE,
+ NULL, ctx, pool);
+}
+
+svn_error_t *
+svn_client_copy2(svn_commit_info_t **commit_info_p,
+ const char *src_path,
+ const svn_opt_revision_t *src_revision,
+ const char *dst_path,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ err = svn_client_copy3(commit_info_p, src_path, src_revision,
+ dst_path, ctx, pool);
+
+ /* If the target exists, try to copy the source as a child of the target.
+ This will obviously fail if target is not a directory, but that's exactly
+ what we want. */
+ if (err && (err->apr_err == SVN_ERR_ENTRY_EXISTS
+ || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
+ {
+ const char *src_basename = svn_path_basename(src_path, pool);
+
+ svn_error_clear(err);
+
+ return svn_client_copy3(commit_info_p, src_path, src_revision,
+ svn_path_join(dst_path, src_basename, pool),
+ ctx, pool);
+ }
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_client_copy(svn_client_commit_info_t **commit_info_p,
+ const char *src_path,
+ const svn_opt_revision_t *src_revision,
+ const char *dst_path,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_commit_info_t *commit_info = NULL;
+ svn_error_t *err;
+
+ err = svn_client_copy2(&commit_info, src_path, src_revision, dst_path,
+ ctx, pool);
+ /* These structs have the same layout for the common fields. */
+ *commit_info_p = (svn_client_commit_info_t *) commit_info;
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_client_move6(const apr_array_header_t *src_paths,
+ const char *dst_path,
+ svn_boolean_t move_as_child,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_client_move7(src_paths, dst_path,
+ move_as_child, make_parents,
+ TRUE /* allow_mixed_revisions */,
+ FALSE /* metadata_only */,
+ revprop_table,
+ commit_callback, commit_baton,
+ ctx, pool));
+}
+
+svn_error_t *
+svn_client_move5(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *src_paths,
+ const char *dst_path,
+ svn_boolean_t force,
+ svn_boolean_t move_as_child,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct capture_baton_t cb;
+
+ cb.info = commit_info_p;
+ cb.pool = pool;
+
+ return svn_client_move6(src_paths, dst_path, move_as_child,
+ make_parents, revprop_table,
+ capture_commit_info, &cb, ctx, pool);
+}
+
+svn_error_t *
+svn_client_move4(svn_commit_info_t **commit_info_p,
+ const char *src_path,
+ const char *dst_path,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *src_paths =
+ apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(src_paths, const char *) = src_path;
+
+
+ return svn_client_move5(commit_info_p, src_paths, dst_path, force, FALSE,
+ FALSE, NULL, ctx, pool);
+}
+
+svn_error_t *
+svn_client_move3(svn_commit_info_t **commit_info_p,
+ const char *src_path,
+ const char *dst_path,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ err = svn_client_move4(commit_info_p, src_path, dst_path, force, ctx, pool);
+
+ /* If the target exists, try to move the source as a child of the target.
+ This will obviously fail if target is not a directory, but that's exactly
+ what we want. */
+ if (err && (err->apr_err == SVN_ERR_ENTRY_EXISTS
+ || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
+ {
+ const char *src_basename = svn_path_basename(src_path, pool);
+
+ svn_error_clear(err);
+
+ return svn_client_move4(commit_info_p, src_path,
+ svn_path_join(dst_path, src_basename, pool),
+ force, ctx, pool);
+ }
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_client_move2(svn_client_commit_info_t **commit_info_p,
+ const char *src_path,
+ const char *dst_path,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_commit_info_t *commit_info = NULL;
+ svn_error_t *err;
+
+ err = svn_client_move3(&commit_info, src_path, dst_path, force, ctx, pool);
+ /* These structs have the same layout for the common fields. */
+ *commit_info_p = (svn_client_commit_info_t *) commit_info;
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_client_move(svn_client_commit_info_t **commit_info_p,
+ const char *src_path,
+ const svn_opt_revision_t *src_revision,
+ const char *dst_path,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ /* It doesn't make sense to specify revisions in a move. */
+
+ /* ### todo: this check could fail wrongly. For example,
+ someone could pass in an svn_opt_revision_number that just
+ happens to be the HEAD. It's fair enough to punt then, IMHO,
+ and just demand that the user not specify a revision at all;
+ beats mucking up this function with RA calls and such. */
+ if (src_revision->kind != svn_opt_revision_unspecified
+ && src_revision->kind != svn_opt_revision_head)
+ {
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot specify revisions (except HEAD) with move operations"));
+ }
+
+ return svn_client_move2(commit_info_p, src_path, dst_path, force, ctx, pool);
+}
+
+/*** From delete.c ***/
+svn_error_t *
+svn_client_delete3(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_boolean_t force,
+ svn_boolean_t keep_local,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct capture_baton_t cb;
+
+ cb.info = commit_info_p;
+ cb.pool = pool;
+
+ return svn_client_delete4(paths, force, keep_local, revprop_table,
+ capture_commit_info, &cb, ctx, pool);
+}
+
+svn_error_t *
+svn_client_delete2(svn_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_delete3(commit_info_p, paths, force, FALSE, NULL,
+ ctx, pool);
+}
+
+svn_error_t *
+svn_client_delete(svn_client_commit_info_t **commit_info_p,
+ const apr_array_header_t *paths,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_commit_info_t *commit_info = NULL;
+ svn_error_t *err = NULL;
+
+ err = svn_client_delete2(&commit_info, paths, force, ctx, pool);
+ /* These structs have the same layout for the common fields. */
+ *commit_info_p = (svn_client_commit_info_t *) commit_info;
+ return svn_error_trace(err);
+}
+
+/*** From diff.c ***/
+
+svn_error_t *
+svn_client_diff5(const apr_array_header_t *diff_options,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t use_git_diff_format,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_stream_t *outstream = svn_stream_from_aprfile2(outfile, TRUE, pool);
+ svn_stream_t *errstream = svn_stream_from_aprfile2(errfile, TRUE, pool);
+
+ return svn_client_diff6(diff_options, path1, revision1, path2,
+ revision2, relative_to_dir, depth,
+ ignore_ancestry, FALSE /* no_diff_added */,
+ no_diff_deleted, show_copies_as_adds,
+ ignore_content_type, FALSE /* ignore_properties */,
+ FALSE /* properties_only */, use_git_diff_format,
+ header_encoding,
+ outstream, errstream, changelists, ctx, pool);
+}
+
+svn_error_t *
+svn_client_diff4(const apr_array_header_t *options,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff5(options, path1, revision1, path2,
+ revision2, relative_to_dir, depth,
+ ignore_ancestry, no_diff_deleted, FALSE,
+ ignore_content_type, FALSE, header_encoding,
+ outfile, errfile, changelists, ctx, pool);
+}
+
+svn_error_t *
+svn_client_diff3(const apr_array_header_t *options,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff4(options, path1, revision1, path2,
+ revision2, NULL,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_ancestry, no_diff_deleted,
+ ignore_content_type, header_encoding,
+ outfile, errfile, NULL, ctx, pool);
+}
+
+svn_error_t *
+svn_client_diff2(const apr_array_header_t *options,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff3(options, path1, revision1, path2, revision2,
+ recurse, ignore_ancestry, no_diff_deleted,
+ ignore_content_type, SVN_APR_LOCALE_CHARSET,
+ outfile, errfile, ctx, pool);
+}
+
+svn_error_t *
+svn_client_diff(const apr_array_header_t *options,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff2(options, path1, revision1, path2, revision2,
+ recurse, ignore_ancestry, no_diff_deleted, FALSE,
+ outfile, errfile, ctx, pool);
+}
+
+svn_error_t *
+svn_client_diff_peg5(const apr_array_header_t *diff_options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t use_git_diff_format,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_stream_t *outstream = svn_stream_from_aprfile2(outfile, TRUE, pool);
+ svn_stream_t *errstream = svn_stream_from_aprfile2(errfile, TRUE, pool);
+
+ return svn_client_diff_peg6(diff_options,
+ path,
+ peg_revision,
+ start_revision,
+ end_revision,
+ relative_to_dir,
+ depth,
+ ignore_ancestry,
+ FALSE /* no_diff_added */,
+ no_diff_deleted,
+ show_copies_as_adds,
+ ignore_content_type,
+ FALSE /* ignore_properties */,
+ FALSE /* properties_only */,
+ use_git_diff_format,
+ header_encoding,
+ outstream,
+ errstream,
+ changelists,
+ ctx,
+ pool);
+}
+
+svn_error_t *
+svn_client_diff_peg4(const apr_array_header_t *options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff_peg5(options,
+ path,
+ peg_revision,
+ start_revision,
+ end_revision,
+ relative_to_dir,
+ depth,
+ ignore_ancestry,
+ no_diff_deleted,
+ FALSE,
+ ignore_content_type,
+ FALSE,
+ header_encoding,
+ outfile,
+ errfile,
+ changelists,
+ ctx,
+ pool);
+}
+
+svn_error_t *
+svn_client_diff_peg3(const apr_array_header_t *options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ const char *header_encoding,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff_peg4(options,
+ path,
+ peg_revision,
+ start_revision,
+ end_revision,
+ NULL,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_ancestry,
+ no_diff_deleted,
+ ignore_content_type,
+ header_encoding,
+ outfile,
+ errfile,
+ NULL,
+ ctx,
+ pool);
+}
+
+svn_error_t *
+svn_client_diff_peg2(const apr_array_header_t *options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t ignore_content_type,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff_peg3(options, path, peg_revision, start_revision,
+ end_revision,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_ancestry, no_diff_deleted,
+ ignore_content_type, SVN_APR_LOCALE_CHARSET,
+ outfile, errfile, ctx, pool);
+}
+
+svn_error_t *
+svn_client_diff_peg(const apr_array_header_t *options,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_deleted,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff_peg2(options, path, peg_revision,
+ start_revision, end_revision, recurse,
+ ignore_ancestry, no_diff_deleted, FALSE,
+ outfile, errfile, ctx, pool);
+}
+
+svn_error_t *
+svn_client_diff_summarize(const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff_summarize2(path1, revision1, path2,
+ revision2,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_ancestry, NULL, summarize_func,
+ summarize_baton, ctx, pool);
+}
+
+svn_error_t *
+svn_client_diff_summarize_peg(const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff_summarize_peg2(path, peg_revision,
+ start_revision, end_revision,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_ancestry, NULL,
+ summarize_func, summarize_baton,
+ ctx, pool);
+}
+
+/*** From export.c ***/
+svn_error_t *
+svn_client_export4(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ const char *to_path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t overwrite,
+ svn_boolean_t ignore_externals,
+ svn_depth_t depth,
+ const char *native_eol,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_export5(result_rev, from_path_or_url, to_path,
+ peg_revision, revision, overwrite, ignore_externals,
+ FALSE, depth, native_eol, ctx, pool);
+}
+
+svn_error_t *
+svn_client_export3(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ const char *to_path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t overwrite,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t recurse,
+ const char *native_eol,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_export4(result_rev, from_path_or_url, to_path,
+ peg_revision, revision, overwrite, ignore_externals,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ native_eol, ctx, pool);
+}
+
+svn_error_t *
+svn_client_export2(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ const char *to_path,
+ svn_opt_revision_t *revision,
+ svn_boolean_t force,
+ const char *native_eol,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_opt_revision_t peg_revision;
+
+ peg_revision.kind = svn_opt_revision_unspecified;
+
+ return svn_client_export3(result_rev, from_path_or_url, to_path,
+ &peg_revision, revision, force, FALSE, TRUE,
+ native_eol, ctx, pool);
+}
+
+
+svn_error_t *
+svn_client_export(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ const char *to_path,
+ svn_opt_revision_t *revision,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_export2(result_rev, from_path_or_url, to_path, revision,
+ force, NULL, ctx, pool);
+}
+
+/*** From list.c ***/
+
+/* Baton for use with wrap_list_func */
+struct list_func_wrapper_baton {
+ void *list_func1_baton;
+ svn_client_list_func_t list_func1;
+};
+
+/* This implements svn_client_list_func2_t */
+static svn_error_t *
+list_func_wrapper(void *baton,
+ const char *path,
+ const svn_dirent_t *dirent,
+ const svn_lock_t *lock,
+ const char *abs_path,
+ const char *external_parent_url,
+ const char *external_target,
+ apr_pool_t *scratch_pool)
+{
+ struct list_func_wrapper_baton *lfwb = baton;
+
+ if (lfwb->list_func1)
+ return lfwb->list_func1(lfwb->list_func1_baton, path, dirent,
+ lock, abs_path, scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper function for svn_client_list2(). It wraps old format baton
+ and callback function in list_func_wrapper_baton and
+ returns new baton and callback to use with svn_client_list3(). */
+static void
+wrap_list_func(svn_client_list_func2_t *list_func2,
+ void **list_func2_baton,
+ svn_client_list_func_t list_func,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ struct list_func_wrapper_baton *lfwb = apr_palloc(result_pool,
+ sizeof(*lfwb));
+
+ /* Set the user provided old format callback in the baton. */
+ lfwb->list_func1_baton = baton;
+ lfwb->list_func1 = list_func;
+
+ *list_func2_baton = lfwb;
+ *list_func2 = list_func_wrapper;
+}
+
+svn_error_t *
+svn_client_list2(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ apr_uint32_t dirent_fields,
+ svn_boolean_t fetch_locks,
+ svn_client_list_func_t list_func,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_client_list_func2_t list_func2;
+ void *list_func2_baton;
+
+ wrap_list_func(&list_func2, &list_func2_baton, list_func, baton, pool);
+
+ return svn_client_list3(path_or_url, peg_revision, revision, depth,
+ dirent_fields, fetch_locks,
+ FALSE /* include externals */,
+ list_func2, list_func2_baton, ctx, pool);
+}
+
+svn_error_t *
+svn_client_list(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ apr_uint32_t dirent_fields,
+ svn_boolean_t fetch_locks,
+ svn_client_list_func_t list_func,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_list2(path_or_url,
+ peg_revision,
+ revision,
+ SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse),
+ dirent_fields,
+ fetch_locks,
+ list_func,
+ baton,
+ ctx,
+ pool);
+}
+
+/* Baton used by compatibility wrapper svn_client_ls3. */
+struct ls_baton {
+ apr_hash_t *dirents;
+ apr_hash_t *locks;
+ apr_pool_t *pool;
+};
+
+/* This implements svn_client_list_func_t. */
+static svn_error_t *
+store_dirent(void *baton, const char *path, const svn_dirent_t *dirent,
+ const svn_lock_t *lock, const char *abs_path, apr_pool_t *pool)
+{
+ struct ls_baton *lb = baton;
+
+ /* The dirent is allocated in a temporary pool, so duplicate it into the
+ correct pool. Note, however, that the locks are stored in the correct
+ pool already. */
+ dirent = svn_dirent_dup(dirent, lb->pool);
+
+ /* An empty path means we are called for the target of the operation.
+ For compatibility, we only store the target if it is a file, and we
+ store it under the basename of the URL. Note that this makes it
+ impossible to differentiate between the target being a directory with a
+ child with the same basename as the target and the target being a file,
+ but that's how it was implemented. */
+ if (path[0] == '\0')
+ {
+ if (dirent->kind == svn_node_file)
+ {
+ const char *base_name = svn_path_basename(abs_path, lb->pool);
+ svn_hash_sets(lb->dirents, base_name, dirent);
+ if (lock)
+ svn_hash_sets(lb->locks, base_name, lock);
+ }
+ }
+ else
+ {
+ path = apr_pstrdup(lb->pool, path);
+ svn_hash_sets(lb->dirents, path, dirent);
+ if (lock)
+ svn_hash_sets(lb->locks, path, lock);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_ls3(apr_hash_t **dirents,
+ apr_hash_t **locks,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct ls_baton lb;
+
+ *dirents = lb.dirents = apr_hash_make(pool);
+ if (locks)
+ *locks = lb.locks = apr_hash_make(pool);
+ lb.pool = pool;
+
+ return svn_client_list(path_or_url, peg_revision, revision, recurse,
+ SVN_DIRENT_ALL, locks != NULL,
+ store_dirent, &lb, ctx, pool);
+}
+
+svn_error_t *
+svn_client_ls2(apr_hash_t **dirents,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+
+ return svn_client_ls3(dirents, NULL, path_or_url, peg_revision,
+ revision, recurse, ctx, pool);
+}
+
+
+svn_error_t *
+svn_client_ls(apr_hash_t **dirents,
+ const char *path_or_url,
+ svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_ls2(dirents, path_or_url, revision,
+ revision, recurse, ctx, pool);
+}
+
+/*** From log.c ***/
+svn_error_t *
+svn_client_log4(const apr_array_header_t *targets,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *revision_ranges;
+
+ revision_ranges = apr_array_make(pool, 1,
+ sizeof(svn_opt_revision_range_t *));
+ APR_ARRAY_PUSH(revision_ranges, svn_opt_revision_range_t *)
+ = svn_opt__revision_range_create(start, end, pool);
+
+ return svn_client_log5(targets, peg_revision, revision_ranges, limit,
+ discover_changed_paths, strict_node_history,
+ include_merged_revisions, revprops, receiver,
+ receiver_baton, ctx, pool);
+}
+
+
+svn_error_t *
+svn_client_log3(const apr_array_header_t *targets,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_log_entry_receiver_t receiver2;
+ void *receiver2_baton;
+
+ svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
+ receiver, receiver_baton,
+ pool);
+
+ return svn_client_log4(targets, peg_revision, start, end, limit,
+ discover_changed_paths, strict_node_history, FALSE,
+ svn_compat_log_revprops_in(pool),
+ receiver2, receiver2_baton, ctx, pool);
+}
+
+svn_error_t *
+svn_client_log2(const apr_array_header_t *targets,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_opt_revision_t peg_revision;
+ peg_revision.kind = svn_opt_revision_unspecified;
+ return svn_client_log3(targets, &peg_revision, start, end, limit,
+ discover_changed_paths, strict_node_history,
+ receiver, receiver_baton, ctx, pool);
+}
+
+svn_error_t *
+svn_client_log(const apr_array_header_t *targets,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ err = svn_client_log2(targets, start, end, 0, discover_changed_paths,
+ strict_node_history, receiver, receiver_baton, ctx,
+ pool);
+
+ /* Special case: If there have been no commits, we'll get an error
+ * for requesting log of a revision higher than 0. But the
+ * default behavior of "svn log" is to give revisions HEAD through
+ * 1, on the assumption that HEAD >= 1.
+ *
+ * So if we got that error for that reason, and it looks like the
+ * user was just depending on the defaults (rather than explicitly
+ * requesting the log for revision 1), then we don't error. Instead
+ * we just invoke the receiver manually on a hand-constructed log
+ * message for revision 0.
+ *
+ * See also http://subversion.tigris.org/issues/show_bug.cgi?id=692.
+ */
+ if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
+ && (start->kind == svn_opt_revision_head)
+ && ((end->kind == svn_opt_revision_number)
+ && (end->value.number == 1)))
+ {
+
+ /* We don't need to check if HEAD is 0, because that must be the case,
+ * by logical deduction: The revision range specified is HEAD:1.
+ * HEAD cannot not exist, so the revision to which "no such revision"
+ * applies is 1. If revision 1 does not exist, then HEAD is 0.
+ * Hence, we deduce the repository is empty without needing access
+ * to further information. */
+
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+
+ /* Log receivers are free to handle revision 0 specially... But
+ just in case some don't, we make up a message here. */
+ SVN_ERR(receiver(receiver_baton,
+ NULL, 0, "", "", _("No commits in repository"),
+ pool));
+ }
+
+ return svn_error_trace(err);
+}
+
+/*** From merge.c ***/
+
+svn_error_t *
+svn_client_merge4(const char *source1,
+ const svn_opt_revision_t *revision1,
+ const char *source2,
+ const svn_opt_revision_t *revision2,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ svn_boolean_t allow_mixed_rev,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_client_merge5(source1, revision1,
+ source2, revision2,
+ target_wcpath,
+ depth,
+ ignore_ancestry /*ignore_mergeinfo*/,
+ ignore_ancestry /*diff_ignore_ancestry*/,
+ force_delete, record_only,
+ dry_run, allow_mixed_rev,
+ merge_options, ctx, pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_merge3(const char *source1,
+ const svn_opt_revision_t *revision1,
+ const char *source2,
+ const svn_opt_revision_t *revision2,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_merge4(source1, revision1, source2, revision2,
+ target_wcpath, depth, ignore_ancestry, force,
+ record_only, dry_run, TRUE, merge_options,
+ ctx, pool);
+}
+
+svn_error_t *
+svn_client_merge2(const char *source1,
+ const svn_opt_revision_t *revision1,
+ const char *source2,
+ const svn_opt_revision_t *revision2,
+ const char *target_wcpath,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_merge3(source1, revision1, source2, revision2,
+ target_wcpath,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_ancestry, force, FALSE, dry_run,
+ merge_options, ctx, pool);
+}
+
+svn_error_t *
+svn_client_merge(const char *source1,
+ const svn_opt_revision_t *revision1,
+ const char *source2,
+ const svn_opt_revision_t *revision2,
+ const char *target_wcpath,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_merge2(source1, revision1, source2, revision2,
+ target_wcpath, recurse, ignore_ancestry, force,
+ dry_run, NULL, ctx, pool);
+}
+
+svn_error_t *
+svn_client_merge_peg4(const char *source_path_or_url,
+ const apr_array_header_t *ranges_to_merge,
+ const svn_opt_revision_t *source_peg_revision,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ svn_boolean_t allow_mixed_rev,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_client_merge_peg5(source_path_or_url,
+ ranges_to_merge,
+ source_peg_revision,
+ target_wcpath,
+ depth,
+ ignore_ancestry /*ignore_mergeinfo*/,
+ ignore_ancestry /*diff_ignore_ancestry*/,
+ force_delete, record_only,
+ dry_run, allow_mixed_rev,
+ merge_options, ctx, pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_merge_peg3(const char *source,
+ const apr_array_header_t *ranges_to_merge,
+ const svn_opt_revision_t *peg_revision,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_merge_peg4(source, ranges_to_merge, peg_revision,
+ target_wcpath, depth, ignore_ancestry, force,
+ record_only, dry_run, TRUE, merge_options,
+ ctx, pool);
+}
+
+svn_error_t *
+svn_client_merge_peg2(const char *source,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ const char *target_wcpath,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *ranges_to_merge =
+ apr_array_make(pool, 1, sizeof(svn_opt_revision_range_t *));
+
+ APR_ARRAY_PUSH(ranges_to_merge, svn_opt_revision_range_t *)
+ = svn_opt__revision_range_create(revision1, revision2, pool);
+ return svn_client_merge_peg3(source, ranges_to_merge,
+ peg_revision,
+ target_wcpath,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_ancestry, force, FALSE, dry_run,
+ merge_options, ctx, pool);
+}
+
+svn_error_t *
+svn_client_merge_peg(const char *source,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ const char *target_wcpath,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_merge_peg2(source, revision1, revision2, peg_revision,
+ target_wcpath, recurse, ignore_ancestry, force,
+ dry_run, NULL, ctx, pool);
+}
+
+/*** From prop_commands.c ***/
+svn_error_t *
+svn_client_propset3(svn_commit_info_t **commit_info_p,
+ const char *propname,
+ const svn_string_t *propval,
+ const char *target,
+ svn_depth_t depth,
+ svn_boolean_t skip_checks,
+ svn_revnum_t base_revision_for_url,
+ const apr_array_header_t *changelists,
+ const apr_hash_t *revprop_table,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ if (svn_path_is_url(target))
+ {
+ struct capture_baton_t cb;
+
+ cb.info = commit_info_p;
+ cb.pool = pool;
+
+ SVN_ERR(svn_client_propset_remote(propname, propval, target, skip_checks,
+ base_revision_for_url, revprop_table,
+ capture_commit_info, &cb, ctx, pool));
+ }
+ else
+ {
+ apr_array_header_t *targets = apr_array_make(pool, 1,
+ sizeof(const char *));
+
+ APR_ARRAY_PUSH(targets, const char *) = target;
+ SVN_ERR(svn_client_propset_local(propname, propval, targets, depth,
+ skip_checks, changelists, ctx, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_propset2(const char *propname,
+ const svn_string_t *propval,
+ const char *target,
+ svn_boolean_t recurse,
+ svn_boolean_t skip_checks,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_propset3(NULL, propname, propval, target,
+ SVN_DEPTH_INFINITY_OR_EMPTY(recurse),
+ skip_checks, SVN_INVALID_REVNUM,
+ NULL, NULL, ctx, pool);
+}
+
+
+svn_error_t *
+svn_client_propset(const char *propname,
+ const svn_string_t *propval,
+ const char *target,
+ svn_boolean_t recurse,
+ apr_pool_t *pool)
+{
+ svn_client_ctx_t *ctx;
+
+ SVN_ERR(svn_client_create_context(&ctx, pool));
+
+ return svn_client_propset2(propname, propval, target, recurse, FALSE,
+ ctx, pool);
+}
+
+svn_error_t *
+svn_client_revprop_set(const char *propname,
+ const svn_string_t *propval,
+ const char *URL,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *set_rev,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_revprop_set2(propname, propval, NULL, URL,
+ revision, set_rev, force, ctx, pool);
+
+}
+
+svn_error_t *
+svn_client_propget4(apr_hash_t **props,
+ const char *propname,
+ const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *actual_revnum,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_client_propget5(props, NULL, propname, target,
+ peg_revision, revision,
+ actual_revnum, depth,
+ changelists, ctx,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_client_propget3(apr_hash_t **props,
+ const char *propname,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *actual_revnum,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *target;
+ apr_hash_t *temp_props;
+ svn_error_t *err;
+
+ if (svn_path_is_url(path_or_url))
+ target = path_or_url;
+ else
+ SVN_ERR(svn_dirent_get_absolute(&target, path_or_url, pool));
+
+ err = svn_client_propget4(&temp_props, propname, target,
+ peg_revision, revision, actual_revnum,
+ depth, changelists, ctx, pool, pool);
+
+ if (err && err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE)
+ {
+ err->apr_err = SVN_ERR_ENTRY_NOT_FOUND;
+ return svn_error_trace(err);
+ }
+ else
+ SVN_ERR(err);
+
+ if (actual_revnum
+ && !svn_path_is_url(path_or_url)
+ && !SVN_IS_VALID_REVNUM(*actual_revnum))
+ {
+ /* Get the actual_revnum; added nodes have no revision yet, and old
+ * callers expected the mock-up revision of 0. */
+ svn_boolean_t added;
+
+ SVN_ERR(svn_wc__node_is_added(&added, ctx->wc_ctx, target, pool));
+ if (added)
+ *actual_revnum = 0;
+ }
+
+ /* We may need to fix up our hash keys for legacy callers. */
+ if (!svn_path_is_url(path_or_url) && strcmp(target, path_or_url) != 0)
+ {
+ apr_hash_index_t *hi;
+
+ *props = apr_hash_make(pool);
+ for (hi = apr_hash_first(pool, temp_props); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *abspath = svn__apr_hash_index_key(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+ const char *relpath = svn_dirent_join(path_or_url,
+ svn_dirent_skip_ancestor(target, abspath),
+ pool);
+
+ svn_hash_sets(*props, relpath, value);
+ }
+ }
+ else
+ *props = temp_props;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_propget2(apr_hash_t **props,
+ const char *propname,
+ const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_propget3(props,
+ propname,
+ target,
+ peg_revision,
+ revision,
+ NULL,
+ SVN_DEPTH_INFINITY_OR_EMPTY(recurse),
+ NULL,
+ ctx,
+ pool);
+}
+
+svn_error_t *
+svn_client_propget(apr_hash_t **props,
+ const char *propname,
+ const char *target,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_propget2(props, propname, target, revision, revision,
+ recurse, ctx, pool);
+}
+
+
+/* Duplicate a HASH containing (char * -> svn_string_t *) key/value
+ pairs using POOL. */
+static apr_hash_t *
+string_hash_dup(apr_hash_t *hash, apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_hash_t *new_hash = apr_hash_make(pool);
+
+ for (hi = apr_hash_first(pool, hash); hi; hi = apr_hash_next(hi))
+ {
+ const char *key = apr_pstrdup(pool, svn__apr_hash_index_key(hi));
+ apr_ssize_t klen = svn__apr_hash_index_klen(hi);
+ svn_string_t *val = svn_string_dup(svn__apr_hash_index_val(hi), pool);
+
+ apr_hash_set(new_hash, key, klen, val);
+ }
+ return new_hash;
+}
+
+svn_client_proplist_item_t *
+svn_client_proplist_item_dup(const svn_client_proplist_item_t *item,
+ apr_pool_t * pool)
+{
+ svn_client_proplist_item_t *new_item = apr_pcalloc(pool, sizeof(*new_item));
+
+ if (item->node_name)
+ new_item->node_name = svn_stringbuf_dup(item->node_name, pool);
+
+ if (item->prop_hash)
+ new_item->prop_hash = string_hash_dup(item->prop_hash, pool);
+
+ return new_item;
+}
+
+/* Baton for use with wrap_proplist_receiver */
+struct proplist_receiver_wrapper_baton {
+ void *baton;
+ svn_proplist_receiver_t receiver;
+};
+
+/* This implements svn_client_proplist_receiver2_t */
+static svn_error_t *
+proplist_wrapper_receiver(void *baton,
+ const char *path,
+ apr_hash_t *prop_hash,
+ apr_array_header_t *inherited_props,
+ apr_pool_t *pool)
+{
+ struct proplist_receiver_wrapper_baton *plrwb = baton;
+
+ if (plrwb->receiver)
+ return plrwb->receiver(plrwb->baton, path, prop_hash, pool);
+
+ return SVN_NO_ERROR;
+}
+
+static void
+wrap_proplist_receiver(svn_proplist_receiver2_t *receiver2,
+ void **receiver2_baton,
+ svn_proplist_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ struct proplist_receiver_wrapper_baton *plrwb = apr_palloc(pool,
+ sizeof(*plrwb));
+
+ /* Set the user provided old format callback in the baton. */
+ plrwb->baton = receiver_baton;
+ plrwb->receiver = receiver;
+
+ *receiver2_baton = plrwb;
+ *receiver2 = proplist_wrapper_receiver;
+}
+
+svn_error_t *
+svn_client_proplist3(const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_proplist_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+
+ svn_proplist_receiver2_t receiver2;
+ void *receiver2_baton;
+
+ wrap_proplist_receiver(&receiver2, &receiver2_baton, receiver, receiver_baton,
+ pool);
+
+ return svn_error_trace(svn_client_proplist4(target, peg_revision, revision,
+ depth, changelists, FALSE,
+ receiver2, receiver2_baton,
+ ctx, pool));
+}
+
+/* Receiver baton used by proplist2() */
+struct proplist_receiver_baton {
+ apr_array_header_t *props;
+ apr_pool_t *pool;
+};
+
+/* Receiver function used by proplist2(). */
+static svn_error_t *
+proplist_receiver_cb(void *baton,
+ const char *path,
+ apr_hash_t *prop_hash,
+ apr_pool_t *pool)
+{
+ struct proplist_receiver_baton *pl_baton =
+ (struct proplist_receiver_baton *) baton;
+ svn_client_proplist_item_t *tmp_item = apr_palloc(pool, sizeof(*tmp_item));
+ svn_client_proplist_item_t *item;
+
+ /* Because the pool passed to the receiver function is likely to be a
+ temporary pool of some kind, we need to make copies of *path and
+ *prop_hash in the pool provided by the baton. */
+ tmp_item->node_name = svn_stringbuf_create(path, pl_baton->pool);
+ tmp_item->prop_hash = prop_hash;
+
+ item = svn_client_proplist_item_dup(tmp_item, pl_baton->pool);
+
+ APR_ARRAY_PUSH(pl_baton->props, const svn_client_proplist_item_t *) = item;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_proplist2(apr_array_header_t **props,
+ const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct proplist_receiver_baton pl_baton;
+
+ *props = apr_array_make(pool, 5, sizeof(svn_client_proplist_item_t *));
+ pl_baton.props = *props;
+ pl_baton.pool = pool;
+
+ return svn_client_proplist3(target, peg_revision, revision,
+ SVN_DEPTH_INFINITY_OR_EMPTY(recurse), NULL,
+ proplist_receiver_cb, &pl_baton, ctx, pool);
+}
+
+
+svn_error_t *
+svn_client_proplist(apr_array_header_t **props,
+ const char *target,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_proplist2(props, target, revision, revision,
+ recurse, ctx, pool);
+}
+
+/*** From status.c ***/
+
+struct status4_wrapper_baton
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc_status_func3_t old_func;
+ void *old_baton;
+};
+
+/* Implements svn_client_status_func_t */
+static svn_error_t *
+status4_wrapper_func(void *baton,
+ const char *path,
+ const svn_client_status_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct status4_wrapper_baton *swb = baton;
+ svn_wc_status2_t *dup;
+ const char *local_abspath;
+ const svn_wc_status3_t *wc_status = status->backwards_compatibility_baton;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+ SVN_ERR(svn_wc__status2_from_3(&dup, wc_status, swb->wc_ctx,
+ local_abspath, scratch_pool,
+ scratch_pool));
+
+ return (*swb->old_func)(swb->old_baton, path, dup, scratch_pool);
+}
+
+svn_error_t *
+svn_client_status4(svn_revnum_t *result_rev,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_wc_status_func3_t status_func,
+ void *status_baton,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t update,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_externals,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct status4_wrapper_baton swb;
+
+ swb.wc_ctx = ctx->wc_ctx;
+ swb.old_func = status_func;
+ swb.old_baton = status_baton;
+
+ return svn_client_status5(result_rev, ctx, path, revision, depth, get_all,
+ update, no_ignore, ignore_externals, TRUE,
+ changelists, status4_wrapper_func, &swb, pool);
+}
+
+struct status3_wrapper_baton
+{
+ svn_wc_status_func2_t old_func;
+ void *old_baton;
+};
+
+static svn_error_t *
+status3_wrapper_func(void *baton,
+ const char *path,
+ svn_wc_status2_t *status,
+ apr_pool_t *pool)
+{
+ struct status3_wrapper_baton *swb = baton;
+
+ swb->old_func(swb->old_baton, path, status);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_status3(svn_revnum_t *result_rev,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_wc_status_func2_t status_func,
+ void *status_baton,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t update,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_externals,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct status3_wrapper_baton swb = { 0 };
+ swb.old_func = status_func;
+ swb.old_baton = status_baton;
+ return svn_client_status4(result_rev, path, revision, status3_wrapper_func,
+ &swb, depth, get_all, update, no_ignore,
+ ignore_externals, changelists, ctx, pool);
+}
+
+svn_error_t *
+svn_client_status2(svn_revnum_t *result_rev,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_wc_status_func2_t status_func,
+ void *status_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t get_all,
+ svn_boolean_t update,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_externals,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_status3(result_rev, path, revision,
+ status_func, status_baton,
+ SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse),
+ get_all, update, no_ignore, ignore_externals, NULL,
+ ctx, pool);
+}
+
+
+/* Baton for old_status_func_cb; does what you think it does. */
+struct old_status_func_cb_baton
+{
+ svn_wc_status_func_t original_func;
+ void *original_baton;
+};
+
+/* Help svn_client_status() accept an old-style status func and baton,
+ by wrapping them before passing along to svn_client_status2().
+
+ This implements the 'svn_wc_status_func2_t' function type. */
+static void old_status_func_cb(void *baton,
+ const char *path,
+ svn_wc_status2_t *status)
+{
+ struct old_status_func_cb_baton *b = baton;
+ svn_wc_status_t *stat = (svn_wc_status_t *) status;
+
+ b->original_func(b->original_baton, path, stat);
+}
+
+
+svn_error_t *
+svn_client_status(svn_revnum_t *result_rev,
+ const char *path,
+ svn_opt_revision_t *revision,
+ svn_wc_status_func_t status_func,
+ void *status_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t get_all,
+ svn_boolean_t update,
+ svn_boolean_t no_ignore,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct old_status_func_cb_baton *b = apr_pcalloc(pool, sizeof(*b));
+ b->original_func = status_func;
+ b->original_baton = status_baton;
+
+ return svn_client_status2(result_rev, path, revision,
+ old_status_func_cb, b,
+ recurse, get_all, update, no_ignore, FALSE,
+ ctx, pool);
+}
+
+/*** From update.c ***/
+svn_error_t *
+svn_client_update3(apr_array_header_t **result_revs,
+ const apr_array_header_t *paths,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_update4(result_revs, paths, revision,
+ depth, depth_is_sticky, ignore_externals,
+ allow_unver_obstructions, TRUE, FALSE,
+ ctx, pool);
+}
+
+svn_error_t *
+svn_client_update2(apr_array_header_t **result_revs,
+ const apr_array_header_t *paths,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_externals,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_update3(result_revs, paths, revision,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE,
+ ignore_externals, FALSE, ctx, pool);
+}
+
+svn_error_t *
+svn_client_update(svn_revnum_t *result_rev,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(const char *));
+ apr_array_header_t *result_revs;
+
+ APR_ARRAY_PUSH(paths, const char *) = path;
+
+ SVN_ERR(svn_client_update2(&result_revs, paths, revision, recurse, FALSE,
+ ctx, pool));
+
+ *result_rev = APR_ARRAY_IDX(result_revs, 0, svn_revnum_t);
+
+ return SVN_NO_ERROR;
+}
+
+/*** From switch.c ***/
+svn_error_t *
+svn_client_switch2(svn_revnum_t *result_rev,
+ const char *path,
+ const char *switch_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_switch3(result_rev, path, switch_url, peg_revision,
+ revision, depth, depth_is_sticky, ignore_externals,
+ allow_unver_obstructions,
+ TRUE /* ignore_ancestry */,
+ ctx, pool);
+}
+
+svn_error_t *
+svn_client_switch(svn_revnum_t *result_rev,
+ const char *path,
+ const char *switch_url,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_opt_revision_t peg_revision;
+ peg_revision.kind = svn_opt_revision_unspecified;
+ return svn_client_switch2(result_rev, path, switch_url,
+ &peg_revision, revision,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ FALSE, FALSE, FALSE, ctx, pool);
+}
+
+/*** From cat.c ***/
+svn_error_t *
+svn_client_cat(svn_stream_t *out,
+ const char *path_or_url,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_cat2(out, path_or_url, revision, revision,
+ ctx, pool);
+}
+
+/*** From checkout.c ***/
+svn_error_t *
+svn_client_checkout2(svn_revnum_t *result_rev,
+ const char *URL,
+ const char *path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_externals,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_client_checkout3(result_rev, URL, path,
+ peg_revision, revision,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_externals, FALSE, ctx, pool));
+}
+
+svn_error_t *
+svn_client_checkout(svn_revnum_t *result_rev,
+ const char *URL,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_opt_revision_t peg_revision;
+
+ peg_revision.kind = svn_opt_revision_unspecified;
+
+ return svn_error_trace(svn_client_checkout2(result_rev, URL, path,
+ &peg_revision, revision, recurse,
+ FALSE, ctx, pool));
+}
+
+/*** From info.c ***/
+
+svn_info_t *
+svn_info_dup(const svn_info_t *info, apr_pool_t *pool)
+{
+ svn_info_t *dupinfo = apr_palloc(pool, sizeof(*dupinfo));
+
+ /* Perform a trivial copy ... */
+ *dupinfo = *info;
+
+ /* ...and then re-copy stuff that needs to be duped into our pool. */
+ if (info->URL)
+ dupinfo->URL = apr_pstrdup(pool, info->URL);
+ if (info->repos_root_URL)
+ dupinfo->repos_root_URL = apr_pstrdup(pool, info->repos_root_URL);
+ if (info->repos_UUID)
+ dupinfo->repos_UUID = apr_pstrdup(pool, info->repos_UUID);
+ if (info->last_changed_author)
+ dupinfo->last_changed_author = apr_pstrdup(pool,
+ info->last_changed_author);
+ if (info->lock)
+ dupinfo->lock = svn_lock_dup(info->lock, pool);
+ if (info->copyfrom_url)
+ dupinfo->copyfrom_url = apr_pstrdup(pool, info->copyfrom_url);
+ if (info->checksum)
+ dupinfo->checksum = apr_pstrdup(pool, info->checksum);
+ if (info->conflict_old)
+ dupinfo->conflict_old = apr_pstrdup(pool, info->conflict_old);
+ if (info->conflict_new)
+ dupinfo->conflict_new = apr_pstrdup(pool, info->conflict_new);
+ if (info->conflict_wrk)
+ dupinfo->conflict_wrk = apr_pstrdup(pool, info->conflict_wrk);
+ if (info->prejfile)
+ dupinfo->prejfile = apr_pstrdup(pool, info->prejfile);
+
+ return dupinfo;
+}
+
+/* Convert an svn_client_info2_t to an svn_info_t, doing shallow copies. */
+static svn_error_t *
+info_from_info2(svn_info_t **new_info,
+ svn_wc_context_t *wc_ctx,
+ const svn_client_info2_t *info2,
+ apr_pool_t *pool)
+{
+ svn_info_t *info = apr_pcalloc(pool, sizeof(*info));
+
+ info->URL = info2->URL;
+ /* Goofy backward compat handling for added nodes. */
+ if (SVN_IS_VALID_REVNUM(info2->rev))
+ info->rev = info2->rev;
+ else
+ info->rev = 0;
+
+ info->kind = info2->kind;
+ info->repos_root_URL = info2->repos_root_URL;
+ info->repos_UUID = info2->repos_UUID;
+ info->last_changed_rev = info2->last_changed_rev;
+ info->last_changed_date = info2->last_changed_date;
+ info->last_changed_author = info2->last_changed_author;
+
+ /* Stupid old structure has a non-const LOCK member. Sigh. */
+ info->lock = (svn_lock_t *)info2->lock;
+
+ info->size64 = info2->size;
+ if (info2->size == SVN_INVALID_FILESIZE)
+ info->size = SVN_INFO_SIZE_UNKNOWN;
+ else if (((svn_filesize_t)(apr_size_t)info->size64) == info->size64)
+ info->size = (apr_size_t)info->size64;
+ else /* >= 4GB */
+ info->size = SVN_INFO_SIZE_UNKNOWN;
+
+ if (info2->wc_info)
+ {
+ info->has_wc_info = TRUE;
+ info->schedule = info2->wc_info->schedule;
+ info->copyfrom_url = info2->wc_info->copyfrom_url;
+ info->copyfrom_rev = info2->wc_info->copyfrom_rev;
+ info->text_time = info2->wc_info->recorded_time;
+ info->prop_time = 0;
+ if (info2->wc_info->checksum
+ && info2->wc_info->checksum->kind == svn_checksum_md5)
+ info->checksum = svn_checksum_to_cstring(
+ info2->wc_info->checksum, pool);
+ else
+ info->checksum = NULL;
+ info->changelist = info2->wc_info->changelist;
+ info->depth = info2->wc_info->depth;
+
+ if (info->depth == svn_depth_unknown && info->kind == svn_node_file)
+ info->depth = svn_depth_infinity;
+
+ info->working_size64 = info2->wc_info->recorded_size;
+ if (((svn_filesize_t)(apr_size_t)info->working_size64) == info->working_size64)
+ info->working_size = (apr_size_t)info->working_size64;
+ else /* >= 4GB */
+ info->working_size = SVN_INFO_SIZE_UNKNOWN;
+ }
+ else
+ {
+ info->has_wc_info = FALSE;
+ info->working_size = SVN_INFO_SIZE_UNKNOWN;
+ info->working_size64 = SVN_INVALID_FILESIZE;
+ info->depth = svn_depth_unknown;
+ }
+
+ /* Populate conflict fields. */
+ if (info2->wc_info && info2->wc_info->conflicts)
+ {
+ int i;
+
+ for (i = 0; i < info2->wc_info->conflicts->nelts; i++)
+ {
+ const svn_wc_conflict_description2_t *conflict
+ = APR_ARRAY_IDX(info2->wc_info->conflicts, i,
+ const svn_wc_conflict_description2_t *);
+
+ /* ### Not really sure what we should do if we get multiple
+ ### conflicts of the same type. */
+ switch (conflict->kind)
+ {
+ case svn_wc_conflict_kind_tree:
+ info->tree_conflict = svn_wc__cd2_to_cd(conflict, pool);
+ break;
+
+ case svn_wc_conflict_kind_text:
+ info->conflict_old = conflict->base_abspath;
+ info->conflict_new = conflict->my_abspath;
+ info->conflict_wrk = conflict->their_abspath;
+ break;
+
+ case svn_wc_conflict_kind_property:
+ info->prejfile = conflict->their_abspath;
+ break;
+ }
+ }
+ }
+
+ if (info2->wc_info && info2->wc_info->checksum)
+ {
+ const svn_checksum_t *md5_checksum;
+
+ SVN_ERR(svn_wc__node_get_md5_from_sha1(&md5_checksum,
+ wc_ctx,
+ info2->wc_info->wcroot_abspath,
+ info2->wc_info->checksum,
+ pool, pool));
+
+ info->checksum = svn_checksum_to_cstring(md5_checksum, pool);
+ }
+
+ *new_info = info;
+ return SVN_NO_ERROR;
+}
+
+struct info_to_relpath_baton
+{
+ const char *anchor_abspath;
+ const char *anchor_relpath;
+ svn_info_receiver_t info_receiver;
+ void *info_baton;
+ svn_wc_context_t *wc_ctx;
+};
+
+static svn_error_t *
+info_receiver_relpath_wrapper(void *baton,
+ const char *abspath_or_url,
+ const svn_client_info2_t *info2,
+ apr_pool_t *scratch_pool)
+{
+ struct info_to_relpath_baton *rb = baton;
+ const char *path = abspath_or_url;
+ svn_info_t *info;
+
+ if (rb->anchor_relpath &&
+ svn_dirent_is_ancestor(rb->anchor_abspath, abspath_or_url))
+ {
+ path = svn_dirent_join(rb->anchor_relpath,
+ svn_dirent_skip_ancestor(rb->anchor_abspath,
+ abspath_or_url),
+ scratch_pool);
+ }
+
+ SVN_ERR(info_from_info2(&info, rb->wc_ctx, info2, scratch_pool));
+
+ SVN_ERR(rb->info_receiver(rb->info_baton,
+ path,
+ info,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_info2(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_info_receiver_t receiver,
+ void *receiver_baton,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct info_to_relpath_baton rb;
+ const char *abspath_or_url = path_or_url;
+
+ rb.anchor_relpath = NULL;
+ rb.info_receiver = receiver;
+ rb.info_baton = receiver_baton;
+ rb.wc_ctx = ctx->wc_ctx;
+
+ if (!svn_path_is_url(path_or_url))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&abspath_or_url, path_or_url, pool));
+ rb.anchor_abspath = abspath_or_url;
+ rb.anchor_relpath = path_or_url;
+ }
+
+ SVN_ERR(svn_client_info3(abspath_or_url,
+ peg_revision,
+ revision,
+ depth,
+ FALSE, TRUE,
+ changelists,
+ info_receiver_relpath_wrapper,
+ &rb,
+ ctx,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_info(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_info_receiver_t receiver,
+ void *receiver_baton,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_info2(path_or_url, peg_revision, revision,
+ receiver, receiver_baton,
+ SVN_DEPTH_INFINITY_OR_EMPTY(recurse),
+ NULL, ctx, pool);
+}
+
+/*** From resolved.c ***/
+svn_error_t *
+svn_client_resolved(const char *path,
+ svn_boolean_t recursive,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_depth_t depth = SVN_DEPTH_INFINITY_OR_EMPTY(recursive);
+ return svn_client_resolve(path, depth,
+ svn_wc_conflict_choose_merged, ctx, pool);
+}
+/*** From revert.c ***/
+svn_error_t *
+svn_client_revert(const apr_array_header_t *paths,
+ svn_boolean_t recursive,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_revert2(paths, SVN_DEPTH_INFINITY_OR_EMPTY(recursive),
+ NULL, ctx, pool);
+}
+
+/*** From ra.c ***/
+svn_error_t *
+svn_client_open_ra_session(svn_ra_session_t **session,
+ const char *url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(
+ svn_client_open_ra_session2(session, url,
+ NULL, ctx,
+ pool, pool));
+}
+
+svn_error_t *
+svn_client_uuid_from_url(const char **uuid,
+ const char *url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ err = svn_client_get_repos_root(NULL, uuid, url,
+ ctx, pool, subpool);
+ /* destroy the RA session */
+ svn_pool_destroy(subpool);
+
+ return svn_error_trace(err);;
+}
+
+svn_error_t *
+svn_client_uuid_from_path2(const char **uuid,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_client_get_repos_root(NULL, uuid,
+ local_abspath, ctx,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_client_uuid_from_path(const char **uuid,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ return svn_error_trace(
+ svn_client_uuid_from_path2(uuid, local_abspath, ctx, pool, pool));
+}
+
+/*** From url.c ***/
+svn_error_t *
+svn_client_root_url_from_path(const char **url,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_error_t *err;
+ if (!svn_path_is_url(path_or_url))
+ SVN_ERR(svn_dirent_get_absolute(&path_or_url, path_or_url, pool));
+
+ err = svn_client_get_repos_root(url, NULL, path_or_url,
+ ctx, pool, subpool);
+
+ /* close ra session */
+ svn_pool_destroy(subpool);
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_client_url_from_path(const char **url,
+ const char *path_or_url,
+ apr_pool_t *pool)
+{
+ svn_client_ctx_t *ctx;
+
+ SVN_ERR(svn_client_create_context(&ctx, pool));
+
+ return svn_client_url_from_path2(url, path_or_url, ctx, pool, pool);
+}
+
+/*** From mergeinfo.c ***/
+svn_error_t *
+svn_client_mergeinfo_log(svn_boolean_t finding_merged,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_peg_revision,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_boolean_t discover_changed_paths,
+ svn_depth_t depth,
+ const apr_array_header_t *revprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_opt_revision_t start_revision, end_revision;
+
+ start_revision.kind = svn_opt_revision_unspecified;
+ end_revision.kind = svn_opt_revision_unspecified;
+
+ return svn_client_mergeinfo_log2(finding_merged,
+ target_path_or_url, target_peg_revision,
+ source_path_or_url, source_peg_revision,
+ &start_revision, &end_revision,
+ receiver, receiver_baton,
+ discover_changed_paths, depth, revprops,
+ ctx, scratch_pool);
+}
+
+svn_error_t *
+svn_client_mergeinfo_log_merged(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const char *merge_source_path_or_url,
+ const svn_opt_revision_t *src_peg_revision,
+ svn_log_entry_receiver_t log_receiver,
+ void *log_receiver_baton,
+ svn_boolean_t discover_changed_paths,
+ const apr_array_header_t *revprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_mergeinfo_log(TRUE, path_or_url, peg_revision,
+ merge_source_path_or_url,
+ src_peg_revision,
+ log_receiver, log_receiver_baton,
+ discover_changed_paths,
+ svn_depth_empty, revprops, ctx,
+ pool);
+}
+
+svn_error_t *
+svn_client_mergeinfo_log_eligible(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const char *merge_source_path_or_url,
+ const svn_opt_revision_t *src_peg_revision,
+ svn_log_entry_receiver_t log_receiver,
+ void *log_receiver_baton,
+ svn_boolean_t discover_changed_paths,
+ const apr_array_header_t *revprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_mergeinfo_log(FALSE, path_or_url, peg_revision,
+ merge_source_path_or_url,
+ src_peg_revision,
+ log_receiver, log_receiver_baton,
+ discover_changed_paths,
+ svn_depth_empty, revprops, ctx,
+ pool);
+}
+
+/*** From relocate.c ***/
+svn_error_t *
+svn_client_relocate(const char *path,
+ const char *from_prefix,
+ const char *to_prefix,
+ svn_boolean_t recurse,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ if (! recurse)
+ SVN_ERR(svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Non-recursive relocation not supported")));
+ return svn_client_relocate2(path, from_prefix, to_prefix, TRUE, ctx, pool);
+}
+
+/*** From util.c ***/
+svn_error_t *
+svn_client_commit_item_create(const svn_client_commit_item3_t **item,
+ apr_pool_t *pool)
+{
+ *item = svn_client_commit_item3_create(pool);
+ return SVN_NO_ERROR;
+}
+
+svn_client_commit_item2_t *
+svn_client_commit_item2_dup(const svn_client_commit_item2_t *item,
+ apr_pool_t *pool)
+{
+ svn_client_commit_item2_t *new_item = apr_palloc(pool, sizeof(*new_item));
+
+ *new_item = *item;
+
+ if (new_item->path)
+ new_item->path = apr_pstrdup(pool, new_item->path);
+
+ if (new_item->url)
+ new_item->url = apr_pstrdup(pool, new_item->url);
+
+ if (new_item->copyfrom_url)
+ new_item->copyfrom_url = apr_pstrdup(pool, new_item->copyfrom_url);
+
+ if (new_item->wcprop_changes)
+ new_item->wcprop_changes = svn_prop_array_dup(new_item->wcprop_changes,
+ pool);
+
+ return new_item;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_client/diff.c b/contrib/subversion/subversion/libsvn_client/diff.c
new file mode 100644
index 0000000..a5a36bd
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/diff.c
@@ -0,0 +1,2723 @@
+/*
+ * diff.c: comparing
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_strings.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include "svn_types.h"
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_diff.h"
+#include "svn_mergeinfo.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_io.h"
+#include "svn_utf.h"
+#include "svn_pools.h"
+#include "svn_config.h"
+#include "svn_props.h"
+#include "svn_subst.h"
+#include "client.h"
+
+#include "private/svn_wc_private.h"
+#include "private/svn_diff_private.h"
+#include "private/svn_subr_private.h"
+
+#include "svn_private_config.h"
+
+
+/* Utilities */
+
+
+#define MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir) \
+ svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL, \
+ _("Path '%s' must be an immediate child of " \
+ "the directory '%s'"), path, relative_to_dir)
+
+/* Calculate the repository relative path of DIFF_RELPATH, using RA_SESSION
+ * and WC_CTX, and return the result in *REPOS_RELPATH.
+ * ORIG_TARGET is the related original target passed to the diff command,
+ * and may be used to derive leading path components missing from PATH.
+ * ANCHOR is the local path where the diff editor is anchored.
+ * Do all allocations in POOL. */
+static svn_error_t *
+make_repos_relpath(const char **repos_relpath,
+ const char *diff_relpath,
+ const char *orig_target,
+ svn_ra_session_t *ra_session,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ const char *orig_repos_relpath = NULL;
+
+ if (! ra_session
+ || (anchor && !svn_path_is_url(orig_target)))
+ {
+ svn_error_t *err;
+ /* We're doing a WC-WC diff, so we can retrieve all information we
+ * need from the working copy. */
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath,
+ svn_dirent_join(anchor, diff_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ err = svn_wc__node_get_repos_info(NULL, repos_relpath, NULL, NULL,
+ wc_ctx, local_abspath,
+ result_pool, scratch_pool);
+
+ if (!ra_session
+ || ! err
+ || (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND))
+ {
+ return svn_error_trace(err);
+ }
+
+ /* The path represents a local working copy path, but does not
+ exist. Fall through to calculate an in-repository location
+ based on the ra session */
+
+ /* ### Maybe we should use the nearest existing ancestor instead? */
+ svn_error_clear(err);
+ }
+
+ {
+ const char *url;
+ const char *repos_root_url;
+
+ /* Would be nice if the RA layer could just provide the parent
+ repos_relpath of the ra session */
+ SVN_ERR(svn_ra_get_session_url(ra_session, &url, scratch_pool));
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
+ scratch_pool));
+
+ orig_repos_relpath = svn_uri_skip_ancestor(repos_root_url, url,
+ scratch_pool);
+
+ *repos_relpath = svn_relpath_join(orig_repos_relpath, diff_relpath,
+ result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Adjust *INDEX_PATH, *ORIG_PATH_1 and *ORIG_PATH_2, representing the changed
+ * node and the two original targets passed to the diff command, to handle the
+ * case when we're dealing with different anchors. RELATIVE_TO_DIR is the
+ * directory the diff target should be considered relative to.
+ * ANCHOR is the local path where the diff editor is anchored. The resulting
+ * values are allocated in RESULT_POOL and temporary allocations are performed
+ * in SCRATCH_POOL. */
+static svn_error_t *
+adjust_paths_for_diff_labels(const char **index_path,
+ const char **orig_path_1,
+ const char **orig_path_2,
+ const char *relative_to_dir,
+ const char *anchor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *new_path = *index_path;
+ const char *new_path1 = *orig_path_1;
+ const char *new_path2 = *orig_path_2;
+
+ if (anchor)
+ new_path = svn_dirent_join(anchor, new_path, result_pool);
+
+ if (relative_to_dir)
+ {
+ /* Possibly adjust the paths shown in the output (see issue #2723). */
+ const char *child_path = svn_dirent_is_child(relative_to_dir, new_path,
+ result_pool);
+
+ if (child_path)
+ new_path = child_path;
+ else if (! strcmp(relative_to_dir, new_path))
+ new_path = ".";
+ else
+ return MAKE_ERR_BAD_RELATIVE_PATH(new_path, relative_to_dir);
+
+ child_path = svn_dirent_is_child(relative_to_dir, new_path1,
+ result_pool);
+ }
+
+ {
+ apr_size_t len;
+ svn_boolean_t is_url1;
+ svn_boolean_t is_url2;
+ /* ### Holy cow. Due to anchor/target weirdness, we can't
+ simply join diff_cmd_baton->orig_path_1 with path, ditto for
+ orig_path_2. That will work when they're directory URLs, but
+ not for file URLs. Nor can we just use anchor1 and anchor2
+ from do_diff(), at least not without some more logic here.
+ What a nightmare.
+
+ For now, to distinguish the two paths, we'll just put the
+ unique portions of the original targets in parentheses after
+ the received path, with ellipses for handwaving. This makes
+ the labels a bit clumsy, but at least distinctive. Better
+ solutions are possible, they'll just take more thought. */
+
+ /* ### BH: We can now just construct the repos_relpath, etc. as the
+ anchor is available. See also make_repos_relpath() */
+
+ is_url1 = svn_path_is_url(new_path1);
+ is_url2 = svn_path_is_url(new_path2);
+
+ if (is_url1 && is_url2)
+ len = strlen(svn_uri_get_longest_ancestor(new_path1, new_path2,
+ scratch_pool));
+ else if (!is_url1 && !is_url2)
+ len = strlen(svn_dirent_get_longest_ancestor(new_path1, new_path2,
+ scratch_pool));
+ else
+ len = 0; /* Path and URL */
+
+ new_path1 += len;
+ new_path2 += len;
+ }
+
+ /* ### Should diff labels print paths in local style? Is there
+ already a standard for this? In any case, this code depends on
+ a particular style, so not calling svn_dirent_local_style() on the
+ paths below.*/
+
+ if (new_path[0] == '\0')
+ new_path = ".";
+
+ if (new_path1[0] == '\0')
+ new_path1 = new_path;
+ else if (svn_path_is_url(new_path1))
+ new_path1 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path1);
+ else if (new_path1[0] == '/')
+ new_path1 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path1);
+ else
+ new_path1 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path1);
+
+ if (new_path2[0] == '\0')
+ new_path2 = new_path;
+ else if (svn_path_is_url(new_path2))
+ new_path1 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path2);
+ else if (new_path2[0] == '/')
+ new_path2 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path2);
+ else
+ new_path2 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path2);
+
+ *index_path = new_path;
+ *orig_path_1 = new_path1;
+ *orig_path_2 = new_path2;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Generate a label for the diff output for file PATH at revision REVNUM.
+ If REVNUM is invalid then it is assumed to be the current working
+ copy. Assumes the paths are already in the desired style (local
+ vs internal). Allocate the label in POOL. */
+static const char *
+diff_label(const char *path,
+ svn_revnum_t revnum,
+ apr_pool_t *pool)
+{
+ const char *label;
+ if (revnum != SVN_INVALID_REVNUM)
+ label = apr_psprintf(pool, _("%s\t(revision %ld)"), path, revnum);
+ else
+ label = apr_psprintf(pool, _("%s\t(working copy)"), path);
+
+ return label;
+}
+
+/* Print a git diff header for an addition within a diff between PATH1 and
+ * PATH2 to the stream OS using HEADER_ENCODING.
+ * All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_added(svn_stream_t *os, const char *header_encoding,
+ const char *path1, const char *path2,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "diff --git a/%s b/%s%s",
+ path1, path2, APR_EOL_STR));
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "new file mode 10644" APR_EOL_STR));
+ return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a deletion within a diff between PATH1 and
+ * PATH2 to the stream OS using HEADER_ENCODING.
+ * All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_deleted(svn_stream_t *os, const char *header_encoding,
+ const char *path1, const char *path2,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "diff --git a/%s b/%s%s",
+ path1, path2, APR_EOL_STR));
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "deleted file mode 10644"
+ APR_EOL_STR));
+ return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a copy from COPYFROM_PATH to PATH to the stream
+ * OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_copied(svn_stream_t *os, const char *header_encoding,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ const char *path,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "diff --git a/%s b/%s%s",
+ copyfrom_path, path, APR_EOL_STR));
+ if (copyfrom_rev != SVN_INVALID_REVNUM)
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "copy from %s@%ld%s", copyfrom_path,
+ copyfrom_rev, APR_EOL_STR));
+ else
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "copy from %s%s", copyfrom_path,
+ APR_EOL_STR));
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "copy to %s%s", path, APR_EOL_STR));
+ return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a rename from COPYFROM_PATH to PATH to the
+ * stream OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_renamed(svn_stream_t *os, const char *header_encoding,
+ const char *copyfrom_path, const char *path,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "diff --git a/%s b/%s%s",
+ copyfrom_path, path, APR_EOL_STR));
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "rename from %s%s", copyfrom_path,
+ APR_EOL_STR));
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "rename to %s%s", path, APR_EOL_STR));
+ return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a modification within a diff between PATH1 and
+ * PATH2 to the stream OS using HEADER_ENCODING.
+ * All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_modified(svn_stream_t *os, const char *header_encoding,
+ const char *path1, const char *path2,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "diff --git a/%s b/%s%s",
+ path1, path2, APR_EOL_STR));
+ return SVN_NO_ERROR;
+}
+
+/* Print a git diff header showing the OPERATION to the stream OS using
+ * HEADER_ENCODING. Return suitable diff labels for the git diff in *LABEL1
+ * and *LABEL2. REPOS_RELPATH1 and REPOS_RELPATH2 are relative to reposroot.
+ * are the paths passed to the original diff command. REV1 and REV2 are
+ * revisions being diffed. COPYFROM_PATH and COPYFROM_REV indicate where the
+ * diffed item was copied from.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+print_git_diff_header(svn_stream_t *os,
+ const char **label1, const char **label2,
+ svn_diff_operation_kind_t operation,
+ const char *repos_relpath1,
+ const char *repos_relpath2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ const char *header_encoding,
+ apr_pool_t *scratch_pool)
+{
+ if (operation == svn_diff_op_deleted)
+ {
+ SVN_ERR(print_git_diff_header_deleted(os, header_encoding,
+ repos_relpath1, repos_relpath2,
+ scratch_pool));
+ *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
+ rev1, scratch_pool);
+ *label2 = diff_label("/dev/null", rev2, scratch_pool);
+
+ }
+ else if (operation == svn_diff_op_copied)
+ {
+ SVN_ERR(print_git_diff_header_copied(os, header_encoding,
+ copyfrom_path, copyfrom_rev,
+ repos_relpath2,
+ scratch_pool));
+ *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
+ rev1, scratch_pool);
+ *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+ rev2, scratch_pool);
+ }
+ else if (operation == svn_diff_op_added)
+ {
+ SVN_ERR(print_git_diff_header_added(os, header_encoding,
+ repos_relpath1, repos_relpath2,
+ scratch_pool));
+ *label1 = diff_label("/dev/null", rev1, scratch_pool);
+ *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+ rev2, scratch_pool);
+ }
+ else if (operation == svn_diff_op_modified)
+ {
+ SVN_ERR(print_git_diff_header_modified(os, header_encoding,
+ repos_relpath1, repos_relpath2,
+ scratch_pool));
+ *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
+ rev1, scratch_pool);
+ *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+ rev2, scratch_pool);
+ }
+ else if (operation == svn_diff_op_moved)
+ {
+ SVN_ERR(print_git_diff_header_renamed(os, header_encoding,
+ copyfrom_path, repos_relpath2,
+ scratch_pool));
+ *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
+ rev1, scratch_pool);
+ *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+ rev2, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* A helper func that writes out verbal descriptions of property diffs
+ to OUTSTREAM. Of course, OUTSTREAM will probably be whatever was
+ passed to svn_client_diff6(), which is probably stdout.
+
+ ### FIXME needs proper docstring
+
+ If USE_GIT_DIFF_FORMAT is TRUE, pring git diff headers, which always
+ show paths relative to the repository root. RA_SESSION and WC_CTX are
+ needed to normalize paths relative the repository root, and are ignored
+ if USE_GIT_DIFF_FORMAT is FALSE.
+
+ ANCHOR is the local path where the diff editor is anchored. */
+static svn_error_t *
+display_prop_diffs(const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ const char *diff_relpath,
+ const char *anchor,
+ const char *orig_path1,
+ const char *orig_path2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *encoding,
+ svn_stream_t *outstream,
+ const char *relative_to_dir,
+ svn_boolean_t show_diff_header,
+ svn_boolean_t use_git_diff_format,
+ svn_ra_session_t *ra_session,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_relpath1 = NULL;
+ const char *repos_relpath2 = NULL;
+ const char *index_path = diff_relpath;
+ const char *adjusted_path1 = orig_path1;
+ const char *adjusted_path2 = orig_path2;
+
+ if (use_git_diff_format)
+ {
+ SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, orig_path1,
+ ra_session, wc_ctx, anchor,
+ scratch_pool, scratch_pool));
+ SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, orig_path2,
+ ra_session, wc_ctx, anchor,
+ scratch_pool, scratch_pool));
+ }
+
+ /* If we're creating a diff on the wc root, path would be empty. */
+ SVN_ERR(adjust_paths_for_diff_labels(&index_path, &adjusted_path1,
+ &adjusted_path2,
+ relative_to_dir, anchor,
+ scratch_pool, scratch_pool));
+
+ if (show_diff_header)
+ {
+ const char *label1;
+ const char *label2;
+
+ label1 = diff_label(adjusted_path1, rev1, scratch_pool);
+ label2 = diff_label(adjusted_path2, rev2, scratch_pool);
+
+ /* ### Should we show the paths in platform specific format,
+ * ### diff_content_changed() does not! */
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
+ "Index: %s" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path));
+
+ if (use_git_diff_format)
+ SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
+ svn_diff_op_modified,
+ repos_relpath1, repos_relpath2,
+ rev1, rev2, NULL,
+ SVN_INVALID_REVNUM,
+ encoding, scratch_pool));
+
+ /* --- label1
+ * +++ label2 */
+ SVN_ERR(svn_diff__unidiff_write_header(
+ outstream, encoding, label1, label2, scratch_pool));
+ }
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
+ _("%sProperty changes on: %s%s"),
+ APR_EOL_STR,
+ use_git_diff_format
+ ? repos_relpath1
+ : index_path,
+ APR_EOL_STR));
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
+ SVN_DIFF__UNDER_STRING APR_EOL_STR));
+
+ SVN_ERR(svn_diff__display_prop_diffs(
+ outstream, encoding, propchanges, original_props,
+ TRUE /* pretty_print_mergeinfo */, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/*-----------------------------------------------------------------*/
+
+/*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
+
+
+struct diff_cmd_baton {
+
+ /* If non-null, the external diff command to invoke. */
+ const char *diff_cmd;
+
+ /* This is allocated in this struct's pool or a higher-up pool. */
+ union {
+ /* If 'diff_cmd' is null, then this is the parsed options to
+ pass to the internal libsvn_diff implementation. */
+ svn_diff_file_options_t *for_internal;
+ /* Else if 'diff_cmd' is non-null, then... */
+ struct {
+ /* ...this is an argument array for the external command, and */
+ const char **argv;
+ /* ...this is the length of argv. */
+ int argc;
+ } for_external;
+ } options;
+
+ apr_pool_t *pool;
+ svn_stream_t *outstream;
+ svn_stream_t *errstream;
+
+ const char *header_encoding;
+
+ /* The original targets passed to the diff command. We may need
+ these to construct distinctive diff labels when comparing the
+ same relative path in the same revision, under different anchors
+ (for example, when comparing a trunk against a branch). */
+ const char *orig_path_1;
+ const char *orig_path_2;
+
+ /* These are the numeric representations of the revisions passed to
+ svn_client_diff6(), either may be SVN_INVALID_REVNUM. We need these
+ because some of the svn_wc_diff_callbacks4_t don't get revision
+ arguments.
+
+ ### Perhaps we should change the callback signatures and eliminate
+ ### these?
+ */
+ svn_revnum_t revnum1;
+ svn_revnum_t revnum2;
+
+ /* Set this if you want diff output even for binary files. */
+ svn_boolean_t force_binary;
+
+ /* The directory that diff target paths should be considered as
+ relative to for output generation (see issue #2723). */
+ const char *relative_to_dir;
+
+ /* Whether property differences are ignored. */
+ svn_boolean_t ignore_properties;
+
+ /* Whether to show only property changes. */
+ svn_boolean_t properties_only;
+
+ /* Whether we're producing a git-style diff. */
+ svn_boolean_t use_git_diff_format;
+
+ /* Whether addition of a file is summarized versus showing a full diff. */
+ svn_boolean_t no_diff_added;
+
+ /* Whether deletion of a file is summarized versus showing a full diff. */
+ svn_boolean_t no_diff_deleted;
+
+ /* Whether to ignore copyfrom information when showing adds */
+ svn_boolean_t no_copyfrom_on_add;
+
+ /* Empty files for creating diffs or NULL if not used yet */
+ const char *empty_file;
+
+ svn_wc_context_t *wc_ctx;
+
+ /* The RA session used during diffs involving the repository. */
+ svn_ra_session_t *ra_session;
+
+ /* The anchor to prefix before wc paths */
+ const char *anchor;
+
+ /* Whether the local diff target of a repos->wc diff is a copy. */
+ svn_boolean_t repos_wc_diff_target_is_copy;
+};
+
+/* An helper for diff_dir_props_changed, diff_file_changed and diff_file_added
+ */
+static svn_error_t *
+diff_props_changed(const char *diff_relpath,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ svn_boolean_t dir_was_added,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ svn_boolean_t show_diff_header,
+ struct diff_cmd_baton *diff_cmd_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *props;
+
+ /* If property differences are ignored, there's nothing to do. */
+ if (diff_cmd_baton->ignore_properties)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props,
+ scratch_pool));
+
+ if (props->nelts > 0)
+ {
+ /* We're using the revnums from the diff_cmd_baton since there's
+ * no revision argument to the svn_wc_diff_callback_t
+ * dir_props_changed(). */
+ SVN_ERR(display_prop_diffs(props, original_props,
+ diff_relpath,
+ diff_cmd_baton->anchor,
+ diff_cmd_baton->orig_path_1,
+ diff_cmd_baton->orig_path_2,
+ rev1,
+ rev2,
+ diff_cmd_baton->header_encoding,
+ diff_cmd_baton->outstream,
+ diff_cmd_baton->relative_to_dir,
+ show_diff_header,
+ diff_cmd_baton->use_git_diff_format,
+ diff_cmd_baton->ra_session,
+ diff_cmd_baton->wc_ctx,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_dir_props_changed(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ svn_boolean_t dir_was_added,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_cmd_baton *diff_cmd_baton = diff_baton;
+
+ return svn_error_trace(diff_props_changed(diff_relpath,
+ /* ### These revs be filled
+ * ### with per node info */
+ dir_was_added
+ ? 0 /* Magic legacy value */
+ : diff_cmd_baton->revnum1,
+ diff_cmd_baton->revnum2,
+ dir_was_added,
+ propchanges,
+ original_props,
+ TRUE /* show_diff_header */,
+ diff_cmd_baton,
+ scratch_pool));
+}
+
+
+/* Show differences between TMPFILE1 and TMPFILE2. DIFF_RELPATH, REV1, and
+ REV2 are used in the headers to indicate the file and revisions. If either
+ MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff,
+ but instead print a warning message.
+
+ If FORCE_DIFF is TRUE, always write a diff, even for empty diffs.
+
+ Set *WROTE_HEADER to TRUE if a diff header was written */
+static svn_error_t *
+diff_content_changed(svn_boolean_t *wrote_header,
+ const char *diff_relpath,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ svn_diff_operation_kind_t operation,
+ svn_boolean_t force_diff,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ struct diff_cmd_baton *diff_cmd_baton,
+ apr_pool_t *scratch_pool)
+{
+ int exitcode;
+ const char *rel_to_dir = diff_cmd_baton->relative_to_dir;
+ svn_stream_t *errstream = diff_cmd_baton->errstream;
+ svn_stream_t *outstream = diff_cmd_baton->outstream;
+ const char *label1, *label2;
+ svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
+ const char *index_path = diff_relpath;
+ const char *path1 = diff_cmd_baton->orig_path_1;
+ const char *path2 = diff_cmd_baton->orig_path_2;
+
+ /* If only property differences are shown, there's nothing to do. */
+ if (diff_cmd_baton->properties_only)
+ return SVN_NO_ERROR;
+
+ /* Generate the diff headers. */
+ SVN_ERR(adjust_paths_for_diff_labels(&index_path, &path1, &path2,
+ rel_to_dir, diff_cmd_baton->anchor,
+ scratch_pool, scratch_pool));
+
+ label1 = diff_label(path1, rev1, scratch_pool);
+ label2 = diff_label(path2, rev2, scratch_pool);
+
+ /* Possible easy-out: if either mime-type is binary and force was not
+ specified, don't attempt to generate a viewable diff at all.
+ Print a warning and exit. */
+ if (mimetype1)
+ mt1_binary = svn_mime_type_is_binary(mimetype1);
+ if (mimetype2)
+ mt2_binary = svn_mime_type_is_binary(mimetype2);
+
+ if (! diff_cmd_baton->force_binary && (mt1_binary || mt2_binary))
+ {
+ /* Print out the diff header. */
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "Index: %s" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path));
+
+ /* ### Print git diff headers. */
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ _("Cannot display: file marked as a binary type.%s"),
+ APR_EOL_STR));
+
+ if (mt1_binary && !mt2_binary)
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "svn:mime-type = %s" APR_EOL_STR, mimetype1));
+ else if (mt2_binary && !mt1_binary)
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "svn:mime-type = %s" APR_EOL_STR, mimetype2));
+ else if (mt1_binary && mt2_binary)
+ {
+ if (strcmp(mimetype1, mimetype2) == 0)
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "svn:mime-type = %s" APR_EOL_STR,
+ mimetype1));
+ else
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "svn:mime-type = (%s, %s)" APR_EOL_STR,
+ mimetype1, mimetype2));
+ }
+
+ /* Exit early. */
+ return SVN_NO_ERROR;
+ }
+
+
+ if (diff_cmd_baton->diff_cmd)
+ {
+ apr_file_t *outfile;
+ apr_file_t *errfile;
+ const char *outfilename;
+ const char *errfilename;
+ svn_stream_t *stream;
+
+ /* Print out the diff header. */
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "Index: %s" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path));
+
+ /* ### Do we want to add git diff headers here too? I'd say no. The
+ * ### 'Index' and '===' line is something subversion has added. The rest
+ * ### is up to the external diff application. We may be dealing with
+ * ### a non-git compatible diff application.*/
+
+ /* We deal in streams, but svn_io_run_diff2() deals in file handles,
+ unfortunately, so we need to make these temporary files, and then
+ copy the contents to our stream. */
+ SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_io_run_diff2(".",
+ diff_cmd_baton->options.for_external.argv,
+ diff_cmd_baton->options.for_external.argc,
+ label1, label2,
+ tmpfile1, tmpfile2,
+ &exitcode, outfile, errfile,
+ diff_cmd_baton->diff_cmd, scratch_pool));
+
+ SVN_ERR(svn_io_file_close(outfile, scratch_pool));
+ SVN_ERR(svn_io_file_close(errfile, scratch_pool));
+
+ /* Now, open and copy our files to our output streams. */
+ SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(outstream,
+ scratch_pool),
+ NULL, NULL, scratch_pool));
+ SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(errstream,
+ scratch_pool),
+ NULL, NULL, scratch_pool));
+
+ /* We have a printed a diff for this path, mark it as visited. */
+ *wrote_header = TRUE;
+ }
+ else /* use libsvn_diff to generate the diff */
+ {
+ svn_diff_t *diff;
+
+ SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2,
+ diff_cmd_baton->options.for_internal,
+ scratch_pool));
+
+ if (force_diff
+ || diff_cmd_baton->use_git_diff_format
+ || svn_diff_contains_diffs(diff))
+ {
+ /* Print out the diff header. */
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "Index: %s" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path));
+
+ if (diff_cmd_baton->use_git_diff_format)
+ {
+ const char *repos_relpath1;
+ const char *repos_relpath2;
+ SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
+ diff_cmd_baton->orig_path_1,
+ diff_cmd_baton->ra_session,
+ diff_cmd_baton->wc_ctx,
+ diff_cmd_baton->anchor,
+ scratch_pool, scratch_pool));
+ SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
+ diff_cmd_baton->orig_path_2,
+ diff_cmd_baton->ra_session,
+ diff_cmd_baton->wc_ctx,
+ diff_cmd_baton->anchor,
+ scratch_pool, scratch_pool));
+ SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
+ operation,
+ repos_relpath1, repos_relpath2,
+ rev1, rev2,
+ copyfrom_path,
+ copyfrom_rev,
+ diff_cmd_baton->header_encoding,
+ scratch_pool));
+ }
+
+ /* Output the actual diff */
+ if (force_diff || svn_diff_contains_diffs(diff))
+ SVN_ERR(svn_diff_file_output_unified3(outstream, diff,
+ tmpfile1, tmpfile2, label1, label2,
+ diff_cmd_baton->header_encoding, rel_to_dir,
+ diff_cmd_baton->options.for_internal->show_c_function,
+ scratch_pool));
+
+ /* We have a printed a diff for this path, mark it as visited. */
+ *wrote_header = TRUE;
+ }
+ }
+
+ /* ### todo: someday we'll need to worry about whether we're going
+ to need to write a diff plug-in mechanism that makes use of the
+ two paths, instead of just blindly running SVN_CLIENT_DIFF. */
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+diff_file_opened(svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ const char *diff_relpath,
+ svn_revnum_t rev,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_file_changed(svn_wc_notify_state_t *content_state,
+ svn_wc_notify_state_t *prop_state,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *prop_changes,
+ apr_hash_t *original_props,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_cmd_baton *diff_cmd_baton = diff_baton;
+ svn_boolean_t wrote_header = FALSE;
+
+ /* During repos->wc diff of a copy revision numbers obtained
+ * from the working copy are always SVN_INVALID_REVNUM. */
+ if (diff_cmd_baton->repos_wc_diff_target_is_copy)
+ {
+ if (rev1 == SVN_INVALID_REVNUM &&
+ diff_cmd_baton->revnum1 != SVN_INVALID_REVNUM)
+ rev1 = diff_cmd_baton->revnum1;
+
+ if (rev2 == SVN_INVALID_REVNUM &&
+ diff_cmd_baton->revnum2 != SVN_INVALID_REVNUM)
+ rev2 = diff_cmd_baton->revnum2;
+ }
+
+ if (tmpfile1)
+ SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
+ tmpfile1, tmpfile2, rev1, rev2,
+ mimetype1, mimetype2,
+ svn_diff_op_modified, FALSE,
+ NULL,
+ SVN_INVALID_REVNUM, diff_cmd_baton,
+ scratch_pool));
+ if (prop_changes->nelts > 0)
+ SVN_ERR(diff_props_changed(diff_relpath, rev1, rev2, FALSE, prop_changes,
+ original_props, !wrote_header,
+ diff_cmd_baton, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Because the repos-diff editor passes at least one empty file to
+ each of these next two functions, they can be dumb wrappers around
+ the main workhorse routine. */
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_file_added(svn_wc_notify_state_t *content_state,
+ svn_wc_notify_state_t *prop_state,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ const apr_array_header_t *prop_changes,
+ apr_hash_t *original_props,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_cmd_baton *diff_cmd_baton = diff_baton;
+ svn_boolean_t wrote_header = FALSE;
+
+ /* During repos->wc diff of a copy revision numbers obtained
+ * from the working copy are always SVN_INVALID_REVNUM. */
+ if (diff_cmd_baton->repos_wc_diff_target_is_copy)
+ {
+ if (rev1 == SVN_INVALID_REVNUM &&
+ diff_cmd_baton->revnum1 != SVN_INVALID_REVNUM)
+ rev1 = diff_cmd_baton->revnum1;
+
+ if (rev2 == SVN_INVALID_REVNUM &&
+ diff_cmd_baton->revnum2 != SVN_INVALID_REVNUM)
+ rev2 = diff_cmd_baton->revnum2;
+ }
+
+ if (diff_cmd_baton->no_copyfrom_on_add
+ && (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_revision)))
+ {
+ apr_hash_t *empty_hash = apr_hash_make(scratch_pool);
+ apr_array_header_t *new_changes;
+
+ /* Rebase changes on having no left source. */
+ if (!diff_cmd_baton->empty_file)
+ SVN_ERR(svn_io_open_unique_file3(NULL, &diff_cmd_baton->empty_file,
+ NULL, svn_io_file_del_on_pool_cleanup,
+ diff_cmd_baton->pool, scratch_pool));
+
+ SVN_ERR(svn_prop_diffs(&new_changes,
+ svn_prop__patch(original_props, prop_changes,
+ scratch_pool),
+ empty_hash,
+ scratch_pool));
+
+ tmpfile1 = diff_cmd_baton->empty_file;
+ prop_changes = new_changes;
+ original_props = empty_hash;
+ copyfrom_revision = SVN_INVALID_REVNUM;
+ }
+
+ if (diff_cmd_baton->no_diff_added)
+ {
+ const char *index_path = diff_relpath;
+
+ if (diff_cmd_baton->anchor)
+ index_path = svn_dirent_join(diff_cmd_baton->anchor, diff_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_stream_printf_from_utf8(diff_cmd_baton->outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "Index: %s (added)" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path));
+ wrote_header = TRUE;
+ }
+ else if (tmpfile1 && copyfrom_path)
+ SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
+ tmpfile1, tmpfile2, rev1, rev2,
+ mimetype1, mimetype2,
+ svn_diff_op_copied,
+ TRUE /* force diff output */,
+ copyfrom_path,
+ copyfrom_revision, diff_cmd_baton,
+ scratch_pool));
+ else if (tmpfile1)
+ SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
+ tmpfile1, tmpfile2, rev1, rev2,
+ mimetype1, mimetype2,
+ svn_diff_op_added,
+ TRUE /* force diff output */,
+ NULL, SVN_INVALID_REVNUM,
+ diff_cmd_baton, scratch_pool));
+
+ if (prop_changes->nelts > 0)
+ SVN_ERR(diff_props_changed(diff_relpath, rev1, rev2,
+ FALSE, prop_changes,
+ original_props, ! wrote_header,
+ diff_cmd_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_file_deleted(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ const char *mimetype1,
+ const char *mimetype2,
+ apr_hash_t *original_props,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_cmd_baton *diff_cmd_baton = diff_baton;
+
+ if (diff_cmd_baton->no_diff_deleted)
+ {
+ const char *index_path = diff_relpath;
+
+ if (diff_cmd_baton->anchor)
+ index_path = svn_dirent_join(diff_cmd_baton->anchor, diff_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_stream_printf_from_utf8(diff_cmd_baton->outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "Index: %s (deleted)" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path));
+ }
+ else
+ {
+ svn_boolean_t wrote_header = FALSE;
+ if (tmpfile1)
+ SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
+ tmpfile1, tmpfile2,
+ diff_cmd_baton->revnum1,
+ diff_cmd_baton->revnum2,
+ mimetype1, mimetype2,
+ svn_diff_op_deleted, FALSE,
+ NULL, SVN_INVALID_REVNUM,
+ diff_cmd_baton,
+ scratch_pool));
+
+ /* Should we also report the properties as deleted? */
+ }
+
+ /* We don't list all the deleted properties. */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_dir_added(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *diff_relpath,
+ svn_revnum_t rev,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* Do nothing. */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_dir_deleted(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* Do nothing. */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_dir_opened(svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *diff_relpath,
+ svn_revnum_t rev,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* Do nothing. */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_dir_closed(svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ svn_boolean_t dir_was_added,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* Do nothing. */
+
+ return SVN_NO_ERROR;
+}
+
+static const svn_wc_diff_callbacks4_t diff_callbacks =
+{
+ diff_file_opened,
+ diff_file_changed,
+ diff_file_added,
+ diff_file_deleted,
+ diff_dir_deleted,
+ diff_dir_opened,
+ diff_dir_added,
+ diff_dir_props_changed,
+ diff_dir_closed
+};
+
+/*-----------------------------------------------------------------*/
+
+/** The logic behind 'svn diff' and 'svn merge'. */
+
+
+/* Hi! This is a comment left behind by Karl, and Ben is too afraid
+ to erase it at this time, because he's not fully confident that all
+ this knowledge has been grokked yet.
+
+ There are five cases:
+ 1. path is not a URL and start_revision != end_revision
+ 2. path is not a URL and start_revision == end_revision
+ 3. path is a URL and start_revision != end_revision
+ 4. path is a URL and start_revision == end_revision
+ 5. path is not a URL and no revisions given
+
+ With only one distinct revision the working copy provides the
+ other. When path is a URL there is no working copy. Thus
+
+ 1: compare repository versions for URL coresponding to working copy
+ 2: compare working copy against repository version
+ 3: compare repository versions for URL
+ 4: nothing to do.
+ 5: compare working copy against text-base
+
+ Case 4 is not as stupid as it looks, for example it may occur if
+ the user specifies two dates that resolve to the same revision. */
+
+
+/** Check if paths PATH_OR_URL1 and PATH_OR_URL2 are urls and if the
+ * revisions REVISION1 and REVISION2 are local. If PEG_REVISION is not
+ * unspecified, ensure that at least one of the two revisions is not
+ * BASE or WORKING.
+ * If PATH_OR_URL1 can only be found in the repository, set *IS_REPOS1
+ * to TRUE. If PATH_OR_URL2 can only be found in the repository, set
+ * *IS_REPOS2 to TRUE. */
+static svn_error_t *
+check_paths(svn_boolean_t *is_repos1,
+ svn_boolean_t *is_repos2,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision)
+{
+ svn_boolean_t is_local_rev1, is_local_rev2;
+
+ /* Verify our revision arguments in light of the paths. */
+ if ((revision1->kind == svn_opt_revision_unspecified)
+ || (revision2->kind == svn_opt_revision_unspecified))
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Not all required revisions are specified"));
+
+ /* Revisions can be said to be local or remote.
+ * BASE and WORKING are local revisions. */
+ is_local_rev1 =
+ ((revision1->kind == svn_opt_revision_base)
+ || (revision1->kind == svn_opt_revision_working));
+ is_local_rev2 =
+ ((revision2->kind == svn_opt_revision_base)
+ || (revision2->kind == svn_opt_revision_working));
+
+ if (peg_revision->kind != svn_opt_revision_unspecified &&
+ is_local_rev1 && is_local_rev2)
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("At least one revision must be something other "
+ "than BASE or WORKING when diffing a URL"));
+
+ /* Working copy paths with non-local revisions get turned into
+ URLs. We don't do that here, though. We simply record that it
+ needs to be done, which is information that helps us choose our
+ diff helper function. */
+ *is_repos1 = ! is_local_rev1 || svn_path_is_url(path_or_url1);
+ *is_repos2 = ! is_local_rev2 || svn_path_is_url(path_or_url2);
+
+ return SVN_NO_ERROR;
+}
+
+/* Raise an error if the diff target URL does not exist at REVISION.
+ * If REVISION does not equal OTHER_REVISION, mention both revisions in
+ * the error message. Use RA_SESSION to contact the repository.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+check_diff_target_exists(const char *url,
+ svn_revnum_t revision,
+ svn_revnum_t other_revision,
+ svn_ra_session_t *ra_session,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ const char *session_url;
+
+ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
+
+ if (strcmp(url, session_url) != 0)
+ SVN_ERR(svn_ra_reparent(ra_session, url, pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", revision, &kind, pool));
+ if (kind == svn_node_none)
+ {
+ if (revision == other_revision)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Diff target '%s' was not found in the "
+ "repository at revision '%ld'"),
+ url, revision);
+ else
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Diff target '%s' was not found in the "
+ "repository at revision '%ld' or '%ld'"),
+ url, revision, other_revision);
+ }
+
+ if (strcmp(url, session_url) != 0)
+ SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return in *RESOLVED_URL the URL which PATH_OR_URL@PEG_REVISION has in
+ * REVISION. If the object has no location in REVISION, set *RESOLVED_URL
+ * to NULL. */
+static svn_error_t *
+resolve_pegged_diff_target_url(const char **resolved_url,
+ svn_ra_session_t *ra_session,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ /* Check if the PATH_OR_URL exists at REVISION. */
+ err = svn_client__repos_locations(resolved_url, NULL,
+ NULL, NULL,
+ ra_session,
+ path_or_url,
+ peg_revision,
+ revision,
+ NULL,
+ ctx, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES ||
+ err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *resolved_url = NULL;
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/** Prepare a repos repos diff between PATH_OR_URL1 and
+ * PATH_OR_URL2@PEG_REVISION, in the revision range REVISION1:REVISION2.
+ * Return URLs and peg revisions in *URL1, *REV1 and in *URL2, *REV2.
+ * Return suitable anchors in *ANCHOR1 and *ANCHOR2, and targets in
+ * *TARGET1 and *TARGET2, based on *URL1 and *URL2.
+ * Indicate the corresponding node kinds in *KIND1 and *KIND2, and verify
+ * that at least one of the diff targets exists.
+ * Set *BASE_PATH corresponding to the URL opened in the new *RA_SESSION
+ * which is pointing at *ANCHOR1.
+ * Use client context CTX. Do all allocations in POOL. */
+static svn_error_t *
+diff_prepare_repos_repos(const char **url1,
+ const char **url2,
+ const char **base_path,
+ svn_revnum_t *rev1,
+ svn_revnum_t *rev2,
+ const char **anchor1,
+ const char **anchor2,
+ const char **target1,
+ const char **target2,
+ svn_node_kind_t *kind1,
+ svn_node_kind_t *kind2,
+ svn_ra_session_t **ra_session,
+ svn_client_ctx_t *ctx,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ apr_pool_t *pool)
+{
+ const char *abspath_or_url2;
+ const char *abspath_or_url1;
+ const char *repos_root_url;
+ const char *wri_abspath = NULL;
+
+ if (!svn_path_is_url(path_or_url2))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&abspath_or_url2, path_or_url2, pool));
+ SVN_ERR(svn_wc__node_get_url(url2, ctx->wc_ctx, abspath_or_url2,
+ pool, pool));
+ wri_abspath = abspath_or_url2;
+ }
+ else
+ *url2 = abspath_or_url2 = apr_pstrdup(pool, path_or_url2);
+
+ if (!svn_path_is_url(path_or_url1))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1, pool));
+ SVN_ERR(svn_wc__node_get_url(url1, ctx->wc_ctx, abspath_or_url1,
+ pool, pool));
+ wri_abspath = abspath_or_url1;
+ }
+ else
+ *url1 = abspath_or_url1 = apr_pstrdup(pool, path_or_url1);
+
+ /* We need exactly one BASE_PATH, so we'll let the BASE_PATH
+ calculated for PATH_OR_URL2 override the one for PATH_OR_URL1
+ (since the diff will be "applied" to URL2 anyway). */
+ *base_path = NULL;
+ if (strcmp(*url1, path_or_url1) != 0)
+ *base_path = path_or_url1;
+ if (strcmp(*url2, path_or_url2) != 0)
+ *base_path = path_or_url2;
+
+ SVN_ERR(svn_client_open_ra_session2(ra_session, *url2, wri_abspath,
+ ctx, pool, pool));
+
+ /* If we are performing a pegged diff, we need to find out what our
+ actual URLs will be. */
+ if (peg_revision->kind != svn_opt_revision_unspecified)
+ {
+ const char *resolved_url1;
+ const char *resolved_url2;
+
+ SVN_ERR(resolve_pegged_diff_target_url(&resolved_url2, *ra_session,
+ path_or_url2, peg_revision,
+ revision2, ctx, pool));
+
+ SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool));
+ SVN_ERR(resolve_pegged_diff_target_url(&resolved_url1, *ra_session,
+ path_or_url1, peg_revision,
+ revision1, ctx, pool));
+
+ /* Either or both URLs might have changed as a result of resolving
+ * the PATH_OR_URL@PEG_REVISION's history. If only one of the URLs
+ * could be resolved, use the same URL for URL1 and URL2, so we can
+ * show a diff that adds or removes the object (see issue #4153). */
+ if (resolved_url2)
+ {
+ *url2 = resolved_url2;
+ if (!resolved_url1)
+ *url1 = resolved_url2;
+ }
+ if (resolved_url1)
+ {
+ *url1 = resolved_url1;
+ if (!resolved_url2)
+ *url2 = resolved_url1;
+ }
+
+ /* Reparent the session, since *URL2 might have changed as a result
+ the above call. */
+ SVN_ERR(svn_ra_reparent(*ra_session, *url2, pool));
+ }
+
+ /* Resolve revision and get path kind for the second target. */
+ SVN_ERR(svn_client__get_revision_number(rev2, NULL, ctx->wc_ctx,
+ (path_or_url2 == *url2) ? NULL : abspath_or_url2,
+ *ra_session, revision2, pool));
+ SVN_ERR(svn_ra_check_path(*ra_session, "", *rev2, kind2, pool));
+
+ /* Do the same for the first target. */
+ SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool));
+ SVN_ERR(svn_client__get_revision_number(rev1, NULL, ctx->wc_ctx,
+ (strcmp(path_or_url1, *url1) == 0) ? NULL : abspath_or_url1,
+ *ra_session, revision1, pool));
+ SVN_ERR(svn_ra_check_path(*ra_session, "", *rev1, kind1, pool));
+
+ /* Either both URLs must exist at their respective revisions,
+ * or one of them may be missing from one side of the diff. */
+ if (*kind1 == svn_node_none && *kind2 == svn_node_none)
+ {
+ if (strcmp(*url1, *url2) == 0)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Diff target '%s' was not found in the "
+ "repository at revisions '%ld' and '%ld'"),
+ *url1, *rev1, *rev2);
+ else
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Diff targets '%s' and '%s' were not found "
+ "in the repository at revisions '%ld' and "
+ "'%ld'"),
+ *url1, *url2, *rev1, *rev2);
+ }
+ else if (*kind1 == svn_node_none)
+ SVN_ERR(check_diff_target_exists(*url1, *rev2, *rev1, *ra_session, pool));
+ else if (*kind2 == svn_node_none)
+ SVN_ERR(check_diff_target_exists(*url2, *rev1, *rev2, *ra_session, pool));
+
+ SVN_ERR(svn_ra_get_repos_root2(*ra_session, &repos_root_url, pool));
+
+ /* Choose useful anchors and targets for our two URLs. */
+ *anchor1 = *url1;
+ *anchor2 = *url2;
+ *target1 = "";
+ *target2 = "";
+
+ /* If none of the targets is the repository root open the parent directory
+ to allow describing replacement of the target itself */
+ if (strcmp(*url1, repos_root_url) != 0
+ && strcmp(*url2, repos_root_url) != 0)
+ {
+ svn_uri_split(anchor1, target1, *url1, pool);
+ svn_uri_split(anchor2, target2, *url2, pool);
+ if (*base_path
+ && (*kind1 == svn_node_file || *kind2 == svn_node_file))
+ *base_path = svn_dirent_dirname(*base_path, pool);
+ SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* A Theoretical Note From Ben, regarding do_diff().
+
+ This function is really svn_client_diff6(). If you read the public
+ API description for svn_client_diff6(), it sounds quite Grand. It
+ sounds really generalized and abstract and beautiful: that it will
+ diff any two paths, be they working-copy paths or URLs, at any two
+ revisions.
+
+ Now, the *reality* is that we have exactly three 'tools' for doing
+ diffing, and thus this routine is built around the use of the three
+ tools. Here they are, for clarity:
+
+ - svn_wc_diff: assumes both paths are the same wcpath.
+ compares wcpath@BASE vs. wcpath@WORKING
+
+ - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING
+
+ - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2
+
+ Since Subversion 1.8 we also have a variant of svn_wc_diff called
+ svn_client__arbitrary_nodes_diff, that allows handling WORKING-WORKING
+ comparisions between nodes in the working copy.
+
+ So the truth of the matter is, if the caller's arguments can't be
+ pigeonholed into one of these use-cases, we currently bail with a
+ friendly apology.
+
+ Perhaps someday a brave soul will truly make svn_client_diff6()
+ perfectly general. For now, we live with the 90% case. Certainly,
+ the commandline client only calls this function in legal ways.
+ When there are other users of svn_client.h, maybe this will become
+ a more pressing issue.
+ */
+
+/* Return a "you can't do that" error, optionally wrapping another
+ error CHILD_ERR. */
+static svn_error_t *
+unsupported_diff_error(svn_error_t *child_err)
+{
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err,
+ _("Sorry, svn_client_diff6 was called in a way "
+ "that is not yet supported"));
+}
+
+/* Perform a diff between two working-copy paths.
+
+ PATH1 and PATH2 are both working copy paths. REVISION1 and
+ REVISION2 are their respective revisions.
+
+ All other options are the same as those passed to svn_client_diff6(). */
+static svn_error_t *
+diff_wc_wc(const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ const apr_array_header_t *changelists,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ struct diff_cmd_baton *callback_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *abspath1;
+ svn_error_t *err;
+ svn_node_kind_t kind;
+
+ SVN_ERR_ASSERT(! svn_path_is_url(path1));
+ SVN_ERR_ASSERT(! svn_path_is_url(path2));
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, pool));
+
+ /* Currently we support only the case where path1 and path2 are the
+ same path. */
+ if ((strcmp(path1, path2) != 0)
+ || (! ((revision1->kind == svn_opt_revision_base)
+ && (revision2->kind == svn_opt_revision_working))))
+ return unsupported_diff_error(
+ svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Only diffs between a path's text-base "
+ "and its working files are supported at this time"
+ )));
+
+
+ /* Resolve named revisions to real numbers. */
+ err = svn_client__get_revision_number(&callback_baton->revnum1, NULL,
+ ctx->wc_ctx, abspath1, NULL,
+ revision1, pool);
+
+ /* In case of an added node, we have no base rev, and we show a revision
+ * number of 0. Note that this code is currently always asking for
+ * svn_opt_revision_base.
+ * ### TODO: get rid of this 0 for added nodes. */
+ if (err && (err->apr_err == SVN_ERR_CLIENT_BAD_REVISION))
+ {
+ svn_error_clear(err);
+ callback_baton->revnum1 = 0;
+ }
+ else
+ SVN_ERR(err);
+
+ callback_baton->revnum2 = SVN_INVALID_REVNUM; /* WC */
+
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1,
+ TRUE, FALSE, pool));
+
+ if (kind != svn_node_dir)
+ callback_baton->anchor = svn_dirent_dirname(path1, pool);
+ else
+ callback_baton->anchor = path1;
+
+ SVN_ERR(svn_wc_diff6(ctx->wc_ctx,
+ abspath1,
+ callbacks, callback_baton,
+ depth,
+ ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+ return SVN_NO_ERROR;
+}
+
+/* Perform a diff between two repository paths.
+
+ PATH_OR_URL1 and PATH_OR_URL2 may be either URLs or the working copy paths.
+ REVISION1 and REVISION2 are their respective revisions.
+ If PEG_REVISION is specified, PATH_OR_URL2 is the path at the peg revision,
+ and the actual two paths compared are determined by following copy
+ history from PATH_OR_URL2.
+
+ All other options are the same as those passed to svn_client_diff6(). */
+static svn_error_t *
+diff_repos_repos(const svn_wc_diff_callbacks4_t *callbacks,
+ struct diff_cmd_baton *callback_baton,
+ svn_client_ctx_t *ctx,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *extra_ra_session;
+
+ const svn_ra_reporter3_t *reporter;
+ void *reporter_baton;
+
+ const svn_delta_editor_t *diff_editor;
+ void *diff_edit_baton;
+
+ const svn_diff_tree_processor_t *diff_processor;
+
+ const char *url1;
+ const char *url2;
+ const char *base_path;
+ svn_revnum_t rev1;
+ svn_revnum_t rev2;
+ svn_node_kind_t kind1;
+ svn_node_kind_t kind2;
+ const char *anchor1;
+ const char *anchor2;
+ const char *target1;
+ const char *target2;
+ svn_ra_session_t *ra_session;
+ const char *wri_abspath = NULL;
+
+ /* Prepare info for the repos repos diff. */
+ SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &base_path, &rev1, &rev2,
+ &anchor1, &anchor2, &target1, &target2,
+ &kind1, &kind2, &ra_session,
+ ctx, path_or_url1, path_or_url2,
+ revision1, revision2, peg_revision,
+ pool));
+
+ /* Find a WC path for the ra session */
+ if (!svn_path_is_url(path_or_url1))
+ SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url1, pool));
+ else if (!svn_path_is_url(path_or_url2))
+ SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url2, pool));
+
+ /* Set up the repos_diff editor on BASE_PATH, if available.
+ Otherwise, we just use "". */
+
+ SVN_ERR(svn_wc__wrap_diff_callbacks(&diff_processor,
+ callbacks, callback_baton,
+ TRUE /* walk_deleted_dirs */,
+ pool, pool));
+
+ /* Get actual URLs. */
+ callback_baton->orig_path_1 = url1;
+ callback_baton->orig_path_2 = url2;
+
+ /* Get numeric revisions. */
+ callback_baton->revnum1 = rev1;
+ callback_baton->revnum2 = rev2;
+
+ callback_baton->ra_session = ra_session;
+ callback_baton->anchor = base_path;
+
+ /* The repository can bring in a new working copy, but not delete
+ everything. Luckily our new diff handler can just be reversed. */
+ if (kind2 == svn_node_none)
+ {
+ const char *str_tmp;
+ svn_revnum_t rev_tmp;
+
+ str_tmp = url2;
+ url2 = url1;
+ url1 = str_tmp;
+
+ rev_tmp = rev2;
+ rev2 = rev1;
+ rev1 = rev_tmp;
+
+ str_tmp = anchor2;
+ anchor2 = anchor1;
+ anchor1 = str_tmp;
+
+ str_tmp = target2;
+ target2 = target1;
+ target1 = str_tmp;
+
+ diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
+ NULL, pool);
+ }
+
+ /* Filter the first path component using a filter processor, until we fixed
+ the diff processing to handle this directly */
+ if ((kind1 != svn_node_file && kind2 != svn_node_file) && target1[0] != '\0')
+ {
+ diff_processor = svn_diff__tree_processor_filter_create(diff_processor,
+ target1, pool);
+ }
+
+ /* Now, we open an extra RA session to the correct anchor
+ location for URL1. This is used during the editor calls to fetch file
+ contents. */
+ SVN_ERR(svn_client_open_ra_session2(&extra_ra_session, anchor1, wri_abspath,
+ ctx, pool, pool));
+
+ SVN_ERR(svn_client__get_diff_editor2(
+ &diff_editor, &diff_edit_baton,
+ extra_ra_session, depth,
+ rev1,
+ TRUE /* text_deltas */,
+ diff_processor,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+
+ /* We want to switch our txn into URL2 */
+ SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton,
+ rev2, target1,
+ depth, ignore_ancestry, TRUE /* text_deltas */,
+ url2, diff_editor, diff_edit_baton, pool));
+
+ /* Drive the reporter; do the diff. */
+ SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
+ svn_depth_infinity,
+ FALSE, NULL,
+ pool));
+
+ return svn_error_trace(reporter->finish_report(reporter_baton, pool));
+}
+
+/* Perform a diff between a repository path and a working-copy path.
+
+ PATH_OR_URL1 may be either a URL or a working copy path. PATH2 is a
+ working copy path. REVISION1 and REVISION2 are their respective
+ revisions. If REVERSE is TRUE, the diff will be done in reverse.
+ If PEG_REVISION is specified, then PATH_OR_URL1 is the path in the peg
+ revision, and the actual repository path to be compared is
+ determined by following copy history.
+
+ All other options are the same as those passed to svn_client_diff6(). */
+static svn_error_t *
+diff_repos_wc(const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *peg_revision,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t reverse,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ const apr_array_header_t *changelists,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ struct diff_cmd_baton *cmd_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *pool = scratch_pool;
+ const char *url1, *anchor, *anchor_url, *target;
+ svn_revnum_t rev;
+ svn_ra_session_t *ra_session;
+ svn_depth_t diff_depth;
+ const svn_ra_reporter3_t *reporter;
+ void *reporter_baton;
+ const svn_delta_editor_t *diff_editor;
+ void *diff_edit_baton;
+ svn_boolean_t rev2_is_base = (revision2->kind == svn_opt_revision_base);
+ svn_boolean_t server_supports_depth;
+ const char *abspath_or_url1;
+ const char *abspath2;
+ const char *anchor_abspath;
+ svn_node_kind_t kind1;
+ svn_node_kind_t kind2;
+ svn_boolean_t is_copy;
+ svn_revnum_t cf_revision;
+ const char *cf_repos_relpath;
+ const char *cf_repos_root_url;
+
+ SVN_ERR_ASSERT(! svn_path_is_url(path2));
+
+ if (!svn_path_is_url(path_or_url1))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1, pool));
+ SVN_ERR(svn_wc__node_get_url(&url1, ctx->wc_ctx, abspath_or_url1,
+ pool, pool));
+ }
+ else
+ {
+ url1 = path_or_url1;
+ abspath_or_url1 = path_or_url1;
+ }
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath2, path2, pool));
+
+ /* Convert path_or_url1 to a URL to feed to do_diff. */
+ SVN_ERR(svn_wc_get_actual_target2(&anchor, &target,
+ ctx->wc_ctx, path2,
+ pool, pool));
+
+ /* Fetch the URL of the anchor directory. */
+ SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, pool));
+ SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath,
+ pool, pool));
+ SVN_ERR_ASSERT(anchor_url != NULL);
+
+ /* If we are performing a pegged diff, we need to find out what our
+ actual URLs will be. */
+ if (peg_revision->kind != svn_opt_revision_unspecified)
+ {
+ SVN_ERR(svn_client__repos_locations(&url1, NULL, NULL, NULL,
+ NULL,
+ path_or_url1,
+ peg_revision,
+ revision1, NULL,
+ ctx, pool));
+ if (!reverse)
+ {
+ cmd_baton->orig_path_1 = url1;
+ cmd_baton->orig_path_2 =
+ svn_path_url_add_component2(anchor_url, target, pool);
+ }
+ else
+ {
+ cmd_baton->orig_path_1 =
+ svn_path_url_add_component2(anchor_url, target, pool);
+ cmd_baton->orig_path_2 = url1;
+ }
+ }
+
+ /* Open an RA session to URL1 to figure out its node kind. */
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, url1, abspath2,
+ ctx, pool, pool));
+ /* Resolve the revision to use for URL1. */
+ SVN_ERR(svn_client__get_revision_number(&rev, NULL, ctx->wc_ctx,
+ (strcmp(path_or_url1, url1) == 0)
+ ? NULL : abspath_or_url1,
+ ra_session, revision1, pool));
+ SVN_ERR(svn_ra_check_path(ra_session, "", rev, &kind1, pool));
+
+ /* Figure out the node kind of the local target. */
+ SVN_ERR(svn_wc_read_kind2(&kind2, ctx->wc_ctx, abspath2,
+ TRUE, FALSE, pool));
+
+ cmd_baton->ra_session = ra_session;
+ cmd_baton->anchor = anchor;
+
+ if (!reverse)
+ cmd_baton->revnum1 = rev;
+ else
+ cmd_baton->revnum2 = rev;
+
+ /* Check if our diff target is a copied node. */
+ SVN_ERR(svn_wc__node_get_origin(&is_copy,
+ &cf_revision,
+ &cf_repos_relpath,
+ &cf_repos_root_url,
+ NULL, NULL,
+ ctx->wc_ctx, abspath2,
+ FALSE, pool, pool));
+
+ /* Use the diff editor to generate the diff. */
+ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, pool));
+ SVN_ERR(svn_wc__get_diff_editor(&diff_editor, &diff_edit_baton,
+ ctx->wc_ctx,
+ anchor_abspath,
+ target,
+ depth,
+ ignore_ancestry || is_copy,
+ show_copies_as_adds,
+ use_git_diff_format,
+ rev2_is_base,
+ reverse,
+ server_supports_depth,
+ changelists,
+ callbacks, callback_baton,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool, pool));
+ SVN_ERR(svn_ra_reparent(ra_session, anchor_url, pool));
+
+ if (depth != svn_depth_infinity)
+ diff_depth = depth;
+ else
+ diff_depth = svn_depth_unknown;
+
+ if (is_copy)
+ {
+ const char *copyfrom_parent_url;
+ const char *copyfrom_basename;
+ svn_depth_t copy_depth;
+
+ cmd_baton->repos_wc_diff_target_is_copy = TRUE;
+
+ /* We're diffing a locally copied/moved node.
+ * Describe the copy source to the reporter instead of the copy itself.
+ * Doing the latter would generate a single add_directory() call to the
+ * diff editor which results in an unexpected diff (the copy would
+ * be shown as deleted). */
+
+ if (cf_repos_relpath[0] == '\0')
+ {
+ copyfrom_parent_url = cf_repos_root_url;
+ copyfrom_basename = "";
+ }
+ else
+ {
+ const char *parent_relpath;
+ svn_relpath_split(&parent_relpath, &copyfrom_basename,
+ cf_repos_relpath, scratch_pool);
+
+ copyfrom_parent_url = svn_path_url_add_component2(cf_repos_root_url,
+ parent_relpath,
+ scratch_pool);
+ }
+ SVN_ERR(svn_ra_reparent(ra_session, copyfrom_parent_url, pool));
+
+ /* Tell the RA layer we want a delta to change our txn to URL1 */
+ SVN_ERR(svn_ra_do_diff3(ra_session,
+ &reporter, &reporter_baton,
+ rev,
+ target,
+ diff_depth,
+ ignore_ancestry,
+ TRUE, /* text_deltas */
+ url1,
+ diff_editor, diff_edit_baton, pool));
+
+ /* Report the copy source. */
+ SVN_ERR(svn_wc__node_get_depth(&copy_depth, ctx->wc_ctx, abspath2,
+ pool));
+
+ if (copy_depth == svn_depth_unknown)
+ copy_depth = svn_depth_infinity;
+
+ SVN_ERR(reporter->set_path(reporter_baton, "",
+ cf_revision,
+ copy_depth, FALSE, NULL, scratch_pool));
+
+ if (strcmp(target, copyfrom_basename) != 0)
+ SVN_ERR(reporter->link_path(reporter_baton, target,
+ svn_path_url_add_component2(
+ cf_repos_root_url,
+ cf_repos_relpath,
+ scratch_pool),
+ cf_revision,
+ copy_depth, FALSE, NULL, scratch_pool));
+
+ /* Finish the report to generate the diff. */
+ SVN_ERR(reporter->finish_report(reporter_baton, pool));
+ }
+ else
+ {
+ /* Tell the RA layer we want a delta to change our txn to URL1 */
+ SVN_ERR(svn_ra_do_diff3(ra_session,
+ &reporter, &reporter_baton,
+ rev,
+ target,
+ diff_depth,
+ ignore_ancestry,
+ TRUE, /* text_deltas */
+ url1,
+ diff_editor, diff_edit_baton, pool));
+
+ /* Create a txn mirror of path2; the diff editor will print
+ diffs in reverse. :-) */
+ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, abspath2,
+ reporter, reporter_baton,
+ FALSE, depth, TRUE,
+ (! server_supports_depth),
+ FALSE,
+ ctx->cancel_func, ctx->cancel_baton,
+ NULL, NULL, /* notification is N/A */
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This is basically just the guts of svn_client_diff[_peg]6(). */
+static svn_error_t *
+do_diff(const svn_wc_diff_callbacks4_t *callbacks,
+ struct diff_cmd_baton *callback_baton,
+ svn_client_ctx_t *ctx,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ const apr_array_header_t *changelists,
+ apr_pool_t *pool)
+{
+ svn_boolean_t is_repos1;
+ svn_boolean_t is_repos2;
+
+ /* Check if paths/revisions are urls/local. */
+ SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2,
+ revision1, revision2, peg_revision));
+
+ if (is_repos1)
+ {
+ if (is_repos2)
+ {
+ /* ### Ignores 'show_copies_as_adds'. */
+ SVN_ERR(diff_repos_repos(callbacks, callback_baton, ctx,
+ path_or_url1, path_or_url2,
+ revision1, revision2,
+ peg_revision, depth, ignore_ancestry,
+ pool));
+ }
+ else /* path_or_url2 is a working copy path */
+ {
+ SVN_ERR(diff_repos_wc(path_or_url1, revision1, peg_revision,
+ path_or_url2, revision2, FALSE, depth,
+ ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists,
+ callbacks, callback_baton, callback_baton,
+ ctx, pool));
+ }
+ }
+ else /* path_or_url1 is a working copy path */
+ {
+ if (is_repos2)
+ {
+ SVN_ERR(diff_repos_wc(path_or_url2, revision2, peg_revision,
+ path_or_url1, revision1, TRUE, depth,
+ ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists,
+ callbacks, callback_baton, callback_baton,
+ ctx, pool));
+ }
+ else /* path_or_url2 is a working copy path */
+ {
+ if (revision1->kind == svn_opt_revision_working
+ && revision2->kind == svn_opt_revision_working)
+ {
+ const char *abspath1;
+ const char *abspath2;
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, pool));
+ SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, pool));
+
+ SVN_ERR(svn_client__arbitrary_nodes_diff(abspath1, abspath2,
+ depth,
+ callbacks,
+ callback_baton,
+ ctx, pool));
+ }
+ else
+ SVN_ERR(diff_wc_wc(path_or_url1, revision1,
+ path_or_url2, revision2,
+ depth, ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists,
+ callbacks, callback_baton, ctx, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Perform a diff between a repository path and a working-copy path.
+
+ PATH_OR_URL1 may be either a URL or a working copy path. PATH2 is a
+ working copy path. REVISION1 and REVISION2 are their respective
+ revisions. If REVERSE is TRUE, the diff will be done in reverse.
+ If PEG_REVISION is specified, then PATH_OR_URL1 is the path in the peg
+ revision, and the actual repository path to be compared is
+ determined by following copy history.
+
+ All other options are the same as those passed to svn_client_diff6(). */
+static svn_error_t *
+diff_summarize_repos_wc(svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *peg_revision,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t reverse,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *anchor, *target;
+ svn_wc_diff_callbacks4_t *callbacks;
+ void *callback_baton;
+ struct diff_cmd_baton cmd_baton;
+
+ SVN_ERR_ASSERT(! svn_path_is_url(path2));
+
+ SVN_ERR(svn_wc_get_actual_target2(&anchor, &target,
+ ctx->wc_ctx, path2,
+ pool, pool));
+
+ SVN_ERR(svn_client__get_diff_summarize_callbacks(
+ &callbacks, &callback_baton, target, reverse,
+ summarize_func, summarize_baton, pool));
+
+ SVN_ERR(diff_repos_wc(path_or_url1, revision1, peg_revision,
+ path2, revision2, reverse,
+ depth, FALSE, TRUE, FALSE, changelists,
+ callbacks, callback_baton, &cmd_baton,
+ ctx, pool));
+ return SVN_NO_ERROR;
+}
+
+/* Perform a summary diff between two working-copy paths.
+
+ PATH1 and PATH2 are both working copy paths. REVISION1 and
+ REVISION2 are their respective revisions.
+
+ All other options are the same as those passed to svn_client_diff6(). */
+static svn_error_t *
+diff_summarize_wc_wc(svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_wc_diff_callbacks4_t *callbacks;
+ void *callback_baton;
+ const char *abspath1, *target1;
+ svn_node_kind_t kind;
+
+ SVN_ERR_ASSERT(! svn_path_is_url(path1));
+ SVN_ERR_ASSERT(! svn_path_is_url(path2));
+
+ /* Currently we support only the case where path1 and path2 are the
+ same path. */
+ if ((strcmp(path1, path2) != 0)
+ || (! ((revision1->kind == svn_opt_revision_base)
+ && (revision2->kind == svn_opt_revision_working))))
+ return unsupported_diff_error
+ (svn_error_create
+ (SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Summarized diffs are only supported between a path's text-base "
+ "and its working files at this time")));
+
+ /* Find the node kind of PATH1 so that we know whether the diff drive will
+ be anchored at PATH1 or its parent dir. */
+ SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, pool));
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1,
+ TRUE, FALSE, pool));
+ target1 = (kind == svn_node_dir) ? "" : svn_dirent_basename(path1, pool);
+ SVN_ERR(svn_client__get_diff_summarize_callbacks(
+ &callbacks, &callback_baton, target1, FALSE,
+ summarize_func, summarize_baton, pool));
+
+ SVN_ERR(svn_wc_diff6(ctx->wc_ctx,
+ abspath1,
+ callbacks, callback_baton,
+ depth,
+ ignore_ancestry, FALSE /* show_copies_as_adds */,
+ FALSE /* use_git_diff_format */, changelists,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+ return SVN_NO_ERROR;
+}
+
+/* Perform a diff summary between two repository paths. */
+static svn_error_t *
+diff_summarize_repos_repos(svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *extra_ra_session;
+
+ const svn_ra_reporter3_t *reporter;
+ void *reporter_baton;
+
+ const svn_delta_editor_t *diff_editor;
+ void *diff_edit_baton;
+
+ const svn_diff_tree_processor_t *diff_processor;
+
+ const char *url1;
+ const char *url2;
+ const char *base_path;
+ svn_revnum_t rev1;
+ svn_revnum_t rev2;
+ svn_node_kind_t kind1;
+ svn_node_kind_t kind2;
+ const char *anchor1;
+ const char *anchor2;
+ const char *target1;
+ const char *target2;
+ svn_ra_session_t *ra_session;
+ svn_wc_diff_callbacks4_t *callbacks;
+ void *callback_baton;
+
+ /* Prepare info for the repos repos diff. */
+ SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &base_path, &rev1, &rev2,
+ &anchor1, &anchor2, &target1, &target2,
+ &kind1, &kind2, &ra_session,
+ ctx, path_or_url1, path_or_url2,
+ revision1, revision2,
+ peg_revision, pool));
+
+ /* Set up the repos_diff editor. */
+ SVN_ERR(svn_client__get_diff_summarize_callbacks(
+ &callbacks, &callback_baton,
+ target1, FALSE, summarize_func, summarize_baton, pool));
+
+ SVN_ERR(svn_wc__wrap_diff_callbacks(&diff_processor,
+ callbacks, callback_baton,
+ TRUE /* walk_deleted_dirs */,
+ pool, pool));
+
+
+ /* The repository can bring in a new working copy, but not delete
+ everything. Luckily our new diff handler can just be reversed. */
+ if (kind2 == svn_node_none)
+ {
+ const char *str_tmp;
+ svn_revnum_t rev_tmp;
+
+ str_tmp = url2;
+ url2 = url1;
+ url1 = str_tmp;
+
+ rev_tmp = rev2;
+ rev2 = rev1;
+ rev1 = rev_tmp;
+
+ str_tmp = anchor2;
+ anchor2 = anchor1;
+ anchor1 = str_tmp;
+
+ str_tmp = target2;
+ target2 = target1;
+ target1 = str_tmp;
+
+ diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
+ NULL, pool);
+ }
+
+ /* Now, we open an extra RA session to the correct anchor
+ location for URL1. This is used to get deleted path information. */
+ SVN_ERR(svn_client_open_ra_session2(&extra_ra_session, anchor1, NULL,
+ ctx, pool, pool));
+
+ SVN_ERR(svn_client__get_diff_editor2(&diff_editor, &diff_edit_baton,
+ extra_ra_session,
+ depth,
+ rev1,
+ FALSE /* text_deltas */,
+ diff_processor,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+
+ /* We want to switch our txn into URL2 */
+ SVN_ERR(svn_ra_do_diff3
+ (ra_session, &reporter, &reporter_baton, rev2, target1,
+ depth, ignore_ancestry,
+ FALSE /* do not create text delta */, url2, diff_editor,
+ diff_edit_baton, pool));
+
+ /* Drive the reporter; do the diff. */
+ SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
+ svn_depth_infinity,
+ FALSE, NULL, pool));
+ return svn_error_trace(reporter->finish_report(reporter_baton, pool));
+}
+
+/* This is basically just the guts of svn_client_diff_summarize[_peg]2(). */
+static svn_error_t *
+do_diff_summarize(svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ apr_pool_t *pool)
+{
+ svn_boolean_t is_repos1;
+ svn_boolean_t is_repos2;
+
+ /* Check if paths/revisions are urls/local. */
+ SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2,
+ revision1, revision2, peg_revision));
+
+ if (is_repos1)
+ {
+ if (is_repos2)
+ SVN_ERR(diff_summarize_repos_repos(summarize_func, summarize_baton, ctx,
+ path_or_url1, path_or_url2,
+ revision1, revision2,
+ peg_revision, depth, ignore_ancestry,
+ pool));
+ else
+ SVN_ERR(diff_summarize_repos_wc(summarize_func, summarize_baton,
+ path_or_url1, revision1,
+ peg_revision,
+ path_or_url2, revision2,
+ FALSE, depth,
+ ignore_ancestry,
+ changelists,
+ ctx, pool));
+ }
+ else /* ! is_repos1 */
+ {
+ if (is_repos2)
+ SVN_ERR(diff_summarize_repos_wc(summarize_func, summarize_baton,
+ path_or_url2, revision2,
+ peg_revision,
+ path_or_url1, revision1,
+ TRUE, depth,
+ ignore_ancestry,
+ changelists,
+ ctx, pool));
+ else
+ {
+ if (revision1->kind == svn_opt_revision_working
+ && revision2->kind == svn_opt_revision_working)
+ {
+ const char *abspath1;
+ const char *abspath2;
+ svn_wc_diff_callbacks4_t *callbacks;
+ void *callback_baton;
+ const char *target;
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, pool));
+ SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, pool));
+
+ SVN_ERR(svn_io_check_resolved_path(abspath1, &kind, pool));
+
+ if (kind == svn_node_dir)
+ target = "";
+ else
+ target = svn_dirent_basename(path_or_url1, NULL);
+
+ SVN_ERR(svn_client__get_diff_summarize_callbacks(
+ &callbacks, &callback_baton, target, FALSE,
+ summarize_func, summarize_baton, pool));
+
+ SVN_ERR(svn_client__arbitrary_nodes_diff(abspath1, abspath2,
+ depth,
+ callbacks,
+ callback_baton,
+ ctx, pool));
+ }
+ else
+ SVN_ERR(diff_summarize_wc_wc(summarize_func, summarize_baton,
+ path_or_url1, revision1,
+ path_or_url2, revision2,
+ depth, ignore_ancestry,
+ changelists, ctx, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Initialize DIFF_CMD_BATON.diff_cmd and DIFF_CMD_BATON.options,
+ * according to OPTIONS and CONFIG. CONFIG and OPTIONS may be null.
+ * Allocate the fields in POOL, which should be at least as long-lived
+ * as the pool DIFF_CMD_BATON itself is allocated in.
+ */
+static svn_error_t *
+set_up_diff_cmd_and_options(struct diff_cmd_baton *diff_cmd_baton,
+ const apr_array_header_t *options,
+ apr_hash_t *config, apr_pool_t *pool)
+{
+ const char *diff_cmd = NULL;
+
+ /* See if there is a diff command and/or diff arguments. */
+ if (config)
+ {
+ svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
+ svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF_CMD, NULL);
+ if (options == NULL)
+ {
+ const char *diff_extensions;
+ svn_config_get(cfg, &diff_extensions, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF_EXTENSIONS, NULL);
+ if (diff_extensions)
+ options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE, pool);
+ }
+ }
+
+ if (options == NULL)
+ options = apr_array_make(pool, 0, sizeof(const char *));
+
+ if (diff_cmd)
+ SVN_ERR(svn_path_cstring_to_utf8(&diff_cmd_baton->diff_cmd, diff_cmd,
+ pool));
+ else
+ diff_cmd_baton->diff_cmd = NULL;
+
+ /* If there was a command, arrange options to pass to it. */
+ if (diff_cmd_baton->diff_cmd)
+ {
+ const char **argv = NULL;
+ int argc = options->nelts;
+ if (argc)
+ {
+ int i;
+ argv = apr_palloc(pool, argc * sizeof(char *));
+ for (i = 0; i < argc; i++)
+ SVN_ERR(svn_utf_cstring_to_utf8(&argv[i],
+ APR_ARRAY_IDX(options, i, const char *), pool));
+ }
+ diff_cmd_baton->options.for_external.argv = argv;
+ diff_cmd_baton->options.for_external.argc = argc;
+ }
+ else /* No command, so arrange options for internal invocation instead. */
+ {
+ diff_cmd_baton->options.for_internal
+ = svn_diff_file_options_create(pool);
+ SVN_ERR(svn_diff_file_options_parse
+ (diff_cmd_baton->options.for_internal, options, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*----------------------------------------------------------------------- */
+
+/*** Public Interfaces. ***/
+
+/* Display context diffs between two PATH/REVISION pairs. Each of
+ these inputs will be one of the following:
+
+ - a repository URL at a given revision.
+ - a working copy path, ignoring local mods.
+ - a working copy path, including local mods.
+
+ We can establish a matrix that shows the nine possible types of
+ diffs we expect to support.
+
+
+ ` . DST || URL:rev | WC:base | WC:working |
+ ` . || | | |
+ SRC ` . || | | |
+ ============++============+============+============+
+ URL:rev || (*) | (*) | (*) |
+ || | | |
+ || | | |
+ || | | |
+ ------------++------------+------------+------------+
+ WC:base || (*) | |
+ || | New svn_wc_diff which |
+ || | is smart enough to |
+ || | handle two WC paths |
+ ------------++------------+ and their related +
+ WC:working || (*) | text-bases and working |
+ || | files. This operation |
+ || | is entirely local. |
+ || | |
+ ------------++------------+------------+------------+
+ * These cases require server communication.
+*/
+svn_error_t *
+svn_client_diff6(const apr_array_header_t *options,
+ const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_added,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t ignore_properties,
+ svn_boolean_t properties_only,
+ svn_boolean_t use_git_diff_format,
+ const char *header_encoding,
+ svn_stream_t *outstream,
+ svn_stream_t *errstream,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct diff_cmd_baton diff_cmd_baton = { 0 };
+ svn_opt_revision_t peg_revision;
+
+ if (ignore_properties && properties_only)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Cannot ignore properties and show only "
+ "properties at the same time"));
+
+ /* We will never do a pegged diff from here. */
+ peg_revision.kind = svn_opt_revision_unspecified;
+
+ /* setup callback and baton */
+ diff_cmd_baton.orig_path_1 = path_or_url1;
+ diff_cmd_baton.orig_path_2 = path_or_url2;
+
+ SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton, options,
+ ctx->config, pool));
+ diff_cmd_baton.pool = pool;
+ diff_cmd_baton.outstream = outstream;
+ diff_cmd_baton.errstream = errstream;
+ diff_cmd_baton.header_encoding = header_encoding;
+ diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
+ diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
+
+ diff_cmd_baton.force_binary = ignore_content_type;
+ diff_cmd_baton.ignore_properties = ignore_properties;
+ diff_cmd_baton.properties_only = properties_only;
+ diff_cmd_baton.relative_to_dir = relative_to_dir;
+ diff_cmd_baton.use_git_diff_format = use_git_diff_format;
+ diff_cmd_baton.no_diff_added = no_diff_added;
+ diff_cmd_baton.no_diff_deleted = no_diff_deleted;
+ diff_cmd_baton.no_copyfrom_on_add = show_copies_as_adds;
+
+ diff_cmd_baton.wc_ctx = ctx->wc_ctx;
+ diff_cmd_baton.ra_session = NULL;
+ diff_cmd_baton.anchor = NULL;
+
+ return do_diff(&diff_callbacks, &diff_cmd_baton, ctx,
+ path_or_url1, path_or_url2, revision1, revision2,
+ &peg_revision,
+ depth, ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists, pool);
+}
+
+svn_error_t *
+svn_client_diff_peg6(const apr_array_header_t *options,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_added,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t ignore_properties,
+ svn_boolean_t properties_only,
+ svn_boolean_t use_git_diff_format,
+ const char *header_encoding,
+ svn_stream_t *outstream,
+ svn_stream_t *errstream,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct diff_cmd_baton diff_cmd_baton = { 0 };
+
+ if (ignore_properties && properties_only)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Cannot ignore properties and show only "
+ "properties at the same time"));
+
+ /* setup callback and baton */
+ diff_cmd_baton.orig_path_1 = path_or_url;
+ diff_cmd_baton.orig_path_2 = path_or_url;
+
+ SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton, options,
+ ctx->config, pool));
+ diff_cmd_baton.pool = pool;
+ diff_cmd_baton.outstream = outstream;
+ diff_cmd_baton.errstream = errstream;
+ diff_cmd_baton.header_encoding = header_encoding;
+ diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
+ diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
+
+ diff_cmd_baton.force_binary = ignore_content_type;
+ diff_cmd_baton.ignore_properties = ignore_properties;
+ diff_cmd_baton.properties_only = properties_only;
+ diff_cmd_baton.relative_to_dir = relative_to_dir;
+ diff_cmd_baton.use_git_diff_format = use_git_diff_format;
+ diff_cmd_baton.no_diff_added = no_diff_added;
+ diff_cmd_baton.no_diff_deleted = no_diff_deleted;
+ diff_cmd_baton.no_copyfrom_on_add = show_copies_as_adds;
+
+ diff_cmd_baton.wc_ctx = ctx->wc_ctx;
+ diff_cmd_baton.ra_session = NULL;
+ diff_cmd_baton.anchor = NULL;
+
+ return do_diff(&diff_callbacks, &diff_cmd_baton, ctx,
+ path_or_url, path_or_url, start_revision, end_revision,
+ peg_revision,
+ depth, ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists, pool);
+}
+
+svn_error_t *
+svn_client_diff_summarize2(const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ /* We will never do a pegged diff from here. */
+ svn_opt_revision_t peg_revision;
+ peg_revision.kind = svn_opt_revision_unspecified;
+
+ return do_diff_summarize(summarize_func, summarize_baton, ctx,
+ path_or_url1, path_or_url2, revision1, revision2,
+ &peg_revision,
+ depth, ignore_ancestry, changelists, pool);
+}
+
+svn_error_t *
+svn_client_diff_summarize_peg2(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return do_diff_summarize(summarize_func, summarize_baton, ctx,
+ path_or_url, path_or_url,
+ start_revision, end_revision, peg_revision,
+ depth, ignore_ancestry, changelists, pool);
+}
+
+svn_client_diff_summarize_t *
+svn_client_diff_summarize_dup(const svn_client_diff_summarize_t *diff,
+ apr_pool_t *pool)
+{
+ svn_client_diff_summarize_t *dup_diff = apr_palloc(pool, sizeof(*dup_diff));
+
+ *dup_diff = *diff;
+
+ if (diff->path)
+ dup_diff->path = apr_pstrdup(pool, diff->path);
+
+ return dup_diff;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/diff_local.c b/contrib/subversion/subversion/libsvn_client/diff_local.c
new file mode 100644
index 0000000..cc7184f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/diff_local.c
@@ -0,0 +1,633 @@
+/*
+ * diff_local.c: comparing local trees with each other
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_strings.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_wc.h"
+#include "svn_diff.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_sorts.h"
+#include "svn_subst.h"
+#include "client.h"
+
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+
+/* Try to get properties for LOCAL_ABSPATH and return them in the property
+ * hash *PROPS. If there are no properties because LOCAL_ABSPATH is not
+ * versioned, return an empty property hash. */
+static svn_error_t *
+get_props(apr_hash_t **props,
+ const char *local_abspath,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ err = svn_wc_prop_list2(props, wc_ctx, local_abspath, result_pool,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
+ err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
+ {
+ svn_error_clear(err);
+ *props = apr_hash_make(result_pool);
+
+ /* ### Apply autoprops, like 'svn add' would? */
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Produce a diff between two arbitrary files at LOCAL_ABSPATH1 and
+ * LOCAL_ABSPATH2, using the diff callbacks from CALLBACKS.
+ * Use PATH as the name passed to diff callbacks.
+ * FILE1_IS_EMPTY and FILE2_IS_EMPTY are used as hints which diff callback
+ * function to use to compare the files (added/deleted/changed).
+ *
+ * If ORIGINAL_PROPS_OVERRIDE is not NULL, use it as original properties
+ * instead of reading properties from LOCAL_ABSPATH1. This is required when
+ * a file replaces a directory, where LOCAL_ABSPATH1 is an empty file that
+ * file content must be diffed against, but properties to diff against come
+ * from the replaced directory. */
+static svn_error_t *
+do_arbitrary_files_diff(const char *local_abspath1,
+ const char *local_abspath2,
+ const char *path,
+ svn_boolean_t file1_is_empty,
+ svn_boolean_t file2_is_empty,
+ apr_hash_t *original_props_override,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *diff_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *original_props;
+ apr_hash_t *modified_props;
+ apr_array_header_t *prop_changes;
+ svn_string_t *original_mime_type = NULL;
+ svn_string_t *modified_mime_type = NULL;
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ /* Try to get properties from either file. It's OK if the files do not
+ * have properties, or if they are unversioned. */
+ if (original_props_override)
+ original_props = original_props_override;
+ else
+ SVN_ERR(get_props(&original_props, local_abspath1, ctx->wc_ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(get_props(&modified_props, local_abspath2, ctx->wc_ctx,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, modified_props, original_props,
+ scratch_pool));
+
+ /* Try to determine the mime-type of each file. */
+ original_mime_type = svn_hash_gets(original_props, SVN_PROP_MIME_TYPE);
+ if (!file1_is_empty && !original_mime_type)
+ {
+ const char *mime_type;
+ SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1,
+ ctx->mimetypes_map, scratch_pool));
+
+ if (mime_type)
+ original_mime_type = svn_string_create(mime_type, scratch_pool);
+ }
+
+ modified_mime_type = svn_hash_gets(modified_props, SVN_PROP_MIME_TYPE);
+ if (!file2_is_empty && !modified_mime_type)
+ {
+ const char *mime_type;
+ SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1,
+ ctx->mimetypes_map, scratch_pool));
+
+ if (mime_type)
+ modified_mime_type = svn_string_create(mime_type, scratch_pool);
+ }
+
+ /* Produce the diff. */
+ if (file1_is_empty && !file2_is_empty)
+ SVN_ERR(callbacks->file_added(NULL, NULL, NULL, path,
+ local_abspath1, local_abspath2,
+ /* ### TODO get real revision info
+ * for versioned files? */
+ SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
+ original_mime_type ?
+ original_mime_type->data : NULL,
+ modified_mime_type ?
+ modified_mime_type->data : NULL,
+ /* ### TODO get copyfrom? */
+ NULL, SVN_INVALID_REVNUM,
+ prop_changes, original_props,
+ diff_baton, scratch_pool));
+ else if (!file1_is_empty && file2_is_empty)
+ SVN_ERR(callbacks->file_deleted(NULL, NULL, path,
+ local_abspath1, local_abspath2,
+ original_mime_type ?
+ original_mime_type->data : NULL,
+ modified_mime_type ?
+ modified_mime_type->data : NULL,
+ original_props,
+ diff_baton, scratch_pool));
+ else
+ {
+ svn_stream_t *file1;
+ svn_stream_t *file2;
+ svn_boolean_t same;
+ svn_string_t *val;
+ /* We have two files, which may or may not be the same.
+
+ ### Our caller assumes that we should ignore symlinks here and
+ handle them as normal paths. Perhaps that should change?
+ */
+ SVN_ERR(svn_stream_open_readonly(&file1, local_abspath1, scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_stream_open_readonly(&file2, local_abspath2, scratch_pool,
+ scratch_pool));
+
+ /* Wrap with normalization, etc. if necessary */
+ if (original_props)
+ {
+ val = svn_hash_gets(original_props, SVN_PROP_EOL_STYLE);
+
+ if (val)
+ {
+ svn_subst_eol_style_t style;
+ const char *eol;
+ svn_subst_eol_style_from_value(&style, &eol, val->data);
+
+ /* ### Ignoring keywords */
+ if (eol)
+ file1 = svn_subst_stream_translated(file1, eol, TRUE,
+ NULL, FALSE,
+ scratch_pool);
+ }
+ }
+
+ if (modified_props)
+ {
+ val = svn_hash_gets(modified_props, SVN_PROP_EOL_STYLE);
+
+ if (val)
+ {
+ svn_subst_eol_style_t style;
+ const char *eol;
+ svn_subst_eol_style_from_value(&style, &eol, val->data);
+
+ /* ### Ignoring keywords */
+ if (eol)
+ file2 = svn_subst_stream_translated(file2, eol, TRUE,
+ NULL, FALSE,
+ scratch_pool);
+ }
+ }
+
+ SVN_ERR(svn_stream_contents_same2(&same, file1, file2, scratch_pool));
+
+ if (! same || prop_changes->nelts > 0)
+ {
+ /* ### We should probably pass the normalized data we created using
+ the subst streams as that is what diff users expect */
+ SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, path,
+ same ? NULL : local_abspath1,
+ same ? NULL : local_abspath2,
+ /* ### TODO get real revision info
+ * for versioned files? */
+ SVN_INVALID_REVNUM /* rev1 */,
+ SVN_INVALID_REVNUM /* rev2 */,
+ original_mime_type ?
+ original_mime_type->data : NULL,
+ modified_mime_type ?
+ modified_mime_type->data : NULL,
+ prop_changes, original_props,
+ diff_baton, scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct arbitrary_diff_walker_baton {
+ /* The root directories of the trees being compared. */
+ const char *root1_abspath;
+ const char *root2_abspath;
+
+ /* TRUE if recursing within an added subtree of root2_abspath that
+ * does not exist in root1_abspath. */
+ svn_boolean_t recursing_within_added_subtree;
+
+ /* TRUE if recursing within an administrative (.i.e. .svn) directory. */
+ svn_boolean_t recursing_within_adm_dir;
+
+ /* The absolute path of the adm dir if RECURSING_WITHIN_ADM_DIR is TRUE.
+ * Else this is NULL.*/
+ const char *adm_dir_abspath;
+
+ /* A path to an empty file used for diffs that add/delete files. */
+ const char *empty_file_abspath;
+
+ const svn_wc_diff_callbacks4_t *callbacks;
+ void *diff_baton;
+ svn_client_ctx_t *ctx;
+ apr_pool_t *pool;
+} arbitrary_diff_walker_baton;
+
+/* Forward declaration needed because this function has a cyclic
+ * dependency with do_arbitrary_dirs_diff(). */
+static svn_error_t *
+arbitrary_diff_walker(void *baton, const char *local_abspath,
+ const apr_finfo_t *finfo,
+ apr_pool_t *scratch_pool);
+
+/* Another forward declaration. */
+static svn_error_t *
+arbitrary_diff_this_dir(struct arbitrary_diff_walker_baton *b,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool);
+
+/* Produce a diff of depth DEPTH between two arbitrary directories at
+ * LOCAL_ABSPATH1 and LOCAL_ABSPATH2, using the provided diff callbacks
+ * to show file changes and, for versioned nodes, property changes.
+ *
+ * If ROOT_ABSPATH1 and ROOT_ABSPATH2 are not NULL, show paths in diffs
+ * relative to these roots, rather than relative to LOCAL_ABSPATH1 and
+ * LOCAL_ABSPATH2. This is needed when crawling a subtree that exists
+ * only within LOCAL_ABSPATH2. */
+static svn_error_t *
+do_arbitrary_dirs_diff(const char *local_abspath1,
+ const char *local_abspath2,
+ const char *root_abspath1,
+ const char *root_abspath2,
+ svn_depth_t depth,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *diff_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *empty_file;
+ svn_node_kind_t kind1;
+
+ struct arbitrary_diff_walker_baton b;
+
+ /* If LOCAL_ABSPATH1 is not a directory, crawl LOCAL_ABSPATH2 instead
+ * and compare it to LOCAL_ABSPATH1, showing only additions.
+ * This case can only happen during recursion from arbitrary_diff_walker(),
+ * because do_arbitrary_nodes_diff() prevents this from happening at
+ * the root of the comparison. */
+ SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
+ b.recursing_within_added_subtree = (kind1 != svn_node_dir);
+
+ b.root1_abspath = root_abspath1 ? root_abspath1 : local_abspath1;
+ b.root2_abspath = root_abspath2 ? root_abspath2 : local_abspath2;
+ b.recursing_within_adm_dir = FALSE;
+ b.adm_dir_abspath = NULL;
+ b.callbacks = callbacks;
+ b.diff_baton = diff_baton;
+ b.ctx = ctx;
+ b.pool = scratch_pool;
+
+ SVN_ERR(svn_io_open_unique_file3(&empty_file, &b.empty_file_abspath,
+ NULL, svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+
+ if (depth <= svn_depth_immediates)
+ SVN_ERR(arbitrary_diff_this_dir(&b, local_abspath1, depth, scratch_pool));
+ else if (depth == svn_depth_infinity)
+ SVN_ERR(svn_io_dir_walk2(b.recursing_within_added_subtree ? local_abspath2
+ : local_abspath1,
+ 0, arbitrary_diff_walker, &b, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Produce a diff of depth DEPTH for the directory at LOCAL_ABSPATH,
+ * using information from the arbitrary_diff_walker_baton B.
+ * LOCAL_ABSPATH is the path being crawled and can be on either side
+ * of the diff depending on baton->recursing_within_added_subtree. */
+static svn_error_t *
+arbitrary_diff_this_dir(struct arbitrary_diff_walker_baton *b,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath1;
+ const char *local_abspath2;
+ svn_node_kind_t kind1;
+ svn_node_kind_t kind2;
+ const char *child_relpath;
+ apr_hash_t *dirents1;
+ apr_hash_t *dirents2;
+ apr_hash_t *merged_dirents;
+ apr_array_header_t *sorted_dirents;
+ int i;
+ apr_pool_t *iterpool;
+
+ if (b->recursing_within_adm_dir)
+ {
+ if (svn_dirent_skip_ancestor(b->adm_dir_abspath, local_abspath))
+ return SVN_NO_ERROR;
+ else
+ {
+ b->recursing_within_adm_dir = FALSE;
+ b->adm_dir_abspath = NULL;
+ }
+ }
+ else if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
+ scratch_pool))
+ {
+ b->recursing_within_adm_dir = TRUE;
+ b->adm_dir_abspath = apr_pstrdup(b->pool, local_abspath);
+ return SVN_NO_ERROR;
+ }
+
+ if (b->recursing_within_added_subtree)
+ child_relpath = svn_dirent_skip_ancestor(b->root2_abspath, local_abspath);
+ else
+ child_relpath = svn_dirent_skip_ancestor(b->root1_abspath, local_abspath);
+ if (!child_relpath)
+ return SVN_NO_ERROR;
+
+ local_abspath1 = svn_dirent_join(b->root1_abspath, child_relpath,
+ scratch_pool);
+ SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
+
+ local_abspath2 = svn_dirent_join(b->root2_abspath, child_relpath,
+ scratch_pool);
+ SVN_ERR(svn_io_check_resolved_path(local_abspath2, &kind2, scratch_pool));
+
+ if (depth > svn_depth_empty)
+ {
+ if (kind1 == svn_node_dir)
+ SVN_ERR(svn_io_get_dirents3(&dirents1, local_abspath1,
+ TRUE, /* only_check_type */
+ scratch_pool, scratch_pool));
+ else
+ dirents1 = apr_hash_make(scratch_pool);
+ }
+
+ if (kind2 == svn_node_dir)
+ {
+ apr_hash_t *original_props;
+ apr_hash_t *modified_props;
+ apr_array_header_t *prop_changes;
+
+ /* Show any property changes for this directory. */
+ SVN_ERR(get_props(&original_props, local_abspath1, b->ctx->wc_ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(get_props(&modified_props, local_abspath2, b->ctx->wc_ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_prop_diffs(&prop_changes, modified_props, original_props,
+ scratch_pool));
+ if (prop_changes->nelts > 0)
+ SVN_ERR(b->callbacks->dir_props_changed(NULL, NULL, child_relpath,
+ FALSE /* was_added */,
+ prop_changes, original_props,
+ b->diff_baton,
+ scratch_pool));
+
+ if (depth > svn_depth_empty)
+ {
+ /* Read directory entries. */
+ SVN_ERR(svn_io_get_dirents3(&dirents2, local_abspath2,
+ TRUE, /* only_check_type */
+ scratch_pool, scratch_pool));
+ }
+ }
+ else if (depth > svn_depth_empty)
+ dirents2 = apr_hash_make(scratch_pool);
+
+ if (depth <= svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* Compare dirents1 to dirents2 and show added/deleted/changed files. */
+ merged_dirents = apr_hash_merge(scratch_pool, dirents1, dirents2,
+ NULL, NULL);
+ sorted_dirents = svn_sort__hash(merged_dirents,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < sorted_dirents->nelts; i++)
+ {
+ svn_sort__item_t elt = APR_ARRAY_IDX(sorted_dirents, i, svn_sort__item_t);
+ const char *name = elt.key;
+ svn_io_dirent2_t *dirent1;
+ svn_io_dirent2_t *dirent2;
+ const char *child1_abspath;
+ const char *child2_abspath;
+
+ svn_pool_clear(iterpool);
+
+ if (b->ctx->cancel_func)
+ SVN_ERR(b->ctx->cancel_func(b->ctx->cancel_baton));
+
+ if (strcmp(name, SVN_WC_ADM_DIR_NAME) == 0)
+ continue;
+
+ dirent1 = svn_hash_gets(dirents1, name);
+ if (!dirent1)
+ {
+ dirent1 = svn_io_dirent2_create(iterpool);
+ dirent1->kind = svn_node_none;
+ }
+ dirent2 = svn_hash_gets(dirents2, name);
+ if (!dirent2)
+ {
+ dirent2 = svn_io_dirent2_create(iterpool);
+ dirent2->kind = svn_node_none;
+ }
+
+ child1_abspath = svn_dirent_join(local_abspath1, name, iterpool);
+ child2_abspath = svn_dirent_join(local_abspath2, name, iterpool);
+
+ if (dirent1->special)
+ SVN_ERR(svn_io_check_resolved_path(child1_abspath, &dirent1->kind,
+ iterpool));
+ if (dirent2->special)
+ SVN_ERR(svn_io_check_resolved_path(child1_abspath, &dirent2->kind,
+ iterpool));
+
+ if (dirent1->kind == svn_node_dir &&
+ dirent2->kind == svn_node_dir)
+ {
+ if (depth == svn_depth_immediates)
+ {
+ /* Not using the walker, so show property diffs on these dirs. */
+ SVN_ERR(do_arbitrary_dirs_diff(child1_abspath, child2_abspath,
+ b->root1_abspath, b->root2_abspath,
+ svn_depth_empty,
+ b->callbacks, b->diff_baton,
+ b->ctx, iterpool));
+ }
+ else
+ {
+ /* Either the walker will visit these directories (with
+ * depth=infinity) and they will be processed as 'this dir'
+ * later, or we're showing file children only (depth=files). */
+ continue;
+ }
+
+ }
+
+ /* Files that exist only in dirents1. */
+ if (dirent1->kind == svn_node_file &&
+ (dirent2->kind == svn_node_dir || dirent2->kind == svn_node_none))
+ SVN_ERR(do_arbitrary_files_diff(child1_abspath, b->empty_file_abspath,
+ svn_relpath_join(child_relpath, name,
+ iterpool),
+ FALSE, TRUE, NULL,
+ b->callbacks, b->diff_baton,
+ b->ctx, iterpool));
+
+ /* Files that exist only in dirents2. */
+ if (dirent2->kind == svn_node_file &&
+ (dirent1->kind == svn_node_dir || dirent1->kind == svn_node_none))
+ {
+ apr_hash_t *original_props;
+
+ SVN_ERR(get_props(&original_props, child1_abspath, b->ctx->wc_ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(do_arbitrary_files_diff(b->empty_file_abspath, child2_abspath,
+ svn_relpath_join(child_relpath, name,
+ iterpool),
+ TRUE, FALSE, original_props,
+ b->callbacks, b->diff_baton,
+ b->ctx, iterpool));
+ }
+
+ /* Files that exist in dirents1 and dirents2. */
+ if (dirent1->kind == svn_node_file && dirent2->kind == svn_node_file)
+ SVN_ERR(do_arbitrary_files_diff(child1_abspath, child2_abspath,
+ svn_relpath_join(child_relpath, name,
+ iterpool),
+ FALSE, FALSE, NULL,
+ b->callbacks, b->diff_baton,
+ b->ctx, scratch_pool));
+
+ /* Directories that only exist in dirents2. These aren't crawled
+ * by this walker so we have to crawl them separately. */
+ if (depth > svn_depth_files &&
+ dirent2->kind == svn_node_dir &&
+ (dirent1->kind == svn_node_file || dirent1->kind == svn_node_none))
+ SVN_ERR(do_arbitrary_dirs_diff(child1_abspath, child2_abspath,
+ b->root1_abspath, b->root2_abspath,
+ depth <= svn_depth_immediates
+ ? svn_depth_empty
+ : svn_depth_infinity ,
+ b->callbacks, b->diff_baton,
+ b->ctx, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* An implementation of svn_io_walk_func_t.
+ * Note: LOCAL_ABSPATH is the path being crawled and can be on either side
+ * of the diff depending on baton->recursing_within_added_subtree. */
+static svn_error_t *
+arbitrary_diff_walker(void *baton, const char *local_abspath,
+ const apr_finfo_t *finfo,
+ apr_pool_t *scratch_pool)
+{
+ struct arbitrary_diff_walker_baton *b = baton;
+
+ if (b->ctx->cancel_func)
+ SVN_ERR(b->ctx->cancel_func(b->ctx->cancel_baton));
+
+ if (finfo->filetype != APR_DIR)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(arbitrary_diff_this_dir(b, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__arbitrary_nodes_diff(const char *local_abspath1,
+ const char *local_abspath2,
+ svn_depth_t depth,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *diff_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind1;
+ svn_node_kind_t kind2;
+
+ SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
+ SVN_ERR(svn_io_check_resolved_path(local_abspath2, &kind2, scratch_pool));
+
+ if (kind1 != kind2)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' is not the same node kind as '%s'"),
+ local_abspath1, local_abspath2);
+
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_infinity;
+
+ if (kind1 == svn_node_file)
+ SVN_ERR(do_arbitrary_files_diff(local_abspath1, local_abspath2,
+ svn_dirent_basename(local_abspath1,
+ scratch_pool),
+ FALSE, FALSE, NULL,
+ callbacks, diff_baton,
+ ctx, scratch_pool));
+ else if (kind1 == svn_node_dir)
+ SVN_ERR(do_arbitrary_dirs_diff(local_abspath1, local_abspath2,
+ NULL, NULL, depth,
+ callbacks, diff_baton,
+ ctx, scratch_pool));
+ else
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' is not a file or directory"),
+ kind1 == svn_node_none ?
+ local_abspath1 : local_abspath2);
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/diff_summarize.c b/contrib/subversion/subversion/libsvn_client/diff_summarize.c
new file mode 100644
index 0000000..df0911b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/diff_summarize.c
@@ -0,0 +1,317 @@
+/*
+ * repos_diff_summarize.c -- The diff callbacks for summarizing
+ * the differences of two repository versions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "client.h"
+
+
+/* Diff callbacks baton. */
+struct summarize_baton_t {
+ /* The target path of the diff, relative to the anchor; "" if target == anchor. */
+ const char *target;
+
+ /* The summarize callback passed down from the API */
+ svn_client_diff_summarize_func_t summarize_func;
+
+ /* Is the diff handling reversed? (add<->delete) */
+ svn_boolean_t reversed;
+
+ /* The summarize callback baton */
+ void *summarize_func_baton;
+
+ /* Which paths have a prop change. Key is a (const char *) path; the value
+ * is any non-null pointer to indicate that this path has a prop change. */
+ apr_hash_t *prop_changes;
+};
+
+
+/* Call B->summarize_func with B->summarize_func_baton, passing it a
+ * summary object composed from PATH (but made to be relative to the target
+ * of the diff), SUMMARIZE_KIND, PROP_CHANGED (or FALSE if the action is an
+ * add or delete) and NODE_KIND. */
+static svn_error_t *
+send_summary(struct summarize_baton_t *b,
+ const char *path,
+ svn_client_diff_summarize_kind_t summarize_kind,
+ svn_boolean_t prop_changed,
+ svn_node_kind_t node_kind,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_diff_summarize_t *sum = apr_pcalloc(scratch_pool, sizeof(*sum));
+
+ SVN_ERR_ASSERT(summarize_kind != svn_client_diff_summarize_kind_normal
+ || prop_changed);
+
+ if (b->reversed)
+ {
+ switch(summarize_kind)
+ {
+ case svn_client_diff_summarize_kind_added:
+ summarize_kind = svn_client_diff_summarize_kind_deleted;
+ break;
+ case svn_client_diff_summarize_kind_deleted:
+ summarize_kind = svn_client_diff_summarize_kind_added;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* PATH is relative to the anchor of the diff, but SUM->path needs to be
+ relative to the target of the diff. */
+ sum->path = svn_relpath_skip_ancestor(b->target, path);
+ sum->summarize_kind = summarize_kind;
+ if (summarize_kind == svn_client_diff_summarize_kind_modified
+ || summarize_kind == svn_client_diff_summarize_kind_normal)
+ sum->prop_changed = prop_changed;
+ sum->node_kind = node_kind;
+
+ SVN_ERR(b->summarize_func(sum, b->summarize_func_baton, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Are there any changes to relevant (normal) props in PROPCHANGES? */
+static svn_boolean_t
+props_changed(const apr_array_header_t *propchanges,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *props;
+
+ svn_error_clear(svn_categorize_props(propchanges, NULL, NULL, &props,
+ scratch_pool));
+ return (props->nelts != 0);
+}
+
+
+static svn_error_t *
+cb_dir_deleted(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct summarize_baton_t *b = diff_baton;
+
+ SVN_ERR(send_summary(b, path, svn_client_diff_summarize_kind_deleted,
+ FALSE, svn_node_dir, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cb_file_deleted(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ const char *mimetype1,
+ const char *mimetype2,
+ apr_hash_t *originalprops,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct summarize_baton_t *b = diff_baton;
+
+ SVN_ERR(send_summary(b, path, svn_client_diff_summarize_kind_deleted,
+ FALSE, svn_node_file, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cb_dir_added(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *path,
+ svn_revnum_t rev,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cb_dir_opened(svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cb_dir_closed(svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ svn_boolean_t dir_was_added,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct summarize_baton_t *b = diff_baton;
+ svn_boolean_t prop_change;
+
+ if (! svn_relpath_skip_ancestor(b->target, path))
+ return SVN_NO_ERROR;
+
+ prop_change = svn_hash_gets(b->prop_changes, path) != NULL;
+ if (dir_was_added || prop_change)
+ SVN_ERR(send_summary(b, path,
+ dir_was_added ? svn_client_diff_summarize_kind_added
+ : svn_client_diff_summarize_kind_normal,
+ prop_change, svn_node_dir, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cb_file_added(svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct summarize_baton_t *b = diff_baton;
+
+ SVN_ERR(send_summary(b, path, svn_client_diff_summarize_kind_added,
+ props_changed(propchanges, scratch_pool),
+ svn_node_file, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cb_file_opened(svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cb_file_changed(svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct summarize_baton_t *b = diff_baton;
+ svn_boolean_t text_change = (tmpfile2 != NULL);
+ svn_boolean_t prop_change = props_changed(propchanges, scratch_pool);
+
+ if (text_change || prop_change)
+ SVN_ERR(send_summary(b, path,
+ text_change ? svn_client_diff_summarize_kind_modified
+ : svn_client_diff_summarize_kind_normal,
+ prop_change, svn_node_file, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cb_dir_props_changed(svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ svn_boolean_t dir_was_added,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct summarize_baton_t *b = diff_baton;
+
+ if (props_changed(propchanges, scratch_pool))
+ svn_hash_sets(b->prop_changes, path, path);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__get_diff_summarize_callbacks(
+ svn_wc_diff_callbacks4_t **callbacks,
+ void **callback_baton,
+ const char *target,
+ svn_boolean_t reversed,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_diff_callbacks4_t *cb = apr_palloc(pool, sizeof(*cb));
+ struct summarize_baton_t *b = apr_palloc(pool, sizeof(*b));
+
+ b->target = target;
+ b->summarize_func = summarize_func;
+ b->summarize_func_baton = summarize_baton;
+ b->prop_changes = apr_hash_make(pool);
+ b->reversed = reversed;
+
+ cb->file_opened = cb_file_opened;
+ cb->file_changed = cb_file_changed;
+ cb->file_added = cb_file_added;
+ cb->file_deleted = cb_file_deleted;
+ cb->dir_deleted = cb_dir_deleted;
+ cb->dir_opened = cb_dir_opened;
+ cb->dir_added = cb_dir_added;
+ cb->dir_props_changed = cb_dir_props_changed;
+ cb->dir_closed = cb_dir_closed;
+
+ *callbacks = cb;
+ *callback_baton = b;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/export.c b/contrib/subversion/subversion/libsvn_client/export.c
new file mode 100644
index 0000000..d6022ed
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/export.c
@@ -0,0 +1,1589 @@
+/*
+ * export.c: export a tree.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_file_io.h>
+#include <apr_md5.h>
+#include "svn_types.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_subst.h"
+#include "svn_time.h"
+#include "svn_props.h"
+#include "client.h"
+
+#include "svn_private_config.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_delta_private.h"
+#include "private/svn_wc_private.h"
+
+#ifndef ENABLE_EV2_IMPL
+#define ENABLE_EV2_IMPL 0
+#endif
+
+
+/*** Code. ***/
+
+/* Add EXTERNALS_PROP_VAL for the export destination path PATH to
+ TRAVERSAL_INFO. */
+static svn_error_t *
+add_externals(apr_hash_t *externals,
+ const char *path,
+ const svn_string_t *externals_prop_val)
+{
+ apr_pool_t *pool = apr_hash_pool_get(externals);
+ const char *local_abspath;
+
+ if (! externals_prop_val)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ svn_hash_sets(externals, local_abspath,
+ apr_pstrmemdup(pool, externals_prop_val->data,
+ externals_prop_val->len));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper function that gets the eol style and optionally overrides the
+ EOL marker for files marked as native with the EOL marker matching
+ the string specified in requested_value which is of the same format
+ as the svn:eol-style property values. */
+static svn_error_t *
+get_eol_style(svn_subst_eol_style_t *style,
+ const char **eol,
+ const char *value,
+ const char *requested_value)
+{
+ svn_subst_eol_style_from_value(style, eol, value);
+ if (requested_value && *style == svn_subst_eol_style_native)
+ {
+ svn_subst_eol_style_t requested_style;
+ const char *requested_eol;
+
+ svn_subst_eol_style_from_value(&requested_style, &requested_eol,
+ requested_value);
+
+ if (requested_style == svn_subst_eol_style_fixed)
+ *eol = requested_eol;
+ else
+ return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
+ _("'%s' is not a valid EOL value"),
+ requested_value);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* If *APPENDABLE_DIRENT_P represents an existing directory, then append
+ * to it the basename of BASENAME_OF and return the result in
+ * *APPENDABLE_DIRENT_P. The kind of BASENAME_OF is either dirent or uri,
+ * as given by IS_URI.
+ */
+static svn_error_t *
+append_basename_if_dir(const char **appendable_dirent_p,
+ const char *basename_of,
+ svn_boolean_t is_uri,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t local_kind;
+ SVN_ERR(svn_io_check_resolved_path(*appendable_dirent_p, &local_kind, pool));
+ if (local_kind == svn_node_dir)
+ {
+ const char *base_name;
+
+ if (is_uri)
+ base_name = svn_uri_basename(basename_of, pool);
+ else
+ base_name = svn_dirent_basename(basename_of, NULL);
+
+ *appendable_dirent_p = svn_dirent_join(*appendable_dirent_p,
+ base_name, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Make an unversioned copy of the versioned file at FROM_ABSPATH. Copy it
+ * to the destination path TO_ABSPATH.
+ *
+ * If REVISION is svn_opt_revision_working, copy the working version,
+ * otherwise copy the base version.
+ *
+ * Expand the file's keywords according to the source file's 'svn:keywords'
+ * property, if present. If copying a locally modified working version,
+ * append 'M' to the revision number and use '(local)' for the author.
+ *
+ * Translate the file's line endings according to the source file's
+ * 'svn:eol-style' property, if present. If NATIVE_EOL is not NULL, use it
+ * in place of the native EOL style. Throw an error if the source file has
+ * inconsistent line endings and EOL translation is attempted.
+ *
+ * Set the destination file's modification time to the source file's
+ * modification time if copying the working version and the working version
+ * is locally modified; otherwise set it to the versioned file's last
+ * changed time.
+ *
+ * Set the destination file's 'executable' flag according to the source
+ * file's 'svn:executable' property.
+ */
+
+/* baton for export_node */
+struct export_info_baton
+{
+ const char *to_path;
+ const svn_opt_revision_t *revision;
+ svn_boolean_t ignore_keywords;
+ svn_boolean_t overwrite;
+ svn_wc_context_t *wc_ctx;
+ const char *native_eol;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+ const char *origin_abspath;
+ svn_boolean_t exported;
+};
+
+/* Export a file or directory. Implements svn_wc_status_func4_t */
+static svn_error_t *
+export_node(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct export_info_baton *eib = baton;
+ svn_wc_context_t *wc_ctx = eib->wc_ctx;
+ apr_hash_t *kw = NULL;
+ svn_subst_eol_style_t style;
+ apr_hash_t *props;
+ svn_string_t *eol_style, *keywords, *executable, *special;
+ const char *eol = NULL;
+ svn_boolean_t local_mod = FALSE;
+ apr_time_t tm;
+ svn_stream_t *source;
+ svn_stream_t *dst_stream;
+ const char *dst_tmp;
+ svn_error_t *err;
+
+ const char *to_abspath = svn_dirent_join(
+ eib->to_path,
+ svn_dirent_skip_ancestor(eib->origin_abspath,
+ local_abspath),
+ scratch_pool);
+
+ eib->exported = TRUE;
+
+ /* Don't export 'deleted' files and directories unless it's a
+ revision other than WORKING. These files and directories
+ don't really exist in WORKING. */
+ if (eib->revision->kind == svn_opt_revision_working
+ && status->node_status == svn_wc_status_deleted)
+ return SVN_NO_ERROR;
+
+ if (status->kind == svn_node_dir)
+ {
+ apr_fileperms_t perm = APR_OS_DEFAULT;
+
+ /* Try to make the new directory. If this fails because the
+ directory already exists, check our FORCE flag to see if we
+ care. */
+
+ /* Keep the source directory's permissions if applicable.
+ Skip retrieving the umask on windows. Apr does not implement setting
+ filesystem privileges on Windows.
+ Retrieving the file permissions with APR_FINFO_PROT | APR_FINFO_OWNER
+ is documented to be 'incredibly expensive' */
+#ifndef WIN32
+ if (eib->revision->kind == svn_opt_revision_working)
+ {
+ apr_finfo_t finfo;
+ SVN_ERR(svn_io_stat(&finfo, local_abspath, APR_FINFO_PROT,
+ scratch_pool));
+ perm = finfo.protection;
+ }
+#endif
+ err = svn_io_dir_make(to_abspath, perm, scratch_pool);
+ if (err)
+ {
+ if (! APR_STATUS_IS_EEXIST(err->apr_err))
+ return svn_error_trace(err);
+ if (! eib->overwrite)
+ SVN_ERR_W(err, _("Destination directory exists, and will not be "
+ "overwritten unless forced"));
+ else
+ svn_error_clear(err);
+ }
+
+ if (eib->notify_func
+ && (strcmp(eib->origin_abspath, local_abspath) != 0))
+ {
+ svn_wc_notify_t *notify =
+ svn_wc_create_notify(to_abspath,
+ svn_wc_notify_update_add, scratch_pool);
+
+ notify->kind = svn_node_dir;
+ (eib->notify_func)(eib->notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+ }
+ else if (status->kind != svn_node_file)
+ {
+ if (strcmp(eib->origin_abspath, local_abspath) != 0)
+ return SVN_NO_ERROR;
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ if (status->file_external)
+ return SVN_NO_ERROR;
+
+ /* Produce overwrite errors for the export root */
+ if (strcmp(local_abspath, eib->origin_abspath) == 0)
+ {
+ svn_node_kind_t to_kind;
+
+ SVN_ERR(svn_io_check_path(to_abspath, &to_kind, scratch_pool));
+
+ if ((to_kind == svn_node_file || to_kind == svn_node_unknown)
+ && !eib->overwrite)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Destination file '%s' exists, and "
+ "will not be overwritten unless forced"),
+ svn_dirent_local_style(to_abspath,
+ scratch_pool));
+ else if (to_kind == svn_node_dir)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Destination '%s' exists. Cannot "
+ "overwrite directory with non-directory"),
+ svn_dirent_local_style(to_abspath,
+ scratch_pool));
+ }
+
+ if (eib->revision->kind != svn_opt_revision_working)
+ {
+ /* Only export 'added' files when the revision is WORKING. This is not
+ WORKING, so skip the 'added' files, since they didn't exist
+ in the BASE revision and don't have an associated text-base.
+
+ 'replaced' files are technically the same as 'added' files.
+ ### TODO: Handle replaced nodes properly.
+ ### svn_opt_revision_base refers to the "new"
+ ### base of the node. That means, if a node is locally
+ ### replaced, export skips this node, as if it was locally
+ ### added, because svn_opt_revision_base refers to the base
+ ### of the added node, not to the node that was deleted.
+ ### In contrast, when the node is copied-here or moved-here,
+ ### the copy/move source's content will be exported.
+ ### It is currently not possible to export the revert-base
+ ### when a node is locally replaced. We need a new
+ ### svn_opt_revision_ enum value for proper distinction
+ ### between revert-base and commit-base.
+
+ Copied-/moved-here nodes have a base, so export both added and
+ replaced files when they involve a copy-/move-here.
+
+ We get all this for free from evaluating SOURCE == NULL:
+ */
+ SVN_ERR(svn_wc_get_pristine_contents2(&source, wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ if (source == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ /* ### hmm. this isn't always a specialfile. this will simply open
+ ### the file readonly if it is a regular file. */
+ SVN_ERR(svn_subst_read_specialfile(&source, local_abspath, scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool,
+ scratch_pool));
+ if (status->node_status != svn_wc_status_normal)
+ local_mod = TRUE;
+ }
+
+ /* We can early-exit if we're creating a special file. */
+ special = svn_hash_gets(props, SVN_PROP_SPECIAL);
+ if (special != NULL)
+ {
+ /* Create the destination as a special file, and copy the source
+ details into the destination stream. */
+ /* ### And forget the notification */
+ SVN_ERR(svn_subst_create_specialfile(&dst_stream, to_abspath,
+ scratch_pool, scratch_pool));
+ return svn_error_trace(
+ svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool));
+ }
+
+
+ eol_style = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
+ keywords = svn_hash_gets(props, SVN_PROP_KEYWORDS);
+ executable = svn_hash_gets(props, SVN_PROP_EXECUTABLE);
+
+ if (eol_style)
+ SVN_ERR(get_eol_style(&style, &eol, eol_style->data, eib->native_eol));
+
+ if (local_mod)
+ {
+ /* Use the modified time from the working copy of
+ the file */
+ SVN_ERR(svn_io_file_affected_time(&tm, local_abspath, scratch_pool));
+ }
+ else
+ {
+ tm = status->changed_date;
+ }
+
+ if (keywords)
+ {
+ svn_revnum_t changed_rev = status->changed_rev;
+ const char *suffix;
+ const char *url = svn_path_url_add_component2(status->repos_root_url,
+ status->repos_relpath,
+ scratch_pool);
+ const char *author = status->changed_author;
+ if (local_mod)
+ {
+ /* For locally modified files, we'll append an 'M'
+ to the revision number, and set the author to
+ "(local)" since we can't always determine the
+ current user's username */
+ suffix = "M";
+ author = _("(local)");
+ }
+ else
+ {
+ suffix = "";
+ }
+
+ SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data,
+ apr_psprintf(scratch_pool, "%ld%s",
+ changed_rev, suffix),
+ url, status->repos_root_url, tm,
+ author, scratch_pool));
+ }
+
+ /* For atomicity, we translate to a tmp file and then rename the tmp file
+ over the real destination. */
+ SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
+ svn_dirent_dirname(to_abspath, scratch_pool),
+ svn_io_file_del_none, scratch_pool,
+ scratch_pool));
+
+ /* If some translation is needed, then wrap the output stream (this is
+ more efficient than wrapping the input). */
+ if (eol || (kw && (apr_hash_count(kw) > 0)))
+ dst_stream = svn_subst_stream_translated(dst_stream,
+ eol,
+ FALSE /* repair */,
+ kw,
+ ! eib->ignore_keywords /* expand */,
+ scratch_pool);
+
+ /* ###: use cancel func/baton in place of NULL/NULL below. */
+ err = svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool);
+
+ if (!err && executable)
+ err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, scratch_pool);
+
+ if (!err)
+ err = svn_io_set_file_affected_time(tm, dst_tmp, scratch_pool);
+
+ if (err)
+ return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE,
+ scratch_pool));
+
+ /* Now that dst_tmp contains the translated data, do the atomic rename. */
+ SVN_ERR(svn_io_file_rename(dst_tmp, to_abspath, scratch_pool));
+
+ if (eib->notify_func)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(to_abspath,
+ svn_wc_notify_update_add, scratch_pool);
+ notify->kind = svn_node_file;
+ (eib->notify_func)(eib->notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Abstraction of open_root.
+ *
+ * Create PATH if it does not exist and is not obstructed, and invoke
+ * NOTIFY_FUNC with NOTIFY_BATON on PATH.
+ *
+ * If PATH exists but is a file, then error with SVN_ERR_WC_NOT_WORKING_COPY.
+ *
+ * If PATH is a already a directory, then error with
+ * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless FORCE, in which case just
+ * export into PATH with no error.
+ */
+static svn_error_t *
+open_root_internal(const char *path,
+ svn_boolean_t force,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_io_check_path(path, &kind, pool));
+ if (kind == svn_node_none)
+ SVN_ERR(svn_io_make_dir_recursively(path, pool));
+ else if (kind == svn_node_file)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' exists and is not a directory"),
+ svn_dirent_local_style(path, pool));
+ else if ((kind != svn_node_dir) || (! force))
+ return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("'%s' already exists"),
+ svn_dirent_local_style(path, pool));
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(path,
+ svn_wc_notify_update_add,
+ pool);
+ notify->kind = svn_node_dir;
+ (*notify_func)(notify_baton, notify, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ---------------------------------------------------------------------- */
+
+
+/*** A dedicated 'export' editor, which does no .svn/ accounting. ***/
+
+
+struct edit_baton
+{
+ const char *repos_root_url;
+ const char *root_path;
+ const char *root_url;
+ svn_boolean_t force;
+ svn_revnum_t *target_revision;
+ apr_hash_t *externals;
+ const char *native_eol;
+ svn_boolean_t ignore_keywords;
+
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+};
+
+
+struct dir_baton
+{
+ struct edit_baton *edit_baton;
+ const char *path;
+};
+
+
+struct file_baton
+{
+ struct edit_baton *edit_baton;
+
+ const char *path;
+ const char *tmppath;
+
+ /* We need to keep this around so we can explicitly close it in close_file,
+ thus flushing its output to disk so we can copy and translate it. */
+ svn_stream_t *tmp_stream;
+
+ /* The MD5 digest of the file's fulltext. This is all zeros until
+ the last textdelta window handler call returns. */
+ unsigned char text_digest[APR_MD5_DIGESTSIZE];
+
+ /* The three svn: properties we might actually care about. */
+ const svn_string_t *eol_style_val;
+ const svn_string_t *keywords_val;
+ const svn_string_t *executable_val;
+ svn_boolean_t special;
+
+ /* Any keyword vals to be substituted */
+ const char *revision;
+ const char *url;
+ const char *repos_root_url;
+ const char *author;
+ apr_time_t date;
+
+ /* Pool associated with this baton. */
+ apr_pool_t *pool;
+};
+
+
+struct handler_baton
+{
+ svn_txdelta_window_handler_t apply_handler;
+ void *apply_baton;
+ apr_pool_t *pool;
+ const char *tmppath;
+};
+
+
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ /* Stashing a target_revision in the baton */
+ *(eb->target_revision) = target_revision;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Just ensure that the main export directory exists. */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
+
+ SVN_ERR(open_root_internal(eb->root_path, eb->force,
+ eb->notify_func, eb->notify_baton, pool));
+
+ /* Build our dir baton. */
+ db->path = eb->root_path;
+ db->edit_baton = eb;
+ *root_baton = db;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Ensure the directory exists, and send feedback. */
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
+ struct edit_baton *eb = pb->edit_baton;
+ const char *full_path = svn_dirent_join(eb->root_path, path, pool);
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_io_check_path(full_path, &kind, pool));
+ if (kind == svn_node_none)
+ SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, pool));
+ else if (kind == svn_node_file)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' exists and is not a directory"),
+ svn_dirent_local_style(full_path, pool));
+ else if (! (kind == svn_node_dir && eb->force))
+ return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("'%s' already exists"),
+ svn_dirent_local_style(full_path, pool));
+
+ if (eb->notify_func)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
+ svn_wc_notify_update_add,
+ pool);
+ notify->kind = svn_node_dir;
+ (*eb->notify_func)(eb->notify_baton, notify, pool);
+ }
+
+ /* Build our dir baton. */
+ db->path = full_path;
+ db->edit_baton = eb;
+ *baton = db;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Build a file baton. */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb = apr_pcalloc(pool, sizeof(*fb));
+ const char *full_path = svn_dirent_join(eb->root_path, path, pool);
+
+ /* PATH is not canonicalized, i.e. it may still contain spaces etc.
+ * but EB->root_url is. */
+ const char *full_url = svn_path_url_add_component2(eb->root_url,
+ path,
+ pool);
+
+ fb->edit_baton = eb;
+ fb->path = full_path;
+ fb->url = full_url;
+ fb->repos_root_url = eb->repos_root_url;
+ fb->pool = pool;
+
+ *baton = fb;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct handler_baton *hb = baton;
+ svn_error_t *err;
+
+ err = hb->apply_handler(window, hb->apply_baton);
+ if (err)
+ {
+ /* We failed to apply the patch; clean up the temporary file. */
+ err = svn_error_compose_create(
+ err,
+ svn_io_remove_file2(hb->tmppath, TRUE, hb->pool));
+ }
+
+ return svn_error_trace(err);
+}
+
+
+
+/* Write incoming data into the tmpfile stream */
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+ struct handler_baton *hb = apr_palloc(pool, sizeof(*hb));
+
+ /* Create a temporary file in the same directory as the file. We're going
+ to rename the thing into place when we're done. */
+ SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
+ svn_dirent_dirname(fb->path, pool),
+ svn_io_file_del_none, fb->pool, fb->pool));
+
+ hb->pool = pool;
+ hb->tmppath = fb->tmppath;
+
+ /* svn_txdelta_apply() closes the stream, but we want to close it in the
+ close_file() function, so disown it here. */
+ /* ### contrast to when we call svn_ra_get_file() which does NOT close the
+ ### tmp_stream. we *should* be much more consistent! */
+ svn_txdelta_apply(svn_stream_empty(pool),
+ svn_stream_disown(fb->tmp_stream, pool),
+ fb->text_digest, NULL, pool,
+ &hb->apply_handler, &hb->apply_baton);
+
+ *handler_baton = hb;
+ *handler = window_handler;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+
+ if (! value)
+ return SVN_NO_ERROR;
+
+ /* Store only the magic three properties. */
+ if (strcmp(name, SVN_PROP_EOL_STYLE) == 0)
+ fb->eol_style_val = svn_string_dup(value, fb->pool);
+
+ else if (! fb->edit_baton->ignore_keywords &&
+ strcmp(name, SVN_PROP_KEYWORDS) == 0)
+ fb->keywords_val = svn_string_dup(value, fb->pool);
+
+ else if (strcmp(name, SVN_PROP_EXECUTABLE) == 0)
+ fb->executable_val = svn_string_dup(value, fb->pool);
+
+ /* Try to fill out the baton's keywords-structure too. */
+ else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
+ fb->revision = apr_pstrdup(fb->pool, value->data);
+
+ else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
+ SVN_ERR(svn_time_from_cstring(&fb->date, value->data, fb->pool));
+
+ else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
+ fb->author = apr_pstrdup(fb->pool, value->data);
+
+ else if (strcmp(name, SVN_PROP_SPECIAL) == 0)
+ fb->special = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ if (value && (strcmp(name, SVN_PROP_EXTERNALS) == 0))
+ SVN_ERR(add_externals(eb->externals, db->path, value));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Move the tmpfile to file, and send feedback. */
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_digest,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+ svn_checksum_t *text_checksum;
+ svn_checksum_t *actual_checksum;
+
+ /* Was a txdelta even sent? */
+ if (! fb->tmppath)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_stream_close(fb->tmp_stream));
+
+ SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, text_digest,
+ pool));
+ actual_checksum = svn_checksum__from_digest_md5(fb->text_digest, pool);
+
+ /* Note that text_digest can be NULL when talking to certain repositories.
+ In that case text_checksum will be NULL and the following match code
+ will note that the checksums match */
+ if (!svn_checksum_match(text_checksum, actual_checksum))
+ return svn_checksum_mismatch_err(text_checksum, actual_checksum, pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(fb->path, pool));
+
+ if ((! fb->eol_style_val) && (! fb->keywords_val) && (! fb->special))
+ {
+ SVN_ERR(svn_io_file_rename(fb->tmppath, fb->path, pool));
+ }
+ else
+ {
+ svn_subst_eol_style_t style;
+ const char *eol = NULL;
+ svn_boolean_t repair = FALSE;
+ apr_hash_t *final_kw = NULL;
+
+ if (fb->eol_style_val)
+ {
+ SVN_ERR(get_eol_style(&style, &eol, fb->eol_style_val->data,
+ eb->native_eol));
+ repair = TRUE;
+ }
+
+ if (fb->keywords_val)
+ SVN_ERR(svn_subst_build_keywords3(&final_kw, fb->keywords_val->data,
+ fb->revision, fb->url,
+ fb->repos_root_url, fb->date,
+ fb->author, pool));
+
+ SVN_ERR(svn_subst_copy_and_translate4(fb->tmppath, fb->path,
+ eol, repair, final_kw,
+ TRUE, /* expand */
+ fb->special,
+ eb->cancel_func, eb->cancel_baton,
+ pool));
+
+ SVN_ERR(svn_io_remove_file2(fb->tmppath, FALSE, pool));
+ }
+
+ if (fb->executable_val)
+ SVN_ERR(svn_io_set_file_executable(fb->path, TRUE, FALSE, pool));
+
+ if (fb->date && (! fb->special))
+ SVN_ERR(svn_io_set_file_affected_time(fb->date, fb->path, pool));
+
+ if (fb->edit_baton->notify_func)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(fb->path,
+ svn_wc_notify_update_add,
+ pool);
+ notify->kind = svn_node_file;
+ (*fb->edit_baton->notify_func)(fb->edit_baton->notify_baton, notify,
+ pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Always use empty props, since the node won't have pre-existing props
+ (This is an export, remember?) */
+ *props = apr_hash_make(result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* An export always gets text against the empty stream (i.e, full texts). */
+ *filename = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_editor_ev1(const svn_delta_editor_t **export_editor,
+ void **edit_baton,
+ struct edit_baton *eb,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_delta_editor_t *editor = svn_delta_default_editor(result_pool);
+
+ editor->set_target_revision = set_target_revision;
+ editor->open_root = open_root;
+ editor->add_directory = add_directory;
+ editor->add_file = add_file;
+ editor->apply_textdelta = apply_textdelta;
+ editor->close_file = close_file;
+ editor->change_file_prop = change_file_prop;
+ editor->change_dir_prop = change_dir_prop;
+
+ SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func,
+ ctx->cancel_baton,
+ editor,
+ eb,
+ export_editor,
+ edit_baton,
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** The Ev2 Implementation ***/
+
+static svn_error_t *
+add_file_ev2(void *baton,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ const char *full_path = svn_dirent_join(eb->root_path, relpath,
+ scratch_pool);
+ /* RELPATH is not canonicalized, i.e. it may still contain spaces etc.
+ * but EB->root_url is. */
+ const char *full_url = svn_path_url_add_component2(eb->root_url,
+ relpath,
+ scratch_pool);
+ const svn_string_t *val;
+ /* The four svn: properties we might actually care about. */
+ const svn_string_t *eol_style_val = NULL;
+ const svn_string_t *keywords_val = NULL;
+ const svn_string_t *executable_val = NULL;
+ svn_boolean_t special = FALSE;
+ /* Any keyword vals to be substituted */
+ const char *revision = NULL;
+ const char *author = NULL;
+ apr_time_t date = 0;
+
+ /* Look at any properties for additional information. */
+ if ( (val = svn_hash_gets(props, SVN_PROP_EOL_STYLE)) )
+ eol_style_val = val;
+
+ if ( !eb->ignore_keywords && (val = svn_hash_gets(props, SVN_PROP_KEYWORDS)) )
+ keywords_val = val;
+
+ if ( (val = svn_hash_gets(props, SVN_PROP_EXECUTABLE)) )
+ executable_val = val;
+
+ /* Try to fill out the baton's keywords-structure too. */
+ if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_REV)) )
+ revision = val->data;
+
+ if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_DATE)) )
+ SVN_ERR(svn_time_from_cstring(&date, val->data, scratch_pool));
+
+ if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_LAST_AUTHOR)) )
+ author = val->data;
+
+ if ( (val = svn_hash_gets(props, SVN_PROP_SPECIAL)) )
+ special = TRUE;
+
+ if (special)
+ {
+ svn_stream_t *tmp_stream;
+
+ SVN_ERR(svn_subst_create_specialfile(&tmp_stream, full_path,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func,
+ eb->cancel_baton, scratch_pool));
+ }
+ else
+ {
+ svn_stream_t *tmp_stream;
+ const char *tmppath;
+
+ /* Create a temporary file in the same directory as the file. We're going
+ to rename the thing into place when we're done. */
+ SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmppath,
+ svn_dirent_dirname(full_path,
+ scratch_pool),
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+
+ /* Possibly wrap the stream to be translated, as dictated by
+ the props. */
+ if (eol_style_val || keywords_val)
+ {
+ svn_subst_eol_style_t style;
+ const char *eol = NULL;
+ svn_boolean_t repair = FALSE;
+ apr_hash_t *final_kw = NULL;
+
+ if (eol_style_val)
+ {
+ SVN_ERR(get_eol_style(&style, &eol, eol_style_val->data,
+ eb->native_eol));
+ repair = TRUE;
+ }
+
+ if (keywords_val)
+ SVN_ERR(svn_subst_build_keywords3(&final_kw, keywords_val->data,
+ revision, full_url,
+ eb->repos_root_url,
+ date, author, scratch_pool));
+
+ /* Writing through a translated stream is more efficient than
+ reading through one, so we wrap TMP_STREAM and not CONTENTS. */
+ tmp_stream = svn_subst_stream_translated(tmp_stream, eol, repair,
+ final_kw, TRUE, /* expand */
+ scratch_pool);
+ }
+
+ SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func,
+ eb->cancel_baton, scratch_pool));
+
+ /* Move the file into place. */
+ SVN_ERR(svn_io_file_rename(tmppath, full_path, scratch_pool));
+ }
+
+ if (executable_val)
+ SVN_ERR(svn_io_set_file_executable(full_path, TRUE, FALSE, scratch_pool));
+
+ if (date && (! special))
+ SVN_ERR(svn_io_set_file_affected_time(date, full_path, scratch_pool));
+
+ if (eb->notify_func)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
+ svn_wc_notify_update_add,
+ scratch_pool);
+ notify->kind = svn_node_file;
+ (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_directory_ev2(void *baton,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_node_kind_t kind;
+ const char *full_path = svn_dirent_join(eb->root_path, relpath,
+ scratch_pool);
+ svn_string_t *val;
+
+ SVN_ERR(svn_io_check_path(full_path, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, scratch_pool));
+ else if (kind == svn_node_file)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' exists and is not a directory"),
+ svn_dirent_local_style(full_path, scratch_pool));
+ else if (! (kind == svn_node_dir && eb->force))
+ return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("'%s' already exists"),
+ svn_dirent_local_style(full_path, scratch_pool));
+
+ if ( (val = svn_hash_gets(props, SVN_PROP_EXTERNALS)) )
+ SVN_ERR(add_externals(eb->externals, full_path, val));
+
+ if (eb->notify_func)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
+ svn_wc_notify_update_add,
+ scratch_pool);
+ notify->kind = svn_node_dir;
+ (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+target_revision_func(void *baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+
+ *eb->target_revision = target_revision;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_editor_ev2(const svn_delta_editor_t **export_editor,
+ void **edit_baton,
+ struct edit_baton *eb,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor_t *editor;
+ struct svn_delta__extra_baton *exb = apr_pcalloc(result_pool, sizeof(*exb));
+ svn_boolean_t *found_abs_paths = apr_palloc(result_pool,
+ sizeof(*found_abs_paths));
+
+ exb->baton = eb;
+ exb->target_revision = target_revision_func;
+
+ SVN_ERR(svn_editor_create(&editor, eb, ctx->cancel_func, ctx->cancel_baton,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_editor_setcb_add_directory(editor, add_directory_ev2,
+ scratch_pool));
+ SVN_ERR(svn_editor_setcb_add_file(editor, add_file_ev2, scratch_pool));
+
+ *found_abs_paths = TRUE;
+
+ SVN_ERR(svn_delta__delta_from_editor(export_editor, edit_baton,
+ editor, NULL, NULL, found_abs_paths,
+ NULL, NULL,
+ fetch_props_func, eb,
+ fetch_base_func, eb,
+ exb, result_pool));
+
+ /* Create the root of the export. */
+ SVN_ERR(open_root_internal(eb->root_path, eb->force, eb->notify_func,
+ eb->notify_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+export_file_ev2(const char *from_path_or_url,
+ const char *to_path,
+ struct edit_baton *eb,
+ svn_client__pathrev_t *loc,
+ svn_ra_session_t *ra_session,
+ svn_boolean_t overwrite,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
+ apr_hash_t *props;
+ svn_stream_t *tmp_stream;
+ svn_node_kind_t to_kind;
+
+ if (svn_path_is_empty(to_path))
+ {
+ if (from_is_url)
+ to_path = svn_uri_basename(from_path_or_url, scratch_pool);
+ else
+ to_path = svn_dirent_basename(from_path_or_url, NULL);
+ eb->root_path = to_path;
+ }
+ else
+ {
+ SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url,
+ from_is_url, scratch_pool));
+ eb->root_path = to_path;
+ }
+
+ SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
+
+ if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
+ ! overwrite)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Destination file '%s' exists, and "
+ "will not be overwritten unless forced"),
+ svn_dirent_local_style(to_path, scratch_pool));
+ else if (to_kind == svn_node_dir)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Destination '%s' exists. Cannot "
+ "overwrite directory with non-directory"),
+ svn_dirent_local_style(to_path, scratch_pool));
+
+ tmp_stream = svn_stream_buffered(scratch_pool);
+
+ SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
+ tmp_stream, NULL, &props, scratch_pool));
+
+ /* Since you cannot actually root an editor at a file, we manually drive
+ * a function of our editor. */
+ SVN_ERR(add_file_ev2(eb, "", NULL, tmp_stream, props, SVN_INVALID_REVNUM,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+export_file(const char *from_path_or_url,
+ const char *to_path,
+ struct edit_baton *eb,
+ svn_client__pathrev_t *loc,
+ svn_ra_session_t *ra_session,
+ svn_boolean_t overwrite,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *props;
+ apr_hash_index_t *hi;
+ struct file_baton *fb = apr_pcalloc(scratch_pool, sizeof(*fb));
+ svn_node_kind_t to_kind;
+ svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
+
+ if (svn_path_is_empty(to_path))
+ {
+ if (from_is_url)
+ to_path = svn_uri_basename(from_path_or_url, scratch_pool);
+ else
+ to_path = svn_dirent_basename(from_path_or_url, NULL);
+ eb->root_path = to_path;
+ }
+ else
+ {
+ SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url,
+ from_is_url, scratch_pool));
+ eb->root_path = to_path;
+ }
+
+ SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
+
+ if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
+ ! overwrite)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Destination file '%s' exists, and "
+ "will not be overwritten unless forced"),
+ svn_dirent_local_style(to_path, scratch_pool));
+ else if (to_kind == svn_node_dir)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Destination '%s' exists. Cannot "
+ "overwrite directory with non-directory"),
+ svn_dirent_local_style(to_path, scratch_pool));
+
+ /* Since you cannot actually root an editor at a file, we
+ * manually drive a few functions of our editor. */
+
+ /* This is the equivalent of a parentless add_file(). */
+ fb->edit_baton = eb;
+ fb->path = eb->root_path;
+ fb->url = eb->root_url;
+ fb->pool = scratch_pool;
+ fb->repos_root_url = eb->repos_root_url;
+
+ /* Copied from apply_textdelta(). */
+ SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
+ svn_dirent_dirname(fb->path, scratch_pool),
+ svn_io_file_del_none,
+ fb->pool, fb->pool));
+
+ /* Step outside the editor-likeness for a moment, to actually talk
+ * to the repository. */
+ /* ### note: the stream will not be closed */
+ SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
+ fb->tmp_stream,
+ NULL, &props, scratch_pool));
+
+ /* Push the props into change_file_prop(), to update the file_baton
+ * with information. */
+ for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ const svn_string_t *propval = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(change_file_prop(fb, propname, propval, scratch_pool));
+ }
+
+ /* And now just use close_file() to do all the keyword and EOL
+ * work, and put the file into place. */
+ SVN_ERR(close_file(fb, NULL, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+export_directory(const char *from_path_or_url,
+ const char *to_path,
+ struct edit_baton *eb,
+ svn_client__pathrev_t *loc,
+ svn_ra_session_t *ra_session,
+ svn_boolean_t overwrite,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t ignore_keywords,
+ svn_depth_t depth,
+ const char *native_eol,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ void *edit_baton;
+ const svn_delta_editor_t *export_editor;
+ const svn_ra_reporter3_t *reporter;
+ void *report_baton;
+ svn_node_kind_t kind;
+
+ if (!ENABLE_EV2_IMPL)
+ SVN_ERR(get_editor_ev1(&export_editor, &edit_baton, eb, ctx,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(get_editor_ev2(&export_editor, &edit_baton, eb, ctx,
+ scratch_pool, scratch_pool));
+
+ /* Manufacture a basic 'report' to the update reporter. */
+ SVN_ERR(svn_ra_do_update3(ra_session,
+ &reporter, &report_baton,
+ loc->rev,
+ "", /* no sub-target */
+ depth,
+ FALSE, /* don't want copyfrom-args */
+ FALSE, /* don't want ignore_ancestry */
+ export_editor, edit_baton,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(reporter->set_path(report_baton, "", loc->rev,
+ /* Depth is irrelevant, as we're
+ passing start_empty=TRUE anyway. */
+ svn_depth_infinity,
+ TRUE, /* "help, my dir is empty!" */
+ NULL, scratch_pool));
+
+ SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
+
+ /* Special case: Due to our sly export/checkout method of updating an
+ * empty directory, no target will have been created if the exported
+ * item is itself an empty directory (export_editor->open_root never
+ * gets called, because there are no "changes" to make to the empty
+ * dir we reported to the repository).
+ *
+ * So we just create the empty dir manually; but we do it via
+ * open_root_internal(), in order to get proper notification.
+ */
+ SVN_ERR(svn_io_check_path(to_path, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ SVN_ERR(open_root_internal
+ (to_path, overwrite, ctx->notify_func2,
+ ctx->notify_baton2, scratch_pool));
+
+ if (! ignore_externals && depth == svn_depth_infinity)
+ {
+ const char *to_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&to_abspath, to_path, scratch_pool));
+ SVN_ERR(svn_client__export_externals(eb->externals,
+ from_path_or_url,
+ to_abspath, eb->repos_root_url,
+ depth, native_eol,
+ ignore_keywords,
+ ctx, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Public Interfaces ***/
+
+svn_error_t *
+svn_client_export5(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ const char *to_path,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_boolean_t overwrite,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t ignore_keywords,
+ svn_depth_t depth,
+ const char *native_eol,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
+ svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
+
+ SVN_ERR_ASSERT(peg_revision != NULL);
+ SVN_ERR_ASSERT(revision != NULL);
+
+ if (svn_path_is_url(to_path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), to_path);
+
+ peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
+ from_path_or_url);
+ revision = svn_cl__rev_default_to_peg(revision, peg_revision);
+
+ if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
+ {
+ svn_client__pathrev_t *loc;
+ svn_ra_session_t *ra_session;
+ svn_node_kind_t kind;
+ struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
+
+ /* Get the RA connection. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
+ from_path_or_url, NULL,
+ peg_revision,
+ revision, ctx, pool));
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &eb->repos_root_url, pool));
+ eb->root_path = to_path;
+ eb->root_url = loc->url;
+ eb->force = overwrite;
+ eb->target_revision = &edit_revision;
+ eb->externals = apr_hash_make(pool);
+ eb->native_eol = native_eol;
+ eb->ignore_keywords = ignore_keywords;
+ eb->cancel_func = ctx->cancel_func;
+ eb->cancel_baton = ctx->cancel_baton;
+ eb->notify_func = ctx->notify_func2;
+ eb->notify_baton = ctx->notify_baton2;
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool));
+
+ if (kind == svn_node_file)
+ {
+ if (!ENABLE_EV2_IMPL)
+ SVN_ERR(export_file(from_path_or_url, to_path, eb, loc, ra_session,
+ overwrite, pool));
+ else
+ SVN_ERR(export_file_ev2(from_path_or_url, to_path, eb, loc,
+ ra_session, overwrite, pool));
+ }
+ else if (kind == svn_node_dir)
+ {
+ SVN_ERR(export_directory(from_path_or_url, to_path,
+ eb, loc, ra_session, overwrite,
+ ignore_externals, ignore_keywords, depth,
+ native_eol, ctx, pool));
+ }
+ else if (kind == svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' doesn't exist"),
+ from_path_or_url);
+ }
+ /* kind == svn_node_unknown not handled */
+ }
+ else
+ {
+ struct export_info_baton eib;
+ svn_node_kind_t kind;
+ apr_hash_t *externals = NULL;
+
+ /* This is a working copy export. */
+ /* just copy the contents of the working copy into the target path. */
+ SVN_ERR(svn_dirent_get_absolute(&from_path_or_url, from_path_or_url,
+ pool));
+
+ SVN_ERR(svn_dirent_get_absolute(&to_path, to_path, pool));
+
+ SVN_ERR(svn_io_check_path(from_path_or_url, &kind, pool));
+
+ /* ### [JAF] If something already exists on disk at the destination path,
+ * the behaviour depends on the node kinds of the source and destination
+ * and on the FORCE flag. The intention (I guess) is to follow the
+ * semantics of svn_client_export5(), semantics that are not fully
+ * documented but would be something like:
+ *
+ * -----------+---------------------------------------------------------
+ * Src | DIR FILE SPECIAL
+ * Dst (disk) +---------------------------------------------------------
+ * NONE | simple copy simple copy (as src=file?)
+ * DIR | merge if forced [2] inside if root [1] (as src=file?)
+ * FILE | err overwr if forced[3] (as src=file?)
+ * SPECIAL | ??? ??? ???
+ * -----------+---------------------------------------------------------
+ *
+ * [1] FILE onto DIR case: If this file is the root of the copy and thus
+ * the only node to be copied, then copy it as a child of the
+ * directory TO, applying these same rules again except that if this
+ * case occurs again (the child path is already a directory) then
+ * error out. If this file is not the root of the copy (it is
+ * reached by recursion), then error out.
+ *
+ * [2] DIR onto DIR case. If the 'FORCE' flag is true then copy the
+ * source's children inside the target dir, else error out. When
+ * copying the children, apply the same set of rules, except in the
+ * FILE onto DIR case error out like in note [1].
+ *
+ * [3] If the 'FORCE' flag is true then overwrite the destination file
+ * else error out.
+ *
+ * The reality (apparently, looking at the code) is somewhat different.
+ * For a start, to detect the source kind, it looks at what is on disk
+ * rather than the versioned working or base node.
+ */
+ if (kind == svn_node_file)
+ SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url, FALSE,
+ pool));
+
+ eib.to_path = to_path;
+ eib.revision = revision;
+ eib.overwrite = overwrite;
+ eib.ignore_keywords = ignore_keywords;
+ eib.wc_ctx = ctx->wc_ctx;
+ eib.native_eol = native_eol;
+ eib.notify_func = ctx->notify_func2;;
+ eib.notify_baton = ctx->notify_baton2;
+ eib.origin_abspath = from_path_or_url;
+ eib.exported = FALSE;
+
+ SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, from_path_or_url, depth,
+ TRUE /* get_all */,
+ TRUE /* no_ignore */,
+ FALSE /* ignore_text_mods */,
+ NULL,
+ export_node, &eib,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+
+ if (!eib.exported)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(from_path_or_url,
+ pool));
+
+ if (!ignore_externals)
+ SVN_ERR(svn_wc__externals_defined_below(&externals, ctx->wc_ctx,
+ from_path_or_url,
+ pool, pool));
+
+ if (externals && apr_hash_count(externals))
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *external_abspath = svn__apr_hash_index_key(hi);
+ const char *relpath;
+ const char *target_abspath;
+
+ svn_pool_clear(iterpool);
+
+ relpath = svn_dirent_skip_ancestor(from_path_or_url,
+ external_abspath);
+
+ target_abspath = svn_dirent_join(to_path, relpath,
+ iterpool);
+
+ /* Ensure that the parent directory exists */
+ SVN_ERR(svn_io_make_dir_recursively(
+ svn_dirent_dirname(target_abspath, iterpool),
+ iterpool));
+
+ SVN_ERR(svn_client_export5(NULL,
+ svn_dirent_join(from_path_or_url,
+ relpath,
+ iterpool),
+ target_abspath,
+ peg_revision, revision,
+ TRUE, ignore_externals,
+ ignore_keywords, depth, native_eol,
+ ctx, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+ }
+
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(to_path,
+ svn_wc_notify_update_completed, pool);
+ notify->revision = edit_revision;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+
+ if (result_rev)
+ *result_rev = edit_revision;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/externals.c b/contrib/subversion/subversion/libsvn_client/externals.c
new file mode 100644
index 0000000..30748ea
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/externals.c
@@ -0,0 +1,1139 @@
+/*
+ * externals.c: handle the svn:externals property
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_uri.h>
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_config.h"
+#include "client.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+/* Remove the directory at LOCAL_ABSPATH from revision control, and do the
+ * same to any revision controlled directories underneath LOCAL_ABSPATH
+ * (including directories not referred to by parent svn administrative areas);
+ * then if LOCAL_ABSPATH is empty afterwards, remove it, else rename it to a
+ * unique name in the same parent directory.
+ *
+ * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control.
+ *
+ * Use SCRATCH_POOL for all temporary allocation.
+ */
+static svn_error_t *
+relegate_dir_external(svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__acquire_write_lock(NULL, wc_ctx, local_abspath,
+ FALSE, scratch_pool, scratch_pool));
+
+ err = svn_wc__external_remove(wc_ctx, wri_abspath, local_abspath, FALSE,
+ cancel_func, cancel_baton, scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD))
+ {
+ const char *parent_dir;
+ const char *dirname;
+ const char *new_path;
+
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+
+ svn_dirent_split(&parent_dir, &dirname, local_abspath, scratch_pool);
+
+ /* Reserve the new dir name. */
+ SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path,
+ parent_dir, dirname, ".OLD",
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+
+ /* Sigh... We must fall ever so slightly from grace.
+
+ Ideally, there would be no window, however brief, when we
+ don't have a reservation on the new name. Unfortunately,
+ at least in the Unix (Linux?) version of apr_file_rename(),
+ you can't rename a directory over a file, because it's just
+ calling stdio rename(), which says:
+
+ ENOTDIR
+ A component used as a directory in oldpath or newpath
+ path is not, in fact, a directory. Or, oldpath is
+ a directory, and newpath exists but is not a directory
+
+ So instead, we get the name, then remove the file (ugh), then
+ rename the directory, hoping that nobody has gotten that name
+ in the meantime -- which would never happen in real life, so
+ no big deal.
+ */
+ /* Do our best, but no biggy if it fails. The rename will fail. */
+ svn_error_clear(svn_io_remove_file2(new_path, TRUE, scratch_pool));
+
+ /* Rename. If this is still a working copy we should use the working
+ copy rename function (to release open handles) */
+ err = svn_wc__rename_wc(wc_ctx, local_abspath, new_path,
+ scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ {
+ svn_error_clear(err);
+
+ /* And if it is no longer a working copy, we should just rename
+ it */
+ err = svn_io_file_rename(local_abspath, new_path, scratch_pool);
+ }
+
+ /* ### TODO: We should notify the user about the rename */
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(err ? local_abspath : new_path,
+ svn_wc_notify_left_local_modifications,
+ scratch_pool);
+ notify->kind = svn_node_dir;
+ notify->err = err;
+
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+ }
+
+ return svn_error_trace(err);
+}
+
+/* Try to update a directory external at PATH to URL at REVISION.
+ Use POOL for temporary allocations, and use the client context CTX. */
+static svn_error_t *
+switch_dir_external(const char *local_abspath,
+ const char *url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ const char *defining_abspath,
+ svn_boolean_t *timestamp_sleep,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ svn_error_t *err;
+ svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM;
+ svn_revnum_t external_rev = SVN_INVALID_REVNUM;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ const char *repos_root_url;
+ const char *repos_uuid;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (peg_revision->kind == svn_opt_revision_number)
+ external_peg_rev = peg_revision->value.number;
+
+ if (revision->kind == svn_opt_revision_number)
+ external_rev = revision->value.number;
+
+ /* If path is a directory, try to update/switch to the correct URL
+ and revision. */
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, pool));
+ if (kind == svn_node_dir)
+ {
+ const char *node_url;
+
+ /* Doubles as an "is versioned" check. */
+ err = svn_wc__node_get_url(&node_url, ctx->wc_ctx, local_abspath,
+ pool, subpool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ goto relegate;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ if (node_url)
+ {
+ /* If we have what appears to be a version controlled
+ subdir, and its top-level URL matches that of our
+ externals definition, perform an update. */
+ if (strcmp(node_url, url) == 0)
+ {
+ SVN_ERR(svn_client__update_internal(NULL, local_abspath,
+ revision, svn_depth_unknown,
+ FALSE, FALSE, FALSE, TRUE,
+ FALSE, TRUE,
+ timestamp_sleep,
+ ctx, subpool));
+ svn_pool_destroy(subpool);
+ goto cleanup;
+ }
+
+ /* We'd really prefer not to have to do a brute-force
+ relegation -- blowing away the current external working
+ copy and checking it out anew -- so we'll first see if we
+ can get away with a generally cheaper relocation (if
+ required) and switch-style update.
+
+ To do so, we need to know the repository root URL of the
+ external working copy as it currently sits. */
+ err = svn_wc__node_get_repos_info(NULL, NULL,
+ &repos_root_url, &repos_uuid,
+ ctx->wc_ctx, local_abspath,
+ pool, subpool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ repos_root_url = NULL;
+ repos_uuid = NULL;
+ }
+
+ if (repos_root_url)
+ {
+ /* If the new external target URL is not obviously a
+ child of the external working copy's current
+ repository root URL... */
+ if (! svn_uri__is_ancestor(repos_root_url, url))
+ {
+ const char *repos_root;
+
+ /* ... then figure out precisely which repository
+ root URL that target URL *is* a child of ... */
+ SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url,
+ ctx, subpool, subpool));
+
+ /* ... and use that to try to relocate the external
+ working copy to the target location. */
+ err = svn_client_relocate2(local_abspath, repos_root_url,
+ repos_root, FALSE, ctx, subpool);
+
+ /* If the relocation failed because the new URL
+ points to a totally different repository, we've
+ no choice but to relegate and check out a new WC. */
+ if (err
+ && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION
+ || (err->apr_err
+ == SVN_ERR_CLIENT_INVALID_RELOCATION)))
+ {
+ svn_error_clear(err);
+ goto relegate;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ /* If the relocation went without a hitch, we should
+ have a new repository root URL. */
+ repos_root_url = repos_root;
+ }
+
+ SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url,
+ peg_revision, revision,
+ svn_depth_infinity,
+ TRUE, FALSE, FALSE,
+ TRUE /* ignore_ancestry */,
+ timestamp_sleep,
+ ctx, subpool));
+
+ SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
+ defining_abspath,
+ local_abspath, svn_node_dir,
+ repos_root_url, repos_uuid,
+ svn_uri_skip_ancestor(
+ repos_root_url,
+ url, subpool),
+ external_peg_rev,
+ external_rev,
+ subpool));
+
+ svn_pool_destroy(subpool);
+ goto cleanup;
+ }
+ }
+ }
+
+ relegate:
+
+ /* Fall back on removing the WC and checking out a new one. */
+
+ /* Ensure that we don't have any RA sessions or WC locks from failed
+ operations above. */
+ svn_pool_destroy(subpool);
+
+ if (kind == svn_node_dir)
+ {
+ /* Buh-bye, old and busted ... */
+ SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath,
+ local_abspath,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool));
+ }
+ else
+ {
+ /* The target dir might have multiple components. Guarantee
+ the path leading down to the last component. */
+ const char *parent = svn_dirent_dirname(local_abspath, pool);
+ SVN_ERR(svn_io_make_dir_recursively(parent, pool));
+ }
+
+ /* ... Hello, new hotness. */
+ SVN_ERR(svn_client__checkout_internal(NULL, url, local_abspath, peg_revision,
+ revision, svn_depth_infinity,
+ FALSE, FALSE, timestamp_sleep,
+ ctx, pool));
+
+ SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL,
+ &repos_root_url,
+ &repos_uuid,
+ ctx->wc_ctx, local_abspath,
+ pool, pool));
+
+ SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
+ defining_abspath,
+ local_abspath, svn_node_dir,
+ repos_root_url, repos_uuid,
+ svn_uri_skip_ancestor(repos_root_url,
+ url, pool),
+ external_peg_rev,
+ external_rev,
+ pool));
+
+ cleanup:
+ /* Issues #4123 and #4130: We don't need to keep the newly checked
+ out external's DB open. */
+ SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Try to update a file external at LOCAL_ABSPATH to URL at REVISION using a
+ access baton that has a write lock. Use SCRATCH_POOL for temporary
+ allocations, and use the client context CTX. */
+static svn_error_t *
+switch_file_external(const char *local_abspath,
+ const char *url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ const char *def_dir_abspath,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_config_t *cfg = ctx->config
+ ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+ svn_boolean_t use_commit_times;
+ const char *diff3_cmd;
+ const char *preserved_exts_str;
+ const apr_array_header_t *preserved_exts;
+ svn_node_kind_t kind, external_kind;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* See if the user wants last-commit timestamps instead of current ones. */
+ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
+
+ /* Get the external diff3, if any. */
+ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
+
+ if (diff3_cmd != NULL)
+ SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
+
+ /* See which files the user wants to preserve the extension of when
+ conflict files are made. */
+ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
+ preserved_exts = *preserved_exts_str
+ ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
+ : NULL;
+
+ {
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* File externals can only be installed inside the current working copy.
+ So verify if the working copy that contains/will contain the target
+ is the defining abspath, or one of its ancestors */
+
+ if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath))
+ return svn_error_createf(
+ SVN_ERR_WC_BAD_PATH, NULL,
+ _("Cannot insert a file external defined on '%s' "
+ "into the working copy '%s'."),
+ svn_dirent_local_style(def_dir_abspath,
+ scratch_pool),
+ svn_dirent_local_style(wcroot_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
+ TRUE, FALSE, scratch_pool));
+
+ SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
+ ctx->wc_ctx, local_abspath, local_abspath,
+ TRUE, scratch_pool, scratch_pool));
+
+ /* If there is a versioned item with this name, ensure it's a file
+ external before working with it. If there is no entry in the
+ working copy, then create an empty file and add it to the working
+ copy. */
+ if (kind != svn_node_none && kind != svn_node_unknown)
+ {
+ if (external_kind != svn_node_file)
+ {
+ return svn_error_createf(
+ SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0,
+ _("The file external from '%s' cannot overwrite the existing "
+ "versioned item at '%s'"),
+ url, svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ }
+ else
+ {
+ svn_node_kind_t disk_kind;
+
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
+
+ if (kind == svn_node_file || kind == svn_node_dir)
+ return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
+ _("The file external '%s' can not be "
+ "created because the node exists."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ {
+ const svn_ra_reporter3_t *reporter;
+ void *report_baton;
+ const svn_delta_editor_t *switch_editor;
+ void *switch_baton;
+ svn_client__pathrev_t *switch_loc;
+ svn_revnum_t revnum;
+ apr_array_header_t *inherited_props;
+ const char *dir_abspath;
+ const char *target;
+
+ svn_dirent_split(&dir_abspath, &target, local_abspath, scratch_pool);
+
+ /* Open an RA session to 'source' URL */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc,
+ url, dir_abspath,
+ peg_revision, revision,
+ ctx, scratch_pool));
+ /* Get the external file's iprops. */
+ SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "",
+ switch_loc->rev,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_ra_reparent(ra_session, svn_uri_dirname(url, scratch_pool),
+ scratch_pool));
+
+ SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton,
+ &revnum, ctx->wc_ctx,
+ local_abspath,
+ def_dir_abspath,
+ switch_loc->url,
+ switch_loc->repos_root_url,
+ switch_loc->repos_uuid,
+ inherited_props,
+ use_commit_times,
+ diff3_cmd, preserved_exts,
+ def_dir_abspath,
+ url, peg_revision, revision,
+ ctx->conflict_func2,
+ ctx->conflict_baton2,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ ctx->notify_func2,
+ ctx->notify_baton2,
+ scratch_pool, scratch_pool));
+
+ /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
+ invalid revnum, that means RA will use the latest revision. */
+ SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
+ switch_loc->rev,
+ target, svn_depth_unknown, url,
+ FALSE /* send_copyfrom */,
+ TRUE /* ignore_ancestry */,
+ switch_editor, switch_baton,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath,
+ reporter, report_baton,
+ TRUE, use_commit_times,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool));
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
+ scratch_pool);
+ notify->kind = svn_node_none;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ notify->revision = revnum;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for
+ directory externals */
+static svn_error_t *
+remove_external2(svn_boolean_t *removed,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_node_kind_t external_kind,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath,
+ local_abspath,
+ (external_kind == svn_node_none),
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ *removed = TRUE;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+remove_external(svn_boolean_t *removed,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_node_kind_t external_kind,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ *removed = FALSE;
+ switch (external_kind)
+ {
+ case svn_node_dir:
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ remove_external2(removed,
+ wc_ctx, wri_abspath,
+ local_abspath, external_kind,
+ cancel_func, cancel_baton,
+ scratch_pool),
+ wc_ctx, local_abspath, FALSE, scratch_pool);
+ break;
+ case svn_node_file:
+ default:
+ SVN_ERR(remove_external2(removed,
+ wc_ctx, wri_abspath,
+ local_abspath, external_kind,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Called when an external that is in the EXTERNALS table is no longer
+ referenced from an svn:externals property */
+static svn_error_t *
+handle_external_item_removal(const svn_client_ctx_t *ctx,
+ const char *defining_abspath,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_node_kind_t external_kind;
+ svn_node_kind_t kind;
+ svn_boolean_t removed = FALSE;
+
+ /* local_abspath should be a wcroot or a file external */
+ SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
+ ctx->wc_ctx, defining_abspath,
+ local_abspath, FALSE,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE,
+ scratch_pool));
+
+ if (external_kind != kind)
+ external_kind = svn_node_none; /* Only remove the registration */
+
+ err = remove_external(&removed,
+ ctx->wc_ctx, defining_abspath, local_abspath,
+ external_kind,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed)
+ {
+ svn_error_clear(err);
+ err = NULL; /* We removed the working copy, so we can't release the
+ lock that was stored inside */
+ }
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify =
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_update_external_removed,
+ scratch_pool);
+
+ notify->kind = kind;
+ notify->err = err;
+
+ (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
+ {
+ notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_left_local_modifications,
+ scratch_pool);
+ notify->kind = svn_node_dir;
+ notify->err = err;
+
+ (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
+ }
+ }
+
+ if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
+ {
+ svn_error_clear(err);
+ err = NULL;
+ }
+
+ return svn_error_trace(err);
+}
+
+static svn_error_t *
+handle_external_item_change(svn_client_ctx_t *ctx,
+ const char *repos_root_url,
+ const char *parent_dir_abspath,
+ const char *parent_dir_url,
+ const char *local_abspath,
+ const char *old_defining_abspath,
+ const svn_wc_external_item2_t *new_item,
+ svn_boolean_t *timestamp_sleep,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_session_t *ra_session;
+ svn_client__pathrev_t *new_loc;
+ const char *new_url;
+ svn_node_kind_t ext_kind;
+
+ SVN_ERR_ASSERT(repos_root_url && parent_dir_url);
+ SVN_ERR_ASSERT(new_item != NULL);
+
+ /* Don't bother to check status, since we'll get that for free by
+ attempting to retrieve the hash values anyway. */
+
+ /* When creating the absolute URL, use the pool and not the
+ iterpool, since the hash table values outlive the iterpool and
+ any pointers they have should also outlive the iterpool. */
+
+ SVN_ERR(svn_wc__resolve_relative_external_url(&new_url,
+ new_item, repos_root_url,
+ parent_dir_url,
+ scratch_pool, scratch_pool));
+
+ /* Determine if the external is a file or directory. */
+ /* Get the RA connection. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
+ new_url, NULL,
+ &(new_item->peg_revision),
+ &(new_item->revision), ctx,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind,
+ scratch_pool));
+
+ if (svn_node_none == ext_kind)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' at revision %ld doesn't exist"),
+ new_loc->url, new_loc->rev);
+
+ if (svn_node_dir != ext_kind && svn_node_file != ext_kind)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' at revision %ld is not a file "
+ "or a directory"),
+ new_loc->url, new_loc->rev);
+
+
+ /* Not protecting against recursive externals. Detecting them in
+ the global case is hard, and it should be pretty obvious to a
+ user when it happens. Worst case: your disk fills up :-). */
+
+ /* First notify that we're about to handle an external. */
+ if (ctx->notify_func2)
+ {
+ (*ctx->notify_func2)(
+ ctx->notify_baton2,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_update_external,
+ scratch_pool),
+ scratch_pool);
+ }
+
+ if (! old_defining_abspath)
+ {
+ /* The target dir might have multiple components. Guarantee the path
+ leading down to the last component. */
+ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ scratch_pool));
+ }
+
+ switch (ext_kind)
+ {
+ case svn_node_dir:
+ SVN_ERR(switch_dir_external(local_abspath, new_url,
+ &(new_item->peg_revision),
+ &(new_item->revision),
+ parent_dir_abspath,
+ timestamp_sleep, ctx,
+ scratch_pool));
+ break;
+ case svn_node_file:
+ if (strcmp(repos_root_url, new_loc->repos_root_url))
+ {
+ const char *local_repos_root_url;
+ const char *local_repos_uuid;
+ const char *ext_repos_relpath;
+ svn_error_t *err;
+
+ /*
+ * The working copy library currently requires that all files
+ * in the working copy have the same repository root URL.
+ * The URL from the file external's definition differs from the
+ * one used by the working copy. As a workaround, replace the
+ * root URL portion of the file external's URL, after making
+ * sure both URLs point to the same repository. See issue #4087.
+ */
+
+ err = svn_wc__node_get_repos_info(NULL, NULL,
+ &local_repos_root_url,
+ &local_repos_uuid,
+ ctx->wc_ctx, parent_dir_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ local_repos_root_url = NULL;
+ local_repos_uuid = NULL;
+ }
+
+ ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url,
+ new_url, scratch_pool);
+ if (local_repos_uuid == NULL || local_repos_root_url == NULL ||
+ ext_repos_relpath == NULL ||
+ strcmp(local_repos_uuid, new_loc->repos_uuid) != 0)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Unsupported external: URL of file external '%s' "
+ "is not in repository '%s'"),
+ new_url, repos_root_url);
+
+ new_url = svn_path_url_add_component2(local_repos_root_url,
+ ext_repos_relpath,
+ scratch_pool);
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
+ new_url,
+ NULL,
+ &(new_item->peg_revision),
+ &(new_item->revision),
+ ctx, scratch_pool));
+ }
+
+ SVN_ERR(switch_file_external(local_abspath,
+ new_url,
+ &new_item->peg_revision,
+ &new_item->revision,
+ parent_dir_abspath,
+ ra_session,
+ ctx,
+ scratch_pool));
+ break;
+
+ default:
+ SVN_ERR_MALFUNCTION();
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+wrap_external_error(const svn_client_ctx_t *ctx,
+ const char *target_abspath,
+ svn_error_t *err,
+ apr_pool_t *scratch_pool)
+{
+ if (err && err->apr_err != SVN_ERR_CANCELLED)
+ {
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notifier = svn_wc_create_notify(
+ target_abspath,
+ svn_wc_notify_failed_external,
+ scratch_pool);
+ notifier->err = err;
+ ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool);
+ }
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return err;
+}
+
+static svn_error_t *
+handle_externals_change(svn_client_ctx_t *ctx,
+ const char *repos_root_url,
+ svn_boolean_t *timestamp_sleep,
+ const char *local_abspath,
+ const char *new_desc_text,
+ apr_hash_t *old_externals,
+ svn_depth_t ambient_depth,
+ svn_depth_t requested_depth,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *new_desc;
+ int i;
+ apr_pool_t *iterpool;
+ const char *url;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Bag out if the depth here is too shallow for externals action. */
+ if ((requested_depth < svn_depth_infinity
+ && requested_depth != svn_depth_unknown)
+ || (ambient_depth < svn_depth_infinity
+ && requested_depth < svn_depth_infinity))
+ return SVN_NO_ERROR;
+
+ if (new_desc_text)
+ SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath,
+ new_desc_text,
+ FALSE, scratch_pool));
+ else
+ new_desc = NULL;
+
+ SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath,
+ scratch_pool, iterpool));
+
+ SVN_ERR_ASSERT(url);
+
+ for (i = 0; new_desc && (i < new_desc->nelts); i++)
+ {
+ const char *old_defining_abspath;
+ svn_wc_external_item2_t *new_item;
+ const char *target_abspath;
+ svn_boolean_t under_root;
+
+ new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *);
+
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath,
+ local_abspath, new_item->target_dir,
+ iterpool));
+
+ if (! under_root)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Path '%s' is not in the working copy"),
+ svn_dirent_local_style(
+ svn_dirent_join(local_abspath, new_item->target_dir,
+ iterpool),
+ iterpool));
+ }
+
+ old_defining_abspath = svn_hash_gets(old_externals, target_abspath);
+
+ SVN_ERR(wrap_external_error(
+ ctx, target_abspath,
+ handle_external_item_change(ctx,
+ repos_root_url,
+ local_abspath, url,
+ target_abspath,
+ old_defining_abspath,
+ new_item,
+ timestamp_sleep,
+ iterpool),
+ iterpool));
+
+ /* And remove already processed items from the to-remove hash */
+ if (old_defining_abspath)
+ svn_hash_sets(old_externals, target_abspath, NULL);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__handle_externals(apr_hash_t *externals_new,
+ apr_hash_t *ambient_depths,
+ const char *repos_root_url,
+ const char *target_abspath,
+ svn_depth_t requested_depth,
+ svn_boolean_t *timestamp_sleep,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *old_external_defs;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ SVN_ERR_ASSERT(repos_root_url);
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_wc__externals_defined_below(&old_external_defs,
+ ctx->wc_ctx, target_abspath,
+ scratch_pool, iterpool));
+
+ for (hi = apr_hash_first(scratch_pool, externals_new);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+ const char *desc_text = svn__apr_hash_index_val(hi);
+ svn_depth_t ambient_depth = svn_depth_infinity;
+
+ svn_pool_clear(iterpool);
+
+ if (ambient_depths)
+ {
+ const char *ambient_depth_w;
+
+ ambient_depth_w = apr_hash_get(ambient_depths, local_abspath,
+ svn__apr_hash_index_klen(hi));
+
+ if (ambient_depth_w == NULL)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT, NULL,
+ _("Traversal of '%s' found no ambient depth"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else
+ {
+ ambient_depth = svn_depth_from_word(ambient_depth_w);
+ }
+ }
+
+ SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep,
+ local_abspath,
+ desc_text, old_external_defs,
+ ambient_depth, requested_depth,
+ iterpool));
+ }
+
+ /* Remove the remaining externals */
+ for (hi = apr_hash_first(scratch_pool, old_external_defs);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *item_abspath = svn__apr_hash_index_key(hi);
+ const char *defining_abspath = svn__apr_hash_index_val(hi);
+ const char *parent_abspath;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(wrap_external_error(
+ ctx, item_abspath,
+ handle_external_item_removal(ctx, defining_abspath,
+ item_abspath, iterpool),
+ iterpool));
+
+ /* Are there any unversioned directories between the removed
+ * external and the DEFINING_ABSPATH which we can remove? */
+ parent_abspath = item_abspath;
+ do {
+ svn_node_kind_t kind;
+
+ parent_abspath = svn_dirent_dirname(parent_abspath, iterpool);
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath,
+ TRUE, FALSE, iterpool));
+ if (kind == svn_node_none)
+ {
+ svn_error_t *err;
+
+ err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool);
+ if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
+ {
+ svn_error_clear(err);
+ break;
+ }
+ else
+ SVN_ERR(err);
+ }
+ } while (strcmp(parent_abspath, defining_abspath) != 0);
+ }
+
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__export_externals(apr_hash_t *externals,
+ const char *from_url,
+ const char *to_abspath,
+ const char *repos_root_url,
+ svn_depth_t requested_depth,
+ const char *native_eol,
+ svn_boolean_t ignore_keywords,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath));
+
+ for (hi = apr_hash_first(scratch_pool, externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+ const char *desc_text = svn__apr_hash_index_val(hi);
+ const char *local_relpath;
+ const char *dir_url;
+ apr_array_header_t *items;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath,
+ desc_text, FALSE,
+ iterpool));
+
+ if (! items->nelts)
+ continue;
+
+ local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath);
+
+ dir_url = svn_path_url_add_component2(from_url, local_relpath,
+ scratch_pool);
+
+ for (i = 0; i < items->nelts; i++)
+ {
+ const char *item_abspath;
+ const char *new_url;
+ svn_boolean_t under_root;
+ svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i,
+ svn_wc_external_item2_t *);
+
+ svn_pool_clear(sub_iterpool);
+
+ SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath,
+ local_abspath, item->target_dir,
+ sub_iterpool));
+
+ if (! under_root)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Path '%s' is not in the working copy"),
+ svn_dirent_local_style(
+ svn_dirent_join(local_abspath, item->target_dir,
+ sub_iterpool),
+ sub_iterpool));
+ }
+
+ SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item,
+ repos_root_url,
+ dir_url, sub_iterpool,
+ sub_iterpool));
+
+ /* The target dir might have multiple components. Guarantee
+ the path leading down to the last component. */
+ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath,
+ sub_iterpool),
+ sub_iterpool));
+
+ SVN_ERR(wrap_external_error(
+ ctx, item_abspath,
+ svn_client_export5(NULL, new_url, item_abspath,
+ &item->peg_revision,
+ &item->revision,
+ TRUE, FALSE, ignore_keywords,
+ svn_depth_infinity,
+ native_eol,
+ ctx, sub_iterpool),
+ sub_iterpool));
+ }
+ }
+
+ svn_pool_destroy(sub_iterpool);
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_client/import.c b/contrib/subversion/subversion/libsvn_client/import.c
new file mode 100644
index 0000000..43e0d79
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/import.c
@@ -0,0 +1,964 @@
+/*
+ * import.c: wrappers around import functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <apr_strings.h>
+#include <apr_hash.h>
+#include <apr_md5.h>
+
+#include "svn_hash.h"
+#include "svn_ra.h"
+#include "svn_delta.h"
+#include "svn_subst.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_error_codes.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_io.h"
+#include "svn_sorts.h"
+#include "svn_props.h"
+
+#include "client.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_magic.h"
+
+#include "svn_private_config.h"
+
+/* Import context baton.
+
+ ### TODO: Add the following items to this baton:
+ /` import editor/baton. `/
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+
+ /` Client context baton `/
+ svn_client_ctx_t `ctx;
+
+ /` Paths (keys) excluded from the import (values ignored) `/
+ apr_hash_t *excludes;
+*/
+typedef struct import_ctx_t
+{
+ /* Whether any changes were made to the repository */
+ svn_boolean_t repos_changed;
+
+ /* A magic cookie for mime-type detection. */
+ svn_magic__cookie_t *magic_cookie;
+
+ /* Collection of all possible configuration file dictated auto-props and
+ svn:auto-props. A hash mapping const char * file patterns to a
+ second hash which maps const char * property names to const char *
+ property values. Properties which don't have a value, e.g.
+ svn:executable, simply map the property name to an empty string.
+ May be NULL if autoprops are disabled. */
+ apr_hash_t *autoprops;
+} import_ctx_t;
+
+
+/* Apply LOCAL_ABSPATH's contents (as a delta against the empty string) to
+ FILE_BATON in EDITOR. Use POOL for any temporary allocation.
+ PROPERTIES is the set of node properties set on this file.
+
+ Fill DIGEST with the md5 checksum of the sent file; DIGEST must be
+ at least APR_MD5_DIGESTSIZE bytes long. */
+
+/* ### how does this compare against svn_wc_transmit_text_deltas2() ??? */
+
+static svn_error_t *
+send_file_contents(const char *local_abspath,
+ void *file_baton,
+ const svn_delta_editor_t *editor,
+ apr_hash_t *properties,
+ unsigned char *digest,
+ apr_pool_t *pool)
+{
+ svn_stream_t *contents;
+ svn_txdelta_window_handler_t handler;
+ void *handler_baton;
+ const svn_string_t *eol_style_val = NULL, *keywords_val = NULL;
+ svn_boolean_t special = FALSE;
+ svn_subst_eol_style_t eol_style;
+ const char *eol;
+ apr_hash_t *keywords;
+
+ /* If there are properties, look for EOL-style and keywords ones. */
+ if (properties)
+ {
+ eol_style_val = apr_hash_get(properties, SVN_PROP_EOL_STYLE,
+ sizeof(SVN_PROP_EOL_STYLE) - 1);
+ keywords_val = apr_hash_get(properties, SVN_PROP_KEYWORDS,
+ sizeof(SVN_PROP_KEYWORDS) - 1);
+ if (svn_hash_gets(properties, SVN_PROP_SPECIAL))
+ special = TRUE;
+ }
+
+ /* Get an editor func that wants to consume the delta stream. */
+ SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool,
+ &handler, &handler_baton));
+
+ if (eol_style_val)
+ svn_subst_eol_style_from_value(&eol_style, &eol, eol_style_val->data);
+ else
+ {
+ eol = NULL;
+ eol_style = svn_subst_eol_style_none;
+ }
+
+ if (keywords_val)
+ SVN_ERR(svn_subst_build_keywords3(&keywords, keywords_val->data,
+ APR_STRINGIFY(SVN_INVALID_REVNUM),
+ "", "", 0, "", pool));
+ else
+ keywords = NULL;
+
+ if (special)
+ {
+ SVN_ERR(svn_subst_read_specialfile(&contents, local_abspath,
+ pool, pool));
+ }
+ else
+ {
+ /* Open the working copy file. */
+ SVN_ERR(svn_stream_open_readonly(&contents, local_abspath, pool, pool));
+
+ /* If we have EOL styles or keywords, then detranslate the file. */
+ if (svn_subst_translation_required(eol_style, eol, keywords,
+ FALSE, TRUE))
+ {
+ if (eol_style == svn_subst_eol_style_unknown)
+ return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
+ _("%s property on '%s' contains "
+ "unrecognized EOL-style '%s'"),
+ SVN_PROP_EOL_STYLE,
+ svn_dirent_local_style(local_abspath,
+ pool),
+ eol_style_val->data);
+
+ /* We're importing, so translate files with 'native' eol-style to
+ * repository-normal form, not to this platform's native EOL. */
+ if (eol_style == svn_subst_eol_style_native)
+ eol = SVN_SUBST_NATIVE_EOL_STR;
+
+ /* Wrap the working copy stream with a filter to detranslate it. */
+ contents = svn_subst_stream_translated(contents,
+ eol,
+ TRUE /* repair */,
+ keywords,
+ FALSE /* expand */,
+ pool);
+ }
+ }
+
+ /* Send the file's contents to the delta-window handler. */
+ return svn_error_trace(svn_txdelta_send_stream(contents, handler,
+ handler_baton, digest,
+ pool));
+}
+
+
+/* Import file PATH as EDIT_PATH in the repository directory indicated
+ * by DIR_BATON in EDITOR.
+ *
+ * Accumulate file paths and their batons in FILES, which must be
+ * non-null. (These are used to send postfix textdeltas later).
+ *
+ * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON
+ * for each file.
+ *
+ * Use POOL for any temporary allocation.
+ */
+static svn_error_t *
+import_file(const svn_delta_editor_t *editor,
+ void *dir_baton,
+ const char *local_abspath,
+ const char *edit_path,
+ const svn_io_dirent2_t *dirent,
+ import_ctx_t *import_ctx,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ void *file_baton;
+ const char *mimetype = NULL;
+ unsigned char digest[APR_MD5_DIGESTSIZE];
+ const char *text_checksum;
+ apr_hash_t* properties;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_path_check_valid(local_abspath, pool));
+
+ /* Add the file, using the pool from the FILES hash. */
+ SVN_ERR(editor->add_file(edit_path, dir_baton, NULL, SVN_INVALID_REVNUM,
+ pool, &file_baton));
+
+ /* Remember that the repository was modified */
+ import_ctx->repos_changed = TRUE;
+
+ if (! dirent->special)
+ {
+ /* add automatic properties */
+ SVN_ERR(svn_client__get_paths_auto_props(&properties, &mimetype,
+ local_abspath,
+ import_ctx->magic_cookie,
+ import_ctx->autoprops,
+ ctx, pool, pool));
+ }
+ else
+ properties = apr_hash_make(pool);
+
+ if (properties)
+ {
+ for (hi = apr_hash_first(pool, properties); hi; hi = apr_hash_next(hi))
+ {
+ const char *pname = svn__apr_hash_index_key(hi);
+ const svn_string_t *pval = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(editor->change_file_prop(file_baton, pname, pval, pool));
+ }
+ }
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(local_abspath, svn_wc_notify_commit_added,
+ pool);
+ notify->kind = svn_node_file;
+ notify->mime_type = mimetype;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+
+ /* If this is a special file, we need to set the svn:special
+ property and create a temporary detranslated version in order to
+ send to the server. */
+ if (dirent->special)
+ {
+ svn_hash_sets(properties, SVN_PROP_SPECIAL,
+ svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool));
+ SVN_ERR(editor->change_file_prop(file_baton, SVN_PROP_SPECIAL,
+ svn_hash_gets(properties,
+ SVN_PROP_SPECIAL),
+ pool));
+ }
+
+ /* Now, transmit the file contents. */
+ SVN_ERR(send_file_contents(local_abspath, file_baton, editor,
+ properties, digest, pool));
+
+ /* Finally, close the file. */
+ text_checksum =
+ svn_checksum_to_cstring(svn_checksum__from_digest_md5(digest, pool), pool);
+
+ return editor->close_file(file_baton, text_checksum, pool);
+}
+
+
+/* Return in CHILDREN a mapping of basenames to dirents for the importable
+ * children of DIR_ABSPATH. EXCLUDES is a hash of absolute paths to filter
+ * out. IGNORES and GLOBAL_IGNORES, if non-NULL, are lists of basename
+ * patterns to filter out.
+ * FILTER_CALLBACK and FILTER_BATON will be called for each absolute path,
+ * allowing users to further filter the list of returned entries.
+ *
+ * Results are returned in RESULT_POOL; use SCRATCH_POOL for temporary data.*/
+static svn_error_t *
+get_filtered_children(apr_hash_t **children,
+ const char *dir_abspath,
+ apr_hash_t *excludes,
+ apr_array_header_t *ignores,
+ apr_array_header_t *global_ignores,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *dirents;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_io_get_dirents3(&dirents, dir_abspath, TRUE, result_pool,
+ scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *base_name = svn__apr_hash_index_key(hi);
+ const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi);
+ const char *local_abspath;
+
+ svn_pool_clear(iterpool);
+
+ local_abspath = svn_dirent_join(dir_abspath, base_name, iterpool);
+
+ if (svn_wc_is_adm_dir(base_name, iterpool))
+ {
+ /* If someone's trying to import a directory named the same
+ as our administrative directories, that's probably not
+ what they wanted to do. If they are importing a file
+ with that name, something is bound to blow up when they
+ checkout what they've imported. So, just skip items with
+ that name. */
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(svn_dirent_join(local_abspath, base_name,
+ iterpool),
+ svn_wc_notify_skip, iterpool);
+ notify->kind = svn_node_dir;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, iterpool);
+ }
+
+ svn_hash_sets(dirents, base_name, NULL);
+ continue;
+ }
+ /* If this is an excluded path, exclude it. */
+ if (svn_hash_gets(excludes, local_abspath))
+ {
+ svn_hash_sets(dirents, base_name, NULL);
+ continue;
+ }
+
+ if (ignores && svn_wc_match_ignore_list(base_name, ignores, iterpool))
+ {
+ svn_hash_sets(dirents, base_name, NULL);
+ continue;
+ }
+
+ if (global_ignores &&
+ svn_wc_match_ignore_list(base_name, global_ignores, iterpool))
+ {
+ svn_hash_sets(dirents, base_name, NULL);
+ continue;
+ }
+
+ if (filter_callback)
+ {
+ svn_boolean_t filter = FALSE;
+
+ SVN_ERR(filter_callback(filter_baton, &filter, local_abspath,
+ dirent, iterpool));
+
+ if (filter)
+ {
+ svn_hash_sets(dirents, base_name, NULL);
+ continue;
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ *children = dirents;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+import_dir(const svn_delta_editor_t *editor,
+ void *dir_baton,
+ const char *local_abspath,
+ const char *edit_path,
+ svn_depth_t depth,
+ apr_hash_t *excludes,
+ apr_array_header_t *global_ignores,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t ignore_unknown_node_types,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ import_ctx_t *import_ctx,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/* Import the children of DIR_ABSPATH, with other arguments similar to
+ * import_dir(). */
+static svn_error_t *
+import_children(const char *dir_abspath,
+ const char *edit_path,
+ apr_hash_t *dirents,
+ const svn_delta_editor_t *editor,
+ void *dir_baton,
+ svn_depth_t depth,
+ apr_hash_t *excludes,
+ apr_array_header_t *global_ignores,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t ignore_unknown_node_types,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ import_ctx_t *import_ctx,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *sorted_dirents;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ sorted_dirents = svn_sort__hash(dirents, svn_sort_compare_items_lexically,
+ scratch_pool);
+ for (i = 0; i < sorted_dirents->nelts; i++)
+ {
+ const char *this_abspath, *this_edit_path;
+ svn_sort__item_t item = APR_ARRAY_IDX(sorted_dirents, i,
+ svn_sort__item_t);
+ const char *filename = item.key;
+ const svn_io_dirent2_t *dirent = item.value;
+
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ /* Typically, we started importing from ".", in which case
+ edit_path is "". So below, this_path might become "./blah",
+ and this_edit_path might become "blah", for example. */
+ this_abspath = svn_dirent_join(dir_abspath, filename, iterpool);
+ this_edit_path = svn_relpath_join(edit_path, filename, iterpool);
+
+ if (dirent->kind == svn_node_dir && depth >= svn_depth_immediates)
+ {
+ /* Recurse. */
+ svn_depth_t depth_below_here = depth;
+ if (depth == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ SVN_ERR(import_dir(editor, dir_baton, this_abspath,
+ this_edit_path, depth_below_here, excludes,
+ global_ignores, no_ignore, no_autoprops,
+ ignore_unknown_node_types, filter_callback,
+ filter_baton, import_ctx, ctx, iterpool));
+ }
+ else if (dirent->kind == svn_node_file && depth >= svn_depth_files)
+ {
+ SVN_ERR(import_file(editor, dir_baton, this_abspath,
+ this_edit_path, dirent,
+ import_ctx, ctx, iterpool));
+ }
+ else if (dirent->kind != svn_node_dir && dirent->kind != svn_node_file)
+ {
+ if (ignore_unknown_node_types)
+ {
+ /*## warn about it*/
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(this_abspath,
+ svn_wc_notify_skip, iterpool);
+ notify->kind = svn_node_dir;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, iterpool);
+ }
+ }
+ else
+ return svn_error_createf
+ (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("Unknown or unversionable type for '%s'"),
+ svn_dirent_local_style(this_abspath, iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Import directory LOCAL_ABSPATH into the repository directory indicated by
+ * DIR_BATON in EDITOR. EDIT_PATH is the path imported as the root
+ * directory, so all edits are relative to that.
+ *
+ * DEPTH is the depth at this point in the descent (it may be changed
+ * for recursive calls).
+ *
+ * Accumulate file paths and their batons in FILES, which must be
+ * non-null. (These are used to send postfix textdeltas later).
+ *
+ * EXCLUDES is a hash whose keys are absolute paths to exclude from
+ * the import (values are unused).
+ *
+ * GLOBAL_IGNORES is an array of const char * ignore patterns. Any child
+ * of LOCAL_ABSPATH which matches one or more of the patterns is not imported.
+ *
+ * If NO_IGNORE is FALSE, don't import files or directories that match
+ * ignore patterns.
+ *
+ * If FILTER_CALLBACK is not NULL, call it with FILTER_BATON on each to be
+ * imported node below LOCAL_ABSPATH to allow filtering nodes.
+ *
+ * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON for each
+ * directory.
+ *
+ * Use POOL for any temporary allocation. */
+static svn_error_t *
+import_dir(const svn_delta_editor_t *editor,
+ void *dir_baton,
+ const char *local_abspath,
+ const char *edit_path,
+ svn_depth_t depth,
+ apr_hash_t *excludes,
+ apr_array_header_t *global_ignores,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t ignore_unknown_node_types,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ import_ctx_t *import_ctx,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_hash_t *dirents;
+ void *this_dir_baton;
+
+ SVN_ERR(svn_path_check_valid(local_abspath, pool));
+ SVN_ERR(get_filtered_children(&dirents, local_abspath, excludes, NULL,
+ global_ignores, filter_callback,
+ filter_baton, ctx, pool, pool));
+
+ /* Import this directory, but not yet its children. */
+ {
+ /* Add the new subdirectory, getting a descent baton from the editor. */
+ SVN_ERR(editor->add_directory(edit_path, dir_baton, NULL,
+ SVN_INVALID_REVNUM, pool, &this_dir_baton));
+
+ /* Remember that the repository was modified */
+ import_ctx->repos_changed = TRUE;
+
+ /* By notifying before the recursive call below, we display
+ a directory add before displaying adds underneath the
+ directory. To do it the other way around, just move this
+ after the recursive call. */
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(local_abspath, svn_wc_notify_commit_added,
+ pool);
+ notify->kind = svn_node_dir;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+ }
+
+ /* Now import the children recursively. */
+ SVN_ERR(import_children(local_abspath, edit_path, dirents, editor,
+ this_dir_baton, depth, excludes, global_ignores,
+ no_ignore, no_autoprops, ignore_unknown_node_types,
+ filter_callback, filter_baton,
+ import_ctx, ctx, pool));
+
+ /* Finally, close the sub-directory. */
+ SVN_ERR(editor->close_directory(this_dir_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Recursively import PATH to a repository using EDITOR and
+ * EDIT_BATON. PATH can be a file or directory.
+ *
+ * DEPTH is the depth at which to import PATH; it behaves as for
+ * svn_client_import4().
+ *
+ * NEW_ENTRIES is an ordered array of path components that must be
+ * created in the repository (where the ordering direction is
+ * parent-to-child). If PATH is a directory, NEW_ENTRIES may be empty
+ * -- the result is an import which creates as many new entries in the
+ * top repository target directory as there are importable entries in
+ * the top of PATH; but if NEW_ENTRIES is not empty, its last item is
+ * the name of a new subdirectory in the repository to hold the
+ * import. If PATH is a file, NEW_ENTRIES may not be empty, and its
+ * last item is the name used for the file in the repository. If
+ * NEW_ENTRIES contains more than one item, all but the last item are
+ * the names of intermediate directories that are created before the
+ * real import begins. NEW_ENTRIES may NOT be NULL.
+ *
+ * EXCLUDES is a hash whose keys are absolute paths to exclude from
+ * the import (values are unused).
+ *
+ * AUTOPROPS is hash of all config file autoprops and
+ * svn:auto-props inherited by the import target, see the
+ * IMPORT_CTX member of the same name.
+ *
+ * LOCAL_IGNORES is an array of const char * ignore patterns which
+ * correspond to the svn:ignore property (if any) set on the root of the
+ * repository target and thus dictates which immediate children of that
+ * target should be ignored and not imported.
+ *
+ * GLOBAL_IGNORES is an array of const char * ignore patterns which
+ * correspond to the svn:global-ignores properties (if any) set on
+ * the root of the repository target or inherited by it.
+ *
+ * If NO_IGNORE is FALSE, don't import files or directories that match
+ * ignore patterns.
+ *
+ * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON for
+ * each imported path, passing actions svn_wc_notify_commit_added.
+ *
+ * Use POOL for any temporary allocation.
+ *
+ * Note: the repository directory receiving the import was specified
+ * when the editor was fetched. (I.e, when EDITOR->open_root() is
+ * called, it returns a directory baton for that directory, which is
+ * not necessarily the root.)
+ */
+static svn_error_t *
+import(const char *local_abspath,
+ const apr_array_header_t *new_entries,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_depth_t depth,
+ apr_hash_t *excludes,
+ apr_hash_t *autoprops,
+ apr_array_header_t *local_ignores,
+ apr_array_header_t *global_ignores,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t ignore_unknown_node_types,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ void *root_baton;
+ apr_array_header_t *batons = NULL;
+ const char *edit_path = "";
+ import_ctx_t *import_ctx = apr_pcalloc(pool, sizeof(*import_ctx));
+ const svn_io_dirent2_t *dirent;
+
+ import_ctx->autoprops = autoprops;
+ svn_magic__init(&import_ctx->magic_cookie, pool);
+
+ /* Get a root dir baton. We pass an invalid revnum to open_root
+ to mean "base this on the youngest revision". Should we have an
+ SVN_YOUNGEST_REVNUM defined for these purposes? */
+ SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
+ pool, &root_baton));
+
+ /* Import a file or a directory tree. */
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, FALSE,
+ pool, pool));
+
+ /* Make the intermediate directory components necessary for properly
+ rooting our import source tree. */
+ if (new_entries->nelts)
+ {
+ int i;
+
+ batons = apr_array_make(pool, new_entries->nelts, sizeof(void *));
+ for (i = 0; i < new_entries->nelts; i++)
+ {
+ const char *component = APR_ARRAY_IDX(new_entries, i, const char *);
+ edit_path = svn_relpath_join(edit_path, component, pool);
+
+ /* If this is the last path component, and we're importing a
+ file, then this component is the name of the file, not an
+ intermediate directory. */
+ if ((i == new_entries->nelts - 1) && (dirent->kind == svn_node_file))
+ break;
+
+ APR_ARRAY_PUSH(batons, void *) = root_baton;
+ SVN_ERR(editor->add_directory(edit_path,
+ root_baton,
+ NULL, SVN_INVALID_REVNUM,
+ pool, &root_baton));
+
+ /* Remember that the repository was modified */
+ import_ctx->repos_changed = TRUE;
+ }
+ }
+ else if (dirent->kind == svn_node_file)
+ {
+ return svn_error_create
+ (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("New entry name required when importing a file"));
+ }
+
+ /* Note that there is no need to check whether PATH's basename is
+ the same name that we reserve for our administrative
+ subdirectories. It would be strange -- though not illegal -- to
+ import the contents of a directory of that name, because the
+ directory's own name is not part of those contents. Of course,
+ if something underneath it also has our reserved name, then we'll
+ error. */
+
+ if (dirent->kind == svn_node_file)
+ {
+ /* This code path ignores EXCLUDES and FILTER, but they don't make
+ much sense for a single file import anyway. */
+ svn_boolean_t ignores_match = FALSE;
+
+ if (!no_ignore)
+ ignores_match =
+ (svn_wc_match_ignore_list(local_abspath, global_ignores, pool)
+ || svn_wc_match_ignore_list(local_abspath, local_ignores, pool));
+
+ if (!ignores_match)
+ SVN_ERR(import_file(editor, root_baton, local_abspath, edit_path,
+ dirent, import_ctx, ctx, pool));
+ }
+ else if (dirent->kind == svn_node_dir)
+ {
+ apr_hash_t *dirents;
+
+ /* If we are creating a new repository directory path to import to,
+ then we disregard any svn:ignore property. */
+ if (!no_ignore && new_entries->nelts)
+ local_ignores = NULL;
+
+ SVN_ERR(get_filtered_children(&dirents, local_abspath, excludes,
+ local_ignores, global_ignores,
+ filter_callback, filter_baton, ctx,
+ pool, pool));
+
+ SVN_ERR(import_children(local_abspath, edit_path, dirents, editor,
+ root_baton, depth, excludes, global_ignores,
+ no_ignore, no_autoprops,
+ ignore_unknown_node_types, filter_callback,
+ filter_baton, import_ctx, ctx, pool));
+
+ }
+ else if (dirent->kind == svn_node_none
+ || dirent->kind == svn_node_unknown)
+ {
+ return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("'%s' does not exist"),
+ svn_dirent_local_style(local_abspath, pool));
+ }
+
+ /* Close up shop; it's time to go home. */
+ SVN_ERR(editor->close_directory(root_baton, pool));
+ if (batons && batons->nelts)
+ {
+ void **baton;
+ while ((baton = (void **) apr_array_pop(batons)))
+ {
+ SVN_ERR(editor->close_directory(*baton, pool));
+ }
+ }
+
+ if (import_ctx->repos_changed)
+ return editor->close_edit(edit_baton, pool);
+ else
+ return editor->abort_edit(edit_baton, pool);
+}
+
+
+/*** Public Interfaces. ***/
+
+svn_error_t *
+svn_client_import5(const char *path,
+ const char *url,
+ svn_depth_t depth,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t ignore_unknown_node_types,
+ const apr_hash_t *revprop_table,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ const char *log_msg = "";
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ svn_ra_session_t *ra_session;
+ apr_hash_t *excludes = apr_hash_make(scratch_pool);
+ svn_node_kind_t kind;
+ const char *local_abspath;
+ apr_array_header_t *new_entries = apr_array_make(scratch_pool, 4,
+ sizeof(const char *));
+ apr_hash_t *commit_revprops;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *autoprops = NULL;
+ apr_array_header_t *global_ignores;
+ apr_array_header_t *local_ignores_arr;
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+
+ /* Create a new commit item and add it to the array. */
+ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
+ {
+ /* If there's a log message gatherer, create a temporary commit
+ item array solely to help generate the log message. The
+ array is not used for the import itself. */
+ svn_client_commit_item3_t *item;
+ const char *tmp_file;
+ apr_array_header_t *commit_items
+ = apr_array_make(scratch_pool, 1, sizeof(item));
+
+ item = svn_client_commit_item3_create(scratch_pool);
+ item->path = apr_pstrdup(scratch_pool, path);
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+
+ SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
+ ctx, scratch_pool));
+ if (! log_msg)
+ return SVN_NO_ERROR;
+ if (tmp_file)
+ {
+ const char *abs_path;
+ SVN_ERR(svn_dirent_get_absolute(&abs_path, tmp_file, scratch_pool));
+ svn_hash_sets(excludes, abs_path, (void *)1);
+ }
+ }
+
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
+
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
+ ctx, scratch_pool, iterpool));
+
+ /* Figure out all the path components we need to create just to have
+ a place to stick our imported tree. */
+ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
+ iterpool));
+
+ /* We can import into directories, but if a file already exists, that's
+ an error. */
+ if (kind == svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_ENTRY_EXISTS, NULL,
+ _("Path '%s' already exists"), url);
+
+ while (kind == svn_node_none)
+ {
+ const char *dir;
+
+ svn_pool_clear(iterpool);
+
+ svn_uri_split(&url, &dir, url, scratch_pool);
+ APR_ARRAY_PUSH(new_entries, const char *) = dir;
+ SVN_ERR(svn_ra_reparent(ra_session, url, iterpool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
+ iterpool));
+ }
+
+ /* Reverse the order of the components we added to our NEW_ENTRIES array. */
+ svn_sort__array_reverse(new_entries, scratch_pool);
+
+ /* The repository doesn't know about the reserved administrative
+ directory. */
+ if (new_entries->nelts)
+ {
+ const char *last_component
+ = APR_ARRAY_IDX(new_entries, new_entries->nelts - 1, const char *);
+
+ if (svn_wc_is_adm_dir(last_component, scratch_pool))
+ return svn_error_createf
+ (SVN_ERR_CL_ADM_DIR_RESERVED, NULL,
+ _("'%s' is a reserved name and cannot be imported"),
+ svn_dirent_local_style(last_component, scratch_pool));
+ }
+
+ SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
+ log_msg, ctx, scratch_pool));
+
+ /* Fetch RA commit editor. */
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
+ svn_client__get_shim_callbacks(ctx->wc_ctx,
+ NULL, scratch_pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ commit_revprops, commit_callback,
+ commit_baton, NULL, TRUE,
+ scratch_pool));
+
+ /* Get inherited svn:auto-props, svn:global-ignores, and
+ svn:ignores for the location we are importing to. */
+ if (!no_autoprops)
+ SVN_ERR(svn_client__get_all_auto_props(&autoprops, url, ctx,
+ scratch_pool, iterpool));
+ if (no_ignore)
+ {
+ global_ignores = NULL;
+ local_ignores_arr = NULL;
+ }
+ else
+ {
+ svn_opt_revision_t rev;
+ apr_array_header_t *config_ignores;
+ apr_hash_t *local_ignores_hash;
+
+ SVN_ERR(svn_client__get_inherited_ignores(&global_ignores, url, ctx,
+ scratch_pool, iterpool));
+ SVN_ERR(svn_wc_get_default_ignores(&config_ignores, ctx->config,
+ scratch_pool));
+ global_ignores = apr_array_append(scratch_pool, global_ignores,
+ config_ignores);
+
+ rev.kind = svn_opt_revision_head;
+ SVN_ERR(svn_client_propget5(&local_ignores_hash, NULL, SVN_PROP_IGNORE, url,
+ &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
+ scratch_pool, scratch_pool));
+ local_ignores_arr = apr_array_make(scratch_pool, 1, sizeof(const char *));
+
+ if (apr_hash_count(local_ignores_hash))
+ {
+ svn_string_t *propval = svn_hash_gets(local_ignores_hash, url);
+ if (propval)
+ {
+ svn_cstring_split_append(local_ignores_arr, propval->data,
+ "\n\r\t\v ", FALSE, scratch_pool);
+ }
+ }
+ }
+
+ /* If an error occurred during the commit, abort the edit and return
+ the error. We don't even care if the abort itself fails. */
+ if ((err = import(local_abspath, new_entries, editor, edit_baton,
+ depth, excludes, autoprops, local_ignores_arr,
+ global_ignores, no_ignore, no_autoprops,
+ ignore_unknown_node_types, filter_callback,
+ filter_baton, ctx, iterpool)))
+ {
+ return svn_error_compose_create(
+ err,
+ editor->abort_edit(edit_baton, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_client/info.c b/contrib/subversion/subversion/libsvn_client/info.c
new file mode 100644
index 0000000..f49f22e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/info.c
@@ -0,0 +1,402 @@
+/*
+ * info.c: return system-generated metadata about paths or URLs.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#include "client.h"
+#include "svn_client.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_wc.h"
+
+#include "svn_private_config.h"
+#include "private/svn_fspath.h"
+#include "private/svn_wc_private.h"
+
+
+svn_client_info2_t *
+svn_client_info2_dup(const svn_client_info2_t *info,
+ apr_pool_t *pool)
+{
+ svn_client_info2_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info));
+
+ if (new_info->URL)
+ new_info->URL = apr_pstrdup(pool, info->URL);
+ if (new_info->repos_root_URL)
+ new_info->repos_root_URL = apr_pstrdup(pool, info->repos_root_URL);
+ if (new_info->repos_UUID)
+ new_info->repos_UUID = apr_pstrdup(pool, info->repos_UUID);
+ if (info->last_changed_author)
+ new_info->last_changed_author = apr_pstrdup(pool, info->last_changed_author);
+ if (new_info->lock)
+ new_info->lock = svn_lock_dup(info->lock, pool);
+ if (new_info->wc_info)
+ new_info->wc_info = svn_wc_info_dup(info->wc_info, pool);
+ return new_info;
+}
+
+/* Set *INFO to a new info struct built from DIRENT
+ and (possibly NULL) svn_lock_t LOCK, all allocated in POOL.
+ Pointer fields are copied by reference, not dup'd. */
+static svn_error_t *
+build_info_from_dirent(svn_client_info2_t **info,
+ const svn_dirent_t *dirent,
+ svn_lock_t *lock,
+ const svn_client__pathrev_t *pathrev,
+ apr_pool_t *pool)
+{
+ svn_client_info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
+
+ tmpinfo->URL = pathrev->url;
+ tmpinfo->rev = pathrev->rev;
+ tmpinfo->kind = dirent->kind;
+ tmpinfo->repos_UUID = pathrev->repos_uuid;
+ tmpinfo->repos_root_URL = pathrev->repos_root_url;
+ tmpinfo->last_changed_rev = dirent->created_rev;
+ tmpinfo->last_changed_date = dirent->time;
+ tmpinfo->last_changed_author = dirent->last_author;
+ tmpinfo->lock = lock;
+ tmpinfo->size = dirent->size;
+
+ tmpinfo->wc_info = NULL;
+
+ *info = tmpinfo;
+ return SVN_NO_ERROR;
+}
+
+
+/* The dirent fields we care about for our calls to svn_ra_get_dir2. */
+#define DIRENT_FIELDS (SVN_DIRENT_KIND | \
+ SVN_DIRENT_CREATED_REV | \
+ SVN_DIRENT_TIME | \
+ SVN_DIRENT_LAST_AUTHOR)
+
+
+/* Helper func for recursively fetching svn_dirent_t's from a remote
+ directory and pushing them at an info-receiver callback.
+
+ DEPTH is the depth starting at DIR, even though RECEIVER is never
+ invoked on DIR: if DEPTH is svn_depth_immediates, then invoke
+ RECEIVER on all children of DIR, but none of their children; if
+ svn_depth_files, then invoke RECEIVER on file children of DIR but
+ not on subdirectories; if svn_depth_infinity, recurse fully.
+ DIR is a relpath, relative to the root of RA_SESSION.
+*/
+static svn_error_t *
+push_dir_info(svn_ra_session_t *ra_session,
+ const svn_client__pathrev_t *pathrev,
+ const char *dir,
+ svn_client_info_receiver2_t receiver,
+ void *receiver_baton,
+ svn_depth_t depth,
+ svn_client_ctx_t *ctx,
+ apr_hash_t *locks,
+ apr_pool_t *pool)
+{
+ apr_hash_t *tmpdirents;
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL,
+ dir, pathrev->rev, DIRENT_FIELDS, pool));
+
+ for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *path, *fs_path;
+ svn_lock_t *lock;
+ svn_client_info2_t *info;
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_dirent_t *the_ent = svn__apr_hash_index_val(hi);
+ svn_client__pathrev_t *child_pathrev;
+
+ svn_pool_clear(subpool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ path = svn_relpath_join(dir, name, subpool);
+ child_pathrev = svn_client__pathrev_join_relpath(pathrev, name, subpool);
+ fs_path = svn_client__pathrev_fspath(child_pathrev, subpool);
+
+ lock = svn_hash_gets(locks, fs_path);
+
+ SVN_ERR(build_info_from_dirent(&info, the_ent, lock, child_pathrev,
+ subpool));
+
+ if (depth >= svn_depth_immediates
+ || (depth == svn_depth_files && the_ent->kind == svn_node_file))
+ {
+ SVN_ERR(receiver(receiver_baton, path, info, subpool));
+ }
+
+ if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir)
+ {
+ SVN_ERR(push_dir_info(ra_session, child_pathrev, path,
+ receiver, receiver_baton,
+ depth, ctx, locks, subpool));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *SAME_P to TRUE if URL exists in the head of the repository and
+ refers to the same resource as it does in REV, using POOL for
+ temporary allocations. RA_SESSION is an open RA session for URL. */
+static svn_error_t *
+same_resource_in_head(svn_boolean_t *same_p,
+ const char *url,
+ svn_revnum_t rev,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_opt_revision_t start_rev, peg_rev;
+ const char *head_url;
+
+ start_rev.kind = svn_opt_revision_head;
+ peg_rev.kind = svn_opt_revision_number;
+ peg_rev.value.number = rev;
+
+ err = svn_client__repos_locations(&head_url, NULL, NULL, NULL,
+ ra_session,
+ url, &peg_rev,
+ &start_rev, NULL,
+ ctx, pool);
+ if (err &&
+ ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) ||
+ (err->apr_err == SVN_ERR_FS_NOT_FOUND)))
+ {
+ svn_error_clear(err);
+ *same_p = FALSE;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ /* ### Currently, the URLs should always be equal, since we can't
+ ### walk forwards in history. */
+ *same_p = (strcmp(url, head_url) == 0);
+
+ return SVN_NO_ERROR;
+}
+
+/* A baton for wc_info_receiver(), containing the wrapped receiver. */
+typedef struct wc_info_receiver_baton_t
+{
+ svn_client_info_receiver2_t client_receiver_func;
+ void *client_receiver_baton;
+} wc_info_receiver_baton_t;
+
+/* A receiver for WC info, implementing svn_client_info_receiver2_t.
+ * Convert the WC info to client info and pass it to the client info
+ * receiver (BATON->client_receiver_func with BATON->client_receiver_baton). */
+static svn_error_t *
+wc_info_receiver(void *baton,
+ const char *abspath_or_url,
+ const svn_wc__info2_t *wc_info,
+ apr_pool_t *scratch_pool)
+{
+ wc_info_receiver_baton_t *b = baton;
+ svn_client_info2_t client_info;
+
+ /* Make a shallow copy in CLIENT_INFO of the contents of WC_INFO. */
+ client_info.repos_root_URL = wc_info->repos_root_URL;
+ client_info.repos_UUID = wc_info->repos_UUID;
+ client_info.rev = wc_info->rev;
+ client_info.URL = wc_info->URL;
+
+ client_info.kind = wc_info->kind;
+ client_info.size = wc_info->size;
+ client_info.last_changed_rev = wc_info->last_changed_rev;
+ client_info.last_changed_date = wc_info->last_changed_date;
+ client_info.last_changed_author = wc_info->last_changed_author;
+
+ client_info.lock = wc_info->lock;
+
+ client_info.wc_info = wc_info->wc_info;
+
+ return b->client_receiver_func(b->client_receiver_baton,
+ abspath_or_url, &client_info, scratch_pool);
+}
+
+svn_error_t *
+svn_client_info3(const char *abspath_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t fetch_excluded,
+ svn_boolean_t fetch_actual_only,
+ const apr_array_header_t *changelists,
+ svn_client_info_receiver2_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session;
+ svn_client__pathrev_t *pathrev;
+ svn_lock_t *lock;
+ svn_boolean_t related;
+ const char *base_name;
+ svn_dirent_t *the_ent;
+ svn_client_info2_t *info;
+ svn_error_t *err;
+
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_empty;
+
+ if ((revision == NULL
+ || revision->kind == svn_opt_revision_unspecified)
+ && (peg_revision == NULL
+ || peg_revision->kind == svn_opt_revision_unspecified))
+ {
+ /* Do all digging in the working copy. */
+ wc_info_receiver_baton_t b;
+
+ b.client_receiver_func = receiver;
+ b.client_receiver_baton = receiver_baton;
+ return svn_error_trace(
+ svn_wc__get_info(ctx->wc_ctx, abspath_or_url, depth,
+ fetch_excluded, fetch_actual_only, changelists,
+ wc_info_receiver, &b,
+ ctx->cancel_func, ctx->cancel_baton, pool));
+ }
+
+ /* Go repository digging instead. */
+
+ /* Trace rename history (starting at path_or_url@peg_revision) and
+ return RA session to the possibly-renamed URL as it exists in REVISION.
+ The ra_session returned will be anchored on this "final" URL. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &pathrev,
+ abspath_or_url, NULL, peg_revision,
+ revision, ctx, pool));
+
+ svn_uri_split(NULL, &base_name, pathrev->url, pool);
+
+ /* Get the dirent for the URL itself. */
+ SVN_ERR(svn_client__ra_stat_compatible(ra_session, pathrev->rev, &the_ent,
+ DIRENT_FIELDS, ctx, pool));
+
+ if (! the_ent)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' non-existent in revision %ld"),
+ pathrev->url, pathrev->rev);
+
+ /* Check if the URL exists in HEAD and refers to the same resource.
+ In this case, we check the repository for a lock on this URL.
+
+ ### There is a possible race here, since HEAD might have changed since
+ ### we checked it. A solution to this problem could be to do the below
+ ### check in a loop which only terminates if the HEAD revision is the same
+ ### before and after this check. That could, however, lead to a
+ ### starvation situation instead. */
+ SVN_ERR(same_resource_in_head(&related, pathrev->url, pathrev->rev,
+ ra_session, ctx, pool));
+ if (related)
+ {
+ err = svn_ra_get_lock(ra_session, &lock, "", pool);
+
+ /* An old mod_dav_svn will always work; there's nothing wrong with
+ doing a PROPFIND for a property named "DAV:supportedlock". But
+ an old svnserve will error. */
+ if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
+ {
+ svn_error_clear(err);
+ lock = NULL;
+ }
+ else if (err)
+ return svn_error_trace(err);
+ }
+ else
+ lock = NULL;
+
+ /* Push the URL's dirent (and lock) at the callback.*/
+ SVN_ERR(build_info_from_dirent(&info, the_ent, lock, pathrev, pool));
+ SVN_ERR(receiver(receiver_baton, base_name, info, pool));
+
+ /* Possibly recurse, using the original RA session. */
+ if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir))
+ {
+ apr_hash_t *locks;
+
+ if (peg_revision->kind == svn_opt_revision_head)
+ {
+ err = svn_ra_get_locks2(ra_session, &locks, "", depth,
+ pool);
+
+ /* Catch specific errors thrown by old mod_dav_svn or svnserve. */
+ if (err &&
+ (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED
+ || err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))
+ {
+ svn_error_clear(err);
+ locks = apr_hash_make(pool); /* use an empty hash */
+ }
+ else if (err)
+ return svn_error_trace(err);
+ }
+ else
+ locks = apr_hash_make(pool); /* use an empty hash */
+
+ SVN_ERR(push_dir_info(ra_session, pathrev, "",
+ receiver, receiver_baton,
+ depth, ctx, locks, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client_get_wc_root(const char **wcroot_abspath,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_wc__get_wcroot(wcroot_abspath, ctx->wc_ctx, local_abspath,
+ result_pool, scratch_pool);
+}
+
+
+/* NOTE: This function was requested by the TortoiseSVN project. See
+ issue #3927. */
+svn_error_t *
+svn_client_min_max_revisions(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ const char *local_abspath,
+ svn_boolean_t committed,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ return svn_wc__min_max_revisions(min_revision, max_revision, ctx->wc_ctx,
+ local_abspath, committed, scratch_pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/iprops.c b/contrib/subversion/subversion/libsvn_client/iprops.c
new file mode 100644
index 0000000..653ce8c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/iprops.c
@@ -0,0 +1,270 @@
+/*
+ * iprops.c: wrappers around wc inherited property functionality
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_wc.h"
+#include "svn_ra.h"
+#include "svn_props.h"
+#include "svn_path.h"
+
+#include "client.h"
+#include "svn_private_config.h"
+
+#include "private/svn_wc_private.h"
+
+
+/*** Code. ***/
+
+/* Determine if LOCAL_ABSPATH needs an inherited property cache. If it does,
+ then set *NEEDS_CACHE to TRUE, set it to FALSE otherwise. All other args
+ are as per svn_client__get_inheritable_props(). */
+static svn_error_t *
+need_to_cache_iprops(svn_boolean_t *needs_cache,
+ const char *local_abspath,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_wc_root;
+ svn_boolean_t is_switched;
+ svn_error_t *err;
+
+ err = svn_wc_check_root(&is_wc_root, &is_switched, NULL,
+ ctx->wc_ctx, local_abspath,
+ scratch_pool);
+
+ /* LOCAL_ABSPATH doesn't need a cache if it doesn't exist. */
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ is_wc_root = FALSE;
+ is_switched = FALSE;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ /* Starting assumption. */
+ *needs_cache = FALSE;
+
+ if (is_wc_root || is_switched)
+ {
+ const char *session_url;
+ const char *session_root_url;
+
+ /* Looks likely that we need an inherited properties cache...Unless
+ LOCAL_ABSPATH is a WC root that points to the repos root. Then it
+ doesn't need a cache because it has nowhere to inherit from. Check
+ for that case. */
+ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool));
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &session_root_url,
+ scratch_pool));
+
+ if (strcmp(session_root_url, session_url) != 0)
+ *needs_cache = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__iprop_relpaths_to_urls(apr_array_header_t *inherited_props,
+ const char *repos_root_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ /* Convert repos root relpaths to full URLs. */
+ if (! (svn_path_is_url(elt->path_or_url)
+ || svn_dirent_is_absolute(elt->path_or_url)))
+ {
+ elt->path_or_url = svn_path_url_add_component2(repos_root_url,
+ elt->path_or_url,
+ result_pool);
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* The real implementation of svn_client__get_inheritable_props */
+static svn_error_t *
+get_inheritable_props(apr_hash_t **wcroot_iprops,
+ const char *local_abspath,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *iprop_paths;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_pool_t *session_pool = NULL;
+ *wcroot_iprops = apr_hash_make(result_pool);
+
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+
+ /* If we don't have a base revision for LOCAL_ABSPATH then it can't
+ possibly be a working copy root, nor can it contain any WC roots
+ in the form of switched subtrees. So there is nothing to cache. */
+
+ SVN_ERR(svn_wc__get_cached_iprop_children(&iprop_paths, depth,
+ ctx->wc_ctx, local_abspath,
+ scratch_pool, iterpool));
+
+ /* If we are in the midst of a checkout or an update that is bringing in
+ an external, then svn_wc__get_cached_iprop_children won't return
+ LOCAL_ABSPATH in IPROPS_PATHS because the former has no cached iprops
+ yet. So make sure LOCAL_ABSPATH is present if it's a WC root. */
+ if (!svn_hash_gets(iprop_paths, local_abspath))
+ {
+ svn_boolean_t needs_cached_iprops;
+
+ SVN_ERR(need_to_cache_iprops(&needs_cached_iprops, local_abspath,
+ ra_session, ctx, iterpool));
+ if (needs_cached_iprops)
+ {
+ const char *target_abspath = apr_pstrdup(scratch_pool,
+ local_abspath);
+
+ /* As value we set TARGET_ABSPATH, but any string besides ""
+ would do */
+ svn_hash_sets(iprop_paths, target_abspath, target_abspath);
+ }
+ }
+
+ for (hi = apr_hash_first(scratch_pool, iprop_paths);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child_abspath = svn__apr_hash_index_key(hi);
+ const char *child_repos_relpath = svn__apr_hash_index_val(hi);
+ const char *url;
+ apr_array_header_t *inherited_props;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ if (*child_repos_relpath == '\0')
+ {
+ /* A repository root doesn't have inherited properties */
+ continue;
+ }
+
+ SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, child_abspath,
+ iterpool, iterpool));
+ if (ra_session)
+ SVN_ERR(svn_ra_reparent(ra_session, url, scratch_pool));
+ else
+ {
+ if (! session_pool)
+ session_pool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
+ ctx,
+ session_pool, iterpool));
+ }
+
+ err = svn_ra_get_inherited_props(ra_session, &inherited_props,
+ "", revision,
+ result_pool, iterpool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_FS_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ continue;
+ }
+
+ svn_hash_sets(*wcroot_iprops,
+ apr_pstrdup(result_pool, child_abspath),
+ inherited_props);
+ }
+
+
+ svn_pool_destroy(iterpool);
+ if (session_pool)
+ svn_pool_destroy(session_pool);
+
+ return SVN_NO_ERROR;
+
+}
+
+svn_error_t *
+svn_client__get_inheritable_props(apr_hash_t **wcroot_iprops,
+ const char *local_abspath,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *old_session_url;
+ svn_error_t *err;
+
+ if (!SVN_IS_VALID_REVNUM(revision))
+ return SVN_NO_ERROR;
+
+ if (ra_session)
+ SVN_ERR(svn_ra_get_session_url(ra_session, &old_session_url, scratch_pool));
+
+ /* We just wrap a simple helper function, as it is to easy to leave the ra
+ session rooted at some wrong path without a wrapper like this.
+
+ During development we had problems where some now deleted switched path
+ made the update try to update to that url instead of the intended url
+ */
+
+ err = get_inheritable_props(wcroot_iprops, local_abspath, revision, depth,
+ ra_session, ctx, result_pool, scratch_pool);
+
+ if (ra_session)
+ {
+ err = svn_error_compose_create(
+ err,
+ svn_ra_reparent(ra_session, old_session_url, scratch_pool));
+ }
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/list.c b/contrib/subversion/subversion/libsvn_client/list.c
new file mode 100644
index 0000000..4093893
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/list.c
@@ -0,0 +1,579 @@
+/*
+ * list.c: list local and remote directory entries.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_time.h"
+#include "svn_sorts.h"
+#include "svn_props.h"
+
+#include "client.h"
+
+#include "private/svn_fspath.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_wc_private.h"
+#include "svn_private_config.h"
+
+/* Prototypes for referencing before declaration */
+static svn_error_t *
+list_externals(apr_hash_t *externals,
+ svn_depth_t depth,
+ apr_uint32_t dirent_fields,
+ svn_boolean_t fetch_locks,
+ svn_client_list_func2_t list_func,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+list_internal(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ apr_uint32_t dirent_fields,
+ svn_boolean_t fetch_locks,
+ svn_boolean_t include_externals,
+ const char *external_parent_url,
+ const char *external_target,
+ svn_client_list_func2_t list_func,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/* Get the directory entries of DIR at REV (relative to the root of
+ RA_SESSION), getting at least the fields specified by DIRENT_FIELDS.
+ Use the cancellation function/baton of CTX to check for cancellation.
+
+ If DEPTH is svn_depth_empty, return immediately. If DEPTH is
+ svn_depth_files, invoke LIST_FUNC on the file entries with BATON;
+ if svn_depth_immediates, invoke it on file and directory entries;
+ if svn_depth_infinity, invoke it on file and directory entries and
+ recurse into the directory entries with the same depth.
+
+ LOCKS, if non-NULL, is a hash mapping const char * paths to svn_lock_t
+ objects and FS_PATH is the absolute filesystem path of the RA session.
+ Use SCRATCH_POOL for temporary allocations.
+
+ If the caller passes EXTERNALS as non-NULL, populate the EXTERNALS
+ hash table whose keys are URLs of the directory which has externals
+ definitions, and whose values are the externals description text.
+ Allocate the hash's keys and values in RESULT_POOL.
+
+ EXTERNAL_PARENT_URL and EXTERNAL_TARGET are set when external items
+ are listed, otherwise both are set to NULL by the caller.
+*/
+static svn_error_t *
+get_dir_contents(apr_uint32_t dirent_fields,
+ const char *dir,
+ svn_revnum_t rev,
+ svn_ra_session_t *ra_session,
+ apr_hash_t *locks,
+ const char *fs_path,
+ svn_depth_t depth,
+ svn_client_ctx_t *ctx,
+ apr_hash_t *externals,
+ const char *external_parent_url,
+ const char *external_target,
+ svn_client_list_func2_t list_func,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *tmpdirents;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *array;
+ svn_error_t *err;
+ apr_hash_t *prop_hash = NULL;
+ const svn_string_t *prop_val = NULL;
+ int i;
+
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* Get the directory's entries. If externals hash is non-NULL, get its
+ properties also. Ignore any not-authorized errors. */
+ err = svn_ra_get_dir2(ra_session, &tmpdirents, NULL,
+ externals ? &prop_hash : NULL,
+ dir, rev, dirent_fields, scratch_pool);
+
+ if (err && ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED) ||
+ (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN)))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* Filter out svn:externals from all properties hash. */
+ if (prop_hash)
+ prop_val = svn_hash_gets(prop_hash, SVN_PROP_EXTERNALS);
+ if (prop_val)
+ {
+ const char *url;
+
+ SVN_ERR(svn_ra_get_session_url(ra_session, &url, scratch_pool));
+
+ svn_hash_sets(externals,
+ svn_path_url_add_component2(url, dir, result_pool),
+ svn_string_dup(prop_val, result_pool));
+ }
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ /* Sort the hash, so we can call the callback in a "deterministic" order. */
+ array = svn_sort__hash(tmpdirents, svn_sort_compare_items_lexically,
+ scratch_pool);
+ for (i = 0; i < array->nelts; ++i)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(array, i, svn_sort__item_t);
+ const char *path;
+ svn_dirent_t *the_ent = item->value;
+ svn_lock_t *lock;
+
+ svn_pool_clear(iterpool);
+
+ path = svn_relpath_join(dir, item->key, iterpool);
+
+ if (locks)
+ {
+ const char *abs_path = svn_fspath__join(fs_path, path, iterpool);
+ lock = svn_hash_gets(locks, abs_path);
+ }
+ else
+ lock = NULL;
+
+ if (the_ent->kind == svn_node_file
+ || depth == svn_depth_immediates
+ || depth == svn_depth_infinity)
+ SVN_ERR(list_func(baton, path, the_ent, lock, fs_path,
+ external_parent_url, external_target, iterpool));
+
+ /* If externals is non-NULL, populate the externals hash table
+ recursively for all directory entries. */
+ if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir)
+ SVN_ERR(get_dir_contents(dirent_fields, path, rev,
+ ra_session, locks, fs_path, depth, ctx,
+ externals, external_parent_url,
+ external_target, list_func, baton,
+ result_pool, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Like svn_ra_stat() but with a compatibility hack for pre-1.2 svnserve. */
+/* ### Maybe we should move this behavior into the svn_ra_stat wrapper? */
+svn_error_t *
+svn_client__ra_stat_compatible(svn_ra_session_t *ra_session,
+ svn_revnum_t rev,
+ svn_dirent_t **dirent_p,
+ apr_uint32_t dirent_fields,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ err = svn_ra_stat(ra_session, "", rev, dirent_p, pool);
+
+ /* svnserve before 1.2 doesn't support the above, so fall back on
+ a less efficient method. */
+ if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
+ {
+ const char *repos_root_url;
+ const char *session_url;
+ svn_node_kind_t kind;
+ svn_dirent_t *dirent;
+
+ svn_error_clear(err);
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
+ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", rev, &kind, pool));
+
+ if (kind != svn_node_none)
+ {
+ if (strcmp(session_url, repos_root_url) != 0)
+ {
+ svn_ra_session_t *parent_session;
+ apr_hash_t *parent_ents;
+ const char *parent_url, *base_name;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Open another session to the path's parent. This server
+ doesn't support svn_ra_reparent anyway, so don't try it. */
+ svn_uri_split(&parent_url, &base_name, session_url, subpool);
+
+ SVN_ERR(svn_client_open_ra_session2(&parent_session, parent_url,
+ NULL, ctx,
+ subpool, subpool));
+
+ /* Get all parent's entries, no props. */
+ SVN_ERR(svn_ra_get_dir2(parent_session, &parent_ents, NULL,
+ NULL, "", rev, dirent_fields, subpool));
+
+ /* Get the relevant entry. */
+ dirent = svn_hash_gets(parent_ents, base_name);
+
+ if (dirent)
+ *dirent_p = svn_dirent_dup(dirent, pool);
+ else
+ *dirent_p = NULL;
+
+ svn_pool_destroy(subpool); /* Close RA session */
+ }
+ else
+ {
+ /* We can't get the directory entry for the repository root,
+ but we can still get the information we want.
+ The created-rev of the repository root must, by definition,
+ be rev. */
+ dirent = apr_palloc(pool, sizeof(*dirent));
+ dirent->kind = kind;
+ dirent->size = SVN_INVALID_FILESIZE;
+ if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ apr_hash_t *props;
+ SVN_ERR(svn_ra_get_dir2(ra_session, NULL, NULL, &props,
+ "", rev, 0 /* no dirent fields */,
+ pool));
+ dirent->has_props = (apr_hash_count(props) != 0);
+ }
+ dirent->created_rev = rev;
+ if (dirent_fields & (SVN_DIRENT_TIME | SVN_DIRENT_LAST_AUTHOR))
+ {
+ apr_hash_t *props;
+ svn_string_t *val;
+
+ SVN_ERR(svn_ra_rev_proplist(ra_session, rev, &props,
+ pool));
+ val = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
+ if (val)
+ SVN_ERR(svn_time_from_cstring(&dirent->time, val->data,
+ pool));
+ else
+ dirent->time = 0;
+
+ val = svn_hash_gets(props, SVN_PROP_REVISION_AUTHOR);
+ dirent->last_author = val ? val->data : NULL;
+ }
+
+ *dirent_p = dirent;
+ }
+ }
+ else
+ *dirent_p = NULL;
+ }
+ else
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* List the file/directory entries for PATH_OR_URL at REVISION.
+ The actual node revision selected is determined by the path as
+ it exists in PEG_REVISION.
+
+ If DEPTH is svn_depth_infinity, then list all file and directory entries
+ recursively. Else if DEPTH is svn_depth_files, list all files under
+ PATH_OR_URL (if any), but not subdirectories. Else if DEPTH is
+ svn_depth_immediates, list all files and include immediate
+ subdirectories (at svn_depth_empty). Else if DEPTH is
+ svn_depth_empty, just list PATH_OR_URL with none of its entries.
+
+ DIRENT_FIELDS controls which fields in the svn_dirent_t's are
+ filled in. To have them totally filled in use SVN_DIRENT_ALL,
+ otherwise simply bitwise OR together the combination of SVN_DIRENT_*
+ fields you care about.
+
+ If FETCH_LOCKS is TRUE, include locks when reporting directory entries.
+
+ If INCLUDE_EXTERNALS is TRUE, also list all external items
+ reached by recursion. DEPTH value passed to the original list target
+ applies for the externals also. EXTERNAL_PARENT_URL is url of the
+ directory which has the externals definitions. EXTERNAL_TARGET is the
+ target subdirectory of externals definitions.
+
+ Report directory entries by invoking LIST_FUNC/BATON.
+ Pass EXTERNAL_PARENT_URL and EXTERNAL_TARGET to LIST_FUNC when external
+ items are listed, otherwise both are set to NULL.
+
+ Use authentication baton cached in CTX to authenticate against the
+ repository.
+
+ Use POOL for all allocations.
+*/
+static svn_error_t *
+list_internal(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ apr_uint32_t dirent_fields,
+ svn_boolean_t fetch_locks,
+ svn_boolean_t include_externals,
+ const char *external_parent_url,
+ const char *external_target,
+ svn_client_list_func2_t list_func,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session;
+ svn_client__pathrev_t *loc;
+ svn_dirent_t *dirent;
+ const char *fs_path;
+ svn_error_t *err;
+ apr_hash_t *locks;
+ apr_hash_t *externals;
+
+ if (include_externals)
+ externals = apr_hash_make(pool);
+ else
+ externals = NULL;
+
+ /* We use the kind field to determine if we should recurse, so we
+ always need it. */
+ dirent_fields |= SVN_DIRENT_KIND;
+
+ /* Get an RA plugin for this filesystem object. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
+ path_or_url, NULL,
+ peg_revision,
+ revision, ctx, pool));
+
+ fs_path = svn_client__pathrev_fspath(loc, pool);
+
+ SVN_ERR(svn_client__ra_stat_compatible(ra_session, loc->rev, &dirent,
+ dirent_fields, ctx, pool));
+ if (! dirent)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("URL '%s' non-existent in revision %ld"),
+ loc->url, loc->rev);
+
+ /* Maybe get all locks under url. */
+ if (fetch_locks)
+ {
+ /* IMPORTANT: If locks are stored in a more temporary pool, we need
+ to fix store_dirent below to duplicate the locks. */
+ err = svn_ra_get_locks2(ra_session, &locks, "", depth, pool);
+
+ if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
+ {
+ svn_error_clear(err);
+ locks = NULL;
+ }
+ else if (err)
+ return svn_error_trace(err);
+ }
+ else
+ locks = NULL;
+
+ /* Report the dirent for the target. */
+ SVN_ERR(list_func(baton, "", dirent, locks
+ ? (svn_hash_gets(locks, fs_path))
+ : NULL, fs_path, external_parent_url,
+ external_target, pool));
+
+ if (dirent->kind == svn_node_dir
+ && (depth == svn_depth_files
+ || depth == svn_depth_immediates
+ || depth == svn_depth_infinity))
+ SVN_ERR(get_dir_contents(dirent_fields, "", loc->rev, ra_session, locks,
+ fs_path, depth, ctx, externals,
+ external_parent_url, external_target, list_func,
+ baton, pool, pool));
+
+ /* We handle externals after listing entries under path_or_url, so that
+ handling external items (and any errors therefrom) doesn't delay
+ the primary operation. */
+ if (include_externals && apr_hash_count(externals))
+ {
+ /* The 'externals' hash populated by get_dir_contents() is processed
+ here. */
+ SVN_ERR(list_externals(externals, depth, dirent_fields,
+ fetch_locks, list_func, baton,
+ ctx, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+wrap_list_error(const svn_client_ctx_t *ctx,
+ const char *target_abspath,
+ svn_error_t *err,
+ apr_pool_t *scratch_pool)
+{
+ if (err && err->apr_err != SVN_ERR_CANCELLED)
+ {
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notifier = svn_wc_create_notify(
+ target_abspath,
+ svn_wc_notify_failed_external,
+ scratch_pool);
+ notifier->err = err;
+ ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool);
+ }
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return err;
+}
+
+
+/* Walk through all the external items and list them. */
+static svn_error_t *
+list_external_items(apr_array_header_t *external_items,
+ const char *externals_parent_url,
+ svn_depth_t depth,
+ apr_uint32_t dirent_fields,
+ svn_boolean_t fetch_locks,
+ svn_client_list_func2_t list_func,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *externals_parent_repos_root_url;
+ apr_pool_t *iterpool;
+ int i;
+
+ SVN_ERR(svn_client_get_repos_root(&externals_parent_repos_root_url,
+ NULL /* uuid */,
+ externals_parent_url, ctx,
+ scratch_pool, scratch_pool));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < external_items->nelts; i++)
+ {
+ const char *resolved_url;
+
+ svn_wc_external_item2_t *item =
+ APR_ARRAY_IDX(external_items, i, svn_wc_external_item2_t *);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc__resolve_relative_external_url(
+ &resolved_url,
+ item,
+ externals_parent_repos_root_url,
+ externals_parent_url,
+ iterpool, iterpool));
+
+ /* List the external */
+ SVN_ERR(wrap_list_error(ctx, item->target_dir,
+ list_internal(resolved_url,
+ &item->peg_revision,
+ &item->revision,
+ depth, dirent_fields,
+ fetch_locks,
+ TRUE,
+ externals_parent_url,
+ item->target_dir,
+ list_func, baton, ctx,
+ iterpool),
+ iterpool));
+
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* List external items defined on each external in EXTERNALS, a const char *
+ externals_parent_url(url of the directory which has the externals
+ definitions) of all externals mapping to the svn_string_t * externals_desc
+ (externals description text). All other options are the same as those
+ passed to svn_client_list(). */
+static svn_error_t *
+list_externals(apr_hash_t *externals,
+ svn_depth_t depth,
+ apr_uint32_t dirent_fields,
+ svn_boolean_t fetch_locks,
+ svn_client_list_func2_t list_func,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *externals_parent_url = svn__apr_hash_index_key(hi);
+ svn_string_t *externals_desc = svn__apr_hash_index_val(hi);
+ apr_array_header_t *external_items;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc_parse_externals_description3(&external_items,
+ externals_parent_url,
+ externals_desc->data,
+ FALSE, iterpool));
+
+ if (! external_items->nelts)
+ continue;
+
+ SVN_ERR(list_external_items(external_items, externals_parent_url, depth,
+ dirent_fields, fetch_locks, list_func,
+ baton, ctx, iterpool));
+
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client_list3(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ apr_uint32_t dirent_fields,
+ svn_boolean_t fetch_locks,
+ svn_boolean_t include_externals,
+ svn_client_list_func2_t list_func,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+
+ return svn_error_trace(list_internal(path_or_url, peg_revision,
+ revision,
+ depth, dirent_fields,
+ fetch_locks,
+ include_externals,
+ NULL, NULL, list_func,
+ baton, ctx, pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_client/locking_commands.c b/contrib/subversion/subversion/libsvn_client/locking_commands.c
new file mode 100644
index 0000000..c768503
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/locking_commands.c
@@ -0,0 +1,552 @@
+/*
+ * locking_commands.c: Implementation of lock and unlock.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_hash.h"
+#include "client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_xml.h"
+#include "svn_pools.h"
+
+#include "svn_private_config.h"
+#include "private/svn_client_private.h"
+#include "private/svn_wc_private.h"
+
+
+/*** Code. ***/
+
+/* For use with store_locks_callback, below. */
+struct lock_baton
+{
+ const char *base_dir_abspath;
+ apr_hash_t *urls_to_paths;
+ svn_client_ctx_t *ctx;
+ apr_pool_t *pool;
+};
+
+
+/* This callback is called by the ra_layer for each path locked.
+ * BATON is a 'struct lock_baton *', PATH is the path being locked,
+ * and LOCK is the lock itself.
+ *
+ * If BATON->base_dir_abspath is not null, then this function either
+ * stores the LOCK on REL_URL or removes any lock tokens from REL_URL
+ * (depending on whether DO_LOCK is true or false respectively), but
+ * only if RA_ERR is null, or (in the unlock case) is something other
+ * than SVN_ERR_FS_LOCK_OWNER_MISMATCH.
+ *
+ * Implements svn_ra_lock_callback_t.
+ */
+static svn_error_t *
+store_locks_callback(void *baton,
+ const char *rel_url,
+ svn_boolean_t do_lock,
+ const svn_lock_t *lock,
+ svn_error_t *ra_err, apr_pool_t *pool)
+{
+ struct lock_baton *lb = baton;
+ svn_wc_notify_t *notify;
+
+ /* Create the notify struct first, so we can tweak it below. */
+ notify = svn_wc_create_notify(rel_url,
+ do_lock
+ ? (ra_err
+ ? svn_wc_notify_failed_lock
+ : svn_wc_notify_locked)
+ : (ra_err
+ ? svn_wc_notify_failed_unlock
+ : svn_wc_notify_unlocked),
+ pool);
+ notify->lock = lock;
+ notify->err = ra_err;
+
+ if (lb->base_dir_abspath)
+ {
+ char *path = svn_hash_gets(lb->urls_to_paths, rel_url);
+ const char *local_abspath;
+
+ local_abspath = svn_dirent_join(lb->base_dir_abspath, path, pool);
+
+ /* Notify a valid working copy path */
+ notify->path = local_abspath;
+ notify->path_prefix = lb->base_dir_abspath;
+
+ if (do_lock)
+ {
+ if (!ra_err)
+ {
+ SVN_ERR(svn_wc_add_lock2(lb->ctx->wc_ctx, local_abspath, lock,
+ lb->pool));
+ notify->lock_state = svn_wc_notify_lock_state_locked;
+ }
+ else
+ notify->lock_state = svn_wc_notify_lock_state_unchanged;
+ }
+ else /* unlocking */
+ {
+ /* Remove our wc lock token either a) if we got no error, or b) if
+ we got any error except for owner mismatch. Note that the only
+ errors that are handed to this callback will be locking-related
+ errors. */
+
+ if (!ra_err ||
+ (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH)))
+ {
+ SVN_ERR(svn_wc_remove_lock2(lb->ctx->wc_ctx, local_abspath,
+ lb->pool));
+ notify->lock_state = svn_wc_notify_lock_state_unlocked;
+ }
+ else
+ notify->lock_state = svn_wc_notify_lock_state_unchanged;
+ }
+ }
+ else
+ notify->url = rel_url; /* Notify that path is actually a url */
+
+ if (lb->ctx->notify_func2)
+ lb->ctx->notify_func2(lb->ctx->notify_baton2, notify, pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This is a wrapper around svn_uri_condense_targets() and
+ * svn_dirent_condense_targets() (the choice of which is made based on
+ * the value of TARGETS_ARE_URIS) which takes care of the
+ * single-target special case.
+ *
+ * Callers are expected to check for an empty *COMMON_PARENT (which
+ * means, "there was nothing common") for themselves.
+ */
+static svn_error_t *
+condense_targets(const char **common_parent,
+ apr_array_header_t **target_relpaths,
+ const apr_array_header_t *targets,
+ svn_boolean_t targets_are_uris,
+ svn_boolean_t remove_redundancies,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (targets_are_uris)
+ {
+ SVN_ERR(svn_uri_condense_targets(common_parent, target_relpaths,
+ targets, remove_redundancies,
+ result_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_dirent_condense_targets(common_parent, target_relpaths,
+ targets, remove_redundancies,
+ result_pool, scratch_pool));
+ }
+
+ /* svn_*_condense_targets leaves *TARGET_RELPATHS empty if TARGETS only
+ had 1 member, so we special case that. */
+ if (apr_is_empty_array(*target_relpaths))
+ {
+ const char *base_name;
+
+ if (targets_are_uris)
+ {
+ svn_uri_split(common_parent, &base_name,
+ *common_parent, result_pool);
+ }
+ else
+ {
+ svn_dirent_split(common_parent, &base_name,
+ *common_parent, result_pool);
+ }
+ APR_ARRAY_PUSH(*target_relpaths, const char *) = base_name;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Lock info. Used in organize_lock_targets.
+ ### Maybe return this instead of the ugly hashes? */
+struct wc_lock_item_t
+{
+ svn_revnum_t revision;
+ const char *lock_token;
+};
+
+/* Set *COMMON_PARENT_URL to the nearest common parent URL of all TARGETS.
+ * If TARGETS are local paths, then the entry for each path is examined
+ * and *COMMON_PARENT is set to the common parent URL for all the
+ * targets (as opposed to the common local path).
+ *
+ * If there is no common parent, either because the targets are a
+ * mixture of URLs and local paths, or because they simply do not
+ * share a common parent, then return SVN_ERR_UNSUPPORTED_FEATURE.
+ *
+ * DO_LOCK is TRUE for locking TARGETS, and FALSE for unlocking them.
+ * FORCE is TRUE for breaking or stealing locks, and FALSE otherwise.
+ *
+ * Each key stored in *REL_TARGETS_P is a path relative to
+ * *COMMON_PARENT. If TARGETS are local paths, then: if DO_LOCK is
+ * true, the value is a pointer to the corresponding base_revision
+ * (allocated in POOL) for the path, else the value is the lock token
+ * (or "" if no token found in the wc).
+ *
+ * If TARGETS is an array of urls, REL_FS_PATHS_P is set to NULL.
+ * Otherwise each key in REL_FS_PATHS_P is an repository path (relative to
+ * COMMON_PARENT) mapped to the target path for TARGET (relative to
+ * the common parent WC path). working copy targets that they "belong" to.
+ *
+ * If *COMMON_PARENT is a URL, then the values are a pointer to
+ * SVN_INVALID_REVNUM (allocated in pool) if DO_LOCK, else "".
+ *
+ * TARGETS may not be empty.
+ */
+static svn_error_t *
+organize_lock_targets(const char **common_parent_url,
+ const char **base_dir,
+ apr_hash_t **rel_targets_p,
+ apr_hash_t **rel_fs_paths_p,
+ const apr_array_header_t *targets,
+ svn_boolean_t do_lock,
+ svn_boolean_t force,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *common_url = NULL;
+ const char *common_dirent = NULL;
+ apr_hash_t *rel_targets_ret = apr_hash_make(result_pool);
+ apr_hash_t *rel_fs_paths = NULL;
+ apr_array_header_t *rel_targets;
+ apr_hash_t *wc_info = apr_hash_make(scratch_pool);
+ svn_boolean_t url_mode;
+ int i;
+
+ SVN_ERR_ASSERT(targets->nelts);
+ SVN_ERR(svn_client__assert_homogeneous_target_type(targets));
+
+ url_mode = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
+
+ if (url_mode)
+ {
+ svn_revnum_t *invalid_revnum =
+ apr_palloc(result_pool, sizeof(*invalid_revnum));
+
+ *invalid_revnum = SVN_INVALID_REVNUM;
+
+ /* Get the common parent URL and a bunch of relpaths, one per target. */
+ SVN_ERR(condense_targets(&common_url, &rel_targets, targets,
+ TRUE, TRUE, result_pool, scratch_pool));
+ if (! (common_url && *common_url))
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("No common parent found, unable to operate "
+ "on disjoint arguments"));
+
+ /* Create mapping of the target relpaths to either
+ SVN_INVALID_REVNUM (if our caller is locking) or to an empty
+ lock token string (if the caller is unlocking). */
+ for (i = 0; i < rel_targets->nelts; i++)
+ {
+ svn_hash_sets(rel_targets_ret,
+ APR_ARRAY_IDX(rel_targets, i, const char *),
+ do_lock
+ ? (const void *)invalid_revnum
+ : (const void *)"");
+ }
+ }
+ else
+ {
+ apr_array_header_t *rel_urls, *target_urls;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Get the common parent dirent and a bunch of relpaths, one per
+ target. */
+ SVN_ERR(condense_targets(&common_dirent, &rel_targets, targets,
+ FALSE, TRUE, result_pool, scratch_pool));
+ if (! (common_dirent && *common_dirent))
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("No common parent found, unable to operate "
+ "on disjoint arguments"));
+
+ /* Get the URL for each target (which also serves to verify that
+ the dirent targets are sane). */
+ target_urls = apr_array_make(scratch_pool, rel_targets->nelts,
+ sizeof(const char *));
+ for (i = 0; i < rel_targets->nelts; i++)
+ {
+ const char *rel_target;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ const char *target_url;
+ struct wc_lock_item_t *wli;
+ const char *local_abspath;
+ svn_node_kind_t kind;
+
+ svn_pool_clear(iterpool);
+
+ rel_target = APR_ARRAY_IDX(rel_targets, i, const char *);
+ local_abspath = svn_dirent_join(common_dirent, rel_target, scratch_pool);
+ wli = apr_pcalloc(scratch_pool, sizeof(*wli));
+
+ SVN_ERR(svn_wc__node_get_base(&kind, &wli->revision, &repos_relpath,
+ &repos_root_url, NULL,
+ &wli->lock_token,
+ wc_ctx, local_abspath,
+ FALSE /* ignore_enoent */,
+ FALSE /* show_hidden */,
+ result_pool, iterpool));
+
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_WC_NOT_FILE, NULL,
+ _("The node '%s' is not a file"),
+ svn_dirent_local_style(local_abspath,
+ iterpool));
+
+ svn_hash_sets(wc_info, local_abspath, wli);
+
+ target_url = svn_path_url_add_component2(repos_root_url,
+ repos_relpath,
+ scratch_pool);
+
+ APR_ARRAY_PUSH(target_urls, const char *) = target_url;
+ }
+
+ /* Now that we have a bunch of URLs for our dirent targets,
+ condense those into a single common parent URL and a bunch of
+ paths relative to that. */
+ SVN_ERR(condense_targets(&common_url, &rel_urls, target_urls,
+ TRUE, FALSE, result_pool, scratch_pool));
+ if (! (common_url && *common_url))
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Unable to lock/unlock across multiple "
+ "repositories"));
+
+ /* Now we need to create a couple of different hash mappings. */
+ rel_fs_paths = apr_hash_make(result_pool);
+ for (i = 0; i < rel_targets->nelts; i++)
+ {
+ const char *rel_target, *rel_url;
+ const char *local_abspath;
+
+ svn_pool_clear(iterpool);
+
+ /* First, we need to map our REL_URL (which is relative to
+ COMMON_URL) to our REL_TARGET (which is relative to
+ COMMON_DIRENT). */
+ rel_target = APR_ARRAY_IDX(rel_targets, i, const char *);
+ rel_url = APR_ARRAY_IDX(rel_urls, i, const char *);
+ svn_hash_sets(rel_fs_paths, rel_url,
+ apr_pstrdup(result_pool, rel_target));
+
+ /* Then, we map our REL_URL (again) to either the base
+ revision of the dirent target with which it is associated
+ (if our caller is locking) or to a (possible empty) lock
+ token string (if the caller is unlocking). */
+ local_abspath = svn_dirent_join(common_dirent, rel_target, iterpool);
+
+ if (do_lock) /* Lock. */
+ {
+ svn_revnum_t *revnum;
+ struct wc_lock_item_t *wli;
+ revnum = apr_palloc(result_pool, sizeof(* revnum));
+
+ wli = svn_hash_gets(wc_info, local_abspath);
+
+ SVN_ERR_ASSERT(wli != NULL);
+
+ *revnum = wli->revision;
+
+ svn_hash_sets(rel_targets_ret, rel_url, revnum);
+ }
+ else /* Unlock. */
+ {
+ const char *lock_token;
+ struct wc_lock_item_t *wli;
+
+ /* If not forcing the unlock, get the lock token. */
+ if (! force)
+ {
+ wli = svn_hash_gets(wc_info, local_abspath);
+
+ SVN_ERR_ASSERT(wli != NULL);
+
+ if (! wli->lock_token)
+ return svn_error_createf(
+ SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
+ _("'%s' is not locked in this working copy"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ lock_token = wli->lock_token
+ ? apr_pstrdup(result_pool, wli->lock_token)
+ : NULL;
+ }
+ else
+ lock_token = NULL;
+
+ /* If breaking a lock, we shouldn't pass any lock token. */
+ svn_hash_sets(rel_targets_ret, rel_url,
+ lock_token ? lock_token : "");
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ /* Set our return variables. */
+ *common_parent_url = common_url;
+ *base_dir = common_dirent;
+ *rel_targets_p = rel_targets_ret;
+ *rel_fs_paths_p = rel_fs_paths;
+
+ return SVN_NO_ERROR;
+}
+
+/* Fetch lock tokens from the repository for the paths in PATH_TOKENS,
+ setting the values to the fetched tokens, allocated in pool. */
+static svn_error_t *
+fetch_tokens(svn_ra_session_t *ra_session, apr_hash_t *path_tokens,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_lock_t *lock;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_ra_get_lock(ra_session, &lock, path, iterpool));
+
+ if (! lock)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_MISSING_LOCK_TOKEN, NULL,
+ _("'%s' is not locked"), path);
+
+ svn_hash_sets(path_tokens, path, apr_pstrdup(pool, lock->token));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client_lock(const apr_array_header_t *targets,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *base_dir;
+ const char *base_dir_abspath = NULL;
+ const char *common_parent_url;
+ svn_ra_session_t *ra_session;
+ apr_hash_t *path_revs, *urls_to_paths;
+ struct lock_baton cb;
+
+ if (apr_is_empty_array(targets))
+ return SVN_NO_ERROR;
+
+ /* Enforce that the comment be xml-escapable. */
+ if (comment)
+ {
+ if (! svn_xml_is_xml_safe(comment, strlen(comment)))
+ return svn_error_create
+ (SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
+ _("Lock comment contains illegal characters"));
+ }
+
+ SVN_ERR(organize_lock_targets(&common_parent_url, &base_dir, &path_revs,
+ &urls_to_paths, targets, TRUE, steal_lock,
+ ctx->wc_ctx, pool, pool));
+
+ /* Open an RA session to the common parent of TARGETS. */
+ if (base_dir)
+ SVN_ERR(svn_dirent_get_absolute(&base_dir_abspath, base_dir, pool));
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, common_parent_url,
+ base_dir_abspath, ctx, pool, pool));
+
+ cb.base_dir_abspath = base_dir_abspath;
+ cb.urls_to_paths = urls_to_paths;
+ cb.ctx = ctx;
+ cb.pool = pool;
+
+ /* Lock the paths. */
+ SVN_ERR(svn_ra_lock(ra_session, path_revs, comment,
+ steal_lock, store_locks_callback, &cb, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_unlock(const apr_array_header_t *targets,
+ svn_boolean_t break_lock,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *base_dir;
+ const char *base_dir_abspath = NULL;
+ const char *common_parent_url;
+ svn_ra_session_t *ra_session;
+ apr_hash_t *path_tokens, *urls_to_paths;
+ struct lock_baton cb;
+
+ if (apr_is_empty_array(targets))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(organize_lock_targets(&common_parent_url, &base_dir, &path_tokens,
+ &urls_to_paths, targets, FALSE, break_lock,
+ ctx->wc_ctx, pool, pool));
+
+ /* Open an RA session. */
+ if (base_dir)
+ SVN_ERR(svn_dirent_get_absolute(&base_dir_abspath, base_dir, pool));
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, common_parent_url,
+ base_dir_abspath, ctx, pool, pool));
+
+ /* If break_lock is not set, lock tokens are required by the server.
+ If the targets were all URLs, ensure that we provide lock tokens,
+ so the repository will only check that the user owns the
+ locks. */
+ if (! base_dir && !break_lock)
+ SVN_ERR(fetch_tokens(ra_session, path_tokens, pool));
+
+ cb.base_dir_abspath = base_dir_abspath;
+ cb.urls_to_paths = urls_to_paths;
+ cb.ctx = ctx;
+ cb.pool = pool;
+
+ /* Unlock the paths. */
+ SVN_ERR(svn_ra_unlock(ra_session, path_tokens, break_lock,
+ store_locks_callback, &cb, pool));
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_client/log.c b/contrib/subversion/subversion/libsvn_client/log.c
new file mode 100644
index 0000000..ca3edac
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/log.c
@@ -0,0 +1,868 @@
+/*
+ * log.c: return log messages
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include <apr_strings.h>
+#include <apr_pools.h>
+
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_compat.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_sorts.h"
+#include "svn_props.h"
+
+#include "client.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+#include <assert.h>
+
+/*** Getting misc. information ***/
+
+/* The baton for use with copyfrom_info_receiver(). */
+typedef struct copyfrom_info_t
+{
+ svn_boolean_t is_first;
+ const char *path;
+ svn_revnum_t rev;
+ apr_pool_t *pool;
+} copyfrom_info_t;
+
+/* A location segment callback for obtaining the copy source of
+ a node at a path and storing it in *BATON (a struct copyfrom_info_t *).
+ Implements svn_location_segment_receiver_t. */
+static svn_error_t *
+copyfrom_info_receiver(svn_location_segment_t *segment,
+ void *baton,
+ apr_pool_t *pool)
+{
+ copyfrom_info_t *copyfrom_info = baton;
+
+ /* If we've already identified the copy source, there's nothing more
+ to do.
+ ### FIXME: We *should* be able to send */
+ if (copyfrom_info->path)
+ return SVN_NO_ERROR;
+
+ /* If this is the first segment, it's not of interest to us. Otherwise
+ (so long as this segment doesn't represent a history gap), it holds
+ our path's previous location (from which it was last copied). */
+ if (copyfrom_info->is_first)
+ {
+ copyfrom_info->is_first = FALSE;
+ }
+ else if (segment->path)
+ {
+ /* The end of the second non-gap segment is the location copied from. */
+ copyfrom_info->path = apr_pstrdup(copyfrom_info->pool, segment->path);
+ copyfrom_info->rev = segment->range_end;
+
+ /* ### FIXME: We *should* be able to return SVN_ERR_CEASE_INVOCATION
+ ### here so we don't get called anymore. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__get_copy_source(const char **original_repos_relpath,
+ svn_revnum_t *original_revision,
+ const char *path_or_url,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ copyfrom_info_t copyfrom_info = { 0 };
+ apr_pool_t *sesspool = svn_pool_create(scratch_pool);
+ svn_ra_session_t *ra_session;
+ svn_client__pathrev_t *at_loc;
+
+ copyfrom_info.is_first = TRUE;
+ copyfrom_info.path = NULL;
+ copyfrom_info.rev = SVN_INVALID_REVNUM;
+ copyfrom_info.pool = result_pool;
+
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &at_loc,
+ path_or_url, NULL,
+ revision, revision,
+ ctx, sesspool));
+
+ /* Find the copy source. Walk the location segments to find the revision
+ at which this node was created (copied or added). */
+
+ err = svn_ra_get_location_segments(ra_session, "", at_loc->rev, at_loc->rev,
+ SVN_INVALID_REVNUM,
+ copyfrom_info_receiver, &copyfrom_info,
+ scratch_pool);
+
+ svn_pool_destroy(sesspool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
+ err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
+ {
+ /* A locally-added but uncommitted versioned resource won't
+ exist in the repository. */
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+
+ *original_repos_relpath = NULL;
+ *original_revision = SVN_INVALID_REVNUM;
+ }
+ return svn_error_trace(err);
+ }
+
+ *original_repos_relpath = copyfrom_info.path;
+ *original_revision = copyfrom_info.rev;
+ return SVN_NO_ERROR;
+}
+
+
+/* compatibility with pre-1.5 servers, which send only author/date/log
+ *revprops in log entries */
+typedef struct pre_15_receiver_baton_t
+{
+ svn_client_ctx_t *ctx;
+ /* ra session for retrieving revprops from old servers */
+ svn_ra_session_t *ra_session;
+ /* caller's list of requested revprops, receiver, and baton */
+ const char *ra_session_url;
+ apr_pool_t *ra_session_pool;
+ const apr_array_header_t *revprops;
+ svn_log_entry_receiver_t receiver;
+ void *baton;
+} pre_15_receiver_baton_t;
+
+static svn_error_t *
+pre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
+{
+ pre_15_receiver_baton_t *rb = baton;
+
+ if (log_entry->revision == SVN_INVALID_REVNUM)
+ return rb->receiver(rb->baton, log_entry, pool);
+
+ /* If only some revprops are requested, get them one at a time on the
+ second ra connection. If all are requested, get them all with
+ svn_ra_rev_proplist. This avoids getting unrequested revprops (which
+ may be arbitrarily large), but means one round-trip per requested
+ revprop. epg isn't entirely sure which should be optimized for. */
+ if (rb->revprops)
+ {
+ int i;
+ svn_boolean_t want_author, want_date, want_log;
+ want_author = want_date = want_log = FALSE;
+ for (i = 0; i < rb->revprops->nelts; i++)
+ {
+ const char *name = APR_ARRAY_IDX(rb->revprops, i, const char *);
+ svn_string_t *value;
+
+ /* If a standard revprop is requested, we know it is already in
+ log_entry->revprops if available. */
+ if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+ {
+ want_author = TRUE;
+ continue;
+ }
+ if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+ {
+ want_date = TRUE;
+ continue;
+ }
+ if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+ {
+ want_log = TRUE;
+ continue;
+ }
+
+ if (rb->ra_session == NULL)
+ SVN_ERR(svn_client_open_ra_session2(&rb->ra_session,
+ rb->ra_session_url, NULL,
+ rb->ctx, rb->ra_session_pool,
+ pool));
+
+ SVN_ERR(svn_ra_rev_prop(rb->ra_session, log_entry->revision,
+ name, &value, pool));
+ if (log_entry->revprops == NULL)
+ log_entry->revprops = apr_hash_make(pool);
+ svn_hash_sets(log_entry->revprops, name, value);
+ }
+ if (log_entry->revprops)
+ {
+ /* Pre-1.5 servers send the standard revprops unconditionally;
+ clear those the caller doesn't want. */
+ if (!want_author)
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, NULL);
+ if (!want_date)
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, NULL);
+ if (!want_log)
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG, NULL);
+ }
+ }
+ else
+ {
+ if (rb->ra_session == NULL)
+ SVN_ERR(svn_client_open_ra_session2(&rb->ra_session,
+ rb->ra_session_url, NULL,
+ rb->ctx, rb->ra_session_pool,
+ pool));
+
+ SVN_ERR(svn_ra_rev_proplist(rb->ra_session, log_entry->revision,
+ &log_entry->revprops, pool));
+ }
+
+ return rb->receiver(rb->baton, log_entry, pool);
+}
+
+/* limit receiver */
+typedef struct limit_receiver_baton_t
+{
+ int limit;
+ svn_log_entry_receiver_t receiver;
+ void *baton;
+} limit_receiver_baton_t;
+
+static svn_error_t *
+limit_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
+{
+ limit_receiver_baton_t *rb = baton;
+
+ rb->limit--;
+
+ return rb->receiver(rb->baton, log_entry, pool);
+}
+
+/* Resolve the URLs or WC path in TARGETS as per the svn_client_log5 API.
+
+ The limitations on TARGETS specified by svn_client_log5 are enforced here.
+ So TARGETS can only contain a single WC path or a URL and zero or more
+ relative paths -- anything else will raise an error.
+
+ PEG_REVISION, TARGETS, and CTX are as per svn_client_log5.
+
+ If TARGETS contains a single WC path then set *RA_TARGET to the absolute
+ path of that single path if PEG_REVISION is dependent on the working copy
+ (e.g. PREV). Otherwise set *RA_TARGET to the corresponding URL for the
+ single WC path. Set *RELATIVE_TARGETS to an array with a single
+ element "".
+
+ If TARGETS contains only a single URL, then set *RA_TARGET to a copy of
+ that URL and *RELATIVE_TARGETS to an array with a single element "".
+
+ If TARGETS contains a single URL and one or more relative paths, then
+ set *RA_TARGET to a copy of that URL and *RELATIVE_TARGETS to a copy of
+ each relative path after the URL.
+
+ If *PEG_REVISION is svn_opt_revision_unspecified, then *PEG_REVISION is
+ set to svn_opt_revision_head for URLs or svn_opt_revision_working for a
+ WC path.
+
+ *RA_TARGET and *RELATIVE_TARGETS are allocated in RESULT_POOL. */
+static svn_error_t *
+resolve_log_targets(apr_array_header_t **relative_targets,
+ const char **ra_target,
+ svn_opt_revision_t *peg_revision,
+ const apr_array_header_t *targets,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_boolean_t url_targets;
+
+ /* Per svn_client_log5, TARGETS contains either a URL followed by zero or
+ more relative paths, or one working copy path. */
+ const char *url_or_path = APR_ARRAY_IDX(targets, 0, const char *);
+
+ /* svn_client_log5 requires at least one target. */
+ if (targets->nelts == 0)
+ return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("No valid target found"));
+
+ /* Initialize the output array. At a minimum, we need room for one
+ (possibly empty) relpath. Otherwise, we have to hold a relpath
+ for every item in TARGETS except the first. */
+ *relative_targets = apr_array_make(result_pool,
+ MAX(1, targets->nelts - 1),
+ sizeof(const char *));
+
+ if (svn_path_is_url(url_or_path))
+ {
+ /* An unspecified PEG_REVISION for a URL path defaults
+ to svn_opt_revision_head. */
+ if (peg_revision->kind == svn_opt_revision_unspecified)
+ peg_revision->kind = svn_opt_revision_head;
+
+ /* The logic here is this: If we get passed one argument, we assume
+ it is the full URL to a file/dir we want log info for. If we get
+ a URL plus some paths, then we assume that the URL is the base,
+ and that the paths passed are relative to it. */
+ if (targets->nelts > 1)
+ {
+ /* We have some paths, let's use them. Start after the URL. */
+ for (i = 1; i < targets->nelts; i++)
+ {
+ const char *target;
+
+ target = APR_ARRAY_IDX(targets, i, const char *);
+
+ if (svn_path_is_url(target) || svn_dirent_is_absolute(target))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a relative path"),
+ target);
+
+ APR_ARRAY_PUSH(*relative_targets, const char *) =
+ apr_pstrdup(result_pool, target);
+ }
+ }
+ else
+ {
+ /* If we have a single URL, then the session will be rooted at
+ it, so just send an empty string for the paths we are
+ interested in. */
+ APR_ARRAY_PUSH(*relative_targets, const char *) = "";
+ }
+
+ /* Remember that our targets are URLs. */
+ url_targets = TRUE;
+ }
+ else /* WC path target. */
+ {
+ const char *target;
+ const char *target_abspath;
+
+ url_targets = FALSE;
+ if (targets->nelts > 1)
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("When specifying working copy paths, only "
+ "one target may be given"));
+
+ /* An unspecified PEG_REVISION for a working copy path defaults
+ to svn_opt_revision_working. */
+ if (peg_revision->kind == svn_opt_revision_unspecified)
+ peg_revision->kind = svn_opt_revision_working;
+
+ /* Get URLs for each target */
+ target = APR_ARRAY_IDX(targets, 0, const char *);
+
+ SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, scratch_pool));
+ SVN_ERR(svn_wc__node_get_url(&url_or_path, ctx->wc_ctx, target_abspath,
+ scratch_pool, scratch_pool));
+ APR_ARRAY_PUSH(*relative_targets, const char *) = "";
+ }
+
+ /* If this is a revision type that requires access to the working copy,
+ * we use our initial target path to figure out where to root the RA
+ * session, otherwise we use our URL. */
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
+ {
+ if (url_targets)
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("PREV, BASE, or COMMITTED revision "
+ "keywords are invalid for URL"));
+
+ else
+ SVN_ERR(svn_dirent_get_absolute(
+ ra_target, APR_ARRAY_IDX(targets, 0, const char *), result_pool));
+ }
+ else
+ {
+ *ra_target = apr_pstrdup(result_pool, url_or_path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Keep track of oldest and youngest opt revs found.
+
+ If REV is younger than *YOUNGEST_REV, or *YOUNGEST_REV is
+ svn_opt_revision_unspecified, then set *YOUNGEST_REV equal to REV.
+
+ If REV is older than *OLDEST_REV, or *OLDEST_REV is
+ svn_opt_revision_unspecified, then set *OLDEST_REV equal to REV. */
+static void
+find_youngest_and_oldest_revs(svn_revnum_t *youngest_rev,
+ svn_revnum_t *oldest_rev,
+ svn_revnum_t rev)
+{
+ /* Is REV younger than YOUNGEST_REV? */
+ if (! SVN_IS_VALID_REVNUM(*youngest_rev)
+ || rev > *youngest_rev)
+ *youngest_rev = rev;
+
+ if (! SVN_IS_VALID_REVNUM(*oldest_rev)
+ || rev < *oldest_rev)
+ *oldest_rev = rev;
+}
+
+typedef struct rev_range_t
+{
+ svn_revnum_t range_start;
+ svn_revnum_t range_end;
+} rev_range_t;
+
+/* Convert array of svn_opt_revision_t ranges to an array of svn_revnum_t
+ ranges.
+
+ Given a log target URL_OR_ABSPATH@PEG_REV and an array of
+ svn_opt_revision_range_t's OPT_REV_RANGES, resolve the opt revs in
+ OPT_REV_RANGES to svn_revnum_t's and return these in *REVISION_RANGES, an
+ array of rev_range_t *.
+
+ Set *YOUNGEST_REV and *OLDEST_REV to the youngest and oldest revisions
+ found in *REVISION_RANGES.
+
+ If the repository needs to be contacted to resolve svn_opt_revision_date or
+ svn_opt_revision_head revisions, then the session used to do this is
+ RA_SESSION; it must be an open session to any URL in the right repository.
+*/
+static svn_error_t*
+convert_opt_rev_array_to_rev_range_array(
+ apr_array_header_t **revision_ranges,
+ svn_revnum_t *youngest_rev,
+ svn_revnum_t *oldest_rev,
+ svn_ra_session_t *ra_session,
+ const char *url_or_abspath,
+ const apr_array_header_t *opt_rev_ranges,
+ const svn_opt_revision_t *peg_rev,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_revnum_t head_rev = SVN_INVALID_REVNUM;
+
+ /* Initialize the input/output parameters. */
+ *youngest_rev = *oldest_rev = SVN_INVALID_REVNUM;
+
+ /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest
+ and oldest revision range that spans all of OPT_REV_RANGES. */
+ *revision_ranges = apr_array_make(result_pool, opt_rev_ranges->nelts,
+ sizeof(rev_range_t *));
+
+ for (i = 0; i < opt_rev_ranges->nelts; i++)
+ {
+ svn_opt_revision_range_t *range;
+ rev_range_t *rev_range;
+ svn_boolean_t start_same_as_end = FALSE;
+
+ range = APR_ARRAY_IDX(opt_rev_ranges, i, svn_opt_revision_range_t *);
+
+ /* Right now RANGE can be any valid pair of svn_opt_revision_t's. We
+ will now convert all RANGEs in place to the corresponding
+ svn_opt_revision_number kind. */
+ if ((range->start.kind != svn_opt_revision_unspecified)
+ && (range->end.kind == svn_opt_revision_unspecified))
+ {
+ /* If the user specified exactly one revision, then start rev is
+ * set but end is not. We show the log message for just that
+ * revision by making end equal to start.
+ *
+ * Note that if the user requested a single dated revision, then
+ * this will cause the same date to be resolved twice. The
+ * extra code complexity to get around this slight inefficiency
+ * doesn't seem worth it, however. */
+ range->end = range->start;
+ }
+ else if (range->start.kind == svn_opt_revision_unspecified)
+ {
+ /* Default to any specified peg revision. Otherwise, if the
+ * first target is a URL, then we default to HEAD:0. Lastly,
+ * the default is BASE:0 since WC@HEAD may not exist. */
+ if (peg_rev->kind == svn_opt_revision_unspecified)
+ {
+ if (svn_path_is_url(url_or_abspath))
+ range->start.kind = svn_opt_revision_head;
+ else
+ range->start.kind = svn_opt_revision_base;
+ }
+ else
+ range->start = *peg_rev;
+
+ if (range->end.kind == svn_opt_revision_unspecified)
+ {
+ range->end.kind = svn_opt_revision_number;
+ range->end.value.number = 0;
+ }
+ }
+
+ if ((range->start.kind == svn_opt_revision_unspecified)
+ || (range->end.kind == svn_opt_revision_unspecified))
+ {
+ return svn_error_create
+ (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Missing required revision specification"));
+ }
+
+ /* Does RANGE describe a single svn_opt_revision_t? */
+ if (range->start.kind == range->end.kind)
+ {
+ if (range->start.kind == svn_opt_revision_number)
+ {
+ if (range->start.value.number == range->end.value.number)
+ start_same_as_end = TRUE;
+ }
+ else if (range->start.kind == svn_opt_revision_date)
+ {
+ if (range->start.value.date == range->end.value.date)
+ start_same_as_end = TRUE;
+ }
+ else
+ {
+ start_same_as_end = TRUE;
+ }
+ }
+
+ rev_range = apr_palloc(result_pool, sizeof(*rev_range));
+ SVN_ERR(svn_client__get_revision_number(
+ &rev_range->range_start, &head_rev,
+ ctx->wc_ctx, url_or_abspath, ra_session,
+ &range->start, scratch_pool));
+ if (start_same_as_end)
+ rev_range->range_end = rev_range->range_start;
+ else
+ SVN_ERR(svn_client__get_revision_number(
+ &rev_range->range_end, &head_rev,
+ ctx->wc_ctx, url_or_abspath, ra_session,
+ &range->end, scratch_pool));
+
+ /* Possibly update the oldest and youngest revisions requested. */
+ find_youngest_and_oldest_revs(youngest_rev,
+ oldest_rev,
+ rev_range->range_start);
+ find_youngest_and_oldest_revs(youngest_rev,
+ oldest_rev,
+ rev_range->range_end);
+ APR_ARRAY_PUSH(*revision_ranges, rev_range_t *) = rev_range;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static int
+compare_rev_to_segment(const void *key_p,
+ const void *element_p)
+{
+ svn_revnum_t rev =
+ * (svn_revnum_t *)key_p;
+ const svn_location_segment_t *segment =
+ *((const svn_location_segment_t * const *) element_p);
+
+ if (rev < segment->range_start)
+ return -1;
+ else if (rev > segment->range_end)
+ return 1;
+ else
+ return 0;
+}
+
+/* Run svn_ra_get_log2 for PATHS, one or more paths relative to RA_SESSION's
+ common parent, for each revision in REVISION_RANGES, an array of
+ rev_range_t.
+
+ RA_SESSION is an open session pointing to ACTUAL_LOC.
+
+ LOG_SEGMENTS is an array of svn_location_segment_t * items representing the
+ history of PATHS from the oldest to youngest revisions found in
+ REVISION_RANGES.
+
+ The TARGETS, LIMIT, DISCOVER_CHANGED_PATHS, STRICT_NODE_HISTORY,
+ INCLUDE_MERGED_REVISIONS, REVPROPS, REAL_RECEIVER, and REAL_RECEIVER_BATON
+ parameters are all as per the svn_client_log5 API. */
+static svn_error_t *
+run_ra_get_log(apr_array_header_t *revision_ranges,
+ apr_array_header_t *paths,
+ apr_array_header_t *log_segments,
+ svn_client__pathrev_t *actual_loc,
+ svn_ra_session_t *ra_session,
+ /* The following are as per svn_client_log5. */
+ const apr_array_header_t *targets,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t real_receiver,
+ void *real_receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ pre_15_receiver_baton_t rb = {0};
+ apr_pool_t *iterpool;
+ svn_boolean_t has_log_revprops;
+
+ SVN_ERR(svn_ra_has_capability(ra_session, &has_log_revprops,
+ SVN_RA_CAPABILITY_LOG_REVPROPS,
+ scratch_pool));
+
+ if (!has_log_revprops)
+ {
+ /* See above pre-1.5 notes. */
+ rb.ctx = ctx;
+
+ /* Create ra session on first use */
+ rb.ra_session_pool = scratch_pool;
+ rb.ra_session_url = actual_loc->url;
+ }
+
+ /* It's a bit complex to correctly handle the special revision words
+ * such as "BASE", "COMMITTED", and "PREV". For example, if the
+ * user runs
+ *
+ * $ svn log -rCOMMITTED foo.txt bar.c
+ *
+ * which committed rev should be used? The younger of the two? The
+ * first one? Should we just error?
+ *
+ * None of the above, I think. Rather, the committed rev of each
+ * target in turn should be used. This is what most users would
+ * expect, and is the most useful interpretation. Of course, this
+ * goes for the other dynamic (i.e., local) revision words too.
+ *
+ * Note that the code to do this is a bit more complex than a simple
+ * loop, because the user might run
+ *
+ * $ svn log -rCOMMITTED:42 foo.txt bar.c
+ *
+ * in which case we want to avoid recomputing the static revision on
+ * every iteration.
+ *
+ * ### FIXME: However, we can't yet handle multiple wc targets anyway.
+ *
+ * We used to iterate over each target in turn, getting the logs for
+ * the named range. This led to revisions being printed in strange
+ * order or being printed more than once. This is issue 1550.
+ *
+ * In r851673, jpieper blocked multiple wc targets in svn/log-cmd.c,
+ * meaning this block not only doesn't work right in that case, but isn't
+ * even testable that way (svn has no unit test suite; we can only test
+ * via the svn command). So, that check is now moved into this function
+ * (see above).
+ *
+ * kfogel ponders future enhancements in r844260:
+ * I think that's okay behavior, since the sense of the command is
+ * that one wants a particular range of logs for *this* file, then
+ * another range for *that* file, and so on. But we should
+ * probably put some sort of separator header between the log
+ * groups. Of course, libsvn_client can't just print stuff out --
+ * it has to take a callback from the client to do that. So we
+ * need to define that callback interface, then have the command
+ * line client pass one down here.
+ *
+ * epg wonders if the repository could send a unified stream of log
+ * entries if the paths and revisions were passed down.
+ */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < revision_ranges->nelts; i++)
+ {
+ const char *old_session_url;
+ const char *path = APR_ARRAY_IDX(targets, 0, const char *);
+ const char *local_abspath_or_url;
+ rev_range_t *range;
+ limit_receiver_baton_t lb;
+ svn_log_entry_receiver_t passed_receiver;
+ void *passed_receiver_baton;
+ const apr_array_header_t *passed_receiver_revprops;
+ svn_location_segment_t **matching_segment;
+ svn_revnum_t younger_rev;
+
+ svn_pool_clear(iterpool);
+
+ if (!svn_path_is_url(path))
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path,
+ iterpool));
+ else
+ local_abspath_or_url = path;
+
+ range = APR_ARRAY_IDX(revision_ranges, i, rev_range_t *);
+
+ /* Issue #4355: Account for renames spanning requested
+ revision ranges. */
+ younger_rev = MAX(range->range_start, range->range_end);
+ matching_segment = bsearch(&younger_rev, log_segments->elts,
+ log_segments->nelts, log_segments->elt_size,
+ compare_rev_to_segment);
+ SVN_ERR_ASSERT(*matching_segment);
+
+ /* A segment with a NULL path means there is gap in the history.
+ We'll just proceed and let svn_ra_get_log2 fail with a useful
+ error...*/
+ if ((*matching_segment)->path != NULL)
+ {
+ /* ...but if there is history, then we must account for issue
+ #4355 and make sure our RA session is pointing at the correct
+ location. */
+ const char *segment_url = svn_path_url_add_component2(
+ actual_loc->repos_root_url, (*matching_segment)->path,
+ scratch_pool);
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url,
+ ra_session,
+ segment_url,
+ scratch_pool));
+ }
+
+ if (has_log_revprops)
+ {
+ passed_receiver = real_receiver;
+ passed_receiver_baton = real_receiver_baton;
+ passed_receiver_revprops = revprops;
+ }
+ else
+ {
+ rb.revprops = revprops;
+ rb.receiver = real_receiver;
+ rb.baton = real_receiver_baton;
+
+ passed_receiver = pre_15_receiver;
+ passed_receiver_baton = &rb;
+ passed_receiver_revprops = svn_compat_log_revprops_in(iterpool);
+ }
+
+ if (limit && revision_ranges->nelts > 1)
+ {
+ lb.limit = limit;
+ lb.receiver = passed_receiver;
+ lb.baton = passed_receiver_baton;
+
+ passed_receiver = limit_receiver;
+ passed_receiver_baton = &lb;
+ }
+
+ SVN_ERR(svn_ra_get_log2(ra_session,
+ paths,
+ range->range_start,
+ range->range_end,
+ limit,
+ discover_changed_paths,
+ strict_node_history,
+ include_merged_revisions,
+ passed_receiver_revprops,
+ passed_receiver,
+ passed_receiver_baton,
+ iterpool));
+
+ if (limit && revision_ranges->nelts > 1)
+ {
+ limit = lb.limit;
+ if (limit == 0)
+ {
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/*** Public Interface. ***/
+
+svn_error_t *
+svn_client_log5(const apr_array_header_t *targets,
+ const svn_opt_revision_t *peg_revision,
+ const apr_array_header_t *opt_rev_ranges,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t real_receiver,
+ void *real_receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session;
+ const char *old_session_url;
+ const char *ra_target;
+ svn_opt_revision_t youngest_opt_rev;
+ svn_revnum_t youngest_rev;
+ svn_revnum_t oldest_rev;
+ svn_opt_revision_t peg_rev;
+ svn_client__pathrev_t *actual_loc;
+ apr_array_header_t *log_segments;
+ apr_array_header_t *revision_ranges;
+ apr_array_header_t *relative_targets;
+
+ if (opt_rev_ranges->nelts == 0)
+ {
+ return svn_error_create
+ (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Missing required revision specification"));
+ }
+
+ /* Make a copy of PEG_REVISION, we may need to change it to a
+ default value. */
+ peg_rev = *peg_revision;
+
+ SVN_ERR(resolve_log_targets(&relative_targets, &ra_target, &peg_rev,
+ targets, ctx, pool, pool));
+
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &actual_loc,
+ ra_target, NULL, &peg_rev, &peg_rev,
+ ctx, pool));
+
+ /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest
+ and oldest revision range that spans all of OPT_REV_RANGES. */
+ SVN_ERR(convert_opt_rev_array_to_rev_range_array(&revision_ranges,
+ &youngest_rev,
+ &oldest_rev,
+ ra_session,
+ ra_target,
+ opt_rev_ranges, &peg_rev,
+ ctx, pool, pool));
+
+ /* Make ACTUAL_LOC and RA_SESSION point to the youngest operative rev. */
+ youngest_opt_rev.kind = svn_opt_revision_number;
+ youngest_opt_rev.value.number = youngest_rev;
+ SVN_ERR(svn_client__resolve_rev_and_url(&actual_loc, ra_session,
+ ra_target, &peg_rev,
+ &youngest_opt_rev, ctx, pool));
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
+ actual_loc->url, pool));
+
+ /* Get the svn_location_segment_t's representing the requested log ranges. */
+ SVN_ERR(svn_client__repos_location_segments(&log_segments, ra_session,
+ actual_loc->url,
+ actual_loc->rev, /* peg */
+ actual_loc->rev, /* start */
+ oldest_rev, /* end */
+ ctx, pool));
+
+ SVN_ERR(run_ra_get_log(revision_ranges, relative_targets, log_segments,
+ actual_loc, ra_session, targets, limit,
+ discover_changed_paths, strict_node_history,
+ include_merged_revisions, revprops, real_receiver,
+ real_receiver_baton, ctx, pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/merge.c b/contrib/subversion/subversion/libsvn_client/merge.c
new file mode 100644
index 0000000..884d63d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/merge.c
@@ -0,0 +1,12674 @@
+/*
+ * merge.c: merging
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes ***/
+
+#include <assert.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+#include <apr_hash.h>
+#include "svn_types.h"
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_delta.h"
+#include "svn_diff.h"
+#include "svn_mergeinfo.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_io.h"
+#include "svn_utf.h"
+#include "svn_pools.h"
+#include "svn_config.h"
+#include "svn_props.h"
+#include "svn_time.h"
+#include "svn_sorts.h"
+#include "svn_subst.h"
+#include "svn_ra.h"
+#include "client.h"
+#include "mergeinfo.h"
+
+#include "private/svn_opt_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_fspath.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_client_private.h"
+#include "private/svn_subr_private.h"
+
+#include "svn_private_config.h"
+
+
+/*-----------------------------------------------------------------------*/
+
+/* MERGEINFO MERGE SOURCE NORMALIZATION
+ *
+ * Nearly any helper function herein that accepts two URL/revision
+ * pairs (or equivalent struct merge_source_t) expects one of two things
+ * to be true:
+ *
+ * 1. that mergeinfo is not being recorded at all for this
+ * operation, or
+ *
+ * 2. that the pairs represent two locations along a single line
+ * of version history such that there are no copies in the
+ * history of the object between the locations when treating
+ * the oldest of the two locations as non-inclusive. In other
+ * words, if there is a copy at all between them, there is only
+ * one copy and its source was the oldest of the two locations.
+ *
+ * We use svn_ra_get_location_segments() to split a given range of
+ * revisions across an object's history into several which obey these
+ * rules. For example, an extract from the log of Subversion's own
+ * /subversion/tags/1.4.5 directory shows the following copies between
+ * r859500 and r866500 (omitting the '/subversion' prefix for clarity):
+ *
+ * r859598:
+ * A /branches/1.4.x (from /trunk:859597)
+ *
+ * r865417:
+ * A /tags/1.4.4 (from /branches/1.4.x:865262)
+ * # Notice that this copy leaves a gap between 865262 and 865417.
+ *
+ * r866420:
+ * A /branches/1.4.5 (from /tags/1.4.4:866419)
+ *
+ * r866425:
+ * D /branches/1.4.5
+ * A /tags/1.4.5 (from /branches/1.4.5:866424)
+ *
+ * In graphical form:
+ *
+ * 859500 859597 865262 866419 866424 866500
+ * . . . . . .
+ * trunk ------------------------------------------------
+ * \ . . .
+ * branches/1.4.x A-------------------------------------
+ * . \______ . .
+ * . \ . .
+ * tags/1.4.4 . A-----------------------
+ * . . \ .
+ * branches/1.4.5 . . A------D
+ * . . . \.
+ * tags/1.4.5 . . . A---------
+ * . . . .
+ * 859598 865417 866420 866425
+ *
+ * A merge of the difference between r859500 and r866500 of this directory
+ * gets split into sequential merges of the following location pairs.
+ *
+ * 859500 859597 865262 865416 866419 866424 866500
+ * . . . . . . .
+ * trunk (======] . . . . .
+ * . . . . .
+ * trunk ( . . . . .
+ * branches/1.4.x ======] . . . .
+ * . . . .
+ * branches/1.4.x ( . . . .
+ * tags/1.4.4 =============] . .
+ * implicit_src_gap (======] . . .
+ * . . .
+ * tags/1.4.4 ( . .
+ * branches/1.4.5 ======] .
+ * . .
+ * branches/1.4.5 ( .
+ * tags/1.4.5 ======]
+ *
+ * which are represented in merge_source_t as:
+ *
+ * [/trunk:859500, /trunk:859597]
+ * (recorded in svn:mergeinfo as /trunk:859501-859597)
+ *
+ * [/trunk:859597, /branches/1.4.x:865262]
+ * (recorded in svn:mergeinfo as /branches/1.4.x:859598-865262)
+ *
+ * [/branches/1.4.x:865262, /tags/1.4.4@866419]
+ * (recorded in svn:mergeinfo as /tags/1.4.4:865263-866419)
+ * (and there is a gap, the revision range [865262, 865416])
+ *
+ * [/tags/1.4.4@866419, /branches/1.4.5@866424]
+ * (recorded in svn:mergeinfo as /branches/1.4.5:866420-866424)
+ *
+ * [/branches/1.4.5@866424, /tags/1.4.5@866500]
+ * (recorded in svn:mergeinfo as /tags/1.4.5:866425-866500)
+ *
+ * Our helper functions would then operate on one of these location
+ * pairs at a time.
+ */
+
+/* WHICH SVN_CLIENT_MERGE* API DO I WANT?
+ *
+ * libsvn_client has three public merge APIs; they are all wrappers
+ * around the do_merge engine. Which one to use depends on the number
+ * of URLs passed as arguments and whether or not specific merge
+ * ranges (-c/-r) are specified.
+ *
+ * 1 URL 2 URLs
+ * +----+--------------------------------+---------------------+
+ * | -c | mergeinfo-driven | |
+ * | or | cherrypicking | |
+ * | -r | (svn_client_merge_peg) | |
+ * |----+--------------------------------+ |
+ * | | mergeinfo-driven | unsupported |
+ * | | 'cherry harvest', i.e. merge | |
+ * | | all revisions from URL that | |
+ * | no | have not already been merged | |
+ * | -c | (svn_client_merge_peg) | |
+ * | or +--------------------------------+---------------------+
+ * | -r | mergeinfo-driven | mergeinfo-writing |
+ * | | whole-branch | diff-and-apply |
+ * | | heuristic merge | (svn_client_merge) |
+ * | | (svn_client_merge_reintegrate) | |
+ * +----+--------------------------------+---------------------+
+ *
+ *
+ */
+
+/* THE CHILDREN_WITH_MERGEINFO ARRAY
+ *
+ * Many of the helper functions in this file pass around an
+ * apr_array_header_t *CHILDREN_WITH_MERGEINFO. This is a depth first
+ * sorted array filled with svn_client__merge_path_t * describing the
+ * merge target and any of its subtrees which have explicit mergeinfo
+ * or otherwise need special attention during a merge.
+ *
+ * During mergeinfo unaware merges, CHILDREN_WITH_MERGEINFO contains
+ * contains only one element (added by do_mergeinfo_unaware_dir_merge)
+ * describing a contiguous range to be merged to the WC merge target.
+ *
+ * During mergeinfo aware merges CHILDREN_WITH_MERGEINFO is created
+ * by get_mergeinfo_paths() and outside of that function and its helpers
+ * should always meet the criteria dictated in get_mergeinfo_paths()'s doc
+ * string. The elements of CHILDREN_WITH_MERGEINFO should never be NULL.
+ *
+ * For clarification on mergeinfo aware vs. mergeinfo unaware merges, see
+ * the doc string for HONOR_MERGEINFO().
+ */
+
+
+/*-----------------------------------------------------------------------*/
+
+/*** Repos-Diff Editor Callbacks ***/
+
+/* */
+typedef struct merge_source_t
+{
+ /* "left" side URL and revision (inclusive iff youngest) */
+ const svn_client__pathrev_t *loc1;
+
+ /* "right" side URL and revision (inclusive iff youngest) */
+ const svn_client__pathrev_t *loc2;
+
+ /* True iff LOC1 is an ancestor of LOC2 or vice-versa (history-wise). */
+ svn_boolean_t ancestral;
+} merge_source_t;
+
+/* Description of the merge target root node (a WC working node) */
+typedef struct merge_target_t
+{
+ /* Absolute path to the WC node */
+ const char *abspath;
+
+ /* The repository location of the base node of the target WC. If the node
+ * is locally added, then URL & REV are NULL & SVN_INVALID_REVNUM.
+ * REPOS_ROOT_URL and REPOS_UUID are always valid. */
+ svn_client__pathrev_t loc;
+
+} merge_target_t;
+
+typedef struct merge_cmd_baton_t {
+ svn_boolean_t force_delete; /* Delete a file/dir even if modified */
+ svn_boolean_t dry_run;
+ svn_boolean_t record_only; /* Whether to merge only mergeinfo
+ differences. */
+ svn_boolean_t same_repos; /* Whether the merge source repository
+ is the same repository as the
+ target. Defaults to FALSE if DRY_RUN
+ is TRUE.*/
+ svn_boolean_t mergeinfo_capable; /* Whether the merge source server
+ is capable of Merge Tracking. */
+ svn_boolean_t ignore_mergeinfo; /* Don't honor mergeinfo; see
+ doc string of do_merge(). FALSE if
+ MERGE_SOURCE->ancestral is FALSE. */
+ svn_boolean_t diff_ignore_ancestry; /* Diff unrelated nodes as if related; see
+ doc string of do_merge(). FALSE if
+ MERGE_SOURCE->ancestral is FALSE. */
+ svn_boolean_t reintegrate_merge; /* Whether this is a --reintegrate
+ merge or not. */
+ const merge_target_t *target; /* Description of merge target node */
+
+ /* The left and right URLs and revs. The value of this field changes to
+ reflect the merge_source_t *currently* being merged by do_merge(). */
+ merge_source_t merge_source;
+
+ /* Rangelist containing single range which describes the gap, if any,
+ in the natural history of the merge source currently being processed.
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=3432.
+ Updated during each call to do_directory_merge(). May be NULL if there
+ is no gap. */
+ svn_rangelist_t *implicit_src_gap;
+
+ svn_client_ctx_t *ctx; /* Client context for callbacks, etc. */
+
+ /* The list of any paths which remained in conflict after a
+ resolution attempt was made. We track this in-memory, rather
+ than just using WC entry state, since the latter doesn't help us
+ when in dry_run mode.
+ ### And because we only want to resolve conflicts that were
+ generated by this merge, not pre-existing ones? */
+ apr_hash_t *conflicted_paths;
+
+ /* A list of absolute paths which had no explicit mergeinfo prior to the
+ merge but got explicit mergeinfo added by the merge. This is populated
+ by merge_change_props() and is allocated in POOL so it is subject to the
+ lifetime limitations of POOL. Is NULL if no paths are found which
+ meet the criteria or DRY_RUN is true. */
+ apr_hash_t *paths_with_new_mergeinfo;
+
+ /* A list of absolute paths whose mergeinfo doesn't need updating after
+ the merge. This can be caused by the removal of mergeinfo by the merge
+ or by deleting the node itself. This is populated by merge_change_props()
+ and the delete callbacks and is allocated in POOL so it is subject to the
+ lifetime limitations of POOL. Is NULL if no paths are found which
+ meet the criteria or DRY_RUN is true. */
+ apr_hash_t *paths_with_deleted_mergeinfo;
+
+ /* The list of absolute skipped paths, which should be examined and
+ cleared after each invocation of the callback. The paths
+ are absolute. Is NULL if MERGE_B->MERGE_SOURCE->ancestral and
+ MERGE_B->REINTEGRATE_MERGE are both false. */
+ apr_hash_t *skipped_abspaths;
+
+ /* The list of absolute merged paths. Unused if MERGE_B->MERGE_SOURCE->ancestral
+ and MERGE_B->REINTEGRATE_MERGE are both false. */
+ apr_hash_t *merged_abspaths;
+
+ /* A hash of (const char *) absolute WC paths mapped to the same which
+ represent the roots of subtrees added by the merge. */
+ apr_hash_t *added_abspaths;
+
+ /* A list of tree conflict victim absolute paths which may be NULL. */
+ apr_hash_t *tree_conflicted_abspaths;
+
+ /* The diff3_cmd in ctx->config, if any, else null. We could just
+ extract this as needed, but since more than one caller uses it,
+ we just set it up when this baton is created. */
+ const char *diff3_cmd;
+ const apr_array_header_t *merge_options;
+
+ /* RA sessions used throughout a merge operation. Opened/re-parented
+ as needed.
+
+ NOTE: During the actual merge editor drive, RA_SESSION1 is used
+ for the primary editing and RA_SESSION2 for fetching additional
+ information -- as necessary -- from the repository. So during
+ this phase of the merge, you *must not* reparent RA_SESSION1; use
+ (temporarily reparenting if you must) RA_SESSION2 instead. */
+ svn_ra_session_t *ra_session1;
+ svn_ra_session_t *ra_session2;
+
+ /* During the merge, *USE_SLEEP is set to TRUE if a sleep will be required
+ afterwards to ensure timestamp integrity, or unchanged if not. */
+ svn_boolean_t *use_sleep;
+
+ /* Pool which has a lifetime limited to one iteration over a given
+ merge source, i.e. it is cleared on every call to do_directory_merge()
+ or do_file_merge() in do_merge(). */
+ apr_pool_t *pool;
+
+
+ /* State for notify_merge_begin() */
+ struct notify_begin_state_t
+ {
+ /* Cache of which abspath was last notified. */
+ const char *last_abspath;
+
+ /* Reference to the one-and-only CHILDREN_WITH_MERGEINFO (see global
+ comment) or a similar list for single-file-merges */
+ const apr_array_header_t *nodes_with_mergeinfo;
+ } notify_begin;
+
+} merge_cmd_baton_t;
+
+
+/* Return TRUE iff we should be taking account of mergeinfo in deciding what
+ changes to merge, for the merge described by MERGE_B. Specifically, that
+ is if the merge source server is capable of merge tracking, the left-side
+ merge source is an ancestor of the right-side (or vice-versa), the merge
+ source is in the same repository as the merge target, and we are not
+ ignoring mergeinfo. */
+#define HONOR_MERGEINFO(merge_b) ((merge_b)->mergeinfo_capable \
+ && (merge_b)->merge_source.ancestral \
+ && (merge_b)->same_repos \
+ && (! (merge_b)->ignore_mergeinfo))
+
+
+/* Return TRUE iff we should be recording mergeinfo for the merge described
+ by MERGE_B. Specifically, that is if we are honoring mergeinfo and the
+ merge is not a dry run. */
+#define RECORD_MERGEINFO(merge_b) (HONOR_MERGEINFO(merge_b) \
+ && !(merge_b)->dry_run)
+
+
+/*-----------------------------------------------------------------------*/
+
+/*** Utilities ***/
+
+/* Return TRUE iff the session URL of RA_SESSION is equal to URL. Useful in
+ * asserting preconditions. */
+static svn_boolean_t
+session_url_is(svn_ra_session_t *ra_session,
+ const char *url,
+ apr_pool_t *scratch_pool)
+{
+ const char *session_url;
+ svn_error_t *err
+ = svn_ra_get_session_url(ra_session, &session_url, scratch_pool);
+
+ SVN_ERR_ASSERT_NO_RETURN(! err);
+ return strcmp(url, session_url) == 0;
+}
+
+/* Return a new merge_source_t structure, allocated in RESULT_POOL,
+ * initialized with deep copies of LOC1 and LOC2 and ANCESTRAL. */
+static merge_source_t *
+merge_source_create(const svn_client__pathrev_t *loc1,
+ const svn_client__pathrev_t *loc2,
+ svn_boolean_t ancestral,
+ apr_pool_t *result_pool)
+{
+ merge_source_t *s
+ = apr_palloc(result_pool, sizeof(*s));
+
+ s->loc1 = svn_client__pathrev_dup(loc1, result_pool);
+ s->loc2 = svn_client__pathrev_dup(loc2, result_pool);
+ s->ancestral = ancestral;
+ return s;
+}
+
+/* Return a deep copy of SOURCE, allocated in RESULT_POOL. */
+static merge_source_t *
+merge_source_dup(const merge_source_t *source,
+ apr_pool_t *result_pool)
+{
+ merge_source_t *s = apr_palloc(result_pool, sizeof(*s));
+
+ s->loc1 = svn_client__pathrev_dup(source->loc1, result_pool);
+ s->loc2 = svn_client__pathrev_dup(source->loc2, result_pool);
+ s->ancestral = source->ancestral;
+ return s;
+}
+
+/* Return SVN_ERR_UNSUPPORTED_FEATURE if URL is not inside the repository
+ of LOCAL_ABSPATH. Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+check_repos_match(const merge_target_t *target,
+ const char *local_abspath,
+ const char *url,
+ apr_pool_t *scratch_pool)
+{
+ if (!svn_uri__is_ancestor(target->loc.repos_root_url, url))
+ return svn_error_createf(
+ SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("URL '%s' of '%s' is not in repository '%s'"),
+ url, svn_dirent_local_style(local_abspath, scratch_pool),
+ target->loc.repos_root_url);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE iff the repository of LOCATION1 is the same as
+ * that of LOCATION2. If STRICT_URLS is true, the URLs must
+ * match (and the UUIDs, just to be sure), otherwise just the UUIDs must
+ * match and the URLs can differ (a common case is http versus https). */
+static svn_boolean_t
+is_same_repos(const svn_client__pathrev_t *location1,
+ const svn_client__pathrev_t *location2,
+ svn_boolean_t strict_urls)
+{
+ if (strict_urls)
+ return (strcmp(location1->repos_root_url, location2->repos_root_url) == 0
+ && strcmp(location1->repos_uuid, location2->repos_uuid) == 0);
+ else
+ return (strcmp(location1->repos_uuid, location2->repos_uuid) == 0);
+}
+
+/* If the repository identified of LOCATION1 is not the same as that
+ * of LOCATION2, throw a SVN_ERR_CLIENT_UNRELATED_RESOURCES
+ * error mentioning PATH1 and PATH2. For STRICT_URLS, see is_same_repos().
+ */
+static svn_error_t *
+check_same_repos(const svn_client__pathrev_t *location1,
+ const char *path1,
+ const svn_client__pathrev_t *location2,
+ const char *path2,
+ svn_boolean_t strict_urls,
+ apr_pool_t *scratch_pool)
+{
+ if (! is_same_repos(location1, location2, strict_urls))
+ return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+ _("'%s' must be from the same repository as "
+ "'%s'"), path1, path2);
+ return SVN_NO_ERROR;
+}
+
+/* Store LOCAL_ABSPATH in PATH_HASH after duplicating it into the pool
+ containing PATH_HASH. */
+static APR_INLINE void
+store_path(apr_hash_t *path_hash, const char *local_abspath)
+{
+ const char *dup_path = apr_pstrdup(apr_hash_pool_get(path_hash),
+ local_abspath);
+
+ svn_hash_sets(path_hash, dup_path, dup_path);
+}
+
+/* Store LOCAL_ABSPATH in *PATH_HASH_P after duplicating it into the pool
+ containing *PATH_HASH_P. If *PATH_HASH_P is NULL, then first set
+ *PATH_HASH_P to a new hash allocated from POOL. */
+static APR_INLINE void
+alloc_and_store_path(apr_hash_t **path_hash_p,
+ const char *local_abspath,
+ apr_pool_t *pool)
+{
+ if (! *path_hash_p)
+ *path_hash_p = apr_hash_make(pool);
+ store_path(*path_hash_p, local_abspath);
+}
+
+/* Return whether any WC path was put in conflict by the merge
+ operation corresponding to MERGE_B. */
+static APR_INLINE svn_boolean_t
+is_path_conflicted_by_merge(merge_cmd_baton_t *merge_b)
+{
+ return (merge_b->conflicted_paths &&
+ apr_hash_count(merge_b->conflicted_paths) > 0);
+}
+
+/* Return a state indicating whether the WC metadata matches the
+ * node kind on disk of the local path LOCAL_ABSPATH.
+ * Use MERGE_B to determine the dry-run details; particularly, if a dry run
+ * noted that it deleted this path, assume matching node kinds (as if both
+ * kinds were svn_node_none).
+ *
+ * - Return svn_wc_notify_state_inapplicable if the node kind matches.
+ * - Return 'obstructed' if there is a node on disk where none or a
+ * different kind is expected, or if the disk node cannot be read.
+ * - Return 'missing' if there is no node on disk but one is expected.
+ * Also return 'missing' for server-excluded nodes (not here due to
+ * authz or other reasons determined by the server).
+ *
+ * Optionally return a bit more info for interested users.
+ **/
+static svn_error_t *
+perform_obstruction_check(svn_wc_notify_state_t *obstruction_state,
+ svn_boolean_t *deleted,
+ svn_boolean_t *excluded,
+ svn_node_kind_t *kind,
+ svn_depth_t *parent_depth,
+ const merge_cmd_baton_t *merge_b,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_context_t *wc_ctx = merge_b->ctx->wc_ctx;
+ svn_node_kind_t wc_kind;
+ svn_boolean_t check_root;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ *obstruction_state = svn_wc_notify_state_inapplicable;
+
+ if (deleted)
+ *deleted = FALSE;
+ if (kind)
+ *kind = svn_node_none;
+
+ if (kind == NULL)
+ kind = &wc_kind;
+
+ check_root = ! strcmp(local_abspath, merge_b->target->abspath);
+
+ SVN_ERR(svn_wc__check_for_obstructions(obstruction_state,
+ kind,
+ deleted,
+ excluded,
+ parent_depth,
+ wc_ctx, local_abspath,
+ check_root,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Create *LEFT and *RIGHT conflict versions for conflict victim
+ * at VICTIM_ABSPATH, with kind NODE_KIND, using information obtained
+ * from MERGE_SOURCE and TARGET.
+ * Allocate returned conflict versions in RESULT_POOL. */
+static svn_error_t *
+make_conflict_versions(const svn_wc_conflict_version_t **left,
+ const svn_wc_conflict_version_t **right,
+ const char *victim_abspath,
+ svn_node_kind_t node_kind,
+ const merge_source_t *merge_source,
+ const merge_target_t *target,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *child = svn_dirent_skip_ancestor(target->abspath,
+ victim_abspath);
+ const char *left_relpath, *right_relpath;
+
+ SVN_ERR_ASSERT(child != NULL);
+ left_relpath = svn_client__pathrev_relpath(merge_source->loc1,
+ scratch_pool);
+ right_relpath = svn_client__pathrev_relpath(merge_source->loc2,
+ scratch_pool);
+
+ *left = svn_wc_conflict_version_create2(
+ merge_source->loc1->repos_root_url,
+ merge_source->loc1->repos_uuid,
+ svn_relpath_join(left_relpath, child, scratch_pool),
+ merge_source->loc1->rev, node_kind, result_pool);
+
+ *right = svn_wc_conflict_version_create2(
+ merge_source->loc2->repos_root_url,
+ merge_source->loc2->repos_uuid,
+ svn_relpath_join(right_relpath, child, scratch_pool),
+ merge_source->loc2->rev, node_kind, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for filter_self_referential_mergeinfo()
+
+ *MERGEINFO is a non-empty, non-null collection of mergeinfo.
+
+ Remove all mergeinfo from *MERGEINFO that describes revision ranges
+ greater than REVISION. Put a copy of any removed mergeinfo, allocated
+ in POOL, into *YOUNGER_MERGEINFO.
+
+ If no mergeinfo is removed from *MERGEINFO then *YOUNGER_MERGEINFO is set
+ to NULL. If all mergeinfo is removed from *MERGEINFO then *MERGEINFO is
+ set to NULL.
+ */
+static svn_error_t*
+split_mergeinfo_on_revision(svn_mergeinfo_t *younger_mergeinfo,
+ svn_mergeinfo_t *mergeinfo,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ *younger_mergeinfo = NULL;
+ for (hi = apr_hash_first(pool, *mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ int i;
+ const char *merge_source_path = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_merge_range_t *range =
+ APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ if (range->end <= revision)
+ {
+ /* This entirely of this range is as old or older than
+ REVISION, so leave it in *MERGEINFO. */
+ continue;
+ }
+ else
+ {
+ /* Since the rangelists in svn_mergeinfo_t's are sorted in
+ increasing order we know that part or all of *this* range
+ and *all* of the remaining ranges in *RANGELIST are younger
+ than REVISION. Remove the younger rangelists from
+ *MERGEINFO and put them in *YOUNGER_MERGEINFO. */
+ int j;
+ svn_rangelist_t *younger_rangelist =
+ apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
+
+ for (j = i; j < rangelist->nelts; j++)
+ {
+ svn_merge_range_t *younger_range = svn_merge_range_dup(
+ APR_ARRAY_IDX(rangelist, j, svn_merge_range_t *), pool);
+
+ /* REVISION might intersect with the first range where
+ range->end > REVISION. If that is the case then split
+ the current range into two, putting the younger half
+ into *YOUNGER_MERGEINFO and leaving the older half in
+ *MERGEINFO. */
+ if (j == i && range->start + 1 <= revision)
+ younger_range->start = range->end = revision;
+
+ APR_ARRAY_PUSH(younger_rangelist, svn_merge_range_t *) =
+ younger_range;
+ }
+
+ /* So far we've only been manipulating rangelists, now we
+ actually create *YOUNGER_MERGEINFO and then remove the older
+ ranges from *MERGEINFO */
+ if (!(*younger_mergeinfo))
+ *younger_mergeinfo = apr_hash_make(pool);
+ svn_hash_sets(*younger_mergeinfo, merge_source_path,
+ younger_rangelist);
+ SVN_ERR(svn_mergeinfo_remove2(mergeinfo, *younger_mergeinfo,
+ *mergeinfo, TRUE, pool, iterpool));
+ break; /* ...out of for (i = 0; i < rangelist->nelts; i++) */
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Make a copy of PROPCHANGES (array of svn_prop_t) into *TRIMMED_PROPCHANGES,
+ omitting any svn:mergeinfo changes. */
+static svn_error_t *
+omit_mergeinfo_changes(apr_array_header_t **trimmed_propchanges,
+ const apr_array_header_t *propchanges,
+ apr_pool_t *result_pool)
+{
+ int i;
+
+ *trimmed_propchanges = apr_array_make(result_pool,
+ propchanges->nelts,
+ sizeof(svn_prop_t));
+
+ for (i = 0; i < propchanges->nelts; ++i)
+ {
+ const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
+
+ /* If this property is not svn:mergeinfo, then copy it. */
+ if (strcmp(change->name, SVN_PROP_MERGEINFO) != 0)
+ APR_ARRAY_PUSH(*trimmed_propchanges, svn_prop_t) = *change;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for merge_props_changed().
+
+ *PROPS is an array of svn_prop_t structures representing regular properties
+ to be added to the working copy TARGET_ABSPATH.
+
+ The merge source and target are assumed to be in the same repository.
+
+ Filter out mergeinfo property additions to TARGET_ABSPATH when
+ those additions refer to the same line of history as TARGET_ABSPATH as
+ described below.
+
+ Examine the added mergeinfo, looking at each range (or single rev)
+ of each source path. If a source_path/range refers to the same line of
+ history as TARGET_ABSPATH (pegged at its base revision), then filter out
+ that range. If the entire rangelist for a given path is filtered then
+ filter out the path as well.
+
+ RA_SESSION is an open RA session to the repository
+ in which both the source and target live, else RA_SESSION is not used. It
+ may be temporarily reparented as needed by this function.
+
+ Use CTX for any further client operations.
+
+ If any filtering occurs, set outgoing *PROPS to a shallow copy (allocated
+ in POOL) of incoming *PROPS minus the filtered mergeinfo. */
+static svn_error_t *
+filter_self_referential_mergeinfo(apr_array_header_t **props,
+ const char *target_abspath,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *adjusted_props;
+ int i;
+ apr_pool_t *iterpool;
+ svn_boolean_t is_copy;
+ const char *repos_relpath;
+ svn_client__pathrev_t target_base;
+
+ /* If PATH itself has been added there is no need to filter. */
+ SVN_ERR(svn_wc__node_get_origin(&is_copy, &target_base.rev, &repos_relpath,
+ &target_base.repos_root_url,
+ &target_base.repos_uuid, NULL,
+ ctx->wc_ctx, target_abspath, FALSE,
+ pool, pool));
+
+ if (is_copy || !repos_relpath)
+ return SVN_NO_ERROR; /* A copy or a local addition */
+
+ target_base.url = svn_path_url_add_component2(target_base.repos_root_url,
+ repos_relpath, pool);
+
+ adjusted_props = apr_array_make(pool, (*props)->nelts, sizeof(svn_prop_t));
+ iterpool = svn_pool_create(pool);
+ for (i = 0; i < (*props)->nelts; ++i)
+ {
+ svn_prop_t *prop = &APR_ARRAY_IDX((*props), i, svn_prop_t);
+
+ svn_mergeinfo_t mergeinfo, younger_mergeinfo;
+ svn_mergeinfo_t filtered_mergeinfo = NULL;
+ svn_mergeinfo_t filtered_younger_mergeinfo = NULL;
+ svn_error_t *err;
+
+ /* If this property isn't mergeinfo or is NULL valued (i.e. prop removal)
+ or empty mergeinfo it does not require any special handling. There
+ is nothing to filter out of empty mergeinfo and the concept of
+ filtering doesn't apply if we are trying to remove mergeinfo
+ entirely. */
+ if ((strcmp(prop->name, SVN_PROP_MERGEINFO) != 0)
+ || (! prop->value) /* Removal of mergeinfo */
+ || (! prop->value->len)) /* Empty mergeinfo */
+ {
+ APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *prop;
+ continue;
+ }
+
+ svn_pool_clear(iterpool);
+
+ /* Non-empty mergeinfo; filter self-referential mergeinfo out. */
+
+ /* Parse the incoming mergeinfo to allow easier manipulation. */
+ err = svn_mergeinfo_parse(&mergeinfo, prop->value->data, iterpool);
+
+ if (err)
+ {
+ /* Issue #3896: If we can't parse it, we certainly can't
+ filter it. */
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ svn_error_clear(err);
+ APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *prop;
+ continue;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ /* The working copy target PATH is at BASE_REVISION. Divide the
+ incoming mergeinfo into two groups. One where all revision ranges
+ are as old or older than BASE_REVISION and one where all revision
+ ranges are younger.
+
+ Note: You may be wondering why we do this.
+
+ For the incoming mergeinfo "older" than target's base revision we
+ can filter out self-referential mergeinfo efficiently using
+ svn_client__get_history_as_mergeinfo(). We simply look at PATH's
+ natural history as mergeinfo and remove that from any incoming
+ mergeinfo.
+
+ For mergeinfo "younger" than the base revision we can't use
+ svn_ra_get_location_segments() to look into PATH's future
+ history. Instead we must use svn_client__repos_locations() and
+ look at each incoming source/range individually and see if PATH
+ at its base revision and PATH at the start of the incoming range
+ exist on the same line of history. If they do then we can filter
+ out the incoming range. But since we have to do this for each
+ range there is a substantial performance penalty to pay if the
+ incoming ranges are not contiguous, i.e. we call
+ svn_client__repos_locations for each discrete range and incur
+ the cost of a roundtrip communication with the repository. */
+ SVN_ERR(split_mergeinfo_on_revision(&younger_mergeinfo,
+ &mergeinfo,
+ target_base.rev,
+ iterpool));
+
+ /* Filter self-referential mergeinfo from younger_mergeinfo. */
+ if (younger_mergeinfo)
+ {
+ apr_hash_index_t *hi;
+ const char *merge_source_root_url;
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session,
+ &merge_source_root_url, iterpool));
+
+ for (hi = apr_hash_first(iterpool, younger_mergeinfo);
+ hi; hi = apr_hash_next(hi))
+ {
+ int j;
+ const char *source_path = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+ const char *merge_source_url;
+ svn_rangelist_t *adjusted_rangelist =
+ apr_array_make(iterpool, 0, sizeof(svn_merge_range_t *));
+
+ merge_source_url =
+ svn_path_url_add_component2(merge_source_root_url,
+ source_path + 1, iterpool);
+
+ for (j = 0; j < rangelist->nelts; j++)
+ {
+ svn_error_t *err2;
+ svn_client__pathrev_t *start_loc;
+ svn_merge_range_t *range =
+ APR_ARRAY_IDX(rangelist, j, svn_merge_range_t *);
+
+ /* Because the merge source normalization code
+ ensures mergeinfo refers to real locations on
+ the same line of history, there's no need to
+ look at the whole range, just the start. */
+
+ /* Check if PATH@BASE_REVISION exists at
+ RANGE->START on the same line of history.
+ (start+1 because RANGE->start is not inclusive.) */
+ err2 = svn_client__repos_location(&start_loc, ra_session,
+ &target_base,
+ range->start + 1,
+ ctx, iterpool, iterpool);
+ if (err2)
+ {
+ if (err2->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES
+ || err2->apr_err == SVN_ERR_FS_NOT_FOUND
+ || err2->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
+ {
+ /* PATH@BASE_REVISION didn't exist at
+ RANGE->START + 1 or is unrelated to the
+ resource PATH@RANGE->START. Some of the
+ requested revisions may not even exist in
+ the repository; a real possibility since
+ mergeinfo is hand editable. In all of these
+ cases clear and ignore the error and don't
+ do any filtering.
+
+ Note: In this last case it is possible that
+ we will allow self-referential mergeinfo to
+ be applied, but fixing it here is potentially
+ very costly in terms of finding what part of
+ a range is actually valid. Simply allowing
+ the merge to proceed without filtering the
+ offending range seems the least worst
+ option. */
+ svn_error_clear(err2);
+ err2 = NULL;
+ APR_ARRAY_PUSH(adjusted_rangelist,
+ svn_merge_range_t *) = range;
+ }
+ else
+ {
+ return svn_error_trace(err2);
+ }
+ }
+ else
+ {
+ /* PATH@BASE_REVISION exists on the same
+ line of history at RANGE->START and RANGE->END.
+ Now check that PATH@BASE_REVISION's path
+ names at RANGE->START and RANGE->END are the same.
+ If the names are not the same then the mergeinfo
+ describing PATH@RANGE->START through
+ PATH@RANGE->END actually belong to some other
+ line of history and we want to record this
+ mergeinfo, not filter it. */
+ if (strcmp(start_loc->url, merge_source_url) != 0)
+ {
+ APR_ARRAY_PUSH(adjusted_rangelist,
+ svn_merge_range_t *) = range;
+ }
+ }
+ /* else no need to add, this mergeinfo is
+ all on the same line of history. */
+ } /* for (j = 0; j < rangelist->nelts; j++) */
+
+ /* Add any rangelists for source_path that are not
+ self-referential. */
+ if (adjusted_rangelist->nelts)
+ {
+ if (!filtered_younger_mergeinfo)
+ filtered_younger_mergeinfo = apr_hash_make(iterpool);
+ svn_hash_sets(filtered_younger_mergeinfo, source_path,
+ adjusted_rangelist);
+ }
+
+ } /* Iteration over each merge source in younger_mergeinfo. */
+ } /* if (younger_mergeinfo) */
+
+ /* Filter self-referential mergeinfo from "older" mergeinfo. */
+ if (mergeinfo)
+ {
+ svn_mergeinfo_t implicit_mergeinfo;
+
+ SVN_ERR(svn_client__get_history_as_mergeinfo(
+ &implicit_mergeinfo, NULL,
+ &target_base, target_base.rev, SVN_INVALID_REVNUM,
+ ra_session, ctx, iterpool));
+
+ /* Remove PATH's implicit mergeinfo from the incoming mergeinfo. */
+ SVN_ERR(svn_mergeinfo_remove2(&filtered_mergeinfo,
+ implicit_mergeinfo,
+ mergeinfo, TRUE, iterpool, iterpool));
+ }
+
+ /* Combine whatever older and younger filtered mergeinfo exists
+ into filtered_mergeinfo. */
+ if (filtered_mergeinfo && filtered_younger_mergeinfo)
+ SVN_ERR(svn_mergeinfo_merge2(filtered_mergeinfo,
+ filtered_younger_mergeinfo, iterpool,
+ iterpool));
+ else if (filtered_younger_mergeinfo)
+ filtered_mergeinfo = filtered_younger_mergeinfo;
+
+ /* If there is any incoming mergeinfo remaining after filtering
+ then put it in adjusted_props. */
+ if (filtered_mergeinfo && apr_hash_count(filtered_mergeinfo))
+ {
+ /* Convert filtered_mergeinfo to a svn_prop_t and put it
+ back in the array. */
+ svn_string_t *filtered_mergeinfo_str;
+ svn_prop_t *adjusted_prop = apr_pcalloc(pool,
+ sizeof(*adjusted_prop));
+ SVN_ERR(svn_mergeinfo_to_string(&filtered_mergeinfo_str,
+ filtered_mergeinfo,
+ pool));
+ adjusted_prop->name = SVN_PROP_MERGEINFO;
+ adjusted_prop->value = filtered_mergeinfo_str;
+ APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *adjusted_prop;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ *props = adjusted_props;
+ return SVN_NO_ERROR;
+}
+
+/* Prepare a set of property changes PROPCHANGES to be used for a merge
+ operation on LOCAL_ABSPATH.
+
+ Remove all non-regular prop-changes (entry-props and WC-props).
+ Remove all non-mergeinfo prop-changes if it's a record-only merge.
+ Remove self-referential mergeinfo (### in some cases...)
+ Remove foreign-repository mergeinfo (### in some cases...)
+
+ Store the resulting property changes in *PROP_UPDATES.
+ Store information on where mergeinfo is updated in MERGE_B.
+
+ Used for both file and directory property merges. */
+static svn_error_t *
+prepare_merge_props_changed(const apr_array_header_t **prop_updates,
+ const char *local_abspath,
+ const apr_array_header_t *propchanges,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *props;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* We only want to merge "regular" version properties: by
+ definition, 'svn merge' shouldn't touch any data within .svn/ */
+ SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props,
+ result_pool));
+
+ /* If we are only applying mergeinfo changes then we need to do
+ additional filtering of PROPS so it contains only mergeinfo changes. */
+ if (merge_b->record_only && props->nelts)
+ {
+ apr_array_header_t *mergeinfo_props =
+ apr_array_make(result_pool, 1, sizeof(svn_prop_t));
+ int i;
+
+ for (i = 0; i < props->nelts; i++)
+ {
+ svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
+
+ if (strcmp(prop->name, SVN_PROP_MERGEINFO) == 0)
+ {
+ APR_ARRAY_PUSH(mergeinfo_props, svn_prop_t) = *prop;
+ break;
+ }
+ }
+ props = mergeinfo_props;
+ }
+
+ if (props->nelts)
+ {
+ /* Issue #3383: We don't want mergeinfo from a foreign repos.
+
+ If this is a merge from a foreign repository we must strip all
+ incoming mergeinfo (including mergeinfo deletions). */
+ if (! merge_b->same_repos)
+ SVN_ERR(omit_mergeinfo_changes(&props, props, result_pool));
+
+ /* If this is a forward merge then don't add new mergeinfo to
+ PATH that is already part of PATH's own history, see
+ http://svn.haxx.se/dev/archive-2008-09/0006.shtml. If the
+ merge sources are not ancestral then there is no concept of a
+ 'forward' or 'reverse' merge and we filter unconditionally. */
+ if (merge_b->merge_source.loc1->rev < merge_b->merge_source.loc2->rev
+ || !merge_b->merge_source.ancestral)
+ {
+ if (HONOR_MERGEINFO(merge_b) || merge_b->reintegrate_merge)
+ SVN_ERR(filter_self_referential_mergeinfo(&props,
+ local_abspath,
+ merge_b->ra_session2,
+ merge_b->ctx,
+ result_pool));
+ }
+ }
+ *prop_updates = props;
+
+ /* Make a record in BATON if we find a PATH where mergeinfo is added
+ where none existed previously or PATH is having its existing
+ mergeinfo deleted. */
+ if (props->nelts)
+ {
+ int i;
+
+ for (i = 0; i < props->nelts; ++i)
+ {
+ svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
+
+ if (strcmp(prop->name, SVN_PROP_MERGEINFO) == 0)
+ {
+ /* Does LOCAL_ABSPATH have any pristine mergeinfo? */
+ svn_boolean_t has_pristine_mergeinfo = FALSE;
+ apr_hash_t *pristine_props;
+
+ SVN_ERR(svn_wc_get_pristine_props(&pristine_props,
+ merge_b->ctx->wc_ctx,
+ local_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ if (pristine_props
+ && svn_hash_gets(pristine_props, SVN_PROP_MERGEINFO))
+ has_pristine_mergeinfo = TRUE;
+
+ if (!has_pristine_mergeinfo && prop->value)
+ {
+ alloc_and_store_path(&merge_b->paths_with_new_mergeinfo,
+ local_abspath, merge_b->pool);
+ }
+ else if (has_pristine_mergeinfo && !prop->value)
+ {
+ alloc_and_store_path(&merge_b->paths_with_deleted_mergeinfo,
+ local_abspath, merge_b->pool);
+ }
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#define CONFLICT_REASON_NONE ((svn_wc_conflict_reason_t)-1)
+#define CONFLICT_REASON_SKIP ((svn_wc_conflict_reason_t)-2)
+#define CONFLICT_REASON_SKIP_WC ((svn_wc_conflict_reason_t)-3)
+
+/* Baton used for testing trees for being editted while performing tree
+ conflict detection for incoming deletes */
+struct dir_delete_baton_t
+{
+ /* Reference to dir baton of directory that is the root of the deletion */
+ struct merge_dir_baton_t *del_root;
+
+ /* Boolean indicating that some edit is found. Allows avoiding more work */
+ svn_boolean_t found_edit;
+
+ /* A list of paths that are compared. Kept up to date until FOUND_EDIT is
+ set to TRUE */
+ apr_hash_t *compared_abspaths;
+};
+
+/* Baton for the merge_dir_*() functions. Initialized in merge_dir_opened() */
+struct merge_dir_baton_t
+{
+ /* Reference to the parent baton, unless the parent is the anchor, in which
+ case PARENT_BATON is NULL */
+ struct merge_dir_baton_t *parent_baton;
+
+ /* The pool containing this baton. Use for RESULT_POOL for storing in this
+ baton */
+ apr_pool_t *pool;
+
+ /* This directory doesn't have a representation in the working copy, so any
+ operation on it will be skipped and possibly cause a tree conflict on the
+ shadow root */
+ svn_boolean_t shadowed;
+
+ /* This node or one of its descendants received operational changes from the
+ merge. If this node is the shadow root its tree conflict status has been
+ applied */
+ svn_boolean_t edited;
+
+ /* If a tree conflict will be installed once edited, it's reason. If a skip
+ should be produced its reason. Otherwise CONFLICT_REASON_NONE for no tree
+ conflict.
+
+ Special values:
+ CONFLICT_REASON_SKIP:
+ The node will be skipped with content and property state as stored in
+ SKIP_REASON.
+
+ CONFLICT_REASON_SKIP_WC:
+ The node will be skipped as an obstructing working copy.
+ */
+ svn_wc_conflict_reason_t tree_conflict_reason;
+ svn_wc_conflict_action_t tree_conflict_action;
+
+ /* When TREE_CONFLICT_REASON is CONFLICT_REASON_SKIP, the skip state to
+ add to the notification */
+ svn_wc_notify_state_t skip_reason;
+
+ /* TRUE if the node was added by this merge. Otherwise FALSE */
+ svn_boolean_t added;
+ svn_boolean_t add_is_replace; /* Add is second part of replace */
+
+ /* TRUE if we are taking over an existing directory as addition, otherwise
+ FALSE. */
+ svn_boolean_t add_existing;
+
+ /* NULL, or an hashtable mapping const char * local_abspaths to
+ const char *kind mapping, containing deleted nodes that still need a delete
+ notification (which may be a replaced notification if the node is not just
+ deleted) */
+ apr_hash_t *pending_deletes;
+
+ /* NULL, or an hashtable mapping const char * LOCAL_ABSPATHs to
+ a const svn_wc_conflict_description2_t * instance, describing the just
+ installed conflict */
+ apr_hash_t *new_tree_conflicts;
+
+ /* If not NULL, a reference to the information of the delete test that is
+ currently in progress. Allocated in the root-directory baton, referenced
+ from all descendants */
+ struct dir_delete_baton_t *delete_state;
+};
+
+/* Baton for the merge_dir_*() functions. Initialized in merge_file_opened() */
+struct merge_file_baton_t
+{
+ /* Reference to the parent baton, unless the parent is the anchor, in which
+ case PARENT_BATON is NULL */
+ struct merge_dir_baton_t *parent_baton;
+
+ /* This file doesn't have a representation in the working copy, so any
+ operation on it will be skipped and possibly cause a tree conflict
+ on the shadow root */
+ svn_boolean_t shadowed;
+
+ /* This node received operational changes from the merge. If this node
+ is the shadow root its tree conflict status has been applied */
+ svn_boolean_t edited;
+
+ /* If a tree conflict will be installed once edited, it's reason. If a skip
+ should be produced its reason. Some special values are defined. See the
+ merge_tree_baton_t for an explanation. */
+ svn_wc_conflict_reason_t tree_conflict_reason;
+ svn_wc_conflict_action_t tree_conflict_action;
+
+ /* When TREE_CONFLICT_REASON is CONFLICT_REASON_SKIP, the skip state to
+ add to the notification */
+ svn_wc_notify_state_t skip_reason;
+
+ /* TRUE if the node was added by this merge. Otherwise FALSE */
+ svn_boolean_t added;
+ svn_boolean_t add_is_replace; /* Add is second part of replace */
+};
+
+/* Forward declaration */
+static svn_error_t *
+notify_merge_begin(merge_cmd_baton_t *merge_b,
+ const char *local_abspath,
+ svn_boolean_t delete_action,
+ apr_pool_t *scratch_pool);
+
+/* Record the skip for future processing and (later) produce the
+ skip notification */
+static svn_error_t *
+record_skip(merge_cmd_baton_t *merge_b,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ svn_wc_notify_action_t action,
+ svn_wc_notify_state_t state,
+ apr_pool_t *scratch_pool)
+{
+ if (merge_b->record_only)
+ return SVN_NO_ERROR; /* ### Why? - Legacy compatibility */
+
+ if (merge_b->merge_source.ancestral
+ || merge_b->reintegrate_merge)
+ {
+ store_path(merge_b->skipped_abspaths, local_abspath);
+ }
+
+ if (merge_b->ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool));
+
+ notify = svn_wc_create_notify(local_abspath, action, scratch_pool);
+ notify->kind = kind;
+ notify->content_state = notify->prop_state = state;
+
+ (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify,
+ scratch_pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Record a tree conflict in the WC, unless this is a dry run or a record-
+ * only merge, or if a tree conflict is already flagged for the VICTIM_PATH.
+ * (The latter can happen if a merge-tracking-aware merge is doing multiple
+ * editor drives because of a gap in the range of eligible revisions.)
+ *
+ * The tree conflict, with its victim specified by VICTIM_PATH, is
+ * assumed to have happened during a merge using merge baton MERGE_B.
+ *
+ * NODE_KIND must be the node kind of "old" and "theirs" and "mine";
+ * this function cannot cope with node kind clashes.
+ * ACTION and REASON correspond to the fields
+ * of the same names in svn_wc_tree_conflict_description_t.
+ */
+static svn_error_t *
+record_tree_conflict(merge_cmd_baton_t *merge_b,
+ const char *local_abspath,
+ struct merge_dir_baton_t *parent_baton,
+ svn_node_kind_t node_kind,
+ svn_wc_conflict_action_t action,
+ svn_wc_conflict_reason_t reason,
+ const svn_wc_conflict_description2_t *existing_conflict,
+ svn_boolean_t notify_tc,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_context_t *wc_ctx = merge_b->ctx->wc_ctx;
+
+ if (merge_b->merge_source.ancestral
+ || merge_b->reintegrate_merge)
+ {
+ store_path(merge_b->tree_conflicted_abspaths, local_abspath);
+ }
+
+ alloc_and_store_path(&merge_b->conflicted_paths, local_abspath,
+ merge_b->pool);
+
+
+ if (!merge_b->record_only && !merge_b->dry_run)
+ {
+ svn_wc_conflict_description2_t *conflict;
+ const svn_wc_conflict_version_t *left;
+ const svn_wc_conflict_version_t *right;
+ apr_pool_t *result_pool = parent_baton ? parent_baton->pool
+ : scratch_pool;
+
+ if (reason == svn_wc_conflict_reason_deleted)
+ {
+ const char *moved_to_abspath;
+
+ SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
+ wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (moved_to_abspath)
+ {
+ /* Local abspath itself has been moved away. If only a
+ descendant is moved away, we call the node itself deleted */
+ reason = svn_wc_conflict_reason_moved_away;
+ }
+ }
+ else if (reason == svn_wc_conflict_reason_added)
+ {
+ const char *moved_from_abspath;
+ SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
+ wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ if (moved_from_abspath)
+ reason = svn_wc_conflict_reason_moved_here;
+ }
+
+ SVN_ERR(make_conflict_versions(&left, &right, local_abspath, node_kind,
+ &merge_b->merge_source, merge_b->target,
+ result_pool, scratch_pool));
+
+ /* Fix up delete of file, add of dir replacement (or other way around) */
+ if (existing_conflict != NULL && existing_conflict->src_left_version)
+ left = existing_conflict->src_left_version;
+
+ conflict = svn_wc_conflict_description_create_tree2(
+ local_abspath, node_kind, svn_wc_operation_merge,
+ left, right, result_pool);
+
+ conflict->action = action;
+ conflict->reason = reason;
+
+ /* May return SVN_ERR_WC_PATH_UNEXPECTED_STATUS */
+ if (existing_conflict)
+ SVN_ERR(svn_wc__del_tree_conflict(wc_ctx, local_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__add_tree_conflict(merge_b->ctx->wc_ctx, conflict,
+ scratch_pool));
+
+ if (parent_baton)
+ {
+ if (! parent_baton->new_tree_conflicts)
+ parent_baton->new_tree_conflicts = apr_hash_make(result_pool);
+
+ svn_hash_sets(parent_baton->new_tree_conflicts,
+ apr_pstrdup(result_pool, local_abspath),
+ conflict);
+ }
+
+ /* ### TODO: Store in parent baton */
+ }
+
+ /* On a replacement we currently get two tree conflicts */
+ if (merge_b->ctx->notify_func2 && notify_tc)
+ {
+ svn_wc_notify_t *notify;
+
+ SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool));
+
+ notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict,
+ scratch_pool);
+ notify->kind = node_kind;
+
+ (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Record the add for future processing and produce the
+ update_add notification
+ */
+static svn_error_t *
+record_update_add(merge_cmd_baton_t *merge_b,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ svn_boolean_t notify_replaced,
+ apr_pool_t *scratch_pool)
+{
+ if (merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
+ {
+ store_path(merge_b->merged_abspaths, local_abspath);
+ }
+
+ if (merge_b->ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+ svn_wc_notify_action_t action = svn_wc_notify_update_add;
+
+ SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool));
+
+ if (notify_replaced)
+ action = svn_wc_notify_update_replace;
+
+ notify = svn_wc_create_notify(local_abspath, action, scratch_pool);
+ notify->kind = kind;
+
+ (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Record the update for future processing and produce the
+ update_update notification */
+static svn_error_t *
+record_update_update(merge_cmd_baton_t *merge_b,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ svn_wc_notify_state_t content_state,
+ svn_wc_notify_state_t prop_state,
+ apr_pool_t *scratch_pool)
+{
+ if (merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
+ {
+ store_path(merge_b->merged_abspaths, local_abspath);
+ }
+
+ if (merge_b->ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool));
+
+ notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_update,
+ scratch_pool);
+ notify->kind = kind;
+ notify->content_state = content_state;
+ notify->prop_state = prop_state;
+
+ (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Record the delete for future processing and for (later) producing the
+ update_delete notification */
+static svn_error_t *
+record_update_delete(merge_cmd_baton_t *merge_b,
+ struct merge_dir_baton_t *parent_db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ apr_pool_t *scratch_pool)
+{
+ /* Update the lists of merged, skipped, tree-conflicted and added paths. */
+ if (merge_b->merge_source.ancestral
+ || merge_b->reintegrate_merge)
+ {
+ /* Issue #4166: If a previous merge added NOTIFY_ABSPATH, but we
+ are now deleting it, then remove it from the list of added
+ paths. */
+ svn_hash_sets(merge_b->added_abspaths, local_abspath, NULL);
+ store_path(merge_b->merged_abspaths, local_abspath);
+ }
+
+ SVN_ERR(notify_merge_begin(merge_b, local_abspath, TRUE, scratch_pool));
+
+ if (parent_db)
+ {
+ const char *dup_abspath = apr_pstrdup(parent_db->pool, local_abspath);
+
+ if (!parent_db->pending_deletes)
+ parent_db->pending_deletes = apr_hash_make(parent_db->pool);
+
+ svn_hash_sets(parent_db->pending_deletes, dup_abspath,
+ svn_node_kind_to_word(kind));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Notify the pending 'D'eletes, that were waiting to see if a matching 'A'dd
+ might make them a 'R'eplace. */
+static svn_error_t *
+handle_pending_notifications(merge_cmd_baton_t *merge_b,
+ struct merge_dir_baton_t *db,
+ apr_pool_t *scratch_pool)
+{
+ if (merge_b->ctx->notify_func2 && db->pending_deletes)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, db->pending_deletes);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *del_abspath = svn__apr_hash_index_key(hi);
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(del_abspath,
+ svn_wc_notify_update_delete,
+ scratch_pool);
+ notify->kind = svn_node_kind_from_word(
+ svn__apr_hash_index_val(hi));
+
+ (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2,
+ notify, scratch_pool);
+ }
+
+ db->pending_deletes = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Helper function for the merge_dir_*() and merge_file_*() functions.
+
+ Installs and notifies pre-recorded tree conflicts and skips for
+ ancestors of operational merges
+ */
+static svn_error_t *
+mark_dir_edited(merge_cmd_baton_t *merge_b,
+ struct merge_dir_baton_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ /* ### Too much common code with mark_file_edited */
+ if (db->edited)
+ return SVN_NO_ERROR;
+
+ if (db->parent_baton && !db->parent_baton->edited)
+ {
+ const char *dir_abspath = svn_dirent_dirname(local_abspath,
+ scratch_pool);
+
+ SVN_ERR(mark_dir_edited(merge_b, db->parent_baton, dir_abspath,
+ scratch_pool));
+ }
+
+ db->edited = TRUE;
+
+ if (! db->shadowed)
+ return SVN_NO_ERROR; /* Easy out */
+
+ if (db->parent_baton
+ && db->parent_baton->delete_state
+ && db->tree_conflict_reason != CONFLICT_REASON_NONE)
+ {
+ db->parent_baton->delete_state->found_edit = TRUE;
+ }
+ else if (db->tree_conflict_reason == CONFLICT_REASON_SKIP
+ || db->tree_conflict_reason == CONFLICT_REASON_SKIP_WC)
+ {
+ /* open_directory() decided not to flag a tree conflict, but
+ for clarity we produce a skip for this node that
+ most likely isn't touched by the merge itself */
+
+ if (merge_b->ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE,
+ scratch_pool));
+
+ notify = svn_wc_create_notify(
+ local_abspath,
+ (db->tree_conflict_reason == CONFLICT_REASON_SKIP)
+ ? svn_wc_notify_skip
+ : svn_wc_notify_update_skip_obstruction,
+ scratch_pool);
+ notify->kind = svn_node_dir;
+ notify->content_state = notify->prop_state = db->skip_reason;
+
+ (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2,
+ notify,
+ scratch_pool);
+ }
+
+ if (merge_b->merge_source.ancestral
+ || merge_b->reintegrate_merge)
+ {
+ store_path(merge_b->skipped_abspaths, local_abspath);
+ }
+ }
+ else if (db->tree_conflict_reason != CONFLICT_REASON_NONE)
+ {
+ /* open_directory() decided that a tree conflict should be raised */
+
+ SVN_ERR(record_tree_conflict(merge_b, local_abspath, db->parent_baton,
+ svn_node_dir, db->tree_conflict_action,
+ db->tree_conflict_reason,
+ NULL, TRUE,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper function for the merge_file_*() functions.
+
+ Installs and notifies pre-recorded tree conflicts and skips for
+ ancestors of operational merges
+ */
+static svn_error_t *
+mark_file_edited(merge_cmd_baton_t *merge_b,
+ struct merge_file_baton_t *fb,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ /* ### Too much common code with mark_dir_edited */
+ if (fb->edited)
+ return SVN_NO_ERROR;
+
+ if (fb->parent_baton && !fb->parent_baton->edited)
+ {
+ const char *dir_abspath = svn_dirent_dirname(local_abspath,
+ scratch_pool);
+
+ SVN_ERR(mark_dir_edited(merge_b, fb->parent_baton, dir_abspath,
+ scratch_pool));
+ }
+
+ fb->edited = TRUE;
+
+ if (! fb->shadowed)
+ return SVN_NO_ERROR; /* Easy out */
+
+ if (fb->parent_baton
+ && fb->parent_baton->delete_state
+ && fb->tree_conflict_reason != CONFLICT_REASON_NONE)
+ {
+ fb->parent_baton->delete_state->found_edit = TRUE;
+ }
+ else if (fb->tree_conflict_reason == CONFLICT_REASON_SKIP
+ || fb->tree_conflict_reason == CONFLICT_REASON_SKIP_WC)
+ {
+ /* open_directory() decided not to flag a tree conflict, but
+ for clarity we produce a skip for this node that
+ most likely isn't touched by the merge itself */
+
+ if (merge_b->ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE,
+ scratch_pool));
+
+ notify = svn_wc_create_notify(local_abspath, svn_wc_notify_skip,
+ scratch_pool);
+ notify->kind = svn_node_file;
+ notify->content_state = notify->prop_state = fb->skip_reason;
+
+ (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2,
+ notify,
+ scratch_pool);
+ }
+
+ if (merge_b->merge_source.ancestral
+ || merge_b->reintegrate_merge)
+ {
+ store_path(merge_b->skipped_abspaths, local_abspath);
+ }
+ }
+ else if (fb->tree_conflict_reason != CONFLICT_REASON_NONE)
+ {
+ /* open_file() decided that a tree conflict should be raised */
+
+ SVN_ERR(record_tree_conflict(merge_b, local_abspath, fb->parent_baton,
+ svn_node_file, fb->tree_conflict_action,
+ fb->tree_conflict_reason,
+ NULL, TRUE,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_diff_tree_processor_t function.
+
+ Called before either merge_file_changed(), merge_file_added(),
+ merge_file_deleted() or merge_file_closed(), unless it sets *SKIP to TRUE.
+
+ When *SKIP is TRUE, the diff driver avoids work on getting the details
+ for the closing callbacks.
+ */
+static svn_error_t *
+merge_file_opened(void **new_file_baton,
+ svn_boolean_t *skip,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_dir_baton_t *pdb = dir_baton;
+ struct merge_file_baton_t *fb;
+ const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+ relpath, scratch_pool);
+
+ fb = apr_pcalloc(result_pool, sizeof(*fb));
+ fb->tree_conflict_reason = CONFLICT_REASON_NONE;
+ fb->tree_conflict_action = svn_wc_conflict_action_edit;
+ fb->skip_reason = svn_wc_notify_state_unknown;
+
+ *new_file_baton = fb;
+
+ if (pdb)
+ {
+ fb->parent_baton = pdb;
+ fb->shadowed = pdb->shadowed;
+ fb->skip_reason = pdb->skip_reason;
+ }
+
+ if (fb->shadowed)
+ {
+ /* An ancestor is tree conflicted. Nothing to do here. */
+ }
+ else if (left_source != NULL)
+ {
+ /* Node is expected to be a file, which will be changed or deleted. */
+ svn_node_kind_t kind;
+ svn_boolean_t is_deleted;
+ svn_boolean_t excluded;
+ svn_depth_t parent_depth;
+
+ if (! right_source)
+ fb->tree_conflict_action = svn_wc_conflict_action_delete;
+
+ {
+ svn_wc_notify_state_t obstr_state;
+
+ SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &excluded,
+ &kind, &parent_depth,
+ merge_b, local_abspath,
+ scratch_pool));
+
+ if (obstr_state != svn_wc_notify_state_inapplicable)
+ {
+ fb->shadowed = TRUE;
+ fb->tree_conflict_reason = CONFLICT_REASON_SKIP;
+ fb->skip_reason = obstr_state;
+ return SVN_NO_ERROR;
+ }
+
+ if (is_deleted)
+ kind = svn_node_none;
+ }
+
+ if (kind == svn_node_none)
+ {
+ fb->shadowed = TRUE;
+
+ /* If this is not the merge target and the parent is too shallow to
+ contain this directory, and the directory is not present
+ via exclusion or depth filtering, skip it instead of recording
+ a tree conflict.
+
+ Non-inheritable mergeinfo will be recorded, allowing
+ future merges into non-shallow working copies to merge
+ changes we missed this time around. */
+ if (pdb && (excluded
+ || (parent_depth != svn_depth_unknown &&
+ parent_depth < svn_depth_files)))
+ {
+ fb->shadowed = TRUE;
+
+ fb->tree_conflict_reason = CONFLICT_REASON_SKIP;
+ fb->skip_reason = svn_wc_notify_state_missing;
+ return SVN_NO_ERROR;
+ }
+
+ if (is_deleted)
+ fb->tree_conflict_reason = svn_wc_conflict_reason_deleted;
+ else
+ fb->tree_conflict_reason = svn_wc_conflict_reason_missing;
+
+ /* ### Similar to directory */
+ *skip = TRUE;
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+ /* ### /Similar */
+ }
+ else if (kind != svn_node_file)
+ {
+ fb->shadowed = TRUE;
+
+ fb->tree_conflict_reason = svn_wc_conflict_reason_obstructed;
+
+ /* ### Similar to directory */
+ *skip = TRUE;
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+ /* ### /Similar */
+ }
+
+ if (! right_source)
+ {
+ /* We want to delete the directory */
+ fb->tree_conflict_action = svn_wc_conflict_action_delete;
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+
+ if (fb->shadowed)
+ {
+ return SVN_NO_ERROR; /* Already set a tree conflict */
+ }
+
+ /* Comparison mode to verify for delete tree conflicts? */
+ if (pdb && pdb->delete_state
+ && pdb->delete_state->found_edit)
+ {
+ /* Earlier nodes found a conflict. Done. */
+ *skip = TRUE;
+ }
+ }
+ }
+ else
+ {
+ const svn_wc_conflict_description2_t *old_tc = NULL;
+
+ /* The node doesn't exist pre-merge: We have an addition */
+ fb->added = TRUE;
+ fb->tree_conflict_action = svn_wc_conflict_action_add;
+
+ if (pdb && pdb->pending_deletes
+ && svn_hash_gets(pdb->pending_deletes, local_abspath))
+ {
+ fb->add_is_replace = TRUE;
+ fb->tree_conflict_action = svn_wc_conflict_action_replace;
+
+ svn_hash_sets(pdb->pending_deletes, local_abspath, NULL);
+ }
+
+ if (pdb
+ && pdb->new_tree_conflicts
+ && (old_tc = svn_hash_gets(pdb->new_tree_conflicts, local_abspath)))
+ {
+ fb->tree_conflict_action = svn_wc_conflict_action_replace;
+ fb->tree_conflict_reason = old_tc->reason;
+
+ /* Update the tree conflict to store that this is a replace */
+ SVN_ERR(record_tree_conflict(merge_b, local_abspath, pdb,
+ svn_node_file,
+ fb->tree_conflict_action,
+ fb->tree_conflict_reason,
+ old_tc, FALSE,
+ scratch_pool));
+
+ if (old_tc->reason == svn_wc_conflict_reason_deleted
+ || old_tc->reason == svn_wc_conflict_reason_moved_away)
+ {
+ /* Issue #3806: Incoming replacements on local deletes produce
+ inconsistent result.
+
+ In this specific case we can continue applying the add part
+ of the replacement. */
+ }
+ else
+ {
+ *skip = TRUE;
+
+ return SVN_NO_ERROR;
+ }
+ }
+ else if (! (merge_b->dry_run
+ && ((pdb && pdb->added) || fb->add_is_replace)))
+ {
+ svn_wc_notify_state_t obstr_state;
+ svn_node_kind_t kind;
+ svn_boolean_t is_deleted;
+
+ SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, NULL,
+ &kind, NULL,
+ merge_b, local_abspath,
+ scratch_pool));
+
+ if (obstr_state != svn_wc_notify_state_inapplicable)
+ {
+ /* Skip the obstruction */
+ fb->shadowed = TRUE;
+ fb->tree_conflict_reason = CONFLICT_REASON_SKIP;
+ fb->skip_reason = obstr_state;
+ }
+ else if (kind != svn_node_none && !is_deleted)
+ {
+ /* Set a tree conflict */
+ fb->shadowed = TRUE;
+ fb->tree_conflict_reason = svn_wc_conflict_reason_obstructed;
+ }
+ }
+
+ /* Handle pending conflicts */
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_diff_tree_processor_t function.
+ *
+ * Called after merge_file_opened() when a node receives only text and/or
+ * property changes between LEFT_SOURCE and RIGHT_SOURCE.
+ *
+ * left_file and right_file can be NULL when the file is not modified.
+ * left_props and right_props are always available.
+ */
+static svn_error_t *
+merge_file_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const char *left_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_boolean_t file_modified,
+ const apr_array_header_t *prop_changes,
+ void *file_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_file_baton_t *fb = file_baton;
+ svn_client_ctx_t *ctx = merge_b->ctx;
+ const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+ relpath, scratch_pool);
+ const svn_wc_conflict_version_t *left;
+ const svn_wc_conflict_version_t *right;
+ svn_wc_notify_state_t text_state;
+ svn_wc_notify_state_t property_state;
+
+ SVN_ERR_ASSERT(local_abspath && svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(!left_file || svn_dirent_is_absolute(left_file));
+ SVN_ERR_ASSERT(!right_file || svn_dirent_is_absolute(right_file));
+
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+
+ if (fb->shadowed)
+ {
+ if (fb->tree_conflict_reason == CONFLICT_REASON_NONE)
+ {
+ /* We haven't notified for this node yet: report a skip */
+ SVN_ERR(record_skip(merge_b, local_abspath, svn_node_file,
+ svn_wc_notify_update_shadowed_update,
+ fb->skip_reason, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* This callback is essentially no more than a wrapper around
+ svn_wc_merge5(). Thank goodness that all the
+ diff-editor-mechanisms are doing the hard work of getting the
+ fulltexts! */
+
+ property_state = svn_wc_notify_state_unchanged;
+ text_state = svn_wc_notify_state_unchanged;
+
+ SVN_ERR(prepare_merge_props_changed(&prop_changes, local_abspath,
+ prop_changes, merge_b,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(make_conflict_versions(&left, &right, local_abspath,
+ svn_node_file, &merge_b->merge_source, merge_b->target,
+ scratch_pool, scratch_pool));
+
+ /* Do property merge now, if we are not going to perform a text merge */
+ if ((merge_b->record_only || !left_file) && prop_changes->nelts)
+ {
+ SVN_ERR(svn_wc_merge_props3(&property_state, ctx->wc_ctx, local_abspath,
+ left, right,
+ left_props, prop_changes,
+ merge_b->dry_run,
+ NULL, NULL,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool));
+ if (property_state == svn_wc_notify_state_conflicted)
+ {
+ alloc_and_store_path(&merge_b->conflicted_paths, local_abspath,
+ merge_b->pool);
+ }
+ }
+
+ /* Easy out: We are only applying mergeinfo differences. */
+ if (merge_b->record_only)
+ {
+ /* NO-OP */
+ }
+ else if (left_file)
+ {
+ svn_boolean_t has_local_mods;
+ enum svn_wc_merge_outcome_t content_outcome;
+
+ /* xgettext: the '.working', '.merge-left.r%ld' and
+ '.merge-right.r%ld' strings are used to tag onto a file
+ name in case of a merge conflict */
+ const char *target_label = _(".working");
+ const char *left_label = apr_psprintf(scratch_pool,
+ _(".merge-left.r%ld"),
+ left_source->revision);
+ const char *right_label = apr_psprintf(scratch_pool,
+ _(".merge-right.r%ld"),
+ right_source->revision);
+
+ SVN_ERR(svn_wc_text_modified_p2(&has_local_mods, ctx->wc_ctx,
+ local_abspath, FALSE, scratch_pool));
+
+ /* Do property merge and text merge in one step so that keyword expansion
+ takes into account the new property values. */
+ SVN_ERR(svn_wc_merge5(&content_outcome, &property_state, ctx->wc_ctx,
+ left_file, right_file, local_abspath,
+ left_label, right_label, target_label,
+ left, right,
+ merge_b->dry_run, merge_b->diff3_cmd,
+ merge_b->merge_options,
+ left_props, prop_changes,
+ NULL, NULL,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ scratch_pool));
+
+ if (content_outcome == svn_wc_merge_conflict
+ || property_state == svn_wc_notify_state_conflicted)
+ {
+ alloc_and_store_path(&merge_b->conflicted_paths, local_abspath,
+ merge_b->pool);
+ }
+
+ if (content_outcome == svn_wc_merge_conflict)
+ text_state = svn_wc_notify_state_conflicted;
+ else if (has_local_mods
+ && content_outcome != svn_wc_merge_unchanged)
+ text_state = svn_wc_notify_state_merged;
+ else if (content_outcome == svn_wc_merge_merged)
+ text_state = svn_wc_notify_state_changed;
+ else if (content_outcome == svn_wc_merge_no_merge)
+ text_state = svn_wc_notify_state_missing;
+ else /* merge_outcome == svn_wc_merge_unchanged */
+ text_state = svn_wc_notify_state_unchanged;
+ }
+
+ if (text_state == svn_wc_notify_state_conflicted
+ || text_state == svn_wc_notify_state_merged
+ || text_state == svn_wc_notify_state_changed
+ || property_state == svn_wc_notify_state_conflicted
+ || property_state == svn_wc_notify_state_merged
+ || property_state == svn_wc_notify_state_changed)
+ {
+ SVN_ERR(record_update_update(merge_b, local_abspath, svn_node_file,
+ text_state, property_state,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_diff_tree_processor_t function.
+ *
+ * Called after merge_file_opened() when a node doesn't exist in LEFT_SOURCE,
+ * but does in RIGHT_SOURCE.
+ *
+ * When a node is replaced instead of just added a separate opened+deleted will
+ * be invoked before the current open+added.
+ */
+static svn_error_t *
+merge_file_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ const char *copyfrom_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *file_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_file_baton_t *fb = file_baton;
+ const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+ relpath, scratch_pool);
+ apr_hash_t *pristine_props;
+ apr_hash_t *new_props;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+
+ if (fb->shadowed)
+ {
+ if (fb->tree_conflict_reason == CONFLICT_REASON_NONE)
+ {
+ /* We haven't notified for this node yet: report a skip */
+ SVN_ERR(record_skip(merge_b, local_abspath, svn_node_file,
+ svn_wc_notify_update_shadowed_add,
+ fb->skip_reason, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Easy out: We are only applying mergeinfo differences. */
+ if (merge_b->record_only)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ if ((merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
+ && ( !fb->parent_baton || !fb->parent_baton->added))
+ {
+ /* Store the roots of added subtrees */
+ store_path(merge_b->added_abspaths, local_abspath);
+ }
+
+ if (!merge_b->dry_run)
+ {
+ const char *copyfrom_url;
+ svn_revnum_t copyfrom_rev;
+ svn_stream_t *new_contents, *pristine_contents;
+
+ /* If this is a merge from the same repository as our
+ working copy, we handle adds as add-with-history.
+ Otherwise, we'll use a pure add. */
+ if (merge_b->same_repos)
+ {
+ const char *child =
+ svn_dirent_skip_ancestor(merge_b->target->abspath,
+ local_abspath);
+ SVN_ERR_ASSERT(child != NULL);
+ copyfrom_url = svn_path_url_add_component2(
+ merge_b->merge_source.loc2->url,
+ child, scratch_pool);
+ copyfrom_rev = right_source->revision;
+ SVN_ERR(check_repos_match(merge_b->target, local_abspath,
+ copyfrom_url, scratch_pool));
+ SVN_ERR(svn_stream_open_readonly(&pristine_contents,
+ right_file,
+ scratch_pool,
+ scratch_pool));
+ new_contents = NULL; /* inherit from new_base_contents */
+
+ pristine_props = right_props; /* Includes last_* information */
+ new_props = NULL; /* No local changes */
+
+ if (svn_hash_gets(pristine_props, SVN_PROP_MERGEINFO))
+ {
+ alloc_and_store_path(&merge_b->paths_with_new_mergeinfo,
+ local_abspath, merge_b->pool);
+ }
+ }
+ else
+ {
+ apr_array_header_t *regular_props;
+
+ copyfrom_url = NULL;
+ copyfrom_rev = SVN_INVALID_REVNUM;
+
+ pristine_contents = svn_stream_empty(scratch_pool);
+ SVN_ERR(svn_stream_open_readonly(&new_contents, right_file,
+ scratch_pool, scratch_pool));
+
+ pristine_props = apr_hash_make(scratch_pool); /* Local addition */
+
+ /* We don't want any foreign properties */
+ SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(right_props,
+ scratch_pool),
+ NULL, NULL, &regular_props,
+ scratch_pool));
+
+ new_props = svn_prop_array_to_hash(regular_props, scratch_pool);
+
+ /* Issue #3383: We don't want mergeinfo from a foreign repository. */
+ svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
+ }
+
+ /* Do everything like if we had called 'svn cp PATH1 PATH2'. */
+ SVN_ERR(svn_wc_add_repos_file4(merge_b->ctx->wc_ctx,
+ local_abspath,
+ pristine_contents,
+ new_contents,
+ pristine_props, new_props,
+ copyfrom_url, copyfrom_rev,
+ merge_b->ctx->cancel_func,
+ merge_b->ctx->cancel_baton,
+ scratch_pool));
+
+ /* Caller must call svn_sleep_for_timestamps() */
+ *merge_b->use_sleep = TRUE;
+ }
+
+ SVN_ERR(record_update_add(merge_b, local_abspath, svn_node_file,
+ fb->add_is_replace, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Compare the two sets of properties PROPS1 and PROPS2, ignoring the
+ * "svn:mergeinfo" property, and noticing only "normal" props. Set *SAME to
+ * true if the rest of the properties are identical or false if they differ.
+ */
+static svn_error_t *
+properties_same_p(svn_boolean_t *same,
+ apr_hash_t *props1,
+ apr_hash_t *props2,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *prop_changes;
+ int i, diffs;
+
+ /* Examine the properties that differ */
+ SVN_ERR(svn_prop_diffs(&prop_changes, props1, props2, scratch_pool));
+ diffs = 0;
+ for (i = 0; i < prop_changes->nelts; i++)
+ {
+ const char *pname = APR_ARRAY_IDX(prop_changes, i, svn_prop_t).name;
+
+ /* Count the properties we're interested in; ignore the rest */
+ if (svn_wc_is_normal_prop(pname)
+ && strcmp(pname, SVN_PROP_MERGEINFO) != 0)
+ diffs++;
+ }
+ *same = (diffs == 0);
+ return SVN_NO_ERROR;
+}
+
+/* Compare the file OLDER_ABSPATH (together with its normal properties in
+ * ORIGINAL_PROPS which may also contain WC props and entry props) with the
+ * versioned file MINE_ABSPATH (together with its versioned properties).
+ * Set *SAME to true if they are the same or false if they differ, ignoring
+ * the "svn:mergeinfo" property, and ignoring differences in keyword
+ * expansion and end-of-line style. */
+static svn_error_t *
+files_same_p(svn_boolean_t *same,
+ const char *older_abspath,
+ apr_hash_t *original_props,
+ const char *mine_abspath,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *working_props;
+
+ SVN_ERR(svn_wc_prop_list2(&working_props, wc_ctx, mine_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Compare the properties */
+ SVN_ERR(properties_same_p(same, original_props, working_props,
+ scratch_pool));
+ if (*same)
+ {
+ svn_stream_t *mine_stream;
+ svn_stream_t *older_stream;
+ svn_opt_revision_t working_rev = { svn_opt_revision_working, { 0 } };
+
+ /* Compare the file content, translating 'mine' to 'normal' form. */
+ if (svn_prop_get_value(working_props, SVN_PROP_SPECIAL) != NULL)
+ SVN_ERR(svn_subst_read_specialfile(&mine_stream, mine_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_client__get_normalized_stream(&mine_stream, wc_ctx,
+ mine_abspath, &working_rev,
+ FALSE, TRUE, NULL, NULL,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_stream_open_readonly(&older_stream, older_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_stream_contents_same2(same, mine_stream, older_stream,
+ scratch_pool));
+
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_diff_tree_processor_t function.
+ *
+ * Called after merge_file_opened() when a node does exist in LEFT_SOURCE, but
+ * no longer exists (or is replaced) in RIGHT_SOURCE.
+ *
+ * When a node is replaced instead of just added a separate opened+added will
+ * be invoked after the current open+deleted.
+ */
+static svn_error_t *
+merge_file_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const char *left_file,
+ /*const*/ apr_hash_t *left_props,
+ void *file_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_file_baton_t *fb = file_baton;
+ const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+ relpath, scratch_pool);
+ svn_boolean_t same;
+
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+
+ if (fb->shadowed)
+ {
+ if (fb->tree_conflict_reason == CONFLICT_REASON_NONE)
+ {
+ /* We haven't notified for this node yet: report a skip */
+ SVN_ERR(record_skip(merge_b, local_abspath, svn_node_file,
+ svn_wc_notify_update_shadowed_delete,
+ fb->skip_reason, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Easy out: We are only applying mergeinfo differences. */
+ if (merge_b->record_only)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* If the files are identical, attempt deletion */
+ if (merge_b->force_delete)
+ same = TRUE;
+ else
+ SVN_ERR(files_same_p(&same, left_file, left_props,
+ local_abspath, merge_b->ctx->wc_ctx,
+ scratch_pool));
+
+ if (fb->parent_baton
+ && fb->parent_baton->delete_state)
+ {
+ if (same)
+ {
+ /* Note that we checked this file */
+ store_path(fb->parent_baton->delete_state->compared_abspaths,
+ local_abspath);
+ }
+ else
+ {
+ /* We found some modification. Parent should raise a tree conflict */
+ fb->parent_baton->delete_state->found_edit = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+ }
+ else if (same)
+ {
+ if (!merge_b->dry_run)
+ SVN_ERR(svn_wc_delete4(merge_b->ctx->wc_ctx, local_abspath,
+ FALSE /* keep_local */, FALSE /* unversioned */,
+ merge_b->ctx->cancel_func,
+ merge_b->ctx->cancel_baton,
+ NULL, NULL /* no notify */,
+ scratch_pool));
+
+ /* Record that we might have deleted mergeinfo */
+ alloc_and_store_path(&merge_b->paths_with_deleted_mergeinfo,
+ local_abspath, merge_b->pool);
+
+ /* And notify the deletion */
+ SVN_ERR(record_update_delete(merge_b, fb->parent_baton, local_abspath,
+ svn_node_file, scratch_pool));
+ }
+ else
+ {
+ /* The files differ, so raise a conflict instead of deleting */
+
+ /* This is use case 5 described in the paper attached to issue
+ * #2282. See also notes/tree-conflicts/detection.txt
+ */
+ SVN_ERR(record_tree_conflict(merge_b, local_abspath, fb->parent_baton,
+ svn_node_file,
+ svn_wc_conflict_action_delete,
+ svn_wc_conflict_reason_edited,
+ NULL, TRUE,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_diff_tree_processor_t function.
+
+ Called before either merge_dir_changed(), merge_dir_added(),
+ merge_dir_deleted() or merge_dir_closed(), unless it sets *SKIP to TRUE.
+
+ After this call and before the close call, all descendants will receive
+ their changes, unless *SKIP_CHILDREN is set to TRUE.
+
+ When *SKIP is TRUE, the diff driver avoids work on getting the details
+ for the closing callbacks.
+
+ The SKIP and SKIP_DESCENDANTS work independantly.
+ */
+static svn_error_t *
+merge_dir_opened(void **new_dir_baton,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *parent_dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_dir_baton_t *db;
+ struct merge_dir_baton_t *pdb = parent_dir_baton;
+
+ const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+ relpath, scratch_pool);
+
+ db = apr_pcalloc(result_pool, sizeof(*db));
+ db->pool = result_pool;
+ db->tree_conflict_reason = CONFLICT_REASON_NONE;
+ db->tree_conflict_action = svn_wc_conflict_action_edit;
+ db->skip_reason = svn_wc_notify_state_unknown;
+
+ *new_dir_baton = db;
+
+ if (pdb)
+ {
+ db->parent_baton = pdb;
+ db->shadowed = pdb->shadowed;
+ db->skip_reason = pdb->skip_reason;
+ }
+
+ if (db->shadowed)
+ {
+ /* An ancestor is tree conflicted. Nothing to do here. */
+ if (! left_source)
+ db->added = TRUE;
+ }
+ else if (left_source != NULL)
+ {
+ /* Node is expected to be a directory. */
+ svn_node_kind_t kind;
+ svn_boolean_t is_deleted;
+ svn_boolean_t excluded;
+ svn_depth_t parent_depth;
+
+ if (! right_source)
+ db->tree_conflict_action = svn_wc_conflict_action_delete;
+
+ /* Check for an obstructed or missing node on disk. */
+ {
+ svn_wc_notify_state_t obstr_state;
+ SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &excluded,
+ &kind, &parent_depth,
+ merge_b, local_abspath,
+ scratch_pool));
+
+ if (obstr_state != svn_wc_notify_state_inapplicable)
+ {
+ db->shadowed = TRUE;
+
+ if (obstr_state == svn_wc_notify_state_obstructed)
+ {
+ svn_boolean_t is_wcroot;
+
+ SVN_ERR(svn_wc_check_root(&is_wcroot, NULL, NULL,
+ merge_b->ctx->wc_ctx,
+ local_abspath, scratch_pool));
+
+ if (is_wcroot)
+ {
+ db->tree_conflict_reason = CONFLICT_REASON_SKIP_WC;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ db->tree_conflict_reason = CONFLICT_REASON_SKIP;
+ db->skip_reason = obstr_state;
+
+ if (! right_source)
+ {
+ *skip = *skip_children = TRUE;
+ SVN_ERR(mark_dir_edited(merge_b, db, local_abspath,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ if (is_deleted)
+ kind = svn_node_none;
+ }
+
+ if (kind == svn_node_none)
+ {
+ db->shadowed = TRUE;
+
+ /* If this is not the merge target and the parent is too shallow to
+ contain this directory, and the directory is not presen
+ via exclusion or depth filtering, skip it instead of recording
+ a tree conflict.
+
+ Non-inheritable mergeinfo will be recorded, allowing
+ future merges into non-shallow working copies to merge
+ changes we missed this time around. */
+ if (pdb && (excluded
+ || (parent_depth != svn_depth_unknown &&
+ parent_depth < svn_depth_immediates)))
+ {
+ db->shadowed = TRUE;
+
+ db->tree_conflict_reason = CONFLICT_REASON_SKIP;
+ db->skip_reason = svn_wc_notify_state_missing;
+
+ return SVN_NO_ERROR;
+ }
+
+ if (is_deleted)
+ db->tree_conflict_reason = svn_wc_conflict_reason_deleted;
+ else
+ db->tree_conflict_reason = svn_wc_conflict_reason_missing;
+
+ /* ### To avoid breaking tests */
+ *skip = TRUE;
+ *skip_children = TRUE;
+ SVN_ERR(mark_dir_edited(merge_b, db, local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+ /* ### /avoid breaking tests */
+ }
+ else if (kind != svn_node_dir)
+ {
+ db->shadowed = TRUE;
+
+ db->tree_conflict_reason = svn_wc_conflict_reason_obstructed;
+
+ /* ### To avoid breaking tests */
+ *skip = TRUE;
+ *skip_children = TRUE;
+ SVN_ERR(mark_dir_edited(merge_b, db, local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+ /* ### /avoid breaking tests */
+ }
+
+ if (! right_source)
+ {
+ /* We want to delete the directory */
+ /* Mark PB edited now? */
+ db->tree_conflict_action = svn_wc_conflict_action_delete;
+ SVN_ERR(mark_dir_edited(merge_b, db, local_abspath, scratch_pool));
+
+ if (db->shadowed)
+ {
+ *skip_children = TRUE;
+ return SVN_NO_ERROR; /* Already set a tree conflict */
+ }
+
+ db->delete_state = (pdb != NULL) ? pdb->delete_state : NULL;
+
+ if (db->delete_state && db->delete_state->found_edit)
+ {
+ /* A sibling found a conflict. Done. */
+ *skip = TRUE;
+ *skip_children = TRUE;
+ }
+ else if (merge_b->force_delete)
+ {
+ /* No comparison necessary */
+ *skip_children = TRUE;
+ }
+ else if (! db->delete_state)
+ {
+ /* Start descendant comparison */
+ db->delete_state = apr_pcalloc(db->pool,
+ sizeof(*db->delete_state));
+
+ db->delete_state->del_root = db;
+ db->delete_state->compared_abspaths = apr_hash_make(db->pool);
+ }
+ }
+ }
+ else
+ {
+ const svn_wc_conflict_description2_t *old_tc = NULL;
+
+ /* The node doesn't exist pre-merge: We have an addition */
+ db->added = TRUE;
+ db->tree_conflict_action = svn_wc_conflict_action_add;
+
+ if (pdb && pdb->pending_deletes
+ && svn_hash_gets(pdb->pending_deletes, local_abspath))
+ {
+ db->add_is_replace = TRUE;
+ db->tree_conflict_action = svn_wc_conflict_action_replace;
+
+ svn_hash_sets(pdb->pending_deletes, local_abspath, NULL);
+ }
+
+ if (pdb
+ && pdb->new_tree_conflicts
+ && (old_tc = svn_hash_gets(pdb->new_tree_conflicts, local_abspath)))
+ {
+ db->tree_conflict_action = svn_wc_conflict_action_replace;
+ db->tree_conflict_reason = old_tc->reason;
+
+ if (old_tc->reason == svn_wc_conflict_reason_deleted
+ || old_tc->reason == svn_wc_conflict_reason_moved_away)
+ {
+ /* Issue #3806: Incoming replacements on local deletes produce
+ inconsistent result.
+
+ In this specific case we can continue applying the add part
+ of the replacement. */
+ }
+ else
+ {
+ *skip = TRUE;
+ *skip_children = TRUE;
+
+ /* Update the tree conflict to store that this is a replace */
+ SVN_ERR(record_tree_conflict(merge_b, local_abspath, pdb,
+ svn_node_dir,
+ db->tree_conflict_action,
+ db->tree_conflict_reason,
+ old_tc, FALSE,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (! (merge_b->dry_run
+ && ((pdb && pdb->added) || db->add_is_replace)))
+ {
+ svn_wc_notify_state_t obstr_state;
+ svn_node_kind_t kind;
+ svn_boolean_t is_deleted;
+
+ SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, NULL,
+ &kind, NULL,
+ merge_b, local_abspath,
+ scratch_pool));
+
+ /* In this case of adding a directory, we have an exception to the
+ * usual "skip if it's inconsistent" rule. If the directory exists
+ * on disk unexpectedly, we simply make it versioned, because we can
+ * do so without risk of destroying data. Only skip if it is
+ * versioned but unexpectedly missing from disk, or is unversioned
+ * but obstructed by a node of the wrong kind. */
+ if (obstr_state == svn_wc_notify_state_obstructed
+ && (is_deleted || kind == svn_node_none))
+ {
+ svn_node_kind_t disk_kind;
+
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind,
+ scratch_pool));
+
+ if (disk_kind == svn_node_dir)
+ {
+ obstr_state = svn_wc_notify_state_inapplicable;
+ db->add_existing = TRUE; /* Take over existing directory */
+ }
+ }
+
+ if (obstr_state != svn_wc_notify_state_inapplicable)
+ {
+ /* Skip the obstruction */
+ db->shadowed = TRUE;
+ db->tree_conflict_reason = CONFLICT_REASON_SKIP;
+ db->skip_reason = obstr_state;
+ }
+ else if (kind != svn_node_none && !is_deleted)
+ {
+ /* Set a tree conflict */
+ db->shadowed = TRUE;
+ db->tree_conflict_reason = svn_wc_conflict_reason_obstructed;
+ }
+ }
+
+ /* Handle pending conflicts */
+ SVN_ERR(mark_dir_edited(merge_b, db, local_abspath, scratch_pool));
+
+ if (db->shadowed)
+ {
+ /* Notified and done. Skip children? */
+ }
+ else if (merge_b->record_only)
+ {
+ /* Ok, we are done for this node and its descendants */
+ *skip = TRUE;
+ *skip_children = TRUE;
+ }
+ else if (! merge_b->dry_run)
+ {
+ /* Create the directory on disk, to allow descendants to be added */
+ if (! db->add_existing)
+ SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT,
+ scratch_pool));
+
+ if (old_tc)
+ {
+ /* svn_wc_add4 and svn_wc_add_from_disk2 can't add a node
+ over an existing tree conflict */
+
+ /* ### These functions should take some tree conflict argument
+ and allow overwriting the tc when one is passed */
+
+ SVN_ERR(svn_wc__del_tree_conflict(merge_b->ctx->wc_ctx,
+ local_abspath,
+ scratch_pool));
+ }
+
+ if (merge_b->same_repos)
+ {
+ const char *original_url;
+
+ original_url = svn_path_url_add_component2(
+ merge_b->merge_source.loc2->url,
+ relpath, scratch_pool);
+
+ /* Limitation (aka HACK):
+ We create a newly added directory with an original URL and
+ revision as that in the repository, but without its properties
+ and children.
+
+ When the merge is cancelled before the final dir_added(), the
+ copy won't really represent the in-repository state of the node.
+ */
+ SVN_ERR(svn_wc_add4(merge_b->ctx->wc_ctx, local_abspath,
+ svn_depth_infinity,
+ original_url,
+ right_source->revision,
+ merge_b->ctx->cancel_func,
+ merge_b->ctx->cancel_baton,
+ NULL, NULL /* no notify! */,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc_add_from_disk2(merge_b->ctx->wc_ctx, local_abspath,
+ apr_hash_make(scratch_pool),
+ NULL, NULL /* no notify! */,
+ scratch_pool));
+ }
+
+ if (old_tc != NULL)
+ {
+ /* ### Should be atomic with svn_wc_add(4|_from_disk2)() */
+ SVN_ERR(record_tree_conflict(merge_b, local_abspath, pdb,
+ svn_node_dir,
+ db->tree_conflict_action,
+ db->tree_conflict_reason,
+ old_tc, FALSE,
+ scratch_pool));
+ }
+ }
+
+ if (! db->shadowed && !merge_b->record_only)
+ SVN_ERR(record_update_add(merge_b, local_abspath, svn_node_dir,
+ db->add_is_replace, scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* An svn_diff_tree_processor_t function.
+ *
+ * Called after merge_dir_opened() when a node exists in both the left and
+ * right source, but has its properties changed inbetween.
+ *
+ * After the merge_dir_opened() but before the call to this merge_dir_changed()
+ * function all descendants will have been updated.
+ */
+static svn_error_t *
+merge_dir_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ const apr_array_header_t *prop_changes,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_dir_baton_t *db = dir_baton;
+ const apr_array_header_t *props;
+ const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+ relpath, scratch_pool);
+
+ SVN_ERR(handle_pending_notifications(merge_b, db, scratch_pool));
+
+ SVN_ERR(mark_dir_edited(merge_b, db, local_abspath, scratch_pool));
+
+ if (db->shadowed)
+ {
+ if (db->tree_conflict_reason == CONFLICT_REASON_NONE)
+ {
+ /* We haven't notified for this node yet: report a skip */
+ SVN_ERR(record_skip(merge_b, local_abspath, svn_node_dir,
+ svn_wc_notify_update_shadowed_update,
+ db->skip_reason, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(prepare_merge_props_changed(&props, local_abspath, prop_changes,
+ merge_b, scratch_pool, scratch_pool));
+
+ if (props->nelts)
+ {
+ const svn_wc_conflict_version_t *left;
+ const svn_wc_conflict_version_t *right;
+ svn_client_ctx_t *ctx = merge_b->ctx;
+ svn_wc_notify_state_t prop_state;
+
+ SVN_ERR(make_conflict_versions(&left, &right, local_abspath,
+ svn_node_dir, &merge_b->merge_source,
+ merge_b->target,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc_merge_props3(&prop_state, ctx->wc_ctx, local_abspath,
+ left, right,
+ left_props, props,
+ merge_b->dry_run,
+ NULL, NULL,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool));
+
+ if (prop_state == svn_wc_notify_state_conflicted)
+ {
+ alloc_and_store_path(&merge_b->conflicted_paths, local_abspath,
+ merge_b->pool);
+ }
+
+ if (prop_state == svn_wc_notify_state_conflicted
+ || prop_state == svn_wc_notify_state_merged
+ || prop_state == svn_wc_notify_state_changed)
+ {
+ SVN_ERR(record_update_update(merge_b, local_abspath, svn_node_file,
+ svn_wc_notify_state_inapplicable,
+ prop_state, scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_diff_tree_processor_t function.
+ *
+ * Called after merge_dir_opened() when a node doesn't exist in LEFT_SOURCE,
+ * but does in RIGHT_SOURCE. After the merge_dir_opened() but before the call
+ * to this merge_dir_added() function all descendants will have been added.
+ *
+ * When a node is replaced instead of just added a separate opened+deleted will
+ * be invoked before the current open+added.
+ */
+static svn_error_t *
+merge_dir_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_dir_baton_t *db = dir_baton;
+ const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+ relpath, scratch_pool);
+
+ /* For consistency; usually a no-op from _dir_added() */
+ SVN_ERR(handle_pending_notifications(merge_b, db, scratch_pool));
+ SVN_ERR(mark_dir_edited(merge_b, db, local_abspath, scratch_pool));
+
+ if (db->shadowed)
+ {
+ if (db->tree_conflict_reason == CONFLICT_REASON_NONE)
+ {
+ /* We haven't notified for this node yet: report a skip */
+ SVN_ERR(record_skip(merge_b, local_abspath, svn_node_dir,
+ svn_wc_notify_update_shadowed_add,
+ db->skip_reason, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR_ASSERT(
+ db->edited /* Marked edited from merge_open_dir() */
+ && ! merge_b->record_only /* Skip details from merge_open_dir() */
+ );
+
+ if ((merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
+ && ( !db->parent_baton || !db->parent_baton->added))
+ {
+ /* Store the roots of added subtrees */
+ store_path(merge_b->added_abspaths, local_abspath);
+ }
+
+ if (merge_b->same_repos)
+ {
+ /* When the directory was added in merge_dir_added() we didn't update its
+ pristine properties. Instead we receive the property changes later and
+ apply them in this function.
+
+ If we would apply them as changes (such as before fixing issue #3405),
+ we would see the unmodified properties as local changes, and the
+ pristine properties would be out of sync with what the repository
+ expects for this directory.
+
+ Instead of doing that we now simply set the properties as the pristine
+ properties via a private libsvn_wc api.
+ */
+
+ const char *copyfrom_url;
+ svn_revnum_t copyfrom_rev;
+ const char *parent_abspath;
+ const char *child;
+
+ /* Creating a hash containing regular and entry props */
+ apr_hash_t *new_pristine_props = right_props;
+
+ parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ child = svn_dirent_is_child(merge_b->target->abspath, local_abspath, NULL);
+ SVN_ERR_ASSERT(child != NULL);
+
+ copyfrom_url = svn_path_url_add_component2(merge_b->merge_source.loc2->url,
+ child, scratch_pool);
+ copyfrom_rev = right_source->revision;
+
+ SVN_ERR(check_repos_match(merge_b->target, parent_abspath, copyfrom_url,
+ scratch_pool));
+
+ if (!merge_b->dry_run)
+ {
+ SVN_ERR(svn_wc__complete_directory_add(merge_b->ctx->wc_ctx,
+ local_abspath,
+ new_pristine_props,
+ copyfrom_url, copyfrom_rev,
+ scratch_pool));
+ }
+
+ if (svn_hash_gets(new_pristine_props, SVN_PROP_MERGEINFO))
+ {
+ alloc_and_store_path(&merge_b->paths_with_new_mergeinfo,
+ local_abspath, merge_b->pool);
+ }
+ }
+ else
+ {
+ apr_array_header_t *regular_props;
+ apr_hash_t *new_props;
+ svn_wc_notify_state_t prop_state;
+
+ SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(right_props,
+ scratch_pool),
+ NULL, NULL, &regular_props, scratch_pool));
+
+ new_props = svn_prop_array_to_hash(regular_props, scratch_pool);
+
+ svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
+
+ /* ### What is the easiest way to set new_props on LOCAL_ABSPATH?
+
+ ### This doesn't need a merge as we just added the node
+ ### (or installed a tree conflict and skipped this node)*/
+
+ SVN_ERR(svn_wc_merge_props3(&prop_state, merge_b->ctx->wc_ctx,
+ local_abspath,
+ NULL, NULL,
+ apr_hash_make(scratch_pool),
+ svn_prop_hash_to_array(new_props,
+ scratch_pool),
+ merge_b->dry_run,
+ NULL, NULL,
+ merge_b->ctx->cancel_func,
+ merge_b->ctx->cancel_baton,
+ scratch_pool));
+ if (prop_state == svn_wc_notify_state_conflicted)
+ {
+ alloc_and_store_path(&merge_b->conflicted_paths, local_abspath,
+ merge_b->pool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for merge_dir_deleted. Implement svn_wc_status_func4_t */
+static svn_error_t *
+verify_touched_by_del_check(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct dir_delete_baton_t *delb = baton;
+
+ if (svn_hash_gets(delb->compared_abspaths, local_abspath))
+ return SVN_NO_ERROR;
+
+ switch (status->node_status)
+ {
+ case svn_wc_status_deleted:
+ case svn_wc_status_ignored:
+ case svn_wc_status_none:
+ return SVN_NO_ERROR;
+
+ default:
+ delb->found_edit = TRUE;
+ return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
+ }
+}
+
+/* An svn_diff_tree_processor_t function.
+ *
+ * Called after merge_dir_opened() when a node existed only in the left source.
+ *
+ * After the merge_dir_opened() but before the call to this merge_dir_deleted()
+ * function all descendants that existed in left_source will have been deleted.
+ *
+ * If this node is replaced, an _opened() followed by a matching _add() will
+ * be invoked after this function.
+ */
+static svn_error_t *
+merge_dir_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ /*const*/ apr_hash_t *left_props,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_dir_baton_t *db = dir_baton;
+ const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+ relpath, scratch_pool);
+ struct dir_delete_baton_t *delb;
+ svn_boolean_t same;
+ apr_hash_t *working_props;
+
+ SVN_ERR(handle_pending_notifications(merge_b, db, scratch_pool));
+ SVN_ERR(mark_dir_edited(merge_b, db, local_abspath, scratch_pool));
+
+ if (db->shadowed)
+ {
+ if (db->tree_conflict_reason == CONFLICT_REASON_NONE)
+ {
+ /* We haven't notified for this node yet: report a skip */
+ SVN_ERR(record_skip(merge_b, local_abspath, svn_node_dir,
+ svn_wc_notify_update_shadowed_delete,
+ db->skip_reason, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Easy out: We are only applying mergeinfo differences. */
+ if (merge_b->record_only)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc_prop_list2(&working_props,
+ merge_b->ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (merge_b->force_delete)
+ same = TRUE;
+ else
+ {
+ /* Compare the properties */
+ SVN_ERR(properties_same_p(&same, left_props, working_props,
+ scratch_pool));
+ }
+
+ delb = db->delete_state;
+ assert(delb != NULL);
+
+ if (! same)
+ {
+ delb->found_edit = TRUE;
+ }
+ else
+ {
+ store_path(delb->compared_abspaths, local_abspath);
+ }
+
+ if (delb->del_root != db)
+ return SVN_NO_ERROR;
+
+ if (delb->found_edit)
+ same = FALSE;
+ else if (merge_b->force_delete)
+ same = TRUE;
+ else
+ {
+ apr_array_header_t *ignores;
+ svn_error_t *err;
+ same = TRUE;
+
+ SVN_ERR(svn_wc_get_default_ignores(&ignores, merge_b->ctx->config,
+ scratch_pool));
+
+ /* None of the descendants was modified, but maybe there are
+ descendants we haven't walked?
+
+ Note that we aren't interested in changes, as we already verified
+ changes in the paths touched by the merge. And the existance of
+ other paths is enough to mark the directory edited */
+ err = svn_wc_walk_status(merge_b->ctx->wc_ctx, local_abspath,
+ svn_depth_infinity, TRUE /* get-all */,
+ FALSE /* no-ignore */,
+ TRUE /* ignore-text-mods */, ignores,
+ verify_touched_by_del_check, delb,
+ merge_b->ctx->cancel_func,
+ merge_b->ctx->cancel_baton,
+ scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ }
+
+ same = ! delb->found_edit;
+ }
+
+ if (same && !merge_b->dry_run)
+ {
+ svn_error_t *err;
+
+ err = svn_wc_delete4(merge_b->ctx->wc_ctx, local_abspath,
+ FALSE /* keep_local */, FALSE /* unversioned */,
+ merge_b->ctx->cancel_func,
+ merge_b->ctx->cancel_baton,
+ NULL, NULL /* no notify */,
+ scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_LEFT_LOCAL_MOD)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ same = FALSE;
+ }
+ }
+
+ if (! same)
+ {
+ /* If the attempt to delete an existing directory failed,
+ * the directory has local modifications (e.g. locally added
+ * files, or property changes). Flag a tree conflict. */
+
+ /* This handles use case 5 described in the paper attached to issue
+ * #2282. See also notes/tree-conflicts/detection.txt
+ */
+ SVN_ERR(record_tree_conflict(merge_b, local_abspath, db->parent_baton,
+ svn_node_dir,
+ svn_wc_conflict_action_delete,
+ svn_wc_conflict_reason_edited,
+ NULL, TRUE,
+ scratch_pool));
+ }
+ else
+ {
+ /* Record that we might have deleted mergeinfo */
+ if (working_props
+ && svn_hash_gets(working_props, SVN_PROP_MERGEINFO))
+ {
+ alloc_and_store_path(&merge_b->paths_with_deleted_mergeinfo,
+ local_abspath, merge_b->pool);
+ }
+
+ SVN_ERR(record_update_delete(merge_b, db->parent_baton, local_abspath,
+ svn_node_dir, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_diff_tree_processor_t function.
+ *
+ * Called after merge_dir_opened() when a node itself didn't change between
+ * the left and right source.
+ *
+ * After the merge_dir_opened() but before the call to this merge_dir_closed()
+ * function all descendants will have been processed.
+ */
+static svn_error_t *
+merge_dir_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_dir_baton_t *db = dir_baton;
+
+ SVN_ERR(handle_pending_notifications(merge_b, db, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_diff_tree_processor_t function.
+
+ Called when the diff driver wants to report an absent path.
+
+ In case of merges this happens when the diff encounters a server-excluded
+ path.
+
+ We register a skipped path, which will make parent mergeinfo non-
+ inheritable. This ensures that a future merge might see these skipped
+ changes as eligable for merging.
+
+ For legacy reasons we also notify the path as skipped.
+ */
+static svn_error_t *
+merge_node_absent(const char *relpath,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ merge_cmd_baton_t *merge_b = processor->baton;
+
+ const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+ relpath, scratch_pool);
+
+ SVN_ERR(record_skip(merge_b, local_abspath, svn_node_unknown,
+ svn_wc_notify_skip, svn_wc_notify_state_missing,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/*-----------------------------------------------------------------------*/
+
+/*** Merge Notification ***/
+
+
+/* Finds a nearest ancestor in CHILDREN_WITH_MERGEINFO for LOCAL_ABSPATH. If
+ PATH_IS_OWN_ANCESTOR is TRUE then a child in CHILDREN_WITH_MERGEINFO
+ where child->abspath == PATH is considered PATH's ancestor. If FALSE,
+ then child->abspath must be a proper ancestor of PATH.
+
+ CHILDREN_WITH_MERGEINFO is expected to be sorted in Depth first
+ order of path. */
+static svn_client__merge_path_t *
+find_nearest_ancestor(const apr_array_header_t *children_with_mergeinfo,
+ svn_boolean_t path_is_own_ancestor,
+ const char *local_abspath)
+{
+ int i;
+
+ SVN_ERR_ASSERT_NO_RETURN(children_with_mergeinfo != NULL);
+
+ for (i = children_with_mergeinfo->nelts - 1; i >= 0 ; i--)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
+
+ if (svn_dirent_is_ancestor(child->abspath, local_abspath)
+ && (path_is_own_ancestor
+ || strcmp(child->abspath, local_abspath) != 0))
+ return child;
+ }
+ return NULL;
+}
+
+/* Find the highest level path in a merge target (possibly the merge target
+ itself) to use in a merge notification header.
+
+ Return the svn_client__merge_path_t * representing the most distant
+ ancestor in CHILDREN_WITH_MERGEINFO of LOCAL_ABSPATH where said
+ ancestor's first remaining ranges element (per the REMAINING_RANGES
+ member of the ancestor) intersect with the first remaining ranges element
+ for every intermediate ancestor svn_client__merge_path_t * of
+ LOCAL_ABSPATH. If no such ancestor is found return NULL.
+
+ If the remaining ranges of the elements in CHILDREN_WITH_MERGEINFO
+ represent a forward merge, then set *START to the oldest revision found
+ in any of the intersecting ancestors and *END to the youngest revision
+ found. If the remaining ranges of the elements in CHILDREN_WITH_MERGEINFO
+ represent a reverse merge, then set *START to the youngest revision
+ found and *END to the oldest revision found. If no ancestors are found
+ then set *START and *END to SVN_INVALID_REVNUM.
+
+ If PATH_IS_OWN_ANCESTOR is TRUE then a child in CHILDREN_WITH_MERGEINFO
+ where child->abspath == PATH is considered PATH's ancestor. If FALSE,
+ then child->abspath must be a proper ancestor of PATH.
+
+ See the CHILDREN_WITH_MERGEINFO ARRAY global comment for more
+ information. */
+static svn_client__merge_path_t *
+find_nearest_ancestor_with_intersecting_ranges(
+ svn_revnum_t *start,
+ svn_revnum_t *end,
+ const apr_array_header_t *children_with_mergeinfo,
+ svn_boolean_t path_is_own_ancestor,
+ const char *local_abspath)
+{
+ int i;
+ svn_client__merge_path_t *nearest_ancestor = NULL;
+
+ *start = SVN_INVALID_REVNUM;
+ *end = SVN_INVALID_REVNUM;
+
+ SVN_ERR_ASSERT_NO_RETURN(children_with_mergeinfo != NULL);
+
+ for (i = children_with_mergeinfo->nelts - 1; i >= 0 ; i--)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
+
+ if (svn_dirent_is_ancestor(child->abspath, local_abspath)
+ && (path_is_own_ancestor
+ || strcmp(child->abspath, local_abspath) != 0))
+ {
+ if (nearest_ancestor == NULL)
+ {
+ /* Found an ancestor. */
+ nearest_ancestor = child;
+
+ if (child->remaining_ranges)
+ {
+ svn_merge_range_t *r1 = APR_ARRAY_IDX(
+ child->remaining_ranges, 0, svn_merge_range_t *);
+ *start = r1->start;
+ *end = r1->end;
+ }
+ else
+ {
+ /* If CHILD->REMAINING_RANGES is null then LOCAL_ABSPATH
+ is inside an absent subtree in the merge target. */
+ *start = SVN_INVALID_REVNUM;
+ *end = SVN_INVALID_REVNUM;
+ break;
+ }
+ }
+ else
+ {
+ /* We'e found another ancestor for LOCAL_ABSPATH. Do its
+ first remaining range intersect with the previously
+ found ancestor? */
+ svn_merge_range_t *r1 =
+ APR_ARRAY_IDX(nearest_ancestor->remaining_ranges, 0,
+ svn_merge_range_t *);
+ svn_merge_range_t *r2 =
+ APR_ARRAY_IDX(child->remaining_ranges, 0,
+ svn_merge_range_t *);
+
+ if (r1 && r2)
+ {
+ svn_merge_range_t range1;
+ svn_merge_range_t range2;
+ svn_boolean_t reverse_merge = r1->start > r2->end;
+
+ /* Flip endpoints if this is a reverse merge. */
+ if (reverse_merge)
+ {
+ range1.start = r1->end;
+ range1.end = r1->start;
+ range2.start = r2->end;
+ range2.end = r2->start;
+ }
+ else
+ {
+ range1.start = r1->start;
+ range1.end = r1->end;
+ range2.start = r2->start;
+ range2.end = r2->end;
+ }
+
+ if (range1.start < range2.end && range2.start < range1.end)
+ {
+ *start = reverse_merge ?
+ MAX(r1->start, r2->start) : MIN(r1->start, r2->start);
+ *end = reverse_merge ?
+ MIN(r1->end, r2->end) : MAX(r1->end, r2->end);
+ nearest_ancestor = child;
+ }
+ }
+ }
+ }
+ }
+ return nearest_ancestor;
+}
+
+/* Notify that we're starting to record mergeinfo for the merge of the
+ * revision range RANGE into TARGET_ABSPATH. RANGE should be null if the
+ * merge sources are not from the same URL.
+ *
+ * This calls the client's notification receiver (as found in the client
+ * context), with a WC abspath.
+ */
+static void
+notify_mergeinfo_recording(const char *target_abspath,
+ const svn_merge_range_t *range,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *n = svn_wc_create_notify(
+ target_abspath, svn_wc_notify_merge_record_info_begin, pool);
+
+ n->merge_range = range ? svn_merge_range_dup(range, pool) : NULL;
+ ctx->notify_func2(ctx->notify_baton2, n, pool);
+ }
+}
+
+/* Notify that we're completing the merge into TARGET_ABSPATH.
+ *
+ * This calls the client's notification receiver (as found in the client
+ * context), with a WC abspath.
+ */
+static void
+notify_merge_completed(const char *target_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *n
+ = svn_wc_create_notify(target_abspath, svn_wc_notify_merge_completed,
+ pool);
+ ctx->notify_func2(ctx->notify_baton2, n, pool);
+ }
+}
+
+/* Is the notification the result of a real operative merge? */
+#define IS_OPERATIVE_NOTIFICATION(notify) \
+ (notify->content_state == svn_wc_notify_state_conflicted \
+ || notify->content_state == svn_wc_notify_state_merged \
+ || notify->content_state == svn_wc_notify_state_changed \
+ || notify->prop_state == svn_wc_notify_state_conflicted \
+ || notify->prop_state == svn_wc_notify_state_merged \
+ || notify->prop_state == svn_wc_notify_state_changed \
+ || notify->action == svn_wc_notify_update_add \
+ || notify->action == svn_wc_notify_tree_conflict)
+
+
+/* Remove merge source gaps from range used for merge notifications.
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=4138
+
+ If IMPLICIT_SRC_GAP is not NULL then it is a rangelist containing a
+ single range (see the implicit_src_gap member of merge_cmd_baton_t).
+ RANGE describes a (possibly reverse) merge.
+
+ If IMPLICIT_SRC_GAP is not NULL and it's sole range intersects with
+ the older revision in *RANGE, then remove IMPLICIT_SRC_GAP's range
+ from *RANGE. */
+static void
+remove_source_gap(svn_merge_range_t *range,
+ apr_array_header_t *implicit_src_gap)
+{
+ if (implicit_src_gap)
+ {
+ svn_merge_range_t *gap_range =
+ APR_ARRAY_IDX(implicit_src_gap, 0, svn_merge_range_t *);
+ if (range->start < range->end)
+ {
+ if (gap_range->start == range->start)
+ range->start = gap_range->end;
+ }
+ else /* Reverse merge */
+ {
+ if (gap_range->start == range->end)
+ range->end = gap_range->end;
+ }
+ }
+}
+
+/* Notify that we're starting a merge
+ *
+ * This calls the client's notification receiver (as found in the client
+ * context), with a WC abspath.
+ */
+static svn_error_t *
+notify_merge_begin(merge_cmd_baton_t *merge_b,
+ const char *local_abspath,
+ svn_boolean_t delete_action,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_notify_t *notify;
+ svn_merge_range_t n_range =
+ {SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, TRUE};
+ const char *notify_abspath;
+
+ if (! merge_b->ctx->notify_func2)
+ return SVN_NO_ERROR;
+
+ /* If our merge sources are ancestors of one another... */
+ if (merge_b->merge_source.ancestral)
+ {
+ const svn_client__merge_path_t *child;
+ /* Find NOTIFY->PATH's nearest ancestor in
+ NOTIFY->CHILDREN_WITH_MERGEINFO. Normally we consider a child in
+ NOTIFY->CHILDREN_WITH_MERGEINFO representing PATH to be an
+ ancestor of PATH, but if this is a deletion of PATH then the
+ notification must be for a proper ancestor of PATH. This ensures
+ we don't get notifications like:
+
+ --- Merging rX into 'PARENT/CHILD'
+ D PARENT/CHILD
+
+ But rather:
+
+ --- Merging rX into 'PARENT'
+ D PARENT/CHILD
+ */
+
+ child = find_nearest_ancestor_with_intersecting_ranges(
+ &(n_range.start), &(n_range.end),
+ merge_b->notify_begin.nodes_with_mergeinfo,
+ ! delete_action, local_abspath);
+
+ if (!child && delete_action)
+ {
+ /* Triggered by file replace in single-file-merge */
+ child = find_nearest_ancestor(merge_b->notify_begin.nodes_with_mergeinfo,
+ TRUE, local_abspath);
+ }
+
+ assert(child != NULL); /* Should always find the merge anchor */
+
+ if (! child)
+ return SVN_NO_ERROR;
+
+ if (merge_b->notify_begin.last_abspath != NULL
+ && strcmp(child->abspath, merge_b->notify_begin.last_abspath) == 0)
+ {
+ /* Don't notify the same merge again */
+ return SVN_NO_ERROR;
+ }
+
+ merge_b->notify_begin.last_abspath = child->abspath;
+
+ if (child->absent || child->remaining_ranges->nelts == 0
+ || !SVN_IS_VALID_REVNUM(n_range.start))
+ {
+ /* No valid information for an header */
+ return SVN_NO_ERROR;
+ }
+
+ notify_abspath = child->abspath;
+ }
+ else
+ {
+ if (merge_b->notify_begin.last_abspath)
+ return SVN_NO_ERROR; /* already notified */
+
+ notify_abspath = merge_b->target->abspath;
+ /* Store something in last_abspath. Any value would do */
+ merge_b->notify_begin.last_abspath = merge_b->target->abspath;
+ }
+
+ notify = svn_wc_create_notify(notify_abspath,
+ merge_b->same_repos
+ ? svn_wc_notify_merge_begin
+ : svn_wc_notify_foreign_merge_begin,
+ scratch_pool);
+
+ if (SVN_IS_VALID_REVNUM(n_range.start))
+ {
+ /* If the merge source has a gap, then don't mention
+ those gap revisions in the notification. */
+ remove_source_gap(&n_range, merge_b->implicit_src_gap);
+ notify->merge_range = &n_range;
+ }
+ else
+ {
+ notify->merge_range = NULL;
+ }
+
+ (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify,
+ scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *OUT_RANGELIST to the intersection of IN_RANGELIST with the simple
+ * (inheritable) revision range REV1:REV2, according to CONSIDER_INHERITANCE.
+ * If REV1 is equal to REV2, the result is an empty rangelist, otherwise
+ * REV1 must be less than REV2.
+ *
+ * Note: If CONSIDER_INHERITANCE is FALSE, the effect is to treat any non-
+ * inheritable input ranges as if they were inheritable. If it is TRUE, the
+ * effect is to discard any non-inheritable input ranges. Therefore the
+ * ranges in *OUT_RANGELIST will always be inheritable. */
+static svn_error_t *
+rangelist_intersect_range(svn_rangelist_t **out_rangelist,
+ const svn_rangelist_t *in_rangelist,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(rev1 <= rev2);
+
+ if (rev1 < rev2)
+ {
+ svn_rangelist_t *simple_rangelist =
+ svn_rangelist__initialize(rev1, rev2, TRUE, scratch_pool);
+
+ SVN_ERR(svn_rangelist_intersect(out_rangelist,
+ simple_rangelist, in_rangelist,
+ consider_inheritance, result_pool));
+ }
+ else
+ {
+ *out_rangelist = apr_array_make(result_pool, 0,
+ sizeof(svn_merge_range_t *));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Helper for fix_deleted_subtree_ranges(). Like fix_deleted_subtree_ranges()
+ this function should only be called when honoring mergeinfo.
+
+ CHILD, PARENT, REVISION1, REVISION2, and RA_SESSION are all cascaded from
+ fix_deleted_subtree_ranges() -- see that function for more information on
+ each.
+
+ If PARENT is not the merge target then PARENT must have already have been
+ processed by this function as a child. Specifically, this means that
+ PARENT->REMAINING_RANGES must already be populated -- it can be an empty
+ rangelist but cannot be NULL.
+
+ PRIMARY_URL is the merge source url of CHILD at the younger of REVISION1
+ and REVISION2.
+
+ Since this function is only invoked for subtrees of the merge target, the
+ guarantees afforded by normalize_merge_sources() don't apply - see the
+ 'MERGEINFO MERGE SOURCE NORMALIZATION' comment at the top of this file.
+ Therefore it is possible that PRIMARY_URL@REVISION1 and
+ PRIMARY_URL@REVISION2 don't describe the endpoints of an unbroken line of
+ history. The purpose of this helper is to identify these cases of broken
+ history and adjust CHILD->REMAINING_RANGES in such a way we don't later try
+ to describe nonexistent path/revisions to the merge report editor -- see
+ drive_merge_report_editor().
+
+ If PRIMARY_URL@REVISION1 and PRIMARY_URL@REVISION2 describe an unbroken
+ line of history then do nothing and leave CHILD->REMAINING_RANGES as-is.
+
+ If neither PRIMARY_URL@REVISION1 nor PRIMARY_URL@REVISION2 exist then
+ there is nothing to merge to CHILD->ABSPATH so set CHILD->REMAINING_RANGES
+ equal to PARENT->REMAINING_RANGES. This will cause the subtree to
+ effectively ignore CHILD -- see 'Note: If the first svn_merge_range_t...'
+ in drive_merge_report_editor()'s doc string.
+
+ If PRIMARY_URL@REVISION1 *xor* PRIMARY_URL@REVISION2 exist then we take the
+ subset of REVISION1:REVISION2 in CHILD->REMAINING_RANGES at which
+ PRIMARY_URL doesn't exist and set that subset equal to
+ PARENT->REMAINING_RANGES' intersection with that non-existent range. Why?
+ Because this causes CHILD->REMAINING_RANGES to be identical to
+ PARENT->REMAINING_RANGES for revisions between REVISION1 and REVISION2 at
+ which PRIMARY_URL doesn't exist. As mentioned above this means that
+ drive_merge_report_editor() won't attempt to describe these non-existent
+ subtree path/ranges to the reporter (which would break the merge).
+
+ If the preceding paragraph wasn't terribly clear then what follows spells
+ out this function's behavior a bit more explicitly:
+
+ For forward merges (REVISION1 < REVISION2)
+
+ If PRIMARY_URL@REVISION1 exists but PRIMARY_URL@REVISION2 doesn't, then
+ find the revision 'N' in which PRIMARY_URL@REVISION1 was deleted. Leave
+ the subset of CHILD->REMAINING_RANGES that intersects with
+ REVISION1:(N - 1) as-is and set the subset of CHILD->REMAINING_RANGES
+ that intersects with (N - 1):REVISION2 equal to PARENT->REMAINING_RANGES'
+ intersection with (N - 1):REVISION2.
+
+ If PRIMARY_URL@REVISION1 doesn't exist but PRIMARY_URL@REVISION2 does,
+ then find the revision 'M' in which PRIMARY_URL@REVISION2 came into
+ existence. Leave the subset of CHILD->REMAINING_RANGES that intersects with
+ (M - 1):REVISION2 as-is and set the subset of CHILD->REMAINING_RANGES
+ that intersects with REVISION1:(M - 1) equal to PARENT->REMAINING_RANGES'
+ intersection with REVISION1:(M - 1).
+
+ For reverse merges (REVISION1 > REVISION2)
+
+ If PRIMARY_URL@REVISION1 exists but PRIMARY_URL@REVISION2 doesn't, then
+ find the revision 'N' in which PRIMARY_URL@REVISION1 came into existence.
+ Leave the subset of CHILD->REMAINING_RANGES that intersects with
+ REVISION2:(N - 1) as-is and set the subset of CHILD->REMAINING_RANGES
+ that intersects with (N - 1):REVISION1 equal to PARENT->REMAINING_RANGES'
+ intersection with (N - 1):REVISION1.
+
+ If PRIMARY_URL@REVISION1 doesn't exist but PRIMARY_URL@REVISION2 does,
+ then find the revision 'M' in which PRIMARY_URL@REVISION2 came into
+ existence. Leave the subset of CHILD->REMAINING_RANGES that intersects with
+ REVISION2:(M - 1) as-is and set the subset of CHILD->REMAINING_RANGES
+ that intersects with (M - 1):REVISION1 equal to PARENT->REMAINING_RANGES'
+ intersection with REVISION1:(M - 1).
+
+ SCRATCH_POOL is used for all temporary allocations. Changes to CHILD are
+ allocated in RESULT_POOL. */
+static svn_error_t *
+adjust_deleted_subtree_ranges(svn_client__merge_path_t *child,
+ svn_client__merge_path_t *parent,
+ svn_revnum_t revision1,
+ svn_revnum_t revision2,
+ const char *primary_url,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_rollback = revision2 < revision1;
+ svn_revnum_t younger_rev = is_rollback ? revision1 : revision2;
+ svn_revnum_t peg_rev = younger_rev;
+ svn_revnum_t older_rev = is_rollback ? revision2 : revision1;
+ apr_array_header_t *segments;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(parent->remaining_ranges);
+
+ err = svn_client__repos_location_segments(&segments, ra_session,
+ primary_url, peg_rev,
+ younger_rev, older_rev, ctx,
+ scratch_pool);
+
+ /* If PRIMARY_URL@peg_rev doesn't exist then
+ svn_client__repos_location_segments() typically returns an
+ SVN_ERR_FS_NOT_FOUND error, but if it doesn't exist for a
+ forward merge over ra_neon then we get SVN_ERR_RA_DAV_REQUEST_FAILED.
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3137 fixed some of
+ the cases where different RA layers returned different error codes to
+ signal the "path not found"...but it looks like there is more to do.
+
+ ### Do we still need to special case for ra_neon (since it no longer
+ exists)? */
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND
+ || err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
+ {
+ /* PRIMARY_URL@peg_rev doesn't exist. Check if PRIMARY_URL@older_rev
+ exists, if neither exist then the editor can simply ignore this
+ subtree. */
+ const char *rel_source_path; /* PRIMARY_URL relative to RA_SESSION */
+ svn_node_kind_t kind;
+
+ svn_error_clear(err);
+ err = NULL;
+
+ SVN_ERR(svn_ra_get_path_relative_to_session(
+ ra_session, &rel_source_path, primary_url, scratch_pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, rel_source_path,
+ older_rev, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ {
+ /* Neither PRIMARY_URL@peg_rev nor PRIMARY_URL@older_rev exist,
+ so there is nothing to merge. Set CHILD->REMAINING_RANGES
+ identical to PARENT's. */
+ child->remaining_ranges =
+ svn_rangelist_dup(parent->remaining_ranges, scratch_pool);
+ }
+ else
+ {
+ svn_rangelist_t *deleted_rangelist;
+ svn_revnum_t rev_primary_url_deleted;
+
+ /* PRIMARY_URL@older_rev exists, so it was deleted at some
+ revision prior to peg_rev, find that revision. */
+ SVN_ERR(svn_ra_get_deleted_rev(ra_session, rel_source_path,
+ older_rev, younger_rev,
+ &rev_primary_url_deleted,
+ scratch_pool));
+
+ /* PRIMARY_URL@older_rev exists and PRIMARY_URL@peg_rev doesn't,
+ so svn_ra_get_deleted_rev() should always find the revision
+ PRIMARY_URL@older_rev was deleted. */
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(rev_primary_url_deleted));
+
+ /* If this is a reverse merge reorder CHILD->REMAINING_RANGES and
+ PARENT->REMAINING_RANGES so both will work with the
+ svn_rangelist_* APIs below. */
+ if (is_rollback)
+ {
+ /* svn_rangelist_reverse operates in place so it's safe
+ to use our scratch_pool. */
+ SVN_ERR(svn_rangelist_reverse(child->remaining_ranges,
+ scratch_pool));
+ SVN_ERR(svn_rangelist_reverse(parent->remaining_ranges,
+ scratch_pool));
+ }
+
+ /* Find the intersection of CHILD->REMAINING_RANGES with the
+ range over which PRIMARY_URL@older_rev exists (ending at
+ the youngest revision at which it still exists). */
+ SVN_ERR(rangelist_intersect_range(&child->remaining_ranges,
+ child->remaining_ranges,
+ older_rev,
+ rev_primary_url_deleted - 1,
+ FALSE,
+ scratch_pool, scratch_pool));
+
+ /* Merge into CHILD->REMAINING_RANGES the intersection of
+ PARENT->REMAINING_RANGES with the range beginning when
+ PRIMARY_URL@older_rev was deleted until younger_rev. */
+ SVN_ERR(rangelist_intersect_range(&deleted_rangelist,
+ parent->remaining_ranges,
+ rev_primary_url_deleted - 1,
+ peg_rev,
+ FALSE,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_rangelist_merge2(child->remaining_ranges,
+ deleted_rangelist, scratch_pool,
+ scratch_pool));
+
+ /* Return CHILD->REMAINING_RANGES and PARENT->REMAINING_RANGES
+ to reverse order if necessary. */
+ if (is_rollback)
+ {
+ SVN_ERR(svn_rangelist_reverse(child->remaining_ranges,
+ scratch_pool));
+ SVN_ERR(svn_rangelist_reverse(parent->remaining_ranges,
+ scratch_pool));
+ }
+ }
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+ else /* PRIMARY_URL@peg_rev exists. */
+ {
+ svn_rangelist_t *non_existent_rangelist;
+ svn_location_segment_t *segment =
+ APR_ARRAY_IDX(segments, (segments->nelts - 1),
+ svn_location_segment_t *);
+
+ /* We know PRIMARY_URL@peg_rev exists as the call to
+ svn_client__repos_location_segments() succeeded. If there is only
+ one segment that starts at oldest_rev then we know that
+ PRIMARY_URL@oldest_rev:PRIMARY_URL@peg_rev describes an unbroken
+ line of history, so there is nothing more to adjust in
+ CHILD->REMAINING_RANGES. */
+ if (segment->range_start == older_rev)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* If this is a reverse merge reorder CHILD->REMAINING_RANGES and
+ PARENT->REMAINING_RANGES so both will work with the
+ svn_rangelist_* APIs below. */
+ if (is_rollback)
+ {
+ SVN_ERR(svn_rangelist_reverse(child->remaining_ranges,
+ scratch_pool));
+ SVN_ERR(svn_rangelist_reverse(parent->remaining_ranges,
+ scratch_pool));
+ }
+
+ /* Intersect CHILD->REMAINING_RANGES with the range where PRIMARY_URL
+ exists. Since segment doesn't span older_rev:peg_rev we know
+ PRIMARY_URL@peg_rev didn't come into existence until
+ segment->range_start + 1. */
+ SVN_ERR(rangelist_intersect_range(&child->remaining_ranges,
+ child->remaining_ranges,
+ segment->range_start, peg_rev,
+ FALSE, scratch_pool, scratch_pool));
+
+ /* Merge into CHILD->REMAINING_RANGES the intersection of
+ PARENT->REMAINING_RANGES with the range before PRIMARY_URL@peg_rev
+ came into existence. */
+ SVN_ERR(rangelist_intersect_range(&non_existent_rangelist,
+ parent->remaining_ranges,
+ older_rev, segment->range_start,
+ FALSE, scratch_pool, scratch_pool));
+ SVN_ERR(svn_rangelist_merge2(child->remaining_ranges,
+ non_existent_rangelist, scratch_pool,
+ scratch_pool));
+
+ /* Return CHILD->REMAINING_RANGES and PARENT->REMAINING_RANGES
+ to reverse order if necessary. */
+ if (is_rollback)
+ {
+ SVN_ERR(svn_rangelist_reverse(child->remaining_ranges,
+ scratch_pool));
+ SVN_ERR(svn_rangelist_reverse(parent->remaining_ranges,
+ scratch_pool));
+ }
+ }
+
+ /* Make a lasting copy of CHILD->REMAINING_RANGES using POOL. */
+ child->remaining_ranges = svn_rangelist_dup(child->remaining_ranges,
+ result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Helper for do_directory_merge().
+
+ SOURCE is cascaded from the argument of the same name in
+ do_directory_merge(). TARGET is the merge target. RA_SESSION is the
+ session for the younger of SOURCE->loc1 and SOURCE->loc2.
+
+ Adjust the subtrees in CHILDREN_WITH_MERGEINFO so that we don't
+ later try to describe invalid paths in drive_merge_report_editor().
+ This function is just a thin wrapper around
+ adjust_deleted_subtree_ranges(), which see for further details.
+
+ SCRATCH_POOL is used for all temporary allocations. Changes to
+ CHILDREN_WITH_MERGEINFO are allocated in RESULT_POOL.
+*/
+static svn_error_t *
+fix_deleted_subtree_ranges(const merge_source_t *source,
+ const merge_target_t *target,
+ svn_ra_session_t *ra_session,
+ apr_array_header_t *children_with_mergeinfo,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_boolean_t is_rollback = source->loc2->rev < source->loc1->rev;
+
+ assert(session_url_is(ra_session,
+ (is_rollback ? source->loc1 : source->loc2)->url,
+ scratch_pool));
+
+ /* CHILDREN_WITH_MERGEINFO is sorted in depth-first order, so
+ start at index 1 to examine only subtrees. */
+ for (i = 1; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
+ svn_client__merge_path_t *parent;
+ svn_rangelist_t *deleted_rangelist, *added_rangelist;
+
+ SVN_ERR_ASSERT(child);
+ if (child->absent)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ /* Find CHILD's parent. */
+ parent = find_nearest_ancestor(children_with_mergeinfo,
+ FALSE, child->abspath);
+
+ /* Since CHILD is a subtree then its parent must be in
+ CHILDREN_WITH_MERGEINFO, see the global comment
+ 'THE CHILDREN_WITH_MERGEINFO ARRAY'. */
+ SVN_ERR_ASSERT(parent);
+
+ /* If this is a reverse merge reorder CHILD->REMAINING_RANGES
+ so it will work with the svn_rangelist_diff API. */
+ if (is_rollback)
+ {
+ SVN_ERR(svn_rangelist_reverse(child->remaining_ranges, iterpool));
+ SVN_ERR(svn_rangelist_reverse(parent->remaining_ranges, iterpool));
+ }
+
+ SVN_ERR(svn_rangelist_diff(&deleted_rangelist, &added_rangelist,
+ child->remaining_ranges,
+ parent->remaining_ranges,
+ TRUE, iterpool));
+
+ if (is_rollback)
+ {
+ SVN_ERR(svn_rangelist_reverse(child->remaining_ranges, iterpool));
+ SVN_ERR(svn_rangelist_reverse(parent->remaining_ranges, iterpool));
+ }
+
+ /* If CHILD is the merge target we then know that SOURCE is provided
+ by normalize_merge_sources() -- see 'MERGEINFO MERGE SOURCE
+ NORMALIZATION'. Due to this normalization we know that SOURCE
+ describes an unbroken line of history such that the entire range
+ described by SOURCE can potentially be merged to CHILD.
+
+ But if CHILD is a subtree we don't have the same guarantees about
+ SOURCE as we do for the merge target. SOURCE->loc1 and/or
+ SOURCE->loc2 might not exist.
+
+ If one or both doesn't exist, then adjust CHILD->REMAINING_RANGES
+ such that we don't later try to describe invalid subtrees in
+ drive_merge_report_editor(), as that will break the merge.
+ If CHILD has the same remaining ranges as PARENT however, then
+ there is no need to make these adjustments, since
+ drive_merge_report_editor() won't attempt to describe CHILD in this
+ case, see the 'Note' in drive_merge_report_editor's docstring. */
+ if (deleted_rangelist->nelts || added_rangelist->nelts)
+ {
+ const char *child_primary_source_url;
+ const char *child_repos_src_path =
+ svn_dirent_is_child(target->abspath, child->abspath, iterpool);
+
+ /* This loop is only processing subtrees, so CHILD->ABSPATH
+ better be a proper child of the merge target. */
+ SVN_ERR_ASSERT(child_repos_src_path);
+
+ child_primary_source_url =
+ svn_path_url_add_component2((source->loc1->rev < source->loc2->rev)
+ ? source->loc2->url : source->loc1->url,
+ child_repos_src_path, iterpool);
+
+ SVN_ERR(adjust_deleted_subtree_ranges(child, parent,
+ source->loc1->rev,
+ source->loc2->rev,
+ child_primary_source_url,
+ ra_session,
+ ctx, result_pool, iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/*-----------------------------------------------------------------------*/
+
+/*** Determining What Remains To Be Merged ***/
+
+/* Get explicit and/or implicit mergeinfo for the working copy path
+ TARGET_ABSPATH.
+
+ If RECORDED_MERGEINFO is not NULL then set *RECORDED_MERGEINFO
+ to TARGET_ABSPATH's explicit or inherited mergeinfo as dictated by
+ INHERIT.
+
+ If IMPLICIT_MERGEINFO is not NULL then set *IMPLICIT_MERGEINFO
+ to TARGET_ABSPATH's implicit mergeinfo (a.k.a. natural history).
+
+ If both RECORDED_MERGEINFO and IMPLICIT_MERGEINFO are not NULL and
+ *RECORDED_MERGEINFO is inherited, then *IMPLICIT_MERGEINFO will be
+ removed from *RECORDED_MERGEINFO.
+
+ If INHERITED is not NULL set *INHERITED to TRUE if *RECORDED_MERGEINFO
+ is inherited rather than explicit. If RECORDED_MERGEINFO is NULL then
+ INHERITED is ignored.
+
+
+ If IMPLICIT_MERGEINFO is not NULL then START and END are limits on
+ the natural history sought, must both be valid revision numbers, and
+ START must be greater than END. If TARGET_ABSPATH's base revision
+ is older than START, then the base revision is used as the younger
+ bound in place of START.
+
+ RA_SESSION is an RA session open to the repository in which TARGET_ABSPATH
+ lives. It may be temporarily reparented as needed by this function.
+
+ Allocate *RECORDED_MERGEINFO and *IMPLICIT_MERGEINFO in RESULT_POOL.
+ Use SCRATCH_POOL for any temporary allocations. */
+static svn_error_t *
+get_full_mergeinfo(svn_mergeinfo_t *recorded_mergeinfo,
+ svn_mergeinfo_t *implicit_mergeinfo,
+ svn_boolean_t *inherited,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_ra_session_t *ra_session,
+ const char *target_abspath,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* First, we get the real mergeinfo. */
+ if (recorded_mergeinfo)
+ {
+ SVN_ERR(svn_client__get_wc_or_repos_mergeinfo(recorded_mergeinfo,
+ inherited,
+ NULL /* from_repos */,
+ FALSE,
+ inherit, ra_session,
+ target_abspath,
+ ctx, result_pool));
+ }
+
+ if (implicit_mergeinfo)
+ {
+ svn_client__pathrev_t *target;
+
+ /* Assert that we have sane input. */
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start) && SVN_IS_VALID_REVNUM(end)
+ && (start > end));
+
+ /* Retrieve the origin (original_*) of the node, or just the
+ url if the node was not copied. */
+ SVN_ERR(svn_client__wc_node_get_origin(&target, target_abspath, ctx,
+ scratch_pool, scratch_pool));
+
+ if (! target)
+ {
+ /* We've been asked to operate on a locally added target, so its
+ * implicit mergeinfo is empty. */
+ *implicit_mergeinfo = apr_hash_make(result_pool);
+ }
+ else if (target->rev <= end)
+ {
+ /* We're asking about a range outside our natural history
+ altogether. That means our implicit mergeinfo is empty. */
+ *implicit_mergeinfo = apr_hash_make(result_pool);
+ }
+ else
+ {
+ /* Fetch so-called "implicit mergeinfo" (that is, natural
+ history). */
+
+ /* Do not ask for implicit mergeinfo from TARGET_ABSPATH's future.
+ TARGET_ABSPATH might not even exist, and even if it does the
+ working copy is *at* TARGET_REV so its implicit history ends
+ at TARGET_REV! */
+ if (target->rev < start)
+ start = target->rev;
+
+ /* Fetch the implicit mergeinfo. */
+ SVN_ERR(svn_client__get_history_as_mergeinfo(implicit_mergeinfo,
+ NULL,
+ target, start, end,
+ ra_session, ctx,
+ result_pool));
+ }
+ } /*if (implicit_mergeinfo) */
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for ensure_implicit_mergeinfo().
+
+ PARENT, CHILD, REVISION1, REVISION2 and CTX
+ are all cascaded from the arguments of the same names in
+ ensure_implicit_mergeinfo(). PARENT and CHILD must both exist, i.e.
+ this function should never be called where CHILD is the merge target.
+
+ If PARENT->IMPLICIT_MERGEINFO is NULL, obtain it from the server.
+
+ Set CHILD->IMPLICIT_MERGEINFO to the mergeinfo inherited from
+ PARENT->IMPLICIT_MERGEINFO. CHILD->IMPLICIT_MERGEINFO is allocated
+ in RESULT_POOL.
+
+ RA_SESSION is an RA session open to the repository that contains CHILD.
+ It may be temporarily reparented by this function.
+ */
+static svn_error_t *
+inherit_implicit_mergeinfo_from_parent(svn_client__merge_path_t *parent,
+ svn_client__merge_path_t *child,
+ svn_revnum_t revision1,
+ svn_revnum_t revision2,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *path_diff;
+
+ /* This only works on subtrees! */
+ SVN_ERR_ASSERT(parent);
+ SVN_ERR_ASSERT(child);
+
+ /* While PARENT must exist, it is possible we've deferred
+ getting its implicit mergeinfo. If so get it now. */
+ if (!parent->implicit_mergeinfo)
+ SVN_ERR(get_full_mergeinfo(NULL, &(parent->implicit_mergeinfo),
+ NULL, svn_mergeinfo_inherited,
+ ra_session, child->abspath,
+ MAX(revision1, revision2),
+ MIN(revision1, revision2),
+ ctx, result_pool, scratch_pool));
+
+ /* Let CHILD inherit PARENT's implicit mergeinfo. */
+
+ path_diff = svn_dirent_is_child(parent->abspath, child->abspath,
+ scratch_pool);
+ /* PARENT->PATH better be an ancestor of CHILD->ABSPATH! */
+ SVN_ERR_ASSERT(path_diff);
+
+ SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
+ &child->implicit_mergeinfo, parent->implicit_mergeinfo,
+ path_diff, result_pool, scratch_pool));
+ child->implicit_mergeinfo = svn_mergeinfo_dup(child->implicit_mergeinfo,
+ result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Helper of filter_merged_revisions().
+
+ If we have deferred obtaining CHILD->IMPLICIT_MERGEINFO, then get
+ it now, allocating it in RESULT_POOL. If CHILD_INHERITS_PARENT is true
+ then set CHILD->IMPLICIT_MERGEINFO to the mergeinfo inherited from
+ PARENT->IMPLICIT_MERGEINFO, otherwise contact the repository. Use
+ SCRATCH_POOL for all temporary allocations.
+
+ RA_SESSION is an RA session open to the repository that contains CHILD.
+ It may be temporarily reparented by this function.
+
+ PARENT, CHILD, REVISION1, REVISION2 and
+ CTX are all cascaded from the arguments of the same name in
+ filter_merged_revisions() and the same conditions for that function
+ hold here. */
+static svn_error_t *
+ensure_implicit_mergeinfo(svn_client__merge_path_t *parent,
+ svn_client__merge_path_t *child,
+ svn_boolean_t child_inherits_parent,
+ svn_revnum_t revision1,
+ svn_revnum_t revision2,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* If we haven't already found CHILD->IMPLICIT_MERGEINFO then
+ contact the server to get it. */
+
+ if (child->implicit_mergeinfo)
+ return SVN_NO_ERROR;
+
+ if (child_inherits_parent)
+ SVN_ERR(inherit_implicit_mergeinfo_from_parent(parent,
+ child,
+ revision1,
+ revision2,
+ ra_session,
+ ctx,
+ result_pool,
+ scratch_pool));
+ else
+ SVN_ERR(get_full_mergeinfo(NULL,
+ &(child->implicit_mergeinfo),
+ NULL, svn_mergeinfo_inherited,
+ ra_session, child->abspath,
+ MAX(revision1, revision2),
+ MIN(revision1, revision2),
+ ctx, result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for calculate_remaining_ranges().
+
+ Initialize CHILD->REMAINING_RANGES to a rangelist representing the
+ requested merge of REVISION1:REVISION2 from MERGEINFO_PATH to
+ CHILD->ABSPATH.
+
+ For forward merges remove any ranges from CHILD->REMAINING_RANGES that
+ have already been merged to CHILD->ABSPATH per TARGET_MERGEINFO or
+ CHILD->IMPLICIT_MERGEINFO. For reverse merges remove any ranges from
+ CHILD->REMAINING_RANGES that have not already been merged to CHILD->ABSPATH
+ per TARGET_MERGEINFO or CHILD->IMPLICIT_MERGEINFO. If we have deferred
+ obtaining CHILD->IMPLICIT_MERGEINFO and it is necessary to use it for
+ these calculations, then get it from the server, allocating it in
+ RESULT_POOL.
+
+ CHILD represents a working copy path which is the merge target or one of
+ the target's subtrees. If not NULL, PARENT is CHILD's nearest path-wise
+ ancestor - see 'THE CHILDREN_WITH_MERGEINFO ARRAY'.
+
+ If the function needs to consider CHILD->IMPLICIT_MERGEINFO and
+ CHILD_INHERITS_IMPLICIT is true, then set CHILD->IMPLICIT_MERGEINFO to the
+ mergeinfo inherited from PARENT->IMPLICIT_MERGEINFO. Otherwise contact
+ the repository for CHILD->IMPLICIT_MERGEINFO.
+
+ NOTE: If PARENT is present then this function must have previously been
+ called for PARENT, i.e. if populate_remaining_ranges() is calling this
+ function for a set of svn_client__merge_path_t* the calls must be made
+ in depth-first order.
+
+ MERGEINFO_PATH is the merge source relative to the repository root.
+
+ REVISION1 and REVISION2 describe the merge range requested from
+ MERGEINFO_PATH.
+
+ TARGET_RANGELIST is the portion of CHILD->ABSPATH's explicit or inherited
+ mergeinfo that intersects with the merge history described by
+ MERGEINFO_PATH@REVISION1:MERGEINFO_PATH@REVISION2. TARGET_RANGELIST
+ should be NULL if there is no explicit or inherited mergeinfo on
+ CHILD->ABSPATH or an empty list if CHILD->ABSPATH has empty mergeinfo or
+ explicit mergeinfo that exclusively describes non-intersecting history
+ with MERGEINFO_PATH@REVISION1:MERGEINFO_PATH@REVISION2.
+
+ SCRATCH_POOL is used for all temporary allocations.
+
+ NOTE: This should only be called when honoring mergeinfo.
+
+ NOTE: Like calculate_remaining_ranges() if PARENT is present then this
+ function must have previously been called for PARENT.
+*/
+static svn_error_t *
+filter_merged_revisions(svn_client__merge_path_t *parent,
+ svn_client__merge_path_t *child,
+ const char *mergeinfo_path,
+ svn_rangelist_t *target_rangelist,
+ svn_revnum_t revision1,
+ svn_revnum_t revision2,
+ svn_boolean_t child_inherits_implicit,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_rangelist_t *requested_rangelist,
+ *target_implicit_rangelist, *explicit_rangelist;
+
+ /* Convert REVISION1 and REVISION2 to a rangelist.
+
+ Note: Talking about a requested merge range's inheritability
+ doesn't make much sense, but as we are using svn_merge_range_t
+ to describe it we need to pick *something*. Since all the
+ rangelist manipulations in this function either don't consider
+ inheritance by default or we are requesting that they don't (i.e.
+ svn_rangelist_remove and svn_rangelist_intersect) then we could
+ set the inheritability as FALSE, it won't matter either way. */
+ requested_rangelist = svn_rangelist__initialize(revision1, revision2,
+ TRUE, scratch_pool);
+
+ /* Now filter out revisions that have already been merged to CHILD. */
+
+ if (revision1 > revision2) /* This is a reverse merge. */
+ {
+ svn_rangelist_t *added_rangelist, *deleted_rangelist;
+
+ /* The revert range and will need to be reversed for
+ our svn_rangelist_* APIs to work properly. */
+ SVN_ERR(svn_rangelist_reverse(requested_rangelist, scratch_pool));
+
+ /* Set EXPLICIT_RANGELIST to the list of source-range revs that are
+ already recorded as merged to target. */
+ if (target_rangelist)
+ {
+ /* Return the intersection of the revs which are both already
+ represented by CHILD's explicit or inherited mergeinfo.
+
+ We don't consider inheritance when determining intersecting
+ ranges. If we *did* consider inheritance, then our calculation
+ would be wrong. For example, if the CHILD->REMAINING_RANGES is
+ 5:3 and TARGET_RANGELIST is r5* (non-inheritable) then the
+ intersection would be r4. And that would be wrong as we clearly
+ want to reverse merge both r4 and r5 in this case. Ignoring the
+ ranges' inheritance results in an intersection of r4-5.
+
+ You might be wondering about CHILD's children, doesn't the above
+ imply that we will reverse merge r4-5 from them? Nope, this is
+ safe to do because any path whose parent has non-inheritable
+ ranges is always considered a subtree with differing mergeinfo
+ even if that path has no explicit mergeinfo prior to the
+ merge -- See condition 3 in the doc string for
+ merge.c:get_mergeinfo_paths(). */
+ SVN_ERR(svn_rangelist_intersect(&explicit_rangelist,
+ target_rangelist,
+ requested_rangelist,
+ FALSE, scratch_pool));
+ }
+ else
+ {
+ explicit_rangelist =
+ apr_array_make(result_pool, 0, sizeof(svn_merge_range_t *));
+ }
+
+ /* Was any part of the requested reverse merge not accounted for in
+ CHILD's explicit or inherited mergeinfo? */
+ SVN_ERR(svn_rangelist_diff(&deleted_rangelist, &added_rangelist,
+ requested_rangelist, explicit_rangelist,
+ FALSE, scratch_pool));
+
+ if (deleted_rangelist->nelts == 0)
+ {
+ /* The whole of REVISION1:REVISION2 was represented in CHILD's
+ explicit/inherited mergeinfo, allocate CHILD's remaining
+ ranges in POOL and then we are done. */
+ SVN_ERR(svn_rangelist_reverse(requested_rangelist, scratch_pool));
+ child->remaining_ranges = svn_rangelist_dup(requested_rangelist,
+ result_pool);
+ }
+ else /* We need to check CHILD's implicit mergeinfo. */
+ {
+ svn_rangelist_t *implicit_rangelist;
+
+ SVN_ERR(ensure_implicit_mergeinfo(parent,
+ child,
+ child_inherits_implicit,
+ revision1,
+ revision2,
+ ra_session,
+ ctx,
+ result_pool,
+ scratch_pool));
+
+ target_implicit_rangelist = svn_hash_gets(child->implicit_mergeinfo,
+ mergeinfo_path);
+
+ if (target_implicit_rangelist)
+ SVN_ERR(svn_rangelist_intersect(&implicit_rangelist,
+ target_implicit_rangelist,
+ requested_rangelist,
+ FALSE, scratch_pool));
+ else
+ implicit_rangelist = apr_array_make(scratch_pool, 0,
+ sizeof(svn_merge_range_t *));
+
+ SVN_ERR(svn_rangelist_merge2(implicit_rangelist,
+ explicit_rangelist, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_rangelist_reverse(implicit_rangelist, scratch_pool));
+ child->remaining_ranges = svn_rangelist_dup(implicit_rangelist,
+ result_pool);
+ }
+ }
+ else /* This is a forward merge */
+ {
+ /* Set EXPLICIT_RANGELIST to the list of source-range revs that are
+ NOT already recorded as merged to target. */
+ if (target_rangelist)
+ {
+ /* See earlier comment preceding svn_rangelist_intersect() for
+ why we don't consider inheritance here. */
+ SVN_ERR(svn_rangelist_remove(&explicit_rangelist,
+ target_rangelist,
+ requested_rangelist, FALSE,
+ scratch_pool));
+ }
+ else
+ {
+ explicit_rangelist = svn_rangelist_dup(requested_rangelist,
+ scratch_pool);
+ }
+
+ if (explicit_rangelist->nelts == 0)
+ {
+ child->remaining_ranges =
+ apr_array_make(result_pool, 0, sizeof(svn_merge_range_t *));
+ }
+ else
+/* ### TODO: Which evil shall we choose?
+ ###
+ ### If we allow all forward-merges not already found in recorded
+ ### mergeinfo, we destroy the ability to, say, merge the whole of a
+ ### branch to the trunk while automatically ignoring the revisions
+ ### common to both. That's bad.
+ ###
+ ### If we allow only forward-merges not found in either recorded
+ ### mergeinfo or implicit mergeinfo (natural history), then the
+ ### previous scenario works great, but we can't reverse-merge a
+ ### previous change made to our line of history and then remake it
+ ### (because the reverse-merge will leave no mergeinfo trace, and
+ ### the remake-it attempt will still find the original change in
+ ### natural mergeinfo. But you know, that we happen to use 'merge'
+ ### for revision undoing is somewhat unnatural anyway, so I'm
+ ### finding myself having little interest in caring too much about
+ ### this. That said, if we had a way of storing reverse merge
+ ### ranges, we'd be in good shape either way.
+*/
+#ifdef SVN_MERGE__ALLOW_ALL_FORWARD_MERGES_FROM_SELF
+ {
+ /* ### Don't consider implicit mergeinfo. */
+ child->remaining_ranges = svn_rangelist_dup(explicit_rangelist,
+ pool);
+ }
+#else
+ {
+ /* Based on CHILD's TARGET_MERGEINFO there are ranges to merge.
+ Check CHILD's implicit mergeinfo to see if these remaining
+ ranges are represented there. */
+ SVN_ERR(ensure_implicit_mergeinfo(parent,
+ child,
+ child_inherits_implicit,
+ revision1,
+ revision2,
+ ra_session,
+ ctx,
+ result_pool,
+ scratch_pool));
+
+ target_implicit_rangelist = svn_hash_gets(child->implicit_mergeinfo,
+ mergeinfo_path);
+ if (target_implicit_rangelist)
+ SVN_ERR(svn_rangelist_remove(&(child->remaining_ranges),
+ target_implicit_rangelist,
+ explicit_rangelist,
+ FALSE, result_pool));
+ else
+ child->remaining_ranges = svn_rangelist_dup(explicit_rangelist,
+ result_pool);
+ }
+#endif
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for do_file_merge and do_directory_merge (by way of
+ populate_remaining_ranges() for the latter).
+
+ Determine what portions of SOURCE have already
+ been merged to CHILD->ABSPATH and populate CHILD->REMAINING_RANGES with
+ the ranges that still need merging.
+
+ SOURCE and CTX are all cascaded from the caller's arguments of the same
+ names. Note that this means SOURCE adheres to the requirements noted in
+ `MERGEINFO MERGE SOURCE NORMALIZATION'.
+
+ CHILD represents a working copy path which is the merge target or one of
+ the target's subtrees. If not NULL, PARENT is CHILD's nearest path-wise
+ ancestor - see 'THE CHILDREN_WITH_MERGEINFO ARRAY'. TARGET_MERGEINFO is
+ the working mergeinfo on CHILD.
+
+ RA_SESSION is the session for the younger of SOURCE->loc1 and
+ SOURCE->loc2.
+
+ If the function needs to consider CHILD->IMPLICIT_MERGEINFO and
+ CHILD_INHERITS_IMPLICIT is true, then set CHILD->IMPLICIT_MERGEINFO to the
+ mergeinfo inherited from PARENT->IMPLICIT_MERGEINFO. Otherwise contact
+ the repository for CHILD->IMPLICIT_MERGEINFO.
+
+ If not null, IMPLICIT_SRC_GAP is the gap, if any, in the natural history
+ of SOURCE, see merge_cmd_baton_t.implicit_src_gap.
+
+ SCRATCH_POOL is used for all temporary allocations. Changes to CHILD and
+ PARENT are made in RESULT_POOL.
+
+ NOTE: This should only be called when honoring mergeinfo.
+
+ NOTE: If PARENT is present then this function must have previously been
+ called for PARENT, i.e. if populate_remaining_ranges() is calling this
+ function for a set of svn_client__merge_path_t* the calls must be made
+ in depth-first order.
+
+ NOTE: When performing reverse merges, return
+ SVN_ERR_CLIENT_NOT_READY_TO_MERGE if both locations in SOURCE and
+ CHILD->ABSPATH are all on the same line of history but CHILD->ABSPATH's
+ base revision is older than the SOURCE->rev1:rev2 range, see comment re
+ issue #2973 below.
+*/
+static svn_error_t *
+calculate_remaining_ranges(svn_client__merge_path_t *parent,
+ svn_client__merge_path_t *child,
+ const merge_source_t *source,
+ svn_mergeinfo_t target_mergeinfo,
+ const apr_array_header_t *implicit_src_gap,
+ svn_boolean_t child_inherits_implicit,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_client__pathrev_t *primary_src
+ = (source->loc1->rev < source->loc2->rev) ? source->loc2 : source->loc1;
+ const char *mergeinfo_path = svn_client__pathrev_fspath(primary_src,
+ scratch_pool);
+ /* Intersection of TARGET_MERGEINFO and the merge history
+ described by SOURCE. */
+ svn_rangelist_t *target_rangelist;
+ svn_revnum_t child_base_revision;
+
+ /* Since this function should only be called when honoring mergeinfo and
+ * SOURCE adheres to the requirements noted in 'MERGEINFO MERGE SOURCE
+ * NORMALIZATION', SOURCE must be 'ancestral'. */
+ SVN_ERR_ASSERT(source->ancestral);
+
+ /* Determine which of the requested ranges to consider merging... */
+
+ /* Set TARGET_RANGELIST to the portion of TARGET_MERGEINFO that refers
+ to SOURCE (excluding any gap in SOURCE): first get all ranges from
+ TARGET_MERGEINFO that refer to the path of SOURCE, and then prune
+ any ranges that lie in the gap in SOURCE.
+
+ ### [JAF] In fact, that may still leave some ranges that lie entirely
+ outside the range of SOURCE; it seems we don't care about that. */
+ if (target_mergeinfo)
+ target_rangelist = svn_hash_gets(target_mergeinfo, mergeinfo_path);
+ else
+ target_rangelist = NULL;
+ if (implicit_src_gap && target_rangelist)
+ {
+ /* Remove any mergeinfo referring to the 'gap' in SOURCE, as that
+ mergeinfo doesn't really refer to SOURCE at all but instead
+ refers to locations that are non-existent or on a different
+ line of history. (Issue #3242.) */
+ SVN_ERR(svn_rangelist_remove(&target_rangelist,
+ implicit_src_gap, target_rangelist,
+ FALSE, result_pool));
+ }
+
+ /* Initialize CHILD->REMAINING_RANGES and filter out revisions already
+ merged (or, in the case of reverse merges, ranges not yet merged). */
+ SVN_ERR(filter_merged_revisions(parent, child, mergeinfo_path,
+ target_rangelist,
+ source->loc1->rev, source->loc2->rev,
+ child_inherits_implicit,
+ ra_session, ctx, result_pool,
+ scratch_pool));
+
+ /* Issue #2973 -- from the continuing series of "Why, since the advent of
+ merge tracking, allowing merges into mixed rev and locally modified
+ working copies isn't simple and could be considered downright evil".
+
+ If reverse merging a range to the WC path represented by CHILD, from
+ that path's own history, where the path inherits no locally modified
+ mergeinfo from its WC parents (i.e. there is no uncommitted merge to
+ the WC), and the path's base revision is older than the range, then
+ the merge will always be a no-op. This is because we only allow reverse
+ merges of ranges in the path's explicit or natural mergeinfo and a
+ reverse merge from the path's future history obviously isn't going to be
+ in either, hence the no-op.
+
+ The problem is two-fold. First, in a mixed rev WC, the change we
+ want to revert might actually be to some child of the target path
+ which is at a younger base revision. Sure, we can merge directly
+ to that child or update the WC or even use --ignore-ancestry and then
+ successfully run the reverse merge, but that gets to the second
+ problem: Those courses of action are not very obvious. Before 1.5 if
+ a user committed a change that didn't touch the commit target, then
+ immediately decided to revert that change via a reverse merge it would
+ just DTRT. But with the advent of merge tracking the user gets a no-op.
+
+ So in the name of user friendliness, return an error suggesting a helpful
+ course of action.
+ */
+ SVN_ERR(svn_wc__node_get_base(NULL, &child_base_revision,
+ NULL, NULL, NULL, NULL,
+ ctx->wc_ctx, child->abspath,
+ TRUE /* ignore_enoent */,
+ FALSE /* show_hidden */,
+ scratch_pool, scratch_pool));
+ /* If CHILD has no base revision then it hasn't been committed yet, so it
+ can't have any "future" history. */
+ if (SVN_IS_VALID_REVNUM(child_base_revision)
+ && ((child->remaining_ranges)->nelts == 0) /* Inoperative merge */
+ && (source->loc2->rev < source->loc1->rev) /* Reverse merge */
+ && (child_base_revision <= source->loc2->rev)) /* From CHILD's future */
+ {
+ /* Hmmm, an inoperative reverse merge from the "future". If it is
+ from our own future return a helpful error. */
+ svn_error_t *err;
+ svn_client__pathrev_t *start_loc;
+
+ err = svn_client__repos_location(&start_loc,
+ ra_session,
+ source->loc1,
+ child_base_revision,
+ ctx, scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND
+ || err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ const char *url;
+
+ SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, child->abspath,
+ scratch_pool, scratch_pool));
+ if (strcmp(start_loc->url, url) == 0)
+ return svn_error_create(SVN_ERR_CLIENT_MERGE_UPDATE_REQUIRED, NULL,
+ _("Cannot reverse-merge a range from a "
+ "path's own future history; try "
+ "updating first"));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for populate_remaining_ranges().
+
+ SOURCE is cascaded from the arguments of the same name in
+ populate_remaining_ranges().
+
+ Note: The following comments assume a forward merge, i.e.
+ SOURCE->loc1->rev < SOURCE->loc2->rev. If this is a reverse merge then
+ all the following comments still apply, but with SOURCE->loc1 switched
+ with SOURCE->loc2.
+
+ Like populate_remaining_ranges(), SOURCE must adhere to the restrictions
+ documented in 'MERGEINFO MERGE SOURCE NORMALIZATION'. These restrictions
+ allow for a *single* gap in SOURCE, GAP_REV1:GAP_REV2 exclusive:inclusive
+ (where SOURCE->loc1->rev == GAP_REV1 <= GAP_REV2 < SOURCE->loc2->rev),
+ if SOURCE->loc2->url@(GAP_REV2+1) was copied from SOURCE->loc1. If such
+ a gap exists, set *GAP_START and *GAP_END to the starting and ending
+ revisions of the gap. Otherwise set both to SVN_INVALID_REVNUM.
+
+ For example, if the natural history of URL@2:URL@9 is 'trunk/:2,7-9' this
+ would indicate that trunk@7 was copied from trunk@2. This function would
+ return GAP_START:GAP_END of 2:6 in this case. Note that a path 'trunk'
+ might exist at r3-6, but it would not be on the same line of history as
+ trunk@9.
+
+ ### GAP_START is basically redundant, as (if there is a gap at all) it is
+ necessarily the older revision of SOURCE.
+
+ RA_SESSION is an open RA session to the repository in which SOURCE lives.
+*/
+static svn_error_t *
+find_gaps_in_merge_source_history(svn_revnum_t *gap_start,
+ svn_revnum_t *gap_end,
+ const merge_source_t *source,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_t implicit_src_mergeinfo;
+ svn_revnum_t old_rev = MIN(source->loc1->rev, source->loc2->rev);
+ const svn_client__pathrev_t *primary_src
+ = (source->loc1->rev < source->loc2->rev) ? source->loc2 : source->loc1;
+ const char *merge_src_fspath = svn_client__pathrev_fspath(primary_src,
+ scratch_pool);
+ svn_rangelist_t *rangelist;
+
+ SVN_ERR_ASSERT(source->ancestral);
+
+ /* Start by assuming there is no gap. */
+ *gap_start = *gap_end = SVN_INVALID_REVNUM;
+
+ /* Easy out: There can't be a gap between adjacent revisions. */
+ if (abs(source->loc1->rev - source->loc2->rev) == 1)
+ return SVN_NO_ERROR;
+
+ /* Get SOURCE as mergeinfo. */
+ SVN_ERR(svn_client__get_history_as_mergeinfo(&implicit_src_mergeinfo, NULL,
+ primary_src,
+ primary_src->rev, old_rev,
+ ra_session,
+ ctx, scratch_pool));
+
+ rangelist = svn_hash_gets(implicit_src_mergeinfo, merge_src_fspath);
+
+ if (!rangelist) /* ### Can we ever not find a rangelist? */
+ return SVN_NO_ERROR;
+
+ /* A gap in natural history can result from either a copy or
+ a rename. If from a copy then history as mergeinfo will look
+ something like this:
+
+ '/trunk:X,Y-Z'
+
+ If from a rename it will look like this:
+
+ '/trunk_old_name:X'
+ '/trunk_new_name:Y-Z'
+
+ In both cases the gap, if it exists, is M-N, where M = X + 1 and
+ N = Y - 1.
+
+ Note that per the rules of 'MERGEINFO MERGE SOURCE NORMALIZATION' we
+ should never have multiple gaps, e.g. if we see anything like the
+ following then something is quite wrong:
+
+ '/trunk_old_name:A,B-C'
+ '/trunk_new_name:D-E'
+ */
+
+ if (rangelist->nelts > 1) /* Copy */
+ {
+ const svn_merge_range_t *gap;
+ /* As mentioned above, multiple gaps *shouldn't* be possible. */
+ SVN_ERR_ASSERT(apr_hash_count(implicit_src_mergeinfo) == 1);
+
+ gap = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1,
+ const svn_merge_range_t *);
+
+ *gap_start = MIN(source->loc1->rev, source->loc2->rev);
+ *gap_end = gap->start;
+
+ /* ### Issue #4132:
+ ### This assertion triggers in merge_tests.py svnmucc_abuse_1()
+ ### when a node is replaced by an older copy of itself.
+
+ BH: I think we should review this and the 'rename' case to find
+ out which behavior we really want, and if we can really
+ determine what happened this way. */
+ SVN_ERR_ASSERT(*gap_start < *gap_end);
+ }
+ else if (apr_hash_count(implicit_src_mergeinfo) > 1) /* Rename */
+ {
+ svn_rangelist_t *requested_rangelist =
+ svn_rangelist__initialize(MIN(source->loc1->rev, source->loc2->rev),
+ MAX(source->loc1->rev, source->loc2->rev),
+ TRUE, scratch_pool);
+ svn_rangelist_t *implicit_rangelist =
+ apr_array_make(scratch_pool, 2, sizeof(svn_merge_range_t *));
+ svn_rangelist_t *gap_rangelist;
+
+ SVN_ERR(svn_rangelist__merge_many(implicit_rangelist,
+ implicit_src_mergeinfo,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_rangelist_remove(&gap_rangelist, implicit_rangelist,
+ requested_rangelist, FALSE,
+ scratch_pool));
+
+ /* If there is anything left it is the gap. */
+ if (gap_rangelist->nelts)
+ {
+ svn_merge_range_t *gap_range =
+ APR_ARRAY_IDX(gap_rangelist, 0, svn_merge_range_t *);
+
+ *gap_start = gap_range->start;
+ *gap_end = gap_range->end;
+ }
+ }
+
+ SVN_ERR_ASSERT(*gap_start == MIN(source->loc1->rev, source->loc2->rev)
+ || (*gap_start == SVN_INVALID_REVNUM
+ && *gap_end == SVN_INVALID_REVNUM));
+ return SVN_NO_ERROR;
+}
+
+/* Helper for do_directory_merge().
+
+ For each (svn_client__merge_path_t *) child in CHILDREN_WITH_MERGEINFO,
+ populate that child's 'remaining_ranges' list with (### ... what?),
+ and populate that child's 'implicit_mergeinfo' with its implicit
+ mergeinfo (natural history). CHILDREN_WITH_MERGEINFO is expected
+ to be sorted in depth first order and each child must be processed in
+ that order. The inheritability of all calculated ranges is TRUE.
+
+ If mergeinfo is being honored (based on MERGE_B -- see HONOR_MERGEINFO()
+ for how this is determined), this function will actually try to be
+ intelligent about populating remaining_ranges list. Otherwise, it
+ will claim that each child has a single remaining range, from
+ SOURCE->rev1, to SOURCE->rev2.
+ ### We also take the short-cut if doing record-only. Why?
+
+ SCRATCH_POOL is used for all temporary allocations. Changes to
+ CHILDREN_WITH_MERGEINFO are made in RESULT_POOL.
+
+ Note that if SOURCE->rev1 > SOURCE->rev2, then each child's remaining_ranges
+ member does not adhere to the API rules for rangelists described in
+ svn_mergeinfo.h -- See svn_client__merge_path_t.
+
+ See `MERGEINFO MERGE SOURCE NORMALIZATION' for more requirements
+ around SOURCE.
+*/
+static svn_error_t *
+populate_remaining_ranges(apr_array_header_t *children_with_mergeinfo,
+ const merge_source_t *source,
+ svn_ra_session_t *ra_session,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+ svn_revnum_t gap_start, gap_end;
+
+ /* If we aren't honoring mergeinfo or this is a --record-only merge,
+ we'll make quick work of this by simply adding dummy SOURCE->rev1:rev2
+ ranges for all children. */
+ if (! HONOR_MERGEINFO(merge_b) || merge_b->record_only)
+ {
+ for (i = 0; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i,
+ svn_client__merge_path_t *);
+
+ svn_pool_clear(iterpool);
+
+ /* Issue #3646 'record-only merges create self-referential
+ mergeinfo'. Get the merge target's implicit mergeinfo (natural
+ history). We'll use it later to avoid setting self-referential
+ mergeinfo -- see filter_natural_history_from_mergeinfo(). */
+ if (i == 0) /* First item is always the merge target. */
+ {
+ SVN_ERR(get_full_mergeinfo(NULL, /* child->pre_merge_mergeinfo */
+ &(child->implicit_mergeinfo),
+ NULL, /* child->inherited_mergeinfo */
+ svn_mergeinfo_inherited, ra_session,
+ child->abspath,
+ MAX(source->loc1->rev,
+ source->loc2->rev),
+ MIN(source->loc1->rev,
+ source->loc2->rev),
+ merge_b->ctx, result_pool,
+ iterpool));
+ }
+ else
+ {
+ /* Issue #3443 - Subtrees of the merge target can inherit
+ their parent's implicit mergeinfo in most cases. */
+ svn_client__merge_path_t *parent
+ = find_nearest_ancestor(children_with_mergeinfo,
+ FALSE, child->abspath);
+ svn_boolean_t child_inherits_implicit;
+
+ /* If CHILD is a subtree then its parent must be in
+ CHILDREN_WITH_MERGEINFO, see the global comment
+ 'THE CHILDREN_WITH_MERGEINFO ARRAY'. */
+ SVN_ERR_ASSERT(parent);
+
+ child_inherits_implicit = (parent && !child->switched);
+ SVN_ERR(ensure_implicit_mergeinfo(parent, child,
+ child_inherits_implicit,
+ source->loc1->rev,
+ source->loc2->rev,
+ ra_session, merge_b->ctx,
+ result_pool, iterpool));
+ }
+
+ child->remaining_ranges = svn_rangelist__initialize(source->loc1->rev,
+ source->loc2->rev,
+ TRUE,
+ result_pool);
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+
+ /* If, in the merge source's history, there was a copy from an older
+ revision, then SOURCE->loc2->url won't exist at some range M:N, where
+ SOURCE->loc1->rev < M < N < SOURCE->loc2->rev. The rules of 'MERGEINFO
+ MERGE SOURCE NORMALIZATION' allow this, but we must ignore these gaps
+ when calculating what ranges remain to be merged from SOURCE. If we
+ don't and try to merge any part of SOURCE->loc2->url@M:N we would
+ break the editor since no part of that actually exists. See
+ http://svn.haxx.se/dev/archive-2008-11/0618.shtml.
+
+ Find the gaps in the merge target's history, if any. Eventually
+ we will adjust CHILD->REMAINING_RANGES such that we don't describe
+ non-existent paths to the editor. */
+ SVN_ERR(find_gaps_in_merge_source_history(&gap_start, &gap_end,
+ source,
+ ra_session, merge_b->ctx,
+ iterpool));
+
+ /* Stash any gap in the merge command baton, we'll need it later when
+ recording mergeinfo describing this merge. */
+ if (SVN_IS_VALID_REVNUM(gap_start) && SVN_IS_VALID_REVNUM(gap_end))
+ merge_b->implicit_src_gap = svn_rangelist__initialize(gap_start, gap_end,
+ TRUE, result_pool);
+
+ for (i = 0; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
+ const char *child_repos_path
+ = svn_dirent_skip_ancestor(merge_b->target->abspath, child->abspath);
+ merge_source_t child_source;
+ svn_client__merge_path_t *parent = NULL;
+ svn_boolean_t child_inherits_implicit;
+
+ svn_pool_clear(iterpool);
+
+ /* If the path is absent don't do subtree merge either. */
+ SVN_ERR_ASSERT(child);
+ if (child->absent)
+ continue;
+
+ SVN_ERR_ASSERT(child_repos_path != NULL);
+ child_source.loc1 = svn_client__pathrev_join_relpath(
+ source->loc1, child_repos_path, iterpool);
+ child_source.loc2 = svn_client__pathrev_join_relpath(
+ source->loc2, child_repos_path, iterpool);
+ /* ### Is the child 'ancestral' over the same revision range? It's
+ * not necessarily true that a child is 'ancestral' if the parent is,
+ * nor that it's not if the parent is not. However, here we claim
+ * that it is. Before we had this 'ancestral' field that we need to
+ * set explicitly, the claim was implicit. Either way, the impact is
+ * that we might pass calculate_remaining_ranges() a source that is
+ * not in fact 'ancestral' (despite its 'ancestral' field being true),
+ * contrary to its doc-string. */
+ child_source.ancestral = source->ancestral;
+
+ /* Get the explicit/inherited mergeinfo for CHILD. If CHILD is the
+ merge target then also get its implicit mergeinfo. Otherwise defer
+ this until we know it is absolutely necessary, since it requires an
+ expensive round trip communication with the server. */
+ SVN_ERR(get_full_mergeinfo(
+ child->pre_merge_mergeinfo ? NULL : &(child->pre_merge_mergeinfo),
+ /* Get implicit only for merge target. */
+ (i == 0) ? &(child->implicit_mergeinfo) : NULL,
+ &(child->inherited_mergeinfo),
+ svn_mergeinfo_inherited, ra_session,
+ child->abspath,
+ MAX(source->loc1->rev, source->loc2->rev),
+ MIN(source->loc1->rev, source->loc2->rev),
+ merge_b->ctx, result_pool, iterpool));
+
+ /* If CHILD isn't the merge target find its parent. */
+ if (i > 0)
+ {
+ parent = find_nearest_ancestor(children_with_mergeinfo,
+ FALSE, child->abspath);
+ /* If CHILD is a subtree then its parent must be in
+ CHILDREN_WITH_MERGEINFO, see the global comment
+ 'THE CHILDREN_WITH_MERGEINFO ARRAY'. */
+ SVN_ERR_ASSERT(parent);
+ }
+
+ /* Issue #3443 - Can CHILD inherit PARENT's implicit mergeinfo, saving
+ us from having to ask the repos? The only time we can't do this is if
+ CHILD is the merge target and so there is no PARENT to inherit from
+ or if CHILD is the root of a switched subtree, in which case PARENT
+ exists but is not CHILD's repository parent. */
+ child_inherits_implicit = (parent && !child->switched);
+
+ SVN_ERR(calculate_remaining_ranges(parent, child,
+ &child_source,
+ child->pre_merge_mergeinfo,
+ merge_b->implicit_src_gap,
+ child_inherits_implicit,
+ ra_session,
+ merge_b->ctx, result_pool,
+ iterpool));
+
+ /* Deal with any gap in SOURCE's natural history.
+
+ If the gap is a proper subset of CHILD->REMAINING_RANGES then we can
+ safely ignore it since we won't describe this path/rev pair.
+
+ If the gap exactly matches or is a superset of a range in
+ CHILD->REMAINING_RANGES then we must remove that range so we don't
+ attempt to describe non-existent paths via the reporter, this will
+ break the editor and our merge.
+
+ If the gap adjoins or overlaps a range in CHILD->REMAINING_RANGES
+ then we must *add* the gap so we span the missing revisions. */
+ if (child->remaining_ranges->nelts
+ && merge_b->implicit_src_gap)
+ {
+ int j;
+ svn_boolean_t proper_subset = FALSE;
+ svn_boolean_t overlaps_or_adjoins = FALSE;
+
+ /* If this is a reverse merge reorder CHILD->REMAINING_RANGES
+ so it will work with the svn_rangelist_* APIs below. */
+ if (source->loc1->rev > source->loc2->rev)
+ SVN_ERR(svn_rangelist_reverse(child->remaining_ranges, iterpool));
+
+ for (j = 0; j < child->remaining_ranges->nelts; j++)
+ {
+ svn_merge_range_t *range
+ = APR_ARRAY_IDX(child->remaining_ranges, j, svn_merge_range_t *);
+
+ if ((range->start <= gap_start && gap_end < range->end)
+ || (range->start < gap_start && gap_end <= range->end))
+ {
+ proper_subset = TRUE;
+ break;
+ }
+ else if ((gap_start == range->start) && (range->end == gap_end))
+ {
+ break;
+ }
+ else if (gap_start <= range->end && range->start <= gap_end)
+ /* intersect */
+ {
+ overlaps_or_adjoins = TRUE;
+ break;
+ }
+ }
+
+ if (!proper_subset)
+ {
+ /* We need to make adjustments. Remove from, or add the gap
+ to, CHILD->REMAINING_RANGES as appropriate. */
+
+ if (overlaps_or_adjoins)
+ SVN_ERR(svn_rangelist_merge2(child->remaining_ranges,
+ merge_b->implicit_src_gap,
+ result_pool, iterpool));
+ else /* equals == TRUE */
+ SVN_ERR(svn_rangelist_remove(&(child->remaining_ranges),
+ merge_b->implicit_src_gap,
+ child->remaining_ranges, FALSE,
+ result_pool));
+ }
+
+ if (source->loc1->rev > source->loc2->rev) /* Reverse merge */
+ SVN_ERR(svn_rangelist_reverse(child->remaining_ranges, iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/*-----------------------------------------------------------------------*/
+
+/*** Other Helper Functions ***/
+
+/* Calculate the new mergeinfo for the target tree rooted at TARGET_ABSPATH
+ based on MERGES (a mapping of absolute WC paths to rangelists representing
+ a merge from the source SOURCE_FSPATH).
+
+ If RESULT_CATALOG is NULL, then record the new mergeinfo in the WC (at,
+ and possibly below, TARGET_ABSPATH).
+
+ If RESULT_CATALOG is not NULL, then don't record the new mergeinfo on the
+ WC, but instead record it in RESULT_CATALOG, where the keys are absolute
+ working copy paths and the values are the new mergeinfos for each.
+ Allocate additions to RESULT_CATALOG in pool which RESULT_CATALOG was
+ created in. */
+static svn_error_t *
+update_wc_mergeinfo(svn_mergeinfo_catalog_t result_catalog,
+ const char *target_abspath,
+ const char *source_fspath,
+ apr_hash_t *merges,
+ svn_boolean_t is_rollback,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+
+ /* Combine the mergeinfo for the revision range just merged into
+ the WC with its on-disk mergeinfo. */
+ for (hi = apr_hash_first(scratch_pool, merges); hi; hi = apr_hash_next(hi))
+ {
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *ranges = svn__apr_hash_index_val(hi);
+ svn_rangelist_t *rangelist;
+ svn_error_t *err;
+ const char *local_abspath_rel_to_target;
+ const char *fspath;
+ svn_mergeinfo_t mergeinfo;
+
+ svn_pool_clear(iterpool);
+
+ /* As some of the merges may've changed the WC's mergeinfo, get
+ a fresh copy before using it to update the WC's mergeinfo. */
+ err = svn_client__parse_mergeinfo(&mergeinfo, ctx->wc_ctx,
+ local_abspath, iterpool, iterpool);
+
+ /* If a directory PATH was skipped because it is missing or was
+ obstructed by an unversioned item then there's nothing we can
+ do with that, so skip it. */
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_NOT_LOCKED
+ || err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ continue;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ /* If we are attempting to set empty revision range override mergeinfo
+ on a path with no explicit mergeinfo, we first need the
+ mergeinfo that path inherits. */
+ if (mergeinfo == NULL && ranges->nelts == 0)
+ {
+ SVN_ERR(svn_client__get_wc_mergeinfo(&mergeinfo, NULL,
+ svn_mergeinfo_nearest_ancestor,
+ local_abspath, NULL, NULL,
+ FALSE, ctx, iterpool, iterpool));
+ }
+
+ if (mergeinfo == NULL)
+ mergeinfo = apr_hash_make(iterpool);
+
+ local_abspath_rel_to_target = svn_dirent_skip_ancestor(target_abspath,
+ local_abspath);
+ SVN_ERR_ASSERT(local_abspath_rel_to_target != NULL);
+ fspath = svn_fspath__join(source_fspath,
+ local_abspath_rel_to_target,
+ iterpool);
+ rangelist = svn_hash_gets(mergeinfo, fspath);
+ if (rangelist == NULL)
+ rangelist = apr_array_make(iterpool, 0, sizeof(svn_merge_range_t *));
+
+ if (is_rollback)
+ {
+ ranges = svn_rangelist_dup(ranges, iterpool);
+ SVN_ERR(svn_rangelist_reverse(ranges, iterpool));
+ SVN_ERR(svn_rangelist_remove(&rangelist, ranges, rangelist,
+ FALSE,
+ iterpool));
+ }
+ else
+ {
+ SVN_ERR(svn_rangelist_merge2(rangelist, ranges, iterpool, iterpool));
+ }
+ /* Update the mergeinfo by adjusting the path's rangelist. */
+ svn_hash_sets(mergeinfo, fspath, rangelist);
+
+ if (is_rollback && apr_hash_count(mergeinfo) == 0)
+ mergeinfo = NULL;
+
+ svn_mergeinfo__remove_empty_rangelists(mergeinfo, scratch_pool);
+
+ if (result_catalog)
+ {
+ svn_mergeinfo_t existing_mergeinfo =
+ svn_hash_gets(result_catalog, local_abspath);
+ apr_pool_t *result_catalog_pool = apr_hash_pool_get(result_catalog);
+
+ if (existing_mergeinfo)
+ SVN_ERR(svn_mergeinfo_merge2(mergeinfo, existing_mergeinfo,
+ result_catalog_pool, scratch_pool));
+ svn_hash_sets(result_catalog,
+ apr_pstrdup(result_catalog_pool, local_abspath),
+ svn_mergeinfo_dup(mergeinfo, result_catalog_pool));
+ }
+ else
+ {
+ err = svn_client__record_wc_mergeinfo(local_abspath, mergeinfo,
+ TRUE, ctx, iterpool);
+
+ if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
+ {
+ /* PATH isn't just missing, it's not even versioned as far
+ as this working copy knows. But it was included in
+ MERGES, which means that the server knows about it.
+ Likely we don't have access to the source due to authz
+ restrictions. For now just clear the error and
+ continue...
+
+ ### TODO: Set non-inheritable mergeinfo on PATH's immediate
+ ### parent and normal mergeinfo on PATH's siblings which we
+ ### do have access to. */
+ svn_error_clear(err);
+ }
+ else
+ SVN_ERR(err);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Helper for record_mergeinfo_for_dir_merge().
+
+ Record override mergeinfo on any paths skipped during a merge.
+
+ Set empty mergeinfo on each path in MERGE_B->SKIPPED_ABSPATHS so the path
+ does not incorrectly inherit mergeinfo that will later be describing
+ the merge.
+
+ MERGEINFO_PATH and MERGE_B are cascaded from
+ arguments of the same name in the caller.
+
+ IS_ROLLBACK is true if the caller is recording a reverse merge and false
+ otherwise. RANGELIST is the set of revisions being merged from
+ MERGEINFO_PATH to MERGE_B->target. */
+static svn_error_t *
+record_skips_in_mergeinfo(const char *mergeinfo_path,
+ const svn_rangelist_t *rangelist,
+ svn_boolean_t is_rollback,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ apr_hash_t *merges;
+ apr_size_t nbr_skips = apr_hash_count(merge_b->skipped_abspaths);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ if (nbr_skips == 0)
+ return SVN_NO_ERROR;
+
+ merges = apr_hash_make(scratch_pool);
+
+ /* Override the mergeinfo for child paths which weren't actually merged. */
+ for (hi = apr_hash_first(scratch_pool, merge_b->skipped_abspaths); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *skipped_abspath = svn__apr_hash_index_key(hi);
+ svn_wc_notify_state_t obstruction_state;
+
+ svn_pool_clear(iterpool);
+
+ /* Before we override, make sure this is a versioned path, it might
+ be an external or missing from disk due to authz restrictions. */
+ SVN_ERR(perform_obstruction_check(&obstruction_state, NULL, NULL,
+ NULL, NULL,
+ merge_b, skipped_abspath,
+ iterpool));
+ if (obstruction_state == svn_wc_notify_state_obstructed
+ || obstruction_state == svn_wc_notify_state_missing)
+ continue;
+
+ /* Add an empty range list for this path.
+
+ ### TODO: This works fine for a file path skipped because it is
+ ### missing as long as the file's parent directory is present.
+ ### But missing directory paths skipped are not handled yet,
+ ### see issue #2915.
+
+ ### TODO: An empty range is fine if the skipped path doesn't
+ ### inherit any mergeinfo from a parent, but if it does
+ ### we need to account for that. See issue #3440
+ ### http://subversion.tigris.org/issues/show_bug.cgi?id=3440. */
+ svn_hash_sets(merges, skipped_abspath,
+ apr_array_make(scratch_pool, 0,
+ sizeof(svn_merge_range_t *)));
+
+ /* if (nbr_skips < notify_b->nbr_notifications)
+ ### Use RANGELIST as the mergeinfo for all children of
+ ### this path which were not also explicitly
+ ### skipped? */
+ }
+ SVN_ERR(update_wc_mergeinfo(NULL, merge_b->target->abspath,
+ mergeinfo_path, merges,
+ is_rollback, merge_b->ctx, iterpool));
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Data for reporting when a merge aborted because of raising conflicts.
+ */
+typedef struct single_range_conflict_report_t
+{
+ /* What sub-range of the requested source raised conflicts?
+ * The 'inheritable' flag is ignored. */
+ merge_source_t *conflicted_range;
+ /* What sub-range of the requested source remains to be merged?
+ * NULL if no more. The 'inheritable' flag is ignored. */
+ merge_source_t *remaining_source;
+
+} single_range_conflict_report_t;
+
+/* Create a single_range_conflict_report_t, containing deep copies of
+ * CONFLICTED_RANGE and REMAINING_SOURCE, allocated in RESULT_POOL. */
+static single_range_conflict_report_t *
+single_range_conflict_report_create(const merge_source_t *conflicted_range,
+ const merge_source_t *remaining_source,
+ apr_pool_t *result_pool)
+{
+ single_range_conflict_report_t *report
+ = apr_palloc(result_pool, sizeof(*report));
+
+ assert(conflicted_range != NULL);
+
+ report->conflicted_range = merge_source_dup(conflicted_range, result_pool);
+ report->remaining_source
+ = remaining_source ? merge_source_dup(remaining_source, result_pool)
+ : NULL;
+ return report;
+}
+
+/* Data for reporting when a merge aborted because of raising conflicts.
+ *
+ * ### TODO: More info, including the ranges (or other parameters) the user
+ * needs to complete the merge.
+ */
+typedef struct conflict_report_t
+{
+ const char *target_abspath;
+ /* The revision range during which conflicts were raised */
+ const merge_source_t *conflicted_range;
+ /* Was the conflicted range the last range in the whole requested merge? */
+ svn_boolean_t was_last_range;
+} conflict_report_t;
+
+/* Return a new conflict_report_t containing deep copies of the parameters,
+ * allocated in RESULT_POOL. */
+static conflict_report_t *
+conflict_report_create(const char *target_abspath,
+ const merge_source_t *conflicted_range,
+ svn_boolean_t was_last_range,
+ apr_pool_t *result_pool)
+{
+ conflict_report_t *report = apr_palloc(result_pool, sizeof(*report));
+
+ report->target_abspath = apr_pstrdup(result_pool, target_abspath);
+ report->conflicted_range = merge_source_dup(conflicted_range, result_pool);
+ report->was_last_range = was_last_range;
+ return report;
+}
+
+/* Return a deep copy of REPORT, allocated in RESULT_POOL. */
+static conflict_report_t *
+conflict_report_dup(const conflict_report_t *report,
+ apr_pool_t *result_pool)
+{
+ conflict_report_t *new = apr_pmemdup(result_pool, report, sizeof(*new));
+
+ new->target_abspath = apr_pstrdup(result_pool, report->target_abspath);
+ new->conflicted_range = merge_source_dup(report->conflicted_range,
+ result_pool);
+ return new;
+}
+
+/* Create and return an error structure appropriate for the unmerged
+ revisions range(s). */
+static APR_INLINE svn_error_t *
+make_merge_conflict_error(conflict_report_t *report,
+ apr_pool_t *scratch_pool)
+{
+ assert(!report || svn_dirent_is_absolute(report->target_abspath));
+
+ if (report && ! report->was_last_range)
+ {
+ svn_error_t *err = svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
+ _("One or more conflicts were produced while merging r%ld:%ld into\n"
+ "'%s' --\n"
+ "resolve all conflicts and rerun the merge to apply the remaining\n"
+ "unmerged revisions"),
+ report->conflicted_range->loc1->rev, report->conflicted_range->loc2->rev,
+ svn_dirent_local_style(report->target_abspath, scratch_pool));
+ assert(report->conflicted_range->loc1->rev != report->conflicted_range->loc2->rev); /* ### is a valid case in a 2-URL merge */
+ return err;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Helper for do_directory_merge().
+
+ TARGET_WCPATH is a directory and CHILDREN_WITH_MERGEINFO is filled
+ with paths (svn_client__merge_path_t *) arranged in depth first order,
+ which have mergeinfo set on them or meet one of the other criteria
+ defined in get_mergeinfo_paths(). Remove any paths absent from disk
+ or scheduled for deletion from CHILDREN_WITH_MERGEINFO which are equal to
+ or are descendants of TARGET_WCPATH by setting those children to NULL. */
+static void
+remove_absent_children(const char *target_wcpath,
+ apr_array_header_t *children_with_mergeinfo)
+{
+ /* Before we try to override mergeinfo for skipped paths, make sure
+ the path isn't absent due to authz restrictions, because there's
+ nothing we can do about those. */
+ int i;
+ for (i = 0; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
+ if ((child->absent || child->scheduled_for_deletion)
+ && svn_dirent_is_ancestor(target_wcpath, child->abspath))
+ {
+ svn_sort__array_delete(children_with_mergeinfo, i--, 1);
+ }
+ }
+}
+
+/* Helper for do_directory_merge() to handle the case where a merge editor
+ drive removes explicit mergeinfo from a subtree of the merge target.
+
+ MERGE_B is cascaded from the argument of the same name in
+ do_directory_merge(). For each path (if any) in
+ MERGE_B->PATHS_WITH_DELETED_MERGEINFO remove that path from
+ CHILDREN_WITH_MERGEINFO.
+
+ The one exception is for the merge target itself,
+ MERGE_B->target->abspath, this must always be present in
+ CHILDREN_WITH_MERGEINFO so this is never removed by this
+ function. */
+static void
+remove_children_with_deleted_mergeinfo(merge_cmd_baton_t *merge_b,
+ apr_array_header_t *children_with_mergeinfo)
+{
+ int i;
+
+ if (!merge_b->paths_with_deleted_mergeinfo)
+ return;
+
+ /* CHILDREN_WITH_MERGEINFO[0] is the always the merge target
+ so start at the first child. */
+ for (i = 1; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
+
+ if (svn_hash_gets(merge_b->paths_with_deleted_mergeinfo, child->abspath))
+ {
+ svn_sort__array_delete(children_with_mergeinfo, i--, 1);
+ }
+ }
+}
+
+/* Helper for do_directory_merge().
+
+ Set up the diff editor report to merge the SOURCE diff
+ into TARGET_ABSPATH and drive it.
+
+ If mergeinfo is not being honored (based on MERGE_B -- see the doc
+ string for HONOR_MERGEINFO() for how this is determined), then ignore
+ CHILDREN_WITH_MERGEINFO and merge the SOURCE diff to TARGET_ABSPATH.
+
+ If mergeinfo is being honored then perform a history-aware merge,
+ describing TARGET_ABSPATH and its subtrees to the reporter in such as way
+ as to avoid repeating merges already performed per the mergeinfo and
+ natural history of TARGET_ABSPATH and its subtrees.
+
+ The ranges that still need to be merged to the TARGET_ABSPATH and its
+ subtrees are described in CHILDREN_WITH_MERGEINFO, an array of
+ svn_client__merge_path_t * -- see 'THE CHILDREN_WITH_MERGEINFO ARRAY'
+ comment at the top of this file for more info. Note that it is possible
+ TARGET_ABSPATH and/or some of its subtrees need only a subset, or no part,
+ of SOURCE to be merged. Though there is little point to
+ calling this function if TARGET_ABSPATH and all its subtrees have already
+ had SOURCE merged, this will work but is a no-op.
+
+ SOURCE->rev1 and SOURCE->rev2 must be bound by the set of remaining_ranges
+ fields in CHILDREN_WITH_MERGEINFO's elements, specifically:
+
+ For forward merges (SOURCE->rev1 < SOURCE->rev2):
+
+ 1) The first svn_merge_range_t * element of each child's remaining_ranges
+ array must meet one of the following conditions:
+
+ a) The range's start field is greater than or equal to SOURCE->rev2.
+
+ b) The range's end field is SOURCE->rev2.
+
+ 2) Among all the ranges that meet condition 'b' the oldest start
+ revision must equal SOURCE->rev1.
+
+ For reverse merges (SOURCE->rev1 > SOURCE->rev2):
+
+ 1) The first svn_merge_range_t * element of each child's remaining_ranges
+ array must meet one of the following conditions:
+
+ a) The range's start field is less than or equal to SOURCE->rev2.
+
+ b) The range's end field is SOURCE->rev2.
+
+ 2) Among all the ranges that meet condition 'b' the youngest start
+ revision must equal SOURCE->rev1.
+
+ Note: If the first svn_merge_range_t * element of some subtree child's
+ remaining_ranges array is the same as the first range of that child's
+ nearest path-wise ancestor, then the subtree child *will not* be described
+ to the reporter.
+
+ DEPTH, NOTIFY_B, and MERGE_B are cascaded from do_directory_merge(), see
+ that function for more info.
+
+ MERGE_B->ra_session1 and MERGE_B->ra_session2 are RA sessions open to any
+ URL in the repository of SOURCE; they may be temporarily reparented within
+ this function.
+
+ If SOURCE->ancestral is set, then SOURCE->loc1 must be a
+ historical ancestor of SOURCE->loc2, or vice-versa (see
+ `MERGEINFO MERGE SOURCE NORMALIZATION' for more requirements around
+ SOURCE in this case).
+*/
+static svn_error_t *
+drive_merge_report_editor(const char *target_abspath,
+ const merge_source_t *source,
+ const apr_array_header_t *children_with_mergeinfo,
+ const svn_diff_tree_processor_t *processor,
+ svn_depth_t depth,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *scratch_pool)
+{
+ const svn_ra_reporter3_t *reporter;
+ const svn_delta_editor_t *diff_editor;
+ void *diff_edit_baton;
+ void *report_baton;
+ svn_revnum_t target_start;
+ svn_boolean_t honor_mergeinfo = HONOR_MERGEINFO(merge_b);
+ const char *old_sess1_url, *old_sess2_url;
+ svn_boolean_t is_rollback = source->loc1->rev > source->loc2->rev;
+
+ /* Start with a safe default starting revision for the editor and the
+ merge target. */
+ target_start = source->loc1->rev;
+
+ /* If we are honoring mergeinfo the starting revision for the merge target
+ might not be SOURCE->rev1, in fact the merge target might not need *any*
+ part of SOURCE merged -- Instead some subtree of the target
+ needs SOURCE -- So get the right starting revision for the
+ target. */
+ if (honor_mergeinfo)
+ {
+ svn_client__merge_path_t *child;
+
+ /* CHILDREN_WITH_MERGEINFO must always exist if we are honoring
+ mergeinfo and must have at least one element (describing the
+ merge target). */
+ SVN_ERR_ASSERT(children_with_mergeinfo);
+ SVN_ERR_ASSERT(children_with_mergeinfo->nelts);
+
+ /* Get the merge target's svn_client__merge_path_t, which is always
+ the first in the array due to depth first sorting requirement,
+ see 'THE CHILDREN_WITH_MERGEINFO ARRAY'. */
+ child = APR_ARRAY_IDX(children_with_mergeinfo, 0,
+ svn_client__merge_path_t *);
+ SVN_ERR_ASSERT(child);
+ if (child->remaining_ranges->nelts == 0)
+ {
+ /* The merge target doesn't need anything merged. */
+ target_start = source->loc2->rev;
+ }
+ else
+ {
+ /* The merge target has remaining revisions to merge. These
+ ranges may fully or partially overlap the range described
+ by SOURCE->rev1:rev2 or may not intersect that range at
+ all. */
+ svn_merge_range_t *range =
+ APR_ARRAY_IDX(child->remaining_ranges, 0,
+ svn_merge_range_t *);
+ if ((!is_rollback && range->start > source->loc2->rev)
+ || (is_rollback && range->start < source->loc2->rev))
+ {
+ /* Merge target's first remaining range doesn't intersect. */
+ target_start = source->loc2->rev;
+ }
+ else
+ {
+ /* Merge target's first remaining range partially or
+ fully overlaps. */
+ target_start = range->start;
+ }
+ }
+ }
+
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_sess1_url,
+ merge_b->ra_session1,
+ source->loc1->url, scratch_pool));
+ /* Temporarily point our second RA session to SOURCE->loc1->url, too. We use
+ this to request individual file contents. */
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_sess2_url,
+ merge_b->ra_session2,
+ source->loc1->url, scratch_pool));
+
+ /* Get the diff editor and a reporter with which to, ultimately,
+ drive it. */
+ SVN_ERR(svn_client__get_diff_editor2(&diff_editor, &diff_edit_baton,
+ merge_b->ra_session2,
+ depth,
+ source->loc1->rev,
+ TRUE /* text_deltas */,
+ processor,
+ merge_b->ctx->cancel_func,
+ merge_b->ctx->cancel_baton,
+ scratch_pool));
+ SVN_ERR(svn_ra_do_diff3(merge_b->ra_session1,
+ &reporter, &report_baton, source->loc2->rev,
+ "", depth, merge_b->diff_ignore_ancestry,
+ TRUE, /* text_deltas */
+ source->loc2->url, diff_editor, diff_edit_baton,
+ scratch_pool));
+
+ /* Drive the reporter. */
+ SVN_ERR(reporter->set_path(report_baton, "", target_start, depth,
+ FALSE, NULL, scratch_pool));
+ if (honor_mergeinfo && children_with_mergeinfo)
+ {
+ /* Describe children with mergeinfo overlapping this merge
+ operation such that no repeated diff is retrieved for them from
+ the repository. */
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Start with CHILDREN_WITH_MERGEINFO[1], CHILDREN_WITH_MERGEINFO[0]
+ is always the merge target (TARGET_ABSPATH). */
+ for (i = 1; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_merge_range_t *range;
+ const char *child_repos_path;
+ const svn_client__merge_path_t *parent;
+ const svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i,
+ svn_client__merge_path_t *);
+
+ SVN_ERR_ASSERT(child);
+ if (child->absent)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ /* Find this child's nearest wc ancestor with mergeinfo. */
+ parent = find_nearest_ancestor(children_with_mergeinfo,
+ FALSE, child->abspath);
+
+ /* If a subtree needs the same range applied as its nearest parent
+ with mergeinfo or neither the subtree nor this parent need
+ SOURCE->rev1:rev2 merged, then we don't need to describe the
+ subtree separately. In the latter case this could break the
+ editor if child->abspath didn't exist at SOURCE->rev2 and we
+ attempt to describe it via a reporter set_path call. */
+ if (child->remaining_ranges->nelts)
+ {
+ range = APR_ARRAY_IDX(child->remaining_ranges, 0,
+ svn_merge_range_t *);
+ if ((!is_rollback && range->start > source->loc2->rev)
+ || (is_rollback && range->start < source->loc2->rev))
+ {
+ /* This child's first remaining range comes after the range
+ we are currently merging, so skip it. We expect to get
+ to it in a subsequent call to this function. */
+ continue;
+ }
+ else if (parent->remaining_ranges->nelts)
+ {
+ svn_merge_range_t *parent_range =
+ APR_ARRAY_IDX(parent->remaining_ranges, 0,
+ svn_merge_range_t *);
+ svn_merge_range_t *child_range =
+ APR_ARRAY_IDX(child->remaining_ranges, 0,
+ svn_merge_range_t *);
+ if (parent_range->start == child_range->start)
+ continue; /* Subtree needs same range as parent. */
+ }
+ }
+ else /* child->remaining_ranges->nelts == 0*/
+ {
+ /* If both the subtree and its parent need no ranges applied
+ consider that as the "same ranges" and don't describe
+ the subtree. */
+ if (parent->remaining_ranges->nelts == 0)
+ continue;
+ }
+
+ /* Ok, we really need to describe this subtree as it needs different
+ ranges applied than its nearest working copy parent. */
+ child_repos_path = svn_dirent_is_child(target_abspath,
+ child->abspath,
+ iterpool);
+ /* This loop is only processing subtrees, so CHILD->ABSPATH
+ better be a proper child of the merge target. */
+ SVN_ERR_ASSERT(child_repos_path);
+
+ if ((child->remaining_ranges->nelts == 0)
+ || (is_rollback && (range->start < source->loc2->rev))
+ || (!is_rollback && (range->start > source->loc2->rev)))
+ {
+ /* Nothing to merge to this child. We'll claim we have
+ it up to date so the server doesn't send us
+ anything. */
+ SVN_ERR(reporter->set_path(report_baton, child_repos_path,
+ source->loc2->rev, depth, FALSE,
+ NULL, iterpool));
+ }
+ else
+ {
+ SVN_ERR(reporter->set_path(report_baton, child_repos_path,
+ range->start, depth, FALSE,
+ NULL, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+ }
+ SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
+
+ /* Point the merge baton's RA sessions back where they were. */
+ SVN_ERR(svn_ra_reparent(merge_b->ra_session1, old_sess1_url, scratch_pool));
+ SVN_ERR(svn_ra_reparent(merge_b->ra_session2, old_sess2_url, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Iterate over each svn_client__merge_path_t * element in
+ CHILDREN_WITH_MERGEINFO and, if START_REV is true, find the most inclusive
+ start revision among those element's first remaining_ranges element. If
+ START_REV is false, then look for the most inclusive end revision.
+
+ If IS_ROLLBACK is true the youngest start or end (as per START_REV)
+ revision is considered the "most inclusive" otherwise the oldest revision
+ is.
+
+ If none of CHILDREN_WITH_MERGEINFO's elements have any remaining ranges
+ return SVN_INVALID_REVNUM. */
+static svn_revnum_t
+get_most_inclusive_rev(const apr_array_header_t *children_with_mergeinfo,
+ svn_boolean_t is_rollback,
+ svn_boolean_t start_rev)
+{
+ int i;
+ svn_revnum_t most_inclusive_rev = SVN_INVALID_REVNUM;
+
+ for (i = 0; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
+
+ if ((! child) || child->absent)
+ continue;
+ if (child->remaining_ranges->nelts > 0)
+ {
+ svn_merge_range_t *range =
+ APR_ARRAY_IDX(child->remaining_ranges, 0, svn_merge_range_t *);
+
+ /* Are we looking for the most inclusive start or end rev? */
+ svn_revnum_t rev = start_rev ? range->start : range->end;
+
+ if ((most_inclusive_rev == SVN_INVALID_REVNUM)
+ || (is_rollback && (rev > most_inclusive_rev))
+ || ((! is_rollback) && (rev < most_inclusive_rev)))
+ most_inclusive_rev = rev;
+ }
+ }
+ return most_inclusive_rev;
+}
+
+
+/* If first item in each child of CHILDREN_WITH_MERGEINFO's
+ remaining_ranges is inclusive of END_REV, Slice the first range in
+ to two at END_REV. All the allocations are persistent and allocated
+ from POOL. */
+static void
+slice_remaining_ranges(apr_array_header_t *children_with_mergeinfo,
+ svn_boolean_t is_rollback, svn_revnum_t end_rev,
+ apr_pool_t *pool)
+{
+ int i;
+ for (i = 0; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i,
+ svn_client__merge_path_t *);
+ if (!child || child->absent)
+ continue;
+ if (child->remaining_ranges->nelts > 0)
+ {
+ svn_merge_range_t *range = APR_ARRAY_IDX(child->remaining_ranges, 0,
+ svn_merge_range_t *);
+ if ((is_rollback && (range->start > end_rev)
+ && (range->end < end_rev))
+ || (!is_rollback && (range->start < end_rev)
+ && (range->end > end_rev)))
+ {
+ svn_merge_range_t *split_range1, *split_range2;
+
+ split_range1 = svn_merge_range_dup(range, pool);
+ split_range2 = svn_merge_range_dup(range, pool);
+ split_range1->end = end_rev;
+ split_range2->start = end_rev;
+ APR_ARRAY_IDX(child->remaining_ranges, 0,
+ svn_merge_range_t *) = split_range1;
+ svn_sort__array_insert(&split_range2, child->remaining_ranges, 1);
+ }
+ }
+ }
+}
+
+/* Helper for do_directory_merge().
+
+ For each child in CHILDREN_WITH_MERGEINFO remove the first remaining_ranges
+ svn_merge_range_t *element of the child if that range has an end revision
+ equal to REVISION.
+
+ If a range is removed from a child's remaining_ranges array, allocate the
+ new remaining_ranges array in POOL.
+ */
+static void
+remove_first_range_from_remaining_ranges(svn_revnum_t revision,
+ apr_array_header_t
+ *children_with_mergeinfo,
+ apr_pool_t *pool)
+{
+ int i;
+
+ for (i = 0; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i,
+ svn_client__merge_path_t *);
+ if (!child || child->absent)
+ continue;
+ if (child->remaining_ranges->nelts > 0)
+ {
+ svn_merge_range_t *first_range =
+ APR_ARRAY_IDX(child->remaining_ranges, 0, svn_merge_range_t *);
+ if (first_range->end == revision)
+ {
+ svn_sort__array_delete(child->remaining_ranges, 0, 1);
+ }
+ }
+ }
+}
+
+/* Get a file's content and properties from the repository.
+ Set *FILENAME to the local path to a new temporary file holding its text,
+ and set *PROPS to a new hash of its properties.
+
+ RA_SESSION is a session open to the correct repository, which will be
+ temporarily reparented to the URL of the file itself. LOCATION is the
+ repository location of the file.
+
+ The resulting file and the return values live as long as RESULT_POOL, all
+ other allocations occur in SCRATCH_POOL.
+*/
+static svn_error_t *
+single_file_merge_get_file(const char **filename,
+ apr_hash_t **props,
+ svn_ra_session_t *ra_session,
+ const svn_client__pathrev_t *location,
+ const char *wc_target,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *stream;
+ const char *old_sess_url;
+ svn_error_t *err;
+
+ SVN_ERR(svn_stream_open_unique(&stream, filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_sess_url, ra_session, location->url,
+ scratch_pool));
+ err = svn_ra_get_file(ra_session, "", location->rev,
+ stream, NULL, props, scratch_pool);
+ SVN_ERR(svn_error_compose_create(
+ err, svn_ra_reparent(ra_session, old_sess_url, scratch_pool)));
+
+ return svn_error_trace(svn_stream_close(stream));
+}
+
+/* Compare two svn_client__merge_path_t elements **A and **B, given the
+ addresses of pointers to them. Return an integer less than, equal to, or
+ greater than zero if A sorts before, the same as, or after B, respectively.
+ This is a helper for qsort() and bsearch() on an array of such elements. */
+static int
+compare_merge_path_t_as_paths(const void *a,
+ const void *b)
+{
+ const svn_client__merge_path_t *child1
+ = *((const svn_client__merge_path_t * const *) a);
+ const svn_client__merge_path_t *child2
+ = *((const svn_client__merge_path_t * const *) b);
+
+ return svn_path_compare_paths(child1->abspath, child2->abspath);
+}
+
+/* Return a pointer to the element of CHILDREN_WITH_MERGEINFO whose path
+ * is PATH, or return NULL if there is no such element. */
+static svn_client__merge_path_t *
+get_child_with_mergeinfo(const apr_array_header_t *children_with_mergeinfo,
+ const char *abspath)
+{
+ svn_client__merge_path_t merge_path;
+ svn_client__merge_path_t *key;
+ svn_client__merge_path_t **pchild;
+
+ merge_path.abspath = abspath;
+ key = &merge_path;
+ pchild = bsearch(&key, children_with_mergeinfo->elts,
+ children_with_mergeinfo->nelts,
+ children_with_mergeinfo->elt_size,
+ compare_merge_path_t_as_paths);
+ return pchild ? *pchild : NULL;
+}
+
+/* Insert a deep copy of INSERT_ELEMENT into the CHILDREN_WITH_MERGEINFO
+ array at its correct position. Allocate the new storage in POOL.
+ CHILDREN_WITH_MERGEINFO is a depth first sorted array of
+ (svn_client__merge_path_t *).
+
+ ### Most callers don't need this to deep-copy the new element.
+ ### It may be more efficient for some callers to insert a bunch of items
+ out of order and then sort afterwards. (One caller is doing a qsort
+ after calling this anyway.)
+ */
+static void
+insert_child_to_merge(apr_array_header_t *children_with_mergeinfo,
+ const svn_client__merge_path_t *insert_element,
+ apr_pool_t *pool)
+{
+ int insert_index;
+ const svn_client__merge_path_t *new_element;
+
+ /* Find where to insert the new element */
+ insert_index =
+ svn_sort__bsearch_lower_bound(&insert_element, children_with_mergeinfo,
+ compare_merge_path_t_as_paths);
+
+ new_element = svn_client__merge_path_dup(insert_element, pool);
+ svn_sort__array_insert(&new_element, children_with_mergeinfo, insert_index);
+}
+
+/* Helper for get_mergeinfo_paths().
+
+ CHILDREN_WITH_MERGEINFO, DEPTH, and POOL are
+ all cascaded from the arguments of the same name to get_mergeinfo_paths().
+
+ TARGET is the merge target.
+
+ *CHILD is the element in in CHILDREN_WITH_MERGEINFO that
+ get_mergeinfo_paths() is iterating over and *CURR_INDEX is index for
+ *CHILD.
+
+ If CHILD->ABSPATH is equal to MERGE_CMD_BATON->target->abspath do nothing.
+ Else if CHILD->ABSPATH is switched or absent then make sure its immediate
+ (as opposed to nearest) parent in CHILDREN_WITH_MERGEINFO is marked as
+ missing a child. If the immediate parent does not exist in
+ CHILDREN_WITH_MERGEINFO then create it (and increment *CURR_INDEX so that
+ caller doesn't process the inserted element). Also ensure that
+ CHILD->ABSPATH's siblings which are not already present in
+ CHILDREN_WITH_MERGEINFO are also added to the array, limited by DEPTH
+ (e.g. don't add directory siblings of a switched file).
+ Use POOL for temporary allocations only, any new CHILDREN_WITH_MERGEINFO
+ elements are allocated in POOL. */
+static svn_error_t *
+insert_parent_and_sibs_of_sw_absent_del_subtree(
+ apr_array_header_t *children_with_mergeinfo,
+ const merge_target_t *target,
+ int *curr_index,
+ svn_client__merge_path_t *child,
+ svn_depth_t depth,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_client__merge_path_t *parent;
+ const char *parent_abspath;
+ apr_pool_t *iterpool;
+ const apr_array_header_t *children;
+ int i;
+
+ if (!(child->absent
+ || (child->switched
+ && strcmp(target->abspath,
+ child->abspath) != 0)))
+ return SVN_NO_ERROR;
+
+ parent_abspath = svn_dirent_dirname(child->abspath, pool);
+ parent = get_child_with_mergeinfo(children_with_mergeinfo, parent_abspath);
+ if (parent)
+ {
+ parent->missing_child = child->absent;
+ parent->switched_child = child->switched;
+ }
+ else
+ {
+ /* Create a new element to insert into CHILDREN_WITH_MERGEINFO. */
+ parent = svn_client__merge_path_create(parent_abspath, pool);
+ parent->missing_child = child->absent;
+ parent->switched_child = child->switched;
+ /* Insert PARENT into CHILDREN_WITH_MERGEINFO. */
+ insert_child_to_merge(children_with_mergeinfo, parent, pool);
+ /* Increment for loop index so we don't process the inserted element. */
+ (*curr_index)++;
+ } /*(parent == NULL) */
+
+ /* Add all of PARENT's non-missing children that are not already present.*/
+ SVN_ERR(svn_wc__node_get_children(&children, ctx->wc_ctx,
+ parent_abspath, FALSE, pool, pool));
+ iterpool = svn_pool_create(pool);
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *child_abspath = APR_ARRAY_IDX(children, i, const char *);
+ svn_client__merge_path_t *sibling_of_missing;
+
+ svn_pool_clear(iterpool);
+
+ /* Does this child already exist in CHILDREN_WITH_MERGEINFO? */
+ sibling_of_missing = get_child_with_mergeinfo(children_with_mergeinfo,
+ child_abspath);
+ /* Create the missing child and insert it into CHILDREN_WITH_MERGEINFO.*/
+ if (!sibling_of_missing)
+ {
+ /* Don't add directory children if DEPTH is svn_depth_files. */
+ if (depth == svn_depth_files)
+ {
+ svn_node_kind_t child_kind;
+
+ SVN_ERR(svn_wc_read_kind2(&child_kind,
+ ctx->wc_ctx, child_abspath,
+ FALSE, FALSE, iterpool));
+ if (child_kind != svn_node_file)
+ continue;
+ }
+
+ sibling_of_missing = svn_client__merge_path_create(child_abspath,
+ pool);
+ insert_child_to_merge(children_with_mergeinfo, sibling_of_missing,
+ pool);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* pre_merge_status_cb's baton */
+struct pre_merge_status_baton_t
+{
+ svn_wc_context_t *wc_ctx;
+
+ /* const char *absolute_wc_path to svn_depth_t * mapping for depths
+ of empty, immediates, and files. */
+ apr_hash_t *shallow_subtrees;
+
+ /* const char *absolute_wc_path to the same, for all paths missing
+ from the working copy. */
+ apr_hash_t *missing_subtrees;
+
+ /* const char *absolute_wc_path const char * repos relative path, describing
+ the root of each switched subtree in the working copy and the repository
+ relative path it is switched to. */
+ apr_hash_t *switched_subtrees;
+
+ /* A pool to allocate additions to the above hashes in. */
+ apr_pool_t *pool;
+};
+
+/* A svn_wc_status_func4_t callback used by get_mergeinfo_paths to gather
+ all switched, depth filtered and missing subtrees under a merge target.
+
+ Note that this doesn't see server and user excluded trees. */
+static svn_error_t *
+pre_merge_status_cb(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct pre_merge_status_baton_t *pmsb = baton;
+
+ if (status->switched && !status->file_external)
+ {
+ store_path(pmsb->switched_subtrees, local_abspath);
+ }
+
+ if (status->depth == svn_depth_empty
+ || status->depth == svn_depth_files)
+ {
+ const char *dup_abspath;
+ svn_depth_t *depth = apr_pmemdup(pmsb->pool, &status->depth,
+ sizeof *depth);
+
+ dup_abspath = apr_pstrdup(pmsb->pool, local_abspath);
+
+ svn_hash_sets(pmsb->shallow_subtrees, dup_abspath, depth);
+ }
+
+ if (status->node_status == svn_wc_status_missing)
+ {
+ svn_boolean_t new_missing_root = TRUE;
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, pmsb->missing_subtrees);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *missing_root_path = svn__apr_hash_index_key(hi);
+
+ if (svn_dirent_is_ancestor(missing_root_path,
+ local_abspath))
+ {
+ new_missing_root = FALSE;
+ break;
+ }
+ }
+
+ if (new_missing_root)
+ store_path(pmsb->missing_subtrees, local_abspath);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Find all the subtrees in the working copy tree rooted at TARGET_ABSPATH
+ * that have explicit mergeinfo.
+ * Set *SUBTREES_WITH_MERGEINFO to a hash mapping (const char *) absolute
+ * WC path to (svn_mergeinfo_t *) mergeinfo.
+ *
+ * ### Is this function equivalent to:
+ *
+ * svn_client__get_wc_mergeinfo_catalog(
+ * subtrees_with_mergeinfo, inherited=NULL, include_descendants=TRUE,
+ * svn_mergeinfo_explicit, target_abspath, limit_path=NULL,
+ * walked_path=NULL, ignore_invalid_mergeinfo=FALSE, ...)
+ *
+ * except for the catalog keys being abspaths instead of repo-relpaths?
+ */
+static svn_error_t *
+get_wc_explicit_mergeinfo_catalog(apr_hash_t **subtrees_with_mergeinfo,
+ const char *target_abspath,
+ svn_depth_t depth,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_opt_revision_t working_revision = { svn_opt_revision_working, { 0 } };
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_hash_t *externals;
+
+ SVN_ERR(svn_client_propget5(subtrees_with_mergeinfo, NULL,
+ SVN_PROP_MERGEINFO, target_abspath,
+ &working_revision, &working_revision, NULL,
+ depth, NULL, ctx, result_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__externals_defined_below(&externals, ctx->wc_ctx,
+ target_abspath, scratch_pool,
+ scratch_pool));
+
+ /* Convert property values to svn_mergeinfo_t. */
+ for (hi = apr_hash_first(scratch_pool, *subtrees_with_mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *wc_path = svn__apr_hash_index_key(hi);
+ svn_string_t *mergeinfo_string = svn__apr_hash_index_val(hi);
+ svn_mergeinfo_t mergeinfo;
+ svn_error_t *err;
+
+ /* svn_client_propget5 picks up file externals with
+ mergeinfo, but we don't want those. */
+ if (svn_hash_gets(externals, wc_path))
+ {
+ svn_hash_sets(*subtrees_with_mergeinfo, wc_path, NULL);
+ continue;
+ }
+
+ svn_pool_clear(iterpool);
+
+ err = svn_mergeinfo_parse(&mergeinfo, mergeinfo_string->data,
+ result_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ err = svn_error_createf(
+ SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING, err,
+ _("Invalid mergeinfo detected on '%s', "
+ "merge tracking not possible"),
+ svn_dirent_local_style(wc_path, scratch_pool));
+ }
+ return svn_error_trace(err);
+ }
+ svn_hash_sets(*subtrees_with_mergeinfo, wc_path, mergeinfo);
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for do_directory_merge() when performing merge-tracking aware
+ merges.
+
+ Walk of the working copy tree rooted at TARGET->abspath to
+ depth DEPTH. Create an svn_client__merge_path_t * for any path which meets
+ one or more of the following criteria:
+
+ 1) Path has working svn:mergeinfo.
+ 2) Path is switched.
+ 3) Path is a subtree of the merge target (i.e. is not equal to
+ TARGET->abspath) and has no mergeinfo of its own but
+ its immediate parent has mergeinfo with non-inheritable ranges. If
+ this isn't a dry-run and the merge is between differences in the same
+ repository, then this function will set working mergeinfo on the path
+ equal to the mergeinfo inheritable from its parent.
+ 4) Path has an immediate child (or children) missing from the WC because
+ the child is switched or absent from the WC, or due to a sparse
+ checkout.
+ 5) Path has a sibling (or siblings) missing from the WC because the
+ sibling is switched, absent, scheduled for deletion, or missing due to
+ a sparse checkout.
+ 6) Path is absent from disk due to an authz restriction.
+ 7) Path is equal to TARGET->abspath.
+ 8) Path is an immediate *directory* child of
+ TARGET->abspath and DEPTH is svn_depth_immediates.
+ 9) Path is an immediate *file* child of TARGET->abspath
+ and DEPTH is svn_depth_files.
+ 10) Path is at a depth of 'empty' or 'files'.
+ 11) Path is missing from disk (e.g. due to an OS-level deletion).
+
+ If subtrees within the requested DEPTH are unexpectedly missing disk,
+ then raise SVN_ERR_CLIENT_NOT_READY_TO_MERGE.
+
+ Store the svn_client__merge_path_t *'s in *CHILDREN_WITH_MERGEINFO in
+ depth-first order based on the svn_client__merge_path_t *s path member as
+ sorted by svn_path_compare_paths(). Set the remaining_ranges field of each
+ element to NULL.
+
+ Note: Since the walk is rooted at TARGET->abspath, the
+ latter is guaranteed to be in *CHILDREN_WITH_MERGEINFO and due to the
+ depth-first ordering it is guaranteed to be the first element in
+ *CHILDREN_WITH_MERGEINFO.
+
+ MERGE_CMD_BATON is cascaded from the argument of the same name in
+ do_directory_merge().
+*/
+static svn_error_t *
+get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo,
+ const merge_target_t *target,
+ svn_depth_t depth,
+ svn_boolean_t dry_run,
+ svn_boolean_t same_repos,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *subtrees_with_mergeinfo;
+ apr_hash_t *excluded_subtrees;
+ apr_hash_t *switched_subtrees;
+ apr_hash_t *shallow_subtrees;
+ apr_hash_t *missing_subtrees;
+ struct pre_merge_status_baton_t pre_merge_status_baton;
+
+ /* Case 1: Subtrees with explicit mergeinfo. */
+ SVN_ERR(get_wc_explicit_mergeinfo_catalog(&subtrees_with_mergeinfo,
+ target->abspath,
+ depth, ctx,
+ result_pool, scratch_pool));
+ if (subtrees_with_mergeinfo)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, subtrees_with_mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *wc_path = svn__apr_hash_index_key(hi);
+ svn_mergeinfo_t mergeinfo = svn__apr_hash_index_val(hi);
+ svn_client__merge_path_t *mergeinfo_child =
+ svn_client__merge_path_create(wc_path, result_pool);
+
+ svn_pool_clear(iterpool);
+
+ /* Stash this child's pre-existing mergeinfo. */
+ mergeinfo_child->pre_merge_mergeinfo = mergeinfo;
+
+ /* Note if this child has non-inheritable mergeinfo */
+ mergeinfo_child->has_noninheritable
+ = svn_mergeinfo__is_noninheritable(
+ mergeinfo_child->pre_merge_mergeinfo, iterpool);
+
+ /* Append it. We'll sort below. */
+ APR_ARRAY_PUSH(children_with_mergeinfo, svn_client__merge_path_t *)
+ = svn_client__merge_path_dup(mergeinfo_child, result_pool);
+ }
+
+ /* Sort CHILDREN_WITH_MERGEINFO by each child's path (i.e. as per
+ compare_merge_path_t_as_paths). Any subsequent insertions of new
+ children with insert_child_to_merge() require this ordering. */
+ qsort(children_with_mergeinfo->elts,
+ children_with_mergeinfo->nelts,
+ children_with_mergeinfo->elt_size,
+ compare_merge_path_t_as_paths);
+ }
+
+ /* Case 2: Switched subtrees
+ Case 10: Paths at depths of 'empty' or 'files'
+ Case 11: Paths missing from disk */
+ pre_merge_status_baton.wc_ctx = ctx->wc_ctx;
+ switched_subtrees = apr_hash_make(scratch_pool);
+ pre_merge_status_baton.switched_subtrees = switched_subtrees;
+ shallow_subtrees = apr_hash_make(scratch_pool);
+ pre_merge_status_baton.shallow_subtrees = shallow_subtrees;
+ missing_subtrees = apr_hash_make(scratch_pool);
+ pre_merge_status_baton.missing_subtrees = missing_subtrees;
+ pre_merge_status_baton.pool = scratch_pool;
+ SVN_ERR(svn_wc_walk_status(ctx->wc_ctx,
+ target->abspath,
+ depth,
+ TRUE /* get_all */,
+ FALSE /* no_ignore */,
+ TRUE /* ignore_text_mods */,
+ NULL /* ingore_patterns */,
+ pre_merge_status_cb, &pre_merge_status_baton,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool));
+
+ /* Issue #2915: Raise an error describing the roots of any missing
+ subtrees, i.e. those that the WC thinks are on disk but have been
+ removed outside of Subversion. */
+ if (apr_hash_count(missing_subtrees))
+ {
+ apr_hash_index_t *hi;
+ svn_stringbuf_t *missing_subtree_err_buf =
+ svn_stringbuf_create(_("Merge tracking not allowed with missing "
+ "subtrees; try restoring these items "
+ "first:\n"), scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, missing_subtrees);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_pool_clear(iterpool);
+ svn_stringbuf_appendcstr(missing_subtree_err_buf,
+ svn_dirent_local_style(
+ svn__apr_hash_index_key(hi), iterpool));
+ svn_stringbuf_appendcstr(missing_subtree_err_buf, "\n");
+ }
+
+ return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE,
+ NULL, missing_subtree_err_buf->data);
+ }
+
+ if (apr_hash_count(switched_subtrees))
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, switched_subtrees);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *wc_path = svn__apr_hash_index_key(hi);
+ svn_client__merge_path_t *child = get_child_with_mergeinfo(
+ children_with_mergeinfo, wc_path);
+
+ if (child)
+ {
+ child->switched = TRUE;
+ }
+ else
+ {
+ svn_client__merge_path_t *switched_child =
+ svn_client__merge_path_create(wc_path, result_pool);
+ switched_child->switched = TRUE;
+ insert_child_to_merge(children_with_mergeinfo, switched_child,
+ result_pool);
+ }
+ }
+ }
+
+ if (apr_hash_count(shallow_subtrees))
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, shallow_subtrees);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_boolean_t new_shallow_child = FALSE;
+ const char *wc_path = svn__apr_hash_index_key(hi);
+ svn_depth_t *child_depth = svn__apr_hash_index_val(hi);
+ svn_client__merge_path_t *shallow_child = get_child_with_mergeinfo(
+ children_with_mergeinfo, wc_path);
+
+ if (shallow_child)
+ {
+ if (*child_depth == svn_depth_empty
+ || *child_depth == svn_depth_files)
+ shallow_child->missing_child = TRUE;
+ }
+ else
+ {
+ shallow_child = svn_client__merge_path_create(wc_path,
+ result_pool);
+ new_shallow_child = TRUE;
+
+ if (*child_depth == svn_depth_empty
+ || *child_depth == svn_depth_files)
+ shallow_child->missing_child = TRUE;
+ }
+
+ /* A little trickery: If PATH doesn't have any mergeinfo or has
+ only inheritable mergeinfo, we still describe it as having
+ non-inheritable mergeinfo if it is missing a child due to
+ a shallow depth. Why? Because the mergeinfo we'll add to PATH
+ to describe the merge must be non-inheritable, so PATH's missing
+ children don't inherit it. Marking these PATHs as non-
+ inheritable allows the logic for case 3 to properly account
+ for PATH's children. */
+ if (!shallow_child->has_noninheritable
+ && (*child_depth == svn_depth_empty
+ || *child_depth == svn_depth_files))
+ {
+ shallow_child->has_noninheritable = TRUE;
+ }
+
+ if (new_shallow_child)
+ insert_child_to_merge(children_with_mergeinfo, shallow_child,
+ result_pool);
+ }
+ }
+
+ /* Case 6: Paths absent from disk due to server or user exclusion. */
+ SVN_ERR(svn_wc__get_excluded_subtrees(&excluded_subtrees,
+ ctx->wc_ctx, target->abspath,
+ result_pool, scratch_pool));
+ if (excluded_subtrees)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, excluded_subtrees);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *wc_path = svn__apr_hash_index_key(hi);
+ svn_client__merge_path_t *child = get_child_with_mergeinfo(
+ children_with_mergeinfo, wc_path);
+
+ if (child)
+ {
+ child->absent = TRUE;
+ }
+ else
+ {
+ svn_client__merge_path_t *absent_child =
+ svn_client__merge_path_create(wc_path, result_pool);
+ absent_child->absent = TRUE;
+ insert_child_to_merge(children_with_mergeinfo, absent_child,
+ result_pool);
+ }
+ }
+ }
+
+ /* Case 7: The merge target MERGE_CMD_BATON->target->abspath is always
+ present. */
+ if (!get_child_with_mergeinfo(children_with_mergeinfo,
+ target->abspath))
+ {
+ svn_client__merge_path_t *target_child =
+ svn_client__merge_path_create(target->abspath,
+ result_pool);
+ insert_child_to_merge(children_with_mergeinfo, target_child,
+ result_pool);
+ }
+
+ /* Case 8: Path is an immediate *directory* child of
+ MERGE_CMD_BATON->target->abspath and DEPTH is svn_depth_immediates.
+
+ Case 9: Path is an immediate *file* child of
+ MERGE_CMD_BATON->target->abspath and DEPTH is svn_depth_files. */
+ if (depth == svn_depth_immediates || depth == svn_depth_files)
+ {
+ int j;
+ const apr_array_header_t *immediate_children;
+
+ SVN_ERR(svn_wc__node_get_children_of_working_node(
+ &immediate_children, ctx->wc_ctx,
+ target->abspath, FALSE, scratch_pool, scratch_pool));
+
+ for (j = 0; j < immediate_children->nelts; j++)
+ {
+ const char *immediate_child_abspath =
+ APR_ARRAY_IDX(immediate_children, j, const char *);
+ svn_node_kind_t immediate_child_kind;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_wc_read_kind2(&immediate_child_kind,
+ ctx->wc_ctx, immediate_child_abspath,
+ FALSE, FALSE, iterpool));
+ if ((immediate_child_kind == svn_node_dir
+ && depth == svn_depth_immediates)
+ || (immediate_child_kind == svn_node_file
+ && depth == svn_depth_files))
+ {
+ if (!get_child_with_mergeinfo(children_with_mergeinfo,
+ immediate_child_abspath))
+ {
+ svn_client__merge_path_t *immediate_child =
+ svn_client__merge_path_create(immediate_child_abspath,
+ result_pool);
+
+ if (immediate_child_kind == svn_node_dir
+ && depth == svn_depth_immediates)
+ immediate_child->immediate_child_dir = TRUE;
+
+ insert_child_to_merge(children_with_mergeinfo,
+ immediate_child, result_pool);
+ }
+ }
+ }
+ }
+
+ /* If DEPTH isn't empty then cover cases 3), 4), and 5), possibly adding
+ elements to CHILDREN_WITH_MERGEINFO. */
+ if (depth <= svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ for (i = 0; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i,
+ svn_client__merge_path_t *);
+ svn_pool_clear(iterpool);
+
+ /* Case 3) Where merging to a path with a switched child the path
+ gets non-inheritable mergeinfo for the merge range performed and
+ the child gets its own set of mergeinfo. If the switched child
+ later "returns", e.g. a switched path is unswitched, the child
+ may not have any explicit mergeinfo. If the initial merge is
+ repeated we don't want to repeat the merge for the path, but we
+ do want to repeat it for the previously switched child. To
+ ensure this we check if all of CHILD's non-missing children have
+ explicit mergeinfo (they should already be present in
+ CHILDREN_WITH_MERGEINFO if they do). If not,
+ add the children without mergeinfo to CHILDREN_WITH_MERGEINFO so
+ do_directory_merge() will merge them independently.
+
+ But that's not enough! Since do_directory_merge() performs
+ the merges on the paths in CHILDREN_WITH_MERGEINFO in a depth
+ first manner it will merge the previously switched path's parent
+ first. As part of this merge it will update the parent's
+ previously non-inheritable mergeinfo and make it inheritable
+ (since it notices the path has no missing children), then when
+ do_directory_merge() finally merges the previously missing
+ child it needs to get mergeinfo from the child's nearest
+ ancestor, but since do_directory_merge() already tweaked that
+ mergeinfo, removing the non-inheritable flag, it appears that the
+ child already has been merged to. To prevent this we set
+ override mergeinfo on the child now, before any merging is done,
+ so it has explicit mergeinfo that reflects only CHILD's
+ inheritable mergeinfo. */
+
+ /* If depth is immediates or files then don't add new children if
+ CHILD is a subtree of the merge target; those children are below
+ the operational depth of the merge. */
+ if (child->has_noninheritable
+ && (i == 0 || depth == svn_depth_infinity))
+ {
+ const apr_array_header_t *children;
+ int j;
+
+ SVN_ERR(svn_wc__node_get_children(&children,
+ ctx->wc_ctx,
+ child->abspath, FALSE,
+ iterpool, iterpool));
+ for (j = 0; j < children->nelts; j++)
+ {
+ svn_client__merge_path_t *child_of_noninheritable;
+ const char *child_abspath = APR_ARRAY_IDX(children, j,
+ const char*);
+
+ /* Does this child already exist in CHILDREN_WITH_MERGEINFO?
+ If not, create it and insert it into
+ CHILDREN_WITH_MERGEINFO and set override mergeinfo on
+ it. */
+ child_of_noninheritable =
+ get_child_with_mergeinfo(children_with_mergeinfo,
+ child_abspath);
+ if (!child_of_noninheritable)
+ {
+ /* Don't add directory children if DEPTH
+ is svn_depth_files. */
+ if (depth == svn_depth_files)
+ {
+ svn_node_kind_t child_kind;
+ SVN_ERR(svn_wc_read_kind2(&child_kind,
+ ctx->wc_ctx, child_abspath,
+ FALSE, FALSE, iterpool));
+ if (child_kind != svn_node_file)
+ continue;
+ }
+ /* else DEPTH is infinity or immediates so we want both
+ directory and file children. */
+
+ child_of_noninheritable =
+ svn_client__merge_path_create(child_abspath, result_pool);
+ child_of_noninheritable->child_of_noninheritable = TRUE;
+ insert_child_to_merge(children_with_mergeinfo,
+ child_of_noninheritable,
+ result_pool);
+ if (!dry_run && same_repos)
+ {
+ svn_mergeinfo_t mergeinfo;
+
+ SVN_ERR(svn_client__get_wc_mergeinfo(
+ &mergeinfo, NULL,
+ svn_mergeinfo_nearest_ancestor,
+ child_of_noninheritable->abspath,
+ target->abspath, NULL, FALSE,
+ ctx, iterpool, iterpool));
+
+ SVN_ERR(svn_client__record_wc_mergeinfo(
+ child_of_noninheritable->abspath, mergeinfo,
+ FALSE, ctx, iterpool));
+ }
+ }
+ }
+ }
+ /* Case 4 and 5 are handled by the following function. */
+ SVN_ERR(insert_parent_and_sibs_of_sw_absent_del_subtree(
+ children_with_mergeinfo, target, &i, child,
+ depth, ctx, result_pool));
+ } /* i < children_with_mergeinfo->nelts */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements the svn_log_entry_receiver_t interface.
+ *
+ * BATON is an 'apr_array_header_t *' array of 'svn_revnum_t'.
+ * Push a copy of LOG_ENTRY->revision onto BATON. Thus, a
+ * series of invocations of this callback accumulates the
+ * corresponding set of revisions into BATON.
+ */
+static svn_error_t *
+log_changed_revs(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *revs = baton;
+
+ APR_ARRAY_PUSH(revs, svn_revnum_t) = log_entry->revision;
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *MIN_REV_P to the oldest and *MAX_REV_P to the youngest start or end
+ * revision occurring in RANGELIST, or to SVN_INVALID_REVNUM if RANGELIST
+ * is empty. */
+static void
+merge_range_find_extremes(svn_revnum_t *min_rev_p,
+ svn_revnum_t *max_rev_p,
+ const svn_rangelist_t *rangelist)
+{
+ int i;
+
+ *min_rev_p = SVN_INVALID_REVNUM;
+ *max_rev_p = SVN_INVALID_REVNUM;
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_merge_range_t *range
+ = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ svn_revnum_t range_min = MIN(range->start, range->end);
+ svn_revnum_t range_max = MAX(range->start, range->end);
+
+ if ((! SVN_IS_VALID_REVNUM(*min_rev_p)) || (range_min < *min_rev_p))
+ *min_rev_p = range_min;
+ if ((! SVN_IS_VALID_REVNUM(*max_rev_p)) || (range_max > *max_rev_p))
+ *max_rev_p = range_max;
+ }
+}
+
+/* Wrapper around svn_ra_get_log2(). Invoke RECEIVER with RECEIVER_BATON
+ * on each commit from YOUNGEST_REV to OLDEST_REV in which TARGET_RELPATH
+ * changed. TARGET_RELPATH is relative to RA_SESSION's URL.
+ * Important: Revision properties are not retrieved by this function for
+ * performance reasons.
+ */
+static svn_error_t *
+get_log(svn_ra_session_t *ra_session,
+ const char *target_relpath,
+ svn_revnum_t youngest_rev,
+ svn_revnum_t oldest_rev,
+ svn_boolean_t discover_changed_paths,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *log_targets;
+ apr_array_header_t *revprops;
+
+ log_targets = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(log_targets, const char *) = target_relpath;
+
+ revprops = apr_array_make(pool, 0, sizeof(const char *));
+
+ SVN_ERR(svn_ra_get_log2(ra_session, log_targets, youngest_rev,
+ oldest_rev, 0 /* limit */, discover_changed_paths,
+ FALSE /* strict_node_history */,
+ FALSE /* include_merged_revisions */,
+ revprops, receiver, receiver_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *OPERATIVE_RANGES_P to an array of svn_merge_range_t * merge
+ range objects copied wholesale from RANGES which have the property
+ that in some revision within that range the object identified by
+ RA_SESSION was modified (if by "modified" we mean "'svn log' would
+ return that revision). *OPERATIVE_RANGES_P is allocated from the
+ same pool as RANGES, and the ranges within it are shared with
+ RANGES, too.
+
+ *OPERATIVE_RANGES_P may be the same as RANGES (that is, the output
+ parameter is set only after the input is no longer used).
+
+ Use POOL for temporary allocations. */
+static svn_error_t *
+remove_noop_merge_ranges(svn_rangelist_t **operative_ranges_p,
+ svn_ra_session_t *ra_session,
+ const svn_rangelist_t *ranges,
+ apr_pool_t *pool)
+{
+ int i;
+ svn_revnum_t oldest_rev, youngest_rev;
+ apr_array_header_t *changed_revs =
+ apr_array_make(pool, ranges->nelts, sizeof(svn_revnum_t));
+ svn_rangelist_t *operative_ranges =
+ apr_array_make(ranges->pool, ranges->nelts, ranges->elt_size);
+
+ /* Find the revision extremes of the RANGES we have. */
+ merge_range_find_extremes(&oldest_rev, &youngest_rev, ranges);
+ if (SVN_IS_VALID_REVNUM(oldest_rev))
+ oldest_rev++; /* make it inclusive */
+
+ /* Get logs across those ranges, recording which revisions hold
+ changes to our object's history. */
+ SVN_ERR(get_log(ra_session, "", youngest_rev, oldest_rev, FALSE,
+ log_changed_revs, changed_revs, pool));
+
+ /* Are there *any* changes? */
+ if (changed_revs->nelts)
+ {
+ /* Our list of changed revisions should be in youngest-to-oldest
+ order. */
+ svn_revnum_t youngest_changed_rev
+ = APR_ARRAY_IDX(changed_revs, 0, svn_revnum_t);
+ svn_revnum_t oldest_changed_rev
+ = APR_ARRAY_IDX(changed_revs, changed_revs->nelts - 1, svn_revnum_t);
+
+ /* Now, copy from RANGES to *OPERATIVE_RANGES, filtering out ranges
+ that aren't operative (by virtue of not having any revisions
+ represented in the CHANGED_REVS array). */
+ for (i = 0; i < ranges->nelts; i++)
+ {
+ svn_merge_range_t *range = APR_ARRAY_IDX(ranges, i,
+ svn_merge_range_t *);
+ svn_revnum_t range_min = MIN(range->start, range->end) + 1;
+ svn_revnum_t range_max = MAX(range->start, range->end);
+ int j;
+
+ /* If the merge range is entirely outside the range of changed
+ revisions, we've no use for it. */
+ if ((range_min > youngest_changed_rev)
+ || (range_max < oldest_changed_rev))
+ continue;
+
+ /* Walk through the changed_revs to see if any of them fall
+ inside our current range. */
+ for (j = 0; j < changed_revs->nelts; j++)
+ {
+ svn_revnum_t changed_rev
+ = APR_ARRAY_IDX(changed_revs, j, svn_revnum_t);
+ if ((changed_rev >= range_min) && (changed_rev <= range_max))
+ {
+ APR_ARRAY_PUSH(operative_ranges, svn_merge_range_t *) =
+ range;
+ break;
+ }
+ }
+ }
+ }
+
+ *operative_ranges_p = operative_ranges;
+ return SVN_NO_ERROR;
+}
+
+
+/*-----------------------------------------------------------------------*/
+
+/*** Merge Source Normalization ***/
+
+/* qsort-compatible sort routine, rating merge_source_t * objects to
+ be in descending (youngest-to-oldest) order based on their ->loc1->rev
+ component. */
+static int
+compare_merge_source_ts(const void *a,
+ const void *b)
+{
+ svn_revnum_t a_rev = (*(const merge_source_t *const *)a)->loc1->rev;
+ svn_revnum_t b_rev = (*(const merge_source_t *const *)b)->loc1->rev;
+ if (a_rev == b_rev)
+ return 0;
+ return a_rev < b_rev ? 1 : -1;
+}
+
+/* Set *MERGE_SOURCE_TS_P to a list of merge sources generated by
+ slicing history location SEGMENTS with a given requested merge
+ RANGE. Use SOURCE_LOC for full source URL calculation.
+
+ Order the merge sources in *MERGE_SOURCE_TS_P from oldest to
+ youngest. */
+static svn_error_t *
+combine_range_with_segments(apr_array_header_t **merge_source_ts_p,
+ const svn_merge_range_t *range,
+ const apr_array_header_t *segments,
+ const svn_client__pathrev_t *source_loc,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *merge_source_ts =
+ apr_array_make(pool, 1, sizeof(merge_source_t *));
+ svn_revnum_t minrev = MIN(range->start, range->end) + 1;
+ svn_revnum_t maxrev = MAX(range->start, range->end);
+ svn_boolean_t subtractive = (range->start > range->end);
+ int i;
+
+ for (i = 0; i < segments->nelts; i++)
+ {
+ svn_location_segment_t *segment =
+ APR_ARRAY_IDX(segments, i, svn_location_segment_t *);
+ svn_client__pathrev_t *loc1, *loc2;
+ merge_source_t *merge_source;
+ const char *path1 = NULL;
+ svn_revnum_t rev1;
+
+ /* If this segment doesn't overlap our range at all, or
+ represents a gap, ignore it. */
+ if ((segment->range_end < minrev)
+ || (segment->range_start > maxrev)
+ || (! segment->path))
+ continue;
+
+ /* If our range spans a segment boundary, we have to point our
+ merge_source_t's path1 to the path of the immediately older
+ segment, else it points to the same location as its path2. */
+ rev1 = MAX(segment->range_start, minrev) - 1;
+ if (minrev <= segment->range_start)
+ {
+ if (i > 0)
+ {
+ path1 = (APR_ARRAY_IDX(segments, i - 1,
+ svn_location_segment_t *))->path;
+ }
+ /* If we've backed PATH1 up into a segment gap, let's back
+ it up further still to the segment before the gap. We'll
+ have to adjust rev1, too. */
+ if ((! path1) && (i > 1))
+ {
+ path1 = (APR_ARRAY_IDX(segments, i - 2,
+ svn_location_segment_t *))->path;
+ rev1 = (APR_ARRAY_IDX(segments, i - 2,
+ svn_location_segment_t *))->range_end;
+ }
+ }
+ else
+ {
+ path1 = apr_pstrdup(pool, segment->path);
+ }
+
+ /* If we don't have two valid paths, we won't know what to do
+ when merging. This could happen if someone requested a merge
+ where the source didn't exist in a particular revision or
+ something. The merge code would probably bomb out anyway, so
+ we'll just *not* create a merge source in this case. */
+ if (! (path1 && segment->path))
+ continue;
+
+ /* Build our merge source structure. */
+ loc1 = svn_client__pathrev_create_with_relpath(
+ source_loc->repos_root_url, source_loc->repos_uuid,
+ rev1, path1, pool);
+ loc2 = svn_client__pathrev_create_with_relpath(
+ source_loc->repos_root_url, source_loc->repos_uuid,
+ MIN(segment->range_end, maxrev), segment->path, pool);
+ /* If this is subtractive, reverse the whole calculation. */
+ if (subtractive)
+ merge_source = merge_source_create(loc2, loc1, TRUE /* ancestral */,
+ pool);
+ else
+ merge_source = merge_source_create(loc1, loc2, TRUE /* ancestral */,
+ pool);
+
+ APR_ARRAY_PUSH(merge_source_ts, merge_source_t *) = merge_source;
+ }
+
+ /* If this was a subtractive merge, and we created more than one
+ merge source, we need to reverse the sort ordering of our sources. */
+ if (subtractive && (merge_source_ts->nelts > 1))
+ qsort(merge_source_ts->elts, merge_source_ts->nelts,
+ merge_source_ts->elt_size, compare_merge_source_ts);
+
+ *merge_source_ts_p = merge_source_ts;
+ return SVN_NO_ERROR;
+}
+
+/* Similar to normalize_merge_sources() except the input MERGE_RANGE_TS is a
+ * rangelist.
+ */
+static svn_error_t *
+normalize_merge_sources_internal(apr_array_header_t **merge_sources_p,
+ const svn_client__pathrev_t *source_loc,
+ const svn_rangelist_t *merge_range_ts,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_revnum_t source_peg_revnum = source_loc->rev;
+ svn_revnum_t oldest_requested, youngest_requested;
+ svn_revnum_t trim_revision = SVN_INVALID_REVNUM;
+ apr_array_header_t *segments;
+ int i;
+
+ /* Initialize our return variable. */
+ *merge_sources_p = apr_array_make(result_pool, 1, sizeof(merge_source_t *));
+
+ /* No ranges to merge? No problem. */
+ if (merge_range_ts->nelts == 0)
+ return SVN_NO_ERROR;
+
+ /* Find the extremes of the revisions across our set of ranges. */
+ merge_range_find_extremes(&oldest_requested, &youngest_requested,
+ merge_range_ts);
+
+ /* ### FIXME: Our underlying APIs can't yet handle the case where
+ the peg revision isn't the youngest of the three revisions. So
+ we'll just verify that the source in the peg revision is related
+ to the source in the youngest requested revision (which is
+ all the underlying APIs would do in this case right now anyway). */
+ if (source_peg_revnum < youngest_requested)
+ {
+ svn_client__pathrev_t *start_loc;
+
+ SVN_ERR(svn_client__repos_location(&start_loc,
+ ra_session, source_loc,
+ youngest_requested,
+ ctx, scratch_pool, scratch_pool));
+ source_peg_revnum = youngest_requested;
+ }
+
+ /* Fetch the locations for our merge range span. */
+ SVN_ERR(svn_client__repos_location_segments(&segments,
+ ra_session, source_loc->url,
+ source_peg_revnum,
+ youngest_requested,
+ oldest_requested,
+ ctx, result_pool));
+
+ /* See if we fetched enough history to do the job. "Surely we did,"
+ you say. "After all, we covered the entire requested merge
+ range." Yes, that's true, but if our first segment doesn't
+ extend back to the oldest request revision, we've got a special
+ case to deal with. Or if the first segment represents a gap,
+ that's another special case. */
+ trim_revision = SVN_INVALID_REVNUM;
+ if (segments->nelts)
+ {
+ svn_location_segment_t *first_segment =
+ APR_ARRAY_IDX(segments, 0, svn_location_segment_t *);
+
+ /* If the first segment doesn't start with the OLDEST_REQUESTED
+ revision, we'll need to pass a trim revision to our range
+ cruncher. */
+ if (first_segment->range_start != oldest_requested)
+ {
+ trim_revision = first_segment->range_start;
+ }
+
+ /* Else, if the first segment has no path (and therefore is a
+ gap), then we'll fetch the copy source revision from the
+ second segment (provided there is one, of course) and use it
+ to prepend an extra pathful segment to our list.
+
+ ### We could avoid this bit entirely if we'd passed
+ ### SVN_INVALID_REVNUM instead of OLDEST_REQUESTED to
+ ### svn_client__repos_location_segments(), but that would
+ ### really penalize clients hitting pre-1.5 repositories with
+ ### the typical small merge range request (because of the
+ ### lack of a node-origins cache in the repository). */
+ else if (! first_segment->path)
+ {
+ if (segments->nelts > 1)
+ {
+ svn_location_segment_t *second_segment =
+ APR_ARRAY_IDX(segments, 1, svn_location_segment_t *);
+ const char *segment_url;
+ const char *original_repos_relpath;
+ svn_revnum_t original_revision;
+ svn_opt_revision_t range_start_rev;
+ range_start_rev.kind = svn_opt_revision_number;
+ range_start_rev.value.number = second_segment->range_start;
+
+ segment_url = svn_path_url_add_component2(
+ source_loc->repos_root_url, second_segment->path,
+ scratch_pool);
+ SVN_ERR(svn_client__get_copy_source(&original_repos_relpath,
+ &original_revision,
+ segment_url,
+ &range_start_rev, ctx,
+ result_pool, scratch_pool));
+ /* Got copyfrom data? Fix up the first segment to cover
+ back to COPYFROM_REV + 1, and then prepend a new
+ segment covering just COPYFROM_REV. */
+ if (original_repos_relpath)
+ {
+ svn_location_segment_t *new_segment =
+ apr_pcalloc(result_pool, sizeof(*new_segment));
+
+ new_segment->path = original_repos_relpath;
+ new_segment->range_start = original_revision;
+ new_segment->range_end = original_revision;
+ svn_sort__array_insert(&new_segment, segments, 0);
+ }
+ }
+ }
+ }
+
+ /* For each range in our requested range set, try to determine the
+ path(s) associated with that range. */
+ for (i = 0; i < merge_range_ts->nelts; i++)
+ {
+ svn_merge_range_t *range =
+ APR_ARRAY_IDX(merge_range_ts, i, svn_merge_range_t *);
+ apr_array_header_t *merge_sources;
+
+ if (SVN_IS_VALID_REVNUM(trim_revision))
+ {
+ /* If the range predates the trim revision, discard it. */
+ if (MAX(range->start, range->end) < trim_revision)
+ continue;
+
+ /* If the range overlaps the trim revision, trim it. */
+ if (range->start < trim_revision)
+ range->start = trim_revision;
+ if (range->end < trim_revision)
+ range->end = trim_revision;
+ }
+
+ /* Copy the resulting merge sources into master list thereof. */
+ SVN_ERR(combine_range_with_segments(&merge_sources, range,
+ segments, source_loc,
+ result_pool));
+ apr_array_cat(*merge_sources_p, merge_sources);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Determine the normalized ranges to merge from a given line of history.
+
+ Calculate the result by intersecting the list of location segments at
+ which SOURCE_LOC existed along its line of history with the requested
+ revision ranges in RANGES_TO_MERGE. RANGES_TO_MERGE is an array of
+ (svn_opt_revision_range_t *) revision ranges. Use SOURCE_PATH_OR_URL to
+ resolve any WC-relative revision specifiers (such as 'base') in
+ RANGES_TO_MERGE.
+
+ Set *MERGE_SOURCES_P to an array of merge_source_t * objects, each
+ describing a normalized range of revisions to be merged from the line
+ history of SOURCE_LOC. Order the objects from oldest to youngest.
+
+ RA_SESSION is an RA session open to the repository of SOURCE_LOC; it may
+ be temporarily reparented within this function. Use RA_SESSION to find
+ the location segments along the line of history of SOURCE_LOC.
+
+ Allocate MERGE_SOURCES_P and its contents in RESULT_POOL.
+
+ See `MERGEINFO MERGE SOURCE NORMALIZATION' for more on the
+ background of this function.
+*/
+static svn_error_t *
+normalize_merge_sources(apr_array_header_t **merge_sources_p,
+ const char *source_path_or_url,
+ const svn_client__pathrev_t *source_loc,
+ const apr_array_header_t *ranges_to_merge,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *source_abspath_or_url;
+ svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
+ svn_rangelist_t *merge_range_ts;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ if(!svn_path_is_url(source_path_or_url))
+ SVN_ERR(svn_dirent_get_absolute(&source_abspath_or_url, source_path_or_url,
+ scratch_pool));
+ else
+ source_abspath_or_url = source_path_or_url;
+
+ /* Create a list to hold svn_merge_range_t's. */
+ merge_range_ts = apr_array_make(scratch_pool, ranges_to_merge->nelts,
+ sizeof(svn_merge_range_t *));
+
+ for (i = 0; i < ranges_to_merge->nelts; i++)
+ {
+ svn_opt_revision_range_t *range
+ = APR_ARRAY_IDX(ranges_to_merge, i, svn_opt_revision_range_t *);
+ svn_merge_range_t mrange;
+
+ svn_pool_clear(iterpool);
+
+ /* Resolve revisions to real numbers, validating as we go. */
+ if ((range->start.kind == svn_opt_revision_unspecified)
+ || (range->end.kind == svn_opt_revision_unspecified))
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Not all required revisions are specified"));
+
+ SVN_ERR(svn_client__get_revision_number(&mrange.start, &youngest_rev,
+ ctx->wc_ctx,
+ source_abspath_or_url,
+ ra_session, &range->start,
+ iterpool));
+ SVN_ERR(svn_client__get_revision_number(&mrange.end, &youngest_rev,
+ ctx->wc_ctx,
+ source_abspath_or_url,
+ ra_session, &range->end,
+ iterpool));
+
+ /* If this isn't a no-op range... */
+ if (mrange.start != mrange.end)
+ {
+ /* ...then add it to the list. */
+ mrange.inheritable = TRUE;
+ APR_ARRAY_PUSH(merge_range_ts, svn_merge_range_t *)
+ = svn_merge_range_dup(&mrange, scratch_pool);
+ }
+ }
+
+ SVN_ERR(normalize_merge_sources_internal(
+ merge_sources_p, source_loc,
+ merge_range_ts, ra_session, ctx, result_pool, scratch_pool));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/*-----------------------------------------------------------------------*/
+
+/*** Merge Workhorse Functions ***/
+
+/* Helper for do_directory_merge() and do_file_merge() which filters out a
+ path's own natural history from the mergeinfo describing a merge.
+
+ Given the natural history IMPLICIT_MERGEINFO of some wc merge target path,
+ the repository-relative merge source path SOURCE_REL_PATH, and the
+ requested merge range REQUESTED_RANGE from SOURCE_REL_PATH, remove any
+ portion of REQUESTED_RANGE which is already described in
+ IMPLICIT_MERGEINFO. Store the result in *FILTERED_RANGELIST.
+
+ This function only filters natural history for mergeinfo that will be
+ *added* during a forward merge. Removing natural history from explicit
+ mergeinfo is harmless. If REQUESTED_RANGE describes a reverse merge,
+ then *FILTERED_RANGELIST is simply populated with one range described
+ by REQUESTED_RANGE. *FILTERED_RANGELIST is never NULL.
+
+ Allocate *FILTERED_RANGELIST in POOL. */
+static svn_error_t *
+filter_natural_history_from_mergeinfo(svn_rangelist_t **filtered_rangelist,
+ const char *source_rel_path,
+ svn_mergeinfo_t implicit_mergeinfo,
+ svn_merge_range_t *requested_range,
+ apr_pool_t *pool)
+{
+ /* Make the REQUESTED_RANGE into a rangelist. */
+ svn_rangelist_t *requested_rangelist =
+ svn_rangelist__initialize(requested_range->start, requested_range->end,
+ requested_range->inheritable, pool);
+
+ *filtered_rangelist = NULL;
+
+ /* For forward merges: If the IMPLICIT_MERGEINFO already describes ranges
+ associated with SOURCE_REL_PATH then filter those ranges out. */
+ if (implicit_mergeinfo
+ && (requested_range->start < requested_range->end))
+ {
+ svn_rangelist_t *implied_rangelist =
+ svn_hash_gets(implicit_mergeinfo, source_rel_path);
+
+ if (implied_rangelist)
+ SVN_ERR(svn_rangelist_remove(filtered_rangelist,
+ implied_rangelist,
+ requested_rangelist,
+ FALSE, pool));
+ }
+
+ /* If no filtering was performed the filtered rangelist is
+ simply the requested rangelist.*/
+ if (! (*filtered_rangelist))
+ *filtered_rangelist = requested_rangelist;
+
+ return SVN_NO_ERROR;
+}
+
+/* Return a merge source representing the sub-range from START_REV to
+ END_REV of SOURCE. SOURCE obeys the rules described in the
+ 'MERGEINFO MERGE SOURCE NORMALIZATION' comment at the top of this file.
+ The younger of START_REV and END_REV is inclusive while the older is
+ exclusive.
+
+ Allocate the result structure in POOL but leave the URLs in it as shallow
+ copies of the URLs in SOURCE.
+*/
+static merge_source_t *
+subrange_source(const merge_source_t *source,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ apr_pool_t *pool)
+{
+ svn_boolean_t is_rollback = (source->loc1->rev > source->loc2->rev);
+ svn_boolean_t same_urls = (strcmp(source->loc1->url, source->loc2->url) == 0);
+ svn_client__pathrev_t loc1 = *source->loc1;
+ svn_client__pathrev_t loc2 = *source->loc2;
+
+ /* For this function we require that the input source is 'ancestral'. */
+ SVN_ERR_ASSERT_NO_RETURN(source->ancestral);
+ SVN_ERR_ASSERT_NO_RETURN(start_rev != end_rev);
+
+ loc1.rev = start_rev;
+ loc2.rev = end_rev;
+ if (! same_urls)
+ {
+ if (is_rollback && (end_rev != source->loc2->rev))
+ {
+ loc2.url = source->loc1->url;
+ }
+ if ((! is_rollback) && (start_rev != source->loc1->rev))
+ {
+ loc1.url = source->loc2->url;
+ }
+ }
+ return merge_source_create(&loc1, &loc2, source->ancestral, pool);
+}
+
+/* The single-file, simplified version of do_directory_merge(), which see for
+ parameter descriptions.
+
+ Additional parameters:
+
+ If SOURCES_RELATED is set, the "left" and "right" sides of SOURCE are
+ historically related (ancestors, uncles, second
+ cousins thrice removed, etc...). (This is used to simulate the
+ history checks that the repository logic does in the directory case.)
+
+ If mergeinfo is being recorded to describe this merge, and RESULT_CATALOG
+ is not NULL, then don't record the new mergeinfo on the TARGET_ABSPATH,
+ but instead record it in RESULT_CATALOG, where the key is TARGET_ABSPATH
+ and the value is the new mergeinfo for that path. Allocate additions
+ to RESULT_CATALOG in pool which RESULT_CATALOG was created in.
+
+ CONFLICTED_RANGE is as documented for do_directory_merge().
+
+ Note: MERGE_B->RA_SESSION1 must be associated with SOURCE->loc1->url and
+ MERGE_B->RA_SESSION2 with SOURCE->loc2->url.
+*/
+static svn_error_t *
+do_file_merge(svn_mergeinfo_catalog_t result_catalog,
+ single_range_conflict_report_t **conflict_report,
+ const merge_source_t *source,
+ const char *target_abspath,
+ const svn_diff_tree_processor_t *processor,
+ svn_boolean_t sources_related,
+ svn_boolean_t squelch_mergeinfo_notifications,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_rangelist_t *remaining_ranges;
+ svn_client_ctx_t *ctx = merge_b->ctx;
+ svn_merge_range_t range;
+ svn_mergeinfo_t target_mergeinfo;
+ svn_boolean_t inherited = FALSE;
+ svn_boolean_t is_rollback = (source->loc1->rev > source->loc2->rev);
+ const svn_client__pathrev_t *primary_src
+ = is_rollback ? source->loc1 : source->loc2;
+ svn_boolean_t honor_mergeinfo = HONOR_MERGEINFO(merge_b);
+ svn_client__merge_path_t *merge_target = NULL;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
+
+ *conflict_report = NULL;
+
+ /* Note that this is a single-file merge. */
+ range.start = source->loc1->rev;
+ range.end = source->loc2->rev;
+ range.inheritable = TRUE;
+
+ merge_target = svn_client__merge_path_create(target_abspath, scratch_pool);
+
+ if (honor_mergeinfo)
+ {
+ svn_error_t *err;
+
+ /* Fetch mergeinfo. */
+ err = get_full_mergeinfo(&target_mergeinfo,
+ &(merge_target->implicit_mergeinfo),
+ &inherited, svn_mergeinfo_inherited,
+ merge_b->ra_session1, target_abspath,
+ MAX(source->loc1->rev, source->loc2->rev),
+ MIN(source->loc1->rev, source->loc2->rev),
+ ctx, scratch_pool, iterpool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ err = svn_error_createf(
+ SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING, err,
+ _("Invalid mergeinfo detected on merge target '%s', "
+ "merge tracking not possible"),
+ svn_dirent_local_style(target_abspath, scratch_pool));
+ }
+ return svn_error_trace(err);
+ }
+
+ /* Calculate remaining merges unless this is a record only merge.
+ In that case the remaining range is the whole range described
+ by SOURCE->rev1:rev2. */
+ if (!merge_b->record_only)
+ {
+ /* ### Bug? calculate_remaining_ranges() needs 'source' to adhere
+ * to the requirements of 'MERGEINFO MERGE SOURCE NORMALIZATION'
+ * here, but it doesn't appear to be guaranteed so. */
+ SVN_ERR(calculate_remaining_ranges(NULL, merge_target,
+ source,
+ target_mergeinfo,
+ merge_b->implicit_src_gap, FALSE,
+ merge_b->ra_session1,
+ ctx, scratch_pool,
+ iterpool));
+ remaining_ranges = merge_target->remaining_ranges;
+
+ /* We are honoring mergeinfo and this is not a simple record only
+ merge which blindly records mergeinfo describing the merge of
+ SOURCE->LOC1->URL@SOURCE->LOC1->REV through
+ SOURCE->LOC2->URL@SOURCE->LOC2->REV. This means that the oldest
+ and youngest revisions merged (as determined above by
+ calculate_remaining_ranges) might differ from those described
+ in SOURCE. To keep the '--- Merging *' notifications consistent
+ with the '--- Recording mergeinfo *' notifications, we adjust
+ RANGE to account for such changes. */
+ if (remaining_ranges->nelts)
+ {
+ svn_merge_range_t *adj_start_range =
+ APR_ARRAY_IDX(remaining_ranges, 0, svn_merge_range_t *);
+ svn_merge_range_t *adj_end_range =
+ APR_ARRAY_IDX(remaining_ranges, remaining_ranges->nelts - 1,
+ svn_merge_range_t *);
+ range.start = adj_start_range->start;
+ range.end = adj_end_range->end;
+ }
+ }
+ }
+
+ /* The simple cases where our remaining range is SOURCE->rev1:rev2. */
+ if (!honor_mergeinfo || merge_b->record_only)
+ {
+ remaining_ranges = apr_array_make(scratch_pool, 1, sizeof(&range));
+ APR_ARRAY_PUSH(remaining_ranges, svn_merge_range_t *) = &range;
+ }
+
+ if (!merge_b->record_only)
+ {
+ svn_rangelist_t *ranges_to_merge = apr_array_copy(scratch_pool,
+ remaining_ranges);
+ const char *target_relpath = ""; /* relative to root of merge */
+
+ if (source->ancestral)
+ {
+ apr_array_header_t *child_with_mergeinfo;
+ svn_client__merge_path_t *target_info;
+
+ /* If we have ancestrally related sources and more than one
+ range to merge, eliminate no-op ranges before going through
+ the effort of downloading the many copies of the file
+ required to do these merges (two copies per range). */
+ if (remaining_ranges->nelts > 1)
+ {
+ const char *old_sess_url;
+ svn_error_t *err;
+
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_sess_url,
+ merge_b->ra_session1,
+ primary_src->url,
+ iterpool));
+ err = remove_noop_merge_ranges(&ranges_to_merge,
+ merge_b->ra_session1,
+ remaining_ranges, scratch_pool);
+ SVN_ERR(svn_error_compose_create(
+ err, svn_ra_reparent(merge_b->ra_session1,
+ old_sess_url, iterpool)));
+ }
+
+ /* To support notify_merge_begin() initialize our
+ CHILD_WITH_MERGEINFO. See the comment
+ 'THE CHILDREN_WITH_MERGEINFO ARRAY' at the start of this file. */
+
+ child_with_mergeinfo = apr_array_make(scratch_pool, 1,
+ sizeof(svn_client__merge_path_t *));
+
+ /* ### Create a fake copy of merge_target as we don't keep
+ remaining_ranges in sync (yet). */
+ target_info = apr_pcalloc(scratch_pool, sizeof(*target_info));
+
+ target_info->abspath = merge_target->abspath;
+ target_info->remaining_ranges = ranges_to_merge;
+
+ APR_ARRAY_PUSH(child_with_mergeinfo, svn_client__merge_path_t *)
+ = target_info;
+
+ /* And store in baton to allow using it from notify_merge_begin() */
+ merge_b->notify_begin.nodes_with_mergeinfo = child_with_mergeinfo;
+ }
+
+ while (ranges_to_merge->nelts > 0)
+ {
+ svn_merge_range_t *r = APR_ARRAY_IDX(ranges_to_merge, 0,
+ svn_merge_range_t *);
+ const merge_source_t *real_source;
+ const char *left_file, *right_file;
+ apr_hash_t *left_props, *right_props;
+ const svn_diff_source_t *left_source;
+ const svn_diff_source_t *right_source;
+
+ svn_pool_clear(iterpool);
+
+ /* Ensure any subsequent drives gets their own notification. */
+ merge_b->notify_begin.last_abspath = NULL;
+
+ /* While we currently don't allow it, in theory we could be
+ fetching two fulltexts from two different repositories here. */
+ if (source->ancestral)
+ real_source = subrange_source(source, r->start, r->end, iterpool);
+ else
+ real_source = source;
+ SVN_ERR(single_file_merge_get_file(&left_file, &left_props,
+ merge_b->ra_session1,
+ real_source->loc1,
+ target_abspath,
+ iterpool, iterpool));
+ SVN_ERR(single_file_merge_get_file(&right_file, &right_props,
+ merge_b->ra_session2,
+ real_source->loc2,
+ target_abspath,
+ iterpool, iterpool));
+ /* Calculate sources for the diff processor */
+ left_source = svn_diff__source_create(r->start, iterpool);
+ right_source = svn_diff__source_create(r->end, iterpool);
+
+
+ /* If the sources are related or we're ignoring ancestry in diffs,
+ do a text-n-props merge; otherwise, do a delete-n-add merge. */
+ if (! (merge_b->diff_ignore_ancestry || sources_related))
+ {
+ struct merge_dir_baton_t dir_baton;
+ void *file_baton;
+ svn_boolean_t skip;
+
+ /* Initialize minimal dir baton to allow calculating 'R'eplace
+ from 'D'elete + 'A'dd. */
+
+ memset(&dir_baton, 0, sizeof(dir_baton));
+ dir_baton.pool = iterpool;
+ dir_baton.tree_conflict_reason = CONFLICT_REASON_NONE;
+ dir_baton.tree_conflict_action = svn_wc_conflict_action_edit;
+ dir_baton.skip_reason = svn_wc_notify_state_unknown;
+
+ /* Delete... */
+ file_baton = NULL;
+ skip = FALSE;
+ SVN_ERR(processor->file_opened(&file_baton, &skip, target_relpath,
+ left_source,
+ NULL /* right_source */,
+ NULL /* copyfrom_source */,
+ &dir_baton,
+ processor,
+ iterpool, iterpool));
+ if (! skip)
+ SVN_ERR(processor->file_deleted(target_relpath,
+ left_source,
+ left_file,
+ left_props,
+ file_baton,
+ processor,
+ iterpool));
+
+ /* ...plus add... */
+ file_baton = NULL;
+ skip = FALSE;
+ SVN_ERR(processor->file_opened(&file_baton, &skip, target_relpath,
+ NULL /* left_source */,
+ right_source,
+ NULL /* copyfrom_source */,
+ &dir_baton,
+ processor,
+ iterpool, iterpool));
+ if (! skip)
+ SVN_ERR(processor->file_added(target_relpath,
+ NULL /* copyfrom_source */,
+ right_source,
+ NULL /* copyfrom_file */,
+ right_file,
+ NULL /* copyfrom_props */,
+ right_props,
+ file_baton,
+ processor,
+ iterpool));
+ /* ... equals replace. */
+ }
+ else
+ {
+ void *file_baton = NULL;
+ svn_boolean_t skip = FALSE;
+ apr_array_header_t *propchanges;
+
+
+ /* Deduce property diffs. */
+ SVN_ERR(svn_prop_diffs(&propchanges, right_props, left_props,
+ iterpool));
+
+ SVN_ERR(processor->file_opened(&file_baton, &skip, target_relpath,
+ left_source,
+ right_source,
+ NULL /* copyfrom_source */,
+ NULL /* dir_baton */,
+ processor,
+ iterpool, iterpool));
+ if (! skip)
+ SVN_ERR(processor->file_changed(target_relpath,
+ left_source,
+ right_source,
+ left_file,
+ right_file,
+ left_props,
+ right_props,
+ TRUE /* file changed */,
+ propchanges,
+ file_baton,
+ processor,
+ iterpool));
+ }
+
+ if (is_path_conflicted_by_merge(merge_b))
+ {
+ merge_source_t *remaining_range = NULL;
+
+ if (real_source->loc2->rev != source->loc2->rev)
+ remaining_range = subrange_source(source,
+ real_source->loc2->rev,
+ source->loc2->rev,
+ scratch_pool);
+ *conflict_report = single_range_conflict_report_create(
+ real_source, remaining_range, result_pool);
+
+ /* Only record partial mergeinfo if only a partial merge was
+ performed before a conflict was encountered. */
+ range.end = r->end;
+ break;
+ }
+
+ /* Now delete the just merged range from the hash
+ (This list is used from notify_merge_begin)
+
+ Directory merges use remove_first_range_from_remaining_ranges() */
+ svn_sort__array_delete(ranges_to_merge, 0, 1);
+ }
+ merge_b->notify_begin.last_abspath = NULL;
+ } /* !merge_b->record_only */
+
+ /* Record updated WC mergeinfo to account for our new merges, minus
+ any unresolved conflicts and skips. We use the original
+ REMAINING_RANGES here because we want to record all the requested
+ merge ranges, include the noop ones. */
+ if (RECORD_MERGEINFO(merge_b) && remaining_ranges->nelts)
+ {
+ const char *mergeinfo_path = svn_client__pathrev_fspath(primary_src,
+ scratch_pool);
+ svn_rangelist_t *filtered_rangelist;
+
+ /* Filter any ranges from TARGET_WCPATH's own history, there is no
+ need to record this explicitly in mergeinfo, it is already part
+ of TARGET_WCPATH's natural history (implicit mergeinfo). */
+ SVN_ERR(filter_natural_history_from_mergeinfo(
+ &filtered_rangelist,
+ mergeinfo_path,
+ merge_target->implicit_mergeinfo,
+ &range,
+ iterpool));
+
+ /* Only record mergeinfo if there is something other than
+ self-referential mergeinfo, but don't record mergeinfo if
+ TARGET_WCPATH was skipped. */
+ if (filtered_rangelist->nelts
+ && (apr_hash_count(merge_b->skipped_abspaths) == 0))
+ {
+ apr_hash_t *merges = apr_hash_make(iterpool);
+
+ /* If merge target has inherited mergeinfo set it before
+ recording the first merge range. */
+ if (inherited)
+ SVN_ERR(svn_client__record_wc_mergeinfo(target_abspath,
+ target_mergeinfo,
+ FALSE, ctx,
+ iterpool));
+
+ svn_hash_sets(merges, target_abspath, filtered_rangelist);
+
+ if (!squelch_mergeinfo_notifications)
+ {
+ /* Notify that we are recording mergeinfo describing a merge. */
+ svn_merge_range_t n_range;
+
+ SVN_ERR(svn_mergeinfo__get_range_endpoints(
+ &n_range.end, &n_range.start, merges, iterpool));
+ n_range.inheritable = TRUE;
+ notify_mergeinfo_recording(target_abspath, &n_range,
+ merge_b->ctx, iterpool);
+ }
+
+ SVN_ERR(update_wc_mergeinfo(result_catalog, target_abspath,
+ mergeinfo_path, merges, is_rollback,
+ ctx, iterpool));
+ }
+ }
+
+ merge_b->notify_begin.nodes_with_mergeinfo = NULL;
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for do_directory_merge() to handle the case where a merge editor
+ drive adds explicit mergeinfo to a path which didn't have any explicit
+ mergeinfo previously.
+
+ MERGE_B is cascaded from the argument of the same
+ name in do_directory_merge(). Should be called only after
+ do_directory_merge() has called populate_remaining_ranges() and populated
+ the remaining_ranges field of each child in
+ CHILDREN_WITH_MERGEINFO (i.e. the remaining_ranges fields can be
+ empty but never NULL).
+
+ If MERGE_B->DRY_RUN is true do nothing, if it is false then
+ for each path (if any) in MERGE_B->PATHS_WITH_NEW_MERGEINFO merge that
+ path's inherited mergeinfo (if any) with its working explicit mergeinfo
+ and set that as the path's new explicit mergeinfo. Then add an
+ svn_client__merge_path_t * element representing the path to
+ CHILDREN_WITH_MERGEINFO if it isn't already present. All fields
+ in any elements added to CHILDREN_WITH_MERGEINFO are initialized
+ to FALSE/NULL with the exception of 'path' and 'remaining_ranges'. The
+ latter is set to a rangelist equal to the remaining_ranges of the path's
+ nearest path-wise ancestor in CHILDREN_WITH_MERGEINFO.
+
+ Any elements added to CHILDREN_WITH_MERGEINFO are allocated
+ in POOL. */
+static svn_error_t *
+process_children_with_new_mergeinfo(merge_cmd_baton_t *merge_b,
+ apr_array_header_t *children_with_mergeinfo,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool;
+ apr_hash_index_t *hi;
+
+ if (!merge_b->paths_with_new_mergeinfo || merge_b->dry_run)
+ return SVN_NO_ERROR;
+
+ /* Iterate over each path with explicit mergeinfo added by the merge. */
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, merge_b->paths_with_new_mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *abspath_with_new_mergeinfo = svn__apr_hash_index_key(hi);
+ svn_mergeinfo_t path_inherited_mergeinfo;
+ svn_mergeinfo_t path_explicit_mergeinfo;
+ svn_client__merge_path_t *new_child;
+
+ svn_pool_clear(iterpool);
+
+ /* Note: We could skip recording inherited mergeinfo here if this path
+ was added (with preexisting mergeinfo) by the merge. That's actually
+ more correct, since the inherited mergeinfo likely describes
+ non-existent or unrelated merge history, but it's not quite so simple
+ as that, see http://subversion.tigris.org/issues/show_bug.cgi?id=4309
+ */
+
+ /* Get the path's new explicit mergeinfo... */
+ SVN_ERR(svn_client__get_wc_mergeinfo(&path_explicit_mergeinfo, NULL,
+ svn_mergeinfo_explicit,
+ abspath_with_new_mergeinfo,
+ NULL, NULL, FALSE,
+ merge_b->ctx,
+ iterpool, iterpool));
+ /* ...there *should* always be explicit mergeinfo at this point
+ but you can't be too careful. */
+ if (path_explicit_mergeinfo)
+ {
+ /* Get the mergeinfo the path would have inherited before
+ the merge. */
+ SVN_ERR(svn_client__get_wc_or_repos_mergeinfo(
+ &path_inherited_mergeinfo,
+ NULL, NULL,
+ FALSE,
+ svn_mergeinfo_nearest_ancestor, /* We only want inherited MI */
+ merge_b->ra_session2,
+ abspath_with_new_mergeinfo,
+ merge_b->ctx,
+ iterpool));
+
+ /* If the path inherited any mergeinfo then merge that with the
+ explicit mergeinfo and record the result as the path's new
+ explicit mergeinfo. */
+ if (path_inherited_mergeinfo)
+ {
+ SVN_ERR(svn_mergeinfo_merge2(path_explicit_mergeinfo,
+ path_inherited_mergeinfo,
+ iterpool, iterpool));
+ SVN_ERR(svn_client__record_wc_mergeinfo(
+ abspath_with_new_mergeinfo,
+ path_explicit_mergeinfo,
+ FALSE, merge_b->ctx, iterpool));
+ }
+
+ /* If the path is not in CHILDREN_WITH_MERGEINFO then add it. */
+ new_child =
+ get_child_with_mergeinfo(children_with_mergeinfo,
+ abspath_with_new_mergeinfo);
+ if (!new_child)
+ {
+ const svn_client__merge_path_t *parent
+ = find_nearest_ancestor(children_with_mergeinfo,
+ FALSE, abspath_with_new_mergeinfo);
+ new_child
+ = svn_client__merge_path_create(abspath_with_new_mergeinfo,
+ pool);
+
+ /* If path_with_new_mergeinfo is the merge target itself
+ then it should already be in
+ CHILDREN_WITH_MERGEINFO per the criteria of
+ get_mergeinfo_paths() and we shouldn't be in this block.
+ If path_with_new_mergeinfo is a subtree then it must have
+ a parent in CHILDREN_WITH_MERGEINFO if only
+ the merge target itself...so if we don't find a parent
+ the caller has done something quite wrong. */
+ SVN_ERR_ASSERT(parent);
+ SVN_ERR_ASSERT(parent->remaining_ranges);
+
+ /* Set the path's remaining_ranges equal to its parent's. */
+ new_child->remaining_ranges = svn_rangelist_dup(
+ parent->remaining_ranges, pool);
+ insert_child_to_merge(children_with_mergeinfo, new_child, pool);
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return true if any path in SUBTREES is equal to, or is a subtree of,
+ LOCAL_ABSPATH. Return false otherwise. The keys of SUBTREES are
+ (const char *) absolute paths and its values are irrelevant.
+ If SUBTREES is NULL return false. */
+static svn_boolean_t
+path_is_subtree(const char *local_abspath,
+ apr_hash_t *subtrees,
+ apr_pool_t *pool)
+{
+ if (subtrees)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, subtrees);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *path_touched_by_merge = svn__apr_hash_index_key(hi);
+ if (svn_dirent_is_ancestor(local_abspath, path_touched_by_merge))
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/* Return true if any merged, skipped, added or tree-conflicted path
+ recorded in MERGE_B is equal to, or is a subtree of LOCAL_ABSPATH. Return
+ false otherwise.
+
+ ### Why not text- or prop-conflicted paths? Are such paths guaranteed
+ to be recorded as 'merged' or 'skipped' or 'added', perhaps?
+*/
+static svn_boolean_t
+subtree_touched_by_merge(const char *local_abspath,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *pool)
+{
+ return (path_is_subtree(local_abspath, merge_b->merged_abspaths, pool)
+ || path_is_subtree(local_abspath, merge_b->skipped_abspaths, pool)
+ || path_is_subtree(local_abspath, merge_b->added_abspaths, pool)
+ || path_is_subtree(local_abspath, merge_b->tree_conflicted_abspaths,
+ pool));
+}
+
+/* Helper for do_directory_merge() when performing mergeinfo unaware merges.
+
+ Merge the SOURCE diff into TARGET_DIR_WCPATH.
+
+ SOURCE, DEPTH, NOTIFY_B, and MERGE_B
+ are all cascaded from do_directory_merge's arguments of the same names.
+
+ CONFLICT_REPORT is as documented for do_directory_merge().
+
+ NOTE: This is a very thin wrapper around drive_merge_report_editor() and
+ exists only to populate CHILDREN_WITH_MERGEINFO with the single element
+ expected during mergeinfo unaware merges.
+*/
+static svn_error_t *
+do_mergeinfo_unaware_dir_merge(single_range_conflict_report_t **conflict_report,
+ const merge_source_t *source,
+ const char *target_dir_wcpath,
+ apr_array_header_t *children_with_mergeinfo,
+ const svn_diff_tree_processor_t *processor,
+ svn_depth_t depth,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Initialize CHILDREN_WITH_MERGEINFO and populate it with
+ one element describing the merge of SOURCE->rev1:rev2 to
+ TARGET_DIR_WCPATH. */
+ svn_client__merge_path_t *item
+ = svn_client__merge_path_create(target_dir_wcpath, scratch_pool);
+
+ *conflict_report = NULL;
+ item->remaining_ranges = svn_rangelist__initialize(source->loc1->rev,
+ source->loc2->rev,
+ TRUE, scratch_pool);
+ APR_ARRAY_PUSH(children_with_mergeinfo,
+ svn_client__merge_path_t *) = item;
+ SVN_ERR(drive_merge_report_editor(target_dir_wcpath,
+ source,
+ NULL, processor, depth,
+ merge_b, scratch_pool));
+ if (is_path_conflicted_by_merge(merge_b))
+ {
+ *conflict_report = single_range_conflict_report_create(
+ source, NULL, result_pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* A svn_log_entry_receiver_t baton for log_find_operative_subtree_revs(). */
+typedef struct log_find_operative_subtree_baton_t
+{
+ /* Mapping of const char * absolute working copy paths to those
+ path's const char * repos absolute paths. */
+ apr_hash_t *operative_children;
+
+ /* As per the arguments of the same name to
+ get_operative_immediate_children(). */
+ const char *merge_source_fspath;
+ const char *merge_target_abspath;
+ svn_depth_t depth;
+ svn_wc_context_t *wc_ctx;
+
+ /* A pool to allocate additions to the hashes in. */
+ apr_pool_t *result_pool;
+} log_find_operative_subtree_baton_t;
+
+/* A svn_log_entry_receiver_t callback for
+ get_inoperative_immediate_children(). */
+static svn_error_t *
+log_find_operative_subtree_revs(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ log_find_operative_subtree_baton_t *log_baton = baton;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ /* It's possible that authz restrictions on the merge source prevent us
+ from knowing about any of the changes for LOG_ENTRY->REVISION. */
+ if (!log_entry->changed_paths2)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(pool);
+
+ for (hi = apr_hash_first(pool, log_entry->changed_paths2);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_log_changed_path2_t *change = svn__apr_hash_index_val(hi);
+
+ {
+ const char *child;
+ const char *potential_child;
+ const char *rel_path =
+ svn_fspath__skip_ancestor(log_baton->merge_source_fspath, path);
+
+ /* Some affected paths might be the root of the merge source or
+ entirely outside our subtree of interest. In either case they
+ are not operative *immediate* children. */
+ if (rel_path == NULL
+ || rel_path[0] == '\0')
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ child = svn_relpath_dirname(rel_path, iterpool);
+ if (child[0] == '\0')
+ {
+ /* The svn_log_changed_path2_t.node_kind members in
+ LOG_ENTRY->CHANGED_PATHS2 may be set to
+ svn_node_unknown, see svn_log_changed_path2_t and
+ svn_fs_paths_changed2. In that case we check the
+ type of the corresponding subtree in the merge
+ target. */
+ svn_node_kind_t node_kind;
+
+ if (change->node_kind == svn_node_unknown)
+ {
+ const char *wc_child_abspath =
+ svn_dirent_join(log_baton->merge_target_abspath,
+ rel_path, iterpool);
+
+ SVN_ERR(svn_wc_read_kind2(&node_kind, log_baton->wc_ctx,
+ wc_child_abspath, FALSE, FALSE,
+ iterpool));
+ }
+ else
+ {
+ node_kind = change->node_kind;
+ }
+
+ /* We only care about immediate directory children if
+ DEPTH is svn_depth_files. */
+ if (log_baton->depth == svn_depth_files
+ && node_kind != svn_node_dir)
+ continue;
+
+ /* If depth is svn_depth_immediates, then we only care
+ about changes to proper subtrees of PATH. If the change
+ is to PATH itself then PATH is within the operational
+ depth of the merge. */
+ if (log_baton->depth == svn_depth_immediates)
+ continue;
+
+ child = rel_path;
+ }
+
+ potential_child = svn_dirent_join(log_baton->merge_target_abspath,
+ child, iterpool);
+
+ if (change->action == 'A'
+ || !svn_hash_gets(log_baton->operative_children,
+ potential_child))
+ {
+ svn_hash_sets(log_baton->operative_children,
+ apr_pstrdup(log_baton->result_pool,
+ potential_child),
+ apr_pstrdup(log_baton->result_pool, path));
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Find immediate subtrees of MERGE_TARGET_ABSPATH which would have
+ additional differences applied if record_mergeinfo_for_dir_merge() were
+ recording mergeinfo describing a merge at svn_depth_infinity, rather
+ than at DEPTH (which is assumed to be shallow; if
+ DEPTH == svn_depth_infinity then this function does nothing beyond
+ setting *OPERATIVE_CHILDREN to an empty hash).
+
+ MERGE_SOURCE_FSPATH is the absolute repository path of the merge
+ source. OLDEST_REV and YOUNGEST_REV are the revisions merged from
+ MERGE_SOURCE_FSPATH to MERGE_TARGET_ABSPATH.
+
+ RA_SESSION points to MERGE_SOURCE_FSPATH.
+
+ Set *OPERATIVE_CHILDREN to a hash (mapping const char * absolute
+ working copy paths to those path's const char * repos absolute paths)
+ containing all the immediate subtrees of MERGE_TARGET_ABSPATH which would
+ have a different diff applied if MERGE_SOURCE_FSPATH
+ -r(OLDEST_REV - 1):YOUNGEST_REV were merged to MERGE_TARGET_ABSPATH at
+ svn_depth_infinity rather than DEPTH.
+
+ RESULT_POOL is used to allocate the contents of *OPERATIVE_CHILDREN.
+ SCRATCH_POOL is used for temporary allocations. */
+static svn_error_t *
+get_operative_immediate_children(apr_hash_t **operative_children,
+ const char *merge_source_fspath,
+ svn_revnum_t oldest_rev,
+ svn_revnum_t youngest_rev,
+ const char *merge_target_abspath,
+ svn_depth_t depth,
+ svn_wc_context_t *wc_ctx,
+ svn_ra_session_t *ra_session,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ log_find_operative_subtree_baton_t log_baton;
+
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(oldest_rev));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
+ SVN_ERR_ASSERT(oldest_rev <= youngest_rev);
+
+ *operative_children = apr_hash_make(result_pool);
+
+ if (depth == svn_depth_infinity)
+ return SVN_NO_ERROR;
+
+ /* Now remove any paths from *OPERATIVE_CHILDREN that are inoperative when
+ merging MERGE_SOURCE_REPOS_PATH -r(OLDEST_REV - 1):YOUNGEST_REV to
+ MERGE_TARGET_ABSPATH at --depth infinity. */
+ log_baton.operative_children = *operative_children;
+ log_baton.merge_source_fspath = merge_source_fspath;
+ log_baton.merge_target_abspath = merge_target_abspath;
+ log_baton.depth = depth;
+ log_baton.wc_ctx = wc_ctx;
+ log_baton.result_pool = result_pool;
+
+ SVN_ERR(get_log(ra_session, "", youngest_rev, oldest_rev,
+ TRUE, /* discover_changed_paths */
+ log_find_operative_subtree_revs,
+ &log_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for record_mergeinfo_for_dir_merge(): Identify which elements of
+ CHILDREN_WITH_MERGEINFO need new mergeinfo set to accurately
+ describe a merge, what inheritance type such new mergeinfo should have,
+ and what subtrees can be ignored altogether.
+
+ For each svn_client__merge_path_t CHILD in CHILDREN_WITH_MERGEINFO,
+ set CHILD->RECORD_MERGEINFO and CHILD->RECORD_NONINHERITABLE to true
+ if the subtree needs mergeinfo to describe the merge and if that
+ mergeinfo should be non-inheritable respectively.
+
+ If OPERATIVE_MERGE is true, then the merge being described is operative
+ as per subtree_touched_by_merge(). OPERATIVE_MERGE is false otherwise.
+
+ MERGED_RANGE, MERGEINFO_FSPATH, DEPTH, NOTIFY_B, and MERGE_B are all
+ cascaded from record_mergeinfo_for_dir_merge's arguments of the same
+ names.
+
+ SCRATCH_POOL is used for temporary allocations.
+*/
+static svn_error_t *
+flag_subtrees_needing_mergeinfo(svn_boolean_t operative_merge,
+ const svn_merge_range_t *merged_range,
+ apr_array_header_t *children_with_mergeinfo,
+ const char *mergeinfo_fspath,
+ svn_depth_t depth,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+ apr_hash_t *operative_immediate_children = NULL;
+
+ assert(! merge_b->dry_run);
+
+ if (!merge_b->record_only
+ && merged_range->start <= merged_range->end
+ && (depth < svn_depth_infinity))
+ SVN_ERR(get_operative_immediate_children(
+ &operative_immediate_children,
+ mergeinfo_fspath, merged_range->start + 1, merged_range->end,
+ merge_b->target->abspath, depth, merge_b->ctx->wc_ctx,
+ merge_b->ra_session1, scratch_pool, iterpool));
+
+ /* Issue #4056: Walk NOTIFY_B->CHILDREN_WITH_MERGEINFO reverse depth-first
+ order. This way each child knows if it has operative missing/switched
+ children which necessitates non-inheritable mergeinfo. */
+ for (i = children_with_mergeinfo->nelts - 1; i >= 0; i--)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i,
+ svn_client__merge_path_t *);
+
+ /* Can't record mergeinfo on something that isn't here. */
+ if (child->absent)
+ continue;
+
+ /* Verify that remove_children_with_deleted_mergeinfo() did its job */
+ assert((i == 0)
+ ||! merge_b->paths_with_deleted_mergeinfo
+ || !svn_hash_gets(merge_b->paths_with_deleted_mergeinfo,
+ child->abspath));
+
+ /* Don't record mergeinfo on skipped paths. */
+ if (svn_hash_gets(merge_b->skipped_abspaths, child->abspath))
+ continue;
+
+ /* ### ptb: Yes, we could combine the following into a single
+ ### conditional, but clarity would suffer (even more than
+ ### it does now). */
+ if (i == 0)
+ {
+ /* Always record mergeinfo on the merge target. */
+ child->record_mergeinfo = TRUE;
+ }
+ else if (merge_b->record_only && !merge_b->reintegrate_merge)
+ {
+ /* Always record mergeinfo for --record-only merges. */
+ child->record_mergeinfo = TRUE;
+ }
+ else if (child->immediate_child_dir
+ && !child->pre_merge_mergeinfo
+ && operative_immediate_children
+ && svn_hash_gets(operative_immediate_children, child->abspath))
+ {
+ /* We must record mergeinfo on those issue #3642 children
+ that are operative at a greater depth. */
+ child->record_mergeinfo = TRUE;
+ }
+
+ if (operative_merge
+ && subtree_touched_by_merge(child->abspath, merge_b, iterpool))
+ {
+ svn_pool_clear(iterpool);
+
+ /* This subtree was affected by the merge. */
+ child->record_mergeinfo = TRUE;
+
+ /* Were any CHILD's missing children skipped by the merge?
+ If not, then CHILD's missing children don't need to be
+ considered when recording mergeinfo describing the merge. */
+ if (! merge_b->reintegrate_merge
+ && child->missing_child
+ && !path_is_subtree(child->abspath,
+ merge_b->skipped_abspaths,
+ iterpool))
+ {
+ child->missing_child = FALSE;
+ }
+
+ /* If CHILD has an immediate switched child or children and
+ none of these were touched by the merge, then we don't need
+ need to do any special handling of those switched subtrees
+ (e.g. record non-inheritable mergeinfo) when recording
+ mergeinfo describing the merge. */
+ if (child->switched_child)
+ {
+ int j;
+ svn_boolean_t operative_switched_child = FALSE;
+
+ for (j = i + 1;
+ j < children_with_mergeinfo->nelts;
+ j++)
+ {
+ svn_client__merge_path_t *potential_child =
+ APR_ARRAY_IDX(children_with_mergeinfo, j,
+ svn_client__merge_path_t *);
+ if (!svn_dirent_is_ancestor(child->abspath,
+ potential_child->abspath))
+ break;
+
+ /* POTENTIAL_CHILD is a subtree of CHILD, but is it
+ an immediate child? */
+ if (strcmp(child->abspath,
+ svn_dirent_dirname(potential_child->abspath,
+ iterpool)))
+ continue;
+
+ if (potential_child->switched
+ && potential_child->record_mergeinfo)
+ {
+ operative_switched_child = TRUE;
+ break;
+ }
+ }
+
+ /* Can we treat CHILD as if it has no switched children? */
+ if (! operative_switched_child)
+ child->switched_child = FALSE;
+ }
+ }
+
+ if (child->record_mergeinfo)
+ {
+ /* We need to record mergeinfo, but should that mergeinfo be
+ non-inheritable? */
+ svn_node_kind_t path_kind;
+ SVN_ERR(svn_wc_read_kind2(&path_kind, merge_b->ctx->wc_ctx,
+ child->abspath, FALSE, FALSE, iterpool));
+
+ /* Only directories can have non-inheritable mergeinfo. */
+ if (path_kind == svn_node_dir)
+ {
+ /* There are two general cases where non-inheritable mergeinfo
+ is required:
+
+ 1) There merge target has missing subtrees (due to authz
+ restrictions, switched subtrees, or a shallow working
+ copy).
+
+ 2) The operational depth of the merge itself is shallow. */
+
+ /* We've already determined the first case. */
+ child->record_noninheritable =
+ child->missing_child || child->switched_child;
+
+ /* The second case requires a bit more work. */
+ if (i == 0)
+ {
+ /* If CHILD is the root of the merge target and the
+ operational depth is empty or files, then the mere
+ existence of operative immediate children means we
+ must record non-inheritable mergeinfo.
+
+ ### What about svn_depth_immediates? In that case
+ ### the merge target needs only normal inheritable
+ ### mergeinfo and the target's immediate children will
+ ### get non-inheritable mergeinfo, assuming they
+ ### need even that. */
+ if (depth < svn_depth_immediates
+ && operative_immediate_children
+ && apr_hash_count(operative_immediate_children))
+ child->record_noninheritable = TRUE;
+ }
+ else if (depth == svn_depth_immediates)
+ {
+ /* An immediate directory child of the merge target, which
+ was affected by a --depth=immediates merge, needs
+ non-inheritable mergeinfo. */
+ if (svn_hash_gets(operative_immediate_children,
+ child->abspath))
+ child->record_noninheritable = TRUE;
+ }
+ }
+ }
+ else /* child->record_mergeinfo */
+ {
+ /* If CHILD is in NOTIFY_B->CHILDREN_WITH_MERGEINFO simply
+ because it had no explicit mergeinfo of its own at the
+ start of the merge but is the child of of some path with
+ non-inheritable mergeinfo, then the explicit mergeinfo it
+ has *now* was set by get_mergeinfo_paths() -- see criteria
+ 3 in that function's doc string. So since CHILD->ABSPATH
+ was not touched by the merge we can remove the
+ mergeinfo. */
+ if (child->child_of_noninheritable)
+ SVN_ERR(svn_client__record_wc_mergeinfo(child->abspath,
+ NULL, FALSE,
+ merge_b->ctx,
+ iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Helper for do_directory_merge().
+
+ If RESULT_CATALOG is NULL then record mergeinfo describing a merge of
+ MERGED_RANGE->START:MERGED_RANGE->END from the repository relative path
+ MERGEINFO_FSPATH to the merge target (and possibly its subtrees) described
+ by NOTIFY_B->CHILDREN_WITH_MERGEINFO -- see the global comment
+ 'THE CHILDREN_WITH_MERGEINFO ARRAY'. Obviously this should only
+ be called if recording mergeinfo -- see doc string for RECORD_MERGEINFO().
+
+ If RESULT_CATALOG is not NULL, then don't record the new mergeinfo on the
+ WC, but instead record it in RESULT_CATALOG, where the keys are absolute
+ working copy paths and the values are the new mergeinfos for each.
+ Allocate additions to RESULT_CATALOG in pool which RESULT_CATALOG was
+ created in.
+
+ DEPTH, NOTIFY_B, MERGE_B, and SQUELCH_MERGEINFO_NOTIFICATIONS are all
+ cascaded from do_directory_merge's arguments of the same names.
+
+ SCRATCH_POOL is used for temporary allocations.
+*/
+static svn_error_t *
+record_mergeinfo_for_dir_merge(svn_mergeinfo_catalog_t result_catalog,
+ const svn_merge_range_t *merged_range,
+ const char *mergeinfo_fspath,
+ apr_array_header_t *children_with_mergeinfo,
+ svn_depth_t depth,
+ svn_boolean_t squelch_mergeinfo_notifications,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_boolean_t is_rollback = (merged_range->start > merged_range->end);
+ svn_boolean_t operative_merge;
+
+ /* Update the WC mergeinfo here to account for our new
+ merges, minus any unresolved conflicts and skips. */
+
+ /* We need a scratch pool for iterations below. */
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ svn_merge_range_t range = *merged_range;
+
+ assert(! merge_b->dry_run);
+
+ /* Regardless of what subtrees in MERGE_B->target->abspath might be missing
+ could this merge have been operative? */
+ operative_merge = subtree_touched_by_merge(merge_b->target->abspath,
+ merge_b, iterpool);
+
+ /* If this couldn't be an operative merge then don't bother with
+ the added complexity (and user confusion) of non-inheritable ranges.
+ There is no harm in subtrees inheriting inoperative mergeinfo. */
+ if (!operative_merge)
+ range.inheritable = TRUE;
+
+ /* Remove absent children at or under MERGE_B->target->abspath from
+ NOTIFY_B->CHILDREN_WITH_MERGEINFO
+ before we calculate the merges performed. */
+ remove_absent_children(merge_b->target->abspath,
+ children_with_mergeinfo);
+
+ /* Determine which subtrees of interest need mergeinfo recorded... */
+ SVN_ERR(flag_subtrees_needing_mergeinfo(operative_merge, &range,
+ children_with_mergeinfo,
+ mergeinfo_fspath, depth,
+ merge_b, iterpool));
+
+ /* ...and then record it. */
+ for (i = 0; i < children_with_mergeinfo->nelts; i++)
+ {
+ const char *child_repos_path;
+ const char *child_merge_src_fspath;
+ svn_rangelist_t *child_merge_rangelist;
+ apr_hash_t *child_merges;
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i,
+ svn_client__merge_path_t *);
+ SVN_ERR_ASSERT(child);
+
+ svn_pool_clear(iterpool);
+
+ if (child->record_mergeinfo)
+ {
+ child_repos_path = svn_dirent_skip_ancestor(merge_b->target->abspath,
+ child->abspath);
+ SVN_ERR_ASSERT(child_repos_path != NULL);
+ child_merge_src_fspath = svn_fspath__join(mergeinfo_fspath,
+ child_repos_path,
+ iterpool);
+ /* Filter any ranges from each child's natural history before
+ setting mergeinfo describing the merge. */
+ SVN_ERR(filter_natural_history_from_mergeinfo(
+ &child_merge_rangelist, child_merge_src_fspath,
+ child->implicit_mergeinfo, &range, iterpool));
+
+ if (child_merge_rangelist->nelts == 0)
+ continue;
+
+ if (!squelch_mergeinfo_notifications)
+ {
+ /* If the merge source has a gap, then don't mention
+ those gap revisions in the notification. */
+ remove_source_gap(&range, merge_b->implicit_src_gap);
+ notify_mergeinfo_recording(child->abspath, &range,
+ merge_b->ctx, iterpool);
+ }
+
+ /* If we are here we know we will be recording some mergeinfo, but
+ before we do, set override mergeinfo on skipped paths so they
+ don't incorrectly inherit the mergeinfo we are about to set. */
+ if (i == 0)
+ SVN_ERR(record_skips_in_mergeinfo(mergeinfo_fspath,
+ child_merge_rangelist,
+ is_rollback, merge_b, iterpool));
+
+ /* We may need to record non-inheritable mergeinfo that applies
+ only to CHILD->ABSPATH. */
+ if (child->record_noninheritable)
+ svn_rangelist__set_inheritance(child_merge_rangelist, FALSE);
+
+ /* If CHILD has inherited mergeinfo set it before
+ recording the first merge range. */
+ if (child->inherited_mergeinfo)
+ SVN_ERR(svn_client__record_wc_mergeinfo(
+ child->abspath,
+ child->pre_merge_mergeinfo,
+ FALSE, merge_b->ctx,
+ iterpool));
+ if (merge_b->implicit_src_gap)
+ {
+ /* If this is a reverse merge reorder CHILD->REMAINING_RANGES
+ so it will work with the svn_rangelist_remove API. */
+ if (is_rollback)
+ SVN_ERR(svn_rangelist_reverse(child_merge_rangelist,
+ iterpool));
+
+ SVN_ERR(svn_rangelist_remove(&child_merge_rangelist,
+ merge_b->implicit_src_gap,
+ child_merge_rangelist, FALSE,
+ iterpool));
+ if (is_rollback)
+ SVN_ERR(svn_rangelist_reverse(child_merge_rangelist,
+ iterpool));
+ }
+
+ child_merges = apr_hash_make(iterpool);
+
+ /* The short story:
+
+ If we are describing a forward merge, then the naive mergeinfo
+ defined by MERGE_SOURCE_PATH:MERGED_RANGE->START:
+ MERGE_SOURCE_PATH:MERGED_RANGE->END may contain non-existent
+ path-revs or may describe other lines of history. We must
+ remove these invalid portion(s) before recording mergeinfo
+ describing the merge.
+
+ The long story:
+
+ If CHILD is the merge target we know that
+ MERGE_SOURCE_PATH:MERGED_RANGE->END exists. Further, if there
+ were no copies in MERGE_SOURCE_PATH's history going back to
+ RANGE->START then we know that
+ MERGE_SOURCE_PATH:MERGED_RANGE->START exists too and the two
+ describe an unbroken line of history, and thus
+ MERGE_SOURCE_PATH:MERGED_RANGE->START:
+ MERGE_SOURCE_PATH:MERGED_RANGE->END is a valid description of
+ the merge -- see normalize_merge_sources() and the global comment
+ 'MERGEINFO MERGE SOURCE NORMALIZATION'.
+
+ However, if there *was* a copy, then
+ MERGE_SOURCE_PATH:MERGED_RANGE->START doesn't exist or is
+ unrelated to MERGE_SOURCE_PATH:MERGED_RANGE->END. Also, we
+ don't know if (MERGE_SOURCE_PATH:MERGED_RANGE->START)+1 through
+ (MERGE_SOURCE_PATH:MERGED_RANGE->END)-1 actually exist.
+
+ If CHILD is a subtree of the merge target, then nothing is
+ guaranteed beyond the fact that MERGE_SOURCE_PATH exists at
+ MERGED_RANGE->END. */
+ if ((!merge_b->record_only || merge_b->reintegrate_merge)
+ && (!is_rollback))
+ {
+ svn_error_t *err;
+ svn_mergeinfo_t subtree_history_as_mergeinfo;
+ svn_rangelist_t *child_merge_src_rangelist;
+ svn_client__pathrev_t *subtree_mergeinfo_pathrev
+ = svn_client__pathrev_create_with_relpath(
+ merge_b->target->loc.repos_root_url,
+ merge_b->target->loc.repos_uuid,
+ merged_range->end, child_merge_src_fspath + 1,
+ iterpool);
+
+ /* Confirm that the naive mergeinfo we want to set on
+ CHILD->ABSPATH both exists and is part of
+ (MERGE_SOURCE_PATH+CHILD_REPOS_PATH)@MERGED_RANGE->END's
+ history. */
+ /* We know MERGED_RANGE->END is younger than MERGE_RANGE->START
+ because we only do this for forward merges. */
+ err = svn_client__get_history_as_mergeinfo(
+ &subtree_history_as_mergeinfo, NULL,
+ subtree_mergeinfo_pathrev,
+ merged_range->end, merged_range->start,
+ merge_b->ra_session2, merge_b->ctx, iterpool);
+
+ /* If CHILD is a subtree it may have been deleted prior to
+ MERGED_RANGE->END so the above call to get its history
+ will fail. */
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_FS_NOT_FOUND)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+ }
+ else
+ {
+ child_merge_src_rangelist = svn_hash_gets(
+ subtree_history_as_mergeinfo,
+ child_merge_src_fspath);
+ SVN_ERR(svn_rangelist_intersect(&child_merge_rangelist,
+ child_merge_rangelist,
+ child_merge_src_rangelist,
+ FALSE, iterpool));
+ if (child->record_noninheritable)
+ svn_rangelist__set_inheritance(child_merge_rangelist,
+ FALSE);
+ }
+ }
+
+ svn_hash_sets(child_merges, child->abspath, child_merge_rangelist);
+ SVN_ERR(update_wc_mergeinfo(result_catalog,
+ child->abspath,
+ child_merge_src_fspath,
+ child_merges, is_rollback,
+ merge_b->ctx, iterpool));
+
+ /* Once is enough: We don't need to record mergeinfo describing
+ the merge a second. If CHILD->ABSPATH is in
+ MERGE_B->ADDED_ABSPATHS, we'll do just that, so remove the
+ former from the latter. */
+ svn_hash_sets(merge_b->added_abspaths, child->abspath, NULL);
+ }
+
+ /* Elide explicit subtree mergeinfo whether or not we updated it. */
+ if (i > 0)
+ {
+ svn_boolean_t in_switched_subtree = FALSE;
+
+ if (child->switched)
+ in_switched_subtree = TRUE;
+ else if (i > 1)
+ {
+ /* Check if CHILD is part of a switched subtree */
+ svn_client__merge_path_t *parent;
+ int j = i - 1;
+ for (; j > 0; j--)
+ {
+ parent = APR_ARRAY_IDX(children_with_mergeinfo,
+ j, svn_client__merge_path_t *);
+ if (parent
+ && parent->switched
+ && svn_dirent_is_ancestor(parent->abspath,
+ child->abspath))
+ {
+ in_switched_subtree = TRUE;
+ break;
+ }
+ }
+ }
+
+ /* Allow mergeinfo on switched subtrees to elide to the
+ repository. Otherwise limit elision to the merge target
+ for now. do_directory_merge() will eventually try to
+ elide that when the merge is complete. */
+ SVN_ERR(svn_client__elide_mergeinfo(
+ child->abspath,
+ in_switched_subtree ? NULL : merge_b->target->abspath,
+ merge_b->ctx, iterpool));
+ }
+ } /* (i = 0; i < notify_b->children_with_mergeinfo->nelts; i++) */
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Helper for do_directory_merge().
+
+ Record mergeinfo describing a merge of
+ MERGED_RANGE->START:MERGED_RANGE->END from the repository relative path
+ MERGEINFO_FSPATH to each path in ADDED_ABSPATHS which has explicit
+ mergeinfo or is the immediate child of a parent with explicit
+ non-inheritable mergeinfo.
+
+ DEPTH, MERGE_B, and SQUELCH_MERGEINFO_NOTIFICATIONS, are
+ cascaded from do_directory_merge's arguments of the same names.
+
+ Note: This is intended to support forward merges only, i.e.
+ MERGED_RANGE->START must be older than MERGED_RANGE->END.
+*/
+static svn_error_t *
+record_mergeinfo_for_added_subtrees(
+ svn_merge_range_t *merged_range,
+ const char *mergeinfo_fspath,
+ svn_depth_t depth,
+ svn_boolean_t squelch_mergeinfo_notifications,
+ apr_hash_t *added_abspaths,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool;
+ apr_hash_index_t *hi;
+
+ /* If no paths were added by the merge then we have nothing to do. */
+ if (!added_abspaths)
+ return SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(merged_range->start < merged_range->end);
+
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, added_abspaths); hi; hi = apr_hash_next(hi))
+ {
+ const char *added_abspath = svn__apr_hash_index_key(hi);
+ const char *dir_abspath;
+ svn_mergeinfo_t parent_mergeinfo;
+ svn_mergeinfo_t added_path_mergeinfo;
+
+ svn_pool_clear(iterpool);
+ dir_abspath = svn_dirent_dirname(added_abspath, iterpool);
+
+ /* Grab the added path's explicit mergeinfo. */
+ SVN_ERR(svn_client__get_wc_mergeinfo(&added_path_mergeinfo, NULL,
+ svn_mergeinfo_explicit,
+ added_abspath, NULL, NULL, FALSE,
+ merge_b->ctx, iterpool, iterpool));
+
+ /* If the added path doesn't have explicit mergeinfo, does its immediate
+ parent have non-inheritable mergeinfo? */
+ if (!added_path_mergeinfo)
+ SVN_ERR(svn_client__get_wc_mergeinfo(&parent_mergeinfo, NULL,
+ svn_mergeinfo_explicit,
+ dir_abspath, NULL, NULL, FALSE,
+ merge_b->ctx,
+ iterpool, iterpool));
+
+ if (added_path_mergeinfo
+ || svn_mergeinfo__is_noninheritable(parent_mergeinfo, iterpool))
+ {
+ svn_node_kind_t added_path_kind;
+ svn_mergeinfo_t merge_mergeinfo;
+ svn_mergeinfo_t adds_history_as_mergeinfo;
+ svn_rangelist_t *rangelist;
+ const char *rel_added_path;
+ const char *added_path_mergeinfo_fspath;
+ svn_client__pathrev_t *added_path_pathrev;
+
+ SVN_ERR(svn_wc_read_kind2(&added_path_kind, merge_b->ctx->wc_ctx,
+ added_abspath, FALSE, FALSE, iterpool));
+
+ /* Calculate the naive mergeinfo describing the merge. */
+ merge_mergeinfo = apr_hash_make(iterpool);
+ rangelist = svn_rangelist__initialize(
+ merged_range->start, merged_range->end,
+ ((added_path_kind == svn_node_file)
+ || (!(depth == svn_depth_infinity
+ || depth == svn_depth_immediates))),
+ iterpool);
+
+ /* Create the new mergeinfo path for added_path's mergeinfo.
+ (added_abspath had better be a child of MERGE_B->target->abspath
+ or something is *really* wrong.) */
+ rel_added_path = svn_dirent_is_child(merge_b->target->abspath,
+ added_abspath, iterpool);
+ SVN_ERR_ASSERT(rel_added_path);
+ added_path_mergeinfo_fspath = svn_fspath__join(mergeinfo_fspath,
+ rel_added_path,
+ iterpool);
+ svn_hash_sets(merge_mergeinfo, added_path_mergeinfo_fspath,
+ rangelist);
+
+ /* Don't add new mergeinfo to describe the merge if that mergeinfo
+ contains non-existent merge sources.
+
+ We know that MERGEINFO_PATH/rel_added_path's history does not
+ span MERGED_RANGE->START:MERGED_RANGE->END but rather that it
+ was added at some revions greater than MERGED_RANGE->START
+ (assuming this is a forward merge). It may have been added,
+ deleted, and re-added many times. The point is that we cannot
+ blindly apply the naive mergeinfo calculated above because it
+ will describe non-existent merge sources. To avoid this we get
+ take the intersection of the naive mergeinfo with
+ MERGEINFO_PATH/rel_added_path's history. */
+ added_path_pathrev = svn_client__pathrev_create_with_relpath(
+ merge_b->target->loc.repos_root_url,
+ merge_b->target->loc.repos_uuid,
+ MAX(merged_range->start, merged_range->end),
+ added_path_mergeinfo_fspath + 1, iterpool);
+ SVN_ERR(svn_client__get_history_as_mergeinfo(
+ &adds_history_as_mergeinfo, NULL,
+ added_path_pathrev,
+ MAX(merged_range->start, merged_range->end),
+ MIN(merged_range->start, merged_range->end),
+ merge_b->ra_session2, merge_b->ctx, iterpool));
+
+ SVN_ERR(svn_mergeinfo_intersect2(&merge_mergeinfo,
+ merge_mergeinfo,
+ adds_history_as_mergeinfo,
+ FALSE, iterpool, iterpool));
+
+ /* Combine the explicit mergeinfo on the added path (if any)
+ with the mergeinfo describing this merge. */
+ if (added_path_mergeinfo)
+ SVN_ERR(svn_mergeinfo_merge2(merge_mergeinfo,
+ added_path_mergeinfo,
+ iterpool, iterpool));
+ SVN_ERR(svn_client__record_wc_mergeinfo(
+ added_abspath, merge_mergeinfo,
+ !squelch_mergeinfo_notifications, merge_b->ctx, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+/* Baton structure for log_noop_revs. */
+typedef struct log_noop_baton_t
+{
+ /* See the comment 'THE CHILDREN_WITH_MERGEINFO ARRAY' at the start
+ of this file.*/
+ apr_array_header_t *children_with_mergeinfo;
+
+ /* Absolute repository path of younger of the two merge sources
+ being diffed. */
+ const char *source_fspath;
+
+ /* The merge target. */
+ const merge_target_t *target;
+
+ /* Initially empty rangelists allocated in POOL. The rangelists are
+ * populated across multiple invocations of log_noop_revs(). */
+ svn_rangelist_t *operative_ranges;
+ svn_rangelist_t *merged_ranges;
+
+ /* Pool to store the rangelists. */
+ apr_pool_t *pool;
+} log_noop_baton_t;
+
+/* Helper for log_noop_revs: Merge a svn_merge_range_t representation of
+ REVISION into RANGELIST. New elements added to rangelist are allocated
+ in RESULT_POOL.
+
+ This is *not* a general purpose rangelist merge but a special replacement
+ for svn_rangelist_merge when REVISION is guaranteed to be younger than any
+ element in RANGELIST. svn_rangelist_merge is O(n) worst-case (i.e. when
+ all the ranges in output rangelist are older than the incoming changes).
+ This turns the special case of a single incoming younger range into O(1).
+ */
+static svn_error_t *
+rangelist_merge_revision(svn_rangelist_t *rangelist,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool)
+{
+ svn_merge_range_t *new_range;
+ if (rangelist->nelts)
+ {
+ svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1,
+ svn_merge_range_t *);
+ if (range->end == revision - 1)
+ {
+ /* REVISION is adjacent to the youngest range in RANGELIST
+ so we can simply expand that range to encompass REVISION. */
+ range->end = revision;
+ return SVN_NO_ERROR;
+ }
+ }
+ new_range = apr_palloc(result_pool, sizeof(*new_range));
+ new_range->start = revision - 1;
+ new_range->end = revision;
+ new_range->inheritable = TRUE;
+
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = new_range;
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements the svn_log_entry_receiver_t interface.
+
+ BATON is an log_noop_baton_t *.
+
+ Add LOG_ENTRY->REVISION to BATON->OPERATIVE_RANGES.
+
+ If LOG_ENTRY->REVISION has already been fully merged to
+ BATON->target->abspath per the mergeinfo in BATON->CHILDREN_WITH_MERGEINFO,
+ then add LOG_ENTRY->REVISION to BATON->MERGED_RANGES.
+
+ Use SCRATCH_POOL for temporary allocations. Allocate additions to
+ BATON->MERGED_RANGES and BATON->OPERATIVE_RANGES in BATON->POOL.
+
+ Note: This callback must be invoked from oldest LOG_ENTRY->REVISION
+ to youngest LOG_ENTRY->REVISION -- see rangelist_merge_revision().
+*/
+static svn_error_t *
+log_noop_revs(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *scratch_pool)
+{
+ log_noop_baton_t *log_gap_baton = baton;
+ apr_hash_index_t *hi;
+ svn_revnum_t revision;
+ svn_boolean_t log_entry_rev_required = FALSE;
+
+ revision = log_entry->revision;
+
+ /* It's possible that authz restrictions on the merge source prevent us
+ from knowing about any of the changes for LOG_ENTRY->REVISION. */
+ if (!log_entry->changed_paths2)
+ return SVN_NO_ERROR;
+
+ /* Unconditionally add LOG_ENTRY->REVISION to BATON->OPERATIVE_MERGES. */
+ SVN_ERR(rangelist_merge_revision(log_gap_baton->operative_ranges,
+ revision,
+ log_gap_baton->pool));
+
+ /* Examine each path affected by LOG_ENTRY->REVISION. If the explicit or
+ inherited mergeinfo for *all* of the corresponding paths under
+ BATON->target->abspath reflects that LOG_ENTRY->REVISION has been
+ merged, then add LOG_ENTRY->REVISION to BATON->MERGED_RANGES. */
+ for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *fspath = svn__apr_hash_index_key(hi);
+ const char *rel_path;
+ const char *cwmi_abspath;
+ svn_rangelist_t *paths_explicit_rangelist = NULL;
+ svn_boolean_t mergeinfo_inherited = FALSE;
+
+ /* Adjust REL_PATH so it is relative to the merge source then use it to
+ calculate what path in the merge target would be affected by this
+ revision. */
+ rel_path = svn_fspath__skip_ancestor(log_gap_baton->source_fspath,
+ fspath);
+ /* Is PATH even within the merge target? If it isn't we
+ can disregard it altogether. */
+ if (rel_path == NULL)
+ continue;
+ cwmi_abspath = svn_dirent_join(log_gap_baton->target->abspath,
+ rel_path, scratch_pool);
+
+ /* Find any explicit or inherited mergeinfo for PATH. */
+ while (!log_entry_rev_required)
+ {
+ svn_client__merge_path_t *child = get_child_with_mergeinfo(
+ log_gap_baton->children_with_mergeinfo, cwmi_abspath);
+
+ if (child && child->pre_merge_mergeinfo)
+ {
+ /* Found some explicit mergeinfo, grab any ranges
+ for PATH. */
+ paths_explicit_rangelist =
+ svn_hash_gets(child->pre_merge_mergeinfo, fspath);
+ break;
+ }
+
+ if (cwmi_abspath[0] == '\0'
+ || svn_dirent_is_root(cwmi_abspath, strlen(cwmi_abspath))
+ || strcmp(log_gap_baton->target->abspath, cwmi_abspath) == 0)
+ {
+ /* Can't crawl any higher. */
+ break;
+ }
+
+ /* Didn't find anything so crawl up to the parent. */
+ cwmi_abspath = svn_dirent_dirname(cwmi_abspath, scratch_pool);
+ fspath = svn_fspath__dirname(fspath, scratch_pool);
+
+ /* At this point *if* we find mergeinfo it will be inherited. */
+ mergeinfo_inherited = TRUE;
+ }
+
+ if (paths_explicit_rangelist)
+ {
+ svn_rangelist_t *intersecting_range;
+ svn_rangelist_t *rangelist;
+
+ rangelist = svn_rangelist__initialize(revision - 1, revision, TRUE,
+ scratch_pool);
+
+ /* If PATH inherited mergeinfo we must consider inheritance in the
+ event the inherited mergeinfo is actually non-inheritable. */
+ SVN_ERR(svn_rangelist_intersect(&intersecting_range,
+ paths_explicit_rangelist,
+ rangelist,
+ mergeinfo_inherited, scratch_pool));
+
+ if (intersecting_range->nelts == 0)
+ log_entry_rev_required = TRUE;
+ }
+ else
+ {
+ log_entry_rev_required = TRUE;
+ }
+ }
+
+ if (!log_entry_rev_required)
+ SVN_ERR(rangelist_merge_revision(log_gap_baton->merged_ranges,
+ revision,
+ log_gap_baton->pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for do_directory_merge().
+
+ SOURCE is cascaded from the argument of the same name in
+ do_directory_merge(). TARGET is the merge target. RA_SESSION is the
+ session for SOURCE->loc2.
+
+ Find all the ranges required by subtrees in
+ CHILDREN_WITH_MERGEINFO that are *not* required by
+ TARGET->abspath (i.e. CHILDREN_WITH_MERGEINFO[0]). If such
+ ranges exist, then find any subset of ranges which, if merged, would be
+ inoperative. Finally, if any inoperative ranges are found then remove
+ these ranges from all of the subtree's REMAINING_RANGES.
+
+ This function should only be called when honoring mergeinfo during
+ forward merges (i.e. SOURCE->rev1 < SOURCE->rev2).
+*/
+static svn_error_t *
+remove_noop_subtree_ranges(const merge_source_t *source,
+ const merge_target_t *target,
+ svn_ra_session_t *ra_session,
+ apr_array_header_t *children_with_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* ### Do we need to check that we are at a uniform working revision? */
+ int i;
+ svn_client__merge_path_t *root_child =
+ APR_ARRAY_IDX(children_with_mergeinfo, 0, svn_client__merge_path_t *);
+ svn_rangelist_t *requested_ranges;
+ svn_rangelist_t *subtree_gap_ranges;
+ svn_rangelist_t *subtree_remaining_ranges;
+ log_noop_baton_t log_gap_baton;
+ svn_merge_range_t *oldest_gap_rev;
+ svn_merge_range_t *youngest_gap_rev;
+ svn_rangelist_t *inoperative_ranges;
+ apr_pool_t *iterpool;
+ const char *longest_common_subtree_ancestor = NULL;
+ svn_error_t *err;
+
+ assert(session_url_is(ra_session, source->loc2->url, scratch_pool));
+
+ /* This function is only intended to work with forward merges. */
+ if (source->loc1->rev > source->loc2->rev)
+ return SVN_NO_ERROR;
+
+ /* Another easy out: There are no subtrees. */
+ if (children_with_mergeinfo->nelts < 2)
+ return SVN_NO_ERROR;
+
+ subtree_remaining_ranges = apr_array_make(scratch_pool, 1,
+ sizeof(svn_merge_range_t *));
+
+ /* Given the requested merge of SOURCE->rev1:rev2 might there be any
+ part of this range required for subtrees but not for the target? */
+ requested_ranges = svn_rangelist__initialize(MIN(source->loc1->rev,
+ source->loc2->rev),
+ MAX(source->loc1->rev,
+ source->loc2->rev),
+ TRUE, scratch_pool);
+ SVN_ERR(svn_rangelist_remove(&subtree_gap_ranges,
+ root_child->remaining_ranges,
+ requested_ranges, FALSE, scratch_pool));
+
+ /* Early out, nothing to operate on */
+ if (!subtree_gap_ranges->nelts)
+ return SVN_NO_ERROR;
+
+ /* Create a rangelist describing every range required across all subtrees. */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 1; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
+
+ svn_pool_clear(iterpool);
+
+ /* Issue #4269: Keep track of the longest common ancestor of all the
+ subtrees which require merges. This may be a child of
+ TARGET->ABSPATH, which will allow us to narrow the log request
+ below. */
+ if (child->remaining_ranges && child->remaining_ranges->nelts)
+ {
+ if (longest_common_subtree_ancestor)
+ longest_common_subtree_ancestor = svn_dirent_get_longest_ancestor(
+ longest_common_subtree_ancestor, child->abspath, scratch_pool);
+ else
+ longest_common_subtree_ancestor = child->abspath;
+ }
+
+ /* CHILD->REMAINING_RANGES will be NULL if child is absent. */
+ if (child->remaining_ranges && child->remaining_ranges->nelts)
+ SVN_ERR(svn_rangelist_merge2(subtree_remaining_ranges,
+ child->remaining_ranges,
+ scratch_pool, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ /* It's possible that none of the subtrees had any remaining ranges. */
+ if (!subtree_remaining_ranges->nelts)
+ return SVN_NO_ERROR;
+
+ /* Ok, *finally* we can answer what part(s) of SOURCE->rev1:rev2 are
+ required for the subtrees but not the target. */
+ SVN_ERR(svn_rangelist_intersect(&subtree_gap_ranges,
+ subtree_gap_ranges,
+ subtree_remaining_ranges, FALSE,
+ scratch_pool));
+
+ /* Another early out */
+ if (!subtree_gap_ranges->nelts)
+ return SVN_NO_ERROR;
+
+ /* One or more subtrees need some revisions that the target doesn't need.
+ Use log to determine if any of these revisions are inoperative. */
+ oldest_gap_rev = APR_ARRAY_IDX(subtree_gap_ranges, 0, svn_merge_range_t *);
+ youngest_gap_rev = APR_ARRAY_IDX(subtree_gap_ranges,
+ subtree_gap_ranges->nelts - 1, svn_merge_range_t *);
+
+ /* Set up the log baton. */
+ log_gap_baton.children_with_mergeinfo = children_with_mergeinfo;
+ log_gap_baton.source_fspath
+ = svn_client__pathrev_fspath(source->loc2, result_pool);
+ log_gap_baton.target = target;
+ log_gap_baton.merged_ranges = apr_array_make(scratch_pool, 0,
+ sizeof(svn_revnum_t *));
+ log_gap_baton.operative_ranges = apr_array_make(scratch_pool, 0,
+ sizeof(svn_revnum_t *));
+ log_gap_baton.pool = svn_pool_create(scratch_pool);
+
+ /* Find the longest common ancestor of all subtrees relative to
+ RA_SESSION's URL. */
+ if (longest_common_subtree_ancestor)
+ longest_common_subtree_ancestor =
+ svn_dirent_skip_ancestor(target->abspath,
+ longest_common_subtree_ancestor);
+ else
+ longest_common_subtree_ancestor = "";
+
+ /* Invoke the svn_log_entry_receiver_t receiver log_noop_revs() from
+ oldest to youngest. The receiver is optimized to add ranges to
+ log_gap_baton.merged_ranges and log_gap_baton.operative_ranges, but
+ requires that the revs arrive oldest to youngest -- see log_noop_revs()
+ and rangelist_merge_revision(). */
+ err = get_log(ra_session, longest_common_subtree_ancestor,
+ oldest_gap_rev->start + 1, youngest_gap_rev->end, TRUE,
+ log_noop_revs, &log_gap_baton, scratch_pool);
+
+ /* It's possible that the only subtrees with mergeinfo in TARGET don't have
+ any corresponding subtree in SOURCE between SOURCE->REV1 < SOURCE->REV2.
+ So it's also possible that we may ask for the logs of non-existent paths.
+ If we do, then assume that no subtree requires any ranges that are not
+ already required by the TARGET. */
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_FS_NOT_FOUND
+ && longest_common_subtree_ancestor[0] != '\0')
+ return svn_error_trace(err);
+
+ /* Asked about a non-existent subtree in SOURCE. */
+ svn_error_clear(err);
+ log_gap_baton.merged_ranges =
+ svn_rangelist__initialize(oldest_gap_rev->start,
+ youngest_gap_rev->end,
+ TRUE, scratch_pool);
+ }
+ else
+ {
+ inoperative_ranges = svn_rangelist__initialize(oldest_gap_rev->start,
+ youngest_gap_rev->end,
+ TRUE, scratch_pool);
+ SVN_ERR(svn_rangelist_remove(&(inoperative_ranges),
+ log_gap_baton.operative_ranges,
+ inoperative_ranges, FALSE, scratch_pool));
+ SVN_ERR(svn_rangelist_merge2(log_gap_baton.merged_ranges, inoperative_ranges,
+ scratch_pool, scratch_pool));
+ }
+
+ for (i = 1; i < children_with_mergeinfo->nelts; i++)
+ {
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
+
+ /* CHILD->REMAINING_RANGES will be NULL if child is absent. */
+ if (child->remaining_ranges && child->remaining_ranges->nelts)
+ {
+ /* Remove inoperative ranges from all children so we don't perform
+ inoperative editor drives. */
+ SVN_ERR(svn_rangelist_remove(&(child->remaining_ranges),
+ log_gap_baton.merged_ranges,
+ child->remaining_ranges,
+ FALSE, result_pool));
+ }
+ }
+
+ svn_pool_destroy(log_gap_baton.pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Perform a merge of changes in SOURCE to the working copy path
+ TARGET_ABSPATH. Both URLs in SOURCE, and TARGET_ABSPATH all represent
+ directories -- for the single file case, the caller should use
+ do_file_merge().
+
+ CHILDREN_WITH_MERGEINFO and MERGE_B describe the merge being performed
+ As this function is for a mergeinfo-aware merge, SOURCE->ancestral
+ should be TRUE, and SOURCE->loc1 must be a historical ancestor of
+ SOURCE->loc2, or vice-versa (see `MERGEINFO MERGE SOURCE NORMALIZATION'
+ for more requirements around SOURCE).
+
+ Mergeinfo changes will be recorded unless MERGE_B->dry_run is true.
+
+ If mergeinfo is being recorded, SQUELCH_MERGEINFO_NOTIFICATIONS is FALSE,
+ and MERGE_B->CTX->NOTIFY_FUNC2 is not NULL, then call
+ MERGE_B->CTX->NOTIFY_FUNC2 with MERGE_B->CTX->NOTIFY_BATON2 and a
+ svn_wc_notify_merge_record_info_begin notification before any mergeinfo
+ changes are made to describe the merge performed.
+
+ If mergeinfo is being recorded to describe this merge, and RESULT_CATALOG
+ is not NULL, then don't record the new mergeinfo on the WC, but instead
+ record it in RESULT_CATALOG, where the keys are absolute working copy
+ paths and the values are the new mergeinfos for each. Allocate additions
+ to RESULT_CATALOG in pool which RESULT_CATALOG was created in.
+
+ Handle DEPTH as documented for svn_client_merge5().
+
+ CONFLICT_REPORT is as documented for do_directory_merge().
+
+ Perform any temporary allocations in SCRATCH_POOL.
+
+ NOTE: This is a wrapper around drive_merge_report_editor() which
+ handles the complexities inherent to situations where a given
+ directory's children may have intersecting merges (because they
+ meet one or more of the criteria described in get_mergeinfo_paths()).
+*/
+static svn_error_t *
+do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog,
+ single_range_conflict_report_t **conflict_report,
+ const merge_source_t *source,
+ const char *target_abspath,
+ apr_array_header_t *children_with_mergeinfo,
+ const svn_diff_tree_processor_t *processor,
+ svn_depth_t depth,
+ svn_boolean_t squelch_mergeinfo_notifications,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* The range defining the mergeinfo we will record to describe the merge
+ (assuming we are recording mergeinfo
+
+ Note: This may be a subset of SOURCE->rev1:rev2 if
+ populate_remaining_ranges() determines that some part of
+ SOURCE->rev1:rev2 has already been wholly merged to TARGET_ABSPATH.
+ Also, the actual editor drive(s) may be a subset of RANGE, if
+ remove_noop_subtree_ranges() and/or fix_deleted_subtree_ranges()
+ further tweak things. */
+ svn_merge_range_t range;
+
+ svn_ra_session_t *ra_session;
+ svn_client__merge_path_t *target_merge_path;
+ svn_boolean_t is_rollback = (source->loc1->rev > source->loc2->rev);
+
+ SVN_ERR_ASSERT(source->ancestral);
+
+ /*** If we get here, we're dealing with related sources from the
+ same repository as the target -- merge tracking might be
+ happenin'! ***/
+
+ *conflict_report = NULL;
+
+ /* Point our RA_SESSION to the URL of our youngest merge source side. */
+ ra_session = is_rollback ? merge_b->ra_session1 : merge_b->ra_session2;
+
+ /* Fill NOTIFY_B->CHILDREN_WITH_MERGEINFO with child paths (const
+ svn_client__merge_path_t *) which might have intersecting merges
+ because they meet one or more of the criteria described in
+ get_mergeinfo_paths(). Here the paths are arranged in a depth
+ first order. */
+ SVN_ERR(get_mergeinfo_paths(children_with_mergeinfo,
+ merge_b->target, depth,
+ merge_b->dry_run, merge_b->same_repos,
+ merge_b->ctx, scratch_pool, scratch_pool));
+
+ /* The first item from the NOTIFY_B->CHILDREN_WITH_MERGEINFO is always
+ the target thanks to depth-first ordering. */
+ target_merge_path = APR_ARRAY_IDX(children_with_mergeinfo, 0,
+ svn_client__merge_path_t *);
+
+ /* If we are honoring mergeinfo, then for each item in
+ NOTIFY_B->CHILDREN_WITH_MERGEINFO, we need to calculate what needs to be
+ merged, and then merge it. Otherwise, we just merge what we were asked
+ to merge across the whole tree. */
+ SVN_ERR(populate_remaining_ranges(children_with_mergeinfo,
+ source, ra_session,
+ merge_b, scratch_pool, scratch_pool));
+
+ /* Always start with a range which describes the most inclusive merge
+ possible, i.e. SOURCE->rev1:rev2. */
+ range.start = source->loc1->rev;
+ range.end = source->loc2->rev;
+ range.inheritable = TRUE;
+
+ if (!merge_b->reintegrate_merge)
+ {
+ svn_revnum_t new_range_start, start_rev;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* The merge target TARGET_ABSPATH and/or its subtrees may not need all
+ of SOURCE->rev1:rev2 applied. So examine
+ NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the oldest starting
+ revision that actually needs to be merged (for reverse merges this is
+ the youngest starting revision).
+
+ We'll do this twice, right now for the start of the mergeinfo we will
+ ultimately record to describe this merge and then later for the
+ start of the actual editor drive. */
+ new_range_start = get_most_inclusive_rev(
+ children_with_mergeinfo, is_rollback, TRUE);
+ if (SVN_IS_VALID_REVNUM(new_range_start))
+ range.start = new_range_start;
+
+ /* Remove inoperative ranges from any subtrees' remaining_ranges
+ to spare the expense of noop editor drives. */
+ if (!is_rollback)
+ SVN_ERR(remove_noop_subtree_ranges(source, merge_b->target,
+ ra_session,
+ children_with_mergeinfo,
+ scratch_pool, iterpool));
+
+ /* Adjust subtrees' remaining_ranges to deal with issue #3067:
+ * "subtrees that don't exist at the start or end of a merge range
+ * shouldn't break the merge". */
+ SVN_ERR(fix_deleted_subtree_ranges(source, merge_b->target,
+ ra_session,
+ children_with_mergeinfo,
+ merge_b->ctx, scratch_pool, iterpool));
+
+ /* remove_noop_subtree_ranges() and/or fix_deleted_subtree_range()
+ may have further refined the starting revision for our editor
+ drive. */
+ start_rev =
+ get_most_inclusive_rev(children_with_mergeinfo,
+ is_rollback, TRUE);
+
+ /* Is there anything to merge? */
+ if (SVN_IS_VALID_REVNUM(start_rev))
+ {
+ /* Now examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the oldest
+ ending revision that actually needs to be merged (for reverse
+ merges this is the youngest ending revision). */
+ svn_revnum_t end_rev =
+ get_most_inclusive_rev(children_with_mergeinfo,
+ is_rollback, FALSE);
+
+ /* While END_REV is valid, do the following:
+
+ 1. Tweak each NOTIFY_B->CHILDREN_WITH_MERGEINFO element so that
+ the element's remaining_ranges member has as its first element
+ a range that ends with end_rev.
+
+ 2. Starting with start_rev, call drive_merge_report_editor()
+ on MERGE_B->target->abspath for start_rev:end_rev.
+
+ 3. Remove the first element from each
+ NOTIFY_B->CHILDREN_WITH_MERGEINFO element's remaining_ranges
+ member.
+
+ 4. Again examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the most
+ inclusive starting revision that actually needs to be merged and
+ update start_rev. This prevents us from needlessly contacting the
+ repository and doing a diff where we describe the entire target
+ tree as *not* needing any of the requested range. This can happen
+ whenever we have mergeinfo with gaps in it for the merge source.
+
+ 5. Again examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the most
+ inclusive ending revision that actually needs to be merged and
+ update end_rev.
+
+ 6. Lather, rinse, repeat.
+ */
+
+ while (end_rev != SVN_INVALID_REVNUM)
+ {
+ merge_source_t *real_source;
+ svn_merge_range_t *first_target_range
+ = (target_merge_path->remaining_ranges->nelts == 0 ? NULL
+ : APR_ARRAY_IDX(target_merge_path->remaining_ranges, 0,
+ svn_merge_range_t *));
+
+ /* Issue #3324: Stop editor abuse! Don't call
+ drive_merge_report_editor() in such a way that we request an
+ editor with svn_client__get_diff_editor() for some rev X,
+ then call svn_ra_do_diff3() for some revision Y, and then
+ call reporter->set_path(PATH=="") to set the root revision
+ for the editor drive to revision Z where
+ (X != Z && X < Z < Y). This is bogus because the server will
+ send us the diff between X:Y but the client is expecting the
+ diff between Y:Z. See issue #3324 for full details on the
+ problems this can cause. */
+ if (first_target_range
+ && start_rev != first_target_range->start)
+ {
+ if (is_rollback)
+ {
+ if (end_rev < first_target_range->start)
+ end_rev = first_target_range->start;
+ }
+ else
+ {
+ if (end_rev > first_target_range->start)
+ end_rev = first_target_range->start;
+ }
+ }
+
+ svn_pool_clear(iterpool);
+
+ slice_remaining_ranges(children_with_mergeinfo,
+ is_rollback, end_rev, scratch_pool);
+
+ /* Reset variables that must be reset for every drive */
+ merge_b->notify_begin.last_abspath = NULL;
+
+ real_source = subrange_source(source, start_rev, end_rev, iterpool);
+ SVN_ERR(drive_merge_report_editor(
+ merge_b->target->abspath,
+ real_source,
+ children_with_mergeinfo,
+ processor,
+ depth,
+ merge_b,
+ iterpool));
+
+ /* If any paths picked up explicit mergeinfo as a result of
+ the merge we need to make sure any mergeinfo those paths
+ inherited is recorded and then add these paths to
+ NOTIFY_B->CHILDREN_WITH_MERGEINFO.*/
+ SVN_ERR(process_children_with_new_mergeinfo(
+ merge_b, children_with_mergeinfo,
+ scratch_pool));
+
+ /* If any subtrees had their explicit mergeinfo deleted as a
+ result of the merge then remove these paths from
+ NOTIFY_B->CHILDREN_WITH_MERGEINFO since there is no need
+ to consider these subtrees for subsequent editor drives
+ nor do we want to record mergeinfo on them describing
+ the merge itself. */
+ remove_children_with_deleted_mergeinfo(
+ merge_b, children_with_mergeinfo);
+
+ /* Prepare for the next iteration (if any). */
+ remove_first_range_from_remaining_ranges(
+ end_rev, children_with_mergeinfo, scratch_pool);
+
+ /* If we raised any conflicts, break out and report how much
+ we have merged. */
+ if (is_path_conflicted_by_merge(merge_b))
+ {
+ merge_source_t *remaining_range = NULL;
+
+ if (real_source->loc2->rev != source->loc2->rev)
+ remaining_range = subrange_source(source,
+ real_source->loc2->rev,
+ source->loc2->rev,
+ scratch_pool);
+ *conflict_report = single_range_conflict_report_create(
+ real_source, remaining_range,
+ result_pool);
+
+ range.end = end_rev;
+ break;
+ }
+
+ start_rev =
+ get_most_inclusive_rev(children_with_mergeinfo,
+ is_rollback, TRUE);
+ end_rev =
+ get_most_inclusive_rev(children_with_mergeinfo,
+ is_rollback, FALSE);
+ }
+ }
+ svn_pool_destroy(iterpool);
+ }
+ else
+ {
+ if (!merge_b->record_only)
+ {
+ /* Reset cur_ancestor_abspath to null so that subsequent cherry
+ picked revision ranges will be notified upon subsequent
+ operative merge. */
+ merge_b->notify_begin.last_abspath = NULL;
+
+ SVN_ERR(drive_merge_report_editor(merge_b->target->abspath,
+ source,
+ NULL,
+ processor,
+ depth,
+ merge_b,
+ scratch_pool));
+ }
+ }
+
+ /* Record mergeinfo where appropriate.*/
+ if (RECORD_MERGEINFO(merge_b))
+ {
+ const svn_client__pathrev_t *primary_src
+ = is_rollback ? source->loc1 : source->loc2;
+ const char *mergeinfo_path
+ = svn_client__pathrev_fspath(primary_src, scratch_pool);
+
+ SVN_ERR(record_mergeinfo_for_dir_merge(result_catalog,
+ &range,
+ mergeinfo_path,
+ children_with_mergeinfo,
+ depth,
+ squelch_mergeinfo_notifications,
+ merge_b,
+ scratch_pool));
+
+ /* If a path has an immediate parent with non-inheritable mergeinfo at
+ this point, then it meets criteria 3 or 5 described in
+ get_mergeinfo_paths' doc string. For paths which exist prior to a
+ merge explicit mergeinfo has already been set. But for paths added
+ during the merge this is not the case. The path might have explicit
+ mergeinfo from the merge source, but no mergeinfo yet exists
+ describing *this* merge. So the added path has either incomplete
+ explicit mergeinfo or inherits incomplete mergeinfo from its
+ immediate parent (if any, the parent might have only non-inheritable
+ ranges in which case the path simply inherits empty mergeinfo).
+
+ So here we look at the root path of each subtree added during the
+ merge and set explicit mergeinfo on it if it meets the aforementioned
+ conditions. */
+ if (range.start < range.end) /* Nothing to record on added subtrees
+ resulting from reverse merges. */
+ {
+ SVN_ERR(record_mergeinfo_for_added_subtrees(
+ &range, mergeinfo_path, depth,
+ squelch_mergeinfo_notifications,
+ merge_b->added_abspaths, merge_b, scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for do_merge() when the merge target is a directory.
+ *
+ * If any conflict is raised during the merge, set *CONFLICTED_RANGE to
+ * the revision sub-range that raised the conflict. In this case, the
+ * merge will have ended at revision CONFLICTED_RANGE and mergeinfo will
+ * have been recorded for all revision sub-ranges up to and including
+ * CONFLICTED_RANGE. Otherwise, set *CONFLICTED_RANGE to NULL.
+ */
+static svn_error_t *
+do_directory_merge(svn_mergeinfo_catalog_t result_catalog,
+ single_range_conflict_report_t **conflict_report,
+ const merge_source_t *source,
+ const char *target_abspath,
+ const svn_diff_tree_processor_t *processor,
+ svn_depth_t depth,
+ svn_boolean_t squelch_mergeinfo_notifications,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *children_with_mergeinfo;
+
+ /* Initialize CHILDREN_WITH_MERGEINFO. See the comment
+ 'THE CHILDREN_WITH_MERGEINFO ARRAY' at the start of this file. */
+ children_with_mergeinfo =
+ apr_array_make(scratch_pool, 16, sizeof(svn_client__merge_path_t *));
+
+ /* And make it read-only accessible from the baton */
+ merge_b->notify_begin.nodes_with_mergeinfo = children_with_mergeinfo;
+
+ /* If we are not honoring mergeinfo we can skip right to the
+ business of merging changes! */
+ if (HONOR_MERGEINFO(merge_b))
+ SVN_ERR(do_mergeinfo_aware_dir_merge(result_catalog, conflict_report,
+ source, target_abspath,
+ children_with_mergeinfo,
+ processor, depth,
+ squelch_mergeinfo_notifications,
+ merge_b, result_pool, scratch_pool));
+ else
+ SVN_ERR(do_mergeinfo_unaware_dir_merge(conflict_report,
+ source, target_abspath,
+ children_with_mergeinfo,
+ processor, depth,
+ merge_b, result_pool, scratch_pool));
+
+ merge_b->notify_begin.nodes_with_mergeinfo = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/** Ensure that *RA_SESSION is opened to URL, either by reusing
+ * *RA_SESSION if it is non-null and already opened to URL's
+ * repository, or by allocating a new *RA_SESSION in POOL.
+ * (RA_SESSION itself cannot be null, of course.)
+ *
+ * CTX is used as for svn_client_open_ra_session().
+ */
+static svn_error_t *
+ensure_ra_session_url(svn_ra_session_t **ra_session,
+ const char *url,
+ const char *wri_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ if (*ra_session)
+ {
+ err = svn_ra_reparent(*ra_session, url, pool);
+ }
+
+ /* SVN_ERR_RA_ILLEGAL_URL is raised when url doesn't point to the same
+ repository as ra_session. */
+ if (! *ra_session || (err && err->apr_err == SVN_ERR_RA_ILLEGAL_URL))
+ {
+ svn_error_clear(err);
+ err = svn_client_open_ra_session2(ra_session, url, wri_abspath,
+ ctx, pool, pool);
+ }
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* Drive a merge of MERGE_SOURCES into working copy node TARGET
+ and possibly record mergeinfo describing the merge -- see
+ RECORD_MERGEINFO().
+
+ If MODIFIED_SUBTREES is not NULL and all the MERGE_SOURCES are 'ancestral'
+ or REINTEGRATE_MERGE is true, then replace *MODIFIED_SUBTREES with a new
+ hash containing all the paths that *MODIFIED_SUBTREES contained before,
+ and also every path modified, skipped, added, or tree-conflicted
+ by the merge. Keys and values of the hash are both (const char *)
+ absolute paths. The contents of the hash are allocated in RESULT_POOL.
+
+ If the merge raises any conflicts while merging a revision range, return
+ early and set *CONFLICT_REPORT to describe the details. (In this case,
+ notify that the merge is complete if and only if this was the last
+ revision range of the merge.) If there are no conflicts, set
+ *CONFLICT_REPORT to NULL. A revision range here can be one specified
+ in MERGE_SOURCES or an internally generated sub-range of one of those
+ when merge tracking is in use.
+
+ For every (const merge_source_t *) merge source in MERGE_SOURCES, if
+ SOURCE->ANCESTRAL is set, then the "left" and "right" side are
+ ancestrally related. (See 'MERGEINFO MERGE SOURCE NORMALIZATION'
+ for more on what that means and how it matters.)
+
+ If SOURCES_RELATED is set, the "left" and "right" sides of the
+ merge source are historically related (ancestors, uncles, second
+ cousins thrice removed, etc...). (This is passed through to
+ do_file_merge() to simulate the history checks that the repository
+ logic does in the directory case.)
+
+ SAME_REPOS is TRUE iff the merge sources live in the same
+ repository as the one from which the target working copy has been
+ checked out.
+
+ If mergeinfo is being recorded, SQUELCH_MERGEINFO_NOTIFICATIONS is FALSE,
+ and CTX->NOTIFY_FUNC2 is not NULL, then call CTX->NOTIFY_FUNC2 with
+ CTX->NOTIFY_BATON2 and a svn_wc_notify_merge_record_info_begin
+ notification before any mergeinfo changes are made to describe the merge
+ performed.
+
+ If mergeinfo is being recorded to describe this merge, and RESULT_CATALOG
+ is not NULL, then don't record the new mergeinfo on the WC, but instead
+ record it in RESULT_CATALOG, where the keys are absolute working copy
+ paths and the values are the new mergeinfos for each. Allocate additions
+ to RESULT_CATALOG in pool which RESULT_CATALOG was created in.
+
+ FORCE_DELETE, DRY_RUN, RECORD_ONLY, DEPTH, MERGE_OPTIONS,
+ and CTX are as described in the docstring for svn_client_merge_peg3().
+
+ If IGNORE_MERGEINFO is true, disable merge tracking, by treating the two
+ sources as unrelated even if they actually have a common ancestor. See
+ the macro HONOR_MERGEINFO().
+
+ If DIFF_IGNORE_ANCESTRY is true, diff the 'left' and 'right' versions
+ of a node (if they are the same kind) as if they were related, even if
+ they are not related. Otherwise, diff unrelated items as a deletion
+ of one thing and the addition of another.
+
+ If not NULL, RECORD_ONLY_PATHS is a hash of (const char *) paths mapped
+ to the same. If RECORD_ONLY is true and RECORD_ONLY_PATHS is not NULL,
+ then record mergeinfo describing the merge only on subtrees which contain
+ items from RECORD_ONLY_PATHS. If RECORD_ONLY is true and RECORD_ONLY_PATHS
+ is NULL, then record mergeinfo on every subtree with mergeinfo in
+ TARGET.
+
+ REINTEGRATE_MERGE is TRUE if this is a reintegrate merge.
+
+ *USE_SLEEP will be set TRUE if a sleep is required to ensure timestamp
+ integrity, *USE_SLEEP will be unchanged if no sleep is required.
+
+ SCRATCH_POOL is used for all temporary allocations.
+*/
+static svn_error_t *
+do_merge(apr_hash_t **modified_subtrees,
+ svn_mergeinfo_catalog_t result_catalog,
+ conflict_report_t **conflict_report,
+ svn_boolean_t *use_sleep,
+ const apr_array_header_t *merge_sources,
+ const merge_target_t *target,
+ svn_ra_session_t *src_session,
+ svn_boolean_t sources_related,
+ svn_boolean_t same_repos,
+ svn_boolean_t ignore_mergeinfo,
+ svn_boolean_t diff_ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t dry_run,
+ svn_boolean_t record_only,
+ apr_hash_t *record_only_paths,
+ svn_boolean_t reintegrate_merge,
+ svn_boolean_t squelch_mergeinfo_notifications,
+ svn_depth_t depth,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ merge_cmd_baton_t merge_cmd_baton = { 0 };
+ svn_config_t *cfg;
+ const char *diff3_cmd;
+ int i;
+ svn_boolean_t checked_mergeinfo_capability = FALSE;
+ svn_ra_session_t *ra_session1 = NULL, *ra_session2 = NULL;
+ const char *old_src_session_url = NULL;
+ apr_pool_t *iterpool;
+ const svn_diff_tree_processor_t *processor;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(target->abspath));
+
+ *conflict_report = NULL;
+
+ /* Check from some special conditions when in record-only mode
+ (which is a merge-tracking thing). */
+ if (record_only)
+ {
+ svn_boolean_t sources_ancestral = TRUE;
+ int j;
+
+ /* Find out whether all of the sources are 'ancestral'. */
+ for (j = 0; j < merge_sources->nelts; j++)
+ if (! APR_ARRAY_IDX(merge_sources, j, merge_source_t *)->ancestral)
+ {
+ sources_ancestral = FALSE;
+ break;
+ }
+
+ /* We can't do a record-only merge if the sources aren't related. */
+ if (! sources_ancestral)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Use of two URLs is not compatible with "
+ "mergeinfo modification"));
+
+ /* We can't do a record-only merge if the sources aren't from
+ the same repository as the target. */
+ if (! same_repos)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Merge from foreign repository is not "
+ "compatible with mergeinfo modification"));
+
+ /* If this is a dry-run record-only merge, there's nothing to do. */
+ if (dry_run)
+ return SVN_NO_ERROR;
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* Ensure a known depth. */
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_infinity;
+
+ /* Set up the diff3 command, so various callers don't have to. */
+ cfg = ctx->config
+ ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
+
+ if (diff3_cmd != NULL)
+ SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
+
+ /* Build the merge context baton (or at least the parts of it that
+ don't need to be reset for each merge source). */
+ merge_cmd_baton.force_delete = force_delete;
+ merge_cmd_baton.dry_run = dry_run;
+ merge_cmd_baton.record_only = record_only;
+ merge_cmd_baton.ignore_mergeinfo = ignore_mergeinfo;
+ merge_cmd_baton.diff_ignore_ancestry = diff_ignore_ancestry;
+ merge_cmd_baton.same_repos = same_repos;
+ merge_cmd_baton.mergeinfo_capable = FALSE;
+ merge_cmd_baton.ctx = ctx;
+ merge_cmd_baton.reintegrate_merge = reintegrate_merge;
+ merge_cmd_baton.target = target;
+ merge_cmd_baton.pool = iterpool;
+ merge_cmd_baton.merge_options = merge_options;
+ merge_cmd_baton.diff3_cmd = diff3_cmd;
+ merge_cmd_baton.use_sleep = use_sleep;
+
+ /* Do we already know the specific subtrees with mergeinfo we want
+ to record-only mergeinfo on? */
+ if (record_only && record_only_paths)
+ merge_cmd_baton.merged_abspaths = record_only_paths;
+ else
+ merge_cmd_baton.merged_abspaths = apr_hash_make(result_pool);
+
+ merge_cmd_baton.skipped_abspaths = apr_hash_make(result_pool);
+ merge_cmd_baton.added_abspaths = apr_hash_make(result_pool);
+ merge_cmd_baton.tree_conflicted_abspaths = apr_hash_make(result_pool);
+
+ {
+ svn_diff_tree_processor_t *merge_processor;
+
+ merge_processor = svn_diff__tree_processor_create(&merge_cmd_baton,
+ scratch_pool);
+
+ merge_processor->dir_opened = merge_dir_opened;
+ merge_processor->dir_changed = merge_dir_changed;
+ merge_processor->dir_added = merge_dir_added;
+ merge_processor->dir_deleted = merge_dir_deleted;
+ merge_processor->dir_closed = merge_dir_closed;
+
+ merge_processor->file_opened = merge_file_opened;
+ merge_processor->file_changed = merge_file_changed;
+ merge_processor->file_added = merge_file_added;
+ merge_processor->file_deleted = merge_file_deleted;
+ /* Not interested in file_closed() */
+
+ merge_processor->node_absent = merge_node_absent;
+
+ processor = merge_processor;
+ }
+
+ if (src_session)
+ {
+ SVN_ERR(svn_ra_get_session_url(src_session, &old_src_session_url,
+ scratch_pool));
+ ra_session1 = src_session;
+ }
+
+ for (i = 0; i < merge_sources->nelts; i++)
+ {
+ svn_node_kind_t src1_kind;
+ merge_source_t *source =
+ APR_ARRAY_IDX(merge_sources, i, merge_source_t *);
+ single_range_conflict_report_t *conflicted_range_report;
+
+ svn_pool_clear(iterpool);
+
+ /* Sanity check: if our left- and right-side merge sources are
+ the same, there's nothing to here. */
+ if ((strcmp(source->loc1->url, source->loc2->url) == 0)
+ && (source->loc1->rev == source->loc2->rev))
+ continue;
+
+ /* Establish RA sessions to our URLs, reuse where possible. */
+ SVN_ERR(ensure_ra_session_url(&ra_session1, source->loc1->url,
+ target->abspath, ctx, scratch_pool));
+ SVN_ERR(ensure_ra_session_url(&ra_session2, source->loc2->url,
+ target->abspath, ctx, scratch_pool));
+
+ /* Populate the portions of the merge context baton that need to
+ be reset for each merge source iteration. */
+ merge_cmd_baton.merge_source = *source;
+ merge_cmd_baton.implicit_src_gap = NULL;
+ merge_cmd_baton.conflicted_paths = NULL;
+ merge_cmd_baton.paths_with_new_mergeinfo = NULL;
+ merge_cmd_baton.paths_with_deleted_mergeinfo = NULL;
+ merge_cmd_baton.ra_session1 = ra_session1;
+ merge_cmd_baton.ra_session2 = ra_session2;
+
+ merge_cmd_baton.notify_begin.last_abspath = NULL;
+
+ /* Populate the portions of the merge context baton that require
+ an RA session to set, but shouldn't be reset for each iteration. */
+ if (! checked_mergeinfo_capability)
+ {
+ SVN_ERR(svn_ra_has_capability(ra_session1,
+ &merge_cmd_baton.mergeinfo_capable,
+ SVN_RA_CAPABILITY_MERGEINFO,
+ iterpool));
+ checked_mergeinfo_capability = TRUE;
+ }
+
+ SVN_ERR(svn_ra_check_path(ra_session1, "", source->loc1->rev,
+ &src1_kind, iterpool));
+
+ /* Run the merge; if there are conflicts, allow the callback to
+ * resolve them, and if it resolves all of them, then run the
+ * merge again with the remaining revision range, until it is all
+ * done. */
+ do
+ {
+ /* Merge as far as possible without resolving any conflicts */
+ if (src1_kind != svn_node_dir)
+ {
+ SVN_ERR(do_file_merge(result_catalog, &conflicted_range_report,
+ source, target->abspath,
+ processor,
+ sources_related,
+ squelch_mergeinfo_notifications,
+ &merge_cmd_baton, iterpool, iterpool));
+ }
+ else /* Directory */
+ {
+ SVN_ERR(do_directory_merge(result_catalog, &conflicted_range_report,
+ source, target->abspath,
+ processor,
+ depth, squelch_mergeinfo_notifications,
+ &merge_cmd_baton, iterpool, iterpool));
+ }
+
+ /* Give the conflict resolver callback the opportunity to
+ * resolve any conflicts that were raised. If it resolves all
+ * of them, go around again to merge the next sub-range (if any). */
+ if (conflicted_range_report && ctx->conflict_func2 && ! dry_run)
+ {
+ svn_boolean_t conflicts_remain;
+
+ SVN_ERR(svn_client__resolve_conflicts(
+ &conflicts_remain, merge_cmd_baton.conflicted_paths,
+ ctx, iterpool));
+ if (conflicts_remain)
+ break;
+
+ merge_cmd_baton.conflicted_paths = NULL;
+ /* Caution: this source is in iterpool */
+ source = conflicted_range_report->remaining_source;
+ conflicted_range_report = NULL;
+ }
+ else
+ break;
+ }
+ while (source);
+
+ /* The final mergeinfo on TARGET_WCPATH may itself elide. */
+ if (! dry_run)
+ SVN_ERR(svn_client__elide_mergeinfo(target->abspath, NULL,
+ ctx, iterpool));
+
+ /* If conflicts occurred while merging any but the very last
+ * range of a multi-pass merge, we raise an error that aborts
+ * the merge. The user will be asked to resolve conflicts
+ * before merging subsequent revision ranges. */
+ if (conflicted_range_report)
+ {
+ *conflict_report = conflict_report_create(
+ target->abspath, conflicted_range_report->conflicted_range,
+ (i == merge_sources->nelts - 1
+ && ! conflicted_range_report->remaining_source),
+ result_pool);
+ break;
+ }
+ }
+
+ if (! *conflict_report || (*conflict_report)->was_last_range)
+ {
+ /* Let everyone know we're finished here. */
+ notify_merge_completed(target->abspath, ctx, iterpool);
+ }
+
+ /* Does the caller want to know what the merge has done? */
+ if (modified_subtrees)
+ {
+ *modified_subtrees =
+ apr_hash_overlay(result_pool, *modified_subtrees,
+ merge_cmd_baton.merged_abspaths);
+ *modified_subtrees =
+ apr_hash_overlay(result_pool, *modified_subtrees,
+ merge_cmd_baton.added_abspaths);
+ *modified_subtrees =
+ apr_hash_overlay(result_pool, *modified_subtrees,
+ merge_cmd_baton.skipped_abspaths);
+ *modified_subtrees =
+ apr_hash_overlay(result_pool, *modified_subtrees,
+ merge_cmd_baton.tree_conflicted_abspaths);
+ }
+
+ if (src_session)
+ SVN_ERR(svn_ra_reparent(src_session, old_src_session_url, iterpool));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Perform a two-URL merge between URLs which are related, but neither
+ is a direct ancestor of the other. This first does a real two-URL
+ merge (unless this is record-only), followed by record-only merges
+ to represent the changed mergeinfo.
+
+ Set *CONFLICT_REPORT to indicate if there were any conflicts, as in
+ do_merge().
+
+ The diff to be merged is between SOURCE->loc1 (in URL1_RA_SESSION1)
+ and SOURCE->loc2 (in URL2_RA_SESSION2); YCA is their youngest
+ common ancestor.
+
+ SAME_REPOS must be true if and only if the source URLs are in the same
+ repository as the target working copy.
+
+ DIFF_IGNORE_ANCESTRY is as in do_merge().
+
+ Other arguments are as in all of the public merge APIs.
+
+ *USE_SLEEP will be set TRUE if a sleep is required to ensure timestamp
+ integrity, *USE_SLEEP will be unchanged if no sleep is required.
+
+ SCRATCH_POOL is used for all temporary allocations.
+ */
+static svn_error_t *
+merge_cousins_and_supplement_mergeinfo(conflict_report_t **conflict_report,
+ svn_boolean_t *use_sleep,
+ const merge_target_t *target,
+ svn_ra_session_t *URL1_ra_session,
+ svn_ra_session_t *URL2_ra_session,
+ const merge_source_t *source,
+ const svn_client__pathrev_t *yca,
+ svn_boolean_t same_repos,
+ svn_depth_t depth,
+ svn_boolean_t diff_ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *remove_sources, *add_sources;
+ apr_hash_t *modified_subtrees = NULL;
+
+ /* Sure we could use SCRATCH_POOL throughout this function, but since this
+ is a wrapper around three separate merges we'll create a subpool we can
+ clear between each of the three. If the merge target has a lot of
+ subtree mergeinfo, then this will help keep memory use in check. */
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+
+ assert(session_url_is(URL1_ra_session, source->loc1->url, scratch_pool));
+ assert(session_url_is(URL2_ra_session, source->loc2->url, scratch_pool));
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(target->abspath));
+ SVN_ERR_ASSERT(! source->ancestral);
+
+ SVN_ERR(normalize_merge_sources_internal(
+ &remove_sources, source->loc1,
+ svn_rangelist__initialize(source->loc1->rev, yca->rev, TRUE,
+ scratch_pool),
+ URL1_ra_session, ctx, scratch_pool, subpool));
+
+ SVN_ERR(normalize_merge_sources_internal(
+ &add_sources, source->loc2,
+ svn_rangelist__initialize(yca->rev, source->loc2->rev, TRUE,
+ scratch_pool),
+ URL2_ra_session, ctx, scratch_pool, subpool));
+
+ *conflict_report = NULL;
+
+ /* If this isn't a record-only merge, we'll first do a stupid
+ point-to-point merge... */
+ if (! record_only)
+ {
+ apr_array_header_t *faux_sources =
+ apr_array_make(scratch_pool, 1, sizeof(merge_source_t *));
+
+ modified_subtrees = apr_hash_make(scratch_pool);
+ APR_ARRAY_PUSH(faux_sources, const merge_source_t *) = source;
+ SVN_ERR(do_merge(&modified_subtrees, NULL, conflict_report, use_sleep,
+ faux_sources, target,
+ URL1_ra_session, TRUE, same_repos,
+ FALSE /*ignore_mergeinfo*/, diff_ignore_ancestry,
+ force_delete, dry_run, FALSE, NULL, TRUE,
+ FALSE, depth, merge_options, ctx,
+ scratch_pool, subpool));
+ if (*conflict_report)
+ {
+ *conflict_report = conflict_report_dup(*conflict_report, result_pool);
+ if (! (*conflict_report)->was_last_range)
+ return SVN_NO_ERROR;
+ }
+ }
+ else if (! same_repos)
+ {
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Merge from foreign repository is not "
+ "compatible with mergeinfo modification"));
+ }
+
+ /* ... and now, if we're doing the mergeinfo thang, we execute a
+ pair of record-only merges using the real sources we've
+ calculated.
+
+ Issue #3648: We don't actually perform these two record-only merges
+ on the WC at first, but rather see what each would do and store that
+ in two mergeinfo catalogs. We then merge the catalogs together and
+ then record the result in the WC. This prevents the second record
+ only merge from removing legitimate mergeinfo history, from the same
+ source, that was made in prior merges. */
+ if (same_repos && !dry_run)
+ {
+ svn_mergeinfo_catalog_t add_result_catalog =
+ apr_hash_make(scratch_pool);
+ svn_mergeinfo_catalog_t remove_result_catalog =
+ apr_hash_make(scratch_pool);
+
+ notify_mergeinfo_recording(target->abspath, NULL, ctx, scratch_pool);
+ svn_pool_clear(subpool);
+ SVN_ERR(do_merge(NULL, add_result_catalog, conflict_report, use_sleep,
+ add_sources, target,
+ URL1_ra_session, TRUE, same_repos,
+ FALSE /*ignore_mergeinfo*/, diff_ignore_ancestry,
+ force_delete, dry_run, TRUE,
+ modified_subtrees, TRUE,
+ TRUE, depth, merge_options, ctx,
+ scratch_pool, subpool));
+ if (*conflict_report)
+ {
+ *conflict_report = conflict_report_dup(*conflict_report, result_pool);
+ if (! (*conflict_report)->was_last_range)
+ return SVN_NO_ERROR;
+ }
+ svn_pool_clear(subpool);
+ SVN_ERR(do_merge(NULL, remove_result_catalog, conflict_report, use_sleep,
+ remove_sources, target,
+ URL1_ra_session, TRUE, same_repos,
+ FALSE /*ignore_mergeinfo*/, diff_ignore_ancestry,
+ force_delete, dry_run, TRUE,
+ modified_subtrees, TRUE,
+ TRUE, depth, merge_options, ctx,
+ scratch_pool, subpool));
+ if (*conflict_report)
+ {
+ *conflict_report = conflict_report_dup(*conflict_report, result_pool);
+ if (! (*conflict_report)->was_last_range)
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(svn_mergeinfo_catalog_merge(add_result_catalog,
+ remove_result_catalog,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_client__record_wc_mergeinfo_catalog(add_result_catalog,
+ ctx, scratch_pool));
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* Perform checks to determine whether the working copy at TARGET_ABSPATH
+ * can safely be used as a merge target. Checks are performed according to
+ * the ALLOW_MIXED_REV, ALLOW_LOCAL_MODS, and ALLOW_SWITCHED_SUBTREES
+ * parameters. If any checks fail, raise SVN_ERR_CLIENT_NOT_READY_TO_MERGE.
+ *
+ * E.g. if all the ALLOW_* parameters are FALSE, TARGET_ABSPATH must
+ * be a single-revision, pristine, unswitched working copy.
+ * In other words, it must reflect a subtree of the repository as found
+ * at single revision -- although sparse checkouts are permitted. */
+static svn_error_t *
+ensure_wc_is_suitable_merge_target(const char *target_abspath,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t allow_mixed_rev,
+ svn_boolean_t allow_local_mods,
+ svn_boolean_t allow_switched_subtrees,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t target_kind;
+
+ /* Check the target exists. */
+ SVN_ERR(svn_io_check_path(target_abspath, &target_kind, scratch_pool));
+ if (target_kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Path '%s' does not exist"),
+ svn_dirent_local_style(target_abspath,
+ scratch_pool));
+ SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, target_abspath,
+ FALSE, FALSE, scratch_pool));
+ if (target_kind != svn_node_dir && target_kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Merge target '%s' does not exist in the "
+ "working copy"), target_abspath);
+
+ /* Perform the mixed-revision check first because it's the cheapest one. */
+ if (! allow_mixed_rev)
+ {
+ svn_revnum_t min_rev;
+ svn_revnum_t max_rev;
+
+ SVN_ERR(svn_client_min_max_revisions(&min_rev, &max_rev, target_abspath,
+ FALSE, ctx, scratch_pool));
+
+ if (!(SVN_IS_VALID_REVNUM(min_rev) && SVN_IS_VALID_REVNUM(max_rev)))
+ {
+ svn_boolean_t is_added;
+
+ /* Allow merge into added nodes. */
+ SVN_ERR(svn_wc__node_is_added(&is_added, ctx->wc_ctx, target_abspath,
+ scratch_pool));
+ if (is_added)
+ return SVN_NO_ERROR;
+ else
+ return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+ _("Cannot determine revision of working "
+ "copy"));
+ }
+
+ if (min_rev != max_rev)
+ return svn_error_createf(SVN_ERR_CLIENT_MERGE_UPDATE_REQUIRED, NULL,
+ _("Cannot merge into mixed-revision working "
+ "copy [%ld:%ld]; try updating first"),
+ min_rev, max_rev);
+ }
+
+ /* Next, check for switched subtrees. */
+ if (! allow_switched_subtrees)
+ {
+ svn_boolean_t is_switched;
+
+ SVN_ERR(svn_wc__has_switched_subtrees(&is_switched, ctx->wc_ctx,
+ target_abspath, NULL,
+ scratch_pool));
+ if (is_switched)
+ return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+ _("Cannot merge into a working copy "
+ "with a switched subtree"));
+ }
+
+ /* This is the most expensive check, so it is performed last.*/
+ if (! allow_local_mods)
+ {
+ svn_boolean_t is_modified;
+
+ SVN_ERR(svn_wc__has_local_mods(&is_modified, ctx->wc_ctx,
+ target_abspath,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ scratch_pool));
+ if (is_modified)
+ return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+ _("Cannot merge into a working copy "
+ "that has local modifications"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Throw an error if PATH_OR_URL is a path and REVISION isn't a repository
+ * revision. */
+static svn_error_t *
+ensure_wc_path_has_repo_revision(const char *path_or_url,
+ const svn_opt_revision_t *revision,
+ apr_pool_t *scratch_pool)
+{
+ if (revision->kind != svn_opt_revision_number
+ && revision->kind != svn_opt_revision_date
+ && revision->kind != svn_opt_revision_head
+ && ! svn_path_is_url(path_or_url))
+ return svn_error_createf(
+ SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Invalid merge source '%s'; a working copy path can only be "
+ "used with a repository revision (a number, a date, or head)"),
+ svn_dirent_local_style(path_or_url, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* "Open" the target WC for a merge. That means:
+ * - find out its exact repository location
+ * - check the WC for suitability (throw an error if unsuitable)
+ *
+ * Set *TARGET_P to a new, fully initialized, target description structure.
+ *
+ * ALLOW_MIXED_REV, ALLOW_LOCAL_MODS, ALLOW_SWITCHED_SUBTREES determine
+ * whether the WC is deemed suitable; see ensure_wc_is_suitable_merge_target()
+ * for details.
+ *
+ * If the node is locally added, the rev and URL will be null/invalid. Some
+ * kinds of merge can use such a target; others can't.
+ */
+static svn_error_t *
+open_target_wc(merge_target_t **target_p,
+ const char *wc_abspath,
+ svn_boolean_t allow_mixed_rev,
+ svn_boolean_t allow_local_mods,
+ svn_boolean_t allow_switched_subtrees,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ merge_target_t *target = apr_palloc(result_pool, sizeof(*target));
+ svn_client__pathrev_t *origin;
+
+ target->abspath = apr_pstrdup(result_pool, wc_abspath);
+
+ SVN_ERR(svn_client__wc_node_get_origin(&origin, wc_abspath, ctx,
+ result_pool, scratch_pool));
+ if (origin)
+ {
+ target->loc = *origin;
+ }
+ else
+ {
+ svn_error_t *err;
+ /* The node has no location in the repository. It's unversioned or
+ * locally added or locally deleted.
+ *
+ * If it's locally added or deleted, find the repository root
+ * URL and UUID anyway, and leave the node URL and revision as NULL
+ * and INVALID. If it's unversioned, this will throw an error. */
+ err = svn_wc__node_get_repos_info(NULL, NULL,
+ &target->loc.repos_root_url,
+ &target->loc.repos_uuid,
+ ctx->wc_ctx, wc_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY
+ && err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED)
+ return svn_error_trace(err);
+
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err,
+ _("Merge target '%s' does not exist in the "
+ "working copy"),
+ svn_dirent_local_style(wc_abspath,
+ scratch_pool));
+ }
+
+ target->loc.rev = SVN_INVALID_REVNUM;
+ target->loc.url = NULL;
+ }
+
+ SVN_ERR(ensure_wc_is_suitable_merge_target(
+ wc_abspath, ctx,
+ allow_mixed_rev, allow_local_mods, allow_switched_subtrees,
+ scratch_pool));
+
+ *target_p = target;
+ return SVN_NO_ERROR;
+}
+
+/*-----------------------------------------------------------------------*/
+
+/*** Public APIs ***/
+
+/* The body of svn_client_merge5(), which see for details.
+ *
+ * If SOURCE1 @ REVISION1 is related to SOURCE2 @ REVISION2 then use merge
+ * tracking (subject to other constraints -- see HONOR_MERGEINFO());
+ * otherwise disable merge tracking.
+ *
+ * IGNORE_MERGEINFO and DIFF_IGNORE_ANCESTRY are as in do_merge().
+ */
+static svn_error_t *
+merge_locked(conflict_report_t **conflict_report,
+ const char *source1,
+ const svn_opt_revision_t *revision1,
+ const char *source2,
+ const svn_opt_revision_t *revision2,
+ const char *target_abspath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_mergeinfo,
+ svn_boolean_t diff_ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ svn_boolean_t allow_mixed_rev,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ merge_target_t *target;
+ svn_client__pathrev_t *source1_loc, *source2_loc;
+ svn_boolean_t sources_related = FALSE;
+ svn_ra_session_t *ra_session1, *ra_session2;
+ apr_array_header_t *merge_sources;
+ svn_error_t *err;
+ svn_boolean_t use_sleep = FALSE;
+ svn_client__pathrev_t *yca = NULL;
+ apr_pool_t *sesspool;
+ svn_boolean_t same_repos;
+
+ /* ### FIXME: This function really ought to do a history check on
+ the left and right sides of the merge source, and -- if one is an
+ ancestor of the other -- just call svn_client_merge_peg3() with
+ the appropriate args. */
+
+ SVN_ERR(open_target_wc(&target, target_abspath,
+ allow_mixed_rev, TRUE, TRUE,
+ ctx, scratch_pool, scratch_pool));
+
+ /* Open RA sessions to both sides of our merge source, and resolve URLs
+ * and revisions. */
+ sesspool = svn_pool_create(scratch_pool);
+ SVN_ERR(svn_client__ra_session_from_path2(
+ &ra_session1, &source1_loc,
+ source1, NULL, revision1, revision1, ctx, sesspool));
+ SVN_ERR(svn_client__ra_session_from_path2(
+ &ra_session2, &source2_loc,
+ source2, NULL, revision2, revision2, ctx, sesspool));
+
+ /* We can't do a diff between different repositories. */
+ /* ### We should also insist that the root URLs of the two sources match,
+ * as we are only carrying around a single source-repos-root from now
+ * on, and URL calculations will go wrong if they differ.
+ * Alternatively, teach the code to cope with differing root URLs. */
+ SVN_ERR(check_same_repos(source1_loc, source1_loc->url,
+ source2_loc, source2_loc->url,
+ FALSE /* strict_urls */, scratch_pool));
+
+ /* Do our working copy and sources come from the same repository? */
+ same_repos = is_same_repos(&target->loc, source1_loc, TRUE /* strict_urls */);
+
+ /* Unless we're ignoring ancestry, see if the two sources are related. */
+ if (! ignore_mergeinfo)
+ SVN_ERR(svn_client__get_youngest_common_ancestor(
+ &yca, source1_loc, source2_loc, ra_session1, ctx,
+ scratch_pool, scratch_pool));
+
+ /* Check for a youngest common ancestor. If we have one, we'll be
+ doing merge tracking.
+
+ So, given a requested merge of the differences between A and
+ B, and a common ancestor of C, we will find ourselves in one of
+ four positions, and four different approaches:
+
+ A == B == C there's nothing to merge
+
+ A == C != B we merge the changes between A (or C) and B
+
+ B == C != A we merge the changes between B (or C) and A
+
+ A != B != C we merge the changes between A and B without
+ merge recording, then record-only two merges:
+ from A to C, and from C to B
+ */
+ if (yca)
+ {
+ /* Note that our merge sources are related. */
+ sources_related = TRUE;
+
+ /* If the common ancestor matches the right side of our merge,
+ then we only need to reverse-merge the left side. */
+ if ((strcmp(yca->url, source2_loc->url) == 0)
+ && (yca->rev == source2_loc->rev))
+ {
+ SVN_ERR(normalize_merge_sources_internal(
+ &merge_sources, source1_loc,
+ svn_rangelist__initialize(source1_loc->rev, yca->rev, TRUE,
+ scratch_pool),
+ ra_session1, ctx, scratch_pool, scratch_pool));
+ }
+ /* If the common ancestor matches the left side of our merge,
+ then we only need to merge the right side. */
+ else if ((strcmp(yca->url, source1_loc->url) == 0)
+ && (yca->rev == source1_loc->rev))
+ {
+ SVN_ERR(normalize_merge_sources_internal(
+ &merge_sources, source2_loc,
+ svn_rangelist__initialize(yca->rev, source2_loc->rev, TRUE,
+ scratch_pool),
+ ra_session2, ctx, scratch_pool, scratch_pool));
+ }
+ /* And otherwise, we need to do both: reverse merge the left
+ side, and merge the right. */
+ else
+ {
+ merge_source_t source;
+
+ source.loc1 = source1_loc;
+ source.loc2 = source2_loc;
+ source.ancestral = FALSE;
+
+ err = merge_cousins_and_supplement_mergeinfo(conflict_report,
+ &use_sleep,
+ target,
+ ra_session1,
+ ra_session2,
+ &source,
+ yca,
+ same_repos,
+ depth,
+ diff_ignore_ancestry,
+ force_delete,
+ record_only, dry_run,
+ merge_options,
+ ctx,
+ result_pool,
+ scratch_pool);
+ /* Close our temporary RA sessions (this could've happened
+ after the second call to normalize_merge_sources() inside
+ the merge_cousins_and_supplement_mergeinfo() routine). */
+ svn_pool_destroy(sesspool);
+
+ if (use_sleep)
+ svn_io_sleep_for_timestamps(target->abspath, scratch_pool);
+
+ SVN_ERR(err);
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ {
+ merge_source_t source;
+
+ source.loc1 = source1_loc;
+ source.loc2 = source2_loc;
+ source.ancestral = FALSE;
+
+ /* Build a single-item merge_source_t array. */
+ merge_sources = apr_array_make(scratch_pool, 1, sizeof(merge_source_t *));
+ APR_ARRAY_PUSH(merge_sources, merge_source_t *) = &source;
+ }
+
+ err = do_merge(NULL, NULL, conflict_report, &use_sleep,
+ merge_sources, target,
+ ra_session1, sources_related, same_repos,
+ ignore_mergeinfo, diff_ignore_ancestry, force_delete, dry_run,
+ record_only, NULL, FALSE, FALSE, depth, merge_options,
+ ctx, result_pool, scratch_pool);
+
+ /* Close our temporary RA sessions. */
+ svn_pool_destroy(sesspool);
+
+ if (use_sleep)
+ svn_io_sleep_for_timestamps(target->abspath, scratch_pool);
+
+ SVN_ERR(err);
+ return SVN_NO_ERROR;
+}
+
+/* Set *TARGET_ABSPATH to the absolute path of, and *LOCK_ABSPATH to
+ the absolute path to lock for, TARGET_WCPATH. */
+static svn_error_t *
+get_target_and_lock_abspath(const char **target_abspath,
+ const char **lock_abspath,
+ const char *target_wcpath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool)
+{
+ svn_node_kind_t kind;
+ SVN_ERR(svn_dirent_get_absolute(target_abspath, target_wcpath,
+ result_pool));
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, *target_abspath,
+ FALSE, FALSE, result_pool));
+ if (kind == svn_node_dir)
+ *lock_abspath = *target_abspath;
+ else
+ *lock_abspath = svn_dirent_dirname(*target_abspath, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_merge5(const char *source1,
+ const svn_opt_revision_t *revision1,
+ const char *source2,
+ const svn_opt_revision_t *revision2,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_mergeinfo,
+ svn_boolean_t diff_ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ svn_boolean_t allow_mixed_rev,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *target_abspath, *lock_abspath;
+ conflict_report_t *conflict_report;
+
+ /* Sanity check our input -- we require specified revisions,
+ * and either 2 paths or 2 URLs. */
+ if ((revision1->kind == svn_opt_revision_unspecified)
+ || (revision2->kind == svn_opt_revision_unspecified))
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Not all required revisions are specified"));
+ if (svn_path_is_url(source1) != svn_path_is_url(source2))
+ return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Merge sources must both be "
+ "either paths or URLs"));
+ /* A WC path must be used with a repository revision, as we can't
+ * (currently) use the WC itself as a source, we can only read the URL
+ * from it and use that. */
+ SVN_ERR(ensure_wc_path_has_repo_revision(source1, revision1, pool));
+ SVN_ERR(ensure_wc_path_has_repo_revision(source2, revision2, pool));
+
+ SVN_ERR(get_target_and_lock_abspath(&target_abspath, &lock_abspath,
+ target_wcpath, ctx, pool));
+
+ if (!dry_run)
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ merge_locked(&conflict_report,
+ source1, revision1, source2, revision2,
+ target_abspath, depth, ignore_mergeinfo,
+ diff_ignore_ancestry,
+ force_delete, record_only, dry_run,
+ allow_mixed_rev, merge_options, ctx, pool, pool),
+ ctx->wc_ctx, lock_abspath, FALSE /* lock_anchor */, pool);
+ else
+ SVN_ERR(merge_locked(&conflict_report,
+ source1, revision1, source2, revision2,
+ target_abspath, depth, ignore_mergeinfo,
+ diff_ignore_ancestry,
+ force_delete, record_only, dry_run,
+ allow_mixed_rev, merge_options, ctx, pool, pool));
+
+ SVN_ERR(make_merge_conflict_error(conflict_report, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Check if mergeinfo for a given path is described explicitly or via
+ inheritance in a mergeinfo catalog.
+
+ If REPOS_REL_PATH exists in CATALOG and has mergeinfo containing
+ MERGEINFO, then set *IN_CATALOG to TRUE. If REPOS_REL_PATH does
+ not exist in CATALOG, then find its nearest parent which does exist.
+ If the mergeinfo REPOS_REL_PATH would inherit from that parent
+ contains MERGEINFO then set *IN_CATALOG to TRUE. Set *IN_CATALOG
+ to FALSE in all other cases.
+
+ Set *CAT_KEY_PATH to the key path in CATALOG for REPOS_REL_PATH's
+ explicit or inherited mergeinfo. If no explicit or inherited mergeinfo
+ is found for REPOS_REL_PATH then set *CAT_KEY_PATH to NULL.
+
+ User RESULT_POOL to allocate *CAT_KEY_PATH. Use SCRATCH_POOL for
+ temporary allocations. */
+static svn_error_t *
+mergeinfo_in_catalog(svn_boolean_t *in_catalog,
+ const char **cat_key_path,
+ const char *repos_rel_path,
+ svn_mergeinfo_t mergeinfo,
+ svn_mergeinfo_catalog_t catalog,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *walk_path = NULL;
+
+ *in_catalog = FALSE;
+ *cat_key_path = NULL;
+
+ if (mergeinfo && catalog && apr_hash_count(catalog))
+ {
+ const char *path = repos_rel_path;
+
+ /* Start with the assumption there is no explicit or inherited
+ mergeinfo for REPOS_REL_PATH in CATALOG. */
+ svn_mergeinfo_t mergeinfo_in_cat = NULL;
+
+ while (1)
+ {
+ mergeinfo_in_cat = svn_hash_gets(catalog, path);
+
+ if (mergeinfo_in_cat) /* Found it! */
+ {
+ *cat_key_path = apr_pstrdup(result_pool, path);
+ break;
+ }
+ else /* Look for inherited mergeinfo. */
+ {
+ walk_path = svn_relpath_join(svn_relpath_basename(path,
+ scratch_pool),
+ walk_path ? walk_path : "",
+ scratch_pool);
+ path = svn_relpath_dirname(path, scratch_pool);
+
+ if (path[0] == '\0') /* No mergeinfo to inherit. */
+ break;
+ }
+ }
+
+ if (mergeinfo_in_cat)
+ {
+ if (walk_path)
+ SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(&mergeinfo_in_cat,
+ mergeinfo_in_cat,
+ walk_path,
+ scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_mergeinfo_intersect2(&mergeinfo_in_cat,
+ mergeinfo_in_cat, mergeinfo,
+ TRUE,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_mergeinfo__equals(in_catalog, mergeinfo_in_cat,
+ mergeinfo, TRUE, scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* A svn_log_entry_receiver_t baton for log_find_operative_revs(). */
+typedef struct log_find_operative_baton_t
+{
+ /* The catalog of explicit mergeinfo on a reintegrate source. */
+ svn_mergeinfo_catalog_t merged_catalog;
+
+ /* The catalog of unmerged history from the reintegrate target to
+ the source which we will create. Allocated in RESULT_POOL. */
+ svn_mergeinfo_catalog_t unmerged_catalog;
+
+ /* The repository absolute path of the reintegrate target. */
+ const char *target_fspath;
+
+ /* The path of the reintegrate source relative to the repository root. */
+ const char *source_repos_rel_path;
+
+ apr_pool_t *result_pool;
+} log_find_operative_baton_t;
+
+/* A svn_log_entry_receiver_t callback for find_unsynced_ranges(). */
+static svn_error_t *
+log_find_operative_revs(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ log_find_operative_baton_t *log_baton = baton;
+ apr_hash_index_t *hi;
+ svn_revnum_t revision;
+
+ /* It's possible that authz restrictions on the merge source prevent us
+ from knowing about any of the changes for LOG_ENTRY->REVISION. */
+ if (!log_entry->changed_paths2)
+ return SVN_NO_ERROR;
+
+ revision = log_entry->revision;
+
+ for (hi = apr_hash_first(pool, log_entry->changed_paths2);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *subtree_missing_this_rev;
+ const char *path = svn__apr_hash_index_key(hi);
+ const char *rel_path;
+ const char *source_rel_path;
+ svn_boolean_t in_catalog;
+ svn_mergeinfo_t log_entry_as_mergeinfo;
+
+ rel_path = svn_fspath__skip_ancestor(log_baton->target_fspath, path);
+ /* Easy out: The path is not within the tree of interest. */
+ if (rel_path == NULL)
+ continue;
+
+ source_rel_path = svn_relpath_join(log_baton->source_repos_rel_path,
+ rel_path, pool);
+
+ SVN_ERR(svn_mergeinfo_parse(&log_entry_as_mergeinfo,
+ apr_psprintf(pool, "%s:%ld",
+ path, revision),
+ pool));
+
+ SVN_ERR(mergeinfo_in_catalog(&in_catalog, &subtree_missing_this_rev,
+ source_rel_path, log_entry_as_mergeinfo,
+ log_baton->merged_catalog,
+ pool, pool));
+
+ if (!in_catalog)
+ {
+ svn_mergeinfo_t unmerged_for_key;
+ const char *suffix, *missing_path;
+
+ /* If there is no mergeinfo on the source tree we'll say
+ the "subtree" missing this revision is the root of the
+ source. */
+ if (!subtree_missing_this_rev)
+ subtree_missing_this_rev = log_baton->source_repos_rel_path;
+
+ suffix = svn_relpath_skip_ancestor(subtree_missing_this_rev,
+ source_rel_path);
+ if (suffix)
+ {
+ missing_path = apr_pstrmemdup(pool, path,
+ strlen(path) - strlen(suffix) - 1);
+ }
+ else
+ {
+ missing_path = path;
+ }
+
+ SVN_ERR(svn_mergeinfo_parse(&log_entry_as_mergeinfo,
+ apr_psprintf(pool, "%s:%ld",
+ missing_path, revision),
+ log_baton->result_pool));
+ unmerged_for_key = svn_hash_gets(log_baton->unmerged_catalog,
+ subtree_missing_this_rev);
+
+ if (unmerged_for_key)
+ {
+ SVN_ERR(svn_mergeinfo_merge2(unmerged_for_key,
+ log_entry_as_mergeinfo,
+ log_baton->result_pool,
+ pool));
+ }
+ else
+ {
+ svn_hash_sets(log_baton->unmerged_catalog,
+ apr_pstrdup(log_baton->result_pool,
+ subtree_missing_this_rev),
+ log_entry_as_mergeinfo);
+ }
+
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Determine if the mergeinfo on a reintegrate source SOURCE_LOC,
+ reflects that the source is fully synced with the reintegrate target
+ TARGET_LOC, even if a naive interpretation of the source's
+ mergeinfo says otherwise -- See issue #3577.
+
+ UNMERGED_CATALOG represents the history (as mergeinfo) from
+ TARGET_LOC that is not represented in SOURCE_LOC's
+ explicit/inherited mergeinfo as represented by MERGED_CATALOG.
+ MERGEINFO_CATALOG may be empty if the source has no explicit or inherited
+ mergeinfo.
+
+ Check that all of the unmerged revisions in UNMERGED_CATALOG's
+ mergeinfos are "phantoms", that is, one of the following conditions holds:
+
+ 1) The revision affects no corresponding paths in SOURCE_LOC.
+
+ 2) The revision affects corresponding paths in SOURCE_LOC,
+ but based on the mergeinfo in MERGED_CATALOG, the change was
+ previously merged.
+
+ Make a deep copy, allocated in RESULT_POOL, of any portions of
+ UNMERGED_CATALOG that are not phantoms, to TRUE_UNMERGED_CATALOG.
+
+ Note: The keys in all mergeinfo catalogs used here are relative to the
+ root of the repository.
+
+ RA_SESSION is an RA session open to the repository of TARGET_LOC; it may
+ be temporarily reparented within this function.
+
+ Use SCRATCH_POOL for all temporary allocations. */
+static svn_error_t *
+find_unsynced_ranges(const svn_client__pathrev_t *source_loc,
+ const svn_client__pathrev_t *target_loc,
+ svn_mergeinfo_catalog_t unmerged_catalog,
+ svn_mergeinfo_catalog_t merged_catalog,
+ svn_mergeinfo_catalog_t true_unmerged_catalog,
+ svn_ra_session_t *ra_session,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_rangelist_t *potentially_unmerged_ranges = NULL;
+
+ /* Convert all the unmerged history to a rangelist. */
+ if (apr_hash_count(unmerged_catalog))
+ {
+ apr_hash_index_t *hi_catalog;
+
+ potentially_unmerged_ranges =
+ apr_array_make(scratch_pool, 1, sizeof(svn_merge_range_t *));
+
+ for (hi_catalog = apr_hash_first(scratch_pool, unmerged_catalog);
+ hi_catalog;
+ hi_catalog = apr_hash_next(hi_catalog))
+ {
+ svn_mergeinfo_t mergeinfo = svn__apr_hash_index_val(hi_catalog);
+
+ SVN_ERR(svn_rangelist__merge_many(potentially_unmerged_ranges,
+ mergeinfo,
+ scratch_pool, scratch_pool));
+ }
+ }
+
+ /* Find any unmerged revisions which both affect the source and
+ are not yet merged to it. */
+ if (potentially_unmerged_ranges)
+ {
+ svn_revnum_t oldest_rev =
+ (APR_ARRAY_IDX(potentially_unmerged_ranges,
+ 0,
+ svn_merge_range_t *))->start + 1;
+ svn_revnum_t youngest_rev =
+ (APR_ARRAY_IDX(potentially_unmerged_ranges,
+ potentially_unmerged_ranges->nelts - 1,
+ svn_merge_range_t *))->end;
+ log_find_operative_baton_t log_baton;
+ const char *old_session_url;
+ svn_error_t *err;
+
+ log_baton.merged_catalog = merged_catalog;
+ log_baton.unmerged_catalog = true_unmerged_catalog;
+ log_baton.source_repos_rel_path
+ = svn_client__pathrev_relpath(source_loc, scratch_pool);
+ log_baton.target_fspath
+ = svn_client__pathrev_fspath(target_loc, scratch_pool);
+ log_baton.result_pool = result_pool;
+
+ SVN_ERR(svn_client__ensure_ra_session_url(
+ &old_session_url, ra_session, target_loc->url, scratch_pool));
+ err = get_log(ra_session, "", youngest_rev, oldest_rev,
+ TRUE, /* discover_changed_paths */
+ log_find_operative_revs, &log_baton,
+ scratch_pool);
+ SVN_ERR(svn_error_compose_create(
+ err, svn_ra_reparent(ra_session, old_session_url, scratch_pool)));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Find the youngest revision that has been merged from target to source.
+ *
+ * If any location in TARGET_HISTORY_AS_MERGEINFO is mentioned in
+ * SOURCE_MERGEINFO, then we know that at least one merge was done from the
+ * target to the source. In that case, set *YOUNGEST_MERGED_REV to the
+ * youngest revision of that intersection (unless *YOUNGEST_MERGED_REV is
+ * already younger than that). Otherwise, leave *YOUNGEST_MERGED_REV alone.
+ */
+static svn_error_t *
+find_youngest_merged_rev(svn_revnum_t *youngest_merged_rev,
+ svn_mergeinfo_t target_history_as_mergeinfo,
+ svn_mergeinfo_t source_mergeinfo,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_t explicit_source_target_history_intersection;
+
+ SVN_ERR(svn_mergeinfo_intersect2(
+ &explicit_source_target_history_intersection,
+ source_mergeinfo, target_history_as_mergeinfo, TRUE,
+ scratch_pool, scratch_pool));
+ if (apr_hash_count(explicit_source_target_history_intersection))
+ {
+ svn_revnum_t old_rev, young_rev;
+
+ /* Keep track of the youngest revision merged from target to source. */
+ SVN_ERR(svn_mergeinfo__get_range_endpoints(
+ &young_rev, &old_rev,
+ explicit_source_target_history_intersection, scratch_pool));
+ if (!SVN_IS_VALID_REVNUM(*youngest_merged_rev)
+ || (young_rev > *youngest_merged_rev))
+ *youngest_merged_rev = young_rev;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *FILTERED_MERGEINFO_P to the parts of TARGET_HISTORY_AS_MERGEINFO
+ * that are not present in the source branch.
+ *
+ * SOURCE_MERGEINFO is the explicit or inherited mergeinfo of the source
+ * branch SOURCE_PATHREV. Extend SOURCE_MERGEINFO, modifying it in
+ * place, to include the natural history (implicit mergeinfo) of
+ * SOURCE_PATHREV. ### But make these additions in SCRATCH_POOL.
+ *
+ * SOURCE_RA_SESSION is an RA session open to the repository containing
+ * SOURCE_PATHREV; it may be temporarily reparented within this function.
+ *
+ * ### [JAF] This function is named '..._subroutine' simply because I
+ * factored it out based on code similarity, without knowing what it's
+ * purpose is. We should clarify its purpose and choose a better name.
+ */
+static svn_error_t *
+find_unmerged_mergeinfo_subroutine(svn_mergeinfo_t *filtered_mergeinfo_p,
+ svn_mergeinfo_t target_history_as_mergeinfo,
+ svn_mergeinfo_t source_mergeinfo,
+ const svn_client__pathrev_t *source_pathrev,
+ svn_ra_session_t *source_ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_t source_history_as_mergeinfo;
+
+ /* Get the source path's natural history and merge it into source
+ path's explicit or inherited mergeinfo. */
+ SVN_ERR(svn_client__get_history_as_mergeinfo(
+ &source_history_as_mergeinfo, NULL /* has_rev_zero_history */,
+ source_pathrev, source_pathrev->rev, SVN_INVALID_REVNUM,
+ source_ra_session, ctx, scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(source_mergeinfo,
+ source_history_as_mergeinfo,
+ scratch_pool, scratch_pool));
+
+ /* Now source_mergeinfo represents everything we know about
+ source_path's history. Now we need to know what part, if any, of the
+ corresponding target's history is *not* part of source_path's total
+ history; because it is neither shared history nor was it ever merged
+ from the target to the source. */
+ SVN_ERR(svn_mergeinfo_remove2(filtered_mergeinfo_p,
+ source_mergeinfo,
+ target_history_as_mergeinfo, TRUE,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Helper for calculate_left_hand_side() which produces a mergeinfo catalog
+ describing what parts of of the reintegrate target have not previously been
+ merged to the reintegrate source.
+
+ SOURCE_CATALOG is the collection of explicit mergeinfo on SOURCE_LOC and
+ all its children, i.e. the mergeinfo catalog for the reintegrate source.
+
+ TARGET_HISTORY_HASH is a hash of (const char *) paths mapped to
+ svn_mergeinfo_t representing the location history. Each of these
+ path keys represent a path in the reintegrate target, relative to the
+ repository root, which has explicit mergeinfo and/or is the reintegrate
+ target itself. The svn_mergeinfo_t's contain the natural history of each
+ path@TARGET_REV. Effectively this is the mergeinfo catalog on the
+ reintegrate target.
+
+ YC_ANCESTOR_REV is the revision of the youngest common ancestor of the
+ reintegrate source and the reintegrate target.
+
+ SOURCE_LOC is the reintegrate source.
+
+ SOURCE_RA_SESSION is a session opened to the URL of SOURCE_LOC
+ and TARGET_RA_SESSION is open to TARGET->loc.url.
+
+ For each entry in TARGET_HISTORY_HASH check that the history it
+ represents is contained in either the explicit mergeinfo for the
+ corresponding path in SOURCE_CATALOG, the corresponding path's inherited
+ mergeinfo (if no explicit mergeinfo for the path is found in
+ SOURCE_CATALOG), or the corresponding path's natural history. Populate
+ *UNMERGED_TO_SOURCE_CATALOG with the corresponding source paths mapped to
+ the mergeinfo from the target's natural history which is *not* found. Also
+ include any mergeinfo from SOURCE_CATALOG which explicitly describes the
+ target's history but for which *no* entry was found in
+ TARGET_HISTORY_HASH.
+
+ If no part of TARGET_HISTORY_HASH is found in SOURCE_CATALOG set
+ *YOUNGEST_MERGED_REV to SVN_INVALID_REVNUM; otherwise set it to the youngest
+ revision previously merged from the target to the source, and filter
+ *UNMERGED_TO_SOURCE_CATALOG so that it contains no ranges greater than
+ *YOUNGEST_MERGED_REV.
+
+ *UNMERGED_TO_SOURCE_CATALOG is (deeply) allocated in RESULT_POOL.
+ SCRATCH_POOL is used for all temporary allocations. */
+static svn_error_t *
+find_unmerged_mergeinfo(svn_mergeinfo_catalog_t *unmerged_to_source_catalog,
+ svn_revnum_t *youngest_merged_rev,
+ svn_revnum_t yc_ancestor_rev,
+ svn_mergeinfo_catalog_t source_catalog,
+ apr_hash_t *target_history_hash,
+ const svn_client__pathrev_t *source_loc,
+ const merge_target_t *target,
+ svn_ra_session_t *source_ra_session,
+ svn_ra_session_t *target_ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *source_repos_rel_path
+ = svn_client__pathrev_relpath(source_loc, scratch_pool);
+ const char *target_repos_rel_path
+ = svn_client__pathrev_relpath(&target->loc, scratch_pool);
+ apr_hash_index_t *hi;
+ svn_mergeinfo_catalog_t new_catalog = apr_hash_make(result_pool);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ assert(session_url_is(source_ra_session, source_loc->url, scratch_pool));
+ assert(session_url_is(target_ra_session, target->loc.url, scratch_pool));
+
+ *youngest_merged_rev = SVN_INVALID_REVNUM;
+
+ /* Examine the natural history of each path in the reintegrate target
+ with explicit mergeinfo. */
+ for (hi = apr_hash_first(scratch_pool, target_history_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *target_path = svn__apr_hash_index_key(hi);
+ svn_mergeinfo_t target_history_as_mergeinfo = svn__apr_hash_index_val(hi);
+ const char *path_rel_to_session
+ = svn_relpath_skip_ancestor(target_repos_rel_path, target_path);
+ const char *source_path;
+ svn_client__pathrev_t *source_pathrev;
+ svn_mergeinfo_t source_mergeinfo, filtered_mergeinfo;
+
+ svn_pool_clear(iterpool);
+
+ source_path = svn_relpath_join(source_repos_rel_path,
+ path_rel_to_session, iterpool);
+ source_pathrev = svn_client__pathrev_join_relpath(
+ source_loc, path_rel_to_session, iterpool);
+
+ /* Remove any target history that is also part of the source's history,
+ i.e. their common ancestry. By definition this has already been
+ "merged" from the target to the source. If the source has explicit
+ self referential mergeinfo it would intersect with the target's
+ history below, making it appear that some merges had been done from
+ the target to the source, when this might not actually be the case. */
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &target_history_as_mergeinfo, target_history_as_mergeinfo,
+ source_loc->rev, yc_ancestor_rev, TRUE, iterpool, iterpool));
+
+ /* Look for any explicit mergeinfo on the source path corresponding to
+ the target path. If we find any remove that from SOURCE_CATALOG.
+ When this iteration over TARGET_HISTORY_HASH is complete all that
+ should be left in SOURCE_CATALOG are subtrees that have explicit
+ mergeinfo on the reintegrate source where there is no corresponding
+ explicit mergeinfo on the reintegrate target. */
+ source_mergeinfo = svn_hash_gets(source_catalog, source_path);
+ if (source_mergeinfo)
+ {
+ svn_hash_sets(source_catalog, source_path, NULL);
+
+ SVN_ERR(find_youngest_merged_rev(youngest_merged_rev,
+ target_history_as_mergeinfo,
+ source_mergeinfo,
+ iterpool));
+ }
+ else
+ {
+ /* There is no mergeinfo on source_path *or* source_path doesn't
+ exist at all. If simply doesn't exist we can ignore it
+ altogether. */
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_ra_check_path(source_ra_session,
+ path_rel_to_session,
+ source_loc->rev, &kind, iterpool));
+ if (kind == svn_node_none)
+ continue;
+ /* Else source_path does exist though it has no explicit mergeinfo.
+ Find its inherited mergeinfo. If it doesn't have any then simply
+ set source_mergeinfo to an empty hash. */
+ SVN_ERR(svn_client__get_repos_mergeinfo(
+ &source_mergeinfo, source_ra_session,
+ source_pathrev->url, source_pathrev->rev,
+ svn_mergeinfo_inherited, FALSE /*squelch_incapable*/,
+ iterpool));
+ if (!source_mergeinfo)
+ source_mergeinfo = apr_hash_make(iterpool);
+ }
+
+ /* Use scratch_pool rather than iterpool because filtered_mergeinfo
+ is going into new_catalog below and needs to last to the end of
+ this function. */
+ SVN_ERR(find_unmerged_mergeinfo_subroutine(
+ &filtered_mergeinfo, target_history_as_mergeinfo,
+ source_mergeinfo, source_pathrev,
+ source_ra_session, ctx, scratch_pool, iterpool));
+ svn_hash_sets(new_catalog, apr_pstrdup(scratch_pool, source_path),
+ filtered_mergeinfo);
+ }
+
+ /* Are there any subtrees with explicit mergeinfo still left in the merge
+ source where there was no explicit mergeinfo for the corresponding path
+ in the merge target? If so, add the intersection of those path's
+ mergeinfo and the corresponding target path's mergeinfo to
+ new_catalog. */
+ for (hi = apr_hash_first(scratch_pool, source_catalog);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *source_path = svn__apr_hash_index_key(hi);
+ const char *path_rel_to_session =
+ svn_relpath_skip_ancestor(source_repos_rel_path, source_path);
+ const char *source_url;
+ svn_mergeinfo_t source_mergeinfo = svn__apr_hash_index_val(hi);
+ svn_mergeinfo_t filtered_mergeinfo;
+ svn_client__pathrev_t *target_pathrev;
+ svn_mergeinfo_t target_history_as_mergeinfo;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ source_url = svn_path_url_add_component2(source_loc->url,
+ path_rel_to_session, iterpool);
+ target_pathrev = svn_client__pathrev_join_relpath(
+ &target->loc, path_rel_to_session, iterpool);
+ err = svn_client__get_history_as_mergeinfo(&target_history_as_mergeinfo,
+ NULL /* has_rev_zero_history */,
+ target_pathrev,
+ target->loc.rev,
+ SVN_INVALID_REVNUM,
+ target_ra_session,
+ ctx, iterpool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND
+ || err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
+ {
+ /* This path with explicit mergeinfo in the source doesn't
+ exist on the target. */
+ svn_error_clear(err);
+ err = NULL;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+ else
+ {
+ svn_client__pathrev_t *pathrev;
+
+ SVN_ERR(find_youngest_merged_rev(youngest_merged_rev,
+ target_history_as_mergeinfo,
+ source_mergeinfo,
+ iterpool));
+
+ /* Use scratch_pool rather than iterpool because filtered_mergeinfo
+ is going into new_catalog below and needs to last to the end of
+ this function. */
+ /* ### Why looking at SOURCE_url at TARGET_rev? */
+ SVN_ERR(svn_client__pathrev_create_with_session(
+ &pathrev, source_ra_session, target->loc.rev, source_url,
+ iterpool));
+ SVN_ERR(find_unmerged_mergeinfo_subroutine(
+ &filtered_mergeinfo, target_history_as_mergeinfo,
+ source_mergeinfo, pathrev,
+ source_ra_session, ctx, scratch_pool, iterpool));
+ if (apr_hash_count(filtered_mergeinfo))
+ svn_hash_sets(new_catalog,
+ apr_pstrdup(scratch_pool, source_path),
+ filtered_mergeinfo);
+ }
+ }
+
+ /* Limit new_catalog to the youngest revisions previously merged from
+ the target to the source. */
+ if (SVN_IS_VALID_REVNUM(*youngest_merged_rev))
+ SVN_ERR(svn_mergeinfo__filter_catalog_by_ranges(&new_catalog,
+ new_catalog,
+ *youngest_merged_rev,
+ 0, /* No oldest bound. */
+ TRUE,
+ scratch_pool,
+ scratch_pool));
+
+ /* Make a shiny new copy before blowing away all the temporary pools. */
+ *unmerged_to_source_catalog = svn_mergeinfo_catalog_dup(new_catalog,
+ result_pool);
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_client_merge_reintegrate() which calculates the
+ 'left hand side' of the underlying two-URL merge that a --reintegrate
+ merge actually performs. If no merge should be performed, set
+ *LEFT_P to NULL.
+
+ TARGET->abspath is the absolute working copy path of the reintegrate
+ merge.
+
+ SOURCE_LOC is the reintegrate source.
+
+ SUBTREES_WITH_MERGEINFO is a hash of (const char *) absolute paths mapped
+ to (svn_mergeinfo_t *) mergeinfo values for each working copy path with
+ explicit mergeinfo in TARGET->abspath. Actually we only need to know the
+ paths, not the mergeinfo.
+
+ TARGET->loc.rev is the working revision the entire WC tree rooted at
+ TARGET is at.
+
+ Populate *UNMERGED_TO_SOURCE_CATALOG with the mergeinfo describing what
+ parts of TARGET->loc have not been merged to SOURCE_LOC, up to the
+ youngest revision ever merged from the TARGET->abspath to the source if
+ such exists, see doc string for find_unmerged_mergeinfo().
+
+ SOURCE_RA_SESSION is a session opened to the SOURCE_LOC
+ and TARGET_RA_SESSION is open to TARGET->loc.url.
+
+ *LEFT_P, *MERGED_TO_SOURCE_CATALOG , and *UNMERGED_TO_SOURCE_CATALOG are
+ allocated in RESULT_POOL. SCRATCH_POOL is used for all temporary
+ allocations. */
+static svn_error_t *
+calculate_left_hand_side(svn_client__pathrev_t **left_p,
+ svn_mergeinfo_catalog_t *merged_to_source_catalog,
+ svn_mergeinfo_catalog_t *unmerged_to_source_catalog,
+ const merge_target_t *target,
+ apr_hash_t *subtrees_with_mergeinfo,
+ const svn_client__pathrev_t *source_loc,
+ svn_ra_session_t *source_ra_session,
+ svn_ra_session_t *target_ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_catalog_t mergeinfo_catalog, unmerged_catalog;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ /* hash of paths mapped to arrays of svn_mergeinfo_t. */
+ apr_hash_t *target_history_hash = apr_hash_make(scratch_pool);
+ svn_revnum_t youngest_merged_rev;
+ svn_client__pathrev_t *yc_ancestor;
+
+ assert(session_url_is(source_ra_session, source_loc->url, scratch_pool));
+ assert(session_url_is(target_ra_session, target->loc.url, scratch_pool));
+
+ /* Initialize our return variables. */
+ *left_p = NULL;
+
+ /* TARGET->abspath may not have explicit mergeinfo and thus may not be
+ contained within SUBTREES_WITH_MERGEINFO. If this is the case then
+ add a dummy item for TARGET->abspath so we get its history (i.e. implicit
+ mergeinfo) below. */
+ if (!svn_hash_gets(subtrees_with_mergeinfo, target->abspath))
+ svn_hash_sets(subtrees_with_mergeinfo, target->abspath,
+ apr_hash_make(result_pool));
+
+ /* Get the history segments (as mergeinfo) for TARGET->abspath and any of
+ its subtrees with explicit mergeinfo. */
+ for (hi = apr_hash_first(scratch_pool, subtrees_with_mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+ svn_client__pathrev_t *target_child;
+ const char *repos_relpath;
+ svn_mergeinfo_t target_history_as_mergeinfo;
+
+ svn_pool_clear(iterpool);
+
+ /* Convert the absolute path with mergeinfo on it to a path relative
+ to the session root. */
+ SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, NULL, NULL,
+ ctx->wc_ctx, local_abspath,
+ scratch_pool, iterpool));
+ target_child = svn_client__pathrev_create_with_relpath(
+ target->loc.repos_root_url, target->loc.repos_uuid,
+ target->loc.rev, repos_relpath, iterpool);
+ SVN_ERR(svn_client__get_history_as_mergeinfo(&target_history_as_mergeinfo,
+ NULL /* has_rev_zero_hist */,
+ target_child,
+ target->loc.rev,
+ SVN_INVALID_REVNUM,
+ target_ra_session,
+ ctx, scratch_pool));
+
+ svn_hash_sets(target_history_hash, repos_relpath,
+ target_history_as_mergeinfo);
+ }
+
+ /* Check that SOURCE_LOC and TARGET->loc are
+ actually related, we can't reintegrate if they are not. Also
+ get an initial value for the YCA revision number. */
+ SVN_ERR(svn_client__get_youngest_common_ancestor(
+ &yc_ancestor, source_loc, &target->loc, target_ra_session, ctx,
+ iterpool, iterpool));
+ if (! yc_ancestor)
+ return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+ _("'%s@%ld' must be ancestrally related to "
+ "'%s@%ld'"), source_loc->url, source_loc->rev,
+ target->loc.url, target->loc.rev);
+
+ /* If the source revision is the same as the youngest common
+ revision, then there can't possibly be any unmerged revisions
+ that we need to apply to target. */
+ if (source_loc->rev == yc_ancestor->rev)
+ {
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+
+ /* Get the mergeinfo from the source, including its descendants
+ with differing explicit mergeinfo. */
+ SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
+ &mergeinfo_catalog, source_ra_session,
+ source_loc->url, source_loc->rev,
+ svn_mergeinfo_inherited, FALSE /* squelch_incapable */,
+ TRUE /* include_descendants */, iterpool, iterpool));
+
+ if (!mergeinfo_catalog)
+ mergeinfo_catalog = apr_hash_make(iterpool);
+
+ *merged_to_source_catalog = svn_mergeinfo_catalog_dup(mergeinfo_catalog,
+ result_pool);
+
+ /* Filter the source's mergeinfo catalog so that we are left with
+ mergeinfo that describes what has *not* previously been merged from
+ TARGET->loc to SOURCE_LOC. */
+ SVN_ERR(find_unmerged_mergeinfo(&unmerged_catalog,
+ &youngest_merged_rev,
+ yc_ancestor->rev,
+ mergeinfo_catalog,
+ target_history_hash,
+ source_loc,
+ target,
+ source_ra_session,
+ target_ra_session,
+ ctx,
+ iterpool, iterpool));
+
+ /* Simplify unmerged_catalog through elision then make a copy in POOL. */
+ SVN_ERR(svn_client__elide_mergeinfo_catalog(unmerged_catalog,
+ iterpool));
+ *unmerged_to_source_catalog = svn_mergeinfo_catalog_dup(unmerged_catalog,
+ result_pool);
+
+ if (youngest_merged_rev == SVN_INVALID_REVNUM)
+ {
+ /* We never merged to the source. Just return the branch point. */
+ *left_p = svn_client__pathrev_dup(yc_ancestor, result_pool);
+ }
+ else
+ {
+ /* We've previously merged some or all of the target, up to
+ youngest_merged_rev, to the source. Set
+ *LEFT_P to cover the youngest part of this range. */
+ SVN_ERR(svn_client__repos_location(left_p, target_ra_session,
+ &target->loc, youngest_merged_rev,
+ ctx, result_pool, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Determine the URLs and revisions needed to perform a reintegrate merge
+ * from SOURCE_LOC into the working copy at TARGET.
+ *
+ * SOURCE_RA_SESSION and TARGET_RA_SESSION are RA sessions opened to the
+ * URLs of SOURCE_LOC and TARGET->loc respectively.
+ *
+ * Set *SOURCE_P to
+ * the source-left and source-right locations of the required merge. Set
+ * *YC_ANCESTOR_P to the location of the youngest ancestor.
+ * Any of these output pointers may be NULL if not wanted.
+ *
+ * See svn_client_find_reintegrate_merge() for other details.
+ */
+static svn_error_t *
+find_reintegrate_merge(merge_source_t **source_p,
+ svn_client__pathrev_t **yc_ancestor_p,
+ svn_ra_session_t *source_ra_session,
+ const svn_client__pathrev_t *source_loc,
+ svn_ra_session_t *target_ra_session,
+ const merge_target_t *target,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__pathrev_t *yc_ancestor;
+ svn_client__pathrev_t *loc1;
+ merge_source_t source;
+ svn_mergeinfo_catalog_t unmerged_to_source_mergeinfo_catalog;
+ svn_mergeinfo_catalog_t merged_to_source_mergeinfo_catalog;
+ svn_error_t *err;
+ apr_hash_t *subtrees_with_mergeinfo;
+
+ assert(session_url_is(source_ra_session, source_loc->url, scratch_pool));
+ assert(session_url_is(target_ra_session, target->loc.url, scratch_pool));
+
+ /* As the WC tree is "pure", use its last-updated-to revision as
+ the default revision for the left side of our merge, since that's
+ what the repository sub-tree is required to be up to date with
+ (with regard to the WC). */
+ /* ### Bogus/obsolete comment? */
+
+ /* Can't reintegrate to or from the root of the repository. */
+ if (strcmp(source_loc->url, source_loc->repos_root_url) == 0
+ || strcmp(target->loc.url, target->loc.repos_root_url) == 0)
+ return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+ _("Neither the reintegrate source nor target "
+ "can be the root of the repository"));
+
+ /* Find all the subtrees in TARGET_WCPATH that have explicit mergeinfo. */
+ err = get_wc_explicit_mergeinfo_catalog(&subtrees_with_mergeinfo,
+ target->abspath, svn_depth_infinity,
+ ctx, scratch_pool, scratch_pool);
+ /* Issue #3896: If invalid mergeinfo in the reintegrate target
+ prevents us from proceeding, then raise the best error possible. */
+ if (err && err->apr_err == SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING)
+ err = svn_error_quick_wrap(err, _("Reintegrate merge not possible"));
+ SVN_ERR(err);
+
+ SVN_ERR(calculate_left_hand_side(&loc1,
+ &merged_to_source_mergeinfo_catalog,
+ &unmerged_to_source_mergeinfo_catalog,
+ target,
+ subtrees_with_mergeinfo,
+ source_loc,
+ source_ra_session,
+ target_ra_session,
+ ctx,
+ scratch_pool, scratch_pool));
+
+ /* Did calculate_left_hand_side() decide that there was no merge to
+ be performed here? */
+ if (! loc1)
+ {
+ if (source_p)
+ *source_p = NULL;
+ if (yc_ancestor_p)
+ *yc_ancestor_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ source.loc1 = loc1;
+ source.loc2 = source_loc;
+
+ /* If the target was moved after the source was branched from it,
+ it is possible that the left URL differs from the target's current
+ URL. If so, then adjust TARGET_RA_SESSION to point to the old URL. */
+ if (strcmp(source.loc1->url, target->loc.url))
+ SVN_ERR(svn_ra_reparent(target_ra_session, source.loc1->url, scratch_pool));
+
+ SVN_ERR(svn_client__get_youngest_common_ancestor(
+ &yc_ancestor, source.loc2, source.loc1, target_ra_session,
+ ctx, scratch_pool, scratch_pool));
+
+ if (! yc_ancestor)
+ return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+ _("'%s@%ld' must be ancestrally related to "
+ "'%s@%ld'"),
+ source.loc1->url, source.loc1->rev,
+ source.loc2->url, source.loc2->rev);
+
+ /* The source side of a reintegrate merge is not 'ancestral', except in
+ * the degenerate case where source == YCA. */
+ source.ancestral = (loc1->rev == yc_ancestor->rev);
+
+ if (source.loc1->rev > yc_ancestor->rev)
+ {
+ /* Have we actually merged anything to the source from the
+ target? If so, make sure we've merged a contiguous
+ prefix. */
+ svn_mergeinfo_catalog_t final_unmerged_catalog = apr_hash_make(scratch_pool);
+
+ SVN_ERR(find_unsynced_ranges(source_loc, yc_ancestor,
+ unmerged_to_source_mergeinfo_catalog,
+ merged_to_source_mergeinfo_catalog,
+ final_unmerged_catalog,
+ target_ra_session, scratch_pool,
+ scratch_pool));
+
+ if (apr_hash_count(final_unmerged_catalog))
+ {
+ svn_string_t *source_mergeinfo_cat_string;
+
+ SVN_ERR(svn_mergeinfo__catalog_to_formatted_string(
+ &source_mergeinfo_cat_string,
+ final_unmerged_catalog,
+ " ", " Missing ranges: ", scratch_pool));
+ return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE,
+ NULL,
+ _("Reintegrate can only be used if "
+ "revisions %ld through %ld were "
+ "previously merged from %s to the "
+ "reintegrate source, but this is "
+ "not the case:\n%s"),
+ yc_ancestor->rev + 1, source.loc2->rev,
+ target->loc.url,
+ source_mergeinfo_cat_string->data);
+ }
+ }
+
+ /* Left side: trunk@youngest-trunk-rev-merged-to-branch-at-specified-peg-rev
+ * Right side: branch@specified-peg-revision */
+ if (source_p)
+ *source_p = merge_source_dup(&source, result_pool);
+
+ if (yc_ancestor_p)
+ *yc_ancestor_p = svn_client__pathrev_dup(yc_ancestor, result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Resolve the source and target locations and open RA sessions to them, and
+ * perform some checks appropriate for a reintegrate merge.
+ *
+ * Set *SOURCE_RA_SESSION_P and *SOURCE_LOC_P to a new session and the
+ * repository location of SOURCE_PATH_OR_URL at SOURCE_PEG_REVISION. Set
+ * *TARGET_RA_SESSION_P and *TARGET_P to a new session and the repository
+ * location of the WC at TARGET_ABSPATH.
+ *
+ * Throw a SVN_ERR_CLIENT_UNRELATED_RESOURCES error if the target WC node is
+ * a locally added node or if the source and target are not in the same
+ * repository. Throw a SVN_ERR_CLIENT_NOT_READY_TO_MERGE error if the
+ * target WC is not at a single revision without switched subtrees and
+ * without local mods.
+ *
+ * Allocate all the outputs in RESULT_POOL.
+ */
+static svn_error_t *
+open_reintegrate_source_and_target(svn_ra_session_t **source_ra_session_p,
+ svn_client__pathrev_t **source_loc_p,
+ svn_ra_session_t **target_ra_session_p,
+ merge_target_t **target_p,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ const char *target_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__pathrev_t *source_loc;
+ merge_target_t *target;
+
+ /* Open the target WC. A reintegrate merge requires the merge target to
+ * reflect a subtree of the repository as found at a single revision. */
+ SVN_ERR(open_target_wc(&target, target_abspath,
+ FALSE, FALSE, FALSE,
+ ctx, scratch_pool, scratch_pool));
+ SVN_ERR(svn_client_open_ra_session2(target_ra_session_p,
+ target->loc.url, target->abspath,
+ ctx, result_pool, scratch_pool));
+ if (! target->loc.url)
+ return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+ _("Can't reintegrate into '%s' because it is "
+ "locally added and therefore not related to "
+ "the merge source"),
+ svn_dirent_local_style(target->abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_client__ra_session_from_path2(
+ source_ra_session_p, &source_loc,
+ source_path_or_url, NULL, source_peg_revision, source_peg_revision,
+ ctx, result_pool));
+
+ /* source_loc and target->loc are required to be in the same repository,
+ as mergeinfo doesn't come into play for cross-repository merging. */
+ SVN_ERR(check_same_repos(source_loc,
+ svn_dirent_local_style(source_path_or_url,
+ scratch_pool),
+ &target->loc,
+ svn_dirent_local_style(target->abspath,
+ scratch_pool),
+ TRUE /* strict_urls */, scratch_pool));
+
+ *source_loc_p = source_loc;
+ *target_p = target;
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_client_merge_reintegrate(), which see for details. */
+static svn_error_t *
+merge_reintegrate_locked(conflict_report_t **conflict_report,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ const char *target_abspath,
+ svn_boolean_t diff_ignore_ancestry,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_session_t *target_ra_session, *source_ra_session;
+ merge_target_t *target;
+ svn_client__pathrev_t *source_loc;
+ merge_source_t *source;
+ svn_client__pathrev_t *yc_ancestor;
+ svn_boolean_t use_sleep = FALSE;
+ svn_error_t *err;
+
+ SVN_ERR(open_reintegrate_source_and_target(
+ &source_ra_session, &source_loc, &target_ra_session, &target,
+ source_path_or_url, source_peg_revision, target_abspath,
+ ctx, scratch_pool, scratch_pool));
+
+ SVN_ERR(find_reintegrate_merge(&source, &yc_ancestor,
+ source_ra_session, source_loc,
+ target_ra_session, target,
+ ctx, scratch_pool, scratch_pool));
+
+ if (! source)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* Do the real merge! */
+ /* ### TODO(reint): Make sure that one isn't the same line ancestor
+ ### of the other (what's erroneously referred to as "ancestrally
+ ### related" in this source file). For now, we just say the source
+ ### isn't "ancestral" even if it is (in the degenerate case where
+ ### source-left equals YCA). */
+ source->ancestral = FALSE;
+ err = merge_cousins_and_supplement_mergeinfo(conflict_report,
+ &use_sleep,
+ target,
+ target_ra_session,
+ source_ra_session,
+ source, yc_ancestor,
+ TRUE /* same_repos */,
+ svn_depth_infinity,
+ diff_ignore_ancestry,
+ FALSE /* force_delete */,
+ FALSE /* record_only */,
+ dry_run,
+ merge_options,
+ ctx,
+ result_pool, scratch_pool);
+
+ if (use_sleep)
+ svn_io_sleep_for_timestamps(target_abspath, scratch_pool);
+
+ SVN_ERR(err);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_merge_reintegrate(const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ const char *target_wcpath,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *target_abspath, *lock_abspath;
+ conflict_report_t *conflict_report;
+
+ SVN_ERR(get_target_and_lock_abspath(&target_abspath, &lock_abspath,
+ target_wcpath, ctx, pool));
+
+ if (!dry_run)
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ merge_reintegrate_locked(&conflict_report,
+ source_path_or_url, source_peg_revision,
+ target_abspath,
+ FALSE /*diff_ignore_ancestry*/,
+ dry_run, merge_options, ctx, pool, pool),
+ ctx->wc_ctx, lock_abspath, FALSE /* lock_anchor */, pool);
+ else
+ SVN_ERR(merge_reintegrate_locked(&conflict_report,
+ source_path_or_url, source_peg_revision,
+ target_abspath,
+ FALSE /*diff_ignore_ancestry*/,
+ dry_run, merge_options, ctx, pool, pool));
+
+ SVN_ERR(make_merge_conflict_error(conflict_report, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_client_merge_peg5(), which see for details.
+ *
+ * IGNORE_MERGEINFO and DIFF_IGNORE_ANCESTRY are as in do_merge().
+ */
+static svn_error_t *
+merge_peg_locked(conflict_report_t **conflict_report,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ const svn_rangelist_t *ranges_to_merge,
+ const char *target_abspath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_mergeinfo,
+ svn_boolean_t diff_ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ svn_boolean_t allow_mixed_rev,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ merge_target_t *target;
+ svn_client__pathrev_t *source_loc;
+ apr_array_header_t *merge_sources;
+ svn_ra_session_t *ra_session;
+ apr_pool_t *sesspool;
+ svn_boolean_t use_sleep = FALSE;
+ svn_error_t *err;
+ svn_boolean_t same_repos;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
+
+ SVN_ERR(open_target_wc(&target, target_abspath,
+ allow_mixed_rev, TRUE, TRUE,
+ ctx, scratch_pool, scratch_pool));
+
+ /* Create a short lived session pool */
+ sesspool = svn_pool_create(scratch_pool);
+
+ /* Open an RA session to our source URL, and determine its root URL. */
+ SVN_ERR(svn_client__ra_session_from_path2(
+ &ra_session, &source_loc,
+ source_path_or_url, NULL, source_peg_revision, source_peg_revision,
+ ctx, sesspool));
+
+ /* Normalize our merge sources. */
+ SVN_ERR(normalize_merge_sources(&merge_sources, source_path_or_url,
+ source_loc,
+ ranges_to_merge, ra_session, ctx,
+ scratch_pool, scratch_pool));
+
+ /* Check for same_repos. */
+ same_repos = is_same_repos(&target->loc, source_loc, TRUE /* strict_urls */);
+
+ /* Do the real merge! (We say with confidence that our merge
+ sources are both ancestral and related.) */
+ err = do_merge(NULL, NULL, conflict_report, &use_sleep,
+ merge_sources, target, ra_session,
+ TRUE /*sources_related*/, same_repos, ignore_mergeinfo,
+ diff_ignore_ancestry, force_delete, dry_run,
+ record_only, NULL, FALSE, FALSE, depth, merge_options,
+ ctx, result_pool, scratch_pool);
+
+ /* We're done with our RA session. */
+ svn_pool_destroy(sesspool);
+
+ if (use_sleep)
+ svn_io_sleep_for_timestamps(target_abspath, scratch_pool);
+
+ SVN_ERR(err);
+ return SVN_NO_ERROR;
+}
+
+/* Details of an automatic merge. */
+typedef struct automatic_merge_t
+{
+ svn_client__pathrev_t *yca, *base, *right, *target;
+ svn_boolean_t is_reintegrate_like;
+ svn_boolean_t allow_mixed_rev, allow_local_mods, allow_switched_subtrees;
+} automatic_merge_t;
+
+static svn_error_t *
+client_find_automatic_merge(automatic_merge_t **merge_p,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_abspath,
+ svn_boolean_t allow_mixed_rev,
+ svn_boolean_t allow_local_mods,
+ svn_boolean_t allow_switched_subtrees,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+do_automatic_merge_locked(conflict_report_t **conflict_report,
+ const automatic_merge_t *merge,
+ const char *target_abspath,
+ svn_depth_t depth,
+ svn_boolean_t diff_ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_client_merge_peg5(const char *source_path_or_url,
+ const apr_array_header_t *ranges_to_merge,
+ const svn_opt_revision_t *source_peg_revision,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_mergeinfo,
+ svn_boolean_t diff_ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ svn_boolean_t allow_mixed_rev,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *target_abspath, *lock_abspath;
+ conflict_report_t *conflict_report;
+
+ /* No ranges to merge? No problem. */
+ if (ranges_to_merge != NULL && ranges_to_merge->nelts == 0)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(get_target_and_lock_abspath(&target_abspath, &lock_abspath,
+ target_wcpath, ctx, pool));
+
+ /* Do an automatic merge if no revision ranges are specified. */
+ if (ranges_to_merge == NULL)
+ {
+ automatic_merge_t *merge;
+
+ if (ignore_mergeinfo)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Cannot merge automatically while "
+ "ignoring mergeinfo"));
+
+ /* Find the details of the merge needed. */
+ SVN_ERR(client_find_automatic_merge(
+ &merge,
+ source_path_or_url, source_peg_revision,
+ target_abspath,
+ allow_mixed_rev,
+ TRUE /*allow_local_mods*/,
+ TRUE /*allow_switched_subtrees*/,
+ ctx, pool, pool));
+
+ if (!dry_run)
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ do_automatic_merge_locked(&conflict_report,
+ merge,
+ target_abspath, depth,
+ diff_ignore_ancestry,
+ force_delete, record_only, dry_run,
+ merge_options, ctx, pool, pool),
+ ctx->wc_ctx, lock_abspath, FALSE /* lock_anchor */, pool);
+ else
+ SVN_ERR(do_automatic_merge_locked(&conflict_report,
+ merge,
+ target_abspath, depth,
+ diff_ignore_ancestry,
+ force_delete, record_only, dry_run,
+ merge_options, ctx, pool, pool));
+ }
+ else if (!dry_run)
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ merge_peg_locked(&conflict_report,
+ source_path_or_url, source_peg_revision,
+ ranges_to_merge,
+ target_abspath, depth, ignore_mergeinfo,
+ diff_ignore_ancestry,
+ force_delete, record_only, dry_run,
+ allow_mixed_rev, merge_options, ctx, pool, pool),
+ ctx->wc_ctx, lock_abspath, FALSE /* lock_anchor */, pool);
+ else
+ SVN_ERR(merge_peg_locked(&conflict_report,
+ source_path_or_url, source_peg_revision,
+ ranges_to_merge,
+ target_abspath, depth, ignore_mergeinfo,
+ diff_ignore_ancestry,
+ force_delete, record_only, dry_run,
+ allow_mixed_rev, merge_options, ctx, pool, pool));
+
+ SVN_ERR(make_merge_conflict_error(conflict_report, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* The location-history of a branch.
+ *
+ * This structure holds the set of path-revisions occupied by a branch,
+ * from an externally chosen 'tip' location back to its origin. The
+ * 'tip' location is the youngest location that we are considering on
+ * the branch. */
+typedef struct branch_history_t
+{
+ /* The tip location of the branch. That is, the youngest location that's
+ * in the repository and that we're considering. If we're considering a
+ * target branch right up to an uncommitted WC, then this is the WC base
+ * (pristine) location. */
+ svn_client__pathrev_t *tip;
+ /* The location-segment history, as mergeinfo. */
+ svn_mergeinfo_t history;
+ /* Whether the location-segment history reached as far as (necessarily
+ the root path in) revision 0 -- a fact that can't be represented as
+ mergeinfo. */
+ svn_boolean_t has_r0_history;
+} branch_history_t;
+
+/* Return the location on BRANCH_HISTORY at revision REV, or NULL if none. */
+static svn_client__pathrev_t *
+location_on_branch_at_rev(const branch_history_t *branch_history,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, branch_history->history); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *fspath = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+ int i;
+
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_merge_range_t *r = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ if (r->start < rev && rev <= r->end)
+ {
+ return svn_client__pathrev_create_with_relpath(
+ branch_history->tip->repos_root_url,
+ branch_history->tip->repos_uuid,
+ rev, fspath + 1, result_pool);
+ }
+ }
+ }
+ return NULL;
+}
+
+/* */
+typedef struct source_and_target_t
+{
+ svn_client__pathrev_t *source;
+ svn_ra_session_t *source_ra_session;
+ branch_history_t source_branch;
+
+ merge_target_t *target;
+ svn_ra_session_t *target_ra_session;
+ branch_history_t target_branch;
+
+ /* Repos location of the youngest common ancestor of SOURCE and TARGET. */
+ svn_client__pathrev_t *yca;
+} source_and_target_t;
+
+/* Set *INTERSECTION_P to the intersection of BRANCH_HISTORY with the
+ * revision range OLDEST_REV to YOUNGEST_REV (inclusive).
+ *
+ * If the intersection is empty, the result will be a branch history object
+ * containing an empty (not null) history.
+ *
+ * ### The 'tip' of the result is currently unchanged.
+ */
+static svn_error_t *
+branch_history_intersect_range(branch_history_t **intersection_p,
+ const branch_history_t *branch_history,
+ svn_revnum_t oldest_rev,
+ svn_revnum_t youngest_rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ branch_history_t *result = apr_palloc(result_pool, sizeof(*result));
+
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(oldest_rev));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
+ SVN_ERR_ASSERT(oldest_rev >= 1);
+ /* Allow a just-empty range (oldest = youngest + 1) but not an
+ * arbitrary reverse range (such as oldest = youngest + 2). */
+ SVN_ERR_ASSERT(oldest_rev <= youngest_rev + 1);
+
+ if (oldest_rev <= youngest_rev)
+ {
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &result->history, branch_history->history,
+ youngest_rev, oldest_rev - 1, TRUE /* include_range */,
+ result_pool, scratch_pool));
+ result->history = svn_mergeinfo_dup(result->history, result_pool);
+ }
+ else
+ {
+ result->history = apr_hash_make(result_pool);
+ }
+ result->has_r0_history = FALSE;
+
+ /* ### TODO: Set RESULT->tip to the tip of the intersection. */
+ result->tip = svn_client__pathrev_dup(branch_history->tip, result_pool);
+
+ *intersection_p = result;
+ return SVN_NO_ERROR;
+}
+
+/* Set *OLDEST_P and *YOUNGEST_P to the oldest and youngest locations
+ * (inclusive) along BRANCH. OLDEST_P and/or YOUNGEST_P may be NULL if not
+ * wanted.
+ */
+static svn_error_t *
+branch_history_get_endpoints(svn_client__pathrev_t **oldest_p,
+ svn_client__pathrev_t **youngest_p,
+ const branch_history_t *branch,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_revnum_t youngest_rev, oldest_rev;
+
+ SVN_ERR(svn_mergeinfo__get_range_endpoints(
+ &youngest_rev, &oldest_rev,
+ branch->history, scratch_pool));
+ if (oldest_p)
+ *oldest_p = location_on_branch_at_rev(
+ branch, oldest_rev + 1, result_pool, scratch_pool);
+ if (youngest_p)
+ *youngest_p = location_on_branch_at_rev(
+ branch, youngest_rev, result_pool, scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Implements the svn_log_entry_receiver_t interface.
+
+ Set *BATON to LOG_ENTRY->revision and return SVN_ERR_CEASE_INVOCATION. */
+static svn_error_t *
+operative_rev_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ svn_revnum_t *operative_rev = baton;
+
+ *operative_rev = log_entry->revision;
+
+ /* We've found the youngest merged or oldest eligible revision, so
+ we're done...
+
+ ...but wait, shouldn't we care if LOG_ENTRY->NON_INHERITABLE is
+ true? Because if it is, then LOG_ENTRY->REVISION is only
+ partially merged/elgibile! And our only caller,
+ find_last_merged_location (via short_circuit_mergeinfo_log) is
+ interested in *fully* merged revisions. That's all true, but if
+ find_last_merged_location() finds the youngest merged revision it
+ will also check for the oldest eligible revision. So in the case
+ the youngest merged rev is non-inheritable, the *same* non-inheritable
+ rev will be found as the oldest eligible rev -- and
+ find_last_merged_location() handles that situation. */
+ return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
+}
+
+/* Wrapper around svn_client_mergeinfo_log2. All arguments are as per
+ that API. The discover_changed_paths, depth, and revprops args to
+ svn_client_mergeinfo_log2 are always TRUE, svn_depth_infinity_t,
+ and NULL respectively.
+
+ If RECEIVER raises a SVN_ERR_CEASE_INVOCATION error, but still sets
+ *REVISION to a valid revnum, then clear the error. Otherwise return
+ any error. */
+static svn_error_t*
+short_circuit_mergeinfo_log(svn_boolean_t finding_merged,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_peg_revision,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ const svn_opt_revision_t *source_start_revision,
+ const svn_opt_revision_t *source_end_revision,
+ svn_log_entry_receiver_t receiver,
+ svn_revnum_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = svn_client_mergeinfo_log2(finding_merged,
+ target_path_or_url,
+ target_peg_revision,
+ source_path_or_url,
+ source_peg_revision,
+ source_start_revision,
+ source_end_revision,
+ receiver, revision,
+ TRUE, svn_depth_infinity,
+ NULL, ctx, scratch_pool);
+
+ if (err)
+ {
+ /* We expect RECEIVER to short-circuit the (potentially expensive) log
+ by raising an SVN_ERR_CEASE_INVOCATION -- see operative_rev_receiver.
+ So we can ignore that error, but only as long as we actually found a
+ valid revision. */
+ if (SVN_IS_VALID_REVNUM(*revision)
+ && err->apr_err == SVN_ERR_CEASE_INVOCATION)
+ {
+ svn_error_clear(err);
+ err = NULL;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Set *BASE_P to the last location on SOURCE_BRANCH such that all changes
+ * on SOURCE_BRANCH after YCA up to and including *BASE_P have already
+ * been fully merged into TARGET.
+ *
+ * *BASE_P TIP
+ * o-------o-----------o--- SOURCE_BRANCH
+ * / \
+ * -----o prev. \
+ * YCA \ merges \
+ * o-----------o----------- TARGET branch
+ *
+ * In terms of mergeinfo:
+ *
+ * Source a--... o=change, -=no-op revision
+ * branch / \
+ * YCA --> o a---o---o---o---o--- d=delete, a=add-as-a-copy
+ *
+ * Eligible -.eee.eeeeeeeeeeeeeeeeeeee .=not a source branch location
+ *
+ * Tgt-mi -.mmm.mm-mm-------m------- m=merged to root of TARGET or
+ * subtree of TARGET with no
+ * operative changes outside of that
+ * subtree, -=not merged
+ *
+ * Eligible -.---.--e--eeeeeee-eeeeeee
+ *
+ * Next --------^----------------- BASE is just before here.
+ *
+ * / \
+ * -----o prev. \
+ * YCA \ merges \
+ * o-----------o-------------
+ *
+ * If no revisions from SOURCE_BRANCH have been completely merged to TARGET,
+ * then set *BASE_P to the YCA.
+ */
+static svn_error_t *
+find_last_merged_location(svn_client__pathrev_t **base_p,
+ svn_client__pathrev_t *yca,
+ const branch_history_t *source_branch,
+ svn_client__pathrev_t *target,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_opt_revision_t source_peg_rev, source_start_rev, source_end_rev,
+ target_opt_rev;
+ svn_revnum_t youngest_merged_rev = SVN_INVALID_REVNUM;
+
+ source_peg_rev.kind = svn_opt_revision_number;
+ source_peg_rev.value.number = source_branch->tip->rev;
+ source_start_rev.kind = svn_opt_revision_number;
+ source_start_rev.value.number = yca->rev;
+ source_end_rev.kind = svn_opt_revision_number;
+ source_end_rev.value.number = source_branch->tip->rev;
+ target_opt_rev.kind = svn_opt_revision_number;
+ target_opt_rev.value.number = target->rev;
+
+ /* Find the youngest revision fully merged from SOURCE_BRANCH to TARGET,
+ if such a revision exists. */
+ SVN_ERR(short_circuit_mergeinfo_log(TRUE, /* Find merged */
+ target->url, &target_opt_rev,
+ source_branch->tip->url,
+ &source_peg_rev,
+ &source_end_rev, &source_start_rev,
+ operative_rev_receiver,
+ &youngest_merged_rev,
+ ctx, scratch_pool));
+
+ if (!SVN_IS_VALID_REVNUM(youngest_merged_rev))
+ {
+ /* No revisions have been completely merged from SOURCE_BRANCH to
+ TARGET so the base for the next merge is the YCA. */
+ *base_p = yca;
+ }
+ else
+ {
+ /* One or more revisions have already been completely merged from
+ SOURCE_BRANCH to TARGET, now find the oldest revision, older
+ than the youngest merged revision, which is still eligible to
+ be merged, if such exists. */
+ branch_history_t *contiguous_source;
+ svn_revnum_t base_rev;
+ svn_revnum_t oldest_eligible_rev = SVN_INVALID_REVNUM;
+
+ /* If the only revisions eligible are younger than the youngest merged
+ revision we can simply assume that the youngest eligible revision
+ is the youngest merged revision. Obviously this may not be true!
+ The revisions between the youngest merged revision and the tip of
+ the branch may have several inoperative revisions -- they may *all*
+ be inoperative revisions! But for the purpose of this function
+ (i.e. finding the youngest revision after the YCA where all revs have
+ been merged) that doesn't matter. */
+ source_end_rev.value.number = youngest_merged_rev;
+ SVN_ERR(short_circuit_mergeinfo_log(FALSE, /* Find eligible */
+ target->url, &target_opt_rev,
+ source_branch->tip->url,
+ &source_peg_rev,
+ &source_start_rev, &source_end_rev,
+ operative_rev_receiver,
+ &oldest_eligible_rev,
+ ctx, scratch_pool));
+
+ /* If there are revisions eligible for merging, use the oldest one
+ to calculate the base. Otherwise there are no operative revisions
+ to merge and we can simple set the base to the youngest revision
+ already merged. */
+ if (SVN_IS_VALID_REVNUM(oldest_eligible_rev))
+ base_rev = oldest_eligible_rev - 1;
+ else
+ base_rev = youngest_merged_rev;
+
+ /* Find the branch location just before the oldest eligible rev.
+ (We can't just use the base revs calculated above because the branch
+ might have a gap there.) */
+ SVN_ERR(branch_history_intersect_range(&contiguous_source,
+ source_branch, yca->rev,
+ base_rev,
+ scratch_pool, scratch_pool));
+ SVN_ERR(branch_history_get_endpoints(NULL, base_p, contiguous_source,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Find a merge base location on the target branch, like in a sync
+ * merge.
+ *
+ * BASE S_T->source
+ * o-------o-----------o---
+ * / \ \
+ * -----o prev. \ \ this
+ * YCA \ merge \ \ merge
+ * o-----------o-----------o
+ * S_T->target
+ *
+ * Set *BASE_P to BASE, the youngest location in the history of S_T->source
+ * (at or after the YCA) at which all revisions up to BASE are effectively
+ * merged into S_T->target.
+ *
+ * If no locations on the history of S_T->source are effectively merged to
+ * S_T->target, set *BASE_P to the YCA.
+ */
+static svn_error_t *
+find_base_on_source(svn_client__pathrev_t **base_p,
+ source_and_target_t *s_t,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(find_last_merged_location(base_p,
+ s_t->yca,
+ &s_t->source_branch,
+ s_t->target_branch.tip,
+ ctx, result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Find a merge base location on the target branch, like in a reintegrate
+ * merge.
+ *
+ * S_T->source
+ * o-----------o-------o---
+ * / prev. / \
+ * -----o merge / \ this
+ * YCA \ / \ merge
+ * o-------o---------------o
+ * BASE S_T->target
+ *
+ * Set *BASE_P to BASE, the youngest location in the history of S_T->target
+ * (at or after the YCA) at which all revisions up to BASE are effectively
+ * merged into S_T->source.
+ *
+ * If no locations on the history of S_T->target are effectively merged to
+ * S_T->source, set *BASE_P to the YCA.
+ */
+static svn_error_t *
+find_base_on_target(svn_client__pathrev_t **base_p,
+ source_and_target_t *s_t,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(find_last_merged_location(base_p,
+ s_t->yca,
+ &s_t->target_branch,
+ s_t->source,
+ ctx, result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of client_find_automatic_merge(), which see.
+ */
+static svn_error_t *
+find_automatic_merge(svn_client__pathrev_t **base_p,
+ svn_boolean_t *is_reintegrate_like,
+ source_and_target_t *s_t,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__pathrev_t *base_on_source, *base_on_target;
+
+ /* Get the location-history of each branch. */
+ s_t->source_branch.tip = s_t->source;
+ SVN_ERR(svn_client__get_history_as_mergeinfo(
+ &s_t->source_branch.history, &s_t->source_branch.has_r0_history,
+ s_t->source, SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
+ s_t->source_ra_session, ctx, scratch_pool));
+ s_t->target_branch.tip = &s_t->target->loc;
+ SVN_ERR(svn_client__get_history_as_mergeinfo(
+ &s_t->target_branch.history, &s_t->target_branch.has_r0_history,
+ &s_t->target->loc, SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
+ s_t->target_ra_session, ctx, scratch_pool));
+
+ SVN_ERR(svn_client__get_youngest_common_ancestor(
+ &s_t->yca, s_t->source, &s_t->target->loc, s_t->source_ra_session,
+ ctx, result_pool, result_pool));
+ if (! s_t->yca)
+ return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+ _("'%s@%ld' must be ancestrally related to "
+ "'%s@%ld'"),
+ s_t->source->url, s_t->source->rev,
+ s_t->target->loc.url, s_t->target->loc.rev);
+
+ /* Find the latest revision of A synced to B and the latest
+ * revision of B synced to A.
+ *
+ * base_on_source = youngest_complete_synced_point(source, target)
+ * base_on_target = youngest_complete_synced_point(target, source)
+ */
+ SVN_ERR(find_base_on_source(&base_on_source, s_t,
+ ctx, scratch_pool, scratch_pool));
+ SVN_ERR(find_base_on_target(&base_on_target, s_t,
+ ctx, scratch_pool, scratch_pool));
+
+ /* Choose a base. */
+ if (base_on_source->rev >= base_on_target->rev)
+ {
+ *base_p = base_on_source;
+ *is_reintegrate_like = FALSE;
+ }
+ else
+ {
+ *base_p = base_on_target;
+ *is_reintegrate_like = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/** Find out what kind of automatic merge would be needed, when the target
+ * is only known as a repository location rather than a WC.
+ *
+ * Like find_automatic_merge() except that the target is
+ * specified by @a target_path_or_url at @a target_revision, which must
+ * refer to a repository location, instead of by a WC path argument.
+ */
+static svn_error_t *
+find_automatic_merge_no_wc(automatic_merge_t **merge_p,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ source_and_target_t *s_t = apr_palloc(scratch_pool, sizeof(*s_t));
+ svn_client__pathrev_t *target_loc;
+ automatic_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
+
+ /* Source */
+ SVN_ERR(svn_client__ra_session_from_path2(
+ &s_t->source_ra_session, &s_t->source,
+ source_path_or_url, NULL, source_revision, source_revision,
+ ctx, result_pool));
+
+ /* Target */
+ SVN_ERR(svn_client__ra_session_from_path2(
+ &s_t->target_ra_session, &target_loc,
+ target_path_or_url, NULL, target_revision, target_revision,
+ ctx, result_pool));
+ s_t->target = apr_palloc(scratch_pool, sizeof(*s_t->target));
+ s_t->target->abspath = NULL; /* indicate the target is not a WC */
+ s_t->target->loc = *target_loc;
+
+ SVN_ERR(find_automatic_merge(&merge->base, &merge->is_reintegrate_like, s_t,
+ ctx, result_pool, scratch_pool));
+
+ merge->right = s_t->source;
+ merge->target = &s_t->target->loc;
+ merge->yca = s_t->yca;
+ *merge_p = merge;
+
+ return SVN_NO_ERROR;
+}
+
+/* Find the information needed to merge all unmerged changes from a source
+ * branch into a target branch.
+ *
+ * Set @a *merge_p to the information needed to merge all unmerged changes
+ * (up to @a source_revision) from the source branch @a source_path_or_url
+ * at @a source_revision into the target WC at @a target_abspath.
+ *
+ * The flags @a allow_mixed_rev, @a allow_local_mods and
+ * @a allow_switched_subtrees enable merging into a WC that is in any or all
+ * of the states described by their names, but only if this function decides
+ * that the merge will be in the same direction as the last automatic merge.
+ * If, on the other hand, the last automatic merge was in the opposite
+ * direction, then such states of the WC are not allowed regardless
+ * of these flags. This function merely records these flags in the
+ * @a *merge_p structure; do_automatic_merge_locked() checks the WC
+ * state for compliance.
+ *
+ * Allocate the @a *merge_p structure in @a result_pool.
+ */
+static svn_error_t *
+client_find_automatic_merge(automatic_merge_t **merge_p,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_abspath,
+ svn_boolean_t allow_mixed_rev,
+ svn_boolean_t allow_local_mods,
+ svn_boolean_t allow_switched_subtrees,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ source_and_target_t *s_t = apr_palloc(result_pool, sizeof(*s_t));
+ automatic_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
+
+ /* "Open" the target WC. Check the target WC for mixed-rev, local mods and
+ * switched subtrees yet to faster exit and notify user before contacting
+ * with server. After we find out what kind of merge is required, then if a
+ * reintegrate-like merge is required we'll do the stricter checks, in
+ * do_automatic_merge_locked(). */
+ SVN_ERR(open_target_wc(&s_t->target, target_abspath,
+ allow_mixed_rev,
+ allow_local_mods,
+ allow_switched_subtrees,
+ ctx, result_pool, scratch_pool));
+
+ /* Open RA sessions to the source and target trees. */
+ SVN_ERR(svn_client_open_ra_session2(&s_t->target_ra_session,
+ s_t->target->loc.url,
+ s_t->target->abspath,
+ ctx, result_pool, scratch_pool));
+ /* ### check for null URL (i.e. added path) here, like in reintegrate? */
+ SVN_ERR(svn_client__ra_session_from_path2(
+ &s_t->source_ra_session, &s_t->source,
+ source_path_or_url, NULL, source_revision, source_revision,
+ ctx, result_pool));
+
+ /* Check source is in same repos as target. */
+ SVN_ERR(check_same_repos(s_t->source, source_path_or_url,
+ &s_t->target->loc, target_abspath,
+ TRUE /* strict_urls */, scratch_pool));
+
+ SVN_ERR(find_automatic_merge(&merge->base, &merge->is_reintegrate_like, s_t,
+ ctx, result_pool, scratch_pool));
+ merge->yca = s_t->yca;
+ merge->right = s_t->source;
+ merge->allow_mixed_rev = allow_mixed_rev;
+ merge->allow_local_mods = allow_local_mods;
+ merge->allow_switched_subtrees = allow_switched_subtrees;
+
+ *merge_p = merge;
+
+ /* TODO: Close the source and target sessions here? */
+
+ return SVN_NO_ERROR;
+}
+
+/* Perform an automatic merge, given the information in MERGE which
+ * must have come from calling client_find_automatic_merge().
+ *
+ * Four locations are inputs: YCA, BASE, RIGHT, TARGET, as shown
+ * depending on whether the base is on the source branch or the target
+ * branch of this merge.
+ *
+ * RIGHT (is_reintegrate_like)
+ * o-----------o-------o---
+ * / prev. / \
+ * -----o merge / \ this
+ * YCA \ / \ merge
+ * o-------o---------------o
+ * BASE TARGET
+ *
+ * or
+ *
+ * BASE RIGHT (! is_reintegrate_like)
+ * o-------o-----------o---
+ * / \ \
+ * -----o prev. \ \ this
+ * YCA \ merge \ \ merge
+ * o-----------o-----------o
+ * TARGET
+ *
+ * ### TODO: The reintegrate-like code path does not yet
+ * eliminate already-cherry-picked revisions from the source.
+ */
+static svn_error_t *
+do_automatic_merge_locked(conflict_report_t **conflict_report,
+ const automatic_merge_t *merge,
+ const char *target_abspath,
+ svn_depth_t depth,
+ svn_boolean_t diff_ignore_ancestry,
+ svn_boolean_t force_delete,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ merge_target_t *target;
+ svn_boolean_t reintegrate_like = merge->is_reintegrate_like;
+ svn_boolean_t use_sleep = FALSE;
+ svn_error_t *err;
+
+ SVN_ERR(open_target_wc(&target, target_abspath,
+ merge->allow_mixed_rev && ! reintegrate_like,
+ merge->allow_local_mods && ! reintegrate_like,
+ merge->allow_switched_subtrees && ! reintegrate_like,
+ ctx, scratch_pool, scratch_pool));
+
+ if (reintegrate_like)
+ {
+ merge_source_t source;
+ svn_ra_session_t *base_ra_session = NULL;
+ svn_ra_session_t *right_ra_session = NULL;
+ svn_ra_session_t *target_ra_session = NULL;
+
+ if (record_only)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("The required merge is reintegrate-like, "
+ "and the record-only option "
+ "cannot be used with this kind of merge"));
+
+ if (depth != svn_depth_unknown)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("The required merge is reintegrate-like, "
+ "and the depth option "
+ "cannot be used with this kind of merge"));
+
+ if (force_delete)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("The required merge is reintegrate-like, "
+ "and the force_delete option "
+ "cannot be used with this kind of merge"));
+
+ SVN_ERR(ensure_ra_session_url(&base_ra_session, merge->base->url,
+ target->abspath, ctx, scratch_pool));
+ SVN_ERR(ensure_ra_session_url(&right_ra_session, merge->right->url,
+ target->abspath, ctx, scratch_pool));
+ SVN_ERR(ensure_ra_session_url(&target_ra_session, target->loc.url,
+ target->abspath, ctx, scratch_pool));
+
+ /* Check for and reject any abnormalities -- such as revisions that
+ * have not yet been merged in the opposite direction -- that a
+ * 'reintegrate' merge would have rejected. */
+ {
+ merge_source_t *source2;
+
+ SVN_ERR(find_reintegrate_merge(&source2, NULL,
+ right_ra_session, merge->right,
+ target_ra_session, target,
+ ctx, scratch_pool, scratch_pool));
+ }
+
+ source.loc1 = merge->base;
+ source.loc2 = merge->right;
+ source.ancestral = ! merge->is_reintegrate_like;
+
+ err = merge_cousins_and_supplement_mergeinfo(conflict_report,
+ &use_sleep,
+ target,
+ base_ra_session,
+ right_ra_session,
+ &source, merge->yca,
+ TRUE /* same_repos */,
+ depth,
+ FALSE /*diff_ignore_ancestry*/,
+ force_delete, record_only,
+ dry_run,
+ merge_options,
+ ctx,
+ result_pool, scratch_pool);
+ }
+ else /* ! merge->is_reintegrate_like */
+ {
+ /* Ignoring the base that we found, we pass the YCA instead and let
+ do_merge() work out which subtrees need which revision ranges to
+ be merged. This enables do_merge() to fill in revision-range
+ gaps that are older than the base that we calculated (which is
+ for the root path of the merge).
+
+ An improvement would be to change find_automatic_merge() to
+ find the base for each sutree, and then here use the oldest base
+ among all subtrees. */
+ apr_array_header_t *merge_sources;
+ svn_ra_session_t *ra_session = NULL;
+
+ /* Normalize our merge sources, do_merge() requires this. See the
+ 'MERGEINFO MERGE SOURCE NORMALIZATION' global comment. */
+ SVN_ERR(ensure_ra_session_url(&ra_session, merge->right->url,
+ target->abspath, ctx, scratch_pool));
+ SVN_ERR(normalize_merge_sources_internal(
+ &merge_sources, merge->right,
+ svn_rangelist__initialize(merge->yca->rev, merge->right->rev, TRUE,
+ scratch_pool),
+ ra_session, ctx, scratch_pool, scratch_pool));
+
+ err = do_merge(NULL, NULL, conflict_report, &use_sleep,
+ merge_sources, target, ra_session,
+ TRUE /*related*/, TRUE /*same_repos*/,
+ FALSE /*ignore_mergeinfo*/, diff_ignore_ancestry,
+ force_delete, dry_run,
+ record_only, NULL, FALSE, FALSE, depth, merge_options,
+ ctx, result_pool, scratch_pool);
+ }
+
+ if (use_sleep)
+ svn_io_sleep_for_timestamps(target_abspath, scratch_pool);
+
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_get_merging_summary(svn_boolean_t *needs_reintegration,
+ const char **yca_url, svn_revnum_t *yca_rev,
+ const char **base_url, svn_revnum_t *base_rev,
+ const char **right_url, svn_revnum_t *right_rev,
+ const char **target_url, svn_revnum_t *target_rev,
+ const char **repos_root_url,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t target_is_wc;
+ automatic_merge_t *merge;
+
+ target_is_wc = (! svn_path_is_url(target_path_or_url))
+ && (target_revision->kind == svn_opt_revision_unspecified
+ || target_revision->kind == svn_opt_revision_working);
+ if (target_is_wc)
+ SVN_ERR(client_find_automatic_merge(
+ &merge,
+ source_path_or_url, source_revision,
+ target_path_or_url,
+ TRUE, TRUE, TRUE, /* allow_* */
+ ctx, scratch_pool, scratch_pool));
+ else
+ SVN_ERR(find_automatic_merge_no_wc(
+ &merge,
+ source_path_or_url, source_revision,
+ target_path_or_url, target_revision,
+ ctx, scratch_pool, scratch_pool));
+
+ if (needs_reintegration)
+ *needs_reintegration = merge->is_reintegrate_like;
+ if (yca_url)
+ *yca_url = apr_pstrdup(result_pool, merge->yca->url);
+ if (yca_rev)
+ *yca_rev = merge->yca->rev;
+ if (base_url)
+ *base_url = apr_pstrdup(result_pool, merge->base->url);
+ if (base_rev)
+ *base_rev = merge->base->rev;
+ if (right_url)
+ *right_url = apr_pstrdup(result_pool, merge->right->url);
+ if (right_rev)
+ *right_rev = merge->right->rev;
+ if (target_url)
+ *target_url = apr_pstrdup(result_pool, merge->target->url);
+ if (target_rev)
+ *target_rev = merge->target->rev;
+ if (repos_root_url)
+ *repos_root_url = apr_pstrdup(result_pool, merge->yca->repos_root_url);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/mergeinfo.c b/contrib/subversion/subversion/libsvn_client/mergeinfo.c
new file mode 100644
index 0000000..453cc66
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/mergeinfo.c
@@ -0,0 +1,2191 @@
+/*
+ * mergeinfo.c : merge history functions for the libsvn_client library
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_string.h"
+#include "svn_opt.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "svn_sorts.h"
+#include "svn_ra.h"
+#include "svn_client.h"
+#include "svn_hash.h"
+
+#include "private/svn_opt_private.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_fspath.h"
+#include "private/svn_client_private.h"
+#include "client.h"
+#include "mergeinfo.h"
+#include "svn_private_config.h"
+
+
+
+svn_client__merge_path_t *
+svn_client__merge_path_dup(const svn_client__merge_path_t *old,
+ apr_pool_t *pool)
+{
+ svn_client__merge_path_t *new = apr_pmemdup(pool, old, sizeof(*old));
+
+ new->abspath = apr_pstrdup(pool, old->abspath);
+ if (new->remaining_ranges)
+ new->remaining_ranges = svn_rangelist_dup(old->remaining_ranges, pool);
+ if (new->pre_merge_mergeinfo)
+ new->pre_merge_mergeinfo = svn_mergeinfo_dup(old->pre_merge_mergeinfo,
+ pool);
+ if (new->implicit_mergeinfo)
+ new->implicit_mergeinfo = svn_mergeinfo_dup(old->implicit_mergeinfo,
+ pool);
+
+ return new;
+}
+
+svn_client__merge_path_t *
+svn_client__merge_path_create(const char *abspath,
+ apr_pool_t *pool)
+{
+ svn_client__merge_path_t *result = apr_pcalloc(pool, sizeof(*result));
+
+ result->abspath = apr_pstrdup(pool, abspath);
+ return result;
+}
+
+svn_error_t *
+svn_client__parse_mergeinfo(svn_mergeinfo_t *mergeinfo,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_string_t *propval;
+
+ *mergeinfo = NULL;
+
+ /* ### Use svn_wc_prop_get() would actually be sufficient for now.
+ ### DannyB thinks that later we'll need behavior more like
+ ### svn_client__get_prop_from_wc(). */
+ SVN_ERR(svn_wc_prop_get2(&propval, wc_ctx, local_abspath, SVN_PROP_MERGEINFO,
+ scratch_pool, scratch_pool));
+ if (propval)
+ SVN_ERR(svn_mergeinfo_parse(mergeinfo, propval->data, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__record_wc_mergeinfo(const char *local_abspath,
+ svn_mergeinfo_t mergeinfo,
+ svn_boolean_t do_notification,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_string_t *mergeinfo_str = NULL;
+ svn_boolean_t mergeinfo_changes = FALSE;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Convert MERGEINFO (if any) into text for storage as a property value. */
+ if (mergeinfo)
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_str, mergeinfo, scratch_pool));
+
+ if (do_notification && ctx->notify_func2)
+ SVN_ERR(svn_client__mergeinfo_status(&mergeinfo_changes, ctx->wc_ctx,
+ local_abspath, scratch_pool));
+
+ /* Record the new mergeinfo in the WC. */
+ /* ### Later, we'll want behavior more analogous to
+ ### svn_client__get_prop_from_wc(). */
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, SVN_PROP_MERGEINFO,
+ mergeinfo_str, svn_depth_empty,
+ TRUE /* skip checks */, NULL,
+ NULL, NULL /* cancellation */,
+ NULL, NULL /* notification */,
+ scratch_pool));
+
+ if (do_notification && ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify =
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_merge_record_info,
+ scratch_pool);
+ if (mergeinfo_changes)
+ notify->prop_state = svn_wc_notify_state_merged;
+ else
+ notify->prop_state = svn_wc_notify_state_changed;
+
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__record_wc_mergeinfo_catalog(apr_hash_t *result_catalog,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ if (apr_hash_count(result_catalog))
+ {
+ int i;
+ apr_array_header_t *sorted_cat =
+ svn_sort__hash(result_catalog, svn_sort_compare_items_as_paths,
+ scratch_pool);
+
+ /* Write the mergeinfo out in sorted order of the paths (presumably just
+ * so that the notifications are in a predictable, convenient order). */
+ for (i = 0; i < sorted_cat->nelts; i++)
+ {
+ svn_sort__item_t elt = APR_ARRAY_IDX(sorted_cat, i,
+ svn_sort__item_t);
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+ err = svn_client__record_wc_mergeinfo(elt.key, elt.value, TRUE,
+ ctx, iterpool);
+
+ if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
+ {
+ /* PATH isn't just missing, it's not even versioned as far
+ as this working copy knows. But it was included in
+ MERGES, which means that the server knows about it.
+ Likely we don't have access to the source due to authz
+ restrictions. For now just clear the error and
+ continue... */
+ svn_error_clear(err);
+ }
+ else
+ {
+ SVN_ERR(err);
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/*-----------------------------------------------------------------------*/
+
+/*** Retrieving mergeinfo. ***/
+
+svn_error_t *
+svn_client__get_wc_mergeinfo(svn_mergeinfo_t *mergeinfo,
+ svn_boolean_t *inherited_p,
+ svn_mergeinfo_inheritance_t inherit,
+ const char *local_abspath,
+ const char *limit_abspath,
+ const char **walked_path,
+ svn_boolean_t ignore_invalid_mergeinfo,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *walk_relpath = "";
+ svn_mergeinfo_t wc_mergeinfo;
+ svn_revnum_t base_revision;
+ apr_pool_t *iterpool;
+ svn_boolean_t inherited;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ if (limit_abspath)
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(limit_abspath));
+
+ SVN_ERR(svn_wc__node_get_base(NULL, &base_revision, NULL, NULL, NULL, NULL,
+ ctx->wc_ctx, local_abspath,
+ TRUE /* ignore_enoent */,
+ FALSE /* show_hidden */,
+ scratch_pool, scratch_pool));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (TRUE)
+ {
+ svn_pool_clear(iterpool);
+
+ /* Don't look for explicit mergeinfo on LOCAL_ABSPATH if we are only
+ interested in inherited mergeinfo. */
+ if (inherit == svn_mergeinfo_nearest_ancestor)
+ {
+ wc_mergeinfo = NULL;
+ inherit = svn_mergeinfo_inherited;
+ }
+ else
+ {
+ /* Look for mergeinfo on LOCAL_ABSPATH. If there isn't any and we
+ want inherited mergeinfo, walk towards the root of the WC until
+ we encounter either (a) an unversioned directory, or
+ (b) mergeinfo. If we encounter (b), use that inherited
+ mergeinfo as our baseline. */
+ svn_error_t *err = svn_client__parse_mergeinfo(&wc_mergeinfo,
+ ctx->wc_ctx,
+ local_abspath,
+ result_pool,
+ iterpool);
+ if ((ignore_invalid_mergeinfo || walk_relpath [0] != '\0')
+ && err
+ && err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ svn_error_clear(err);
+ wc_mergeinfo = apr_hash_make(result_pool);
+ break;
+ }
+ else
+ {
+ SVN_ERR(err);
+ }
+ }
+
+ if (wc_mergeinfo == NULL &&
+ inherit != svn_mergeinfo_explicit &&
+ !svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+ {
+ svn_boolean_t is_wc_root;
+ svn_boolean_t is_switched;
+ svn_revnum_t parent_base_rev;
+ svn_revnum_t parent_changed_rev;
+
+ /* Don't look any higher than the limit path. */
+ if (limit_abspath && strcmp(limit_abspath, local_abspath) == 0)
+ break;
+
+ /* If we've reached the root of the working copy don't look any
+ higher. */
+ SVN_ERR(svn_wc_check_root(&is_wc_root, &is_switched, NULL,
+ ctx->wc_ctx, local_abspath, iterpool));
+ if (is_wc_root || is_switched)
+ break;
+
+ /* No explicit mergeinfo on this path. Look higher up the
+ directory tree while keeping track of what we've walked. */
+ walk_relpath = svn_relpath_join(svn_dirent_basename(local_abspath,
+ iterpool),
+ walk_relpath, result_pool);
+ local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__node_get_base(NULL, &parent_base_rev, NULL, NULL,
+ NULL, NULL,
+ ctx->wc_ctx, local_abspath,
+ TRUE, FALSE,
+ scratch_pool, scratch_pool));
+
+ /* ### This checks the WORKING changed_rev, so invalid on replacement
+ ### not even reliable in case an ancestor was copied from a
+ ### different location */
+ SVN_ERR(svn_wc__node_get_changed_info(&parent_changed_rev,
+ NULL, NULL,
+ ctx->wc_ctx, local_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ /* Look in LOCAL_ABSPATH's parent for inherited mergeinfo if
+ LOCAL_ABSPATH has no base revision because it is an uncommitted
+ addition, or if its base revision falls within the inclusive
+ range of its parent's last changed revision to the parent's base
+ revision; otherwise stop looking for inherited mergeinfo. */
+ if (SVN_IS_VALID_REVNUM(base_revision)
+ && (base_revision < parent_changed_rev
+ || parent_base_rev < base_revision))
+ break;
+
+ /* We haven't yet risen above the root of the WC. */
+ continue;
+ }
+ break;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ if (svn_path_is_empty(walk_relpath))
+ {
+ /* Mergeinfo is explicit. */
+ inherited = FALSE;
+ *mergeinfo = wc_mergeinfo;
+ }
+ else
+ {
+ /* Mergeinfo may be inherited. */
+ if (wc_mergeinfo)
+ {
+ inherited = TRUE;
+ SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(mergeinfo,
+ wc_mergeinfo,
+ walk_relpath,
+ result_pool,
+ scratch_pool));
+ }
+ else
+ {
+ inherited = FALSE;
+ *mergeinfo = NULL;
+ }
+ }
+
+ if (walked_path)
+ *walked_path = walk_relpath;
+
+ /* Remove non-inheritable mergeinfo and paths mapped to empty ranges
+ which may occur if WCPATH's mergeinfo is not explicit. */
+ if (inherited
+ && apr_hash_count(*mergeinfo)) /* Nothing to do for empty mergeinfo. */
+ {
+ SVN_ERR(svn_mergeinfo_inheritable2(mergeinfo, *mergeinfo, NULL,
+ SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
+ TRUE, result_pool, scratch_pool));
+ svn_mergeinfo__remove_empty_rangelists(*mergeinfo, result_pool);
+ }
+
+ if (inherited_p)
+ *inherited_p = inherited;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__get_wc_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat,
+ svn_boolean_t *inherited,
+ svn_boolean_t include_descendants,
+ svn_mergeinfo_inheritance_t inherit,
+ const char *local_abspath,
+ const char *limit_path,
+ const char **walked_path,
+ svn_boolean_t ignore_invalid_mergeinfo,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *target_repos_relpath;
+ svn_mergeinfo_t mergeinfo;
+ const char *repos_root;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ *mergeinfo_cat = NULL;
+ SVN_ERR(svn_wc__node_get_repos_info(NULL, &target_repos_relpath,
+ &repos_root, NULL,
+ ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Get the mergeinfo for the LOCAL_ABSPATH target and set *INHERITED and
+ *WALKED_PATH. */
+ SVN_ERR(svn_client__get_wc_mergeinfo(&mergeinfo, inherited, inherit,
+ local_abspath, limit_path,
+ walked_path, ignore_invalid_mergeinfo,
+ ctx, result_pool, scratch_pool));
+
+ /* Add any explicit/inherited mergeinfo for LOCAL_ABSPATH to
+ *MERGEINFO_CAT. */
+ if (mergeinfo)
+ {
+ *mergeinfo_cat = apr_hash_make(result_pool);
+ svn_hash_sets(*mergeinfo_cat,
+ apr_pstrdup(result_pool, target_repos_relpath), mergeinfo);
+ }
+
+ /* If LOCAL_ABSPATH is a directory and we want the subtree mergeinfo too,
+ then get it.
+
+ With WC-NG it is cheaper to do a single db transaction, than first
+ looking if we really have a directory. */
+ if (include_descendants)
+ {
+ apr_hash_t *mergeinfo_props;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_wc__prop_retrieve_recursive(&mergeinfo_props,
+ ctx->wc_ctx, local_abspath,
+ SVN_PROP_MERGEINFO,
+ scratch_pool, scratch_pool));
+
+ /* Convert *mergeinfo_props into a proper svn_mergeinfo_catalog_t */
+ for (hi = apr_hash_first(scratch_pool, mergeinfo_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *node_abspath = svn__apr_hash_index_key(hi);
+ svn_string_t *propval = svn__apr_hash_index_val(hi);
+ svn_mergeinfo_t subtree_mergeinfo;
+ const char *repos_relpath;
+
+ if (strcmp(node_abspath, local_abspath) == 0)
+ continue; /* Already parsed in svn_client__get_wc_mergeinfo */
+
+ SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, NULL, NULL,
+ ctx->wc_ctx, node_abspath,
+ result_pool, scratch_pool));
+
+ SVN_ERR(svn_mergeinfo_parse(&subtree_mergeinfo, propval->data,
+ result_pool));
+
+ /* If the target had no explicit/inherited mergeinfo and this is the
+ first subtree with mergeinfo found, then the catalog will still
+ be NULL. */
+ if (*mergeinfo_cat == NULL)
+ *mergeinfo_cat = apr_hash_make(result_pool);
+
+ svn_hash_sets(*mergeinfo_cat, repos_relpath, subtree_mergeinfo);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__get_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
+ svn_ra_session_t *ra_session,
+ const char *url,
+ svn_revnum_t rev,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t squelch_incapable,
+ apr_pool_t *pool)
+{
+ svn_mergeinfo_catalog_t tgt_mergeinfo_cat;
+
+ *target_mergeinfo = NULL;
+
+ SVN_ERR(svn_client__get_repos_mergeinfo_catalog(&tgt_mergeinfo_cat,
+ ra_session,
+ url, rev, inherit,
+ squelch_incapable, FALSE,
+ pool, pool));
+
+ if (tgt_mergeinfo_cat && apr_hash_count(tgt_mergeinfo_cat))
+ {
+ /* We asked only for the REL_PATH's mergeinfo, not any of its
+ descendants. So if there is anything in the catalog it is the
+ mergeinfo for REL_PATH. */
+ *target_mergeinfo =
+ svn__apr_hash_index_val(apr_hash_first(pool, tgt_mergeinfo_cat));
+
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__get_repos_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat,
+ svn_ra_session_t *ra_session,
+ const char *url,
+ svn_revnum_t rev,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t squelch_incapable,
+ svn_boolean_t include_descendants,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_mergeinfo_catalog_t repos_mergeinfo_cat;
+ apr_array_header_t *rel_paths = apr_array_make(scratch_pool, 1,
+ sizeof(const char *));
+ const char *old_session_url;
+
+ APR_ARRAY_PUSH(rel_paths, const char *) = "";
+
+ /* Fetch the mergeinfo. */
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url,
+ ra_session, url, scratch_pool));
+ err = svn_ra_get_mergeinfo(ra_session, &repos_mergeinfo_cat, rel_paths,
+ rev, inherit, include_descendants, result_pool);
+ err = svn_error_compose_create(
+ err, svn_ra_reparent(ra_session, old_session_url, scratch_pool));
+ if (err)
+ {
+ if (squelch_incapable && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
+ {
+ svn_error_clear(err);
+ *mergeinfo_cat = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ if (repos_mergeinfo_cat == NULL)
+ {
+ *mergeinfo_cat = NULL;
+ }
+ else
+ {
+ const char *session_relpath;
+
+ SVN_ERR(svn_ra_get_path_relative_to_root(ra_session, &session_relpath,
+ url, scratch_pool));
+
+ if (session_relpath[0] == '\0')
+ *mergeinfo_cat = repos_mergeinfo_cat;
+ else
+ SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(mergeinfo_cat,
+ repos_mergeinfo_cat,
+ session_relpath,
+ result_pool,
+ scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__get_wc_or_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
+ svn_boolean_t *inherited,
+ svn_boolean_t *from_repos,
+ svn_boolean_t repos_only,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_ra_session_t *ra_session,
+ const char *target_wcpath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_mergeinfo_catalog_t tgt_mergeinfo_cat;
+
+ *target_mergeinfo = NULL;
+
+ SVN_ERR(svn_client__get_wc_or_repos_mergeinfo_catalog(&tgt_mergeinfo_cat,
+ inherited, from_repos,
+ FALSE,
+ repos_only,
+ FALSE, inherit,
+ ra_session,
+ target_wcpath, ctx,
+ pool, pool));
+ if (tgt_mergeinfo_cat && apr_hash_count(tgt_mergeinfo_cat))
+ {
+ /* We asked only for the TARGET_WCPATH's mergeinfo, not any of its
+ descendants. It this mergeinfo is in the catalog, it's keyed
+ on TARGET_WCPATH's root-relative path. We could dig that up
+ so we can peek into our catalog, but it ought to be the only
+ thing in the catalog, so we'll just fetch the first hash item. */
+ *target_mergeinfo =
+ svn__apr_hash_index_val(apr_hash_first(pool, tgt_mergeinfo_cat));
+
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__get_wc_or_repos_mergeinfo_catalog(
+ svn_mergeinfo_catalog_t *target_mergeinfo_catalog,
+ svn_boolean_t *inherited_p,
+ svn_boolean_t *from_repos,
+ svn_boolean_t include_descendants,
+ svn_boolean_t repos_only,
+ svn_boolean_t ignore_invalid_mergeinfo,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_ra_session_t *ra_session,
+ const char *target_wcpath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *url;
+ svn_revnum_t target_rev;
+ const char *local_abspath;
+ const char *repos_root;
+ const char *repos_relpath;
+ svn_mergeinfo_catalog_t target_mergeinfo_cat_wc = NULL;
+ svn_mergeinfo_catalog_t target_mergeinfo_cat_repos = NULL;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, target_wcpath,
+ scratch_pool));
+
+ if (from_repos)
+ *from_repos = FALSE;
+
+ /* We may get an entry with abbreviated information from TARGET_WCPATH's
+ parent if TARGET_WCPATH is missing. These limited entries do not have
+ a URL and without that we cannot get accurate mergeinfo for
+ TARGET_WCPATH. */
+ SVN_ERR(svn_wc__node_get_origin(NULL, &target_rev, &repos_relpath,
+ &repos_root, NULL, NULL,
+ ctx->wc_ctx, local_abspath, FALSE,
+ scratch_pool, scratch_pool));
+
+ if (repos_relpath)
+ url = svn_path_url_add_component2(repos_root, repos_relpath, scratch_pool);
+ else
+ url = NULL;
+
+ if (!repos_only)
+ {
+ svn_boolean_t inherited;
+ SVN_ERR(svn_client__get_wc_mergeinfo_catalog(&target_mergeinfo_cat_wc,
+ &inherited,
+ include_descendants,
+ inherit,
+ local_abspath,
+ NULL, NULL,
+ ignore_invalid_mergeinfo,
+ ctx,
+ result_pool,
+ scratch_pool));
+ if (inherited_p)
+ *inherited_p = inherited;
+
+ /* If we want LOCAL_ABSPATH's inherited mergeinfo, were we able to
+ get it from the working copy? If not, then we must ask the
+ repository. */
+ if (! (inherited
+ || (inherit == svn_mergeinfo_explicit)
+ || (repos_relpath
+ && target_mergeinfo_cat_wc
+ && svn_hash_gets(target_mergeinfo_cat_wc, repos_relpath))))
+ {
+ repos_only = TRUE;
+ /* We already have any subtree mergeinfo from the working copy, no
+ need to ask the server for it again. */
+ include_descendants = FALSE;
+ }
+ }
+
+ if (repos_only)
+ {
+ /* No need to check the repos if this is a local addition. */
+ if (url != NULL)
+ {
+ apr_hash_t *original_props;
+
+ /* Check to see if we have local modifications which removed all of
+ TARGET_WCPATH's pristine mergeinfo. If that is the case then
+ TARGET_WCPATH effectively has no mergeinfo. */
+ SVN_ERR(svn_wc_get_pristine_props(&original_props,
+ ctx->wc_ctx, local_abspath,
+ result_pool, scratch_pool));
+ if (!svn_hash_gets(original_props, SVN_PROP_MERGEINFO))
+ {
+ apr_pool_t *sesspool = NULL;
+
+ if (! ra_session)
+ {
+ sesspool = svn_pool_create(scratch_pool);
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
+ ctx,
+ sesspool, sesspool));
+ }
+
+ SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
+ &target_mergeinfo_cat_repos, ra_session,
+ url, target_rev, inherit,
+ TRUE, include_descendants,
+ result_pool, scratch_pool));
+
+ if (target_mergeinfo_cat_repos
+ && svn_hash_gets(target_mergeinfo_cat_repos, repos_relpath))
+ {
+ if (inherited_p)
+ *inherited_p = TRUE;
+ if (from_repos)
+ *from_repos = TRUE;
+ }
+
+ /* If we created an RA_SESSION above, destroy it.
+ Otherwise, if reparented an existing session, point
+ it back where it was when we were called. */
+ if (sesspool)
+ {
+ svn_pool_destroy(sesspool);
+ }
+ }
+ }
+ }
+
+ /* Combine the mergeinfo from the working copy and repository as needed. */
+ if (target_mergeinfo_cat_wc)
+ {
+ *target_mergeinfo_catalog = target_mergeinfo_cat_wc;
+ if (target_mergeinfo_cat_repos)
+ SVN_ERR(svn_mergeinfo_catalog_merge(*target_mergeinfo_catalog,
+ target_mergeinfo_cat_repos,
+ result_pool, scratch_pool));
+ }
+ else if (target_mergeinfo_cat_repos)
+ {
+ *target_mergeinfo_catalog = target_mergeinfo_cat_repos;
+ }
+ else
+ {
+ *target_mergeinfo_catalog = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__get_history_as_mergeinfo(svn_mergeinfo_t *mergeinfo_p,
+ svn_boolean_t *has_rev_zero_history,
+ const svn_client__pathrev_t *pathrev,
+ svn_revnum_t range_youngest,
+ svn_revnum_t range_oldest,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *segments;
+
+ /* Fetch the location segments for our URL@PEG_REVNUM. */
+ if (! SVN_IS_VALID_REVNUM(range_youngest))
+ range_youngest = pathrev->rev;
+ if (! SVN_IS_VALID_REVNUM(range_oldest))
+ range_oldest = 0;
+
+ SVN_ERR(svn_client__repos_location_segments(&segments, ra_session,
+ pathrev->url, pathrev->rev,
+ range_youngest, range_oldest,
+ ctx, pool));
+
+ if (has_rev_zero_history)
+ {
+ *has_rev_zero_history = FALSE;
+ if (segments->nelts)
+ {
+ svn_location_segment_t *oldest_segment =
+ APR_ARRAY_IDX(segments, 0, svn_location_segment_t *);
+ if (oldest_segment->range_start == 0)
+ *has_rev_zero_history = TRUE;
+ }
+ }
+
+ SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(mergeinfo_p, segments, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/*-----------------------------------------------------------------------*/
+
+/*** Eliding mergeinfo. ***/
+
+/* Given the mergeinfo (CHILD_MERGEINFO) for a path, and the
+ mergeinfo of its nearest ancestor with mergeinfo (PARENT_MERGEINFO), compare
+ CHILD_MERGEINFO to PARENT_MERGEINFO to see if the former elides to
+ the latter, following the elision rules described in
+ svn_client__elide_mergeinfo()'s docstring. Set *ELIDES to whether
+ or not CHILD_MERGEINFO is redundant.
+
+ Note: This function assumes that PARENT_MERGEINFO is definitive;
+ i.e. if it is NULL then the caller not only walked the entire WC
+ looking for inherited mergeinfo, but queried the repository if none
+ was found in the WC. This is rather important since this function
+ says empty mergeinfo should be elided if PARENT_MERGEINFO is NULL,
+ and we don't want to do that unless we are *certain* that the empty
+ mergeinfo on PATH isn't overriding anything.
+
+ If PATH_SUFFIX and PARENT_MERGEINFO are not NULL append PATH_SUFFIX
+ to each path in PARENT_MERGEINFO before performing the comparison. */
+static svn_error_t *
+should_elide_mergeinfo(svn_boolean_t *elides,
+ svn_mergeinfo_t parent_mergeinfo,
+ svn_mergeinfo_t child_mergeinfo,
+ const char *path_suffix,
+ apr_pool_t *scratch_pool)
+{
+ /* Easy out: No child mergeinfo to elide. */
+ if (child_mergeinfo == NULL)
+ {
+ *elides = FALSE;
+ }
+ else if (apr_hash_count(child_mergeinfo) == 0)
+ {
+ /* Empty mergeinfo elides to empty mergeinfo or to "nothing",
+ i.e. it isn't overriding any parent. Otherwise it doesn't
+ elide. */
+ *elides = (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0);
+ }
+ else if (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0)
+ {
+ /* Non-empty mergeinfo never elides to empty mergeinfo
+ or no mergeinfo. */
+ *elides = FALSE;
+ }
+ else
+ {
+ /* Both CHILD_MERGEINFO and PARENT_MERGEINFO are non-NULL and
+ non-empty. */
+ svn_mergeinfo_t path_tweaked_parent_mergeinfo;
+
+ /* If we need to adjust the paths in PARENT_MERGEINFO do it now. */
+ if (path_suffix)
+ SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
+ &path_tweaked_parent_mergeinfo, parent_mergeinfo,
+ path_suffix, scratch_pool, scratch_pool));
+ else
+ path_tweaked_parent_mergeinfo = parent_mergeinfo;
+
+ SVN_ERR(svn_mergeinfo__equals(elides,
+ path_tweaked_parent_mergeinfo,
+ child_mergeinfo, TRUE, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_client__elide_mergeinfo().
+
+ Given a working copy LOCAL_ABSPATH, its mergeinfo hash CHILD_MERGEINFO, and
+ the mergeinfo of LOCAL_ABSPATH's nearest ancestor PARENT_MERGEINFO, use
+ should_elide_mergeinfo() to decide whether or not CHILD_MERGEINFO elides to
+ PARENT_MERGEINFO; PATH_SUFFIX means the same as in that function.
+
+ If elision does occur, then remove the mergeinfo for LOCAL_ABSPATH.
+
+ If CHILD_MERGEINFO is NULL, do nothing.
+
+ Use SCRATCH_POOL for temporary allocations.
+*/
+static svn_error_t *
+elide_mergeinfo(svn_mergeinfo_t parent_mergeinfo,
+ svn_mergeinfo_t child_mergeinfo,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t elides;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(should_elide_mergeinfo(&elides,
+ parent_mergeinfo, child_mergeinfo, NULL,
+ scratch_pool));
+
+ if (elides)
+ {
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, SVN_PROP_MERGEINFO,
+ NULL, svn_depth_empty, TRUE, NULL,
+ NULL, NULL /* cancellation */,
+ NULL, NULL /* notification */,
+ scratch_pool));
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_merge_elide_info,
+ scratch_pool);
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+
+ notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_update_update,
+ scratch_pool);
+ notify->prop_state = svn_wc_notify_state_changed;
+
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__elide_mergeinfo(const char *target_abspath,
+ const char *wc_elision_limit_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *limit_abspath = wc_elision_limit_abspath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
+ SVN_ERR_ASSERT(!wc_elision_limit_abspath || svn_dirent_is_absolute(wc_elision_limit_abspath));
+
+ /* Check for first easy out: We are already at the limit path. */
+ if (!limit_abspath
+ || strcmp(target_abspath, limit_abspath) != 0)
+ {
+ svn_mergeinfo_t target_mergeinfo;
+ svn_mergeinfo_t mergeinfo = NULL;
+ svn_boolean_t inherited;
+ const char *walk_path;
+ svn_error_t *err;
+
+ /* Get the TARGET_WCPATH's explicit mergeinfo. */
+ err = svn_client__get_wc_mergeinfo(&target_mergeinfo, &inherited,
+ svn_mergeinfo_inherited,
+ target_abspath,
+ limit_abspath,
+ &walk_path, FALSE,
+ ctx, pool, pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ /* Issue #3896: If we attempt elision because invalid
+ mergeinfo is present on TARGET_WCPATH, then don't let
+ the merge fail, just skip the elision attempt. */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ /* If TARGET_WCPATH has no explicit mergeinfo, there's nothing to
+ elide, we're done. */
+ if (inherited || target_mergeinfo == NULL)
+ return SVN_NO_ERROR;
+
+ /* Get TARGET_WCPATH's inherited mergeinfo from the WC. */
+ err = svn_client__get_wc_mergeinfo(&mergeinfo, NULL,
+ svn_mergeinfo_nearest_ancestor,
+ target_abspath,
+ limit_abspath,
+ &walk_path, FALSE, ctx, pool, pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ /* Issue #3896 again, but invalid mergeinfo is inherited. */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ /* If TARGET_WCPATH inherited no mergeinfo from the WC and we are
+ not limiting our search to the working copy then check if it
+ inherits any from the repos. */
+ if (!mergeinfo && !wc_elision_limit_abspath)
+ {
+ err = svn_client__get_wc_or_repos_mergeinfo(
+ &mergeinfo, NULL, NULL, TRUE,
+ svn_mergeinfo_nearest_ancestor,
+ NULL, target_abspath, ctx, pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ /* Issue #3896 again, but invalid mergeinfo is inherited
+ from the repository. */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+ }
+
+ /* If there is nowhere to elide TARGET_WCPATH's mergeinfo to and
+ the elision is limited, then we are done.*/
+ if (!mergeinfo && wc_elision_limit_abspath)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(elide_mergeinfo(mergeinfo, target_mergeinfo, target_abspath,
+ ctx, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *MERGEINFO_CATALOG to the explicit or inherited mergeinfo for
+ PATH_OR_URL@PEG_REVISION. If INCLUDE_DESCENDANTS is true, also
+ store in *MERGEINFO_CATALOG the explicit mergeinfo on any subtrees
+ under PATH_OR_URL. Key all mergeinfo in *MERGEINFO_CATALOG on
+ repository relpaths.
+
+ If no mergeinfo is found then set *MERGEINFO_CATALOG to NULL.
+
+ Set *REPOS_ROOT to the root URL of the repository associated with
+ PATH_OR_URL.
+
+ Allocate *MERGEINFO_CATALOG and all its contents in RESULT_POOL. Use
+ SCRATCH_POOL for all temporary allocations.
+
+ Return SVN_ERR_UNSUPPORTED_FEATURE if the server does not support
+ Merge Tracking. */
+static svn_error_t *
+get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo_catalog,
+ const char **repos_root,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ svn_boolean_t include_descendants,
+ svn_boolean_t ignore_invalid_mergeinfo,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_session_t *ra_session;
+ const char *local_abspath;
+ svn_boolean_t use_url = svn_path_is_url(path_or_url);
+ svn_client__pathrev_t *peg_loc;
+
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &peg_loc,
+ path_or_url, NULL, peg_revision,
+ peg_revision, ctx, scratch_pool));
+
+ /* If PATH_OR_URL is as working copy path determine if we will need to
+ contact the repository for the requested PEG_REVISION. */
+ if (!use_url)
+ {
+ svn_client__pathrev_t *origin;
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
+ scratch_pool));
+
+ SVN_ERR(svn_client__wc_node_get_origin(&origin, local_abspath, ctx,
+ scratch_pool, scratch_pool));
+ if (!origin
+ || strcmp(origin->url, peg_loc->url) != 0
+ || peg_loc->rev != origin->rev)
+ {
+ use_url = TRUE; /* Don't rely on local mergeinfo */
+ }
+ }
+
+ /* Check server Merge Tracking capability. */
+ SVN_ERR(svn_ra__assert_mergeinfo_capable_server(ra_session, path_or_url,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, repos_root, result_pool));
+
+ if (use_url)
+ {
+ SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
+ mergeinfo_catalog, ra_session, peg_loc->url, peg_loc->rev,
+ svn_mergeinfo_inherited, FALSE, include_descendants,
+ result_pool, scratch_pool));
+ }
+ else /* ! svn_path_is_url() */
+ {
+ SVN_ERR(svn_client__get_wc_or_repos_mergeinfo_catalog(
+ mergeinfo_catalog, NULL, NULL, include_descendants, FALSE,
+ ignore_invalid_mergeinfo, svn_mergeinfo_inherited,
+ ra_session, path_or_url, ctx,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*** In-memory mergeinfo elision ***/
+svn_error_t *
+svn_client__elide_mergeinfo_catalog(svn_mergeinfo_catalog_t mergeinfo_catalog,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *sorted_hash;
+ apr_array_header_t *elidable_paths = apr_array_make(scratch_pool, 1,
+ sizeof(const char *));
+ apr_array_header_t *dir_stack = apr_array_make(scratch_pool, 1,
+ sizeof(const char *));
+ apr_pool_t *iterpool;
+ int i;
+
+ /* Here's the general algorithm:
+ Walk through the paths sorted in tree order. For each path, pop
+ the dir_stack until it is either empty or the top item contains a parent
+ of the current path. Check to see if that mergeinfo is then elidable,
+ and build the list of elidable mergeinfo based upon that determination.
+ Finally, push the path of interest onto the stack, and continue. */
+ sorted_hash = svn_sort__hash(mergeinfo_catalog,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < sorted_hash->nelts; i++)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(sorted_hash, i,
+ svn_sort__item_t);
+ const char *path = item->key;
+
+ if (dir_stack->nelts > 0)
+ {
+ const char *top;
+ const char *path_suffix;
+ svn_boolean_t elides = FALSE;
+
+ svn_pool_clear(iterpool);
+
+ /* Pop off any paths which are not ancestors of PATH. */
+ do
+ {
+ top = APR_ARRAY_IDX(dir_stack, dir_stack->nelts - 1,
+ const char *);
+ path_suffix = svn_dirent_is_child(top, path, NULL);
+
+ if (!path_suffix)
+ apr_array_pop(dir_stack);
+ }
+ while (dir_stack->nelts > 0 && !path_suffix);
+
+ /* If we have a path suffix, it means we haven't popped the stack
+ clean. */
+ if (path_suffix)
+ {
+ SVN_ERR(should_elide_mergeinfo(&elides,
+ svn_hash_gets(mergeinfo_catalog, top),
+ svn_hash_gets(mergeinfo_catalog, path),
+ path_suffix,
+ iterpool));
+
+ if (elides)
+ APR_ARRAY_PUSH(elidable_paths, const char *) = path;
+ }
+ }
+
+ APR_ARRAY_PUSH(dir_stack, const char *) = path;
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Now remove the elidable paths from the catalog. */
+ for (i = 0; i < elidable_paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(elidable_paths, i, const char *);
+ svn_hash_sets(mergeinfo_catalog, path, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for filter_log_entry_with_rangelist().
+
+ DEPTH_FIRST_CATALOG_INDEX is an array of svn_sort__item_t's. The keys are
+ repository-absolute const char *paths, the values are svn_mergeinfo_t for
+ each path.
+
+ Return a pointer to the mergeinfo value of the nearest path-wise ancestor
+ of FSPATH in DEPTH_FIRST_CATALOG_INDEX. A path is considered its
+ own ancestor, so if a key exactly matches FSPATH, return that
+ key's mergeinfo and set *ANCESTOR_IS_SELF to true (set it to false in all
+ other cases).
+
+ If DEPTH_FIRST_CATALOG_INDEX is NULL, empty, or no ancestor is found, then
+ return NULL. */
+static svn_mergeinfo_t
+find_nearest_ancestor(const apr_array_header_t *depth_first_catalog_index,
+ svn_boolean_t *ancestor_is_self,
+ const char *fspath)
+{
+ int ancestor_index = -1;
+
+ *ancestor_is_self = FALSE;
+
+ if (depth_first_catalog_index)
+ {
+ int i;
+
+ for (i = 0; i < depth_first_catalog_index->nelts; i++)
+ {
+ svn_sort__item_t item = APR_ARRAY_IDX(depth_first_catalog_index, i,
+ svn_sort__item_t);
+ if (svn_fspath__skip_ancestor(item.key, fspath))
+ {
+ ancestor_index = i;
+
+ /* There's no nearer ancestor than FSPATH itself. */
+ if (strcmp(item.key, fspath) == 0)
+ {
+ *ancestor_is_self = TRUE;
+ break;
+ }
+ }
+
+ }
+ }
+
+ if (ancestor_index == -1)
+ return NULL;
+ else
+ return (APR_ARRAY_IDX(depth_first_catalog_index,
+ ancestor_index,
+ svn_sort__item_t)).value;
+}
+
+/* Baton for use with the filter_log_entry_with_rangelist()
+ svn_log_entry_receiver_t callback. */
+struct filter_log_entry_baton_t
+{
+ /* Is TRUE if RANGELIST describes potentially merged revisions, is FALSE
+ if RANGELIST describes potentially eligible revisions. */
+ svn_boolean_t filtering_merged;
+
+ /* Unsorted array of repository relative paths representing the merge
+ sources. There will be more than one source */
+ const apr_array_header_t *merge_source_fspaths;
+
+ /* The repository-absolute path we are calling svn_client_log5() on. */
+ const char *target_fspath;
+
+ /* Mergeinfo catalog for the tree rooted at TARGET_FSPATH.
+ The path keys must be repository-absolute. */
+ svn_mergeinfo_catalog_t target_mergeinfo_catalog;
+
+ /* Depth first sorted array of svn_sort__item_t's for
+ TARGET_MERGEINFO_CATALOG. */
+ apr_array_header_t *depth_first_catalog_index;
+
+ /* A rangelist describing all the revisions potentially merged or
+ potentially eligible for merging (see FILTERING_MERGED) based on
+ the target's explicit or inherited mergeinfo. */
+ const svn_rangelist_t *rangelist;
+
+ /* The wrapped svn_log_entry_receiver_t callback and baton which
+ filter_log_entry_with_rangelist() is acting as a filter for. */
+ svn_log_entry_receiver_t log_receiver;
+ void *log_receiver_baton;
+
+ svn_client_ctx_t *ctx;
+};
+
+/* Implements the svn_log_entry_receiver_t interface. BATON is a
+ `struct filter_log_entry_baton_t *'.
+
+ Call the wrapped log receiver BATON->log_receiver (with
+ BATON->log_receiver_baton) if:
+
+ BATON->FILTERING_MERGED is FALSE and the changes represented by LOG_ENTRY
+ have been fully merged from BATON->merge_source_fspaths to the WC target
+ based on the mergeinfo for the WC contained in BATON->TARGET_MERGEINFO_CATALOG.
+
+ Or
+
+ BATON->FILTERING_MERGED is TRUE and the changes represented by LOG_ENTRY
+ have not been merged, or only partially merged, from
+ BATON->merge_source_fspaths to the WC target based on the mergeinfo for the
+ WC contained in BATON->TARGET_MERGEINFO_CATALOG. */
+static svn_error_t *
+filter_log_entry_with_rangelist(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct filter_log_entry_baton_t *fleb = baton;
+ svn_rangelist_t *intersection, *this_rangelist;
+
+ if (fleb->ctx->cancel_func)
+ SVN_ERR(fleb->ctx->cancel_func(fleb->ctx->cancel_baton));
+
+ /* Ignore r0 because there can be no "change 0" in a merge range. */
+ if (log_entry->revision == 0)
+ return SVN_NO_ERROR;
+
+ this_rangelist = svn_rangelist__initialize(log_entry->revision - 1,
+ log_entry->revision,
+ TRUE, pool);
+
+ /* Don't consider inheritance yet, see if LOG_ENTRY->REVISION is
+ fully or partially represented in BATON->RANGELIST. */
+ SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist,
+ this_rangelist, FALSE, pool));
+ if (! (intersection && intersection->nelts))
+ return SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(intersection->nelts == 1);
+
+ /* Ok, we know LOG_ENTRY->REVISION is represented in BATON->RANGELIST,
+ but is it only partially represented, i.e. is the corresponding range in
+ BATON->RANGELIST non-inheritable? Ask for the same intersection as
+ above but consider inheritance this time, if the intersection is empty
+ we know the range in BATON->RANGELIST is non-inheritable. */
+ SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist,
+ this_rangelist, TRUE, pool));
+ log_entry->non_inheritable = !intersection->nelts;
+
+ /* If the paths changed by LOG_ENTRY->REVISION are provided we can determine
+ if LOG_ENTRY->REVISION, while only partially represented in
+ BATON->RANGELIST, is in fact completely applied to all affected paths.
+ ### And ... what if it is, or if it isn't? What do we do with the answer?
+ And how do we cope if the changed paths are not provided? */
+ if ((log_entry->non_inheritable || !fleb->filtering_merged)
+ && log_entry->changed_paths2)
+ {
+ apr_hash_index_t *hi;
+ svn_boolean_t all_subtrees_have_this_rev = TRUE;
+ svn_rangelist_t *this_rev_rangelist =
+ svn_rangelist__initialize(log_entry->revision - 1,
+ log_entry->revision, TRUE, pool);
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (hi = apr_hash_first(pool, log_entry->changed_paths2);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ int i;
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_log_changed_path2_t *change = svn__apr_hash_index_val(hi);
+ const char *target_fspath_affected;
+ svn_mergeinfo_t nearest_ancestor_mergeinfo;
+ svn_boolean_t found_this_revision = FALSE;
+ const char *merge_source_rel_target;
+ const char *merge_source_fspath;
+ svn_boolean_t ancestor_is_self;
+
+ svn_pool_clear(iterpool);
+
+ /* Check that PATH is a subtree of at least one of the
+ merge sources. If not then ignore this path. */
+ for (i = 0; i < fleb->merge_source_fspaths->nelts; i++)
+ {
+ merge_source_fspath = APR_ARRAY_IDX(fleb->merge_source_fspaths,
+ i, const char *);
+
+ merge_source_rel_target
+ = svn_fspath__skip_ancestor(merge_source_fspath, path);
+ if (merge_source_rel_target)
+ {
+ /* If MERGE_SOURCE was itself deleted, replaced, or added
+ in LOG_ENTRY->REVISION then ignore this PATH since you
+ can't merge a addition or deletion of yourself. */
+ if (merge_source_rel_target[0] == '\0'
+ && (change->action != 'M'))
+ i = fleb->merge_source_fspaths->nelts;
+ break;
+ }
+ }
+ /* If we examined every merge source path and PATH is a child of
+ none of them then we can ignore this PATH. */
+ if (i == fleb->merge_source_fspaths->nelts)
+ continue;
+
+ /* Calculate the target path which PATH would affect if merged. */
+ target_fspath_affected = svn_fspath__join(fleb->target_fspath,
+ merge_source_rel_target,
+ iterpool);
+
+ nearest_ancestor_mergeinfo =
+ find_nearest_ancestor(fleb->depth_first_catalog_index,
+ &ancestor_is_self,
+ target_fspath_affected);
+
+ /* Issue #3791: A path should never have explicit mergeinfo
+ describing its own addition (that's self-referential). Nor will
+ it have explicit mergeinfo describing its own deletion (we
+ obviously can't add new mergeinfo to a path we are deleting).
+
+ This lack of explicit mergeinfo should not cause such revisions
+ to show up as eligible however. If PATH was deleted, replaced,
+ or added in LOG_ENTRY->REVISION, but the corresponding
+ TARGET_PATH_AFFECTED already exists and has explicit mergeinfo
+ describing merges from PATH *after* LOG_ENTRY->REVISION, then
+ ignore this PATH. If it was deleted in LOG_ENTRY->REVISION it's
+ obviously back. If it was added or replaced it's still around
+ possibly it was replaced one or more times, but it's back now.
+ Regardless, LOG_ENTRY->REVISION is *not* an eligible revision! */
+ if (ancestor_is_self /* Explicit mergeinfo on TARGET_PATH_AFFECTED */
+ && (change->action != 'M'))
+ {
+ svn_rangelist_t *rangelist =
+ svn_hash_gets(nearest_ancestor_mergeinfo, path);
+ svn_merge_range_t *youngest_range = APR_ARRAY_IDX(
+ rangelist, rangelist->nelts - 1, svn_merge_range_t *);
+
+ if (youngest_range
+ && (youngest_range->end > log_entry->revision))
+ continue;
+ }
+
+ if (nearest_ancestor_mergeinfo)
+ {
+ apr_hash_index_t *hi2;
+
+ for (hi2 = apr_hash_first(iterpool, nearest_ancestor_mergeinfo);
+ hi2;
+ hi2 = apr_hash_next(hi2))
+ {
+ const char *mergeinfo_path = svn__apr_hash_index_key(hi2);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi2);
+
+ /* Does the mergeinfo for PATH reflect if
+ LOG_ENTRY->REVISION was previously merged
+ from MERGE_SOURCE_FSPATH? */
+ if (svn_fspath__skip_ancestor(merge_source_fspath,
+ mergeinfo_path))
+ {
+ /* Something was merged from MERGE_SOURCE_FSPATH, does
+ it include LOG_ENTRY->REVISION? */
+ SVN_ERR(svn_rangelist_intersect(&intersection,
+ rangelist,
+ this_rev_rangelist,
+ FALSE,
+ iterpool));
+ if (intersection->nelts)
+ {
+ if (ancestor_is_self)
+ {
+ /* TARGET_PATH_AFFECTED has explicit mergeinfo,
+ so we don't need to worry if that mergeinfo
+ is inheritable or not. */
+ found_this_revision = TRUE;
+ break;
+ }
+ else
+ {
+ /* TARGET_PATH_AFFECTED inherited its mergeinfo,
+ so we have to ignore non-inheritable
+ ranges. */
+ SVN_ERR(svn_rangelist_intersect(
+ &intersection,
+ rangelist,
+ this_rev_rangelist,
+ TRUE, iterpool));
+ if (intersection->nelts)
+ {
+ found_this_revision = TRUE;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!found_this_revision)
+ {
+ /* As soon as any PATH is found that is not fully merged for
+ LOG_ENTRY->REVISION then we can stop. */
+ all_subtrees_have_this_rev = FALSE;
+ break;
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ if (all_subtrees_have_this_rev)
+ {
+ if (fleb->filtering_merged)
+ log_entry->non_inheritable = FALSE;
+ else
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Call the wrapped log receiver which this function is filtering for. */
+ return fleb->log_receiver(fleb->log_receiver_baton, log_entry, pool);
+}
+
+static svn_error_t *
+logs_for_mergeinfo_rangelist(const char *source_url,
+ const apr_array_header_t *merge_source_fspaths,
+ svn_boolean_t filtering_merged,
+ const svn_rangelist_t *rangelist,
+ svn_boolean_t oldest_revs_first,
+ svn_mergeinfo_catalog_t target_mergeinfo_catalog,
+ const char *target_fspath,
+ svn_boolean_t discover_changed_paths,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t log_receiver,
+ void *log_receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *target;
+ svn_merge_range_t *oldest_range, *youngest_range;
+ apr_array_header_t *revision_ranges;
+ svn_opt_revision_t oldest_rev, youngest_rev;
+ struct filter_log_entry_baton_t fleb;
+
+ if (! rangelist->nelts)
+ return SVN_NO_ERROR;
+
+ /* Sort the rangelist. */
+ qsort(rangelist->elts, rangelist->nelts,
+ rangelist->elt_size, svn_sort_compare_ranges);
+
+ /* Build a single-member log target list using SOURCE_URL. */
+ target = apr_array_make(scratch_pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(target, const char *) = source_url;
+
+ /* Calculate and construct the bounds of our log request. */
+ youngest_range = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1,
+ svn_merge_range_t *);
+ youngest_rev.kind = svn_opt_revision_number;
+ youngest_rev.value.number = youngest_range->end;
+ oldest_range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *);
+ oldest_rev.kind = svn_opt_revision_number;
+ oldest_rev.value.number = oldest_range->start;
+
+ if (! target_mergeinfo_catalog)
+ target_mergeinfo_catalog = apr_hash_make(scratch_pool);
+
+ /* FILTER_LOG_ENTRY_BATON_T->TARGET_MERGEINFO_CATALOG's keys are required
+ to be repository-absolute. */
+ SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(&target_mergeinfo_catalog,
+ target_mergeinfo_catalog, "/",
+ scratch_pool, scratch_pool));
+
+ /* Build the log filtering callback baton. */
+ fleb.filtering_merged = filtering_merged;
+ fleb.merge_source_fspaths = merge_source_fspaths;
+ fleb.target_mergeinfo_catalog = target_mergeinfo_catalog;
+ fleb.depth_first_catalog_index =
+ svn_sort__hash(target_mergeinfo_catalog,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
+ fleb.target_fspath = target_fspath;
+ fleb.rangelist = rangelist;
+ fleb.log_receiver = log_receiver;
+ fleb.log_receiver_baton = log_receiver_baton;
+ fleb.ctx = ctx;
+
+ /* Drive the log. */
+ revision_ranges = apr_array_make(scratch_pool, 1,
+ sizeof(svn_opt_revision_range_t *));
+ if (oldest_revs_first)
+ APR_ARRAY_PUSH(revision_ranges, svn_opt_revision_range_t *)
+ = svn_opt__revision_range_create(&oldest_rev, &youngest_rev, scratch_pool);
+ else
+ APR_ARRAY_PUSH(revision_ranges, svn_opt_revision_range_t *)
+ = svn_opt__revision_range_create(&youngest_rev, &oldest_rev, scratch_pool);
+ SVN_ERR(svn_client_log5(target, &youngest_rev, revision_ranges,
+ 0, discover_changed_paths, FALSE, FALSE, revprops,
+ filter_log_entry_with_rangelist, &fleb, ctx,
+ scratch_pool));
+
+ /* Check for cancellation. */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *OUT_MERGEINFO to a shallow copy of MERGEINFO with each source path
+ converted to a (URI-encoded) URL based on REPOS_ROOT_URL. *OUT_MERGEINFO
+ is declared as 'apr_hash_t *' because its key do not obey the rules of
+ 'svn_mergeinfo_t'.
+
+ Allocate *OUT_MERGEINFO and the new keys in RESULT_POOL. Use
+ SCRATCH_POOL for any temporary allocations. */
+static svn_error_t *
+mergeinfo_relpaths_to_urls(apr_hash_t **out_mergeinfo,
+ svn_mergeinfo_t mergeinfo,
+ const char *repos_root_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *out_mergeinfo = NULL;
+ if (mergeinfo)
+ {
+ apr_hash_index_t *hi;
+ apr_hash_t *full_path_mergeinfo = apr_hash_make(result_pool);
+
+ for (hi = apr_hash_first(scratch_pool, mergeinfo);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *key = svn__apr_hash_index_key(hi);
+ void *val = svn__apr_hash_index_val(hi);
+
+ svn_hash_sets(full_path_mergeinfo,
+ svn_path_url_add_component2(repos_root_url, key + 1,
+ result_pool),
+ val);
+ }
+ *out_mergeinfo = full_path_mergeinfo;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Public APIs ***/
+
+svn_error_t *
+svn_client_mergeinfo_get_merged(apr_hash_t **mergeinfo_p,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *repos_root;
+ svn_mergeinfo_catalog_t mergeinfo_cat;
+ svn_mergeinfo_t mergeinfo;
+
+ SVN_ERR(get_mergeinfo(&mergeinfo_cat, &repos_root, path_or_url,
+ peg_revision, FALSE, FALSE, ctx, pool, pool));
+ if (mergeinfo_cat)
+ {
+ const char *repos_relpath;
+
+ if (! svn_path_is_url(path_or_url))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&path_or_url, path_or_url, pool));
+ SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, NULL, NULL,
+ ctx->wc_ctx, path_or_url,
+ pool, pool));
+ }
+ else
+ {
+ repos_relpath = svn_uri_skip_ancestor(repos_root, path_or_url, pool);
+
+ SVN_ERR_ASSERT(repos_relpath != NULL); /* Or get_mergeinfo failed */
+ }
+
+ mergeinfo = svn_hash_gets(mergeinfo_cat, repos_relpath);
+ }
+ else
+ {
+ mergeinfo = NULL;
+ }
+
+ SVN_ERR(mergeinfo_relpaths_to_urls(mergeinfo_p, mergeinfo,
+ repos_root, pool, pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client_mergeinfo_log2(svn_boolean_t finding_merged,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_peg_revision,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_peg_revision,
+ const svn_opt_revision_t *source_start_revision,
+ const svn_opt_revision_t *source_end_revision,
+ svn_log_entry_receiver_t log_receiver,
+ void *log_receiver_baton,
+ svn_boolean_t discover_changed_paths,
+ svn_depth_t depth,
+ const apr_array_header_t *revprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *log_target = NULL;
+ const char *repos_root;
+ const char *target_repos_relpath;
+ svn_mergeinfo_catalog_t target_mergeinfo_cat;
+
+ /* A hash of paths, at or under TARGET_PATH_OR_URL, mapped to
+ rangelists. Not technically mergeinfo, so not using the
+ svn_mergeinfo_t type. */
+ apr_hash_t *inheritable_subtree_merges;
+
+ svn_mergeinfo_t source_history;
+ svn_mergeinfo_t target_history;
+ svn_rangelist_t *master_noninheritable_rangelist;
+ svn_rangelist_t *master_inheritable_rangelist;
+ apr_array_header_t *merge_source_fspaths =
+ apr_array_make(scratch_pool, 1, sizeof(const char *));
+ apr_hash_index_t *hi_catalog;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ svn_boolean_t oldest_revs_first = TRUE;
+
+ /* We currently only support depth = empty | infinity. */
+ if (depth != svn_depth_infinity && depth != svn_depth_empty)
+ return svn_error_create(
+ SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Only depths 'infinity' and 'empty' are currently supported"));
+
+ /* Validate and sanitize the incoming source operative revision range. */
+ if (!((source_start_revision->kind == svn_opt_revision_unspecified) ||
+ (source_start_revision->kind == svn_opt_revision_number) ||
+ (source_start_revision->kind == svn_opt_revision_date) ||
+ (source_start_revision->kind == svn_opt_revision_head)))
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
+ if (!((source_end_revision->kind == svn_opt_revision_unspecified) ||
+ (source_end_revision->kind == svn_opt_revision_number) ||
+ (source_end_revision->kind == svn_opt_revision_date) ||
+ (source_end_revision->kind == svn_opt_revision_head)))
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
+ if ((source_end_revision->kind != svn_opt_revision_unspecified)
+ && (source_start_revision->kind == svn_opt_revision_unspecified))
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
+ if ((source_end_revision->kind == svn_opt_revision_unspecified)
+ && (source_start_revision->kind != svn_opt_revision_unspecified))
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
+
+ /* We need the union of TARGET_PATH_OR_URL@TARGET_PEG_REVISION's mergeinfo
+ and MERGE_SOURCE_URL's history. It's not enough to do path
+ matching, because renames in the history of MERGE_SOURCE_URL
+ throw that all in a tizzy. Of course, if there's no mergeinfo on
+ the target, that vastly simplifies matters (we'll have nothing to
+ do). */
+ /* This get_mergeinfo() call doubles as a mergeinfo capabilities check. */
+ SVN_ERR(get_mergeinfo(&target_mergeinfo_cat, &repos_root,
+ target_path_or_url, target_peg_revision,
+ depth == svn_depth_infinity, TRUE,
+ ctx, scratch_pool, scratch_pool));
+
+ if (!svn_path_is_url(target_path_or_url))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&target_path_or_url,
+ target_path_or_url, scratch_pool));
+ SVN_ERR(svn_wc__node_get_repos_info(NULL, &target_repos_relpath,
+ NULL, NULL,
+ ctx->wc_ctx, target_path_or_url,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ target_repos_relpath = svn_uri_skip_ancestor(repos_root,
+ target_path_or_url,
+ scratch_pool);
+
+ /* TARGET_REPOS_REL should be non-NULL, else get_mergeinfo
+ should have failed. */
+ SVN_ERR_ASSERT(target_repos_relpath != NULL);
+ }
+
+ if (!target_mergeinfo_cat)
+ {
+ /* If we are looking for what has been merged and there is no
+ mergeinfo then we already know the answer. If we are looking
+ for eligible revisions then create a catalog with empty mergeinfo
+ on the target. This is semantically equivalent to no mergeinfo
+ and gives us something to combine with MERGE_SOURCE_URL's
+ history. */
+ if (finding_merged)
+ {
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ target_mergeinfo_cat = apr_hash_make(scratch_pool);
+ svn_hash_sets(target_mergeinfo_cat, target_repos_relpath,
+ apr_hash_make(scratch_pool));
+ }
+ }
+
+ /* Fetch the location history as mergeinfo, for the source branch
+ * (between the given start and end revisions), and, if we're finding
+ * merged revisions, then also for the entire target branch.
+ *
+ * ### TODO: As the source and target must be in the same repository, we
+ * should share a single session, tracking the two URLs separately. */
+ {
+ apr_pool_t *sesspool = svn_pool_create(scratch_pool);
+ svn_ra_session_t *source_session, *target_session;
+ svn_client__pathrev_t *pathrev;
+ svn_revnum_t start_rev, end_rev, youngest_rev = SVN_INVALID_REVNUM;
+
+ if (! finding_merged)
+ {
+ SVN_ERR(svn_client__ra_session_from_path2(&target_session, &pathrev,
+ target_path_or_url, NULL,
+ target_peg_revision,
+ target_peg_revision,
+ ctx, sesspool));
+ SVN_ERR(svn_client__get_history_as_mergeinfo(&target_history, NULL,
+ pathrev,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ target_session, ctx,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_client__ra_session_from_path2(&source_session, &pathrev,
+ source_path_or_url, NULL,
+ source_peg_revision,
+ source_peg_revision,
+ ctx, sesspool));
+ SVN_ERR(svn_client__get_revision_number(&start_rev, &youngest_rev,
+ ctx->wc_ctx, source_path_or_url,
+ source_session,
+ source_start_revision,
+ sesspool));
+ SVN_ERR(svn_client__get_revision_number(&end_rev, &youngest_rev,
+ ctx->wc_ctx, source_path_or_url,
+ source_session,
+ source_end_revision,
+ sesspool));
+ SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history, NULL,
+ pathrev,
+ MAX(end_rev, start_rev),
+ MIN(end_rev, start_rev),
+ source_session, ctx,
+ scratch_pool));
+ if (start_rev > end_rev)
+ oldest_revs_first = FALSE;
+
+ /* Close the source and target sessions. */
+ svn_pool_destroy(sesspool);
+ }
+
+ /* Separate the explicit or inherited mergeinfo on TARGET_PATH_OR_URL,
+ and possibly its explicit subtree mergeinfo, into their
+ inheritable and non-inheritable parts. */
+ master_noninheritable_rangelist = apr_array_make(scratch_pool, 64,
+ sizeof(svn_merge_range_t *));
+ master_inheritable_rangelist = apr_array_make(scratch_pool, 64,
+ sizeof(svn_merge_range_t *));
+ inheritable_subtree_merges = apr_hash_make(scratch_pool);
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (hi_catalog = apr_hash_first(scratch_pool, target_mergeinfo_cat);
+ hi_catalog;
+ hi_catalog = apr_hash_next(hi_catalog))
+ {
+ svn_mergeinfo_t subtree_mergeinfo = svn__apr_hash_index_val(hi_catalog);
+ svn_mergeinfo_t subtree_history;
+ svn_mergeinfo_t subtree_source_history;
+ svn_mergeinfo_t subtree_inheritable_mergeinfo;
+ svn_mergeinfo_t subtree_noninheritable_mergeinfo;
+ svn_mergeinfo_t merged_noninheritable;
+ svn_mergeinfo_t merged;
+ const char *subtree_path = svn__apr_hash_index_key(hi_catalog);
+ svn_boolean_t is_subtree = strcmp(subtree_path,
+ target_repos_relpath) != 0;
+ svn_pool_clear(iterpool);
+
+ if (is_subtree)
+ {
+ /* If SUBTREE_PATH is a proper subtree of TARGET_PATH_OR_URL
+ then make a copy of SOURCE_HISTORY that is path adjusted
+ for the subtree. */
+ const char *subtree_rel_path =
+ subtree_path + strlen(target_repos_relpath) + 1;
+
+ SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
+ &subtree_source_history, source_history,
+ subtree_rel_path, scratch_pool, scratch_pool));
+
+ if (!finding_merged)
+ SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
+ &subtree_history, target_history,
+ subtree_rel_path, scratch_pool, scratch_pool));
+ }
+ else
+ {
+ subtree_source_history = source_history;
+ if (!finding_merged)
+ subtree_history = target_history;
+ }
+
+ if (!finding_merged)
+ {
+ svn_mergeinfo_t merged_via_history;
+ SVN_ERR(svn_mergeinfo_intersect2(&merged_via_history,
+ subtree_history,
+ subtree_source_history, TRUE,
+ scratch_pool, iterpool));
+ SVN_ERR(svn_mergeinfo_merge2(subtree_mergeinfo,
+ merged_via_history,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_mergeinfo_inheritable2(&subtree_inheritable_mergeinfo,
+ subtree_mergeinfo, NULL,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ TRUE, scratch_pool, iterpool));
+ SVN_ERR(svn_mergeinfo_inheritable2(&subtree_noninheritable_mergeinfo,
+ subtree_mergeinfo, NULL,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ FALSE, scratch_pool, iterpool));
+
+ /* Find the intersection of the non-inheritable part of
+ SUBTREE_MERGEINFO and SOURCE_HISTORY. svn_mergeinfo_intersect2()
+ won't consider non-inheritable and inheritable ranges
+ intersecting unless we ignore inheritance, but in doing so the
+ resulting intersections have all inheritable ranges. To get
+ around this we set the inheritance on the result to all
+ non-inheritable. */
+ SVN_ERR(svn_mergeinfo_intersect2(&merged_noninheritable,
+ subtree_noninheritable_mergeinfo,
+ subtree_source_history, FALSE,
+ scratch_pool, iterpool));
+ svn_mergeinfo__set_inheritance(merged_noninheritable, FALSE,
+ scratch_pool);
+
+ /* Keep track of all ranges partially merged to any and all
+ subtrees. */
+ SVN_ERR(svn_rangelist__merge_many(master_noninheritable_rangelist,
+ merged_noninheritable,
+ scratch_pool, iterpool));
+
+ /* Find the intersection of the inheritable part of TGT_MERGEINFO
+ and SOURCE_HISTORY. */
+ SVN_ERR(svn_mergeinfo_intersect2(&merged,
+ subtree_inheritable_mergeinfo,
+ subtree_source_history, FALSE,
+ scratch_pool, iterpool));
+
+ /* Keep track of all ranges fully merged to any and all
+ subtrees. */
+ if (apr_hash_count(merged))
+ {
+ /* The inheritable rangelist merged from SUBTREE_SOURCE_HISTORY
+ to SUBTREE_PATH. */
+ svn_rangelist_t *subtree_merged_rangelist =
+ apr_array_make(scratch_pool, 1, sizeof(svn_merge_range_t *));
+
+ SVN_ERR(svn_rangelist__merge_many(master_inheritable_rangelist,
+ merged, scratch_pool, iterpool));
+ SVN_ERR(svn_rangelist__merge_many(subtree_merged_rangelist,
+ merged, scratch_pool, iterpool));
+
+ svn_hash_sets(inheritable_subtree_merges, subtree_path,
+ subtree_merged_rangelist);
+ }
+ else
+ {
+ /* Map SUBTREE_PATH to an empty rangelist if there was nothing
+ fully merged. e.g. Only empty or non-inheritable mergeinfo
+ on the subtree or mergeinfo unrelated to the source. */
+ svn_hash_sets(inheritable_subtree_merges, subtree_path,
+ apr_array_make(scratch_pool, 0,
+ sizeof(svn_merge_range_t *)));
+ }
+ }
+
+ /* Make sure every range in MASTER_INHERITABLE_RANGELIST is fully merged to
+ each subtree (including the target itself). Any revisions which don't
+ exist in *every* subtree are *potentially* only partially merged to the
+ tree rooted at TARGET_PATH_OR_URL, so move those revisions to
+ MASTER_NONINHERITABLE_RANGELIST. It may turn out that that a revision
+ was merged to the only subtree it affects, but we need to examine the
+ logs to make this determination (which will be done by
+ logs_for_mergeinfo_rangelist). */
+ if (master_inheritable_rangelist->nelts)
+ {
+ for (hi = apr_hash_first(scratch_pool, inheritable_subtree_merges);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_rangelist_t *deleted_rangelist;
+ svn_rangelist_t *added_rangelist;
+ svn_rangelist_t *subtree_merged_rangelist =
+ svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_rangelist_diff(&deleted_rangelist, &added_rangelist,
+ master_inheritable_rangelist,
+ subtree_merged_rangelist, TRUE,
+ iterpool));
+
+ if (deleted_rangelist->nelts)
+ {
+ svn_rangelist__set_inheritance(deleted_rangelist, FALSE);
+ SVN_ERR(svn_rangelist_merge2(master_noninheritable_rangelist,
+ deleted_rangelist,
+ scratch_pool, iterpool));
+ SVN_ERR(svn_rangelist_remove(&master_inheritable_rangelist,
+ deleted_rangelist,
+ master_inheritable_rangelist,
+ FALSE,
+ scratch_pool));
+ }
+ }
+ }
+
+ if (finding_merged)
+ {
+ /* Roll all the merged revisions into one rangelist. */
+ SVN_ERR(svn_rangelist_merge2(master_inheritable_rangelist,
+ master_noninheritable_rangelist,
+ scratch_pool, scratch_pool));
+
+ }
+ else
+ {
+ /* Create the starting rangelist for what might be eligible. */
+ svn_rangelist_t *source_master_rangelist =
+ apr_array_make(scratch_pool, 1, sizeof(svn_merge_range_t *));
+
+ SVN_ERR(svn_rangelist__merge_many(source_master_rangelist,
+ source_history,
+ scratch_pool, scratch_pool));
+
+ /* From what might be eligible subtract what we know is
+ partially merged and then merge that back. */
+ SVN_ERR(svn_rangelist_remove(&source_master_rangelist,
+ master_noninheritable_rangelist,
+ source_master_rangelist,
+ FALSE, scratch_pool));
+ SVN_ERR(svn_rangelist_merge2(source_master_rangelist,
+ master_noninheritable_rangelist,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_rangelist_remove(&master_inheritable_rangelist,
+ master_inheritable_rangelist,
+ source_master_rangelist,
+ TRUE, scratch_pool));
+ }
+
+ /* Nothing merged? Not even when considering shared history if
+ looking for eligible revisions (i.e. !FINDING_MERGED)? Then there
+ is nothing more to do. */
+ if (! master_inheritable_rangelist->nelts)
+ {
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* Determine the correct (youngest) target for 'svn log'. */
+ svn_merge_range_t *youngest_range
+ = APR_ARRAY_IDX(master_inheritable_rangelist,
+ master_inheritable_rangelist->nelts - 1,
+ svn_merge_range_t *);
+ svn_rangelist_t *youngest_rangelist =
+ svn_rangelist__initialize(youngest_range->end - 1,
+ youngest_range->end,
+ youngest_range->inheritable,
+ scratch_pool);;
+
+ for (hi = apr_hash_first(scratch_pool, source_history);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *key = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *subtree_merged_rangelist =
+ svn__apr_hash_index_val(hi);
+ svn_rangelist_t *intersecting_rangelist;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_rangelist_intersect(&intersecting_rangelist,
+ youngest_rangelist,
+ subtree_merged_rangelist,
+ FALSE, iterpool));
+
+ APR_ARRAY_PUSH(merge_source_fspaths, const char *) = key;
+
+ if (intersecting_rangelist->nelts)
+ log_target = key;
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ /* Step 4: Finally, we run 'svn log' to drive our log receiver, but
+ using a receiver filter to only allow revisions to pass through
+ that are in our rangelist. */
+ log_target = svn_path_url_add_component2(repos_root, log_target + 1,
+ scratch_pool);
+
+ SVN_ERR(logs_for_mergeinfo_rangelist(log_target, merge_source_fspaths,
+ finding_merged,
+ master_inheritable_rangelist,
+ oldest_revs_first,
+ target_mergeinfo_cat,
+ svn_fspath__join("/",
+ target_repos_relpath,
+ scratch_pool),
+ discover_changed_paths,
+ revprops,
+ log_receiver, log_receiver_baton,
+ ctx, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_suggest_merge_sources(apr_array_header_t **suggestions,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *repos_root;
+ const char *copyfrom_path;
+ apr_array_header_t *list;
+ svn_revnum_t copyfrom_rev;
+ svn_mergeinfo_catalog_t mergeinfo_cat;
+ svn_mergeinfo_t mergeinfo;
+ apr_hash_index_t *hi;
+
+ list = apr_array_make(pool, 1, sizeof(const char *));
+
+ /* In our ideal algorithm, the list of recommendations should be
+ ordered by:
+
+ 1. The most recent existing merge source.
+ 2. The copyfrom source (which will also be listed as a merge
+ source if the copy was made with a 1.5+ client and server).
+ 3. All other merge sources, most recent to least recent.
+
+ However, determining the order of application of merge sources
+ requires a new RA API. Until such an API is available, our
+ algorithm will be:
+
+ 1. The copyfrom source.
+ 2. All remaining merge sources (unordered).
+ */
+
+ /* ### TODO: Share ra_session batons to improve efficiency? */
+ SVN_ERR(get_mergeinfo(&mergeinfo_cat, &repos_root, path_or_url,
+ peg_revision, FALSE, FALSE, ctx, pool, pool));
+
+ if (mergeinfo_cat && apr_hash_count(mergeinfo_cat))
+ {
+ /* We asked only for the PATH_OR_URL's mergeinfo, not any of its
+ descendants. So if there is anything in the catalog it is the
+ mergeinfo for PATH_OR_URL. */
+ mergeinfo = svn__apr_hash_index_val(apr_hash_first(pool, mergeinfo_cat));
+ }
+ else
+ {
+ mergeinfo = NULL;
+ }
+
+ SVN_ERR(svn_client__get_copy_source(&copyfrom_path, &copyfrom_rev,
+ path_or_url, peg_revision, ctx,
+ pool, pool));
+ if (copyfrom_path)
+ {
+ APR_ARRAY_PUSH(list, const char *) =
+ svn_path_url_add_component2(repos_root, copyfrom_path, pool);
+ }
+
+ if (mergeinfo)
+ {
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ const char *rel_path = svn__apr_hash_index_key(hi);
+
+ if (copyfrom_path == NULL || strcmp(rel_path, copyfrom_path) != 0)
+ APR_ARRAY_PUSH(list, const char *) = \
+ svn_path_url_add_component2(repos_root, rel_path + 1, pool);
+ }
+ }
+
+ *suggestions = list;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__mergeinfo_status(svn_boolean_t *mergeinfo_changes,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *propchanges;
+ int i;
+
+ *mergeinfo_changes = FALSE;
+
+ SVN_ERR(svn_wc_get_prop_diffs2(&propchanges, NULL, wc_ctx,
+ local_abspath, scratch_pool, scratch_pool));
+
+ for (i = 0; i < propchanges->nelts; i++)
+ {
+ svn_prop_t prop = APR_ARRAY_IDX(propchanges, i, svn_prop_t);
+ if (strcmp(prop.name, SVN_PROP_MERGEINFO) == 0)
+ {
+ *mergeinfo_changes = TRUE;
+ break;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/mergeinfo.h b/contrib/subversion/subversion/libsvn_client/mergeinfo.h
new file mode 100644
index 0000000..0c4cf05
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/mergeinfo.h
@@ -0,0 +1,414 @@
+/*
+ * mergeinfo.h : Client library-internal mergeinfo APIs.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_CLIENT_MERGEINFO_H
+#define SVN_LIBSVN_CLIENT_MERGEINFO_H
+
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "private/svn_client_private.h"
+
+
+/*** Data Structures ***/
+
+
+/* Structure to store information about working copy paths that need special
+ consideration during a mergeinfo aware merge -- See the
+ 'THE CHILDREN_WITH_MERGEINFO ARRAY' meta comment and the doc string for the
+ function get_mergeinfo_paths() in libsvn_client/merge.c.
+*/
+typedef struct svn_client__merge_path_t
+{
+ const char *abspath; /* Absolute working copy path. */
+ svn_boolean_t missing_child; /* ABSPATH has an immediate child which
+ is missing, but is not switched. */
+ svn_boolean_t switched_child; /* ABSPATH has an immediate child which
+ is switched. */
+ svn_boolean_t switched; /* ABSPATH is switched. */
+ svn_boolean_t has_noninheritable; /* ABSPATH has svn:mergeinfo set on it
+ which includes non-inheritable
+ revision ranges. */
+ svn_boolean_t absent; /* ABSPATH is absent from the WC,
+ probably due to authz
+ restrictions. */
+
+ svn_boolean_t child_of_noninheritable; /* ABSPATH has no explicit mergeinfo
+ itself but is the child of a
+ path with noniheritable
+ mergeinfo. */
+
+ /* The remaining ranges to be merged to ABSPATH. When describing a forward
+ merge this rangelist adheres to the rules for rangelists described in
+ svn_mergeinfo.h. However, when describing reverse merges this
+ rangelist can contain reverse merge ranges that are not sorted per
+ svn_sort_compare_ranges(), but rather are sorted such that the ranges
+ with the youngest start revisions come first. In both the forward and
+ reverse merge cases the ranges should never overlap. This rangelist
+ may be empty but should never be NULL unless ABSENT is true. */
+ svn_rangelist_t *remaining_ranges;
+
+ svn_mergeinfo_t pre_merge_mergeinfo; /* Explicit or inherited mergeinfo
+ on ABSPATH prior to a merge.
+ May be NULL. */
+ svn_mergeinfo_t implicit_mergeinfo; /* Implicit mergeinfo on ABSPATH
+ prior to a merge. May be NULL. */
+ svn_boolean_t inherited_mergeinfo; /* Whether PRE_MERGE_MERGEINFO was
+ explicit or inherited. */
+ svn_boolean_t scheduled_for_deletion; /* ABSPATH is scheduled for
+ deletion. */
+ svn_boolean_t immediate_child_dir; /* ABSPATH is an immediate child
+ directory of the merge target,
+ has no explicit mergeinfo prior
+ to the merge, and the operational
+ depth of the merge is
+ svn_depth_immediates. */
+ svn_boolean_t record_mergeinfo; /* Mergeinfo needs to be recorded
+ on ABSPATH to describe the
+ merge. */
+ svn_boolean_t record_noninheritable; /* Non-inheritable mergeinfo needs to
+ be recorded on ABSPATH to describe
+ the merge. Implies RECORD_MERGEINFO
+ is true. */
+} svn_client__merge_path_t;
+
+/* Return a deep copy of the merge-path structure OLD, allocated in POOL. */
+svn_client__merge_path_t *
+svn_client__merge_path_dup(const svn_client__merge_path_t *old,
+ apr_pool_t *pool);
+
+/* Create a new merge path structure, allocated in POOL. Initialize the
+ * 'abspath' member to a deep copy of ABSPATH and all other fields to zero
+ * bytes. */
+svn_client__merge_path_t *
+svn_client__merge_path_create(const char *abspath,
+ apr_pool_t *pool);
+
+
+
+/*** Functions ***/
+
+/* Find explicit or inherited WC mergeinfo for LOCAL_ABSPATH, and return it
+ in *MERGEINFO (NULL if no mergeinfo is set). Set *INHERITED to
+ whether the mergeinfo was inherited (TRUE or FALSE), if INHERITED is
+ non-null.
+
+ This function will search for inherited mergeinfo in the parents of
+ LOCAL_ABSPATH only if the base revision of LOCAL_ABSPATH falls within
+ the range of the parent's last committed revision to the parent's base
+ revision (inclusive) or is LOCAL_ABSPATH is a local addition. If asking
+ for the inherited mergeinfo of an added path (i.e. one with no base
+ revision), that path may inherit mergeinfo from its nearest parent
+ with a base revision and explicit mergeinfo.
+
+ INHERIT indicates whether explicit, explicit or inherited, or only
+ inherited mergeinfo for LOCAL_ABSPATH is retrieved.
+
+ Don't look for inherited mergeinfo any higher than LIMIT_ABSPATH
+ (ignored if NULL) or beyond any switched path.
+
+ Set *WALKED_PATH to the path climbed from LOCAL_ABSPATH to find inherited
+ mergeinfo, or "" if none was found. (ignored if NULL).
+
+ If IGNORE_INVALID_MERGEINFO is true, then syntactically invalid explicit
+ mergeinfo on found on LOCAL_ABSPATH is ignored and *MERGEINFO is set to an
+ empty hash. If IGNORE_INVALID_MERGEINFO is false, then syntactically
+ invalid explicit mergeinfo on found on LOCAL_ABSPATH results in a
+ SVN_ERR_MERGEINFO_PARSE_ERROR error. Regardless of
+ IGNORE_INVALID_MERGEINFO, if LOCAL_ABSPATH inherits invalid mergeinfo,
+ then *MERGEINFO is always set to an empty hash and no parse error is
+ raised. */
+svn_error_t *
+svn_client__get_wc_mergeinfo(svn_mergeinfo_t *mergeinfo,
+ svn_boolean_t *inherited,
+ svn_mergeinfo_inheritance_t inherit,
+ const char *local_abspath,
+ const char *limit_abspath,
+ const char **walked_path,
+ svn_boolean_t ignore_invalid_mergeinfo,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* If INCLUDE_DESCENDANTS is FALSE, behave exactly like
+ svn_client__get_wc_mergeinfo() except the mergeinfo for LOCAL_ABSPATH is
+ put in the mergeinfo catalog MERGEINFO_CAT, mapped from LOCAL_ABSPATH's
+ repository root-relative path.
+
+ If INCLUDE_DESCENDANTS is true, then any subtrees under LOCAL_ABSPATH with
+ explicit mergeinfo are also included in MERGEINFO_CAT and again the
+ keys are the repository root-relative paths of the subtrees. If no
+ mergeinfo is found, then *MERGEINFO_CAT is set to NULL. */
+svn_error_t *
+svn_client__get_wc_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat,
+ svn_boolean_t *inherited,
+ svn_boolean_t include_descendants,
+ svn_mergeinfo_inheritance_t inherit,
+ const char *local_abspath,
+ const char *limit_path,
+ const char **walked_path,
+ svn_boolean_t ignore_invalid_mergeinfo,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Obtain any mergeinfo for URL from the repository, and set
+ it in *TARGET_MERGEINFO.
+
+ INHERIT indicates whether explicit, explicit or inherited, or only
+ inherited mergeinfo for URL is obtained.
+
+ If URL does not exist at REV, SVN_ERR_FS_NOT_FOUND or
+ SVN_ERR_RA_DAV_REQUEST_FAILED is returned and *TARGET_MERGEINFO
+ is untouched.
+
+ If there is no mergeinfo available for URL, or if the server
+ doesn't support a mergeinfo capability and SQUELCH_INCAPABLE is
+ TRUE, set *TARGET_MERGEINFO to NULL. If the server doesn't support
+ a mergeinfo capability and SQUELCH_INCAPABLE is FALSE, return an
+ SVN_ERR_UNSUPPORTED_FEATURE error.
+
+ RA_SESSION is an open RA session to the repository in which URL lives;
+ it may be temporarily reparented by this function.
+*/
+svn_error_t *
+svn_client__get_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
+ svn_ra_session_t *ra_session,
+ const char *url,
+ svn_revnum_t rev,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t squelch_incapable,
+ apr_pool_t *pool);
+
+/* If INCLUDE_DESCENDANTS is FALSE, behave exactly like
+ svn_client__get_repos_mergeinfo() except the mergeinfo for URL
+ is put in the mergeinfo catalog MERGEINFO_CAT, with the key being
+ the repository root-relative path of URL.
+
+ If INCLUDE_DESCENDANTS is true, then any subtrees under URL
+ with explicit mergeinfo are also included in MERGEINFO_CAT. The
+ keys for the subtree mergeinfo are the repository root-relative
+ paths of the subtrees. If no mergeinfo is found, then
+ *TARGET_MERGEINFO_CAT is set to NULL. */
+svn_error_t *
+svn_client__get_repos_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat,
+ svn_ra_session_t *ra_session,
+ const char *url,
+ svn_revnum_t rev,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t squelch_incapable,
+ svn_boolean_t include_descendants,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Retrieve the direct mergeinfo for the TARGET_WCPATH from the WC's
+ mergeinfo prop, or that inherited from its nearest ancestor if the
+ target has no info of its own.
+
+ If no mergeinfo can be obtained from the WC or REPOS_ONLY is TRUE,
+ get it from the repository. If the repository is contacted for mergeinfo
+ and RA_SESSION does not point to TARGET_WCPATH's URL, then it is
+ temporarily reparented. If RA_SESSION is NULL, then a temporary session
+ is opened as needed.
+
+ Store any mergeinfo obtained for TARGET_WCPATH in
+ *TARGET_MERGEINFO, if no mergeinfo is found *TARGET_MERGEINFO is
+ NULL.
+
+ Like svn_client__get_wc_mergeinfo(), this function considers no
+ inherited mergeinfo to be found in the WC when trying to crawl into
+ a parent path with a different working revision.
+
+ INHERIT indicates whether explicit, explicit or inherited, or only
+ inherited mergeinfo for TARGET_WCPATH is retrieved.
+
+ If FROM_REPOS is not NULL, then set *FROM_REPOS to true if
+ *TARGET_MERGEINFO is inherited and the repository was contacted to
+ obtain it. Set *FROM_REPOS to false otherwise.
+
+ If TARGET_WCPATH inherited its mergeinfo from a working copy ancestor
+ or if it was obtained from the repository, set *INHERITED to TRUE, set it
+ to FALSE otherwise, if INHERITED is non-null. */
+svn_error_t *
+svn_client__get_wc_or_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo,
+ svn_boolean_t *inherited,
+ svn_boolean_t *from_repos,
+ svn_boolean_t repos_only,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_ra_session_t *ra_session,
+ const char *target_wcpath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* If INCLUDE_DESCENDANTS is false then behaves exactly like
+ svn_client__get_wc_or_repos_mergeinfo() except the mergeinfo for
+ TARGET_WCPATH is put in the mergeinfo catalog
+ TARGET_MERGEINFO_CATALOG, mapped from TARGET_WCPATH's repository
+ root-relative path.
+
+ IGNORE_INVALID_MERGEINFO behaves as per the argument of the same
+ name to svn_client__get_wc_mergeinfo(). It is applicable only if
+ the mergeinfo for TARGET_WCPATH is obtained from the working copy.
+
+ If INCLUDE_DESCENDANTS is true, then any subtrees under
+ TARGET_WCPATH with explicit mergeinfo are also included in
+ TARGET_MERGEINFO_CATALOG and again the keys are the repository
+ root-relative paths of the subtrees. If no mergeinfo is found,
+ then *TARGET_MERGEINFO_CAT is set to NULL. */
+svn_error_t *
+svn_client__get_wc_or_repos_mergeinfo_catalog(
+ svn_mergeinfo_catalog_t *target_mergeinfo_catalog,
+ svn_boolean_t *inherited,
+ svn_boolean_t *from_repos,
+ svn_boolean_t include_descendants,
+ svn_boolean_t repos_only,
+ svn_boolean_t ignore_invalid_mergeinfo,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_ra_session_t *ra_session,
+ const char *target_wcpath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *MERGEINFO_P to a mergeinfo constructed solely from the
+ natural history of PATHREV.
+
+ If RANGE_YOUNGEST and RANGE_OLDEST are valid, use them as inclusive
+ bounds on the revision ranges of returned mergeinfo. PATHREV->rev,
+ RANGE_YOUNGEST and RANGE_OLDEST are governed by the same rules as the
+ PEG_REVISION, START_REV, and END_REV parameters (respectively) of
+ svn_ra_get_location_segments().
+
+ If HAS_REV_ZERO_HISTORY is not NULL, then set *HAS_REV_ZERO_HISTORY to
+ TRUE if the natural history includes revision 0, else to FALSE.
+
+ RA_SESSION is an open RA session to the repository of PATHREV;
+ it may be temporarily reparented by this function.
+*/
+svn_error_t *
+svn_client__get_history_as_mergeinfo(svn_mergeinfo_t *mergeinfo_p,
+ svn_boolean_t *has_rev_zero_history,
+ const svn_client__pathrev_t *pathrev,
+ svn_revnum_t range_youngest,
+ svn_revnum_t range_oldest,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Parse any explicit mergeinfo on LOCAL_ABSPATH and store it in
+ *MERGEINFO. If no record of any mergeinfo exists, set *MERGEINFO to NULL.
+ Does not acount for inherited mergeinfo. */
+svn_error_t *
+svn_client__parse_mergeinfo(svn_mergeinfo_t *mergeinfo,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Write MERGEINFO into the WC for LOCAL_ABSPATH. If MERGEINFO is NULL,
+ remove any SVN_PROP_MERGEINFO for LOCAL_ABSPATH. If MERGEINFO is empty,
+ record an empty property value (e.g. ""). If CTX->NOTIFY_FUNC2 is
+ not null call it with notification type svn_wc_notify_merge_record_info
+ if DO_NOTIFICATION is true.
+
+ Use WC_CTX to access the working copy, and SCRATCH_POOL for any temporary
+ allocations. */
+svn_error_t *
+svn_client__record_wc_mergeinfo(const char *local_abspath,
+ svn_mergeinfo_t mergeinfo,
+ svn_boolean_t do_notification,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/* Write mergeinfo into the WC.
+ *
+ * For each path in RESULT_CATALOG, set the SVN_PROP_MERGEINFO
+ * property to represent the given mergeinfo, or remove the property
+ * if the given mergeinfo is null, and notify the change. Leave
+ * other paths unchanged. RESULT_CATALOG maps (const char *) WC paths
+ * to (svn_mergeinfo_t) mergeinfo. */
+svn_error_t *
+svn_client__record_wc_mergeinfo_catalog(apr_hash_t *result_catalog,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/* Elide any svn:mergeinfo set on TARGET_ABSPATH to its nearest working
+ copy (or possibly repository) ancestor with equivalent mergeinfo.
+
+ If WC_ELISION_LIMIT_ABSPATH is NULL check up to the root of the
+ working copy or the nearest switched parent for an elision
+ destination, if none is found check the repository, otherwise check
+ as far as WC_ELISION_LIMIT_ABSPATH within the working copy.
+ TARGET_WCPATH and WC_ELISION_LIMIT_ABSPATH, if it exists, must both be
+ absolute or relative to the working directory.
+
+ Elision occurs if:
+
+ A) TARGET_ABSPATH has empty mergeinfo and no parent path with
+ explicit mergeinfo can be found in either the WC or the
+ repository (WC_ELISION_LIMIT_PATH must be NULL for this to
+ occur).
+
+ B) TARGET_ABSPATH has empty mergeinfo and its nearest parent also
+ has empty mergeinfo.
+
+ C) TARGET_ABSPATH has the same mergeinfo as its nearest parent
+ when that parent's mergeinfo is adjusted for the path
+ difference between the two, e.g.:
+
+ TARGET_ABSPATH = A_COPY/D/H
+ TARGET_ABSPATH's mergeinfo = '/A/D/H:3'
+ TARGET_ABSPATH nearest parent = A_COPY
+ Parent's mergeinfo = '/A:3'
+ Path difference = 'D/H'
+ Parent's adjusted mergeinfo = '/A/D/H:3'
+
+ If Elision occurs remove the svn:mergeinfo property from
+ TARGET_ABSPATH. */
+svn_error_t *
+svn_client__elide_mergeinfo(const char *target_abspath,
+ const char *wc_elision_limit_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Simplify a mergeinfo catalog, if possible, via elision.
+
+ For each path in MERGEINFO_CATALOG, check if the path's mergeinfo can
+ elide to the path's nearest path-wise parent in MERGEINFO_CATALOG. If
+ so, remove that path from MERGEINFO_CATALOG. Elidability is determined
+ as per svn_client__elide_mergeinfo except that elision to the repository
+ is not considered.
+
+ SCRATCH_POOL is used for temporary allocations. */
+svn_error_t *
+svn_client__elide_mergeinfo_catalog(svn_mergeinfo_catalog_t mergeinfo_catalog,
+ apr_pool_t *scratch_pool);
+
+/* Set *MERGEINFO_CHANGES to TRUE if LOCAL_ABSPATH has locally modified
+ mergeinfo, set *MERGEINFO_CHANGES to FALSE otherwise. */
+svn_error_t *
+svn_client__mergeinfo_status(svn_boolean_t *mergeinfo_changes,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+#endif /* SVN_LIBSVN_CLIENT_MERGEINFO_H */
diff --git a/contrib/subversion/subversion/libsvn_client/patch.c b/contrib/subversion/subversion/libsvn_client/patch.c
new file mode 100644
index 0000000..b965646
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/patch.c
@@ -0,0 +1,3043 @@
+/*
+ * patch.c: patch application support
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_hash.h>
+#include <apr_fnmatch.h>
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_diff.h"
+#include "svn_hash.h"
+#include "svn_io.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_sorts.h"
+#include "svn_subst.h"
+#include "svn_wc.h"
+#include "client.h"
+
+#include "svn_private_config.h"
+#include "private/svn_eol_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+
+typedef struct hunk_info_t {
+ /* The hunk. */
+ svn_diff_hunk_t *hunk;
+
+ /* The line where the hunk matched in the target file. */
+ svn_linenum_t matched_line;
+
+ /* Whether this hunk has been rejected. */
+ svn_boolean_t rejected;
+
+ /* Whether this hunk has already been applied (either manually
+ * or by an earlier run of patch). */
+ svn_boolean_t already_applied;
+
+ /* The fuzz factor used when matching this hunk, i.e. how many
+ * lines of leading and trailing context to ignore during matching. */
+ svn_linenum_t fuzz;
+} hunk_info_t;
+
+/* A struct carrying information related to the patched and unpatched
+ * content of a target, be it a property or the text of a file. */
+typedef struct target_content_t {
+ /* Indicates whether unpatched content existed prior to patching. */
+ svn_boolean_t existed;
+
+ /* The line last read from the unpatched content. */
+ svn_linenum_t current_line;
+
+ /* The EOL-style of the unpatched content. Either 'none', 'fixed',
+ * or 'native'. See the documentation of svn_subst_eol_style_t. */
+ svn_subst_eol_style_t eol_style;
+
+ /* If the EOL_STYLE above is not 'none', this is the EOL string
+ * corresponding to the EOL-style. Else, it is the EOL string the
+ * last line read from the target file was using. */
+ const char *eol_str;
+
+ /* An array containing apr_off_t offsets marking the beginning of
+ * each line in the unpatched content. */
+ apr_array_header_t *lines;
+
+ /* An array containing hunk_info_t structures for hunks already matched. */
+ apr_array_header_t *hunks;
+
+ /* True if end-of-file was reached while reading from the unpatched
+ * content. */
+ svn_boolean_t eof;
+
+ /* The keywords of the target. They will be contracted when reading
+ * unpatched content and expanded when writing patched content.
+ * When patching properties this hash is always empty. */
+ apr_hash_t *keywords;
+
+ /* A callback, with an associated baton, to read a line of unpatched
+ * content. */
+ svn_error_t *(*readline)(void *baton, svn_stringbuf_t **line,
+ const char **eol_str, svn_boolean_t *eof,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool);
+ void *read_baton;
+
+ /* A callback to get the current byte offset within the unpatched
+ * content. Uses the read baton. */
+ svn_error_t * (*tell)(void *baton, apr_off_t *offset,
+ apr_pool_t *scratch_pool);
+
+ /* A callback to seek to an offset within the unpatched content.
+ * Uses the read baton. */
+ svn_error_t * (*seek)(void *baton, apr_off_t offset,
+ apr_pool_t *scratch_pool);
+
+ /* A callback to write data to the patched content, with an
+ * associated baton. */
+ svn_error_t * (*write)(void *baton, const char *buf, apr_size_t len,
+ apr_pool_t *scratch_pool);
+ void *write_baton;
+
+} target_content_t;
+
+typedef struct prop_patch_target_t {
+
+ /* The name of the property */
+ const char *name;
+
+ /* The property value. This is NULL in case the property did not exist
+ * prior to patch application (see also CONTENT->existed).
+ * Note that the patch implementation does not support binary properties,
+ * so this string is not expected to contain embedded NUL characters. */
+ const svn_string_t *value;
+
+ /* The patched property value.
+ * This is equivalent to the target, except that in appropriate
+ * places it contains the modified text as it appears in the patch file. */
+ svn_stringbuf_t *patched_value;
+
+ /* All information that is specific to the content of the property. */
+ target_content_t *content;
+
+ /* Represents the operation performed on the property. It can be added,
+ * deleted or modified.
+ * ### Should we use flags instead since we're not using all enum values? */
+ svn_diff_operation_kind_t operation;
+
+ /* ### Here we'll add flags telling if the prop was added, deleted,
+ * ### had_rejects, had_local_mods prior to patching and so on. */
+} prop_patch_target_t;
+
+typedef struct patch_target_t {
+ /* The target path as it appeared in the patch file,
+ * but in canonicalised form. */
+ const char *canon_path_from_patchfile;
+
+ /* The target path, relative to the working copy directory the
+ * patch is being applied to. A patch strip count applies to this
+ * and only this path. This is never NULL. */
+ const char *local_relpath;
+
+ /* The absolute path of the target on the filesystem.
+ * Any symlinks the path from the patch file may contain are resolved.
+ * Is not always known, so it may be NULL. */
+ const char *local_abspath;
+
+ /* The target file, read-only. This is NULL in case the target
+ * file did not exist prior to patch application (see also
+ * CONTENT->existed). */
+ apr_file_t *file;
+
+ /* The target file is a symlink */
+ svn_boolean_t is_symlink;
+
+ /* The patched file.
+ * This is equivalent to the target, except that in appropriate
+ * places it contains the modified text as it appears in the patch file.
+ * The data in this file is written in repository-normal form.
+ * EOL transformation and keyword contraction is performed when the
+ * patched result is installed in the working copy. */
+ apr_file_t *patched_file;
+
+ /* Path to the patched file. */
+ const char *patched_path;
+
+ /* Hunks that are rejected will be written to this file. */
+ apr_file_t *reject_file;
+
+ /* Path to the reject file. */
+ const char *reject_path;
+
+ /* The node kind of the target as found in WC-DB prior
+ * to patch application. */
+ svn_node_kind_t db_kind;
+
+ /* The target's kind on disk prior to patch application. */
+ svn_node_kind_t kind_on_disk;
+
+ /* True if the target was locally deleted prior to patching. */
+ svn_boolean_t locally_deleted;
+
+ /* True if the target had to be skipped for some reason. */
+ svn_boolean_t skipped;
+
+ /* True if the target has been filtered by the patch callback. */
+ svn_boolean_t filtered;
+
+ /* True if at least one hunk was rejected. */
+ svn_boolean_t had_rejects;
+
+ /* True if at least one property hunk was rejected. */
+ svn_boolean_t had_prop_rejects;
+
+ /* True if the target file had local modifications before the
+ * patch was applied to it. */
+ svn_boolean_t local_mods;
+
+ /* True if the target was added by the patch, which means that it did
+ * not exist on disk before patching and has content after patching. */
+ svn_boolean_t added;
+
+ /* True if the target ended up being deleted by the patch. */
+ svn_boolean_t deleted;
+
+ /* True if the target ended up being replaced by the patch
+ * (i.e. a new file was added on top locally deleted node). */
+ svn_boolean_t replaced;
+
+ /* True if the target has the executable bit set. */
+ svn_boolean_t executable;
+
+ /* True if the patch changed the text of the target. */
+ svn_boolean_t has_text_changes;
+
+ /* True if the patch changed any of the properties of the target. */
+ svn_boolean_t has_prop_changes;
+
+ /* True if the patch contained a svn:special property. */
+ svn_boolean_t is_special;
+
+ /* All the information that is specific to the content of the target. */
+ target_content_t *content;
+
+ /* A hash table of prop_patch_target_t objects keyed by property names. */
+ apr_hash_t *prop_targets;
+
+} patch_target_t;
+
+
+/* A smaller struct containing a subset of patch_target_t.
+ * Carries the minimal amount of information we still need for a
+ * target after we're done patching it so we can free other resources. */
+typedef struct patch_target_info_t {
+ const char *local_abspath;
+ svn_boolean_t deleted;
+} patch_target_info_t;
+
+
+/* Strip STRIP_COUNT components from the front of PATH, returning
+ * the result in *RESULT, allocated in RESULT_POOL.
+ * Do temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+strip_path(const char **result, const char *path, int strip_count,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_array_header_t *components;
+ apr_array_header_t *stripped;
+
+ components = svn_path_decompose(path, scratch_pool);
+ if (strip_count > components->nelts)
+ return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
+ _("Cannot strip %u components from '%s'"),
+ strip_count,
+ svn_dirent_local_style(path, scratch_pool));
+
+ stripped = apr_array_make(scratch_pool, components->nelts - strip_count,
+ sizeof(const char *));
+ for (i = strip_count; i < components->nelts; i++)
+ {
+ const char *component;
+
+ component = APR_ARRAY_IDX(components, i, const char *);
+ APR_ARRAY_PUSH(stripped, const char *) = component;
+ }
+
+ *result = svn_path_compose(stripped, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
+ * WC_CTX is a context for the working copy the patch is applied to.
+ * Use RESULT_POOL for allocations of fields in TARGET.
+ * Use SCRATCH_POOL for all other allocations. */
+static svn_error_t *
+obtain_eol_and_keywords_for_file(apr_hash_t **keywords,
+ svn_subst_eol_style_t *eol_style,
+ const char **eol_str,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *props;
+ svn_string_t *keywords_val, *eol_style_val;
+
+ SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
+ if (keywords_val)
+ {
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *rev_str;
+ const char *author;
+ const char *url;
+ const char *root_url;
+
+ SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
+ &changed_date,
+ &author, wc_ctx,
+ local_abspath,
+ scratch_pool,
+ scratch_pool));
+ rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
+ SVN_ERR(svn_wc__node_get_url(&url, wc_ctx,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &root_url, NULL,
+ wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_subst_build_keywords3(keywords,
+ keywords_val->data,
+ rev_str, url, root_url, changed_date,
+ author, result_pool));
+ }
+
+ eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
+ if (eol_style_val)
+ {
+ svn_subst_eol_style_from_value(eol_style,
+ eol_str,
+ eol_style_val->data);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Resolve the exact path for a patch TARGET at path PATH_FROM_PATCHFILE,
+ * which is the path of the target as it appeared in the patch file.
+ * Put a canonicalized version of PATH_FROM_PATCHFILE into
+ * TARGET->CANON_PATH_FROM_PATCHFILE.
+ * WC_CTX is a context for the working copy the patch is applied to.
+ * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
+ * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
+ * Indicate in TARGET->SKIPPED whether the target should be skipped.
+ * STRIP_COUNT specifies the number of leading path components
+ * which should be stripped from target paths in the patch.
+ * PROP_CHANGES_ONLY specifies whether the target path is allowed to have
+ * only property changes, and no content changes (in which case the target
+ * must be a directory).
+ * Use RESULT_POOL for allocations of fields in TARGET.
+ * Use SCRATCH_POOL for all other allocations. */
+static svn_error_t *
+resolve_target_path(patch_target_t *target,
+ const char *path_from_patchfile,
+ const char *wcroot_abspath,
+ int strip_count,
+ svn_boolean_t prop_changes_only,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *stripped_path;
+ svn_wc_status3_t *status;
+ svn_error_t *err;
+ svn_boolean_t under_root;
+
+ target->canon_path_from_patchfile = svn_dirent_internal_style(
+ path_from_patchfile, result_pool);
+
+ /* We allow properties to be set on the wc root dir. */
+ if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
+ {
+ /* An empty patch target path? What gives? Skip this. */
+ target->skipped = TRUE;
+ target->local_abspath = NULL;
+ target->local_relpath = "";
+ return SVN_NO_ERROR;
+ }
+
+ if (strip_count > 0)
+ SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
+ strip_count, result_pool, scratch_pool));
+ else
+ stripped_path = target->canon_path_from_patchfile;
+
+ if (svn_dirent_is_absolute(stripped_path))
+ {
+ target->local_relpath = svn_dirent_is_child(wcroot_abspath,
+ stripped_path,
+ result_pool);
+
+ if (! target->local_relpath)
+ {
+ /* The target path is either outside of the working copy
+ * or it is the working copy itself. Skip it. */
+ target->skipped = TRUE;
+ target->local_abspath = NULL;
+ target->local_relpath = stripped_path;
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ {
+ target->local_relpath = stripped_path;
+ }
+
+ /* Make sure the path is secure to use. We want the target to be inside
+ * of the working copy and not be fooled by symlinks it might contain. */
+ SVN_ERR(svn_dirent_is_under_root(&under_root,
+ &target->local_abspath, wcroot_abspath,
+ target->local_relpath, result_pool));
+
+ if (! under_root)
+ {
+ /* The target path is outside of the working copy. Skip it. */
+ target->skipped = TRUE;
+ target->local_abspath = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Skip things we should not be messing with. */
+ err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ target->locally_deleted = TRUE;
+ target->db_kind = svn_node_none;
+ status = NULL;
+ }
+ else if (status->node_status == svn_wc_status_ignored ||
+ status->node_status == svn_wc_status_unversioned ||
+ status->node_status == svn_wc_status_missing ||
+ status->node_status == svn_wc_status_obstructed ||
+ status->conflicted)
+ {
+ target->skipped = TRUE;
+ return SVN_NO_ERROR;
+ }
+ else if (status->node_status == svn_wc_status_deleted)
+ {
+ target->locally_deleted = TRUE;
+ }
+
+ if (status && (status->kind != svn_node_unknown))
+ target->db_kind = status->kind;
+ else
+ target->db_kind = svn_node_none;
+
+ SVN_ERR(svn_io_check_special_path(target->local_abspath,
+ &target->kind_on_disk, &target->is_symlink,
+ scratch_pool));
+
+ if (target->locally_deleted)
+ {
+ const char *moved_to_abspath;
+
+ SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
+ wc_ctx, target->local_abspath,
+ result_pool, scratch_pool));
+ if (moved_to_abspath)
+ {
+ target->local_abspath = moved_to_abspath;
+ target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath,
+ moved_to_abspath);
+ SVN_ERR_ASSERT(target->local_relpath &&
+ target->local_relpath[0] != '\0');
+
+ /* As far as we are concerned this target is not locally deleted. */
+ target->locally_deleted = FALSE;
+
+ SVN_ERR(svn_io_check_special_path(target->local_abspath,
+ &target->kind_on_disk,
+ &target->is_symlink,
+ scratch_pool));
+ }
+ else if (target->kind_on_disk != svn_node_none)
+ {
+ target->skipped = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for reading from properties. */
+typedef struct prop_read_baton_t {
+ const svn_string_t *value;
+ apr_off_t offset;
+} prop_read_baton_t;
+
+/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
+ * the unpatched property value accessed via BATON.
+ * Reading stops either after a line-terminator was found, or if
+ * the property value runs out in which case *EOF is set to TRUE.
+ * The line-terminator is not stored in *STRINGBUF.
+ *
+ * If the line is empty or could not be read, *line is set to NULL.
+ *
+ * The line-terminator is detected automatically and stored in *EOL
+ * if EOL is not NULL. If the end of the property value is reached
+ * and does not end with a newline character, and EOL is not NULL,
+ * *EOL is set to NULL.
+ *
+ * SCRATCH_POOL is used for temporary allocations.
+ */
+static svn_error_t *
+readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str,
+ svn_boolean_t *eof, apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ prop_read_baton_t *b = (prop_read_baton_t *)baton;
+ svn_stringbuf_t *str = NULL;
+ const char *c;
+ svn_boolean_t found_eof;
+
+ if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len)
+ {
+ *eol_str = NULL;
+ *eof = TRUE;
+ *line = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Read bytes into STR up to and including, but not storing,
+ * the next EOL sequence. */
+ *eol_str = NULL;
+ found_eof = FALSE;
+ do
+ {
+ c = b->value->data + b->offset;
+ b->offset++;
+
+ if (*c == '\0')
+ {
+ found_eof = TRUE;
+ break;
+ }
+ else if (*c == '\n')
+ {
+ *eol_str = "\n";
+ }
+ else if (*c == '\r')
+ {
+ *eol_str = "\r";
+ if (*(c + 1) == '\n')
+ {
+ *eol_str = "\r\n";
+ b->offset++;
+ }
+ }
+ else
+ {
+ if (str == NULL)
+ str = svn_stringbuf_create_ensure(80, result_pool);
+ svn_stringbuf_appendbyte(str, *c);
+ }
+
+ if (*eol_str)
+ break;
+ }
+ while (c < b->value->data + b->value->len);
+
+ if (eof)
+ *eof = found_eof;
+ *line = str;
+
+ return SVN_NO_ERROR;
+}
+
+/* Return in *OFFSET the current byte offset for reading from the
+ * unpatched property value accessed via BATON.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
+{
+ prop_read_baton_t *b = (prop_read_baton_t *)baton;
+ *offset = b->offset;
+ return SVN_NO_ERROR;
+}
+
+/* Seek to the specified by OFFSET in the unpatched property value accessed
+ * via BATON. Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
+{
+ prop_read_baton_t *b = (prop_read_baton_t *)baton;
+ b->offset = offset;
+ return SVN_NO_ERROR;
+}
+
+/* Write LEN bytes from BUF into the patched property value accessed
+ * via BATON. Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+write_prop(void *baton, const char *buf, apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton;
+ svn_stringbuf_appendbytes(patched_value, buf, len);
+ return SVN_NO_ERROR;
+}
+
+/* Initialize a PROP_TARGET structure for PROP_NAME on the patch target
+ * at LOCAL_ABSPATH. OPERATION indicates the operation performed on the
+ * property. Use working copy context WC_CTX.
+ * Allocate results in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+init_prop_target(prop_patch_target_t **prop_target,
+ const char *prop_name,
+ svn_diff_operation_kind_t operation,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ prop_patch_target_t *new_prop_target;
+ target_content_t *content;
+ const svn_string_t *value;
+ svn_error_t *err;
+ prop_read_baton_t *prop_read_baton;
+
+ content = apr_pcalloc(result_pool, sizeof(*content));
+
+ /* All other fields are FALSE or NULL due to apr_pcalloc(). */
+ content->current_line = 1;
+ content->eol_style = svn_subst_eol_style_none;
+ content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
+ content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
+ content->keywords = apr_hash_make(result_pool);
+
+ new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target));
+ new_prop_target->name = apr_pstrdup(result_pool, prop_name);
+ new_prop_target->operation = operation;
+ new_prop_target->content = content;
+
+ err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ value = NULL;
+ }
+ else
+ return svn_error_trace(err);
+ }
+ content->existed = (value != NULL);
+ new_prop_target->value = value;
+ new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
+
+
+ /* Wire up the read and write callbacks. */
+ prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton));
+ prop_read_baton->value = value;
+ prop_read_baton->offset = 0;
+ content->readline = readline_prop;
+ content->tell = tell_prop;
+ content->seek = seek_prop;
+ content->read_baton = prop_read_baton;
+ content->write = write_prop;
+ content->write_baton = new_prop_target->patched_value;
+
+ *prop_target = new_prop_target;
+
+ return SVN_NO_ERROR;
+}
+
+/* Allocate *STRINGBUF in RESULT_POOL, and read into it one line from
+ * the unpatched file content accessed via BATON.
+ * Reading stops either after a line-terminator was found,
+ * or if EOF is reached in which case *EOF is set to TRUE.
+ * The line-terminator is not stored in *STRINGBUF.
+ *
+ * If the line is empty or could not be read, *line is set to NULL.
+ *
+ * The line-terminator is detected automatically and stored in *EOL
+ * if EOL is not NULL. If EOF is reached and FILE does not end
+ * with a newline character, and EOL is not NULL, *EOL is set to NULL.
+ *
+ * SCRATCH_POOL is used for temporary allocations.
+ */
+static svn_error_t *
+readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str,
+ svn_boolean_t *eof, apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *file = (apr_file_t *)baton;
+ svn_stringbuf_t *str = NULL;
+ apr_size_t numbytes;
+ char c;
+ svn_boolean_t found_eof;
+
+ /* Read bytes into STR up to and including, but not storing,
+ * the next EOL sequence. */
+ *eol_str = NULL;
+ numbytes = 1;
+ found_eof = FALSE;
+ while (!found_eof)
+ {
+ SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
+ &found_eof, scratch_pool));
+ if (numbytes != 1)
+ {
+ found_eof = TRUE;
+ break;
+ }
+
+ if (c == '\n')
+ {
+ *eol_str = "\n";
+ }
+ else if (c == '\r')
+ {
+ *eol_str = "\r";
+
+ if (!found_eof)
+ {
+ apr_off_t pos;
+
+ /* Check for "\r\n" by peeking at the next byte. */
+ pos = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
+ SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
+ &found_eof, scratch_pool));
+ if (numbytes == 1 && c == '\n')
+ {
+ *eol_str = "\r\n";
+ }
+ else
+ {
+ /* Pretend we never peeked. */
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
+ found_eof = FALSE;
+ numbytes = 1;
+ }
+ }
+ }
+ else
+ {
+ if (str == NULL)
+ str = svn_stringbuf_create_ensure(80, result_pool);
+ svn_stringbuf_appendbyte(str, c);
+ }
+
+ if (*eol_str)
+ break;
+ }
+
+ if (eof)
+ *eof = found_eof;
+ *line = str;
+
+ return SVN_NO_ERROR;
+}
+
+/* Return in *OFFSET the current byte offset for reading from the
+ * unpatched file content accessed via BATON.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
+{
+ apr_file_t *file = (apr_file_t *)baton;
+ *offset = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Seek to the specified by OFFSET in the unpatched file content accessed
+ * via BATON. Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
+{
+ apr_file_t *file = (apr_file_t *)baton;
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Write LEN bytes from BUF into the patched file content accessed
+ * via BATON. Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+write_file(void *baton, const char *buf, apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *file = (apr_file_t *)baton;
+ SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Handling symbolic links:
+ *
+ * In Subversion, symlinks can be represented on disk in two distinct ways.
+ * On systems which support symlinks, a symlink is created on disk.
+ * On systems which do not support symlink, a file is created on disk
+ * which contains the "normal form" of the symlink, which looks like:
+ * link TARGET
+ * where TARGET is the file the symlink points to.
+ *
+ * When reading symlinks (i.e. the link itself, not the file the symlink
+ * is pointing to) through the svn_subst_create_specialfile() function
+ * into a buffer, the buffer always contains the "normal form" of the symlink.
+ * Due to this representation symlinks always contain a single line of text.
+ *
+ * The functions below are needed to deal with the case where a patch
+ * wants to change the TARGET that a symlink points to.
+ */
+
+/* Baton for the (readline|tell|seek|write)_symlink functions. */
+struct symlink_baton_t
+{
+ /* The path to the symlink on disk (not the path to the target of the link) */
+ const char *local_abspath;
+
+ /* Indicates whether the "normal form" of the symlink has been read. */
+ svn_boolean_t at_eof;
+};
+
+/* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form"
+ * of the symlink accessed via BATON.
+ *
+ * Otherwise behaves like readline_file(), which see.
+ */
+static svn_error_t *
+readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str,
+ svn_boolean_t *eof, apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct symlink_baton_t *sb = baton;
+
+ if (eof)
+ *eof = TRUE;
+ if (eol_str)
+ *eol_str = NULL;
+
+ if (sb->at_eof)
+ {
+ *line = NULL;
+ }
+ else
+ {
+ svn_string_t *dest;
+
+ SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool));
+ *line = svn_stringbuf_createf(result_pool, "link %s", dest->data);
+ sb->at_eof = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
+ * the symlink has already been read. */
+static svn_error_t *
+tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
+{
+ struct symlink_baton_t *sb = baton;
+
+ *offset = sb->at_eof ? 1 : 0;
+ return SVN_NO_ERROR;
+}
+
+/* If offset is non-zero, mark the symlink as having been read in its
+ * "normal form". Else, mark the symlink as not having been read yet. */
+static svn_error_t *
+seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
+{
+ struct symlink_baton_t *sb = baton;
+
+ sb->at_eof = (offset != 0);
+ return SVN_NO_ERROR;
+}
+
+
+/* Set the target of the symlink accessed via BATON.
+ * The contents of BUF must be a valid "normal form" of a symlink. */
+static svn_error_t *
+write_symlink(void *baton, const char *buf, apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ const char *target_abspath = baton;
+ const char *new_name;
+ const char *link = apr_pstrndup(scratch_pool, buf, len);
+
+ if (strncmp(link, "link ", 5) != 0)
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
+ _("Invalid link representation"));
+
+ link += 5; /* Skip "link " */
+
+ /* We assume the entire symlink is written at once, as the patch
+ format is line based */
+
+ SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link,
+ ".tmp", scratch_pool));
+
+ SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return a suitable filename for the target of PATCH.
+ * Examine the ``old'' and ``new'' file names, and choose the file name
+ * with the fewest path components, the shortest basename, and the shortest
+ * total file name length (in that order). In case of a tie, return the new
+ * filename. This heuristic is also used by Larry Wall's UNIX patch (except
+ * that it prompts for a filename in case of a tie).
+ * Additionally, for compatibility with git, if one of the filenames
+ * is "/dev/null", use the other filename. */
+static const char *
+choose_target_filename(const svn_patch_t *patch)
+{
+ apr_size_t old;
+ apr_size_t new;
+
+ if (strcmp(patch->old_filename, "/dev/null") == 0)
+ return patch->new_filename;
+ if (strcmp(patch->new_filename, "/dev/null") == 0)
+ return patch->old_filename;
+
+ old = svn_path_component_count(patch->old_filename);
+ new = svn_path_component_count(patch->new_filename);
+
+ if (old == new)
+ {
+ old = strlen(svn_dirent_basename(patch->old_filename, NULL));
+ new = strlen(svn_dirent_basename(patch->new_filename, NULL));
+
+ if (old == new)
+ {
+ old = strlen(patch->old_filename);
+ new = strlen(patch->new_filename);
+ }
+ }
+
+ return (old < new) ? patch->old_filename : patch->new_filename;
+}
+
+/* Attempt to initialize a *PATCH_TARGET structure for a target file
+ * described by PATCH. Use working copy context WC_CTX.
+ * STRIP_COUNT specifies the number of leading path components
+ * which should be stripped from target paths in the patch.
+ * The patch target structure is allocated in RESULT_POOL, but if the target
+ * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
+ * treated as not fully initialized, e.g. the caller should not not do any
+ * further operations on the target if it is marked to be skipped.
+ * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
+ * soon as they are no longer needed.
+ * Use SCRATCH_POOL for all other allocations. */
+static svn_error_t *
+init_patch_target(patch_target_t **patch_target,
+ const svn_patch_t *patch,
+ const char *wcroot_abspath,
+ svn_wc_context_t *wc_ctx, int strip_count,
+ svn_boolean_t remove_tempfiles,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ patch_target_t *target;
+ target_content_t *content;
+ svn_boolean_t has_prop_changes = FALSE;
+ svn_boolean_t prop_changes_only = FALSE;
+
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, patch->prop_patches);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
+ if (! has_prop_changes)
+ has_prop_changes = prop_patch->hunks->nelts > 0;
+ else
+ break;
+ }
+ }
+
+ prop_changes_only = has_prop_changes && patch->hunks->nelts == 0;
+
+ content = apr_pcalloc(result_pool, sizeof(*content));
+
+ /* All other fields in content are FALSE or NULL due to apr_pcalloc().*/
+ content->current_line = 1;
+ content->eol_style = svn_subst_eol_style_none;
+ content->lines = apr_array_make(result_pool, 0, sizeof(apr_off_t));
+ content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
+ content->keywords = apr_hash_make(result_pool);
+
+ target = apr_pcalloc(result_pool, sizeof(*target));
+
+ /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
+ target->db_kind = svn_node_none;
+ target->kind_on_disk = svn_node_none;
+ target->content = content;
+ target->prop_targets = apr_hash_make(result_pool);
+
+ SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
+ wcroot_abspath, strip_count, prop_changes_only,
+ wc_ctx, result_pool, scratch_pool));
+ if (! target->skipped)
+ {
+ const char *diff_header;
+ apr_size_t len;
+
+ /* Create a temporary file to write the patched result to.
+ * Also grab various bits of information about the file. */
+ if (target->is_symlink)
+ {
+ struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
+ content->existed = TRUE;
+
+ sb->local_abspath = target->local_abspath;
+
+ /* Wire up the read callbacks. */
+ content->read_baton = sb;
+
+ content->readline = readline_symlink;
+ content->seek = seek_symlink;
+ content->tell = tell_symlink;
+ }
+ else if (target->kind_on_disk == svn_node_file)
+ {
+ SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, result_pool));
+ SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
+ target->local_abspath, FALSE,
+ scratch_pool));
+ SVN_ERR(svn_io_is_file_executable(&target->executable,
+ target->local_abspath,
+ scratch_pool));
+ SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
+ &content->eol_style,
+ &content->eol_str,
+ wc_ctx,
+ target->local_abspath,
+ result_pool,
+ scratch_pool));
+ content->existed = TRUE;
+
+ /* Wire up the read callbacks. */
+ content->readline = readline_file;
+ content->seek = seek_file;
+ content->tell = tell_file;
+ content->read_baton = target->file;
+ }
+
+ /* ### Is it ok to set the operation of the target already here? Isn't
+ * ### the target supposed to be marked with an operation after we have
+ * ### determined that the changes will apply cleanly to the WC? Maybe
+ * ### we should have kept the patch field in patch_target_t to be
+ * ### able to distinguish between 'what the patch says we should do'
+ * ### and 'what we can do with the given state of our WC'. */
+ if (patch->operation == svn_diff_op_added)
+ target->added = TRUE;
+ else if (patch->operation == svn_diff_op_deleted)
+ target->deleted = TRUE;
+
+ if (! target->is_symlink)
+ {
+ /* Open a temporary file to write the patched result to. */
+ SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
+ &target->patched_path, NULL,
+ remove_tempfiles ?
+ svn_io_file_del_on_pool_cleanup :
+ svn_io_file_del_none,
+ result_pool, scratch_pool));
+
+ /* Put the write callback in place. */
+ content->write = write_file;
+ content->write_baton = target->patched_file;
+ }
+ else
+ {
+ /* Put the write callback in place. */
+ SVN_ERR(svn_io_open_unique_file3(NULL,
+ &target->patched_path, NULL,
+ remove_tempfiles ?
+ svn_io_file_del_on_pool_cleanup :
+ svn_io_file_del_none,
+ result_pool, scratch_pool));
+
+ content->write_baton = (void*)target->patched_path;
+
+ content->write = write_symlink;
+ }
+
+ /* Open a temporary file to write rejected hunks to. */
+ SVN_ERR(svn_io_open_unique_file3(&target->reject_file,
+ &target->reject_path, NULL,
+ remove_tempfiles ?
+ svn_io_file_del_on_pool_cleanup :
+ svn_io_file_del_none,
+ result_pool, scratch_pool));
+
+ /* The reject file needs a diff header. */
+ diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s",
+ target->canon_path_from_patchfile,
+ APR_EOL_STR,
+ target->canon_path_from_patchfile,
+ APR_EOL_STR);
+ len = strlen(diff_header);
+ SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len,
+ &len, scratch_pool));
+
+ /* Handle properties. */
+ if (! target->skipped)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(result_pool, patch->prop_patches);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *prop_name = svn__apr_hash_index_key(hi);
+ svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
+ prop_patch_target_t *prop_target;
+
+ SVN_ERR(init_prop_target(&prop_target,
+ prop_name,
+ prop_patch->operation,
+ wc_ctx, target->local_abspath,
+ result_pool, scratch_pool));
+ svn_hash_sets(target->prop_targets, prop_name, prop_target);
+ }
+ }
+ }
+
+ *patch_target = target;
+ return SVN_NO_ERROR;
+}
+
+/* Read a *LINE from CONTENT. If the line has not been read before
+ * mark the line in CONTENT->LINES.
+ * If a line could be read successfully, increase CONTENT->CURRENT_LINE,
+ * and allocate *LINE in RESULT_POOL.
+ * Do temporary allocations in SCRATCH_POOL.
+ */
+static svn_error_t *
+readline(target_content_t *content,
+ const char **line,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *line_raw;
+ const char *eol_str;
+ svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1;
+
+ if (content->eof || content->readline == NULL)
+ {
+ *line = "";
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR_ASSERT(content->current_line <= max_line);
+ if (content->current_line == max_line)
+ {
+ apr_off_t offset;
+
+ SVN_ERR(content->tell(content->read_baton, &offset,
+ scratch_pool));
+ APR_ARRAY_PUSH(content->lines, apr_off_t) = offset;
+ }
+
+ SVN_ERR(content->readline(content->read_baton, &line_raw,
+ &eol_str, &content->eof,
+ result_pool, scratch_pool));
+ if (content->eol_style == svn_subst_eol_style_none)
+ content->eol_str = eol_str;
+
+ if (line_raw)
+ {
+ /* Contract keywords. */
+ SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
+ NULL, FALSE,
+ content->keywords, FALSE,
+ result_pool));
+ }
+ else
+ *line = "";
+
+ if ((line_raw && line_raw->len > 0) || eol_str)
+ content->current_line++;
+
+ SVN_ERR_ASSERT(content->current_line > 0);
+
+ return SVN_NO_ERROR;
+}
+
+/* Seek to the specified LINE in CONTENT.
+ * Mark any lines not read before in CONTENT->LINES.
+ * Do temporary allocations in SCRATCH_POOL.
+ */
+static svn_error_t *
+seek_to_line(target_content_t *content, svn_linenum_t line,
+ apr_pool_t *scratch_pool)
+{
+ svn_linenum_t saved_line;
+ svn_boolean_t saved_eof;
+
+ SVN_ERR_ASSERT(line > 0);
+
+ if (line == content->current_line)
+ return SVN_NO_ERROR;
+
+ saved_line = content->current_line;
+ saved_eof = content->eof;
+
+ if (line <= (svn_linenum_t)content->lines->nelts)
+ {
+ apr_off_t offset;
+
+ offset = APR_ARRAY_IDX(content->lines, line - 1, apr_off_t);
+ SVN_ERR(content->seek(content->read_baton, offset,
+ scratch_pool));
+ content->current_line = line;
+ }
+ else
+ {
+ const char *dummy;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ while (! content->eof && content->current_line < line)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(readline(content, &dummy, iterpool, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ /* After seeking backwards from EOF position clear EOF indicator. */
+ if (saved_eof && saved_line > content->current_line)
+ content->eof = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Indicate in *MATCHED whether the original text of HUNK matches the patch
+ * CONTENT at its current line. Lines within FUZZ lines of the start or
+ * end of HUNK will always match. If IGNORE_WHITESPACE is set, we ignore
+ * whitespace when doing the matching. When this function returns, neither
+ * CONTENT->CURRENT_LINE nor the file offset in the target file will
+ * have changed. If MATCH_MODIFIED is TRUE, match the modified hunk text,
+ * rather than the original hunk text.
+ * Do temporary allocations in POOL. */
+static svn_error_t *
+match_hunk(svn_boolean_t *matched, target_content_t *content,
+ svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t match_modified, apr_pool_t *pool)
+{
+ svn_stringbuf_t *hunk_line;
+ const char *target_line;
+ svn_linenum_t lines_read;
+ svn_linenum_t saved_line;
+ svn_boolean_t hunk_eof;
+ svn_boolean_t lines_matched;
+ apr_pool_t *iterpool;
+ svn_linenum_t hunk_length;
+ svn_linenum_t leading_context;
+ svn_linenum_t trailing_context;
+
+ *matched = FALSE;
+
+ if (content->eof)
+ return SVN_NO_ERROR;
+
+ saved_line = content->current_line;
+ lines_read = 0;
+ lines_matched = FALSE;
+ leading_context = svn_diff_hunk_get_leading_context(hunk);
+ trailing_context = svn_diff_hunk_get_trailing_context(hunk);
+ if (match_modified)
+ {
+ svn_diff_hunk_reset_modified_text(hunk);
+ hunk_length = svn_diff_hunk_get_modified_length(hunk);
+ }
+ else
+ {
+ svn_diff_hunk_reset_original_text(hunk);
+ hunk_length = svn_diff_hunk_get_original_length(hunk);
+ }
+ iterpool = svn_pool_create(pool);
+ do
+ {
+ const char *hunk_line_translated;
+
+ svn_pool_clear(iterpool);
+
+ if (match_modified)
+ SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
+ NULL, &hunk_eof,
+ iterpool, iterpool));
+ else
+ SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_line,
+ NULL, &hunk_eof,
+ iterpool, iterpool));
+
+ /* Contract keywords, if any, before matching. */
+ SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
+ &hunk_line_translated,
+ NULL, FALSE,
+ content->keywords, FALSE,
+ iterpool));
+ SVN_ERR(readline(content, &target_line, iterpool, iterpool));
+
+ lines_read++;
+
+ /* If the last line doesn't have a newline, we get EOF but still
+ * have a non-empty line to compare. */
+ if ((hunk_eof && hunk_line->len == 0) ||
+ (content->eof && *target_line == 0))
+ break;
+
+ /* Leading/trailing fuzzy lines always match. */
+ if ((lines_read <= fuzz && leading_context > fuzz) ||
+ (lines_read > hunk_length - fuzz && trailing_context > fuzz))
+ lines_matched = TRUE;
+ else
+ {
+ if (ignore_whitespace)
+ {
+ char *hunk_line_trimmed;
+ char *target_line_trimmed;
+
+ hunk_line_trimmed = apr_pstrdup(iterpool, hunk_line_translated);
+ target_line_trimmed = apr_pstrdup(iterpool, target_line);
+ apr_collapse_spaces(hunk_line_trimmed, hunk_line_trimmed);
+ apr_collapse_spaces(target_line_trimmed, target_line_trimmed);
+ lines_matched = ! strcmp(hunk_line_trimmed, target_line_trimmed);
+ }
+ else
+ lines_matched = ! strcmp(hunk_line_translated, target_line);
+ }
+ }
+ while (lines_matched);
+
+ *matched = lines_matched && hunk_eof && hunk_line->len == 0;
+ SVN_ERR(seek_to_line(content, saved_line, iterpool));
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Scan lines of CONTENT for a match of the original text of HUNK,
+ * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
+ * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
+ * Return the line at which HUNK was matched in *MATCHED_LINE.
+ * If the hunk did not match at all, set *MATCHED_LINE to zero.
+ * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
+ * return the line number at which the first match occurred in *MATCHED_LINE.
+ * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
+ * return the line number at which the last match occurred in *MATCHED_LINE.
+ * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
+ * If MATCH_MODIFIED is TRUE, match the modified hunk text,
+ * rather than the original hunk text.
+ * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
+ * Do all allocations in POOL. */
+static svn_error_t *
+scan_for_match(svn_linenum_t *matched_line,
+ target_content_t *content,
+ svn_diff_hunk_t *hunk, svn_boolean_t match_first,
+ svn_linenum_t upper_line, svn_linenum_t fuzz,
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t match_modified,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool;
+
+ *matched_line = 0;
+ iterpool = svn_pool_create(pool);
+ while ((content->current_line < upper_line || upper_line == 0) &&
+ ! content->eof)
+ {
+ svn_boolean_t matched;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(match_hunk(&matched, content, hunk, fuzz, ignore_whitespace,
+ match_modified, iterpool));
+ if (matched)
+ {
+ svn_boolean_t taken = FALSE;
+ int i;
+
+ /* Don't allow hunks to match at overlapping locations. */
+ for (i = 0; i < content->hunks->nelts; i++)
+ {
+ const hunk_info_t *hi;
+ svn_linenum_t length;
+
+ hi = APR_ARRAY_IDX(content->hunks, i, const hunk_info_t *);
+
+ if (match_modified)
+ length = svn_diff_hunk_get_modified_length(hi->hunk);
+ else
+ length = svn_diff_hunk_get_original_length(hi->hunk);
+
+ taken = (! hi->rejected &&
+ content->current_line >= hi->matched_line &&
+ content->current_line < (hi->matched_line + length));
+ if (taken)
+ break;
+ }
+
+ if (! taken)
+ {
+ *matched_line = content->current_line;
+ if (match_first)
+ break;
+ }
+ }
+
+ if (! content->eof)
+ SVN_ERR(seek_to_line(content, content->current_line + 1,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Indicate in *MATCH whether the content described by CONTENT
+ * matches the modified text of HUNK.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+match_existing_target(svn_boolean_t *match,
+ target_content_t *content,
+ svn_diff_hunk_t *hunk,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t lines_matched;
+ apr_pool_t *iterpool;
+ svn_boolean_t hunk_eof;
+ svn_linenum_t saved_line;
+
+ svn_diff_hunk_reset_modified_text(hunk);
+
+ saved_line = content->current_line;
+
+ iterpool = svn_pool_create(scratch_pool);
+ do
+ {
+ const char *line;
+ svn_stringbuf_t *hunk_line;
+ const char *line_translated;
+ const char *hunk_line_translated;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(readline(content, &line, iterpool, iterpool));
+ SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_line,
+ NULL, &hunk_eof,
+ iterpool, iterpool));
+ /* Contract keywords. */
+ SVN_ERR(svn_subst_translate_cstring2(line, &line_translated,
+ NULL, FALSE,
+ content->keywords,
+ FALSE, iterpool));
+ SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
+ &hunk_line_translated,
+ NULL, FALSE,
+ content->keywords,
+ FALSE, iterpool));
+ lines_matched = ! strcmp(line_translated, hunk_line_translated);
+ if (content->eof != hunk_eof)
+ {
+ svn_pool_destroy(iterpool);
+ *match = FALSE;
+ return SVN_NO_ERROR;
+ }
+ }
+ while (lines_matched && ! content->eof && ! hunk_eof);
+ svn_pool_destroy(iterpool);
+
+ *match = (lines_matched && content->eof == hunk_eof);
+ SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Determine the line at which a HUNK applies to CONTENT of the TARGET
+ * file, and return an appropriate hunk_info object in *HI, allocated from
+ * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
+ * line can be determined, set HI->REJECTED to TRUE.
+ * IGNORE_WHITESPACE tells whether whitespace should be considered when
+ * matching. IS_PROP_HUNK indicates whether the hunk patches file content
+ * or a property.
+ * When this function returns, neither CONTENT->CURRENT_LINE nor
+ * the file offset in the target file will have changed.
+ * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
+ * Do temporary allocations in POOL. */
+static svn_error_t *
+get_hunk_info(hunk_info_t **hi, patch_target_t *target,
+ target_content_t *content,
+ svn_diff_hunk_t *hunk, svn_linenum_t fuzz,
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t is_prop_hunk,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ svn_linenum_t matched_line;
+ svn_linenum_t original_start;
+ svn_boolean_t already_applied;
+
+ original_start = svn_diff_hunk_get_original_start(hunk);
+ already_applied = FALSE;
+
+ /* An original offset of zero means that this hunk wants to create
+ * a new file. Don't bother matching hunks in that case, since
+ * the hunk applies at line 1. If the file already exists, the hunk
+ * is rejected, unless the file is versioned and its content matches
+ * the file the patch wants to create. */
+ if (original_start == 0 && fuzz > 0)
+ {
+ matched_line = 0; /* reject any fuzz for new files */
+ }
+ else if (original_start == 0 && ! is_prop_hunk)
+ {
+ if (target->kind_on_disk == svn_node_file)
+ {
+ const svn_io_dirent2_t *dirent;
+ SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE,
+ TRUE, scratch_pool, scratch_pool));
+
+ if (dirent->kind == svn_node_file
+ && !dirent->special
+ && dirent->filesize == 0)
+ {
+ matched_line = 1; /* Matched an on-disk empty file */
+ }
+ else
+ {
+ if (target->db_kind == svn_node_file)
+ {
+ svn_boolean_t file_matches;
+
+ /* ### I can't reproduce anything but a no-match here.
+ The content is already at eof, so any hunk fails */
+ SVN_ERR(match_existing_target(&file_matches, content, hunk,
+ scratch_pool));
+ if (file_matches)
+ {
+ matched_line = 1;
+ already_applied = TRUE;
+ }
+ else
+ matched_line = 0; /* reject */
+ }
+ else
+ matched_line = 0; /* reject */
+ }
+ }
+ else
+ matched_line = 1;
+ }
+ /* Same conditions apply as for the file case above.
+ *
+ * ### Since the hunk says the prop should be added we just assume so for
+ * ### now and don't bother with storing the previous lines and such. When
+ * ### we have the diff operation available we can just check for adds. */
+ else if (original_start == 0 && is_prop_hunk)
+ {
+ if (content->existed)
+ {
+ svn_boolean_t prop_matches;
+
+ SVN_ERR(match_existing_target(&prop_matches, content, hunk,
+ scratch_pool));
+
+ if (prop_matches)
+ {
+ matched_line = 1;
+ already_applied = TRUE;
+ }
+ else
+ matched_line = 0; /* reject */
+ }
+ else
+ matched_line = 1;
+ }
+ else if (original_start > 0 && content->existed)
+ {
+ svn_linenum_t saved_line = content->current_line;
+
+ /* Scan for a match at the line where the hunk thinks it
+ * should be going. */
+ SVN_ERR(seek_to_line(content, original_start, scratch_pool));
+ if (content->current_line != original_start)
+ {
+ /* Seek failed. */
+ matched_line = 0;
+ }
+ else
+ SVN_ERR(scan_for_match(&matched_line, content, hunk, TRUE,
+ original_start + 1, fuzz,
+ ignore_whitespace, FALSE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ if (matched_line != original_start)
+ {
+ /* Check if the hunk is already applied.
+ * We only check for an exact match here, and don't bother checking
+ * for already applied patches with offset/fuzz, because such a
+ * check would be ambiguous. */
+ if (fuzz == 0)
+ {
+ svn_linenum_t modified_start;
+
+ modified_start = svn_diff_hunk_get_modified_start(hunk);
+ if (modified_start == 0)
+ {
+ /* Patch wants to delete the file. */
+ already_applied = target->locally_deleted;
+ }
+ else
+ {
+ SVN_ERR(seek_to_line(content, modified_start,
+ scratch_pool));
+ SVN_ERR(scan_for_match(&matched_line, content,
+ hunk, TRUE,
+ modified_start + 1,
+ fuzz, ignore_whitespace, TRUE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ already_applied = (matched_line == modified_start);
+ }
+ }
+ else
+ already_applied = FALSE;
+
+ if (! already_applied)
+ {
+ /* Scan the whole file again from the start. */
+ SVN_ERR(seek_to_line(content, 1, scratch_pool));
+
+ /* Scan forward towards the hunk's line and look for a line
+ * where the hunk matches. */
+ SVN_ERR(scan_for_match(&matched_line, content, hunk, FALSE,
+ original_start, fuzz,
+ ignore_whitespace, FALSE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* In tie-break situations, we arbitrarily prefer early matches
+ * to save us from scanning the rest of the file. */
+ if (matched_line == 0)
+ {
+ /* Scan forward towards the end of the file and look
+ * for a line where the hunk matches. */
+ SVN_ERR(scan_for_match(&matched_line, content, hunk,
+ TRUE, 0, fuzz, ignore_whitespace,
+ FALSE, cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ }
+ }
+
+ SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
+ }
+ else
+ {
+ /* The hunk wants to modify a file which doesn't exist. */
+ matched_line = 0;
+ }
+
+ (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t));
+ (*hi)->hunk = hunk;
+ (*hi)->matched_line = matched_line;
+ (*hi)->rejected = (matched_line == 0);
+ (*hi)->already_applied = already_applied;
+ (*hi)->fuzz = fuzz;
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy lines to the patched content until the specified LINE has been
+ * reached. Indicate in *EOF whether end-of-file was encountered while
+ * reading from the target.
+ * If LINE is zero, copy lines until end-of-file has been reached.
+ * Do all allocations in POOL. */
+static svn_error_t *
+copy_lines_to_target(target_content_t *content, svn_linenum_t line,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(pool);
+ while ((content->current_line < line || line == 0) && ! content->eof)
+ {
+ const char *target_line;
+ apr_size_t len;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(readline(content, &target_line, iterpool, iterpool));
+ if (! content->eof)
+ target_line = apr_pstrcat(iterpool, target_line, content->eol_str,
+ (char *)NULL);
+ len = strlen(target_line);
+ SVN_ERR(content->write(content->write_baton, target_line,
+ len, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Write the diff text of HUNK to TARGET's reject file,
+ * and mark TARGET as having had rejects.
+ * We don't expand keywords, nor normalise line-endings, in reject files.
+ * Do temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+reject_hunk(patch_target_t *target, target_content_t *content,
+ svn_diff_hunk_t *hunk, const char *prop_name,
+ apr_pool_t *pool)
+{
+ const char *hunk_header;
+ apr_size_t len;
+ svn_boolean_t eof;
+ static const char * const text_atat = "@@";
+ static const char * const prop_atat = "##";
+ const char *atat;
+ apr_pool_t *iterpool;
+
+ if (prop_name)
+ {
+ const char *prop_header;
+
+ /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'.
+ */
+ prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
+ len = strlen(prop_header);
+ SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header,
+ len, &len, pool));
+ atat = prop_atat;
+ }
+ else
+ {
+ atat = text_atat;
+ }
+
+ hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s",
+ atat,
+ svn_diff_hunk_get_original_start(hunk),
+ svn_diff_hunk_get_original_length(hunk),
+ svn_diff_hunk_get_modified_start(hunk),
+ svn_diff_hunk_get_modified_length(hunk),
+ atat,
+ APR_EOL_STR);
+ len = strlen(hunk_header);
+ SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len,
+ &len, pool));
+
+ iterpool = svn_pool_create(pool);
+ do
+ {
+ svn_stringbuf_t *hunk_line;
+ const char *eol_str;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
+ &eof, iterpool, iterpool));
+ if (! eof)
+ {
+ if (hunk_line->len >= 1)
+ {
+ len = hunk_line->len;
+ SVN_ERR(svn_io_file_write_full(target->reject_file,
+ hunk_line->data, len, &len,
+ iterpool));
+ }
+
+ if (eol_str)
+ {
+ len = strlen(eol_str);
+ SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str,
+ len, &len, iterpool));
+ }
+ }
+ }
+ while (! eof);
+ svn_pool_destroy(iterpool);
+
+ if (prop_name)
+ target->had_prop_rejects = TRUE;
+ else
+ target->had_rejects = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Write the modified text of the hunk described by HI to the patched
+ * CONTENT. TARGET is the patch target.
+ * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
+ * a property with the given name.
+ * Do temporary allocations in POOL. */
+static svn_error_t *
+apply_hunk(patch_target_t *target, target_content_t *content,
+ hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
+{
+ svn_linenum_t lines_read;
+ svn_boolean_t eof;
+ apr_pool_t *iterpool;
+
+ /* ### Is there a cleaner way to describe if we have an existing target?
+ */
+ if (target->kind_on_disk == svn_node_file || prop_name)
+ {
+ svn_linenum_t line;
+
+ /* Move forward to the hunk's line, copying data as we go.
+ * Also copy leading lines of context which matched with fuzz.
+ * The target has changed on the fuzzy-matched lines,
+ * so we should retain the target's version of those lines. */
+ SVN_ERR(copy_lines_to_target(content, hi->matched_line + hi->fuzz,
+ pool));
+
+ /* Skip the target's version of the hunk.
+ * Don't skip trailing lines which matched with fuzz. */
+ line = content->current_line +
+ svn_diff_hunk_get_original_length(hi->hunk) - (2 * hi->fuzz);
+ SVN_ERR(seek_to_line(content, line, pool));
+ if (content->current_line != line && ! content->eof)
+ {
+ /* Seek failed, reject this hunk. */
+ hi->rejected = TRUE;
+ SVN_ERR(reject_hunk(target, content, hi->hunk, prop_name, pool));
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Write the hunk's version to the patched result.
+ * Don't write the lines which matched with fuzz. */
+ lines_read = 0;
+ svn_diff_hunk_reset_modified_text(hi->hunk);
+ iterpool = svn_pool_create(pool);
+ do
+ {
+ svn_stringbuf_t *hunk_line;
+ const char *eol_str;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_diff_hunk_readline_modified_text(hi->hunk, &hunk_line,
+ &eol_str, &eof,
+ iterpool, iterpool));
+ lines_read++;
+ if (lines_read > hi->fuzz &&
+ lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz)
+ {
+ apr_size_t len;
+
+ if (hunk_line->len >= 1)
+ {
+ len = hunk_line->len;
+ SVN_ERR(content->write(content->write_baton,
+ hunk_line->data, len, iterpool));
+ }
+
+ if (eol_str)
+ {
+ /* Use the EOL as it was read from the patch file,
+ * unless the target's EOL style is set by svn:eol-style */
+ if (content->eol_style != svn_subst_eol_style_none)
+ eol_str = content->eol_str;
+
+ len = strlen(eol_str);
+ SVN_ERR(content->write(content->write_baton,
+ eol_str, len, iterpool));
+ }
+ }
+ }
+ while (! eof);
+ svn_pool_destroy(iterpool);
+
+ if (prop_name)
+ target->has_prop_changes = TRUE;
+ else
+ target->has_text_changes = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Use client context CTX to send a suitable notification for hunk HI,
+ * using TARGET to determine the path. If the hunk is a property hunk,
+ * PROP_NAME must be the name of the property, else NULL.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+send_hunk_notification(const hunk_info_t *hi,
+ const patch_target_t *target,
+ const char *prop_name,
+ const svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_wc_notify_t *notify;
+ svn_wc_notify_action_t action;
+
+ if (hi->already_applied)
+ action = svn_wc_notify_patch_hunk_already_applied;
+ else if (hi->rejected)
+ action = svn_wc_notify_patch_rejected_hunk;
+ else
+ action = svn_wc_notify_patch_applied_hunk;
+
+ notify = svn_wc_create_notify(target->local_abspath
+ ? target->local_abspath
+ : target->local_relpath,
+ action, pool);
+ notify->hunk_original_start =
+ svn_diff_hunk_get_original_start(hi->hunk);
+ notify->hunk_original_length =
+ svn_diff_hunk_get_original_length(hi->hunk);
+ notify->hunk_modified_start =
+ svn_diff_hunk_get_modified_start(hi->hunk);
+ notify->hunk_modified_length =
+ svn_diff_hunk_get_modified_length(hi->hunk);
+ notify->hunk_matched_line = hi->matched_line;
+ notify->hunk_fuzz = hi->fuzz;
+ notify->prop_name = prop_name;
+
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Use client context CTX to send a suitable notification for a patch TARGET.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+send_patch_notification(const patch_target_t *target,
+ const svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_wc_notify_t *notify;
+ svn_wc_notify_action_t action;
+
+ if (! ctx->notify_func2)
+ return SVN_NO_ERROR;
+
+ if (target->skipped)
+ action = svn_wc_notify_skip;
+ else if (target->deleted)
+ action = svn_wc_notify_delete;
+ else if (target->added || target->replaced)
+ action = svn_wc_notify_add;
+ else
+ action = svn_wc_notify_patch;
+
+ notify = svn_wc_create_notify(target->local_abspath ? target->local_abspath
+ : target->local_relpath,
+ action, pool);
+ notify->kind = svn_node_file;
+
+ if (action == svn_wc_notify_skip)
+ {
+ if (target->db_kind == svn_node_none ||
+ target->db_kind == svn_node_unknown)
+ notify->content_state = svn_wc_notify_state_missing;
+ else if (target->db_kind == svn_node_dir)
+ notify->content_state = svn_wc_notify_state_obstructed;
+ else
+ notify->content_state = svn_wc_notify_state_unknown;
+ }
+ else
+ {
+ if (target->had_rejects)
+ notify->content_state = svn_wc_notify_state_conflicted;
+ else if (target->local_mods)
+ notify->content_state = svn_wc_notify_state_merged;
+ else if (target->has_text_changes)
+ notify->content_state = svn_wc_notify_state_changed;
+
+ if (target->had_prop_rejects)
+ notify->prop_state = svn_wc_notify_state_conflicted;
+ else if (target->has_prop_changes)
+ notify->prop_state = svn_wc_notify_state_changed;
+ }
+
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+
+ if (action == svn_wc_notify_patch)
+ {
+ int i;
+ apr_pool_t *iterpool;
+ apr_hash_index_t *hash_index;
+
+ iterpool = svn_pool_create(pool);
+ for (i = 0; i < target->content->hunks->nelts; i++)
+ {
+ const hunk_info_t *hi;
+
+ svn_pool_clear(iterpool);
+
+ hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
+
+ SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */,
+ ctx, iterpool));
+ }
+
+ for (hash_index = apr_hash_first(pool, target->prop_targets);
+ hash_index;
+ hash_index = apr_hash_next(hash_index))
+ {
+ prop_patch_target_t *prop_target;
+
+ prop_target = svn__apr_hash_index_val(hash_index);
+
+ for (i = 0; i < prop_target->content->hunks->nelts; i++)
+ {
+ const hunk_info_t *hi;
+
+ svn_pool_clear(iterpool);
+
+ hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
+ hunk_info_t *);
+
+ /* Don't notify on the hunk level for added or deleted props. */
+ if (prop_target->operation != svn_diff_op_added &&
+ prop_target->operation != svn_diff_op_deleted)
+ SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
+ ctx, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
+ * into temporary files, to be installed in the working copy later.
+ * Return information about the patch target in *PATCH_TARGET, allocated
+ * in RESULT_POOL. Use WC_CTX as the working copy context.
+ * STRIP_COUNT specifies the number of leading path components
+ * which should be stripped from target paths in the patch.
+ * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch().
+ * IGNORE_WHITESPACE tells whether whitespace should be considered when
+ * doing the matching.
+ * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
+ * Do temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
+ const char *abs_wc_path, svn_wc_context_t *wc_ctx,
+ int strip_count,
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t remove_tempfiles,
+ svn_client_patch_func_t patch_func,
+ void *patch_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ patch_target_t *target;
+ apr_pool_t *iterpool;
+ int i;
+ static const svn_linenum_t MAX_FUZZ = 2;
+ apr_hash_index_t *hash_index;
+
+ SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
+ remove_tempfiles, result_pool, scratch_pool));
+ if (target->skipped)
+ {
+ *patch_target = target;
+ return SVN_NO_ERROR;
+ }
+
+ if (patch_func)
+ {
+ SVN_ERR(patch_func(patch_baton, &target->filtered,
+ target->canon_path_from_patchfile,
+ target->patched_path, target->reject_path,
+ scratch_pool));
+ if (target->filtered)
+ {
+ *patch_target = target;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+ /* Match hunks. */
+ for (i = 0; i < patch->hunks->nelts; i++)
+ {
+ svn_diff_hunk_t *hunk;
+ hunk_info_t *hi;
+ svn_linenum_t fuzz = 0;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
+
+ /* Determine the line the hunk should be applied at.
+ * If no match is found initially, try with fuzz. */
+ do
+ {
+ SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
+ ignore_whitespace,
+ FALSE /* is_prop_hunk */,
+ cancel_func, cancel_baton,
+ result_pool, iterpool));
+ fuzz++;
+ }
+ while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
+
+ APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
+ }
+
+ /* Apply or reject hunks. */
+ for (i = 0; i < target->content->hunks->nelts; i++)
+ {
+ hunk_info_t *hi;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
+ if (hi->already_applied)
+ continue;
+ else if (hi->rejected)
+ SVN_ERR(reject_hunk(target, target->content, hi->hunk,
+ NULL /* prop_name */,
+ iterpool));
+ else
+ SVN_ERR(apply_hunk(target, target->content, hi,
+ NULL /* prop_name */, iterpool));
+ }
+
+ if (target->kind_on_disk == svn_node_file)
+ {
+ /* Copy any remaining lines to target. */
+ SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
+ if (! target->content->eof)
+ {
+ /* We could not copy the entire target file to the temporary file,
+ * and would truncate the target if we copied the temporary file
+ * on top of it. Skip this target. */
+ target->skipped = TRUE;
+ }
+ }
+
+ /* Match property hunks. */
+ for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
+ hash_index;
+ hash_index = apr_hash_next(hash_index))
+ {
+ svn_prop_patch_t *prop_patch;
+ const char *prop_name;
+ prop_patch_target_t *prop_target;
+
+ prop_name = svn__apr_hash_index_key(hash_index);
+ prop_patch = svn__apr_hash_index_val(hash_index);
+
+ if (! strcmp(prop_name, SVN_PROP_SPECIAL))
+ target->is_special = TRUE;
+
+ /* We'll store matched hunks in prop_content. */
+ prop_target = svn_hash_gets(target->prop_targets, prop_name);
+
+ for (i = 0; i < prop_patch->hunks->nelts; i++)
+ {
+ svn_diff_hunk_t *hunk;
+ hunk_info_t *hi;
+ svn_linenum_t fuzz = 0;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_diff_hunk_t *);
+
+ /* Determine the line the hunk should be applied at.
+ * If no match is found initially, try with fuzz. */
+ do
+ {
+ SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
+ hunk, fuzz,
+ ignore_whitespace,
+ TRUE /* is_prop_hunk */,
+ cancel_func, cancel_baton,
+ result_pool, iterpool));
+ fuzz++;
+ }
+ while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
+
+ APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
+ }
+ }
+
+ /* Apply or reject property hunks. */
+ for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
+ hash_index;
+ hash_index = apr_hash_next(hash_index))
+ {
+ prop_patch_target_t *prop_target;
+
+ prop_target = svn__apr_hash_index_val(hash_index);
+
+ for (i = 0; i < prop_target->content->hunks->nelts; i++)
+ {
+ hunk_info_t *hi;
+
+ svn_pool_clear(iterpool);
+
+ hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
+ hunk_info_t *);
+ if (hi->already_applied)
+ continue;
+ else if (hi->rejected)
+ SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
+ prop_target->name,
+ iterpool));
+ else
+ SVN_ERR(apply_hunk(target, prop_target->content, hi,
+ prop_target->name,
+ iterpool));
+ }
+
+ if (prop_target->content->existed)
+ {
+ /* Copy any remaining lines to target. */
+ SVN_ERR(copy_lines_to_target(prop_target->content, 0,
+ scratch_pool));
+ if (! prop_target->content->eof)
+ {
+ /* We could not copy the entire target property to the
+ * temporary file, and would truncate the target if we
+ * copied the temporary file on top of it. Skip this target. */
+ target->skipped = TRUE;
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ if (!target->is_symlink)
+ {
+ /* Now close files we don't need any longer to get their contents
+ * flushed to disk.
+ * But we're not closing the reject file -- it still needed and
+ * will be closed later in write_out_rejected_hunks(). */
+ if (target->kind_on_disk == svn_node_file)
+ SVN_ERR(svn_io_file_close(target->file, scratch_pool));
+
+ SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
+ }
+
+ if (! target->skipped)
+ {
+ apr_finfo_t working_file;
+ apr_finfo_t patched_file;
+
+ /* Get sizes of the patched temporary file and the working file.
+ * We'll need those to figure out whether we should delete the
+ * patched file. */
+ SVN_ERR(svn_io_stat(&patched_file, target->patched_path,
+ APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
+ if (target->kind_on_disk == svn_node_file)
+ SVN_ERR(svn_io_stat(&working_file, target->local_abspath,
+ APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
+ else
+ working_file.size = 0;
+
+ if (patched_file.size == 0 && working_file.size > 0)
+ {
+ /* If a unidiff removes all lines from a file, that usually
+ * means deletion, so we can confidently schedule the target
+ * for deletion. In the rare case where the unidiff was really
+ * meant to replace a file with an empty one, this may not
+ * be desirable. But the deletion can easily be reverted and
+ * creating an empty file manually is not exactly hard either. */
+ target->deleted = (target->db_kind == svn_node_file);
+ }
+ else if (patched_file.size == 0 && working_file.size == 0)
+ {
+ /* The target was empty or non-existent to begin with
+ * and no content was changed by patching.
+ * Report this as skipped if it didn't exist, unless in the special
+ * case of adding an empty file which has properties set on it or
+ * adding an empty file with a 'git diff' */
+ if (target->kind_on_disk == svn_node_none
+ && ! target->has_prop_changes
+ && ! target->added)
+ target->skipped = TRUE;
+ }
+ else if (patched_file.size > 0 && working_file.size == 0)
+ {
+ /* The patch has created a file. */
+ if (target->locally_deleted)
+ target->replaced = TRUE;
+ else if (target->db_kind == svn_node_none)
+ target->added = TRUE;
+ }
+ }
+
+ *patch_target = target;
+
+ return SVN_NO_ERROR;
+}
+
+/* Try to create missing parent directories for TARGET in the working copy
+ * rooted at ABS_WC_PATH, and add the parents to version control.
+ * If the parents cannot be created, mark the target as skipped.
+ * Use client context CTX. If DRY_RUN is true, do not create missing
+ * parents but issue notifications only.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+create_missing_parents(patch_target_t *target,
+ const char *abs_wc_path,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t dry_run,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ apr_array_header_t *components;
+ int present_components;
+ int i;
+ apr_pool_t *iterpool;
+
+ /* Check if we can safely create the target's parent. */
+ local_abspath = abs_wc_path;
+ components = svn_path_decompose(target->local_relpath, scratch_pool);
+ present_components = 0;
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < components->nelts - 1; i++)
+ {
+ const char *component;
+ svn_node_kind_t wc_kind, disk_kind;
+
+ svn_pool_clear(iterpool);
+
+ component = APR_ARRAY_IDX(components, i, const char *);
+ local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
+
+ SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath,
+ FALSE, TRUE, iterpool));
+
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
+
+ if (disk_kind == svn_node_file || wc_kind == svn_node_file)
+ {
+ /* on-disk files and missing files are obstructions */
+ target->skipped = TRUE;
+ break;
+ }
+ else if (disk_kind == svn_node_dir)
+ {
+ if (wc_kind == svn_node_dir)
+ present_components++;
+ else
+ {
+ target->skipped = TRUE;
+ break;
+ }
+ }
+ else if (wc_kind != svn_node_none)
+ {
+ /* Node is missing */
+ target->skipped = TRUE;
+ break;
+ }
+ else
+ {
+ /* It's not a file, it's not a dir...
+ Let's add a dir */
+ break;
+ }
+ }
+ if (! target->skipped)
+ {
+ local_abspath = abs_wc_path;
+ for (i = 0; i < present_components; i++)
+ {
+ const char *component;
+ component = APR_ARRAY_IDX(components, i, const char *);
+ local_abspath = svn_dirent_join(local_abspath,
+ component, scratch_pool);
+ }
+
+ if (!dry_run && present_components < components->nelts - 1)
+ SVN_ERR(svn_io_make_dir_recursively(
+ svn_dirent_join(
+ abs_wc_path,
+ svn_relpath_dirname(target->local_relpath,
+ scratch_pool),
+ scratch_pool),
+ scratch_pool));
+
+ for (i = present_components; i < components->nelts - 1; i++)
+ {
+ const char *component;
+
+ svn_pool_clear(iterpool);
+
+ component = APR_ARRAY_IDX(components, i, const char *);
+ local_abspath = svn_dirent_join(local_abspath, component,
+ scratch_pool);
+ if (dry_run)
+ {
+ if (ctx->notify_func2)
+ {
+ /* Just do notification. */
+ svn_wc_notify_t *notify;
+ notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_add,
+ iterpool);
+ notify->kind = svn_node_dir;
+ ctx->notify_func2(ctx->notify_baton2, notify,
+ iterpool);
+ }
+ }
+ else
+ {
+ /* Create the missing component and add it
+ * to version control. Allow cancellation since we
+ * have not modified the working copy yet for this
+ * target. */
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath,
+ NULL /*props*/,
+ ctx->notify_func2, ctx->notify_baton2,
+ iterpool));
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Install a patched TARGET into the working copy at ABS_WC_PATH.
+ * Use client context CTX to retrieve WC_CTX, and possibly doing
+ * notifications. If DRY_RUN is TRUE, don't modify the working copy.
+ * Do temporary allocations in POOL. */
+static svn_error_t *
+install_patched_target(patch_target_t *target, const char *abs_wc_path,
+ svn_client_ctx_t *ctx, svn_boolean_t dry_run,
+ apr_pool_t *pool)
+{
+ if (target->deleted)
+ {
+ if (! dry_run)
+ {
+ /* Schedule the target for deletion. Suppress
+ * notification, we'll do it manually in a minute
+ * because we also need to notify during dry-run.
+ * Also suppress cancellation, because we'd rather
+ * notify about what we did before aborting. */
+ SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
+ FALSE /* keep_local */, FALSE,
+ NULL, NULL, NULL, NULL, pool));
+ }
+ }
+ else
+ {
+ svn_node_kind_t parent_db_kind;
+ if (target->added || target->replaced)
+ {
+ const char *parent_abspath;
+
+ parent_abspath = svn_dirent_dirname(target->local_abspath,
+ pool);
+ /* If the target's parent directory does not yet exist
+ * we need to create it before we can copy the patched
+ * result in place. */
+ SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx,
+ parent_abspath, FALSE, FALSE, pool));
+
+ /* We can't add targets under nodes scheduled for delete, so add
+ a new directory if needed. */
+ if (parent_db_kind == svn_node_dir
+ || parent_db_kind == svn_node_file)
+ {
+ if (parent_db_kind != svn_node_dir)
+ target->skipped = TRUE;
+ else
+ {
+ svn_node_kind_t disk_kind;
+
+ SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool));
+ if (disk_kind != svn_node_dir)
+ target->skipped = TRUE;
+ }
+ }
+ else
+ SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
+ dry_run, pool));
+
+ }
+ else
+ {
+ svn_node_kind_t wc_kind;
+
+ /* The target should exist */
+ SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx,
+ target->local_abspath,
+ FALSE, FALSE, pool));
+
+ if (target->kind_on_disk == svn_node_none
+ || wc_kind != target->kind_on_disk)
+ {
+ target->skipped = TRUE;
+ }
+ }
+
+ if (! dry_run && ! target->skipped)
+ {
+ if (target->is_special)
+ {
+ svn_stream_t *stream;
+ svn_stream_t *patched_stream;
+
+ SVN_ERR(svn_stream_open_readonly(&patched_stream,
+ target->patched_path,
+ pool, pool));
+ SVN_ERR(svn_subst_create_specialfile(&stream,
+ target->local_abspath,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(patched_stream, stream,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+ }
+ else
+ {
+ svn_boolean_t repair_eol;
+
+ /* Copy the patched file on top of the target file.
+ * Always expand keywords in the patched file, but repair EOL
+ * only if svn:eol-style dictates a particular style. */
+ repair_eol = (target->content->eol_style ==
+ svn_subst_eol_style_fixed ||
+ target->content->eol_style ==
+ svn_subst_eol_style_native);
+
+ SVN_ERR(svn_subst_copy_and_translate4(
+ target->patched_path, target->local_abspath,
+ target->content->eol_str, repair_eol,
+ target->content->keywords,
+ TRUE /* expand */, FALSE /* special */,
+ ctx->cancel_func, ctx->cancel_baton, pool));
+ }
+
+ if (target->added || target->replaced)
+ {
+ /* The target file didn't exist previously,
+ * so add it to version control.
+ * Suppress notification, we'll do that later (and also
+ * during dry-run). Don't allow cancellation because
+ * we'd rather notify about what we did before aborting. */
+ SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
+ NULL /*props*/,
+ NULL, NULL, pool));
+ }
+
+ /* Restore the target's executable bit if necessary. */
+ SVN_ERR(svn_io_set_file_executable(target->local_abspath,
+ target->executable,
+ FALSE, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
+ * TRUE, don't modify the working copy.
+ * Do temporary allocations in POOL.
+ */
+static svn_error_t *
+write_out_rejected_hunks(patch_target_t *target,
+ svn_boolean_t dry_run,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_io_file_close(target->reject_file, pool));
+
+ if (! dry_run && (target->had_rejects || target->had_prop_rejects))
+ {
+ /* Write out rejected hunks, if any. */
+ SVN_ERR(svn_io_copy_file(target->reject_path,
+ apr_psprintf(pool, "%s.svnpatch.rej",
+ target->local_abspath),
+ FALSE, pool));
+ /* ### TODO mark file as conflicted. */
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Install the patched properties for TARGET. Use client context CTX to
+ * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
+ * Do temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+install_patched_prop_targets(patch_target_t *target,
+ svn_client_ctx_t *ctx, svn_boolean_t dry_run,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, target->prop_targets);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ prop_patch_target_t *prop_target = svn__apr_hash_index_val(hi);
+ const svn_string_t *prop_val;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ /* For a deleted prop we only set the value to NULL. */
+ if (prop_target->operation == svn_diff_op_deleted)
+ {
+ if (! dry_run)
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+ prop_target->name, NULL, svn_depth_empty,
+ TRUE /* skip_checks */,
+ NULL /* changelist_filter */,
+ NULL, NULL /* cancellation */,
+ NULL, NULL /* notification */,
+ iterpool));
+ continue;
+ }
+
+ /* If the patch target doesn't exist yet, the patch wants to add an
+ * empty file with properties set on it. So create an empty file and
+ * add it to version control. But if the patch was in the 'git format'
+ * then the file has already been added.
+ *
+ * ### How can we tell whether the patch really wanted to create
+ * ### an empty directory? */
+ if (! target->has_text_changes
+ && target->kind_on_disk == svn_node_none
+ && ! target->added)
+ {
+ if (! dry_run)
+ {
+ SVN_ERR(svn_io_file_create(target->local_abspath, "",
+ scratch_pool));
+ SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath,
+ NULL /*props*/,
+ /* suppress notification */
+ NULL, NULL,
+ iterpool));
+ }
+ target->added = TRUE;
+ }
+
+ /* Attempt to set the property, and reject all hunks if this
+ fails. If the property had a non-empty value, but now has
+ an empty one, we'll just delete the property altogether. */
+ if (prop_target->value && prop_target->value->len
+ && prop_target->patched_value && !prop_target->patched_value->len)
+ prop_val = NULL;
+ else
+ prop_val = svn_stringbuf__morph_into_string(prop_target->patched_value);
+
+ if (dry_run)
+ {
+ const svn_string_t *canon_propval;
+
+ err = svn_wc_canonicalize_svn_prop(&canon_propval,
+ prop_target->name,
+ prop_val, target->local_abspath,
+ target->db_kind,
+ TRUE, /* ### Skipping checks */
+ NULL, NULL,
+ iterpool);
+ }
+ else
+ {
+ err = svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+ prop_target->name, prop_val, svn_depth_empty,
+ TRUE /* skip_checks */,
+ NULL /* changelist_filter */,
+ NULL, NULL /* cancellation */,
+ NULL, NULL /* notification */,
+ iterpool);
+ }
+
+ if (err)
+ {
+ /* ### The errors which svn_wc_canonicalize_svn_prop() will
+ * ### return aren't documented. */
+ if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
+ err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
+ err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
+ err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
+ err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
+ {
+ int i;
+
+ svn_error_clear(err);
+
+ for (i = 0; i < prop_target->content->hunks->nelts; i++)
+ {
+ hunk_info_t *hunk_info;
+
+ hunk_info = APR_ARRAY_IDX(prop_target->content->hunks,
+ i, hunk_info_t *);
+ hunk_info->rejected = TRUE;
+ SVN_ERR(reject_hunk(target, prop_target->content,
+ hunk_info->hunk, prop_target->name,
+ iterpool));
+ }
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for can_delete_callback */
+struct can_delete_baton_t
+{
+ svn_boolean_t must_keep;
+ const apr_array_header_t *targets_info;
+ const char *local_abspath;
+};
+
+/* Implements svn_wc_status_func4_t. */
+static svn_error_t *
+can_delete_callback(void *baton,
+ const char *abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *pool)
+{
+ struct can_delete_baton_t *cb = baton;
+ int i;
+
+ switch(status->node_status)
+ {
+ case svn_wc_status_none:
+ case svn_wc_status_deleted:
+ return SVN_NO_ERROR;
+
+ default:
+ if (! strcmp(cb->local_abspath, abspath))
+ return SVN_NO_ERROR; /* Only interested in descendants */
+
+ for (i = 0; i < cb->targets_info->nelts; i++)
+ {
+ const patch_target_info_t *target_info =
+ APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *);
+
+ if (! strcmp(target_info->local_abspath, abspath))
+ {
+ if (target_info->deleted)
+ return SVN_NO_ERROR;
+
+ break; /* Cease invocation; must keep */
+ }
+ }
+
+ cb->must_keep = TRUE;
+
+ return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
+ }
+}
+
+static svn_error_t *
+check_ancestor_delete(const char *deleted_target,
+ apr_array_header_t *targets_info,
+ const char *apply_root,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct can_delete_baton_t cb;
+ svn_error_t *err;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool);
+
+ while (svn_dirent_is_child(apply_root, dir_abspath, iterpool))
+ {
+ svn_pool_clear(iterpool);
+
+ cb.local_abspath = dir_abspath;
+ cb.must_keep = FALSE;
+ cb.targets_info = targets_info;
+
+ err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity,
+ TRUE, FALSE, FALSE, NULL,
+ can_delete_callback, &cb,
+ ctx->cancel_func, ctx->cancel_baton,
+ iterpool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ }
+
+ if (cb.must_keep)
+ {
+ break;
+ }
+
+ if (! dry_run)
+ {
+ SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE,
+ ctx->cancel_func, ctx->cancel_baton,
+ NULL, NULL,
+ scratch_pool));
+ }
+
+ {
+ patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti));
+
+ pti->local_abspath = apr_pstrdup(result_pool, dir_abspath);
+ pti->deleted = TRUE;
+
+ APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
+ }
+
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete,
+ iterpool);
+ notify->kind = svn_node_dir;
+
+ ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
+ }
+
+ /* And check if we must also delete the parent */
+ dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* This function is the main entry point into the patch code. */
+static svn_error_t *
+apply_patches(/* The path to the patch file. */
+ const char *patch_abspath,
+ /* The abspath to the working copy the patch should be applied to. */
+ const char *abs_wc_path,
+ /* Indicates whether we're doing a dry run. */
+ svn_boolean_t dry_run,
+ /* Number of leading components to strip from patch target paths. */
+ int strip_count,
+ /* Whether to apply the patch in reverse. */
+ svn_boolean_t reverse,
+ /* Whether to ignore whitespace when matching context lines. */
+ svn_boolean_t ignore_whitespace,
+ /* As in svn_client_patch(). */
+ svn_boolean_t remove_tempfiles,
+ /* As in svn_client_patch(). */
+ svn_client_patch_func_t patch_func,
+ void *patch_baton,
+ /* The client context. */
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_patch_t *patch;
+ apr_pool_t *iterpool;
+ svn_patch_file_t *patch_file;
+ apr_array_header_t *targets_info;
+
+ /* Try to open the patch file. */
+ SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool));
+
+ /* Apply patches. */
+ targets_info = apr_array_make(scratch_pool, 0,
+ sizeof(patch_target_info_t *));
+ iterpool = svn_pool_create(scratch_pool);
+ do
+ {
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
+ reverse, ignore_whitespace,
+ iterpool, iterpool));
+ if (patch)
+ {
+ patch_target_t *target;
+
+ SVN_ERR(apply_one_patch(&target, patch, abs_wc_path,
+ ctx->wc_ctx, strip_count,
+ ignore_whitespace, remove_tempfiles,
+ patch_func, patch_baton,
+ ctx->cancel_func, ctx->cancel_baton,
+ iterpool, iterpool));
+ if (! target->filtered)
+ {
+ /* Save info we'll still need when we're done patching. */
+ patch_target_info_t *target_info =
+ apr_pcalloc(scratch_pool, sizeof(patch_target_info_t));
+ target_info->local_abspath = apr_pstrdup(scratch_pool,
+ target->local_abspath);
+ target_info->deleted = target->deleted;
+
+ if (! target->skipped)
+ {
+ APR_ARRAY_PUSH(targets_info,
+ patch_target_info_t *) = target_info;
+
+ if (target->has_text_changes
+ || target->added
+ || target->deleted)
+ SVN_ERR(install_patched_target(target, abs_wc_path,
+ ctx, dry_run, iterpool));
+
+ if (target->has_prop_changes && (!target->deleted))
+ SVN_ERR(install_patched_prop_targets(target, ctx,
+ dry_run, iterpool));
+
+ SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool));
+ }
+ SVN_ERR(send_patch_notification(target, ctx, iterpool));
+
+ if (target->deleted && !target->skipped)
+ {
+ SVN_ERR(check_ancestor_delete(target_info->local_abspath,
+ targets_info, abs_wc_path,
+ dry_run, ctx,
+ scratch_pool, iterpool));
+ }
+ }
+ }
+ }
+ while (patch);
+
+ SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_patch(const char *patch_abspath,
+ const char *wc_dir_abspath,
+ svn_boolean_t dry_run,
+ int strip_count,
+ svn_boolean_t reverse,
+ svn_boolean_t ignore_whitespace,
+ svn_boolean_t remove_tempfiles,
+ svn_client_patch_func_t patch_func,
+ void *patch_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+
+ if (strip_count < 0)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("strip count must be positive"));
+
+ if (svn_path_is_url(wc_dir_abspath))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"),
+ svn_dirent_local_style(wc_dir_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_io_check_path(patch_abspath, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' does not exist"),
+ svn_dirent_local_style(patch_abspath,
+ scratch_pool));
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a file"),
+ svn_dirent_local_style(patch_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_io_check_path(wc_dir_abspath, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' does not exist"),
+ svn_dirent_local_style(wc_dir_abspath,
+ scratch_pool));
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a directory"),
+ svn_dirent_local_style(wc_dir_abspath,
+ scratch_pool));
+
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count,
+ reverse, ignore_whitespace, remove_tempfiles,
+ patch_func, patch_baton, ctx, scratch_pool),
+ ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool);
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/prop_commands.c b/contrib/subversion/subversion/libsvn_client/prop_commands.c
new file mode 100644
index 0000000..c3c1cfa
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/prop_commands.c
@@ -0,0 +1,1559 @@
+/*
+ * prop_commands.c: Implementation of propset, propget, and proplist.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include "svn_error.h"
+#include "svn_client.h"
+#include "client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_hash.h"
+#include "svn_sorts.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_client_private.h"
+
+
+/*** Code. ***/
+
+/* Return an SVN_ERR_CLIENT_PROPERTY_NAME error if NAME is a wcprop,
+ else return SVN_NO_ERROR. */
+static svn_error_t *
+error_if_wcprop_name(const char *name)
+{
+ if (svn_property_kind2(name) == svn_prop_wc_kind)
+ {
+ return svn_error_createf
+ (SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is a wcprop, thus not accessible to clients"),
+ name);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct getter_baton
+{
+ svn_ra_session_t *ra_session;
+ svn_revnum_t base_revision_for_url;
+};
+
+
+static svn_error_t *
+get_file_for_validation(const svn_string_t **mime_type,
+ svn_stream_t *stream,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct getter_baton *gb = baton;
+ svn_ra_session_t *ra_session = gb->ra_session;
+ apr_hash_t *props;
+
+ SVN_ERR(svn_ra_get_file(ra_session, "", gb->base_revision_for_url,
+ stream, NULL,
+ (mime_type ? &props : NULL),
+ pool));
+
+ if (mime_type)
+ *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE);
+
+ return SVN_NO_ERROR;
+}
+
+
+static
+svn_error_t *
+do_url_propset(const char *url,
+ const char *propname,
+ const svn_string_t *propval,
+ const svn_node_kind_t kind,
+ const svn_revnum_t base_revision_for_url,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool)
+{
+ void *root_baton;
+
+ SVN_ERR(editor->open_root(edit_baton, base_revision_for_url, pool,
+ &root_baton));
+
+ if (kind == svn_node_file)
+ {
+ void *file_baton;
+ const char *uri_basename = svn_uri_basename(url, pool);
+
+ SVN_ERR(editor->open_file(uri_basename, root_baton,
+ base_revision_for_url, pool, &file_baton));
+ SVN_ERR(editor->change_file_prop(file_baton, propname, propval, pool));
+ SVN_ERR(editor->close_file(file_baton, NULL, pool));
+ }
+ else
+ {
+ SVN_ERR(editor->change_dir_prop(root_baton, propname, propval, pool));
+ }
+
+ return editor->close_directory(root_baton, pool);
+}
+
+static svn_error_t *
+propset_on_url(const char *propname,
+ const svn_string_t *propval,
+ const char *target,
+ svn_boolean_t skip_checks,
+ svn_revnum_t base_revision_for_url,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ enum svn_prop_kind prop_kind = svn_property_kind2(propname);
+ svn_ra_session_t *ra_session;
+ svn_node_kind_t node_kind;
+ const char *message;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ apr_hash_t *commit_revprops;
+ svn_error_t *err;
+
+ if (prop_kind != svn_prop_regular_kind)
+ return svn_error_createf
+ (SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is not a regular property"), propname);
+
+ /* Open an RA session for the URL. Note that we don't have a local
+ directory, nor a place to put temp files. */
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, target, NULL,
+ ctx, pool, pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", base_revision_for_url,
+ &node_kind, pool));
+ if (node_kind == svn_node_none)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' does not exist in revision %ld"),
+ target, base_revision_for_url);
+
+ if (node_kind == svn_node_file)
+ {
+ /* We need to reparent our session one directory up, since editor
+ semantics require the root is a directory.
+
+ ### How does this interact with authz? */
+ const char *parent_url;
+ parent_url = svn_uri_dirname(target, pool);
+
+ SVN_ERR(svn_ra_reparent(ra_session, parent_url, pool));
+ }
+
+ /* Setting an inappropriate property is not allowed (unless
+ overridden by 'skip_checks', in some circumstances). Deleting an
+ inappropriate property is allowed, however, since older clients
+ allowed (and other clients possibly still allow) setting it in
+ the first place. */
+ if (propval && svn_prop_is_svn_prop(propname))
+ {
+ const svn_string_t *new_value;
+ struct getter_baton gb;
+
+ gb.ra_session = ra_session;
+ gb.base_revision_for_url = base_revision_for_url;
+ SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, propname, propval,
+ target, node_kind, skip_checks,
+ get_file_for_validation, &gb, pool));
+ propval = new_value;
+ }
+
+ /* Create a new commit item and add it to the array. */
+ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
+ {
+ svn_client_commit_item3_t *item;
+ const char *tmp_file;
+ apr_array_header_t *commit_items = apr_array_make(pool, 1, sizeof(item));
+
+ item = svn_client_commit_item3_create(pool);
+ item->url = target;
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+ SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
+ ctx, pool));
+ if (! message)
+ return SVN_NO_ERROR;
+ }
+ else
+ message = "";
+
+ SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
+ message, ctx, pool));
+
+ /* Fetch RA commit editor. */
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
+ svn_client__get_shim_callbacks(ctx->wc_ctx,
+ NULL, pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ commit_revprops,
+ commit_callback,
+ commit_baton,
+ NULL, TRUE, /* No lock tokens */
+ pool));
+
+ err = do_url_propset(target, propname, propval, node_kind,
+ base_revision_for_url, editor, edit_baton, pool);
+
+ if (err)
+ {
+ /* At least try to abort the edit (and fs txn) before throwing err. */
+ svn_error_clear(editor->abort_edit(edit_baton, pool));
+ return svn_error_trace(err);
+ }
+
+ /* Close the edit. */
+ return editor->close_edit(edit_baton, pool);
+}
+
+/* Check that PROPNAME is a valid name for a versioned property. Return an
+ * error if it is not valid, specifically if it is:
+ * - the name of a standard Subversion rev-prop; or
+ * - in the namespace of WC-props; or
+ * - not a well-formed property name (except if PROPVAL is NULL: in other
+ * words we do allow deleting a prop with an ill-formed name).
+ *
+ * Since Subversion controls the "svn:" property namespace, we don't honor
+ * a 'skip_checks' flag here. Checks for unusual property combinations such
+ * as svn:eol-style with a non-text svn:mime-type might understandably be
+ * skipped, but things such as using a property name reserved for revprops
+ * on a local target are never allowed.
+ */
+static svn_error_t *
+check_prop_name(const char *propname,
+ const svn_string_t *propval)
+{
+ if (svn_prop_is_known_svn_rev_prop(propname))
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("Revision property '%s' not allowed "
+ "in this context"), propname);
+
+ SVN_ERR(error_if_wcprop_name(propname));
+
+ if (propval && ! svn_prop_name_is_valid(propname))
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("Bad property name: '%s'"), propname);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_propset_local(const char *propname,
+ const svn_string_t *propval,
+ const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_boolean_t skip_checks,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_boolean_t targets_are_urls;
+ int i;
+
+ if (targets->nelts == 0)
+ return SVN_NO_ERROR;
+
+ /* Check for homogeneity among our targets. */
+ targets_are_urls = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
+ SVN_ERR(svn_client__assert_homogeneous_target_type(targets));
+
+ if (targets_are_urls)
+ return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Targets must be working copy paths"));
+
+ SVN_ERR(check_prop_name(propname, propval));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ svn_node_kind_t kind;
+ const char *target_abspath;
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ /* Check for cancellation */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, iterpool));
+
+ /* Call prop_set for deleted nodes to have special errors */
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath,
+ FALSE, FALSE, iterpool));
+
+ if (kind == svn_node_unknown || kind == svn_node_none)
+ {
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(
+ target_abspath,
+ svn_wc_notify_path_nonexistent,
+ iterpool);
+
+ ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
+ }
+ }
+
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ svn_wc_prop_set4(ctx->wc_ctx, target_abspath, propname,
+ propval, depth, skip_checks, changelists,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2, iterpool),
+ ctx->wc_ctx, target_abspath, FALSE /* lock_anchor */, iterpool);
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_propset_remote(const char *propname,
+ const svn_string_t *propval,
+ const char *url,
+ svn_boolean_t skip_checks,
+ svn_revnum_t base_revision_for_url,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ if (!svn_path_is_url(url))
+ return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Targets must be URLs"));
+
+ SVN_ERR(check_prop_name(propname, propval));
+
+ /* The rationale for requiring the base_revision_for_url
+ argument is that without it, it's too easy to possibly
+ overwrite someone else's change without noticing. (See also
+ tools/examples/svnput.c). */
+ if (! SVN_IS_VALID_REVNUM(base_revision_for_url))
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Setting property on non-local targets "
+ "needs a base revision"));
+
+ /* ### When you set svn:eol-style or svn:keywords on a wc file,
+ ### Subversion sends a textdelta at commit time to properly
+ ### normalize the file in the repository. If we want to
+ ### support editing these properties on URLs, then we should
+ ### generate the same textdelta; for now, we won't support
+ ### editing these properties on URLs. (Admittedly, this
+ ### means that all the machinery with get_file_for_validation
+ ### is unused.)
+ */
+ if ((strcmp(propname, SVN_PROP_EOL_STYLE) == 0) ||
+ (strcmp(propname, SVN_PROP_KEYWORDS) == 0))
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Setting property '%s' on non-local "
+ "targets is not supported"), propname);
+
+ SVN_ERR(propset_on_url(propname, propval, url, skip_checks,
+ base_revision_for_url, revprop_table,
+ commit_callback, commit_baton, ctx, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+check_and_set_revprop(svn_revnum_t *set_rev,
+ svn_ra_session_t *ra_session,
+ const char *propname,
+ const svn_string_t *original_propval,
+ const svn_string_t *propval,
+ apr_pool_t *pool)
+{
+ if (original_propval)
+ {
+ /* Ensure old value hasn't changed behind our back. */
+ svn_string_t *current;
+ SVN_ERR(svn_ra_rev_prop(ra_session, *set_rev, propname, &current, pool));
+
+ if (original_propval->data && (! current))
+ {
+ return svn_error_createf(
+ SVN_ERR_RA_OUT_OF_DATE, NULL,
+ _("revprop '%s' in r%ld is unexpectedly absent "
+ "in repository (maybe someone else deleted it?)"),
+ propname, *set_rev);
+ }
+ else if (original_propval->data
+ && (! svn_string_compare(original_propval, current)))
+ {
+ return svn_error_createf(
+ SVN_ERR_RA_OUT_OF_DATE, NULL,
+ _("revprop '%s' in r%ld has unexpected value "
+ "in repository (maybe someone else changed it?)"),
+ propname, *set_rev);
+ }
+ else if ((! original_propval->data) && current)
+ {
+ return svn_error_createf(
+ SVN_ERR_RA_OUT_OF_DATE, NULL,
+ _("revprop '%s' in r%ld is unexpectedly present "
+ "in repository (maybe someone else set it?)"),
+ propname, *set_rev);
+ }
+ }
+
+ SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname,
+ NULL, propval, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_revprop_set2(const char *propname,
+ const svn_string_t *propval,
+ const svn_string_t *original_propval,
+ const char *URL,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *set_rev,
+ svn_boolean_t force,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session;
+ svn_boolean_t be_atomic;
+
+ if ((strcmp(propname, SVN_PROP_REVISION_AUTHOR) == 0)
+ && propval
+ && strchr(propval->data, '\n') != NULL
+ && (! force))
+ return svn_error_create(SVN_ERR_CLIENT_REVISION_AUTHOR_CONTAINS_NEWLINE,
+ NULL, _("Author name should not contain a newline;"
+ " value will not be set unless forced"));
+
+ if (propval && ! svn_prop_name_is_valid(propname))
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("Bad property name: '%s'"), propname);
+
+ /* Open an RA session for the URL. */
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
+ ctx, pool, pool));
+
+ /* Resolve the revision into something real, and return that to the
+ caller as well. */
+ SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
+ ra_session, revision, pool));
+
+ SVN_ERR(svn_ra_has_capability(ra_session, &be_atomic,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS, pool));
+ if (be_atomic)
+ {
+ /* Convert ORIGINAL_PROPVAL to an OLD_VALUE_P. */
+ const svn_string_t *const *old_value_p;
+ const svn_string_t *unset = NULL;
+
+ if (original_propval == NULL)
+ old_value_p = NULL;
+ else if (original_propval->data == NULL)
+ old_value_p = &unset;
+ else
+ old_value_p = &original_propval;
+
+ /* The actual RA call. */
+ SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname,
+ old_value_p, propval, pool));
+ }
+ else
+ {
+ /* The actual RA call. */
+ SVN_ERR(check_and_set_revprop(set_rev, ra_session, propname,
+ original_propval, propval, pool));
+ }
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify_url(URL,
+ propval == NULL
+ ? svn_wc_notify_revprop_deleted
+ : svn_wc_notify_revprop_set,
+ pool);
+ notify->prop_name = propname;
+ notify->revision = *set_rev;
+
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for the remote case of svn_client_propget.
+ *
+ * If PROPS is not null, then get the value of property PROPNAME in REVNUM,
+ using RA_LIB and SESSION. Store the value ('svn_string_t *') in PROPS,
+ under the path key "TARGET_PREFIX/TARGET_RELATIVE" ('const char *').
+ *
+ * If INHERITED_PROPS is not null, then set *INHERITED_PROPS to a
+ * depth-first ordered array of svn_prop_inherited_item_t * structures
+ * representing the PROPNAME properties inherited by the target. If
+ * INHERITABLE_PROPS in not null and no inheritable properties are found,
+ * then set *INHERITED_PROPS to an empty array.
+ *
+ * Recurse according to DEPTH, similarly to svn_client_propget3().
+ *
+ * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE".
+ * Yes, caller passes this; it makes the recursion more efficient :-).
+ *
+ * Allocate PROPS and *INHERITED_PROPS in RESULT_POOL, but do all temporary
+ * work in SCRATCH_POOL. The two pools can be the same; recursive
+ * calls may use a different SCRATCH_POOL, however.
+ */
+static svn_error_t *
+remote_propget(apr_hash_t *props,
+ apr_array_header_t **inherited_props,
+ const char *propname,
+ const char *target_prefix,
+ const char *target_relative,
+ svn_node_kind_t kind,
+ svn_revnum_t revnum,
+ svn_ra_session_t *ra_session,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *dirents;
+ apr_hash_t *prop_hash = NULL;
+ const svn_string_t *val;
+ const char *target_full_url =
+ svn_path_url_add_component2(target_prefix, target_relative,
+ scratch_pool);
+
+ if (kind == svn_node_dir)
+ {
+ SVN_ERR(svn_ra_get_dir2(ra_session,
+ (depth >= svn_depth_files ? &dirents : NULL),
+ NULL, &prop_hash, target_relative, revnum,
+ SVN_DIRENT_KIND, scratch_pool));
+ }
+ else if (kind == svn_node_file)
+ {
+ SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum,
+ NULL, NULL, &prop_hash, scratch_pool));
+ }
+ else if (kind == svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("'%s' does not exist in revision %ld"),
+ target_full_url, revnum);
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("Unknown node kind for '%s'"),
+ target_full_url);
+ }
+
+ if (inherited_props)
+ {
+ const char *repos_root_url;
+
+ /* We will filter out all but PROPNAME later, making a final copy
+ in RESULT_POOL, so pass SCRATCH_POOL for all pools. */
+ SVN_ERR(svn_ra_get_inherited_props(ra_session, inherited_props,
+ target_relative, revnum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
+ scratch_pool));
+ SVN_ERR(svn_client__iprop_relpaths_to_urls(*inherited_props,
+ repos_root_url,
+ scratch_pool,
+ scratch_pool));
+ }
+
+ /* Make a copy of any inherited PROPNAME properties in RESULT_POOL. */
+ if (inherited_props)
+ {
+ int i;
+ apr_array_header_t *final_iprops =
+ apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *));
+
+ for (i = 0; i < (*inherited_props)->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX((*inherited_props), i, svn_prop_inherited_item_t *);
+ svn_string_t *iprop_val = svn_hash_gets(iprop->prop_hash, propname);
+
+ if (iprop_val)
+ {
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(result_pool, sizeof(*new_iprop));
+ new_iprop->path_or_url =
+ apr_pstrdup(result_pool, iprop->path_or_url);
+ new_iprop->prop_hash = apr_hash_make(result_pool);
+ svn_hash_sets(new_iprop->prop_hash,
+ apr_pstrdup(result_pool, propname),
+ svn_string_dup(iprop_val, result_pool));
+ APR_ARRAY_PUSH(final_iprops, svn_prop_inherited_item_t *) =
+ new_iprop;
+ }
+ }
+ *inherited_props = final_iprops;
+ }
+
+ if (prop_hash
+ && (val = svn_hash_gets(prop_hash, propname)))
+ {
+ svn_hash_sets(props,
+ apr_pstrdup(result_pool, target_full_url),
+ svn_string_dup(val, result_pool));
+ }
+
+ if (depth >= svn_depth_files
+ && kind == svn_node_dir
+ && apr_hash_count(dirents) > 0)
+ {
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, dirents);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *this_name = svn__apr_hash_index_key(hi);
+ svn_dirent_t *this_ent = svn__apr_hash_index_val(hi);
+ const char *new_target_relative;
+ svn_depth_t depth_below_here = depth;
+
+ svn_pool_clear(iterpool);
+
+ if (depth == svn_depth_files && this_ent->kind == svn_node_dir)
+ continue;
+
+ if (depth == svn_depth_files || depth == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ new_target_relative = svn_relpath_join(target_relative, this_name,
+ iterpool);
+
+ SVN_ERR(remote_propget(props, NULL,
+ propname,
+ target_prefix,
+ new_target_relative,
+ this_ent->kind,
+ revnum,
+ ra_session,
+ depth_below_here,
+ result_pool, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for recursive_propget_receiver(). */
+struct recursive_propget_receiver_baton
+{
+ apr_hash_t *props; /* Hash to collect props. */
+ apr_pool_t *pool; /* Pool to allocate additions to PROPS. */
+ svn_wc_context_t *wc_ctx; /* Working copy context. */
+};
+
+/* An implementation of svn_wc__proplist_receiver_t. */
+static svn_error_t *
+recursive_propget_receiver(void *baton,
+ const char *local_abspath,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ struct recursive_propget_receiver_baton *b = baton;
+
+ if (apr_hash_count(props))
+ {
+ apr_hash_index_t *hi = apr_hash_first(scratch_pool, props);
+ svn_hash_sets(b->props, apr_pstrdup(b->pool, local_abspath),
+ svn_string_dup(svn__apr_hash_index_val(hi), b->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the property value for any PROPNAME set on TARGET in *PROPS,
+ with WC paths of char * for keys and property values of
+ svn_string_t * for values. Assumes that PROPS is non-NULL. Additions
+ to *PROPS are allocated in RESULT_POOL, temporary allocations happen in
+ SCRATCH_POOL.
+
+ CHANGELISTS is an array of const char * changelist names, used as a
+ restrictive filter on items whose properties are set; that is,
+ don't set properties on any item unless it's a member of one of
+ those changelists. If CHANGELISTS is empty (or altogether NULL),
+ no changelist filtering occurs.
+
+ Treat DEPTH as in svn_client_propget3().
+*/
+static svn_error_t *
+get_prop_from_wc(apr_hash_t **props,
+ const char *propname,
+ const char *target_abspath,
+ svn_boolean_t pristine,
+ svn_node_kind_t kind,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct recursive_propget_receiver_baton rb;
+
+ /* Technically, svn_depth_unknown just means use whatever depth(s)
+ we find in the working copy. But this is a walk over extant
+ working copy paths: if they're there at all, then by definition
+ the local depth reaches them, so let's just use svn_depth_infinity
+ to get there. */
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_infinity;
+
+ if (!pristine && depth == svn_depth_infinity
+ && (!changelists || changelists->nelts == 0))
+ {
+ /* Handle this common svn:mergeinfo case more efficient than the target
+ list handling in the recursive retrieval. */
+ SVN_ERR(svn_wc__prop_retrieve_recursive(
+ props, ctx->wc_ctx, target_abspath, propname,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ *props = apr_hash_make(result_pool);
+ rb.props = *props;
+ rb.pool = result_pool;
+ rb.wc_ctx = ctx->wc_ctx;
+
+ SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, target_abspath,
+ propname, depth, pristine,
+ changelists,
+ recursive_propget_receiver, &rb,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Note: this implementation is very similar to svn_client_proplist. */
+svn_error_t *
+svn_client_propget5(apr_hash_t **props,
+ apr_array_header_t **inherited_props,
+ const char *propname,
+ const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *actual_revnum,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_revnum_t revnum;
+ svn_boolean_t local_explicit_props;
+ svn_boolean_t local_iprops;
+
+ SVN_ERR(error_if_wcprop_name(propname));
+ if (!svn_path_is_url(target))
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(target));
+
+ peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
+ target);
+ revision = svn_cl__rev_default_to_peg(revision, peg_revision);
+
+ local_explicit_props =
+ (! svn_path_is_url(target)
+ && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
+ && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind));
+
+ local_iprops =
+ (local_explicit_props
+ && (peg_revision->kind == svn_opt_revision_working
+ || peg_revision->kind == svn_opt_revision_unspecified )
+ && (revision->kind == svn_opt_revision_working
+ || revision->kind == svn_opt_revision_unspecified ));
+
+ if (local_explicit_props)
+ {
+ svn_node_kind_t kind;
+ svn_boolean_t pristine;
+ svn_error_t *err;
+
+ /* If FALSE, we want the working revision. */
+ pristine = (revision->kind == svn_opt_revision_committed
+ || revision->kind == svn_opt_revision_base);
+
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target,
+ pristine, FALSE,
+ scratch_pool));
+
+ if (kind == svn_node_unknown || kind == svn_node_none)
+ {
+ /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only
+ for this function. */
+ return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(target,
+ scratch_pool));
+ }
+
+ err = svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
+ target, NULL, revision,
+ scratch_pool);
+ if (err && err->apr_err == SVN_ERR_CLIENT_BAD_REVISION)
+ {
+ svn_error_clear(err);
+ revnum = SVN_INVALID_REVNUM;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ if (inherited_props && local_iprops)
+ {
+ const char *repos_root_url;
+
+ SVN_ERR(svn_wc__get_iprops(inherited_props, ctx->wc_ctx,
+ target, propname,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_client_get_repos_root(&repos_root_url, NULL,
+ target, ctx, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client__iprop_relpaths_to_urls(*inherited_props,
+ repos_root_url,
+ result_pool,
+ scratch_pool));
+ }
+
+ SVN_ERR(get_prop_from_wc(props, propname, target,
+ pristine, kind,
+ depth, changelists, ctx, result_pool,
+ scratch_pool));
+ }
+
+ if ((inherited_props && !local_iprops)
+ || !local_explicit_props)
+ {
+ svn_ra_session_t *ra_session;
+ svn_node_kind_t kind;
+ svn_opt_revision_t new_operative_rev;
+ svn_opt_revision_t new_peg_rev;
+
+ /* Peg or operative revisions may be WC specific for
+ TARGET's explicit props, but still require us to
+ contact the repository for the inherited properties. */
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)
+ || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
+ {
+ svn_revnum_t origin_rev;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *local_abspath;
+ const char *copy_root_abspath;
+ svn_boolean_t is_copy;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, target,
+ scratch_pool));
+
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
+ {
+ SVN_ERR(svn_wc__node_get_origin(&is_copy,
+ &origin_rev,
+ &repos_relpath,
+ &repos_root_url,
+ &repos_uuid,
+ &copy_root_abspath,
+ ctx->wc_ctx,
+ local_abspath,
+ FALSE, /* scan_deleted */
+ result_pool,
+ scratch_pool));
+ if (repos_relpath)
+ {
+ target = svn_path_url_add_component2(repos_root_url,
+ repos_relpath,
+ scratch_pool);
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
+ {
+ svn_revnum_t resolved_peg_rev;
+
+ SVN_ERR(svn_client__get_revision_number(
+ &resolved_peg_rev, NULL, ctx->wc_ctx,
+ local_abspath, NULL, peg_revision, scratch_pool));
+ new_peg_rev.kind = svn_opt_revision_number;
+ new_peg_rev.value.number = resolved_peg_rev;
+ peg_revision = &new_peg_rev;
+ }
+
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
+ {
+ svn_revnum_t resolved_operative_rev;
+
+ SVN_ERR(svn_client__get_revision_number(
+ &resolved_operative_rev, NULL, ctx->wc_ctx,
+ local_abspath, NULL, revision, scratch_pool));
+ new_operative_rev.kind = svn_opt_revision_number;
+ new_operative_rev.value.number = resolved_operative_rev;
+ revision = &new_operative_rev;
+ }
+ }
+ else
+ {
+ /* TARGET doesn't exist in the repository, so there are
+ obviously not inherited props to be found there. */
+ local_iprops = TRUE;
+ *inherited_props = apr_array_make(
+ result_pool, 0, sizeof(svn_prop_inherited_item_t *));
+ }
+ }
+ }
+
+ /* Do we still have anything to ask the repository about? */
+ if (!local_explicit_props || !local_iprops)
+ {
+ svn_client__pathrev_t *loc;
+
+ /* Get an RA plugin for this filesystem object. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
+ target, NULL,
+ peg_revision,
+ revision, ctx,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind,
+ scratch_pool));
+
+ if (!local_explicit_props)
+ *props = apr_hash_make(result_pool);
+
+ SVN_ERR(remote_propget(!local_explicit_props ? *props : NULL,
+ !local_iprops ? inherited_props : NULL,
+ propname, loc->url, "",
+ kind, loc->rev, ra_session,
+ depth, result_pool, scratch_pool));
+ revnum = loc->rev;
+ }
+ }
+
+ if (actual_revnum)
+ *actual_revnum = revnum;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_revprop_get(const char *propname,
+ svn_string_t **propval,
+ const char *URL,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *set_rev,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_error_t *err;
+
+ /* Open an RA session for the URL. Note that we don't have a local
+ directory, nor a place to put temp files. */
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
+ ctx, subpool, subpool));
+
+ /* Resolve the revision into something real, and return that to the
+ caller as well. */
+ SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
+ ra_session, revision, subpool));
+
+ /* The actual RA call. */
+ err = svn_ra_rev_prop(ra_session, *set_rev, propname, propval, pool);
+
+ /* Close RA session */
+ svn_pool_destroy(subpool);
+ return svn_error_trace(err);
+}
+
+
+/* Call RECEIVER for the given PATH and its PROP_HASH and/or
+ * INHERITED_PROPERTIES.
+ *
+ * If PROP_HASH is null or has zero count or INHERITED_PROPERTIES is null,
+ * then do nothing.
+ */
+static svn_error_t*
+call_receiver(const char *path,
+ apr_hash_t *prop_hash,
+ apr_array_header_t *inherited_properties,
+ svn_proplist_receiver2_t receiver,
+ void *receiver_baton,
+ apr_pool_t *scratch_pool)
+{
+ if ((prop_hash && apr_hash_count(prop_hash))
+ || inherited_properties)
+ SVN_ERR(receiver(receiver_baton, path, prop_hash, inherited_properties,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for the remote case of svn_client_proplist.
+ *
+ * If GET_EXPLICIT_PROPS is true, then call RECEIVER for paths at or under
+ * "TARGET_PREFIX/TARGET_RELATIVE@REVNUM" (obtained using RA_SESSION) which
+ * have regular properties. If GET_TARGET_INHERITED_PROPS is true, then send
+ * the target's inherited properties to the callback.
+ *
+ * The 'path' and keys for 'prop_hash' and 'inherited_prop' arguments to
+ * RECEIVER are all URLs.
+ *
+ * RESULT_POOL is used to allocated the 'path', 'prop_hash', and
+ * 'inherited_prop' arguments to RECEIVER. SCRATCH_POOL is used for all
+ * other (temporary) allocations.
+ *
+ * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE".
+ *
+ * If the target is a directory, only fetch properties for the files
+ * and directories at depth DEPTH. DEPTH has not effect on inherited
+ * properties.
+ */
+static svn_error_t *
+remote_proplist(const char *target_prefix,
+ const char *target_relative,
+ svn_node_kind_t kind,
+ svn_revnum_t revnum,
+ svn_ra_session_t *ra_session,
+ svn_boolean_t get_explicit_props,
+ svn_boolean_t get_target_inherited_props,
+ svn_depth_t depth,
+ svn_proplist_receiver2_t receiver,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *dirents;
+ apr_hash_t *prop_hash = NULL;
+ apr_hash_index_t *hi;
+ const char *target_full_url =
+ svn_path_url_add_component2(target_prefix, target_relative, scratch_pool);
+ apr_array_header_t *inherited_props;
+
+ /* Note that we pass only the SCRATCH_POOL to svn_ra_get[dir*|file*] because
+ we'll be filtering out non-regular properties from PROP_HASH before we
+ return. */
+ if (kind == svn_node_dir)
+ {
+ SVN_ERR(svn_ra_get_dir2(ra_session,
+ (depth > svn_depth_empty) ? &dirents : NULL,
+ NULL, &prop_hash, target_relative, revnum,
+ SVN_DIRENT_KIND, scratch_pool));
+ }
+ else if (kind == svn_node_file)
+ {
+ SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum,
+ NULL, NULL, &prop_hash, scratch_pool));
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("Unknown node kind for '%s'"),
+ target_full_url);
+ }
+
+ if (get_target_inherited_props)
+ {
+ const char *repos_root_url;
+
+ SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props,
+ target_relative, revnum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
+ scratch_pool));
+ SVN_ERR(svn_client__iprop_relpaths_to_urls(inherited_props,
+ repos_root_url,
+ scratch_pool,
+ scratch_pool));
+ }
+ else
+ {
+ inherited_props = NULL;
+ }
+
+ if (!get_explicit_props)
+ prop_hash = NULL;
+ else
+ {
+ /* Filter out non-regular properties, since the RA layer returns all
+ kinds. Copy regular properties keys/vals from the prop_hash
+ allocated in SCRATCH_POOL to the "final" hash allocated in
+ RESULT_POOL. */
+ for (hi = apr_hash_first(scratch_pool, prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ apr_ssize_t klen = svn__apr_hash_index_klen(hi);
+ svn_prop_kind_t prop_kind;
+
+ prop_kind = svn_property_kind2(name);
+
+ if (prop_kind != svn_prop_regular_kind)
+ {
+ apr_hash_set(prop_hash, name, klen, NULL);
+ }
+ }
+ }
+
+ SVN_ERR(call_receiver(target_full_url, prop_hash, inherited_props,
+ receiver, receiver_baton, scratch_pool));
+
+ if (depth > svn_depth_empty
+ && get_explicit_props
+ && (kind == svn_node_dir) && (apr_hash_count(dirents) > 0))
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, dirents);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *this_name = svn__apr_hash_index_key(hi);
+ svn_dirent_t *this_ent = svn__apr_hash_index_val(hi);
+ const char *new_target_relative;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ svn_pool_clear(iterpool);
+
+ new_target_relative = svn_relpath_join(target_relative,
+ this_name, iterpool);
+
+ if (this_ent->kind == svn_node_file
+ || depth > svn_depth_files)
+ {
+ svn_depth_t depth_below_here = depth;
+
+ if (depth == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ SVN_ERR(remote_proplist(target_prefix,
+ new_target_relative,
+ this_ent->kind,
+ revnum,
+ ra_session,
+ TRUE /* get_explicit_props */,
+ FALSE /* get_target_inherited_props */,
+ depth_below_here,
+ receiver, receiver_baton,
+ cancel_func, cancel_baton,
+ iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Baton for recursive_proplist_receiver(). */
+struct recursive_proplist_receiver_baton
+{
+ svn_wc_context_t *wc_ctx; /* Working copy context. */
+ svn_proplist_receiver2_t wrapped_receiver; /* Proplist receiver to call. */
+ void *wrapped_receiver_baton; /* Baton for the proplist receiver. */
+
+ /* Anchor, anchor_abspath pair for converting to relative paths */
+ const char *anchor;
+ const char *anchor_abspath;
+};
+
+/* An implementation of svn_wc__proplist_receiver_t. */
+static svn_error_t *
+recursive_proplist_receiver(void *baton,
+ const char *local_abspath,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ struct recursive_proplist_receiver_baton *b = baton;
+ const char *path;
+
+ /* Attempt to convert absolute paths to relative paths for
+ * presentation purposes, if needed. */
+ if (b->anchor && b->anchor_abspath)
+ {
+ path = svn_dirent_join(b->anchor,
+ svn_dirent_skip_ancestor(b->anchor_abspath,
+ local_abspath),
+ scratch_pool);
+ }
+ else
+ path = local_abspath;
+
+ return svn_error_trace(b->wrapped_receiver(b->wrapped_receiver_baton,
+ path, props, NULL,
+ scratch_pool));
+}
+
+/* Helper for svn_client_proplist4 when retrieving properties and/or
+ inherited properties from the repository. Except as noted below,
+ all arguments are as per svn_client_proplist4.
+
+ GET_EXPLICIT_PROPS controls if explicit props are retrieved. */
+static svn_error_t *
+get_remote_props(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t get_explicit_props,
+ svn_boolean_t get_target_inherited_props,
+ svn_proplist_receiver2_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_session_t *ra_session;
+ svn_node_kind_t kind;
+ svn_opt_revision_t new_operative_rev;
+ svn_opt_revision_t new_peg_rev;
+ svn_client__pathrev_t *loc;
+
+ /* Peg or operative revisions may be WC specific for
+ PATH_OR_URL's explicit props, but still require us to
+ contact the repository for the inherited properties. */
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)
+ || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
+ {
+ svn_revnum_t origin_rev;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *local_abspath;
+ const char *copy_root_abspath;
+ svn_boolean_t is_copy;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
+ scratch_pool));
+
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
+ {
+ SVN_ERR(svn_wc__node_get_origin(&is_copy,
+ &origin_rev,
+ &repos_relpath,
+ &repos_root_url,
+ &repos_uuid,
+ &copy_root_abspath,
+ ctx->wc_ctx,
+ local_abspath,
+ FALSE, /* scan_deleted */
+ scratch_pool,
+ scratch_pool));
+ if (repos_relpath)
+ {
+ path_or_url =
+ svn_path_url_add_component2(repos_root_url,
+ repos_relpath,
+ scratch_pool);
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
+ {
+ svn_revnum_t resolved_peg_rev;
+
+ SVN_ERR(svn_client__get_revision_number(&resolved_peg_rev,
+ NULL, ctx->wc_ctx,
+ local_abspath, NULL,
+ peg_revision,
+ scratch_pool));
+ new_peg_rev.kind = svn_opt_revision_number;
+ new_peg_rev.value.number = resolved_peg_rev;
+ peg_revision = &new_peg_rev;
+ }
+
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
+ {
+ svn_revnum_t resolved_operative_rev;
+
+ SVN_ERR(svn_client__get_revision_number(
+ &resolved_operative_rev,
+ NULL, ctx->wc_ctx,
+ local_abspath, NULL,
+ revision,
+ scratch_pool));
+ new_operative_rev.kind = svn_opt_revision_number;
+ new_operative_rev.value.number = resolved_operative_rev;
+ revision = &new_operative_rev;
+ }
+ }
+ else
+ {
+ /* PATH_OR_URL doesn't exist in the repository, so there are
+ obviously not inherited props to be found there. If we
+ aren't looking for explicit props then we're done. */
+ if (!get_explicit_props)
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+
+ /* Get an RA session for this URL. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
+ path_or_url, NULL,
+ peg_revision,
+ revision, ctx,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind,
+ scratch_pool));
+
+ SVN_ERR(remote_proplist(loc->url, "", kind, loc->rev, ra_session,
+ get_explicit_props,
+ get_target_inherited_props,
+ depth, receiver, receiver_baton,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_client_proplist4 when retrieving properties and
+ possibly inherited properties from the WC. All arguments are as
+ per svn_client_proplist4. */
+static svn_error_t *
+get_local_props(const char *path_or_url,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_boolean_t get_target_inherited_props,
+ svn_proplist_receiver2_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t pristine;
+ svn_node_kind_t kind;
+ apr_hash_t *changelist_hash = NULL;
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
+ scratch_pool));
+
+ pristine = ((revision->kind == svn_opt_revision_committed)
+ || (revision->kind == svn_opt_revision_base));
+
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
+ pristine, FALSE, scratch_pool));
+
+ if (kind == svn_node_unknown || kind == svn_node_none)
+ {
+ /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only
+ for this function. */
+ return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ if (get_target_inherited_props)
+ {
+ apr_array_header_t *iprops;
+ const char *repos_root_url;
+
+ SVN_ERR(svn_wc__get_iprops(&iprops, ctx->wc_ctx, local_abspath,
+ NULL, scratch_pool, scratch_pool));
+ SVN_ERR(svn_client_get_repos_root(&repos_root_url, NULL, local_abspath,
+ ctx, scratch_pool, scratch_pool));
+ SVN_ERR(svn_client__iprop_relpaths_to_urls(iprops, repos_root_url,
+ scratch_pool,
+ scratch_pool));
+ SVN_ERR(call_receiver(path_or_url, NULL, iprops, receiver,
+ receiver_baton, scratch_pool));
+ }
+
+ if (changelists && changelists->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
+ changelists, scratch_pool));
+
+ /* Fetch, recursively or not. */
+ if (kind == svn_node_dir)
+ {
+ struct recursive_proplist_receiver_baton rb;
+
+ rb.wc_ctx = ctx->wc_ctx;
+ rb.wrapped_receiver = receiver;
+ rb.wrapped_receiver_baton = receiver_baton;
+
+ if (strcmp(path_or_url, local_abspath) != 0)
+ {
+ rb.anchor = path_or_url;
+ rb.anchor_abspath = local_abspath;
+ }
+ else
+ {
+ rb.anchor = NULL;
+ rb.anchor_abspath = NULL;
+ }
+
+ SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, local_abspath, NULL,
+ depth, pristine, changelists,
+ recursive_proplist_receiver, &rb,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool));
+ }
+ else if (svn_wc__changelist_match(ctx->wc_ctx, local_abspath,
+ changelist_hash, scratch_pool))
+ {
+ apr_hash_t *props;
+
+ if (pristine)
+ SVN_ERR(svn_wc_get_pristine_props(&props,
+ ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ {
+ svn_error_t *err;
+
+ err = svn_wc_prop_list2(&props, ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool);
+
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+ /* As svn_wc_prop_list2() doesn't return NULL for locally-deleted
+ let's do that here. */
+ svn_error_clear(err);
+ props = apr_hash_make(scratch_pool);
+ }
+ }
+
+ SVN_ERR(call_receiver(path_or_url, props, NULL,
+ receiver, receiver_baton, scratch_pool));
+
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_proplist4(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_boolean_t get_target_inherited_props,
+ svn_proplist_receiver2_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t local_explicit_props;
+ svn_boolean_t local_iprops;
+
+ peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
+ path_or_url);
+ revision = svn_cl__rev_default_to_peg(revision, peg_revision);
+
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_empty;
+
+ /* Are explicit props available locally? */
+ local_explicit_props =
+ (! svn_path_is_url(path_or_url)
+ && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
+ && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind));
+
+ /* If we want iprops are they available locally? */
+ local_iprops =
+ (get_target_inherited_props /* We want iprops */
+ && local_explicit_props /* No local explicit props means no local iprops. */
+ && (peg_revision->kind == svn_opt_revision_working
+ || peg_revision->kind == svn_opt_revision_unspecified )
+ && (revision->kind == svn_opt_revision_working
+ || revision->kind == svn_opt_revision_unspecified ));
+
+ if ((get_target_inherited_props && !local_iprops)
+ || !local_explicit_props)
+ {
+ SVN_ERR(get_remote_props(path_or_url, peg_revision, revision, depth,
+ !local_explicit_props,
+ (get_target_inherited_props && !local_iprops),
+ receiver, receiver_baton, ctx, scratch_pool));
+ }
+
+ if (local_explicit_props)
+ {
+ SVN_ERR(get_local_props(path_or_url, revision, depth, changelists,
+ local_iprops, receiver, receiver_baton, ctx,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_revprop_list(apr_hash_t **props,
+ const char *URL,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *set_rev,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session;
+ apr_hash_t *proplist;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_error_t *err;
+
+ /* Open an RA session for the URL. Note that we don't have a local
+ directory, nor a place to put temp files. */
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
+ ctx, subpool, subpool));
+
+ /* Resolve the revision into something real, and return that to the
+ caller as well. */
+ SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
+ ra_session, revision, subpool));
+
+ /* The actual RA call. */
+ err = svn_ra_rev_proplist(ra_session, *set_rev, &proplist, pool);
+
+ *props = proplist;
+ svn_pool_destroy(subpool); /* Close RA session */
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/ra.c b/contrib/subversion/subversion/libsvn_client/ra.c
new file mode 100644
index 0000000..33d3de5
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/ra.c
@@ -0,0 +1,1147 @@
+/*
+ * ra.c : routines for interacting with the RA layer
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_pools.h>
+
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_sorts.h"
+#include "svn_ra.h"
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "client.h"
+#include "mergeinfo.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_client_private.h"
+
+
+/* This is the baton that we pass svn_ra_open3(), and is associated with
+ the callback table we provide to RA. */
+typedef struct callback_baton_t
+{
+ /* Holds the directory that corresponds to the REPOS_URL at svn_ra_open3()
+ time. When callbacks specify a relative path, they are joined with
+ this base directory. */
+ const char *base_dir_abspath;
+
+ /* TEMPORARY: Is 'base_dir_abspath' a versioned path? cmpilato
+ suspects that the commit-to-multiple-disjoint-working-copies
+ code is getting this all wrong, sometimes passing an unversioned
+ (or versioned in a foreign wc) path here which sorta kinda
+ happens to work most of the time but is ultimately incorrect. */
+ svn_boolean_t base_dir_isversioned;
+
+ /* Used as wri_abspath for obtaining access to the pristine store */
+ const char *wcroot_abspath;
+
+ /* An array of svn_client_commit_item3_t * structures, present only
+ during working copy commits. */
+ const apr_array_header_t *commit_items;
+
+ /* A client context. */
+ svn_client_ctx_t *ctx;
+
+} callback_baton_t;
+
+
+
+static svn_error_t *
+open_tmp_file(apr_file_t **fp,
+ void *callback_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_io_open_unique_file3(fp, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ pool, pool));
+}
+
+
+/* This implements the 'svn_ra_get_wc_prop_func_t' interface. */
+static svn_error_t *
+get_wc_prop(void *baton,
+ const char *relpath,
+ const char *name,
+ const svn_string_t **value,
+ apr_pool_t *pool)
+{
+ callback_baton_t *cb = baton;
+ const char *local_abspath = NULL;
+ svn_error_t *err;
+
+ *value = NULL;
+
+ /* If we have a list of commit_items, search through that for a
+ match for this relative URL. */
+ if (cb->commit_items)
+ {
+ int i;
+ for (i = 0; i < cb->commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item
+ = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *);
+
+ if (! strcmp(relpath, item->session_relpath))
+ {
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
+ local_abspath = item->path;
+ break;
+ }
+ }
+
+ /* Commits can only query relpaths in the commit_items list
+ since the commit driver traverses paths as they are, or will
+ be, in the repository. Non-commits query relpaths in the
+ working copy. */
+ if (! local_abspath)
+ return SVN_NO_ERROR;
+ }
+
+ /* If we don't have a base directory, then there are no properties. */
+ else if (cb->base_dir_abspath == NULL)
+ return SVN_NO_ERROR;
+
+ else
+ local_abspath = svn_dirent_join(cb->base_dir_abspath, relpath, pool);
+
+ err = svn_wc_prop_get2(value, cb->ctx->wc_ctx, local_abspath, name,
+ pool, pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ err = NULL;
+ }
+ return svn_error_trace(err);
+}
+
+/* This implements the 'svn_ra_push_wc_prop_func_t' interface. */
+static svn_error_t *
+push_wc_prop(void *baton,
+ const char *relpath,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ callback_baton_t *cb = baton;
+ int i;
+
+ /* If we're committing, search through the commit_items list for a
+ match for this relative URL. */
+ if (! cb->commit_items)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Attempt to set wcprop '%s' on '%s' in a non-commit operation"),
+ name, svn_dirent_local_style(relpath, pool));
+
+ for (i = 0; i < cb->commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item
+ = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *);
+
+ if (strcmp(relpath, item->session_relpath) == 0)
+ {
+ apr_pool_t *changes_pool = item->incoming_prop_changes->pool;
+ svn_prop_t *prop = apr_palloc(changes_pool, sizeof(*prop));
+
+ prop->name = apr_pstrdup(changes_pool, name);
+ if (value)
+ prop->value = svn_string_dup(value, changes_pool);
+ else
+ prop->value = NULL;
+
+ /* Buffer the propchange to take effect during the
+ post-commit process. */
+ APR_ARRAY_PUSH(item->incoming_prop_changes, svn_prop_t *) = prop;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the 'svn_ra_set_wc_prop_func_t' interface. */
+static svn_error_t *
+set_wc_prop(void *baton,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ callback_baton_t *cb = baton;
+ const char *local_abspath;
+
+ local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool);
+
+ /* We pass 1 for the 'force' parameter here. Since the property is
+ coming from the repository, we definitely want to accept it.
+ Ideally, we'd raise a conflict if, say, the received property is
+ svn:eol-style yet the file has a locally added svn:mime-type
+ claiming that it's binary. Probably the repository is still
+ right, but the conflict would remind the user to make sure.
+ Unfortunately, we don't have a clean mechanism for doing that
+ here, so we just set the property and hope for the best. */
+ return svn_error_trace(svn_wc_prop_set4(cb->ctx->wc_ctx, local_abspath,
+ name,
+ value, svn_depth_empty,
+ TRUE /* skip_checks */,
+ NULL /* changelist_filter */,
+ NULL, NULL /* cancellation */,
+ NULL, NULL /* notification */,
+ pool));
+}
+
+
+/* This implements the `svn_ra_invalidate_wc_props_func_t' interface. */
+static svn_error_t *
+invalidate_wc_props(void *baton,
+ const char *path,
+ const char *prop_name,
+ apr_pool_t *pool)
+{
+ callback_baton_t *cb = baton;
+ const char *local_abspath;
+
+ local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool);
+
+ /* It's easier just to clear the whole dav_cache than to remove
+ individual items from it recursively like this. And since we
+ know that the RA providers that ship with Subversion only
+ invalidate the one property they use the most from this cache,
+ and that we're intentionally trying to get away from the use of
+ the cache altogether anyway, there's little to lose in wiping the
+ whole cache. Is it the most well-behaved approach to take? Not
+ so much. We choose not to care. */
+ return svn_error_trace(svn_wc__node_clear_dav_cache_recursive(
+ cb->ctx->wc_ctx, local_abspath, pool));
+}
+
+
+/* This implements the `svn_ra_get_wc_contents_func_t' interface. */
+static svn_error_t *
+get_wc_contents(void *baton,
+ svn_stream_t **contents,
+ const svn_checksum_t *checksum,
+ apr_pool_t *pool)
+{
+ callback_baton_t *cb = baton;
+
+ if (! cb->wcroot_abspath)
+ {
+ *contents = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(
+ svn_wc__get_pristine_contents_by_checksum(contents,
+ cb->ctx->wc_ctx,
+ cb->wcroot_abspath,
+ checksum,
+ pool, pool));
+}
+
+
+static svn_error_t *
+cancel_callback(void *baton)
+{
+ callback_baton_t *b = baton;
+ return svn_error_trace((b->ctx->cancel_func)(b->ctx->cancel_baton));
+}
+
+
+static svn_error_t *
+get_client_string(void *baton,
+ const char **name,
+ apr_pool_t *pool)
+{
+ callback_baton_t *b = baton;
+ *name = apr_pstrdup(pool, b->ctx->client_name);
+ return SVN_NO_ERROR;
+}
+
+
+#define SVN_CLIENT__MAX_REDIRECT_ATTEMPTS 3 /* ### TODO: Make configurable. */
+
+svn_error_t *
+svn_client__open_ra_session_internal(svn_ra_session_t **ra_session,
+ const char **corrected_url,
+ const char *base_url,
+ const char *base_dir_abspath,
+ const apr_array_header_t *commit_items,
+ svn_boolean_t write_dav_props,
+ svn_boolean_t read_dav_props,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_callbacks2_t *cbtable;
+ callback_baton_t *cb = apr_pcalloc(result_pool, sizeof(*cb));
+ const char *uuid = NULL;
+
+ SVN_ERR_ASSERT(!write_dav_props || read_dav_props);
+ SVN_ERR_ASSERT(!read_dav_props || base_dir_abspath != NULL);
+ SVN_ERR_ASSERT(base_dir_abspath == NULL
+ || svn_dirent_is_absolute(base_dir_abspath));
+
+ SVN_ERR(svn_ra_create_callbacks(&cbtable, result_pool));
+ cbtable->open_tmp_file = open_tmp_file;
+ cbtable->get_wc_prop = read_dav_props ? get_wc_prop : NULL;
+ cbtable->set_wc_prop = (write_dav_props && read_dav_props)
+ ? set_wc_prop : NULL;
+ cbtable->push_wc_prop = commit_items ? push_wc_prop : NULL;
+ cbtable->invalidate_wc_props = (write_dav_props && read_dav_props)
+ ? invalidate_wc_props : NULL;
+ cbtable->auth_baton = ctx->auth_baton; /* new-style */
+ cbtable->progress_func = ctx->progress_func;
+ cbtable->progress_baton = ctx->progress_baton;
+ cbtable->cancel_func = ctx->cancel_func ? cancel_callback : NULL;
+ cbtable->get_client_string = get_client_string;
+ if (base_dir_abspath)
+ cbtable->get_wc_contents = get_wc_contents;
+
+ cb->commit_items = commit_items;
+ cb->ctx = ctx;
+
+ if (base_dir_abspath && (read_dav_props || write_dav_props))
+ {
+ svn_error_t *err = svn_wc__node_get_repos_info(NULL, NULL, NULL, &uuid,
+ ctx->wc_ctx,
+ base_dir_abspath,
+ result_pool,
+ scratch_pool);
+
+ if (err && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY
+ || err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
+ || err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED))
+ {
+ svn_error_clear(err);
+ uuid = NULL;
+ }
+ else
+ {
+ SVN_ERR(err);
+ cb->base_dir_isversioned = TRUE;
+ }
+ cb->base_dir_abspath = apr_pstrdup(result_pool, base_dir_abspath);
+ }
+
+ if (base_dir_abspath)
+ {
+ svn_error_t *err = svn_wc__get_wcroot(&cb->wcroot_abspath,
+ ctx->wc_ctx, base_dir_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY
+ && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ cb->wcroot_abspath = NULL;
+ }
+ }
+
+ /* If the caller allows for auto-following redirections, and the
+ RA->open() call above reveals a CORRECTED_URL, try the new URL.
+ We'll do this in a loop up to some maximum number follow-and-retry
+ attempts. */
+ if (corrected_url)
+ {
+ apr_hash_t *attempted = apr_hash_make(scratch_pool);
+ int attempts_left = SVN_CLIENT__MAX_REDIRECT_ATTEMPTS;
+
+ *corrected_url = NULL;
+ while (attempts_left--)
+ {
+ const char *corrected = NULL;
+
+ /* Try to open the RA session. If this is our last attempt,
+ don't accept corrected URLs from the RA provider. */
+ SVN_ERR(svn_ra_open4(ra_session,
+ attempts_left == 0 ? NULL : &corrected,
+ base_url, uuid, cbtable, cb, ctx->config,
+ result_pool));
+
+ /* No error and no corrected URL? We're done here. */
+ if (! corrected)
+ break;
+
+ /* Notify the user that a redirect is being followed. */
+ if (ctx->notify_func2 != NULL)
+ {
+ svn_wc_notify_t *notify =
+ svn_wc_create_notify_url(corrected,
+ svn_wc_notify_url_redirect,
+ scratch_pool);
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ /* Our caller will want to know what our final corrected URL was. */
+ *corrected_url = corrected;
+
+ /* Make sure we've not attempted this URL before. */
+ if (svn_hash_gets(attempted, corrected))
+ return svn_error_createf(SVN_ERR_CLIENT_CYCLE_DETECTED, NULL,
+ _("Redirect cycle detected for URL '%s'"),
+ corrected);
+
+ /* Remember this CORRECTED_URL so we don't wind up in a loop. */
+ svn_hash_sets(attempted, corrected, (void *)1);
+ base_url = corrected;
+ }
+ }
+ else
+ {
+ SVN_ERR(svn_ra_open4(ra_session, NULL, base_url,
+ uuid, cbtable, cb, ctx->config, result_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+#undef SVN_CLIENT__MAX_REDIRECT_ATTEMPTS
+
+
+svn_error_t *
+svn_client_open_ra_session2(svn_ra_session_t **session,
+ const char *url,
+ const char *wri_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_client__open_ra_session_internal(session, NULL, url,
+ wri_abspath, NULL,
+ FALSE, FALSE,
+ ctx, result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_client__resolve_rev_and_url(svn_client__pathrev_t **resolved_loc_p,
+ svn_ra_session_t *ra_session,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_opt_revision_t peg_rev = *peg_revision;
+ svn_opt_revision_t start_rev = *revision;
+ const char *url;
+ svn_revnum_t rev;
+
+ /* Default revisions: peg -> working or head; operative -> peg. */
+ SVN_ERR(svn_opt_resolve_revisions(&peg_rev, &start_rev,
+ svn_path_is_url(path_or_url),
+ TRUE /* notice_local_mods */,
+ pool));
+
+ /* Run the history function to get the object's (possibly
+ different) url in REVISION. */
+ SVN_ERR(svn_client__repos_locations(&url, &rev, NULL, NULL,
+ ra_session, path_or_url, &peg_rev,
+ &start_rev, NULL, ctx, pool));
+
+ SVN_ERR(svn_client__pathrev_create_with_session(resolved_loc_p,
+ ra_session, rev, url, pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__ra_session_from_path2(svn_ra_session_t **ra_session_p,
+ svn_client__pathrev_t **resolved_loc_p,
+ const char *path_or_url,
+ const char *base_dir_abspath,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session;
+ const char *initial_url;
+ const char *corrected_url;
+ svn_client__pathrev_t *resolved_loc;
+ const char *wri_abspath;
+
+ SVN_ERR(svn_client_url_from_path2(&initial_url, path_or_url, ctx, pool,
+ pool));
+ if (! initial_url)
+ return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
+ _("'%s' has no URL"), path_or_url);
+
+ if (base_dir_abspath)
+ wri_abspath = base_dir_abspath;
+ else if (!svn_path_is_url(path_or_url))
+ SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url, pool));
+ else
+ wri_abspath = NULL;
+
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
+ initial_url,
+ wri_abspath,
+ NULL /* commit_items */,
+ base_dir_abspath != NULL,
+ base_dir_abspath != NULL,
+ ctx, pool, pool));
+
+ /* If we got a CORRECTED_URL, we'll want to refer to that as the
+ URL-ized form of PATH_OR_URL from now on. */
+ if (corrected_url && svn_path_is_url(path_or_url))
+ path_or_url = corrected_url;
+
+ SVN_ERR(svn_client__resolve_rev_and_url(&resolved_loc, ra_session,
+ path_or_url, peg_revision, revision,
+ ctx, pool));
+
+ /* Make the session point to the real URL. */
+ SVN_ERR(svn_ra_reparent(ra_session, resolved_loc->url, pool));
+
+ *ra_session_p = ra_session;
+ if (resolved_loc_p)
+ *resolved_loc_p = resolved_loc;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__ensure_ra_session_url(const char **old_session_url,
+ svn_ra_session_t *ra_session,
+ const char *session_url,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_ra_get_session_url(ra_session, old_session_url, pool));
+ if (! session_url)
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &session_url, pool));
+ if (strcmp(*old_session_url, session_url) != 0)
+ SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Repository Locations ***/
+
+struct gls_receiver_baton_t
+{
+ apr_array_header_t *segments;
+ svn_client_ctx_t *ctx;
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+gls_receiver(svn_location_segment_t *segment,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct gls_receiver_baton_t *b = baton;
+ APR_ARRAY_PUSH(b->segments, svn_location_segment_t *) =
+ svn_location_segment_dup(segment, b->pool);
+ if (b->ctx->cancel_func)
+ SVN_ERR((b->ctx->cancel_func)(b->ctx->cancel_baton));
+ return SVN_NO_ERROR;
+}
+
+/* A qsort-compatible function which sorts svn_location_segment_t's
+ based on their revision range covering, resulting in ascending
+ (oldest-to-youngest) ordering. */
+static int
+compare_segments(const void *a, const void *b)
+{
+ const svn_location_segment_t *a_seg
+ = *((const svn_location_segment_t * const *) a);
+ const svn_location_segment_t *b_seg
+ = *((const svn_location_segment_t * const *) b);
+ if (a_seg->range_start == b_seg->range_start)
+ return 0;
+ return (a_seg->range_start < b_seg->range_start) ? -1 : 1;
+}
+
+svn_error_t *
+svn_client__repos_location_segments(apr_array_header_t **segments,
+ svn_ra_session_t *ra_session,
+ const char *url,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct gls_receiver_baton_t gls_receiver_baton;
+ const char *old_session_url;
+ svn_error_t *err;
+
+ *segments = apr_array_make(pool, 8, sizeof(svn_location_segment_t *));
+ gls_receiver_baton.segments = *segments;
+ gls_receiver_baton.ctx = ctx;
+ gls_receiver_baton.pool = pool;
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
+ url, pool));
+ err = svn_ra_get_location_segments(ra_session, "", peg_revision,
+ start_revision, end_revision,
+ gls_receiver, &gls_receiver_baton,
+ pool);
+ SVN_ERR(svn_error_compose_create(
+ err, svn_ra_reparent(ra_session, old_session_url, pool)));
+ qsort((*segments)->elts, (*segments)->nelts,
+ (*segments)->elt_size, compare_segments);
+ return SVN_NO_ERROR;
+}
+
+/* Set *START_URL and *END_URL to the URLs that the object URL@PEG_REVNUM
+ * had in revisions START_REVNUM and END_REVNUM. Return an error if the
+ * node cannot be traced back to one of the requested revisions.
+ *
+ * START_URL and/or END_URL may be NULL if not wanted. START_REVNUM and
+ * END_REVNUM must be valid revision numbers except that END_REVNUM may
+ * be SVN_INVALID_REVNUM if END_URL is NULL.
+ *
+ * RA_SESSION is an open RA session parented at URL.
+ */
+static svn_error_t *
+repos_locations(const char **start_url,
+ const char **end_url,
+ svn_ra_session_t *ra_session,
+ const char *url,
+ svn_revnum_t peg_revnum,
+ svn_revnum_t start_revnum,
+ svn_revnum_t end_revnum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_url, *start_path, *end_path;
+ apr_array_header_t *revs;
+ apr_hash_t *rev_locs;
+
+ SVN_ERR_ASSERT(peg_revnum != SVN_INVALID_REVNUM);
+ SVN_ERR_ASSERT(start_revnum != SVN_INVALID_REVNUM);
+ SVN_ERR_ASSERT(end_revnum != SVN_INVALID_REVNUM || end_url == NULL);
+
+ /* Avoid a network request in the common easy case. */
+ if (start_revnum == peg_revnum
+ && (end_revnum == peg_revnum || end_revnum == SVN_INVALID_REVNUM))
+ {
+ if (start_url)
+ *start_url = apr_pstrdup(result_pool, url);
+ if (end_url)
+ *end_url = apr_pstrdup(result_pool, url);
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, scratch_pool));
+
+ revs = apr_array_make(scratch_pool, 2, sizeof(svn_revnum_t));
+ APR_ARRAY_PUSH(revs, svn_revnum_t) = start_revnum;
+ if (end_revnum != start_revnum && end_revnum != SVN_INVALID_REVNUM)
+ APR_ARRAY_PUSH(revs, svn_revnum_t) = end_revnum;
+
+ SVN_ERR(svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum,
+ revs, scratch_pool));
+
+ /* We'd better have all the paths we were looking for! */
+ if (start_url)
+ {
+ start_path = apr_hash_get(rev_locs, &start_revnum, sizeof(svn_revnum_t));
+ if (! start_path)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+ _("Unable to find repository location for '%s' in revision %ld"),
+ url, start_revnum);
+ *start_url = svn_path_url_add_component2(repos_url, start_path + 1,
+ result_pool);
+ }
+
+ if (end_url)
+ {
+ end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(svn_revnum_t));
+ if (! end_path)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+ _("The location for '%s' for revision %ld does not exist in the "
+ "repository or refers to an unrelated object"),
+ url, end_revnum);
+
+ *end_url = svn_path_url_add_component2(repos_url, end_path + 1,
+ result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__repos_location(svn_client__pathrev_t **op_loc_p,
+ svn_ra_session_t *ra_session,
+ const svn_client__pathrev_t *peg_loc,
+ svn_revnum_t op_revnum,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *old_session_url;
+ const char *op_url;
+ svn_error_t *err;
+
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
+ peg_loc->url, scratch_pool));
+ err = repos_locations(&op_url, NULL, ra_session,
+ peg_loc->url, peg_loc->rev,
+ op_revnum, SVN_INVALID_REVNUM,
+ result_pool, scratch_pool);
+ SVN_ERR(svn_error_compose_create(
+ err, svn_ra_reparent(ra_session, old_session_url, scratch_pool)));
+
+ *op_loc_p = svn_client__pathrev_create(peg_loc->repos_root_url,
+ peg_loc->repos_uuid,
+ op_revnum, op_url, result_pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__repos_locations(const char **start_url,
+ svn_revnum_t *start_revision,
+ const char **end_url,
+ svn_revnum_t *end_revision,
+ svn_ra_session_t *ra_session,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *url;
+ const char *local_abspath_or_url;
+ svn_revnum_t peg_revnum = SVN_INVALID_REVNUM;
+ svn_revnum_t start_revnum, end_revnum;
+ svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Ensure that we are given some real revision data to work with.
+ (It's okay if the END is unspecified -- in that case, we'll just
+ set it to the same thing as START.) */
+ if (revision->kind == svn_opt_revision_unspecified
+ || start->kind == svn_opt_revision_unspecified)
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
+
+ if (end == NULL)
+ {
+ static const svn_opt_revision_t unspecified_rev
+ = { svn_opt_revision_unspecified, { 0 } };
+
+ end = &unspecified_rev;
+ }
+
+ /* Determine LOCAL_ABSPATH_OR_URL, URL, and possibly PEG_REVNUM.
+ If we are looking at the working version of a WC path that is scheduled
+ as a copy, then we need to use the copy-from URL and peg revision. */
+ if (! svn_path_is_url(path))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path, subpool));
+
+ if (revision->kind == svn_opt_revision_working)
+ {
+ const char *repos_root_url;
+ const char *repos_relpath;
+ svn_boolean_t is_copy;
+
+ SVN_ERR(svn_wc__node_get_origin(&is_copy, &peg_revnum, &repos_relpath,
+ &repos_root_url, NULL, NULL,
+ ctx->wc_ctx, local_abspath_or_url,
+ FALSE, subpool, subpool));
+
+ if (repos_relpath)
+ url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ pool);
+ else
+ url = NULL;
+
+ if (url && is_copy && ra_session)
+ {
+ const char *session_url;
+ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url,
+ subpool));
+
+ if (strcmp(session_url, url) != 0)
+ {
+ /* We can't use the caller provided RA session now :( */
+ ra_session = NULL;
+ }
+ }
+ }
+ else
+ url = NULL;
+
+ if (! url)
+ SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx,
+ local_abspath_or_url, pool, subpool));
+
+ if (!url)
+ {
+ return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
+ _("'%s' has no URL"),
+ svn_dirent_local_style(path, pool));
+ }
+ }
+ else
+ {
+ local_abspath_or_url = path;
+ url = path;
+ }
+
+ /* ### We should be smarter here. If the callers just asks for BASE and
+ WORKING revisions, we should already have the correct URLs, so we
+ don't need to do anything more here in that case. */
+
+ /* Open a RA session to this URL if we don't have one already. */
+ if (! ra_session)
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
+ ctx, subpool, subpool));
+
+ /* Resolve the opt_revision_ts. */
+ if (peg_revnum == SVN_INVALID_REVNUM)
+ SVN_ERR(svn_client__get_revision_number(&peg_revnum, &youngest_rev,
+ ctx->wc_ctx, local_abspath_or_url,
+ ra_session, revision, pool));
+
+ SVN_ERR(svn_client__get_revision_number(&start_revnum, &youngest_rev,
+ ctx->wc_ctx, local_abspath_or_url,
+ ra_session, start, pool));
+ if (end->kind == svn_opt_revision_unspecified)
+ end_revnum = start_revnum;
+ else
+ SVN_ERR(svn_client__get_revision_number(&end_revnum, &youngest_rev,
+ ctx->wc_ctx, local_abspath_or_url,
+ ra_session, end, pool));
+
+ /* Set the output revision variables. */
+ if (start_revision)
+ {
+ *start_revision = start_revnum;
+ }
+ if (end_revision && end->kind != svn_opt_revision_unspecified)
+ {
+ *end_revision = end_revnum;
+ }
+
+ SVN_ERR(repos_locations(start_url, end_url,
+ ra_session, url, peg_revnum,
+ start_revnum, end_revnum,
+ pool, subpool));
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__get_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p,
+ const svn_client__pathrev_t *loc1,
+ const svn_client__pathrev_t *loc2,
+ svn_ra_session_t *session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *sesspool = NULL;
+ apr_hash_t *history1, *history2;
+ apr_hash_index_t *hi;
+ svn_revnum_t yc_revision = SVN_INVALID_REVNUM;
+ const char *yc_relpath = NULL;
+ svn_boolean_t has_rev_zero_history1;
+ svn_boolean_t has_rev_zero_history2;
+
+ if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0)
+ {
+ *ancestor_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Open an RA session for the two locations. */
+ if (session == NULL)
+ {
+ sesspool = svn_pool_create(scratch_pool);
+ SVN_ERR(svn_client_open_ra_session2(&session, loc1->url, NULL, ctx,
+ sesspool, sesspool));
+ }
+
+ /* We're going to cheat and use history-as-mergeinfo because it
+ saves us a bunch of annoying custom data comparisons and such. */
+ SVN_ERR(svn_client__get_history_as_mergeinfo(&history1,
+ &has_rev_zero_history1,
+ loc1,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ session, ctx, scratch_pool));
+ SVN_ERR(svn_client__get_history_as_mergeinfo(&history2,
+ &has_rev_zero_history2,
+ loc2,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ session, ctx, scratch_pool));
+ /* Close the ra session if we opened one. */
+ if (sesspool)
+ svn_pool_destroy(sesspool);
+
+ /* Loop through the first location's history, check for overlapping
+ paths and ranges in the second location's history, and
+ remembering the youngest matching location. */
+ for (hi = apr_hash_first(scratch_pool, history1); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ apr_ssize_t path_len = svn__apr_hash_index_klen(hi);
+ svn_rangelist_t *ranges1 = svn__apr_hash_index_val(hi);
+ svn_rangelist_t *ranges2, *common;
+
+ ranges2 = apr_hash_get(history2, path, path_len);
+ if (ranges2)
+ {
+ /* We have a path match. Now, did our two histories share
+ any revisions at that path? */
+ SVN_ERR(svn_rangelist_intersect(&common, ranges1, ranges2,
+ TRUE, scratch_pool));
+ if (common->nelts)
+ {
+ svn_merge_range_t *yc_range =
+ APR_ARRAY_IDX(common, common->nelts - 1, svn_merge_range_t *);
+ if ((! SVN_IS_VALID_REVNUM(yc_revision))
+ || (yc_range->end > yc_revision))
+ {
+ yc_revision = yc_range->end;
+ yc_relpath = path + 1;
+ }
+ }
+ }
+ }
+
+ /* It's possible that PATH_OR_URL1 and PATH_OR_URL2's only common
+ history is revision 0. */
+ if (!yc_relpath && has_rev_zero_history1 && has_rev_zero_history2)
+ {
+ yc_relpath = "";
+ yc_revision = 0;
+ }
+
+ if (yc_relpath)
+ {
+ *ancestor_p = svn_client__pathrev_create_with_relpath(
+ loc1->repos_root_url, loc1->repos_uuid,
+ yc_revision, yc_relpath, result_pool);
+ }
+ else
+ {
+ *ancestor_p = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__youngest_common_ancestor(const char **ancestor_url,
+ svn_revnum_t *ancestor_rev,
+ const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *sesspool = svn_pool_create(scratch_pool);
+ svn_ra_session_t *session;
+ svn_client__pathrev_t *loc1, *loc2, *ancestor;
+
+ /* Resolve the two locations */
+ SVN_ERR(svn_client__ra_session_from_path2(&session, &loc1,
+ path_or_url1, NULL,
+ revision1, revision1,
+ ctx, sesspool));
+ SVN_ERR(svn_client__resolve_rev_and_url(&loc2, session,
+ path_or_url2, revision2, revision2,
+ ctx, scratch_pool));
+
+ SVN_ERR(svn_client__get_youngest_common_ancestor(
+ &ancestor, loc1, loc2, session, ctx, result_pool, scratch_pool));
+
+ if (ancestor)
+ {
+ *ancestor_url = ancestor->url;
+ *ancestor_rev = ancestor->rev;
+ }
+ else
+ {
+ *ancestor_url = NULL;
+ *ancestor_rev = SVN_INVALID_REVNUM;
+ }
+ svn_pool_destroy(sesspool);
+ return SVN_NO_ERROR;
+}
+
+
+struct ra_ev2_baton {
+ /* The working copy context, from the client context. */
+ svn_wc_context_t *wc_ctx;
+
+ /* For a given REPOS_RELPATH, provide a LOCAL_ABSPATH that represents
+ that repository node. */
+ apr_hash_t *relpath_map;
+};
+
+
+svn_error_t *
+svn_client__ra_provide_base(svn_stream_t **contents,
+ svn_revnum_t *revision,
+ void *baton,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct ra_ev2_baton *reb = baton;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
+ if (!local_abspath)
+ {
+ *contents = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_wc_get_pristine_contents2(contents, reb->wc_ctx, local_abspath,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ *contents = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (*contents != NULL)
+ {
+ /* The pristine contents refer to the BASE, or to the pristine of
+ a copy/move to this location. Fetch the correct revision. */
+ SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL,
+ reb->wc_ctx, local_abspath, FALSE,
+ scratch_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__ra_provide_props(apr_hash_t **props,
+ svn_revnum_t *revision,
+ void *baton,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct ra_ev2_baton *reb = baton;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
+ if (!local_abspath)
+ {
+ *props = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_wc_get_pristine_props(props, reb->wc_ctx, local_abspath,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ *props = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (*props != NULL)
+ {
+ /* The pristine props refer to the BASE, or to the pristine props of
+ a copy/move to this location. Fetch the correct revision. */
+ SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL,
+ reb->wc_ctx, local_abspath, FALSE,
+ scratch_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client__ra_get_copysrc_kind(svn_node_kind_t *kind,
+ void *baton,
+ const char *repos_relpath,
+ svn_revnum_t src_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct ra_ev2_baton *reb = baton;
+ const char *local_abspath;
+
+ local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
+ if (!local_abspath)
+ {
+ *kind = svn_node_unknown;
+ return SVN_NO_ERROR;
+ }
+
+ /* ### what to do with SRC_REVISION? */
+
+ SVN_ERR(svn_wc_read_kind2(kind, reb->wc_ctx, local_abspath,
+ FALSE, FALSE, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+void *
+svn_client__ra_make_cb_baton(svn_wc_context_t *wc_ctx,
+ apr_hash_t *relpath_map,
+ apr_pool_t *result_pool)
+{
+ struct ra_ev2_baton *reb = apr_palloc(result_pool, sizeof(*reb));
+
+ SVN_ERR_ASSERT_NO_RETURN(wc_ctx != NULL);
+ SVN_ERR_ASSERT_NO_RETURN(relpath_map != NULL);
+
+ reb->wc_ctx = wc_ctx;
+ reb->relpath_map = relpath_map;
+
+ return reb;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/relocate.c b/contrib/subversion/subversion/libsvn_client/relocate.c
new file mode 100644
index 0000000..ed8d09c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/relocate.c
@@ -0,0 +1,289 @@
+/*
+ * relocate.c: wrapper around wc relocation functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "client.h"
+
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* Repository root and UUID for a repository. */
+struct url_uuid_t
+{
+ const char *root;
+ const char *uuid;
+};
+
+struct validator_baton_t
+{
+ svn_client_ctx_t *ctx;
+ const char *path;
+ apr_array_header_t *url_uuids;
+ apr_pool_t *pool;
+
+};
+
+
+static svn_error_t *
+validator_func(void *baton,
+ const char *uuid,
+ const char *url,
+ const char *root_url,
+ apr_pool_t *pool)
+{
+ struct validator_baton_t *b = baton;
+ struct url_uuid_t *url_uuid = NULL;
+ const char *disable_checks;
+
+ apr_array_header_t *uuids = b->url_uuids;
+ int i;
+
+ for (i = 0; i < uuids->nelts; ++i)
+ {
+ struct url_uuid_t *uu = &APR_ARRAY_IDX(uuids, i,
+ struct url_uuid_t);
+ if (svn_uri__is_ancestor(uu->root, url))
+ {
+ url_uuid = uu;
+ break;
+ }
+ }
+
+ disable_checks = getenv("SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_RELOCATE_VALIDATION");
+ if (disable_checks && (strcmp(disable_checks, "yes") == 0))
+ {
+ /* Lie about URL_UUID's components, claiming they match the
+ expectations of the validation code below. */
+ url_uuid = apr_pcalloc(pool, sizeof(*url_uuid));
+ url_uuid->root = apr_pstrdup(pool, root_url);
+ url_uuid->uuid = apr_pstrdup(pool, uuid);
+ }
+
+ /* We use an RA session in a subpool to get the UUID of the
+ repository at the new URL so we can force the RA session to close
+ by destroying the subpool. */
+ if (! url_uuid)
+ {
+ apr_pool_t *sesspool = svn_pool_create(pool);
+
+ url_uuid = &APR_ARRAY_PUSH(uuids, struct url_uuid_t);
+ SVN_ERR(svn_client_get_repos_root(&url_uuid->root,
+ &url_uuid->uuid,
+ url, b->ctx,
+ pool, sesspool));
+
+ svn_pool_destroy(sesspool);
+ }
+
+ /* Make sure the url is a repository root if desired. */
+ if (root_url
+ && strcmp(root_url, url_uuid->root) != 0)
+ return svn_error_createf(SVN_ERR_CLIENT_INVALID_RELOCATION, NULL,
+ _("'%s' is not the root of the repository"),
+ url);
+
+ /* Make sure the UUIDs match. */
+ if (uuid && strcmp(uuid, url_uuid->uuid) != 0)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_RELOCATION, NULL,
+ _("The repository at '%s' has uuid '%s', but the WC has '%s'"),
+ url, url_uuid->uuid, uuid);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Examing the array of svn_wc_external_item2_t's EXT_DESC (parsed
+ from the svn:externals property set on LOCAL_ABSPATH) and determine
+ if the external working copies described by such should be
+ relocated as a side-effect of the relocation of their parent
+ working copy (from OLD_PARENT_REPOS_ROOT_URL to
+ NEW_PARENT_REPOS_ROOT_URL). If so, attempt said relocation. */
+static svn_error_t *
+relocate_externals(const char *local_abspath,
+ apr_array_header_t *ext_desc,
+ const char *old_parent_repos_root_url,
+ const char *new_parent_repos_root_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ int i;
+
+ /* Parse an externals definition into an array of external items. */
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < ext_desc->nelts; i++)
+ {
+ svn_wc_external_item2_t *ext_item =
+ APR_ARRAY_IDX(ext_desc, i, svn_wc_external_item2_t *);
+ const char *target_repos_root_url;
+ const char *target_abspath;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ /* If this external isn't pulled in via a relative URL, ignore
+ it. There's no sense in relocating a working copy only to
+ have the next 'svn update' try to point it back to another
+ location. */
+ if (! ((strncmp("../", ext_item->url, 3) == 0) ||
+ (strncmp("^/", ext_item->url, 2) == 0)))
+ continue;
+
+ /* If the external working copy's not-yet-relocated repos root
+ URL matches the primary working copy's pre-relocated
+ repository root URL, try to relocate that external, too.
+ You might wonder why this check is needed, given that we're
+ already limiting ourselves to externals pulled via URLs
+ relative to their primary working copy. Well, it's because
+ you can use "../" to "crawl up" above one repository's URL
+ space and down into another one. */
+ SVN_ERR(svn_dirent_get_absolute(&target_abspath,
+ svn_dirent_join(local_abspath,
+ ext_item->target_dir,
+ iterpool),
+ iterpool));
+ err = svn_client_get_repos_root(&target_repos_root_url, NULL /* uuid */,
+ target_abspath, ctx, iterpool, iterpool);
+
+ /* Ignore externals that aren't present in the working copy.
+ * This can happen if an external is deleted from disk accidentally,
+ * or if an external is configured on a locally added directory. */
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ continue;
+ }
+ else
+ SVN_ERR(err);
+
+ if (strcmp(target_repos_root_url, old_parent_repos_root_url) == 0)
+ SVN_ERR(svn_client_relocate2(target_abspath,
+ old_parent_repos_root_url,
+ new_parent_repos_root_url,
+ FALSE, ctx, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_relocate2(const char *wcroot_dir,
+ const char *from_prefix,
+ const char *to_prefix,
+ svn_boolean_t ignore_externals,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct validator_baton_t vb;
+ const char *local_abspath;
+ apr_hash_t *externals_hash = NULL;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = NULL;
+ const char *old_repos_root_url, *new_repos_root_url;
+
+ /* Populate our validator callback baton, and call the relocate code. */
+ vb.ctx = ctx;
+ vb.path = wcroot_dir;
+ vb.url_uuids = apr_array_make(pool, 1, sizeof(struct url_uuid_t));
+ vb.pool = pool;
+
+ if (svn_path_is_url(wcroot_dir))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"),
+ wcroot_dir);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, wcroot_dir, pool));
+
+ /* If we're ignoring externals, just relocate and get outta here. */
+ if (ignore_externals)
+ {
+ return svn_error_trace(svn_wc_relocate4(ctx->wc_ctx, local_abspath,
+ from_prefix, to_prefix,
+ validator_func, &vb, pool));
+ }
+
+ /* Fetch our current root URL. */
+ SVN_ERR(svn_client_get_repos_root(&old_repos_root_url, NULL /* uuid */,
+ local_abspath, ctx, pool, pool));
+
+ /* Perform the relocation. */
+ SVN_ERR(svn_wc_relocate4(ctx->wc_ctx, local_abspath, from_prefix, to_prefix,
+ validator_func, &vb, pool));
+
+ /* Now fetch new current root URL. */
+ SVN_ERR(svn_client_get_repos_root(&new_repos_root_url, NULL /* uuid */,
+ local_abspath, ctx, pool, pool));
+
+
+ /* Relocate externals, too (if any). */
+ SVN_ERR(svn_wc__externals_gather_definitions(&externals_hash, NULL,
+ ctx->wc_ctx, local_abspath,
+ svn_depth_infinity,
+ pool, pool));
+ if (! apr_hash_count(externals_hash))
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(pool);
+
+ for (hi = apr_hash_first(pool, externals_hash);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ const char *this_abspath = svn__apr_hash_index_key(hi);
+ const char *value = svn__apr_hash_index_val(hi);
+ apr_array_header_t *ext_desc;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc_parse_externals_description3(&ext_desc, this_abspath,
+ value, FALSE,
+ iterpool));
+ if (ext_desc->nelts)
+ SVN_ERR(relocate_externals(this_abspath, ext_desc, old_repos_root_url,
+ new_repos_root_url, ctx, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/repos_diff.c b/contrib/subversion/subversion/libsvn_client/repos_diff.c
new file mode 100644
index 0000000..6a7725f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/repos_diff.c
@@ -0,0 +1,1405 @@
+/*
+ * repos_diff.c -- The diff editor for comparing two repository versions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* This code uses an editor driven by a tree delta between two
+ * repository revisions (REV1 and REV2). For each file encountered in
+ * the delta the editor constructs two temporary files, one for each
+ * revision. This necessitates a separate request for the REV1 version
+ * of the file when the delta shows the file being modified or
+ * deleted. Files that are added by the delta do not require a
+ * separate request, the REV1 version is empty and the delta is
+ * sufficient to construct the REV2 version. When both versions of
+ * each file have been created the diff callback is invoked to display
+ * the difference between the two files. */
+
+#include <apr_uri.h>
+#include <apr_md5.h>
+#include <assert.h>
+
+#include "svn_checksum.h"
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_io.h"
+#include "svn_props.h"
+#include "svn_private_config.h"
+
+#include "client.h"
+
+#include "private/svn_subr_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_editor.h"
+
+/* Overall crawler editor baton. */
+struct edit_baton {
+ /* The passed depth */
+ svn_depth_t depth;
+
+ /* The result processor */
+ const svn_diff_tree_processor_t *processor;
+
+ /* RA_SESSION is the open session for making requests to the RA layer */
+ svn_ra_session_t *ra_session;
+
+ /* The rev1 from the '-r Rev1:Rev2' command line option */
+ svn_revnum_t revision;
+
+ /* The rev2 from the '-r Rev1:Rev2' option, specifically set by
+ set_target_revision(). */
+ svn_revnum_t target_revision;
+
+ /* The path to a temporary empty file used for add/delete
+ differences. The path is cached here so that it can be reused,
+ since all empty files are the same. */
+ const char *empty_file;
+
+ /* Empty hash used for adds. */
+ apr_hash_t *empty_hash;
+
+ /* Whether to report text deltas */
+ svn_boolean_t text_deltas;
+
+ /* A callback used to see if the client wishes to cancel the running
+ operation. */
+ svn_cancel_func_t cancel_func;
+
+ /* A baton to pass to the cancellation callback. */
+ void *cancel_baton;
+
+ apr_pool_t *pool;
+};
+
+typedef struct deleted_path_notify_t
+{
+ svn_node_kind_t kind;
+ svn_wc_notify_action_t action;
+ svn_wc_notify_state_t state;
+ svn_boolean_t tree_conflicted;
+} deleted_path_notify_t;
+
+/* Directory level baton.
+ */
+struct dir_baton {
+ /* Gets set if the directory is added rather than replaced/unchanged. */
+ svn_boolean_t added;
+
+ /* Gets set if this operation caused a tree-conflict on this directory
+ * (does not show tree-conflicts persisting from before this operation). */
+ svn_boolean_t tree_conflicted;
+
+ /* If TRUE, this node is skipped entirely.
+ * This is used to skip all children of a tree-conflicted
+ * directory without setting TREE_CONFLICTED to TRUE everywhere. */
+ svn_boolean_t skip;
+
+ /* If TRUE, all children of this directory are skipped. */
+ svn_boolean_t skip_children;
+
+ /* The path of the directory within the repository */
+ const char *path;
+
+ /* The baton for the parent directory, or null if this is the root of the
+ hierarchy to be compared. */
+ struct dir_baton *parent_baton;
+
+ /* The overall crawler editor baton. */
+ struct edit_baton *edit_baton;
+
+ /* A cache of any property changes (svn_prop_t) received for this dir. */
+ apr_array_header_t *propchanges;
+
+ /* Boolean indicating whether a node property was changed */
+ svn_boolean_t has_propchange;
+
+ /* Baton for svn_diff_tree_processor_t */
+ void *pdb;
+ svn_diff_source_t *left_source;
+ svn_diff_source_t *right_source;
+
+ /* The pool passed in by add_dir, open_dir, or open_root.
+ Also, the pool this dir baton is allocated in. */
+ apr_pool_t *pool;
+
+ /* Base revision of directory. */
+ svn_revnum_t base_revision;
+
+ /* Number of users of baton. Its pool will be destroyed 0 */
+ int users;
+};
+
+/* File level baton.
+ */
+struct file_baton {
+ /* Reference to parent baton */
+ struct dir_baton *parent_baton;
+
+ /* Gets set if the file is added rather than replaced. */
+ svn_boolean_t added;
+
+ /* Gets set if this operation caused a tree-conflict on this file
+ * (does not show tree-conflicts persisting from before this operation). */
+ svn_boolean_t tree_conflicted;
+
+ /* If TRUE, this node is skipped entirely.
+ * This is currently used to skip all children of a tree-conflicted
+ * directory. */
+ svn_boolean_t skip;
+
+ /* The path of the file within the repository */
+ const char *path;
+
+ /* The path and APR file handle to the temporary file that contains the
+ first repository version. Also, the pristine-property list of
+ this file. */
+ const char *path_start_revision;
+ apr_hash_t *pristine_props;
+ svn_revnum_t base_revision;
+
+ /* The path and APR file handle to the temporary file that contains the
+ second repository version. These fields are set when processing
+ textdelta and file deletion, and will be NULL if there's no
+ textual difference between the two revisions. */
+ const char *path_end_revision;
+
+ /* APPLY_HANDLER/APPLY_BATON represent the delta application baton. */
+ svn_txdelta_window_handler_t apply_handler;
+ void *apply_baton;
+
+ /* The overall crawler editor baton. */
+ struct edit_baton *edit_baton;
+
+ /* Holds the checksum of the start revision file */
+ svn_checksum_t *start_md5_checksum;
+
+ /* Holds the resulting md5 digest of a textdelta transform */
+ unsigned char result_digest[APR_MD5_DIGESTSIZE];
+ svn_checksum_t *result_md5_checksum;
+
+ /* A cache of any property changes (svn_prop_t) received for this file. */
+ apr_array_header_t *propchanges;
+
+ /* Boolean indicating whether a node property was changed */
+ svn_boolean_t has_propchange;
+
+ /* Baton for svn_diff_tree_processor_t */
+ void *pfb;
+ svn_diff_source_t *left_source;
+ svn_diff_source_t *right_source;
+
+ /* The pool passed in by add_file or open_file.
+ Also, the pool this file_baton is allocated in. */
+ apr_pool_t *pool;
+};
+
+/* Create a new directory baton for PATH in POOL. ADDED is set if
+ * this directory is being added rather than replaced. PARENT_BATON is
+ * the baton of the parent directory (or NULL if this is the root of
+ * the comparison hierarchy). The directory and its parent may or may
+ * not exist in the working copy. EDIT_BATON is the overall crawler
+ * editor baton.
+ */
+static struct dir_baton *
+make_dir_baton(const char *path,
+ struct dir_baton *parent_baton,
+ struct edit_baton *edit_baton,
+ svn_boolean_t added,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool)
+{
+ apr_pool_t *dir_pool = svn_pool_create(result_pool);
+ struct dir_baton *dir_baton = apr_pcalloc(dir_pool, sizeof(*dir_baton));
+
+ dir_baton->parent_baton = parent_baton;
+ dir_baton->edit_baton = edit_baton;
+ dir_baton->added = added;
+ dir_baton->tree_conflicted = FALSE;
+ dir_baton->skip = FALSE;
+ dir_baton->skip_children = FALSE;
+ dir_baton->pool = dir_pool;
+ dir_baton->path = apr_pstrdup(dir_pool, path);
+ dir_baton->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t));
+ dir_baton->base_revision = base_revision;
+ dir_baton->users++;
+
+ if (parent_baton)
+ parent_baton->users++;
+
+ return dir_baton;
+}
+
+/* New function. Called by everyone who has a reference when done */
+static svn_error_t *
+release_dir(struct dir_baton *db)
+{
+ assert(db->users > 0);
+
+ db->users--;
+ if (db->users)
+ return SVN_NO_ERROR;
+
+ {
+ struct dir_baton *pb = db->parent_baton;
+
+ svn_pool_destroy(db->pool);
+
+ if (pb != NULL)
+ SVN_ERR(release_dir(pb));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Create a new file baton for PATH in POOL, which is a child of
+ * directory PARENT_PATH. ADDED is set if this file is being added
+ * rather than replaced. EDIT_BATON is a pointer to the global edit
+ * baton.
+ */
+static struct file_baton *
+make_file_baton(const char *path,
+ struct dir_baton *parent_baton,
+ svn_boolean_t added,
+ apr_pool_t *result_pool)
+{
+ apr_pool_t *file_pool = svn_pool_create(result_pool);
+ struct file_baton *file_baton = apr_pcalloc(file_pool, sizeof(*file_baton));
+
+ file_baton->parent_baton = parent_baton;
+ file_baton->edit_baton = parent_baton->edit_baton;
+ file_baton->added = added;
+ file_baton->tree_conflicted = FALSE;
+ file_baton->skip = FALSE;
+ file_baton->pool = file_pool;
+ file_baton->path = apr_pstrdup(file_pool, path);
+ file_baton->propchanges = apr_array_make(file_pool, 8, sizeof(svn_prop_t));
+ file_baton->base_revision = parent_baton->edit_baton->revision;
+
+ parent_baton->users++;
+
+ return file_baton;
+}
+
+/* Get revision FB->base_revision of the file described by FB from the
+ * repository, through FB->edit_baton->ra_session.
+ *
+ * Unless PROPS_ONLY is true:
+ * Set FB->path_start_revision to the path of a new temporary file containing
+ * the file's text.
+ * Set FB->start_md5_checksum to that file's MD-5 checksum.
+ * Install a pool cleanup handler on FB->pool to delete the file.
+ *
+ * Always:
+ * Set FB->pristine_props to a new hash containing the file's properties.
+ *
+ * Allocate all results in FB->pool.
+ */
+static svn_error_t *
+get_file_from_ra(struct file_baton *fb,
+ svn_boolean_t props_only,
+ apr_pool_t *scratch_pool)
+{
+ if (! props_only)
+ {
+ svn_stream_t *fstream;
+
+ SVN_ERR(svn_stream_open_unique(&fstream, &(fb->path_start_revision),
+ NULL, svn_io_file_del_on_pool_cleanup,
+ fb->pool, scratch_pool));
+
+ fstream = svn_stream_checksummed2(fstream, NULL, &fb->start_md5_checksum,
+ svn_checksum_md5, TRUE, scratch_pool);
+
+ /* Retrieve the file and its properties */
+ SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
+ fb->path,
+ fb->base_revision,
+ fstream, NULL,
+ &(fb->pristine_props),
+ fb->pool));
+ SVN_ERR(svn_stream_close(fstream));
+ }
+ else
+ {
+ SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session,
+ fb->path,
+ fb->base_revision,
+ NULL, NULL,
+ &(fb->pristine_props),
+ fb->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Remove every no-op property change from CHANGES: that is, remove every
+ entry in which the target value is the same as the value of the
+ corresponding property in PRISTINE_PROPS.
+
+ Issue #3657 'dav update report handler in skelta mode can cause
+ spurious conflicts'. When communicating with the repository via ra_serf,
+ the change_dir_prop and change_file_prop svn_delta_editor_t
+ callbacks are called (obviously) when a directory or file property has
+ changed between the start and end of the edit. Less obvious however,
+ is that these callbacks may be made describing *all* of the properties
+ on FILE_BATON->PATH when using the DAV providers, not just the change(s).
+ (Specifically ra_serf does it for diff/merge/update/switch).
+
+ This means that the change_[file|dir]_prop svn_delta_editor_t callbacks
+ may be made where there are no property changes (i.e. a noop change of
+ NAME from VALUE to VALUE). Normally this is harmless, but during a
+ merge it can result in spurious conflicts if the WC's pristine property
+ NAME has a value other than VALUE. In an ideal world the mod_dav_svn
+ update report handler, when in 'skelta' mode and describing changes to
+ a path on which a property has changed, wouldn't ask the client to later
+ fetch all properties and figure out what has changed itself. The server
+ already knows which properties have changed!
+
+ Regardless, such a change is not yet implemented, and even when it is,
+ the client should DTRT with regard to older servers which behave this
+ way. Hence this little hack: We populate FILE_BATON->PROPCHANGES only
+ with *actual* property changes.
+
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=3657#desc9 and
+ http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details.
+ */
+static void
+remove_non_prop_changes(apr_hash_t *pristine_props,
+ apr_array_header_t *changes)
+{
+ int i;
+
+ for (i = 0; i < changes->nelts; i++)
+ {
+ svn_prop_t *change = &APR_ARRAY_IDX(changes, i, svn_prop_t);
+
+ if (change->value)
+ {
+ const svn_string_t *old_val = svn_hash_gets(pristine_props,
+ change->name);
+
+ if (old_val && svn_string_compare(old_val, change->value))
+ {
+ int j;
+
+ /* Remove the matching change by shifting the rest */
+ for (j = i; j < changes->nelts - 1; j++)
+ {
+ APR_ARRAY_IDX(changes, j, svn_prop_t)
+ = APR_ARRAY_IDX(changes, j+1, svn_prop_t);
+ }
+ changes->nelts--;
+ }
+ }
+ }
+}
+
+/* Get the empty file associated with the edit baton. This is cached so
+ * that it can be reused, all empty files are the same.
+ */
+static svn_error_t *
+get_empty_file(struct edit_baton *eb,
+ const char **empty_file_path)
+{
+ /* Create the file if it does not exist */
+ /* Note that we tried to use /dev/null in r857294, but
+ that won't work on Windows: it's impossible to stat NUL */
+ if (!eb->empty_file)
+ SVN_ERR(svn_io_open_unique_file3(NULL, &(eb->empty_file), NULL,
+ svn_io_file_del_on_pool_cleanup,
+ eb->pool, eb->pool));
+
+ *empty_file_path = eb->empty_file;
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ eb->target_revision = target_revision;
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. The root of the comparison hierarchy */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *db = make_dir_baton("", NULL, eb, FALSE, base_revision,
+ eb->pool);
+
+ db->left_source = svn_diff__source_create(eb->revision, db->pool);
+ db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
+
+ SVN_ERR(eb->processor->dir_opened(&db->pdb,
+ &db->skip,
+ &db->skip_children,
+ "",
+ db->left_source,
+ db->right_source,
+ NULL,
+ NULL,
+ eb->processor,
+ db->pool,
+ db->pool /* scratch_pool */));
+
+ *root_baton = db;
+ return SVN_NO_ERROR;
+}
+
+/* Compare a file being deleted against an empty file.
+ */
+static svn_error_t *
+diff_deleted_file(const char *path,
+ struct dir_baton *db,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = db->edit_baton;
+ struct file_baton *fb = make_file_baton(path, db, FALSE, scratch_pool);
+ svn_boolean_t skip = FALSE;
+ svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
+ scratch_pool);
+
+ if (eb->cancel_func)
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ SVN_ERR(eb->processor->file_opened(&fb->pfb, &skip, path,
+ left_source,
+ NULL /* right_source */,
+ NULL /* copyfrom_source */,
+ db->pdb,
+ eb->processor,
+ scratch_pool, scratch_pool));
+
+ if (eb->cancel_func)
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ if (skip)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(get_file_from_ra(fb, ! eb->text_deltas, scratch_pool));
+
+ SVN_ERR(eb->processor->file_deleted(fb->path,
+ left_source,
+ fb->path_start_revision,
+ fb->pristine_props,
+ fb->pfb,
+ eb->processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Recursively walk tree rooted at DIR (at EB->revision) in the repository,
+ * reporting all children as deleted. Part of a workaround for issue 2333.
+ *
+ * DIR is a repository path relative to the URL in EB->ra_session. EB is
+ * the overall crawler editor baton. EB->revision must be a valid revision
+ * number, not SVN_INVALID_REVNUM. Use EB->cancel_func (if not null) with
+ * EB->cancel_baton for cancellation.
+ */
+/* ### TODO: Handle depth. */
+static svn_error_t *
+diff_deleted_dir(const char *path,
+ struct dir_baton *pb,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *db;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_boolean_t skip = FALSE;
+ svn_boolean_t skip_children = FALSE;
+ apr_hash_t *dirents = NULL;
+ apr_hash_t *left_props = NULL;
+ svn_diff_source_t *left_source = svn_diff__source_create(eb->revision,
+ scratch_pool);
+ db = make_dir_baton(path, pb, pb->edit_baton, FALSE, SVN_INVALID_REVNUM,
+ scratch_pool);
+
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(eb->revision));
+
+ if (eb->cancel_func)
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ SVN_ERR(eb->processor->dir_opened(&db->pdb, &skip, &skip_children,
+ path,
+ left_source,
+ NULL /* right_source */,
+ NULL /* copyfrom_source */,
+ pb->pdb,
+ eb->processor,
+ scratch_pool, iterpool));
+
+ if (!skip || !skip_children)
+ SVN_ERR(svn_ra_get_dir2(eb->ra_session,
+ skip_children ? NULL : &dirents,
+ NULL,
+ skip ? NULL : &left_props,
+ path,
+ eb->revision,
+ SVN_DIRENT_KIND,
+ scratch_pool));
+
+ /* The "old" dir will be skipped by the repository report. If required,
+ * crawl it recursively, diffing each file against the empty file. This
+ * is a workaround for issue 2333 "'svn diff URL1 URL2' not reverse of
+ * 'svn diff URL2 URL1'". */
+ if (! skip_children)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, dirents); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child_path;
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_dirent_t *dirent = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ child_path = svn_relpath_join(path, name, iterpool);
+
+ if (dirent->kind == svn_node_file)
+ {
+ SVN_ERR(diff_deleted_file(child_path, db, iterpool));
+ }
+ else if (dirent->kind == svn_node_dir)
+ {
+ SVN_ERR(diff_deleted_dir(child_path, db, iterpool));
+ }
+ }
+ }
+
+ if (! skip)
+ {
+ SVN_ERR(eb->processor->dir_deleted(path,
+ left_source,
+ left_props,
+ db->pdb,
+ eb->processor,
+ scratch_pool));
+ }
+
+ SVN_ERR(release_dir(db));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t base_revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ svn_node_kind_t kind;
+ apr_pool_t *scratch_pool;
+
+ /* Process skips. */
+ if (pb->skip_children)
+ return SVN_NO_ERROR;
+
+ scratch_pool = svn_pool_create(eb->pool);
+
+ /* We need to know if this is a directory or a file */
+ SVN_ERR(svn_ra_check_path(eb->ra_session, path, eb->revision, &kind,
+ scratch_pool));
+
+ switch (kind)
+ {
+ case svn_node_file:
+ {
+ SVN_ERR(diff_deleted_file(path, pb, scratch_pool));
+ break;
+ }
+ case svn_node_dir:
+ {
+ SVN_ERR(diff_deleted_dir(path, pb, scratch_pool));
+ break;
+ }
+ default:
+ break;
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *db;
+
+ /* ### TODO: support copyfrom? */
+
+ db = make_dir_baton(path, pb, eb, TRUE, SVN_INVALID_REVNUM, pb->pool);
+ *child_baton = db;
+
+ /* Skip *everything* within a newly tree-conflicted directory,
+ * and directories the children of which should be skipped. */
+ if (pb->skip_children)
+ {
+ db->skip = TRUE;
+ db->skip_children = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ db->right_source = svn_diff__source_create(eb->target_revision,
+ db->pool);
+
+ SVN_ERR(eb->processor->dir_opened(&db->pdb,
+ &db->skip,
+ &db->skip_children,
+ db->path,
+ NULL,
+ db->right_source,
+ NULL /* copyfrom_source */,
+ pb->pdb,
+ eb->processor,
+ db->pool, db->pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *db;
+
+ db = make_dir_baton(path, pb, eb, FALSE, base_revision, pb->pool);
+
+ *child_baton = db;
+
+ /* Process Skips. */
+ if (pb->skip_children)
+ {
+ db->skip = TRUE;
+ db->skip_children = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ db->left_source = svn_diff__source_create(eb->revision, db->pool);
+ db->right_source = svn_diff__source_create(eb->target_revision, db->pool);
+
+ SVN_ERR(eb->processor->dir_opened(&db->pdb,
+ &db->skip, &db->skip_children,
+ path,
+ db->left_source,
+ db->right_source,
+ NULL /* copyfrom */,
+ pb ? pb->pdb : NULL,
+ eb->processor,
+ db->pool, db->pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb;
+
+ /* ### TODO: support copyfrom? */
+
+ fb = make_file_baton(path, pb, TRUE, pb->pool);
+ *file_baton = fb;
+
+ /* Process Skips. */
+ if (pb->skip_children)
+ {
+ fb->skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ fb->pristine_props = pb->edit_baton->empty_hash;
+
+ fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
+
+ SVN_ERR(eb->processor->file_opened(&fb->pfb,
+ &fb->skip,
+ path,
+ NULL,
+ fb->right_source,
+ NULL /* copy source */,
+ pb->pdb,
+ eb->processor,
+ fb->pool, fb->pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct file_baton *fb;
+ struct edit_baton *eb = pb->edit_baton;
+ fb = make_file_baton(path, pb, FALSE, pb->pool);
+ *file_baton = fb;
+
+ /* Process Skips. */
+ if (pb->skip_children)
+ {
+ fb->skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ fb->base_revision = base_revision;
+
+ fb->left_source = svn_diff__source_create(eb->revision, fb->pool);
+ fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool);
+
+ SVN_ERR(eb->processor->file_opened(&fb->pfb,
+ &fb->skip,
+ path,
+ fb->left_source,
+ fb->right_source,
+ NULL /* copy source */,
+ pb->pdb,
+ eb->processor,
+ fb->pool, fb->pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Do the work of applying the text delta. */
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window,
+ void *window_baton)
+{
+ struct file_baton *fb = window_baton;
+
+ SVN_ERR(fb->apply_handler(window, fb->apply_baton));
+
+ if (!window)
+ {
+ fb->result_md5_checksum = svn_checksum__from_digest_md5(
+ fb->result_digest,
+ fb->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_stream_lazyopen_func_t. */
+static svn_error_t *
+lazy_open_source(svn_stream_t **stream,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct file_baton *fb = baton;
+
+ SVN_ERR(svn_stream_open_readonly(stream, fb->path_start_revision,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_stream_lazyopen_func_t. */
+static svn_error_t *
+lazy_open_result(svn_stream_t **stream,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct file_baton *fb = baton;
+
+ SVN_ERR(svn_stream_open_unique(stream, &fb->path_end_revision, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_md5_digest,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+ svn_stream_t *src_stream;
+ svn_stream_t *result_stream;
+ apr_pool_t *scratch_pool = fb->pool;
+
+ /* Skip *everything* within a newly tree-conflicted directory. */
+ if (fb->skip)
+ {
+ *handler = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* If we're not sending file text, then ignore any that we receive. */
+ if (! fb->edit_baton->text_deltas)
+ {
+ /* Supply valid paths to indicate there is a text change. */
+ SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_start_revision));
+ SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_end_revision));
+
+ *handler = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+
+ return SVN_NO_ERROR;
+ }
+
+ /* We need the expected pristine file, so go get it */
+ if (!fb->added)
+ SVN_ERR(get_file_from_ra(fb, FALSE, scratch_pool));
+ else
+ SVN_ERR(get_empty_file(fb->edit_baton, &(fb->path_start_revision)));
+
+ SVN_ERR_ASSERT(fb->path_start_revision != NULL);
+
+ if (base_md5_digest != NULL)
+ {
+ svn_checksum_t *base_md5_checksum;
+
+ SVN_ERR(svn_checksum_parse_hex(&base_md5_checksum, svn_checksum_md5,
+ base_md5_digest, scratch_pool));
+
+ if (!svn_checksum_match(base_md5_checksum, fb->start_md5_checksum))
+ return svn_error_trace(svn_checksum_mismatch_err(
+ base_md5_checksum,
+ fb->start_md5_checksum,
+ scratch_pool,
+ _("Base checksum mismatch for '%s'"),
+ fb->path));
+ }
+
+ /* Open the file to be used as the base for second revision */
+ src_stream = svn_stream_lazyopen_create(lazy_open_source, fb, TRUE,
+ scratch_pool);
+
+ /* Open the file that will become the second revision after applying the
+ text delta, it starts empty */
+ result_stream = svn_stream_lazyopen_create(lazy_open_result, fb, TRUE,
+ scratch_pool);
+
+ svn_txdelta_apply(src_stream,
+ result_stream,
+ fb->result_digest,
+ fb->path, fb->pool,
+ &(fb->apply_handler), &(fb->apply_baton));
+
+ *handler = window_handler;
+ *handler_baton = file_baton;
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. When the file is closed we have a temporary
+ * file containing a pristine version of the repository file. This can
+ * be compared against the working copy.
+ *
+ * ### Ignore TEXT_CHECKSUM for now. Someday we can use it to verify
+ * ### the integrity of the file being diffed. Done efficiently, this
+ * ### would probably involve calculating the checksum as the data is
+ * ### received, storing the final checksum in the file_baton, and
+ * ### comparing against it here.
+ */
+static svn_error_t *
+close_file(void *file_baton,
+ const char *expected_md5_digest,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct dir_baton *pb = fb->parent_baton;
+ struct edit_baton *eb = fb->edit_baton;
+ apr_pool_t *scratch_pool;
+
+ /* Skip *everything* within a newly tree-conflicted directory. */
+ if (fb->skip)
+ {
+ svn_pool_destroy(fb->pool);
+ SVN_ERR(release_dir(pb));
+ return SVN_NO_ERROR;
+ }
+
+ scratch_pool = fb->pool;
+
+ if (expected_md5_digest && eb->text_deltas)
+ {
+ svn_checksum_t *expected_md5_checksum;
+
+ SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
+ expected_md5_digest, scratch_pool));
+
+ if (!svn_checksum_match(expected_md5_checksum, fb->result_md5_checksum))
+ return svn_error_trace(svn_checksum_mismatch_err(
+ expected_md5_checksum,
+ fb->result_md5_checksum,
+ pool,
+ _("Checksum mismatch for '%s'"),
+ fb->path));
+ }
+
+ if (fb->added || fb->path_end_revision || fb->has_propchange)
+ {
+ apr_hash_t *right_props;
+
+ if (!fb->added && !fb->pristine_props)
+ {
+ /* We didn't receive a text change, so we have no pristine props.
+ Retrieve just the props now. */
+ SVN_ERR(get_file_from_ra(fb, TRUE, scratch_pool));
+ }
+
+ if (fb->pristine_props)
+ remove_non_prop_changes(fb->pristine_props, fb->propchanges);
+
+ right_props = svn_prop__patch(fb->pristine_props, fb->propchanges,
+ fb->pool);
+
+ if (fb->added)
+ SVN_ERR(eb->processor->file_added(fb->path,
+ NULL /* copyfrom_src */,
+ fb->right_source,
+ NULL /* copyfrom_file */,
+ fb->path_end_revision,
+ NULL /* copyfrom_props */,
+ right_props,
+ fb->pfb,
+ eb->processor,
+ fb->pool));
+ else
+ SVN_ERR(eb->processor->file_changed(fb->path,
+ fb->left_source,
+ fb->right_source,
+ fb->path_end_revision
+ ? fb->path_start_revision
+ : NULL,
+ fb->path_end_revision,
+ fb->pristine_props,
+ right_props,
+ (fb->path_end_revision != NULL),
+ fb->propchanges,
+ fb->pfb,
+ eb->processor,
+ fb->pool));
+ }
+
+ svn_pool_destroy(fb->pool); /* Destroy file and scratch pool */
+
+ SVN_ERR(release_dir(pb));
+
+ return SVN_NO_ERROR;
+}
+
+/* Report any accumulated prop changes via the 'dir_props_changed' callback,
+ * and then call the 'dir_closed' callback. Notify about any deleted paths
+ * within this directory that have not already been notified, and then about
+ * this directory itself (unless it was added, in which case the notification
+ * was done at that time).
+ *
+ * An svn_delta_editor_t function. */
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+ apr_pool_t *scratch_pool;
+ apr_hash_t *pristine_props;
+ svn_boolean_t send_changed = FALSE;
+
+ scratch_pool = db->pool;
+
+ if ((db->has_propchange || db->added) && !db->skip)
+ {
+ if (db->added)
+ {
+ pristine_props = eb->empty_hash;
+ }
+ else
+ {
+ SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, &pristine_props,
+ db->path, db->base_revision, 0, scratch_pool));
+ }
+
+ if (db->propchanges->nelts > 0)
+ {
+ remove_non_prop_changes(pristine_props, db->propchanges);
+ }
+
+ if (db->propchanges->nelts > 0 || db->added)
+ {
+ apr_hash_t *right_props;
+
+ right_props = svn_prop__patch(pristine_props, db->propchanges,
+ scratch_pool);
+
+ if (db->added)
+ {
+ SVN_ERR(eb->processor->dir_added(db->path,
+ NULL /* copyfrom */,
+ db->right_source,
+ NULL /* copyfrom props */,
+ right_props,
+ db->pdb,
+ eb->processor,
+ db->pool));
+ }
+ else
+ {
+ SVN_ERR(eb->processor->dir_changed(db->path,
+ db->left_source,
+ db->right_source,
+ pristine_props,
+ right_props,
+ db->propchanges,
+ db->pdb,
+ eb->processor,
+ db->pool));
+ }
+
+ send_changed = TRUE; /* Skip dir_closed */
+ }
+ }
+
+ if (! db->skip && !send_changed)
+ {
+ SVN_ERR(eb->processor->dir_closed(db->path,
+ db->left_source,
+ db->right_source,
+ db->pdb,
+ eb->processor,
+ db->pool));
+ }
+ SVN_ERR(release_dir(db));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Record a prop change, which we will report later in close_file().
+ *
+ * An svn_delta_editor_t function. */
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ svn_prop_t *propchange;
+ svn_prop_kind_t propkind;
+
+ /* Skip *everything* within a newly tree-conflicted directory. */
+ if (fb->skip)
+ return SVN_NO_ERROR;
+
+ propkind = svn_property_kind2(name);
+ if (propkind == svn_prop_wc_kind)
+ return SVN_NO_ERROR;
+ else if (propkind == svn_prop_regular_kind)
+ fb->has_propchange = TRUE;
+
+ propchange = apr_array_push(fb->propchanges);
+ propchange->name = apr_pstrdup(fb->pool, name);
+ propchange->value = value ? svn_string_dup(value, fb->pool) : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* Make a note of this prop change, to be reported when the dir is closed.
+ *
+ * An svn_delta_editor_t function. */
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ svn_prop_t *propchange;
+ svn_prop_kind_t propkind;
+
+ /* Skip *everything* within a newly tree-conflicted directory. */
+ if (db->skip)
+ return SVN_NO_ERROR;
+
+ propkind = svn_property_kind2(name);
+ if (propkind == svn_prop_wc_kind)
+ return SVN_NO_ERROR;
+ else if (propkind == svn_prop_regular_kind)
+ db->has_propchange = TRUE;
+
+ propchange = apr_array_push(db->propchanges);
+ propchange->name = apr_pstrdup(db->pool, name);
+ propchange->value = value ? svn_string_dup(value, db->pool) : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ svn_pool_destroy(eb->pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Notify that the node at PATH is 'missing'.
+ * An svn_delta_editor_t function. */
+static svn_error_t *
+absent_directory(const char *path,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+
+ SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Notify that the node at PATH is 'missing'.
+ * An svn_delta_editor_t function. */
+static svn_error_t *
+absent_file(const char *path,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+
+ SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->revision;
+
+ SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_node_kind_t node_kind;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->revision;
+
+ SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind,
+ scratch_pool));
+
+ if (node_kind == svn_node_file)
+ {
+ SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision,
+ NULL, NULL, props, result_pool));
+ }
+ else if (node_kind == svn_node_dir)
+ {
+ apr_array_header_t *tmp_props;
+
+ SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path,
+ base_revision, 0 /* Dirent fields */,
+ result_pool));
+ tmp_props = svn_prop_hash_to_array(*props, result_pool);
+ SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
+ result_pool));
+ *props = svn_prop_array_to_hash(tmp_props, result_pool);
+ }
+ else
+ {
+ *props = apr_hash_make(result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_stream_t *fstream;
+ svn_error_t *err;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->revision;
+
+ SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+
+ err = svn_ra_get_file(eb->ra_session, path, base_revision,
+ fstream, NULL, NULL, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ SVN_ERR(svn_stream_close(fstream));
+
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ SVN_ERR(svn_stream_close(fstream));
+
+ return SVN_NO_ERROR;
+}
+
+/* Create a repository diff editor and baton. */
+svn_error_t *
+svn_client__get_diff_editor2(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_ra_session_t *ra_session,
+ svn_depth_t depth,
+ svn_revnum_t revision,
+ svn_boolean_t text_deltas,
+ const svn_diff_tree_processor_t *processor,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool)
+{
+ apr_pool_t *editor_pool = svn_pool_create(result_pool);
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(editor_pool);
+ struct edit_baton *eb = apr_pcalloc(editor_pool, sizeof(*eb));
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(editor_pool);
+
+ eb->pool = editor_pool;
+ eb->depth = depth;
+
+ eb->processor = processor;
+
+ eb->ra_session = ra_session;
+
+ eb->revision = revision;
+ eb->empty_file = NULL;
+ eb->empty_hash = apr_hash_make(eb->pool);
+ eb->text_deltas = text_deltas;
+ eb->cancel_func = cancel_func;
+ eb->cancel_baton = cancel_baton;
+
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->open_root = open_root;
+ tree_editor->delete_entry = delete_entry;
+ tree_editor->add_directory = add_directory;
+ tree_editor->open_directory = open_directory;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->close_file = close_file;
+ tree_editor->close_directory = close_directory;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->change_dir_prop = change_dir_prop;
+ tree_editor->close_edit = close_edit;
+ tree_editor->absent_directory = absent_directory;
+ tree_editor->absent_file = absent_file;
+
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
+ tree_editor, eb,
+ editor, edit_baton,
+ eb->pool));
+
+ shim_callbacks->fetch_kind_func = fetch_kind_func;
+ shim_callbacks->fetch_props_func = fetch_props_func;
+ shim_callbacks->fetch_base_func = fetch_base_func;
+ shim_callbacks->fetch_baton = eb;
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ NULL, NULL, shim_callbacks,
+ result_pool, result_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/resolved.c b/contrib/subversion/subversion/libsvn_client/resolved.c
new file mode 100644
index 0000000..0496371
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/resolved.c
@@ -0,0 +1,148 @@
+/*
+ * resolved.c: wrapper around wc resolved functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <stdlib.h>
+
+#include "svn_types.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_hash.h"
+#include "svn_sorts.h"
+#include "client.h"
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+/*** Code. ***/
+
+svn_error_t *
+svn_client__resolve_conflicts(svn_boolean_t *conflicts_remain,
+ apr_hash_t *conflicted_paths,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *array;
+ int i;
+
+ if (conflicts_remain)
+ *conflicts_remain = FALSE;
+
+ SVN_ERR(svn_hash_keys(&array, conflicted_paths, scratch_pool));
+ qsort(array->elts, array->nelts, array->elt_size,
+ svn_sort_compare_paths);
+
+ for (i = 0; i < array->nelts; i++)
+ {
+ const char *local_abspath = APR_ARRAY_IDX(array, i, const char *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_wc__resolve_conflicts(ctx->wc_ctx, local_abspath,
+ svn_depth_empty,
+ TRUE /* resolve_text */,
+ "" /* resolve_prop (ALL props) */,
+ TRUE /* resolve_tree */,
+ svn_wc_conflict_choose_unspecified,
+ ctx->conflict_func2,
+ ctx->conflict_baton2,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ iterpool));
+
+ if (conflicts_remain)
+ {
+ svn_error_t *err;
+ svn_boolean_t text_c, prop_c, tree_c;
+
+ err = svn_wc_conflicted_p3(&text_c, &prop_c, &tree_c,
+ ctx->wc_ctx, local_abspath,
+ iterpool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ text_c = prop_c = tree_c = FALSE;
+ }
+ else
+ {
+ SVN_ERR(err);
+ }
+ if (text_c || prop_c || tree_c)
+ *conflicts_remain = TRUE;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_resolve(const char *path,
+ svn_depth_t depth,
+ svn_wc_conflict_choice_t conflict_choice,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_error_t *err;
+ const char *lock_abspath;
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ /* Similar to SVN_WC__CALL_WITH_WRITE_LOCK but using a custom
+ locking function. */
+
+ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
+ local_abspath, pool, pool));
+ err = svn_wc__resolve_conflicts(ctx->wc_ctx, local_abspath,
+ depth,
+ TRUE /* resolve_text */,
+ "" /* resolve_prop (ALL props) */,
+ TRUE /* resolve_tree */,
+ conflict_choice,
+ ctx->conflict_func2,
+ ctx->conflict_baton2,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool);
+
+ err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
+ lock_abspath,
+ pool));
+ svn_io_sleep_for_timestamps(path, pool);
+
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/revert.c b/contrib/subversion/subversion/libsvn_client/revert.c
new file mode 100644
index 0000000..681e39c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/revert.c
@@ -0,0 +1,201 @@
+/*
+ * revert.c: wrapper around wc revert functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_path.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_time.h"
+#include "svn_config.h"
+#include "client.h"
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+struct revert_with_write_lock_baton {
+ const char *local_abspath;
+ svn_depth_t depth;
+ svn_boolean_t use_commit_times;
+ const apr_array_header_t *changelists;
+ svn_client_ctx_t *ctx;
+};
+
+/* (Note: All arguments are in the baton above.)
+
+ Attempt to revert LOCAL_ABSPATH.
+
+ If DEPTH is svn_depth_empty, revert just the properties on the
+ directory; else if svn_depth_files, revert the properties and any
+ files immediately under the directory; else if
+ svn_depth_immediates, revert all of the preceding plus properties
+ on immediate subdirectories; else if svn_depth_infinity, revert
+ path and everything under it fully recursively.
+
+ CHANGELISTS is an array of const char * changelist names, used as a
+ restrictive filter on items reverted; that is, don't revert any
+ item unless it's a member of one of those changelists. If
+ CHANGELISTS is empty (or altogether NULL), no changelist filtering occurs.
+
+ Consult CTX to determine whether or not to revert timestamp to the
+ time of last commit ('use-commit-times = yes').
+
+ If PATH is unversioned, return SVN_ERR_UNVERSIONED_RESOURCE. */
+static svn_error_t *
+revert(void *baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ struct revert_with_write_lock_baton *b = baton;
+ svn_error_t *err;
+
+ err = svn_wc_revert4(b->ctx->wc_ctx,
+ b->local_abspath,
+ b->depth,
+ b->use_commit_times,
+ b->changelists,
+ b->ctx->cancel_func, b->ctx->cancel_baton,
+ b->ctx->notify_func2, b->ctx->notify_baton2,
+ scratch_pool);
+
+ if (err)
+ {
+ /* If target isn't versioned, just send a 'skip'
+ notification and move on. */
+ if (err->apr_err == SVN_ERR_ENTRY_NOT_FOUND
+ || err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE
+ || err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ if (b->ctx->notify_func2)
+ (*b->ctx->notify_func2)(
+ b->ctx->notify_baton2,
+ svn_wc_create_notify(b->local_abspath, svn_wc_notify_skip,
+ scratch_pool),
+ scratch_pool);
+ svn_error_clear(err);
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_client_revert2(const apr_array_header_t *paths,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool;
+ svn_error_t *err = SVN_NO_ERROR;
+ int i;
+ svn_config_t *cfg;
+ svn_boolean_t use_commit_times;
+ struct revert_with_write_lock_baton baton;
+
+ /* Don't even attempt to modify the working copy if any of the
+ * targets look like URLs. URLs are invalid input. */
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+ }
+
+ cfg = ctx->config
+ ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+
+ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_USE_COMMIT_TIMES,
+ FALSE));
+
+ subpool = svn_pool_create(pool);
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *local_abspath, *lock_target;
+ svn_boolean_t wc_root;
+
+ svn_pool_clear(subpool);
+
+ /* See if we've been asked to cancel this operation. */
+ if ((ctx->cancel_func)
+ && ((err = ctx->cancel_func(ctx->cancel_baton))))
+ goto errorful;
+
+ err = svn_dirent_get_absolute(&local_abspath, path, pool);
+ if (err)
+ goto errorful;
+
+ baton.local_abspath = local_abspath;
+ baton.depth = depth;
+ baton.use_commit_times = use_commit_times;
+ baton.changelists = changelists;
+ baton.ctx = ctx;
+
+ err = svn_wc__is_wcroot(&wc_root, ctx->wc_ctx, local_abspath, pool);
+ if (err)
+ goto errorful;
+ lock_target = wc_root ? local_abspath
+ : svn_dirent_dirname(local_abspath, pool);
+ err = svn_wc__call_with_write_lock(revert, &baton, ctx->wc_ctx,
+ lock_target, FALSE, pool, pool);
+ if (err)
+ goto errorful;
+ }
+
+ errorful:
+
+ {
+ /* Sleep to ensure timestamp integrity. */
+ const char *sleep_path = NULL;
+
+ /* Only specify a path if we are certain all paths are on the
+ same filesystem */
+ if (paths->nelts == 1)
+ sleep_path = APR_ARRAY_IDX(paths, 0, const char *);
+
+ svn_io_sleep_for_timestamps(sleep_path, subpool);
+ }
+
+ svn_pool_destroy(subpool);
+
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/revisions.c b/contrib/subversion/subversion/libsvn_client/revisions.c
new file mode 100644
index 0000000..ec255c1
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/revisions.c
@@ -0,0 +1,191 @@
+/*
+ * revisions.c: discovering revisions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_pools.h>
+
+#include "svn_error.h"
+#include "svn_ra.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "client.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+
+svn_error_t *
+svn_client__get_revision_number(svn_revnum_t *revnum,
+ svn_revnum_t *youngest_rev,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_ra_session_t *ra_session,
+ const svn_opt_revision_t *revision,
+ apr_pool_t *scratch_pool)
+{
+ switch (revision->kind)
+ {
+ case svn_opt_revision_unspecified:
+ *revnum = SVN_INVALID_REVNUM;
+ break;
+
+ case svn_opt_revision_number:
+ *revnum = revision->value.number;
+ break;
+
+ case svn_opt_revision_head:
+ /* If our caller provided a value for HEAD that he wants us to
+ use, we'll use it. Otherwise, we have to query the
+ repository (and possible return our fetched value in
+ *YOUNGEST_REV, too). */
+ if (youngest_rev && SVN_IS_VALID_REVNUM(*youngest_rev))
+ {
+ *revnum = *youngest_rev;
+ }
+ else
+ {
+ if (! ra_session)
+ return svn_error_create(SVN_ERR_CLIENT_RA_ACCESS_REQUIRED,
+ NULL, NULL);
+ SVN_ERR(svn_ra_get_latest_revnum(ra_session, revnum, scratch_pool));
+ if (youngest_rev)
+ *youngest_rev = *revnum;
+ }
+ break;
+
+ case svn_opt_revision_working:
+ case svn_opt_revision_base:
+ {
+ svn_error_t *err;
+
+ /* Sanity check. */
+ if (local_abspath == NULL)
+ return svn_error_create(SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED,
+ NULL, NULL);
+
+ /* The BASE, COMMITTED, and PREV revision keywords do not
+ apply to URLs. */
+ if (svn_path_is_url(local_abspath))
+ goto invalid_rev_arg;
+
+ err = svn_wc__node_get_origin(NULL, revnum, NULL, NULL, NULL, NULL,
+ wc_ctx, local_abspath, TRUE,
+ scratch_pool, scratch_pool);
+
+ /* Return the same error as older code did (before and at r935091).
+ At least svn_client_proplist4 promises SVN_ERR_ENTRY_NOT_FOUND. */
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ else
+ SVN_ERR(err);
+
+ if (! SVN_IS_VALID_REVNUM(*revnum))
+ return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Path '%s' has no committed "
+ "revision"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ break;
+
+ case svn_opt_revision_committed:
+ case svn_opt_revision_previous:
+ {
+ /* Sanity check. */
+ if (local_abspath == NULL)
+ return svn_error_create(SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED,
+ NULL, NULL);
+
+ /* The BASE, COMMITTED, and PREV revision keywords do not
+ apply to URLs. */
+ if (svn_path_is_url(local_abspath))
+ goto invalid_rev_arg;
+
+ SVN_ERR(svn_wc__node_get_changed_info(revnum, NULL, NULL,
+ wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ if (! SVN_IS_VALID_REVNUM(*revnum))
+ return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Path '%s' has no committed "
+ "revision"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ if (revision->kind == svn_opt_revision_previous)
+ (*revnum)--;
+ }
+ break;
+
+ case svn_opt_revision_date:
+ /* ### When revision->kind == svn_opt_revision_date, is there an
+ ### optimization such that we can compare
+ ### revision->value->date with the committed-date in the
+ ### entries file (or rather, with some range of which
+ ### committed-date is one endpoint), and sometimes avoid a
+ ### trip over the RA layer? The only optimizations I can
+ ### think of involve examining other entries to build a
+ ### timespan across which committed-revision is known to be
+ ### the head, but it doesn't seem worth it. -kff */
+ if (! ra_session)
+ return svn_error_create(SVN_ERR_CLIENT_RA_ACCESS_REQUIRED, NULL, NULL);
+ SVN_ERR(svn_ra_get_dated_revision(ra_session, revnum,
+ revision->value.date, scratch_pool));
+ break;
+
+ default:
+ return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Unrecognized revision type requested for "
+ "'%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ /* Final check -- if our caller provided a youngest revision, and
+ the number we wound up with (after talking to the server) is younger
+ than that revision, we need to stick to our caller's idea of "youngest".
+ */
+ if (youngest_rev
+ && (revision->kind == svn_opt_revision_head
+ || revision->kind == svn_opt_revision_date)
+ && SVN_IS_VALID_REVNUM(*youngest_rev)
+ && SVN_IS_VALID_REVNUM(*revnum)
+ && (*revnum > *youngest_rev))
+ *revnum = *youngest_rev;
+
+ return SVN_NO_ERROR;
+
+ invalid_rev_arg:
+ return svn_error_create(
+ SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("PREV, BASE, or COMMITTED revision keywords are invalid for URL"));
+
+}
diff --git a/contrib/subversion/subversion/libsvn_client/status.c b/contrib/subversion/subversion/libsvn_client/status.c
new file mode 100644
index 0000000..e581d37
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/status.c
@@ -0,0 +1,767 @@
+/*
+ * status.c: return the status of a working copy dirent
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+#include <apr_strings.h>
+#include <apr_pools.h>
+
+#include "svn_pools.h"
+#include "client.h"
+
+#include "svn_path.h"
+#include "svn_dirent_uri.h"
+#include "svn_delta.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_client_private.h"
+
+
+/*** Getting update information ***/
+
+/* Baton for tweak_status. It wraps a bit of extra functionality
+ around the received status func/baton, so we can remember if the
+ target was deleted in HEAD and tweak incoming status structures
+ accordingly. */
+struct status_baton
+{
+ svn_boolean_t deleted_in_repos; /* target is deleted in repos */
+ apr_hash_t *changelist_hash; /* keys are changelist names */
+ svn_client_status_func_t real_status_func; /* real status function */
+ void *real_status_baton; /* real status baton */
+ const char *anchor_abspath; /* Absolute path of anchor */
+ const char *anchor_relpath; /* Relative path of anchor */
+ svn_wc_context_t *wc_ctx; /* A working copy context. */
+};
+
+/* A status callback function which wraps the *real* status
+ function/baton. This sucker takes care of any status tweaks we
+ need to make (such as noting that the target of the status is
+ missing from HEAD in the repository).
+
+ This implements the 'svn_wc_status_func4_t' function type. */
+static svn_error_t *
+tweak_status(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct status_baton *sb = baton;
+ const char *path = local_abspath;
+ svn_client_status_t *cst;
+
+ if (sb->anchor_abspath)
+ path = svn_dirent_join(sb->anchor_relpath,
+ svn_dirent_skip_ancestor(sb->anchor_abspath, path),
+ scratch_pool);
+
+ /* If the status item has an entry, but doesn't belong to one of the
+ changelists our caller is interested in, we filter out this status
+ transmission. */
+ if (sb->changelist_hash
+ && (! status->changelist
+ || ! svn_hash_gets(sb->changelist_hash, status->changelist)))
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* If we know that the target was deleted in HEAD of the repository,
+ we need to note that fact in all the status structures that come
+ through here. */
+ if (sb->deleted_in_repos)
+ {
+ svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
+ new_status->repos_node_status = svn_wc_status_deleted;
+ status = new_status;
+ }
+
+ SVN_ERR(svn_client__create_status(&cst, sb->wc_ctx, local_abspath, status,
+ scratch_pool, scratch_pool));
+
+ /* Call the real status function/baton. */
+ return sb->real_status_func(sb->real_status_baton, path, cst,
+ scratch_pool);
+}
+
+/* A baton for our reporter that is used to collect locks. */
+typedef struct report_baton_t {
+ const svn_ra_reporter3_t* wrapped_reporter;
+ void *wrapped_report_baton;
+ /* The common ancestor URL of all paths included in the report. */
+ char *ancestor;
+ void *set_locks_baton;
+ svn_depth_t depth;
+ svn_client_ctx_t *ctx;
+ /* Pool to store locks in. */
+ apr_pool_t *pool;
+} report_baton_t;
+
+/* Implements svn_ra_reporter3_t->set_path. */
+static svn_error_t *
+reporter_set_path(void *report_baton, const char *path,
+ svn_revnum_t revision, svn_depth_t depth,
+ svn_boolean_t start_empty, const char *lock_token,
+ apr_pool_t *pool)
+{
+ report_baton_t *rb = report_baton;
+
+ return rb->wrapped_reporter->set_path(rb->wrapped_report_baton, path,
+ revision, depth, start_empty,
+ lock_token, pool);
+}
+
+/* Implements svn_ra_reporter3_t->delete_path. */
+static svn_error_t *
+reporter_delete_path(void *report_baton, const char *path, apr_pool_t *pool)
+{
+ report_baton_t *rb = report_baton;
+
+ return rb->wrapped_reporter->delete_path(rb->wrapped_report_baton, path,
+ pool);
+}
+
+/* Implements svn_ra_reporter3_t->link_path. */
+static svn_error_t *
+reporter_link_path(void *report_baton, const char *path, const char *url,
+ svn_revnum_t revision, svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token, apr_pool_t *pool)
+{
+ report_baton_t *rb = report_baton;
+
+ if (!svn_uri__is_ancestor(rb->ancestor, url))
+ {
+ const char *ancestor;
+
+ ancestor = svn_uri_get_longest_ancestor(url, rb->ancestor, pool);
+
+ /* If we got a shorter ancestor, truncate our current ancestor.
+ Note that svn_uri_get_longest_ancestor will allocate its return
+ value even if it identical to one of its arguments. */
+
+ rb->ancestor[strlen(ancestor)] = '\0';
+ rb->depth = svn_depth_infinity;
+ }
+
+ return rb->wrapped_reporter->link_path(rb->wrapped_report_baton, path, url,
+ revision, depth, start_empty,
+ lock_token, pool);
+}
+
+/* Implements svn_ra_reporter3_t->finish_report. */
+static svn_error_t *
+reporter_finish_report(void *report_baton, apr_pool_t *pool)
+{
+ report_baton_t *rb = report_baton;
+ svn_ra_session_t *ras;
+ apr_hash_t *locks;
+ const char *repos_root;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_error_t *err = SVN_NO_ERROR;
+
+ /* Open an RA session to our common ancestor and grab the locks under it.
+ */
+ SVN_ERR(svn_client_open_ra_session2(&ras, rb->ancestor, NULL,
+ rb->ctx, subpool, subpool));
+
+ /* The locks need to live throughout the edit. Note that if the
+ server doesn't support lock discovery, we'll just not do locky
+ stuff. */
+ err = svn_ra_get_locks2(ras, &locks, "", rb->depth, rb->pool);
+ if (err && ((err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)
+ || (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ locks = apr_hash_make(rb->pool);
+ }
+ SVN_ERR(err);
+
+ SVN_ERR(svn_ra_get_repos_root2(ras, &repos_root, rb->pool));
+
+ /* Close the RA session. */
+ svn_pool_destroy(subpool);
+
+ SVN_ERR(svn_wc_status_set_repos_locks(rb->set_locks_baton, locks,
+ repos_root, rb->pool));
+
+ return rb->wrapped_reporter->finish_report(rb->wrapped_report_baton, pool);
+}
+
+/* Implements svn_ra_reporter3_t->abort_report. */
+static svn_error_t *
+reporter_abort_report(void *report_baton, apr_pool_t *pool)
+{
+ report_baton_t *rb = report_baton;
+
+ return rb->wrapped_reporter->abort_report(rb->wrapped_report_baton, pool);
+}
+
+/* A reporter that keeps track of the common URL ancestor of all paths in
+ the WC and fetches repository locks for all paths under this ancestor. */
+static svn_ra_reporter3_t lock_fetch_reporter = {
+ reporter_set_path,
+ reporter_delete_path,
+ reporter_link_path,
+ reporter_finish_report,
+ reporter_abort_report
+};
+
+/* Perform status operations on each external in EXTERNAL_MAP, a const char *
+ local_abspath of all externals mapping to the const char* defining_abspath.
+ All other options are the same as those passed to svn_client_status().
+
+ If ANCHOR_ABSPATH and ANCHOR-RELPATH are not null, use them to provide
+ properly formatted relative paths */
+static svn_error_t *
+do_external_status(svn_client_ctx_t *ctx,
+ apr_hash_t *external_map,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t update,
+ svn_boolean_t no_ignore,
+ const char *anchor_abspath,
+ const char *anchor_relpath,
+ svn_client_status_func_t status_func,
+ void *status_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Loop over the hash of new values (we don't care about the old
+ ones). This is a mapping of versioned directories to property
+ values. */
+ for (hi = apr_hash_first(scratch_pool, external_map);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_node_kind_t external_kind;
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+ const char *defining_abspath = svn__apr_hash_index_val(hi);
+ svn_node_kind_t kind;
+ svn_opt_revision_t opt_rev;
+ const char *status_path;
+
+ svn_pool_clear(iterpool);
+
+ /* Obtain information on the expected external. */
+ SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL,
+ &opt_rev.value.number,
+ ctx->wc_ctx, defining_abspath,
+ local_abspath, FALSE,
+ iterpool, iterpool));
+
+ if (external_kind != svn_node_dir)
+ continue;
+
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, iterpool));
+ if (kind != svn_node_dir)
+ continue;
+
+ if (SVN_IS_VALID_REVNUM(opt_rev.value.number))
+ opt_rev.kind = svn_opt_revision_number;
+ else
+ opt_rev.kind = svn_opt_revision_unspecified;
+
+ /* Tell the client we're starting an external status set. */
+ if (ctx->notify_func2)
+ ctx->notify_func2(
+ ctx->notify_baton2,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_status_external,
+ iterpool), iterpool);
+
+ status_path = local_abspath;
+ if (anchor_abspath)
+ {
+ status_path = svn_dirent_join(anchor_relpath,
+ svn_dirent_skip_ancestor(anchor_abspath,
+ status_path),
+ iterpool);
+ }
+
+ /* And then do the status. */
+ SVN_ERR(svn_client_status5(NULL, ctx, status_path, &opt_rev, depth,
+ get_all, update, no_ignore, FALSE, FALSE,
+ NULL, status_func, status_baton,
+ iterpool));
+ }
+
+ /* Destroy SUBPOOL and (implicitly) ITERPOOL. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/*** Public Interface. ***/
+
+
+svn_error_t *
+svn_client_status5(svn_revnum_t *result_rev,
+ svn_client_ctx_t *ctx,
+ const char *path,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t update,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t depth_as_sticky,
+ const apr_array_header_t *changelists,
+ svn_client_status_func_t status_func,
+ void *status_baton,
+ apr_pool_t *pool) /* ### aka scratch_pool */
+{
+ struct status_baton sb;
+ const char *dir, *dir_abspath;
+ const char *target_abspath;
+ const char *target_basename;
+ apr_array_header_t *ignores;
+ svn_error_t *err;
+ apr_hash_t *changelist_hash = NULL;
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+
+ if (changelists && changelists->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool));
+
+ if (result_rev)
+ *result_rev = SVN_INVALID_REVNUM;
+
+ sb.real_status_func = status_func;
+ sb.real_status_baton = status_baton;
+ sb.deleted_in_repos = FALSE;
+ sb.changelist_hash = changelist_hash;
+ sb.wc_ctx = ctx->wc_ctx;
+
+ SVN_ERR(svn_dirent_get_absolute(&target_abspath, path, pool));
+
+ if (update)
+ {
+ /* The status editor only works on directories, so get the ancestor
+ if necessary */
+
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath,
+ TRUE, FALSE, pool));
+
+ /* Dir must be a working copy directory or the status editor fails */
+ if (kind == svn_node_dir)
+ {
+ dir_abspath = target_abspath;
+ target_basename = "";
+ dir = path;
+ }
+ else
+ {
+ dir_abspath = svn_dirent_dirname(target_abspath, pool);
+ target_basename = svn_dirent_basename(target_abspath, NULL);
+ dir = svn_dirent_dirname(path, pool);
+
+ if (kind == svn_node_file)
+ {
+ if (depth == svn_depth_empty)
+ depth = svn_depth_files;
+ }
+ else
+ {
+ err = svn_wc_read_kind2(&kind, ctx->wc_ctx, dir_abspath,
+ FALSE, FALSE, pool);
+
+ svn_error_clear(err);
+
+ if (err || kind != svn_node_dir)
+ {
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(path, pool));
+ }
+ }
+ }
+ }
+ else
+ {
+ dir = path;
+ dir_abspath = target_abspath;
+ }
+
+ if (svn_dirent_is_absolute(dir))
+ {
+ sb.anchor_abspath = NULL;
+ sb.anchor_relpath = NULL;
+ }
+ else
+ {
+ sb.anchor_abspath = dir_abspath;
+ sb.anchor_relpath = dir;
+ }
+
+ /* Get the status edit, and use our wrapping status function/baton
+ as the callback pair. */
+ SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool));
+
+ /* If we want to know about out-of-dateness, we crawl the working copy and
+ let the RA layer drive the editor for real. Otherwise, we just close the
+ edit. :-) */
+ if (update)
+ {
+ svn_ra_session_t *ra_session;
+ const char *URL;
+ svn_node_kind_t kind;
+ svn_boolean_t server_supports_depth;
+ const svn_delta_editor_t *editor;
+ void *edit_baton, *set_locks_baton;
+ svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
+
+ /* Get full URL from the ANCHOR. */
+ SVN_ERR(svn_client_url_from_path2(&URL, dir_abspath, ctx,
+ pool, pool));
+
+ if (!URL)
+ return svn_error_createf
+ (SVN_ERR_ENTRY_MISSING_URL, NULL,
+ _("Entry '%s' has no URL"),
+ svn_dirent_local_style(dir, pool));
+
+ /* Open a repository session to the URL. */
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL,
+ dir_abspath, NULL,
+ FALSE, TRUE,
+ ctx, pool, pool));
+
+ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, pool));
+
+ SVN_ERR(svn_wc__get_status_editor(&editor, &edit_baton, &set_locks_baton,
+ &edit_revision, ctx->wc_ctx,
+ dir_abspath, target_basename,
+ depth, get_all,
+ no_ignore, depth_as_sticky,
+ server_supports_depth,
+ ignores, tweak_status, &sb,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool, pool));
+
+
+ /* Verify that URL exists in HEAD. If it doesn't, this can save
+ us a whole lot of hassle; if it does, the cost of this
+ request should be minimal compared to the size of getting
+ back the average amount of "out-of-date" information. */
+ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM,
+ &kind, pool));
+ if (kind == svn_node_none)
+ {
+ svn_boolean_t added;
+
+ /* Our status target does not exist in HEAD. If we've got
+ it locally added, that's okay. But if it was previously
+ versioned, then it must have since been deleted from the
+ repository. (Note that "locally replaced" doesn't count
+ as "added" in this case.) */
+ SVN_ERR(svn_wc__node_is_added(&added, ctx->wc_ctx,
+ dir_abspath, pool));
+ if (! added)
+ sb.deleted_in_repos = TRUE;
+
+ /* And now close the edit. */
+ SVN_ERR(editor->close_edit(edit_baton, pool));
+ }
+ else
+ {
+ svn_revnum_t revnum;
+ report_baton_t rb;
+ svn_depth_t status_depth;
+
+ if (revision->kind == svn_opt_revision_head)
+ {
+ /* Cause the revision number to be omitted from the request,
+ which implies HEAD. */
+ revnum = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ /* Get a revision number for our status operation. */
+ SVN_ERR(svn_client__get_revision_number(&revnum, NULL,
+ ctx->wc_ctx,
+ target_abspath,
+ ra_session, revision,
+ pool));
+ }
+
+ if (depth_as_sticky || !server_supports_depth)
+ status_depth = depth;
+ else
+ status_depth = svn_depth_unknown; /* Use depth from WC */
+
+ /* Do the deed. Let the RA layer drive the status editor. */
+ SVN_ERR(svn_ra_do_status2(ra_session, &rb.wrapped_reporter,
+ &rb.wrapped_report_baton,
+ target_basename, revnum, status_depth,
+ editor, edit_baton, pool));
+
+ /* Init the report baton. */
+ rb.ancestor = apr_pstrdup(pool, URL); /* Edited later */
+ rb.set_locks_baton = set_locks_baton;
+ rb.ctx = ctx;
+ rb.pool = pool;
+
+ if (depth == svn_depth_unknown)
+ rb.depth = svn_depth_infinity;
+ else
+ rb.depth = depth;
+
+ /* Drive the reporter structure, describing the revisions
+ within PATH. When we call reporter->finish_report,
+ EDITOR will be driven to describe differences between our
+ working copy and HEAD. */
+ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx,
+ target_abspath,
+ &lock_fetch_reporter, &rb,
+ FALSE /* restore_files */,
+ depth, (! depth_as_sticky),
+ (! server_supports_depth),
+ FALSE /* use_commit_times */,
+ ctx->cancel_func, ctx->cancel_baton,
+ NULL, NULL, pool));
+ }
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(target_abspath,
+ svn_wc_notify_status_completed, pool);
+ notify->revision = edit_revision;
+ (ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+
+ /* If the caller wants the result revision, give it to them. */
+ if (result_rev)
+ *result_rev = edit_revision;
+ }
+ else
+ {
+ err = svn_wc_walk_status(ctx->wc_ctx, target_abspath,
+ depth, get_all, no_ignore, FALSE, ignores,
+ tweak_status, &sb,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_MISSING)
+ {
+ /* This error code is checked for in svn to continue after
+ this error */
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(path, pool));
+ }
+
+ SVN_ERR(err);
+ }
+
+ /* If there are svn:externals set, we don't want those to show up as
+ unversioned or unrecognized, so patch up the hash. If caller wants
+ all the statuses, we will change unversioned status items that
+ are interesting to an svn:externals property to
+ svn_wc_status_unversioned, otherwise we'll just remove the status
+ item altogether.
+
+ We only descend into an external if depth is svn_depth_infinity or
+ svn_depth_unknown. However, there are conceivable behaviors that
+ would involve descending under other circumstances; thus, we pass
+ depth anyway, so the code will DTRT if we change the conditional
+ in the future.
+ */
+ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals))
+ {
+ apr_hash_t *external_map;
+ SVN_ERR(svn_wc__externals_defined_below(&external_map,
+ ctx->wc_ctx, target_abspath,
+ pool, pool));
+
+
+ SVN_ERR(do_external_status(ctx, external_map,
+ depth, get_all,
+ update, no_ignore,
+ sb.anchor_abspath, sb.anchor_relpath,
+ status_func, status_baton, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_client_status_t *
+svn_client_status_dup(const svn_client_status_t *status,
+ apr_pool_t *result_pool)
+{
+ svn_client_status_t *st = apr_palloc(result_pool, sizeof(*st));
+
+ *st = *status;
+
+ if (status->local_abspath)
+ st->local_abspath = apr_pstrdup(result_pool, status->local_abspath);
+
+ if (status->repos_root_url)
+ st->repos_root_url = apr_pstrdup(result_pool, status->repos_root_url);
+
+ if (status->repos_uuid)
+ st->repos_uuid = apr_pstrdup(result_pool, status->repos_uuid);
+
+ if (status->repos_relpath)
+ st->repos_relpath = apr_pstrdup(result_pool, status->repos_relpath);
+
+ if (status->changed_author)
+ st->changed_author = apr_pstrdup(result_pool, status->changed_author);
+
+ if (status->lock)
+ st->lock = svn_lock_dup(status->lock, result_pool);
+
+ if (status->changelist)
+ st->changelist = apr_pstrdup(result_pool, status->changelist);
+
+ if (status->ood_changed_author)
+ st->ood_changed_author = apr_pstrdup(result_pool, status->ood_changed_author);
+
+ if (status->repos_lock)
+ st->repos_lock = svn_lock_dup(status->repos_lock, result_pool);
+
+ if (status->backwards_compatibility_baton)
+ {
+ const svn_wc_status3_t *wc_st = status->backwards_compatibility_baton;
+
+ st->backwards_compatibility_baton = svn_wc_dup_status3(wc_st,
+ result_pool);
+ }
+
+ if (status->moved_from_abspath)
+ st->moved_from_abspath =
+ apr_pstrdup(result_pool, status->moved_from_abspath);
+
+ if (status->moved_to_abspath)
+ st->moved_to_abspath = apr_pstrdup(result_pool, status->moved_to_abspath);
+
+ return st;
+}
+
+svn_error_t *
+svn_client__create_status(svn_client_status_t **cst,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *cst = apr_pcalloc(result_pool, sizeof(**cst));
+
+ (*cst)->kind = status->kind;
+ (*cst)->local_abspath = local_abspath;
+ (*cst)->filesize = status->filesize;
+ (*cst)->versioned = status->versioned;
+
+ (*cst)->conflicted = status->conflicted;
+
+ (*cst)->node_status = status->node_status;
+ (*cst)->text_status = status->text_status;
+ (*cst)->prop_status = status->prop_status;
+
+ if (status->kind == svn_node_dir)
+ (*cst)->wc_is_locked = status->locked;
+
+ (*cst)->copied = status->copied;
+ (*cst)->revision = status->revision;
+
+ (*cst)->changed_rev = status->changed_rev;
+ (*cst)->changed_date = status->changed_date;
+ (*cst)->changed_author = status->changed_author;
+
+ (*cst)->repos_root_url = status->repos_root_url;
+ (*cst)->repos_uuid = status->repos_uuid;
+ (*cst)->repos_relpath = status->repos_relpath;
+
+ (*cst)->switched = status->switched;
+
+ (*cst)->file_external = status->file_external;
+ if (status->file_external)
+ {
+ (*cst)->switched = FALSE;
+ }
+
+ (*cst)->lock = status->lock;
+
+ (*cst)->changelist = status->changelist;
+ (*cst)->depth = status->depth;
+
+ /* Out of date information */
+ (*cst)->ood_kind = status->ood_kind;
+ (*cst)->repos_node_status = status->repos_node_status;
+ (*cst)->repos_text_status = status->repos_text_status;
+ (*cst)->repos_prop_status = status->repos_prop_status;
+ (*cst)->repos_lock = status->repos_lock;
+
+ (*cst)->ood_changed_rev = status->ood_changed_rev;
+ (*cst)->ood_changed_date = status->ood_changed_date;
+ (*cst)->ood_changed_author = status->ood_changed_author;
+
+ /* When changing the value of backwards_compatibility_baton, also
+ change its use in status4_wrapper_func in deprecated.c */
+ (*cst)->backwards_compatibility_baton = status;
+
+ if (status->versioned && status->conflicted)
+ {
+ svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
+
+ /* Note: This checks the on disk markers to automatically hide
+ text/property conflicts that are hidden by removing their
+ markers */
+ SVN_ERR(svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
+ &tree_conflicted, wc_ctx, local_abspath,
+ scratch_pool));
+
+ if (text_conflicted)
+ (*cst)->text_status = svn_wc_status_conflicted;
+
+ if (prop_conflicted)
+ (*cst)->prop_status = svn_wc_status_conflicted;
+
+ /* ### Also set this for tree_conflicts? */
+ if (text_conflicted || prop_conflicted)
+ (*cst)->node_status = svn_wc_status_conflicted;
+ }
+
+ (*cst)->moved_from_abspath = status->moved_from_abspath;
+ (*cst)->moved_to_abspath = status->moved_to_abspath;
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_client/switch.c b/contrib/subversion/subversion/libsvn_client/switch.c
new file mode 100644
index 0000000..fae03de
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/switch.c
@@ -0,0 +1,487 @@
+/*
+ * switch.c: implement 'switch' feature via WC & RA interfaces.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_time.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_config.h"
+#include "svn_pools.h"
+#include "client.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+/*** Code. ***/
+
+/* This feature is essentially identical to 'svn update' (see
+ ./update.c), but with two differences:
+
+ - the reporter->finish_report() routine needs to make the server
+ run delta_dirs() on two *different* paths, rather than on two
+ identical paths.
+
+ - after the update runs, we need to more than just
+ ensure_uniform_revision; we need to rewrite all the entries'
+ URL attributes.
+*/
+
+
+/* A conflict callback that simply records the conflicted path in BATON.
+
+ Implements svn_wc_conflict_resolver_func2_t.
+*/
+static svn_error_t *
+record_conflict(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *description,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *conflicted_paths = baton;
+
+ svn_hash_sets(conflicted_paths,
+ apr_pstrdup(apr_hash_pool_get(conflicted_paths),
+ description->local_abspath), "");
+ *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
+ NULL, result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* ...
+
+ Add the paths of any conflict victims to CONFLICTED_PATHS, if that
+ is not null.
+*/
+static svn_error_t *
+switch_internal(svn_revnum_t *result_rev,
+ apr_hash_t *conflicted_paths,
+ const char *local_abspath,
+ const char *anchor_abspath,
+ const char *switch_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t *timestamp_sleep,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const svn_ra_reporter3_t *reporter;
+ void *report_baton;
+ const char *anchor_url, *target;
+ svn_client__pathrev_t *switch_loc;
+ svn_ra_session_t *ra_session;
+ svn_revnum_t revnum;
+ const char *diff3_cmd;
+ apr_hash_t *wcroot_iprops;
+ apr_array_header_t *inherited_props;
+ svn_boolean_t use_commit_times;
+ const svn_delta_editor_t *switch_editor;
+ void *switch_edit_baton;
+ const char *preserved_exts_str;
+ apr_array_header_t *preserved_exts;
+ svn_boolean_t server_supports_depth;
+ struct svn_client__dirent_fetcher_baton_t dfb;
+ svn_config_t *cfg = ctx->config
+ ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+
+ /* An unknown depth can't be sticky. */
+ if (depth == svn_depth_unknown)
+ depth_is_sticky = FALSE;
+
+ /* Do not support the situation of both exclude and switch a target. */
+ if (depth == svn_depth_exclude)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot both exclude and switch a path"));
+
+ /* Get the external diff3, if any. */
+ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
+
+ if (diff3_cmd != NULL)
+ SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool));
+
+ /* See if the user wants last-commit timestamps instead of current ones. */
+ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
+
+ {
+ svn_boolean_t has_working;
+ SVN_ERR(svn_wc__node_has_working(&has_working, ctx->wc_ctx, local_abspath,
+ pool));
+
+ if (has_working)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot switch '%s' because it is not in the "
+ "repository yet"),
+ svn_dirent_local_style(local_abspath, pool));
+ }
+
+ /* See which files the user wants to preserve the extension of when
+ conflict files are made. */
+ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
+ preserved_exts = *preserved_exts_str
+ ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool)
+ : NULL;
+
+ /* Sanity check. Without these, the switch is meaningless. */
+ SVN_ERR_ASSERT(switch_url && (switch_url[0] != '\0'));
+
+ if (strcmp(local_abspath, anchor_abspath))
+ target = svn_dirent_basename(local_abspath, pool);
+ else
+ target = "";
+
+ SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath,
+ pool, pool));
+ if (! anchor_url)
+ return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
+ _("Directory '%s' has no URL"),
+ svn_dirent_local_style(anchor_abspath, pool));
+
+ /* We may need to crop the tree if the depth is sticky */
+ if (depth_is_sticky && depth < svn_depth_infinity)
+ {
+ svn_node_kind_t target_kind;
+
+ if (depth == svn_depth_exclude)
+ {
+ SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
+ local_abspath,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool));
+
+ /* Target excluded, we are done now */
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
+ TRUE, TRUE, pool));
+
+ if (target_kind == svn_node_dir)
+ SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool));
+ }
+
+ /* Open an RA session to 'source' URL */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc,
+ switch_url, anchor_abspath,
+ peg_revision, revision,
+ ctx, pool));
+
+ /* Disallow a switch operation to change the repository root of the
+ target. */
+ if (! svn_uri__is_ancestor(switch_loc->repos_root_url, anchor_url))
+ return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL,
+ _("'%s'\nis not the same repository as\n'%s'"),
+ anchor_url, switch_loc->repos_root_url);
+
+ /* If we're not ignoring ancestry, then error out if the switch
+ source and target don't have a common ancestory.
+
+ ### We're acting on the anchor here, not the target. Is that
+ ### okay? */
+ if (! ignore_ancestry)
+ {
+ svn_client__pathrev_t *target_base_loc, *yca;
+
+ SVN_ERR(svn_client__wc_node_get_base(&target_base_loc, local_abspath,
+ ctx->wc_ctx, pool, pool));
+
+ if (!target_base_loc)
+ yca = NULL; /* Not versioned */
+ else
+ {
+ /* ### It would be nice if this function could reuse the existing
+ ra session instead of opening two for its own use. */
+ SVN_ERR(svn_client__get_youngest_common_ancestor(
+ &yca, switch_loc, target_base_loc, ra_session, ctx,
+ pool, pool));
+ }
+ if (! yca)
+ return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+ _("'%s' shares no common ancestry with '%s'"),
+ switch_url,
+ svn_dirent_dirname(local_abspath, pool));
+ }
+
+ wcroot_iprops = apr_hash_make(pool);
+
+ /* Will the base of LOCAL_ABSPATH require an iprop cache post-switch?
+ If we are switching LOCAL_ABSPATH to the root of the repository then
+ we don't need to cache inherited properties. In all other cases we
+ *might* need to cache iprops. */
+ if (strcmp(switch_loc->repos_root_url, switch_loc->url) != 0)
+ {
+ svn_boolean_t wc_root;
+ svn_boolean_t needs_iprop_cache = TRUE;
+
+ SVN_ERR(svn_wc__is_wcroot(&wc_root, ctx->wc_ctx, local_abspath,
+ pool));
+
+ /* Switching the WC root to anything but the repos root means
+ we need an iprop cache. */
+ if (!wc_root)
+ {
+ /* We know we are switching a subtree to something other than the
+ repos root, but if we are unswitching that subtree we don't
+ need an iprops cache. */
+ const char *target_parent_url;
+ const char *unswitched_url;
+
+ /* Calculate the URL LOCAL_ABSPATH would have if it was unswitched
+ relative to its parent. */
+ SVN_ERR(svn_wc__node_get_url(&target_parent_url, ctx->wc_ctx,
+ svn_dirent_dirname(local_abspath,
+ pool),
+ pool, pool));
+ unswitched_url = svn_path_url_add_component2(
+ target_parent_url,
+ svn_dirent_basename(local_abspath, pool),
+ pool);
+
+ /* If LOCAL_ABSPATH will be unswitched relative to its parent, then
+ it doesn't need an iprop cache. Note: It doesn't matter if
+ LOCAL_ABSPATH is withing a switched subtree, only if it's the
+ *root* of a switched subtree.*/
+ if (strcmp(unswitched_url, switch_loc->url) == 0)
+ needs_iprop_cache = FALSE;
+ }
+
+
+ if (needs_iprop_cache)
+ {
+ SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props,
+ "", switch_loc->rev, pool,
+ pool));
+ svn_hash_sets(wcroot_iprops, local_abspath, inherited_props);
+ }
+ }
+
+ SVN_ERR(svn_ra_reparent(ra_session, anchor_url, pool));
+
+ /* Fetch the switch (update) editor. If REVISION is invalid, that's
+ okay; the RA driver will call editor->set_target_revision() later on. */
+ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, pool));
+
+ dfb.ra_session = ra_session;
+ dfb.anchor_url = anchor_url;
+ dfb.target_revision = switch_loc->rev;
+
+ SVN_ERR(svn_wc__get_switch_editor(&switch_editor, &switch_edit_baton,
+ &revnum, ctx->wc_ctx, anchor_abspath,
+ target, switch_loc->url, wcroot_iprops,
+ use_commit_times, depth,
+ depth_is_sticky, allow_unver_obstructions,
+ server_supports_depth,
+ diff3_cmd, preserved_exts,
+ svn_client__dirent_fetcher, &dfb,
+ conflicted_paths ? record_conflict : NULL,
+ conflicted_paths,
+ NULL, NULL,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool, pool));
+
+ /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
+ invalid revnum, that means RA will use the latest revision. */
+ SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
+ switch_loc->rev,
+ target,
+ depth_is_sticky ? depth : svn_depth_unknown,
+ switch_loc->url,
+ FALSE /* send_copyfrom_args */,
+ ignore_ancestry,
+ switch_editor, switch_edit_baton,
+ pool, pool));
+
+ /* Past this point, we assume the WC is going to be modified so we will
+ * need to sleep for timestamps. */
+ *timestamp_sleep = TRUE;
+
+ /* Drive the reporter structure, describing the revisions within
+ PATH. When we call reporter->finish_report, the update_editor
+ will be driven by svn_repos_dir_delta2. */
+ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
+ report_baton, TRUE,
+ depth, (! depth_is_sticky),
+ (! server_supports_depth),
+ use_commit_times,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool));
+
+ /* We handle externals after the switch is complete, so that
+ handling external items (and any errors therefrom) doesn't delay
+ the primary operation. */
+ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals))
+ {
+ apr_hash_t *new_externals;
+ apr_hash_t *new_depths;
+ SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
+ &new_depths,
+ ctx->wc_ctx, local_abspath,
+ depth, pool, pool));
+
+ SVN_ERR(svn_client__handle_externals(new_externals,
+ new_depths,
+ switch_loc->repos_root_url,
+ local_abspath,
+ depth, timestamp_sleep,
+ ctx, pool));
+ }
+
+ /* Let everyone know we're finished here. */
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(anchor_abspath, svn_wc_notify_update_completed,
+ pool);
+ notify->kind = svn_node_none;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ notify->revision = revnum;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+
+ /* If the caller wants the result revision, give it to them. */
+ if (result_rev)
+ *result_rev = revnum;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__switch_internal(svn_revnum_t *result_rev,
+ const char *path,
+ const char *switch_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t *timestamp_sleep,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *local_abspath, *anchor_abspath;
+ svn_boolean_t acquired_lock;
+ svn_error_t *err, *err1, *err2;
+ apr_hash_t *conflicted_paths
+ = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
+
+ SVN_ERR_ASSERT(path);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ /* Rely on svn_wc__acquire_write_lock setting ANCHOR_ABSPATH even
+ when it returns SVN_ERR_WC_LOCKED */
+ err = svn_wc__acquire_write_lock(&anchor_abspath,
+ ctx->wc_ctx, local_abspath, TRUE,
+ pool, pool);
+ if (err && err->apr_err != SVN_ERR_WC_LOCKED)
+ return svn_error_trace(err);
+
+ acquired_lock = (err == SVN_NO_ERROR);
+ svn_error_clear(err);
+
+ err1 = switch_internal(result_rev, conflicted_paths,
+ local_abspath, anchor_abspath,
+ switch_url, peg_revision, revision,
+ depth, depth_is_sticky,
+ ignore_externals,
+ allow_unver_obstructions, ignore_ancestry,
+ timestamp_sleep, ctx, pool);
+
+ /* Give the conflict resolver callback the opportunity to
+ * resolve any conflicts that were raised. */
+ if (! err1 && ctx->conflict_func2)
+ {
+ err1 = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
+ }
+
+ if (acquired_lock)
+ err2 = svn_wc__release_write_lock(ctx->wc_ctx, anchor_abspath, pool);
+ else
+ err2 = SVN_NO_ERROR;
+
+ return svn_error_compose_create(err1, err2);
+}
+
+svn_error_t *
+svn_client_switch3(svn_revnum_t *result_rev,
+ const char *path,
+ const char *switch_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t ignore_ancestry,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_boolean_t sleep_here = FALSE;
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+
+ err = svn_client__switch_internal(result_rev, path, switch_url,
+ peg_revision, revision, depth,
+ depth_is_sticky, ignore_externals,
+ allow_unver_obstructions,
+ ignore_ancestry, &sleep_here, ctx, pool);
+
+ /* Sleep to ensure timestamp integrity (we do this regardless of
+ errors in the actual switch operation(s)). */
+ if (sleep_here)
+ svn_io_sleep_for_timestamps(path, pool);
+
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/update.c b/contrib/subversion/subversion/libsvn_client/update.c
new file mode 100644
index 0000000..21f33ec
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/update.c
@@ -0,0 +1,707 @@
+/*
+ * update.c: wrappers around wc update functionality
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_config.h"
+#include "svn_time.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_io.h"
+#include "client.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+/* Implements svn_wc_dirents_func_t for update and switch handling. Assumes
+ a struct svn_client__dirent_fetcher_baton_t * baton */
+svn_error_t *
+svn_client__dirent_fetcher(void *baton,
+ apr_hash_t **dirents,
+ const char *repos_root_url,
+ const char *repos_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct svn_client__dirent_fetcher_baton_t *dfb = baton;
+ const char *old_url = NULL;
+ const char *session_relpath;
+ svn_node_kind_t kind;
+ const char *url;
+
+ url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ scratch_pool);
+
+ if (!svn_uri__is_ancestor(dfb->anchor_url, url))
+ {
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_url, dfb->ra_session,
+ url, scratch_pool));
+ session_relpath = "";
+ }
+ else
+ SVN_ERR(svn_ra_get_path_relative_to_session(dfb->ra_session,
+ &session_relpath, url,
+ scratch_pool));
+
+ /* Is session_relpath still a directory? */
+ SVN_ERR(svn_ra_check_path(dfb->ra_session, session_relpath,
+ dfb->target_revision, &kind, scratch_pool));
+
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_ra_get_dir2(dfb->ra_session, dirents, NULL, NULL,
+ session_relpath, dfb->target_revision,
+ SVN_DIRENT_KIND, result_pool));
+ else
+ *dirents = NULL;
+
+ if (old_url)
+ SVN_ERR(svn_ra_reparent(dfb->ra_session, old_url, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Code. ***/
+
+/* Set *CLEAN_CHECKOUT to FALSE only if LOCAL_ABSPATH is a non-empty
+ folder. ANCHOR_ABSPATH is the w/c root and LOCAL_ABSPATH will still
+ be considered empty, if it is equal to ANCHOR_ABSPATH and only
+ contains the admin sub-folder.
+ If the w/c folder already exists but cannot be openend, we return
+ "unclean" - just in case. Most likely, the caller will have to bail
+ out later due to the same error we got here.
+ */
+static svn_error_t *
+is_empty_wc(svn_boolean_t *clean_checkout,
+ const char *local_abspath,
+ const char *anchor_abspath,
+ apr_pool_t *pool)
+{
+ apr_dir_t *dir;
+ apr_finfo_t finfo;
+ svn_error_t *err;
+
+ /* "clean" until found dirty */
+ *clean_checkout = TRUE;
+
+ /* open directory. If it does not exist, yet, a clean one will
+ be created by the caller. */
+ err = svn_io_dir_open(&dir, local_abspath, pool);
+ if (err)
+ {
+ if (! APR_STATUS_IS_ENOENT(err->apr_err))
+ *clean_checkout = FALSE;
+
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ for (err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool);
+ err == SVN_NO_ERROR;
+ err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool))
+ {
+ /* Ignore entries for this dir and its parent, robustly.
+ (APR promises that they'll come first, so technically
+ this guard could be moved outside the loop. But Ryan Bloom
+ says he doesn't believe it, and I believe him. */
+ if (! (finfo.name[0] == '.'
+ && (finfo.name[1] == '\0'
+ || (finfo.name[1] == '.' && finfo.name[2] == '\0'))))
+ {
+ if ( ! svn_wc_is_adm_dir(finfo.name, pool)
+ || strcmp(local_abspath, anchor_abspath) != 0)
+ {
+ *clean_checkout = FALSE;
+ break;
+ }
+ }
+ }
+
+ if (err)
+ {
+ if (! APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* There was some issue reading the folder content.
+ * We better disable optimizations in that case. */
+ *clean_checkout = FALSE;
+ }
+
+ svn_error_clear(err);
+ }
+
+ return svn_io_dir_close(dir);
+}
+
+/* A conflict callback that simply records the conflicted path in BATON.
+
+ Implements svn_wc_conflict_resolver_func2_t.
+*/
+static svn_error_t *
+record_conflict(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *description,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *conflicted_paths = baton;
+
+ svn_hash_sets(conflicted_paths,
+ apr_pstrdup(apr_hash_pool_get(conflicted_paths),
+ description->local_abspath), "");
+ *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
+ NULL, result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* This is a helper for svn_client__update_internal(), which see for
+ an explanation of most of these parameters. Some stuff that's
+ unique is as follows:
+
+ ANCHOR_ABSPATH is the local absolute path of the update anchor.
+ This is typically either the same as LOCAL_ABSPATH, or the
+ immediate parent of LOCAL_ABSPATH.
+
+ If NOTIFY_SUMMARY is set (and there's a notification handler in
+ CTX), transmit the final update summary upon successful
+ completion of the update.
+
+ Add the paths of any conflict victims to CONFLICTED_PATHS, if that
+ is not null.
+*/
+static svn_error_t *
+update_internal(svn_revnum_t *result_rev,
+ apr_hash_t *conflicted_paths,
+ const char *local_abspath,
+ const char *anchor_abspath,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t *timestamp_sleep,
+ svn_boolean_t notify_summary,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *update_editor;
+ void *update_edit_baton;
+ const svn_ra_reporter3_t *reporter;
+ void *report_baton;
+ const char *corrected_url;
+ const char *target;
+ const char *repos_root_url;
+ const char *repos_relpath;
+ const char *repos_uuid;
+ const char *anchor_url;
+ svn_revnum_t revnum;
+ svn_boolean_t use_commit_times;
+ svn_boolean_t clean_checkout = FALSE;
+ const char *diff3_cmd;
+ apr_hash_t *wcroot_iprops;
+ svn_opt_revision_t opt_rev;
+ svn_ra_session_t *ra_session;
+ const char *preserved_exts_str;
+ apr_array_header_t *preserved_exts;
+ struct svn_client__dirent_fetcher_baton_t dfb;
+ svn_boolean_t server_supports_depth;
+ svn_boolean_t cropping_target;
+ svn_boolean_t target_conflicted = FALSE;
+ svn_config_t *cfg = ctx->config
+ ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+
+ if (result_rev)
+ *result_rev = SVN_INVALID_REVNUM;
+
+ /* An unknown depth can't be sticky. */
+ if (depth == svn_depth_unknown)
+ depth_is_sticky = FALSE;
+
+ if (strcmp(local_abspath, anchor_abspath))
+ target = svn_dirent_basename(local_abspath, pool);
+ else
+ target = "";
+
+ /* Check if our anchor exists in BASE. If it doesn't we can't update. */
+ SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url,
+ &repos_uuid, NULL,
+ ctx->wc_ctx, anchor_abspath,
+ TRUE, FALSE,
+ pool, pool));
+
+ /* It does not make sense to update conflict victims. */
+ if (repos_relpath)
+ {
+ svn_error_t *err;
+ svn_boolean_t text_conflicted, prop_conflicted;
+
+ anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ pool);
+
+ err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
+ NULL,
+ ctx->wc_ctx, local_abspath, pool);
+
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* tree-conflicts are handled by the update editor */
+ if (!err && (text_conflicted || prop_conflicted))
+ target_conflicted = TRUE;
+ }
+ else
+ anchor_url = NULL;
+
+ if (! anchor_url || target_conflicted)
+ {
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *nt;
+
+ nt = svn_wc_create_notify(local_abspath,
+ target_conflicted
+ ? svn_wc_notify_skip_conflicted
+ : svn_wc_notify_update_skip_working_only,
+ pool);
+
+ ctx->notify_func2(ctx->notify_baton2, nt, pool);
+ }
+ return SVN_NO_ERROR;
+ }
+
+ /* We may need to crop the tree if the depth is sticky */
+ cropping_target = (depth_is_sticky && depth < svn_depth_infinity);
+ if (cropping_target)
+ {
+ svn_node_kind_t target_kind;
+
+ if (depth == svn_depth_exclude)
+ {
+ SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
+ local_abspath,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool));
+
+ /* Target excluded, we are done now */
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
+ TRUE, TRUE, pool));
+ if (target_kind == svn_node_dir)
+ {
+ SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool));
+ }
+ }
+
+ /* check whether the "clean c/o" optimization is applicable */
+ SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, pool));
+
+ /* Get the external diff3, if any. */
+ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
+
+ if (diff3_cmd != NULL)
+ SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool));
+
+ /* See if the user wants last-commit timestamps instead of current ones. */
+ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
+
+ /* See which files the user wants to preserve the extension of when
+ conflict files are made. */
+ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
+ preserved_exts = *preserved_exts_str
+ ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool)
+ : NULL;
+
+ /* Let everyone know we're starting a real update (unless we're
+ asked not to). */
+ if (ctx->notify_func2 && notify_summary)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started,
+ pool);
+ notify->kind = svn_node_none;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+
+ /* Open an RA session for the URL */
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
+ anchor_url,
+ anchor_abspath, NULL, TRUE,
+ TRUE, ctx, pool, pool));
+
+ /* If we got a corrected URL from the RA subsystem, we'll need to
+ relocate our working copy first. */
+ if (corrected_url)
+ {
+ const char *new_repos_root_url;
+
+ /* To relocate everything inside our repository we need the old and new
+ repos root. */
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url, pool));
+
+ /* svn_client_relocate2() will check the uuid */
+ SVN_ERR(svn_client_relocate2(anchor_abspath, anchor_url,
+ new_repos_root_url, ignore_externals,
+ ctx, pool));
+
+ /* Store updated repository root for externals */
+ repos_root_url = new_repos_root_url;
+ /* ### We should update anchor_loc->repos_uuid too, although currently
+ * we don't use it. */
+ anchor_url = corrected_url;
+ }
+
+ /* Resolve unspecified REVISION now, because we need to retrieve the
+ correct inherited props prior to the editor drive and we need to
+ use the same value of HEAD for both. */
+ opt_rev.kind = revision->kind;
+ opt_rev.value = revision->value;
+ if (opt_rev.kind == svn_opt_revision_unspecified)
+ opt_rev.kind = svn_opt_revision_head;
+
+ /* ### todo: shouldn't svn_client__get_revision_number be able
+ to take a URL as easily as a local path? */
+ SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
+ local_abspath, ra_session, &opt_rev,
+ pool));
+
+ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, pool));
+
+ dfb.ra_session = ra_session;
+ dfb.target_revision = revnum;
+ dfb.anchor_url = anchor_url;
+
+ SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath,
+ revnum, depth, ra_session,
+ ctx, pool, pool));
+
+ /* Fetch the update editor. If REVISION is invalid, that's okay;
+ the RA driver will call editor->set_target_revision later on. */
+ SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton,
+ &revnum, ctx->wc_ctx, anchor_abspath,
+ target, wcroot_iprops, use_commit_times,
+ depth, depth_is_sticky,
+ allow_unver_obstructions,
+ adds_as_modification,
+ server_supports_depth,
+ clean_checkout,
+ diff3_cmd, preserved_exts,
+ svn_client__dirent_fetcher, &dfb,
+ conflicted_paths ? record_conflict : NULL,
+ conflicted_paths,
+ NULL, NULL,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool, pool));
+
+ /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
+ invalid revnum, that means RA will use the latest revision. */
+ SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton,
+ revnum, target,
+ (!server_supports_depth || depth_is_sticky
+ ? depth
+ : svn_depth_unknown),
+ FALSE /* send_copyfrom_args */,
+ FALSE /* ignore_ancestry */,
+ update_editor, update_edit_baton, pool, pool));
+
+ /* Past this point, we assume the WC is going to be modified so we will
+ * need to sleep for timestamps. */
+ *timestamp_sleep = TRUE;
+
+ /* Drive the reporter structure, describing the revisions within
+ PATH. When we call reporter->finish_report, the
+ update_editor will be driven by svn_repos_dir_delta2. */
+ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
+ report_baton, TRUE,
+ depth, (! depth_is_sticky),
+ (! server_supports_depth),
+ use_commit_times,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ pool));
+
+ /* We handle externals after the update is complete, so that
+ handling external items (and any errors therefrom) doesn't delay
+ the primary operation. */
+ if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target)
+ && (! ignore_externals))
+ {
+ apr_hash_t *new_externals;
+ apr_hash_t *new_depths;
+ SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
+ &new_depths,
+ ctx->wc_ctx, local_abspath,
+ depth, pool, pool));
+
+ SVN_ERR(svn_client__handle_externals(new_externals,
+ new_depths,
+ repos_root_url, local_abspath,
+ depth, timestamp_sleep,
+ ctx, pool));
+ }
+
+ /* Let everyone know we're finished here (unless we're asked not to). */
+ if (ctx->notify_func2 && notify_summary)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
+ pool);
+ notify->kind = svn_node_none;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ notify->revision = revnum;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+
+ /* If the caller wants the result revision, give it to them. */
+ if (result_rev)
+ *result_rev = revnum;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__update_internal(svn_revnum_t *result_rev,
+ const char *local_abspath,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t make_parents,
+ svn_boolean_t innerupdate,
+ svn_boolean_t *timestamp_sleep,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *anchor_abspath, *lockroot_abspath;
+ svn_error_t *err;
+ svn_opt_revision_t peg_revision = *revision;
+ apr_hash_t *conflicted_paths
+ = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(! (innerupdate && make_parents));
+
+ if (make_parents)
+ {
+ int i;
+ const char *parent_abspath = local_abspath;
+ apr_array_header_t *missing_parents =
+ apr_array_make(pool, 4, sizeof(const char *));
+
+ while (1)
+ {
+ /* Try to lock. If we can't lock because our target (or its
+ parent) isn't a working copy, we'll try to walk up the
+ tree to find a working copy, remembering this path's
+ parent as one we need to flesh out. */
+ err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
+ parent_abspath, !innerupdate,
+ pool, pool);
+ if (!err)
+ break;
+ if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ || svn_dirent_is_root(parent_abspath, strlen(parent_abspath)))
+ return err;
+ svn_error_clear(err);
+
+ /* Remember the parent of our update target as a missing
+ parent. */
+ parent_abspath = svn_dirent_dirname(parent_abspath, pool);
+ APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath;
+ }
+
+ /* Run 'svn up --depth=empty' (effectively) on the missing
+ parents, if any. */
+ anchor_abspath = lockroot_abspath;
+ for (i = missing_parents->nelts - 1; i >= 0; i--)
+ {
+ const char *missing_parent =
+ APR_ARRAY_IDX(missing_parents, i, const char *);
+
+ err = update_internal(result_rev, conflicted_paths,
+ missing_parent, anchor_abspath,
+ &peg_revision, svn_depth_empty, FALSE,
+ ignore_externals, allow_unver_obstructions,
+ adds_as_modification, timestamp_sleep,
+ FALSE, ctx, pool);
+ if (err)
+ goto cleanup;
+ anchor_abspath = missing_parent;
+
+ /* If we successfully updated a missing parent, let's re-use
+ the returned revision number for future updates for the
+ sake of consistency. */
+ peg_revision.kind = svn_opt_revision_number;
+ peg_revision.value.number = *result_rev;
+ }
+ }
+ else
+ {
+ SVN_ERR(svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
+ local_abspath, !innerupdate,
+ pool, pool));
+ anchor_abspath = lockroot_abspath;
+ }
+
+ err = update_internal(result_rev, conflicted_paths,
+ local_abspath, anchor_abspath,
+ &peg_revision, depth, depth_is_sticky,
+ ignore_externals, allow_unver_obstructions,
+ adds_as_modification, timestamp_sleep,
+ TRUE, ctx, pool);
+
+ /* Give the conflict resolver callback the opportunity to
+ * resolve any conflicts that were raised. */
+ if (! err && ctx->conflict_func2)
+ {
+ err = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
+ }
+
+ cleanup:
+ err = svn_error_compose_create(
+ err,
+ svn_wc__release_write_lock(ctx->wc_ctx, lockroot_abspath, pool));
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_client_update4(apr_array_header_t **result_revs,
+ const apr_array_header_t *paths,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t ignore_externals,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t make_parents,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ const char *path = NULL;
+ svn_boolean_t sleep = FALSE;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ if (result_revs)
+ *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t));
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ path = APR_ARRAY_IDX(paths, i, const char *);
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+ }
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ svn_revnum_t result_rev;
+ const char *local_abspath;
+ path = APR_ARRAY_IDX(paths, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ {
+ err = ctx->cancel_func(ctx->cancel_baton);
+ if (err)
+ goto cleanup;
+ }
+
+ err = svn_dirent_get_absolute(&local_abspath, path, iterpool);
+ if (err)
+ goto cleanup;
+ err = svn_client__update_internal(&result_rev, local_abspath,
+ revision, depth, depth_is_sticky,
+ ignore_externals,
+ allow_unver_obstructions,
+ adds_as_modification,
+ make_parents,
+ FALSE, &sleep,
+ ctx,
+ iterpool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ goto cleanup;
+
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+
+ /* SVN_ERR_WC_NOT_WORKING_COPY: it's not versioned */
+
+ result_rev = SVN_INVALID_REVNUM;
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+ notify = svn_wc_create_notify(path,
+ svn_wc_notify_skip,
+ iterpool);
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, iterpool);
+ }
+ }
+ if (result_revs)
+ APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev;
+ }
+ svn_pool_destroy(iterpool);
+
+ cleanup:
+ if (sleep)
+ svn_io_sleep_for_timestamps((paths->nelts == 1) ? path : NULL, pool);
+
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_client/upgrade.c b/contrib/subversion/subversion/libsvn_client/upgrade.c
new file mode 100644
index 0000000..b9f3235
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/upgrade.c
@@ -0,0 +1,327 @@
+/*
+ * upgrade.c: wrapper around wc upgrade functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_time.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_config.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "client.h"
+#include "svn_props.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+/*** Code. ***/
+
+/* callback baton for fetch_repos_info */
+struct repos_info_baton
+{
+ apr_pool_t *state_pool;
+ svn_client_ctx_t *ctx;
+ const char *last_repos;
+ const char *last_uuid;
+};
+
+/* svn_wc_upgrade_get_repos_info_t implementation for calling
+ svn_wc_upgrade() from svn_client_upgrade() */
+static svn_error_t *
+fetch_repos_info(const char **repos_root,
+ const char **repos_uuid,
+ void *baton,
+ const char *url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct repos_info_baton *ri = baton;
+
+ /* The same info is likely to retrieved multiple times (e.g. externals) */
+ if (ri->last_repos && svn_uri__is_ancestor(ri->last_repos, url))
+ {
+ *repos_root = apr_pstrdup(result_pool, ri->last_repos);
+ *repos_uuid = apr_pstrdup(result_pool, ri->last_uuid);
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_client_get_repos_root(repos_root, repos_uuid, url, ri->ctx,
+ result_pool, scratch_pool));
+
+ /* Store data for further calls */
+ ri->last_repos = apr_pstrdup(ri->state_pool, *repos_root);
+ ri->last_uuid = apr_pstrdup(ri->state_pool, *repos_uuid);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_upgrade(const char *path,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ apr_hash_t *externals;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ apr_pool_t *iterpool2;
+ svn_opt_revision_t rev = {svn_opt_revision_unspecified, {0}};
+ struct repos_info_baton info_baton;
+
+ info_baton.state_pool = scratch_pool;
+ info_baton.ctx = ctx;
+ info_baton.last_repos = NULL;
+ info_baton.last_uuid = NULL;
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+ SVN_ERR(svn_wc_upgrade(ctx->wc_ctx, local_abspath,
+ fetch_repos_info, &info_baton,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool));
+
+ /* Now it's time to upgrade the externals too. We do it after the wc
+ upgrade to avoid that errors in the externals causes the wc upgrade to
+ fail. Thanks to caching the performance penalty of walking the wc a
+ second time shouldn't be too severe */
+ SVN_ERR(svn_client_propget5(&externals, NULL, SVN_PROP_EXTERNALS,
+ local_abspath, &rev, &rev, NULL,
+ svn_depth_infinity, NULL, ctx,
+ scratch_pool, scratch_pool));
+
+ iterpool = svn_pool_create(scratch_pool);
+ iterpool2 = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, externals); hi;
+ hi = apr_hash_next(hi))
+ {
+ int i;
+ const char *externals_parent_abspath;
+ const char *externals_parent_url;
+ const char *externals_parent_repos_root_url;
+ const char *externals_parent_repos_relpath;
+ const char *externals_parent = svn__apr_hash_index_key(hi);
+ svn_string_t *external_desc = svn__apr_hash_index_val(hi);
+ apr_array_header_t *externals_p;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+ externals_p = apr_array_make(iterpool, 1,
+ sizeof(svn_wc_external_item2_t*));
+
+ /* In this loop, an error causes the respective externals definition, or
+ * the external (inner loop), to be skipped, so that upgrade carries on
+ * with the other externals. */
+
+ err = svn_dirent_get_absolute(&externals_parent_abspath,
+ externals_parent, iterpool);
+
+ if (!err)
+ err = svn_wc__node_get_repos_info(NULL,
+ &externals_parent_repos_relpath,
+ &externals_parent_repos_root_url,
+ NULL,
+ ctx->wc_ctx,
+ externals_parent_abspath,
+ iterpool, iterpool);
+
+ if (!err)
+ externals_parent_url = svn_path_url_add_component2(
+ externals_parent_repos_root_url,
+ externals_parent_repos_relpath,
+ iterpool);
+ if (!err)
+ err = svn_wc_parse_externals_description3(
+ &externals_p, svn_dirent_dirname(path, iterpool),
+ external_desc->data, FALSE, iterpool);
+ if (err)
+ {
+ svn_wc_notify_t *notify =
+ svn_wc_create_notify(externals_parent,
+ svn_wc_notify_failed_external,
+ scratch_pool);
+ notify->err = err;
+
+ ctx->notify_func2(ctx->notify_baton2,
+ notify, scratch_pool);
+
+ svn_error_clear(err);
+
+ /* Next externals definition, please... */
+ continue;
+ }
+
+ for (i = 0; i < externals_p->nelts; i++)
+ {
+ svn_wc_external_item2_t *item;
+ const char *resolved_url;
+ const char *external_abspath;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ svn_node_kind_t external_kind;
+ svn_revnum_t peg_revision;
+ svn_revnum_t revision;
+
+ item = APR_ARRAY_IDX(externals_p, i, svn_wc_external_item2_t*);
+
+ svn_pool_clear(iterpool2);
+ external_abspath = svn_dirent_join(externals_parent_abspath,
+ item->target_dir,
+ iterpool2);
+
+ err = svn_wc__resolve_relative_external_url(
+ &resolved_url,
+ item,
+ externals_parent_repos_root_url,
+ externals_parent_url,
+ scratch_pool, scratch_pool);
+ if (err)
+ goto handle_error;
+
+ /* This is a hack. We only need to call svn_wc_upgrade() on external
+ * dirs, as file externals are upgraded along with their defining
+ * WC. Reading the kind will throw an exception on an external dir,
+ * saying that the wc must be upgraded. If it's a file, the lookup
+ * is done in an adm_dir belonging to the defining wc (which has
+ * already been upgraded) and no error is returned. If it doesn't
+ * exist (external that isn't checked out yet), we'll just get
+ * svn_node_none. */
+ err = svn_wc_read_kind2(&external_kind, ctx->wc_ctx,
+ external_abspath, TRUE, FALSE, iterpool2);
+ if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
+ {
+ svn_error_clear(err);
+
+ err = svn_client_upgrade(external_abspath, ctx, iterpool2);
+ if (err)
+ goto handle_error;
+ }
+ else if (err)
+ goto handle_error;
+
+ /* The upgrade of any dir should be done now, get the now reliable
+ * kind. */
+ err = svn_wc_read_kind2(&external_kind, ctx->wc_ctx, external_abspath,
+ TRUE, FALSE, iterpool2);
+ if (err)
+ goto handle_error;
+
+ /* Update the EXTERNALS table according to the root URL,
+ * relpath and uuid known in the upgraded external WC. */
+
+ /* We should probably have a function that provides all three
+ * of root URL, repos relpath and uuid at once, but here goes... */
+
+ /* First get the relpath, as that returns SVN_ERR_WC_PATH_NOT_FOUND
+ * when the node is not present in the file system.
+ * svn_wc__node_get_repos_info() would try to derive the URL. */
+ err = svn_wc__node_get_repos_info(NULL,
+ &repos_relpath,
+ &repos_root_url,
+ &repos_uuid,
+ ctx->wc_ctx,
+ external_abspath,
+ iterpool2, iterpool2);
+ if (err)
+ goto handle_error;
+
+ /* If we haven't got any information from the checked out external,
+ * or if the URL information mismatches the external's definition,
+ * ask fetch_repos_info() to find out the repos root. */
+ if (0 != strcmp(resolved_url,
+ svn_path_url_add_component2(repos_root_url,
+ repos_relpath,
+ scratch_pool)))
+ {
+ err = fetch_repos_info(&repos_root_url,
+ &repos_uuid,
+ &info_baton,
+ resolved_url,
+ scratch_pool, scratch_pool);
+ if (err)
+ goto handle_error;
+
+ repos_relpath = svn_uri_skip_ancestor(repos_root_url,
+ resolved_url,
+ iterpool2);
+
+ /* There's just the URL, no idea what kind the external is.
+ * That's fine, as the external isn't even checked out yet.
+ * The kind will be set during the next 'update'. */
+ external_kind = svn_node_unknown;
+ }
+
+ if (err)
+ goto handle_error;
+
+ peg_revision = (item->peg_revision.kind == svn_opt_revision_number
+ ? item->peg_revision.value.number
+ : SVN_INVALID_REVNUM);
+
+ revision = (item->revision.kind == svn_opt_revision_number
+ ? item->revision.value.number
+ : SVN_INVALID_REVNUM);
+
+ err = svn_wc__upgrade_add_external_info(ctx->wc_ctx,
+ external_abspath,
+ external_kind,
+ externals_parent,
+ repos_relpath,
+ repos_root_url,
+ repos_uuid,
+ peg_revision,
+ revision,
+ iterpool2);
+handle_error:
+ if (err)
+ {
+ svn_wc_notify_t *notify =
+ svn_wc_create_notify(external_abspath,
+ svn_wc_notify_failed_external,
+ scratch_pool);
+ notify->err = err;
+ ctx->notify_func2(ctx->notify_baton2,
+ notify, scratch_pool);
+ svn_error_clear(err);
+ /* Next external node, please... */
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ svn_pool_destroy(iterpool2);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/url.c b/contrib/subversion/subversion/libsvn_client/url.c
new file mode 100644
index 0000000..36019ad
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/url.c
@@ -0,0 +1,63 @@
+/*
+ * url.c: converting paths to urls
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_pools.h>
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_types.h"
+#include "svn_opt.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+
+#include "private/svn_wc_private.h"
+#include "client.h"
+#include "svn_private_config.h"
+
+
+
+svn_error_t *
+svn_client_url_from_path2(const char **url,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (!svn_path_is_url(path_or_url))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&path_or_url, path_or_url,
+ scratch_pool));
+
+ return svn_error_trace(
+ svn_wc__node_get_url(url, ctx->wc_ctx, path_or_url,
+ result_pool, scratch_pool));
+ }
+ else
+ *url = svn_uri_canonicalize(path_or_url, result_pool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/util.c b/contrib/subversion/subversion/libsvn_client/util.c
new file mode 100644
index 0000000..5ac0b8f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/util.c
@@ -0,0 +1,457 @@
+/*
+ * util.c : utility functions for the libsvn_client library
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_types.h"
+#include "svn_opt.h"
+#include "svn_props.h"
+#include "svn_path.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+
+#include "private/svn_client_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_fspath.h"
+
+#include "client.h"
+
+#include "svn_private_config.h"
+
+svn_client__pathrev_t *
+svn_client__pathrev_create(const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t rev,
+ const char *url,
+ apr_pool_t *result_pool)
+{
+ svn_client__pathrev_t *loc = apr_palloc(result_pool, sizeof(*loc));
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_path_is_url(repos_root_url));
+ SVN_ERR_ASSERT_NO_RETURN(svn_path_is_url(url));
+
+ loc->repos_root_url = apr_pstrdup(result_pool, repos_root_url);
+ loc->repos_uuid = apr_pstrdup(result_pool, repos_uuid);
+ loc->rev = rev;
+ loc->url = apr_pstrdup(result_pool, url);
+ return loc;
+}
+
+svn_client__pathrev_t *
+svn_client__pathrev_create_with_relpath(const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t rev,
+ const char *relpath,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR_ASSERT_NO_RETURN(svn_relpath_is_canonical(relpath));
+
+ return svn_client__pathrev_create(
+ repos_root_url, repos_uuid, rev,
+ svn_path_url_add_component2(repos_root_url, relpath, result_pool),
+ result_pool);
+}
+
+svn_error_t *
+svn_client__pathrev_create_with_session(svn_client__pathrev_t **pathrev_p,
+ svn_ra_session_t *ra_session,
+ svn_revnum_t rev,
+ const char *url,
+ apr_pool_t *result_pool)
+{
+ svn_client__pathrev_t *pathrev = apr_palloc(result_pool, sizeof(*pathrev));
+
+ SVN_ERR_ASSERT(svn_path_is_url(url));
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &pathrev->repos_root_url,
+ result_pool));
+ SVN_ERR(svn_ra_get_uuid2(ra_session, &pathrev->repos_uuid, result_pool));
+ pathrev->rev = rev;
+ pathrev->url = apr_pstrdup(result_pool, url);
+ *pathrev_p = pathrev;
+ return SVN_NO_ERROR;
+}
+
+svn_client__pathrev_t *
+svn_client__pathrev_dup(const svn_client__pathrev_t *pathrev,
+ apr_pool_t *result_pool)
+{
+ return svn_client__pathrev_create(
+ pathrev->repos_root_url, pathrev->repos_uuid,
+ pathrev->rev, pathrev->url, result_pool);
+}
+
+svn_client__pathrev_t *
+svn_client__pathrev_join_relpath(const svn_client__pathrev_t *pathrev,
+ const char *relpath,
+ apr_pool_t *result_pool)
+{
+ return svn_client__pathrev_create(
+ pathrev->repos_root_url, pathrev->repos_uuid, pathrev->rev,
+ svn_path_url_add_component2(pathrev->url, relpath, result_pool),
+ result_pool);
+}
+
+const char *
+svn_client__pathrev_relpath(const svn_client__pathrev_t *pathrev,
+ apr_pool_t *result_pool)
+{
+ return svn_uri_skip_ancestor(pathrev->repos_root_url, pathrev->url,
+ result_pool);
+}
+
+const char *
+svn_client__pathrev_fspath(const svn_client__pathrev_t *pathrev,
+ apr_pool_t *result_pool)
+{
+ return svn_fspath__canonicalize(svn_uri_skip_ancestor(
+ pathrev->repos_root_url, pathrev->url,
+ result_pool),
+ result_pool);
+}
+
+
+svn_client_commit_item3_t *
+svn_client_commit_item3_create(apr_pool_t *pool)
+{
+ return apr_pcalloc(pool, sizeof(svn_client_commit_item3_t));
+}
+
+svn_client_commit_item3_t *
+svn_client_commit_item3_dup(const svn_client_commit_item3_t *item,
+ apr_pool_t *pool)
+{
+ svn_client_commit_item3_t *new_item = apr_palloc(pool, sizeof(*new_item));
+
+ *new_item = *item;
+
+ if (new_item->path)
+ new_item->path = apr_pstrdup(pool, new_item->path);
+
+ if (new_item->url)
+ new_item->url = apr_pstrdup(pool, new_item->url);
+
+ if (new_item->copyfrom_url)
+ new_item->copyfrom_url = apr_pstrdup(pool, new_item->copyfrom_url);
+
+ if (new_item->incoming_prop_changes)
+ new_item->incoming_prop_changes =
+ svn_prop_array_dup(new_item->incoming_prop_changes, pool);
+
+ if (new_item->outgoing_prop_changes)
+ new_item->outgoing_prop_changes =
+ svn_prop_array_dup(new_item->outgoing_prop_changes, pool);
+
+ return new_item;
+}
+
+svn_error_t *
+svn_client__wc_node_get_base(svn_client__pathrev_t **base_p,
+ const char *wc_abspath,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *relpath;
+
+ *base_p = apr_palloc(result_pool, sizeof(**base_p));
+
+ SVN_ERR(svn_wc__node_get_base(NULL,
+ &(*base_p)->rev,
+ &relpath,
+ &(*base_p)->repos_root_url,
+ &(*base_p)->repos_uuid,
+ NULL,
+ wc_ctx, wc_abspath,
+ TRUE /* ignore_enoent */,
+ TRUE /* show_hidden */,
+ result_pool, scratch_pool));
+ if ((*base_p)->repos_root_url && relpath)
+ {
+ (*base_p)->url = svn_path_url_add_component2(
+ (*base_p)->repos_root_url, relpath, result_pool);
+ }
+ else
+ {
+ *base_p = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__wc_node_get_origin(svn_client__pathrev_t **origin_p,
+ const char *wc_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *relpath;
+
+ *origin_p = apr_palloc(result_pool, sizeof(**origin_p));
+
+ SVN_ERR(svn_wc__node_get_origin(NULL /* is_copy */,
+ &(*origin_p)->rev,
+ &relpath,
+ &(*origin_p)->repos_root_url,
+ &(*origin_p)->repos_uuid,
+ NULL, ctx->wc_ctx, wc_abspath,
+ FALSE /* scan_deleted */,
+ result_pool, scratch_pool));
+ if ((*origin_p)->repos_root_url && relpath)
+ {
+ (*origin_p)->url = svn_path_url_add_component2(
+ (*origin_p)->repos_root_url, relpath, result_pool);
+ }
+ else
+ {
+ *origin_p = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_get_repos_root(const char **repos_root,
+ const char **repos_uuid,
+ const char *abspath_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_session_t *ra_session;
+
+ /* If PATH_OR_URL is a local path we can fetch the repos root locally. */
+ if (!svn_path_is_url(abspath_or_url))
+ {
+ svn_error_t *err;
+ err = svn_wc__node_get_repos_info(NULL, NULL, repos_root, repos_uuid,
+ ctx->wc_ctx, abspath_or_url,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ if (repos_root)
+ *repos_root = NULL;
+ if (repos_uuid)
+ *repos_uuid = NULL;
+ }
+ return SVN_NO_ERROR;
+ }
+
+ /* If PATH_OR_URL was a URL, we use the RA layer to look it up. */
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, abspath_or_url, NULL,
+ ctx, scratch_pool, scratch_pool));
+
+ if (repos_root)
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, repos_root, result_pool));
+ if (repos_uuid)
+ SVN_ERR(svn_ra_get_uuid2(ra_session, repos_uuid, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+const svn_opt_revision_t *
+svn_cl__rev_default_to_head_or_base(const svn_opt_revision_t *revision,
+ const char *path_or_url)
+{
+ static svn_opt_revision_t head_rev = { svn_opt_revision_head, { 0 } };
+ static svn_opt_revision_t base_rev = { svn_opt_revision_base, { 0 } };
+
+ if (revision->kind == svn_opt_revision_unspecified)
+ return svn_path_is_url(path_or_url) ? &head_rev : &base_rev;
+ return revision;
+}
+
+const svn_opt_revision_t *
+svn_cl__rev_default_to_head_or_working(const svn_opt_revision_t *revision,
+ const char *path_or_url)
+{
+ static svn_opt_revision_t head_rev = { svn_opt_revision_head, { 0 } };
+ static svn_opt_revision_t work_rev = { svn_opt_revision_working, { 0 } };
+
+ if (revision->kind == svn_opt_revision_unspecified)
+ return svn_path_is_url(path_or_url) ? &head_rev : &work_rev;
+ return revision;
+}
+
+const svn_opt_revision_t *
+svn_cl__rev_default_to_peg(const svn_opt_revision_t *revision,
+ const svn_opt_revision_t *peg_revision)
+{
+ if (revision->kind == svn_opt_revision_unspecified)
+ return peg_revision;
+ return revision;
+}
+
+svn_error_t *
+svn_client__assert_homogeneous_target_type(const apr_array_header_t *targets)
+{
+ svn_boolean_t wc_present = FALSE, url_present = FALSE;
+ int i;
+
+ for (i = 0; i < targets->nelts; ++i)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (! svn_path_is_url(target))
+ wc_present = TRUE;
+ else
+ url_present = TRUE;
+ if (url_present && wc_present)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Cannot mix repository and working copy "
+ "targets"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct shim_callbacks_baton
+{
+ svn_wc_context_t *wc_ctx;
+ apr_hash_t *relpath_map;
+};
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct shim_callbacks_baton *scb = baton;
+ const char *local_abspath;
+
+ local_abspath = svn_hash_gets(scb->relpath_map, path);
+ if (!local_abspath)
+ {
+ *props = apr_hash_make(result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* Reads the pristine properties of WORKING, not those of BASE */
+ SVN_ERR(svn_wc_get_pristine_props(props, scb->wc_ctx, local_abspath,
+ result_pool, scratch_pool));
+
+ if (!*props)
+ *props = apr_hash_make(result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct shim_callbacks_baton *scb = baton;
+ const char *local_abspath;
+
+ local_abspath = svn_hash_gets(scb->relpath_map, path);
+ if (!local_abspath)
+ {
+ *kind = svn_node_unknown;
+ return SVN_NO_ERROR;
+ }
+ /* Reads the WORKING kind. Not the BASE kind */
+ SVN_ERR(svn_wc_read_kind2(kind, scb->wc_ctx, local_abspath,
+ TRUE, FALSE, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct shim_callbacks_baton *scb = baton;
+ const char *local_abspath;
+ svn_stream_t *pristine_stream;
+ svn_stream_t *temp_stream;
+ svn_error_t *err;
+
+ local_abspath = svn_hash_gets(scb->relpath_map, path);
+ if (!local_abspath)
+ {
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Reads the pristine of WORKING, not of BASE */
+ err = svn_wc_get_pristine_contents2(&pristine_stream, scb->wc_ctx,
+ local_abspath, scratch_pool,
+ scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ SVN_ERR(svn_stream_open_unique(&temp_stream, filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(pristine_stream, temp_stream, NULL, NULL,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_delta_shim_callbacks_t *
+svn_client__get_shim_callbacks(svn_wc_context_t *wc_ctx,
+ apr_hash_t *relpath_map,
+ apr_pool_t *result_pool)
+{
+ svn_delta_shim_callbacks_t *callbacks =
+ svn_delta_shim_callbacks_default(result_pool);
+ struct shim_callbacks_baton *scb = apr_pcalloc(result_pool, sizeof(*scb));
+
+ scb->wc_ctx = wc_ctx;
+ if (relpath_map)
+ scb->relpath_map = relpath_map;
+ else
+ scb->relpath_map = apr_hash_make(result_pool);
+
+ callbacks->fetch_props_func = fetch_props_func;
+ callbacks->fetch_kind_func = fetch_kind_func;
+ callbacks->fetch_base_func = fetch_base_func;
+ callbacks->fetch_baton = scb;
+
+ return callbacks;
+}
diff --git a/contrib/subversion/subversion/libsvn_client/version.c b/contrib/subversion/subversion/libsvn_client/version.c
new file mode 100644
index 0000000..2ad6417
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_client/version.c
@@ -0,0 +1,33 @@
+/*
+ * version.c: library version number
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_version.h"
+#include "svn_client.h"
+
+const svn_version_t *
+svn_client_version(void)
+{
+ SVN_VERSION_BODY;
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/cancel.c b/contrib/subversion/subversion/libsvn_delta/cancel.c
new file mode 100644
index 0000000..89995a8
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/cancel.c
@@ -0,0 +1,378 @@
+/*
+ * cancel.c: Routines to support cancellation of running subversion functions.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_delta.h"
+
+struct edit_baton
+{
+ const svn_delta_editor_t *wrapped_editor;
+ void *wrapped_edit_baton;
+
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+};
+
+struct dir_baton
+{
+ void *edit_baton;
+ void *wrapped_dir_baton;
+};
+
+struct file_baton
+{
+ void *edit_baton;
+ void *wrapped_file_baton;
+};
+
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
+ target_revision,
+ pool);
+}
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *dir_baton = apr_palloc(pool, sizeof(*dir_baton));
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
+ base_revision,
+ pool,
+ &dir_baton->wrapped_dir_baton));
+
+ dir_baton->edit_baton = edit_baton;
+
+ *root_baton = dir_baton;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t base_revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->delete_entry(path,
+ base_revision,
+ pb->wrapped_dir_baton,
+ pool);
+}
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *b = apr_palloc(pool, sizeof(*b));
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ SVN_ERR(eb->wrapped_editor->add_directory(path,
+ pb->wrapped_dir_baton,
+ copyfrom_path,
+ copyfrom_revision,
+ pool,
+ &b->wrapped_dir_baton));
+
+ b->edit_baton = eb;
+ *child_baton = b;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *db = apr_palloc(pool, sizeof(*db));
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ SVN_ERR(eb->wrapped_editor->open_directory(path,
+ pb->wrapped_dir_baton,
+ base_revision,
+ pool,
+ &db->wrapped_dir_baton));
+
+ db->edit_baton = eb;
+ *child_baton = db;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb = apr_palloc(pool, sizeof(*fb));
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ SVN_ERR(eb->wrapped_editor->add_file(path,
+ pb->wrapped_dir_baton,
+ copyfrom_path,
+ copyfrom_revision,
+ pool,
+ &fb->wrapped_file_baton));
+
+ fb->edit_baton = eb;
+ *file_baton = fb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb = apr_palloc(pool, sizeof(*fb));
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ SVN_ERR(eb->wrapped_editor->open_file(path,
+ pb->wrapped_dir_baton,
+ base_revision,
+ pool,
+ &fb->wrapped_file_baton));
+
+ fb->edit_baton = eb;
+ *file_baton = fb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->apply_textdelta(fb->wrapped_file_baton,
+ base_checksum,
+ pool,
+ handler,
+ handler_baton);
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->close_file(fb->wrapped_file_baton,
+ text_checksum, pool);
+}
+
+static svn_error_t *
+absent_file(const char *path,
+ void *file_baton,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->absent_file(path, fb->wrapped_file_baton,
+ pool);
+}
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->close_directory(db->wrapped_dir_baton, pool);
+}
+
+static svn_error_t *
+absent_directory(const char *path,
+ void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->absent_directory(path, db->wrapped_dir_baton,
+ pool);
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->change_file_prop(fb->wrapped_file_baton,
+ name, value, pool);
+}
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->change_dir_prop(db->wrapped_dir_baton,
+ name,
+ value,
+ pool);
+}
+
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
+}
+
+static svn_error_t *
+abort_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ return eb->wrapped_editor->abort_edit(eb->wrapped_edit_baton, pool);
+}
+
+svn_error_t *
+svn_delta_get_cancellation_editor(svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_edit_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool)
+{
+ if (cancel_func)
+ {
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
+ struct edit_baton *eb = apr_palloc(pool, sizeof(*eb));
+
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->open_root = open_root;
+ tree_editor->delete_entry = delete_entry;
+ tree_editor->add_directory = add_directory;
+ tree_editor->open_directory = open_directory;
+ tree_editor->change_dir_prop = change_dir_prop;
+ tree_editor->close_directory = close_directory;
+ tree_editor->absent_directory = absent_directory;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->absent_file = absent_file;
+ tree_editor->close_edit = close_edit;
+ tree_editor->abort_edit = abort_edit;
+
+ eb->wrapped_editor = wrapped_editor;
+ eb->wrapped_edit_baton = wrapped_edit_baton;
+ eb->cancel_func = cancel_func;
+ eb->cancel_baton = cancel_baton;
+
+ *editor = tree_editor;
+ *edit_baton = eb;
+ }
+ else
+ {
+ *editor = wrapped_editor;
+ *edit_baton = wrapped_edit_baton;
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/compat.c b/contrib/subversion/subversion/libsvn_delta/compat.c
new file mode 100644
index 0000000..8d315d1
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/compat.c
@@ -0,0 +1,2010 @@
+/*
+ * compat.c : Wrappers and callbacks for compatibility.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <stddef.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_delta.h"
+#include "svn_sorts.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "svn_private_config.h"
+
+#include "private/svn_delta_private.h"
+
+
+struct file_rev_handler_wrapper_baton {
+ void *baton;
+ svn_file_rev_handler_old_t handler;
+};
+
+/* This implements svn_file_rev_handler_t. */
+static svn_error_t *
+file_rev_handler_wrapper(void *baton,
+ const char *path,
+ svn_revnum_t rev,
+ apr_hash_t *rev_props,
+ svn_boolean_t result_of_merge,
+ svn_txdelta_window_handler_t *delta_handler,
+ void **delta_baton,
+ apr_array_header_t *prop_diffs,
+ apr_pool_t *pool)
+{
+ struct file_rev_handler_wrapper_baton *fwb = baton;
+
+ if (fwb->handler)
+ return fwb->handler(fwb->baton,
+ path,
+ rev,
+ rev_props,
+ delta_handler,
+ delta_baton,
+ prop_diffs,
+ pool);
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_compat_wrap_file_rev_handler(svn_file_rev_handler_t *handler2,
+ void **handler2_baton,
+ svn_file_rev_handler_old_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ struct file_rev_handler_wrapper_baton *fwb = apr_pcalloc(pool, sizeof(*fwb));
+
+ /* Set the user provided old format callback in the baton. */
+ fwb->baton = handler_baton;
+ fwb->handler = handler;
+
+ *handler2_baton = fwb;
+ *handler2 = file_rev_handler_wrapper;
+}
+
+
+/* The following code maps the calls to a traditional delta editor to an
+ * Editorv2 editor. It does this by keeping track of a lot of state, and
+ * then communicating that state to Ev2 upon closure of the file or dir (or
+ * edit). Note that Ev2 calls add_symlink() and alter_symlink() are not
+ * present in the delta editor paradigm, so we never call them.
+ *
+ * The general idea here is that we have to see *all* the actions on a node's
+ * parent before we can process that node, which means we need to buffer a
+ * large amount of information in the dir batons, and then process it in the
+ * close_directory() handler.
+ *
+ * There are a few ways we alter the callback stream. One is when unlocking
+ * paths. To tell a client a path should be unlocked, the server sends a
+ * prop-del for the SVN_PROP_ENTRY_LOCK_TOKEN property. This causes problems,
+ * since the client doesn't have this property in the first place, but the
+ * deletion has side effects (unlike deleting a non-existent regular property
+ * would). To solve this, we introduce *another* function into the API, not
+ * a part of the Ev2 callbacks, but a companion which is used to register
+ * the unlock of a path. See ev2_change_file_prop() for implemenation
+ * details.
+ */
+
+struct ev2_edit_baton
+{
+ svn_editor_t *editor;
+
+ apr_hash_t *changes; /* REPOS_RELPATH -> struct change_node */
+
+ apr_array_header_t *path_order;
+ int paths_processed;
+
+ /* For calculating relpaths from Ev1 copyfrom urls. */
+ const char *repos_root;
+ const char *base_relpath;
+
+ apr_pool_t *edit_pool;
+ struct svn_delta__extra_baton *exb;
+ svn_boolean_t closed;
+
+ svn_boolean_t *found_abs_paths; /* Did we strip an incoming '/' from the
+ paths? */
+
+ svn_delta_fetch_props_func_t fetch_props_func;
+ void *fetch_props_baton;
+
+ svn_delta_fetch_base_func_t fetch_base_func;
+ void *fetch_base_baton;
+
+ svn_delta__unlock_func_t do_unlock;
+ void *unlock_baton;
+};
+
+struct ev2_dir_baton
+{
+ struct ev2_edit_baton *eb;
+ const char *path;
+ svn_revnum_t base_revision;
+
+ const char *copyfrom_relpath;
+ svn_revnum_t copyfrom_rev;
+};
+
+struct ev2_file_baton
+{
+ struct ev2_edit_baton *eb;
+ const char *path;
+ svn_revnum_t base_revision;
+ const char *delta_base;
+};
+
+enum restructure_action_t
+{
+ RESTRUCTURE_NONE = 0,
+ RESTRUCTURE_ADD, /* add the node, maybe replacing. maybe copy */
+ RESTRUCTURE_ADD_ABSENT, /* add an absent node, possibly replacing */
+ RESTRUCTURE_DELETE /* delete this node */
+};
+
+/* Records everything about how this node is to be changed. */
+struct change_node
+{
+ /* what kind of (tree) restructure is occurring at this node? */
+ enum restructure_action_t action;
+
+ svn_node_kind_t kind; /* the NEW kind of this node */
+
+ /* We need two revisions: one to specify the revision we are altering,
+ and a second to specify the revision to delete/replace. These are
+ mutually exclusive, but they need to be separate to ensure we don't
+ confuse the operation on this node. For example, we may delete a
+ node and replace it we use DELETING for REPLACES_REV, and ignore
+ the value placed into CHANGING when properties were set/changed
+ on the new node. Or we simply change a node (setting CHANGING),
+ and DELETING remains SVN_INVALID_REVNUM, indicating we are not
+ attempting to replace a node. */
+ svn_revnum_t changing;
+ svn_revnum_t deleting;
+
+ apr_hash_t *props; /* new/final set of props to apply */
+
+ const char *contents_abspath; /* file containing new fulltext */
+ svn_checksum_t *checksum; /* checksum of new fulltext */
+
+ /* If COPYFROM_PATH is not NULL, then copy PATH@REV to this node.
+ RESTRUCTURE must be RESTRUCTURE_ADD. */
+ const char *copyfrom_path;
+ svn_revnum_t copyfrom_rev;
+
+ /* Record whether an incoming propchange unlocked this node. */
+ svn_boolean_t unlock;
+};
+
+
+static struct change_node *
+locate_change(struct ev2_edit_baton *eb,
+ const char *relpath)
+{
+ struct change_node *change = svn_hash_gets(eb->changes, relpath);
+
+ if (change != NULL)
+ return change;
+
+ /* Shift RELPATH into the proper pool, and record the observed order. */
+ relpath = apr_pstrdup(eb->edit_pool, relpath);
+ APR_ARRAY_PUSH(eb->path_order, const char *) = relpath;
+
+ /* Return an empty change. Callers will tweak as needed. */
+ change = apr_pcalloc(eb->edit_pool, sizeof(*change));
+ change->changing = SVN_INVALID_REVNUM;
+ change->deleting = SVN_INVALID_REVNUM;
+
+ svn_hash_sets(eb->changes, relpath, change);
+
+ return change;
+}
+
+
+static svn_error_t *
+apply_propedit(struct ev2_edit_baton *eb,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t base_revision,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct change_node *change = locate_change(eb, relpath);
+
+ SVN_ERR_ASSERT(change->kind == svn_node_unknown || change->kind == kind);
+ change->kind = kind;
+
+ /* We're now changing the node. Record the revision. */
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing)
+ || change->changing == base_revision);
+ change->changing = base_revision;
+
+ if (change->props == NULL)
+ {
+ /* Fetch the original set of properties. We'll apply edits to create
+ the new/target set of properties.
+
+ If this is a copied/moved now, then the original properties come
+ from there. If the node has been added, it starts with empty props.
+ Otherwise, we get the properties from BASE. */
+
+ if (change->copyfrom_path)
+ SVN_ERR(eb->fetch_props_func(&change->props,
+ eb->fetch_props_baton,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ eb->edit_pool, scratch_pool));
+ else if (change->action == RESTRUCTURE_ADD)
+ change->props = apr_hash_make(eb->edit_pool);
+ else
+ SVN_ERR(eb->fetch_props_func(&change->props,
+ eb->fetch_props_baton,
+ relpath, base_revision,
+ eb->edit_pool, scratch_pool));
+ }
+
+ if (value == NULL)
+ svn_hash_sets(change->props, name, NULL);
+ else
+ svn_hash_sets(change->props,
+ apr_pstrdup(eb->edit_pool, name),
+ svn_string_dup(value, eb->edit_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Find all the paths which are immediate children of PATH and return their
+ basenames in a list. */
+static apr_array_header_t *
+get_children(struct ev2_edit_baton *eb,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *children = apr_array_make(pool, 1, sizeof(const char *));
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, eb->changes); hi; hi = apr_hash_next(hi))
+ {
+ const char *repos_relpath = svn__apr_hash_index_key(hi);
+ const char *child;
+
+ /* Find potential children. */
+ child = svn_relpath_skip_ancestor(path, repos_relpath);
+ if (!child || !*child)
+ continue;
+
+ /* If we have a path separator, it's a deep child, so just ignore it.
+ ### Is there an API we should be using for this? */
+ if (strchr(child, '/') != NULL)
+ continue;
+
+ APR_ARRAY_PUSH(children, const char *) = child;
+ }
+
+ return children;
+}
+
+
+static svn_error_t *
+process_actions(struct ev2_edit_baton *eb,
+ const char *repos_relpath,
+ const struct change_node *change,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *props = NULL;
+ svn_stream_t *contents = NULL;
+ svn_checksum_t *checksum = NULL;
+ svn_node_kind_t kind = svn_node_unknown;
+
+ SVN_ERR_ASSERT(change != NULL);
+
+ if (change->unlock)
+ SVN_ERR(eb->do_unlock(eb->unlock_baton, repos_relpath, scratch_pool));
+
+ if (change->action == RESTRUCTURE_DELETE)
+ {
+ /* If the action was left as RESTRUCTURE_DELETE, then a
+ replacement is not occurring. Just do the delete and bail. */
+ SVN_ERR(svn_editor_delete(eb->editor, repos_relpath,
+ change->deleting));
+
+ /* No further work possible on this node. */
+ return SVN_NO_ERROR;
+ }
+ if (change->action == RESTRUCTURE_ADD_ABSENT)
+ {
+ SVN_ERR(svn_editor_add_absent(eb->editor, repos_relpath,
+ change->kind, change->deleting));
+
+ /* No further work possible on this node. */
+ return SVN_NO_ERROR;
+ }
+
+ if (change->contents_abspath != NULL)
+ {
+ /* We can only set text on files. */
+ /* ### validate we aren't overwriting KIND? */
+ kind = svn_node_file;
+
+ /* ### the checksum might be in CHANGE->CHECKSUM */
+ SVN_ERR(svn_io_file_checksum2(&checksum, change->contents_abspath,
+ svn_checksum_sha1, scratch_pool));
+ SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ if (change->props != NULL)
+ {
+ /* ### validate we aren't overwriting KIND? */
+ kind = change->kind;
+ props = change->props;
+ }
+
+ if (change->action == RESTRUCTURE_ADD)
+ {
+ /* An add might be a replace. Grab the revnum we're replacing. */
+ svn_revnum_t replaces_rev = change->deleting;
+
+ kind = change->kind;
+
+ if (change->copyfrom_path != NULL)
+ {
+ SVN_ERR(svn_editor_copy(eb->editor, change->copyfrom_path,
+ change->copyfrom_rev,
+ repos_relpath, replaces_rev));
+ /* Fall through to possibly make changes post-copy. */
+ }
+ else
+ {
+ /* If no properties were defined, then use an empty set. */
+ if (props == NULL)
+ props = apr_hash_make(scratch_pool);
+
+ if (kind == svn_node_dir)
+ {
+ const apr_array_header_t *children;
+
+ children = get_children(eb, repos_relpath, scratch_pool);
+ SVN_ERR(svn_editor_add_directory(eb->editor, repos_relpath,
+ children, props,
+ replaces_rev));
+ }
+ else
+ {
+ /* If this file was added, but apply_txdelta() was not
+ called (ie. no CONTENTS_ABSPATH), then we're adding
+ an empty file. */
+ if (change->contents_abspath == NULL)
+ {
+ contents = svn_stream_empty(scratch_pool);
+ checksum = svn_checksum_empty_checksum(svn_checksum_sha1,
+ scratch_pool);
+ }
+
+ SVN_ERR(svn_editor_add_file(eb->editor, repos_relpath,
+ checksum, contents, props,
+ replaces_rev));
+ }
+
+ /* No further work possible on this node. */
+ return SVN_NO_ERROR;
+ }
+ }
+
+#if 0
+ /* There *should* be work for this node. But it seems that isn't true
+ in some cases. Future investigation... */
+ SVN_ERR_ASSERT(props || contents);
+#endif
+ if (props || contents)
+ {
+ /* Changes to properties or content should have indicated the revision
+ it was intending to change.
+
+ Oop. Not true. The node may be locally-added. */
+#if 0
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(change->changing));
+#endif
+
+ /* ### we need to gather up the target set of children */
+
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_editor_alter_directory(eb->editor, repos_relpath,
+ change->changing, NULL, props));
+ else
+ SVN_ERR(svn_editor_alter_file(eb->editor, repos_relpath,
+ change->changing, props,
+ checksum, contents));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+run_ev2_actions(struct ev2_edit_baton *eb,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* Possibly pick up where we left off. Ocassionally, we do some of these
+ as part of close_edit() and then some more as part of abort_edit() */
+ for (; eb->paths_processed < eb->path_order->nelts; ++eb->paths_processed)
+ {
+ const char *repos_relpath = APR_ARRAY_IDX(eb->path_order,
+ eb->paths_processed,
+ const char *);
+ const struct change_node *change = svn_hash_gets(eb->changes,
+ repos_relpath);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(process_actions(eb, repos_relpath, change, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static const char *
+map_to_repos_relpath(struct ev2_edit_baton *eb,
+ const char *path_or_url,
+ apr_pool_t *result_pool)
+{
+ if (svn_path_is_url(path_or_url))
+ {
+ return svn_uri_skip_ancestor(eb->repos_root, path_or_url, result_pool);
+ }
+ else
+ {
+ return svn_relpath_join(eb->base_relpath,
+ path_or_url[0] == '/'
+ ? path_or_url + 1 : path_or_url,
+ result_pool);
+ }
+}
+
+
+static svn_error_t *
+ev2_set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_edit_baton *eb = edit_baton;
+
+ if (eb->exb->target_revision)
+ SVN_ERR(eb->exb->target_revision(eb->exb->baton, target_revision,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **root_baton)
+{
+ struct ev2_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db));
+ struct ev2_edit_baton *eb = edit_baton;
+
+ db->eb = eb;
+ db->path = apr_pstrdup(eb->edit_pool, eb->base_relpath);
+ db->base_revision = base_revision;
+
+ *root_baton = db;
+
+ if (eb->exb->start_edit)
+ SVN_ERR(eb->exb->start_edit(eb->exb->baton, base_revision));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_dir_baton *pb = parent_baton;
+ svn_revnum_t base_revision;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ struct change_node *change = locate_change(pb->eb, relpath);
+
+ if (SVN_IS_VALID_REVNUM(revision))
+ base_revision = revision;
+ else
+ base_revision = pb->base_revision;
+
+ SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
+ change->action = RESTRUCTURE_DELETE;
+
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->deleting)
+ || change->deleting == base_revision);
+ change->deleting = base_revision;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *result_pool,
+ void **child_baton)
+{
+ /* ### fix this? */
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev2_dir_baton *pb = parent_baton;
+ struct ev2_dir_baton *cb = apr_pcalloc(result_pool, sizeof(*cb));
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ struct change_node *change = locate_change(pb->eb, relpath);
+
+ /* ### assert that RESTRUCTURE is NONE or DELETE? */
+ change->action = RESTRUCTURE_ADD;
+ change->kind = svn_node_dir;
+
+ cb->eb = pb->eb;
+ cb->path = apr_pstrdup(result_pool, relpath);
+ cb->base_revision = pb->base_revision;
+ *child_baton = cb;
+
+ if (!copyfrom_path)
+ {
+ if (pb->copyfrom_relpath)
+ {
+ const char *name = svn_relpath_basename(relpath, scratch_pool);
+ cb->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name,
+ result_pool);
+ cb->copyfrom_rev = pb->copyfrom_rev;
+ }
+ }
+ else
+ {
+ /* A copy */
+
+ change->copyfrom_path = map_to_repos_relpath(pb->eb, copyfrom_path,
+ pb->eb->edit_pool);
+ change->copyfrom_rev = copyfrom_revision;
+
+ cb->copyfrom_relpath = change->copyfrom_path;
+ cb->copyfrom_rev = change->copyfrom_rev;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **child_baton)
+{
+ /* ### fix this? */
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev2_dir_baton *pb = parent_baton;
+ struct ev2_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db));
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+
+ db->eb = pb->eb;
+ db->path = apr_pstrdup(result_pool, relpath);
+ db->base_revision = base_revision;
+
+ if (pb->copyfrom_relpath)
+ {
+ /* We are inside a copy. */
+ const char *name = svn_relpath_basename(relpath, scratch_pool);
+
+ db->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name,
+ result_pool);
+ db->copyfrom_rev = pb->copyfrom_rev;
+ }
+
+ *child_baton = db;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_dir_baton *db = dir_baton;
+
+ SVN_ERR(apply_propedit(db->eb, db->path, svn_node_dir, db->base_revision,
+ name, value, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_close_directory(void *dir_baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_absent_directory(const char *path,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_dir_baton *pb = parent_baton;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ struct change_node *change = locate_change(pb->eb, relpath);
+
+ /* ### assert that RESTRUCTURE is NONE or DELETE? */
+ change->action = RESTRUCTURE_ADD_ABSENT;
+ change->kind = svn_node_dir;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *result_pool,
+ void **file_baton)
+{
+ /* ### fix this? */
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
+ struct ev2_dir_baton *pb = parent_baton;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ struct change_node *change = locate_change(pb->eb, relpath);
+
+ /* ### assert that RESTRUCTURE is NONE or DELETE? */
+ change->action = RESTRUCTURE_ADD;
+ change->kind = svn_node_file;
+
+ fb->eb = pb->eb;
+ fb->path = apr_pstrdup(result_pool, relpath);
+ fb->base_revision = pb->base_revision;
+ *file_baton = fb;
+
+ if (!copyfrom_path)
+ {
+ /* Don't bother fetching the base, as in an add we don't have a base. */
+ fb->delta_base = NULL;
+ }
+ else
+ {
+ /* A copy */
+
+ change->copyfrom_path = map_to_repos_relpath(fb->eb, copyfrom_path,
+ fb->eb->edit_pool);
+ change->copyfrom_rev = copyfrom_revision;
+
+ SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
+ fb->eb->fetch_base_baton,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **file_baton)
+{
+ /* ### fix this? */
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
+ struct ev2_dir_baton *pb = parent_baton;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+
+ fb->eb = pb->eb;
+ fb->path = apr_pstrdup(result_pool, relpath);
+ fb->base_revision = base_revision;
+
+ if (pb->copyfrom_relpath)
+ {
+ /* We're in a copied directory, so the delta base is going to be
+ based up on the copy source. */
+ const char *name = svn_relpath_basename(relpath, scratch_pool);
+ const char *copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath,
+ name,
+ scratch_pool);
+
+ SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
+ fb->eb->fetch_base_baton,
+ copyfrom_relpath, pb->copyfrom_rev,
+ result_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
+ fb->eb->fetch_base_baton,
+ relpath, base_revision,
+ result_pool, scratch_pool));
+ }
+
+ *file_baton = fb;
+ return SVN_NO_ERROR;
+}
+
+struct handler_baton
+{
+ svn_txdelta_window_handler_t apply_handler;
+ void *apply_baton;
+
+ svn_stream_t *source;
+
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct handler_baton *hb = baton;
+ svn_error_t *err;
+
+ err = hb->apply_handler(window, hb->apply_baton);
+ if (window != NULL && !err)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_stream_close(hb->source));
+
+ svn_pool_destroy(hb->pool);
+
+ return svn_error_trace(err);
+}
+
+
+static svn_error_t *
+ev2_apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *result_pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct ev2_file_baton *fb = file_baton;
+ apr_pool_t *handler_pool = svn_pool_create(fb->eb->edit_pool);
+ struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
+ struct change_node *change;
+ svn_stream_t *target;
+ /* ### fix this. for now, we know this has a "short" lifetime. */
+ apr_pool_t *scratch_pool = handler_pool;
+
+ change = locate_change(fb->eb, fb->path);
+ SVN_ERR_ASSERT(change->contents_abspath == NULL);
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing)
+ || change->changing == fb->base_revision);
+ change->changing = fb->base_revision;
+
+ if (! fb->delta_base)
+ hb->source = svn_stream_empty(handler_pool);
+ else
+ SVN_ERR(svn_stream_open_readonly(&hb->source, fb->delta_base, handler_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_stream_open_unique(&target, &change->contents_abspath, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ fb->eb->edit_pool, scratch_pool));
+
+ svn_txdelta_apply(hb->source, target,
+ NULL, NULL,
+ handler_pool,
+ &hb->apply_handler, &hb->apply_baton);
+
+ hb->pool = handler_pool;
+
+ *handler_baton = hb;
+ *handler = window_handler;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_file_baton *fb = file_baton;
+
+ if (!strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) && value == NULL)
+ {
+ /* We special case the lock token propery deletion, which is the
+ server's way of telling the client to unlock the path. */
+
+ /* ### this duplicates much of apply_propedit(). fix in future. */
+ const char *relpath = map_to_repos_relpath(fb->eb, fb->path,
+ scratch_pool);
+ struct change_node *change = locate_change(fb->eb, relpath);
+
+ change->unlock = TRUE;
+ }
+
+ SVN_ERR(apply_propedit(fb->eb, fb->path, svn_node_file, fb->base_revision,
+ name, value, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_absent_file(const char *path,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_dir_baton *pb = parent_baton;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ struct change_node *change = locate_change(pb->eb, relpath);
+
+ /* ### assert that RESTRUCTURE is NONE or DELETE? */
+ change->action = RESTRUCTURE_ADD_ABSENT;
+ change->kind = svn_node_file;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_close_edit(void *edit_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_edit_baton *eb = edit_baton;
+
+ SVN_ERR(run_ev2_actions(edit_baton, scratch_pool));
+ eb->closed = TRUE;
+ return svn_error_trace(svn_editor_complete(eb->editor));
+}
+
+static svn_error_t *
+ev2_abort_edit(void *edit_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_edit_baton *eb = edit_baton;
+
+ SVN_ERR(run_ev2_actions(edit_baton, scratch_pool));
+ if (!eb->closed)
+ return svn_error_trace(svn_editor_abort(eb->editor));
+ else
+ return SVN_NO_ERROR;
+}
+
+/* Return a svn_delta_editor_t * in DEDITOR, with an accompanying baton in
+ * DEDITOR_BATON, which will drive EDITOR. These will both be
+ * allocated in RESULT_POOL, which may become large and long-lived;
+ * SCRATCH_POOL is used for temporary allocations.
+ *
+ * The other parameters are as follows:
+ * - UNLOCK_FUNC / UNLOCK_BATON: A callback / baton which will be called
+ * when an unlocking action is received.
+ * - FOUND_ABS_PATHS: A pointer to a boolean flag which will be set if
+ * this shim determines that it is receiving absolute paths.
+ * - FETCH_PROPS_FUNC / FETCH_PROPS_BATON: A callback / baton pair which
+ * will be used by the shim handlers if they need to determine the
+ * existing properties on a path.
+ * - FETCH_BASE_FUNC / FETCH_BASE_BATON: A callback / baton pair which will
+ * be used by the shims handlers if they need to determine the base
+ * text of a path. It should only be invoked for files.
+ * - EXB: An 'extra baton' which is used to communicate between the shims.
+ * Its callbacks should be invoked at the appropriate time by this
+ * shim.
+ */
+svn_error_t *
+svn_delta__delta_from_editor(const svn_delta_editor_t **deditor,
+ void **dedit_baton,
+ svn_editor_t *editor,
+ svn_delta__unlock_func_t unlock_func,
+ void *unlock_baton,
+ svn_boolean_t *found_abs_paths,
+ const char *repos_root,
+ const char *base_relpath,
+ svn_delta_fetch_props_func_t fetch_props_func,
+ void *fetch_props_baton,
+ svn_delta_fetch_base_func_t fetch_base_func,
+ void *fetch_base_baton,
+ struct svn_delta__extra_baton *exb,
+ apr_pool_t *pool)
+{
+ /* Static 'cause we don't want it to be on the stack. */
+ static svn_delta_editor_t delta_editor = {
+ ev2_set_target_revision,
+ ev2_open_root,
+ ev2_delete_entry,
+ ev2_add_directory,
+ ev2_open_directory,
+ ev2_change_dir_prop,
+ ev2_close_directory,
+ ev2_absent_directory,
+ ev2_add_file,
+ ev2_open_file,
+ ev2_apply_textdelta,
+ ev2_change_file_prop,
+ ev2_close_file,
+ ev2_absent_file,
+ ev2_close_edit,
+ ev2_abort_edit
+ };
+ struct ev2_edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
+
+ if (!base_relpath)
+ base_relpath = "";
+ else if (base_relpath[0] == '/')
+ base_relpath += 1;
+
+ eb->editor = editor;
+ eb->changes = apr_hash_make(pool);
+ eb->path_order = apr_array_make(pool, 1, sizeof(const char *));
+ eb->edit_pool = pool;
+ eb->found_abs_paths = found_abs_paths;
+ *eb->found_abs_paths = FALSE;
+ eb->exb = exb;
+ eb->repos_root = apr_pstrdup(pool, repos_root);
+ eb->base_relpath = apr_pstrdup(pool, base_relpath);
+
+ eb->fetch_props_func = fetch_props_func;
+ eb->fetch_props_baton = fetch_props_baton;
+
+ eb->fetch_base_func = fetch_base_func;
+ eb->fetch_base_baton = fetch_base_baton;
+
+ eb->do_unlock = unlock_func;
+ eb->unlock_baton = unlock_baton;
+
+ *dedit_baton = eb;
+ *deditor = &delta_editor;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ### note the similarity to struct change_node. these structures will
+ ### be combined in the future. */
+struct operation {
+ /* ### leave these two here for now. still used. */
+ svn_revnum_t base_revision;
+ void *baton;
+};
+
+struct editor_baton
+{
+ const svn_delta_editor_t *deditor;
+ void *dedit_baton;
+
+ svn_delta_fetch_kind_func_t fetch_kind_func;
+ void *fetch_kind_baton;
+
+ svn_delta_fetch_props_func_t fetch_props_func;
+ void *fetch_props_baton;
+
+ struct operation root;
+ svn_boolean_t *make_abs_paths;
+ const char *repos_root;
+ const char *base_relpath;
+
+ /* REPOS_RELPATH -> struct change_node * */
+ apr_hash_t *changes;
+
+ apr_pool_t *edit_pool;
+};
+
+
+/* Insert a new change for RELPATH, or return an existing one. */
+static struct change_node *
+insert_change(const char *relpath,
+ apr_hash_t *changes)
+{
+ apr_pool_t *result_pool;
+ struct change_node *change;
+
+ change = svn_hash_gets(changes, relpath);
+ if (change != NULL)
+ return change;
+
+ result_pool = apr_hash_pool_get(changes);
+
+ /* Return an empty change. Callers will tweak as needed. */
+ change = apr_pcalloc(result_pool, sizeof(*change));
+ change->changing = SVN_INVALID_REVNUM;
+ change->deleting = SVN_INVALID_REVNUM;
+
+ svn_hash_sets(changes, apr_pstrdup(result_pool, relpath), change);
+
+ return change;
+}
+
+
+/* This implements svn_editor_cb_add_directory_t */
+static svn_error_t *
+add_directory_cb(void *baton,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ change->action = RESTRUCTURE_ADD;
+ change->kind = svn_node_dir;
+ change->deleting = replaces_rev;
+ change->props = svn_prop_hash_dup(props, eb->edit_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_add_file_t */
+static svn_error_t *
+add_file_cb(void *baton,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ const char *tmp_filename;
+ svn_stream_t *tmp_stream;
+ svn_checksum_t *md5_checksum;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ /* We may need to re-checksum these contents */
+ if (!(checksum && checksum->kind == svn_checksum_md5))
+ contents = svn_stream_checksummed2(contents, &md5_checksum, NULL,
+ svn_checksum_md5, TRUE, scratch_pool);
+ else
+ md5_checksum = (svn_checksum_t *)checksum;
+
+ /* Spool the contents to a tempfile, and provide that to the driver. */
+ SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ eb->edit_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL, scratch_pool));
+
+ change->action = RESTRUCTURE_ADD;
+ change->kind = svn_node_file;
+ change->deleting = replaces_rev;
+ change->props = svn_prop_hash_dup(props, eb->edit_pool);
+ change->contents_abspath = tmp_filename;
+ change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_add_symlink_t */
+static svn_error_t *
+add_symlink_cb(void *baton,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+#if 0
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ change->action = RESTRUCTURE_ADD;
+ change->kind = svn_node_symlink;
+ change->deleting = replaces_rev;
+ change->props = svn_prop_hash_dup(props, eb->edit_pool);
+ /* ### target */
+#endif
+
+ SVN__NOT_IMPLEMENTED();
+}
+
+/* This implements svn_editor_cb_add_absent_t */
+static svn_error_t *
+add_absent_cb(void *baton,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ change->action = RESTRUCTURE_ADD_ABSENT;
+ change->kind = kind;
+ change->deleting = replaces_rev;
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_alter_directory_t */
+static svn_error_t *
+alter_directory_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ /* ### should we verify the kind is truly a directory? */
+
+ /* ### do we need to do anything with CHILDREN? */
+
+ /* Note: this node may already have information in CHANGE as a result
+ of an earlier copy/move operation. */
+ change->kind = svn_node_dir;
+ change->changing = revision;
+ change->props = svn_prop_hash_dup(props, eb->edit_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_alter_file_t */
+static svn_error_t *
+alter_file_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ const char *tmp_filename;
+ svn_stream_t *tmp_stream;
+ svn_checksum_t *md5_checksum;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ /* ### should we verify the kind is truly a file? */
+
+ if (contents)
+ {
+ /* We may need to re-checksum these contents */
+ if (!(checksum && checksum->kind == svn_checksum_md5))
+ contents = svn_stream_checksummed2(contents, &md5_checksum, NULL,
+ svn_checksum_md5, TRUE,
+ scratch_pool);
+ else
+ md5_checksum = (svn_checksum_t *)checksum;
+
+ /* Spool the contents to a tempfile, and provide that to the driver. */
+ SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ eb->edit_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL,
+ scratch_pool));
+ }
+
+ /* Note: this node may already have information in CHANGE as a result
+ of an earlier copy/move operation. */
+
+ change->kind = svn_node_file;
+ change->changing = revision;
+ if (props != NULL)
+ change->props = svn_prop_hash_dup(props, eb->edit_pool);
+ if (contents != NULL)
+ {
+ change->contents_abspath = tmp_filename;
+ change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_alter_symlink_t */
+static svn_error_t *
+alter_symlink_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target,
+ apr_pool_t *scratch_pool)
+{
+ /* ### should we verify the kind is truly a symlink? */
+
+ /* ### do something */
+
+ SVN__NOT_IMPLEMENTED();
+}
+
+/* This implements svn_editor_cb_delete_t */
+static svn_error_t *
+delete_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ change->action = RESTRUCTURE_DELETE;
+ /* change->kind = svn_node_unknown; */
+ change->deleting = revision;
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_copy_t */
+static svn_error_t *
+copy_cb(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(dst_relpath, eb->changes);
+
+ change->action = RESTRUCTURE_ADD;
+ /* change->kind = svn_node_unknown; */
+ change->deleting = replaces_rev;
+ change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
+ change->copyfrom_rev = src_revision;
+
+ /* We need the source's kind to know whether to call add_directory()
+ or add_file() later on. */
+ SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ scratch_pool));
+
+ /* Note: this node may later have alter_*() called on it. */
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_move_t */
+static svn_error_t *
+move_cb(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change;
+
+ /* Remap a move into a DELETE + COPY. */
+
+ change = insert_change(src_relpath, eb->changes);
+ change->action = RESTRUCTURE_DELETE;
+ /* change->kind = svn_node_unknown; */
+ change->deleting = src_revision;
+
+ change = insert_change(dst_relpath, eb->changes);
+ change->action = RESTRUCTURE_ADD;
+ /* change->kind = svn_node_unknown; */
+ change->deleting = replaces_rev;
+ change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
+ change->copyfrom_rev = src_revision;
+
+ /* We need the source's kind to know whether to call add_directory()
+ or add_file() later on. */
+ SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ scratch_pool));
+
+ /* Note: this node may later have alter_*() called on it. */
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_rotate_t */
+static svn_error_t *
+rotate_cb(void *baton,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions,
+ apr_pool_t *scratch_pool)
+{
+ SVN__NOT_IMPLEMENTED();
+}
+
+
+static int
+count_components(const char *relpath)
+{
+ int count = 1;
+ const char *slash = strchr(relpath, '/');
+
+ while (slash != NULL)
+ {
+ ++count;
+ slash = strchr(slash + 1, '/');
+ }
+ return count;
+}
+
+
+static int
+sort_deletes_first(const svn_sort__item_t *item1,
+ const svn_sort__item_t *item2)
+{
+ const char *relpath1 = item1->key;
+ const char *relpath2 = item2->key;
+ const struct change_node *change1 = item1->value;
+ const struct change_node *change2 = item2->value;
+ const char *slash1;
+ const char *slash2;
+ ptrdiff_t len1;
+ ptrdiff_t len2;
+
+ /* Force the root to always sort first. Otherwise, it may look like a
+ sibling of its children (no slashes), and could get sorted *after*
+ any children that get deleted. */
+ if (*relpath1 == '\0')
+ return -1;
+ if (*relpath2 == '\0')
+ return 1;
+
+ /* Are these two items siblings? The 'if' statement tests if they are
+ siblings in the root directory, or that slashes were found in both
+ paths, that the length of the paths to those slashes match, and that
+ the path contents up to those slashes also match. */
+ slash1 = strrchr(relpath1, '/');
+ slash2 = strrchr(relpath2, '/');
+ if ((slash1 == NULL && slash2 == NULL)
+ || (slash1 != NULL
+ && slash2 != NULL
+ && (len1 = slash1 - relpath1) == (len2 = slash2 - relpath2)
+ && memcmp(relpath1, relpath2, len1) == 0))
+ {
+ if (change1->action == RESTRUCTURE_DELETE)
+ {
+ if (change2->action == RESTRUCTURE_DELETE)
+ {
+ /* If both items are being deleted, then we don't care about
+ the order. State they are equal. */
+ return 0;
+ }
+
+ /* ITEM1 is being deleted. Sort it before the surviving item. */
+ return -1;
+ }
+ if (change2->action == RESTRUCTURE_DELETE)
+ /* ITEM2 is being deleted. Sort it before the surviving item. */
+ return 1;
+
+ /* Normally, we don't care about the ordering of two siblings. However,
+ if these siblings are directories, then we need to provide an
+ ordering so that the quicksort algorithm will further sort them
+ relative to the maybe-directory's children.
+
+ Without this additional ordering, we could see that A/B/E and A/B/F
+ are equal. And then A/B/E/child is sorted before A/B/F. But since
+ E and F are "equal", A/B/E could arrive *after* A/B/F and after the
+ A/B/E/child node. */
+
+ /* FALLTHROUGH */
+ }
+
+ /* Paths-to-be-deleted with fewer components always sort earlier.
+
+ For example, gamma will sort before E/alpha.
+
+ Without this test, E/alpha lexicographically sorts before gamma,
+ but gamma sorts before E when gamma is to be deleted. This kind of
+ ordering would place E/alpha before E. Not good.
+
+ With this test, gamma sorts before E/alpha. E and E/alpha are then
+ sorted by svn_path_compare_paths() (which places E before E/alpha). */
+ if (change1->action == RESTRUCTURE_DELETE
+ || change2->action == RESTRUCTURE_DELETE)
+ {
+ int count1 = count_components(relpath1);
+ int count2 = count_components(relpath2);
+
+ if (count1 < count2 && change1->action == RESTRUCTURE_DELETE)
+ return -1;
+ if (count1 > count2 && change2->action == RESTRUCTURE_DELETE)
+ return 1;
+ }
+
+ /* Use svn_path_compare_paths() to get correct depth-based ordering. */
+ return svn_path_compare_paths(relpath1, relpath2);
+}
+
+
+static const apr_array_header_t *
+get_sorted_paths(apr_hash_t *changes,
+ const char *base_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *items;
+ apr_array_header_t *paths;
+ int i;
+
+ /* Construct a sorted array of svn_sort__item_t structs. Within a given
+ directory, nodes that are to be deleted will appear first. */
+ items = svn_sort__hash(changes, sort_deletes_first, scratch_pool);
+
+ /* Build a new array with just the paths, trimmed to relative paths for
+ the Ev1 drive. */
+ paths = apr_array_make(scratch_pool, items->nelts, sizeof(const char *));
+ for (i = items->nelts; i--; )
+ {
+ const svn_sort__item_t *item;
+
+ item = &APR_ARRAY_IDX(items, i, const svn_sort__item_t);
+ APR_ARRAY_IDX(paths, i, const char *)
+ = svn_relpath_skip_ancestor(base_relpath, item->key);
+ }
+
+ /* We didn't use PUSH, so set the proper number of elements. */
+ paths->nelts = items->nelts;
+
+ return paths;
+}
+
+
+static svn_error_t *
+drive_ev1_props(const struct editor_baton *eb,
+ const char *repos_relpath,
+ const struct change_node *change,
+ void *node_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *old_props;
+ apr_array_header_t *propdiffs;
+ int i;
+
+ /* If there are no properties to install, then just exit. */
+ if (change->props == NULL)
+ return SVN_NO_ERROR;
+
+ if (change->copyfrom_path)
+ {
+ /* The pristine properties are from the copy/move source. */
+ SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ scratch_pool, iterpool));
+ }
+ else if (change->action == RESTRUCTURE_ADD)
+ {
+ /* Locally-added nodes have no pristine properties.
+
+ Note: we can use iterpool; this hash only needs to survive to
+ the propdiffs call, and there are no contents to preserve. */
+ old_props = apr_hash_make(iterpool);
+ }
+ else
+ {
+ /* Fetch the pristine properties for whatever we're editing. */
+ SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
+ repos_relpath, change->changing,
+ scratch_pool, iterpool));
+ }
+
+ SVN_ERR(svn_prop_diffs(&propdiffs, change->props, old_props, scratch_pool));
+
+ for (i = 0; i < propdiffs->nelts; i++)
+ {
+ /* Note: the array returned by svn_prop_diffs() is an array of
+ actual structures, not pointers to them. */
+ const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
+
+ svn_pool_clear(iterpool);
+
+ if (change->kind == svn_node_dir)
+ SVN_ERR(eb->deditor->change_dir_prop(node_baton,
+ prop->name, prop->value,
+ iterpool));
+ else
+ SVN_ERR(eb->deditor->change_file_prop(node_baton,
+ prop->name, prop->value,
+ iterpool));
+ }
+
+ /* Handle the funky unlock protocol. Note: only possibly on files. */
+ if (change->unlock)
+ {
+ SVN_ERR_ASSERT(change->kind == svn_node_file);
+ SVN_ERR(eb->deditor->change_file_prop(node_baton,
+ SVN_PROP_ENTRY_LOCK_TOKEN, NULL,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_delta_path_driver_cb_func_t */
+static svn_error_t *
+apply_change(void **dir_baton,
+ void *parent_baton,
+ void *callback_baton,
+ const char *ev1_relpath,
+ apr_pool_t *result_pool)
+{
+ /* ### fix this? */
+ apr_pool_t *scratch_pool = result_pool;
+ const struct editor_baton *eb = callback_baton;
+ const struct change_node *change;
+ const char *relpath;
+ void *file_baton = NULL;
+
+ /* Typically, we are not creating new directory batons. */
+ *dir_baton = NULL;
+
+ relpath = svn_relpath_join(eb->base_relpath, ev1_relpath, scratch_pool);
+ change = svn_hash_gets(eb->changes, relpath);
+
+ /* The callback should only be called for paths in CHANGES. */
+ SVN_ERR_ASSERT(change != NULL);
+
+ /* Are we editing the root of the tree? */
+ if (parent_baton == NULL)
+ {
+ /* The root was opened in start_edit_func() */
+ *dir_baton = eb->root.baton;
+
+ /* Only property edits are allowed on the root. */
+ SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
+ SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool));
+
+ /* No further action possible for the root. */
+ return SVN_NO_ERROR;
+ }
+
+ if (change->action == RESTRUCTURE_DELETE)
+ {
+ SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting,
+ parent_baton, scratch_pool));
+
+ /* No futher action possible for this node. */
+ return SVN_NO_ERROR;
+ }
+
+ /* If we're not deleting this node, then we should know its kind. */
+ SVN_ERR_ASSERT(change->kind != svn_node_unknown);
+
+ if (change->action == RESTRUCTURE_ADD_ABSENT)
+ {
+ if (change->kind == svn_node_dir)
+ SVN_ERR(eb->deditor->absent_directory(ev1_relpath, parent_baton,
+ scratch_pool));
+ else
+ SVN_ERR(eb->deditor->absent_file(ev1_relpath, parent_baton,
+ scratch_pool));
+
+ /* No further action possible for this node. */
+ return SVN_NO_ERROR;
+ }
+ /* RESTRUCTURE_NONE or RESTRUCTURE_ADD */
+
+ if (change->action == RESTRUCTURE_ADD)
+ {
+ const char *copyfrom_url = NULL;
+ svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* Do we have an old node to delete first? */
+ if (SVN_IS_VALID_REVNUM(change->deleting))
+ SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting,
+ parent_baton, scratch_pool));
+
+ /* Are we copying the node from somewhere? */
+ if (change->copyfrom_path)
+ {
+ if (eb->repos_root)
+ copyfrom_url = svn_path_url_add_component2(eb->repos_root,
+ change->copyfrom_path,
+ scratch_pool);
+ else
+ copyfrom_url = change->copyfrom_path;
+
+ /* Make this an FS path by prepending "/" */
+ if (copyfrom_url[0] != '/')
+ copyfrom_url = apr_pstrcat(scratch_pool, "/", copyfrom_url, NULL);
+
+ copyfrom_rev = change->copyfrom_rev;
+ }
+
+ if (change->kind == svn_node_dir)
+ SVN_ERR(eb->deditor->add_directory(ev1_relpath, parent_baton,
+ copyfrom_url, copyfrom_rev,
+ result_pool, dir_baton));
+ else
+ SVN_ERR(eb->deditor->add_file(ev1_relpath, parent_baton,
+ copyfrom_url, copyfrom_rev,
+ result_pool, &file_baton));
+ }
+ else
+ {
+ if (change->kind == svn_node_dir)
+ SVN_ERR(eb->deditor->open_directory(ev1_relpath, parent_baton,
+ change->changing,
+ result_pool, dir_baton));
+ else
+ SVN_ERR(eb->deditor->open_file(ev1_relpath, parent_baton,
+ change->changing,
+ result_pool, &file_baton));
+ }
+
+ /* Apply any properties in CHANGE to the node. */
+ if (change->kind == svn_node_dir)
+ SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool));
+ else
+ SVN_ERR(drive_ev1_props(eb, relpath, change, file_baton, scratch_pool));
+
+ if (change->contents_abspath)
+ {
+ svn_txdelta_window_handler_t handler;
+ void *handler_baton;
+ svn_stream_t *contents;
+
+ /* ### would be nice to have a BASE_CHECKSUM, but hey: this is the
+ ### shim code... */
+ SVN_ERR(eb->deditor->apply_textdelta(file_baton, NULL, scratch_pool,
+ &handler, &handler_baton));
+ SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
+ scratch_pool, scratch_pool));
+ /* ### it would be nice to send a true txdelta here, but whatever. */
+ SVN_ERR(svn_txdelta_send_stream(contents, handler, handler_baton,
+ NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(contents));
+ }
+
+ if (file_baton)
+ {
+ const char *digest = svn_checksum_to_cstring(change->checksum,
+ scratch_pool);
+
+ SVN_ERR(eb->deditor->close_file(file_baton, digest, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+drive_changes(const struct editor_baton *eb,
+ apr_pool_t *scratch_pool)
+{
+ struct change_node *change;
+ const apr_array_header_t *paths;
+
+ /* If we never opened a root baton, then the caller aborted the editor
+ before it even began. There is nothing to do. Bail. */
+ if (eb->root.baton == NULL)
+ return SVN_NO_ERROR;
+
+ /* We need to make the path driver believe we want to make changes to
+ the root. Otherwise, it will attempt an open_root(), which we already
+ did in start_edit_func(). We can forge up a change record, if one
+ does not already exist. */
+ change = insert_change(eb->base_relpath, eb->changes);
+ change->kind = svn_node_dir;
+ /* No property changes (tho they might exist from a real change). */
+
+ /* Get a sorted list of Ev1-relative paths. */
+ paths = get_sorted_paths(eb->changes, eb->base_relpath, scratch_pool);
+ SVN_ERR(svn_delta_path_driver2(eb->deditor, eb->dedit_baton, paths,
+ FALSE, apply_change, (void *)eb,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_complete_t */
+static svn_error_t *
+complete_cb(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ svn_error_t *err;
+
+ /* Drive the tree we've created. */
+ err = drive_changes(eb, scratch_pool);
+ if (!err)
+ {
+ err = svn_error_compose_create(err, eb->deditor->close_edit(
+ eb->dedit_baton,
+ scratch_pool));
+ }
+
+ if (err)
+ svn_error_clear(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool));
+
+ return svn_error_trace(err);
+}
+
+/* This implements svn_editor_cb_abort_t */
+static svn_error_t *
+abort_cb(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ svn_error_t *err;
+ svn_error_t *err2;
+
+ /* We still need to drive anything we collected in the editor to this
+ point. */
+
+ /* Drive the tree we've created. */
+ err = drive_changes(eb, scratch_pool);
+
+ err2 = eb->deditor->abort_edit(eb->dedit_baton, scratch_pool);
+
+ if (err2)
+ {
+ if (err)
+ svn_error_clear(err2);
+ else
+ err = err2;
+ }
+
+ return svn_error_trace(err);
+}
+
+static svn_error_t *
+start_edit_func(void *baton,
+ svn_revnum_t base_revision)
+{
+ struct editor_baton *eb = baton;
+
+ eb->root.base_revision = base_revision;
+
+ /* For some Ev1 editors (such as the repos commit editor), the root must
+ be open before can invoke any callbacks. The open_root() call sets up
+ stuff (eg. open an FS txn) which will be needed. */
+ SVN_ERR(eb->deditor->open_root(eb->dedit_baton, eb->root.base_revision,
+ eb->edit_pool, &eb->root.baton));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+target_revision_func(void *baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+
+ SVN_ERR(eb->deditor->set_target_revision(eb->dedit_baton, target_revision,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+do_unlock(void *baton,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+
+ {
+ /* PATH is REPOS_RELPATH */
+ struct change_node *change = insert_change(path, eb->changes);
+
+ /* We will need to propagate a deletion of SVN_PROP_ENTRY_LOCK_TOKEN */
+ change->unlock = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return an svn_editor_t * in EDITOR_P which will drive
+ * DEDITOR/DEDIT_BATON. EDITOR_P is allocated in RESULT_POOL, which may
+ * become large and long-lived; SCRATCH_POOL is used for temporary
+ * allocations.
+ *
+ * The other parameters are as follows:
+ * - EXB: An 'extra_baton' used for passing information between the coupled
+ * shims. This includes actions like 'start edit' and 'set target'.
+ * As this shim receives these actions, it provides the extra baton
+ * to its caller.
+ * - UNLOCK_FUNC / UNLOCK_BATON: A callback / baton pair which a caller
+ * can use to notify this shim that a path should be unlocked (in the
+ * 'svn lock' sense). As this shim receives this action, it provides
+ * this callback / baton to its caller.
+ * - SEND_ABS_PATHS: A pointer which will be set prior to this edit (but
+ * not necessarily at the invocation of editor_from_delta()),and
+ * which indicates whether incoming paths should be expected to
+ * be absolute or relative.
+ * - CANCEL_FUNC / CANCEL_BATON: The usual; folded into the produced editor.
+ * - FETCH_KIND_FUNC / FETCH_KIND_BATON: A callback / baton pair which will
+ * be used by the shim handlers if they need to determine the kind of
+ * a path.
+ * - FETCH_PROPS_FUNC / FETCH_PROPS_BATON: A callback / baton pair which
+ * will be used by the shim handlers if they need to determine the
+ * existing properties on a path.
+ */
+svn_error_t *
+svn_delta__editor_from_delta(svn_editor_t **editor_p,
+ struct svn_delta__extra_baton **exb,
+ svn_delta__unlock_func_t *unlock_func,
+ void **unlock_baton,
+ const svn_delta_editor_t *deditor,
+ void *dedit_baton,
+ svn_boolean_t *send_abs_paths,
+ const char *repos_root,
+ const char *base_relpath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_delta_fetch_kind_func_t fetch_kind_func,
+ void *fetch_kind_baton,
+ svn_delta_fetch_props_func_t fetch_props_func,
+ void *fetch_props_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor_t *editor;
+ static const svn_editor_cb_many_t editor_cbs = {
+ add_directory_cb,
+ add_file_cb,
+ add_symlink_cb,
+ add_absent_cb,
+ alter_directory_cb,
+ alter_file_cb,
+ alter_symlink_cb,
+ delete_cb,
+ copy_cb,
+ move_cb,
+ rotate_cb,
+ complete_cb,
+ abort_cb
+ };
+ struct editor_baton *eb = apr_pcalloc(result_pool, sizeof(*eb));
+ struct svn_delta__extra_baton *extra_baton = apr_pcalloc(result_pool,
+ sizeof(*extra_baton));
+
+ if (!base_relpath)
+ base_relpath = "";
+ else if (base_relpath[0] == '/')
+ base_relpath += 1;
+
+ eb->deditor = deditor;
+ eb->dedit_baton = dedit_baton;
+ eb->edit_pool = result_pool;
+ eb->repos_root = apr_pstrdup(result_pool, repos_root);
+ eb->base_relpath = apr_pstrdup(result_pool, base_relpath);
+
+ eb->changes = apr_hash_make(result_pool);
+
+ eb->fetch_kind_func = fetch_kind_func;
+ eb->fetch_kind_baton = fetch_kind_baton;
+ eb->fetch_props_func = fetch_props_func;
+ eb->fetch_props_baton = fetch_props_baton;
+
+ eb->root.base_revision = SVN_INVALID_REVNUM;
+
+ eb->make_abs_paths = send_abs_paths;
+
+ SVN_ERR(svn_editor_create(&editor, eb, cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_editor_setcb_many(editor, &editor_cbs, scratch_pool));
+
+ *editor_p = editor;
+
+ *unlock_func = do_unlock;
+ *unlock_baton = eb;
+
+ extra_baton->start_edit = start_edit_func;
+ extra_baton->target_revision = target_revision_func;
+ extra_baton->baton = eb;
+
+ *exb = extra_baton;
+
+ return SVN_NO_ERROR;
+}
+
+svn_delta_shim_callbacks_t *
+svn_delta_shim_callbacks_default(apr_pool_t *result_pool)
+{
+ svn_delta_shim_callbacks_t *shim_callbacks = apr_pcalloc(result_pool,
+ sizeof(*shim_callbacks));
+ return shim_callbacks;
+}
+
+/* To enable editor shims throughout Subversion, ENABLE_EV2_SHIMS should be
+ * defined. This can be done manually, or by providing `--enable-ev2-shims'
+ * to `configure'. */
+
+svn_error_t *
+svn_editor__insert_shims(const svn_delta_editor_t **deditor_out,
+ void **dedit_baton_out,
+ const svn_delta_editor_t *deditor_in,
+ void *dedit_baton_in,
+ const char *repos_root,
+ const char *base_relpath,
+ svn_delta_shim_callbacks_t *shim_callbacks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+#ifndef ENABLE_EV2_SHIMS
+ /* Shims disabled, just copy the editor and baton directly. */
+ *deditor_out = deditor_in;
+ *dedit_baton_out = dedit_baton_in;
+#else
+ /* Use our shim APIs to create an intermediate svn_editor_t, and then
+ wrap that again back into a svn_delta_editor_t. This introduces
+ a lot of overhead. */
+ svn_editor_t *editor;
+
+ /* The "extra baton" is a set of functions and a baton which allows the
+ shims to communicate additional events to each other.
+ svn_delta__editor_from_delta() returns a pointer to this baton, which
+ svn_delta__delta_from_editor() should then store. */
+ struct svn_delta__extra_baton *exb;
+
+ /* The reason this is a pointer is that we don't know the appropriate
+ value until we start receiving paths. So process_actions() sets the
+ flag, which drive_tree() later consumes. */
+ svn_boolean_t *found_abs_paths = apr_palloc(result_pool,
+ sizeof(*found_abs_paths));
+
+ svn_delta__unlock_func_t unlock_func;
+ void *unlock_baton;
+
+ SVN_ERR_ASSERT(shim_callbacks->fetch_kind_func != NULL);
+ SVN_ERR_ASSERT(shim_callbacks->fetch_props_func != NULL);
+ SVN_ERR_ASSERT(shim_callbacks->fetch_base_func != NULL);
+
+ SVN_ERR(svn_delta__editor_from_delta(&editor, &exb,
+ &unlock_func, &unlock_baton,
+ deditor_in, dedit_baton_in,
+ found_abs_paths, repos_root, base_relpath,
+ NULL, NULL,
+ shim_callbacks->fetch_kind_func,
+ shim_callbacks->fetch_baton,
+ shim_callbacks->fetch_props_func,
+ shim_callbacks->fetch_baton,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_delta__delta_from_editor(deditor_out, dedit_baton_out, editor,
+ unlock_func, unlock_baton,
+ found_abs_paths,
+ repos_root, base_relpath,
+ shim_callbacks->fetch_props_func,
+ shim_callbacks->fetch_baton,
+ shim_callbacks->fetch_base_func,
+ shim_callbacks->fetch_baton,
+ exb, result_pool));
+
+#endif
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/compose_delta.c b/contrib/subversion/subversion/libsvn_delta/compose_delta.c
new file mode 100644
index 0000000..7b96438
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/compose_delta.c
@@ -0,0 +1,837 @@
+/*
+ * compose_delta.c: Delta window composition.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <assert.h>
+
+#include <apr_general.h> /* For APR_INLINE */
+
+#include "svn_delta.h"
+#include "svn_pools.h"
+#include "delta.h"
+
+/* Define a MIN macro if this platform doesn't already have one. */
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+
+/* ==================================================================== */
+/* Support for efficient small-block allocation from pools. */
+
+/* The following structs will be allocated and freed often: */
+
+/* A node in the range index tree. */
+typedef struct range_index_node_t range_index_node_t;
+struct range_index_node_t
+{
+ /* 'offset' and 'limit' define the range in the source window. */
+ apr_size_t offset;
+ apr_size_t limit;
+
+ /* 'target_offset' is where that range is represented in the target. */
+ apr_size_t target_offset;
+
+ /* 'left' and 'right' link the node into a splay tree. */
+ range_index_node_t *left, *right;
+
+ /* 'prev' and 'next' link it into an ordered, doubly-linked list. */
+ range_index_node_t *prev, *next;
+};
+
+/* A node in a list of ranges for source and target op copies. */
+enum range_kind
+ {
+ range_from_source,
+ range_from_target
+ };
+
+typedef struct range_list_node_t range_list_node_t;
+struct range_list_node_t
+{
+ /* Where does the range come from?
+ 'offset' and 'limit' always refer to the "virtual" source data
+ for the second delta window. For a target range, the actual
+ offset to use for generating the target op is 'target_offset';
+ that field isn't used by source ranges. */
+ enum range_kind kind;
+
+ /* 'offset' and 'limit' define the range. */
+ apr_size_t offset;
+ apr_size_t limit;
+
+ /* 'target_offset' is the start of the range in the target. */
+ apr_size_t target_offset;
+
+ /* 'prev' and 'next' link the node into an ordered, doubly-linked list. */
+ range_list_node_t *prev, *next;
+};
+
+
+/* This is what will be allocated: */
+typedef union alloc_block_t alloc_block_t;
+union alloc_block_t
+{
+ range_index_node_t index_node;
+ range_list_node_t list_node;
+
+ /* Links free blocks into a freelist. */
+ alloc_block_t *next_free;
+};
+
+
+/* Allocate a block. */
+static APR_INLINE void *
+alloc_block(apr_pool_t *pool, alloc_block_t **free_list)
+{
+ alloc_block_t *block;
+ if (*free_list == NULL)
+ block = apr_palloc(pool, sizeof(*block));
+ else
+ {
+ block = *free_list;
+ *free_list = block->next_free;
+ }
+ return block;
+}
+
+/* Return the block back to the free list. */
+static APR_INLINE void
+free_block(void *ptr, alloc_block_t **free_list)
+{
+ /* Wrapper functions take care of type safety. */
+ alloc_block_t *const block = ptr;
+ block->next_free = *free_list;
+ *free_list = block;
+}
+
+
+
+/* ==================================================================== */
+/* Mapping offsets in the target streem to txdelta ops. */
+
+typedef struct offset_index_t
+{
+ int length;
+ apr_size_t *offs;
+} offset_index_t;
+
+/* Create an index mapping target stream offsets to delta ops in
+ WINDOW. Allocate from POOL. */
+
+static offset_index_t *
+create_offset_index(const svn_txdelta_window_t *window, apr_pool_t *pool)
+{
+ offset_index_t *ndx = apr_palloc(pool, sizeof(*ndx));
+ apr_size_t offset = 0;
+ int i;
+
+ ndx->length = window->num_ops;
+ ndx->offs = apr_palloc(pool, (ndx->length + 1) * sizeof(*ndx->offs));
+
+ for (i = 0; i < ndx->length; ++i)
+ {
+ ndx->offs[i] = offset;
+ offset += window->ops[i].length;
+ }
+ ndx->offs[ndx->length] = offset;
+
+ return ndx;
+}
+
+/* Find the index of the delta op thet defines that data at OFFSET in
+ NDX. HINT is an arbitrary positin within NDX and doesn't even need
+ to be valid. To effectively speed up the search, use the last result
+ as hint because most lookups come as a sequence of decreasing values
+ for OFFSET and they concentrate on the lower end of the array. */
+
+static apr_size_t
+search_offset_index(const offset_index_t *ndx,
+ apr_size_t offset,
+ apr_size_t hint)
+{
+ apr_size_t lo, hi, op;
+
+ assert(offset < ndx->offs[ndx->length]);
+
+ lo = 0;
+ hi = ndx->length;
+
+ /* If we got a valid hint, use it to reduce the range to cover.
+ Note that this will only be useful if either the hint is a
+ hit (i.e. equals the desired result) or narrows the range
+ length by a factor larger than 2. */
+
+ if (hint < hi)
+ {
+ if (offset < ndx->offs[hint])
+ hi = hint;
+ else if (offset < ndx->offs[hint+1])
+ return hint;
+ else
+ lo = hint+1;
+ }
+
+ /* ordinary binary search */
+
+ for (op = (lo + hi)/2; lo != hi; op = (lo + hi)/2)
+ {
+ if (offset < ndx->offs[op])
+ hi = op;
+ else
+ lo = ++op;
+ }
+
+ --lo;
+ assert(ndx->offs[lo] <= offset && offset < ndx->offs[lo + 1]);
+ return lo;
+}
+
+
+
+/* ==================================================================== */
+/* Mapping ranges in the source stream to ranges in the composed delta. */
+
+/* The range index tree. */
+typedef struct range_index_t
+{
+ range_index_node_t *tree;
+ alloc_block_t *free_list;
+ apr_pool_t *pool;
+} range_index_t;
+
+/* Create a range index tree. Allocate from POOL. */
+static range_index_t *
+create_range_index(apr_pool_t *pool)
+{
+ range_index_t *ndx = apr_palloc(pool, sizeof(*ndx));
+ ndx->tree = NULL;
+ ndx->pool = pool;
+ ndx->free_list = NULL;
+ return ndx;
+}
+
+/* Allocate a node for the range index tree. */
+static range_index_node_t *
+alloc_range_index_node(range_index_t *ndx,
+ apr_size_t offset,
+ apr_size_t limit,
+ apr_size_t target_offset)
+{
+ range_index_node_t *const node = alloc_block(ndx->pool, &ndx->free_list);
+ node->offset = offset;
+ node->limit = limit;
+ node->target_offset = target_offset;
+ node->left = node->right = NULL;
+ node->prev = node->next = NULL;
+ return node;
+}
+
+/* Free a node from the range index tree. */
+static void
+free_range_index_node(range_index_t *ndx, range_index_node_t *node)
+{
+ if (node->next)
+ node->next->prev = node->prev;
+ if (node->prev)
+ node->prev->next = node->next;
+ free_block(node, &ndx->free_list);
+}
+
+
+/* Splay the index tree, using OFFSET as the key. */
+
+static void
+splay_range_index(apr_size_t offset, range_index_t *ndx)
+{
+ range_index_node_t *tree = ndx->tree;
+ range_index_node_t scratch_node;
+ range_index_node_t *left, *right;
+
+ if (tree == NULL)
+ return;
+
+ scratch_node.left = scratch_node.right = NULL;
+ left = right = &scratch_node;
+
+ for (;;)
+ {
+ if (offset < tree->offset)
+ {
+ if (tree->left != NULL
+ && offset < tree->left->offset)
+ {
+ /* Right rotation */
+ range_index_node_t *const node = tree->left;
+ tree->left = node->right;
+ node->right = tree;
+ tree = node;
+ }
+ if (tree->left == NULL)
+ break;
+
+ /* Remember the right subtree */
+ right->left = tree;
+ right = tree;
+ tree = tree->left;
+ }
+ else if (offset > tree->offset)
+ {
+ if (tree->right != NULL
+ && offset > tree->right->offset)
+ {
+ /* Left rotation */
+ range_index_node_t *const node = tree->right;
+ tree->right = node->left;
+ node->left = tree;
+ tree = node;
+ }
+ if (tree->right == NULL)
+ break;
+
+ /* Remember the left subtree */
+ left->right = tree;
+ left = tree;
+ tree = tree->right;
+ }
+ else
+ break;
+ }
+
+ /* Link in the left and right subtrees */
+ left->right = tree->left;
+ right->left = tree->right;
+ tree->left = scratch_node.right;
+ tree->right = scratch_node.left;
+
+ /* The basic top-down splay is finished, but we may still need to
+ turn the tree around. What we want is to put the node with the
+ largest offset where node->offset <= offset at the top of the
+ tree, so that we can insert the new data (or search for existing
+ ranges) to the right of the root. This makes cleaning up the
+ tree after an insert much simpler, and -- incidentally -- makes
+ the whole range index magic work. */
+ if (offset < tree->offset && tree->left != NULL)
+ {
+ if (tree->left->right == NULL)
+ {
+ /* A single right rotation is enough. */
+ range_index_node_t *const node = tree->left;
+ tree->left = node->right; /* Which is always NULL. */
+ node->right = tree;
+ tree = node;
+ }
+ else
+ {
+ /* Slide down to the rightmost node in the left subtree. */
+ range_index_node_t **nodep = &tree->left;
+ while ((*nodep)->right != NULL)
+ nodep = &(*nodep)->right;
+
+ /* Now move this node to root in one giant promotion. */
+ right = tree;
+ left = tree->left;
+ tree = *nodep;
+ *nodep = tree->left;
+ right->left = tree->right; /* Which is always NULL, too. */
+ tree->left = left;
+ tree->right = right;
+ }
+ }
+
+ /* Sanity check ... */
+ assert((offset >= tree->offset)
+ || ((tree->left == NULL)
+ && (tree->prev == NULL)));
+ ndx->tree = tree;
+}
+
+
+/* Remove all ranges from NDX that fall into the root's range. To
+ keep the range index as small as possible, we must also remove
+ nodes that don't fall into the new range, but have become redundant
+ because the new range overlaps the beginning of the next range.
+ Like this:
+
+ new-range: |-----------------|
+ range-1: |-----------------|
+ range-2: |--------------------|
+
+ Before new-range was inserted, range-1 and range-2 were both
+ necessary. Now the union of new-range and range-2 completely covers
+ range-1, which has become redundant now.
+
+ FIXME: But, of course, there's a catch. range-1 must still remain
+ in the tree if we want to optimize the number of target copy ops in
+ the case were a copy falls within range-1, but starts before
+ range-2 and ends after new-range. */
+
+static void
+delete_subtree(range_index_t *ndx, range_index_node_t *node)
+{
+ if (node != NULL)
+ {
+ delete_subtree(ndx, node->left);
+ delete_subtree(ndx, node->right);
+ free_range_index_node(ndx, node);
+ }
+}
+
+static void
+clean_tree(range_index_t *ndx, apr_size_t limit)
+{
+ apr_size_t top_offset = limit + 1;
+ range_index_node_t **nodep = &ndx->tree->right;
+ while (*nodep != NULL)
+ {
+ range_index_node_t *const node = *nodep;
+ apr_size_t const offset =
+ (node->right != NULL && node->right->offset < top_offset
+ ? node->right->offset
+ : top_offset);
+
+ if (node->limit <= limit
+ || (node->offset < limit && offset < limit))
+ {
+ *nodep = node->right;
+ node->right = NULL;
+ delete_subtree(ndx, node);
+ }
+ else
+ {
+ top_offset = node->offset;
+ nodep = &node->left;
+ }
+ }
+}
+
+
+/* Add a range [OFFSET, LIMIT) into NDX. If NDX already contains a
+ range that encloses [OFFSET, LIMIT), do nothing. Otherwise, remove
+ all ranges from NDX that are superseded by the new range.
+ NOTE: The range index must be splayed to OFFSET! */
+
+static void
+insert_range(apr_size_t offset, apr_size_t limit, apr_size_t target_offset,
+ range_index_t *ndx)
+{
+ range_index_node_t *node = NULL;
+
+ if (ndx->tree == NULL)
+ {
+ node = alloc_range_index_node(ndx, offset, limit, target_offset);
+ ndx->tree = node;
+ }
+ else
+ {
+ if (offset == ndx->tree->offset
+ && limit > ndx->tree->limit)
+ {
+ ndx->tree->limit = limit;
+ ndx->tree->target_offset = target_offset;
+ clean_tree(ndx, limit);
+ }
+ else if (offset > ndx->tree->offset
+ && limit > ndx->tree->limit)
+ {
+ /* We have to make the same sort of checks as clean_tree()
+ does for superseded ranges. Have to merge these someday. */
+
+ const svn_boolean_t insert_range_p =
+ (!ndx->tree->next
+ || ndx->tree->limit < ndx->tree->next->offset
+ || limit > ndx->tree->next->limit);
+
+ if (insert_range_p)
+ {
+ /* Again, we have to check if the new node and the one
+ to the left of the root override root's range. */
+ if (ndx->tree->prev && ndx->tree->prev->limit > offset)
+ {
+ /* Replace the data in the splayed node. */
+ ndx->tree->offset = offset;
+ ndx->tree->limit = limit;
+ ndx->tree->target_offset = target_offset;
+ }
+ else
+ {
+ /* Insert the range to the right of the splayed node. */
+ node = alloc_range_index_node(ndx, offset, limit,
+ target_offset);
+ if ((node->next = ndx->tree->next) != NULL)
+ node->next->prev = node;
+ ndx->tree->next = node;
+ node->prev = ndx->tree;
+
+ node->right = ndx->tree->right;
+ ndx->tree->right = NULL;
+ node->left = ndx->tree;
+ ndx->tree = node;
+ }
+ clean_tree(ndx, limit);
+ }
+ else
+ /* Ignore the range */;
+ }
+ else if (offset < ndx->tree->offset)
+ {
+ assert(ndx->tree->left == NULL);
+
+ /* Insert the range left of the splayed node */
+ node = alloc_range_index_node(ndx, offset, limit, target_offset);
+ node->left = node->prev = NULL;
+ node->right = node->next = ndx->tree;
+ ndx->tree = node->next->prev = node;
+ clean_tree(ndx, limit);
+ }
+ else
+ /* Ignore the range */;
+ }
+}
+
+
+
+/* ==================================================================== */
+/* Juggling with lists of ranges. */
+
+/* Allocate a node and add it to the range list. LIST is the head of
+ the range list, TAIL is the last node in the list. NDX holds the
+ freelist; OFFSET, LIMIT and KIND are node data. */
+static range_list_node_t *
+alloc_range_list(range_list_node_t **list,
+ range_list_node_t **tail,
+ range_index_t *ndx,
+ enum range_kind kind,
+ apr_size_t offset,
+ apr_size_t limit,
+ apr_size_t target_offset)
+{
+ range_list_node_t *const node = alloc_block(ndx->pool, &ndx->free_list);
+ node->kind = kind;
+ node->offset = offset;
+ node->limit = limit;
+ node->target_offset = target_offset;
+ if (*list == NULL)
+ {
+ node->prev = node->next = NULL;
+ *list = *tail = node;
+ }
+ else
+ {
+ node->prev = *tail;
+ node->next = NULL;
+ (*tail)->next = node;
+ *tail = node;
+ }
+ return *list;
+}
+
+/* Free a range list. LIST is the head of the list, NDX holds the freelist. */
+static void
+free_range_list(range_list_node_t *list, range_index_t *ndx)
+{
+ while (list)
+ {
+ range_list_node_t *const node = list;
+ list = node->next;
+ free_block(node, &ndx->free_list);
+ }
+}
+
+
+/* Based on the data in NDX, build a list of ranges that cover
+ [OFFSET, LIMIT) in the "virtual" source data.
+ NOTE: The range index must be splayed to OFFSET! */
+
+static range_list_node_t *
+build_range_list(apr_size_t offset, apr_size_t limit, range_index_t *ndx)
+{
+ range_list_node_t *range_list = NULL;
+ range_list_node_t *last_range = NULL;
+ range_index_node_t *node = ndx->tree;
+
+ while (offset < limit)
+ {
+ if (node == NULL)
+ return alloc_range_list(&range_list, &last_range, ndx,
+ range_from_source,
+ offset, limit, 0);
+
+ if (offset < node->offset)
+ {
+ if (limit <= node->offset)
+ return alloc_range_list(&range_list, &last_range, ndx,
+ range_from_source,
+ offset, limit, 0);
+ else
+ {
+ alloc_range_list(&range_list, &last_range, ndx,
+ range_from_source,
+ offset, node->offset, 0);
+ offset = node->offset;
+ }
+ }
+ else
+ {
+ /* TODO: (Potential optimization) Investigate if it would
+ make sense to forbid short range_from_target lengths
+ (this comment originally said "shorter than, say,
+ VD_KEY_SIZE (see vdelta.c)", but Subversion no longer
+ uses vdelta). */
+
+ if (offset >= node->limit)
+ node = node->next;
+ else
+ {
+ const apr_size_t target_offset =
+ offset - node->offset + node->target_offset;
+
+ if (limit <= node->limit)
+ return alloc_range_list(&range_list, &last_range, ndx,
+ range_from_target,
+ offset, limit, target_offset);
+ else
+ {
+ alloc_range_list(&range_list, &last_range, ndx,
+ range_from_target,
+ offset, node->limit, target_offset);
+ offset = node->limit;
+ node = node->next;
+ }
+ }
+ }
+ }
+
+ /* A range's offset isn't smaller than its limit? Impossible! */
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+}
+
+
+/* Copy the instructions from WINDOW that define the range [OFFSET,
+ LIMIT) in WINDOW's target stream to TARGET_OFFSET in the window
+ represented by BUILD_BATON. HINT is a position in the instructions
+ array that helps finding the position for OFFSET. A safe default
+ is 0. Use NDX to find the instructions in WINDOW. Allocate space
+ in BUILD_BATON from POOL. */
+
+static void
+copy_source_ops(apr_size_t offset, apr_size_t limit,
+ apr_size_t target_offset,
+ apr_size_t hint,
+ svn_txdelta__ops_baton_t *build_baton,
+ const svn_txdelta_window_t *window,
+ const offset_index_t *ndx,
+ apr_pool_t *pool)
+{
+ apr_size_t op_ndx = search_offset_index(ndx, offset, hint);
+ for (;; ++op_ndx)
+ {
+ const svn_txdelta_op_t *const op = &window->ops[op_ndx];
+ const apr_size_t *const off = &ndx->offs[op_ndx];
+ apr_size_t fix_offset;
+ apr_size_t fix_limit;
+
+ if (off[0] >= limit)
+ break;
+
+ fix_offset = (offset > off[0] ? offset - off[0] : 0);
+ fix_limit = (off[1] > limit ? off[1] - limit : 0);
+
+ /* It would be extremely weird if the fixed-up op had zero length. */
+ assert(fix_offset + fix_limit < op->length);
+
+ if (op->action_code != svn_txdelta_target)
+ {
+ /* Delta ops that don't depend on the virtual target can be
+ copied to the composite unchanged. */
+ const char *const new_data = (op->action_code == svn_txdelta_new
+ ? (window->new_data->data
+ + op->offset + fix_offset)
+ : NULL);
+
+ svn_txdelta__insert_op(build_baton, op->action_code,
+ op->offset + fix_offset,
+ op->length - fix_offset - fix_limit,
+ new_data, pool);
+ }
+ else
+ {
+ /* The source of a target copy must start before the current
+ offset in the (virtual) target stream. */
+ assert(op->offset < off[0]);
+
+ if (op->offset + op->length - fix_limit <= off[0])
+ {
+ /* The recursion _must_ end, otherwise the delta has
+ circular references, and that is not possible. */
+ copy_source_ops(op->offset + fix_offset,
+ op->offset + op->length - fix_limit,
+ target_offset,
+ op_ndx,
+ build_baton, window, ndx, pool);
+ }
+ else
+ {
+ /* This is an overlapping target copy.
+ The idea here is to transpose the pattern, then generate
+ another overlapping copy. */
+ const apr_size_t ptn_length = off[0] - op->offset;
+ const apr_size_t ptn_overlap = fix_offset % ptn_length;
+ apr_size_t fix_off = fix_offset;
+ apr_size_t tgt_off = target_offset;
+ assert(ptn_length > ptn_overlap);
+
+ /* ### FIXME: ptn_overlap is unsigned, so the if() condition
+ below is always true! Either it should be '> 0', or the
+ code block should be unconditional. See also r842362. */
+ if (ptn_overlap >= 0)
+ {
+ /* Issue second subrange in the pattern. */
+ const apr_size_t length =
+ MIN(op->length - fix_off - fix_limit,
+ ptn_length - ptn_overlap);
+ copy_source_ops(op->offset + ptn_overlap,
+ op->offset + ptn_overlap + length,
+ tgt_off,
+ op_ndx,
+ build_baton, window, ndx, pool);
+ fix_off += length;
+ tgt_off += length;
+ }
+
+ assert(fix_off + fix_limit <= op->length);
+ if (ptn_overlap > 0
+ && fix_off + fix_limit < op->length)
+ {
+ /* Issue the first subrange in the pattern. */
+ const apr_size_t length =
+ MIN(op->length - fix_off - fix_limit, ptn_overlap);
+ copy_source_ops(op->offset,
+ op->offset + length,
+ tgt_off,
+ op_ndx,
+ build_baton, window, ndx, pool);
+ fix_off += length;
+ tgt_off += length;
+ }
+
+ assert(fix_off + fix_limit <= op->length);
+ if (fix_off + fix_limit < op->length)
+ {
+ /* Now multiply the pattern */
+ svn_txdelta__insert_op(build_baton, svn_txdelta_target,
+ tgt_off - ptn_length,
+ op->length - fix_off - fix_limit,
+ NULL, pool);
+ }
+ }
+ }
+
+ /* Adjust the target offset for the next op in the list. */
+ target_offset += op->length - fix_offset - fix_limit;
+ }
+}
+
+
+
+/* ==================================================================== */
+/* Bringing it all together. */
+
+
+svn_txdelta_window_t *
+svn_txdelta_compose_windows(const svn_txdelta_window_t *window_A,
+ const svn_txdelta_window_t *window_B,
+ apr_pool_t *pool)
+{
+ svn_txdelta__ops_baton_t build_baton = { 0 };
+ svn_txdelta_window_t *composite;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ offset_index_t *offset_index = create_offset_index(window_A, subpool);
+ range_index_t *range_index = create_range_index(subpool);
+ apr_size_t target_offset = 0;
+ int i;
+
+ /* Read the description of the delta composition algorithm in
+ notes/fs-improvements.txt before going any further.
+ You have been warned. */
+ build_baton.new_data = svn_stringbuf_create_empty(pool);
+ for (i = 0; i < window_B->num_ops; ++i)
+ {
+ const svn_txdelta_op_t *const op = &window_B->ops[i];
+ if (op->action_code != svn_txdelta_source)
+ {
+ /* Delta ops that don't depend on the source can be copied
+ to the composite unchanged. */
+ const char *const new_data =
+ (op->action_code == svn_txdelta_new
+ ? window_B->new_data->data + op->offset
+ : NULL);
+ svn_txdelta__insert_op(&build_baton, op->action_code,
+ op->offset, op->length,
+ new_data, pool);
+ }
+ else
+ {
+ /* NOTE: Remember that `offset' and `limit' refer to
+ positions in window_B's _source_ stream, which is the
+ same as window_A's _target_ stream! */
+ const apr_size_t offset = op->offset;
+ const apr_size_t limit = op->offset + op->length;
+ range_list_node_t *range_list, *range;
+ apr_size_t tgt_off = target_offset;
+
+ splay_range_index(offset, range_index);
+ range_list = build_range_list(offset, limit, range_index);
+
+ for (range = range_list; range; range = range->next)
+ {
+ if (range->kind == range_from_target)
+ svn_txdelta__insert_op(&build_baton, svn_txdelta_target,
+ range->target_offset,
+ range->limit - range->offset,
+ NULL, pool);
+ else
+ copy_source_ops(range->offset, range->limit, tgt_off, 0,
+ &build_baton, window_A, offset_index,
+ pool);
+
+ tgt_off += range->limit - range->offset;
+ }
+ assert(tgt_off == target_offset + op->length);
+
+ free_range_list(range_list, range_index);
+ insert_range(offset, limit, target_offset, range_index);
+ }
+
+ /* Remember the new offset in the would-be target stream. */
+ target_offset += op->length;
+ }
+
+ svn_pool_destroy(subpool);
+
+ composite = svn_txdelta__make_window(&build_baton, pool);
+ composite->sview_offset = window_A->sview_offset;
+ composite->sview_len = window_A->sview_len;
+ composite->tview_len = window_B->tview_len;
+ return composite;
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/debug_editor.c b/contrib/subversion/subversion/libsvn_delta/debug_editor.c
new file mode 100644
index 0000000..7c2cdec
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/debug_editor.c
@@ -0,0 +1,437 @@
+/*
+ * debug_editor.c : An editor that writes the operations it does to stderr.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_io.h"
+
+#include "debug_editor.h"
+
+struct edit_baton
+{
+ const svn_delta_editor_t *wrapped_editor;
+ void *wrapped_edit_baton;
+
+ int indent_level;
+
+ svn_stream_t *out;
+};
+
+struct dir_baton
+{
+ void *edit_baton;
+ void *wrapped_dir_baton;
+};
+
+struct file_baton
+{
+ void *edit_baton;
+ void *wrapped_file_baton;
+};
+
+static svn_error_t *
+write_indent(struct edit_baton *eb, apr_pool_t *pool)
+{
+ int i;
+
+ /* This is DBG_FLAG from ../libsvn_subr/debug.c */
+ SVN_ERR(svn_stream_puts(eb->out, "DBG:"));
+ for (i = 0; i < eb->indent_level; ++i)
+ SVN_ERR(svn_stream_puts(eb->out, " "));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "set_target_revision : %ld\n",
+ target_revision));
+
+ return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
+ target_revision,
+ pool);
+}
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *dir_baton = apr_palloc(pool, sizeof(*dir_baton));
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "open_root : %ld\n",
+ base_revision));
+ eb->indent_level++;
+
+ SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
+ base_revision,
+ pool,
+ &dir_baton->wrapped_dir_baton));
+
+ dir_baton->edit_baton = edit_baton;
+
+ *root_baton = dir_baton;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t base_revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "delete_entry : %s:%ld\n",
+ path, base_revision));
+
+ return eb->wrapped_editor->delete_entry(path,
+ base_revision,
+ pb->wrapped_dir_baton,
+ pool);
+}
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *b = apr_palloc(pool, sizeof(*b));
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool,
+ "add_directory : '%s' [from '%s':%ld]\n",
+ path, copyfrom_path, copyfrom_revision));
+ eb->indent_level++;
+
+ SVN_ERR(eb->wrapped_editor->add_directory(path,
+ pb->wrapped_dir_baton,
+ copyfrom_path,
+ copyfrom_revision,
+ pool,
+ &b->wrapped_dir_baton));
+
+ b->edit_baton = eb;
+ *child_baton = b;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *db = apr_palloc(pool, sizeof(*db));
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "open_directory : '%s':%ld\n",
+ path, base_revision));
+ eb->indent_level++;
+
+ SVN_ERR(eb->wrapped_editor->open_directory(path,
+ pb->wrapped_dir_baton,
+ base_revision,
+ pool,
+ &db->wrapped_dir_baton));
+
+ db->edit_baton = eb;
+ *child_baton = db;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb = apr_palloc(pool, sizeof(*fb));
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool,
+ "add_file : '%s' [from '%s':%ld]\n",
+ path, copyfrom_path, copyfrom_revision));
+
+ eb->indent_level++;
+
+ SVN_ERR(eb->wrapped_editor->add_file(path,
+ pb->wrapped_dir_baton,
+ copyfrom_path,
+ copyfrom_revision,
+ pool,
+ &fb->wrapped_file_baton));
+
+ fb->edit_baton = eb;
+ *file_baton = fb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb = apr_palloc(pool, sizeof(*fb));
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "open_file : '%s':%ld\n",
+ path, base_revision));
+
+ eb->indent_level++;
+
+ SVN_ERR(eb->wrapped_editor->open_file(path,
+ pb->wrapped_dir_baton,
+ base_revision,
+ pool,
+ &fb->wrapped_file_baton));
+
+ fb->edit_baton = eb;
+ *file_baton = fb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "apply_textdelta : %s\n",
+ base_checksum));
+
+ SVN_ERR(eb->wrapped_editor->apply_textdelta(fb->wrapped_file_baton,
+ base_checksum,
+ pool,
+ handler,
+ handler_baton));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ eb->indent_level--;
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "close_file : %s\n",
+ text_checksum));
+
+ SVN_ERR(eb->wrapped_editor->close_file(fb->wrapped_file_baton,
+ text_checksum, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+absent_file(const char *path,
+ void *file_baton,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "absent_file : %s\n", path));
+
+ SVN_ERR(eb->wrapped_editor->absent_file(path, fb->wrapped_file_baton,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ eb->indent_level--;
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "close_directory\n"));
+
+ SVN_ERR(eb->wrapped_editor->close_directory(db->wrapped_dir_baton,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+absent_directory(const char *path,
+ void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "absent_directory : %s\n",
+ path));
+
+ SVN_ERR(eb->wrapped_editor->absent_directory(path, db->wrapped_dir_baton,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "change_file_prop : %s\n",
+ name));
+
+ SVN_ERR(eb->wrapped_editor->change_file_prop(fb->wrapped_file_baton,
+ name,
+ value,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "change_dir_prop : %s\n", name));
+
+ SVN_ERR(eb->wrapped_editor->change_dir_prop(db->wrapped_dir_baton,
+ name,
+ value,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ SVN_ERR(write_indent(eb, pool));
+ SVN_ERR(svn_stream_printf(eb->out, pool, "close_edit\n"));
+
+ SVN_ERR(eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_delta__get_debug_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_edit_baton,
+ apr_pool_t *pool)
+{
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
+ struct edit_baton *eb = apr_palloc(pool, sizeof(*eb));
+ apr_file_t *errfp;
+ svn_stream_t *out;
+
+ apr_status_t apr_err = apr_file_open_stderr(&errfp, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Problem opening stderr");
+
+ out = svn_stream_from_aprfile2(errfp, TRUE, pool);
+
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->open_root = open_root;
+ tree_editor->delete_entry = delete_entry;
+ tree_editor->add_directory = add_directory;
+ tree_editor->open_directory = open_directory;
+ tree_editor->change_dir_prop = change_dir_prop;
+ tree_editor->close_directory = close_directory;
+ tree_editor->absent_directory = absent_directory;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->absent_file = absent_file;
+ tree_editor->close_edit = close_edit;
+
+ eb->wrapped_editor = wrapped_editor;
+ eb->wrapped_edit_baton = wrapped_edit_baton;
+ eb->out = out;
+ eb->indent_level = 0;
+
+ *editor = tree_editor;
+ *edit_baton = eb;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/debug_editor.h b/contrib/subversion/subversion/libsvn_delta/debug_editor.h
new file mode 100644
index 0000000..2b031af
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/debug_editor.h
@@ -0,0 +1,49 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_DEBUG_EDITOR_H
+#define SVN_DEBUG_EDITOR_H
+
+#include "svn_delta.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Return a debug editor that wraps @a wrapped_editor.
+ *
+ * The debug editor simply prints an indication of what callbacks are being
+ * called to @c stderr, and is only intended for use in debugging subversion
+ * editors.
+ */
+svn_error_t *
+svn_delta__get_debug_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_baton,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DEBUG_EDITOR_H */
diff --git a/contrib/subversion/subversion/libsvn_delta/default_editor.c b/contrib/subversion/subversion/libsvn_delta/default_editor.c
new file mode 100644
index 0000000..2f1c973
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/default_editor.c
@@ -0,0 +1,161 @@
+/*
+ * default_editor.c -- provide a basic svn_delta_editor_t
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+
+#include "svn_types.h"
+#include "svn_delta.h"
+
+
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+static svn_error_t *
+add_item(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **baton)
+{
+ *baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+single_baton_func(void *baton,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+absent_xxx_func(const char *path,
+ void *baton,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **root_baton)
+{
+ *root_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_item(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **baton)
+{
+ *baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_delta_noop_window_handler(svn_txdelta_window_t *window,
+ void *baton)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ *handler = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+
+
+static const svn_delta_editor_t default_editor =
+{
+ set_target_revision,
+ open_root,
+ delete_entry,
+ add_item,
+ open_item,
+ change_prop,
+ single_baton_func,
+ absent_xxx_func,
+ add_item,
+ open_item,
+ apply_textdelta,
+ change_prop,
+ close_file,
+ absent_xxx_func,
+ single_baton_func,
+ single_baton_func
+};
+
+svn_delta_editor_t *
+svn_delta_default_editor(apr_pool_t *pool)
+{
+ return apr_pmemdup(pool, &default_editor, sizeof(default_editor));
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/delta.h b/contrib/subversion/subversion/libsvn_delta/delta.h
new file mode 100644
index 0000000..95fe4f7
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/delta.h
@@ -0,0 +1,96 @@
+/*
+ * delta.h: private delta library things
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_delta.h"
+
+#ifndef SVN_LIBSVN_DELTA_H
+#define SVN_LIBSVN_DELTA_H
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Private interface for text deltas. */
+
+/* The standard size of one svndiff window. */
+
+#define SVN_DELTA_WINDOW_SIZE 102400
+
+
+/* Context/baton for building an operation sequence. */
+
+typedef struct svn_txdelta__ops_baton_t {
+ int num_ops; /* current number of ops */
+ int src_ops; /* current number of source copy ops */
+ int ops_size; /* number of ops allocated */
+ svn_txdelta_op_t *ops; /* the operations */
+
+ svn_stringbuf_t *new_data; /* any new data used by the operations */
+} svn_txdelta__ops_baton_t;
+
+
+/* Insert a delta op into the delta window being built via BUILD_BATON. If
+ OPCODE is svn_delta_new, bytes from NEW_DATA are copied into the window
+ data and OFFSET is ignored. Otherwise NEW_DATA is ignored. All
+ allocations are performed in POOL. */
+void svn_txdelta__insert_op(svn_txdelta__ops_baton_t *build_baton,
+ enum svn_delta_action opcode,
+ apr_size_t offset,
+ apr_size_t length,
+ const char *new_data,
+ apr_pool_t *pool);
+
+/* Remove / truncate the last delta ops spanning the last MAX_LEN bytes
+ from the delta window being built via BUILD_BATON starting. Return the
+ number of bytes that were actually removed. */
+apr_size_t
+svn_txdelta__remove_copy(svn_txdelta__ops_baton_t *build_baton,
+ apr_size_t max_len);
+
+/* Allocate a delta window from POOL. */
+svn_txdelta_window_t *
+svn_txdelta__make_window(const svn_txdelta__ops_baton_t *build_baton,
+ apr_pool_t *pool);
+
+
+/* Create xdelta window data. Allocate temporary data from POOL. */
+void svn_txdelta__xdelta(svn_txdelta__ops_baton_t *build_baton,
+ const char *start,
+ apr_size_t source_len,
+ apr_size_t target_len,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_DELTA_H */
diff --git a/contrib/subversion/subversion/libsvn_delta/deprecated.c b/contrib/subversion/subversion/libsvn_delta/deprecated.c
new file mode 100644
index 0000000..4171244
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/deprecated.c
@@ -0,0 +1,48 @@
+/*
+ * deprecated.c: holding file for all deprecated APIs.
+ * "we can't lose 'em, but we can shun 'em!"
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* We define this here to remove any further warnings about the usage of
+ deprecated functions in this file. */
+#define SVN_DEPRECATED
+
+#include "svn_delta.h"
+#include "svn_sorts.h"
+
+
+svn_error_t *
+svn_delta_path_driver(const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_revnum_t revision,
+ const apr_array_header_t *paths,
+ svn_delta_path_driver_cb_func_t callback_func,
+ void *callback_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* REVISION is dropped on the floor. */
+
+ return svn_error_trace(svn_delta_path_driver2(editor, edit_baton, paths,
+ TRUE,
+ callback_func, callback_baton,
+ scratch_pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/depth_filter_editor.c b/contrib/subversion/subversion/libsvn_delta/depth_filter_editor.c
new file mode 100644
index 0000000..b161979
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/depth_filter_editor.c
@@ -0,0 +1,485 @@
+/*
+ * depth_filter_editor.c -- provide a svn_delta_editor_t which wraps
+ * another editor and provides depth-based filtering
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_delta.h"
+
+
+/*** Batons, and the Toys That Create Them ***/
+
+struct edit_baton
+{
+ /* The editor/baton we're wrapping. */
+ const svn_delta_editor_t *wrapped_editor;
+ void *wrapped_edit_baton;
+
+ /* The depth to which we are limiting the drive of the wrapped
+ editor/baton. */
+ svn_depth_t requested_depth;
+
+ /* Does the wrapped editor/baton have an explicit target (in the
+ anchor/target sense of the word)? */
+ svn_boolean_t has_target;
+};
+
+struct node_baton
+{
+ /* TRUE iff this node was filtered out -- that is, not allowed to
+ pass through to the wrapped editor -- by virtue of not appearing
+ at a depth in the tree that was "inside" the requested depth. Of
+ course, any children of this node will be deeper still, and so
+ will also be filtered out for the same reason. */
+ svn_boolean_t filtered;
+
+ /* Pointer to the edit_baton. */
+ void *edit_baton;
+
+ /* The real node baton we're wrapping. May be a directory or file
+ baton; we don't care. */
+ void *wrapped_baton;
+
+ /* The calculated depth (in terms of counted, stacked, integral
+ deepnesses) of this node. If the node is a directory, this value
+ is 1 greater than the value of the same on its parent directory;
+ if a file, it is equal to its parent directory's depth value. */
+ int dir_depth;
+};
+
+/* Allocate and return a new node_baton structure, populated via the
+ the input to this helper function. */
+static struct node_baton *
+make_node_baton(void *edit_baton,
+ svn_boolean_t filtered,
+ int dir_depth,
+ apr_pool_t *pool)
+{
+ struct node_baton *b = apr_palloc(pool, sizeof(*b));
+ b->edit_baton = edit_baton;
+ b->wrapped_baton = NULL;
+ b->filtered = filtered;
+ b->dir_depth = dir_depth;
+ return b;
+}
+
+/* Return TRUE iff changes to immediate children of the directory
+ identified by PB, when those children are of node kind KIND, are
+ allowed by the requested depth which this editor is trying to
+ preserve. EB is the edit baton. */
+static svn_boolean_t
+okay_to_edit(struct edit_baton *eb,
+ struct node_baton *pb,
+ svn_node_kind_t kind)
+{
+ int effective_depth;
+
+ /* If we've already filter out the parent directory, we necessarily
+ are filtering out its children, too. */
+ if (pb->filtered)
+ return FALSE;
+
+ /* Calculate the effective depth of the parent directory.
+
+ NOTE: "Depth" in this sense is not the same as the Subversion
+ magic depth keywords. Here, we're talking about a literal,
+ integral, stacked depth of directories.
+
+ The root of the edit is generally depth=1, subdirectories thereof
+ depth=2, and so on. But if we have an edit target -- which means
+ that the real target of the edit operation isn't the root
+ directory, but is instead some immediate child thereof -- we have
+ to adjust our calculated effected depth such that the target
+ itself is depth=1 (as are its siblings, which we trust aren't
+ present in the edit at all), immediate subdirectories thereof are
+ depth=2, and so on.
+ */
+ effective_depth = pb->dir_depth - (eb->has_target ? 1 : 0);
+ switch (eb->requested_depth)
+ {
+ case svn_depth_empty:
+ return (effective_depth <= 0);
+ case svn_depth_files:
+ return ((effective_depth <= 0)
+ || (kind == svn_node_file && effective_depth == 1));
+ case svn_depth_immediates:
+ return (effective_depth <= 1);
+ case svn_depth_unknown:
+ case svn_depth_exclude:
+ case svn_depth_infinity:
+ /* Shouldn't reach; see svn_delta_depth_filter_editor() */
+ default:
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ }
+}
+
+
+/*** Editor Functions ***/
+
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ /* Nothing depth-y to filter here. */
+ return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
+ target_revision, pool);
+}
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ struct node_baton *b;
+
+ /* The root node always gets through cleanly. */
+ b = make_node_baton(edit_baton, FALSE, 1, pool);
+ SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton, base_revision,
+ pool, &b->wrapped_baton));
+
+ *root_baton = b;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t base_revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct node_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+
+ /* ### FIXME: We don't know the type of the entry, which ordinarily
+ doesn't matter, but is a key (*the* key, in fact) distinction
+ between depth "files" and depths "immediates". If the server is
+ telling us to delete a subdirectory and our requested depth was
+ "immediates", that's fine; if our requested depth was "files",
+ though, this deletion shouldn't survive filtering. For now,
+ we'll claim to our helper function that the to-be-deleted thing
+ is a file because that's the conservative route to take. */
+ if (okay_to_edit(eb, pb, svn_node_file))
+ SVN_ERR(eb->wrapped_editor->delete_entry(path, base_revision,
+ pb->wrapped_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct node_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct node_baton *b = NULL;
+
+ /* Check for sufficient depth. */
+ if (okay_to_edit(eb, pb, svn_node_dir))
+ {
+ b = make_node_baton(eb, FALSE, pb->dir_depth + 1, pool);
+ SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_baton,
+ copyfrom_path,
+ copyfrom_revision,
+ pool, &b->wrapped_baton));
+ }
+ else
+ {
+ b = make_node_baton(eb, TRUE, pb->dir_depth + 1, pool);
+ }
+
+ *child_baton = b;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct node_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct node_baton *b;
+
+ /* Check for sufficient depth. */
+ if (okay_to_edit(eb, pb, svn_node_dir))
+ {
+ b = make_node_baton(eb, FALSE, pb->dir_depth + 1, pool);
+ SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_baton,
+ base_revision, pool,
+ &b->wrapped_baton));
+ }
+ else
+ {
+ b = make_node_baton(eb, TRUE, pb->dir_depth + 1, pool);
+ }
+
+ *child_baton = b;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct node_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct node_baton *b = NULL;
+
+ /* Check for sufficient depth. */
+ if (okay_to_edit(eb, pb, svn_node_file))
+ {
+ b = make_node_baton(eb, FALSE, pb->dir_depth, pool);
+ SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_baton,
+ copyfrom_path, copyfrom_revision,
+ pool, &b->wrapped_baton));
+ }
+ else
+ {
+ b = make_node_baton(eb, TRUE, pb->dir_depth, pool);
+ }
+
+ *child_baton = b;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct node_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct node_baton *b;
+
+ /* Check for sufficient depth. */
+ if (okay_to_edit(eb, pb, svn_node_file))
+ {
+ b = make_node_baton(eb, FALSE, pb->dir_depth, pool);
+ SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_baton,
+ base_revision, pool,
+ &b->wrapped_baton));
+ }
+ else
+ {
+ b = make_node_baton(eb, TRUE, pb->dir_depth, pool);
+ }
+
+ *child_baton = b;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct node_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ /* For filtered files, we just consume the textdelta. */
+ if (fb->filtered)
+ {
+ *handler = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+ }
+ else
+ {
+ SVN_ERR(eb->wrapped_editor->apply_textdelta(fb->wrapped_baton,
+ base_checksum, pool,
+ handler, handler_baton));
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ struct node_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ /* Don't close filtered files. */
+ if (! fb->filtered)
+ SVN_ERR(eb->wrapped_editor->close_file(fb->wrapped_baton,
+ text_checksum, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+absent_file(const char *path,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct node_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+
+ /* Don't report absent items in filtered directories. */
+ if (! pb->filtered)
+ SVN_ERR(eb->wrapped_editor->absent_file(path, pb->wrapped_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct node_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ /* Don't close filtered directories. */
+ if (! db->filtered)
+ SVN_ERR(eb->wrapped_editor->close_directory(db->wrapped_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+absent_directory(const char *path,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct node_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+
+ /* Don't report absent items in filtered directories. */
+ if (! pb->filtered)
+ SVN_ERR(eb->wrapped_editor->absent_directory(path, pb->wrapped_baton,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct node_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ /* No propchanges on filtered files. */
+ if (! fb->filtered)
+ SVN_ERR(eb->wrapped_editor->change_file_prop(fb->wrapped_baton,
+ name, value, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct node_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ /* No propchanges on filtered nodes. */
+ if (! db->filtered)
+ SVN_ERR(eb->wrapped_editor->change_dir_prop(db->wrapped_baton,
+ name, value, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+ return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
+}
+
+svn_error_t *
+svn_delta_depth_filter_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_edit_baton,
+ svn_depth_t requested_depth,
+ svn_boolean_t has_target,
+ apr_pool_t *pool)
+{
+ svn_delta_editor_t *depth_filter_editor;
+ struct edit_baton *eb;
+
+ /* Easy out: if the caller wants infinite depth, there's nothing to
+ filter, so just return the editor we were supposed to wrap. And
+ if they've asked for an unknown depth, we can't possibly know
+ what that means, so why bother? */
+ if ((requested_depth == svn_depth_unknown)
+ || (requested_depth == svn_depth_infinity))
+ {
+ *editor = wrapped_editor;
+ *edit_baton = wrapped_edit_baton;
+ return SVN_NO_ERROR;
+ }
+
+ depth_filter_editor = svn_delta_default_editor(pool);
+ depth_filter_editor->set_target_revision = set_target_revision;
+ depth_filter_editor->open_root = open_root;
+ depth_filter_editor->delete_entry = delete_entry;
+ depth_filter_editor->add_directory = add_directory;
+ depth_filter_editor->open_directory = open_directory;
+ depth_filter_editor->change_dir_prop = change_dir_prop;
+ depth_filter_editor->close_directory = close_directory;
+ depth_filter_editor->absent_directory = absent_directory;
+ depth_filter_editor->add_file = add_file;
+ depth_filter_editor->open_file = open_file;
+ depth_filter_editor->apply_textdelta = apply_textdelta;
+ depth_filter_editor->change_file_prop = change_file_prop;
+ depth_filter_editor->close_file = close_file;
+ depth_filter_editor->absent_file = absent_file;
+ depth_filter_editor->close_edit = close_edit;
+
+ eb = apr_palloc(pool, sizeof(*eb));
+ eb->wrapped_editor = wrapped_editor;
+ eb->wrapped_edit_baton = wrapped_edit_baton;
+ eb->has_target = has_target;
+ eb->requested_depth = requested_depth;
+
+ *editor = depth_filter_editor;
+ *edit_baton = eb;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/editor.c b/contrib/subversion/subversion/libsvn_delta/editor.c
new file mode 100644
index 0000000..1dc94b2
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/editor.c
@@ -0,0 +1,956 @@
+/*
+ * editor.c : editing trees of versioned resources
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+
+#include "private/svn_editor.h"
+
+#ifdef SVN_DEBUG
+/* This enables runtime checks of the editor API constraints. This may
+ introduce additional memory and runtime overhead, and should not be used
+ in production builds.
+
+ ### Remove before release?
+
+ ### Disabled for now. If I call svn_editor_alter_directory(A) then
+ svn_editor_add_file(A/f) the latter fails on SHOULD_ALLOW_ADD.
+ If I modify svn_editor_alter_directory to MARK_ALLOW_ADD(child)
+ then if I call svn_editor_alter_directory(A) followed by
+ svn_editor_alter_directory(A/B/C) the latter fails on
+ VERIFY_PARENT_MAY_EXIST. */
+#if 0
+#define ENABLE_ORDERING_CHECK
+#endif
+#endif
+
+
+struct svn_editor_t
+{
+ void *baton;
+
+ /* Standard cancellation function. Called before each callback. */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ /* Our callback functions match that of the set-many structure, so
+ just use that. */
+ svn_editor_cb_many_t funcs;
+
+ /* This pool is used as the scratch_pool for all callbacks. */
+ apr_pool_t *scratch_pool;
+
+#ifdef ENABLE_ORDERING_CHECK
+ svn_boolean_t within_callback;
+
+ apr_hash_t *pending_incomplete_children;
+ apr_hash_t *completed_nodes;
+ svn_boolean_t finished;
+
+ apr_pool_t *state_pool;
+#endif
+};
+
+
+#ifdef ENABLE_ORDERING_CHECK
+
+#define START_CALLBACK(editor) \
+ do { \
+ svn_editor_t *editor__tmp_e = (editor); \
+ SVN_ERR_ASSERT(!editor__tmp_e->within_callback); \
+ editor__tmp_e->within_callback = TRUE; \
+ } while (0)
+#define END_CALLBACK(editor) ((editor)->within_callback = FALSE)
+
+/* Marker to indicate no further changes are allowed on this node. */
+static const int marker_done = 0;
+#define MARKER_DONE (&marker_done)
+
+/* Marker indicating that add_* may be called for this path, or that it
+ can be the destination of a copy or move. For copy/move, the path
+ will switch to MARKER_ALLOW_ALTER, to enable further tweaks. */
+static const int marker_allow_add = 0;
+#define MARKER_ALLOW_ADD (&marker_allow_add)
+
+/* Marker indicating that alter_* may be called for this path. */
+static const int marker_allow_alter = 0;
+#define MARKER_ALLOW_ALTER (&marker_allow_alter)
+
+/* Just like MARKER_DONE, but also indicates that the node was created
+ via add_directory(). This allows us to verify that the CHILDREN param
+ was comprehensive. */
+static const int marker_added_dir = 0;
+#define MARKER_ADDED_DIR (&marker_added_dir)
+
+#define MARK_FINISHED(editor) ((editor)->finished = TRUE)
+#define SHOULD_NOT_BE_FINISHED(editor) SVN_ERR_ASSERT(!(editor)->finished)
+
+#define CLEAR_INCOMPLETE(editor, relpath) \
+ svn_hash_sets((editor)->pending_incomplete_children, relpath, NULL);
+
+#define MARK_RELPATH(editor, relpath, value) \
+ svn_hash_sets((editor)->completed_nodes, \
+ apr_pstrdup((editor)->state_pool, relpath), value)
+
+#define MARK_COMPLETED(editor, relpath) \
+ MARK_RELPATH(editor, relpath, MARKER_DONE)
+#define SHOULD_NOT_BE_COMPLETED(editor, relpath) \
+ SVN_ERR_ASSERT(svn_hash_gets((editor)->completed_nodes, relpath) == NULL)
+
+#define MARK_ALLOW_ADD(editor, relpath) \
+ MARK_RELPATH(editor, relpath, MARKER_ALLOW_ADD)
+#define SHOULD_ALLOW_ADD(editor, relpath) \
+ SVN_ERR_ASSERT(allow_either(editor, relpath, MARKER_ALLOW_ADD, NULL))
+
+#define MARK_ALLOW_ALTER(editor, relpath) \
+ MARK_RELPATH(editor, relpath, MARKER_ALLOW_ALTER)
+#define SHOULD_ALLOW_ALTER(editor, relpath) \
+ SVN_ERR_ASSERT(allow_either(editor, relpath, MARKER_ALLOW_ALTER, NULL))
+
+#define MARK_ADDED_DIR(editor, relpath) \
+ MARK_RELPATH(editor, relpath, MARKER_ADDED_DIR)
+#define CHECK_UNKNOWN_CHILD(editor, relpath) \
+ SVN_ERR_ASSERT(check_unknown_child(editor, relpath))
+
+/* When a child is changed in some way, mark the parent directory as needing
+ to be "stable" (no future structural changes). IOW, only allow "alter" on
+ the parent. Prevents parent-add/delete/move after any child operation. */
+#define MARK_PARENT_STABLE(editor, relpath) \
+ mark_parent_stable(editor, relpath)
+
+/* If the parent is MARKER_ALLOW_ADD, then it has been moved-away, and we
+ know it does not exist. All other cases: it might exist. */
+#define VERIFY_PARENT_MAY_EXIST(editor, relpath) \
+ SVN_ERR_ASSERT(svn_hash_gets((editor)->completed_nodes, \
+ svn_relpath_dirname(relpath, \
+ (editor)->scratch_pool)) \
+ != MARKER_ALLOW_ADD)
+
+/* If the parent is MARKER_ADDED_DIR, then we should not be deleting
+ children(*). If the parent is MARKER_ALLOW_ADD, then it has been
+ moved-away, so children cannot exist. That leaves MARKER_DONE,
+ MARKER_ALLOW_ALTER, and NULL as possible values. Just assert that
+ we didn't get either of the bad ones.
+
+ (*) if the child as added via add_*(), then it would have been marked
+ as completed and delete/move-away already test against completed nodes.
+ This test is to beware of trying to delete "children" that are not
+ actually (and can't possibly be) present. */
+#define CHILD_DELETIONS_ALLOWED(editor, relpath) \
+ SVN_ERR_ASSERT(!allow_either(editor, \
+ svn_relpath_dirname(relpath, \
+ (editor)->scratch_pool), \
+ MARKER_ADDED_DIR, MARKER_ALLOW_ADD))
+
+static svn_boolean_t
+allow_either(const svn_editor_t *editor,
+ const char *relpath,
+ const void *marker1,
+ const void *marker2)
+{
+ void *value = svn_hash_gets(editor->completed_nodes, relpath);
+ return value == marker1 || value == marker2;
+}
+
+static svn_boolean_t
+check_unknown_child(const svn_editor_t *editor,
+ const char *relpath)
+{
+ const char *parent;
+
+ /* If we already know about the new child, then exit early. */
+ if (svn_hash_gets(editor->pending_incomplete_children, relpath) != NULL)
+ return TRUE;
+
+ parent = svn_relpath_dirname(relpath, editor->scratch_pool);
+
+ /* Was this parent created via svn_editor_add_directory() ? */
+ if (svn_hash_gets(editor->completed_nodes, parent)
+ == MARKER_ADDED_DIR)
+ {
+ /* Whoops. This child should have been listed in that add call,
+ and placed into ->pending_incomplete_children. */
+ return FALSE;
+ }
+
+ /* The parent was not added in this drive. */
+ return TRUE;
+}
+
+static void
+mark_parent_stable(const svn_editor_t *editor,
+ const char *relpath)
+{
+ const char *parent = svn_relpath_dirname(relpath, editor->scratch_pool);
+ const void *marker = svn_hash_gets(editor->completed_nodes, parent);
+
+ /* If RELPATH has already been marked (to disallow adds, or that it
+ has been fully-completed), then do nothing. */
+ if (marker == MARKER_ALLOW_ALTER
+ || marker == MARKER_DONE
+ || marker == MARKER_ADDED_DIR)
+ return;
+
+ /* If the marker is MARKER_ALLOW_ADD, then that means the parent was
+ moved away. There is no way to work on a child. That should have
+ been tested before we got here by VERIFY_PARENT_MAY_EXIST(). */
+ SVN_ERR_ASSERT_NO_RETURN(marker != MARKER_ALLOW_ADD);
+
+ /* MARKER is NULL. Upgrade it to MARKER_ALLOW_ALTER. */
+ MARK_RELPATH(editor, parent, MARKER_ALLOW_ALTER);
+}
+
+#else
+
+/* Be wary with the definition of these macros so that we don't
+ end up with "statement with no effect" warnings. Obviously, this
+ depends upon particular usage, which is easy to verify. */
+
+#define START_CALLBACK(editor) /* empty */
+#define END_CALLBACK(editor) /* empty */
+
+#define MARK_FINISHED(editor) /* empty */
+#define SHOULD_NOT_BE_FINISHED(editor) /* empty */
+
+#define CLEAR_INCOMPLETE(editor, relpath) /* empty */
+
+#define MARK_COMPLETED(editor, relpath) /* empty */
+#define SHOULD_NOT_BE_COMPLETED(editor, relpath) /* empty */
+
+#define MARK_ALLOW_ADD(editor, relpath) /* empty */
+#define SHOULD_ALLOW_ADD(editor, relpath) /* empty */
+
+#define MARK_ALLOW_ALTER(editor, relpath) /* empty */
+#define SHOULD_ALLOW_ALTER(editor, relpath) /* empty */
+
+#define MARK_ADDED_DIR(editor, relpath) /* empty */
+#define CHECK_UNKNOWN_CHILD(editor, relpath) /* empty */
+
+#define MARK_PARENT_STABLE(editor, relpath) /* empty */
+#define VERIFY_PARENT_MAY_EXIST(editor, relpath) /* empty */
+#define CHILD_DELETIONS_ALLOWED(editor, relpath) /* empty */
+
+#endif /* ENABLE_ORDERING_CHECK */
+
+
+svn_error_t *
+svn_editor_create(svn_editor_t **editor,
+ void *editor_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *editor = apr_pcalloc(result_pool, sizeof(**editor));
+
+ (*editor)->baton = editor_baton;
+ (*editor)->cancel_func = cancel_func;
+ (*editor)->cancel_baton = cancel_baton;
+ (*editor)->scratch_pool = svn_pool_create(result_pool);
+
+#ifdef ENABLE_ORDERING_CHECK
+ (*editor)->pending_incomplete_children = apr_hash_make(result_pool);
+ (*editor)->completed_nodes = apr_hash_make(result_pool);
+ (*editor)->finished = FALSE;
+ (*editor)->state_pool = result_pool;
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+
+void *
+svn_editor_get_baton(const svn_editor_t *editor)
+{
+ return editor->baton;
+}
+
+
+svn_error_t *
+svn_editor_setcb_add_directory(svn_editor_t *editor,
+ svn_editor_cb_add_directory_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_add_directory = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_add_file(svn_editor_t *editor,
+ svn_editor_cb_add_file_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_add_file = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_add_symlink(svn_editor_t *editor,
+ svn_editor_cb_add_symlink_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_add_symlink = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_add_absent(svn_editor_t *editor,
+ svn_editor_cb_add_absent_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_add_absent = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_alter_directory(svn_editor_t *editor,
+ svn_editor_cb_alter_directory_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_alter_directory = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_alter_file(svn_editor_t *editor,
+ svn_editor_cb_alter_file_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_alter_file = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_alter_symlink(svn_editor_t *editor,
+ svn_editor_cb_alter_symlink_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_alter_symlink = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_delete(svn_editor_t *editor,
+ svn_editor_cb_delete_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_delete = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_copy(svn_editor_t *editor,
+ svn_editor_cb_copy_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_copy = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_move(svn_editor_t *editor,
+ svn_editor_cb_move_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_move = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_rotate(svn_editor_t *editor,
+ svn_editor_cb_rotate_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_rotate = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_complete(svn_editor_t *editor,
+ svn_editor_cb_complete_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_complete = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_abort(svn_editor_t *editor,
+ svn_editor_cb_abort_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_abort = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_many(svn_editor_t *editor,
+ const svn_editor_cb_many_t *many,
+ apr_pool_t *scratch_pool)
+{
+#define COPY_CALLBACK(NAME) if (many->NAME) editor->funcs.NAME = many->NAME
+
+ COPY_CALLBACK(cb_add_directory);
+ COPY_CALLBACK(cb_add_file);
+ COPY_CALLBACK(cb_add_symlink);
+ COPY_CALLBACK(cb_add_absent);
+ COPY_CALLBACK(cb_alter_directory);
+ COPY_CALLBACK(cb_alter_file);
+ COPY_CALLBACK(cb_alter_symlink);
+ COPY_CALLBACK(cb_delete);
+ COPY_CALLBACK(cb_copy);
+ COPY_CALLBACK(cb_move);
+ COPY_CALLBACK(cb_rotate);
+ COPY_CALLBACK(cb_complete);
+ COPY_CALLBACK(cb_abort);
+
+#undef COPY_CALLBACK
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+check_cancel(svn_editor_t *editor)
+{
+ svn_error_t *err = NULL;
+
+ if (editor->cancel_func)
+ {
+ START_CALLBACK(editor);
+ err = editor->cancel_func(editor->cancel_baton);
+ END_CALLBACK(editor);
+ }
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_add_directory(svn_editor_t *editor,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT(children != NULL);
+ SVN_ERR_ASSERT(props != NULL);
+ /* ### validate children are just basenames? */
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ADD(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHECK_UNKNOWN_CHILD(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_add_directory)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_add_directory(editor->baton, relpath, children,
+ props, replaces_rev,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_ADDED_DIR(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+ CLEAR_INCOMPLETE(editor, relpath);
+
+#ifdef ENABLE_ORDERING_CHECK
+ {
+ int i;
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *child_basename = APR_ARRAY_IDX(children, i, const char *);
+ const char *child = svn_relpath_join(relpath, child_basename,
+ editor->state_pool);
+
+ svn_hash_sets(editor->pending_incomplete_children, child, "");
+ }
+ }
+#endif
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_add_file(svn_editor_t *editor,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT(checksum != NULL
+ && checksum->kind == SVN_EDITOR_CHECKSUM_KIND);
+ SVN_ERR_ASSERT(contents != NULL);
+ SVN_ERR_ASSERT(props != NULL);
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ADD(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHECK_UNKNOWN_CHILD(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_add_file)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_add_file(editor->baton, relpath,
+ checksum, contents, props,
+ replaces_rev, editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+ CLEAR_INCOMPLETE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_add_symlink(svn_editor_t *editor,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT(props != NULL);
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ADD(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHECK_UNKNOWN_CHILD(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_add_symlink)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_add_symlink(editor->baton, relpath, target, props,
+ replaces_rev, editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+ CLEAR_INCOMPLETE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_add_absent(svn_editor_t *editor,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t replaces_rev)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ADD(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHECK_UNKNOWN_CHILD(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_add_absent)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_add_absent(editor->baton, relpath, kind,
+ replaces_rev, editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+ CLEAR_INCOMPLETE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_alter_directory(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ apr_hash_t *props)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT(children != NULL || props != NULL);
+ /* ### validate children are just basenames? */
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ALTER(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_alter_directory)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_alter_directory(editor->baton,
+ relpath, revision,
+ children, props,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+
+#ifdef ENABLE_ORDERING_CHECK
+ /* ### this is not entirely correct. we probably need to adjust the
+ ### check_unknown_child() function for this scenario. */
+#if 0
+ {
+ int i;
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *child_basename = APR_ARRAY_IDX(children, i, const char *);
+ const char *child = svn_relpath_join(relpath, child_basename,
+ editor->state_pool);
+
+ apr_hash_set(editor->pending_incomplete_children, child,
+ APR_HASH_KEY_STRING, "");
+ /* Perhaps MARK_ALLOW_ADD(editor, child); ? */
+ }
+ }
+#endif
+#endif
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_alter_file(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT((checksum != NULL && contents != NULL)
+ || (checksum == NULL && contents == NULL));
+ SVN_ERR_ASSERT(props != NULL || checksum != NULL);
+ if (checksum)
+ SVN_ERR_ASSERT(checksum->kind == SVN_EDITOR_CHECKSUM_KIND);
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ALTER(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_alter_file)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_alter_file(editor->baton,
+ relpath, revision, props,
+ checksum, contents,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_alter_symlink(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT(props != NULL || target != NULL);
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ALTER(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_alter_symlink)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_alter_symlink(editor->baton,
+ relpath, revision, props,
+ target,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_delete(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_NOT_BE_COMPLETED(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHILD_DELETIONS_ALLOWED(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_delete)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_delete(editor->baton, relpath, revision,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_copy(svn_editor_t *editor,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath));
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(dst_relpath));
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ADD(editor, dst_relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, src_relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, dst_relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_copy)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_copy(editor->baton, src_relpath, src_revision,
+ dst_relpath, replaces_rev,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_ALLOW_ALTER(editor, dst_relpath);
+ MARK_PARENT_STABLE(editor, dst_relpath);
+ CLEAR_INCOMPLETE(editor, dst_relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_move(svn_editor_t *editor,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath));
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(dst_relpath));
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_NOT_BE_COMPLETED(editor, src_relpath);
+ SHOULD_ALLOW_ADD(editor, dst_relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, src_relpath);
+ CHILD_DELETIONS_ALLOWED(editor, src_relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, dst_relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_move)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_move(editor->baton, src_relpath, src_revision,
+ dst_relpath, replaces_rev,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_ALLOW_ADD(editor, src_relpath);
+ MARK_PARENT_STABLE(editor, src_relpath);
+ MARK_ALLOW_ALTER(editor, dst_relpath);
+ MARK_PARENT_STABLE(editor, dst_relpath);
+ CLEAR_INCOMPLETE(editor, dst_relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_rotate(svn_editor_t *editor,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SHOULD_NOT_BE_FINISHED(editor);
+#ifdef ENABLE_ORDERING_CHECK
+ {
+ int i;
+ for (i = 0; i < relpaths->nelts; i++)
+ {
+ const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *);
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SHOULD_NOT_BE_COMPLETED(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHILD_DELETIONS_ALLOWED(editor, relpath);
+ }
+ }
+#endif
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_rotate)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_rotate(editor->baton, relpaths, revisions,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+#ifdef ENABLE_ORDERING_CHECK
+ {
+ int i;
+ for (i = 0; i < relpaths->nelts; i++)
+ {
+ const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *);
+ MARK_ALLOW_ALTER(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+ }
+ }
+#endif
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_complete(svn_editor_t *editor)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SHOULD_NOT_BE_FINISHED(editor);
+#ifdef ENABLE_ORDERING_CHECK
+ SVN_ERR_ASSERT(apr_hash_count(editor->pending_incomplete_children) == 0);
+#endif
+
+ if (editor->funcs.cb_complete)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_complete(editor->baton, editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_FINISHED(editor);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_abort(svn_editor_t *editor)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SHOULD_NOT_BE_FINISHED(editor);
+
+ if (editor->funcs.cb_abort)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_abort(editor->baton, editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_FINISHED(editor);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/path_driver.c b/contrib/subversion/subversion/libsvn_delta/path_driver.c
new file mode 100644
index 0000000..62e703a
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/path_driver.c
@@ -0,0 +1,298 @@
+/*
+ * path_driver.c -- drive an editor across a set of paths
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+
+#include "svn_types.h"
+#include "svn_delta.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_sorts.h"
+#include "private/svn_fspath.h"
+
+
+/*** Helper functions. ***/
+
+typedef struct dir_stack_t
+{
+ void *dir_baton; /* the dir baton. */
+ apr_pool_t *pool; /* the pool associated with the dir baton. */
+
+} dir_stack_t;
+
+
+/* Call EDITOR's open_directory() function with the PATH argument, then
+ * add the resulting dir baton to the dir baton stack.
+ */
+static svn_error_t *
+open_dir(apr_array_header_t *db_stack,
+ const svn_delta_editor_t *editor,
+ const char *path,
+ apr_pool_t *pool)
+{
+ void *parent_db, *db;
+ dir_stack_t *item;
+ apr_pool_t *subpool;
+
+ /* Assert that we are in a stable state. */
+ SVN_ERR_ASSERT(db_stack && db_stack->nelts);
+
+ /* Get the parent dir baton. */
+ item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *);
+ parent_db = item->dir_baton;
+
+ /* Call the EDITOR's open_directory function to get a new directory
+ baton. */
+ subpool = svn_pool_create(pool);
+ SVN_ERR(editor->open_directory(path, parent_db, SVN_INVALID_REVNUM, subpool,
+ &db));
+
+ /* Now add the dir baton to the stack. */
+ item = apr_pcalloc(subpool, sizeof(*item));
+ item->dir_baton = db;
+ item->pool = subpool;
+ APR_ARRAY_PUSH(db_stack, dir_stack_t *) = item;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Pop a directory from the dir baton stack and update the stack
+ * pointer.
+ *
+ * This function calls the EDITOR's close_directory() function.
+ */
+static svn_error_t *
+pop_stack(apr_array_header_t *db_stack,
+ const svn_delta_editor_t *editor)
+{
+ dir_stack_t *item;
+
+ /* Assert that we are in a stable state. */
+ SVN_ERR_ASSERT(db_stack && db_stack->nelts);
+
+ /* Close the most recent directory pushed to the stack. */
+ item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, dir_stack_t *);
+ (void) apr_array_pop(db_stack);
+ SVN_ERR(editor->close_directory(item->dir_baton, item->pool));
+ svn_pool_destroy(item->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Count the number of path components in PATH. */
+static int
+count_components(const char *path)
+{
+ int count = 1;
+ const char *instance = path;
+
+ if ((strlen(path) == 1) && (path[0] == '/'))
+ return 0;
+
+ do
+ {
+ instance++;
+ instance = strchr(instance, '/');
+ if (instance)
+ count++;
+ }
+ while (instance);
+
+ return count;
+}
+
+
+
+/*** Public interfaces ***/
+svn_error_t *
+svn_delta_path_driver2(const svn_delta_editor_t *editor,
+ void *edit_baton,
+ const apr_array_header_t *paths,
+ svn_boolean_t sort_paths,
+ svn_delta_path_driver_cb_func_t callback_func,
+ void *callback_baton,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *db_stack = apr_array_make(pool, 4, sizeof(void *));
+ const char *last_path = NULL;
+ int i = 0;
+ void *parent_db = NULL, *db = NULL;
+ const char *path;
+ apr_pool_t *subpool, *iterpool;
+ dir_stack_t *item;
+
+ /* Do nothing if there are no paths. */
+ if (! paths->nelts)
+ return SVN_NO_ERROR;
+
+ subpool = svn_pool_create(pool);
+ iterpool = svn_pool_create(pool);
+
+ /* sort paths if necessary */
+ if (sort_paths && paths->nelts > 1)
+ {
+ apr_array_header_t *sorted = apr_array_copy(subpool, paths);
+ qsort(sorted->elts, sorted->nelts, sorted->elt_size,
+ svn_sort_compare_paths);
+ paths = sorted;
+ }
+
+ item = apr_pcalloc(subpool, sizeof(*item));
+
+ /* If the root of the edit is also a target path, we want to call
+ the callback function to let the user open the root directory and
+ do what needs to be done. Otherwise, we'll do the open_root()
+ ourselves. */
+ path = APR_ARRAY_IDX(paths, 0, const char *);
+ if (svn_path_is_empty(path))
+ {
+ SVN_ERR(callback_func(&db, NULL, callback_baton, path, subpool));
+ last_path = path;
+ i++;
+ }
+ else
+ {
+ SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM, subpool, &db));
+ }
+ item->pool = subpool;
+ item->dir_baton = db;
+ APR_ARRAY_PUSH(db_stack, void *) = item;
+
+ /* Now, loop over the commit items, traversing the URL tree and
+ driving the editor. */
+ for (; i < paths->nelts; i++)
+ {
+ const char *pdir, *bname;
+ const char *common = "";
+ size_t common_len;
+
+ /* Clear the iteration pool. */
+ svn_pool_clear(iterpool);
+
+ /* Get the next path. */
+ path = APR_ARRAY_IDX(paths, i, const char *);
+
+ /*** Step A - Find the common ancestor of the last path and the
+ current one. For the first iteration, this is just the
+ empty string. ***/
+ if (i > 0)
+ common = (last_path[0] == '/')
+ ? svn_fspath__get_longest_ancestor(last_path, path, iterpool)
+ : svn_relpath_get_longest_ancestor(last_path, path, iterpool);
+ common_len = strlen(common);
+
+ /*** Step B - Close any directories between the last path and
+ the new common ancestor, if any need to be closed.
+ Sometimes there is nothing to do here (like, for the first
+ iteration, or when the last path was an ancestor of the
+ current one). ***/
+ if ((i > 0) && (strlen(last_path) > common_len))
+ {
+ const char *rel = last_path + (common_len ? (common_len + 1) : 0);
+ int count = count_components(rel);
+ while (count--)
+ {
+ SVN_ERR(pop_stack(db_stack, editor));
+ }
+ }
+
+ /*** Step C - Open any directories between the common ancestor
+ and the parent of the current path. ***/
+ if (*path == '/')
+ svn_fspath__split(&pdir, &bname, path, iterpool);
+ else
+ svn_relpath_split(&pdir, &bname, path, iterpool);
+ if (strlen(pdir) > common_len)
+ {
+ const char *piece = pdir + common_len + 1;
+
+ while (1)
+ {
+ const char *rel = pdir;
+
+ /* Find the first separator. */
+ piece = strchr(piece, '/');
+
+ /* Calculate REL as the portion of PDIR up to (but not
+ including) the location to which PIECE is pointing. */
+ if (piece)
+ rel = apr_pstrmemdup(iterpool, pdir, piece - pdir);
+
+ /* Open the subdirectory. */
+ SVN_ERR(open_dir(db_stack, editor, rel, pool));
+
+ /* If we found a '/', advance our PIECE pointer to
+ character just after that '/'. Otherwise, we're
+ done. */
+ if (piece)
+ piece++;
+ else
+ break;
+ }
+ }
+
+ /*** Step D - Tell our caller to handle the current path. ***/
+ item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *);
+ parent_db = item->dir_baton;
+ subpool = svn_pool_create(pool);
+ SVN_ERR(callback_func(&db, parent_db, callback_baton, path, subpool));
+ if (db)
+ {
+ item = apr_pcalloc(subpool, sizeof(*item));
+ item->dir_baton = db;
+ item->pool = subpool;
+ APR_ARRAY_PUSH(db_stack, void *) = item;
+ }
+ else
+ {
+ svn_pool_destroy(subpool);
+ }
+
+ /*** Step E - Save our state for the next iteration. If our
+ caller opened or added PATH as a directory, that becomes
+ our LAST_PATH. Otherwise, we use PATH's parent
+ directory. ***/
+
+ /* NOTE: The variable LAST_PATH needs to outlive the loop. */
+ if (db)
+ last_path = path; /* lives in a pool outside our control. */
+ else
+ last_path = apr_pstrdup(pool, pdir); /* duping into POOL. */
+ }
+
+ /* Destroy the iteration subpool. */
+ svn_pool_destroy(iterpool);
+
+ /* Close down any remaining open directory batons. */
+ while (db_stack->nelts)
+ {
+ SVN_ERR(pop_stack(db_stack, editor));
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/svndiff.c b/contrib/subversion/subversion/libsvn_delta/svndiff.c
new file mode 100644
index 0000000..f4b9dc6
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/svndiff.c
@@ -0,0 +1,1103 @@
+/*
+ * svndiff.c -- Encoding and decoding svndiff-format deltas.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <assert.h>
+#include <string.h>
+#include "svn_delta.h"
+#include "svn_io.h"
+#include "delta.h"
+#include "svn_pools.h"
+#include "svn_private_config.h"
+#include <zlib.h>
+
+#include "private/svn_error_private.h"
+#include "private/svn_delta_private.h"
+
+/* The zlib compressBound function was not exported until 1.2.0. */
+#if ZLIB_VERNUM >= 0x1200
+#define svnCompressBound(LEN) compressBound(LEN)
+#else
+#define svnCompressBound(LEN) ((LEN) + ((LEN) >> 12) + ((LEN) >> 14) + 11)
+#endif
+
+/* For svndiff1, address/instruction/new data under this size will not
+ be compressed using zlib as a secondary compressor. */
+#define MIN_COMPRESS_SIZE 512
+
+/* ----- Text delta to svndiff ----- */
+
+/* We make one of these and get it passed back to us in calls to the
+ window handler. We only use it to record the write function and
+ baton passed to svn_txdelta_to_svndiff3(). */
+struct encoder_baton {
+ svn_stream_t *output;
+ svn_boolean_t header_done;
+ int version;
+ int compression_level;
+ apr_pool_t *pool;
+};
+
+/* This is at least as big as the largest size of an integer that
+ encode_int can generate; it is sufficient for creating buffers for
+ it to write into. This assumes that integers are at most 64 bits,
+ and so 10 bytes (with 7 bits of information each) are sufficient to
+ represent them. */
+#define MAX_ENCODED_INT_LEN 10
+/* This is at least as big as the largest size for a single instruction. */
+#define MAX_INSTRUCTION_LEN (2*MAX_ENCODED_INT_LEN+1)
+/* This is at least as big as the largest possible instructions
+ section: in theory, the instructions could be SVN_DELTA_WINDOW_SIZE
+ 1-byte copy-from-source instructions (though this is very unlikely). */
+#define MAX_INSTRUCTION_SECTION_LEN (SVN_DELTA_WINDOW_SIZE*MAX_INSTRUCTION_LEN)
+
+/* Encode VAL into the buffer P using the variable-length svndiff
+ integer format. Return the incremented value of P after the
+ encoded bytes have been written. P must point to a buffer of size
+ at least MAX_ENCODED_INT_LEN.
+
+ This encoding uses the high bit of each byte as a continuation bit
+ and the other seven bits as data bits. High-order data bits are
+ encoded first, followed by lower-order bits, so the value can be
+ reconstructed by concatenating the data bits from left to right and
+ interpreting the result as a binary number. Examples (brackets
+ denote byte boundaries, spaces are for clarity only):
+
+ 1 encodes as [0 0000001]
+ 33 encodes as [0 0100001]
+ 129 encodes as [1 0000001] [0 0000001]
+ 2000 encodes as [1 0001111] [0 1010000]
+*/
+static unsigned char *
+encode_int(unsigned char *p, svn_filesize_t val)
+{
+ int n;
+ svn_filesize_t v;
+ unsigned char cont;
+
+ SVN_ERR_ASSERT_NO_RETURN(val >= 0);
+
+ /* Figure out how many bytes we'll need. */
+ v = val >> 7;
+ n = 1;
+ while (v > 0)
+ {
+ v = v >> 7;
+ n++;
+ }
+
+ SVN_ERR_ASSERT_NO_RETURN(n <= MAX_ENCODED_INT_LEN);
+
+ /* Encode the remaining bytes; n is always the number of bytes
+ coming after the one we're encoding. */
+ while (--n >= 0)
+ {
+ cont = ((n > 0) ? 0x1 : 0x0) << 7;
+ *p++ = (unsigned char)(((val >> (n * 7)) & 0x7f) | cont);
+ }
+
+ return p;
+}
+
+
+/* Append an encoded integer to a string. */
+static void
+append_encoded_int(svn_stringbuf_t *header, svn_filesize_t val)
+{
+ unsigned char buf[MAX_ENCODED_INT_LEN], *p;
+
+ p = encode_int(buf, val);
+ svn_stringbuf_appendbytes(header, (const char *)buf, p - buf);
+}
+
+/* If IN is a string that is >= MIN_COMPRESS_SIZE and the COMPRESSION_LEVEL
+ is not SVN_DELTA_COMPRESSION_LEVEL_NONE, zlib compress it and places the
+ result in OUT, with an integer prepended specifying the original size.
+ If IN is < MIN_COMPRESS_SIZE, or if the compressed version of IN was no
+ smaller than the original IN, OUT will be a copy of IN with the size
+ prepended as an integer. */
+static svn_error_t *
+zlib_encode(const char *data,
+ apr_size_t len,
+ svn_stringbuf_t *out,
+ int compression_level)
+{
+ unsigned long endlen;
+ apr_size_t intlen;
+
+ svn_stringbuf_setempty(out);
+ append_encoded_int(out, len);
+ intlen = out->len;
+
+ /* Compression initialization overhead is considered to large for
+ short buffers. Also, if we don't actually want to compress data,
+ ZLIB will produce an output no shorter than the input. Hence,
+ the DATA would directly appended to OUT, so we can do that directly
+ without calling ZLIB before. */
+ if ( (len < MIN_COMPRESS_SIZE)
+ || (compression_level == SVN_DELTA_COMPRESSION_LEVEL_NONE))
+ {
+ svn_stringbuf_appendbytes(out, data, len);
+ }
+ else
+ {
+ int zerr;
+
+ svn_stringbuf_ensure(out, svnCompressBound(len) + intlen);
+ endlen = out->blocksize;
+
+ zerr = compress2((unsigned char *)out->data + intlen, &endlen,
+ (const unsigned char *)data, len,
+ compression_level);
+ if (zerr != Z_OK)
+ return svn_error_trace(svn_error__wrap_zlib(
+ zerr, "compress2",
+ _("Compression of svndiff data failed")));
+
+ /* Compression didn't help :(, just append the original text */
+ if (endlen >= len)
+ {
+ svn_stringbuf_appendbytes(out, data, len);
+ return SVN_NO_ERROR;
+ }
+ out->len = endlen + intlen;
+ out->data[out->len] = 0;
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+send_simple_insertion_window(svn_txdelta_window_t *window,
+ struct encoder_baton *eb)
+{
+ unsigned char headers[4 + 5 * MAX_ENCODED_INT_LEN + MAX_INSTRUCTION_LEN];
+ unsigned char ibuf[MAX_INSTRUCTION_LEN];
+ unsigned char *header_current;
+ apr_size_t header_len;
+ apr_size_t ip_len, i;
+ apr_size_t len = window->new_data->len;
+
+ /* there is only one target copy op. It must span the whole window */
+ assert(window->ops[0].action_code == svn_txdelta_new);
+ assert(window->ops[0].length == window->tview_len);
+ assert(window->ops[0].offset == 0);
+
+ /* write stream header if necessary */
+ if (!eb->header_done)
+ {
+ eb->header_done = TRUE;
+ headers[0] = 'S';
+ headers[1] = 'V';
+ headers[2] = 'N';
+ headers[3] = (unsigned char)eb->version;
+ header_current = headers + 4;
+ }
+ else
+ {
+ header_current = headers;
+ }
+
+ /* Encode the action code and length. */
+ if (window->tview_len >> 6 == 0)
+ {
+ ibuf[0] = (unsigned char)(window->tview_len + (0x2 << 6));
+ ip_len = 1;
+ }
+ else
+ {
+ ibuf[0] = (0x2 << 6);
+ ip_len = encode_int(ibuf + 1, window->tview_len) - ibuf;
+ }
+
+ /* encode the window header. Please note that the source window may
+ * have content despite not being used for deltification. */
+ header_current = encode_int(header_current, window->sview_offset);
+ header_current = encode_int(header_current, window->sview_len);
+ header_current = encode_int(header_current, window->tview_len);
+ header_current[0] = (unsigned char)ip_len; /* 1 instruction */
+ header_current = encode_int(&header_current[1], len);
+
+ /* append instructions (1 to a handful of bytes) */
+ for (i = 0; i < ip_len; ++i)
+ header_current[i] = ibuf[i];
+
+ header_len = header_current - headers + ip_len;
+
+ /* Write out the window. */
+ SVN_ERR(svn_stream_write(eb->output, (const char *)headers, &header_len));
+ if (len)
+ SVN_ERR(svn_stream_write(eb->output, window->new_data->data, &len));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct encoder_baton *eb = baton;
+ apr_pool_t *pool;
+ svn_stringbuf_t *instructions;
+ svn_stringbuf_t *i1;
+ svn_stringbuf_t *header;
+ const svn_string_t *newdata;
+ unsigned char ibuf[MAX_INSTRUCTION_LEN], *ip;
+ const svn_txdelta_op_t *op;
+ apr_size_t len;
+
+ /* use specialized code if there is no source */
+ if (window && !window->src_ops && window->num_ops == 1 && !eb->version)
+ return svn_error_trace(send_simple_insertion_window(window, eb));
+
+ /* Make sure we write the header. */
+ if (!eb->header_done)
+ {
+ char svnver[4] = {'S','V','N','\0'};
+ len = 4;
+ svnver[3] = (char)eb->version;
+ SVN_ERR(svn_stream_write(eb->output, svnver, &len));
+ eb->header_done = TRUE;
+ }
+
+ if (window == NULL)
+ {
+ svn_stream_t *output = eb->output;
+
+ /* We're done; clean up.
+
+ We clean our pool first. Given that the output stream was passed
+ TO us, we'll assume it has a longer lifetime, and that it will not
+ be affected by our pool destruction.
+
+ The contrary point of view (close the stream first): that could
+ tell our user that everything related to the output stream is done,
+ and a cleanup of the user pool should occur. However, that user
+ pool could include the subpool we created for our work (eb->pool),
+ which would then make our call to svn_pool_destroy() puke.
+ */
+ svn_pool_destroy(eb->pool);
+
+ return svn_stream_close(output);
+ }
+
+ /* create the necessary data buffers */
+ pool = svn_pool_create(eb->pool);
+ instructions = svn_stringbuf_create_empty(pool);
+ i1 = svn_stringbuf_create_empty(pool);
+ header = svn_stringbuf_create_empty(pool);
+
+ /* Encode the instructions. */
+ for (op = window->ops; op < window->ops + window->num_ops; op++)
+ {
+ /* Encode the action code and length. */
+ ip = ibuf;
+ switch (op->action_code)
+ {
+ case svn_txdelta_source: *ip = 0; break;
+ case svn_txdelta_target: *ip = (0x1 << 6); break;
+ case svn_txdelta_new: *ip = (0x2 << 6); break;
+ }
+ if (op->length >> 6 == 0)
+ *ip++ |= (unsigned char)op->length;
+ else
+ ip = encode_int(ip + 1, op->length);
+ if (op->action_code != svn_txdelta_new)
+ ip = encode_int(ip, op->offset);
+ svn_stringbuf_appendbytes(instructions, (const char *)ibuf, ip - ibuf);
+ }
+
+ /* Encode the header. */
+ append_encoded_int(header, window->sview_offset);
+ append_encoded_int(header, window->sview_len);
+ append_encoded_int(header, window->tview_len);
+ if (eb->version == 1)
+ {
+ SVN_ERR(zlib_encode(instructions->data, instructions->len,
+ i1, eb->compression_level));
+ instructions = i1;
+ }
+ append_encoded_int(header, instructions->len);
+ if (eb->version == 1)
+ {
+ svn_stringbuf_t *temp = svn_stringbuf_create_empty(pool);
+ svn_string_t *tempstr = svn_string_create_empty(pool);
+ SVN_ERR(zlib_encode(window->new_data->data, window->new_data->len,
+ temp, eb->compression_level));
+ tempstr->data = temp->data;
+ tempstr->len = temp->len;
+ newdata = tempstr;
+ }
+ else
+ newdata = window->new_data;
+
+ append_encoded_int(header, newdata->len);
+
+ /* Write out the window. */
+ len = header->len;
+ SVN_ERR(svn_stream_write(eb->output, header->data, &len));
+ if (instructions->len > 0)
+ {
+ len = instructions->len;
+ SVN_ERR(svn_stream_write(eb->output, instructions->data, &len));
+ }
+ if (newdata->len > 0)
+ {
+ len = newdata->len;
+ SVN_ERR(svn_stream_write(eb->output, newdata->data, &len));
+ }
+
+ svn_pool_destroy(pool);
+ return SVN_NO_ERROR;
+}
+
+void
+svn_txdelta_to_svndiff3(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ svn_stream_t *output,
+ int svndiff_version,
+ int compression_level,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ struct encoder_baton *eb;
+
+ eb = apr_palloc(subpool, sizeof(*eb));
+ eb->output = output;
+ eb->header_done = FALSE;
+ eb->pool = subpool;
+ eb->version = svndiff_version;
+ eb->compression_level = compression_level;
+
+ *handler = window_handler;
+ *handler_baton = eb;
+}
+
+void
+svn_txdelta_to_svndiff2(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ svn_stream_t *output,
+ int svndiff_version,
+ apr_pool_t *pool)
+{
+ svn_txdelta_to_svndiff3(handler, handler_baton, output, svndiff_version,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+}
+
+void
+svn_txdelta_to_svndiff(svn_stream_t *output,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ svn_txdelta_to_svndiff3(handler, handler_baton, output, 0,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+}
+
+
+/* ----- svndiff to text delta ----- */
+
+/* An svndiff parser object. */
+struct decode_baton
+{
+ /* Once the svndiff parser has enough data buffered to create a
+ "window", it passes this window to the caller's consumer routine. */
+ svn_txdelta_window_handler_t consumer_func;
+ void *consumer_baton;
+
+ /* Pool to create subpools from; each developing window will be a
+ subpool. */
+ apr_pool_t *pool;
+
+ /* The current subpool which contains our current window-buffer. */
+ apr_pool_t *subpool;
+
+ /* The actual svndiff data buffer, living within subpool. */
+ svn_stringbuf_t *buffer;
+
+ /* The offset and size of the last source view, so that we can check
+ to make sure the next one isn't sliding backwards. */
+ svn_filesize_t last_sview_offset;
+ apr_size_t last_sview_len;
+
+ /* We have to discard four bytes at the beginning for the header.
+ This field keeps track of how many of those bytes we have read. */
+ apr_size_t header_bytes;
+
+ /* Do we want an error to occur when we close the stream that
+ indicates we didn't send the whole svndiff data? If you plan to
+ not transmit the whole svndiff data stream, you will want this to
+ be FALSE. */
+ svn_boolean_t error_on_early_close;
+
+ /* svndiff version in use by delta. */
+ unsigned char version;
+};
+
+
+/* Decode an svndiff-encoded integer into *VAL and return a pointer to
+ the byte after the integer. The bytes to be decoded live in the
+ range [P..END-1]. If these bytes do not contain a whole encoded
+ integer, return NULL; in this case *VAL is undefined.
+
+ See the comment for encode_int() earlier in this file for more detail on
+ the encoding format. */
+static const unsigned char *
+decode_file_offset(svn_filesize_t *val,
+ const unsigned char *p,
+ const unsigned char *end)
+{
+ svn_filesize_t temp = 0;
+
+ if (p + MAX_ENCODED_INT_LEN < end)
+ end = p + MAX_ENCODED_INT_LEN;
+ /* Decode bytes until we're done. */
+ while (p < end)
+ {
+ /* Don't use svn_filesize_t here, because this might be 64 bits
+ * on 32 bit targets. Optimizing compilers may or may not be
+ * able to reduce that to the effective code below. */
+ unsigned int c = *p++;
+
+ temp = (temp << 7) | (c & 0x7f);
+ if (c < 0x80)
+ {
+ *val = temp;
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+
+/* Same as above, only decode into a size variable. */
+static const unsigned char *
+decode_size(apr_size_t *val,
+ const unsigned char *p,
+ const unsigned char *end)
+{
+ apr_size_t temp = 0;
+
+ if (p + MAX_ENCODED_INT_LEN < end)
+ end = p + MAX_ENCODED_INT_LEN;
+ /* Decode bytes until we're done. */
+ while (p < end)
+ {
+ apr_size_t c = *p++;
+
+ temp = (temp << 7) | (c & 0x7f);
+ if (c < 0x80)
+ {
+ *val = temp;
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+/* Decode the possibly-zlib compressed string of length INLEN that is in
+ IN, into OUT. We expect an integer is prepended to IN that specifies
+ the original size, and that if encoded size == original size, that the
+ remaining data is not compressed.
+ In that case, we will simply return pointer into IN as data pointer for
+ OUT, COPYLESS_ALLOWED has been set. The, the caller is expected not to
+ modify the contents of OUT.
+ An error is returned if the decoded length exceeds the given LIMIT.
+ */
+static svn_error_t *
+zlib_decode(const unsigned char *in, apr_size_t inLen, svn_stringbuf_t *out,
+ apr_size_t limit)
+{
+ apr_size_t len;
+ const unsigned char *oldplace = in;
+
+ /* First thing in the string is the original length. */
+ in = decode_size(&len, in, in + inLen);
+ if (in == NULL)
+ return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA, NULL,
+ _("Decompression of svndiff data failed: no size"));
+ if (len > limit)
+ return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA, NULL,
+ _("Decompression of svndiff data failed: "
+ "size too large"));
+ /* We need to subtract the size of the encoded original length off the
+ * still remaining input length. */
+ inLen -= (in - oldplace);
+ if (inLen == len)
+ {
+ svn_stringbuf_ensure(out, len);
+ memcpy(out->data, in, len);
+ out->data[len] = 0;
+ out->len = len;
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ unsigned long zlen = len;
+ int zerr;
+
+ svn_stringbuf_ensure(out, len);
+ zerr = uncompress((unsigned char *)out->data, &zlen, in, inLen);
+ if (zerr != Z_OK)
+ return svn_error_trace(svn_error__wrap_zlib(
+ zerr, "uncompress",
+ _("Decompression of svndiff data failed")));
+
+ /* Zlib should not produce something that has a different size than the
+ original length we stored. */
+ if (zlen != len)
+ return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA,
+ NULL,
+ _("Size of uncompressed data "
+ "does not match stored original length"));
+ out->data[zlen] = 0;
+ out->len = zlen;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Decode an instruction into OP, returning a pointer to the text
+ after the instruction. Note that if the action code is
+ svn_txdelta_new, the offset field of *OP will not be set. */
+static const unsigned char *
+decode_instruction(svn_txdelta_op_t *op,
+ const unsigned char *p,
+ const unsigned char *end)
+{
+ apr_size_t c;
+ apr_size_t action;
+
+ if (p == end)
+ return NULL;
+
+ /* We need this more than once */
+ c = *p++;
+
+ /* Decode the instruction selector. */
+ action = (c >> 6) & 0x3;
+ if (action >= 0x3)
+ return NULL;
+
+ /* This relies on enum svn_delta_action values to match and never to be
+ redefined. */
+ op->action_code = (enum svn_delta_action)(action);
+
+ /* Decode the length and offset. */
+ op->length = c & 0x3f;
+ if (op->length == 0)
+ {
+ p = decode_size(&op->length, p, end);
+ if (p == NULL)
+ return NULL;
+ }
+ if (action != svn_txdelta_new)
+ {
+ p = decode_size(&op->offset, p, end);
+ if (p == NULL)
+ return NULL;
+ }
+
+ return p;
+}
+
+/* Count the instructions in the range [P..END-1] and make sure they
+ are valid for the given window lengths. Return an error if the
+ instructions are invalid; otherwise set *NINST to the number of
+ instructions. */
+static svn_error_t *
+count_and_verify_instructions(int *ninst,
+ const unsigned char *p,
+ const unsigned char *end,
+ apr_size_t sview_len,
+ apr_size_t tview_len,
+ apr_size_t new_len)
+{
+ int n = 0;
+ svn_txdelta_op_t op;
+ apr_size_t tpos = 0, npos = 0;
+
+ while (p < end)
+ {
+ p = decode_instruction(&op, p, end);
+
+ /* Detect any malformed operations from the instruction stream. */
+ if (p == NULL)
+ return svn_error_createf
+ (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
+ _("Invalid diff stream: insn %d cannot be decoded"), n);
+ else if (op.length == 0)
+ return svn_error_createf
+ (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
+ _("Invalid diff stream: insn %d has length zero"), n);
+ else if (op.length > tview_len - tpos)
+ return svn_error_createf
+ (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
+ _("Invalid diff stream: insn %d overflows the target view"), n);
+
+ switch (op.action_code)
+ {
+ case svn_txdelta_source:
+ if (op.length > sview_len - op.offset ||
+ op.offset > sview_len)
+ return svn_error_createf
+ (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
+ _("Invalid diff stream: "
+ "[src] insn %d overflows the source view"), n);
+ break;
+ case svn_txdelta_target:
+ if (op.offset >= tpos)
+ return svn_error_createf
+ (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
+ _("Invalid diff stream: "
+ "[tgt] insn %d starts beyond the target view position"), n);
+ break;
+ case svn_txdelta_new:
+ if (op.length > new_len - npos)
+ return svn_error_createf
+ (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
+ _("Invalid diff stream: "
+ "[new] insn %d overflows the new data section"), n);
+ npos += op.length;
+ break;
+ }
+ tpos += op.length;
+ n++;
+ }
+ if (tpos != tview_len)
+ return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
+ _("Delta does not fill the target window"));
+ if (npos != new_len)
+ return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
+ _("Delta does not contain enough new data"));
+
+ *ninst = n;
+ return SVN_NO_ERROR;
+}
+
+/* Given the five integer fields of a window header and a pointer to
+ the remainder of the window contents, fill in a delta window
+ structure *WINDOW. New allocations will be performed in POOL;
+ the new_data field of *WINDOW will refer directly to memory pointed
+ to by DATA. */
+static svn_error_t *
+decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset,
+ apr_size_t sview_len, apr_size_t tview_len, apr_size_t inslen,
+ apr_size_t newlen, const unsigned char *data, apr_pool_t *pool,
+ unsigned int version)
+{
+ const unsigned char *insend;
+ int ninst;
+ apr_size_t npos;
+ svn_txdelta_op_t *ops, *op;
+ svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data));
+
+ window->sview_offset = sview_offset;
+ window->sview_len = sview_len;
+ window->tview_len = tview_len;
+
+ insend = data + inslen;
+
+ if (version == 1)
+ {
+ svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool);
+ svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool);
+
+ /* these may in fact simply return references to insend */
+
+ SVN_ERR(zlib_decode(insend, newlen, ndout,
+ SVN_DELTA_WINDOW_SIZE));
+ SVN_ERR(zlib_decode(data, insend - data, instout,
+ MAX_INSTRUCTION_SECTION_LEN));
+
+ newlen = ndout->len;
+ data = (unsigned char *)instout->data;
+ insend = (unsigned char *)instout->data + instout->len;
+
+ new_data->data = (const char *) ndout->data;
+ new_data->len = newlen;
+ }
+ else
+ {
+ new_data->data = (const char *) insend;
+ new_data->len = newlen;
+ }
+
+ /* Count the instructions and make sure they are all valid. */
+ SVN_ERR(count_and_verify_instructions(&ninst, data, insend,
+ sview_len, tview_len, newlen));
+
+ /* Allocate a buffer for the instructions and decode them. */
+ ops = apr_palloc(pool, ninst * sizeof(*ops));
+ npos = 0;
+ window->src_ops = 0;
+ for (op = ops; op < ops + ninst; op++)
+ {
+ data = decode_instruction(op, data, insend);
+ if (op->action_code == svn_txdelta_source)
+ ++window->src_ops;
+ else if (op->action_code == svn_txdelta_new)
+ {
+ op->offset = npos;
+ npos += op->length;
+ }
+ }
+ SVN_ERR_ASSERT(data == insend);
+
+ window->ops = ops;
+ window->num_ops = ninst;
+ window->new_data = new_data;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_handler(void *baton,
+ const char *buffer,
+ apr_size_t *len)
+{
+ struct decode_baton *db = (struct decode_baton *) baton;
+ const unsigned char *p, *end;
+ svn_filesize_t sview_offset;
+ apr_size_t sview_len, tview_len, inslen, newlen, remaining;
+ apr_size_t buflen = *len;
+
+ /* Chew up four bytes at the beginning for the header. */
+ if (db->header_bytes < 4)
+ {
+ apr_size_t nheader = 4 - db->header_bytes;
+ if (nheader > buflen)
+ nheader = buflen;
+ if (memcmp(buffer, "SVN\0" + db->header_bytes, nheader) == 0)
+ db->version = 0;
+ else if (memcmp(buffer, "SVN\1" + db->header_bytes, nheader) == 0)
+ db->version = 1;
+ else
+ return svn_error_create(SVN_ERR_SVNDIFF_INVALID_HEADER, NULL,
+ _("Svndiff has invalid header"));
+ buflen -= nheader;
+ buffer += nheader;
+ db->header_bytes += nheader;
+ }
+
+ /* Concatenate the old with the new. */
+ svn_stringbuf_appendbytes(db->buffer, buffer, buflen);
+
+ /* We have a buffer of svndiff data that might be good for:
+
+ a) an integral number of windows' worth of data - this is a
+ trivial case. Make windows from our data and ship them off.
+
+ b) a non-integral number of windows' worth of data - we shall
+ consume the integral portion of the window data, and then
+ somewhere in the following loop the decoding of the svndiff
+ data will run out of stuff to decode, and will simply return
+ SVN_NO_ERROR, anxiously awaiting more data.
+ */
+
+ while (1)
+ {
+ apr_pool_t *newpool;
+ svn_txdelta_window_t window;
+
+ /* Read the header, if we have enough bytes for that. */
+ p = (const unsigned char *) db->buffer->data;
+ end = (const unsigned char *) db->buffer->data + db->buffer->len;
+
+ p = decode_file_offset(&sview_offset, p, end);
+ if (p == NULL)
+ return SVN_NO_ERROR;
+
+ p = decode_size(&sview_len, p, end);
+ if (p == NULL)
+ return SVN_NO_ERROR;
+
+ p = decode_size(&tview_len, p, end);
+ if (p == NULL)
+ return SVN_NO_ERROR;
+
+ p = decode_size(&inslen, p, end);
+ if (p == NULL)
+ return SVN_NO_ERROR;
+
+ p = decode_size(&newlen, p, end);
+ if (p == NULL)
+ return SVN_NO_ERROR;
+
+ if (tview_len > SVN_DELTA_WINDOW_SIZE ||
+ sview_len > SVN_DELTA_WINDOW_SIZE ||
+ /* for svndiff1, newlen includes the original length */
+ newlen > SVN_DELTA_WINDOW_SIZE + MAX_ENCODED_INT_LEN ||
+ inslen > MAX_INSTRUCTION_SECTION_LEN)
+ return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
+ _("Svndiff contains a too-large window"));
+
+ /* Check for integer overflow. */
+ if (sview_offset < 0 || inslen + newlen < inslen
+ || sview_len + tview_len < sview_len
+ || (apr_size_t)sview_offset + sview_len < (apr_size_t)sview_offset)
+ return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
+ _("Svndiff contains corrupt window header"));
+
+ /* Check for source windows which slide backwards. */
+ if (sview_len > 0
+ && (sview_offset < db->last_sview_offset
+ || (sview_offset + sview_len
+ < db->last_sview_offset + db->last_sview_len)))
+ return svn_error_create
+ (SVN_ERR_SVNDIFF_BACKWARD_VIEW, NULL,
+ _("Svndiff has backwards-sliding source views"));
+
+ /* Wait for more data if we don't have enough bytes for the
+ whole window. */
+ if ((apr_size_t) (end - p) < inslen + newlen)
+ return SVN_NO_ERROR;
+
+ /* Decode the window and send it off. */
+ SVN_ERR(decode_window(&window, sview_offset, sview_len, tview_len,
+ inslen, newlen, p, db->subpool,
+ db->version));
+ SVN_ERR(db->consumer_func(&window, db->consumer_baton));
+
+ /* Make a new subpool and buffer, saving aside the remaining
+ data in the old buffer. */
+ newpool = svn_pool_create(db->pool);
+ p += inslen + newlen;
+ remaining = db->buffer->data + db->buffer->len - (const char *) p;
+ db->buffer =
+ svn_stringbuf_ncreate((const char *) p, remaining, newpool);
+
+ /* Remember the offset and length of the source view for next time. */
+ db->last_sview_offset = sview_offset;
+ db->last_sview_len = sview_len;
+
+ /* We've copied stuff out of the old pool. Toss that pool and use
+ our new pool.
+ ### might be nice to avoid the copy and just use svn_pool_clear
+ ### to get rid of whatever the "other stuff" is. future project...
+ */
+ svn_pool_destroy(db->subpool);
+ db->subpool = newpool;
+ }
+
+ /* NOTREACHED */
+}
+
+/* Minimal svn_stream_t write handler, doing nothing */
+static svn_error_t *
+noop_write_handler(void *baton,
+ const char *buffer,
+ apr_size_t *len)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_handler(void *baton)
+{
+ struct decode_baton *db = (struct decode_baton *) baton;
+ svn_error_t *err;
+
+ /* Make sure that we're at a plausible end of stream, returning an
+ error if we are expected to do so. */
+ if ((db->error_on_early_close)
+ && (db->header_bytes < 4 || db->buffer->len != 0))
+ return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
+ _("Unexpected end of svndiff input"));
+
+ /* Tell the window consumer that we're done, and clean up. */
+ err = db->consumer_func(NULL, db->consumer_baton);
+ svn_pool_destroy(db->pool);
+ return err;
+}
+
+
+svn_stream_t *
+svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ svn_boolean_t error_on_early_close,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ struct decode_baton *db = apr_palloc(pool, sizeof(*db));
+ svn_stream_t *stream;
+
+ db->consumer_func = handler;
+ db->consumer_baton = handler_baton;
+ db->pool = subpool;
+ db->subpool = svn_pool_create(subpool);
+ db->buffer = svn_stringbuf_create_empty(db->subpool);
+ db->last_sview_offset = 0;
+ db->last_sview_len = 0;
+ db->header_bytes = 0;
+ db->error_on_early_close = error_on_early_close;
+ stream = svn_stream_create(db, pool);
+
+ if (handler != svn_delta_noop_window_handler)
+ {
+ svn_stream_set_write(stream, write_handler);
+ svn_stream_set_close(stream, close_handler);
+ }
+ else
+ {
+ /* And else we just ignore everything as efficiently as we can.
+ by only hooking a no-op handler */
+ svn_stream_set_write(stream, noop_write_handler);
+ }
+ return stream;
+}
+
+
+/* Routines for reading one svndiff window at a time. */
+
+/* Read one byte from STREAM into *BYTE. */
+static svn_error_t *
+read_one_byte(unsigned char *byte, svn_stream_t *stream)
+{
+ char c;
+ apr_size_t len = 1;
+
+ SVN_ERR(svn_stream_read(stream, &c, &len));
+ if (len == 0)
+ return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
+ _("Unexpected end of svndiff input"));
+ *byte = (unsigned char) c;
+ return SVN_NO_ERROR;
+}
+
+/* Read and decode one integer from STREAM into *SIZE. */
+static svn_error_t *
+read_one_size(apr_size_t *size, svn_stream_t *stream)
+{
+ unsigned char c;
+
+ *size = 0;
+ while (1)
+ {
+ SVN_ERR(read_one_byte(&c, stream));
+ *size = (*size << 7) | (c & 0x7f);
+ if (!(c & 0x80))
+ break;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Read a window header from STREAM and check it for integer overflow. */
+static svn_error_t *
+read_window_header(svn_stream_t *stream, svn_filesize_t *sview_offset,
+ apr_size_t *sview_len, apr_size_t *tview_len,
+ apr_size_t *inslen, apr_size_t *newlen)
+{
+ unsigned char c;
+
+ /* Read the source view offset by hand, since it's not an apr_size_t. */
+ *sview_offset = 0;
+ while (1)
+ {
+ SVN_ERR(read_one_byte(&c, stream));
+ *sview_offset = (*sview_offset << 7) | (c & 0x7f);
+ if (!(c & 0x80))
+ break;
+ }
+
+ /* Read the four size fields. */
+ SVN_ERR(read_one_size(sview_len, stream));
+ SVN_ERR(read_one_size(tview_len, stream));
+ SVN_ERR(read_one_size(inslen, stream));
+ SVN_ERR(read_one_size(newlen, stream));
+
+ if (*tview_len > SVN_DELTA_WINDOW_SIZE ||
+ *sview_len > SVN_DELTA_WINDOW_SIZE ||
+ /* for svndiff1, newlen includes the original length */
+ *newlen > SVN_DELTA_WINDOW_SIZE + MAX_ENCODED_INT_LEN ||
+ *inslen > MAX_INSTRUCTION_SECTION_LEN)
+ return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
+ _("Svndiff contains a too-large window"));
+
+ /* Check for integer overflow. */
+ if (*sview_offset < 0 || *inslen + *newlen < *inslen
+ || *sview_len + *tview_len < *sview_len
+ || (apr_size_t)*sview_offset + *sview_len < (apr_size_t)*sview_offset)
+ return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
+ _("Svndiff contains corrupt window header"));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_txdelta_read_svndiff_window(svn_txdelta_window_t **window,
+ svn_stream_t *stream,
+ int svndiff_version,
+ apr_pool_t *pool)
+{
+ svn_filesize_t sview_offset;
+ apr_size_t sview_len, tview_len, inslen, newlen, len;
+ unsigned char *buf;
+
+ SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
+ &inslen, &newlen));
+ len = inslen + newlen;
+ buf = apr_palloc(pool, len);
+ SVN_ERR(svn_stream_read(stream, (char*)buf, &len));
+ if (len < inslen + newlen)
+ return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
+ _("Unexpected end of svndiff input"));
+ *window = apr_palloc(pool, sizeof(**window));
+ return decode_window(*window, sview_offset, sview_len, tview_len, inslen,
+ newlen, buf, pool, svndiff_version);
+}
+
+
+svn_error_t *
+svn_txdelta_skip_svndiff_window(apr_file_t *file,
+ int svndiff_version,
+ apr_pool_t *pool)
+{
+ svn_stream_t *stream = svn_stream_from_aprfile2(file, TRUE, pool);
+ svn_filesize_t sview_offset;
+ apr_size_t sview_len, tview_len, inslen, newlen;
+ apr_off_t offset;
+
+ SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
+ &inslen, &newlen));
+
+ offset = inslen + newlen;
+ return svn_io_file_seek(file, APR_CUR, &offset, pool);
+}
+
+
+svn_error_t *
+svn__compress(svn_string_t *in,
+ svn_stringbuf_t *out,
+ int compression_level)
+{
+ return zlib_encode(in->data, in->len, out, compression_level);
+}
+
+svn_error_t *
+svn__decompress(svn_string_t *in,
+ svn_stringbuf_t *out,
+ apr_size_t limit)
+{
+ return zlib_decode((const unsigned char*)in->data, in->len, out, limit);
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/text_delta.c b/contrib/subversion/subversion/libsvn_delta/text_delta.c
new file mode 100644
index 0000000..be2c434
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/text_delta.c
@@ -0,0 +1,1041 @@
+/*
+ * text-delta.c -- Internal text delta representation
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <assert.h>
+#include <string.h>
+
+#include <apr_general.h> /* for APR_INLINE */
+#include <apr_md5.h> /* for, um...MD5 stuff */
+
+#include "svn_delta.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "svn_checksum.h"
+
+#include "delta.h"
+
+
+/* Text delta stream descriptor. */
+
+struct svn_txdelta_stream_t {
+ /* Copied from parameters to svn_txdelta_stream_create. */
+ void *baton;
+ svn_txdelta_next_window_fn_t next_window;
+ svn_txdelta_md5_digest_fn_t md5_digest;
+};
+
+/* Delta stream baton. */
+struct txdelta_baton {
+ /* These are copied from parameters passed to svn_txdelta. */
+ svn_stream_t *source;
+ svn_stream_t *target;
+
+ /* Private data */
+ svn_boolean_t more_source; /* FALSE if source stream hit EOF. */
+ svn_boolean_t more; /* TRUE if there are more data in the pool. */
+ svn_filesize_t pos; /* Offset of next read in source file. */
+ char *buf; /* Buffer for input data. */
+
+ svn_checksum_ctx_t *context; /* If not NULL, the context for computing
+ the checksum. */
+ svn_checksum_t *checksum; /* If non-NULL, the checksum of TARGET. */
+
+ apr_pool_t *result_pool; /* For results (e.g. checksum) */
+};
+
+
+/* Target-push stream descriptor. */
+
+struct tpush_baton {
+ /* These are copied from parameters passed to svn_txdelta_target_push. */
+ svn_stream_t *source;
+ svn_txdelta_window_handler_t wh;
+ void *whb;
+ apr_pool_t *pool;
+
+ /* Private data */
+ char *buf;
+ svn_filesize_t source_offset;
+ apr_size_t source_len;
+ svn_boolean_t source_done;
+ apr_size_t target_len;
+};
+
+
+/* Text delta applicator. */
+
+struct apply_baton {
+ /* These are copied from parameters passed to svn_txdelta_apply. */
+ svn_stream_t *source;
+ svn_stream_t *target;
+
+ /* Private data. Between calls, SBUF contains the data from the
+ * last window's source view, as specified by SBUF_OFFSET and
+ * SBUF_LEN. The contents of TBUF are not interesting between
+ * calls. */
+ apr_pool_t *pool; /* Pool to allocate data from */
+ char *sbuf; /* Source buffer */
+ apr_size_t sbuf_size; /* Allocated source buffer space */
+ svn_filesize_t sbuf_offset; /* Offset of SBUF data in source stream */
+ apr_size_t sbuf_len; /* Length of SBUF data */
+ char *tbuf; /* Target buffer */
+ apr_size_t tbuf_size; /* Allocated target buffer space */
+
+ apr_md5_ctx_t md5_context; /* Leads to result_digest below. */
+ unsigned char *result_digest; /* MD5 digest of resultant fulltext;
+ must point to at least APR_MD5_DIGESTSIZE
+ bytes of storage. */
+
+ const char *error_info; /* Optional extra info for error returns. */
+};
+
+
+
+svn_txdelta_window_t *
+svn_txdelta__make_window(const svn_txdelta__ops_baton_t *build_baton,
+ apr_pool_t *pool)
+{
+ svn_txdelta_window_t *window;
+ svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data));
+
+ window = apr_palloc(pool, sizeof(*window));
+ window->sview_offset = 0;
+ window->sview_len = 0;
+ window->tview_len = 0;
+
+ window->num_ops = build_baton->num_ops;
+ window->src_ops = build_baton->src_ops;
+ window->ops = build_baton->ops;
+
+ /* just copy the fields over, rather than alloc/copying into a whole new
+ svn_string_t structure. */
+ /* ### would be much nicer if window->new_data were not a ptr... */
+ new_data->data = build_baton->new_data->data;
+ new_data->len = build_baton->new_data->len;
+ window->new_data = new_data;
+
+ return window;
+}
+
+
+/* Compute and return a delta window using the xdelta algorithm on
+ DATA, which contains SOURCE_LEN bytes of source data and TARGET_LEN
+ bytes of target data. SOURCE_OFFSET gives the offset of the source
+ data, and is simply copied into the window's sview_offset field. */
+static svn_txdelta_window_t *
+compute_window(const char *data, apr_size_t source_len, apr_size_t target_len,
+ svn_filesize_t source_offset, apr_pool_t *pool)
+{
+ svn_txdelta__ops_baton_t build_baton = { 0 };
+ svn_txdelta_window_t *window;
+
+ /* Compute the delta operations. */
+ build_baton.new_data = svn_stringbuf_create_empty(pool);
+
+ if (source_len == 0)
+ svn_txdelta__insert_op(&build_baton, svn_txdelta_new, 0, target_len, data,
+ pool);
+ else
+ svn_txdelta__xdelta(&build_baton, data, source_len, target_len, pool);
+
+ /* Create and return the delta window. */
+ window = svn_txdelta__make_window(&build_baton, pool);
+ window->sview_offset = source_offset;
+ window->sview_len = source_len;
+ window->tview_len = target_len;
+ return window;
+}
+
+
+
+svn_txdelta_window_t *
+svn_txdelta_window_dup(const svn_txdelta_window_t *window,
+ apr_pool_t *pool)
+{
+ svn_txdelta__ops_baton_t build_baton = { 0 };
+ svn_txdelta_window_t *new_window;
+ const apr_size_t ops_size = (window->num_ops * sizeof(*build_baton.ops));
+
+ build_baton.num_ops = window->num_ops;
+ build_baton.src_ops = window->src_ops;
+ build_baton.ops_size = window->num_ops;
+ build_baton.ops = apr_palloc(pool, ops_size);
+ memcpy(build_baton.ops, window->ops, ops_size);
+ build_baton.new_data =
+ svn_stringbuf_create_from_string(window->new_data, pool);
+
+ new_window = svn_txdelta__make_window(&build_baton, pool);
+ new_window->sview_offset = window->sview_offset;
+ new_window->sview_len = window->sview_len;
+ new_window->tview_len = window->tview_len;
+ return new_window;
+}
+
+/* This is a private interlibrary compatibility wrapper. */
+svn_txdelta_window_t *
+svn_txdelta__copy_window(const svn_txdelta_window_t *window,
+ apr_pool_t *pool);
+svn_txdelta_window_t *
+svn_txdelta__copy_window(const svn_txdelta_window_t *window,
+ apr_pool_t *pool)
+{
+ return svn_txdelta_window_dup(window, pool);
+}
+
+
+/* Insert a delta op into a delta window. */
+
+void
+svn_txdelta__insert_op(svn_txdelta__ops_baton_t *build_baton,
+ enum svn_delta_action opcode,
+ apr_size_t offset,
+ apr_size_t length,
+ const char *new_data,
+ apr_pool_t *pool)
+{
+ svn_txdelta_op_t *op;
+
+ /* Check if this op can be merged with the previous op. The delta
+ combiner sometimes generates such ops, and this is the obvious
+ place to make the check. */
+ if (build_baton->num_ops > 0)
+ {
+ op = &build_baton->ops[build_baton->num_ops - 1];
+ if (op->action_code == opcode
+ && (opcode == svn_txdelta_new
+ || op->offset + op->length == offset))
+ {
+ op->length += length;
+ if (opcode == svn_txdelta_new)
+ svn_stringbuf_appendbytes(build_baton->new_data,
+ new_data, length);
+ return;
+ }
+ }
+
+ /* Create space for the new op. */
+ if (build_baton->num_ops == build_baton->ops_size)
+ {
+ svn_txdelta_op_t *const old_ops = build_baton->ops;
+ int const new_ops_size = (build_baton->ops_size == 0
+ ? 16 : 2 * build_baton->ops_size);
+ build_baton->ops =
+ apr_palloc(pool, new_ops_size * sizeof(*build_baton->ops));
+
+ /* Copy any existing ops into the new array */
+ if (old_ops)
+ memcpy(build_baton->ops, old_ops,
+ build_baton->ops_size * sizeof(*build_baton->ops));
+ build_baton->ops_size = new_ops_size;
+ }
+
+ /* Insert the op. svn_delta_source and svn_delta_target are
+ just inserted. For svn_delta_new, the new data must be
+ copied into the window. */
+ op = &build_baton->ops[build_baton->num_ops];
+ switch (opcode)
+ {
+ case svn_txdelta_source:
+ ++build_baton->src_ops;
+ /*** FALLTHRU ***/
+ case svn_txdelta_target:
+ op->action_code = opcode;
+ op->offset = offset;
+ op->length = length;
+ break;
+ case svn_txdelta_new:
+ op->action_code = opcode;
+ op->offset = build_baton->new_data->len;
+ op->length = length;
+ svn_stringbuf_appendbytes(build_baton->new_data, new_data, length);
+ break;
+ default:
+ assert(!"unknown delta op.");
+ }
+
+ ++build_baton->num_ops;
+}
+
+apr_size_t
+svn_txdelta__remove_copy(svn_txdelta__ops_baton_t *build_baton,
+ apr_size_t max_len)
+{
+ svn_txdelta_op_t *op;
+ apr_size_t len = 0;
+
+ /* remove ops back to front */
+ while (build_baton->num_ops > 0)
+ {
+ op = &build_baton->ops[build_baton->num_ops-1];
+
+ /* we can't modify svn_txdelta_target ops -> stop there */
+ if (op->action_code == svn_txdelta_target)
+ break;
+
+ /* handle the case that we cannot remove the op entirely */
+ if (op->length + len > max_len)
+ {
+ /* truncate only insertions. Copies don't benefit
+ from being truncated. */
+ if (op->action_code == svn_txdelta_new)
+ {
+ build_baton->new_data->len -= max_len - len;
+ op->length -= max_len - len;
+ len = max_len;
+ }
+
+ break;
+ }
+
+ /* drop the op entirely */
+ if (op->action_code == svn_txdelta_new)
+ build_baton->new_data->len -= op->length;
+
+ len += op->length;
+ --build_baton->num_ops;
+ }
+
+ return len;
+}
+
+
+
+/* Generic delta stream functions. */
+
+svn_txdelta_stream_t *
+svn_txdelta_stream_create(void *baton,
+ svn_txdelta_next_window_fn_t next_window,
+ svn_txdelta_md5_digest_fn_t md5_digest,
+ apr_pool_t *pool)
+{
+ svn_txdelta_stream_t *stream = apr_palloc(pool, sizeof(*stream));
+
+ stream->baton = baton;
+ stream->next_window = next_window;
+ stream->md5_digest = md5_digest;
+
+ return stream;
+}
+
+svn_error_t *
+svn_txdelta_next_window(svn_txdelta_window_t **window,
+ svn_txdelta_stream_t *stream,
+ apr_pool_t *pool)
+{
+ return stream->next_window(window, stream->baton, pool);
+}
+
+const unsigned char *
+svn_txdelta_md5_digest(svn_txdelta_stream_t *stream)
+{
+ return stream->md5_digest(stream->baton);
+}
+
+
+
+static svn_error_t *
+txdelta_next_window(svn_txdelta_window_t **window,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct txdelta_baton *b = baton;
+ apr_size_t source_len = SVN_DELTA_WINDOW_SIZE;
+ apr_size_t target_len = SVN_DELTA_WINDOW_SIZE;
+
+ /* Read the source stream. */
+ if (b->more_source)
+ {
+ SVN_ERR(svn_stream_read(b->source, b->buf, &source_len));
+ b->more_source = (source_len == SVN_DELTA_WINDOW_SIZE);
+ }
+ else
+ source_len = 0;
+
+ /* Read the target stream. */
+ SVN_ERR(svn_stream_read(b->target, b->buf + source_len, &target_len));
+ b->pos += source_len;
+
+ if (target_len == 0)
+ {
+ /* No target data? We're done; return the final window. */
+ if (b->context != NULL)
+ SVN_ERR(svn_checksum_final(&b->checksum, b->context, b->result_pool));
+
+ *window = NULL;
+ b->more = FALSE;
+ return SVN_NO_ERROR;
+ }
+ else if (b->context != NULL)
+ SVN_ERR(svn_checksum_update(b->context, b->buf + source_len, target_len));
+
+ *window = compute_window(b->buf, source_len, target_len,
+ b->pos - source_len, pool);
+
+ /* That's it. */
+ return SVN_NO_ERROR;
+}
+
+
+static const unsigned char *
+txdelta_md5_digest(void *baton)
+{
+ struct txdelta_baton *b = baton;
+ /* If there are more windows for this stream, the digest has not yet
+ been calculated. */
+ if (b->more)
+ return NULL;
+
+ /* If checksumming has not been activated, there will be no digest. */
+ if (b->context == NULL)
+ return NULL;
+
+ /* The checksum should be there. */
+ return b->checksum->digest;
+}
+
+
+svn_error_t *
+svn_txdelta_run(svn_stream_t *source,
+ svn_stream_t *target,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ svn_checksum_kind_t checksum_kind,
+ svn_checksum_t **checksum,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ struct txdelta_baton tb = { 0 };
+ svn_txdelta_window_t *window;
+
+ tb.source = source;
+ tb.target = target;
+ tb.more_source = TRUE;
+ tb.more = TRUE;
+ tb.pos = 0;
+ tb.buf = apr_palloc(scratch_pool, 2 * SVN_DELTA_WINDOW_SIZE);
+ tb.result_pool = result_pool;
+
+ if (checksum != NULL)
+ tb.context = svn_checksum_ctx_create(checksum_kind, scratch_pool);
+
+ do
+ {
+ /* free the window (if any) */
+ svn_pool_clear(iterpool);
+
+ /* read in a single delta window */
+ SVN_ERR(txdelta_next_window(&window, &tb, iterpool));
+
+ /* shove it at the handler */
+ SVN_ERR((*handler)(window, handler_baton));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+ while (window != NULL);
+
+ svn_pool_destroy(iterpool);
+
+ if (checksum != NULL)
+ *checksum = tb.checksum; /* should be there! */
+
+ return SVN_NO_ERROR;
+}
+
+
+void
+svn_txdelta2(svn_txdelta_stream_t **stream,
+ svn_stream_t *source,
+ svn_stream_t *target,
+ svn_boolean_t calculate_checksum,
+ apr_pool_t *pool)
+{
+ struct txdelta_baton *b = apr_pcalloc(pool, sizeof(*b));
+
+ b->source = source;
+ b->target = target;
+ b->more_source = TRUE;
+ b->more = TRUE;
+ b->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE);
+ b->context = calculate_checksum
+ ? svn_checksum_ctx_create(svn_checksum_md5, pool)
+ : NULL;
+ b->result_pool = pool;
+
+ *stream = svn_txdelta_stream_create(b, txdelta_next_window,
+ txdelta_md5_digest, pool);
+}
+
+void
+svn_txdelta(svn_txdelta_stream_t **stream,
+ svn_stream_t *source,
+ svn_stream_t *target,
+ apr_pool_t *pool)
+{
+ svn_txdelta2(stream, source, target, TRUE, pool);
+}
+
+
+
+/* Functions for implementing a "target push" delta. */
+
+/* This is the write handler for a target-push delta stream. It reads
+ * source data, buffers target data, and fires off delta windows when
+ * the target data buffer is full. */
+static svn_error_t *
+tpush_write_handler(void *baton, const char *data, apr_size_t *len)
+{
+ struct tpush_baton *tb = baton;
+ apr_size_t chunk_len, data_len = *len;
+ apr_pool_t *pool = svn_pool_create(tb->pool);
+ svn_txdelta_window_t *window;
+
+ while (data_len > 0)
+ {
+ svn_pool_clear(pool);
+
+ /* Make sure we're all full up on source data, if possible. */
+ if (tb->source_len == 0 && !tb->source_done)
+ {
+ tb->source_len = SVN_DELTA_WINDOW_SIZE;
+ SVN_ERR(svn_stream_read(tb->source, tb->buf, &tb->source_len));
+ if (tb->source_len < SVN_DELTA_WINDOW_SIZE)
+ tb->source_done = TRUE;
+ }
+
+ /* Copy in the target data, up to SVN_DELTA_WINDOW_SIZE. */
+ chunk_len = SVN_DELTA_WINDOW_SIZE - tb->target_len;
+ if (chunk_len > data_len)
+ chunk_len = data_len;
+ memcpy(tb->buf + tb->source_len + tb->target_len, data, chunk_len);
+ data += chunk_len;
+ data_len -= chunk_len;
+ tb->target_len += chunk_len;
+
+ /* If we're full of target data, compute and fire off a window. */
+ if (tb->target_len == SVN_DELTA_WINDOW_SIZE)
+ {
+ window = compute_window(tb->buf, tb->source_len, tb->target_len,
+ tb->source_offset, pool);
+ SVN_ERR(tb->wh(window, tb->whb));
+ tb->source_offset += tb->source_len;
+ tb->source_len = 0;
+ tb->target_len = 0;
+ }
+ }
+
+ svn_pool_destroy(pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* This is the close handler for a target-push delta stream. It sends
+ * a final window if there is any buffered target data, and then sends
+ * a NULL window signifying the end of the window stream. */
+static svn_error_t *
+tpush_close_handler(void *baton)
+{
+ struct tpush_baton *tb = baton;
+ svn_txdelta_window_t *window;
+
+ /* Send a final window if we have any residual target data. */
+ if (tb->target_len > 0)
+ {
+ window = compute_window(tb->buf, tb->source_len, tb->target_len,
+ tb->source_offset, tb->pool);
+ SVN_ERR(tb->wh(window, tb->whb));
+ }
+
+ /* Send a final NULL window signifying the end. */
+ return tb->wh(NULL, tb->whb);
+}
+
+
+svn_stream_t *
+svn_txdelta_target_push(svn_txdelta_window_handler_t handler,
+ void *handler_baton, svn_stream_t *source,
+ apr_pool_t *pool)
+{
+ struct tpush_baton *tb;
+ svn_stream_t *stream;
+
+ /* Initialize baton. */
+ tb = apr_palloc(pool, sizeof(*tb));
+ tb->source = source;
+ tb->wh = handler;
+ tb->whb = handler_baton;
+ tb->pool = pool;
+ tb->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE);
+ tb->source_offset = 0;
+ tb->source_len = 0;
+ tb->source_done = FALSE;
+ tb->target_len = 0;
+
+ /* Create and return writable stream. */
+ stream = svn_stream_create(tb, pool);
+ svn_stream_set_write(stream, tpush_write_handler);
+ svn_stream_set_close(stream, tpush_close_handler);
+ return stream;
+}
+
+
+
+/* Functions for applying deltas. */
+
+/* Ensure that BUF has enough space for VIEW_LEN bytes. */
+static APR_INLINE svn_error_t *
+size_buffer(char **buf, apr_size_t *buf_size,
+ apr_size_t view_len, apr_pool_t *pool)
+{
+ if (view_len > *buf_size)
+ {
+ *buf_size *= 2;
+ if (*buf_size < view_len)
+ *buf_size = view_len;
+ SVN_ERR_ASSERT(APR_ALIGN_DEFAULT(*buf_size) >= *buf_size);
+ *buf = apr_palloc(pool, *buf_size);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy LEN bytes from SOURCE to TARGET, optimizing for the case where LEN
+ * is often very small. Return a pointer to the first byte after the copied
+ * target range, unlike standard memcpy(), as a potential further
+ * optimization for the caller.
+ *
+ * memcpy() is hard to tune for a wide range of buffer lengths. Therefore,
+ * it is often tuned for high throughput on large buffers and relatively
+ * low latency for mid-sized buffers (tens of bytes). However, the overhead
+ * for very small buffers (<10 bytes) is still high. Even passing the
+ * parameters, for instance, may take as long as copying 3 bytes.
+ *
+ * Because short copy sequences seem to be a common case, at least in
+ * "format 2" FSFS repositories, we copy them directly. Larger buffer sizes
+ * aren't hurt measurably by the exta 'if' clause. */
+static APR_INLINE char *
+fast_memcpy(char *target, const char *source, apr_size_t len)
+{
+ if (len > 7)
+ {
+ memcpy(target, source, len);
+ target += len;
+ }
+ else
+ {
+ /* memcpy is not exactly fast for small block sizes.
+ * Since they are common, let's run optimized code for them. */
+ const char *end = source + len;
+ for (; source != end; source++)
+ *(target++) = *source;
+ }
+
+ return target;
+}
+
+/* Copy LEN bytes from SOURCE to TARGET. Unlike memmove() or memcpy(),
+ * create repeating patterns if the source and target ranges overlap.
+ * Return a pointer to the first byte after the copied target range. */
+static APR_INLINE char *
+patterning_copy(char *target, const char *source, apr_size_t len)
+{
+ const char *end = source + len;
+
+ /* On many machines, we can do "chunky" copies. */
+
+#if SVN_UNALIGNED_ACCESS_IS_OK
+
+ if (end + sizeof(apr_uint32_t) <= target)
+ {
+ /* Source and target are at least 4 bytes apart, so we can copy in
+ * 4-byte chunks. */
+ for (; source + sizeof(apr_uint32_t) <= end;
+ source += sizeof(apr_uint32_t),
+ target += sizeof(apr_uint32_t))
+ *(apr_uint32_t *)(target) = *(apr_uint32_t *)(source);
+ }
+
+#endif
+
+ /* fall through to byte-wise copy (either for the below-chunk-size tail
+ * or the whole copy) */
+ for (; source != end; source++)
+ *(target++) = *source;
+
+ return target;
+}
+
+void
+svn_txdelta_apply_instructions(svn_txdelta_window_t *window,
+ const char *sbuf, char *tbuf,
+ apr_size_t *tlen)
+{
+ const svn_txdelta_op_t *op;
+ apr_size_t tpos = 0;
+
+ for (op = window->ops; op < window->ops + window->num_ops; op++)
+ {
+ const apr_size_t buf_len = (op->length < *tlen - tpos
+ ? op->length : *tlen - tpos);
+
+ /* Check some invariants common to all instructions. */
+ assert(tpos + op->length <= window->tview_len);
+
+ switch (op->action_code)
+ {
+ case svn_txdelta_source:
+ /* Copy from source area. */
+ assert(sbuf);
+ assert(op->offset + op->length <= window->sview_len);
+ fast_memcpy(tbuf + tpos, sbuf + op->offset, buf_len);
+ break;
+
+ case svn_txdelta_target:
+ /* Copy from target area. We can't use memcpy() or the like
+ * since we need a specific semantics for overlapping copies:
+ * they must result in repeating patterns.
+ * Note that most copies won't have overlapping source and
+ * target ranges (they are just a result of self-compressed
+ * data) but a small percentage will. */
+ assert(op->offset < tpos);
+ patterning_copy(tbuf + tpos, tbuf + op->offset, buf_len);
+ break;
+
+ case svn_txdelta_new:
+ /* Copy from window new area. */
+ assert(op->offset + op->length <= window->new_data->len);
+ fast_memcpy(tbuf + tpos,
+ window->new_data->data + op->offset,
+ buf_len);
+ break;
+
+ default:
+ assert(!"Invalid delta instruction code");
+ }
+
+ tpos += op->length;
+ if (tpos >= *tlen)
+ return; /* The buffer is full. */
+ }
+
+ /* Check that we produced the right amount of data. */
+ assert(tpos == window->tview_len);
+ *tlen = tpos;
+}
+
+/* This is a private interlibrary compatibility wrapper. */
+void
+svn_txdelta__apply_instructions(svn_txdelta_window_t *window,
+ const char *sbuf, char *tbuf,
+ apr_size_t *tlen);
+void
+svn_txdelta__apply_instructions(svn_txdelta_window_t *window,
+ const char *sbuf, char *tbuf,
+ apr_size_t *tlen)
+{
+ svn_txdelta_apply_instructions(window, sbuf, tbuf, tlen);
+}
+
+
+/* Apply WINDOW to the streams given by APPL. */
+static svn_error_t *
+apply_window(svn_txdelta_window_t *window, void *baton)
+{
+ struct apply_baton *ab = (struct apply_baton *) baton;
+ apr_size_t len;
+ svn_error_t *err;
+
+ if (window == NULL)
+ {
+ /* We're done; just clean up. */
+ if (ab->result_digest)
+ apr_md5_final(ab->result_digest, &(ab->md5_context));
+
+ err = svn_stream_close(ab->target);
+ svn_pool_destroy(ab->pool);
+
+ return err;
+ }
+
+ /* Make sure the source view didn't slide backwards. */
+ SVN_ERR_ASSERT(window->sview_len == 0
+ || (window->sview_offset >= ab->sbuf_offset
+ && (window->sview_offset + window->sview_len
+ >= ab->sbuf_offset + ab->sbuf_len)));
+
+ /* Make sure there's enough room in the target buffer. */
+ SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool));
+
+ /* Prepare the source buffer for reading from the input stream. */
+ if (window->sview_offset != ab->sbuf_offset
+ || window->sview_len > ab->sbuf_size)
+ {
+ char *old_sbuf = ab->sbuf;
+
+ /* Make sure there's enough room. */
+ SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len,
+ ab->pool));
+
+ /* If the existing view overlaps with the new view, copy the
+ * overlap to the beginning of the new buffer. */
+ if ( (apr_size_t)ab->sbuf_offset + ab->sbuf_len
+ > (apr_size_t)window->sview_offset)
+ {
+ apr_size_t start =
+ (apr_size_t)(window->sview_offset - ab->sbuf_offset);
+ memmove(ab->sbuf, old_sbuf + start, ab->sbuf_len - start);
+ ab->sbuf_len -= start;
+ }
+ else
+ ab->sbuf_len = 0;
+ ab->sbuf_offset = window->sview_offset;
+ }
+
+ /* Read the remainder of the source view into the buffer. */
+ if (ab->sbuf_len < window->sview_len)
+ {
+ len = window->sview_len - ab->sbuf_len;
+ err = svn_stream_read(ab->source, ab->sbuf + ab->sbuf_len, &len);
+ if (err == SVN_NO_ERROR && len != window->sview_len - ab->sbuf_len)
+ err = svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
+ "Delta source ended unexpectedly");
+ if (err != SVN_NO_ERROR)
+ return err;
+ ab->sbuf_len = window->sview_len;
+ }
+
+ /* Apply the window instructions to the source view to generate
+ the target view. */
+ len = window->tview_len;
+ svn_txdelta_apply_instructions(window, ab->sbuf, ab->tbuf, &len);
+ SVN_ERR_ASSERT(len == window->tview_len);
+
+ /* Write out the output. */
+
+ /* ### We've also considered just adding two (optionally null)
+ arguments to svn_stream_create(): read_checksum and
+ write_checksum. Then instead of every caller updating an md5
+ context when it calls svn_stream_write() or svn_stream_read(),
+ streams would do it automatically, and verify the checksum in
+ svn_stream_closed(). But this might be overkill for issue #689;
+ so for now we just update the context here. */
+ if (ab->result_digest)
+ apr_md5_update(&(ab->md5_context), ab->tbuf, len);
+
+ return svn_stream_write(ab->target, ab->tbuf, &len);
+}
+
+
+void
+svn_txdelta_apply(svn_stream_t *source,
+ svn_stream_t *target,
+ unsigned char *result_digest,
+ const char *error_info,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ struct apply_baton *ab;
+
+ ab = apr_palloc(subpool, sizeof(*ab));
+ ab->source = source;
+ ab->target = target;
+ ab->pool = subpool;
+ ab->sbuf = NULL;
+ ab->sbuf_size = 0;
+ ab->sbuf_offset = 0;
+ ab->sbuf_len = 0;
+ ab->tbuf = NULL;
+ ab->tbuf_size = 0;
+ ab->result_digest = result_digest;
+
+ if (result_digest)
+ apr_md5_init(&(ab->md5_context));
+
+ if (error_info)
+ ab->error_info = apr_pstrdup(subpool, error_info);
+ else
+ ab->error_info = NULL;
+
+ *handler = apply_window;
+ *handler_baton = ab;
+}
+
+
+
+/* Convenience routines */
+
+svn_error_t *
+svn_txdelta_send_string(const svn_string_t *string,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_txdelta_window_t window = { 0 };
+ svn_txdelta_op_t op;
+
+ /* Build a single `new' op */
+ op.action_code = svn_txdelta_new;
+ op.offset = 0;
+ op.length = string->len;
+
+ /* Build a single window containing a ptr to the string. */
+ window.tview_len = string->len;
+ window.num_ops = 1;
+ window.ops = &op;
+ window.new_data = string;
+
+ /* Push the one window at the handler. */
+ SVN_ERR((*handler)(&window, handler_baton));
+
+ /* Push a NULL at the handler, because we're done. */
+ return (*handler)(NULL, handler_baton);
+}
+
+svn_error_t *svn_txdelta_send_stream(svn_stream_t *stream,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ unsigned char *digest,
+ apr_pool_t *pool)
+{
+ svn_txdelta_window_t delta_window = { 0 };
+ svn_txdelta_op_t delta_op;
+ svn_string_t window_data;
+ char read_buf[SVN__STREAM_CHUNK_SIZE + 1];
+ svn_checksum_ctx_t *md5_checksum_ctx;
+
+ if (digest)
+ md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+
+ while (1)
+ {
+ apr_size_t read_len = SVN__STREAM_CHUNK_SIZE;
+
+ SVN_ERR(svn_stream_read(stream, read_buf, &read_len));
+ if (read_len == 0)
+ break;
+
+ window_data.data = read_buf;
+ window_data.len = read_len;
+
+ delta_op.action_code = svn_txdelta_new;
+ delta_op.offset = 0;
+ delta_op.length = read_len;
+
+ delta_window.tview_len = read_len;
+ delta_window.num_ops = 1;
+ delta_window.ops = &delta_op;
+ delta_window.new_data = &window_data;
+
+ SVN_ERR(handler(&delta_window, handler_baton));
+
+ if (digest)
+ SVN_ERR(svn_checksum_update(md5_checksum_ctx, read_buf, read_len));
+
+ if (read_len < SVN__STREAM_CHUNK_SIZE)
+ break;
+ }
+ SVN_ERR(handler(NULL, handler_baton));
+
+ if (digest)
+ {
+ svn_checksum_t *md5_checksum;
+
+ SVN_ERR(svn_checksum_final(&md5_checksum, md5_checksum_ctx, pool));
+ memcpy(digest, md5_checksum->digest, APR_MD5_DIGESTSIZE);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_txdelta_send_txstream(svn_txdelta_stream_t *txstream,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_txdelta_window_t *window;
+
+ /* create a pool just for the windows */
+ apr_pool_t *wpool = svn_pool_create(pool);
+
+ do
+ {
+ /* free the window (if any) */
+ svn_pool_clear(wpool);
+
+ /* read in a single delta window */
+ SVN_ERR(svn_txdelta_next_window(&window, txstream, wpool));
+
+ /* shove it at the handler */
+ SVN_ERR((*handler)(window, handler_baton));
+ }
+ while (window != NULL);
+
+ svn_pool_destroy(wpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_txdelta_send_contents(const unsigned char *contents,
+ apr_size_t len,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_string_t new_data;
+ svn_txdelta_op_t op = { svn_txdelta_new, 0, 0 };
+ svn_txdelta_window_t window = { 0, 0, 0, 1, 0 };
+ window.ops = &op;
+ window.new_data = &new_data;
+
+ /* send CONTENT as a series of max-sized windows */
+ while (len > 0)
+ {
+ /* stuff next chunk into the window */
+ window.tview_len = len < SVN_DELTA_WINDOW_SIZE
+ ? len
+ : SVN_DELTA_WINDOW_SIZE;
+ op.length = window.tview_len;
+ new_data.len = window.tview_len;
+ new_data.data = (const char*)contents;
+
+ /* update remaining */
+ contents += window.tview_len;
+ len -= window.tview_len;
+
+ /* shove it at the handler */
+ SVN_ERR((*handler)(&window, handler_baton));
+ }
+
+ /* indicate end of stream */
+ SVN_ERR((*handler)(NULL, handler_baton));
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_delta/version.c b/contrib/subversion/subversion/libsvn_delta/version.c
new file mode 100644
index 0000000..ffbfb0f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/version.c
@@ -0,0 +1,33 @@
+/*
+ * version.c: library version number
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_version.h"
+#include "svn_delta.h"
+
+const svn_version_t *
+svn_delta_version(void)
+{
+ SVN_VERSION_BODY;
+}
diff --git a/contrib/subversion/subversion/libsvn_delta/xdelta.c b/contrib/subversion/subversion/libsvn_delta/xdelta.c
new file mode 100644
index 0000000..2075a51
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_delta/xdelta.c
@@ -0,0 +1,514 @@
+/*
+ * xdelta.c: xdelta generator.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <assert.h>
+
+#include <apr_general.h> /* for APR_INLINE */
+#include <apr_hash.h>
+
+#include "svn_hash.h"
+#include "svn_delta.h"
+#include "delta.h"
+
+/* This is pseudo-adler32. It is adler32 without the prime modulus.
+ The idea is borrowed from monotone, and is a translation of the C++
+ code. Graydon Hoare, the author of the original code, gave his
+ explicit permission to use it under these terms at 8:02pm on
+ Friday, February 11th, 2005. */
+
+/* Size of the blocks we compute checksums for. This was chosen out of
+ thin air. Monotone used 64, xdelta1 used 64, rsync uses 128.
+ However, later optimizations assume it to be 256 or less.
+ */
+#define MATCH_BLOCKSIZE 64
+
+/* "no" / "invalid" / "unused" value for positions within the delta windows
+ */
+#define NO_POSITION ((apr_uint32_t)-1)
+
+/* Feed C_IN into the adler32 checksum and remove C_OUT at the same time.
+ * This function may (and will) only be called for characters that are
+ * MATCH_BLOCKSIZE positions apart.
+ *
+ * Please note that the lower 16 bits cannot overflow in neither direction.
+ * Therefore, we don't need to split the value into separate values for
+ * sum(char) and sum(sum(char)).
+ */
+static APR_INLINE apr_uint32_t
+adler32_replace(apr_uint32_t adler32, const char c_out, const char c_in)
+{
+ adler32 -= (MATCH_BLOCKSIZE * 0x10000u * ((unsigned char) c_out));
+
+ adler32 -= (unsigned char)c_out;
+ adler32 += (unsigned char)c_in;
+
+ return adler32 + adler32 * 0x10000;
+}
+
+/* Calculate an pseudo-adler32 checksum for MATCH_BLOCKSIZE bytes starting
+ at DATA. Return the checksum value. */
+
+static APR_INLINE apr_uint32_t
+init_adler32(const char *data)
+{
+ const unsigned char *input = (const unsigned char *)data;
+ const unsigned char *last = input + MATCH_BLOCKSIZE;
+
+ apr_uint32_t s1 = 0;
+ apr_uint32_t s2 = 0;
+
+ for (; input < last; input += 8)
+ {
+ s1 += input[0]; s2 += s1;
+ s1 += input[1]; s2 += s1;
+ s1 += input[2]; s2 += s1;
+ s1 += input[3]; s2 += s1;
+ s1 += input[4]; s2 += s1;
+ s1 += input[5]; s2 += s1;
+ s1 += input[6]; s2 += s1;
+ s1 += input[7]; s2 += s1;
+ }
+
+ return s2 * 0x10000 + s1;
+}
+
+/* Information for a block of the delta source. The length of the
+ block is the smaller of MATCH_BLOCKSIZE and the difference between
+ the size of the source data and the position of this block. */
+struct block
+{
+ apr_uint32_t adlersum;
+
+/* Even in 64 bit systems, store only 32 bit offsets in our hash table
+ (our delta window size much much smaller then 4GB).
+ That reduces the hash table size by 50% from 32to 16KB
+ and makes it easier to fit into the CPU's L1 cache. */
+ apr_uint32_t pos; /* NO_POSITION -> block is not used */
+};
+
+/* A hash table, using open addressing, of the blocks of the source. */
+struct blocks
+{
+ /* The largest valid index of slots.
+ This value has an upper bound proportionate to the text delta
+ window size, so unless we dramatically increase the window size,
+ it's safe to make this a 32-bit value. In any case, it has to be
+ hte same width as the block position index, (struct
+ block).pos. */
+ apr_uint32_t max;
+ /* Source buffer that the positions in SLOTS refer to. */
+ const char* data;
+ /* The vector of blocks. A pos value of NO_POSITION represents an unused
+ slot. */
+ struct block *slots;
+};
+
+
+/* Return a hash value calculated from the adler32 SUM, suitable for use with
+ our hash table. */
+static apr_uint32_t hash_func(apr_uint32_t sum)
+{
+ /* Since the adl32 checksum have a bad distribution for the 11th to 16th
+ bits when used for our small block size, we add some bits from the
+ other half of the checksum. */
+ return sum ^ (sum >> 12);
+}
+
+/* Insert a block with the checksum ADLERSUM at position POS in the source
+ data into the table BLOCKS. Ignore true duplicates, i.e. blocks with
+ actually the same content. */
+static void
+add_block(struct blocks *blocks, apr_uint32_t adlersum, apr_uint32_t pos)
+{
+ apr_uint32_t h = hash_func(adlersum) & blocks->max;
+
+ /* This will terminate, since we know that we will not fill the table. */
+ for (; blocks->slots[h].pos != NO_POSITION; h = (h + 1) & blocks->max)
+ if (blocks->slots[h].adlersum == adlersum)
+ if (memcmp(blocks->data + blocks->slots[h].pos, blocks->data + pos,
+ MATCH_BLOCKSIZE) == 0)
+ return;
+
+ blocks->slots[h].adlersum = adlersum;
+ blocks->slots[h].pos = pos;
+}
+
+/* Find a block in BLOCKS with the checksum ADLERSUM and matching the content
+ at DATA, returning its position in the source data. If there is no such
+ block, return NO_POSITION. */
+static apr_uint32_t
+find_block(const struct blocks *blocks,
+ apr_uint32_t adlersum,
+ const char* data)
+{
+ apr_uint32_t h = hash_func(adlersum) & blocks->max;
+
+ for (; blocks->slots[h].pos != NO_POSITION; h = (h + 1) & blocks->max)
+ if (blocks->slots[h].adlersum == adlersum)
+ if (memcmp(blocks->data + blocks->slots[h].pos, data,
+ MATCH_BLOCKSIZE) == 0)
+ return blocks->slots[h].pos;
+
+ return NO_POSITION;
+}
+
+/* Initialize the matches table from DATA of size DATALEN. This goes
+ through every block of MATCH_BLOCKSIZE bytes in the source and
+ checksums it, inserting the result into the BLOCKS table. */
+static void
+init_blocks_table(const char *data,
+ apr_size_t datalen,
+ struct blocks *blocks,
+ apr_pool_t *pool)
+{
+ apr_size_t nblocks;
+ apr_size_t wnslots = 1;
+ apr_uint32_t nslots;
+ apr_uint32_t i;
+
+ /* Be pessimistic about the block count. */
+ nblocks = datalen / MATCH_BLOCKSIZE + 1;
+ /* Find nearest larger power of two. */
+ while (wnslots <= nblocks)
+ wnslots *= 2;
+ /* Double the number of slots to avoid a too high load. */
+ wnslots *= 2;
+ /* Narrow the number of slots to 32 bits, which is the size of the
+ block position index in the hash table.
+ Sanity check: On 64-bit platforms, apr_size_t is likely to be
+ larger than apr_uint32_t. Make sure that the number of slots
+ actually fits into blocks->max. It's safe to use a hard assert
+ here, because the largest possible value for nslots is
+ proportional to the text delta window size and is therefore much
+ smaller than the range of an apr_uint32_t. If we ever happen to
+ increase the window size too much, this assertion will get
+ triggered by the test suite. */
+ nslots = (apr_uint32_t) wnslots;
+ SVN_ERR_ASSERT_NO_RETURN(wnslots == nslots);
+ blocks->max = nslots - 1;
+ blocks->data = data;
+ blocks->slots = apr_palloc(pool, nslots * sizeof(*(blocks->slots)));
+ for (i = 0; i < nslots; ++i)
+ {
+ /* Avoid using an indeterminate value in the lookup. */
+ blocks->slots[i].adlersum = 0;
+ blocks->slots[i].pos = NO_POSITION;
+ }
+
+ /* If there is an odd block at the end of the buffer, we will
+ not use that shorter block for deltification (only indirectly
+ as an extension of some previous block). */
+ for (i = 0; i + MATCH_BLOCKSIZE <= datalen; i += MATCH_BLOCKSIZE)
+ add_block(blocks, init_adler32(data + i), i);
+}
+
+/* Return the lowest position at which A and B differ. If no difference
+ * can be found in the first MAX_LEN characters, MAX_LEN will be returned.
+ */
+static apr_size_t
+match_length(const char *a, const char *b, apr_size_t max_len)
+{
+ apr_size_t pos = 0;
+
+#if SVN_UNALIGNED_ACCESS_IS_OK
+
+ /* Chunky processing is so much faster ...
+ *
+ * We can't make this work on architectures that require aligned access
+ * because A and B will probably have different alignment. So, skipping
+ * the first few chars until alignment is reached is not an option.
+ */
+ for (; pos + sizeof(apr_size_t) <= max_len; pos += sizeof(apr_size_t))
+ if (*(const apr_size_t*)(a + pos) != *(const apr_size_t*)(b + pos))
+ break;
+
+#endif
+
+ for (; pos < max_len; ++pos)
+ if (a[pos] != b[pos])
+ break;
+
+ return pos;
+}
+
+/* Return the number of bytes before A and B that don't differ. If no
+ * difference can be found in the first MAX_LEN characters, MAX_LEN will
+ * be returned. Please note that A-MAX_LEN and B-MAX_LEN must both be
+ * valid addresses.
+ */
+static apr_size_t
+reverse_match_length(const char *a, const char *b, apr_size_t max_len)
+{
+ apr_size_t pos = 0;
+
+#if SVN_UNALIGNED_ACCESS_IS_OK
+
+ /* Chunky processing is so much faster ...
+ *
+ * We can't make this work on architectures that require aligned access
+ * because A and B will probably have different alignment. So, skipping
+ * the first few chars until alignment is reached is not an option.
+ */
+ for (pos = sizeof(apr_size_t); pos <= max_len; pos += sizeof(apr_size_t))
+ if (*(const apr_size_t*)(a - pos) != *(const apr_size_t*)(b - pos))
+ break;
+
+ pos -= sizeof(apr_size_t);
+
+#endif
+
+ /* If we find a mismatch at -pos, pos-1 characters matched.
+ */
+ while (++pos <= max_len)
+ if (a[0-pos] != b[0-pos])
+ return pos - 1;
+
+ /* No mismatch found -> at least MAX_LEN matching chars.
+ */
+ return max_len;
+}
+
+
+/* Try to find a match for the target data B in BLOCKS, and then
+ extend the match as long as data in A and B at the match position
+ continues to match. We set the position in A we ended up in (in
+ case we extended it backwards) in APOSP and update the corresponding
+ position within B given in BPOSP. PENDING_INSERT_START sets the
+ lower limit to BPOSP.
+ Return number of matching bytes starting at ASOP. Return 0 if
+ no match has been found.
+ */
+static apr_size_t
+find_match(const struct blocks *blocks,
+ const apr_uint32_t rolling,
+ const char *a,
+ apr_size_t asize,
+ const char *b,
+ apr_size_t bsize,
+ apr_size_t *bposp,
+ apr_size_t *aposp,
+ apr_size_t pending_insert_start)
+{
+ apr_size_t apos, bpos = *bposp;
+ apr_size_t delta, max_delta;
+
+ apos = find_block(blocks, rolling, b + bpos);
+
+ /* See if we have a match. */
+ if (apos == NO_POSITION)
+ return 0;
+
+ /* Extend the match forward as far as possible */
+ max_delta = asize - apos - MATCH_BLOCKSIZE < bsize - bpos - MATCH_BLOCKSIZE
+ ? asize - apos - MATCH_BLOCKSIZE
+ : bsize - bpos - MATCH_BLOCKSIZE;
+ delta = match_length(a + apos + MATCH_BLOCKSIZE,
+ b + bpos + MATCH_BLOCKSIZE,
+ max_delta);
+
+ /* See if we can extend backwards (max MATCH_BLOCKSIZE-1 steps because A's
+ content has been sampled only every MATCH_BLOCKSIZE positions). */
+ while (apos > 0 && bpos > pending_insert_start && a[apos-1] == b[bpos-1])
+ {
+ --apos;
+ --bpos;
+ ++delta;
+ }
+
+ *aposp = apos;
+ *bposp = bpos;
+
+ return MATCH_BLOCKSIZE + delta;
+}
+
+/* Utility for compute_delta() that compares the range B[START,BSIZE) with
+ * the range of similar size before A[ASIZE]. Create corresponding copy and
+ * insert operations.
+ *
+ * BUILD_BATON and POOL will be passed through from compute_delta().
+ */
+static void
+store_delta_trailer(svn_txdelta__ops_baton_t *build_baton,
+ const char *a,
+ apr_size_t asize,
+ const char *b,
+ apr_size_t bsize,
+ apr_size_t start,
+ apr_pool_t *pool)
+{
+ apr_size_t end_match;
+ apr_size_t max_len = asize > (bsize - start) ? bsize - start : asize;
+ if (max_len == 0)
+ return;
+
+ end_match = reverse_match_length(a + asize, b + bsize, max_len);
+ if (end_match <= 4)
+ end_match = 0;
+
+ if (bsize - start > end_match)
+ svn_txdelta__insert_op(build_baton, svn_txdelta_new,
+ start, bsize - start - end_match, b + start, pool);
+ if (end_match)
+ svn_txdelta__insert_op(build_baton, svn_txdelta_source,
+ asize - end_match, end_match, NULL, pool);
+}
+
+
+/* Compute a delta from A to B using xdelta.
+
+ The basic xdelta algorithm is as follows:
+
+ 1. Go through the source data, checksumming every MATCH_BLOCKSIZE
+ block of bytes using adler32, and inserting the checksum into a
+ match table with the position of the match.
+ 2. Go through the target byte by byte, seeing if that byte starts a
+ match that we have in the match table.
+ 2a. If so, try to extend the match as far as possible both
+ forwards and backwards, and then insert a source copy
+ operation into the delta ops builder for the match.
+ 2b. If not, insert the byte as new data using an insert delta op.
+
+ Our implementation doesn't immediately insert "insert" operations,
+ it waits until we have another copy, or we are done. The reasoning
+ is twofold:
+
+ 1. Otherwise, we would just be building a ton of 1 byte insert
+ operations
+ 2. So that we can extend a source match backwards into a pending
+ insert operation, and possibly remove the need for the insert
+ entirely. This can happen due to stream alignment.
+*/
+static void
+compute_delta(svn_txdelta__ops_baton_t *build_baton,
+ const char *a,
+ apr_size_t asize,
+ const char *b,
+ apr_size_t bsize,
+ apr_pool_t *pool)
+{
+ struct blocks blocks;
+ apr_uint32_t rolling;
+ apr_size_t lo = 0, pending_insert_start = 0;
+
+ /* Optimization: directly compare window starts. If more than 4
+ * bytes match, we can immediately create a matching windows.
+ * Shorter sequences result in a net data increase. */
+ lo = match_length(a, b, asize > bsize ? bsize : asize);
+ if ((lo > 4) || (lo == bsize))
+ {
+ svn_txdelta__insert_op(build_baton, svn_txdelta_source,
+ 0, lo, NULL, pool);
+ pending_insert_start = lo;
+ }
+ else
+ lo = 0;
+
+ /* If the size of the target is smaller than the match blocksize, just
+ insert the entire target. */
+ if ((bsize - lo < MATCH_BLOCKSIZE) || (asize < MATCH_BLOCKSIZE))
+ {
+ store_delta_trailer(build_baton, a, asize, b, bsize, lo, pool);
+ return;
+ }
+
+ /* Initialize the matches table. */
+ init_blocks_table(a, asize, &blocks, pool);
+
+ /* Initialize our rolling checksum. */
+ rolling = init_adler32(b + lo);
+ while (lo < bsize)
+ {
+ apr_size_t matchlen = 0;
+ apr_size_t apos;
+
+ if (lo + MATCH_BLOCKSIZE <= bsize)
+ matchlen = find_match(&blocks, rolling, a, asize, b, bsize,
+ &lo, &apos, pending_insert_start);
+
+ /* If we didn't find a real match, insert the byte at the target
+ position into the pending insert. */
+ if (matchlen == 0)
+ {
+ /* move block one position forward. Short blocks at the end of
+ the buffer cannot be used as the beginning of a new match */
+ if (lo + MATCH_BLOCKSIZE < bsize)
+ rolling = adler32_replace(rolling, b[lo], b[lo+MATCH_BLOCKSIZE]);
+
+ lo++;
+ }
+ else
+ {
+ /* store the sequence of B that is between the matches */
+ if (lo - pending_insert_start > 0)
+ svn_txdelta__insert_op(build_baton, svn_txdelta_new,
+ 0, lo - pending_insert_start,
+ b + pending_insert_start, pool);
+ else
+ {
+ /* the match borders on the previous op. Maybe, we found a
+ * match that is better than / overlapping the previous one. */
+ apr_size_t len = reverse_match_length(a + apos, b + lo, apos < lo ? apos : lo);
+ if (len > 0)
+ {
+ len = svn_txdelta__remove_copy(build_baton, len);
+ apos -= len;
+ matchlen += len;
+ lo -= len;
+ }
+ }
+
+ /* Reset the pending insert start to immediately after the
+ match. */
+ lo += matchlen;
+ pending_insert_start = lo;
+ svn_txdelta__insert_op(build_baton, svn_txdelta_source,
+ apos, matchlen, NULL, pool);
+
+ /* Calculate the Adler32 sum for the first block behind the match.
+ * Ignore short buffers at the end of B.
+ */
+ if (lo + MATCH_BLOCKSIZE <= bsize)
+ rolling = init_adler32(b + lo);
+ }
+ }
+
+ /* If we still have an insert pending at the end, throw it in. */
+ store_delta_trailer(build_baton, a, asize, b, bsize, pending_insert_start, pool);
+}
+
+void
+svn_txdelta__xdelta(svn_txdelta__ops_baton_t *build_baton,
+ const char *data,
+ apr_size_t source_len,
+ apr_size_t target_len,
+ apr_pool_t *pool)
+{
+ /* We should never be asked to compute something when the source_len is 0;
+ we just use a single insert op there (and rely on zlib for
+ compression). */
+ assert(source_len != 0);
+ compute_delta(build_baton, data, source_len,
+ data + source_len, target_len,
+ pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_diff/deprecated.c b/contrib/subversion/subversion/libsvn_diff/deprecated.c
new file mode 100644
index 0000000..891ad5f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/deprecated.c
@@ -0,0 +1,289 @@
+/*
+ * deprecated.c: holding file for all deprecated APIs.
+ * "we can't lose 'em, but we can shun 'em!"
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+/* We define this here to remove any further warnings about the usage of
+ deprecated functions in this file. */
+#define SVN_DEPRECATED
+
+#include "svn_diff.h"
+#include "svn_utf.h"
+
+#include "svn_private_config.h"
+
+
+
+
+/*** Code. ***/
+struct fns_wrapper_baton
+{
+ /* We put the old baton in front of this one, so that we can still use
+ this baton in place of the old. This prevents us from having to
+ implement simple wrappers around each member of diff_fns_t. */
+ void *old_baton;
+ const svn_diff_fns_t *vtable;
+};
+
+static svn_error_t *
+datasources_open(void *baton,
+ apr_off_t *prefix_lines,
+ apr_off_t *suffix_lines,
+ const svn_diff_datasource_e *datasources,
+ apr_size_t datasource_len)
+{
+ struct fns_wrapper_baton *fwb = baton;
+ apr_size_t i;
+
+ /* Just iterate over the datasources, using the old singular version. */
+ for (i = 0; i < datasource_len; i++)
+ {
+ SVN_ERR(fwb->vtable->datasource_open(fwb->old_baton, datasources[i]));
+ }
+
+ /* Don't claim any prefix or suffix matches. */
+ *prefix_lines = 0;
+ *suffix_lines = 0;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+datasource_close(void *baton,
+ svn_diff_datasource_e datasource)
+{
+ struct fns_wrapper_baton *fwb = baton;
+ return fwb->vtable->datasource_close(fwb->old_baton, datasource);
+}
+
+static svn_error_t *
+datasource_get_next_token(apr_uint32_t *hash,
+ void **token,
+ void *baton,
+ svn_diff_datasource_e datasource)
+{
+ struct fns_wrapper_baton *fwb = baton;
+ return fwb->vtable->datasource_get_next_token(hash, token, fwb->old_baton,
+ datasource);
+}
+
+static svn_error_t *
+token_compare(void *baton,
+ void *ltoken,
+ void *rtoken,
+ int *compare)
+{
+ struct fns_wrapper_baton *fwb = baton;
+ return fwb->vtable->token_compare(fwb->old_baton, ltoken, rtoken, compare);
+}
+
+static void
+token_discard(void *baton,
+ void *token)
+{
+ struct fns_wrapper_baton *fwb = baton;
+ fwb->vtable->token_discard(fwb->old_baton, token);
+}
+
+static void
+token_discard_all(void *baton)
+{
+ struct fns_wrapper_baton *fwb = baton;
+ fwb->vtable->token_discard_all(fwb->old_baton);
+}
+
+
+static void
+wrap_diff_fns(svn_diff_fns2_t **diff_fns2,
+ struct fns_wrapper_baton **baton2,
+ const svn_diff_fns_t *diff_fns,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ /* Initialize the return vtable. */
+ *diff_fns2 = apr_palloc(result_pool, sizeof(**diff_fns2));
+
+ (*diff_fns2)->datasources_open = datasources_open;
+ (*diff_fns2)->datasource_close = datasource_close;
+ (*diff_fns2)->datasource_get_next_token = datasource_get_next_token;
+ (*diff_fns2)->token_compare = token_compare;
+ (*diff_fns2)->token_discard = token_discard;
+ (*diff_fns2)->token_discard_all = token_discard_all;
+
+ /* Initialize the wrapper baton. */
+ *baton2 = apr_palloc(result_pool, sizeof (**baton2));
+ (*baton2)->old_baton = baton;
+ (*baton2)->vtable = diff_fns;
+}
+
+
+/*** From diff_file.c ***/
+svn_error_t *
+svn_diff_file_output_unified2(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_path,
+ const char *modified_path,
+ const char *original_header,
+ const char *modified_header,
+ const char *header_encoding,
+ apr_pool_t *pool)
+{
+ return svn_diff_file_output_unified3(output_stream, diff,
+ original_path, modified_path,
+ original_header, modified_header,
+ header_encoding, NULL, FALSE, pool);
+}
+
+svn_error_t *
+svn_diff_file_output_unified(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_path,
+ const char *modified_path,
+ const char *original_header,
+ const char *modified_header,
+ apr_pool_t *pool)
+{
+ return svn_diff_file_output_unified2(output_stream, diff,
+ original_path, modified_path,
+ original_header, modified_header,
+ SVN_APR_LOCALE_CHARSET, pool);
+}
+
+svn_error_t *
+svn_diff_file_diff(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ apr_pool_t *pool)
+{
+ return svn_diff_file_diff_2(diff, original, modified,
+ svn_diff_file_options_create(pool), pool);
+}
+
+svn_error_t *
+svn_diff_file_diff3(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ const char *latest,
+ apr_pool_t *pool)
+{
+ return svn_diff_file_diff3_2(diff, original, modified, latest,
+ svn_diff_file_options_create(pool), pool);
+}
+
+svn_error_t *
+svn_diff_file_diff4(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ const char *latest,
+ const char *ancestor,
+ apr_pool_t *pool)
+{
+ return svn_diff_file_diff4_2(diff, original, modified, latest, ancestor,
+ svn_diff_file_options_create(pool), pool);
+}
+
+svn_error_t *
+svn_diff_file_output_merge(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_path,
+ const char *modified_path,
+ const char *latest_path,
+ const char *conflict_original,
+ const char *conflict_modified,
+ const char *conflict_latest,
+ const char *conflict_separator,
+ svn_boolean_t display_original_in_conflict,
+ svn_boolean_t display_resolved_conflicts,
+ apr_pool_t *pool)
+{
+ svn_diff_conflict_display_style_t style =
+ svn_diff_conflict_display_modified_latest;
+
+ if (display_resolved_conflicts)
+ style = svn_diff_conflict_display_resolved_modified_latest;
+
+ if (display_original_in_conflict)
+ style = svn_diff_conflict_display_modified_original_latest;
+
+ return svn_diff_file_output_merge2(output_stream,
+ diff,
+ original_path,
+ modified_path,
+ latest_path,
+ conflict_original,
+ conflict_modified,
+ conflict_latest,
+ conflict_separator,
+ style,
+ pool);
+}
+
+
+/*** From diff.c ***/
+svn_error_t *
+svn_diff_diff(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns_t *vtable,
+ apr_pool_t *pool)
+{
+ svn_diff_fns2_t *diff_fns2;
+ struct fns_wrapper_baton *fwb;
+
+ wrap_diff_fns(&diff_fns2, &fwb, vtable, diff_baton, pool);
+ return svn_diff_diff_2(diff, fwb, diff_fns2, pool);
+}
+
+
+/*** From diff3.c ***/
+svn_error_t *
+svn_diff_diff3(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns_t *vtable,
+ apr_pool_t *pool)
+{
+ svn_diff_fns2_t *diff_fns2;
+ struct fns_wrapper_baton *fwb;
+
+ wrap_diff_fns(&diff_fns2, &fwb, vtable, diff_baton, pool);
+ return svn_diff_diff3_2(diff, fwb, diff_fns2, pool);
+}
+
+
+/*** From diff4.c ***/
+svn_error_t *
+svn_diff_diff4(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns_t *vtable,
+ apr_pool_t *pool)
+{
+ svn_diff_fns2_t *diff_fns2;
+ struct fns_wrapper_baton *fwb;
+
+ wrap_diff_fns(&diff_fns2, &fwb, vtable, diff_baton, pool);
+ return svn_diff_diff4_2(diff, fwb, diff_fns2, pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_diff/diff.c b/contrib/subversion/subversion/libsvn_diff/diff.c
new file mode 100644
index 0000000..f43a3be
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/diff.c
@@ -0,0 +1,199 @@
+/*
+ * diff.c : routines for doing diffs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_general.h>
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_diff.h"
+#include "svn_types.h"
+
+#include "diff.h"
+
+
+svn_diff__token_index_t*
+svn_diff__get_token_counts(svn_diff__position_t *loop_start,
+ svn_diff__token_index_t num_tokens,
+ apr_pool_t *pool)
+{
+ svn_diff__token_index_t *token_counts;
+ svn_diff__token_index_t token_index;
+ svn_diff__position_t *current;
+
+ token_counts = apr_palloc(pool, num_tokens * sizeof(*token_counts));
+ for (token_index = 0; token_index < num_tokens; token_index++)
+ token_counts[token_index] = 0;
+
+ current = loop_start;
+ if (current != NULL)
+ {
+ do
+ {
+ token_counts[current->token_index]++;
+ current = current->next;
+ }
+ while (current != loop_start);
+ }
+
+ return token_counts;
+}
+
+
+svn_diff_t *
+svn_diff__diff(svn_diff__lcs_t *lcs,
+ apr_off_t original_start, apr_off_t modified_start,
+ svn_boolean_t want_common,
+ apr_pool_t *pool)
+{
+ svn_diff_t *diff;
+ svn_diff_t **diff_ref = &diff;
+
+ while (1)
+ {
+ if (original_start < lcs->position[0]->offset
+ || modified_start < lcs->position[1]->offset)
+ {
+ (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
+
+ (*diff_ref)->type = svn_diff__type_diff_modified;
+ (*diff_ref)->original_start = original_start - 1;
+ (*diff_ref)->original_length =
+ lcs->position[0]->offset - original_start;
+ (*diff_ref)->modified_start = modified_start - 1;
+ (*diff_ref)->modified_length =
+ lcs->position[1]->offset - modified_start;
+ (*diff_ref)->latest_start = 0;
+ (*diff_ref)->latest_length = 0;
+
+ diff_ref = &(*diff_ref)->next;
+ }
+
+ /* Detect the EOF */
+ if (lcs->length == 0)
+ break;
+
+ original_start = lcs->position[0]->offset;
+ modified_start = lcs->position[1]->offset;
+
+ if (want_common)
+ {
+ (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
+
+ (*diff_ref)->type = svn_diff__type_common;
+ (*diff_ref)->original_start = original_start - 1;
+ (*diff_ref)->original_length = lcs->length;
+ (*diff_ref)->modified_start = modified_start - 1;
+ (*diff_ref)->modified_length = lcs->length;
+ (*diff_ref)->latest_start = 0;
+ (*diff_ref)->latest_length = 0;
+
+ diff_ref = &(*diff_ref)->next;
+ }
+
+ original_start += lcs->length;
+ modified_start += lcs->length;
+
+ lcs = lcs->next;
+ }
+
+ *diff_ref = NULL;
+
+ return diff;
+}
+
+
+svn_error_t *
+svn_diff_diff_2(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns2_t *vtable,
+ apr_pool_t *pool)
+{
+ svn_diff__tree_t *tree;
+ svn_diff__position_t *position_list[2];
+ svn_diff__token_index_t num_tokens;
+ svn_diff__token_index_t *token_counts[2];
+ svn_diff_datasource_e datasource[] = {svn_diff_datasource_original,
+ svn_diff_datasource_modified};
+ svn_diff__lcs_t *lcs;
+ apr_pool_t *subpool;
+ apr_pool_t *treepool;
+ apr_off_t prefix_lines = 0;
+ apr_off_t suffix_lines = 0;
+
+ *diff = NULL;
+
+ subpool = svn_pool_create(pool);
+ treepool = svn_pool_create(pool);
+
+ svn_diff__tree_create(&tree, treepool);
+
+ SVN_ERR(vtable->datasources_open(diff_baton, &prefix_lines, &suffix_lines,
+ datasource, 2));
+
+ /* Insert the data into the tree */
+ SVN_ERR(svn_diff__get_tokens(&position_list[0],
+ tree,
+ diff_baton, vtable,
+ svn_diff_datasource_original,
+ prefix_lines,
+ subpool));
+
+ SVN_ERR(svn_diff__get_tokens(&position_list[1],
+ tree,
+ diff_baton, vtable,
+ svn_diff_datasource_modified,
+ prefix_lines,
+ subpool));
+
+ num_tokens = svn_diff__get_node_count(tree);
+
+ /* The cool part is that we don't need the tokens anymore.
+ * Allow the app to clean them up if it wants to.
+ */
+ if (vtable->token_discard_all != NULL)
+ vtable->token_discard_all(diff_baton);
+
+ /* We don't need the nodes in the tree either anymore, nor the tree itself */
+ svn_pool_destroy(treepool);
+
+ token_counts[0] = svn_diff__get_token_counts(position_list[0], num_tokens,
+ subpool);
+ token_counts[1] = svn_diff__get_token_counts(position_list[1], num_tokens,
+ subpool);
+
+ /* Get the lcs */
+ lcs = svn_diff__lcs(position_list[0], position_list[1], token_counts[0],
+ token_counts[1], num_tokens, prefix_lines,
+ suffix_lines, subpool);
+
+ /* Produce the diff */
+ *diff = svn_diff__diff(lcs, 1, 1, TRUE, pool);
+
+ /* Get rid of all the data we don't have a use for anymore */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_diff/diff.h b/contrib/subversion/subversion/libsvn_diff/diff.h
new file mode 100644
index 0000000..51a84c6
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/diff.h
@@ -0,0 +1,217 @@
+/*
+ * diff.h : private header file
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#if !defined(DIFF_H)
+#define DIFF_H
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_general.h>
+
+#include "svn_diff.h"
+#include "svn_types.h"
+
+#define SVN_DIFF__UNIFIED_CONTEXT_SIZE 3
+
+typedef struct svn_diff__node_t svn_diff__node_t;
+typedef struct svn_diff__tree_t svn_diff__tree_t;
+typedef struct svn_diff__position_t svn_diff__position_t;
+typedef struct svn_diff__lcs_t svn_diff__lcs_t;
+
+typedef enum svn_diff__type_e
+{
+ svn_diff__type_common,
+ svn_diff__type_diff_modified,
+ svn_diff__type_diff_latest,
+ svn_diff__type_diff_common,
+ svn_diff__type_conflict
+} svn_diff__type_e;
+
+struct svn_diff_t {
+ svn_diff_t *next;
+ svn_diff__type_e type;
+ apr_off_t original_start;
+ apr_off_t original_length;
+ apr_off_t modified_start;
+ apr_off_t modified_length;
+ apr_off_t latest_start;
+ apr_off_t latest_length;
+ svn_diff_t *resolved_diff;
+};
+
+/* Type used for token indices and counts of tokens. Must be signed. */
+typedef long int svn_diff__token_index_t;
+
+struct svn_diff__position_t
+{
+ svn_diff__position_t *next;
+ svn_diff__token_index_t token_index;
+ apr_off_t offset;
+};
+
+struct svn_diff__lcs_t
+{
+ svn_diff__lcs_t *next;
+ svn_diff__position_t *position[2];
+ apr_off_t length;
+ int refcount;
+};
+
+
+/* State used when normalizing whitespace and EOL styles. */
+typedef enum svn_diff__normalize_state_t
+{
+ /* Initial state; not in a sequence of whitespace. */
+ svn_diff__normalize_state_normal,
+ /* We're in a sequence of whitespace characters. Only entered if
+ we ignore whitespace. */
+ svn_diff__normalize_state_whitespace,
+ /* The previous character was CR. */
+ svn_diff__normalize_state_cr
+} svn_diff__normalize_state_t;
+
+
+/*
+ * Calculate the Longest Common Subsequence (LCS) between two datasources
+ * POSITION_LIST1 and POSITION_LIST2, with TOKEN_COUNTS_LIST1 and
+ * TOKEN_COUNTS_LIST2 the corresponding counts of the different tokens
+ * (indexed by the 'token_index' of the positions of each position_list).
+ *
+ * From the beginning of each list, PREFIX_LINES lines will be assumed to be
+ * equal and be excluded from the comparison process. Similarly, SUFFIX_LINES
+ * at the end of both sequences will be skipped.
+ *
+ * The resulting lcs structure will be the return value of this function.
+ * Allocations will be made from POOL.
+ */
+svn_diff__lcs_t *
+svn_diff__lcs(svn_diff__position_t *position_list1, /* pointer to tail (ring) */
+ svn_diff__position_t *position_list2, /* pointer to tail (ring) */
+ svn_diff__token_index_t *token_counts_list1, /* array of counts */
+ svn_diff__token_index_t *token_counts_list2, /* array of counts */
+ svn_diff__token_index_t num_tokens, /* length of count arrays */
+ apr_off_t prefix_lines,
+ apr_off_t suffix_lines,
+ apr_pool_t *pool);
+
+
+/*
+ * Returns number of tokens in a tree
+ */
+svn_diff__token_index_t
+svn_diff__get_node_count(svn_diff__tree_t *tree);
+
+/*
+ * Support functions to build a tree of token positions
+ */
+void
+svn_diff__tree_create(svn_diff__tree_t **tree, apr_pool_t *pool);
+
+
+/*
+ * Get all tokens from a datasource. Return the
+ * last item in the (circular) list.
+ */
+svn_error_t *
+svn_diff__get_tokens(svn_diff__position_t **position_list,
+ svn_diff__tree_t *tree,
+ void *diff_baton,
+ const svn_diff_fns2_t *vtable,
+ svn_diff_datasource_e datasource,
+ apr_off_t prefix_lines,
+ apr_pool_t *pool);
+
+/*
+ * Returns an array with the counts for the tokens in
+ * the looped linked list given in loop_start.
+ * num_tokens equals the highest possible token index +1.
+ */
+svn_diff__token_index_t*
+svn_diff__get_token_counts(svn_diff__position_t *loop_start,
+ svn_diff__token_index_t num_tokens,
+ apr_pool_t *pool);
+
+/* Morph a svn_lcs_t into a svn_diff_t. */
+svn_diff_t *
+svn_diff__diff(svn_diff__lcs_t *lcs,
+ apr_off_t original_start, apr_off_t modified_start,
+ svn_boolean_t want_common,
+ apr_pool_t *pool);
+
+void
+svn_diff__resolve_conflict(svn_diff_t *hunk,
+ svn_diff__position_t **position_list1,
+ svn_diff__position_t **position_list2,
+ svn_diff__token_index_t num_tokens,
+ apr_pool_t *pool);
+
+
+/* Normalize the characters pointed to by the buffer BUF (of length *LENGTHP)
+ * according to the options *OPTS, starting in the state *STATEP.
+ *
+ * Adjust *LENGTHP and *STATEP to be the length of the normalized buffer and
+ * the final state, respectively.
+ * Normalized data is written to the memory at *TGT. BUF and TGT may point
+ * to the same memory area. The memory area pointed to by *TGT should be
+ * large enough to hold *LENGTHP bytes.
+ * When on return *TGT is not equal to the value passed in, it points somewhere
+ * into the memory region designated by BUF and *LENGTHP.
+ */
+void
+svn_diff__normalize_buffer(char **tgt,
+ apr_off_t *lengthp,
+ svn_diff__normalize_state_t *statep,
+ const char *buf,
+ const svn_diff_file_options_t *opts);
+
+/* Set *OUT_STR to a newline followed by a "\ No newline at end of file" line.
+ *
+ * The text will be encoded into HEADER_ENCODING.
+ */
+svn_error_t *
+svn_diff__unified_append_no_newline_msg(svn_stringbuf_t *stringbuf,
+ const char *header_encoding,
+ apr_pool_t *scratch_pool);
+
+/* Write a unidiff hunk header to OUTPUT_STREAM.
+ *
+ * The header will use HUNK_DELIMITER (which should usually be "@@") before
+ * and after the line-number ranges which are formed from OLD_START,
+ * OLD_LENGTH, NEW_START and NEW_LENGTH. If HUNK_EXTRA_CONTEXT is not NULL,
+ * it will be written after the final delimiter, with an intervening space.
+ *
+ * The text will be encoded into HEADER_ENCODING.
+ */
+svn_error_t *
+svn_diff__unified_write_hunk_header(svn_stream_t *output_stream,
+ const char *header_encoding,
+ const char *hunk_delimiter,
+ apr_off_t old_start,
+ apr_off_t old_length,
+ apr_off_t new_start,
+ apr_off_t new_length,
+ const char *hunk_extra_context,
+ apr_pool_t *scratch_pool);
+
+
+#endif /* DIFF_H */
diff --git a/contrib/subversion/subversion/libsvn_diff/diff3.c b/contrib/subversion/subversion/libsvn_diff/diff3.c
new file mode 100644
index 0000000..8b7c9b3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/diff3.c
@@ -0,0 +1,529 @@
+/*
+ * diff3.c : routines for doing diffs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_general.h>
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_diff.h"
+#include "svn_types.h"
+
+#include "diff.h"
+
+
+void
+svn_diff__resolve_conflict(svn_diff_t *hunk,
+ svn_diff__position_t **position_list1,
+ svn_diff__position_t **position_list2,
+ svn_diff__token_index_t num_tokens,
+ apr_pool_t *pool)
+{
+ apr_off_t modified_start = hunk->modified_start + 1;
+ apr_off_t latest_start = hunk->latest_start + 1;
+ apr_off_t common_length;
+ apr_off_t modified_length = hunk->modified_length;
+ apr_off_t latest_length = hunk->latest_length;
+ svn_diff__position_t *start_position[2];
+ svn_diff__position_t *position[2];
+ svn_diff__token_index_t *token_counts[2];
+ svn_diff__lcs_t *lcs = NULL;
+ svn_diff__lcs_t **lcs_ref = &lcs;
+ svn_diff_t **diff_ref = &hunk->resolved_diff;
+ apr_pool_t *subpool;
+
+ /* First find the starting positions for the
+ * comparison
+ */
+
+ start_position[0] = *position_list1;
+ start_position[1] = *position_list2;
+
+ while (start_position[0]->offset < modified_start)
+ start_position[0] = start_position[0]->next;
+
+ while (start_position[1]->offset < latest_start)
+ start_position[1] = start_position[1]->next;
+
+ position[0] = start_position[0];
+ position[1] = start_position[1];
+
+ common_length = modified_length < latest_length
+ ? modified_length : latest_length;
+
+ while (common_length > 0
+ && position[0]->token_index == position[1]->token_index)
+ {
+ position[0] = position[0]->next;
+ position[1] = position[1]->next;
+
+ common_length--;
+ }
+
+ if (common_length == 0
+ && modified_length == latest_length)
+ {
+ hunk->type = svn_diff__type_diff_common;
+ hunk->resolved_diff = NULL;
+
+ *position_list1 = position[0];
+ *position_list2 = position[1];
+
+ return;
+ }
+
+ hunk->type = svn_diff__type_conflict;
+
+ /* ### If we have a conflict we can try to find the
+ * ### common parts in it by getting an lcs between
+ * ### modified (start to start + length) and
+ * ### latest (start to start + length).
+ * ### We use this lcs to create a simple diff. Only
+ * ### where there is a diff between the two, we have
+ * ### a conflict.
+ * ### This raises a problem; several common diffs and
+ * ### conflicts can occur within the same original
+ * ### block. This needs some thought.
+ * ###
+ * ### NB: We can use the node _pointers_ to identify
+ * ### different tokens
+ */
+
+ subpool = svn_pool_create(pool);
+
+ /* Calculate how much of the two sequences was
+ * actually the same.
+ */
+ common_length = (modified_length < latest_length
+ ? modified_length : latest_length)
+ - common_length;
+
+ /* If there were matching symbols at the start of
+ * both sequences, record that fact.
+ */
+ if (common_length > 0)
+ {
+ lcs = apr_palloc(subpool, sizeof(*lcs));
+ lcs->next = NULL;
+ lcs->position[0] = start_position[0];
+ lcs->position[1] = start_position[1];
+ lcs->length = common_length;
+
+ lcs_ref = &lcs->next;
+ }
+
+ modified_length -= common_length;
+ latest_length -= common_length;
+
+ modified_start = start_position[0]->offset;
+ latest_start = start_position[1]->offset;
+
+ start_position[0] = position[0];
+ start_position[1] = position[1];
+
+ /* Create a new ring for svn_diff__lcs to grok.
+ * We can safely do this given we don't need the
+ * positions we processed anymore.
+ */
+ if (modified_length == 0)
+ {
+ *position_list1 = position[0];
+ position[0] = NULL;
+ }
+ else
+ {
+ while (--modified_length)
+ position[0] = position[0]->next;
+
+ *position_list1 = position[0]->next;
+ position[0]->next = start_position[0];
+ }
+
+ if (latest_length == 0)
+ {
+ *position_list2 = position[1];
+ position[1] = NULL;
+ }
+ else
+ {
+ while (--latest_length)
+ position[1] = position[1]->next;
+
+ *position_list2 = position[1]->next;
+ position[1]->next = start_position[1];
+ }
+
+ token_counts[0] = svn_diff__get_token_counts(position[0], num_tokens,
+ subpool);
+ token_counts[1] = svn_diff__get_token_counts(position[1], num_tokens,
+ subpool);
+
+ *lcs_ref = svn_diff__lcs(position[0], position[1], token_counts[0],
+ token_counts[1], num_tokens, 0, 0, subpool);
+
+ /* Fix up the EOF lcs element in case one of
+ * the two sequences was NULL.
+ */
+ if ((*lcs_ref)->position[0]->offset == 1)
+ (*lcs_ref)->position[0] = *position_list1;
+
+ if ((*lcs_ref)->position[1]->offset == 1)
+ (*lcs_ref)->position[1] = *position_list2;
+
+ /* Produce the resolved diff */
+ while (1)
+ {
+ if (modified_start < lcs->position[0]->offset
+ || latest_start < lcs->position[1]->offset)
+ {
+ (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
+
+ (*diff_ref)->type = svn_diff__type_conflict;
+ (*diff_ref)->original_start = hunk->original_start;
+ (*diff_ref)->original_length = hunk->original_length;
+ (*diff_ref)->modified_start = modified_start - 1;
+ (*diff_ref)->modified_length = lcs->position[0]->offset
+ - modified_start;
+ (*diff_ref)->latest_start = latest_start - 1;
+ (*diff_ref)->latest_length = lcs->position[1]->offset
+ - latest_start;
+ (*diff_ref)->resolved_diff = NULL;
+
+ diff_ref = &(*diff_ref)->next;
+ }
+
+ /* Detect the EOF */
+ if (lcs->length == 0)
+ break;
+
+ modified_start = lcs->position[0]->offset;
+ latest_start = lcs->position[1]->offset;
+
+ (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
+
+ (*diff_ref)->type = svn_diff__type_diff_common;
+ (*diff_ref)->original_start = hunk->original_start;
+ (*diff_ref)->original_length = hunk->original_length;
+ (*diff_ref)->modified_start = modified_start - 1;
+ (*diff_ref)->modified_length = lcs->length;
+ (*diff_ref)->latest_start = latest_start - 1;
+ (*diff_ref)->latest_length = lcs->length;
+ (*diff_ref)->resolved_diff = NULL;
+
+ diff_ref = &(*diff_ref)->next;
+
+ modified_start += lcs->length;
+ latest_start += lcs->length;
+
+ lcs = lcs->next;
+ }
+
+ *diff_ref = NULL;
+
+ svn_pool_destroy(subpool);
+}
+
+
+svn_error_t *
+svn_diff_diff3_2(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns2_t *vtable,
+ apr_pool_t *pool)
+{
+ svn_diff__tree_t *tree;
+ svn_diff__position_t *position_list[3];
+ svn_diff__token_index_t num_tokens;
+ svn_diff__token_index_t *token_counts[3];
+ svn_diff_datasource_e datasource[] = {svn_diff_datasource_original,
+ svn_diff_datasource_modified,
+ svn_diff_datasource_latest};
+ svn_diff__lcs_t *lcs_om;
+ svn_diff__lcs_t *lcs_ol;
+ apr_pool_t *subpool;
+ apr_pool_t *treepool;
+ apr_off_t prefix_lines = 0;
+ apr_off_t suffix_lines = 0;
+
+ *diff = NULL;
+
+ subpool = svn_pool_create(pool);
+ treepool = svn_pool_create(pool);
+
+ svn_diff__tree_create(&tree, treepool);
+
+ SVN_ERR(vtable->datasources_open(diff_baton, &prefix_lines, &suffix_lines,
+ datasource, 3));
+
+ SVN_ERR(svn_diff__get_tokens(&position_list[0],
+ tree,
+ diff_baton, vtable,
+ svn_diff_datasource_original,
+ prefix_lines,
+ subpool));
+
+ SVN_ERR(svn_diff__get_tokens(&position_list[1],
+ tree,
+ diff_baton, vtable,
+ svn_diff_datasource_modified,
+ prefix_lines,
+ subpool));
+
+ SVN_ERR(svn_diff__get_tokens(&position_list[2],
+ tree,
+ diff_baton, vtable,
+ svn_diff_datasource_latest,
+ prefix_lines,
+ subpool));
+
+ num_tokens = svn_diff__get_node_count(tree);
+
+ /* Get rid of the tokens, we don't need them to calc the diff */
+ if (vtable->token_discard_all != NULL)
+ vtable->token_discard_all(diff_baton);
+
+ /* We don't need the nodes in the tree either anymore, nor the tree itself */
+ svn_pool_destroy(treepool);
+
+ token_counts[0] = svn_diff__get_token_counts(position_list[0], num_tokens,
+ subpool);
+ token_counts[1] = svn_diff__get_token_counts(position_list[1], num_tokens,
+ subpool);
+ token_counts[2] = svn_diff__get_token_counts(position_list[2], num_tokens,
+ subpool);
+
+ /* Get the lcs for original-modified and original-latest */
+ lcs_om = svn_diff__lcs(position_list[0], position_list[1], token_counts[0],
+ token_counts[1], num_tokens, prefix_lines,
+ suffix_lines, subpool);
+ lcs_ol = svn_diff__lcs(position_list[0], position_list[2], token_counts[0],
+ token_counts[2], num_tokens, prefix_lines,
+ suffix_lines, subpool);
+
+ /* Produce a merged diff */
+ {
+ svn_diff_t **diff_ref = diff;
+
+ apr_off_t original_start = 1;
+ apr_off_t modified_start = 1;
+ apr_off_t latest_start = 1;
+ apr_off_t original_sync;
+ apr_off_t modified_sync;
+ apr_off_t latest_sync;
+ apr_off_t common_length;
+ apr_off_t modified_length;
+ apr_off_t latest_length;
+ svn_boolean_t is_modified;
+ svn_boolean_t is_latest;
+ svn_diff__position_t sentinel_position[2];
+
+ /* Point the position lists to the start of the list
+ * so that common_diff/conflict detection actually is
+ * able to work.
+ */
+ if (position_list[1])
+ {
+ sentinel_position[0].next = position_list[1]->next;
+ sentinel_position[0].offset = position_list[1]->offset + 1;
+ position_list[1]->next = &sentinel_position[0];
+ position_list[1] = sentinel_position[0].next;
+ }
+ else
+ {
+ sentinel_position[0].offset = prefix_lines + 1;
+ sentinel_position[0].next = NULL;
+ position_list[1] = &sentinel_position[0];
+ }
+
+ if (position_list[2])
+ {
+ sentinel_position[1].next = position_list[2]->next;
+ sentinel_position[1].offset = position_list[2]->offset + 1;
+ position_list[2]->next = &sentinel_position[1];
+ position_list[2] = sentinel_position[1].next;
+ }
+ else
+ {
+ sentinel_position[1].offset = prefix_lines + 1;
+ sentinel_position[1].next = NULL;
+ position_list[2] = &sentinel_position[1];
+ }
+
+ while (1)
+ {
+ /* Find the sync points */
+ while (1)
+ {
+ if (lcs_om->position[0]->offset > lcs_ol->position[0]->offset)
+ {
+ original_sync = lcs_om->position[0]->offset;
+
+ while (lcs_ol->position[0]->offset + lcs_ol->length
+ < original_sync)
+ lcs_ol = lcs_ol->next;
+
+ /* If the sync point is the EOF, and our current lcs segment
+ * doesn't reach as far as EOF, we need to skip this segment.
+ */
+ if (lcs_om->length == 0 && lcs_ol->length > 0
+ && lcs_ol->position[0]->offset + lcs_ol->length
+ == original_sync
+ && lcs_ol->position[1]->offset + lcs_ol->length
+ != lcs_ol->next->position[1]->offset)
+ lcs_ol = lcs_ol->next;
+
+ if (lcs_ol->position[0]->offset <= original_sync)
+ break;
+ }
+ else
+ {
+ original_sync = lcs_ol->position[0]->offset;
+
+ while (lcs_om->position[0]->offset + lcs_om->length
+ < original_sync)
+ lcs_om = lcs_om->next;
+
+ /* If the sync point is the EOF, and our current lcs segment
+ * doesn't reach as far as EOF, we need to skip this segment.
+ */
+ if (lcs_ol->length == 0 && lcs_om->length > 0
+ && lcs_om->position[0]->offset + lcs_om->length
+ == original_sync
+ && lcs_om->position[1]->offset + lcs_om->length
+ != lcs_om->next->position[1]->offset)
+ lcs_om = lcs_om->next;
+
+ if (lcs_om->position[0]->offset <= original_sync)
+ break;
+ }
+ }
+
+ modified_sync = lcs_om->position[1]->offset
+ + (original_sync - lcs_om->position[0]->offset);
+ latest_sync = lcs_ol->position[1]->offset
+ + (original_sync - lcs_ol->position[0]->offset);
+
+ /* Determine what is modified, if anything */
+ is_modified = lcs_om->position[0]->offset - original_start > 0
+ || lcs_om->position[1]->offset - modified_start > 0;
+
+ is_latest = lcs_ol->position[0]->offset - original_start > 0
+ || lcs_ol->position[1]->offset - latest_start > 0;
+
+ if (is_modified || is_latest)
+ {
+ modified_length = modified_sync - modified_start;
+ latest_length = latest_sync - latest_start;
+
+ (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
+
+ (*diff_ref)->original_start = original_start - 1;
+ (*diff_ref)->original_length = original_sync - original_start;
+ (*diff_ref)->modified_start = modified_start - 1;
+ (*diff_ref)->modified_length = modified_length;
+ (*diff_ref)->latest_start = latest_start - 1;
+ (*diff_ref)->latest_length = latest_length;
+ (*diff_ref)->resolved_diff = NULL;
+
+ if (is_modified && is_latest)
+ {
+ svn_diff__resolve_conflict(*diff_ref,
+ &position_list[1],
+ &position_list[2],
+ num_tokens,
+ pool);
+ }
+ else if (is_modified)
+ {
+ (*diff_ref)->type = svn_diff__type_diff_modified;
+ }
+ else
+ {
+ (*diff_ref)->type = svn_diff__type_diff_latest;
+ }
+
+ diff_ref = &(*diff_ref)->next;
+ }
+
+ /* Detect EOF */
+ if (lcs_om->length == 0 || lcs_ol->length == 0)
+ break;
+
+ modified_length = lcs_om->length
+ - (original_sync - lcs_om->position[0]->offset);
+ latest_length = lcs_ol->length
+ - (original_sync - lcs_ol->position[0]->offset);
+ common_length = modified_length < latest_length
+ ? modified_length : latest_length;
+
+ (*diff_ref) = apr_palloc(pool, sizeof(**diff_ref));
+
+ (*diff_ref)->type = svn_diff__type_common;
+ (*diff_ref)->original_start = original_sync - 1;
+ (*diff_ref)->original_length = common_length;
+ (*diff_ref)->modified_start = modified_sync - 1;
+ (*diff_ref)->modified_length = common_length;
+ (*diff_ref)->latest_start = latest_sync - 1;
+ (*diff_ref)->latest_length = common_length;
+ (*diff_ref)->resolved_diff = NULL;
+
+ diff_ref = &(*diff_ref)->next;
+
+ /* Set the new offsets */
+ original_start = original_sync + common_length;
+ modified_start = modified_sync + common_length;
+ latest_start = latest_sync + common_length;
+
+ /* Make it easier for diff_common/conflict detection
+ by recording last lcs start positions
+ */
+ if (position_list[1]->offset < lcs_om->position[1]->offset)
+ position_list[1] = lcs_om->position[1];
+
+ if (position_list[2]->offset < lcs_ol->position[1]->offset)
+ position_list[2] = lcs_ol->position[1];
+
+ /* Make sure we are pointing to lcs entries beyond
+ * the range we just processed
+ */
+ while (original_start >= lcs_om->position[0]->offset + lcs_om->length
+ && lcs_om->length > 0)
+ {
+ lcs_om = lcs_om->next;
+ }
+
+ while (original_start >= lcs_ol->position[0]->offset + lcs_ol->length
+ && lcs_ol->length > 0)
+ {
+ lcs_ol = lcs_ol->next;
+ }
+ }
+
+ *diff_ref = NULL;
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_diff/diff4.c b/contrib/subversion/subversion/libsvn_diff/diff4.c
new file mode 100644
index 0000000..9f3cb8c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/diff4.c
@@ -0,0 +1,314 @@
+/*
+ * diff.c : routines for doing diffs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_general.h>
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_diff.h"
+#include "svn_types.h"
+
+#include "diff.h"
+
+/*
+ * Variance adjustment rules:
+ *
+ * See notes/variance-adjusted-patching.html
+ *
+ * ###: Expand this comment to contain the full set of adjustment
+ * ###: rules instead of pointing to a webpage.
+ */
+
+/*
+ * In the text below consider the following:
+ *
+ * O = Original
+ * M = Modified
+ * L = Latest
+ * A = Ancestor
+ * X:Y = diff between X and Y
+ * X:Y:Z = 3-way diff between X, Y and Z
+ * P = O:L, possibly adjusted
+ *
+ * diff4 -- Variance adjusted diff algorithm
+ *
+ * 1. Create a diff O:L and call that P.
+ *
+ * 2. Morph P into a 3-way diff by performing the following
+ * transformation: O:L -> O:O:L.
+ *
+ * 3. Create a diff A:O.
+ *
+ * 4. Using A:O...
+ *
+ * #. Using M:A...
+ *
+ * #. Resolve conflicts...
+ *
+
+ 1. Out-range added line: decrement the line numbers in every hunk in P
+ that comes after the addition. This undoes the effect of the add, since
+ the add never happened in D.
+
+ 2. Out-range deleted line: increment the line numbers in every hunk in P
+ that comes after the deletion. This undoes the effect of the deletion,
+ since the deletion never happened in D.
+
+ 3. Out-range edited line: do nothing. Out-range edits are irrelevant to P.
+
+ 4. Added line in context range in P: remove the corresponding line from
+ the context, optionally replacing it with new context based on that
+ region in M, and adjust line numbers and mappings appropriately.
+
+ 5. Added line in affected text range in P: this is a dependency problem
+ -- part of the change T:18-T:19 depends on changes introduced to T after
+ B branched. There are several possible behaviors, depending on what the
+ user wants. One is to generate an informative error, stating that
+ T:18-T:19 depends on some other change (T:N-T:M, where N>=8, M<=18,
+ and M-N == 1); the exact revisions can be discovered automatically using
+ the same process as "cvs annotate", though it may take some time to do
+ so. Another option is to include the change in P, as an insertion of the
+ "after" version of the text, and adjust line numbers and mappings
+ accordingly. (And if all this isn't sounding a lot like a directory
+ merge algorithm, try drinking more of the Kool-Aid.) A third option is
+ to include it as an insertion, but with metadata (such as CVS-style
+ conflict markers) indicating that the line attempting to be patched
+ does not exist in B.
+
+ 6. Deleted line that is in-range in P: request another universe -- this
+ situation can't happen in ours.
+
+ 7. In-range edited line: reverse that edit in the "before" version of the
+ corresponding line in the appropriate hunk in P, to obtain the version of
+ the line that will be found in B when P is applied.
+*/
+
+
+static void
+adjust_diff(svn_diff_t *diff, svn_diff_t *adjust)
+{
+ svn_diff_t *hunk;
+ apr_off_t range_start;
+ apr_off_t range_end;
+ apr_off_t adjustment;
+
+ for (; adjust; adjust = adjust->next)
+ {
+ range_start = adjust->modified_start;
+ range_end = range_start + adjust->modified_length;
+ adjustment = adjust->original_length - adjust->modified_length;
+
+ /* No change in line count, so no modifications. [3, 7] */
+ if (adjustment == 0)
+ continue;
+
+ for (hunk = diff; hunk; hunk = hunk->next)
+ {
+ /* Changes are in the range before this hunk. Adjust the start
+ * of the hunk. [1, 2]
+ */
+ if (hunk->modified_start >= range_end)
+ {
+ hunk->modified_start += adjustment;
+ continue;
+ }
+
+ /* Changes are in the range beyond this hunk. No adjustments
+ * needed. [1, 2]
+ */
+ if (hunk->modified_start + hunk->modified_length <= range_start)
+ continue;
+
+ /* From here on changes are in the range of this hunk. */
+
+ /* This is a context hunk. Adjust the length. [4]
+ */
+ if (hunk->type == svn_diff__type_diff_modified)
+ {
+ hunk->modified_length += adjustment;
+ continue;
+ }
+
+ /* Mark as conflicted. This happens in the reverse case when a line
+ * is added in range and in the forward case when a line is deleted
+ * in range. [5 (reverse), 6 (forward)]
+ */
+ if (adjustment < 0)
+ hunk->type = svn_diff__type_conflict;
+
+ /* Adjust the length of this hunk (reverse the change). [5, 6] */
+ hunk->modified_length -= adjustment;
+ }
+ }
+}
+
+svn_error_t *
+svn_diff_diff4_2(svn_diff_t **diff,
+ void *diff_baton,
+ const svn_diff_fns2_t *vtable,
+ apr_pool_t *pool)
+{
+ svn_diff__tree_t *tree;
+ svn_diff__position_t *position_list[4];
+ svn_diff__token_index_t num_tokens;
+ svn_diff__token_index_t *token_counts[4];
+ svn_diff_datasource_e datasource[] = {svn_diff_datasource_original,
+ svn_diff_datasource_modified,
+ svn_diff_datasource_latest,
+ svn_diff_datasource_ancestor};
+ svn_diff__lcs_t *lcs_ol;
+ svn_diff__lcs_t *lcs_adjust;
+ svn_diff_t *diff_ol;
+ svn_diff_t *diff_adjust;
+ svn_diff_t *hunk;
+ apr_pool_t *subpool;
+ apr_pool_t *subpool2;
+ apr_pool_t *subpool3;
+ apr_off_t prefix_lines = 0;
+ apr_off_t suffix_lines = 0;
+
+ *diff = NULL;
+
+ subpool = svn_pool_create(pool);
+ subpool2 = svn_pool_create(subpool);
+ subpool3 = svn_pool_create(subpool2);
+
+ svn_diff__tree_create(&tree, subpool3);
+
+ SVN_ERR(vtable->datasources_open(diff_baton, &prefix_lines, &suffix_lines,
+ datasource, 4));
+
+ SVN_ERR(svn_diff__get_tokens(&position_list[0],
+ tree,
+ diff_baton, vtable,
+ svn_diff_datasource_original,
+ prefix_lines,
+ subpool2));
+
+ SVN_ERR(svn_diff__get_tokens(&position_list[1],
+ tree,
+ diff_baton, vtable,
+ svn_diff_datasource_modified,
+ prefix_lines,
+ subpool));
+
+ SVN_ERR(svn_diff__get_tokens(&position_list[2],
+ tree,
+ diff_baton, vtable,
+ svn_diff_datasource_latest,
+ prefix_lines,
+ subpool));
+
+ SVN_ERR(svn_diff__get_tokens(&position_list[3],
+ tree,
+ diff_baton, vtable,
+ svn_diff_datasource_ancestor,
+ prefix_lines,
+ subpool2));
+
+ num_tokens = svn_diff__get_node_count(tree);
+
+ /* Get rid of the tokens, we don't need them to calc the diff */
+ if (vtable->token_discard_all != NULL)
+ vtable->token_discard_all(diff_baton);
+
+ /* We don't need the nodes in the tree either anymore, nor the tree itself */
+ svn_pool_clear(subpool3);
+
+ token_counts[0] = svn_diff__get_token_counts(position_list[0], num_tokens,
+ subpool);
+ token_counts[1] = svn_diff__get_token_counts(position_list[1], num_tokens,
+ subpool);
+ token_counts[2] = svn_diff__get_token_counts(position_list[2], num_tokens,
+ subpool);
+ token_counts[3] = svn_diff__get_token_counts(position_list[3], num_tokens,
+ subpool);
+
+ /* Get the lcs for original - latest */
+ lcs_ol = svn_diff__lcs(position_list[0], position_list[2],
+ token_counts[0], token_counts[2],
+ num_tokens, prefix_lines,
+ suffix_lines, subpool3);
+ diff_ol = svn_diff__diff(lcs_ol, 1, 1, TRUE, pool);
+
+ svn_pool_clear(subpool3);
+
+ for (hunk = diff_ol; hunk; hunk = hunk->next)
+ {
+ hunk->latest_start = hunk->modified_start;
+ hunk->latest_length = hunk->modified_length;
+ hunk->modified_start = hunk->original_start;
+ hunk->modified_length = hunk->original_length;
+
+ if (hunk->type == svn_diff__type_diff_modified)
+ hunk->type = svn_diff__type_diff_latest;
+ else
+ hunk->type = svn_diff__type_diff_modified;
+ }
+
+ /* Get the lcs for common ancestor - original
+ * Do reverse adjustements
+ */
+ lcs_adjust = svn_diff__lcs(position_list[3], position_list[2],
+ token_counts[3], token_counts[2],
+ num_tokens, prefix_lines,
+ suffix_lines, subpool3);
+ diff_adjust = svn_diff__diff(lcs_adjust, 1, 1, FALSE, subpool3);
+ adjust_diff(diff_ol, diff_adjust);
+
+ svn_pool_clear(subpool3);
+
+ /* Get the lcs for modified - common ancestor
+ * Do forward adjustments
+ */
+ lcs_adjust = svn_diff__lcs(position_list[1], position_list[3],
+ token_counts[1], token_counts[3],
+ num_tokens, prefix_lines,
+ suffix_lines, subpool3);
+ diff_adjust = svn_diff__diff(lcs_adjust, 1, 1, FALSE, subpool3);
+ adjust_diff(diff_ol, diff_adjust);
+
+ /* Get rid of the position lists for original and ancestor, and delete
+ * our scratchpool.
+ */
+ svn_pool_destroy(subpool2);
+
+ /* Now we try and resolve the conflicts we encountered */
+ for (hunk = diff_ol; hunk; hunk = hunk->next)
+ {
+ if (hunk->type == svn_diff__type_conflict)
+ {
+ svn_diff__resolve_conflict(hunk, &position_list[1],
+ &position_list[2], num_tokens, pool);
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ *diff = diff_ol;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_diff/diff_file.c b/contrib/subversion/subversion/libsvn_diff/diff_file.c
new file mode 100644
index 0000000..e70c2f9
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/diff_file.c
@@ -0,0 +1,2414 @@
+/*
+ * diff_file.c : routines for doing diffs on files
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_general.h>
+#include <apr_file_io.h>
+#include <apr_file_info.h>
+#include <apr_time.h>
+#include <apr_mmap.h>
+#include <apr_getopt.h>
+
+#include "svn_error.h"
+#include "svn_diff.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_subst.h"
+#include "svn_io.h"
+#include "svn_utf.h"
+#include "svn_pools.h"
+#include "diff.h"
+#include "svn_private_config.h"
+#include "svn_path.h"
+#include "svn_ctype.h"
+
+#include "private/svn_utf_private.h"
+#include "private/svn_eol_private.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_adler32.h"
+#include "private/svn_diff_private.h"
+
+/* A token, i.e. a line read from a file. */
+typedef struct svn_diff__file_token_t
+{
+ /* Next token in free list. */
+ struct svn_diff__file_token_t *next;
+ svn_diff_datasource_e datasource;
+ /* Offset in the datasource. */
+ apr_off_t offset;
+ /* Offset of the normalized token (may skip leading whitespace) */
+ apr_off_t norm_offset;
+ /* Total length - before normalization. */
+ apr_off_t raw_length;
+ /* Total length - after normalization. */
+ apr_off_t length;
+} svn_diff__file_token_t;
+
+
+typedef struct svn_diff__file_baton_t
+{
+ const svn_diff_file_options_t *options;
+
+ struct file_info {
+ const char *path; /* path to this file, absolute or relative to CWD */
+
+ /* All the following fields are active while this datasource is open */
+ apr_file_t *file; /* handle of this file */
+ apr_off_t size; /* total raw size in bytes of this file */
+
+ /* The current chunk: CHUNK_SIZE bytes except for the last chunk. */
+ int chunk; /* the current chunk number, zero-based */
+ char *buffer; /* a buffer containing the current chunk */
+ char *curp; /* current position in the current chunk */
+ char *endp; /* next memory address after the current chunk */
+
+ svn_diff__normalize_state_t normalize_state;
+
+ /* Where the identical suffix starts in this datasource */
+ int suffix_start_chunk;
+ apr_off_t suffix_offset_in_chunk;
+ } files[4];
+
+ /* List of free tokens that may be reused. */
+ svn_diff__file_token_t *tokens;
+
+ apr_pool_t *pool;
+} svn_diff__file_baton_t;
+
+static int
+datasource_to_index(svn_diff_datasource_e datasource)
+{
+ switch (datasource)
+ {
+ case svn_diff_datasource_original:
+ return 0;
+
+ case svn_diff_datasource_modified:
+ return 1;
+
+ case svn_diff_datasource_latest:
+ return 2;
+
+ case svn_diff_datasource_ancestor:
+ return 3;
+ }
+
+ return -1;
+}
+
+/* Files are read in chunks of 128k. There is no support for this number
+ * whatsoever. If there is a number someone comes up with that has some
+ * argumentation, let's use that.
+ */
+/* If you change this number, update test_norm_offset(),
+ * test_identical_suffix() and and test_token_compare() in diff-diff3-test.c.
+ */
+#define CHUNK_SHIFT 17
+#define CHUNK_SIZE (1 << CHUNK_SHIFT)
+
+#define chunk_to_offset(chunk) ((chunk) << CHUNK_SHIFT)
+#define offset_to_chunk(offset) ((offset) >> CHUNK_SHIFT)
+#define offset_in_chunk(offset) ((offset) & (CHUNK_SIZE - 1))
+
+
+/* Read a chunk from a FILE into BUFFER, starting from OFFSET, going for
+ * *LENGTH. The actual bytes read are stored in *LENGTH on return.
+ */
+static APR_INLINE svn_error_t *
+read_chunk(apr_file_t *file, const char *path,
+ char *buffer, apr_off_t length,
+ apr_off_t offset, apr_pool_t *pool)
+{
+ /* XXX: The final offset may not be the one we asked for.
+ * XXX: Check.
+ */
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));
+ return svn_io_file_read_full2(file, buffer, (apr_size_t) length,
+ NULL, NULL, pool);
+}
+
+
+/* Map or read a file at PATH. *BUFFER will point to the file
+ * contents; if the file was mapped, *FILE and *MM will contain the
+ * mmap context; otherwise they will be NULL. SIZE will contain the
+ * file size. Allocate from POOL.
+ */
+#if APR_HAS_MMAP
+#define MMAP_T_PARAM(NAME) apr_mmap_t **NAME,
+#define MMAP_T_ARG(NAME) &(NAME),
+#else
+#define MMAP_T_PARAM(NAME)
+#define MMAP_T_ARG(NAME)
+#endif
+
+static svn_error_t *
+map_or_read_file(apr_file_t **file,
+ MMAP_T_PARAM(mm)
+ char **buffer, apr_off_t *size,
+ const char *path, apr_pool_t *pool)
+{
+ apr_finfo_t finfo;
+ apr_status_t rv;
+
+ *buffer = NULL;
+
+ SVN_ERR(svn_io_file_open(file, path, APR_READ, APR_OS_DEFAULT, pool));
+ SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, *file, pool));
+
+#if APR_HAS_MMAP
+ if (finfo.size > APR_MMAP_THRESHOLD)
+ {
+ rv = apr_mmap_create(mm, *file, 0, (apr_size_t) finfo.size,
+ APR_MMAP_READ, pool);
+ if (rv == APR_SUCCESS)
+ {
+ *buffer = (*mm)->mm;
+ }
+
+ /* On failure we just fall through and try reading the file into
+ * memory instead.
+ */
+ }
+#endif /* APR_HAS_MMAP */
+
+ if (*buffer == NULL && finfo.size > 0)
+ {
+ *buffer = apr_palloc(pool, (apr_size_t) finfo.size);
+
+ SVN_ERR(svn_io_file_read_full2(*file, *buffer, (apr_size_t) finfo.size,
+ NULL, NULL, pool));
+
+ /* Since we have the entire contents of the file we can
+ * close it now.
+ */
+ SVN_ERR(svn_io_file_close(*file, pool));
+
+ *file = NULL;
+ }
+
+ *size = finfo.size;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* For all files in the FILE array, increment the curp pointer. If a file
+ * points before the beginning of file, let it point at the first byte again.
+ * If the end of the current chunk is reached, read the next chunk in the
+ * buffer and point curp to the start of the chunk. If EOF is reached, set
+ * curp equal to endp to indicate EOF. */
+#define INCREMENT_POINTERS(all_files, files_len, pool) \
+ do { \
+ apr_size_t svn_macro__i; \
+ \
+ for (svn_macro__i = 0; svn_macro__i < (files_len); svn_macro__i++) \
+ { \
+ if ((all_files)[svn_macro__i].curp < (all_files)[svn_macro__i].endp - 1)\
+ (all_files)[svn_macro__i].curp++; \
+ else \
+ SVN_ERR(increment_chunk(&(all_files)[svn_macro__i], (pool))); \
+ } \
+ } while (0)
+
+
+/* For all files in the FILE array, decrement the curp pointer. If the
+ * start of a chunk is reached, read the previous chunk in the buffer and
+ * point curp to the last byte of the chunk. If the beginning of a FILE is
+ * reached, set chunk to -1 to indicate BOF. */
+#define DECREMENT_POINTERS(all_files, files_len, pool) \
+ do { \
+ apr_size_t svn_macro__i; \
+ \
+ for (svn_macro__i = 0; svn_macro__i < (files_len); svn_macro__i++) \
+ { \
+ if ((all_files)[svn_macro__i].curp > (all_files)[svn_macro__i].buffer) \
+ (all_files)[svn_macro__i].curp--; \
+ else \
+ SVN_ERR(decrement_chunk(&(all_files)[svn_macro__i], (pool))); \
+ } \
+ } while (0)
+
+
+static svn_error_t *
+increment_chunk(struct file_info *file, apr_pool_t *pool)
+{
+ apr_off_t length;
+ apr_off_t last_chunk = offset_to_chunk(file->size);
+
+ if (file->chunk == -1)
+ {
+ /* We are at BOF (Beginning Of File). Point to first chunk/byte again. */
+ file->chunk = 0;
+ file->curp = file->buffer;
+ }
+ else if (file->chunk == last_chunk)
+ {
+ /* We are at the last chunk. Indicate EOF by setting curp == endp. */
+ file->curp = file->endp;
+ }
+ else
+ {
+ /* There are still chunks left. Read next chunk and reset pointers. */
+ file->chunk++;
+ length = file->chunk == last_chunk ?
+ offset_in_chunk(file->size) : CHUNK_SIZE;
+ SVN_ERR(read_chunk(file->file, file->path, file->buffer,
+ length, chunk_to_offset(file->chunk),
+ pool));
+ file->endp = file->buffer + length;
+ file->curp = file->buffer;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+decrement_chunk(struct file_info *file, apr_pool_t *pool)
+{
+ if (file->chunk == 0)
+ {
+ /* We are already at the first chunk. Indicate BOF (Beginning Of File)
+ by setting chunk = -1 and curp = endp - 1. Both conditions are
+ important. They help the increment step to catch the BOF situation
+ in an efficient way. */
+ file->chunk--;
+ file->curp = file->endp - 1;
+ }
+ else
+ {
+ /* Read previous chunk and reset pointers. */
+ file->chunk--;
+ SVN_ERR(read_chunk(file->file, file->path, file->buffer,
+ CHUNK_SIZE, chunk_to_offset(file->chunk),
+ pool));
+ file->endp = file->buffer + CHUNK_SIZE;
+ file->curp = file->endp - 1;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Check whether one of the FILEs has its pointers 'before' the beginning of
+ * the file (this can happen while scanning backwards). This is the case if
+ * one of them has chunk == -1. */
+static svn_boolean_t
+is_one_at_bof(struct file_info file[], apr_size_t file_len)
+{
+ apr_size_t i;
+
+ for (i = 0; i < file_len; i++)
+ if (file[i].chunk == -1)
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Check whether one of the FILEs has its pointers at EOF (this is the case if
+ * one of them has curp == endp (this can only happen at the last chunk)) */
+static svn_boolean_t
+is_one_at_eof(struct file_info file[], apr_size_t file_len)
+{
+ apr_size_t i;
+
+ for (i = 0; i < file_len; i++)
+ if (file[i].curp == file[i].endp)
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Quickly determine whether there is a eol char in CHUNK.
+ * (mainly copy-n-paste from eol.c#svn_eol__find_eol_start).
+ */
+
+#if SVN_UNALIGNED_ACCESS_IS_OK
+static svn_boolean_t contains_eol(apr_uintptr_t chunk)
+{
+ apr_uintptr_t r_test = chunk ^ SVN__R_MASK;
+ apr_uintptr_t n_test = chunk ^ SVN__N_MASK;
+
+ r_test |= (r_test & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET;
+ n_test |= (n_test & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET;
+
+ return (r_test & n_test & SVN__BIT_7_SET) != SVN__BIT_7_SET;
+}
+#endif
+
+/* Find the prefix which is identical between all elements of the FILE array.
+ * Return the number of prefix lines in PREFIX_LINES. REACHED_ONE_EOF will be
+ * set to TRUE if one of the FILEs reached its end while scanning prefix,
+ * i.e. at least one file consisted entirely of prefix. Otherwise,
+ * REACHED_ONE_EOF is set to FALSE.
+ *
+ * After this function is finished, the buffers, chunks, curp's and endp's
+ * of the FILEs are set to point at the first byte after the prefix. */
+static svn_error_t *
+find_identical_prefix(svn_boolean_t *reached_one_eof, apr_off_t *prefix_lines,
+ struct file_info file[], apr_size_t file_len,
+ apr_pool_t *pool)
+{
+ svn_boolean_t had_cr = FALSE;
+ svn_boolean_t is_match;
+ apr_off_t lines = 0;
+ apr_size_t i;
+
+ *reached_one_eof = FALSE;
+
+ for (i = 1, is_match = TRUE; i < file_len; i++)
+ is_match = is_match && *file[0].curp == *file[i].curp;
+ while (is_match)
+ {
+#if SVN_UNALIGNED_ACCESS_IS_OK
+ apr_ssize_t max_delta, delta;
+#endif /* SVN_UNALIGNED_ACCESS_IS_OK */
+
+ /* ### TODO: see if we can take advantage of
+ diff options like ignore_eol_style or ignore_space. */
+ /* check for eol, and count */
+ if (*file[0].curp == '\r')
+ {
+ lines++;
+ had_cr = TRUE;
+ }
+ else if (*file[0].curp == '\n' && !had_cr)
+ {
+ lines++;
+ }
+ else
+ {
+ had_cr = FALSE;
+ }
+
+ INCREMENT_POINTERS(file, file_len, pool);
+
+#if SVN_UNALIGNED_ACCESS_IS_OK
+
+ /* Try to advance as far as possible with machine-word granularity.
+ * Determine how far we may advance with chunky ops without reaching
+ * endp for any of the files.
+ * Signedness is important here if curp gets close to endp.
+ */
+ max_delta = file[0].endp - file[0].curp - sizeof(apr_uintptr_t);
+ for (i = 1; i < file_len; i++)
+ {
+ delta = file[i].endp - file[i].curp - sizeof(apr_uintptr_t);
+ if (delta < max_delta)
+ max_delta = delta;
+ }
+
+ is_match = TRUE;
+ for (delta = 0; delta < max_delta; delta += sizeof(apr_uintptr_t))
+ {
+ apr_uintptr_t chunk = *(const apr_uintptr_t *)(file[0].curp + delta);
+ if (contains_eol(chunk))
+ break;
+
+ for (i = 1; i < file_len; i++)
+ if (chunk != *(const apr_uintptr_t *)(file[i].curp + delta))
+ {
+ is_match = FALSE;
+ break;
+ }
+
+ if (! is_match)
+ break;
+ }
+
+ if (delta /* > 0*/)
+ {
+ /* We either found a mismatch or an EOL at or shortly behind curp+delta
+ * or we cannot proceed with chunky ops without exceeding endp.
+ * In any way, everything up to curp + delta is equal and not an EOL.
+ */
+ for (i = 0; i < file_len; i++)
+ file[i].curp += delta;
+
+ /* Skipped data without EOL markers, so last char was not a CR. */
+ had_cr = FALSE;
+ }
+#endif
+
+ *reached_one_eof = is_one_at_eof(file, file_len);
+ if (*reached_one_eof)
+ break;
+ else
+ for (i = 1, is_match = TRUE; i < file_len; i++)
+ is_match = is_match && *file[0].curp == *file[i].curp;
+ }
+
+ if (had_cr)
+ {
+ /* Check if we ended in the middle of a \r\n for one file, but \r for
+ another. If so, back up one byte, so the next loop will back up
+ the entire line. Also decrement lines, since we counted one
+ too many for the \r. */
+ svn_boolean_t ended_at_nonmatching_newline = FALSE;
+ for (i = 0; i < file_len; i++)
+ if (file[i].curp < file[i].endp)
+ ended_at_nonmatching_newline = ended_at_nonmatching_newline
+ || *file[i].curp == '\n';
+ if (ended_at_nonmatching_newline)
+ {
+ lines--;
+ DECREMENT_POINTERS(file, file_len, pool);
+ }
+ }
+
+ /* Back up one byte, so we point at the last identical byte */
+ DECREMENT_POINTERS(file, file_len, pool);
+
+ /* Back up to the last eol sequence (\n, \r\n or \r) */
+ while (!is_one_at_bof(file, file_len) &&
+ *file[0].curp != '\n' && *file[0].curp != '\r')
+ DECREMENT_POINTERS(file, file_len, pool);
+
+ /* Slide one byte forward, to point past the eol sequence */
+ INCREMENT_POINTERS(file, file_len, pool);
+
+ *prefix_lines = lines;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The number of identical suffix lines to keep with the middle section. These
+ * lines are not eliminated as suffix, and can be picked up by the token
+ * parsing and lcs steps. This is mainly for backward compatibility with
+ * the previous diff (and blame) output (if there are multiple diff solutions,
+ * our lcs algorithm prefers taking common lines from the start, rather than
+ * from the end. By giving it back some suffix lines, we give it some wiggle
+ * room to find the exact same diff as before).
+ *
+ * The number 50 is more or less arbitrary, based on some real-world tests
+ * with big files (and then doubling the required number to be on the safe
+ * side). This has a negligible effect on the power of the optimization. */
+/* If you change this number, update test_identical_suffix() in diff-diff3-test.c */
+#ifndef SUFFIX_LINES_TO_KEEP
+#define SUFFIX_LINES_TO_KEEP 50
+#endif
+
+/* Find the suffix which is identical between all elements of the FILE array.
+ * Return the number of suffix lines in SUFFIX_LINES.
+ *
+ * Before this function is called the FILEs' pointers and chunks should be
+ * positioned right after the identical prefix (which is the case after
+ * find_identical_prefix), so we can determine where suffix scanning should
+ * ultimately stop. */
+static svn_error_t *
+find_identical_suffix(apr_off_t *suffix_lines, struct file_info file[],
+ apr_size_t file_len, apr_pool_t *pool)
+{
+ struct file_info file_for_suffix[4] = { { 0 } };
+ apr_off_t length[4];
+ apr_off_t suffix_min_chunk0;
+ apr_off_t suffix_min_offset0;
+ apr_off_t min_file_size;
+ int suffix_lines_to_keep = SUFFIX_LINES_TO_KEEP;
+ svn_boolean_t is_match;
+ apr_off_t lines = 0;
+ svn_boolean_t had_cr;
+ svn_boolean_t had_nl;
+ apr_size_t i;
+
+ /* Initialize file_for_suffix[].
+ Read last chunk, position curp at last byte. */
+ for (i = 0; i < file_len; i++)
+ {
+ file_for_suffix[i].path = file[i].path;
+ file_for_suffix[i].file = file[i].file;
+ file_for_suffix[i].size = file[i].size;
+ file_for_suffix[i].chunk =
+ (int) offset_to_chunk(file_for_suffix[i].size); /* last chunk */
+ length[i] = offset_in_chunk(file_for_suffix[i].size);
+ if (length[i] == 0)
+ {
+ /* last chunk is an empty chunk -> start at next-to-last chunk */
+ file_for_suffix[i].chunk = file_for_suffix[i].chunk - 1;
+ length[i] = CHUNK_SIZE;
+ }
+
+ if (file_for_suffix[i].chunk == file[i].chunk)
+ {
+ /* Prefix ended in last chunk, so we can reuse the prefix buffer */
+ file_for_suffix[i].buffer = file[i].buffer;
+ }
+ else
+ {
+ /* There is at least more than 1 chunk,
+ so allocate full chunk size buffer */
+ file_for_suffix[i].buffer = apr_palloc(pool, CHUNK_SIZE);
+ SVN_ERR(read_chunk(file_for_suffix[i].file, file_for_suffix[i].path,
+ file_for_suffix[i].buffer, length[i],
+ chunk_to_offset(file_for_suffix[i].chunk),
+ pool));
+ }
+ file_for_suffix[i].endp = file_for_suffix[i].buffer + length[i];
+ file_for_suffix[i].curp = file_for_suffix[i].endp - 1;
+ }
+
+ /* Get the chunk and pointer offset (for file[0]) at which we should stop
+ scanning backward for the identical suffix, i.e. when we reach prefix. */
+ suffix_min_chunk0 = file[0].chunk;
+ suffix_min_offset0 = file[0].curp - file[0].buffer;
+
+ /* Compensate if other files are smaller than file[0] */
+ for (i = 1, min_file_size = file[0].size; i < file_len; i++)
+ if (file[i].size < min_file_size)
+ min_file_size = file[i].size;
+ if (file[0].size > min_file_size)
+ {
+ suffix_min_chunk0 += (file[0].size - min_file_size) / CHUNK_SIZE;
+ suffix_min_offset0 += (file[0].size - min_file_size) % CHUNK_SIZE;
+ }
+
+ /* Scan backwards until mismatch or until we reach the prefix. */
+ for (i = 1, is_match = TRUE; i < file_len; i++)
+ is_match = is_match
+ && *file_for_suffix[0].curp == *file_for_suffix[i].curp;
+ if (is_match && *file_for_suffix[0].curp != '\r'
+ && *file_for_suffix[0].curp != '\n')
+ /* Count an extra line for the last line not ending in an eol. */
+ lines++;
+
+ had_nl = FALSE;
+ while (is_match)
+ {
+ svn_boolean_t reached_prefix;
+#if SVN_UNALIGNED_ACCESS_IS_OK
+ /* Initialize the minimum pointer positions. */
+ const char *min_curp[4];
+ svn_boolean_t can_read_word;
+#endif /* SVN_UNALIGNED_ACCESS_IS_OK */
+
+ /* ### TODO: see if we can take advantage of
+ diff options like ignore_eol_style or ignore_space. */
+ /* check for eol, and count */
+ if (*file_for_suffix[0].curp == '\n')
+ {
+ lines++;
+ had_nl = TRUE;
+ }
+ else if (*file_for_suffix[0].curp == '\r' && !had_nl)
+ {
+ lines++;
+ }
+ else
+ {
+ had_nl = FALSE;
+ }
+
+ DECREMENT_POINTERS(file_for_suffix, file_len, pool);
+
+#if SVN_UNALIGNED_ACCESS_IS_OK
+ for (i = 0; i < file_len; i++)
+ min_curp[i] = file_for_suffix[i].buffer;
+
+ /* If we are in the same chunk that contains the last part of the common
+ prefix, use the min_curp[0] pointer to make sure we don't get a
+ suffix that overlaps the already determined common prefix. */
+ if (file_for_suffix[0].chunk == suffix_min_chunk0)
+ min_curp[0] += suffix_min_offset0;
+
+ /* Scan quickly by reading with machine-word granularity. */
+ for (i = 0, can_read_word = TRUE; i < file_len; i++)
+ can_read_word = can_read_word
+ && ( (file_for_suffix[i].curp + 1
+ - sizeof(apr_uintptr_t))
+ > min_curp[i]);
+ while (can_read_word)
+ {
+ apr_uintptr_t chunk;
+
+ /* For each file curp is positioned at the current byte, but we
+ want to examine the current byte and the ones before the current
+ location as one machine word. */
+
+ chunk = *(const apr_uintptr_t *)(file_for_suffix[0].curp + 1
+ - sizeof(apr_uintptr_t));
+ if (contains_eol(chunk))
+ break;
+
+ for (i = 1, is_match = TRUE; i < file_len; i++)
+ is_match = is_match
+ && ( chunk
+ == *(const apr_uintptr_t *)
+ (file_for_suffix[i].curp + 1
+ - sizeof(apr_uintptr_t)));
+
+ if (! is_match)
+ break;
+
+ for (i = 0; i < file_len; i++)
+ {
+ file_for_suffix[i].curp -= sizeof(apr_uintptr_t);
+ can_read_word = can_read_word
+ && ( (file_for_suffix[i].curp + 1
+ - sizeof(apr_uintptr_t))
+ > min_curp[i]);
+ }
+
+ /* We skipped some bytes, so there are no closing EOLs */
+ had_nl = FALSE;
+ had_cr = FALSE;
+ }
+
+ /* The > min_curp[i] check leaves at least one final byte for checking
+ in the non block optimized case below. */
+#endif
+
+ reached_prefix = file_for_suffix[0].chunk == suffix_min_chunk0
+ && (file_for_suffix[0].curp - file_for_suffix[0].buffer)
+ == suffix_min_offset0;
+ if (reached_prefix || is_one_at_bof(file_for_suffix, file_len))
+ break;
+
+ is_match = TRUE;
+ for (i = 1; i < file_len; i++)
+ is_match = is_match
+ && *file_for_suffix[0].curp == *file_for_suffix[i].curp;
+ }
+
+ /* Slide one byte forward, to point at the first byte of identical suffix */
+ INCREMENT_POINTERS(file_for_suffix, file_len, pool);
+
+ /* Slide forward until we find an eol sequence to add the rest of the line
+ we're in. Then add SUFFIX_LINES_TO_KEEP more lines. Stop if at least
+ one file reaches its end. */
+ do
+ {
+ had_cr = FALSE;
+ while (!is_one_at_eof(file_for_suffix, file_len)
+ && *file_for_suffix[0].curp != '\n'
+ && *file_for_suffix[0].curp != '\r')
+ INCREMENT_POINTERS(file_for_suffix, file_len, pool);
+
+ /* Slide one or two more bytes, to point past the eol. */
+ if (!is_one_at_eof(file_for_suffix, file_len)
+ && *file_for_suffix[0].curp == '\r')
+ {
+ lines--;
+ had_cr = TRUE;
+ INCREMENT_POINTERS(file_for_suffix, file_len, pool);
+ }
+ if (!is_one_at_eof(file_for_suffix, file_len)
+ && *file_for_suffix[0].curp == '\n')
+ {
+ if (!had_cr)
+ lines--;
+ INCREMENT_POINTERS(file_for_suffix, file_len, pool);
+ }
+ }
+ while (!is_one_at_eof(file_for_suffix, file_len)
+ && suffix_lines_to_keep--);
+
+ if (is_one_at_eof(file_for_suffix, file_len))
+ lines = 0;
+
+ /* Save the final suffix information in the original file_info */
+ for (i = 0; i < file_len; i++)
+ {
+ file[i].suffix_start_chunk = file_for_suffix[i].chunk;
+ file[i].suffix_offset_in_chunk =
+ file_for_suffix[i].curp - file_for_suffix[i].buffer;
+ }
+
+ *suffix_lines = lines;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Let FILE stand for the array of file_info struct elements of BATON->files
+ * that are indexed by the elements of the DATASOURCE array.
+ * BATON's type is (svn_diff__file_baton_t *).
+ *
+ * For each file in the FILE array, open the file at FILE.path; initialize
+ * FILE.file, FILE.size, FILE.buffer, FILE.curp and FILE.endp; allocate a
+ * buffer and read the first chunk. Then find the prefix and suffix lines
+ * which are identical between all the files. Return the number of identical
+ * prefix lines in PREFIX_LINES, and the number of identical suffix lines in
+ * SUFFIX_LINES.
+ *
+ * Finding the identical prefix and suffix allows us to exclude those from the
+ * rest of the diff algorithm, which increases performance by reducing the
+ * problem space.
+ *
+ * Implements svn_diff_fns2_t::datasources_open. */
+static svn_error_t *
+datasources_open(void *baton,
+ apr_off_t *prefix_lines,
+ apr_off_t *suffix_lines,
+ const svn_diff_datasource_e *datasources,
+ apr_size_t datasources_len)
+{
+ svn_diff__file_baton_t *file_baton = baton;
+ struct file_info files[4];
+ apr_finfo_t finfo[4];
+ apr_off_t length[4];
+#ifndef SVN_DISABLE_PREFIX_SUFFIX_SCANNING
+ svn_boolean_t reached_one_eof;
+#endif
+ apr_size_t i;
+
+ /* Make sure prefix_lines and suffix_lines are set correctly, even if we
+ * exit early because one of the files is empty. */
+ *prefix_lines = 0;
+ *suffix_lines = 0;
+
+ /* Open datasources and read first chunk */
+ for (i = 0; i < datasources_len; i++)
+ {
+ struct file_info *file
+ = &file_baton->files[datasource_to_index(datasources[i])];
+ SVN_ERR(svn_io_file_open(&file->file, file->path,
+ APR_READ, APR_OS_DEFAULT, file_baton->pool));
+ SVN_ERR(svn_io_file_info_get(&finfo[i], APR_FINFO_SIZE,
+ file->file, file_baton->pool));
+ file->size = finfo[i].size;
+ length[i] = finfo[i].size > CHUNK_SIZE ? CHUNK_SIZE : finfo[i].size;
+ file->buffer = apr_palloc(file_baton->pool, (apr_size_t) length[i]);
+ SVN_ERR(read_chunk(file->file, file->path, file->buffer,
+ length[i], 0, file_baton->pool));
+ file->endp = file->buffer + length[i];
+ file->curp = file->buffer;
+ /* Set suffix_start_chunk to a guard value, so if suffix scanning is
+ * skipped because one of the files is empty, or because of
+ * reached_one_eof, we can still easily check for the suffix during
+ * token reading (datasource_get_next_token). */
+ file->suffix_start_chunk = -1;
+
+ files[i] = *file;
+ }
+
+ for (i = 0; i < datasources_len; i++)
+ if (length[i] == 0)
+ /* There will not be any identical prefix/suffix, so we're done. */
+ return SVN_NO_ERROR;
+
+#ifndef SVN_DISABLE_PREFIX_SUFFIX_SCANNING
+
+ SVN_ERR(find_identical_prefix(&reached_one_eof, prefix_lines,
+ files, datasources_len, file_baton->pool));
+
+ if (!reached_one_eof)
+ /* No file consisted totally of identical prefix,
+ * so there may be some identical suffix. */
+ SVN_ERR(find_identical_suffix(suffix_lines, files, datasources_len,
+ file_baton->pool));
+
+#endif
+
+ /* Copy local results back to baton. */
+ for (i = 0; i < datasources_len; i++)
+ file_baton->files[datasource_to_index(datasources[i])] = files[i];
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_diff_fns2_t::datasource_close */
+static svn_error_t *
+datasource_close(void *baton, svn_diff_datasource_e datasource)
+{
+ /* Do nothing. The compare_token function needs previous datasources
+ * to stay available until all datasources are processed.
+ */
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_diff_fns2_t::datasource_get_next_token */
+static svn_error_t *
+datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,
+ svn_diff_datasource_e datasource)
+{
+ svn_diff__file_baton_t *file_baton = baton;
+ svn_diff__file_token_t *file_token;
+ struct file_info *file = &file_baton->files[datasource_to_index(datasource)];
+ char *endp;
+ char *curp;
+ char *eol;
+ apr_off_t last_chunk;
+ apr_off_t length;
+ apr_uint32_t h = 0;
+ /* Did the last chunk end in a CR character? */
+ svn_boolean_t had_cr = FALSE;
+
+ *token = NULL;
+
+ curp = file->curp;
+ endp = file->endp;
+
+ last_chunk = offset_to_chunk(file->size);
+
+ /* Are we already at the end of a chunk? */
+ if (curp == endp)
+ {
+ /* Are we at EOF */
+ if (last_chunk == file->chunk)
+ return SVN_NO_ERROR; /* EOF */
+
+ /* Or right before an identical suffix in the next chunk? */
+ if (file->chunk + 1 == file->suffix_start_chunk
+ && file->suffix_offset_in_chunk == 0)
+ return SVN_NO_ERROR;
+ }
+
+ /* Stop when we encounter the identical suffix. If suffix scanning was not
+ * performed, suffix_start_chunk will be -1, so this condition will never
+ * be true. */
+ if (file->chunk == file->suffix_start_chunk
+ && (curp - file->buffer) == file->suffix_offset_in_chunk)
+ return SVN_NO_ERROR;
+
+ /* Allocate a new token, or fetch one from the "reusable tokens" list. */
+ file_token = file_baton->tokens;
+ if (file_token)
+ {
+ file_baton->tokens = file_token->next;
+ }
+ else
+ {
+ file_token = apr_palloc(file_baton->pool, sizeof(*file_token));
+ }
+
+ file_token->datasource = datasource;
+ file_token->offset = chunk_to_offset(file->chunk)
+ + (curp - file->buffer);
+ file_token->norm_offset = file_token->offset;
+ file_token->raw_length = 0;
+ file_token->length = 0;
+
+ while (1)
+ {
+ eol = svn_eol__find_eol_start(curp, endp - curp);
+ if (eol)
+ {
+ had_cr = (*eol == '\r');
+ eol++;
+ /* If we have the whole eol sequence in the chunk... */
+ if (!(had_cr && eol == endp))
+ {
+ /* Also skip past the '\n' in an '\r\n' sequence. */
+ if (had_cr && *eol == '\n')
+ eol++;
+ break;
+ }
+ }
+
+ if (file->chunk == last_chunk)
+ {
+ eol = endp;
+ break;
+ }
+
+ length = endp - curp;
+ file_token->raw_length += length;
+ {
+ char *c = curp;
+
+ svn_diff__normalize_buffer(&c, &length,
+ &file->normalize_state,
+ curp, file_baton->options);
+ if (file_token->length == 0)
+ {
+ /* When we are reading the first part of the token, move the
+ normalized offset past leading ignored characters, if any. */
+ file_token->norm_offset += (c - curp);
+ }
+ file_token->length += length;
+ h = svn__adler32(h, c, length);
+ }
+
+ curp = endp = file->buffer;
+ file->chunk++;
+ length = file->chunk == last_chunk ?
+ offset_in_chunk(file->size) : CHUNK_SIZE;
+ endp += length;
+ file->endp = endp;
+
+ /* Issue #4283: Normally we should have checked for reaching the skipped
+ suffix here, but because we assume that a suffix always starts on a
+ line and token boundary we rely on catching the suffix earlier in this
+ function.
+
+ When changing things here, make sure the whitespace settings are
+ applied, or we mught not reach the exact suffix boundary as token
+ boundary. */
+ SVN_ERR(read_chunk(file->file, file->path,
+ curp, length,
+ chunk_to_offset(file->chunk),
+ file_baton->pool));
+
+ /* If the last chunk ended in a CR, we're done. */
+ if (had_cr)
+ {
+ eol = curp;
+ if (*curp == '\n')
+ ++eol;
+ break;
+ }
+ }
+
+ length = eol - curp;
+ file_token->raw_length += length;
+ file->curp = eol;
+
+ /* If the file length is exactly a multiple of CHUNK_SIZE, we will end up
+ * with a spurious empty token. Avoid returning it.
+ * Note that we use the unnormalized length; we don't want a line containing
+ * only spaces (and no trailing newline) to appear like a non-existent
+ * line. */
+ if (file_token->raw_length > 0)
+ {
+ char *c = curp;
+ svn_diff__normalize_buffer(&c, &length,
+ &file->normalize_state,
+ curp, file_baton->options);
+ if (file_token->length == 0)
+ {
+ /* When we are reading the first part of the token, move the
+ normalized offset past leading ignored characters, if any. */
+ file_token->norm_offset += (c - curp);
+ }
+
+ file_token->length += length;
+
+ *hash = svn__adler32(h, c, length);
+ *token = file_token;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#define COMPARE_CHUNK_SIZE 4096
+
+/* Implements svn_diff_fns2_t::token_compare */
+static svn_error_t *
+token_compare(void *baton, void *token1, void *token2, int *compare)
+{
+ svn_diff__file_baton_t *file_baton = baton;
+ svn_diff__file_token_t *file_token[2];
+ char buffer[2][COMPARE_CHUNK_SIZE];
+ char *bufp[2];
+ apr_off_t offset[2];
+ struct file_info *file[2];
+ apr_off_t length[2];
+ apr_off_t total_length;
+ /* How much is left to read of each token from the file. */
+ apr_off_t raw_length[2];
+ int i;
+ svn_diff__normalize_state_t state[2];
+
+ file_token[0] = token1;
+ file_token[1] = token2;
+ if (file_token[0]->length < file_token[1]->length)
+ {
+ *compare = -1;
+ return SVN_NO_ERROR;
+ }
+
+ if (file_token[0]->length > file_token[1]->length)
+ {
+ *compare = 1;
+ return SVN_NO_ERROR;
+ }
+
+ total_length = file_token[0]->length;
+ if (total_length == 0)
+ {
+ *compare = 0;
+ return SVN_NO_ERROR;
+ }
+
+ for (i = 0; i < 2; ++i)
+ {
+ int idx = datasource_to_index(file_token[i]->datasource);
+
+ file[i] = &file_baton->files[idx];
+ offset[i] = file_token[i]->norm_offset;
+ state[i] = svn_diff__normalize_state_normal;
+
+ if (offset_to_chunk(offset[i]) == file[i]->chunk)
+ {
+ /* If the start of the token is in memory, the entire token is
+ * in memory.
+ */
+ bufp[i] = file[i]->buffer;
+ bufp[i] += offset_in_chunk(offset[i]);
+
+ length[i] = total_length;
+ raw_length[i] = 0;
+ }
+ else
+ {
+ apr_off_t skipped;
+
+ length[i] = 0;
+
+ /* When we skipped the first part of the token via the whitespace
+ normalization we must reduce the raw length of the token */
+ skipped = (file_token[i]->norm_offset - file_token[i]->offset);
+
+ raw_length[i] = file_token[i]->raw_length - skipped;
+ }
+ }
+
+ do
+ {
+ apr_off_t len;
+ for (i = 0; i < 2; i++)
+ {
+ if (length[i] == 0)
+ {
+ /* Error if raw_length is 0, that's an unexpected change
+ * of the file that can happen when ingoring whitespace
+ * and that can lead to an infinite loop. */
+ if (raw_length[i] == 0)
+ return svn_error_createf(SVN_ERR_DIFF_DATASOURCE_MODIFIED,
+ NULL,
+ _("The file '%s' changed unexpectedly"
+ " during diff"),
+ file[i]->path);
+
+ /* Read a chunk from disk into a buffer */
+ bufp[i] = buffer[i];
+ length[i] = raw_length[i] > COMPARE_CHUNK_SIZE ?
+ COMPARE_CHUNK_SIZE : raw_length[i];
+
+ SVN_ERR(read_chunk(file[i]->file,
+ file[i]->path,
+ bufp[i], length[i], offset[i],
+ file_baton->pool));
+ offset[i] += length[i];
+ raw_length[i] -= length[i];
+ /* bufp[i] gets reset to buffer[i] before reading each chunk,
+ so, overwriting it isn't a problem */
+ svn_diff__normalize_buffer(&bufp[i], &length[i], &state[i],
+ bufp[i], file_baton->options);
+
+ /* assert(length[i] == file_token[i]->length); */
+ }
+ }
+
+ len = length[0] > length[1] ? length[1] : length[0];
+
+ /* Compare two chunks (that could be entire tokens if they both reside
+ * in memory).
+ */
+ *compare = memcmp(bufp[0], bufp[1], (size_t) len);
+ if (*compare != 0)
+ return SVN_NO_ERROR;
+
+ total_length -= len;
+ length[0] -= len;
+ length[1] -= len;
+ bufp[0] += len;
+ bufp[1] += len;
+ }
+ while(total_length > 0);
+
+ *compare = 0;
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_diff_fns2_t::token_discard */
+static void
+token_discard(void *baton, void *token)
+{
+ svn_diff__file_baton_t *file_baton = baton;
+ svn_diff__file_token_t *file_token = token;
+
+ /* Prepend FILE_TOKEN to FILE_BATON->TOKENS, for reuse. */
+ file_token->next = file_baton->tokens;
+ file_baton->tokens = file_token;
+}
+
+
+/* Implements svn_diff_fns2_t::token_discard_all */
+static void
+token_discard_all(void *baton)
+{
+ svn_diff__file_baton_t *file_baton = baton;
+
+ /* Discard all memory in use by the tokens, and close all open files. */
+ svn_pool_clear(file_baton->pool);
+}
+
+
+static const svn_diff_fns2_t svn_diff__file_vtable =
+{
+ datasources_open,
+ datasource_close,
+ datasource_get_next_token,
+ token_compare,
+ token_discard,
+ token_discard_all
+};
+
+/* Id for the --ignore-eol-style option, which doesn't have a short name. */
+#define SVN_DIFF__OPT_IGNORE_EOL_STYLE 256
+
+/* Options supported by svn_diff_file_options_parse(). */
+static const apr_getopt_option_t diff_options[] =
+{
+ { "ignore-space-change", 'b', 0, NULL },
+ { "ignore-all-space", 'w', 0, NULL },
+ { "ignore-eol-style", SVN_DIFF__OPT_IGNORE_EOL_STYLE, 0, NULL },
+ { "show-c-function", 'p', 0, NULL },
+ /* ### For compatibility; we don't support the argument to -u, because
+ * ### we don't have optional argument support. */
+ { "unified", 'u', 0, NULL },
+ { NULL, 0, 0, NULL }
+};
+
+svn_diff_file_options_t *
+svn_diff_file_options_create(apr_pool_t *pool)
+{
+ return apr_pcalloc(pool, sizeof(svn_diff_file_options_t));
+}
+
+/* A baton for use with opt_parsing_error_func(). */
+struct opt_parsing_error_baton_t
+{
+ svn_error_t *err;
+ apr_pool_t *pool;
+};
+
+/* Store an error message from apr_getopt_long(). Set BATON->err to a new
+ * error with a message generated from FMT and the remaining arguments.
+ * Implements apr_getopt_err_fn_t. */
+static void
+opt_parsing_error_func(void *baton,
+ const char *fmt, ...)
+{
+ struct opt_parsing_error_baton_t *b = baton;
+ const char *message;
+ va_list ap;
+
+ va_start(ap, fmt);
+ message = apr_pvsprintf(b->pool, fmt, ap);
+ va_end(ap);
+
+ /* Skip leading ": " (if present, which it always is in known cases). */
+ if (strncmp(message, ": ", 2) == 0)
+ message += 2;
+
+ b->err = svn_error_create(SVN_ERR_INVALID_DIFF_OPTION, NULL, message);
+}
+
+svn_error_t *
+svn_diff_file_options_parse(svn_diff_file_options_t *options,
+ const apr_array_header_t *args,
+ apr_pool_t *pool)
+{
+ apr_getopt_t *os;
+ struct opt_parsing_error_baton_t opt_parsing_error_baton;
+ /* Make room for each option (starting at index 1) plus trailing NULL. */
+ const char **argv = apr_palloc(pool, sizeof(char*) * (args->nelts + 2));
+
+ opt_parsing_error_baton.err = NULL;
+ opt_parsing_error_baton.pool = pool;
+
+ argv[0] = "";
+ memcpy((void *) (argv + 1), args->elts, sizeof(char*) * args->nelts);
+ argv[args->nelts + 1] = NULL;
+
+ apr_getopt_init(&os, pool, args->nelts + 1, argv);
+
+ /* Capture any error message from apr_getopt_long(). This will typically
+ * say which option is wrong, which we would not otherwise know. */
+ os->errfn = opt_parsing_error_func;
+ os->errarg = &opt_parsing_error_baton;
+
+ while (1)
+ {
+ const char *opt_arg;
+ int opt_id;
+ apr_status_t err = apr_getopt_long(os, diff_options, &opt_id, &opt_arg);
+
+ if (APR_STATUS_IS_EOF(err))
+ break;
+ if (err)
+ /* Wrap apr_getopt_long()'s error message. Its doc string implies
+ * it always will produce one, but never mind if it doesn't. Avoid
+ * using the message associated with the return code ERR, because
+ * it refers to the "command line" which may be misleading here. */
+ return svn_error_create(SVN_ERR_INVALID_DIFF_OPTION,
+ opt_parsing_error_baton.err,
+ _("Error in options to internal diff"));
+
+ switch (opt_id)
+ {
+ case 'b':
+ /* -w takes precedence over -b. */
+ if (! options->ignore_space)
+ options->ignore_space = svn_diff_file_ignore_space_change;
+ break;
+ case 'w':
+ options->ignore_space = svn_diff_file_ignore_space_all;
+ break;
+ case SVN_DIFF__OPT_IGNORE_EOL_STYLE:
+ options->ignore_eol_style = TRUE;
+ break;
+ case 'p':
+ options->show_c_function = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* Check for spurious arguments. */
+ if (os->ind < os->argc)
+ return svn_error_createf(SVN_ERR_INVALID_DIFF_OPTION, NULL,
+ _("Invalid argument '%s' in diff options"),
+ os->argv[os->ind]);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff_file_diff_2(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool)
+{
+ svn_diff__file_baton_t baton = { 0 };
+
+ baton.options = options;
+ baton.files[0].path = original;
+ baton.files[1].path = modified;
+ baton.pool = svn_pool_create(pool);
+
+ SVN_ERR(svn_diff_diff_2(diff, &baton, &svn_diff__file_vtable, pool));
+
+ svn_pool_destroy(baton.pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff_file_diff3_2(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ const char *latest,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool)
+{
+ svn_diff__file_baton_t baton = { 0 };
+
+ baton.options = options;
+ baton.files[0].path = original;
+ baton.files[1].path = modified;
+ baton.files[2].path = latest;
+ baton.pool = svn_pool_create(pool);
+
+ SVN_ERR(svn_diff_diff3_2(diff, &baton, &svn_diff__file_vtable, pool));
+
+ svn_pool_destroy(baton.pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff_file_diff4_2(svn_diff_t **diff,
+ const char *original,
+ const char *modified,
+ const char *latest,
+ const char *ancestor,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool)
+{
+ svn_diff__file_baton_t baton = { 0 };
+
+ baton.options = options;
+ baton.files[0].path = original;
+ baton.files[1].path = modified;
+ baton.files[2].path = latest;
+ baton.files[3].path = ancestor;
+ baton.pool = svn_pool_create(pool);
+
+ SVN_ERR(svn_diff_diff4_2(diff, &baton, &svn_diff__file_vtable, pool));
+
+ svn_pool_destroy(baton.pool);
+ return SVN_NO_ERROR;
+}
+
+
+/** Display unified context diffs **/
+
+/* Maximum length of the extra context to show when show_c_function is set.
+ * GNU diff uses 40, let's be brave and use 50 instead. */
+#define SVN_DIFF__EXTRA_CONTEXT_LENGTH 50
+typedef struct svn_diff__file_output_baton_t
+{
+ svn_stream_t *output_stream;
+ const char *header_encoding;
+
+ /* Cached markers, in header_encoding. */
+ const char *context_str;
+ const char *delete_str;
+ const char *insert_str;
+
+ const char *path[2];
+ apr_file_t *file[2];
+
+ apr_off_t current_line[2];
+
+ char buffer[2][4096];
+ apr_size_t length[2];
+ char *curp[2];
+
+ apr_off_t hunk_start[2];
+ apr_off_t hunk_length[2];
+ svn_stringbuf_t *hunk;
+
+ /* Should we emit C functions in the unified diff header */
+ svn_boolean_t show_c_function;
+ /* Extra strings to skip over if we match. */
+ apr_array_header_t *extra_skip_match;
+ /* "Context" to append to the @@ line when the show_c_function option
+ * is set. */
+ svn_stringbuf_t *extra_context;
+ /* Extra context for the current hunk. */
+ char hunk_extra_context[SVN_DIFF__EXTRA_CONTEXT_LENGTH + 1];
+
+ apr_pool_t *pool;
+} svn_diff__file_output_baton_t;
+
+typedef enum svn_diff__file_output_unified_type_e
+{
+ svn_diff__file_output_unified_skip,
+ svn_diff__file_output_unified_context,
+ svn_diff__file_output_unified_delete,
+ svn_diff__file_output_unified_insert
+} svn_diff__file_output_unified_type_e;
+
+
+static svn_error_t *
+output_unified_line(svn_diff__file_output_baton_t *baton,
+ svn_diff__file_output_unified_type_e type, int idx)
+{
+ char *curp;
+ char *eol;
+ apr_size_t length;
+ svn_error_t *err;
+ svn_boolean_t bytes_processed = FALSE;
+ svn_boolean_t had_cr = FALSE;
+ /* Are we collecting extra context? */
+ svn_boolean_t collect_extra = FALSE;
+
+ length = baton->length[idx];
+ curp = baton->curp[idx];
+
+ /* Lazily update the current line even if we're at EOF.
+ * This way we fake output of context at EOF
+ */
+ baton->current_line[idx]++;
+
+ if (length == 0 && apr_file_eof(baton->file[idx]))
+ {
+ return SVN_NO_ERROR;
+ }
+
+ do
+ {
+ if (length > 0)
+ {
+ if (!bytes_processed)
+ {
+ switch (type)
+ {
+ case svn_diff__file_output_unified_context:
+ svn_stringbuf_appendcstr(baton->hunk, baton->context_str);
+ baton->hunk_length[0]++;
+ baton->hunk_length[1]++;
+ break;
+ case svn_diff__file_output_unified_delete:
+ svn_stringbuf_appendcstr(baton->hunk, baton->delete_str);
+ baton->hunk_length[0]++;
+ break;
+ case svn_diff__file_output_unified_insert:
+ svn_stringbuf_appendcstr(baton->hunk, baton->insert_str);
+ baton->hunk_length[1]++;
+ break;
+ default:
+ break;
+ }
+
+ if (baton->show_c_function
+ && (type == svn_diff__file_output_unified_skip
+ || type == svn_diff__file_output_unified_context)
+ && (svn_ctype_isalpha(*curp) || *curp == '$' || *curp == '_')
+ && !svn_cstring_match_glob_list(curp,
+ baton->extra_skip_match))
+ {
+ svn_stringbuf_setempty(baton->extra_context);
+ collect_extra = TRUE;
+ }
+ }
+
+ eol = svn_eol__find_eol_start(curp, length);
+
+ if (eol != NULL)
+ {
+ apr_size_t len;
+
+ had_cr = (*eol == '\r');
+ eol++;
+ len = (apr_size_t)(eol - curp);
+
+ if (! had_cr || len < length)
+ {
+ if (had_cr && *eol == '\n')
+ {
+ ++eol;
+ ++len;
+ }
+
+ length -= len;
+
+ if (type != svn_diff__file_output_unified_skip)
+ {
+ svn_stringbuf_appendbytes(baton->hunk, curp, len);
+ }
+ if (collect_extra)
+ {
+ svn_stringbuf_appendbytes(baton->extra_context,
+ curp, len);
+ }
+
+ baton->curp[idx] = eol;
+ baton->length[idx] = length;
+
+ err = SVN_NO_ERROR;
+
+ break;
+ }
+ }
+
+ if (type != svn_diff__file_output_unified_skip)
+ {
+ svn_stringbuf_appendbytes(baton->hunk, curp, length);
+ }
+
+ if (collect_extra)
+ {
+ svn_stringbuf_appendbytes(baton->extra_context, curp, length);
+ }
+
+ bytes_processed = TRUE;
+ }
+
+ curp = baton->buffer[idx];
+ length = sizeof(baton->buffer[idx]);
+
+ err = svn_io_file_read(baton->file[idx], curp, &length, baton->pool);
+
+ /* If the last chunk ended with a CR, we look for an LF at the start
+ of this chunk. */
+ if (had_cr)
+ {
+ if (! err && length > 0 && *curp == '\n')
+ {
+ if (type != svn_diff__file_output_unified_skip)
+ {
+ svn_stringbuf_appendbyte(baton->hunk, *curp);
+ }
+ /* We don't append the LF to extra_context, since it would
+ * just be stripped anyway. */
+ ++curp;
+ --length;
+ }
+
+ baton->curp[idx] = curp;
+ baton->length[idx] = length;
+
+ break;
+ }
+ }
+ while (! err);
+
+ if (err && ! APR_STATUS_IS_EOF(err->apr_err))
+ return err;
+
+ if (err && APR_STATUS_IS_EOF(err->apr_err))
+ {
+ svn_error_clear(err);
+ /* Special case if we reach the end of file AND the last line is in the
+ changed range AND the file doesn't end with a newline */
+ if (bytes_processed && (type != svn_diff__file_output_unified_skip)
+ && ! had_cr)
+ {
+ SVN_ERR(svn_diff__unified_append_no_newline_msg(
+ baton->hunk, baton->header_encoding, baton->pool));
+ }
+
+ baton->length[idx] = 0;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static APR_INLINE svn_error_t *
+output_unified_diff_range(svn_diff__file_output_baton_t *output_baton,
+ int source,
+ svn_diff__file_output_unified_type_e type,
+ apr_off_t until)
+{
+ while (output_baton->current_line[source] < until)
+ {
+ SVN_ERR(output_unified_line(output_baton, type, source));
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+output_unified_flush_hunk(svn_diff__file_output_baton_t *baton)
+{
+ apr_off_t target_line;
+ apr_size_t hunk_len;
+ apr_off_t old_start;
+ apr_off_t new_start;
+
+ if (svn_stringbuf_isempty(baton->hunk))
+ {
+ /* Nothing to flush */
+ return SVN_NO_ERROR;
+ }
+
+ target_line = baton->hunk_start[0] + baton->hunk_length[0]
+ + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+
+ /* Add trailing context to the hunk */
+ SVN_ERR(output_unified_diff_range(baton, 0 /* original */,
+ svn_diff__file_output_unified_context,
+ target_line));
+
+ old_start = baton->hunk_start[0];
+ new_start = baton->hunk_start[1];
+
+ /* If the file is non-empty, convert the line indexes from
+ zero based to one based */
+ if (baton->hunk_length[0])
+ old_start++;
+ if (baton->hunk_length[1])
+ new_start++;
+
+ /* Write the hunk header */
+ SVN_ERR(svn_diff__unified_write_hunk_header(
+ baton->output_stream, baton->header_encoding, "@@",
+ old_start, baton->hunk_length[0],
+ new_start, baton->hunk_length[1],
+ baton->hunk_extra_context,
+ baton->pool));
+
+ /* Output the hunk content */
+ hunk_len = baton->hunk->len;
+ SVN_ERR(svn_stream_write(baton->output_stream, baton->hunk->data,
+ &hunk_len));
+
+ /* Prepare for the next hunk */
+ baton->hunk_length[0] = 0;
+ baton->hunk_length[1] = 0;
+ baton->hunk_start[0] = 0;
+ baton->hunk_start[1] = 0;
+ svn_stringbuf_setempty(baton->hunk);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+output_unified_diff_modified(void *baton,
+ apr_off_t original_start, apr_off_t original_length,
+ apr_off_t modified_start, apr_off_t modified_length,
+ apr_off_t latest_start, apr_off_t latest_length)
+{
+ svn_diff__file_output_baton_t *output_baton = baton;
+ apr_off_t context_prefix_length;
+ apr_off_t prev_context_end;
+ svn_boolean_t init_hunk = FALSE;
+
+ if (original_start > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
+ context_prefix_length = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+ else
+ context_prefix_length = original_start;
+
+ /* Calculate where the previous hunk will end if we would write it now
+ (including the necessary context at the end) */
+ if (output_baton->hunk_length[0] > 0 || output_baton->hunk_length[1] > 0)
+ {
+ prev_context_end = output_baton->hunk_start[0]
+ + output_baton->hunk_length[0]
+ + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+ }
+ else
+ {
+ prev_context_end = -1;
+
+ if (output_baton->hunk_start[0] == 0
+ && (original_length > 0 || modified_length > 0))
+ init_hunk = TRUE;
+ }
+
+ /* If the changed range is far enough from the previous range, flush the current
+ hunk. */
+ {
+ apr_off_t new_hunk_start = (original_start - context_prefix_length);
+
+ if (output_baton->current_line[0] < new_hunk_start
+ && prev_context_end <= new_hunk_start)
+ {
+ SVN_ERR(output_unified_flush_hunk(output_baton));
+ init_hunk = TRUE;
+ }
+ else if (output_baton->hunk_length[0] > 0
+ || output_baton->hunk_length[1] > 0)
+ {
+ /* We extend the current hunk */
+
+
+ /* Original: Output the context preceding the changed range */
+ SVN_ERR(output_unified_diff_range(output_baton, 0 /* original */,
+ svn_diff__file_output_unified_context,
+ original_start));
+ }
+ }
+
+ /* Original: Skip lines until we are at the beginning of the context we want
+ to display */
+ SVN_ERR(output_unified_diff_range(output_baton, 0 /* original */,
+ svn_diff__file_output_unified_skip,
+ original_start - context_prefix_length));
+
+ /* Note that the above skip stores data for the show_c_function support below */
+
+ if (init_hunk)
+ {
+ SVN_ERR_ASSERT(output_baton->hunk_length[0] == 0
+ && output_baton->hunk_length[1] == 0);
+
+ output_baton->hunk_start[0] = original_start - context_prefix_length;
+ output_baton->hunk_start[1] = modified_start - context_prefix_length;
+ }
+
+ if (init_hunk && output_baton->show_c_function)
+ {
+ apr_size_t p;
+ const char *invalid_character;
+
+ /* Save the extra context for later use.
+ * Note that the last byte of the hunk_extra_context array is never
+ * touched after it is zero-initialized, so the array is always
+ * 0-terminated. */
+ strncpy(output_baton->hunk_extra_context,
+ output_baton->extra_context->data,
+ SVN_DIFF__EXTRA_CONTEXT_LENGTH);
+ /* Trim whitespace at the end, most notably to get rid of any
+ * newline characters. */
+ p = strlen(output_baton->hunk_extra_context);
+ while (p > 0
+ && svn_ctype_isspace(output_baton->hunk_extra_context[p - 1]))
+ {
+ output_baton->hunk_extra_context[--p] = '\0';
+ }
+ invalid_character =
+ svn_utf__last_valid(output_baton->hunk_extra_context,
+ SVN_DIFF__EXTRA_CONTEXT_LENGTH);
+ for (p = invalid_character - output_baton->hunk_extra_context;
+ p < SVN_DIFF__EXTRA_CONTEXT_LENGTH; p++)
+ {
+ output_baton->hunk_extra_context[p] = '\0';
+ }
+ }
+
+ /* Modified: Skip lines until we are at the start of the changed range */
+ SVN_ERR(output_unified_diff_range(output_baton, 1 /* modified */,
+ svn_diff__file_output_unified_skip,
+ modified_start));
+
+ /* Original: Output the context preceding the changed range */
+ SVN_ERR(output_unified_diff_range(output_baton, 0 /* original */,
+ svn_diff__file_output_unified_context,
+ original_start));
+
+ /* Both: Output the changed range */
+ SVN_ERR(output_unified_diff_range(output_baton, 0 /* original */,
+ svn_diff__file_output_unified_delete,
+ original_start + original_length));
+ SVN_ERR(output_unified_diff_range(output_baton, 1 /* modified */,
+ svn_diff__file_output_unified_insert,
+ modified_start + modified_length));
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *HEADER to a new string consisting of PATH, a tab, and PATH's mtime. */
+static svn_error_t *
+output_unified_default_hdr(const char **header, const char *path,
+ apr_pool_t *pool)
+{
+ apr_finfo_t file_info;
+ apr_time_exp_t exploded_time;
+ char time_buffer[64];
+ apr_size_t time_len;
+ const char *utf8_timestr;
+
+ SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_MTIME, pool));
+ apr_time_exp_lt(&exploded_time, file_info.mtime);
+
+ apr_strftime(time_buffer, &time_len, sizeof(time_buffer) - 1,
+ /* Order of date components can be different in different languages */
+ _("%a %b %e %H:%M:%S %Y"), &exploded_time);
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, time_buffer, pool));
+
+ *header = apr_psprintf(pool, "%s\t%s", path, utf8_timestr);
+
+ return SVN_NO_ERROR;
+}
+
+static const svn_diff_output_fns_t svn_diff__file_output_unified_vtable =
+{
+ NULL, /* output_common */
+ output_unified_diff_modified,
+ NULL, /* output_diff_latest */
+ NULL, /* output_diff_common */
+ NULL /* output_conflict */
+};
+
+svn_error_t *
+svn_diff_file_output_unified3(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_path,
+ const char *modified_path,
+ const char *original_header,
+ const char *modified_header,
+ const char *header_encoding,
+ const char *relative_to_dir,
+ svn_boolean_t show_c_function,
+ apr_pool_t *pool)
+{
+ if (svn_diff_contains_diffs(diff))
+ {
+ svn_diff__file_output_baton_t baton;
+ int i;
+
+ memset(&baton, 0, sizeof(baton));
+ baton.output_stream = output_stream;
+ baton.pool = pool;
+ baton.header_encoding = header_encoding;
+ baton.path[0] = original_path;
+ baton.path[1] = modified_path;
+ baton.hunk = svn_stringbuf_create_empty(pool);
+ baton.show_c_function = show_c_function;
+ baton.extra_context = svn_stringbuf_create_empty(pool);
+
+ if (show_c_function)
+ {
+ baton.extra_skip_match = apr_array_make(pool, 3, sizeof(char **));
+
+ APR_ARRAY_PUSH(baton.extra_skip_match, const char *) = "public:*";
+ APR_ARRAY_PUSH(baton.extra_skip_match, const char *) = "private:*";
+ APR_ARRAY_PUSH(baton.extra_skip_match, const char *) = "protected:*";
+ }
+
+ SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.context_str, " ",
+ header_encoding, pool));
+ SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.delete_str, "-",
+ header_encoding, pool));
+ SVN_ERR(svn_utf_cstring_from_utf8_ex2(&baton.insert_str, "+",
+ header_encoding, pool));
+
+ if (relative_to_dir)
+ {
+ /* Possibly adjust the "original" and "modified" paths shown in
+ the output (see issue #2723). */
+ const char *child_path;
+
+ if (! original_header)
+ {
+ child_path = svn_dirent_is_child(relative_to_dir,
+ original_path, pool);
+ if (child_path)
+ original_path = child_path;
+ else
+ return svn_error_createf(
+ SVN_ERR_BAD_RELATIVE_PATH, NULL,
+ _("Path '%s' must be inside "
+ "the directory '%s'"),
+ svn_dirent_local_style(original_path, pool),
+ svn_dirent_local_style(relative_to_dir,
+ pool));
+ }
+
+ if (! modified_header)
+ {
+ child_path = svn_dirent_is_child(relative_to_dir,
+ modified_path, pool);
+ if (child_path)
+ modified_path = child_path;
+ else
+ return svn_error_createf(
+ SVN_ERR_BAD_RELATIVE_PATH, NULL,
+ _("Path '%s' must be inside "
+ "the directory '%s'"),
+ svn_dirent_local_style(modified_path, pool),
+ svn_dirent_local_style(relative_to_dir,
+ pool));
+ }
+ }
+
+ for (i = 0; i < 2; i++)
+ {
+ SVN_ERR(svn_io_file_open(&baton.file[i], baton.path[i],
+ APR_READ, APR_OS_DEFAULT, pool));
+ }
+
+ if (original_header == NULL)
+ {
+ SVN_ERR(output_unified_default_hdr(&original_header, original_path,
+ pool));
+ }
+
+ if (modified_header == NULL)
+ {
+ SVN_ERR(output_unified_default_hdr(&modified_header, modified_path,
+ pool));
+ }
+
+ SVN_ERR(svn_diff__unidiff_write_header(output_stream, header_encoding,
+ original_header, modified_header,
+ pool));
+
+ SVN_ERR(svn_diff_output(diff, &baton,
+ &svn_diff__file_output_unified_vtable));
+ SVN_ERR(output_unified_flush_hunk(&baton));
+
+ for (i = 0; i < 2; i++)
+ {
+ SVN_ERR(svn_io_file_close(baton.file[i], pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/** Display diff3 **/
+
+/* A stream to remember *leading* context. Note that this stream does
+ *not* copy the data that it is remembering; it just saves
+ *pointers! */
+typedef struct context_saver_t {
+ svn_stream_t *stream;
+ const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
+ apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
+ apr_size_t next_slot;
+ apr_size_t total_written;
+} context_saver_t;
+
+
+static svn_error_t *
+context_saver_stream_write(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ context_saver_t *cs = baton;
+ cs->data[cs->next_slot] = data;
+ cs->len[cs->next_slot] = *len;
+ cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+ cs->total_written++;
+ return SVN_NO_ERROR;
+}
+
+typedef struct svn_diff3__file_output_baton_t
+{
+ svn_stream_t *output_stream;
+
+ const char *path[3];
+
+ apr_off_t current_line[3];
+
+ char *buffer[3];
+ char *endp[3];
+ char *curp[3];
+
+ /* The following four members are in the encoding used for the output. */
+ const char *conflict_modified;
+ const char *conflict_original;
+ const char *conflict_separator;
+ const char *conflict_latest;
+
+ const char *marker_eol;
+
+ svn_diff_conflict_display_style_t conflict_style;
+
+ /* The rest of the fields are for
+ svn_diff_conflict_display_only_conflicts only. Note that for
+ these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or
+ (soon after a conflict) a "trailing context stream", never the
+ actual output stream.*/
+ /* The actual output stream. */
+ svn_stream_t *real_output_stream;
+ context_saver_t *context_saver;
+ /* Used to allocate context_saver and trailing context streams, and
+ for some printfs. */
+ apr_pool_t *pool;
+} svn_diff3__file_output_baton_t;
+
+static svn_error_t *
+flush_context_saver(context_saver_t *cs,
+ svn_stream_t *output_stream)
+{
+ int i;
+ for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)
+ {
+ apr_size_t slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+ if (cs->data[slot])
+ {
+ apr_size_t len = cs->len[slot];
+ SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+static void
+make_context_saver(svn_diff3__file_output_baton_t *fob)
+{
+ context_saver_t *cs;
+
+ svn_pool_clear(fob->pool);
+ cs = apr_pcalloc(fob->pool, sizeof(*cs));
+ cs->stream = svn_stream_empty(fob->pool);
+ svn_stream_set_baton(cs->stream, cs);
+ svn_stream_set_write(cs->stream, context_saver_stream_write);
+ fob->context_saver = cs;
+ fob->output_stream = cs->stream;
+}
+
+
+/* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to
+ BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to
+ a context_saver; used for *trailing* context. */
+
+struct trailing_context_printer {
+ apr_size_t lines_to_print;
+ svn_diff3__file_output_baton_t *fob;
+};
+
+
+
+static svn_error_t *
+trailing_context_printer_write(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct trailing_context_printer *tcp = baton;
+ SVN_ERR_ASSERT(tcp->lines_to_print > 0);
+ SVN_ERR(svn_stream_write(tcp->fob->real_output_stream, data, len));
+ tcp->lines_to_print--;
+ if (tcp->lines_to_print == 0)
+ make_context_saver(tcp->fob);
+ return SVN_NO_ERROR;
+}
+
+
+static void
+make_trailing_context_printer(svn_diff3__file_output_baton_t *btn)
+{
+ struct trailing_context_printer *tcp;
+ svn_stream_t *s;
+
+ svn_pool_clear(btn->pool);
+
+ tcp = apr_pcalloc(btn->pool, sizeof(*tcp));
+ tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+ tcp->fob = btn;
+ s = svn_stream_empty(btn->pool);
+ svn_stream_set_baton(s, tcp);
+ svn_stream_set_write(s, trailing_context_printer_write);
+ btn->output_stream = s;
+}
+
+
+
+typedef enum svn_diff3__file_output_type_e
+{
+ svn_diff3__file_output_skip,
+ svn_diff3__file_output_normal
+} svn_diff3__file_output_type_e;
+
+
+static svn_error_t *
+output_line(svn_diff3__file_output_baton_t *baton,
+ svn_diff3__file_output_type_e type, int idx)
+{
+ char *curp;
+ char *endp;
+ char *eol;
+ apr_size_t len;
+
+ curp = baton->curp[idx];
+ endp = baton->endp[idx];
+
+ /* Lazily update the current line even if we're at EOF.
+ */
+ baton->current_line[idx]++;
+
+ if (curp == endp)
+ return SVN_NO_ERROR;
+
+ eol = svn_eol__find_eol_start(curp, endp - curp);
+ if (!eol)
+ eol = endp;
+ else
+ {
+ svn_boolean_t had_cr = (*eol == '\r');
+ eol++;
+ if (had_cr && eol != endp && *eol == '\n')
+ eol++;
+ }
+
+ if (type != svn_diff3__file_output_skip)
+ {
+ len = eol - curp;
+ /* Note that the trailing context printer assumes that
+ svn_stream_write is called exactly once per line. */
+ SVN_ERR(svn_stream_write(baton->output_stream, curp, &len));
+ }
+
+ baton->curp[idx] = eol;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+output_marker_eol(svn_diff3__file_output_baton_t *btn)
+{
+ return svn_stream_puts(btn->output_stream, btn->marker_eol);
+}
+
+static svn_error_t *
+output_hunk(void *baton, int idx, apr_off_t target_line,
+ apr_off_t target_length)
+{
+ svn_diff3__file_output_baton_t *output_baton = baton;
+
+ /* Skip lines until we are at the start of the changed range */
+ while (output_baton->current_line[idx] < target_line)
+ {
+ SVN_ERR(output_line(output_baton, svn_diff3__file_output_skip, idx));
+ }
+
+ target_line += target_length;
+
+ while (output_baton->current_line[idx] < target_line)
+ {
+ SVN_ERR(output_line(output_baton, svn_diff3__file_output_normal, idx));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+output_common(void *baton, apr_off_t original_start, apr_off_t original_length,
+ apr_off_t modified_start, apr_off_t modified_length,
+ apr_off_t latest_start, apr_off_t latest_length)
+{
+ return output_hunk(baton, 1, modified_start, modified_length);
+}
+
+static svn_error_t *
+output_diff_modified(void *baton,
+ apr_off_t original_start, apr_off_t original_length,
+ apr_off_t modified_start, apr_off_t modified_length,
+ apr_off_t latest_start, apr_off_t latest_length)
+{
+ return output_hunk(baton, 1, modified_start, modified_length);
+}
+
+static svn_error_t *
+output_diff_latest(void *baton,
+ apr_off_t original_start, apr_off_t original_length,
+ apr_off_t modified_start, apr_off_t modified_length,
+ apr_off_t latest_start, apr_off_t latest_length)
+{
+ return output_hunk(baton, 2, latest_start, latest_length);
+}
+
+static svn_error_t *
+output_conflict(void *baton,
+ apr_off_t original_start, apr_off_t original_length,
+ apr_off_t modified_start, apr_off_t modified_length,
+ apr_off_t latest_start, apr_off_t latest_length,
+ svn_diff_t *diff);
+
+static const svn_diff_output_fns_t svn_diff3__file_output_vtable =
+{
+ output_common,
+ output_diff_modified,
+ output_diff_latest,
+ output_diff_modified, /* output_diff_common */
+ output_conflict
+};
+
+
+
+static svn_error_t *
+output_conflict_with_context(svn_diff3__file_output_baton_t *btn,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length)
+{
+ /* Are we currently saving starting context (as opposed to printing
+ trailing context)? If so, flush it. */
+ if (btn->output_stream == btn->context_saver->stream)
+ {
+ if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
+ SVN_ERR(svn_stream_puts(btn->real_output_stream, "@@\n"));
+ SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));
+ }
+
+ /* Print to the real output stream. */
+ btn->output_stream = btn->real_output_stream;
+
+ /* Output the conflict itself. */
+ SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
+ (modified_length == 1
+ ? "%s (%" APR_OFF_T_FMT ")"
+ : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
+ btn->conflict_modified,
+ modified_start + 1, modified_length));
+ SVN_ERR(output_marker_eol(btn));
+ SVN_ERR(output_hunk(btn, 1/*modified*/, modified_start, modified_length));
+
+ SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
+ (original_length == 1
+ ? "%s (%" APR_OFF_T_FMT ")"
+ : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
+ btn->conflict_original,
+ original_start + 1, original_length));
+ SVN_ERR(output_marker_eol(btn));
+ SVN_ERR(output_hunk(btn, 0/*original*/, original_start, original_length));
+
+ SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
+ "%s%s", btn->conflict_separator, btn->marker_eol));
+ SVN_ERR(output_hunk(btn, 2/*latest*/, latest_start, latest_length));
+ SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
+ (latest_length == 1
+ ? "%s (%" APR_OFF_T_FMT ")"
+ : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
+ btn->conflict_latest,
+ latest_start + 1, latest_length));
+ SVN_ERR(output_marker_eol(btn));
+
+ /* Go into print-trailing-context mode instead. */
+ make_trailing_context_printer(btn);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+output_conflict(void *baton,
+ apr_off_t original_start, apr_off_t original_length,
+ apr_off_t modified_start, apr_off_t modified_length,
+ apr_off_t latest_start, apr_off_t latest_length,
+ svn_diff_t *diff)
+{
+ svn_diff3__file_output_baton_t *file_baton = baton;
+
+ svn_diff_conflict_display_style_t style = file_baton->conflict_style;
+
+ if (style == svn_diff_conflict_display_only_conflicts)
+ return output_conflict_with_context(file_baton,
+ original_start, original_length,
+ modified_start, modified_length,
+ latest_start, latest_length);
+
+ if (style == svn_diff_conflict_display_resolved_modified_latest)
+ {
+ if (diff)
+ return svn_diff_output(diff, baton,
+ &svn_diff3__file_output_vtable);
+ else
+ style = svn_diff_conflict_display_modified_latest;
+ }
+
+ if (style == svn_diff_conflict_display_modified_latest ||
+ style == svn_diff_conflict_display_modified_original_latest)
+ {
+ SVN_ERR(svn_stream_puts(file_baton->output_stream,
+ file_baton->conflict_modified));
+ SVN_ERR(output_marker_eol(file_baton));
+
+ SVN_ERR(output_hunk(baton, 1, modified_start, modified_length));
+
+ if (style == svn_diff_conflict_display_modified_original_latest)
+ {
+ SVN_ERR(svn_stream_puts(file_baton->output_stream,
+ file_baton->conflict_original));
+ SVN_ERR(output_marker_eol(file_baton));
+ SVN_ERR(output_hunk(baton, 0, original_start, original_length));
+ }
+
+ SVN_ERR(svn_stream_puts(file_baton->output_stream,
+ file_baton->conflict_separator));
+ SVN_ERR(output_marker_eol(file_baton));
+
+ SVN_ERR(output_hunk(baton, 2, latest_start, latest_length));
+
+ SVN_ERR(svn_stream_puts(file_baton->output_stream,
+ file_baton->conflict_latest));
+ SVN_ERR(output_marker_eol(file_baton));
+ }
+ else if (style == svn_diff_conflict_display_modified)
+ SVN_ERR(output_hunk(baton, 1, modified_start, modified_length));
+ else if (style == svn_diff_conflict_display_latest)
+ SVN_ERR(output_hunk(baton, 2, latest_start, latest_length));
+ else /* unknown style */
+ SVN_ERR_MALFUNCTION();
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff_file_output_merge2(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_path,
+ const char *modified_path,
+ const char *latest_path,
+ const char *conflict_original,
+ const char *conflict_modified,
+ const char *conflict_latest,
+ const char *conflict_separator,
+ svn_diff_conflict_display_style_t style,
+ apr_pool_t *pool)
+{
+ svn_diff3__file_output_baton_t baton;
+ apr_file_t *file[3];
+ int idx;
+#if APR_HAS_MMAP
+ apr_mmap_t *mm[3] = { 0 };
+#endif /* APR_HAS_MMAP */
+ const char *eol;
+ svn_boolean_t conflicts_only =
+ (style == svn_diff_conflict_display_only_conflicts);
+
+ memset(&baton, 0, sizeof(baton));
+ if (conflicts_only)
+ {
+ baton.pool = svn_pool_create(pool);
+ make_context_saver(&baton);
+ baton.real_output_stream = output_stream;
+ }
+ else
+ baton.output_stream = output_stream;
+ baton.path[0] = original_path;
+ baton.path[1] = modified_path;
+ baton.path[2] = latest_path;
+ SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_modified,
+ conflict_modified ? conflict_modified
+ : apr_psprintf(pool, "<<<<<<< %s",
+ modified_path),
+ pool));
+ SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_original,
+ conflict_original ? conflict_original
+ : apr_psprintf(pool, "||||||| %s",
+ original_path),
+ pool));
+ SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_separator,
+ conflict_separator ? conflict_separator
+ : "=======", pool));
+ SVN_ERR(svn_utf_cstring_from_utf8(&baton.conflict_latest,
+ conflict_latest ? conflict_latest
+ : apr_psprintf(pool, ">>>>>>> %s",
+ latest_path),
+ pool));
+
+ baton.conflict_style = style;
+
+ for (idx = 0; idx < 3; idx++)
+ {
+ apr_off_t size;
+
+ SVN_ERR(map_or_read_file(&file[idx],
+ MMAP_T_ARG(mm[idx])
+ &baton.buffer[idx], &size,
+ baton.path[idx], pool));
+
+ baton.curp[idx] = baton.buffer[idx];
+ baton.endp[idx] = baton.buffer[idx];
+
+ if (baton.endp[idx])
+ baton.endp[idx] += size;
+ }
+
+ /* Check what eol marker we should use for conflict markers.
+ We use the eol marker of the modified file and fall back on the
+ platform's eol marker if that file doesn't contain any newlines. */
+ eol = svn_eol__detect_eol(baton.buffer[1], baton.endp[1] - baton.buffer[1],
+ NULL);
+ if (! eol)
+ eol = APR_EOL_STR;
+ baton.marker_eol = eol;
+
+ SVN_ERR(svn_diff_output(diff, &baton,
+ &svn_diff3__file_output_vtable));
+
+ for (idx = 0; idx < 3; idx++)
+ {
+#if APR_HAS_MMAP
+ if (mm[idx])
+ {
+ apr_status_t rv = apr_mmap_delete(mm[idx]);
+ if (rv != APR_SUCCESS)
+ {
+ return svn_error_wrap_apr(rv, _("Failed to delete mmap '%s'"),
+ baton.path[idx]);
+ }
+ }
+#endif /* APR_HAS_MMAP */
+
+ if (file[idx])
+ {
+ SVN_ERR(svn_io_file_close(file[idx], pool));
+ }
+ }
+
+ if (conflicts_only)
+ svn_pool_destroy(baton.pool);
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_diff/diff_memory.c b/contrib/subversion/subversion/libsvn_diff/diff_memory.c
new file mode 100644
index 0000000..00f4c7f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/diff_memory.c
@@ -0,0 +1,1161 @@
+/*
+ * diff_memory.c : routines for doing diffs on in-memory data
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define WANT_MEMFUNC
+#define WANT_STRFUNC
+#include <apr.h>
+#include <apr_want.h>
+#include <apr_tables.h>
+
+#include <assert.h>
+
+#include "svn_diff.h"
+#include "svn_pools.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_utf.h"
+#include "diff.h"
+#include "svn_private_config.h"
+#include "private/svn_adler32.h"
+#include "private/svn_diff_private.h"
+
+typedef struct source_tokens_t
+{
+ /* A token simply is an svn_string_t pointing to
+ the data of the in-memory data source, containing
+ the raw token text, with length stored in the string */
+ apr_array_header_t *tokens;
+
+ /* Next token to be consumed */
+ apr_size_t next_token;
+
+ /* The source, containing the in-memory data to be diffed */
+ const svn_string_t *source;
+
+ /* The last token ends with a newline character (sequence) */
+ svn_boolean_t ends_without_eol;
+} source_tokens_t;
+
+typedef struct diff_mem_baton_t
+{
+ /* The tokens for each of the sources */
+ source_tokens_t sources[4];
+
+ /* Normalization buffer; we only ever compare 2 tokens at the same time */
+ char *normalization_buf[2];
+
+ /* Options for normalized comparison of the data sources */
+ const svn_diff_file_options_t *normalization_options;
+} diff_mem_baton_t;
+
+
+static int
+datasource_to_index(svn_diff_datasource_e datasource)
+{
+ switch (datasource)
+ {
+ case svn_diff_datasource_original:
+ return 0;
+
+ case svn_diff_datasource_modified:
+ return 1;
+
+ case svn_diff_datasource_latest:
+ return 2;
+
+ case svn_diff_datasource_ancestor:
+ return 3;
+ }
+
+ return -1;
+}
+
+
+/* Implements svn_diff_fns2_t::datasources_open */
+static svn_error_t *
+datasources_open(void *baton,
+ apr_off_t *prefix_lines,
+ apr_off_t *suffix_lines,
+ const svn_diff_datasource_e *datasources,
+ apr_size_t datasources_len)
+{
+ /* Do nothing: everything is already there and initialized to 0 */
+ *prefix_lines = 0;
+ *suffix_lines = 0;
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_diff_fns2_t::datasource_close */
+static svn_error_t *
+datasource_close(void *baton, svn_diff_datasource_e datasource)
+{
+ /* Do nothing. The compare_token function needs previous datasources
+ * to stay available until all datasources are processed.
+ */
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_diff_fns2_t::datasource_get_next_token */
+static svn_error_t *
+datasource_get_next_token(apr_uint32_t *hash, void **token, void *baton,
+ svn_diff_datasource_e datasource)
+{
+ diff_mem_baton_t *mem_baton = baton;
+ source_tokens_t *src = &(mem_baton->sources[datasource_to_index(datasource)]);
+
+ if ((apr_size_t)src->tokens->nelts > src->next_token)
+ {
+ /* There are actually tokens to be returned */
+ char *buf = mem_baton->normalization_buf[0];
+ svn_string_t *tok = (*token)
+ = APR_ARRAY_IDX(src->tokens, src->next_token, svn_string_t *);
+ apr_off_t len = tok->len;
+ svn_diff__normalize_state_t state
+ = svn_diff__normalize_state_normal;
+
+ svn_diff__normalize_buffer(&buf, &len, &state, tok->data,
+ mem_baton->normalization_options);
+ *hash = svn__adler32(0, buf, len);
+ src->next_token++;
+ }
+ else
+ *token = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_diff_fns2_t::token_compare */
+static svn_error_t *
+token_compare(void *baton, void *token1, void *token2, int *result)
+{
+ /* Implement the same behaviour as diff_file.c:token_compare(),
+ but be simpler, because we know we'll have all data in memory */
+ diff_mem_baton_t *btn = baton;
+ svn_string_t *t1 = token1;
+ svn_string_t *t2 = token2;
+ char *buf1 = btn->normalization_buf[0];
+ char *buf2 = btn->normalization_buf[1];
+ apr_off_t len1 = t1->len;
+ apr_off_t len2 = t2->len;
+ svn_diff__normalize_state_t state = svn_diff__normalize_state_normal;
+
+ svn_diff__normalize_buffer(&buf1, &len1, &state, t1->data,
+ btn->normalization_options);
+ state = svn_diff__normalize_state_normal;
+ svn_diff__normalize_buffer(&buf2, &len2, &state, t2->data,
+ btn->normalization_options);
+
+ if (len1 != len2)
+ *result = (len1 < len2) ? -1 : 1;
+ else
+ *result = (len1 == 0) ? 0 : memcmp(buf1, buf2, (size_t) len1);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_diff_fns2_t::token_discard */
+static void
+token_discard(void *baton, void *token)
+{
+ /* No-op, we have no use for discarded tokens... */
+}
+
+
+/* Implements svn_diff_fns2_t::token_discard_all */
+static void
+token_discard_all(void *baton)
+{
+ /* Do nothing.
+ Note that in the file case, this function discards all
+ tokens allocated, but we're geared toward small in-memory diffs.
+ Meaning that there's no special pool to clear.
+ */
+}
+
+
+static const svn_diff_fns2_t svn_diff__mem_vtable =
+{
+ datasources_open,
+ datasource_close,
+ datasource_get_next_token,
+ token_compare,
+ token_discard,
+ token_discard_all
+};
+
+/* Fill SRC with the diff tokens (e.g. lines).
+
+ TEXT is assumed to live long enough for the tokens to
+ stay valid during their lifetime: no data is copied,
+ instead, svn_string_t's are allocated pointing straight
+ into TEXT.
+*/
+static void
+fill_source_tokens(source_tokens_t *src,
+ const svn_string_t *text,
+ apr_pool_t *pool)
+{
+ const char *curp;
+ const char *endp;
+ const char *startp;
+
+ src->tokens = apr_array_make(pool, 0, sizeof(svn_string_t *));
+ src->next_token = 0;
+ src->source = text;
+
+ for (startp = curp = text->data, endp = curp + text->len;
+ curp != endp; curp++)
+ {
+ if (curp != endp && *curp == '\r' && *(curp + 1) == '\n')
+ curp++;
+
+ if (*curp == '\r' || *curp == '\n')
+ {
+ APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
+ svn_string_ncreate(startp, curp - startp + 1, pool);
+
+ startp = curp + 1;
+ }
+ }
+
+ /* If there's anything remaining (ie last line doesn't have a newline) */
+ if (startp != endp)
+ {
+ APR_ARRAY_PUSH(src->tokens, svn_string_t *) =
+ svn_string_ncreate(startp, endp - startp, pool);
+ src->ends_without_eol = TRUE;
+ }
+ else
+ src->ends_without_eol = FALSE;
+}
+
+
+static void
+alloc_normalization_bufs(diff_mem_baton_t *btn,
+ int sources,
+ apr_pool_t *pool)
+{
+ apr_size_t max_len = 0;
+ apr_off_t idx;
+ int i;
+
+ for (i = 0; i < sources; i++)
+ {
+ apr_array_header_t *tokens = btn->sources[i].tokens;
+ if (tokens->nelts > 0)
+ for (idx = 0; idx < tokens->nelts; idx++)
+ {
+ apr_size_t token_len
+ = APR_ARRAY_IDX(tokens, idx, svn_string_t *)->len;
+ max_len = (max_len < token_len) ? token_len : max_len;
+ }
+ }
+
+ btn->normalization_buf[0] = apr_palloc(pool, max_len);
+ btn->normalization_buf[1] = apr_palloc(pool, max_len);
+}
+
+svn_error_t *
+svn_diff_mem_string_diff(svn_diff_t **diff,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool)
+{
+ diff_mem_baton_t baton;
+
+ fill_source_tokens(&(baton.sources[0]), original, pool);
+ fill_source_tokens(&(baton.sources[1]), modified, pool);
+ alloc_normalization_bufs(&baton, 2, pool);
+
+ baton.normalization_options = options;
+
+ return svn_diff_diff_2(diff, &baton, &svn_diff__mem_vtable, pool);
+}
+
+svn_error_t *
+svn_diff_mem_string_diff3(svn_diff_t **diff,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ const svn_string_t *latest,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool)
+{
+ diff_mem_baton_t baton;
+
+ fill_source_tokens(&(baton.sources[0]), original, pool);
+ fill_source_tokens(&(baton.sources[1]), modified, pool);
+ fill_source_tokens(&(baton.sources[2]), latest, pool);
+ alloc_normalization_bufs(&baton, 3, pool);
+
+ baton.normalization_options = options;
+
+ return svn_diff_diff3_2(diff, &baton, &svn_diff__mem_vtable, pool);
+}
+
+
+svn_error_t *
+svn_diff_mem_string_diff4(svn_diff_t **diff,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ const svn_string_t *latest,
+ const svn_string_t *ancestor,
+ const svn_diff_file_options_t *options,
+ apr_pool_t *pool)
+{
+ diff_mem_baton_t baton;
+
+ fill_source_tokens(&(baton.sources[0]), original, pool);
+ fill_source_tokens(&(baton.sources[1]), modified, pool);
+ fill_source_tokens(&(baton.sources[2]), latest, pool);
+ fill_source_tokens(&(baton.sources[3]), ancestor, pool);
+ alloc_normalization_bufs(&baton, 4, pool);
+
+ baton.normalization_options = options;
+
+ return svn_diff_diff4_2(diff, &baton, &svn_diff__mem_vtable, pool);
+}
+
+
+typedef enum unified_output_e
+{
+ unified_output_context = 0,
+ unified_output_delete,
+ unified_output_insert,
+ unified_output_skip
+} unified_output_e;
+
+/* Baton for generating unified diffs */
+typedef struct unified_output_baton_t
+{
+ svn_stream_t *output_stream;
+ const char *header_encoding;
+ source_tokens_t sources[2]; /* 0 == original; 1 == modified */
+ apr_off_t current_token[2]; /* current token per source */
+
+ /* Cached markers, in header_encoding,
+ indexed using unified_output_e */
+ const char *prefix_str[3];
+
+ svn_stringbuf_t *hunk; /* in-progress hunk data */
+ apr_off_t hunk_length[2]; /* 0 == original; 1 == modified */
+ apr_off_t hunk_start[2]; /* 0 == original; 1 == modified */
+
+ /* The delimiters of the hunk header, '@@' for text hunks and '##' for
+ * property hunks. */
+ const char *hunk_delimiter;
+ /* The string to print after a line that does not end with a newline.
+ * It must start with a '\'. Typically "\ No newline at end of file". */
+ const char *no_newline_string;
+
+ /* Pool for allocation of temporary memory in the callbacks
+ Should be cleared on entry of each iteration of a callback */
+ apr_pool_t *pool;
+} output_baton_t;
+
+
+/* Append tokens (lines) FIRST up to PAST_LAST
+ from token-source index TOKENS with change-type TYPE
+ to the current hunk.
+*/
+static svn_error_t *
+output_unified_token_range(output_baton_t *btn,
+ int tokens,
+ unified_output_e type,
+ apr_off_t until)
+{
+ source_tokens_t *source = &btn->sources[tokens];
+
+ if (until > source->tokens->nelts)
+ until = source->tokens->nelts;
+
+ if (until <= btn->current_token[tokens])
+ return SVN_NO_ERROR;
+
+ /* Do the loop with prefix and token */
+ while (TRUE)
+ {
+ svn_string_t *token =
+ APR_ARRAY_IDX(source->tokens, btn->current_token[tokens],
+ svn_string_t *);
+
+ if (type != unified_output_skip)
+ {
+ svn_stringbuf_appendcstr(btn->hunk, btn->prefix_str[type]);
+ svn_stringbuf_appendbytes(btn->hunk, token->data, token->len);
+ }
+
+ if (type == unified_output_context)
+ {
+ btn->hunk_length[0]++;
+ btn->hunk_length[1]++;
+ }
+ else if (type == unified_output_delete)
+ btn->hunk_length[0]++;
+ else if (type == unified_output_insert)
+ btn->hunk_length[1]++;
+
+ /* ### TODO: Add skip processing for -p handling? */
+
+ btn->current_token[tokens]++;
+ if (btn->current_token[tokens] == until)
+ break;
+ }
+
+ if (btn->current_token[tokens] == source->tokens->nelts
+ && source->ends_without_eol)
+ {
+ const char *out_str;
+
+ SVN_ERR(svn_utf_cstring_from_utf8_ex2(
+ &out_str, btn->no_newline_string,
+ btn->header_encoding, btn->pool));
+ svn_stringbuf_appendcstr(btn->hunk, out_str);
+ }
+
+
+
+ return SVN_NO_ERROR;
+}
+
+/* Flush the hunk currently built up in BATON
+ into the BATON's output_stream.
+ Use the specified HUNK_DELIMITER.
+ If HUNK_DELIMITER is NULL, fall back to the default delimiter. */
+static svn_error_t *
+output_unified_flush_hunk(output_baton_t *baton,
+ const char *hunk_delimiter)
+{
+ apr_off_t target_token;
+ apr_size_t hunk_len;
+ apr_off_t old_start;
+ apr_off_t new_start;
+
+ if (svn_stringbuf_isempty(baton->hunk))
+ return SVN_NO_ERROR;
+
+ svn_pool_clear(baton->pool);
+
+ /* Write the trailing context */
+ target_token = baton->hunk_start[0] + baton->hunk_length[0]
+ + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+ SVN_ERR(output_unified_token_range(baton, 0 /*original*/,
+ unified_output_context,
+ target_token));
+ if (hunk_delimiter == NULL)
+ hunk_delimiter = "@@";
+
+ old_start = baton->hunk_start[0];
+ new_start = baton->hunk_start[1];
+
+ /* If the file is non-empty, convert the line indexes from
+ zero based to one based */
+ if (baton->hunk_length[0])
+ old_start++;
+ if (baton->hunk_length[1])
+ new_start++;
+
+ /* Write the hunk header */
+ SVN_ERR(svn_diff__unified_write_hunk_header(
+ baton->output_stream, baton->header_encoding, hunk_delimiter,
+ old_start, baton->hunk_length[0],
+ new_start, baton->hunk_length[1],
+ NULL /* hunk_extra_context */,
+ baton->pool));
+
+ hunk_len = baton->hunk->len;
+ SVN_ERR(svn_stream_write(baton->output_stream,
+ baton->hunk->data, &hunk_len));
+
+ /* Prepare for the next hunk */
+ baton->hunk_length[0] = 0;
+ baton->hunk_length[1] = 0;
+ baton->hunk_start[0] = 0;
+ baton->hunk_start[1] = 0;
+ svn_stringbuf_setempty(baton->hunk);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_diff_output_fns_t::output_diff_modified */
+static svn_error_t *
+output_unified_diff_modified(void *baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length)
+{
+ output_baton_t *output_baton = baton;
+ apr_off_t context_prefix_length;
+ apr_off_t prev_context_end;
+ svn_boolean_t init_hunk = FALSE;
+
+ if (original_start > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
+ context_prefix_length = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+ else
+ context_prefix_length = original_start;
+
+ /* Calculate where the previous hunk will end if we would write it now
+ (including the necessary context at the end) */
+ if (output_baton->hunk_length[0] > 0 || output_baton->hunk_length[1] > 0)
+ {
+ prev_context_end = output_baton->hunk_start[0]
+ + output_baton->hunk_length[0]
+ + SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+ }
+ else
+ {
+ prev_context_end = -1;
+
+ if (output_baton->hunk_start[0] == 0
+ && (original_length > 0 || modified_length > 0))
+ init_hunk = TRUE;
+ }
+
+ /* If the changed range is far enough from the previous range, flush the current
+ hunk. */
+ {
+ apr_off_t new_hunk_start = (original_start - context_prefix_length);
+
+ if (output_baton->current_token[0] < new_hunk_start
+ && prev_context_end <= new_hunk_start)
+ {
+ SVN_ERR(output_unified_flush_hunk(output_baton,
+ output_baton->hunk_delimiter));
+ init_hunk = TRUE;
+ }
+ else if (output_baton->hunk_length[0] > 0
+ || output_baton->hunk_length[1] > 0)
+ {
+ /* We extend the current hunk */
+
+ /* Original: Output the context preceding the changed range */
+ SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
+ unified_output_context,
+ original_start));
+ }
+ }
+
+ /* Original: Skip lines until we are at the beginning of the context we want
+ to display */
+ SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
+ unified_output_skip,
+ original_start - context_prefix_length));
+
+ if (init_hunk)
+ {
+ SVN_ERR_ASSERT(output_baton->hunk_length[0] == 0
+ && output_baton->hunk_length[1] == 0);
+
+ output_baton->hunk_start[0] = original_start - context_prefix_length;
+ output_baton->hunk_start[1] = modified_start - context_prefix_length;
+ }
+
+ /* Modified: Skip lines until we are at the start of the changed range */
+ SVN_ERR(output_unified_token_range(output_baton, 1 /* modified */,
+ unified_output_skip,
+ modified_start));
+
+ /* Original: Output the context preceding the changed range */
+ SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
+ unified_output_context,
+ original_start));
+
+ /* Both: Output the changed range */
+ SVN_ERR(output_unified_token_range(output_baton, 0 /* original */,
+ unified_output_delete,
+ original_start + original_length));
+ SVN_ERR(output_unified_token_range(output_baton, 1 /* modified */,
+ unified_output_insert,
+ modified_start + modified_length));
+
+ return SVN_NO_ERROR;
+}
+
+static const svn_diff_output_fns_t mem_output_unified_vtable =
+{
+ NULL, /* output_common */
+ output_unified_diff_modified,
+ NULL, /* output_diff_latest */
+ NULL, /* output_diff_common */
+ NULL /* output_conflict */
+};
+
+
+svn_error_t *
+svn_diff_mem_string_output_unified2(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ svn_boolean_t with_diff_header,
+ const char *hunk_delimiter,
+ const char *original_header,
+ const char *modified_header,
+ const char *header_encoding,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ apr_pool_t *pool)
+{
+
+ if (svn_diff_contains_diffs(diff))
+ {
+ output_baton_t baton;
+
+ memset(&baton, 0, sizeof(baton));
+ baton.output_stream = output_stream;
+ baton.pool = svn_pool_create(pool);
+ baton.header_encoding = header_encoding;
+ baton.hunk = svn_stringbuf_create_empty(pool);
+ baton.hunk_delimiter = hunk_delimiter;
+ baton.no_newline_string
+ = (hunk_delimiter == NULL || strcmp(hunk_delimiter, "##") != 0)
+ ? APR_EOL_STR SVN_DIFF__NO_NEWLINE_AT_END_OF_FILE APR_EOL_STR
+ : APR_EOL_STR SVN_DIFF__NO_NEWLINE_AT_END_OF_PROPERTY APR_EOL_STR;
+
+ SVN_ERR(svn_utf_cstring_from_utf8_ex2
+ (&(baton.prefix_str[unified_output_context]), " ",
+ header_encoding, pool));
+ SVN_ERR(svn_utf_cstring_from_utf8_ex2
+ (&(baton.prefix_str[unified_output_delete]), "-",
+ header_encoding, pool));
+ SVN_ERR(svn_utf_cstring_from_utf8_ex2
+ (&(baton.prefix_str[unified_output_insert]), "+",
+ header_encoding, pool));
+
+ fill_source_tokens(&baton.sources[0], original, pool);
+ fill_source_tokens(&baton.sources[1], modified, pool);
+
+ if (with_diff_header)
+ {
+ SVN_ERR(svn_diff__unidiff_write_header(
+ output_stream, header_encoding,
+ original_header, modified_header, pool));
+ }
+
+ SVN_ERR(svn_diff_output(diff, &baton,
+ &mem_output_unified_vtable));
+
+ SVN_ERR(output_unified_flush_hunk(&baton, hunk_delimiter));
+
+ svn_pool_destroy(baton.pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff_mem_string_output_unified(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const char *original_header,
+ const char *modified_header,
+ const char *header_encoding,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_diff_mem_string_output_unified2(output_stream,
+ diff,
+ TRUE,
+ NULL,
+ original_header,
+ modified_header,
+ header_encoding,
+ original,
+ modified,
+ pool));
+ return SVN_NO_ERROR;
+}
+
+
+
+/* diff3 merge output */
+
+/* A stream to remember *leading* context. Note that this stream does
+ *not* copy the data that it is remembering; it just saves
+ *pointers! */
+typedef struct context_saver_t {
+ svn_stream_t *stream;
+ const char *data[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
+ apr_size_t len[SVN_DIFF__UNIFIED_CONTEXT_SIZE];
+ apr_size_t next_slot;
+ apr_size_t total_written;
+} context_saver_t;
+
+
+static svn_error_t *
+context_saver_stream_write(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ context_saver_t *cs = baton;
+ cs->data[cs->next_slot] = data;
+ cs->len[cs->next_slot] = *len;
+ cs->next_slot = (cs->next_slot + 1) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+ cs->total_written++;
+ return SVN_NO_ERROR;
+}
+
+
+typedef struct merge_output_baton_t
+{
+ svn_stream_t *output_stream;
+
+ /* Tokenized source text */
+ source_tokens_t sources[3];
+ apr_off_t next_token[3];
+
+ /* Markers for marking conflicted sections */
+ const char *markers[4]; /* 0 = original, 1 = modified,
+ 2 = separator, 3 = latest (end) */
+ const char *marker_eol;
+
+ svn_diff_conflict_display_style_t conflict_style;
+
+ /* The rest of the fields are for
+ svn_diff_conflict_display_only_conflicts only. Note that for
+ these batons, OUTPUT_STREAM is either CONTEXT_SAVER->STREAM or
+ (soon after a conflict) a "trailing context stream", never the
+ actual output stream.*/
+ /* The actual output stream. */
+ svn_stream_t *real_output_stream;
+ context_saver_t *context_saver;
+ /* Used to allocate context_saver and trailing context streams, and
+ for some printfs. */
+ apr_pool_t *pool;
+} merge_output_baton_t;
+
+
+static svn_error_t *
+flush_context_saver(context_saver_t *cs,
+ svn_stream_t *output_stream)
+{
+ int i;
+ for (i = 0; i < SVN_DIFF__UNIFIED_CONTEXT_SIZE; i++)
+ {
+ apr_size_t slot = (i + cs->next_slot) % SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+ if (cs->data[slot])
+ {
+ apr_size_t len = cs->len[slot];
+ SVN_ERR(svn_stream_write(output_stream, cs->data[slot], &len));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+
+static void
+make_context_saver(merge_output_baton_t *mob)
+{
+ context_saver_t *cs;
+
+ svn_pool_clear(mob->pool);
+ cs = apr_pcalloc(mob->pool, sizeof(*cs));
+ cs->stream = svn_stream_empty(mob->pool);
+ svn_stream_set_baton(cs->stream, cs);
+ svn_stream_set_write(cs->stream, context_saver_stream_write);
+ mob->context_saver = cs;
+ mob->output_stream = cs->stream;
+}
+
+
+/* A stream which prints SVN_DIFF__UNIFIED_CONTEXT_SIZE lines to
+ BATON->REAL_OUTPUT_STREAM, and then changes BATON->OUTPUT_STREAM to
+ a context_saver; used for *trailing* context. */
+
+struct trailing_context_printer {
+ apr_size_t lines_to_print;
+ merge_output_baton_t *mob;
+};
+
+
+static svn_error_t *
+trailing_context_printer_write(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct trailing_context_printer *tcp = baton;
+ SVN_ERR_ASSERT(tcp->lines_to_print > 0);
+ SVN_ERR(svn_stream_write(tcp->mob->real_output_stream, data, len));
+ tcp->lines_to_print--;
+ if (tcp->lines_to_print == 0)
+ make_context_saver(tcp->mob);
+ return SVN_NO_ERROR;
+}
+
+
+static void
+make_trailing_context_printer(merge_output_baton_t *btn)
+{
+ struct trailing_context_printer *tcp;
+ svn_stream_t *s;
+
+ svn_pool_clear(btn->pool);
+
+ tcp = apr_pcalloc(btn->pool, sizeof(*tcp));
+ tcp->lines_to_print = SVN_DIFF__UNIFIED_CONTEXT_SIZE;
+ tcp->mob = btn;
+ s = svn_stream_empty(btn->pool);
+ svn_stream_set_baton(s, tcp);
+ svn_stream_set_write(s, trailing_context_printer_write);
+ btn->output_stream = s;
+}
+
+
+static svn_error_t *
+output_merge_token_range(apr_size_t *lines_printed_p,
+ merge_output_baton_t *btn,
+ int idx, apr_off_t first,
+ apr_off_t length)
+{
+ apr_array_header_t *tokens = btn->sources[idx].tokens;
+ apr_size_t lines_printed = 0;
+
+ for (; length > 0 && first < tokens->nelts; length--, first++)
+ {
+ svn_string_t *token = APR_ARRAY_IDX(tokens, first, svn_string_t *);
+ apr_size_t len = token->len;
+
+ /* Note that the trailing context printer assumes that
+ svn_stream_write is called exactly once per line. */
+ SVN_ERR(svn_stream_write(btn->output_stream, token->data, &len));
+ lines_printed++;
+ }
+
+ if (lines_printed_p)
+ *lines_printed_p = lines_printed;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+output_marker_eol(merge_output_baton_t *btn)
+{
+ return svn_stream_puts(btn->output_stream, btn->marker_eol);
+}
+
+static svn_error_t *
+output_merge_marker(merge_output_baton_t *btn, int idx)
+{
+ SVN_ERR(svn_stream_puts(btn->output_stream, btn->markers[idx]));
+ return output_marker_eol(btn);
+}
+
+static svn_error_t *
+output_common_modified(void *baton,
+ apr_off_t original_start, apr_off_t original_length,
+ apr_off_t modified_start, apr_off_t modified_length,
+ apr_off_t latest_start, apr_off_t latest_length)
+{
+ return output_merge_token_range(NULL, baton, 1/*modified*/,
+ modified_start, modified_length);
+}
+
+static svn_error_t *
+output_latest(void *baton,
+ apr_off_t original_start, apr_off_t original_length,
+ apr_off_t modified_start, apr_off_t modified_length,
+ apr_off_t latest_start, apr_off_t latest_length)
+{
+ return output_merge_token_range(NULL, baton, 2/*latest*/,
+ latest_start, latest_length);
+}
+
+static svn_error_t *
+output_conflict(void *baton,
+ apr_off_t original_start, apr_off_t original_length,
+ apr_off_t modified_start, apr_off_t modified_length,
+ apr_off_t latest_start, apr_off_t latest_length,
+ svn_diff_t *diff);
+
+static const svn_diff_output_fns_t merge_output_vtable =
+{
+ output_common_modified, /* common */
+ output_common_modified, /* modified */
+ output_latest,
+ output_common_modified, /* output_diff_common */
+ output_conflict
+};
+
+static svn_error_t *
+output_conflict(void *baton,
+ apr_off_t original_start, apr_off_t original_length,
+ apr_off_t modified_start, apr_off_t modified_length,
+ apr_off_t latest_start, apr_off_t latest_length,
+ svn_diff_t *diff)
+{
+ merge_output_baton_t *btn = baton;
+
+ svn_diff_conflict_display_style_t style = btn->conflict_style;
+
+ if (style == svn_diff_conflict_display_resolved_modified_latest)
+ {
+ if (diff)
+ return svn_diff_output(diff, baton, &merge_output_vtable);
+ else
+ style = svn_diff_conflict_display_modified_latest;
+ }
+
+ if (style == svn_diff_conflict_display_modified_latest ||
+ style == svn_diff_conflict_display_modified_original_latest)
+ {
+ SVN_ERR(output_merge_marker(btn, 1/*modified*/));
+ SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
+ modified_start, modified_length));
+
+ if (style == svn_diff_conflict_display_modified_original_latest)
+ {
+ SVN_ERR(output_merge_marker(btn, 0/*original*/));
+ SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,
+ original_start, original_length));
+ }
+
+ SVN_ERR(output_merge_marker(btn, 2/*separator*/));
+ SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
+ latest_start, latest_length));
+ SVN_ERR(output_merge_marker(btn, 3/*latest (end)*/));
+ }
+ else if (style == svn_diff_conflict_display_modified)
+ SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
+ modified_start, modified_length));
+ else if (style == svn_diff_conflict_display_latest)
+ SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
+ latest_start, latest_length));
+ else /* unknown style */
+ SVN_ERR_MALFUNCTION();
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+output_conflict_with_context(void *baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length,
+ svn_diff_t *diff)
+{
+ merge_output_baton_t *btn = baton;
+
+ /* Are we currently saving starting context (as opposed to printing
+ trailing context)? If so, flush it. */
+ if (btn->output_stream == btn->context_saver->stream)
+ {
+ if (btn->context_saver->total_written > SVN_DIFF__UNIFIED_CONTEXT_SIZE)
+ SVN_ERR(svn_stream_puts(btn->real_output_stream, "@@\n"));
+ SVN_ERR(flush_context_saver(btn->context_saver, btn->real_output_stream));
+ }
+
+ /* Print to the real output stream. */
+ btn->output_stream = btn->real_output_stream;
+
+ /* Output the conflict itself. */
+ SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
+ (modified_length == 1
+ ? "%s (%" APR_OFF_T_FMT ")"
+ : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
+ btn->markers[1],
+ modified_start + 1, modified_length));
+ SVN_ERR(output_marker_eol(btn));
+ SVN_ERR(output_merge_token_range(NULL, btn, 1/*modified*/,
+ modified_start, modified_length));
+
+ SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
+ (original_length == 1
+ ? "%s (%" APR_OFF_T_FMT ")"
+ : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
+ btn->markers[0],
+ original_start + 1, original_length));
+ SVN_ERR(output_marker_eol(btn));
+ SVN_ERR(output_merge_token_range(NULL, btn, 0/*original*/,
+ original_start, original_length));
+
+ SVN_ERR(output_merge_marker(btn, 2/*separator*/));
+ SVN_ERR(output_merge_token_range(NULL, btn, 2/*latest*/,
+ latest_start, latest_length));
+ SVN_ERR(svn_stream_printf(btn->output_stream, btn->pool,
+ (latest_length == 1
+ ? "%s (%" APR_OFF_T_FMT ")"
+ : "%s (%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT ")"),
+ btn->markers[3],
+ latest_start + 1, latest_length));
+ SVN_ERR(output_marker_eol(btn));
+
+ /* Go into print-trailing-context mode instead. */
+ make_trailing_context_printer(btn);
+
+ return SVN_NO_ERROR;
+}
+
+
+static const svn_diff_output_fns_t merge_only_conflicts_output_vtable =
+{
+ output_common_modified,
+ output_common_modified,
+ output_latest,
+ output_common_modified,
+ output_conflict_with_context
+};
+
+
+/* TOKEN is the first token in the modified file.
+ Return its line-ending, if any. */
+static const char *
+detect_eol(svn_string_t *token)
+{
+ const char *curp;
+
+ if (token->len == 0)
+ return NULL;
+
+ curp = token->data + token->len - 1;
+ if (*curp == '\r')
+ return "\r";
+ else if (*curp != '\n')
+ return NULL;
+ else
+ {
+ if (token->len == 1
+ || *(--curp) != '\r')
+ return "\n";
+ else
+ return "\r\n";
+ }
+}
+
+svn_error_t *
+svn_diff_mem_string_output_merge2(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ const svn_string_t *latest,
+ const char *conflict_original,
+ const char *conflict_modified,
+ const char *conflict_latest,
+ const char *conflict_separator,
+ svn_diff_conflict_display_style_t style,
+ apr_pool_t *pool)
+{
+ merge_output_baton_t btn;
+ const char *eol;
+ svn_boolean_t conflicts_only =
+ (style == svn_diff_conflict_display_only_conflicts);
+ const svn_diff_output_fns_t *vtable = conflicts_only
+ ? &merge_only_conflicts_output_vtable : &merge_output_vtable;
+
+ memset(&btn, 0, sizeof(btn));
+
+ if (conflicts_only)
+ {
+ btn.pool = svn_pool_create(pool);
+ make_context_saver(&btn);
+ btn.real_output_stream = output_stream;
+ }
+ else
+ btn.output_stream = output_stream;
+
+ fill_source_tokens(&(btn.sources[0]), original, pool);
+ fill_source_tokens(&(btn.sources[1]), modified, pool);
+ fill_source_tokens(&(btn.sources[2]), latest, pool);
+
+ btn.conflict_style = style;
+
+ if (btn.sources[1].tokens->nelts > 0)
+ {
+ eol = detect_eol(APR_ARRAY_IDX(btn.sources[1].tokens, 0, svn_string_t *));
+ if (!eol)
+ eol = APR_EOL_STR; /* use the platform default */
+ }
+ else
+ eol = APR_EOL_STR; /* use the platform default */
+
+ btn.marker_eol = eol;
+
+ SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[1],
+ conflict_modified
+ ? conflict_modified
+ : "<<<<<<< (modified)",
+ pool));
+ SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[0],
+ conflict_original
+ ? conflict_original
+ : "||||||| (original)",
+ pool));
+ SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[2],
+ conflict_separator
+ ? conflict_separator
+ : "=======",
+ pool));
+ SVN_ERR(svn_utf_cstring_from_utf8(&btn.markers[3],
+ conflict_latest
+ ? conflict_latest
+ : ">>>>>>> (latest)",
+ pool));
+
+ SVN_ERR(svn_diff_output(diff, &btn, vtable));
+ if (conflicts_only)
+ svn_pool_destroy(btn.pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff_mem_string_output_merge(svn_stream_t *output_stream,
+ svn_diff_t *diff,
+ const svn_string_t *original,
+ const svn_string_t *modified,
+ const svn_string_t *latest,
+ const char *conflict_original,
+ const char *conflict_modified,
+ const char *conflict_latest,
+ const char *conflict_separator,
+ svn_boolean_t display_original_in_conflict,
+ svn_boolean_t display_resolved_conflicts,
+ apr_pool_t *pool)
+{
+ svn_diff_conflict_display_style_t style =
+ svn_diff_conflict_display_modified_latest;
+
+ if (display_resolved_conflicts)
+ style = svn_diff_conflict_display_resolved_modified_latest;
+
+ if (display_original_in_conflict)
+ style = svn_diff_conflict_display_modified_original_latest;
+
+ return svn_diff_mem_string_output_merge2(output_stream,
+ diff,
+ original,
+ modified,
+ latest,
+ conflict_original,
+ conflict_modified,
+ conflict_latest,
+ conflict_separator,
+ style,
+ pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_diff/diff_tree.c b/contrib/subversion/subversion/libsvn_diff/diff_tree.c
new file mode 100644
index 0000000..8490179
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/diff_tree.c
@@ -0,0 +1,1705 @@
+/*
+ * diff_tree.c : default diff tree processor
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_general.h>
+
+#include <assert.h>
+
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_types.h"
+
+#include "private/svn_diff_tree.h"
+#include "svn_private_config.h"
+
+typedef struct tree_processor_t
+{
+ svn_diff_tree_processor_t tp;
+
+ /* void *future_extension */
+} tree_processor_t;
+
+
+static svn_error_t *
+default_dir_opened(void **new_dir_baton,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *parent_dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *new_dir_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+default_dir_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(processor->dir_closed(relpath, NULL, right_source,
+ dir_baton, processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+default_dir_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ /*const*/ apr_hash_t *left_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(processor->dir_closed(relpath, left_source, NULL,
+ dir_baton, processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+default_dir_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ const apr_array_header_t *prop_changes,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(processor->dir_closed(relpath,
+ left_source, right_source,
+ dir_baton,
+ processor, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+default_dir_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+default_file_opened(void **new_file_baton,
+ svn_boolean_t *skip,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *new_file_baton = dir_baton;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+default_file_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ const char *copyfrom_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(processor->file_closed(relpath,
+ NULL, right_source,
+ file_baton, processor, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+default_file_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const char *left_file,
+ /*const*/ apr_hash_t *left_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(processor->file_closed(relpath,
+ left_source, NULL,
+ file_baton, processor, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+default_file_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const char *left_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_boolean_t file_modified,
+ const apr_array_header_t *prop_changes,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(processor->file_closed(relpath,
+ left_source, right_source,
+ file_baton, processor, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+default_file_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+default_node_absent(const char *relpath,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+svn_diff_tree_processor_t *
+svn_diff__tree_processor_create(void *baton,
+ apr_pool_t *result_pool)
+{
+ tree_processor_t *wrapper;
+ wrapper = apr_pcalloc(result_pool, sizeof(*wrapper));
+
+ wrapper->tp.baton = baton;
+
+ wrapper->tp.dir_opened = default_dir_opened;
+ wrapper->tp.dir_added = default_dir_added;
+ wrapper->tp.dir_deleted = default_dir_deleted;
+ wrapper->tp.dir_changed = default_dir_changed;
+ wrapper->tp.dir_closed = default_dir_closed;
+
+ wrapper->tp.file_opened = default_file_opened;
+ wrapper->tp.file_added = default_file_added;
+ wrapper->tp.file_deleted = default_file_deleted;
+ wrapper->tp.file_changed = default_file_changed;
+ wrapper->tp.file_closed = default_file_closed;
+
+ wrapper->tp.node_absent = default_node_absent;
+
+
+ return &wrapper->tp;
+}
+
+struct reverse_tree_baton_t
+{
+ const svn_diff_tree_processor_t *processor;
+ const char *prefix_relpath;
+};
+
+static svn_error_t *
+reverse_dir_opened(void **new_dir_baton,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *parent_dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct reverse_tree_baton_t *rb = processor->baton;
+
+ if (rb->prefix_relpath)
+ relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
+
+ SVN_ERR(rb->processor->dir_opened(new_dir_baton, skip, skip_children,
+ relpath,
+ right_source, left_source,
+ NULL /* copyfrom */,
+ parent_dir_baton,
+ rb->processor,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reverse_dir_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct reverse_tree_baton_t *rb = processor->baton;
+
+ if (rb->prefix_relpath)
+ relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
+
+ SVN_ERR(rb->processor->dir_deleted(relpath,
+ right_source,
+ right_props,
+ dir_baton,
+ rb->processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reverse_dir_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ /*const*/ apr_hash_t *left_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct reverse_tree_baton_t *rb = processor->baton;
+
+ if (rb->prefix_relpath)
+ relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
+
+ SVN_ERR(rb->processor->dir_added(relpath,
+ NULL,
+ left_source,
+ NULL,
+ left_props,
+ dir_baton,
+ rb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reverse_dir_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ const apr_array_header_t *prop_changes,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct reverse_tree_baton_t *rb = processor->baton;
+ apr_array_header_t *reversed_prop_changes = NULL;
+
+ if (rb->prefix_relpath)
+ relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
+
+ if (prop_changes)
+ {
+ SVN_ERR_ASSERT(left_props != NULL && right_props != NULL);
+ SVN_ERR(svn_prop_diffs(&reversed_prop_changes, left_props, right_props,
+ scratch_pool));
+ }
+
+ SVN_ERR(rb->processor->dir_changed(relpath,
+ right_source,
+ left_source,
+ right_props,
+ left_props,
+ reversed_prop_changes,
+ dir_baton,
+ rb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reverse_dir_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct reverse_tree_baton_t *rb = processor->baton;
+
+ if (rb->prefix_relpath)
+ relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
+
+ SVN_ERR(rb->processor->dir_closed(relpath,
+ right_source,
+ left_source,
+ dir_baton,
+ rb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reverse_file_opened(void **new_file_baton,
+ svn_boolean_t *skip,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct reverse_tree_baton_t *rb = processor->baton;
+
+ if (rb->prefix_relpath)
+ relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
+
+ SVN_ERR(rb->processor->file_opened(new_file_baton,
+ skip,
+ relpath,
+ right_source,
+ left_source,
+ NULL /* copy_from */,
+ dir_baton,
+ rb->processor,
+ result_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reverse_file_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ const char *copyfrom_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct reverse_tree_baton_t *rb = processor->baton;
+
+ if (rb->prefix_relpath)
+ relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
+
+ SVN_ERR(rb->processor->file_deleted(relpath,
+ right_source,
+ right_file,
+ right_props,
+ file_baton,
+ rb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reverse_file_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const char *left_file,
+ /*const*/ apr_hash_t *left_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct reverse_tree_baton_t *rb = processor->baton;
+
+ if (rb->prefix_relpath)
+ relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
+
+ SVN_ERR(rb->processor->file_added(relpath,
+ NULL /* copyfrom src */,
+ left_source,
+ NULL /* copyfrom file */,
+ left_file,
+ NULL /* copyfrom props */,
+ left_props,
+ file_baton,
+ rb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reverse_file_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const char *left_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_boolean_t file_modified,
+ const apr_array_header_t *prop_changes,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct reverse_tree_baton_t *rb = processor->baton;
+ apr_array_header_t *reversed_prop_changes = NULL;
+
+ if (rb->prefix_relpath)
+ relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
+
+ if (prop_changes)
+ {
+ SVN_ERR_ASSERT(left_props != NULL && right_props != NULL);
+ SVN_ERR(svn_prop_diffs(&reversed_prop_changes, left_props, right_props,
+ scratch_pool));
+ }
+
+ SVN_ERR(rb->processor->file_changed(relpath,
+ right_source,
+ left_source,
+ right_file,
+ left_file,
+ right_props,
+ left_props,
+ file_modified,
+ reversed_prop_changes,
+ file_baton,
+ rb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reverse_file_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct reverse_tree_baton_t *rb = processor->baton;
+
+ if (rb->prefix_relpath)
+ relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
+
+ SVN_ERR(rb->processor->file_closed(relpath,
+ right_source,
+ left_source,
+ file_baton,
+ rb->processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reverse_node_absent(const char *relpath,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct reverse_tree_baton_t *rb = processor->baton;
+
+ if (rb->prefix_relpath)
+ relpath = svn_relpath_join(rb->prefix_relpath, relpath, scratch_pool);
+
+ SVN_ERR(rb->processor->node_absent(relpath,
+ dir_baton,
+ rb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+const svn_diff_tree_processor_t *
+svn_diff__tree_processor_reverse_create(const svn_diff_tree_processor_t * processor,
+ const char *prefix_relpath,
+ apr_pool_t *result_pool)
+{
+ struct reverse_tree_baton_t *rb;
+ svn_diff_tree_processor_t *reverse;
+
+ rb = apr_pcalloc(result_pool, sizeof(*rb));
+ rb->processor = processor;
+ if (prefix_relpath)
+ rb->prefix_relpath = apr_pstrdup(result_pool, prefix_relpath);
+
+ reverse = svn_diff__tree_processor_create(rb, result_pool);
+
+ reverse->dir_opened = reverse_dir_opened;
+ reverse->dir_added = reverse_dir_added;
+ reverse->dir_deleted = reverse_dir_deleted;
+ reverse->dir_changed = reverse_dir_changed;
+ reverse->dir_closed = reverse_dir_closed;
+
+ reverse->file_opened = reverse_file_opened;
+ reverse->file_added = reverse_file_added;
+ reverse->file_deleted = reverse_file_deleted;
+ reverse->file_changed = reverse_file_changed;
+ reverse->file_closed = reverse_file_closed;
+
+ reverse->node_absent = reverse_node_absent;
+
+ return reverse;
+}
+
+struct filter_tree_baton_t
+{
+ const svn_diff_tree_processor_t *processor;
+ const char *prefix_relpath;
+};
+
+static svn_error_t *
+filter_dir_opened(void **new_dir_baton,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *parent_dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct filter_tree_baton_t *fb = processor->baton;
+
+ relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
+
+ if (! relpath)
+ {
+ /* Skip work for this, but NOT for DESCENDANTS */
+ *skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(fb->processor->dir_opened(new_dir_baton, skip, skip_children,
+ relpath,
+ left_source, right_source,
+ copyfrom_source,
+ parent_dir_baton,
+ fb->processor,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+filter_dir_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct filter_tree_baton_t *fb = processor->baton;
+
+ relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
+ assert(relpath != NULL); /* Driver error */
+
+ SVN_ERR(fb->processor->dir_added(relpath,
+ copyfrom_source,
+ right_source,
+ copyfrom_props,
+ right_props,
+ dir_baton,
+ fb->processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+filter_dir_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ /*const*/ apr_hash_t *left_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct filter_tree_baton_t *fb = processor->baton;
+
+ relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
+ assert(relpath != NULL); /* Driver error */
+
+ SVN_ERR(fb->processor->dir_deleted(relpath,
+ left_source,
+ left_props,
+ dir_baton,
+ fb->processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+filter_dir_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ const apr_array_header_t *prop_changes,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct filter_tree_baton_t *fb = processor->baton;
+
+ relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
+ assert(relpath != NULL); /* Driver error */
+
+ SVN_ERR(fb->processor->dir_changed(relpath,
+ left_source,
+ right_source,
+ left_props,
+ right_props,
+ prop_changes,
+ dir_baton,
+ fb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+filter_dir_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct filter_tree_baton_t *fb = processor->baton;
+
+ relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
+ assert(relpath != NULL); /* Driver error */
+
+ SVN_ERR(fb->processor->dir_closed(relpath,
+ left_source,
+ right_source,
+ dir_baton,
+ fb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+filter_file_opened(void **new_file_baton,
+ svn_boolean_t *skip,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct filter_tree_baton_t *fb = processor->baton;
+
+ relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
+
+ if (! relpath)
+ {
+ *skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(fb->processor->file_opened(new_file_baton,
+ skip,
+ relpath,
+ left_source,
+ right_source,
+ copyfrom_source,
+ dir_baton,
+ fb->processor,
+ result_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+filter_file_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ const char *copyfrom_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct filter_tree_baton_t *fb = processor->baton;
+
+ relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
+ assert(relpath != NULL); /* Driver error */
+
+ SVN_ERR(fb->processor->file_added(relpath,
+ copyfrom_source,
+ right_source,
+ copyfrom_file,
+ right_file,
+ copyfrom_props,
+ right_props,
+ file_baton,
+ fb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+filter_file_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const char *left_file,
+ /*const*/ apr_hash_t *left_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct filter_tree_baton_t *fb = processor->baton;
+
+ relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
+ assert(relpath != NULL); /* Driver error */
+
+ SVN_ERR(fb->processor->file_deleted(relpath,
+ left_source,
+ left_file,
+ left_props,
+ file_baton,
+ fb->processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+filter_file_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const char *left_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_boolean_t file_modified,
+ const apr_array_header_t *prop_changes,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct filter_tree_baton_t *fb = processor->baton;
+
+ relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
+ assert(relpath != NULL); /* Driver error */
+
+ SVN_ERR(fb->processor->file_changed(relpath,
+ left_source,
+ right_source,
+ left_file,
+ right_file,
+ left_props,
+ right_props,
+ file_modified,
+ prop_changes,
+ file_baton,
+ fb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+filter_file_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct filter_tree_baton_t *fb = processor->baton;
+
+ relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
+ assert(relpath != NULL); /* Driver error */
+
+ SVN_ERR(fb->processor->file_closed(relpath,
+ left_source,
+ right_source,
+ file_baton,
+ fb->processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+filter_node_absent(const char *relpath,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct filter_tree_baton_t *fb = processor->baton;
+
+ relpath = svn_relpath_skip_ancestor(fb->prefix_relpath, relpath);
+ assert(relpath != NULL); /* Driver error */
+
+ SVN_ERR(fb->processor->node_absent(relpath,
+ dir_baton,
+ fb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+const svn_diff_tree_processor_t *
+svn_diff__tree_processor_filter_create(const svn_diff_tree_processor_t * processor,
+ const char *prefix_relpath,
+ apr_pool_t *result_pool)
+{
+ struct filter_tree_baton_t *fb;
+ svn_diff_tree_processor_t *filter;
+
+ fb = apr_pcalloc(result_pool, sizeof(*fb));
+ fb->processor = processor;
+ if (prefix_relpath)
+ fb->prefix_relpath = apr_pstrdup(result_pool, prefix_relpath);
+
+ filter = svn_diff__tree_processor_create(fb, result_pool);
+
+ filter->dir_opened = filter_dir_opened;
+ filter->dir_added = filter_dir_added;
+ filter->dir_deleted = filter_dir_deleted;
+ filter->dir_changed = filter_dir_changed;
+ filter->dir_closed = filter_dir_closed;
+
+ filter->file_opened = filter_file_opened;
+ filter->file_added = filter_file_added;
+ filter->file_deleted = filter_file_deleted;
+ filter->file_changed = filter_file_changed;
+ filter->file_closed = filter_file_closed;
+
+ filter->node_absent = filter_node_absent;
+
+ return filter;
+}
+
+struct copy_as_changed_baton_t
+{
+ const svn_diff_tree_processor_t *processor;
+};
+
+static svn_error_t *
+copy_as_changed_dir_opened(void **new_dir_baton,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *parent_dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_as_changed_baton_t *cb = processor->baton;
+
+ if (!left_source && copyfrom_source)
+ {
+ assert(right_source != NULL);
+
+ left_source = copyfrom_source;
+ copyfrom_source = NULL;
+ }
+
+ SVN_ERR(cb->processor->dir_opened(new_dir_baton, skip, skip_children,
+ relpath,
+ left_source, right_source,
+ copyfrom_source,
+ parent_dir_baton,
+ cb->processor,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+copy_as_changed_dir_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_as_changed_baton_t *cb = processor->baton;
+
+ if (copyfrom_source)
+ {
+ apr_array_header_t *propchanges;
+ SVN_ERR(svn_prop_diffs(&propchanges, right_props, copyfrom_props,
+ scratch_pool));
+ SVN_ERR(cb->processor->dir_changed(relpath,
+ copyfrom_source,
+ right_source,
+ copyfrom_props,
+ right_props,
+ propchanges,
+ dir_baton,
+ cb->processor,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(cb->processor->dir_added(relpath,
+ copyfrom_source,
+ right_source,
+ copyfrom_props,
+ right_props,
+ dir_baton,
+ cb->processor,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+copy_as_changed_dir_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ /*const*/ apr_hash_t *left_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_as_changed_baton_t *cb = processor->baton;
+
+ SVN_ERR(cb->processor->dir_deleted(relpath,
+ left_source,
+ left_props,
+ dir_baton,
+ cb->processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+copy_as_changed_dir_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ const apr_array_header_t *prop_changes,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_as_changed_baton_t *cb = processor->baton;
+
+ SVN_ERR(cb->processor->dir_changed(relpath,
+ left_source,
+ right_source,
+ left_props,
+ right_props,
+ prop_changes,
+ dir_baton,
+ cb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+copy_as_changed_dir_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_as_changed_baton_t *cb = processor->baton;
+
+ SVN_ERR(cb->processor->dir_closed(relpath,
+ left_source,
+ right_source,
+ dir_baton,
+ cb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+copy_as_changed_file_opened(void **new_file_baton,
+ svn_boolean_t *skip,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_as_changed_baton_t *cb = processor->baton;
+
+ if (!left_source && copyfrom_source)
+ {
+ assert(right_source != NULL);
+
+ left_source = copyfrom_source;
+ copyfrom_source = NULL;
+ }
+
+ SVN_ERR(cb->processor->file_opened(new_file_baton,
+ skip,
+ relpath,
+ left_source,
+ right_source,
+ copyfrom_source,
+ dir_baton,
+ cb->processor,
+ result_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+copy_as_changed_file_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ const char *copyfrom_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_as_changed_baton_t *cb = processor->baton;
+
+ if (copyfrom_source)
+ {
+ apr_array_header_t *propchanges;
+ svn_boolean_t same;
+ SVN_ERR(svn_prop_diffs(&propchanges, right_props, copyfrom_props,
+ scratch_pool));
+
+ /* "" is sometimes a marker for just modified (E.g. no-textdeltas),
+ and it is certainly not a file */
+ if (*copyfrom_file && *right_file)
+ {
+ SVN_ERR(svn_io_files_contents_same_p(&same, copyfrom_file,
+ right_file, scratch_pool));
+ }
+ else
+ same = FALSE;
+
+ SVN_ERR(cb->processor->file_changed(relpath,
+ copyfrom_source,
+ right_source,
+ copyfrom_file,
+ right_file,
+ copyfrom_props,
+ right_props,
+ !same,
+ propchanges,
+ file_baton,
+ cb->processor,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(cb->processor->file_added(relpath,
+ copyfrom_source,
+ right_source,
+ copyfrom_file,
+ right_file,
+ copyfrom_props,
+ right_props,
+ file_baton,
+ cb->processor,
+ scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+copy_as_changed_file_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const char *left_file,
+ /*const*/ apr_hash_t *left_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_as_changed_baton_t *cb = processor->baton;
+
+ SVN_ERR(cb->processor->file_deleted(relpath,
+ left_source,
+ left_file,
+ left_props,
+ file_baton,
+ cb->processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+copy_as_changed_file_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const char *left_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_boolean_t file_modified,
+ const apr_array_header_t *prop_changes,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_as_changed_baton_t *cb = processor->baton;
+
+ SVN_ERR(cb->processor->file_changed(relpath,
+ left_source,
+ right_source,
+ left_file,
+ right_file,
+ left_props,
+ right_props,
+ file_modified,
+ prop_changes,
+ file_baton,
+ cb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+copy_as_changed_file_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_as_changed_baton_t *cb = processor->baton;
+
+ SVN_ERR(cb->processor->file_closed(relpath,
+ left_source,
+ right_source,
+ file_baton,
+ cb->processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+copy_as_changed_node_absent(const char *relpath,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct copy_as_changed_baton_t *cb = processor->baton;
+
+ SVN_ERR(cb->processor->node_absent(relpath,
+ dir_baton,
+ cb->processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+const svn_diff_tree_processor_t *
+svn_diff__tree_processor_copy_as_changed_create(
+ const svn_diff_tree_processor_t * processor,
+ apr_pool_t *result_pool)
+{
+ struct copy_as_changed_baton_t *cb;
+ svn_diff_tree_processor_t *filter;
+
+ cb = apr_pcalloc(result_pool, sizeof(*cb));
+ cb->processor = processor;
+
+ filter = svn_diff__tree_processor_create(cb, result_pool);
+ filter->dir_opened = copy_as_changed_dir_opened;
+ filter->dir_added = copy_as_changed_dir_added;
+ filter->dir_deleted = copy_as_changed_dir_deleted;
+ filter->dir_changed = copy_as_changed_dir_changed;
+ filter->dir_closed = copy_as_changed_dir_closed;
+
+ filter->file_opened = copy_as_changed_file_opened;
+ filter->file_added = copy_as_changed_file_added;
+ filter->file_deleted = copy_as_changed_file_deleted;
+ filter->file_changed = copy_as_changed_file_changed;
+ filter->file_closed = copy_as_changed_file_closed;
+
+ filter->node_absent = copy_as_changed_node_absent;
+
+ return filter;
+}
+
+
+/* Processor baton for the tee tree processor */
+struct tee_baton_t
+{
+ const svn_diff_tree_processor_t *p1;
+ const svn_diff_tree_processor_t *p2;
+};
+
+/* Wrapper baton for file and directory batons in the tee processor */
+struct tee_node_baton_t
+{
+ void *baton1;
+ void *baton2;
+};
+
+static svn_error_t *
+tee_dir_opened(void **new_dir_baton,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *parent_dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct tee_baton_t *tb = processor->baton;
+ struct tee_node_baton_t *pb = parent_dir_baton;
+ struct tee_node_baton_t *nb = apr_pcalloc(result_pool, sizeof(*nb));
+
+ SVN_ERR(tb->p1->dir_opened(&(nb->baton1),
+ skip,
+ skip_children,
+ relpath,
+ left_source,
+ right_source,
+ copyfrom_source,
+ pb ? pb->baton1 : NULL,
+ tb->p1,
+ result_pool,
+ scratch_pool));
+
+ SVN_ERR(tb->p2->dir_opened(&(nb->baton2),
+ skip,
+ skip_children,
+ relpath,
+ left_source,
+ right_source,
+ copyfrom_source,
+ pb ? pb->baton2 : NULL,
+ tb->p2,
+ result_pool,
+ scratch_pool));
+
+ *new_dir_baton = nb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tee_dir_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct tee_baton_t *tb = processor->baton;
+ struct tee_node_baton_t *db = dir_baton;
+
+ SVN_ERR(tb->p1->dir_added(relpath,
+ copyfrom_source,
+ right_source,
+ copyfrom_props,
+ right_props,
+ db->baton1,
+ tb->p1,
+ scratch_pool));
+
+ SVN_ERR(tb->p2->dir_added(relpath,
+ copyfrom_source,
+ right_source,
+ copyfrom_props,
+ right_props,
+ db->baton2,
+ tb->p2,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tee_dir_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ /*const*/ apr_hash_t *left_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct tee_baton_t *tb = processor->baton;
+ struct tee_node_baton_t *db = dir_baton;
+
+ SVN_ERR(tb->p1->dir_deleted(relpath,
+ left_source,
+ left_props,
+ db->baton1,
+ tb->p1,
+ scratch_pool));
+
+ SVN_ERR(tb->p2->dir_deleted(relpath,
+ left_source,
+ left_props,
+ db->baton2,
+ tb->p2,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tee_dir_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ const apr_array_header_t *prop_changes,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct tee_baton_t *tb = processor->baton;
+ struct tee_node_baton_t *db = dir_baton;
+
+ SVN_ERR(tb->p1->dir_changed(relpath,
+ left_source,
+ right_source,
+ left_props,
+ right_props,
+ prop_changes,
+ db->baton1,
+ tb->p1,
+ scratch_pool));
+
+ SVN_ERR(tb->p2->dir_changed(relpath,
+ left_source,
+ right_source,
+ left_props,
+ right_props,
+ prop_changes,
+ db->baton2,
+ tb->p2,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tee_dir_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct tee_baton_t *tb = processor->baton;
+ struct tee_node_baton_t *db = dir_baton;
+
+ SVN_ERR(tb->p1->dir_closed(relpath,
+ left_source,
+ right_source,
+ db->baton1,
+ tb->p1,
+ scratch_pool));
+
+ SVN_ERR(tb->p2->dir_closed(relpath,
+ left_source,
+ right_source,
+ db->baton2,
+ tb->p2,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tee_file_opened(void **new_file_baton,
+ svn_boolean_t *skip,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct tee_baton_t *tb = processor->baton;
+ struct tee_node_baton_t *pb = dir_baton;
+ struct tee_node_baton_t *nb = apr_pcalloc(result_pool, sizeof(*nb));
+
+ SVN_ERR(tb->p1->file_opened(&(nb->baton1),
+ skip,
+ relpath,
+ left_source,
+ right_source,
+ copyfrom_source,
+ pb ? pb->baton1 : NULL,
+ tb->p1,
+ result_pool,
+ scratch_pool));
+
+ SVN_ERR(tb->p2->file_opened(&(nb->baton2),
+ skip,
+ relpath,
+ left_source,
+ right_source,
+ copyfrom_source,
+ pb ? pb->baton2 : NULL,
+ tb->p2,
+ result_pool,
+ scratch_pool));
+
+ *new_file_baton = nb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tee_file_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ const char *copyfrom_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct tee_baton_t *tb = processor->baton;
+ struct tee_node_baton_t *fb = file_baton;
+
+ SVN_ERR(tb->p1->file_added(relpath,
+ copyfrom_source,
+ right_source,
+ copyfrom_file,
+ right_file,
+ copyfrom_props,
+ right_props,
+ fb->baton1,
+ tb->p1,
+ scratch_pool));
+
+ SVN_ERR(tb->p2->file_added(relpath,
+ copyfrom_source,
+ right_source,
+ copyfrom_file,
+ right_file,
+ copyfrom_props,
+ right_props,
+ fb->baton2,
+ tb->p2,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tee_file_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const char *left_file,
+ /*const*/ apr_hash_t *left_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct tee_baton_t *tb = processor->baton;
+ struct tee_node_baton_t *fb = file_baton;
+
+ SVN_ERR(tb->p1->file_deleted(relpath,
+ left_source,
+ left_file,
+ left_props,
+ fb->baton1,
+ tb->p1,
+ scratch_pool));
+
+ SVN_ERR(tb->p2->file_deleted(relpath,
+ left_source,
+ left_file,
+ left_props,
+ fb->baton2,
+ tb->p2,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tee_file_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const char *left_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_boolean_t file_modified,
+ const apr_array_header_t *prop_changes,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct tee_baton_t *tb = processor->baton;
+ struct tee_node_baton_t *fb = file_baton;
+
+ SVN_ERR(tb->p1->file_changed(relpath,
+ left_source,
+ right_source,
+ left_file,
+ right_file,
+ left_props,
+ right_props,
+ file_modified,
+ prop_changes,
+ fb->baton1,
+ tb->p1,
+ scratch_pool));
+
+ SVN_ERR(tb->p2->file_changed(relpath,
+ left_source,
+ right_source,
+ left_file,
+ right_file,
+ left_props,
+ right_props,
+ file_modified,
+ prop_changes,
+ fb->baton2,
+ tb->p2,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tee_file_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct tee_baton_t *tb = processor->baton;
+ struct tee_node_baton_t *fb = file_baton;
+
+ SVN_ERR(tb->p1->file_closed(relpath,
+ left_source,
+ right_source,
+ fb->baton1,
+ tb->p1,
+ scratch_pool));
+
+ SVN_ERR(tb->p2->file_closed(relpath,
+ left_source,
+ right_source,
+ fb->baton2,
+ tb->p2,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tee_node_absent(const char *relpath,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ struct tee_baton_t *tb = processor->baton;
+ struct tee_node_baton_t *db = dir_baton;
+
+ SVN_ERR(tb->p1->node_absent(relpath,
+ db ? db->baton1 : NULL,
+ tb->p1,
+ scratch_pool));
+
+ SVN_ERR(tb->p2->node_absent(relpath,
+ db ? db->baton2 : NULL,
+ tb->p2,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+const svn_diff_tree_processor_t *
+svn_diff__tree_processor_tee_create(const svn_diff_tree_processor_t *processor1,
+ const svn_diff_tree_processor_t *processor2,
+ apr_pool_t *result_pool)
+{
+ struct tee_baton_t *tb = apr_pcalloc(result_pool, sizeof(*tb));
+ svn_diff_tree_processor_t *tee;
+ tb->p1 = processor1;
+ tb->p2 = processor2;
+
+ tee = svn_diff__tree_processor_create(tb, result_pool);
+
+ tee->dir_opened = tee_dir_opened;
+ tee->dir_added = tee_dir_added;
+ tee->dir_deleted = tee_dir_deleted;
+ tee->dir_changed = tee_dir_changed;
+ tee->dir_closed = tee_dir_closed;
+ tee->file_opened = tee_file_opened;
+ tee->file_added = tee_file_added;
+ tee->file_deleted = tee_file_deleted;
+ tee->file_changed = tee_file_changed;
+ tee->file_closed = tee_file_closed;
+ tee->node_absent = tee_node_absent;
+
+ return tee;
+}
+
+svn_diff_source_t *
+svn_diff__source_create(svn_revnum_t revision,
+ apr_pool_t *result_pool)
+{
+ svn_diff_source_t *src = apr_pcalloc(result_pool, sizeof(*src));
+
+ src->revision = revision;
+ return src;
+}
diff --git a/contrib/subversion/subversion/libsvn_diff/lcs.c b/contrib/subversion/subversion/libsvn_diff/lcs.c
new file mode 100644
index 0000000..8087a92
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/lcs.c
@@ -0,0 +1,375 @@
+/*
+ * lcs.c : routines for creating an lcs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_general.h>
+
+#include "diff.h"
+
+
+/*
+ * Calculate the Longest Common Subsequence (LCS) between two datasources.
+ * This function is what makes the diff code tick.
+ *
+ * The LCS algorithm implemented here is based on the approach described
+ * by Sun Wu, Udi Manber and Gene Meyers in "An O(NP) Sequence Comparison
+ * Algorithm", but has been modified for better performance.
+ *
+ * Let M and N be the lengths (number of tokens) of the two sources
+ * ('files'). The goal is to reach the end of both sources (files) with the
+ * minimum number of insertions + deletions. Since there is a known length
+ * difference N-M between the files, that is equivalent to just the minimum
+ * number of deletions, or equivalently the minimum number of insertions.
+ * For symmetry, we use the lesser number - deletions if M<N, insertions if
+ * M>N.
+ *
+ * Let 'k' be the difference in remaining length between the files, i.e.
+ * if we're at the beginning of both files, k=N-M, whereas k=0 for the
+ * 'end state', at the end of both files. An insertion will increase k by
+ * one, while a deletion decreases k by one. If k<0, then insertions are
+ * 'free' - we need those to reach the end state k=0 anyway - but deletions
+ * are costly: Adding a deletion means that we will have to add an additional
+ * insertion later to reach the end state, so it doesn't matter if we count
+ * deletions or insertions. Similarly, deletions are free for k>0.
+ *
+ * Let a 'state' be a given position in each file {pos1, pos2}. An array
+ * 'fp' keeps track of the best possible state (largest values of
+ * {pos1, pos2}) that can be achieved for a given cost 'p' (# moves away
+ * from k=0), as well as a linked list of what matches were used to reach
+ * that state. For each new value of p, we find for each value of k the
+ * best achievable state for that k - either by doing a costly operation
+ * (deletion if k<0) from a state achieved at a lower p, or doing a free
+ * operation (insertion if k<0) from a state achieved at the same p -
+ * and in both cases advancing past any matching regions found. This is
+ * handled by running loops over k in order of descending absolute value.
+ *
+ * A recent improvement of the algorithm is to ignore tokens that are unique
+ * to one file or the other, as those are known from the start to be
+ * impossible to match.
+ */
+
+typedef struct svn_diff__snake_t svn_diff__snake_t;
+
+struct svn_diff__snake_t
+{
+ apr_off_t y;
+ svn_diff__lcs_t *lcs;
+ svn_diff__position_t *position[2];
+};
+
+static APR_INLINE void
+svn_diff__snake(svn_diff__snake_t *fp_k,
+ svn_diff__token_index_t *token_counts[2],
+ svn_diff__lcs_t **freelist,
+ apr_pool_t *pool)
+{
+ svn_diff__position_t *start_position[2];
+ svn_diff__position_t *position[2];
+ svn_diff__lcs_t *lcs;
+ svn_diff__lcs_t *previous_lcs;
+
+ /* The previous entry at fp[k] is going to be replaced. See if we
+ * can mark that lcs node for reuse, because the sequence up to this
+ * point was a dead end.
+ */
+ lcs = fp_k[0].lcs;
+ while (lcs)
+ {
+ lcs->refcount--;
+ if (lcs->refcount)
+ break;
+
+ previous_lcs = lcs->next;
+ lcs->next = *freelist;
+ *freelist = lcs;
+ lcs = previous_lcs;
+ }
+
+ if (fp_k[-1].y >= fp_k[1].y)
+ {
+ start_position[0] = fp_k[-1].position[0];
+ start_position[1] = fp_k[-1].position[1]->next;
+
+ previous_lcs = fp_k[-1].lcs;
+ }
+ else
+ {
+ start_position[0] = fp_k[1].position[0]->next;
+ start_position[1] = fp_k[1].position[1];
+
+ previous_lcs = fp_k[1].lcs;
+ }
+
+
+ if (previous_lcs)
+ {
+ previous_lcs->refcount++;
+ }
+
+ /* ### Optimization, skip all positions that don't have matchpoints
+ * ### anyway. Beware of the sentinel, don't skip it!
+ */
+
+ position[0] = start_position[0];
+ position[1] = start_position[1];
+
+ while (1)
+ {
+ while (position[0]->token_index == position[1]->token_index)
+ {
+ position[0] = position[0]->next;
+ position[1] = position[1]->next;
+ }
+
+ if (position[1] != start_position[1])
+ {
+ lcs = *freelist;
+ if (lcs)
+ {
+ *freelist = lcs->next;
+ }
+ else
+ {
+ lcs = apr_palloc(pool, sizeof(*lcs));
+ }
+
+ lcs->position[0] = start_position[0];
+ lcs->position[1] = start_position[1];
+ lcs->length = position[1]->offset - start_position[1]->offset;
+ lcs->next = previous_lcs;
+ lcs->refcount = 1;
+ previous_lcs = lcs;
+ start_position[0] = position[0];
+ start_position[1] = position[1];
+ }
+
+ /* Skip any and all tokens that only occur in one of the files */
+ if (position[0]->token_index >= 0
+ && token_counts[1][position[0]->token_index] == 0)
+ start_position[0] = position[0] = position[0]->next;
+ else if (position[1]->token_index >= 0
+ && token_counts[0][position[1]->token_index] == 0)
+ start_position[1] = position[1] = position[1]->next;
+ else
+ break;
+ }
+
+ fp_k[0].lcs = previous_lcs;
+ fp_k[0].position[0] = position[0];
+ fp_k[0].position[1] = position[1];
+
+ fp_k[0].y = position[1]->offset;
+}
+
+
+static svn_diff__lcs_t *
+svn_diff__lcs_reverse(svn_diff__lcs_t *lcs)
+{
+ svn_diff__lcs_t *next;
+ svn_diff__lcs_t *prev;
+
+ next = NULL;
+ while (lcs != NULL)
+ {
+ prev = lcs->next;
+ lcs->next = next;
+ next = lcs;
+ lcs = prev;
+ }
+
+ return next;
+}
+
+
+/* Prepends a new lcs chunk for the amount of LINES at the given positions
+ * POS0_OFFSET and POS1_OFFSET to the given LCS chain, and returns it.
+ * This function assumes LINES > 0. */
+static svn_diff__lcs_t *
+prepend_lcs(svn_diff__lcs_t *lcs, apr_off_t lines,
+ apr_off_t pos0_offset, apr_off_t pos1_offset,
+ apr_pool_t *pool)
+{
+ svn_diff__lcs_t *new_lcs;
+
+ SVN_ERR_ASSERT_NO_RETURN(lines > 0);
+
+ new_lcs = apr_palloc(pool, sizeof(*new_lcs));
+ new_lcs->position[0] = apr_pcalloc(pool, sizeof(*new_lcs->position[0]));
+ new_lcs->position[0]->offset = pos0_offset;
+ new_lcs->position[1] = apr_pcalloc(pool, sizeof(*new_lcs->position[1]));
+ new_lcs->position[1]->offset = pos1_offset;
+ new_lcs->length = lines;
+ new_lcs->refcount = 1;
+ new_lcs->next = lcs;
+
+ return new_lcs;
+}
+
+
+svn_diff__lcs_t *
+svn_diff__lcs(svn_diff__position_t *position_list1, /* pointer to tail (ring) */
+ svn_diff__position_t *position_list2, /* pointer to tail (ring) */
+ svn_diff__token_index_t *token_counts_list1, /* array of counts */
+ svn_diff__token_index_t *token_counts_list2, /* array of counts */
+ svn_diff__token_index_t num_tokens,
+ apr_off_t prefix_lines,
+ apr_off_t suffix_lines,
+ apr_pool_t *pool)
+{
+ apr_off_t length[2];
+ svn_diff__token_index_t *token_counts[2];
+ svn_diff__token_index_t unique_count[2];
+ svn_diff__token_index_t token_index;
+ svn_diff__snake_t *fp;
+ apr_off_t d;
+ apr_off_t k;
+ apr_off_t p = 0;
+ svn_diff__lcs_t *lcs, *lcs_freelist = NULL;
+
+ svn_diff__position_t sentinel_position[2];
+
+ /* Since EOF is always a sync point we tack on an EOF link
+ * with sentinel positions
+ */
+ lcs = apr_palloc(pool, sizeof(*lcs));
+ lcs->position[0] = apr_pcalloc(pool, sizeof(*lcs->position[0]));
+ lcs->position[0]->offset = position_list1
+ ? position_list1->offset + suffix_lines + 1
+ : prefix_lines + suffix_lines + 1;
+ lcs->position[1] = apr_pcalloc(pool, sizeof(*lcs->position[1]));
+ lcs->position[1]->offset = position_list2
+ ? position_list2->offset + suffix_lines + 1
+ : prefix_lines + suffix_lines + 1;
+ lcs->length = 0;
+ lcs->refcount = 1;
+ lcs->next = NULL;
+
+ if (position_list1 == NULL || position_list2 == NULL)
+ {
+ if (suffix_lines)
+ lcs = prepend_lcs(lcs, suffix_lines,
+ lcs->position[0]->offset - suffix_lines,
+ lcs->position[1]->offset - suffix_lines,
+ pool);
+ if (prefix_lines)
+ lcs = prepend_lcs(lcs, prefix_lines, 1, 1, pool);
+
+ return lcs;
+ }
+
+ unique_count[1] = unique_count[0] = 0;
+ for (token_index = 0; token_index < num_tokens; token_index++)
+ {
+ if (token_counts_list1[token_index] == 0)
+ unique_count[1] += token_counts_list2[token_index];
+ if (token_counts_list2[token_index] == 0)
+ unique_count[0] += token_counts_list1[token_index];
+ }
+
+ /* Calculate lengths M and N of the sequences to be compared. Do not
+ * count tokens unique to one file, as those are ignored in __snake.
+ */
+ length[0] = position_list1->offset - position_list1->next->offset + 1
+ - unique_count[0];
+ length[1] = position_list2->offset - position_list2->next->offset + 1
+ - unique_count[1];
+
+ /* strikerXXX: here we allocate the furthest point array, which is
+ * strikerXXX: sized M + N + 3 (!)
+ */
+ fp = apr_pcalloc(pool,
+ sizeof(*fp) * (apr_size_t)(length[0] + length[1] + 3));
+
+ /* The origo of fp corresponds to the end state, where we are
+ * at the end of both files. The valid states thus span from
+ * -N (at end of first file and at the beginning of the second
+ * file) to +M (the opposite :). Finally, svn_diff__snake needs
+ * 1 extra slot on each side to work.
+ */
+ fp += length[1] + 1;
+
+ sentinel_position[0].next = position_list1->next;
+ position_list1->next = &sentinel_position[0];
+ sentinel_position[0].offset = position_list1->offset + 1;
+ token_counts[0] = token_counts_list1;
+
+ sentinel_position[1].next = position_list2->next;
+ position_list2->next = &sentinel_position[1];
+ sentinel_position[1].offset = position_list2->offset + 1;
+ token_counts[1] = token_counts_list2;
+
+ /* Negative indices will not be used elsewhere
+ */
+ sentinel_position[0].token_index = -1;
+ sentinel_position[1].token_index = -2;
+
+ /* position d = M - N corresponds to the initial state, where
+ * we are at the beginning of both files.
+ */
+ d = length[0] - length[1];
+
+ /* k = d - 1 will be the first to be used to get previous
+ * position information from, make sure it holds sane
+ * data
+ */
+ fp[d - 1].position[0] = sentinel_position[0].next;
+ fp[d - 1].position[1] = &sentinel_position[1];
+
+ p = 0;
+ do
+ {
+ /* For k < 0, insertions are free */
+ for (k = (d < 0 ? d : 0) - p; k < 0; k++)
+ {
+ svn_diff__snake(fp + k, token_counts, &lcs_freelist, pool);
+ }
+ /* for k > 0, deletions are free */
+ for (k = (d > 0 ? d : 0) + p; k >= 0; k--)
+ {
+ svn_diff__snake(fp + k, token_counts, &lcs_freelist, pool);
+ }
+
+ p++;
+ }
+ while (fp[0].position[1] != &sentinel_position[1]);
+
+ if (suffix_lines)
+ lcs->next = prepend_lcs(fp[0].lcs, suffix_lines,
+ lcs->position[0]->offset - suffix_lines,
+ lcs->position[1]->offset - suffix_lines,
+ pool);
+ else
+ lcs->next = fp[0].lcs;
+
+ lcs = svn_diff__lcs_reverse(lcs);
+
+ position_list1->next = sentinel_position[0].next;
+ position_list2->next = sentinel_position[1].next;
+
+ if (prefix_lines)
+ return prepend_lcs(lcs, prefix_lines, 1, 1, pool);
+ else
+ return lcs;
+}
diff --git a/contrib/subversion/subversion/libsvn_diff/parse-diff.c b/contrib/subversion/subversion/libsvn_diff/parse-diff.c
new file mode 100644
index 0000000..a01b4d5
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/parse-diff.c
@@ -0,0 +1,1373 @@
+/*
+ * parse-diff.c: functions for parsing diff files
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_string.h"
+#include "svn_utf.h"
+#include "svn_dirent_uri.h"
+#include "svn_diff.h"
+
+#include "private/svn_eol_private.h"
+#include "private/svn_dep_compat.h"
+
+/* Helper macro for readability */
+#define starts_with(str, start) \
+ (strncmp((str), (start), strlen(start)) == 0)
+
+/* Like strlen() but for string literals. */
+#define STRLEN_LITERAL(str) (sizeof(str) - 1)
+
+/* This struct describes a range within a file, as well as the
+ * current cursor position within the range. All numbers are in bytes. */
+struct svn_diff__hunk_range {
+ apr_off_t start;
+ apr_off_t end;
+ apr_off_t current;
+};
+
+struct svn_diff_hunk_t {
+ /* The patch this hunk belongs to. */
+ svn_patch_t *patch;
+
+ /* APR file handle to the patch file this hunk came from. */
+ apr_file_t *apr_file;
+
+ /* Ranges used to keep track of this hunk's texts positions within
+ * the patch file. */
+ struct svn_diff__hunk_range diff_text_range;
+ struct svn_diff__hunk_range original_text_range;
+ struct svn_diff__hunk_range modified_text_range;
+
+ /* Hunk ranges as they appeared in the patch file.
+ * All numbers are lines, not bytes. */
+ svn_linenum_t original_start;
+ svn_linenum_t original_length;
+ svn_linenum_t modified_start;
+ svn_linenum_t modified_length;
+
+ /* Number of lines of leading and trailing hunk context. */
+ svn_linenum_t leading_context;
+ svn_linenum_t trailing_context;
+};
+
+void
+svn_diff_hunk_reset_diff_text(svn_diff_hunk_t *hunk)
+{
+ hunk->diff_text_range.current = hunk->diff_text_range.start;
+}
+
+void
+svn_diff_hunk_reset_original_text(svn_diff_hunk_t *hunk)
+{
+ if (hunk->patch->reverse)
+ hunk->modified_text_range.current = hunk->modified_text_range.start;
+ else
+ hunk->original_text_range.current = hunk->original_text_range.start;
+}
+
+void
+svn_diff_hunk_reset_modified_text(svn_diff_hunk_t *hunk)
+{
+ if (hunk->patch->reverse)
+ hunk->original_text_range.current = hunk->original_text_range.start;
+ else
+ hunk->modified_text_range.current = hunk->modified_text_range.start;
+}
+
+svn_linenum_t
+svn_diff_hunk_get_original_start(const svn_diff_hunk_t *hunk)
+{
+ return hunk->patch->reverse ? hunk->modified_start : hunk->original_start;
+}
+
+svn_linenum_t
+svn_diff_hunk_get_original_length(const svn_diff_hunk_t *hunk)
+{
+ return hunk->patch->reverse ? hunk->modified_length : hunk->original_length;
+}
+
+svn_linenum_t
+svn_diff_hunk_get_modified_start(const svn_diff_hunk_t *hunk)
+{
+ return hunk->patch->reverse ? hunk->original_start : hunk->modified_start;
+}
+
+svn_linenum_t
+svn_diff_hunk_get_modified_length(const svn_diff_hunk_t *hunk)
+{
+ return hunk->patch->reverse ? hunk->original_length : hunk->modified_length;
+}
+
+svn_linenum_t
+svn_diff_hunk_get_leading_context(const svn_diff_hunk_t *hunk)
+{
+ return hunk->leading_context;
+}
+
+svn_linenum_t
+svn_diff_hunk_get_trailing_context(const svn_diff_hunk_t *hunk)
+{
+ return hunk->trailing_context;
+}
+
+/* Try to parse a positive number from a decimal number encoded
+ * in the string NUMBER. Return parsed number in OFFSET, and return
+ * TRUE if parsing was successful. */
+static svn_boolean_t
+parse_offset(svn_linenum_t *offset, const char *number)
+{
+ svn_error_t *err;
+ apr_uint64_t val;
+
+ err = svn_cstring_strtoui64(&val, number, 0, SVN_LINENUM_MAX_VALUE, 10);
+ if (err)
+ {
+ svn_error_clear(err);
+ return FALSE;
+ }
+
+ *offset = (svn_linenum_t)val;
+
+ return TRUE;
+}
+
+/* Try to parse a hunk range specification from the string RANGE.
+ * Return parsed information in *START and *LENGTH, and return TRUE
+ * if the range parsed correctly. Note: This function may modify the
+ * input value RANGE. */
+static svn_boolean_t
+parse_range(svn_linenum_t *start, svn_linenum_t *length, char *range)
+{
+ char *comma;
+
+ if (*range == 0)
+ return FALSE;
+
+ comma = strstr(range, ",");
+ if (comma)
+ {
+ if (strlen(comma + 1) > 0)
+ {
+ /* Try to parse the length. */
+ if (! parse_offset(length, comma + 1))
+ return FALSE;
+
+ /* Snip off the end of the string,
+ * so we can comfortably parse the line
+ * number the hunk starts at. */
+ *comma = '\0';
+ }
+ else
+ /* A comma but no length? */
+ return FALSE;
+ }
+ else
+ {
+ *length = 1;
+ }
+
+ /* Try to parse the line number the hunk starts at. */
+ return parse_offset(start, range);
+}
+
+/* Try to parse a hunk header in string HEADER, putting parsed information
+ * into HUNK. Return TRUE if the header parsed correctly. ATAT is the
+ * character string used to delimit the hunk header.
+ * Do all allocations in POOL. */
+static svn_boolean_t
+parse_hunk_header(const char *header, svn_diff_hunk_t *hunk,
+ const char *atat, apr_pool_t *pool)
+{
+ const char *p;
+ const char *start;
+ svn_stringbuf_t *range;
+
+ p = header + strlen(atat);
+ if (*p != ' ')
+ /* No. */
+ return FALSE;
+ p++;
+ if (*p != '-')
+ /* Nah... */
+ return FALSE;
+ /* OK, this may be worth allocating some memory for... */
+ range = svn_stringbuf_create_ensure(31, pool);
+ start = ++p;
+ while (*p && *p != ' ')
+ {
+ p++;
+ }
+
+ if (*p != ' ')
+ /* No no no... */
+ return FALSE;
+
+ svn_stringbuf_appendbytes(range, start, p - start);
+
+ /* Try to parse the first range. */
+ if (! parse_range(&hunk->original_start, &hunk->original_length, range->data))
+ return FALSE;
+
+ /* Clear the stringbuf so we can reuse it for the second range. */
+ svn_stringbuf_setempty(range);
+ p++;
+ if (*p != '+')
+ /* Eeek! */
+ return FALSE;
+ /* OK, this may be worth copying... */
+ start = ++p;
+ while (*p && *p != ' ')
+ {
+ p++;
+ }
+ if (*p != ' ')
+ /* No no no... */
+ return FALSE;
+
+ svn_stringbuf_appendbytes(range, start, p - start);
+
+ /* Check for trailing @@ */
+ p++;
+ if (! starts_with(p, atat))
+ return FALSE;
+
+ /* There may be stuff like C-function names after the trailing @@,
+ * but we ignore that. */
+
+ /* Try to parse the second range. */
+ if (! parse_range(&hunk->modified_start, &hunk->modified_length, range->data))
+ return FALSE;
+
+ /* Hunk header is good. */
+ return TRUE;
+}
+
+/* Read a line of original or modified hunk text from the specified
+ * RANGE within FILE. FILE is expected to contain unidiff text.
+ * Leading unidiff symbols ('+', '-', and ' ') are removed from the line,
+ * Any lines commencing with the VERBOTEN character are discarded.
+ * VERBOTEN should be '+' or '-', depending on which form of hunk text
+ * is being read.
+ *
+ * All other parameters are as in svn_diff_hunk_readline_original_text()
+ * and svn_diff_hunk_readline_modified_text().
+ */
+static svn_error_t *
+hunk_readline_original_or_modified(apr_file_t *file,
+ struct svn_diff__hunk_range *range,
+ svn_stringbuf_t **stringbuf,
+ const char **eol,
+ svn_boolean_t *eof,
+ char verboten,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_size_t max_len;
+ svn_boolean_t filtered;
+ apr_off_t pos;
+ svn_stringbuf_t *str;
+
+ if (range->current >= range->end)
+ {
+ /* We're past the range. Indicate that no bytes can be read. */
+ *eof = TRUE;
+ if (eol)
+ *eol = NULL;
+ *stringbuf = svn_stringbuf_create_empty(result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ pos = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &range->current, scratch_pool));
+ do
+ {
+ max_len = range->end - range->current;
+ SVN_ERR(svn_io_file_readline(file, &str, eol, eof, max_len,
+ result_pool, scratch_pool));
+ range->current = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &range->current, scratch_pool));
+ filtered = (str->data[0] == verboten || str->data[0] == '\\');
+ }
+ while (filtered && ! *eof);
+
+ if (filtered)
+ {
+ /* EOF, return an empty string. */
+ *stringbuf = svn_stringbuf_create_ensure(0, result_pool);
+ }
+ else if (str->data[0] == '+' || str->data[0] == '-' || str->data[0] == ' ')
+ {
+ /* Shave off leading unidiff symbols. */
+ *stringbuf = svn_stringbuf_create(str->data + 1, result_pool);
+ }
+ else
+ {
+ /* Return the line as-is. */
+ *stringbuf = svn_stringbuf_dup(str, result_pool);
+ }
+
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff_hunk_readline_original_text(svn_diff_hunk_t *hunk,
+ svn_stringbuf_t **stringbuf,
+ const char **eol,
+ svn_boolean_t *eof,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ hunk_readline_original_or_modified(hunk->apr_file,
+ hunk->patch->reverse ?
+ &hunk->modified_text_range :
+ &hunk->original_text_range,
+ stringbuf, eol, eof,
+ hunk->patch->reverse ? '-' : '+',
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_diff_hunk_readline_modified_text(svn_diff_hunk_t *hunk,
+ svn_stringbuf_t **stringbuf,
+ const char **eol,
+ svn_boolean_t *eof,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ hunk_readline_original_or_modified(hunk->apr_file,
+ hunk->patch->reverse ?
+ &hunk->original_text_range :
+ &hunk->modified_text_range,
+ stringbuf, eol, eof,
+ hunk->patch->reverse ? '+' : '-',
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_diff_hunk_readline_diff_text(svn_diff_hunk_t *hunk,
+ svn_stringbuf_t **stringbuf,
+ const char **eol,
+ svn_boolean_t *eof,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_diff_hunk_t dummy;
+ svn_stringbuf_t *line;
+ apr_size_t max_len;
+ apr_off_t pos;
+
+ if (hunk->diff_text_range.current >= hunk->diff_text_range.end)
+ {
+ /* We're past the range. Indicate that no bytes can be read. */
+ *eof = TRUE;
+ if (eol)
+ *eol = NULL;
+ *stringbuf = svn_stringbuf_create_empty(result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ pos = 0;
+ SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_CUR, &pos, scratch_pool));
+ SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET,
+ &hunk->diff_text_range.current, scratch_pool));
+ max_len = hunk->diff_text_range.end - hunk->diff_text_range.current;
+ SVN_ERR(svn_io_file_readline(hunk->apr_file, &line, eol, eof, max_len,
+ result_pool,
+ scratch_pool));
+ hunk->diff_text_range.current = 0;
+ SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_CUR,
+ &hunk->diff_text_range.current, scratch_pool));
+ SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &pos, scratch_pool));
+
+ if (hunk->patch->reverse)
+ {
+ if (parse_hunk_header(line->data, &dummy, "@@", scratch_pool))
+ {
+ /* Line is a hunk header, reverse it. */
+ line = svn_stringbuf_createf(result_pool,
+ "@@ -%lu,%lu +%lu,%lu @@",
+ hunk->modified_start,
+ hunk->modified_length,
+ hunk->original_start,
+ hunk->original_length);
+ }
+ else if (parse_hunk_header(line->data, &dummy, "##", scratch_pool))
+ {
+ /* Line is a hunk header, reverse it. */
+ line = svn_stringbuf_createf(result_pool,
+ "## -%lu,%lu +%lu,%lu ##",
+ hunk->modified_start,
+ hunk->modified_length,
+ hunk->original_start,
+ hunk->original_length);
+ }
+ else
+ {
+ if (line->data[0] == '+')
+ line->data[0] = '-';
+ else if (line->data[0] == '-')
+ line->data[0] = '+';
+ }
+ }
+
+ *stringbuf = line;
+
+ return SVN_NO_ERROR;
+}
+
+/* Parse *PROP_NAME from HEADER as the part after the INDICATOR line.
+ * Allocate *PROP_NAME in RESULT_POOL.
+ * Set *PROP_NAME to NULL if no valid property name was found. */
+static svn_error_t *
+parse_prop_name(const char **prop_name, const char *header,
+ const char *indicator, apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_utf_cstring_to_utf8(prop_name,
+ header + strlen(indicator),
+ result_pool));
+ if (**prop_name == '\0')
+ *prop_name = NULL;
+ else if (! svn_prop_name_is_valid(*prop_name))
+ {
+ svn_stringbuf_t *buf = svn_stringbuf_create(*prop_name, result_pool);
+ svn_stringbuf_strip_whitespace(buf);
+ *prop_name = (svn_prop_name_is_valid(buf->data) ? buf->data : NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the next *HUNK from a PATCH in APR_FILE.
+ * If no hunk can be found, set *HUNK to NULL.
+ * Set IS_PROPERTY to TRUE if we have a property hunk. If the returned HUNK
+ * is the first belonging to a certain property, then PROP_NAME and
+ * PROP_OPERATION will be set too. If we have a text hunk, PROP_NAME will be
+ * NULL. If IGNORE_WHITESPACE is TRUE, lines without leading spaces will be
+ * treated as context lines. Allocate results in RESULT_POOL.
+ * Use SCRATCH_POOL for all other allocations. */
+static svn_error_t *
+parse_next_hunk(svn_diff_hunk_t **hunk,
+ svn_boolean_t *is_property,
+ const char **prop_name,
+ svn_diff_operation_kind_t *prop_operation,
+ svn_patch_t *patch,
+ apr_file_t *apr_file,
+ svn_boolean_t ignore_whitespace,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ static const char * const minus = "--- ";
+ static const char * const text_atat = "@@";
+ static const char * const prop_atat = "##";
+ svn_stringbuf_t *line;
+ svn_boolean_t eof, in_hunk, hunk_seen;
+ apr_off_t pos, last_line;
+ apr_off_t start, end;
+ apr_off_t original_end;
+ apr_off_t modified_end;
+ svn_linenum_t original_lines;
+ svn_linenum_t modified_lines;
+ svn_linenum_t leading_context;
+ svn_linenum_t trailing_context;
+ svn_boolean_t changed_line_seen;
+ enum {
+ noise_line,
+ original_line,
+ modified_line,
+ context_line
+ } last_line_type;
+ apr_pool_t *iterpool;
+
+ *prop_operation = svn_diff_op_unchanged;
+
+ /* We only set this if we have a property hunk header. */
+ *prop_name = NULL;
+ *is_property = FALSE;
+
+ if (apr_file_eof(apr_file) == APR_EOF)
+ {
+ /* No more hunks here. */
+ *hunk = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ in_hunk = FALSE;
+ hunk_seen = FALSE;
+ leading_context = 0;
+ trailing_context = 0;
+ changed_line_seen = FALSE;
+ original_end = 0;
+ modified_end = 0;
+ *hunk = apr_pcalloc(result_pool, sizeof(**hunk));
+
+ /* Get current seek position -- APR has no ftell() :( */
+ pos = 0;
+ SVN_ERR(svn_io_file_seek(apr_file, APR_CUR, &pos, scratch_pool));
+
+ /* Start out assuming noise. */
+ last_line_type = noise_line;
+
+ iterpool = svn_pool_create(scratch_pool);
+ do
+ {
+
+ svn_pool_clear(iterpool);
+
+ /* Remember the current line's offset, and read the line. */
+ last_line = pos;
+ SVN_ERR(svn_io_file_readline(apr_file, &line, NULL, &eof, APR_SIZE_MAX,
+ iterpool, iterpool));
+
+ /* Update line offset for next iteration. */
+ pos = 0;
+ SVN_ERR(svn_io_file_seek(apr_file, APR_CUR, &pos, iterpool));
+
+ /* Lines starting with a backslash indicate a missing EOL:
+ * "\ No newline at end of file" or "end of property". */
+ if (line->data[0] == '\\')
+ {
+ if (in_hunk)
+ {
+ char eolbuf[2];
+ apr_size_t len;
+ apr_off_t off;
+ apr_off_t hunk_text_end;
+
+ /* Comment terminates the hunk text and says the hunk text
+ * has no trailing EOL. Snip off trailing EOL which is part
+ * of the patch file but not part of the hunk text. */
+ off = last_line - 2;
+ SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &off, iterpool));
+ len = sizeof(eolbuf);
+ SVN_ERR(svn_io_file_read_full2(apr_file, eolbuf, len, &len,
+ &eof, iterpool));
+ if (eolbuf[0] == '\r' && eolbuf[1] == '\n')
+ hunk_text_end = last_line - 2;
+ else if (eolbuf[1] == '\n' || eolbuf[1] == '\r')
+ hunk_text_end = last_line - 1;
+ else
+ hunk_text_end = last_line;
+
+ if (last_line_type == original_line && original_end == 0)
+ original_end = hunk_text_end;
+ else if (last_line_type == modified_line && modified_end == 0)
+ modified_end = hunk_text_end;
+ else if (last_line_type == context_line)
+ {
+ if (original_end == 0)
+ original_end = hunk_text_end;
+ if (modified_end == 0)
+ modified_end = hunk_text_end;
+ }
+
+ SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &pos, iterpool));
+ }
+
+ continue;
+ }
+
+ if (in_hunk)
+ {
+ char c;
+ static const char add = '+';
+ static const char del = '-';
+
+ if (! hunk_seen)
+ {
+ /* We're reading the first line of the hunk, so the start
+ * of the line just read is the hunk text's byte offset. */
+ start = last_line;
+ }
+
+ c = line->data[0];
+ if (original_lines > 0 && modified_lines > 0 &&
+ ((c == ' ')
+ /* Tolerate chopped leading spaces on empty lines. */
+ || (! eof && line->len == 0)
+ /* Maybe tolerate chopped leading spaces on non-empty lines. */
+ || (ignore_whitespace && c != del && c != add)))
+ {
+ /* It's a "context" line in the hunk. */
+ hunk_seen = TRUE;
+ original_lines--;
+ modified_lines--;
+ if (changed_line_seen)
+ trailing_context++;
+ else
+ leading_context++;
+ last_line_type = context_line;
+ }
+ else if (original_lines > 0 && c == del)
+ {
+ /* It's a "deleted" line in the hunk. */
+ hunk_seen = TRUE;
+ changed_line_seen = TRUE;
+
+ /* A hunk may have context in the middle. We only want
+ trailing lines of context. */
+ if (trailing_context > 0)
+ trailing_context = 0;
+
+ original_lines--;
+ last_line_type = original_line;
+ }
+ else if (modified_lines > 0 && c == add)
+ {
+ /* It's an "added" line in the hunk. */
+ hunk_seen = TRUE;
+ changed_line_seen = TRUE;
+
+ /* A hunk may have context in the middle. We only want
+ trailing lines of context. */
+ if (trailing_context > 0)
+ trailing_context = 0;
+
+ modified_lines--;
+ last_line_type = modified_line;
+ }
+ else
+ {
+ if (eof)
+ {
+ /* The hunk ends at EOF. */
+ end = pos;
+ }
+ else
+ {
+ /* The start of the current line marks the first byte
+ * after the hunk text. */
+ end = last_line;
+ }
+
+ if (original_end == 0)
+ original_end = end;
+ if (modified_end == 0)
+ modified_end = end;
+ break; /* Hunk was empty or has been read. */
+ }
+ }
+ else
+ {
+ if (starts_with(line->data, text_atat))
+ {
+ /* Looks like we have a hunk header, try to rip it apart. */
+ in_hunk = parse_hunk_header(line->data, *hunk, text_atat,
+ iterpool);
+ if (in_hunk)
+ {
+ original_lines = (*hunk)->original_length;
+ modified_lines = (*hunk)->modified_length;
+ *is_property = FALSE;
+ }
+ }
+ else if (starts_with(line->data, prop_atat))
+ {
+ /* Looks like we have a property hunk header, try to rip it
+ * apart. */
+ in_hunk = parse_hunk_header(line->data, *hunk, prop_atat,
+ iterpool);
+ if (in_hunk)
+ {
+ original_lines = (*hunk)->original_length;
+ modified_lines = (*hunk)->modified_length;
+ *is_property = TRUE;
+ }
+ }
+ else if (starts_with(line->data, "Added: "))
+ {
+ SVN_ERR(parse_prop_name(prop_name, line->data, "Added: ",
+ result_pool));
+ if (*prop_name)
+ *prop_operation = svn_diff_op_added;
+ }
+ else if (starts_with(line->data, "Deleted: "))
+ {
+ SVN_ERR(parse_prop_name(prop_name, line->data, "Deleted: ",
+ result_pool));
+ if (*prop_name)
+ *prop_operation = svn_diff_op_deleted;
+ }
+ else if (starts_with(line->data, "Modified: "))
+ {
+ SVN_ERR(parse_prop_name(prop_name, line->data, "Modified: ",
+ result_pool));
+ if (*prop_name)
+ *prop_operation = svn_diff_op_modified;
+ }
+ else if (starts_with(line->data, minus)
+ || starts_with(line->data, "diff --git "))
+ /* This could be a header of another patch. Bail out. */
+ break;
+ }
+ }
+ /* Check for the line length since a file may not have a newline at the
+ * end and we depend upon the last line to be an empty one. */
+ while (! eof || line->len > 0);
+ svn_pool_destroy(iterpool);
+
+ if (! eof)
+ /* Rewind to the start of the line just read, so subsequent calls
+ * to this function or svn_diff_parse_next_patch() don't end
+ * up skipping the line -- it may contain a patch or hunk header. */
+ SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &last_line, scratch_pool));
+
+ if (hunk_seen && start < end)
+ {
+ (*hunk)->patch = patch;
+ (*hunk)->apr_file = apr_file;
+ (*hunk)->leading_context = leading_context;
+ (*hunk)->trailing_context = trailing_context;
+ (*hunk)->diff_text_range.start = start;
+ (*hunk)->diff_text_range.current = start;
+ (*hunk)->diff_text_range.end = end;
+ (*hunk)->original_text_range.start = start;
+ (*hunk)->original_text_range.current = start;
+ (*hunk)->original_text_range.end = original_end;
+ (*hunk)->modified_text_range.start = start;
+ (*hunk)->modified_text_range.current = start;
+ (*hunk)->modified_text_range.end = modified_end;
+ }
+ else
+ /* Something went wrong, just discard the result. */
+ *hunk = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* Compare function for sorting hunks after parsing.
+ * We sort hunks by their original line offset. */
+static int
+compare_hunks(const void *a, const void *b)
+{
+ const svn_diff_hunk_t *ha = *((const svn_diff_hunk_t *const *)a);
+ const svn_diff_hunk_t *hb = *((const svn_diff_hunk_t *const *)b);
+
+ if (ha->original_start < hb->original_start)
+ return -1;
+ if (ha->original_start > hb->original_start)
+ return 1;
+ return 0;
+}
+
+/* Possible states of the diff header parser. */
+enum parse_state
+{
+ state_start, /* initial */
+ state_git_diff_seen, /* diff --git */
+ state_git_tree_seen, /* a tree operation, rather then content change */
+ state_git_minus_seen, /* --- /dev/null; or --- a/ */
+ state_git_plus_seen, /* +++ /dev/null; or +++ a/ */
+ state_move_from_seen, /* rename from foo.c */
+ state_copy_from_seen, /* copy from foo.c */
+ state_minus_seen, /* --- foo.c */
+ state_unidiff_found, /* valid start of a regular unidiff header */
+ state_git_header_found /* valid start of a --git diff header */
+};
+
+/* Data type describing a valid state transition of the parser. */
+struct transition
+{
+ const char *expected_input;
+ enum parse_state required_state;
+
+ /* A callback called upon each parser state transition. */
+ svn_error_t *(*fn)(enum parse_state *new_state, char *input,
+ svn_patch_t *patch, apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+};
+
+/* UTF-8 encode and canonicalize the content of LINE as FILE_NAME. */
+static svn_error_t *
+grab_filename(const char **file_name, const char *line, apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *utf8_path;
+ const char *canon_path;
+
+ /* Grab the filename and encode it in UTF-8. */
+ /* TODO: Allow specifying the patch file's encoding.
+ * For now, we assume its encoding is native. */
+ /* ### This can fail if the filename cannot be represented in the current
+ * ### locale's encoding. */
+ SVN_ERR(svn_utf_cstring_to_utf8(&utf8_path,
+ line,
+ scratch_pool));
+
+ /* Canonicalize the path name. */
+ canon_path = svn_dirent_canonicalize(utf8_path, scratch_pool);
+
+ *file_name = apr_pstrdup(result_pool, canon_path);
+
+ return SVN_NO_ERROR;
+}
+
+/* Parse the '--- ' line of a regular unidiff. */
+static svn_error_t *
+diff_minus(enum parse_state *new_state, char *line, svn_patch_t *patch,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ /* If we can find a tab, it separates the filename from
+ * the rest of the line which we can discard. */
+ char *tab = strchr(line, '\t');
+ if (tab)
+ *tab = '\0';
+
+ SVN_ERR(grab_filename(&patch->old_filename, line + STRLEN_LITERAL("--- "),
+ result_pool, scratch_pool));
+
+ *new_state = state_minus_seen;
+
+ return SVN_NO_ERROR;
+}
+
+/* Parse the '+++ ' line of a regular unidiff. */
+static svn_error_t *
+diff_plus(enum parse_state *new_state, char *line, svn_patch_t *patch,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ /* If we can find a tab, it separates the filename from
+ * the rest of the line which we can discard. */
+ char *tab = strchr(line, '\t');
+ if (tab)
+ *tab = '\0';
+
+ SVN_ERR(grab_filename(&patch->new_filename, line + STRLEN_LITERAL("+++ "),
+ result_pool, scratch_pool));
+
+ *new_state = state_unidiff_found;
+
+ return SVN_NO_ERROR;
+}
+
+/* Parse the first line of a git extended unidiff. */
+static svn_error_t *
+git_start(enum parse_state *new_state, char *line, svn_patch_t *patch,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ const char *old_path_start;
+ char *old_path_end;
+ const char *new_path_start;
+ const char *new_path_end;
+ char *new_path_marker;
+ const char *old_path_marker;
+
+ /* ### Add handling of escaped paths
+ * http://www.kernel.org/pub/software/scm/git/docs/git-diff.html:
+ *
+ * TAB, LF, double quote and backslash characters in pathnames are
+ * represented as \t, \n, \" and \\, respectively. If there is need for
+ * such substitution then the whole pathname is put in double quotes.
+ */
+
+ /* Our line should look like this: 'diff --git a/path b/path'.
+ *
+ * If we find any deviations from that format, we return with state reset
+ * to start.
+ */
+ old_path_marker = strstr(line, " a/");
+
+ if (! old_path_marker)
+ {
+ *new_state = state_start;
+ return SVN_NO_ERROR;
+ }
+
+ if (! *(old_path_marker + 3))
+ {
+ *new_state = state_start;
+ return SVN_NO_ERROR;
+ }
+
+ new_path_marker = strstr(old_path_marker, " b/");
+
+ if (! new_path_marker)
+ {
+ *new_state = state_start;
+ return SVN_NO_ERROR;
+ }
+
+ if (! *(new_path_marker + 3))
+ {
+ *new_state = state_start;
+ return SVN_NO_ERROR;
+ }
+
+ /* By now, we know that we have a line on the form '--git diff a/.+ b/.+'
+ * We only need the filenames when we have deleted or added empty
+ * files. In those cases the old_path and new_path is identical on the
+ * 'diff --git' line. For all other cases we fetch the filenames from
+ * other header lines. */
+ old_path_start = line + STRLEN_LITERAL("diff --git a/");
+ new_path_end = line + strlen(line);
+ new_path_start = old_path_start;
+
+ while (TRUE)
+ {
+ ptrdiff_t len_old;
+ ptrdiff_t len_new;
+
+ new_path_marker = strstr(new_path_start, " b/");
+
+ /* No new path marker, bail out. */
+ if (! new_path_marker)
+ break;
+
+ old_path_end = new_path_marker;
+ new_path_start = new_path_marker + STRLEN_LITERAL(" b/");
+
+ /* No path after the marker. */
+ if (! *new_path_start)
+ break;
+
+ len_old = old_path_end - old_path_start;
+ len_new = new_path_end - new_path_start;
+
+ /* Are the paths before and after the " b/" marker the same? */
+ if (len_old == len_new
+ && ! strncmp(old_path_start, new_path_start, len_old))
+ {
+ *old_path_end = '\0';
+ SVN_ERR(grab_filename(&patch->old_filename, old_path_start,
+ result_pool, scratch_pool));
+
+ SVN_ERR(grab_filename(&patch->new_filename, new_path_start,
+ result_pool, scratch_pool));
+ break;
+ }
+ }
+
+ /* We assume that the path is only modified until we've found a 'tree'
+ * header */
+ patch->operation = svn_diff_op_modified;
+
+ *new_state = state_git_diff_seen;
+ return SVN_NO_ERROR;
+}
+
+/* Parse the '--- ' line of a git extended unidiff. */
+static svn_error_t *
+git_minus(enum parse_state *new_state, char *line, svn_patch_t *patch,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ /* If we can find a tab, it separates the filename from
+ * the rest of the line which we can discard. */
+ char *tab = strchr(line, '\t');
+ if (tab)
+ *tab = '\0';
+
+ if (starts_with(line, "--- /dev/null"))
+ SVN_ERR(grab_filename(&patch->old_filename, "/dev/null",
+ result_pool, scratch_pool));
+ else
+ SVN_ERR(grab_filename(&patch->old_filename, line + STRLEN_LITERAL("--- a/"),
+ result_pool, scratch_pool));
+
+ *new_state = state_git_minus_seen;
+ return SVN_NO_ERROR;
+}
+
+/* Parse the '+++ ' line of a git extended unidiff. */
+static svn_error_t *
+git_plus(enum parse_state *new_state, char *line, svn_patch_t *patch,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ /* If we can find a tab, it separates the filename from
+ * the rest of the line which we can discard. */
+ char *tab = strchr(line, '\t');
+ if (tab)
+ *tab = '\0';
+
+ if (starts_with(line, "+++ /dev/null"))
+ SVN_ERR(grab_filename(&patch->new_filename, "/dev/null",
+ result_pool, scratch_pool));
+ else
+ SVN_ERR(grab_filename(&patch->new_filename, line + STRLEN_LITERAL("+++ b/"),
+ result_pool, scratch_pool));
+
+ *new_state = state_git_header_found;
+ return SVN_NO_ERROR;
+}
+
+/* Parse the 'rename from ' line of a git extended unidiff. */
+static svn_error_t *
+git_move_from(enum parse_state *new_state, char *line, svn_patch_t *patch,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(grab_filename(&patch->old_filename,
+ line + STRLEN_LITERAL("rename from "),
+ result_pool, scratch_pool));
+
+ *new_state = state_move_from_seen;
+ return SVN_NO_ERROR;
+}
+
+/* Parse the 'rename to ' line of a git extended unidiff. */
+static svn_error_t *
+git_move_to(enum parse_state *new_state, char *line, svn_patch_t *patch,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(grab_filename(&patch->new_filename,
+ line + STRLEN_LITERAL("rename to "),
+ result_pool, scratch_pool));
+
+ patch->operation = svn_diff_op_moved;
+
+ *new_state = state_git_tree_seen;
+ return SVN_NO_ERROR;
+}
+
+/* Parse the 'copy from ' line of a git extended unidiff. */
+static svn_error_t *
+git_copy_from(enum parse_state *new_state, char *line, svn_patch_t *patch,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(grab_filename(&patch->old_filename,
+ line + STRLEN_LITERAL("copy from "),
+ result_pool, scratch_pool));
+
+ *new_state = state_copy_from_seen;
+ return SVN_NO_ERROR;
+}
+
+/* Parse the 'copy to ' line of a git extended unidiff. */
+static svn_error_t *
+git_copy_to(enum parse_state *new_state, char *line, svn_patch_t *patch,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(grab_filename(&patch->new_filename, line + STRLEN_LITERAL("copy to "),
+ result_pool, scratch_pool));
+
+ patch->operation = svn_diff_op_copied;
+
+ *new_state = state_git_tree_seen;
+ return SVN_NO_ERROR;
+}
+
+/* Parse the 'new file ' line of a git extended unidiff. */
+static svn_error_t *
+git_new_file(enum parse_state *new_state, char *line, svn_patch_t *patch,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ patch->operation = svn_diff_op_added;
+
+ /* Filename already retrieved from diff --git header. */
+
+ *new_state = state_git_tree_seen;
+ return SVN_NO_ERROR;
+}
+
+/* Parse the 'deleted file ' line of a git extended unidiff. */
+static svn_error_t *
+git_deleted_file(enum parse_state *new_state, char *line, svn_patch_t *patch,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ patch->operation = svn_diff_op_deleted;
+
+ /* Filename already retrieved from diff --git header. */
+
+ *new_state = state_git_tree_seen;
+ return SVN_NO_ERROR;
+}
+
+/* Add a HUNK associated with the property PROP_NAME to PATCH. */
+static svn_error_t *
+add_property_hunk(svn_patch_t *patch, const char *prop_name,
+ svn_diff_hunk_t *hunk, svn_diff_operation_kind_t operation,
+ apr_pool_t *result_pool)
+{
+ svn_prop_patch_t *prop_patch;
+
+ prop_patch = svn_hash_gets(patch->prop_patches, prop_name);
+
+ if (! prop_patch)
+ {
+ prop_patch = apr_palloc(result_pool, sizeof(svn_prop_patch_t));
+ prop_patch->name = prop_name;
+ prop_patch->operation = operation;
+ prop_patch->hunks = apr_array_make(result_pool, 1,
+ sizeof(svn_diff_hunk_t *));
+
+ svn_hash_sets(patch->prop_patches, prop_name, prop_patch);
+ }
+
+ APR_ARRAY_PUSH(prop_patch->hunks, svn_diff_hunk_t *) = hunk;
+
+ return SVN_NO_ERROR;
+}
+
+struct svn_patch_file_t
+{
+ /* The APR file handle to the patch file. */
+ apr_file_t *apr_file;
+
+ /* The file offset at which the next patch is expected. */
+ apr_off_t next_patch_offset;
+};
+
+svn_error_t *
+svn_diff_open_patch_file(svn_patch_file_t **patch_file,
+ const char *local_abspath,
+ apr_pool_t *result_pool)
+{
+ svn_patch_file_t *p;
+
+ p = apr_palloc(result_pool, sizeof(*p));
+ SVN_ERR(svn_io_file_open(&p->apr_file, local_abspath,
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
+ result_pool));
+ p->next_patch_offset = 0;
+ *patch_file = p;
+
+ return SVN_NO_ERROR;
+}
+
+/* Parse hunks from APR_FILE and store them in PATCH->HUNKS.
+ * Parsing stops if no valid next hunk can be found.
+ * If IGNORE_WHITESPACE is TRUE, lines without
+ * leading spaces will be treated as context lines.
+ * Allocate results in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+parse_hunks(svn_patch_t *patch, apr_file_t *apr_file,
+ svn_boolean_t ignore_whitespace,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ svn_diff_hunk_t *hunk;
+ svn_boolean_t is_property;
+ const char *last_prop_name;
+ const char *prop_name;
+ svn_diff_operation_kind_t prop_operation;
+ apr_pool_t *iterpool;
+
+ last_prop_name = NULL;
+
+ patch->hunks = apr_array_make(result_pool, 10, sizeof(svn_diff_hunk_t *));
+ patch->prop_patches = apr_hash_make(result_pool);
+ iterpool = svn_pool_create(scratch_pool);
+ do
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(parse_next_hunk(&hunk, &is_property, &prop_name, &prop_operation,
+ patch, apr_file, ignore_whitespace, result_pool,
+ iterpool));
+
+ if (hunk && is_property)
+ {
+ if (! prop_name)
+ prop_name = last_prop_name;
+ else
+ last_prop_name = prop_name;
+ SVN_ERR(add_property_hunk(patch, prop_name, hunk, prop_operation,
+ result_pool));
+ }
+ else if (hunk)
+ {
+ APR_ARRAY_PUSH(patch->hunks, svn_diff_hunk_t *) = hunk;
+ last_prop_name = NULL;
+ }
+
+ }
+ while (hunk);
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* State machine for the diff header parser.
+ * Expected Input Required state Function to call */
+static struct transition transitions[] =
+{
+ {"--- ", state_start, diff_minus},
+ {"+++ ", state_minus_seen, diff_plus},
+ {"diff --git", state_start, git_start},
+ {"--- a/", state_git_diff_seen, git_minus},
+ {"--- a/", state_git_tree_seen, git_minus},
+ {"--- /dev/null", state_git_tree_seen, git_minus},
+ {"+++ b/", state_git_minus_seen, git_plus},
+ {"+++ /dev/null", state_git_minus_seen, git_plus},
+ {"rename from ", state_git_diff_seen, git_move_from},
+ {"rename to ", state_move_from_seen, git_move_to},
+ {"copy from ", state_git_diff_seen, git_copy_from},
+ {"copy to ", state_copy_from_seen, git_copy_to},
+ {"new file ", state_git_diff_seen, git_new_file},
+ {"deleted file ", state_git_diff_seen, git_deleted_file},
+};
+
+svn_error_t *
+svn_diff_parse_next_patch(svn_patch_t **patch,
+ svn_patch_file_t *patch_file,
+ svn_boolean_t reverse,
+ svn_boolean_t ignore_whitespace,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_off_t pos, last_line;
+ svn_boolean_t eof;
+ svn_boolean_t line_after_tree_header_read = FALSE;
+ apr_pool_t *iterpool;
+ enum parse_state state = state_start;
+
+ if (apr_file_eof(patch_file->apr_file) == APR_EOF)
+ {
+ /* No more patches here. */
+ *patch = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ *patch = apr_pcalloc(result_pool, sizeof(**patch));
+
+ pos = patch_file->next_patch_offset;
+ SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &pos, scratch_pool));
+
+ iterpool = svn_pool_create(scratch_pool);
+ do
+ {
+ svn_stringbuf_t *line;
+ svn_boolean_t valid_header_line = FALSE;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ /* Remember the current line's offset, and read the line. */
+ last_line = pos;
+ SVN_ERR(svn_io_file_readline(patch_file->apr_file, &line, NULL, &eof,
+ APR_SIZE_MAX, iterpool, iterpool));
+
+ if (! eof)
+ {
+ /* Update line offset for next iteration. */
+ pos = 0;
+ SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_CUR, &pos,
+ iterpool));
+ }
+
+ /* Run the state machine. */
+ for (i = 0; i < (sizeof(transitions) / sizeof(transitions[0])); i++)
+ {
+ if (starts_with(line->data, transitions[i].expected_input)
+ && state == transitions[i].required_state)
+ {
+ SVN_ERR(transitions[i].fn(&state, line->data, *patch,
+ result_pool, iterpool));
+ valid_header_line = TRUE;
+ break;
+ }
+ }
+
+ if (state == state_unidiff_found || state == state_git_header_found)
+ {
+ /* We have a valid diff header, yay! */
+ break;
+ }
+ else if (state == state_git_tree_seen && line_after_tree_header_read)
+ {
+ /* git patches can contain an index line after the file mode line */
+ if (!starts_with(line->data, "index "))
+ {
+ /* We have a valid diff header for a patch with only tree changes.
+ * Rewind to the start of the line just read, so subsequent calls
+ * to this function don't end up skipping the line -- it may
+ * contain a patch. */
+ SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &last_line,
+ scratch_pool));
+ break;
+ }
+ }
+ else if (state == state_git_tree_seen)
+ {
+ line_after_tree_header_read = TRUE;
+ }
+ else if (! valid_header_line && state != state_start
+ && !starts_with(line->data, "index "))
+ {
+ /* We've encountered an invalid diff header.
+ *
+ * Rewind to the start of the line just read - it may be a new
+ * header that begins there. */
+ SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &last_line,
+ scratch_pool));
+ state = state_start;
+ }
+
+ }
+ while (! eof);
+
+ (*patch)->reverse = reverse;
+ if (reverse)
+ {
+ const char *temp;
+ temp = (*patch)->old_filename;
+ (*patch)->old_filename = (*patch)->new_filename;
+ (*patch)->new_filename = temp;
+ }
+
+ if ((*patch)->old_filename == NULL || (*patch)->new_filename == NULL)
+ {
+ /* Something went wrong, just discard the result. */
+ *patch = NULL;
+ }
+ else
+ SVN_ERR(parse_hunks(*patch, patch_file->apr_file, ignore_whitespace,
+ result_pool, iterpool));
+
+ svn_pool_destroy(iterpool);
+
+ patch_file->next_patch_offset = 0;
+ SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_CUR,
+ &patch_file->next_patch_offset, scratch_pool));
+
+ if (*patch)
+ {
+ /* Usually, hunks appear in the patch sorted by their original line
+ * offset. But just in case they weren't parsed in this order for
+ * some reason, we sort them so that our caller can assume that hunks
+ * are sorted as if parsed from a usual patch. */
+ qsort((*patch)->hunks->elts, (*patch)->hunks->nelts,
+ (*patch)->hunks->elt_size, compare_hunks);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff_close_patch_file(svn_patch_file_t *patch_file,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_io_file_close(patch_file->apr_file,
+ scratch_pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_diff/token.c b/contrib/subversion/subversion/libsvn_diff/token.c
new file mode 100644
index 0000000..6388d9f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/token.c
@@ -0,0 +1,198 @@
+/*
+ * token.c : routines for doing diffs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_general.h>
+
+#include "svn_error.h"
+#include "svn_diff.h"
+#include "svn_types.h"
+
+#include "diff.h"
+
+
+/*
+ * Prime number to use as the size of the hash table. This number was
+ * not selected by testing of any kind and may need tweaking.
+ */
+#define SVN_DIFF__HASH_SIZE 127
+
+struct svn_diff__node_t
+{
+ svn_diff__node_t *parent;
+ svn_diff__node_t *left;
+ svn_diff__node_t *right;
+
+ apr_uint32_t hash;
+ svn_diff__token_index_t index;
+ void *token;
+};
+
+struct svn_diff__tree_t
+{
+ svn_diff__node_t *root[SVN_DIFF__HASH_SIZE];
+ apr_pool_t *pool;
+ svn_diff__token_index_t node_count;
+};
+
+
+/*
+ * Returns number of tokens in a tree
+ */
+svn_diff__token_index_t
+svn_diff__get_node_count(svn_diff__tree_t *tree)
+{
+ return tree->node_count;
+}
+
+/*
+ * Support functions to build a tree of token positions
+ */
+
+void
+svn_diff__tree_create(svn_diff__tree_t **tree, apr_pool_t *pool)
+{
+ *tree = apr_pcalloc(pool, sizeof(**tree));
+ (*tree)->pool = pool;
+ (*tree)->node_count = 0;
+}
+
+
+static svn_error_t *
+tree_insert_token(svn_diff__node_t **node, svn_diff__tree_t *tree,
+ void *diff_baton,
+ const svn_diff_fns2_t *vtable,
+ apr_uint32_t hash, void *token)
+{
+ svn_diff__node_t *new_node;
+ svn_diff__node_t **node_ref;
+ svn_diff__node_t *parent;
+ int rv;
+
+ SVN_ERR_ASSERT(token);
+
+ parent = NULL;
+ node_ref = &tree->root[hash % SVN_DIFF__HASH_SIZE];
+
+ while (*node_ref != NULL)
+ {
+ parent = *node_ref;
+
+ rv = hash - parent->hash;
+ if (!rv)
+ SVN_ERR(vtable->token_compare(diff_baton, parent->token, token, &rv));
+
+ if (rv == 0)
+ {
+ /* Discard the previous token. This helps in cases where
+ * only recently read tokens are still in memory.
+ */
+ if (vtable->token_discard != NULL)
+ vtable->token_discard(diff_baton, parent->token);
+
+ parent->token = token;
+ *node = parent;
+
+ return SVN_NO_ERROR;
+ }
+ else if (rv > 0)
+ {
+ node_ref = &parent->left;
+ }
+ else
+ {
+ node_ref = &parent->right;
+ }
+ }
+
+ /* Create a new node */
+ new_node = apr_palloc(tree->pool, sizeof(*new_node));
+ new_node->parent = parent;
+ new_node->left = NULL;
+ new_node->right = NULL;
+ new_node->hash = hash;
+ new_node->token = token;
+ new_node->index = tree->node_count++;
+
+ *node = *node_ref = new_node;
+
+ return SVN_NO_ERROR;
+}
+
+
+/*
+ * Get all tokens from a datasource. Return the
+ * last item in the (circular) list.
+ */
+svn_error_t *
+svn_diff__get_tokens(svn_diff__position_t **position_list,
+ svn_diff__tree_t *tree,
+ void *diff_baton,
+ const svn_diff_fns2_t *vtable,
+ svn_diff_datasource_e datasource,
+ apr_off_t prefix_lines,
+ apr_pool_t *pool)
+{
+ svn_diff__position_t *start_position;
+ svn_diff__position_t *position = NULL;
+ svn_diff__position_t **position_ref;
+ svn_diff__node_t *node;
+ void *token;
+ apr_off_t offset;
+ apr_uint32_t hash;
+
+ *position_list = NULL;
+
+ position_ref = &start_position;
+ offset = prefix_lines;
+ hash = 0; /* The callback fn doesn't need to touch it per se */
+ while (1)
+ {
+ SVN_ERR(vtable->datasource_get_next_token(&hash, &token,
+ diff_baton, datasource));
+ if (token == NULL)
+ break;
+
+ offset++;
+ SVN_ERR(tree_insert_token(&node, tree, diff_baton, vtable, hash, token));
+
+ /* Create a new position */
+ position = apr_palloc(pool, sizeof(*position));
+ position->next = NULL;
+ position->token_index = node->index;
+ position->offset = offset;
+
+ *position_ref = position;
+ position_ref = &position->next;
+ }
+
+ *position_ref = start_position;
+
+ SVN_ERR(vtable->datasource_close(diff_baton, datasource));
+
+ *position_list = position;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_diff/util.c b/contrib/subversion/subversion/libsvn_diff/util.c
new file mode 100644
index 0000000..9e1f411
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_diff/util.c
@@ -0,0 +1,591 @@
+/*
+ * util.c : routines for doing diffs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr.h>
+#include <apr_general.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "svn_error.h"
+#include "svn_diff.h"
+#include "svn_types.h"
+#include "svn_ctype.h"
+#include "svn_utf.h"
+#include "svn_version.h"
+
+#include "private/svn_diff_private.h"
+#include "diff.h"
+
+#include "svn_private_config.h"
+
+
+svn_boolean_t
+svn_diff_contains_conflicts(svn_diff_t *diff)
+{
+ while (diff != NULL)
+ {
+ if (diff->type == svn_diff__type_conflict)
+ {
+ return TRUE;
+ }
+
+ diff = diff->next;
+ }
+
+ return FALSE;
+}
+
+svn_boolean_t
+svn_diff_contains_diffs(svn_diff_t *diff)
+{
+ while (diff != NULL)
+ {
+ if (diff->type != svn_diff__type_common)
+ {
+ return TRUE;
+ }
+
+ diff = diff->next;
+ }
+
+ return FALSE;
+}
+
+svn_error_t *
+svn_diff_output(svn_diff_t *diff,
+ void *output_baton,
+ const svn_diff_output_fns_t *vtable)
+{
+ svn_error_t *(*output_fn)(void *,
+ apr_off_t, apr_off_t,
+ apr_off_t, apr_off_t,
+ apr_off_t, apr_off_t);
+
+ while (diff != NULL)
+ {
+ switch (diff->type)
+ {
+ case svn_diff__type_common:
+ output_fn = vtable->output_common;
+ break;
+
+ case svn_diff__type_diff_common:
+ output_fn = vtable->output_diff_common;
+ break;
+
+ case svn_diff__type_diff_modified:
+ output_fn = vtable->output_diff_modified;
+ break;
+
+ case svn_diff__type_diff_latest:
+ output_fn = vtable->output_diff_latest;
+ break;
+
+ case svn_diff__type_conflict:
+ output_fn = NULL;
+ if (vtable->output_conflict != NULL)
+ {
+ SVN_ERR(vtable->output_conflict(output_baton,
+ diff->original_start, diff->original_length,
+ diff->modified_start, diff->modified_length,
+ diff->latest_start, diff->latest_length,
+ diff->resolved_diff));
+ }
+ break;
+
+ default:
+ output_fn = NULL;
+ break;
+ }
+
+ if (output_fn != NULL)
+ {
+ SVN_ERR(output_fn(output_baton,
+ diff->original_start, diff->original_length,
+ diff->modified_start, diff->modified_length,
+ diff->latest_start, diff->latest_length));
+ }
+
+ diff = diff->next;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+void
+svn_diff__normalize_buffer(char **tgt,
+ apr_off_t *lengthp,
+ svn_diff__normalize_state_t *statep,
+ const char *buf,
+ const svn_diff_file_options_t *opts)
+{
+ /* Variables for looping through BUF */
+ const char *curp, *endp;
+
+ /* Variable to record normalizing state */
+ svn_diff__normalize_state_t state = *statep;
+
+ /* Variables to track what needs copying into the target buffer */
+ const char *start = buf;
+ apr_size_t include_len = 0;
+ svn_boolean_t last_skipped = FALSE; /* makes sure we set 'start' */
+
+ /* Variable to record the state of the target buffer */
+ char *tgt_newend = *tgt;
+
+ /* If this is a noop, then just get out of here. */
+ if (! opts->ignore_space && ! opts->ignore_eol_style)
+ {
+ *tgt = (char *)buf;
+ return;
+ }
+
+
+ /* It only took me forever to get this routine right,
+ so here my thoughts go:
+
+ Below, we loop through the data, doing 2 things:
+
+ - Normalizing
+ - Copying other data
+
+ The routine tries its hardest *not* to copy data, but instead
+ returning a pointer into already normalized existing data.
+
+ To this end, a block 'other data' shouldn't be copied when found,
+ but only as soon as it can't be returned in-place.
+
+ On a character level, there are 3 possible operations:
+
+ - Skip the character (don't include in the normalized data)
+ - Include the character (do include in the normalizad data)
+ - Include as another character
+ This is essentially the same as skipping the current character
+ and inserting a given character in the output data.
+
+ The macros below (SKIP, INCLUDE and INCLUDE_AS) are defined to
+ handle the character based operations. The macros themselves
+ collect character level data into blocks.
+
+ At all times designate the START, INCLUDED_LEN and CURP pointers
+ an included and and skipped block like this:
+
+ [ start, start + included_len ) [ start + included_len, curp )
+ INCLUDED EXCLUDED
+
+ When the routine flips from skipping to including, the last
+ included block has to be flushed to the output buffer.
+ */
+
+ /* Going from including to skipping; only schedules the current
+ included section for flushing.
+ Also, simply chop off the character if it's the first in the buffer,
+ so we can possibly just return the remainder of the buffer */
+#define SKIP \
+ do { \
+ if (start == curp) \
+ ++start; \
+ last_skipped = TRUE; \
+ } while (0)
+
+#define INCLUDE \
+ do { \
+ if (last_skipped) \
+ COPY_INCLUDED_SECTION; \
+ ++include_len; \
+ last_skipped = FALSE; \
+ } while (0)
+
+#define COPY_INCLUDED_SECTION \
+ do { \
+ if (include_len > 0) \
+ { \
+ memmove(tgt_newend, start, include_len); \
+ tgt_newend += include_len; \
+ include_len = 0; \
+ } \
+ start = curp; \
+ } while (0)
+
+ /* Include the current character as character X.
+ If the current character already *is* X, add it to the
+ currently included region, increasing chances for consecutive
+ fully normalized blocks. */
+#define INCLUDE_AS(x) \
+ do { \
+ if (*curp == (x)) \
+ INCLUDE; \
+ else \
+ { \
+ INSERT((x)); \
+ SKIP; \
+ } \
+ } while (0)
+
+ /* Insert character X in the output buffer */
+#define INSERT(x) \
+ do { \
+ COPY_INCLUDED_SECTION; \
+ *tgt_newend++ = (x); \
+ } while (0)
+
+ for (curp = buf, endp = buf + *lengthp; curp != endp; ++curp)
+ {
+ switch (*curp)
+ {
+ case '\r':
+ if (opts->ignore_eol_style)
+ INCLUDE_AS('\n');
+ else
+ INCLUDE;
+ state = svn_diff__normalize_state_cr;
+ break;
+
+ case '\n':
+ if (state == svn_diff__normalize_state_cr
+ && opts->ignore_eol_style)
+ SKIP;
+ else
+ INCLUDE;
+ state = svn_diff__normalize_state_normal;
+ break;
+
+ default:
+ if (svn_ctype_isspace(*curp)
+ && opts->ignore_space != svn_diff_file_ignore_space_none)
+ {
+ /* Whitespace but not '\r' or '\n' */
+ if (state != svn_diff__normalize_state_whitespace
+ && opts->ignore_space
+ == svn_diff_file_ignore_space_change)
+ /*### If we can postpone insertion of the space
+ until the next non-whitespace character,
+ we have a potential of reducing the number of copies:
+ If this space is followed by more spaces,
+ this will cause a block-copy.
+ If the next non-space block is considered normalized
+ *and* preceded by a space, we can take advantage of that. */
+ /* Note, the above optimization applies to 90% of the source
+ lines in our own code, since it (generally) doesn't use
+ more than one space per blank section, except for the
+ beginning of a line. */
+ INCLUDE_AS(' ');
+ else
+ SKIP;
+ state = svn_diff__normalize_state_whitespace;
+ }
+ else
+ {
+ /* Non-whitespace character, or whitespace character in
+ svn_diff_file_ignore_space_none mode. */
+ INCLUDE;
+ state = svn_diff__normalize_state_normal;
+ }
+ }
+ }
+
+ /* If we're not in whitespace, flush the last chunk of data.
+ * Note that this will work correctly when this is the last chunk of the
+ * file:
+ * * If there is an eol, it will either have been output when we entered
+ * the state_cr, or it will be output now.
+ * * If there is no eol and we're not in whitespace, then we just output
+ * everything below.
+ * * If there's no eol and we are in whitespace, we want to ignore
+ * whitespace unconditionally. */
+
+ if (*tgt == tgt_newend)
+ {
+ /* we haven't copied any data in to *tgt and our chunk consists
+ only of one block of (already normalized) data.
+ Just return the block. */
+ *tgt = (char *)start;
+ *lengthp = include_len;
+ }
+ else
+ {
+ COPY_INCLUDED_SECTION;
+ *lengthp = tgt_newend - *tgt;
+ }
+
+ *statep = state;
+
+#undef SKIP
+#undef INCLUDE
+#undef INCLUDE_AS
+#undef INSERT
+#undef COPY_INCLUDED_SECTION
+}
+
+svn_error_t *
+svn_diff__unified_append_no_newline_msg(svn_stringbuf_t *stringbuf,
+ const char *header_encoding,
+ apr_pool_t *scratch_pool)
+{
+ const char *out_str;
+
+ SVN_ERR(svn_utf_cstring_from_utf8_ex2(
+ &out_str,
+ APR_EOL_STR
+ SVN_DIFF__NO_NEWLINE_AT_END_OF_FILE APR_EOL_STR,
+ header_encoding, scratch_pool));
+ svn_stringbuf_appendcstr(stringbuf, out_str);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff__unified_write_hunk_header(svn_stream_t *output_stream,
+ const char *header_encoding,
+ const char *hunk_delimiter,
+ apr_off_t old_start,
+ apr_off_t old_length,
+ apr_off_t new_start,
+ apr_off_t new_length,
+ const char *hunk_extra_context,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding,
+ scratch_pool,
+ "%s -%" APR_OFF_T_FMT,
+ hunk_delimiter, old_start));
+ /* If the hunk length is 1, suppress the number of lines in the hunk
+ * (it is 1 implicitly) */
+ if (old_length != 1)
+ {
+ SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding,
+ scratch_pool,
+ ",%" APR_OFF_T_FMT, old_length));
+ }
+
+ SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding,
+ scratch_pool,
+ " +%" APR_OFF_T_FMT, new_start));
+ if (new_length != 1)
+ {
+ SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding,
+ scratch_pool,
+ ",%" APR_OFF_T_FMT, new_length));
+ }
+
+ if (hunk_extra_context == NULL)
+ hunk_extra_context = "";
+ SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding,
+ scratch_pool,
+ " %s%s%s" APR_EOL_STR,
+ hunk_delimiter,
+ hunk_extra_context[0] ? " " : "",
+ hunk_extra_context));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff__unidiff_write_header(svn_stream_t *output_stream,
+ const char *header_encoding,
+ const char *old_header,
+ const char *new_header,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding,
+ scratch_pool,
+ "--- %s" APR_EOL_STR
+ "+++ %s" APR_EOL_STR,
+ old_header,
+ new_header));
+ return SVN_NO_ERROR;
+}
+
+/* A helper function for display_prop_diffs. Output the differences between
+ the mergeinfo stored in ORIG_MERGEINFO_VAL and NEW_MERGEINFO_VAL in a
+ human-readable form to OUTSTREAM, using ENCODING. Use POOL for temporary
+ allocations. */
+static svn_error_t *
+display_mergeinfo_diff(const char *old_mergeinfo_val,
+ const char *new_mergeinfo_val,
+ const char *encoding,
+ svn_stream_t *outstream,
+ apr_pool_t *pool)
+{
+ apr_hash_t *old_mergeinfo_hash, *new_mergeinfo_hash, *added, *deleted;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_hash_index_t *hi;
+
+ if (old_mergeinfo_val)
+ SVN_ERR(svn_mergeinfo_parse(&old_mergeinfo_hash, old_mergeinfo_val, pool));
+ else
+ old_mergeinfo_hash = NULL;
+
+ if (new_mergeinfo_val)
+ SVN_ERR(svn_mergeinfo_parse(&new_mergeinfo_hash, new_mergeinfo_val, pool));
+ else
+ new_mergeinfo_hash = NULL;
+
+ SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, old_mergeinfo_hash,
+ new_mergeinfo_hash,
+ TRUE, pool, pool));
+
+ for (hi = apr_hash_first(pool, deleted);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *from_path = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *merge_revarray = svn__apr_hash_index_val(hi);
+ svn_string_t *merge_revstr;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_rangelist_to_string(&merge_revstr, merge_revarray,
+ iterpool));
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, iterpool,
+ _(" Reverse-merged %s:r%s%s"),
+ from_path, merge_revstr->data,
+ APR_EOL_STR));
+ }
+
+ for (hi = apr_hash_first(pool, added);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *from_path = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *merge_revarray = svn__apr_hash_index_val(hi);
+ svn_string_t *merge_revstr;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_rangelist_to_string(&merge_revstr, merge_revarray,
+ iterpool));
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, iterpool,
+ _(" Merged %s:r%s%s"),
+ from_path, merge_revstr->data,
+ APR_EOL_STR));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_diff__display_prop_diffs(svn_stream_t *outstream,
+ const char *encoding,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ svn_boolean_t pretty_print_mergeinfo,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ for (i = 0; i < propchanges->nelts; i++)
+ {
+ const char *action;
+ const svn_string_t *original_value;
+ const svn_prop_t *propchange
+ = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
+
+ if (original_props)
+ original_value = svn_hash_gets(original_props, propchange->name);
+ else
+ original_value = NULL;
+
+ /* If the property doesn't exist on either side, or if it exists
+ with the same value, skip it. This can happen if the client is
+ hitting an old mod_dav_svn server that doesn't understand the
+ "send-all" REPORT style. */
+ if ((! (original_value || propchange->value))
+ || (original_value && propchange->value
+ && svn_string_compare(original_value, propchange->value)))
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ if (! original_value)
+ action = "Added";
+ else if (! propchange->value)
+ action = "Deleted";
+ else
+ action = "Modified";
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, iterpool,
+ "%s: %s%s", action,
+ propchange->name, APR_EOL_STR));
+
+ if (pretty_print_mergeinfo
+ && strcmp(propchange->name, SVN_PROP_MERGEINFO) == 0)
+ {
+ const char *orig = original_value ? original_value->data : NULL;
+ const char *val = propchange->value ? propchange->value->data : NULL;
+ svn_error_t *err = display_mergeinfo_diff(orig, val, encoding,
+ outstream, iterpool);
+
+ /* Issue #3896: If we can't pretty-print mergeinfo differences
+ because invalid mergeinfo is present, then don't let the diff
+ fail, just print the diff as any other property. */
+ if (err && err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ svn_error_clear(err);
+ }
+ else
+ {
+ SVN_ERR(err);
+ continue;
+ }
+ }
+
+ {
+ svn_diff_t *diff;
+ svn_diff_file_options_t options = { 0 };
+ const svn_string_t *orig
+ = original_value ? original_value
+ : svn_string_create_empty(iterpool);
+ const svn_string_t *val
+ = propchange->value ? propchange->value
+ : svn_string_create_empty(iterpool);
+
+ SVN_ERR(svn_diff_mem_string_diff(&diff, orig, val, &options,
+ iterpool));
+
+ /* UNIX patch will try to apply a diff even if the diff header
+ * is missing. It tries to be helpful by asking the user for a
+ * target filename when it can't determine the target filename
+ * from the diff header. But there usually are no files which
+ * UNIX patch could apply the property diff to, so we use "##"
+ * instead of "@@" as the default hunk delimiter for property diffs.
+ * We also supress the diff header. */
+ SVN_ERR(svn_diff_mem_string_output_unified2(
+ outstream, diff, FALSE /* no header */, "##", NULL, NULL,
+ encoding, orig, val, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return the library version number. */
+const svn_version_t *
+svn_diff_version(void)
+{
+ SVN_VERSION_BODY;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs/access.c b/contrib/subversion/subversion/libsvn_fs/access.c
new file mode 100644
index 0000000..9918be4
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs/access.c
@@ -0,0 +1,105 @@
+/*
+ * access.c: shared code to manipulate svn_fs_access_t objects
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_hash.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_fs.h"
+#include "private/svn_fs_private.h"
+
+#include "fs-loader.h"
+
+
+
+svn_error_t *
+svn_fs_create_access(svn_fs_access_t **access_ctx,
+ const char *username,
+ apr_pool_t *pool)
+{
+ svn_fs_access_t *ac;
+
+ SVN_ERR_ASSERT(username != NULL);
+
+ ac = apr_pcalloc(pool, sizeof(*ac));
+ ac->username = apr_pstrdup(pool, username);
+ ac->lock_tokens = apr_hash_make(pool);
+ *access_ctx = ac;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_set_access(svn_fs_t *fs,
+ svn_fs_access_t *access_ctx)
+{
+ fs->access_ctx = access_ctx;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_get_access(svn_fs_access_t **access_ctx,
+ svn_fs_t *fs)
+{
+ *access_ctx = fs->access_ctx;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_access_get_username(const char **username,
+ svn_fs_access_t *access_ctx)
+{
+ *username = access_ctx->username;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_access_add_lock_token2(svn_fs_access_t *access_ctx,
+ const char *path,
+ const char *token)
+{
+ svn_hash_sets(access_ctx->lock_tokens, token, path);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_access_add_lock_token(svn_fs_access_t *access_ctx,
+ const char *token)
+{
+ return svn_fs_access_add_lock_token2(access_ctx, (const char *) 1, token);
+}
+
+apr_hash_t *
+svn_fs__access_get_lock_tokens(svn_fs_access_t *access_ctx)
+{
+ return access_ctx->lock_tokens;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs/editor.c b/contrib/subversion/subversion/libsvn_fs/editor.c
new file mode 100644
index 0000000..a75f210
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs/editor.c
@@ -0,0 +1,850 @@
+/*
+ * editor.c: Editor for modifying FS transactions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_fs.h"
+#include "svn_props.h"
+#include "svn_path.h"
+
+#include "svn_private_config.h"
+
+#include "fs-loader.h"
+
+#include "private/svn_fspath.h"
+#include "private/svn_fs_private.h"
+#include "private/svn_editor.h"
+
+
+struct edit_baton {
+ /* The transaction associated with this editor. */
+ svn_fs_txn_t *txn;
+
+ /* Has this editor been completed? */
+ svn_boolean_t completed;
+
+ /* We sometimes need the cancellation beyond what svn_editor_t provides */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ /* The pool that the txn lives within. When we create a ROOT, it will
+ be allocated within a subpool of this. The root will be closed in
+ complete/abort and that subpool will be destroyed.
+
+ This pool SHOULD NOT be used for any allocations. */
+ apr_pool_t *txn_pool;
+
+ /* This is the root from the txn. Use get_root() to fetch/create this
+ member as appropriate. */
+ svn_fs_root_t *root;
+};
+
+#define FSPATH(relpath, pool) apr_pstrcat(pool, "/", relpath, NULL)
+#define UNUSED(x) ((void)(x))
+
+
+static svn_error_t *
+get_root(svn_fs_root_t **root,
+ struct edit_baton *eb)
+{
+ if (eb->root == NULL)
+ SVN_ERR(svn_fs_txn_root(&eb->root, eb->txn, eb->txn_pool));
+ *root = eb->root;
+ return SVN_NO_ERROR;
+}
+
+
+/* Apply each property in PROPS to the node at FSPATH in ROOT. */
+static svn_error_t *
+add_new_props(svn_fs_root_t *root,
+ const char *fspath,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+
+ /* ### it would be nice to have svn_fs_set_node_props(). but since we
+ ### don't... add each property to the node. this is a new node, so
+ ### we don't need to worry about deleting props. just adding. */
+
+ for (hi = apr_hash_first(scratch_pool, props); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_string_t *value = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_fs_change_node_prop(root, fspath, name, value, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+alter_props(svn_fs_root_t *root,
+ const char *fspath,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *old_props;
+ apr_array_header_t *propdiffs;
+ int i;
+
+ SVN_ERR(svn_fs_node_proplist(&old_props, root, fspath, scratch_pool));
+
+ SVN_ERR(svn_prop_diffs(&propdiffs, props, old_props, scratch_pool));
+
+ for (i = 0; i < propdiffs->nelts; ++i)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
+
+ svn_pool_clear(iterpool);
+
+ /* Add, change, or delete properties. */
+ SVN_ERR(svn_fs_change_node_prop(root, fspath, prop->name, prop->value,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+set_text(svn_fs_root_t *root,
+ const char *fspath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *fs_contents;
+
+ /* ### We probably don't have an MD5 checksum, so no digest is available
+ ### for svn_fs_apply_text() to validate. It would be nice to have an
+ ### FS API that takes our CHECKSUM/CONTENTS pair (and PROPS!). */
+ SVN_ERR(svn_fs_apply_text(&fs_contents, root, fspath,
+ NULL /* result_checksum */,
+ scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, fs_contents,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The caller wants to modify REVISION of FSPATH. Is that allowed? */
+static svn_error_t *
+can_modify(svn_fs_root_t *txn_root,
+ const char *fspath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_revnum_t created_rev;
+
+ /* Out-of-dateness check: compare the created-rev of the node
+ in the txn against the created-rev of FSPATH. */
+ SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, fspath,
+ scratch_pool));
+
+ /* Uncommitted nodes (eg. a descendent of a copy/move/rotate destination)
+ have no (committed) revision number. Let the caller go ahead and
+ modify these nodes.
+
+ Note: strictly speaking, they might be performing an "illegal" edit
+ in certain cases, but let's just assume they're Good Little Boys.
+
+ If CREATED_REV is invalid, that means it's already mutable in the
+ txn, which means it has already passed this out-of-dateness check.
+ (Usually, this happens when looking at a parent directory of an
+ already-modified node) */
+ if (!SVN_IS_VALID_REVNUM(created_rev))
+ return SVN_NO_ERROR;
+
+ /* If the node is immutable (has a revision), then the caller should
+ have supplied a valid revision number [that they expect to change].
+ The checks further below will determine the out-of-dateness of the
+ specified revision. */
+ /* ### ugh. descendents of copy/move/rotate destinations carry along
+ ### their original immutable state and (thus) a valid CREATED_REV.
+ ### but they are logically uncommitted, so the caller will pass
+ ### SVN_INVALID_REVNUM. (technically, the caller could provide
+ ### ORIGINAL_REV, but that is semantically incorrect for the Ev2
+ ### API).
+ ###
+ ### for now, we will assume the caller knows what they are doing
+ ### and an invalid revision implies such a descendent. in the
+ ### future, we could examine the ancestor chain looking for a
+ ### copy/move/rotate-here node and allow the modification (and the
+ ### converse: if no such ancestor, the caller must specify the
+ ### correct/intended revision to modify).
+ */
+#if 1
+ if (!SVN_IS_VALID_REVNUM(revision))
+ return SVN_NO_ERROR;
+#else
+ if (!SVN_IS_VALID_REVNUM(revision))
+ /* ### use a custom error code? */
+ return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Revision for modifying '%s' is required"),
+ fspath);
+#endif
+
+ if (revision < created_rev)
+ {
+ /* We asked to change a node that is *older* than what we found
+ in the transaction. The client is out of date. */
+ return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("'%s' is out of date; try updating"),
+ fspath);
+ }
+
+ if (revision > created_rev)
+ {
+ /* We asked to change a node that is *newer* than what we found
+ in the transaction. Given that the transaction was based off
+ of 'youngest', then either:
+ - the caller asked to modify a future node
+ - the caller has committed more revisions since this txn
+ was constructed, and is asking to modify a node in one
+ of those new revisions.
+ In either case, the node may not have changed in those new
+ revisions; use the node's ID to determine this case. */
+ const svn_fs_id_t *txn_noderev_id;
+ svn_fs_root_t *rev_root;
+ const svn_fs_id_t *new_noderev_id;
+
+ /* The ID of the node that we would be modifying in the txn */
+ SVN_ERR(svn_fs_node_id(&txn_noderev_id, txn_root, fspath,
+ scratch_pool));
+
+ /* Get the ID from the future/new revision. */
+ SVN_ERR(svn_fs_revision_root(&rev_root, svn_fs_root_fs(txn_root),
+ revision, scratch_pool));
+ SVN_ERR(svn_fs_node_id(&new_noderev_id, rev_root, fspath,
+ scratch_pool));
+ svn_fs_close_root(rev_root);
+
+ /* Has the target node changed in the future? */
+ if (svn_fs_compare_ids(txn_noderev_id, new_noderev_id) != 0)
+ {
+ /* Restarting the commit will base the txn on the future/new
+ revision, allowing the modification at REVISION. */
+ /* ### use a custom error code */
+ return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
+ _("'%s' has been modified since the "
+ "commit began (restart the commit)"),
+ fspath);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Can we create a node at FSPATH in TXN_ROOT? If something already exists
+ at that path, then the client MAY be out of date. We then have to see if
+ the path was created/modified in this transaction. IOW, it is new and
+ can be replaced without problem.
+
+ Note: the editor protocol disallows double-modifications. This is to
+ ensure somebody does not accidentally overwrite another file due to
+ being out-of-date. */
+static svn_error_t *
+can_create(svn_fs_root_t *txn_root,
+ const char *fspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ const char *cur_fspath;
+
+ SVN_ERR(svn_fs_check_path(&kind, txn_root, fspath, scratch_pool));
+ if (kind == svn_node_none)
+ return SVN_NO_ERROR;
+
+ /* ### I'm not sure if this works perfectly. We might have an ancestor
+ ### that was modified as a result of a change on a cousin. We might
+ ### misinterpret that as a *-here node which brought along this
+ ### child. Need to write a test to verify. We may also be able to
+ ### test the ancestor to determine if it has been *-here in this
+ ### txn, or just a simple modification. */
+
+ /* Are any of the parents copied/moved/rotated-here? */
+ for (cur_fspath = fspath;
+ strlen(cur_fspath) > 1; /* not the root */
+ cur_fspath = svn_fspath__dirname(cur_fspath, scratch_pool))
+ {
+ svn_revnum_t created_rev;
+
+ SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, cur_fspath,
+ scratch_pool));
+ if (!SVN_IS_VALID_REVNUM(created_rev))
+ {
+ /* The node has no created revision, meaning it is uncommitted.
+ Thus, it was created in this transaction, or it has already
+ been modified in some way (implying it has already passed a
+ modification check. */
+ /* ### verify the node has been *-here ?? */
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("'%s' already exists, so may be out"
+ " of date; try updating"),
+ fspath);
+}
+
+
+/* This implements svn_editor_cb_add_directory_t */
+static svn_error_t *
+add_directory_cb(void *baton,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ const char *fspath = FSPATH(relpath, scratch_pool);
+ svn_fs_root_t *root;
+
+ /* Note: we ignore CHILDREN. We have no "incomplete" state to worry about,
+ so we don't need to be aware of what children will be created. */
+
+ SVN_ERR(get_root(&root, eb));
+
+ if (SVN_IS_VALID_REVNUM(replaces_rev))
+ {
+ SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
+ SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(can_create(root, fspath, scratch_pool));
+ }
+
+ SVN_ERR(svn_fs_make_dir(root, fspath, scratch_pool));
+ SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_add_file_t */
+static svn_error_t *
+add_file_cb(void *baton,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ const char *fspath = FSPATH(relpath, scratch_pool);
+ svn_fs_root_t *root;
+
+ SVN_ERR(get_root(&root, eb));
+
+ if (SVN_IS_VALID_REVNUM(replaces_rev))
+ {
+ SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
+ SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(can_create(root, fspath, scratch_pool));
+ }
+
+ SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool));
+
+ SVN_ERR(set_text(root, fspath, checksum, contents,
+ eb->cancel_func, eb->cancel_baton, scratch_pool));
+ SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_add_symlink_t */
+static svn_error_t *
+add_symlink_cb(void *baton,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ const char *fspath = FSPATH(relpath, scratch_pool);
+ svn_fs_root_t *root;
+
+ SVN_ERR(get_root(&root, eb));
+
+ if (SVN_IS_VALID_REVNUM(replaces_rev))
+ {
+ SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
+ SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(can_create(root, fspath, scratch_pool));
+ }
+
+ /* ### we probably need to construct a file with specific contents
+ ### (until the FS grows some symlink APIs) */
+#if 0
+ SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool));
+ SVN_ERR(svn_fs_apply_text(&fs_contents, root, fspath,
+ NULL /* result_checksum */,
+ scratch_pool));
+ /* ### SVN_ERR(svn_stream_printf(fs_contents, ..., scratch_pool)); */
+ apr_hash_set(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING,
+ SVN_PROP_SPECIAL_VALUE);
+
+ SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
+#endif
+
+ SVN__NOT_IMPLEMENTED();
+}
+
+
+/* This implements svn_editor_cb_add_absent_t */
+static svn_error_t *
+add_absent_cb(void *baton,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ /* This is a programming error. Code should not attempt to create these
+ kinds of nodes within the FS. */
+ /* ### use a custom error code */
+ return svn_error_create(
+ SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The filesystem does not support 'absent' nodes"));
+}
+
+
+/* This implements svn_editor_cb_alter_directory_t */
+static svn_error_t *
+alter_directory_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ const char *fspath = FSPATH(relpath, scratch_pool);
+ svn_fs_root_t *root;
+
+ /* Note: we ignore CHILDREN. We have no "incomplete" state to worry about,
+ so we don't need to be aware of what children will be created. */
+
+ SVN_ERR(get_root(&root, eb));
+ SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
+
+ if (props)
+ SVN_ERR(alter_props(root, fspath, props, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_alter_file_t */
+static svn_error_t *
+alter_file_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ const char *fspath = FSPATH(relpath, scratch_pool);
+ svn_fs_root_t *root;
+
+ SVN_ERR(get_root(&root, eb));
+ SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
+
+ if (contents != NULL)
+ {
+ SVN_ERR_ASSERT(checksum != NULL);
+ SVN_ERR(set_text(root, fspath, checksum, contents,
+ eb->cancel_func, eb->cancel_baton, scratch_pool));
+ }
+
+ if (props != NULL)
+ {
+ SVN_ERR(alter_props(root, fspath, props, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_alter_symlink_t */
+static svn_error_t *
+alter_symlink_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+
+ UNUSED(eb); SVN__NOT_IMPLEMENTED();
+}
+
+
+/* This implements svn_editor_cb_delete_t */
+static svn_error_t *
+delete_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ const char *fspath = FSPATH(relpath, scratch_pool);
+ svn_fs_root_t *root;
+
+ SVN_ERR(get_root(&root, eb));
+ SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
+
+ SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_copy_t */
+static svn_error_t *
+copy_cb(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ const char *src_fspath = FSPATH(src_relpath, scratch_pool);
+ const char *dst_fspath = FSPATH(dst_relpath, scratch_pool);
+ svn_fs_root_t *root;
+ svn_fs_root_t *src_root;
+
+ SVN_ERR(get_root(&root, eb));
+
+ /* Check if we can we replace the maybe-specified destination (revision). */
+ if (SVN_IS_VALID_REVNUM(replaces_rev))
+ {
+ SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
+ SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(can_create(root, dst_fspath, scratch_pool));
+ }
+
+ SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
+ scratch_pool));
+ SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
+ svn_fs_close_root(src_root);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_move_t */
+static svn_error_t *
+move_cb(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ const char *src_fspath = FSPATH(src_relpath, scratch_pool);
+ const char *dst_fspath = FSPATH(dst_relpath, scratch_pool);
+ svn_fs_root_t *root;
+ svn_fs_root_t *src_root;
+
+ SVN_ERR(get_root(&root, eb));
+
+ /* Check if we delete the specified source (revision), and can we replace
+ the maybe-specified destination (revision). */
+ SVN_ERR(can_modify(root, src_fspath, src_revision, scratch_pool));
+ if (SVN_IS_VALID_REVNUM(replaces_rev))
+ {
+ SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
+ SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(can_create(root, dst_fspath, scratch_pool));
+ }
+
+ /* ### would be nice to have svn_fs_move() */
+
+ /* Copy the src to the dst. */
+ SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
+ scratch_pool));
+ SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
+ svn_fs_close_root(src_root);
+
+ /* Notice: we're deleting the src repos path from the dst root. */
+ SVN_ERR(svn_fs_delete(root, src_fspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_rotate_t */
+static svn_error_t *
+rotate_cb(void *baton,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+
+ UNUSED(eb); SVN__NOT_IMPLEMENTED();
+}
+
+
+/* This implements svn_editor_cb_complete_t */
+static svn_error_t *
+complete_cb(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+
+ /* Watch out for a following call to svn_fs_editor_commit(). Note that
+ we are likely here because svn_fs_editor_commit() was called, and it
+ invoked svn_editor_complete(). */
+ eb->completed = TRUE;
+
+ if (eb->root != NULL)
+ {
+ svn_fs_close_root(eb->root);
+ eb->root = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_abort_t */
+static svn_error_t *
+abort_cb(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_error_t *err;
+
+ /* Don't allow a following call to svn_fs_editor_commit(). */
+ eb->completed = TRUE;
+
+ if (eb->root != NULL)
+ {
+ svn_fs_close_root(eb->root);
+ eb->root = NULL;
+ }
+
+ /* ### should we examine the error and attempt svn_fs_purge_txn() ? */
+ err = svn_fs_abort_txn(eb->txn, scratch_pool);
+
+ /* For safety, clear the now-useless txn. */
+ eb->txn = NULL;
+
+ return svn_error_trace(err);
+}
+
+
+static svn_error_t *
+make_editor(svn_editor_t **editor,
+ svn_fs_txn_t *txn,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ static const svn_editor_cb_many_t editor_cbs = {
+ add_directory_cb,
+ add_file_cb,
+ add_symlink_cb,
+ add_absent_cb,
+ alter_directory_cb,
+ alter_file_cb,
+ alter_symlink_cb,
+ delete_cb,
+ copy_cb,
+ move_cb,
+ rotate_cb,
+ complete_cb,
+ abort_cb
+ };
+ struct edit_baton *eb = apr_pcalloc(result_pool, sizeof(*eb));
+
+ eb->txn = txn;
+ eb->cancel_func = cancel_func;
+ eb->cancel_baton = cancel_baton;
+ eb->txn_pool = result_pool;
+
+ SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs__editor_create(svn_editor_t **editor,
+ const char **txn_name,
+ svn_fs_t *fs,
+ apr_uint32_t flags,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_revnum_t revision;
+ svn_fs_txn_t *txn;
+
+ SVN_ERR(svn_fs_youngest_rev(&revision, fs, scratch_pool));
+ SVN_ERR(svn_fs_begin_txn2(&txn, fs, revision, flags, result_pool));
+ SVN_ERR(svn_fs_txn_name(txn_name, txn, result_pool));
+ return svn_error_trace(make_editor(editor, txn,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_fs__editor_create_for(svn_editor_t **editor,
+ svn_fs_t *fs,
+ const char *txn_name,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_txn_t *txn;
+
+ SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, result_pool));
+ return svn_error_trace(make_editor(editor, txn,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_fs__editor_commit(svn_revnum_t *revision,
+ svn_error_t **post_commit_err,
+ const char **conflict_path,
+ svn_editor_t *editor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = svn_editor_get_baton(editor);
+ const char *inner_conflict_path;
+ svn_error_t *err = NULL;
+
+ /* make sure people are using the correct sequencing. */
+ if (eb->completed)
+ return svn_error_create(SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION,
+ NULL, NULL);
+
+ *revision = SVN_INVALID_REVNUM;
+ *post_commit_err = NULL;
+ *conflict_path = NULL;
+
+ /* Clean up internal resources (eg. eb->root). This also allows the
+ editor infrastructure to know this editor is "complete". */
+ err = svn_editor_complete(editor);
+
+ /* Note: docco for svn_fs_commit_txn() states that CONFLICT_PATH will
+ be allocated in the txn's pool. But it lies. Regardless, we want
+ it placed into RESULT_POOL. */
+
+ if (!err)
+ err = svn_fs_commit_txn(&inner_conflict_path,
+ revision,
+ eb->txn,
+ scratch_pool);
+ if (SVN_IS_VALID_REVNUM(*revision))
+ {
+ if (err)
+ {
+ /* Case 3. ERR is a post-commit (cleanup) error. */
+
+ /* Pass responsibility via POST_COMMIT_ERR. */
+ *post_commit_err = err;
+ err = SVN_NO_ERROR;
+ }
+ /* else: Case 1. */
+ }
+ else
+ {
+ SVN_ERR_ASSERT(err != NULL);
+ if (err->apr_err == SVN_ERR_FS_CONFLICT)
+ {
+ /* Case 2. */
+
+ /* Copy this into the correct pool (see note above). */
+ *conflict_path = apr_pstrdup(result_pool, inner_conflict_path);
+
+ /* Return sucess. The caller should inspect CONFLICT_PATH to
+ determine this particular case. */
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ /* else: Case 4. */
+
+ /* Abort the TXN. Nobody wants to use it. */
+ /* ### should we examine the error and attempt svn_fs_purge_txn() ? */
+ err = svn_error_compose_create(
+ err,
+ svn_fs_abort_txn(eb->txn, scratch_pool));
+ }
+
+ /* For safety, clear the now-useless txn. */
+ eb->txn = NULL;
+
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_fs/fs-loader.c b/contrib/subversion/subversion/libsvn_fs/fs-loader.c
new file mode 100644
index 0000000..01d6ba1
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs/fs-loader.c
@@ -0,0 +1,1602 @@
+/*
+ * fs_loader.c: Front-end to the various FS back ends
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <string.h>
+#include <apr.h>
+#include <apr_hash.h>
+#include <apr_md5.h>
+#include <apr_thread_mutex.h>
+#include <apr_uuid.h>
+#include <apr_strings.h>
+
+#include "svn_hash.h"
+#include "svn_ctype.h"
+#include "svn_types.h"
+#include "svn_dso.h"
+#include "svn_version.h"
+#include "svn_fs.h"
+#include "svn_path.h"
+#include "svn_xml.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_private_config.h"
+
+#include "private/svn_fs_private.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_utf_private.h"
+#include "private/svn_mutex.h"
+#include "private/svn_subr_private.h"
+
+#include "fs-loader.h"
+
+/* This is defined by configure on platforms which use configure, but
+ we need to define a fallback for Windows. */
+#ifndef DEFAULT_FS_TYPE
+#define DEFAULT_FS_TYPE "fsfs"
+#endif
+
+#define FS_TYPE_FILENAME "fs-type"
+
+/* A pool common to all FS objects. See the documentation on the
+ open/create functions in fs-loader.h and for svn_fs_initialize(). */
+static apr_pool_t *common_pool;
+svn_mutex__t *common_pool_lock;
+
+
+/* --- Utility functions for the loader --- */
+
+struct fs_type_defn {
+ const char *fs_type;
+ const char *fsap_name;
+ fs_init_func_t initfunc;
+ struct fs_type_defn *next;
+};
+
+static struct fs_type_defn base_defn =
+ {
+ SVN_FS_TYPE_BDB, "base",
+#ifdef SVN_LIBSVN_FS_LINKS_FS_BASE
+ svn_fs_base__init,
+#else
+ NULL,
+#endif
+ NULL
+ };
+
+static struct fs_type_defn fsfs_defn =
+ {
+ SVN_FS_TYPE_FSFS, "fs",
+#ifdef SVN_LIBSVN_FS_LINKS_FS_FS
+ svn_fs_fs__init,
+#else
+ NULL,
+#endif
+ &base_defn
+ };
+
+static struct fs_type_defn *fs_modules = &fsfs_defn;
+
+
+static svn_error_t *
+load_module(fs_init_func_t *initfunc, const char *name, apr_pool_t *pool)
+{
+ *initfunc = NULL;
+
+#if defined(SVN_USE_DSO) && APR_HAS_DSO
+ {
+ apr_dso_handle_t *dso;
+ apr_dso_handle_sym_t symbol;
+ const char *libname;
+ const char *funcname;
+ apr_status_t status;
+ const char *p;
+
+ /* Demand a simple alphanumeric name so that the generated DSO
+ name is sensible. */
+ for (p = name; *p; ++p)
+ if (!svn_ctype_isalnum(*p))
+ return svn_error_createf(SVN_ERR_FS_UNKNOWN_FS_TYPE, NULL,
+ _("Invalid name for FS type '%s'"),
+ name);
+
+ libname = apr_psprintf(pool, "libsvn_fs_%s-%d.so.%d",
+ name, SVN_VER_MAJOR, SVN_SOVERSION);
+ funcname = apr_psprintf(pool, "svn_fs_%s__init", name);
+
+ /* Find/load the specified library. If we get an error, assume
+ the library doesn't exist. The library will be unloaded when
+ pool is destroyed. */
+ SVN_ERR(svn_dso_load(&dso, libname));
+ if (! dso)
+ return SVN_NO_ERROR;
+
+ /* find the initialization routine */
+ status = apr_dso_sym(&symbol, dso, funcname);
+ if (status)
+ return svn_error_wrap_apr(status, _("'%s' does not define '%s()'"),
+ libname, funcname);
+
+ *initfunc = (fs_init_func_t) symbol;
+ }
+#endif /* APR_HAS_DSO */
+
+ return SVN_NO_ERROR;
+}
+
+/* Fetch a library vtable by a pointer into the library definitions array. */
+static svn_error_t *
+get_library_vtable_direct(fs_library_vtable_t **vtable,
+ const struct fs_type_defn *fst,
+ apr_pool_t *pool)
+{
+ fs_init_func_t initfunc = NULL;
+ const svn_version_t *my_version = svn_fs_version();
+ const svn_version_t *fs_version;
+
+ initfunc = fst->initfunc;
+ if (! initfunc)
+ SVN_ERR(load_module(&initfunc, fst->fsap_name, pool));
+
+ if (! initfunc)
+ return svn_error_createf(SVN_ERR_FS_UNKNOWN_FS_TYPE, NULL,
+ _("Failed to load module for FS type '%s'"),
+ fst->fs_type);
+
+ {
+ /* Per our API compatibility rules, we cannot ensure that
+ svn_fs_initialize is called by the application. If not, we
+ cannot create the common pool and lock in a thread-safe fashion,
+ nor can we clean up the common pool if libsvn_fs is dynamically
+ unloaded. This function makes a best effort by creating the
+ common pool as a child of the global pool; the window of failure
+ due to thread collision is small. */
+ if (!common_pool)
+ SVN_ERR(svn_fs_initialize(NULL));
+
+ /* Invoke the FS module's initfunc function with the common
+ pool protected by a lock. */
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ initfunc(my_version, vtable, common_pool));
+ }
+ fs_version = (*vtable)->get_version();
+ if (!svn_ver_equal(my_version, fs_version))
+ return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
+ _("Mismatched FS module version for '%s':"
+ " found %d.%d.%d%s,"
+ " expected %d.%d.%d%s"),
+ fst->fs_type,
+ my_version->major, my_version->minor,
+ my_version->patch, my_version->tag,
+ fs_version->major, fs_version->minor,
+ fs_version->patch, fs_version->tag);
+ return SVN_NO_ERROR;
+}
+
+#if defined(SVN_USE_DSO) && APR_HAS_DSO
+/* Return *FST for the third party FS_TYPE */
+static svn_error_t *
+get_or_allocate_third(struct fs_type_defn **fst,
+ const char *fs_type)
+{
+ while (*fst)
+ {
+ if (strcmp(fs_type, (*fst)->fs_type) == 0)
+ return SVN_NO_ERROR;
+ fst = &(*fst)->next;
+ }
+
+ *fst = apr_palloc(common_pool, sizeof(struct fs_type_defn));
+ (*fst)->fs_type = apr_pstrdup(common_pool, fs_type);
+ (*fst)->fsap_name = (*fst)->fs_type;
+ (*fst)->initfunc = NULL;
+ (*fst)->next = NULL;
+
+ return SVN_NO_ERROR;
+}
+#endif
+
+/* Fetch a library vtable by FS type. */
+static svn_error_t *
+get_library_vtable(fs_library_vtable_t **vtable, const char *fs_type,
+ apr_pool_t *pool)
+{
+ struct fs_type_defn **fst = &fs_modules;
+ svn_boolean_t known = FALSE;
+
+ /* There are two FS module definitions known at compile time. We
+ want to check these without any locking overhead even when
+ dynamic third party modules are enabled. The third party modules
+ cannot be checked until the lock is held. */
+ if (strcmp(fs_type, (*fst)->fs_type) == 0)
+ known = TRUE;
+ else
+ {
+ fst = &(*fst)->next;
+ if (strcmp(fs_type, (*fst)->fs_type) == 0)
+ known = TRUE;
+ }
+
+#if defined(SVN_USE_DSO) && APR_HAS_DSO
+ /* Third party FS modules that are unknown at compile time.
+
+ A third party FS is identified by the file fs-type containing a
+ third party name, say "foo". The loader will load the DSO with
+ the name "libsvn_fs_foo" and use the entry point with the name
+ "svn_fs_foo__init".
+
+ Note: the BDB and FSFS modules don't follow this naming scheme
+ and this allows them to be used to test the third party loader.
+ Change the content of fs-type to "base" in a BDB filesystem or to
+ "fs" in an FSFS filesystem and they will be loaded as third party
+ modules. */
+ if (!known)
+ {
+ fst = &(*fst)->next;
+ if (!common_pool) /* Best-effort init, see get_library_vtable_direct. */
+ SVN_ERR(svn_fs_initialize(NULL));
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ get_or_allocate_third(fst, fs_type));
+ known = TRUE;
+ }
+#endif
+ if (!known)
+ return svn_error_createf(SVN_ERR_FS_UNKNOWN_FS_TYPE, NULL,
+ _("Unknown FS type '%s'"), fs_type);
+ return get_library_vtable_direct(vtable, *fst, pool);
+}
+
+svn_error_t *
+svn_fs_type(const char **fs_type, const char *path, apr_pool_t *pool)
+{
+ const char *filename;
+ char buf[128];
+ svn_error_t *err;
+ apr_file_t *file;
+ apr_size_t len;
+
+ /* Read the fsap-name file to get the FSAP name, or assume the (old)
+ default. For old repositories I suppose we could check some
+ other file, DB_CONFIG or strings say, but for now just check the
+ directory exists. */
+ filename = svn_dirent_join(path, FS_TYPE_FILENAME, pool);
+ err = svn_io_file_open(&file, filename, APR_READ|APR_BUFFERED, 0, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_node_kind_t kind;
+ svn_error_t *err2 = svn_io_check_path(path, &kind, pool);
+ if (err2)
+ {
+ svn_error_clear(err2);
+ return err;
+ }
+ if (kind == svn_node_dir)
+ {
+ svn_error_clear(err);
+ *fs_type = SVN_FS_TYPE_BDB;
+ return SVN_NO_ERROR;
+ }
+ return err;
+ }
+ else if (err)
+ return err;
+
+ len = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+ *fs_type = apr_pstrdup(pool, buf);
+
+ return SVN_NO_ERROR;
+}
+
+/* Fetch the library vtable for an existing FS. */
+static svn_error_t *
+fs_library_vtable(fs_library_vtable_t **vtable, const char *path,
+ apr_pool_t *pool)
+{
+ const char *fs_type;
+
+ SVN_ERR(svn_fs_type(&fs_type, path, pool));
+
+ /* Fetch the library vtable by name, now that we've chosen one. */
+ return svn_error_trace(get_library_vtable(vtable, fs_type, pool));
+}
+
+static svn_error_t *
+write_fs_type(const char *path, const char *fs_type, apr_pool_t *pool)
+{
+ const char *filename;
+ apr_file_t *file;
+
+ filename = svn_dirent_join(path, FS_TYPE_FILENAME, pool);
+ SVN_ERR(svn_io_file_open(&file, filename,
+ APR_WRITE|APR_CREATE|APR_TRUNCATE|APR_BUFFERED,
+ APR_OS_DEFAULT, pool));
+ SVN_ERR(svn_io_file_write_full(file, fs_type, strlen(fs_type), NULL,
+ pool));
+ SVN_ERR(svn_io_file_write_full(file, "\n", 1, NULL, pool));
+ return svn_error_trace(svn_io_file_close(file, pool));
+}
+
+
+/* --- Functions for operating on filesystems by pathname --- */
+
+static apr_status_t uninit(void *data)
+{
+ common_pool = NULL;
+ return APR_SUCCESS;
+}
+
+svn_error_t *
+svn_fs_initialize(apr_pool_t *pool)
+{
+ /* Protect against multiple calls. */
+ if (common_pool)
+ return SVN_NO_ERROR;
+
+ common_pool = svn_pool_create(pool);
+ SVN_ERR(svn_mutex__init(&common_pool_lock, TRUE, common_pool));
+
+ /* ### This won't work if POOL is NULL and libsvn_fs is loaded as a DSO
+ ### (via libsvn_ra_local say) since the global common_pool will live
+ ### longer than the DSO, which gets unloaded when the pool used to
+ ### load it is cleared, and so when the handler runs it will refer to
+ ### a function that no longer exists. libsvn_ra_local attempts to
+ ### work around this by explicitly calling svn_fs_initialize. */
+ apr_pool_cleanup_register(common_pool, NULL, uninit, apr_pool_cleanup_null);
+ return SVN_NO_ERROR;
+}
+
+/* A default warning handling function. */
+static void
+default_warning_func(void *baton, svn_error_t *err)
+{
+ /* The one unforgiveable sin is to fail silently. Dumping to stderr
+ or /dev/tty is not acceptable default behavior for server
+ processes, since those may both be equivalent to /dev/null. */
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+}
+
+svn_error_t *
+svn_fs__path_valid(const char *path, apr_pool_t *pool)
+{
+ /* UTF-8 encoded string without NULs. */
+ if (! svn_utf__cstring_is_valid(path))
+ {
+ return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL,
+ _("Path '%s' is not in UTF-8"), path);
+ }
+
+ /* No "." or ".." elements. */
+ if (svn_path_is_backpath_present(path)
+ || svn_path_is_dotpath_present(path))
+ {
+ return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL,
+ _("Path '%s' contains '.' or '..' element"),
+ path);
+ }
+
+ /* That's good enough. */
+ return SVN_NO_ERROR;
+}
+
+/* Allocate svn_fs_t structure. */
+static svn_fs_t *
+fs_new(apr_hash_t *fs_config, apr_pool_t *pool)
+{
+ svn_fs_t *fs = apr_palloc(pool, sizeof(*fs));
+ fs->pool = pool;
+ fs->path = NULL;
+ fs->warning = default_warning_func;
+ fs->warning_baton = NULL;
+ fs->config = fs_config;
+ fs->access_ctx = NULL;
+ fs->vtable = NULL;
+ fs->fsap_data = NULL;
+ fs->uuid = NULL;
+ return fs;
+}
+
+svn_fs_t *
+svn_fs_new(apr_hash_t *fs_config, apr_pool_t *pool)
+{
+ return fs_new(fs_config, pool);
+}
+
+void
+svn_fs_set_warning_func(svn_fs_t *fs, svn_fs_warning_callback_t warning,
+ void *warning_baton)
+{
+ fs->warning = warning;
+ fs->warning_baton = warning_baton;
+}
+
+svn_error_t *
+svn_fs_create(svn_fs_t **fs_p, const char *path, apr_hash_t *fs_config,
+ apr_pool_t *pool)
+{
+ fs_library_vtable_t *vtable;
+
+ const char *fs_type = svn_hash__get_cstring(fs_config,
+ SVN_FS_CONFIG_FS_TYPE,
+ DEFAULT_FS_TYPE);
+ SVN_ERR(get_library_vtable(&vtable, fs_type, pool));
+
+ /* Create the FS directory and write out the fsap-name file. */
+ SVN_ERR(svn_io_dir_make_sgid(path, APR_OS_DEFAULT, pool));
+ SVN_ERR(write_fs_type(path, fs_type, pool));
+
+ /* Perform the actual creation. */
+ *fs_p = fs_new(fs_config, pool);
+
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ vtable->create(*fs_p, path, pool, common_pool));
+ SVN_ERR(vtable->set_svn_fs_open(*fs_p, svn_fs_open));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_open(svn_fs_t **fs_p, const char *path, apr_hash_t *fs_config,
+ apr_pool_t *pool)
+{
+ fs_library_vtable_t *vtable;
+
+ SVN_ERR(fs_library_vtable(&vtable, path, pool));
+ *fs_p = fs_new(fs_config, pool);
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ vtable->open_fs(*fs_p, path, pool, common_pool));
+ SVN_ERR(vtable->set_svn_fs_open(*fs_p, svn_fs_open));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_upgrade(const char *path, apr_pool_t *pool)
+{
+ fs_library_vtable_t *vtable;
+ svn_fs_t *fs;
+
+ SVN_ERR(fs_library_vtable(&vtable, path, pool));
+ fs = fs_new(NULL, pool);
+
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ vtable->upgrade_fs(fs, path, pool, common_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_verify(const char *path,
+ apr_hash_t *fs_config,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ fs_library_vtable_t *vtable;
+ svn_fs_t *fs;
+
+ SVN_ERR(fs_library_vtable(&vtable, path, pool));
+ fs = fs_new(fs_config, pool);
+
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ vtable->verify_fs(fs, path, start, end,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton,
+ pool, common_pool));
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_fs_path(svn_fs_t *fs, apr_pool_t *pool)
+{
+ return apr_pstrdup(pool, fs->path);
+}
+
+apr_hash_t *
+svn_fs_config(svn_fs_t *fs, apr_pool_t *pool)
+{
+ if (fs->config)
+ return apr_hash_copy(pool, fs->config);
+
+ return NULL;
+}
+
+svn_error_t *
+svn_fs_delete_fs(const char *path, apr_pool_t *pool)
+{
+ fs_library_vtable_t *vtable;
+
+ SVN_ERR(fs_library_vtable(&vtable, path, pool));
+ return svn_error_trace(vtable->delete_fs(path, pool));
+}
+
+svn_error_t *
+svn_fs_hotcopy2(const char *src_path, const char *dst_path,
+ svn_boolean_t clean, svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ fs_library_vtable_t *vtable;
+ const char *src_fs_type;
+ svn_fs_t *src_fs;
+ svn_fs_t *dst_fs;
+ const char *dst_fs_type;
+ svn_node_kind_t dst_kind;
+
+ if (strcmp(src_path, dst_path) == 0)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Hotcopy source and destination are equal"));
+
+ SVN_ERR(svn_fs_type(&src_fs_type, src_path, scratch_pool));
+ SVN_ERR(get_library_vtable(&vtable, src_fs_type, scratch_pool));
+ src_fs = fs_new(NULL, scratch_pool);
+ dst_fs = fs_new(NULL, scratch_pool);
+
+ SVN_ERR(svn_io_check_path(dst_path, &dst_kind, scratch_pool));
+ if (dst_kind == svn_node_file)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' already exists and is a file"),
+ svn_dirent_local_style(dst_path,
+ scratch_pool));
+ if (dst_kind == svn_node_unknown)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' already exists and has an unknown "
+ "node kind"),
+ svn_dirent_local_style(dst_path,
+ scratch_pool));
+ if (dst_kind == svn_node_dir)
+ {
+ svn_node_kind_t type_file_kind;
+
+ SVN_ERR(svn_io_check_path(svn_dirent_join(dst_path,
+ FS_TYPE_FILENAME,
+ scratch_pool),
+ &type_file_kind, scratch_pool));
+ if (type_file_kind != svn_node_none)
+ {
+ SVN_ERR(svn_fs_type(&dst_fs_type, dst_path, scratch_pool));
+ if (strcmp(src_fs_type, dst_fs_type) != 0)
+ return svn_error_createf(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("The filesystem type of the hotcopy source "
+ "('%s') does not match the filesystem "
+ "type of the hotcopy destination ('%s')"),
+ src_fs_type, dst_fs_type);
+ }
+ }
+
+ SVN_ERR(vtable->hotcopy(src_fs, dst_fs, src_path, dst_path, clean,
+ incremental, cancel_func, cancel_baton,
+ scratch_pool));
+ return svn_error_trace(write_fs_type(dst_path, src_fs_type, scratch_pool));
+}
+
+svn_error_t *
+svn_fs_hotcopy(const char *src_path, const char *dest_path,
+ svn_boolean_t clean, apr_pool_t *pool)
+{
+ return svn_error_trace(svn_fs_hotcopy2(src_path, dest_path, clean,
+ FALSE, NULL, NULL, pool));
+}
+
+svn_error_t *
+svn_fs_pack(const char *path,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ fs_library_vtable_t *vtable;
+ svn_fs_t *fs;
+
+ SVN_ERR(fs_library_vtable(&vtable, path, pool));
+ fs = fs_new(NULL, pool);
+
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ vtable->pack_fs(fs, path, notify_func, notify_baton,
+ cancel_func, cancel_baton, pool,
+ common_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_recover(const char *path,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool)
+{
+ fs_library_vtable_t *vtable;
+ svn_fs_t *fs;
+
+ SVN_ERR(fs_library_vtable(&vtable, path, pool));
+ fs = fs_new(NULL, pool);
+
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ vtable->open_fs_for_recovery(fs, path, pool,
+ common_pool));
+ return svn_error_trace(vtable->recover(fs, cancel_func, cancel_baton,
+ pool));
+}
+
+svn_error_t *
+svn_fs_verify_root(svn_fs_root_t *root,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_t *fs = root->fs;
+ SVN_ERR(fs->vtable->verify_root(root, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_freeze(svn_fs_t *fs,
+ svn_fs_freeze_func_t freeze_func,
+ void *freeze_baton,
+ apr_pool_t *pool)
+{
+ SVN_ERR(fs->vtable->freeze(fs, freeze_func, freeze_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* --- Berkeley-specific functions --- */
+
+svn_error_t *
+svn_fs_create_berkeley(svn_fs_t *fs, const char *path)
+{
+ fs_library_vtable_t *vtable;
+
+ SVN_ERR(get_library_vtable(&vtable, SVN_FS_TYPE_BDB, fs->pool));
+
+ /* Create the FS directory and write out the fsap-name file. */
+ SVN_ERR(svn_io_dir_make_sgid(path, APR_OS_DEFAULT, fs->pool));
+ SVN_ERR(write_fs_type(path, SVN_FS_TYPE_BDB, fs->pool));
+
+ /* Perform the actual creation. */
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ vtable->create(fs, path, fs->pool, common_pool));
+ SVN_ERR(vtable->set_svn_fs_open(fs, svn_fs_open));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_open_berkeley(svn_fs_t *fs, const char *path)
+{
+ fs_library_vtable_t *vtable;
+
+ SVN_ERR(fs_library_vtable(&vtable, path, fs->pool));
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ vtable->open_fs(fs, path, fs->pool, common_pool));
+ SVN_ERR(vtable->set_svn_fs_open(fs, svn_fs_open));
+
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_fs_berkeley_path(svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_fs_path(fs, pool);
+}
+
+svn_error_t *
+svn_fs_delete_berkeley(const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(svn_fs_delete_fs(path, pool));
+}
+
+svn_error_t *
+svn_fs_hotcopy_berkeley(const char *src_path, const char *dest_path,
+ svn_boolean_t clean_logs, apr_pool_t *pool)
+{
+ return svn_error_trace(svn_fs_hotcopy2(src_path, dest_path, clean_logs,
+ FALSE, NULL, NULL, pool));
+}
+
+svn_error_t *
+svn_fs_berkeley_recover(const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(svn_fs_recover(path, NULL, NULL, pool));
+}
+
+svn_error_t *
+svn_fs_set_berkeley_errcall(svn_fs_t *fs,
+ void (*handler)(const char *errpfx, char *msg))
+{
+ return svn_error_trace(fs->vtable->bdb_set_errcall(fs, handler));
+}
+
+svn_error_t *
+svn_fs_berkeley_logfiles(apr_array_header_t **logfiles,
+ const char *path,
+ svn_boolean_t only_unused,
+ apr_pool_t *pool)
+{
+ fs_library_vtable_t *vtable;
+
+ SVN_ERR(fs_library_vtable(&vtable, path, pool));
+ return svn_error_trace(vtable->bdb_logfiles(logfiles, path, only_unused,
+ pool));
+}
+
+
+/* --- Transaction functions --- */
+
+svn_error_t *
+svn_fs_begin_txn2(svn_fs_txn_t **txn_p, svn_fs_t *fs, svn_revnum_t rev,
+ apr_uint32_t flags, apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->begin_txn(txn_p, fs, rev, flags, pool));
+}
+
+
+svn_error_t *
+svn_fs_begin_txn(svn_fs_txn_t **txn_p, svn_fs_t *fs, svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_fs_begin_txn2(txn_p, fs, rev, 0, pool));
+}
+
+
+svn_error_t *
+svn_fs_commit_txn(const char **conflict_p, svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn, apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ *new_rev = SVN_INVALID_REVNUM;
+ if (conflict_p)
+ *conflict_p = NULL;
+
+ err = txn->vtable->commit(conflict_p, new_rev, txn, pool);
+
+#ifdef SVN_DEBUG
+ /* Check postconditions. */
+ if (conflict_p)
+ {
+ SVN_ERR_ASSERT_E(! (SVN_IS_VALID_REVNUM(*new_rev) && *conflict_p != NULL),
+ err);
+ SVN_ERR_ASSERT_E((*conflict_p != NULL)
+ == (err && err->apr_err == SVN_ERR_FS_CONFLICT),
+ err);
+ }
+#endif
+
+ SVN_ERR(err);
+
+#ifdef PACK_AFTER_EVERY_COMMIT
+ {
+ svn_fs_t *fs = txn->fs;
+ const char *fs_path = svn_fs_path(fs, pool);
+ err = svn_fs_pack(fs_path, NULL, NULL, NULL, NULL, pool);
+ if (err && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
+ /* Pre-1.6 filesystem. */
+ svn_error_clear(err);
+ else if (err)
+ /* Real error. */
+ return svn_error_trace(err);
+ }
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_abort_txn(svn_fs_txn_t *txn, apr_pool_t *pool)
+{
+ return svn_error_trace(txn->vtable->abort(txn, pool));
+}
+
+svn_error_t *
+svn_fs_purge_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->purge_txn(fs, txn_id, pool));
+}
+
+svn_error_t *
+svn_fs_txn_name(const char **name_p, svn_fs_txn_t *txn, apr_pool_t *pool)
+{
+ *name_p = apr_pstrdup(pool, txn->id);
+ return SVN_NO_ERROR;
+}
+
+svn_revnum_t
+svn_fs_txn_base_revision(svn_fs_txn_t *txn)
+{
+ return txn->base_rev;
+}
+
+svn_error_t *
+svn_fs_open_txn(svn_fs_txn_t **txn, svn_fs_t *fs, const char *name,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->open_txn(txn, fs, name, pool));
+}
+
+svn_error_t *
+svn_fs_list_transactions(apr_array_header_t **names_p, svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->list_transactions(names_p, fs, pool));
+}
+
+svn_error_t *
+svn_fs_txn_prop(svn_string_t **value_p, svn_fs_txn_t *txn,
+ const char *propname, apr_pool_t *pool)
+{
+ return svn_error_trace(txn->vtable->get_prop(value_p, txn, propname, pool));
+}
+
+svn_error_t *
+svn_fs_txn_proplist(apr_hash_t **table_p, svn_fs_txn_t *txn, apr_pool_t *pool)
+{
+ return svn_error_trace(txn->vtable->get_proplist(table_p, txn, pool));
+}
+
+svn_error_t *
+svn_fs_change_txn_prop(svn_fs_txn_t *txn, const char *name,
+ const svn_string_t *value, apr_pool_t *pool)
+{
+ return svn_error_trace(txn->vtable->change_prop(txn, name, value, pool));
+}
+
+svn_error_t *
+svn_fs_change_txn_props(svn_fs_txn_t *txn, const apr_array_header_t *props,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(txn->vtable->change_props(txn, props, pool));
+}
+
+
+/* --- Root functions --- */
+
+svn_error_t *
+svn_fs_revision_root(svn_fs_root_t **root_p, svn_fs_t *fs, svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ /* We create a subpool for each root object to allow us to implement
+ svn_fs_close_root. */
+ apr_pool_t *subpool = svn_pool_create(pool);
+ return svn_error_trace(fs->vtable->revision_root(root_p, fs, rev, subpool));
+}
+
+svn_error_t *
+svn_fs_txn_root(svn_fs_root_t **root_p, svn_fs_txn_t *txn, apr_pool_t *pool)
+{
+ /* We create a subpool for each root object to allow us to implement
+ svn_fs_close_root. */
+ apr_pool_t *subpool = svn_pool_create(pool);
+ return svn_error_trace(txn->vtable->root(root_p, txn, subpool));
+}
+
+void
+svn_fs_close_root(svn_fs_root_t *root)
+{
+ svn_pool_destroy(root->pool);
+}
+
+svn_fs_t *
+svn_fs_root_fs(svn_fs_root_t *root)
+{
+ return root->fs;
+}
+
+svn_boolean_t
+svn_fs_is_txn_root(svn_fs_root_t *root)
+{
+ return root->is_txn_root;
+}
+
+svn_boolean_t
+svn_fs_is_revision_root(svn_fs_root_t *root)
+{
+ return !root->is_txn_root;
+}
+
+const char *
+svn_fs_txn_root_name(svn_fs_root_t *root, apr_pool_t *pool)
+{
+ return root->is_txn_root ? apr_pstrdup(pool, root->txn) : NULL;
+}
+
+svn_revnum_t
+svn_fs_txn_root_base_revision(svn_fs_root_t *root)
+{
+ return root->is_txn_root ? root->rev : SVN_INVALID_REVNUM;
+}
+
+svn_revnum_t
+svn_fs_revision_root_revision(svn_fs_root_t *root)
+{
+ return root->is_txn_root ? SVN_INVALID_REVNUM : root->rev;
+}
+
+svn_error_t *
+svn_fs_paths_changed2(apr_hash_t **changed_paths_p, svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ return root->vtable->paths_changed(changed_paths_p, root, pool);
+}
+
+svn_error_t *
+svn_fs_paths_changed(apr_hash_t **changed_paths_p, svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ apr_hash_t *changed_paths_new_structs;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_fs_paths_changed2(&changed_paths_new_structs, root, pool));
+ *changed_paths_p = apr_hash_make(pool);
+ for (hi = apr_hash_first(pool, changed_paths_new_structs);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *vkey;
+ apr_ssize_t klen;
+ void *vval;
+ svn_fs_path_change2_t *val;
+ svn_fs_path_change_t *change;
+ apr_hash_this(hi, &vkey, &klen, &vval);
+ val = vval;
+ change = apr_palloc(pool, sizeof(*change));
+ change->node_rev_id = val->node_rev_id;
+ change->change_kind = val->change_kind;
+ change->text_mod = val->text_mod;
+ change->prop_mod = val->prop_mod;
+ apr_hash_set(*changed_paths_p, vkey, klen, change);
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_check_path(svn_node_kind_t *kind_p, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->check_path(kind_p, root, path, pool));
+}
+
+svn_error_t *
+svn_fs_node_history(svn_fs_history_t **history_p, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->node_history(history_p, root, path,
+ pool));
+}
+
+svn_error_t *
+svn_fs_is_dir(svn_boolean_t *is_dir, svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(root->vtable->check_path(&kind, root, path, pool));
+ *is_dir = (kind == svn_node_dir);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_is_file(svn_boolean_t *is_file, svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(root->vtable->check_path(&kind, root, path, pool));
+ *is_file = (kind == svn_node_file);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_node_id(const svn_fs_id_t **id_p, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->node_id(id_p, root, path, pool));
+}
+
+svn_error_t *
+svn_fs_node_created_rev(svn_revnum_t *revision, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->node_created_rev(revision, root, path,
+ pool));
+}
+
+svn_error_t *
+svn_fs_node_origin_rev(svn_revnum_t *revision, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->node_origin_rev(revision, root, path,
+ pool));
+}
+
+svn_error_t *
+svn_fs_node_created_path(const char **created_path, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->node_created_path(created_path, root,
+ path, pool));
+}
+
+svn_error_t *
+svn_fs_node_prop(svn_string_t **value_p, svn_fs_root_t *root,
+ const char *path, const char *propname, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->node_prop(value_p, root, path,
+ propname, pool));
+}
+
+svn_error_t *
+svn_fs_node_proplist(apr_hash_t **table_p, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->node_proplist(table_p, root, path,
+ pool));
+}
+
+svn_error_t *
+svn_fs_change_node_prop(svn_fs_root_t *root, const char *path,
+ const char *name, const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->change_node_prop(root, path, name,
+ value, pool));
+}
+
+svn_error_t *
+svn_fs_props_changed(svn_boolean_t *changed_p, svn_fs_root_t *root1,
+ const char *path1, svn_fs_root_t *root2,
+ const char *path2, apr_pool_t *pool)
+{
+ return svn_error_trace(root1->vtable->props_changed(changed_p,
+ root1, path1,
+ root2, path2,
+ pool));
+}
+
+svn_error_t *
+svn_fs_copied_from(svn_revnum_t *rev_p, const char **path_p,
+ svn_fs_root_t *root, const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->copied_from(rev_p, path_p, root, path,
+ pool));
+}
+
+svn_error_t *
+svn_fs_closest_copy(svn_fs_root_t **root_p, const char **path_p,
+ svn_fs_root_t *root, const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->closest_copy(root_p, path_p,
+ root, path, pool));
+}
+
+svn_error_t *
+svn_fs_get_mergeinfo2(svn_mergeinfo_catalog_t *catalog,
+ svn_fs_root_t *root,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(root->vtable->get_mergeinfo(
+ catalog, root, paths, inherit, include_descendants,
+ adjust_inherited_mergeinfo, result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_fs_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
+ svn_fs_root_t *root,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->get_mergeinfo(catalog, root, paths,
+ inherit,
+ include_descendants,
+ TRUE, pool, pool));
+}
+
+svn_error_t *
+svn_fs_merge(const char **conflict_p, svn_fs_root_t *source_root,
+ const char *source_path, svn_fs_root_t *target_root,
+ const char *target_path, svn_fs_root_t *ancestor_root,
+ const char *ancestor_path, apr_pool_t *pool)
+{
+ return svn_error_trace(target_root->vtable->merge(conflict_p,
+ source_root, source_path,
+ target_root, target_path,
+ ancestor_root,
+ ancestor_path, pool));
+}
+
+svn_error_t *
+svn_fs_dir_entries(apr_hash_t **entries_p, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->dir_entries(entries_p, root, path,
+ pool));
+}
+
+svn_error_t *
+svn_fs_make_dir(svn_fs_root_t *root, const char *path, apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__path_valid(path, pool));
+ return svn_error_trace(root->vtable->make_dir(root, path, pool));
+}
+
+svn_error_t *
+svn_fs_delete(svn_fs_root_t *root, const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->delete_node(root, path, pool));
+}
+
+svn_error_t *
+svn_fs_copy(svn_fs_root_t *from_root, const char *from_path,
+ svn_fs_root_t *to_root, const char *to_path, apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__path_valid(to_path, pool));
+ return svn_error_trace(to_root->vtable->copy(from_root, from_path,
+ to_root, to_path, pool));
+}
+
+svn_error_t *
+svn_fs_revision_link(svn_fs_root_t *from_root, svn_fs_root_t *to_root,
+ const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(to_root->vtable->revision_link(from_root, to_root,
+ path, pool));
+}
+
+svn_error_t *
+svn_fs_file_length(svn_filesize_t *length_p, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->file_length(length_p, root, path,
+ pool));
+}
+
+svn_error_t *
+svn_fs_file_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t kind,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_boolean_t force,
+ apr_pool_t *pool)
+{
+ SVN_ERR(root->vtable->file_checksum(checksum, kind, root, path, pool));
+
+ if (force && (*checksum == NULL || (*checksum)->kind != kind))
+ {
+ svn_stream_t *contents, *checksum_contents;
+
+ SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
+ checksum_contents = svn_stream_checksummed2(contents, checksum, NULL,
+ kind, TRUE, pool);
+
+ /* This will force a read of any remaining data (which is all of it in
+ this case) and dump the checksum into checksum->digest. */
+ SVN_ERR(svn_stream_close(checksum_contents));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_file_md5_checksum(unsigned char digest[],
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *md5sum;
+
+ SVN_ERR(svn_fs_file_checksum(&md5sum, svn_checksum_md5, root, path, TRUE,
+ pool));
+ memcpy(digest, md5sum->digest, APR_MD5_DIGESTSIZE);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_file_contents(svn_stream_t **contents, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ return svn_error_trace(root->vtable->file_contents(contents, root, path,
+ pool));
+}
+
+svn_error_t *
+svn_fs_try_process_file_contents(svn_boolean_t *success,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool)
+{
+ /* if the FS doesn't implement this function, report a "failed" attempt */
+ if (root->vtable->try_process_file_contents == NULL)
+ {
+ *success = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(root->vtable->try_process_file_contents(
+ success,
+ root, path,
+ processor, baton, pool));
+}
+
+svn_error_t *
+svn_fs_make_file(svn_fs_root_t *root, const char *path, apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__path_valid(path, pool));
+ return svn_error_trace(root->vtable->make_file(root, path, pool));
+}
+
+svn_error_t *
+svn_fs_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
+ void **contents_baton_p, svn_fs_root_t *root,
+ const char *path, const char *base_checksum,
+ const char *result_checksum, apr_pool_t *pool)
+{
+ svn_checksum_t *base, *result;
+
+ /* TODO: If we ever rev this API, we should make the supplied checksums
+ svn_checksum_t structs. */
+ SVN_ERR(svn_checksum_parse_hex(&base, svn_checksum_md5, base_checksum,
+ pool));
+ SVN_ERR(svn_checksum_parse_hex(&result, svn_checksum_md5, result_checksum,
+ pool));
+
+ return svn_error_trace(root->vtable->apply_textdelta(contents_p,
+ contents_baton_p,
+ root,
+ path,
+ base,
+ result,
+ pool));
+}
+
+svn_error_t *
+svn_fs_apply_text(svn_stream_t **contents_p, svn_fs_root_t *root,
+ const char *path, const char *result_checksum,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *result;
+
+ /* TODO: If we ever rev this API, we should make the supplied checksum an
+ svn_checksum_t struct. */
+ SVN_ERR(svn_checksum_parse_hex(&result, svn_checksum_md5, result_checksum,
+ pool));
+
+ return svn_error_trace(root->vtable->apply_text(contents_p, root, path,
+ result, pool));
+}
+
+svn_error_t *
+svn_fs_contents_changed(svn_boolean_t *changed_p, svn_fs_root_t *root1,
+ const char *path1, svn_fs_root_t *root2,
+ const char *path2, apr_pool_t *pool)
+{
+ return svn_error_trace(root1->vtable->contents_changed(changed_p,
+ root1, path1,
+ root2, path2,
+ pool));
+}
+
+svn_error_t *
+svn_fs_youngest_rev(svn_revnum_t *youngest_p, svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->youngest_rev(youngest_p, fs, pool));
+}
+
+svn_error_t *
+svn_fs_deltify_revision(svn_fs_t *fs, svn_revnum_t revision, apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->deltify(fs, revision, pool));
+}
+
+svn_error_t *
+svn_fs_revision_prop(svn_string_t **value_p, svn_fs_t *fs, svn_revnum_t rev,
+ const char *propname, apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->revision_prop(value_p, fs, rev,
+ propname, pool));
+}
+
+svn_error_t *
+svn_fs_revision_proplist(apr_hash_t **table_p, svn_fs_t *fs, svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->revision_proplist(table_p, fs, rev,
+ pool));
+}
+
+svn_error_t *
+svn_fs_change_rev_prop2(svn_fs_t *fs, svn_revnum_t rev, const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value, apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->change_rev_prop(fs, rev, name,
+ old_value_p,
+ value, pool));
+}
+
+svn_error_t *
+svn_fs_change_rev_prop(svn_fs_t *fs, svn_revnum_t rev, const char *name,
+ const svn_string_t *value, apr_pool_t *pool)
+{
+ return svn_error_trace(
+ svn_fs_change_rev_prop2(fs, rev, name, NULL, value, pool));
+}
+
+svn_error_t *
+svn_fs_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path, apr_pool_t *pool)
+{
+ return svn_error_trace(target_root->vtable->get_file_delta_stream(
+ stream_p,
+ source_root, source_path,
+ target_root, target_path, pool));
+}
+
+svn_error_t *
+svn_fs_get_uuid(svn_fs_t *fs, const char **uuid, apr_pool_t *pool)
+{
+ /* If you change this, consider changing svn_fs__identifier(). */
+ *uuid = apr_pstrdup(pool, fs->uuid);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_set_uuid(svn_fs_t *fs, const char *uuid, apr_pool_t *pool)
+{
+ if (! uuid)
+ {
+ uuid = svn_uuid_generate(pool);
+ }
+ else
+ {
+ apr_uuid_t parsed_uuid;
+ apr_status_t apr_err = apr_uuid_parse(&parsed_uuid, uuid);
+ if (apr_err)
+ return svn_error_createf(SVN_ERR_BAD_UUID, NULL,
+ _("Malformed UUID '%s'"), uuid);
+ }
+ return svn_error_trace(fs->vtable->set_uuid(fs, uuid, pool));
+}
+
+svn_error_t *
+svn_fs_lock(svn_lock_t **lock, svn_fs_t *fs, const char *path,
+ const char *token, const char *comment,
+ svn_boolean_t is_dav_comment, apr_time_t expiration_date,
+ svn_revnum_t current_rev, svn_boolean_t steal_lock,
+ apr_pool_t *pool)
+{
+ /* Enforce that the comment be xml-escapable. */
+ if (comment)
+ {
+ if (! svn_xml_is_xml_safe(comment, strlen(comment)))
+ return svn_error_create
+ (SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
+ _("Lock comment contains illegal characters"));
+ }
+
+ /* Enforce that the token be an XML-safe URI. */
+ if (token)
+ {
+ const char *c;
+
+ if (strncmp(token, "opaquelocktoken:", 16))
+ return svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ _("Lock token URI '%s' has bad scheme; "
+ "expected '%s'"),
+ token, "opaquelocktoken");
+
+ for (c = token; *c; c++)
+ if (! svn_ctype_isascii(*c))
+ return svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ _("Lock token '%s' is not ASCII "
+ "at byte %u"),
+ token, (unsigned)(c - token));
+
+ /* strlen(token) == c - token. */
+ if (! svn_xml_is_xml_safe(token, c - token))
+ return svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ _("Lock token URI '%s' is not XML-safe"),
+ token);
+ }
+
+ if (expiration_date < 0)
+ return svn_error_create
+ (SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Negative expiration date passed to svn_fs_lock"));
+
+ return svn_error_trace(fs->vtable->lock(lock, fs, path, token, comment,
+ is_dav_comment, expiration_date,
+ current_rev, steal_lock, pool));
+}
+
+svn_error_t *
+svn_fs_generate_lock_token(const char **token, svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->generate_lock_token(token, fs, pool));
+}
+
+svn_error_t *
+svn_fs_unlock(svn_fs_t *fs, const char *path, const char *token,
+ svn_boolean_t break_lock, apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->unlock(fs, path, token, break_lock,
+ pool));
+}
+
+svn_error_t *
+svn_fs_get_lock(svn_lock_t **lock, svn_fs_t *fs, const char *path,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(fs->vtable->get_lock(lock, fs, path, pool));
+}
+
+svn_error_t *
+svn_fs_get_locks2(svn_fs_t *fs, const char *path, svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton, apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT((depth == svn_depth_empty) ||
+ (depth == svn_depth_files) ||
+ (depth == svn_depth_immediates) ||
+ (depth == svn_depth_infinity));
+ return svn_error_trace(fs->vtable->get_locks(fs, path, depth,
+ get_locks_func,
+ get_locks_baton, pool));
+}
+
+svn_error_t *
+svn_fs_get_locks(svn_fs_t *fs, const char *path,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton, apr_pool_t *pool)
+{
+ return svn_error_trace(svn_fs_get_locks2(fs, path, svn_depth_infinity,
+ get_locks_func, get_locks_baton,
+ pool));
+}
+
+
+/* --- History functions --- */
+
+svn_error_t *
+svn_fs_history_prev(svn_fs_history_t **prev_history_p,
+ svn_fs_history_t *history, svn_boolean_t cross_copies,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(history->vtable->prev(prev_history_p, history,
+ cross_copies, pool));
+}
+
+svn_error_t *
+svn_fs_history_location(const char **path, svn_revnum_t *revision,
+ svn_fs_history_t *history, apr_pool_t *pool)
+{
+ return svn_error_trace(history->vtable->location(path, revision, history,
+ pool));
+}
+
+
+/* --- Node-ID functions --- */
+
+svn_fs_id_t *
+svn_fs_parse_id(const char *data, apr_size_t len, apr_pool_t *pool)
+{
+ fs_library_vtable_t *vtable;
+ svn_error_t *err;
+
+ err = get_library_vtable(&vtable, SVN_FS_TYPE_BDB, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+ return vtable->parse_id(data, len, pool);
+}
+
+svn_string_t *
+svn_fs_unparse_id(const svn_fs_id_t *id, apr_pool_t *pool)
+{
+ return id->vtable->unparse(id, pool);
+}
+
+svn_boolean_t
+svn_fs_check_related(const svn_fs_id_t *a, const svn_fs_id_t *b)
+{
+ return (a->vtable->compare(a, b) != -1);
+}
+
+int
+svn_fs_compare_ids(const svn_fs_id_t *a, const svn_fs_id_t *b)
+{
+ return a->vtable->compare(a, b);
+}
+
+svn_error_t *
+svn_fs_print_modules(svn_stringbuf_t *output,
+ apr_pool_t *pool)
+{
+ const struct fs_type_defn *defn = fs_modules;
+ fs_library_vtable_t *vtable;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ while (defn)
+ {
+ char *line;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ err = get_library_vtable_direct(&vtable, defn, iterpool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_UNKNOWN_FS_TYPE)
+ {
+ svn_error_clear(err);
+ defn = defn->next;
+ continue;
+ }
+ else
+ return err;
+ }
+
+ line = apr_psprintf(iterpool, "* fs_%s : %s\n",
+ defn->fsap_name, vtable->get_description());
+ svn_stringbuf_appendcstr(output, line);
+ defn = defn->next;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_fs_path_change2_t *
+svn_fs_path_change2_create(const svn_fs_id_t *node_rev_id,
+ svn_fs_path_change_kind_t change_kind,
+ apr_pool_t *pool)
+{
+ return svn_fs__path_change_create_internal(node_rev_id, change_kind, pool);
+}
+
+/* Return the library version number. */
+const svn_version_t *
+svn_fs_version(void)
+{
+ SVN_VERSION_BODY;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs/fs-loader.h b/contrib/subversion/subversion/libsvn_fs/fs-loader.h
new file mode 100644
index 0000000..532ff05
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs/fs-loader.h
@@ -0,0 +1,502 @@
+/*
+ * fs_loader.h: Declarations for the FS loader library
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef LIBSVN_FS_FS_H
+#define LIBSVN_FS_FS_H
+
+#include "svn_types.h"
+#include "svn_fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* The FS loader library implements the a front end to "filesystem
+ abstract providers" (FSAPs), which implement the svn_fs API.
+
+ The loader library divides up the FS API into several categories:
+
+ - Top-level functions, which operate on paths to an FS
+ - Functions which operate on an FS object
+ - Functions which operate on a transaction object
+ - Functions which operate on a root object
+ - Functions which operate on a history object
+ - Functions which operate on a noderev-ID object
+
+ Some generic fields of the FS, transaction, root, and history
+ objects are defined by the loader library; the rest are stored in
+ the "fsap_data" field which is defined by the FSAP. Likewise, some
+ of the very simple svn_fs API functions (such as svn_fs_root_fs)
+ are defined by the loader library, while the rest are implemented
+ through vtable calls defined by the FSAP.
+
+ If you are considering writing a new database-backed filesystem
+ implementation, it may be appropriate to add a second, lower-level
+ abstraction to the libsvn_fs_base library which currently
+ implements the BDB filesystem type. Consult the dev list for
+ details on the "FSP-level" abstraction concept.
+*/
+
+
+
+/*** Top-level library vtable type ***/
+
+typedef struct fs_library_vtable_t
+{
+ /* This field should always remain first in the vtable.
+ Apart from that, it can be changed however you like, since exact
+ version equality is required between loader and module. This policy
+ was weaker during 1.1.x, but only in ways which do not conflict with
+ this statement, now that the minor version has increased. */
+ const svn_version_t *(*get_version)(void);
+
+ /* The open_fs/create/open_fs_for_recovery/upgrade_fs functions are
+ serialized so that they may use the common_pool parameter to
+ allocate fs-global objects such as the bdb env cache. */
+ svn_error_t *(*create)(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool);
+ svn_error_t *(*open_fs)(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool);
+ /* open_for_recovery() is like open(), but used to fill in an fs pointer
+ that will be passed to recover(). We assume that the open() method
+ might not be immediately appropriate for recovery. */
+ svn_error_t *(*open_fs_for_recovery)(svn_fs_t *fs, const char *path,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool);
+ svn_error_t *(*upgrade_fs)(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool);
+ svn_error_t *(*verify_fs)(svn_fs_t *fs, const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool);
+ svn_error_t *(*delete_fs)(const char *path, apr_pool_t *pool);
+ svn_error_t *(*hotcopy)(svn_fs_t *src_fs, svn_fs_t *dst_fs,
+ const char *src_path, const char *dst_path,
+ svn_boolean_t clean, svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool);
+ const char *(*get_description)(void);
+ svn_error_t *(*recover)(svn_fs_t *fs,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool);
+ svn_error_t *(*pack_fs)(svn_fs_t *fs, const char *path,
+ svn_fs_pack_notify_t notify_func, void *notify_baton,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool, apr_pool_t *common_pool);
+
+ /* Provider-specific functions should go here, even if they could go
+ in an object vtable, so that they are all kept together. */
+ svn_error_t *(*bdb_logfiles)(apr_array_header_t **logfiles,
+ const char *path, svn_boolean_t only_unused,
+ apr_pool_t *pool);
+
+ /* This is to let the base provider implement the deprecated
+ svn_fs_parse_id, which we've decided doesn't belong in the FS
+ API. If we change our minds and decide to add a real
+ svn_fs_parse_id variant which takes an FS object, it should go
+ into the FS vtable. */
+ svn_fs_id_t *(*parse_id)(const char *data, apr_size_t len,
+ apr_pool_t *pool);
+ /* Allow an FSAP to call svn_fs_open(), which is in a higher-level library
+ (libsvn_fs-1.so) and cannot easily be moved to libsvn_fs_util. */
+ svn_error_t *(*set_svn_fs_open)(svn_fs_t *fs,
+ svn_error_t *(*svn_fs_open_)(svn_fs_t **,
+ const char *,
+ apr_hash_t *,
+ apr_pool_t *));
+
+} fs_library_vtable_t;
+
+/* This is the type of symbol an FS module defines to fetch the
+ library vtable. The LOADER_VERSION parameter must remain first in
+ the list, and the function must use the C calling convention on all
+ platforms, so that the init functions can safely read the version
+ parameter. The COMMON_POOL parameter must be a pool with a greater
+ lifetime than the fs module so that fs global state can be kept
+ in it and cleaned up on termination before the fs module is unloaded.
+ Calls to these functions are globally serialized so that they have
+ exclusive access to the COMMON_POOL parameter.
+
+ ### need to force this to be __cdecl on Windows... how?? */
+typedef svn_error_t *(*fs_init_func_t)(const svn_version_t *loader_version,
+ fs_library_vtable_t **vtable,
+ apr_pool_t* common_pool);
+
+/* Here are the declarations for the FS module init functions. If we
+ are using DSO loading, they won't actually be linked into
+ libsvn_fs. Note that these private functions have a common_pool
+ parameter that may be used for fs module scoped variables such as
+ the bdb cache. This will be the same common_pool that is passed
+ to the create and open functions and these init functions (as well
+ as the open and create functions) are globally serialized so that
+ they have exclusive access to the common_pool. */
+svn_error_t *svn_fs_base__init(const svn_version_t *loader_version,
+ fs_library_vtable_t **vtable,
+ apr_pool_t* common_pool);
+svn_error_t *svn_fs_fs__init(const svn_version_t *loader_version,
+ fs_library_vtable_t **vtable,
+ apr_pool_t* common_pool);
+
+
+
+/*** vtable types for the abstract FS objects ***/
+
+typedef struct fs_vtable_t
+{
+ svn_error_t *(*youngest_rev)(svn_revnum_t *youngest_p, svn_fs_t *fs,
+ apr_pool_t *pool);
+ svn_error_t *(*revision_prop)(svn_string_t **value_p, svn_fs_t *fs,
+ svn_revnum_t rev, const char *propname,
+ apr_pool_t *pool);
+ svn_error_t *(*revision_proplist)(apr_hash_t **table_p, svn_fs_t *fs,
+ svn_revnum_t rev, apr_pool_t *pool);
+ svn_error_t *(*change_rev_prop)(svn_fs_t *fs, svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+ /* There is no get_uuid(); see svn_fs_t.uuid docstring. */
+ svn_error_t *(*set_uuid)(svn_fs_t *fs, const char *uuid, apr_pool_t *pool);
+ svn_error_t *(*revision_root)(svn_fs_root_t **root_p, svn_fs_t *fs,
+ svn_revnum_t rev, apr_pool_t *pool);
+ svn_error_t *(*begin_txn)(svn_fs_txn_t **txn_p, svn_fs_t *fs,
+ svn_revnum_t rev, apr_uint32_t flags,
+ apr_pool_t *pool);
+ svn_error_t *(*open_txn)(svn_fs_txn_t **txn, svn_fs_t *fs,
+ const char *name, apr_pool_t *pool);
+ svn_error_t *(*purge_txn)(svn_fs_t *fs, const char *txn_id,
+ apr_pool_t *pool);
+ svn_error_t *(*list_transactions)(apr_array_header_t **names_p,
+ svn_fs_t *fs, apr_pool_t *pool);
+ svn_error_t *(*deltify)(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool);
+ svn_error_t *(*lock)(svn_lock_t **lock, svn_fs_t *fs,
+ const char *path, const char *token,
+ const char *comment, svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_revnum_t current_rev, svn_boolean_t steal_lock,
+ apr_pool_t *pool);
+ svn_error_t *(*generate_lock_token)(const char **token, svn_fs_t *fs,
+ apr_pool_t *pool);
+ svn_error_t *(*unlock)(svn_fs_t *fs, const char *path, const char *token,
+ svn_boolean_t break_lock, apr_pool_t *pool);
+ svn_error_t *(*get_lock)(svn_lock_t **lock, svn_fs_t *fs,
+ const char *path, apr_pool_t *pool);
+ svn_error_t *(*get_locks)(svn_fs_t *fs, const char *path, svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ apr_pool_t *pool);
+ svn_error_t *(*verify_root)(svn_fs_root_t *root,
+ apr_pool_t *pool);
+ svn_error_t *(*freeze)(svn_fs_t *fs,
+ svn_fs_freeze_func_t freeze_func,
+ void *freeze_baton, apr_pool_t *pool);
+ svn_error_t *(*bdb_set_errcall)(svn_fs_t *fs,
+ void (*handler)(const char *errpfx,
+ char *msg));
+} fs_vtable_t;
+
+
+typedef struct txn_vtable_t
+{
+ svn_error_t *(*commit)(const char **conflict_p, svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn, apr_pool_t *pool);
+ svn_error_t *(*abort)(svn_fs_txn_t *txn, apr_pool_t *pool);
+ svn_error_t *(*get_prop)(svn_string_t **value_p, svn_fs_txn_t *txn,
+ const char *propname, apr_pool_t *pool);
+ svn_error_t *(*get_proplist)(apr_hash_t **table_p, svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+ svn_error_t *(*change_prop)(svn_fs_txn_t *txn, const char *name,
+ const svn_string_t *value, apr_pool_t *pool);
+ svn_error_t *(*root)(svn_fs_root_t **root_p, svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+ svn_error_t *(*change_props)(svn_fs_txn_t *txn, const apr_array_header_t *props,
+ apr_pool_t *pool);
+} txn_vtable_t;
+
+
+/* Some of these operations accept multiple root arguments. Since the
+ roots may not all have the same vtable, we need a rule to determine
+ which root's vtable is used. The rule is: if one of the roots is
+ named "target", we use that root's vtable; otherwise, we use the
+ first root argument's vtable.
+ These callbacks correspond to svn_fs_* functions in include/svn_fs.h,
+ see there for details.
+ Note: delete_node() corresponds to svn_fs_delete(). */
+typedef struct root_vtable_t
+{
+ /* Determining what has changed in a root */
+ svn_error_t *(*paths_changed)(apr_hash_t **changed_paths_p,
+ svn_fs_root_t *root,
+ apr_pool_t *pool);
+
+ /* Generic node operations */
+ svn_error_t *(*check_path)(svn_node_kind_t *kind_p, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool);
+ svn_error_t *(*node_history)(svn_fs_history_t **history_p,
+ svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool);
+ svn_error_t *(*node_id)(const svn_fs_id_t **id_p, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool);
+ svn_error_t *(*node_created_rev)(svn_revnum_t *revision,
+ svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool);
+ svn_error_t *(*node_origin_rev)(svn_revnum_t *revision,
+ svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool);
+ svn_error_t *(*node_created_path)(const char **created_path,
+ svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool);
+ svn_error_t *(*delete_node)(svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool);
+ svn_error_t *(*copied_from)(svn_revnum_t *rev_p, const char **path_p,
+ svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool);
+ svn_error_t *(*closest_copy)(svn_fs_root_t **root_p, const char **path_p,
+ svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool);
+
+ /* Property operations */
+ svn_error_t *(*node_prop)(svn_string_t **value_p, svn_fs_root_t *root,
+ const char *path, const char *propname,
+ apr_pool_t *pool);
+ svn_error_t *(*node_proplist)(apr_hash_t **table_p, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool);
+ svn_error_t *(*change_node_prop)(svn_fs_root_t *root, const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+ svn_error_t *(*props_changed)(int *changed_p, svn_fs_root_t *root1,
+ const char *path1, svn_fs_root_t *root2,
+ const char *path2, apr_pool_t *pool);
+
+ /* Directories */
+ svn_error_t *(*dir_entries)(apr_hash_t **entries_p, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool);
+ svn_error_t *(*make_dir)(svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool);
+ svn_error_t *(*copy)(svn_fs_root_t *from_root, const char *from_path,
+ svn_fs_root_t *to_root, const char *to_path,
+ apr_pool_t *pool);
+ svn_error_t *(*revision_link)(svn_fs_root_t *from_root,
+ svn_fs_root_t *to_root,
+ const char *path,
+ apr_pool_t *pool);
+
+ /* Files */
+ svn_error_t *(*file_length)(svn_filesize_t *length_p, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool);
+ svn_error_t *(*file_checksum)(svn_checksum_t **checksum,
+ svn_checksum_kind_t kind, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool);
+ svn_error_t *(*file_contents)(svn_stream_t **contents,
+ svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool);
+ svn_error_t *(*try_process_file_contents)(svn_boolean_t *success,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool);
+ svn_error_t *(*make_file)(svn_fs_root_t *root, const char *path,
+ apr_pool_t *pool);
+ svn_error_t *(*apply_textdelta)(svn_txdelta_window_handler_t *contents_p,
+ void **contents_baton_p,
+ svn_fs_root_t *root, const char *path,
+ svn_checksum_t *base_checksum,
+ svn_checksum_t *result_checksum,
+ apr_pool_t *pool);
+ svn_error_t *(*apply_text)(svn_stream_t **contents_p, svn_fs_root_t *root,
+ const char *path, svn_checksum_t *result_checksum,
+ apr_pool_t *pool);
+ svn_error_t *(*contents_changed)(int *changed_p, svn_fs_root_t *root1,
+ const char *path1, svn_fs_root_t *root2,
+ const char *path2, apr_pool_t *pool);
+ svn_error_t *(*get_file_delta_stream)(svn_txdelta_stream_t **stream_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ apr_pool_t *pool);
+
+ /* Merging. */
+ svn_error_t *(*merge)(const char **conflict_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ svn_fs_root_t *ancestor_root,
+ const char *ancestor_path,
+ apr_pool_t *pool);
+ /* Mergeinfo. */
+ svn_error_t *(*get_mergeinfo)(svn_mergeinfo_catalog_t *catalog,
+ svn_fs_root_t *root,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+} root_vtable_t;
+
+
+typedef struct history_vtable_t
+{
+ svn_error_t *(*prev)(svn_fs_history_t **prev_history_p,
+ svn_fs_history_t *history, svn_boolean_t cross_copies,
+ apr_pool_t *pool);
+ svn_error_t *(*location)(const char **path, svn_revnum_t *revision,
+ svn_fs_history_t *history, apr_pool_t *pool);
+} history_vtable_t;
+
+
+typedef struct id_vtable_t
+{
+ svn_string_t *(*unparse)(const svn_fs_id_t *id, apr_pool_t *pool);
+ int (*compare)(const svn_fs_id_t *a, const svn_fs_id_t *b);
+} id_vtable_t;
+
+
+
+/*** Definitions of the abstract FS object types ***/
+
+/* These are transaction properties that correspond to the bitfields
+ in the 'flags' argument to svn_fs_lock(). */
+#define SVN_FS__PROP_TXN_CHECK_LOCKS SVN_PROP_PREFIX "check-locks"
+#define SVN_FS__PROP_TXN_CHECK_OOD SVN_PROP_PREFIX "check-ood"
+
+struct svn_fs_t
+{
+ /* The pool in which this fs object is allocated */
+ apr_pool_t *pool;
+
+ /* The path to the repository's top-level directory */
+ char *path;
+
+ /* A callback for printing warning messages */
+ svn_fs_warning_callback_t warning;
+ void *warning_baton;
+
+ /* The filesystem configuration */
+ apr_hash_t *config;
+
+ /* An access context indicating who's using the fs */
+ svn_fs_access_t *access_ctx;
+
+ /* FSAP-specific vtable and private data */
+ fs_vtable_t *vtable;
+ void *fsap_data;
+
+ /* UUID, stored by open(), create(), and set_uuid(). */
+ const char *uuid;
+};
+
+
+struct svn_fs_txn_t
+{
+ /* The filesystem to which this transaction belongs */
+ svn_fs_t *fs;
+
+ /* The revision on which this transaction is based, or
+ SVN_INVALID_REVISION if the transaction is not based on a
+ revision at all */
+ svn_revnum_t base_rev;
+
+ /* The ID of this transaction */
+ const char *id;
+
+ /* FSAP-specific vtable and private data */
+ txn_vtable_t *vtable;
+ void *fsap_data;
+};
+
+
+struct svn_fs_root_t
+{
+ /* A pool managing this root (and only this root!) */
+ apr_pool_t *pool;
+
+ /* The filesystem to which this root belongs */
+ svn_fs_t *fs;
+
+ /* The kind of root this is */
+ svn_boolean_t is_txn_root;
+
+ /* For transaction roots, the name of the transaction */
+ const char *txn;
+
+ /* For transaction roots, flags describing the txn's behavior. */
+ apr_uint32_t txn_flags;
+
+ /* For revision roots, the number of the revision; for transaction
+ roots, the number of the revision on which the transaction is
+ based. */
+ svn_revnum_t rev;
+
+ /* FSAP-specific vtable and private data */
+ root_vtable_t *vtable;
+ void *fsap_data;
+};
+
+
+struct svn_fs_history_t
+{
+ /* FSAP-specific vtable and private data */
+ history_vtable_t *vtable;
+ void *fsap_data;
+};
+
+
+struct svn_fs_id_t
+{
+ /* FSAP-specific vtable and private data */
+ id_vtable_t *vtable;
+ void *fsap_data;
+};
+
+
+struct svn_fs_access_t
+{
+ /* An authenticated username using the fs */
+ const char *username;
+
+ /* A collection of lock-tokens supplied by the fs caller.
+ Hash maps (const char *) UUID --> (void *) 1
+ fs functions should really only be interested whether a UUID
+ exists as a hash key at all; the value is irrelevant. */
+ apr_hash_t *lock_tokens;
+};
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb-err.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb-err.c
new file mode 100644
index 0000000..3d51711
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb-err.c
@@ -0,0 +1,106 @@
+/*
+ * err.c : implementation of fs-private error functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include <apr_strings.h>
+
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../err.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+
+/* Return a distinguished error for any db error code we want to detect
+ * programatically; otherwise return a generic error.
+ */
+static int
+bdb_err_to_apr_err(int db_err)
+{
+ if (db_err == DB_LOCK_DEADLOCK)
+ return SVN_ERR_FS_BERKELEY_DB_DEADLOCK;
+ else
+ return SVN_ERR_FS_BERKELEY_DB;
+}
+
+
+svn_error_t *
+svn_fs_bdb__dberr(bdb_env_baton_t *bdb_baton, int db_err)
+{
+ svn_error_t *child_errors;
+
+ child_errors = bdb_baton->error_info->pending_errors;
+ bdb_baton->error_info->pending_errors = NULL;
+
+ return svn_error_create(bdb_err_to_apr_err(db_err), child_errors,
+ db_strerror(db_err));
+}
+
+
+svn_error_t *
+svn_fs_bdb__dberrf(bdb_env_baton_t *bdb_baton,
+ int db_err, const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+ svn_error_t *err;
+ svn_error_t *child_errors;
+
+ child_errors = bdb_baton->error_info->pending_errors;
+ bdb_baton->error_info->pending_errors = NULL;
+
+ err = svn_error_create(bdb_err_to_apr_err(db_err), child_errors, NULL);
+
+ va_start(ap, fmt);
+ msg = apr_pvsprintf(err->pool, fmt, ap);
+ va_end(ap);
+ err->message = apr_psprintf(err->pool, "%s%s", msg, db_strerror(db_err));
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__wrap_db(svn_fs_t *fs, const char *operation, int db_err)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ if (! db_err)
+ {
+ svn_error_clear(bfd->bdb->error_info->pending_errors);
+ bfd->bdb->error_info->pending_errors = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ bfd = fs->fsap_data;
+ return svn_fs_bdb__dberrf
+ (bfd->bdb, db_err,
+ _("Berkeley DB error for filesystem '%s' while %s:\n"),
+ fs->path ? fs->path : "(none)", _(operation));
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb-err.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb-err.h
new file mode 100644
index 0000000..200afe9
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb-err.h
@@ -0,0 +1,115 @@
+/*
+ * err.h : interface to routines for returning Berkeley DB errors
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#ifndef SVN_LIBSVN_FS_BDB_ERR_H
+#define SVN_LIBSVN_FS_BDB_ERR_H
+
+#include <apr_pools.h>
+
+#include "svn_error.h"
+#include "svn_fs.h"
+
+#include "env.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Return an svn_error_t object that reports a Berkeley DB error.
+ DB_ERR is the error value returned by the Berkeley DB routine.
+ Wrap and consume pending errors in BDB. */
+svn_error_t *svn_fs_bdb__dberr(bdb_env_baton_t *bdb_baton, int db_err);
+
+
+/* Allocate an error object for a Berkeley DB error, with a formatted message.
+ Wrap and consume pending errors in BDB.
+
+ DB_ERR is the Berkeley DB error code.
+ FMT is a printf-style format string, describing how to format any
+ subsequent arguments.
+
+ The svn_error_t object returned has a message consisting of:
+ - the text specified by FMT and the subsequent arguments, and
+ - the Berkeley DB error message for the error code DB_ERR.
+
+ There is no separator between the two messages; if you want one,
+ you should include it in FMT. */
+svn_error_t *svn_fs_bdb__dberrf(bdb_env_baton_t *bdb_baton, int db_err,
+ const char *fmt, ...)
+ __attribute__((format(printf, 3, 4)));
+
+
+/* Clear errors associated with BDB. */
+void svn_fs_bdb__clear_err(bdb_env_t *bdb);
+
+
+/* Check the return status from the Berkeley DB operation. If the
+ operation succeeded, return zero. Otherwise, construct an
+ appropriate Subversion error object describing what went wrong.
+ - FS is the Subversion filesystem we're operating on.
+ - OPERATION is a gerund clause describing what we were trying to do.
+ - BDB_ERR is the return status from the Berkeley DB function. */
+svn_error_t *svn_fs_bdb__wrap_db(svn_fs_t *fs,
+ const char *operation,
+ int db_err);
+
+
+/* A terse wrapper for svn_fs_bdb__wrap_db. */
+#define BDB_WRAP(fs, op, err) (svn_fs_bdb__wrap_db((fs), (op), (err)))
+
+/* If EXPR returns a non-zero value, pass that value to
+ svn_fs_bdb__dberr and return that function's value. This is like
+ SVN_ERR, but is used by functions that return a Subversion error
+ and call other functions that return a Berkeley DB error code. */
+#define SVN_BDB_ERR(bdb, expr) \
+ do { \
+ int db_err__temp = (expr); \
+ if (db_err__temp) \
+ return svn_fs_bdb__dberr((bdb), db_err__temp); \
+ svn_error_clear((bdb)->error_info->pending_errors); \
+ (bdb)->error_info->pending_errors = NULL; \
+ } while (0)
+
+
+/* If EXPR returns a non-zero value, return it. This is like SVN_ERR,
+ but for functions that return a Berkeley DB error code. */
+#define BDB_ERR(expr) \
+ do { \
+ int db_err__temp = (expr); \
+ if (db_err__temp) \
+ return db_err__temp; \
+ } while (0)
+
+
+/* Verify that FS refers to an open database; return an appropriate
+ error if this is not the case. */
+svn_error_t *svn_fs_bdb__check_fs(svn_fs_t *fs);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_BDB_ERR_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb_compat.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb_compat.c
new file mode 100644
index 0000000..5596eee
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb_compat.c
@@ -0,0 +1,34 @@
+/* bdb_compat.c --- Compatibility wrapper for different BDB versions.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "bdb_compat.h"
+
+int
+svn_fs_bdb__check_version(void)
+{
+ int major, minor;
+
+ db_version(&major, &minor, NULL);
+ if (major != DB_VERSION_MAJOR || minor != DB_VERSION_MINOR)
+ return DB_OLD_VERSION;
+ return 0;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb_compat.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb_compat.h
new file mode 100644
index 0000000..bea62de
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/bdb_compat.h
@@ -0,0 +1,135 @@
+/* svn_bdb_compat.h --- Compatibility wrapper for different BDB versions.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_BDB_COMPAT_H
+#define SVN_LIBSVN_FS_BDB_COMPAT_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Symbols and constants */
+
+/* BDB 4.1 introduced the DB_AUTO_COMMIT flag. Older versions can just
+ use 0 instead. */
+#ifdef DB_AUTO_COMMIT
+#define SVN_BDB_AUTO_COMMIT (DB_AUTO_COMMIT)
+#else
+#define SVN_BDB_AUTO_COMMIT (0)
+#endif
+
+/* DB_INCOMPLETE is obsolete in BDB 4.1. */
+#ifdef DB_INCOMPLETE
+#define SVN_BDB_HAS_DB_INCOMPLETE 1
+#else
+#define SVN_BDB_HAS_DB_INCOMPLETE 0
+#endif
+
+/* In BDB 4.3, "buffer too small" errors come back with
+ DB_BUFFER_SMALL (instead of ENOMEM, which is now fatal). */
+#ifdef DB_BUFFER_SMALL
+#define SVN_BDB_DB_BUFFER_SMALL DB_BUFFER_SMALL
+#else
+#define SVN_BDB_DB_BUFFER_SMALL ENOMEM
+#endif
+
+/* BDB 4.4 introdiced the DB_REGISTER flag for DBEnv::open that allows
+ for automatic recovery of the databases after a program crash. */
+#ifdef DB_REGISTER
+#define SVN_BDB_AUTO_RECOVER (DB_REGISTER | DB_RECOVER)
+#else
+#define SVN_BDB_AUTO_RECOVER (0)
+#endif
+
+
+/* Explicit BDB version check. */
+#define SVN_BDB_VERSION_AT_LEAST(major,minor) \
+ (DB_VERSION_MAJOR > (major) \
+ || (DB_VERSION_MAJOR == (major) && DB_VERSION_MINOR >= (minor)))
+
+
+/* Parameter lists */
+
+/* In BDB 4.1, DB->open takes a transaction parameter. We'll ignore it
+ when building with 4.0. */
+#if SVN_BDB_VERSION_AT_LEAST(4,1)
+#define SVN_BDB_OPEN_PARAMS(env,txn) (env), (txn)
+#else
+#define SVN_BDB_OPEN_PARAMS(env,txn) (env)
+#endif
+
+/* In BDB 4.3, the error gatherer function grew a new DBENV parameter,
+ and the MSG parameter's type changed. */
+#if SVN_BDB_VERSION_AT_LEAST(4,3)
+/* Prevents most compilers from whining about unused parameters. */
+#define SVN_BDB_ERROR_GATHERER_IGNORE(varname) ((void)(varname))
+#else
+#define bdb_error_gatherer(param1, param2, param3) \
+ bdb_error_gatherer(param2, char *msg)
+#define SVN_BDB_ERROR_GATHERER_IGNORE(varname) ((void)0)
+#endif
+
+/* In BDB 4.3 and later, the file names in DB_ENV->open and DB->open
+ are assumed to be encoded in UTF-8 on Windows. */
+#if defined(WIN32) && SVN_BDB_VERSION_AT_LEAST(4,3)
+#define SVN_BDB_PATH_UTF8 (1)
+#else
+#define SVN_BDB_PATH_UTF8 (0)
+#endif
+
+/* In BDB 4.6, the cursor routines were renamed, and the old names
+ deprecated. */
+#if SVN_BDB_VERSION_AT_LEAST(4,6)
+#define svn_bdb_dbc_close(c) ((c)->close(c))
+#define svn_bdb_dbc_count(c,r,f) ((c)->count(c,r,f))
+#define svn_bdb_dbc_del(c,f) ((c)->del(c,f))
+#define svn_bdb_dbc_dup(c,p,f) ((c)->dup(c,p,f))
+#define svn_bdb_dbc_get(c,k,d,f) ((c)->get(c,k,d,f))
+#define svn_bdb_dbc_pget(c,k,p,d,f) ((c)->pget(c,k,p,d,f))
+#define svn_bdb_dbc_put(c,k,d,f) ((c)->put(c,k,d,f))
+#else
+#define svn_bdb_dbc_close(c) ((c)->c_close(c))
+#define svn_bdb_dbc_count(c,r,f) ((c)->c_count(c,r,f))
+#define svn_bdb_dbc_del(c,f) ((c)->c_del(c,f))
+#define svn_bdb_dbc_dup(c,p,f) ((c)->c_dup(c,p,f))
+#define svn_bdb_dbc_get(c,k,d,f) ((c)->c_get(c,k,d,f))
+#define svn_bdb_dbc_pget(c,k,p,d,f) ((c)->c_pget(c,k,p,d,f))
+#define svn_bdb_dbc_put(c,k,d,f) ((c)->c_put(c,k,d,f))
+#endif
+
+/* Before calling db_create, we must check that the version of the BDB
+ libraries we're linking with is the same as the one we compiled
+ against, because the DB->open call is not binary compatible between
+ BDB 4.0 and 4.1. This function returns DB_OLD_VERSION if the
+ compile-time and run-time versions of BDB don't match. */
+int svn_fs_bdb__check_version(void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_BDB_COMPAT_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/changes-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/changes-table.c
new file mode 100644
index 0000000..80ff468
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/changes-table.c
@@ -0,0 +1,457 @@
+/* changes-table.c : operations on the `changes' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "bdb_compat.h"
+
+#include <apr_hash.h>
+#include <apr_tables.h>
+
+#include "svn_hash.h"
+#include "svn_fs.h"
+#include "svn_pools.h"
+#include "svn_path.h"
+#include "../fs.h"
+#include "../err.h"
+#include "../trail.h"
+#include "../id.h"
+#include "../util/fs_skels.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "dbt.h"
+#include "changes-table.h"
+
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+#include "svn_private_config.h"
+
+
+/*** Creating and opening the changes table. ***/
+
+int
+svn_fs_bdb__open_changes_table(DB **changes_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *changes;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&changes, env, 0));
+
+ /* Enable duplicate keys. This allows us to store the changes
+ one-per-row. Note: this must occur before ->open(). */
+ BDB_ERR(changes->set_flags(changes, DB_DUP));
+
+ BDB_ERR((changes->open)(SVN_BDB_OPEN_PARAMS(changes, NULL),
+ "changes", 0, DB_BTREE,
+ open_flags, 0666));
+
+ *changes_p = changes;
+ return 0;
+}
+
+
+
+/*** Storing and retrieving changes. ***/
+
+svn_error_t *
+svn_fs_bdb__changes_add(svn_fs_t *fs,
+ const char *key,
+ change_t *change,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, value;
+ svn_skel_t *skel;
+
+ /* Convert native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_change_skel(&skel, change, pool));
+
+ /* Store a new record into the database. */
+ svn_fs_base__str_to_dbt(&query, key);
+ svn_fs_base__skel_to_dbt(&value, skel, pool);
+ svn_fs_base__trail_debug(trail, "changes", "put");
+ return BDB_WRAP(fs, N_("creating change"),
+ bfd->changes->put(bfd->changes, trail->db_txn,
+ &query, &value, 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__changes_delete(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ int db_err;
+ DBT query;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ svn_fs_base__trail_debug(trail, "changes", "del");
+ db_err = bfd->changes->del(bfd->changes, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, key), 0);
+
+ /* If there're no changes for KEY, that is acceptable. Any other
+ error should be propagated to the caller, though. */
+ if ((db_err) && (db_err != DB_NOTFOUND))
+ {
+ SVN_ERR(BDB_WRAP(fs, N_("deleting changes"), db_err));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Merge the internal-use-only CHANGE into a hash of public-FS
+ svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
+ single succinct change per path. */
+static svn_error_t *
+fold_change(apr_hash_t *changes,
+ const change_t *change)
+{
+ apr_pool_t *pool = apr_hash_pool_get(changes);
+ svn_fs_path_change2_t *old_change, *new_change;
+ const char *path;
+
+ if ((old_change = svn_hash_gets(changes, change->path)))
+ {
+ /* This path already exists in the hash, so we have to merge
+ this change into the already existing one. */
+
+ /* Since the path already exists in the hash, we don't have to
+ dup the allocation for the path itself. */
+ path = change->path;
+
+ /* Sanity check: only allow NULL node revision ID in the
+ `reset' case. */
+ if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Missing required node revision ID"));
+
+ /* Sanity check: we should be talking about the same node
+ revision ID as our last change except where the last change
+ was a deletion. */
+ if (change->noderev_id
+ && (! svn_fs_base__id_eq(old_change->node_rev_id,
+ change->noderev_id))
+ && (old_change->change_kind != svn_fs_path_change_delete))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: new node revision ID without delete"));
+
+ /* Sanity check: an add, replacement, or reset must be the first
+ thing to follow a deletion. */
+ if ((old_change->change_kind == svn_fs_path_change_delete)
+ && (! ((change->kind == svn_fs_path_change_replace)
+ || (change->kind == svn_fs_path_change_reset)
+ || (change->kind == svn_fs_path_change_add))))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: non-add change on deleted path"));
+
+ /* Sanity check: an add can't follow anything except
+ a delete or reset. */
+ if ((change->kind == svn_fs_path_change_add)
+ && (old_change->change_kind != svn_fs_path_change_delete)
+ && (old_change->change_kind != svn_fs_path_change_reset))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: add change on preexisting path"));
+
+ /* Now, merge that change in. */
+ switch (change->kind)
+ {
+ case svn_fs_path_change_reset:
+ /* A reset here will simply remove the path change from the
+ hash. */
+ old_change = NULL;
+ break;
+
+ case svn_fs_path_change_delete:
+ if (old_change->change_kind == svn_fs_path_change_add)
+ {
+ /* If the path was introduced in this transaction via an
+ add, and we are deleting it, just remove the path
+ altogether. */
+ old_change = NULL;
+ }
+ else
+ {
+ /* A deletion overrules all previous changes. */
+ old_change->change_kind = svn_fs_path_change_delete;
+ old_change->text_mod = change->text_mod;
+ old_change->prop_mod = change->prop_mod;
+ }
+ break;
+
+ case svn_fs_path_change_add:
+ case svn_fs_path_change_replace:
+ /* An add at this point must be following a previous delete,
+ so treat it just like a replace. */
+ old_change->change_kind = svn_fs_path_change_replace;
+ old_change->node_rev_id = svn_fs_base__id_copy(change->noderev_id,
+ pool);
+ old_change->text_mod = change->text_mod;
+ old_change->prop_mod = change->prop_mod;
+ break;
+
+ case svn_fs_path_change_modify:
+ default:
+ if (change->text_mod)
+ old_change->text_mod = TRUE;
+ if (change->prop_mod)
+ old_change->prop_mod = TRUE;
+ break;
+ }
+
+ /* Point our new_change to our (possibly modified) old_change. */
+ new_change = old_change;
+ }
+ else
+ {
+ /* This change is new to the hash, so make a new public change
+ structure from the internal one (in the hash's pool), and dup
+ the path into the hash's pool, too. */
+ new_change = svn_fs__path_change_create_internal(
+ svn_fs_base__id_copy(change->noderev_id, pool),
+ change->kind,
+ pool);
+ new_change->text_mod = change->text_mod;
+ new_change->prop_mod = change->prop_mod;
+ new_change->node_kind = svn_node_unknown;
+ new_change->copyfrom_known = FALSE;
+ path = apr_pstrdup(pool, change->path);
+ }
+
+ /* Add (or update) this path. */
+ svn_hash_sets(changes, path, new_change);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__changes_fetch(apr_hash_t **changes_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBC *cursor;
+ DBT query, result;
+ int db_err = 0, db_c_err = 0;
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_hash_t *changes = apr_hash_make(pool);
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Get a cursor on the first record matching KEY, and then loop over
+ the records, adding them to the return array. */
+ svn_fs_base__trail_debug(trail, "changes", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"),
+ bfd->changes->cursor(bfd->changes, trail->db_txn,
+ &cursor, 0)));
+
+ /* Advance the cursor to the key that we're looking for. */
+ svn_fs_base__str_to_dbt(&query, key);
+ svn_fs_base__result_dbt(&result);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
+ if (! db_err)
+ svn_fs_base__track_dbt(&result, pool);
+
+ while (! db_err)
+ {
+ change_t *change;
+ svn_skel_t *result_skel;
+
+ /* Clear the per-iteration subpool. */
+ svn_pool_clear(subpool);
+
+ /* RESULT now contains a change record associated with KEY. We
+ need to parse that skel into an change_t structure ... */
+ result_skel = svn_skel__parse(result.data, result.size, subpool);
+ if (! result_skel)
+ {
+ err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Error reading changes for key '%s'"),
+ key);
+ goto cleanup;
+ }
+ err = svn_fs_base__parse_change_skel(&change, result_skel, subpool);
+ if (err)
+ goto cleanup;
+
+ /* ... and merge it with our return hash. */
+ err = fold_change(changes, change);
+ if (err)
+ goto cleanup;
+
+ /* Now, if our change was a deletion or replacement, we have to
+ blow away any changes thus far on paths that are (or, were)
+ children of this path.
+ ### i won't bother with another iteration pool here -- at
+ most we talking about a few extra dups of paths into what
+ is already a temporary subpool.
+ */
+ if ((change->kind == svn_fs_path_change_delete)
+ || (change->kind == svn_fs_path_change_replace))
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(subpool, changes);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ /* KEY is the path. */
+ const void *hashkey;
+ apr_ssize_t klen;
+ const char *child_relpath;
+
+ apr_hash_this(hi, &hashkey, &klen, NULL);
+
+ /* If we come across our own path, ignore it.
+ If we come across a child of our path, remove it. */
+ child_relpath = svn_fspath__skip_ancestor(change->path, hashkey);
+ if (child_relpath && *child_relpath)
+ apr_hash_set(changes, hashkey, klen, NULL);
+ }
+ }
+
+ /* Advance the cursor to the next record with this same KEY, and
+ fetch that record. */
+ svn_fs_base__result_dbt(&result);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
+ if (! db_err)
+ svn_fs_base__track_dbt(&result, pool);
+ }
+
+ /* Destroy the per-iteration subpool. */
+ svn_pool_destroy(subpool);
+
+ /* If there are no (more) change records for this KEY, we're
+ finished. Just return the (possibly empty) array. Any other
+ error, however, needs to get handled appropriately. */
+ if (db_err && (db_err != DB_NOTFOUND))
+ err = BDB_WRAP(fs, N_("fetching changes"), db_err);
+
+ cleanup:
+ /* Close the cursor. */
+ db_c_err = svn_bdb_dbc_close(cursor);
+
+ /* If we had an error prior to closing the cursor, return the error. */
+ if (err)
+ return svn_error_trace(err);
+
+ /* If our only error thus far was when we closed the cursor, return
+ that error. */
+ if (db_c_err)
+ SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err));
+
+ /* Finally, set our return variable and get outta here. */
+ *changes_p = changes;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__changes_fetch_raw(apr_array_header_t **changes_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBC *cursor;
+ DBT query, result;
+ int db_err = 0, db_c_err = 0;
+ svn_error_t *err = SVN_NO_ERROR;
+ change_t *change;
+ apr_array_header_t *changes = apr_array_make(pool, 4, sizeof(change));
+
+ /* Get a cursor on the first record matching KEY, and then loop over
+ the records, adding them to the return array. */
+ svn_fs_base__trail_debug(trail, "changes", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"),
+ bfd->changes->cursor(bfd->changes, trail->db_txn,
+ &cursor, 0)));
+
+ /* Advance the cursor to the key that we're looking for. */
+ svn_fs_base__str_to_dbt(&query, key);
+ svn_fs_base__result_dbt(&result);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
+ if (! db_err)
+ svn_fs_base__track_dbt(&result, pool);
+
+ while (! db_err)
+ {
+ svn_skel_t *result_skel;
+
+ /* RESULT now contains a change record associated with KEY. We
+ need to parse that skel into an change_t structure ... */
+ result_skel = svn_skel__parse(result.data, result.size, pool);
+ if (! result_skel)
+ {
+ err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Error reading changes for key '%s'"),
+ key);
+ goto cleanup;
+ }
+ err = svn_fs_base__parse_change_skel(&change, result_skel, pool);
+ if (err)
+ goto cleanup;
+
+ /* ... and add it to our return array. */
+ APR_ARRAY_PUSH(changes, change_t *) = change;
+
+ /* Advance the cursor to the next record with this same KEY, and
+ fetch that record. */
+ svn_fs_base__result_dbt(&result);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
+ if (! db_err)
+ svn_fs_base__track_dbt(&result, pool);
+ }
+
+ /* If there are no (more) change records for this KEY, we're
+ finished. Just return the (possibly empty) array. Any other
+ error, however, needs to get handled appropriately. */
+ if (db_err && (db_err != DB_NOTFOUND))
+ err = BDB_WRAP(fs, N_("fetching changes"), db_err);
+
+ cleanup:
+ /* Close the cursor. */
+ db_c_err = svn_bdb_dbc_close(cursor);
+
+ /* If we had an error prior to closing the cursor, return the error. */
+ if (err)
+ return svn_error_trace(err);
+
+ /* If our only error thus far was when we closed the cursor, return
+ that error. */
+ if (db_c_err)
+ SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err));
+
+ /* Finally, set our return variable and get outta here. */
+ *changes_p = changes;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/changes-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/changes-table.h
new file mode 100644
index 0000000..c9df636
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/changes-table.h
@@ -0,0 +1,94 @@
+/* changes-table.h : internal interface to `changes' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_CHANGES_TABLE_H
+#define SVN_LIBSVN_FS_CHANGES_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_io.h"
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `changes' table in ENV. If CREATE is non-zero, create one
+ if it doesn't exist. Set *CHANGES_P to the new table. Return a
+ Berkeley DB error code. */
+int svn_fs_bdb__open_changes_table(DB **changes_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Add CHANGE as a record to the `changes' table in FS as part of
+ TRAIL, keyed on KEY.
+
+ CHANGE->path is expected to be a canonicalized filesystem path (see
+ svn_fs__canonicalize_abspath).
+
+ Note that because the `changes' table uses duplicate keys, this
+ function will not overwrite prior additions that have the KEY
+ key, but simply adds this new record alongside previous ones. */
+svn_error_t *svn_fs_bdb__changes_add(svn_fs_t *fs,
+ const char *key,
+ change_t *change,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Remove all changes associated with KEY from the `changes' table in
+ FS, as part of TRAIL. */
+svn_error_t *svn_fs_bdb__changes_delete(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Return a hash *CHANGES_P, keyed on const char * paths, and
+ containing svn_fs_path_change2_t * values representing summarized
+ changed records associated with KEY in FS, as part of TRAIL.
+ Allocate the array and its items in POOL. */
+svn_error_t *svn_fs_bdb__changes_fetch(apr_hash_t **changes_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Return an array *CHANGES_P of change_t * items representing
+ all the change records associated with KEY in FS, as part of TRAIL.
+ Allocate the array and its items in POOL. */
+svn_error_t *svn_fs_bdb__changes_fetch_raw(apr_array_header_t **changes_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_CHANGES_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/checksum-reps-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/checksum-reps-table.c
new file mode 100644
index 0000000..f4a34c3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/checksum-reps-table.c
@@ -0,0 +1,208 @@
+/* checksum-reps-table.c : operations on the `checksum-reps' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_strings.h>
+
+#include "bdb_compat.h"
+#include "../fs.h"
+#include "../err.h"
+#include "../key-gen.h"
+#include "dbt.h"
+#include "../trail.h"
+#include "bdb-err.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "checksum-reps-table.h"
+
+#include "svn_private_config.h"
+
+
+int svn_fs_bdb__open_checksum_reps_table(DB **checksum_reps_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *checksum_reps;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&checksum_reps, env, 0));
+ error = (checksum_reps->open)(SVN_BDB_OPEN_PARAMS(checksum_reps, NULL),
+ "checksum-reps", 0, DB_BTREE,
+ open_flags, 0666);
+
+ /* Create the checksum-reps table if it doesn't exist. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(checksum_reps->close(checksum_reps, 0));
+ return svn_fs_bdb__open_checksum_reps_table(checksum_reps_p, env, TRUE);
+ }
+
+ /* Create the initial `next-key' table entry. */
+ if (create)
+ {
+ DBT key, value;
+ BDB_ERR(checksum_reps->put(checksum_reps, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ BDB_ERR(error);
+
+ *checksum_reps_p = checksum_reps;
+ return 0;
+}
+
+svn_error_t *svn_fs_bdb__get_checksum_rep(const char **rep_key,
+ svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+
+ /* We only allow SHA1 checksums in this table. */
+ if (checksum->kind != svn_checksum_sha1)
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
+ _("Only SHA1 checksums can be used as keys in the "
+ "checksum-reps table.\n"));
+
+ svn_fs_base__trail_debug(trail, "checksum-reps", "get");
+ db_err = bfd->checksum_reps->get(bfd->checksum_reps, trail->db_txn,
+ svn_fs_base__checksum_to_dbt(&key, checksum),
+ svn_fs_base__result_dbt(&value), 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_no_such_checksum_rep(fs, checksum);
+
+ *rep_key = apr_pstrmemdup(pool, value.data, value.size);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_fs_bdb__set_checksum_rep(svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+
+ /* We only allow SHA1 checksums in this table. */
+ if (checksum->kind != svn_checksum_sha1)
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
+ _("Only SHA1 checksums can be used as keys in the "
+ "checksum-reps table.\n"));
+
+ /* Create a key from our CHECKSUM. */
+ svn_fs_base__checksum_to_dbt(&key, checksum);
+
+ /* Check to see if we already have a mapping for CHECKSUM. If so,
+ and the value is the same one we were about to write, that's
+ cool -- just do nothing. If, however, the value is *different*,
+ that's a red flag! */
+ svn_fs_base__trail_debug(trail, "checksum-reps", "get");
+ db_err = bfd->checksum_reps->get(bfd->checksum_reps, trail->db_txn,
+ &key, svn_fs_base__result_dbt(&value), 0);
+ svn_fs_base__track_dbt(&value, pool);
+ if (db_err != DB_NOTFOUND)
+ {
+ const char *sum_str = svn_checksum_to_cstring_display(checksum, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Representation key for checksum '%s' exists in filesystem '%s'."),
+ sum_str, fs->path);
+ }
+
+ /* Create a value from our REP_KEY, and add this record to the table. */
+ svn_fs_base__str_to_dbt(&value, rep_key);
+ svn_fs_base__trail_debug(trail, "checksum-reps", "put");
+ SVN_ERR(BDB_WRAP(fs, N_("storing checksum-reps record"),
+ bfd->checksum_reps->put(bfd->checksum_reps, trail->db_txn,
+ &key, &value, 0)));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_fs_bdb__delete_checksum_rep(svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+
+ /* We only allow SHA1 checksums in this table. */
+ if (checksum->kind != svn_checksum_sha1)
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
+ _("Only SHA1 checksums can be used as keys in the "
+ "checksum-reps table.\n"));
+
+ svn_fs_base__checksum_to_dbt(&key, checksum);
+ svn_fs_base__trail_debug(trail, "checksum-reps", "del");
+ SVN_ERR(BDB_WRAP(fs, N_("deleting entry from 'checksum-reps' table"),
+ bfd->checksum_reps->del(bfd->checksum_reps,
+ trail->db_txn, &key, 0)));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_fs_bdb__reserve_rep_reuse_id(const char **id_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ apr_size_t len;
+ char next_key[MAX_KEY_SIZE];
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+
+ /* Get the current value associated with the `next-key' key in the
+ `checksum-reps' table. */
+ svn_fs_base__trail_debug(trail, "checksum-reps", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("allocating new representation reuse ID "
+ "(getting 'next-key')"),
+ bfd->checksum_reps->get(bfd->checksum_reps, trail->db_txn,
+ &query,
+ svn_fs_base__result_dbt(&result),
+ 0)));
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* Set our return value. */
+ *id_p = apr_pstrmemdup(pool, result.data, result.size);
+
+ /* Bump to future key. */
+ len = result.size;
+ svn_fs_base__next_key(result.data, &len, next_key);
+ svn_fs_base__trail_debug(trail, "checksum_reps", "put");
+ db_err = bfd->checksum_reps->put(bfd->checksum_reps, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query,
+ NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&result, next_key),
+ 0);
+
+ return BDB_WRAP(fs, N_("bumping next representation reuse ID"), db_err);
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/checksum-reps-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/checksum-reps-table.h
new file mode 100644
index 0000000..ccdcd48
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/checksum-reps-table.h
@@ -0,0 +1,89 @@
+/* checksum-reps-table.h : internal interface to ops on `checksum-reps' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_CHECKSUM_REPS_TABLE_H
+#define SVN_LIBSVN_FS_CHECKSUM_REPS_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "svn_checksum.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `checksum-reps' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *CHECKSUM_REPS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_checksum_reps_table(DB **checksum_reps_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+/* Set *REP_KEY to the representation key stored as the value of key
+ CHECKSUM in the `checksum-reps' table. Do this as part of TRAIL.
+ Use POOL for allocations.
+
+ If no such node revision ID is stored for CHECKSUM, return
+ SVN_ERR_FS_NO_SUCH_CHECKSUM_REP. */
+svn_error_t *svn_fs_bdb__get_checksum_rep(const char **rep_key,
+ svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Store in the `checksum-reps' table a mapping of CHECKSUM to
+ representation key REP_KEY in FS. Do this as part of TRAIL. Use
+ POOL for temporary allocations.
+
+ WARNING: NEVER store a record that maps a checksum to a mutable
+ representation. Ever. Under pain of dismemberment and death. */
+svn_error_t *svn_fs_bdb__set_checksum_rep(svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Delete from the `checksum-reps' table the mapping of CHECKSUM to a
+ representation key in FS. Do this as part of TRAIL. Use POOL for
+ temporary allocations. */
+svn_error_t *svn_fs_bdb__delete_checksum_rep(svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Reserve a unique reuse ID in the `checksum-reps' table in FS for a
+ new instance of a re-used representation as part of TRAIL. Return
+ the slot's id in *REUSE_ID_P, allocated in POOL. */
+svn_error_t *svn_fs_bdb__reserve_rep_reuse_id(const char **reuse_id_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_CHECKSUM_REPS_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/copies-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/copies-table.c
new file mode 100644
index 0000000..7bf6ca8
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/copies-table.c
@@ -0,0 +1,210 @@
+/* copies-table.c : operations on the `copies' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+
+#include "bdb_compat.h"
+
+#include "private/svn_skel.h"
+
+#include "../fs.h"
+#include "../err.h"
+#include "../key-gen.h"
+#include "dbt.h"
+#include "../util/fs_skels.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "copies-table.h"
+#include "rev-table.h"
+
+#include "svn_private_config.h"
+
+
+int
+svn_fs_bdb__open_copies_table(DB **copies_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *copies;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&copies, env, 0));
+ BDB_ERR((copies->open)(SVN_BDB_OPEN_PARAMS(copies, NULL),
+ "copies", 0, DB_BTREE,
+ open_flags, 0666));
+
+ /* Create the initial `next-key' table entry. */
+ if (create)
+ {
+ DBT key, value;
+ BDB_ERR(copies->put(copies, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ *copies_p = copies;
+ return 0;
+}
+
+
+/* Store COPY as a copy named COPY_ID in FS as part of TRAIL. */
+/* ### only has one caller; might not need to be abstracted */
+static svn_error_t *
+put_copy(svn_fs_t *fs,
+ const copy_t *copy,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ svn_skel_t *copy_skel;
+ DBT key, value;
+
+ /* Convert native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_copy_skel(&copy_skel, copy, pool));
+
+ /* Only in the context of this function do we know that the DB call
+ will not attempt to modify COPY_ID, so the cast belongs here. */
+ svn_fs_base__str_to_dbt(&key, copy_id);
+ svn_fs_base__skel_to_dbt(&value, copy_skel, pool);
+ svn_fs_base__trail_debug(trail, "copies", "put");
+ return BDB_WRAP(fs, N_("storing copy record"),
+ bfd->copies->put(bfd->copies, trail->db_txn,
+ &key, &value, 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__reserve_copy_id(const char **id_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ apr_size_t len;
+ char next_key[MAX_KEY_SIZE];
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+
+ /* Get the current value associated with the `next-key' key in the
+ copies table. */
+ svn_fs_base__trail_debug(trail, "copies", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("allocating new copy ID (getting 'next-key')"),
+ bfd->copies->get(bfd->copies, trail->db_txn, &query,
+ svn_fs_base__result_dbt(&result),
+ 0)));
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* Set our return value. */
+ *id_p = apr_pstrmemdup(pool, result.data, result.size);
+
+ /* Bump to future key. */
+ len = result.size;
+ svn_fs_base__next_key(result.data, &len, next_key);
+ svn_fs_base__trail_debug(trail, "copies", "put");
+ db_err = bfd->copies->put(bfd->copies, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&result, next_key),
+ 0);
+
+ return BDB_WRAP(fs, N_("bumping next copy key"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__create_copy(svn_fs_t *fs,
+ const char *copy_id,
+ const char *src_path,
+ const char *src_txn_id,
+ const svn_fs_id_t *dst_noderev_id,
+ copy_kind_t kind,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ copy_t copy;
+ copy.kind = kind;
+ copy.src_path = src_path;
+ copy.src_txn_id = src_txn_id;
+ copy.dst_noderev_id = dst_noderev_id;
+ return put_copy(fs, &copy, copy_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_bdb__delete_copy(svn_fs_t *fs,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&key, copy_id);
+ svn_fs_base__trail_debug(trail, "copies", "del");
+ db_err = bfd->copies->del(bfd->copies, trail->db_txn, &key, 0);
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_no_such_copy(fs, copy_id);
+ return BDB_WRAP(fs, N_("deleting entry from 'copies' table"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__get_copy(copy_t **copy_p,
+ svn_fs_t *fs,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+ svn_skel_t *skel;
+ copy_t *copy;
+
+ /* Only in the context of this function do we know that the DB call
+ will not attempt to modify copy_id, so the cast belongs here. */
+ svn_fs_base__trail_debug(trail, "copies", "get");
+ db_err = bfd->copies->get(bfd->copies, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, copy_id),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_no_such_copy(fs, copy_id);
+ SVN_ERR(BDB_WRAP(fs, N_("reading copy"), db_err));
+
+ /* Unparse COPY skel */
+ skel = svn_skel__parse(value.data, value.size, pool);
+ if (! skel)
+ return svn_fs_base__err_corrupt_copy(fs, copy_id);
+
+ /* Convert skel to native type. */
+ SVN_ERR(svn_fs_base__parse_copy_skel(&copy, skel, pool));
+ *copy_p = copy;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/copies-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/copies-table.h
new file mode 100644
index 0000000..08ab139
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/copies-table.h
@@ -0,0 +1,93 @@
+/* copies-table.h : internal interface to ops on `copies' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_COPIES_TABLE_H
+#define SVN_LIBSVN_FS_COPIES_TABLE_H
+
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `copies' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *COPIES_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_copies_table(DB **copies_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+/* Reserve a slot in the `copies' table in FS for a new copy operation
+ as part of TRAIL. Return the slot's id in *COPY_ID_P, allocated in
+ POOL. */
+svn_error_t *svn_fs_bdb__reserve_copy_id(const char **copy_id_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Create a new copy with id COPY_ID in FS as part of TRAIL.
+ SRC_PATH/SRC_TXN_ID are the path/transaction ID (respectively) of
+ the copy source, and DST_NODEREV_ID is the node revision id of the
+ copy destination. KIND describes the type of copy operation.
+
+ SRC_PATH is expected to be a canonicalized filesystem path (see
+ svn_fs__canonicalize_abspath).
+
+ COPY_ID should generally come from a call to
+ svn_fs_bdb__reserve_copy_id(). */
+svn_error_t *svn_fs_bdb__create_copy(svn_fs_t *fs,
+ const char *copy_id,
+ const char *src_path,
+ const char *src_txn_id,
+ const svn_fs_id_t *dst_noderev_id,
+ copy_kind_t kind,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Remove the copy whose name is COPY_ID from the `copies' table of
+ FS, as part of TRAIL. If there is no such copy,
+ SVN_ERR_FS_NO_SUCH_COPY is the error returned. */
+svn_error_t *svn_fs_bdb__delete_copy(svn_fs_t *fs,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Retrieve the copy *COPY_P named COPY_ID from the `copies' table of
+ FS, as part of TRAIL. Perform all allocations in POOL. If
+ there is no such copy, SVN_ERR_FS_NO_SUCH_COPY is the error
+ returned. */
+svn_error_t *svn_fs_bdb__get_copy(copy_t **copy_p,
+ svn_fs_t *fs,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_COPIES_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/dbt.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/dbt.c
new file mode 100644
index 0000000..a18ba47
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/dbt.c
@@ -0,0 +1,170 @@
+/* dbt.c --- DBT-frobbing functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <apr_pools.h>
+#include <apr_md5.h>
+#include <apr_sha1.h>
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "../id.h"
+#include "dbt.h"
+
+
+DBT *
+svn_fs_base__clear_dbt(DBT *dbt)
+{
+ memset(dbt, 0, sizeof(*dbt));
+
+ return dbt;
+}
+
+
+DBT *svn_fs_base__nodata_dbt(DBT *dbt)
+{
+ svn_fs_base__clear_dbt(dbt);
+
+ /* A `nodata' dbt is one which retrieves zero bytes from offset zero,
+ and stores them in a zero-byte buffer in user-allocated memory. */
+ dbt->flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL);
+ dbt->doff = dbt->dlen = 0;
+
+ return dbt;
+}
+
+
+DBT *
+svn_fs_base__set_dbt(DBT *dbt, const void *data, apr_size_t size)
+{
+ svn_fs_base__clear_dbt(dbt);
+
+ dbt->data = (void *) data;
+ dbt->size = (u_int32_t) size;
+
+ return dbt;
+}
+
+
+DBT *
+svn_fs_base__result_dbt(DBT *dbt)
+{
+ svn_fs_base__clear_dbt(dbt);
+ dbt->flags |= DB_DBT_MALLOC;
+
+ return dbt;
+}
+
+
+/* An APR pool cleanup function that simply applies `free' to its
+ argument. */
+static apr_status_t
+apr_free_cleanup(void *arg)
+{
+ free(arg);
+
+ return 0;
+}
+
+
+DBT *
+svn_fs_base__track_dbt(DBT *dbt, apr_pool_t *pool)
+{
+ if (dbt->data)
+ apr_pool_cleanup_register(pool, dbt->data, apr_free_cleanup,
+ apr_pool_cleanup_null);
+
+ return dbt;
+}
+
+
+DBT *
+svn_fs_base__recno_dbt(DBT *dbt, db_recno_t *recno)
+{
+ svn_fs_base__set_dbt(dbt, recno, sizeof(*recno));
+ dbt->ulen = dbt->size;
+ dbt->flags |= DB_DBT_USERMEM;
+
+ return dbt;
+}
+
+
+int
+svn_fs_base__compare_dbt(const DBT *a, const DBT *b)
+{
+ int common_size = a->size > b->size ? b->size : a->size;
+ int cmp = memcmp(a->data, b->data, common_size);
+
+ if (cmp)
+ return cmp;
+ else
+ return a->size - b->size;
+}
+
+
+
+/* Building DBT's from interesting things. */
+
+
+/* Set DBT to the unparsed form of ID; allocate memory from POOL.
+ Return DBT. */
+DBT *
+svn_fs_base__id_to_dbt(DBT *dbt,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ svn_string_t *unparsed_id = svn_fs_base__id_unparse(id, pool);
+ svn_fs_base__set_dbt(dbt, unparsed_id->data, unparsed_id->len);
+ return dbt;
+}
+
+
+/* Set DBT to the unparsed form of SKEL; allocate memory from POOL. */
+DBT *
+svn_fs_base__skel_to_dbt(DBT *dbt,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *unparsed_skel = svn_skel__unparse(skel, pool);
+ svn_fs_base__set_dbt(dbt, unparsed_skel->data, unparsed_skel->len);
+ return dbt;
+}
+
+
+/* Set DBT to the text of the null-terminated string STR. DBT will
+ refer to STR's storage. Return DBT. */
+DBT *
+svn_fs_base__str_to_dbt(DBT *dbt, const char *str)
+{
+ svn_fs_base__set_dbt(dbt, str, strlen(str));
+ return dbt;
+}
+
+DBT *
+svn_fs_base__checksum_to_dbt(DBT *dbt, svn_checksum_t *checksum)
+{
+ svn_fs_base__set_dbt(dbt, checksum->digest, svn_checksum_size(checksum));
+
+ return dbt;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/dbt.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/dbt.h
new file mode 100644
index 0000000..db93d77
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/dbt.h
@@ -0,0 +1,120 @@
+/* dbt.h --- interface to DBT-frobbing functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_DBT_H
+#define SVN_LIBSVN_FS_DBT_H
+
+#include <apr_pools.h>
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+#include "private/svn_skel.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Set all fields of DBT to zero. Return DBT. */
+DBT *svn_fs_base__clear_dbt(DBT *dbt);
+
+
+/* Set DBT to retrieve no data. This is useful when you're just
+ probing the table to see if an entry exists, or to find a key, but
+ don't care what the value is. Return DBT. */
+DBT *svn_fs_base__nodata_dbt(DBT *dbt);
+
+
+/* Set DBT to refer to the SIZE bytes at DATA. Return DBT. */
+DBT *svn_fs_base__set_dbt(DBT *dbt, const void *data, apr_size_t size);
+
+
+/* Prepare DBT to hold data returned from Berkeley DB. Return DBT.
+
+ Clear all its fields to zero, but set the DB_DBT_MALLOC flag,
+ requesting that Berkeley DB place the returned data in a freshly
+ malloc'd block. If the database operation succeeds, the caller
+ then owns the data block, and is responsible for making sure it
+ gets freed.
+
+ You can use this with svn_fs_base__track_dbt:
+
+ svn_fs_base__result_dbt (&foo);
+ ... some Berkeley DB operation that puts data in foo ...
+ svn_fs_base__track_dbt (&foo, pool);
+
+ This arrangement is:
+ - thread-safe --- the returned data is allocated via malloc, and
+ won't be overwritten if some other thread performs an operation
+ on the same table. See the explanation of ``Retrieved key/data
+ permanence'' in the section of the Berkeley DB manual on the DBT
+ type.
+ - pool-friendly --- the data returned by Berkeley DB is now guaranteed
+ to be freed when POOL is cleared. */
+DBT *svn_fs_base__result_dbt(DBT *dbt);
+
+/* Arrange for POOL to `track' DBT's data: when POOL is cleared,
+ DBT->data will be freed, using `free'. If DBT->data is zero,
+ do nothing.
+
+ This is meant for use with svn_fs_base__result_dbt; see the explanation
+ there. */
+DBT *svn_fs_base__track_dbt(DBT *dbt, apr_pool_t *pool);
+
+
+/* Prepare DBT for use as a key into a RECNO table. This call makes
+ DBT refer to the db_recno_t pointed to by RECNO as its buffer; the
+ record number you assign to *RECNO will be the table key. */
+DBT *svn_fs_base__recno_dbt(DBT *dbt, db_recno_t *recno);
+
+
+/* Compare two DBT values in byte-by-byte lexicographic order. */
+int svn_fs_base__compare_dbt(const DBT *a, const DBT *b);
+
+
+/* Set DBT to the unparsed form of ID; allocate memory from POOL.
+ Return DBT. */
+DBT *svn_fs_base__id_to_dbt(DBT *dbt, const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+
+/* Set DBT to the unparsed form of SKEL; allocate memory from POOL.
+ Return DBT. */
+DBT *svn_fs_base__skel_to_dbt(DBT *dbt, svn_skel_t *skel, apr_pool_t *pool);
+
+
+/* Set DBT to the text of the null-terminated string STR. DBT will
+ refer to STR's storage. Return DBT. */
+DBT *svn_fs_base__str_to_dbt(DBT *dbt, const char *str);
+
+
+/* Set DBT to the bytes contained by CHECKSUM. DBT will refer to CHECKSUM's
+ storage. Return DBT.*/
+DBT *svn_fs_base__checksum_to_dbt(DBT* dbt, svn_checksum_t *checksum);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_DBT_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/env.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/env.c
new file mode 100644
index 0000000..557c9dc
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/env.c
@@ -0,0 +1,719 @@
+/* env.h : managing the BDB environment
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <assert.h>
+
+#include <apr.h>
+#if APR_HAS_THREADS
+#include <apr_thread_proc.h>
+#include <apr_time.h>
+#endif
+
+#include <apr_strings.h>
+#include <apr_hash.h>
+
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_utf.h"
+#include "private/svn_atomic.h"
+#include "private/svn_mutex.h"
+
+#include "bdb-err.h"
+#include "bdb_compat.h"
+
+#include "env.h"
+
+/* A note about the BDB environment descriptor cache.
+
+ With the advent of DB_REGISTER in BDB-4.4, a process may only open
+ an environment handle once. This means that we must maintain a
+ cache of open environment handles, with reference counts. We
+ allocate each environment descriptor (a bdb_env_t) from its own
+ pool. The cache itself (and the cache pool) are shared between
+ threads, so all direct or indirect access to the pool is serialized
+ with a global mutex.
+
+ Because several threads can now use the same DB_ENV handle, we must
+ use the DB_THREAD flag when opening the environments, otherwise the
+ env handles (and all of libsvn_fs_base) won't be thread-safe.
+
+ If we use DB_THREAD, however, all of the code that reads data from
+ the database without a cursor must use either DB_DBT_MALLOC,
+ DB_DBT_REALLOC, or DB_DBT_USERMEM, as described in the BDB
+ documentation.
+
+ (Oh, yes -- using DB_THREAD might not work on some systems. But
+ then, it's quite probable that threading is seriously broken on
+ those systems anyway, so we'll rely on APR_HAS_THREADS.)
+*/
+
+
+/* The cache key for a Berkeley DB environment descriptor. This is a
+ combination of the device ID and INODE number of the Berkeley DB
+ config file.
+
+ XXX FIXME: Although the dev+inode combination is supposed do be
+ unique, apparently that's not always the case with some remote
+ filesystems. We /should/ be safe using this as a unique hash key,
+ because the database must be on a local filesystem. We can hope,
+ anyway. */
+typedef struct bdb_env_key_t
+{
+ apr_dev_t device;
+ apr_ino_t inode;
+} bdb_env_key_t;
+
+/* The cached Berkeley DB environment descriptor. */
+struct bdb_env_t
+{
+ /**************************************************************************/
+ /* Error Reporting */
+
+ /* A (char *) casted pointer to this structure is passed to BDB's
+ set_errpfx(), which treats it as a NUL-terminated character
+ string to prefix all BDB error messages. However, svn also
+ registers bdb_error_gatherer() as an error handler with
+ set_errcall() which turns off BDB's default printing of errors to
+ stderr and anytime thereafter when BDB reports an error and
+ before the BDB function returns, it calls bdb_error_gatherer()
+ and passes the same error prefix (char *) pointer given to
+ set_errpfx(). The bdb_error_gatherer() callback casts the
+ (char *) it back to a (bdb_env_t *).
+
+ To avoid problems should BDB ever try to interpret our baton as a
+ string, the first field in the structure is a char
+ errpfx_string[]. Initializers of this structure must strcpy the
+ value of BDB_ERRPFX_STRING into this array. */
+ char errpfx_string[sizeof(BDB_ERRPFX_STRING)];
+
+ /* Extended error information. */
+#if APR_HAS_THREADS
+ apr_threadkey_t *error_info; /* Points to a bdb_error_info_t. */
+#else
+ bdb_error_info_t error_info;
+#endif
+
+ /**************************************************************************/
+ /* BDB Environment Cache */
+
+ /* The Berkeley DB environment. */
+ DB_ENV *env;
+
+ /* The flags with which this environment was opened. Reopening the
+ environment with a different set of flags is not allowed. Trying
+ to change the state of the DB_PRIVATE flag is an especially bad
+ idea, so svn_fs_bdb__open() forbids any flag changes. */
+ u_int32_t flags;
+
+ /* The home path of this environment; a canonical SVN path encoded in
+ UTF-8 and allocated from this decriptor's pool. */
+ const char *path;
+
+ /* The home path of this environment, in the form expected by BDB. */
+ const char *path_bdb;
+
+ /* The reference count for this environment handle; this is
+ essentially the difference between the number of calls to
+ svn_fs_bdb__open and svn_fs_bdb__close. */
+ unsigned refcount;
+
+ /* If this flag is TRUE, someone has detected that the environment
+ descriptor is in a panicked state and should be removed from the
+ cache.
+
+ Note 1: Once this flag is set, it must not be cleared again.
+
+ Note 2: Unlike other fields in this structure, this field is not
+ protected by the cache mutex on threaded platforms, and
+ should only be accesses via the svn_atomic functions. */
+ volatile svn_atomic_t panic;
+
+ /* The key for the environment descriptor cache. */
+ bdb_env_key_t key;
+
+ /* The handle of the open DB_CONFIG file.
+
+ We keep the DB_CONFIG file open in this process as long as the
+ environment handle itself is open. On Windows, this guarantees
+ that the cache key remains unique; here's what the Windows SDK
+ docs have to say about the file index (interpreted as the INODE
+ number by APR):
+
+ "This value is useful only while the file is open by at least
+ one process. If no processes have it open, the index may
+ change the next time the file is opened."
+
+ Now, we certainly don't want a unique key to change while it's
+ being used, do we... */
+ apr_file_t *dbconfig_file;
+
+ /* The pool associated with this environment descriptor.
+
+ Because the descriptor has a life of its own, the structure and
+ any data associated with it are allocated from their own global
+ pool. */
+ apr_pool_t *pool;
+
+};
+
+
+#if APR_HAS_THREADS
+/* Get the thread-specific error info from a bdb_env_t. */
+static bdb_error_info_t *
+get_error_info(const bdb_env_t *bdb)
+{
+ void *priv;
+ apr_threadkey_private_get(&priv, bdb->error_info);
+ if (!priv)
+ {
+ priv = calloc(1, sizeof(bdb_error_info_t));
+ apr_threadkey_private_set(priv, bdb->error_info);
+ }
+ return priv;
+}
+#else
+#define get_error_info(bdb) (&(bdb)->error_info)
+#endif /* APR_HAS_THREADS */
+
+
+/* Convert a BDB error to a Subversion error. */
+static svn_error_t *
+convert_bdb_error(bdb_env_t *bdb, int db_err)
+{
+ if (db_err)
+ {
+ bdb_env_baton_t bdb_baton;
+ bdb_baton.env = bdb->env;
+ bdb_baton.bdb = bdb;
+ bdb_baton.error_info = get_error_info(bdb);
+ SVN_BDB_ERR(&bdb_baton, db_err);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* Allocating an appropriate Berkeley DB environment object. */
+
+/* BDB error callback. See bdb_error_info_t in env.h for more info.
+ Note: bdb_error_gatherer is a macro with BDB < 4.3, so be careful how
+ you use it! */
+static void
+bdb_error_gatherer(const DB_ENV *dbenv, const char *baton, const char *msg)
+{
+ /* See the documentation at bdb_env_t's definition why the
+ (bdb_env_t *) cast is safe and why it is done. */
+ bdb_error_info_t *error_info = get_error_info((const bdb_env_t *) baton);
+ svn_error_t *new_err;
+
+ SVN_BDB_ERROR_GATHERER_IGNORE(dbenv);
+
+ new_err = svn_error_createf(APR_SUCCESS, NULL, "bdb: %s", msg);
+ if (error_info->pending_errors)
+ svn_error_compose(error_info->pending_errors, new_err);
+ else
+ error_info->pending_errors = new_err;
+
+ if (error_info->user_callback)
+ error_info->user_callback(NULL, (char *)msg); /* ### I hate this cast... */
+}
+
+
+/* Pool cleanup for the cached environment descriptor. */
+static apr_status_t
+cleanup_env(void *data)
+{
+ bdb_env_t *bdb = data;
+ bdb->pool = NULL;
+ bdb->dbconfig_file = NULL; /* will be closed during pool destruction */
+#if APR_HAS_THREADS
+ apr_threadkey_private_delete(bdb->error_info);
+#endif /* APR_HAS_THREADS */
+
+ /* If there are no references to this descriptor, free its memory here,
+ so that we don't leak it if create_env returns an error.
+ See bdb_close, which takes care of freeing this memory if the
+ environment is still open when the cache is destroyed. */
+ if (!bdb->refcount)
+ free(data);
+
+ return APR_SUCCESS;
+}
+
+#if APR_HAS_THREADS
+/* This cleanup is the fall back plan. If the thread exits and the
+ environment hasn't been closed it's responsible for cleanup of the
+ thread local error info variable, which would otherwise be leaked.
+ Normally it will not be called, because svn_fs_bdb__close will
+ set the thread's error info to NULL after cleaning it up. */
+static void
+cleanup_error_info(void *baton)
+{
+ bdb_error_info_t *error_info = baton;
+
+ if (error_info)
+ svn_error_clear(error_info->pending_errors);
+
+ free(error_info);
+}
+#endif /* APR_HAS_THREADS */
+
+/* Create a Berkeley DB environment. */
+static svn_error_t *
+create_env(bdb_env_t **bdbp, const char *path, apr_pool_t *pool)
+{
+ int db_err;
+ bdb_env_t *bdb;
+ const char *path_bdb;
+ char *tmp_path, *tmp_path_bdb;
+ apr_size_t path_size, path_bdb_size;
+
+#if SVN_BDB_PATH_UTF8
+ path_bdb = svn_dirent_local_style(path, pool);
+#else
+ SVN_ERR(svn_utf_cstring_from_utf8(&path_bdb,
+ svn_dirent_local_style(path, pool),
+ pool));
+#endif
+
+ /* Allocate the whole structure, including strings, from the heap,
+ because it must survive the cache pool cleanup. */
+ path_size = strlen(path) + 1;
+ path_bdb_size = strlen(path_bdb) + 1;
+ /* Using calloc() to ensure the padding bytes in bdb->key (which is used as
+ * a hash key) are zeroed. */
+ bdb = calloc(1, sizeof(*bdb) + path_size + path_bdb_size);
+
+ /* We must initialize this now, as our callers may assume their bdb
+ pointer is valid when checking for errors. */
+ apr_pool_cleanup_register(pool, bdb, cleanup_env, apr_pool_cleanup_null);
+ apr_cpystrn(bdb->errpfx_string, BDB_ERRPFX_STRING,
+ sizeof(bdb->errpfx_string));
+ bdb->path = tmp_path = (char*)(bdb + 1);
+ bdb->path_bdb = tmp_path_bdb = tmp_path + path_size;
+ apr_cpystrn(tmp_path, path, path_size);
+ apr_cpystrn(tmp_path_bdb, path_bdb, path_bdb_size);
+ bdb->pool = pool;
+ *bdbp = bdb;
+
+#if APR_HAS_THREADS
+ {
+ apr_status_t apr_err = apr_threadkey_private_create(&bdb->error_info,
+ cleanup_error_info,
+ pool);
+ if (apr_err)
+ return svn_error_create(apr_err, NULL,
+ "Can't allocate thread-specific storage"
+ " for the Berkeley DB environment descriptor");
+ }
+#endif /* APR_HAS_THREADS */
+
+ db_err = db_env_create(&(bdb->env), 0);
+ if (!db_err)
+ {
+ /* See the documentation at bdb_env_t's definition why the
+ (char *) cast is safe and why it is done. */
+ bdb->env->set_errpfx(bdb->env, (char *) bdb);
+
+ /* bdb_error_gatherer is in parens to stop macro expansion. */
+ bdb->env->set_errcall(bdb->env, (bdb_error_gatherer));
+
+ /* Needed on Windows in case Subversion and Berkeley DB are using
+ different C runtime libraries */
+ db_err = bdb->env->set_alloc(bdb->env, malloc, realloc, free);
+
+ /* If we detect a deadlock, select a transaction to abort at
+ random from those participating in the deadlock. */
+ if (!db_err)
+ db_err = bdb->env->set_lk_detect(bdb->env, DB_LOCK_RANDOM);
+ }
+ return convert_bdb_error(bdb, db_err);
+}
+
+
+
+/* The environment descriptor cache. */
+
+/* The global pool used for this cache. */
+static apr_pool_t *bdb_cache_pool = NULL;
+
+/* The cache. The items are bdb_env_t structures. */
+static apr_hash_t *bdb_cache = NULL;
+
+/* The mutex that protects bdb_cache. */
+static svn_mutex__t *bdb_cache_lock = NULL;
+
+/* Cleanup callback to NULL out the cache, so we don't try to use it after
+ the pool has been cleared during global shutdown. */
+static apr_status_t
+clear_cache(void *data)
+{
+ bdb_cache = NULL;
+ bdb_cache_lock = NULL;
+ return APR_SUCCESS;
+}
+
+static volatile svn_atomic_t bdb_cache_state = 0;
+
+static svn_error_t *
+bdb_init_cb(void *baton, apr_pool_t *pool)
+{
+ bdb_cache_pool = svn_pool_create(pool);
+ bdb_cache = apr_hash_make(bdb_cache_pool);
+
+ SVN_ERR(svn_mutex__init(&bdb_cache_lock, TRUE, bdb_cache_pool));
+ apr_pool_cleanup_register(bdb_cache_pool, NULL, clear_cache,
+ apr_pool_cleanup_null);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_bdb__init(apr_pool_t* pool)
+{
+ return svn_atomic__init_once(&bdb_cache_state, bdb_init_cb, NULL, pool);
+}
+
+/* Construct a cache key for the BDB environment at PATH in *KEYP.
+ if DBCONFIG_FILE is not NULL, return the opened file handle.
+ Allocate from POOL. */
+static svn_error_t *
+bdb_cache_key(bdb_env_key_t *keyp, apr_file_t **dbconfig_file,
+ const char *path, apr_pool_t *pool)
+{
+ const char *dbcfg_file_name = svn_dirent_join(path, BDB_CONFIG_FILE, pool);
+ apr_file_t *dbcfg_file;
+ apr_status_t apr_err;
+ apr_finfo_t finfo;
+
+ SVN_ERR(svn_io_file_open(&dbcfg_file, dbcfg_file_name,
+ APR_READ, APR_OS_DEFAULT, pool));
+
+ apr_err = apr_file_info_get(&finfo, APR_FINFO_DEV | APR_FINFO_INODE,
+ dbcfg_file);
+ if (apr_err)
+ return svn_error_wrap_apr
+ (apr_err, "Can't create BDB environment cache key");
+
+ /* Make sure that any padding in the key is always cleared, so that
+ the key's hash deterministic. */
+ memset(keyp, 0, sizeof *keyp);
+ keyp->device = finfo.device;
+ keyp->inode = finfo.inode;
+
+ if (dbconfig_file)
+ *dbconfig_file = dbcfg_file;
+ else
+ apr_file_close(dbcfg_file);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Find a BDB environment in the cache.
+ Return the environment's panic state in *PANICP.
+
+ Note: You MUST acquire the cache mutex before calling this function.
+*/
+static bdb_env_t *
+bdb_cache_get(const bdb_env_key_t *keyp, svn_boolean_t *panicp)
+{
+ bdb_env_t *bdb = apr_hash_get(bdb_cache, keyp, sizeof *keyp);
+ if (bdb && bdb->env)
+ {
+ *panicp = !!svn_atomic_read(&bdb->panic);
+#if SVN_BDB_VERSION_AT_LEAST(4,2)
+ if (!*panicp)
+ {
+ u_int32_t flags;
+ if (bdb->env->get_flags(bdb->env, &flags)
+ || (flags & DB_PANIC_ENVIRONMENT))
+ {
+ /* Something is wrong with the environment. */
+ svn_atomic_set(&bdb->panic, TRUE);
+ *panicp = TRUE;
+ bdb = NULL;
+ }
+ }
+#endif /* at least bdb-4.2 */
+ }
+ else
+ {
+ *panicp = FALSE;
+ }
+ return bdb;
+}
+
+
+
+/* Close and destroy a BDB environment descriptor. */
+static svn_error_t *
+bdb_close(bdb_env_t *bdb)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ /* This bit is delcate; we must propagate the error from
+ DB_ENV->close to the caller, and always destroy the pool. */
+ int db_err = bdb->env->close(bdb->env, 0);
+
+ /* If automatic database recovery is enabled, ignore DB_RUNRECOVERY
+ errors, since they're dealt with eventually by BDB itself. */
+ if (db_err && (!SVN_BDB_AUTO_RECOVER || db_err != DB_RUNRECOVERY))
+ err = convert_bdb_error(bdb, db_err);
+
+ /* Free the environment descriptor. The pool cleanup will do this unless
+ the cache has already been destroyed. */
+ if (bdb->pool)
+ svn_pool_destroy(bdb->pool);
+ else
+ free(bdb);
+ return svn_error_trace(err);
+}
+
+
+static svn_error_t *
+svn_fs_bdb__close_internal(bdb_env_t *bdb)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ if (--bdb->refcount != 0)
+ {
+ /* If the environment is panicked and automatic recovery is not
+ enabled, return an appropriate error. */
+#if !SVN_BDB_AUTO_RECOVER
+ if (svn_atomic_read(&bdb->panic))
+ err = svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL,
+ db_strerror(DB_RUNRECOVERY));
+#endif
+ }
+ else
+ {
+ /* If the bdb cache has been set to NULL that means we are
+ shutting down, and the pool that holds the bdb cache has
+ already been destroyed, so accessing it here would be a Bad
+ Thing (tm) */
+ if (bdb_cache)
+ apr_hash_set(bdb_cache, &bdb->key, sizeof bdb->key, NULL);
+ err = bdb_close(bdb);
+ }
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_fs_bdb__close(bdb_env_baton_t *bdb_baton)
+{
+ bdb_env_t *bdb = bdb_baton->bdb;
+
+ SVN_ERR_ASSERT(bdb_baton->env == bdb_baton->bdb->env);
+ SVN_ERR_ASSERT(bdb_baton->error_info->refcount > 0);
+
+ /* Neutralize bdb_baton's pool cleanup to prevent double-close. See
+ cleanup_env_baton(). */
+ bdb_baton->bdb = NULL;
+
+ /* Note that we only bother with this cleanup if the pool is non-NULL, to
+ guard against potential races between this and the cleanup_env cleanup
+ callback. It's not clear if that can actually happen, but better safe
+ than sorry. */
+ if (0 == --bdb_baton->error_info->refcount && bdb->pool)
+ {
+ svn_error_clear(bdb_baton->error_info->pending_errors);
+#if APR_HAS_THREADS
+ free(bdb_baton->error_info);
+ apr_threadkey_private_set(NULL, bdb->error_info);
+#endif
+ }
+
+ /* This may run during final pool cleanup when the lock is NULL. */
+ SVN_MUTEX__WITH_LOCK(bdb_cache_lock, svn_fs_bdb__close_internal(bdb));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Open and initialize a BDB environment. */
+static svn_error_t *
+bdb_open(bdb_env_t *bdb, u_int32_t flags, int mode)
+{
+#if APR_HAS_THREADS
+ flags |= DB_THREAD;
+#endif
+ SVN_ERR(convert_bdb_error
+ (bdb, (bdb->env->open)(bdb->env, bdb->path_bdb, flags, mode)));
+
+#if SVN_BDB_AUTO_COMMIT
+ /* Assert the BDB_AUTO_COMMIT flag on the opened environment. This
+ will force all operations on the environment (and handles that
+ are opened within the environment) to be transactional. */
+
+ SVN_ERR(convert_bdb_error
+ (bdb, bdb->env->set_flags(bdb->env, SVN_BDB_AUTO_COMMIT, 1)));
+#endif
+
+ return bdb_cache_key(&bdb->key, &bdb->dbconfig_file,
+ bdb->path, bdb->pool);
+}
+
+
+/* Pool cleanup for the environment baton. */
+static apr_status_t
+cleanup_env_baton(void *data)
+{
+ bdb_env_baton_t *bdb_baton = data;
+
+ if (bdb_baton->bdb)
+ svn_error_clear(svn_fs_bdb__close(bdb_baton));
+
+ return APR_SUCCESS;
+}
+
+
+static svn_error_t *
+svn_fs_bdb__open_internal(bdb_env_baton_t **bdb_batonp,
+ const char *path,
+ u_int32_t flags, int mode,
+ apr_pool_t *pool)
+{
+ bdb_env_key_t key;
+ bdb_env_t *bdb;
+ svn_boolean_t panic;
+
+ /* We can safely discard the open DB_CONFIG file handle. If the
+ environment descriptor is in the cache, the key's immutability is
+ guaranteed. If it's not, we don't care if the key changes,
+ between here and the actual insertion of the newly-created
+ environment into the cache, because no other thread can touch the
+ cache in the meantime. */
+ SVN_ERR(bdb_cache_key(&key, NULL, path, pool));
+
+ bdb = bdb_cache_get(&key, &panic);
+ if (panic)
+ return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL,
+ db_strerror(DB_RUNRECOVERY));
+
+ /* Make sure that the environment's open flags haven't changed. */
+ if (bdb && bdb->flags != flags)
+ {
+ /* Handle changes to the DB_PRIVATE flag specially */
+ if ((flags ^ bdb->flags) & DB_PRIVATE)
+ {
+ if (flags & DB_PRIVATE)
+ return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL,
+ "Reopening a public Berkeley DB"
+ " environment with private attributes");
+ else
+ return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL,
+ "Reopening a private Berkeley DB"
+ " environment with public attributes");
+ }
+
+ /* Otherwise return a generic "flags-mismatch" error. */
+ return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL,
+ "Reopening a Berkeley DB environment"
+ " with different attributes");
+ }
+
+ if (!bdb)
+ {
+ svn_error_t *err;
+
+ SVN_ERR(create_env(&bdb, path, svn_pool_create(bdb_cache_pool)));
+ err = bdb_open(bdb, flags, mode);
+ if (err)
+ {
+ /* Clean up, and we can't do anything about returned errors. */
+ svn_error_clear(bdb_close(bdb));
+ return svn_error_trace(err);
+ }
+
+ apr_hash_set(bdb_cache, &bdb->key, sizeof bdb->key, bdb);
+ bdb->flags = flags;
+ bdb->refcount = 1;
+ }
+ else
+ {
+ ++bdb->refcount;
+ }
+
+ *bdb_batonp = apr_palloc(pool, sizeof **bdb_batonp);
+ (*bdb_batonp)->env = bdb->env;
+ (*bdb_batonp)->bdb = bdb;
+ (*bdb_batonp)->error_info = get_error_info(bdb);
+ ++(*bdb_batonp)->error_info->refcount;
+ apr_pool_cleanup_register(pool, *bdb_batonp, cleanup_env_baton,
+ apr_pool_cleanup_null);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_bdb__open(bdb_env_baton_t **bdb_batonp, const char *path,
+ u_int32_t flags, int mode,
+ apr_pool_t *pool)
+{
+ SVN_MUTEX__WITH_LOCK(bdb_cache_lock,
+ svn_fs_bdb__open_internal(bdb_batonp,
+ path,
+ flags,
+ mode,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_boolean_t
+svn_fs_bdb__get_panic(bdb_env_baton_t *bdb_baton)
+{
+ /* An invalid baton is equivalent to a panicked environment; in both
+ cases, database cleanups should be skipped. */
+ if (!bdb_baton->bdb)
+ return TRUE;
+
+ assert(bdb_baton->env == bdb_baton->bdb->env);
+ return !!svn_atomic_read(&bdb_baton->bdb->panic);
+}
+
+void
+svn_fs_bdb__set_panic(bdb_env_baton_t *bdb_baton)
+{
+ if (!bdb_baton->bdb)
+ return;
+
+ assert(bdb_baton->env == bdb_baton->bdb->env);
+ svn_atomic_set(&bdb_baton->bdb->panic, TRUE);
+}
+
+
+/* This function doesn't actually open the environment, so it doesn't
+ have to look in the cache. Callers are supposed to own an
+ exclusive lock on the filesystem anyway. */
+svn_error_t *
+svn_fs_bdb__remove(const char *path, apr_pool_t *pool)
+{
+ bdb_env_t *bdb;
+
+ SVN_ERR(create_env(&bdb, path, pool));
+ return convert_bdb_error
+ (bdb, bdb->env->remove(bdb->env, bdb->path_bdb, DB_FORCE));
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/env.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/env.h
new file mode 100644
index 0000000..a8cce4e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/env.h
@@ -0,0 +1,159 @@
+/* env.h : managing the BDB environment
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_BDB_ENV_H
+#define SVN_LIBSVN_FS_BDB_ENV_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+
+#include "bdb_compat.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* The name of the Berkeley DB config file. */
+#define BDB_CONFIG_FILE "DB_CONFIG"
+
+/* Prefix string for BDB errors. */
+#define BDB_ERRPFX_STRING "svn (bdb): "
+
+
+/* Opaque descriptor of an open BDB environment. */
+typedef struct bdb_env_t bdb_env_t;
+
+
+/* Thread-specific error info related to the bdb_env_t. */
+typedef struct bdb_error_info_t
+{
+ /* We hold the extended info here until the Berkeley DB function returns.
+ It usually returns an error code, triggering the collection and
+ wrapping of the additional errors stored here.
+
+ Note: In some circumstances BDB will call the error function and not
+ go on to return an error code, so the caller must always check whether
+ pending_errors is non-NULL to avoid leaking errors. This behaviour
+ has been seen when running recovery on a repository upgraded to 4.3
+ that still has old 4.2 log files present, a typical error string is
+ "Skipping log file db/log.0000000002: historic log version 8" */
+ svn_error_t *pending_errors;
+
+ /* We permitted clients of our library to install a Berkeley BDB errcall.
+ Since we now use the errcall ourselves, we must store and invoke a user
+ errcall, to maintain our API guarantees. */
+ void (*user_callback)(const char *errpfx, char *msg);
+
+ /* The reference count. It counts the number of bdb_env_baton_t
+ instances that refer to this object. */
+ unsigned refcount;
+
+} bdb_error_info_t;
+
+
+/* The Berkeley DB environment baton. */
+typedef struct bdb_env_baton_t
+{
+ /* The Berkeley DB environment. This pointer must be identical to
+ the one in the bdb_env_t. */
+ DB_ENV *env;
+
+ /* The (opaque) cached environment descriptor. */
+ bdb_env_t *bdb;
+
+ /* The error info related to this baton. */
+ bdb_error_info_t *error_info;
+} bdb_env_baton_t;
+
+
+
+/* Flag combination for opening a shared BDB environment. */
+#define SVN_BDB_STANDARD_ENV_FLAGS (DB_CREATE \
+ | DB_INIT_LOCK \
+ | DB_INIT_LOG \
+ | DB_INIT_MPOOL \
+ | DB_INIT_TXN \
+ | SVN_BDB_AUTO_RECOVER)
+
+/* Flag combination for opening a private BDB environment. */
+#define SVN_BDB_PRIVATE_ENV_FLAGS (DB_CREATE \
+ | DB_INIT_LOG \
+ | DB_INIT_MPOOL \
+ | DB_INIT_TXN \
+ | DB_PRIVATE)
+
+
+/* Iniitalize the BDB back-end's private stuff. */
+svn_error_t *svn_fs_bdb__init(apr_pool_t* pool);
+
+
+/* Allocate the Berkeley DB descriptor BDB and open the environment.
+ *
+ * Allocate *BDBP from POOL and open (*BDBP)->env in PATH, using FLAGS
+ * and MODE. If applicable, set the BDB_AUTO_COMMIT flag for this
+ * environment.
+ *
+ * Use POOL for temporary allocation.
+ *
+ * Note: This function may return a bdb_env_baton_t object that refers
+ * to a previously opened environment. If FLAGS contains
+ * DB_PRIVATE and the environment is already open, the function
+ * will fail (this isn't a problem in practice, because a caller
+ * should obtain an exclusive lock on the repository before
+ * opening the environment).
+ */
+
+svn_error_t *svn_fs_bdb__open(bdb_env_baton_t **bdb_batonp,
+ const char *path,
+ u_int32_t flags, int mode,
+ apr_pool_t *pool);
+
+/* Close the Berkeley DB descriptor BDB.
+ *
+ * Note: This function might not actually close the environment if it
+ * has been opened more than once.
+ */
+svn_error_t *svn_fs_bdb__close(bdb_env_baton_t *bdb_baton);
+
+
+/* Get the panic state of the open BDB environment. */
+svn_boolean_t svn_fs_bdb__get_panic(bdb_env_baton_t *bdb_baton);
+
+/* Set the panic flag on the open BDB environment. */
+void svn_fs_bdb__set_panic(bdb_env_baton_t *bdb_baton);
+
+
+/* Remove the Berkeley DB environment at PATH.
+ *
+ * Use POOL for temporary allocation.
+ */
+svn_error_t *svn_fs_bdb__remove(const char *path, apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_BDB_ENV_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/lock-tokens-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/lock-tokens-table.c
new file mode 100644
index 0000000..e70ef17
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/lock-tokens-table.c
@@ -0,0 +1,157 @@
+/* lock-tokens-table.c : operations on the `lock-tokens' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+#include <assert.h>
+
+#include "bdb_compat.h"
+
+#include "svn_pools.h"
+#include "private/svn_skel.h"
+
+#include "dbt.h"
+#include "../err.h"
+#include "../fs.h"
+#include "../util/fs_skels.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "lock-tokens-table.h"
+#include "locks-table.h"
+
+#include "private/svn_fs_util.h"
+
+
+int
+svn_fs_bdb__open_lock_tokens_table(DB **lock_tokens_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *lock_tokens;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&lock_tokens, env, 0));
+ error = (lock_tokens->open)(SVN_BDB_OPEN_PARAMS(lock_tokens, NULL),
+ "lock-tokens", 0, DB_BTREE,
+ open_flags, 0666);
+
+ /* Create the table if it doesn't yet exist. This is a form of
+ automagical repository upgrading. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(lock_tokens->close(lock_tokens, 0));
+ return svn_fs_bdb__open_lock_tokens_table(lock_tokens_p, env, TRUE);
+ }
+ BDB_ERR(error);
+
+ *lock_tokens_p = lock_tokens;
+ return 0;
+}
+
+
+svn_error_t *
+svn_fs_bdb__lock_token_add(svn_fs_t *fs,
+ const char *path,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+
+ svn_fs_base__str_to_dbt(&key, path);
+ svn_fs_base__str_to_dbt(&value, lock_token);
+ svn_fs_base__trail_debug(trail, "lock-tokens", "add");
+ return BDB_WRAP(fs, N_("storing lock token record"),
+ bfd->lock_tokens->put(bfd->lock_tokens, trail->db_txn,
+ &key, &value, 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__lock_token_delete(svn_fs_t *fs,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&key, path);
+ svn_fs_base__trail_debug(trail, "lock-tokens", "del");
+ db_err = bfd->lock_tokens->del(bfd->lock_tokens, trail->db_txn, &key, 0);
+ if (db_err == DB_NOTFOUND)
+ return SVN_FS__ERR_NO_SUCH_LOCK(fs, path);
+ return BDB_WRAP(fs, N_("deleting entry from 'lock-tokens' table"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__lock_token_get(const char **lock_token_p,
+ svn_fs_t *fs,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ svn_error_t *err;
+ svn_lock_t *lock;
+ const char *lock_token;
+ int db_err;
+
+ svn_fs_base__trail_debug(trail, "lock-tokens", "get");
+ db_err = bfd->lock_tokens->get(bfd->lock_tokens, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, path),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return SVN_FS__ERR_NO_SUCH_LOCK(fs, path);
+ SVN_ERR(BDB_WRAP(fs, N_("reading lock token"), db_err));
+
+ lock_token = apr_pstrmemdup(pool, value.data, value.size);
+
+ /* Make sure the token still points to an existing, non-expired
+ lock, by doing a lookup in the `locks' table. */
+ err = svn_fs_bdb__lock_get(&lock, fs, lock_token, trail, pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ /* If `locks' doesn't have the lock, then we should lose it too. */
+ svn_error_t *delete_err;
+ delete_err = svn_fs_bdb__lock_token_delete(fs, path, trail, pool);
+ if (delete_err)
+ svn_error_compose(err, delete_err);
+ return svn_error_trace(err);
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ *lock_token_p = lock_token;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/lock-tokens-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/lock-tokens-table.h
new file mode 100644
index 0000000..de958ce
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/lock-tokens-table.h
@@ -0,0 +1,96 @@
+/* lock-tokens-table.h : internal interface to ops on `lock-tokens' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_LOCK_TOKENS_TABLE_H
+#define SVN_LIBSVN_FS_LOCK_TOKENS_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `lock-tokens' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *LOCK_TOKENS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_lock_tokens_table(DB **locks_tokens_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Add a lock-token to the `lock-tokens' table in FS, as part of TRAIL.
+ Use PATH as the key and LOCK_TOKEN as the value.
+
+ Warning: if PATH already exists as a key, then its value will be
+ overwritten. */
+svn_error_t *
+svn_fs_bdb__lock_token_add(svn_fs_t *fs,
+ const char *path,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Remove the lock-token whose key is PATH from the `lock-tokens'
+ table of FS, as part of TRAIL.
+
+ If PATH doesn't exist as a key, return SVN_ERR_FS_NO_SUCH_LOCK.
+*/
+svn_error_t *
+svn_fs_bdb__lock_token_delete(svn_fs_t *fs,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Retrieve the lock-token *LOCK_TOKEN_P pointed to by PATH from the
+ `lock-tokens' table of FS, as part of TRAIL. Perform all
+ allocations in POOL.
+
+ If PATH doesn't exist as a key, return SVN_ERR_FS_NO_SUCH_LOCK.
+
+ If PATH points to a token which points to an expired lock, return
+ SVN_ERR_FS_LOCK_EXPIRED. (After this, both the token and lock are
+ gone from their respective tables.)
+
+ If PATH points to a token which points to a non-existent lock,
+ return SVN_ERR_FS_BAD_LOCK_TOKEN. (After this, the token is also
+ removed from the `lock-tokens' table.)
+ */
+svn_error_t *
+svn_fs_bdb__lock_token_get(const char **lock_token_p,
+ svn_fs_t *fs,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_LOCK_TOKENS_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/locks-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/locks-table.c
new file mode 100644
index 0000000..a22663f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/locks-table.c
@@ -0,0 +1,328 @@
+/* locks-table.c : operations on the `locks' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+#include <assert.h>
+
+#include "bdb_compat.h"
+
+#include "svn_pools.h"
+#include "svn_path.h"
+#include "private/svn_skel.h"
+
+#include "dbt.h"
+#include "../err.h"
+#include "../fs.h"
+#include "../util/fs_skels.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "locks-table.h"
+#include "lock-tokens-table.h"
+
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+
+
+int
+svn_fs_bdb__open_locks_table(DB **locks_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *locks;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&locks, env, 0));
+ error = (locks->open)(SVN_BDB_OPEN_PARAMS(locks, NULL),
+ "locks", 0, DB_BTREE,
+ open_flags, 0666);
+
+ /* Create the table if it doesn't yet exist. This is a form of
+ automagical repository upgrading. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(locks->close(locks, 0));
+ return svn_fs_bdb__open_locks_table(locks_p, env, TRUE);
+ }
+ BDB_ERR(error);
+
+ *locks_p = locks;
+ return 0;
+}
+
+
+
+svn_error_t *
+svn_fs_bdb__lock_add(svn_fs_t *fs,
+ const char *lock_token,
+ svn_lock_t *lock,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ svn_skel_t *lock_skel;
+ DBT key, value;
+
+ /* Convert native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool));
+
+ svn_fs_base__str_to_dbt(&key, lock_token);
+ svn_fs_base__skel_to_dbt(&value, lock_skel, pool);
+ svn_fs_base__trail_debug(trail, "lock", "add");
+ return BDB_WRAP(fs, N_("storing lock record"),
+ bfd->locks->put(bfd->locks, trail->db_txn,
+ &key, &value, 0));
+}
+
+
+
+svn_error_t *
+svn_fs_bdb__lock_delete(svn_fs_t *fs,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&key, lock_token);
+ svn_fs_base__trail_debug(trail, "locks", "del");
+ db_err = bfd->locks->del(bfd->locks, trail->db_txn, &key, 0);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_bad_lock_token(fs, lock_token);
+ return BDB_WRAP(fs, N_("deleting lock from 'locks' table"), db_err);
+}
+
+
+
+svn_error_t *
+svn_fs_bdb__lock_get(svn_lock_t **lock_p,
+ svn_fs_t *fs,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+ svn_skel_t *skel;
+ svn_lock_t *lock;
+
+ svn_fs_base__trail_debug(trail, "lock", "get");
+ db_err = bfd->locks->get(bfd->locks, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, lock_token),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_bad_lock_token(fs, lock_token);
+ SVN_ERR(BDB_WRAP(fs, N_("reading lock"), db_err));
+
+ /* Parse TRANSACTION skel */
+ skel = svn_skel__parse(value.data, value.size, pool);
+ if (! skel)
+ return svn_fs_base__err_corrupt_lock(fs, lock_token);
+
+ /* Convert skel to native type. */
+ SVN_ERR(svn_fs_base__parse_lock_skel(&lock, skel, pool));
+
+ /* Possibly auto-expire the lock. */
+ if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
+ {
+ SVN_ERR(svn_fs_bdb__lock_delete(fs, lock_token, trail, pool));
+ return SVN_FS__ERR_LOCK_EXPIRED(fs, lock_token);
+ }
+
+ *lock_p = lock;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+get_lock(svn_lock_t **lock_p,
+ svn_fs_t *fs,
+ const char *path,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ *lock_p = NULL;
+
+ /* Make sure the token points to an existing, non-expired lock, by
+ doing a lookup in the `locks' table. Use 'pool'. */
+ err = svn_fs_bdb__lock_get(lock_p, fs, lock_token, trail, pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ svn_error_clear(err);
+
+ /* If `locks' doesn't have the lock, then we should lose it
+ from `lock-tokens' table as well, then skip to the next
+ matching path-key. */
+ err = svn_fs_bdb__lock_token_delete(fs, path, trail, pool);
+ }
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__locks_get(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBC *cursor;
+ DBT key, value;
+ int db_err, db_c_err;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ const char *lock_token;
+ svn_lock_t *lock;
+ svn_error_t *err;
+ const char *lookup_path = path;
+ apr_size_t lookup_len;
+
+ /* First, try to lookup PATH itself. */
+ err = svn_fs_bdb__lock_token_get(&lock_token, fs, path, trail, pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)
+ || (err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)))
+ {
+ svn_error_clear(err);
+ }
+ else if (err)
+ {
+ return svn_error_trace(err);
+ }
+ else
+ {
+ SVN_ERR(get_lock(&lock, fs, path, lock_token, trail, pool));
+ if (lock && get_locks_func)
+ {
+ SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
+
+ /* Found a lock so PATH is a file and we can ignore depth */
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* If we're only looking at PATH itself (depth = empty), stop here. */
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* Now go hunt for possible children of PATH. */
+
+ svn_fs_base__trail_debug(trail, "lock-tokens", "cursor");
+ db_err = bfd->lock_tokens->cursor(bfd->lock_tokens, trail->db_txn,
+ &cursor, 0);
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading lock tokens"),
+ db_err));
+
+ /* Since the key is going to be returned as well as the value make
+ sure BDB malloc's the returned key. */
+ svn_fs_base__str_to_dbt(&key, lookup_path);
+ key.flags |= DB_DBT_MALLOC;
+
+ /* Get the first matching key that is either equal or greater than
+ the one passed in, by passing in the DB_RANGE_SET flag. */
+ db_err = svn_bdb_dbc_get(cursor, &key, svn_fs_base__result_dbt(&value),
+ DB_SET_RANGE);
+
+ if (!svn_fspath__is_root(path, strlen(path)))
+ lookup_path = apr_pstrcat(pool, path, "/", (char *)NULL);
+ lookup_len = strlen(lookup_path);
+
+ /* As long as the prefix of the returned KEY matches LOOKUP_PATH we
+ know it is either LOOKUP_PATH or a decendant thereof. */
+ while ((! db_err)
+ && lookup_len < key.size
+ && strncmp(lookup_path, key.data, lookup_len) == 0)
+ {
+ const char *child_path;
+
+ svn_pool_clear(subpool);
+
+ svn_fs_base__track_dbt(&key, subpool);
+ svn_fs_base__track_dbt(&value, subpool);
+
+ /* Create a usable path and token in temporary memory. */
+ child_path = apr_pstrmemdup(subpool, key.data, key.size);
+ lock_token = apr_pstrmemdup(subpool, value.data, value.size);
+
+ if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
+ {
+ /* On the assumption that we only store locks for files,
+ depth=files and depth=immediates should boil down to the
+ same set of results. So just see if CHILD_PATH is an
+ immediate child of PATH. If not, we don't care about
+ this item. */
+ const char *rel_path = svn_fspath__skip_ancestor(path, child_path);
+ if (!rel_path || (svn_path_component_count(rel_path) != 1))
+ goto loop_it;
+ }
+
+ /* Get the lock for CHILD_PATH. */
+ err = get_lock(&lock, fs, child_path, lock_token, trail, subpool);
+ if (err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return svn_error_trace(err);
+ }
+
+ /* Lock is verified, hand it off to our callback. */
+ if (lock && get_locks_func)
+ {
+ err = get_locks_func(get_locks_baton, lock, subpool);
+ if (err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return svn_error_trace(err);
+ }
+ }
+
+ loop_it:
+ svn_fs_base__result_dbt(&key);
+ svn_fs_base__result_dbt(&value);
+ db_err = svn_bdb_dbc_get(cursor, &key, &value, DB_NEXT);
+ }
+
+ svn_pool_destroy(subpool);
+ db_c_err = svn_bdb_dbc_close(cursor);
+
+ if (db_err && (db_err != DB_NOTFOUND))
+ SVN_ERR(BDB_WRAP(fs, N_("fetching lock tokens"), db_err));
+ if (db_c_err)
+ SVN_ERR(BDB_WRAP(fs, N_("fetching lock tokens (closing cursor)"),
+ db_c_err));
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/locks-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/locks-table.h
new file mode 100644
index 0000000..e0d087c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/locks-table.h
@@ -0,0 +1,110 @@
+/* locks-table.h : internal interface to ops on `locks' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_LOCKS_TABLE_H
+#define SVN_LIBSVN_FS_LOCKS_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `locks' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *LOCKS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_locks_table(DB **locks_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Add a lock to the `locks' table in FS, as part of TRAIL.
+
+ Use LOCK_TOKEN as the key, presumably a string form of an apr_uuid_t.
+ Convert LOCK into a skel and store it as the value.
+
+ Warning: if LOCK_TOKEN already exists as a key, then its value
+ will be overwritten. */
+svn_error_t *svn_fs_bdb__lock_add(svn_fs_t *fs,
+ const char *lock_token,
+ svn_lock_t *lock,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Remove the lock whose key is LOCK_TOKEN from the `locks' table of
+ FS, as part of TRAIL.
+
+ Return SVN_ERR_FS_BAD_LOCK_TOKEN if LOCK_TOKEN does not exist as a
+ table key. */
+svn_error_t *svn_fs_bdb__lock_delete(svn_fs_t *fs,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Retrieve the lock *LOCK_P pointed to by LOCK_TOKEN from the `locks'
+ table of FS, as part of TRAIL. Perform all allocations in POOL.
+
+ Return SVN_ERR_FS_BAD_LOCK_TOKEN if LOCK_TOKEN does not exist as a
+ table key.
+
+ Before returning LOCK_P, check its expiration date. If expired,
+ remove the row from the `locks' table and return SVN_ERR_FS_LOCK_EXPIRED.
+ */
+svn_error_t *svn_fs_bdb__lock_get(svn_lock_t **lock_p,
+ svn_fs_t *fs,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Retrieve locks representing all locks that exist at or below PATH
+ in FS. Pass each lock to GET_LOCKS_FUNC callback along with
+ GET_LOCKS_BATON.
+
+ Use DEPTH to filter the reported locks to only those within the
+ requested depth of PATH.
+
+ This function promises to auto-expire any locks encountered while
+ building the hash. That means that the caller can trust that each
+ returned lock hasn't yet expired.
+*/
+svn_error_t *svn_fs_bdb__locks_get(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_LOCKS_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/miscellaneous-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/miscellaneous-table.c
new file mode 100644
index 0000000..21a05ca
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/miscellaneous-table.c
@@ -0,0 +1,135 @@
+/* miscellaneous-table.c : operations on the `miscellaneous' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+#include <assert.h>
+#include "bdb_compat.h"
+
+#include "svn_pools.h"
+#include "dbt.h"
+#include "../err.h"
+#include "../fs.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "miscellaneous-table.h"
+
+#include "private/svn_fs_util.h"
+
+
+int
+svn_fs_bdb__open_miscellaneous_table(DB **miscellaneous_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *miscellaneous;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&miscellaneous, env, 0));
+ error = (miscellaneous->open)(SVN_BDB_OPEN_PARAMS(miscellaneous, NULL),
+ "miscellaneous", 0, DB_BTREE,
+ open_flags, 0666);
+
+ /* Create the table if it doesn't yet exist. This is a form of
+ automagical repository upgrading. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(miscellaneous->close(miscellaneous, 0));
+ return svn_fs_bdb__open_miscellaneous_table(miscellaneous_p, env, TRUE);
+ }
+ BDB_ERR(error);
+
+ /* If we're creating the table from scratch (not upgrading), record the
+ upgrade rev as 0. */
+ if (create)
+ {
+ DBT key, value;
+
+ BDB_ERR(miscellaneous->put
+ (miscellaneous, 0,
+ svn_fs_base__str_to_dbt
+ (&key, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ *miscellaneous_p = miscellaneous;
+ return 0;
+}
+
+
+svn_error_t *
+svn_fs_bdb__miscellaneous_set(svn_fs_t *fs,
+ const char *key_str,
+ const char *val,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+
+ svn_fs_base__str_to_dbt(&key, key_str);
+ if (val == NULL)
+ {
+ svn_fs_base__trail_debug(trail, "miscellaneous", "del");
+ return BDB_WRAP(fs, N_("deleting record from 'miscellaneous' table"),
+ bfd->miscellaneous->del(bfd->miscellaneous,
+ trail->db_txn, &key, 0));
+ }
+ else
+ {
+ svn_fs_base__str_to_dbt(&value, val);
+ svn_fs_base__trail_debug(trail, "miscellaneous", "add");
+ return BDB_WRAP(fs, N_("storing miscellaneous record"),
+ bfd->miscellaneous->put(bfd->miscellaneous,
+ trail->db_txn,
+ &key, &value, 0));
+ }
+}
+
+
+svn_error_t *
+svn_fs_bdb__miscellaneous_get(const char **val,
+ svn_fs_t *fs,
+ const char *key_str,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+
+ *val = NULL;
+ svn_fs_base__trail_debug(trail, "miscellaneous", "get");
+ db_err = bfd->miscellaneous->get(bfd->miscellaneous, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, key_str),
+ svn_fs_base__result_dbt(&value), 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err != DB_NOTFOUND)
+ {
+ SVN_ERR(BDB_WRAP(fs, N_("fetching miscellaneous record"), db_err));
+ *val = apr_pstrmemdup(pool, value.data, value.size);
+ }
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/miscellaneous-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/miscellaneous-table.h
new file mode 100644
index 0000000..f972d02
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/miscellaneous-table.h
@@ -0,0 +1,71 @@
+/* miscellaneous-table.h : internal interface to ops on `miscellaneous' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_MISCELLANEOUS_TABLE_H
+#define SVN_LIBSVN_FS_MISCELLANEOUS_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `miscellaneous' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *MISCELLANEOUS_P to the new table.
+ Return a Berkeley DB error code. */
+int
+svn_fs_bdb__open_miscellaneous_table(DB **miscellaneous_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Add data to the `miscellaneous' table in FS, as part of TRAIL.
+
+ KEY and VAL should be NULL-terminated strings. If VAL is NULL,
+ the key is removed from the table. */
+svn_error_t *
+svn_fs_bdb__miscellaneous_set(svn_fs_t *fs,
+ const char *key,
+ const char *val,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *VAL to the value of data cooresponding to KEY in the
+ `miscellaneous' table of FS, or to NULL if that key isn't found. */
+svn_error_t *
+svn_fs_bdb__miscellaneous_get(const char **val,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_MISCELLANEOUS_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/node-origins-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/node-origins-table.c
new file mode 100644
index 0000000..48dc43b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/node-origins-table.c
@@ -0,0 +1,145 @@
+/* node-origins-table.c : operations on the `node-origins' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "bdb_compat.h"
+#include "../fs.h"
+#include "../err.h"
+#include "../id.h"
+#include "dbt.h"
+#include "../trail.h"
+#include "bdb-err.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "node-origins-table.h"
+
+#include "svn_private_config.h"
+
+
+int svn_fs_bdb__open_node_origins_table(DB **node_origins_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *node_origins;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&node_origins, env, 0));
+ error = (node_origins->open)(SVN_BDB_OPEN_PARAMS(node_origins, NULL),
+ "node-origins", 0, DB_BTREE,
+ open_flags, 0666);
+
+ /* Create the node-origins table if it doesn't exist. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(node_origins->close(node_origins, 0));
+ return svn_fs_bdb__open_node_origins_table(node_origins_p, env, TRUE);
+ }
+
+ BDB_ERR(error);
+
+ *node_origins_p = node_origins;
+ return 0;
+}
+
+svn_error_t *svn_fs_bdb__get_node_origin(const svn_fs_id_t **origin_id,
+ svn_fs_t *fs,
+ const char *node_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+
+ svn_fs_base__trail_debug(trail, "node-origins", "get");
+ db_err = bfd->node_origins->get(bfd->node_origins, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, node_id),
+ svn_fs_base__result_dbt(&value), 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_no_such_node_origin(fs, node_id);
+
+ *origin_id = svn_fs_base__id_parse(value.data, value.size, pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_fs_bdb__set_node_origin(svn_fs_t *fs,
+ const char *node_id,
+ const svn_fs_id_t *origin_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+
+ /* Create a key from our NODE_ID. */
+ svn_fs_base__str_to_dbt(&key, node_id);
+
+ /* Check to see if we already have a mapping for NODE_ID. If so,
+ and the value is the same one we were about to write. That's
+ cool -- just do nothing. If, however, the value is *different*,
+ that's a red flag! */
+ svn_fs_base__trail_debug(trail, "node-origins", "get");
+ db_err = bfd->node_origins->get(bfd->node_origins, trail->db_txn,
+ &key, svn_fs_base__result_dbt(&value), 0);
+ svn_fs_base__track_dbt(&value, pool);
+ if (db_err != DB_NOTFOUND)
+ {
+ const svn_string_t *origin_id_str =
+ svn_fs_base__id_unparse(origin_id, pool);
+ const svn_string_t *old_origin_id_str =
+ svn_string_ncreate(value.data, value.size, pool);
+
+ if (! svn_string_compare(origin_id_str, old_origin_id_str))
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Node origin for '%s' exists in filesystem '%s' with a different "
+ "value (%s) than what we were about to store (%s)"),
+ node_id, fs->path, old_origin_id_str->data, origin_id_str->data);
+ else
+ return SVN_NO_ERROR;
+ }
+
+ /* Create a value from our ORIGIN_ID, and add this record to the table. */
+ svn_fs_base__id_to_dbt(&value, origin_id, pool);
+ svn_fs_base__trail_debug(trail, "node-origins", "put");
+ return BDB_WRAP(fs, N_("storing node-origins record"),
+ bfd->node_origins->put(bfd->node_origins, trail->db_txn,
+ &key, &value, 0));
+}
+
+svn_error_t *svn_fs_bdb__delete_node_origin(svn_fs_t *fs,
+ const char *node_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+
+ svn_fs_base__str_to_dbt(&key, node_id);
+ svn_fs_base__trail_debug(trail, "node-origins", "del");
+ return BDB_WRAP(fs, N_("deleting entry from 'node-origins' table"),
+ bfd->node_origins->del(bfd->node_origins,
+ trail->db_txn, &key, 0));
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/node-origins-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/node-origins-table.h
new file mode 100644
index 0000000..44587ca
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/node-origins-table.h
@@ -0,0 +1,76 @@
+/* node-origins-table.h : internal interface to ops on `node-origins' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_NODE_ORIGINS_TABLE_H
+#define SVN_LIBSVN_FS_NODE_ORIGINS_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `node-origins' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *NODE_ORIGINS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_node_origins_table(DB **node_origins_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+/* Set *ORIGIN_ID to the node revision ID from which the history of
+ all nodes in FS whose node ID is NODE_ID springs, as determined by
+ a look in the `node-origins' table. Do this as part of TRAIL. Use
+ POOL for allocations.
+
+ If no such node revision ID is stored for NODE_ID, return
+ SVN_ERR_FS_NO_SUCH_NODE_ORIGIN. */
+svn_error_t *svn_fs_bdb__get_node_origin(const svn_fs_id_t **origin_id,
+ svn_fs_t *fs,
+ const char *node_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Store in the `node-origins' table a mapping of NODE_ID to original
+ node revision ID ORIGIN_ID for FS. Do this as part of TRAIL. Use
+ POOL for temporary allocations. */
+svn_error_t *svn_fs_bdb__set_node_origin(svn_fs_t *fs,
+ const char *node_id,
+ const svn_fs_id_t *origin_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Delete from the `node-origins' table the record for NODE_ID in FS.
+ Do this as part of TRAIL. Use POOL for temporary allocations. */
+svn_error_t *svn_fs_bdb__delete_node_origin(svn_fs_t *fs,
+ const char *node_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_NODE_ORIGINS_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/nodes-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/nodes-table.c
new file mode 100644
index 0000000..d0f475f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/nodes-table.c
@@ -0,0 +1,259 @@
+/* nodes-table.c : working with the `nodes' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+#include <assert.h>
+
+#include "bdb_compat.h"
+
+#include "svn_fs.h"
+#include "private/svn_skel.h"
+
+#include "../fs.h"
+#include "../err.h"
+#include "dbt.h"
+#include "../util/fs_skels.h"
+#include "../trail.h"
+#include "../key-gen.h"
+#include "../id.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "nodes-table.h"
+
+#include "svn_private_config.h"
+
+
+
+/* Opening/creating the `nodes' table. */
+
+
+int
+svn_fs_bdb__open_nodes_table(DB **nodes_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *nodes;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&nodes, env, 0));
+ BDB_ERR((nodes->open)(SVN_BDB_OPEN_PARAMS(nodes, NULL),
+ "nodes", 0, DB_BTREE,
+ open_flags, 0666));
+
+ /* Create the `next-key' table entry (use '1' because '0' is
+ reserved for the root directory to use). */
+ if (create)
+ {
+ DBT key, value;
+
+ BDB_ERR(nodes->put(nodes, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "1"), 0));
+ }
+
+ *nodes_p = nodes;
+ return 0;
+}
+
+
+
+/* Choosing node revision ID's. */
+
+svn_error_t *
+svn_fs_bdb__new_node_id(svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ apr_size_t len;
+ char next_key[MAX_KEY_SIZE];
+ int db_err;
+ const char *next_node_id;
+
+ SVN_ERR_ASSERT(txn_id);
+
+ /* Get the current value associated with the `next-key' key in the table. */
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+ svn_fs_base__trail_debug(trail, "nodes", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("allocating new node ID (getting 'next-key')"),
+ bfd->nodes->get(bfd->nodes, trail->db_txn,
+ &query,
+ svn_fs_base__result_dbt(&result),
+ 0)));
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* Squirrel away our next node id value. */
+ next_node_id = apr_pstrmemdup(pool, result.data, result.size);
+
+ /* Bump to future key. */
+ len = result.size;
+ svn_fs_base__next_key(result.data, &len, next_key);
+ svn_fs_base__trail_debug(trail, "nodes", "put");
+ db_err = bfd->nodes->put(bfd->nodes, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&result, next_key),
+ 0);
+ SVN_ERR(BDB_WRAP(fs, N_("bumping next node ID key"), db_err));
+
+ /* Create and return the new node id. */
+ *id_p = svn_fs_base__id_create(next_node_id, copy_id, txn_id, pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__new_successor_id(svn_fs_id_t **successor_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *new_id;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(txn_id);
+
+ /* Create and return the new successor ID. */
+ new_id = svn_fs_base__id_create(svn_fs_base__id_node_id(id),
+ copy_id ? copy_id
+ : svn_fs_base__id_copy_id(id),
+ txn_id, pool);
+
+ /* Now, make sure this NEW_ID doesn't already exist in FS. */
+ err = svn_fs_bdb__get_node_revision(NULL, fs, new_id, trail, trail->pool);
+ if ((! err) || (err->apr_err != SVN_ERR_FS_ID_NOT_FOUND))
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(id, pool);
+ svn_string_t *new_id_str = svn_fs_base__id_unparse(new_id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_ALREADY_EXISTS, err,
+ _("Successor id '%s' (for '%s') already exists in filesystem '%s'"),
+ new_id_str->data, id_str->data, fs->path);
+ }
+
+ /* err is SVN_ERR_FS_ID_NOT_FOUND, meaning the ID is available. But
+ we don't want this error. */
+ svn_error_clear(err);
+
+ /* Return the new node revision ID. */
+ *successor_p = new_id;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Removing node revisions. */
+svn_error_t *
+svn_fs_bdb__delete_nodes_entry(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+
+ svn_fs_base__trail_debug(trail, "nodes", "del");
+ return BDB_WRAP(fs, N_("deleting entry from 'nodes' table"),
+ bfd->nodes->del(bfd->nodes,
+ trail->db_txn,
+ svn_fs_base__id_to_dbt(&key, id, pool),
+ 0));
+}
+
+
+
+
+/* Storing and retrieving NODE-REVISIONs. */
+
+
+svn_error_t *
+svn_fs_bdb__get_node_revision(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ node_revision_t *noderev;
+ svn_skel_t *skel;
+ int db_err;
+ DBT key, value;
+
+ svn_fs_base__trail_debug(trail, "nodes", "get");
+ db_err = bfd->nodes->get(bfd->nodes, trail->db_txn,
+ svn_fs_base__id_to_dbt(&key, id, pool),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_dangling_id(fs, id);
+
+ /* Handle any other error conditions. */
+ SVN_ERR(BDB_WRAP(fs, N_("reading node revision"), db_err));
+
+ /* If our caller doesn't really care about the return value here,
+ just return successfully. */
+ if (! noderev_p)
+ return SVN_NO_ERROR;
+
+ /* Parse and the NODE-REVISION skel. */
+ skel = svn_skel__parse(value.data, value.size, pool);
+
+ /* Convert to a native FS type. */
+ SVN_ERR(svn_fs_base__parse_node_revision_skel(&noderev, skel, pool));
+ *noderev_p = noderev;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__put_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ node_revision_t *noderev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DB_TXN *db_txn = trail->db_txn;
+ DBT key, value;
+ svn_skel_t *skel;
+
+ /* Convert from native type into skel */
+ SVN_ERR(svn_fs_base__unparse_node_revision_skel(&skel, noderev,
+ bfd->format, pool));
+ svn_fs_base__trail_debug(trail, "nodes", "put");
+ return BDB_WRAP(fs, N_("storing node revision"),
+ bfd->nodes->put(bfd->nodes, db_txn,
+ svn_fs_base__id_to_dbt(&key, id, pool),
+ svn_fs_base__skel_to_dbt(&value, skel,
+ pool),
+ 0));
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/nodes-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/nodes-table.h
new file mode 100644
index 0000000..c1687ab
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/nodes-table.h
@@ -0,0 +1,121 @@
+/* nodes-table.h : interface to `nodes' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_NODES_TABLE_H
+#define SVN_LIBSVN_FS_NODES_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Creating and opening the `nodes' table. */
+
+
+/* Open a `nodes' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *NODES_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_nodes_table(DB **nodes_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Check FS's `nodes' table to find an unused node number, and set
+ *ID_P to the ID of the first revision of an entirely new node in
+ FS, with copy_id COPY_ID, created in transaction TXN_ID, as part
+ of TRAIL. Allocate the new ID, and do all temporary allocation,
+ in POOL. */
+svn_error_t *svn_fs_bdb__new_node_id(svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete node revision ID from FS's `nodes' table, as part of TRAIL.
+ WARNING: This does not check that the node revision is mutable!
+ Callers should do that check themselves.
+
+ todo: Jim and Karl are both not sure whether it would be better for
+ this to check mutability or not. On the one hand, having the
+ lowest level do that check would seem intuitively good. On the
+ other hand, we'll need a way to delete even immutable nodes someday
+ -- for example, someone accidentally commits NDA-protected data to
+ a public repository and wants to remove it. Thoughts? */
+svn_error_t *svn_fs_bdb__delete_nodes_entry(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *SUCCESSOR_P to the ID of an immediate successor to node
+ revision ID in FS that does not exist yet, as part of TRAIL.
+ Allocate *SUCCESSOR_P in POOL.
+
+ Use the current Subversion transaction name TXN_ID, and optionally
+ a copy id COPY_ID, in the determination of the new node revision
+ ID. */
+svn_error_t *svn_fs_bdb__new_successor_id(svn_fs_id_t **successor_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *NODEREV_P to the node-revision for the node ID in FS, as
+ part of TRAIL. Do any allocations in POOL. Allow NODEREV_P
+ to be NULL, in which case it is not used, and this function acts as
+ an existence check for ID in FS. */
+svn_error_t *svn_fs_bdb__get_node_revision(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Store NODEREV as the node-revision for the node whose id
+ is ID in FS, as part of TRAIL. Do any necessary temporary
+ allocation in POOL.
+
+ After this call, the node table manager assumes that NODE's
+ contents will change frequently. */
+svn_error_t *svn_fs_bdb__put_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ node_revision_t *noderev,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_NODES_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/reps-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/reps-table.c
new file mode 100644
index 0000000..1c8ea6d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/reps-table.c
@@ -0,0 +1,204 @@
+/* reps-table.c : operations on the `representations' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "bdb_compat.h"
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../util/fs_skels.h"
+#include "../err.h"
+#include "dbt.h"
+#include "../trail.h"
+#include "../key-gen.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "reps-table.h"
+#include "strings-table.h"
+
+
+#include "svn_private_config.h"
+
+
+/*** Creating and opening the representations table. ***/
+
+int
+svn_fs_bdb__open_reps_table(DB **reps_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *reps;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&reps, env, 0));
+ BDB_ERR((reps->open)(SVN_BDB_OPEN_PARAMS(reps, NULL),
+ "representations", 0, DB_BTREE,
+ open_flags, 0666));
+
+ /* Create the `next-key' table entry. */
+ if (create)
+ {
+ DBT key, value;
+
+ BDB_ERR(reps->put
+ (reps, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ *reps_p = reps;
+ return 0;
+}
+
+
+
+/*** Storing and retrieving reps. ***/
+
+svn_error_t *
+svn_fs_bdb__read_rep(representation_t **rep_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ svn_skel_t *skel;
+ int db_err;
+ DBT query, result;
+
+ svn_fs_base__trail_debug(trail, "representations", "get");
+ db_err = bfd->representations->get(bfd->representations,
+ trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, key),
+ svn_fs_base__result_dbt(&result), 0);
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REPRESENTATION, 0,
+ _("No such representation '%s'"), key);
+
+ /* Handle any other error conditions. */
+ SVN_ERR(BDB_WRAP(fs, N_("reading representation"), db_err));
+
+ /* Parse the REPRESENTATION skel. */
+ skel = svn_skel__parse(result.data, result.size, pool);
+
+ /* Convert to a native type. */
+ return svn_fs_base__parse_representation_skel(rep_p, skel, pool);
+}
+
+
+svn_error_t *
+svn_fs_bdb__write_rep(svn_fs_t *fs,
+ const char *key,
+ const representation_t *rep,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ svn_skel_t *skel;
+
+ /* Convert from native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_representation_skel(&skel, rep,
+ bfd->format, pool));
+
+ /* Now write the record. */
+ svn_fs_base__trail_debug(trail, "representations", "put");
+ return BDB_WRAP(fs, N_("storing representation"),
+ bfd->representations->put
+ (bfd->representations, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, key),
+ svn_fs_base__skel_to_dbt(&result, skel, pool),
+ 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__write_new_rep(const char **key,
+ svn_fs_t *fs,
+ const representation_t *rep,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ int db_err;
+ apr_size_t len;
+ char next_key[MAX_KEY_SIZE];
+
+ /* ### todo: see issue #409 for why bumping the key as part of this
+ trail is problematic. */
+
+ /* Get the current value associated with `next-key'. */
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+ svn_fs_base__trail_debug(trail, "representations", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("allocating new representation (getting next-key)"),
+ bfd->representations->get
+ (bfd->representations, trail->db_txn, &query,
+ svn_fs_base__result_dbt(&result), 0)));
+
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* Store the new rep. */
+ *key = apr_pstrmemdup(pool, result.data, result.size);
+ SVN_ERR(svn_fs_bdb__write_rep(fs, *key, rep, trail, pool));
+
+ /* Bump to future key. */
+ len = result.size;
+ svn_fs_base__next_key(result.data, &len, next_key);
+ svn_fs_base__trail_debug(trail, "representations", "put");
+ db_err = bfd->representations->put
+ (bfd->representations, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&result, next_key),
+ 0);
+
+ return BDB_WRAP(fs, N_("bumping next representation key"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__delete_rep(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBT query;
+
+ svn_fs_base__trail_debug(trail, "representations", "del");
+ db_err = bfd->representations->del
+ (bfd->representations, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, key), 0);
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REPRESENTATION, 0,
+ _("No such representation '%s'"), key);
+
+ /* Handle any other error conditions. */
+ return BDB_WRAP(fs, N_("deleting representation"), db_err);
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/reps-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/reps-table.h
new file mode 100644
index 0000000..b5cd27d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/reps-table.h
@@ -0,0 +1,94 @@
+/* reps-table.h : internal interface to `representations' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_REPS_TABLE_H
+#define SVN_LIBSVN_FS_REPS_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_io.h"
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Creating the `representations' table. ***/
+
+/* Open a `representations' table in ENV. If CREATE is non-zero,
+ create one if it doesn't exist. Set *REPS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_reps_table(DB **reps_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+
+/*** Storing and retrieving reps. ***/
+
+/* Set *REP_P to point to the representation for the key KEY in
+ FS, as part of TRAIL. Perform all allocations in POOL.
+
+ If KEY is not a representation in FS, the error
+ SVN_ERR_FS_NO_SUCH_REPRESENTATION is returned. */
+svn_error_t *svn_fs_bdb__read_rep(representation_t **rep_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Store REP as the representation for KEY in FS, as part of
+ TRAIL. Do any necessary temporary allocation in POOL. */
+svn_error_t *svn_fs_bdb__write_rep(svn_fs_t *fs,
+ const char *key,
+ const representation_t *rep,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Store REP as a new representation in FS, and the new rep's key in
+ *KEY, as part of trail. The new key is allocated in POOL. */
+svn_error_t *svn_fs_bdb__write_new_rep(const char **key,
+ svn_fs_t *fs,
+ const representation_t *rep,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Delete representation KEY from FS, as part of TRAIL.
+ WARNING: This does not ensure that no one references this
+ representation! Callers should ensure that themselves. */
+svn_error_t *svn_fs_bdb__delete_rep(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_REPS_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/rev-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/rev-table.c
new file mode 100644
index 0000000..b752249
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/rev-table.c
@@ -0,0 +1,221 @@
+ /* rev-table.c : working with the `revisions' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "bdb_compat.h"
+
+#include "svn_fs.h"
+#include "private/svn_skel.h"
+
+#include "../fs.h"
+#include "../err.h"
+#include "../util/fs_skels.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "dbt.h"
+#include "rev-table.h"
+
+#include "svn_private_config.h"
+#include "private/svn_fs_util.h"
+
+
+/* Opening/creating the `revisions' table. */
+
+int svn_fs_bdb__open_revisions_table(DB **revisions_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *revisions;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&revisions, env, 0));
+ BDB_ERR((revisions->open)(SVN_BDB_OPEN_PARAMS(revisions, NULL),
+ "revisions", 0, DB_RECNO,
+ open_flags, 0666));
+
+ *revisions_p = revisions;
+ return 0;
+}
+
+
+
+/* Storing and retrieving filesystem revisions. */
+
+
+svn_error_t *
+svn_fs_bdb__get_rev(revision_t **revision_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBT key, value;
+ svn_skel_t *skel;
+ revision_t *revision;
+
+ /* Turn the revision number into a Berkeley DB record number.
+ Revisions are numbered starting with zero; Berkeley DB record
+ numbers begin with one. */
+ db_recno_t recno = (db_recno_t) rev + 1;
+
+ svn_fs_base__trail_debug(trail, "revisions", "get");
+ db_err = bfd->revisions->get(bfd->revisions, trail->db_txn,
+ svn_fs_base__set_dbt(&key, &recno,
+ sizeof(recno)),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ /* If there's no such revision, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_dangling_rev(fs, rev);
+
+ /* Handle any other error conditions. */
+ SVN_ERR(BDB_WRAP(fs, N_("reading filesystem revision"), db_err));
+
+ /* Parse REVISION skel. */
+ skel = svn_skel__parse(value.data, value.size, pool);
+ if (! skel)
+ return svn_fs_base__err_corrupt_fs_revision(fs, rev);
+
+ /* Convert skel to native type. */
+ SVN_ERR(svn_fs_base__parse_revision_skel(&revision, skel, pool));
+
+ *revision_p = revision;
+ return SVN_NO_ERROR;
+}
+
+
+/* Write REVISION to FS as part of TRAIL. If *REV is a valid revision
+ number, write this revision as one that corresponds to *REV, else
+ write a new revision and return its newly created revision number
+ in *REV. */
+svn_error_t *
+svn_fs_bdb__put_rev(svn_revnum_t *rev,
+ svn_fs_t *fs,
+ const revision_t *revision,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ db_recno_t recno = 0;
+ svn_skel_t *skel;
+ DBT key, value;
+
+ /* Convert native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_revision_skel(&skel, revision, pool));
+
+ if (SVN_IS_VALID_REVNUM(*rev))
+ {
+ DBT query, result;
+
+ /* Update the filesystem revision with the new skel. */
+ recno = (db_recno_t) *rev + 1;
+ svn_fs_base__trail_debug(trail, "revisions", "put");
+ db_err = bfd->revisions->put
+ (bfd->revisions, trail->db_txn,
+ svn_fs_base__set_dbt(&query, &recno, sizeof(recno)),
+ svn_fs_base__skel_to_dbt(&result, skel, pool), 0);
+ return BDB_WRAP(fs, N_("updating filesystem revision"), db_err);
+ }
+
+ svn_fs_base__trail_debug(trail, "revisions", "put");
+ db_err = bfd->revisions->put(bfd->revisions, trail->db_txn,
+ svn_fs_base__recno_dbt(&key, &recno),
+ svn_fs_base__skel_to_dbt(&value, skel, pool),
+ DB_APPEND);
+ SVN_ERR(BDB_WRAP(fs, N_("storing filesystem revision"), db_err));
+
+ /* Turn the record number into a Subversion revision number.
+ Revisions are numbered starting with zero; Berkeley DB record
+ numbers begin with one. */
+ *rev = recno - 1;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Getting the youngest revision. */
+
+
+svn_error_t *
+svn_fs_bdb__youngest_rev(svn_revnum_t *youngest_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBC *cursor = 0;
+ DBT key, value;
+ db_recno_t recno;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* Create a database cursor. */
+ svn_fs_base__trail_debug(trail, "revisions", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("getting youngest revision (creating cursor)"),
+ bfd->revisions->cursor(bfd->revisions, trail->db_txn,
+ &cursor, 0)));
+
+ /* Find the last entry in the `revisions' table. */
+ db_err = svn_bdb_dbc_get(cursor,
+ svn_fs_base__recno_dbt(&key, &recno),
+ svn_fs_base__nodata_dbt(&value),
+ DB_LAST);
+
+ if (db_err)
+ {
+ /* Free the cursor. Ignore any error value --- the error above
+ is more interesting. */
+ svn_bdb_dbc_close(cursor);
+
+ if (db_err == DB_NOTFOUND)
+ /* The revision 0 should always be present, at least. */
+ return
+ svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ "Corrupt DB: revision 0 missing from 'revisions' table, in "
+ "filesystem '%s'", fs->path);
+
+ SVN_ERR(BDB_WRAP(fs, N_("getting youngest revision (finding last entry)"),
+ db_err));
+ }
+
+ /* You can't commit a transaction with open cursors, because:
+ 1) key/value pairs don't get deleted until the cursors referring
+ to them are closed, so closing a cursor can fail for various
+ reasons, and txn_commit shouldn't fail that way, and
+ 2) using a cursor after committing its transaction can cause
+ undetectable database corruption. */
+ SVN_ERR(BDB_WRAP(fs, N_("getting youngest revision (closing cursor)"),
+ svn_bdb_dbc_close(cursor)));
+
+ /* Turn the record number into a Subversion revision number.
+ Revisions are numbered starting with zero; Berkeley DB record
+ numbers begin with one. */
+ *youngest_p = recno - 1;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/rev-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/rev-table.h
new file mode 100644
index 0000000..47209a8
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/rev-table.h
@@ -0,0 +1,85 @@
+/* rev-table.h : internal interface to revision table operations
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_REV_TABLE_H
+#define SVN_LIBSVN_FS_REV_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+
+#include "../fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Creating and opening the `revisions' table. */
+
+/* Open a `revisions' table in ENV. If CREATE is non-zero, create one
+ if it doesn't exist. Set *REVS_P to the new table. Return a
+ Berkeley DB error code. */
+int svn_fs_bdb__open_revisions_table(DB **revisions_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+
+/* Storing and retrieving filesystem revisions. */
+
+
+/* Set *REVISION_P to point to the revision structure for the
+ filesystem revision REV in FS, as part of TRAIL. Perform all
+ allocations in POOL. */
+svn_error_t *svn_fs_bdb__get_rev(revision_t **revision_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Store REVISION in FS as revision *REV as part of TRAIL. If *REV is
+ an invalid revision number, create a brand new revision and return
+ its revision number as *REV to the caller. Do any necessary
+ temporary allocation in POOL. */
+svn_error_t *svn_fs_bdb__put_rev(svn_revnum_t *rev,
+ svn_fs_t *fs,
+ const revision_t *revision,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *YOUNGEST_P to the youngest revision in filesystem FS,
+ as part of TRAIL. Use POOL for all temporary allocation. */
+svn_error_t *svn_fs_bdb__youngest_rev(svn_revnum_t *youngest_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_REV_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/strings-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/strings-table.c
new file mode 100644
index 0000000..f5348e7
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/strings-table.c
@@ -0,0 +1,541 @@
+/* strings-table.c : operations on the `strings' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "bdb_compat.h"
+#include "svn_fs.h"
+#include "svn_pools.h"
+#include "../fs.h"
+#include "../err.h"
+#include "dbt.h"
+#include "../trail.h"
+#include "../key-gen.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "strings-table.h"
+
+#include "svn_private_config.h"
+
+
+/*** Creating and opening the strings table. ***/
+
+int
+svn_fs_bdb__open_strings_table(DB **strings_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *strings;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&strings, env, 0));
+
+ /* Enable duplicate keys. This allows the data to be spread out across
+ multiple records. Note: this must occur before ->open(). */
+ BDB_ERR(strings->set_flags(strings, DB_DUP));
+
+ BDB_ERR((strings->open)(SVN_BDB_OPEN_PARAMS(strings, NULL),
+ "strings", 0, DB_BTREE,
+ open_flags, 0666));
+
+ if (create)
+ {
+ DBT key, value;
+
+ /* Create the `next-key' table entry. */
+ BDB_ERR(strings->put
+ (strings, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ *strings_p = strings;
+ return 0;
+}
+
+
+
+/*** Storing and retrieving strings. ***/
+
+/* Allocate *CURSOR and advance it to first row in the set of rows
+ whose key is defined by QUERY. Set *LENGTH to the size of that
+ first row. */
+static svn_error_t *
+locate_key(apr_size_t *length,
+ DBC **cursor,
+ DBT *query,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBT result;
+
+ svn_fs_base__trail_debug(trail, "strings", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
+ bfd->strings->cursor(bfd->strings, trail->db_txn,
+ cursor, 0)));
+
+ /* Set up the DBT for reading the length of the record. */
+ svn_fs_base__clear_dbt(&result);
+ result.ulen = 0;
+ result.flags |= DB_DBT_USERMEM;
+
+ /* Advance the cursor to the key that we're looking for. */
+ db_err = svn_bdb_dbc_get(*cursor, query, &result, DB_SET);
+
+ /* We don't need to svn_fs_base__track_dbt() the result, because nothing
+ was allocated in it. */
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ {
+ svn_bdb_dbc_close(*cursor);
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_STRING, 0,
+ "No such string '%s'", (const char *)query->data);
+ }
+ if (db_err)
+ {
+ DBT rerun;
+
+ if (db_err != SVN_BDB_DB_BUFFER_SMALL)
+ {
+ svn_bdb_dbc_close(*cursor);
+ return BDB_WRAP(fs, N_("moving cursor"), db_err);
+ }
+
+ /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
+ zero length buf), so we need to re-run the operation to make
+ it happen. */
+ svn_fs_base__clear_dbt(&rerun);
+ rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
+ db_err = svn_bdb_dbc_get(*cursor, query, &rerun, DB_SET);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(*cursor);
+ return BDB_WRAP(fs, N_("rerunning cursor move"), db_err);
+ }
+ }
+
+ /* ### this cast might not be safe? */
+ *length = (apr_size_t) result.size;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Advance CURSOR by a single row in the set of rows whose keys match
+ CURSOR's current location. Set *LENGTH to the size of that next
+ row. If any error occurs, CURSOR will be destroyed. */
+static int
+get_next_length(apr_size_t *length, DBC *cursor, DBT *query)
+{
+ DBT result;
+ int db_err;
+
+ /* Set up the DBT for reading the length of the record. */
+ svn_fs_base__clear_dbt(&result);
+ result.ulen = 0;
+ result.flags |= DB_DBT_USERMEM;
+
+ /* Note: this may change the QUERY DBT, but that's okay: we're going
+ to be sticking with the same key anyways. */
+ db_err = svn_bdb_dbc_get(cursor, query, &result, DB_NEXT_DUP);
+
+ /* Note that we exit on DB_NOTFOUND. The caller uses that to end a loop. */
+ if (db_err)
+ {
+ DBT rerun;
+
+ if (db_err != SVN_BDB_DB_BUFFER_SMALL)
+ {
+ svn_bdb_dbc_close(cursor);
+ return db_err;
+ }
+
+ /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
+ zero length buf), so we need to re-run the operation to make
+ it happen. */
+ svn_fs_base__clear_dbt(&rerun);
+ rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
+ db_err = svn_bdb_dbc_get(cursor, query, &rerun, DB_NEXT_DUP);
+ if (db_err)
+ svn_bdb_dbc_close(cursor);
+ }
+
+ /* ### this cast might not be safe? */
+ *length = (apr_size_t) result.size;
+ return db_err;
+}
+
+
+svn_error_t *
+svn_fs_bdb__string_read(svn_fs_t *fs,
+ const char *key,
+ char *buf,
+ svn_filesize_t offset,
+ apr_size_t *len,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ int db_err;
+ DBT query, result;
+ DBC *cursor;
+ apr_size_t length, bytes_read = 0;
+
+ svn_fs_base__str_to_dbt(&query, key);
+
+ SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool));
+
+ /* Seek through the records for this key, trying to find the record that
+ includes OFFSET. Note that we don't require reading from more than
+ one record since we're allowed to return partial reads. */
+ while (length <= offset)
+ {
+ offset -= length;
+
+ /* Remember, if any error happens, our cursor has been closed
+ for us. */
+ db_err = get_next_length(&length, cursor, &query);
+
+ /* No more records? They tried to read past the end. */
+ if (db_err == DB_NOTFOUND)
+ {
+ *len = 0;
+ return SVN_NO_ERROR;
+ }
+ if (db_err)
+ return BDB_WRAP(fs, N_("reading string"), db_err);
+ }
+
+ /* The current record contains OFFSET. Fetch the contents now. Note that
+ OFFSET has been moved to be relative to this record. The length could
+ quite easily extend past this record, so we use DB_DBT_PARTIAL and
+ read successive records until we've filled the request. */
+ while (1)
+ {
+ svn_fs_base__clear_dbt(&result);
+ result.data = buf + bytes_read;
+ result.ulen = *len - bytes_read;
+ result.doff = (u_int32_t)offset;
+ result.dlen = *len - bytes_read;
+ result.flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_CURRENT);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return BDB_WRAP(fs, N_("reading string"), db_err);
+ }
+
+ bytes_read += result.size;
+ if (bytes_read == *len)
+ {
+ /* Done with the cursor. */
+ SVN_ERR(BDB_WRAP(fs, N_("closing string-reading cursor"),
+ svn_bdb_dbc_close(cursor)));
+ break;
+ }
+
+ /* Remember, if any error happens, our cursor has been closed
+ for us. */
+ db_err = get_next_length(&length, cursor, &query);
+ if (db_err == DB_NOTFOUND)
+ break;
+ if (db_err)
+ return BDB_WRAP(fs, N_("reading string"), db_err);
+
+ /* We'll be reading from the beginning of the next record */
+ offset = 0;
+ }
+
+ *len = bytes_read;
+ return SVN_NO_ERROR;
+}
+
+
+/* Get the current 'next-key' value and bump the record. */
+static svn_error_t *
+get_key_and_bump(svn_fs_t *fs,
+ const char **key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBC *cursor;
+ char next_key[MAX_KEY_SIZE];
+ apr_size_t key_len;
+ int db_err;
+ DBT query;
+ DBT result;
+
+ /* ### todo: see issue #409 for why bumping the key as part of this
+ trail is problematic. */
+
+ /* Open a cursor and move it to the 'next-key' value. We can then fetch
+ the contents and use the cursor to overwrite those contents. Since
+ this database allows duplicates, we can't do an arbitrary 'put' to
+ write the new value -- that would append, not overwrite. */
+
+ svn_fs_base__trail_debug(trail, "strings", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
+ bfd->strings->cursor(bfd->strings, trail->db_txn,
+ &cursor, 0)));
+
+ /* Advance the cursor to 'next-key' and read it. */
+
+ db_err = svn_bdb_dbc_get(cursor,
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY),
+ svn_fs_base__result_dbt(&result),
+ DB_SET);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return BDB_WRAP(fs, N_("getting next-key value"), db_err);
+ }
+
+ svn_fs_base__track_dbt(&result, pool);
+ *key = apr_pstrmemdup(pool, result.data, result.size);
+
+ /* Bump to future key. */
+ key_len = result.size;
+ svn_fs_base__next_key(result.data, &key_len, next_key);
+
+ /* Shove the new key back into the database, at the cursor position. */
+ db_err = svn_bdb_dbc_put(cursor, &query,
+ svn_fs_base__str_to_dbt(&result, next_key),
+ DB_CURRENT);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor); /* ignore the error, the original is
+ more important. */
+ return BDB_WRAP(fs, N_("bumping next string key"), db_err);
+ }
+
+ return BDB_WRAP(fs, N_("closing string-reading cursor"),
+ svn_bdb_dbc_close(cursor));
+}
+
+svn_error_t *
+svn_fs_bdb__string_append(svn_fs_t *fs,
+ const char **key,
+ apr_size_t len,
+ const char *buf,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+
+ /* If the passed-in key is NULL, we graciously generate a new string
+ using the value of the `next-key' record in the strings table. */
+ if (*key == NULL)
+ {
+ SVN_ERR(get_key_and_bump(fs, key, trail, pool));
+ }
+
+ /* Store a new record into the database. */
+ svn_fs_base__trail_debug(trail, "strings", "put");
+ return BDB_WRAP(fs, N_("appending string"),
+ bfd->strings->put
+ (bfd->strings, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, *key),
+ svn_fs_base__set_dbt(&result, buf, len),
+ 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__string_clear(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBT query, result;
+
+ svn_fs_base__str_to_dbt(&query, key);
+
+ /* Torch the prior contents */
+ svn_fs_base__trail_debug(trail, "strings", "del");
+ db_err = bfd->strings->del(bfd->strings, trail->db_txn, &query, 0);
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_STRING, 0,
+ "No such string '%s'", key);
+
+ /* Handle any other error conditions. */
+ SVN_ERR(BDB_WRAP(fs, N_("clearing string"), db_err));
+
+ /* Shove empty data back in for this key. */
+ svn_fs_base__clear_dbt(&result);
+ result.data = 0;
+ result.size = 0;
+ result.flags |= DB_DBT_USERMEM;
+
+ svn_fs_base__trail_debug(trail, "strings", "put");
+ return BDB_WRAP(fs, N_("storing empty contents"),
+ bfd->strings->put(bfd->strings, trail->db_txn,
+ &query, &result, 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__string_size(svn_filesize_t *size,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ int db_err;
+ DBT query;
+ DBC *cursor;
+ apr_size_t length;
+ svn_filesize_t total;
+
+ svn_fs_base__str_to_dbt(&query, key);
+
+ SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool));
+
+ total = length;
+ while (1)
+ {
+ /* Remember, if any error happens, our cursor has been closed
+ for us. */
+ db_err = get_next_length(&length, cursor, &query);
+
+ /* No more records? Then return the total length. */
+ if (db_err == DB_NOTFOUND)
+ {
+ *size = total;
+ return SVN_NO_ERROR;
+ }
+ if (db_err)
+ return BDB_WRAP(fs, N_("fetching string length"), db_err);
+
+ total += length;
+ }
+
+ /* NOTREACHED */
+}
+
+
+svn_error_t *
+svn_fs_bdb__string_delete(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBT query;
+
+ svn_fs_base__trail_debug(trail, "strings", "del");
+ db_err = bfd->strings->del(bfd->strings, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, key), 0);
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_STRING, 0,
+ "No such string '%s'", key);
+
+ /* Handle any other error conditions. */
+ return BDB_WRAP(fs, N_("deleting string"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__string_copy(svn_fs_t *fs,
+ const char **new_key,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query;
+ DBT result;
+ DBT copykey;
+ DBC *cursor;
+ int db_err;
+
+ /* Copy off the old key in case the caller is sharing storage
+ between the old and new keys. */
+ const char *old_key = apr_pstrdup(pool, key);
+
+ SVN_ERR(get_key_and_bump(fs, new_key, trail, pool));
+
+ svn_fs_base__trail_debug(trail, "strings", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
+ bfd->strings->cursor(bfd->strings, trail->db_txn,
+ &cursor, 0)));
+
+ svn_fs_base__str_to_dbt(&query, old_key);
+ svn_fs_base__str_to_dbt(&copykey, *new_key);
+
+ svn_fs_base__clear_dbt(&result);
+
+ /* Move to the first record and fetch its data (under BDB's mem mgmt). */
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return BDB_WRAP(fs, N_("getting next-key value"), db_err);
+ }
+
+ while (1)
+ {
+ /* ### can we pass a BDB-provided buffer to another BDB function?
+ ### they are supposed to have a duration up to certain points
+ ### of calling back into BDB, but I'm not sure what the exact
+ ### rules are. it is definitely nicer to use BDB buffers here
+ ### to simplify things and reduce copies, but... hrm.
+ */
+
+ /* Write the data to the database */
+ svn_fs_base__trail_debug(trail, "strings", "put");
+ db_err = bfd->strings->put(bfd->strings, trail->db_txn,
+ &copykey, &result, 0);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return BDB_WRAP(fs, N_("writing copied data"), db_err);
+ }
+
+ /* Read the next chunk. Terminate loop if we're done. */
+ svn_fs_base__clear_dbt(&result);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
+ if (db_err == DB_NOTFOUND)
+ break;
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return BDB_WRAP(fs, N_("fetching string data for a copy"), db_err);
+ }
+ }
+
+ return BDB_WRAP(fs, N_("closing string-reading cursor"),
+ svn_bdb_dbc_close(cursor));
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/strings-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/strings-table.h
new file mode 100644
index 0000000..443cb72
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/strings-table.h
@@ -0,0 +1,143 @@
+/* strings-table.h : internal interface to `strings' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_STRINGS_TABLE_H
+#define SVN_LIBSVN_FS_STRINGS_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_io.h"
+#include "svn_fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* This interface provides raw access to the `strings' table. It does
+ not deal with deltification, undeltification, or skels. It just
+ reads and writes strings of bytes. */
+
+
+/* Open a `strings' table in ENV. If CREATE is non-zero, create
+ * one if it doesn't exist. Set *STRINGS_P to the new table.
+ * Return a Berkeley DB error code.
+ */
+int svn_fs_bdb__open_strings_table(DB **strings_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Read *LEN bytes into BUF from OFFSET in string KEY in FS, as part
+ * of TRAIL.
+ *
+ * On return, *LEN is set to the number of bytes read. If this value
+ * is less than the number requested, the end of the string has been
+ * reached (no error is returned on end-of-string).
+ *
+ * If OFFSET is past the end of the string, then *LEN will be set to
+ * zero. Callers which are advancing OFFSET as they read portions of
+ * the string can terminate their loop when *LEN is returned as zero
+ * (which will occur when OFFSET == length(the string)).
+ *
+ * If string KEY does not exist, the error SVN_ERR_FS_NO_SUCH_STRING
+ * is returned.
+ */
+svn_error_t *svn_fs_bdb__string_read(svn_fs_t *fs,
+ const char *key,
+ char *buf,
+ svn_filesize_t offset,
+ apr_size_t *len,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *SIZE to the size in bytes of string KEY in FS, as part of
+ * TRAIL.
+ *
+ * If string KEY does not exist, return SVN_ERR_FS_NO_SUCH_STRING.
+ */
+svn_error_t *svn_fs_bdb__string_size(svn_filesize_t *size,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Append LEN bytes from BUF to string *KEY in FS, as part of TRAIL.
+ *
+ * If *KEY is null, then create a new string and store the new key in
+ * *KEY (allocating it in POOL), and write LEN bytes from BUF
+ * as the initial contents of the string.
+ *
+ * If *KEY is not null but there is no string named *KEY, return
+ * SVN_ERR_FS_NO_SUCH_STRING.
+ *
+ * Note: to overwrite the old contents of a string, call
+ * svn_fs_bdb__string_clear() and then svn_fs_bdb__string_append(). */
+svn_error_t *svn_fs_bdb__string_append(svn_fs_t *fs,
+ const char **key,
+ apr_size_t len,
+ const char *buf,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Make string KEY in FS zero length, as part of TRAIL.
+ * If the string does not exist, return SVN_ERR_FS_NO_SUCH_STRING.
+ */
+svn_error_t *svn_fs_bdb__string_clear(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete string KEY from FS, as part of TRAIL.
+ *
+ * If string KEY does not exist, return SVN_ERR_FS_NO_SUCH_STRING.
+ *
+ * WARNING: Deleting a string renders unusable any representations
+ * that refer to it. Be careful.
+ */
+svn_error_t *svn_fs_bdb__string_delete(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Copy the contents of the string referred to by KEY in FS into a new
+ * record, returning the new record's key in *NEW_KEY. All
+ * allocations (including *NEW_KEY) occur in POOL. */
+svn_error_t *svn_fs_bdb__string_copy(svn_fs_t *fs,
+ const char **new_key,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_STRINGS_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/txn-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/txn-table.c
new file mode 100644
index 0000000..54a0e28
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/txn-table.c
@@ -0,0 +1,325 @@
+/* txn-table.c : operations on the `transactions' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+#include <assert.h>
+
+#include "bdb_compat.h"
+
+#include "svn_pools.h"
+#include "private/svn_skel.h"
+
+#include "dbt.h"
+#include "../err.h"
+#include "../fs.h"
+#include "../key-gen.h"
+#include "../util/fs_skels.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "txn-table.h"
+
+#include "svn_private_config.h"
+
+
+static svn_boolean_t
+is_committed(transaction_t *txn)
+{
+ return (txn->kind == transaction_kind_committed);
+}
+
+
+int
+svn_fs_bdb__open_transactions_table(DB **transactions_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *txns;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&txns, env, 0));
+ BDB_ERR((txns->open)(SVN_BDB_OPEN_PARAMS(txns, NULL),
+ "transactions", 0, DB_BTREE,
+ open_flags, 0666));
+
+ /* Create the `next-key' table entry. */
+ if (create)
+ {
+ DBT key, value;
+
+ BDB_ERR(txns->put(txns, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ *transactions_p = txns;
+ return 0;
+}
+
+
+svn_error_t *
+svn_fs_bdb__put_txn(svn_fs_t *fs,
+ const transaction_t *txn,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ svn_skel_t *txn_skel;
+ DBT key, value;
+
+ /* Convert native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_transaction_skel(&txn_skel, txn, pool));
+
+ /* Only in the context of this function do we know that the DB call
+ will not attempt to modify txn_name, so the cast belongs here. */
+ svn_fs_base__str_to_dbt(&key, txn_name);
+ svn_fs_base__skel_to_dbt(&value, txn_skel, pool);
+ svn_fs_base__trail_debug(trail, "transactions", "put");
+ return BDB_WRAP(fs, N_("storing transaction record"),
+ bfd->transactions->put(bfd->transactions, trail->db_txn,
+ &key, &value, 0));
+}
+
+
+/* Allocate a Subversion transaction ID in FS, as part of TRAIL. Set
+ *ID_P to the new transaction ID, allocated in POOL. */
+static svn_error_t *
+allocate_txn_id(const char **id_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ apr_size_t len;
+ char next_key[MAX_KEY_SIZE];
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+
+ /* Get the current value associated with the `next-key' key in the table. */
+ svn_fs_base__trail_debug(trail, "transactions", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("allocating new transaction ID (getting 'next-key')"),
+ bfd->transactions->get(bfd->transactions, trail->db_txn,
+ &query,
+ svn_fs_base__result_dbt(&result),
+ 0)));
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* Set our return value. */
+ *id_p = apr_pstrmemdup(pool, result.data, result.size);
+
+ /* Bump to future key. */
+ len = result.size;
+ svn_fs_base__next_key(result.data, &len, next_key);
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+ svn_fs_base__str_to_dbt(&result, next_key);
+ svn_fs_base__trail_debug(trail, "transactions", "put");
+ db_err = bfd->transactions->put(bfd->transactions, trail->db_txn,
+ &query, &result, 0);
+
+ return BDB_WRAP(fs, N_("bumping next transaction key"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__create_txn(const char **txn_name_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *root_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const char *txn_name;
+ transaction_t txn;
+
+ SVN_ERR(allocate_txn_id(&txn_name, fs, trail, pool));
+ txn.kind = transaction_kind_normal;
+ txn.root_id = root_id;
+ txn.base_id = root_id;
+ txn.proplist = NULL;
+ txn.copies = NULL;
+ txn.revision = SVN_INVALID_REVNUM;
+ SVN_ERR(svn_fs_bdb__put_txn(fs, &txn, txn_name, trail, pool));
+
+ *txn_name_p = txn_name;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__delete_txn(svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+ transaction_t *txn;
+
+ /* Make sure TXN is dead. */
+ SVN_ERR(svn_fs_bdb__get_txn(&txn, fs, txn_name, trail, pool));
+ if (is_committed(txn))
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ /* Delete the transaction from the `transactions' table. */
+ svn_fs_base__str_to_dbt(&key, txn_name);
+ svn_fs_base__trail_debug(trail, "transactions", "del");
+ return BDB_WRAP(fs, N_("deleting entry from 'transactions' table"),
+ bfd->transactions->del(bfd->transactions,
+ trail->db_txn, &key, 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__get_txn(transaction_t **txn_p,
+ svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+ svn_skel_t *skel;
+ transaction_t *transaction;
+
+ /* Only in the context of this function do we know that the DB call
+ will not attempt to modify txn_name, so the cast belongs here. */
+ svn_fs_base__trail_debug(trail, "transactions", "get");
+ db_err = bfd->transactions->get(bfd->transactions, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, txn_name),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_no_such_txn(fs, txn_name);
+ SVN_ERR(BDB_WRAP(fs, N_("reading transaction"), db_err));
+
+ /* Parse TRANSACTION skel */
+ skel = svn_skel__parse(value.data, value.size, pool);
+ if (! skel)
+ return svn_fs_base__err_corrupt_txn(fs, txn_name);
+
+ /* Convert skel to native type. */
+ SVN_ERR(svn_fs_base__parse_transaction_skel(&transaction, skel, pool));
+ *txn_p = transaction;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__get_txn_list(apr_array_header_t **names_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ apr_size_t const next_key_key_len = strlen(NEXT_KEY_KEY);
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_array_header_t *names;
+ DBC *cursor;
+ DBT key, value;
+ int db_err, db_c_err;
+
+ /* Allocate the initial names array */
+ names = apr_array_make(pool, 4, sizeof(const char *));
+
+ /* Create a database cursor to list the transaction names. */
+ svn_fs_base__trail_debug(trail, "transactions", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (opening cursor)"),
+ bfd->transactions->cursor(bfd->transactions,
+ trail->db_txn, &cursor, 0)));
+
+ /* Build a null-terminated array of keys in the transactions table. */
+ for (db_err = svn_bdb_dbc_get(cursor,
+ svn_fs_base__result_dbt(&key),
+ svn_fs_base__result_dbt(&value),
+ DB_FIRST);
+ db_err == 0;
+ db_err = svn_bdb_dbc_get(cursor,
+ svn_fs_base__result_dbt(&key),
+ svn_fs_base__result_dbt(&value),
+ DB_NEXT))
+ {
+ transaction_t *txn;
+ svn_skel_t *txn_skel;
+ svn_error_t *err;
+
+ /* Clear the per-iteration subpool */
+ svn_pool_clear(subpool);
+
+ /* Track the memory alloc'd for fetching the key and value here
+ so that when the containing pool is cleared, this memory is
+ freed. */
+ svn_fs_base__track_dbt(&key, subpool);
+ svn_fs_base__track_dbt(&value, subpool);
+
+ /* Ignore the "next-key" key. */
+ if (key.size == next_key_key_len
+ && 0 == memcmp(key.data, NEXT_KEY_KEY, next_key_key_len))
+ continue;
+
+ /* Parse TRANSACTION skel */
+ txn_skel = svn_skel__parse(value.data, value.size, subpool);
+ if (! txn_skel)
+ {
+ svn_bdb_dbc_close(cursor);
+ return svn_fs_base__err_corrupt_txn
+ (fs, apr_pstrmemdup(pool, key.data, key.size));
+ }
+
+ /* Convert skel to native type. */
+ if ((err = svn_fs_base__parse_transaction_skel(&txn, txn_skel,
+ subpool)))
+ {
+ svn_bdb_dbc_close(cursor);
+ return svn_error_trace(err);
+ }
+
+ /* If this is an immutable "committed" transaction, ignore it. */
+ if (is_committed(txn))
+ continue;
+
+ /* Add the transaction name to the NAMES array, duping it into POOL. */
+ APR_ARRAY_PUSH(names, const char *) = apr_pstrmemdup(pool, key.data,
+ key.size);
+ }
+
+ /* Check for errors, but close the cursor first. */
+ db_c_err = svn_bdb_dbc_close(cursor);
+ if (db_err != DB_NOTFOUND)
+ {
+ SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (listing keys)"),
+ db_err));
+ }
+ SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (closing cursor)"),
+ db_c_err));
+
+ /* Destroy the per-iteration subpool */
+ svn_pool_destroy(subpool);
+
+ *names_p = names;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/txn-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/txn-table.h
new file mode 100644
index 0000000..ff0cc9c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/txn-table.h
@@ -0,0 +1,100 @@
+/* txn-table.h : internal interface to ops on `transactions' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_TXN_TABLE_H
+#define SVN_LIBSVN_FS_TXN_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `transactions' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *TRANSACTIONS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_transactions_table(DB **transactions_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Create a new transaction in FS as part of TRAIL, with an initial
+ root and base root ID of ROOT_ID. Set *TXN_NAME_P to the name of the
+ new transaction, allocated in POOL. */
+svn_error_t *svn_fs_bdb__create_txn(const char **txn_name_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *root_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Remove the transaction whose name is TXN_NAME from the `transactions'
+ table of FS, as part of TRAIL.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+svn_error_t *svn_fs_bdb__delete_txn(svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Retrieve the transaction *TXN_P for the Subversion transaction
+ named TXN_NAME from the `transactions' table of FS, as part of
+ TRAIL. Perform all allocations in POOL.
+
+ If there is no such transaction, SVN_ERR_FS_NO_SUCH_TRANSACTION is
+ the error returned. */
+svn_error_t *svn_fs_bdb__get_txn(transaction_t **txn_p,
+ svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Store the Subversion transaction TXN in FS with an ID of TXN_NAME as
+ part of TRAIL. */
+svn_error_t *svn_fs_bdb__put_txn(svn_fs_t *fs,
+ const transaction_t *txn,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *NAMES_P to an array of const char * IDs (unfinished
+ transactions in FS) as part of TRAIL. Allocate the array and the
+ names in POOL, and use POOL for any temporary allocations. */
+svn_error_t *svn_fs_bdb__get_txn_list(apr_array_header_t **names_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_TXN_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/uuids-table.c b/contrib/subversion/subversion/libsvn_fs_base/bdb/uuids-table.c
new file mode 100644
index 0000000..0481894
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/uuids-table.c
@@ -0,0 +1,149 @@
+/* uuids-table.c : operations on the `uuids' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_uuid.h>
+
+#include "bdb_compat.h"
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../err.h"
+#include "dbt.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "uuids-table.h"
+
+#include "svn_private_config.h"
+
+
+/*** Creating and opening the uuids table.
+ When the table is created, the repository's uuid is
+ generated and stored as record #1. ***/
+
+int
+svn_fs_bdb__open_uuids_table(DB **uuids_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *uuids;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&uuids, env, 0));
+ BDB_ERR(uuids->set_re_len(uuids, APR_UUID_FORMATTED_LENGTH));
+
+ error = (uuids->open)(SVN_BDB_OPEN_PARAMS(uuids, NULL),
+ "uuids", 0, DB_RECNO,
+ open_flags, 0666);
+
+ /* This is a temporary compatibility check; it creates the
+ UUIDs table if one does not already exist. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(uuids->close(uuids, 0));
+ return svn_fs_bdb__open_uuids_table(uuids_p, env, TRUE);
+ }
+
+ BDB_ERR(error);
+
+ if (create)
+ {
+ char buffer[APR_UUID_FORMATTED_LENGTH + 1];
+ DBT key, value;
+ apr_uuid_t uuid;
+ int recno = 0;
+
+ svn_fs_base__clear_dbt(&key);
+ key.data = &recno;
+ key.size = sizeof(recno);
+ key.ulen = key.size;
+ key.flags |= DB_DBT_USERMEM;
+
+ svn_fs_base__clear_dbt(&value);
+ value.data = buffer;
+ value.size = sizeof(buffer) - 1;
+
+ apr_uuid_get(&uuid);
+ apr_uuid_format(buffer, &uuid);
+
+ BDB_ERR(uuids->put(uuids, 0, &key, &value, DB_APPEND));
+ }
+
+ *uuids_p = uuids;
+ return 0;
+}
+
+svn_error_t *svn_fs_bdb__get_uuid(svn_fs_t *fs,
+ int idx,
+ const char **uuid,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ char buffer[APR_UUID_FORMATTED_LENGTH + 1];
+ DB *uuids = bfd->uuids;
+ DBT key;
+ DBT value;
+
+ svn_fs_base__clear_dbt(&key);
+ key.data = &idx;
+ key.size = sizeof(idx);
+
+ svn_fs_base__clear_dbt(&value);
+ value.data = buffer;
+ value.size = sizeof(buffer) - 1;
+ value.ulen = value.size;
+ value.flags |= DB_DBT_USERMEM;
+
+ svn_fs_base__trail_debug(trail, "uuids", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("get repository uuid"),
+ uuids->get(uuids, trail->db_txn, &key, &value, 0)));
+
+ *uuid = apr_pstrmemdup(pool, value.data, value.size);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_fs_bdb__set_uuid(svn_fs_t *fs,
+ int idx,
+ const char *uuid,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DB *uuids = bfd->uuids;
+ DBT key;
+ DBT value;
+
+ svn_fs_base__clear_dbt(&key);
+ key.data = &idx;
+ key.size = sizeof(idx);
+
+ svn_fs_base__clear_dbt(&value);
+ value.size = (u_int32_t) strlen(uuid);
+ value.data = apr_pstrmemdup(pool, uuid, value.size + 1);
+
+ svn_fs_base__trail_debug(trail, "uuids", "put");
+ return BDB_WRAP(fs, N_("set repository uuid"),
+ uuids->put(uuids, trail->db_txn, &key, &value, 0));
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/bdb/uuids-table.h b/contrib/subversion/subversion/libsvn_fs_base/bdb/uuids-table.h
new file mode 100644
index 0000000..f6d38df
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/bdb/uuids-table.h
@@ -0,0 +1,69 @@
+/* uuids-table.h : internal interface to `uuids' table
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_UUIDS_TABLE_H
+#define SVN_LIBSVN_FS_UUIDS_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_io.h"
+#include "svn_fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `uuids' table in @a env.
+ *
+ * Open a `uuids' table in @a env. If @a create is non-zero, create
+ * one if it doesn't exist. Set @a *uuids_p to the new table.
+ * Return a Berkeley DB error code.
+ */
+int svn_fs_bdb__open_uuids_table(DB **uuids_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+/* Get the UUID at index @a idx in the uuids table within @a fs,
+ * storing the result in @a *uuid.
+ */
+svn_error_t *svn_fs_bdb__get_uuid(svn_fs_t *fs,
+ int idx,
+ const char **uuid,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Set the UUID at index @a idx in the uuids table within @a fs
+ * to @a uuid.
+ */
+svn_error_t *svn_fs_bdb__set_uuid(svn_fs_t *fs,
+ int idx,
+ const char *uuid,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_UUIDS_TABLE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/dag.c b/contrib/subversion/subversion/libsvn_fs_base/dag.c
new file mode 100644
index 0000000..510ccbb
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/dag.c
@@ -0,0 +1,1758 @@
+/* dag.c : DAG-like interface filesystem, private to libsvn_fs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+
+#include "svn_path.h"
+#include "svn_time.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_hash.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "dag.h"
+#include "err.h"
+#include "fs.h"
+#include "key-gen.h"
+#include "node-rev.h"
+#include "trail.h"
+#include "reps-strings.h"
+#include "revs-txns.h"
+#include "id.h"
+
+#include "util/fs_skels.h"
+
+#include "bdb/txn-table.h"
+#include "bdb/rev-table.h"
+#include "bdb/nodes-table.h"
+#include "bdb/copies-table.h"
+#include "bdb/reps-table.h"
+#include "bdb/strings-table.h"
+#include "bdb/checksum-reps-table.h"
+#include "bdb/changes-table.h"
+#include "bdb/node-origins-table.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+
+/* Initializing a filesystem. */
+
+struct dag_node_t
+{
+ /*** NOTE: Keeping in-memory representations of disk data that can
+ be changed by other accessors is a nasty business. Such
+ representations are basically a cache with some pretty complex
+ invalidation rules. For example, the "node revision"
+ associated with a DAG node ID can look completely different to
+ a process that has modified that information as part of a
+ Berkeley DB transaction than it does to some other process.
+ That said, there are some aspects of a "node revision" which
+ never change, like its 'id' or 'kind'. Our best bet is to
+ limit ourselves to exposing outside of this interface only
+ those immutable aspects of a DAG node representation. ***/
+
+ /* The filesystem this dag node came from. */
+ svn_fs_t *fs;
+
+ /* The node revision ID for this dag node. */
+ svn_fs_id_t *id;
+
+ /* The node's type (file, dir, etc.) */
+ svn_node_kind_t kind;
+
+ /* the path at which this node was created. */
+ const char *created_path;
+};
+
+
+
+/* Trivial helper/accessor functions. */
+svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node)
+{
+ return node->kind;
+}
+
+
+const svn_fs_id_t *
+svn_fs_base__dag_get_id(dag_node_t *node)
+{
+ return node->id;
+}
+
+
+const char *
+svn_fs_base__dag_get_created_path(dag_node_t *node)
+{
+ return node->created_path;
+}
+
+
+svn_fs_t *
+svn_fs_base__dag_get_fs(dag_node_t *node)
+{
+ return node->fs;
+}
+
+
+svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node,
+ const char *txn_id)
+{
+ return (strcmp(svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)),
+ txn_id) == 0);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_node(dag_node_t **node,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *new_node;
+ node_revision_t *noderev;
+
+ /* Construct the node. */
+ new_node = apr_pcalloc(pool, sizeof(*new_node));
+ new_node->fs = fs;
+ new_node->id = svn_fs_base__id_copy(id, pool);
+
+ /* Grab the contents so we can cache some of the immutable parts of it. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
+
+ /* Initialize the KIND and CREATED_PATH attributes */
+ new_node->kind = noderev->kind;
+ new_node->created_path = noderev->created_path;
+
+ /* Return a fresh new node */
+ *node = new_node;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_revision(svn_revnum_t *rev,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* Use the txn ID from the NODE's id to look up the transaction and
+ get its revision number. */
+ return svn_fs_base__txn_get_revision
+ (rev, svn_fs_base__dag_get_fs(node),
+ svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)), trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+ *id_p = noderev->predecessor_id;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_predecessor_count(int *count,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+ *count = noderev->predecessor_count;
+ return SVN_NO_ERROR;
+}
+
+
+/* Trail body for svn_fs_base__dag_init_fs. */
+static svn_error_t *
+txn_body_dag_init_fs(void *baton,
+ trail_t *trail)
+{
+ node_revision_t noderev;
+ revision_t revision;
+ svn_revnum_t rev = SVN_INVALID_REVNUM;
+ svn_fs_t *fs = trail->fs;
+ svn_string_t date;
+ const char *txn_id;
+ const char *copy_id;
+ svn_fs_id_t *root_id = svn_fs_base__id_create("0", "0", "0", trail->pool);
+
+ /* Create empty root directory with node revision 0.0.0. */
+ memset(&noderev, 0, sizeof(noderev));
+ noderev.kind = svn_node_dir;
+ noderev.created_path = "/";
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, root_id, &noderev,
+ trail, trail->pool));
+
+ /* Create a new transaction (better have an id of "0") */
+ SVN_ERR(svn_fs_bdb__create_txn(&txn_id, fs, root_id, trail, trail->pool));
+ if (strcmp(txn_id, "0"))
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: initial transaction id not '0' in filesystem '%s'"),
+ fs->path);
+
+ /* Create a default copy (better have an id of "0") */
+ SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, trail->pool));
+ if (strcmp(copy_id, "0"))
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: initial copy id not '0' in filesystem '%s'"), fs->path);
+ SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, NULL, NULL, root_id,
+ copy_kind_real, trail, trail->pool));
+
+ /* Link it into filesystem revision 0. */
+ revision.txn_id = txn_id;
+ SVN_ERR(svn_fs_bdb__put_rev(&rev, fs, &revision, trail, trail->pool));
+ if (rev != 0)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: initial revision number "
+ "is not '0' in filesystem '%s'"), fs->path);
+
+ /* Promote our transaction to a "committed" transaction. */
+ SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, rev,
+ trail, trail->pool));
+
+ /* Set a date on revision 0. */
+ date.data = svn_time_to_cstring(apr_time_now(), trail->pool);
+ date.len = strlen(date.data);
+ return svn_fs_base__set_rev_prop(fs, 0, SVN_PROP_REVISION_DATE, NULL, &date,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_init_fs(svn_fs_t *fs)
+{
+ return svn_fs_base__retry_txn(fs, txn_body_dag_init_fs, NULL,
+ TRUE, fs->pool);
+}
+
+
+
+/*** Directory node functions ***/
+
+/* Some of these are helpers for functions outside this section. */
+
+/* Given directory NODEREV in FS, set *ENTRIES_P to its entries list
+ hash, as part of TRAIL, or to NULL if NODEREV has no entries. The
+ entries list will be allocated in POOL, and the entries in that
+ list will not have interesting value in their 'kind' fields. If
+ NODEREV is not a directory, return the error SVN_ERR_FS_NOT_DIRECTORY. */
+static svn_error_t *
+get_dir_entries(apr_hash_t **entries_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_hash_t *entries = NULL;
+ apr_hash_index_t *hi;
+ svn_string_t entries_raw;
+ svn_skel_t *entries_skel;
+
+ /* Error if this is not a directory. */
+ if (noderev->kind != svn_node_dir)
+ return svn_error_create
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to get entries of a non-directory node"));
+
+ /* If there's a DATA-KEY, there might be entries to fetch. */
+ if (noderev->data_key)
+ {
+ /* Now we have a rep, follow through to get the entries. */
+ SVN_ERR(svn_fs_base__rep_contents(&entries_raw, fs, noderev->data_key,
+ trail, pool));
+ entries_skel = svn_skel__parse(entries_raw.data, entries_raw.len, pool);
+
+ /* Were there entries? Make a hash from them. */
+ if (entries_skel)
+ SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
+ pool));
+ }
+
+ /* No hash? No problem. */
+ *entries_p = NULL;
+ if (! entries)
+ return SVN_NO_ERROR;
+
+ /* Else, convert the hash from a name->id mapping to a name->dirent one. */
+ *entries_p = apr_hash_make(pool);
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+ svn_fs_dirent_t *dirent = apr_palloc(pool, sizeof(*dirent));
+
+ /* KEY will be the entry name in ancestor, VAL the id. */
+ apr_hash_this(hi, &key, &klen, &val);
+ dirent->name = key;
+ dirent->id = val;
+ dirent->kind = svn_node_unknown;
+ apr_hash_set(*entries_p, key, klen, dirent);
+ }
+
+ /* Return our findings. */
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *ID_P to the node-id for entry NAME in PARENT, as part of
+ TRAIL. If no such entry, set *ID_P to NULL but do not error. The
+ entry is allocated in POOL or in the same pool as PARENT;
+ the caller should copy if it cares. */
+static svn_error_t *
+dir_entry_id_from_node(const svn_fs_id_t **id_p,
+ dag_node_t *parent,
+ const char *name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_hash_t *entries;
+ svn_fs_dirent_t *dirent;
+
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, parent, trail, pool));
+ if (entries)
+ dirent = svn_hash_gets(entries, name);
+ else
+ dirent = NULL;
+
+ *id_p = dirent ? dirent->id : NULL;
+ return SVN_NO_ERROR;
+}
+
+
+/* Add or set in PARENT a directory entry NAME pointing to ID.
+ Allocations are done in TRAIL.
+
+ Assumptions:
+ - PARENT is a mutable directory.
+ - ID does not refer to an ancestor of parent
+ - NAME is a single path component
+*/
+static svn_error_t *
+set_entry(dag_node_t *parent,
+ const char *name,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *parent_noderev;
+ const char *rep_key, *mutable_rep_key;
+ apr_hash_t *entries = NULL;
+ svn_stream_t *wstream;
+ apr_size_t len;
+ svn_string_t raw_entries;
+ svn_stringbuf_t *raw_entries_buf;
+ svn_skel_t *entries_skel;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
+
+ /* Get the parent's node-revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
+ trail, pool));
+ rep_key = parent_noderev->data_key;
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
+ fs, txn_id, trail, pool));
+
+ /* If the parent node already pointed at a mutable representation,
+ we don't need to do anything. But if it didn't, either because
+ the parent didn't refer to any rep yet or because it referred to
+ an immutable one, we must make the parent refer to the mutable
+ rep we just created. */
+ if (! svn_fs_base__same_keys(rep_key, mutable_rep_key))
+ {
+ parent_noderev->data_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
+ trail, pool));
+ }
+
+ /* If the new representation inherited nothing, start a new entries
+ list for it. Else, go read its existing entries list. */
+ if (rep_key)
+ {
+ SVN_ERR(svn_fs_base__rep_contents(&raw_entries, fs, rep_key,
+ trail, pool));
+ entries_skel = svn_skel__parse(raw_entries.data, raw_entries.len, pool);
+ if (entries_skel)
+ SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
+ pool));
+ }
+
+ /* If we still have no ENTRIES hash, make one here. */
+ if (! entries)
+ entries = apr_hash_make(pool);
+
+ /* Now, add our new entry to the entries list. */
+ svn_hash_sets(entries, name, id);
+
+ /* Finally, replace the old entries list with the new one. */
+ SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries,
+ pool));
+ raw_entries_buf = svn_skel__unparse(entries_skel, pool);
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
+ mutable_rep_key, txn_id,
+ TRUE, trail, pool));
+ len = raw_entries_buf->len;
+ SVN_ERR(svn_stream_write(wstream, raw_entries_buf->data, &len));
+ return svn_stream_close(wstream);
+}
+
+
+/* Make a new entry named NAME in PARENT, as part of TRAIL. If IS_DIR
+ is true, then the node revision the new entry points to will be a
+ directory, else it will be a file. The new node will be allocated
+ in POOL. PARENT must be mutable, and must not have an entry
+ named NAME. */
+static svn_error_t *
+make_entry(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ svn_boolean_t is_dir,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *new_node_id;
+ node_revision_t new_noderev;
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ _("Attempted to create a node with an illegal name '%s'"), name);
+
+ /* Make sure that parent is a directory */
+ if (parent->kind != svn_node_dir)
+ return svn_error_create
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to create entry in non-directory parent"));
+
+ /* Check that the parent is mutable. */
+ if (! svn_fs_base__dag_check_mutable(parent, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to clone child of non-mutable node"));
+
+ /* Check that parent does not already have an entry named NAME. */
+ SVN_ERR(dir_entry_id_from_node(&new_node_id, parent, name, trail, pool));
+ if (new_node_id)
+ return svn_error_createf
+ (SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Attempted to create entry that already exists"));
+
+ /* Create the new node's NODE-REVISION */
+ memset(&new_noderev, 0, sizeof(new_noderev));
+ new_noderev.kind = is_dir ? svn_node_dir : svn_node_file;
+ new_noderev.created_path = svn_fspath__join(parent_path, name, pool);
+ SVN_ERR(svn_fs_base__create_node
+ (&new_node_id, svn_fs_base__dag_get_fs(parent), &new_noderev,
+ svn_fs_base__id_copy_id(svn_fs_base__dag_get_id(parent)),
+ txn_id, trail, pool));
+
+ /* Create a new dag_node_t for our new node */
+ SVN_ERR(svn_fs_base__dag_get_node(child_p,
+ svn_fs_base__dag_get_fs(parent),
+ new_node_id, trail, pool));
+
+ /* We can safely call set_entry because we already know that
+ PARENT is mutable, and we just created CHILD, so we know it has
+ no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */
+ return set_entry(parent, name, svn_fs_base__dag_get_id(*child_p),
+ txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_dir_entries(apr_hash_t **entries,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+ return get_dir_entries(entries, node->fs, noderev, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_set_entry(dag_node_t *node,
+ const char *entry_name,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* Check it's a directory. */
+ if (node->kind != svn_node_dir)
+ return svn_error_create
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to set entry in non-directory node"));
+
+ /* Check it's mutable. */
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return svn_error_create
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to set entry in immutable node"));
+
+ return set_entry(node, entry_name, id, txn_id, trail, pool);
+}
+
+
+
+/*** Proplists. ***/
+
+svn_error_t *
+svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ apr_hash_t *proplist = NULL;
+ svn_string_t raw_proplist;
+ svn_skel_t *proplist_skel;
+
+ /* Go get a fresh NODE-REVISION for this node. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+
+ /* Get property key (returning early if there isn't one) . */
+ if (! noderev->prop_key)
+ {
+ *proplist_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Get the string associated with the property rep, parsing it as a
+ skel, and then attempt to parse *that* into a property hash. */
+ SVN_ERR(svn_fs_base__rep_contents(&raw_proplist,
+ svn_fs_base__dag_get_fs(node),
+ noderev->prop_key, trail, pool));
+ proplist_skel = svn_skel__parse(raw_proplist.data, raw_proplist.len, pool);
+ if (proplist_skel)
+ SVN_ERR(svn_skel__parse_proplist(&proplist, proplist_skel, pool));
+
+ *proplist_p = proplist;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_set_proplist(dag_node_t *node,
+ const apr_hash_t *proplist,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ const char *rep_key, *mutable_rep_key;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ svn_stream_t *wstream;
+ apr_size_t len;
+ svn_skel_t *proplist_skel;
+ svn_stringbuf_t *raw_proplist_buf;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* Sanity check: this node better be mutable! */
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ {
+ svn_string_t *idstr = svn_fs_base__id_unparse(node->id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Can't set proplist on *immutable* node-revision %s"),
+ idstr->data);
+ }
+
+ /* Go get a fresh NODE-REVISION for this node. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, node->id,
+ trail, pool));
+ rep_key = noderev->prop_key;
+
+ /* Flatten the proplist into a string. */
+ SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, proplist, pool));
+ raw_proplist_buf = svn_skel__unparse(proplist_skel, pool);
+
+ /* If this repository supports representation sharing, and the
+ resulting property list is exactly the same as another string in
+ the database, just use the previously existing string and get
+ outta here. */
+ if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
+ {
+ svn_error_t *err;
+ const char *dup_rep_key;
+ svn_checksum_t *checksum;
+
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1, raw_proplist_buf->data,
+ raw_proplist_buf->len, pool));
+
+ err = svn_fs_bdb__get_checksum_rep(&dup_rep_key, fs, checksum,
+ trail, pool);
+ if (! err)
+ {
+ if (noderev->prop_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
+ txn_id, trail, pool));
+ noderev->prop_key = dup_rep_key;
+ return svn_fs_bdb__put_node_revision(fs, node->id, noderev,
+ trail, pool);
+ }
+ else if (err)
+ {
+ if (err->apr_err != SVN_ERR_FS_NO_SUCH_CHECKSUM_REP)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ }
+
+ /* Get a mutable version of this rep (updating the node revision if
+ this isn't a NOOP) */
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
+ fs, txn_id, trail, pool));
+ if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
+ {
+ noderev->prop_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, node->id, noderev,
+ trail, pool));
+ }
+
+ /* Replace the old property list with the new one. */
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
+ mutable_rep_key, txn_id,
+ TRUE, trail, pool));
+ len = raw_proplist_buf->len;
+ SVN_ERR(svn_stream_write(wstream, raw_proplist_buf->data, &len));
+ SVN_ERR(svn_stream_close(wstream));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Roots. ***/
+
+svn_error_t *
+svn_fs_base__dag_revision_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *root_id;
+
+ SVN_ERR(svn_fs_base__rev_get_root(&root_id, fs, rev, trail, pool));
+ return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_txn_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *root_id, *ignored;
+
+ SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &ignored, fs, txn_id,
+ trail, pool));
+ return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_txn_base_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *base_root_id, *ignored;
+
+ SVN_ERR(svn_fs_base__get_txn_ids(&ignored, &base_root_id, fs, txn_id,
+ trail, pool));
+ return svn_fs_base__dag_get_node(node_p, fs, base_root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_clone_child(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *cur_entry; /* parent's current entry named NAME */
+ const svn_fs_id_t *new_node_id; /* node id we'll put into NEW_NODE */
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
+
+ /* First check that the parent is mutable. */
+ if (! svn_fs_base__dag_check_mutable(parent, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to clone child of non-mutable node"));
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ _("Attempted to make a child clone with an illegal name '%s'"), name);
+
+ /* Find the node named NAME in PARENT's entries list if it exists. */
+ SVN_ERR(svn_fs_base__dag_open(&cur_entry, parent, name, trail, pool));
+
+ /* Check for mutability in the node we found. If it's mutable, we
+ don't need to clone it. */
+ if (svn_fs_base__dag_check_mutable(cur_entry, txn_id))
+ {
+ /* This has already been cloned */
+ new_node_id = cur_entry->id;
+ }
+ else
+ {
+ node_revision_t *noderev;
+
+ /* Go get a fresh NODE-REVISION for current child node. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, cur_entry->id,
+ trail, pool));
+
+ /* Do the clone thingy here. */
+ noderev->predecessor_id = cur_entry->id;
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ noderev->created_path = svn_fspath__join(parent_path, name, pool);
+ SVN_ERR(svn_fs_base__create_successor(&new_node_id, fs, cur_entry->id,
+ noderev, copy_id, txn_id,
+ trail, pool));
+
+ /* Replace the ID in the parent's ENTRY list with the ID which
+ refers to the mutable clone of this child. */
+ SVN_ERR(set_entry(parent, name, new_node_id, txn_id, trail, pool));
+ }
+
+ /* Initialize the youngster. */
+ return svn_fs_base__dag_get_node(child_p, fs, new_node_id, trail, pool);
+}
+
+
+
+svn_error_t *
+svn_fs_base__dag_clone_root(dag_node_t **root_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *base_root_id, *root_id;
+ node_revision_t *noderev;
+
+ /* Get the node ID's of the root directories of the transaction and
+ its base revision. */
+ SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs, txn_id,
+ trail, pool));
+
+ /* Oh, give me a clone...
+ (If they're the same, we haven't cloned the transaction's root
+ directory yet.) */
+ if (svn_fs_base__id_eq(root_id, base_root_id))
+ {
+ const char *base_copy_id = svn_fs_base__id_copy_id(base_root_id);
+
+ /* Of my own flesh and bone...
+ (Get the NODE-REVISION for the base node, and then write
+ it back out as the clone.) */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, base_root_id,
+ trail, pool));
+
+ /* With its Y-chromosome changed to X...
+ (Store it with an updated predecessor count.) */
+ /* ### TODO: Does it even makes sense to have a different copy id for
+ the root node? That is, does this function need a copy_id
+ passed in? */
+ noderev->predecessor_id = svn_fs_base__id_copy(base_root_id, pool);
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ SVN_ERR(svn_fs_base__create_successor(&root_id, fs, base_root_id,
+ noderev, base_copy_id,
+ txn_id, trail, pool));
+
+ /* ... And when it is grown
+ * Then my own little clone
+ * Will be of the opposite sex!
+ */
+ SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, root_id, trail, pool));
+ }
+
+ /*
+ * (Sung to the tune of "Home, Home on the Range", with thanks to
+ * Randall Garrett and Isaac Asimov.)
+ */
+
+ /* One way or another, root_id now identifies a cloned root node. */
+ return svn_fs_base__dag_get_node(root_p, fs, root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_delete(dag_node_t *parent,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *parent_noderev;
+ const char *rep_key, *mutable_rep_key;
+ apr_hash_t *entries = NULL;
+ svn_skel_t *entries_skel;
+ svn_fs_t *fs = parent->fs;
+ svn_string_t str;
+ svn_fs_id_t *id = NULL;
+ dag_node_t *node;
+
+ /* Make sure parent is a directory. */
+ if (parent->kind != svn_node_dir)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to delete entry '%s' from *non*-directory node"), name);
+
+ /* Make sure parent is mutable. */
+ if (! svn_fs_base__dag_check_mutable(parent, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to delete entry '%s' from immutable directory node"),
+ name);
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ _("Attempted to delete a node with an illegal name '%s'"), name);
+
+ /* Get a fresh NODE-REVISION for the parent node. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
+ trail, pool));
+
+ /* Get the key for the parent's entries list (data) representation. */
+ rep_key = parent_noderev->data_key;
+
+ /* No REP_KEY means no representation, and no representation means
+ no data, and no data means no entries...there's nothing here to
+ delete! */
+ if (! rep_key)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
+ _("Delete failed: directory has no entry '%s'"), name);
+
+ /* Ensure we have a key to a mutable representation of the entries
+ list. We'll have to update the NODE-REVISION if it points to an
+ immutable version. */
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
+ fs, txn_id, trail, pool));
+ if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
+ {
+ parent_noderev->data_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
+ trail, pool));
+ }
+
+ /* Read the representation, then use it to get the string that holds
+ the entries list. Parse that list into a skel, and parse *that*
+ into a hash. */
+
+ SVN_ERR(svn_fs_base__rep_contents(&str, fs, rep_key, trail, pool));
+ entries_skel = svn_skel__parse(str.data, str.len, pool);
+ if (entries_skel)
+ SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, pool));
+
+ /* Find NAME in the ENTRIES skel. */
+ if (entries)
+ id = svn_hash_gets(entries, name);
+
+ /* If we never found ID in ENTRIES (perhaps because there are no
+ ENTRIES, perhaps because ID just isn't in the existing ENTRIES
+ ... it doesn't matter), return an error. */
+ if (! id)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
+ _("Delete failed: directory has no entry '%s'"), name);
+
+ /* Use the ID of this ENTRY to get the entry's node. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, svn_fs_base__dag_get_fs(parent),
+ id, trail, pool));
+
+ /* If mutable, remove it and any mutable children from db. */
+ SVN_ERR(svn_fs_base__dag_delete_if_mutable(parent->fs, id, txn_id,
+ trail, pool));
+
+ /* Remove this entry from its parent's entries list. */
+ svn_hash_sets(entries, name, NULL);
+
+ /* Replace the old entries list with the new one. */
+ {
+ svn_stream_t *ws;
+ svn_stringbuf_t *unparsed_entries;
+ apr_size_t len;
+
+ SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries, pool));
+ unparsed_entries = svn_skel__unparse(entries_skel, pool);
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
+ txn_id, TRUE, trail,
+ pool));
+ len = unparsed_entries->len;
+ SVN_ERR(svn_stream_write(ws, unparsed_entries->data, &len));
+ SVN_ERR(svn_stream_close(ws));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_remove_node(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+ node_revision_t *noderev;
+
+ /* Fetch the node. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
+
+ /* If immutable, do nothing and return immediately. */
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted removal of immutable node"));
+
+ /* Get a fresh node-revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
+
+ /* Delete any mutable property representation. */
+ if (noderev->prop_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
+ txn_id, trail, pool));
+
+ /* Delete any mutable data representation. */
+ if (noderev->data_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->data_key,
+ txn_id, trail, pool));
+
+ /* Delete any mutable edit representation (files only). */
+ if (noderev->edit_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
+ txn_id, trail, pool));
+
+ /* Delete the node revision itself. */
+ return svn_fs_base__delete_node_revision(fs, id,
+ noderev->predecessor_id == NULL,
+ trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+
+ /* Get the node. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
+
+ /* If immutable, do nothing and return immediately. */
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return SVN_NO_ERROR;
+
+ /* Else it's mutable. Recurse on directories... */
+ if (node->kind == svn_node_dir)
+ {
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+
+ /* Loop over hash entries */
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, pool));
+ if (entries)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ void *val;
+ svn_fs_dirent_t *dirent;
+
+ apr_hash_this(hi, NULL, NULL, &val);
+ dirent = val;
+ SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id,
+ txn_id, trail,
+ subpool));
+ }
+ }
+ }
+
+ /* ... then delete the node itself, any mutable representations and
+ strings it points to, and possibly its node-origins record. */
+ return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_make_file(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* Call our little helper function */
+ return make_entry(child_p, parent, parent_path, name, FALSE,
+ txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_make_dir(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* Call our little helper function */
+ return make_entry(child_p, parent, parent_path, name, TRUE,
+ txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_contents(svn_stream_t **contents,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ _("Attempted to get textual contents of a *non*-file node"));
+
+ /* Go get a fresh node-revision for FILE. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
+ trail, pool));
+
+ /* Our job is to _return_ a stream on the file's contents, so the
+ stream has to be trail-independent. Here, we pass NULL to tell
+ the stream that we're not providing it a trail that lives across
+ reads. This means the stream will do each read in a one-off,
+ temporary trail. */
+ return svn_fs_base__rep_contents_read_stream(contents, file->fs,
+ noderev->data_key,
+ FALSE, trail, pool);
+
+ /* Note that we're not registering any `close' func, because there's
+ nothing to cleanup outside of our trail. When the trail is
+ freed, the stream/baton will be too. */
+}
+
+
+svn_error_t *
+svn_fs_base__dag_file_length(svn_filesize_t *length,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ _("Attempted to get length of a *non*-file node"));
+
+ /* Go get a fresh node-revision for FILE, and . */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
+ trail, pool));
+ if (noderev->data_key)
+ SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs,
+ noderev->data_key, trail, pool));
+ else
+ *length = 0;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_file_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t checksum_kind,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ _("Attempted to get checksum of a *non*-file node"));
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
+ trail, pool));
+ if (! noderev->data_key)
+ {
+ *checksum = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (checksum_kind == svn_checksum_md5)
+ return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs,
+ noderev->data_key,
+ trail, pool);
+ else if (checksum_kind == svn_checksum_sha1)
+ return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs,
+ noderev->data_key,
+ trail, pool);
+ else
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_edit_stream(svn_stream_t **contents,
+ dag_node_t *file,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = file->fs; /* just for nicer indentation */
+ node_revision_t *noderev;
+ const char *mutable_rep_key;
+ svn_stream_t *ws;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ _("Attempted to set textual contents of a *non*-file node"));
+
+ /* Make sure our node is mutable. */
+ if (! svn_fs_base__dag_check_mutable(file, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to set textual contents of an immutable node"));
+
+ /* Get the node revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
+ trail, pool));
+
+ /* If this node already has an EDIT-DATA-KEY, destroy the data
+ associated with that key. */
+ if (noderev->edit_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
+ txn_id, trail, pool));
+
+ /* Now, let's ensure that we have a new EDIT-DATA-KEY available for
+ use. */
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs,
+ txn_id, trail, pool));
+
+ /* We made a new rep, so update the node revision. */
+ noderev->edit_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev,
+ trail, pool));
+
+ /* Return a writable stream with which to set new contents. */
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
+ txn_id, FALSE, trail,
+ pool));
+ *contents = ws;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_base__dag_finalize_edits(dag_node_t *file,
+ const svn_checksum_t *checksum,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = file->fs; /* just for nicer indentation */
+ node_revision_t *noderev;
+ const char *old_data_key, *new_data_key, *useless_data_key = NULL;
+ const char *data_key_uniquifier = NULL;
+ svn_checksum_t *md5_checksum, *sha1_checksum;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ _("Attempted to set textual contents of a *non*-file node"));
+
+ /* Make sure our node is mutable. */
+ if (! svn_fs_base__dag_check_mutable(file, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to set textual contents of an immutable node"));
+
+ /* Get the node revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
+ trail, pool));
+
+ /* If this node has no EDIT-DATA-KEY, this is a no-op. */
+ if (! noderev->edit_key)
+ return SVN_NO_ERROR;
+
+ /* Get our representation's checksums. */
+ SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum,
+ fs, noderev->edit_key,
+ trail, pool));
+
+ /* If our caller provided a checksum of the right kind to compare, do so. */
+ if (checksum)
+ {
+ svn_checksum_t *test_checksum;
+
+ if (checksum->kind == svn_checksum_md5)
+ test_checksum = md5_checksum;
+ else if (checksum->kind == svn_checksum_sha1)
+ test_checksum = sha1_checksum;
+ else
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
+
+ if (! svn_checksum_match(checksum, test_checksum))
+ return svn_checksum_mismatch_err(checksum, test_checksum, pool,
+ _("Checksum mismatch on representation '%s'"),
+ noderev->edit_key);
+ }
+
+ /* Now, we want to delete the old representation and replace it with
+ the new. Of course, we don't actually delete anything until
+ everything is being properly referred to by the node-revision
+ skel.
+
+ Now, if the result of all this editing is that we've created a
+ representation that describes content already represented
+ immutably in our database, we don't even need to keep these edits.
+ We can simply point our data_key at that pre-existing
+ representation and throw away our work! In this situation,
+ though, we'll need a unique ID to help other code distinguish
+ between "the contents weren't touched" and "the contents were
+ touched but still look the same" (to state it oversimply). */
+ old_data_key = noderev->data_key;
+ if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
+ {
+ svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs,
+ sha1_checksum,
+ trail, pool);
+ if (! err)
+ {
+ useless_data_key = noderev->edit_key;
+ err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier,
+ trail->fs, trail, pool);
+ }
+ else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ new_data_key = noderev->edit_key;
+ }
+ SVN_ERR(err);
+ }
+ else
+ {
+ new_data_key = noderev->edit_key;
+ }
+
+ noderev->data_key = new_data_key;
+ noderev->data_key_uniquifier = data_key_uniquifier;
+ noderev->edit_key = NULL;
+
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool));
+
+ /* Only *now* can we safely destroy the old representation (if it
+ even existed in the first place). */
+ if (old_data_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id,
+ trail, pool));
+
+ /* If we've got a discardable rep (probably because we ended up
+ re-using a preexisting one), throw out the discardable rep. */
+ if (useless_data_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key,
+ txn_id, trail, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+dag_node_t *
+svn_fs_base__dag_dup(dag_node_t *node,
+ apr_pool_t *pool)
+{
+ /* Allocate our new node. */
+ dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
+
+ new_node->fs = node->fs;
+ new_node->id = svn_fs_base__id_copy(node->id, pool);
+ new_node->kind = node->kind;
+ new_node->created_path = apr_pstrdup(pool, node->created_path);
+ return new_node;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_open(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *node_id;
+
+ /* Ensure that NAME exists in PARENT's entry list. */
+ SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool));
+ if (! node_id)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Attempted to open non-existent child node '%s'"), name);
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ _("Attempted to open node with an illegal name '%s'"), name);
+
+ /* Now get the node that was requested. */
+ return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent),
+ node_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_copy(dag_node_t *to_node,
+ const char *entry,
+ dag_node_t *from_node,
+ svn_boolean_t preserve_history,
+ svn_revnum_t from_rev,
+ const char *from_path,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *id;
+
+ if (preserve_history)
+ {
+ node_revision_t *noderev;
+ const char *copy_id;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node);
+ const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node);
+ const char *from_txn_id = NULL;
+
+ /* Make a copy of the original node revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id,
+ trail, pool));
+
+ /* Reserve a copy ID for this new copy. */
+ SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, pool));
+
+ /* Create a successor with its predecessor pointing at the copy
+ source. */
+ noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool);
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ noderev->created_path = svn_fspath__join
+ (svn_fs_base__dag_get_created_path(to_node), entry, pool);
+ SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev,
+ copy_id, txn_id, trail, pool));
+
+ /* Translate FROM_REV into a transaction ID. */
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev,
+ trail, pool));
+
+ /* Now that we've done the copy, we need to add the information
+ about the copy to the `copies' table, using the COPY_ID we
+ reserved above. */
+ SVN_ERR(svn_fs_bdb__create_copy
+ (fs, copy_id,
+ svn_fs__canonicalize_abspath(from_path, pool),
+ from_txn_id, id, copy_kind_real, trail, pool));
+
+ /* Finally, add the COPY_ID to the transaction's list of copies
+ so that, if this transaction is aborted, the `copies' table
+ entry we added above will be cleaned up. */
+ SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool));
+ }
+ else /* don't preserve history */
+ {
+ id = svn_fs_base__dag_get_id(from_node);
+ }
+
+ /* Set the entry in to_node to the new id. */
+ return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id,
+ trail, pool);
+}
+
+
+
+/*** Deltification ***/
+
+/* Maybe change the representation identified by TARGET_REP_KEY to be
+ a delta against the representation identified by SOURCE_REP_KEY.
+ Some reasons why we wouldn't include:
+
+ - TARGET_REP_KEY and SOURCE_REP_KEY are the same key.
+
+ - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if
+ TXN_ID is non-NULL).
+
+ - The delta provides less space savings that a fulltext (this is
+ a detail handled by lower logic layers, not this function).
+
+ Do this work in TRAIL, using POOL for necessary allocations.
+*/
+static svn_error_t *
+maybe_deltify_mutable_rep(const char *target_rep_key,
+ const char *source_rep_key,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ if (! (target_rep_key && source_rep_key
+ && (strcmp(target_rep_key, source_rep_key) != 0)))
+ return SVN_NO_ERROR;
+
+ if (txn_id)
+ {
+ representation_t *target_rep;
+ SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key,
+ trail, pool));
+ if (strcmp(target_rep->txn_id, txn_id) != 0)
+ return SVN_NO_ERROR;
+ }
+
+ return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key,
+ trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_deltify(dag_node_t *target,
+ dag_node_t *source,
+ svn_boolean_t props_only,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *source_nr, *target_nr;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(target);
+
+ /* Get node revisions for the two nodes. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id,
+ trail, pool));
+
+ /* If TARGET and SOURCE both have properties, and are not sharing a
+ property key, deltify TARGET's properties. */
+ SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key,
+ txn_id, trail, pool));
+
+ /* If we are not only attending to properties, and if TARGET and
+ SOURCE both have data, and are not sharing a data key, deltify
+ TARGET's data. */
+ if (! props_only)
+ SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key,
+ txn_id, trail, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Maybe store a `checksum-reps' index record for the representation whose
+ key is REP. (If there's already a rep for this checksum, we don't
+ bother overwriting it.) */
+static svn_error_t *
+maybe_store_checksum_rep(const char *rep,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_fs_t *fs = trail->fs;
+ svn_checksum_t *sha1_checksum;
+
+ /* We want the SHA1 checksum, if any. */
+ SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum,
+ fs, rep, trail, pool));
+ if (sha1_checksum)
+ {
+ err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool);
+ if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ }
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_fs_base__dag_index_checksums(dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id,
+ trail, pool));
+ if ((node_rev->kind == svn_node_file) && node_rev->data_key)
+ SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool));
+ if (node_rev->prop_key)
+ SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Committing ***/
+
+svn_error_t *
+svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ revision_t revision;
+ svn_string_t date;
+ apr_hash_t *txnprops;
+ svn_fs_t *fs = txn->fs;
+ const char *txn_id = txn->id;
+
+ /* Remove any temporary transaction properties initially created by
+ begin_txn(). */
+ SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail));
+
+ /* Add new revision entry to `revisions' table. */
+ revision.txn_id = txn_id;
+ *new_rev = SVN_INVALID_REVNUM;
+ SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool));
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
+ SVN_ERR(svn_fs_base__set_txn_prop
+ (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool));
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
+ SVN_ERR(svn_fs_base__set_txn_prop
+ (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool));
+
+ /* Promote the unfinished transaction to a committed one. */
+ SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev,
+ trail, pool));
+
+ /* Set a date on the commit. We wait until now to fetch the date,
+ so it's definitely newer than any previous revision's date. */
+ date.data = svn_time_to_cstring(apr_time_now(), pool);
+ date.len = strlen(date.data);
+ return svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE,
+ NULL, &date, trail, pool);
+}
+
+
+/*** Comparison. ***/
+
+svn_error_t *
+svn_fs_base__things_different(svn_boolean_t *props_changed,
+ svn_boolean_t *contents_changed,
+ dag_node_t *node1,
+ dag_node_t *node2,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev1, *noderev2;
+
+ /* If we have no place to store our results, don't bother doing
+ anything. */
+ if (! props_changed && ! contents_changed)
+ return SVN_NO_ERROR;
+
+ /* The node revision skels for these two nodes. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id,
+ trail, pool));
+
+ /* Compare property keys. */
+ if (props_changed != NULL)
+ *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key,
+ noderev2->prop_key));
+
+ /* Compare contents keys and their (optional) uniquifiers. */
+ if (contents_changed != NULL)
+ *contents_changed =
+ (! (svn_fs_base__same_keys(noderev1->data_key,
+ noderev2->data_key)
+ /* Technically, these uniquifiers aren't used and "keys",
+ but keys are base-36 stringified numbers, so we'll take
+ this liberty. */
+ && (svn_fs_base__same_keys(noderev1->data_key_uniquifier,
+ noderev2->data_key_uniquifier))));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Mergeinfo tracking stuff ***/
+
+svn_error_t *
+svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo,
+ apr_int64_t *count,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
+ if (has_mergeinfo)
+ *has_mergeinfo = node_rev->has_mergeinfo;
+ if (count)
+ *count = node_rev->mergeinfo_count;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node,
+ svn_boolean_t has_mergeinfo,
+ svn_boolean_t *had_mergeinfo,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
+
+ SVN_ERR(svn_fs_base__test_required_feature_format
+ (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
+
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted merge tracking info change on "
+ "immutable node"));
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
+ *had_mergeinfo = node_rev->has_mergeinfo;
+
+ /* Are we changing the node? */
+ if ((! has_mergeinfo) != (! *had_mergeinfo))
+ {
+ /* Note the new has-mergeinfo state. */
+ node_rev->has_mergeinfo = has_mergeinfo;
+
+ /* Increment or decrement the mergeinfo count as necessary. */
+ if (has_mergeinfo)
+ node_rev->mergeinfo_count++;
+ else
+ node_rev->mergeinfo_count--;
+
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node,
+ apr_int64_t count_delta,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
+
+ SVN_ERR(svn_fs_base__test_required_feature_format
+ (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
+
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted mergeinfo count change on "
+ "immutable node"));
+
+ if (count_delta == 0)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
+ node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta;
+ if ((node_rev->mergeinfo_count < 0)
+ || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1)))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ apr_psprintf(pool,
+ _("Invalid value (%%%s) for node "
+ "revision mergeinfo count"),
+ APR_INT64_T_FMT),
+ node_rev->mergeinfo_count);
+
+ return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/dag.h b/contrib/subversion/subversion/libsvn_fs_base/dag.h
new file mode 100644
index 0000000..4c50c84
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/dag.h
@@ -0,0 +1,587 @@
+/* dag.h : DAG-like interface filesystem, private to libsvn_fs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_DAG_H
+#define SVN_LIBSVN_FS_DAG_H
+
+#include "svn_fs.h"
+
+#include "trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* The interface in this file provides all the essential filesystem
+ operations, but exposes the filesystem's DAG structure. This makes
+ it simpler to implement than the public interface, since a client
+ of this interface has to understand and cope with shared structure
+ directly as it appears in the database. However, it's still a
+ self-consistent set of invariants to maintain, making it
+ (hopefully) a useful interface boundary.
+
+ In other words:
+
+ - The dag_node_t interface exposes the internal DAG structure of
+ the filesystem, while the svn_fs.h interface does any cloning
+ necessary to make the filesystem look like a tree.
+
+ - The dag_node_t interface exposes the existence of copy nodes,
+ whereas the svn_fs.h handles them transparently.
+
+ - dag_node_t's must be explicitly cloned, whereas the svn_fs.h
+ operations make clones implicitly.
+
+ - Callers of the dag_node_t interface use Berkeley DB transactions
+ to ensure consistency between operations, while callers of the
+ svn_fs.h interface use Subversion transactions. */
+
+
+/* Initializing a filesystem. */
+
+
+/* Given a filesystem FS, which contains all the necessary tables,
+ create the initial revision 0, and the initial root directory. */
+svn_error_t *svn_fs_base__dag_init_fs(svn_fs_t *fs);
+
+
+
+/* Generic DAG node stuff. */
+
+typedef struct dag_node_t dag_node_t;
+
+
+/* Fill *NODE with a dag_node_t representing node revision ID in FS,
+ allocating in POOL. */
+svn_error_t *svn_fs_base__dag_get_node(dag_node_t **node,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Return a new dag_node_t object referring to the same node as NODE,
+ allocated in POOL. */
+dag_node_t *svn_fs_base__dag_dup(dag_node_t *node,
+ apr_pool_t *pool);
+
+
+/* Return the filesystem containing NODE. */
+svn_fs_t *svn_fs_base__dag_get_fs(dag_node_t *node);
+
+
+/* Set *REV to NODE's revision number, as part of TRAIL. If NODE has
+ never been committed as part of a revision, set *REV to
+ SVN_INVALID_REVNUM. */
+svn_error_t *svn_fs_base__dag_get_revision(svn_revnum_t *rev,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Return the node revision ID of NODE. The value returned is shared
+ with NODE, and will be deallocated when NODE is. */
+const svn_fs_id_t *svn_fs_base__dag_get_id(dag_node_t *node);
+
+
+/* Return the created path of NODE. The value returned is shared
+ with NODE, and will be deallocated when NODE is. */
+const char *svn_fs_base__dag_get_created_path(dag_node_t *node);
+
+
+/* Set *ID_P to the node revision ID of NODE's immediate predecessor,
+ or NULL if NODE has no predecessor, as part of TRAIL. The returned
+ ID will be allocated in POOL. */
+svn_error_t *svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *COUNT to the number of predecessors NODE has (recursively), or
+ -1 if not known, as part of TRAIL. */
+svn_error_t *svn_fs_base__dag_get_predecessor_count(int *count,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Return non-zero IFF NODE is currently mutable under Subversion
+ transaction TXN_ID. */
+svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node,
+ const char *txn_id);
+
+/* Return the node kind of NODE. */
+svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node);
+
+/* Set *PROPLIST_P to a PROPLIST hash representing the entire property
+ list of NODE, as part of TRAIL. The hash has const char * names
+ (the property names) and svn_string_t * values (the property values).
+
+ If properties do not exist on NODE, *PROPLIST_P will be set to NULL.
+
+ The returned property list is allocated in POOL. */
+svn_error_t *svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Set the property list of NODE to PROPLIST, as part of TRAIL. The
+ node being changed must be mutable. TXN_ID is the Subversion
+ transaction under which this occurs. */
+svn_error_t *svn_fs_base__dag_set_proplist(dag_node_t *node,
+ const apr_hash_t *proplist,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+/* Mergeinfo tracking stuff. */
+
+/* If HAS_MERGEINFO is not null, set *HAS_MERGEINFO to TRUE iff NODE
+ records that its property list contains merge tracking information.
+
+ If COUNT is not null, set *COUNT to the number of nodes --
+ including NODE itself -- in the subtree rooted at NODE which claim
+ to carry merge tracking information.
+
+ Do this as part of TRAIL, and use POOL for necessary allocations.
+
+ NOTE: No validation against NODE's actual property list is
+ performed. */
+svn_error_t *svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo,
+ apr_int64_t *count,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* If HAS_MERGEINFO is set, record on NODE that its property list
+ carries merge tracking information. Otherwise, record on NODE its
+ property list does *not* carry merge tracking information. NODE
+ must be mutable under TXN_ID (the Subversion transaction under
+ which this operation occurs). Set *HAD_MERGEINFO to the previous
+ state of this record.
+
+ Update the mergeinfo count on NODE as necessary.
+
+ Do all of this as part of TRAIL, and use POOL for necessary
+ allocations.
+
+ NOTE: No validation against NODE's actual property list is
+ performed. */
+svn_error_t *svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node,
+ svn_boolean_t has_mergeinfo,
+ svn_boolean_t *had_mergeinfo,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Record on NODE a change of COUNT_DELTA nodes -- including NODE
+ itself -- in the subtree rooted at NODE claim to carry merge
+ tracking information. That is, add COUNT_DELTA to NODE's current
+ mergeinfo count (regardless of whether COUNT_DELTA is a positive or
+ negative integer).
+
+ NODE must be mutable under TXN_ID (the Subversion transaction under
+ which this operation occurs). Do this as part of TRAIL, and use
+ POOL for necessary allocations.
+
+ NOTE: No validation of these claims is performed. */
+svn_error_t *svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node,
+ apr_int64_t count_delta,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Revision and transaction roots. */
+
+
+/* Open the root of revision REV of filesystem FS, as part of TRAIL.
+ Set *NODE_P to the new node. Allocate the node in POOL. */
+svn_error_t *svn_fs_base__dag_revision_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *NODE_P to the root of transaction TXN_ID in FS, as part
+ of TRAIL. Allocate the node in POOL.
+
+ Note that the root node of TXN_ID is not necessarily mutable. If no
+ changes have been made in the transaction, then it may share its
+ root directory with its base revision. To get a mutable root node
+ for a transaction, call svn_fs_base__dag_clone_root. */
+svn_error_t *svn_fs_base__dag_txn_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *NODE_P to the base root of transaction TXN_ID in FS, as part
+ of TRAIL. Allocate the node in POOL. */
+svn_error_t *svn_fs_base__dag_txn_base_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Clone the root directory of TXN_ID in FS, and update the
+ `transactions' table entry to point to it, unless this has been
+ done already. In either case, set *ROOT_P to a reference to the
+ root directory clone. Do all this as part of TRAIL, and allocate
+ *ROOT_P in POOL. */
+svn_error_t *svn_fs_base__dag_clone_root(dag_node_t **root_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Commit the transaction TXN->id in TXN->FS, as part of TRAIL. Store the
+ new revision number in *NEW_REV. This entails:
+ - marking the tree of mutable nodes at TXN->id's root as immutable,
+ and marking all their contents as stable
+ - creating a new revision, with TXN->id's root as its root directory
+ - promoting TXN->id to a "committed" transaction.
+
+ Beware! This does not make sure that TXN->id is based on the very
+ latest revision in TXN->FS. If the caller doesn't take care of this,
+ you may lose people's work!
+
+ Do any necessary temporary allocation in a subpool of POOL.
+ Consume temporary space at most proportional to the maximum depth
+ of SVN_TXN's tree of mutable nodes. */
+svn_error_t *svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Directories. */
+
+
+/* Open the node named NAME in the directory PARENT, as part of TRAIL.
+ Set *CHILD_P to the new node, allocated in POOL. NAME must be a
+ single path component; it cannot be a slash-separated directory
+ path. */
+svn_error_t *svn_fs_base__dag_open(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *name,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *ENTRIES_P to a hash table of NODE's entries, as part of TRAIL,
+ or NULL if NODE has no entries. The keys of the table are entry
+ names, and the values are svn_fs_dirent_t's.
+
+ The returned table is allocated in POOL.
+
+ NOTE: the 'kind' field of the svn_fs_dirent_t's is set to
+ svn_node_unknown by this function -- callers that need in
+ interesting value in these slots should fill them in using a new
+ TRAIL, since the list of entries can be arbitrarily large. */
+svn_error_t *svn_fs_base__dag_dir_entries(apr_hash_t **entries_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set ENTRY_NAME in NODE to point to ID, as part of TRAIL. NODE must
+ be a mutable directory. ID can refer to a mutable or immutable
+ node. If ENTRY_NAME does not exist, it will be created. TXN_ID is
+ the Subversion transaction under which this occurs.*/
+svn_error_t *svn_fs_base__dag_set_entry(dag_node_t *node,
+ const char *entry_name,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Make a new mutable clone of the node named NAME in PARENT, and
+ adjust PARENT's directory entry to point to it, as part of TRAIL,
+ unless NAME in PARENT already refers to a mutable node. In either
+ case, set *CHILD_P to a reference to the new node, allocated in
+ POOL. PARENT must be mutable. NAME must be a single path
+ component; it cannot be a slash-separated directory path.
+ PARENT_PATH must be the canonicalized absolute path of the parent
+ directory.
+
+ COPY_ID, if non-NULL, is a key into the `copies' table, and
+ indicates that this new node is being created as the result of a
+ copy operation, and specifically which operation that was.
+
+ PATH is the canonicalized absolute path at which this node is being
+ created.
+
+ TXN_ID is the Subversion transaction under which this occurs. */
+svn_error_t *svn_fs_base__dag_clone_child(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete the directory entry named NAME from PARENT, as part of
+ TRAIL. PARENT must be mutable. NAME must be a single path
+ component; it cannot be a slash-separated directory path. If the
+ entry being deleted points to a mutable node revision, also remove
+ that node revision and (if it is a directory) all mutable node
+ revisions reachable from it. Also delete the node-origins record
+ for each deleted node revision that had no predecessor.
+
+ TXN_ID is the Subversion transaction under which this occurs.
+
+ If return SVN_ERR_FS_NO_SUCH_ENTRY, then there is no entry NAME in
+ PARENT. */
+svn_error_t *svn_fs_base__dag_delete(dag_node_t *parent,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete the node revision assigned to node ID from FS's `nodes'
+ table, as part of TRAIL. Also delete any mutable representations
+ and strings associated with that node revision. Also delete the
+ node-origins record for this node revision's node id, if this node
+ revision had no predecessor.
+
+ ID may refer to a file or directory, which must be mutable. TXN_ID
+ is the Subversion transaction under which this occurs.
+
+ NOTE: If ID represents a directory, and that directory has mutable
+ children, you risk orphaning those children by leaving them
+ dangling, disconnected from all DAG trees. It is assumed that
+ callers of this interface know what in the world they are doing. */
+svn_error_t *svn_fs_base__dag_remove_node(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete all mutable node revisions reachable from node ID, including
+ ID itself, from FS's `nodes' table, as part of TRAIL. Also delete
+ any mutable representations and strings associated with that node
+ revision. Also delete the node-origins record for each deleted
+ node revision that had no predecessor.
+
+ ID may refer to a file or directory, which may be mutable or
+ immutable. TXN_ID is the Subversion transaction under which this
+ occurs. */
+svn_error_t *svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Create a new mutable directory named NAME in PARENT, as part of
+ TRAIL. Set *CHILD_P to a reference to the new node, allocated in
+ POOL. The new directory has no contents, and no properties.
+ PARENT must be mutable. NAME must be a single path component; it
+ cannot be a slash-separated directory path. PARENT_PATH must be
+ the canonicalized absolute path of the parent directory. PARENT
+ must not currently have an entry named NAME. Do any temporary
+ allocation in POOL. TXN_ID is the Subversion transaction
+ under which this occurs. */
+svn_error_t *svn_fs_base__dag_make_dir(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+/* Files. */
+
+
+/* Set *CONTENTS to a readable generic stream which yields the
+ contents of FILE, as part of TRAIL. Allocate the stream in POOL.
+ If FILE is not a file, return SVN_ERR_FS_NOT_FILE. */
+svn_error_t *svn_fs_base__dag_get_contents(svn_stream_t **contents,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Return a generic writable stream in *CONTENTS with which to set the
+ contents of FILE as part of TRAIL. Allocate the stream in POOL.
+ TXN_ID is the Subversion transaction under which this occurs. Any
+ previous edits on the file will be deleted, and a new edit stream
+ will be constructed. */
+svn_error_t *svn_fs_base__dag_get_edit_stream(svn_stream_t **contents,
+ dag_node_t *file,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Signify the completion of edits to FILE made using the stream
+ returned by svn_fs_base__dag_get_edit_stream, as part of TRAIL. TXN_ID
+ is the Subversion transaction under which this occurs.
+
+ If CHECKSUM is non-null, it must match the checksum for FILE's
+ contents (note: this is not recalculated, the recorded checksum is
+ used), else the error SVN_ERR_CHECKSUM_MISMATCH is returned.
+
+ This operation is a no-op if no edits are present. */
+svn_error_t *svn_fs_base__dag_finalize_edits(dag_node_t *file,
+ const svn_checksum_t *checksum,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *LENGTH to the length of the contents of FILE, as part of TRAIL. */
+svn_error_t *svn_fs_base__dag_file_length(svn_filesize_t *length,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Put the checksum of type CHECKSUM_KIND recorded for FILE into
+ * CHECKSUM, as part of TRAIL.
+ *
+ * If no stored checksum of the requested kind is available, do not
+ * calculate the checksum, just put NULL into CHECKSUM.
+ */
+svn_error_t *svn_fs_base__dag_file_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t checksum_kind,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Create a new mutable file named NAME in PARENT, as part of TRAIL.
+ Set *CHILD_P to a reference to the new node, allocated in
+ POOL. The new file's contents are the empty string, and it
+ has no properties. PARENT must be mutable. NAME must be a single
+ path component; it cannot be a slash-separated directory path.
+ PARENT_PATH must be the canonicalized absolute path of the parent
+ directory. TXN_ID is the Subversion transaction under which this
+ occurs. */
+svn_error_t *svn_fs_base__dag_make_file(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+/* Copies */
+
+/* Make ENTRY in TO_NODE be a copy of FROM_NODE, as part of TRAIL.
+ TO_NODE must be mutable. TXN_ID is the Subversion transaction
+ under which this occurs.
+
+ If PRESERVE_HISTORY is true, the new node will record that it was
+ copied from FROM_PATH in FROM_REV; therefore, FROM_NODE should be
+ the node found at FROM_PATH in FROM_REV, although this is not
+ checked.
+
+ If PRESERVE_HISTORY is false, FROM_PATH and FROM_REV are ignored. */
+svn_error_t *svn_fs_base__dag_copy(dag_node_t *to_node,
+ const char *entry,
+ dag_node_t *from_node,
+ svn_boolean_t preserve_history,
+ svn_revnum_t from_rev,
+ const char *from_path,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+/* Deltification */
+
+/* Change TARGET's representation to be a delta against SOURCE, as
+ part of TRAIL. If TARGET or SOURCE does not exist, do nothing and
+ return success. If PROPS_ONLY is non-zero, only the node property
+ portion of TARGET will be deltified.
+
+ If TXN_ID is non-NULL, it is the transaction ID in which TARGET's
+ representation(s) must have been created (otherwise deltification
+ is silently not attempted).
+
+ WARNING WARNING WARNING: Do *NOT* call this with a mutable SOURCE
+ node. Things will go *very* sour if you deltify TARGET against a
+ node that might just disappear from the filesystem in the (near)
+ future. */
+svn_error_t *svn_fs_base__dag_deltify(dag_node_t *target,
+ dag_node_t *source,
+ svn_boolean_t props_only,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Index NODE's backing data representations by their checksum. Do
+ this as part of TRAIL. Use POOL for allocations. */
+svn_error_t *svn_fs_base__dag_index_checksums(dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Comparison */
+
+/* Find out what is the same between two nodes.
+
+ If PROPS_CHANGED is non-null, set *PROPS_CHANGED to 1 if the two
+ nodes have different property lists, or to 0 if same.
+
+ If CONTENTS_CHANGED is non-null, set *CONTENTS_CHANGED to 1 if the
+ two nodes have different contents, or to 0 if same. For files,
+ file contents are compared; for directories, the entries lists are
+ compared. If one is a file and the other is a directory, the one's
+ contents will be compared to the other's entries list. (Not
+ terribly useful, I suppose, but that's the caller's business.)
+
+ ### todo: This function only compares rep keys at the moment. This
+ may leave us with a slight chance of a false positive, though I
+ don't really see how that would happen in practice. Nevertheless,
+ it should probably be fixed. */
+svn_error_t *svn_fs_base__things_different(svn_boolean_t *props_changed,
+ svn_boolean_t *contents_changed,
+ dag_node_t *node1,
+ dag_node_t *node2,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_DAG_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/err.c b/contrib/subversion/subversion/libsvn_fs_base/err.c
new file mode 100644
index 0000000..c1e691d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/err.c
@@ -0,0 +1,177 @@
+/*
+ * err.c : implementation of fs-private error functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "svn_private_config.h"
+#include "svn_fs.h"
+#include "err.h"
+#include "id.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+
+
+/* Building common error objects. */
+
+
+svn_error_t *
+svn_fs_base__err_corrupt_fs_revision(svn_fs_t *fs, svn_revnum_t rev)
+{
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt filesystem revision %ld in filesystem '%s'"),
+ rev, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
+{
+ svn_string_t *id_str = svn_fs_base__id_unparse(id, fs->pool);
+ return svn_error_createf
+ (SVN_ERR_FS_ID_NOT_FOUND, 0,
+ _("Reference to non-existent node '%s' in filesystem '%s'"),
+ id_str->data, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_dangling_rev(svn_fs_t *fs, svn_revnum_t rev)
+{
+ /* Log the UUID as this error may be reported to the client. */
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ _("No such revision %ld in filesystem '%s'"),
+ rev, fs->uuid);
+}
+
+
+svn_error_t *
+svn_fs_base__err_corrupt_txn(svn_fs_t *fs,
+ const char *txn)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt entry in 'transactions' table for '%s'"
+ " in filesystem '%s'"), txn, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_corrupt_copy(svn_fs_t *fs, const char *copy_id)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt entry in 'copies' table for '%s' in filesystem '%s'"),
+ copy_id, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_no_such_txn(svn_fs_t *fs, const char *txn)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_TRANSACTION, 0,
+ _("No transaction named '%s' in filesystem '%s'"),
+ txn, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_txn_not_mutable(svn_fs_t *fs, const char *txn)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_TRANSACTION_NOT_MUTABLE, 0,
+ _("Cannot modify transaction named '%s' in filesystem '%s'"),
+ txn, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_no_such_copy(svn_fs_t *fs, const char *copy_id)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_COPY, 0,
+ _("No copy with id '%s' in filesystem '%s'"), copy_id, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_bad_lock_token(svn_fs_t *fs, const char *lock_token)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_BAD_LOCK_TOKEN, 0,
+ _("Token '%s' does not point to any existing lock in filesystem '%s'"),
+ lock_token, fs->path);
+}
+
+svn_error_t *
+svn_fs_base__err_no_lock_token(svn_fs_t *fs, const char *path)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_NO_LOCK_TOKEN, 0,
+ _("No token given for path '%s' in filesystem '%s'"), path, fs->path);
+}
+
+svn_error_t *
+svn_fs_base__err_corrupt_lock(svn_fs_t *fs, const char *lock_token)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt lock in 'locks' table for '%s' in filesystem '%s'"),
+ lock_token, fs->path);
+}
+
+svn_error_t *
+svn_fs_base__err_no_such_node_origin(svn_fs_t *fs, const char *node_id)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_NODE_ORIGIN, 0,
+ _("No record in 'node-origins' table for node id '%s' in "
+ "filesystem '%s'"), node_id, fs->path);
+}
+
+svn_error_t *
+svn_fs_base__err_no_such_checksum_rep(svn_fs_t *fs, svn_checksum_t *checksum)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_CHECKSUM_REP, 0,
+ _("No record in 'checksum-reps' table for checksum '%s' in "
+ "filesystem '%s'"), svn_checksum_to_cstring_display(checksum,
+ fs->pool),
+ fs->path);
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/err.h b/contrib/subversion/subversion/libsvn_fs_base/err.h
new file mode 100644
index 0000000..5c03d9f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/err.h
@@ -0,0 +1,98 @@
+/*
+ * err.h : interface to routines for returning Berkeley DB errors
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#ifndef SVN_LIBSVN_FS_ERR_H
+#define SVN_LIBSVN_FS_ERR_H
+
+#include <apr_pools.h>
+
+#include "svn_error.h"
+#include "svn_fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* Building common error objects. */
+
+
+/* SVN_ERR_FS_CORRUPT: the REVISION skel of revision REV in FS is corrupt. */
+svn_error_t *svn_fs_base__err_corrupt_fs_revision(svn_fs_t *fs,
+ svn_revnum_t rev);
+
+/* SVN_ERR_FS_ID_NOT_FOUND: something in FS refers to node revision
+ ID, but that node revision doesn't exist. */
+svn_error_t *svn_fs_base__err_dangling_id(svn_fs_t *fs,
+ const svn_fs_id_t *id);
+
+/* SVN_ERR_FS_CORRUPT: something in FS refers to filesystem revision REV,
+ but that filesystem revision doesn't exist. */
+svn_error_t *svn_fs_base__err_dangling_rev(svn_fs_t *fs, svn_revnum_t rev);
+
+/* SVN_ERR_FS_CORRUPT: the entry for TXN in the `transactions' table
+ is corrupt. */
+svn_error_t *svn_fs_base__err_corrupt_txn(svn_fs_t *fs, const char *txn);
+
+/* SVN_ERR_FS_CORRUPT: the entry for COPY_ID in the `copies' table
+ is corrupt. */
+svn_error_t *svn_fs_base__err_corrupt_copy(svn_fs_t *fs, const char *copy_id);
+
+/* SVN_ERR_FS_NO_SUCH_TRANSACTION: there is no transaction named TXN in FS. */
+svn_error_t *svn_fs_base__err_no_such_txn(svn_fs_t *fs, const char *txn);
+
+/* SVN_ERR_FS_TRANSACTION_NOT_MUTABLE: trying to change the
+ unchangeable transaction named TXN in FS. */
+svn_error_t *svn_fs_base__err_txn_not_mutable(svn_fs_t *fs, const char *txn);
+
+/* SVN_ERR_FS_NO_SUCH_COPY: there is no copy with id COPY_ID in FS. */
+svn_error_t *svn_fs_base__err_no_such_copy(svn_fs_t *fs, const char *copy_id);
+
+/* SVN_ERR_FS_BAD_LOCK_TOKEN: LOCK_TOKEN does not refer to a lock in FS. */
+svn_error_t *svn_fs_base__err_bad_lock_token(svn_fs_t *fs,
+ const char *lock_token);
+
+/* SVN_ERR_FS_NO_LOCK_TOKEN: no lock token given for PATH in FS. */
+svn_error_t *svn_fs_base__err_no_lock_token(svn_fs_t *fs, const char *path);
+
+/* SVN_ERR_FS_CORRUPT: a lock in `locks' table is corrupt. */
+svn_error_t *svn_fs_base__err_corrupt_lock(svn_fs_t *fs,
+ const char *lock_token);
+
+/* SVN_ERR_FS_NO_SUCH_NODE_ORIGIN: no recorded node origin for NODE_ID
+ in FS. */
+svn_error_t *svn_fs_base__err_no_such_node_origin(svn_fs_t *fs,
+ const char *node_id);
+
+/* SVN_ERR_FS_NO_SUCH_CHECKSUM_REP: no recorded rep key for CHECKSUM in FS. */
+svn_error_t *svn_fs_base__err_no_such_checksum_rep(svn_fs_t *fs,
+ svn_checksum_t *checksum);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_ERR_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/fs.c b/contrib/subversion/subversion/libsvn_fs_base/fs.c
new file mode 100644
index 0000000..bee921b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/fs.c
@@ -0,0 +1,1436 @@
+/* fs.c --- creating, opening and closing filesystems
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <apr_general.h>
+#include <apr_pools.h>
+#include <apr_file_io.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_fs.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_delta.h"
+#include "svn_version.h"
+#include "fs.h"
+#include "err.h"
+#include "dag.h"
+#include "revs-txns.h"
+#include "uuid.h"
+#include "tree.h"
+#include "id.h"
+#include "lock.h"
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "bdb/bdb-err.h"
+#include "bdb/bdb_compat.h"
+#include "bdb/env.h"
+#include "bdb/nodes-table.h"
+#include "bdb/rev-table.h"
+#include "bdb/txn-table.h"
+#include "bdb/copies-table.h"
+#include "bdb/changes-table.h"
+#include "bdb/reps-table.h"
+#include "bdb/strings-table.h"
+#include "bdb/uuids-table.h"
+#include "bdb/locks-table.h"
+#include "bdb/lock-tokens-table.h"
+#include "bdb/node-origins-table.h"
+#include "bdb/miscellaneous-table.h"
+#include "bdb/checksum-reps-table.h"
+
+#include "../libsvn_fs/fs-loader.h"
+#include "private/svn_fs_util.h"
+
+
+/* Checking for return values, and reporting errors. */
+
+/* Check that we're using the right Berkeley DB version. */
+/* FIXME: This check should be abstracted into the DB back-end layer. */
+static svn_error_t *
+check_bdb_version(void)
+{
+ int major, minor, patch;
+
+ db_version(&major, &minor, &patch);
+
+ /* First, check that we're using a reasonably correct of Berkeley DB. */
+ if ((major < SVN_FS_WANT_DB_MAJOR)
+ || (major == SVN_FS_WANT_DB_MAJOR && minor < SVN_FS_WANT_DB_MINOR)
+ || (major == SVN_FS_WANT_DB_MAJOR && minor == SVN_FS_WANT_DB_MINOR
+ && patch < SVN_FS_WANT_DB_PATCH))
+ return svn_error_createf(SVN_ERR_FS_GENERAL, 0,
+ _("Bad database version: got %d.%d.%d,"
+ " should be at least %d.%d.%d"),
+ major, minor, patch,
+ SVN_FS_WANT_DB_MAJOR,
+ SVN_FS_WANT_DB_MINOR,
+ SVN_FS_WANT_DB_PATCH);
+
+ /* Now, check that the version we're running against is the same as
+ the one we compiled with. */
+ if (major != DB_VERSION_MAJOR || minor != DB_VERSION_MINOR)
+ return svn_error_createf(SVN_ERR_FS_GENERAL, 0,
+ _("Bad database version:"
+ " compiled with %d.%d.%d,"
+ " running against %d.%d.%d"),
+ DB_VERSION_MAJOR,
+ DB_VERSION_MINOR,
+ DB_VERSION_PATCH,
+ major, minor, patch);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Cleanup functions. */
+
+/* Close a database in the filesystem FS.
+ DB_PTR is a pointer to the DB pointer in *FS to close.
+ NAME is the name of the database, for use in error messages. */
+static svn_error_t *
+cleanup_fs_db(svn_fs_t *fs, DB **db_ptr, const char *name)
+{
+ /* If the BDB environment is panicked, don't do anything, since
+ attempting to close the database will fail anyway. */
+ base_fs_data_t *bfd = fs->fsap_data;
+ if (*db_ptr && !svn_fs_bdb__get_panic(bfd->bdb))
+ {
+ DB *db = *db_ptr;
+ char *msg = apr_psprintf(fs->pool, "closing '%s' database", name);
+ int db_err;
+
+ *db_ptr = 0;
+ db_err = db->close(db, 0);
+ if (db_err == DB_RUNRECOVERY)
+ {
+ /* We can ignore DB_RUNRECOVERY errors from DB->close, but
+ must set the panic flag in the environment baton. The
+ error will be propagated appropriately from
+ svn_fs_bdb__close. */
+ svn_fs_bdb__set_panic(bfd->bdb);
+ db_err = 0;
+ }
+
+#if SVN_BDB_HAS_DB_INCOMPLETE
+ /* We can ignore DB_INCOMPLETE on db->close and db->sync; it
+ * just means someone else was using the db at the same time
+ * we were. See the Berkeley documentation at:
+ * http://www.sleepycat.com/docs/ref/program/errorret.html#DB_INCOMPLETE
+ * http://www.sleepycat.com/docs/api_c/db_close.html
+ */
+ if (db_err == DB_INCOMPLETE)
+ db_err = 0;
+#endif /* SVN_BDB_HAS_DB_INCOMPLETE */
+
+ SVN_ERR(BDB_WRAP(fs, msg, db_err));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Close whatever Berkeley DB resources are allocated to FS. */
+static svn_error_t *
+cleanup_fs(svn_fs_t *fs)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ bdb_env_baton_t *bdb = (bfd ? bfd->bdb : NULL);
+
+ if (!bdb)
+ return SVN_NO_ERROR;
+
+ /* Close the databases. */
+ SVN_ERR(cleanup_fs_db(fs, &bfd->nodes, "nodes"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->revisions, "revisions"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->transactions, "transactions"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->copies, "copies"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->changes, "changes"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->representations, "representations"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->strings, "strings"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->uuids, "uuids"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->locks, "locks"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->lock_tokens, "lock-tokens"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->node_origins, "node-origins"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->checksum_reps, "checksum-reps"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->miscellaneous, "miscellaneous"));
+
+ /* Finally, close the environment. */
+ bfd->bdb = 0;
+ {
+ svn_error_t *err = svn_fs_bdb__close(bdb);
+ if (err)
+ return svn_error_createf
+ (err->apr_err, err,
+ _("Berkeley DB error for filesystem '%s'"
+ " while closing environment:\n"),
+ fs->path);
+ }
+ return SVN_NO_ERROR;
+}
+
+#if 0 /* Set to 1 for instrumenting. */
+static void print_fs_stats(svn_fs_t *fs)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DB_TXN_STAT *t;
+ DB_LOCK_STAT *l;
+ int db_err;
+
+ /* Print transaction statistics for this DB env. */
+ if ((db_err = bfd->bdb->env->txn_stat(bfd->bdb->env, &t, 0)) != 0)
+ fprintf(stderr, "Error running bfd->bdb->env->txn_stat(): %s",
+ db_strerror(db_err));
+ else
+ {
+ printf("*** DB transaction stats, right before closing env:\n");
+ printf(" Number of transactions currently active: %d\n",
+ t->st_nactive);
+ printf(" Max number of active transactions at any one time: %d\n",
+ t->st_maxnactive);
+ printf(" Number of transactions that have begun: %d\n",
+ t->st_nbegins);
+ printf(" Number of transactions that have aborted: %d\n",
+ t->st_naborts);
+ printf(" Number of transactions that have committed: %d\n",
+ t->st_ncommits);
+ printf(" Number of times a thread was forced to wait: %d\n",
+ t->st_region_wait);
+ printf(" Number of times a thread didn't need to wait: %d\n",
+ t->st_region_nowait);
+ printf("*** End DB transaction stats.\n\n");
+ }
+
+ /* Print transaction statistics for this DB env. */
+ if ((db_err = bfd->bdb->env->lock_stat(bfd->bdb->env, &l, 0)) != 0)
+ fprintf(stderr, "Error running bfd->bdb->env->lock_stat(): %s",
+ db_strerror(db_err));
+ else
+ {
+ printf("*** DB lock stats, right before closing env:\n");
+ printf(" The number of current locks: %d\n",
+ l->st_nlocks);
+ printf(" Max number of locks at any one time: %d\n",
+ l->st_maxnlocks);
+ printf(" Number of current lockers: %d\n",
+ l->st_nlockers);
+ printf(" Max number of lockers at any one time: %d\n",
+ l->st_maxnlockers);
+ printf(" Number of current objects: %d\n",
+ l->st_nobjects);
+ printf(" Max number of objects at any one time: %d\n",
+ l->st_maxnobjects);
+ printf(" Total number of locks requested: %d\n",
+ l->st_nrequests);
+ printf(" Total number of locks released: %d\n",
+ l->st_nreleases);
+ printf(" Total number of lock reqs failed because "
+ "DB_LOCK_NOWAIT was set: %d\n", l->st_nnowaits);
+ printf(" Total number of locks not immediately available "
+ "due to conflicts: %d\n", l->st_nconflicts);
+ printf(" Number of deadlocks detected: %d\n", l->st_ndeadlocks);
+ printf(" Number of times a thread waited before "
+ "obtaining the region lock: %d\n", l->st_region_wait);
+ printf(" Number of times a thread didn't have to wait: %d\n",
+ l->st_region_nowait);
+ printf("*** End DB lock stats.\n\n");
+ }
+
+}
+#else
+# define print_fs_stats(fs)
+#endif /* 0/1 */
+
+/* An APR pool cleanup function for a filesystem. DATA must be a
+ pointer to the filesystem to clean up.
+
+ When the filesystem object's pool is freed, we want the resources
+ held by Berkeley DB to go away, just like everything else. So we
+ register this cleanup function with the filesystem's pool, and let
+ it take care of closing the databases, the environment, and any
+ other DB objects we might be using. APR calls this function before
+ actually freeing the pool's memory.
+
+ It's a pity that we can't return an svn_error_t object from an APR
+ cleanup function. For now, we return the rather generic
+ SVN_ERR_FS_CLEANUP, and pass the real svn_error_t to the registered
+ warning callback. */
+
+static apr_status_t
+cleanup_fs_apr(void *data)
+{
+ svn_fs_t *fs = data;
+ svn_error_t *err;
+
+ print_fs_stats(fs);
+
+ err = cleanup_fs(fs);
+ if (! err)
+ return APR_SUCCESS;
+
+ /* Darn. An error during cleanup. Call the warning handler to
+ try and do something "right" with this error. Note that
+ the default will simply abort(). */
+ (*fs->warning)(fs->warning_baton, err);
+
+ svn_error_clear(err);
+
+ return SVN_ERR_FS_CLEANUP;
+}
+
+
+static svn_error_t *
+base_bdb_set_errcall(svn_fs_t *fs,
+ void (*db_errcall_fcn)(const char *errpfx, char *msg))
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ bfd->bdb->error_info->user_callback = db_errcall_fcn;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Write the DB_CONFIG file. */
+static svn_error_t *
+bdb_write_config(svn_fs_t *fs)
+{
+ const char *dbconfig_file_name =
+ svn_dirent_join(fs->path, BDB_CONFIG_FILE, fs->pool);
+ apr_file_t *dbconfig_file = NULL;
+ int i;
+
+ static const char dbconfig_contents[] =
+ "# This is the configuration file for the Berkeley DB environment\n"
+ "# used by your Subversion repository.\n"
+ "# You must run 'svnadmin recover' whenever you modify this file,\n"
+ "# for your changes to take effect.\n"
+ "\n"
+ "### Lock subsystem\n"
+ "#\n"
+ "# Make sure you read the documentation at:\n"
+ "#\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/lock_max.html\n"
+ "#\n"
+ "# before tweaking these values.\n"
+ "#\n"
+ "set_lk_max_locks 2000\n"
+ "set_lk_max_lockers 2000\n"
+ "set_lk_max_objects 2000\n"
+ "\n"
+ "### Log file subsystem\n"
+ "#\n"
+ "# Make sure you read the documentation at:\n"
+ "#\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/api_reference/C/envset_lg_bsize.html\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/api_reference/C/envset_lg_max.html\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_limits.html\n"
+ "#\n"
+ "# Increase the size of the in-memory log buffer from the default\n"
+ "# of 32 Kbytes to 256 Kbytes. Decrease the log file size from\n"
+ "# 10 Mbytes to 1 Mbyte. This will help reduce the amount of disk\n"
+ "# space required for hot backups. The size of the log file must be\n"
+ "# at least four times the size of the in-memory log buffer.\n"
+ "#\n"
+ "# Note: Decreasing the in-memory buffer size below 256 Kbytes will hurt\n"
+ "# hurt commit performance. For details, see:\n"
+ "#\n"
+ "# http://svn.haxx.se/dev/archive-2002-02/0141.shtml\n"
+ "#\n"
+ "set_lg_bsize 262144\n"
+ "set_lg_max 1048576\n"
+ "#\n"
+ "# If you see \"log region out of memory\" errors, bump lg_regionmax.\n"
+ "# For more information, see:\n"
+ "#\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n"
+ "# http://svn.haxx.se/users/archive-2004-10/1000.shtml\n"
+ "#\n"
+ "set_lg_regionmax 131072\n"
+ "#\n"
+ /* ### Configure this with "svnadmin create --bdb-cache-size" */
+ "# The default cache size in BDB is only 256k. As explained in\n"
+ "# http://svn.haxx.se/dev/archive-2004-12/0368.shtml, this is too\n"
+ "# small for most applications. Bump this number if \"db_stat -m\"\n"
+ "# shows too many cache misses.\n"
+ "#\n"
+ "set_cachesize 0 1048576 1\n";
+
+ /* Run-time configurable options.
+ Each option set consists of a minimum required BDB version, a
+ config hash key, a header, an inactive form and an active
+ form. We always write the header; then, depending on the
+ run-time configuration and the BDB version we're compiling
+ against, we write either the active or inactive form of the
+ value. */
+ static const struct
+ {
+ int bdb_major;
+ int bdb_minor;
+ const char *config_key;
+ const char *header;
+ const char *inactive;
+ const char *active;
+ } dbconfig_options[] = {
+ /* Controlled by "svnadmin create --bdb-txn-nosync" */
+ { 4, 0, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
+ /* header */
+ "#\n"
+ "# Disable fsync of log files on transaction commit. Read the\n"
+ "# documentation about DB_TXN_NOSYNC at:\n"
+ "#\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n"
+ "#\n"
+ "# [requires Berkeley DB 4.0]\n"
+ "#\n",
+ /* inactive */
+ "#set_flags DB_TXN_NOSYNC\n",
+ /* active */
+ "set_flags DB_TXN_NOSYNC\n" },
+ /* Controlled by "svnadmin create --bdb-log-keep" */
+ { 4, 2, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
+ /* header */
+ "#\n"
+ "# Enable automatic removal of unused transaction log files.\n"
+ "# Read the documentation about DB_LOG_AUTOREMOVE at:\n"
+ "#\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n"
+ "#\n"
+ "# [requires Berkeley DB 4.2]\n"
+ "#\n",
+ /* inactive */
+ "#set_flags DB_LOG_AUTOREMOVE\n",
+ /* active */
+ "set_flags DB_LOG_AUTOREMOVE\n" },
+ };
+ static const int dbconfig_options_length =
+ sizeof(dbconfig_options)/sizeof(*dbconfig_options);
+
+
+ SVN_ERR(svn_io_file_open(&dbconfig_file, dbconfig_file_name,
+ APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
+ fs->pool));
+
+ SVN_ERR(svn_io_file_write_full(dbconfig_file, dbconfig_contents,
+ sizeof(dbconfig_contents) - 1, NULL,
+ fs->pool));
+
+ /* Write the variable DB_CONFIG flags. */
+ for (i = 0; i < dbconfig_options_length; ++i)
+ {
+ void *value = NULL;
+ const char *choice;
+
+ if (fs->config)
+ {
+ value = svn_hash_gets(fs->config, dbconfig_options[i].config_key);
+ }
+
+ SVN_ERR(svn_io_file_write_full(dbconfig_file,
+ dbconfig_options[i].header,
+ strlen(dbconfig_options[i].header),
+ NULL, fs->pool));
+
+ if (((DB_VERSION_MAJOR == dbconfig_options[i].bdb_major
+ && DB_VERSION_MINOR >= dbconfig_options[i].bdb_minor)
+ || DB_VERSION_MAJOR > dbconfig_options[i].bdb_major)
+ && value != NULL && strcmp(value, "0") != 0)
+ choice = dbconfig_options[i].active;
+ else
+ choice = dbconfig_options[i].inactive;
+
+ SVN_ERR(svn_io_file_write_full(dbconfig_file, choice, strlen(choice),
+ NULL, fs->pool));
+ }
+
+ return svn_io_file_close(dbconfig_file, fs->pool);
+}
+
+static svn_error_t *
+base_bdb_verify_root(svn_fs_root_t *root,
+ apr_pool_t *scratch_pool)
+{
+ /* Verifying is currently a no op for BDB. */
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+base_bdb_freeze(svn_fs_t *fs,
+ svn_fs_freeze_func_t freeze_func,
+ void *freeze_baton,
+ apr_pool_t *pool)
+{
+ SVN__NOT_IMPLEMENTED();
+}
+
+
+/* Creating a new filesystem */
+
+static fs_vtable_t fs_vtable = {
+ svn_fs_base__youngest_rev,
+ svn_fs_base__revision_prop,
+ svn_fs_base__revision_proplist,
+ svn_fs_base__change_rev_prop,
+ svn_fs_base__set_uuid,
+ svn_fs_base__revision_root,
+ svn_fs_base__begin_txn,
+ svn_fs_base__open_txn,
+ svn_fs_base__purge_txn,
+ svn_fs_base__list_transactions,
+ svn_fs_base__deltify,
+ svn_fs_base__lock,
+ svn_fs_base__generate_lock_token,
+ svn_fs_base__unlock,
+ svn_fs_base__get_lock,
+ svn_fs_base__get_locks,
+ base_bdb_verify_root,
+ base_bdb_freeze,
+ base_bdb_set_errcall,
+};
+
+/* Where the format number is stored. */
+#define FORMAT_FILE "format"
+
+/* Depending on CREATE, create or open the environment and databases
+ for filesystem FS in PATH. Use POOL for temporary allocations. */
+static svn_error_t *
+open_databases(svn_fs_t *fs,
+ svn_boolean_t create,
+ int format,
+ const char *path,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd;
+
+ SVN_ERR(svn_fs__check_fs(fs, FALSE));
+
+ bfd = apr_pcalloc(fs->pool, sizeof(*bfd));
+ fs->vtable = &fs_vtable;
+ fs->fsap_data = bfd;
+
+ /* Initialize the fs's path. */
+ fs->path = apr_pstrdup(fs->pool, path);
+
+ if (create)
+ SVN_ERR(bdb_write_config(fs));
+
+ /* Create the Berkeley DB environment. */
+ {
+ svn_error_t *err = svn_fs_bdb__open(&(bfd->bdb), path,
+ SVN_BDB_STANDARD_ENV_FLAGS,
+ 0666, fs->pool);
+ if (err)
+ {
+ if (create)
+ return svn_error_createf
+ (err->apr_err, err,
+ _("Berkeley DB error for filesystem '%s'"
+ " while creating environment:\n"),
+ fs->path);
+ else
+ return svn_error_createf
+ (err->apr_err, err,
+ _("Berkeley DB error for filesystem '%s'"
+ " while opening environment:\n"),
+ fs->path);
+ }
+ }
+
+ /* We must register the FS cleanup function *after* opening the
+ environment, so that it's run before the environment baton
+ cleanup. */
+ apr_pool_cleanup_register(fs->pool, fs, cleanup_fs_apr,
+ apr_pool_cleanup_null);
+
+
+ /* Create the databases in the environment. */
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'nodes' table")
+ : N_("opening 'nodes' table")),
+ svn_fs_bdb__open_nodes_table(&bfd->nodes,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'revisions' table")
+ : N_("opening 'revisions' table")),
+ svn_fs_bdb__open_revisions_table(&bfd->revisions,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'transactions' table")
+ : N_("opening 'transactions' table")),
+ svn_fs_bdb__open_transactions_table(&bfd->transactions,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'copies' table")
+ : N_("opening 'copies' table")),
+ svn_fs_bdb__open_copies_table(&bfd->copies,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'changes' table")
+ : N_("opening 'changes' table")),
+ svn_fs_bdb__open_changes_table(&bfd->changes,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'representations' table")
+ : N_("opening 'representations' table")),
+ svn_fs_bdb__open_reps_table(&bfd->representations,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'strings' table")
+ : N_("opening 'strings' table")),
+ svn_fs_bdb__open_strings_table(&bfd->strings,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'uuids' table")
+ : N_("opening 'uuids' table")),
+ svn_fs_bdb__open_uuids_table(&bfd->uuids,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'locks' table")
+ : N_("opening 'locks' table")),
+ svn_fs_bdb__open_locks_table(&bfd->locks,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'lock-tokens' table")
+ : N_("opening 'lock-tokens' table")),
+ svn_fs_bdb__open_lock_tokens_table(&bfd->lock_tokens,
+ bfd->bdb->env,
+ create)));
+
+ if (format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)
+ {
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'node-origins' table")
+ : N_("opening 'node-origins' table")),
+ svn_fs_bdb__open_node_origins_table(&bfd->node_origins,
+ bfd->bdb->env,
+ create)));
+ }
+
+ if (format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT)
+ {
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'miscellaneous' table")
+ : N_("opening 'miscellaneous' table")),
+ svn_fs_bdb__open_miscellaneous_table(&bfd->miscellaneous,
+ bfd->bdb->env,
+ create)));
+ }
+
+ if (format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
+ {
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'checksum-reps' table")
+ : N_("opening 'checksum-reps' table")),
+ svn_fs_bdb__open_checksum_reps_table(&bfd->checksum_reps,
+ bfd->bdb->env,
+ create)));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Called by functions that initialize an svn_fs_t struct, after that
+ initialization is done, to populate svn_fs_t->uuid. */
+static svn_error_t *
+populate_opened_fs(svn_fs_t *fs, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_fs_base__populate_uuid(fs, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+base_create(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ int format = SVN_FS_BASE__FORMAT_NUMBER;
+ svn_error_t *svn_err;
+
+ /* See if compatibility with older versions was explicitly requested. */
+ if (fs->config)
+ {
+ if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
+ format = 1;
+ else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
+ format = 2;
+ else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
+ format = 3;
+ }
+
+ /* Create the environment and databases. */
+ svn_err = open_databases(fs, TRUE, format, path, pool);
+ if (svn_err) goto error;
+
+ /* Initialize the DAG subsystem. */
+ svn_err = svn_fs_base__dag_init_fs(fs);
+ if (svn_err) goto error;
+
+ /* This filesystem is ready. Stamp it with a format number. */
+ svn_err = svn_io_write_version_file(
+ svn_dirent_join(fs->path, FORMAT_FILE, pool), format, pool);
+ if (svn_err) goto error;
+
+ ((base_fs_data_t *) fs->fsap_data)->format = format;
+
+ SVN_ERR(populate_opened_fs(fs, pool));
+ return SVN_NO_ERROR;;
+
+error:
+ svn_error_clear(cleanup_fs(fs));
+ return svn_err;
+}
+
+
+/* Gaining access to an existing Berkeley DB-based filesystem. */
+
+svn_error_t *
+svn_fs_base__test_required_feature_format(svn_fs_t *fs,
+ const char *feature,
+ int requires)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ if (bfd->format < requires)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The '%s' feature requires version %d of the filesystem schema; "
+ "filesystem '%s' uses only version %d"),
+ feature, requires, fs->path, bfd->format);
+ return SVN_NO_ERROR;
+}
+
+/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
+ number is not the same as the format number supported by this
+ Subversion. */
+static svn_error_t *
+check_format(int format)
+{
+ /* We currently support any format less than the compiled format number
+ simultaneously. */
+ if (format <= SVN_FS_BASE__FORMAT_NUMBER)
+ return SVN_NO_ERROR;
+
+ return svn_error_createf(
+ SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
+ _("Expected FS format '%d'; found format '%d'"),
+ SVN_FS_BASE__FORMAT_NUMBER, format);
+}
+
+static svn_error_t *
+base_open(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ int format;
+ svn_error_t *svn_err;
+ svn_boolean_t write_format_file = FALSE;
+
+ /* Read the FS format number. */
+ svn_err = svn_io_read_version_file(&format,
+ svn_dirent_join(path, FORMAT_FILE, pool),
+ pool);
+ if (svn_err && APR_STATUS_IS_ENOENT(svn_err->apr_err))
+ {
+ /* Pre-1.2 filesystems did not have a format file (you could say
+ they were format "0"), so they get upgraded on the fly.
+ However, we stopped "upgrading on the fly" in 1.5, so older
+ filesystems should only be bumped to 1.3, which is format "1". */
+ svn_error_clear(svn_err);
+ svn_err = SVN_NO_ERROR;
+ format = 1;
+ write_format_file = TRUE;
+ }
+ else if (svn_err)
+ goto error;
+
+ /* Create the environment and databases. */
+ svn_err = open_databases(fs, FALSE, format, path, pool);
+ if (svn_err) goto error;
+
+ ((base_fs_data_t *) fs->fsap_data)->format = format;
+ SVN_ERR(check_format(format));
+
+ /* If we lack a format file, write one. */
+ if (write_format_file)
+ {
+ svn_err = svn_io_write_version_file(svn_dirent_join(path, FORMAT_FILE,
+ pool),
+ format, pool);
+ if (svn_err) goto error;
+ }
+
+ SVN_ERR(populate_opened_fs(fs, pool));
+ return SVN_NO_ERROR;
+
+ error:
+ svn_error_clear(cleanup_fs(fs));
+ return svn_err;
+}
+
+
+/* Running recovery on a Berkeley DB-based filesystem. */
+
+
+/* Recover a database at PATH. Perform catastrophic recovery if FATAL
+ is TRUE. Use POOL for temporary allocation. */
+static svn_error_t *
+bdb_recover(const char *path, svn_boolean_t fatal, apr_pool_t *pool)
+{
+ bdb_env_baton_t *bdb;
+
+ /* Here's the comment copied from db_recover.c:
+
+ Initialize the environment -- we don't actually do anything
+ else, that all that's needed to run recovery.
+
+ Note that we specify a private environment, as we're about to
+ create a region, and we don't want to leave it around. If we
+ leave the region around, the application that should create it
+ will simply join it instead, and will then be running with
+ incorrectly sized (and probably terribly small) caches. */
+
+ /* Note that since we're using a private environment, we shoudl
+ /not/ initialize locking. We want the environment files to go
+ away. */
+
+ SVN_ERR(svn_fs_bdb__open(&bdb, path,
+ ((fatal ? DB_RECOVER_FATAL : DB_RECOVER)
+ | SVN_BDB_PRIVATE_ENV_FLAGS),
+ 0666, pool));
+ return svn_fs_bdb__close(bdb);
+}
+
+static svn_error_t *
+base_open_for_recovery(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ /* Just stash the path in the fs pointer - it's all we really need. */
+ fs->path = apr_pstrdup(fs->pool, path);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+base_upgrade(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ const char *version_file_path;
+ int old_format_number;
+ svn_error_t *err;
+
+ version_file_path = svn_dirent_join(path, FORMAT_FILE, pool);
+
+ /* Read the old number so we've got it on hand later on. */
+ err = svn_io_read_version_file(&old_format_number, version_file_path, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* Pre-1.2 filesystems do not have a 'format' file. */
+ old_format_number = 0;
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* Bump the format file's stored version number. */
+ SVN_ERR(svn_io_write_version_file(version_file_path,
+ SVN_FS_BASE__FORMAT_NUMBER, pool));
+
+ /* Check and see if we need to record the "bump" revision. */
+ if (old_format_number < SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_revnum_t youngest_rev;
+ const char *value;
+
+ /* Open the filesystem in a subpool (so we can control its
+ closure) and do our fiddling.
+
+ NOTE: By using base_open() here instead of open_databases(),
+ we will end up re-reading the format file that we just wrote.
+ But it's better to use the existing encapsulation of "opening
+ the filesystem" rather than duplicating (or worse, partially
+ duplicating) that logic here. */
+ SVN_ERR(base_open(fs, path, subpool, common_pool));
+
+ /* Fetch the youngest rev, and record it */
+ SVN_ERR(svn_fs_base__youngest_rev(&youngest_rev, fs, subpool));
+ value = apr_psprintf(subpool, "%ld", youngest_rev);
+ SVN_ERR(svn_fs_base__miscellaneous_set
+ (fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE,
+ value, subpool));
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+base_verify(svn_fs_t *fs, const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ /* Verifying is currently a no op for BDB. */
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+base_bdb_recover(svn_fs_t *fs,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool)
+{
+ /* The fs pointer is a fake created in base_open_for_recovery above.
+ We only care about the path. */
+ return bdb_recover(fs->path, FALSE, pool);
+}
+
+static svn_error_t *
+base_bdb_pack(svn_fs_t *fs,
+ const char *path,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel,
+ void *cancel_baton,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ /* Packing is currently a no op for BDB. */
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Running the 'archive' command on a Berkeley DB-based filesystem. */
+
+
+static svn_error_t *
+base_bdb_logfiles(apr_array_header_t **logfiles,
+ const char *path,
+ svn_boolean_t only_unused,
+ apr_pool_t *pool)
+{
+ bdb_env_baton_t *bdb;
+ char **filelist;
+ char **filename;
+ u_int32_t flags = only_unused ? 0 : DB_ARCH_LOG;
+
+ *logfiles = apr_array_make(pool, 4, sizeof(const char *));
+
+ SVN_ERR(svn_fs_bdb__open(&bdb, path,
+ SVN_BDB_STANDARD_ENV_FLAGS,
+ 0666, pool));
+ SVN_BDB_ERR(bdb, bdb->env->log_archive(bdb->env, &filelist, flags));
+
+ if (filelist == NULL)
+ return svn_fs_bdb__close(bdb);
+
+ for (filename = filelist; *filename != NULL; ++filename)
+ {
+ APR_ARRAY_PUSH(*logfiles, const char *) = apr_pstrdup(pool, *filename);
+ }
+
+ free(filelist);
+
+ return svn_fs_bdb__close(bdb);
+}
+
+
+
+/* Copying a live Berkeley DB-base filesystem. */
+
+/**
+ * Delete all unused log files from DBD enviroment at @a live_path that exist
+ * in @a backup_path.
+ */
+static svn_error_t *
+svn_fs_base__clean_logs(const char *live_path,
+ const char *backup_path,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *logfiles;
+
+ SVN_ERR(base_bdb_logfiles(&logfiles,
+ live_path,
+ TRUE, /* Only unused logs */
+ pool));
+
+ { /* Process unused logs from live area */
+ int idx;
+ apr_pool_t *sub_pool = svn_pool_create(pool);
+
+ /* Process log files. */
+ for (idx = 0; idx < logfiles->nelts; idx++)
+ {
+ const char *log_file = APR_ARRAY_IDX(logfiles, idx, const char *);
+ const char *live_log_path;
+ const char *backup_log_path;
+
+ svn_pool_clear(sub_pool);
+ live_log_path = svn_dirent_join(live_path, log_file, sub_pool);
+ backup_log_path = svn_dirent_join(backup_path, log_file, sub_pool);
+
+ { /* Compare files. No point in using MD5 and wasting CPU cycles as we
+ got full copies of both logs */
+
+ svn_boolean_t files_match = FALSE;
+ svn_node_kind_t kind;
+
+ /* Check to see if there is a corresponding log file in the backup
+ directory */
+ SVN_ERR(svn_io_check_path(backup_log_path, &kind, pool));
+
+ /* If the copy of the log exists, compare them */
+ if (kind == svn_node_file)
+ SVN_ERR(svn_io_files_contents_same_p(&files_match,
+ live_log_path,
+ backup_log_path,
+ sub_pool));
+
+ /* If log files do not match, go to the next log file. */
+ if (!files_match)
+ continue;
+ }
+
+ SVN_ERR(svn_io_remove_file2(live_log_path, FALSE, sub_pool));
+ }
+
+ svn_pool_destroy(sub_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* DB_ENV->get_flags() and DB->get_pagesize() don't exist prior to
+ Berkeley DB 4.2. */
+#if SVN_BDB_VERSION_AT_LEAST(4, 2)
+
+/* Open the BDB environment at PATH and compare its configuration
+ flags with FLAGS. If every flag in FLAGS is set in the
+ environment, then set *MATCH to true. Else set *MATCH to false. */
+static svn_error_t *
+check_env_flags(svn_boolean_t *match,
+ u_int32_t flags,
+ const char *path,
+ apr_pool_t *pool)
+{
+ bdb_env_baton_t *bdb;
+#if SVN_BDB_VERSION_AT_LEAST(4, 7)
+ int flag_state;
+#else
+ u_int32_t envflags;
+#endif
+
+ SVN_ERR(svn_fs_bdb__open(&bdb, path,
+ SVN_BDB_STANDARD_ENV_FLAGS,
+ 0666, pool));
+#if SVN_BDB_VERSION_AT_LEAST(4, 7)
+ SVN_BDB_ERR(bdb, bdb->env->log_get_config(bdb->env, flags, &flag_state));
+#else
+ SVN_BDB_ERR(bdb, bdb->env->get_flags(bdb->env, &envflags));
+#endif
+
+ SVN_ERR(svn_fs_bdb__close(bdb));
+
+#if SVN_BDB_VERSION_AT_LEAST(4, 7)
+ if (flag_state == 0)
+#else
+ if (flags & envflags)
+#endif
+ *match = TRUE;
+ else
+ *match = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *PAGESIZE to the size of pages used to hold items in the
+ database environment located at PATH.
+*/
+static svn_error_t *
+get_db_pagesize(u_int32_t *pagesize,
+ const char *path,
+ apr_pool_t *pool)
+{
+ bdb_env_baton_t *bdb;
+ DB *nodes_table;
+
+ SVN_ERR(svn_fs_bdb__open(&bdb, path,
+ SVN_BDB_STANDARD_ENV_FLAGS,
+ 0666, pool));
+
+ /* ### We're only asking for the pagesize on the 'nodes' table.
+ Is this enough? We never call DB->set_pagesize() on any of
+ our tables, so presumably BDB is using the same default
+ pagesize for all our databases, right? */
+ SVN_BDB_ERR(bdb, svn_fs_bdb__open_nodes_table(&nodes_table, bdb->env,
+ FALSE));
+ SVN_BDB_ERR(bdb, nodes_table->get_pagesize(nodes_table, pagesize));
+ SVN_BDB_ERR(bdb, nodes_table->close(nodes_table, 0));
+
+ return svn_fs_bdb__close(bdb);
+}
+#endif /* SVN_BDB_VERSION_AT_LEAST(4, 2) */
+
+
+/* Copy FILENAME from SRC_DIR to DST_DIR in byte increments of size
+ CHUNKSIZE. The read/write buffer of size CHUNKSIZE will be
+ allocated in POOL. If ALLOW_MISSING is set, we won't make a fuss
+ if FILENAME isn't found in SRC_DIR; otherwise, we will. */
+static svn_error_t *
+copy_db_file_safely(const char *src_dir,
+ const char *dst_dir,
+ const char *filename,
+ u_int32_t chunksize,
+ svn_boolean_t allow_missing,
+ apr_pool_t *pool)
+{
+ apr_file_t *s = NULL, *d = NULL; /* init to null important for APR */
+ const char *file_src_path = svn_dirent_join(src_dir, filename, pool);
+ const char *file_dst_path = svn_dirent_join(dst_dir, filename, pool);
+ svn_error_t *err;
+ char *buf;
+
+ /* Open source file. If it's missing and that's allowed, there's
+ nothing more to do here. */
+ err = svn_io_file_open(&s, file_src_path,
+ (APR_READ | APR_LARGEFILE),
+ APR_OS_DEFAULT, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err) && allow_missing)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* Open destination file. */
+ SVN_ERR(svn_io_file_open(&d, file_dst_path, (APR_WRITE | APR_CREATE |
+ APR_LARGEFILE),
+ APR_OS_DEFAULT, pool));
+
+ /* Allocate our read/write buffer. */
+ buf = apr_palloc(pool, chunksize);
+
+ /* Copy bytes till the cows come home. */
+ while (1)
+ {
+ apr_size_t bytes_this_time = chunksize;
+ svn_error_t *read_err, *write_err;
+
+ /* Read 'em. */
+ if ((read_err = svn_io_file_read(s, buf, &bytes_this_time, pool)))
+ {
+ if (APR_STATUS_IS_EOF(read_err->apr_err))
+ svn_error_clear(read_err);
+ else
+ {
+ svn_error_clear(svn_io_file_close(s, pool));
+ svn_error_clear(svn_io_file_close(d, pool));
+ return read_err;
+ }
+ }
+
+ /* Write 'em. */
+ if ((write_err = svn_io_file_write_full(d, buf, bytes_this_time, NULL,
+ pool)))
+ {
+ svn_error_clear(svn_io_file_close(s, pool));
+ svn_error_clear(svn_io_file_close(d, pool));
+ return write_err;
+ }
+
+ /* read_err is either NULL, or a dangling pointer - but it is only a
+ dangling pointer if it used to be an EOF error. */
+ if (read_err)
+ {
+ SVN_ERR(svn_io_file_close(s, pool));
+ SVN_ERR(svn_io_file_close(d, pool));
+ break; /* got EOF on read, all files closed, all done. */
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+static svn_error_t *
+base_hotcopy(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ const char *src_path,
+ const char *dest_path,
+ svn_boolean_t clean_logs,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ u_int32_t pagesize;
+ svn_boolean_t log_autoremove = FALSE;
+ int format;
+
+ if (incremental)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("BDB repositories do not support incremental "
+ "hotcopy"));
+
+ /* Check the FS format number to be certain that we know how to
+ hotcopy this FS. Pre-1.2 filesystems did not have a format file (you
+ could say they were format "0"), so we will error here. This is not
+ optimal, but since this has been the case since 1.2.0, and no one has
+ complained, it apparently isn't much of a concern. (We did not check
+ the 'format' file in 1.2.x, but we did blindly try to copy 'locks',
+ which would have errored just the same.) */
+ SVN_ERR(svn_io_read_version_file(
+ &format, svn_dirent_join(src_path, FORMAT_FILE, pool), pool));
+ SVN_ERR(check_format(format));
+
+ /* If using Berkeley DB 4.2 or later, note whether the DB_LOG_AUTO_REMOVE
+ feature is on. If it is, we have a potential race condition:
+ another process might delete a logfile while we're in the middle
+ of copying all the logfiles. (This is not a huge deal; at worst,
+ the hotcopy fails with a file-not-found error.) */
+#if SVN_BDB_VERSION_AT_LEAST(4, 2)
+ err = check_env_flags(&log_autoremove,
+#if SVN_BDB_VERSION_AT_LEAST(4, 7)
+ DB_LOG_AUTO_REMOVE,
+ /* DB_LOG_AUTO_REMOVE was named DB_LOG_AUTOREMOVE before Berkeley DB 4.7. */
+#else
+ DB_LOG_AUTOREMOVE,
+#endif
+ src_path, pool);
+#endif
+ SVN_ERR(err);
+
+ /* Copy the DB_CONFIG file. */
+ SVN_ERR(svn_io_dir_file_copy(src_path, dest_path, "DB_CONFIG", pool));
+
+ /* In order to copy the database files safely and atomically, we
+ must copy them in chunks which are multiples of the page-size
+ used by BDB. See sleepycat docs for details, or svn issue #1818. */
+#if SVN_BDB_VERSION_AT_LEAST(4, 2)
+ SVN_ERR(get_db_pagesize(&pagesize, src_path, pool));
+ if (pagesize < SVN__STREAM_CHUNK_SIZE)
+ {
+ /* use the largest multiple of BDB pagesize we can. */
+ int multiple = SVN__STREAM_CHUNK_SIZE / pagesize;
+ pagesize *= multiple;
+ }
+#else
+ /* default to 128K chunks, which should be safe.
+ BDB almost certainly uses a power-of-2 pagesize. */
+ pagesize = (4096 * 32);
+#endif
+
+ /* Copy the databases. */
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "nodes", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "transactions", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "revisions", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "copies", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "changes", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "representations", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "strings", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "uuids", pagesize, TRUE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "locks", pagesize, TRUE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "lock-tokens", pagesize, TRUE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "node-origins", pagesize, TRUE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "checksum-reps", pagesize, TRUE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "miscellaneous", pagesize, TRUE, pool));
+
+ {
+ apr_array_header_t *logfiles;
+ int idx;
+ apr_pool_t *subpool;
+
+ SVN_ERR(base_bdb_logfiles(&logfiles,
+ src_path,
+ FALSE, /* All logs */
+ pool));
+
+ /* Process log files. */
+ subpool = svn_pool_create(pool);
+ for (idx = 0; idx < logfiles->nelts; idx++)
+ {
+ svn_pool_clear(subpool);
+ err = svn_io_dir_file_copy(src_path, dest_path,
+ APR_ARRAY_IDX(logfiles, idx,
+ const char *),
+ subpool);
+ if (err)
+ {
+ if (log_autoremove)
+ return
+ svn_error_quick_wrap
+ (err,
+ _("Error copying logfile; the DB_LOG_AUTOREMOVE feature\n"
+ "may be interfering with the hotcopy algorithm. If\n"
+ "the problem persists, try deactivating this feature\n"
+ "in DB_CONFIG"));
+ else
+ return svn_error_trace(err);
+ }
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ /* Since this is a copy we will have exclusive access to the repository. */
+ err = bdb_recover(dest_path, TRUE, pool);
+ if (err)
+ {
+ if (log_autoremove)
+ return
+ svn_error_quick_wrap
+ (err,
+ _("Error running catastrophic recovery on hotcopy; the\n"
+ "DB_LOG_AUTOREMOVE feature may be interfering with the\n"
+ "hotcopy algorithm. If the problem persists, try deactivating\n"
+ "this feature in DB_CONFIG"));
+ else
+ return svn_error_trace(err);
+ }
+
+ /* Only now that the hotcopied filesystem is complete,
+ stamp it with a format file. */
+ SVN_ERR(svn_io_write_version_file(
+ svn_dirent_join(dest_path, FORMAT_FILE, pool), format, pool));
+
+ if (clean_logs)
+ SVN_ERR(svn_fs_base__clean_logs(src_path, dest_path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Deleting a Berkeley DB-based filesystem. */
+
+
+static svn_error_t *
+base_delete_fs(const char *path,
+ apr_pool_t *pool)
+{
+ /* First, use the Berkeley DB library function to remove any shared
+ memory segments. */
+ SVN_ERR(svn_fs_bdb__remove(path, pool));
+
+ /* Remove the environment directory. */
+ return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
+}
+
+static const svn_version_t *
+base_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+static const char *
+base_get_description(void)
+{
+ return _("Module for working with a Berkeley DB repository.");
+}
+
+static svn_error_t *
+base_set_svn_fs_open(svn_fs_t *fs,
+ svn_error_t *(*svn_fs_open_)(svn_fs_t **,
+ const char *,
+ apr_hash_t *,
+ apr_pool_t *))
+{
+ return SVN_NO_ERROR;
+}
+
+
+/* Base FS library vtable, used by the FS loader library. */
+static fs_library_vtable_t library_vtable = {
+ base_version,
+ base_create,
+ base_open,
+ base_open_for_recovery,
+ base_upgrade,
+ base_verify,
+ base_delete_fs,
+ base_hotcopy,
+ base_get_description,
+ base_bdb_recover,
+ base_bdb_pack,
+ base_bdb_logfiles,
+ svn_fs_base__id_parse,
+ base_set_svn_fs_open
+};
+
+svn_error_t *
+svn_fs_base__init(const svn_version_t *loader_version,
+ fs_library_vtable_t **vtable, apr_pool_t* common_pool)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+
+ /* Simplified version check to make sure we can safely use the
+ VTABLE parameter. The FS loader does a more exhaustive check. */
+ if (loader_version->major != SVN_VER_MAJOR)
+ return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
+ _("Unsupported FS loader version (%d) for bdb"),
+ loader_version->major);
+ SVN_ERR(svn_ver_check_list(base_version(), checklist));
+ SVN_ERR(check_bdb_version());
+ SVN_ERR(svn_fs_bdb__init(common_pool));
+
+ *vtable = &library_vtable;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/fs.h b/contrib/subversion/subversion/libsvn_fs_base/fs.h
new file mode 100644
index 0000000..017c898
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/fs.h
@@ -0,0 +1,357 @@
+/* fs.h : interface to Subversion filesystem, private to libsvn_fs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_BASE_H
+#define SVN_LIBSVN_FS_BASE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include "svn_fs.h"
+
+#include "bdb/env.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Filesystem schema versions ***/
+
+/* The format number of this filesystem. This is independent of the
+ repository format number, and independent of any other FS back
+ ends. See the SVN_FS_BASE__MIN_*_FORMAT defines to get a sense of
+ what changes and features were added in which versions of this
+ back-end's format. */
+#define SVN_FS_BASE__FORMAT_NUMBER 4
+
+/* Minimum format number that supports representation sharing. This
+ also brings in the support for storing SHA1 checksums. */
+#define SVN_FS_BASE__MIN_REP_SHARING_FORMAT 4
+
+/* Minimum format number that supports the 'miscellaneous' table */
+#define SVN_FS_BASE__MIN_MISCELLANY_FORMAT 4
+
+/* Minimum format number that supports forward deltas */
+#define SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT 4
+
+/* Minimum format number that supports node-origins tracking */
+#define SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT 3
+
+/* Minimum format number that supports mergeinfo */
+#define SVN_FS_BASE__MIN_MERGEINFO_FORMAT 3
+
+/* Minimum format number that supports svndiff version 1. */
+#define SVN_FS_BASE__MIN_SVNDIFF1_FORMAT 2
+
+/* Return SVN_ERR_UNSUPPORTED_FEATURE if the version of filesystem FS does
+ not indicate support for FEATURE (which REQUIRES a newer version). */
+svn_error_t *
+svn_fs_base__test_required_feature_format(svn_fs_t *fs,
+ const char *feature,
+ int requires);
+
+
+
+/*** Miscellany keys. ***/
+
+/* Revision at which the repo started using forward deltas. */
+#define SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE "forward-delta-rev"
+
+
+
+/*** The filesystem structure. ***/
+
+typedef struct base_fs_data_t
+{
+ /* A Berkeley DB environment for all the filesystem's databases.
+ This establishes the scope of the filesystem's transactions. */
+ bdb_env_baton_t *bdb;
+
+ /* The filesystem's various tables. See `structure' for details. */
+ DB *changes;
+ DB *copies;
+ DB *nodes;
+ DB *representations;
+ DB *revisions;
+ DB *strings;
+ DB *transactions;
+ DB *uuids;
+ DB *locks;
+ DB *lock_tokens;
+ DB *node_origins;
+ DB *miscellaneous;
+ DB *checksum_reps;
+
+ /* A boolean for tracking when we have a live Berkeley DB
+ transaction trail alive. */
+ svn_boolean_t in_txn_trail;
+
+ /* The format number of this FS. */
+ int format;
+
+} base_fs_data_t;
+
+
+/*** Filesystem Revision ***/
+typedef struct revision_t
+{
+ /* id of the transaction that was committed to create this
+ revision. */
+ const char *txn_id;
+
+} revision_t;
+
+
+/*** Transaction Kind ***/
+typedef enum transaction_kind_t
+{
+ transaction_kind_normal = 1, /* normal, uncommitted */
+ transaction_kind_committed, /* committed */
+ transaction_kind_dead /* uncommitted and dead */
+
+} transaction_kind_t;
+
+
+/*** Filesystem Transaction ***/
+typedef struct transaction_t
+{
+ /* kind of transaction. */
+ transaction_kind_t kind;
+
+ /* revision which this transaction was committed to create, or an
+ invalid revision number if this transaction was never committed. */
+ svn_revnum_t revision;
+
+ /* property list (const char * name, svn_string_t * value).
+ may be NULL if there are no properties. */
+ apr_hash_t *proplist;
+
+ /* node revision id of the root node. */
+ const svn_fs_id_t *root_id;
+
+ /* node revision id of the node which is the root of the revision
+ upon which this txn is base. (unfinished only) */
+ const svn_fs_id_t *base_id;
+
+ /* copies list (const char * copy_ids), or NULL if there have been
+ no copies in this transaction. */
+ apr_array_header_t *copies;
+
+} transaction_t;
+
+
+/*** Node-Revision ***/
+typedef struct node_revision_t
+{
+ /* node kind */
+ svn_node_kind_t kind;
+
+ /* predecessor node revision id, or NULL if there is no predecessor
+ for this node revision */
+ const svn_fs_id_t *predecessor_id;
+
+ /* number of predecessors this node revision has (recursively), or
+ -1 if not known (for backward compatibility). */
+ int predecessor_count;
+
+ /* representation key for this node's properties. may be NULL if
+ there are no properties. */
+ const char *prop_key;
+
+ /* representation key for this node's text data (files) or entries
+ list (dirs). may be NULL if there are no contents. */
+ const char *data_key;
+
+ /* data representation instance identifier. Sounds fancy, but is
+ really just a way to distinguish between "I use the same rep key
+ as another node because we share ancestry and haven't had our
+ text touched at all" and "I use the same rep key as another node
+ only because one or both of us decided to pick up a shared
+ representation after-the-fact." May be NULL (if this node
+ revision isn't using a shared rep, or isn't the original
+ "assignee" of a shared rep). */
+ const char *data_key_uniquifier;
+
+ /* representation key for this node's text-data-in-progess (files
+ only). NULL if no edits are currently in-progress. This field
+ is always NULL for kinds other than "file". */
+ const char *edit_key;
+
+ /* path at which this node first came into existence. */
+ const char *created_path;
+
+ /* does this node revision have the mergeinfo tracking property set
+ on it? (only valid for FS schema 3 and newer) */
+ svn_boolean_t has_mergeinfo;
+
+ /* number of children of this node which have the mergeinfo tracking
+ property set (0 for files; valid only for FS schema 3 and newer). */
+ apr_int64_t mergeinfo_count;
+
+} node_revision_t;
+
+
+/*** Representation Kind ***/
+typedef enum rep_kind_t
+{
+ rep_kind_fulltext = 1, /* fulltext */
+ rep_kind_delta /* delta */
+
+} rep_kind_t;
+
+
+/*** "Delta" Offset/Window Chunk ***/
+typedef struct rep_delta_chunk_t
+{
+ /* diff format version number ### at this point, "svndiff" is the
+ only format used. */
+ apr_byte_t version;
+
+ /* starting offset of the data represented by this chunk */
+ svn_filesize_t offset;
+
+ /* string-key to which this representation points. */
+ const char *string_key;
+
+ /* size of the fulltext data represented by this delta window. */
+ apr_size_t size;
+
+ /* representation-key to use when needed source data for
+ undeltification. */
+ const char *rep_key;
+
+ /* apr_off_t rep_offset; ### not implemented */
+
+} rep_delta_chunk_t;
+
+
+/*** Representation ***/
+typedef struct representation_t
+{
+ /* representation kind */
+ rep_kind_t kind;
+
+ /* transaction ID under which representation was created (used as a
+ mutability flag when compared with a current editing
+ transaction). */
+ const char *txn_id;
+
+ /* Checksums for the contents produced by this representation.
+ These checksum is for the contents the rep shows to consumers,
+ regardless of how the rep stores the data under the hood. It is
+ independent of the storage (fulltext, delta, whatever).
+
+ If this is NULL, then for compatibility behave as though
+ this checksum matches the expected checksum. */
+ svn_checksum_t *md5_checksum;
+ svn_checksum_t *sha1_checksum;
+
+ /* kind-specific stuff */
+ union
+ {
+ /* fulltext stuff */
+ struct
+ {
+ /* string-key which holds the fulltext data */
+ const char *string_key;
+
+ } fulltext;
+
+ /* delta stuff */
+ struct
+ {
+ /* an array of rep_delta_chunk_t * chunks of delta
+ information */
+ apr_array_header_t *chunks;
+
+ } delta;
+ } contents;
+} representation_t;
+
+
+/*** Copy Kind ***/
+typedef enum copy_kind_t
+{
+ copy_kind_real = 1, /* real copy */
+ copy_kind_soft /* soft copy */
+
+} copy_kind_t;
+
+
+/*** Copy ***/
+typedef struct copy_t
+{
+ /* What kind of copy occurred. */
+ copy_kind_t kind;
+
+ /* Path of copy source. */
+ const char *src_path;
+
+ /* Transaction id of copy source. */
+ const char *src_txn_id;
+
+ /* Node-revision of copy destination. */
+ const svn_fs_id_t *dst_noderev_id;
+
+} copy_t;
+
+
+/*** Change ***/
+typedef struct change_t
+{
+ /* Path of the change. */
+ const char *path;
+
+ /* Node revision ID of the change. */
+ const svn_fs_id_t *noderev_id;
+
+ /* The kind of change. */
+ svn_fs_path_change_kind_t kind;
+
+ /* Text or property mods? */
+ svn_boolean_t text_mod;
+ svn_boolean_t prop_mod;
+
+} change_t;
+
+
+/*** Lock node ***/
+typedef struct lock_node_t
+{
+ /* entries list, maps (const char *) name --> (const char *) lock-node-id */
+ apr_hash_t *entries;
+
+ /* optional lock-token, might be NULL. */
+ const char *lock_token;
+
+} lock_node_t;
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_BASE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/id.c b/contrib/subversion/subversion/libsvn_fs_base/id.c
new file mode 100644
index 0000000..c063d02
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/id.c
@@ -0,0 +1,208 @@
+/* id.c : operations on node-revision IDs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "id.h"
+#include "../libsvn_fs/fs-loader.h"
+
+
+
+typedef struct id_private_t {
+ const char *node_id;
+ const char *copy_id;
+ const char *txn_id;
+} id_private_t;
+
+
+/* Accessing ID Pieces. */
+
+const char *
+svn_fs_base__id_node_id(const svn_fs_id_t *id)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return pvt->node_id;
+}
+
+
+const char *
+svn_fs_base__id_copy_id(const svn_fs_id_t *id)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return pvt->copy_id;
+}
+
+
+const char *
+svn_fs_base__id_txn_id(const svn_fs_id_t *id)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return pvt->txn_id;
+}
+
+
+svn_string_t *
+svn_fs_base__id_unparse(const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return svn_string_createf(pool, "%s.%s.%s",
+ pvt->node_id, pvt->copy_id, pvt->txn_id);
+}
+
+
+/*** Comparing node IDs ***/
+
+svn_boolean_t
+svn_fs_base__id_eq(const svn_fs_id_t *a,
+ const svn_fs_id_t *b)
+{
+ id_private_t *pvta = a->fsap_data, *pvtb = b->fsap_data;
+
+ if (a == b)
+ return TRUE;
+ if (strcmp(pvta->node_id, pvtb->node_id) != 0)
+ return FALSE;
+ if (strcmp(pvta->copy_id, pvtb->copy_id) != 0)
+ return FALSE;
+ if (strcmp(pvta->txn_id, pvtb->txn_id) != 0)
+ return FALSE;
+ return TRUE;
+}
+
+
+svn_boolean_t
+svn_fs_base__id_check_related(const svn_fs_id_t *a,
+ const svn_fs_id_t *b)
+{
+ id_private_t *pvta = a->fsap_data, *pvtb = b->fsap_data;
+
+ if (a == b)
+ return TRUE;
+
+ return (strcmp(pvta->node_id, pvtb->node_id) == 0);
+}
+
+
+int
+svn_fs_base__id_compare(const svn_fs_id_t *a,
+ const svn_fs_id_t *b)
+{
+ if (svn_fs_base__id_eq(a, b))
+ return 0;
+ return (svn_fs_base__id_check_related(a, b) ? 1 : -1);
+}
+
+
+
+/* Creating ID's. */
+
+static id_vtable_t id_vtable = {
+ svn_fs_base__id_unparse,
+ svn_fs_base__id_compare
+};
+
+
+svn_fs_id_t *
+svn_fs_base__id_create(const char *node_id,
+ const char *copy_id,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *id = apr_palloc(pool, sizeof(*id));
+ id_private_t *pvt = apr_palloc(pool, sizeof(*pvt));
+
+ pvt->node_id = apr_pstrdup(pool, node_id);
+ pvt->copy_id = apr_pstrdup(pool, copy_id);
+ pvt->txn_id = apr_pstrdup(pool, txn_id);
+ id->vtable = &id_vtable;
+ id->fsap_data = pvt;
+ return id;
+}
+
+
+svn_fs_id_t *
+svn_fs_base__id_copy(const svn_fs_id_t *id, apr_pool_t *pool)
+{
+ svn_fs_id_t *new_id = apr_palloc(pool, sizeof(*new_id));
+ id_private_t *new_pvt = apr_palloc(pool, sizeof(*new_pvt));
+ id_private_t *pvt = id->fsap_data;
+
+ new_pvt->node_id = apr_pstrdup(pool, pvt->node_id);
+ new_pvt->copy_id = apr_pstrdup(pool, pvt->copy_id);
+ new_pvt->txn_id = apr_pstrdup(pool, pvt->txn_id);
+ new_id->vtable = &id_vtable;
+ new_id->fsap_data = new_pvt;
+ return new_id;
+}
+
+
+svn_fs_id_t *
+svn_fs_base__id_parse(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *id;
+ id_private_t *pvt;
+ char *data_copy, *str;
+
+ /* Dup the ID data into POOL. Our returned ID will have references
+ into this memory. */
+ data_copy = apr_pstrmemdup(pool, data, len);
+
+ /* Alloc a new svn_fs_id_t structure. */
+ id = apr_palloc(pool, sizeof(*id));
+ pvt = apr_palloc(pool, sizeof(*pvt));
+ id->vtable = &id_vtable;
+ id->fsap_data = pvt;
+
+ /* Now, we basically just need to "split" this data on `.'
+ characters. We will use svn_cstring_tokenize, which will put
+ terminators where each of the '.'s used to be. Then our new
+ id field will reference string locations inside our duplicate
+ string.*/
+
+ /* Node Id */
+ str = svn_cstring_tokenize(".", &data_copy);
+ if (str == NULL)
+ return NULL;
+ pvt->node_id = str;
+
+ /* Copy Id */
+ str = svn_cstring_tokenize(".", &data_copy);
+ if (str == NULL)
+ return NULL;
+ pvt->copy_id = str;
+
+ /* Txn Id */
+ str = svn_cstring_tokenize(".", &data_copy);
+ if (str == NULL)
+ return NULL;
+ pvt->txn_id = str;
+
+ return id;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/id.h b/contrib/subversion/subversion/libsvn_fs_base/id.h
new file mode 100644
index 0000000..4cdb45c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/id.h
@@ -0,0 +1,81 @@
+/* id.h : interface to node ID functions, private to libsvn_fs_base
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_BASE_ID_H
+#define SVN_LIBSVN_FS_BASE_ID_H
+
+#include "svn_fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** ID accessor functions. ***/
+
+/* Get the "node id" portion of ID. */
+const char *svn_fs_base__id_node_id(const svn_fs_id_t *id);
+
+/* Get the "copy id" portion of ID. */
+const char *svn_fs_base__id_copy_id(const svn_fs_id_t *id);
+
+/* Get the "txn id" portion of ID. */
+const char *svn_fs_base__id_txn_id(const svn_fs_id_t *id);
+
+/* Convert ID into string form, allocated in POOL. */
+svn_string_t *svn_fs_base__id_unparse(const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Return true if A and B are equal. */
+svn_boolean_t svn_fs_base__id_eq(const svn_fs_id_t *a,
+ const svn_fs_id_t *b);
+
+/* Return true if A and B are related. */
+svn_boolean_t svn_fs_base__id_check_related(const svn_fs_id_t *a,
+ const svn_fs_id_t *b);
+
+/* Return 0 if A and B are equal, 1 if they are related, -1 otherwise. */
+int svn_fs_base__id_compare(const svn_fs_id_t *a,
+ const svn_fs_id_t *b);
+
+/* Create an ID based on NODE_ID, COPY_ID, and TXN_ID, allocated in
+ POOL. */
+svn_fs_id_t *svn_fs_base__id_create(const char *node_id,
+ const char *copy_id,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+/* Return a copy of ID, allocated from POOL. */
+svn_fs_id_t *svn_fs_base__id_copy(const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Return an ID resulting from parsing the string DATA (with length
+ LEN), or NULL if DATA is an invalid ID string. */
+svn_fs_id_t *svn_fs_base__id_parse(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_ID_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/key-gen.c b/contrib/subversion/subversion/libsvn_fs_base/key-gen.c
new file mode 100644
index 0000000..411207d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/key-gen.c
@@ -0,0 +1,131 @@
+/* key-gen.c --- manufacturing sequential keys for some db tables
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <stdlib.h>
+#include <apr.h>
+
+#include "key-gen.h"
+
+
+
+/*** Keys for reps and strings. ***/
+
+
+void
+svn_fs_base__next_key(const char *this, apr_size_t *len, char *next)
+{
+ apr_size_t olen = *len; /* remember the first length */
+ int i = olen - 1; /* initial index; we work backwards */
+ char c; /* current char */
+ svn_boolean_t carry = TRUE; /* boolean: do we have a carry or not?
+ We start with a carry, because we're
+ incrementing the number, after all. */
+
+ /* Leading zeros are not allowed, except for the string "0". */
+ if ((*len > 1) && (this[0] == '0'))
+ {
+ *len = 0;
+ return;
+ }
+
+ for (i = (olen - 1); i >= 0; i--)
+ {
+ c = this[i];
+
+ /* Validate as we go. */
+ if (! (((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'z'))))
+ {
+ *len = 0;
+ return;
+ }
+
+ if (carry)
+ {
+ if (c == 'z')
+ next[i] = '0';
+ else
+ {
+ carry = FALSE;
+
+ if (c == '9')
+ next[i] = 'a';
+ else
+ next[i] = c + 1;
+ }
+ }
+ else
+ next[i] = c;
+ }
+
+ /* The new length is OLEN, plus 1 if there's a carry out of the
+ leftmost digit. */
+ *len = olen + (carry ? 1 : 0);
+
+ /* Ensure that we haven't overrun the (ludicrous) bound on key length.
+ Note that MAX_KEY_SIZE is a bound on the size *including*
+ the trailing null byte. */
+ assert(*len < MAX_KEY_SIZE);
+
+ /* Now we know it's safe to add the null terminator. */
+ next[*len] = '\0';
+
+ /* Handle any leftover carry. */
+ if (carry)
+ {
+ memmove(next+1, next, olen);
+ next[0] = '1';
+ }
+}
+
+
+int
+svn_fs_base__key_compare(const char *a, const char *b)
+{
+ int a_len = strlen(a);
+ int b_len = strlen(b);
+ int cmp;
+
+ if (a_len > b_len)
+ return 1;
+ if (b_len > a_len)
+ return -1;
+ cmp = strcmp(a, b);
+ return (cmp ? (cmp / abs(cmp)) : 0);
+}
+
+
+svn_boolean_t
+svn_fs_base__same_keys(const char *a, const char *b)
+{
+ if (! (a || b))
+ return TRUE;
+ if (a && (! b))
+ return FALSE;
+ if ((! a) && b)
+ return FALSE;
+ return (strcmp(a, b) == 0);
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/key-gen.h b/contrib/subversion/subversion/libsvn_fs_base/key-gen.h
new file mode 100644
index 0000000..e1cd1aa
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/key-gen.h
@@ -0,0 +1,100 @@
+/* key-gen.c --- manufacturing sequential keys for some db tables
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_KEY_GEN_H
+#define SVN_LIBSVN_FS_KEY_GEN_H
+
+#include <apr.h>
+
+#include "svn_types.h"
+#include "private/svn_skel.h" /* ### for svn_fs_base__{get,put}size() */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* The alphanumeric keys passed in and out of svn_fs_base__next_key
+ are guaranteed never to be longer than this many bytes,
+ *including* the trailing null byte. It is therefore safe
+ to declare a key as "char key[MAX_KEY_SIZE]".
+
+ Note that this limit will be a problem if the number of
+ keys in a table ever exceeds
+
+ 18217977168218728251394687124089371267338971528174
+ 76066745969754933395997209053270030282678007662838
+ 67331479599455916367452421574456059646801054954062
+ 15017704234999886990788594743994796171248406730973
+ 80736524850563115569208508785942830080999927310762
+ 50733948404739350551934565743979678824151197232629
+ 947748581376,
+
+ but that's a risk we'll live with for now. */
+#define MAX_KEY_SIZE 200
+
+/* In many of the databases, the value at this key is the key to use
+ when storing a new record. */
+#define NEXT_KEY_KEY "next-key"
+
+
+/* Generate the next key after a given alphanumeric key.
+ *
+ * The first *LEN bytes of THIS are an ascii representation of a
+ * number in base 36: digits 0-9 have their usual values, and a-z have
+ * values 10-35.
+ *
+ * The new key is stored in NEXT, null-terminated. NEXT must be at
+ * least *LEN + 2 bytes long -- one extra byte to hold a possible
+ * overflow column, and one for null termination. On return, *LEN
+ * will be set to the length of the new key, not counting the null
+ * terminator. In other words, the outgoing *LEN will be either equal
+ * to the incoming, or to the incoming + 1.
+ *
+ * If THIS contains anything other than digits and lower-case
+ * alphabetic characters, or if it starts with `0' but is not the
+ * string "0", then *LEN is set to zero and the effect on NEXT
+ * is undefined.
+ */
+void svn_fs_base__next_key(const char *this, apr_size_t *len, char *next);
+
+
+/* Compare two strings A and B as base-36 alphanumeric keys.
+ *
+ * Return -1, 0, or 1 if A is less than, equal to, or greater than B,
+ * respectively.
+ */
+int svn_fs_base__key_compare(const char *a, const char *b);
+
+/* Compare two strings A and B as base-36 alphanumber keys.
+ *
+ * Return TRUE iff both keys are NULL or both keys have the same
+ * contents.
+ */
+svn_boolean_t svn_fs_base__same_keys(const char *a, const char *b);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_KEY_GEN_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/lock.c b/contrib/subversion/subversion/libsvn_fs_base/lock.c
new file mode 100644
index 0000000..79f72cc
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/lock.c
@@ -0,0 +1,594 @@
+/* lock.c : functions for manipulating filesystem locks.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_private_config.h"
+
+#include <apr_uuid.h>
+
+#include "lock.h"
+#include "tree.h"
+#include "err.h"
+#include "bdb/locks-table.h"
+#include "bdb/lock-tokens-table.h"
+#include "util/fs_skels.h"
+#include "../libsvn_fs/fs-loader.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_dep_compat.h"
+
+
+/* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as
+ part of TRAIL. */
+static svn_error_t *
+add_lock_and_token(svn_lock_t *lock,
+ const char *lock_token,
+ const char *path,
+ trail_t *trail)
+{
+ SVN_ERR(svn_fs_bdb__lock_add(trail->fs, lock_token, lock,
+ trail, trail->pool));
+ return svn_fs_bdb__lock_token_add(trail->fs, path, lock_token,
+ trail, trail->pool);
+}
+
+
+/* Delete LOCK_TOKEN and its corresponding lock (associated with PATH,
+ whose KIND is supplied), as part of TRAIL. */
+static svn_error_t *
+delete_lock_and_token(const char *lock_token,
+ const char *path,
+ trail_t *trail)
+{
+ SVN_ERR(svn_fs_bdb__lock_delete(trail->fs, lock_token,
+ trail, trail->pool));
+ return svn_fs_bdb__lock_token_delete(trail->fs, path,
+ trail, trail->pool);
+}
+
+
+struct lock_args
+{
+ svn_lock_t **lock_p;
+ const char *path;
+ const char *token;
+ const char *comment;
+ svn_boolean_t is_dav_comment;
+ svn_boolean_t steal_lock;
+ apr_time_t expiration_date;
+ svn_revnum_t current_rev;
+};
+
+
+static svn_error_t *
+txn_body_lock(void *baton, trail_t *trail)
+{
+ struct lock_args *args = baton;
+ svn_node_kind_t kind = svn_node_file;
+ svn_lock_t *existing_lock;
+ svn_lock_t *lock;
+
+ SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
+
+ /* Until we implement directory locks someday, we only allow locks
+ on files or non-existent paths. */
+ if (kind == svn_node_dir)
+ return SVN_FS__ERR_NOT_FILE(trail->fs, args->path);
+
+ /* While our locking implementation easily supports the locking of
+ nonexistent paths, we deliberately choose not to allow such madness. */
+ if (kind == svn_node_none)
+ {
+ if (SVN_IS_VALID_REVNUM(args->current_rev))
+ return svn_error_createf(
+ SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"),
+ args->path);
+ else
+ return svn_error_createf(
+ SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"),
+ args->path);
+ }
+
+ /* There better be a username attached to the fs. */
+ if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(trail->fs);
+
+ /* Is the caller attempting to lock an out-of-date working file? */
+ if (SVN_IS_VALID_REVNUM(args->current_rev))
+ {
+ svn_revnum_t created_rev;
+ SVN_ERR(svn_fs_base__get_path_created_rev(&created_rev, args->path,
+ trail, trail->pool));
+
+ /* SVN_INVALID_REVNUM means the path doesn't exist. So
+ apparently somebody is trying to lock something in their
+ working copy, but somebody else has deleted the thing
+ from HEAD. That counts as being 'out of date'. */
+ if (! SVN_IS_VALID_REVNUM(created_rev))
+ return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
+ "Path '%s' doesn't exist in HEAD revision",
+ args->path);
+
+ if (args->current_rev < created_rev)
+ return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
+ "Lock failed: newer version of '%s' exists",
+ args->path);
+ }
+
+ /* If the caller provided a TOKEN, we *really* need to see
+ if a lock already exists with that token, and if so, verify that
+ the lock's path matches PATH. Otherwise we run the risk of
+ breaking the 1-to-1 mapping of lock tokens to locked paths. */
+ if (args->token)
+ {
+ svn_lock_t *lock_from_token;
+ svn_error_t *err = svn_fs_bdb__lock_get(&lock_from_token, trail->fs,
+ args->token, trail,
+ trail->pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ svn_error_clear(err);
+ }
+ else
+ {
+ SVN_ERR(err);
+ if (strcmp(lock_from_token->path, args->path) != 0)
+ return svn_error_create(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ "Lock failed: token refers to existing "
+ "lock with non-matching path.");
+ }
+ }
+
+ /* Is the path already locked?
+
+ Note that this next function call will automatically ignore any
+ errors about {the path not existing as a key, the path's token
+ not existing as a key, the lock just having been expired}. And
+ that's totally fine. Any of these three errors are perfectly
+ acceptable to ignore; it means that the path is now free and
+ clear for locking, because the bdb funcs just cleared out both
+ of the tables for us. */
+ SVN_ERR(svn_fs_base__get_lock_helper(&existing_lock, args->path,
+ trail, trail->pool));
+ if (existing_lock)
+ {
+ if (! args->steal_lock)
+ {
+ /* Sorry, the path is already locked. */
+ return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail->fs,
+ existing_lock);
+ }
+ else
+ {
+ /* ARGS->steal_lock is set, so fs_username is "stealing" the
+ lock from lock->owner. Destroy the existing lock. */
+ SVN_ERR(delete_lock_and_token(existing_lock->token,
+ existing_lock->path, trail));
+ }
+ }
+
+ /* Create a new lock, and add it to the tables. */
+ lock = svn_lock_create(trail->pool);
+ if (args->token)
+ lock->token = apr_pstrdup(trail->pool, args->token);
+ else
+ SVN_ERR(svn_fs_base__generate_lock_token(&(lock->token), trail->fs,
+ trail->pool));
+ lock->path = apr_pstrdup(trail->pool, args->path);
+ lock->owner = apr_pstrdup(trail->pool, trail->fs->access_ctx->username);
+ lock->comment = apr_pstrdup(trail->pool, args->comment);
+ lock->is_dav_comment = args->is_dav_comment;
+ lock->creation_date = apr_time_now();
+ lock->expiration_date = args->expiration_date;
+ SVN_ERR(add_lock_and_token(lock, lock->token, args->path, trail));
+ *(args->lock_p) = lock;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_base__lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_revnum_t current_rev,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool)
+{
+ struct lock_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.lock_p = lock;
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.token = token;
+ args.comment = comment;
+ args.is_dav_comment = is_dav_comment;
+ args.steal_lock = steal_lock;
+ args.expiration_date = expiration_date;
+ args.current_rev = current_rev;
+
+ return svn_fs_base__retry_txn(fs, txn_body_lock, &args, FALSE, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__generate_lock_token(const char **token,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ /* Notice that 'fs' is currently unused. But perhaps someday, we'll
+ want to use the fs UUID + some incremented number? For now, we
+ generate a URI that matches the DAV RFC. We could change this to
+ some other URI scheme someday, if we wish. */
+ *token = apr_pstrcat(pool, "opaquelocktoken:",
+ svn_uuid_generate(pool), (char *)NULL);
+ return SVN_NO_ERROR;
+}
+
+
+struct unlock_args
+{
+ const char *path;
+ const char *token;
+ svn_boolean_t break_lock;
+};
+
+
+static svn_error_t *
+txn_body_unlock(void *baton, trail_t *trail)
+{
+ struct unlock_args *args = baton;
+ const char *lock_token;
+ svn_lock_t *lock;
+
+ /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
+ SVN_ERR(svn_fs_bdb__lock_token_get(&lock_token, trail->fs, args->path,
+ trail, trail->pool));
+
+ /* If not breaking the lock, we need to do some more checking. */
+ if (!args->break_lock)
+ {
+ /* Sanity check: The lock token must exist, and must match. */
+ if (args->token == NULL)
+ return svn_fs_base__err_no_lock_token(trail->fs, args->path);
+ else if (strcmp(lock_token, args->token) != 0)
+ return SVN_FS__ERR_NO_SUCH_LOCK(trail->fs, args->path);
+
+ SVN_ERR(svn_fs_bdb__lock_get(&lock, trail->fs, lock_token,
+ trail, trail->pool));
+
+ /* There better be a username attached to the fs. */
+ if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(trail->fs);
+
+ /* And that username better be the same as the lock's owner. */
+ if (strcmp(trail->fs->access_ctx->username, lock->owner) != 0)
+ return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
+ trail->fs,
+ trail->fs->access_ctx->username,
+ lock->owner);
+ }
+
+ /* Remove a row from each of the locking tables. */
+ return delete_lock_and_token(lock_token, args->path, trail);
+}
+
+
+svn_error_t *
+svn_fs_base__unlock(svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool)
+{
+ struct unlock_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.token = token;
+ args.break_lock = break_lock;
+ return svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__get_lock_helper(svn_lock_t **lock_p,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const char *lock_token;
+ svn_error_t *err;
+
+ err = svn_fs_bdb__lock_token_get(&lock_token, trail->fs, path,
+ trail, pool);
+
+ /* We've deliberately decided that this function doesn't tell the
+ caller *why* the lock is unavailable. */
+ if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
+ || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ svn_error_clear(err);
+ *lock_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ /* Same situation here. */
+ err = svn_fs_bdb__lock_get(lock_p, trail->fs, lock_token, trail, pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ svn_error_clear(err);
+ *lock_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ return svn_error_trace(err);
+}
+
+
+struct lock_token_get_args
+{
+ svn_lock_t **lock_p;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_get_lock(void *baton, trail_t *trail)
+{
+ struct lock_token_get_args *args = baton;
+ return svn_fs_base__get_lock_helper(args->lock_p, args->path,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__get_lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct lock_token_get_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.lock_p = lock;
+ return svn_fs_base__retry_txn(fs, txn_body_get_lock, &args, FALSE, pool);
+}
+
+/* Implements `svn_fs_get_locks_callback_t', spooling lock information
+ to a stream as the filesystem provides it. BATON is an 'svn_stream_t *'
+ object pointing to the stream. We'll write the spool stream with a
+ format like so:
+
+ SKEL1_LEN "\n" SKEL1 "\n" SKEL2_LEN "\n" SKEL2 "\n" ...
+
+ where each skel is a lock skel (the same format we use to store
+ locks in the `locks' table). */
+static svn_error_t *
+spool_locks_info(void *baton,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ svn_skel_t *lock_skel;
+ svn_stream_t *stream = baton;
+ const char *skel_len;
+ svn_stringbuf_t *skel_buf;
+ apr_size_t len;
+
+ SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool));
+ skel_buf = svn_skel__unparse(lock_skel, pool);
+ skel_len = apr_psprintf(pool, "%" APR_SIZE_T_FMT "\n", skel_buf->len);
+ len = strlen(skel_len);
+ SVN_ERR(svn_stream_write(stream, skel_len, &len));
+ len = skel_buf->len;
+ SVN_ERR(svn_stream_write(stream, skel_buf->data, &len));
+ len = 1;
+ return svn_stream_write(stream, "\n", &len);
+}
+
+
+struct locks_get_args
+{
+ const char *path;
+ svn_depth_t depth;
+ svn_stream_t *stream;
+};
+
+
+static svn_error_t *
+txn_body_get_locks(void *baton, trail_t *trail)
+{
+ struct locks_get_args *args = baton;
+ return svn_fs_bdb__locks_get(trail->fs, args->path, args->depth,
+ spool_locks_info, args->stream,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__get_locks(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ apr_pool_t *pool)
+{
+ struct locks_get_args args;
+ svn_stream_t *stream;
+ svn_stringbuf_t *buf;
+ svn_boolean_t eof;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.depth = depth;
+ /* Enough for 100+ locks if the comments are small. */
+ args.stream = svn_stream__from_spillbuf(4 * 1024 /* blocksize */,
+ 64 * 1024 /* maxsize */,
+ pool);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_locks, &args, FALSE, pool));
+
+ /* Read the stream calling GET_LOCKS_FUNC(). */
+ stream = args.stream;
+
+ while (1)
+ {
+ apr_size_t len, skel_len;
+ char c, *skel_buf;
+ svn_skel_t *lock_skel;
+ svn_lock_t *lock;
+ apr_uint64_t ui64;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ /* Read a skel length line and parse it for the skel's length. */
+ SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));
+ if (eof)
+ break;
+ err = svn_cstring_strtoui64(&ui64, buf->data, 0, APR_SIZE_MAX, 10);
+ if (err)
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, err, NULL);
+ skel_len = (apr_size_t)ui64;
+
+ /* Now read that much into a buffer. */
+ skel_buf = apr_palloc(pool, skel_len + 1);
+ SVN_ERR(svn_stream_read(stream, skel_buf, &skel_len));
+ skel_buf[skel_len] = '\0';
+
+ /* Read the extra newline that follows the skel. */
+ len = 1;
+ SVN_ERR(svn_stream_read(stream, &c, &len));
+ if (c != '\n')
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
+
+ /* Parse the skel into a lock, and notify the caller. */
+ lock_skel = svn_skel__parse(skel_buf, skel_len, iterpool);
+ SVN_ERR(svn_fs_base__parse_lock_skel(&lock, lock_skel, iterpool));
+ SVN_ERR(get_locks_func(get_locks_baton, lock, iterpool));
+ }
+
+ SVN_ERR(svn_stream_close(stream));
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Utility function: verify that a lock can be used.
+
+ If no username is attached to the FS, return SVN_ERR_FS_NO_USER.
+
+ If the FS username doesn't match LOCK's owner, return
+ SVN_ERR_FS_LOCK_OWNER_MISMATCH.
+
+ If FS hasn't been supplied with a matching lock-token for LOCK,
+ return SVN_ERR_FS_BAD_LOCK_TOKEN.
+
+ Otherwise return SVN_NO_ERROR.
+ */
+static svn_error_t *
+verify_lock(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ if ((! fs->access_ctx) || (! fs->access_ctx->username))
+ return svn_error_createf
+ (SVN_ERR_FS_NO_USER, NULL,
+ _("Cannot verify lock on path '%s'; no username available"),
+ lock->path);
+
+ else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
+ return svn_error_createf
+ (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
+ _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
+ fs->access_ctx->username, lock->path, lock->owner);
+
+ else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
+ return svn_error_createf
+ (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ _("Cannot verify lock on path '%s'; no matching lock-token available"),
+ lock->path);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the svn_fs_get_locks_callback_t interface, where
+ BATON is just an svn_fs_t object. */
+static svn_error_t *
+get_locks_callback(void *baton,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ return verify_lock(baton, lock, pool);
+}
+
+
+/* The main routine for lock enforcement, used throughout libsvn_fs_base. */
+svn_error_t *
+svn_fs_base__allow_locked_operation(const char *path,
+ svn_boolean_t recurse,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ if (recurse)
+ {
+ /* Discover all locks at or below the path. */
+ SVN_ERR(svn_fs_bdb__locks_get(trail->fs, path, svn_depth_infinity,
+ get_locks_callback,
+ trail->fs, trail, pool));
+ }
+ else
+ {
+ svn_lock_t *lock;
+
+ /* Discover any lock attached to the path. */
+ SVN_ERR(svn_fs_base__get_lock_helper(&lock, path, trail, pool));
+ if (lock)
+ SVN_ERR(verify_lock(trail->fs, lock, pool));
+ }
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/lock.h b/contrib/subversion/subversion/libsvn_fs_base/lock.h
new file mode 100644
index 0000000..603e78c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/lock.h
@@ -0,0 +1,120 @@
+/* lock.h : internal interface to lock functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_LOCK_H
+#define SVN_LIBSVN_FS_LOCK_H
+
+#include "trail.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* These functions implement part of the FS loader library's fs
+ vtables. See the public svn_fs.h for docstrings.*/
+
+svn_error_t *svn_fs_base__lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_revnum_t current_rev,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__generate_lock_token(const char **token,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__unlock(svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__get_lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool);
+
+svn_error_t *
+svn_fs_base__get_locks(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ apr_pool_t *pool);
+
+
+
+/* These next functions are 'helpers' for internal fs use:
+ if a fs function's txn_body needs to enforce existing locks, it
+ should use these routines:
+*/
+
+
+/* Implements main logic of 'svn_fs_get_lock' (or in this
+ case, svn_fs_base__get_lock() above.) See svn_fs.h. */
+svn_error_t *
+svn_fs_base__get_lock_helper(svn_lock_t **lock_p,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Examine PATH for existing locks, and check whether they can be
+ used. Do all work in the context of TRAIL, using POOL for
+ temporary allocations.
+
+ If no locks are present, return SVN_NO_ERROR.
+
+ If PATH is locked (or contains locks "below" it, when RECURSE is
+ set), then verify that:
+
+ 1. a username has been supplied to TRAIL->fs's access-context,
+ else return SVN_ERR_FS_NO_USER.
+
+ 2. for every lock discovered, the current username in the access
+ context of TRAIL->fs matches the "owner" of the lock, else
+ return SVN_ERR_FS_LOCK_OWNER_MISMATCH.
+
+ 3. for every lock discovered, a matching lock token has been
+ passed into TRAIL->fs's access-context, else return
+ SVN_ERR_FS_BAD_LOCK_TOKEN.
+
+ If all three conditions are met, return SVN_NO_ERROR.
+*/
+svn_error_t *svn_fs_base__allow_locked_operation(const char *path,
+ svn_boolean_t recurse,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_LOCK_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/node-rev.c b/contrib/subversion/subversion/libsvn_fs_base/node-rev.c
new file mode 100644
index 0000000..949a24b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/node-rev.c
@@ -0,0 +1,126 @@
+/* node-rev.c --- storing and retrieving NODE-REVISION skels
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+#include "fs.h"
+#include "err.h"
+#include "node-rev.h"
+#include "reps-strings.h"
+#include "id.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "bdb/nodes-table.h"
+#include "bdb/node-origins-table.h"
+
+
+/* Creating completely new nodes. */
+
+
+svn_error_t *
+svn_fs_base__create_node(const svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *id;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* Find an unused ID for the node. */
+ SVN_ERR(svn_fs_bdb__new_node_id(&id, fs, copy_id, txn_id, trail, pool));
+
+ /* Store its NODE-REVISION skel. */
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, noderev, trail, pool));
+
+ /* Add a record in the node origins index table if our format
+ supports it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)
+ {
+ SVN_ERR(svn_fs_bdb__set_node_origin(fs, svn_fs_base__id_node_id(id),
+ id, trail, pool));
+ }
+
+ *id_p = id;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Creating new revisions of existing nodes. */
+
+svn_error_t *
+svn_fs_base__create_successor(const svn_fs_id_t **new_id_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *old_id,
+ node_revision_t *new_noderev,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *new_id;
+
+ /* Choose an ID for the new node, and store it in the database. */
+ SVN_ERR(svn_fs_bdb__new_successor_id(&new_id, fs, old_id, copy_id,
+ txn_id, trail, pool));
+
+ /* Store the new skel under that ID. */
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, new_id, new_noderev,
+ trail, pool));
+
+ *new_id_p = new_id;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Deleting a node revision. */
+
+svn_error_t *
+svn_fs_base__delete_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ svn_boolean_t origin_also,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* ### todo: here, we should adjust other nodes to compensate for
+ the missing node. */
+
+ /* Delete the node origin record, too, if asked to do so and our
+ format supports it. */
+ if (origin_also && (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT))
+ {
+ SVN_ERR(svn_fs_bdb__delete_node_origin(fs, svn_fs_base__id_node_id(id),
+ trail, pool));
+ }
+
+ return svn_fs_bdb__delete_nodes_entry(fs, id, trail, pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/node-rev.h b/contrib/subversion/subversion/libsvn_fs_base/node-rev.h
new file mode 100644
index 0000000..206be20
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/node-rev.h
@@ -0,0 +1,101 @@
+/* node-rev.h : interface to node revision retrieval and storage
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_NODE_REV_H
+#define SVN_LIBSVN_FS_NODE_REV_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+#include "trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Functions. ***/
+
+/* Create an entirely new, mutable node in the filesystem FS, whose
+ NODE-REVISION is NODEREV, as part of TRAIL. Set *ID_P to the new
+ node revision's ID. Use POOL for any temporary allocation.
+
+ COPY_ID is the copy_id to use in the node revision ID returned in
+ *ID_P.
+
+ TXN_ID is the Subversion transaction under which this occurs.
+
+ After this call, the node table manager assumes that the new node's
+ contents will change frequently. */
+svn_error_t *svn_fs_base__create_node(const svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Create a node revision in FS which is an immediate successor of
+ OLD_ID, whose contents are NEW_NR, as part of TRAIL. Set *NEW_ID_P
+ to the new node revision's ID. Use POOL for any temporary
+ allocation.
+
+ COPY_ID, if non-NULL, is a key into the `copies' table, and
+ indicates that this new node is being created as the result of a
+ copy operation, and specifically which operation that was.
+
+ TXN_ID is the Subversion transaction under which this occurs.
+
+ After this call, the deltification code assumes that the new node's
+ contents will change frequently, and will avoid representing other
+ nodes as deltas against this node's contents. */
+svn_error_t *svn_fs_base__create_successor(const svn_fs_id_t **new_id_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *old_id,
+ node_revision_t *new_nr,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete node revision ID from FS's `nodes' table, as part of TRAIL.
+ If ORIGIN_ALSO is set, also delete the record for this ID's node ID
+ from the `node-origins' index table (which is typically only done
+ if the caller thinks that ID points to the only node revision ID in
+ its line of history).
+
+ WARNING: This does not check that the node revision is mutable!
+ Callers should do that check themselves. */
+svn_error_t *svn_fs_base__delete_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ svn_boolean_t origin_also,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_NODE_REV_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/notes/TODO b/contrib/subversion/subversion/libsvn_fs_base/notes/TODO
new file mode 100644
index 0000000..c72af03
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/notes/TODO
@@ -0,0 +1,137 @@
+What's happening now
+
+The filesystem needs some path validation stuffs independent of the
+SVN path utilities. A filesystem path is a well-defined Thing that
+should be held a safe distance away from future changes to SVN's
+general path library.
+
+
+Incorrectnesses
+
+We must ensure that node numbers are never reused. If we open a node,
+svn_fs_delete it, and then create new nodes, what happens when the
+original node structure suddenly comes to refer to an entirely
+different node? Files become directories?
+
+We should convert filenames to some canonical Unicode form, for
+comparison.
+
+Does everyone call svn_fs__check_fs who should?
+
+svn_fs_delete will actually delete non-empty directories, if they're
+not cloned. This is inconsistent; should it be fixed?
+
+Does every operation on a deleted node or completed transaction fail
+gracefully?
+
+Produce helpful error messages when filename paths contain null
+characters.
+
+
+Uglinesses
+
+Fix up comments in svn_fs.h for transactions.
+
+Add `public name' member to filesystem structure, to use to identify
+the filesystem in error messages. When driven by DAV, this could be a
+URL.
+
+When a dag function signals an error, it has no idea what the path of
+the relevant node was. But node revision ID's are pretty useless to
+the user. tree.c should probably rewrap some errors.
+
+svn_fs__getsize shouldn't rely on a maximum value for detecting
+overflow.
+
+The use of svn_fs__getsize in svn_fs__parse_id is ugly --- what if
+svn_vernum_t and apr_size_t aren't the same size?
+
+Consider some macros or accessory functions for referencing the pieces
+of the NODE-REVISION skel (instead of seeing stuff like
+node->children->next->next and such other unreadable rubbish)
+
+
+Slownesses
+
+We don't store older node revisions as deltas yet.
+
+The delta algorithm walks the whole tree using a single pool, so the
+memory used is proportional to the size of the target tree. Instead,
+it should use a separate subpool every time it recurses into a new
+directory, and free that subpool as soon as it's done processing that
+subdirectory, so the memory used is proportional to the depth of the
+tree.
+
+We should move as much real content out of the NODE-REVISION skel as
+possible; the skels should be holding only small stuff (node kind,
+flags).
+- File contents and deltas should be moved out to a `contents' table.
+ The NODE-REVISION skel should simply contain a key into that table.
+- Directory contents should be moved out to a `directories' table,
+ with a separate table entry for each directory entry. Keys into the
+ table should be of the form `NODE-ID ENTRY-NAME NODE-REVISION', and
+ values should be node revision ID's, or the word `deleted'; to look
+ up an entry named E in a directory whose node revision is N.R,
+ search for the entry `N E x', where x is the largest number present
+ <= R.
+- Property lists should be moved out to a table `properties', indexed
+ similarly to the above. We could deltify property contents the
+ same way we do file contents.
+
+
+Amenities
+
+Extend svn_fs_copy to handle mutable nodes.
+
+Long term ideas:
+
+- directory entry cache:
+ Create a cache mapping a node revision id X plus a filename component
+ N onto a new node revision id Y, meaning that X is a directory in
+ which the name N is bound to ID Y. If everything were in the cache,
+ this function could run with no I/O except for the final node.
+
+ Since node revisions never change, we wouldn't have to worry about
+ invalidating the cache. Mutable node objects will need special
+ handling, of course.
+
+- fulltext cache:
+ If we've recently computed a node's fulltext, we might want to keep
+ that around in case we need to compute one of its nearby ancestors'
+ fulltext, too. This could be a waste, though --- the access
+ patterns are a mix of linear scan (backwards to reconstruct a given
+ revision) and random (who knows what node we'll hit next), so it's
+ not clear what cache policy would be effective. Best to record some
+ data on how many delta applications a given cache would avoid before
+ implementing it.
+
+- delta cache:
+ As people update, we're going to be recomputing text deltas for the
+ most recently changed files pretty often. It might be worthwhile to
+ cache the deltas for a little while.
+
+- Handle Unicode canonicalization for directory and property names
+ ourselves. People should be able to hand us any valid UTF-8
+ sequence, perhaps with precomposed characters or non-spacing marks
+ in a non-canonical order, and find the appropriate matches, given
+ the rules defined by the Unicode standard.
+
+Keeping repositories alive in the long term: Berkeley DB is infamous
+for changing its file format from one revision to the next. If someone
+saves a Subversion 1.0 repository on a CD somewhere, and then tries to
+read it seven years later, their chance of being able to read it with
+the latest revision of Subversion is nil. The solution:
+
+- Define a simply XML repository dump format for the complete
+ repository data. This should be the same format we use for CVS
+ repository conversion. We'll have an import function.
+
+- Write a program that is simple and self-contained --- does not use
+ Berkeley DB, no fancy XML tools, uses nothing but POSIX read and
+ seek --- that can dump a Subversion repository in that format.
+
+- For each revision of Subversion, make a sample repository, and
+ archive a copy of it away as test data.
+
+- Write a test suite that verifies that the repository dump program
+ can handle all of the archived formats.
diff --git a/contrib/subversion/subversion/libsvn_fs_base/notes/fs-history b/contrib/subversion/subversion/libsvn_fs_base/notes/fs-history
new file mode 100644
index 0000000..e4f51a6
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/notes/fs-history
@@ -0,0 +1,270 @@
+ -*- text -*-
+
+ Subversion Filesystem History
+ (a love song for libsvn_fs, by C. Michael Pilato)
+
+
+The Subversion filesystem can be your best friend, or your worst
+enemy, usually depending on which side of the public API you are
+working on. Callers of the libsvn_fs interfaces do their work in a
+world pleasantly addressed by roots (the name given to a revision or
+transaction snapshot of the versioned directory tree) and paths under
+those roots. But once you swim beneath the surface, you quickly
+realize that there is a beast both beautiful and dangerous lying in
+wait. What looks to the outside world as a sort of coordinate system
+with axes for "Time" and "Location" is, in fact, a complicated DAG
+subsystem, with nodes that represent revisions of versioned locations
+all interconnected in various relationships with each other.
+
+The goal of this document is straightforward: to relay knowledge about
+how to untangle that DAG subsystem -- knowledge which likely lives
+only in the minds of a few developers -- so that the Few might become
+the Many.
+
+
+
+Node-Revisions: The Nodes of the DAG
+
+When working outside the filesystem API, people generally talk about
+their versioned resources in terms of the paths of those resources,
+and the global revisions (or revisions-to-be) in which those paths
+are located. But inside the filesystem, paths are broken down and
+stored as a hierarchical linked-list of path components. Each of
+these path components has its own historical lineage (because
+Subversion versions directories, too!), and each revision of that
+lineage is referred to as a "node-revision". It is these
+node-revisions which are the nodes of the DAG subsystem, or "DAG
+nodes".
+
+DAG nodes are identified by unique keys called "node-revision IDs",
+and are inter-connected in a variety of ways. A DAG node that
+represents a directory stores information about which other DAG nodes
+represent the children of that directory. A DAG node contains
+information about which other DAG node is its historical predecessor.
+By tracing these links from node to node, we can effectively traverse
+both space and time, both the geography and the chronology of the
+filesystem landscape.
+
+For example, the path "/trunk/src/main.c" in revision 4 of the
+filesystem consumes four DAG nodes: one for "/", one for "/trunk", one
+for "/trunk/src", and one for "/trunk/src/main.c". The DAG node for
+"/" contains a list of the names and node-revision IDs of its
+children, among which is the node-revision ID for the child named
+"trunk". Similar links are found in "/trunk" (for "src") and
+"/trunk/src" (for "main.c"). Additionally, if these paths existed in
+different forms in previous revisions of the filesystem, their DAG
+nodes would store the node-revision IDs of their respective
+predecessor nodes.
+
+Whenever someone uses the public API to query for information about a
+versioned path under a particular root, the typical course of action
+under-the-hood is as follows:
+
+ 1. The root refers to a particular snapshot of the DAG node tree,
+ and from this we can learn the node-revision ID of the node
+ which represents the root directory ("/") as it appears in that
+ snapshot. Given this node-revision ID, it's all DAG from here.
+
+ 2. The path is split into components and traversed, beginning with
+ the root node, and walking down toward the full path. Each
+ intermediate node-revision is read, its entries list parsed, and
+ the next component looked up in that entries list to find the
+ next node-revision ID along the traversal path.
+
+ 3. Finally, we wind up with a node-revision ID for our original
+ path. We use it and its associated node-revision to answer the
+ query.
+
+Seems pretty easy, doesn't it? Keep reading.
+
+
+
+All About Node-Revision IDs
+
+As previously mentioned, each node-revision in the filesystem has a
+unique key, referred to as the node-revision ID. This key is
+typically represented as a string which looks like a period-separated
+list of its three components:
+
+ 1. node ID: This key is unique to the members of a single
+ historical lineage. Differing versions of the same versioned
+ resource, irrespective of the paths and revision in which those
+ versions are located, all share this ID. If two node-revisions
+ have different node IDs, their are historically unrelated.
+
+ 2. copy ID: This key uniquely identifies a copy operation, and is
+ sometimes referred to (or at least thought of) as a "branch ID."
+ If two node-revisions have the same copy ID, they are said to be
+ on the same branch. The only exception to this is in the key
+ "0", a special key that means "not branched".
+
+ 3. txn ID: This key uniquely identifies the Subversion transaction
+ in which this node-revision came into existence.
+
+Whenever someone uses the public API to *modify* a versioned resource,
+these actions are much the same as those used when querying. But
+there are important differences.
+
+ 1. The path is traversed in the same manner is described in the
+ previous section. The result is an in-memory linked-list of
+ information about the node-revisions which comprise the
+ components of the path.
+
+ 2. But before any changes can be made to a path, its node-revision
+ and those of its parent directories must first be cloned so that
+ changes to them don't affect previous incarnations of those
+ node-revisions. This process is called "making the path
+ mutable". If previous operations under this transaction caused
+ one or more of the parent directories to be made mutable
+ already, they are not again cloned.
+
+ 3. Once the path and all its parents are mutable, the desired
+ changes can be made to the cloned node-revision, and they in no
+ way affect prior history.
+
+To clone a node-revision means to literally make a duplicate of it
+which is granted its own unique node-revision ID. The new
+node-revision ID consists of the same node ID as the node-revision
+that was cloned (since this is just another point along the historical
+lineage of this versioned resource), a copy ID (which will be
+discussed later), and the txn ID in which this modification is
+occuring.
+
+There are some cool things we can read between the lines above. Since
+the only time a node-revision comes into existence is when it is brand
+new or a fresh clone, and we never do cloning except during a
+modification, then we can use the txn ID as a sort of mutability flag.
+Mutability of a node-revision is determined by comparing the txn ID of
+the node-revision with the ID of the Subversion transaction being used
+to modify the filesystem -- if, and only if, they are the same, the node
+is allowed to be changed inside that transaction.
+
+So, we know how txn IDs come into existence now. And the origin of
+node IDs hardly warrants its own paragraph: brand new lines of history
+(introduced with svn_fs_make_file() and svn_fs_make_dir()) get new
+unique node IDs, and every other node-revision that is created simply
+keeps the same node ID as the node-revision on which it is based.
+
+So what about those copy IDs?
+
+Copy IDs are assigned to nodes-revisions to denote on which "branch"
+of a line of history that node-revision resides. (They are called
+copy IDs for political reasons, really -- Subversion doesn't expose a
+branching API per se, instead promoting the idea that branches are
+just forks in the development of a line of history that can just as
+easily be represented using path semantics.) New copy IDs are
+allocated whenever a branching operation occurs. New node-revisions
+can inherit the copy IDs of their predecessors (in the trivial cloning
+case), inherit the copy-IDs of one of their parents (by nature of
+their parent being copied), or inherit new copy-IDs. In the absence
+of any branching, node-revisions are assigned the special copy ID "0".
+
+
+
+Copies and Copy IDs
+
+Currently there are two kinds of copy operation. The first is a
+"real" copy, and is the direct result of a call to svn_fs_copy().
+When a real copy is made, the node-revision of the copy source is
+cloned, and earns its own brand new unique node-revision ID. This
+node-revision ID is constructed from the original node ID, a brand new
+copy ID, and (as always) the txn ID of the transaction in which the
+copy is being made.
+
+The Subversion filesystem uses a "cheap copy/lazy migration" model.
+This means that when a directory node-revision is copied via
+svn_fs_copy(), only the node-revision of the top of the copied "tree"
+is cloned (again, earning a new copy ID), not every child of that
+tree. This makes the svn_fs_copy() operation quite fast, at least
+compared to the alternative. From that point, any children of the
+copy target are lazily migrated. The first time they are themselves
+modified after the original copy, they are cloned from their original
+source location into the new target location. This lazy migration
+procedure costs about the same as a regular cloning operation, which
+keeps the "cheap copy" cheap, even the morning after.
+
+Copies of files behave no differently than copies of directories. But
+files never have children, so effectively the "tree" being copied is
+exactly one node-revision. This node-revision is explicitly cloned at
+the time of the copy, and there is nothing to lazily migrate
+afterwards.
+
+The second type of copy operation is a "soft" copy. These types of
+copies are not explicitly triggered via the filesystem API, but are
+implementation artifacts of other filesystem operations. A soft copy
+happens whenever a node-revision exists in a different branch than
+that of its parent, and the parent is again branched. Huh?! Let's
+see if an example will help explain this a bit.
+
+Say you have a directory, "/trunk". Now, into "/trunk" you copy a
+file "README" from some other part of the tree. You have now
+effectively branched the original "README"'s history -- part of it
+will live on in the original location, but part of it now thrives in
+its new "/trunk/README" location. The copy operation assigned a brand
+new copy ID to the new node-revision for "/trunk/README", which is
+necessarily different from the copy ID assigned to the node-revision
+for "/trunk".
+
+Later, you decide to copy "/trunk" to "/branches/mine". So the new
+"/branches/mine" also gets a brand new copy ID, since it is now a
+historical branch of "/trunk". But what happens when
+"/branches/mine/README" is cloned later as part of some edits you are
+making? What copy ID does the new clone get? Because "/trunk/README"
+was on a different historical branch than "/trunk", our copy of
+"/trunk" causes (in "README") a branch of a branch. So
+"/branches/mine/README" gets a brand new copy ID, and the filesystem
+remembers that the copy operation associated with that ID was a soft
+copy.
+
+ [### Right about here, C-Mike's memory starts getting fuzzy ###]
+
+The following is the copy ID inheritance algorithm, used to calculate
+what copy ID a node revision will use when cloned for mutability.
+Remember that a node revision is never cloned until its parent is
+first cloned.
+
+ 1. If the node revision is already mutable, its copy ID never
+ changes.
+
+ 2. If the node revision has a copy ID of "0" (which is to say, it's
+ never been itself copied or cloned as a child of a copied
+ parent), then it inherits whatever copy ID its parent winds up
+ with.
+
+ 3. If the node revision is on the same branch as its parent before
+ cloning, it will remain on the same branch as its parent after
+ cloning. A node revision can be said to be on the same branch
+ as its parent if:
+
+ a) their copy IDs are the same, or
+
+ b) the node revision is not a branch point (meaning, it was
+ not the node revision created by the copy associated with
+ its copy ID), or
+
+ c) the node revision is a branch point which being accessed via
+ its copy destination path.
+
+ 4. If, however, the node revision is *not* on the same branch as
+ its parent before cloning, it cannot be on the same branch as
+ its parent after cloning. This breaks down to two cases:
+
+ a) If the node revision was the target of the copy operation
+ whose ID it holds, then it gets to keep its same copy ID.
+
+ b) Otherwise, the node revision is the unedited child of some
+ parent that was copied, and wasn't on the same branch as
+ that parent before the copy. In this special case, the
+ cloned node revision will get a brand new copy ID which
+ points to one of those "soft copy" things we've been
+ talking about.
+
+The initial root directory's node revision, created when the
+filesystem is initialized, begins life with a magical "0" copy ID.
+Afterward, any new nodes (as in, freshly created files and
+directories) begin life with the same copy ID as their parent.
+
+
+Traversing History
+
+ ### todo: put the history harvesting algorithm here
diff --git a/contrib/subversion/subversion/libsvn_fs_base/notes/structure b/contrib/subversion/subversion/libsvn_fs_base/notes/structure
new file mode 100644
index 0000000..8e2159f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/notes/structure
@@ -0,0 +1,1086 @@
+Subversion on Berkeley DB -*- text -*-
+
+There are many different ways to implement the Subversion filesystem
+interface. You could implement it directly using ordinary POSIX
+filesystem operations; you could build it using an SQL server as a
+back end; you could build it on RCS; and so on.
+
+This implementation of the Subversion filesystem interface is built on
+top of Berkeley DB (http://www.sleepycat.com). Berkeley DB supports
+transactions and recoverability, making it well-suited for Subversion.
+
+
+
+Nodes and Node Revisions
+
+In a Subversion filesystem, a `node' corresponds roughly to an
+`inode' in a Unix filesystem:
+
+ * A node is either a file or a directory.
+
+ * A node's contents change over time.
+
+ * When you change a node's contents, it's still the same node; it's
+ just been changed. So a node's identity isn't bound to a specific
+ set of contents.
+
+ * If you rename a node, it's still the same node, just under a
+ different name. So a node's identity isn't bound to a particular
+ filename.
+
+A `node revision' refers to a node's contents at a specific point in
+time. Changing a node's contents always creates a new revision of that
+node. Once created, a node revision's contents never change.
+
+When we create a node, its initial contents are the initial revision of
+the node. As users make changes to the node over time, we create new
+revisions of that same node. When a user commits a change that deletes
+a file from the filesystem, we don't delete the node, or any revision
+of it --- those stick around to allow us to recreate prior revisions of
+the filesystem. Instead, we just remove the reference to the node
+from the directory.
+
+
+
+ID's
+
+Within the database, we refer to nodes and node revisions using a
+string of three unique identifiers (the "node ID", the "copy ID", and
+the "txn ID"), separated by periods.
+
+ node_revision_id ::= node_id '.' copy_id '.' txn_id
+
+The node ID is unique to a particular node in the filesystem across
+all of revision history. That is, two node revisions who share
+revision history (perhaps because they are different revisions of the
+same node, or because one is a copy of the other, e.g.) have the same
+node ID, whereas two node revisions who have no common revision
+history will not have the same node ID.
+
+The copy ID is a key into the `copies' table (see `Copies' below), and
+identifies that a given node revision, or one of its ancestors,
+resulted from a unique filesystem copy operation.
+
+The txn ID is just an identifier that is unique to a single filesystem
+commit. All node revisions created as part of a commit share this txn
+ID (which, incidentally, gets its name from the fact that this id is
+the same id used as the primary key of Subversion transactions; see
+`Transactions' below).
+
+A directory entry identifies the file or subdirectory it refers to
+using a node revision ID --- not a node ID. This means that a change
+to a file far down in a directory hierarchy requires the parent
+directory of the changed node to be updated, to hold the new node
+revision ID. Now, since that parent directory has changed, its parent
+needs to be updated, and so on to the root. We call this process
+"bubble-up".
+
+If a particular subtree was unaffected by a given commit, the node
+revision ID that appears in its parent will be unchanged. When
+doing an update, we can notice this, and ignore that entire
+subtree. This makes it efficient to find localized changes in
+large trees.
+
+
+
+A Word About Keys
+
+Some of the Subversion database tables use base-36 numbers as their
+keys. Some debate exists about whether the use of base-36 (as opposed
+to, say, regular decimal values) is either necessary or good. It is
+outside the scope of this document to make a claim for or against this
+usage. As such, the reader will please note that for the majority of
+the document, the use of the term "number" when referring to keys of
+database tables should be interpreted to mean "a monotonically
+increasing unique key whose order with respect to other keys in the
+table is irrelevant". :-)
+
+To determine the actual type currently in use for the keys of a given
+table, you are invited to check out the "Appendix: Filesystem
+structure summary" section of this document.
+
+
+
+NODE-REVISION: how we represent a node revision
+
+We represent a given revision of a file or directory node using a list
+skel (see include/private/svn_skel.h for an explanation of skels).
+A node revision skel has the form:
+
+ (HEADER PROP-KEY KIND-SPECIFIC ...)
+
+where HEADER is a header skel, whose structure is common to all nodes,
+PROP-KEY is the key of the representation that contains this node's
+properties list, and the KIND-SPECIFIC elements carry data dependent
+on what kind of node this is --- file, directory, etc.
+
+HEADER has the form:
+
+ (KIND CREATED-PATH [PRED-ID [PRED-COUNT [HAS-MERGEINFO MERGEINFO-COUNT]]])
+
+where:
+
+ * KIND indicates what sort of node this is. It must be one of the
+ following:
+ - "file", indicating that the node is a file (see FILE below).
+ - "dir", indicating that the node is a directory (see DIR below).
+
+ * CREATED-PATH is the canonicalized absolute filesystem path at
+ which this node was created.
+
+ * PRED-ID, if present, indicates the node revision which is the
+ immediate ancestor of this node.
+
+ * PRED-COUNT, if present, indicates the number of predecessors the
+ node revision has (recursively).
+
+ * HAS-MERGEINFO and MERGEINFO-COUNT, if present, indicate ...
+ ### TODO
+
+Note that a node cannot change its kind from one revision to the next.
+A directory node is always a directory; a file node is always a file;
+etc. The fact that the node's kind is stored in each node revision,
+rather than in some revision-independent place, might suggest that
+it's possible for a node to change kinds from revision to revision, but
+Subversion does not allow this.
+
+PROP-KEY is a key into the `representations' table (see REPRESENTATIONS
+below), whose value is a representation pointing to a string
+(see `strings' table) that is a PROPLIST skel.
+
+The KIND-SPECIFIC portions are discussed below.
+
+
+
+PROPLIST: a property list is a list skel of the form:
+
+ (NAME1 VALUE1 NAME2 VALUE2 ...)
+
+where each NAMEi is the name of a property, and VALUEi is the value of
+the property named NAMEi. Every valid property list has an even
+number of elements.
+
+
+
+FILE: how files are represented.
+
+If a NODE-REVISION's header's KIND is "file", then the node-revision
+skel represents a file, and has the form:
+
+ (HEADER PROP-KEY DATA-INFO [EDIT-DATA-KEY])
+
+where
+
+ DATA-INFO ::= DATA-KEY | (DATA-KEY DATA-KEY-UNIQID)
+
+and DATA-KEY identifies the representation for the file's current
+contents, and EDIT-DATA-KEY identifies the representation currently
+available for receiving new contents for the file.
+
+DATA-KEY-UNIQID ...
+### TODO
+
+See discussion of representations later.
+
+
+
+DIR: how directories are represented.
+
+If the header's KIND is "dir", then the node-revision skel
+represents a directory, and has the form:
+
+ (HEADER PROP-KEY ENTRIES-KEY)
+
+where ENTRIES-KEY identifies the representation for the directory's
+entries list (see discussion of representations later). An entries
+list has the form
+
+ (ENTRY ...)
+
+where each entry is
+
+ (NAME ID)
+
+where:
+
+ * NAME is the name of the directory entry, in UTF-8, and
+
+ * ID is the ID of the node revision to which this entry refers
+
+
+
+REPRESENTATIONS: where and how Subversion stores your data.
+
+Some parts of a node revision are essentially constant-length: for
+example, the KIND field and the REV. Other parts can have
+arbitrarily varying length: property lists, file contents, and
+directory entry lists. This variable-length data is often similar
+from one revision to the next, so Subversion stores just the deltas
+between them, instead of successive fulltexts.
+
+The HEADER portion of a node revision holds the constant-length stuff,
+which is never deltified. The rest of a node revision just points to
+data stored outside the node revision proper. This design makes the
+repository code easier to maintain, because deltification and
+undeltification are confined to a layer separate from node revisions,
+and makes the code more efficient, because Subversion can retrieve
+just the parts of a node it needs for a given operation.
+
+Deltifiable data is stored in the `strings' table, as mediated by the
+`representations' table. Here's how it works:
+
+The `strings' table stores only raw bytes. A given string could be
+any one of these:
+
+ - a file's contents
+ - a delta that reconstructs file contents, or part of a file's contents
+ - a directory entry list skel
+ - a delta that reconstructs a dir entry list skel, or part of same
+ - a property list skel
+ - a delta that reconstructs a property list skel, or part of same
+
+There is no way to tell, from looking at a string, what kind of data
+it is. A directory entry list skel is indistinguishable from file
+contents that just happen to look exactly like the unparsed form of a
+directory entry list skel. File contents that just happen to look
+like svndiff data are indistinguishable from delta data.
+
+The code is able to interpret a given string because Subversion
+
+ a) knows whether to be looking for a property list or some
+ kind-specific data,
+
+ b) knows the `kind' of the node revision in question,
+
+ c) always goes through the `representations' table to discover if
+ any undeltification or other transformation is needed.
+
+The `representations' table is an intermediary between node revisions
+and strings. Node revisions never refer directly into the `strings'
+table; instead, they always refer into the `representations' table,
+which knows whether a given string is a fulltext or a delta, and if it
+is a delta, what it is a delta against. That, combined with the
+knowledge in (a) and (b) above, allows Subversion to retrieve the data
+and parse it appropriately. A representation has the form:
+
+ (HEADER KIND-SPECIFIC)
+
+where HEADER is
+
+ (KIND TXN [MD5 [SHA1]])
+
+The KIND is "fulltext" or "delta". TXN is the txn ID for the txn in
+which this representation was created. MD5 is a checksum of the
+representation's contents, that is, what the representation produces,
+regardless of whether it is stored deltified or as fulltext. (For
+compatibility with older versions of Subversion, MD5 may be
+absent, in which case the filesystem behaves as though the checksum is
+there and is correct.) An additional kind of checksum, SHA1, is present
+in newer formats, starting with version ...
+### TODO
+
+The TXN also serves as a kind of mutability flag: if txn T tries to
+change a representation's contents, but the rep's TXN is not T, then
+something has gone horribly wrong and T should leave the rep alone
+(and probably error). Of course, "change a representation" here means
+changing what the rep's consumer sees. Switching a representation's
+storage strategy, for example from fulltext to deltified, wouldn't
+count as a change, since that wouldn't affect what the rep produces.
+
+KIND-SPECIFIC varies considerably depending on the kind of
+representation. Here are the two forms currently recognized:
+
+ (("fulltext" TXN [MD5 [SHA1]]) STRING-KEY)
+ The data is at STRING-KEY in the `strings' table.
+
+ (("delta" TXN [MD5 [SHA1]]) (OFFSET WINDOW) ...)
+ Each OFFSET indicates the point in the fulltext that this
+ element reconstructs, and WINDOW says how to reconstruct it:
+
+ WINDOW ::= (DIFF SIZE REP-KEY [REP-OFFSET]) ;
+ DIFF ::= ("svndiff" VERSION STRING-KEY)
+
+ Notice that a WINDOW holds only metadata. REP-KEY says what
+ the window should be applied against, or none if this is a
+ self-compressed delta; SIZE says how much data this window
+ reconstructs; VERSION says what version of the svndiff format
+ is being used (currently only version 0 is supported); and
+ STRING-KEY says which string contains the actual svndiff data
+ (there is no diff data held directly in the representations
+ table, of course).
+
+ Note also that REP-KEY might refer to a representation that
+ itself requires undeltification. We use a delta combiner to
+ combine all the deltas needed to reproduce the fulltext from
+ some stored plaintext.
+
+ Branko says this is what REP-OFFSET is for:
+ > The offsets embedded in the svndiff are stored in a string;
+ > these offsets would be in the representation. The point is that
+ > you get all the information you need to select the appropriate
+ > windows from the rep skel -- without touching a single
+ > string. This means a bit more space used in the repository, but
+ > lots less memory used on the server.
+
+ We'll see if it turns out to be necessary.
+
+In the future, there may be other representations, for example
+indicating that the text is stored elsewhere in the database, or
+perhaps in an ordinary Unix file.
+
+Let's work through an example node revision:
+
+ (("file" REV COUNT) PROP-KEY "2345")
+
+The entry for key "2345" in `representations' is:
+
+ (("delta" TXN CHECKSUM) (0 (("svndiff" 0 "1729") 65 "2343")))
+
+and the entry for key "2343" in `representations' is:
+
+ (("fulltext" TXN CHECKSUM) "1001")
+
+while the entry for key "1729" in `strings' is:
+
+ <some unprintable glob of svndiff data>
+
+which, when applied to the fulltext at key "1001" in strings, results
+in this new fulltext:
+
+ "((some text) (that looks) (deceptively like) (directory entries))"
+
+Et voila! Subversion knew enough, via the `representations' and
+`strings' tables, to undeltify and get that fulltext; and knew enough,
+because of the node revision's "file" type, to interpret the result as
+file contents, not as a directory entry list.
+
+(Note that the `strings' table stores multiple DB values per key.
+That is, although it's accurate to say there is one string per key,
+the string may be divided into multiple consecutive blocks, all
+sharing that key. You use a Berkeley DB cursor to find the desired
+value[s], when retrieving a particular offset+len in a string.)
+
+Representations know nothing about ancestry -- the `representations'
+table never refers to node revision id's, only to strings or to other
+representations. In other words, while the `nodes' table allows
+recovery of ancestry information, the `representations' and `strings'
+tables together handle deltification and undeltification
+*independently* of ancestry. At present, Subversion generally stores
+the youngest strings in "fulltext" form, and older strings as "delta"s
+against them (unless the delta would save no space compared to the
+fulltext). However, there's nothing magic about that particular
+arrangement. Other interesting alternatives:
+
+ * We could store the N most recently accessed strings as fulltexts,
+ letting access patterns determine the most appropriate
+ representation for each revision.
+
+ * We could occasionally store deltas against the N'th younger
+ revision, storing larger jumps with a frequency inverse to the
+ distance covered, yielding a tree-structured history.
+
+Since the filesystem interface doesn't expose these details, we can
+change the representation pretty much as we please to optimize
+whatever parameter we care about --- storage size, speed, robustness,
+etc.
+
+Representations never share strings - every string is referred to by
+exactly one representation. This is so that when we change a
+representation to a different form (e.g. during deltification), we can
+delete the strings containing the old form, and know that we're not
+messing up any other reps by doing so.
+
+
+Further Notes On Deltifying:
+----------------------------
+
+When a representation is deltified, it is changed in place.
+New strings are created containing the new delta, the representation
+is changed to refer to the new strings, and the original (usually
+fulltext) string or strings are deleted.
+
+The node revisions referring to that representation will not be
+changed; instead, the same rep key will now be associated with
+different value. That way, we get reader locking for free: if someone
+is reading a file while Subversion is deltifying that file, one of the
+two sides will get a DB_DEADLOCK and svn_fs__retry_txn() will retry.
+
+### todo: add a note about cycle-checking here, too.
+
+
+
+The Berkeley DB "nodes" table
+
+The database contains a table called "nodes", which is a btree indexed
+by node revision ID's, mapping them onto REPRESENTATION skels. Node 0
+is always the root directory, and node revision ID 0.0.0 is always the
+empty directory. We use the value of the key 'next-key' to indicate
+the next unused node ID.
+
+Assuming that we store the most recent revision on every branch as
+fulltext, and all other revisions as deltas, we can retrieve any node
+revision by searching for the last revision of the node, and then
+walking backwards to specific revision we desire, applying deltas as
+we go.
+
+
+
+REVISION: filesystem revisions, and the Berkeley DB "revisions" table
+
+We represent a filesystem revision using a skel of the form:
+
+ ("revision" TXN)
+
+where TXN is the key into the `transactions' table (see 'Transactions' below)
+whose value is the transaction that was committed to create this revision.
+
+The database contains a table called "revisions", which is a
+record-number table mapping revision numbers onto REVISION skels.
+Since Berkeley DB record numbers start with 1, whereas Subversion
+filesystem revision numbers start at zero, revision V is stored as
+record number V+1 in the `revisions' table. Filesystem revision zero
+always has node revision 0.0.0 as its root directory; that node
+revision is guaranteed to be an empty directory.
+
+
+
+Transactions
+
+Every transaction ends when it is either successfully committed, or
+aborted. We call a transaction which has been either committed or
+aborted "finished", and one which hasn't "unfinished".
+
+Transactions are identified by unique numbers, called transaction
+ID's. Currently, transaction ID's are never reused, though this is
+not mandated by the schema. In the database, we always represent a
+transaction ID in its shortest ASCII form.
+
+The Berkeley DB `transactions' table records both unfinished and
+committed transactions. Every key in this table is a transaction ID.
+Unfinished transactions have values that are skels of one of the
+following forms:
+
+ ("transaction" ROOT-ID BASE-ID PROPLIST COPIES)
+ ("dead" ROOT-ID BASE-ID PROPLIST COPIES)
+
+where:
+
+ * ROOT-ID is the node revision ID of the transaction's root
+ directory. This is of the form 0.0.THIS-TXN-ID.
+
+ * BASE-ID is the node revision ID of the root of the transaction's
+ base revision. This is of the form 0.0.BASE-TXN-ID - the base
+ transaction is, of course, the transaction of the base revision.
+
+ * PROPLIST is a skel giving the revision properties for the
+ transaction.
+
+ * COPIES contains a list of keys into the `copies' table,
+ referencing all the filesystem copies created inside of this
+ transaction. If the transaction is aborted, these copies get
+ removed from the `copies' table.
+
+ * A "dead" transaction is one that has been requested to be
+ destroyed, and should never, ever, be committed.
+
+Committed transaction, however, have values that are skels of the form:
+
+ ("committed" ROOT-ID REV PROPLIST COPIES)
+
+where:
+
+ * ROOT-ID is the node revision ID of the committed transaction's (or
+ revision's) root node.
+
+ * REV represents the revision that was created when the
+ transaction was committed.
+
+ * PROPLIST is a skel giving the revision properties for the
+ committed transaction.
+
+ * COPIES contains a list of keys into the `copies' table,
+ referencing all the filesystem copies created by this committed
+ transaction. Nothing currently uses this information for
+ committed transactions, but it could be useful in the future.
+
+As the sole exception to the rule above, the `transactions' table
+always has one entry whose key is `next-key', and whose value is the
+lowest transaction ID that has never yet been used. We use this entry
+to allocate ID's for new transactions.
+
+The `transactions' table is a btree, with no particular sort order.
+
+
+
+Changes
+
+As modifications are made (files and dirs added or removed, text and
+properties changed, etc.) on Subversion transaction trees, the
+filesystem tracks the basic change made in the Berkeley DB `changes'
+table.
+
+The `changes' table is a btree with Berkeley's "duplicate keys"
+functionality (and with no particular sort order), and maps the
+one-to-many relationship of a transaction ID to a "change" item.
+Change items are skels of the form:
+
+ ("change" PATH ID CHANGE-KIND TEXT-MOD PROP-MOD)
+
+where:
+
+ * PATH is the path that was operated on to enact this change.
+
+ * ID is the node revision ID of the node changed. The precise
+ meaning varies based on the kind of the change:
+ - "add" or "modify": a new node revision created in the current
+ txn.
+ - "delete": a node revision from a previous txn.
+ - "replace": a replace operation actually acts on two node
+ revisions, one being deleted, one being added. Only the added
+ node-revision ID is recorded in the `changes' table - this is
+ a design flaw.
+ - "reset": no node revision applies. A zero atom is used as a
+ placeholder.
+
+ * CHANGE-KIND is one of the following:
+
+ - "add" : PATH/ID was added to the filesystem.
+ - "delete" : PATH/ID was removed from the filesystem.
+ - "replace" : PATH/ID was removed, then re-added to the filesystem.
+ - "modify" : PATH/ID was otherwise modified.
+ - "reset" : Ignore any previous changes for PATH/ID in this txn.
+ This kind is no longer created by Subversion 1.3.0
+ and later, and can probably be removed at the next
+ schema bump.
+
+ * TEXT-MOD is a bit specifying whether or not the contents of
+ this node was modified.
+
+ * PROP-MOD is a bit specifying whether or not the properties of
+ this node where modified.
+
+In order to fully describe the changes made to any given path as part
+of a single transaction, one must read all the change items associated
+with the transaction's ID, and "collapse" multiple entries that refer
+to that path.
+
+
+
+Copies
+
+Each time a filesystem copy operation is performed, Subversion records
+meta-data about that copy.
+
+Copies are identified by unique numbers called copy ID's. Currently,
+copy ID's are never reused, though this is not mandated by the schema.
+In the database, we always represent a copy ID in its shortest ASCII
+form.
+
+The Berkeley DB `copies' table records all filesystem copies. Every
+key in this table is copy ID, and every value is a skel of one of the
+following forms:
+
+ ("copy" SRC-PATH SRC-TXN DST-NODE-ID)
+ ("soft-copy" SRC-PATH SRC-TXN DST-NODE-ID)
+
+where:
+
+ * "copy" indicates an explicitly requested copy, and "soft-copy"
+ indicates a node that was cloned internally as part of an
+ explicitly requested copy of some parent directory. See the
+ section "Copies and Copy IDs" in the file <fs-history> for
+ details.
+
+ * SRC-PATH and SRC-TXN are the canonicalized absolute path and
+ transaction ID, respectively, of the source of the copy.
+
+ * DST-NODE-ID represents the new node revision created as a result
+ of the copy.
+
+As the sole exception to the rule above, the `copies' table always has
+one entry whose key is `next-key', and whose value is the lowest copy ID
+that has never yet been used. We use this entry to allocate new
+copy ID's.
+
+The `copies' table is a btree, with no particular sort order.
+
+
+
+Locks
+
+When a caller locks a file -- reserving an exclusive right to modify
+or delete it -- an lock object is created in a `locks' table.
+
+The `locks' table is a btree whose key is a UUID string known as
+a "lock-token", and whose value is a skel representing a lock. The
+fields in the skel mirror those of an svn_lock__t (see svn_types.h):
+
+ ("lock" PATH TOKEN OWNER COMMENT XML-P CREATION-DATE EXPIRATION-DATE)
+
+where:
+
+ * PATH is the absolute filesystem path reserved by the lock.
+
+ * TOKEN is the universally unique identifier of the lock, known
+ as the lock-token. This is the same as the row's key.
+
+ * OWNER is the authenticated username that "owns" the lock.
+
+ * COMMENT is a string describing the lock. It may be empty, or it
+ might describe the rationale for locking.
+
+ * XML-P is a boolean (either 0 or 1) indicating whether the COMMENT
+ field is wrapped in an XML tag. (This is something only used by
+ the DAV layer, for webdav interoperabliity.)
+
+ * CREATION-DATE is a string representation of the date/time when
+ the lock was created. (see svn_time_to_cstring())
+
+ * EXPIRATION-DATE is a string representation of the date/time when
+ the lock will cease to be valid. (see svn_time_to_cstring())
+
+In addition to creating a lock in the `locks' table, a new row is
+created in a `lock-tokens' table. The `lock-tokens' table is a btree
+whose key is an absolute path in the filesystem. The value of each
+key is a lock-token (which is a key into the `locks' table.)
+
+To test if a path is locked, simply check if the path is a key in the
+`lock-tokens' table. To see if a certain directory has any locked
+children below, we ask BerkeleyDB to do a "greater or equal match" on
+the directory path, and see if any results come back. If they do,
+then at least one of the directory's children is locked, and thus the
+directory cannot be deleted without further investigation.
+
+Locks are ephemeral things, not historied in any way. They are
+potentially created and deleted quite often. When a lock is
+destroyed, the appropriate row is removed from the `locks' table.
+Additionally, the locked-path is removed from the `lock-tokens' table.
+
+
+
+
+
+Merge rules
+
+The Subversion filesystem must provide the following characteristics:
+
+- clients can submit arbitrary rearrangements of the tree, to be
+ performed as atomic changes to the filesystem tree
+- multiple clients can submit non-overlapping changes at the same time,
+ without blocking
+- readers must never block other readers or writers
+- writers must never block readers
+- writers may block writers
+
+Merging rules:
+
+ The general principle: a series of changes can be merged iff the
+ final outcome is independent of the order you apply them in.
+
+Merging algorithm:
+
+ For each entry NAME in the directory ANCESTOR:
+
+ Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
+ the name within ANCESTOR, SOURCE, and TARGET respectively.
+ (Possibly null if NAME does not exist in SOURCE or TARGET.)
+
+ If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
+ No changes were made to this entry while the transaction was in
+ progress, so do nothing to the target.
+
+ Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
+ A change was made to this entry while the transaction was in
+ process, but the transaction did not touch this entry. Replace
+ TARGET-ENTRY with SOURCE-ENTRY.
+
+ Else:
+ Changes were made to this entry both within the transaction and
+ to the repository while the transaction was in progress. They
+ must be merged or declared to be in conflict.
+
+ If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
+ double delete; if one of them is null, that's a delete versus
+ a modification. In any of these cases, flag a conflict.
+
+ If any of the three entries is of type file, declare a conflict.
+
+ If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
+ modification of ANCESTOR-ENTRY (determine by comparing the
+ node-id fields), declare a conflict. A replacement is
+ incompatible with a modification or other replacement--even
+ an identical replacement.
+
+ Direct modifications were made to the directory ANCESTOR-ENTRY
+ in both SOURCE and TARGET. Recursively merge these
+ modifications.
+
+ For each leftover entry NAME in the directory SOURCE:
+
+ If NAME exists in TARGET, declare a conflict. Even if SOURCE and
+ TARGET are adding exactly the same thing, two additions are not
+ auto-mergeable with each other.
+
+ Add NAME to TARGET with the entry from SOURCE.
+
+ Now that we are done merging the changes from SOURCE into the
+ directory TARGET, update TARGET's predecessor to be SOURCE.
+
+The following algorithm was used when the Subversion filesystem was
+initially written, but has been replaced with the simpler and more
+performant algorithm above:
+
+ Merging two nodes, A and B, with respect to a common ancestor
+ ANCESTOR:
+
+ - First, the merge fails unless A, B, and ANCESTOR are all the same
+ kind of node.
+ - If A and B are text files:
+ - If A is an ancestor of B, then B is the merged result.
+ - If A is identical to B, then B (arbitrarily) is the merged
+ result.
+ - Otherwise, the merge fails.
+ - If A and B are both directories:
+ - For every directory entry E in either A, B, or ANCESTOR, here
+ are the cases:
+ - E exists in neither ANCESTOR nor A.
+ - E doesn't exist in ANCESTOR, and has been added to A.
+ - E exists in ANCESTOR, but has been deleted from A.
+ - E exists in both ANCESTOR and A ...
+ - but refers to different nodes.
+ - but refers to different revisions of the same node.
+ - and refers to the same node revision.
+
+ The same set of possible relationships with ANCESTOR holds for B,
+ so there are thirty-six combinations. The matrix is symmetrical
+ with A and B reversed, so we only have to describe one triangular
+ half, including the diagonal --- 21 combinations.
+
+ - (6) E exists in neither ANCESTOR nor A:
+ - (1) E exists in neither ANCESTOR nor B. Can't occur, by
+ assumption that E exists in either A, B, or ancestor.
+ - (1) E has been added to B. Add E in the merged result. ***
+ - (1) E has been deleted from B. Can't occur, by assumption
+ that E doesn't exist in ANCESTOR.
+ - (3) E exists in both ANCESTOR and B. Can't occur, by
+ assumption that E doesn't exist in ancestor.
+ - (5) E doesn't exist in ANCESTOR, and has been added to A.
+ - (1) E doesn't exist in ANCESTOR, and has been added to B.
+ Conflict.
+ - (1) E exists in ANCESTOR, but has been deleted from B.
+ Can't occur, by assumption that E doesn't exist in
+ ANCESTOR.
+ - (3) E exists in both ANCESTOR and B. Can't occur, by
+ assumption that E doesn't exist in ANCESTOR.
+ - (4) E exists in ANCESTOR, but has been deleted from A.
+ - (1) E exists in ANCESTOR, but has been deleted from B. If
+ neither delete was a result of a rename, then omit E from
+ the merged tree. *** Otherwise, conflict.
+ - E exists in both ANCESTOR and B ...
+ - (1) but refers to different nodes. Conflict.
+ - (1) but refers to different revisions of the same node.
+ Conflict.
+ - (1) and refers to the same node revision. Omit E from
+ the merged tree. ***
+ - (3) E exists in both ANCESTOR and A, but refers to different
+ nodes.
+ - (1) E exists in both ANCESTOR and B, but refers to
+ different nodes. Conflict.
+ - (1) E exists in both ANCESTOR and B, but refers to
+ different revisions of the same node. Conflict.
+ - (1) E exists in both ANCESTOR and B, and refers to the same
+ node revision. Replace E with A's node revision. ***
+ - (2) E exists in both ANCESTOR and A, but refers to different
+ revisions of the same node.
+ - (1) E exists in both ANCESTOR and B, but refers to
+ different revisions of the same node. Try to merge A/E and
+ B/E, recursively. ***
+ - (1) E exists in both ANCESTOR and B, and refers to the same
+ node revision. Replace E with A's node revision. ***
+ - (1) E exists in both ANCESTOR and A, and refers to the same
+ node revision.
+ - (1) E exists in both ANCESTOR and B, and refers to the same
+ node revision. Nothing has happened to ANCESTOR/E, so no
+ change is necessary.
+
+ *** == something actually happens
+
+
+Non-Historical Properties
+
+[[Yes, do tell.]]
+
+
+UUIDs: Universally Unique Identifiers
+
+Every filesystem has a UUID. This is represented as record #1 in the
+`uuids' table.
+
+
+Layers
+
+In previous structurings of the code, I had trouble keeping track of
+exactly who has implemented which promises, based on which other
+promises from whom.
+
+I hope the arrangement below will help me keep things straight, and
+make the code more reliable. The files are arranged in order from
+low-level to high-level: each file depends only on services provided
+by the files before it.
+
+skel.c, id.c, dbt.c, convert-size.c
+
+ Low-level utility functions.
+
+fs_skels.c Routines for marshaling between skels and native FS types.
+
+fs.c Creating and destroying filesystem objects.
+
+err.c Error handling.
+
+nodes-table.c, txn-table.c, rev-table.c, reps-table.c, strings-table.c
+
+ Create and open particular database tables.
+ Responsible for intra-record consistency.
+
+node-rev.c Creating, reading, and writing node revisions.
+ Responsible for deciding what gets deltified when.
+
+reps-strings.c
+ Retrieval and storage of represented strings.
+ This will handle delta-based storage,
+
+dag.c Operations on the DAG filesystem. "DAG" because the
+ interface exposes the filesystem's sharing structure.
+ Enforce inter-record consistency.
+
+tree.c Operations on the tree filesystem. This layer is
+ built on top of dag.c, but transparently distinguishes
+ virtual copies, making the underlying DAG look like a
+ real tree. This makes incomplete transactions behave
+ like ordinary mutable filesystems.
+
+delta.c Computing deltas.
+
+
+
+Appendix: Filesystem structure summary
+======================================
+
+Berkeley DB tables
+------------------
+
+ "nodes" : btree(ID -> NODE-REVISION, "next-key" -> NODE-ID)
+ "revisions" : recno(REVISION)
+ "transactions" : btree(TXN -> TRANSACTION, "next-key" -> TXN)
+ "changes" : btree(TXN -> CHANGE)
+ "copies" : btree(CPY -> COPY, "next-key" -> CPY)
+ "strings" : btree(STR -> STRING, "next-key" -> STR)
+ "representations" : btree(REP -> REPRESENTATION, "next-key" -> REP)
+ "uuids" : recno(UUID)
+ "locks" : btree(TOKEN -> LOCK)
+ "lock-tokens" : btree(PATH -> TOKEN)
+ "node-origins" : btree(NODE-ID -> ID)
+ "checksum-reps" : btree(SHA1SUM -> REP, "next-key" -> number-36)
+ "miscellaneous" : btree(STRING -> STRING)
+
+Syntactic elements
+------------------
+
+Table keys:
+
+ ID ::= NODE-REV-ID ;
+ TXN ::= number-36 ;
+ CPY ::= number-36 ;
+ STR ::= number-36 ;
+ REP ::= number-36 ;
+ TOKEN ::= uuid ;
+
+Property lists:
+
+ PROPLIST ::= (PROP ...) ;
+ PROP ::= atom atom ;
+
+
+Filesystem revisions:
+
+ REVISION ::= ("revision" TXN) ;
+
+
+Transactions:
+
+ TRANSACTION ::= UNFINISHED-TXN | COMMITTED-TXN | DEAD-TXN
+ UNFINISHED-TXN ::= ("transaction" ROOT-ID BASE-ID PROPLIST COPIES) ;
+ COMMITTED-TXN ::= ("committed" ROOT-ID REV PROPLIST COPIES) ;
+ DEAD-TXN ::= ("dead" ROOT-ID BASE-ID PROPLIST COPIES) ;
+ ROOT-ID ::= NODE-REV-ID ;
+ BASE-ID ::= NODE-REV-ID ;
+ COPIES ::= (CPY ...) ;
+ REV ::= number ;
+
+
+Changes:
+
+ CHANGE ::= ("change" PATH ID CHANGE-KIND TEXT-MOD PROP-MOD) ;
+ CHANGE-KIND ::= "add" | "delete" | "replace" | "modify" | "reset";
+ TEXT-MOD ::= atom ;
+ PROP-MOD ::= atom ;
+
+
+Copies:
+
+ COPY ::= REAL-COPY | SOFT-COPY
+ REAL-COPY ::= ("copy" SRC-PATH SRC-TXN DST-NODE-ID)
+ SOFT-COPY ::= ("soft-copy" SRC-PATH SRC-TXN DST-NODE-ID)
+ SRC-PATH ::= atom ;
+ SRC-TXN ::= TXN ;
+ DST-NODE-ID ::= NODE-REV-ID ;
+
+
+Entries lists:
+
+ ENTRIES ::= (ENTRY ...) ;
+ ENTRY ::= (NAME ID) ;
+ NAME ::= atom ;
+
+
+Node revisions:
+
+ NODE-REVISION ::= FILE | DIR ;
+ FILE ::= (HEADER PROP-KEY DATA-INFO [EDIT-DATA-KEY]) ;
+ DIR ::= (HEADER PROP-KEY ENTRIES-KEY) ;
+ HEADER ::= (KIND CREATED-PATH
+ [PRED-ID [PRED-COUNT
+ [HAS-MERGEINFO MERGEINFO-COUNT]]]) ;
+ KIND ::= "file" | "dir" ;
+ PRED-ID ::= NODE-REV-ID | "";
+ PRED-COUNT ::= number | "" ;
+ CREATED-PATH ::= atom ;
+ PROP-KEY ::= atom ;
+ DATA-INFO ::= DATA-KEY | (DATA-KEY DATA-KEY-UNIQID)
+ DATA-KEY ::= atom ;
+ DATA-KEY-UNIQID ::= atom ;
+ EDIT-DATA-KEY ::= atom ;
+ HAS-MERGEINFO ::= "0" | "1" ;
+ MERGEINFO-COUNT ::= number ;
+
+
+Representations:
+
+ REPRESENTATION ::= FULLTEXT | DELTA ;
+ FULLTEXT ::= (HEADER STRING-KEY) ;
+ DELTA ::= (HEADER (OFFSET WINDOW) ...) ;
+ WINDOW ::= (DIFF SIZE REP-KEY [REP-OFFSET]) ;
+ DIFF ::= ("svndiff" VERSION STRING-KEY) ;
+ VERSION ::= number ;
+ REP-KEY ::= atom ;
+ STRING-KEY ::= atom ;
+ OFFSET ::= number ;
+ REP-OFFSET ::= number ;
+
+ HEADER ::= (KIND TXN [MD5 [SHA1]]) ;
+ KIND ::= "fulltext" | "delta" ;
+
+ SIZE ::= number ;
+ MD5 ::= ("md5" MD5SUM) ;
+ SHA1 ::= ("sha1" SHA1SUM) ;
+ MD5SUM ::= atom ;
+ SHA1SUM ::= atom ;
+
+
+Strings:
+
+ STRING ::= RAWTEXT | LISTTEXT | DIFFTEXT
+ RAWTEXT ::= /{anything.class}*/ ;
+ LISTTEXT ::= list ;
+ DIFFTEXT ::= /{anything.class}*/ ;
+
+
+Node revision IDs:
+
+ NODE-REV-ID ::= NODE-ID '.' CPY '.' TXN ;
+ NODE-ID ::= number ;
+
+UUIDs:
+ UUID ::= uuid ;
+
+
+Locks:
+
+ LOCK ::= ("lock" PATH TOKEN OWNER
+ COMMENT XML-P CR-DATE [X-DATE]);
+ PATH ::= atom ;
+ OWNER ::= atom ;
+ COMMENT ::= atom ;
+ XML-P ::= "0" | "1" ;
+ CR-DATE ::= atom ;
+ X-DATE ::= atom ;
+
+Lock tokens:
+
+ (the value is just a lock-token, which is a uuid)
+
+
+Node origins:
+
+ NODE-ID ::= NODE-REV-ID ;
+
+
+Lexical elements
+----------------
+
+UUIDs:
+
+ uuid ::= hexits-32 '-' hexits-16 '-' hexits-16 '-'
+ hexits-16 '-' hexits-48 ;
+
+Numbers:
+
+ number ::= /{digit.class}+/ ;
+ number-36 ::= /{base36.class}+/ ;
+ hexits-32 ::= /{base16.class}{8}/ ;
+ hexits-16 ::= /{base16.class}{4}/ ;
+ hexits-48 ::= /{base16.class}{12}/ ;
+
+(Note: the following are described in skel.h)
+Skels:
+
+ skel ::= atom | list;
+ list ::= list.head list.body.opt list.tail ;
+ atom ::= atom.imp-len | atom.exp-len ;
+
+ list.head ::= '(' spaces.opt ;
+ list.tail ::= spaces.opt ')' ;
+ list.body.opt ::= | list.body ;
+ list.body ::= skel | list.body spaces.opt skel ;
+
+ atom.imp-len ::= /{name.class}[^\(\){ws.class}]*/ ;
+ atom.exp-len ::= /({digit.class}+){ws.class}.{\1}/ ;
+
+ spaces.opt ::= /{ws.class}*/ ;
+
+
+Character classes:
+
+ ws.class ::= [\t\n\f\r\ ] ;
+ digit.class ::= [0-9] ;
+ name.class ::= [A-Za-z] ;
+ base16.class ::= [0-9a-f]
+ base36.class ::= [a-z0-9]
+ anything.class ::= anything at all ;
+
+
+
+Appendix: 'miscellaneous' table contents
+======================================
+
+The 'miscellaneous' table contains string keys mapped to string
+values. Here is a table of the supported keys, the descriptions of
+their values, and the filesystem format version in which they were
+introduced.
+
+ Fmt Key Value
+ --- ------------------ ------------------------------------
+ 4 forward-delta-rev Youngest revision in the repository as of
+ the moment when it was upgraded to support
+ forward deltas.
diff --git a/contrib/subversion/subversion/libsvn_fs_base/reps-strings.c b/contrib/subversion/subversion/libsvn_fs_base/reps-strings.c
new file mode 100644
index 0000000..553075d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/reps-strings.c
@@ -0,0 +1,1617 @@
+/* reps-strings.c : intepreting representations with respect to strings
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <assert.h>
+
+#include "svn_fs.h"
+#include "svn_pools.h"
+
+#include "fs.h"
+#include "err.h"
+#include "trail.h"
+#include "reps-strings.h"
+
+#include "bdb/reps-table.h"
+#include "bdb/strings-table.h"
+
+#include "../libsvn_fs/fs-loader.h"
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+
+/*** Helper Functions ***/
+
+
+/* Return non-zero iff REP is mutable under transaction TXN_ID. */
+static svn_boolean_t rep_is_mutable(representation_t *rep,
+ const char *txn_id)
+{
+ if ((! rep->txn_id) || (strcmp(rep->txn_id, txn_id) != 0))
+ return FALSE;
+ return TRUE;
+}
+
+/* Helper macro that evaluates to an error message indicating that
+ the representation referred to by X has an unknown node kind. */
+#define UNKNOWN_NODE_KIND(x) \
+ svn_error_createf \
+ (SVN_ERR_FS_CORRUPT, NULL, \
+ _("Unknown node kind for representation '%s'"), x)
+
+/* Return a `fulltext' representation, allocated in POOL, which
+ * references the string STR_KEY.
+ *
+ * If TXN_ID is non-zero and non-NULL, make the representation mutable
+ * under that TXN_ID.
+ *
+ * If STR_KEY is non-null, copy it into an allocation from POOL.
+ *
+ * If MD5_CHECKSUM is non-null, use it as the MD5 checksum for the new
+ * rep; else initialize the rep with an all-zero (i.e., always
+ * successful) MD5 checksum.
+ *
+ * If SHA1_CHECKSUM is non-null, use it as the SHA1 checksum for the new
+ * rep; else initialize the rep with an all-zero (i.e., always
+ * successful) SHA1 checksum.
+ */
+static representation_t *
+make_fulltext_rep(const char *str_key,
+ const char *txn_id,
+ svn_checksum_t *md5_checksum,
+ svn_checksum_t *sha1_checksum,
+ apr_pool_t *pool)
+
+{
+ representation_t *rep = apr_pcalloc(pool, sizeof(*rep));
+ if (txn_id && *txn_id)
+ rep->txn_id = apr_pstrdup(pool, txn_id);
+ rep->kind = rep_kind_fulltext;
+ rep->md5_checksum = svn_checksum_dup(md5_checksum, pool);
+ rep->sha1_checksum = svn_checksum_dup(sha1_checksum, pool);
+ rep->contents.fulltext.string_key
+ = str_key ? apr_pstrdup(pool, str_key) : NULL;
+ return rep;
+}
+
+
+/* Set *KEYS to an array of string keys gleaned from `delta'
+ representation REP. Allocate *KEYS in POOL. */
+static svn_error_t *
+delta_string_keys(apr_array_header_t **keys,
+ const representation_t *rep,
+ apr_pool_t *pool)
+{
+ const char *key;
+ int i;
+ apr_array_header_t *chunks;
+
+ if (rep->kind != rep_kind_delta)
+ return svn_error_create
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Representation is not of type 'delta'"));
+
+ /* Set up a convenience variable. */
+ chunks = rep->contents.delta.chunks;
+
+ /* Initialize *KEYS to an empty array. */
+ *keys = apr_array_make(pool, chunks->nelts, sizeof(key));
+ if (! chunks->nelts)
+ return SVN_NO_ERROR;
+
+ /* Now, push the string keys for each window into *KEYS */
+ for (i = 0; i < chunks->nelts; i++)
+ {
+ rep_delta_chunk_t *chunk = APR_ARRAY_IDX(chunks, i, rep_delta_chunk_t *);
+
+ key = apr_pstrdup(pool, chunk->string_key);
+ APR_ARRAY_PUSH(*keys, const char *) = key;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Delete the strings associated with array KEYS in FS as part of TRAIL. */
+static svn_error_t *
+delete_strings(const apr_array_header_t *keys,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ int i;
+ const char *str_key;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ for (i = 0; i < keys->nelts; i++)
+ {
+ svn_pool_clear(subpool);
+ str_key = APR_ARRAY_IDX(keys, i, const char *);
+ SVN_ERR(svn_fs_bdb__string_delete(fs, str_key, trail, subpool));
+ }
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Reading the contents from a representation. ***/
+
+struct compose_handler_baton
+{
+ /* The combined window, and the pool it's allocated from. */
+ svn_txdelta_window_t *window;
+ apr_pool_t *window_pool;
+
+ /* If the incoming window was self-compressed, and the combined WINDOW
+ exists from previous iterations, SOURCE_BUF will point to the
+ expanded self-compressed window. */
+ char *source_buf;
+
+ /* The trail for this operation. WINDOW_POOL will be a child of
+ TRAIL->pool. No allocations will be made from TRAIL->pool itself. */
+ trail_t *trail;
+
+ /* TRUE when no more windows have to be read/combined. */
+ svn_boolean_t done;
+
+ /* TRUE if we've just started reading a new window. We need this
+ because the svndiff handler will push a NULL window at the end of
+ the stream, and we have to ignore that; but we must also know
+ when it's appropriate to push a NULL window at the combiner. */
+ svn_boolean_t init;
+};
+
+
+/* Handle one window. If BATON is emtpy, copy the WINDOW into it;
+ otherwise, combine WINDOW with the one in BATON, unless WINDOW
+ is self-compressed (i.e., does not copy from the source view),
+ in which case expand. */
+
+static svn_error_t *
+compose_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct compose_handler_baton *cb = baton;
+ SVN_ERR_ASSERT(!cb->done || window == NULL);
+ SVN_ERR_ASSERT(cb->trail && cb->trail->pool);
+
+ if (!cb->init && !window)
+ return SVN_NO_ERROR;
+
+ /* We should never get here if we've already expanded a
+ self-compressed window. */
+ SVN_ERR_ASSERT(!cb->source_buf);
+
+ if (cb->window)
+ {
+ if (window && (window->sview_len == 0 || window->src_ops == 0))
+ {
+ /* This is a self-compressed window. Don't combine it with
+ the others, because the combiner may go quadratic. Instead,
+ expand it here and signal that the combination has
+ ended. */
+ apr_size_t source_len = window->tview_len;
+ SVN_ERR_ASSERT(cb->window->sview_len == source_len);
+ cb->source_buf = apr_palloc(cb->window_pool, source_len);
+ svn_txdelta_apply_instructions(window, NULL,
+ cb->source_buf, &source_len);
+ cb->done = TRUE;
+ }
+ else
+ {
+ /* Combine the incoming window with whatever's in the baton. */
+ apr_pool_t *composite_pool = svn_pool_create(cb->trail->pool);
+ svn_txdelta_window_t *composite;
+
+ composite = svn_txdelta_compose_windows(window, cb->window,
+ composite_pool);
+ svn_pool_destroy(cb->window_pool);
+ cb->window = composite;
+ cb->window_pool = composite_pool;
+ cb->done = (composite->sview_len == 0 || composite->src_ops == 0);
+ }
+ }
+ else if (window)
+ {
+ /* Copy the (first) window into the baton. */
+ apr_pool_t *window_pool = svn_pool_create(cb->trail->pool);
+ SVN_ERR_ASSERT(cb->window_pool == NULL);
+ cb->window = svn_txdelta_window_dup(window, window_pool);
+ cb->window_pool = window_pool;
+ cb->done = (window->sview_len == 0 || window->src_ops == 0);
+ }
+ else
+ cb->done = TRUE;
+
+ cb->init = FALSE;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Read one delta window from REP[CUR_CHUNK] and push it at the
+ composition handler. */
+
+static svn_error_t *
+get_one_window(struct compose_handler_baton *cb,
+ svn_fs_t *fs,
+ representation_t *rep,
+ int cur_chunk)
+{
+ svn_stream_t *wstream;
+ char diffdata[4096]; /* hunk of svndiff data */
+ svn_filesize_t off; /* offset into svndiff data */
+ apr_size_t amt; /* how much svndiff data to/was read */
+ const char *str_key;
+
+ apr_array_header_t *chunks = rep->contents.delta.chunks;
+ rep_delta_chunk_t *this_chunk, *first_chunk;
+
+ cb->init = TRUE;
+ if (chunks->nelts <= cur_chunk)
+ return compose_handler(NULL, cb);
+
+ /* Set up a window handling stream for the svndiff data. */
+ wstream = svn_txdelta_parse_svndiff(compose_handler, cb, TRUE,
+ cb->trail->pool);
+
+ /* First things first: send the "SVN"{version} header through the
+ stream. ### For now, we will just use the version specified
+ in the first chunk, and then verify that no chunks have a
+ different version number than the one used. In the future,
+ we might simply convert chunks that use a different version
+ of the diff format -- or, heck, a different format
+ altogether -- to the format/version of the first chunk. */
+ first_chunk = APR_ARRAY_IDX(chunks, 0, rep_delta_chunk_t*);
+ diffdata[0] = 'S';
+ diffdata[1] = 'V';
+ diffdata[2] = 'N';
+ diffdata[3] = (char) (first_chunk->version);
+ amt = 4;
+ SVN_ERR(svn_stream_write(wstream, diffdata, &amt));
+ /* FIXME: The stream write handler is borked; assert (amt == 4); */
+
+ /* Get this string key which holds this window's data.
+ ### todo: make sure this is an `svndiff' DIFF skel here. */
+ this_chunk = APR_ARRAY_IDX(chunks, cur_chunk, rep_delta_chunk_t*);
+ str_key = this_chunk->string_key;
+
+ /* Run through the svndiff data, at least as far as necessary. */
+ off = 0;
+ do
+ {
+ amt = sizeof(diffdata);
+ SVN_ERR(svn_fs_bdb__string_read(fs, str_key, diffdata,
+ off, &amt, cb->trail,
+ cb->trail->pool));
+ off += amt;
+ SVN_ERR(svn_stream_write(wstream, diffdata, &amt));
+ }
+ while (amt != 0);
+ SVN_ERR(svn_stream_close(wstream));
+
+ SVN_ERR_ASSERT(!cb->init);
+ SVN_ERR_ASSERT(cb->window != NULL);
+ SVN_ERR_ASSERT(cb->window_pool != NULL);
+ return SVN_NO_ERROR;
+}
+
+
+/* Undeltify a range of data. DELTAS is the set of delta windows to
+ combine, FULLTEXT is the source text, CUR_CHUNK is the index of the
+ delta chunk we're starting from. OFFSET is the relative offset of
+ the requested data within the chunk; BUF and LEN are what we're
+ undeltifying to. */
+
+static svn_error_t *
+rep_undeltify_range(svn_fs_t *fs,
+ const apr_array_header_t *deltas,
+ representation_t *fulltext,
+ int cur_chunk,
+ char *buf,
+ apr_size_t offset,
+ apr_size_t *len,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_size_t len_read = 0;
+
+ do
+ {
+ struct compose_handler_baton cb = { 0 };
+ char *source_buf, *target_buf;
+ apr_size_t target_len;
+ int cur_rep;
+
+ cb.trail = trail;
+ cb.done = FALSE;
+ for (cur_rep = 0; !cb.done && cur_rep < deltas->nelts; ++cur_rep)
+ {
+ representation_t *const rep =
+ APR_ARRAY_IDX(deltas, cur_rep, representation_t*);
+ SVN_ERR(get_one_window(&cb, fs, rep, cur_chunk));
+ }
+
+ if (!cb.window)
+ /* That's it, no more source data is available. */
+ break;
+
+ /* The source view length should not be 0 if there are source
+ copy ops in the window. */
+ SVN_ERR_ASSERT(cb.window->sview_len > 0 || cb.window->src_ops == 0);
+
+ /* cb.window is the combined delta window. Read the source text
+ into a buffer. */
+ if (cb.source_buf)
+ {
+ /* The combiner already created the source text from a
+ self-compressed window. */
+ source_buf = cb.source_buf;
+ }
+ else if (fulltext && cb.window->sview_len > 0 && cb.window->src_ops > 0)
+ {
+ apr_size_t source_len = cb.window->sview_len;
+ source_buf = apr_palloc(cb.window_pool, source_len);
+ SVN_ERR(svn_fs_bdb__string_read
+ (fs, fulltext->contents.fulltext.string_key,
+ source_buf, cb.window->sview_offset, &source_len,
+ trail, pool));
+ if (source_len != cb.window->sview_len)
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Svndiff source length inconsistency"));
+ }
+ else
+ {
+ source_buf = NULL; /* Won't read anything from here. */
+ }
+
+ if (offset > 0)
+ {
+ target_len = *len - len_read + offset;
+ target_buf = apr_palloc(cb.window_pool, target_len);
+ }
+ else
+ {
+ target_len = *len - len_read;
+ target_buf = buf;
+ }
+
+ svn_txdelta_apply_instructions(cb.window, source_buf,
+ target_buf, &target_len);
+ if (offset > 0)
+ {
+ SVN_ERR_ASSERT(target_len > offset);
+ target_len -= offset;
+ memcpy(buf, target_buf + offset, target_len);
+ offset = 0; /* Read from the beginning of the next chunk. */
+ }
+ /* Don't need this window any more. */
+ svn_pool_destroy(cb.window_pool);
+
+ len_read += target_len;
+ buf += target_len;
+ ++cur_chunk;
+ }
+ while (len_read < *len);
+
+ *len = len_read;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Calculate the index of the chunk in REP that contains REP_OFFSET,
+ and find the relative CHUNK_OFFSET within the chunk.
+ Return -1 if offset is beyond the end of the represented data.
+ ### The basic assumption is that all delta windows are the same size
+ and aligned at the same offset, so this number is the same in all
+ dependent deltas. Oh, and the chunks in REP must be ordered. */
+
+static int
+get_chunk_offset(representation_t *rep,
+ svn_filesize_t rep_offset,
+ apr_size_t *chunk_offset)
+{
+ const apr_array_header_t *chunks = rep->contents.delta.chunks;
+ int cur_chunk;
+ assert(chunks->nelts);
+
+ /* ### Yes, this is a linear search. I'll change this to bisection
+ the very second we notice it's slowing us down. */
+ for (cur_chunk = 0; cur_chunk < chunks->nelts; ++cur_chunk)
+ {
+ const rep_delta_chunk_t *const this_chunk
+ = APR_ARRAY_IDX(chunks, cur_chunk, rep_delta_chunk_t*);
+
+ if ((this_chunk->offset + this_chunk->size) > rep_offset)
+ {
+ assert(this_chunk->offset <= rep_offset);
+ assert(rep_offset - this_chunk->offset < SVN_MAX_OBJECT_SIZE);
+ *chunk_offset = (apr_size_t) (rep_offset - this_chunk->offset);
+ return cur_chunk;
+ }
+ }
+
+ return -1;
+}
+
+/* Copy into BUF *LEN bytes starting at OFFSET from the string
+ represented via REP_KEY in FS, as part of TRAIL.
+ The number of bytes actually copied is stored in *LEN. */
+static svn_error_t *
+rep_read_range(svn_fs_t *fs,
+ const char *rep_key,
+ svn_filesize_t offset,
+ char *buf,
+ apr_size_t *len,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+ apr_size_t chunk_offset;
+
+ /* Read in our REP. */
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+ if (rep->kind == rep_kind_fulltext)
+ {
+ SVN_ERR(svn_fs_bdb__string_read(fs, rep->contents.fulltext.string_key,
+ buf, offset, len, trail, pool));
+ }
+ else if (rep->kind == rep_kind_delta)
+ {
+ const int cur_chunk = get_chunk_offset(rep, offset, &chunk_offset);
+ if (cur_chunk < 0)
+ *len = 0;
+ else
+ {
+ svn_error_t *err;
+ /* Preserve for potential use in error message. */
+ const char *first_rep_key = rep_key;
+ /* Make a list of all the rep's we need to undeltify this range.
+ We'll have to read them within this trail anyway, so we might
+ as well do it once and up front. */
+ apr_array_header_t *reps = apr_array_make(pool, 30, sizeof(rep));
+ do
+ {
+ const rep_delta_chunk_t *const first_chunk
+ = APR_ARRAY_IDX(rep->contents.delta.chunks,
+ 0, rep_delta_chunk_t*);
+ const rep_delta_chunk_t *const chunk
+ = APR_ARRAY_IDX(rep->contents.delta.chunks,
+ cur_chunk, rep_delta_chunk_t*);
+
+ /* Verify that this chunk is of the same version as the first. */
+ if (first_chunk->version != chunk->version)
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Diff version inconsistencies in representation '%s'"),
+ rep_key);
+
+ rep_key = chunk->rep_key;
+ APR_ARRAY_PUSH(reps, representation_t *) = rep;
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key,
+ trail, pool));
+ }
+ while (rep->kind == rep_kind_delta
+ && rep->contents.delta.chunks->nelts > cur_chunk);
+
+ /* Right. We've either just read the fulltext rep, or a rep that's
+ too short, in which case we'll undeltify without source data.*/
+ if (rep->kind != rep_kind_delta && rep->kind != rep_kind_fulltext)
+ return UNKNOWN_NODE_KIND(rep_key);
+
+ if (rep->kind == rep_kind_delta)
+ rep = NULL; /* Don't use source data */
+
+ err = rep_undeltify_range(fs, reps, rep, cur_chunk, buf,
+ chunk_offset, len, trail, pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_CORRUPT)
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, err,
+ _("Corruption detected whilst reading delta chain from "
+ "representation '%s' to '%s'"), first_rep_key, rep_key);
+ else
+ return svn_error_trace(err);
+ }
+ }
+ }
+ else /* unknown kind */
+ return UNKNOWN_NODE_KIND(rep_key);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__get_mutable_rep(const char **new_rep_key,
+ const char *rep_key,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep = NULL;
+ const char *new_str = NULL;
+
+ /* We were passed an existing REP_KEY, so examine it. If it is
+ mutable already, then just return REP_KEY as the mutable result
+ key. */
+ if (rep_key && (rep_key[0] != '\0'))
+ {
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+ if (rep_is_mutable(rep, txn_id))
+ {
+ *new_rep_key = rep_key;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Either we weren't provided a base key to examine, or the base key
+ we were provided was not mutable. So, let's make a new
+ representation and return its key to the caller. */
+ SVN_ERR(svn_fs_bdb__string_append(fs, &new_str, 0, NULL, trail, pool));
+ rep = make_fulltext_rep(new_str, txn_id,
+ svn_checksum_empty_checksum(svn_checksum_md5,
+ pool),
+ svn_checksum_empty_checksum(svn_checksum_sha1,
+ pool),
+ pool);
+ return svn_fs_bdb__write_new_rep(new_rep_key, fs, rep, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__delete_rep_if_mutable(svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+ if (! rep_is_mutable(rep, txn_id))
+ return SVN_NO_ERROR;
+
+ if (rep->kind == rep_kind_fulltext)
+ {
+ SVN_ERR(svn_fs_bdb__string_delete(fs,
+ rep->contents.fulltext.string_key,
+ trail, pool));
+ }
+ else if (rep->kind == rep_kind_delta)
+ {
+ apr_array_header_t *keys;
+ SVN_ERR(delta_string_keys(&keys, rep, pool));
+ SVN_ERR(delete_strings(keys, fs, trail, pool));
+ }
+ else /* unknown kind */
+ return UNKNOWN_NODE_KIND(rep_key);
+
+ return svn_fs_bdb__delete_rep(fs, rep_key, trail, pool);
+}
+
+
+
+/*** Reading and writing data via representations. ***/
+
+/** Reading. **/
+
+struct rep_read_baton
+{
+ /* The FS from which we're reading. */
+ svn_fs_t *fs;
+
+ /* The representation skel whose contents we want to read. If this
+ is NULL, the rep has never had any contents, so all reads fetch 0
+ bytes.
+
+ Formerly, we cached the entire rep skel here, not just the key.
+ That way we didn't have to fetch the rep from the db every time
+ we want to read a little bit more of the file. Unfortunately,
+ this has a problem: if, say, a file's representation changes
+ while we're reading (changes from fulltext to delta, for
+ example), we'll never know it. So for correctness, we now
+ refetch the representation skel every time we want to read
+ another chunk. */
+ const char *rep_key;
+
+ /* How many bytes have been read already. */
+ svn_filesize_t offset;
+
+ /* If present, the read will be done as part of this trail, and the
+ trail's pool will be used. Otherwise, see `pool' below. */
+ trail_t *trail;
+
+ /* MD5 checksum context. Initialized when the baton is created, updated as
+ we read data, and finalized when the stream is closed. */
+ svn_checksum_ctx_t *md5_checksum_ctx;
+
+ /* Final resting place of the checksum created by md5_checksum_cxt. */
+ svn_checksum_t *md5_checksum;
+
+ /* SHA1 checksum context. Initialized when the baton is created, updated as
+ we read data, and finalized when the stream is closed. */
+ svn_checksum_ctx_t *sha1_checksum_ctx;
+
+ /* Final resting place of the checksum created by sha1_checksum_cxt. */
+ svn_checksum_t *sha1_checksum;
+
+ /* The length of the rep's contents (as fulltext, that is,
+ independent of how the rep actually stores the data.) This is
+ retrieved when the baton is created, and used to determine when
+ we have read the last byte, at which point we compare checksums.
+
+ Getting this at baton creation time makes interleaved reads and
+ writes on the same rep in the same trail impossible. But we're
+ not doing that, and probably no one ever should. And anyway if
+ they do, they should see problems immediately. */
+ svn_filesize_t size;
+
+ /* Set to FALSE when the baton is created, TRUE when the checksum_ctx
+ is digestified. */
+ svn_boolean_t checksum_finalized;
+
+ /* Used for temporary allocations. This pool is cleared at the
+ start of each invocation of the relevant stream read function --
+ see rep_read_contents(). */
+ apr_pool_t *scratch_pool;
+
+};
+
+
+static svn_error_t *
+rep_read_get_baton(struct rep_read_baton **rb_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ svn_boolean_t use_trail_for_reads,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ struct rep_read_baton *b;
+
+ b = apr_pcalloc(pool, sizeof(*b));
+ b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+ b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
+
+ if (rep_key)
+ SVN_ERR(svn_fs_base__rep_contents_size(&(b->size), fs, rep_key,
+ trail, pool));
+ else
+ b->size = 0;
+
+ b->checksum_finalized = FALSE;
+ b->fs = fs;
+ b->trail = use_trail_for_reads ? trail : NULL;
+ b->scratch_pool = svn_pool_create(pool);
+ b->rep_key = rep_key;
+ b->offset = 0;
+
+ *rb_p = b;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Retrieving data. ***/
+
+svn_error_t *
+svn_fs_base__rep_contents_size(svn_filesize_t *size_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+
+ if (rep->kind == rep_kind_fulltext)
+ {
+ /* Get the size by asking Berkeley for the string's length. */
+ SVN_ERR(svn_fs_bdb__string_size(size_p, fs,
+ rep->contents.fulltext.string_key,
+ trail, pool));
+ }
+ else if (rep->kind == rep_kind_delta)
+ {
+ /* Get the size by finding the last window pkg in the delta and
+ adding its offset to its size. This way, we won't even be
+ messed up by overlapping windows, as long as the window pkgs
+ are still ordered. */
+ apr_array_header_t *chunks = rep->contents.delta.chunks;
+ rep_delta_chunk_t *last_chunk;
+
+ SVN_ERR_ASSERT(chunks->nelts);
+
+ last_chunk = APR_ARRAY_IDX(chunks, chunks->nelts - 1,
+ rep_delta_chunk_t *);
+ *size_p = last_chunk->offset + last_chunk->size;
+ }
+ else /* unknown kind */
+ return UNKNOWN_NODE_KIND(rep_key);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__rep_contents_checksums(svn_checksum_t **md5_checksum,
+ svn_checksum_t **sha1_checksum,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+ if (md5_checksum)
+ *md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
+ if (sha1_checksum)
+ *sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__rep_contents(svn_string_t *str,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_filesize_t contents_size;
+ apr_size_t len;
+ char *data;
+
+ SVN_ERR(svn_fs_base__rep_contents_size(&contents_size, fs, rep_key,
+ trail, pool));
+
+ /* What if the contents are larger than we can handle? */
+ if (contents_size > SVN_MAX_OBJECT_SIZE)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Rep contents are too large: "
+ "got %s, limit is %s"),
+ apr_psprintf(pool, "%" SVN_FILESIZE_T_FMT, contents_size),
+ apr_psprintf(pool, "%" APR_SIZE_T_FMT, SVN_MAX_OBJECT_SIZE));
+ else
+ str->len = (apr_size_t) contents_size;
+
+ data = apr_palloc(pool, str->len);
+ str->data = data;
+ len = str->len;
+ SVN_ERR(rep_read_range(fs, rep_key, 0, data, &len, trail, pool));
+
+ /* Paranoia. */
+ if (len != str->len)
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Failure reading representation '%s'"), rep_key);
+
+ /* Just the standard paranoia. */
+ {
+ representation_t *rep;
+ svn_checksum_t *checksum, *rep_checksum;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+ rep_checksum = rep->sha1_checksum ? rep->sha1_checksum : rep->md5_checksum;
+ SVN_ERR(svn_checksum(&checksum, rep_checksum->kind, str->data, str->len,
+ pool));
+
+ if (! svn_checksum_match(checksum, rep_checksum))
+ return svn_error_create(SVN_ERR_FS_CORRUPT,
+ svn_checksum_mismatch_err(rep_checksum, checksum, pool,
+ _("Checksum mismatch on representation '%s'"),
+ rep_key),
+ NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct read_rep_args
+{
+ struct rep_read_baton *rb; /* The data source. */
+ char *buf; /* Where to put what we read. */
+ apr_size_t *len; /* How much to read / was read. */
+};
+
+
+/* BATON is of type `read_rep_args':
+
+ Read into BATON->rb->buf the *(BATON->len) bytes starting at
+ BATON->rb->offset from the data represented at BATON->rb->rep_key
+ in BATON->rb->fs, as part of TRAIL.
+
+ Afterwards, *(BATON->len) is the number of bytes actually read, and
+ BATON->rb->offset is incremented by that amount.
+
+ If BATON->rb->rep_key is null, this is assumed to mean the file's
+ contents have no representation, i.e., the file has no contents.
+ In that case, if BATON->rb->offset > 0, return the error
+ SVN_ERR_FS_FILE_CONTENTS_CHANGED, else just set *(BATON->len) to
+ zero and return. */
+static svn_error_t *
+txn_body_read_rep(void *baton, trail_t *trail)
+{
+ struct read_rep_args *args = baton;
+
+ if (args->rb->rep_key)
+ {
+ SVN_ERR(rep_read_range(args->rb->fs,
+ args->rb->rep_key,
+ args->rb->offset,
+ args->buf,
+ args->len,
+ trail,
+ args->rb->scratch_pool));
+
+ args->rb->offset += *(args->len);
+
+ /* We calculate the checksum just once, the moment we see the
+ * last byte of data. But we can't assume there was a short
+ * read. The caller may have known the length of the data and
+ * requested exactly that amount, so there would never be a
+ * short read. (That's why the read baton has to know the
+ * length of the data in advance.)
+ *
+ * On the other hand, some callers invoke the stream reader in a
+ * loop whose termination condition is that the read returned
+ * zero bytes of data -- which usually results in the read
+ * function being called one more time *after* the call that got
+ * a short read (indicating end-of-stream).
+ *
+ * The conditions below ensure that we compare checksums even
+ * when there is no short read associated with the last byte of
+ * data, while also ensuring that it's harmless to repeatedly
+ * read 0 bytes from the stream.
+ */
+ if (! args->rb->checksum_finalized)
+ {
+ SVN_ERR(svn_checksum_update(args->rb->md5_checksum_ctx, args->buf,
+ *(args->len)));
+ SVN_ERR(svn_checksum_update(args->rb->sha1_checksum_ctx, args->buf,
+ *(args->len)));
+
+ if (args->rb->offset == args->rb->size)
+ {
+ representation_t *rep;
+
+ SVN_ERR(svn_checksum_final(&args->rb->md5_checksum,
+ args->rb->md5_checksum_ctx,
+ trail->pool));
+ SVN_ERR(svn_checksum_final(&args->rb->sha1_checksum,
+ args->rb->sha1_checksum_ctx,
+ trail->pool));
+ args->rb->checksum_finalized = TRUE;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, args->rb->fs,
+ args->rb->rep_key,
+ trail, trail->pool));
+
+ if (rep->md5_checksum
+ && (! svn_checksum_match(rep->md5_checksum,
+ args->rb->md5_checksum)))
+ return svn_error_create(SVN_ERR_FS_CORRUPT,
+ svn_checksum_mismatch_err(rep->md5_checksum,
+ args->rb->sha1_checksum, trail->pool,
+ _("MD5 checksum mismatch on representation '%s'"),
+ args->rb->rep_key),
+ NULL);
+
+ if (rep->sha1_checksum
+ && (! svn_checksum_match(rep->sha1_checksum,
+ args->rb->sha1_checksum)))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT,
+ svn_checksum_mismatch_err(rep->sha1_checksum,
+ args->rb->sha1_checksum, trail->pool,
+ _("SHA1 checksum mismatch on representation '%s'"),
+ args->rb->rep_key),
+ NULL);
+ }
+ }
+ }
+ else if (args->rb->offset > 0)
+ {
+ return
+ svn_error_create
+ (SVN_ERR_FS_REP_CHANGED, NULL,
+ _("Null rep, but offset past zero already"));
+ }
+ else
+ *(args->len) = 0;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+rep_read_contents(void *baton, char *buf, apr_size_t *len)
+{
+ struct rep_read_baton *rb = baton;
+ struct read_rep_args args;
+
+ /* Clear the scratch pool of the results of previous invocations. */
+ svn_pool_clear(rb->scratch_pool);
+
+ args.rb = rb;
+ args.buf = buf;
+ args.len = len;
+
+ /* If we got a trail, use it; else make one. */
+ if (rb->trail)
+ SVN_ERR(txn_body_read_rep(&args, rb->trail));
+ else
+ {
+ /* In the case of reading from the db, any returned data should
+ live in our pre-allocated buffer, so the whole operation can
+ happen within a single malloc/free cycle. This prevents us
+ from creating millions of unnecessary trail subpools when
+ reading a big file. */
+ SVN_ERR(svn_fs_base__retry_txn(rb->fs,
+ txn_body_read_rep,
+ &args,
+ TRUE,
+ rb->scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/** Writing. **/
+
+
+struct rep_write_baton
+{
+ /* The FS in which we're writing. */
+ svn_fs_t *fs;
+
+ /* The representation skel whose contents we want to write. */
+ const char *rep_key;
+
+ /* The transaction id under which this write action will take
+ place. */
+ const char *txn_id;
+
+ /* If present, do the write as part of this trail, and use trail's
+ pool. Otherwise, see `pool' below. */
+ trail_t *trail;
+
+ /* SHA1 and MD5 checksums. Initialized when the baton is created,
+ updated as we write data, and finalized and stored when the
+ stream is closed. */
+ svn_checksum_ctx_t *md5_checksum_ctx;
+ svn_checksum_t *md5_checksum;
+ svn_checksum_ctx_t *sha1_checksum_ctx;
+ svn_checksum_t *sha1_checksum;
+ svn_boolean_t finalized;
+
+ /* Used for temporary allocations, iff `trail' (above) is null. */
+ apr_pool_t *pool;
+
+};
+
+
+static struct rep_write_baton *
+rep_write_get_baton(svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ struct rep_write_baton *b;
+
+ b = apr_pcalloc(pool, sizeof(*b));
+ b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+ b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
+ b->fs = fs;
+ b->trail = trail;
+ b->pool = pool;
+ b->rep_key = rep_key;
+ b->txn_id = txn_id;
+ return b;
+}
+
+
+
+/* Write LEN bytes from BUF into the end of the string represented via
+ REP_KEY in FS, as part of TRAIL. If the representation is not
+ mutable, return the error SVN_FS_REP_NOT_MUTABLE. */
+static svn_error_t *
+rep_write(svn_fs_t *fs,
+ const char *rep_key,
+ const char *buf,
+ apr_size_t len,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+
+ if (! rep_is_mutable(rep, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_REP_NOT_MUTABLE, NULL,
+ _("Rep '%s' is not mutable"), rep_key);
+
+ if (rep->kind == rep_kind_fulltext)
+ {
+ SVN_ERR(svn_fs_bdb__string_append
+ (fs, &(rep->contents.fulltext.string_key), len, buf,
+ trail, pool));
+ }
+ else if (rep->kind == rep_kind_delta)
+ {
+ /* There should never be a case when we have a mutable
+ non-fulltext rep. The only code that creates mutable reps is
+ in this file, and it creates them fulltext. */
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Rep '%s' both mutable and non-fulltext"), rep_key);
+ }
+ else /* unknown kind */
+ return UNKNOWN_NODE_KIND(rep_key);
+
+ return SVN_NO_ERROR;
+}
+
+
+struct write_rep_args
+{
+ struct rep_write_baton *wb; /* Destination. */
+ const char *buf; /* Data. */
+ apr_size_t len; /* How much to write. */
+};
+
+
+/* BATON is of type `write_rep_args':
+ Append onto BATON->wb->rep_key's contents BATON->len bytes of
+ data from BATON->wb->buf, in BATON->rb->fs, as part of TRAIL.
+
+ If the representation is not mutable, return the error
+ SVN_FS_REP_NOT_MUTABLE. */
+static svn_error_t *
+txn_body_write_rep(void *baton, trail_t *trail)
+{
+ struct write_rep_args *args = baton;
+
+ SVN_ERR(rep_write(args->wb->fs,
+ args->wb->rep_key,
+ args->buf,
+ args->len,
+ args->wb->txn_id,
+ trail,
+ trail->pool));
+ SVN_ERR(svn_checksum_update(args->wb->md5_checksum_ctx,
+ args->buf, args->len));
+ SVN_ERR(svn_checksum_update(args->wb->sha1_checksum_ctx,
+ args->buf, args->len));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+rep_write_contents(void *baton,
+ const char *buf,
+ apr_size_t *len)
+{
+ struct rep_write_baton *wb = baton;
+ struct write_rep_args args;
+
+ /* We toss LEN's indirectness because if not all the bytes are
+ written, it's an error, so we wouldn't be reporting anything back
+ through *LEN anyway. */
+ args.wb = wb;
+ args.buf = buf;
+ args.len = *len;
+
+ /* If we got a trail, use it; else make one. */
+ if (wb->trail)
+ SVN_ERR(txn_body_write_rep(&args, wb->trail));
+ else
+ {
+ /* In the case of simply writing the rep to the db, we're
+ *certain* that there's no data coming back to us that needs
+ to be preserved... so the whole operation can happen within a
+ single malloc/free cycle. This prevents us from creating
+ millions of unnecessary trail subpools when writing a big
+ file. */
+ SVN_ERR(svn_fs_base__retry_txn(wb->fs,
+ txn_body_write_rep,
+ &args,
+ TRUE,
+ wb->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for rep_write_close_contents(); see that doc string for
+ more. BATON is of type `struct rep_write_baton'. */
+static svn_error_t *
+txn_body_write_close_rep(void *baton, trail_t *trail)
+{
+ struct rep_write_baton *wb = baton;
+ representation_t *rep;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, wb->fs, wb->rep_key,
+ trail, trail->pool));
+ rep->md5_checksum = svn_checksum_dup(wb->md5_checksum, trail->pool);
+ rep->sha1_checksum = svn_checksum_dup(wb->sha1_checksum, trail->pool);
+ return svn_fs_bdb__write_rep(wb->fs, wb->rep_key, rep,
+ trail, trail->pool);
+}
+
+
+/* BATON is of type `struct rep_write_baton'.
+ *
+ * Finalize BATON->md5_context and store the resulting digest under
+ * BATON->rep_key.
+ */
+static svn_error_t *
+rep_write_close_contents(void *baton)
+{
+ struct rep_write_baton *wb = baton;
+
+ /* ### Thought: if we fixed apr-util MD5 contexts to allow repeated
+ digestification, then we wouldn't need a stream close function at
+ all -- instead, we could update the stored checksum each time a
+ write occurred, which would have the added advantage of making
+ interleaving reads and writes work. Currently, they'd fail with
+ a checksum mismatch, it just happens that our code never tries to
+ do that anyway. */
+
+ if (! wb->finalized)
+ {
+ SVN_ERR(svn_checksum_final(&wb->md5_checksum, wb->md5_checksum_ctx,
+ wb->pool));
+ SVN_ERR(svn_checksum_final(&wb->sha1_checksum, wb->sha1_checksum_ctx,
+ wb->pool));
+ wb->finalized = TRUE;
+ }
+
+ /* If we got a trail, use it; else make one. */
+ if (wb->trail)
+ return txn_body_write_close_rep(wb, wb->trail);
+ else
+ /* We need to keep our trail pool around this time so the
+ checksums we've calculated survive. */
+ return svn_fs_base__retry_txn(wb->fs, txn_body_write_close_rep,
+ wb, FALSE, wb->pool);
+}
+
+
+/** Public read and write stream constructors. **/
+
+svn_error_t *
+svn_fs_base__rep_contents_read_stream(svn_stream_t **rs_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ svn_boolean_t use_trail_for_reads,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ struct rep_read_baton *rb;
+
+ SVN_ERR(rep_read_get_baton(&rb, fs, rep_key, use_trail_for_reads,
+ trail, pool));
+ *rs_p = svn_stream_create(rb, pool);
+ svn_stream_set_read(*rs_p, rep_read_contents);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Clear the contents of REP_KEY, so that it represents the empty
+ string, as part of TRAIL. TXN_ID is the id of the Subversion
+ transaction under which this occurs. If REP_KEY is not mutable,
+ return the error SVN_ERR_FS_REP_NOT_MUTABLE. */
+static svn_error_t *
+rep_contents_clear(svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+ const char *str_key;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+
+ /* Make sure it's mutable. */
+ if (! rep_is_mutable(rep, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_REP_NOT_MUTABLE, NULL,
+ _("Rep '%s' is not mutable"), rep_key);
+
+ SVN_ERR_ASSERT(rep->kind == rep_kind_fulltext);
+
+ /* If rep has no string, just return success. Else, clear the
+ underlying string. */
+ str_key = rep->contents.fulltext.string_key;
+ if (str_key && *str_key)
+ {
+ SVN_ERR(svn_fs_bdb__string_clear(fs, str_key, trail, pool));
+ rep->md5_checksum = NULL;
+ rep->sha1_checksum = NULL;
+ SVN_ERR(svn_fs_bdb__write_rep(fs, rep_key, rep, trail, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__rep_contents_write_stream(svn_stream_t **ws_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ svn_boolean_t use_trail_for_writes,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ struct rep_write_baton *wb;
+
+ /* Clear the current rep contents (free mutability check!). */
+ SVN_ERR(rep_contents_clear(fs, rep_key, txn_id, trail, pool));
+
+ /* Now, generate the write baton and stream. */
+ wb = rep_write_get_baton(fs, rep_key, txn_id,
+ use_trail_for_writes ? trail : NULL, pool);
+ *ws_p = svn_stream_create(wb, pool);
+ svn_stream_set_write(*ws_p, rep_write_contents);
+ svn_stream_set_close(*ws_p, rep_write_close_contents);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Deltified storage. ***/
+
+/* Baton for svn_write_fn_t write_string_set(). */
+struct write_svndiff_strings_baton
+{
+ /* The fs where lives the string we're writing. */
+ svn_fs_t *fs;
+
+ /* The key of the string we're writing to. Typically this is
+ initialized to NULL, so svn_fs_base__string_append() can fill in a
+ value. */
+ const char *key;
+
+ /* The amount of txdelta data written to the current
+ string-in-progress. */
+ apr_size_t size;
+
+ /* The amount of svndiff header information we've written thus far
+ to the strings table. */
+ apr_size_t header_read;
+
+ /* The version number of the svndiff data written. ### You'd better
+ not count on this being populated after the first chunk is sent
+ through the interface, since it lives at the 4th byte of the
+ stream. */
+ apr_byte_t version;
+
+ /* The trail we're writing in. */
+ trail_t *trail;
+
+};
+
+
+/* Function of type `svn_write_fn_t', for writing to a collection of
+ strings; BATON is `struct write_svndiff_strings_baton *'.
+
+ On the first call, BATON->key is null. A new string key in
+ BATON->fs is chosen and stored in BATON->key; each call appends
+ *LEN bytes from DATA onto the string. *LEN is never changed; if
+ the write fails to write all *LEN bytes, an error is returned.
+ BATON->size is used to track the total amount of data written via
+ this handler, and must be reset by the caller to 0 when appropriate. */
+static svn_error_t *
+write_svndiff_strings(void *baton, const char *data, apr_size_t *len)
+{
+ struct write_svndiff_strings_baton *wb = baton;
+ const char *buf = data;
+ apr_size_t nheader = 0;
+
+ /* If we haven't stripped all the header information from this
+ stream yet, keep stripping. If someone sends a first window
+ through here that's shorter than 4 bytes long, this will probably
+ cause a nuclear reactor meltdown somewhere in the American
+ midwest. */
+ if (wb->header_read < 4)
+ {
+ nheader = 4 - wb->header_read;
+ *len -= nheader;
+ buf += nheader;
+ wb->header_read += nheader;
+
+ /* If we have *now* read the full 4-byte header, check that
+ least byte for the version number of the svndiff format. */
+ if (wb->header_read == 4)
+ wb->version = *(buf - 1);
+ }
+
+ /* Append to the current string we're writing (or create a new one
+ if WB->key is NULL). */
+ SVN_ERR(svn_fs_bdb__string_append(wb->fs, &(wb->key), *len,
+ buf, wb->trail, wb->trail->pool));
+
+ /* Make sure we (still) have a key. */
+ if (wb->key == NULL)
+ return svn_error_create(SVN_ERR_FS_GENERAL, NULL,
+ _("Failed to get new string key"));
+
+ /* Restore *LEN to the value it *would* have been were it not for
+ header stripping. */
+ *len += nheader;
+
+ /* Increment our running total of bytes written to this string. */
+ wb->size += *len;
+
+ return SVN_NO_ERROR;
+}
+
+
+typedef struct window_write_t
+{
+ const char *key; /* string key for this window */
+ apr_size_t svndiff_len; /* amount of svndiff data written to the string */
+ svn_filesize_t text_off; /* offset of fulltext represented by this window */
+ apr_size_t text_len; /* amount of fulltext data represented by this window */
+
+} window_write_t;
+
+
+svn_error_t *
+svn_fs_base__rep_deltify(svn_fs_t *fs,
+ const char *target,
+ const char *source,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ svn_stream_t *source_stream; /* stream to read the source */
+ svn_stream_t *target_stream; /* stream to read the target */
+ svn_txdelta_stream_t *txdelta_stream; /* stream to read delta windows */
+
+ /* window-y things, and an array to track them */
+ window_write_t *ww;
+ apr_array_header_t *windows;
+
+ /* stream to write new (deltified) target data and its baton */
+ svn_stream_t *new_target_stream;
+ struct write_svndiff_strings_baton new_target_baton;
+
+ /* window handler/baton for writing to above stream */
+ svn_txdelta_window_handler_t new_target_handler;
+ void *new_target_handler_baton;
+
+ /* yes, we do windows */
+ svn_txdelta_window_t *window;
+
+ /* The current offset into the fulltext that our window is about to
+ write. This doubles, after all windows are written, as the
+ total size of the svndiff data for the deltification process. */
+ svn_filesize_t tview_off = 0;
+
+ /* The total amount of diff data written while deltifying. */
+ svn_filesize_t diffsize = 0;
+
+ /* TARGET's original string keys */
+ apr_array_header_t *orig_str_keys;
+
+ /* The checksums for the representation's fulltext contents. */
+ svn_checksum_t *rep_md5_checksum;
+ svn_checksum_t *rep_sha1_checksum;
+
+ /* MD5 digest */
+ const unsigned char *digest;
+
+ /* pool for holding the windows */
+ apr_pool_t *wpool;
+
+ /* Paranoia: never allow a rep to be deltified against itself,
+ because then there would be no fulltext reachable in the delta
+ chain, and badness would ensue. */
+ if (strcmp(target, source) == 0)
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Attempt to deltify '%s' against itself"),
+ target);
+
+ /* Set up a handler for the svndiff data, which will write each
+ window to its own string in the `strings' table. */
+ new_target_baton.fs = fs;
+ new_target_baton.trail = trail;
+ new_target_baton.header_read = FALSE;
+ new_target_stream = svn_stream_create(&new_target_baton, pool);
+ svn_stream_set_write(new_target_stream, write_svndiff_strings);
+
+ /* Get streams to our source and target text data. */
+ SVN_ERR(svn_fs_base__rep_contents_read_stream(&source_stream, fs, source,
+ TRUE, trail, pool));
+ SVN_ERR(svn_fs_base__rep_contents_read_stream(&target_stream, fs, target,
+ TRUE, trail, pool));
+
+ /* Setup a stream to convert the textdelta data into svndiff windows. */
+ svn_txdelta2(&txdelta_stream, source_stream, target_stream, TRUE, pool);
+
+ if (bfd->format >= SVN_FS_BASE__MIN_SVNDIFF1_FORMAT)
+ svn_txdelta_to_svndiff3(&new_target_handler, &new_target_handler_baton,
+ new_target_stream, 1,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+ else
+ svn_txdelta_to_svndiff3(&new_target_handler, &new_target_handler_baton,
+ new_target_stream, 0,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+
+ /* subpool for the windows */
+ wpool = svn_pool_create(pool);
+
+ /* Now, loop, manufacturing and dispatching windows of svndiff data. */
+ windows = apr_array_make(pool, 1, sizeof(ww));
+ do
+ {
+ /* Reset some baton variables. */
+ new_target_baton.size = 0;
+ new_target_baton.key = NULL;
+
+ /* Free the window. */
+ svn_pool_clear(wpool);
+
+ /* Fetch the next window of txdelta data. */
+ SVN_ERR(svn_txdelta_next_window(&window, txdelta_stream, wpool));
+
+ /* Send off this package to be written as svndiff data. */
+ SVN_ERR(new_target_handler(window, new_target_handler_baton));
+ if (window)
+ {
+ /* Add a new window description to our array. */
+ ww = apr_pcalloc(pool, sizeof(*ww));
+ ww->key = new_target_baton.key;
+ ww->svndiff_len = new_target_baton.size;
+ ww->text_off = tview_off;
+ ww->text_len = window->tview_len;
+ APR_ARRAY_PUSH(windows, window_write_t *) = ww;
+
+ /* Update our recordkeeping variables. */
+ tview_off += window->tview_len;
+ diffsize += ww->svndiff_len;
+ }
+
+ } while (window);
+
+ svn_pool_destroy(wpool);
+
+ /* Having processed all the windows, we can query the MD5 digest
+ from the stream. */
+ digest = svn_txdelta_md5_digest(txdelta_stream);
+ if (! digest)
+ return svn_error_createf
+ (SVN_ERR_DELTA_MD5_CHECKSUM_ABSENT, NULL,
+ _("Failed to calculate MD5 digest for '%s'"),
+ source);
+
+ /* Construct a list of the strings used by the old representation so
+ that we can delete them later. While we are here, if the old
+ representation was a fulltext, check to make sure the delta we're
+ replacing it with is actually smaller. (Don't perform this check
+ if we're replacing a delta; in that case, we're going for a time
+ optimization, not a space optimization.) */
+ {
+ representation_t *old_rep;
+ const char *str_key;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&old_rep, fs, target, trail, pool));
+ if (old_rep->kind == rep_kind_fulltext)
+ {
+ svn_filesize_t old_size = 0;
+
+ str_key = old_rep->contents.fulltext.string_key;
+ SVN_ERR(svn_fs_bdb__string_size(&old_size, fs, str_key,
+ trail, pool));
+ orig_str_keys = apr_array_make(pool, 1, sizeof(str_key));
+ APR_ARRAY_PUSH(orig_str_keys, const char *) = str_key;
+
+ /* If the new data is NOT an space optimization, destroy the
+ string(s) we created, and get outta here. */
+ if (diffsize >= old_size)
+ {
+ int i;
+ for (i = 0; i < windows->nelts; i++)
+ {
+ ww = APR_ARRAY_IDX(windows, i, window_write_t *);
+ SVN_ERR(svn_fs_bdb__string_delete(fs, ww->key, trail, pool));
+ }
+ return SVN_NO_ERROR;
+ }
+ }
+ else if (old_rep->kind == rep_kind_delta)
+ SVN_ERR(delta_string_keys(&orig_str_keys, old_rep, pool));
+ else /* unknown kind */
+ return UNKNOWN_NODE_KIND(target);
+
+ /* Save the checksums, since the new rep needs them. */
+ rep_md5_checksum = svn_checksum_dup(old_rep->md5_checksum, pool);
+ rep_sha1_checksum = svn_checksum_dup(old_rep->sha1_checksum, pool);
+ }
+
+ /* Hook the new strings we wrote into the rest of the filesystem by
+ building a new representation to replace our old one. */
+ {
+ representation_t new_rep;
+ rep_delta_chunk_t *chunk;
+ apr_array_header_t *chunks;
+ int i;
+
+ new_rep.kind = rep_kind_delta;
+ new_rep.txn_id = NULL;
+
+ /* Migrate the old rep's checksums to the new rep. */
+ new_rep.md5_checksum = svn_checksum_dup(rep_md5_checksum, pool);
+ new_rep.sha1_checksum = svn_checksum_dup(rep_sha1_checksum, pool);
+
+ chunks = apr_array_make(pool, windows->nelts, sizeof(chunk));
+
+ /* Loop through the windows we wrote, creating and adding new
+ chunks to the representation. */
+ for (i = 0; i < windows->nelts; i++)
+ {
+ ww = APR_ARRAY_IDX(windows, i, window_write_t *);
+
+ /* Allocate a chunk and its window */
+ chunk = apr_palloc(pool, sizeof(*chunk));
+ chunk->offset = ww->text_off;
+
+ /* Populate the window */
+ chunk->version = new_target_baton.version;
+ chunk->string_key = ww->key;
+ chunk->size = ww->text_len;
+ chunk->rep_key = source;
+
+ /* Add this chunk to the array. */
+ APR_ARRAY_PUSH(chunks, rep_delta_chunk_t *) = chunk;
+ }
+
+ /* Put the chunks array into the representation. */
+ new_rep.contents.delta.chunks = chunks;
+
+ /* Write out the new representation. */
+ SVN_ERR(svn_fs_bdb__write_rep(fs, target, &new_rep, trail, pool));
+
+ /* Delete the original pre-deltified strings. */
+ SVN_ERR(delete_strings(orig_str_keys, fs, trail, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/reps-strings.h b/contrib/subversion/subversion/libsvn_fs_base/reps-strings.h
new file mode 100644
index 0000000..475af0c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/reps-strings.h
@@ -0,0 +1,176 @@
+/* reps-strings.h : interpreting representations with respect to strings
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_REPS_STRINGS_H
+#define SVN_LIBSVN_FS_REPS_STRINGS_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_io.h"
+#include "svn_fs.h"
+
+#include "trail.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* Get or create a mutable representation in FS, and set *NEW_REP_KEY to its
+ key.
+
+ TXN_ID is the id of the Subversion transaction under which this occurs.
+
+ If REP_KEY is not null and is already a mutable representation, set
+ *NEW_REP_KEY to REP_KEY, else create a brand new rep and set *NEW_REP_KEY
+ to its key, allocated in POOL. */
+svn_error_t *svn_fs_base__get_mutable_rep(const char **new_rep_key,
+ const char *rep_key,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete REP_KEY from FS if REP_KEY is mutable, as part of trail, or
+ do nothing if REP_KEY is immutable. If a mutable rep is deleted,
+ the string it refers to is deleted as well. TXN_ID is the id of
+ the Subversion transaction under which this occurs.
+
+ If no such rep, return SVN_ERR_FS_NO_SUCH_REPRESENTATION. */
+svn_error_t *svn_fs_base__delete_rep_if_mutable(svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+
+/*** Reading and writing rep contents. ***/
+
+/* Set *SIZE_P to the size of REP_KEY's contents in FS, as part of TRAIL.
+ Note: this is the fulltext size, no matter how the contents are
+ represented in storage. */
+svn_error_t *svn_fs_base__rep_contents_size(svn_filesize_t *size_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* If MD5_CHECKSUM is non-NULL, set *MD5_CHECKSUM to the MD5 checksum
+ for REP_KEY in FS, as part of TRAIL.
+
+ If SHA1_CHECKSUM is non-NULL, set *SHA1_CHECKSUM to the SHA1
+ checksum for REP_KEY in FS, as part of TRAIL.
+
+ These are the prerecorded checksums for the rep's contents'
+ fulltext. If one or both of the checksums is not stored, do not
+ calculate one dynamically, just put NULL into the respective return
+ value. (By convention, the NULL checksum is considered to match
+ any checksum.) */
+svn_error_t *
+svn_fs_base__rep_contents_checksums(svn_checksum_t **md5_checksum,
+ svn_checksum_t **sha1_checksum,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set STR->data to the contents of REP_KEY in FS, and STR->len to the
+ contents' length, as part of TRAIL. The data is allocated in
+ POOL. If an error occurs, the effect on STR->data and
+ STR->len is undefined.
+
+ Note: this is the fulltext contents, no matter how the contents are
+ represented in storage. */
+svn_error_t *svn_fs_base__rep_contents(svn_string_t *str,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *RS_P to a stream to read the contents of REP_KEY in FS.
+ Allocate the stream in POOL.
+
+ REP_KEY may be null, in which case reads just return 0 bytes.
+
+ If USE_TRAIL_FOR_READS is TRUE, the stream's reads are part
+ of TRAIL; otherwise, each read happens in an internal, one-off
+ trail (though TRAIL is still required). POOL may be TRAIL->pool. */
+svn_error_t *
+svn_fs_base__rep_contents_read_stream(svn_stream_t **rs_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ svn_boolean_t use_trail_for_reads,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *WS_P to a stream to write the contents of REP_KEY. Allocate
+ the stream in POOL. TXN_ID is the id of the Subversion transaction
+ under which this occurs.
+
+ If USE_TRAIL_FOR_WRITES is TRUE, the stream's writes are part
+ of TRAIL; otherwise, each write happens in an internal, one-off
+ trail (though TRAIL is still required). POOL may be TRAIL->pool.
+
+ If REP_KEY is not mutable, writes to *WS_P will return the
+ error SVN_ERR_FS_REP_NOT_MUTABLE. */
+svn_error_t *
+svn_fs_base__rep_contents_write_stream(svn_stream_t **ws_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ svn_boolean_t use_trail_for_writes,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+/*** Deltified storage. ***/
+
+/* Offer TARGET the chance to store its contents as a delta against
+ SOURCE, in FS, as part of TRAIL. TARGET and SOURCE are both
+ representation keys.
+
+ This usually results in TARGET's data being stored as a diff
+ against SOURCE; but it might not, if it turns out to be more
+ efficient to store the contents some other way. */
+svn_error_t *svn_fs_base__rep_deltify(svn_fs_t *fs,
+ const char *target,
+ const char *source,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_REPS_STRINGS_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/revs-txns.c b/contrib/subversion/subversion/libsvn_fs_base/revs-txns.c
new file mode 100644
index 0000000..d218843
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/revs-txns.c
@@ -0,0 +1,1067 @@
+/* revs-txns.c : operations on revision and transactions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+
+#include <apr_tables.h>
+#include <apr_pools.h>
+
+#include "svn_pools.h"
+#include "svn_time.h"
+#include "svn_fs.h"
+#include "svn_props.h"
+#include "svn_hash.h"
+#include "svn_io.h"
+
+#include "fs.h"
+#include "dag.h"
+#include "err.h"
+#include "trail.h"
+#include "tree.h"
+#include "revs-txns.h"
+#include "key-gen.h"
+#include "id.h"
+#include "bdb/rev-table.h"
+#include "bdb/txn-table.h"
+#include "bdb/copies-table.h"
+#include "bdb/changes-table.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+#include "private/svn_fs_util.h"
+
+
+/*** Helpers ***/
+
+/* Set *txn_p to a transaction object allocated in POOL for the
+ transaction in FS whose id is TXN_ID. If EXPECT_DEAD is set, this
+ transaction must be a dead one, else an error is returned. If
+ EXPECT_DEAD is not set, the transaction must *not* be a dead one,
+ else an error is returned. */
+static svn_error_t *
+get_txn(transaction_t **txn_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ svn_boolean_t expect_dead,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+ SVN_ERR(svn_fs_bdb__get_txn(&txn, fs, txn_id, trail, pool));
+ if (expect_dead && (txn->kind != transaction_kind_dead))
+ return svn_error_createf(SVN_ERR_FS_TRANSACTION_NOT_DEAD, 0,
+ _("Transaction is not dead: '%s'"), txn_id);
+ if ((! expect_dead) && (txn->kind == transaction_kind_dead))
+ return svn_error_createf(SVN_ERR_FS_TRANSACTION_DEAD, 0,
+ _("Transaction is dead: '%s'"), txn_id);
+ *txn_p = txn;
+ return SVN_NO_ERROR;
+}
+
+
+/* This is only for symmetry with the get_txn() helper. */
+#define put_txn svn_fs_bdb__put_txn
+
+
+
+/*** Revisions ***/
+
+/* Return the committed transaction record *TXN_P and its ID *TXN_ID
+ (as long as those parameters aren't NULL) for the revision REV in
+ FS as part of TRAIL. */
+static svn_error_t *
+get_rev_txn(transaction_t **txn_p,
+ const char **txn_id,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ revision_t *revision;
+ transaction_t *txn;
+
+ SVN_ERR(svn_fs_bdb__get_rev(&revision, fs, rev, trail, pool));
+ if (revision->txn_id == NULL)
+ return svn_fs_base__err_corrupt_fs_revision(fs, rev);
+
+ SVN_ERR(get_txn(&txn, fs, revision->txn_id, FALSE, trail, pool));
+ if (txn->revision != rev)
+ return svn_fs_base__err_corrupt_txn(fs, revision->txn_id);
+
+ if (txn_p)
+ *txn_p = txn;
+ if (txn_id)
+ *txn_id = revision->txn_id;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__rev_get_root(const svn_fs_id_t **root_id_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR(get_rev_txn(&txn, NULL, fs, rev, trail, pool));
+ if (txn->root_id == NULL)
+ return svn_fs_base__err_corrupt_fs_revision(fs, rev);
+
+ *root_id_p = txn->root_id;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__rev_get_txn_id(const char **txn_id_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ revision_t *revision;
+
+ SVN_ERR(svn_fs_bdb__get_rev(&revision, fs, rev, trail, pool));
+ if (revision->txn_id == NULL)
+ return svn_fs_base__err_corrupt_fs_revision(fs, rev);
+
+ *txn_id_p = revision->txn_id;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+txn_body_youngest_rev(void *baton, trail_t *trail)
+{
+ return svn_fs_bdb__youngest_rev(baton, trail->fs, trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__youngest_rev(svn_revnum_t *youngest_p,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ svn_revnum_t youngest;
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_youngest_rev, &youngest,
+ TRUE, pool));
+ *youngest_p = youngest;
+ return SVN_NO_ERROR;
+}
+
+
+struct revision_proplist_args {
+ apr_hash_t **table_p;
+ svn_revnum_t rev;
+};
+
+
+static svn_error_t *
+txn_body_revision_proplist(void *baton, trail_t *trail)
+{
+ struct revision_proplist_args *args = baton;
+ transaction_t *txn;
+
+ SVN_ERR(get_rev_txn(&txn, NULL, trail->fs, args->rev, trail, trail->pool));
+ *(args->table_p) = txn->proplist;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__revision_proplist(apr_hash_t **table_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ struct revision_proplist_args args;
+ apr_hash_t *table;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.table_p = &table;
+ args.rev = rev;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args,
+ FALSE, pool));
+
+ *table_p = table ? table : apr_hash_make(pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__revision_prop(svn_string_t **value_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ struct revision_proplist_args args;
+ apr_hash_t *table;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* Get the proplist. */
+ args.table_p = &table;
+ args.rev = rev;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args,
+ FALSE, pool));
+
+ /* And then the prop from that list (if there was a list). */
+ *value_p = svn_hash_gets(table, propname);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__set_rev_prop(svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+ const char *txn_id;
+
+ SVN_ERR(get_rev_txn(&txn, &txn_id, fs, rev, trail, pool));
+
+ /* If there's no proplist, but we're just deleting a property, exit now. */
+ if ((! txn->proplist) && (! value))
+ return SVN_NO_ERROR;
+
+ /* Now, if there's no proplist, we know we need to make one. */
+ if (! txn->proplist)
+ txn->proplist = apr_hash_make(pool);
+
+ /* Set the property. */
+ if (old_value_p)
+ {
+ const svn_string_t *wanted_value = *old_value_p;
+ const svn_string_t *present_value = svn_hash_gets(txn->proplist, name);
+ if ((!wanted_value != !present_value)
+ || (wanted_value && present_value
+ && !svn_string_compare(wanted_value, present_value)))
+ {
+ /* What we expected isn't what we found. */
+ return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
+ _("revprop '%s' has unexpected value in "
+ "filesystem"),
+ name);
+ }
+ /* Fall through. */
+ }
+ svn_hash_sets(txn->proplist, name, value);
+
+ /* Overwrite the revision. */
+ return put_txn(fs, txn, txn_id, trail, pool);
+}
+
+
+struct change_rev_prop_args {
+ svn_revnum_t rev;
+ const char *name;
+ const svn_string_t *const *old_value_p;
+ const svn_string_t *value;
+};
+
+
+static svn_error_t *
+txn_body_change_rev_prop(void *baton, trail_t *trail)
+{
+ struct change_rev_prop_args *args = baton;
+
+ return svn_fs_base__set_rev_prop(trail->fs, args->rev,
+ args->name, args->old_value_p, args->value,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__change_rev_prop(svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct change_rev_prop_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.rev = rev;
+ args.name = name;
+ args.old_value_p = old_value_p;
+ args.value = value;
+ return svn_fs_base__retry_txn(fs, txn_body_change_rev_prop, &args,
+ TRUE, pool);
+}
+
+
+
+/*** Transactions ***/
+
+svn_error_t *
+svn_fs_base__txn_make_committed(svn_fs_t *fs,
+ const char *txn_name,
+ svn_revnum_t revision,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+
+ /* Make sure the TXN is not committed already. */
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ /* Convert TXN to a committed transaction. */
+ txn->base_id = NULL;
+ txn->revision = revision;
+ txn->kind = transaction_kind_committed;
+ return put_txn(fs, txn, txn_name, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__txn_get_revision(svn_revnum_t *revision,
+ svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ *revision = txn->revision;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__get_txn_ids(const svn_fs_id_t **root_id_p,
+ const svn_fs_id_t **base_root_id_p,
+ svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ *root_id_p = txn->root_id;
+ *base_root_id_p = txn->base_id;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__set_txn_root(svn_fs_t *fs,
+ const char *txn_name,
+ const svn_fs_id_t *new_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ if (! svn_fs_base__id_eq(txn->root_id, new_id))
+ {
+ txn->root_id = new_id;
+ SVN_ERR(put_txn(fs, txn, txn_name, trail, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__set_txn_base(svn_fs_t *fs,
+ const char *txn_name,
+ const svn_fs_id_t *new_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ if (! svn_fs_base__id_eq(txn->base_id, new_id))
+ {
+ txn->base_id = new_id;
+ SVN_ERR(put_txn(fs, txn, txn_name, trail, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__add_txn_copy(svn_fs_t *fs,
+ const char *txn_name,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ /* Get the transaction and ensure its mutability. */
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ /* Allocate a new array if this transaction has no copies. */
+ if (! txn->copies)
+ txn->copies = apr_array_make(pool, 1, sizeof(copy_id));
+
+ /* Add COPY_ID to the array. */
+ APR_ARRAY_PUSH(txn->copies, const char *) = copy_id;
+
+ /* Finally, write out the transaction. */
+ return put_txn(fs, txn, txn_name, trail, pool);
+}
+
+
+
+/* Generic transaction operations. */
+
+struct txn_proplist_args {
+ apr_hash_t **table_p;
+ const char *id;
+};
+
+
+static svn_error_t *
+txn_body_txn_proplist(void *baton, trail_t *trail)
+{
+ transaction_t *txn;
+ struct txn_proplist_args *args = baton;
+
+ SVN_ERR(get_txn(&txn, trail->fs, args->id, FALSE, trail, trail->pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(trail->fs, args->id);
+
+ *(args->table_p) = txn->proplist;
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_base__txn_proplist_in_trail(apr_hash_t **table_p,
+ const char *txn_id,
+ trail_t *trail)
+{
+ struct txn_proplist_args args;
+ apr_hash_t *table;
+
+ args.table_p = &table;
+ args.id = txn_id;
+ SVN_ERR(txn_body_txn_proplist(&args, trail));
+
+ *table_p = table ? table : apr_hash_make(trail->pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_base__txn_proplist(apr_hash_t **table_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ struct txn_proplist_args args;
+ apr_hash_t *table;
+ svn_fs_t *fs = txn->fs;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.table_p = &table;
+ args.id = txn->id;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args,
+ FALSE, pool));
+
+ *table_p = table ? table : apr_hash_make(pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__txn_prop(svn_string_t **value_p,
+ svn_fs_txn_t *txn,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ struct txn_proplist_args args;
+ apr_hash_t *table;
+ svn_fs_t *fs = txn->fs;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* Get the proplist. */
+ args.table_p = &table;
+ args.id = txn->id;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args,
+ FALSE, pool));
+
+ /* And then the prop from that list (if there was a list). */
+ *value_p = svn_hash_gets(table, propname);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+struct change_txn_prop_args {
+ svn_fs_t *fs;
+ const char *id;
+ const char *name;
+ const svn_string_t *value;
+};
+
+
+svn_error_t *
+svn_fs_base__set_txn_prop(svn_fs_t *fs,
+ const char *txn_name,
+ const char *name,
+ const svn_string_t *value,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ /* If there's no proplist, but we're just deleting a property, exit now. */
+ if ((! txn->proplist) && (! value))
+ return SVN_NO_ERROR;
+
+ /* Now, if there's no proplist, we know we need to make one. */
+ if (! txn->proplist)
+ txn->proplist = apr_hash_make(pool);
+
+ /* Set the property. */
+ svn_hash_sets(txn->proplist, name, value);
+
+ /* Now overwrite the transaction. */
+ return put_txn(fs, txn, txn_name, trail, pool);
+}
+
+
+static svn_error_t *
+txn_body_change_txn_prop(void *baton, trail_t *trail)
+{
+ struct change_txn_prop_args *args = baton;
+ return svn_fs_base__set_txn_prop(trail->fs, args->id, args->name,
+ args->value, trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__change_txn_prop(svn_fs_txn_t *txn,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct change_txn_prop_args args;
+ svn_fs_t *fs = txn->fs;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.id = txn->id;
+ args.name = name;
+ args.value = value;
+ return svn_fs_base__retry_txn(fs, txn_body_change_txn_prop, &args,
+ TRUE, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__change_txn_props(svn_fs_txn_t *txn,
+ const apr_array_header_t *props,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ for (i = 0; i < props->nelts; i++)
+ {
+ svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_fs_base__change_txn_prop(txn, prop->name,
+ prop->value, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Creating a transaction */
+
+static txn_vtable_t txn_vtable = {
+ svn_fs_base__commit_txn,
+ svn_fs_base__abort_txn,
+ svn_fs_base__txn_prop,
+ svn_fs_base__txn_proplist,
+ svn_fs_base__change_txn_prop,
+ svn_fs_base__txn_root,
+ svn_fs_base__change_txn_props
+};
+
+
+/* Allocate and return a new transaction object in POOL for FS whose
+ transaction ID is ID. ID is not copied. */
+static svn_fs_txn_t *
+make_txn(svn_fs_t *fs,
+ const char *id,
+ svn_revnum_t base_rev,
+ apr_pool_t *pool)
+{
+ svn_fs_txn_t *txn = apr_pcalloc(pool, sizeof(*txn));
+
+ txn->fs = fs;
+ txn->id = id;
+ txn->base_rev = base_rev;
+ txn->vtable = &txn_vtable;
+ txn->fsap_data = NULL;
+
+ return txn;
+}
+
+
+struct begin_txn_args
+{
+ svn_fs_txn_t **txn_p;
+ svn_revnum_t base_rev;
+ apr_uint32_t flags;
+};
+
+
+static svn_error_t *
+txn_body_begin_txn(void *baton, trail_t *trail)
+{
+ struct begin_txn_args *args = baton;
+ const svn_fs_id_t *root_id;
+ const char *txn_id;
+
+ SVN_ERR(svn_fs_base__rev_get_root(&root_id, trail->fs, args->base_rev,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_bdb__create_txn(&txn_id, trail->fs, root_id,
+ trail, trail->pool));
+
+ if (args->flags & SVN_FS_TXN_CHECK_OOD)
+ {
+ struct change_txn_prop_args cpargs;
+ cpargs.fs = trail->fs;
+ cpargs.id = txn_id;
+ cpargs.name = SVN_FS__PROP_TXN_CHECK_OOD;
+ cpargs.value = svn_string_create("true", trail->pool);
+
+ SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
+ }
+
+ if (args->flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ struct change_txn_prop_args cpargs;
+ cpargs.fs = trail->fs;
+ cpargs.id = txn_id;
+ cpargs.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
+ cpargs.value = svn_string_create("true", trail->pool);
+
+ SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
+ }
+
+ *args->txn_p = make_txn(trail->fs, txn_id, args->base_rev, trail->pool);
+ return SVN_NO_ERROR;
+}
+
+/* Note: it is acceptable for this function to call back into
+ public FS API interfaces because it does not itself use trails. */
+svn_error_t *
+svn_fs_base__begin_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ svn_fs_txn_t *txn;
+ struct begin_txn_args args;
+ svn_string_t date;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.txn_p = &txn;
+ args.base_rev = rev;
+ args.flags = flags;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_begin_txn, &args, FALSE, pool));
+
+ *txn_p = txn;
+
+ /* Put a datestamp on the newly created txn, so we always know
+ exactly how old it is. (This will help sysadmins identify
+ long-abandoned txns that may need to be manually removed.) When
+ a txn is promoted to a revision, this property will be
+ automatically overwritten with a revision datestamp. */
+ date.data = svn_time_to_cstring(apr_time_now(), pool);
+ date.len = strlen(date.data);
+ return svn_fs_base__change_txn_prop(txn, SVN_PROP_REVISION_DATE,
+ &date, pool);
+}
+
+
+struct open_txn_args
+{
+ svn_fs_txn_t **txn_p;
+ const char *name;
+};
+
+
+static svn_error_t *
+txn_body_open_txn(void *baton, trail_t *trail)
+{
+ struct open_txn_args *args = baton;
+ transaction_t *fstxn;
+ svn_revnum_t base_rev = SVN_INVALID_REVNUM;
+ const char *txn_id;
+
+ SVN_ERR(get_txn(&fstxn, trail->fs, args->name, FALSE, trail, trail->pool));
+ if (fstxn->kind != transaction_kind_committed)
+ {
+ txn_id = svn_fs_base__id_txn_id(fstxn->base_id);
+ SVN_ERR(svn_fs_base__txn_get_revision(&base_rev, trail->fs, txn_id,
+ trail, trail->pool));
+ }
+
+ *args->txn_p = make_txn(trail->fs, args->name, base_rev, trail->pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__open_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ const char *name,
+ apr_pool_t *pool)
+{
+ svn_fs_txn_t *txn;
+ struct open_txn_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.txn_p = &txn;
+ args.name = name;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_open_txn, &args, FALSE, pool));
+
+ *txn_p = txn;
+ return SVN_NO_ERROR;
+}
+
+
+struct cleanup_txn_args
+{
+ transaction_t **txn_p;
+ const char *name;
+};
+
+
+static svn_error_t *
+txn_body_cleanup_txn(void *baton, trail_t *trail)
+{
+ struct cleanup_txn_args *args = baton;
+ return get_txn(args->txn_p, trail->fs, args->name, TRUE,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+txn_body_cleanup_txn_copy(void *baton, trail_t *trail)
+{
+ const char *copy_id = *(const char **)baton;
+ svn_error_t *err = svn_fs_bdb__delete_copy(trail->fs, copy_id, trail,
+ trail->pool);
+
+ /* Copy doesn't exist? No sweat. */
+ if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_COPY))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+}
+
+
+static svn_error_t *
+txn_body_cleanup_txn_changes(void *baton, trail_t *trail)
+{
+ const char *key = *(const char **)baton;
+
+ return svn_fs_bdb__changes_delete(trail->fs, key, trail, trail->pool);
+}
+
+
+struct get_dirents_args
+{
+ apr_hash_t **dirents;
+ const svn_fs_id_t *id;
+ const char *txn_id;
+};
+
+
+static svn_error_t *
+txn_body_get_dirents(void *baton, trail_t *trail)
+{
+ struct get_dirents_args *args = baton;
+ dag_node_t *node;
+
+ /* Get the node. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id,
+ trail, trail->pool));
+
+ /* If immutable, do nothing and return. */
+ if (! svn_fs_base__dag_check_mutable(node, args->txn_id))
+ return SVN_NO_ERROR;
+
+ /* If a directory, do nothing and return. */
+ *(args->dirents) = NULL;
+ if (svn_fs_base__dag_node_kind(node) != svn_node_dir)
+ return SVN_NO_ERROR;
+
+ /* Else it's mutable. Get its dirents. */
+ return svn_fs_base__dag_dir_entries(args->dirents, node,
+ trail, trail->pool);
+}
+
+
+struct remove_node_args
+{
+ const svn_fs_id_t *id;
+ const char *txn_id;
+};
+
+
+static svn_error_t *
+txn_body_remove_node(void *baton, trail_t *trail)
+{
+ struct remove_node_args *args = baton;
+ return svn_fs_base__dag_remove_node(trail->fs, args->id, args->txn_id,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+delete_txn_tree(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ struct get_dirents_args dirent_args;
+ struct remove_node_args rm_args;
+ apr_hash_t *dirents = NULL;
+ apr_hash_index_t *hi;
+ svn_error_t *err;
+
+ /* If this sucker isn't mutable, there's nothing to do. */
+ if (svn_fs_base__key_compare(svn_fs_base__id_txn_id(id), txn_id) != 0)
+ return SVN_NO_ERROR;
+
+ /* See if the thing has dirents that need to be recursed upon. If
+ you can't find the thing itself, don't sweat it. We probably
+ already cleaned it up. */
+ dirent_args.dirents = &dirents;
+ dirent_args.id = id;
+ dirent_args.txn_id = txn_id;
+ err = svn_fs_base__retry_txn(fs, txn_body_get_dirents, &dirent_args,
+ FALSE, pool);
+ if (err && (err->apr_err == SVN_ERR_FS_ID_NOT_FOUND))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* If there are dirents upon which to recurse ... recurse. */
+ if (dirents)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Loop over hash entries */
+ for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ void *val;
+ svn_fs_dirent_t *dirent;
+
+ svn_pool_clear(subpool);
+ apr_hash_this(hi, NULL, NULL, &val);
+ dirent = val;
+ SVN_ERR(delete_txn_tree(fs, dirent->id, txn_id, subpool));
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ /* Remove the node. */
+ rm_args.id = id;
+ rm_args.txn_id = txn_id;
+ return svn_fs_base__retry_txn(fs, txn_body_remove_node, &rm_args,
+ TRUE, pool);
+}
+
+
+static svn_error_t *
+txn_body_delete_txn(void *baton, trail_t *trail)
+{
+ const char *txn_id = *(const char **)baton;
+
+ return svn_fs_bdb__delete_txn(trail->fs, txn_id, trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__purge_txn(svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ struct cleanup_txn_args args;
+ transaction_t *txn;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* Open the transaction, expecting it to be dead. */
+ args.txn_p = &txn;
+ args.name = txn_id;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn, &args,
+ FALSE, pool));
+
+ /* Delete the mutable portion of the tree hanging from the
+ transaction (which should gracefully recover if we've already
+ done this). */
+ SVN_ERR(delete_txn_tree(fs, txn->root_id, txn_id, pool));
+
+ /* Kill the transaction's changes (which should gracefully recover
+ if...). */
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn_changes,
+ &txn_id, TRUE, pool));
+
+ /* Kill the transaction's copies (which should gracefully...). */
+ if (txn->copies)
+ {
+ int i;
+
+ for (i = 0; i < txn->copies->nelts; i++)
+ {
+ SVN_ERR(svn_fs_base__retry_txn
+ (fs, txn_body_cleanup_txn_copy,
+ &APR_ARRAY_IDX(txn->copies, i, const char *),
+ TRUE, pool));
+ }
+ }
+
+ /* Kill the transaction itself (which ... just kidding -- this has
+ no graceful failure mode). */
+ return svn_fs_base__retry_txn(fs, txn_body_delete_txn, &txn_id,
+ TRUE, pool);
+}
+
+
+static svn_error_t *
+txn_body_abort_txn(void *baton, trail_t *trail)
+{
+ svn_fs_txn_t *txn = baton;
+ transaction_t *fstxn;
+
+ /* Get the transaction by its id, set it to "dead", and store the
+ transaction. */
+ SVN_ERR(get_txn(&fstxn, txn->fs, txn->id, FALSE, trail, trail->pool));
+ if (fstxn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(txn->fs, txn->id);
+
+ fstxn->kind = transaction_kind_dead;
+ return put_txn(txn->fs, fstxn, txn->id, trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__abort_txn(svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
+
+ /* Set the transaction to "dead". */
+ SVN_ERR(svn_fs_base__retry_txn(txn->fs, txn_body_abort_txn, txn,
+ TRUE, pool));
+
+ /* Now, purge it. */
+ SVN_ERR_W(svn_fs_base__purge_txn(txn->fs, txn->id, pool),
+ _("Transaction aborted, but cleanup failed"));
+
+ return SVN_NO_ERROR;
+}
+
+
+struct list_transactions_args
+{
+ apr_array_header_t **names_p;
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+txn_body_list_transactions(void* baton, trail_t *trail)
+{
+ struct list_transactions_args *args = baton;
+ return svn_fs_bdb__get_txn_list(args->names_p, trail->fs,
+ trail, args->pool);
+}
+
+svn_error_t *
+svn_fs_base__list_transactions(apr_array_header_t **names_p,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *names;
+ struct list_transactions_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.names_p = &names;
+ args.pool = pool;
+ SVN_ERR(svn_fs_base__retry(fs, txn_body_list_transactions, &args,
+ FALSE, pool));
+
+ *names_p = names;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/revs-txns.h b/contrib/subversion/subversion/libsvn_fs_base/revs-txns.h
new file mode 100644
index 0000000..558a90c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/revs-txns.h
@@ -0,0 +1,231 @@
+/* revs-txns.h : internal interface to revision and transactions operations
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_REVS_TXNS_H
+#define SVN_LIBSVN_FS_REVS_TXNS_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+
+#include "fs.h"
+#include "trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/*** Revisions ***/
+
+/* Set *ROOT_ID_P to the ID of the root directory of revision REV in FS,
+ as part of TRAIL. Allocate the ID in POOL. */
+svn_error_t *svn_fs_base__rev_get_root(const svn_fs_id_t **root_id_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *TXN_ID_P to the ID of the transaction that was committed to
+ create REV in FS, as part of TRAIL. Allocate the ID in POOL. */
+svn_error_t *svn_fs_base__rev_get_txn_id(const char **txn_id_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set property NAME to VALUE on REV in FS, as part of TRAIL. */
+svn_error_t *svn_fs_base__set_rev_prop(svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+/*** Transactions ***/
+
+/* Convert the unfinished transaction in FS named TXN_NAME to a
+ committed transaction that refers to REVISION as part of TRAIL.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+svn_error_t *svn_fs_base__txn_make_committed(svn_fs_t *fs,
+ const char *txn_name,
+ svn_revnum_t revision,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *REVISION to the revision which was created when FS transaction
+ TXN_NAME was committed, or to SVN_INVALID_REVNUM if the transaction
+ has not been committed. Do all of this as part of TRAIL. */
+svn_error_t *svn_fs_base__txn_get_revision(svn_revnum_t *revision,
+ svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Retrieve information about the Subversion transaction TXN_NAME from
+ the `transactions' table of FS, as part of TRAIL.
+ Set *ROOT_ID_P to the ID of the transaction's root directory.
+ Set *BASE_ROOT_ID_P to the ID of the root directory of the
+ transaction's base revision.
+
+ If there is no such transaction, SVN_ERR_FS_NO_SUCH_TRANSACTION is
+ the error returned.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed.
+
+ Allocate *ROOT_ID_P and *BASE_ROOT_ID_P in POOL. */
+svn_error_t *svn_fs_base__get_txn_ids(const svn_fs_id_t **root_id_p,
+ const svn_fs_id_t **base_root_id_p,
+ svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set the root directory of the Subversion transaction TXN_NAME in FS
+ to ROOT_ID, as part of TRAIL. Do any necessary temporary
+ allocation in POOL.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+svn_error_t *svn_fs_base__set_txn_root(svn_fs_t *fs,
+ const char *txn_name,
+ const svn_fs_id_t *root_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Add COPY_ID to the list of copies made under the Subversion
+ transaction TXN_NAME in FS as part of TRAIL.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+svn_error_t *svn_fs_base__add_txn_copy(svn_fs_t *fs,
+ const char *txn_name,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set the base root directory of TXN_NAME in FS to NEW_ID, as part of
+ TRAIL. Do any necessary temporary allocation in POOL.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+svn_error_t *svn_fs_base__set_txn_base(svn_fs_t *fs,
+ const char *txn_name,
+ const svn_fs_id_t *new_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set a property NAME to VALUE on transaction TXN_NAME in FS as part
+ of TRAIL. Use POOL for any necessary allocations.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+svn_error_t *svn_fs_base__set_txn_prop(svn_fs_t *fs,
+ const char *txn_name,
+ const char *name,
+ const svn_string_t *value,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* These functions implement some of the calls in the FS loader
+ library's fs and txn vtables. */
+
+svn_error_t *svn_fs_base__youngest_rev(svn_revnum_t *youngest_p, svn_fs_t *fs,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__revision_prop(svn_string_t **value_p, svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *propname,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__revision_proplist(apr_hash_t **table_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__change_rev_prop(svn_fs_t *fs, svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__begin_txn(svn_fs_txn_t **txn_p, svn_fs_t *fs,
+ svn_revnum_t rev, apr_uint32_t flags,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__open_txn(svn_fs_txn_t **txn, svn_fs_t *fs,
+ const char *name, apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__purge_txn(svn_fs_t *fs, const char *txn_id,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__list_transactions(apr_array_header_t **names_p,
+ svn_fs_t *fs, apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__abort_txn(svn_fs_txn_t *txn, apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__txn_prop(svn_string_t **value_p, svn_fs_txn_t *txn,
+ const char *propname, apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__txn_proplist(apr_hash_t **table_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+/* Helper func: variant of __txn_proplist that uses an existing TRAIL.
+ * TXN_ID identifies the transaction.
+ * *TABLE_P will be non-null upon success.
+ */
+svn_error_t *svn_fs_base__txn_proplist_in_trail(apr_hash_t **table_p,
+ const char *txn_id,
+ trail_t *trail);
+
+svn_error_t *svn_fs_base__change_txn_prop(svn_fs_txn_t *txn, const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__change_txn_props(svn_fs_txn_t *txn,
+ const apr_array_header_t *props,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_REVS_TXNS_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/trail.c b/contrib/subversion/subversion/libsvn_fs_base/trail.c
new file mode 100644
index 0000000..8fdf9be
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/trail.c
@@ -0,0 +1,292 @@
+/* trail.c : backing out of aborted Berkeley DB transactions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include <apr_pools.h>
+#include "svn_pools.h"
+#include "svn_fs.h"
+#include "fs.h"
+#include "err.h"
+#include "bdb/bdb-err.h"
+#include "bdb/bdb_compat.h"
+#include "trail.h"
+#include "../libsvn_fs/fs-loader.h"
+
+
+#if defined(SVN_FS__TRAIL_DEBUG)
+
+struct trail_debug_t
+{
+ struct trail_debug_t *prev;
+ const char *table;
+ const char *op;
+};
+
+void
+svn_fs_base__trail_debug(trail_t *trail, const char *table, const char *op)
+{
+ struct trail_debug_t *trail_debug;
+
+ trail_debug = apr_palloc(trail->pool, sizeof(*trail_debug));
+ trail_debug->prev = trail->trail_debug;
+ trail_debug->table = table;
+ trail_debug->op = op;
+ trail->trail_debug = trail_debug;
+}
+
+static void
+print_trail_debug(trail_t *trail,
+ const char *txn_body_fn_name,
+ const char *filename, int line)
+{
+ struct trail_debug_t *trail_debug;
+
+ fprintf(stderr, "(%s, %s, %u, %u): ",
+ txn_body_fn_name, filename, line, trail->db_txn ? 1 : 0);
+
+ trail_debug = trail->trail_debug;
+ while (trail_debug)
+ {
+ fprintf(stderr, "(%s, %s) ", trail_debug->table, trail_debug->op);
+ trail_debug = trail_debug->prev;
+ }
+ fprintf(stderr, "\n");
+}
+#else
+#define print_trail_debug(trail, txn_body_fn_name, filename, line)
+#endif /* defined(SVN_FS__TRAIL_DEBUG) */
+
+
+static svn_error_t *
+begin_trail(trail_t **trail_p,
+ svn_fs_t *fs,
+ svn_boolean_t use_txn,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ trail_t *trail = apr_pcalloc(pool, sizeof(*trail));
+
+ trail->pool = svn_pool_create(pool);
+ trail->fs = fs;
+ if (use_txn)
+ {
+ /* [*]
+ If we're already inside a trail operation, abort() -- this is
+ a coding problem (and will likely hang the repository anyway). */
+ SVN_ERR_ASSERT(! bfd->in_txn_trail);
+
+ SVN_ERR(BDB_WRAP(fs, N_("beginning Berkeley DB transaction"),
+ bfd->bdb->env->txn_begin(bfd->bdb->env, 0,
+ &trail->db_txn, 0)));
+ bfd->in_txn_trail = TRUE;
+ }
+ else
+ {
+ trail->db_txn = NULL;
+ }
+
+ *trail_p = trail;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+abort_trail(trail_t *trail)
+{
+ svn_fs_t *fs = trail->fs;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ if (trail->db_txn)
+ {
+ /* [**]
+ We have to reset the in_txn_trail flag *before* calling
+ DB_TXN->abort(). If we did it the other way around, the next
+ call to begin_trail() (e.g., as part of a txn retry) would
+ cause an abort, even though there's strictly speaking no
+ programming error involved (see comment [*] above).
+
+ In any case, if aborting the txn fails, restarting it will
+ most likely fail for the same reason, and so it's better to
+ see the returned error than to abort. An obvious example is
+ when DB_TXN->abort() returns DB_RUNRECOVERY. */
+ bfd->in_txn_trail = FALSE;
+ SVN_ERR(BDB_WRAP(fs, N_("aborting Berkeley DB transaction"),
+ trail->db_txn->abort(trail->db_txn)));
+ }
+ svn_pool_destroy(trail->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+commit_trail(trail_t *trail)
+{
+ int db_err;
+ svn_fs_t *fs = trail->fs;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* According to the example in the Berkeley DB manual, txn_commit
+ doesn't return DB_LOCK_DEADLOCK --- all deadlocks are reported
+ earlier. */
+ if (trail->db_txn)
+ {
+ /* See comment [**] in abort_trail() above.
+ An error during txn commit will abort the transaction anyway. */
+ bfd->in_txn_trail = FALSE;
+ SVN_ERR(BDB_WRAP(fs, N_("committing Berkeley DB transaction"),
+ trail->db_txn->commit(trail->db_txn, 0)));
+ }
+
+ /* Do a checkpoint here, if enough has gone on.
+ The checkpoint parameters below are pretty arbitrary. Perhaps
+ there should be an svn_fs_berkeley_mumble function to set them. */
+ db_err = bfd->bdb->env->txn_checkpoint(bfd->bdb->env, 1024, 5, 0);
+
+ /* Pre-4.1 Berkeley documentation says:
+
+ The DB_ENV->txn_checkpoint function returns a non-zero error
+ value on failure, 0 on success, and returns DB_INCOMPLETE if
+ there were pages that needed to be written to complete the
+ checkpoint but that DB_ENV->memp_sync was unable to write
+ immediately.
+
+ It's safe to ignore DB_INCOMPLETE if we get it while
+ checkpointing. (Post-4.1 Berkeley doesn't have DB_INCOMPLETE
+ anymore, so it's not an issue there.) */
+ if (db_err)
+ {
+#if SVN_BDB_HAS_DB_INCOMPLETE
+ if (db_err != DB_INCOMPLETE)
+#endif /* SVN_BDB_HAS_DB_INCOMPLETE */
+ {
+ return svn_fs_bdb__wrap_db
+ (fs, "checkpointing after Berkeley DB transaction", db_err);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+do_retry(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton, trail_t *trail),
+ void *baton,
+ svn_boolean_t use_txn,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool,
+ const char *txn_body_fn_name,
+ const char *filename,
+ int line)
+{
+ for (;;)
+ {
+ trail_t *trail;
+ svn_error_t *svn_err, *err;
+ svn_boolean_t deadlocked = FALSE;
+
+ SVN_ERR(begin_trail(&trail, fs, use_txn, pool));
+
+ /* Do the body of the transaction. */
+ svn_err = (*txn_body)(baton, trail);
+
+ if (! svn_err)
+ {
+ /* The transaction succeeded! Commit it. */
+ SVN_ERR(commit_trail(trail));
+
+ if (use_txn)
+ print_trail_debug(trail, txn_body_fn_name, filename, line);
+
+ /* If our caller doesn't want us to keep trail memory
+ around, destroy our subpool. */
+ if (destroy_trail_pool)
+ svn_pool_destroy(trail->pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Search for a deadlock error on the stack. */
+ for (err = svn_err; err; err = err->child)
+ if (err->apr_err == SVN_ERR_FS_BERKELEY_DB_DEADLOCK)
+ deadlocked = TRUE;
+
+ /* Is this a real error, or do we just need to retry? */
+ if (! deadlocked)
+ {
+ /* Ignore any error returns. The first error is more valuable. */
+ svn_error_clear(abort_trail(trail));
+ return svn_err;
+ }
+
+ svn_error_clear(svn_err);
+
+ /* We deadlocked. Abort the transaction, and try again. */
+ SVN_ERR(abort_trail(trail));
+ }
+}
+
+
+svn_error_t *
+svn_fs_base__retry_debug(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton, trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool,
+ const char *txn_body_fn_name,
+ const char *filename,
+ int line)
+{
+ return do_retry(fs, txn_body, baton, TRUE, destroy_trail_pool, pool,
+ txn_body_fn_name, filename, line);
+}
+
+
+#if defined(SVN_FS__TRAIL_DEBUG)
+#undef svn_fs_base__retry_txn
+#endif
+
+svn_error_t *
+svn_fs_base__retry_txn(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton, trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool)
+{
+ return do_retry(fs, txn_body, baton, TRUE, destroy_trail_pool, pool,
+ "unknown", "", 0);
+}
+
+
+svn_error_t *
+svn_fs_base__retry(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton, trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool)
+{
+ return do_retry(fs, txn_body, baton, FALSE, destroy_trail_pool, pool,
+ NULL, NULL, 0);
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/trail.h b/contrib/subversion/subversion/libsvn_fs_base/trail.h
new file mode 100644
index 0000000..87fc313
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/trail.h
@@ -0,0 +1,239 @@
+/* trail.h : internal interface to backing out of aborted Berkeley DB txns
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_TRAIL_H
+#define SVN_LIBSVN_FS_TRAIL_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include <apr_pools.h>
+#include "svn_fs.h"
+#include "fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* "How do I get a trail object? All these functions in the
+ filesystem expect them, and I can't find a function that returns
+ one."
+
+ Well, there isn't a function that returns a trail. All trails come
+ from svn_fs_base__retry_txn. Here's how to use that:
+
+ When using Berkeley DB transactions to protect the integrity of a
+ database, there are several things you need to keep in mind:
+
+ - Any Berkeley DB operation you perform as part of a Berkeley DB
+ transaction may return DB_LOCK_DEADLOCK, meaning that your
+ operation interferes with some other transaction in progress.
+ When this happens, you must abort the transaction, which undoes
+ all the changes you've made so far, and try it again. So every
+ piece of code you ever write to bang on the DB needs to be
+ wrapped up in a retry loop.
+
+ - If, while you're doing your database operations, you also change
+ some in-memory data structures, then you may want to revert those
+ changes if the transaction deadlocks and needs to be retried.
+
+ - If you get a `real' error (i.e., something other than
+ DB_LOCK_DEADLOCK), you must abort your DB transaction, to release
+ its locks and return the database to its previous state.
+ Similarly, you may want to unroll some changes you've made to
+ in-memory data structures.
+
+ - Since a transaction insulates you from database changes made by
+ other processes, it's often possible to cache information about
+ database contents while the transaction lasts. However, this
+ cache may become stale once your transaction is over. So you may
+ need to clear your cache once the transaction completes, either
+ successfully or unsuccessfully.
+
+ The `svn_fs_base__retry_txn' function and its friends help you manage
+ some of that, in one nice package.
+
+ To use it, write your code in a function like this:
+
+ static svn_error_t *
+ txn_body_do_my_thing (void *baton,
+ trail_t *trail)
+ {
+ ...
+ Do everything which needs to be protected by a Berkeley DB
+ transaction here. Use TRAIL->db_txn as your Berkeley DB
+ transaction, and do your allocation in TRAIL->pool. Pass
+ TRAIL on through to any functions which require one.
+
+ If a Berkeley DB operation returns DB_LOCK_DEADLOCK, just
+ return that using the normal Subversion error mechanism
+ (using DB_ERR, for example); don't write a retry loop. If you
+ encounter some other kind of error, return it in the normal
+ fashion.
+ ...
+ }
+
+ Now, call svn_fs_base__retry_txn, passing a pointer to your function as
+ an argument:
+
+ err = svn_fs_base__retry_txn (fs, txn_body_do_my_thing, baton, pool);
+
+ This will simply invoke your function `txn_body_do_my_thing',
+ passing BATON through unchanged, and providing a fresh TRAIL
+ object, containing a pointer to the filesystem object, a Berkeley
+ DB transaction and an APR pool -- a subpool of POOL -- you should
+ use.
+
+ If your function returns a Subversion error wrapping a Berkeley DB
+ DB_LOCK_DEADLOCK error, `svn_fs_base__retry_txn' will abort the trail's
+ Berkeley DB transaction for you (thus undoing any database changes
+ you've made), free the trail's subpool (thus undoing any allocation
+ you may have done), and try the whole thing again with a new trail,
+ containing a new Berkeley DB transaction and pool.
+
+ If your function returns any other kind of Subversion error,
+ `svn_fs_base__retry_txn' will abort the trail's Berkeley DB transaction,
+ free the subpool, and return your error to its caller.
+
+ If, heavens forbid, your function actually succeeds, returning
+ SVN_NO_ERROR, `svn_fs_base__retry_txn' commits the trail's Berkeley DB
+ transaction, thus making your DB changes permanent, leaves the
+ trail's pool alone so all the objects it contains are still
+ around (unless you request otherwise), and returns SVN_NO_ERROR.
+
+
+ Keep the amount of work done in a trail small. C-Mike Pilato said to me:
+
+ I want to draw your attention to something that you may or may not realize
+ about designing for the BDB backend. The 'trail' objects are (generally)
+ representative of Berkeley DB transactions -- that part I'm sure you know.
+ But you might not realize the value of keeping transactions as small as
+ possible. Berkeley DB will accumulate locks (which I believe are
+ page-level, not as tight as row-level like you might hope) over the course
+ of a transaction, releasing those locks only at transaction commit/abort.
+ Berkeley DB backends are configured to have a maximum number of locks and
+ lockers allowed, and it's easier than you might think to hit the max-locks
+ thresholds (especially under high concurrency) and see an error (typically a
+ "Cannot allocate memory") result from that.
+
+ For example, in [a loop] you are writing a bunch of rows to the
+ `changes' table. Could be 10. Could be 100,000. 100,000 writes and
+ associated locks might be a problem or it might not. But I use it as a way
+ to encourage you to think about reducing the amount of work you spend in any
+ one trail [...].
+*/
+
+struct trail_t
+{
+ /* A Berkeley DB transaction. */
+ DB_TXN *db_txn;
+
+ /* The filesystem object with which this trail is associated. */
+ svn_fs_t *fs;
+
+ /* A pool to allocate things in as part of that transaction --- a
+ subpool of the one passed to `begin_trail'. We destroy this pool
+ if we abort the transaction, and leave it around otherwise. */
+ apr_pool_t *pool;
+
+#if defined(SVN_FS__TRAIL_DEBUG)
+ struct trail_debug_t *trail_debug;
+#endif
+};
+typedef struct trail_t trail_t;
+
+
+/* Try a Berkeley DB transaction repeatedly until it doesn't deadlock.
+
+ That is:
+ - Begin a new Berkeley DB transaction, DB_TXN, in the filesystem FS.
+ - Allocate a subpool of POOL, TXN_POOL.
+ - Start a new trail, TRAIL, pointing to DB_TXN and TXN_POOL.
+ - Apply TXN_BODY to BATON and TRAIL. TXN_BODY should try to do
+ some series of DB operations which needs to be atomic, using
+ TRAIL->db_txn as the transaction, and TRAIL->pool for allocation.
+ If a DB operation deadlocks, or if any other kind of error
+ happens, TXN_BODY should simply return with an appropriate
+ svn_error_t, E.
+ - If TXN_BODY returns SVN_NO_ERROR, then commit the transaction,
+ run any completion functions, and return SVN_NO_ERROR. Do *not*
+ free TXN_POOL (unless DESTROY_TRAIL_POOL is set).
+ - If E is a Berkeley DB error indicating that a deadlock occurred,
+ abort the DB transaction and free TXN_POOL. Then retry the whole
+ thing from the top.
+ - If E is any other kind of error, free TXN_POOL and return E.
+
+ One benefit of using this function is that it makes it easy to
+ ensure that whatever transactions a filesystem function starts, it
+ either aborts or commits before it returns. If we don't somehow
+ complete all our transactions, later operations could deadlock. */
+svn_error_t *
+svn_fs_base__retry_txn(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton,
+ trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool);
+
+svn_error_t *
+svn_fs_base__retry_debug(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton,
+ trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool,
+ const char *txn_body_fn_name,
+ const char *filename,
+ int line);
+
+#if defined(SVN_FS__TRAIL_DEBUG)
+#define svn_fs_base__retry_txn(fs, txn_body, baton, destroy, pool) \
+ svn_fs_base__retry_debug(fs, txn_body, baton, destroy, pool, \
+ #txn_body, __FILE__, __LINE__)
+#endif
+
+
+/* Try an action repeatedly until it doesn't deadlock. This is
+ exactly like svn_fs_base__retry_txn() (whose documentation you really
+ should read) except that no Berkeley DB transaction is created. */
+svn_error_t *svn_fs_base__retry(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton,
+ trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool);
+
+
+/* Record that OPeration is being done on TABLE in the TRAIL. */
+#if defined(SVN_FS__TRAIL_DEBUG)
+void svn_fs_base__trail_debug(trail_t *trail, const char *table,
+ const char *op);
+#else
+#define svn_fs_base__trail_debug(trail, table, operation)
+#endif
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_TRAIL_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/tree.c b/contrib/subversion/subversion/libsvn_fs_base/tree.c
new file mode 100644
index 0000000..e603af4
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/tree.c
@@ -0,0 +1,5451 @@
+/* tree.c : tree-like filesystem, built on DAG filesystem
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+/* The job of this layer is to take a filesystem with lots of node
+ sharing going on --- the real DAG filesystem as it appears in the
+ database --- and make it look and act like an ordinary tree
+ filesystem, with no sharing.
+
+ We do just-in-time cloning: you can walk from some unfinished
+ transaction's root down into directories and files shared with
+ committed revisions; as soon as you try to change something, the
+ appropriate nodes get cloned (and parent directory entries updated)
+ invisibly, behind your back. Any other references you have to
+ nodes that have been cloned by other changes, even made by other
+ processes, are automatically updated to point to the right clones. */
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "svn_private_config.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "svn_mergeinfo.h"
+#include "svn_fs.h"
+#include "svn_sorts.h"
+#include "svn_checksum.h"
+#include "fs.h"
+#include "err.h"
+#include "trail.h"
+#include "node-rev.h"
+#include "key-gen.h"
+#include "dag.h"
+#include "tree.h"
+#include "lock.h"
+#include "revs-txns.h"
+#include "id.h"
+#include "bdb/txn-table.h"
+#include "bdb/rev-table.h"
+#include "bdb/nodes-table.h"
+#include "bdb/changes-table.h"
+#include "bdb/copies-table.h"
+#include "bdb/node-origins-table.h"
+#include "bdb/miscellaneous-table.h"
+#include "../libsvn_fs/fs-loader.h"
+#include "private/svn_fspath.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_mergeinfo_private.h"
+
+
+/* ### I believe this constant will become internal to reps-strings.c.
+ ### see the comment in window_consumer() for more information. */
+
+/* ### the comment also seems to need tweaking: the log file stuff
+ ### is no longer an issue... */
+/* Data written to the filesystem through the svn_fs_apply_textdelta()
+ interface is cached in memory until the end of the data stream, or
+ until a size trigger is hit. Define that trigger here (in bytes).
+ Setting the value to 0 will result in no filesystem buffering at
+ all. The value only really matters when dealing with file contents
+ bigger than the value itself. Above that point, large values here
+ allow the filesystem to buffer more data in memory before flushing
+ to the database, which increases memory usage but greatly decreases
+ the amount of disk access (and log-file generation) in database.
+ Smaller values will limit your overall memory consumption, but can
+ drastically hurt throughput by necessitating more write operations
+ to the database (which also generates more log-files). */
+#define WRITE_BUFFER_SIZE 512000
+
+/* The maximum number of cache items to maintain in the node cache. */
+#define NODE_CACHE_MAX_KEYS 32
+
+
+
+/* The root structure. */
+
+/* Structure for svn_fs_root_t's node_cache hash values. */
+struct dag_node_cache_t
+{
+ dag_node_t *node; /* NODE to be cached. */
+ int idx; /* Index into the keys array for this cache item's key. */
+ apr_pool_t *pool; /* Pool in which NODE is allocated. */
+};
+
+
+typedef struct base_root_data_t
+{
+
+ /* For revision roots, this is a dag node for the revision's root
+ directory. For transaction roots, we open the root directory
+ afresh every time, since the root may have been cloned, or
+ the transaction may have disappeared altogether. */
+ dag_node_t *root_dir;
+
+ /* Cache structures, for mapping const char * PATH to const
+ struct dag_node_cache_t * structures.
+
+ ### Currently this is only used for revision roots. To be safe
+ for transaction roots, you must have the guarantee that there is
+ never more than a single transaction root per Subversion
+ transaction ever open at a given time -- having two roots open to
+ the same Subversion transaction would be a request for pain.
+ Also, you have to ensure that if a 'make_path_mutable()' fails for
+ any reason, you don't leave cached nodes for the portion of that
+ function that succeeded. In other words, this cache must never,
+ ever, lie. */
+ apr_hash_t *node_cache;
+ const char *node_cache_keys[NODE_CACHE_MAX_KEYS];
+ int node_cache_idx;
+} base_root_data_t;
+
+
+static svn_fs_root_t *make_revision_root(svn_fs_t *fs, svn_revnum_t rev,
+ dag_node_t *root_dir,
+ apr_pool_t *pool);
+
+static svn_fs_root_t *make_txn_root(svn_fs_t *fs, const char *txn,
+ svn_revnum_t base_rev, apr_uint32_t flags,
+ apr_pool_t *pool);
+
+
+/*** Node Caching in the Roots. ***/
+
+/* Return NODE for PATH from ROOT's node cache, or NULL if the node
+ isn't cached. */
+static dag_node_t *
+dag_node_cache_get(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ base_root_data_t *brd = root->fsap_data;
+ struct dag_node_cache_t *cache_item;
+
+ /* Assert valid input. */
+ assert(*path == '/');
+
+ /* Only allow revision roots. */
+ if (root->is_txn_root)
+ return NULL;
+
+ /* Look in the cache for our desired item. */
+ cache_item = svn_hash_gets(brd->node_cache, path);
+ if (cache_item)
+ return svn_fs_base__dag_dup(cache_item->node, pool);
+
+ return NULL;
+}
+
+
+/* Add the NODE for PATH to ROOT's node cache. Callers should *NOT*
+ call this unless they are adding a currently un-cached item to the
+ cache, or are replacing the NODE for PATH with a new (different)
+ one. */
+static void
+dag_node_cache_set(svn_fs_root_t *root,
+ const char *path,
+ dag_node_t *node)
+{
+ base_root_data_t *brd = root->fsap_data;
+ const char *cache_path;
+ apr_pool_t *cache_pool;
+ struct dag_node_cache_t *cache_item;
+ int num_keys = apr_hash_count(brd->node_cache);
+
+ /* What? No POOL passed to this function?
+
+ To ensure that our cache values live as long as the svn_fs_root_t
+ in which they are ultimately stored, and to allow us to free()
+ them individually without harming the rest, they are each
+ allocated from a subpool of ROOT's pool. We'll keep one subpool
+ around for each cache slot -- as we start expiring stuff
+ to make room for more entries, we'll re-use the expired thing's
+ pool. */
+
+ /* Assert valid input and state. */
+ assert(*path == '/');
+ assert((brd->node_cache_idx <= num_keys)
+ && (num_keys <= NODE_CACHE_MAX_KEYS));
+
+ /* Only allow revision roots. */
+ if (root->is_txn_root)
+ return;
+
+ /* Special case: the caller wants us to replace an existing cached
+ node with a new one. If the callers aren't mindless, this should
+ only happen when a node is made mutable under a transaction
+ root, and that only happens once under that root. So, we'll be a
+ little bit sloppy here, and count on callers doing the right
+ thing. */
+ cache_item = svn_hash_gets(brd->node_cache, path);
+ if (cache_item)
+ {
+ /* ### This section is somehow broken. I don't know how, but it
+ ### is. And I don't want to spend any more time on it. So,
+ ### callers, use only revision root and don't try to update
+ ### an already-cached thing. -- cmpilato */
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+
+#if 0
+ int cache_index = cache_item->idx;
+ cache_path = brd->node_cache_keys[cache_index];
+ cache_pool = cache_item->pool;
+ cache_item->node = svn_fs_base__dag_dup(node, cache_pool);
+
+ /* Now, move the cache key reference to the end of the keys in
+ the keys array (unless it's already at the end). ### Yes,
+ it's a memmove(), but we're not talking about pages of memory
+ here. */
+ if (cache_index != (num_keys - 1))
+ {
+ int move_num = NODE_CACHE_MAX_KEYS - cache_index - 1;
+ memmove(brd->node_cache_keys + cache_index,
+ brd->node_cache_keys + cache_index + 1,
+ move_num * sizeof(const char *));
+ cache_index = num_keys - 1;
+ brd->node_cache_keys[cache_index] = cache_path;
+ }
+
+ /* Advance the cache pointers. */
+ cache_item->idx = cache_index;
+ brd->node_cache_idx = (cache_index + 1) % NODE_CACHE_MAX_KEYS;
+ return;
+#endif
+ }
+
+ /* We're adding a new cache item. First, see if we have room for it
+ (otherwise, make some room). */
+ if (apr_hash_count(brd->node_cache) == NODE_CACHE_MAX_KEYS)
+ {
+ /* No room. Expire the oldest thing. */
+ cache_path = brd->node_cache_keys[brd->node_cache_idx];
+ cache_item = svn_hash_gets(brd->node_cache, cache_path);
+ svn_hash_sets(brd->node_cache, cache_path, NULL);
+ cache_pool = cache_item->pool;
+ svn_pool_clear(cache_pool);
+ }
+ else
+ {
+ cache_pool = svn_pool_create(root->pool);
+ }
+
+ /* Make the cache item, allocated in its own pool. */
+ cache_item = apr_palloc(cache_pool, sizeof(*cache_item));
+ cache_item->node = svn_fs_base__dag_dup(node, cache_pool);
+ cache_item->idx = brd->node_cache_idx;
+ cache_item->pool = cache_pool;
+
+ /* Now add it to the cache. */
+ cache_path = apr_pstrdup(cache_pool, path);
+ svn_hash_sets(brd->node_cache, cache_path, cache_item);
+ brd->node_cache_keys[brd->node_cache_idx] = cache_path;
+
+ /* Advance the cache pointer. */
+ brd->node_cache_idx = (brd->node_cache_idx + 1) % NODE_CACHE_MAX_KEYS;
+}
+
+
+
+
+/* Creating transaction and revision root nodes. */
+
+struct txn_root_args
+{
+ svn_fs_root_t **root_p;
+ svn_fs_txn_t *txn;
+};
+
+
+static svn_error_t *
+txn_body_txn_root(void *baton,
+ trail_t *trail)
+{
+ struct txn_root_args *args = baton;
+ svn_fs_root_t **root_p = args->root_p;
+ svn_fs_txn_t *txn = args->txn;
+ svn_fs_t *fs = txn->fs;
+ const char *svn_txn_id = txn->id;
+ const svn_fs_id_t *root_id, *base_root_id;
+ svn_fs_root_t *root;
+ apr_hash_t *txnprops;
+ apr_uint32_t flags = 0;
+
+ /* Verify that the transaction actually exists. */
+ SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs,
+ svn_txn_id, trail, trail->pool));
+
+ /* Look for special txn props that represent the 'flags' behavior of
+ the transaction. */
+ SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, svn_txn_id, trail));
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
+ flags |= SVN_FS_TXN_CHECK_OOD;
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
+ flags |= SVN_FS_TXN_CHECK_LOCKS;
+
+ root = make_txn_root(fs, svn_txn_id, txn->base_rev, flags, trail->pool);
+
+ *root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__txn_root(svn_fs_root_t **root_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ struct txn_root_args args;
+
+ args.root_p = &root;
+ args.txn = txn;
+ SVN_ERR(svn_fs_base__retry_txn(txn->fs, txn_body_txn_root, &args,
+ FALSE, pool));
+
+ *root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+struct revision_root_args
+{
+ svn_fs_root_t **root_p;
+ svn_revnum_t rev;
+};
+
+
+static svn_error_t *
+txn_body_revision_root(void *baton,
+ trail_t *trail)
+{
+ struct revision_root_args *args = baton;
+ dag_node_t *root_dir;
+ svn_fs_root_t *root;
+
+ SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, args->rev,
+ trail, trail->pool));
+ root = make_revision_root(trail->fs, args->rev, root_dir, trail->pool);
+
+ *args->root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__revision_root(svn_fs_root_t **root_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ struct revision_root_args args;
+ svn_fs_root_t *root;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.root_p = &root;
+ args.rev = rev;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_root, &args,
+ FALSE, pool));
+
+ *root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Getting dag nodes for roots. */
+
+
+/* Set *NODE_P to a freshly opened dag node referring to the root
+ directory of ROOT, as part of TRAIL. */
+static svn_error_t *
+root_node(dag_node_t **node_p,
+ svn_fs_root_t *root,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_root_data_t *brd = root->fsap_data;
+
+ if (! root->is_txn_root)
+ {
+ /* It's a revision root, so we already have its root directory
+ opened. */
+ *node_p = svn_fs_base__dag_dup(brd->root_dir, pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* It's a transaction root. Open a fresh copy. */
+ return svn_fs_base__dag_txn_root(node_p, root->fs, root->txn,
+ trail, pool);
+ }
+}
+
+
+/* Set *NODE_P to a mutable root directory for ROOT, cloning if
+ necessary, as part of TRAIL. ROOT must be a transaction root. Use
+ ERROR_PATH in error messages. */
+static svn_error_t *
+mutable_root_node(dag_node_t **node_p,
+ svn_fs_root_t *root,
+ const char *error_path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ if (root->is_txn_root)
+ return svn_fs_base__dag_clone_root(node_p, root->fs, root->txn,
+ trail, pool);
+ else
+ /* If it's not a transaction root, we can't change its contents. */
+ return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path);
+}
+
+
+
+/* Traversing directory paths. */
+
+typedef enum copy_id_inherit_t
+{
+ copy_id_inherit_unknown = 0,
+ copy_id_inherit_self,
+ copy_id_inherit_parent,
+ copy_id_inherit_new
+
+} copy_id_inherit_t;
+
+/* A linked list representing the path from a node up to a root
+ directory. We use this for cloning, and for operations that need
+ to deal with both a node and its parent directory. For example, a
+ `delete' operation needs to know that the node actually exists, but
+ also needs to change the parent directory. */
+typedef struct parent_path_t
+{
+
+ /* A node along the path. This could be the final node, one of its
+ parents, or the root. Every parent path ends with an element for
+ the root directory. */
+ dag_node_t *node;
+
+ /* The name NODE has in its parent directory. This is zero for the
+ root directory, which (obviously) has no name in its parent. */
+ char *entry;
+
+ /* The parent of NODE, or zero if NODE is the root directory. */
+ struct parent_path_t *parent;
+
+ /* The copy ID inheritance style. */
+ copy_id_inherit_t copy_inherit;
+
+ /* If copy ID inheritance style is copy_id_inherit_new, this is the
+ path which should be implicitly copied; otherwise, this is NULL. */
+ const char *copy_src_path;
+
+} parent_path_t;
+
+
+/* Return the FS path for the parent path chain object PARENT_PATH,
+ allocated in POOL. */
+static const char *
+parent_path_path(parent_path_t *parent_path,
+ apr_pool_t *pool)
+{
+ const char *path_so_far = "/";
+ if (parent_path->parent)
+ path_so_far = parent_path_path(parent_path->parent, pool);
+ return parent_path->entry
+ ? svn_fspath__join(path_so_far, parent_path->entry, pool)
+ : path_so_far;
+}
+
+
+/* Return the FS path for the parent path chain object CHILD relative
+ to its ANCESTOR in the same chain, allocated in POOL. */
+static const char *
+parent_path_relpath(parent_path_t *child,
+ parent_path_t *ancestor,
+ apr_pool_t *pool)
+{
+ const char *path_so_far = "";
+ parent_path_t *this_node = child;
+ while (this_node != ancestor)
+ {
+ assert(this_node != NULL);
+ path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool);
+ this_node = this_node->parent;
+ }
+ return path_so_far;
+}
+
+
+/* Choose a copy ID inheritance method *INHERIT_P to be used in the
+ event that immutable node CHILD in FS needs to be made mutable. If
+ the inheritance method is copy_id_inherit_new, also return a
+ *COPY_SRC_PATH on which to base the new copy ID (else return NULL
+ for that path). CHILD must have a parent (it cannot be the root
+ node). TXN_ID is the transaction in which these items might be
+ mutable. */
+static svn_error_t *
+get_copy_inheritance(copy_id_inherit_t *inherit_p,
+ const char **copy_src_path,
+ svn_fs_t *fs,
+ parent_path_t *child,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *child_id, *parent_id;
+ const char *child_copy_id, *parent_copy_id;
+ const char *id_path = NULL;
+
+ SVN_ERR_ASSERT(child && child->parent && txn_id);
+
+ /* Initialize our return variables (default: self-inheritance). */
+ *inherit_p = copy_id_inherit_self;
+ *copy_src_path = NULL;
+
+ /* Initialize some convenience variables. */
+ child_id = svn_fs_base__dag_get_id(child->node);
+ parent_id = svn_fs_base__dag_get_id(child->parent->node);
+ child_copy_id = svn_fs_base__id_copy_id(child_id);
+ parent_copy_id = svn_fs_base__id_copy_id(parent_id);
+
+ /* Easy out: if this child is already mutable, we have nothing to do. */
+ if (svn_fs_base__key_compare(svn_fs_base__id_txn_id(child_id), txn_id) == 0)
+ return SVN_NO_ERROR;
+
+ /* If the child and its parent are on the same branch, then the
+ child will inherit the copy ID of its parent when made mutable.
+ This is trivially detectable when the child and its parent have
+ the same copy ID. But that's not the sole indicator of
+ same-branchness. It might be the case that the parent was the
+ result of a copy, but the child has not yet been cloned for
+ mutability since that copy. Detection of this latter case
+ basically means making sure the copy IDs don't differ for some
+ other reason, such as that the child was the direct target of the
+ copy whose ID it has. There is a special case here, too -- if
+ the child's copy ID is the special ID "0", it can't have been the
+ target of any copy, and therefore must be on the same branch as
+ its parent. */
+ if ((strcmp(child_copy_id, "0") == 0)
+ || (svn_fs_base__key_compare(child_copy_id, parent_copy_id) == 0))
+ {
+ *inherit_p = copy_id_inherit_parent;
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ copy_t *copy;
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs, child_copy_id, trail, pool));
+ if (svn_fs_base__id_compare(copy->dst_noderev_id, child_id) == -1)
+ {
+ *inherit_p = copy_id_inherit_parent;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* If we get here, the child and its parent are not on speaking
+ terms -- there will be no parental inheritance handed down in
+ *this* generation. */
+
+ /* If the child was created at a different path than the one we are
+ expecting its clone to live, one of its parents must have been
+ created via a copy since the child was created. The child isn't
+ on the same branch as its parent (we caught those cases early);
+ it can't keep its current copy ID because there's been an
+ affecting copy (its clone won't be on the same branch as the
+ child is). That leaves only one course of action -- to assign
+ the child a brand new "soft" copy ID. */
+ id_path = svn_fs_base__dag_get_created_path(child->node);
+ if (strcmp(id_path, parent_path_path(child, pool)) != 0)
+ {
+ *inherit_p = copy_id_inherit_new;
+ *copy_src_path = id_path;
+ return SVN_NO_ERROR;
+ }
+
+ /* The node gets to keep its own ID. */
+ return SVN_NO_ERROR;
+}
+
+
+/* Allocate a new parent_path_t node from POOL, referring to NODE,
+ ENTRY, PARENT, and COPY_ID. */
+static parent_path_t *
+make_parent_path(dag_node_t *node,
+ char *entry,
+ parent_path_t *parent,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path = apr_pcalloc(pool, sizeof(*parent_path));
+ parent_path->node = node;
+ parent_path->entry = entry;
+ parent_path->parent = parent;
+ parent_path->copy_inherit = copy_id_inherit_unknown;
+ parent_path->copy_src_path = NULL;
+ return parent_path;
+}
+
+
+/* Flags for open_path. */
+typedef enum open_path_flags_t {
+
+ /* The last component of the PATH need not exist. (All parent
+ directories must exist, as usual.) If the last component doesn't
+ exist, simply leave the `node' member of the bottom parent_path
+ component zero. */
+ open_path_last_optional = 1
+
+} open_path_flags_t;
+
+
+/* Open the node identified by PATH in ROOT, as part of TRAIL. Set
+ *PARENT_PATH_P to a path from the node up to ROOT, allocated in
+ TRAIL->pool. The resulting *PARENT_PATH_P value is guaranteed to
+ contain at least one element, for the root directory.
+
+ If resulting *PARENT_PATH_P will eventually be made mutable and
+ modified, or if copy ID inheritance information is otherwise
+ needed, TXN_ID should be the ID of the mutability transaction. If
+ TXN_ID is NULL, no copy ID in heritance information will be
+ calculated for the *PARENT_PATH_P chain.
+
+ If FLAGS & open_path_last_optional is zero, return the error
+ SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If
+ non-zero, require all the parent directories to exist as normal,
+ but if the final path component doesn't exist, simply return a path
+ whose bottom `node' member is zero. This option is useful for
+ callers that create new nodes --- we find the parent directory for
+ them, and tell them whether the entry exists already.
+
+ NOTE: Public interfaces which only *read* from the filesystem
+ should not call this function directly, but should instead use
+ get_dag().
+*/
+static svn_error_t *
+open_path(parent_path_t **parent_path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ int flags,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = root->fs;
+ dag_node_t *here; /* The directory we're currently looking at. */
+ parent_path_t *parent_path; /* The path from HERE up to the root. */
+ const char *rest; /* The portion of PATH we haven't traversed yet. */
+ const char *canon_path = svn_fs__canonicalize_abspath(path, pool);
+ const char *path_so_far = "/";
+
+ /* Make a parent_path item for the root node, using its own current
+ copy id. */
+ SVN_ERR(root_node(&here, root, trail, pool));
+ parent_path = make_parent_path(here, 0, 0, pool);
+ parent_path->copy_inherit = copy_id_inherit_self;
+
+ rest = canon_path + 1; /* skip the leading '/', it saves in iteration */
+
+ /* Whenever we are at the top of this loop:
+ - HERE is our current directory,
+ - ID is the node revision ID of HERE,
+ - REST is the path we're going to find in HERE, and
+ - PARENT_PATH includes HERE and all its parents. */
+ for (;;)
+ {
+ const char *next;
+ char *entry;
+ dag_node_t *child;
+
+ /* Parse out the next entry from the path. */
+ entry = svn_fs__next_entry_name(&next, rest, pool);
+
+ /* Calculate the path traversed thus far. */
+ path_so_far = svn_fspath__join(path_so_far, entry, pool);
+
+ if (*entry == '\0')
+ {
+ /* Given the behavior of svn_fs__next_entry_name(), this
+ happens when the path either starts or ends with a slash.
+ In either case, we stay put: the current directory stays
+ the same, and we add nothing to the parent path. */
+ child = here;
+ }
+ else
+ {
+ copy_id_inherit_t inherit;
+ const char *copy_path = NULL;
+ svn_error_t *err = SVN_NO_ERROR;
+ dag_node_t *cached_node;
+
+ /* If we found a directory entry, follow it. First, we
+ check our node cache, and, failing that, we hit the DAG
+ layer. */
+ cached_node = dag_node_cache_get(root, path_so_far, pool);
+ if (cached_node)
+ child = cached_node;
+ else
+ err = svn_fs_base__dag_open(&child, here, entry, trail, pool);
+
+ /* "file not found" requires special handling. */
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ /* If this was the last path component, and the caller
+ said it was optional, then don't return an error;
+ just put a NULL node pointer in the path. */
+
+ svn_error_clear(err);
+
+ if ((flags & open_path_last_optional)
+ && (! next || *next == '\0'))
+ {
+ parent_path = make_parent_path(NULL, entry, parent_path,
+ pool);
+ break;
+ }
+ else
+ {
+ /* Build a better error message than svn_fs_base__dag_open
+ can provide, giving the root and full path name. */
+ return SVN_FS__NOT_FOUND(root, path);
+ }
+ }
+
+ /* Other errors we return normally. */
+ SVN_ERR(err);
+
+ /* Now, make a parent_path item for CHILD. */
+ parent_path = make_parent_path(child, entry, parent_path, pool);
+ if (txn_id)
+ {
+ SVN_ERR(get_copy_inheritance(&inherit, &copy_path,
+ fs, parent_path, txn_id,
+ trail, pool));
+ parent_path->copy_inherit = inherit;
+ parent_path->copy_src_path = apr_pstrdup(pool, copy_path);
+ }
+
+ /* Cache the node we found (if it wasn't already cached). */
+ if (! cached_node)
+ dag_node_cache_set(root, path_so_far, child);
+ }
+
+ /* Are we finished traversing the path? */
+ if (! next)
+ break;
+
+ /* The path isn't finished yet; we'd better be in a directory. */
+ if (svn_fs_base__dag_node_kind(child) != svn_node_dir)
+ SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far),
+ apr_psprintf(pool, _("Failure opening '%s'"), path));
+
+ rest = next;
+ here = child;
+ }
+
+ *parent_path_p = parent_path;
+ return SVN_NO_ERROR;
+}
+
+
+/* Make the node referred to by PARENT_PATH mutable, if it isn't
+ already, as part of TRAIL. ROOT must be the root from which
+ PARENT_PATH descends. Clone any parent directories as needed.
+ Adjust the dag nodes in PARENT_PATH to refer to the clones. Use
+ ERROR_PATH in error messages. */
+static svn_error_t *
+make_path_mutable(svn_fs_root_t *root,
+ parent_path_t *parent_path,
+ const char *error_path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *cloned_node;
+ const char *txn_id = root->txn;
+ svn_fs_t *fs = root->fs;
+
+ /* Is the node mutable already? */
+ if (svn_fs_base__dag_check_mutable(parent_path->node, txn_id))
+ return SVN_NO_ERROR;
+
+ /* Are we trying to clone the root, or somebody's child node? */
+ if (parent_path->parent)
+ {
+ const svn_fs_id_t *parent_id;
+ const svn_fs_id_t *node_id = svn_fs_base__dag_get_id(parent_path->node);
+ const char *copy_id = NULL;
+ const char *copy_src_path = parent_path->copy_src_path;
+ copy_id_inherit_t inherit = parent_path->copy_inherit;
+ const char *clone_path;
+
+ /* We're trying to clone somebody's child. Make sure our parent
+ is mutable. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent,
+ error_path, trail, pool));
+
+ switch (inherit)
+ {
+ case copy_id_inherit_parent:
+ parent_id = svn_fs_base__dag_get_id(parent_path->parent->node);
+ copy_id = svn_fs_base__id_copy_id(parent_id);
+ break;
+
+ case copy_id_inherit_new:
+ SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, pool));
+ break;
+
+ case copy_id_inherit_self:
+ copy_id = NULL;
+ break;
+
+ case copy_id_inherit_unknown:
+ default:
+ SVN_ERR_MALFUNCTION(); /* uh-oh -- somebody didn't calculate copy-ID
+ inheritance data. */
+ }
+
+ /* Now make this node mutable. */
+ clone_path = parent_path_path(parent_path->parent, pool);
+ SVN_ERR(svn_fs_base__dag_clone_child(&cloned_node,
+ parent_path->parent->node,
+ clone_path,
+ parent_path->entry,
+ copy_id, txn_id,
+ trail, pool));
+
+ /* If we just created a brand new copy ID, we need to store a
+ `copies' table entry for it, as well as a notation in the
+ transaction that should this transaction be terminated, our
+ new copy needs to be removed. */
+ if (inherit == copy_id_inherit_new)
+ {
+ const svn_fs_id_t *new_node_id =
+ svn_fs_base__dag_get_id(cloned_node);
+ SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, copy_src_path,
+ svn_fs_base__id_txn_id(node_id),
+ new_node_id,
+ copy_kind_soft, trail, pool));
+ SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id,
+ trail, pool));
+ }
+ }
+ else
+ {
+ /* We're trying to clone the root directory. */
+ SVN_ERR(mutable_root_node(&cloned_node, root, error_path, trail, pool));
+ }
+
+ /* Update the PARENT_PATH link to refer to the clone. */
+ parent_path->node = cloned_node;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Walk up PARENT_PATH to the root of the tree, adjusting each node's
+ mergeinfo count by COUNT_DELTA as part of Subversion transaction
+ TXN_ID and TRAIL. Use POOL for allocations. */
+static svn_error_t *
+adjust_parent_mergeinfo_counts(parent_path_t *parent_path,
+ apr_int64_t count_delta,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool;
+ parent_path_t *pp = parent_path;
+
+ if (count_delta == 0)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(pool);
+
+ while (pp)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_fs_base__dag_adjust_mergeinfo_count(pp->node, count_delta,
+ txn_id, trail,
+ iterpool));
+ pp = pp->parent;
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Open the node identified by PATH in ROOT, as part of TRAIL. Set
+ *DAG_NODE_P to the node we find, allocated in TRAIL->pool. Return
+ the error SVN_ERR_FS_NOT_FOUND if this node doesn't exist. */
+static svn_error_t *
+get_dag(dag_node_t **dag_node_p,
+ svn_fs_root_t *root,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path;
+ dag_node_t *node = NULL;
+
+ /* Canonicalize the input PATH. */
+ path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* If ROOT is a revision root, we'll look for the DAG in our cache. */
+ node = dag_node_cache_get(root, path, pool);
+ if (! node)
+ {
+ /* Call open_path with no flags, as we want this to return an error
+ if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, root, path, 0, NULL, trail, pool));
+ node = parent_path->node;
+
+ /* No need to cache our find -- open_path() will do that for us. */
+ }
+
+ *dag_node_p = node;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Populating the `changes' table. */
+
+/* Add a change to the changes table in FS, keyed on transaction id
+ TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
+ PATH (whose node revision id is--or was, in the case of a
+ deletion--NODEREV_ID), and optionally that TEXT_MODs or PROP_MODs
+ occurred. Do all this as part of TRAIL. */
+static svn_error_t *
+add_change(svn_fs_t *fs,
+ const char *txn_id,
+ const char *path,
+ const svn_fs_id_t *noderev_id,
+ svn_fs_path_change_kind_t change_kind,
+ svn_boolean_t text_mod,
+ svn_boolean_t prop_mod,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ change_t change;
+ change.path = svn_fs__canonicalize_abspath(path, pool);
+ change.noderev_id = noderev_id;
+ change.kind = change_kind;
+ change.text_mod = text_mod;
+ change.prop_mod = prop_mod;
+ return svn_fs_bdb__changes_add(fs, txn_id, &change, trail, pool);
+}
+
+
+
+/* Generic node operations. */
+
+
+struct node_id_args {
+ const svn_fs_id_t **id_p;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_id(void *baton, trail_t *trail)
+{
+ struct node_id_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ *args->id_p = svn_fs_base__id_copy(svn_fs_base__dag_get_id(node),
+ trail->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_id(const svn_fs_id_t **id_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ base_root_data_t *brd = root->fsap_data;
+
+ if (! root->is_txn_root
+ && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0'))))
+ {
+ /* Optimize the case where we don't need any db access at all.
+ The root directory ("" or "/") node is stored in the
+ svn_fs_root_t object, and never changes when it's a revision
+ root, so we can just reach in and grab it directly. */
+ *id_p = svn_fs_base__id_copy(svn_fs_base__dag_get_id(brd->root_dir),
+ pool);
+ }
+ else
+ {
+ const svn_fs_id_t *id;
+ struct node_id_args args;
+
+ args.id_p = &id;
+ args.root = root;
+ args.path = path;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_id, &args,
+ FALSE, pool));
+ *id_p = id;
+ }
+ return SVN_NO_ERROR;
+}
+
+
+struct node_created_rev_args {
+ svn_revnum_t revision;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_created_rev(void *baton, trail_t *trail)
+{
+ struct node_created_rev_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ return svn_fs_base__dag_get_revision(&(args->revision), node,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_node_created_rev(svn_revnum_t *revision,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct node_created_rev_args args;
+
+ args.revision = SVN_INVALID_REVNUM;
+ args.root = root;
+ args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn
+ (root->fs, txn_body_node_created_rev, &args, TRUE, pool));
+ *revision = args.revision;
+ return SVN_NO_ERROR;
+}
+
+
+struct node_created_path_args {
+ const char **created_path;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_created_path(void *baton, trail_t *trail)
+{
+ struct node_created_path_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ *args->created_path = svn_fs_base__dag_get_created_path(node);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_created_path(const char **created_path,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct node_created_path_args args;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ args.created_path = created_path;
+ args.root = root;
+ args.path = path;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_created_path, &args,
+ FALSE, scratch_pool));
+ if (*created_path)
+ *created_path = apr_pstrdup(pool, *created_path);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+struct node_kind_args {
+ const svn_fs_id_t *id;
+ svn_node_kind_t kind; /* OUT parameter */
+};
+
+
+static svn_error_t *
+txn_body_node_kind(void *baton, trail_t *trail)
+{
+ struct node_kind_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id,
+ trail, trail->pool));
+ args->kind = svn_fs_base__dag_node_kind(node);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+node_kind(svn_node_kind_t *kind_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct node_kind_args args;
+ const svn_fs_id_t *node_id;
+
+ /* Get the node id. */
+ SVN_ERR(base_node_id(&node_id, root, path, pool));
+
+ /* Use the node id to get the real kind. */
+ args.id = node_id;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_kind, &args,
+ TRUE, pool));
+
+ *kind_p = args.kind;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_check_path(svn_node_kind_t *kind_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = node_kind(kind_p, root, path, pool);
+ if (err &&
+ ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ *kind_p = svn_node_none;
+ }
+
+ return svn_error_trace(err);
+}
+
+
+struct node_prop_args
+{
+ svn_string_t **value_p;
+ svn_fs_root_t *root;
+ const char *path;
+ const char *propname;
+};
+
+
+static svn_error_t *
+txn_body_node_prop(void *baton,
+ trail_t *trail)
+{
+ struct node_prop_args *args = baton;
+ dag_node_t *node;
+ apr_hash_t *proplist;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node,
+ trail, trail->pool));
+ *(args->value_p) = NULL;
+ if (proplist)
+ *(args->value_p) = svn_hash_gets(proplist, args->propname);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_prop(svn_string_t **value_p,
+ svn_fs_root_t *root,
+ const char *path,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ struct node_prop_args args;
+ svn_string_t *value;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ args.value_p = &value;
+ args.root = root;
+ args.path = path;
+ args.propname = propname;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_prop, &args,
+ FALSE, scratch_pool));
+ *value_p = value ? svn_string_dup(value, pool) : NULL;
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+struct node_proplist_args {
+ apr_hash_t **table_p;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_proplist(void *baton, trail_t *trail)
+{
+ struct node_proplist_args *args = baton;
+ dag_node_t *node;
+ apr_hash_t *proplist;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node,
+ trail, trail->pool));
+ *args->table_p = proplist ? proplist : apr_hash_make(trail->pool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_proplist(apr_hash_t **table_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_hash_t *table;
+ struct node_proplist_args args;
+
+ args.table_p = &table;
+ args.root = root;
+ args.path = path;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_proplist, &args,
+ FALSE, pool));
+
+ *table_p = table;
+ return SVN_NO_ERROR;
+}
+
+
+struct change_node_prop_args {
+ svn_fs_root_t *root;
+ const char *path;
+ const char *name;
+ const svn_string_t *value;
+};
+
+
+static svn_error_t *
+txn_body_change_node_prop(void *baton,
+ trail_t *trail)
+{
+ struct change_node_prop_args *args = baton;
+ parent_path_t *parent_path;
+ apr_hash_t *proplist;
+ const char *txn_id = args->root->txn;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ SVN_ERR(open_path(&parent_path, args->root, args->path, 0, txn_id,
+ trail, trail->pool));
+
+ /* Check to see if path is locked; if so, check that we can use it.
+ Notice that we're doing this non-recursively, regardless of node kind. */
+ if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_base__allow_locked_operation
+ (args->path, FALSE, trail, trail->pool));
+
+ SVN_ERR(make_path_mutable(args->root, parent_path, args->path,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, parent_path->node,
+ trail, trail->pool));
+
+ /* If there's no proplist, but we're just deleting a property, exit now. */
+ if ((! proplist) && (! args->value))
+ return SVN_NO_ERROR;
+
+ /* Now, if there's no proplist, we know we need to make one. */
+ if (! proplist)
+ proplist = apr_hash_make(trail->pool);
+
+ /* Set the property. */
+ svn_hash_sets(proplist, args->name, args->value);
+
+ /* Overwrite the node's proplist. */
+ SVN_ERR(svn_fs_base__dag_set_proplist(parent_path->node, proplist,
+ txn_id, trail, trail->pool));
+
+ /* If this was a change to the mergeinfo property, and our version
+ of the filesystem cares, we have some extra recording to do.
+
+ ### If the format *doesn't* support mergeinfo recording, should
+ ### we fuss about attempts to change the svn:mergeinfo property
+ ### in any way save to delete it? */
+ if ((bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ && (strcmp(args->name, SVN_PROP_MERGEINFO) == 0))
+ {
+ svn_boolean_t had_mergeinfo, has_mergeinfo = args->value != NULL;
+
+ /* First, note on our node that it has mergeinfo. */
+ SVN_ERR(svn_fs_base__dag_set_has_mergeinfo(parent_path->node,
+ has_mergeinfo,
+ &had_mergeinfo, txn_id,
+ trail, trail->pool));
+
+ /* If this is a change from the old state, we need to update our
+ node's parents' mergeinfo counts by a factor of 1. */
+ if (parent_path->parent && ((! had_mergeinfo) != (! has_mergeinfo)))
+ SVN_ERR(adjust_parent_mergeinfo_counts(parent_path->parent,
+ has_mergeinfo ? 1 : -1,
+ txn_id, trail, trail->pool));
+ }
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(args->root->fs, txn_id,
+ args->path, svn_fs_base__dag_get_id(parent_path->node),
+ svn_fs_path_change_modify, FALSE, TRUE, trail,
+ trail->pool);
+}
+
+
+static svn_error_t *
+base_change_node_prop(svn_fs_root_t *root,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct change_node_prop_args args;
+
+ if (! root->is_txn_root)
+ return SVN_FS__NOT_TXN(root);
+
+ args.root = root;
+ args.path = path;
+ args.name = name;
+ args.value = value;
+ return svn_fs_base__retry_txn(root->fs, txn_body_change_node_prop, &args,
+ TRUE, pool);
+}
+
+
+struct things_changed_args
+{
+ svn_boolean_t *changed_p;
+ svn_fs_root_t *root1;
+ svn_fs_root_t *root2;
+ const char *path1;
+ const char *path2;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+txn_body_props_changed(void *baton, trail_t *trail)
+{
+ struct things_changed_args *args = baton;
+ dag_node_t *node1, *node2;
+
+ SVN_ERR(get_dag(&node1, args->root1, args->path1, trail, trail->pool));
+ SVN_ERR(get_dag(&node2, args->root2, args->path2, trail, trail->pool));
+ return svn_fs_base__things_different(args->changed_p, NULL,
+ node1, node2, trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_props_changed(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ struct things_changed_args args;
+
+ /* Check that roots are in the same fs. */
+ if (root1->fs != root2->fs)
+ return svn_error_create
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Cannot compare property value between two different filesystems"));
+
+ args.root1 = root1;
+ args.root2 = root2;
+ args.path1 = path1;
+ args.path2 = path2;
+ args.changed_p = changed_p;
+ args.pool = pool;
+
+ return svn_fs_base__retry_txn(root1->fs, txn_body_props_changed, &args,
+ TRUE, pool);
+}
+
+
+
+/* Miscellaneous table handling */
+
+struct miscellaneous_set_args
+{
+ const char *key;
+ const char *val;
+};
+
+static svn_error_t *
+txn_body_miscellaneous_set(void *baton, trail_t *trail)
+{
+ struct miscellaneous_set_args *msa = baton;
+
+ return svn_fs_bdb__miscellaneous_set(trail->fs, msa->key, msa->val, trail,
+ trail->pool);
+}
+
+svn_error_t *
+svn_fs_base__miscellaneous_set(svn_fs_t *fs,
+ const char *key,
+ const char *val,
+ apr_pool_t *pool)
+{
+ struct miscellaneous_set_args msa;
+ msa.key = key;
+ msa.val = val;
+
+ return svn_fs_base__retry_txn(fs, txn_body_miscellaneous_set, &msa,
+ TRUE, pool);
+}
+
+struct miscellaneous_get_args
+{
+ const char *key;
+ const char **val;
+};
+
+static svn_error_t *
+txn_body_miscellaneous_get(void *baton, trail_t *trail)
+{
+ struct miscellaneous_get_args *mga = baton;
+ return svn_fs_bdb__miscellaneous_get(mga->val, trail->fs, mga->key, trail,
+ trail->pool);
+}
+
+svn_error_t *
+svn_fs_base__miscellaneous_get(const char **val,
+ svn_fs_t *fs,
+ const char *key,
+ apr_pool_t *pool)
+{
+ struct miscellaneous_get_args mga;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ mga.key = key;
+ mga.val = val;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_miscellaneous_get, &mga,
+ FALSE, scratch_pool));
+ if (*val)
+ *val = apr_pstrdup(pool, *val);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Getting a directory's entries */
+
+
+struct dir_entries_args
+{
+ apr_hash_t **table_p;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+/* *(BATON->table_p) will never be NULL on successful return */
+static svn_error_t *
+txn_body_dir_entries(void *baton,
+ trail_t *trail)
+{
+ struct dir_entries_args *args = baton;
+ dag_node_t *node;
+ apr_hash_t *entries;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+
+ /* Get the entries for PARENT_PATH. */
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, trail->pool));
+
+ /* Potentially initialize the return value to an empty hash. */
+ *args->table_p = entries ? entries : apr_hash_make(trail->pool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_dir_entries(apr_hash_t **table_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct dir_entries_args args;
+ apr_pool_t *iterpool;
+ apr_hash_t *table;
+ svn_fs_t *fs = root->fs;
+ apr_hash_index_t *hi;
+
+ args.table_p = &table;
+ args.root = root;
+ args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_dir_entries, &args,
+ FALSE, pool));
+
+ iterpool = svn_pool_create(pool);
+
+ /* Add in the kind data. */
+ for (hi = apr_hash_first(pool, table); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *entry;
+ struct node_kind_args nk_args;
+ void *val;
+
+ svn_pool_clear(iterpool);
+
+ /* KEY will be the entry name in ancestor (about which we
+ simply don't care), VAL the dirent. */
+ apr_hash_this(hi, NULL, NULL, &val);
+ entry = val;
+ nk_args.id = entry->id;
+
+ /* We don't need to have the retry function destroy the trail
+ pool because we're already doing that via the use of an
+ iteration pool. */
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_node_kind, &nk_args,
+ FALSE, iterpool));
+ entry->kind = nk_args.kind;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ *table_p = table;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Merges and commits. */
+
+
+struct deltify_committed_args
+{
+ svn_fs_t *fs; /* the filesystem */
+ svn_revnum_t rev; /* revision just committed */
+ const char *txn_id; /* transaction just committed */
+};
+
+
+struct txn_deltify_args
+{
+ /* The transaction ID whose nodes are being deltified. */
+ const char *txn_id;
+
+ /* The target is what we're deltifying. */
+ const svn_fs_id_t *tgt_id;
+
+ /* The base is what we're deltifying against. It's not necessarily
+ the "next" revision of the node; skip deltas mean we sometimes
+ deltify against a successor many generations away. This may be
+ NULL, in which case we'll avoid deltification and simply index
+ TGT_ID's data checksum. */
+ const svn_fs_id_t *base_id;
+
+ /* We only deltify props for directories.
+ ### Didn't we try removing this horrid little optimization once?
+ ### What was the result? I would have thought that skip deltas
+ ### mean directory undeltification is cheap enough now. */
+ svn_boolean_t is_dir;
+};
+
+
+static svn_error_t *
+txn_body_txn_deltify(void *baton, trail_t *trail)
+{
+ struct txn_deltify_args *args = baton;
+ dag_node_t *tgt_node, *base_node;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ SVN_ERR(svn_fs_base__dag_get_node(&tgt_node, trail->fs, args->tgt_id,
+ trail, trail->pool));
+ /* If we have something to deltify against, do so. */
+ if (args->base_id)
+ {
+ SVN_ERR(svn_fs_base__dag_get_node(&base_node, trail->fs, args->base_id,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_deltify(tgt_node, base_node, args->is_dir,
+ args->txn_id, trail, trail->pool));
+ }
+
+ /* If we support rep sharing, and this isn't a directory, record a
+ mapping of TGT_NODE's data checksum to its representation key. */
+ if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
+ SVN_ERR(svn_fs_base__dag_index_checksums(tgt_node, trail, trail->pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+struct txn_pred_count_args
+{
+ const svn_fs_id_t *id;
+ int pred_count;
+};
+
+
+static svn_error_t *
+txn_body_pred_count(void *baton, trail_t *trail)
+{
+ node_revision_t *noderev;
+ struct txn_pred_count_args *args = baton;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, trail->fs,
+ args->id, trail, trail->pool));
+ args->pred_count = noderev->predecessor_count;
+ return SVN_NO_ERROR;
+}
+
+
+struct txn_pred_id_args
+{
+ const svn_fs_id_t *id; /* The node id whose predecessor we want. */
+ const svn_fs_id_t *pred_id; /* The returned predecessor id. */
+ apr_pool_t *pool; /* The pool in which to allocate pred_id. */
+};
+
+
+static svn_error_t *
+txn_body_pred_id(void *baton, trail_t *trail)
+{
+ node_revision_t *nr;
+ struct txn_pred_id_args *args = baton;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&nr, trail->fs, args->id,
+ trail, trail->pool));
+ if (nr->predecessor_id)
+ args->pred_id = svn_fs_base__id_copy(nr->predecessor_id, args->pool);
+ else
+ args->pred_id = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Deltify PATH in ROOT's predecessor iff PATH is mutable under TXN_ID
+ in FS. If PATH is a mutable directory, recurse.
+
+ NODE_ID is the node revision ID for PATH in ROOT, or NULL if that
+ value isn't known. KIND is the node kind for PATH in ROOT, or
+ svn_node_unknown is the kind isn't known.
+
+ Use POOL for necessary allocations. */
+static svn_error_t *
+deltify_mutable(svn_fs_t *fs,
+ svn_fs_root_t *root,
+ const char *path,
+ const svn_fs_id_t *node_id,
+ svn_node_kind_t kind,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *id = node_id;
+ apr_hash_t *entries = NULL;
+ struct txn_deltify_args td_args;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* Get the ID for PATH under ROOT if it wasn't provided. */
+ if (! node_id)
+ SVN_ERR(base_node_id(&id, root, path, pool));
+
+ /* Check for mutability. Not mutable? Go no further. This is safe
+ to do because for items in the tree to be mutable, their parent
+ dirs must also be mutable. Therefore, if a directory is not
+ mutable under TXN_ID, its children cannot be. */
+ if (strcmp(svn_fs_base__id_txn_id(id), txn_id))
+ return SVN_NO_ERROR;
+
+ /* Is this a directory? */
+ if (kind == svn_node_unknown)
+ SVN_ERR(base_check_path(&kind, root, path, pool));
+
+ /* If this is a directory, read its entries. */
+ if (kind == svn_node_dir)
+ SVN_ERR(base_dir_entries(&entries, root, path, pool));
+
+ /* If there are entries, recurse on 'em. */
+ if (entries)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ /* KEY will be the entry name, VAL the dirent */
+ const void *key;
+ void *val;
+ svn_fs_dirent_t *entry;
+ svn_pool_clear(subpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ entry = val;
+ SVN_ERR(deltify_mutable(fs, root,
+ svn_fspath__join(path, key, subpool),
+ entry->id, entry->kind, txn_id, subpool));
+ }
+
+ svn_pool_destroy(subpool);
+ }
+
+ /* Index ID's data checksum. */
+ td_args.txn_id = txn_id;
+ td_args.tgt_id = id;
+ td_args.base_id = NULL;
+ td_args.is_dir = (kind == svn_node_dir);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args,
+ TRUE, pool));
+
+ /* Finally, deltify old data against this node. */
+ {
+ /* Prior to 1.6, we use the following algorithm to deltify nodes:
+
+ Redeltify predecessor node-revisions of the one we added. The
+ idea is to require at most 2*lg(N) deltas to be applied to get
+ to any node-revision in a chain of N predecessors. We do this
+ using a technique derived from skip lists:
+
+ - Always redeltify the immediate parent
+
+ - If the number of predecessors is divisible by 2,
+ redeltify the revision two predecessors back
+
+ - If the number of predecessors is divisible by 4,
+ redeltify the revision four predecessors back
+
+ ... and so on.
+
+ That's the theory, anyway. Unfortunately, if we strictly
+ follow that theory we get a bunch of overhead up front and no
+ great benefit until the number of predecessors gets large. So,
+ stop at redeltifying the parent if the number of predecessors
+ is less than 32, and also skip the second level (redeltifying
+ two predecessors back), since that doesn't help much. Also,
+ don't redeltify the oldest node-revision; it's potentially
+ expensive and doesn't help retrieve any other revision.
+ (Retrieving the oldest node-revision will still be fast, just
+ not as blindingly so.)
+
+ For 1.6 and beyond, we just deltify the current node against its
+ predecessors, using skip deltas similar to the way FSFS does. */
+
+ int pred_count;
+ const svn_fs_id_t *pred_id;
+ struct txn_pred_count_args tpc_args;
+ apr_pool_t *subpools[2];
+ int active_subpool = 0;
+ svn_revnum_t forward_delta_rev = 0;
+
+ tpc_args.id = id;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_count, &tpc_args,
+ TRUE, pool));
+ pred_count = tpc_args.pred_count;
+
+ /* If nothing to deltify, then we're done. */
+ if (pred_count == 0)
+ return SVN_NO_ERROR;
+
+ subpools[0] = svn_pool_create(pool);
+ subpools[1] = svn_pool_create(pool);
+
+ /* If we support the 'miscellaneous' table, check it to see if
+ there is a point in time before which we don't want to do
+ deltification. */
+ /* ### FIXME: I think this is an unnecessary restriction. We
+ ### should be able to do something meaningful for most
+ ### deltification requests -- what that is depends on the
+ ### directory of the deltas for that revision, though. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT)
+ {
+ const char *val;
+ SVN_ERR(svn_fs_base__miscellaneous_get
+ (&val, fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE, pool));
+ if (val)
+ SVN_ERR(svn_revnum_parse(&forward_delta_rev, val, NULL));
+ }
+
+ if (bfd->format >= SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT
+ && forward_delta_rev <= root->rev)
+ {
+ /**** FORWARD DELTA STORAGE ****/
+
+ /* Decide which predecessor to deltify against. Flip the rightmost '1'
+ bit of the predecessor count to determine which file rev (counting
+ from 0) we want to use. (To see why count & (count - 1) unsets the
+ rightmost set bit, think about how you decrement a binary number. */
+ pred_count = pred_count & (pred_count - 1);
+
+ /* Walk back a number of predecessors equal to the difference between
+ pred_count and the original predecessor count. (For example, if
+ the node has ten predecessors and we want the eighth node, walk back
+ two predecessors. */
+ pred_id = id;
+
+ /* We need to use two alternating pools because the id used in the
+ call to txn_body_pred_id is allocated by the previous inner
+ loop iteration. If we would clear the pool each iteration we
+ would free the previous result. */
+ while ((pred_count++) < tpc_args.pred_count)
+ {
+ struct txn_pred_id_args tpi_args;
+
+ active_subpool = !active_subpool;
+ svn_pool_clear(subpools[active_subpool]);
+
+ tpi_args.id = pred_id;
+ tpi_args.pool = subpools[active_subpool];
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id, &tpi_args,
+ FALSE, subpools[active_subpool]));
+ pred_id = tpi_args.pred_id;
+
+ if (pred_id == NULL)
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: faulty predecessor count"));
+
+ }
+
+ /* Finally, do the deltification. */
+ td_args.txn_id = txn_id;
+ td_args.tgt_id = id;
+ td_args.base_id = pred_id;
+ td_args.is_dir = (kind == svn_node_dir);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args,
+ TRUE, subpools[active_subpool]));
+ }
+ else
+ {
+ int nlevels, lev, count;
+
+ /**** REVERSE DELTA STORAGE ****/
+
+ /* Decide how many predecessors to redeltify. To save overhead,
+ don't redeltify anything but the immediate predecessor if there
+ are less than 32 predecessors. */
+ nlevels = 1;
+ if (pred_count >= 32)
+ {
+ while (pred_count % 2 == 0)
+ {
+ pred_count /= 2;
+ nlevels++;
+ }
+
+ /* Don't redeltify the oldest revision. */
+ if (1 << (nlevels - 1) == pred_count)
+ nlevels--;
+ }
+
+ /* Redeltify the desired number of predecessors. */
+ count = 0;
+ pred_id = id;
+
+ /* We need to use two alternating pools because the id used in the
+ call to txn_body_pred_id is allocated by the previous inner
+ loop iteration. If we would clear the pool each iteration we
+ would free the previous result. */
+ for (lev = 0; lev < nlevels; lev++)
+ {
+ /* To save overhead, skip the second level (that is, never
+ redeltify the node-revision two predecessors back). */
+ if (lev == 1)
+ continue;
+
+ /* Note that COUNT is not reset between levels, and neither is
+ PREDNODE; we just keep counting from where we were up to
+ where we're supposed to get. */
+ while (count < (1 << lev))
+ {
+ struct txn_pred_id_args tpi_args;
+
+ active_subpool = !active_subpool;
+ svn_pool_clear(subpools[active_subpool]);
+
+ tpi_args.id = pred_id;
+ tpi_args.pool = subpools[active_subpool];
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id,
+ &tpi_args, FALSE,
+ subpools[active_subpool]));
+ pred_id = tpi_args.pred_id;
+
+ if (pred_id == NULL)
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: faulty predecessor count"));
+
+ count++;
+ }
+
+ /* Finally, do the deltification. */
+ td_args.txn_id = NULL; /* Don't require mutable reps */
+ td_args.tgt_id = pred_id;
+ td_args.base_id = id;
+ td_args.is_dir = (kind == svn_node_dir);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args,
+ TRUE, subpools[active_subpool]));
+
+ }
+ }
+
+ svn_pool_destroy(subpools[0]);
+ svn_pool_destroy(subpools[1]);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct get_root_args
+{
+ svn_fs_root_t *root;
+ dag_node_t *node;
+};
+
+
+/* Set ARGS->node to the root node of ARGS->root. */
+static svn_error_t *
+txn_body_get_root(void *baton, trail_t *trail)
+{
+ struct get_root_args *args = baton;
+ return get_dag(&(args->node), args->root, "", trail, trail->pool);
+}
+
+
+
+static svn_error_t *
+update_ancestry(svn_fs_t *fs,
+ const svn_fs_id_t *source_id,
+ const svn_fs_id_t *target_id,
+ const char *txn_id,
+ const char *target_path,
+ int source_pred_count,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Set target's predecessor-id to source_id. */
+ if (strcmp(svn_fs_base__id_txn_id(target_id), txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Unexpected immutable node at '%s'"), target_path);
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, target_id,
+ trail, pool));
+ noderev->predecessor_id = source_id;
+ noderev->predecessor_count = source_pred_count;
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ return svn_fs_bdb__put_node_revision(fs, target_id, noderev, trail, pool);
+}
+
+
+/* Set the contents of CONFLICT_PATH to PATH, and return an
+ SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
+ at PATH. Perform all allocations in POOL (except the allocation of
+ CONFLICT_PATH, which should be handled outside this function). */
+static svn_error_t *
+conflict_err(svn_stringbuf_t *conflict_path,
+ const char *path)
+{
+ svn_stringbuf_set(conflict_path, path);
+ return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
+ _("Conflict at '%s'"), path);
+}
+
+
+/* Merge changes between ANCESTOR and SOURCE into TARGET as part of
+ * TRAIL. ANCESTOR and TARGET must be distinct node revisions.
+ * TARGET_PATH should correspond to TARGET's full path in its
+ * filesystem, and is used for reporting conflict location.
+ *
+ * SOURCE, TARGET, and ANCESTOR are generally directories; this
+ * function recursively merges the directories' contents. If any are
+ * files, this function simply returns an error whenever SOURCE,
+ * TARGET, and ANCESTOR are all distinct node revisions.
+ *
+ * If there are differences between ANCESTOR and SOURCE that conflict
+ * with changes between ANCESTOR and TARGET, this function returns an
+ * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
+ * conflicting node in TARGET, with TARGET_PATH prepended as a path.
+ *
+ * If there are no conflicting differences, CONFLICT_P is updated to
+ * the empty string.
+ *
+ * CONFLICT_P must point to a valid svn_stringbuf_t.
+ *
+ * Do any necessary temporary allocation in POOL.
+ */
+static svn_error_t *
+merge(svn_stringbuf_t *conflict_p,
+ const char *target_path,
+ dag_node_t *target,
+ dag_node_t *source,
+ dag_node_t *ancestor,
+ const char *txn_id,
+ apr_int64_t *mergeinfo_increment_out,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *source_id, *target_id, *ancestor_id;
+ apr_hash_t *s_entries, *t_entries, *a_entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ svn_fs_t *fs;
+ int pred_count;
+ apr_int64_t mergeinfo_increment = 0;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ /* Make sure everyone comes from the same filesystem. */
+ fs = svn_fs_base__dag_get_fs(ancestor);
+ if ((fs != svn_fs_base__dag_get_fs(source))
+ || (fs != svn_fs_base__dag_get_fs(target)))
+ {
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Bad merge; ancestor, source, and target not all in same fs"));
+ }
+
+ /* We have the same fs, now check it. */
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ source_id = svn_fs_base__dag_get_id(source);
+ target_id = svn_fs_base__dag_get_id(target);
+ ancestor_id = svn_fs_base__dag_get_id(ancestor);
+
+ /* It's improper to call this function with ancestor == target. */
+ if (svn_fs_base__id_eq(ancestor_id, target_id))
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(target_id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Bad merge; target '%s' has id '%s', same as ancestor"),
+ target_path, id_str->data);
+ }
+
+ svn_stringbuf_setempty(conflict_p);
+
+ /* Base cases:
+ * Either no change made in source, or same change as made in target.
+ * Both mean nothing to merge here.
+ */
+ if (svn_fs_base__id_eq(ancestor_id, source_id)
+ || (svn_fs_base__id_eq(source_id, target_id)))
+ return SVN_NO_ERROR;
+
+ /* Else proceed, knowing all three are distinct node revisions.
+ *
+ * How to merge from this point:
+ *
+ * if (not all 3 are directories)
+ * {
+ * early exit with conflict;
+ * }
+ *
+ * // Property changes may only be made to up-to-date
+ * // directories, because once the client commits the prop
+ * // change, it bumps the directory's revision, and therefore
+ * // must be able to depend on there being no other changes to
+ * // that directory in the repository.
+ * if (target's property list differs from ancestor's)
+ * conflict;
+ *
+ * For each entry NAME in the directory ANCESTOR:
+ *
+ * Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
+ * the name within ANCESTOR, SOURCE, and TARGET respectively.
+ * (Possibly null if NAME does not exist in SOURCE or TARGET.)
+ *
+ * If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
+ * No changes were made to this entry while the transaction was in
+ * progress, so do nothing to the target.
+ *
+ * Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
+ * A change was made to this entry while the transaction was in
+ * process, but the transaction did not touch this entry. Replace
+ * TARGET-ENTRY with SOURCE-ENTRY.
+ *
+ * Else:
+ * Changes were made to this entry both within the transaction and
+ * to the repository while the transaction was in progress. They
+ * must be merged or declared to be in conflict.
+ *
+ * If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
+ * double delete; flag a conflict.
+ *
+ * If any of the three entries is of type file, declare a conflict.
+ *
+ * If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
+ * modification of ANCESTOR-ENTRY (determine by comparing the
+ * node-id fields), declare a conflict. A replacement is
+ * incompatible with a modification or other replacement--even
+ * an identical replacement.
+ *
+ * Direct modifications were made to the directory ANCESTOR-ENTRY
+ * in both SOURCE and TARGET. Recursively merge these
+ * modifications.
+ *
+ * For each leftover entry NAME in the directory SOURCE:
+ *
+ * If NAME exists in TARGET, declare a conflict. Even if SOURCE and
+ * TARGET are adding exactly the same thing, two additions are not
+ * auto-mergeable with each other.
+ *
+ * Add NAME to TARGET with the entry from SOURCE.
+ *
+ * Now that we are done merging the changes from SOURCE into the
+ * directory TARGET, update TARGET's predecessor to be SOURCE.
+ */
+
+ if ((svn_fs_base__dag_node_kind(source) != svn_node_dir)
+ || (svn_fs_base__dag_node_kind(target) != svn_node_dir)
+ || (svn_fs_base__dag_node_kind(ancestor) != svn_node_dir))
+ {
+ return conflict_err(conflict_p, target_path);
+ }
+
+
+ /* Possible early merge failure: if target and ancestor have
+ different property lists, then the merge should fail.
+ Propchanges can *only* be committed on an up-to-date directory.
+ ### TODO: see issue #418 about the inelegance of this.
+
+ Another possible, similar, early merge failure: if source and
+ ancestor have different property lists (meaning someone else
+ changed directory properties while our commit transaction was
+ happening), the merge should fail. See issue #2751.
+ */
+ {
+ node_revision_t *tgt_nr, *anc_nr, *src_nr;
+
+ /* Get node revisions for our id's. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&tgt_nr, fs, target_id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&anc_nr, fs, ancestor_id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&src_nr, fs, source_id,
+ trail, pool));
+
+ /* Now compare the prop-keys of the skels. Note that just because
+ the keys are different -doesn't- mean the proplists have
+ different contents. But merge() isn't concerned with contents;
+ it doesn't do a brute-force comparison on textual contents, so
+ it won't do that here either. Checking to see if the propkey
+ atoms are `equal' is enough. */
+ if (! svn_fs_base__same_keys(tgt_nr->prop_key, anc_nr->prop_key))
+ return conflict_err(conflict_p, target_path);
+ if (! svn_fs_base__same_keys(src_nr->prop_key, anc_nr->prop_key))
+ return conflict_err(conflict_p, target_path);
+ }
+
+ /* ### todo: it would be more efficient to simply check for a NULL
+ entries hash where necessary below than to allocate an empty hash
+ here, but another day, another day... */
+ SVN_ERR(svn_fs_base__dag_dir_entries(&s_entries, source, trail, pool));
+ if (! s_entries)
+ s_entries = apr_hash_make(pool);
+ SVN_ERR(svn_fs_base__dag_dir_entries(&t_entries, target, trail, pool));
+ if (! t_entries)
+ t_entries = apr_hash_make(pool);
+ SVN_ERR(svn_fs_base__dag_dir_entries(&a_entries, ancestor, trail, pool));
+ if (! a_entries)
+ a_entries = apr_hash_make(pool);
+
+ /* for each entry E in a_entries... */
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, a_entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *s_entry, *t_entry, *a_entry;
+
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+
+ svn_pool_clear(iterpool);
+
+ /* KEY will be the entry name in ancestor, VAL the dirent */
+ apr_hash_this(hi, &key, &klen, &val);
+ a_entry = val;
+
+ s_entry = apr_hash_get(s_entries, key, klen);
+ t_entry = apr_hash_get(t_entries, key, klen);
+
+ /* No changes were made to this entry while the transaction was
+ in progress, so do nothing to the target. */
+ if (s_entry && svn_fs_base__id_eq(a_entry->id, s_entry->id))
+ goto end;
+
+ /* A change was made to this entry while the transaction was in
+ process, but the transaction did not touch this entry. */
+ else if (t_entry && svn_fs_base__id_eq(a_entry->id, t_entry->id))
+ {
+ dag_node_t *t_ent_node;
+ apr_int64_t mergeinfo_start;
+ SVN_ERR(svn_fs_base__dag_get_node(&t_ent_node, fs,
+ t_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_start,
+ t_ent_node, trail,
+ iterpool));
+ mergeinfo_increment -= mergeinfo_start;
+
+ if (s_entry)
+ {
+ dag_node_t *s_ent_node;
+ apr_int64_t mergeinfo_end;
+ SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs,
+ s_entry->id, trail,
+ iterpool));
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL,
+ &mergeinfo_end,
+ s_ent_node, trail,
+ iterpool));
+ mergeinfo_increment += mergeinfo_end;
+ SVN_ERR(svn_fs_base__dag_set_entry(target, key, s_entry->id,
+ txn_id, trail, iterpool));
+ }
+ else
+ {
+ SVN_ERR(svn_fs_base__dag_delete(target, key, txn_id,
+ trail, iterpool));
+ }
+ }
+
+ /* Changes were made to this entry both within the transaction
+ and to the repository while the transaction was in progress.
+ They must be merged or declared to be in conflict. */
+ else
+ {
+ dag_node_t *s_ent_node, *t_ent_node, *a_ent_node;
+ const char *new_tpath;
+ apr_int64_t sub_mergeinfo_increment;
+
+ /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
+ double delete; if one of them is null, that's a delete versus
+ a modification. In any of these cases, flag a conflict. */
+ if (s_entry == NULL || t_entry == NULL)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
+ modification of ANCESTOR-ENTRY, declare a conflict. */
+ if (strcmp(svn_fs_base__id_node_id(s_entry->id),
+ svn_fs_base__id_node_id(a_entry->id)) != 0
+ || strcmp(svn_fs_base__id_copy_id(s_entry->id),
+ svn_fs_base__id_copy_id(a_entry->id)) != 0
+ || strcmp(svn_fs_base__id_node_id(t_entry->id),
+ svn_fs_base__id_node_id(a_entry->id)) != 0
+ || strcmp(svn_fs_base__id_copy_id(t_entry->id),
+ svn_fs_base__id_copy_id(a_entry->id)) != 0)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* Fetch the nodes for our entries. */
+ SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs,
+ s_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_node(&t_ent_node, fs,
+ t_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_node(&a_ent_node, fs,
+ a_entry->id, trail, iterpool));
+
+ /* If any of the three entries is of type file, flag a conflict. */
+ if ((svn_fs_base__dag_node_kind(s_ent_node) == svn_node_file)
+ || (svn_fs_base__dag_node_kind(t_ent_node) == svn_node_file)
+ || (svn_fs_base__dag_node_kind(a_ent_node) == svn_node_file))
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* Direct modifications were made to the directory
+ ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively
+ merge these modifications. */
+ new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool);
+ SVN_ERR(merge(conflict_p, new_tpath,
+ t_ent_node, s_ent_node, a_ent_node,
+ txn_id, &sub_mergeinfo_increment, trail, iterpool));
+ mergeinfo_increment += sub_mergeinfo_increment;
+ }
+
+ /* We've taken care of any possible implications E could have.
+ Remove it from source_entries, so it's easy later to loop
+ over all the source entries that didn't exist in
+ ancestor_entries. */
+ end:
+ apr_hash_set(s_entries, key, klen, NULL);
+ }
+
+ /* For each entry E in source but not in ancestor */
+ for (hi = apr_hash_first(pool, s_entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *s_entry, *t_entry;
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+ dag_node_t *s_ent_node;
+ apr_int64_t mergeinfo_s;
+
+ svn_pool_clear(iterpool);
+
+ apr_hash_this(hi, &key, &klen, &val);
+ s_entry = val;
+ t_entry = apr_hash_get(t_entries, key, klen);
+
+ /* If NAME exists in TARGET, declare a conflict. */
+ if (t_entry)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ t_entry->name,
+ iterpool));
+
+ SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs,
+ s_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_s,
+ s_ent_node, trail,
+ iterpool));
+ mergeinfo_increment += mergeinfo_s;
+ SVN_ERR(svn_fs_base__dag_set_entry
+ (target, s_entry->name, s_entry->id, txn_id, trail, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Now that TARGET has absorbed all of the history between ANCESTOR
+ and SOURCE, we can update its predecessor to point to SOURCE. */
+ SVN_ERR(svn_fs_base__dag_get_predecessor_count(&pred_count, source,
+ trail, pool));
+ SVN_ERR(update_ancestry(fs, source_id, target_id, txn_id, target_path,
+ pred_count, trail, pool));
+
+ /* Tweak mergeinfo data if our format supports it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ {
+ SVN_ERR(svn_fs_base__dag_adjust_mergeinfo_count(target,
+ mergeinfo_increment,
+ txn_id, trail, pool));
+ }
+
+ if (mergeinfo_increment_out)
+ *mergeinfo_increment_out = mergeinfo_increment;
+
+ return SVN_NO_ERROR;
+}
+
+
+struct merge_args
+{
+ /* The ancestor for the merge. If this is null, then TXN's base is
+ used as the ancestor for the merge. */
+ dag_node_t *ancestor_node;
+
+ /* This is the SOURCE node for the merge. It may not be null. */
+ dag_node_t *source_node;
+
+ /* This is the TARGET of the merge. It may not be null. If
+ ancestor_node above is null, then this txn's base is used as the
+ ancestor for the merge. */
+ svn_fs_txn_t *txn;
+
+ /* If a conflict results, this is updated to the path in the txn that
+ conflicted. It must point to a valid svn_stringbuf_t before calling
+ svn_fs_base__retry_txn, as this determines the pool used to allocate any
+ required memory. */
+ svn_stringbuf_t *conflict;
+};
+
+
+/* Merge changes between an ancestor and BATON->source_node into
+ BATON->txn. The ancestor is either BATON->ancestor_node, or if
+ that is null, BATON->txn's base node.
+
+ If the merge is successful, BATON->txn's base will become
+ BATON->source_node, and its root node will have a new ID, a
+ successor of BATON->source_node. */
+static svn_error_t *
+txn_body_merge(void *baton, trail_t *trail)
+{
+ struct merge_args *args = baton;
+ dag_node_t *source_node, *txn_root_node, *ancestor_node;
+ const svn_fs_id_t *source_id;
+ svn_fs_t *fs = args->txn->fs;
+ const char *txn_id = args->txn->id;
+
+ source_node = args->source_node;
+ ancestor_node = args->ancestor_node;
+ source_id = svn_fs_base__dag_get_id(source_node);
+
+ SVN_ERR(svn_fs_base__dag_txn_root(&txn_root_node, fs, txn_id,
+ trail, trail->pool));
+
+ if (ancestor_node == NULL)
+ {
+ SVN_ERR(svn_fs_base__dag_txn_base_root(&ancestor_node, fs,
+ txn_id, trail, trail->pool));
+ }
+
+ if (svn_fs_base__id_eq(svn_fs_base__dag_get_id(ancestor_node),
+ svn_fs_base__dag_get_id(txn_root_node)))
+ {
+ /* If no changes have been made in TXN since its current base,
+ then it can't conflict with any changes since that base. So
+ we just set *both* its base and root to source, making TXN
+ in effect a repeat of source. */
+
+ /* ### kff todo: this would, of course, be a mighty silly thing
+ for the caller to do, and we might want to consider whether
+ this response is really appropriate. */
+
+ SVN_ERR(svn_fs_base__set_txn_base(fs, txn_id, source_id,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, source_id,
+ trail, trail->pool));
+ }
+ else
+ {
+ int pred_count;
+
+ SVN_ERR(merge(args->conflict, "/", txn_root_node, source_node,
+ ancestor_node, txn_id, NULL, trail, trail->pool));
+
+ SVN_ERR(svn_fs_base__dag_get_predecessor_count(&pred_count,
+ source_node, trail,
+ trail->pool));
+
+ /* After the merge, txn's new "ancestor" is now really the node
+ at source_id, so record that fact. Think of this as
+ ratcheting the txn forward in time, so it can't backslide and
+ forget the merging work that's already been done. */
+ SVN_ERR(update_ancestry(fs, source_id,
+ svn_fs_base__dag_get_id(txn_root_node),
+ txn_id, "/", pred_count, trail, trail->pool));
+ SVN_ERR(svn_fs_base__set_txn_base(fs, txn_id, source_id,
+ trail, trail->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Verify that there are registered with TRAIL->fs all the locks
+ necessary to permit all the changes associated with TXN_NAME. */
+static svn_error_t *
+verify_locks(const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *changes;
+ apr_hash_index_t *hi;
+ apr_array_header_t *changed_paths;
+ svn_stringbuf_t *last_recursed = NULL;
+ int i;
+
+ /* Fetch the changes for this transaction. */
+ SVN_ERR(svn_fs_bdb__changes_fetch(&changes, trail->fs, txn_name,
+ trail, pool));
+
+ /* Make an array of the changed paths, and sort them depth-first-ily. */
+ changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
+ sizeof(const char *));
+ for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_hash_this(hi, &key, NULL, NULL);
+ APR_ARRAY_PUSH(changed_paths, const char *) = key;
+ }
+ qsort(changed_paths->elts, changed_paths->nelts,
+ changed_paths->elt_size, svn_sort_compare_paths);
+
+ /* Now, traverse the array of changed paths, verify locks. Note
+ that if we need to do a recursive verification a path, we'll skip
+ over children of that path when we get to them. */
+ for (i = 0; i < changed_paths->nelts; i++)
+ {
+ const char *path;
+ svn_fs_path_change2_t *change;
+ svn_boolean_t recurse = TRUE;
+
+ svn_pool_clear(subpool);
+ path = APR_ARRAY_IDX(changed_paths, i, const char *);
+
+ /* If this path has already been verified as part of a recursive
+ check of one of its parents, no need to do it again. */
+ if (last_recursed
+ && svn_fspath__skip_ancestor(last_recursed->data, path))
+ continue;
+
+ /* Fetch the change associated with our path. */
+ change = svn_hash_gets(changes, path);
+
+ /* What does it mean to succeed at lock verification for a given
+ path? For an existing file or directory getting modified
+ (text, props), it means we hold the lock on the file or
+ directory. For paths being added or removed, we need to hold
+ the locks for that path and any children of that path.
+
+ WHEW! We have no reliable way to determine the node kind of
+ deleted items, but fortunately we are going to do a recursive
+ check on deleted paths regardless of their kind. */
+ if (change->change_kind == svn_fs_path_change_modify)
+ recurse = FALSE;
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, recurse,
+ trail, subpool));
+
+ /* If we just did a recursive check, remember the path we
+ checked (so children can be skipped). */
+ if (recurse)
+ {
+ if (! last_recursed)
+ last_recursed = svn_stringbuf_create(path, pool);
+ else
+ svn_stringbuf_set(last_recursed, path);
+ }
+ }
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+struct commit_args
+{
+ svn_fs_txn_t *txn;
+ svn_revnum_t new_rev;
+};
+
+
+/* Commit ARGS->txn, setting ARGS->new_rev to the resulting new
+ * revision, if ARGS->txn is up-to-date with respect to the repository.
+ *
+ * Up-to-date means that ARGS->txn's base root is the same as the root
+ * of the youngest revision. If ARGS->txn is not up-to-date, the
+ * error SVN_ERR_FS_TXN_OUT_OF_DATE is returned, and the commit fails: no
+ * new revision is created, and ARGS->new_rev is not touched.
+ *
+ * If the commit succeeds, ARGS->txn is destroyed.
+ */
+static svn_error_t *
+txn_body_commit(void *baton, trail_t *trail)
+{
+ struct commit_args *args = baton;
+
+ svn_fs_txn_t *txn = args->txn;
+ svn_fs_t *fs = txn->fs;
+ const char *txn_name = txn->id;
+
+ svn_revnum_t youngest_rev;
+ const svn_fs_id_t *y_rev_root_id;
+ dag_node_t *txn_base_root_node;
+
+ /* Getting the youngest revision locks the revisions table until
+ this trail is done. */
+ SVN_ERR(svn_fs_bdb__youngest_rev(&youngest_rev, fs, trail, trail->pool));
+
+ /* If the root of the youngest revision is the same as txn's base,
+ then no further merging is necessary and we can commit. */
+ SVN_ERR(svn_fs_base__rev_get_root(&y_rev_root_id, fs, youngest_rev,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_txn_base_root(&txn_base_root_node, fs, txn_name,
+ trail, trail->pool));
+ /* ### kff todo: it seems weird to grab the ID for one, and the node
+ for the other. We can certainly do the comparison we need, but
+ it would be nice to grab the same type of information from the
+ start, instead of having to transform one of them. */
+ if (! svn_fs_base__id_eq(y_rev_root_id,
+ svn_fs_base__dag_get_id(txn_base_root_node)))
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(y_rev_root_id,
+ trail->pool);
+ return svn_error_createf
+ (SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
+ _("Transaction '%s' out-of-date with respect to revision '%s'"),
+ txn_name, id_str->data);
+ }
+
+ /* Locks may have been added (or stolen) between the calling of
+ previous svn_fs.h functions and svn_fs_commit_txn(), so we need
+ to re-examine every changed-path in the txn and re-verify all
+ discovered locks. */
+ SVN_ERR(verify_locks(txn_name, trail, trail->pool));
+
+ /* Else, commit the txn. */
+ return svn_fs_base__dag_commit_txn(&(args->new_rev), txn, trail,
+ trail->pool);
+}
+
+
+/* Note: it is acceptable for this function to call back into
+ top-level FS interfaces because it does not itself use trails. */
+svn_error_t *
+svn_fs_base__commit_txn(const char **conflict_p,
+ svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ /* How do commits work in Subversion?
+ *
+ * When you're ready to commit, here's what you have:
+ *
+ * 1. A transaction, with a mutable tree hanging off it.
+ * 2. A base revision, against which TXN_TREE was made.
+ * 3. A latest revision, which may be newer than the base rev.
+ *
+ * The problem is that if latest != base, then one can't simply
+ * attach the txn root as the root of the new revision, because that
+ * would lose all the changes between base and latest. It is also
+ * not acceptable to insist that base == latest; in a busy
+ * repository, commits happen too fast to insist that everyone keep
+ * their entire tree up-to-date at all times. Non-overlapping
+ * changes should not interfere with each other.
+ *
+ * The solution is to merge the changes between base and latest into
+ * the txn tree [see the function merge()]. The txn tree is the
+ * only one of the three trees that is mutable, so it has to be the
+ * one to adjust.
+ *
+ * You might have to adjust it more than once, if a new latest
+ * revision gets committed while you were merging in the previous
+ * one. For example:
+ *
+ * 1. Jane starts txn T, based at revision 6.
+ * 2. Someone commits (or already committed) revision 7.
+ * 3. Jane's starts merging the changes between 6 and 7 into T.
+ * 4. Meanwhile, someone commits revision 8.
+ * 5. Jane finishes the 6-->7 merge. T could now be committed
+ * against a latest revision of 7, if only that were still the
+ * latest. Unfortunately, 8 is now the latest, so...
+ * 6. Jane starts merging the changes between 7 and 8 into T.
+ * 7. Meanwhile, no one commits any new revisions. Whew.
+ * 8. Jane commits T, creating revision 9, whose tree is exactly
+ * T's tree, except immutable now.
+ *
+ * Lather, rinse, repeat.
+ */
+
+ svn_error_t *err;
+ svn_fs_t *fs = txn->fs;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Initialize output params. */
+ *new_rev = SVN_INVALID_REVNUM;
+ if (conflict_p)
+ *conflict_p = NULL;
+
+ while (1729)
+ {
+ struct get_root_args get_root_args;
+ struct merge_args merge_args;
+ struct commit_args commit_args;
+ svn_revnum_t youngish_rev;
+ svn_fs_root_t *youngish_root;
+ dag_node_t *youngish_root_node;
+
+ svn_pool_clear(subpool);
+
+ /* Get the *current* youngest revision, in one short-lived
+ Berkeley transaction. (We don't want the revisions table
+ locked while we do the main merge.) We call it "youngish"
+ because new revisions might get committed after we've
+ obtained it. */
+
+ SVN_ERR(svn_fs_base__youngest_rev(&youngish_rev, fs, subpool));
+ SVN_ERR(svn_fs_base__revision_root(&youngish_root, fs, youngish_rev,
+ subpool));
+
+ /* Get the dag node for the youngest revision, also in one
+ Berkeley transaction. Later we'll use it as the SOURCE
+ argument to a merge, and if the merge succeeds, this youngest
+ root node will become the new base root for the svn txn that
+ was the target of the merge (but note that the youngest rev
+ may have changed by then -- that's why we're careful to get
+ this root in its own bdb txn here). */
+ get_root_args.root = youngish_root;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args,
+ FALSE, subpool));
+ youngish_root_node = get_root_args.node;
+
+ /* Try to merge. If the merge succeeds, the base root node of
+ TARGET's txn will become the same as youngish_root_node, so
+ any future merges will only be between that node and whatever
+ the root node of the youngest rev is by then. */
+ merge_args.ancestor_node = NULL;
+ merge_args.source_node = youngish_root_node;
+ merge_args.txn = txn;
+ merge_args.conflict = svn_stringbuf_create_empty(pool); /* use pool */
+ err = svn_fs_base__retry_txn(fs, txn_body_merge, &merge_args,
+ FALSE, subpool);
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
+ *conflict_p = merge_args.conflict->data;
+ return svn_error_trace(err);
+ }
+
+ /* Try to commit. */
+ commit_args.txn = txn;
+ err = svn_fs_base__retry_txn(fs, txn_body_commit, &commit_args,
+ FALSE, subpool);
+ if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE))
+ {
+ /* Did someone else finish committing a new revision while we
+ were in mid-merge or mid-commit? If so, we'll need to
+ loop again to merge the new changes in, then try to
+ commit again. Or if that's not what happened, then just
+ return the error. */
+ svn_revnum_t youngest_rev;
+ svn_error_t *err2 = svn_fs_base__youngest_rev(&youngest_rev, fs,
+ subpool);
+ if (err2)
+ {
+ svn_error_clear(err);
+ return svn_error_trace(err2); /* err2 is bad,
+ it should not occur */
+ }
+ else if (youngest_rev == youngish_rev)
+ return svn_error_trace(err);
+ else
+ svn_error_clear(err);
+ }
+ else if (err)
+ {
+ return svn_error_trace(err);
+ }
+ else
+ {
+ /* Set the return value -- our brand spankin' new revision! */
+ *new_rev = commit_args.new_rev;
+ break;
+ }
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* Note: it is acceptable for this function to call back into
+ public FS API interfaces because it does not itself use trails. */
+static svn_error_t *
+base_merge(const char **conflict_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ svn_fs_root_t *ancestor_root,
+ const char *ancestor_path,
+ apr_pool_t *pool)
+{
+ dag_node_t *source, *ancestor;
+ struct get_root_args get_root_args;
+ struct merge_args merge_args;
+ svn_fs_txn_t *txn;
+ svn_error_t *err;
+ svn_fs_t *fs;
+
+ if (! target_root->is_txn_root)
+ return SVN_FS__NOT_TXN(target_root);
+
+ /* Paranoia. */
+ fs = ancestor_root->fs;
+ if ((source_root->fs != fs) || (target_root->fs != fs))
+ {
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Bad merge; ancestor, source, and target not all in same fs"));
+ }
+
+ /* ### kff todo: is there any compelling reason to get the nodes in
+ one db transaction? Right now we don't; txn_body_get_root() gets
+ one node at a time. This will probably need to change:
+
+ Jim Blandy <jimb@zwingli.cygnus.com> writes:
+ > svn_fs_merge needs to be a single transaction, to protect it against
+ > people deleting parents of nodes it's working on, etc.
+ */
+
+ /* Get the ancestor node. */
+ get_root_args.root = ancestor_root;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args,
+ FALSE, pool));
+ ancestor = get_root_args.node;
+
+ /* Get the source node. */
+ get_root_args.root = source_root;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args,
+ FALSE, pool));
+ source = get_root_args.node;
+
+ /* Open a txn for the txn root into which we're merging. */
+ SVN_ERR(svn_fs_base__open_txn(&txn, fs, target_root->txn, pool));
+
+ /* Merge changes between ANCESTOR and SOURCE into TXN. */
+ merge_args.source_node = source;
+ merge_args.ancestor_node = ancestor;
+ merge_args.txn = txn;
+ merge_args.conflict = svn_stringbuf_create_empty(pool);
+ err = svn_fs_base__retry_txn(fs, txn_body_merge, &merge_args, FALSE, pool);
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
+ *conflict_p = merge_args.conflict->data;
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct rev_get_txn_id_args
+{
+ const char **txn_id;
+ svn_revnum_t revision;
+};
+
+
+static svn_error_t *
+txn_body_rev_get_txn_id(void *baton, trail_t *trail)
+{
+ struct rev_get_txn_id_args *args = baton;
+ return svn_fs_base__rev_get_txn_id(args->txn_id, trail->fs,
+ args->revision, trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__deltify(svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ const char *txn_id;
+ struct rev_get_txn_id_args args;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ if (bfd->format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT)
+ {
+ const char *val;
+ svn_revnum_t forward_delta_rev = 0;
+
+ SVN_ERR(svn_fs_base__miscellaneous_get
+ (&val, fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE, pool));
+ if (val)
+ SVN_ERR(svn_revnum_parse(&forward_delta_rev, val, NULL));
+
+ /* ### FIXME: Unnecessarily harsh requirement? (cmpilato). */
+ if (revision <= forward_delta_rev)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot deltify revisions prior to r%ld"), forward_delta_rev+1);
+ }
+
+ SVN_ERR(svn_fs_base__revision_root(&root, fs, revision, pool));
+
+ args.txn_id = &txn_id;
+ args.revision = revision;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_rev_get_txn_id, &args,
+ FALSE, pool));
+
+ return deltify_mutable(fs, root, "/", NULL, svn_node_dir, txn_id, pool);
+}
+
+
+/* Modifying directories */
+
+
+struct make_dir_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_make_dir(void *baton,
+ trail_t *trail)
+{
+ struct make_dir_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ parent_path_t *parent_path;
+ dag_node_t *sub_dir;
+ const char *txn_id = root->txn;
+
+ SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
+ txn_id, trail, trail->pool));
+
+ /* If there's already a sub-directory by that name, complain. This
+ also catches the case of trying to make a subdirectory named `/'. */
+ if (parent_path->node)
+ return SVN_FS__ALREADY_EXISTS(root, path);
+
+ /* Check to see if some lock is 'reserving' a file-path or dir-path
+ at that location, or even some child-path; if so, check that we
+ can use it. */
+ if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* Create the subdirectory. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_make_dir(&sub_dir,
+ parent_path->parent->node,
+ parent_path_path(parent_path->parent,
+ trail->pool),
+ parent_path->entry,
+ txn_id,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path,
+ svn_fs_base__dag_get_id(sub_dir),
+ svn_fs_path_change_add, FALSE, FALSE,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_make_dir(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct make_dir_args args;
+
+ if (! root->is_txn_root)
+ return SVN_FS__NOT_TXN(root);
+
+ args.root = root;
+ args.path = path;
+ return svn_fs_base__retry_txn(root->fs, txn_body_make_dir, &args,
+ TRUE, pool);
+}
+
+
+struct delete_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+/* If this returns SVN_ERR_FS_NO_SUCH_ENTRY, it means that the
+ basename of PATH is missing from its parent, that is, the final
+ target of the deletion is missing. */
+static svn_error_t *
+txn_body_delete(void *baton,
+ trail_t *trail)
+{
+ struct delete_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ parent_path_t *parent_path;
+ const char *txn_id = root->txn;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ if (! root->is_txn_root)
+ return SVN_FS__NOT_TXN(root);
+
+ SVN_ERR(open_path(&parent_path, root, path, 0, txn_id,
+ trail, trail->pool));
+
+ /* We can't remove the root of the filesystem. */
+ if (! parent_path->parent)
+ return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL,
+ _("The root directory cannot be deleted"));
+
+ /* Check to see if path (or any child thereof) is locked; if so,
+ check that we can use the existing lock(s). */
+ if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* Make the parent directory mutable. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path,
+ trail, trail->pool));
+
+ /* Decrement mergeinfo counts on the parents of this node by the
+ count it previously carried, if our format supports it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ {
+ apr_int64_t mergeinfo_count;
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_count,
+ parent_path->node,
+ trail, trail->pool));
+ SVN_ERR(adjust_parent_mergeinfo_counts(parent_path->parent,
+ -mergeinfo_count, txn_id,
+ trail, trail->pool));
+ }
+
+ /* Do the deletion. */
+ SVN_ERR(svn_fs_base__dag_delete(parent_path->parent->node,
+ parent_path->entry,
+ txn_id, trail, trail->pool));
+
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path,
+ svn_fs_base__dag_get_id(parent_path->node),
+ svn_fs_path_change_delete, FALSE, FALSE, trail,
+ trail->pool);
+}
+
+
+static svn_error_t *
+base_delete_node(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct delete_args args;
+
+ args.root = root;
+ args.path = path;
+ return svn_fs_base__retry_txn(root->fs, txn_body_delete, &args,
+ TRUE, pool);
+}
+
+
+struct copy_args
+{
+ svn_fs_root_t *from_root;
+ const char *from_path;
+ svn_fs_root_t *to_root;
+ const char *to_path;
+ svn_boolean_t preserve_history;
+};
+
+
+static svn_error_t *
+txn_body_copy(void *baton,
+ trail_t *trail)
+{
+ struct copy_args *args = baton;
+ svn_fs_root_t *from_root = args->from_root;
+ const char *from_path = args->from_path;
+ svn_fs_root_t *to_root = args->to_root;
+ const char *to_path = args->to_path;
+ dag_node_t *from_node;
+ parent_path_t *to_parent_path;
+ const char *txn_id = to_root->txn;
+
+ /* Get the NODE for FROM_PATH in FROM_ROOT.*/
+ SVN_ERR(get_dag(&from_node, from_root, from_path, trail, trail->pool));
+
+ /* Build up the parent path from TO_PATH in TO_ROOT. If the last
+ component does not exist, it's not that big a deal. We'll just
+ make one there. */
+ SVN_ERR(open_path(&to_parent_path, to_root, to_path,
+ open_path_last_optional, txn_id, trail, trail->pool));
+
+ /* Check to see if to-path (or any child thereof) is locked, or at
+ least 'reserved', whether it exists or not; if so, check that we
+ can use the existing lock(s). */
+ if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(to_path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* If the destination node already exists as the same node as the
+ source (in other words, this operation would result in nothing
+ happening at all), just do nothing an return successfully,
+ proud that you saved yourself from a tiresome task. */
+ if ((to_parent_path->node)
+ && (svn_fs_base__id_compare(svn_fs_base__dag_get_id(from_node),
+ svn_fs_base__dag_get_id
+ (to_parent_path->node)) == 0))
+ return SVN_NO_ERROR;
+
+ if (! from_root->is_txn_root)
+ {
+ svn_fs_path_change_kind_t kind;
+ dag_node_t *new_node;
+ apr_int64_t old_mergeinfo_count = 0, mergeinfo_count;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ /* If TO_PATH already existed prior to the copy, note that this
+ operation is a replacement, not an addition. */
+ if (to_parent_path->node)
+ kind = svn_fs_path_change_replace;
+ else
+ kind = svn_fs_path_change_add;
+
+ /* Make sure the target node's parents are mutable. */
+ SVN_ERR(make_path_mutable(to_root, to_parent_path->parent,
+ to_path, trail, trail->pool));
+
+ /* If this is a replacement operation, we need to know the old
+ node's mergeinfo count. */
+ if (to_parent_path->node)
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL,
+ &old_mergeinfo_count,
+ to_parent_path->node,
+ trail, trail->pool));
+ /* Do the copy. */
+ SVN_ERR(svn_fs_base__dag_copy(to_parent_path->parent->node,
+ to_parent_path->entry,
+ from_node,
+ args->preserve_history,
+ from_root->rev,
+ from_path, txn_id, trail, trail->pool));
+
+ /* Adjust the mergeinfo counts of the destination's parents if
+ our format supports it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ {
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL,
+ &mergeinfo_count,
+ from_node, trail,
+ trail->pool));
+ SVN_ERR(adjust_parent_mergeinfo_counts
+ (to_parent_path->parent,
+ mergeinfo_count - old_mergeinfo_count,
+ txn_id, trail, trail->pool));
+ }
+
+ /* Make a record of this modification in the changes table. */
+ SVN_ERR(get_dag(&new_node, to_root, to_path, trail, trail->pool));
+ SVN_ERR(add_change(to_root->fs, txn_id, to_path,
+ svn_fs_base__dag_get_id(new_node),
+ kind, FALSE, FALSE, trail, trail->pool));
+ }
+ else
+ {
+ /* See IZ Issue #436 */
+ /* Copying from transaction roots not currently available.
+
+ ### cmpilato todo someday: make this not so. :-) Note that
+ when copying from mutable trees, you have to make sure that
+ you aren't creating a cyclic graph filesystem, and a simple
+ referencing operation won't cut it. Currently, we should not
+ be able to reach this clause, and the interface reports that
+ this only works from immutable trees anyway, but JimB has
+ stated that this requirement need not be necessary in the
+ future. */
+
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
+ Use POOL for temporary allocation only.
+ Note: this code is duplicated between libsvn_fs_fs and libsvn_fs_base. */
+static svn_error_t *
+fs_same_p(svn_boolean_t *same_p,
+ svn_fs_t *fs1,
+ svn_fs_t *fs2,
+ apr_pool_t *pool)
+{
+ *same_p = ! strcmp(fs1->uuid, fs2->uuid);
+ return SVN_NO_ERROR;
+}
+
+/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
+ TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in
+ the copies table. Perform temporary allocations in POOL. */
+static svn_error_t *
+copy_helper(svn_fs_root_t *from_root,
+ const char *from_path,
+ svn_fs_root_t *to_root,
+ const char *to_path,
+ svn_boolean_t preserve_history,
+ apr_pool_t *pool)
+{
+ struct copy_args args;
+ svn_boolean_t same_p;
+
+ /* Use an error check, not an assert, because even the caller cannot
+ guarantee that a filesystem's UUID has not changed "on the fly". */
+ SVN_ERR(fs_same_p(&same_p, from_root->fs, to_root->fs, pool));
+ if (! same_p)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot copy between two different filesystems ('%s' and '%s')"),
+ from_root->fs->path, to_root->fs->path);
+
+ if (! to_root->is_txn_root)
+ return SVN_FS__NOT_TXN(to_root);
+
+ if (from_root->is_txn_root)
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Copy from mutable tree not currently supported"));
+
+ args.from_root = from_root;
+ args.from_path = from_path;
+ args.to_root = to_root;
+ args.to_path = to_path;
+ args.preserve_history = preserve_history;
+
+ return svn_fs_base__retry_txn(to_root->fs, txn_body_copy, &args,
+ TRUE, pool);
+}
+
+static svn_error_t *
+base_copy(svn_fs_root_t *from_root,
+ const char *from_path,
+ svn_fs_root_t *to_root,
+ const char *to_path,
+ apr_pool_t *pool)
+{
+ return copy_helper(from_root, from_path, to_root, to_path, TRUE, pool);
+}
+
+
+static svn_error_t *
+base_revision_link(svn_fs_root_t *from_root,
+ svn_fs_root_t *to_root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ return copy_helper(from_root, path, to_root, path, FALSE, pool);
+}
+
+
+struct copied_from_args
+{
+ svn_fs_root_t *root; /* Root for the node whose ancestry we seek. */
+ const char *path; /* Path for the node whose ancestry we seek. */
+
+ svn_revnum_t result_rev; /* Revision, if any, of the ancestor. */
+ const char *result_path; /* Path, if any, of the ancestor. */
+
+ apr_pool_t *pool; /* Allocate `result_path' here. */
+};
+
+
+static svn_error_t *
+txn_body_copied_from(void *baton, trail_t *trail)
+{
+ struct copied_from_args *args = baton;
+ const svn_fs_id_t *node_id, *pred_id;
+ dag_node_t *node;
+ svn_fs_t *fs = args->root->fs;
+
+ /* Clear the return variables. */
+ args->result_path = NULL;
+ args->result_rev = SVN_INVALID_REVNUM;
+
+ /* Fetch the NODE in question. */
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ node_id = svn_fs_base__dag_get_id(node);
+
+ /* Check the node's predecessor-ID. If it doesn't have one, it
+ isn't a copy. */
+ SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id, node,
+ trail, trail->pool));
+ if (! pred_id)
+ return SVN_NO_ERROR;
+
+ /* If NODE's copy-ID is the same as that of its predecessor... */
+ if (svn_fs_base__key_compare(svn_fs_base__id_copy_id(node_id),
+ svn_fs_base__id_copy_id(pred_id)) != 0)
+ {
+ /* ... then NODE was either the target of a copy operation,
+ a copied subtree item. We examine the actual copy record
+ to determine which is the case. */
+ copy_t *copy;
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs,
+ svn_fs_base__id_copy_id(node_id),
+ trail, trail->pool));
+ if ((copy->kind == copy_kind_real)
+ && svn_fs_base__id_eq(copy->dst_noderev_id, node_id))
+ {
+ args->result_path = copy->src_path;
+ SVN_ERR(svn_fs_base__txn_get_revision(&(args->result_rev), fs,
+ copy->src_txn_id,
+ trail, trail->pool));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_copied_from(svn_revnum_t *rev_p,
+ const char **path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct copied_from_args args;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ args.root = root;
+ args.path = path;
+ args.pool = pool;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_copied_from, &args,
+ FALSE, scratch_pool));
+
+ *rev_p = args.result_rev;
+ *path_p = args.result_path ? apr_pstrdup(pool, args.result_path) : NULL;
+
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Files. */
+
+
+struct make_file_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_make_file(void *baton,
+ trail_t *trail)
+{
+ struct make_file_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ parent_path_t *parent_path;
+ dag_node_t *child;
+ const char *txn_id = root->txn;
+
+ SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
+ txn_id, trail, trail->pool));
+
+ /* If there's already a file by that name, complain.
+ This also catches the case of trying to make a file named `/'. */
+ if (parent_path->node)
+ return SVN_FS__ALREADY_EXISTS(root, path);
+
+ /* Check to see if some lock is 'reserving' a file-path or dir-path
+ at that location, or even some child-path; if so, check that we
+ can use it. */
+ if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* Create the file. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_make_file(&child,
+ parent_path->parent->node,
+ parent_path_path(parent_path->parent,
+ trail->pool),
+ parent_path->entry,
+ txn_id,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path,
+ svn_fs_base__dag_get_id(child),
+ svn_fs_path_change_add, TRUE, FALSE,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_make_file(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct make_file_args args;
+
+ args.root = root;
+ args.path = path;
+ return svn_fs_base__retry_txn(root->fs, txn_body_make_file, &args,
+ TRUE, pool);
+}
+
+
+
+struct file_length_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+ svn_filesize_t length; /* OUT parameter */
+};
+
+static svn_error_t *
+txn_body_file_length(void *baton,
+ trail_t *trail)
+{
+ struct file_length_args *args = baton;
+ dag_node_t *file;
+
+ /* First create a dag_node_t from the root/path pair. */
+ SVN_ERR(get_dag(&file, args->root, args->path, trail, trail->pool));
+
+ /* Now fetch its length */
+ return svn_fs_base__dag_file_length(&args->length, file,
+ trail, trail->pool);
+}
+
+static svn_error_t *
+base_file_length(svn_filesize_t *length_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct file_length_args args;
+
+ args.root = root;
+ args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_file_length, &args,
+ TRUE, pool));
+
+ *length_p = args.length;
+ return SVN_NO_ERROR;
+}
+
+
+struct file_checksum_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+ svn_checksum_kind_t kind;
+ svn_checksum_t **checksum; /* OUT parameter */
+};
+
+static svn_error_t *
+txn_body_file_checksum(void *baton,
+ trail_t *trail)
+{
+ struct file_checksum_args *args = baton;
+ dag_node_t *file;
+
+ SVN_ERR(get_dag(&file, args->root, args->path, trail, trail->pool));
+
+ return svn_fs_base__dag_file_checksum(args->checksum, args->kind, file,
+ trail, trail->pool);
+}
+
+static svn_error_t *
+base_file_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t kind,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct file_checksum_args args;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ args.root = root;
+ args.path = path;
+ args.kind = kind;
+ args.checksum = checksum;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_file_checksum, &args,
+ FALSE, scratch_pool));
+ *checksum = svn_checksum_dup(*checksum, pool);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* --- Machinery for svn_fs_file_contents() --- */
+
+
+/* Local baton type for txn_body_get_file_contents. */
+typedef struct file_contents_baton_t
+{
+ /* The file we want to read. */
+ svn_fs_root_t *root;
+ const char *path;
+
+ /* The dag_node that will be made from the above. */
+ dag_node_t *node;
+
+ /* The pool in which `file_stream' (below) is allocated. */
+ apr_pool_t *pool;
+
+ /* The readable file stream that will be made from the
+ dag_node. (And returned to the caller.) */
+ svn_stream_t *file_stream;
+
+} file_contents_baton_t;
+
+
+/* Main body of svn_fs_file_contents; converts a root/path pair into
+ a readable file stream (in the context of a db txn). */
+static svn_error_t *
+txn_body_get_file_contents(void *baton, trail_t *trail)
+{
+ file_contents_baton_t *fb = (file_contents_baton_t *) baton;
+
+ /* First create a dag_node_t from the root/path pair. */
+ SVN_ERR(get_dag(&(fb->node), fb->root, fb->path, trail, trail->pool));
+
+ /* Then create a readable stream from the dag_node_t. */
+ return svn_fs_base__dag_get_contents(&(fb->file_stream),
+ fb->node, trail, fb->pool);
+}
+
+
+
+static svn_error_t *
+base_file_contents(svn_stream_t **contents,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ file_contents_baton_t *fb = apr_pcalloc(pool, sizeof(*fb));
+ fb->root = root;
+ fb->path = path;
+ fb->pool = pool;
+
+ /* Create the readable stream in the context of a db txn. */
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_get_file_contents, fb,
+ FALSE, pool));
+
+ *contents = fb->file_stream;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_file_contents() --- */
+
+
+
+/* --- Machinery for svn_fs_apply_textdelta() --- */
+
+
+/* Local baton type for all the helper functions below. */
+typedef struct txdelta_baton_t
+{
+ /* This is the custom-built window consumer given to us by the delta
+ library; it uniquely knows how to read data from our designated
+ "source" stream, interpret the window, and write data to our
+ designated "target" stream (in this case, our repos file.) */
+ svn_txdelta_window_handler_t interpreter;
+ void *interpreter_baton;
+
+ /* The original file info */
+ svn_fs_root_t *root;
+ const char *path;
+
+ /* Derived from the file info */
+ dag_node_t *node;
+
+ svn_stream_t *source_stream;
+ svn_stream_t *target_stream;
+ svn_stream_t *string_stream;
+ svn_stringbuf_t *target_string;
+
+ /* Checksums for the base text against which a delta is to be
+ applied, and for the resultant fulltext, respectively. Either or
+ both may be null, in which case ignored. */
+ svn_checksum_t *base_checksum;
+ svn_checksum_t *result_checksum;
+
+ /* Pool used by db txns */
+ apr_pool_t *pool;
+
+} txdelta_baton_t;
+
+
+/* A trail-ready wrapper around svn_fs_base__dag_finalize_edits.
+ * This closes BATON->target_stream.
+ *
+ * Note: If you're confused about how this function relates to another
+ * of similar name, think of it this way:
+ *
+ * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
+ * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
+ */
+static svn_error_t *
+txn_body_txdelta_finalize_edits(void *baton, trail_t *trail)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+ SVN_ERR(svn_fs_base__dag_finalize_edits(tb->node,
+ tb->result_checksum,
+ tb->root->txn,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(tb->root->fs, tb->root->txn, tb->path,
+ svn_fs_base__dag_get_id(tb->node),
+ svn_fs_path_change_modify, TRUE, FALSE, trail,
+ trail->pool);
+}
+
+
+/* ### see comment in window_consumer() regarding this function. */
+
+/* Helper function of generic type `svn_write_fn_t'. Implements a
+ writable stream which appends to an svn_stringbuf_t. */
+static svn_error_t *
+write_to_string(void *baton, const char *data, apr_size_t *len)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+ svn_stringbuf_appendbytes(tb->target_string, data, *len);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* The main window handler returned by svn_fs_apply_textdelta. */
+static svn_error_t *
+window_consumer(svn_txdelta_window_t *window, void *baton)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+
+ /* Send the window right through to the custom window interpreter.
+ In theory, the interpreter will then write more data to
+ cb->target_string. */
+ SVN_ERR(tb->interpreter(window, tb->interpreter_baton));
+
+ /* ### the write_to_string() callback for the txdelta's output stream
+ ### should be doing all the flush determination logic, not here.
+ ### in a drastic case, a window could generate a LOT more than the
+ ### maximum buffer size. we want to flush to the underlying target
+ ### stream much sooner (e.g. also in a streamy fashion). also, by
+ ### moving this logic inside the stream, the stream becomes nice
+ ### and encapsulated: it holds all the logic about buffering and
+ ### flushing.
+ ###
+ ### further: I believe the buffering should be removed from tree.c
+ ### the buffering should go into the target_stream itself, which
+ ### is defined by reps-string.c. Specifically, I think the
+ ### rep_write_contents() function will handle the buffering and
+ ### the spill to the underlying DB. by locating it there, then
+ ### anybody who gets a writable stream for FS content can take
+ ### advantage of the buffering capability. this will be important
+ ### when we export an FS API function for writing a fulltext into
+ ### the FS, rather than forcing that fulltext thru apply_textdelta.
+ */
+
+ /* Check to see if we need to purge the portion of the contents that
+ have been written thus far. */
+ if ((! window) || (tb->target_string->len > WRITE_BUFFER_SIZE))
+ {
+ apr_size_t len = tb->target_string->len;
+ SVN_ERR(svn_stream_write(tb->target_stream,
+ tb->target_string->data,
+ &len));
+ svn_stringbuf_setempty(tb->target_string);
+ }
+
+ /* Is the window NULL? If so, we're done. */
+ if (! window)
+ {
+ /* Close the internal-use stream. ### This used to be inside of
+ txn_body_fulltext_finalize_edits(), but that invoked a nested
+ Berkeley DB transaction -- scandalous! */
+ SVN_ERR(svn_stream_close(tb->target_stream));
+
+ /* Tell the dag subsystem that we're finished with our edits. */
+ SVN_ERR(svn_fs_base__retry_txn(tb->root->fs,
+ txn_body_txdelta_finalize_edits, tb,
+ FALSE, tb->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+txn_body_apply_textdelta(void *baton, trail_t *trail)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+ parent_path_t *parent_path;
+ const char *txn_id = tb->root->txn;
+
+ /* Call open_path with no flags, as we want this to return an error
+ if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id,
+ trail, trail->pool));
+
+ /* Check to see if path is locked; if so, check that we can use it. */
+ if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_base__allow_locked_operation(tb->path, FALSE,
+ trail, trail->pool));
+
+ /* Now, make sure this path is mutable. */
+ SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path,
+ trail, trail->pool));
+ tb->node = parent_path->node;
+
+ if (tb->base_checksum)
+ {
+ svn_checksum_t *checksum;
+
+ /* Until we finalize the node, its data_key points to the old
+ contents, in other words, the base text. */
+ SVN_ERR(svn_fs_base__dag_file_checksum(&checksum,
+ tb->base_checksum->kind,
+ tb->node, trail, trail->pool));
+ /* TODO: This only compares checksums if they are the same kind, but
+ we're calculating both SHA1 and MD5 checksums somewhere in
+ reps-strings.c. Could we keep them both around somehow so this
+ check could be more comprehensive? */
+ if (!svn_checksum_match(tb->base_checksum, checksum))
+ return svn_checksum_mismatch_err(tb->base_checksum, checksum,
+ trail->pool,
+ _("Base checksum mismatch on '%s'"),
+ tb->path);
+ }
+
+ /* Make a readable "source" stream out of the current contents of
+ ROOT/PATH; obviously, this must done in the context of a db_txn.
+ The stream is returned in tb->source_stream. */
+ SVN_ERR(svn_fs_base__dag_get_contents(&(tb->source_stream),
+ tb->node, trail, tb->pool));
+
+ /* Make a writable "target" stream */
+ SVN_ERR(svn_fs_base__dag_get_edit_stream(&(tb->target_stream), tb->node,
+ txn_id, trail, tb->pool));
+
+ /* Make a writable "string" stream which writes data to
+ tb->target_string. */
+ tb->target_string = svn_stringbuf_create_empty(tb->pool);
+ tb->string_stream = svn_stream_create(tb, tb->pool);
+ svn_stream_set_write(tb->string_stream, write_to_string);
+
+ /* Now, create a custom window handler that uses our two streams. */
+ svn_txdelta_apply(tb->source_stream,
+ tb->string_stream,
+ NULL,
+ tb->path,
+ tb->pool,
+ &(tb->interpreter),
+ &(tb->interpreter_baton));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
+ void **contents_baton_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_checksum_t *base_checksum,
+ svn_checksum_t *result_checksum,
+ apr_pool_t *pool)
+{
+ txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
+
+ tb->root = root;
+ tb->path = path;
+ tb->pool = pool;
+ tb->base_checksum = svn_checksum_dup(base_checksum, pool);
+ tb->result_checksum = svn_checksum_dup(result_checksum, pool);
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_apply_textdelta, tb,
+ FALSE, pool));
+
+ *contents_p = window_consumer;
+ *contents_baton_p = tb;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_apply_textdelta() --- */
+
+/* --- Machinery for svn_fs_apply_text() --- */
+
+/* Baton for svn_fs_apply_text(). */
+struct text_baton_t
+{
+ /* The original file info */
+ svn_fs_root_t *root;
+ const char *path;
+
+ /* Derived from the file info */
+ dag_node_t *node;
+
+ /* The returned stream that will accept the file's new contents. */
+ svn_stream_t *stream;
+
+ /* The actual fs stream that the returned stream will write to. */
+ svn_stream_t *file_stream;
+
+ /* Checksum for the final fulltext written to the file. May
+ be null, in which case ignored. */
+ svn_checksum_t *result_checksum;
+
+ /* Pool used by db txns */
+ apr_pool_t *pool;
+};
+
+
+/* A trail-ready wrapper around svn_fs_base__dag_finalize_edits, but for
+ * fulltext data, not text deltas. Closes BATON->file_stream.
+ *
+ * Note: If you're confused about how this function relates to another
+ * of similar name, think of it this way:
+ *
+ * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
+ * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
+ */
+static svn_error_t *
+txn_body_fulltext_finalize_edits(void *baton, trail_t *trail)
+{
+ struct text_baton_t *tb = baton;
+ SVN_ERR(svn_fs_base__dag_finalize_edits(tb->node,
+ tb->result_checksum,
+ tb->root->txn,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(tb->root->fs, tb->root->txn, tb->path,
+ svn_fs_base__dag_get_id(tb->node),
+ svn_fs_path_change_modify, TRUE, FALSE, trail,
+ trail->pool);
+}
+
+/* Write function for the publically returned stream. */
+static svn_error_t *
+text_stream_writer(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct text_baton_t *tb = baton;
+
+ /* Psst, here's some data. Pass it on to the -real- file stream. */
+ return svn_stream_write(tb->file_stream, data, len);
+}
+
+/* Close function for the publically returned stream. */
+static svn_error_t *
+text_stream_closer(void *baton)
+{
+ struct text_baton_t *tb = baton;
+
+ /* Close the internal-use stream. ### This used to be inside of
+ txn_body_fulltext_finalize_edits(), but that invoked a nested
+ Berkeley DB transaction -- scandalous! */
+ SVN_ERR(svn_stream_close(tb->file_stream));
+
+ /* Need to tell fs that we're done sending text */
+ return svn_fs_base__retry_txn(tb->root->fs,
+ txn_body_fulltext_finalize_edits, tb,
+ FALSE, tb->pool);
+}
+
+
+static svn_error_t *
+txn_body_apply_text(void *baton, trail_t *trail)
+{
+ struct text_baton_t *tb = baton;
+ parent_path_t *parent_path;
+ const char *txn_id = tb->root->txn;
+
+ /* Call open_path with no flags, as we want this to return an error
+ if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id,
+ trail, trail->pool));
+
+ /* Check to see if path is locked; if so, check that we can use it. */
+ if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_base__allow_locked_operation(tb->path, FALSE,
+ trail, trail->pool));
+
+ /* Now, make sure this path is mutable. */
+ SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path,
+ trail, trail->pool));
+ tb->node = parent_path->node;
+
+ /* Make a writable stream for replacing the file's text. */
+ SVN_ERR(svn_fs_base__dag_get_edit_stream(&(tb->file_stream), tb->node,
+ txn_id, trail, tb->pool));
+
+ /* Create a 'returnable' stream which writes to the file_stream. */
+ tb->stream = svn_stream_create(tb, tb->pool);
+ svn_stream_set_write(tb->stream, text_stream_writer);
+ svn_stream_set_close(tb->stream, text_stream_closer);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_apply_text(svn_stream_t **contents_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_checksum_t *result_checksum,
+ apr_pool_t *pool)
+{
+ struct text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
+
+ tb->root = root;
+ tb->path = path;
+ tb->pool = pool;
+ tb->result_checksum = svn_checksum_dup(result_checksum, pool);
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_apply_text, tb,
+ FALSE, pool));
+
+ *contents_p = tb->stream;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_apply_text() --- */
+
+
+/* Note: we're sharing the `things_changed_args' struct with
+ svn_fs_props_changed(). */
+
+static svn_error_t *
+txn_body_contents_changed(void *baton, trail_t *trail)
+{
+ struct things_changed_args *args = baton;
+ dag_node_t *node1, *node2;
+
+ SVN_ERR(get_dag(&node1, args->root1, args->path1, trail, trail->pool));
+ SVN_ERR(get_dag(&node2, args->root2, args->path2, trail, trail->pool));
+ return svn_fs_base__things_different(NULL, args->changed_p,
+ node1, node2, trail, trail->pool);
+}
+
+
+/* Note: it is acceptable for this function to call back into
+ top-level interfaces because it does not itself use trails. */
+static svn_error_t *
+base_contents_changed(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ struct things_changed_args args;
+
+ /* Check that roots are in the same fs. */
+ if (root1->fs != root2->fs)
+ return svn_error_create
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Cannot compare file contents between two different filesystems"));
+
+ /* Check that both paths are files. */
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(base_check_path(&kind, root1, path1, pool));
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
+
+ SVN_ERR(base_check_path(&kind, root2, path2, pool));
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
+ }
+
+ args.root1 = root1;
+ args.root2 = root2;
+ args.path1 = path1;
+ args.path2 = path2;
+ args.changed_p = changed_p;
+ args.pool = pool;
+
+ return svn_fs_base__retry_txn(root1->fs, txn_body_contents_changed, &args,
+ TRUE, pool);
+}
+
+
+
+/* Public interface to computing file text deltas. */
+
+/* Note: it is acceptable for this function to call back into
+ public FS API interfaces because it does not itself use trails. */
+static svn_error_t *
+base_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ apr_pool_t *pool)
+{
+ svn_stream_t *source, *target;
+ svn_txdelta_stream_t *delta_stream;
+
+ /* Get read functions for the source file contents. */
+ if (source_root && source_path)
+ SVN_ERR(base_file_contents(&source, source_root, source_path, pool));
+ else
+ source = svn_stream_empty(pool);
+
+ /* Get read functions for the target file contents. */
+ SVN_ERR(base_file_contents(&target, target_root, target_path, pool));
+
+ /* Create a delta stream that turns the ancestor into the target. */
+ svn_txdelta2(&delta_stream, source, target, TRUE, pool);
+
+ *stream_p = delta_stream;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Finding Changes */
+
+struct paths_changed_args
+{
+ apr_hash_t *changes;
+ svn_fs_root_t *root;
+};
+
+
+static svn_error_t *
+txn_body_paths_changed(void *baton,
+ trail_t *trail)
+{
+ /* WARNING: This is called *without* the protection of a Berkeley DB
+ transaction. If you modify this function, keep that in mind. */
+
+ struct paths_changed_args *args = baton;
+ const char *txn_id;
+ svn_fs_t *fs = args->root->fs;
+
+ /* Get the transaction ID from ROOT. */
+ if (! args->root->is_txn_root)
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, args->root->rev,
+ trail, trail->pool));
+ else
+ txn_id = args->root->txn;
+
+ return svn_fs_bdb__changes_fetch(&(args->changes), fs, txn_id,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_paths_changed(apr_hash_t **changed_paths_p,
+ svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ struct paths_changed_args args;
+ args.root = root;
+ args.changes = NULL;
+ SVN_ERR(svn_fs_base__retry(root->fs, txn_body_paths_changed, &args,
+ FALSE, pool));
+ *changed_paths_p = args.changes;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Our coolio opaque history object. */
+typedef struct base_history_data_t
+{
+ /* filesystem object */
+ svn_fs_t *fs;
+
+ /* path and revision of historical location */
+ const char *path;
+ svn_revnum_t revision;
+
+ /* internal-use hints about where to resume the history search. */
+ const char *path_hint;
+ svn_revnum_t rev_hint;
+
+ /* FALSE until the first call to svn_fs_history_prev(). */
+ svn_boolean_t is_interesting;
+} base_history_data_t;
+
+
+static svn_fs_history_t *assemble_history(svn_fs_t *fs, const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t is_interesting,
+ const char *path_hint,
+ svn_revnum_t rev_hint,
+ apr_pool_t *pool);
+
+
+static svn_error_t *
+base_node_history(svn_fs_history_t **history_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ /* We require a revision root. */
+ if (root->is_txn_root)
+ return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
+
+ /* And we require that the path exist in the root. */
+ SVN_ERR(base_check_path(&kind, root, path, pool));
+ if (kind == svn_node_none)
+ return SVN_FS__NOT_FOUND(root, path);
+
+ /* Okay, all seems well. Build our history object and return it. */
+ *history_p = assemble_history(root->fs,
+ svn_fs__canonicalize_abspath(path, pool),
+ root->rev, FALSE, NULL,
+ SVN_INVALID_REVNUM, pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Examine the PARENT_PATH structure chain to determine how copy IDs
+ would be doled out in the event that PARENT_PATH was made mutable.
+ Return the ID of the copy that last affected PARENT_PATH (and the
+ COPY itself, if we've already fetched it).
+*/
+static svn_error_t *
+examine_copy_inheritance(const char **copy_id,
+ copy_t **copy,
+ svn_fs_t *fs,
+ parent_path_t *parent_path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* The default response -- our current copy ID, and no fetched COPY. */
+ *copy_id = svn_fs_base__id_copy_id
+ (svn_fs_base__dag_get_id(parent_path->node));
+ *copy = NULL;
+
+ /* If we have no parent (we are looking at the root node), or if
+ this node is supposed to inherit from itself, return that fact. */
+ if (! parent_path->parent)
+ return SVN_NO_ERROR;
+
+ /* We could be a branch destination (which would answer our question
+ altogether)! But then, again, we might just have been modified
+ in this revision, so all bets are off. */
+ if (parent_path->copy_inherit == copy_id_inherit_self)
+ {
+ /* A copy ID of "0" means we've never been branched. Therefore,
+ there are no copies relevant to our history. */
+ if (((*copy_id)[0] == '0') && ((*copy_id)[1] == '\0'))
+ return SVN_NO_ERROR;
+
+ /* Get the COPY record. If it was a real copy (not an implicit
+ one), we have our answer. Otherwise, we fall through to the
+ recursive case. */
+ SVN_ERR(svn_fs_bdb__get_copy(copy, fs, *copy_id, trail, pool));
+ if ((*copy)->kind != copy_kind_soft)
+ return SVN_NO_ERROR;
+ }
+
+ /* Otherwise, our answer is dependent upon our parent. */
+ return examine_copy_inheritance(copy_id, copy, fs,
+ parent_path->parent, trail, pool);
+}
+
+
+struct history_prev_args
+{
+ svn_fs_history_t **prev_history_p;
+ svn_fs_history_t *history;
+ svn_boolean_t cross_copies;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+txn_body_history_prev(void *baton, trail_t *trail)
+{
+ struct history_prev_args *args = baton;
+ svn_fs_history_t **prev_history = args->prev_history_p;
+ svn_fs_history_t *history = args->history;
+ base_history_data_t *bhd = history->fsap_data;
+ const char *commit_path, *src_path, *path = bhd->path;
+ svn_revnum_t commit_rev, src_rev, dst_rev, revision = bhd->revision;
+ apr_pool_t *retpool = args->pool;
+ svn_fs_t *fs = bhd->fs;
+ parent_path_t *parent_path;
+ dag_node_t *node;
+ svn_fs_root_t *root;
+ const svn_fs_id_t *node_id;
+ const char *end_copy_id = NULL;
+ struct revision_root_args rr_args;
+ svn_boolean_t reported = bhd->is_interesting;
+ const char *txn_id;
+ copy_t *copy = NULL;
+ svn_boolean_t retry = FALSE;
+
+ /* Initialize our return value. */
+ *prev_history = NULL;
+
+ /* If our last history report left us hints about where to pickup
+ the chase, then our last report was on the destination of a
+ copy. If we are crossing copies, start from those locations,
+ otherwise, we're all done here. */
+ if (bhd->path_hint && SVN_IS_VALID_REVNUM(bhd->rev_hint))
+ {
+ reported = FALSE;
+ if (! args->cross_copies)
+ return SVN_NO_ERROR;
+ path = bhd->path_hint;
+ revision = bhd->rev_hint;
+ }
+
+ /* Construct a ROOT for the current revision. */
+ rr_args.root_p = &root;
+ rr_args.rev = revision;
+ SVN_ERR(txn_body_revision_root(&rr_args, trail));
+
+ /* Open PATH/REVISION, and get its node and a bunch of other
+ goodies. */
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, revision, trail,
+ trail->pool));
+ SVN_ERR(open_path(&parent_path, root, path, 0, txn_id,
+ trail, trail->pool));
+ node = parent_path->node;
+ node_id = svn_fs_base__dag_get_id(node);
+ commit_path = svn_fs_base__dag_get_created_path(node);
+ SVN_ERR(svn_fs_base__dag_get_revision(&commit_rev, node,
+ trail, trail->pool));
+
+ /* The Subversion filesystem is written in such a way that a given
+ line of history may have at most one interesting history point
+ per filesystem revision. Either that node was edited (and
+ possibly copied), or it was copied but not edited. And a copy
+ source cannot be from the same revision as its destination. So,
+ if our history revision matches its node's commit revision, we
+ know that ... */
+ if (revision == commit_rev)
+ {
+ if (! reported)
+ {
+ /* ... we either have not yet reported on this revision (and
+ need now to do so) ... */
+ *prev_history = assemble_history(fs,
+ apr_pstrdup(retpool, commit_path),
+ commit_rev, TRUE, NULL,
+ SVN_INVALID_REVNUM, retpool);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* ... or we *have* reported on this revision, and must now
+ progress toward this node's predecessor (unless there is
+ no predecessor, in which case we're all done!). */
+ const svn_fs_id_t *pred_id;
+
+ SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id, node,
+ trail, trail->pool));
+ if (! pred_id)
+ return SVN_NO_ERROR;
+
+ /* Replace NODE and friends with the information from its
+ predecessor. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, fs, pred_id,
+ trail, trail->pool));
+ node_id = svn_fs_base__dag_get_id(node);
+ commit_path = svn_fs_base__dag_get_created_path(node);
+ SVN_ERR(svn_fs_base__dag_get_revision(&commit_rev, node,
+ trail, trail->pool));
+ }
+ }
+
+ /* Calculate a possibly relevant copy ID. */
+ SVN_ERR(examine_copy_inheritance(&end_copy_id, &copy, fs,
+ parent_path, trail, trail->pool));
+
+ /* Initialize some state variables. */
+ src_path = NULL;
+ src_rev = SVN_INVALID_REVNUM;
+ dst_rev = SVN_INVALID_REVNUM;
+
+ /* If our current copy ID (which is either the real copy ID of our
+ node, or the last copy ID which would affect our node if it were
+ to be made mutable) diffs at all from that of its predecessor
+ (which is either a real predecessor, or is the node itself
+ playing the predecessor role to an imaginary mutable successor),
+ then we need to report a copy. */
+ if (svn_fs_base__key_compare(svn_fs_base__id_copy_id(node_id),
+ end_copy_id) != 0)
+ {
+ const char *remainder;
+ dag_node_t *dst_node;
+ const char *copy_dst;
+
+ /* Get the COPY record if we haven't already fetched it. */
+ if (! copy)
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs, end_copy_id, trail,
+ trail->pool));
+
+ /* Figure out the destination path of the copy operation. */
+ SVN_ERR(svn_fs_base__dag_get_node(&dst_node, fs,
+ copy->dst_noderev_id,
+ trail, trail->pool));
+ copy_dst = svn_fs_base__dag_get_created_path(dst_node);
+
+ /* If our current path was the very destination of the copy,
+ then our new current path will be the copy source. If our
+ current path was instead the *child* of the destination of
+ the copy, then figure out its previous location by taking its
+ path relative to the copy destination and appending that to
+ the copy source. Finally, if our current path doesn't meet
+ one of these other criteria ... ### for now just fallback to
+ the old copy hunt algorithm. */
+ remainder = svn_fspath__skip_ancestor(copy_dst, path);
+
+ if (remainder)
+ {
+ /* If we get here, then our current path is the destination
+ of, or the child of the destination of, a copy. Fill
+ in the return values and get outta here. */
+ SVN_ERR(svn_fs_base__txn_get_revision
+ (&src_rev, fs, copy->src_txn_id, trail, trail->pool));
+ SVN_ERR(svn_fs_base__txn_get_revision
+ (&dst_rev, fs,
+ svn_fs_base__id_txn_id(copy->dst_noderev_id),
+ trail, trail->pool));
+ src_path = svn_fspath__join(copy->src_path, remainder,
+ trail->pool);
+ if (copy->kind == copy_kind_soft)
+ retry = TRUE;
+ }
+ }
+
+ /* If we calculated a copy source path and revision, and the
+ copy source revision doesn't pre-date a revision in which we
+ *know* our node was modified, we'll make a 'copy-style' history
+ object. */
+ if (src_path && SVN_IS_VALID_REVNUM(src_rev) && (src_rev >= commit_rev))
+ {
+ /* It's possible for us to find a copy location that is the same
+ as the history point we've just reported. If that happens,
+ we simply need to take another trip through this history
+ search. */
+ if ((dst_rev == revision) && reported)
+ retry = TRUE;
+
+ *prev_history = assemble_history(fs, apr_pstrdup(retpool, path),
+ dst_rev, ! retry,
+ src_path, src_rev, retpool);
+ }
+ else
+ {
+ *prev_history = assemble_history(fs, apr_pstrdup(retpool, commit_path),
+ commit_rev, TRUE, NULL,
+ SVN_INVALID_REVNUM, retpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_history_prev(svn_fs_history_t **prev_history_p,
+ svn_fs_history_t *history,
+ svn_boolean_t cross_copies,
+ apr_pool_t *pool)
+{
+ svn_fs_history_t *prev_history = NULL;
+ base_history_data_t *bhd = history->fsap_data;
+ svn_fs_t *fs = bhd->fs;
+
+ /* Special case: the root directory changes in every single
+ revision, no exceptions. And, the root can't be the target (or
+ child of a target -- duh) of a copy. So, if that's our path,
+ then we need only decrement our revision by 1, and there you go. */
+ if (strcmp(bhd->path, "/") == 0)
+ {
+ if (! bhd->is_interesting)
+ prev_history = assemble_history(fs, "/", bhd->revision,
+ 1, NULL, SVN_INVALID_REVNUM, pool);
+ else if (bhd->revision > 0)
+ prev_history = assemble_history(fs, "/", bhd->revision - 1,
+ 1, NULL, SVN_INVALID_REVNUM, pool);
+ }
+ else
+ {
+ struct history_prev_args args;
+ prev_history = history;
+
+ while (1)
+ {
+ /* Get a trail, and get to work. */
+
+ args.prev_history_p = &prev_history;
+ args.history = prev_history;
+ args.cross_copies = cross_copies;
+ args.pool = pool;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_history_prev, &args,
+ FALSE, pool));
+ if (! prev_history)
+ break;
+ bhd = prev_history->fsap_data;
+ if (bhd->is_interesting)
+ break;
+ }
+ }
+
+ *prev_history_p = prev_history;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_history_location(const char **path,
+ svn_revnum_t *revision,
+ svn_fs_history_t *history,
+ apr_pool_t *pool)
+{
+ base_history_data_t *bhd = history->fsap_data;
+
+ *path = apr_pstrdup(pool, bhd->path);
+ *revision = bhd->revision;
+ return SVN_NO_ERROR;
+}
+
+
+static history_vtable_t history_vtable = {
+ base_history_prev,
+ base_history_location
+};
+
+
+
+struct closest_copy_args
+{
+ svn_fs_root_t **root_p;
+ const char **path_p;
+ svn_fs_root_t *root;
+ const char *path;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+txn_body_closest_copy(void *baton, trail_t *trail)
+{
+ struct closest_copy_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ svn_fs_t *fs = root->fs;
+ parent_path_t *parent_path;
+ const svn_fs_id_t *node_id;
+ const char *txn_id, *copy_id;
+ copy_t *copy = NULL;
+ svn_fs_root_t *copy_dst_root;
+ dag_node_t *path_node_in_copy_dst, *copy_dst_node, *copy_dst_root_node;
+ const char *copy_dst_path;
+ svn_revnum_t copy_dst_rev, created_rev;
+ svn_error_t *err;
+
+ *(args->path_p) = NULL;
+ *(args->root_p) = NULL;
+
+ /* Get the transaction ID associated with our root. */
+ if (root->is_txn_root)
+ txn_id = root->txn;
+ else
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, root->rev,
+ trail, trail->pool));
+
+ /* Open PATH in ROOT -- it must exist. */
+ SVN_ERR(open_path(&parent_path, root, path, 0, txn_id,
+ trail, trail->pool));
+ node_id = svn_fs_base__dag_get_id(parent_path->node);
+
+ /* Now, examine the copy inheritance rules in play should our path
+ be made mutable in the future (if it isn't already). This will
+ tell us about the youngest affecting copy. */
+ SVN_ERR(examine_copy_inheritance(&copy_id, &copy, fs, parent_path,
+ trail, trail->pool));
+
+ /* Easy out: if the copy ID is 0, there's nothing of interest here. */
+ if (((copy_id)[0] == '0') && ((copy_id)[1] == '\0'))
+ return SVN_NO_ERROR;
+
+ /* Fetch our copy if examine_copy_inheritance() didn't do it for us. */
+ if (! copy)
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs, copy_id, trail, trail->pool));
+
+ /* Figure out the destination path and revision of the copy operation. */
+ SVN_ERR(svn_fs_base__dag_get_node(&copy_dst_node, fs, copy->dst_noderev_id,
+ trail, trail->pool));
+ copy_dst_path = svn_fs_base__dag_get_created_path(copy_dst_node);
+ SVN_ERR(svn_fs_base__dag_get_revision(&copy_dst_rev, copy_dst_node,
+ trail, trail->pool));
+
+ /* Turn that revision into a revision root. */
+ SVN_ERR(svn_fs_base__dag_revision_root(&copy_dst_root_node, fs,
+ copy_dst_rev, trail, args->pool));
+ copy_dst_root = make_revision_root(fs, copy_dst_rev,
+ copy_dst_root_node, args->pool);
+
+ /* It is possible that this node was created from scratch at some
+ revision between COPY_DST_REV and the transaction associated with
+ our ROOT. Make sure that PATH exists as of COPY_DST_REV and is
+ related to this node-rev. */
+ err = get_dag(&path_node_in_copy_dst, copy_dst_root, path,
+ trail, trail->pool);
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+ }
+ if ((svn_fs_base__dag_node_kind(path_node_in_copy_dst) == svn_node_none)
+ || (! (svn_fs_base__id_check_related
+ (node_id, svn_fs_base__dag_get_id(path_node_in_copy_dst)))))
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* One final check must be done here. If you copy a directory and
+ create a new entity somewhere beneath that directory in the same
+ txn, then we can't claim that the copy affected the new entity.
+ For example, if you do:
+
+ copy dir1 dir2
+ create dir2/new-thing
+ commit
+
+ then dir2/new-thing was not affected by the copy of dir1 to dir2.
+ We detect this situation by asking if PATH@COPY_DST_REV's
+ created-rev is COPY_DST_REV, and that node-revision has no
+ predecessors, then there is no relevant closest copy.
+ */
+ SVN_ERR(svn_fs_base__dag_get_revision(&created_rev, path_node_in_copy_dst,
+ trail, trail->pool));
+ if (created_rev == copy_dst_rev)
+ {
+ const svn_fs_id_t *pred_id;
+ SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id,
+ path_node_in_copy_dst,
+ trail, trail->pool));
+ if (! pred_id)
+ return SVN_NO_ERROR;
+ }
+
+ *(args->path_p) = apr_pstrdup(args->pool, copy_dst_path);
+ *(args->root_p) = copy_dst_root;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_closest_copy(svn_fs_root_t **root_p,
+ const char **path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct closest_copy_args args;
+ svn_fs_t *fs = root->fs;
+ svn_fs_root_t *closest_root = NULL;
+ const char *closest_path = NULL;
+
+ args.root_p = &closest_root;
+ args.path_p = &closest_path;
+ args.root = root;
+ args.path = path;
+ args.pool = pool;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_closest_copy, &args,
+ FALSE, pool));
+ *root_p = closest_root;
+ *path_p = closest_path;
+ return SVN_NO_ERROR;
+}
+
+
+/* Return a new history object (marked as "interesting") for PATH and
+ REVISION, allocated in POOL, and with its members set to the values
+ of the parameters provided. Note that PATH and PATH_HINT are not
+ duped into POOL -- it is the responsibility of the caller to ensure
+ that this happens. */
+static svn_fs_history_t *
+assemble_history(svn_fs_t *fs,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t is_interesting,
+ const char *path_hint,
+ svn_revnum_t rev_hint,
+ apr_pool_t *pool)
+{
+ svn_fs_history_t *history = apr_pcalloc(pool, sizeof(*history));
+ base_history_data_t *bhd = apr_pcalloc(pool, sizeof(*bhd));
+ bhd->path = path;
+ bhd->revision = revision;
+ bhd->is_interesting = is_interesting;
+ bhd->path_hint = path_hint;
+ bhd->rev_hint = rev_hint;
+ bhd->fs = fs;
+ history->vtable = &history_vtable;
+ history->fsap_data = bhd;
+ return history;
+}
+
+
+svn_error_t *
+svn_fs_base__get_path_kind(svn_node_kind_t *kind,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_revnum_t head_rev;
+ svn_fs_root_t *root;
+ dag_node_t *root_dir, *path_node;
+ svn_error_t *err;
+
+ /* Get HEAD revision, */
+ SVN_ERR(svn_fs_bdb__youngest_rev(&head_rev, trail->fs, trail, pool));
+
+ /* Then convert it into a root_t, */
+ SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, head_rev,
+ trail, pool));
+ root = make_revision_root(trail->fs, head_rev, root_dir, pool);
+
+ /* And get the dag_node for path in the root_t. */
+ err = get_dag(&path_node, root, path, trail, pool);
+ if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND))
+ {
+ svn_error_clear(err);
+ *kind = svn_node_none;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ *kind = svn_fs_base__dag_node_kind(path_node);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__get_path_created_rev(svn_revnum_t *rev,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_revnum_t head_rev, created_rev;
+ svn_fs_root_t *root;
+ dag_node_t *root_dir, *path_node;
+ svn_error_t *err;
+
+ /* Get HEAD revision, */
+ SVN_ERR(svn_fs_bdb__youngest_rev(&head_rev, trail->fs, trail, pool));
+
+ /* Then convert it into a root_t, */
+ SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, head_rev,
+ trail, pool));
+ root = make_revision_root(trail->fs, head_rev, root_dir, pool);
+
+ /* And get the dag_node for path in the root_t. */
+ err = get_dag(&path_node, root, path, trail, pool);
+ if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND))
+ {
+ svn_error_clear(err);
+ *rev = SVN_INVALID_REVNUM;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ /* Find the created_rev of the dag_node. */
+ SVN_ERR(svn_fs_base__dag_get_revision(&created_rev, path_node,
+ trail, pool));
+
+ *rev = created_rev;
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Finding the Origin of a Line of History ***/
+
+/* Set *PREV_PATH and *PREV_REV to the path and revision which
+ represent the location at which PATH in FS was located immediately
+ prior to REVISION iff there was a copy operation (to PATH or one of
+ its parent directories) between that previous location and
+ PATH@REVISION.
+
+ If there was no such copy operation in that portion of PATH's
+ history, set *PREV_PATH to NULL and *PREV_REV to SVN_INVALID_REVNUM.
+
+ WARNING: Do *not* call this from inside a trail. */
+static svn_error_t *
+prev_location(const char **prev_path,
+ svn_revnum_t *prev_rev,
+ svn_fs_t *fs,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *copy_path, *copy_src_path, *remainder;
+ svn_fs_root_t *copy_root;
+ svn_revnum_t copy_src_rev;
+
+ /* Ask about the most recent copy which affected PATH@REVISION. If
+ there was no such copy, we're done. */
+ SVN_ERR(base_closest_copy(&copy_root, &copy_path, root, path, pool));
+ if (! copy_root)
+ {
+ *prev_rev = SVN_INVALID_REVNUM;
+ *prev_path = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Ultimately, it's not the path of the closest copy's source that
+ we care about -- it's our own path's location in the copy source
+ revision. So we'll tack the relative path that expresses the
+ difference between the copy destination and our path in the copy
+ revision onto the copy source path to determine this information.
+
+ In other words, if our path is "/branches/my-branch/foo/bar", and
+ we know that the closest relevant copy was a copy of "/trunk" to
+ "/branches/my-branch", then that relative path under the copy
+ destination is "/foo/bar". Tacking that onto the copy source
+ path tells us that our path was located at "/trunk/foo/bar"
+ before the copy.
+ */
+ SVN_ERR(base_copied_from(&copy_src_rev, &copy_src_path,
+ copy_root, copy_path, pool));
+ remainder = svn_fspath__skip_ancestor(copy_path, path);
+ *prev_path = svn_fspath__join(copy_src_path, remainder, pool);
+ *prev_rev = copy_src_rev;
+ return SVN_NO_ERROR;
+}
+
+
+struct id_created_rev_args {
+ svn_revnum_t revision;
+ const svn_fs_id_t *id;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_id_created_rev(void *baton, trail_t *trail)
+{
+ struct id_created_rev_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id,
+ trail, trail->pool));
+ return svn_fs_base__dag_get_revision(&(args->revision), node,
+ trail, trail->pool);
+}
+
+
+struct get_set_node_origin_args {
+ const svn_fs_id_t *origin_id;
+ const char *node_id;
+};
+
+
+static svn_error_t *
+txn_body_get_node_origin(void *baton, trail_t *trail)
+{
+ struct get_set_node_origin_args *args = baton;
+ return svn_fs_bdb__get_node_origin(&(args->origin_id), trail->fs,
+ args->node_id, trail, trail->pool);
+}
+
+static svn_error_t *
+txn_body_set_node_origin(void *baton, trail_t *trail)
+{
+ struct get_set_node_origin_args *args = baton;
+ return svn_fs_bdb__set_node_origin(trail->fs, args->node_id,
+ args->origin_id, trail, trail->pool);
+}
+
+static svn_error_t *
+base_node_origin_rev(svn_revnum_t *revision,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = root->fs;
+ base_fs_data_t *bfd = fs->fsap_data;
+ struct get_set_node_origin_args args;
+ const svn_fs_id_t *origin_id = NULL;
+ struct id_created_rev_args icr_args;
+
+ /* Canonicalize the input path so that the path-math that
+ prev_location() does below will work. */
+ path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* If we have support for the node-origins table, we'll try to use
+ it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)
+ {
+ const svn_fs_id_t *id;
+ svn_error_t *err;
+
+ SVN_ERR(base_node_id(&id, root, path, pool));
+ args.node_id = svn_fs_base__id_node_id(id);
+ err = svn_fs_base__retry_txn(root->fs, txn_body_get_node_origin, &args,
+ FALSE, pool);
+
+ /* If we got a value for the origin node-revision-ID, that's
+ great. If we didn't, that's sad but non-fatal -- we'll just
+ figure it out the hard way, then record it so we don't have
+ suffer again the next time. */
+ if (! err)
+ {
+ origin_id = args.origin_id;
+ }
+ else if (err->apr_err == SVN_ERR_FS_NO_SUCH_NODE_ORIGIN)
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+ }
+
+ /* If we haven't yet found a node origin ID, we'll go spelunking for one. */
+ if (! origin_id)
+ {
+ svn_fs_root_t *curroot = root;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_pool_t *predidpool = svn_pool_create(pool);
+ svn_stringbuf_t *lastpath =
+ svn_stringbuf_create(path, pool);
+ svn_revnum_t lastrev = SVN_INVALID_REVNUM;
+ const svn_fs_id_t *pred_id;
+
+ /* Walk the closest-copy chain back to the first copy in our history.
+
+ NOTE: We merely *assume* that this is faster than walking the
+ predecessor chain, because we *assume* that copies of parent
+ directories happen less often than modifications to a given item. */
+ while (1)
+ {
+ svn_revnum_t currev;
+ const char *curpath = lastpath->data;
+
+ /* Get a root pointing to LASTREV. (The first time around,
+ LASTREV is invalid, but that's cool because CURROOT is
+ already initialized.) */
+ if (SVN_IS_VALID_REVNUM(lastrev))
+ SVN_ERR(svn_fs_base__revision_root(&curroot, fs,
+ lastrev, subpool));
+
+ /* Find the previous location using the closest-copy shortcut. */
+ SVN_ERR(prev_location(&curpath, &currev, fs, curroot,
+ curpath, subpool));
+ if (! curpath)
+ break;
+
+ /* Update our LASTPATH and LASTREV variables (which survive
+ SUBPOOL). */
+ svn_stringbuf_set(lastpath, curpath);
+ lastrev = currev;
+ }
+
+ /* Walk the predecessor links back to origin. */
+ SVN_ERR(base_node_id(&pred_id, curroot, lastpath->data, pool));
+ while (1)
+ {
+ struct txn_pred_id_args pid_args;
+ svn_pool_clear(subpool);
+ pid_args.id = pred_id;
+ pid_args.pred_id = NULL;
+ pid_args.pool = subpool;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id, &pid_args,
+ FALSE, subpool));
+ if (! pid_args.pred_id)
+ break;
+ svn_pool_clear(predidpool);
+ pred_id = svn_fs_base__id_copy(pid_args.pred_id, predidpool);
+ }
+
+ /* Okay. PRED_ID should hold our origin ID now. */
+ origin_id = svn_fs_base__id_copy(pred_id, pool);
+
+ /* If our filesystem version supports it, let's remember this
+ value from now on. */
+ if (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)
+ {
+ args.origin_id = origin_id;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_set_node_origin,
+ &args, TRUE, subpool));
+ }
+
+ svn_pool_destroy(predidpool);
+ svn_pool_destroy(subpool);
+ }
+
+ /* Okay. We have an origin node-revision-ID. Let's get a created
+ revision from it. */
+ icr_args.id = origin_id;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_id_created_rev, &icr_args,
+ TRUE, pool));
+ *revision = icr_args.revision;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Mergeinfo Queries */
+
+
+/* Examine directory NODE's immediately children for mergeinfo.
+
+ For those which have explicit mergeinfo, add their mergeinfo to
+ RESULT_CATALOG (allocated in RESULT_CATALOG's pool).
+
+ For those which don't, but sit atop trees which contain mergeinfo
+ somewhere deeper, add them to *CHILDREN_ATOP_MERGEINFO_TREES, a
+ hash mapping dirent names to dag_node_t * objects, allocated
+ from that hash's pool.
+
+ For those which neither have explicit mergeinfo nor sit atop trees
+ which contain mergeinfo, ignore them.
+
+ Use TRAIL->pool for temporary allocations. */
+
+struct get_mergeinfo_data_and_entries_baton
+{
+ svn_mergeinfo_catalog_t result_catalog;
+ apr_hash_t *children_atop_mergeinfo_trees;
+ dag_node_t *node;
+ const char *node_path;
+};
+
+static svn_error_t *
+txn_body_get_mergeinfo_data_and_entries(void *baton, trail_t *trail)
+{
+ struct get_mergeinfo_data_and_entries_baton *args = baton;
+ dag_node_t *node = args->node;
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(trail->pool);
+ apr_pool_t *result_pool = apr_hash_pool_get(args->result_catalog);
+ apr_pool_t *children_pool =
+ apr_hash_pool_get(args->children_atop_mergeinfo_trees);
+
+ SVN_ERR_ASSERT(svn_fs_base__dag_node_kind(node) == svn_node_dir);
+
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, trail->pool));
+ for (hi = apr_hash_first(trail->pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ void *val;
+ svn_fs_dirent_t *dirent;
+ const svn_fs_id_t *child_id;
+ dag_node_t *child_node;
+ svn_boolean_t has_mergeinfo;
+ apr_int64_t kid_count;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, NULL, NULL, &val);
+ dirent = val;
+ child_id = dirent->id;
+
+ /* Get the node for this child. */
+ SVN_ERR(svn_fs_base__dag_get_node(&child_node, trail->fs, child_id,
+ trail, iterpool));
+
+ /* Query the child node's mergeinfo stats. */
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(&has_mergeinfo, &kid_count,
+ child_node, trail,
+ iterpool));
+
+ /* If the child has mergeinfo, add it to the result catalog. */
+ if (has_mergeinfo)
+ {
+ apr_hash_t *plist;
+ svn_mergeinfo_t child_mergeinfo;
+ svn_string_t *pval;
+ svn_error_t *err;
+
+ SVN_ERR(svn_fs_base__dag_get_proplist(&plist, child_node,
+ trail, iterpool));
+ pval = svn_hash_gets(plist, SVN_PROP_MERGEINFO);
+ if (! pval)
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(child_id,
+ iterpool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Node-revision '%s' claims to have "
+ "mergeinfo but doesn't"),
+ id_str->data);
+ }
+ /* Issue #3896: If syntactically invalid mergeinfo is present on
+ CHILD_NODE then treat it as if no mergeinfo is present rather
+ than raising a parse error. */
+ err = svn_mergeinfo_parse(&child_mergeinfo, pval->data,
+ result_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ svn_hash_sets(args->result_catalog,
+ svn_fspath__join(args->node_path, dirent->name,
+ result_pool),
+ child_mergeinfo);
+ }
+ }
+
+ /* If the child has descendants with mergeinfo -- that is, if
+ the count of descendants beneath it carrying mergeinfo, not
+ including itself, is non-zero -- then add it to the
+ children_atop_mergeinfo_trees hash to be crawled later. */
+ if ((kid_count - (has_mergeinfo ? 1 : 0)) > 0)
+ {
+ if (svn_fs_base__dag_node_kind(child_node) != svn_node_dir)
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(child_id,
+ iterpool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Node-revision '%s' claims to sit "
+ "atop a tree containing mergeinfo "
+ "but is not a directory"),
+ id_str->data);
+ }
+ svn_hash_sets(args->children_atop_mergeinfo_trees,
+ apr_pstrdup(children_pool, dirent->name),
+ svn_fs_base__dag_dup(child_node, children_pool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+crawl_directory_for_mergeinfo(svn_fs_t *fs,
+ dag_node_t *node,
+ const char *node_path,
+ svn_mergeinfo_catalog_t result_catalog,
+ apr_pool_t *pool)
+{
+ struct get_mergeinfo_data_and_entries_baton gmdae_args;
+ apr_hash_t *children_atop_mergeinfo_trees = apr_hash_make(pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ /* Add mergeinfo for immediate children that have it, and fetch
+ immediate children that *don't* have it but sit atop trees that do. */
+ gmdae_args.result_catalog = result_catalog;
+ gmdae_args.children_atop_mergeinfo_trees = children_atop_mergeinfo_trees;
+ gmdae_args.node = node;
+ gmdae_args.node_path = node_path;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_mergeinfo_data_and_entries,
+ &gmdae_args, FALSE, pool));
+
+ /* If no children sit atop trees with mergeinfo, we're done.
+ Otherwise, recurse on those children. */
+
+ if (apr_hash_count(children_atop_mergeinfo_trees) == 0)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, children_atop_mergeinfo_trees);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ SVN_ERR(crawl_directory_for_mergeinfo(fs, val,
+ svn_fspath__join(node_path, key,
+ iterpool),
+ result_catalog, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Calculate the mergeinfo for PATH under revision ROOT using
+ inheritance type INHERIT. Set *MERGEINFO to the mergeinfo, or to
+ NULL if there is none. Results are allocated in POOL; TRAIL->pool
+ is used for temporary allocations. */
+
+struct get_mergeinfo_for_path_baton
+{
+ svn_mergeinfo_t *mergeinfo;
+ svn_fs_root_t *root;
+ const char *path;
+ svn_mergeinfo_inheritance_t inherit;
+ svn_boolean_t adjust_inherited_mergeinfo;
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+txn_body_get_mergeinfo_for_path(void *baton, trail_t *trail)
+{
+ struct get_mergeinfo_for_path_baton *args = baton;
+ parent_path_t *parent_path, *nearest_ancestor;
+ apr_hash_t *proplist;
+ svn_string_t *mergeinfo_string;
+ apr_pool_t *iterpool;
+ dag_node_t *node = NULL;
+
+ *(args->mergeinfo) = NULL;
+
+ SVN_ERR(open_path(&parent_path, args->root, args->path, 0,
+ NULL, trail, trail->pool));
+
+ /* Init the nearest ancestor. */
+ nearest_ancestor = parent_path;
+ if (args->inherit == svn_mergeinfo_nearest_ancestor)
+ {
+ if (! parent_path->parent)
+ return SVN_NO_ERROR;
+ nearest_ancestor = parent_path->parent;
+ }
+
+ iterpool = svn_pool_create(trail->pool);
+ while (TRUE)
+ {
+ svn_boolean_t has_mergeinfo;
+ apr_int64_t count;
+
+ svn_pool_clear(iterpool);
+
+ node = nearest_ancestor->node;
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(&has_mergeinfo, &count,
+ node, trail, iterpool));
+ if (has_mergeinfo)
+ break;
+
+ /* No need to loop if we're looking for explicit mergeinfo. */
+ if (args->inherit == svn_mergeinfo_explicit)
+ {
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+
+ nearest_ancestor = nearest_ancestor->parent;
+
+ /* Run out? There's no mergeinfo. */
+ if (! nearest_ancestor)
+ {
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node, trail, trail->pool));
+ mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
+ if (! mergeinfo_string)
+ {
+ svn_string_t *id_str =
+ svn_fs_base__id_unparse(svn_fs_base__dag_get_id(node), trail->pool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Node-revision '%s' claims to have "
+ "mergeinfo but doesn't"), id_str->data);
+ }
+
+ /* Parse the mergeinfo; store the result in ARGS->MERGEINFO. */
+ {
+ /* Issue #3896: If a node has syntactically invalid mergeinfo, then
+ treat it as if no mergeinfo is present rather than raising a parse
+ error. */
+ svn_error_t *err = svn_mergeinfo_parse(args->mergeinfo,
+ mergeinfo_string->data,
+ args->pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ svn_error_clear(err);
+ err = NULL;
+ args->mergeinfo = NULL;
+ }
+ return svn_error_trace(err);
+ }
+ }
+
+ /* If our nearest ancestor is the very path we inquired about, we
+ can return the mergeinfo results directly. Otherwise, we're
+ inheriting the mergeinfo, so we need to a) remove non-inheritable
+ ranges and b) telescope the merged-from paths. */
+ if (args->adjust_inherited_mergeinfo && (nearest_ancestor != parent_path))
+ {
+ svn_mergeinfo_t tmp_mergeinfo;
+
+ SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *args->mergeinfo,
+ NULL, SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM, TRUE,
+ trail->pool, trail->pool));
+ SVN_ERR(svn_fs__append_to_merged_froms(args->mergeinfo, tmp_mergeinfo,
+ parent_path_relpath(
+ parent_path, nearest_ancestor,
+ trail->pool),
+ args->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set **NODE to the dag node for PATH in ROOT (allocated in POOL),
+ and query its mergeinfo stats, setting HAS_MERGEINFO and
+ CHILD_MERGEINFO_COUNT appropriately. */
+
+struct get_node_mergeinfo_stats_baton
+{
+ dag_node_t *node;
+ svn_boolean_t has_mergeinfo;
+ apr_int64_t child_mergeinfo_count;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+static svn_error_t *
+txn_body_get_node_mergeinfo_stats(void *baton, trail_t *trail)
+{
+ struct get_node_mergeinfo_stats_baton *args = baton;
+
+ SVN_ERR(get_dag(&(args->node), args->root, args->path,
+ trail, trail->pool));
+ return svn_fs_base__dag_get_mergeinfo_stats(&(args->has_mergeinfo),
+ &(args->child_mergeinfo_count),
+ args->node, trail,
+ trail->pool);
+}
+
+
+/* Get the mergeinfo for a set of paths, returned in
+ *MERGEINFO_CATALOG. Returned values are allocated in POOL, while
+ temporary values are allocated in a sub-pool. */
+static svn_error_t *
+get_mergeinfos_for_paths(svn_fs_root_t *root,
+ svn_mergeinfo_catalog_t *mergeinfo_catalog,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_catalog_t result_catalog = apr_hash_make(result_pool);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ svn_mergeinfo_t path_mergeinfo;
+ struct get_mergeinfo_for_path_baton gmfp_args;
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ path = svn_fs__canonicalize_abspath(path, iterpool);
+
+ /* Get the mergeinfo for PATH itself. */
+ gmfp_args.mergeinfo = &path_mergeinfo;
+ gmfp_args.root = root;
+ gmfp_args.path = path;
+ gmfp_args.inherit = inherit;
+ gmfp_args.pool = result_pool;
+ gmfp_args.adjust_inherited_mergeinfo = adjust_inherited_mergeinfo;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs,
+ txn_body_get_mergeinfo_for_path,
+ &gmfp_args, FALSE, iterpool));
+ if (path_mergeinfo)
+ svn_hash_sets(result_catalog, apr_pstrdup(result_pool, path),
+ path_mergeinfo);
+
+ /* If we're including descendants, do so. */
+ if (include_descendants)
+ {
+ svn_boolean_t do_crawl;
+ struct get_node_mergeinfo_stats_baton gnms_args;
+
+ /* Query the node and its mergeinfo stats. */
+ gnms_args.root = root;
+ gnms_args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs,
+ txn_body_get_node_mergeinfo_stats,
+ &gnms_args, FALSE, iterpool));
+
+ /* Determine if there's anything worth crawling here. */
+ if (svn_fs_base__dag_node_kind(gnms_args.node) != svn_node_dir)
+ do_crawl = FALSE;
+ else
+ do_crawl = ((gnms_args.child_mergeinfo_count > 1)
+ || ((gnms_args.child_mergeinfo_count == 1)
+ && (! gnms_args.has_mergeinfo)));
+
+ /* If it's worth crawling, crawl. */
+ if (do_crawl)
+ SVN_ERR(crawl_directory_for_mergeinfo(root->fs, gnms_args.node,
+ path, result_catalog,
+ iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ *mergeinfo_catalog = result_catalog;
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_fs_get_mergeinfo. */
+static svn_error_t *
+base_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
+ svn_fs_root_t *root,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Verify that our filesystem version supports mergeinfo stuff. */
+ SVN_ERR(svn_fs_base__test_required_feature_format
+ (root->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
+
+ /* We require a revision root. */
+ if (root->is_txn_root)
+ return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
+
+ /* Retrieve a path -> mergeinfo mapping. */
+ return get_mergeinfos_for_paths(root, catalog, paths,
+ inherit, include_descendants,
+ adjust_inherited_mergeinfo,
+ result_pool, scratch_pool);
+}
+
+
+
+/* Creating root objects. */
+
+
+static root_vtable_t root_vtable = {
+ base_paths_changed,
+ base_check_path,
+ base_node_history,
+ base_node_id,
+ base_node_created_rev,
+ base_node_origin_rev,
+ base_node_created_path,
+ base_delete_node,
+ base_copied_from,
+ base_closest_copy,
+ base_node_prop,
+ base_node_proplist,
+ base_change_node_prop,
+ base_props_changed,
+ base_dir_entries,
+ base_make_dir,
+ base_copy,
+ base_revision_link,
+ base_file_length,
+ base_file_checksum,
+ base_file_contents,
+ NULL,
+ base_make_file,
+ base_apply_textdelta,
+ base_apply_text,
+ base_contents_changed,
+ base_get_file_delta_stream,
+ base_merge,
+ base_get_mergeinfo,
+};
+
+
+/* Construct a new root object in FS, allocated from POOL. */
+static svn_fs_root_t *
+make_root(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = apr_pcalloc(pool, sizeof(*root));
+ base_root_data_t *brd = apr_palloc(pool, sizeof(*brd));
+
+ root->fs = fs;
+ root->pool = pool;
+
+ /* Init the node ID cache. */
+ brd->node_cache = apr_hash_make(pool);
+ brd->node_cache_idx = 0;
+ root->vtable = &root_vtable;
+ root->fsap_data = brd;
+
+ return root;
+}
+
+
+/* Construct a root object referring to the root of REVISION in FS,
+ whose root directory is ROOT_DIR. Create the new root in POOL. */
+static svn_fs_root_t *
+make_revision_root(svn_fs_t *fs,
+ svn_revnum_t rev,
+ dag_node_t *root_dir,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = make_root(fs, pool);
+ base_root_data_t *brd = root->fsap_data;
+
+ root->is_txn_root = FALSE;
+ root->rev = rev;
+ brd->root_dir = root_dir;
+
+ return root;
+}
+
+
+/* Construct a root object referring to the root of the transaction
+ named TXN and based on revision BASE_REV in FS. FLAGS represents
+ the behavior of the transaction. Create the new root in POOL. */
+static svn_fs_root_t *
+make_txn_root(svn_fs_t *fs,
+ const char *txn,
+ svn_revnum_t base_rev,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = make_root(fs, pool);
+ root->is_txn_root = TRUE;
+ root->txn = apr_pstrdup(root->pool, txn);
+ root->txn_flags = flags;
+ root->rev = base_rev;
+
+ return root;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/tree.h b/contrib/subversion/subversion/libsvn_fs_base/tree.h
new file mode 100644
index 0000000..2e81a17
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/tree.h
@@ -0,0 +1,99 @@
+/* tree.h : internal interface to tree node functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_TREE_H
+#define SVN_LIBSVN_FS_TREE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include "svn_props.h"
+
+
+
+/* These functions implement some of the calls in the FS loader
+ library's fs and txn vtables. */
+
+svn_error_t *svn_fs_base__revision_root(svn_fs_root_t **root_p, svn_fs_t *fs,
+ svn_revnum_t rev, apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__deltify(svn_fs_t *fs, svn_revnum_t rev,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__commit_txn(const char **conflict_p,
+ svn_revnum_t *new_rev, svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__txn_root(svn_fs_root_t **root_p, svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+
+
+/* Inserting and retrieving miscellany records in the fs */
+
+/* Set the value of miscellaneous records KEY to VAL in FS. To remove
+ a value altogether, pass NULL for VAL.
+
+ KEY and VAL should be NULL-terminated strings. */
+svn_error_t *
+svn_fs_base__miscellaneous_set(svn_fs_t *fs,
+ const char *key,
+ const char *val,
+ apr_pool_t *pool);
+
+/* Retrieve the miscellany records for KEY into *VAL for FS, allocated
+ in POOL. If the fs doesn't support miscellany storage, or the value
+ does not exist, *VAL is set to NULL.
+
+ KEY should be a NULL-terminated string. */
+svn_error_t *
+svn_fs_base__miscellaneous_get(const char **val,
+ svn_fs_t *fs,
+ const char *key,
+ apr_pool_t *pool);
+
+
+
+
+
+/* Helper func: in the context of TRAIL, return the KIND of PATH in
+ head revision. If PATH doesn't exist, set *KIND to svn_node_none.*/
+svn_error_t *svn_fs_base__get_path_kind(svn_node_kind_t *kind,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Helper func: in the context of TRAIL, set *REV to the created-rev
+ of PATH in head revision. If PATH doesn't exist, set *REV to
+ SVN_INVALID_REVNUM. */
+svn_error_t *svn_fs_base__get_path_created_rev(svn_revnum_t *rev,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_TREE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/util/fs_skels.c b/contrib/subversion/subversion/libsvn_fs_base/util/fs_skels.c
new file mode 100644
index 0000000..f3466b9
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/util/fs_skels.c
@@ -0,0 +1,1515 @@
+/* fs_skels.c --- conversion between fs native types and skeletons
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+
+#include <apr_md5.h>
+#include <apr_sha1.h>
+
+#include "svn_error.h"
+#include "svn_string.h"
+#include "svn_types.h"
+#include "svn_time.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_subr_private.h"
+
+#include "svn_checksum.h"
+#include "fs_skels.h"
+#include "../id.h"
+
+
+static svn_error_t *
+skel_err(const char *skel_type)
+{
+ return svn_error_createf(SVN_ERR_FS_MALFORMED_SKEL, NULL,
+ "Malformed%s%s skeleton",
+ skel_type ? " " : "",
+ skel_type ? skel_type : "");
+}
+
+
+
+/*** Validity Checking ***/
+
+static svn_boolean_t
+is_valid_checksum_skel(svn_skel_t *skel)
+{
+ if (svn_skel__list_length(skel) != 2)
+ return FALSE;
+
+ if (svn_skel__matches_atom(skel->children, "md5")
+ && skel->children->next->is_atom)
+ return TRUE;
+
+ if (svn_skel__matches_atom(skel->children, "sha1")
+ && skel->children->next->is_atom)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_revision_skel(svn_skel_t *skel)
+{
+ int len = svn_skel__list_length(skel);
+
+ if ((len == 2)
+ && svn_skel__matches_atom(skel->children, "revision")
+ && skel->children->next->is_atom)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_transaction_skel(svn_skel_t *skel, transaction_kind_t *kind)
+{
+ int len = svn_skel__list_length(skel);
+
+ if (len != 5)
+ return FALSE;
+
+ /* Determine (and verify) the kind. */
+ if (svn_skel__matches_atom(skel->children, "transaction"))
+ *kind = transaction_kind_normal;
+ else if (svn_skel__matches_atom(skel->children, "committed"))
+ *kind = transaction_kind_committed;
+ else if (svn_skel__matches_atom(skel->children, "dead"))
+ *kind = transaction_kind_dead;
+ else
+ return FALSE;
+
+ if (skel->children->next->is_atom
+ && skel->children->next->next->is_atom
+ && (! skel->children->next->next->next->is_atom)
+ && (! skel->children->next->next->next->next->is_atom))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_rep_delta_chunk_skel(svn_skel_t *skel)
+{
+ int len;
+ svn_skel_t *window;
+ svn_skel_t *diff;
+
+ /* check the delta skel. */
+ if ((svn_skel__list_length(skel) != 2)
+ || (! skel->children->is_atom))
+ return FALSE;
+
+ /* check the window. */
+ window = skel->children->next;
+ len = svn_skel__list_length(window);
+ if ((len < 3) || (len > 4))
+ return FALSE;
+ if (! ((! window->children->is_atom)
+ && (window->children->next->is_atom)
+ && (window->children->next->next->is_atom)))
+ return FALSE;
+ if ((len == 4)
+ && (! window->children->next->next->next->is_atom))
+ return FALSE;
+
+ /* check the diff. ### currently we support only svndiff version
+ 0 delta data. */
+ diff = window->children;
+ if ((svn_skel__list_length(diff) == 3)
+ && (svn_skel__matches_atom(diff->children, "svndiff"))
+ && ((svn_skel__matches_atom(diff->children->next, "0"))
+ || (svn_skel__matches_atom(diff->children->next, "1")))
+ && (diff->children->next->next->is_atom))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_representation_skel(svn_skel_t *skel)
+{
+ int len = svn_skel__list_length(skel);
+ svn_skel_t *header;
+ int header_len;
+
+ /* the rep has at least two items in it, a HEADER list, and at least
+ one piece of kind-specific data. */
+ if (len < 2)
+ return FALSE;
+
+ /* check the header. it must have KIND and TXN atoms, and
+ optionally 1 or 2 checksums (which is a list form). */
+ header = skel->children;
+ header_len = svn_skel__list_length(header);
+ if (! (((header_len == 2) /* 2 means old repository, checksum absent */
+ && (header->children->is_atom)
+ && (header->children->next->is_atom))
+ || ((header_len == 3) /* 3 means md5 checksum present */
+ && (header->children->is_atom)
+ && (header->children->next->is_atom)
+ && (is_valid_checksum_skel(header->children->next->next)))
+ || ((header_len == 4) /* 3 means md5 and sha1 checksums present */
+ && (header->children->is_atom)
+ && (header->children->next->is_atom)
+ && (is_valid_checksum_skel(header->children->next->next))
+ && (is_valid_checksum_skel(header->children->next->next->next)))))
+ return FALSE;
+
+ /* check for fulltext rep. */
+ if ((len == 2)
+ && (svn_skel__matches_atom(header->children, "fulltext")))
+ return TRUE;
+
+ /* check for delta rep. */
+ if ((len >= 2)
+ && (svn_skel__matches_atom(header->children, "delta")))
+ {
+ /* it's a delta rep. check the validity. */
+ svn_skel_t *chunk = skel->children->next;
+
+ /* loop over chunks, checking each one. */
+ while (chunk)
+ {
+ if (! is_valid_rep_delta_chunk_skel(chunk))
+ return FALSE;
+ chunk = chunk->next;
+ }
+
+ /* all good on this delta rep. */
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_node_revision_header_skel(svn_skel_t *skel, svn_skel_t **kind_p)
+{
+ int len = svn_skel__list_length(skel);
+
+ if (len < 2)
+ return FALSE;
+
+ /* set the *KIND_P pointer. */
+ *kind_p = skel->children;
+
+ /* check for valid lengths. */
+ if (! ((len == 2) || (len == 3) || (len == 4) || (len == 6)))
+ return FALSE;
+
+ /* got mergeinfo stuff? */
+ if ((len > 4)
+ && (! (skel->children->next->next->next->next->is_atom
+ && skel->children->next->next->next->next->next->is_atom)))
+ return FALSE;
+
+ /* got predecessor count? */
+ if ((len > 3)
+ && (! skel->children->next->next->next->is_atom))
+ return FALSE;
+
+ /* got predecessor? */
+ if ((len > 2)
+ && (! skel->children->next->next->is_atom))
+ return FALSE;
+
+ /* got the basics? */
+ if (! (skel->children->is_atom
+ && skel->children->next->is_atom
+ && (skel->children->next->data[0] == '/')))
+ return FALSE;
+
+ return TRUE;
+}
+
+
+static svn_boolean_t
+is_valid_node_revision_skel(svn_skel_t *skel)
+{
+ int len = svn_skel__list_length(skel);
+ svn_skel_t *header = skel->children;
+ svn_skel_t *kind;
+
+ if (len < 1)
+ return FALSE;
+
+ if (! is_valid_node_revision_header_skel(header, &kind))
+ return FALSE;
+
+ if (svn_skel__matches_atom(kind, "dir"))
+ {
+ if (! ((len == 3)
+ && header->next->is_atom
+ && header->next->next->is_atom))
+ return FALSE;
+ }
+ else if (svn_skel__matches_atom(kind, "file"))
+ {
+ if (len < 3)
+ return FALSE;
+
+ if (! header->next->is_atom)
+ return FALSE;
+
+ /* As of SVN_FS_BASE__MIN_REP_SHARING_FORMAT version, the
+ DATA-KEY slot can be a 2-tuple. */
+ if (! header->next->next->is_atom)
+ {
+ if (! ((svn_skel__list_length(header->next->next) == 2)
+ && header->next->next->children->is_atom
+ && header->next->next->children->len
+ && header->next->next->children->next->is_atom
+ && header->next->next->children->next->len))
+ return FALSE;
+ }
+
+ if ((len > 3) && (! header->next->next->next->is_atom))
+ return FALSE;
+
+ if (len > 4)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+static svn_boolean_t
+is_valid_copy_skel(svn_skel_t *skel)
+{
+ return ((svn_skel__list_length(skel) == 4)
+ && (svn_skel__matches_atom(skel->children, "copy")
+ || svn_skel__matches_atom(skel->children, "soft-copy"))
+ && skel->children->next->is_atom
+ && skel->children->next->next->is_atom
+ && skel->children->next->next->next->is_atom);
+}
+
+
+static svn_boolean_t
+is_valid_change_skel(svn_skel_t *skel, svn_fs_path_change_kind_t *kind)
+{
+ if ((svn_skel__list_length(skel) == 6)
+ && svn_skel__matches_atom(skel->children, "change")
+ && skel->children->next->is_atom
+ && skel->children->next->next->is_atom
+ && skel->children->next->next->next->is_atom
+ && skel->children->next->next->next->next->is_atom
+ && skel->children->next->next->next->next->next->is_atom)
+ {
+ svn_skel_t *kind_skel = skel->children->next->next->next;
+
+ /* check the kind (and return it) */
+ if (svn_skel__matches_atom(kind_skel, "reset"))
+ {
+ if (kind)
+ *kind = svn_fs_path_change_reset;
+ return TRUE;
+ }
+ if (svn_skel__matches_atom(kind_skel, "add"))
+ {
+ if (kind)
+ *kind = svn_fs_path_change_add;
+ return TRUE;
+ }
+ if (svn_skel__matches_atom(kind_skel, "delete"))
+ {
+ if (kind)
+ *kind = svn_fs_path_change_delete;
+ return TRUE;
+ }
+ if (svn_skel__matches_atom(kind_skel, "replace"))
+ {
+ if (kind)
+ *kind = svn_fs_path_change_replace;
+ return TRUE;
+ }
+ if (svn_skel__matches_atom(kind_skel, "modify"))
+ {
+ if (kind)
+ *kind = svn_fs_path_change_modify;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_lock_skel(svn_skel_t *skel)
+{
+ if ((svn_skel__list_length(skel) == 8)
+ && svn_skel__matches_atom(skel->children, "lock")
+ && skel->children->next->is_atom
+ && skel->children->next->next->is_atom
+ && skel->children->next->next->next->is_atom
+ && skel->children->next->next->next->next->is_atom
+ && skel->children->next->next->next->next->next->is_atom
+ && skel->children->next->next->next->next->next->next->is_atom
+ && skel->children->next->next->next->next->next->next->next->is_atom)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+
+/*** Parsing (conversion from skeleton to native FS type) ***/
+
+svn_error_t *
+svn_fs_base__parse_revision_skel(revision_t **revision_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ revision_t *revision;
+
+ /* Validate the skel. */
+ if (! is_valid_revision_skel(skel))
+ return skel_err("revision");
+
+ /* Create the returned structure */
+ revision = apr_pcalloc(pool, sizeof(*revision));
+ revision->txn_id = apr_pstrmemdup(pool, skel->children->next->data,
+ skel->children->next->len);
+
+ /* Return the structure. */
+ *revision_p = revision;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_transaction_skel(transaction_t **transaction_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ transaction_t *transaction;
+ transaction_kind_t kind;
+ svn_skel_t *root_id, *base_id_or_rev, *proplist, *copies;
+ int len;
+
+ /* Validate the skel. */
+ if (! is_valid_transaction_skel(skel, &kind))
+ return skel_err("transaction");
+
+ root_id = skel->children->next;
+ base_id_or_rev = skel->children->next->next;
+ proplist = skel->children->next->next->next;
+ copies = skel->children->next->next->next->next;
+
+ /* Create the returned structure */
+ transaction = apr_pcalloc(pool, sizeof(*transaction));
+
+ /* KIND */
+ transaction->kind = kind;
+
+ /* REVISION or BASE-ID */
+ if (kind == transaction_kind_committed)
+ {
+ /* Committed transactions have a revision number... */
+ transaction->base_id = NULL;
+ transaction->revision =
+ SVN_STR_TO_REV(apr_pstrmemdup(pool, base_id_or_rev->data,
+ base_id_or_rev->len));
+ if (! SVN_IS_VALID_REVNUM(transaction->revision))
+ return skel_err("transaction");
+
+ }
+ else
+ {
+ /* ...where unfinished transactions have a base node-revision-id. */
+ transaction->revision = SVN_INVALID_REVNUM;
+ transaction->base_id = svn_fs_base__id_parse(base_id_or_rev->data,
+ base_id_or_rev->len, pool);
+ }
+
+ /* ROOT-ID */
+ transaction->root_id = svn_fs_base__id_parse(root_id->data,
+ root_id->len, pool);
+
+ /* PROPLIST */
+ SVN_ERR(svn_skel__parse_proplist(&(transaction->proplist),
+ proplist, pool));
+
+ /* COPIES */
+ if ((len = svn_skel__list_length(copies)))
+ {
+ const char *copy_id;
+ apr_array_header_t *txncopies;
+ svn_skel_t *cpy = copies->children;
+
+ txncopies = apr_array_make(pool, len, sizeof(copy_id));
+ while (cpy)
+ {
+ copy_id = apr_pstrmemdup(pool, cpy->data, cpy->len);
+ APR_ARRAY_PUSH(txncopies, const char *) = copy_id;
+ cpy = cpy->next;
+ }
+ transaction->copies = txncopies;
+ }
+
+ /* Return the structure. */
+ *transaction_p = transaction;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_representation_skel(representation_t **rep_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+ svn_skel_t *header_skel;
+
+ /* Validate the skel. */
+ if (! is_valid_representation_skel(skel))
+ return skel_err("representation");
+ header_skel = skel->children;
+
+ /* Create the returned structure */
+ rep = apr_pcalloc(pool, sizeof(*rep));
+
+ /* KIND */
+ if (svn_skel__matches_atom(header_skel->children, "fulltext"))
+ rep->kind = rep_kind_fulltext;
+ else
+ rep->kind = rep_kind_delta;
+
+ /* TXN */
+ rep->txn_id = apr_pstrmemdup(pool, header_skel->children->next->data,
+ header_skel->children->next->len);
+
+ /* MD5 */
+ if (header_skel->children->next->next)
+ {
+ svn_skel_t *checksum_skel = header_skel->children->next->next;
+ rep->md5_checksum =
+ svn_checksum__from_digest_md5((const unsigned char *)
+ (checksum_skel->children->next->data),
+ pool);
+
+ /* SHA1 */
+ if (header_skel->children->next->next->next)
+ {
+ checksum_skel = header_skel->children->next->next->next;
+ rep->sha1_checksum =
+ svn_checksum__from_digest_sha1(
+ (const unsigned char *)(checksum_skel->children->next->data),
+ pool);
+ }
+ }
+
+ /* KIND-SPECIFIC stuff */
+ if (rep->kind == rep_kind_fulltext)
+ {
+ /* "fulltext"-specific. */
+ rep->contents.fulltext.string_key
+ = apr_pstrmemdup(pool,
+ skel->children->next->data,
+ skel->children->next->len);
+ }
+ else
+ {
+ /* "delta"-specific. */
+ svn_skel_t *chunk_skel = skel->children->next;
+ rep_delta_chunk_t *chunk;
+ apr_array_header_t *chunks;
+
+ /* Alloc the chunk array. */
+ chunks = apr_array_make(pool, svn_skel__list_length(skel) - 1,
+ sizeof(chunk));
+
+ /* Process the chunks. */
+ while (chunk_skel)
+ {
+ svn_skel_t *window_skel = chunk_skel->children->next;
+ svn_skel_t *diff_skel = window_skel->children;
+ apr_int64_t val;
+ apr_uint64_t uval;
+ const char *str;
+
+ /* Allocate a chunk and its window */
+ chunk = apr_palloc(pool, sizeof(*chunk));
+
+ /* Populate the window */
+ str = apr_pstrmemdup(pool, diff_skel->children->next->data,
+ diff_skel->children->next->len);
+ SVN_ERR(svn_cstring_strtoui64(&uval, str, 0, 255, 10));
+ chunk->version = (apr_byte_t)uval;
+
+ chunk->string_key
+ = apr_pstrmemdup(pool,
+ diff_skel->children->next->next->data,
+ diff_skel->children->next->next->len);
+
+ str = apr_pstrmemdup(pool, window_skel->children->next->data,
+ window_skel->children->next->len);
+ SVN_ERR(svn_cstring_strtoui64(&uval, str, 0, APR_SIZE_MAX, 10));
+ chunk->size = (apr_size_t)uval;
+
+ chunk->rep_key
+ = apr_pstrmemdup(pool,
+ window_skel->children->next->next->data,
+ window_skel->children->next->next->len);
+
+ str = apr_pstrmemdup(pool, chunk_skel->children->data,
+ chunk_skel->children->len);
+ SVN_ERR(svn_cstring_strtoi64(&val, str, 0, APR_INT64_MAX, 10));
+ chunk->offset = (svn_filesize_t)val;
+
+ /* Add this chunk to the array. */
+ APR_ARRAY_PUSH(chunks, rep_delta_chunk_t *) = chunk;
+
+ /* Next... */
+ chunk_skel = chunk_skel->next;
+ }
+
+ /* Add the chunks array to the representation. */
+ rep->contents.delta.chunks = chunks;
+ }
+
+ /* Return the structure. */
+ *rep_p = rep;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_node_revision_skel(node_revision_t **noderev_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ svn_skel_t *header_skel, *cur_skel;
+
+ /* Validate the skel. */
+ if (! is_valid_node_revision_skel(skel))
+ return skel_err("node-revision");
+ header_skel = skel->children;
+
+ /* Create the returned structure */
+ noderev = apr_pcalloc(pool, sizeof(*noderev));
+
+ /* KIND */
+ if (svn_skel__matches_atom(header_skel->children, "dir"))
+ noderev->kind = svn_node_dir;
+ else
+ noderev->kind = svn_node_file;
+
+ /* CREATED-PATH */
+ noderev->created_path = apr_pstrmemdup(pool,
+ header_skel->children->next->data,
+ header_skel->children->next->len);
+
+ /* PREDECESSOR-ID */
+ if (header_skel->children->next->next)
+ {
+ cur_skel = header_skel->children->next->next;
+ if (cur_skel->len)
+ noderev->predecessor_id = svn_fs_base__id_parse(cur_skel->data,
+ cur_skel->len, pool);
+
+ /* PREDECESSOR-COUNT */
+ noderev->predecessor_count = -1;
+ if (cur_skel->next)
+ {
+ const char *str;
+
+ cur_skel = cur_skel->next;
+ if (cur_skel->len)
+ {
+ str = apr_pstrmemdup(pool, cur_skel->data, cur_skel->len);
+ SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, str));
+ }
+
+ /* HAS-MERGEINFO and MERGEINFO-COUNT */
+ if (cur_skel->next)
+ {
+ int val;
+
+ cur_skel = cur_skel->next;
+ str = apr_pstrmemdup(pool, cur_skel->data, cur_skel->len);
+ SVN_ERR(svn_cstring_atoi(&val, str));
+ noderev->has_mergeinfo = (val != 0);
+
+ str = apr_pstrmemdup(pool, cur_skel->next->data,
+ cur_skel->next->len);
+ SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, str));
+ }
+ }
+ }
+
+ /* PROP-KEY */
+ if (skel->children->next->len)
+ noderev->prop_key = apr_pstrmemdup(pool, skel->children->next->data,
+ skel->children->next->len);
+
+ /* DATA-KEY */
+ if (skel->children->next->next->is_atom)
+ {
+ /* This is a real data rep key. */
+ if (skel->children->next->next->len)
+ noderev->data_key = apr_pstrmemdup(pool,
+ skel->children->next->next->data,
+ skel->children->next->next->len);
+ noderev->data_key_uniquifier = NULL;
+ }
+ else
+ {
+ /* This is a 2-tuple with a data rep key and a uniquifier. */
+ noderev->data_key =
+ apr_pstrmemdup(pool,
+ skel->children->next->next->children->data,
+ skel->children->next->next->children->len);
+ noderev->data_key_uniquifier =
+ apr_pstrmemdup(pool,
+ skel->children->next->next->children->next->data,
+ skel->children->next->next->children->next->len);
+ }
+
+ /* EDIT-DATA-KEY (optional, files only) */
+ if ((noderev->kind == svn_node_file)
+ && skel->children->next->next->next
+ && skel->children->next->next->next->len)
+ noderev->edit_key
+ = apr_pstrmemdup(pool, skel->children->next->next->next->data,
+ skel->children->next->next->next->len);
+
+ /* Return the structure. */
+ *noderev_p = noderev;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_copy_skel(copy_t **copy_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ copy_t *copy;
+
+ /* Validate the skel. */
+ if (! is_valid_copy_skel(skel))
+ return skel_err("copy");
+
+ /* Create the returned structure */
+ copy = apr_pcalloc(pool, sizeof(*copy));
+
+ /* KIND */
+ if (svn_skel__matches_atom(skel->children, "soft-copy"))
+ copy->kind = copy_kind_soft;
+ else
+ copy->kind = copy_kind_real;
+
+ /* SRC-PATH */
+ copy->src_path = apr_pstrmemdup(pool,
+ skel->children->next->data,
+ skel->children->next->len);
+
+ /* SRC-TXN-ID */
+ copy->src_txn_id = apr_pstrmemdup(pool,
+ skel->children->next->next->data,
+ skel->children->next->next->len);
+
+ /* DST-NODE-ID */
+ copy->dst_noderev_id
+ = svn_fs_base__id_parse(skel->children->next->next->next->data,
+ skel->children->next->next->next->len, pool);
+
+ /* Return the structure. */
+ *copy_p = copy;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_entries_skel(apr_hash_t **entries_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ apr_hash_t *entries = NULL;
+ int len = svn_skel__list_length(skel);
+ svn_skel_t *elt;
+
+ if (! (len >= 0))
+ return skel_err("entries");
+
+ if (len > 0)
+ {
+ /* Else, allocate a hash and populate it. */
+ entries = apr_hash_make(pool);
+
+ /* Check entries are well-formed as we go along. */
+ for (elt = skel->children; elt; elt = elt->next)
+ {
+ const char *name;
+ svn_fs_id_t *id;
+
+ /* ENTRY must be a list of two elements. */
+ if (svn_skel__list_length(elt) != 2)
+ return skel_err("entries");
+
+ /* Get the entry's name and ID. */
+ name = apr_pstrmemdup(pool, elt->children->data,
+ elt->children->len);
+ id = svn_fs_base__id_parse(elt->children->next->data,
+ elt->children->next->len, pool);
+
+ /* Add the entry to the hash. */
+ apr_hash_set(entries, name, elt->children->len, id);
+ }
+ }
+
+ /* Return the structure. */
+ *entries_p = entries;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_change_skel(change_t **change_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ change_t *change;
+ svn_fs_path_change_kind_t kind;
+
+ /* Validate the skel. */
+ if (! is_valid_change_skel(skel, &kind))
+ return skel_err("change");
+
+ /* Create the returned structure */
+ change = apr_pcalloc(pool, sizeof(*change));
+
+ /* PATH */
+ change->path = apr_pstrmemdup(pool, skel->children->next->data,
+ skel->children->next->len);
+
+ /* NODE-REV-ID */
+ if (skel->children->next->next->len)
+ change->noderev_id = svn_fs_base__id_parse
+ (skel->children->next->next->data, skel->children->next->next->len,
+ pool);
+
+ /* KIND */
+ change->kind = kind;
+
+ /* TEXT-MOD */
+ if (skel->children->next->next->next->next->len)
+ change->text_mod = TRUE;
+
+ /* PROP-MOD */
+ if (skel->children->next->next->next->next->next->len)
+ change->prop_mod = TRUE;
+
+ /* Return the structure. */
+ *change_p = change;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_lock_skel(svn_lock_t **lock_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ svn_lock_t *lock;
+ const char *timestr;
+
+ /* Validate the skel. */
+ if (! is_valid_lock_skel(skel))
+ return skel_err("lock");
+
+ /* Create the returned structure */
+ lock = apr_pcalloc(pool, sizeof(*lock));
+
+ /* PATH */
+ lock->path = apr_pstrmemdup(pool, skel->children->next->data,
+ skel->children->next->len);
+
+ /* LOCK-TOKEN */
+ lock->token = apr_pstrmemdup(pool,
+ skel->children->next->next->data,
+ skel->children->next->next->len);
+
+ /* OWNER */
+ lock->owner = apr_pstrmemdup(pool,
+ skel->children->next->next->next->data,
+ skel->children->next->next->next->len);
+
+ /* COMMENT (could be just an empty atom) */
+ if (skel->children->next->next->next->next->len)
+ lock->comment =
+ apr_pstrmemdup(pool,
+ skel->children->next->next->next->next->data,
+ skel->children->next->next->next->next->len);
+
+ /* XML_P */
+ if (svn_skel__matches_atom
+ (skel->children->next->next->next->next->next, "1"))
+ lock->is_dav_comment = TRUE;
+ else
+ lock->is_dav_comment = FALSE;
+
+ /* CREATION-DATE */
+ timestr = apr_pstrmemdup
+ (pool,
+ skel->children->next->next->next->next->next->next->data,
+ skel->children->next->next->next->next->next->next->len);
+ SVN_ERR(svn_time_from_cstring(&(lock->creation_date),
+ timestr, pool));
+
+ /* EXPIRATION-DATE (could be just an empty atom) */
+ if (skel->children->next->next->next->next->next->next->next->len)
+ {
+ timestr =
+ apr_pstrmemdup
+ (pool,
+ skel->children->next->next->next->next->next->next->next->data,
+ skel->children->next->next->next->next->next->next->next->len);
+ SVN_ERR(svn_time_from_cstring(&(lock->expiration_date),
+ timestr, pool));
+ }
+
+ /* Return the structure. */
+ *lock_p = lock;
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Unparsing (conversion from native FS type to skeleton) ***/
+
+svn_error_t *
+svn_fs_base__unparse_revision_skel(svn_skel_t **skel_p,
+ const revision_t *revision,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+
+ /* TXN_ID */
+ svn_skel__prepend(svn_skel__str_atom(revision->txn_id, pool), skel);
+
+ /* "revision" */
+ svn_skel__prepend(svn_skel__str_atom("revision", pool), skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_revision_skel(skel))
+ return skel_err("revision");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_transaction_skel(svn_skel_t **skel_p,
+ const transaction_t *transaction,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+ svn_skel_t *proplist_skel, *copies_skel, *header_skel;
+ svn_string_t *id_str;
+ transaction_kind_t kind;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+
+ switch (transaction->kind)
+ {
+ case transaction_kind_committed:
+ header_skel = svn_skel__str_atom("committed", pool);
+ if ((transaction->base_id)
+ || (! SVN_IS_VALID_REVNUM(transaction->revision)))
+ return skel_err("transaction");
+ break;
+ case transaction_kind_dead:
+ header_skel = svn_skel__str_atom("dead", pool);
+ if ((! transaction->base_id)
+ || (SVN_IS_VALID_REVNUM(transaction->revision)))
+ return skel_err("transaction");
+ break;
+ case transaction_kind_normal:
+ header_skel = svn_skel__str_atom("transaction", pool);
+ if ((! transaction->base_id)
+ || (SVN_IS_VALID_REVNUM(transaction->revision)))
+ return skel_err("transaction");
+ break;
+ default:
+ return skel_err("transaction");
+ }
+
+
+ /* COPIES */
+ copies_skel = svn_skel__make_empty_list(pool);
+ if (transaction->copies && transaction->copies->nelts)
+ {
+ int i;
+ for (i = transaction->copies->nelts - 1; i >= 0; i--)
+ {
+ svn_skel__prepend(svn_skel__str_atom(
+ APR_ARRAY_IDX(transaction->copies, i,
+ const char *),
+ pool),
+ copies_skel);
+ }
+ }
+ svn_skel__prepend(copies_skel, skel);
+
+ /* PROPLIST */
+ SVN_ERR(svn_skel__unparse_proplist(&proplist_skel,
+ transaction->proplist, pool));
+ svn_skel__prepend(proplist_skel, skel);
+
+ /* REVISION or BASE-ID */
+ if (transaction->kind == transaction_kind_committed)
+ {
+ /* Committed transactions have a revision number... */
+ svn_skel__prepend(svn_skel__str_atom(apr_psprintf(pool, "%ld",
+ transaction->revision),
+ pool), skel);
+ }
+ else
+ {
+ /* ...where other transactions have a base node revision ID. */
+ id_str = svn_fs_base__id_unparse(transaction->base_id, pool);
+ svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len, pool),
+ skel);
+ }
+
+ /* ROOT-ID */
+ id_str = svn_fs_base__id_unparse(transaction->root_id, pool);
+ svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len, pool), skel);
+
+ /* KIND (see above) */
+ svn_skel__prepend(header_skel, skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_transaction_skel(skel, &kind))
+ return skel_err("transaction");
+ if (kind != transaction->kind)
+ return skel_err("transaction");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+/* Construct a skel representing CHECKSUM, allocated in POOL, and prepend
+ * it onto the existing skel SKEL. */
+static svn_error_t *
+prepend_checksum(svn_skel_t *skel,
+ svn_checksum_t *checksum,
+ apr_pool_t *pool)
+{
+ svn_skel_t *checksum_skel = svn_skel__make_empty_list(pool);
+
+ switch (checksum->kind)
+ {
+ case svn_checksum_md5:
+ svn_skel__prepend(svn_skel__mem_atom(checksum->digest,
+ APR_MD5_DIGESTSIZE, pool),
+ checksum_skel);
+ svn_skel__prepend(svn_skel__str_atom("md5", pool), checksum_skel);
+ break;
+
+ case svn_checksum_sha1:
+ svn_skel__prepend(svn_skel__mem_atom(checksum->digest,
+ APR_SHA1_DIGESTSIZE, pool),
+ checksum_skel);
+ svn_skel__prepend(svn_skel__str_atom("sha1", pool), checksum_skel);
+ break;
+
+ default:
+ return skel_err("checksum");
+ }
+ svn_skel__prepend(checksum_skel, skel);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_representation_skel(svn_skel_t **skel_p,
+ const representation_t *rep,
+ int format,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(pool);
+ svn_skel_t *header_skel = svn_skel__make_empty_list(pool);
+
+ /** Some parts of the header are common to all representations; do
+ those parts first. **/
+
+ /* SHA1 */
+ if ((format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT) && rep->sha1_checksum)
+ SVN_ERR(prepend_checksum(header_skel, rep->sha1_checksum, pool));
+
+ /* MD5 */
+ {
+ svn_checksum_t *md5_checksum = rep->md5_checksum;
+ if (! md5_checksum)
+ md5_checksum = svn_checksum_create(svn_checksum_md5, pool);
+ SVN_ERR(prepend_checksum(header_skel, md5_checksum, pool));
+ }
+
+ /* TXN */
+ if (rep->txn_id)
+ svn_skel__prepend(svn_skel__str_atom(rep->txn_id, pool), header_skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), header_skel);
+
+ /** Do the kind-specific stuff. **/
+
+ if (rep->kind == rep_kind_fulltext)
+ {
+ /*** Fulltext Representation. ***/
+
+ /* STRING-KEY */
+ if ((! rep->contents.fulltext.string_key)
+ || (! *rep->contents.fulltext.string_key))
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__str_atom(rep->contents.fulltext.string_key,
+ pool), skel);
+
+ /* "fulltext" */
+ svn_skel__prepend(svn_skel__str_atom("fulltext", pool), header_skel);
+
+ /* header */
+ svn_skel__prepend(header_skel, skel);
+ }
+ else if (rep->kind == rep_kind_delta)
+ {
+ /*** Delta Representation. ***/
+ int i;
+ apr_array_header_t *chunks = rep->contents.delta.chunks;
+
+ /* Loop backwards through the windows, creating and prepending skels. */
+ for (i = chunks->nelts; i > 0; i--)
+ {
+ svn_skel_t *window_skel = svn_skel__make_empty_list(pool);
+ svn_skel_t *chunk_skel = svn_skel__make_empty_list(pool);
+ svn_skel_t *diff_skel = svn_skel__make_empty_list(pool);
+ const char *size_str, *offset_str, *version_str;
+ rep_delta_chunk_t *chunk = APR_ARRAY_IDX(chunks, i - 1,
+ rep_delta_chunk_t *);
+
+ /* OFFSET */
+ offset_str = apr_psprintf(pool, "%" SVN_FILESIZE_T_FMT,
+ chunk->offset);
+
+ /* SIZE */
+ size_str = apr_psprintf(pool, "%" APR_SIZE_T_FMT, chunk->size);
+
+ /* VERSION */
+ version_str = apr_psprintf(pool, "%d", chunk->version);
+
+ /* DIFF */
+ if ((! chunk->string_key) || (! *chunk->string_key))
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), diff_skel);
+ else
+ svn_skel__prepend(svn_skel__str_atom(chunk->string_key, pool),
+ diff_skel);
+ svn_skel__prepend(svn_skel__str_atom(version_str, pool), diff_skel);
+ svn_skel__prepend(svn_skel__str_atom("svndiff", pool), diff_skel);
+
+ /* REP-KEY */
+ if ((! chunk->rep_key) || (! *(chunk->rep_key)))
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool),
+ window_skel);
+ else
+ svn_skel__prepend(svn_skel__str_atom(chunk->rep_key, pool),
+ window_skel);
+ svn_skel__prepend(svn_skel__str_atom(size_str, pool), window_skel);
+ svn_skel__prepend(diff_skel, window_skel);
+
+ /* window header. */
+ svn_skel__prepend(window_skel, chunk_skel);
+ svn_skel__prepend(svn_skel__str_atom(offset_str, pool),
+ chunk_skel);
+
+ /* Add this window item to the main skel. */
+ svn_skel__prepend(chunk_skel, skel);
+ }
+
+ /* "delta" */
+ svn_skel__prepend(svn_skel__str_atom("delta", pool), header_skel);
+
+ /* header */
+ svn_skel__prepend(header_skel, skel);
+ }
+ else /* unknown kind */
+ SVN_ERR_MALFUNCTION();
+
+ /* Validate and return the skel. */
+ if (! is_valid_representation_skel(skel))
+ return skel_err("representation");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_node_revision_skel(svn_skel_t **skel_p,
+ const node_revision_t *noderev,
+ int format,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+ svn_skel_t *header_skel;
+ const char *num_str;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+ header_skel = svn_skel__make_empty_list(pool);
+
+ /* Store mergeinfo stuffs only if the schema level supports it. */
+ if (format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ {
+ /* MERGEINFO-COUNT */
+ num_str = apr_psprintf(pool, "%" APR_INT64_T_FMT,
+ noderev->mergeinfo_count);
+ svn_skel__prepend(svn_skel__str_atom(num_str, pool), header_skel);
+
+ /* HAS-MERGEINFO */
+ svn_skel__prepend(svn_skel__mem_atom(noderev->has_mergeinfo ? "1" : "0",
+ 1, pool), header_skel);
+
+ /* PREDECESSOR-COUNT padding (only if we *don't* have a valid
+ value; if we do, we'll pick that up below) */
+ if (noderev->predecessor_count == -1)
+ {
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), header_skel);
+ }
+ }
+
+ /* PREDECESSOR-COUNT */
+ if (noderev->predecessor_count != -1)
+ {
+ const char *count_str = apr_psprintf(pool, "%d",
+ noderev->predecessor_count);
+ svn_skel__prepend(svn_skel__str_atom(count_str, pool), header_skel);
+ }
+
+ /* PREDECESSOR-ID */
+ if (noderev->predecessor_id)
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(noderev->predecessor_id,
+ pool);
+ svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len, pool),
+ header_skel);
+ }
+ else
+ {
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), header_skel);
+ }
+
+ /* CREATED-PATH */
+ svn_skel__prepend(svn_skel__str_atom(noderev->created_path, pool),
+ header_skel);
+
+ /* KIND */
+ if (noderev->kind == svn_node_file)
+ svn_skel__prepend(svn_skel__str_atom("file", pool), header_skel);
+ else if (noderev->kind == svn_node_dir)
+ svn_skel__prepend(svn_skel__str_atom("dir", pool), header_skel);
+ else
+ SVN_ERR_MALFUNCTION();
+
+ /* ### do we really need to check *node->FOO_key ? if a key doesn't
+ ### exist, then the field should be NULL ... */
+
+ /* EDIT-DATA-KEY (optional) */
+ if ((noderev->edit_key) && (*noderev->edit_key))
+ svn_skel__prepend(svn_skel__str_atom(noderev->edit_key, pool), skel);
+
+ /* DATA-KEY | (DATA-KEY DATA-KEY-UNIQID) */
+ if ((noderev->data_key_uniquifier) && (*noderev->data_key_uniquifier))
+ {
+ /* Build a 2-tuple with a rep key and uniquifier. */
+ svn_skel_t *data_key_skel = svn_skel__make_empty_list(pool);
+
+ /* DATA-KEY-UNIQID */
+ svn_skel__prepend(svn_skel__str_atom(noderev->data_key_uniquifier,
+ pool),
+ data_key_skel);
+
+ /* DATA-KEY */
+ if ((noderev->data_key) && (*noderev->data_key))
+ svn_skel__prepend(svn_skel__str_atom(noderev->data_key, pool),
+ data_key_skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), data_key_skel);
+
+ /* Add our 2-tuple to the main skel. */
+ svn_skel__prepend(data_key_skel, skel);
+ }
+ else
+ {
+ /* Just store the rep key (or empty placeholder) in the main skel. */
+ if ((noderev->data_key) && (*noderev->data_key))
+ svn_skel__prepend(svn_skel__str_atom(noderev->data_key, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+ }
+
+ /* PROP-KEY */
+ if ((noderev->prop_key) && (*noderev->prop_key))
+ svn_skel__prepend(svn_skel__str_atom(noderev->prop_key, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* HEADER */
+ svn_skel__prepend(header_skel, skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_node_revision_skel(skel))
+ return skel_err("node-revision");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_copy_skel(svn_skel_t **skel_p,
+ const copy_t *copy,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+ svn_string_t *tmp_str;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+
+ /* DST-NODE-ID */
+ tmp_str = svn_fs_base__id_unparse(copy->dst_noderev_id, pool);
+ svn_skel__prepend(svn_skel__mem_atom(tmp_str->data, tmp_str->len, pool),
+ skel);
+
+ /* SRC-TXN-ID */
+ if ((copy->src_txn_id) && (*copy->src_txn_id))
+ svn_skel__prepend(svn_skel__str_atom(copy->src_txn_id, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* SRC-PATH */
+ if ((copy->src_path) && (*copy->src_path))
+ svn_skel__prepend(svn_skel__str_atom(copy->src_path, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* "copy" */
+ if (copy->kind == copy_kind_real)
+ svn_skel__prepend(svn_skel__str_atom("copy", pool), skel);
+ else
+ svn_skel__prepend(svn_skel__str_atom("soft-copy", pool), skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_copy_skel(skel))
+ return skel_err("copy");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_entries_skel(svn_skel_t **skel_p,
+ apr_hash_t *entries,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(pool);
+ apr_hash_index_t *hi;
+
+ /* Create the skel. */
+ if (entries)
+ {
+ /* Loop over hash entries */
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+ svn_fs_id_t *value;
+ svn_string_t *id_str;
+ svn_skel_t *entry_skel = svn_skel__make_empty_list(pool);
+
+ apr_hash_this(hi, &key, &klen, &val);
+ value = val;
+
+ /* VALUE */
+ id_str = svn_fs_base__id_unparse(value, pool);
+ svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len,
+ pool),
+ entry_skel);
+
+ /* NAME */
+ svn_skel__prepend(svn_skel__mem_atom(key, klen, pool), entry_skel);
+
+ /* Add entry to the entries skel. */
+ svn_skel__prepend(entry_skel, skel);
+ }
+ }
+
+ /* Return the skel. */
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_change_skel(svn_skel_t **skel_p,
+ const change_t *change,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+ svn_string_t *tmp_str;
+ svn_fs_path_change_kind_t kind;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+
+ /* PROP-MOD */
+ if (change->prop_mod)
+ svn_skel__prepend(svn_skel__str_atom("1", pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* TEXT-MOD */
+ if (change->text_mod)
+ svn_skel__prepend(svn_skel__str_atom("1", pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* KIND */
+ switch (change->kind)
+ {
+ case svn_fs_path_change_reset:
+ svn_skel__prepend(svn_skel__str_atom("reset", pool), skel);
+ break;
+ case svn_fs_path_change_add:
+ svn_skel__prepend(svn_skel__str_atom("add", pool), skel);
+ break;
+ case svn_fs_path_change_delete:
+ svn_skel__prepend(svn_skel__str_atom("delete", pool), skel);
+ break;
+ case svn_fs_path_change_replace:
+ svn_skel__prepend(svn_skel__str_atom("replace", pool), skel);
+ break;
+ case svn_fs_path_change_modify:
+ default:
+ svn_skel__prepend(svn_skel__str_atom("modify", pool), skel);
+ break;
+ }
+
+ /* NODE-REV-ID */
+ if (change->noderev_id)
+ {
+ tmp_str = svn_fs_base__id_unparse(change->noderev_id, pool);
+ svn_skel__prepend(svn_skel__mem_atom(tmp_str->data, tmp_str->len, pool),
+ skel);
+ }
+ else
+ {
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+ }
+
+ /* PATH */
+ svn_skel__prepend(svn_skel__str_atom(change->path, pool), skel);
+
+ /* "change" */
+ svn_skel__prepend(svn_skel__str_atom("change", pool), skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_change_skel(skel, &kind))
+ return skel_err("change");
+ if (kind != change->kind)
+ return skel_err("change");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_lock_skel(svn_skel_t **skel_p,
+ const svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+
+ /* EXP-DATE is optional. If not present, just use an empty atom. */
+ if (lock->expiration_date)
+ svn_skel__prepend(svn_skel__str_atom(
+ svn_time_to_cstring(lock->expiration_date, pool),
+ pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* CREATION-DATE */
+ svn_skel__prepend(svn_skel__str_atom(
+ svn_time_to_cstring(lock->creation_date, pool),
+ pool), skel);
+
+ /* XML_P */
+ if (lock->is_dav_comment)
+ svn_skel__prepend(svn_skel__str_atom("1", pool), skel);
+ else
+ svn_skel__prepend(svn_skel__str_atom("0", pool), skel);
+
+ /* COMMENT */
+ if (lock->comment)
+ svn_skel__prepend(svn_skel__str_atom(lock->comment, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* OWNER */
+ svn_skel__prepend(svn_skel__str_atom(lock->owner, pool), skel);
+
+ /* LOCK-TOKEN */
+ svn_skel__prepend(svn_skel__str_atom(lock->token, pool), skel);
+
+ /* PATH */
+ svn_skel__prepend(svn_skel__str_atom(lock->path, pool), skel);
+
+ /* "lock" */
+ svn_skel__prepend(svn_skel__str_atom("lock", pool), skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_lock_skel(skel))
+ return skel_err("lock");
+
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_base/util/fs_skels.h b/contrib/subversion/subversion/libsvn_fs_base/util/fs_skels.h
new file mode 100644
index 0000000..63dab80
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/util/fs_skels.h
@@ -0,0 +1,177 @@
+/* fs_skels.h : headers for conversion between fs native types and
+ * skeletons
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_FS_SKELS_H
+#define SVN_LIBSVN_FS_FS_SKELS_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_fs.h"
+#include "../fs.h"
+#include "private/svn_skel.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Parsing (conversion from skeleton to native FS type) ***/
+
+
+/* Parse a `REVISION' SKEL and set *REVISION_P to the newly allocated
+ result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_revision_skel(revision_t **revision_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `TRANSACTION' SKEL and set *TRANSACTION_P to the newly allocated
+ result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_transaction_skel(transaction_t **transaction_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `REPRESENTATION' SKEL and set *REP_P to the newly allocated
+ result. Use POOL for all allocations. */
+
+svn_error_t *
+svn_fs_base__parse_representation_skel(representation_t **rep_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `NODE-REVISION' SKEL and set *NODEREV_P to the newly allocated
+ result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_node_revision_skel(node_revision_t **noderev_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `COPY' SKEL and set *COPY_P to the newly allocated result. Use
+ POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_copy_skel(copy_t **copy_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse an `ENTRIES' SKEL and set *ENTRIES_P to a new hash with const
+ char * names (the directory entry name) and svn_fs_id_t * values
+ (the node-id of the entry), or NULL if SKEL contains no entries.
+ Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_entries_skel(apr_hash_t **entries_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `CHANGE' SKEL and set *CHANGE_P to the newly allocated result.
+ Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_change_skel(change_t **change_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `LOCK' SKEL and set *LOCK_P to the newly allocated result. Use
+ POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_lock_skel(svn_lock_t **lock_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+
+
+/*** Unparsing (conversion from native FS type to skeleton) ***/
+
+
+/* Unparse REVISION into a newly allocated `REVISION' skel and set *SKEL_P
+ to the result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__unparse_revision_skel(svn_skel_t **skel_p,
+ const revision_t *revision,
+ apr_pool_t *pool);
+
+/* Unparse TRANSACTION into a newly allocated `TRANSACTION' skel and set
+ *SKEL_P to the result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__unparse_transaction_skel(svn_skel_t **skel_p,
+ const transaction_t *transaction,
+ apr_pool_t *pool);
+
+/* Unparse REP into a newly allocated `REPRESENTATION' skel and set *SKEL_P
+ to the result. Use POOL for all allocations. FORMAT is the format
+ version of the filesystem. */
+svn_error_t *
+svn_fs_base__unparse_representation_skel(svn_skel_t **skel_p,
+ const representation_t *rep,
+ int format,
+ apr_pool_t *pool);
+
+/* Unparse NODEREV into a newly allocated `NODE-REVISION' skel and set
+ *SKEL_P to the result. Use POOL for all allocations. FORMAT is the
+ format version of the filesystem. */
+svn_error_t *
+svn_fs_base__unparse_node_revision_skel(svn_skel_t **skel_p,
+ const node_revision_t *noderev,
+ int format,
+ apr_pool_t *pool);
+
+/* Unparse COPY into a newly allocated `COPY' skel and set *SKEL_P to the
+ result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__unparse_copy_skel(svn_skel_t **skel_p,
+ const copy_t *copy,
+ apr_pool_t *pool);
+
+/* Unparse an ENTRIES hash, which has const char * names (the entry
+ name) and svn_fs_id_t * values (the node-id of the entry) into a newly
+ allocated `ENTRIES' skel and set *SKEL_P to the result. Use POOL for all
+ allocations. */
+svn_error_t *
+svn_fs_base__unparse_entries_skel(svn_skel_t **skel_p,
+ apr_hash_t *entries,
+ apr_pool_t *pool);
+
+/* Unparse CHANGE into a newly allocated `CHANGE' skel and set *SKEL_P to
+ the result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__unparse_change_skel(svn_skel_t **skel_p,
+ const change_t *change,
+ apr_pool_t *pool);
+
+/* Unparse LOCK into a newly allocated `LOCK' skel and set *SKEL_P to the
+ result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__unparse_lock_skel(svn_skel_t **skel_p,
+ const svn_lock_t *lock,
+ apr_pool_t *pool);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_FS_SKELS_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_base/uuid.c b/contrib/subversion/subversion/libsvn_fs_base/uuid.c
new file mode 100644
index 0000000..c865df3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/uuid.c
@@ -0,0 +1,116 @@
+/* uuid.c : operations on repository uuids
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_pools.h"
+#include "fs.h"
+#include "trail.h"
+#include "err.h"
+#include "uuid.h"
+#include "bdb/uuids-table.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "private/svn_fs_util.h"
+
+
+struct get_uuid_args
+{
+ int idx;
+ const char **uuid;
+};
+
+
+static svn_error_t *
+txn_body_get_uuid(void *baton, trail_t *trail)
+{
+ struct get_uuid_args *args = baton;
+ return svn_fs_bdb__get_uuid(trail->fs, args->idx, args->uuid,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__populate_uuid(svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* We hit the database. */
+ {
+ const char *uuid;
+ struct get_uuid_args args;
+
+ args.idx = 1;
+ args.uuid = &uuid;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_uuid, &args,
+ FALSE, scratch_pool));
+
+ if (uuid)
+ {
+ /* Toss what we find into the cache. */
+ fs->uuid = apr_pstrdup(fs->pool, uuid);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct set_uuid_args
+{
+ int idx;
+ const char *uuid;
+};
+
+
+static svn_error_t *
+txn_body_set_uuid(void *baton, trail_t *trail)
+{
+ struct set_uuid_args *args = baton;
+ return svn_fs_bdb__set_uuid(trail->fs, args->idx, args->uuid,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__set_uuid(svn_fs_t *fs,
+ const char *uuid,
+ apr_pool_t *pool)
+{
+ struct set_uuid_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ if (! uuid)
+ uuid = svn_uuid_generate(pool);
+
+ args.idx = 1;
+ args.uuid = uuid;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_set_uuid, &args, TRUE, pool));
+
+ /* Toss our value into the cache. */
+ if (uuid)
+ fs->uuid = apr_pstrdup(fs->pool, uuid);
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_fs_base/uuid.h b/contrib/subversion/subversion/libsvn_fs_base/uuid.h
new file mode 100644
index 0000000..453a390
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_base/uuid.h
@@ -0,0 +1,49 @@
+/* uuid.h : internal interface to uuid functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_UUID_H
+#define SVN_LIBSVN_FS_UUID_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* Set FS->UUID to the value read from the database, allocated
+ in FS->POOL. Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *svn_fs_base__populate_uuid(svn_fs_t *fs,
+ apr_pool_t *scratch_pool);
+
+
+/* These functions implement some of the calls in the FS loader
+ library's fs vtable. */
+
+svn_error_t *svn_fs_base__set_uuid(svn_fs_t *fs, const char *uuid,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_UUID_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/caching.c b/contrib/subversion/subversion/libsvn_fs_fs/caching.c
new file mode 100644
index 0000000..4af48b8
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/caching.c
@@ -0,0 +1,692 @@
+/* caching.c : in-memory caching
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "fs.h"
+#include "fs_fs.h"
+#include "id.h"
+#include "dag.h"
+#include "tree.h"
+#include "temp_serializer.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_config.h"
+#include "svn_cache_config.h"
+
+#include "svn_private_config.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+
+#include "private/svn_debug.h"
+#include "private/svn_subr_private.h"
+
+/* Take the ORIGINAL string and replace all occurrences of ":" without
+ * limiting the key space. Allocate the result in POOL.
+ */
+static const char *
+normalize_key_part(const char *original,
+ apr_pool_t *pool)
+{
+ apr_size_t i;
+ apr_size_t len = strlen(original);
+ svn_stringbuf_t *normalized = svn_stringbuf_create_ensure(len, pool);
+
+ for (i = 0; i < len; ++i)
+ {
+ char c = original[i];
+ switch (c)
+ {
+ case ':': svn_stringbuf_appendbytes(normalized, "%_", 2);
+ break;
+ case '%': svn_stringbuf_appendbytes(normalized, "%%", 2);
+ break;
+ default : svn_stringbuf_appendbyte(normalized, c);
+ }
+ }
+
+ return normalized->data;
+}
+
+/* Return a memcache in *MEMCACHE_P for FS if it's configured to use
+ memcached, or NULL otherwise. Also, sets *FAIL_STOP to a boolean
+ indicating whether cache errors should be returned to the caller or
+ just passed to the FS warning handler.
+
+ *CACHE_TXDELTAS, *CACHE_FULLTEXTS and *CACHE_REVPROPS flags will be set
+ according to FS->CONFIG. *CACHE_NAMESPACE receives the cache prefix
+ to use.
+
+ Use FS->pool for allocating the memcache and CACHE_NAMESPACE, and POOL
+ for temporary allocations. */
+static svn_error_t *
+read_config(svn_memcache_t **memcache_p,
+ svn_boolean_t *fail_stop,
+ const char **cache_namespace,
+ svn_boolean_t *cache_txdeltas,
+ svn_boolean_t *cache_fulltexts,
+ svn_boolean_t *cache_revprops,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ SVN_ERR(svn_cache__make_memcache_from_config(memcache_p, ffd->config,
+ fs->pool));
+
+ /* No cache namespace by default. I.e. all FS instances share the
+ * cached data. If you specify different namespaces, the data will
+ * share / compete for the same cache memory but keys will not match
+ * across namespaces and, thus, cached data will not be shared between
+ * namespaces.
+ *
+ * Since the namespace will be concatenated with other elements to form
+ * the complete key prefix, we must make sure that the resulting string
+ * is unique and cannot be created by any other combination of elements.
+ */
+ *cache_namespace
+ = normalize_key_part(svn_hash__get_cstring(fs->config,
+ SVN_FS_CONFIG_FSFS_CACHE_NS,
+ ""),
+ pool);
+
+ /* don't cache text deltas by default.
+ * Once we reconstructed the fulltexts from the deltas,
+ * these deltas are rarely re-used. Therefore, only tools
+ * like svnadmin will activate this to speed up operations
+ * dump and verify.
+ */
+ *cache_txdeltas
+ = svn_hash__get_bool(fs->config,
+ SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
+ FALSE);
+ /* by default, cache fulltexts.
+ * Most SVN tools care about reconstructed file content.
+ * Thus, this is a reasonable default.
+ * SVN admin tools may set that to FALSE because fulltexts
+ * won't be re-used rendering the cache less effective
+ * by squeezing wanted data out.
+ */
+ *cache_fulltexts
+ = svn_hash__get_bool(fs->config,
+ SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
+ TRUE);
+
+ /* don't cache revprops by default.
+ * Revprop caching significantly speeds up operations like
+ * svn ls -v. However, it requires synchronization that may
+ * not be available or efficient in the current server setup.
+ *
+ * If the caller chose option "2", enable revprop caching if
+ * the required API support is there to make it efficient.
+ */
+ if (strcmp(svn_hash__get_cstring(fs->config,
+ SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
+ ""), "2"))
+ *cache_revprops
+ = svn_hash__get_bool(fs->config,
+ SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
+ FALSE);
+ else
+ *cache_revprops = svn_named_atomic__is_efficient();
+
+ return svn_config_get_bool(ffd->config, fail_stop,
+ CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
+ FALSE);
+}
+
+
+/* Implements svn_cache__error_handler_t
+ * This variant clears the error after logging it.
+ */
+static svn_error_t *
+warn_and_continue_on_cache_errors(svn_error_t *err,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = baton;
+ (fs->warning)(fs->warning_baton, err);
+ svn_error_clear(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_cache__error_handler_t
+ * This variant logs the error and passes it on to the callers.
+ */
+static svn_error_t *
+warn_and_fail_on_cache_errors(svn_error_t *err,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = baton;
+ (fs->warning)(fs->warning_baton, err);
+ return err;
+}
+
+#ifdef SVN_DEBUG_CACHE_DUMP_STATS
+/* Baton to be used for the dump_cache_statistics() pool cleanup function, */
+struct dump_cache_baton_t
+{
+ /* the pool about to be cleaned up. Will be used for temp. allocations. */
+ apr_pool_t *pool;
+
+ /* the cache to dump the statistics for */
+ svn_cache__t *cache;
+};
+
+/* APR pool cleanup handler that will printf the statistics of the
+ cache referenced by the baton in BATON_VOID. */
+static apr_status_t
+dump_cache_statistics(void *baton_void)
+{
+ struct dump_cache_baton_t *baton = baton_void;
+
+ apr_status_t result = APR_SUCCESS;
+ svn_cache__info_t info;
+ svn_string_t *text_stats;
+ apr_array_header_t *lines;
+ int i;
+
+ svn_error_t *err = svn_cache__get_info(baton->cache,
+ &info,
+ TRUE,
+ baton->pool);
+
+ if (! err)
+ {
+ text_stats = svn_cache__format_info(&info, baton->pool);
+ lines = svn_cstring_split(text_stats->data, "\n", FALSE, baton->pool);
+
+ for (i = 0; i < lines->nelts; ++i)
+ {
+ const char *line = APR_ARRAY_IDX(lines, i, const char *);
+#ifdef SVN_DEBUG
+ SVN_DBG(("%s\n", line));
+#endif
+ }
+ }
+
+ /* process error returns */
+ if (err)
+ {
+ result = err->apr_err;
+ svn_error_clear(err);
+ }
+
+ return result;
+}
+#endif /* SVN_DEBUG_CACHE_DUMP_STATS */
+
+/* This function sets / registers the required callbacks for a given
+ * not transaction-specific CACHE object in FS, if CACHE is not NULL.
+ *
+ * All these svn_cache__t instances shall be handled uniformly. Unless
+ * ERROR_HANDLER is NULL, register it for the given CACHE in FS.
+ */
+static svn_error_t *
+init_callbacks(svn_cache__t *cache,
+ svn_fs_t *fs,
+ svn_cache__error_handler_t error_handler,
+ apr_pool_t *pool)
+{
+ if (cache != NULL)
+ {
+#ifdef SVN_DEBUG_CACHE_DUMP_STATS
+
+ /* schedule printing the access statistics upon pool cleanup,
+ * i.e. end of FSFS session.
+ */
+ struct dump_cache_baton_t *baton;
+
+ baton = apr_palloc(pool, sizeof(*baton));
+ baton->pool = pool;
+ baton->cache = cache;
+
+ apr_pool_cleanup_register(pool,
+ baton,
+ dump_cache_statistics,
+ apr_pool_cleanup_null);
+#endif
+
+ if (error_handler)
+ SVN_ERR(svn_cache__set_error_handler(cache,
+ error_handler,
+ fs,
+ pool));
+
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Sets *CACHE_P to cache instance based on provided options.
+ * Creates memcache if MEMCACHE is not NULL. Creates membuffer cache if
+ * MEMBUFFER is not NULL. Fallbacks to inprocess cache if MEMCACHE and
+ * MEMBUFFER are NULL and pages is non-zero. Sets *CACHE_P to NULL
+ * otherwise.
+ *
+ * Unless NO_HANDLER is true, register an error handler that reports errors
+ * as warnings to the FS warning callback.
+ *
+ * Cache is allocated in POOL.
+ * */
+static svn_error_t *
+create_cache(svn_cache__t **cache_p,
+ svn_memcache_t *memcache,
+ svn_membuffer_t *membuffer,
+ apr_int64_t pages,
+ apr_int64_t items_per_page,
+ svn_cache__serialize_func_t serializer,
+ svn_cache__deserialize_func_t deserializer,
+ apr_ssize_t klen,
+ const char *prefix,
+ svn_fs_t *fs,
+ svn_boolean_t no_handler,
+ apr_pool_t *pool)
+{
+ svn_cache__error_handler_t error_handler = no_handler
+ ? NULL
+ : warn_and_fail_on_cache_errors;
+
+ if (memcache)
+ {
+ SVN_ERR(svn_cache__create_memcache(cache_p, memcache,
+ serializer, deserializer, klen,
+ prefix, pool));
+ error_handler = no_handler
+ ? NULL
+ : warn_and_continue_on_cache_errors;
+ }
+ else if (membuffer)
+ {
+ SVN_ERR(svn_cache__create_membuffer_cache(
+ cache_p, membuffer, serializer, deserializer,
+ klen, prefix, FALSE, pool));
+ }
+ else if (pages)
+ {
+ SVN_ERR(svn_cache__create_inprocess(
+ cache_p, serializer, deserializer, klen, pages,
+ items_per_page, FALSE, prefix, pool));
+ }
+ else
+ {
+ *cache_p = NULL;
+ }
+
+ SVN_ERR(init_callbacks(*cache_p, fs, error_handler, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__initialize_caches(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ const char *prefix = apr_pstrcat(pool,
+ "fsfs:", fs->uuid,
+ "/", normalize_key_part(fs->path, pool),
+ ":",
+ (char *)NULL);
+ svn_memcache_t *memcache;
+ svn_membuffer_t *membuffer;
+ svn_boolean_t no_handler;
+ svn_boolean_t cache_txdeltas;
+ svn_boolean_t cache_fulltexts;
+ svn_boolean_t cache_revprops;
+ const char *cache_namespace;
+
+ /* Evaluating the cache configuration. */
+ SVN_ERR(read_config(&memcache,
+ &no_handler,
+ &cache_namespace,
+ &cache_txdeltas,
+ &cache_fulltexts,
+ &cache_revprops,
+ fs,
+ pool));
+
+ prefix = apr_pstrcat(pool, "ns:", cache_namespace, ":", prefix, NULL);
+
+ membuffer = svn_cache__get_global_membuffer_cache();
+
+ /* Make the cache for revision roots. For the vast majority of
+ * commands, this is only going to contain a few entries (svnadmin
+ * dump/verify is an exception here), so to reduce overhead let's
+ * try to keep it to just one page. I estimate each entry has about
+ * 72 bytes of overhead (svn_revnum_t key, svn_fs_id_t +
+ * id_private_t + 3 strings for value, and the cache_entry); the
+ * default pool size is 8192, so about a hundred should fit
+ * comfortably. */
+ SVN_ERR(create_cache(&(ffd->rev_root_id_cache),
+ NULL,
+ membuffer,
+ 1, 100,
+ svn_fs_fs__serialize_id,
+ svn_fs_fs__deserialize_id,
+ sizeof(svn_revnum_t),
+ apr_pstrcat(pool, prefix, "RRI", (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ /* Rough estimate: revision DAG nodes have size around 320 bytes, so
+ * let's put 16 on a page. */
+ SVN_ERR(create_cache(&(ffd->rev_node_cache),
+ NULL,
+ membuffer,
+ 1024, 16,
+ svn_fs_fs__dag_serialize,
+ svn_fs_fs__dag_deserialize,
+ APR_HASH_KEY_STRING,
+ apr_pstrcat(pool, prefix, "DAG", (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ /* 1st level DAG node cache */
+ ffd->dag_node_cache = svn_fs_fs__create_dag_cache(pool);
+
+ /* Very rough estimate: 1K per directory. */
+ SVN_ERR(create_cache(&(ffd->dir_cache),
+ NULL,
+ membuffer,
+ 1024, 8,
+ svn_fs_fs__serialize_dir_entries,
+ svn_fs_fs__deserialize_dir_entries,
+ APR_HASH_KEY_STRING,
+ apr_pstrcat(pool, prefix, "DIR", (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ /* Only 16 bytes per entry (a revision number + the corresponding offset).
+ Since we want ~8k pages, that means 512 entries per page. */
+ SVN_ERR(create_cache(&(ffd->packed_offset_cache),
+ NULL,
+ membuffer,
+ 32, 1,
+ svn_fs_fs__serialize_manifest,
+ svn_fs_fs__deserialize_manifest,
+ sizeof(svn_revnum_t),
+ apr_pstrcat(pool, prefix, "PACK-MANIFEST",
+ (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ /* initialize node revision cache, if caching has been enabled */
+ SVN_ERR(create_cache(&(ffd->node_revision_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ svn_fs_fs__serialize_node_revision,
+ svn_fs_fs__deserialize_node_revision,
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "NODEREVS", (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ /* initialize node change list cache, if caching has been enabled */
+ SVN_ERR(create_cache(&(ffd->changes_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ svn_fs_fs__serialize_changes,
+ svn_fs_fs__deserialize_changes,
+ sizeof(svn_revnum_t),
+ apr_pstrcat(pool, prefix, "CHANGES", (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ /* if enabled, cache fulltext and other derived information */
+ if (cache_fulltexts)
+ {
+ SVN_ERR(create_cache(&(ffd->fulltext_cache),
+ memcache,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ /* Values are svn_stringbuf_t */
+ NULL, NULL,
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "TEXT", (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ SVN_ERR(create_cache(&(ffd->properties_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ svn_fs_fs__serialize_properties,
+ svn_fs_fs__deserialize_properties,
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "PROP",
+ (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ SVN_ERR(create_cache(&(ffd->mergeinfo_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ svn_fs_fs__serialize_mergeinfo,
+ svn_fs_fs__deserialize_mergeinfo,
+ APR_HASH_KEY_STRING,
+ apr_pstrcat(pool, prefix, "MERGEINFO",
+ (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ SVN_ERR(create_cache(&(ffd->mergeinfo_existence_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ /* Values are svn_stringbuf_t */
+ NULL, NULL,
+ APR_HASH_KEY_STRING,
+ apr_pstrcat(pool, prefix, "HAS_MERGEINFO",
+ (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+ }
+ else
+ {
+ ffd->fulltext_cache = NULL;
+ ffd->properties_cache = NULL;
+ ffd->mergeinfo_cache = NULL;
+ ffd->mergeinfo_existence_cache = NULL;
+ }
+
+ /* initialize revprop cache, if full-text caching has been enabled */
+ if (cache_revprops)
+ {
+ SVN_ERR(create_cache(&(ffd->revprop_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ svn_fs_fs__serialize_properties,
+ svn_fs_fs__deserialize_properties,
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "REVPROP",
+ (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+ }
+ else
+ {
+ ffd->revprop_cache = NULL;
+ }
+
+ /* if enabled, cache text deltas and their combinations */
+ if (cache_txdeltas)
+ {
+ SVN_ERR(create_cache(&(ffd->txdelta_window_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ svn_fs_fs__serialize_txdelta_window,
+ svn_fs_fs__deserialize_txdelta_window,
+ APR_HASH_KEY_STRING,
+ apr_pstrcat(pool, prefix, "TXDELTA_WINDOW",
+ (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ SVN_ERR(create_cache(&(ffd->combined_window_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ /* Values are svn_stringbuf_t */
+ NULL, NULL,
+ APR_HASH_KEY_STRING,
+ apr_pstrcat(pool, prefix, "COMBINED_WINDOW",
+ (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+ }
+ else
+ {
+ ffd->txdelta_window_cache = NULL;
+ ffd->combined_window_cache = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton to be used for the remove_txn_cache() pool cleanup function, */
+struct txn_cleanup_baton_t
+{
+ /* the cache to reset */
+ svn_cache__t *txn_cache;
+
+ /* the position where to reset it */
+ svn_cache__t **to_reset;
+};
+
+/* APR pool cleanup handler that will reset the cache pointer given in
+ BATON_VOID. */
+static apr_status_t
+remove_txn_cache(void *baton_void)
+{
+ struct txn_cleanup_baton_t *baton = baton_void;
+
+ /* be careful not to hurt performance by resetting newer txn's caches. */
+ if (*baton->to_reset == baton->txn_cache)
+ {
+ /* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */
+ *baton->to_reset = NULL;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* This function sets / registers the required callbacks for a given
+ * transaction-specific *CACHE object, if CACHE is not NULL and a no-op
+ * otherwise. In particular, it will ensure that *CACHE gets reset to NULL
+ * upon POOL destruction latest.
+ */
+static void
+init_txn_callbacks(svn_cache__t **cache,
+ apr_pool_t *pool)
+{
+ if (*cache != NULL)
+ {
+ struct txn_cleanup_baton_t *baton;
+
+ baton = apr_palloc(pool, sizeof(*baton));
+ baton->txn_cache = *cache;
+ baton->to_reset = cache;
+
+ apr_pool_cleanup_register(pool,
+ baton,
+ remove_txn_cache,
+ apr_pool_cleanup_null);
+ }
+}
+
+svn_error_t *
+svn_fs_fs__initialize_txn_caches(svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* Transaction content needs to be carefully prefixed to virtually
+ eliminate any chance for conflicts. The (repo, txn_id) pair
+ should be unique but if a transaction fails, it might be possible
+ to start a new transaction later that receives the same id.
+ Therefore, throw in a uuid as well - just to be sure. */
+ const char *prefix = apr_pstrcat(pool,
+ "fsfs:", fs->uuid,
+ "/", fs->path,
+ ":", txn_id,
+ ":", svn_uuid_generate(pool), ":",
+ (char *)NULL);
+
+ /* We don't support caching for concurrent transactions in the SAME
+ * FSFS session. Maybe, you forgot to clean POOL. */
+ if (ffd->txn_dir_cache != NULL || ffd->concurrent_transactions)
+ {
+ ffd->txn_dir_cache = NULL;
+ ffd->concurrent_transactions = TRUE;
+
+ return SVN_NO_ERROR;
+ }
+
+ /* create a txn-local directory cache */
+ SVN_ERR(create_cache(&ffd->txn_dir_cache,
+ NULL,
+ svn_cache__get_global_membuffer_cache(),
+ 1024, 8,
+ svn_fs_fs__serialize_dir_entries,
+ svn_fs_fs__deserialize_dir_entries,
+ APR_HASH_KEY_STRING,
+ apr_pstrcat(pool, prefix, "TXNDIR",
+ (char *)NULL),
+ fs,
+ TRUE,
+ pool));
+
+ /* reset the transaction-specific cache if the pool gets cleaned up. */
+ init_txn_callbacks(&(ffd->txn_dir_cache), pool);
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_fs_fs__reset_txn_caches(svn_fs_t *fs)
+{
+ /* we can always just reset the caches. This may degrade performance but
+ * can never cause in incorrect behavior. */
+
+ fs_fs_data_t *ffd = fs->fsap_data;
+ ffd->txn_dir_cache = NULL;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/dag.c b/contrib/subversion/subversion/libsvn_fs_fs/dag.c
new file mode 100644
index 0000000..3c51ffd
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/dag.c
@@ -0,0 +1,1338 @@
+/* dag.c : DAG-like interface filesystem, private to libsvn_fs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "dag.h"
+#include "fs.h"
+#include "key-gen.h"
+#include "fs_fs.h"
+#include "id.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "private/svn_fspath.h"
+#include "svn_private_config.h"
+#include "private/svn_temp_serializer.h"
+#include "temp_serializer.h"
+
+
+/* Initializing a filesystem. */
+
+struct dag_node_t
+{
+ /* The filesystem this dag node came from. */
+ svn_fs_t *fs;
+
+ /* The node revision ID for this dag node, allocated in POOL. */
+ svn_fs_id_t *id;
+
+ /* In the special case that this node is the root of a transaction
+ that has not yet been modified, the node revision ID for this dag
+ node's predecessor; otherwise NULL. (Used in
+ svn_fs_node_created_rev.) */
+ const svn_fs_id_t *fresh_root_predecessor_id;
+
+ /* The node's type (file, dir, etc.) */
+ svn_node_kind_t kind;
+
+ /* The node's NODE-REVISION, or NULL if we haven't read it in yet.
+ This is allocated in this node's POOL.
+
+ If you're willing to respect all the rules above, you can munge
+ this yourself, but you're probably better off just calling
+ `get_node_revision' and `set_node_revision', which take care of
+ things for you. */
+ node_revision_t *node_revision;
+
+ /* The pool to allocate NODE_REVISION in. */
+ apr_pool_t *node_pool;
+
+ /* the path at which this node was created. */
+ const char *created_path;
+};
+
+
+
+/* Trivial helper/accessor functions. */
+svn_node_kind_t svn_fs_fs__dag_node_kind(dag_node_t *node)
+{
+ return node->kind;
+}
+
+
+const svn_fs_id_t *
+svn_fs_fs__dag_get_id(const dag_node_t *node)
+{
+ return node->id;
+}
+
+
+const char *
+svn_fs_fs__dag_get_created_path(dag_node_t *node)
+{
+ return node->created_path;
+}
+
+
+svn_fs_t *
+svn_fs_fs__dag_get_fs(dag_node_t *node)
+{
+ return node->fs;
+}
+
+void
+svn_fs_fs__dag_set_fs(dag_node_t *node, svn_fs_t *fs)
+{
+ node->fs = fs;
+}
+
+
+/* Dup NODEREV and all associated data into POOL.
+ Leaves the id and is_fresh_txn_root fields as zero bytes. */
+static node_revision_t *
+copy_node_revision(node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ node_revision_t *nr = apr_pcalloc(pool, sizeof(*nr));
+ nr->kind = noderev->kind;
+ if (noderev->predecessor_id)
+ nr->predecessor_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
+ nr->predecessor_count = noderev->predecessor_count;
+ if (noderev->copyfrom_path)
+ nr->copyfrom_path = apr_pstrdup(pool, noderev->copyfrom_path);
+ nr->copyfrom_rev = noderev->copyfrom_rev;
+ nr->copyroot_path = apr_pstrdup(pool, noderev->copyroot_path);
+ nr->copyroot_rev = noderev->copyroot_rev;
+ nr->data_rep = svn_fs_fs__rep_copy(noderev->data_rep, pool);
+ nr->prop_rep = svn_fs_fs__rep_copy(noderev->prop_rep, pool);
+ nr->mergeinfo_count = noderev->mergeinfo_count;
+ nr->has_mergeinfo = noderev->has_mergeinfo;
+
+ if (noderev->created_path)
+ nr->created_path = apr_pstrdup(pool, noderev->created_path);
+ return nr;
+}
+
+
+/* Set *NODEREV_P to the cached node-revision for NODE.
+ If the node-revision was not already cached in NODE, read it in,
+ allocating the cache in NODE->NODE_POOL.
+
+ If you plan to change the contents of NODE, be careful! We're
+ handing you a pointer directly to our cached node-revision, not
+ your own copy. If you change it as part of some operation, but
+ then some Berkeley DB function deadlocks or gets an error, you'll
+ need to back out your changes, or else the cache will reflect
+ changes that never got committed. It's probably best not to change
+ the structure at all. */
+static svn_error_t *
+get_node_revision(node_revision_t **noderev_p,
+ dag_node_t *node)
+{
+ /* If we've already got a copy, there's no need to read it in. */
+ if (! node->node_revision)
+ {
+ node_revision_t *noderev;
+
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, node->fs,
+ node->id, node->node_pool));
+ node->node_revision = noderev;
+ }
+
+ /* Now NODE->node_revision is set. */
+ *noderev_p = node->node_revision;
+ return SVN_NO_ERROR;
+}
+
+
+svn_boolean_t svn_fs_fs__dag_check_mutable(const dag_node_t *node)
+{
+ return (svn_fs_fs__id_txn_id(svn_fs_fs__dag_get_id(node)) != NULL);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_get_node(dag_node_t **node,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ dag_node_t *new_node;
+ node_revision_t *noderev;
+
+ /* Construct the node. */
+ new_node = apr_pcalloc(pool, sizeof(*new_node));
+ new_node->fs = fs;
+ new_node->id = svn_fs_fs__id_copy(id, pool);
+
+ /* Grab the contents so we can inspect the node's kind and created path. */
+ new_node->node_pool = pool;
+ SVN_ERR(get_node_revision(&noderev, new_node));
+
+ /* Initialize the KIND and CREATED_PATH attributes */
+ new_node->kind = noderev->kind;
+ new_node->created_path = apr_pstrdup(pool, noderev->created_path);
+
+ if (noderev->is_fresh_txn_root)
+ new_node->fresh_root_predecessor_id = noderev->predecessor_id;
+ else
+ new_node->fresh_root_predecessor_id = NULL;
+
+ /* Return a fresh new node */
+ *node = new_node;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_get_revision(svn_revnum_t *rev,
+ dag_node_t *node,
+ apr_pool_t *pool)
+{
+ /* In the special case that this is an unmodified transaction root,
+ we need to actually get the revision of the noderev's predecessor
+ (the revision root); see Issue #2608. */
+ const svn_fs_id_t *correct_id = node->fresh_root_predecessor_id
+ ? node->fresh_root_predecessor_id : node->id;
+
+ /* Look up the committed revision from the Node-ID. */
+ *rev = svn_fs_fs__id_rev(correct_id);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_get_predecessor_id(const svn_fs_id_t **id_p,
+ dag_node_t *node)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+ *id_p = noderev->predecessor_id;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_get_predecessor_count(int *count,
+ dag_node_t *node)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+ *count = noderev->predecessor_count;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__dag_get_mergeinfo_count(apr_int64_t *count,
+ dag_node_t *node)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+ *count = noderev->mergeinfo_count;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__dag_has_mergeinfo(svn_boolean_t *has_mergeinfo,
+ dag_node_t *node)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+ *has_mergeinfo = noderev->has_mergeinfo;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__dag_has_descendants_with_mergeinfo(svn_boolean_t *do_they,
+ dag_node_t *node)
+{
+ node_revision_t *noderev;
+
+ if (node->kind != svn_node_dir)
+ {
+ *do_they = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(get_node_revision(&noderev, node));
+ if (noderev->mergeinfo_count > 1)
+ *do_they = TRUE;
+ else if (noderev->mergeinfo_count == 1 && !noderev->has_mergeinfo)
+ *do_they = TRUE;
+ else
+ *do_they = FALSE;
+ return SVN_NO_ERROR;
+}
+
+
+/*** Directory node functions ***/
+
+/* Some of these are helpers for functions outside this section. */
+
+/* Set *ID_P to the node-id for entry NAME in PARENT. If no such
+ entry, set *ID_P to NULL but do not error. The node-id is
+ allocated in POOL. */
+static svn_error_t *
+dir_entry_id_from_node(const svn_fs_id_t **id_p,
+ dag_node_t *parent,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_dirent_t *dirent;
+
+ SVN_ERR(svn_fs_fs__dag_dir_entry(&dirent, parent, name, scratch_pool));
+ *id_p = dirent ? svn_fs_fs__id_copy(dirent->id, result_pool) : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Add or set in PARENT a directory entry NAME pointing to ID.
+ Allocations are done in POOL.
+
+ Assumptions:
+ - PARENT is a mutable directory.
+ - ID does not refer to an ancestor of parent
+ - NAME is a single path component
+*/
+static svn_error_t *
+set_entry(dag_node_t *parent,
+ const char *name,
+ const svn_fs_id_t *id,
+ svn_node_kind_t kind,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ node_revision_t *parent_noderev;
+
+ /* Get the parent's node-revision. */
+ SVN_ERR(get_node_revision(&parent_noderev, parent));
+
+ /* Set the new entry. */
+ return svn_fs_fs__set_entry(parent->fs, txn_id, parent_noderev, name, id,
+ kind, pool);
+}
+
+
+/* Make a new entry named NAME in PARENT. If IS_DIR is true, then the
+ node revision the new entry points to will be a directory, else it
+ will be a file. The new node will be allocated in POOL. PARENT
+ must be mutable, and must not have an entry named NAME.
+
+ Use POOL for all allocations, except caching the node_revision in PARENT.
+ */
+static svn_error_t *
+make_entry(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ svn_boolean_t is_dir,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *new_node_id;
+ node_revision_t new_noderev, *parent_noderev;
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ _("Attempted to create a node with an illegal name '%s'"), name);
+
+ /* Make sure that parent is a directory */
+ if (parent->kind != svn_node_dir)
+ return svn_error_create
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to create entry in non-directory parent"));
+
+ /* Check that the parent is mutable. */
+ if (! svn_fs_fs__dag_check_mutable(parent))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to clone child of non-mutable node"));
+
+ /* Create the new node's NODE-REVISION */
+ memset(&new_noderev, 0, sizeof(new_noderev));
+ new_noderev.kind = is_dir ? svn_node_dir : svn_node_file;
+ new_noderev.created_path = svn_fspath__join(parent_path, name, pool);
+
+ SVN_ERR(get_node_revision(&parent_noderev, parent));
+ new_noderev.copyroot_path = apr_pstrdup(pool,
+ parent_noderev->copyroot_path);
+ new_noderev.copyroot_rev = parent_noderev->copyroot_rev;
+ new_noderev.copyfrom_rev = SVN_INVALID_REVNUM;
+ new_noderev.copyfrom_path = NULL;
+
+ SVN_ERR(svn_fs_fs__create_node
+ (&new_node_id, svn_fs_fs__dag_get_fs(parent), &new_noderev,
+ svn_fs_fs__id_copy_id(svn_fs_fs__dag_get_id(parent)),
+ txn_id, pool));
+
+ /* Create a new dag_node_t for our new node */
+ SVN_ERR(svn_fs_fs__dag_get_node(child_p, svn_fs_fs__dag_get_fs(parent),
+ new_node_id, pool));
+
+ /* We can safely call set_entry because we already know that
+ PARENT is mutable, and we just created CHILD, so we know it has
+ no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */
+ return set_entry(parent, name, svn_fs_fs__dag_get_id(*child_p),
+ new_noderev.kind, txn_id, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_dir_entries(apr_hash_t **entries,
+ dag_node_t *node,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ if (noderev->kind != svn_node_dir)
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Can't get entries of non-directory"));
+
+ return svn_fs_fs__rep_contents_dir(entries, node->fs, noderev, pool);
+}
+
+svn_error_t *
+svn_fs_fs__dag_dir_entry(svn_fs_dirent_t **dirent,
+ dag_node_t *node,
+ const char* name,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ if (noderev->kind != svn_node_dir)
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Can't get entries of non-directory"));
+
+ /* Get a dirent hash for this directory. */
+ return svn_fs_fs__rep_contents_dir_entry(dirent, node->fs,
+ noderev, name, pool, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_set_entry(dag_node_t *node,
+ const char *entry_name,
+ const svn_fs_id_t *id,
+ svn_node_kind_t kind,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ /* Check it's a directory. */
+ if (node->kind != svn_node_dir)
+ return svn_error_create
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to set entry in non-directory node"));
+
+ /* Check it's mutable. */
+ if (! svn_fs_fs__dag_check_mutable(node))
+ return svn_error_create
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to set entry in immutable node"));
+
+ return set_entry(node, entry_name, id, kind, txn_id, pool);
+}
+
+
+
+/*** Proplists. ***/
+
+svn_error_t *
+svn_fs_fs__dag_get_proplist(apr_hash_t **proplist_p,
+ dag_node_t *node,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ apr_hash_t *proplist = NULL;
+
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ SVN_ERR(svn_fs_fs__get_proplist(&proplist, node->fs,
+ noderev, pool));
+
+ *proplist_p = proplist;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_set_proplist(dag_node_t *node,
+ apr_hash_t *proplist,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Sanity check: this node better be mutable! */
+ if (! svn_fs_fs__dag_check_mutable(node))
+ {
+ svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Can't set proplist on *immutable* node-revision %s",
+ idstr->data);
+ }
+
+ /* Go get a fresh NODE-REVISION for this node. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ /* Set the new proplist. */
+ return svn_fs_fs__set_proplist(node->fs, noderev, proplist, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_increment_mergeinfo_count(dag_node_t *node,
+ apr_int64_t increment,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Sanity check: this node better be mutable! */
+ if (! svn_fs_fs__dag_check_mutable(node))
+ {
+ svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Can't increment mergeinfo count on *immutable* node-revision %s",
+ idstr->data);
+ }
+
+ if (increment == 0)
+ return SVN_NO_ERROR;
+
+ /* Go get a fresh NODE-REVISION for this node. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ noderev->mergeinfo_count += increment;
+ if (noderev->mergeinfo_count < 0)
+ {
+ svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ apr_psprintf(pool,
+ _("Can't increment mergeinfo count on node-revision %%s "
+ "to negative value %%%s"),
+ APR_INT64_T_FMT),
+ idstr->data, noderev->mergeinfo_count);
+ }
+ if (noderev->mergeinfo_count > 1 && noderev->kind == svn_node_file)
+ {
+ svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ apr_psprintf(pool,
+ _("Can't increment mergeinfo count on *file* "
+ "node-revision %%s to %%%s (> 1)"),
+ APR_INT64_T_FMT),
+ idstr->data, noderev->mergeinfo_count);
+ }
+
+ /* Flush it out. */
+ return svn_fs_fs__put_node_revision(node->fs, noderev->id,
+ noderev, FALSE, pool);
+}
+
+svn_error_t *
+svn_fs_fs__dag_set_has_mergeinfo(dag_node_t *node,
+ svn_boolean_t has_mergeinfo,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Sanity check: this node better be mutable! */
+ if (! svn_fs_fs__dag_check_mutable(node))
+ {
+ svn_string_t *idstr = svn_fs_fs__id_unparse(node->id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Can't set mergeinfo flag on *immutable* node-revision %s",
+ idstr->data);
+ }
+
+ /* Go get a fresh NODE-REVISION for this node. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ noderev->has_mergeinfo = has_mergeinfo;
+
+ /* Flush it out. */
+ return svn_fs_fs__put_node_revision(node->fs, noderev->id,
+ noderev, FALSE, pool);
+}
+
+
+/*** Roots. ***/
+
+svn_error_t *
+svn_fs_fs__dag_revision_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *root_id;
+
+ SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
+ return svn_fs_fs__dag_get_node(node_p, fs, root_id, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_txn_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *root_id, *ignored;
+
+ SVN_ERR(svn_fs_fs__get_txn_ids(&root_id, &ignored, fs, txn_id, pool));
+ return svn_fs_fs__dag_get_node(node_p, fs, root_id, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_txn_base_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *base_root_id, *ignored;
+
+ SVN_ERR(svn_fs_fs__get_txn_ids(&ignored, &base_root_id, fs, txn_id, pool));
+ return svn_fs_fs__dag_get_node(node_p, fs, base_root_id, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_clone_child(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *copy_id,
+ const char *txn_id,
+ svn_boolean_t is_parent_copyroot,
+ apr_pool_t *pool)
+{
+ dag_node_t *cur_entry; /* parent's current entry named NAME */
+ const svn_fs_id_t *new_node_id; /* node id we'll put into NEW_NODE */
+ svn_fs_t *fs = svn_fs_fs__dag_get_fs(parent);
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* First check that the parent is mutable. */
+ if (! svn_fs_fs__dag_check_mutable(parent))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Attempted to clone child of non-mutable node");
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ "Attempted to make a child clone with an illegal name '%s'", name);
+
+ /* Find the node named NAME in PARENT's entries list if it exists. */
+ SVN_ERR(svn_fs_fs__dag_open(&cur_entry, parent, name, pool, subpool));
+
+ /* Check for mutability in the node we found. If it's mutable, we
+ don't need to clone it. */
+ if (svn_fs_fs__dag_check_mutable(cur_entry))
+ {
+ /* This has already been cloned */
+ new_node_id = cur_entry->id;
+ }
+ else
+ {
+ node_revision_t *noderev, *parent_noderev;
+
+ /* Go get a fresh NODE-REVISION for current child node. */
+ SVN_ERR(get_node_revision(&noderev, cur_entry));
+
+ if (is_parent_copyroot)
+ {
+ SVN_ERR(get_node_revision(&parent_noderev, parent));
+ noderev->copyroot_rev = parent_noderev->copyroot_rev;
+ noderev->copyroot_path = apr_pstrdup(pool,
+ parent_noderev->copyroot_path);
+ }
+
+ noderev->copyfrom_path = NULL;
+ noderev->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ noderev->predecessor_id = svn_fs_fs__id_copy(cur_entry->id, pool);
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ noderev->created_path = svn_fspath__join(parent_path, name, pool);
+
+ SVN_ERR(svn_fs_fs__create_successor(&new_node_id, fs, cur_entry->id,
+ noderev, copy_id, txn_id, pool));
+
+ /* Replace the ID in the parent's ENTRY list with the ID which
+ refers to the mutable clone of this child. */
+ SVN_ERR(set_entry(parent, name, new_node_id, noderev->kind, txn_id,
+ pool));
+ }
+
+ /* Initialize the youngster. */
+ svn_pool_destroy(subpool);
+ return svn_fs_fs__dag_get_node(child_p, fs, new_node_id, pool);
+}
+
+
+
+svn_error_t *
+svn_fs_fs__dag_clone_root(dag_node_t **root_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *base_root_id, *root_id;
+
+ /* Get the node ID's of the root directories of the transaction and
+ its base revision. */
+ SVN_ERR(svn_fs_fs__get_txn_ids(&root_id, &base_root_id, fs, txn_id, pool));
+
+ /* Oh, give me a clone...
+ (If they're the same, we haven't cloned the transaction's root
+ directory yet.) */
+ SVN_ERR_ASSERT(!svn_fs_fs__id_eq(root_id, base_root_id));
+
+ /*
+ * (Sung to the tune of "Home, Home on the Range", with thanks to
+ * Randall Garrett and Isaac Asimov.)
+ */
+
+ /* One way or another, root_id now identifies a cloned root node. */
+ return svn_fs_fs__dag_get_node(root_p, fs, root_id, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_delete(dag_node_t *parent,
+ const char *name,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ node_revision_t *parent_noderev;
+ svn_fs_t *fs = parent->fs;
+ svn_fs_dirent_t *dirent;
+ svn_fs_id_t *id;
+ apr_pool_t *subpool;
+
+ /* Make sure parent is a directory. */
+ if (parent->kind != svn_node_dir)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ "Attempted to delete entry '%s' from *non*-directory node", name);
+
+ /* Make sure parent is mutable. */
+ if (! svn_fs_fs__dag_check_mutable(parent))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Attempted to delete entry '%s' from immutable directory node", name);
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ "Attempted to delete a node with an illegal name '%s'", name);
+
+ /* Get a fresh NODE-REVISION for the parent node. */
+ SVN_ERR(get_node_revision(&parent_noderev, parent));
+
+ subpool = svn_pool_create(pool);
+
+ /* Search this directory for a dirent with that NAME. */
+ SVN_ERR(svn_fs_fs__rep_contents_dir_entry(&dirent, fs, parent_noderev,
+ name, subpool, subpool));
+
+ /* If we never found ID in ENTRIES (perhaps because there are no
+ ENTRIES, perhaps because ID just isn't in the existing ENTRIES
+ ... it doesn't matter), return an error. */
+ if (! dirent)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
+ "Delete failed--directory has no entry '%s'", name);
+
+ /* Copy the ID out of the subpool and release the rest of the
+ directory listing. */
+ id = svn_fs_fs__id_copy(dirent->id, pool);
+ svn_pool_destroy(subpool);
+
+ /* If mutable, remove it and any mutable children from db. */
+ SVN_ERR(svn_fs_fs__dag_delete_if_mutable(parent->fs, id, pool));
+
+ /* Remove this entry from its parent's entries list. */
+ return svn_fs_fs__set_entry(parent->fs, txn_id, parent_noderev, name,
+ NULL, svn_node_unknown, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_remove_node(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+
+ /* Fetch the node. */
+ SVN_ERR(svn_fs_fs__dag_get_node(&node, fs, id, pool));
+
+ /* If immutable, do nothing and return immediately. */
+ if (! svn_fs_fs__dag_check_mutable(node))
+ return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Attempted removal of immutable node");
+
+ /* Delete the node revision. */
+ return svn_fs_fs__delete_node_revision(fs, id, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_delete_if_mutable(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+
+ /* Get the node. */
+ SVN_ERR(svn_fs_fs__dag_get_node(&node, fs, id, pool));
+
+ /* If immutable, do nothing and return immediately. */
+ if (! svn_fs_fs__dag_check_mutable(node))
+ return SVN_NO_ERROR;
+
+ /* Else it's mutable. Recurse on directories... */
+ if (node->kind == svn_node_dir)
+ {
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+
+ /* Loop over hash entries */
+ SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, node, pool));
+ if (entries)
+ {
+ for (hi = apr_hash_first(pool, entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(svn_fs_fs__dag_delete_if_mutable(fs, dirent->id,
+ pool));
+ }
+ }
+ }
+
+ /* ... then delete the node itself, after deleting any mutable
+ representations and strings it points to. */
+ return svn_fs_fs__dag_remove_node(fs, id, pool);
+}
+
+svn_error_t *
+svn_fs_fs__dag_make_file(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ /* Call our little helper function */
+ return make_entry(child_p, parent, parent_path, name, FALSE, txn_id, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_make_dir(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ /* Call our little helper function */
+ return make_entry(child_p, parent, parent_path, name, TRUE, txn_id, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_get_contents(svn_stream_t **contents_p,
+ dag_node_t *file,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ svn_stream_t *contents;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ "Attempted to get textual contents of a *non*-file node");
+
+ /* Go get a fresh node-revision for FILE. */
+ SVN_ERR(get_node_revision(&noderev, file));
+
+ /* Get a stream to the contents. */
+ SVN_ERR(svn_fs_fs__get_contents(&contents, file->fs,
+ noderev, pool));
+
+ *contents_p = contents;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ dag_node_t *source,
+ dag_node_t *target,
+ apr_pool_t *pool)
+{
+ node_revision_t *src_noderev;
+ node_revision_t *tgt_noderev;
+
+ /* Make sure our nodes are files. */
+ if ((source && source->kind != svn_node_file)
+ || target->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ "Attempted to get textual contents of a *non*-file node");
+
+ /* Go get fresh node-revisions for the nodes. */
+ if (source)
+ SVN_ERR(get_node_revision(&src_noderev, source));
+ else
+ src_noderev = NULL;
+ SVN_ERR(get_node_revision(&tgt_noderev, target));
+
+ /* Get the delta stream. */
+ return svn_fs_fs__get_file_delta_stream(stream_p, target->fs,
+ src_noderev, tgt_noderev, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_try_process_file_contents(svn_boolean_t *success,
+ dag_node_t *node,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Go get fresh node-revisions for the nodes. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ return svn_fs_fs__try_process_file_contents(success, node->fs,
+ noderev,
+ processor, baton, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_file_length(svn_filesize_t *length,
+ dag_node_t *file,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ "Attempted to get length of a *non*-file node");
+
+ /* Go get a fresh node-revision for FILE, and . */
+ SVN_ERR(get_node_revision(&noderev, file));
+
+ return svn_fs_fs__file_length(length, noderev, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_file_checksum(svn_checksum_t **checksum,
+ dag_node_t *file,
+ svn_checksum_kind_t kind,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ "Attempted to get checksum of a *non*-file node");
+
+ SVN_ERR(get_node_revision(&noderev, file));
+
+ return svn_fs_fs__file_checksum(checksum, noderev, kind, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_get_edit_stream(svn_stream_t **contents,
+ dag_node_t *file,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ svn_stream_t *ws;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ "Attempted to set textual contents of a *non*-file node");
+
+ /* Make sure our node is mutable. */
+ if (! svn_fs_fs__dag_check_mutable(file))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ "Attempted to set textual contents of an immutable node");
+
+ /* Get the node revision. */
+ SVN_ERR(get_node_revision(&noderev, file));
+
+ SVN_ERR(svn_fs_fs__set_contents(&ws, file->fs, noderev, pool));
+
+ *contents = ws;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_fs__dag_finalize_edits(dag_node_t *file,
+ const svn_checksum_t *checksum,
+ apr_pool_t *pool)
+{
+ if (checksum)
+ {
+ svn_checksum_t *file_checksum;
+
+ SVN_ERR(svn_fs_fs__dag_file_checksum(&file_checksum, file,
+ checksum->kind, pool));
+ if (!svn_checksum_match(checksum, file_checksum))
+ return svn_checksum_mismatch_err(checksum, file_checksum, pool,
+ _("Checksum mismatch for '%s'"),
+ file->created_path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+dag_node_t *
+svn_fs_fs__dag_dup(const dag_node_t *node,
+ apr_pool_t *pool)
+{
+ /* Allocate our new node. */
+ dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
+
+ new_node->fs = node->fs;
+ new_node->id = svn_fs_fs__id_copy(node->id, pool);
+ new_node->kind = node->kind;
+ new_node->created_path = apr_pstrdup(pool, node->created_path);
+
+ /* Only copy cached node_revision_t for immutable nodes. */
+ if (node->node_revision && !svn_fs_fs__dag_check_mutable(node))
+ {
+ new_node->node_revision = copy_node_revision(node->node_revision, pool);
+ new_node->node_revision->id =
+ svn_fs_fs__id_copy(node->node_revision->id, pool);
+ new_node->node_revision->is_fresh_txn_root =
+ node->node_revision->is_fresh_txn_root;
+ }
+ new_node->node_pool = pool;
+
+ return new_node;
+}
+
+svn_error_t *
+svn_fs_fs__dag_serialize(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ dag_node_t *node = in;
+ svn_stringbuf_t *serialized;
+
+ /* create an serialization context and serialize the dag node as root */
+ svn_temp_serializer__context_t *context =
+ svn_temp_serializer__init(node,
+ sizeof(*node),
+ 1024 - SVN_TEMP_SERIALIZER__OVERHEAD,
+ pool);
+
+ /* for mutable nodes, we will _never_ cache the noderev */
+ if (node->node_revision && !svn_fs_fs__dag_check_mutable(node))
+ svn_fs_fs__noderev_serialize(context, &node->node_revision);
+ else
+ svn_temp_serializer__set_null(context,
+ (const void * const *)&node->node_revision);
+
+ /* The deserializer will use its own pool. */
+ svn_temp_serializer__set_null(context,
+ (const void * const *)&node->node_pool);
+
+ /* serialize other sub-structures */
+ svn_fs_fs__id_serialize(context, (const svn_fs_id_t **)&node->id);
+ svn_fs_fs__id_serialize(context, &node->fresh_root_predecessor_id);
+ svn_temp_serializer__add_string(context, &node->created_path);
+
+ /* return serialized data */
+ serialized = svn_temp_serializer__get(context);
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__dag_deserialize(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ dag_node_t *node = (dag_node_t *)data;
+ if (data_len == 0)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Empty noderev in cache"));
+
+ /* Copy the _full_ buffer as it also contains the sub-structures. */
+ node->fs = NULL;
+
+ /* fixup all references to sub-structures */
+ svn_fs_fs__id_deserialize(node, &node->id);
+ svn_fs_fs__id_deserialize(node,
+ (svn_fs_id_t **)&node->fresh_root_predecessor_id);
+ svn_fs_fs__noderev_deserialize(node, &node->node_revision);
+ node->node_pool = pool;
+
+ svn_temp_deserializer__resolve(node, (void**)&node->created_path);
+
+ /* return result */
+ *out = node;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__dag_open(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_fs_id_t *node_id;
+
+ /* Ensure that NAME exists in PARENT's entry list. */
+ SVN_ERR(dir_entry_id_from_node(&node_id, parent, name,
+ scratch_pool, scratch_pool));
+ if (! node_id)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL,
+ "Attempted to open non-existent child node '%s'", name);
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ "Attempted to open node with an illegal name '%s'", name);
+
+ /* Now get the node that was requested. */
+ return svn_fs_fs__dag_get_node(child_p, svn_fs_fs__dag_get_fs(parent),
+ node_id, result_pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__dag_copy(dag_node_t *to_node,
+ const char *entry,
+ dag_node_t *from_node,
+ svn_boolean_t preserve_history,
+ svn_revnum_t from_rev,
+ const char *from_path,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *id;
+
+ if (preserve_history)
+ {
+ node_revision_t *from_noderev, *to_noderev;
+ const char *copy_id;
+ const svn_fs_id_t *src_id = svn_fs_fs__dag_get_id(from_node);
+ svn_fs_t *fs = svn_fs_fs__dag_get_fs(from_node);
+
+ /* Make a copy of the original node revision. */
+ SVN_ERR(get_node_revision(&from_noderev, from_node));
+ to_noderev = copy_node_revision(from_noderev, pool);
+
+ /* Reserve a copy ID for this new copy. */
+ SVN_ERR(svn_fs_fs__reserve_copy_id(&copy_id, fs, txn_id, pool));
+
+ /* Create a successor with its predecessor pointing at the copy
+ source. */
+ to_noderev->predecessor_id = svn_fs_fs__id_copy(src_id, pool);
+ if (to_noderev->predecessor_count != -1)
+ to_noderev->predecessor_count++;
+ to_noderev->created_path =
+ svn_fspath__join(svn_fs_fs__dag_get_created_path(to_node), entry,
+ pool);
+ to_noderev->copyfrom_path = apr_pstrdup(pool, from_path);
+ to_noderev->copyfrom_rev = from_rev;
+
+ /* Set the copyroot equal to our own id. */
+ to_noderev->copyroot_path = NULL;
+
+ SVN_ERR(svn_fs_fs__create_successor(&id, fs, src_id, to_noderev,
+ copy_id, txn_id, pool));
+
+ }
+ else /* don't preserve history */
+ {
+ id = svn_fs_fs__dag_get_id(from_node);
+ }
+
+ /* Set the entry in to_node to the new id. */
+ return svn_fs_fs__dag_set_entry(to_node, entry, id, from_node->kind,
+ txn_id, pool);
+}
+
+
+
+/*** Comparison. ***/
+
+svn_error_t *
+svn_fs_fs__dag_things_different(svn_boolean_t *props_changed,
+ svn_boolean_t *contents_changed,
+ dag_node_t *node1,
+ dag_node_t *node2)
+{
+ node_revision_t *noderev1, *noderev2;
+
+ /* If we have no place to store our results, don't bother doing
+ anything. */
+ if (! props_changed && ! contents_changed)
+ return SVN_NO_ERROR;
+
+ /* The node revision skels for these two nodes. */
+ SVN_ERR(get_node_revision(&noderev1, node1));
+ SVN_ERR(get_node_revision(&noderev2, node2));
+
+ /* Compare property keys. */
+ if (props_changed != NULL)
+ *props_changed = (! svn_fs_fs__noderev_same_rep_key(noderev1->prop_rep,
+ noderev2->prop_rep));
+
+ /* Compare contents keys. */
+ if (contents_changed != NULL)
+ *contents_changed =
+ (! svn_fs_fs__noderev_same_rep_key(noderev1->data_rep,
+ noderev2->data_rep));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__dag_get_copyroot(svn_revnum_t *rev,
+ const char **path,
+ dag_node_t *node)
+{
+ node_revision_t *noderev;
+
+ /* Go get a fresh node-revision for NODE. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ *rev = noderev->copyroot_rev;
+ *path = noderev->copyroot_path;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__dag_get_copyfrom_rev(svn_revnum_t *rev,
+ dag_node_t *node)
+{
+ node_revision_t *noderev;
+
+ /* Go get a fresh node-revision for NODE. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ *rev = noderev->copyfrom_rev;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__dag_get_copyfrom_path(const char **path,
+ dag_node_t *node)
+{
+ node_revision_t *noderev;
+
+ /* Go get a fresh node-revision for NODE. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ *path = noderev->copyfrom_path;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__dag_update_ancestry(dag_node_t *target,
+ dag_node_t *source,
+ apr_pool_t *pool)
+{
+ node_revision_t *source_noderev, *target_noderev;
+
+ if (! svn_fs_fs__dag_check_mutable(target))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to update ancestry of non-mutable node"));
+
+ SVN_ERR(get_node_revision(&source_noderev, source));
+ SVN_ERR(get_node_revision(&target_noderev, target));
+
+ target_noderev->predecessor_id = source->id;
+ target_noderev->predecessor_count = source_noderev->predecessor_count;
+ if (target_noderev->predecessor_count != -1)
+ target_noderev->predecessor_count++;
+
+ return svn_fs_fs__put_node_revision(target->fs, target->id, target_noderev,
+ FALSE, pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/dag.h b/contrib/subversion/subversion/libsvn_fs_fs/dag.h
new file mode 100644
index 0000000..867b025
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/dag.h
@@ -0,0 +1,581 @@
+/* dag.h : DAG-like interface filesystem, private to libsvn_fs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_DAG_H
+#define SVN_LIBSVN_FS_DAG_H
+
+#include "svn_fs.h"
+#include "svn_delta.h"
+#include "private/svn_cache.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* The interface in this file provides all the essential filesystem
+ operations, but exposes the filesystem's DAG structure. This makes
+ it simpler to implement than the public interface, since a client
+ of this interface has to understand and cope with shared structure
+ directly as it appears in the database. However, it's still a
+ self-consistent set of invariants to maintain, making it
+ (hopefully) a useful interface boundary.
+
+ In other words:
+
+ - The dag_node_t interface exposes the internal DAG structure of
+ the filesystem, while the svn_fs.h interface does any cloning
+ necessary to make the filesystem look like a tree.
+
+ - The dag_node_t interface exposes the existence of copy nodes,
+ whereas the svn_fs.h handles them transparently.
+
+ - dag_node_t's must be explicitly cloned, whereas the svn_fs.h
+ operations make clones implicitly.
+
+ - Callers of the dag_node_t interface use Berkeley DB transactions
+ to ensure consistency between operations, while callers of the
+ svn_fs.h interface use Subversion transactions. */
+
+
+/* Generic DAG node stuff. */
+
+typedef struct dag_node_t dag_node_t;
+
+/* Fill *NODE with a dag_node_t representing node revision ID in FS,
+ allocating in POOL. */
+svn_error_t *
+svn_fs_fs__dag_get_node(dag_node_t **node,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+
+/* Return a new dag_node_t object referring to the same node as NODE,
+ allocated in POOL. If you're trying to build a structure in a
+ pool that wants to refer to dag nodes that may have been allocated
+ elsewhere, you can call this function and avoid inter-pool pointers. */
+dag_node_t *
+svn_fs_fs__dag_dup(const dag_node_t *node,
+ apr_pool_t *pool);
+
+/* Serialize a DAG node, except don't try to preserve the 'fs' member.
+ Implements svn_cache__serialize_func_t */
+svn_error_t *
+svn_fs_fs__dag_serialize(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/* Deserialize a DAG node, leaving the 'fs' member as NULL.
+ Implements svn_cache__deserialize_func_t */
+svn_error_t *
+svn_fs_fs__dag_deserialize(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/* Return the filesystem containing NODE. */
+svn_fs_t *svn_fs_fs__dag_get_fs(dag_node_t *node);
+
+/* Changes the filesystem containing NODE to FS. (Used when pulling
+ nodes out of a shared cache, say.) */
+void svn_fs_fs__dag_set_fs(dag_node_t *node, svn_fs_t *fs);
+
+
+/* Set *REV to NODE's revision number, allocating in POOL. If NODE
+ has never been committed as part of a revision, set *REV to
+ SVN_INVALID_REVNUM. */
+svn_error_t *svn_fs_fs__dag_get_revision(svn_revnum_t *rev,
+ dag_node_t *node,
+ apr_pool_t *pool);
+
+
+/* Return the node revision ID of NODE. The value returned is shared
+ with NODE, and will be deallocated when NODE is. */
+const svn_fs_id_t *svn_fs_fs__dag_get_id(const dag_node_t *node);
+
+
+/* Return the created path of NODE. The value returned is shared
+ with NODE, and will be deallocated when NODE is. */
+const char *svn_fs_fs__dag_get_created_path(dag_node_t *node);
+
+
+/* Set *ID_P to the node revision ID of NODE's immediate predecessor,
+ or NULL if NODE has no predecessor.
+ */
+svn_error_t *svn_fs_fs__dag_get_predecessor_id(const svn_fs_id_t **id_p,
+ dag_node_t *node);
+
+
+/* Set *COUNT to the number of predecessors NODE has (recursively), or
+ -1 if not known.
+ */
+/* ### This function is currently only used by 'verify'. */
+svn_error_t *svn_fs_fs__dag_get_predecessor_count(int *count,
+ dag_node_t *node);
+
+/* Set *COUNT to the number of node under NODE (inclusive) with
+ svn:mergeinfo properties.
+ */
+svn_error_t *svn_fs_fs__dag_get_mergeinfo_count(apr_int64_t *count,
+ dag_node_t *node);
+
+/* Set *DO_THEY to a flag indicating whether or not NODE is a
+ directory with at least one descendant (not including itself) with
+ svn:mergeinfo.
+ */
+svn_error_t *
+svn_fs_fs__dag_has_descendants_with_mergeinfo(svn_boolean_t *do_they,
+ dag_node_t *node);
+
+/* Set *HAS_MERGEINFO to a flag indicating whether or not NODE itself
+ has svn:mergeinfo set on it.
+ */
+svn_error_t *
+svn_fs_fs__dag_has_mergeinfo(svn_boolean_t *has_mergeinfo,
+ dag_node_t *node);
+
+/* Return non-zero IFF NODE is currently mutable. */
+svn_boolean_t svn_fs_fs__dag_check_mutable(const dag_node_t *node);
+
+/* Return the node kind of NODE. */
+svn_node_kind_t svn_fs_fs__dag_node_kind(dag_node_t *node);
+
+/* Set *PROPLIST_P to a PROPLIST hash representing the entire property
+ list of NODE, allocating from POOL. The hash has const char *
+ names (the property names) and svn_string_t * values (the property
+ values).
+
+ If properties do not exist on NODE, *PROPLIST_P will be set to
+ NULL.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_get_proplist(apr_hash_t **proplist_p,
+ dag_node_t *node,
+ apr_pool_t *pool);
+
+/* Set the property list of NODE to PROPLIST, allocating from POOL.
+ The node being changed must be mutable.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_set_proplist(dag_node_t *node,
+ apr_hash_t *proplist,
+ apr_pool_t *pool);
+
+/* Increment the mergeinfo_count field on NODE by INCREMENT. The node
+ being changed must be mutable.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_increment_mergeinfo_count(dag_node_t *node,
+ apr_int64_t increment,
+ apr_pool_t *pool);
+
+/* Set the has-mergeinfo flag on NODE to HAS_MERGEINFO. The node
+ being changed must be mutable.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_set_has_mergeinfo(dag_node_t *node,
+ svn_boolean_t has_mergeinfo,
+ apr_pool_t *pool);
+
+
+
+/* Revision and transaction roots. */
+
+
+/* Open the root of revision REV of filesystem FS, allocating from
+ POOL. Set *NODE_P to the new node. */
+svn_error_t *svn_fs_fs__dag_revision_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+
+/* Set *NODE_P to the root of transaction TXN_ID in FS, allocating
+ from POOL.
+
+ Note that the root node of TXN_ID is not necessarily mutable. If
+ no changes have been made in the transaction, then it may share its
+ root directory with its base revision. To get a mutable root node
+ for a transaction, call svn_fs_fs__dag_clone_root. */
+svn_error_t *svn_fs_fs__dag_txn_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+
+/* Set *NODE_P to the base root of transaction TXN_ID in FS,
+ allocating from POOL. Allocate the node in TRAIL->pool. */
+svn_error_t *svn_fs_fs__dag_txn_base_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+
+/* Clone the root directory of TXN_ID in FS, and update the
+ `transactions' table entry to point to it, unless this has been
+ done already. In either case, set *ROOT_P to a reference to the
+ root directory clone. Allocate *ROOT_P in POOL. */
+svn_error_t *svn_fs_fs__dag_clone_root(dag_node_t **root_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+
+
+/* Directories. */
+
+
+/* Open the node named NAME in the directory PARENT. Set *CHILD_P to
+ the new node, allocated in RESULT_POOL. NAME must be a single path
+ component; it cannot be a slash-separated directory path.
+ */
+svn_error_t *
+svn_fs_fs__dag_open(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *ENTRIES_P to a hash table of NODE's entries. The keys of the
+ table are entry names, and the values are svn_fs_dirent_t's. The
+ returned table (and its keys and values) is allocated in POOL,
+ which is also used for temporary allocations. */
+svn_error_t *svn_fs_fs__dag_dir_entries(apr_hash_t **entries_p,
+ dag_node_t *node,
+ apr_pool_t *pool);
+
+/* Fetches the NODE's entries and returns a copy of the entry selected
+ by the key value given in NAME and set *DIRENT to a copy of that
+ entry. If such entry was found, the copy will be allocated in POOL.
+ Otherwise, the *DIRENT will be set to NULL.
+ */
+/* ### This function is currently only called from dag.c. */
+svn_error_t * svn_fs_fs__dag_dir_entry(svn_fs_dirent_t **dirent,
+ dag_node_t *node,
+ const char* name,
+ apr_pool_t *pool);
+
+/* Set ENTRY_NAME in NODE to point to ID (with kind KIND), allocating
+ from POOL. NODE must be a mutable directory. ID can refer to a
+ mutable or immutable node. If ENTRY_NAME does not exist, it will
+ be created. TXN_ID is the Subversion transaction under which this
+ occurs.
+
+ Use POOL for all allocations, including to cache the node_revision in
+ NODE.
+ */
+svn_error_t *svn_fs_fs__dag_set_entry(dag_node_t *node,
+ const char *entry_name,
+ const svn_fs_id_t *id,
+ svn_node_kind_t kind,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+
+/* Make a new mutable clone of the node named NAME in PARENT, and
+ adjust PARENT's directory entry to point to it, unless NAME in
+ PARENT already refers to a mutable node. In either case, set
+ *CHILD_P to a reference to the new node, allocated in POOL. PARENT
+ must be mutable. NAME must be a single path component; it cannot
+ be a slash-separated directory path. PARENT_PATH must be the
+ canonicalized absolute path of the parent directory.
+
+ COPY_ID, if non-NULL, is a key into the `copies' table, and
+ indicates that this new node is being created as the result of a
+ copy operation, and specifically which operation that was.
+
+ PATH is the canonicalized absolute path at which this node is being
+ created.
+
+ TXN_ID is the Subversion transaction under which this occurs.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_clone_child(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *copy_id,
+ const char *txn_id,
+ svn_boolean_t is_parent_copyroot,
+ apr_pool_t *pool);
+
+
+/* Delete the directory entry named NAME from PARENT, allocating from
+ POOL. PARENT must be mutable. NAME must be a single path
+ component; it cannot be a slash-separated directory path. If the
+ node being deleted is a mutable directory, remove all mutable nodes
+ reachable from it. TXN_ID is the Subversion transaction under
+ which this occurs.
+
+ If return SVN_ERR_FS_NO_SUCH_ENTRY, then there is no entry NAME in
+ PARENT.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_delete(dag_node_t *parent,
+ const char *name,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+
+/* Delete the node revision assigned to node ID from FS's `nodes'
+ table, allocating from POOL. Also delete any mutable
+ representations and strings associated with that node revision. ID
+ may refer to a file or directory, which must be mutable.
+
+ NOTE: If ID represents a directory, and that directory has mutable
+ children, you risk orphaning those children by leaving them
+ dangling, disconnected from all DAG trees. It is assumed that
+ callers of this interface know what in the world they are doing. */
+svn_error_t *svn_fs_fs__dag_remove_node(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+
+/* Delete all mutable node revisions reachable from node ID, including
+ ID itself, from FS's `nodes' table, allocating from POOL. Also
+ delete any mutable representations and strings associated with that
+ node revision. ID may refer to a file or directory, which may be
+ mutable or immutable. */
+svn_error_t *svn_fs_fs__dag_delete_if_mutable(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+
+/* Create a new mutable directory named NAME in PARENT. Set *CHILD_P
+ to a reference to the new node, allocated in POOL. The new
+ directory has no contents, and no properties. PARENT must be
+ mutable. NAME must be a single path component; it cannot be a
+ slash-separated directory path. PARENT_PATH must be the
+ canonicalized absolute path of the parent directory. PARENT must
+ not currently have an entry named NAME. TXN_ID is the Subversion
+ transaction under which this occurs.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_make_dir(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+
+
+/* Files. */
+
+
+/* Set *CONTENTS to a readable generic stream which yields the
+ contents of FILE. Allocate the stream in POOL.
+
+ If FILE is not a file, return SVN_ERR_FS_NOT_FILE.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_get_contents(svn_stream_t **contents,
+ dag_node_t *file,
+ apr_pool_t *pool);
+
+/* Attempt to fetch the contents of NODE and pass it along with the BATON
+ to the PROCESSOR. Set *SUCCESS only of the data could be provided
+ and the processor had been called.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *
+svn_fs_fs__dag_try_process_file_contents(svn_boolean_t *success,
+ dag_node_t *node,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool);
+
+
+/* Set *STREAM_P to a delta stream that will turn the contents of SOURCE into
+ the contents of TARGET, allocated in POOL. If SOURCE is null, the empty
+ string will be used.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *
+svn_fs_fs__dag_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ dag_node_t *source,
+ dag_node_t *target,
+ apr_pool_t *pool);
+
+/* Return a generic writable stream in *CONTENTS with which to set the
+ contents of FILE. Allocate the stream in POOL.
+
+ Any previous edits on the file will be deleted, and a new edit
+ stream will be constructed.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_get_edit_stream(svn_stream_t **contents,
+ dag_node_t *file,
+ apr_pool_t *pool);
+
+
+/* Signify the completion of edits to FILE made using the stream
+ returned by svn_fs_fs__dag_get_edit_stream, allocating from POOL.
+
+ If CHECKSUM is non-null, it must match the checksum for FILE's
+ contents (note: this is not recalculated, the recorded checksum is
+ used), else the error SVN_ERR_CHECKSUM_MISMATCH is returned.
+
+ This operation is a no-op if no edits are present.
+
+ Use POOL for all allocations, including to cache the node_revision in
+ FILE.
+ */
+svn_error_t *svn_fs_fs__dag_finalize_edits(dag_node_t *file,
+ const svn_checksum_t *checksum,
+ apr_pool_t *pool);
+
+
+/* Set *LENGTH to the length of the contents of FILE.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_file_length(svn_filesize_t *length,
+ dag_node_t *file,
+ apr_pool_t *pool);
+
+/* Put the recorded checksum of type KIND for FILE into CHECKSUM, allocating
+ from POOL.
+
+ If no stored checksum is available, do not calculate the checksum,
+ just put NULL into CHECKSUM.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *
+svn_fs_fs__dag_file_checksum(svn_checksum_t **checksum,
+ dag_node_t *file,
+ svn_checksum_kind_t kind,
+ apr_pool_t *pool);
+
+/* Create a new mutable file named NAME in PARENT. Set *CHILD_P to a
+ reference to the new node, allocated in POOL. The new file's
+ contents are the empty string, and it has no properties. PARENT
+ must be mutable. NAME must be a single path component; it cannot
+ be a slash-separated directory path. PARENT_PATH must be the
+ canonicalized absolute path of the parent directory. TXN_ID is the
+ Subversion transaction under which this occurs.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_make_file(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+
+
+/* Copies */
+
+/* Make ENTRY in TO_NODE be a copy of FROM_NODE, allocating from POOL.
+ TO_NODE must be mutable. TXN_ID is the Subversion transaction
+ under which this occurs.
+
+ If PRESERVE_HISTORY is true, the new node will record that it was
+ copied from FROM_PATH in FROM_REV; therefore, FROM_NODE should be
+ the node found at FROM_PATH in FROM_REV, although this is not
+ checked. FROM_PATH should be canonicalized before being passed
+ here.
+
+ If PRESERVE_HISTORY is false, FROM_PATH and FROM_REV are ignored.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *svn_fs_fs__dag_copy(dag_node_t *to_node,
+ const char *entry,
+ dag_node_t *from_node,
+ svn_boolean_t preserve_history,
+ svn_revnum_t from_rev,
+ const char *from_path,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+
+/* Comparison */
+
+/* Find out what is the same between two nodes.
+
+ If PROPS_CHANGED is non-null, set *PROPS_CHANGED to 1 if the two
+ nodes have different property lists, or to 0 if same.
+
+ If CONTENTS_CHANGED is non-null, set *CONTENTS_CHANGED to 1 if the
+ two nodes have different contents, or to 0 if same. For files,
+ file contents are compared; for directories, the entries lists are
+ compared. If one is a file and the other is a directory, the one's
+ contents will be compared to the other's entries list. (Not
+ terribly useful, I suppose, but that's the caller's business.)
+
+ ### todo: This function only compares rep keys at the moment. This
+ may leave us with a slight chance of a false positive, though I
+ don't really see how that would happen in practice. Nevertheless,
+ it should probably be fixed.
+ */
+svn_error_t *svn_fs_fs__dag_things_different(svn_boolean_t *props_changed,
+ svn_boolean_t *contents_changed,
+ dag_node_t *node1,
+ dag_node_t *node2);
+
+
+/* Set *REV and *PATH to the copyroot revision and path of node NODE, or
+ to SVN_INVALID_REVNUM and NULL if no copyroot exists.
+ */
+svn_error_t *svn_fs_fs__dag_get_copyroot(svn_revnum_t *rev,
+ const char **path,
+ dag_node_t *node);
+
+/* Set *REV to the copyfrom revision associated with NODE.
+ */
+svn_error_t *svn_fs_fs__dag_get_copyfrom_rev(svn_revnum_t *rev,
+ dag_node_t *node);
+
+/* Set *PATH to the copyfrom path associated with NODE.
+ */
+svn_error_t *svn_fs_fs__dag_get_copyfrom_path(const char **path,
+ dag_node_t *node);
+
+/* Update *TARGET so that SOURCE is it's predecessor.
+ */
+svn_error_t *
+svn_fs_fs__dag_update_ancestry(dag_node_t *target,
+ dag_node_t *source,
+ apr_pool_t *pool);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_DAG_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/fs.c b/contrib/subversion/subversion/libsvn_fs_fs/fs.c
new file mode 100644
index 0000000..4f3a340
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/fs.c
@@ -0,0 +1,456 @@
+/* fs.c --- creating, opening and closing filesystems
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <apr_general.h>
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_thread_mutex.h>
+
+#include "svn_fs.h"
+#include "svn_delta.h"
+#include "svn_version.h"
+#include "svn_pools.h"
+#include "fs.h"
+#include "fs_fs.h"
+#include "tree.h"
+#include "lock.h"
+#include "id.h"
+#include "rep-cache.h"
+#include "svn_private_config.h"
+#include "private/svn_fs_util.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+/* A prefix for the pool userdata variables used to hold
+ per-filesystem shared data. See fs_serialized_init. */
+#define SVN_FSFS_SHARED_USERDATA_PREFIX "svn-fsfs-shared-"
+
+
+
+static svn_error_t *
+fs_serialized_init(svn_fs_t *fs, apr_pool_t *common_pool, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ const char *key;
+ void *val;
+ fs_fs_shared_data_t *ffsd;
+ apr_status_t status;
+
+ /* Note that we are allocating a small amount of long-lived data for
+ each separate repository opened during the lifetime of the
+ svn_fs_initialize pool. It's unlikely that anyone will notice
+ the modest expenditure; the alternative is to allocate each structure
+ in a subpool, add a reference-count, and add a serialized deconstructor
+ to the FS vtable. That's more machinery than it's worth.
+
+ Using the uuid to obtain the lock creates a corner case if a
+ caller uses svn_fs_set_uuid on the repository in a process where
+ other threads might be using the same repository through another
+ FS object. The only real-world consumer of svn_fs_set_uuid is
+ "svnadmin load", so this is a low-priority problem, and we don't
+ know of a better way of associating such data with the
+ repository. */
+
+ SVN_ERR_ASSERT(fs->uuid);
+ key = apr_pstrcat(pool, SVN_FSFS_SHARED_USERDATA_PREFIX, fs->uuid,
+ (char *) NULL);
+ status = apr_pool_userdata_get(&val, key, common_pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't fetch FSFS shared data"));
+ ffsd = val;
+
+ if (!ffsd)
+ {
+ ffsd = apr_pcalloc(common_pool, sizeof(*ffsd));
+ ffsd->common_pool = common_pool;
+
+ /* POSIX fcntl locks are per-process, so we need a mutex for
+ intra-process synchronization when grabbing the repository write
+ lock. */
+ SVN_ERR(svn_mutex__init(&ffsd->fs_write_lock,
+ SVN_FS_FS__USE_LOCK_MUTEX, common_pool));
+
+ /* ... not to mention locking the txn-current file. */
+ SVN_ERR(svn_mutex__init(&ffsd->txn_current_lock,
+ SVN_FS_FS__USE_LOCK_MUTEX, common_pool));
+
+ SVN_ERR(svn_mutex__init(&ffsd->txn_list_lock,
+ SVN_FS_FS__USE_LOCK_MUTEX, common_pool));
+
+ key = apr_pstrdup(common_pool, key);
+ status = apr_pool_userdata_set(ffsd, key, NULL, common_pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't store FSFS shared data"));
+ }
+
+ ffd->shared = ffsd;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* This function is provided for Subversion 1.0.x compatibility. It
+ has no effect for fsfs backed Subversion filesystems. It conforms
+ to the fs_library_vtable_t.bdb_set_errcall() API. */
+static svn_error_t *
+fs_set_errcall(svn_fs_t *fs,
+ void (*db_errcall_fcn)(const char *errpfx, char *msg))
+{
+
+ return SVN_NO_ERROR;
+}
+
+struct fs_freeze_baton_t {
+ svn_fs_t *fs;
+ svn_fs_freeze_func_t freeze_func;
+ void *freeze_baton;
+};
+
+static svn_error_t *
+fs_freeze_body(void *baton,
+ apr_pool_t *pool)
+{
+ struct fs_freeze_baton_t *b = baton;
+ svn_boolean_t exists;
+
+ SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, b->fs, pool));
+ if (exists)
+ SVN_ERR(svn_fs_fs__lock_rep_cache(b->fs, pool));
+
+ SVN_ERR(b->freeze_func(b->freeze_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fs_freeze(svn_fs_t *fs,
+ svn_fs_freeze_func_t freeze_func,
+ void *freeze_baton,
+ apr_pool_t *pool)
+{
+ struct fs_freeze_baton_t b;
+
+ b.fs = fs;
+ b.freeze_func = freeze_func;
+ b.freeze_baton = freeze_baton;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ SVN_ERR(svn_fs_fs__with_write_lock(fs, fs_freeze_body, &b, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* The vtable associated with a specific open filesystem. */
+static fs_vtable_t fs_vtable = {
+ svn_fs_fs__youngest_rev,
+ svn_fs_fs__revision_prop,
+ svn_fs_fs__revision_proplist,
+ svn_fs_fs__change_rev_prop,
+ svn_fs_fs__set_uuid,
+ svn_fs_fs__revision_root,
+ svn_fs_fs__begin_txn,
+ svn_fs_fs__open_txn,
+ svn_fs_fs__purge_txn,
+ svn_fs_fs__list_transactions,
+ svn_fs_fs__deltify,
+ svn_fs_fs__lock,
+ svn_fs_fs__generate_lock_token,
+ svn_fs_fs__unlock,
+ svn_fs_fs__get_lock,
+ svn_fs_fs__get_locks,
+ svn_fs_fs__verify_root,
+ fs_freeze,
+ fs_set_errcall
+};
+
+
+/* Creating a new filesystem. */
+
+/* Set up vtable and fsap_data fields in FS. */
+static svn_error_t *
+initialize_fs_struct(svn_fs_t *fs)
+{
+ fs_fs_data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd));
+ fs->vtable = &fs_vtable;
+ fs->fsap_data = ffd;
+ return SVN_NO_ERROR;
+}
+
+/* This implements the fs_library_vtable_t.create() API. Create a new
+ fsfs-backed Subversion filesystem at path PATH and link it into
+ *FS. Perform temporary allocations in POOL, and fs-global allocations
+ in COMMON_POOL. */
+static svn_error_t *
+fs_create(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ SVN_ERR(svn_fs__check_fs(fs, FALSE));
+
+ SVN_ERR(initialize_fs_struct(fs));
+
+ SVN_ERR(svn_fs_fs__create(fs, path, pool));
+
+ SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
+ return fs_serialized_init(fs, common_pool, pool);
+}
+
+
+
+/* Gaining access to an existing filesystem. */
+
+/* This implements the fs_library_vtable_t.open() API. Open an FSFS
+ Subversion filesystem located at PATH, set *FS to point to the
+ correct vtable for the filesystem. Use POOL for any temporary
+ allocations, and COMMON_POOL for fs-global allocations. */
+static svn_error_t *
+fs_open(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ SVN_ERR(initialize_fs_struct(fs));
+
+ SVN_ERR(svn_fs_fs__open(fs, path, pool));
+
+ SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
+ return fs_serialized_init(fs, common_pool, pool);
+}
+
+
+
+/* This implements the fs_library_vtable_t.open_for_recovery() API. */
+static svn_error_t *
+fs_open_for_recovery(svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool, apr_pool_t *common_pool)
+{
+ /* Recovery for FSFS is currently limited to recreating the 'current'
+ file from the latest revision. */
+
+ /* The only thing we have to watch out for is that the 'current' file
+ might not exist. So we'll try to create it here unconditionally,
+ and just ignore any errors that might indicate that it's already
+ present. (We'll need it to exist later anyway as a source for the
+ new file's permissions). */
+
+ /* Use a partly-filled fs pointer first to create 'current'. This will fail
+ if 'current' already exists, but we don't care about that. */
+ fs->path = apr_pstrdup(fs->pool, path);
+ svn_error_clear(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
+ "0 1 1\n", pool));
+
+ /* Now open the filesystem properly by calling the vtable method directly. */
+ return fs_open(fs, path, pool, common_pool);
+}
+
+
+
+/* This implements the fs_library_vtable_t.upgrade_fs() API. */
+static svn_error_t *
+fs_upgrade(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ SVN_ERR(svn_fs__check_fs(fs, FALSE));
+ SVN_ERR(initialize_fs_struct(fs));
+ SVN_ERR(svn_fs_fs__open(fs, path, pool));
+ SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
+ SVN_ERR(fs_serialized_init(fs, common_pool, pool));
+ return svn_fs_fs__upgrade(fs, pool);
+}
+
+static svn_error_t *
+fs_verify(svn_fs_t *fs, const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ SVN_ERR(svn_fs__check_fs(fs, FALSE));
+ SVN_ERR(initialize_fs_struct(fs));
+ SVN_ERR(svn_fs_fs__open(fs, path, pool));
+ SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
+ SVN_ERR(fs_serialized_init(fs, common_pool, pool));
+ return svn_fs_fs__verify(fs, start, end, notify_func, notify_baton,
+ cancel_func, cancel_baton, pool);
+}
+
+static svn_error_t *
+fs_pack(svn_fs_t *fs,
+ const char *path,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ SVN_ERR(svn_fs__check_fs(fs, FALSE));
+ SVN_ERR(initialize_fs_struct(fs));
+ SVN_ERR(svn_fs_fs__open(fs, path, pool));
+ SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
+ SVN_ERR(fs_serialized_init(fs, common_pool, pool));
+ return svn_fs_fs__pack(fs, notify_func, notify_baton,
+ cancel_func, cancel_baton, pool);
+}
+
+
+
+
+/* This implements the fs_library_vtable_t.hotcopy() API. Copy a
+ possibly live Subversion filesystem SRC_FS from SRC_PATH to a
+ DST_FS at DEST_PATH. If INCREMENTAL is TRUE, make an effort not to
+ re-copy data which already exists in DST_FS.
+ The CLEAN_LOGS argument is ignored and included for Subversion
+ 1.0.x compatibility. Perform all temporary allocations in POOL. */
+static svn_error_t *
+fs_hotcopy(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ const char *src_path,
+ const char *dst_path,
+ svn_boolean_t clean_logs,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__check_fs(src_fs, FALSE));
+ SVN_ERR(initialize_fs_struct(src_fs));
+ SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
+ SVN_ERR(svn_fs_fs__initialize_caches(src_fs, pool));
+ SVN_ERR(fs_serialized_init(src_fs, pool, pool));
+
+ SVN_ERR(svn_fs__check_fs(dst_fs, FALSE));
+ SVN_ERR(initialize_fs_struct(dst_fs));
+ /* In INCREMENTAL mode, svn_fs_fs__hotcopy() will open DST_FS.
+ Otherwise, it's not an FS yet --- possibly just an empty dir --- so
+ can't be opened.
+ */
+ return svn_fs_fs__hotcopy(src_fs, dst_fs, src_path, dst_path,
+ incremental, cancel_func, cancel_baton, pool);
+}
+
+
+
+/* This function is included for Subversion 1.0.x compatibility. It
+ has no effect for fsfs backed Subversion filesystems. It conforms
+ to the fs_library_vtable_t.bdb_logfiles() API. */
+static svn_error_t *
+fs_logfiles(apr_array_header_t **logfiles,
+ const char *path,
+ svn_boolean_t only_unused,
+ apr_pool_t *pool)
+{
+ /* A no-op for FSFS. */
+ *logfiles = apr_array_make(pool, 0, sizeof(const char *));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+
+/* Delete the filesystem located at path PATH. Perform any temporary
+ allocations in POOL. */
+static svn_error_t *
+fs_delete_fs(const char *path,
+ apr_pool_t *pool)
+{
+ /* Remove everything. */
+ return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
+}
+
+static const svn_version_t *
+fs_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+static const char *
+fs_get_description(void)
+{
+ return _("Module for working with a plain file (FSFS) repository.");
+}
+
+static svn_error_t *
+fs_set_svn_fs_open(svn_fs_t *fs,
+ svn_error_t *(*svn_fs_open_)(svn_fs_t **,
+ const char *,
+ apr_hash_t *,
+ apr_pool_t *))
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ ffd->svn_fs_open_ = svn_fs_open_;
+ return SVN_NO_ERROR;
+}
+
+
+/* Base FS library vtable, used by the FS loader library. */
+
+static fs_library_vtable_t library_vtable = {
+ fs_version,
+ fs_create,
+ fs_open,
+ fs_open_for_recovery,
+ fs_upgrade,
+ fs_verify,
+ fs_delete_fs,
+ fs_hotcopy,
+ fs_get_description,
+ svn_fs_fs__recover,
+ fs_pack,
+ fs_logfiles,
+ NULL /* parse_id */,
+ fs_set_svn_fs_open
+};
+
+svn_error_t *
+svn_fs_fs__init(const svn_version_t *loader_version,
+ fs_library_vtable_t **vtable, apr_pool_t* common_pool)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+
+ /* Simplified version check to make sure we can safely use the
+ VTABLE parameter. The FS loader does a more exhaustive check. */
+ if (loader_version->major != SVN_VER_MAJOR)
+ return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
+ _("Unsupported FS loader version (%d) for fsfs"),
+ loader_version->major);
+ SVN_ERR(svn_ver_check_list(fs_version(), checklist));
+
+ *vtable = &library_vtable;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/fs.h b/contrib/subversion/subversion/libsvn_fs_fs/fs.h
new file mode 100644
index 0000000..ea301f6
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/fs.h
@@ -0,0 +1,523 @@
+/* fs.h : interface to Subversion filesystem, private to libsvn_fs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_FS_H
+#define SVN_LIBSVN_FS_FS_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_network_io.h>
+
+#include "svn_fs.h"
+#include "svn_config.h"
+#include "private/svn_atomic.h"
+#include "private/svn_cache.h"
+#include "private/svn_fs_private.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_mutex.h"
+#include "private/svn_named_atomic.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** The filesystem structure. ***/
+
+/* Following are defines that specify the textual elements of the
+ native filesystem directories and revision files. */
+
+/* Names of special files in the fs_fs filesystem. */
+#define PATH_FORMAT "format" /* Contains format number */
+#define PATH_UUID "uuid" /* Contains UUID */
+#define PATH_CURRENT "current" /* Youngest revision */
+#define PATH_LOCK_FILE "write-lock" /* Revision lock file */
+#define PATH_REVS_DIR "revs" /* Directory of revisions */
+#define PATH_REVPROPS_DIR "revprops" /* Directory of revprops */
+#define PATH_TXNS_DIR "transactions" /* Directory of transactions */
+#define PATH_NODE_ORIGINS_DIR "node-origins" /* Lazy node-origin cache */
+#define PATH_TXN_PROTOS_DIR "txn-protorevs" /* Directory of proto-revs */
+#define PATH_TXN_CURRENT "txn-current" /* File with next txn key */
+#define PATH_TXN_CURRENT_LOCK "txn-current-lock" /* Lock for txn-current */
+#define PATH_LOCKS_DIR "locks" /* Directory of locks */
+#define PATH_MIN_UNPACKED_REV "min-unpacked-rev" /* Oldest revision which
+ has not been packed. */
+#define PATH_REVPROP_GENERATION "revprop-generation"
+ /* Current revprop generation*/
+#define PATH_MANIFEST "manifest" /* Manifest file name */
+#define PATH_PACKED "pack" /* Packed revision data file */
+#define PATH_EXT_PACKED_SHARD ".pack" /* Extension for packed
+ shards */
+/* If you change this, look at tests/svn_test_fs.c(maybe_install_fsfs_conf) */
+#define PATH_CONFIG "fsfs.conf" /* Configuration */
+
+/* Names of special files and file extensions for transactions */
+#define PATH_CHANGES "changes" /* Records changes made so far */
+#define PATH_TXN_PROPS "props" /* Transaction properties */
+#define PATH_NEXT_IDS "next-ids" /* Next temporary ID assignments */
+#define PATH_PREFIX_NODE "node." /* Prefix for node filename */
+#define PATH_EXT_TXN ".txn" /* Extension of txn dir */
+#define PATH_EXT_CHILDREN ".children" /* Extension for dir contents */
+#define PATH_EXT_PROPS ".props" /* Extension for node props */
+#define PATH_EXT_REV ".rev" /* Extension of protorev file */
+#define PATH_EXT_REV_LOCK ".rev-lock" /* Extension of protorev lock file */
+/* Names of files in legacy FS formats */
+#define PATH_REV "rev" /* Proto rev file */
+#define PATH_REV_LOCK "rev-lock" /* Proto rev (write) lock file */
+
+/* Names of sections and options in fsfs.conf. */
+#define CONFIG_SECTION_CACHES "caches"
+#define CONFIG_OPTION_FAIL_STOP "fail-stop"
+#define CONFIG_SECTION_REP_SHARING "rep-sharing"
+#define CONFIG_OPTION_ENABLE_REP_SHARING "enable-rep-sharing"
+#define CONFIG_SECTION_DELTIFICATION "deltification"
+#define CONFIG_OPTION_ENABLE_DIR_DELTIFICATION "enable-dir-deltification"
+#define CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION "enable-props-deltification"
+#define CONFIG_OPTION_MAX_DELTIFICATION_WALK "max-deltification-walk"
+#define CONFIG_OPTION_MAX_LINEAR_DELTIFICATION "max-linear-deltification"
+#define CONFIG_SECTION_PACKED_REVPROPS "packed-revprops"
+#define CONFIG_OPTION_REVPROP_PACK_SIZE "revprop-pack-size"
+#define CONFIG_OPTION_COMPRESS_PACKED_REVPROPS "compress-packed-revprops"
+
+/* The format number of this filesystem.
+ This is independent of the repository format number, and
+ independent of any other FS back ends. */
+#define SVN_FS_FS__FORMAT_NUMBER 6
+
+/* The minimum format number that supports svndiff version 1. */
+#define SVN_FS_FS__MIN_SVNDIFF1_FORMAT 2
+
+/* The minimum format number that supports transaction ID generation
+ using a transaction sequence in the txn-current file. */
+#define SVN_FS_FS__MIN_TXN_CURRENT_FORMAT 3
+
+/* The minimum format number that supports the "layout" filesystem
+ format option. */
+#define SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT 3
+
+/* The minimum format number that stores protorevs in a separate directory. */
+#define SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT 3
+
+/* The minimum format number that doesn't keep node and copy ID counters. */
+#define SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT 3
+
+/* The minimum format number that maintains minfo-here and minfo-count
+ noderev fields. */
+#define SVN_FS_FS__MIN_MERGEINFO_FORMAT 3
+
+/* The minimum format number that allows rep sharing. */
+#define SVN_FS_FS__MIN_REP_SHARING_FORMAT 4
+
+/* The minimum format number that supports packed shards. */
+#define SVN_FS_FS__MIN_PACKED_FORMAT 4
+
+/* The minimum format number that stores node kinds in changed-paths lists. */
+#define SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT 4
+
+/* 1.8 deltification options should work with any FSFS repo but to avoid
+ * issues with very old servers, restrict those options to the 1.6+ format*/
+#define SVN_FS_FS__MIN_DELTIFICATION_FORMAT 4
+
+/* The 1.7-dev format, never released, that packed revprops into SQLite
+ revprops.db . */
+#define SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT 5
+
+/* The minimum format number that supports packed revprops. */
+#define SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT 6
+
+/* The minimum format number that supports a configuration file (fsfs.conf) */
+#define SVN_FS_FS__MIN_CONFIG_FILE 4
+
+/* Private FSFS-specific data shared between all svn_txn_t objects that
+ relate to a particular transaction in a filesystem (as identified
+ by transaction id and filesystem UUID). Objects of this type are
+ allocated in their own subpool of the common pool. */
+typedef struct fs_fs_shared_txn_data_t
+{
+ /* The next transaction in the list, or NULL if there is no following
+ transaction. */
+ struct fs_fs_shared_txn_data_t *next;
+
+ /* This transaction's ID. For repositories whose format is less
+ than SVN_FS_FS__MIN_TXN_CURRENT_FORMAT, the ID is in the form
+ <rev>-<uniqueifier>, where <uniqueifier> runs from 0-99999 (see
+ create_txn_dir_pre_1_5() in fs_fs.c). For newer repositories,
+ the form is <rev>-<200 digit base 36 number> (see
+ create_txn_dir() in fs_fs.c). */
+ char txn_id[SVN_FS__TXN_MAX_LEN+1];
+
+ /* Whether the transaction's prototype revision file is locked for
+ writing by any thread in this process (including the current
+ thread; recursive locks are not permitted). This is effectively
+ a non-recursive mutex. */
+ svn_boolean_t being_written;
+
+ /* The pool in which this object has been allocated; a subpool of the
+ common pool. */
+ apr_pool_t *pool;
+} fs_fs_shared_txn_data_t;
+
+/* On most operating systems apr implements file locks per process, not
+ per file. On Windows apr implements the locking as per file handle
+ locks, so we don't have to add our own mutex for just in-process
+ synchronization. */
+/* Compare ../libsvn_subr/named_atomic.c:USE_THREAD_MUTEX */
+#if APR_HAS_THREADS && !defined(WIN32)
+#define SVN_FS_FS__USE_LOCK_MUTEX 1
+#else
+#define SVN_FS_FS__USE_LOCK_MUTEX 0
+#endif
+
+/* Private FSFS-specific data shared between all svn_fs_t objects that
+ relate to a particular filesystem, as identified by filesystem UUID.
+ Objects of this type are allocated in the common pool. */
+typedef struct fs_fs_shared_data_t
+{
+ /* A list of shared transaction objects for each transaction that is
+ currently active, or NULL if none are. All access to this list,
+ including the contents of the objects stored in it, is synchronised
+ under TXN_LIST_LOCK. */
+ fs_fs_shared_txn_data_t *txns;
+
+ /* A free transaction object, or NULL if there is no free object.
+ Access to this object is synchronised under TXN_LIST_LOCK. */
+ fs_fs_shared_txn_data_t *free_txn;
+
+ /* A lock for intra-process synchronization when accessing the TXNS list. */
+ svn_mutex__t *txn_list_lock;
+
+ /* A lock for intra-process synchronization when grabbing the
+ repository write lock. */
+ svn_mutex__t *fs_write_lock;
+
+ /* A lock for intra-process synchronization when locking the
+ txn-current file. */
+ svn_mutex__t *txn_current_lock;
+
+ /* The common pool, under which this object is allocated, subpools
+ of which are used to allocate the transaction objects. */
+ apr_pool_t *common_pool;
+} fs_fs_shared_data_t;
+
+/* Data structure for the 1st level DAG node cache. */
+typedef struct fs_fs_dag_cache_t fs_fs_dag_cache_t;
+
+/* Key type for all caches that use revision + offset / counter as key. */
+typedef struct pair_cache_key_t
+{
+ svn_revnum_t revision;
+
+ apr_int64_t second;
+} pair_cache_key_t;
+
+/* Private (non-shared) FSFS-specific data for each svn_fs_t object.
+ Any caches in here may be NULL. */
+typedef struct fs_fs_data_t
+{
+ /* The format number of this FS. */
+ int format;
+ /* The maximum number of files to store per directory (for sharded
+ layouts) or zero (for linear layouts). */
+ int max_files_per_dir;
+
+ /* The revision that was youngest, last time we checked. */
+ svn_revnum_t youngest_rev_cache;
+
+ /* The fsfs.conf file, parsed. Allocated in FS->pool. */
+ svn_config_t *config;
+
+ /* Caches of immutable data. (Note that if these are created with
+ svn_cache__create_memcache, the data can be shared between
+ multiple svn_fs_t's for the same filesystem.) */
+
+ /* A cache of revision root IDs, mapping from (svn_revnum_t *) to
+ (svn_fs_id_t *). (Not threadsafe.) */
+ svn_cache__t *rev_root_id_cache;
+
+ /* Caches native dag_node_t* instances and acts as a 1st level cache */
+ fs_fs_dag_cache_t *dag_node_cache;
+
+ /* DAG node cache for immutable nodes. Maps (revision, fspath)
+ to (dag_node_t *). This is the 2nd level cache for DAG nodes. */
+ svn_cache__t *rev_node_cache;
+
+ /* A cache of the contents of immutable directories; maps from
+ unparsed FS ID to a apr_hash_t * mapping (const char *) dirent
+ names to (svn_fs_dirent_t *). */
+ svn_cache__t *dir_cache;
+
+ /* Fulltext cache; currently only used with memcached. Maps from
+ rep key (revision/offset) to svn_string_t. */
+ svn_cache__t *fulltext_cache;
+
+ /* Access object to the atomics namespace used by revprop caching.
+ Will be NULL until the first access. */
+ svn_atomic_namespace__t *revprop_namespace;
+
+ /* Access object to the revprop "generation". Will be NULL until
+ the first access. */
+ svn_named_atomic__t *revprop_generation;
+
+ /* Access object to the revprop update timeout. Will be NULL until
+ the first access. */
+ svn_named_atomic__t *revprop_timeout;
+
+ /* Revision property cache. Maps from (rev,generation) to apr_hash_t. */
+ svn_cache__t *revprop_cache;
+
+ /* Node properties cache. Maps from rep key to apr_hash_t. */
+ svn_cache__t *properties_cache;
+
+ /* Pack manifest cache; a cache mapping (svn_revnum_t) shard number to
+ a manifest; and a manifest is a mapping from (svn_revnum_t) revision
+ number offset within a shard to (apr_off_t) byte-offset in the
+ respective pack file. */
+ svn_cache__t *packed_offset_cache;
+
+ /* Cache for txdelta_window_t objects; the key is (revFilePath, offset) */
+ svn_cache__t *txdelta_window_cache;
+
+ /* Cache for combined windows as svn_stringbuf_t objects;
+ the key is (revFilePath, offset) */
+ svn_cache__t *combined_window_cache;
+
+ /* Cache for node_revision_t objects; the key is (revision, id offset) */
+ svn_cache__t *node_revision_cache;
+
+ /* Cache for change lists as APR arrays of change_t * objects; the key
+ is the revision */
+ svn_cache__t *changes_cache;
+
+ /* Cache for svn_mergeinfo_t objects; the key is a combination of
+ revision, inheritance flags and path. */
+ svn_cache__t *mergeinfo_cache;
+
+ /* Cache for presence of svn_mergeinfo_t on a noderev; the key is a
+ combination of revision, inheritance flags and path; value is "1"
+ if the node has mergeinfo, "0" if it doesn't. */
+ svn_cache__t *mergeinfo_existence_cache;
+
+ /* TRUE while the we hold a lock on the write lock file. */
+ svn_boolean_t has_write_lock;
+
+ /* If set, there are or have been more than one concurrent transaction */
+ svn_boolean_t concurrent_transactions;
+
+ /* Temporary cache for changed directories yet to be committed; maps from
+ unparsed FS ID to ###x. NULL outside transactions. */
+ svn_cache__t *txn_dir_cache;
+
+ /* Data shared between all svn_fs_t objects for a given filesystem. */
+ fs_fs_shared_data_t *shared;
+
+ /* The sqlite database used for rep caching. */
+ svn_sqlite__db_t *rep_cache_db;
+
+ /* Thread-safe boolean */
+ svn_atomic_t rep_cache_db_opened;
+
+ /* The oldest revision not in a pack file. It also applies to revprops
+ * if revprop packing has been enabled by the FSFS format version. */
+ svn_revnum_t min_unpacked_rev;
+
+ /* Whether rep-sharing is supported by the filesystem
+ * and allowed by the configuration. */
+ svn_boolean_t rep_sharing_allowed;
+
+ /* File size limit in bytes up to which multiple revprops shall be packed
+ * into a single file. */
+ apr_int64_t revprop_pack_size;
+
+ /* Whether packed revprop files shall be compressed. */
+ svn_boolean_t compress_packed_revprops;
+
+ /* Whether directory nodes shall be deltified just like file nodes. */
+ svn_boolean_t deltify_directories;
+
+ /* Whether nodes properties shall be deltified. */
+ svn_boolean_t deltify_properties;
+
+ /* Restart deltification histories after each multiple of this value */
+ apr_int64_t max_deltification_walk;
+
+ /* Maximum number of length of the linear part at the top of the
+ * deltification history after which skip deltas will be used. */
+ apr_int64_t max_linear_deltification;
+
+ /* Pointer to svn_fs_open. */
+ svn_error_t *(*svn_fs_open_)(svn_fs_t **, const char *, apr_hash_t *,
+ apr_pool_t *);
+} fs_fs_data_t;
+
+
+/*** Filesystem Transaction ***/
+typedef struct transaction_t
+{
+ /* property list (const char * name, svn_string_t * value).
+ may be NULL if there are no properties. */
+ apr_hash_t *proplist;
+
+ /* node revision id of the root node. */
+ const svn_fs_id_t *root_id;
+
+ /* node revision id of the node which is the root of the revision
+ upon which this txn is base. (unfinished only) */
+ const svn_fs_id_t *base_id;
+
+ /* copies list (const char * copy_ids), or NULL if there have been
+ no copies in this transaction. */
+ apr_array_header_t *copies;
+
+} transaction_t;
+
+
+/*** Representation ***/
+/* If you add fields to this, check to see if you need to change
+ * svn_fs_fs__rep_copy. */
+typedef struct representation_t
+{
+ /* Checksums for the contents produced by this representation.
+ This checksum is for the contents the rep shows to consumers,
+ regardless of how the rep stores the data under the hood. It is
+ independent of the storage (fulltext, delta, whatever).
+
+ If checksum is NULL, then for compatibility behave as though this
+ checksum matches the expected checksum.
+
+ The md5 checksum is always filled, unless this is rep which was
+ retrieved from the rep-cache. The sha1 checksum is only computed on
+ a write, for use with rep-sharing; it may be read from an existing
+ representation, but otherwise it is NULL. */
+ svn_checksum_t *md5_checksum;
+ svn_checksum_t *sha1_checksum;
+
+ /* Revision where this representation is located. */
+ svn_revnum_t revision;
+
+ /* Offset into the revision file where it is located. */
+ apr_off_t offset;
+
+ /* The size of the representation in bytes as seen in the revision
+ file. */
+ svn_filesize_t size;
+
+ /* The size of the fulltext of the representation. If this is 0,
+ * the fulltext size is equal to representation size in the rev file, */
+ svn_filesize_t expanded_size;
+
+ /* Is this representation a transaction? */
+ const char *txn_id;
+
+ /* For rep-sharing, we need a way of uniquifying node-revs which share the
+ same representation (see svn_fs_fs__noderev_same_rep_key() ). So, we
+ store the original txn of the node rev (not the rep!), along with some
+ intra-node uniqification content.
+
+ May be NULL, in which case, it is considered to match other NULL
+ values.*/
+ const char *uniquifier;
+} representation_t;
+
+
+/*** Node-Revision ***/
+/* If you add fields to this, check to see if you need to change
+ * copy_node_revision in dag.c. */
+typedef struct node_revision_t
+{
+ /* node kind */
+ svn_node_kind_t kind;
+
+ /* The node-id for this node-rev. */
+ const svn_fs_id_t *id;
+
+ /* predecessor node revision id, or NULL if there is no predecessor
+ for this node revision */
+ const svn_fs_id_t *predecessor_id;
+
+ /* If this node-rev is a copy, where was it copied from? */
+ const char *copyfrom_path;
+ svn_revnum_t copyfrom_rev;
+
+ /* Helper for history tracing, root of the parent tree from whence
+ this node-rev was copied. */
+ svn_revnum_t copyroot_rev;
+ const char *copyroot_path;
+
+ /* number of predecessors this node revision has (recursively), or
+ -1 if not known (for backward compatibility). */
+ int predecessor_count;
+
+ /* representation key for this node's properties. may be NULL if
+ there are no properties. */
+ representation_t *prop_rep;
+
+ /* representation for this node's data. may be NULL if there is
+ no data. */
+ representation_t *data_rep;
+
+ /* path at which this node first came into existence. */
+ const char *created_path;
+
+ /* is this the unmodified root of a transaction? */
+ svn_boolean_t is_fresh_txn_root;
+
+ /* Number of nodes with svn:mergeinfo properties that are
+ descendants of this node (including it itself) */
+ apr_int64_t mergeinfo_count;
+
+ /* Does this node itself have svn:mergeinfo? */
+ svn_boolean_t has_mergeinfo;
+
+} node_revision_t;
+
+
+/*** Change ***/
+typedef struct change_t
+{
+ /* Path of the change. */
+ const char *path;
+
+ /* Node revision ID of the change. */
+ const svn_fs_id_t *noderev_id;
+
+ /* The kind of change. */
+ svn_fs_path_change_kind_t kind;
+
+ /* Text or property mods? */
+ svn_boolean_t text_mod;
+ svn_boolean_t prop_mod;
+
+ /* Node kind (possibly svn_node_unknown). */
+ svn_node_kind_t node_kind;
+
+ /* Copyfrom revision and path. */
+ svn_revnum_t copyfrom_rev;
+ const char * copyfrom_path;
+
+} change_t;
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_FS_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/fs_fs.c b/contrib/subversion/subversion/libsvn_fs_fs/fs_fs.c
new file mode 100644
index 0000000..0354a1f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/fs_fs.c
@@ -0,0 +1,11469 @@
+/* fs_fs.c --- filesystem operations specific to fs_fs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <apr_general.h>
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_uuid.h>
+#include <apr_lib.h>
+#include <apr_md5.h>
+#include <apr_sha1.h>
+#include <apr_strings.h>
+#include <apr_thread_mutex.h>
+
+#include "svn_pools.h"
+#include "svn_fs.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_props.h"
+#include "svn_sorts.h"
+#include "svn_string.h"
+#include "svn_time.h"
+#include "svn_mergeinfo.h"
+#include "svn_config.h"
+#include "svn_ctype.h"
+#include "svn_version.h"
+
+#include "fs.h"
+#include "tree.h"
+#include "lock.h"
+#include "key-gen.h"
+#include "fs_fs.h"
+#include "id.h"
+#include "rep-cache.h"
+#include "temp_serializer.h"
+
+#include "private/svn_string_private.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_delta_private.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+#include "temp_serializer.h"
+
+/* An arbitrary maximum path length, so clients can't run us out of memory
+ * by giving us arbitrarily large paths. */
+#define FSFS_MAX_PATH_LEN 4096
+
+/* The default maximum number of files per directory to store in the
+ rev and revprops directory. The number below is somewhat arbitrary,
+ and can be overridden by defining the macro while compiling; the
+ figure of 1000 is reasonable for VFAT filesystems, which are by far
+ the worst performers in this area. */
+#ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
+#define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
+#endif
+
+/* Begin deltification after a node history exceeded this this limit.
+ Useful values are 4 to 64 with 16 being a good compromise between
+ computational overhead and repository size savings.
+ Should be a power of 2.
+ Values < 2 will result in standard skip-delta behavior. */
+#define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16
+
+/* Finding a deltification base takes operations proportional to the
+ number of changes being skipped. To prevent exploding runtime
+ during commits, limit the deltification range to this value.
+ Should be a power of 2 minus one.
+ Values < 1 disable deltification. */
+#define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023
+
+/* Give writing processes 10 seconds to replace an existing revprop
+ file with a new one. After that time, we assume that the writing
+ process got aborted and that we have re-read revprops. */
+#define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
+
+/* The following are names of atomics that will be used to communicate
+ * revprop updates across all processes on this machine. */
+#define ATOMIC_REVPROP_GENERATION "rev-prop-generation"
+#define ATOMIC_REVPROP_TIMEOUT "rev-prop-timeout"
+#define ATOMIC_REVPROP_NAMESPACE "rev-prop-atomics"
+
+/* Following are defines that specify the textual elements of the
+ native filesystem directories and revision files. */
+
+/* Headers used to describe node-revision in the revision file. */
+#define HEADER_ID "id"
+#define HEADER_TYPE "type"
+#define HEADER_COUNT "count"
+#define HEADER_PROPS "props"
+#define HEADER_TEXT "text"
+#define HEADER_CPATH "cpath"
+#define HEADER_PRED "pred"
+#define HEADER_COPYFROM "copyfrom"
+#define HEADER_COPYROOT "copyroot"
+#define HEADER_FRESHTXNRT "is-fresh-txn-root"
+#define HEADER_MINFO_HERE "minfo-here"
+#define HEADER_MINFO_CNT "minfo-cnt"
+
+/* Kinds that a change can be. */
+#define ACTION_MODIFY "modify"
+#define ACTION_ADD "add"
+#define ACTION_DELETE "delete"
+#define ACTION_REPLACE "replace"
+#define ACTION_RESET "reset"
+
+/* True and False flags. */
+#define FLAG_TRUE "true"
+#define FLAG_FALSE "false"
+
+/* Kinds that a node-rev can be. */
+#define KIND_FILE "file"
+#define KIND_DIR "dir"
+
+/* Kinds of representation. */
+#define REP_PLAIN "PLAIN"
+#define REP_DELTA "DELTA"
+
+/* Notes:
+
+To avoid opening and closing the rev-files all the time, it would
+probably be advantageous to keep each rev-file open for the
+lifetime of the transaction object. I'll leave that as a later
+optimization for now.
+
+I didn't keep track of pool lifetimes at all in this code. There
+are likely some errors because of that.
+
+*/
+
+/* The vtable associated with an open transaction object. */
+static txn_vtable_t txn_vtable = {
+ svn_fs_fs__commit_txn,
+ svn_fs_fs__abort_txn,
+ svn_fs_fs__txn_prop,
+ svn_fs_fs__txn_proplist,
+ svn_fs_fs__change_txn_prop,
+ svn_fs_fs__txn_root,
+ svn_fs_fs__change_txn_props
+};
+
+/* Declarations. */
+
+static svn_error_t *
+read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
+ const char *path,
+ apr_pool_t *pool);
+
+static svn_error_t *
+update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool);
+
+static svn_error_t *
+get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
+
+static svn_error_t *
+verify_walker(representation_t *rep,
+ void *baton,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool);
+
+/* Pathname helper functions */
+
+/* Return TRUE is REV is packed in FS, FALSE otherwise. */
+static svn_boolean_t
+is_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ return (rev < ffd->min_unpacked_rev);
+}
+
+/* Return TRUE is REV is packed in FS, FALSE otherwise. */
+static svn_boolean_t
+is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* rev 0 will not be packed */
+ return (rev < ffd->min_unpacked_rev)
+ && (rev != 0)
+ && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT);
+}
+
+static const char *
+path_format(svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_FORMAT, pool);
+}
+
+static APR_INLINE const char *
+path_uuid(svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_UUID, pool);
+}
+
+const char *
+svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_CURRENT, pool);
+}
+
+static APR_INLINE const char *
+path_txn_current(svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool);
+}
+
+static APR_INLINE const char *
+path_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool);
+}
+
+static APR_INLINE const char *
+path_lock(svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool);
+}
+
+static const char *
+path_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool);
+}
+
+static const char *
+path_rev_packed(svn_fs_t *fs, svn_revnum_t rev, const char *kind,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ assert(ffd->max_files_per_dir);
+ assert(is_packed_rev(fs, rev));
+
+ return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
+ apr_psprintf(pool,
+ "%ld" PATH_EXT_PACKED_SHARD,
+ rev / ffd->max_files_per_dir),
+ kind, NULL);
+}
+
+static const char *
+path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ assert(ffd->max_files_per_dir);
+ return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
+ apr_psprintf(pool, "%ld",
+ rev / ffd->max_files_per_dir),
+ NULL);
+}
+
+static const char *
+path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ assert(! is_packed_rev(fs, rev));
+
+ if (ffd->max_files_per_dir)
+ {
+ return svn_dirent_join(path_rev_shard(fs, rev, pool),
+ apr_psprintf(pool, "%ld", rev),
+ pool);
+ }
+
+ return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
+ apr_psprintf(pool, "%ld", rev), NULL);
+}
+
+svn_error_t *
+svn_fs_fs__path_rev_absolute(const char **path,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT
+ || ! is_packed_rev(fs, rev))
+ {
+ *path = path_rev(fs, rev, pool);
+ }
+ else
+ {
+ *path = path_rev_packed(fs, rev, PATH_PACKED, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static const char *
+path_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ assert(ffd->max_files_per_dir);
+ return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
+ apr_psprintf(pool, "%ld",
+ rev / ffd->max_files_per_dir),
+ NULL);
+}
+
+static const char *
+path_revprops_pack_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ assert(ffd->max_files_per_dir);
+ return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
+ apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD,
+ rev / ffd->max_files_per_dir),
+ NULL);
+}
+
+static const char *
+path_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (ffd->max_files_per_dir)
+ {
+ return svn_dirent_join(path_revprops_shard(fs, rev, pool),
+ apr_psprintf(pool, "%ld", rev),
+ pool);
+ }
+
+ return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
+ apr_psprintf(pool, "%ld", rev), NULL);
+}
+
+static APR_INLINE const char *
+path_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL);
+ return svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
+ apr_pstrcat(pool, txn_id, PATH_EXT_TXN,
+ (char *)NULL),
+ NULL);
+}
+
+/* Return the name of the sha1->rep mapping file in transaction TXN_ID
+ * within FS for the given SHA1 checksum. Use POOL for allocations.
+ */
+static APR_INLINE const char *
+path_txn_sha1(svn_fs_t *fs, const char *txn_id, svn_checksum_t *sha1,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join(path_txn_dir(fs, txn_id, pool),
+ svn_checksum_to_cstring(sha1, pool),
+ pool);
+}
+
+static APR_INLINE const char *
+path_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
+{
+ return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool);
+}
+
+static APR_INLINE const char *
+path_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
+{
+ return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool);
+}
+
+static APR_INLINE const char *
+path_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
+{
+ return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool);
+}
+
+static APR_INLINE const char *
+path_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool);
+}
+
+
+static APR_INLINE const char *
+path_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
+ return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
+ apr_pstrcat(pool, txn_id, PATH_EXT_REV,
+ (char *)NULL),
+ NULL);
+ else
+ return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool);
+}
+
+static APR_INLINE const char *
+path_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
+ return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
+ apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK,
+ (char *)NULL),
+ NULL);
+ else
+ return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK,
+ pool);
+}
+
+static const char *
+path_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
+{
+ const char *txn_id = svn_fs_fs__id_txn_id(id);
+ const char *node_id = svn_fs_fs__id_node_id(id);
+ const char *copy_id = svn_fs_fs__id_copy_id(id);
+ const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s",
+ node_id, copy_id);
+
+ return svn_dirent_join(path_txn_dir(fs, txn_id, pool), name, pool);
+}
+
+static APR_INLINE const char *
+path_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
+{
+ return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS,
+ (char *)NULL);
+}
+
+static APR_INLINE const char *
+path_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
+{
+ return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool),
+ PATH_EXT_CHILDREN, (char *)NULL);
+}
+
+static APR_INLINE const char *
+path_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool)
+{
+ size_t len = strlen(node_id);
+ const char *node_id_minus_last_char =
+ (len == 1) ? "0" : apr_pstrmemdup(pool, node_id, len - 1);
+ return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR,
+ node_id_minus_last_char, NULL);
+}
+
+static APR_INLINE const char *
+path_and_offset_of(apr_file_t *file, apr_pool_t *pool)
+{
+ const char *path;
+ apr_off_t offset = 0;
+
+ if (apr_file_name_get(&path, file) != APR_SUCCESS)
+ path = "(unknown)";
+
+ if (apr_file_seek(file, APR_CUR, &offset) != APR_SUCCESS)
+ offset = -1;
+
+ return apr_psprintf(pool, "%s:%" APR_OFF_T_FMT, path, offset);
+}
+
+
+
+/* Functions for working with shared transaction data. */
+
+/* Return the transaction object for transaction TXN_ID from the
+ transaction list of filesystem FS (which must already be locked via the
+ txn_list_lock mutex). If the transaction does not exist in the list,
+ then create a new transaction object and return it (if CREATE_NEW is
+ true) or return NULL (otherwise). */
+static fs_fs_shared_txn_data_t *
+get_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ fs_fs_shared_data_t *ffsd = ffd->shared;
+ fs_fs_shared_txn_data_t *txn;
+
+ for (txn = ffsd->txns; txn; txn = txn->next)
+ if (strcmp(txn->txn_id, txn_id) == 0)
+ break;
+
+ if (txn || !create_new)
+ return txn;
+
+ /* Use the transaction object from the (single-object) freelist,
+ if one is available, or otherwise create a new object. */
+ if (ffsd->free_txn)
+ {
+ txn = ffsd->free_txn;
+ ffsd->free_txn = NULL;
+ }
+ else
+ {
+ apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
+ txn = apr_palloc(subpool, sizeof(*txn));
+ txn->pool = subpool;
+ }
+
+ assert(strlen(txn_id) < sizeof(txn->txn_id));
+ apr_cpystrn(txn->txn_id, txn_id, sizeof(txn->txn_id));
+ txn->being_written = FALSE;
+
+ /* Link this transaction into the head of the list. We will typically
+ be dealing with only one active transaction at a time, so it makes
+ sense for searches through the transaction list to look at the
+ newest transactions first. */
+ txn->next = ffsd->txns;
+ ffsd->txns = txn;
+
+ return txn;
+}
+
+/* Free the transaction object for transaction TXN_ID, and remove it
+ from the transaction list of filesystem FS (which must already be
+ locked via the txn_list_lock mutex). Do nothing if the transaction
+ does not exist. */
+static void
+free_shared_txn(svn_fs_t *fs, const char *txn_id)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ fs_fs_shared_data_t *ffsd = ffd->shared;
+ fs_fs_shared_txn_data_t *txn, *prev = NULL;
+
+ for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
+ if (strcmp(txn->txn_id, txn_id) == 0)
+ break;
+
+ if (!txn)
+ return;
+
+ if (prev)
+ prev->next = txn->next;
+ else
+ ffsd->txns = txn->next;
+
+ /* As we typically will be dealing with one transaction after another,
+ we will maintain a single-object free list so that we can hopefully
+ keep reusing the same transaction object. */
+ if (!ffsd->free_txn)
+ ffsd->free_txn = txn;
+ else
+ svn_pool_destroy(txn->pool);
+}
+
+
+/* Obtain a lock on the transaction list of filesystem FS, call BODY
+ with FS, BATON, and POOL, and then unlock the transaction list.
+ Return what BODY returned. */
+static svn_error_t *
+with_txnlist_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(svn_fs_t *fs,
+ const void *baton,
+ apr_pool_t *pool),
+ const void *baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ fs_fs_shared_data_t *ffsd = ffd->shared;
+
+ SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
+ body(fs, baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
+static svn_error_t *
+get_lock_on_filesystem(const char *lock_filename,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool);
+
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* No lock file? No big deal; these are just empty files
+ anyway. Create it and try again. */
+ svn_error_clear(err);
+ err = NULL;
+
+ SVN_ERR(svn_io_file_create(lock_filename, "", pool));
+ SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool));
+ }
+
+ return svn_error_trace(err);
+}
+
+/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
+ When registered with the pool holding the lock on the lock file,
+ this makes sure the flag gets reset just before we release the lock. */
+static apr_status_t
+reset_lock_flag(void *baton_void)
+{
+ fs_fs_data_t *ffd = baton_void;
+ ffd->has_write_lock = FALSE;
+ return APR_SUCCESS;
+}
+
+/* Obtain a write lock on the file LOCK_FILENAME (protecting with
+ LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
+ BATON and that subpool, destroy the subpool (releasing the write
+ lock) and return what BODY returned. If IS_GLOBAL_LOCK is set,
+ set the HAS_WRITE_LOCK flag while we keep the write lock. */
+static svn_error_t *
+with_some_lock_file(svn_fs_t *fs,
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool),
+ void *baton,
+ const char *lock_filename,
+ svn_boolean_t is_global_lock,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_error_t *err = get_lock_on_filesystem(lock_filename, subpool);
+
+ if (!err)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (is_global_lock)
+ {
+ /* set the "got the lock" flag and register reset function */
+ apr_pool_cleanup_register(subpool,
+ ffd,
+ reset_lock_flag,
+ apr_pool_cleanup_null);
+ ffd->has_write_lock = TRUE;
+ }
+
+ /* nobody else will modify the repo state
+ => read HEAD & pack info once */
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ SVN_ERR(update_min_unpacked_rev(fs, pool));
+ SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path,
+ pool));
+ err = body(baton, subpool);
+ }
+
+ svn_pool_destroy(subpool);
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_fs_fs__with_write_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ fs_fs_shared_data_t *ffsd = ffd->shared;
+
+ SVN_MUTEX__WITH_LOCK(ffsd->fs_write_lock,
+ with_some_lock_file(fs, body, baton,
+ path_lock(fs, pool),
+ TRUE,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Run BODY (with BATON and POOL) while the txn-current file
+ of FS is locked. */
+static svn_error_t *
+with_txn_current_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ fs_fs_shared_data_t *ffsd = ffd->shared;
+
+ SVN_MUTEX__WITH_LOCK(ffsd->txn_current_lock,
+ with_some_lock_file(fs, body, baton,
+ path_txn_current_lock(fs, pool),
+ FALSE,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
+ which see. */
+struct unlock_proto_rev_baton
+{
+ const char *txn_id;
+ void *lockcookie;
+};
+
+/* Callback used in the implementation of unlock_proto_rev(). */
+static svn_error_t *
+unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
+{
+ const struct unlock_proto_rev_baton *b = baton;
+ const char *txn_id = b->txn_id;
+ apr_file_t *lockfile = b->lockcookie;
+ fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE);
+ apr_status_t apr_err;
+
+ if (!txn)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Can't unlock unknown transaction '%s'"),
+ txn_id);
+ if (!txn->being_written)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Can't unlock nonlocked transaction '%s'"),
+ txn_id);
+
+ apr_err = apr_file_unlock(lockfile);
+ if (apr_err)
+ return svn_error_wrap_apr
+ (apr_err,
+ _("Can't unlock prototype revision lockfile for transaction '%s'"),
+ txn_id);
+ apr_err = apr_file_close(lockfile);
+ if (apr_err)
+ return svn_error_wrap_apr
+ (apr_err,
+ _("Can't close prototype revision lockfile for transaction '%s'"),
+ txn_id);
+
+ txn->being_written = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Unlock the prototype revision file for transaction TXN_ID in filesystem
+ FS using cookie LOCKCOOKIE. The original prototype revision file must
+ have been closed _before_ calling this function.
+
+ Perform temporary allocations in POOL. */
+static svn_error_t *
+unlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie,
+ apr_pool_t *pool)
+{
+ struct unlock_proto_rev_baton b;
+
+ b.txn_id = txn_id;
+ b.lockcookie = lockcookie;
+ return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
+}
+
+/* Same as unlock_proto_rev(), but requires that the transaction list
+ lock is already held. */
+static svn_error_t *
+unlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id,
+ void *lockcookie,
+ apr_pool_t *pool)
+{
+ struct unlock_proto_rev_baton b;
+
+ b.txn_id = txn_id;
+ b.lockcookie = lockcookie;
+ return unlock_proto_rev_body(fs, &b, pool);
+}
+
+/* A structure used by get_writable_proto_rev() and
+ get_writable_proto_rev_body(), which see. */
+struct get_writable_proto_rev_baton
+{
+ apr_file_t **file;
+ void **lockcookie;
+ const char *txn_id;
+};
+
+/* Callback used in the implementation of get_writable_proto_rev(). */
+static svn_error_t *
+get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
+{
+ const struct get_writable_proto_rev_baton *b = baton;
+ apr_file_t **file = b->file;
+ void **lockcookie = b->lockcookie;
+ const char *txn_id = b->txn_id;
+ svn_error_t *err;
+ fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE);
+
+ /* First, ensure that no thread in this process (including this one)
+ is currently writing to this transaction's proto-rev file. */
+ if (txn->being_written)
+ return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
+ _("Cannot write to the prototype revision file "
+ "of transaction '%s' because a previous "
+ "representation is currently being written by "
+ "this process"),
+ txn_id);
+
+
+ /* We know that no thread in this process is writing to the proto-rev
+ file, and by extension, that no thread in this process is holding a
+ lock on the prototype revision lock file. It is therefore safe
+ for us to attempt to lock this file, to see if any other process
+ is holding a lock. */
+
+ {
+ apr_file_t *lockfile;
+ apr_status_t apr_err;
+ const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool);
+
+ /* Open the proto-rev lockfile, creating it if necessary, as it may
+ not exist if the transaction dates from before the lockfiles were
+ introduced.
+
+ ### We'd also like to use something like svn_io_file_lock2(), but
+ that forces us to create a subpool just to be able to unlock
+ the file, which seems a waste. */
+ SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
+ APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
+
+ apr_err = apr_file_lock(lockfile,
+ APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
+ if (apr_err)
+ {
+ svn_error_clear(svn_io_file_close(lockfile, pool));
+
+ if (APR_STATUS_IS_EAGAIN(apr_err))
+ return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
+ _("Cannot write to the prototype revision "
+ "file of transaction '%s' because a "
+ "previous representation is currently "
+ "being written by another process"),
+ txn_id);
+
+ return svn_error_wrap_apr(apr_err,
+ _("Can't get exclusive lock on file '%s'"),
+ svn_dirent_local_style(lockfile_path, pool));
+ }
+
+ *lockcookie = lockfile;
+ }
+
+ /* We've successfully locked the transaction; mark it as such. */
+ txn->being_written = TRUE;
+
+
+ /* Now open the prototype revision file and seek to the end. */
+ err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool),
+ APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
+
+ /* You might expect that we could dispense with the following seek
+ and achieve the same thing by opening the file using APR_APPEND.
+ Unfortunately, APR's buffered file implementation unconditionally
+ places its initial file pointer at the start of the file (even for
+ files opened with APR_APPEND), so we need this seek to reconcile
+ the APR file pointer to the OS file pointer (since we need to be
+ able to read the current file position later). */
+ if (!err)
+ {
+ apr_off_t offset = 0;
+ err = svn_io_file_seek(*file, APR_END, &offset, pool);
+ }
+
+ if (err)
+ {
+ err = svn_error_compose_create(
+ err,
+ unlock_proto_rev_list_locked(fs, txn_id, *lockcookie, pool));
+
+ *lockcookie = NULL;
+ }
+
+ return svn_error_trace(err);
+}
+
+/* Get a handle to the prototype revision file for transaction TXN_ID in
+ filesystem FS, and lock it for writing. Return FILE, a file handle
+ positioned at the end of the file, and LOCKCOOKIE, a cookie that
+ should be passed to unlock_proto_rev() to unlock the file once FILE
+ has been closed.
+
+ If the prototype revision file is already locked, return error
+ SVN_ERR_FS_REP_BEING_WRITTEN.
+
+ Perform all allocations in POOL. */
+static svn_error_t *
+get_writable_proto_rev(apr_file_t **file,
+ void **lockcookie,
+ svn_fs_t *fs, const char *txn_id,
+ apr_pool_t *pool)
+{
+ struct get_writable_proto_rev_baton b;
+
+ b.file = file;
+ b.lockcookie = lockcookie;
+ b.txn_id = txn_id;
+
+ return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool);
+}
+
+/* Callback used in the implementation of purge_shared_txn(). */
+static svn_error_t *
+purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
+{
+ const char *txn_id = baton;
+
+ free_shared_txn(fs, txn_id);
+ svn_fs_fs__reset_txn_caches(fs);
+
+ return SVN_NO_ERROR;
+}
+
+/* Purge the shared data for transaction TXN_ID in filesystem FS.
+ Perform all allocations in POOL. */
+static svn_error_t *
+purge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
+{
+ return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
+}
+
+
+
+/* Fetch the current offset of FILE into *OFFSET_P. */
+static svn_error_t *
+get_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool)
+{
+ apr_off_t offset;
+
+ /* Note that, for buffered files, one (possibly surprising) side-effect
+ of this call is to flush any unwritten data to disk. */
+ offset = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
+ *offset_p = offset;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Check that BUF, a nul-terminated buffer of text from file PATH,
+ contains only digits at OFFSET and beyond, raising an error if not.
+ TITLE contains a user-visible description of the file, usually the
+ short file name.
+
+ Uses POOL for temporary allocation. */
+static svn_error_t *
+check_file_buffer_numeric(const char *buf, apr_off_t offset,
+ const char *path, const char *title,
+ apr_pool_t *pool)
+{
+ const char *p;
+
+ for (p = buf + offset; *p; p++)
+ if (!svn_ctype_isdigit(*p))
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
+ title, svn_dirent_local_style(path, pool), *p, buf);
+
+ return SVN_NO_ERROR;
+}
+
+/* Check that BUF, a nul-terminated buffer of text from format file PATH,
+ contains only digits at OFFSET and beyond, raising an error if not.
+
+ Uses POOL for temporary allocation. */
+static svn_error_t *
+check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
+ const char *path, apr_pool_t *pool)
+{
+ return check_file_buffer_numeric(buf, offset, path, "Format", pool);
+}
+
+/* Read the format number and maximum number of files per directory
+ from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
+ respectively.
+
+ *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
+ will be set to zero if a linear scheme should be used.
+
+ Use POOL for temporary allocation. */
+static svn_error_t *
+read_format(int *pformat, int *max_files_per_dir,
+ const char *path, apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_stream_t *stream;
+ svn_stringbuf_t *content;
+ svn_stringbuf_t *buf;
+ svn_boolean_t eos = FALSE;
+
+ err = svn_stringbuf_from_file2(&content, path, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* Treat an absent format file as format 1. Do not try to
+ create the format file on the fly, because the repository
+ might be read-only for us, or this might be a read-only
+ operation, and the spirit of FSFS is to make no changes
+ whatseover in read-only operations. See thread starting at
+ http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
+ for more. */
+ svn_error_clear(err);
+ *pformat = 1;
+ *max_files_per_dir = 0;
+
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ stream = svn_stream_from_stringbuf(content, pool);
+ SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
+ if (buf->len == 0 && eos)
+ {
+ /* Return a more useful error message. */
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("Can't read first line of format file '%s'"),
+ svn_dirent_local_style(path, pool));
+ }
+
+ /* Check that the first line contains only digits. */
+ SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool));
+ SVN_ERR(svn_cstring_atoi(pformat, buf->data));
+
+ /* Set the default values for anything that can be set via an option. */
+ *max_files_per_dir = 0;
+
+ /* Read any options. */
+ while (!eos)
+ {
+ SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
+ if (buf->len == 0)
+ break;
+
+ if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT &&
+ strncmp(buf->data, "layout ", 7) == 0)
+ {
+ if (strcmp(buf->data + 7, "linear") == 0)
+ {
+ *max_files_per_dir = 0;
+ continue;
+ }
+
+ if (strncmp(buf->data + 7, "sharded ", 8) == 0)
+ {
+ /* Check that the argument is numeric. */
+ SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path, pool));
+ SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15));
+ continue;
+ }
+ }
+
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("'%s' contains invalid filesystem format option '%s'"),
+ svn_dirent_local_style(path, pool), buf->data);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Write the format number and maximum number of files per directory
+ to a new format file in PATH, possibly expecting to overwrite a
+ previously existing file.
+
+ Use POOL for temporary allocation. */
+static svn_error_t *
+write_format(const char *path, int format, int max_files_per_dir,
+ svn_boolean_t overwrite, apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb;
+
+ SVN_ERR_ASSERT(1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER);
+
+ sb = svn_stringbuf_createf(pool, "%d\n", format);
+
+ if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
+ {
+ if (max_files_per_dir)
+ svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n",
+ max_files_per_dir));
+ else
+ svn_stringbuf_appendcstr(sb, "layout linear\n");
+ }
+
+ /* svn_io_write_version_file() does a load of magic to allow it to
+ replace version files that already exist. We only need to do
+ that when we're allowed to overwrite an existing file. */
+ if (! overwrite)
+ {
+ /* Create the file */
+ SVN_ERR(svn_io_file_create(path, sb->data, pool));
+ }
+ else
+ {
+ const char *path_tmp;
+
+ SVN_ERR(svn_io_write_unique(&path_tmp,
+ svn_dirent_dirname(path, pool),
+ sb->data, sb->len,
+ svn_io_file_del_none, pool));
+
+ /* rename the temp file as the real destination */
+ SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
+ }
+
+ /* And set the perms to make it read only */
+ return svn_io_set_file_read_only(path, FALSE, pool);
+}
+
+/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
+ number is not the same as a format number supported by this
+ Subversion. */
+static svn_error_t *
+check_format(int format)
+{
+ /* Blacklist. These formats may be either younger or older than
+ SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */
+ if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT)
+ return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
+ _("Found format '%d', only created by "
+ "unreleased dev builds; see "
+ "http://subversion.apache.org"
+ "/docs/release-notes/1.7#revprop-packing"),
+ format);
+
+ /* We support all formats from 1-current simultaneously */
+ if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
+ return SVN_NO_ERROR;
+
+ return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
+ _("Expected FS format between '1' and '%d'; found format '%d'"),
+ SVN_FS_FS__FORMAT_NUMBER, format);
+}
+
+svn_boolean_t
+svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
+}
+
+/* Read the configuration information of the file system at FS_PATH
+ * and set the respective values in FFD. Use POOL for allocations.
+ */
+static svn_error_t *
+read_config(fs_fs_data_t *ffd,
+ const char *fs_path,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_config_read3(&ffd->config,
+ svn_dirent_join(fs_path, PATH_CONFIG, pool),
+ FALSE, FALSE, FALSE, pool));
+
+ /* Initialize ffd->rep_sharing_allowed. */
+ if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
+ SVN_ERR(svn_config_get_bool(ffd->config, &ffd->rep_sharing_allowed,
+ CONFIG_SECTION_REP_SHARING,
+ CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
+ else
+ ffd->rep_sharing_allowed = FALSE;
+
+ /* Initialize deltification settings in ffd. */
+ if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
+ {
+ SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_directories,
+ CONFIG_SECTION_DELTIFICATION,
+ CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
+ FALSE));
+ SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_properties,
+ CONFIG_SECTION_DELTIFICATION,
+ CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION,
+ FALSE));
+ SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_deltification_walk,
+ CONFIG_SECTION_DELTIFICATION,
+ CONFIG_OPTION_MAX_DELTIFICATION_WALK,
+ SVN_FS_FS_MAX_DELTIFICATION_WALK));
+ SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_linear_deltification,
+ CONFIG_SECTION_DELTIFICATION,
+ CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
+ SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
+ }
+ else
+ {
+ ffd->deltify_directories = FALSE;
+ ffd->deltify_properties = FALSE;
+ ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
+ ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
+ }
+
+ /* Initialize revprop packing settings in ffd. */
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
+ {
+ SVN_ERR(svn_config_get_bool(ffd->config, &ffd->compress_packed_revprops,
+ CONFIG_SECTION_PACKED_REVPROPS,
+ CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
+ FALSE));
+ SVN_ERR(svn_config_get_int64(ffd->config, &ffd->revprop_pack_size,
+ CONFIG_SECTION_PACKED_REVPROPS,
+ CONFIG_OPTION_REVPROP_PACK_SIZE,
+ ffd->compress_packed_revprops
+ ? 0x100
+ : 0x40));
+
+ ffd->revprop_pack_size *= 1024;
+ }
+ else
+ {
+ ffd->revprop_pack_size = 0x10000;
+ ffd->compress_packed_revprops = FALSE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_config(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+#define NL APR_EOL_STR
+ static const char * const fsfs_conf_contents =
+"### This file controls the configuration of the FSFS filesystem." NL
+"" NL
+"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]" NL
+"### These options name memcached servers used to cache internal FSFS" NL
+"### data. See http://www.danga.com/memcached/ for more information on" NL
+"### memcached. To use memcached with FSFS, run one or more memcached" NL
+"### servers, and specify each of them as an option like so:" NL
+"# first-server = 127.0.0.1:11211" NL
+"# remote-memcached = mymemcached.corp.example.com:11212" NL
+"### The option name is ignored; the value is of the form HOST:PORT." NL
+"### memcached servers can be shared between multiple repositories;" NL
+"### however, if you do this, you *must* ensure that repositories have" NL
+"### distinct UUIDs and paths, or else cached data from one repository" NL
+"### might be used by another accidentally. Note also that memcached has" NL
+"### no authentication for reads or writes, so you must ensure that your" NL
+"### memcached servers are only accessible by trusted users." NL
+"" NL
+"[" CONFIG_SECTION_CACHES "]" NL
+"### When a cache-related error occurs, normally Subversion ignores it" NL
+"### and continues, logging an error if the server is appropriately" NL
+"### configured (and ignoring it with file:// access). To make" NL
+"### Subversion never ignore cache errors, uncomment this line." NL
+"# " CONFIG_OPTION_FAIL_STOP " = true" NL
+"" NL
+"[" CONFIG_SECTION_REP_SHARING "]" NL
+"### To conserve space, the filesystem can optionally avoid storing" NL
+"### duplicate representations. This comes at a slight cost in" NL
+"### performance, as maintaining a database of shared representations can" NL
+"### increase commit times. The space savings are dependent upon the size" NL
+"### of the repository, the number of objects it contains and the amount of" NL
+"### duplication between them, usually a function of the branching and" NL
+"### merging process." NL
+"###" NL
+"### The following parameter enables rep-sharing in the repository. It can" NL
+"### be switched on and off at will, but for best space-saving results" NL
+"### should be enabled consistently over the life of the repository." NL
+"### 'svnadmin verify' will check the rep-cache regardless of this setting." NL
+"### rep-sharing is enabled by default." NL
+"# " CONFIG_OPTION_ENABLE_REP_SHARING " = true" NL
+"" NL
+"[" CONFIG_SECTION_DELTIFICATION "]" NL
+"### To conserve space, the filesystem stores data as differences against" NL
+"### existing representations. This comes at a slight cost in performance," NL
+"### as calculating differences can increase commit times. Reading data" NL
+"### will also create higher CPU load and the data will be fragmented." NL
+"### Since deltification tends to save significant amounts of disk space," NL
+"### the overall I/O load can actually be lower." NL
+"###" NL
+"### The options in this section allow for tuning the deltification" NL
+"### strategy. Their effects on data size and server performance may vary" NL
+"### from one repository to another. Versions prior to 1.8 will ignore" NL
+"### this section." NL
+"###" NL
+"### The following parameter enables deltification for directories. It can" NL
+"### be switched on and off at will, but for best space-saving results" NL
+"### should be enabled consistently over the life of the repository." NL
+"### Repositories containing large directories will benefit greatly." NL
+"### In rarely read repositories, the I/O overhead may be significant as" NL
+"### cache hit rates will most likely be low" NL
+"### directory deltification is disabled by default." NL
+"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = false" NL
+"###" NL
+"### The following parameter enables deltification for properties on files" NL
+"### and directories. Overall, this is a minor tuning option but can save" NL
+"### some disk space if you merge frequently or frequently change node" NL
+"### properties. You should not activate this if rep-sharing has been" NL
+"### disabled because this may result in a net increase in repository size." NL
+"### property deltification is disabled by default." NL
+"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = false" NL
+"###" NL
+"### During commit, the server may need to walk the whole change history of" NL
+"### of a given node to find a suitable deltification base. This linear" NL
+"### process can impact commit times, svnadmin load and similar operations." NL
+"### This setting limits the depth of the deltification history. If the" NL
+"### threshold has been reached, the node will be stored as fulltext and a" NL
+"### new deltification history begins." NL
+"### Note, this is unrelated to svn log." NL
+"### Very large values rarely provide significant additional savings but" NL
+"### can impact performance greatly - in particular if directory" NL
+"### deltification has been activated. Very small values may be useful in" NL
+"### repositories that are dominated by large, changing binaries." NL
+"### Should be a power of two minus 1. A value of 0 will effectively" NL
+"### disable deltification." NL
+"### For 1.8, the default value is 1023; earlier versions have no limit." NL
+"# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023" NL
+"###" NL
+"### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL
+"### delta information where a simple delta against the latest version is" NL
+"### often smaller. By default, 1.8+ will therefore use skip deltas only" NL
+"### after the linear chain of deltas has grown beyond the threshold" NL
+"### specified by this setting." NL
+"### Values up to 64 can result in some reduction in repository size for" NL
+"### the cost of quickly increasing I/O and CPU costs. Similarly, smaller" NL
+"### numbers can reduce those costs at the cost of more disk space. For" NL
+"### rarely read repositories or those containing larger binaries, this may" NL
+"### present a better trade-off." NL
+"### Should be a power of two. A value of 1 or smaller will cause the" NL
+"### exclusive use of skip-deltas (as in pre-1.8)." NL
+"### For 1.8, the default value is 16; earlier versions use 1." NL
+"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16" NL
+"" NL
+"[" CONFIG_SECTION_PACKED_REVPROPS "]" NL
+"### This parameter controls the size (in kBytes) of packed revprop files." NL
+"### Revprops of consecutive revisions will be concatenated into a single" NL
+"### file up to but not exceeding the threshold given here. However, each" NL
+"### pack file may be much smaller and revprops of a single revision may be" NL
+"### much larger than the limit set here. The threshold will be applied" NL
+"### before optional compression takes place." NL
+"### Large values will reduce disk space usage at the expense of increased" NL
+"### latency and CPU usage reading and changing individual revprops. They" NL
+"### become an advantage when revprop caching has been enabled because a" NL
+"### lot of data can be read in one go. Values smaller than 4 kByte will" NL
+"### not improve latency any further and quickly render revprop packing" NL
+"### ineffective." NL
+"### revprop-pack-size is 64 kBytes by default for non-compressed revprop" NL
+"### pack files and 256 kBytes when compression has been enabled." NL
+"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 64" NL
+"###" NL
+"### To save disk space, packed revprop files may be compressed. Standard" NL
+"### revprops tend to allow for very effective compression. Reading and" NL
+"### even more so writing, become significantly more CPU intensive. With" NL
+"### revprop caching enabled, the overhead can be offset by reduced I/O" NL
+"### unless you often modify revprops after packing." NL
+"### Compressing packed revprops is disabled by default." NL
+"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false" NL
+;
+#undef NL
+ return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
+ fsfs_conf_contents, pool);
+}
+
+static svn_error_t *
+read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
+ const char *path,
+ apr_pool_t *pool)
+{
+ char buf[80];
+ apr_file_t *file;
+ apr_size_t len;
+
+ SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, pool));
+ len = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ *min_unpacked_rev = SVN_STR_TO_REV(buf);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT);
+
+ return read_min_unpacked_rev(&ffd->min_unpacked_rev,
+ path_min_unpacked_rev(fs, pool),
+ pool);
+}
+
+svn_error_t *
+svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_file_t *uuid_file;
+ int format, max_files_per_dir;
+ char buf[APR_UUID_FORMATTED_LENGTH + 2];
+ apr_size_t limit;
+
+ fs->path = apr_pstrdup(fs->pool, path);
+
+ /* Read the FS format number. */
+ SVN_ERR(read_format(&format, &max_files_per_dir,
+ path_format(fs, pool), pool));
+ SVN_ERR(check_format(format));
+
+ /* Now we've got a format number no matter what. */
+ ffd->format = format;
+ ffd->max_files_per_dir = max_files_per_dir;
+
+ /* Read in and cache the repository uuid. */
+ SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+
+ limit = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, pool));
+ fs->uuid = apr_pstrdup(fs->pool, buf);
+
+ SVN_ERR(svn_io_file_close(uuid_file, pool));
+
+ /* Read the min unpacked revision. */
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ SVN_ERR(update_min_unpacked_rev(fs, pool));
+
+ /* Read the configuration file. */
+ SVN_ERR(read_config(ffd, fs->path, pool));
+
+ return get_youngest(&(ffd->youngest_rev_cache), path, pool);
+}
+
+/* Wrapper around svn_io_file_create which ignores EEXIST. */
+static svn_error_t *
+create_file_ignore_eexist(const char *file,
+ const char *contents,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_io_file_create(file, contents, pool);
+ if (err && APR_STATUS_IS_EEXIST(err->apr_err))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+}
+
+/* forward declarations */
+
+static svn_error_t *
+pack_revprops_shard(const char *pack_file_dir,
+ const char *shard_path,
+ apr_int64_t shard,
+ int max_files_per_dir,
+ apr_off_t max_pack_size,
+ int compression_level,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+delete_revprops_shard(const char *shard_path,
+ apr_int64_t shard,
+ int max_files_per_dir,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* In the filesystem FS, pack all revprop shards up to min_unpacked_rev.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+upgrade_pack_revprops(svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ const char *revprops_shard_path;
+ const char *revprops_pack_file_dir;
+ apr_int64_t shard;
+ apr_int64_t first_unpacked_shard
+ = ffd->min_unpacked_rev / ffd->max_files_per_dir;
+
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
+ scratch_pool);
+ int compression_level = ffd->compress_packed_revprops
+ ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
+ : SVN_DELTA_COMPRESSION_LEVEL_NONE;
+
+ /* first, pack all revprops shards to match the packed revision shards */
+ for (shard = 0; shard < first_unpacked_shard; ++shard)
+ {
+ revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
+ apr_psprintf(iterpool,
+ "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
+ shard),
+ iterpool);
+ revprops_shard_path = svn_dirent_join(revsprops_dir,
+ apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
+ iterpool);
+
+ SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
+ shard, ffd->max_files_per_dir,
+ (int)(0.9 * ffd->revprop_pack_size),
+ compression_level,
+ NULL, NULL, iterpool));
+ svn_pool_clear(iterpool);
+ }
+
+ /* delete the non-packed revprops shards afterwards */
+ for (shard = 0; shard < first_unpacked_shard; ++shard)
+ {
+ revprops_shard_path = svn_dirent_join(revsprops_dir,
+ apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
+ iterpool);
+ SVN_ERR(delete_revprops_shard(revprops_shard_path,
+ shard, ffd->max_files_per_dir,
+ NULL, NULL, iterpool));
+ svn_pool_clear(iterpool);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+upgrade_body(void *baton, apr_pool_t *pool)
+{
+ svn_fs_t *fs = baton;
+ int format, max_files_per_dir;
+ const char *format_path = path_format(fs, pool);
+ svn_node_kind_t kind;
+
+ /* Read the FS format number and max-files-per-dir setting. */
+ SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool));
+ SVN_ERR(check_format(format));
+
+ /* If the config file does not exist, create one. */
+ SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
+ &kind, pool));
+ switch (kind)
+ {
+ case svn_node_none:
+ SVN_ERR(write_config(fs, pool));
+ break;
+ case svn_node_file:
+ break;
+ default:
+ return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
+ _("'%s' is not a regular file."
+ " Please move it out of "
+ "the way and try again"),
+ svn_dirent_join(fs->path, PATH_CONFIG, pool));
+ }
+
+ /* If we're already up-to-date, there's nothing else to be done here. */
+ if (format == SVN_FS_FS__FORMAT_NUMBER)
+ return SVN_NO_ERROR;
+
+ /* If our filesystem predates the existance of the 'txn-current
+ file', make that file and its corresponding lock file. */
+ if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+ {
+ SVN_ERR(create_file_ignore_eexist(path_txn_current(fs, pool), "0\n",
+ pool));
+ SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs, pool), "",
+ pool));
+ }
+
+ /* If our filesystem predates the existance of the 'txn-protorevs'
+ dir, make that directory. */
+ if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
+ {
+ /* We don't use path_txn_proto_rev() here because it expects
+ we've already bumped our format. */
+ SVN_ERR(svn_io_make_dir_recursively(
+ svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, pool), pool));
+ }
+
+ /* If our filesystem is new enough, write the min unpacked rev file. */
+ if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
+ SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
+
+ /* If the file system supports revision packing but not revprop packing,
+ pack the revprops up to the point that revision data has been packed. */
+ if ( format >= SVN_FS_FS__MIN_PACKED_FORMAT
+ && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
+ SVN_ERR(upgrade_pack_revprops(fs, pool));
+
+ /* Bump the format file. */
+ return write_format(format_path, SVN_FS_FS__FORMAT_NUMBER, max_files_per_dir,
+ TRUE, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool)
+{
+ return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool);
+}
+
+
+/* Functions for dealing with recoverable errors on mutable files
+ *
+ * Revprops, current, and txn-current files are mutable; that is, they
+ * change as part of normal fsfs operation, in constrat to revs files, or
+ * the format file, which are written once at create (or upgrade) time.
+ * When more than one host writes to the same repository, we will
+ * sometimes see these recoverable errors when accesssing these files.
+ *
+ * These errors all relate to NFS, and thus we only use this retry code if
+ * ESTALE is defined.
+ *
+ ** ESTALE
+ *
+ * In NFS v3 and under, the server doesn't track opened files. If you
+ * unlink(2) or rename(2) a file held open by another process *on the
+ * same host*, that host's kernel typically renames the file to
+ * .nfsXXXX and automatically deletes that when it's no longer open,
+ * but this behavior is not required.
+ *
+ * For obvious reasons, this does not work *across hosts*. No one
+ * knows about the opened file; not the server, and not the deleting
+ * client. So the file vanishes, and the reader gets stale NFS file
+ * handle.
+ *
+ ** EIO, ENOENT
+ *
+ * Some client implementations (at least the 2.6.18.5 kernel that ships
+ * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or
+ * even EIO errors when trying to read these files that have been renamed
+ * over on some other host.
+ *
+ ** Solution
+ *
+ * Try open and read of such files in try_stringbuf_from_file(). Call
+ * this function within a loop of RECOVERABLE_RETRY_COUNT iterations
+ * (though, realistically, the second try will succeed).
+ */
+
+#define RECOVERABLE_RETRY_COUNT 10
+
+/* Read the file at PATH and return its content in *CONTENT. *CONTENT will
+ * not be modified unless the whole file was read successfully.
+ *
+ * ESTALE, EIO and ENOENT will not cause this function to return an error
+ * unless LAST_ATTEMPT has been set. If MISSING is not NULL, indicate
+ * missing files (ENOENT) there.
+ *
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+try_stringbuf_from_file(svn_stringbuf_t **content,
+ svn_boolean_t *missing,
+ const char *path,
+ svn_boolean_t last_attempt,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_stringbuf_from_file2(content, path, pool);
+ if (missing)
+ *missing = FALSE;
+
+ if (err)
+ {
+ *content = NULL;
+
+ if (APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ if (!last_attempt)
+ {
+ svn_error_clear(err);
+ if (missing)
+ *missing = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+#ifdef ESTALE
+ else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
+ || APR_TO_OS_ERROR(err->apr_err) == EIO)
+ {
+ if (!last_attempt)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ }
+#endif
+ }
+
+ return svn_error_trace(err);
+}
+
+/* Read the 'current' file FNAME and store the contents in *BUF.
+ Allocations are performed in POOL. */
+static svn_error_t *
+read_content(svn_stringbuf_t **content, const char *fname, apr_pool_t *pool)
+{
+ int i;
+ *content = NULL;
+
+ for (i = 0; !*content && (i < RECOVERABLE_RETRY_COUNT); ++i)
+ SVN_ERR(try_stringbuf_from_file(content, NULL,
+ fname, i + 1 < RECOVERABLE_RETRY_COUNT,
+ pool));
+
+ if (!*content)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Can't read '%s'"),
+ svn_dirent_local_style(fname, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Find the youngest revision in a repository at path FS_PATH and
+ return it in *YOUNGEST_P. Perform temporary allocations in
+ POOL. */
+static svn_error_t *
+get_youngest(svn_revnum_t *youngest_p,
+ const char *fs_path,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *buf;
+ SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool),
+ pool));
+
+ *youngest_p = SVN_STR_TO_REV(buf->data);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ SVN_ERR(get_youngest(youngest_p, fs->path, pool));
+ ffd->youngest_rev_cache = *youngest_p;
+
+ return SVN_NO_ERROR;
+}
+
+/* Given a revision file FILE that has been pre-positioned at the
+ beginning of a Node-Rev header block, read in that header block and
+ store it in the apr_hash_t HEADERS. All allocations will be from
+ POOL. */
+static svn_error_t * read_header_block(apr_hash_t **headers,
+ svn_stream_t *stream,
+ apr_pool_t *pool)
+{
+ *headers = apr_hash_make(pool);
+
+ while (1)
+ {
+ svn_stringbuf_t *header_str;
+ const char *name, *value;
+ apr_size_t i = 0;
+ svn_boolean_t eof;
+
+ SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
+
+ if (eof || header_str->len == 0)
+ break; /* end of header block */
+
+ while (header_str->data[i] != ':')
+ {
+ if (header_str->data[i] == '\0')
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Found malformed header '%s' in "
+ "revision file"),
+ header_str->data);
+ i++;
+ }
+
+ /* Create a 'name' string and point to it. */
+ header_str->data[i] = '\0';
+ name = header_str->data;
+
+ /* Skip over the NULL byte and the space following it. */
+ i += 2;
+
+ if (i > header_str->len)
+ {
+ /* Restore the original line for the error. */
+ i -= 2;
+ header_str->data[i] = ':';
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Found malformed header '%s' in "
+ "revision file"),
+ header_str->data);
+ }
+
+ value = header_str->data + i;
+
+ /* header_str is safely in our pool, so we can use bits of it as
+ key and value. */
+ svn_hash_sets(*headers, name, value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer
+ than the current youngest revision or is simply not a valid
+ revision number, else return success.
+
+ FSFS is based around the concept that commits only take effect when
+ the number in "current" is bumped. Thus if there happens to be a rev
+ or revprops file installed for a revision higher than the one recorded
+ in "current" (because a commit failed between installing the rev file
+ and bumping "current", or because an administrator rolled back the
+ repository by resetting "current" without deleting rev files, etc), it
+ ought to be completely ignored. This function provides the check
+ by which callers can make that decision. */
+static svn_error_t *
+ensure_revision_exists(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (! SVN_IS_VALID_REVNUM(rev))
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("Invalid revision number '%ld'"), rev);
+
+
+ /* Did the revision exist the last time we checked the current
+ file? */
+ if (rev <= ffd->youngest_rev_cache)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool));
+
+ /* Check again. */
+ if (rev <= ffd->youngest_rev_cache)
+ return SVN_NO_ERROR;
+
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"), rev);
+}
+
+svn_error_t *
+svn_fs_fs__revision_exists(svn_revnum_t rev,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ /* Different order of parameters. */
+ SVN_ERR(ensure_revision_exists(fs, rev, pool));
+ return SVN_NO_ERROR;
+}
+
+/* Open the correct revision file for REV. If the filesystem FS has
+ been packed, *FILE will be set to the packed file; otherwise, set *FILE
+ to the revision file for REV. Return SVN_ERR_FS_NO_SUCH_REVISION if the
+ file doesn't exist.
+
+ TODO: Consider returning an indication of whether this is a packed rev
+ file, so the caller need not rely on is_packed_rev() which in turn
+ relies on the cached FFD->min_unpacked_rev value not having changed
+ since the rev file was opened.
+
+ Use POOL for allocations. */
+static svn_error_t *
+open_pack_or_rev_file(apr_file_t **file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_error_t *err;
+ const char *path;
+ svn_boolean_t retry = FALSE;
+
+ do
+ {
+ err = svn_fs_fs__path_rev_absolute(&path, fs, rev, pool);
+
+ /* open the revision file in buffered r/o mode */
+ if (! err)
+ err = svn_io_file_open(file, path,
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
+
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ {
+ /* Could not open the file. This may happen if the
+ * file once existed but got packed later. */
+ svn_error_clear(err);
+
+ /* if that was our 2nd attempt, leave it at that. */
+ if (retry)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"), rev);
+
+ /* We failed for the first time. Refresh cache & retry. */
+ SVN_ERR(update_min_unpacked_rev(fs, pool));
+
+ retry = TRUE;
+ }
+ else
+ {
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"), rev);
+ }
+ }
+ else
+ {
+ retry = FALSE;
+ }
+ }
+ while (retry);
+
+ return svn_error_trace(err);
+}
+
+/* Reads a line from STREAM and converts it to a 64 bit integer to be
+ * returned in *RESULT. If we encounter eof, set *HIT_EOF and leave
+ * *RESULT unchanged. If HIT_EOF is NULL, EOF causes an "corrupt FS"
+ * error return.
+ * SCRATCH_POOL is used for temporary allocations.
+ */
+static svn_error_t *
+read_number_from_stream(apr_int64_t *result,
+ svn_boolean_t *hit_eof,
+ svn_stream_t *stream,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *sb;
+ svn_boolean_t eof;
+ svn_error_t *err;
+
+ SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
+ if (hit_eof)
+ *hit_eof = eof;
+ else
+ if (eof)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
+
+ if (!eof)
+ {
+ err = svn_cstring_atoi64(result, sb->data);
+ if (err)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ _("Number '%s' invalid or too large"),
+ sb->data);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
+ Use POOL for temporary allocations. */
+static svn_error_t *
+get_packed_offset(apr_off_t *rev_offset,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_stream_t *manifest_stream;
+ svn_boolean_t is_cached;
+ svn_revnum_t shard;
+ apr_int64_t shard_pos;
+ apr_array_header_t *manifest;
+ apr_pool_t *iterpool;
+
+ shard = rev / ffd->max_files_per_dir;
+
+ /* position of the shard within the manifest */
+ shard_pos = rev % ffd->max_files_per_dir;
+
+ /* fetch exactly that element into *rev_offset, if the manifest is found
+ in the cache */
+ SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached,
+ ffd->packed_offset_cache, &shard,
+ svn_fs_fs__get_sharded_offset, &shard_pos,
+ pool));
+
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ /* Open the manifest file. */
+ SVN_ERR(svn_stream_open_readonly(&manifest_stream,
+ path_rev_packed(fs, rev, PATH_MANIFEST,
+ pool),
+ pool, pool));
+
+ /* While we're here, let's just read the entire manifest file into an array,
+ so we can cache the entire thing. */
+ iterpool = svn_pool_create(pool);
+ manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t));
+ while (1)
+ {
+ svn_boolean_t eof;
+ apr_int64_t val;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
+ if (eof)
+ break;
+
+ APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
+ }
+ svn_pool_destroy(iterpool);
+
+ *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
+ apr_off_t);
+
+ /* Close up shop and cache the array. */
+ SVN_ERR(svn_stream_close(manifest_stream));
+ return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool);
+}
+
+/* Open the revision file for revision REV in filesystem FS and store
+ the newly opened file in FILE. Seek to location OFFSET before
+ returning. Perform temporary allocations in POOL. */
+static svn_error_t *
+open_and_seek_revision(apr_file_t **file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_off_t offset,
+ apr_pool_t *pool)
+{
+ apr_file_t *rev_file;
+
+ SVN_ERR(ensure_revision_exists(fs, rev, pool));
+
+ SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, pool));
+
+ if (is_packed_rev(fs, rev))
+ {
+ apr_off_t rev_offset;
+
+ SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
+ offset += rev_offset;
+ }
+
+ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+
+ *file = rev_file;
+
+ return SVN_NO_ERROR;
+}
+
+/* Open the representation for a node-revision in transaction TXN_ID
+ in filesystem FS and store the newly opened file in FILE. Seek to
+ location OFFSET before returning. Perform temporary allocations in
+ POOL. Only appropriate for file contents, nor props or directory
+ contents. */
+static svn_error_t *
+open_and_seek_transaction(apr_file_t **file,
+ svn_fs_t *fs,
+ const char *txn_id,
+ representation_t *rep,
+ apr_pool_t *pool)
+{
+ apr_file_t *rev_file;
+ apr_off_t offset;
+
+ SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+
+ offset = rep->offset;
+ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+
+ *file = rev_file;
+
+ return SVN_NO_ERROR;
+}
+
+/* Given a node-id ID, and a representation REP in filesystem FS, open
+ the correct file and seek to the correction location. Store this
+ file in *FILE_P. Perform any allocations in POOL. */
+static svn_error_t *
+open_and_seek_representation(apr_file_t **file_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ apr_pool_t *pool)
+{
+ if (! rep->txn_id)
+ return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
+ pool);
+ else
+ return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
+}
+
+/* Parse the description of a representation from STRING and store it
+ into *REP_P. If the representation is mutable (the revision is
+ given as -1), then use TXN_ID for the representation's txn_id
+ field. If MUTABLE_REP_TRUNCATED is true, then this representation
+ is for property or directory contents, and no information will be
+ expected except the "-1" revision number for a mutable
+ representation. Allocate *REP_P in POOL. */
+static svn_error_t *
+read_rep_offsets_body(representation_t **rep_p,
+ char *string,
+ const char *txn_id,
+ svn_boolean_t mutable_rep_truncated,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+ char *str;
+ apr_int64_t val;
+
+ rep = apr_pcalloc(pool, sizeof(*rep));
+ *rep_p = rep;
+
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
+
+
+ rep->revision = SVN_STR_TO_REV(str);
+ if (rep->revision == SVN_INVALID_REVNUM)
+ {
+ rep->txn_id = txn_id;
+ if (mutable_rep_truncated)
+ return SVN_NO_ERROR;
+ }
+
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
+
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ rep->offset = (apr_off_t)val;
+
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
+
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ rep->size = (svn_filesize_t)val;
+
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
+
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ rep->expanded_size = (svn_filesize_t)val;
+
+ /* Read in the MD5 hash. */
+ str = svn_cstring_tokenize(" ", &string);
+ if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
+
+ SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str,
+ pool));
+
+ /* The remaining fields are only used for formats >= 4, so check that. */
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return SVN_NO_ERROR;
+
+ /* Read the SHA1 hash. */
+ if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
+
+ SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str,
+ pool));
+
+ /* Read the uniquifier. */
+ str = svn_cstring_tokenize(" ", &string);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
+
+ rep->uniquifier = apr_pstrdup(pool, str);
+
+ return SVN_NO_ERROR;
+}
+
+/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
+ and adding an error message. */
+static svn_error_t *
+read_rep_offsets(representation_t **rep_p,
+ char *string,
+ const svn_fs_id_t *noderev_id,
+ svn_boolean_t mutable_rep_truncated,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ const char *txn_id;
+
+ if (noderev_id)
+ txn_id = svn_fs_fs__id_txn_id(noderev_id);
+ else
+ txn_id = NULL;
+
+ err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated,
+ pool);
+ if (err)
+ {
+ const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(noderev_id, pool);
+ const char *where;
+ where = apr_psprintf(pool,
+ _("While reading representation offsets "
+ "for node-revision '%s':"),
+ noderev_id ? id_unparsed->data : "(null)");
+
+ return svn_error_quick_wrap(err, where);
+ }
+ else
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
+{
+ svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool);
+ return svn_error_createf
+ (SVN_ERR_FS_ID_NOT_FOUND, 0,
+ _("Reference to non-existent node '%s' in filesystem '%s'"),
+ id_str->data, fs->path);
+}
+
+/* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev
+ * caching has been enabled and the data can be found, IS_CACHED will
+ * be set to TRUE. The noderev will be allocated from POOL.
+ *
+ * Non-permanent ids (e.g. ids within a TXN) will not be cached.
+ */
+static svn_error_t *
+get_cached_node_revision_body(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ svn_boolean_t *is_cached,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id))
+ {
+ *is_cached = FALSE;
+ }
+ else
+ {
+ pair_cache_key_t key = { 0 };
+
+ key.revision = svn_fs_fs__id_rev(id);
+ key.second = svn_fs_fs__id_offset(id);
+ SVN_ERR(svn_cache__get((void **) noderev_p,
+ is_cached,
+ ffd->node_revision_cache,
+ &key,
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* If noderev caching has been enabled, store the NODEREV_P for the given ID
+ * in FS' node revsion cache. SCRATCH_POOL is used for temporary allcations.
+ *
+ * Non-permanent ids (e.g. ids within a TXN) will not be cached.
+ */
+static svn_error_t *
+set_cached_node_revision_body(node_revision_t *noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
+ {
+ pair_cache_key_t key = { 0 };
+
+ key.revision = svn_fs_fs__id_rev(id);
+ key.second = svn_fs_fs__id_offset(id);
+ return svn_cache__set(ffd->node_revision_cache,
+ &key,
+ noderev_p,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Get the node-revision for the node ID in FS.
+ Set *NODEREV_P to the new node-revision structure, allocated in POOL.
+ See svn_fs_fs__get_node_revision, which wraps this and adds another
+ error. */
+static svn_error_t *
+get_node_revision_body(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ apr_file_t *revision_file;
+ svn_error_t *err;
+ svn_boolean_t is_cached = FALSE;
+
+ /* First, try a cache lookup. If that succeeds, we are done here. */
+ SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ if (svn_fs_fs__id_txn_id(id))
+ {
+ /* This is a transaction node-rev. */
+ err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
+ }
+ else
+ {
+ /* This is a revision node-rev. */
+ err = open_and_seek_revision(&revision_file, fs,
+ svn_fs_fs__id_rev(id),
+ svn_fs_fs__id_offset(id),
+ pool);
+ }
+
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return svn_error_trace(err_dangling_id(fs, id));
+ }
+
+ return svn_error_trace(err);
+ }
+
+ SVN_ERR(svn_fs_fs__read_noderev(noderev_p,
+ svn_stream_from_aprfile2(revision_file, FALSE,
+ pool),
+ pool));
+
+ /* The noderev is not in cache, yet. Add it, if caching has been enabled. */
+ return set_cached_node_revision_body(*noderev_p, fs, id, pool);
+}
+
+svn_error_t *
+svn_fs_fs__read_noderev(node_revision_t **noderev_p,
+ svn_stream_t *stream,
+ apr_pool_t *pool)
+{
+ apr_hash_t *headers;
+ node_revision_t *noderev;
+ char *value;
+ const char *noderev_id;
+
+ SVN_ERR(read_header_block(&headers, stream, pool));
+
+ noderev = apr_pcalloc(pool, sizeof(*noderev));
+
+ /* Read the node-rev id. */
+ value = svn_hash_gets(headers, HEADER_ID);
+ if (value == NULL)
+ /* ### More information: filename/offset coordinates */
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Missing id field in node-rev"));
+
+ SVN_ERR(svn_stream_close(stream));
+
+ noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
+ noderev_id = value; /* for error messages later */
+
+ /* Read the type. */
+ value = svn_hash_gets(headers, HEADER_TYPE);
+
+ if ((value == NULL) ||
+ (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
+ /* ### s/kind/type/ */
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Missing kind field in node-rev '%s'"),
+ noderev_id);
+
+ noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file
+ : svn_node_dir;
+
+ /* Read the 'count' field. */
+ value = svn_hash_gets(headers, HEADER_COUNT);
+ if (value)
+ SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
+ else
+ noderev->predecessor_count = 0;
+
+ /* Get the properties location. */
+ value = svn_hash_gets(headers, HEADER_PROPS);
+ if (value)
+ {
+ SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
+ noderev->id, TRUE, pool));
+ }
+
+ /* Get the data location. */
+ value = svn_hash_gets(headers, HEADER_TEXT);
+ if (value)
+ {
+ SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
+ noderev->id,
+ (noderev->kind == svn_node_dir), pool));
+ }
+
+ /* Get the created path. */
+ value = svn_hash_gets(headers, HEADER_CPATH);
+ if (value == NULL)
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Missing cpath field in node-rev '%s'"),
+ noderev_id);
+ }
+ else
+ {
+ noderev->created_path = apr_pstrdup(pool, value);
+ }
+
+ /* Get the predecessor ID. */
+ value = svn_hash_gets(headers, HEADER_PRED);
+ if (value)
+ noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value),
+ pool);
+
+ /* Get the copyroot. */
+ value = svn_hash_gets(headers, HEADER_COPYROOT);
+ if (value == NULL)
+ {
+ noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path);
+ noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
+ }
+ else
+ {
+ char *str;
+
+ str = svn_cstring_tokenize(" ", &value);
+ if (str == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed copyroot line in node-rev '%s'"),
+ noderev_id);
+
+ noderev->copyroot_rev = SVN_STR_TO_REV(str);
+
+ if (*value == '\0')
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed copyroot line in node-rev '%s'"),
+ noderev_id);
+ noderev->copyroot_path = apr_pstrdup(pool, value);
+ }
+
+ /* Get the copyfrom. */
+ value = svn_hash_gets(headers, HEADER_COPYFROM);
+ if (value == NULL)
+ {
+ noderev->copyfrom_path = NULL;
+ noderev->copyfrom_rev = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ char *str = svn_cstring_tokenize(" ", &value);
+ if (str == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed copyfrom line in node-rev '%s'"),
+ noderev_id);
+
+ noderev->copyfrom_rev = SVN_STR_TO_REV(str);
+
+ if (*value == 0)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed copyfrom line in node-rev '%s'"),
+ noderev_id);
+ noderev->copyfrom_path = apr_pstrdup(pool, value);
+ }
+
+ /* Get whether this is a fresh txn root. */
+ value = svn_hash_gets(headers, HEADER_FRESHTXNRT);
+ noderev->is_fresh_txn_root = (value != NULL);
+
+ /* Get the mergeinfo count. */
+ value = svn_hash_gets(headers, HEADER_MINFO_CNT);
+ if (value)
+ SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
+ else
+ noderev->mergeinfo_count = 0;
+
+ /* Get whether *this* node has mergeinfo. */
+ value = svn_hash_gets(headers, HEADER_MINFO_HERE);
+ noderev->has_mergeinfo = (value != NULL);
+
+ *noderev_p = noderev;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool);
+ if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
+ {
+ svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ "Corrupt node-revision '%s'",
+ id_string->data);
+ }
+ return svn_error_trace(err);
+}
+
+
+/* Return a formatted string, compatible with filesystem format FORMAT,
+ that represents the location of representation REP. If
+ MUTABLE_REP_TRUNCATED is given, the rep is for props or dir contents,
+ and only a "-1" revision number will be given for a mutable rep.
+ If MAY_BE_CORRUPT is true, guard for NULL when constructing the string.
+ Perform the allocation from POOL. */
+static const char *
+representation_string(representation_t *rep,
+ int format,
+ svn_boolean_t mutable_rep_truncated,
+ svn_boolean_t may_be_corrupt,
+ apr_pool_t *pool)
+{
+ if (rep->txn_id && mutable_rep_truncated)
+ return "-1";
+
+#define DISPLAY_MAYBE_NULL_CHECKSUM(checksum) \
+ ((!may_be_corrupt || (checksum) != NULL) \
+ ? svn_checksum_to_cstring_display((checksum), pool) \
+ : "(null)")
+
+ if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || rep->sha1_checksum == NULL)
+ return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
+ " %" SVN_FILESIZE_T_FMT " %s",
+ rep->revision, rep->offset, rep->size,
+ rep->expanded_size,
+ DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum));
+
+ return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
+ " %" SVN_FILESIZE_T_FMT " %s %s %s",
+ rep->revision, rep->offset, rep->size,
+ rep->expanded_size,
+ DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum),
+ DISPLAY_MAYBE_NULL_CHECKSUM(rep->sha1_checksum),
+ rep->uniquifier);
+
+#undef DISPLAY_MAYBE_NULL_CHECKSUM
+
+}
+
+
+svn_error_t *
+svn_fs_fs__write_noderev(svn_stream_t *outfile,
+ node_revision_t *noderev,
+ int format,
+ svn_boolean_t include_mergeinfo,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n",
+ svn_fs_fs__id_unparse(noderev->id,
+ pool)->data));
+
+ SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n",
+ (noderev->kind == svn_node_file) ?
+ KIND_FILE : KIND_DIR));
+
+ if (noderev->predecessor_id)
+ SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n",
+ svn_fs_fs__id_unparse(noderev->predecessor_id,
+ pool)->data));
+
+ SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n",
+ noderev->predecessor_count));
+
+ if (noderev->data_rep)
+ SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n",
+ representation_string(noderev->data_rep,
+ format,
+ (noderev->kind
+ == svn_node_dir),
+ FALSE,
+ pool)));
+
+ if (noderev->prop_rep)
+ SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
+ representation_string(noderev->prop_rep, format,
+ TRUE, FALSE, pool)));
+
+ SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n",
+ noderev->created_path));
+
+ if (noderev->copyfrom_path)
+ SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld"
+ " %s\n",
+ noderev->copyfrom_rev,
+ noderev->copyfrom_path));
+
+ if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
+ (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
+ SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld"
+ " %s\n",
+ noderev->copyroot_rev,
+ noderev->copyroot_path));
+
+ if (noderev->is_fresh_txn_root)
+ SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
+
+ if (include_mergeinfo)
+ {
+ if (noderev->mergeinfo_count > 0)
+ SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %"
+ APR_INT64_T_FMT "\n",
+ noderev->mergeinfo_count));
+
+ if (noderev->has_mergeinfo)
+ SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
+ }
+
+ return svn_stream_puts(outfile, "\n");
+}
+
+svn_error_t *
+svn_fs_fs__put_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ node_revision_t *noderev,
+ svn_boolean_t fresh_txn_root,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_file_t *noderev_file;
+ const char *txn_id = svn_fs_fs__id_txn_id(id);
+
+ noderev->is_fresh_txn_root = fresh_txn_root;
+
+ if (! txn_id)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Attempted to write to non-transaction '%s'"),
+ svn_fs_fs__id_unparse(id, pool)->data);
+
+ SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool),
+ APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+
+ SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
+ pool),
+ noderev, ffd->format,
+ svn_fs_fs__fs_supports_mergeinfo(fs),
+ pool));
+
+ SVN_ERR(svn_io_file_close(noderev_file, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* For the in-transaction NODEREV within FS, write the sha1->rep mapping
+ * file in the respective transaction, if rep sharing has been enabled etc.
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+store_sha1_rep_mapping(svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* if rep sharing has been enabled and the noderev has a data rep and
+ * its SHA-1 is known, store the rep struct under its SHA1. */
+ if ( ffd->rep_sharing_allowed
+ && noderev->data_rep
+ && noderev->data_rep->sha1_checksum)
+ {
+ apr_file_t *rep_file;
+ const char *file_name = path_txn_sha1(fs,
+ svn_fs_fs__id_txn_id(noderev->id),
+ noderev->data_rep->sha1_checksum,
+ pool);
+ const char *rep_string = representation_string(noderev->data_rep,
+ ffd->format,
+ (noderev->kind
+ == svn_node_dir),
+ FALSE,
+ pool);
+ SVN_ERR(svn_io_file_open(&rep_file, file_name,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+
+ SVN_ERR(svn_io_file_write_full(rep_file, rep_string,
+ strlen(rep_string), NULL, pool));
+
+ SVN_ERR(svn_io_file_close(rep_file, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This structure is used to hold the information associated with a
+ REP line. */
+struct rep_args
+{
+ svn_boolean_t is_delta;
+ svn_boolean_t is_delta_vs_empty;
+
+ svn_revnum_t base_revision;
+ apr_off_t base_offset;
+ svn_filesize_t base_length;
+};
+
+/* Read the next line from file FILE and parse it as a text
+ representation entry. Return the parsed entry in *REP_ARGS_P.
+ Perform all allocations in POOL. */
+static svn_error_t *
+read_rep_line(struct rep_args **rep_args_p,
+ apr_file_t *file,
+ apr_pool_t *pool)
+{
+ char buffer[160];
+ apr_size_t limit;
+ struct rep_args *rep_args;
+ char *str, *last_str = buffer;
+ apr_int64_t val;
+
+ limit = sizeof(buffer);
+ SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool));
+
+ rep_args = apr_pcalloc(pool, sizeof(*rep_args));
+ rep_args->is_delta = FALSE;
+
+ if (strcmp(buffer, REP_PLAIN) == 0)
+ {
+ *rep_args_p = rep_args;
+ return SVN_NO_ERROR;
+ }
+
+ if (strcmp(buffer, REP_DELTA) == 0)
+ {
+ /* This is a delta against the empty stream. */
+ rep_args->is_delta = TRUE;
+ rep_args->is_delta_vs_empty = TRUE;
+ *rep_args_p = rep_args;
+ return SVN_NO_ERROR;
+ }
+
+ rep_args->is_delta = TRUE;
+ rep_args->is_delta_vs_empty = FALSE;
+
+ /* We have hopefully a DELTA vs. a non-empty base revision. */
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (! str || (strcmp(str, REP_DELTA) != 0))
+ goto error;
+
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (! str)
+ goto error;
+ rep_args->base_revision = SVN_STR_TO_REV(str);
+
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (! str)
+ goto error;
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ rep_args->base_offset = (apr_off_t)val;
+
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (! str)
+ goto error;
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ rep_args->base_length = (svn_filesize_t)val;
+
+ *rep_args_p = rep_args;
+ return SVN_NO_ERROR;
+
+ error:
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed representation header at %s"),
+ path_and_offset_of(file, pool));
+}
+
+/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID
+ of the header located at OFFSET and store it in *ID_P. Allocate
+ temporary variables from POOL. */
+static svn_error_t *
+get_fs_id_at_offset(svn_fs_id_t **id_p,
+ apr_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_off_t offset,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *id;
+ apr_hash_t *headers;
+ const char *node_id_str;
+
+ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+
+ SVN_ERR(read_header_block(&headers,
+ svn_stream_from_aprfile2(rev_file, TRUE, pool),
+ pool));
+
+ /* In error messages, the offset is relative to the pack file,
+ not to the rev file. */
+
+ node_id_str = svn_hash_gets(headers, HEADER_ID);
+
+ if (node_id_str == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Missing node-id in node-rev at r%ld "
+ "(offset %s)"),
+ rev,
+ apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
+
+ id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
+
+ if (id == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Corrupt node-id '%s' in node-rev at r%ld "
+ "(offset %s)"),
+ node_id_str, rev,
+ apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
+
+ *id_p = id;
+
+ /* ### assert that the txn_id is REV/OFFSET ? */
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Given an open revision file REV_FILE in FS for REV, locate the trailer that
+ specifies the offset to the root node-id and to the changed path
+ information. Store the root node offset in *ROOT_OFFSET and the
+ changed path offset in *CHANGES_OFFSET. If either of these
+ pointers is NULL, do nothing with it.
+
+ If PACKED is true, REV_FILE should be a packed shard file.
+ ### There is currently no such parameter. This function assumes that
+ is_packed_rev(FS, REV) will indicate whether REV_FILE is a packed
+ file. Therefore FS->fsap_data->min_unpacked_rev must not have been
+ refreshed since REV_FILE was opened if there is a possibility that
+ revision REV may have become packed since then.
+ TODO: Take an IS_PACKED parameter instead, in order to remove this
+ requirement.
+
+ Allocate temporary variables from POOL. */
+static svn_error_t *
+get_root_changes_offset(apr_off_t *root_offset,
+ apr_off_t *changes_offset,
+ apr_file_t *rev_file,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_off_t offset;
+ apr_off_t rev_offset;
+ char buf[64];
+ int i, num_bytes;
+ const char *str;
+ apr_size_t len;
+ apr_seek_where_t seek_relative;
+
+ /* Determine where to seek to in the file.
+
+ If we've got a pack file, we want to seek to the end of the desired
+ revision. But we don't track that, so we seek to the beginning of the
+ next revision.
+
+ Unless the next revision is in a different file, in which case, we can
+ just seek to the end of the pack file -- just like we do in the
+ non-packed case. */
+ if (is_packed_rev(fs, rev) && ((rev + 1) % ffd->max_files_per_dir != 0))
+ {
+ SVN_ERR(get_packed_offset(&offset, fs, rev + 1, pool));
+ seek_relative = APR_SET;
+ }
+ else
+ {
+ seek_relative = APR_END;
+ offset = 0;
+ }
+
+ /* Offset of the revision from the start of the pack file, if applicable. */
+ if (is_packed_rev(fs, rev))
+ SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
+ else
+ rev_offset = 0;
+
+ /* We will assume that the last line containing the two offsets
+ will never be longer than 64 characters. */
+ SVN_ERR(svn_io_file_seek(rev_file, seek_relative, &offset, pool));
+
+ offset -= sizeof(buf);
+ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+
+ /* Read in this last block, from which we will identify the last line. */
+ len = sizeof(buf);
+ SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
+
+ /* This cast should be safe since the maximum amount read, 64, will
+ never be bigger than the size of an int. */
+ num_bytes = (int) len;
+
+ /* The last byte should be a newline. */
+ if (buf[num_bytes - 1] != '\n')
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision file (r%ld) lacks trailing newline"),
+ rev);
+ }
+
+ /* Look for the next previous newline. */
+ for (i = num_bytes - 2; i >= 0; i--)
+ {
+ if (buf[i] == '\n')
+ break;
+ }
+
+ if (i < 0)
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Final line in revision file (r%ld) longer "
+ "than 64 characters"),
+ rev);
+ }
+
+ i++;
+ str = &buf[i];
+
+ /* find the next space */
+ for ( ; i < (num_bytes - 2) ; i++)
+ if (buf[i] == ' ')
+ break;
+
+ if (i == (num_bytes - 2))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Final line in revision file r%ld missing space"),
+ rev);
+
+ if (root_offset)
+ {
+ apr_int64_t val;
+
+ buf[i] = '\0';
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ *root_offset = rev_offset + (apr_off_t)val;
+ }
+
+ i++;
+ str = &buf[i];
+
+ /* find the next newline */
+ for ( ; i < num_bytes; i++)
+ if (buf[i] == '\n')
+ break;
+
+ if (changes_offset)
+ {
+ apr_int64_t val;
+
+ buf[i] = '\0';
+ SVN_ERR(svn_cstring_atoi64(&val, str));
+ *changes_offset = rev_offset + (apr_off_t)val;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Move a file into place from OLD_FILENAME in the transactions
+ directory to its final location NEW_FILENAME in the repository. On
+ Unix, match the permissions of the new file to the permissions of
+ PERMS_REFERENCE. Temporary allocations are from POOL.
+
+ This function almost duplicates svn_io_file_move(), but it tries to
+ guarantee a flush. */
+static svn_error_t *
+move_into_place(const char *old_filename,
+ const char *new_filename,
+ const char *perms_reference,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool));
+
+ /* Move the file into place. */
+ err = svn_io_file_rename(old_filename, new_filename, pool);
+ if (err && APR_STATUS_IS_EXDEV(err->apr_err))
+ {
+ apr_file_t *file;
+
+ /* Can't rename across devices; fall back to copying. */
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
+
+ /* Flush the target of the copy to disk. */
+ SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
+ APR_OS_DEFAULT, pool));
+ /* ### BH: Does this really guarantee a flush of the data written
+ ### via a completely different handle on all operating systems?
+ ###
+ ### Maybe we should perform the copy ourselves instead of making
+ ### apr do that and flush the real handle? */
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+ }
+ if (err)
+ return svn_error_trace(err);
+
+#ifdef __linux__
+ {
+ /* Linux has the unusual feature that fsync() on a file is not
+ enough to ensure that a file's directory entries have been
+ flushed to disk; you have to fsync the directory as well.
+ On other operating systems, we'd only be asking for trouble
+ by trying to open and fsync a directory. */
+ const char *dirname;
+ apr_file_t *file;
+
+ dirname = svn_dirent_dirname(new_filename, pool);
+ SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
+ pool));
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+ }
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_file_t *revision_file;
+ apr_off_t root_offset;
+ svn_fs_id_t *root_id = NULL;
+ svn_boolean_t is_cached;
+
+ SVN_ERR(ensure_revision_exists(fs, rev, pool));
+
+ SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached,
+ ffd->rev_root_id_cache, &rev, pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
+ SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, fs, rev,
+ pool));
+
+ SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev,
+ root_offset, pool));
+
+ SVN_ERR(svn_io_file_close(revision_file, pool));
+
+ SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, pool));
+
+ *root_id_p = root_id;
+
+ return SVN_NO_ERROR;
+}
+
+/* Revprop caching management.
+ *
+ * Mechanism:
+ * ----------
+ *
+ * Revprop caching needs to be activated and will be deactivated for the
+ * respective FS instance if the necessary infrastructure could not be
+ * initialized. In deactivated mode, there is almost no runtime overhead
+ * associated with revprop caching. As long as no revprops are being read
+ * or changed, revprop caching imposes no overhead.
+ *
+ * When activated, we cache revprops using (revision, generation) pairs
+ * as keys with the generation being incremented upon every revprop change.
+ * Since the cache is process-local, the generation needs to be tracked
+ * for at least as long as the process lives but may be reset afterwards.
+ *
+ * To track the revprop generation, we use two-layer approach. On the lower
+ * level, we use named atomics to have a system-wide consistent value for
+ * the current revprop generation. However, those named atomics will only
+ * remain valid for as long as at least one process / thread in the system
+ * accesses revprops in the respective repository. The underlying shared
+ * memory gets cleaned up afterwards.
+ *
+ * On the second level, we will use a persistent file to track the latest
+ * revprop generation. It will be written upon each revprop change but
+ * only be read if we are the first process to initialize the named atomics
+ * with that value.
+ *
+ * The overhead for the second and following accesses to revprops is
+ * almost zero on most systems.
+ *
+ *
+ * Tech aspects:
+ * -------------
+ *
+ * A problem is that we need to provide a globally available file name to
+ * back the SHM implementation on OSes that need it. We can only assume
+ * write access to some file within the respective repositories. Because
+ * a given server process may access thousands of repositories during its
+ * lifetime, keeping the SHM data alive for all of them is also not an
+ * option.
+ *
+ * So, we store the new revprop generation on disk as part of each
+ * setrevprop call, i.e. this write will be serialized and the write order
+ * be guaranteed by the repository write lock.
+ *
+ * The only racy situation occurs when the data is being read again by two
+ * processes concurrently but in that situation, the first process to
+ * finish that procedure is guaranteed to be the only one that initializes
+ * the SHM data. Since even writers will first go through that
+ * initialization phase, they will never operate on stale data.
+ */
+
+/* Read revprop generation as stored on disk for repository FS. The result
+ * is returned in *CURRENT. Default to 2 if no such file is available.
+ */
+static svn_error_t *
+read_revprop_generation_file(apr_int64_t *current,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_file_t *file;
+ char buf[80];
+ apr_size_t len;
+ const char *path = path_revprop_generation(fs, pool);
+
+ err = svn_io_file_open(&file, path,
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ *current = 2;
+
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ len = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
+
+ /* Check that the first line contains only digits. */
+ SVN_ERR(check_file_buffer_numeric(buf, 0, path,
+ "Revprop Generation", pool));
+ SVN_ERR(svn_cstring_atoi64(current, buf));
+
+ return svn_io_file_close(file, pool);
+}
+
+/* Write the CURRENT revprop generation to disk for repository FS.
+ */
+static svn_error_t *
+write_revprop_generation_file(svn_fs_t *fs,
+ apr_int64_t current,
+ apr_pool_t *pool)
+{
+ apr_file_t *file;
+ const char *tmp_path;
+
+ char buf[SVN_INT64_BUFFER_SIZE];
+ apr_size_t len = svn__i64toa(buf, current);
+ buf[len] = '\n';
+
+ SVN_ERR(svn_io_open_unique_file3(&file, &tmp_path, fs->path,
+ svn_io_file_del_none, pool, pool));
+ SVN_ERR(svn_io_file_write_full(file, buf, len + 1, NULL, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ return move_into_place(tmp_path, path_revprop_generation(fs, pool),
+ tmp_path, pool);
+}
+
+/* Make sure the revprop_namespace member in FS is set. */
+static svn_error_t *
+ensure_revprop_namespace(svn_fs_t *fs)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ return ffd->revprop_namespace == NULL
+ ? svn_atomic_namespace__create(&ffd->revprop_namespace,
+ svn_dirent_join(fs->path,
+ ATOMIC_REVPROP_NAMESPACE,
+ fs->pool),
+ fs->pool)
+ : SVN_NO_ERROR;
+}
+
+/* Make sure the revprop_namespace member in FS is set. */
+static svn_error_t *
+cleanup_revprop_namespace(svn_fs_t *fs)
+{
+ const char *name = svn_dirent_join(fs->path,
+ ATOMIC_REVPROP_NAMESPACE,
+ fs->pool);
+ return svn_error_trace(svn_atomic_namespace__cleanup(name, fs->pool));
+}
+
+/* Make sure the revprop_generation member in FS is set and, if necessary,
+ * initialized with the latest value stored on disk.
+ */
+static svn_error_t *
+ensure_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ SVN_ERR(ensure_revprop_namespace(fs));
+ if (ffd->revprop_generation == NULL)
+ {
+ apr_int64_t current = 0;
+
+ SVN_ERR(svn_named_atomic__get(&ffd->revprop_generation,
+ ffd->revprop_namespace,
+ ATOMIC_REVPROP_GENERATION,
+ TRUE));
+
+ /* If the generation is at 0, we just created a new namespace
+ * (it would be at least 2 otherwise). Read the latest generation
+ * from disk and if we are the first one to initialize the atomic
+ * (i.e. is still 0), set it to the value just gotten.
+ */
+ SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
+ if (current == 0)
+ {
+ SVN_ERR(read_revprop_generation_file(&current, fs, pool));
+ SVN_ERR(svn_named_atomic__cmpxchg(NULL, current, 0,
+ ffd->revprop_generation));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Make sure the revprop_timeout member in FS is set. */
+static svn_error_t *
+ensure_revprop_timeout(svn_fs_t *fs)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ SVN_ERR(ensure_revprop_namespace(fs));
+ return ffd->revprop_timeout == NULL
+ ? svn_named_atomic__get(&ffd->revprop_timeout,
+ ffd->revprop_namespace,
+ ATOMIC_REVPROP_TIMEOUT,
+ TRUE)
+ : SVN_NO_ERROR;
+}
+
+/* Create an error object with the given MESSAGE and pass it to the
+ WARNING member of FS. */
+static void
+log_revprop_cache_init_warning(svn_fs_t *fs,
+ svn_error_t *underlying_err,
+ const char *message)
+{
+ svn_error_t *err = svn_error_createf(SVN_ERR_FS_REVPROP_CACHE_INIT_FAILURE,
+ underlying_err,
+ message, fs->path);
+
+ if (fs->warning)
+ (fs->warning)(fs->warning_baton, err);
+
+ svn_error_clear(err);
+}
+
+/* Test whether revprop cache and necessary infrastructure are
+ available in FS. */
+static svn_boolean_t
+has_revprop_cache(svn_fs_t *fs, apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_error_t *error;
+
+ /* is the cache (still) enabled? */
+ if (ffd->revprop_cache == NULL)
+ return FALSE;
+
+ /* is it efficient? */
+ if (!svn_named_atomic__is_efficient())
+ {
+ /* access to it would be quite slow
+ * -> disable the revprop cache for good
+ */
+ ffd->revprop_cache = NULL;
+ log_revprop_cache_init_warning(fs, NULL,
+ "Revprop caching for '%s' disabled"
+ " because it would be inefficient.");
+
+ return FALSE;
+ }
+
+ /* try to access our SHM-backed infrastructure */
+ error = ensure_revprop_generation(fs, pool);
+ if (error)
+ {
+ /* failure -> disable revprop cache for good */
+
+ ffd->revprop_cache = NULL;
+ log_revprop_cache_init_warning(fs, error,
+ "Revprop caching for '%s' disabled "
+ "because SHM infrastructure for revprop "
+ "caching failed to initialize.");
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Baton structure for revprop_generation_fixup. */
+typedef struct revprop_generation_fixup_t
+{
+ /* revprop generation to read */
+ apr_int64_t *generation;
+
+ /* containing the revprop_generation member to query */
+ fs_fs_data_t *ffd;
+} revprop_generation_upgrade_t;
+
+/* If the revprop generation has an odd value, it means the original writer
+ of the revprop got killed. We don't know whether that process as able
+ to change the revprop data but we assume that it was. Therefore, we
+ increase the generation in that case to basically invalidate everyones
+ cache content.
+ Execute this onlx while holding the write lock to the repo in baton->FFD.
+ */
+static svn_error_t *
+revprop_generation_fixup(void *void_baton,
+ apr_pool_t *pool)
+{
+ revprop_generation_upgrade_t *baton = void_baton;
+ assert(baton->ffd->has_write_lock);
+
+ /* Maybe, either the original revprop writer or some other reader has
+ already corrected / bumped the revprop generation. Thus, we need
+ to read it again. */
+ SVN_ERR(svn_named_atomic__read(baton->generation,
+ baton->ffd->revprop_generation));
+
+ /* Cause everyone to re-read revprops upon their next access, if the
+ last revprop write did not complete properly. */
+ while (*baton->generation % 2)
+ SVN_ERR(svn_named_atomic__add(baton->generation,
+ 1,
+ baton->ffd->revprop_generation));
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the current revprop generation and return it in *GENERATION.
+ Also, detect aborted / crashed writers and recover from that.
+ Use the access object in FS to set the shared mem values. */
+static svn_error_t *
+read_revprop_generation(apr_int64_t *generation,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ apr_int64_t current = 0;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* read the current revprop generation number */
+ SVN_ERR(ensure_revprop_generation(fs, pool));
+ SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
+
+ /* is an unfinished revprop write under the way? */
+ if (current % 2)
+ {
+ apr_int64_t timeout = 0;
+
+ /* read timeout for the write operation */
+ SVN_ERR(ensure_revprop_timeout(fs));
+ SVN_ERR(svn_named_atomic__read(&timeout, ffd->revprop_timeout));
+
+ /* has the writer process been aborted,
+ * i.e. has the timeout been reached?
+ */
+ if (apr_time_now() > timeout)
+ {
+ revprop_generation_upgrade_t baton;
+ baton.generation = &current;
+ baton.ffd = ffd;
+
+ /* Ensure that the original writer process no longer exists by
+ * acquiring the write lock to this repository. Then, fix up
+ * the revprop generation.
+ */
+ if (ffd->has_write_lock)
+ SVN_ERR(revprop_generation_fixup(&baton, pool));
+ else
+ SVN_ERR(svn_fs_fs__with_write_lock(fs, revprop_generation_fixup,
+ &baton, pool));
+ }
+ }
+
+ /* return the value we just got */
+ *generation = current;
+ return SVN_NO_ERROR;
+}
+
+/* Set the revprop generation to the next odd number to indicate that
+ there is a revprop write process under way. If that times out,
+ readers shall recover from that state & re-read revprops.
+ Use the access object in FS to set the shared mem value. */
+static svn_error_t *
+begin_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
+{
+ apr_int64_t current;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* set the timeout for the write operation */
+ SVN_ERR(ensure_revprop_timeout(fs));
+ SVN_ERR(svn_named_atomic__write(NULL,
+ apr_time_now() + REVPROP_CHANGE_TIMEOUT,
+ ffd->revprop_timeout));
+
+ /* set the revprop generation to an odd value to indicate
+ * that a write is in progress
+ */
+ SVN_ERR(ensure_revprop_generation(fs, pool));
+ do
+ {
+ SVN_ERR(svn_named_atomic__add(&current,
+ 1,
+ ffd->revprop_generation));
+ }
+ while (current % 2 == 0);
+
+ return SVN_NO_ERROR;
+}
+
+/* Set the revprop generation to the next even number to indicate that
+ a) readers shall re-read revprops, and
+ b) the write process has been completed (no recovery required)
+ Use the access object in FS to set the shared mem value. */
+static svn_error_t *
+end_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
+{
+ apr_int64_t current = 1;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* set the revprop generation to an even value to indicate
+ * that a write has been completed
+ */
+ SVN_ERR(ensure_revprop_generation(fs, pool));
+ do
+ {
+ SVN_ERR(svn_named_atomic__add(&current,
+ 1,
+ ffd->revprop_generation));
+ }
+ while (current % 2);
+
+ /* Save the latest generation to disk. FS is currently in a "locked"
+ * state such that we can be sure the be the only ones to write that
+ * file.
+ */
+ return write_revprop_generation_file(fs, current, pool);
+}
+
+/* Container for all data required to access the packed revprop file
+ * for a given REVISION. This structure will be filled incrementally
+ * by read_pack_revprops() its sub-routines.
+ */
+typedef struct packed_revprops_t
+{
+ /* revision number to read (not necessarily the first in the pack) */
+ svn_revnum_t revision;
+
+ /* current revprop generation. Used when populating the revprop cache */
+ apr_int64_t generation;
+
+ /* the actual revision properties */
+ apr_hash_t *properties;
+
+ /* their size when serialized to a single string
+ * (as found in PACKED_REVPROPS) */
+ apr_size_t serialized_size;
+
+
+ /* name of the pack file (without folder path) */
+ const char *filename;
+
+ /* packed shard folder path */
+ const char *folder;
+
+ /* sum of values in SIZES */
+ apr_size_t total_size;
+
+ /* first revision in the pack */
+ svn_revnum_t start_revision;
+
+ /* size of the revprops in PACKED_REVPROPS */
+ apr_array_header_t *sizes;
+
+ /* offset of the revprops in PACKED_REVPROPS */
+ apr_array_header_t *offsets;
+
+
+ /* concatenation of the serialized representation of all revprops
+ * in the pack, i.e. the pack content without header and compression */
+ svn_stringbuf_t *packed_revprops;
+
+ /* content of the manifest.
+ * Maps long(rev - START_REVISION) to const char* pack file name */
+ apr_array_header_t *manifest;
+} packed_revprops_t;
+
+/* Parse the serialized revprops in CONTENT and return them in *PROPERTIES.
+ * Also, put them into the revprop cache, if activated, for future use.
+ * Three more parameters are being used to update the revprop cache: FS is
+ * our file system, the revprops belong to REVISION and the global revprop
+ * GENERATION is used as well.
+ *
+ * The returned hash will be allocated in POOL, SCRATCH_POOL is being used
+ * for temporary allocations.
+ */
+static svn_error_t *
+parse_revprop(apr_hash_t **properties,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_int64_t generation,
+ svn_string_t *content,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *stream = svn_stream_from_string(content, scratch_pool);
+ *properties = apr_hash_make(pool);
+
+ SVN_ERR(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool));
+ if (has_revprop_cache(fs, pool))
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ pair_cache_key_t key = { 0 };
+
+ key.revision = revision;
+ key.second = generation;
+ SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, *properties,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the non-packed revprops for revision REV in FS, put them into the
+ * revprop cache if activated and return them in *PROPERTIES. GENERATION
+ * is the current revprop generation.
+ *
+ * If the data could not be read due to an otherwise recoverable error,
+ * leave *PROPERTIES unchanged. No error will be returned in that case.
+ *
+ * Allocations will be done in POOL.
+ */
+static svn_error_t *
+read_non_packed_revprop(apr_hash_t **properties,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_int64_t generation,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *content = NULL;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_boolean_t missing = FALSE;
+ int i;
+
+ for (i = 0; i < RECOVERABLE_RETRY_COUNT && !missing && !content; ++i)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(try_stringbuf_from_file(&content,
+ &missing,
+ path_revprops(fs, rev, iterpool),
+ i + 1 < RECOVERABLE_RETRY_COUNT,
+ iterpool));
+ }
+
+ if (content)
+ SVN_ERR(parse_revprop(properties, fs, rev, generation,
+ svn_stringbuf__morph_into_string(content),
+ pool, iterpool));
+
+ svn_pool_clear(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST
+ * members. Use POOL for allocating results and SCRATCH_POOL for temporaries.
+ */
+static svn_error_t *
+get_revprop_packname(svn_fs_t *fs,
+ packed_revprops_t *revprops,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_stringbuf_t *content = NULL;
+ const char *manifest_file_path;
+ int idx;
+
+ /* read content of the manifest file */
+ revprops->folder = path_revprops_pack_shard(fs, revprops->revision, pool);
+ manifest_file_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
+
+ SVN_ERR(read_content(&content, manifest_file_path, pool));
+
+ /* parse the manifest. Every line is a file name */
+ revprops->manifest = apr_array_make(pool, ffd->max_files_per_dir,
+ sizeof(const char*));
+ while (content->data)
+ {
+ APR_ARRAY_PUSH(revprops->manifest, const char*) = content->data;
+ content->data = strchr(content->data, '\n');
+ if (content->data)
+ {
+ *content->data = 0;
+ content->data++;
+ }
+ }
+
+ /* Index for our revision. Rev 0 is excluded from the first shard. */
+ idx = (int)(revprops->revision % ffd->max_files_per_dir);
+ if (revprops->revision < ffd->max_files_per_dir)
+ --idx;
+
+ if (revprops->manifest->nelts <= idx)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Packed revprop manifest for rev %ld too "
+ "small"), revprops->revision);
+
+ /* Now get the file name */
+ revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*);
+
+ return SVN_NO_ERROR;
+}
+
+/* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS,
+ * fill the START_REVISION, SIZES, OFFSETS members. Also, make
+ * PACKED_REVPROPS point to the first serialized revprop.
+ *
+ * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as
+ * well as the SERIALIZED_SIZE member. If revprop caching has been
+ * enabled, parse all revprops in the pack and cache them.
+ */
+static svn_error_t *
+parse_packed_revprops(svn_fs_t *fs,
+ packed_revprops_t *revprops,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *stream;
+ apr_int64_t first_rev, count, i;
+ apr_off_t offset;
+ const char *header_end;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* decompress (even if the data is only "stored", there is still a
+ * length header to remove) */
+ svn_string_t *compressed
+ = svn_stringbuf__morph_into_string(revprops->packed_revprops);
+ svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(pool);
+ SVN_ERR(svn__decompress(compressed, uncompressed, 0x1000000));
+
+ /* read first revision number and number of revisions in the pack */
+ stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
+ SVN_ERR(read_number_from_stream(&first_rev, NULL, stream, iterpool));
+ SVN_ERR(read_number_from_stream(&count, NULL, stream, iterpool));
+
+ /* make PACKED_REVPROPS point to the first char after the header.
+ * This is where the serialized revprops are. */
+ header_end = strstr(uncompressed->data, "\n\n");
+ if (header_end == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Header end not found"));
+
+ offset = header_end - uncompressed->data + 2;
+
+ revprops->packed_revprops = svn_stringbuf_create_empty(pool);
+ revprops->packed_revprops->data = uncompressed->data + offset;
+ revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
+ revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset);
+
+ /* STREAM still points to the first entry in the sizes list.
+ * Init / construct REVPROPS members. */
+ revprops->start_revision = (svn_revnum_t)first_rev;
+ revprops->sizes = apr_array_make(pool, (int)count, sizeof(offset));
+ revprops->offsets = apr_array_make(pool, (int)count, sizeof(offset));
+
+ /* Now parse, revision by revision, the size and content of each
+ * revisions' revprops. */
+ for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i)
+ {
+ apr_int64_t size;
+ svn_string_t serialized;
+ apr_hash_t *properties;
+ svn_revnum_t revision = (svn_revnum_t)(first_rev + i);
+
+ /* read & check the serialized size */
+ SVN_ERR(read_number_from_stream(&size, NULL, stream, iterpool));
+ if (size + offset > (apr_int64_t)revprops->packed_revprops->len)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Packed revprop size exceeds pack file size"));
+
+ /* Parse this revprops list, if necessary */
+ serialized.data = revprops->packed_revprops->data + offset;
+ serialized.len = (apr_size_t)size;
+
+ if (revision == revprops->revision)
+ {
+ SVN_ERR(parse_revprop(&revprops->properties, fs, revision,
+ revprops->generation, &serialized,
+ pool, iterpool));
+ revprops->serialized_size = serialized.len;
+ }
+ else
+ {
+ /* If revprop caching is enabled, parse any revprops.
+ * They will get cached as a side-effect of this. */
+ if (has_revprop_cache(fs, pool))
+ SVN_ERR(parse_revprop(&properties, fs, revision,
+ revprops->generation, &serialized,
+ iterpool, iterpool));
+ }
+
+ /* fill REVPROPS data structures */
+ APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len;
+ APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset;
+ revprops->total_size += serialized.len;
+
+ offset += serialized.len;
+
+ svn_pool_clear(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* In filesystem FS, read the packed revprops for revision REV into
+ * *REVPROPS. Use GENERATION to populate the revprop cache, if enabled.
+ * Allocate data in POOL.
+ */
+static svn_error_t *
+read_pack_revprop(packed_revprops_t **revprops,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_int64_t generation,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_boolean_t missing = FALSE;
+ svn_error_t *err;
+ packed_revprops_t *result;
+ int i;
+
+ /* someone insisted that REV is packed. Double-check if necessary */
+ if (!is_packed_revprop(fs, rev))
+ SVN_ERR(update_min_unpacked_rev(fs, iterpool));
+
+ if (!is_packed_revprop(fs, rev))
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such packed revision %ld"), rev);
+
+ /* initialize the result data structure */
+ result = apr_pcalloc(pool, sizeof(*result));
+ result->revision = rev;
+ result->generation = generation;
+
+ /* try to read the packed revprops. This may require retries if we have
+ * concurrent writers. */
+ for (i = 0; i < RECOVERABLE_RETRY_COUNT && !result->packed_revprops; ++i)
+ {
+ const char *file_path;
+
+ /* there might have been concurrent writes.
+ * Re-read the manifest and the pack file.
+ */
+ SVN_ERR(get_revprop_packname(fs, result, pool, iterpool));
+ file_path = svn_dirent_join(result->folder,
+ result->filename,
+ iterpool);
+ SVN_ERR(try_stringbuf_from_file(&result->packed_revprops,
+ &missing,
+ file_path,
+ i + 1 < RECOVERABLE_RETRY_COUNT,
+ pool));
+
+ /* If we could not find the file, there was a write.
+ * So, we should refresh our revprop generation info as well such
+ * that others may find data we will put into the cache. They would
+ * consider it outdated, otherwise.
+ */
+ if (missing && has_revprop_cache(fs, pool))
+ SVN_ERR(read_revprop_generation(&result->generation, fs, pool));
+
+ svn_pool_clear(iterpool);
+ }
+
+ /* the file content should be available now */
+ if (!result->packed_revprops)
+ return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL,
+ _("Failed to read revprop pack file for rev %ld"), rev);
+
+ /* parse it. RESULT will be complete afterwards. */
+ err = parse_packed_revprops(fs, result, pool, iterpool);
+ svn_pool_destroy(iterpool);
+ if (err)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ _("Revprop pack file for rev %ld is corrupt"), rev);
+
+ *revprops = result;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
+ *
+ * Allocations will be done in POOL.
+ */
+static svn_error_t *
+get_revision_proplist(apr_hash_t **proplist_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_int64_t generation = 0;
+
+ /* not found, yet */
+ *proplist_p = NULL;
+
+ /* should they be available at all? */
+ SVN_ERR(ensure_revision_exists(fs, rev, pool));
+
+ /* Try cache lookup first. */
+ if (has_revprop_cache(fs, pool))
+ {
+ svn_boolean_t is_cached;
+ pair_cache_key_t key = { 0 };
+
+ SVN_ERR(read_revprop_generation(&generation, fs, pool));
+
+ key.revision = rev;
+ key.second = generation;
+ SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
+ ffd->revprop_cache, &key, pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+ }
+
+ /* if REV had not been packed when we began, try reading it from the
+ * non-packed shard. If that fails, we will fall through to packed
+ * shard reads. */
+ if (!is_packed_revprop(fs, rev))
+ {
+ svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
+ generation, pool);
+ if (err)
+ {
+ if (!APR_STATUS_IS_ENOENT(err->apr_err)
+ || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ *proplist_p = NULL; /* in case read_non_packed_revprop changed it */
+ }
+ }
+
+ /* if revprop packing is available and we have not read the revprops, yet,
+ * try reading them from a packed shard. If that fails, REV is most
+ * likely invalid (or its revprops highly contested). */
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p)
+ {
+ packed_revprops_t *packed_revprops;
+ SVN_ERR(read_pack_revprop(&packed_revprops, fs, rev, generation, pool));
+ *proplist_p = packed_revprops->properties;
+ }
+
+ /* The revprops should have been there. Did we get them? */
+ if (!*proplist_p)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("Could not read revprops for revision %ld"),
+ rev);
+
+ return SVN_NO_ERROR;
+}
+
+/* Serialize the revision property list PROPLIST of revision REV in
+ * filesystem FS to a non-packed file. Return the name of that temporary
+ * file in *TMP_PATH and the file path that it must be moved to in
+ * *FINAL_PATH.
+ *
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+write_non_packed_revprop(const char **final_path,
+ const char **tmp_path,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_hash_t *proplist,
+ apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+ *final_path = path_revprops(fs, rev, pool);
+
+ /* ### do we have a directory sitting around already? we really shouldn't
+ ### have to get the dirname here. */
+ SVN_ERR(svn_stream_open_unique(&stream, tmp_path,
+ svn_dirent_dirname(*final_path, pool),
+ svn_io_file_del_none, pool, pool));
+ SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ return SVN_NO_ERROR;
+}
+
+/* After writing the new revprop file(s), call this function to move the
+ * file at TMP_PATH to FINAL_PATH and give it the permissions from
+ * PERMS_REFERENCE.
+ *
+ * If indicated in BUMP_GENERATION, increase FS' revprop generation.
+ * Finally, delete all the temporary files given in FILES_TO_DELETE.
+ * The latter may be NULL.
+ *
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+switch_to_new_revprop(svn_fs_t *fs,
+ const char *final_path,
+ const char *tmp_path,
+ const char *perms_reference,
+ apr_array_header_t *files_to_delete,
+ svn_boolean_t bump_generation,
+ apr_pool_t *pool)
+{
+ /* Now, we may actually be replacing revprops. Make sure that all other
+ threads and processes will know about this. */
+ if (bump_generation)
+ SVN_ERR(begin_revprop_change(fs, pool));
+
+ SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool));
+
+ /* Indicate that the update (if relevant) has been completed. */
+ if (bump_generation)
+ SVN_ERR(end_revprop_change(fs, pool));
+
+ /* Clean up temporary files, if necessary. */
+ if (files_to_delete)
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ for (i = 0; i < files_to_delete->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
+ SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
+ svn_pool_clear(iterpool);
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Write a pack file header to STREAM that starts at revision START_REVISION
+ * and contains the indexes [START,END) of SIZES.
+ */
+static svn_error_t *
+serialize_revprops_header(svn_stream_t *stream,
+ svn_revnum_t start_revision,
+ apr_array_header_t *sizes,
+ int start,
+ int end,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ SVN_ERR_ASSERT(start < end);
+
+ /* start revision and entry count */
+ SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision));
+ SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start));
+
+ /* the sizes array */
+ for (i = start; i < end; ++i)
+ {
+ apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
+ SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
+ size));
+ }
+
+ /* the double newline char indicates the end of the header */
+ SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
+
+ svn_pool_clear(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Writes the a pack file to FILE_STREAM. It copies the serialized data
+ * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
+ *
+ * The data for the latter is taken from NEW_SERIALIZED. Note, that
+ * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
+ * taken in that case but only a subset of the old data will be copied.
+ *
+ * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
+ * POOL is used for temporary allocations.
+ */
+static svn_error_t *
+repack_revprops(svn_fs_t *fs,
+ packed_revprops_t *revprops,
+ int start,
+ int end,
+ int changed_index,
+ svn_stringbuf_t *new_serialized,
+ apr_off_t new_total_size,
+ svn_stream_t *file_stream,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_stream_t *stream;
+ int i;
+
+ /* create data empty buffers and the stream object */
+ svn_stringbuf_t *uncompressed
+ = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool);
+ svn_stringbuf_t *compressed
+ = svn_stringbuf_create_empty(pool);
+ stream = svn_stream_from_stringbuf(uncompressed, pool);
+
+ /* write the header*/
+ SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
+ revprops->sizes, start, end, pool));
+
+ /* append the serialized revprops */
+ for (i = start; i < end; ++i)
+ if (i == changed_index)
+ {
+ SVN_ERR(svn_stream_write(stream,
+ new_serialized->data,
+ &new_serialized->len));
+ }
+ else
+ {
+ apr_size_t size
+ = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
+ apr_size_t offset
+ = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
+
+ SVN_ERR(svn_stream_write(stream,
+ revprops->packed_revprops->data + offset,
+ &size));
+ }
+
+ /* flush the stream buffer (if any) to our underlying data buffer */
+ SVN_ERR(svn_stream_close(stream));
+
+ /* compress / store the data */
+ SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
+ compressed,
+ ffd->compress_packed_revprops
+ ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
+ : SVN_DELTA_COMPRESSION_LEVEL_NONE));
+
+ /* finally, write the content to the target stream and close it */
+ SVN_ERR(svn_stream_write(file_stream, compressed->data, &compressed->len));
+ SVN_ERR(svn_stream_close(file_stream));
+
+ return SVN_NO_ERROR;
+}
+
+/* Allocate a new pack file name for the revisions at index [START,END)
+ * of REVPROPS->MANIFEST. Add the name of old file to FILES_TO_DELETE,
+ * auto-create that array if necessary. Return an open file stream to
+ * the new file in *STREAM allocated in POOL.
+ */
+static svn_error_t *
+repack_stream_open(svn_stream_t **stream,
+ svn_fs_t *fs,
+ packed_revprops_t *revprops,
+ int start,
+ int end,
+ apr_array_header_t **files_to_delete,
+ apr_pool_t *pool)
+{
+ apr_int64_t tag;
+ const char *tag_string;
+ svn_string_t *new_filename;
+ int i;
+ apr_file_t *file;
+
+ /* get the old (= current) file name and enlist it for later deletion */
+ const char *old_filename
+ = APR_ARRAY_IDX(revprops->manifest, start, const char*);
+
+ if (*files_to_delete == NULL)
+ *files_to_delete = apr_array_make(pool, 3, sizeof(const char*));
+
+ APR_ARRAY_PUSH(*files_to_delete, const char*)
+ = svn_dirent_join(revprops->folder, old_filename, pool);
+
+ /* increase the tag part, i.e. the counter after the dot */
+ tag_string = strchr(old_filename, '.');
+ if (tag_string == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Packed file '%s' misses a tag"),
+ old_filename);
+
+ SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
+ new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT,
+ revprops->start_revision + start,
+ ++tag);
+
+ /* update the manifest to point to the new file */
+ for (i = start; i < end; ++i)
+ APR_ARRAY_IDX(revprops->manifest, i, const char*) = new_filename->data;
+
+ /* create a file stream for the new file */
+ SVN_ERR(svn_io_file_open(&file, svn_dirent_join(revprops->folder,
+ new_filename->data,
+ pool),
+ APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
+ *stream = svn_stream_from_aprfile2(file, FALSE, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* For revision REV in filesystem FS, set the revision properties to
+ * PROPLIST. Return a new file in *TMP_PATH that the caller shall move
+ * to *FINAL_PATH to make the change visible. Files to be deleted will
+ * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+write_packed_revprop(const char **final_path,
+ const char **tmp_path,
+ apr_array_header_t **files_to_delete,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_hash_t *proplist,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ packed_revprops_t *revprops;
+ apr_int64_t generation = 0;
+ svn_stream_t *stream;
+ svn_stringbuf_t *serialized;
+ apr_off_t new_total_size;
+ int changed_index;
+
+ /* read the current revprop generation. This value will not change
+ * while we hold the global write lock to this FS. */
+ if (has_revprop_cache(fs, pool))
+ SVN_ERR(read_revprop_generation(&generation, fs, pool));
+
+ /* read contents of the current pack file */
+ SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool));
+
+ /* serialize the new revprops */
+ serialized = svn_stringbuf_create_empty(pool);
+ stream = svn_stream_from_stringbuf(serialized, pool);
+ SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ /* calculate the size of the new data */
+ changed_index = (int)(rev - revprops->start_revision);
+ new_total_size = revprops->total_size - revprops->serialized_size
+ + serialized->len
+ + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
+
+ APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
+
+ /* can we put the new data into the same pack as the before? */
+ if ( new_total_size < ffd->revprop_pack_size
+ || revprops->sizes->nelts == 1)
+ {
+ /* simply replace the old pack file with new content as we do it
+ * in the non-packed case */
+
+ *final_path = svn_dirent_join(revprops->folder, revprops->filename,
+ pool);
+ SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
+ svn_io_file_del_none, pool, pool));
+ SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
+ changed_index, serialized, new_total_size,
+ stream, pool));
+ }
+ else
+ {
+ /* split the pack file into two of roughly equal size */
+ int right_count, left_count, i;
+
+ int left = 0;
+ int right = revprops->sizes->nelts - 1;
+ apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
+ apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
+
+ /* let left and right side grow such that their size difference
+ * is minimal after each step. */
+ while (left <= right)
+ if ( left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
+ < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
+ {
+ left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
+ + SVN_INT64_BUFFER_SIZE;
+ ++left;
+ }
+ else
+ {
+ right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
+ + SVN_INT64_BUFFER_SIZE;
+ --right;
+ }
+
+ /* since the items need much less than SVN_INT64_BUFFER_SIZE
+ * bytes to represent their length, the split may not be optimal */
+ left_count = left;
+ right_count = revprops->sizes->nelts - left;
+
+ /* if new_size is large, one side may exceed the pack size limit.
+ * In that case, split before and after the modified revprop.*/
+ if ( left_size > ffd->revprop_pack_size
+ || right_size > ffd->revprop_pack_size)
+ {
+ left_count = changed_index;
+ right_count = revprops->sizes->nelts - left_count - 1;
+ }
+
+ /* write the new, split files */
+ if (left_count)
+ {
+ SVN_ERR(repack_stream_open(&stream, fs, revprops, 0,
+ left_count, files_to_delete, pool));
+ SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
+ changed_index, serialized, new_total_size,
+ stream, pool));
+ }
+
+ if (left_count + right_count < revprops->sizes->nelts)
+ {
+ SVN_ERR(repack_stream_open(&stream, fs, revprops, changed_index,
+ changed_index + 1, files_to_delete,
+ pool));
+ SVN_ERR(repack_revprops(fs, revprops, changed_index,
+ changed_index + 1,
+ changed_index, serialized, new_total_size,
+ stream, pool));
+ }
+
+ if (right_count)
+ {
+ SVN_ERR(repack_stream_open(&stream, fs, revprops,
+ revprops->sizes->nelts - right_count,
+ revprops->sizes->nelts,
+ files_to_delete, pool));
+ SVN_ERR(repack_revprops(fs, revprops,
+ revprops->sizes->nelts - right_count,
+ revprops->sizes->nelts, changed_index,
+ serialized, new_total_size, stream,
+ pool));
+ }
+
+ /* write the new manifest */
+ *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
+ SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
+ svn_io_file_del_none, pool, pool));
+
+ for (i = 0; i < revprops->manifest->nelts; ++i)
+ {
+ const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
+ const char*);
+ SVN_ERR(svn_stream_printf(stream, pool, "%s\n", filename));
+ }
+
+ SVN_ERR(svn_stream_close(stream));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set the revision property list of revision REV in filesystem FS to
+ PROPLIST. Use POOL for temporary allocations. */
+static svn_error_t *
+set_revision_proplist(svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_hash_t *proplist,
+ apr_pool_t *pool)
+{
+ svn_boolean_t is_packed;
+ svn_boolean_t bump_generation = FALSE;
+ const char *final_path;
+ const char *tmp_path;
+ const char *perms_reference;
+ apr_array_header_t *files_to_delete = NULL;
+
+ SVN_ERR(ensure_revision_exists(fs, rev, pool));
+
+ /* this info will not change while we hold the global FS write lock */
+ is_packed = is_packed_revprop(fs, rev);
+
+ /* Test whether revprops already exist for this revision.
+ * Only then will we need to bump the revprop generation. */
+ if (has_revprop_cache(fs, pool))
+ {
+ if (is_packed)
+ {
+ bump_generation = TRUE;
+ }
+ else
+ {
+ svn_node_kind_t kind;
+ SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind,
+ pool));
+ bump_generation = kind != svn_node_none;
+ }
+ }
+
+ /* Serialize the new revprop data */
+ if (is_packed)
+ SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
+ fs, rev, proplist, pool));
+ else
+ SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
+ fs, rev, proplist, pool));
+
+ /* We use the rev file of this revision as the perms reference,
+ * because when setting revprops for the first time, the revprop
+ * file won't exist and therefore can't serve as its own reference.
+ * (Whereas the rev file should already exist at this point.)
+ */
+ SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool));
+
+ /* Now, switch to the new revprop data. */
+ SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
+ files_to_delete, bump_generation, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Represents where in the current svndiff data block each
+ representation is. */
+struct rep_state
+{
+ apr_file_t *file;
+ /* The txdelta window cache to use or NULL. */
+ svn_cache__t *window_cache;
+ /* Caches un-deltified windows. May be NULL. */
+ svn_cache__t *combined_cache;
+ apr_off_t start; /* The starting offset for the raw
+ svndiff/plaintext data minus header. */
+ apr_off_t off; /* The current offset into the file. */
+ apr_off_t end; /* The end offset of the raw data. */
+ int ver; /* If a delta, what svndiff version? */
+ int chunk_index;
+};
+
+/* See create_rep_state, which wraps this and adds another error. */
+static svn_error_t *
+create_rep_state_body(struct rep_state **rep_state,
+ struct rep_args **rep_args,
+ apr_file_t **file_hint,
+ svn_revnum_t *rev_hint,
+ representation_t *rep,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
+ struct rep_args *ra;
+ unsigned char buf[4];
+
+ /* If the hint is
+ * - given,
+ * - refers to a valid revision,
+ * - refers to a packed revision,
+ * - as does the rep we want to read, and
+ * - refers to the same pack file as the rep
+ * ...
+ */
+ if ( file_hint && rev_hint && *file_hint
+ && SVN_IS_VALID_REVNUM(*rev_hint)
+ && *rev_hint < ffd->min_unpacked_rev
+ && rep->revision < ffd->min_unpacked_rev
+ && ( (*rev_hint / ffd->max_files_per_dir)
+ == (rep->revision / ffd->max_files_per_dir)))
+ {
+ /* ... we can re-use the same, already open file object
+ */
+ apr_off_t offset;
+ SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool));
+
+ offset += rep->offset;
+ SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
+
+ rs->file = *file_hint;
+ }
+ else
+ {
+ /* otherwise, create a new file object
+ */
+ SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
+ }
+
+ /* remember the current file, if suggested by the caller */
+ if (file_hint)
+ *file_hint = rs->file;
+ if (rev_hint)
+ *rev_hint = rep->revision;
+
+ /* continue constructing RS and RA */
+ rs->window_cache = ffd->txdelta_window_cache;
+ rs->combined_cache = ffd->combined_window_cache;
+
+ SVN_ERR(read_rep_line(&ra, rs->file, pool));
+ SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
+ rs->off = rs->start;
+ rs->end = rs->start + rep->size;
+ *rep_state = rs;
+ *rep_args = ra;
+
+ if (!ra->is_delta)
+ /* This is a plaintext, so just return the current rep_state. */
+ return SVN_NO_ERROR;
+
+ /* We are dealing with a delta, find out what version. */
+ SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf),
+ NULL, NULL, pool));
+ /* ### Layering violation */
+ if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed svndiff data in representation"));
+ rs->ver = buf[3];
+ rs->chunk_index = 0;
+ rs->off += 4;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the rep args for REP in filesystem FS and create a rep_state
+ for reading the representation. Return the rep_state in *REP_STATE
+ and the rep args in *REP_ARGS, both allocated in POOL.
+
+ When reading multiple reps, i.e. a skip delta chain, you may provide
+ non-NULL FILE_HINT and REV_HINT. (If FILE_HINT is not NULL, in the first
+ call it should be a pointer to NULL.) The function will use these variables
+ to store the previous call results and tries to re-use them. This may
+ result in significant savings in I/O for packed files.
+ */
+static svn_error_t *
+create_rep_state(struct rep_state **rep_state,
+ struct rep_args **rep_args,
+ apr_file_t **file_hint,
+ svn_revnum_t *rev_hint,
+ representation_t *rep,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = create_rep_state_body(rep_state, rep_args,
+ file_hint, rev_hint,
+ rep, fs, pool);
+ if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* ### This always returns "-1" for transaction reps, because
+ ### this particular bit of code doesn't know if the rep is
+ ### stored in the protorev or in the mutable area (for props
+ ### or dir contents). It is pretty rare for FSFS to *read*
+ ### from the protorev file, though, so this is probably OK.
+ ### And anyone going to debug corruption errors is probably
+ ### going to jump straight to this comment anyway! */
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ "Corrupt representation '%s'",
+ rep
+ ? representation_string(rep, ffd->format, TRUE,
+ TRUE, pool)
+ : "(null)");
+ }
+ /* ### Call representation_string() ? */
+ return svn_error_trace(err);
+}
+
+struct rep_read_baton
+{
+ /* The FS from which we're reading. */
+ svn_fs_t *fs;
+
+ /* If not NULL, this is the base for the first delta window in rs_list */
+ svn_stringbuf_t *base_window;
+
+ /* The state of all prior delta representations. */
+ apr_array_header_t *rs_list;
+
+ /* The plaintext state, if there is a plaintext. */
+ struct rep_state *src_state;
+
+ /* The index of the current delta chunk, if we are reading a delta. */
+ int chunk_index;
+
+ /* The buffer where we store undeltified data. */
+ char *buf;
+ apr_size_t buf_pos;
+ apr_size_t buf_len;
+
+ /* A checksum context for summing the data read in order to verify it.
+ Note: we don't need to use the sha1 checksum because we're only doing
+ data verification, for which md5 is perfectly safe. */
+ svn_checksum_ctx_t *md5_checksum_ctx;
+
+ svn_boolean_t checksum_finalized;
+
+ /* The stored checksum of the representation we are reading, its
+ length, and the amount we've read so far. Some of this
+ information is redundant with rs_list and src_state, but it's
+ convenient for the checksumming code to have it here. */
+ svn_checksum_t *md5_checksum;
+
+ svn_filesize_t len;
+ svn_filesize_t off;
+
+ /* The key for the fulltext cache for this rep, if there is a
+ fulltext cache. */
+ pair_cache_key_t fulltext_cache_key;
+ /* The text we've been reading, if we're going to cache it. */
+ svn_stringbuf_t *current_fulltext;
+
+ /* Used for temporary allocations during the read. */
+ apr_pool_t *pool;
+
+ /* Pool used to store file handles and other data that is persistant
+ for the entire stream read. */
+ apr_pool_t *filehandle_pool;
+};
+
+/* Combine the name of the rev file in RS with the given OFFSET to form
+ * a cache lookup key. Allocations will be made from POOL. May return
+ * NULL if the key cannot be constructed. */
+static const char*
+get_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool)
+{
+ const char *name;
+ const char *last_part;
+ const char *name_last;
+
+ /* the rev file name containing the txdelta window.
+ * If this fails we are in serious trouble anyways.
+ * And if nobody else detects the problems, the file content checksum
+ * comparison _will_ find them.
+ */
+ if (apr_file_name_get(&name, rs->file))
+ return NULL;
+
+ /* Handle packed files as well by scanning backwards until we find the
+ * revision or pack number. */
+ name_last = name + strlen(name) - 1;
+ while (! svn_ctype_isdigit(*name_last))
+ --name_last;
+
+ last_part = name_last;
+ while (svn_ctype_isdigit(*last_part))
+ --last_part;
+
+ /* We must differentiate between packed files (as of today, the number
+ * is being followed by a dot) and non-packed files (followed by \0).
+ * Otherwise, there might be overlaps in the numbering range if the
+ * repo gets packed after caching the txdeltas of non-packed revs.
+ * => add the first non-digit char to the packed number. */
+ if (name_last[1] != '\0')
+ ++name_last;
+
+ /* copy one char MORE than the actual number to mark packed files,
+ * i.e. packed revision file content uses different key space then
+ * non-packed ones: keys for packed rev file content ends with a dot
+ * for non-packed rev files they end with a digit. */
+ name = apr_pstrndup(pool, last_part + 1, name_last - last_part);
+ return svn_fs_fs__combine_number_and_string(offset, name, pool);
+}
+
+/* Read the WINDOW_P for the rep state RS from the current FSFS session's
+ * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
+ * cache has been given. If a cache is available IS_CACHED will inform
+ * the caller about the success of the lookup. Allocations (of the window
+ * in particualar) will be made from POOL.
+ *
+ * If the information could be found, put RS and the position within the
+ * rev file into the same state as if the data had just been read from it.
+ */
+static svn_error_t *
+get_cached_window(svn_txdelta_window_t **window_p,
+ struct rep_state *rs,
+ svn_boolean_t *is_cached,
+ apr_pool_t *pool)
+{
+ if (! rs->window_cache)
+ {
+ /* txdelta window has not been enabled */
+ *is_cached = FALSE;
+ }
+ else
+ {
+ /* ask the cache for the desired txdelta window */
+ svn_fs_fs__txdelta_cached_window_t *cached_window;
+ SVN_ERR(svn_cache__get((void **) &cached_window,
+ is_cached,
+ rs->window_cache,
+ get_window_key(rs, rs->off, pool),
+ pool));
+
+ if (*is_cached)
+ {
+ /* found it. Pass it back to the caller. */
+ *window_p = cached_window->window;
+
+ /* manipulate the RS as if we just read the data */
+ rs->chunk_index++;
+ rs->off = cached_window->end_offset;
+
+ /* manipulate the rev file as if we just read from it */
+ SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Store the WINDOW read at OFFSET for the rep state RS in the current
+ * FSFS session's cache. This will be a no-op if no cache has been given.
+ * Temporary allocations will be made from SCRATCH_POOL. */
+static svn_error_t *
+set_cached_window(svn_txdelta_window_t *window,
+ struct rep_state *rs,
+ apr_off_t offset,
+ apr_pool_t *scratch_pool)
+{
+ if (rs->window_cache)
+ {
+ /* store the window and the first offset _past_ it */
+ svn_fs_fs__txdelta_cached_window_t cached_window;
+
+ cached_window.window = window;
+ cached_window.end_offset = rs->off;
+
+ /* but key it with the start offset because that is the known state
+ * when we will look it up */
+ return svn_cache__set(rs->window_cache,
+ get_window_key(rs, offset, scratch_pool),
+ &cached_window,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the WINDOW_P for the rep state RS from the current FSFS session's
+ * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
+ * cache has been given. If a cache is available IS_CACHED will inform
+ * the caller about the success of the lookup. Allocations (of the window
+ * in particualar) will be made from POOL.
+ */
+static svn_error_t *
+get_cached_combined_window(svn_stringbuf_t **window_p,
+ struct rep_state *rs,
+ svn_boolean_t *is_cached,
+ apr_pool_t *pool)
+{
+ if (! rs->combined_cache)
+ {
+ /* txdelta window has not been enabled */
+ *is_cached = FALSE;
+ }
+ else
+ {
+ /* ask the cache for the desired txdelta window */
+ return svn_cache__get((void **)window_p,
+ is_cached,
+ rs->combined_cache,
+ get_window_key(rs, rs->start, pool),
+ pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Store the WINDOW read at OFFSET for the rep state RS in the current
+ * FSFS session's cache. This will be a no-op if no cache has been given.
+ * Temporary allocations will be made from SCRATCH_POOL. */
+static svn_error_t *
+set_cached_combined_window(svn_stringbuf_t *window,
+ struct rep_state *rs,
+ apr_off_t offset,
+ apr_pool_t *scratch_pool)
+{
+ if (rs->combined_cache)
+ {
+ /* but key it with the start offset because that is the known state
+ * when we will look it up */
+ return svn_cache__set(rs->combined_cache,
+ get_window_key(rs, offset, scratch_pool),
+ window,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Build an array of rep_state structures in *LIST giving the delta
+ reps from first_rep to a plain-text or self-compressed rep. Set
+ *SRC_STATE to the plain-text rep we find at the end of the chain,
+ or to NULL if the final delta representation is self-compressed.
+ The representation to start from is designated by filesystem FS, id
+ ID, and representation REP.
+ Also, set *WINDOW_P to the base window content for *LIST, if it
+ could be found in cache. Otherwise, *LIST will contain the base
+ representation for the whole delta chain.
+ Finally, return the expanded size of the representation in
+ *EXPANDED_SIZE. It will take care of cases where only the on-disk
+ size is known. */
+static svn_error_t *
+build_rep_list(apr_array_header_t **list,
+ svn_stringbuf_t **window_p,
+ struct rep_state **src_state,
+ svn_filesize_t *expanded_size,
+ svn_fs_t *fs,
+ representation_t *first_rep,
+ apr_pool_t *pool)
+{
+ representation_t rep;
+ struct rep_state *rs = NULL;
+ struct rep_args *rep_args;
+ svn_boolean_t is_cached = FALSE;
+ apr_file_t *last_file = NULL;
+ svn_revnum_t last_revision;
+
+ *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
+ rep = *first_rep;
+
+ /* The value as stored in the data struct.
+ 0 is either for unknown length or actually zero length. */
+ *expanded_size = first_rep->expanded_size;
+
+ /* for the top-level rep, we need the rep_args */
+ SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
+ &last_revision, &rep, fs, pool));
+
+ /* Unknown size or empty representation?
+ That implies the this being the first iteration.
+ Usually size equals on-disk size, except for empty,
+ compressed representations (delta, size = 4).
+ Please note that for all non-empty deltas have
+ a 4-byte header _plus_ some data. */
+ if (*expanded_size == 0)
+ if (! rep_args->is_delta || first_rep->size != 4)
+ *expanded_size = first_rep->size;
+
+ while (1)
+ {
+ /* fetch state, if that has not been done already */
+ if (!rs)
+ SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
+ &last_revision, &rep, fs, pool));
+
+ SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
+ if (is_cached)
+ {
+ /* We already have a reconstructed window in our cache.
+ Write a pseudo rep_state with the full length. */
+ rs->off = rs->start;
+ rs->end = rs->start + (*window_p)->len;
+ *src_state = rs;
+ return SVN_NO_ERROR;
+ }
+
+ if (!rep_args->is_delta)
+ {
+ /* This is a plaintext, so just return the current rep_state. */
+ *src_state = rs;
+ return SVN_NO_ERROR;
+ }
+
+ /* Push this rep onto the list. If it's self-compressed, we're done. */
+ APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
+ if (rep_args->is_delta_vs_empty)
+ {
+ *src_state = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ rep.revision = rep_args->base_revision;
+ rep.offset = rep_args->base_offset;
+ rep.size = rep_args->base_length;
+ rep.txn_id = NULL;
+
+ rs = NULL;
+ }
+}
+
+
+/* Create a rep_read_baton structure for node revision NODEREV in
+ filesystem FS and store it in *RB_P. If FULLTEXT_CACHE_KEY is not
+ NULL, it is the rep's key in the fulltext cache, and a stringbuf
+ must be allocated to store the text. Perform all allocations in
+ POOL. If rep is mutable, it must be for file contents. */
+static svn_error_t *
+rep_read_get_baton(struct rep_read_baton **rb_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ pair_cache_key_t fulltext_cache_key,
+ apr_pool_t *pool)
+{
+ struct rep_read_baton *b;
+
+ b = apr_pcalloc(pool, sizeof(*b));
+ b->fs = fs;
+ b->base_window = NULL;
+ b->chunk_index = 0;
+ b->buf = NULL;
+ b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+ b->checksum_finalized = FALSE;
+ b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
+ b->len = rep->expanded_size;
+ b->off = 0;
+ b->fulltext_cache_key = fulltext_cache_key;
+ b->pool = svn_pool_create(pool);
+ b->filehandle_pool = svn_pool_create(pool);
+
+ SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
+ &b->src_state, &b->len, fs, rep,
+ b->filehandle_pool));
+
+ if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
+ b->current_fulltext = svn_stringbuf_create_ensure
+ ((apr_size_t)b->len,
+ b->filehandle_pool);
+ else
+ b->current_fulltext = NULL;
+
+ /* Save our output baton. */
+ *rb_p = b;
+
+ return SVN_NO_ERROR;
+}
+
+/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
+ window into *NWIN. */
+static svn_error_t *
+read_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
+ struct rep_state *rs, apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+ svn_boolean_t is_cached;
+ apr_off_t old_offset;
+
+ SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
+
+ /* RS->FILE may be shared between RS instances -> make sure we point
+ * to the right data. */
+ SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
+
+ /* Skip windows to reach the current chunk if we aren't there yet. */
+ while (rs->chunk_index < this_chunk)
+ {
+ SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
+ rs->chunk_index++;
+ SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
+ if (rs->off >= rs->end)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Reading one svndiff window read "
+ "beyond the end of the "
+ "representation"));
+ }
+
+ /* Read the next window. But first, try to find it in the cache. */
+ SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+
+ /* Actually read the next window. */
+ old_offset = rs->off;
+ stream = svn_stream_from_aprfile2(rs->file, TRUE, pool);
+ SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
+ rs->chunk_index++;
+ SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
+
+ if (rs->off > rs->end)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Reading one svndiff window read beyond "
+ "the end of the representation"));
+
+ /* the window has not been cached before, thus cache it now
+ * (if caching is used for them at all) */
+ return set_cached_window(*nwin, rs, old_offset, pool);
+}
+
+/* Read SIZE bytes from the representation RS and return it in *NWIN. */
+static svn_error_t *
+read_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs,
+ apr_size_t size, apr_pool_t *pool)
+{
+ /* RS->FILE may be shared between RS instances -> make sure we point
+ * to the right data. */
+ SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
+
+ /* Read the plain data. */
+ *nwin = svn_stringbuf_create_ensure(size, pool);
+ SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL,
+ pool));
+ (*nwin)->data[size] = 0;
+
+ /* Update RS. */
+ rs->off += (apr_off_t)size;
+
+ return SVN_NO_ERROR;
+}
+
+/* Get the undeltified window that is a result of combining all deltas
+ from the current desired representation identified in *RB with its
+ base representation. Store the window in *RESULT. */
+static svn_error_t *
+get_combined_window(svn_stringbuf_t **result,
+ struct rep_read_baton *rb)
+{
+ apr_pool_t *pool, *new_pool, *window_pool;
+ int i;
+ svn_txdelta_window_t *window;
+ apr_array_header_t *windows;
+ svn_stringbuf_t *source, *buf = rb->base_window;
+ struct rep_state *rs;
+
+ /* Read all windows that we need to combine. This is fine because
+ the size of each window is relatively small (100kB) and skip-
+ delta limits the number of deltas in a chain to well under 100.
+ Stop early if one of them does not depend on its predecessors. */
+ window_pool = svn_pool_create(rb->pool);
+ windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
+ for (i = 0; i < rb->rs_list->nelts; ++i)
+ {
+ rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
+ SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool));
+
+ APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
+ if (window->src_ops == 0)
+ {
+ ++i;
+ break;
+ }
+ }
+
+ /* Combine in the windows from the other delta reps. */
+ pool = svn_pool_create(rb->pool);
+ for (--i; i >= 0; --i)
+ {
+
+ rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
+ window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
+
+ /* Maybe, we've got a PLAIN start representation. If we do, read
+ as much data from it as the needed for the txdelta window's source
+ view.
+ Note that BUF / SOURCE may only be NULL in the first iteration. */
+ source = buf;
+ if (source == NULL && rb->src_state != NULL)
+ SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len,
+ pool));
+
+ /* Combine this window with the current one. */
+ new_pool = svn_pool_create(rb->pool);
+ buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
+ buf->len = window->tview_len;
+
+ svn_txdelta_apply_instructions(window, source ? source->data : NULL,
+ buf->data, &buf->len);
+ if (buf->len != window->tview_len)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("svndiff window length is "
+ "corrupt"));
+
+ /* Cache windows only if the whole rep content could be read as a
+ single chunk. Only then will no other chunk need a deeper RS
+ list than the cached chunk. */
+ if ((rb->chunk_index == 0) && (rs->off == rs->end))
+ SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
+
+ /* Cycle pools so that we only need to hold three windows at a time. */
+ svn_pool_destroy(pool);
+ pool = new_pool;
+ }
+
+ svn_pool_destroy(window_pool);
+
+ *result = buf;
+ return SVN_NO_ERROR;
+}
+
+/* Returns whether or not the expanded fulltext of the file is cachable
+ * based on its size SIZE. The decision depends on the cache used by RB.
+ */
+static svn_boolean_t
+fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
+{
+ return (size < APR_SIZE_MAX)
+ && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
+}
+
+/* Close method used on streams returned by read_representation().
+ */
+static svn_error_t *
+rep_read_contents_close(void *baton)
+{
+ struct rep_read_baton *rb = baton;
+
+ svn_pool_destroy(rb->pool);
+ svn_pool_destroy(rb->filehandle_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the next *LEN bytes of the rep and store them in *BUF. */
+static svn_error_t *
+get_contents(struct rep_read_baton *rb,
+ char *buf,
+ apr_size_t *len)
+{
+ apr_size_t copy_len, remaining = *len;
+ char *cur = buf;
+ struct rep_state *rs;
+
+ /* Special case for when there are no delta reps, only a plain
+ text. */
+ if (rb->rs_list->nelts == 0)
+ {
+ copy_len = remaining;
+ rs = rb->src_state;
+
+ if (rb->base_window != NULL)
+ {
+ /* We got the desired rep directly from the cache.
+ This is where we need the pseudo rep_state created
+ by build_rep_list(). */
+ apr_size_t offset = (apr_size_t)(rs->off - rs->start);
+ if (copy_len + offset > rb->base_window->len)
+ copy_len = offset < rb->base_window->len
+ ? rb->base_window->len - offset
+ : 0ul;
+
+ memcpy (cur, rb->base_window->data + offset, copy_len);
+ }
+ else
+ {
+ if (((apr_off_t) copy_len) > rs->end - rs->off)
+ copy_len = (apr_size_t) (rs->end - rs->off);
+ SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
+ NULL, rb->pool));
+ }
+
+ rs->off += copy_len;
+ *len = copy_len;
+ return SVN_NO_ERROR;
+ }
+
+ while (remaining > 0)
+ {
+ /* If we have buffered data from a previous chunk, use that. */
+ if (rb->buf)
+ {
+ /* Determine how much to copy from the buffer. */
+ copy_len = rb->buf_len - rb->buf_pos;
+ if (copy_len > remaining)
+ copy_len = remaining;
+
+ /* Actually copy the data. */
+ memcpy(cur, rb->buf + rb->buf_pos, copy_len);
+ rb->buf_pos += copy_len;
+ cur += copy_len;
+ remaining -= copy_len;
+
+ /* If the buffer is all used up, clear it and empty the
+ local pool. */
+ if (rb->buf_pos == rb->buf_len)
+ {
+ svn_pool_clear(rb->pool);
+ rb->buf = NULL;
+ }
+ }
+ else
+ {
+ svn_stringbuf_t *sbuf = NULL;
+
+ rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
+ if (rs->off == rs->end)
+ break;
+
+ /* Get more buffered data by evaluating a chunk. */
+ SVN_ERR(get_combined_window(&sbuf, rb));
+
+ rb->chunk_index++;
+ rb->buf_len = sbuf->len;
+ rb->buf = sbuf->data;
+ rb->buf_pos = 0;
+ }
+ }
+
+ *len = cur - buf;
+
+ return SVN_NO_ERROR;
+}
+
+/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
+ representation and store them in *BUF. Sum as we read and verify
+ the MD5 sum at the end. */
+static svn_error_t *
+rep_read_contents(void *baton,
+ char *buf,
+ apr_size_t *len)
+{
+ struct rep_read_baton *rb = baton;
+
+ /* Get the next block of data. */
+ SVN_ERR(get_contents(rb, buf, len));
+
+ if (rb->current_fulltext)
+ svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
+
+ /* Perform checksumming. We want to check the checksum as soon as
+ the last byte of data is read, in case the caller never performs
+ a short read, but we don't want to finalize the MD5 context
+ twice. */
+ if (!rb->checksum_finalized)
+ {
+ SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
+ rb->off += *len;
+ if (rb->off == rb->len)
+ {
+ svn_checksum_t *md5_checksum;
+
+ rb->checksum_finalized = TRUE;
+ SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
+ rb->pool));
+ if (!svn_checksum_match(md5_checksum, rb->md5_checksum))
+ return svn_error_create(SVN_ERR_FS_CORRUPT,
+ svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum,
+ rb->pool,
+ _("Checksum mismatch while reading representation")),
+ NULL);
+ }
+ }
+
+ if (rb->off == rb->len && rb->current_fulltext)
+ {
+ fs_fs_data_t *ffd = rb->fs->fsap_data;
+ SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
+ rb->current_fulltext, rb->pool));
+ rb->current_fulltext = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return a stream in *CONTENTS_P that will read the contents of a
+ representation stored at the location given by REP. Appropriate
+ for any kind of immutable representation, but only for file
+ contents (not props or directory contents) in mutable
+ representations.
+
+ If REP is NULL, the representation is assumed to be empty, and the
+ empty stream is returned.
+*/
+static svn_error_t *
+read_representation(svn_stream_t **contents_p,
+ svn_fs_t *fs,
+ representation_t *rep,
+ apr_pool_t *pool)
+{
+ if (! rep)
+ {
+ *contents_p = svn_stream_empty(pool);
+ }
+ else
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ pair_cache_key_t fulltext_cache_key = { 0 };
+ svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
+ struct rep_read_baton *rb;
+
+ fulltext_cache_key.revision = rep->revision;
+ fulltext_cache_key.second = rep->offset;
+ if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
+ && fulltext_size_is_cachable(ffd, len))
+ {
+ svn_stringbuf_t *fulltext;
+ svn_boolean_t is_cached;
+ SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
+ ffd->fulltext_cache, &fulltext_cache_key,
+ pool));
+ if (is_cached)
+ {
+ *contents_p = svn_stream_from_stringbuf(fulltext, pool);
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ fulltext_cache_key.revision = SVN_INVALID_REVNUM;
+
+ SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
+
+ *contents_p = svn_stream_create(rb, pool);
+ svn_stream_set_read(*contents_p, rep_read_contents);
+ svn_stream_set_close(*contents_p, rep_read_contents_close);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_contents(svn_stream_t **contents_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ return read_representation(contents_p, fs, noderev->data_rep, pool);
+}
+
+/* Baton used when reading delta windows. */
+struct delta_read_baton
+{
+ struct rep_state *rs;
+ svn_checksum_t *checksum;
+};
+
+/* This implements the svn_txdelta_next_window_fn_t interface. */
+static svn_error_t *
+delta_read_next_window(svn_txdelta_window_t **window, void *baton,
+ apr_pool_t *pool)
+{
+ struct delta_read_baton *drb = baton;
+
+ if (drb->rs->off == drb->rs->end)
+ {
+ *window = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool);
+}
+
+/* This implements the svn_txdelta_md5_digest_fn_t interface. */
+static const unsigned char *
+delta_read_md5_digest(void *baton)
+{
+ struct delta_read_baton *drb = baton;
+
+ if (drb->checksum->kind == svn_checksum_md5)
+ return drb->checksum->digest;
+ else
+ return NULL;
+}
+
+svn_error_t *
+svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_t *fs,
+ node_revision_t *source,
+ node_revision_t *target,
+ apr_pool_t *pool)
+{
+ svn_stream_t *source_stream, *target_stream;
+
+ /* Try a shortcut: if the target is stored as a delta against the source,
+ then just use that delta. */
+ if (source && source->data_rep && target->data_rep)
+ {
+ struct rep_state *rep_state;
+ struct rep_args *rep_args;
+
+ /* Read target's base rep if any. */
+ SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
+ target->data_rep, fs, pool));
+ /* If that matches source, then use this delta as is. */
+ if (rep_args->is_delta
+ && (rep_args->is_delta_vs_empty
+ || (rep_args->base_revision == source->data_rep->revision
+ && rep_args->base_offset == source->data_rep->offset)))
+ {
+ /* Create the delta read baton. */
+ struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
+ drb->rs = rep_state;
+ drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum,
+ pool);
+ *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
+ delta_read_md5_digest, pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(svn_io_file_close(rep_state->file, pool));
+ }
+
+ /* Read both fulltexts and construct a delta. */
+ if (source)
+ SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
+ else
+ source_stream = svn_stream_empty(pool);
+ SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
+
+ /* Because source and target stream will already verify their content,
+ * there is no need to do this once more. In particular if the stream
+ * content is being fetched from cache. */
+ svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for cache_access_wrapper. Wraps the original parameters of
+ * svn_fs_fs__try_process_file_content().
+ */
+typedef struct cache_access_wrapper_baton_t
+{
+ svn_fs_process_contents_func_t func;
+ void* baton;
+} cache_access_wrapper_baton_t;
+
+/* Wrapper to translate between svn_fs_process_contents_func_t and
+ * svn_cache__partial_getter_func_t.
+ */
+static svn_error_t *
+cache_access_wrapper(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ cache_access_wrapper_baton_t *wrapper_baton = baton;
+
+ SVN_ERR(wrapper_baton->func((const unsigned char *)data,
+ data_len - 1, /* cache adds terminating 0 */
+ wrapper_baton->baton,
+ pool));
+
+ /* non-NULL value to signal the calling cache that all went well */
+ *out = baton;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool)
+{
+ representation_t *rep = noderev->data_rep;
+ if (rep)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ pair_cache_key_t fulltext_cache_key = { 0 };
+
+ fulltext_cache_key.revision = rep->revision;
+ fulltext_cache_key.second = rep->offset;
+ if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
+ && fulltext_size_is_cachable(ffd, rep->expanded_size))
+ {
+ cache_access_wrapper_baton_t wrapper_baton;
+ void *dummy = NULL;
+
+ wrapper_baton.func = processor;
+ wrapper_baton.baton = baton;
+ return svn_cache__get_partial(&dummy, success,
+ ffd->fulltext_cache,
+ &fulltext_cache_key,
+ cache_access_wrapper,
+ &wrapper_baton,
+ pool);
+ }
+ }
+
+ *success = FALSE;
+ return SVN_NO_ERROR;
+}
+
+/* Fetch the contents of a directory into ENTRIES. Values are stored
+ as filename to string mappings; further conversion is necessary to
+ convert them into svn_fs_dirent_t values. */
+static svn_error_t *
+get_dir_contents(apr_hash_t *entries,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ svn_stream_t *contents;
+
+ if (noderev->data_rep && noderev->data_rep->txn_id)
+ {
+ const char *filename = path_txn_node_children(fs, noderev->id, pool);
+
+ /* The representation is mutable. Read the old directory
+ contents from the mutable children file, followed by the
+ changes we've made in this transaction. */
+ SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
+ SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
+ SVN_ERR(svn_stream_close(contents));
+ }
+ else if (noderev->data_rep)
+ {
+ /* use a temporary pool for temp objects.
+ * Also undeltify content before parsing it. Otherwise, we could only
+ * parse it byte-by-byte.
+ */
+ apr_pool_t *text_pool = svn_pool_create(pool);
+ apr_size_t len = noderev->data_rep->expanded_size
+ ? (apr_size_t)noderev->data_rep->expanded_size
+ : (apr_size_t)noderev->data_rep->size;
+ svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
+ text->len = len;
+
+ /* The representation is immutable. Read it normally. */
+ SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool));
+ SVN_ERR(svn_stream_read(contents, text->data, &text->len));
+ SVN_ERR(svn_stream_close(contents));
+
+ /* de-serialize hash */
+ contents = svn_stream_from_stringbuf(text, text_pool);
+ SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
+
+ svn_pool_destroy(text_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static const char *
+unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "%s %s",
+ (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
+ svn_fs_fs__id_unparse(id, pool)->data);
+}
+
+/* Given a hash ENTRIES of dirent structions, return a hash in
+ *STR_ENTRIES_P, that has svn_string_t as the values in the format
+ specified by the fs_fs directory contents file. Perform
+ allocations in POOL. */
+static svn_error_t *
+unparse_dir_entries(apr_hash_t **str_entries_p,
+ apr_hash_t *entries,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ /* For now, we use a our own hash function to ensure that we get a
+ * (largely) stable order when serializing the data. It also gives
+ * us some performance improvement.
+ *
+ * ### TODO ###
+ * Use some sorted or other fixed order data container.
+ */
+ *str_entries_p = svn_hash__make(pool);
+
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
+ const char *new_val;
+
+ apr_hash_this(hi, &key, &klen, NULL);
+ new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
+ apr_hash_set(*str_entries_p, key, klen,
+ svn_string_create(new_val, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Given a hash STR_ENTRIES with values as svn_string_t as specified
+ in an FSFS directory contents listing, return a hash of dirents in
+ *ENTRIES_P. Perform allocations in POOL. */
+static svn_error_t *
+parse_dir_entries(apr_hash_t **entries_p,
+ apr_hash_t *str_entries,
+ const char *unparsed_id,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ *entries_p = apr_hash_make(pool);
+
+ /* Translate the string dir entries into real entries. */
+ for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_string_t *str_val = svn__apr_hash_index_val(hi);
+ char *str, *last_str;
+ svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
+
+ last_str = apr_pstrdup(pool, str_val->data);
+ dirent->name = apr_pstrdup(pool, name);
+
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt in '%s'"),
+ unparsed_id);
+
+ if (strcmp(str, KIND_FILE) == 0)
+ {
+ dirent->kind = svn_node_file;
+ }
+ else if (strcmp(str, KIND_DIR) == 0)
+ {
+ dirent->kind = svn_node_dir;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt in '%s'"),
+ unparsed_id);
+ }
+
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt in '%s'"),
+ unparsed_id);
+
+ dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
+
+ svn_hash_sets(*entries_p, dirent->name, dirent);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the cache object in FS responsible to storing the directory
+ * the NODEREV. If none exists, return NULL. */
+static svn_cache__t *
+locate_dir_cache(svn_fs_t *fs,
+ node_revision_t *noderev)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ return svn_fs_fs__id_txn_id(noderev->id)
+ ? ffd->txn_dir_cache
+ : ffd->dir_cache;
+}
+
+svn_error_t *
+svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ const char *unparsed_id = NULL;
+ apr_hash_t *unparsed_entries, *parsed_entries;
+
+ /* find the cache we may use */
+ svn_cache__t *cache = locate_dir_cache(fs, noderev);
+ if (cache)
+ {
+ svn_boolean_t found;
+
+ unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
+ SVN_ERR(svn_cache__get((void **) entries_p, &found, cache,
+ unparsed_id, pool));
+ if (found)
+ return SVN_NO_ERROR;
+ }
+
+ /* Read in the directory hash. */
+ unparsed_entries = apr_hash_make(pool);
+ SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
+ SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
+ unparsed_id, pool));
+
+ /* Update the cache, if we are to use one. */
+ if (cache)
+ SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool));
+
+ *entries_p = parsed_entries;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t found = FALSE;
+
+ /* find the cache we may use */
+ svn_cache__t *cache = locate_dir_cache(fs, noderev);
+ if (cache)
+ {
+ const char *unparsed_id =
+ svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data;
+
+ /* Cache lookup. */
+ SVN_ERR(svn_cache__get_partial((void **)dirent,
+ &found,
+ cache,
+ unparsed_id,
+ svn_fs_fs__extract_dir_entry,
+ (void*)name,
+ result_pool));
+ }
+
+ /* fetch data from disk if we did not find it in the cache */
+ if (! found)
+ {
+ apr_hash_t *entries;
+ svn_fs_dirent_t *entry;
+ svn_fs_dirent_t *entry_copy = NULL;
+
+ /* read the dir from the file system. It will probably be put it
+ into the cache for faster lookup in future calls. */
+ SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
+ scratch_pool));
+
+ /* find desired entry and return a copy in POOL, if found */
+ entry = svn_hash_gets(entries, name);
+ if (entry != NULL)
+ {
+ entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
+ entry_copy->name = apr_pstrdup(result_pool, entry->name);
+ entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool);
+ entry_copy->kind = entry->kind;
+ }
+
+ *dirent = entry_copy;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ apr_hash_t *proplist;
+ svn_stream_t *stream;
+
+ if (noderev->prop_rep && noderev->prop_rep->txn_id)
+ {
+ const char *filename = path_txn_node_props(fs, noderev->id, pool);
+ proplist = apr_hash_make(pool);
+
+ SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
+ SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+ }
+ else if (noderev->prop_rep)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ representation_t *rep = noderev->prop_rep;
+ pair_cache_key_t key = { 0 };
+
+ key.revision = rep->revision;
+ key.second = rep->offset;
+ if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
+ {
+ svn_boolean_t is_cached;
+ SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
+ ffd->properties_cache, &key, pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+ }
+
+ proplist = apr_hash_make(pool);
+ SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
+ SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
+ SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
+ }
+ else
+ {
+ /* return an empty prop list if the node doesn't have any props */
+ proplist = apr_hash_make(pool);
+ }
+
+ *proplist_p = proplist;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__file_length(svn_filesize_t *length,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ if (noderev->data_rep)
+ *length = noderev->data_rep->expanded_size;
+ else
+ *length = 0;
+
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_fs_fs__noderev_same_rep_key(representation_t *a,
+ representation_t *b)
+{
+ if (a == b)
+ return TRUE;
+
+ if (a == NULL || b == NULL)
+ return FALSE;
+
+ if (a->offset != b->offset)
+ return FALSE;
+
+ if (a->revision != b->revision)
+ return FALSE;
+
+ if (a->uniquifier == b->uniquifier)
+ return TRUE;
+
+ if (a->uniquifier == NULL || b->uniquifier == NULL)
+ return FALSE;
+
+ return strcmp(a->uniquifier, b->uniquifier) == 0;
+}
+
+svn_error_t *
+svn_fs_fs__file_checksum(svn_checksum_t **checksum,
+ node_revision_t *noderev,
+ svn_checksum_kind_t kind,
+ apr_pool_t *pool)
+{
+ if (noderev->data_rep)
+ {
+ switch(kind)
+ {
+ case svn_checksum_md5:
+ *checksum = svn_checksum_dup(noderev->data_rep->md5_checksum,
+ pool);
+ break;
+ case svn_checksum_sha1:
+ *checksum = svn_checksum_dup(noderev->data_rep->sha1_checksum,
+ pool);
+ break;
+ default:
+ *checksum = NULL;
+ }
+ }
+ else
+ *checksum = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+representation_t *
+svn_fs_fs__rep_copy(representation_t *rep,
+ apr_pool_t *pool)
+{
+ representation_t *rep_new;
+
+ if (rep == NULL)
+ return NULL;
+
+ rep_new = apr_pcalloc(pool, sizeof(*rep_new));
+
+ memcpy(rep_new, rep, sizeof(*rep_new));
+ rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
+ rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
+ rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier);
+
+ return rep_new;
+}
+
+/* Merge the internal-use-only CHANGE into a hash of public-FS
+ svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
+ single summarical (is that real word?) change per path. Also keep
+ the COPYFROM_CACHE up to date with new adds and replaces. */
+static svn_error_t *
+fold_change(apr_hash_t *changes,
+ const change_t *change,
+ apr_hash_t *copyfrom_cache)
+{
+ apr_pool_t *pool = apr_hash_pool_get(changes);
+ svn_fs_path_change2_t *old_change, *new_change;
+ const char *path;
+ apr_size_t path_len = strlen(change->path);
+
+ if ((old_change = apr_hash_get(changes, change->path, path_len)))
+ {
+ /* This path already exists in the hash, so we have to merge
+ this change into the already existing one. */
+
+ /* Sanity check: only allow NULL node revision ID in the
+ `reset' case. */
+ if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Missing required node revision ID"));
+
+ /* Sanity check: we should be talking about the same node
+ revision ID as our last change except where the last change
+ was a deletion. */
+ if (change->noderev_id
+ && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
+ && (old_change->change_kind != svn_fs_path_change_delete))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: new node revision ID "
+ "without delete"));
+
+ /* Sanity check: an add, replacement, or reset must be the first
+ thing to follow a deletion. */
+ if ((old_change->change_kind == svn_fs_path_change_delete)
+ && (! ((change->kind == svn_fs_path_change_replace)
+ || (change->kind == svn_fs_path_change_reset)
+ || (change->kind == svn_fs_path_change_add))))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: non-add change on deleted path"));
+
+ /* Sanity check: an add can't follow anything except
+ a delete or reset. */
+ if ((change->kind == svn_fs_path_change_add)
+ && (old_change->change_kind != svn_fs_path_change_delete)
+ && (old_change->change_kind != svn_fs_path_change_reset))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: add change on preexisting path"));
+
+ /* Now, merge that change in. */
+ switch (change->kind)
+ {
+ case svn_fs_path_change_reset:
+ /* A reset here will simply remove the path change from the
+ hash. */
+ old_change = NULL;
+ break;
+
+ case svn_fs_path_change_delete:
+ if (old_change->change_kind == svn_fs_path_change_add)
+ {
+ /* If the path was introduced in this transaction via an
+ add, and we are deleting it, just remove the path
+ altogether. */
+ old_change = NULL;
+ }
+ else
+ {
+ /* A deletion overrules all previous changes. */
+ old_change->change_kind = svn_fs_path_change_delete;
+ old_change->text_mod = change->text_mod;
+ old_change->prop_mod = change->prop_mod;
+ old_change->copyfrom_rev = SVN_INVALID_REVNUM;
+ old_change->copyfrom_path = NULL;
+ }
+ break;
+
+ case svn_fs_path_change_add:
+ case svn_fs_path_change_replace:
+ /* An add at this point must be following a previous delete,
+ so treat it just like a replace. */
+ old_change->change_kind = svn_fs_path_change_replace;
+ old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
+ pool);
+ old_change->text_mod = change->text_mod;
+ old_change->prop_mod = change->prop_mod;
+ if (change->copyfrom_rev == SVN_INVALID_REVNUM)
+ {
+ old_change->copyfrom_rev = SVN_INVALID_REVNUM;
+ old_change->copyfrom_path = NULL;
+ }
+ else
+ {
+ old_change->copyfrom_rev = change->copyfrom_rev;
+ old_change->copyfrom_path = apr_pstrdup(pool,
+ change->copyfrom_path);
+ }
+ break;
+
+ case svn_fs_path_change_modify:
+ default:
+ if (change->text_mod)
+ old_change->text_mod = TRUE;
+ if (change->prop_mod)
+ old_change->prop_mod = TRUE;
+ break;
+ }
+
+ /* Point our new_change to our (possibly modified) old_change. */
+ new_change = old_change;
+ }
+ else
+ {
+ /* This change is new to the hash, so make a new public change
+ structure from the internal one (in the hash's pool), and dup
+ the path into the hash's pool, too. */
+ new_change = apr_pcalloc(pool, sizeof(*new_change));
+ new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
+ new_change->change_kind = change->kind;
+ new_change->text_mod = change->text_mod;
+ new_change->prop_mod = change->prop_mod;
+ /* In FSFS, copyfrom_known is *always* true, since we've always
+ * stored copyfroms in changed paths lists. */
+ new_change->copyfrom_known = TRUE;
+ if (change->copyfrom_rev != SVN_INVALID_REVNUM)
+ {
+ new_change->copyfrom_rev = change->copyfrom_rev;
+ new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path);
+ }
+ else
+ {
+ new_change->copyfrom_rev = SVN_INVALID_REVNUM;
+ new_change->copyfrom_path = NULL;
+ }
+ }
+
+ if (new_change)
+ new_change->node_kind = change->node_kind;
+
+ /* Add (or update) this path.
+
+ Note: this key might already be present, and it would be nice to
+ re-use its value, but there is no way to fetch it. The API makes no
+ guarantees that this (new) key will not be retained. Thus, we (again)
+ copy the key into the target pool to ensure a proper lifetime. */
+ path = apr_pstrmemdup(pool, change->path, path_len);
+ apr_hash_set(changes, path, path_len, new_change);
+
+ /* Update the copyfrom cache, if any. */
+ if (copyfrom_cache)
+ {
+ apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache);
+ const char *copyfrom_string = NULL, *copyfrom_key = path;
+ if (new_change)
+ {
+ if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev))
+ copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
+ new_change->copyfrom_rev,
+ new_change->copyfrom_path);
+ else
+ copyfrom_string = "";
+ }
+ /* We need to allocate a copy of the key in the copyfrom_pool if
+ * we're not doing a deletion and if it isn't already there. */
+ if ( copyfrom_string
+ && ( ! apr_hash_count(copyfrom_cache)
+ || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len)))
+ copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len);
+
+ apr_hash_set(copyfrom_cache, copyfrom_key, path_len,
+ copyfrom_string);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* The 256 is an arbitrary size large enough to hold the node id and the
+ * various flags. */
+#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
+
+/* Read the next entry in the changes record from file FILE and store
+ the resulting change in *CHANGE_P. If there is no next record,
+ store NULL there. Perform all allocations from POOL. */
+static svn_error_t *
+read_change(change_t **change_p,
+ apr_file_t *file,
+ apr_pool_t *pool)
+{
+ char buf[MAX_CHANGE_LINE_LEN];
+ apr_size_t len = sizeof(buf);
+ change_t *change;
+ char *str, *last_str = buf, *kind_str;
+ svn_error_t *err;
+
+ /* Default return value. */
+ *change_p = NULL;
+
+ err = svn_io_read_length_line(file, buf, &len, pool);
+
+ /* Check for a blank line. */
+ if (err || (len == 0))
+ {
+ if (err && APR_STATUS_IS_EOF(err->apr_err))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ if ((len == 0) && (! err))
+ return SVN_NO_ERROR;
+ return svn_error_trace(err);
+ }
+
+ change = apr_pcalloc(pool, sizeof(*change));
+
+ /* Get the node-id of the change. */
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid changes line in rev-file"));
+
+ change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
+ if (change->noderev_id == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid changes line in rev-file"));
+
+ /* Get the change type. */
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid changes line in rev-file"));
+
+ /* Don't bother to check the format number before looking for
+ * node-kinds: just read them if you find them. */
+ change->node_kind = svn_node_unknown;
+ kind_str = strchr(str, '-');
+ if (kind_str)
+ {
+ /* Cap off the end of "str" (the action). */
+ *kind_str = '\0';
+ kind_str++;
+ if (strcmp(kind_str, KIND_FILE) == 0)
+ change->node_kind = svn_node_file;
+ else if (strcmp(kind_str, KIND_DIR) == 0)
+ change->node_kind = svn_node_dir;
+ else
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid changes line in rev-file"));
+ }
+
+ if (strcmp(str, ACTION_MODIFY) == 0)
+ {
+ change->kind = svn_fs_path_change_modify;
+ }
+ else if (strcmp(str, ACTION_ADD) == 0)
+ {
+ change->kind = svn_fs_path_change_add;
+ }
+ else if (strcmp(str, ACTION_DELETE) == 0)
+ {
+ change->kind = svn_fs_path_change_delete;
+ }
+ else if (strcmp(str, ACTION_REPLACE) == 0)
+ {
+ change->kind = svn_fs_path_change_replace;
+ }
+ else if (strcmp(str, ACTION_RESET) == 0)
+ {
+ change->kind = svn_fs_path_change_reset;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change kind in rev file"));
+ }
+
+ /* Get the text-mod flag. */
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid changes line in rev-file"));
+
+ if (strcmp(str, FLAG_TRUE) == 0)
+ {
+ change->text_mod = TRUE;
+ }
+ else if (strcmp(str, FLAG_FALSE) == 0)
+ {
+ change->text_mod = FALSE;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid text-mod flag in rev-file"));
+ }
+
+ /* Get the prop-mod flag. */
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid changes line in rev-file"));
+
+ if (strcmp(str, FLAG_TRUE) == 0)
+ {
+ change->prop_mod = TRUE;
+ }
+ else if (strcmp(str, FLAG_FALSE) == 0)
+ {
+ change->prop_mod = FALSE;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid prop-mod flag in rev-file"));
+ }
+
+ /* Get the changed path. */
+ change->path = apr_pstrdup(pool, last_str);
+
+
+ /* Read the next line, the copyfrom line. */
+ len = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
+
+ if (len == 0)
+ {
+ change->copyfrom_rev = SVN_INVALID_REVNUM;
+ change->copyfrom_path = NULL;
+ }
+ else
+ {
+ last_str = buf;
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (! str)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid changes line in rev-file"));
+ change->copyfrom_rev = SVN_STR_TO_REV(str);
+
+ if (! last_str)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid changes line in rev-file"));
+
+ change->copyfrom_path = apr_pstrdup(pool, last_str);
+ }
+
+ *change_p = change;
+
+ return SVN_NO_ERROR;
+}
+
+/* Examine all the changed path entries in CHANGES and store them in
+ *CHANGED_PATHS. Folding is done to remove redundant or unnecessary
+ *data. Store a hash of paths to copyfrom "REV PATH" strings in
+ COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that
+ the changed-path entries have already been folded (by
+ write_final_changed_path_info) and may be out of order, so we shouldn't
+ remove children of replaced or deleted directories. Do all
+ allocations in POOL. */
+static svn_error_t *
+process_changes(apr_hash_t *changed_paths,
+ apr_hash_t *copyfrom_cache,
+ apr_array_header_t *changes,
+ svn_boolean_t prefolded,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ /* Read in the changes one by one, folding them into our local hash
+ as necessary. */
+
+ for (i = 0; i < changes->nelts; ++i)
+ {
+ change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
+
+ SVN_ERR(fold_change(changed_paths, change, copyfrom_cache));
+
+ /* Now, if our change was a deletion or replacement, we have to
+ blow away any changes thus far on paths that are (or, were)
+ children of this path.
+ ### i won't bother with another iteration pool here -- at
+ most we talking about a few extra dups of paths into what
+ is already a temporary subpool.
+ */
+
+ if (((change->kind == svn_fs_path_change_delete)
+ || (change->kind == svn_fs_path_change_replace))
+ && ! prefolded)
+ {
+ apr_hash_index_t *hi;
+
+ /* a potential child path must contain at least 2 more chars
+ (the path separator plus at least one char for the name).
+ Also, we should not assume that all paths have been normalized
+ i.e. some might have trailing path separators.
+ */
+ apr_ssize_t change_path_len = strlen(change->path);
+ apr_ssize_t min_child_len = change_path_len == 0
+ ? 1
+ : change->path[change_path_len-1] == '/'
+ ? change_path_len + 1
+ : change_path_len + 2;
+
+ /* CAUTION: This is the inner loop of an O(n^2) algorithm.
+ The number of changes to process may be >> 1000.
+ Therefore, keep the inner loop as tight as possible.
+ */
+ for (hi = apr_hash_first(iterpool, changed_paths);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ /* KEY is the path. */
+ const void *path;
+ apr_ssize_t klen;
+ apr_hash_this(hi, &path, &klen, NULL);
+
+ /* If we come across a child of our path, remove it.
+ Call svn_dirent_is_child only if there is a chance that
+ this is actually a sub-path.
+ */
+ if ( klen >= min_child_len
+ && svn_dirent_is_child(change->path, path, iterpool))
+ apr_hash_set(changed_paths, path, klen, NULL);
+ }
+ }
+
+ /* Clear the per-iteration subpool. */
+ svn_pool_clear(iterpool);
+ }
+
+ /* Destroy the per-iteration subpool. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Fetch all the changes from FILE and store them in *CHANGES. Do all
+ allocations in POOL. */
+static svn_error_t *
+read_all_changes(apr_array_header_t **changes,
+ apr_file_t *file,
+ apr_pool_t *pool)
+{
+ change_t *change;
+
+ /* pre-allocate enough room for most change lists
+ (will be auto-expanded as necessary) */
+ *changes = apr_array_make(pool, 30, sizeof(change_t *));
+
+ SVN_ERR(read_change(&change, file, pool));
+ while (change)
+ {
+ APR_ARRAY_PUSH(*changes, change_t*) = change;
+ SVN_ERR(read_change(&change, file, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ apr_file_t *file;
+ apr_hash_t *changed_paths = apr_hash_make(pool);
+ apr_array_header_t *changes;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+
+ SVN_ERR(read_all_changes(&changes, file, scratch_pool));
+ SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool));
+ svn_pool_destroy(scratch_pool);
+
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ *changed_paths_p = changed_paths;
+
+ return SVN_NO_ERROR;
+}
+
+/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
+ * Allocate the result in POOL.
+ */
+static svn_error_t *
+get_changes(apr_array_header_t **changes,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ apr_off_t changes_offset;
+ apr_file_t *revision_file;
+ svn_boolean_t found;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* try cache lookup first */
+
+ if (ffd->changes_cache)
+ {
+ SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
+ &rev, pool));
+ if (found)
+ return SVN_NO_ERROR;
+ }
+
+ /* read changes from revision file */
+
+ SVN_ERR(ensure_revision_exists(fs, rev, pool));
+
+ SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
+
+ SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs,
+ rev, pool));
+
+ SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
+ SVN_ERR(read_all_changes(changes, revision_file, pool));
+
+ SVN_ERR(svn_io_file_close(revision_file, pool));
+
+ /* cache for future reference */
+
+ if (ffd->changes_cache)
+ SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_hash_t *copyfrom_cache,
+ apr_pool_t *pool)
+{
+ apr_hash_t *changed_paths;
+ apr_array_header_t *changes;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(get_changes(&changes, fs, rev, scratch_pool));
+
+ changed_paths = svn_hash__make(pool);
+
+ SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes,
+ TRUE, pool));
+ svn_pool_destroy(scratch_pool);
+
+ *changed_paths_p = changed_paths;
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy a revision node-rev SRC into the current transaction TXN_ID in
+ the filesystem FS. This is only used to create the root of a transaction.
+ Allocations are from POOL. */
+static svn_error_t *
+create_new_txn_noderev_from_rev(svn_fs_t *fs,
+ const char *txn_id,
+ svn_fs_id_t *src,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ const char *node_id, *copy_id;
+
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
+
+ if (svn_fs_fs__id_txn_id(noderev->id))
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Copying from transactions not allowed"));
+
+ noderev->predecessor_id = noderev->id;
+ noderev->predecessor_count++;
+ noderev->copyfrom_path = NULL;
+ noderev->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* For the transaction root, the copyroot never changes. */
+
+ node_id = svn_fs_fs__id_node_id(noderev->id);
+ copy_id = svn_fs_fs__id_copy_id(noderev->id);
+ noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
+
+ return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
+}
+
+/* A structure used by get_and_increment_txn_key_body(). */
+struct get_and_increment_txn_key_baton {
+ svn_fs_t *fs;
+ char *txn_id;
+ apr_pool_t *pool;
+};
+
+/* Callback used in the implementation of create_txn_dir(). This gets
+ the current base 36 value in PATH_TXN_CURRENT and increments it.
+ It returns the original value by the baton. */
+static svn_error_t *
+get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
+{
+ struct get_and_increment_txn_key_baton *cb = baton;
+ const char *txn_current_filename = path_txn_current(cb->fs, pool);
+ const char *tmp_filename;
+ char next_txn_id[MAX_KEY_SIZE+3];
+ apr_size_t len;
+
+ svn_stringbuf_t *buf;
+ SVN_ERR(read_content(&buf, txn_current_filename, cb->pool));
+
+ /* remove trailing newlines */
+ svn_stringbuf_strip_whitespace(buf);
+ cb->txn_id = buf->data;
+ len = buf->len;
+
+ /* Increment the key and add a trailing \n to the string so the
+ txn-current file has a newline in it. */
+ svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
+ next_txn_id[len] = '\n';
+ ++len;
+ next_txn_id[len] = '\0';
+
+ SVN_ERR(svn_io_write_unique(&tmp_filename,
+ svn_dirent_dirname(txn_current_filename, pool),
+ next_txn_id, len, svn_io_file_del_none, pool));
+ SVN_ERR(move_into_place(tmp_filename, txn_current_filename,
+ txn_current_filename, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Create a unique directory for a transaction in FS based on revision
+ REV. Return the ID for this transaction in *ID_P. Use a sequence
+ value in the transaction ID to prevent reuse of transaction IDs. */
+static svn_error_t *
+create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ struct get_and_increment_txn_key_baton cb;
+ const char *txn_dir;
+
+ /* Get the current transaction sequence value, which is a base-36
+ number, from the txn-current file, and write an
+ incremented value back out to the file. Place the revision
+ number the transaction is based off into the transaction id. */
+ cb.pool = pool;
+ cb.fs = fs;
+ SVN_ERR(with_txn_current_lock(fs,
+ get_and_increment_txn_key_body,
+ &cb,
+ pool));
+ *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
+
+ txn_dir = svn_dirent_join_many(pool,
+ fs->path,
+ PATH_TXNS_DIR,
+ apr_pstrcat(pool, *id_p, PATH_EXT_TXN,
+ (char *)NULL),
+ NULL);
+
+ return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
+}
+
+/* Create a unique directory for a transaction in FS based on revision
+ REV. Return the ID for this transaction in *ID_P. This
+ implementation is used in svn 1.4 and earlier repositories and is
+ kept in 1.5 and greater to support the --pre-1.4-compatible and
+ --pre-1.5-compatible repository creation options. Reused
+ transaction IDs are possible with this implementation. */
+static svn_error_t *
+create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ unsigned int i;
+ apr_pool_t *subpool;
+ const char *unique_path, *prefix;
+
+ /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
+ prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
+ apr_psprintf(pool, "%ld", rev), NULL);
+
+ subpool = svn_pool_create(pool);
+ for (i = 1; i <= 99999; i++)
+ {
+ svn_error_t *err;
+
+ svn_pool_clear(subpool);
+ unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
+ err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
+ if (! err)
+ {
+ /* We succeeded. Return the basename minus the ".txn" extension. */
+ const char *name = svn_dirent_basename(unique_path, subpool);
+ *id_p = apr_pstrndup(pool, name,
+ strlen(name) - strlen(PATH_EXT_TXN));
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+ }
+ if (! APR_STATUS_IS_EEXIST(err->apr_err))
+ return svn_error_trace(err);
+ svn_error_clear(err);
+ }
+
+ return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
+ NULL,
+ _("Unable to create transaction directory "
+ "in '%s' for revision %ld"),
+ svn_dirent_local_style(fs->path, pool),
+ rev);
+}
+
+svn_error_t *
+svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_fs_txn_t *txn;
+ svn_fs_id_t *root_id;
+
+ txn = apr_pcalloc(pool, sizeof(*txn));
+
+ /* Get the txn_id. */
+ if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+ SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
+ else
+ SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
+
+ txn->fs = fs;
+ txn->base_rev = rev;
+
+ txn->vtable = &txn_vtable;
+ *txn_p = txn;
+
+ /* Create a new root node for this transaction. */
+ SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
+ SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
+
+ /* Create an empty rev file. */
+ SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
+ pool));
+
+ /* Create an empty rev-lock file. */
+ SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
+ pool));
+
+ /* Create an empty changes file. */
+ SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
+ pool));
+
+ /* Create the next-ids file. */
+ return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
+ pool);
+}
+
+/* Store the property list for transaction TXN_ID in PROPLIST.
+ Perform temporary allocations in POOL. */
+static svn_error_t *
+get_txn_proplist(apr_hash_t *proplist,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+
+ /* Check for issue #3696. (When we find and fix the cause, we can change
+ * this to an assertion.) */
+ if (txn_id == NULL)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Internal error: a null transaction id was "
+ "passed to get_txn_proplist()"));
+
+ /* Open the transaction properties file. */
+ SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
+ pool, pool));
+
+ /* Read in the property list. */
+ SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
+
+ return svn_stream_close(stream);
+}
+
+svn_error_t *
+svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
+ svn_prop_t prop;
+
+ prop.name = name;
+ prop.value = value;
+ APR_ARRAY_PUSH(props, svn_prop_t) = prop;
+
+ return svn_fs_fs__change_txn_props(txn, props, pool);
+}
+
+svn_error_t *
+svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
+ const apr_array_header_t *props,
+ apr_pool_t *pool)
+{
+ const char *txn_prop_filename;
+ svn_stringbuf_t *buf;
+ svn_stream_t *stream;
+ apr_hash_t *txn_prop = apr_hash_make(pool);
+ int i;
+ svn_error_t *err;
+
+ err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
+ /* Here - and here only - we need to deal with the possibility that the
+ transaction property file doesn't yet exist. The rest of the
+ implementation assumes that the file exists, but we're called to set the
+ initial transaction properties as the transaction is being created. */
+ if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
+ svn_error_clear(err);
+ else if (err)
+ return svn_error_trace(err);
+
+ for (i = 0; i < props->nelts; i++)
+ {
+ svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
+
+ svn_hash_sets(txn_prop, prop->name, prop->value);
+ }
+
+ /* Create a new version of the file and write out the new props. */
+ /* Open the transaction properties file. */
+ buf = svn_stringbuf_create_ensure(1024, pool);
+ stream = svn_stream_from_stringbuf(buf, pool);
+ SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+ SVN_ERR(svn_io_write_unique(&txn_prop_filename,
+ path_txn_dir(txn->fs, txn->id, pool),
+ buf->data,
+ buf->len,
+ svn_io_file_del_none,
+ pool));
+ return svn_io_file_rename(txn_prop_filename,
+ path_txn_props(txn->fs, txn->id, pool),
+ pool);
+}
+
+svn_error_t *
+svn_fs_fs__get_txn(transaction_t **txn_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+ node_revision_t *noderev;
+ svn_fs_id_t *root_id;
+
+ txn = apr_pcalloc(pool, sizeof(*txn));
+ txn->proplist = apr_hash_make(pool);
+
+ SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
+ root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
+
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
+
+ txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
+ txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
+ txn->copies = NULL;
+
+ *txn_p = txn;
+
+ return SVN_NO_ERROR;
+}
+
+/* Write out the currently available next node_id NODE_ID and copy_id
+ COPY_ID for transaction TXN_ID in filesystem FS. The next node-id is
+ used both for creating new unique nodes for the given transaction, as
+ well as uniquifying representations. Perform temporary allocations in
+ POOL. */
+static svn_error_t *
+write_next_ids(svn_fs_t *fs,
+ const char *txn_id,
+ const char *node_id,
+ const char *copy_id,
+ apr_pool_t *pool)
+{
+ apr_file_t *file;
+ svn_stream_t *out_stream;
+
+ SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
+ APR_WRITE | APR_TRUNCATE,
+ APR_OS_DEFAULT, pool));
+
+ out_stream = svn_stream_from_aprfile2(file, TRUE, pool);
+
+ SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
+
+ SVN_ERR(svn_stream_close(out_stream));
+ return svn_io_file_close(file, pool);
+}
+
+/* Find out what the next unique node-id and copy-id are for
+ transaction TXN_ID in filesystem FS. Store the results in *NODE_ID
+ and *COPY_ID. The next node-id is used both for creating new unique
+ nodes for the given transaction, as well as uniquifying representations.
+ Perform all allocations in POOL. */
+static svn_error_t *
+read_next_ids(const char **node_id,
+ const char **copy_id,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ apr_file_t *file;
+ char buf[MAX_KEY_SIZE*2+3];
+ apr_size_t limit;
+ char *str, *last_str = buf;
+
+ SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+
+ limit = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
+
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ /* Parse this into two separate strings. */
+
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (! str)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("next-id file corrupt"));
+
+ *node_id = apr_pstrdup(pool, str);
+
+ str = svn_cstring_tokenize(" ", &last_str);
+ if (! str)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("next-id file corrupt"));
+
+ *copy_id = apr_pstrdup(pool, str);
+
+ return SVN_NO_ERROR;
+}
+
+/* Get a new and unique to this transaction node-id for transaction
+ TXN_ID in filesystem FS. Store the new node-id in *NODE_ID_P.
+ Node-ids are guaranteed to be unique to this transction, but may
+ not necessarily be sequential. Perform all allocations in POOL. */
+static svn_error_t *
+get_new_txn_node_id(const char **node_id_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const char *cur_node_id, *cur_copy_id;
+ char *node_id;
+ apr_size_t len;
+
+ /* First read in the current next-ids file. */
+ SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
+
+ node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
+
+ len = strlen(cur_node_id);
+ svn_fs_fs__next_key(cur_node_id, &len, node_id);
+
+ SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
+
+ *node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__create_node(const svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *copy_id,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const char *node_id;
+ const svn_fs_id_t *id;
+
+ /* Get a new node-id for this node. */
+ SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
+
+ id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
+
+ noderev->id = id;
+
+ SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
+
+ *id_p = id;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__purge_txn(svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* Remove the shared transaction object associated with this transaction. */
+ SVN_ERR(purge_shared_txn(fs, txn_id, pool));
+ /* Remove the directory associated with this transaction. */
+ SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
+ NULL, NULL, pool));
+ if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
+ {
+ /* Delete protorev and its lock, which aren't in the txn
+ directory. It's OK if they don't exist (for example, if this
+ is post-commit and the proto-rev has been moved into
+ place). */
+ SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool),
+ TRUE, pool));
+ SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool),
+ TRUE, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
+
+ /* Now, purge the transaction. */
+ SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
+ apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
+ txn->id));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__set_entry(svn_fs_t *fs,
+ const char *txn_id,
+ node_revision_t *parent_noderev,
+ const char *name,
+ const svn_fs_id_t *id,
+ svn_node_kind_t kind,
+ apr_pool_t *pool)
+{
+ representation_t *rep = parent_noderev->data_rep;
+ const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
+ apr_file_t *file;
+ svn_stream_t *out;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ if (!rep || !rep->txn_id)
+ {
+ const char *unique_suffix;
+ apr_hash_t *entries;
+
+ /* Before we can modify the directory, we need to dump its old
+ contents into a mutable representation file. */
+ SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
+ subpool));
+ SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
+ SVN_ERR(svn_io_file_open(&file, filename,
+ APR_WRITE | APR_CREATE | APR_BUFFERED,
+ APR_OS_DEFAULT, pool));
+ out = svn_stream_from_aprfile2(file, TRUE, pool);
+ SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
+
+ svn_pool_clear(subpool);
+
+ /* Mark the node-rev's data rep as mutable. */
+ rep = apr_pcalloc(pool, sizeof(*rep));
+ rep->revision = SVN_INVALID_REVNUM;
+ rep->txn_id = txn_id;
+ SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool));
+ rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix);
+ parent_noderev->data_rep = rep;
+ SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
+ parent_noderev, FALSE, pool));
+ }
+ else
+ {
+ /* The directory rep is already mutable, so just open it for append. */
+ SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
+ APR_OS_DEFAULT, pool));
+ out = svn_stream_from_aprfile2(file, TRUE, pool);
+ }
+
+ /* if we have a directory cache for this transaction, update it */
+ if (ffd->txn_dir_cache)
+ {
+ /* build parameters: (name, new entry) pair */
+ const char *key =
+ svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
+ replace_baton_t baton;
+
+ baton.name = name;
+ baton.new_entry = NULL;
+
+ if (id)
+ {
+ baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
+ baton.new_entry->name = name;
+ baton.new_entry->kind = kind;
+ baton.new_entry->id = id;
+ }
+
+ /* actually update the cached directory (if cached) */
+ SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
+ svn_fs_fs__replace_dir_entry, &baton,
+ subpool));
+ }
+ svn_pool_clear(subpool);
+
+ /* Append an incremental hash entry for the entry change. */
+ if (id)
+ {
+ const char *val = unparse_dir_entry(kind, id, subpool);
+
+ SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n"
+ "V %" APR_SIZE_T_FMT "\n%s\n",
+ strlen(name), name,
+ strlen(val), val));
+ }
+ else
+ {
+ SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
+ strlen(name), name));
+ }
+
+ SVN_ERR(svn_io_file_close(file, subpool));
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* Write a single change entry, path PATH, change CHANGE, and copyfrom
+ string COPYFROM, into the file specified by FILE. Only include the
+ node kind field if INCLUDE_NODE_KIND is true. All temporary
+ allocations are in POOL. */
+static svn_error_t *
+write_change_entry(apr_file_t *file,
+ const char *path,
+ svn_fs_path_change2_t *change,
+ svn_boolean_t include_node_kind,
+ apr_pool_t *pool)
+{
+ const char *idstr, *buf;
+ const char *change_string = NULL;
+ const char *kind_string = "";
+
+ switch (change->change_kind)
+ {
+ case svn_fs_path_change_modify:
+ change_string = ACTION_MODIFY;
+ break;
+ case svn_fs_path_change_add:
+ change_string = ACTION_ADD;
+ break;
+ case svn_fs_path_change_delete:
+ change_string = ACTION_DELETE;
+ break;
+ case svn_fs_path_change_replace:
+ change_string = ACTION_REPLACE;
+ break;
+ case svn_fs_path_change_reset:
+ change_string = ACTION_RESET;
+ break;
+ default:
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change type %d"),
+ change->change_kind);
+ }
+
+ if (change->node_rev_id)
+ idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
+ else
+ idstr = ACTION_RESET;
+
+ if (include_node_kind)
+ {
+ SVN_ERR_ASSERT(change->node_kind == svn_node_dir
+ || change->node_kind == svn_node_file);
+ kind_string = apr_psprintf(pool, "-%s",
+ change->node_kind == svn_node_dir
+ ? KIND_DIR : KIND_FILE);
+ }
+ buf = apr_psprintf(pool, "%s %s%s %s %s %s\n",
+ idstr, change_string, kind_string,
+ change->text_mod ? FLAG_TRUE : FLAG_FALSE,
+ change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
+ path);
+
+ SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
+
+ if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
+ {
+ buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev,
+ change->copyfrom_path);
+ SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
+ }
+
+ return svn_io_file_write_full(file, "\n", 1, NULL, pool);
+}
+
+svn_error_t *
+svn_fs_fs__add_change(svn_fs_t *fs,
+ const char *txn_id,
+ const char *path,
+ const svn_fs_id_t *id,
+ svn_fs_path_change_kind_t change_kind,
+ svn_boolean_t text_mod,
+ svn_boolean_t prop_mod,
+ svn_node_kind_t node_kind,
+ svn_revnum_t copyfrom_rev,
+ const char *copyfrom_path,
+ apr_pool_t *pool)
+{
+ apr_file_t *file;
+ svn_fs_path_change2_t *change;
+
+ SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
+ APR_APPEND | APR_WRITE | APR_CREATE
+ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+
+ change = svn_fs__path_change_create_internal(id, change_kind, pool);
+ change->text_mod = text_mod;
+ change->prop_mod = prop_mod;
+ change->node_kind = node_kind;
+ change->copyfrom_rev = copyfrom_rev;
+ change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
+
+ SVN_ERR(write_change_entry(file, path, change, TRUE, pool));
+
+ return svn_io_file_close(file, pool);
+}
+
+/* This baton is used by the representation writing streams. It keeps
+ track of the checksum information as well as the total size of the
+ representation so far. */
+struct rep_write_baton
+{
+ /* The FS we are writing to. */
+ svn_fs_t *fs;
+
+ /* Actual file to which we are writing. */
+ svn_stream_t *rep_stream;
+
+ /* A stream from the delta combiner. Data written here gets
+ deltified, then eventually written to rep_stream. */
+ svn_stream_t *delta_stream;
+
+ /* Where is this representation header stored. */
+ apr_off_t rep_offset;
+
+ /* Start of the actual data. */
+ apr_off_t delta_start;
+
+ /* How many bytes have been written to this rep already. */
+ svn_filesize_t rep_size;
+
+ /* The node revision for which we're writing out info. */
+ node_revision_t *noderev;
+
+ /* Actual output file. */
+ apr_file_t *file;
+ /* Lock 'cookie' used to unlock the output file once we've finished
+ writing to it. */
+ void *lockcookie;
+
+ svn_checksum_ctx_t *md5_checksum_ctx;
+ svn_checksum_ctx_t *sha1_checksum_ctx;
+
+ apr_pool_t *pool;
+
+ apr_pool_t *parent_pool;
+};
+
+/* Handler for the write method of the representation writable stream.
+ BATON is a rep_write_baton, DATA is the data to write, and *LEN is
+ the length of this data. */
+static svn_error_t *
+rep_write_contents(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct rep_write_baton *b = baton;
+
+ SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
+ SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
+ b->rep_size += *len;
+
+ /* If we are writing a delta, use that stream. */
+ if (b->delta_stream)
+ return svn_stream_write(b->delta_stream, data, len);
+ else
+ return svn_stream_write(b->rep_stream, data, len);
+}
+
+/* Given a node-revision NODEREV in filesystem FS, return the
+ representation in *REP to use as the base for a text representation
+ delta if PROPS is FALSE. If PROPS has been set, a suitable props
+ base representation will be returned. Perform temporary allocations
+ in *POOL. */
+static svn_error_t *
+choose_delta_base(representation_t **rep,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ svn_boolean_t props,
+ apr_pool_t *pool)
+{
+ int count;
+ int walk;
+ node_revision_t *base;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_boolean_t maybe_shared_rep = FALSE;
+
+ /* If we have no predecessors, then use the empty stream as a
+ base. */
+ if (! noderev->predecessor_count)
+ {
+ *rep = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Flip the rightmost '1' bit of the predecessor count to determine
+ which file rev (counting from 0) we want to use. (To see why
+ count & (count - 1) unsets the rightmost set bit, think about how
+ you decrement a binary number.) */
+ count = noderev->predecessor_count;
+ count = count & (count - 1);
+
+ /* We use skip delta for limiting the number of delta operations
+ along very long node histories. Close to HEAD however, we create
+ a linear history to minimize delta size. */
+ walk = noderev->predecessor_count - count;
+ if (walk < (int)ffd->max_linear_deltification)
+ count = noderev->predecessor_count - 1;
+
+ /* Finding the delta base over a very long distance can become extremely
+ expensive for very deep histories, possibly causing client timeouts etc.
+ OTOH, this is a rare operation and its gains are minimal. Lets simply
+ start deltification anew close every other 1000 changes or so. */
+ if (walk > (int)ffd->max_deltification_walk)
+ {
+ *rep = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Walk back a number of predecessors equal to the difference
+ between count and the original predecessor count. (For example,
+ if noderev has ten predecessors and we want the eighth file rev,
+ walk back two predecessors.) */
+ base = noderev;
+ while ((count++) < noderev->predecessor_count)
+ {
+ SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
+ base->predecessor_id, pool));
+
+ /* If there is a shared rep along the way, we need to limit the
+ * length of the deltification chain.
+ *
+ * Please note that copied nodes - such as branch directories - will
+ * look the same (false positive) while reps shared within the same
+ * revision will not be caught (false negative).
+ */
+ if (props)
+ {
+ if ( base->prop_rep
+ && svn_fs_fs__id_rev(base->id) > base->prop_rep->revision)
+ maybe_shared_rep = TRUE;
+ }
+ else
+ {
+ if ( base->data_rep
+ && svn_fs_fs__id_rev(base->id) > base->data_rep->revision)
+ maybe_shared_rep = TRUE;
+ }
+ }
+
+ /* return a suitable base representation */
+ *rep = props ? base->prop_rep : base->data_rep;
+
+ /* if we encountered a shared rep, it's parent chain may be different
+ * from the node-rev parent chain. */
+ if (*rep && maybe_shared_rep)
+ {
+ /* Check whether the length of the deltification chain is acceptable.
+ * Otherwise, shared reps may form a non-skipping delta chain in
+ * extreme cases. */
+ apr_pool_t *sub_pool = svn_pool_create(pool);
+ representation_t base_rep = **rep;
+
+ /* Some reasonable limit, depending on how acceptable longer linear
+ * chains are in this repo. Also, allow for some minimal chain. */
+ int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2;
+
+ /* re-use open files between iterations */
+ svn_revnum_t rev_hint = SVN_INVALID_REVNUM;
+ apr_file_t *file_hint = NULL;
+
+ /* follow the delta chain towards the end but for at most
+ * MAX_CHAIN_LENGTH steps. */
+ for (; max_chain_length; --max_chain_length)
+ {
+ struct rep_state *rep_state;
+ struct rep_args *rep_args;
+
+ SVN_ERR(create_rep_state_body(&rep_state,
+ &rep_args,
+ &file_hint,
+ &rev_hint,
+ &base_rep,
+ fs,
+ sub_pool));
+ if (!rep_args->is_delta || !rep_args->base_revision)
+ break;
+
+ base_rep.revision = rep_args->base_revision;
+ base_rep.offset = rep_args->base_offset;
+ base_rep.size = rep_args->base_length;
+ base_rep.txn_id = NULL;
+ }
+
+ /* start new delta chain if the current one has grown too long */
+ if (max_chain_length == 0)
+ *rep = NULL;
+
+ svn_pool_destroy(sub_pool);
+ }
+
+ /* verify that the reps don't form a degenerated '*/
+ return SVN_NO_ERROR;
+}
+
+/* Something went wrong and the pool for the rep write is being
+ cleared before we've finished writing the rep. So we need
+ to remove the rep from the protorevfile and we need to unlock
+ the protorevfile. */
+static apr_status_t
+rep_write_cleanup(void *data)
+{
+ struct rep_write_baton *b = data;
+ const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
+ svn_error_t *err;
+
+ /* Truncate and close the protorevfile. */
+ err = svn_io_file_trunc(b->file, b->rep_offset, b->pool);
+ err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool));
+
+ /* Remove our lock regardless of any preceeding errors so that the
+ being_written flag is always removed and stays consistent with the
+ file lock which will be removed no matter what since the pool is
+ going away. */
+ err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id,
+ b->lockcookie, b->pool));
+ if (err)
+ {
+ apr_status_t rc = err->apr_err;
+ svn_error_clear(err);
+ return rc;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Get a rep_write_baton and store it in *WB_P for the representation
+ indicated by NODEREV in filesystem FS. Perform allocations in
+ POOL. Only appropriate for file contents, not for props or
+ directory contents. */
+static svn_error_t *
+rep_write_get_baton(struct rep_write_baton **wb_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ struct rep_write_baton *b;
+ apr_file_t *file;
+ representation_t *base_rep;
+ svn_stream_t *source;
+ const char *header;
+ svn_txdelta_window_handler_t wh;
+ void *whb;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
+
+ b = apr_pcalloc(pool, sizeof(*b));
+
+ b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
+ b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+
+ b->fs = fs;
+ b->parent_pool = pool;
+ b->pool = svn_pool_create(pool);
+ b->rep_size = 0;
+ b->noderev = noderev;
+
+ /* Open the prototype rev file and seek to its end. */
+ SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
+ fs, svn_fs_fs__id_txn_id(noderev->id),
+ b->pool));
+
+ b->file = file;
+ b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool);
+
+ SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
+
+ /* Get the base for this delta. */
+ SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool));
+ SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
+
+ /* Write out the rep header. */
+ if (base_rep)
+ {
+ header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
+ SVN_FILESIZE_T_FMT "\n",
+ base_rep->revision, base_rep->offset,
+ base_rep->size);
+ }
+ else
+ {
+ header = REP_DELTA "\n";
+ }
+ SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
+ b->pool));
+
+ /* Now determine the offset of the actual svndiff data. */
+ SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
+
+ /* Cleanup in case something goes wrong. */
+ apr_pool_cleanup_register(b->pool, b, rep_write_cleanup,
+ apr_pool_cleanup_null);
+
+ /* Prepare to write the svndiff data. */
+ svn_txdelta_to_svndiff3(&wh,
+ &whb,
+ b->rep_stream,
+ diff_version,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
+ pool);
+
+ b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
+
+ *wb_p = b;
+
+ return SVN_NO_ERROR;
+}
+
+/* For the hash REP->SHA1, try to find an already existing representation
+ in FS and return it in *OUT_REP. If no such representation exists or
+ if rep sharing has been disabled for FS, NULL will be returned. Since
+ there may be new duplicate representations within the same uncommitted
+ revision, those can be passed in REPS_HASH (maps a sha1 digest onto
+ representation_t*), otherwise pass in NULL for REPS_HASH.
+ POOL will be used for allocations. The lifetime of the returned rep is
+ limited by both, POOL and REP lifetime.
+ */
+static svn_error_t *
+get_shared_rep(representation_t **old_rep,
+ svn_fs_t *fs,
+ representation_t *rep,
+ apr_hash_t *reps_hash,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* Return NULL, if rep sharing has been disabled. */
+ *old_rep = NULL;
+ if (!ffd->rep_sharing_allowed)
+ return SVN_NO_ERROR;
+
+ /* Check and see if we already have a representation somewhere that's
+ identical to the one we just wrote out. Start with the hash lookup
+ because it is cheepest. */
+ if (reps_hash)
+ *old_rep = apr_hash_get(reps_hash,
+ rep->sha1_checksum->digest,
+ APR_SHA1_DIGESTSIZE);
+
+ /* If we haven't found anything yet, try harder and consult our DB. */
+ if (*old_rep == NULL)
+ {
+ err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum,
+ pool);
+ /* ### Other error codes that we shouldn't mask out? */
+ if (err == SVN_NO_ERROR)
+ {
+ if (*old_rep)
+ SVN_ERR(verify_walker(*old_rep, NULL, fs, pool));
+ }
+ else if (err->apr_err == SVN_ERR_FS_CORRUPT
+ || SVN_ERROR_IN_CATEGORY(err->apr_err,
+ SVN_ERR_MALFUNC_CATEGORY_START))
+ {
+ /* Fatal error; don't mask it.
+
+ In particular, this block is triggered when the rep-cache refers
+ to revisions in the future. We signal that as a corruption situation
+ since, once those revisions are less than youngest (because of more
+ commits), the rep-cache would be invalid.
+ */
+ SVN_ERR(err);
+ }
+ else
+ {
+ /* Something's wrong with the rep-sharing index. We can continue
+ without rep-sharing, but warn.
+ */
+ (fs->warning)(fs->warning_baton, err);
+ svn_error_clear(err);
+ *old_rep = NULL;
+ }
+ }
+
+ /* look for intra-revision matches (usually data reps but not limited
+ to them in case props happen to look like some data rep)
+ */
+ if (*old_rep == NULL && rep->txn_id)
+ {
+ svn_node_kind_t kind;
+ const char *file_name
+ = path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool);
+
+ /* in our txn, is there a rep file named with the wanted SHA1?
+ If so, read it and use that rep.
+ */
+ SVN_ERR(svn_io_check_path(file_name, &kind, pool));
+ if (kind == svn_node_file)
+ {
+ svn_stringbuf_t *rep_string;
+ SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool));
+ SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data,
+ rep->txn_id, FALSE, pool));
+ }
+ }
+
+ /* Add information that is missing in the cached data. */
+ if (*old_rep)
+ {
+ /* Use the old rep for this content. */
+ (*old_rep)->md5_checksum = rep->md5_checksum;
+ (*old_rep)->uniquifier = rep->uniquifier;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Close handler for the representation write stream. BATON is a
+ rep_write_baton. Writes out a new node-rev that correctly
+ references the representation we just finished writing. */
+static svn_error_t *
+rep_write_contents_close(void *baton)
+{
+ struct rep_write_baton *b = baton;
+ const char *unique_suffix;
+ representation_t *rep;
+ representation_t *old_rep;
+ apr_off_t offset;
+
+ rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
+ rep->offset = b->rep_offset;
+
+ /* Close our delta stream so the last bits of svndiff are written
+ out. */
+ if (b->delta_stream)
+ SVN_ERR(svn_stream_close(b->delta_stream));
+
+ /* Determine the length of the svndiff data. */
+ SVN_ERR(get_file_offset(&offset, b->file, b->pool));
+ rep->size = offset - b->delta_start;
+
+ /* Fill in the rest of the representation field. */
+ rep->expanded_size = b->rep_size;
+ rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
+ SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool));
+ rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id,
+ unique_suffix);
+ rep->revision = SVN_INVALID_REVNUM;
+
+ /* Finalize the checksum. */
+ SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx,
+ b->parent_pool));
+ SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx,
+ b->parent_pool));
+
+ /* Check and see if we already have a representation somewhere that's
+ identical to the one we just wrote out. */
+ SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool));
+
+ if (old_rep)
+ {
+ /* We need to erase from the protorev the data we just wrote. */
+ SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool));
+
+ /* Use the old rep for this content. */
+ b->noderev->data_rep = old_rep;
+ }
+ else
+ {
+ /* Write out our cosmetic end marker. */
+ SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
+
+ b->noderev->data_rep = rep;
+ }
+
+ /* Remove cleanup callback. */
+ apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup);
+
+ /* Write out the new node-rev information. */
+ SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
+ b->pool));
+ if (!old_rep)
+ SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool));
+
+ SVN_ERR(svn_io_file_close(b->file, b->pool));
+ SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
+ svn_pool_destroy(b->pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Store a writable stream in *CONTENTS_P that will receive all data
+ written and store it as the file data representation referenced by
+ NODEREV in filesystem FS. Perform temporary allocations in
+ POOL. Only appropriate for file data, not props or directory
+ contents. */
+static svn_error_t *
+set_representation(svn_stream_t **contents_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ struct rep_write_baton *wb;
+
+ if (! svn_fs_fs__id_txn_id(noderev->id))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Attempted to write to non-transaction '%s'"),
+ svn_fs_fs__id_unparse(noderev->id, pool)->data);
+
+ SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
+
+ *contents_p = svn_stream_create(wb, pool);
+ svn_stream_set_write(*contents_p, rep_write_contents);
+ svn_stream_set_close(*contents_p, rep_write_contents_close);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__set_contents(svn_stream_t **stream,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool)
+{
+ if (noderev->kind != svn_node_file)
+ return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
+ _("Can't set text contents of a directory"));
+
+ return set_representation(stream, fs, noderev, pool);
+}
+
+svn_error_t *
+svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *old_idp,
+ node_revision_t *new_noderev,
+ const char *copy_id,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *id;
+
+ if (! copy_id)
+ copy_id = svn_fs_fs__id_copy_id(old_idp);
+ id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
+ txn_id, pool);
+
+ new_noderev->id = id;
+
+ if (! new_noderev->copyroot_path)
+ {
+ new_noderev->copyroot_path = apr_pstrdup(pool,
+ new_noderev->created_path);
+ new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
+ }
+
+ SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
+ pool));
+
+ *new_id_p = id;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__set_proplist(svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_hash_t *proplist,
+ apr_pool_t *pool)
+{
+ const char *filename = path_txn_node_props(fs, noderev->id, pool);
+ apr_file_t *file;
+ svn_stream_t *out;
+
+ /* Dump the property list to the mutable property file. */
+ SVN_ERR(svn_io_file_open(&file, filename,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+ out = svn_stream_from_aprfile2(file, TRUE, pool);
+ SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ /* Mark the node-rev's prop rep as mutable, if not already done. */
+ if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
+ {
+ noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
+ noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
+ SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the 'current' file for filesystem FS and store the next
+ available node id in *NODE_ID, and the next available copy id in
+ *COPY_ID. Allocations are performed from POOL. */
+static svn_error_t *
+get_next_revision_ids(const char **node_id,
+ const char **copy_id,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ char *buf;
+ char *str;
+ svn_stringbuf_t *content;
+
+ SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool));
+ buf = content->data;
+
+ str = svn_cstring_tokenize(" ", &buf);
+ if (! str)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Corrupt 'current' file"));
+
+ str = svn_cstring_tokenize(" ", &buf);
+ if (! str)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Corrupt 'current' file"));
+
+ *node_id = apr_pstrdup(pool, str);
+
+ str = svn_cstring_tokenize(" \n", &buf);
+ if (! str)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Corrupt 'current' file"));
+
+ *copy_id = apr_pstrdup(pool, str);
+
+ return SVN_NO_ERROR;
+}
+
+/* This baton is used by the stream created for write_hash_rep. */
+struct write_hash_baton
+{
+ svn_stream_t *stream;
+
+ apr_size_t size;
+
+ svn_checksum_ctx_t *md5_ctx;
+ svn_checksum_ctx_t *sha1_ctx;
+};
+
+/* The handler for the write_hash_rep stream. BATON is a
+ write_hash_baton, DATA has the data to write and *LEN is the number
+ of bytes to write. */
+static svn_error_t *
+write_hash_handler(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct write_hash_baton *whb = baton;
+
+ SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
+ SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
+
+ SVN_ERR(svn_stream_write(whb->stream, data, len));
+ whb->size += *len;
+
+ return SVN_NO_ERROR;
+}
+
+/* Write out the hash HASH as a text representation to file FILE. In
+ the process, record position, the total size of the dump and MD5 as
+ well as SHA1 in REP. If rep sharing has been enabled and REPS_HASH
+ is not NULL, it will be used in addition to the on-disk cache to find
+ earlier reps with the same content. When such existing reps can be
+ found, we will truncate the one just written from the file and return
+ the existing rep. Perform temporary allocations in POOL. */
+static svn_error_t *
+write_hash_rep(representation_t *rep,
+ apr_file_t *file,
+ apr_hash_t *hash,
+ svn_fs_t *fs,
+ apr_hash_t *reps_hash,
+ apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+ struct write_hash_baton *whb;
+ representation_t *old_rep;
+
+ SVN_ERR(get_file_offset(&rep->offset, file, pool));
+
+ whb = apr_pcalloc(pool, sizeof(*whb));
+
+ whb->stream = svn_stream_from_aprfile2(file, TRUE, pool);
+ whb->size = 0;
+ whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+ whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
+
+ stream = svn_stream_create(whb, pool);
+ svn_stream_set_write(stream, write_hash_handler);
+
+ SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
+
+ SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
+
+ /* Store the results. */
+ SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
+ SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
+
+ /* Check and see if we already have a representation somewhere that's
+ identical to the one we just wrote out. */
+ SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
+
+ if (old_rep)
+ {
+ /* We need to erase from the protorev the data we just wrote. */
+ SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
+
+ /* Use the old rep for this content. */
+ memcpy(rep, old_rep, sizeof (*rep));
+ }
+ else
+ {
+ /* Write out our cosmetic end marker. */
+ SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
+
+ /* update the representation */
+ rep->size = whb->size;
+ rep->expanded_size = 0;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Write out the hash HASH pertaining to the NODEREV in FS as a deltified
+ text representation to file FILE. In the process, record the total size
+ and the md5 digest in REP. If rep sharing has been enabled and REPS_HASH
+ is not NULL, it will be used in addition to the on-disk cache to find
+ earlier reps with the same content. When such existing reps can be found,
+ we will truncate the one just written from the file and return the existing
+ rep. If PROPS is set, assume that we want to a props representation as
+ the base for our delta. Perform temporary allocations in POOL. */
+static svn_error_t *
+write_hash_delta_rep(representation_t *rep,
+ apr_file_t *file,
+ apr_hash_t *hash,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_hash_t *reps_hash,
+ svn_boolean_t props,
+ apr_pool_t *pool)
+{
+ svn_txdelta_window_handler_t diff_wh;
+ void *diff_whb;
+
+ svn_stream_t *file_stream;
+ svn_stream_t *stream;
+ representation_t *base_rep;
+ representation_t *old_rep;
+ svn_stream_t *source;
+ const char *header;
+
+ apr_off_t rep_end = 0;
+ apr_off_t delta_start = 0;
+
+ struct write_hash_baton *whb;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
+
+ /* Get the base for this delta. */
+ SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool));
+ SVN_ERR(read_representation(&source, fs, base_rep, pool));
+
+ SVN_ERR(get_file_offset(&rep->offset, file, pool));
+
+ /* Write out the rep header. */
+ if (base_rep)
+ {
+ header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
+ SVN_FILESIZE_T_FMT "\n",
+ base_rep->revision, base_rep->offset,
+ base_rep->size);
+ }
+ else
+ {
+ header = REP_DELTA "\n";
+ }
+ SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
+ pool));
+
+ SVN_ERR(get_file_offset(&delta_start, file, pool));
+ file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
+
+ /* Prepare to write the svndiff data. */
+ svn_txdelta_to_svndiff3(&diff_wh,
+ &diff_whb,
+ file_stream,
+ diff_version,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
+ pool);
+
+ whb = apr_pcalloc(pool, sizeof(*whb));
+ whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
+ whb->size = 0;
+ whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+ whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
+
+ /* serialize the hash */
+ stream = svn_stream_create(whb, pool);
+ svn_stream_set_write(stream, write_hash_handler);
+
+ SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(whb->stream));
+
+ /* Store the results. */
+ SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
+ SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
+
+ /* Check and see if we already have a representation somewhere that's
+ identical to the one we just wrote out. */
+ SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
+
+ if (old_rep)
+ {
+ /* We need to erase from the protorev the data we just wrote. */
+ SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
+
+ /* Use the old rep for this content. */
+ memcpy(rep, old_rep, sizeof (*rep));
+ }
+ else
+ {
+ /* Write out our cosmetic end marker. */
+ SVN_ERR(get_file_offset(&rep_end, file, pool));
+ SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
+
+ /* update the representation */
+ rep->expanded_size = whb->size;
+ rep->size = rep_end - delta_start;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
+ of (not yet committed) revision REV in FS. Use POOL for temporary
+ allocations.
+
+ If you change this function, consider updating svn_fs_fs__verify() too.
+ */
+static svn_error_t *
+validate_root_noderev(svn_fs_t *fs,
+ node_revision_t *root_noderev,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ svn_revnum_t head_revnum = rev-1;
+ int head_predecessor_count;
+
+ SVN_ERR_ASSERT(rev > 0);
+
+ /* Compute HEAD_PREDECESSOR_COUNT. */
+ {
+ svn_fs_root_t *head_revision;
+ const svn_fs_id_t *head_root_id;
+ node_revision_t *head_root_noderev;
+
+ /* Get /@HEAD's noderev. */
+ SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
+ SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
+ SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
+ pool));
+
+ head_predecessor_count = head_root_noderev->predecessor_count;
+ }
+
+ /* Check that the root noderev's predecessor count equals REV.
+
+ This kind of corruption was seen on svn.apache.org (both on
+ the root noderev and on other fspaths' noderevs); see
+ issue #4129.
+
+ Normally (rev == root_noderev->predecessor_count), but here we
+ use a more roundabout check that should only trigger on new instances
+ of the corruption, rather then trigger on each and every new commit
+ to a repository that has triggered the bug somewhere in its root
+ noderev's history.
+ */
+ if (root_noderev->predecessor_count != -1
+ && (root_noderev->predecessor_count - head_predecessor_count)
+ != (rev - head_revnum))
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("predecessor count for "
+ "the root node-revision is wrong: "
+ "found (%d+%ld != %d), committing r%ld"),
+ head_predecessor_count,
+ rev - head_revnum, /* This is equal to 1. */
+ root_noderev->predecessor_count,
+ rev);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy a node-revision specified by id ID in fileystem FS from a
+ transaction into the proto-rev-file FILE. Set *NEW_ID_P to a
+ pointer to the new node-id which will be allocated in POOL.
+ If this is a directory, copy all children as well.
+
+ START_NODE_ID and START_COPY_ID are
+ the first available node and copy ids for this filesystem, for older
+ FS formats.
+
+ REV is the revision number that this proto-rev-file will represent.
+
+ INITIAL_OFFSET is the offset of the proto-rev-file on entry to
+ commit_body.
+
+ If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
+ REPS_POOL) of each data rep that is new in this revision.
+
+ If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
+ of the representations of each property rep that is new in this
+ revision.
+
+ AT_ROOT is true if the node revision being written is the root
+ node-revision. It is only controls additional sanity checking
+ logic.
+
+ Temporary allocations are also from POOL. */
+static svn_error_t *
+write_final_rev(const svn_fs_id_t **new_id_p,
+ apr_file_t *file,
+ svn_revnum_t rev,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *start_node_id,
+ const char *start_copy_id,
+ apr_off_t initial_offset,
+ apr_array_header_t *reps_to_cache,
+ apr_hash_t *reps_hash,
+ apr_pool_t *reps_pool,
+ svn_boolean_t at_root,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ apr_off_t my_offset;
+ char my_node_id_buf[MAX_KEY_SIZE + 2];
+ char my_copy_id_buf[MAX_KEY_SIZE + 2];
+ const svn_fs_id_t *new_id;
+ const char *node_id, *copy_id, *my_node_id, *my_copy_id;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ *new_id_p = NULL;
+
+ /* Check to see if this is a transaction node. */
+ if (! svn_fs_fs__id_txn_id(id))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
+
+ if (noderev->kind == svn_node_dir)
+ {
+ apr_pool_t *subpool;
+ apr_hash_t *entries, *str_entries;
+ apr_array_header_t *sorted_entries;
+ int i;
+
+ /* This is a directory. Write out all the children first. */
+ subpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
+ /* For the sake of the repository administrator sort the entries
+ so that the final file is deterministic and repeatable,
+ however the rest of the FSFS code doesn't require any
+ particular order here. */
+ sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically,
+ pool);
+ for (i = 0; i < sorted_entries->nelts; ++i)
+ {
+ svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i,
+ svn_sort__item_t).value;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
+ start_node_id, start_copy_id, initial_offset,
+ reps_to_cache, reps_hash, reps_pool, FALSE,
+ subpool));
+ if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
+ dirent->id = svn_fs_fs__id_copy(new_id, pool);
+ }
+ svn_pool_destroy(subpool);
+
+ if (noderev->data_rep && noderev->data_rep->txn_id)
+ {
+ /* Write out the contents of this directory as a text rep. */
+ SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
+
+ noderev->data_rep->txn_id = NULL;
+ noderev->data_rep->revision = rev;
+
+ if (ffd->deltify_directories)
+ SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
+ str_entries, fs, noderev, NULL,
+ FALSE, pool));
+ else
+ SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
+ fs, NULL, pool));
+ }
+ }
+ else
+ {
+ /* This is a file. We should make sure the data rep, if it
+ exists in a "this" state, gets rewritten to our new revision
+ num. */
+
+ if (noderev->data_rep && noderev->data_rep->txn_id)
+ {
+ noderev->data_rep->txn_id = NULL;
+ noderev->data_rep->revision = rev;
+
+ /* See issue 3845. Some unknown mechanism caused the
+ protorev file to get truncated, so check for that
+ here. */
+ if (noderev->data_rep->offset + noderev->data_rep->size
+ > initial_offset)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Truncated protorev file detected"));
+ }
+ }
+
+ /* Fix up the property reps. */
+ if (noderev->prop_rep && noderev->prop_rep->txn_id)
+ {
+ apr_hash_t *proplist;
+ SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
+
+ noderev->prop_rep->txn_id = NULL;
+ noderev->prop_rep->revision = rev;
+
+ if (ffd->deltify_properties)
+ SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
+ proplist, fs, noderev, reps_hash,
+ TRUE, pool));
+ else
+ SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
+ fs, reps_hash, pool));
+ }
+
+
+ /* Convert our temporary ID into a permanent revision one. */
+ SVN_ERR(get_file_offset(&my_offset, file, pool));
+
+ node_id = svn_fs_fs__id_node_id(noderev->id);
+ if (*node_id == '_')
+ {
+ if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev);
+ else
+ {
+ svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf);
+ my_node_id = my_node_id_buf;
+ }
+ }
+ else
+ my_node_id = node_id;
+
+ copy_id = svn_fs_fs__id_copy_id(noderev->id);
+ if (*copy_id == '_')
+ {
+ if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev);
+ else
+ {
+ svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf);
+ my_copy_id = my_copy_id_buf;
+ }
+ }
+ else
+ my_copy_id = copy_id;
+
+ if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
+ noderev->copyroot_rev = rev;
+
+ new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
+ pool);
+
+ noderev->id = new_id;
+
+ if (ffd->rep_sharing_allowed)
+ {
+ /* Save the data representation's hash in the rep cache. */
+ if ( noderev->data_rep && noderev->kind == svn_node_file
+ && noderev->data_rep->revision == rev)
+ {
+ SVN_ERR_ASSERT(reps_to_cache && reps_pool);
+ APR_ARRAY_PUSH(reps_to_cache, representation_t *)
+ = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
+ }
+
+ if (noderev->prop_rep && noderev->prop_rep->revision == rev)
+ {
+ /* Add new property reps to hash and on-disk cache. */
+ representation_t *copy
+ = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
+
+ SVN_ERR_ASSERT(reps_to_cache && reps_pool);
+ APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
+
+ apr_hash_set(reps_hash,
+ copy->sha1_checksum->digest,
+ APR_SHA1_DIGESTSIZE,
+ copy);
+ }
+ }
+
+ /* don't serialize SHA1 for dirs to disk (waste of space) */
+ if (noderev->data_rep && noderev->kind == svn_node_dir)
+ noderev->data_rep->sha1_checksum = NULL;
+
+ /* don't serialize SHA1 for props to disk (waste of space) */
+ if (noderev->prop_rep)
+ noderev->prop_rep->sha1_checksum = NULL;
+
+ /* Workaround issue #4031: is-fresh-txn-root in revision files. */
+ noderev->is_fresh_txn_root = FALSE;
+
+ /* Write out our new node-revision. */
+ if (at_root)
+ SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
+
+ SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool),
+ noderev, ffd->format,
+ svn_fs_fs__fs_supports_mergeinfo(fs),
+ pool));
+
+ /* Return our ID that references the revision file. */
+ *new_id_p = noderev->id;
+
+ return SVN_NO_ERROR;
+}
+
+/* Write the changed path info from transaction TXN_ID in filesystem
+ FS to the permanent rev-file FILE. *OFFSET_P is set the to offset
+ in the file of the beginning of this information. Perform
+ temporary allocations in POOL. */
+static svn_error_t *
+write_final_changed_path_info(apr_off_t *offset_p,
+ apr_file_t *file,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ apr_hash_t *changed_paths;
+ apr_off_t offset;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_boolean_t include_node_kinds =
+ ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
+ apr_array_header_t *sorted_changed_paths;
+ int i;
+
+ SVN_ERR(get_file_offset(&offset, file, pool));
+
+ SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool));
+ /* For the sake of the repository administrator sort the changes so
+ that the final file is deterministic and repeatable, however the
+ rest of the FSFS code doesn't require any particular order here. */
+ sorted_changed_paths = svn_sort__hash(changed_paths,
+ svn_sort_compare_items_lexically, pool);
+
+ /* Iterate through the changed paths one at a time, and convert the
+ temporary node-id into a permanent one for each change entry. */
+ for (i = 0; i < sorted_changed_paths->nelts; ++i)
+ {
+ node_revision_t *noderev;
+ const svn_fs_id_t *id;
+ svn_fs_path_change2_t *change;
+ const char *path;
+
+ svn_pool_clear(iterpool);
+
+ change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
+ path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
+
+ id = change->node_rev_id;
+
+ /* If this was a delete of a mutable node, then it is OK to
+ leave the change entry pointing to the non-existent temporary
+ node, since it will never be used. */
+ if ((change->change_kind != svn_fs_path_change_delete) &&
+ (! svn_fs_fs__id_txn_id(id)))
+ {
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
+
+ /* noderev has the permanent node-id at this point, so we just
+ substitute it for the temporary one. */
+ change->node_rev_id = noderev->id;
+ }
+
+ /* Write out the new entry into the final rev-file. */
+ SVN_ERR(write_change_entry(file, path, change, include_node_kinds,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ *offset_p = offset;
+
+ return SVN_NO_ERROR;
+}
+
+/* Atomically update the 'current' file to hold the specifed REV,
+ NEXT_NODE_ID, and NEXT_COPY_ID. (The two next-ID parameters are
+ ignored and may be NULL if the FS format does not use them.)
+ Perform temporary allocations in POOL. */
+static svn_error_t *
+write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
+ const char *next_copy_id, apr_pool_t *pool)
+{
+ char *buf;
+ const char *tmp_name, *name;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ /* Now we can just write out this line. */
+ if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ buf = apr_psprintf(pool, "%ld\n", rev);
+ else
+ buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
+
+ name = svn_fs_fs__path_current(fs, pool);
+ SVN_ERR(svn_io_write_unique(&tmp_name,
+ svn_dirent_dirname(name, pool),
+ buf, strlen(buf),
+ svn_io_file_del_none, pool));
+
+ return move_into_place(tmp_name, name, name, pool);
+}
+
+/* Open a new svn_fs_t handle to FS, set that handle's concept of "current
+ youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
+ NEW_REV's revision root.
+
+ Intended to be called as the very last step in a commit before 'current'
+ is bumped. This implies that we are holding the write lock. */
+static svn_error_t *
+verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
+ svn_revnum_t new_rev,
+ apr_pool_t *pool)
+{
+#ifdef SVN_DEBUG
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_fs_t *ft; /* fs++ == ft */
+ svn_fs_root_t *root;
+ fs_fs_data_t *ft_ffd;
+ apr_hash_t *fs_config;
+
+ SVN_ERR_ASSERT(ffd->svn_fs_open_);
+
+ /* make sure FT does not simply return data cached by other instances
+ * but actually retrieves it from disk at least once.
+ */
+ fs_config = apr_hash_make(pool);
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
+ svn_uuid_generate(pool));
+ SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
+ fs_config,
+ pool));
+ ft_ffd = ft->fsap_data;
+ /* Don't let FT consult rep-cache.db, either. */
+ ft_ffd->rep_sharing_allowed = FALSE;
+
+ /* Time travel! */
+ ft_ffd->youngest_rev_cache = new_rev;
+
+ SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
+ SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
+ SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
+ SVN_ERR(svn_fs_fs__verify_root(root, pool));
+#endif /* SVN_DEBUG */
+
+ return SVN_NO_ERROR;
+}
+
+/* Update the 'current' file to hold the correct next node and copy_ids
+ from transaction TXN_ID in filesystem FS. The current revision is
+ set to REV. Perform temporary allocations in POOL. */
+static svn_error_t *
+write_final_current(svn_fs_t *fs,
+ const char *txn_id,
+ svn_revnum_t rev,
+ const char *start_node_id,
+ const char *start_copy_id,
+ apr_pool_t *pool)
+{
+ const char *txn_node_id, *txn_copy_id;
+ char new_node_id[MAX_KEY_SIZE + 2];
+ char new_copy_id[MAX_KEY_SIZE + 2];
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ return write_current(fs, rev, NULL, NULL, pool);
+
+ /* To find the next available ids, we add the id that used to be in
+ the 'current' file, to the next ids from the transaction file. */
+ SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
+
+ svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
+ svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
+
+ return write_current(fs, rev, new_node_id, new_copy_id, pool);
+}
+
+/* Verify that the user registed with FS has all the locks necessary to
+ permit all the changes associate with TXN_NAME.
+ The FS write lock is assumed to be held by the caller. */
+static svn_error_t *
+verify_locks(svn_fs_t *fs,
+ const char *txn_name,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *changes;
+ apr_hash_index_t *hi;
+ apr_array_header_t *changed_paths;
+ svn_stringbuf_t *last_recursed = NULL;
+ int i;
+
+ /* Fetch the changes for this transaction. */
+ SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool));
+
+ /* Make an array of the changed paths, and sort them depth-first-ily. */
+ changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
+ sizeof(const char *));
+ for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
+ APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi);
+ qsort(changed_paths->elts, changed_paths->nelts,
+ changed_paths->elt_size, svn_sort_compare_paths);
+
+ /* Now, traverse the array of changed paths, verify locks. Note
+ that if we need to do a recursive verification a path, we'll skip
+ over children of that path when we get to them. */
+ for (i = 0; i < changed_paths->nelts; i++)
+ {
+ const char *path;
+ svn_fs_path_change2_t *change;
+ svn_boolean_t recurse = TRUE;
+
+ svn_pool_clear(subpool);
+ path = APR_ARRAY_IDX(changed_paths, i, const char *);
+
+ /* If this path has already been verified as part of a recursive
+ check of one of its parents, no need to do it again. */
+ if (last_recursed
+ && svn_dirent_is_child(last_recursed->data, path, subpool))
+ continue;
+
+ /* Fetch the change associated with our path. */
+ change = svn_hash_gets(changes, path);
+
+ /* What does it mean to succeed at lock verification for a given
+ path? For an existing file or directory getting modified
+ (text, props), it means we hold the lock on the file or
+ directory. For paths being added or removed, we need to hold
+ the locks for that path and any children of that path.
+
+ WHEW! We have no reliable way to determine the node kind
+ of deleted items, but fortunately we are going to do a
+ recursive check on deleted paths regardless of their kind. */
+ if (change->change_kind == svn_fs_path_change_modify)
+ recurse = FALSE;
+ SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
+ subpool));
+
+ /* If we just did a recursive check, remember the path we
+ checked (so children can be skipped). */
+ if (recurse)
+ {
+ if (! last_recursed)
+ last_recursed = svn_stringbuf_create(path, pool);
+ else
+ svn_stringbuf_set(last_recursed, path);
+ }
+ }
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* Baton used for commit_body below. */
+struct commit_baton {
+ svn_revnum_t *new_rev_p;
+ svn_fs_t *fs;
+ svn_fs_txn_t *txn;
+ apr_array_header_t *reps_to_cache;
+ apr_hash_t *reps_hash;
+ apr_pool_t *reps_pool;
+};
+
+/* The work-horse for svn_fs_fs__commit, called with the FS write lock.
+ This implements the svn_fs_fs__with_write_lock() 'body' callback
+ type. BATON is a 'struct commit_baton *'. */
+static svn_error_t *
+commit_body(void *baton, apr_pool_t *pool)
+{
+ struct commit_baton *cb = baton;
+ fs_fs_data_t *ffd = cb->fs->fsap_data;
+ const char *old_rev_filename, *rev_filename, *proto_filename;
+ const char *revprop_filename, *final_revprop;
+ const svn_fs_id_t *root_id, *new_root_id;
+ const char *start_node_id = NULL, *start_copy_id = NULL;
+ svn_revnum_t old_rev, new_rev;
+ apr_file_t *proto_file;
+ void *proto_file_lockcookie;
+ apr_off_t initial_offset, changed_path_offset;
+ char *buf;
+ apr_hash_t *txnprops;
+ apr_array_header_t *txnprop_list;
+ svn_prop_t prop;
+ svn_string_t date;
+
+ /* Get the current youngest revision. */
+ SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
+
+ /* Check to make sure this transaction is based off the most recent
+ revision. */
+ if (cb->txn->base_rev != old_rev)
+ return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
+ _("Transaction out of date"));
+
+ /* Locks may have been added (or stolen) between the calling of
+ previous svn_fs.h functions and svn_fs_commit_txn(), so we need
+ to re-examine every changed-path in the txn and re-verify all
+ discovered locks. */
+ SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
+
+ /* Get the next node_id and copy_id to use. */
+ if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
+ pool));
+
+ /* We are going to be one better than this puny old revision. */
+ new_rev = old_rev + 1;
+
+ /* Get a write handle on the proto revision file. */
+ SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
+ cb->fs, cb->txn->id, pool));
+ SVN_ERR(get_file_offset(&initial_offset, proto_file, pool));
+
+ /* Write out all the node-revisions and directory contents. */
+ root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
+ SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
+ start_node_id, start_copy_id, initial_offset,
+ cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
+ TRUE, pool));
+
+ /* Write the changed-path information. */
+ SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
+ cb->fs, cb->txn->id, pool));
+
+ /* Write the final line. */
+ buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
+ svn_fs_fs__id_offset(new_root_id),
+ changed_path_offset);
+ SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
+ pool));
+ SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
+ SVN_ERR(svn_io_file_close(proto_file, pool));
+
+ /* We don't unlock the prototype revision file immediately to avoid a
+ race with another caller writing to the prototype revision file
+ before we commit it. */
+
+ /* Remove any temporary txn props representing 'flags'. */
+ SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
+ txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t));
+ prop.value = NULL;
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
+ {
+ prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
+ APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
+ }
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
+ {
+ prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
+ APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
+ }
+
+ if (! apr_is_empty_array(txnprop_list))
+ SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool));
+
+ /* Create the shard for the rev and revprop file, if we're sharding and
+ this is the first revision of a new shard. We don't care if this
+ fails because the shard already existed for some reason. */
+ if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
+ {
+ /* Create the revs shard. */
+ {
+ const char *new_dir = path_rev_shard(cb->fs, new_rev, pool);
+ svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
+ if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
+ return svn_error_trace(err);
+ svn_error_clear(err);
+ SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
+ PATH_REVS_DIR,
+ pool),
+ new_dir, pool));
+ }
+
+ /* Create the revprops shard. */
+ SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
+ {
+ const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool);
+ svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
+ if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
+ return svn_error_trace(err);
+ svn_error_clear(err);
+ SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
+ PATH_REVPROPS_DIR,
+ pool),
+ new_dir, pool));
+ }
+ }
+
+ /* Move the finished rev file into place. */
+ SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename,
+ cb->fs, old_rev, pool));
+ rev_filename = path_rev(cb->fs, new_rev, pool);
+ proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
+ SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename,
+ pool));
+
+ /* Now that we've moved the prototype revision file out of the way,
+ we can unlock it (since further attempts to write to the file
+ will fail as it no longer exists). We must do this so that we can
+ remove the transaction directory later. */
+ SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
+
+ /* Update commit time to ensure that svn:date revprops remain ordered. */
+ date.data = svn_time_to_cstring(apr_time_now(), pool);
+ date.len = strlen(date.data);
+
+ SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
+ &date, pool));
+
+ /* Move the revprops file into place. */
+ SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
+ revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
+ final_revprop = path_revprops(cb->fs, new_rev, pool);
+ SVN_ERR(move_into_place(revprop_filename, final_revprop,
+ old_rev_filename, pool));
+
+ /* Update the 'current' file. */
+ SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
+ SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
+ start_copy_id, pool));
+
+ /* At this point the new revision is committed and globally visible
+ so let the caller know it succeeded by giving it the new revision
+ number, which fulfills svn_fs_commit_txn() contract. Any errors
+ after this point do not change the fact that a new revision was
+ created. */
+ *cb->new_rev_p = new_rev;
+
+ ffd->youngest_rev_cache = new_rev;
+
+ /* Remove this transaction directory. */
+ SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Add the representations in REPS_TO_CACHE (an array of representation_t *)
+ * to the rep-cache database of FS. */
+static svn_error_t *
+write_reps_to_cache(svn_fs_t *fs,
+ const apr_array_header_t *reps_to_cache,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ for (i = 0; i < reps_to_cache->nelts; i++)
+ {
+ representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
+
+ /* FALSE because we don't care if another parallel commit happened to
+ * collide with us. (Non-parallel collisions will not be detected.) */
+ SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__commit(svn_revnum_t *new_rev_p,
+ svn_fs_t *fs,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ struct commit_baton cb;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ cb.new_rev_p = new_rev_p;
+ cb.fs = fs;
+ cb.txn = txn;
+
+ if (ffd->rep_sharing_allowed)
+ {
+ cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
+ cb.reps_hash = apr_hash_make(pool);
+ cb.reps_pool = pool;
+ }
+ else
+ {
+ cb.reps_to_cache = NULL;
+ cb.reps_hash = NULL;
+ cb.reps_pool = NULL;
+ }
+
+ SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
+
+ /* At this point, *NEW_REV_P has been set, so errors below won't affect
+ the success of the commit. (See svn_fs_commit_txn().) */
+
+ if (ffd->rep_sharing_allowed)
+ {
+ SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
+
+ /* Write new entries to the rep-sharing database.
+ *
+ * We use an sqlite transaction to speed things up;
+ * see <http://www.sqlite.org/faq.html#q19>.
+ */
+ SVN_SQLITE__WITH_TXN(
+ write_reps_to_cache(fs, cb.reps_to_cache, pool),
+ ffd->rep_cache_db);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__reserve_copy_id(const char **copy_id_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const char *cur_node_id, *cur_copy_id;
+ char *copy_id;
+ apr_size_t len;
+
+ /* First read in the current next-ids file. */
+ SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
+
+ copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
+
+ len = strlen(cur_copy_id);
+ svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
+
+ SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
+
+ *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL);
+
+ return SVN_NO_ERROR;
+}
+
+/* Write out the zeroth revision for filesystem FS. */
+static svn_error_t *
+write_revision_zero(svn_fs_t *fs)
+{
+ const char *path_revision_zero = path_rev(fs, 0, fs->pool);
+ apr_hash_t *proplist;
+ svn_string_t date;
+
+ /* Write out a rev file for revision 0. */
+ SVN_ERR(svn_io_file_create(path_revision_zero,
+ "PLAIN\nEND\nENDREP\n"
+ "id: 0.0.r0/17\n"
+ "type: dir\n"
+ "count: 0\n"
+ "text: 0 0 4 4 "
+ "2d2977d1c96f487abe4a1e202dd03b4e\n"
+ "cpath: /\n"
+ "\n\n17 107\n", fs->pool));
+ SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
+
+ /* Set a date on revision 0. */
+ date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
+ date.len = strlen(date.data);
+ proplist = apr_hash_make(fs->pool);
+ svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
+ return set_revision_proplist(fs, 0, proplist, fs->pool);
+}
+
+svn_error_t *
+svn_fs_fs__create(svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool)
+{
+ int format = SVN_FS_FS__FORMAT_NUMBER;
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ fs->path = apr_pstrdup(pool, path);
+ /* See if compatibility with older versions was explicitly requested. */
+ if (fs->config)
+ {
+ if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
+ format = 1;
+ else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
+ format = 2;
+ else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
+ format = 3;
+ else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE))
+ format = 4;
+ }
+ ffd->format = format;
+
+ /* Override the default linear layout if this is a new-enough format. */
+ if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
+ ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
+
+ /* Create the revision data directories. */
+ if (ffd->max_files_per_dir)
+ SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool));
+ else
+ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
+ pool),
+ pool));
+
+ /* Create the revprops directory. */
+ if (ffd->max_files_per_dir)
+ SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool),
+ pool));
+ else
+ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
+ PATH_REVPROPS_DIR,
+ pool),
+ pool));
+
+ /* Create the transaction directory. */
+ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR,
+ pool),
+ pool));
+
+ /* Create the protorevs directory. */
+ if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
+ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR,
+ pool),
+ pool));
+
+ /* Create the 'current' file. */
+ SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
+ (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
+ ? "0\n" : "0 1 1\n"),
+ pool));
+ SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool));
+ SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool));
+
+ SVN_ERR(write_revision_zero(fs));
+
+ SVN_ERR(write_config(fs, pool));
+
+ SVN_ERR(read_config(ffd, fs->path, pool));
+
+ /* Create the min unpacked rev file. */
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
+
+ /* Create the txn-current file if the repository supports
+ the transaction sequence file. */
+ if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+ {
+ SVN_ERR(svn_io_file_create(path_txn_current(fs, pool),
+ "0\n", pool));
+ SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool),
+ "", pool));
+ }
+
+ /* This filesystem is ready. Stamp it with a format number. */
+ SVN_ERR(write_format(path_format(fs, pool),
+ ffd->format, ffd->max_files_per_dir, FALSE, pool));
+
+ ffd->youngest_rev_cache = 0;
+ return SVN_NO_ERROR;
+}
+
+/* Part of the recovery procedure. Return the largest revision *REV in
+ filesystem FS. Use POOL for temporary allocation. */
+static svn_error_t *
+recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
+{
+ /* Discovering the largest revision in the filesystem would be an
+ expensive operation if we did a readdir() or searched linearly,
+ so we'll do a form of binary search. left is a revision that we
+ know exists, right a revision that we know does not exist. */
+ apr_pool_t *iterpool;
+ svn_revnum_t left, right = 1;
+
+ iterpool = svn_pool_create(pool);
+ /* Keep doubling right, until we find a revision that doesn't exist. */
+ while (1)
+ {
+ svn_error_t *err;
+ apr_file_t *file;
+
+ err = open_pack_or_rev_file(&file, fs, right, iterpool);
+ svn_pool_clear(iterpool);
+
+ if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
+ {
+ svn_error_clear(err);
+ break;
+ }
+ else
+ SVN_ERR(err);
+
+ right <<= 1;
+ }
+
+ left = right >> 1;
+
+ /* We know that left exists and right doesn't. Do a normal bsearch to find
+ the last revision. */
+ while (left + 1 < right)
+ {
+ svn_revnum_t probe = left + ((right - left) / 2);
+ svn_error_t *err;
+ apr_file_t *file;
+
+ err = open_pack_or_rev_file(&file, fs, probe, iterpool);
+ svn_pool_clear(iterpool);
+
+ if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
+ {
+ svn_error_clear(err);
+ right = probe;
+ }
+ else
+ {
+ SVN_ERR(err);
+ left = probe;
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ /* left is now the largest revision that exists. */
+ *rev = left;
+ return SVN_NO_ERROR;
+}
+
+/* A baton for reading a fixed amount from an open file. For
+ recover_find_max_ids() below. */
+struct recover_read_from_file_baton
+{
+ apr_file_t *file;
+ apr_pool_t *pool;
+ apr_off_t remaining;
+};
+
+/* A stream read handler used by recover_find_max_ids() below.
+ Read and return at most BATON->REMAINING bytes from the stream,
+ returning nothing after that to indicate EOF. */
+static svn_error_t *
+read_handler_recover(void *baton, char *buffer, apr_size_t *len)
+{
+ struct recover_read_from_file_baton *b = baton;
+ svn_filesize_t bytes_to_read = *len;
+
+ if (b->remaining == 0)
+ {
+ /* Return a successful read of zero bytes to signal EOF. */
+ *len = 0;
+ return SVN_NO_ERROR;
+ }
+
+ if (bytes_to_read > b->remaining)
+ bytes_to_read = b->remaining;
+ b->remaining -= bytes_to_read;
+
+ return svn_io_file_read_full2(b->file, buffer, (apr_size_t) bytes_to_read,
+ len, NULL, b->pool);
+}
+
+/* Part of the recovery procedure. Read the directory noderev at offset
+ OFFSET of file REV_FILE (the revision file of revision REV of
+ filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
+ and copy-id of that node, if greater than the current value stored
+ in either. Recurse into any child directories that were modified in
+ this revision.
+
+ MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
+
+ Perform temporary allocation in POOL. */
+static svn_error_t *
+recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev,
+ apr_file_t *rev_file, apr_off_t offset,
+ char *max_node_id, char *max_copy_id,
+ apr_pool_t *pool)
+{
+ apr_hash_t *headers;
+ char *value;
+ representation_t *data_rep;
+ struct rep_args *ra;
+ struct recover_read_from_file_baton baton;
+ svn_stream_t *stream;
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+ SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile2(rev_file, TRUE,
+ pool),
+ pool));
+
+ /* Check that this is a directory. It should be. */
+ value = svn_hash_gets(headers, HEADER_TYPE);
+ if (value == NULL || strcmp(value, KIND_DIR) != 0)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Recovery encountered a non-directory node"));
+
+ /* Get the data location. No data location indicates an empty directory. */
+ value = svn_hash_gets(headers, HEADER_TEXT);
+ if (!value)
+ return SVN_NO_ERROR;
+ SVN_ERR(read_rep_offsets(&data_rep, value, NULL, FALSE, pool));
+
+ /* If the directory's data representation wasn't changed in this revision,
+ we've already scanned the directory's contents for noderevs, so we don't
+ need to again. This will occur if a property is changed on a directory
+ without changing the directory's contents. */
+ if (data_rep->revision != rev)
+ return SVN_NO_ERROR;
+
+ /* We could use get_dir_contents(), but this is much cheaper. It does
+ rely on directory entries being stored as PLAIN reps, though. */
+ offset = data_rep->offset;
+ SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+ SVN_ERR(read_rep_line(&ra, rev_file, pool));
+ if (ra->is_delta)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Recovery encountered a deltified directory "
+ "representation"));
+
+ /* Now create a stream that's allowed to read only as much data as is
+ stored in the representation. */
+ baton.file = rev_file;
+ baton.pool = pool;
+ baton.remaining = data_rep->expanded_size;
+ stream = svn_stream_create(&baton, pool);
+ svn_stream_set_read(stream, read_handler_recover);
+
+ /* Now read the entries from that stream. */
+ entries = apr_hash_make(pool);
+ SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ /* Now check each of the entries in our directory to find new node and
+ copy ids, and recurse into new subdirectories. */
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ char *str_val;
+ char *str;
+ svn_node_kind_t kind;
+ svn_fs_id_t *id;
+ const char *node_id, *copy_id;
+ apr_off_t child_dir_offset;
+ const svn_string_t *path = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ str_val = apr_pstrdup(iterpool, path->data);
+
+ str = svn_cstring_tokenize(" ", &str_val);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt"));
+
+ if (strcmp(str, KIND_FILE) == 0)
+ kind = svn_node_file;
+ else if (strcmp(str, KIND_DIR) == 0)
+ kind = svn_node_dir;
+ else
+ {
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt"));
+ }
+
+ str = svn_cstring_tokenize(" ", &str_val);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Directory entry corrupt"));
+
+ id = svn_fs_fs__id_parse(str, strlen(str), iterpool);
+
+ if (svn_fs_fs__id_rev(id) != rev)
+ {
+ /* If the node wasn't modified in this revision, we've already
+ checked the node and copy id. */
+ continue;
+ }
+
+ node_id = svn_fs_fs__id_node_id(id);
+ copy_id = svn_fs_fs__id_copy_id(id);
+
+ if (svn_fs_fs__key_compare(node_id, max_node_id) > 0)
+ {
+ SVN_ERR_ASSERT(strlen(node_id) < MAX_KEY_SIZE);
+ apr_cpystrn(max_node_id, node_id, MAX_KEY_SIZE);
+ }
+ if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0)
+ {
+ SVN_ERR_ASSERT(strlen(copy_id) < MAX_KEY_SIZE);
+ apr_cpystrn(max_copy_id, copy_id, MAX_KEY_SIZE);
+ }
+
+ if (kind == svn_node_file)
+ continue;
+
+ child_dir_offset = svn_fs_fs__id_offset(id);
+ SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
+ max_node_id, max_copy_id, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
+ * Use POOL for temporary allocations.
+ * Set *MISSING, if the reason is a missing manifest or pack file.
+ */
+static svn_boolean_t
+packed_revprop_available(svn_boolean_t *missing,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_stringbuf_t *content = NULL;
+
+ /* try to read the manifest file */
+ const char *folder = path_revprops_pack_shard(fs, revision, pool);
+ const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool);
+
+ svn_error_t *err = try_stringbuf_from_file(&content,
+ missing,
+ manifest_path,
+ FALSE,
+ pool);
+
+ /* if the manifest cannot be read, consider the pack files inaccessible
+ * even if the file itself exists. */
+ if (err)
+ {
+ svn_error_clear(err);
+ return FALSE;
+ }
+
+ if (*missing)
+ return FALSE;
+
+ /* parse manifest content until we find the entry for REVISION.
+ * Revision 0 is never packed. */
+ revision = revision < ffd->max_files_per_dir
+ ? revision - 1
+ : revision % ffd->max_files_per_dir;
+ while (content->data)
+ {
+ char *next = strchr(content->data, '\n');
+ if (next)
+ {
+ *next = 0;
+ ++next;
+ }
+
+ if (revision-- == 0)
+ {
+ /* the respective pack file must exist (and be a file) */
+ svn_node_kind_t kind;
+ err = svn_io_check_path(svn_dirent_join(folder, content->data,
+ pool),
+ &kind, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return FALSE;
+ }
+
+ *missing = kind == svn_node_none;
+ return kind == svn_node_file;
+ }
+
+ content->data = next;
+ }
+
+ return FALSE;
+}
+
+/* Baton used for recover_body below. */
+struct recover_baton {
+ svn_fs_t *fs;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+};
+
+/* The work-horse for svn_fs_fs__recover, called with the FS
+ write lock. This implements the svn_fs_fs__with_write_lock()
+ 'body' callback type. BATON is a 'struct recover_baton *'. */
+static svn_error_t *
+recover_body(void *baton, apr_pool_t *pool)
+{
+ struct recover_baton *b = baton;
+ svn_fs_t *fs = b->fs;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_revnum_t max_rev;
+ char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE];
+ char *next_node_id = NULL, *next_copy_id = NULL;
+ svn_revnum_t youngest_rev;
+ svn_node_kind_t youngest_revprops_kind;
+
+ /* Lose potentially corrupted data in temp files */
+ SVN_ERR(cleanup_revprop_namespace(fs));
+
+ /* We need to know the largest revision in the filesystem. */
+ SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
+
+ /* Get the expected youngest revision */
+ SVN_ERR(get_youngest(&youngest_rev, fs->path, pool));
+
+ /* Policy note:
+
+ Since the revprops file is written after the revs file, the true
+ maximum available revision is the youngest one for which both are
+ present. That's probably the same as the max_rev we just found,
+ but if it's not, we could, in theory, repeatedly decrement
+ max_rev until we find a revision that has both a revs and
+ revprops file, then write db/current with that.
+
+ But we choose not to. If a repository is so corrupt that it's
+ missing at least one revprops file, we shouldn't assume that the
+ youngest revision for which both the revs and revprops files are
+ present is healthy. In other words, we're willing to recover
+ from a missing or out-of-date db/current file, because db/current
+ is truly redundant -- it's basically a cache so we don't have to
+ find max_rev each time, albeit a cache with unusual semantics,
+ since it also officially defines when a revision goes live. But
+ if we're missing more than the cache, it's time to back out and
+ let the admin reconstruct things by hand: correctness at that
+ point may depend on external things like checking a commit email
+ list, looking in particular working copies, etc.
+
+ This policy matches well with a typical naive backup scenario.
+ Say you're rsyncing your FSFS repository nightly to the same
+ location. Once revs and revprops are written, you've got the
+ maximum rev; if the backup should bomb before db/current is
+ written, then db/current could stay arbitrarily out-of-date, but
+ we can still recover. It's a small window, but we might as well
+ do what we can. */
+
+ /* Even if db/current were missing, it would be created with 0 by
+ get_youngest(), so this conditional remains valid. */
+ if (youngest_rev > max_rev)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Expected current rev to be <= %ld "
+ "but found %ld"), max_rev, youngest_rev);
+
+ /* We only need to search for maximum IDs for old FS formats which
+ se global ID counters. */
+ if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ {
+ /* Next we need to find the maximum node id and copy id in use across the
+ filesystem. Unfortunately, the only way we can get this information
+ is to scan all the noderevs of all the revisions and keep track as
+ we go along. */
+ svn_revnum_t rev;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0";
+ apr_size_t len;
+
+ for (rev = 0; rev <= max_rev; rev++)
+ {
+ apr_file_t *rev_file;
+ apr_off_t root_offset;
+
+ svn_pool_clear(iterpool);
+
+ if (b->cancel_func)
+ SVN_ERR(b->cancel_func(b->cancel_baton));
+
+ SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, iterpool));
+ SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, fs, rev,
+ iterpool));
+ SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
+ max_node_id, max_copy_id, iterpool));
+ SVN_ERR(svn_io_file_close(rev_file, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Now that we finally have the maximum revision, node-id and copy-id, we
+ can bump the two ids to get the next of each. */
+ len = strlen(max_node_id);
+ svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf);
+ next_node_id = next_node_id_buf;
+ len = strlen(max_copy_id);
+ svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf);
+ next_copy_id = next_copy_id_buf;
+ }
+
+ /* Before setting current, verify that there is a revprops file
+ for the youngest revision. (Issue #2992) */
+ SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool),
+ &youngest_revprops_kind, pool));
+ if (youngest_revprops_kind == svn_node_none)
+ {
+ svn_boolean_t missing = TRUE;
+ if (!packed_revprop_available(&missing, fs, max_rev, pool))
+ {
+ if (missing)
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision %ld has a revs file but no "
+ "revprops file"),
+ max_rev);
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision %ld has a revs file but the "
+ "revprops file is inaccessible"),
+ max_rev);
+ }
+ }
+ }
+ else if (youngest_revprops_kind != svn_node_file)
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision %ld has a non-file where its "
+ "revprops file should be"),
+ max_rev);
+ }
+
+ /* Prune younger-than-(newfound-youngest) revisions from the rep
+ cache if sharing is enabled taking care not to create the cache
+ if it does not exist. */
+ if (ffd->rep_sharing_allowed)
+ {
+ svn_boolean_t rep_cache_exists;
+
+ SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool));
+ if (rep_cache_exists)
+ SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool));
+ }
+
+ /* Now store the discovered youngest revision, and the next IDs if
+ relevant, in a new 'current' file. */
+ return write_current(fs, max_rev, next_node_id, next_copy_id, pool);
+}
+
+/* This implements the fs_library_vtable_t.recover() API. */
+svn_error_t *
+svn_fs_fs__recover(svn_fs_t *fs,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool)
+{
+ struct recover_baton b;
+
+ /* We have no way to take out an exclusive lock in FSFS, so we're
+ restricted as to the types of recovery we can do. Luckily,
+ we just want to recreate the 'current' file, and we can do that just
+ by blocking other writers. */
+ b.fs = fs;
+ b.cancel_func = cancel_func;
+ b.cancel_baton = cancel_baton;
+ return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool);
+}
+
+svn_error_t *
+svn_fs_fs__set_uuid(svn_fs_t *fs,
+ const char *uuid,
+ apr_pool_t *pool)
+{
+ char *my_uuid;
+ apr_size_t my_uuid_len;
+ const char *tmp_path;
+ const char *uuid_path = path_uuid(fs, pool);
+
+ if (! uuid)
+ uuid = svn_uuid_generate(pool);
+
+ /* Make sure we have a copy in FS->POOL, and append a newline. */
+ my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL);
+ my_uuid_len = strlen(my_uuid);
+
+ SVN_ERR(svn_io_write_unique(&tmp_path,
+ svn_dirent_dirname(uuid_path, pool),
+ my_uuid, my_uuid_len,
+ svn_io_file_del_none, pool));
+
+ /* We use the permissions of the 'current' file, because the 'uuid'
+ file does not exist during repository creation. */
+ SVN_ERR(move_into_place(tmp_path, uuid_path,
+ svn_fs_fs__path_current(fs, pool), pool));
+
+ /* Remove the newline we added, and stash the UUID. */
+ my_uuid[my_uuid_len - 1] = '\0';
+ fs->uuid = my_uuid;
+
+ return SVN_NO_ERROR;
+}
+
+/** Node origin lazy cache. */
+
+/* If directory PATH does not exist, create it and give it the same
+ permissions as FS_path.*/
+svn_error_t *
+svn_fs_fs__ensure_dir_exists(const char *path,
+ const char *fs_path,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
+ if (err && APR_STATUS_IS_EEXIST(err->apr_err))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* We successfully created a new directory. Dup the permissions
+ from FS->path. */
+ return svn_io_copy_perms(fs_path, path, pool);
+}
+
+/* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
+ 'svn_string_t *' node revision IDs. Use POOL for allocations. */
+static svn_error_t *
+get_node_origins_from_file(svn_fs_t *fs,
+ apr_hash_t **node_origins,
+ const char *node_origins_file,
+ apr_pool_t *pool)
+{
+ apr_file_t *fd;
+ svn_error_t *err;
+ svn_stream_t *stream;
+
+ *node_origins = NULL;
+ err = svn_io_file_open(&fd, node_origins_file,
+ APR_READ, APR_OS_DEFAULT, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ stream = svn_stream_from_aprfile2(fd, FALSE, pool);
+ *node_origins = apr_hash_make(pool);
+ SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool));
+ return svn_stream_close(stream);
+}
+
+svn_error_t *
+svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
+ svn_fs_t *fs,
+ const char *node_id,
+ apr_pool_t *pool)
+{
+ apr_hash_t *node_origins;
+
+ *origin_id = NULL;
+ SVN_ERR(get_node_origins_from_file(fs, &node_origins,
+ path_node_origin(fs, node_id, pool),
+ pool));
+ if (node_origins)
+ {
+ svn_string_t *origin_id_str =
+ svn_hash_gets(node_origins, node_id);
+ if (origin_id_str)
+ *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
+ origin_id_str->len, pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for svn_fs_fs__set_node_origin. Takes a NODE_ID/NODE_REV_ID
+ pair and adds it to the NODE_ORIGINS_PATH file. */
+static svn_error_t *
+set_node_origins_for_file(svn_fs_t *fs,
+ const char *node_origins_path,
+ const char *node_id,
+ svn_string_t *node_rev_id,
+ apr_pool_t *pool)
+{
+ const char *path_tmp;
+ svn_stream_t *stream;
+ apr_hash_t *origins_hash;
+ svn_string_t *old_node_rev_id;
+
+ SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
+ PATH_NODE_ORIGINS_DIR,
+ pool),
+ fs->path, pool));
+
+ /* Read the previously existing origins (if any), and merge our
+ update with it. */
+ SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
+ node_origins_path, pool));
+ if (! origins_hash)
+ origins_hash = apr_hash_make(pool);
+
+ old_node_rev_id = svn_hash_gets(origins_hash, node_id);
+
+ if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Node origin for '%s' exists with a different "
+ "value (%s) than what we were about to store "
+ "(%s)"),
+ node_id, old_node_rev_id->data, node_rev_id->data);
+
+ svn_hash_sets(origins_hash, node_id, node_rev_id);
+
+ /* Sure, there's a race condition here. Two processes could be
+ trying to add different cache elements to the same file at the
+ same time, and the entries added by the first one to write will
+ be lost. But this is just a cache of reconstructible data, so
+ we'll accept this problem in return for not having to deal with
+ locking overhead. */
+
+ /* Create a temporary file, write out our hash, and close the file. */
+ SVN_ERR(svn_stream_open_unique(&stream, &path_tmp,
+ svn_dirent_dirname(node_origins_path, pool),
+ svn_io_file_del_none, pool, pool));
+ SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ /* Rename the temp file as the real destination */
+ return svn_io_file_rename(path_tmp, node_origins_path, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__set_node_origin(svn_fs_t *fs,
+ const char *node_id,
+ const svn_fs_id_t *node_rev_id,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ const char *filename = path_node_origin(fs, node_id, pool);
+
+ err = set_node_origins_for_file(fs, filename,
+ node_id,
+ svn_fs_fs__id_unparse(node_rev_id, pool),
+ pool);
+ if (err && APR_STATUS_IS_EACCES(err->apr_err))
+ {
+ /* It's just a cache; stop trying if I can't write. */
+ svn_error_clear(err);
+ err = NULL;
+ }
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_fs_fs__list_transactions(apr_array_header_t **names_p,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ const char *txn_dir;
+ apr_hash_t *dirents;
+ apr_hash_index_t *hi;
+ apr_array_header_t *names;
+ apr_size_t ext_len = strlen(PATH_EXT_TXN);
+
+ names = apr_array_make(pool, 1, sizeof(const char *));
+
+ /* Get the transactions directory. */
+ txn_dir = svn_dirent_join(fs->path, PATH_TXNS_DIR, pool);
+
+ /* Now find a listing of this directory. */
+ SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
+
+ /* Loop through all the entries and return anything that ends with '.txn'. */
+ for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ apr_ssize_t klen = svn__apr_hash_index_klen(hi);
+ const char *id;
+
+ /* The name must end with ".txn" to be considered a transaction. */
+ if ((apr_size_t) klen <= ext_len
+ || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
+ continue;
+
+ /* Truncate the ".txn" extension and store the ID. */
+ id = apr_pstrndup(pool, name, strlen(name) - ext_len);
+ APR_ARRAY_PUSH(names, const char *) = id;
+ }
+
+ *names_p = names;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ const char *name,
+ apr_pool_t *pool)
+{
+ svn_fs_txn_t *txn;
+ svn_node_kind_t kind;
+ transaction_t *local_txn;
+
+ /* First check to see if the directory exists. */
+ SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool));
+
+ /* Did we find it? */
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
+ _("No such transaction '%s'"),
+ name);
+
+ txn = apr_pcalloc(pool, sizeof(*txn));
+
+ /* Read in the root node of this transaction. */
+ txn->id = apr_pstrdup(pool, name);
+ txn->fs = fs;
+
+ SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool));
+
+ txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
+
+ txn->vtable = &txn_vtable;
+ *txn_p = txn;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__txn_proplist(apr_hash_t **table_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ apr_hash_t *proplist = apr_hash_make(pool);
+ SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool));
+ *table_p = proplist;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__delete_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
+
+ /* Delete any mutable property representation. */
+ if (noderev->prop_rep && noderev->prop_rep->txn_id)
+ SVN_ERR(svn_io_remove_file2(path_txn_node_props(fs, id, pool), FALSE,
+ pool));
+
+ /* Delete any mutable data representation. */
+ if (noderev->data_rep && noderev->data_rep->txn_id
+ && noderev->kind == svn_node_dir)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ SVN_ERR(svn_io_remove_file2(path_txn_node_children(fs, id, pool), FALSE,
+ pool));
+
+ /* remove the corresponding entry from the cache, if such exists */
+ if (ffd->txn_dir_cache)
+ {
+ const char *key = svn_fs_fs__id_unparse(id, pool)->data;
+ SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
+ }
+ }
+
+ return svn_io_remove_file2(path_txn_node_rev(fs, id, pool), FALSE, pool);
+}
+
+
+
+/*** Revisions ***/
+
+svn_error_t *
+svn_fs_fs__revision_prop(svn_string_t **value_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ apr_hash_t *table;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool));
+
+ *value_p = svn_hash_gets(table, propname);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Baton used for change_rev_prop_body below. */
+struct change_rev_prop_baton {
+ svn_fs_t *fs;
+ svn_revnum_t rev;
+ const char *name;
+ const svn_string_t *const *old_value_p;
+ const svn_string_t *value;
+};
+
+/* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
+ write lock. This implements the svn_fs_fs__with_write_lock()
+ 'body' callback type. BATON is a 'struct change_rev_prop_baton *'. */
+static svn_error_t *
+change_rev_prop_body(void *baton, apr_pool_t *pool)
+{
+ struct change_rev_prop_baton *cb = baton;
+ apr_hash_t *table;
+
+ SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool));
+
+ if (cb->old_value_p)
+ {
+ const svn_string_t *wanted_value = *cb->old_value_p;
+ const svn_string_t *present_value = svn_hash_gets(table, cb->name);
+ if ((!wanted_value != !present_value)
+ || (wanted_value && present_value
+ && !svn_string_compare(wanted_value, present_value)))
+ {
+ /* What we expected isn't what we found. */
+ return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
+ _("revprop '%s' has unexpected value in "
+ "filesystem"),
+ cb->name);
+ }
+ /* Fall through. */
+ }
+ svn_hash_sets(table, cb->name, cb->value);
+
+ return set_revision_proplist(cb->fs, cb->rev, table, pool);
+}
+
+svn_error_t *
+svn_fs_fs__change_rev_prop(svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct change_rev_prop_baton cb;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ cb.fs = fs;
+ cb.rev = rev;
+ cb.name = name;
+ cb.old_value_p = old_value_p;
+ cb.value = value;
+
+ return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
+}
+
+
+
+/*** Transactions ***/
+
+svn_error_t *
+svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
+ const svn_fs_id_t **base_root_id_p,
+ svn_fs_t *fs,
+ const char *txn_name,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+ SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
+ *root_id_p = txn->root_id;
+ *base_root_id_p = txn->base_id;
+ return SVN_NO_ERROR;
+}
+
+
+/* Generic transaction operations. */
+
+svn_error_t *
+svn_fs_fs__txn_prop(svn_string_t **value_p,
+ svn_fs_txn_t *txn,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ apr_hash_t *table;
+ svn_fs_t *fs = txn->fs;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
+
+ *value_p = svn_hash_gets(table, propname);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ svn_string_t date;
+ svn_prop_t prop;
+ apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
+
+ /* Put a datestamp on the newly created txn, so we always know
+ exactly how old it is. (This will help sysadmins identify
+ long-abandoned txns that may need to be manually removed.) When
+ a txn is promoted to a revision, this property will be
+ automatically overwritten with a revision datestamp. */
+ date.data = svn_time_to_cstring(apr_time_now(), pool);
+ date.len = strlen(date.data);
+
+ prop.name = SVN_PROP_REVISION_DATE;
+ prop.value = &date;
+ APR_ARRAY_PUSH(props, svn_prop_t) = prop;
+
+ /* Set temporary txn props that represent the requested 'flags'
+ behaviors. */
+ if (flags & SVN_FS_TXN_CHECK_OOD)
+ {
+ prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
+ prop.value = svn_string_create("true", pool);
+ APR_ARRAY_PUSH(props, svn_prop_t) = prop;
+ }
+
+ if (flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
+ prop.value = svn_string_create("true", pool);
+ APR_ARRAY_PUSH(props, svn_prop_t) = prop;
+ }
+
+ return svn_fs_fs__change_txn_props(*txn_p, props, pool);
+}
+
+
+/****** Packing FSFS shards *********/
+
+/* Write a file FILENAME in directory FS_PATH, containing a single line
+ * with the number REVNUM in ASCII decimal. Move the file into place
+ * atomically, overwriting any existing file.
+ *
+ * Similar to write_current(). */
+static svn_error_t *
+write_revnum_file(const char *fs_path,
+ const char *filename,
+ svn_revnum_t revnum,
+ apr_pool_t *scratch_pool)
+{
+ const char *final_path, *tmp_path;
+ svn_stream_t *tmp_stream;
+
+ final_path = svn_dirent_join(fs_path, filename, scratch_pool);
+ SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum));
+ SVN_ERR(svn_stream_close(tmp_stream));
+ SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions
+ * from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations.
+ * CANCEL_FUNC and CANCEL_BATON are what you think they are.
+ *
+ * If for some reason we detect a partial packing already performed, we
+ * remove the pack file and start again.
+ */
+static svn_error_t *
+pack_rev_shard(const char *pack_file_dir,
+ const char *shard_path,
+ apr_int64_t shard,
+ int max_files_per_dir,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const char *pack_file_path, *manifest_file_path;
+ svn_stream_t *pack_stream, *manifest_stream;
+ svn_revnum_t start_rev, end_rev, rev;
+ apr_off_t next_offset;
+ apr_pool_t *iterpool;
+
+ /* Some useful paths. */
+ pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
+ manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool);
+
+ /* Remove any existing pack file for this shard, since it is incomplete. */
+ SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
+ pool));
+
+ /* Create the new directory and pack and manifest files. */
+ SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool));
+ SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool,
+ pool));
+ SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
+ pool, pool));
+
+ start_rev = (svn_revnum_t) (shard * max_files_per_dir);
+ end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
+ next_offset = 0;
+ iterpool = svn_pool_create(pool);
+
+ /* Iterate over the revisions in this shard, squashing them together. */
+ for (rev = start_rev; rev <= end_rev; rev++)
+ {
+ svn_stream_t *rev_stream;
+ apr_finfo_t finfo;
+ const char *path;
+
+ svn_pool_clear(iterpool);
+
+ /* Get the size of the file. */
+ path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
+ iterpool);
+ SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
+
+ /* Update the manifest. */
+ SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT
+ "\n", next_offset));
+ next_offset += finfo.size;
+
+ /* Copy all the bits from the rev file to the end of the pack file. */
+ SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool));
+ SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream,
+ iterpool),
+ cancel_func, cancel_baton, iterpool));
+ }
+
+ SVN_ERR(svn_stream_close(manifest_stream));
+ SVN_ERR(svn_stream_close(pack_stream));
+ SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
+ SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool));
+ SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH
+ * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR.
+ *
+ * The file sizes have already been determined and written to SIZES.
+ * Please note that this function will be executed while the filesystem
+ * has been locked and that revprops files will therefore not be modified
+ * while the pack is in progress.
+ *
+ * COMPRESSION_LEVEL defines how well the resulting pack file shall be
+ * compressed or whether is shall be compressed at all. TOTAL_SIZE is
+ * a hint on which initial buffer size we should use to hold the pack file
+ * content.
+ *
+ * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations
+ * are done in SCRATCH_POOL.
+ */
+static svn_error_t *
+copy_revprops(const char *pack_file_dir,
+ const char *pack_filename,
+ const char *shard_path,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ apr_array_header_t *sizes,
+ apr_size_t total_size,
+ int compression_level,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *pack_stream;
+ apr_file_t *pack_file;
+ svn_revnum_t rev;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_stream_t *stream;
+
+ /* create empty data buffer and a write stream on top of it */
+ svn_stringbuf_t *uncompressed
+ = svn_stringbuf_create_ensure(total_size, scratch_pool);
+ svn_stringbuf_t *compressed
+ = svn_stringbuf_create_empty(scratch_pool);
+ pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
+
+ /* write the pack file header */
+ SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
+ sizes->nelts, iterpool));
+
+ /* Some useful paths. */
+ SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
+ pack_filename,
+ scratch_pool),
+ APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
+ scratch_pool));
+
+ /* Iterate over the revisions in this shard, squashing them together. */
+ for (rev = start_rev; rev <= end_rev; rev++)
+ {
+ const char *path;
+
+ svn_pool_clear(iterpool);
+
+ /* Construct the file name. */
+ path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
+ iterpool);
+
+ /* Copy all the bits from the non-packed revprop file to the end of
+ * the pack file. */
+ SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
+ SVN_ERR(svn_stream_copy3(stream, pack_stream,
+ cancel_func, cancel_baton, iterpool));
+ }
+
+ /* flush stream buffers to content buffer */
+ SVN_ERR(svn_stream_close(pack_stream));
+
+ /* compress the content (or just store it for COMPRESSION_LEVEL 0) */
+ SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
+ compressed, compression_level));
+
+ /* write the pack file content to disk */
+ stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool);
+ SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len));
+ SVN_ERR(svn_stream_close(stream));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR
+ * revprop files in it, create a packed shared at PACK_FILE_DIR.
+ *
+ * COMPRESSION_LEVEL defines how well the resulting pack file shall be
+ * compressed or whether is shall be compressed at all. Individual pack
+ * file containing more than one revision will be limited to a size of
+ * MAX_PACK_SIZE bytes before compression.
+ *
+ * CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary
+ * allocations are done in SCRATCH_POOL.
+ */
+static svn_error_t *
+pack_revprops_shard(const char *pack_file_dir,
+ const char *shard_path,
+ apr_int64_t shard,
+ int max_files_per_dir,
+ apr_off_t max_pack_size,
+ int compression_level,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *manifest_file_path, *pack_filename = NULL;
+ svn_stream_t *manifest_stream;
+ svn_revnum_t start_rev, end_rev, rev;
+ apr_off_t total_size;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *sizes;
+
+ /* Some useful paths. */
+ manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
+ scratch_pool);
+
+ /* Remove any existing pack file for this shard, since it is incomplete. */
+ SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* Create the new directory and manifest file stream. */
+ SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
+ SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
+ scratch_pool, scratch_pool));
+
+ /* revisions to handle. Special case: revision 0 */
+ start_rev = (svn_revnum_t) (shard * max_files_per_dir);
+ end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
+ if (start_rev == 0)
+ ++start_rev;
+
+ /* initialize the revprop size info */
+ sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
+ total_size = 2 * SVN_INT64_BUFFER_SIZE;
+
+ /* Iterate over the revisions in this shard, determine their size and
+ * squashing them together into pack files. */
+ for (rev = start_rev; rev <= end_rev; rev++)
+ {
+ apr_finfo_t finfo;
+ const char *path;
+
+ svn_pool_clear(iterpool);
+
+ /* Get the size of the file. */
+ path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
+ iterpool);
+ SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
+
+ /* if we already have started a pack file and this revprop cannot be
+ * appended to it, write the previous pack file. */
+ if (sizes->nelts != 0 &&
+ total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
+ {
+ SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
+ start_rev, rev-1, sizes, (apr_size_t)total_size,
+ compression_level, cancel_func, cancel_baton,
+ iterpool));
+
+ /* next pack file starts empty again */
+ apr_array_clear(sizes);
+ total_size = 2 * SVN_INT64_BUFFER_SIZE;
+ start_rev = rev;
+ }
+
+ /* Update the manifest. Allocate a file name for the current pack
+ * file if it is a new one */
+ if (sizes->nelts == 0)
+ pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
+
+ SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
+ pack_filename));
+
+ /* add to list of files to put into the current pack file */
+ APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
+ total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
+ }
+
+ /* write the last pack file */
+ if (sizes->nelts != 0)
+ SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
+ start_rev, rev-1, sizes, (apr_size_t)total_size,
+ compression_level, cancel_func, cancel_baton,
+ iterpool));
+
+ /* flush the manifest file and update permissions */
+ SVN_ERR(svn_stream_close(manifest_stream));
+ SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Delete the non-packed revprop SHARD at SHARD_PATH with exactly
+ * MAX_FILES_PER_DIR revprop files in it. If this is shard 0, keep the
+ * revprop file for revision 0.
+ *
+ * CANCEL_FUNC and CANCEL_BATON are used in the usual way. Temporary
+ * allocations are done in SCRATCH_POOL.
+ */
+static svn_error_t *
+delete_revprops_shard(const char *shard_path,
+ apr_int64_t shard,
+ int max_files_per_dir,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ if (shard == 0)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ /* delete all files except the one for revision 0 */
+ for (i = 1; i < max_files_per_dir; ++i)
+ {
+ const char *path = svn_dirent_join(shard_path,
+ apr_psprintf(iterpool, "%d", i),
+ iterpool);
+ if (cancel_func)
+ SVN_ERR((*cancel_func)(cancel_baton));
+
+ SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
+ svn_pool_clear(iterpool);
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+ else
+ SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
+ cancel_func, cancel_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* In the file system at FS_PATH, pack the SHARD in REVS_DIR and
+ * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL
+ * for allocations. REVPROPS_DIR will be NULL if revprop packing is not
+ * supported. COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that
+ * case.
+ *
+ * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly
+ * NOTIFY_FUNC and NOTIFY_BATON.
+ *
+ * If for some reason we detect a partial packing already performed, we
+ * remove the pack file and start again.
+ */
+static svn_error_t *
+pack_shard(const char *revs_dir,
+ const char *revsprops_dir,
+ const char *fs_path,
+ apr_int64_t shard,
+ int max_files_per_dir,
+ apr_off_t max_pack_size,
+ int compression_level,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const char *rev_shard_path, *rev_pack_file_dir;
+ const char *revprops_shard_path, *revprops_pack_file_dir;
+
+ /* Notify caller we're starting to pack this shard. */
+ if (notify_func)
+ SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start,
+ pool));
+
+ /* Some useful paths. */
+ rev_pack_file_dir = svn_dirent_join(revs_dir,
+ apr_psprintf(pool,
+ "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
+ shard),
+ pool);
+ rev_shard_path = svn_dirent_join(revs_dir,
+ apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
+ pool);
+
+ /* pack the revision content */
+ SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path,
+ shard, max_files_per_dir,
+ cancel_func, cancel_baton, pool));
+
+ /* if enabled, pack the revprops in an equivalent way */
+ if (revsprops_dir)
+ {
+ revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
+ apr_psprintf(pool,
+ "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
+ shard),
+ pool);
+ revprops_shard_path = svn_dirent_join(revsprops_dir,
+ apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
+ pool);
+
+ SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
+ shard, max_files_per_dir,
+ (int)(0.9 * max_pack_size),
+ compression_level,
+ cancel_func, cancel_baton, pool));
+ }
+
+ /* Update the min-unpacked-rev file to reflect our newly packed shard.
+ * (This doesn't update ffd->min_unpacked_rev. That will be updated by
+ * update_min_unpacked_rev() when necessary.) */
+ SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV,
+ (svn_revnum_t)((shard + 1) * max_files_per_dir),
+ pool));
+
+ /* Finally, remove the existing shard directories. */
+ SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE,
+ cancel_func, cancel_baton, pool));
+ if (revsprops_dir)
+ SVN_ERR(delete_revprops_shard(revprops_shard_path,
+ shard, max_files_per_dir,
+ cancel_func, cancel_baton, pool));
+
+ /* Notify caller we're starting to pack this shard. */
+ if (notify_func)
+ SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+struct pack_baton
+{
+ svn_fs_t *fs;
+ svn_fs_pack_notify_t notify_func;
+ void *notify_baton;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+};
+
+
+/* The work-horse for svn_fs_fs__pack, called with the FS write lock.
+ This implements the svn_fs_fs__with_write_lock() 'body' callback
+ type. BATON is a 'struct pack_baton *'.
+
+ WARNING: if you add a call to this function, please note:
+ The code currently assumes that any piece of code running with
+ the write-lock set can rely on the ffd->min_unpacked_rev and
+ ffd->min_unpacked_revprop caches to be up-to-date (and, by
+ extension, on not having to use a retry when calling
+ svn_fs_fs__path_rev_absolute() and friends). If you add a call
+ to this function, consider whether you have to call
+ update_min_unpacked_rev().
+ See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith
+ */
+static svn_error_t *
+pack_body(void *baton,
+ apr_pool_t *pool)
+{
+ struct pack_baton *pb = baton;
+ fs_fs_data_t ffd = {0};
+ apr_int64_t completed_shards;
+ apr_int64_t i;
+ svn_revnum_t youngest;
+ apr_pool_t *iterpool;
+ const char *rev_data_path;
+ const char *revprops_data_path = NULL;
+
+ /* read repository settings */
+ SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir,
+ path_format(pb->fs, pool), pool));
+ SVN_ERR(check_format(ffd.format));
+ SVN_ERR(read_config(&ffd, pb->fs->path, pool));
+
+ /* If the repository isn't a new enough format, we don't support packing.
+ Return a friendly error to that effect. */
+ if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("FSFS format (%d) too old to pack; please upgrade the filesystem."),
+ ffd.format);
+
+ /* If we aren't using sharding, we can't do any packing, so quit. */
+ if (!ffd.max_files_per_dir)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev,
+ path_min_unpacked_rev(pb->fs, pool),
+ pool));
+
+ SVN_ERR(get_youngest(&youngest, pb->fs->path, pool));
+ completed_shards = (youngest + 1) / ffd.max_files_per_dir;
+
+ /* See if we've already completed all possible shards thus far. */
+ if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir))
+ return SVN_NO_ERROR;
+
+ rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
+ if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
+ revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
+ pool);
+
+ iterpool = svn_pool_create(pool);
+ for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir;
+ i < completed_shards;
+ i++)
+ {
+ svn_pool_clear(iterpool);
+
+ if (pb->cancel_func)
+ SVN_ERR(pb->cancel_func(pb->cancel_baton));
+
+ SVN_ERR(pack_shard(rev_data_path, revprops_data_path,
+ pb->fs->path, i, ffd.max_files_per_dir,
+ ffd.revprop_pack_size,
+ ffd.compress_packed_revprops
+ ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
+ : SVN_DELTA_COMPRESSION_LEVEL_NONE,
+ pb->notify_func, pb->notify_baton,
+ pb->cancel_func, pb->cancel_baton, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__pack(svn_fs_t *fs,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ struct pack_baton pb = { 0 };
+ pb.fs = fs;
+ pb.notify_func = notify_func;
+ pb.notify_baton = notify_baton;
+ pb.cancel_func = cancel_func;
+ pb.cancel_baton = cancel_baton;
+ return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool);
+}
+
+
+/** Verifying. **/
+
+/* Baton type expected by verify_walker(). The purpose is to reuse open
+ * rev / pack file handles between calls. Its contents need to be cleaned
+ * periodically to limit resource usage.
+ */
+typedef struct verify_walker_baton_t
+{
+ /* number of calls to verify_walker() since the last clean */
+ int iteration_count;
+
+ /* number of files opened since the last clean */
+ int file_count;
+
+ /* progress notification callback to invoke periodically (may be NULL) */
+ svn_fs_progress_notify_func_t notify_func;
+
+ /* baton to use with NOTIFY_FUNC */
+ void *notify_baton;
+
+ /* remember the last revision for which we called notify_func */
+ svn_revnum_t last_notified_revision;
+
+ /* current file handle (or NULL) */
+ apr_file_t *file_hint;
+
+ /* corresponding revision (or SVN_INVALID_REVNUM) */
+ svn_revnum_t rev_hint;
+
+ /* pool to use for the file handles etc. */
+ apr_pool_t *pool;
+} verify_walker_baton_t;
+
+/* Used by svn_fs_fs__verify().
+ Implements svn_fs_fs__walk_rep_reference().walker. */
+static svn_error_t *
+verify_walker(representation_t *rep,
+ void *baton,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ struct rep_state *rs;
+ struct rep_args *rep_args;
+
+ if (baton)
+ {
+ verify_walker_baton_t *walker_baton = baton;
+ apr_file_t * previous_file;
+
+ /* notify and free resources periodically */
+ if ( walker_baton->iteration_count > 1000
+ || walker_baton->file_count > 16)
+ {
+ if ( walker_baton->notify_func
+ && rep->revision != walker_baton->last_notified_revision)
+ {
+ walker_baton->notify_func(rep->revision,
+ walker_baton->notify_baton,
+ scratch_pool);
+ walker_baton->last_notified_revision = rep->revision;
+ }
+
+ svn_pool_clear(walker_baton->pool);
+
+ walker_baton->iteration_count = 0;
+ walker_baton->file_count = 0;
+ walker_baton->file_hint = NULL;
+ walker_baton->rev_hint = SVN_INVALID_REVNUM;
+ }
+
+ /* access the repo data */
+ previous_file = walker_baton->file_hint;
+ SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint,
+ &walker_baton->rev_hint, rep, fs,
+ walker_baton->pool));
+
+ /* update resource usage counters */
+ walker_baton->iteration_count++;
+ if (previous_file != walker_baton->file_hint)
+ walker_baton->file_count++;
+ }
+ else
+ {
+ /* ### Should this be using read_rep_line() directly? */
+ SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__verify(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_boolean_t exists;
+ svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
+
+ if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
+ return SVN_NO_ERROR;
+
+ /* Input validation. */
+ if (! SVN_IS_VALID_REVNUM(start))
+ start = 0;
+ if (! SVN_IS_VALID_REVNUM(end))
+ end = youngest;
+ SVN_ERR(ensure_revision_exists(fs, start, pool));
+ SVN_ERR(ensure_revision_exists(fs, end, pool));
+
+ /* rep-cache verification. */
+ SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
+ if (exists)
+ {
+ /* provide a baton to allow the reuse of open file handles between
+ iterations (saves 2/3 of OS level file operations). */
+ verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
+ baton->rev_hint = SVN_INVALID_REVNUM;
+ baton->pool = svn_pool_create(pool);
+ baton->last_notified_revision = SVN_INVALID_REVNUM;
+ baton->notify_func = notify_func;
+ baton->notify_baton = notify_baton;
+
+ /* tell the user that we are now ready to do *something* */
+ if (notify_func)
+ notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool);
+
+ /* Do not attempt to walk the rep-cache database if its file does
+ not exist, since doing so would create it --- which may confuse
+ the administrator. Don't take any lock. */
+ SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end,
+ verify_walker, baton,
+ cancel_func, cancel_baton,
+ pool));
+
+ /* walker resource cleanup */
+ svn_pool_destroy(baton->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/** Hotcopy. **/
+
+/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
+ * the destination and do not differ in terms of kind, size, and mtime. */
+static svn_error_t *
+hotcopy_io_dir_file_copy(const char *src_path,
+ const char *dst_path,
+ const char *file,
+ apr_pool_t *scratch_pool)
+{
+ const svn_io_dirent2_t *src_dirent;
+ const svn_io_dirent2_t *dst_dirent;
+ const char *src_target;
+ const char *dst_target;
+
+ /* Does the destination already exist? If not, we must copy it. */
+ dst_target = svn_dirent_join(dst_path, file, scratch_pool);
+ SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
+ scratch_pool, scratch_pool));
+ if (dst_dirent->kind != svn_node_none)
+ {
+ /* If the destination's stat information indicates that the file
+ * is equal to the source, don't bother copying the file again. */
+ src_target = svn_dirent_join(src_path, file, scratch_pool);
+ SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
+ scratch_pool, scratch_pool));
+ if (src_dirent->kind == dst_dirent->kind &&
+ src_dirent->special == dst_dirent->special &&
+ src_dirent->filesize == dst_dirent->filesize &&
+ src_dirent->mtime <= dst_dirent->mtime)
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
+ scratch_pool));
+}
+
+/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
+ * NAME is in the internal encoding used by APR; PARENT is in
+ * UTF-8 and in internal (not local) style.
+ *
+ * Use PARENT only for generating an error string if the conversion
+ * fails because NAME could not be represented in UTF-8. In that
+ * case, return a two-level error in which the outer error's message
+ * mentions PARENT, but the inner error's message does not mention
+ * NAME (except possibly in hex) since NAME may not be printable.
+ * Such a compound error at least allows the user to go looking in the
+ * right directory for the problem.
+ *
+ * If there is any other error, just return that error directly.
+ *
+ * If there is any error, the effect on *NAME_P is undefined.
+ *
+ * *NAME_P and NAME may refer to the same storage.
+ */
+static svn_error_t *
+entry_name_to_utf8(const char **name_p,
+ const char *name,
+ const char *parent,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
+ if (err && err->apr_err == APR_EINVAL)
+ {
+ return svn_error_createf(err->apr_err, err,
+ _("Error converting entry "
+ "in directory '%s' to UTF-8"),
+ svn_dirent_local_style(parent, pool));
+ }
+ return err;
+}
+
+/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
+ * exist in the destination and do not differ from the source in terms of
+ * kind, size, and mtime. */
+static svn_error_t *
+hotcopy_io_copy_dir_recursively(const char *src,
+ const char *dst_parent,
+ const char *dst_basename,
+ svn_boolean_t copy_perms,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ apr_status_t status;
+ const char *dst_path;
+ apr_dir_t *this_dir;
+ apr_finfo_t this_entry;
+ apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
+
+ /* Make a subpool for recursion */
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* The 'dst_path' is simply dst_parent/dst_basename */
+ dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
+
+ /* Sanity checks: SRC and DST_PARENT are directories, and
+ DST_BASENAME doesn't already exist in DST_PARENT. */
+ SVN_ERR(svn_io_check_path(src, &kind, subpool));
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Source '%s' is not a directory"),
+ svn_dirent_local_style(src, pool));
+
+ SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Destination '%s' is not a directory"),
+ svn_dirent_local_style(dst_parent, pool));
+
+ SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
+
+ /* Create the new directory. */
+ /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
+ SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
+
+ /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */
+ SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
+
+ for (status = apr_dir_read(&this_entry, flags, this_dir);
+ status == APR_SUCCESS;
+ status = apr_dir_read(&this_entry, flags, this_dir))
+ {
+ if ((this_entry.name[0] == '.')
+ && ((this_entry.name[1] == '\0')
+ || ((this_entry.name[1] == '.')
+ && (this_entry.name[2] == '\0'))))
+ {
+ continue;
+ }
+ else
+ {
+ const char *entryname_utf8;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
+ src, subpool));
+ if (this_entry.filetype == APR_REG) /* regular file */
+ {
+ SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8,
+ subpool));
+ }
+ else if (this_entry.filetype == APR_LNK) /* symlink */
+ {
+ const char *src_target = svn_dirent_join(src, entryname_utf8,
+ subpool);
+ const char *dst_target = svn_dirent_join(dst_path,
+ entryname_utf8,
+ subpool);
+ SVN_ERR(svn_io_copy_link(src_target, dst_target,
+ subpool));
+ }
+ else if (this_entry.filetype == APR_DIR) /* recurse */
+ {
+ const char *src_target;
+
+ /* Prevent infinite recursion by filtering off our
+ newly created destination path. */
+ if (strcmp(src, dst_parent) == 0
+ && strcmp(entryname_utf8, dst_basename) == 0)
+ continue;
+
+ src_target = svn_dirent_join(src, entryname_utf8, subpool);
+ SVN_ERR(hotcopy_io_copy_dir_recursively(src_target,
+ dst_path,
+ entryname_utf8,
+ copy_perms,
+ cancel_func,
+ cancel_baton,
+ subpool));
+ }
+ /* ### support other APR node types someday?? */
+
+ }
+ }
+
+ if (! (APR_STATUS_IS_ENOENT(status)))
+ return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
+ svn_dirent_local_style(src, pool));
+
+ status = apr_dir_close(this_dir);
+ if (status)
+ return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
+ svn_dirent_local_style(src, pool));
+
+ /* Free any memory used by recursion */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
+ * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_copy_shard_file(const char *src_subdir,
+ const char *dst_subdir,
+ svn_revnum_t rev,
+ int max_files_per_dir,
+ apr_pool_t *scratch_pool)
+{
+ const char *src_subdir_shard = src_subdir,
+ *dst_subdir_shard = dst_subdir;
+
+ if (max_files_per_dir)
+ {
+ const char *shard = apr_psprintf(scratch_pool, "%ld",
+ rev / max_files_per_dir);
+ src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
+ dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
+
+ if (rev % max_files_per_dir == 0)
+ {
+ SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
+ SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
+ scratch_pool));
+ }
+ }
+
+ SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
+ apr_psprintf(scratch_pool, "%ld", rev),
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Copy a packed shard containing revision REV, and which contains
+ * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
+ * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
+ * Do not re-copy data which already exists in DST_FS.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev,
+ svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ svn_revnum_t rev,
+ int max_files_per_dir,
+ apr_pool_t *scratch_pool)
+{
+ const char *src_subdir;
+ const char *dst_subdir;
+ const char *packed_shard;
+ const char *src_subdir_packed_shard;
+ svn_revnum_t revprop_rev;
+ apr_pool_t *iterpool;
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
+
+ /* Copy the packed shard. */
+ src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
+ dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
+ packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
+ rev / max_files_per_dir);
+ src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
+ scratch_pool);
+ SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
+ dst_subdir, packed_shard,
+ TRUE /* copy_perms */,
+ NULL /* cancel_func */, NULL,
+ scratch_pool));
+
+ /* Copy revprops belonging to revisions in this pack. */
+ src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
+ dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
+
+ if ( src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
+ || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
+ {
+ /* copy unpacked revprops rev by rev */
+ iterpool = svn_pool_create(scratch_pool);
+ for (revprop_rev = rev;
+ revprop_rev < rev + max_files_per_dir;
+ revprop_rev++)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
+ revprop_rev, max_files_per_dir,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+ else
+ {
+ /* revprop for revision 0 will never be packed */
+ if (rev == 0)
+ SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
+ 0, max_files_per_dir,
+ scratch_pool));
+
+ /* packed revprops folder */
+ packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
+ rev / max_files_per_dir);
+ src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
+ scratch_pool);
+ SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
+ dst_subdir, packed_shard,
+ TRUE /* copy_perms */,
+ NULL /* cancel_func */, NULL,
+ scratch_pool));
+ }
+
+ /* If necessary, update the min-unpacked rev file in the hotcopy. */
+ if (*dst_min_unpacked_rev < rev + max_files_per_dir)
+ {
+ *dst_min_unpacked_rev = rev + max_files_per_dir;
+ SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV,
+ *dst_min_unpacked_rev,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current'
+ * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_update_current(svn_revnum_t *dst_youngest,
+ svn_fs_t *dst_fs,
+ svn_revnum_t new_youngest,
+ apr_pool_t *scratch_pool)
+{
+ char next_node_id[MAX_KEY_SIZE] = "0";
+ char next_copy_id[MAX_KEY_SIZE] = "0";
+ fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+
+ if (*dst_youngest >= new_youngest)
+ return SVN_NO_ERROR;
+
+ /* If necessary, get new current next_node and next_copy IDs. */
+ if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+ {
+ apr_off_t root_offset;
+ apr_file_t *rev_file;
+
+ if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool));
+
+ SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest,
+ scratch_pool));
+ SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file,
+ dst_fs, new_youngest, scratch_pool));
+ SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file,
+ root_offset, next_node_id, next_copy_id,
+ scratch_pool));
+ SVN_ERR(svn_io_file_close(rev_file, scratch_pool));
+ }
+
+ /* Update 'current'. */
+ SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id,
+ scratch_pool));
+
+ *dst_youngest = new_youngest;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
+ * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_remove_rev_files(svn_fs_t *dst_fs,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ int max_files_per_dir,
+ apr_pool_t *scratch_pool)
+{
+ const char *dst_subdir;
+ const char *shard;
+ const char *dst_subdir_shard;
+ svn_revnum_t rev;
+ apr_pool_t *iterpool;
+
+ SVN_ERR_ASSERT(start_rev <= end_rev);
+
+ dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
+
+ /* Pre-compute paths for initial shard. */
+ shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
+ dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (rev = start_rev; rev < end_rev; rev++)
+ {
+ const char *rev_path;
+
+ svn_pool_clear(iterpool);
+
+ /* If necessary, update paths for shard. */
+ if (rev != start_rev && rev % max_files_per_dir == 0)
+ {
+ shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
+ dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
+ }
+
+ rev_path = svn_dirent_join(dst_subdir_shard,
+ apr_psprintf(iterpool, "%ld", rev),
+ iterpool);
+
+ /* Make the rev file writable and remove it. */
+ SVN_ERR(svn_io_set_file_read_write(rev_path, TRUE, iterpool));
+ SVN_ERR(svn_io_remove_file2(rev_path, TRUE, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that DST_FS is a suitable destination for an incremental
+ * hotcopy from SRC_FS. */
+static svn_error_t *
+hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
+ fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+
+ /* We only support incremental hotcopy between the same format. */
+ if (src_ffd->format != dst_ffd->format)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The FSFS format (%d) of the hotcopy source does not match the "
+ "FSFS format (%d) of the hotcopy destination; please upgrade "
+ "both repositories to the same format"),
+ src_ffd->format, dst_ffd->format);
+
+ /* Make sure the UUID of source and destination match up.
+ * We don't want to copy over a different repository. */
+ if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
+ return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
+ _("The UUID of the hotcopy source does "
+ "not match the UUID of the hotcopy "
+ "destination"));
+
+ /* Also require same shard size. */
+ if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The sharding layout configuration "
+ "of the hotcopy source does not match "
+ "the sharding layout configuration of "
+ "the hotcopy destination"));
+ return SVN_NO_ERROR;
+}
+
+
+/* Baton for hotcopy_body(). */
+struct hotcopy_body_baton {
+ svn_fs_t *src_fs;
+ svn_fs_t *dst_fs;
+ svn_boolean_t incremental;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+} hotcopy_body_baton;
+
+/* Perform a hotcopy, either normal or incremental.
+ *
+ * Normal hotcopy assumes that the destination exists as an empty
+ * directory. It behaves like an incremental hotcopy except that
+ * none of the copied files already exist in the destination.
+ *
+ * An incremental hotcopy copies only changed or new files to the destination,
+ * and removes files from the destination no longer present in the source.
+ * While the incremental hotcopy is running, readers should still be able
+ * to access the destintation repository without error and should not see
+ * revisions currently in progress of being copied. Readers are able to see
+ * new fully copied revisions even if the entire incremental hotcopy procedure
+ * has not yet completed.
+ *
+ * Writers are blocked out completely during the entire incremental hotcopy
+ * process to ensure consistency. This function assumes that the repository
+ * write-lock is held.
+ */
+static svn_error_t *
+hotcopy_body(void *baton, apr_pool_t *pool)
+{
+ struct hotcopy_body_baton *hbb = baton;
+ svn_fs_t *src_fs = hbb->src_fs;
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
+ svn_fs_t *dst_fs = hbb->dst_fs;
+ fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+ int max_files_per_dir = src_ffd->max_files_per_dir;
+ svn_boolean_t incremental = hbb->incremental;
+ svn_cancel_func_t cancel_func = hbb->cancel_func;
+ void* cancel_baton = hbb->cancel_baton;
+ svn_revnum_t src_youngest;
+ svn_revnum_t dst_youngest;
+ svn_revnum_t rev;
+ svn_revnum_t src_min_unpacked_rev;
+ svn_revnum_t dst_min_unpacked_rev;
+ const char *src_subdir;
+ const char *dst_subdir;
+ const char *revprop_src_subdir;
+ const char *revprop_dst_subdir;
+ apr_pool_t *iterpool;
+ svn_node_kind_t kind;
+
+ /* Try to copy the config.
+ *
+ * ### We try copying the config file before doing anything else,
+ * ### because higher layers will abort the hotcopy if we throw
+ * ### an error from this function, and that renders the hotcopy
+ * ### unusable anyway. */
+ if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
+ {
+ svn_error_t *err;
+
+ err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
+ pool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* 1.6.0 to 1.6.11 did not copy the configuration file during
+ * hotcopy. So if we're hotcopying a repository which has been
+ * created as a hotcopy itself, it's possible that fsfs.conf
+ * does not exist. Ask the user to re-create it.
+ *
+ * ### It would be nice to make this a non-fatal error,
+ * ### but this function does not get an svn_fs_t object
+ * ### so we have no way of just printing a warning via
+ * ### the fs->warning() callback. */
+
+ const char *msg;
+ const char *src_abspath;
+ const char *dst_abspath;
+ const char *config_relpath;
+ svn_error_t *err2;
+
+ config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
+ err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
+ if (err2)
+ return svn_error_trace(svn_error_compose_create(err, err2));
+ err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
+ if (err2)
+ return svn_error_trace(svn_error_compose_create(err, err2));
+
+ /* ### hack: strip off the 'db/' directory from paths so
+ * ### they make sense to the user */
+ src_abspath = svn_dirent_dirname(src_abspath, pool);
+ dst_abspath = svn_dirent_dirname(dst_abspath, pool);
+
+ msg = apr_psprintf(pool,
+ _("Failed to create hotcopy at '%s'. "
+ "The file '%s' is missing from the source "
+ "repository. Please create this file, for "
+ "instance by running 'svnadmin upgrade %s'"),
+ dst_abspath, config_relpath, src_abspath);
+ return svn_error_quick_wrap(err, msg);
+ }
+ else
+ return svn_error_trace(err);
+ }
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Find the youngest revision in the source and destination.
+ * We only support hotcopies from sources with an equal or greater amount
+ * of revisions than the destination.
+ * This also catches the case where users accidentally swap the
+ * source and destination arguments. */
+ SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool));
+ if (incremental)
+ {
+ SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool));
+ if (src_youngest < dst_youngest)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The hotcopy destination already contains more revisions "
+ "(%lu) than the hotcopy source contains (%lu); are source "
+ "and destination swapped?"),
+ dst_youngest, src_youngest);
+ }
+ else
+ dst_youngest = 0;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Copy the min unpacked rev, and read its value. */
+ if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ {
+ const char *min_unpacked_rev_path;
+
+ min_unpacked_rev_path = svn_dirent_join(src_fs->path,
+ PATH_MIN_UNPACKED_REV,
+ pool);
+ SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev,
+ min_unpacked_rev_path,
+ pool));
+
+ min_unpacked_rev_path = svn_dirent_join(dst_fs->path,
+ PATH_MIN_UNPACKED_REV,
+ pool);
+ SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev,
+ min_unpacked_rev_path,
+ pool));
+
+ /* We only support packs coming from the hotcopy source.
+ * The destination should not be packed independently from
+ * the source. This also catches the case where users accidentally
+ * swap the source and destination arguments. */
+ if (src_min_unpacked_rev < dst_min_unpacked_rev)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The hotcopy destination already contains "
+ "more packed revisions (%lu) than the "
+ "hotcopy source contains (%lu)"),
+ dst_min_unpacked_rev - 1,
+ src_min_unpacked_rev - 1);
+
+ SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
+ PATH_MIN_UNPACKED_REV, pool));
+ }
+ else
+ {
+ src_min_unpacked_rev = 0;
+ dst_min_unpacked_rev = 0;
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /*
+ * Copy the necessary rev files.
+ */
+
+ src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
+ dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
+ SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
+
+ iterpool = svn_pool_create(pool);
+ /* First, copy packed shards. */
+ for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
+ {
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Copy the packed shard. */
+ SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
+ src_fs, dst_fs,
+ rev, max_files_per_dir,
+ iterpool));
+
+ /* If necessary, update 'current' to the most recent packed rev,
+ * so readers can see new revisions which arrived in this pack. */
+ SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs,
+ rev + max_files_per_dir - 1,
+ iterpool));
+
+ /* Remove revision files which are now packed. */
+ if (incremental)
+ SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev, rev + max_files_per_dir,
+ max_files_per_dir, iterpool));
+
+ /* Now that all revisions have moved into the pack, the original
+ * rev dir can be removed. */
+ err = svn_io_remove_dir2(path_rev_shard(dst_fs, rev, iterpool),
+ TRUE, cancel_func, cancel_baton, iterpool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Now, copy pairs of non-packed revisions and revprop files.
+ * If necessary, update 'current' after copying all files from a shard. */
+ SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
+ SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
+ revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
+ revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
+ SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool));
+ for (; rev <= src_youngest; rev++)
+ {
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Copy the rev file. */
+ err = hotcopy_copy_shard_file(src_subdir, dst_subdir,
+ rev, max_files_per_dir,
+ iterpool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err) &&
+ src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ {
+ svn_error_clear(err);
+
+ /* The source rev file does not exist. This can happen if the
+ * source repository is being packed concurrently with this
+ * hotcopy operation.
+ *
+ * If the new revision is now packed, and the youngest revision
+ * we're interested in is not inside this pack, try to copy the
+ * pack instead.
+ *
+ * If the youngest revision ended up being packed, don't try
+ * to be smart and work around this. Just abort the hotcopy. */
+ SVN_ERR(update_min_unpacked_rev(src_fs, pool));
+ if (is_packed_rev(src_fs, rev))
+ {
+ if (is_packed_rev(src_fs, src_youngest))
+ return svn_error_createf(
+ SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("The assumed HEAD revision (%lu) of the "
+ "hotcopy source has been packed while the "
+ "hotcopy was in progress; please restart "
+ "the hotcopy operation"),
+ src_youngest);
+
+ SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
+ src_fs, dst_fs,
+ rev, max_files_per_dir,
+ iterpool));
+ rev = dst_min_unpacked_rev;
+ continue;
+ }
+ else
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("Revision %lu disappeared from the "
+ "hotcopy source while hotcopy was "
+ "in progress"), rev);
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ /* Copy the revprop file. */
+ SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir,
+ revprop_dst_subdir,
+ rev, max_files_per_dir,
+ iterpool));
+
+ /* After completing a full shard, update 'current'. */
+ if (max_files_per_dir && rev % max_files_per_dir == 0)
+ SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* We assume that all revisions were copied now, i.e. we didn't exit the
+ * above loop early. 'rev' was last incremented during exit of the loop. */
+ SVN_ERR_ASSERT(rev == src_youngest + 1);
+
+ /* All revisions were copied. Update 'current'. */
+ SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool));
+
+ /* Replace the locks tree.
+ * This is racy in case readers are currently trying to list locks in
+ * the destination. However, we need to get rid of stale locks.
+ * This is the simplest way of doing this, so we accept this small race. */
+ dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
+ SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
+ pool));
+ src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
+ SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
+ PATH_LOCKS_DIR, TRUE,
+ cancel_func, cancel_baton, pool));
+
+ /* Now copy the node-origins cache tree. */
+ src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
+ SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
+ if (kind == svn_node_dir)
+ SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path,
+ PATH_NODE_ORIGINS_DIR, TRUE,
+ cancel_func, cancel_baton, pool));
+
+ /*
+ * NB: Data copied below is only read by writers, not readers.
+ * Writers are still locked out at this point.
+ */
+
+ if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
+ {
+ /* Copy the rep cache and then remove entries for revisions
+ * younger than the destination's youngest revision. */
+ src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
+ dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
+ SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
+ if (kind == svn_node_file)
+ {
+ SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
+ SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool));
+ }
+ }
+
+ /* Copy the txn-current file. */
+ if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+ SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
+ PATH_TXN_CURRENT, pool));
+
+ /* If a revprop generation file exists in the source filesystem,
+ * reset it to zero (since this is on a different path, it will not
+ * overlap with data already in cache). Also, clean up stale files
+ * used for the named atomics implementation. */
+ SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
+ &kind, pool));
+ if (kind == svn_node_file)
+ SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool));
+
+ SVN_ERR(cleanup_revprop_namespace(dst_fs));
+
+ /* Hotcopied FS is complete. Stamp it with a format file. */
+ SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool),
+ dst_ffd->format, max_files_per_dir, TRUE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set up shared data between SRC_FS and DST_FS. */
+static void
+hotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs)
+{
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
+ fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+
+ /* The common pool and mutexes are shared between src and dst filesystems.
+ * During hotcopy we only grab the mutexes for the destination, so there
+ * is no risk of dead-lock. We don't write to the src filesystem. Shared
+ * data for the src_fs has already been initialised in fs_hotcopy(). */
+ dst_ffd->shared = src_ffd->shared;
+}
+
+/* Create an empty filesystem at DST_FS at DST_PATH with the same
+ * configuration as SRC_FS (uuid, format, and other parameters).
+ * After creation DST_FS has no revisions, not even revision zero. */
+static svn_error_t *
+hotcopy_create_empty_dest(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ const char *dst_path,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
+ fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+
+ dst_fs->path = apr_pstrdup(pool, dst_path);
+
+ dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir;
+ dst_ffd->config = src_ffd->config;
+ dst_ffd->format = src_ffd->format;
+
+ /* Create the revision data directories. */
+ if (dst_ffd->max_files_per_dir)
+ SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool),
+ pool));
+ else
+ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
+ PATH_REVS_DIR, pool),
+ pool));
+
+ /* Create the revprops directory. */
+ if (src_ffd->max_files_per_dir)
+ SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool),
+ pool));
+ else
+ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
+ PATH_REVPROPS_DIR,
+ pool),
+ pool));
+
+ /* Create the transaction directory. */
+ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR,
+ pool),
+ pool));
+
+ /* Create the protorevs directory. */
+ if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
+ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
+ PATH_TXN_PROTOS_DIR,
+ pool),
+ pool));
+
+ /* Create the 'current' file. */
+ SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool),
+ (dst_ffd->format >=
+ SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
+ ? "0\n" : "0 1 1\n"),
+ pool));
+
+ /* Create lock file and UUID. */
+ SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool));
+ SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool));
+
+ /* Create the min unpacked rev file. */
+ if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool),
+ "0\n", pool));
+ /* Create the txn-current file if the repository supports
+ the transaction sequence file. */
+ if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+ {
+ SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool),
+ "0\n", pool));
+ SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool),
+ "", pool));
+ }
+
+ dst_ffd->youngest_rev_cache = 0;
+
+ hotcopy_setup_shared_fs_data(src_fs, dst_fs);
+ SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__hotcopy(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ const char *src_path,
+ const char *dst_path,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ struct hotcopy_body_baton hbb;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
+
+ if (incremental)
+ {
+ const char *dst_format_abspath;
+ svn_node_kind_t dst_format_kind;
+
+ /* Check destination format to be sure we know how to incrementally
+ * hotcopy to the destination FS. */
+ dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
+ SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
+ if (dst_format_kind == svn_node_none)
+ {
+ /* Destination doesn't exist yet. Perform a normal hotcopy to a
+ * empty destination using the same configuration as the source. */
+ SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
+ }
+ else
+ {
+ /* Check the existing repository. */
+ SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
+ SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
+ pool));
+ hotcopy_setup_shared_fs_data(src_fs, dst_fs);
+ SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
+ }
+ }
+ else
+ {
+ /* Start out with an empty destination using the same configuration
+ * as the source. */
+ SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ hbb.src_fs = src_fs;
+ hbb.dst_fs = dst_fs;
+ hbb.incremental = incremental;
+ hbb.cancel_func = cancel_func;
+ hbb.cancel_baton = cancel_baton;
+ SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/fs_fs.h b/contrib/subversion/subversion/libsvn_fs_fs/fs_fs.h
new file mode 100644
index 0000000..c09f861
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/fs_fs.h
@@ -0,0 +1,575 @@
+/* fs_fs.h : interface to the native filesystem layer
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__FS_FS_H
+#define SVN_LIBSVN_FS__FS_FS_H
+
+#include "fs.h"
+
+/* Open the fsfs filesystem pointed to by PATH and associate it with
+ filesystem object FS. Use POOL for temporary allocations.
+
+ ### Some parts of *FS must have been initialized beforehand; some parts
+ (including FS->path) are initialized by this function. */
+svn_error_t *svn_fs_fs__open(svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool);
+
+/* Upgrade the fsfs filesystem FS. Use POOL for temporary allocations. */
+svn_error_t *svn_fs_fs__upgrade(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Verify metadata in fsfs filesystem FS. Limit the checks to revisions
+ * START to END where possible. Indicate progress via the optional
+ * NOTIFY_FUNC callback using NOTIFY_BATON. The optional CANCEL_FUNC
+ * will periodically be called with CANCEL_BATON to allow for preemption.
+ * Use POOL for temporary allocations. */
+svn_error_t *svn_fs_fs__verify(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/* Copy the fsfs filesystem SRC_FS at SRC_PATH into a new copy DST_FS at
+ * DST_PATH. If INCREMENTAL is TRUE, do not re-copy data which already
+ * exists in DST_FS. Use POOL for temporary allocations. */
+svn_error_t * svn_fs_fs__hotcopy(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ const char *src_path,
+ const char *dst_path,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/* Recover the fsfs associated with filesystem FS.
+ Use optional CANCEL_FUNC/CANCEL_BATON for cancellation support.
+ Use POOL for temporary allocations. */
+svn_error_t *svn_fs_fs__recover(svn_fs_t *fs,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/* Set *NODEREV_P to the node-revision for the node ID in FS. Do any
+ allocations in POOL. */
+svn_error_t *svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Store NODEREV as the node-revision for the node whose id is ID in
+ FS, after setting its is_fresh_txn_root to FRESH_TXN_ROOT. Do any
+ necessary temporary allocation in POOL. */
+svn_error_t *svn_fs_fs__put_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ node_revision_t *noderev,
+ svn_boolean_t fresh_txn_root,
+ apr_pool_t *pool);
+
+/* Write the node-revision NODEREV into the stream OUTFILE, compatible with
+ filesystem format FORMAT. Only write mergeinfo-related metadata if
+ INCLUDE_MERGEINFO is true. Temporary allocations are from POOL. */
+/* ### Currently used only by fs_fs.c */
+svn_error_t *
+svn_fs_fs__write_noderev(svn_stream_t *outfile,
+ node_revision_t *noderev,
+ int format,
+ svn_boolean_t include_mergeinfo,
+ apr_pool_t *pool);
+
+/* Read a node-revision from STREAM. Set *NODEREV to the new structure,
+ allocated in POOL. */
+/* ### Currently used only by fs_fs.c */
+svn_error_t *
+svn_fs_fs__read_noderev(node_revision_t **noderev,
+ svn_stream_t *stream,
+ apr_pool_t *pool);
+
+
+/* Set *YOUNGEST to the youngest revision in filesystem FS. Do any
+ temporary allocation in POOL. */
+svn_error_t *svn_fs_fs__youngest_rev(svn_revnum_t *youngest,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Return an error iff REV does not exist in FS. */
+svn_error_t *
+svn_fs_fs__revision_exists(svn_revnum_t rev,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Set *ROOT_ID to the node-id for the root of revision REV in
+ filesystem FS. Do any allocations in POOL. */
+svn_error_t *svn_fs_fs__rev_get_root(svn_fs_id_t **root_id,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Set *ENTRIES to an apr_hash_t of dirent structs that contain the
+ directory entries of node-revision NODEREV in filesystem FS. The
+ returned table (and its keys and values) is allocated in POOL,
+ which is also used for temporary allocations. */
+svn_error_t *svn_fs_fs__rep_contents_dir(apr_hash_t **entries,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool);
+
+/* Set *DIRENT to the entry identified by NAME in the directory given
+ by NODEREV in filesystem FS. If no such entry exits, *DIRENT will
+ be NULL. The returned object is allocated in RESULT_POOL; SCRATCH_POOL
+ used for temporary allocations. */
+svn_error_t *
+svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *CONTENTS to be a readable svn_stream_t that receives the text
+ representation of node-revision NODEREV as seen in filesystem FS.
+ Use POOL for temporary allocations. */
+svn_error_t *svn_fs_fs__get_contents(svn_stream_t **contents,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool);
+
+/* Attempt to fetch the text representation of node-revision NODEREV as
+ seen in filesystem FS and pass it along with the BATON to the PROCESSOR.
+ Set *SUCCESS only of the data could be provided and the processing
+ had been called.
+ Use POOL for all allocations.
+ */
+svn_error_t *
+svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool);
+
+/* Set *STREAM_P to a delta stream turning the contents of the file SOURCE into
+ the contents of the file TARGET, allocated in POOL.
+ If SOURCE is null, the empty string will be used. */
+svn_error_t *svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_t *fs,
+ node_revision_t *source,
+ node_revision_t *target,
+ apr_pool_t *pool);
+
+/* Set *PROPLIST to be an apr_hash_t containing the property list of
+ node-revision NODEREV as seen in filesystem FS. Use POOL for
+ temporary allocations. */
+svn_error_t *svn_fs_fs__get_proplist(apr_hash_t **proplist,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool);
+
+/* Set *PROPLIST to be an apr_hash_t containing the property list of
+ revision REV as seen in filesystem FS. Use POOL for temporary
+ allocations. */
+svn_error_t *svn_fs_fs__revision_proplist(apr_hash_t **proplist,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Set *LENGTH to the be fulltext length of the node revision
+ specified by NODEREV. Use POOL for temporary allocations. */
+svn_error_t *svn_fs_fs__file_length(svn_filesize_t *length,
+ node_revision_t *noderev,
+ apr_pool_t *pool);
+
+/* Return TRUE if the representation keys in A and B both point to the
+ same representation, else return FALSE. */
+svn_boolean_t svn_fs_fs__noderev_same_rep_key(representation_t *a,
+ representation_t *b);
+
+
+/* Return a copy of the representation REP allocated from POOL. */
+representation_t *svn_fs_fs__rep_copy(representation_t *rep,
+ apr_pool_t *pool);
+
+
+/* Return the recorded checksum of type KIND for the text representation
+ of NODREV into CHECKSUM, allocating from POOL. If no stored checksum is
+ available, put all NULL into CHECKSUM. */
+svn_error_t *svn_fs_fs__file_checksum(svn_checksum_t **checksum,
+ node_revision_t *noderev,
+ svn_checksum_kind_t kind,
+ apr_pool_t *pool);
+
+/* Find the paths which were changed in revision REV of filesystem FS
+ and store them in *CHANGED_PATHS_P. Cached copyfrom information
+ will be stored in *COPYFROM_CACHE. Get any temporary allocations
+ from POOL. */
+svn_error_t *svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_hash_t *copyfrom_cache,
+ apr_pool_t *pool);
+
+/* Create a new transaction in filesystem FS, based on revision REV,
+ and store it in *TXN_P. Allocate all necessary variables from
+ POOL. */
+svn_error_t *svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Set the transaction property NAME to the value VALUE in transaction
+ TXN. Perform temporary allocations from POOL. */
+svn_error_t *svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/* Change transaction properties in transaction TXN based on PROPS.
+ Perform temporary allocations from POOL. */
+svn_error_t *svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
+ const apr_array_header_t *props,
+ apr_pool_t *pool);
+
+/* Return whether or not the given FS supports mergeinfo metadata. */
+svn_boolean_t svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs);
+
+/* Store a transaction record in *TXN_P for the transaction identified
+ by TXN_ID in filesystem FS. Allocate everything from POOL. */
+svn_error_t *svn_fs_fs__get_txn(transaction_t **txn_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+/* Abort the existing transaction TXN, performing any temporary
+ allocations in POOL. */
+svn_error_t *svn_fs_fs__abort_txn(svn_fs_txn_t *txn, apr_pool_t *pool);
+
+/* Create an entirely new mutable node in the filesystem FS, whose
+ node-revision is NODEREV. Set *ID_P to the new node revision's ID.
+ Use POOL for any temporary allocation. COPY_ID is the copy_id to
+ use in the node revision ID. TXN_ID is the Subversion transaction
+ under which this occurs. */
+svn_error_t *svn_fs_fs__create_node(const svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *copy_id,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+/* Remove all references to the transaction TXN_ID from filesystem FS.
+ Temporary allocations are from POOL. */
+svn_error_t *svn_fs_fs__purge_txn(svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+/* Add or set in filesystem FS, transaction TXN_ID, in directory
+ PARENT_NODEREV a directory entry for NAME pointing to ID of type
+ KIND. Allocations are done in POOL. */
+svn_error_t *svn_fs_fs__set_entry(svn_fs_t *fs,
+ const char *txn_id,
+ node_revision_t *parent_noderev,
+ const char *name,
+ const svn_fs_id_t *id,
+ svn_node_kind_t kind,
+ apr_pool_t *pool);
+
+/* Add a change to the changes record for filesystem FS in transaction
+ TXN_ID. Mark path PATH, having node-id ID, as changed according to
+ the type in CHANGE_KIND. If the text representation was changed
+ set TEXT_MOD to TRUE, and likewise for PROP_MOD. If this change
+ was the result of a copy, set COPYFROM_REV and COPYFROM_PATH to the
+ revision and path of the copy source, otherwise they should be set
+ to SVN_INVALID_REVNUM and NULL. Perform any temporary allocations
+ from POOL. */
+svn_error_t *svn_fs_fs__add_change(svn_fs_t *fs,
+ const char *txn_id,
+ const char *path,
+ const svn_fs_id_t *id,
+ svn_fs_path_change_kind_t change_kind,
+ svn_boolean_t text_mod,
+ svn_boolean_t prop_mod,
+ svn_node_kind_t node_kind,
+ svn_revnum_t copyfrom_rev,
+ const char *copyfrom_path,
+ apr_pool_t *pool);
+
+/* Return a writable stream in *STREAM that allows storing the text
+ representation of node-revision NODEREV in filesystem FS.
+ Allocations are from POOL. */
+svn_error_t *svn_fs_fs__set_contents(svn_stream_t **stream,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *pool);
+
+/* Create a node revision in FS which is an immediate successor of
+ OLD_ID, whose contents are NEW_NR. Set *NEW_ID_P to the new node
+ revision's ID. Use POOL for any temporary allocation.
+
+ COPY_ID, if non-NULL, is a key into the `copies' table, and
+ indicates that this new node is being created as the result of a
+ copy operation, and specifically which operation that was. If
+ COPY_ID is NULL, then re-use the copy ID from the predecessor node.
+
+ TXN_ID is the Subversion transaction under which this occurs.
+
+ After this call, the deltification code assumes that the new node's
+ contents will change frequently, and will avoid representing other
+ nodes as deltas against this node's contents. */
+svn_error_t *svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *old_idp,
+ node_revision_t *new_noderev,
+ const char *copy_id,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+/* Write a new property list PROPLIST for node-revision NODEREV in
+ filesystem FS. Perform any temporary allocations in POOL. */
+svn_error_t *svn_fs_fs__set_proplist(svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_hash_t *proplist,
+ apr_pool_t *pool);
+
+/* Commit the transaction TXN in filesystem FS and return its new
+ revision number in *REV. If the transaction is out of date, return
+ the error SVN_ERR_FS_TXN_OUT_OF_DATE. Use POOL for temporary
+ allocations. */
+svn_error_t *svn_fs_fs__commit(svn_revnum_t *new_rev_p,
+ svn_fs_t *fs,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+/* Return the next available copy_id in *COPY_ID for the transaction
+ TXN_ID in filesystem FS. Allocate space in POOL. */
+svn_error_t *svn_fs_fs__reserve_copy_id(const char **copy_id,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+/* Create a fs_fs fileysystem referenced by FS at path PATH. Get any
+ temporary allocations from POOL.
+
+ ### Some parts of *FS must have been initialized beforehand; some parts
+ (including FS->path) are initialized by this function. */
+svn_error_t *svn_fs_fs__create(svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool);
+
+/* Set the uuid of repository FS to UUID, if UUID is not NULL;
+ otherwise, set the uuid of FS to a newly generated UUID. Perform
+ temporary allocations in POOL. */
+svn_error_t *svn_fs_fs__set_uuid(svn_fs_t *fs,
+ const char *uuid,
+ apr_pool_t *pool);
+
+/* Set *NAMES_P to an array of names which are all the active
+ transactions in filesystem FS. Allocate the array from POOL. */
+svn_error_t *svn_fs_fs__list_transactions(apr_array_header_t **names_p,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Open the transaction named NAME in filesystem FS. Set *TXN_P to
+ * the transaction. If there is no such transaction, return
+` * SVN_ERR_FS_NO_SUCH_TRANSACTION. Allocate the new transaction in
+ * POOL. */
+svn_error_t *svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ const char *name,
+ apr_pool_t *pool);
+
+/* Return the property list from transaction TXN and store it in
+ *PROPLIST. Allocate the property list from POOL. */
+svn_error_t *svn_fs_fs__txn_proplist(apr_hash_t **proplist,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+/* Delete the mutable node-revision referenced by ID, along with any
+ mutable props or directory contents associated with it. Perform
+ temporary allocations in POOL. */
+svn_error_t *svn_fs_fs__delete_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+
+/* Find the paths which were changed in transaction TXN_ID of
+ filesystem FS and store them in *CHANGED_PATHS_P.
+ Get any temporary allocations from POOL. */
+svn_error_t *svn_fs_fs__txn_changes_fetch(apr_hash_t **changes,
+ svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+
+/* Set *PATH to the path of REV in FS, whether in a pack file or not.
+ Allocate *PATH in POOL.
+
+ Note: If the caller does not have the write lock on FS, then the path is
+ not guaranteed to be correct or to remain correct after the function
+ returns, because the revision might become packed before or after this
+ call. If a file exists at that path, then it is correct; if not, then
+ the caller should call update_min_unpacked_rev() and re-try once. */
+svn_error_t *
+svn_fs_fs__path_rev_absolute(const char **path,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Return the path to the 'current' file in FS.
+ Perform allocation in POOL. */
+const char *
+svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool);
+
+/* Obtain a write lock on the filesystem FS in a subpool of POOL, call
+ BODY with BATON and that subpool, destroy the subpool (releasing the write
+ lock) and return what BODY returned. */
+svn_error_t *
+svn_fs_fs__with_write_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(void *baton,
+ apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool);
+
+/* Find the value of the property named PROPNAME in transaction TXN.
+ Return the contents in *VALUE_P. The contents will be allocated
+ from POOL. */
+svn_error_t *svn_fs_fs__revision_prop(svn_string_t **value_p, svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *propname,
+ apr_pool_t *pool);
+
+/* Change, add, or delete a property on a revision REV in filesystem
+ FS. NAME gives the name of the property, and value, if non-NULL,
+ gives the new contents of the property. If value is NULL, then the
+ property will be deleted. If OLD_VALUE_P is not NULL, do nothing unless the
+ preexisting value is *OLD_VALUE_P. Do any temporary allocation in POOL. */
+svn_error_t *svn_fs_fs__change_rev_prop(svn_fs_t *fs, svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/* Retrieve information about the Subversion transaction SVN_TXN from
+ the `transactions' table of FS, allocating from POOL. Set
+ *ROOT_ID_P to the ID of the transaction's root directory. Set
+ *BASE_ROOT_ID_P to the ID of the root directory of the
+ transaction's base revision.
+
+ If there is no such transaction, SVN_ERR_FS_NO_SUCH_TRANSACTION is
+ the error returned.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed.
+
+ Allocate *ROOT_ID_P and *BASE_ROOT_ID_P in POOL. */
+svn_error_t *svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
+ const svn_fs_id_t **base_root_id_p,
+ svn_fs_t *fs,
+ const char *txn_name,
+ apr_pool_t *pool);
+
+/* Begin a new transaction in filesystem FS, based on existing
+ revision REV. The new transaction is returned in *TXN_P. Allocate
+ the new transaction structure from POOL. */
+svn_error_t *svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p, svn_fs_t *fs,
+ svn_revnum_t rev, apr_uint32_t flags,
+ apr_pool_t *pool);
+
+/* Find the value of the property named PROPNAME in transaction TXN.
+ Return the contents in *VALUE_P. The contents will be allocated
+ from POOL. */
+svn_error_t *svn_fs_fs__txn_prop(svn_string_t **value_p, svn_fs_txn_t *txn,
+ const char *propname, apr_pool_t *pool);
+
+/* If directory PATH does not exist, create it and give it the same
+ permissions as FS_PATH.*/
+svn_error_t *svn_fs_fs__ensure_dir_exists(const char *path,
+ const char *fs_path,
+ apr_pool_t *pool);
+
+/* Update the node origin index for FS, recording the mapping from
+ NODE_ID to NODE_REV_ID. Use POOL for any temporary allocations.
+
+ Because this is just an "optional" cache, this function does not
+ return an error if the underlying storage is readonly; it still
+ returns an error for other error conditions.
+ */
+svn_error_t *
+svn_fs_fs__set_node_origin(svn_fs_t *fs,
+ const char *node_id,
+ const svn_fs_id_t *node_rev_id,
+ apr_pool_t *pool);
+
+/* Set *ORIGIN_ID to the node revision ID from which the history of
+ all nodes in FS whose "Node ID" is NODE_ID springs, as determined
+ by a look in the index. ORIGIN_ID needs to be parsed in an
+ FS-backend-specific way. Use POOL for allocations.
+
+ If there is no entry for NODE_ID in the cache, return NULL
+ in *ORIGIN_ID. */
+svn_error_t *
+svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
+ svn_fs_t *fs,
+ const char *node_id,
+ apr_pool_t *pool);
+
+
+/* Initialize all session-local caches in FS according to the global
+ cache settings. Use POOL for allocations.
+
+ Please note that it is permissible for this function to set some
+ or all of these caches to NULL, regardless of any setting. */
+svn_error_t *
+svn_fs_fs__initialize_caches(svn_fs_t *fs, apr_pool_t *pool);
+
+/* Initialize all transaction-local caches in FS according to the global
+ cache settings and make TXN_ID part of their key space. Use POOL for
+ allocations.
+
+ Please note that it is permissible for this function to set some or all
+ of these caches to NULL, regardless of any setting. */
+svn_error_t *
+svn_fs_fs__initialize_txn_caches(svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+/* Resets the svn_cache__t structures local to the current transaction in FS.
+ Calling it more than once per txn or from outside any txn is allowed. */
+void
+svn_fs_fs__reset_txn_caches(svn_fs_t *fs);
+
+/* Possibly pack the repository at PATH. This just take full shards, and
+ combines all the revision files into a single one, with a manifest header.
+ Use optional CANCEL_FUNC/CANCEL_BATON for cancellation support.
+
+ Existing filesystem references need not change. */
+svn_error_t *
+svn_fs_fs__pack(svn_fs_t *fs,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+#endif
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/id.c b/contrib/subversion/subversion/libsvn_fs_fs/id.c
new file mode 100644
index 0000000..1317829
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/id.c
@@ -0,0 +1,405 @@
+/* id.c : operations on node-revision IDs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "id.h"
+#include "../libsvn_fs/fs-loader.h"
+#include "private/svn_temp_serializer.h"
+#include "private/svn_string_private.h"
+
+
+typedef struct id_private_t {
+ const char *node_id;
+ const char *copy_id;
+ const char *txn_id;
+ svn_revnum_t rev;
+ apr_off_t offset;
+} id_private_t;
+
+
+/* Accessing ID Pieces. */
+
+const char *
+svn_fs_fs__id_node_id(const svn_fs_id_t *id)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return pvt->node_id;
+}
+
+
+const char *
+svn_fs_fs__id_copy_id(const svn_fs_id_t *id)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return pvt->copy_id;
+}
+
+
+const char *
+svn_fs_fs__id_txn_id(const svn_fs_id_t *id)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return pvt->txn_id;
+}
+
+
+svn_revnum_t
+svn_fs_fs__id_rev(const svn_fs_id_t *id)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return pvt->rev;
+}
+
+
+apr_off_t
+svn_fs_fs__id_offset(const svn_fs_id_t *id)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return pvt->offset;
+}
+
+
+svn_string_t *
+svn_fs_fs__id_unparse(const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ if ((! pvt->txn_id))
+ {
+ char rev_string[SVN_INT64_BUFFER_SIZE];
+ char offset_string[SVN_INT64_BUFFER_SIZE];
+
+ svn__i64toa(rev_string, pvt->rev);
+ svn__i64toa(offset_string, pvt->offset);
+ return svn_string_createf(pool, "%s.%s.r%s/%s",
+ pvt->node_id, pvt->copy_id,
+ rev_string, offset_string);
+ }
+ else
+ {
+ return svn_string_createf(pool, "%s.%s.t%s",
+ pvt->node_id, pvt->copy_id,
+ pvt->txn_id);
+ }
+}
+
+
+/*** Comparing node IDs ***/
+
+svn_boolean_t
+svn_fs_fs__id_eq(const svn_fs_id_t *a,
+ const svn_fs_id_t *b)
+{
+ id_private_t *pvta = a->fsap_data, *pvtb = b->fsap_data;
+
+ if (a == b)
+ return TRUE;
+ if (strcmp(pvta->node_id, pvtb->node_id) != 0)
+ return FALSE;
+ if (strcmp(pvta->copy_id, pvtb->copy_id) != 0)
+ return FALSE;
+ if ((pvta->txn_id == NULL) != (pvtb->txn_id == NULL))
+ return FALSE;
+ if (pvta->txn_id && pvtb->txn_id && strcmp(pvta->txn_id, pvtb->txn_id) != 0)
+ return FALSE;
+ if (pvta->rev != pvtb->rev)
+ return FALSE;
+ if (pvta->offset != pvtb->offset)
+ return FALSE;
+ return TRUE;
+}
+
+
+svn_boolean_t
+svn_fs_fs__id_check_related(const svn_fs_id_t *a,
+ const svn_fs_id_t *b)
+{
+ id_private_t *pvta = a->fsap_data, *pvtb = b->fsap_data;
+
+ if (a == b)
+ return TRUE;
+ /* If both node_ids start with _ and they have differing transaction
+ IDs, then it is impossible for them to be related. */
+ if (pvta->node_id[0] == '_')
+ {
+ if (pvta->txn_id && pvtb->txn_id &&
+ (strcmp(pvta->txn_id, pvtb->txn_id) != 0))
+ return FALSE;
+ }
+
+ return (strcmp(pvta->node_id, pvtb->node_id) == 0);
+}
+
+
+int
+svn_fs_fs__id_compare(const svn_fs_id_t *a,
+ const svn_fs_id_t *b)
+{
+ if (svn_fs_fs__id_eq(a, b))
+ return 0;
+ return (svn_fs_fs__id_check_related(a, b) ? 1 : -1);
+}
+
+
+
+/* Creating ID's. */
+
+static id_vtable_t id_vtable = {
+ svn_fs_fs__id_unparse,
+ svn_fs_fs__id_compare
+};
+
+
+svn_fs_id_t *
+svn_fs_fs__id_txn_create(const char *node_id,
+ const char *copy_id,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *id = apr_palloc(pool, sizeof(*id));
+ id_private_t *pvt = apr_palloc(pool, sizeof(*pvt));
+
+ pvt->node_id = apr_pstrdup(pool, node_id);
+ pvt->copy_id = apr_pstrdup(pool, copy_id);
+ pvt->txn_id = apr_pstrdup(pool, txn_id);
+ pvt->rev = SVN_INVALID_REVNUM;
+ pvt->offset = -1;
+
+ id->vtable = &id_vtable;
+ id->fsap_data = pvt;
+ return id;
+}
+
+
+svn_fs_id_t *
+svn_fs_fs__id_rev_create(const char *node_id,
+ const char *copy_id,
+ svn_revnum_t rev,
+ apr_off_t offset,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *id = apr_palloc(pool, sizeof(*id));
+ id_private_t *pvt = apr_palloc(pool, sizeof(*pvt));
+
+ pvt->node_id = apr_pstrdup(pool, node_id);
+ pvt->copy_id = apr_pstrdup(pool, copy_id);
+ pvt->txn_id = NULL;
+ pvt->rev = rev;
+ pvt->offset = offset;
+
+ id->vtable = &id_vtable;
+ id->fsap_data = pvt;
+ return id;
+}
+
+
+svn_fs_id_t *
+svn_fs_fs__id_copy(const svn_fs_id_t *id, apr_pool_t *pool)
+{
+ svn_fs_id_t *new_id = apr_palloc(pool, sizeof(*new_id));
+ id_private_t *new_pvt = apr_palloc(pool, sizeof(*new_pvt));
+ id_private_t *pvt = id->fsap_data;
+
+ new_pvt->node_id = apr_pstrdup(pool, pvt->node_id);
+ new_pvt->copy_id = apr_pstrdup(pool, pvt->copy_id);
+ new_pvt->txn_id = pvt->txn_id ? apr_pstrdup(pool, pvt->txn_id) : NULL;
+ new_pvt->rev = pvt->rev;
+ new_pvt->offset = pvt->offset;
+
+ new_id->vtable = &id_vtable;
+ new_id->fsap_data = new_pvt;
+ return new_id;
+}
+
+
+svn_fs_id_t *
+svn_fs_fs__id_parse(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *id;
+ id_private_t *pvt;
+ char *data_copy, *str;
+
+ /* Dup the ID data into POOL. Our returned ID will have references
+ into this memory. */
+ data_copy = apr_pstrmemdup(pool, data, len);
+
+ /* Alloc a new svn_fs_id_t structure. */
+ id = apr_palloc(pool, sizeof(*id));
+ pvt = apr_palloc(pool, sizeof(*pvt));
+ id->vtable = &id_vtable;
+ id->fsap_data = pvt;
+
+ /* Now, we basically just need to "split" this data on `.'
+ characters. We will use svn_cstring_tokenize, which will put
+ terminators where each of the '.'s used to be. Then our new
+ id field will reference string locations inside our duplicate
+ string.*/
+
+ /* Node Id */
+ str = svn_cstring_tokenize(".", &data_copy);
+ if (str == NULL)
+ return NULL;
+ pvt->node_id = str;
+
+ /* Copy Id */
+ str = svn_cstring_tokenize(".", &data_copy);
+ if (str == NULL)
+ return NULL;
+ pvt->copy_id = str;
+
+ /* Txn/Rev Id */
+ str = svn_cstring_tokenize(".", &data_copy);
+ if (str == NULL)
+ return NULL;
+
+ if (str[0] == 'r')
+ {
+ apr_int64_t val;
+ svn_error_t *err;
+
+ /* This is a revision type ID */
+ pvt->txn_id = NULL;
+
+ data_copy = str + 1;
+ str = svn_cstring_tokenize("/", &data_copy);
+ if (str == NULL)
+ return NULL;
+ pvt->rev = SVN_STR_TO_REV(str);
+
+ str = svn_cstring_tokenize("/", &data_copy);
+ if (str == NULL)
+ return NULL;
+ err = svn_cstring_atoi64(&val, str);
+ if (err)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+ pvt->offset = (apr_off_t)val;
+ }
+ else if (str[0] == 't')
+ {
+ /* This is a transaction type ID */
+ pvt->txn_id = str + 1;
+ pvt->rev = SVN_INVALID_REVNUM;
+ pvt->offset = -1;
+ }
+ else
+ return NULL;
+
+ return id;
+}
+
+/* (de-)serialization support */
+
+/* Serialization of the PVT sub-structure within the CONTEXT.
+ */
+static void
+serialize_id_private(svn_temp_serializer__context_t *context,
+ const id_private_t * const *pvt)
+{
+ const id_private_t *private = *pvt;
+
+ /* serialize the pvt data struct itself */
+ svn_temp_serializer__push(context,
+ (const void * const *)pvt,
+ sizeof(*private));
+
+ /* append the referenced strings */
+ svn_temp_serializer__add_string(context, &private->node_id);
+ svn_temp_serializer__add_string(context, &private->copy_id);
+ svn_temp_serializer__add_string(context, &private->txn_id);
+
+ /* return to caller's nesting level */
+ svn_temp_serializer__pop(context);
+}
+
+/* Serialize an ID within the serialization CONTEXT.
+ */
+void
+svn_fs_fs__id_serialize(svn_temp_serializer__context_t *context,
+ const struct svn_fs_id_t * const *id)
+{
+ /* nothing to do for NULL ids */
+ if (*id == NULL)
+ return;
+
+ /* serialize the id data struct itself */
+ svn_temp_serializer__push(context,
+ (const void * const *)id,
+ sizeof(**id));
+
+ /* serialize the id_private_t data sub-struct */
+ serialize_id_private(context,
+ (const id_private_t * const *)&(*id)->fsap_data);
+
+ /* return to caller's nesting level */
+ svn_temp_serializer__pop(context);
+}
+
+/* Deserialization of the PVT sub-structure in BUFFER.
+ */
+static void
+deserialize_id_private(void *buffer, id_private_t **pvt)
+{
+ /* fixup the reference to the only sub-structure */
+ id_private_t *private;
+ svn_temp_deserializer__resolve(buffer, (void**)pvt);
+
+ /* fixup the sub-structure itself */
+ private = *pvt;
+ svn_temp_deserializer__resolve(private, (void**)&private->node_id);
+ svn_temp_deserializer__resolve(private, (void**)&private->copy_id);
+ svn_temp_deserializer__resolve(private, (void**)&private->txn_id);
+}
+
+/* Deserialize an ID inside the BUFFER.
+ */
+void
+svn_fs_fs__id_deserialize(void *buffer, svn_fs_id_t **id)
+{
+ /* The id maybe all what is in the whole buffer.
+ * Don't try to fixup the pointer in that case*/
+ if (*id != buffer)
+ svn_temp_deserializer__resolve(buffer, (void**)id);
+
+ /* no id, no sub-structure fixup necessary */
+ if (*id == NULL)
+ return;
+
+ /* the stored vtable is bogus at best -> set the right one */
+ (*id)->vtable = &id_vtable;
+
+ /* handle sub-structures */
+ deserialize_id_private(*id, (id_private_t **)&(*id)->fsap_data);
+}
+
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/id.h b/contrib/subversion/subversion/libsvn_fs_fs/id.h
new file mode 100644
index 0000000..11da466
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/id.h
@@ -0,0 +1,116 @@
+/* id.h : interface to node ID functions, private to libsvn_fs_fs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_FS_ID_H
+#define SVN_LIBSVN_FS_FS_ID_H
+
+#include "svn_fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** ID accessor functions. ***/
+
+/* Get the "node id" portion of ID. */
+const char *svn_fs_fs__id_node_id(const svn_fs_id_t *id);
+
+/* Get the "copy id" portion of ID. */
+const char *svn_fs_fs__id_copy_id(const svn_fs_id_t *id);
+
+/* Get the "txn id" portion of ID, or NULL if it is a permanent ID. */
+const char *svn_fs_fs__id_txn_id(const svn_fs_id_t *id);
+
+/* Get the "rev" portion of ID, or SVN_INVALID_REVNUM if it is a
+ transaction ID. */
+svn_revnum_t svn_fs_fs__id_rev(const svn_fs_id_t *id);
+
+/* Access the "offset" portion of the ID, or -1 if it is a transaction
+ ID. */
+apr_off_t svn_fs_fs__id_offset(const svn_fs_id_t *id);
+
+/* Convert ID into string form, allocated in POOL. */
+svn_string_t *svn_fs_fs__id_unparse(const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Return true if A and B are equal. */
+svn_boolean_t svn_fs_fs__id_eq(const svn_fs_id_t *a,
+ const svn_fs_id_t *b);
+
+/* Return true if A and B are related. */
+svn_boolean_t svn_fs_fs__id_check_related(const svn_fs_id_t *a,
+ const svn_fs_id_t *b);
+
+/* Return 0 if A and B are equal, 1 if they are related, -1 otherwise. */
+int svn_fs_fs__id_compare(const svn_fs_id_t *a,
+ const svn_fs_id_t *b);
+
+/* Create an ID within a transaction based on NODE_ID, COPY_ID, and
+ TXN_ID, allocated in POOL. */
+svn_fs_id_t *svn_fs_fs__id_txn_create(const char *node_id,
+ const char *copy_id,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+/* Create a permanent ID based on NODE_ID, COPY_ID, REV, and OFFSET,
+ allocated in POOL. */
+svn_fs_id_t *svn_fs_fs__id_rev_create(const char *node_id,
+ const char *copy_id,
+ svn_revnum_t rev,
+ apr_off_t offset,
+ apr_pool_t *pool);
+
+/* Return a copy of ID, allocated from POOL. */
+svn_fs_id_t *svn_fs_fs__id_copy(const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Return an ID resulting from parsing the string DATA (with length
+ LEN), or NULL if DATA is an invalid ID string. */
+svn_fs_id_t *svn_fs_fs__id_parse(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool);
+
+
+/* (de-)serialization support*/
+
+struct svn_temp_serializer__context_t;
+
+/**
+ * Serialize an @a id within the serialization @a context.
+ */
+void
+svn_fs_fs__id_serialize(struct svn_temp_serializer__context_t *context,
+ const svn_fs_id_t * const *id);
+
+/**
+ * Deserialize an @a id within the @a buffer.
+ */
+void
+svn_fs_fs__id_deserialize(void *buffer,
+ svn_fs_id_t **id);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_FS_ID_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/key-gen.c b/contrib/subversion/subversion/libsvn_fs_fs/key-gen.c
new file mode 100644
index 0000000..a65c59d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/key-gen.c
@@ -0,0 +1,159 @@
+/* key-gen.c --- manufacturing sequential keys for some db tables
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <apr.h>
+#include <apr_network_io.h>
+#include "private/svn_fs_private.h"
+#include "key-gen.h"
+
+/* The Berkeley DB backend uses a key as a transaction name and the
+ maximum key size must be less than the maximum transaction name
+ length. */
+#if MAX_KEY_SIZE > SVN_FS__TXN_MAX_LEN
+#error The MAX_KEY_SIZE used for BDB txn names is greater than SVN_FS__TXN_MAX_LEN.
+#endif
+
+
+/*** Keys for reps and strings. ***/
+
+void
+svn_fs_fs__add_keys(const char *key1, const char *key2, char *result)
+{
+ apr_ssize_t i1 = strlen(key1) - 1;
+ apr_ssize_t i2 = strlen(key2) - 1;
+ int i3 = 0;
+ int val;
+ int carry = 0;
+ char buf[MAX_KEY_SIZE + 2];
+
+ while ((i1 >= 0) || (i2 >= 0) || (carry > 0))
+ {
+ val = carry;
+ if (i1>=0)
+ val += (key1[i1] <= '9') ? (key1[i1] - '0') : (key1[i1] - 'a' + 10);
+
+ if (i2>=0)
+ val += (key2[i2] <= '9') ? (key2[i2] - '0') : (key2[i2] - 'a' + 10);
+
+ carry = val / 36;
+ val = val % 36;
+
+ buf[i3++] = (char)((val <= 9) ? (val + '0') : (val - 10 + 'a'));
+
+ if (i1>=0)
+ i1--;
+ if (i2>=0)
+ i2--;
+ }
+
+ /* Now reverse the resulting string and NULL terminate it. */
+ for (i1 = 0; i1 < i3; i1++)
+ result[i1] = buf[i3 - i1 - 1];
+
+ result[i1] = '\0';
+}
+
+
+void
+svn_fs_fs__next_key(const char *this, apr_size_t *len, char *next)
+{
+ apr_ssize_t i;
+ apr_size_t olen = *len; /* remember the first length */
+ char c; /* current char */
+ svn_boolean_t carry = TRUE; /* boolean: do we have a carry or not?
+ We start with a carry, because we're
+ incrementing the number, after all. */
+
+ /* Leading zeros are not allowed, except for the string "0". */
+ if ((*len > 1) && (this[0] == '0'))
+ {
+ *len = 0;
+ return;
+ }
+
+ for (i = (olen - 1); i >= 0; i--)
+ {
+ c = this[i];
+
+ /* Validate as we go. */
+ if (! (((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'z'))))
+ {
+ *len = 0;
+ return;
+ }
+
+ if (carry)
+ {
+ if (c == 'z')
+ next[i] = '0';
+ else
+ {
+ carry = FALSE;
+
+ if (c == '9')
+ next[i] = 'a';
+ else
+ next[i] = ++c;
+ }
+ }
+ else
+ next[i] = c;
+ }
+
+ /* The new length is OLEN, plus 1 if there's a carry out of the
+ leftmost digit. */
+ *len = olen + (carry ? 1 : 0);
+
+ /* Ensure that we haven't overrun the (ludicrous) bound on key length.
+ Note that MAX_KEY_SIZE is a bound on the size *including*
+ the trailing null byte. */
+ assert(*len < MAX_KEY_SIZE);
+
+ /* Now we know it's safe to add the null terminator. */
+ next[*len] = '\0';
+
+ /* Handle any leftover carry. */
+ if (carry)
+ {
+ memmove(next+1, next, olen);
+ next[0] = '1';
+ }
+}
+
+
+int
+svn_fs_fs__key_compare(const char *a, const char *b)
+{
+ apr_size_t a_len = strlen(a);
+ apr_size_t b_len = strlen(b);
+ int cmp;
+
+ if (a_len > b_len)
+ return 1;
+ if (b_len > a_len)
+ return -1;
+ cmp = strcmp(a, b);
+ return (cmp ? (cmp / abs(cmp)) : 0);
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/key-gen.h b/contrib/subversion/subversion/libsvn_fs_fs/key-gen.h
new file mode 100644
index 0000000..e1b3858
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/key-gen.h
@@ -0,0 +1,91 @@
+/* key-gen.c --- manufacturing sequential keys for some db tables
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_KEY_GEN_H
+#define SVN_LIBSVN_FS_KEY_GEN_H
+
+#include <apr.h>
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* The alphanumeric keys passed in and out of svn_fs_fs__next_key
+ are guaranteed never to be longer than this many bytes,
+ *including* the trailing null byte. It is therefore safe
+ to declare a key as "char key[MAX_KEY_SIZE]".
+
+ Note that this limit will be a problem if the number of
+ keys in a table ever exceeds
+
+ 18217977168218728251394687124089371267338971528174
+ 76066745969754933395997209053270030282678007662838
+ 67331479599455916367452421574456059646801054954062
+ 15017704234999886990788594743994796171248406730973
+ 80736524850563115569208508785942830080999927310762
+ 50733948404739350551934565743979678824151197232629
+ 947748581376,
+
+ but that's a risk we'll live with for now. */
+#define MAX_KEY_SIZE 200
+
+
+/* Generate the next key after a given alphanumeric key.
+ *
+ * The first *LEN bytes of THIS are an ascii representation of a
+ * number in base 36: digits 0-9 have their usual values, and a-z have
+ * values 10-35.
+ *
+ * The new key is stored in NEXT, null-terminated. NEXT must be at
+ * least *LEN + 2 bytes long -- one extra byte to hold a possible
+ * overflow column, and one for null termination. On return, *LEN
+ * will be set to the length of the new key, not counting the null
+ * terminator. In other words, the outgoing *LEN will be either equal
+ * to the incoming, or to the incoming + 1.
+ *
+ * If THIS contains anything other than digits and lower-case
+ * alphabetic characters, or if it starts with `0' but is not the
+ * string "0", then *LEN is set to zero and the effect on NEXT
+ * is undefined.
+ */
+void svn_fs_fs__next_key(const char *this, apr_size_t *len, char *next);
+
+
+/* Compare two strings A and B as base-36 alphanumeric keys.
+ *
+ * Return -1, 0, or 1 if A is less than, equal to, or greater than B,
+ * respectively.
+ */
+int svn_fs_fs__key_compare(const char *a, const char *b);
+
+/* Add two base-36 alphanumeric keys to get a third, the result. */
+void svn_fs_fs__add_keys(const char *key1, const char *key2, char *result);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_KEY_GEN_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/lock.c b/contrib/subversion/subversion/libsvn_fs_fs/lock.c
new file mode 100644
index 0000000..95bd943
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/lock.c
@@ -0,0 +1,1079 @@
+/* lock.c : functions for manipulating filesystem locks.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_fs.h"
+#include "svn_hash.h"
+#include "svn_time.h"
+#include "svn_utf.h"
+
+#include <apr_uuid.h>
+#include <apr_file_io.h>
+#include <apr_file_info.h>
+
+#include "lock.h"
+#include "tree.h"
+#include "fs_fs.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+#include "svn_private_config.h"
+
+/* Names of hash keys used to store a lock for writing to disk. */
+#define PATH_KEY "path"
+#define TOKEN_KEY "token"
+#define OWNER_KEY "owner"
+#define CREATION_DATE_KEY "creation_date"
+#define EXPIRATION_DATE_KEY "expiration_date"
+#define COMMENT_KEY "comment"
+#define IS_DAV_COMMENT_KEY "is_dav_comment"
+#define CHILDREN_KEY "children"
+
+/* Number of characters from the head of a digest file name used to
+ calculate a subdirectory in which to drop that file. */
+#define DIGEST_SUBDIR_LEN 3
+
+
+
+/*** Generic helper functions. ***/
+
+/* Set *DIGEST to the MD5 hash of STR. */
+static svn_error_t *
+make_digest(const char **digest,
+ const char *str,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *checksum;
+
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, str, strlen(str), pool));
+
+ *digest = svn_checksum_to_cstring_display(checksum, pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Set the value of KEY (whose size is KEY_LEN, or APR_HASH_KEY_STRING
+ if unknown) to an svn_string_t-ized version of VALUE (whose size is
+ VALUE_LEN, or APR_HASH_KEY_STRING if unknown) in HASH. The value
+ will be allocated in POOL; KEY will not be duped. If either KEY or VALUE
+ is NULL, this function will do nothing. */
+static void
+hash_store(apr_hash_t *hash,
+ const char *key,
+ apr_ssize_t key_len,
+ const char *value,
+ apr_ssize_t value_len,
+ apr_pool_t *pool)
+{
+ if (! (key && value))
+ return;
+ if (value_len == APR_HASH_KEY_STRING)
+ value_len = strlen(value);
+ apr_hash_set(hash, key, key_len,
+ svn_string_ncreate(value, value_len, pool));
+}
+
+
+/* Fetch the value of KEY from HASH, returning only the cstring data
+ of that value (if it exists). */
+static const char *
+hash_fetch(apr_hash_t *hash,
+ const char *key,
+ apr_pool_t *pool)
+{
+ svn_string_t *str = svn_hash_gets(hash, key);
+ return str ? str->data : NULL;
+}
+
+
+/* SVN_ERR_FS_CORRUPT: the lockfile for PATH in FS is corrupt. */
+static svn_error_t *
+err_corrupt_lockfile(const char *fs_path, const char *path)
+{
+ return
+ svn_error_createf(
+ SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt lockfile for path '%s' in filesystem '%s'"),
+ path, fs_path);
+}
+
+
+/*** Digest file handling functions. ***/
+
+/* Return the path of the lock/entries file for which DIGEST is the
+ hashed repository relative path. */
+static const char *
+digest_path_from_digest(const char *fs_path,
+ const char *digest,
+ apr_pool_t *pool)
+{
+ return svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
+ apr_pstrmemdup(pool, digest, DIGEST_SUBDIR_LEN),
+ digest, NULL);
+}
+
+
+/* Set *DIGEST_PATH to the path to the lock/entries digest file associate
+ with PATH, where PATH is the path to the lock file or lock entries file
+ in FS. */
+static svn_error_t *
+digest_path_from_path(const char **digest_path,
+ const char *fs_path,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *digest;
+ SVN_ERR(make_digest(&digest, path, pool));
+ *digest_path = svn_dirent_join_many(pool, fs_path, PATH_LOCKS_DIR,
+ apr_pstrmemdup(pool, digest,
+ DIGEST_SUBDIR_LEN),
+ digest, NULL);
+ return SVN_NO_ERROR;
+}
+
+
+/* Write to DIGEST_PATH a representation of CHILDREN (which may be
+ empty, if the versioned path in FS represented by DIGEST_PATH has
+ no children) and LOCK (which may be NULL if that versioned path is
+ lock itself locked). Set the permissions of DIGEST_PATH to those of
+ PERMS_REFERENCE. Use POOL for all allocations.
+ */
+static svn_error_t *
+write_digest_file(apr_hash_t *children,
+ svn_lock_t *lock,
+ const char *fs_path,
+ const char *digest_path,
+ const char *perms_reference,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_stream_t *stream;
+ apr_hash_index_t *hi;
+ apr_hash_t *hash = apr_hash_make(pool);
+ const char *tmp_path;
+
+ SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
+ pool), fs_path, pool));
+ SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_dirname(digest_path, pool),
+ fs_path, pool));
+
+ if (lock)
+ {
+ const char *creation_date = NULL, *expiration_date = NULL;
+ if (lock->creation_date)
+ creation_date = svn_time_to_cstring(lock->creation_date, pool);
+ if (lock->expiration_date)
+ expiration_date = svn_time_to_cstring(lock->expiration_date, pool);
+ hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
+ lock->path, APR_HASH_KEY_STRING, pool);
+ hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
+ lock->token, APR_HASH_KEY_STRING, pool);
+ hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
+ lock->owner, APR_HASH_KEY_STRING, pool);
+ hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
+ lock->comment, APR_HASH_KEY_STRING, pool);
+ hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
+ lock->is_dav_comment ? "1" : "0", 1, pool);
+ hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
+ creation_date, APR_HASH_KEY_STRING, pool);
+ hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
+ expiration_date, APR_HASH_KEY_STRING, pool);
+ }
+ if (apr_hash_count(children))
+ {
+ svn_stringbuf_t *children_list = svn_stringbuf_create_empty(pool);
+ for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
+ {
+ svn_stringbuf_appendbytes(children_list,
+ svn__apr_hash_index_key(hi),
+ svn__apr_hash_index_klen(hi));
+ svn_stringbuf_appendbyte(children_list, '\n');
+ }
+ hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
+ children_list->data, children_list->len, pool);
+ }
+
+ SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
+ svn_dirent_dirname(digest_path, pool),
+ svn_io_file_del_none, pool, pool));
+ if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)))
+ {
+ svn_error_clear(svn_stream_close(stream));
+ return svn_error_createf(err->apr_err,
+ err,
+ _("Cannot write lock/entries hashfile '%s'"),
+ svn_dirent_local_style(tmp_path, pool));
+ }
+
+ SVN_ERR(svn_stream_close(stream));
+ SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool));
+ SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Parse the file at DIGEST_PATH, populating the lock LOCK_P in that
+ file (if it exists, and if *LOCK_P is non-NULL) and the hash of
+ CHILDREN_P (if any exist, and if *CHILDREN_P is non-NULL). Use POOL
+ for all allocations. */
+static svn_error_t *
+read_digest_file(apr_hash_t **children_p,
+ svn_lock_t **lock_p,
+ const char *fs_path,
+ const char *digest_path,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_lock_t *lock;
+ apr_hash_t *hash;
+ svn_stream_t *stream;
+ const char *val;
+
+ if (lock_p)
+ *lock_p = NULL;
+ if (children_p)
+ *children_p = apr_hash_make(pool);
+
+ err = svn_stream_open_readonly(&stream, digest_path, pool, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* If our caller doesn't care about anything but the presence of the
+ file... whatever. */
+ if (! (lock_p || children_p))
+ return svn_stream_close(stream);
+
+ hash = apr_hash_make(pool);
+ if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
+ {
+ svn_error_clear(svn_stream_close(stream));
+ return svn_error_createf(err->apr_err,
+ err,
+ _("Can't parse lock/entries hashfile '%s'"),
+ svn_dirent_local_style(digest_path, pool));
+ }
+ SVN_ERR(svn_stream_close(stream));
+
+ /* If our caller cares, see if we have a lock path in our hash. If
+ so, we'll assume we have a lock here. */
+ val = hash_fetch(hash, PATH_KEY, pool);
+ if (val && lock_p)
+ {
+ const char *path = val;
+
+ /* Create our lock and load it up. */
+ lock = svn_lock_create(pool);
+ lock->path = path;
+
+ if (! ((lock->token = hash_fetch(hash, TOKEN_KEY, pool))))
+ return svn_error_trace(err_corrupt_lockfile(fs_path, path));
+
+ if (! ((lock->owner = hash_fetch(hash, OWNER_KEY, pool))))
+ return svn_error_trace(err_corrupt_lockfile(fs_path, path));
+
+ if (! ((val = hash_fetch(hash, IS_DAV_COMMENT_KEY, pool))))
+ return svn_error_trace(err_corrupt_lockfile(fs_path, path));
+ lock->is_dav_comment = (val[0] == '1');
+
+ if (! ((val = hash_fetch(hash, CREATION_DATE_KEY, pool))))
+ return svn_error_trace(err_corrupt_lockfile(fs_path, path));
+ SVN_ERR(svn_time_from_cstring(&(lock->creation_date), val, pool));
+
+ if ((val = hash_fetch(hash, EXPIRATION_DATE_KEY, pool)))
+ SVN_ERR(svn_time_from_cstring(&(lock->expiration_date), val, pool));
+
+ lock->comment = hash_fetch(hash, COMMENT_KEY, pool);
+
+ *lock_p = lock;
+ }
+
+ /* If our caller cares, see if we have any children for this path. */
+ val = hash_fetch(hash, CHILDREN_KEY, pool);
+ if (val && children_p)
+ {
+ apr_array_header_t *kiddos = svn_cstring_split(val, "\n", FALSE, pool);
+ int i;
+
+ for (i = 0; i < kiddos->nelts; i++)
+ {
+ svn_hash_sets(*children_p, APR_ARRAY_IDX(kiddos, i, const char *),
+ (void *)1);
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Lock helper functions (path here are still FS paths, not on-disk
+ schema-supporting paths) ***/
+
+
+/* Write LOCK in FS to the actual OS filesystem.
+
+ Use PERMS_REFERENCE for the permissions of any digest files.
+
+ Note: this takes an FS_PATH because it's called from the hotcopy logic.
+ */
+static svn_error_t *
+set_lock(const char *fs_path,
+ svn_lock_t *lock,
+ const char *perms_reference,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
+ const char *lock_digest_path = NULL;
+ apr_pool_t *subpool;
+
+ SVN_ERR_ASSERT(lock);
+
+ /* Iterate in reverse, creating the lock for LOCK->path, and then
+ just adding entries for its parent, until we reach a parent
+ that's already listed in *its* parent. */
+ subpool = svn_pool_create(pool);
+ while (1729)
+ {
+ const char *digest_path, *digest_file;
+ apr_hash_t *this_children;
+ svn_lock_t *this_lock;
+
+ svn_pool_clear(subpool);
+
+ /* Calculate the DIGEST_PATH for the currently FS path, and then
+ get its DIGEST_FILE basename. */
+ SVN_ERR(digest_path_from_path(&digest_path, fs_path, this_path->data,
+ subpool));
+ digest_file = svn_dirent_basename(digest_path, subpool);
+
+ SVN_ERR(read_digest_file(&this_children, &this_lock, fs_path,
+ digest_path, subpool));
+
+ /* We're either writing a new lock (first time through only) or
+ a new entry (every time but the first). */
+ if (lock)
+ {
+ this_lock = lock;
+ lock = NULL;
+ lock_digest_path = apr_pstrdup(pool, digest_file);
+ }
+ else
+ {
+ /* If we already have an entry for this path, we're done. */
+ if (svn_hash_gets(this_children, lock_digest_path))
+ break;
+ svn_hash_sets(this_children, lock_digest_path, (void *)1);
+ }
+ SVN_ERR(write_digest_file(this_children, this_lock, fs_path,
+ digest_path, perms_reference, subpool));
+
+ /* Prep for next iteration, or bail if we're done. */
+ if (svn_fspath__is_root(this_path->data, this_path->len))
+ break;
+ svn_stringbuf_set(this_path,
+ svn_fspath__dirname(this_path->data, subpool));
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* Delete LOCK from FS in the actual OS filesystem. */
+static svn_error_t *
+delete_lock(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
+ const char *child_to_kill = NULL;
+ apr_pool_t *subpool;
+
+ SVN_ERR_ASSERT(lock);
+
+ /* Iterate in reverse, deleting the lock for LOCK->path, and then
+ deleting its entry as it appears in each of its parents. */
+ subpool = svn_pool_create(pool);
+ while (1729)
+ {
+ const char *digest_path, *digest_file;
+ apr_hash_t *this_children;
+ svn_lock_t *this_lock;
+
+ svn_pool_clear(subpool);
+
+ /* Calculate the DIGEST_PATH for the currently FS path, and then
+ get its DIGEST_FILE basename. */
+ SVN_ERR(digest_path_from_path(&digest_path, fs->path, this_path->data,
+ subpool));
+ digest_file = svn_dirent_basename(digest_path, subpool);
+
+ SVN_ERR(read_digest_file(&this_children, &this_lock, fs->path,
+ digest_path, subpool));
+
+ /* Delete the lock (first time through only). */
+ if (lock)
+ {
+ this_lock = NULL;
+ lock = NULL;
+ child_to_kill = apr_pstrdup(pool, digest_file);
+ }
+
+ if (child_to_kill)
+ svn_hash_sets(this_children, child_to_kill, NULL);
+
+ if (! (this_lock || apr_hash_count(this_children) != 0))
+ {
+ /* Special case: no goodz, no file. And remember to nix
+ the entry for it in its parent. */
+ SVN_ERR(svn_io_remove_file2(digest_path, FALSE, subpool));
+ }
+ else
+ {
+ const char *rev_0_path;
+ SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, fs, 0, pool));
+ SVN_ERR(write_digest_file(this_children, this_lock, fs->path,
+ digest_path, rev_0_path, subpool));
+ }
+
+ /* Prep for next iteration, or bail if we're done. */
+ if (svn_fspath__is_root(this_path->data, this_path->len))
+ break;
+ svn_stringbuf_set(this_path,
+ svn_fspath__dirname(this_path->data, subpool));
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
+ TRUE if the caller (or one of its callers) has taken out the
+ repository-wide write lock, FALSE otherwise. If MUST_EXIST is
+ not set, the function will simply return NULL in *LOCK_P instead
+ of creating an SVN_FS__ERR_NO_SUCH_LOCK error in case the lock
+ was not found (much faster). Use POOL for allocations. */
+static svn_error_t *
+get_lock(svn_lock_t **lock_p,
+ svn_fs_t *fs,
+ const char *path,
+ svn_boolean_t have_write_lock,
+ svn_boolean_t must_exist,
+ apr_pool_t *pool)
+{
+ svn_lock_t *lock = NULL;
+ const char *digest_path;
+ svn_node_kind_t kind;
+
+ SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
+ SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
+
+ *lock_p = NULL;
+ if (kind != svn_node_none)
+ SVN_ERR(read_digest_file(NULL, &lock, fs->path, digest_path, pool));
+
+ if (! lock)
+ return must_exist ? SVN_FS__ERR_NO_SUCH_LOCK(fs, path) : SVN_NO_ERROR;
+
+ /* Don't return an expired lock. */
+ if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
+ {
+ /* Only remove the lock if we have the write lock.
+ Read operations shouldn't change the filesystem. */
+ if (have_write_lock)
+ SVN_ERR(delete_lock(fs, lock, pool));
+ return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
+ }
+
+ *lock_p = lock;
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *LOCK_P to the lock for PATH in FS. HAVE_WRITE_LOCK should be
+ TRUE if the caller (or one of its callers) has taken out the
+ repository-wide write lock, FALSE otherwise. Use POOL for
+ allocations. */
+static svn_error_t *
+get_lock_helper(svn_fs_t *fs,
+ svn_lock_t **lock_p,
+ const char *path,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool)
+{
+ svn_lock_t *lock;
+ svn_error_t *err;
+
+ err = get_lock(&lock, fs, path, have_write_lock, FALSE, pool);
+
+ /* We've deliberately decided that this function doesn't tell the
+ caller *why* the lock is unavailable. */
+ if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
+ || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)))
+ {
+ svn_error_clear(err);
+ *lock_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ *lock_p = lock;
+ return SVN_NO_ERROR;
+}
+
+
+/* Baton for locks_walker(). */
+struct walk_locks_baton {
+ svn_fs_get_locks_callback_t get_locks_func;
+ void *get_locks_baton;
+ svn_fs_t *fs;
+};
+
+/* Implements walk_digests_callback_t. */
+static svn_error_t *
+locks_walker(void *baton,
+ const char *fs_path,
+ const char *digest_path,
+ apr_hash_t *children,
+ svn_lock_t *lock,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool)
+{
+ struct walk_locks_baton *wlb = baton;
+
+ if (lock)
+ {
+ /* Don't report an expired lock. */
+ if (lock->expiration_date == 0
+ || (apr_time_now() <= lock->expiration_date))
+ {
+ if (wlb->get_locks_func)
+ SVN_ERR(wlb->get_locks_func(wlb->get_locks_baton, lock, pool));
+ }
+ else
+ {
+ /* Only remove the lock if we have the write lock.
+ Read operations shouldn't change the filesystem. */
+ if (have_write_lock)
+ SVN_ERR(delete_lock(wlb->fs, lock, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Callback type for walk_digest_files().
+ *
+ * CHILDREN and LOCK come from a read_digest_file(digest_path) call.
+ */
+typedef svn_error_t *(*walk_digests_callback_t)(void *baton,
+ const char *fs_path,
+ const char *digest_path,
+ apr_hash_t *children,
+ svn_lock_t *lock,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool);
+
+/* A recursive function that calls WALK_DIGESTS_FUNC/WALK_DIGESTS_BATON for
+ all lock digest files in and under PATH in FS.
+ HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
+ has the FS write lock. */
+static svn_error_t *
+walk_digest_files(const char *fs_path,
+ const char *digest_path,
+ walk_digests_callback_t walk_digests_func,
+ void *walk_digests_baton,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_hash_t *children;
+ apr_pool_t *subpool;
+ svn_lock_t *lock;
+
+ /* First, send up any locks in the current digest file. */
+ SVN_ERR(read_digest_file(&children, &lock, fs_path, digest_path, pool));
+
+ SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path,
+ children, lock,
+ have_write_lock, pool));
+
+ /* Now, recurse on this thing's child entries (if any; bail otherwise). */
+ if (! apr_hash_count(children))
+ return SVN_NO_ERROR;
+ subpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
+ {
+ const char *digest = svn__apr_hash_index_key(hi);
+ svn_pool_clear(subpool);
+ SVN_ERR(walk_digest_files
+ (fs_path, digest_path_from_digest(fs_path, digest, subpool),
+ walk_digests_func, walk_digests_baton, have_write_lock, subpool));
+ }
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* A recursive function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
+ all locks in and under PATH in FS.
+ HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
+ has the FS write lock. */
+static svn_error_t *
+walk_locks(svn_fs_t *fs,
+ const char *digest_path,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool)
+{
+ struct walk_locks_baton wlb;
+
+ wlb.get_locks_func = get_locks_func;
+ wlb.get_locks_baton = get_locks_baton;
+ wlb.fs = fs;
+ SVN_ERR(walk_digest_files(fs->path, digest_path, locks_walker, &wlb,
+ have_write_lock, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Utility function: verify that a lock can be used. Interesting
+ errors returned from this function:
+
+ SVN_ERR_FS_NO_USER: No username attached to FS.
+ SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
+ SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
+ */
+static svn_error_t *
+verify_lock(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ if ((! fs->access_ctx) || (! fs->access_ctx->username))
+ return svn_error_createf
+ (SVN_ERR_FS_NO_USER, NULL,
+ _("Cannot verify lock on path '%s'; no username available"),
+ lock->path);
+
+ else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
+ return svn_error_createf
+ (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
+ _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
+ fs->access_ctx->username, lock->path, lock->owner);
+
+ else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
+ return svn_error_createf
+ (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ _("Cannot verify lock on path '%s'; no matching lock-token available"),
+ lock->path);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the svn_fs_get_locks_callback_t interface, where
+ BATON is just an svn_fs_t object. */
+static svn_error_t *
+get_locks_callback(void *baton,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ return verify_lock(baton, lock, pool);
+}
+
+
+/* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
+svn_error_t *
+svn_fs_fs__allow_locked_operation(const char *path,
+ svn_fs_t *fs,
+ svn_boolean_t recurse,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool)
+{
+ path = svn_fs__canonicalize_abspath(path, pool);
+ if (recurse)
+ {
+ /* Discover all locks at or below the path. */
+ const char *digest_path;
+ SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
+ SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
+ fs, have_write_lock, pool));
+ }
+ else
+ {
+ /* Discover and verify any lock attached to the path. */
+ svn_lock_t *lock;
+ SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool));
+ if (lock)
+ SVN_ERR(verify_lock(fs, lock, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Baton used for lock_body below. */
+struct lock_baton {
+ svn_lock_t **lock_p;
+ svn_fs_t *fs;
+ const char *path;
+ const char *token;
+ const char *comment;
+ svn_boolean_t is_dav_comment;
+ apr_time_t expiration_date;
+ svn_revnum_t current_rev;
+ svn_boolean_t steal_lock;
+ apr_pool_t *pool;
+};
+
+
+/* This implements the svn_fs_fs__with_write_lock() 'body' callback
+ type, and assumes that the write lock is held.
+ BATON is a 'struct lock_baton *'. */
+static svn_error_t *
+lock_body(void *baton, apr_pool_t *pool)
+{
+ struct lock_baton *lb = baton;
+ svn_node_kind_t kind;
+ svn_lock_t *existing_lock;
+ svn_lock_t *lock;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest;
+ const char *rev_0_path;
+
+ /* Until we implement directory locks someday, we only allow locks
+ on files or non-existent paths. */
+ /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
+ library dependencies, which are not portable. */
+ SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
+ SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
+ SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
+ if (kind == svn_node_dir)
+ return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
+
+ /* While our locking implementation easily supports the locking of
+ nonexistent paths, we deliberately choose not to allow such madness. */
+ if (kind == svn_node_none)
+ {
+ if (SVN_IS_VALID_REVNUM(lb->current_rev))
+ return svn_error_createf(
+ SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"),
+ lb->path);
+ else
+ return svn_error_createf(
+ SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"),
+ lb->path);
+ }
+
+ /* We need to have a username attached to the fs. */
+ if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(lb->fs);
+
+ /* Is the caller attempting to lock an out-of-date working file? */
+ if (SVN_IS_VALID_REVNUM(lb->current_rev))
+ {
+ svn_revnum_t created_rev;
+ SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
+ pool));
+
+ /* SVN_INVALID_REVNUM means the path doesn't exist. So
+ apparently somebody is trying to lock something in their
+ working copy, but somebody else has deleted the thing
+ from HEAD. That counts as being 'out of date'. */
+ if (! SVN_IS_VALID_REVNUM(created_rev))
+ return svn_error_createf
+ (SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"), lb->path);
+
+ if (lb->current_rev < created_rev)
+ return svn_error_createf
+ (SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Lock failed: newer version of '%s' exists"), lb->path);
+ }
+
+ /* If the caller provided a TOKEN, we *really* need to see
+ if a lock already exists with that token, and if so, verify that
+ the lock's path matches PATH. Otherwise we run the risk of
+ breaking the 1-to-1 mapping of lock tokens to locked paths. */
+ /* ### TODO: actually do this check. This is tough, because the
+ schema doesn't supply a lookup-by-token mechanism. */
+
+ /* Is the path already locked?
+
+ Note that this next function call will automatically ignore any
+ errors about {the path not existing as a key, the path's token
+ not existing as a key, the lock just having been expired}. And
+ that's totally fine. Any of these three errors are perfectly
+ acceptable to ignore; it means that the path is now free and
+ clear for locking, because the fsfs funcs just cleared out both
+ of the tables for us. */
+ SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
+ if (existing_lock)
+ {
+ if (! lb->steal_lock)
+ {
+ /* Sorry, the path is already locked. */
+ return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
+ }
+ else
+ {
+ /* STEAL_LOCK was passed, so fs_username is "stealing" the
+ lock from lock->owner. Destroy the existing lock. */
+ SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
+ }
+ }
+
+ /* Create our new lock, and add it to the tables.
+ Ensure that the lock is created in the correct pool. */
+ lock = svn_lock_create(lb->pool);
+ if (lb->token)
+ lock->token = apr_pstrdup(lb->pool, lb->token);
+ else
+ SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
+ lb->pool));
+ lock->path = apr_pstrdup(lb->pool, lb->path);
+ lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
+ lock->comment = apr_pstrdup(lb->pool, lb->comment);
+ lock->is_dav_comment = lb->is_dav_comment;
+ lock->creation_date = apr_time_now();
+ lock->expiration_date = lb->expiration_date;
+ SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, lb->fs, 0, pool));
+ SVN_ERR(set_lock(lb->fs->path, lock, rev_0_path, pool));
+ *lb->lock_p = lock;
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton used for unlock_body below. */
+struct unlock_baton {
+ svn_fs_t *fs;
+ const char *path;
+ const char *token;
+ svn_boolean_t break_lock;
+};
+
+/* This implements the svn_fs_fs__with_write_lock() 'body' callback
+ type, and assumes that the write lock is held.
+ BATON is a 'struct unlock_baton *'. */
+static svn_error_t *
+unlock_body(void *baton, apr_pool_t *pool)
+{
+ struct unlock_baton *ub = baton;
+ svn_lock_t *lock;
+
+ /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
+ SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, TRUE, pool));
+
+ /* Unless breaking the lock, we do some checks. */
+ if (! ub->break_lock)
+ {
+ /* Sanity check: the incoming token should match lock->token. */
+ if (strcmp(ub->token, lock->token) != 0)
+ return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
+
+ /* There better be a username attached to the fs. */
+ if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
+ return SVN_FS__ERR_NO_USER(ub->fs);
+
+ /* And that username better be the same as the lock's owner. */
+ if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
+ return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
+ ub->fs, ub->fs->access_ctx->username, lock->owner);
+ }
+
+ /* Remove lock and lock token files. */
+ return delete_lock(ub->fs, lock, pool);
+}
+
+
+/*** Public API implementations ***/
+
+svn_error_t *
+svn_fs_fs__lock(svn_lock_t **lock_p,
+ svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_revnum_t current_rev,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool)
+{
+ struct lock_baton lb;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ path = svn_fs__canonicalize_abspath(path, pool);
+
+ lb.lock_p = lock_p;
+ lb.fs = fs;
+ lb.path = path;
+ lb.token = token;
+ lb.comment = comment;
+ lb.is_dav_comment = is_dav_comment;
+ lb.expiration_date = expiration_date;
+ lb.current_rev = current_rev;
+ lb.steal_lock = steal_lock;
+ lb.pool = pool;
+
+ return svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__generate_lock_token(const char **token,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* Notice that 'fs' is currently unused. But perhaps someday, we'll
+ want to use the fs UUID + some incremented number? For now, we
+ generate a URI that matches the DAV RFC. We could change this to
+ some other URI scheme someday, if we wish. */
+ *token = apr_pstrcat(pool, "opaquelocktoken:",
+ svn_uuid_generate(pool), (char *)NULL);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__unlock(svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool)
+{
+ struct unlock_baton ub;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ path = svn_fs__canonicalize_abspath(path, pool);
+
+ ub.fs = fs;
+ ub.path = path;
+ ub.token = token;
+ ub.break_lock = break_lock;
+
+ return svn_fs_fs__with_write_lock(fs, unlock_body, &ub, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__get_lock(svn_lock_t **lock_p,
+ svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ path = svn_fs__canonicalize_abspath(path, pool);
+ return get_lock_helper(fs, lock_p, path, FALSE, pool);
+}
+
+
+/* Baton for get_locks_filter_func(). */
+typedef struct get_locks_filter_baton_t
+{
+ const char *path;
+ svn_depth_t requested_depth;
+ svn_fs_get_locks_callback_t get_locks_func;
+ void *get_locks_baton;
+
+} get_locks_filter_baton_t;
+
+
+/* A wrapper for the GET_LOCKS_FUNC passed to svn_fs_fs__get_locks()
+ which filters out locks on paths that aren't within
+ BATON->requested_depth of BATON->path before called
+ BATON->get_locks_func() with BATON->get_locks_baton.
+
+ NOTE: See issue #3660 for details about how the FSFS lock
+ management code is inconsistent. Until that inconsistency is
+ resolved, we take this filtering approach rather than honoring
+ depth requests closer to the crawling code. In other words, once
+ we decide how to resolve issue #3660, there might be a more
+ performant way to honor the depth passed to svn_fs_fs__get_locks(). */
+static svn_error_t *
+get_locks_filter_func(void *baton,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ get_locks_filter_baton_t *b = baton;
+
+ /* Filter out unwanted paths. Since Subversion only allows
+ locks on files, we can treat depth=immediates the same as
+ depth=files for filtering purposes. Meaning, we'll keep
+ this lock if:
+
+ a) its path is the very path we queried, or
+ b) we've asked for a fully recursive answer, or
+ c) we've asked for depth=files or depth=immediates, and this
+ lock is on an immediate child of our query path.
+ */
+ if ((strcmp(b->path, lock->path) == 0)
+ || (b->requested_depth == svn_depth_infinity))
+ {
+ SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
+ }
+ else if ((b->requested_depth == svn_depth_files) ||
+ (b->requested_depth == svn_depth_immediates))
+ {
+ const char *rel_uri = svn_fspath__skip_ancestor(b->path, lock->path);
+ if (rel_uri && (svn_path_component_count(rel_uri) == 1))
+ SVN_ERR(b->get_locks_func(b->get_locks_baton, lock, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_locks(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ apr_pool_t *pool)
+{
+ const char *digest_path;
+ get_locks_filter_baton_t glfb;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ path = svn_fs__canonicalize_abspath(path, pool);
+
+ glfb.path = path;
+ glfb.requested_depth = depth;
+ glfb.get_locks_func = get_locks_func;
+ glfb.get_locks_baton = get_locks_baton;
+
+ /* Get the top digest path in our tree of interest, and then walk it. */
+ SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
+ SVN_ERR(walk_locks(fs, digest_path, get_locks_filter_func, &glfb,
+ FALSE, pool));
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/lock.h b/contrib/subversion/subversion/libsvn_fs_fs/lock.h
new file mode 100644
index 0000000..1acc79e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/lock.h
@@ -0,0 +1,103 @@
+/* lock.h : internal interface to lock functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_LOCK_H
+#define SVN_LIBSVN_FS_LOCK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* These functions implement some of the calls in the FS loader
+ library's fs vtables. */
+
+svn_error_t *svn_fs_fs__lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_revnum_t current_rev,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_fs__generate_lock_token(const char **token,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_fs__unlock(svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_fs__get_lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_fs__get_locks(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ apr_pool_t *pool);
+
+
+/* Examine PATH for existing locks, and check whether they can be
+ used. Use POOL for temporary allocations.
+
+ If no locks are present, return SVN_NO_ERROR.
+
+ If PATH is locked (or contains locks "below" it, when RECURSE is
+ set), then verify that:
+
+ 1. a username has been supplied to TRAIL->fs's access-context,
+ else return SVN_ERR_FS_NO_USER.
+
+ 2. for every lock discovered, the current username in the access
+ context of TRAIL->fs matches the "owner" of the lock, else
+ return SVN_ERR_FS_LOCK_OWNER_MISMATCH.
+
+ 3. for every lock discovered, a matching lock token has been
+ passed into TRAIL->fs's access-context, else return
+ SVN_ERR_FS_BAD_LOCK_TOKEN.
+
+ If all three conditions are met, return SVN_NO_ERROR.
+
+ If the caller (directly or indirectly) has the FS write lock,
+ HAVE_WRITE_LOCK should be true.
+*/
+svn_error_t *svn_fs_fs__allow_locked_operation(const char *path,
+ svn_fs_t *fs,
+ svn_boolean_t recurse,
+ svn_boolean_t have_write_lock,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_LOCK_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/rep-cache-db.h b/contrib/subversion/subversion/libsvn_fs_fs/rep-cache-db.h
new file mode 100644
index 0000000..ed379a1
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/rep-cache-db.h
@@ -0,0 +1,83 @@
+/* This file is automatically generated from rep-cache-db.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_fs_fs/token-map.h.
+ * Do not edit this file -- edit the source and rerun gen-make.py */
+
+#define STMT_CREATE_SCHEMA 0
+#define STMT_0_INFO {"STMT_CREATE_SCHEMA", NULL}
+#define STMT_0 \
+ "CREATE TABLE rep_cache ( " \
+ " hash TEXT NOT NULL PRIMARY KEY, " \
+ " revision INTEGER NOT NULL, " \
+ " offset INTEGER NOT NULL, " \
+ " size INTEGER NOT NULL, " \
+ " expanded_size INTEGER NOT NULL " \
+ " ); " \
+ "PRAGMA USER_VERSION = 1; " \
+ ""
+
+#define STMT_GET_REP 1
+#define STMT_1_INFO {"STMT_GET_REP", NULL}
+#define STMT_1 \
+ "SELECT revision, offset, size, expanded_size " \
+ "FROM rep_cache " \
+ "WHERE hash = ?1 " \
+ ""
+
+#define STMT_SET_REP 2
+#define STMT_2_INFO {"STMT_SET_REP", NULL}
+#define STMT_2 \
+ "INSERT OR FAIL INTO rep_cache (hash, revision, offset, size, expanded_size) " \
+ "VALUES (?1, ?2, ?3, ?4, ?5) " \
+ ""
+
+#define STMT_GET_REPS_FOR_RANGE 3
+#define STMT_3_INFO {"STMT_GET_REPS_FOR_RANGE", NULL}
+#define STMT_3 \
+ "SELECT hash, revision, offset, size, expanded_size " \
+ "FROM rep_cache " \
+ "WHERE revision >= ?1 AND revision <= ?2 " \
+ ""
+
+#define STMT_GET_MAX_REV 4
+#define STMT_4_INFO {"STMT_GET_MAX_REV", NULL}
+#define STMT_4 \
+ "SELECT MAX(revision) " \
+ "FROM rep_cache " \
+ ""
+
+#define STMT_DEL_REPS_YOUNGER_THAN_REV 5
+#define STMT_5_INFO {"STMT_DEL_REPS_YOUNGER_THAN_REV", NULL}
+#define STMT_5 \
+ "DELETE FROM rep_cache " \
+ "WHERE revision > ?1 " \
+ ""
+
+#define STMT_LOCK_REP 6
+#define STMT_6_INFO {"STMT_LOCK_REP", NULL}
+#define STMT_6 \
+ "BEGIN TRANSACTION; " \
+ "INSERT INTO rep_cache VALUES ('dummy', 0, 0, 0, 0) " \
+ ""
+
+#define REP_CACHE_DB_SQL_DECLARE_STATEMENTS(varname) \
+ static const char * const varname[] = { \
+ STMT_0, \
+ STMT_1, \
+ STMT_2, \
+ STMT_3, \
+ STMT_4, \
+ STMT_5, \
+ STMT_6, \
+ NULL \
+ }
+
+#define REP_CACHE_DB_SQL_DECLARE_STATEMENT_INFO(varname) \
+ static const char * const varname[][2] = { \
+ STMT_0_INFO, \
+ STMT_1_INFO, \
+ STMT_2_INFO, \
+ STMT_3_INFO, \
+ STMT_4_INFO, \
+ STMT_5_INFO, \
+ STMT_6_INFO, \
+ {NULL, NULL} \
+ }
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/rep-cache-db.sql b/contrib/subversion/subversion/libsvn_fs_fs/rep-cache-db.sql
new file mode 100644
index 0000000..b88c3e0
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/rep-cache-db.sql
@@ -0,0 +1,65 @@
+/* rep-cache-db.sql -- schema for use in rep-caching
+ * This is intended for use with SQLite 3
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+-- STMT_CREATE_SCHEMA
+/* A table mapping representation hashes to locations in a rev file. */
+CREATE TABLE rep_cache (
+ hash TEXT NOT NULL PRIMARY KEY,
+ revision INTEGER NOT NULL,
+ offset INTEGER NOT NULL,
+ size INTEGER NOT NULL,
+ expanded_size INTEGER NOT NULL
+ );
+
+PRAGMA USER_VERSION = 1;
+
+
+-- STMT_GET_REP
+SELECT revision, offset, size, expanded_size
+FROM rep_cache
+WHERE hash = ?1
+
+-- STMT_SET_REP
+INSERT OR FAIL INTO rep_cache (hash, revision, offset, size, expanded_size)
+VALUES (?1, ?2, ?3, ?4, ?5)
+
+-- STMT_GET_REPS_FOR_RANGE
+SELECT hash, revision, offset, size, expanded_size
+FROM rep_cache
+WHERE revision >= ?1 AND revision <= ?2
+
+-- STMT_GET_MAX_REV
+SELECT MAX(revision)
+FROM rep_cache
+
+-- STMT_DEL_REPS_YOUNGER_THAN_REV
+DELETE FROM rep_cache
+WHERE revision > ?1
+
+/* An INSERT takes an SQLite reserved lock that prevents other writes
+ but doesn't block reads. The incomplete transaction means that no
+ permanent change is made to the database and the transaction is
+ removed when the database is closed. */
+-- STMT_LOCK_REP
+BEGIN TRANSACTION;
+INSERT INTO rep_cache VALUES ('dummy', 0, 0, 0, 0)
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/rep-cache.c b/contrib/subversion/subversion/libsvn_fs_fs/rep-cache.c
new file mode 100644
index 0000000..3a94690
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/rep-cache.c
@@ -0,0 +1,381 @@
+/* rep-sharing.c --- the rep-sharing cache for fsfs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_pools.h"
+
+#include "svn_private_config.h"
+
+#include "fs_fs.h"
+#include "fs.h"
+#include "rep-cache.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_path.h"
+
+#include "private/svn_sqlite.h"
+
+#include "rep-cache-db.h"
+
+/* A few magic values */
+#define REP_CACHE_SCHEMA_FORMAT 1
+
+REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements);
+
+
+
+/** Helper functions. **/
+static APR_INLINE const char *
+path_rep_cache_db(const char *fs_path,
+ apr_pool_t *result_pool)
+{
+ return svn_dirent_join(fs_path, REP_CACHE_DB_NAME, result_pool);
+}
+
+/* Check that REP refers to a revision that exists in FS. */
+static svn_error_t *
+rep_has_been_born(representation_t *rep,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(rep);
+
+ SVN_ERR(svn_fs_fs__revision_exists(rep->revision, fs, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/** Library-private API's. **/
+
+/* Body of svn_fs_fs__open_rep_cache().
+ Implements svn_atomic__init_once().init_func.
+ */
+static svn_error_t *
+open_rep_cache(void *baton,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = baton;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_sqlite__db_t *sdb;
+ const char *db_path;
+ int version;
+
+ /* Open (or create) the sqlite database. It will be automatically
+ closed when fs->pool is destoyed. */
+ db_path = path_rep_cache_db(fs->path, pool);
+ SVN_ERR(svn_sqlite__open(&sdb, db_path,
+ svn_sqlite__mode_rwcreate, statements,
+ 0, NULL,
+ fs->pool, pool));
+
+ SVN_ERR(svn_sqlite__read_schema_version(&version, sdb, pool));
+ if (version < REP_CACHE_SCHEMA_FORMAT)
+ {
+ /* Must be 0 -- an uninitialized (no schema) database. Create
+ the schema. Results in schema version of 1. */
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_SCHEMA));
+ }
+
+ /* This is used as a flag that the database is available so don't
+ set it earlier. */
+ ffd->rep_cache_db = sdb;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__open_rep_cache(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_error_t *err = svn_atomic__init_once(&ffd->rep_cache_db_opened,
+ open_rep_cache, fs, pool);
+ return svn_error_quick_wrap(err, _("Couldn't open rep-cache database"));
+}
+
+svn_error_t *
+svn_fs_fs__exists_rep_cache(svn_boolean_t *exists,
+ svn_fs_t *fs, apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, pool),
+ &kind, pool));
+
+ *exists = (kind != svn_node_none);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_error_t *(*walker)(representation_t *,
+ void *,
+ svn_fs_t *,
+ apr_pool_t *),
+ void *walker_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int iterations = 0;
+
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* Don't check ffd->rep_sharing_allowed. */
+ SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
+
+ if (! ffd->rep_cache_db)
+ SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
+
+ /* Check global invariants. */
+ if (start == 0)
+ {
+ svn_revnum_t max;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
+ STMT_GET_MAX_REV));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ max = svn_sqlite__column_revnum(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (SVN_IS_VALID_REVNUM(max)) /* The rep-cache could be empty. */
+ SVN_ERR(svn_fs_fs__revision_exists(max, fs, iterpool));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
+ STMT_GET_REPS_FOR_RANGE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "rr",
+ start, end));
+
+ /* Walk the cache entries. */
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ representation_t *rep;
+ const char *sha1_digest;
+ svn_error_t *err;
+
+ /* Clear ITERPOOL occasionally. */
+ if (iterations++ % 16 == 0)
+ svn_pool_clear(iterpool);
+
+ /* Check for cancellation. */
+ if (cancel_func)
+ {
+ err = cancel_func(cancel_baton);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+ }
+
+ /* Construct a representation_t. */
+ rep = apr_pcalloc(iterpool, sizeof(*rep));
+ sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool);
+ err = svn_checksum_parse_hex(&rep->sha1_checksum,
+ svn_checksum_sha1, sha1_digest,
+ iterpool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+ rep->revision = svn_sqlite__column_revnum(stmt, 1);
+ rep->offset = svn_sqlite__column_int64(stmt, 2);
+ rep->size = svn_sqlite__column_int64(stmt, 3);
+ rep->expanded_size = svn_sqlite__column_int64(stmt, 4);
+
+ /* Walk. */
+ err = walker(rep, walker_baton, fs, iterpool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This function's caller ignores most errors it returns.
+ If you extend this function, check the callsite to see if you have
+ to make it not-ignore additional error codes. */
+svn_error_t *
+svn_fs_fs__get_rep_reference(representation_t **rep,
+ svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
+ if (! ffd->rep_cache_db)
+ SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
+
+ /* We only allow SHA1 checksums in this table. */
+ if (checksum->kind != svn_checksum_sha1)
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
+ _("Only SHA1 checksums can be used as keys in the "
+ "rep_cache table.\n"));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s",
+ svn_checksum_to_cstring(checksum, pool)));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ *rep = apr_pcalloc(pool, sizeof(**rep));
+ (*rep)->sha1_checksum = svn_checksum_dup(checksum, pool);
+ (*rep)->revision = svn_sqlite__column_revnum(stmt, 0);
+ (*rep)->offset = svn_sqlite__column_int64(stmt, 1);
+ (*rep)->size = svn_sqlite__column_int64(stmt, 2);
+ (*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3);
+ }
+ else
+ *rep = NULL;
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (*rep)
+ SVN_ERR(rep_has_been_born(*rep, fs, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__set_rep_reference(svn_fs_t *fs,
+ representation_t *rep,
+ svn_boolean_t reject_dup,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_sqlite__stmt_t *stmt;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
+ if (! ffd->rep_cache_db)
+ SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
+
+ /* We only allow SHA1 checksums in this table. */
+ if (rep->sha1_checksum == NULL)
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
+ _("Only SHA1 checksums can be used as keys in the "
+ "rep_cache table.\n"));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_SET_REP));
+ SVN_ERR(svn_sqlite__bindf(stmt, "siiii",
+ svn_checksum_to_cstring(rep->sha1_checksum, pool),
+ (apr_int64_t) rep->revision,
+ (apr_int64_t) rep->offset,
+ (apr_int64_t) rep->size,
+ (apr_int64_t) rep->expanded_size));
+
+ err = svn_sqlite__insert(NULL, stmt);
+ if (err)
+ {
+ representation_t *old_rep;
+
+ if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ /* Constraint failed so the mapping for SHA1_CHECKSUM->REP
+ should exist. If so, and the value is the same one we were
+ about to write, that's cool -- just do nothing. If, however,
+ the value is *different*, that's a red flag! */
+ SVN_ERR(svn_fs_fs__get_rep_reference(&old_rep, fs, rep->sha1_checksum,
+ pool));
+
+ if (old_rep)
+ {
+ if (reject_dup && ((old_rep->revision != rep->revision)
+ || (old_rep->offset != rep->offset)
+ || (old_rep->size != rep->size)
+ || (old_rep->expanded_size != rep->expanded_size)))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ apr_psprintf(pool,
+ _("Representation key for checksum '%%s' exists "
+ "in filesystem '%%s' with a different value "
+ "(%%ld,%%%s,%%%s,%%%s) than what we were about "
+ "to store (%%ld,%%%s,%%%s,%%%s)"),
+ APR_OFF_T_FMT, SVN_FILESIZE_T_FMT,
+ SVN_FILESIZE_T_FMT, APR_OFF_T_FMT,
+ SVN_FILESIZE_T_FMT, SVN_FILESIZE_T_FMT),
+ svn_checksum_to_cstring_display(rep->sha1_checksum, pool),
+ fs->path, old_rep->revision, old_rep->offset, old_rep->size,
+ old_rep->expanded_size, rep->revision, rep->offset, rep->size,
+ rep->expanded_size);
+ else
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* Something really odd at this point, we failed to insert the
+ checksum AND failed to read an existing checksum. Do we need
+ to flag this? */
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__del_rep_reference(svn_fs_t *fs,
+ svn_revnum_t youngest,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
+ if (! ffd->rep_cache_db)
+ SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
+ STMT_DEL_REPS_YOUNGER_THAN_REV));
+ SVN_ERR(svn_sqlite__bindf(stmt, "r", youngest));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__lock_rep_cache(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (! ffd->rep_cache_db)
+ SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
+
+ SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/rep-cache.h b/contrib/subversion/subversion/libsvn_fs_fs/rep-cache.h
new file mode 100644
index 0000000..3ccb056
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/rep-cache.h
@@ -0,0 +1,101 @@
+/* rep-cache.h : interface to rep cache db functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_FS_REP_CACHE_H
+#define SVN_LIBSVN_FS_FS_REP_CACHE_H
+
+#include "svn_error.h"
+
+#include "fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define REP_CACHE_DB_NAME "rep-cache.db"
+
+/* Open and create, if needed, the rep cache database associated with FS.
+ Use POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__open_rep_cache(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+/* Set *EXISTS to TRUE iff the rep-cache DB file exists. */
+svn_error_t *
+svn_fs_fs__exists_rep_cache(svn_boolean_t *exists,
+ svn_fs_t *fs, apr_pool_t *pool);
+
+/* Iterate all representations currently in FS's cache. */
+svn_error_t *
+svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_error_t *(*walker)(representation_t *rep,
+ void *walker_baton,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool),
+ void *walker_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/* Return the representation REP in FS which has fulltext CHECKSUM.
+ REP is allocated in POOL. If the rep cache database has not been
+ opened, just set *REP to NULL. */
+svn_error_t *
+svn_fs_fs__get_rep_reference(representation_t **rep,
+ svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ apr_pool_t *pool);
+
+/* Set the representation REP in FS, using REP->CHECKSUM.
+ Use POOL for temporary allocations.
+
+ If the rep cache database has not been opened, this may be a no op.
+
+ If REJECT_DUP is TRUE, return an error if there is an existing
+ match for REP->CHECKSUM. */
+svn_error_t *
+svn_fs_fs__set_rep_reference(svn_fs_t *fs,
+ representation_t *rep,
+ svn_boolean_t reject_dup,
+ apr_pool_t *pool);
+
+/* Delete from the cache all reps corresponding to revisions younger
+ than YOUNGEST. */
+svn_error_t *
+svn_fs_fs__del_rep_reference(svn_fs_t *fs,
+ svn_revnum_t youngest,
+ apr_pool_t *pool);
+
+/* Start a transaction to take an SQLite reserved lock that prevents
+ other writes. */
+svn_error_t *
+svn_fs_fs__lock_rep_cache(svn_fs_t *fs,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_FS_REP_CACHE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/structure b/contrib/subversion/subversion/libsvn_fs_fs/structure
new file mode 100644
index 0000000..41caf1d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/structure
@@ -0,0 +1,621 @@
+This file describes the design, layouts, and file formats of a
+libsvn_fs_fs repository.
+
+Design
+------
+
+In FSFS, each committed revision is represented as an immutable file
+containing the new node-revisions, contents, and changed-path
+information for the revision, plus a second, changeable file
+containing the revision properties.
+
+In contrast to the BDB back end, the contents of recent revision of
+files are stored as deltas against earlier revisions, instead of the
+other way around. This is less efficient for common-case checkouts,
+but brings greater simplicity and robustness, as well as the
+flexibility to make commits work without write access to existing
+revisions. Skip-deltas and delta combination mitigate the checkout
+cost.
+
+In-progress transactions are represented with a prototype rev file
+containing only the new text representations of files (appended to as
+changed file contents come in), along with a separate file for each
+node-revision, directory representation, or property representation
+which has been changed or added in the transaction. During the final
+stage of the commit, these separate files are marshalled onto the end
+of the prototype rev file to form the immutable revision file.
+
+Layout of the FS directory
+--------------------------
+
+The layout of the FS directory (the "db" subdirectory of the
+repository) is:
+
+ revs/ Subdirectory containing revs
+ <shard>/ Shard directory, if sharding is in use (see below)
+ <revnum> File containing rev <revnum>
+ <shard>.pack/ Pack directory, if the repo has been packed (see below)
+ pack Pack file, if the repository has been packed (see below)
+ manifest Pack manifest file, if a pack file exists (see below)
+ revprops/ Subdirectory containing rev-props
+ <shard>/ Shard directory, if sharding is in use (see below)
+ <revnum> File containing rev-props for <revnum>
+ <shard>.pack/ Pack directory, if the repo has been packed (see below)
+ <rev>.<count> Pack file, if the repository has been packed (see below)
+ manifest Pack manifest file, if a pack file exists (see below)
+ revprops.db SQLite database of the packed revision properties
+ transactions/ Subdirectory containing transactions
+ <txnid>.txn/ Directory containing transaction <txnid>
+ txn-protorevs/ Subdirectory containing transaction proto-revision files
+ <txnid>.rev Proto-revision file for transaction <txnid>
+ <txnid>.rev-lock Write lock for proto-rev file
+ txn-current File containing the next transaction key
+ locks/ Subdirectory containing locks
+ <partial-digest>/ Subdirectory named for first 3 letters of an MD5 digest
+ <digest> File containing locks/children for path with <digest>
+ node-origins/ Lazy cache of origin noderevs for nodes
+ <partial-nodeid> File containing noderev ID of origins of nodes
+ current File specifying current revision and next node/copy id
+ fs-type File identifying this filesystem as an FSFS filesystem
+ write-lock Empty file, locked to serialise writers
+ txn-current-lock Empty file, locked to serialise 'txn-current'
+ uuid File containing the UUID of the repository
+ format File containing the format number of this filesystem
+ fsfs.conf Configuration file
+ min-unpacked-rev File containing the oldest revision not in a pack file
+ min-unpacked-revprop File containing the oldest revision of unpacked revprop
+ rep-cache.db SQLite database mapping rep checksums to locations
+
+Files in the revprops directory are in the hash dump format used by
+svn_hash_write.
+
+The format of the "current" file is:
+
+ * Format 3 and above: a single line of the form
+ "<youngest-revision>\n" giving the youngest revision for the
+ repository.
+
+ * Format 2 and below: a single line of the form "<youngest-revision>
+ <next-node-id> <next-copy-id>\n" giving the youngest revision, the
+ next unique node-ID, and the next unique copy-ID for the
+ repository.
+
+The "write-lock" file is an empty file which is locked before the
+final stage of a commit and unlocked after the new "current" file has
+been moved into place to indicate that a new revision is present. It
+is also locked during a revprop propchange while the revprop file is
+read in, mutated, and written out again. Note that readers are never
+blocked by any operation - writers must ensure that the filesystem is
+always in a consistent state.
+
+The "txn-current" file is a file with a single line of text that
+contains only a base-36 number. The current value will be used in the
+next transaction name, along with the revision number the transaction
+is based on. This sequence number ensures that transaction names are
+not reused, even if the transaction is aborted and a new transaction
+based on the same revision is begun. The only operation that FSFS
+performs on this file is "get and increment"; the "txn-current-lock"
+file is locked during this operation.
+
+"fsfs.conf" is a configuration file in the standard Subversion/Python
+config format. It is automatically generated when you create a new
+repository; read the generated file for details on what it controls.
+
+When representation sharing is enabled, the filesystem tracks
+representation checksum and location mappings using a SQLite database in
+"rep-cache.db". The database has a single table, which stores the sha1
+hash text as the primary key, mapped to the representation revision, offset,
+size and expanded size. This file is only consulted during writes and never
+during reads. Consequently, it is not required, and may be removed at an
+abritrary time, with the subsequent loss of rep-sharing capabilities for
+revisions written thereafter.
+
+Filesystem formats
+------------------
+
+The "format" file defines what features are permitted within the
+filesystem, and indicates changes that are not backward-compatible.
+It serves the same purpose as the repository file of the same name.
+
+The filesystem format file was introduced in Subversion 1.2, and so
+will not be present if the repository was created with an older
+version of Subversion. An absent format file should be interpreted as
+indicating a format 1 filesystem.
+
+The format file is a single line of the form "<format number>\n",
+followed by any number of lines specifying 'format options' -
+additional information about the filesystem's format. Each format
+option line is of the form "<option>\n" or "<option> <parameters>\n".
+
+Clients should raise an error if they encounter an option not
+permitted by the format number in use.
+
+The formats are:
+
+ Format 1, understood by Subversion 1.1+
+ Format 2, understood by Subversion 1.4+
+ Format 3, understood by Subversion 1.5+
+ Format 4, understood by Subversion 1.6+
+ Format 5, understood by Subversion 1.7-dev, never released
+ Format 6, understood by Subversion 1.8
+
+The differences between the formats are:
+
+Delta representation in revision files
+ Format 1: svndiff0 only
+ Formats 2+: svndiff0 or svndiff1
+
+Format options
+ Formats 1-2: none permitted
+ Format 3+: "layout" option
+
+Transaction name reuse
+ Formats 1-2: transaction names may be reused
+ Format 3+: transaction names generated using txn-current file
+
+Location of proto-rev file and its lock
+ Formats 1-2: transactions/<txnid>/rev and
+ transactions/<txnid>/rev-lock.
+ Format 3+: txn-protorevs/<txnid>.rev and
+ txn-protorevs/<txnid>.rev-lock.
+
+Node-ID and copy-ID generation
+ Formats 1-2: Node-IDs and copy-IDs are guaranteed to form a
+ monotonically increasing base36 sequence using the "current"
+ file.
+ Format 3+: Node-IDs and copy-IDs use the new revision number to
+ ensure uniqueness and the "current" file just contains the
+ youngest revision.
+
+Mergeinfo metadata:
+ Format 1-2: minfo-here and minfo-count node-revision fields are not
+ stored. svn_fs_get_mergeinfo returns an error.
+ Format 3+: minfo-here and minfo-count node-revision fields are
+ maintained. svn_fs_get_mergeinfo works.
+
+Revision changed paths list:
+ Format 1-3: Does not contain the node's kind.
+ Format 4+: Contains the node's kind.
+
+Shard packing:
+ Format 4: Applied to revision data only.
+ Format 5: Revprops would be packed independently of revision data.
+ Format 6+: Applied equally to revision data and revprop data
+ (i.e. same min packed revision)
+
+# Incomplete list. See SVN_FS_FS__MIN_*_FORMAT
+
+
+Filesystem format options
+-------------------------
+
+Currently, the only recognised format option is "layout", which
+specifies the paths that will be used to store the revision files and
+revision property files.
+
+The "layout" option is followed by the name of the filesystem layout
+and any required parameters. The default layout, if no "layout"
+keyword is specified, is the 'linear' layout.
+
+The known layouts, and the parameters they require, are as follows:
+
+"linear"
+ Revision files and rev-prop files are named after the revision they
+ represent, and are placed directly in the revs/ and revprops/
+ directories. r1234 will be represented by the revision file
+ revs/1234 and the rev-prop file revprops/1234.
+
+"sharded <max-files-per-directory>"
+ Revision files and rev-prop files are named after the revision they
+ represent, and are placed in a subdirectory of the revs/ and
+ revprops/ directories named according to the 'shard' they belong to.
+
+ Shards are numbered from zero and contain between one and the
+ maximum number of files per directory specified in the layout's
+ parameters.
+
+ For the "sharded 1000" layout, r1234 will be represented by the
+ revision file revs/1/1234 and rev-prop file revprops/1/1234. The
+ revs/0/ directory will contain revisions 0-999, revs/1/ will contain
+ 1000-1999, and so on.
+
+Packing revisions
+-----------------
+
+A filesystem can optionally be "packed" to conserve space on disk. The
+packing process concatenates all the revision files in each full shard to
+create pack files. A manifest file is also created for each shard which
+records the indexes of the corresponding revision files in the pack file.
+In addition, the original shard is removed, and reads are redirected to the
+pack file.
+
+The manifest file consists of a list of offsets, one for each revision in the
+pack file. The offsets are stored as ASCII decimal, and separated by a newline
+character.
+
+Packing revision properties (format 5: SQLite)
+---------------------------
+
+This was supported by 1.7-dev builds but never included in a blessed release.
+
+See r1143829 of this file:
+http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/structure?view=markup&pathrev=1143829
+
+
+Packing revision properties (format 6+)
+---------------------------
+
+Similarly to the revision data, packing will concatenate multiple
+revprops into a single file. Since they are mutable data, we put an
+upper limit to the size of these files: We will concatenate the data
+up to the limit and then use a new file for the following revisions.
+
+The limit can be set and changed at will in the configuration file.
+It is 64kB by default. Because a pack file must contain at least one
+complete property list, files containing just one revision may exceed
+that limit.
+
+Furthermore, pack files can be compressed which saves about 75% of
+disk space. A configuration file flag enables the compression; it is
+off by default and may be switched on and off at will. The pack size
+limit is always applied to the uncompressed data. For this reason,
+the default is 256kB while compression has been enabled.
+
+Files are named after their start revision as "<rev>.<counter>" where
+counter will be increased whenever we rewrite a pack file due to a
+revprop change. The manifest file contains the list of pack file
+names, one line for each revision.
+
+Many tools track repository global data in revision properties at
+revision 0. To minimize I/O overhead for those applications, we
+will never pack that revision, i.e. its data is always being kept
+in revprops/0/0.
+
+Pack file format
+
+ Top level: <packed container>
+
+ We always apply data compression to the pack file - using the
+ SVN_DELTA_COMPRESSION_LEVEL_NONE level if compression is disabled.
+ (Note that compression at SVN_DELTA_COMPRESSION_LEVEL_NONE is not
+ a no-op stream transformation although most of the data will remain
+ human readable.)
+
+ container := header '\n' (revprops)+
+ header := start_rev '\n' rev_count '\n' (size '\n')+
+
+ All numbers in the header are given as ASCII decimals. rev_count
+ is the number of revisions packed into this container. There must
+ be exactly as many "size" and serialized "revprops". The "size"
+ values in the list are the length in bytes of the serialized
+ revprops of the respective revision.
+
+Writing to packed revprops
+
+ The old pack file is being read and the new revprops serialized.
+ If they fit into the same pack file, a temp file with the new
+ content gets written and moved into place just like an non-packed
+ revprop file would. No name change or manifest update required.
+
+ If they don't fit into the same pack file, i.e. exceed the pack
+ size limit, the pack will be split into 2 or 3 new packs just
+ before and / or after the modified revision.
+
+ In the current implementation, they will never be merged again.
+ To minimize fragmentation, the initial packing process will only
+ use about 90% of the limit, i.e. leave some room for growth.
+
+ When a pack file gets split, its counter is being increased
+ creating a new file and leaving the old content in place and
+ available for concurrent readers. Only after the new manifest
+ file got moved into place, will the old pack files be deleted.
+
+ Write access to revprops is being serialized by the global
+ filesystem write lock. We only need to build a few retries into
+ the reader code to gracefully handle manifest changes and pack
+ file deletions.
+
+
+Node-revision IDs
+-----------------
+
+A node-rev ID consists of the following three fields:
+
+ node_revision_id ::= node_id '.' copy_id '.' txn_id
+
+At this level, the form of the ID is the same as for BDB - see the
+section called "ID's" in <../libsvn_fs_base/notes/structure>.
+
+In order to support efficient lookup of node-revisions by their IDs
+and to simplify the allocation of fresh node-IDs during a transaction,
+we treat the fields of a node-rev ID in new and interesting ways.
+
+Within a new transaction:
+
+ New node-revision IDs assigned within a transaction have a txn-id
+ field of the form "t<txnid>".
+
+ When a new node-id or copy-id is assigned in a transaction, the ID
+ used is a "_" followed by a base36 number unique to the transaction.
+
+Within a revision:
+
+ Within a revision file, node-revs have a txn-id field of the form
+ "r<rev>/<offset>", to support easy lookup. The <offset> is the (ASCII
+ decimal) number of bytes from the start of the revision file to the
+ start of the node-rev.
+
+ During the final phase of a commit, node-revision IDs are rewritten
+ to have repository-wide unique node-ID and copy-ID fields, and to have
+ "r<rev>/<offset>" txn-id fields.
+
+ In Format 3 and above, this uniqueness is done by changing a temporary
+ id of "_<base36>" to "<base36>-<rev>". Note that this means that the
+ originating revision of a line of history or a copy can be determined
+ by looking at the node ID.
+
+ In Format 2 and below, the "current" file contains global base36
+ node-ID and copy-ID counters; during the commit, the counter value is
+ added to the transaction-specific base36 ID, and the value in
+ "current" is adjusted.
+
+ (It is legal for Format 3 repositories to contain Format 2-style IDs;
+ this just prevents I/O-less node-origin-rev lookup for those nodes.)
+
+The temporary assignment of node-ID and copy-ID fields has
+implications for svn_fs_compare_ids and svn_fs_check_related. The ID
+_1.0.t1 is not related to the ID _1.0.t2 even though they have the
+same node-ID, because temporary node-IDs are restricted in scope to
+the transactions they belong to.
+
+There is a lazily created cache mapping from node-IDs to the full
+node-revision ID where they are created. This is in the node-origins
+directory; the file name is the node-ID without its last character (or
+"0" for single-character node IDs) and the contents is a serialized
+hash mapping from node-ID to node-revision ID. This cache is only
+used for node-IDs of the pre-Format 3 style.
+
+Copy-IDs and copy roots
+-----------------------
+
+Copy-IDs are assigned in the same manner as they are in the BDB
+implementation:
+
+ * A node-rev resulting from a creation operation (with no copy
+ history) receives the copy-ID of its parent directory.
+
+ * A node-rev resulting from a copy operation receives a fresh
+ copy-ID, as one would expect.
+
+ * A node-rev resulting from a modification operation receives a
+ copy-ID depending on whether its predecessor derives from a
+ copy operation or whether it derives from a creation operation
+ with no intervening copies:
+
+ - If the predecessor does not derive from a copy, the new
+ node-rev receives the copy-ID of its parent directory. If the
+ node-rev is being modified through its created-path, this will
+ be the same copy-ID as the predecessor node-rev has; however,
+ if the node-rev is being modified through a copied ancestor
+ directory (i.e. we are performing a "lazy copy"), this will be
+ a different copy-ID.
+
+ - If the predecessor derives from a copy and the node-rev is
+ being modified through its created-path, the new node-rev
+ receives the copy-ID of the predecessor.
+
+ - If the predecessor derives from a copy and the node-rev is not
+ being modified through its created path, the new node-rev
+ receives a fresh copy-ID. This is called a "soft copy"
+ operation, as distinct from a "true copy" operation which was
+ actually requested through the svn_fs interface. Soft copies
+ exist to ensure that the same <node-ID,copy-ID> pair is not
+ used twice within a transaction.
+
+Unlike the BDB implementation, we do not have a "copies" table.
+Instead, each node-revision record contains a "copyroot" field
+identifying the node-rev resulting from the true copy operation most
+proximal to the node-rev. If the node-rev does not itself derive from
+a copy operation, then the copyroot field identifies the copy of an
+ancestor directory; if no ancestor directories derive from a copy
+operation, then the copyroot field identifies the root directory of
+rev 0.
+
+Revision file format
+--------------------
+
+A revision file contains a concatenation of various kinds of data:
+
+ * Text and property representations
+ * Node-revisions
+ * The changed-path data
+ * Two offsets at the very end
+
+A representation begins with a line containing either "PLAIN\n" or
+"DELTA\n" or "DELTA <rev> <offset> <length>\n", where <rev>, <offset>,
+and <length> give the location of the delta base of the representation
+and the amount of data it contains (not counting the header or
+trailer). If no base location is given for a delta, the base is the
+empty stream. After the initial line comes raw svndiff data, followed
+by a cosmetic trailer "ENDREP\n".
+
+If the representation is for the text contents of a directory node,
+the expanded contents are in hash dump format mapping entry names to
+"<type> <id>" pairs, where <type> is "file" or "dir" and <id> gives
+the ID of the child node-rev.
+
+If a representation is for a property list, the expanded contents are
+in the form of a dumped hash map mapping property names to property
+values.
+
+The marshalling syntax for node-revs is a series of fields terminated
+by a blank line. Fields have the syntax "<name>: <value>\n", where
+<name> is a symbolic field name (each symbolic name is used only once
+in a given node-rev) and <value> is the value data. Unrecognized
+fields are ignored, for extensibility. The following fields are
+defined:
+
+ id The ID of the node-rev
+ type "file" or "dir"
+ pred The ID of the predecessor node-rev
+ count Count of node-revs since the base of the node
+ text "<rev> <offset> <length> <size> <digest>" for text rep
+ props "<rev> <offset> <length> <size> <digest>" for props rep
+ <rev> and <offset> give location of rep
+ <length> gives length of rep, sans header and trailer
+ <size> gives size of expanded rep; may be 0 if equal
+ to the length
+ <digest> gives hex MD5 digest of expanded rep
+ ### in formats >=4, also present:
+ <sha1-digest> gives hex SHA1 digest of expanded rep
+ <uniquifier> see representation_t->uniquifier in fs.h
+ cpath FS pathname node was created at
+ copyfrom "<rev> <path>" of copyfrom data
+ copyroot "<rev> <created-path>" of the root of this copy
+ minfo-cnt The number of nodes under (and including) this node
+ which have svn:mergeinfo.
+ minfo-here Exists if this node itself has svn:mergeinfo.
+
+The predecessor of a node-rev crosses both soft and true copies;
+together with the count field, it allows efficient determination of
+the base for skip-deltas. The first node-rev of a node contains no
+"pred" field. A node-revision with no properties may omit the "props"
+field. A node-revision with no contents (a zero-length file or an
+empty directory) may omit the "text" field. In a node-revision
+resulting from a true copy operation, the "copyfrom" field gives the
+copyfrom data. The "copyroot" field identifies the root node-revision
+of the copy; it may be omitted if the node-rev is its own copy root
+(as is the case for node-revs with copy history, and for the root node
+of revision 0). Copy roots are identified by revision and
+created-path, not by node-rev ID, because a copy root may be a
+node-rev which exists later on within the same revision file, meaning
+its offset is not yet known.
+
+The changed-path data is represented as a series of changed-path
+items, each consisting of two lines. The first line has the format
+"<id> <action> <text-mod> <prop-mod> <path>\n", where <id> is the
+node-rev ID of the new node-rev, <action> is "add", "delete",
+"replace", or "modify", <text-mod> and <prop-mod> are "true" or
+"false" indicating whether the text and/or properties changed, and
+<path> is the changed pathname. For deletes, <id> is the node-rev ID
+of the deleted node-rev, and <text-mod> and <prop-mod> are always
+"false". The second line has the format "<rev> <path>\n" containing
+the node-rev's copyfrom information if it has any; if it does not, the
+second line is blank.
+
+Starting with FS format 4, <action> may contain the kind ("file" or
+"dir") of the node, after a hyphen; for example, an added directory
+may be represented as "add-dir".
+
+At the very end of a rev file is a pair of lines containing
+"\n<root-offset> <cp-offset>\n", where <root-offset> is the offset of
+the root directory node revision and <cp-offset> is the offset of the
+changed-path data.
+
+All numbers in the rev file format are unsigned and are represented as
+ASCII decimal.
+
+Transaction layout
+------------------
+
+A transaction directory has the following layout:
+
+ props Transaction props
+ next-ids Next temporary node-ID and copy-ID
+ changes Changed-path information so far
+ node.<nid>.<cid> New node-rev data for node
+ node.<nid>.<cid>.props Props for new node-rev, if changed
+ node.<nid>.<cid>.children Directory contents for node-rev
+ <sha1> Text representation of that sha1
+
+In FS formats 1 and 2, it also contains:
+
+ rev Prototype rev file with new text reps
+ rev-lock Lockfile for writing to the above
+
+In newer formats, these files are in the txn-protorevs/ directory.
+
+The prototype rev file is used to store the text representations as
+they are received from the client. To ensure that only one client is
+writing to the file at a given time, the "rev-lock" file is locked for
+the duration of each write.
+
+The two kinds of props files are all in hash dump format. The "props"
+file will always be present. The "node.<nid>.<cid>.props" file will
+only be present if the node-rev properties have been changed.
+
+The <sha1> files have been introduced in FS format 6. Their content
+is that of text rep references: "<rev> <offset> <length> <size> <digest>"
+They will be written for text reps in the current transaction and be
+used to eliminate duplicate reps within that transaction.
+
+The "next-ids" file contains a single line "<next-temp-node-id>
+<next-temp-copy-id>\n" giving the next temporary node-ID and copy-ID
+assignments (without the leading underscores). The next node-ID is
+also used as a uniquifier for representations which may share the same
+underlying rep.
+
+The "children" file for a node-revision begins with a copy of the hash
+dump representation of the directory entries from the old node-rev (or
+a dump of the empty hash for new directories), and then an incremental
+hash dump entry for each change made to the directory.
+
+The "changes" file contains changed-path entries in the same form as
+the changed-path entries in a rev file, except that <id> and <action>
+may both be "reset" (in which case <text-mod> and <prop-mod> are both
+always "false") to indicate that all changes to a path should be
+considered undone. Reset entries are only used during the final merge
+phase of a transaction. Actions in the "changes" file always contain
+a node kind, even if the FS format is older than format 4.
+
+The node-rev files have the same format as node-revs in a revision
+file, except that the "text" and "props" fields are augmented as
+follows:
+
+ * The "props" field may have the value "-1" if properties have
+ been changed and are contained in a "props" file within the
+ node-rev subdirectory.
+
+ * For directory node-revs, the "text" field may have the value
+ "-1" if entries have been changed and are contained in a
+ "contents" file in the node-rev subdirectory.
+
+ * For the directory node-rev representing the root of the
+ transaction, the "is-fresh-txn-root" field indicates that it has
+ not been made mutable yet (see Issue #2608).
+
+ * For file node-revs, the "text" field may have the value "-1
+ <offset> <length> <size> <digest>" if the text representation is
+ within the prototype rev file.
+
+ * The "copyroot" field may have the value "-1 <created-path>" if the
+ copy root of the node-rev is part of the transaction in process.
+
+Locks layout
+------------
+
+Locks in FSFS are stored in serialized hash format in files whose
+names are MD5 digests of the FS path which the lock is associated
+with. For the purposes of keeping directory inode usage down, these
+digest files live in subdirectories of the main lock directory whose
+names are the first 3 characters of the digest filename.
+
+Also stored in the digest file for a given FS path are pointers to
+other digest files which contain information associated with other FS
+paths that are beneath our path (an immediate child thereof, or a
+grandchild, or a great-grandchild, ...).
+
+To answer the question, "Does path FOO have a lock associated with
+it?", one need only generate the MD5 digest of FOO's
+absolute-in-the-FS path (say, 3b1b011fed614a263986b5c4869604e8), look
+for a file located like so:
+
+ /path/to/repos/locks/3b1/3b1b011fed614a263986b5c4869604e8
+
+And then see if that file contains lock information.
+
+To inquire about locks on children of the path FOO, you would
+reference the same path as above, but look for a list of children in
+that file (instead of lock information). Children are listed as MD5
+digests, too, so you would simply iterate over those digests and
+consult the files they reference for lock information.
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/temp_serializer.c b/contrib/subversion/subversion/libsvn_fs_fs/temp_serializer.c
new file mode 100644
index 0000000..0178143
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/temp_serializer.c
@@ -0,0 +1,1341 @@
+/* temp_serializer.c: serialization functions for caching of FSFS structures
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_pools.h"
+#include "svn_hash.h"
+
+#include "id.h"
+#include "svn_fs.h"
+
+#include "private/svn_fs_util.h"
+#include "private/svn_temp_serializer.h"
+#include "private/svn_subr_private.h"
+
+#include "temp_serializer.h"
+
+/* Utility to encode a signed NUMBER into a variable-length sequence of
+ * 8-bit chars in KEY_BUFFER and return the last writen position.
+ *
+ * Numbers will be stored in 7 bits / byte and using byte values above
+ * 32 (' ') to make them combinable with other string by simply separating
+ * individual parts with spaces.
+ */
+static char*
+encode_number(apr_int64_t number, char *key_buffer)
+{
+ /* encode the sign in the first byte */
+ if (number < 0)
+ {
+ number = -number;
+ *key_buffer = (char)((number & 63) + ' ' + 65);
+ }
+ else
+ *key_buffer = (char)((number & 63) + ' ' + 1);
+ number /= 64;
+
+ /* write 7 bits / byte until no significant bits are left */
+ while (number)
+ {
+ *++key_buffer = (char)((number & 127) + ' ' + 1);
+ number /= 128;
+ }
+
+ /* return the last written position */
+ return key_buffer;
+}
+
+const char*
+svn_fs_fs__combine_number_and_string(apr_int64_t number,
+ const char *string,
+ apr_pool_t *pool)
+{
+ apr_size_t len = strlen(string);
+
+ /* number part requires max. 10x7 bits + 1 space.
+ * Add another 1 for the terminal 0 */
+ char *key_buffer = apr_palloc(pool, len + 12);
+ const char *key = key_buffer;
+
+ /* Prepend the number to the string and separate them by space. No other
+ * number can result in the same prefix, no other string in the same
+ * postfix nor can the boundary between them be ambiguous. */
+ key_buffer = encode_number(number, key_buffer);
+ *++key_buffer = ' ';
+ memcpy(++key_buffer, string, len+1);
+
+ /* return the start of the key */
+ return key;
+}
+
+/* Utility function to serialize string S in the given serialization CONTEXT.
+ */
+static void
+serialize_svn_string(svn_temp_serializer__context_t *context,
+ const svn_string_t * const *s)
+{
+ const svn_string_t *string = *s;
+
+ /* Nothing to do for NULL string references. */
+ if (string == NULL)
+ return;
+
+ svn_temp_serializer__push(context,
+ (const void * const *)s,
+ sizeof(*string));
+
+ /* the "string" content may actually be arbitrary binary data.
+ * Thus, we cannot use svn_temp_serializer__add_string. */
+ svn_temp_serializer__push(context,
+ (const void * const *)&string->data,
+ string->len + 1);
+
+ /* back to the caller's nesting level */
+ svn_temp_serializer__pop(context);
+ svn_temp_serializer__pop(context);
+}
+
+/* Utility function to deserialize the STRING inside the BUFFER.
+ */
+static void
+deserialize_svn_string(void *buffer, svn_string_t **string)
+{
+ svn_temp_deserializer__resolve(buffer, (void **)string);
+ if (*string == NULL)
+ return;
+
+ svn_temp_deserializer__resolve(*string, (void **)&(*string)->data);
+}
+
+/* Utility function to serialize checkum CS within the given serialization
+ * CONTEXT.
+ */
+static void
+serialize_checksum(svn_temp_serializer__context_t *context,
+ svn_checksum_t * const *cs)
+{
+ const svn_checksum_t *checksum = *cs;
+ if (checksum == NULL)
+ return;
+
+ svn_temp_serializer__push(context,
+ (const void * const *)cs,
+ sizeof(*checksum));
+
+ /* The digest is arbitrary binary data.
+ * Thus, we cannot use svn_temp_serializer__add_string. */
+ svn_temp_serializer__push(context,
+ (const void * const *)&checksum->digest,
+ svn_checksum_size(checksum));
+
+ /* return to the caller's nesting level */
+ svn_temp_serializer__pop(context);
+ svn_temp_serializer__pop(context);
+}
+
+/* Utility function to deserialize the checksum CS inside the BUFFER.
+ */
+static void
+deserialize_checksum(void *buffer, svn_checksum_t **cs)
+{
+ svn_temp_deserializer__resolve(buffer, (void **)cs);
+ if (*cs == NULL)
+ return;
+
+ svn_temp_deserializer__resolve(*cs, (void **)&(*cs)->digest);
+}
+
+/* Utility function to serialize the REPRESENTATION within the given
+ * serialization CONTEXT.
+ */
+static void
+serialize_representation(svn_temp_serializer__context_t *context,
+ representation_t * const *representation)
+{
+ const representation_t * rep = *representation;
+ if (rep == NULL)
+ return;
+
+ /* serialize the representation struct itself */
+ svn_temp_serializer__push(context,
+ (const void * const *)representation,
+ sizeof(*rep));
+
+ /* serialize sub-structures */
+ serialize_checksum(context, &rep->md5_checksum);
+ serialize_checksum(context, &rep->sha1_checksum);
+
+ svn_temp_serializer__add_string(context, &rep->txn_id);
+ svn_temp_serializer__add_string(context, &rep->uniquifier);
+
+ /* return to the caller's nesting level */
+ svn_temp_serializer__pop(context);
+}
+
+/* Utility function to deserialize the REPRESENTATIONS inside the BUFFER.
+ */
+static void
+deserialize_representation(void *buffer,
+ representation_t **representation)
+{
+ representation_t *rep;
+
+ /* fixup the reference to the representation itself */
+ svn_temp_deserializer__resolve(buffer, (void **)representation);
+ rep = *representation;
+ if (rep == NULL)
+ return;
+
+ /* fixup of sub-structures */
+ deserialize_checksum(rep, &rep->md5_checksum);
+ deserialize_checksum(rep, &rep->sha1_checksum);
+
+ svn_temp_deserializer__resolve(rep, (void **)&rep->txn_id);
+ svn_temp_deserializer__resolve(rep, (void **)&rep->uniquifier);
+}
+
+/* auxilliary structure representing the content of a directory hash */
+typedef struct hash_data_t
+{
+ /* number of entries in the directory */
+ apr_size_t count;
+
+ /* number of unused dir entry buckets in the index */
+ apr_size_t over_provision;
+
+ /* internal modifying operations counter
+ * (used to repack data once in a while) */
+ apr_size_t operations;
+
+ /* size of the serialization buffer actually used.
+ * (we will allocate more than we actually need such that we may
+ * append more data in situ later) */
+ apr_size_t len;
+
+ /* reference to the entries */
+ svn_fs_dirent_t **entries;
+
+ /* size of the serialized entries and don't be too wasteful
+ * (needed since the entries are no longer in sequence) */
+ apr_uint32_t *lengths;
+} hash_data_t;
+
+static int
+compare_dirent_id_names(const void *lhs, const void *rhs)
+{
+ return strcmp((*(const svn_fs_dirent_t *const *)lhs)->name,
+ (*(const svn_fs_dirent_t *const *)rhs)->name);
+}
+
+/* Utility function to serialize the *ENTRY_P into a the given
+ * serialization CONTEXT. Return the serialized size of the
+ * dir entry in *LENGTH.
+ */
+static void
+serialize_dir_entry(svn_temp_serializer__context_t *context,
+ svn_fs_dirent_t **entry_p,
+ apr_uint32_t *length)
+{
+ svn_fs_dirent_t *entry = *entry_p;
+ apr_size_t initial_length = svn_temp_serializer__get_length(context);
+
+ svn_temp_serializer__push(context,
+ (const void * const *)entry_p,
+ sizeof(svn_fs_dirent_t));
+
+ svn_fs_fs__id_serialize(context, &entry->id);
+ svn_temp_serializer__add_string(context, &entry->name);
+
+ *length = (apr_uint32_t)( svn_temp_serializer__get_length(context)
+ - APR_ALIGN_DEFAULT(initial_length));
+
+ svn_temp_serializer__pop(context);
+}
+
+/* Utility function to serialize the ENTRIES into a new serialization
+ * context to be returned. Allocation will be made form POOL.
+ */
+static svn_temp_serializer__context_t *
+serialize_dir(apr_hash_t *entries, apr_pool_t *pool)
+{
+ hash_data_t hash_data;
+ apr_hash_index_t *hi;
+ apr_size_t i = 0;
+ svn_temp_serializer__context_t *context;
+
+ /* calculate sizes */
+ apr_size_t count = apr_hash_count(entries);
+ apr_size_t over_provision = 2 + count / 4;
+ apr_size_t entries_len = (count + over_provision) * sizeof(svn_fs_dirent_t*);
+ apr_size_t lengths_len = (count + over_provision) * sizeof(apr_uint32_t);
+
+ /* copy the hash entries to an auxilliary struct of known layout */
+ hash_data.count = count;
+ hash_data.over_provision = over_provision;
+ hash_data.operations = 0;
+ hash_data.entries = apr_palloc(pool, entries_len);
+ hash_data.lengths = apr_palloc(pool, lengths_len);
+
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi), ++i)
+ hash_data.entries[i] = svn__apr_hash_index_val(hi);
+
+ /* sort entry index by ID name */
+ qsort(hash_data.entries,
+ count,
+ sizeof(*hash_data.entries),
+ compare_dirent_id_names);
+
+ /* Serialize that aux. structure into a new one. Also, provide a good
+ * estimate for the size of the buffer that we will need. */
+ context = svn_temp_serializer__init(&hash_data,
+ sizeof(hash_data),
+ 50 + count * 200 + entries_len,
+ pool);
+
+ /* serialize entries references */
+ svn_temp_serializer__push(context,
+ (const void * const *)&hash_data.entries,
+ entries_len);
+
+ /* serialize the individual entries and their sub-structures */
+ for (i = 0; i < count; ++i)
+ serialize_dir_entry(context,
+ &hash_data.entries[i],
+ &hash_data.lengths[i]);
+
+ svn_temp_serializer__pop(context);
+
+ /* serialize entries references */
+ svn_temp_serializer__push(context,
+ (const void * const *)&hash_data.lengths,
+ lengths_len);
+
+ return context;
+}
+
+/* Utility function to reconstruct a dir entries hash from serialized data
+ * in BUFFER and HASH_DATA. Allocation will be made form POOL.
+ */
+static apr_hash_t *
+deserialize_dir(void *buffer, hash_data_t *hash_data, apr_pool_t *pool)
+{
+ apr_hash_t *result = svn_hash__make(pool);
+ apr_size_t i;
+ apr_size_t count;
+ svn_fs_dirent_t *entry;
+ svn_fs_dirent_t **entries;
+
+ /* resolve the reference to the entries array */
+ svn_temp_deserializer__resolve(buffer, (void **)&hash_data->entries);
+ entries = hash_data->entries;
+
+ /* fixup the references within each entry and add it to the hash */
+ for (i = 0, count = hash_data->count; i < count; ++i)
+ {
+ svn_temp_deserializer__resolve(entries, (void **)&entries[i]);
+ entry = hash_data->entries[i];
+
+ /* pointer fixup */
+ svn_temp_deserializer__resolve(entry, (void **)&entry->name);
+ svn_fs_fs__id_deserialize(entry, (svn_fs_id_t **)&entry->id);
+
+ /* add the entry to the hash */
+ svn_hash_sets(result, entry->name, entry);
+ }
+
+ /* return the now complete hash */
+ return result;
+}
+
+void
+svn_fs_fs__noderev_serialize(svn_temp_serializer__context_t *context,
+ node_revision_t * const *noderev_p)
+{
+ const node_revision_t *noderev = *noderev_p;
+ if (noderev == NULL)
+ return;
+
+ /* serialize the representation struct itself */
+ svn_temp_serializer__push(context,
+ (const void * const *)noderev_p,
+ sizeof(*noderev));
+
+ /* serialize sub-structures */
+ svn_fs_fs__id_serialize(context, &noderev->id);
+ svn_fs_fs__id_serialize(context, &noderev->predecessor_id);
+ serialize_representation(context, &noderev->prop_rep);
+ serialize_representation(context, &noderev->data_rep);
+
+ svn_temp_serializer__add_string(context, &noderev->copyfrom_path);
+ svn_temp_serializer__add_string(context, &noderev->copyroot_path);
+ svn_temp_serializer__add_string(context, &noderev->created_path);
+
+ /* return to the caller's nesting level */
+ svn_temp_serializer__pop(context);
+}
+
+
+void
+svn_fs_fs__noderev_deserialize(void *buffer,
+ node_revision_t **noderev_p)
+{
+ node_revision_t *noderev;
+
+ /* fixup the reference to the representation itself,
+ * if this is part of a parent structure. */
+ if (buffer != *noderev_p)
+ svn_temp_deserializer__resolve(buffer, (void **)noderev_p);
+
+ noderev = *noderev_p;
+ if (noderev == NULL)
+ return;
+
+ /* fixup of sub-structures */
+ svn_fs_fs__id_deserialize(noderev, (svn_fs_id_t **)&noderev->id);
+ svn_fs_fs__id_deserialize(noderev, (svn_fs_id_t **)&noderev->predecessor_id);
+ deserialize_representation(noderev, &noderev->prop_rep);
+ deserialize_representation(noderev, &noderev->data_rep);
+
+ svn_temp_deserializer__resolve(noderev, (void **)&noderev->copyfrom_path);
+ svn_temp_deserializer__resolve(noderev, (void **)&noderev->copyroot_path);
+ svn_temp_deserializer__resolve(noderev, (void **)&noderev->created_path);
+}
+
+
+/* Utility function to serialize COUNT svn_txdelta_op_t objects
+ * at OPS in the given serialization CONTEXT.
+ */
+static void
+serialize_txdelta_ops(svn_temp_serializer__context_t *context,
+ const svn_txdelta_op_t * const * ops,
+ apr_size_t count)
+{
+ if (*ops == NULL)
+ return;
+
+ /* the ops form a contiguous chunk of memory with no further references */
+ svn_temp_serializer__push(context,
+ (const void * const *)ops,
+ count * sizeof(svn_txdelta_op_t));
+ svn_temp_serializer__pop(context);
+}
+
+/* Utility function to serialize W in the given serialization CONTEXT.
+ */
+static void
+serialize_txdeltawindow(svn_temp_serializer__context_t *context,
+ svn_txdelta_window_t * const * w)
+{
+ svn_txdelta_window_t *window = *w;
+
+ /* serialize the window struct itself */
+ svn_temp_serializer__push(context,
+ (const void * const *)w,
+ sizeof(svn_txdelta_window_t));
+
+ /* serialize its sub-structures */
+ serialize_txdelta_ops(context, &window->ops, window->num_ops);
+ serialize_svn_string(context, &window->new_data);
+
+ svn_temp_serializer__pop(context);
+}
+
+svn_error_t *
+svn_fs_fs__serialize_txdelta_window(void **buffer,
+ apr_size_t *buffer_size,
+ void *item,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__txdelta_cached_window_t *window_info = item;
+ svn_stringbuf_t *serialized;
+
+ /* initialize the serialization process and allocate a buffer large
+ * enough to do without the need of re-allocations in most cases. */
+ apr_size_t text_len = window_info->window->new_data
+ ? window_info->window->new_data->len
+ : 0;
+ svn_temp_serializer__context_t *context =
+ svn_temp_serializer__init(window_info,
+ sizeof(*window_info),
+ 500 + text_len,
+ pool);
+
+ /* serialize the sub-structure(s) */
+ serialize_txdeltawindow(context, &window_info->window);
+
+ /* return the serialized result */
+ serialized = svn_temp_serializer__get(context);
+
+ *buffer = serialized->data;
+ *buffer_size = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_txdelta_window(void **item,
+ void *buffer,
+ apr_size_t buffer_size,
+ apr_pool_t *pool)
+{
+ svn_txdelta_window_t *window;
+
+ /* Copy the _full_ buffer as it also contains the sub-structures. */
+ svn_fs_fs__txdelta_cached_window_t *window_info =
+ (svn_fs_fs__txdelta_cached_window_t *)buffer;
+
+ /* pointer reference fixup */
+ svn_temp_deserializer__resolve(window_info,
+ (void **)&window_info->window);
+ window = window_info->window;
+
+ svn_temp_deserializer__resolve(window, (void **)&window->ops);
+
+ deserialize_svn_string(window, (svn_string_t**)&window->new_data);
+
+ /* done */
+ *item = window_info;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__serialize_manifest(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *manifest = in;
+
+ *data_len = sizeof(apr_off_t) *manifest->nelts;
+ *data = apr_palloc(pool, *data_len);
+ memcpy(*data, manifest->elts, *data_len);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_manifest(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *manifest = apr_array_make(pool, 1, sizeof(apr_off_t));
+
+ manifest->nelts = (int) (data_len / sizeof(apr_off_t));
+ manifest->nalloc = (int) (data_len / sizeof(apr_off_t));
+ manifest->elts = (char*)data;
+
+ *out = manifest;
+
+ return SVN_NO_ERROR;
+}
+
+/* Auxilliary structure representing the content of a properties hash.
+ This structure is much easier to (de-)serialize than an apr_hash.
+ */
+typedef struct properties_data_t
+{
+ /* number of entries in the hash */
+ apr_size_t count;
+
+ /* reference to the keys */
+ const char **keys;
+
+ /* reference to the values */
+ const svn_string_t **values;
+} properties_data_t;
+
+/* Serialize COUNT C-style strings from *STRINGS into CONTEXT. */
+static void
+serialize_cstring_array(svn_temp_serializer__context_t *context,
+ const char ***strings,
+ apr_size_t count)
+{
+ apr_size_t i;
+ const char **entries = *strings;
+
+ /* serialize COUNT entries pointers (the array) */
+ svn_temp_serializer__push(context,
+ (const void * const *)strings,
+ count * sizeof(const char*));
+
+ /* serialize array elements */
+ for (i = 0; i < count; ++i)
+ svn_temp_serializer__add_string(context, &entries[i]);
+
+ svn_temp_serializer__pop(context);
+}
+
+/* Serialize COUNT svn_string_t* items from *STRINGS into CONTEXT. */
+static void
+serialize_svn_string_array(svn_temp_serializer__context_t *context,
+ const svn_string_t ***strings,
+ apr_size_t count)
+{
+ apr_size_t i;
+ const svn_string_t **entries = *strings;
+
+ /* serialize COUNT entries pointers (the array) */
+ svn_temp_serializer__push(context,
+ (const void * const *)strings,
+ count * sizeof(const char*));
+
+ /* serialize array elements */
+ for (i = 0; i < count; ++i)
+ serialize_svn_string(context, &entries[i]);
+
+ svn_temp_serializer__pop(context);
+}
+
+svn_error_t *
+svn_fs_fs__serialize_properties(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ apr_hash_t *hash = in;
+ properties_data_t properties;
+ svn_temp_serializer__context_t *context;
+ apr_hash_index_t *hi;
+ svn_stringbuf_t *serialized;
+ apr_size_t i;
+
+ /* create our auxilliary data structure */
+ properties.count = apr_hash_count(hash);
+ properties.keys = apr_palloc(pool, sizeof(const char*) * (properties.count + 1));
+ properties.values = apr_palloc(pool, sizeof(const char*) * properties.count);
+
+ /* populate it with the hash entries */
+ for (hi = apr_hash_first(pool, hash), i=0; hi; hi = apr_hash_next(hi), ++i)
+ {
+ properties.keys[i] = svn__apr_hash_index_key(hi);
+ properties.values[i] = svn__apr_hash_index_val(hi);
+ }
+
+ /* serialize it */
+ context = svn_temp_serializer__init(&properties,
+ sizeof(properties),
+ properties.count * 100,
+ pool);
+
+ properties.keys[i] = "";
+ serialize_cstring_array(context, &properties.keys, properties.count + 1);
+ serialize_svn_string_array(context, &properties.values, properties.count);
+
+ /* return the serialized result */
+ serialized = svn_temp_serializer__get(context);
+
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_properties(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ apr_hash_t *hash = svn_hash__make(pool);
+ properties_data_t *properties = (properties_data_t *)data;
+ size_t i;
+
+ /* de-serialize our auxilliary data structure */
+ svn_temp_deserializer__resolve(properties, (void**)&properties->keys);
+ svn_temp_deserializer__resolve(properties, (void**)&properties->values);
+
+ /* de-serialize each entry and put it into the hash */
+ for (i = 0; i < properties->count; ++i)
+ {
+ apr_size_t len = properties->keys[i+1] - properties->keys[i] - 1;
+ svn_temp_deserializer__resolve((void*)properties->keys,
+ (void**)&properties->keys[i]);
+
+ deserialize_svn_string((void*)properties->values,
+ (svn_string_t **)&properties->values[i]);
+
+ apr_hash_set(hash,
+ properties->keys[i], len,
+ properties->values[i]);
+ }
+
+ /* done */
+ *out = hash;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__serialize_id(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *id = in;
+ svn_stringbuf_t *serialized;
+
+ /* create an (empty) serialization context with plenty of buffer space */
+ svn_temp_serializer__context_t *context =
+ svn_temp_serializer__init(NULL, 0, 250, pool);
+
+ /* serialize the id */
+ svn_fs_fs__id_serialize(context, &id);
+
+ /* return serialized data */
+ serialized = svn_temp_serializer__get(context);
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_id(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ /* Copy the _full_ buffer as it also contains the sub-structures. */
+ svn_fs_id_t *id = (svn_fs_id_t *)data;
+
+ /* fixup of all pointers etc. */
+ svn_fs_fs__id_deserialize(id, &id);
+
+ /* done */
+ *out = id;
+ return SVN_NO_ERROR;
+}
+
+/** Caching node_revision_t objects. **/
+
+svn_error_t *
+svn_fs_fs__serialize_node_revision(void **buffer,
+ apr_size_t *buffer_size,
+ void *item,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *serialized;
+ node_revision_t *noderev = item;
+
+ /* create an (empty) serialization context with plenty of (initial)
+ * buffer space. */
+ svn_temp_serializer__context_t *context =
+ svn_temp_serializer__init(NULL, 0,
+ 1024 - SVN_TEMP_SERIALIZER__OVERHEAD,
+ pool);
+
+ /* serialize the noderev */
+ svn_fs_fs__noderev_serialize(context, &noderev);
+
+ /* return serialized data */
+ serialized = svn_temp_serializer__get(context);
+ *buffer = serialized->data;
+ *buffer_size = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_node_revision(void **item,
+ void *buffer,
+ apr_size_t buffer_size,
+ apr_pool_t *pool)
+{
+ /* Copy the _full_ buffer as it also contains the sub-structures. */
+ node_revision_t *noderev = (node_revision_t *)buffer;
+
+ /* fixup of all pointers etc. */
+ svn_fs_fs__noderev_deserialize(noderev, &noderev);
+
+ /* done */
+ *item = noderev;
+ return SVN_NO_ERROR;
+}
+
+/* Utility function that returns the directory serialized inside CONTEXT
+ * to DATA and DATA_LEN. */
+static svn_error_t *
+return_serialized_dir_context(svn_temp_serializer__context_t *context,
+ void **data,
+ apr_size_t *data_len)
+{
+ svn_stringbuf_t *serialized = svn_temp_serializer__get(context);
+
+ *data = serialized->data;
+ *data_len = serialized->blocksize;
+ ((hash_data_t *)serialized->data)->len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__serialize_dir_entries(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ apr_hash_t *dir = in;
+
+ /* serialize the dir content into a new serialization context
+ * and return the serialized data */
+ return return_serialized_dir_context(serialize_dir(dir, pool),
+ data,
+ data_len);
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_dir_entries(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ /* Copy the _full_ buffer as it also contains the sub-structures. */
+ hash_data_t *hash_data = (hash_data_t *)data;
+
+ /* reconstruct the hash from the serialized data */
+ *out = deserialize_dir(hash_data, hash_data, pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__get_sharded_offset(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const apr_off_t *manifest = data;
+ apr_int64_t shard_pos = *(apr_int64_t *)baton;
+
+ *(apr_off_t *)out = manifest[shard_pos];
+
+ return SVN_NO_ERROR;
+}
+
+/* Utility function that returns the lowest index of the first entry in
+ * *ENTRIES that points to a dir entry with a name equal or larger than NAME.
+ * If an exact match has been found, *FOUND will be set to TRUE. COUNT is
+ * the number of valid entries in ENTRIES.
+ */
+static apr_size_t
+find_entry(svn_fs_dirent_t **entries,
+ const char *name,
+ apr_size_t count,
+ svn_boolean_t *found)
+{
+ /* binary search for the desired entry by name */
+ apr_size_t lower = 0;
+ apr_size_t upper = count;
+ apr_size_t middle;
+
+ for (middle = upper / 2; lower < upper; middle = (upper + lower) / 2)
+ {
+ const svn_fs_dirent_t *entry =
+ svn_temp_deserializer__ptr(entries, (const void *const *)&entries[middle]);
+ const char* entry_name =
+ svn_temp_deserializer__ptr(entry, (const void *const *)&entry->name);
+
+ int diff = strcmp(entry_name, name);
+ if (diff < 0)
+ lower = middle + 1;
+ else
+ upper = middle;
+ }
+
+ /* check whether we actually found a match */
+ *found = FALSE;
+ if (lower < count)
+ {
+ const svn_fs_dirent_t *entry =
+ svn_temp_deserializer__ptr(entries, (const void *const *)&entries[lower]);
+ const char* entry_name =
+ svn_temp_deserializer__ptr(entry, (const void *const *)&entry->name);
+
+ if (strcmp(entry_name, name) == 0)
+ *found = TRUE;
+ }
+
+ return lower;
+}
+
+svn_error_t *
+svn_fs_fs__extract_dir_entry(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const hash_data_t *hash_data = data;
+ const char* name = baton;
+ svn_boolean_t found;
+
+ /* resolve the reference to the entries array */
+ const svn_fs_dirent_t * const *entries =
+ svn_temp_deserializer__ptr(data, (const void *const *)&hash_data->entries);
+
+ /* resolve the reference to the lengths array */
+ const apr_uint32_t *lengths =
+ svn_temp_deserializer__ptr(data, (const void *const *)&hash_data->lengths);
+
+ /* binary search for the desired entry by name */
+ apr_size_t pos = find_entry((svn_fs_dirent_t **)entries,
+ name,
+ hash_data->count,
+ &found);
+
+ /* de-serialize that entry or return NULL, if no match has been found */
+ *out = NULL;
+ if (found)
+ {
+ const svn_fs_dirent_t *source =
+ svn_temp_deserializer__ptr(entries, (const void *const *)&entries[pos]);
+
+ /* Entries have been serialized one-by-one, each time including all
+ * nested structures and strings. Therefore, they occupy a single
+ * block of memory whose end-offset is either the beginning of the
+ * next entry or the end of the buffer
+ */
+ apr_size_t size = lengths[pos];
+
+ /* copy & deserialize the entry */
+ svn_fs_dirent_t *new_entry = apr_palloc(pool, size);
+ memcpy(new_entry, source, size);
+
+ svn_temp_deserializer__resolve(new_entry, (void **)&new_entry->name);
+ svn_fs_fs__id_deserialize(new_entry, (svn_fs_id_t **)&new_entry->id);
+ *(svn_fs_dirent_t **)out = new_entry;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Utility function for svn_fs_fs__replace_dir_entry that implements the
+ * modification as a simply deserialize / modify / serialize sequence.
+ */
+static svn_error_t *
+slowly_replace_dir_entry(void **data,
+ apr_size_t *data_len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ replace_baton_t *replace_baton = (replace_baton_t *)baton;
+ hash_data_t *hash_data = (hash_data_t *)*data;
+ apr_hash_t *dir;
+
+ SVN_ERR(svn_fs_fs__deserialize_dir_entries((void **)&dir,
+ *data,
+ hash_data->len,
+ pool));
+ svn_hash_sets(dir, replace_baton->name, replace_baton->new_entry);
+
+ return svn_fs_fs__serialize_dir_entries(data, data_len, dir, pool);
+}
+
+svn_error_t *
+svn_fs_fs__replace_dir_entry(void **data,
+ apr_size_t *data_len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ replace_baton_t *replace_baton = (replace_baton_t *)baton;
+ hash_data_t *hash_data = (hash_data_t *)*data;
+ svn_boolean_t found;
+ svn_fs_dirent_t **entries;
+ apr_uint32_t *lengths;
+ apr_uint32_t length;
+ apr_size_t pos;
+
+ svn_temp_serializer__context_t *context;
+
+ /* after quite a number of operations, let's re-pack everything.
+ * This is to limit the number of vasted space as we cannot overwrite
+ * existing data but must always append. */
+ if (hash_data->operations > 2 + hash_data->count / 4)
+ return slowly_replace_dir_entry(data, data_len, baton, pool);
+
+ /* resolve the reference to the entries array */
+ entries = (svn_fs_dirent_t **)
+ svn_temp_deserializer__ptr((const char *)hash_data,
+ (const void *const *)&hash_data->entries);
+
+ /* resolve the reference to the lengths array */
+ lengths = (apr_uint32_t *)
+ svn_temp_deserializer__ptr((const char *)hash_data,
+ (const void *const *)&hash_data->lengths);
+
+ /* binary search for the desired entry by name */
+ pos = find_entry(entries, replace_baton->name, hash_data->count, &found);
+
+ /* handle entry removal (if found at all) */
+ if (replace_baton->new_entry == NULL)
+ {
+ if (found)
+ {
+ /* remove reference to the entry from the index */
+ memmove(&entries[pos],
+ &entries[pos + 1],
+ sizeof(entries[pos]) * (hash_data->count - pos));
+ memmove(&lengths[pos],
+ &lengths[pos + 1],
+ sizeof(lengths[pos]) * (hash_data->count - pos));
+
+ hash_data->count--;
+ hash_data->over_provision++;
+ hash_data->operations++;
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* if not found, prepare to insert the new entry */
+ if (!found)
+ {
+ /* fallback to slow operation if there is no place left to insert an
+ * new entry to index. That will automatically give add some spare
+ * entries ("overprovision"). */
+ if (hash_data->over_provision == 0)
+ return slowly_replace_dir_entry(data, data_len, baton, pool);
+
+ /* make entries[index] available for pointing to the new entry */
+ memmove(&entries[pos + 1],
+ &entries[pos],
+ sizeof(entries[pos]) * (hash_data->count - pos));
+ memmove(&lengths[pos + 1],
+ &lengths[pos],
+ sizeof(lengths[pos]) * (hash_data->count - pos));
+
+ hash_data->count++;
+ hash_data->over_provision--;
+ hash_data->operations++;
+ }
+
+ /* de-serialize the new entry */
+ entries[pos] = replace_baton->new_entry;
+ context = svn_temp_serializer__init_append(hash_data,
+ entries,
+ hash_data->len,
+ *data_len,
+ pool);
+ serialize_dir_entry(context, &entries[pos], &length);
+
+ /* return the updated serialized data */
+ SVN_ERR (return_serialized_dir_context(context,
+ data,
+ data_len));
+
+ /* since the previous call may have re-allocated the buffer, the lengths
+ * pointer may no longer point to the entry in that buffer. Therefore,
+ * re-map it again and store the length value after that. */
+
+ hash_data = (hash_data_t *)*data;
+ lengths = (apr_uint32_t *)
+ svn_temp_deserializer__ptr((const char *)hash_data,
+ (const void *const *)&hash_data->lengths);
+ lengths[pos] = length;
+
+ return SVN_NO_ERROR;
+}
+
+/* Utility function to serialize change CHANGE_P in the given serialization
+ * CONTEXT.
+ */
+static void
+serialize_change(svn_temp_serializer__context_t *context,
+ change_t * const *change_p)
+{
+ const change_t * change = *change_p;
+ if (change == NULL)
+ return;
+
+ /* serialize the change struct itself */
+ svn_temp_serializer__push(context,
+ (const void * const *)change_p,
+ sizeof(*change));
+
+ /* serialize sub-structures */
+ svn_fs_fs__id_serialize(context, &change->noderev_id);
+
+ svn_temp_serializer__add_string(context, &change->path);
+ svn_temp_serializer__add_string(context, &change->copyfrom_path);
+
+ /* return to the caller's nesting level */
+ svn_temp_serializer__pop(context);
+}
+
+/* Utility function to serialize the CHANGE_P within the given
+ * serialization CONTEXT.
+ */
+static void
+deserialize_change(void *buffer, change_t **change_p)
+{
+ change_t * change;
+
+ /* fix-up of the pointer to the struct in question */
+ svn_temp_deserializer__resolve(buffer, (void **)change_p);
+
+ change = *change_p;
+ if (change == NULL)
+ return;
+
+ /* fix-up of sub-structures */
+ svn_fs_fs__id_deserialize(change, (svn_fs_id_t **)&change->noderev_id);
+
+ svn_temp_deserializer__resolve(change, (void **)&change->path);
+ svn_temp_deserializer__resolve(change, (void **)&change->copyfrom_path);
+}
+
+/* Auxiliary structure representing the content of a change_t array.
+ This structure is much easier to (de-)serialize than an APR array.
+ */
+typedef struct changes_data_t
+{
+ /* number of entries in the array */
+ int count;
+
+ /* reference to the changes */
+ change_t **changes;
+} changes_data_t;
+
+svn_error_t *
+svn_fs_fs__serialize_changes(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *array = in;
+ changes_data_t changes;
+ svn_temp_serializer__context_t *context;
+ svn_stringbuf_t *serialized;
+ int i;
+
+ /* initialize our auxiliary data structure */
+ changes.count = array->nelts;
+ changes.changes = apr_palloc(pool, sizeof(change_t*) * changes.count);
+
+ /* populate it with the array elements */
+ for (i = 0; i < changes.count; ++i)
+ changes.changes[i] = APR_ARRAY_IDX(array, i, change_t*);
+
+ /* serialize it and all its elements */
+ context = svn_temp_serializer__init(&changes,
+ sizeof(changes),
+ changes.count * 100,
+ pool);
+
+ svn_temp_serializer__push(context,
+ (const void * const *)&changes.changes,
+ changes.count * sizeof(change_t*));
+
+ for (i = 0; i < changes.count; ++i)
+ serialize_change(context, &changes.changes[i]);
+
+ svn_temp_serializer__pop(context);
+
+ /* return the serialized result */
+ serialized = svn_temp_serializer__get(context);
+
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_changes(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ int i;
+ changes_data_t *changes = (changes_data_t *)data;
+ apr_array_header_t *array = apr_array_make(pool, changes->count,
+ sizeof(change_t *));
+
+ /* de-serialize our auxiliary data structure */
+ svn_temp_deserializer__resolve(changes, (void**)&changes->changes);
+
+ /* de-serialize each entry and add it to the array */
+ for (i = 0; i < changes->count; ++i)
+ {
+ deserialize_change((void*)changes->changes,
+ (change_t **)&changes->changes[i]);
+ APR_ARRAY_PUSH(array, change_t *) = changes->changes[i];
+ }
+
+ /* done */
+ *out = array;
+
+ return SVN_NO_ERROR;
+}
+
+/* Auxiliary structure representing the content of a svn_mergeinfo_t hash.
+ This structure is much easier to (de-)serialize than an APR array.
+ */
+typedef struct mergeinfo_data_t
+{
+ /* number of paths in the hash */
+ unsigned count;
+
+ /* COUNT keys (paths) */
+ const char **keys;
+
+ /* COUNT keys lengths (strlen of path) */
+ apr_ssize_t *key_lengths;
+
+ /* COUNT entries, each giving the number of ranges for the key */
+ int *range_counts;
+
+ /* all ranges in a single, concatenated buffer */
+ svn_merge_range_t *ranges;
+} mergeinfo_data_t;
+
+svn_error_t *
+svn_fs_fs__serialize_mergeinfo(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ svn_mergeinfo_t mergeinfo = in;
+ mergeinfo_data_t merges;
+ svn_temp_serializer__context_t *context;
+ svn_stringbuf_t *serialized;
+ apr_hash_index_t *hi;
+ unsigned i;
+ int k;
+ apr_size_t range_count;
+
+ /* initialize our auxiliary data structure */
+ merges.count = apr_hash_count(mergeinfo);
+ merges.keys = apr_palloc(pool, sizeof(*merges.keys) * merges.count);
+ merges.key_lengths = apr_palloc(pool, sizeof(*merges.key_lengths) *
+ merges.count);
+ merges.range_counts = apr_palloc(pool, sizeof(*merges.range_counts) *
+ merges.count);
+
+ i = 0;
+ range_count = 0;
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi), ++i)
+ {
+ svn_rangelist_t *ranges;
+ apr_hash_this(hi, (const void**)&merges.keys[i],
+ &merges.key_lengths[i],
+ (void **)&ranges);
+ merges.range_counts[i] = ranges->nelts;
+ range_count += ranges->nelts;
+ }
+
+ merges.ranges = apr_palloc(pool, sizeof(*merges.ranges) * range_count);
+
+ i = 0;
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ svn_rangelist_t *ranges = svn__apr_hash_index_val(hi);
+ for (k = 0; k < ranges->nelts; ++k, ++i)
+ merges.ranges[i] = *APR_ARRAY_IDX(ranges, k, svn_merge_range_t*);
+ }
+
+ /* serialize it and all its elements */
+ context = svn_temp_serializer__init(&merges,
+ sizeof(merges),
+ range_count * 30,
+ pool);
+
+ /* keys array */
+ svn_temp_serializer__push(context,
+ (const void * const *)&merges.keys,
+ merges.count * sizeof(*merges.keys));
+
+ for (i = 0; i < merges.count; ++i)
+ svn_temp_serializer__add_string(context, &merges.keys[i]);
+
+ svn_temp_serializer__pop(context);
+
+ /* key lengths array */
+ svn_temp_serializer__push(context,
+ (const void * const *)&merges.key_lengths,
+ merges.count * sizeof(*merges.key_lengths));
+ svn_temp_serializer__pop(context);
+
+ /* range counts array */
+ svn_temp_serializer__push(context,
+ (const void * const *)&merges.range_counts,
+ merges.count * sizeof(*merges.range_counts));
+ svn_temp_serializer__pop(context);
+
+ /* ranges */
+ svn_temp_serializer__push(context,
+ (const void * const *)&merges.ranges,
+ range_count * sizeof(*merges.ranges));
+ svn_temp_serializer__pop(context);
+
+ /* return the serialized result */
+ serialized = svn_temp_serializer__get(context);
+
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_mergeinfo(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ unsigned i;
+ int k, n;
+ mergeinfo_data_t *merges = (mergeinfo_data_t *)data;
+ svn_mergeinfo_t mergeinfo;
+
+ /* de-serialize our auxiliary data structure */
+ svn_temp_deserializer__resolve(merges, (void**)&merges->keys);
+ svn_temp_deserializer__resolve(merges, (void**)&merges->key_lengths);
+ svn_temp_deserializer__resolve(merges, (void**)&merges->range_counts);
+ svn_temp_deserializer__resolve(merges, (void**)&merges->ranges);
+
+ /* de-serialize keys and add entries to the result */
+ n = 0;
+ mergeinfo = svn_hash__make(pool);
+ for (i = 0; i < merges->count; ++i)
+ {
+ svn_rangelist_t *ranges = apr_array_make(pool,
+ merges->range_counts[i],
+ sizeof(svn_merge_range_t*));
+ for (k = 0; k < merges->range_counts[i]; ++k, ++n)
+ APR_ARRAY_PUSH(ranges, svn_merge_range_t*) = &merges->ranges[n];
+
+ svn_temp_deserializer__resolve((void*)merges->keys,
+ (void**)&merges->keys[i]);
+ apr_hash_set(mergeinfo, merges->keys[i], merges->key_lengths[i], ranges);
+ }
+
+ /* done */
+ *out = mergeinfo;
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/temp_serializer.h b/contrib/subversion/subversion/libsvn_fs_fs/temp_serializer.h
new file mode 100644
index 0000000..1009d63
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/temp_serializer.h
@@ -0,0 +1,266 @@
+/* temp_serializer.h : serialization functions for caching of FSFS structures
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS__TEMP_SERIALIZER_H
+#define SVN_LIBSVN_FS__TEMP_SERIALIZER_H
+
+#include "fs.h"
+
+/**
+ * Prepend the @a number to the @a string in a space efficient way such that
+ * no other (number,string) combination can produce the same result.
+ * Allocate temporaries as well as the result from @a pool.
+ */
+const char*
+svn_fs_fs__combine_number_and_string(apr_int64_t number,
+ const char *string,
+ apr_pool_t *pool);
+
+/**
+ * Serialize a @a noderev_p within the serialization @a context.
+ */
+void
+svn_fs_fs__noderev_serialize(struct svn_temp_serializer__context_t *context,
+ node_revision_t * const *noderev_p);
+
+/**
+ * Deserialize a @a noderev_p within the @a buffer.
+ */
+void
+svn_fs_fs__noderev_deserialize(void *buffer,
+ node_revision_t **noderev_p);
+
+/**
+ * #svn_txdelta_window_t is not sufficient for caching the data it
+ * represents because data read process needs auxilliary information.
+ */
+typedef struct
+{
+ /* the txdelta window information cached / to be cached */
+ svn_txdelta_window_t *window;
+
+ /* the revision file read pointer position right after reading the window */
+ apr_off_t end_offset;
+} svn_fs_fs__txdelta_cached_window_t;
+
+/**
+ * Implements #svn_cache__serialize_func_t for
+ * #svn_fs_fs__txdelta_cached_window_t.
+ */
+svn_error_t *
+svn_fs_fs__serialize_txdelta_window(void **buffer,
+ apr_size_t *buffer_size,
+ void *item,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for
+ * #svn_fs_fs__txdelta_cached_window_t.
+ */
+svn_error_t *
+svn_fs_fs__deserialize_txdelta_window(void **item,
+ void *buffer,
+ apr_size_t buffer_size,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__serialize_func_t for a manifest
+ * (@a in is an #apr_array_header_t of apr_off_t elements).
+ */
+svn_error_t *
+svn_fs_fs__serialize_manifest(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for a manifest
+ * (@a *out is an #apr_array_header_t of apr_off_t elements).
+ */
+svn_error_t *
+svn_fs_fs__deserialize_manifest(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__serialize_func_t for a properties hash
+ * (@a in is an #apr_hash_t of svn_string_t elements, keyed by const char*).
+ */
+svn_error_t *
+svn_fs_fs__serialize_properties(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for a properties hash
+ * (@a *out is an #apr_hash_t of svn_string_t elements, keyed by const char*).
+ */
+svn_error_t *
+svn_fs_fs__deserialize_properties(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__serialize_func_t for #svn_fs_id_t
+ */
+svn_error_t *
+svn_fs_fs__serialize_id(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for #svn_fs_id_t
+ */
+svn_error_t *
+svn_fs_fs__deserialize_id(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__serialize_func_t for #node_revision_t
+ */
+svn_error_t *
+svn_fs_fs__serialize_node_revision(void **buffer,
+ apr_size_t *buffer_size,
+ void *item,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for #node_revision_t
+ */
+svn_error_t *
+svn_fs_fs__deserialize_node_revision(void **item,
+ void *buffer,
+ apr_size_t buffer_size,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__serialize_func_t for a directory contents hash
+ */
+svn_error_t *
+svn_fs_fs__serialize_dir_entries(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for a directory contents hash
+ */
+svn_error_t *
+svn_fs_fs__deserialize_dir_entries(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__partial_getter_func_t. Set (apr_off_t) @a *out
+ * to the element indexed by (apr_int64_t) @a *baton within the
+ * serialized manifest array @a data and @a data_len. */
+svn_error_t *
+svn_fs_fs__get_sharded_offset(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__partial_getter_func_t for a single
+ * #svn_fs_dirent_t within a serialized directory contents hash,
+ * identified by its name (const char @a *baton).
+ */
+svn_error_t *
+svn_fs_fs__extract_dir_entry(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *pool);
+
+/**
+ * Describes the change to be done to a directory: Set the entry
+ * identify by @a name to the value @a new_entry. If the latter is
+ * @c NULL, the entry shall be removed if it exists. Otherwise it
+ * will be replaced or automatically added, respectively.
+ */
+typedef struct replace_baton_t
+{
+ /** name of the directory entry to modify */
+ const char *name;
+
+ /** directory entry to insert instead */
+ svn_fs_dirent_t *new_entry;
+} replace_baton_t;
+
+/**
+ * Implements #svn_cache__partial_setter_func_t for a single
+ * #svn_fs_dirent_t within a serialized directory contents hash,
+ * identified by its name in the #replace_baton_t in @a baton.
+ */
+svn_error_t *
+svn_fs_fs__replace_dir_entry(void **data,
+ apr_size_t *data_len,
+ void *baton,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__serialize_func_t for an #apr_array_header_t of
+ * #change_t *.
+ */
+svn_error_t *
+svn_fs_fs__serialize_changes(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for an #apr_array_header_t of
+ * #change_t *.
+ */
+svn_error_t *
+svn_fs_fs__deserialize_changes(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__serialize_func_t for #svn_mergeinfo_t objects.
+ */
+svn_error_t *
+svn_fs_fs__serialize_mergeinfo(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for #svn_mergeinfo_t objects.
+ */
+svn_error_t *
+svn_fs_fs__deserialize_mergeinfo(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+#endif
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/tree.c b/contrib/subversion/subversion/libsvn_fs_fs/tree.c
new file mode 100644
index 0000000..c14955d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/tree.c
@@ -0,0 +1,4420 @@
+/* tree.c : tree-like filesystem, built on DAG filesystem
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+/* The job of this layer is to take a filesystem with lots of node
+ sharing going on --- the real DAG filesystem as it appears in the
+ database --- and make it look and act like an ordinary tree
+ filesystem, with no sharing.
+
+ We do just-in-time cloning: you can walk from some unfinished
+ transaction's root down into directories and files shared with
+ committed revisions; as soon as you try to change something, the
+ appropriate nodes get cloned (and parent directory entries updated)
+ invisibly, behind your back. Any other references you have to
+ nodes that have been cloned by other changes, even made by other
+ processes, are automatically updated to point to the right clones. */
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_hash.h"
+#include "svn_private_config.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "svn_mergeinfo.h"
+#include "svn_fs.h"
+#include "svn_props.h"
+
+#include "fs.h"
+#include "key-gen.h"
+#include "dag.h"
+#include "lock.h"
+#include "tree.h"
+#include "fs_fs.h"
+#include "id.h"
+#include "temp_serializer.h"
+
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+#include "../libsvn_fs/fs-loader.h"
+
+
+/* ### I believe this constant will become internal to reps-strings.c.
+ ### see the comment in window_consumer() for more information. */
+
+/* ### the comment also seems to need tweaking: the log file stuff
+ ### is no longer an issue... */
+/* Data written to the filesystem through the svn_fs_apply_textdelta()
+ interface is cached in memory until the end of the data stream, or
+ until a size trigger is hit. Define that trigger here (in bytes).
+ Setting the value to 0 will result in no filesystem buffering at
+ all. The value only really matters when dealing with file contents
+ bigger than the value itself. Above that point, large values here
+ allow the filesystem to buffer more data in memory before flushing
+ to the database, which increases memory usage but greatly decreases
+ the amount of disk access (and log-file generation) in database.
+ Smaller values will limit your overall memory consumption, but can
+ drastically hurt throughput by necessitating more write operations
+ to the database (which also generates more log-files). */
+#define WRITE_BUFFER_SIZE 512000
+
+
+
+/* The root structures.
+
+ Why do they contain different data? Well, transactions are mutable
+ enough that it isn't safe to cache the DAG node for the root
+ directory or the hash of copyfrom data: somebody else might modify
+ them concurrently on disk! (Why is the DAG node cache safer than
+ the root DAG node? When cloning transaction DAG nodes in and out
+ of the cache, all of the possibly-mutable data from the
+ node_revision_t inside the dag_node_t is dropped.) Additionally,
+ revisions are immutable enough that their DAG node cache can be
+ kept in the FS object and shared among multiple revision root
+ objects.
+*/
+typedef struct fs_rev_root_data_t
+{
+ /* A dag node for the revision's root directory. */
+ dag_node_t *root_dir;
+
+ /* Cache structure for mapping const char * PATH to const char
+ *COPYFROM_STRING, so that paths_changed can remember all the
+ copyfrom information in the changes file.
+ COPYFROM_STRING has the format "REV PATH", or is the empty string if
+ the path was added without history. */
+ apr_hash_t *copyfrom_cache;
+
+} fs_rev_root_data_t;
+
+typedef struct fs_txn_root_data_t
+{
+ const char *txn_id;
+
+ /* Cache of txn DAG nodes (without their nested noderevs, because
+ * it's mutable). Same keys/values as ffd->rev_node_cache. */
+ svn_cache__t *txn_node_cache;
+} fs_txn_root_data_t;
+
+/* Declared here to resolve the circular dependencies. */
+static svn_error_t * get_dag(dag_node_t **dag_node_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_boolean_t needs_lock_cache,
+ apr_pool_t *pool);
+
+static svn_fs_root_t *make_revision_root(svn_fs_t *fs, svn_revnum_t rev,
+ dag_node_t *root_dir,
+ apr_pool_t *pool);
+
+static svn_error_t *make_txn_root(svn_fs_root_t **root_p,
+ svn_fs_t *fs, const char *txn,
+ svn_revnum_t base_rev, apr_uint32_t flags,
+ apr_pool_t *pool);
+
+
+/*** Node Caching ***/
+
+/* 1st level cache */
+
+/* An entry in the first-level cache. REVISION and PATH form the key that
+ will ultimately be matched.
+ */
+typedef struct cache_entry_t
+{
+ /* hash value derived from PATH, REVISION.
+ Used to short-circuit failed lookups. */
+ long int hash_value;
+
+ /* revision to which the NODE belongs */
+ svn_revnum_t revision;
+
+ /* path of the NODE */
+ char *path;
+
+ /* cached value of strlen(PATH). */
+ apr_size_t path_len;
+
+ /* the node allocated in the cache's pool. NULL for empty entries. */
+ dag_node_t *node;
+} cache_entry_t;
+
+/* Number of entries in the cache. Keep this low to keep pressure on the
+ CPU caches low as well. A binary value is most efficient. If we walk
+ a directory tree, we want enough entries to store nodes for all files
+ without overwriting the nodes for the parent folder. That way, there
+ will be no unnecessary misses (except for a few random ones caused by
+ hash collision).
+
+ The actual number of instances may be higher but entries that got
+ overwritten are no longer visible.
+ */
+enum { BUCKET_COUNT = 256 };
+
+/* Each pool that has received a DAG node, will hold at least on lock on
+ our cache to ensure that the node remains valid despite being allocated
+ in the cache's pool. This is the structure to represent the lock.
+ */
+typedef struct cache_lock_t
+{
+ /* pool holding the lock */
+ apr_pool_t *pool;
+
+ /* cache being locked */
+ fs_fs_dag_cache_t *cache;
+
+ /* next lock. NULL at EOL */
+ struct cache_lock_t *next;
+
+ /* previous lock. NULL at list head. Only then this==cache->first_lock */
+ struct cache_lock_t *prev;
+} cache_lock_t;
+
+/* The actual cache structure. All nodes will be allocated in POOL.
+ When the number of INSERTIONS (i.e. objects created form that pool)
+ exceeds a certain threshold, the pool will be cleared and the cache
+ with it.
+
+ To ensure that nodes returned from this structure remain valid, the
+ cache will get locked for the lifetime of the _receiving_ pools (i.e.
+ those in which we would allocate the node if there was no cache.).
+ The cache will only be cleared FIRST_LOCK is 0.
+ */
+struct fs_fs_dag_cache_t
+{
+ /* fixed number of (possibly empty) cache entries */
+ cache_entry_t buckets[BUCKET_COUNT];
+
+ /* pool used for all node allocation */
+ apr_pool_t *pool;
+
+ /* number of entries created from POOL since the last cleanup */
+ apr_size_t insertions;
+
+ /* Property lookups etc. have a very high locality (75% re-hit).
+ Thus, remember the last hit location for optimistic lookup. */
+ apr_size_t last_hit;
+
+ /* List of receiving pools that are still alive. */
+ cache_lock_t *first_lock;
+};
+
+/* Cleanup function to be called when a receiving pool gets cleared.
+ Unlocks the cache once.
+ */
+static apr_status_t
+unlock_cache(void *baton_void)
+{
+ cache_lock_t *lock = baton_void;
+
+ /* remove lock from chain. Update the head */
+ if (lock->next)
+ lock->next->prev = lock->prev;
+ if (lock->prev)
+ lock->prev->next = lock->next;
+ else
+ lock->cache->first_lock = lock->next;
+
+ return APR_SUCCESS;
+}
+
+/* Cleanup function to be called when the cache itself gets destroyed.
+ In that case, we must unregister all unlock requests.
+ */
+static apr_status_t
+unregister_locks(void *baton_void)
+{
+ fs_fs_dag_cache_t *cache = baton_void;
+ cache_lock_t *lock;
+
+ for (lock = cache->first_lock; lock; lock = lock->next)
+ apr_pool_cleanup_kill(lock->pool,
+ lock,
+ unlock_cache);
+
+ return APR_SUCCESS;
+}
+
+fs_fs_dag_cache_t*
+svn_fs_fs__create_dag_cache(apr_pool_t *pool)
+{
+ fs_fs_dag_cache_t *result = apr_pcalloc(pool, sizeof(*result));
+ result->pool = svn_pool_create(pool);
+
+ apr_pool_cleanup_register(pool,
+ result,
+ unregister_locks,
+ apr_pool_cleanup_null);
+
+ return result;
+}
+
+/* Prevent the entries in CACHE from being destroyed, for as long as the
+ POOL lives.
+ */
+static void
+lock_cache(fs_fs_dag_cache_t* cache, apr_pool_t *pool)
+{
+ /* we only need to lock / unlock once per pool. Since we will often ask
+ for multiple nodes with the same pool, we can reduce the overhead.
+ However, if e.g. pools are being used in an alternating pattern,
+ we may lock the cache more than once for the same pool (and register
+ just as many cleanup actions).
+ */
+ cache_lock_t *lock = cache->first_lock;
+
+ /* try to find an existing lock for POOL.
+ But limit the time spent on chasing pointers. */
+ int limiter = 8;
+ while (lock && --limiter)
+ if (lock->pool == pool)
+ return;
+
+ /* create a new lock and put it at the beginning of the lock chain */
+ lock = apr_palloc(pool, sizeof(*lock));
+ lock->cache = cache;
+ lock->pool = pool;
+ lock->next = cache->first_lock;
+ lock->prev = NULL;
+
+ if (cache->first_lock)
+ cache->first_lock->prev = lock;
+ cache->first_lock = lock;
+
+ /* instruct POOL to remove the look upon cleanup */
+ apr_pool_cleanup_register(pool,
+ lock,
+ unlock_cache,
+ apr_pool_cleanup_null);
+}
+
+/* Clears the CACHE at regular intervals (destroying all cached nodes)
+ */
+static void
+auto_clear_dag_cache(fs_fs_dag_cache_t* cache)
+{
+ if (cache->first_lock == NULL && cache->insertions > BUCKET_COUNT)
+ {
+ svn_pool_clear(cache->pool);
+
+ memset(cache->buckets, 0, sizeof(cache->buckets));
+ cache->insertions = 0;
+ }
+}
+
+/* For the given REVISION and PATH, return the respective entry in CACHE.
+ If the entry is empty, its NODE member will be NULL and the caller
+ may then set it to the corresponding DAG node allocated in CACHE->POOL.
+ */
+static cache_entry_t *
+cache_lookup( fs_fs_dag_cache_t *cache
+ , svn_revnum_t revision
+ , const char *path)
+{
+ apr_size_t i, bucket_index;
+ apr_size_t path_len = strlen(path);
+ long int hash_value = revision;
+
+ /* optimistic lookup: hit the same bucket again? */
+ cache_entry_t *result = &cache->buckets[cache->last_hit];
+ if ( (result->revision == revision)
+ && (result->path_len == path_len)
+ && !memcmp(result->path, path, path_len))
+ {
+ return result;
+ }
+
+ /* need to do a full lookup. Calculate the hash value
+ (HASH_VALUE has been initialized to REVISION). */
+ for (i = 0; i + 4 <= path_len; i += 4)
+ hash_value = hash_value * 0xd1f3da69 + *(const apr_uint32_t*)(path + i);
+
+ for (; i < path_len; ++i)
+ hash_value = hash_value * 33 + path[i];
+
+ bucket_index = hash_value + (hash_value >> 16);
+ bucket_index = (bucket_index + (bucket_index >> 8)) % BUCKET_COUNT;
+
+ /* access the corresponding bucket and remember its location */
+ result = &cache->buckets[bucket_index];
+ cache->last_hit = bucket_index;
+
+ /* if it is *NOT* a match, clear the bucket, expect the caller to fill
+ in the node and count it as an insertion */
+ if ( (result->hash_value != hash_value)
+ || (result->revision != revision)
+ || (result->path_len != path_len)
+ || memcmp(result->path, path, path_len))
+ {
+ result->hash_value = hash_value;
+ result->revision = revision;
+ if (result->path_len < path_len)
+ result->path = apr_palloc(cache->pool, path_len + 1);
+ result->path_len = path_len;
+ memcpy(result->path, path, path_len + 1);
+
+ result->node = NULL;
+
+ cache->insertions++;
+ }
+
+ return result;
+}
+
+/* 2nd level cache */
+
+/* Find and return the DAG node cache for ROOT and the key that
+ should be used for PATH. */
+static void
+locate_cache(svn_cache__t **cache,
+ const char **key,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ if (root->is_txn_root)
+ {
+ fs_txn_root_data_t *frd = root->fsap_data;
+ if (cache) *cache = frd->txn_node_cache;
+ if (key && path) *key = path;
+ }
+ else
+ {
+ fs_fs_data_t *ffd = root->fs->fsap_data;
+ if (cache) *cache = ffd->rev_node_cache;
+ if (key && path) *key
+ = svn_fs_fs__combine_number_and_string(root->rev, path, pool);
+ }
+}
+
+/* Return NODE for PATH from ROOT's node cache, or NULL if the node
+ isn't cached; read it from the FS. *NODE remains valid until either
+ POOL or the FS gets cleared or destroyed (whichever comes first).
+
+ Since locking can be expensive and POOL may be long-living, for
+ nodes that will not need to survive the next call to this function,
+ set NEEDS_LOCK_CACHE to FALSE. */
+static svn_error_t *
+dag_node_cache_get(dag_node_t **node_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_boolean_t needs_lock_cache,
+ apr_pool_t *pool)
+{
+ svn_boolean_t found;
+ dag_node_t *node = NULL;
+ svn_cache__t *cache;
+ const char *key;
+
+ SVN_ERR_ASSERT(*path == '/');
+
+ if (!root->is_txn_root)
+ {
+ /* immutable DAG node. use the global caches for it */
+
+ fs_fs_data_t *ffd = root->fs->fsap_data;
+ cache_entry_t *bucket;
+
+ auto_clear_dag_cache(ffd->dag_node_cache);
+ bucket = cache_lookup(ffd->dag_node_cache, root->rev, path);
+ if (bucket->node == NULL)
+ {
+ locate_cache(&cache, &key, root, path, pool);
+ SVN_ERR(svn_cache__get((void **)&node, &found, cache, key,
+ ffd->dag_node_cache->pool));
+ if (found && node)
+ {
+ /* Patch up the FS, since this might have come from an old FS
+ * object. */
+ svn_fs_fs__dag_set_fs(node, root->fs);
+ bucket->node = node;
+ }
+ }
+ else
+ {
+ node = bucket->node;
+ }
+
+ /* if we found a node, make sure it remains valid at least as long
+ as it would when allocated in POOL. */
+ if (node && needs_lock_cache)
+ lock_cache(ffd->dag_node_cache, pool);
+ }
+ else
+ {
+ /* DAG is mutable / may become invalid. Use the TXN-local cache */
+
+ locate_cache(&cache, &key, root, path, pool);
+
+ SVN_ERR(svn_cache__get((void **) &node, &found, cache, key, pool));
+ if (found && node)
+ {
+ /* Patch up the FS, since this might have come from an old FS
+ * object. */
+ svn_fs_fs__dag_set_fs(node, root->fs);
+ }
+ }
+
+ *node_p = node;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Add the NODE for PATH to ROOT's node cache. */
+static svn_error_t *
+dag_node_cache_set(svn_fs_root_t *root,
+ const char *path,
+ dag_node_t *node,
+ apr_pool_t *pool)
+{
+ svn_cache__t *cache;
+ const char *key;
+
+ SVN_ERR_ASSERT(*path == '/');
+
+ /* Do *not* attempt to dup and put the node into L1.
+ * dup() is twice as expensive as an L2 lookup (which will set also L1).
+ */
+ locate_cache(&cache, &key, root, path, pool);
+
+ return svn_cache__set(cache, key, node, pool);
+}
+
+
+/* Baton for find_descendents_in_cache. */
+struct fdic_baton {
+ const char *path;
+ apr_array_header_t *list;
+ apr_pool_t *pool;
+};
+
+/* If the given item is a descendent of BATON->PATH, push
+ * it onto BATON->LIST (copying into BATON->POOL). Implements
+ * the svn_iter_apr_hash_cb_t prototype. */
+static svn_error_t *
+find_descendents_in_cache(void *baton,
+ const void *key,
+ apr_ssize_t klen,
+ void *val,
+ apr_pool_t *pool)
+{
+ struct fdic_baton *b = baton;
+ const char *item_path = key;
+
+ if (svn_fspath__skip_ancestor(b->path, item_path))
+ APR_ARRAY_PUSH(b->list, const char *) = apr_pstrdup(b->pool, item_path);
+
+ return SVN_NO_ERROR;
+}
+
+/* Invalidate cache entries for PATH and any of its children. This
+ should *only* be called on a transaction root! */
+static svn_error_t *
+dag_node_cache_invalidate(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct fdic_baton b;
+ svn_cache__t *cache;
+ apr_pool_t *iterpool;
+ int i;
+
+ b.path = path;
+ b.pool = svn_pool_create(pool);
+ b.list = apr_array_make(b.pool, 1, sizeof(const char *));
+
+ SVN_ERR_ASSERT(root->is_txn_root);
+ locate_cache(&cache, NULL, root, NULL, b.pool);
+
+
+ SVN_ERR(svn_cache__iter(NULL, cache, find_descendents_in_cache,
+ &b, b.pool));
+
+ iterpool = svn_pool_create(b.pool);
+
+ for (i = 0; i < b.list->nelts; i++)
+ {
+ const char *descendent = APR_ARRAY_IDX(b.list, i, const char *);
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_cache__set(cache, descendent, NULL, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ svn_pool_destroy(b.pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Creating transaction and revision root nodes. */
+
+svn_error_t *
+svn_fs_fs__txn_root(svn_fs_root_t **root_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ apr_uint32_t flags = 0;
+ apr_hash_t *txnprops;
+
+ /* Look for the temporary txn props representing 'flags'. */
+ SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, txn, pool));
+ if (txnprops)
+ {
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
+ flags |= SVN_FS_TXN_CHECK_OOD;
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
+ flags |= SVN_FS_TXN_CHECK_LOCKS;
+ }
+
+ return make_txn_root(root_p, txn->fs, txn->id, txn->base_rev, flags, pool);
+}
+
+
+svn_error_t *
+svn_fs_fs__revision_root(svn_fs_root_t **root_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ dag_node_t *root_dir;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ SVN_ERR(svn_fs_fs__dag_revision_root(&root_dir, fs, rev, pool));
+
+ *root_p = make_revision_root(fs, rev, root_dir, pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Getting dag nodes for roots. */
+
+
+/* Set *NODE_P to a freshly opened dag node referring to the root
+ directory of ROOT, allocating from POOL. */
+static svn_error_t *
+root_node(dag_node_t **node_p,
+ svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ if (root->is_txn_root)
+ {
+ /* It's a transaction root. Open a fresh copy. */
+ return svn_fs_fs__dag_txn_root(node_p, root->fs, root->txn, pool);
+ }
+ else
+ {
+ /* It's a revision root, so we already have its root directory
+ opened. */
+ fs_rev_root_data_t *frd = root->fsap_data;
+ *node_p = svn_fs_fs__dag_dup(frd->root_dir, pool);
+ return SVN_NO_ERROR;
+ }
+}
+
+
+/* Set *NODE_P to a mutable root directory for ROOT, cloning if
+ necessary, allocating in POOL. ROOT must be a transaction root.
+ Use ERROR_PATH in error messages. */
+static svn_error_t *
+mutable_root_node(dag_node_t **node_p,
+ svn_fs_root_t *root,
+ const char *error_path,
+ apr_pool_t *pool)
+{
+ if (root->is_txn_root)
+ return svn_fs_fs__dag_clone_root(node_p, root->fs, root->txn, pool);
+ else
+ /* If it's not a transaction root, we can't change its contents. */
+ return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path);
+}
+
+
+
+/* Traversing directory paths. */
+
+typedef enum copy_id_inherit_t
+{
+ copy_id_inherit_unknown = 0,
+ copy_id_inherit_self,
+ copy_id_inherit_parent,
+ copy_id_inherit_new
+
+} copy_id_inherit_t;
+
+/* A linked list representing the path from a node up to a root
+ directory. We use this for cloning, and for operations that need
+ to deal with both a node and its parent directory. For example, a
+ `delete' operation needs to know that the node actually exists, but
+ also needs to change the parent directory. */
+typedef struct parent_path_t
+{
+
+ /* A node along the path. This could be the final node, one of its
+ parents, or the root. Every parent path ends with an element for
+ the root directory. */
+ dag_node_t *node;
+
+ /* The name NODE has in its parent directory. This is zero for the
+ root directory, which (obviously) has no name in its parent. */
+ char *entry;
+
+ /* The parent of NODE, or zero if NODE is the root directory. */
+ struct parent_path_t *parent;
+
+ /* The copy ID inheritance style. */
+ copy_id_inherit_t copy_inherit;
+
+ /* If copy ID inheritance style is copy_id_inherit_new, this is the
+ path which should be implicitly copied; otherwise, this is NULL. */
+ const char *copy_src_path;
+
+} parent_path_t;
+
+/* Return a text string describing the absolute path of parent_path
+ PARENT_PATH. It will be allocated in POOL. */
+static const char *
+parent_path_path(parent_path_t *parent_path,
+ apr_pool_t *pool)
+{
+ const char *path_so_far = "/";
+ if (parent_path->parent)
+ path_so_far = parent_path_path(parent_path->parent, pool);
+ return parent_path->entry
+ ? svn_fspath__join(path_so_far, parent_path->entry, pool)
+ : path_so_far;
+}
+
+
+/* Return the FS path for the parent path chain object CHILD relative
+ to its ANCESTOR in the same chain, allocated in POOL. */
+static const char *
+parent_path_relpath(parent_path_t *child,
+ parent_path_t *ancestor,
+ apr_pool_t *pool)
+{
+ const char *path_so_far = "";
+ parent_path_t *this_node = child;
+ while (this_node != ancestor)
+ {
+ assert(this_node != NULL);
+ path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool);
+ this_node = this_node->parent;
+ }
+ return path_so_far;
+}
+
+
+
+/* Choose a copy ID inheritance method *INHERIT_P to be used in the
+ event that immutable node CHILD in FS needs to be made mutable. If
+ the inheritance method is copy_id_inherit_new, also return a
+ *COPY_SRC_PATH on which to base the new copy ID (else return NULL
+ for that path). CHILD must have a parent (it cannot be the root
+ node). TXN_ID is the transaction in which these items might be
+ mutable. Allocations are taken from POOL. */
+static svn_error_t *
+get_copy_inheritance(copy_id_inherit_t *inherit_p,
+ const char **copy_src_path,
+ svn_fs_t *fs,
+ parent_path_t *child,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *child_id, *parent_id, *copyroot_id;
+ const char *child_copy_id, *parent_copy_id;
+ const char *id_path = NULL;
+ svn_fs_root_t *copyroot_root;
+ dag_node_t *copyroot_node;
+ svn_revnum_t copyroot_rev;
+ const char *copyroot_path;
+
+ SVN_ERR_ASSERT(child && child->parent && txn_id);
+
+ /* Initialize some convenience variables. */
+ child_id = svn_fs_fs__dag_get_id(child->node);
+ parent_id = svn_fs_fs__dag_get_id(child->parent->node);
+ child_copy_id = svn_fs_fs__id_copy_id(child_id);
+ parent_copy_id = svn_fs_fs__id_copy_id(parent_id);
+
+ /* If this child is already mutable, we have nothing to do. */
+ if (svn_fs_fs__id_txn_id(child_id))
+ {
+ *inherit_p = copy_id_inherit_self;
+ *copy_src_path = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* From this point on, we'll assume that the child will just take
+ its copy ID from its parent. */
+ *inherit_p = copy_id_inherit_parent;
+ *copy_src_path = NULL;
+
+ /* Special case: if the child's copy ID is '0', use the parent's
+ copy ID. */
+ if (strcmp(child_copy_id, "0") == 0)
+ return SVN_NO_ERROR;
+
+ /* Compare the copy IDs of the child and its parent. If they are
+ the same, then the child is already on the same branch as the
+ parent, and should use the same mutability copy ID that the
+ parent will use. */
+ if (svn_fs_fs__key_compare(child_copy_id, parent_copy_id) == 0)
+ return SVN_NO_ERROR;
+
+ /* If the child is on the same branch that the parent is on, the
+ child should just use the same copy ID that the parent would use.
+ Else, the child needs to generate a new copy ID to use should it
+ need to be made mutable. We will claim that child is on the same
+ branch as its parent if the child itself is not a branch point,
+ or if it is a branch point that we are accessing via its original
+ copy destination path. */
+ SVN_ERR(svn_fs_fs__dag_get_copyroot(&copyroot_rev, &copyroot_path,
+ child->node));
+ SVN_ERR(svn_fs_fs__revision_root(&copyroot_root, fs, copyroot_rev, pool));
+ SVN_ERR(get_dag(&copyroot_node, copyroot_root, copyroot_path, FALSE, pool));
+ copyroot_id = svn_fs_fs__dag_get_id(copyroot_node);
+
+ if (svn_fs_fs__id_compare(copyroot_id, child_id) == -1)
+ return SVN_NO_ERROR;
+
+ /* Determine if we are looking at the child via its original path or
+ as a subtree item of a copied tree. */
+ id_path = svn_fs_fs__dag_get_created_path(child->node);
+ if (strcmp(id_path, parent_path_path(child, pool)) == 0)
+ {
+ *inherit_p = copy_id_inherit_self;
+ return SVN_NO_ERROR;
+ }
+
+ /* We are pretty sure that the child node is an unedited nested
+ branched node. When it needs to be made mutable, it should claim
+ a new copy ID. */
+ *inherit_p = copy_id_inherit_new;
+ *copy_src_path = id_path;
+ return SVN_NO_ERROR;
+}
+
+/* Allocate a new parent_path_t node from POOL, referring to NODE,
+ ENTRY, PARENT, and COPY_ID. */
+static parent_path_t *
+make_parent_path(dag_node_t *node,
+ char *entry,
+ parent_path_t *parent,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path = apr_pcalloc(pool, sizeof(*parent_path));
+ parent_path->node = node;
+ parent_path->entry = entry;
+ parent_path->parent = parent;
+ parent_path->copy_inherit = copy_id_inherit_unknown;
+ parent_path->copy_src_path = NULL;
+ return parent_path;
+}
+
+
+/* Flags for open_path. */
+typedef enum open_path_flags_t {
+
+ /* The last component of the PATH need not exist. (All parent
+ directories must exist, as usual.) If the last component doesn't
+ exist, simply leave the `node' member of the bottom parent_path
+ component zero. */
+ open_path_last_optional = 1,
+
+ /* When this flag is set, don't bother to lookup the DAG node in
+ our caches because we already tried this. Ignoring this flag
+ has no functional impact. */
+ open_path_uncached = 2,
+
+ /* The caller does not care about the parent node chain but only
+ the final DAG node. */
+ open_path_node_only = 4
+} open_path_flags_t;
+
+
+/* Open the node identified by PATH in ROOT, allocating in POOL. Set
+ *PARENT_PATH_P to a path from the node up to ROOT. The resulting
+ **PARENT_PATH_P value is guaranteed to contain at least one
+ *element, for the root directory. PATH must be in canonical form.
+
+ If resulting *PARENT_PATH_P will eventually be made mutable and
+ modified, or if copy ID inheritance information is otherwise
+ needed, TXN_ID should be the ID of the mutability transaction. If
+ TXN_ID is NULL, no copy ID inheritance information will be
+ calculated for the *PARENT_PATH_P chain.
+
+ If FLAGS & open_path_last_optional is zero, return the error
+ SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If
+ non-zero, require all the parent directories to exist as normal,
+ but if the final path component doesn't exist, simply return a path
+ whose bottom `node' member is zero. This option is useful for
+ callers that create new nodes --- we find the parent directory for
+ them, and tell them whether the entry exists already.
+
+ The remaining bits in FLAGS are hints that allow this function
+ to take shortcuts based on knowledge that the caller provides,
+ such as the caller is not actually being interested in PARENT_PATH_P,
+ but only in (*PARENT_PATH_P)->NODE.
+
+ NOTE: Public interfaces which only *read* from the filesystem
+ should not call this function directly, but should instead use
+ get_dag().
+*/
+static svn_error_t *
+open_path(parent_path_t **parent_path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ int flags,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = root->fs;
+ dag_node_t *here = NULL; /* The directory we're currently looking at. */
+ parent_path_t *parent_path; /* The path from HERE up to the root. */
+ const char *rest; /* The portion of PATH we haven't traversed yet. */
+
+ /* ensure a canonical path representation */
+ const char *path_so_far = "/";
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* callers often traverse the tree in some path-based order. That means
+ a sibling of PATH has been presently accessed. Try to start the lookup
+ directly at the parent node, if the caller did not requested the full
+ parent chain. */
+ const char *directory;
+ assert(svn_fs__is_canonical_abspath(path));
+ if (flags & open_path_node_only)
+ {
+ directory = svn_dirent_dirname(path, pool);
+ if (directory[1] != 0) /* root nodes are covered anyway */
+ SVN_ERR(dag_node_cache_get(&here, root, directory, TRUE, pool));
+ }
+
+ /* did the shortcut work? */
+ if (here)
+ {
+ path_so_far = directory;
+ rest = path + strlen(directory) + 1;
+ }
+ else
+ {
+ /* Make a parent_path item for the root node, using its own current
+ copy id. */
+ SVN_ERR(root_node(&here, root, pool));
+ rest = path + 1; /* skip the leading '/', it saves in iteration */
+ }
+
+ parent_path = make_parent_path(here, 0, 0, pool);
+ parent_path->copy_inherit = copy_id_inherit_self;
+
+ /* Whenever we are at the top of this loop:
+ - HERE is our current directory,
+ - ID is the node revision ID of HERE,
+ - REST is the path we're going to find in HERE, and
+ - PARENT_PATH includes HERE and all its parents. */
+ for (;;)
+ {
+ const char *next;
+ char *entry;
+ dag_node_t *child;
+
+ svn_pool_clear(iterpool);
+
+ /* Parse out the next entry from the path. */
+ entry = svn_fs__next_entry_name(&next, rest, pool);
+
+ /* Calculate the path traversed thus far. */
+ path_so_far = svn_fspath__join(path_so_far, entry, pool);
+
+ if (*entry == '\0')
+ {
+ /* Given the behavior of svn_fs__next_entry_name(), this
+ happens when the path either starts or ends with a slash.
+ In either case, we stay put: the current directory stays
+ the same, and we add nothing to the parent path. */
+ child = here;
+ }
+ else
+ {
+ copy_id_inherit_t inherit;
+ const char *copy_path = NULL;
+ svn_error_t *err = SVN_NO_ERROR;
+ dag_node_t *cached_node = NULL;
+
+ /* If we found a directory entry, follow it. First, we
+ check our node cache, and, failing that, we hit the DAG
+ layer. Don't bother to contact the cache for the last
+ element if we already know the lookup to fail for the
+ complete path. */
+ if (next || !(flags & open_path_uncached))
+ SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far,
+ TRUE, pool));
+ if (cached_node)
+ child = cached_node;
+ else
+ err = svn_fs_fs__dag_open(&child, here, entry, pool, iterpool);
+
+ /* "file not found" requires special handling. */
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ /* If this was the last path component, and the caller
+ said it was optional, then don't return an error;
+ just put a NULL node pointer in the path. */
+
+ svn_error_clear(err);
+
+ if ((flags & open_path_last_optional)
+ && (! next || *next == '\0'))
+ {
+ parent_path = make_parent_path(NULL, entry, parent_path,
+ pool);
+ break;
+ }
+ else
+ {
+ /* Build a better error message than svn_fs_fs__dag_open
+ can provide, giving the root and full path name. */
+ return SVN_FS__NOT_FOUND(root, path);
+ }
+ }
+
+ /* Other errors we return normally. */
+ SVN_ERR(err);
+
+ if (flags & open_path_node_only)
+ {
+ /* Shortcut: the caller only wan'ts the final DAG node. */
+ parent_path->node = child;
+ }
+ else
+ {
+ /* Now, make a parent_path item for CHILD. */
+ parent_path = make_parent_path(child, entry, parent_path, pool);
+ if (txn_id)
+ {
+ SVN_ERR(get_copy_inheritance(&inherit, &copy_path, fs,
+ parent_path, txn_id, iterpool));
+ parent_path->copy_inherit = inherit;
+ parent_path->copy_src_path = apr_pstrdup(pool, copy_path);
+ }
+ }
+
+ /* Cache the node we found (if it wasn't already cached). */
+ if (! cached_node)
+ SVN_ERR(dag_node_cache_set(root, path_so_far, child, iterpool));
+ }
+
+ /* Are we finished traversing the path? */
+ if (! next)
+ break;
+
+ /* The path isn't finished yet; we'd better be in a directory. */
+ if (svn_fs_fs__dag_node_kind(child) != svn_node_dir)
+ SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far),
+ apr_psprintf(iterpool, _("Failure opening '%s'"), path));
+
+ rest = next;
+ here = child;
+ }
+
+ svn_pool_destroy(iterpool);
+ *parent_path_p = parent_path;
+ return SVN_NO_ERROR;
+}
+
+
+/* Make the node referred to by PARENT_PATH mutable, if it isn't
+ already, allocating from POOL. ROOT must be the root from which
+ PARENT_PATH descends. Clone any parent directories as needed.
+ Adjust the dag nodes in PARENT_PATH to refer to the clones. Use
+ ERROR_PATH in error messages. */
+static svn_error_t *
+make_path_mutable(svn_fs_root_t *root,
+ parent_path_t *parent_path,
+ const char *error_path,
+ apr_pool_t *pool)
+{
+ dag_node_t *clone;
+ const char *txn_id = root->txn;
+
+ /* Is the node mutable already? */
+ if (svn_fs_fs__dag_check_mutable(parent_path->node))
+ return SVN_NO_ERROR;
+
+ /* Are we trying to clone the root, or somebody's child node? */
+ if (parent_path->parent)
+ {
+ const svn_fs_id_t *parent_id, *child_id, *copyroot_id;
+ const char *copy_id = NULL;
+ copy_id_inherit_t inherit = parent_path->copy_inherit;
+ const char *clone_path, *copyroot_path;
+ svn_revnum_t copyroot_rev;
+ svn_boolean_t is_parent_copyroot = FALSE;
+ svn_fs_root_t *copyroot_root;
+ dag_node_t *copyroot_node;
+
+ /* We're trying to clone somebody's child. Make sure our parent
+ is mutable. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent,
+ error_path, pool));
+
+ switch (inherit)
+ {
+ case copy_id_inherit_parent:
+ parent_id = svn_fs_fs__dag_get_id(parent_path->parent->node);
+ copy_id = svn_fs_fs__id_copy_id(parent_id);
+ break;
+
+ case copy_id_inherit_new:
+ SVN_ERR(svn_fs_fs__reserve_copy_id(&copy_id, root->fs, txn_id,
+ pool));
+ break;
+
+ case copy_id_inherit_self:
+ copy_id = NULL;
+ break;
+
+ case copy_id_inherit_unknown:
+ default:
+ SVN_ERR_MALFUNCTION(); /* uh-oh -- somebody didn't calculate copy-ID
+ inheritance data. */
+ }
+
+ /* Determine what copyroot our new child node should use. */
+ SVN_ERR(svn_fs_fs__dag_get_copyroot(&copyroot_rev, &copyroot_path,
+ parent_path->node));
+ SVN_ERR(svn_fs_fs__revision_root(&copyroot_root, root->fs,
+ copyroot_rev, pool));
+ SVN_ERR(get_dag(&copyroot_node, copyroot_root, copyroot_path,
+ FALSE, pool));
+
+ child_id = svn_fs_fs__dag_get_id(parent_path->node);
+ copyroot_id = svn_fs_fs__dag_get_id(copyroot_node);
+ if (strcmp(svn_fs_fs__id_node_id(child_id),
+ svn_fs_fs__id_node_id(copyroot_id)) != 0)
+ is_parent_copyroot = TRUE;
+
+ /* Now make this node mutable. */
+ clone_path = parent_path_path(parent_path->parent, pool);
+ SVN_ERR(svn_fs_fs__dag_clone_child(&clone,
+ parent_path->parent->node,
+ clone_path,
+ parent_path->entry,
+ copy_id, txn_id,
+ is_parent_copyroot,
+ pool));
+
+ /* Update the path cache. */
+ SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, pool),
+ clone, pool));
+ }
+ else
+ {
+ /* We're trying to clone the root directory. */
+ SVN_ERR(mutable_root_node(&clone, root, error_path, pool));
+ }
+
+ /* Update the PARENT_PATH link to refer to the clone. */
+ parent_path->node = clone;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Open the node identified by PATH in ROOT. Set DAG_NODE_P to the
+ node we find, allocated in POOL. Return the error
+ SVN_ERR_FS_NOT_FOUND if this node doesn't exist.
+
+ Since locking can be expensive and POOL may be long-living, for
+ nodes that will not need to survive the next call to this function,
+ set NEEDS_LOCK_CACHE to FALSE. */
+static svn_error_t *
+get_dag(dag_node_t **dag_node_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_boolean_t needs_lock_cache,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path;
+ dag_node_t *node = NULL;
+
+ /* First we look for the DAG in our cache
+ (if the path may be canonical). */
+ if (*path == '/')
+ SVN_ERR(dag_node_cache_get(&node, root, path, needs_lock_cache, pool));
+
+ if (! node)
+ {
+ /* Canonicalize the input PATH. */
+ if (! svn_fs__is_canonical_abspath(path))
+ {
+ path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* Try again with the corrected path. */
+ SVN_ERR(dag_node_cache_get(&node, root, path, needs_lock_cache,
+ pool));
+ }
+
+ if (! node)
+ {
+ /* Call open_path with no flags, as we want this to return an
+ * error if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, root, path,
+ open_path_uncached | open_path_node_only,
+ NULL, pool));
+ node = parent_path->node;
+
+ /* No need to cache our find -- open_path() will do that for us. */
+ }
+ }
+
+ *dag_node_p = node;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Populating the `changes' table. */
+
+/* Add a change to the changes table in FS, keyed on transaction id
+ TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
+ PATH (whose node revision id is--or was, in the case of a
+ deletion--NODEREV_ID), and optionally that TEXT_MODs or PROP_MODs
+ occurred. If the change resulted from a copy, COPYFROM_REV and
+ COPYFROM_PATH specify under which revision and path the node was
+ copied from. If this was not part of a copy, COPYFROM_REV should
+ be SVN_INVALID_REVNUM. Do all this as part of POOL. */
+static svn_error_t *
+add_change(svn_fs_t *fs,
+ const char *txn_id,
+ const char *path,
+ const svn_fs_id_t *noderev_id,
+ svn_fs_path_change_kind_t change_kind,
+ svn_boolean_t text_mod,
+ svn_boolean_t prop_mod,
+ svn_node_kind_t node_kind,
+ svn_revnum_t copyfrom_rev,
+ const char *copyfrom_path,
+ apr_pool_t *pool)
+{
+ return svn_fs_fs__add_change(fs, txn_id,
+ svn_fs__canonicalize_abspath(path, pool),
+ noderev_id, change_kind, text_mod, prop_mod,
+ node_kind, copyfrom_rev, copyfrom_path,
+ pool);
+}
+
+
+
+/* Generic node operations. */
+
+/* Get the id of a node referenced by path PATH in ROOT. Return the
+ id in *ID_P allocated in POOL. */
+svn_error_t *
+svn_fs_fs__node_id(const svn_fs_id_t **id_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ if ((! root->is_txn_root)
+ && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0'))))
+ {
+ /* Optimize the case where we don't need any db access at all.
+ The root directory ("" or "/") node is stored in the
+ svn_fs_root_t object, and never changes when it's a revision
+ root, so we can just reach in and grab it directly. */
+ fs_rev_root_data_t *frd = root->fsap_data;
+ *id_p = svn_fs_fs__id_copy(svn_fs_fs__dag_get_id(frd->root_dir), pool);
+ }
+ else
+ {
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+ *id_p = svn_fs_fs__id_copy(svn_fs_fs__dag_get_id(node), pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__node_created_rev(svn_revnum_t *revision,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+ return svn_fs_fs__dag_get_revision(revision, node, pool);
+}
+
+
+/* Set *CREATED_PATH to the path at which PATH under ROOT was created.
+ Return a string allocated in POOL. */
+static svn_error_t *
+fs_node_created_path(const char **created_path,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, root, path, TRUE, pool));
+ *created_path = svn_fs_fs__dag_get_created_path(node);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *KIND_P to the type of node located at PATH under ROOT.
+ Perform temporary allocations in POOL. */
+static svn_error_t *
+node_kind(svn_node_kind_t *kind_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *node_id;
+ dag_node_t *node;
+
+ /* Get the node id. */
+ SVN_ERR(svn_fs_fs__node_id(&node_id, root, path, pool));
+
+ /* Use the node id to get the real kind. */
+ SVN_ERR(svn_fs_fs__dag_get_node(&node, root->fs, node_id, pool));
+ *kind_p = svn_fs_fs__dag_node_kind(node);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *KIND_P to the type of node present at PATH under ROOT. If
+ PATH does not exist under ROOT, set *KIND_P to svn_node_none. Use
+ POOL for temporary allocation. */
+svn_error_t *
+svn_fs_fs__check_path(svn_node_kind_t *kind_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = node_kind(kind_p, root, path, pool);
+ if (err &&
+ ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ *kind_p = svn_node_none;
+ }
+
+ return svn_error_trace(err);
+}
+
+/* Set *VALUE_P to the value of the property named PROPNAME of PATH in
+ ROOT. If the node has no property by that name, set *VALUE_P to
+ zero. Allocate the result in POOL. */
+static svn_error_t *
+fs_node_prop(svn_string_t **value_p,
+ svn_fs_root_t *root,
+ const char *path,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+ apr_hash_t *proplist;
+
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+ SVN_ERR(svn_fs_fs__dag_get_proplist(&proplist, node, pool));
+ *value_p = NULL;
+ if (proplist)
+ *value_p = svn_hash_gets(proplist, propname);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *TABLE_P to the entire property list of PATH under ROOT, as an
+ APR hash table allocated in POOL. The resulting property table
+ maps property names to pointers to svn_string_t objects containing
+ the property value. */
+static svn_error_t *
+fs_node_proplist(apr_hash_t **table_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_hash_t *table;
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+ SVN_ERR(svn_fs_fs__dag_get_proplist(&table, node, pool));
+ *table_p = table ? table : apr_hash_make(pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+increment_mergeinfo_up_tree(parent_path_t *pp,
+ apr_int64_t increment,
+ apr_pool_t *pool)
+{
+ for (; pp; pp = pp->parent)
+ SVN_ERR(svn_fs_fs__dag_increment_mergeinfo_count(pp->node,
+ increment,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Change, add, or delete a node's property value. The affected node
+ is PATH under ROOT, the property value to modify is NAME, and VALUE
+ points to either a string value to set the new contents to, or NULL
+ if the property should be deleted. Perform temporary allocations
+ in POOL. */
+static svn_error_t *
+fs_change_node_prop(svn_fs_root_t *root,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path;
+ apr_hash_t *proplist;
+ const char *txn_id;
+
+ if (! root->is_txn_root)
+ return SVN_FS__NOT_TXN(root);
+ txn_id = root->txn;
+
+ path = svn_fs__canonicalize_abspath(path, pool);
+ SVN_ERR(open_path(&parent_path, root, path, 0, txn_id, pool));
+
+ /* Check (non-recursively) to see if path is locked; if so, check
+ that we can use it. */
+ if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_fs__allow_locked_operation(path, root->fs, FALSE, FALSE,
+ pool));
+
+ SVN_ERR(make_path_mutable(root, parent_path, path, pool));
+ SVN_ERR(svn_fs_fs__dag_get_proplist(&proplist, parent_path->node, pool));
+
+ /* If there's no proplist, but we're just deleting a property, exit now. */
+ if ((! proplist) && (! value))
+ return SVN_NO_ERROR;
+
+ /* Now, if there's no proplist, we know we need to make one. */
+ if (! proplist)
+ proplist = apr_hash_make(pool);
+
+ if (svn_fs_fs__fs_supports_mergeinfo(root->fs)
+ && strcmp (name, SVN_PROP_MERGEINFO) == 0)
+ {
+ apr_int64_t increment = 0;
+ svn_boolean_t had_mergeinfo;
+ SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&had_mergeinfo, parent_path->node));
+
+ if (value && !had_mergeinfo)
+ increment = 1;
+ else if (!value && had_mergeinfo)
+ increment = -1;
+
+ if (increment != 0)
+ {
+ SVN_ERR(increment_mergeinfo_up_tree(parent_path, increment, pool));
+ SVN_ERR(svn_fs_fs__dag_set_has_mergeinfo(parent_path->node,
+ (value != NULL), pool));
+ }
+ }
+
+ /* Set the property. */
+ svn_hash_sets(proplist, name, value);
+
+ /* Overwrite the node's proplist. */
+ SVN_ERR(svn_fs_fs__dag_set_proplist(parent_path->node, proplist,
+ pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path,
+ svn_fs_fs__dag_get_id(parent_path->node),
+ svn_fs_path_change_modify, FALSE, TRUE,
+ svn_fs_fs__dag_node_kind(parent_path->node),
+ SVN_INVALID_REVNUM, NULL, pool);
+}
+
+
+/* Determine if the properties of two path/root combinations are
+ different. Set *CHANGED_P to TRUE if the properties at PATH1 under
+ ROOT1 differ from those at PATH2 under ROOT2, or FALSE otherwise.
+ Both roots must be in the same filesystem. */
+static svn_error_t *
+fs_props_changed(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ dag_node_t *node1, *node2;
+
+ /* Check that roots are in the same fs. */
+ if (root1->fs != root2->fs)
+ return svn_error_create
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Cannot compare property value between two different filesystems"));
+
+ SVN_ERR(get_dag(&node1, root1, path1, TRUE, pool));
+ SVN_ERR(get_dag(&node2, root2, path2, TRUE, pool));
+ return svn_fs_fs__dag_things_different(changed_p, NULL,
+ node1, node2);
+}
+
+
+
+/* Merges and commits. */
+
+/* Set *NODE to the root node of ROOT. */
+static svn_error_t *
+get_root(dag_node_t **node, svn_fs_root_t *root, apr_pool_t *pool)
+{
+ return get_dag(node, root, "/", TRUE, pool);
+}
+
+
+/* Set the contents of CONFLICT_PATH to PATH, and return an
+ SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
+ at PATH. Perform all allocations in POOL (except the allocation of
+ CONFLICT_PATH, which should be handled outside this function). */
+static svn_error_t *
+conflict_err(svn_stringbuf_t *conflict_path,
+ const char *path)
+{
+ svn_stringbuf_set(conflict_path, path);
+ return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
+ _("Conflict at '%s'"), path);
+}
+
+
+/* Merge changes between ANCESTOR and SOURCE into TARGET. ANCESTOR
+ * and TARGET must be distinct node revisions. TARGET_PATH should
+ * correspond to TARGET's full path in its filesystem, and is used for
+ * reporting conflict location.
+ *
+ * SOURCE, TARGET, and ANCESTOR are generally directories; this
+ * function recursively merges the directories' contents. If any are
+ * files, this function simply returns an error whenever SOURCE,
+ * TARGET, and ANCESTOR are all distinct node revisions.
+ *
+ * If there are differences between ANCESTOR and SOURCE that conflict
+ * with changes between ANCESTOR and TARGET, this function returns an
+ * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
+ * conflicting node in TARGET, with TARGET_PATH prepended as a path.
+ *
+ * If there are no conflicting differences, CONFLICT_P is updated to
+ * the empty string.
+ *
+ * CONFLICT_P must point to a valid svn_stringbuf_t.
+ *
+ * Do any necessary temporary allocation in POOL.
+ */
+static svn_error_t *
+merge(svn_stringbuf_t *conflict_p,
+ const char *target_path,
+ dag_node_t *target,
+ dag_node_t *source,
+ dag_node_t *ancestor,
+ const char *txn_id,
+ apr_int64_t *mergeinfo_increment_out,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *source_id, *target_id, *ancestor_id;
+ apr_hash_t *s_entries, *t_entries, *a_entries;
+ apr_hash_index_t *hi;
+ svn_fs_t *fs;
+ apr_pool_t *iterpool;
+ apr_int64_t mergeinfo_increment = 0;
+ svn_boolean_t fs_supports_mergeinfo;
+
+ /* Make sure everyone comes from the same filesystem. */
+ fs = svn_fs_fs__dag_get_fs(ancestor);
+ if ((fs != svn_fs_fs__dag_get_fs(source))
+ || (fs != svn_fs_fs__dag_get_fs(target)))
+ {
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Bad merge; ancestor, source, and target not all in same fs"));
+ }
+
+ /* We have the same fs, now check it. */
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ source_id = svn_fs_fs__dag_get_id(source);
+ target_id = svn_fs_fs__dag_get_id(target);
+ ancestor_id = svn_fs_fs__dag_get_id(ancestor);
+
+ /* It's improper to call this function with ancestor == target. */
+ if (svn_fs_fs__id_eq(ancestor_id, target_id))
+ {
+ svn_string_t *id_str = svn_fs_fs__id_unparse(target_id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Bad merge; target '%s' has id '%s', same as ancestor"),
+ target_path, id_str->data);
+ }
+
+ svn_stringbuf_setempty(conflict_p);
+
+ /* Base cases:
+ * Either no change made in source, or same change as made in target.
+ * Both mean nothing to merge here.
+ */
+ if (svn_fs_fs__id_eq(ancestor_id, source_id)
+ || (svn_fs_fs__id_eq(source_id, target_id)))
+ return SVN_NO_ERROR;
+
+ /* Else proceed, knowing all three are distinct node revisions.
+ *
+ * How to merge from this point:
+ *
+ * if (not all 3 are directories)
+ * {
+ * early exit with conflict;
+ * }
+ *
+ * // Property changes may only be made to up-to-date
+ * // directories, because once the client commits the prop
+ * // change, it bumps the directory's revision, and therefore
+ * // must be able to depend on there being no other changes to
+ * // that directory in the repository.
+ * if (target's property list differs from ancestor's)
+ * conflict;
+ *
+ * For each entry NAME in the directory ANCESTOR:
+ *
+ * Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
+ * the name within ANCESTOR, SOURCE, and TARGET respectively.
+ * (Possibly null if NAME does not exist in SOURCE or TARGET.)
+ *
+ * If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
+ * No changes were made to this entry while the transaction was in
+ * progress, so do nothing to the target.
+ *
+ * Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
+ * A change was made to this entry while the transaction was in
+ * process, but the transaction did not touch this entry. Replace
+ * TARGET-ENTRY with SOURCE-ENTRY.
+ *
+ * Else:
+ * Changes were made to this entry both within the transaction and
+ * to the repository while the transaction was in progress. They
+ * must be merged or declared to be in conflict.
+ *
+ * If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
+ * double delete; flag a conflict.
+ *
+ * If any of the three entries is of type file, declare a conflict.
+ *
+ * If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
+ * modification of ANCESTOR-ENTRY (determine by comparing the
+ * node-id fields), declare a conflict. A replacement is
+ * incompatible with a modification or other replacement--even
+ * an identical replacement.
+ *
+ * Direct modifications were made to the directory ANCESTOR-ENTRY
+ * in both SOURCE and TARGET. Recursively merge these
+ * modifications.
+ *
+ * For each leftover entry NAME in the directory SOURCE:
+ *
+ * If NAME exists in TARGET, declare a conflict. Even if SOURCE and
+ * TARGET are adding exactly the same thing, two additions are not
+ * auto-mergeable with each other.
+ *
+ * Add NAME to TARGET with the entry from SOURCE.
+ *
+ * Now that we are done merging the changes from SOURCE into the
+ * directory TARGET, update TARGET's predecessor to be SOURCE.
+ */
+
+ if ((svn_fs_fs__dag_node_kind(source) != svn_node_dir)
+ || (svn_fs_fs__dag_node_kind(target) != svn_node_dir)
+ || (svn_fs_fs__dag_node_kind(ancestor) != svn_node_dir))
+ {
+ return conflict_err(conflict_p, target_path);
+ }
+
+
+ /* Possible early merge failure: if target and ancestor have
+ different property lists, then the merge should fail.
+ Propchanges can *only* be committed on an up-to-date directory.
+ ### TODO: see issue #418 about the inelegance of this.
+
+ Another possible, similar, early merge failure: if source and
+ ancestor have different property lists (meaning someone else
+ changed directory properties while our commit transaction was
+ happening), the merge should fail. See issue #2751.
+ */
+ {
+ node_revision_t *tgt_nr, *anc_nr, *src_nr;
+
+ /* Get node revisions for our id's. */
+ SVN_ERR(svn_fs_fs__get_node_revision(&tgt_nr, fs, target_id, pool));
+ SVN_ERR(svn_fs_fs__get_node_revision(&anc_nr, fs, ancestor_id, pool));
+ SVN_ERR(svn_fs_fs__get_node_revision(&src_nr, fs, source_id, pool));
+
+ /* Now compare the prop-keys of the skels. Note that just because
+ the keys are different -doesn't- mean the proplists have
+ different contents. But merge() isn't concerned with contents;
+ it doesn't do a brute-force comparison on textual contents, so
+ it won't do that here either. Checking to see if the propkey
+ atoms are `equal' is enough. */
+ if (! svn_fs_fs__noderev_same_rep_key(tgt_nr->prop_rep, anc_nr->prop_rep))
+ return conflict_err(conflict_p, target_path);
+ if (! svn_fs_fs__noderev_same_rep_key(src_nr->prop_rep, anc_nr->prop_rep))
+ return conflict_err(conflict_p, target_path);
+ }
+
+ /* ### todo: it would be more efficient to simply check for a NULL
+ entries hash where necessary below than to allocate an empty hash
+ here, but another day, another day... */
+ SVN_ERR(svn_fs_fs__dag_dir_entries(&s_entries, source, pool));
+ SVN_ERR(svn_fs_fs__dag_dir_entries(&t_entries, target, pool));
+ SVN_ERR(svn_fs_fs__dag_dir_entries(&a_entries, ancestor, pool));
+
+ fs_supports_mergeinfo = svn_fs_fs__fs_supports_mergeinfo(fs);
+
+ /* for each entry E in a_entries... */
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, a_entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *s_entry, *t_entry, *a_entry;
+ const char *name;
+ apr_ssize_t klen;
+
+ svn_pool_clear(iterpool);
+
+ name = svn__apr_hash_index_key(hi);
+ klen = svn__apr_hash_index_klen(hi);
+ a_entry = svn__apr_hash_index_val(hi);
+
+ s_entry = apr_hash_get(s_entries, name, klen);
+ t_entry = apr_hash_get(t_entries, name, klen);
+
+ /* No changes were made to this entry while the transaction was
+ in progress, so do nothing to the target. */
+ if (s_entry && svn_fs_fs__id_eq(a_entry->id, s_entry->id))
+ goto end;
+
+ /* A change was made to this entry while the transaction was in
+ process, but the transaction did not touch this entry. */
+ else if (t_entry && svn_fs_fs__id_eq(a_entry->id, t_entry->id))
+ {
+ dag_node_t *t_ent_node;
+ SVN_ERR(svn_fs_fs__dag_get_node(&t_ent_node, fs,
+ t_entry->id, iterpool));
+ if (fs_supports_mergeinfo)
+ {
+ apr_int64_t mergeinfo_start;
+ SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_start,
+ t_ent_node));
+ mergeinfo_increment -= mergeinfo_start;
+ }
+
+ if (s_entry)
+ {
+ dag_node_t *s_ent_node;
+ SVN_ERR(svn_fs_fs__dag_get_node(&s_ent_node, fs,
+ s_entry->id, iterpool));
+
+ if (fs_supports_mergeinfo)
+ {
+ apr_int64_t mergeinfo_end;
+ SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_end,
+ s_ent_node));
+ mergeinfo_increment += mergeinfo_end;
+ }
+
+ SVN_ERR(svn_fs_fs__dag_set_entry(target, name,
+ s_entry->id,
+ s_entry->kind,
+ txn_id,
+ iterpool));
+ }
+ else
+ {
+ SVN_ERR(svn_fs_fs__dag_delete(target, name, txn_id, iterpool));
+ }
+ }
+
+ /* Changes were made to this entry both within the transaction
+ and to the repository while the transaction was in progress.
+ They must be merged or declared to be in conflict. */
+ else
+ {
+ dag_node_t *s_ent_node, *t_ent_node, *a_ent_node;
+ const char *new_tpath;
+ apr_int64_t sub_mergeinfo_increment;
+
+ /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
+ double delete; if one of them is null, that's a delete versus
+ a modification. In any of these cases, flag a conflict. */
+ if (s_entry == NULL || t_entry == NULL)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* If any of the three entries is of type file, flag a conflict. */
+ if (s_entry->kind == svn_node_file
+ || t_entry->kind == svn_node_file
+ || a_entry->kind == svn_node_file)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
+ modification of ANCESTOR-ENTRY, declare a conflict. */
+ if (strcmp(svn_fs_fs__id_node_id(s_entry->id),
+ svn_fs_fs__id_node_id(a_entry->id)) != 0
+ || strcmp(svn_fs_fs__id_copy_id(s_entry->id),
+ svn_fs_fs__id_copy_id(a_entry->id)) != 0
+ || strcmp(svn_fs_fs__id_node_id(t_entry->id),
+ svn_fs_fs__id_node_id(a_entry->id)) != 0
+ || strcmp(svn_fs_fs__id_copy_id(t_entry->id),
+ svn_fs_fs__id_copy_id(a_entry->id)) != 0)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* Direct modifications were made to the directory
+ ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively
+ merge these modifications. */
+ SVN_ERR(svn_fs_fs__dag_get_node(&s_ent_node, fs,
+ s_entry->id, iterpool));
+ SVN_ERR(svn_fs_fs__dag_get_node(&t_ent_node, fs,
+ t_entry->id, iterpool));
+ SVN_ERR(svn_fs_fs__dag_get_node(&a_ent_node, fs,
+ a_entry->id, iterpool));
+ new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool);
+ SVN_ERR(merge(conflict_p, new_tpath,
+ t_ent_node, s_ent_node, a_ent_node,
+ txn_id,
+ &sub_mergeinfo_increment,
+ iterpool));
+ if (fs_supports_mergeinfo)
+ mergeinfo_increment += sub_mergeinfo_increment;
+ }
+
+ /* We've taken care of any possible implications E could have.
+ Remove it from source_entries, so it's easy later to loop
+ over all the source entries that didn't exist in
+ ancestor_entries. */
+ end:
+ apr_hash_set(s_entries, name, klen, NULL);
+ }
+
+ /* For each entry E in source but not in ancestor */
+ for (hi = apr_hash_first(pool, s_entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *s_entry, *t_entry;
+ const char *name = svn__apr_hash_index_key(hi);
+ apr_ssize_t klen = svn__apr_hash_index_klen(hi);
+ dag_node_t *s_ent_node;
+
+ svn_pool_clear(iterpool);
+
+ s_entry = svn__apr_hash_index_val(hi);
+ t_entry = apr_hash_get(t_entries, name, klen);
+
+ /* If NAME exists in TARGET, declare a conflict. */
+ if (t_entry)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ t_entry->name,
+ iterpool));
+
+ SVN_ERR(svn_fs_fs__dag_get_node(&s_ent_node, fs,
+ s_entry->id, iterpool));
+ if (fs_supports_mergeinfo)
+ {
+ apr_int64_t mergeinfo_s;
+ SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_s,
+ s_ent_node));
+ mergeinfo_increment += mergeinfo_s;
+ }
+
+ SVN_ERR(svn_fs_fs__dag_set_entry
+ (target, s_entry->name, s_entry->id, s_entry->kind,
+ txn_id, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_fs_fs__dag_update_ancestry(target, source, pool));
+
+ if (fs_supports_mergeinfo)
+ SVN_ERR(svn_fs_fs__dag_increment_mergeinfo_count(target,
+ mergeinfo_increment,
+ pool));
+
+ if (mergeinfo_increment_out)
+ *mergeinfo_increment_out = mergeinfo_increment;
+
+ return SVN_NO_ERROR;
+}
+
+/* Merge changes between an ancestor and SOURCE_NODE into
+ TXN. The ancestor is either ANCESTOR_NODE, or if
+ that is null, TXN's base node.
+
+ If the merge is successful, TXN's base will become
+ SOURCE_NODE, and its root node will have a new ID, a
+ successor of SOURCE_NODE.
+
+ If a conflict results, update *CONFLICT to the path in the txn that
+ conflicted; see the CONFLICT_P parameter of merge() for details. */
+static svn_error_t *
+merge_changes(dag_node_t *ancestor_node,
+ dag_node_t *source_node,
+ svn_fs_txn_t *txn,
+ svn_stringbuf_t *conflict,
+ apr_pool_t *pool)
+{
+ dag_node_t *txn_root_node;
+ svn_fs_t *fs = txn->fs;
+ const char *txn_id = txn->id;
+
+ SVN_ERR(svn_fs_fs__dag_txn_root(&txn_root_node, fs, txn_id, pool));
+
+ if (ancestor_node == NULL)
+ {
+ SVN_ERR(svn_fs_fs__dag_txn_base_root(&ancestor_node, fs,
+ txn_id, pool));
+ }
+
+ if (svn_fs_fs__id_eq(svn_fs_fs__dag_get_id(ancestor_node),
+ svn_fs_fs__dag_get_id(txn_root_node)))
+ {
+ /* If no changes have been made in TXN since its current base,
+ then it can't conflict with any changes since that base.
+ The caller isn't supposed to call us in that case. */
+ SVN_ERR_MALFUNCTION();
+ }
+ else
+ SVN_ERR(merge(conflict, "/", txn_root_node,
+ source_node, ancestor_node, txn_id, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__commit_txn(const char **conflict_p,
+ svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ /* How do commits work in Subversion?
+ *
+ * When you're ready to commit, here's what you have:
+ *
+ * 1. A transaction, with a mutable tree hanging off it.
+ * 2. A base revision, against which TXN_TREE was made.
+ * 3. A latest revision, which may be newer than the base rev.
+ *
+ * The problem is that if latest != base, then one can't simply
+ * attach the txn root as the root of the new revision, because that
+ * would lose all the changes between base and latest. It is also
+ * not acceptable to insist that base == latest; in a busy
+ * repository, commits happen too fast to insist that everyone keep
+ * their entire tree up-to-date at all times. Non-overlapping
+ * changes should not interfere with each other.
+ *
+ * The solution is to merge the changes between base and latest into
+ * the txn tree [see the function merge()]. The txn tree is the
+ * only one of the three trees that is mutable, so it has to be the
+ * one to adjust.
+ *
+ * You might have to adjust it more than once, if a new latest
+ * revision gets committed while you were merging in the previous
+ * one. For example:
+ *
+ * 1. Jane starts txn T, based at revision 6.
+ * 2. Someone commits (or already committed) revision 7.
+ * 3. Jane's starts merging the changes between 6 and 7 into T.
+ * 4. Meanwhile, someone commits revision 8.
+ * 5. Jane finishes the 6-->7 merge. T could now be committed
+ * against a latest revision of 7, if only that were still the
+ * latest. Unfortunately, 8 is now the latest, so...
+ * 6. Jane starts merging the changes between 7 and 8 into T.
+ * 7. Meanwhile, no one commits any new revisions. Whew.
+ * 8. Jane commits T, creating revision 9, whose tree is exactly
+ * T's tree, except immutable now.
+ *
+ * Lather, rinse, repeat.
+ */
+
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
+ svn_fs_t *fs = txn->fs;
+
+ /* Limit memory usage when the repository has a high commit rate and
+ needs to run the following while loop multiple times. The memory
+ growth without an iteration pool is very noticeable when the
+ transaction modifies a node that has 20,000 sibling nodes. */
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* Initialize output params. */
+ *new_rev = SVN_INVALID_REVNUM;
+ if (conflict_p)
+ *conflict_p = NULL;
+
+ while (1729)
+ {
+ svn_revnum_t youngish_rev;
+ svn_fs_root_t *youngish_root;
+ dag_node_t *youngish_root_node;
+
+ svn_pool_clear(iterpool);
+
+ /* Get the *current* youngest revision. We call it "youngish"
+ because new revisions might get committed after we've
+ obtained it. */
+
+ SVN_ERR(svn_fs_fs__youngest_rev(&youngish_rev, fs, iterpool));
+ SVN_ERR(svn_fs_fs__revision_root(&youngish_root, fs, youngish_rev,
+ iterpool));
+
+ /* Get the dag node for the youngest revision. Later we'll use
+ it as the SOURCE argument to a merge, and if the merge
+ succeeds, this youngest root node will become the new base
+ root for the svn txn that was the target of the merge (but
+ note that the youngest rev may have changed by then -- that's
+ why we're careful to get this root in its own bdb txn
+ here). */
+ SVN_ERR(get_root(&youngish_root_node, youngish_root, iterpool));
+
+ /* Try to merge. If the merge succeeds, the base root node of
+ TARGET's txn will become the same as youngish_root_node, so
+ any future merges will only be between that node and whatever
+ the root node of the youngest rev is by then. */
+ err = merge_changes(NULL, youngish_root_node, txn, conflict, iterpool);
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
+ *conflict_p = conflict->data;
+ goto cleanup;
+ }
+ txn->base_rev = youngish_rev;
+
+ /* Try to commit. */
+ err = svn_fs_fs__commit(new_rev, fs, txn, iterpool);
+ if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE))
+ {
+ /* Did someone else finish committing a new revision while we
+ were in mid-merge or mid-commit? If so, we'll need to
+ loop again to merge the new changes in, then try to
+ commit again. Or if that's not what happened, then just
+ return the error. */
+ svn_revnum_t youngest_rev;
+ SVN_ERR(svn_fs_fs__youngest_rev(&youngest_rev, fs, iterpool));
+ if (youngest_rev == youngish_rev)
+ goto cleanup;
+ else
+ svn_error_clear(err);
+ }
+ else if (err)
+ {
+ goto cleanup;
+ }
+ else
+ {
+ err = SVN_NO_ERROR;
+ goto cleanup;
+ }
+ }
+
+ cleanup:
+
+ svn_fs_fs__reset_txn_caches(fs);
+
+ svn_pool_destroy(iterpool);
+ return svn_error_trace(err);
+}
+
+
+/* Merge changes between two nodes into a third node. Given nodes
+ SOURCE_PATH under SOURCE_ROOT, TARGET_PATH under TARGET_ROOT and
+ ANCESTOR_PATH under ANCESTOR_ROOT, modify target to contain all the
+ changes between the ancestor and source. If there are conflicts,
+ return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a textual
+ description of the offending changes. Perform any temporary
+ allocations in POOL. */
+static svn_error_t *
+fs_merge(const char **conflict_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ svn_fs_root_t *ancestor_root,
+ const char *ancestor_path,
+ apr_pool_t *pool)
+{
+ dag_node_t *source, *ancestor;
+ svn_fs_txn_t *txn;
+ svn_error_t *err;
+ svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
+
+ if (! target_root->is_txn_root)
+ return SVN_FS__NOT_TXN(target_root);
+
+ /* Paranoia. */
+ if ((source_root->fs != ancestor_root->fs)
+ || (target_root->fs != ancestor_root->fs))
+ {
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Bad merge; ancestor, source, and target not all in same fs"));
+ }
+
+ /* ### kff todo: is there any compelling reason to get the nodes in
+ one db transaction? Right now we don't; txn_body_get_root() gets
+ one node at a time. This will probably need to change:
+
+ Jim Blandy <jimb@zwingli.cygnus.com> writes:
+ > svn_fs_merge needs to be a single transaction, to protect it against
+ > people deleting parents of nodes it's working on, etc.
+ */
+
+ /* Get the ancestor node. */
+ SVN_ERR(get_root(&ancestor, ancestor_root, pool));
+
+ /* Get the source node. */
+ SVN_ERR(get_root(&source, source_root, pool));
+
+ /* Open a txn for the txn root into which we're merging. */
+ SVN_ERR(svn_fs_fs__open_txn(&txn, ancestor_root->fs, target_root->txn,
+ pool));
+
+ /* Merge changes between ANCESTOR and SOURCE into TXN. */
+ err = merge_changes(ancestor, source, txn, conflict, pool);
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
+ *conflict_p = conflict->data;
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deltify(svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ /* Deltify is a no-op for fs_fs. */
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Directories. */
+
+/* Set *TABLE_P to a newly allocated APR hash table containing the
+ entries of the directory at PATH in ROOT. The keys of the table
+ are entry names, as byte strings, excluding the final null
+ character; the table's values are pointers to svn_fs_dirent_t
+ structures. Allocate the table and its contents in POOL. */
+static svn_error_t *
+fs_dir_entries(apr_hash_t **table_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+
+ /* Get the entries for this path in the caller's pool. */
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+ return svn_fs_fs__dag_dir_entries(table_p, node, pool);
+}
+
+/* Raise an error if PATH contains a newline because FSFS cannot handle
+ * such paths. See issue #4340. */
+static svn_error_t *
+check_newline(const char *path, apr_pool_t *pool)
+{
+ char *c = strchr(path, '\n');
+
+ if (c)
+ return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL,
+ _("Invalid control character '0x%02x' in path '%s'"),
+ (unsigned char)*c, svn_path_illegal_path_escape(path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Create a new directory named PATH in ROOT. The new directory has
+ no entries, and no properties. ROOT must be the root of a
+ transaction, not a revision. Do any necessary temporary allocation
+ in POOL. */
+static svn_error_t *
+fs_make_dir(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path;
+ dag_node_t *sub_dir;
+ const char *txn_id = root->txn;
+
+ SVN_ERR(check_newline(path, pool));
+
+ path = svn_fs__canonicalize_abspath(path, pool);
+ SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
+ txn_id, pool));
+
+ /* Check (recursively) to see if some lock is 'reserving' a path at
+ that location, or even some child-path; if so, check that we can
+ use it. */
+ if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_fs__allow_locked_operation(path, root->fs, TRUE, FALSE,
+ pool));
+
+ /* If there's already a sub-directory by that name, complain. This
+ also catches the case of trying to make a subdirectory named `/'. */
+ if (parent_path->node)
+ return SVN_FS__ALREADY_EXISTS(root, path);
+
+ /* Create the subdirectory. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path, pool));
+ SVN_ERR(svn_fs_fs__dag_make_dir(&sub_dir,
+ parent_path->parent->node,
+ parent_path_path(parent_path->parent,
+ pool),
+ parent_path->entry,
+ txn_id,
+ pool));
+
+ /* Add this directory to the path cache. */
+ SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, pool),
+ sub_dir, pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path, svn_fs_fs__dag_get_id(sub_dir),
+ svn_fs_path_change_add, FALSE, FALSE, svn_node_dir,
+ SVN_INVALID_REVNUM, NULL, pool);
+}
+
+
+/* Delete the node at PATH under ROOT. ROOT must be a transaction
+ root. Perform temporary allocations in POOL. */
+static svn_error_t *
+fs_delete_node(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path;
+ const char *txn_id = root->txn;
+ apr_int64_t mergeinfo_count = 0;
+ svn_node_kind_t kind;
+
+ if (! root->is_txn_root)
+ return SVN_FS__NOT_TXN(root);
+
+ path = svn_fs__canonicalize_abspath(path, pool);
+ SVN_ERR(open_path(&parent_path, root, path, 0, txn_id, pool));
+ kind = svn_fs_fs__dag_node_kind(parent_path->node);
+
+ /* We can't remove the root of the filesystem. */
+ if (! parent_path->parent)
+ return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL,
+ _("The root directory cannot be deleted"));
+
+ /* Check to see if path (or any child thereof) is locked; if so,
+ check that we can use the existing lock(s). */
+ if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_fs__allow_locked_operation(path, root->fs, TRUE, FALSE,
+ pool));
+
+ /* Make the parent directory mutable, and do the deletion. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path, pool));
+ if (svn_fs_fs__fs_supports_mergeinfo(root->fs))
+ SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_count,
+ parent_path->node));
+ SVN_ERR(svn_fs_fs__dag_delete(parent_path->parent->node,
+ parent_path->entry,
+ txn_id, pool));
+
+ /* Remove this node and any children from the path cache. */
+ SVN_ERR(dag_node_cache_invalidate(root, parent_path_path(parent_path, pool),
+ pool));
+
+ /* Update mergeinfo counts for parents */
+ if (mergeinfo_count > 0)
+ SVN_ERR(increment_mergeinfo_up_tree(parent_path->parent,
+ -mergeinfo_count,
+ pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path,
+ svn_fs_fs__dag_get_id(parent_path->node),
+ svn_fs_path_change_delete, FALSE, FALSE, kind,
+ SVN_INVALID_REVNUM, NULL, pool);
+}
+
+
+/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
+ Use POOL for temporary allocation only.
+ Note: this code is duplicated between libsvn_fs_fs and libsvn_fs_base. */
+static svn_error_t *
+fs_same_p(svn_boolean_t *same_p,
+ svn_fs_t *fs1,
+ svn_fs_t *fs2,
+ apr_pool_t *pool)
+{
+ *same_p = ! strcmp(fs1->uuid, fs2->uuid);
+ return SVN_NO_ERROR;
+}
+
+/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
+ TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in
+ the copies table. Perform temporary allocations in POOL. */
+static svn_error_t *
+copy_helper(svn_fs_root_t *from_root,
+ const char *from_path,
+ svn_fs_root_t *to_root,
+ const char *to_path,
+ svn_boolean_t preserve_history,
+ apr_pool_t *pool)
+{
+ dag_node_t *from_node;
+ parent_path_t *to_parent_path;
+ const char *txn_id = to_root->txn;
+ svn_boolean_t same_p;
+
+ /* Use an error check, not an assert, because even the caller cannot
+ guarantee that a filesystem's UUID has not changed "on the fly". */
+ SVN_ERR(fs_same_p(&same_p, from_root->fs, to_root->fs, pool));
+ if (! same_p)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot copy between two different filesystems ('%s' and '%s')"),
+ from_root->fs->path, to_root->fs->path);
+
+ if (from_root->is_txn_root)
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Copy from mutable tree not currently supported"));
+
+ /* Get the NODE for FROM_PATH in FROM_ROOT.*/
+ SVN_ERR(get_dag(&from_node, from_root, from_path, TRUE, pool));
+
+ /* Build up the parent path from TO_PATH in TO_ROOT. If the last
+ component does not exist, it's not that big a deal. We'll just
+ make one there. */
+ SVN_ERR(open_path(&to_parent_path, to_root, to_path,
+ open_path_last_optional, txn_id, pool));
+
+ /* Check to see if path (or any child thereof) is locked; if so,
+ check that we can use the existing lock(s). */
+ if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_fs__allow_locked_operation(to_path, to_root->fs,
+ TRUE, FALSE, pool));
+
+ /* If the destination node already exists as the same node as the
+ source (in other words, this operation would result in nothing
+ happening at all), just do nothing an return successfully,
+ proud that you saved yourself from a tiresome task. */
+ if (to_parent_path->node &&
+ svn_fs_fs__id_eq(svn_fs_fs__dag_get_id(from_node),
+ svn_fs_fs__dag_get_id(to_parent_path->node)))
+ return SVN_NO_ERROR;
+
+ if (! from_root->is_txn_root)
+ {
+ svn_fs_path_change_kind_t kind;
+ dag_node_t *new_node;
+ const char *from_canonpath;
+ apr_int64_t mergeinfo_start;
+ apr_int64_t mergeinfo_end;
+
+ /* If TO_PATH already existed prior to the copy, note that this
+ operation is a replacement, not an addition. */
+ if (to_parent_path->node)
+ {
+ kind = svn_fs_path_change_replace;
+ if (svn_fs_fs__fs_supports_mergeinfo(to_root->fs))
+ SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_start,
+ to_parent_path->node));
+ }
+ else
+ {
+ kind = svn_fs_path_change_add;
+ mergeinfo_start = 0;
+ }
+
+ if (svn_fs_fs__fs_supports_mergeinfo(to_root->fs))
+ SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_end,
+ from_node));
+
+ /* Make sure the target node's parents are mutable. */
+ SVN_ERR(make_path_mutable(to_root, to_parent_path->parent,
+ to_path, pool));
+
+ /* Canonicalize the copyfrom path. */
+ from_canonpath = svn_fs__canonicalize_abspath(from_path, pool);
+
+ SVN_ERR(svn_fs_fs__dag_copy(to_parent_path->parent->node,
+ to_parent_path->entry,
+ from_node,
+ preserve_history,
+ from_root->rev,
+ from_canonpath,
+ txn_id, pool));
+
+ if (kind == svn_fs_path_change_replace)
+ SVN_ERR(dag_node_cache_invalidate(to_root,
+ parent_path_path(to_parent_path,
+ pool), pool));
+
+ if (svn_fs_fs__fs_supports_mergeinfo(to_root->fs)
+ && mergeinfo_start != mergeinfo_end)
+ SVN_ERR(increment_mergeinfo_up_tree(to_parent_path->parent,
+ mergeinfo_end - mergeinfo_start,
+ pool));
+
+ /* Make a record of this modification in the changes table. */
+ SVN_ERR(get_dag(&new_node, to_root, to_path, TRUE, pool));
+ SVN_ERR(add_change(to_root->fs, txn_id, to_path,
+ svn_fs_fs__dag_get_id(new_node), kind, FALSE, FALSE,
+ svn_fs_fs__dag_node_kind(from_node),
+ from_root->rev, from_canonpath, pool));
+ }
+ else
+ {
+ /* See IZ Issue #436 */
+ /* Copying from transaction roots not currently available.
+
+ ### cmpilato todo someday: make this not so. :-) Note that
+ when copying from mutable trees, you have to make sure that
+ you aren't creating a cyclic graph filesystem, and a simple
+ referencing operation won't cut it. Currently, we should not
+ be able to reach this clause, and the interface reports that
+ this only works from immutable trees anyway, but JimB has
+ stated that this requirement need not be necessary in the
+ future. */
+
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
+ If FROM_PATH is a directory, copy it recursively. Temporary
+ allocations are from POOL.*/
+static svn_error_t *
+fs_copy(svn_fs_root_t *from_root,
+ const char *from_path,
+ svn_fs_root_t *to_root,
+ const char *to_path,
+ apr_pool_t *pool)
+{
+ SVN_ERR(check_newline(to_path, pool));
+
+ return svn_error_trace(copy_helper(from_root,
+ svn_fs__canonicalize_abspath(from_path,
+ pool),
+ to_root,
+ svn_fs__canonicalize_abspath(to_path,
+ pool),
+ TRUE, pool));
+}
+
+
+/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
+ If FROM_PATH is a directory, copy it recursively. No history is
+ preserved. Temporary allocations are from POOL. */
+static svn_error_t *
+fs_revision_link(svn_fs_root_t *from_root,
+ svn_fs_root_t *to_root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ if (! to_root->is_txn_root)
+ return SVN_FS__NOT_TXN(to_root);
+
+ path = svn_fs__canonicalize_abspath(path, pool);
+ return svn_error_trace(copy_helper(from_root, path, to_root, path,
+ FALSE, pool));
+}
+
+
+/* Discover the copy ancestry of PATH under ROOT. Return a relevant
+ ancestor/revision combination in *PATH_P and *REV_P. Temporary
+ allocations are in POOL. */
+static svn_error_t *
+fs_copied_from(svn_revnum_t *rev_p,
+ const char **path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+ const char *copyfrom_path, *copyfrom_str = NULL;
+ svn_revnum_t copyfrom_rev;
+ char *str, *buf;
+
+ /* Check to see if there is a cached version of this copyfrom
+ entry. */
+ if (! root->is_txn_root) {
+ fs_rev_root_data_t *frd = root->fsap_data;
+ copyfrom_str = svn_hash_gets(frd->copyfrom_cache, path);
+ }
+
+ if (copyfrom_str)
+ {
+ if (*copyfrom_str == 0)
+ {
+ /* We have a cached entry that says there is no copyfrom
+ here. */
+ copyfrom_rev = SVN_INVALID_REVNUM;
+ copyfrom_path = NULL;
+ }
+ else
+ {
+ /* Parse the copyfrom string for our cached entry. */
+ buf = apr_pstrdup(pool, copyfrom_str);
+ str = svn_cstring_tokenize(" ", &buf);
+ copyfrom_rev = SVN_STR_TO_REV(str);
+ copyfrom_path = buf;
+ }
+ }
+ else
+ {
+ /* There is no cached entry, look it up the old-fashioned
+ way. */
+ SVN_ERR(get_dag(&node, root, path, TRUE, pool));
+ SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(&copyfrom_rev, node));
+ SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(&copyfrom_path, node));
+ }
+
+ *rev_p = copyfrom_rev;
+ *path_p = copyfrom_path;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Files. */
+
+/* Create the empty file PATH under ROOT. Temporary allocations are
+ in POOL. */
+static svn_error_t *
+fs_make_file(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path;
+ dag_node_t *child;
+ const char *txn_id = root->txn;
+
+ SVN_ERR(check_newline(path, pool));
+
+ path = svn_fs__canonicalize_abspath(path, pool);
+ SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
+ txn_id, pool));
+
+ /* If there's already a file by that name, complain.
+ This also catches the case of trying to make a file named `/'. */
+ if (parent_path->node)
+ return SVN_FS__ALREADY_EXISTS(root, path);
+
+ /* Check (non-recursively) to see if path is locked; if so, check
+ that we can use it. */
+ if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_fs__allow_locked_operation(path, root->fs, FALSE, FALSE,
+ pool));
+
+ /* Create the file. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path, pool));
+ SVN_ERR(svn_fs_fs__dag_make_file(&child,
+ parent_path->parent->node,
+ parent_path_path(parent_path->parent,
+ pool),
+ parent_path->entry,
+ txn_id,
+ pool));
+
+ /* Add this file to the path cache. */
+ SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, pool), child,
+ pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path, svn_fs_fs__dag_get_id(child),
+ svn_fs_path_change_add, TRUE, FALSE, svn_node_file,
+ SVN_INVALID_REVNUM, NULL, pool);
+}
+
+
+/* Set *LENGTH_P to the size of the file PATH under ROOT. Temporary
+ allocations are in POOL. */
+static svn_error_t *
+fs_file_length(svn_filesize_t *length_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ dag_node_t *file;
+
+ /* First create a dag_node_t from the root/path pair. */
+ SVN_ERR(get_dag(&file, root, path, FALSE, pool));
+
+ /* Now fetch its length */
+ return svn_fs_fs__dag_file_length(length_p, file, pool);
+}
+
+
+/* Set *CHECKSUM to the checksum of type KIND for PATH under ROOT, or
+ NULL if that information isn't available. Temporary allocations
+ are from POOL. */
+static svn_error_t *
+fs_file_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t kind,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ dag_node_t *file;
+
+ SVN_ERR(get_dag(&file, root, path, FALSE, pool));
+ return svn_fs_fs__dag_file_checksum(checksum, file, kind, pool);
+}
+
+
+/* --- Machinery for svn_fs_file_contents() --- */
+
+/* Set *CONTENTS to a readable stream that will return the contents of
+ PATH under ROOT. The stream is allocated in POOL. */
+static svn_error_t *
+fs_file_contents(svn_stream_t **contents,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+ svn_stream_t *file_stream;
+
+ /* First create a dag_node_t from the root/path pair. */
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+
+ /* Then create a readable stream from the dag_node_t. */
+ SVN_ERR(svn_fs_fs__dag_get_contents(&file_stream, node, pool));
+
+ *contents = file_stream;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_file_contents() --- */
+
+
+/* --- Machinery for svn_fs_try_process_file_contents() --- */
+
+static svn_error_t *
+fs_try_process_file_contents(svn_boolean_t *success,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+
+ return svn_fs_fs__dag_try_process_file_contents(success, node,
+ processor, baton, pool);
+}
+
+/* --- End machinery for svn_fs_try_process_file_contents() --- */
+
+
+/* --- Machinery for svn_fs_apply_textdelta() --- */
+
+
+/* Local baton type for all the helper functions below. */
+typedef struct txdelta_baton_t
+{
+ /* This is the custom-built window consumer given to us by the delta
+ library; it uniquely knows how to read data from our designated
+ "source" stream, interpret the window, and write data to our
+ designated "target" stream (in this case, our repos file.) */
+ svn_txdelta_window_handler_t interpreter;
+ void *interpreter_baton;
+
+ /* The original file info */
+ svn_fs_root_t *root;
+ const char *path;
+
+ /* Derived from the file info */
+ dag_node_t *node;
+
+ svn_stream_t *source_stream;
+ svn_stream_t *target_stream;
+ svn_stream_t *string_stream;
+ svn_stringbuf_t *target_string;
+
+ /* MD5 digest for the base text against which a delta is to be
+ applied, and for the resultant fulltext, respectively. Either or
+ both may be null, in which case ignored. */
+ svn_checksum_t *base_checksum;
+ svn_checksum_t *result_checksum;
+
+ /* Pool used by db txns */
+ apr_pool_t *pool;
+
+} txdelta_baton_t;
+
+
+/* ### see comment in window_consumer() regarding this function. */
+
+/* Helper function of generic type `svn_write_fn_t'. Implements a
+ writable stream which appends to an svn_stringbuf_t. */
+static svn_error_t *
+write_to_string(void *baton, const char *data, apr_size_t *len)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+ svn_stringbuf_appendbytes(tb->target_string, data, *len);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* The main window handler returned by svn_fs_apply_textdelta. */
+static svn_error_t *
+window_consumer(svn_txdelta_window_t *window, void *baton)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+
+ /* Send the window right through to the custom window interpreter.
+ In theory, the interpreter will then write more data to
+ cb->target_string. */
+ SVN_ERR(tb->interpreter(window, tb->interpreter_baton));
+
+ /* ### the write_to_string() callback for the txdelta's output stream
+ ### should be doing all the flush determination logic, not here.
+ ### in a drastic case, a window could generate a LOT more than the
+ ### maximum buffer size. we want to flush to the underlying target
+ ### stream much sooner (e.g. also in a streamy fashion). also, by
+ ### moving this logic inside the stream, the stream becomes nice
+ ### and encapsulated: it holds all the logic about buffering and
+ ### flushing.
+ ###
+ ### further: I believe the buffering should be removed from tree.c
+ ### the buffering should go into the target_stream itself, which
+ ### is defined by reps-string.c. Specifically, I think the
+ ### rep_write_contents() function will handle the buffering and
+ ### the spill to the underlying DB. by locating it there, then
+ ### anybody who gets a writable stream for FS content can take
+ ### advantage of the buffering capability. this will be important
+ ### when we export an FS API function for writing a fulltext into
+ ### the FS, rather than forcing that fulltext thru apply_textdelta.
+ */
+
+ /* Check to see if we need to purge the portion of the contents that
+ have been written thus far. */
+ if ((! window) || (tb->target_string->len > WRITE_BUFFER_SIZE))
+ {
+ apr_size_t len = tb->target_string->len;
+ SVN_ERR(svn_stream_write(tb->target_stream,
+ tb->target_string->data,
+ &len));
+ svn_stringbuf_setempty(tb->target_string);
+ }
+
+ /* Is the window NULL? If so, we're done. */
+ if (! window)
+ {
+ /* Close the internal-use stream. ### This used to be inside of
+ txn_body_fulltext_finalize_edits(), but that invoked a nested
+ Berkeley DB transaction -- scandalous! */
+ SVN_ERR(svn_stream_close(tb->target_stream));
+
+ SVN_ERR(svn_fs_fs__dag_finalize_edits(tb->node, tb->result_checksum,
+ tb->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper function for fs_apply_textdelta. BATON is of type
+ txdelta_baton_t. */
+static svn_error_t *
+apply_textdelta(void *baton, apr_pool_t *pool)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+ parent_path_t *parent_path;
+ const char *txn_id = tb->root->txn;
+
+ /* Call open_path with no flags, as we want this to return an error
+ if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id, pool));
+
+ /* Check (non-recursively) to see if path is locked; if so, check
+ that we can use it. */
+ if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_fs__allow_locked_operation(tb->path, tb->root->fs,
+ FALSE, FALSE, pool));
+
+ /* Now, make sure this path is mutable. */
+ SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, pool));
+ tb->node = parent_path->node;
+
+ if (tb->base_checksum)
+ {
+ svn_checksum_t *checksum;
+
+ /* Until we finalize the node, its data_key points to the old
+ contents, in other words, the base text. */
+ SVN_ERR(svn_fs_fs__dag_file_checksum(&checksum, tb->node,
+ tb->base_checksum->kind, pool));
+ if (!svn_checksum_match(tb->base_checksum, checksum))
+ return svn_checksum_mismatch_err(tb->base_checksum, checksum, pool,
+ _("Base checksum mismatch on '%s'"),
+ tb->path);
+ }
+
+ /* Make a readable "source" stream out of the current contents of
+ ROOT/PATH; obviously, this must done in the context of a db_txn.
+ The stream is returned in tb->source_stream. */
+ SVN_ERR(svn_fs_fs__dag_get_contents(&(tb->source_stream),
+ tb->node, tb->pool));
+
+ /* Make a writable "target" stream */
+ SVN_ERR(svn_fs_fs__dag_get_edit_stream(&(tb->target_stream), tb->node,
+ tb->pool));
+
+ /* Make a writable "string" stream which writes data to
+ tb->target_string. */
+ tb->target_string = svn_stringbuf_create_empty(tb->pool);
+ tb->string_stream = svn_stream_create(tb, tb->pool);
+ svn_stream_set_write(tb->string_stream, write_to_string);
+
+ /* Now, create a custom window handler that uses our two streams. */
+ svn_txdelta_apply(tb->source_stream,
+ tb->string_stream,
+ NULL,
+ tb->path,
+ tb->pool,
+ &(tb->interpreter),
+ &(tb->interpreter_baton));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(tb->root->fs, txn_id, tb->path,
+ svn_fs_fs__dag_get_id(tb->node),
+ svn_fs_path_change_modify, TRUE, FALSE, svn_node_file,
+ SVN_INVALID_REVNUM, NULL, pool);
+}
+
+
+/* Set *CONTENTS_P and *CONTENTS_BATON_P to a window handler and baton
+ that will accept text delta windows to modify the contents of PATH
+ under ROOT. Allocations are in POOL. */
+static svn_error_t *
+fs_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
+ void **contents_baton_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_checksum_t *base_checksum,
+ svn_checksum_t *result_checksum,
+ apr_pool_t *pool)
+{
+ txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
+
+ tb->root = root;
+ tb->path = svn_fs__canonicalize_abspath(path, pool);
+ tb->pool = pool;
+ tb->base_checksum = svn_checksum_dup(base_checksum, pool);
+ tb->result_checksum = svn_checksum_dup(result_checksum, pool);
+
+ SVN_ERR(apply_textdelta(tb, pool));
+
+ *contents_p = window_consumer;
+ *contents_baton_p = tb;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_apply_textdelta() --- */
+
+/* --- Machinery for svn_fs_apply_text() --- */
+
+/* Baton for svn_fs_apply_text(). */
+struct text_baton_t
+{
+ /* The original file info */
+ svn_fs_root_t *root;
+ const char *path;
+
+ /* Derived from the file info */
+ dag_node_t *node;
+
+ /* The returned stream that will accept the file's new contents. */
+ svn_stream_t *stream;
+
+ /* The actual fs stream that the returned stream will write to. */
+ svn_stream_t *file_stream;
+
+ /* MD5 digest for the final fulltext written to the file. May
+ be null, in which case ignored. */
+ svn_checksum_t *result_checksum;
+
+ /* Pool used by db txns */
+ apr_pool_t *pool;
+};
+
+
+/* A wrapper around svn_fs_fs__dag_finalize_edits, but for
+ * fulltext data, not text deltas. Closes BATON->file_stream.
+ *
+ * Note: If you're confused about how this function relates to another
+ * of similar name, think of it this way:
+ *
+ * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
+ * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
+ */
+
+/* Write function for the publically returned stream. */
+static svn_error_t *
+text_stream_writer(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct text_baton_t *tb = baton;
+
+ /* Psst, here's some data. Pass it on to the -real- file stream. */
+ return svn_stream_write(tb->file_stream, data, len);
+}
+
+/* Close function for the publically returned stream. */
+static svn_error_t *
+text_stream_closer(void *baton)
+{
+ struct text_baton_t *tb = baton;
+
+ /* Close the internal-use stream. ### This used to be inside of
+ txn_body_fulltext_finalize_edits(), but that invoked a nested
+ Berkeley DB transaction -- scandalous! */
+ SVN_ERR(svn_stream_close(tb->file_stream));
+
+ /* Need to tell fs that we're done sending text */
+ return svn_fs_fs__dag_finalize_edits(tb->node, tb->result_checksum,
+ tb->pool);
+}
+
+
+/* Helper function for fs_apply_text. BATON is of type
+ text_baton_t. */
+static svn_error_t *
+apply_text(void *baton, apr_pool_t *pool)
+{
+ struct text_baton_t *tb = baton;
+ parent_path_t *parent_path;
+ const char *txn_id = tb->root->txn;
+
+ /* Call open_path with no flags, as we want this to return an error
+ if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id, pool));
+
+ /* Check (non-recursively) to see if path is locked; if so, check
+ that we can use it. */
+ if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_fs__allow_locked_operation(tb->path, tb->root->fs,
+ FALSE, FALSE, pool));
+
+ /* Now, make sure this path is mutable. */
+ SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, pool));
+ tb->node = parent_path->node;
+
+ /* Make a writable stream for replacing the file's text. */
+ SVN_ERR(svn_fs_fs__dag_get_edit_stream(&(tb->file_stream), tb->node,
+ tb->pool));
+
+ /* Create a 'returnable' stream which writes to the file_stream. */
+ tb->stream = svn_stream_create(tb, tb->pool);
+ svn_stream_set_write(tb->stream, text_stream_writer);
+ svn_stream_set_close(tb->stream, text_stream_closer);
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(tb->root->fs, txn_id, tb->path,
+ svn_fs_fs__dag_get_id(tb->node),
+ svn_fs_path_change_modify, TRUE, FALSE, svn_node_file,
+ SVN_INVALID_REVNUM, NULL, pool);
+}
+
+
+/* Return a writable stream that will set the contents of PATH under
+ ROOT. RESULT_CHECKSUM is the MD5 checksum of the final result.
+ Temporary allocations are in POOL. */
+static svn_error_t *
+fs_apply_text(svn_stream_t **contents_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_checksum_t *result_checksum,
+ apr_pool_t *pool)
+{
+ struct text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
+
+ tb->root = root;
+ tb->path = svn_fs__canonicalize_abspath(path, pool);
+ tb->pool = pool;
+ tb->result_checksum = svn_checksum_dup(result_checksum, pool);
+
+ SVN_ERR(apply_text(tb, pool));
+
+ *contents_p = tb->stream;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_apply_text() --- */
+
+
+/* Check if the contents of PATH1 under ROOT1 are different from the
+ contents of PATH2 under ROOT2. If they are different set
+ *CHANGED_P to TRUE, otherwise set it to FALSE. */
+static svn_error_t *
+fs_contents_changed(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ dag_node_t *node1, *node2;
+
+ /* Check that roots are in the same fs. */
+ if (root1->fs != root2->fs)
+ return svn_error_create
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Cannot compare file contents between two different filesystems"));
+
+ /* Check that both paths are files. */
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_fs_fs__check_path(&kind, root1, path1, pool));
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
+
+ SVN_ERR(svn_fs_fs__check_path(&kind, root2, path2, pool));
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
+ }
+
+ SVN_ERR(get_dag(&node1, root1, path1, TRUE, pool));
+ SVN_ERR(get_dag(&node2, root2, path2, TRUE, pool));
+ return svn_fs_fs__dag_things_different(NULL, changed_p,
+ node1, node2);
+}
+
+
+
+/* Public interface to computing file text deltas. */
+
+static svn_error_t *
+fs_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ apr_pool_t *pool)
+{
+ dag_node_t *source_node, *target_node;
+
+ if (source_root && source_path)
+ SVN_ERR(get_dag(&source_node, source_root, source_path, TRUE, pool));
+ else
+ source_node = NULL;
+ SVN_ERR(get_dag(&target_node, target_root, target_path, TRUE, pool));
+
+ /* Create a delta stream that turns the source into the target. */
+ return svn_fs_fs__dag_get_file_delta_stream(stream_p, source_node,
+ target_node, pool);
+}
+
+
+
+/* Finding Changes */
+
+/* Set *CHANGED_PATHS_P to a newly allocated hash containing
+ descriptions of the paths changed under ROOT. The hash is keyed
+ with const char * paths and has svn_fs_path_change2_t * values. Use
+ POOL for all allocations. */
+static svn_error_t *
+fs_paths_changed(apr_hash_t **changed_paths_p,
+ svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ if (root->is_txn_root)
+ return svn_fs_fs__txn_changes_fetch(changed_paths_p, root->fs, root->txn,
+ pool);
+ else
+ {
+ fs_rev_root_data_t *frd = root->fsap_data;
+ return svn_fs_fs__paths_changed(changed_paths_p, root->fs, root->rev,
+ frd->copyfrom_cache, pool);
+ }
+}
+
+
+
+/* Our coolio opaque history object. */
+typedef struct fs_history_data_t
+{
+ /* filesystem object */
+ svn_fs_t *fs;
+
+ /* path and revision of historical location */
+ const char *path;
+ svn_revnum_t revision;
+
+ /* internal-use hints about where to resume the history search. */
+ const char *path_hint;
+ svn_revnum_t rev_hint;
+
+ /* FALSE until the first call to svn_fs_history_prev(). */
+ svn_boolean_t is_interesting;
+} fs_history_data_t;
+
+static svn_fs_history_t *
+assemble_history(svn_fs_t *fs,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t is_interesting,
+ const char *path_hint,
+ svn_revnum_t rev_hint,
+ apr_pool_t *pool);
+
+
+/* Set *HISTORY_P to an opaque node history object which represents
+ PATH under ROOT. ROOT must be a revision root. Use POOL for all
+ allocations. */
+static svn_error_t *
+fs_node_history(svn_fs_history_t **history_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ /* We require a revision root. */
+ if (root->is_txn_root)
+ return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
+
+ /* And we require that the path exist in the root. */
+ SVN_ERR(svn_fs_fs__check_path(&kind, root, path, pool));
+ if (kind == svn_node_none)
+ return SVN_FS__NOT_FOUND(root, path);
+
+ /* Okay, all seems well. Build our history object and return it. */
+ *history_p = assemble_history(root->fs,
+ svn_fs__canonicalize_abspath(path, pool),
+ root->rev, FALSE, NULL,
+ SVN_INVALID_REVNUM, pool);
+ return SVN_NO_ERROR;
+}
+
+/* Find the youngest copyroot for path PARENT_PATH or its parents in
+ filesystem FS, and store the copyroot in *REV_P and *PATH_P.
+ Perform all allocations in POOL. */
+static svn_error_t *
+find_youngest_copyroot(svn_revnum_t *rev_p,
+ const char **path_p,
+ svn_fs_t *fs,
+ parent_path_t *parent_path,
+ apr_pool_t *pool)
+{
+ svn_revnum_t rev_mine;
+ svn_revnum_t rev_parent = SVN_INVALID_REVNUM;
+ const char *path_mine;
+ const char *path_parent = NULL;
+
+ /* First find our parent's youngest copyroot. */
+ if (parent_path->parent)
+ SVN_ERR(find_youngest_copyroot(&rev_parent, &path_parent, fs,
+ parent_path->parent, pool));
+
+ /* Find our copyroot. */
+ SVN_ERR(svn_fs_fs__dag_get_copyroot(&rev_mine, &path_mine,
+ parent_path->node));
+
+ /* If a parent and child were copied to in the same revision, prefer
+ the child copy target, since it is the copy relevant to the
+ history of the child. */
+ if (rev_mine >= rev_parent)
+ {
+ *rev_p = rev_mine;
+ *path_p = path_mine;
+ }
+ else
+ {
+ *rev_p = rev_parent;
+ *path_p = path_parent;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *fs_closest_copy(svn_fs_root_t **root_p,
+ const char **path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = root->fs;
+ parent_path_t *parent_path, *copy_dst_parent_path;
+ svn_revnum_t copy_dst_rev, created_rev;
+ const char *copy_dst_path;
+ svn_fs_root_t *copy_dst_root;
+ dag_node_t *copy_dst_node;
+ svn_node_kind_t kind;
+
+ /* Initialize return values. */
+ *root_p = NULL;
+ *path_p = NULL;
+
+ path = svn_fs__canonicalize_abspath(path, pool);
+ SVN_ERR(open_path(&parent_path, root, path, 0, NULL, pool));
+
+ /* Find the youngest copyroot in the path of this node-rev, which
+ will indicate the target of the innermost copy affecting the
+ node-rev. */
+ SVN_ERR(find_youngest_copyroot(&copy_dst_rev, &copy_dst_path,
+ fs, parent_path, pool));
+ if (copy_dst_rev == 0) /* There are no copies affecting this node-rev. */
+ return SVN_NO_ERROR;
+
+ /* It is possible that this node was created from scratch at some
+ revision between COPY_DST_REV and REV. Make sure that PATH
+ exists as of COPY_DST_REV and is related to this node-rev. */
+ SVN_ERR(svn_fs_fs__revision_root(&copy_dst_root, fs, copy_dst_rev, pool));
+ SVN_ERR(svn_fs_fs__check_path(&kind, copy_dst_root, path, pool));
+ if (kind == svn_node_none)
+ return SVN_NO_ERROR;
+ SVN_ERR(open_path(&copy_dst_parent_path, copy_dst_root, path,
+ open_path_node_only, NULL, pool));
+ copy_dst_node = copy_dst_parent_path->node;
+ if (! svn_fs_fs__id_check_related(svn_fs_fs__dag_get_id(copy_dst_node),
+ svn_fs_fs__dag_get_id(parent_path->node)))
+ return SVN_NO_ERROR;
+
+ /* One final check must be done here. If you copy a directory and
+ create a new entity somewhere beneath that directory in the same
+ txn, then we can't claim that the copy affected the new entity.
+ For example, if you do:
+
+ copy dir1 dir2
+ create dir2/new-thing
+ commit
+
+ then dir2/new-thing was not affected by the copy of dir1 to dir2.
+ We detect this situation by asking if PATH@COPY_DST_REV's
+ created-rev is COPY_DST_REV, and that node-revision has no
+ predecessors, then there is no relevant closest copy.
+ */
+ SVN_ERR(svn_fs_fs__dag_get_revision(&created_rev, copy_dst_node, pool));
+ if (created_rev == copy_dst_rev)
+ {
+ const svn_fs_id_t *pred;
+ SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred, copy_dst_node));
+ if (! pred)
+ return SVN_NO_ERROR;
+ }
+
+ /* The copy destination checks out. Return it. */
+ *root_p = copy_dst_root;
+ *path_p = copy_dst_path;
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *PREV_PATH and *PREV_REV to the path and revision which
+ represent the location at which PATH in FS was located immediately
+ prior to REVISION iff there was a copy operation (to PATH or one of
+ its parent directories) between that previous location and
+ PATH@REVISION.
+
+ If there was no such copy operation in that portion of PATH's
+ history, set *PREV_PATH to NULL and *PREV_REV to SVN_INVALID_REVNUM. */
+static svn_error_t *
+prev_location(const char **prev_path,
+ svn_revnum_t *prev_rev,
+ svn_fs_t *fs,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *copy_path, *copy_src_path, *remainder_path;
+ svn_fs_root_t *copy_root;
+ svn_revnum_t copy_src_rev;
+
+ /* Ask about the most recent copy which affected PATH@REVISION. If
+ there was no such copy, we're done. */
+ SVN_ERR(fs_closest_copy(&copy_root, &copy_path, root, path, pool));
+ if (! copy_root)
+ {
+ *prev_rev = SVN_INVALID_REVNUM;
+ *prev_path = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Ultimately, it's not the path of the closest copy's source that
+ we care about -- it's our own path's location in the copy source
+ revision. So we'll tack the relative path that expresses the
+ difference between the copy destination and our path in the copy
+ revision onto the copy source path to determine this information.
+
+ In other words, if our path is "/branches/my-branch/foo/bar", and
+ we know that the closest relevant copy was a copy of "/trunk" to
+ "/branches/my-branch", then that relative path under the copy
+ destination is "/foo/bar". Tacking that onto the copy source
+ path tells us that our path was located at "/trunk/foo/bar"
+ before the copy.
+ */
+ SVN_ERR(fs_copied_from(&copy_src_rev, &copy_src_path,
+ copy_root, copy_path, pool));
+ remainder_path = svn_fspath__skip_ancestor(copy_path, path);
+ *prev_path = svn_fspath__join(copy_src_path, remainder_path, pool);
+ *prev_rev = copy_src_rev;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+fs_node_origin_rev(svn_revnum_t *revision,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = root->fs;
+ const svn_fs_id_t *given_noderev_id, *cached_origin_id;
+ const char *node_id, *dash;
+
+ path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* Check the cache first. */
+ SVN_ERR(svn_fs_fs__node_id(&given_noderev_id, root, path, pool));
+ node_id = svn_fs_fs__id_node_id(given_noderev_id);
+
+ /* Is it a brand new uncommitted node? */
+ if (node_id[0] == '_')
+ {
+ *revision = SVN_INVALID_REVNUM;
+ return SVN_NO_ERROR;
+ }
+
+ /* Maybe this is a new-style node ID that just has the revision
+ sitting right in it. */
+ dash = strchr(node_id, '-');
+ if (dash && *(dash+1))
+ {
+ *revision = SVN_STR_TO_REV(dash + 1);
+ return SVN_NO_ERROR;
+ }
+
+ /* OK, it's an old-style ID? Maybe it's cached. */
+ SVN_ERR(svn_fs_fs__get_node_origin(&cached_origin_id,
+ fs,
+ node_id,
+ pool));
+ if (cached_origin_id != NULL)
+ {
+ *revision = svn_fs_fs__id_rev(cached_origin_id);
+ return SVN_NO_ERROR;
+ }
+
+ {
+ /* Ah well, the answer isn't in the ID itself or in the cache.
+ Let's actually calculate it, then. */
+ svn_fs_root_t *curroot = root;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_pool_t *predidpool = svn_pool_create(pool);
+ svn_stringbuf_t *lastpath = svn_stringbuf_create(path, pool);
+ svn_revnum_t lastrev = SVN_INVALID_REVNUM;
+ dag_node_t *node;
+ const svn_fs_id_t *pred_id;
+
+ /* Walk the closest-copy chain back to the first copy in our history.
+
+ NOTE: We merely *assume* that this is faster than walking the
+ predecessor chain, because we *assume* that copies of parent
+ directories happen less often than modifications to a given item. */
+ while (1)
+ {
+ svn_revnum_t currev;
+ const char *curpath = lastpath->data;
+
+ svn_pool_clear(subpool);
+
+ /* Get a root pointing to LASTREV. (The first time around,
+ LASTREV is invalid, but that's cool because CURROOT is
+ already initialized.) */
+ if (SVN_IS_VALID_REVNUM(lastrev))
+ SVN_ERR(svn_fs_fs__revision_root(&curroot, fs, lastrev, subpool));
+
+ /* Find the previous location using the closest-copy shortcut. */
+ SVN_ERR(prev_location(&curpath, &currev, fs, curroot, curpath,
+ subpool));
+ if (! curpath)
+ break;
+
+ /* Update our LASTPATH and LASTREV variables (which survive
+ SUBPOOL). */
+ svn_stringbuf_set(lastpath, curpath);
+ lastrev = currev;
+ }
+
+ /* Walk the predecessor links back to origin. */
+ SVN_ERR(svn_fs_fs__node_id(&pred_id, curroot, lastpath->data, predidpool));
+ do
+ {
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_fs_fs__dag_get_node(&node, fs, pred_id, subpool));
+
+ /* Why not just fetch the predecessor ID in PREDIDPOOL?
+ Because svn_fs_fs__dag_get_predecessor_id() doesn't
+ necessarily honor the passed-in pool, and might return a
+ value cached in the node (which is allocated in
+ SUBPOOL... maybe). */
+ svn_pool_clear(predidpool);
+ SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id, node));
+ pred_id = pred_id ? svn_fs_fs__id_copy(pred_id, predidpool) : NULL;
+ }
+ while (pred_id);
+
+ /* When we get here, NODE should be the first node-revision in our
+ chain. */
+ SVN_ERR(svn_fs_fs__dag_get_revision(revision, node, pool));
+
+ /* Wow, I don't want to have to do all that again. Let's cache
+ the result. */
+ if (node_id[0] != '_')
+ SVN_ERR(svn_fs_fs__set_node_origin(fs, node_id,
+ svn_fs_fs__dag_get_id(node), pool));
+
+ svn_pool_destroy(subpool);
+ svn_pool_destroy(predidpool);
+ return SVN_NO_ERROR;
+ }
+}
+
+
+struct history_prev_args
+{
+ svn_fs_history_t **prev_history_p;
+ svn_fs_history_t *history;
+ svn_boolean_t cross_copies;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+history_prev(void *baton, apr_pool_t *pool)
+{
+ struct history_prev_args *args = baton;
+ svn_fs_history_t **prev_history = args->prev_history_p;
+ svn_fs_history_t *history = args->history;
+ fs_history_data_t *fhd = history->fsap_data;
+ const char *commit_path, *src_path, *path = fhd->path;
+ svn_revnum_t commit_rev, src_rev, dst_rev;
+ svn_revnum_t revision = fhd->revision;
+ apr_pool_t *retpool = args->pool;
+ svn_fs_t *fs = fhd->fs;
+ parent_path_t *parent_path;
+ dag_node_t *node;
+ svn_fs_root_t *root;
+ svn_boolean_t reported = fhd->is_interesting;
+ svn_revnum_t copyroot_rev;
+ const char *copyroot_path;
+
+ /* Initialize our return value. */
+ *prev_history = NULL;
+
+ /* If our last history report left us hints about where to pickup
+ the chase, then our last report was on the destination of a
+ copy. If we are crossing copies, start from those locations,
+ otherwise, we're all done here. */
+ if (fhd->path_hint && SVN_IS_VALID_REVNUM(fhd->rev_hint))
+ {
+ reported = FALSE;
+ if (! args->cross_copies)
+ return SVN_NO_ERROR;
+ path = fhd->path_hint;
+ revision = fhd->rev_hint;
+ }
+
+ /* Construct a ROOT for the current revision. */
+ SVN_ERR(svn_fs_fs__revision_root(&root, fs, revision, pool));
+
+ /* Open PATH/REVISION, and get its node and a bunch of other
+ goodies. */
+ SVN_ERR(open_path(&parent_path, root, path, 0, NULL, pool));
+ node = parent_path->node;
+ commit_path = svn_fs_fs__dag_get_created_path(node);
+ SVN_ERR(svn_fs_fs__dag_get_revision(&commit_rev, node, pool));
+
+ /* The Subversion filesystem is written in such a way that a given
+ line of history may have at most one interesting history point
+ per filesystem revision. Either that node was edited (and
+ possibly copied), or it was copied but not edited. And a copy
+ source cannot be from the same revision as its destination. So,
+ if our history revision matches its node's commit revision, we
+ know that ... */
+ if (revision == commit_rev)
+ {
+ if (! reported)
+ {
+ /* ... we either have not yet reported on this revision (and
+ need now to do so) ... */
+ *prev_history = assemble_history(fs,
+ apr_pstrdup(retpool, commit_path),
+ commit_rev, TRUE, NULL,
+ SVN_INVALID_REVNUM, retpool);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* ... or we *have* reported on this revision, and must now
+ progress toward this node's predecessor (unless there is
+ no predecessor, in which case we're all done!). */
+ const svn_fs_id_t *pred_id;
+
+ SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id, node));
+ if (! pred_id)
+ return SVN_NO_ERROR;
+
+ /* Replace NODE and friends with the information from its
+ predecessor. */
+ SVN_ERR(svn_fs_fs__dag_get_node(&node, fs, pred_id, pool));
+ commit_path = svn_fs_fs__dag_get_created_path(node);
+ SVN_ERR(svn_fs_fs__dag_get_revision(&commit_rev, node, pool));
+ }
+ }
+
+ /* Find the youngest copyroot in the path of this node, including
+ itself. */
+ SVN_ERR(find_youngest_copyroot(&copyroot_rev, &copyroot_path, fs,
+ parent_path, pool));
+
+ /* Initialize some state variables. */
+ src_path = NULL;
+ src_rev = SVN_INVALID_REVNUM;
+ dst_rev = SVN_INVALID_REVNUM;
+
+ if (copyroot_rev > commit_rev)
+ {
+ const char *remainder_path;
+ const char *copy_dst, *copy_src;
+ svn_fs_root_t *copyroot_root;
+
+ SVN_ERR(svn_fs_fs__revision_root(&copyroot_root, fs, copyroot_rev,
+ pool));
+ SVN_ERR(get_dag(&node, copyroot_root, copyroot_path, FALSE, pool));
+ copy_dst = svn_fs_fs__dag_get_created_path(node);
+
+ /* If our current path was the very destination of the copy,
+ then our new current path will be the copy source. If our
+ current path was instead the *child* of the destination of
+ the copy, then figure out its previous location by taking its
+ path relative to the copy destination and appending that to
+ the copy source. Finally, if our current path doesn't meet
+ one of these other criteria ... ### for now just fallback to
+ the old copy hunt algorithm. */
+ remainder_path = svn_fspath__skip_ancestor(copy_dst, path);
+
+ if (remainder_path)
+ {
+ /* If we get here, then our current path is the destination
+ of, or the child of the destination of, a copy. Fill
+ in the return values and get outta here. */
+ SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(&src_rev, node));
+ SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(&copy_src, node));
+
+ dst_rev = copyroot_rev;
+ src_path = svn_fspath__join(copy_src, remainder_path, pool);
+ }
+ }
+
+ /* If we calculated a copy source path and revision, we'll make a
+ 'copy-style' history object. */
+ if (src_path && SVN_IS_VALID_REVNUM(src_rev))
+ {
+ svn_boolean_t retry = FALSE;
+
+ /* It's possible for us to find a copy location that is the same
+ as the history point we've just reported. If that happens,
+ we simply need to take another trip through this history
+ search. */
+ if ((dst_rev == revision) && reported)
+ retry = TRUE;
+
+ *prev_history = assemble_history(fs, apr_pstrdup(retpool, path),
+ dst_rev, ! retry,
+ src_path, src_rev, retpool);
+ }
+ else
+ {
+ *prev_history = assemble_history(fs, apr_pstrdup(retpool, commit_path),
+ commit_rev, TRUE, NULL,
+ SVN_INVALID_REVNUM, retpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implement svn_fs_history_prev, set *PREV_HISTORY_P to a new
+ svn_fs_history_t object that represents the predecessory of
+ HISTORY. If CROSS_COPIES is true, *PREV_HISTORY_P may be related
+ only through a copy operation. Perform all allocations in POOL. */
+static svn_error_t *
+fs_history_prev(svn_fs_history_t **prev_history_p,
+ svn_fs_history_t *history,
+ svn_boolean_t cross_copies,
+ apr_pool_t *pool)
+{
+ svn_fs_history_t *prev_history = NULL;
+ fs_history_data_t *fhd = history->fsap_data;
+ svn_fs_t *fs = fhd->fs;
+
+ /* Special case: the root directory changes in every single
+ revision, no exceptions. And, the root can't be the target (or
+ child of a target -- duh) of a copy. So, if that's our path,
+ then we need only decrement our revision by 1, and there you go. */
+ if (strcmp(fhd->path, "/") == 0)
+ {
+ if (! fhd->is_interesting)
+ prev_history = assemble_history(fs, "/", fhd->revision,
+ 1, NULL, SVN_INVALID_REVNUM, pool);
+ else if (fhd->revision > 0)
+ prev_history = assemble_history(fs, "/", fhd->revision - 1,
+ 1, NULL, SVN_INVALID_REVNUM, pool);
+ }
+ else
+ {
+ struct history_prev_args args;
+ prev_history = history;
+
+ while (1)
+ {
+ args.prev_history_p = &prev_history;
+ args.history = prev_history;
+ args.cross_copies = cross_copies;
+ args.pool = pool;
+ SVN_ERR(history_prev(&args, pool));
+
+ if (! prev_history)
+ break;
+ fhd = prev_history->fsap_data;
+ if (fhd->is_interesting)
+ break;
+ }
+ }
+
+ *prev_history_p = prev_history;
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *PATH and *REVISION to the path and revision for the HISTORY
+ object. Use POOL for all allocations. */
+static svn_error_t *
+fs_history_location(const char **path,
+ svn_revnum_t *revision,
+ svn_fs_history_t *history,
+ apr_pool_t *pool)
+{
+ fs_history_data_t *fhd = history->fsap_data;
+
+ *path = apr_pstrdup(pool, fhd->path);
+ *revision = fhd->revision;
+ return SVN_NO_ERROR;
+}
+
+static history_vtable_t history_vtable = {
+ fs_history_prev,
+ fs_history_location
+};
+
+/* Return a new history object (marked as "interesting") for PATH and
+ REVISION, allocated in POOL, and with its members set to the values
+ of the parameters provided. Note that PATH and PATH_HINT are not
+ duped into POOL -- it is the responsibility of the caller to ensure
+ that this happens. */
+static svn_fs_history_t *
+assemble_history(svn_fs_t *fs,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t is_interesting,
+ const char *path_hint,
+ svn_revnum_t rev_hint,
+ apr_pool_t *pool)
+{
+ svn_fs_history_t *history = apr_pcalloc(pool, sizeof(*history));
+ fs_history_data_t *fhd = apr_pcalloc(pool, sizeof(*fhd));
+ fhd->path = svn_fs__canonicalize_abspath(path, pool);
+ fhd->revision = revision;
+ fhd->is_interesting = is_interesting;
+ fhd->path_hint = path_hint;
+ fhd->rev_hint = rev_hint;
+ fhd->fs = fs;
+
+ history->vtable = &history_vtable;
+ history->fsap_data = fhd;
+ return history;
+}
+
+
+/* mergeinfo queries */
+
+
+/* DIR_DAG is a directory DAG node which has mergeinfo in its
+ descendants. This function iterates over its children. For each
+ child with immediate mergeinfo, it adds its mergeinfo to
+ RESULT_CATALOG. appropriate arguments. For each child with
+ descendants with mergeinfo, it recurses. Note that it does *not*
+ call the action on the path for DIR_DAG itself.
+
+ POOL is used for temporary allocations, including the mergeinfo
+ hashes passed to actions; RESULT_POOL is used for the mergeinfo added
+ to RESULT_CATALOG.
+ */
+static svn_error_t *
+crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root,
+ const char *this_path,
+ dag_node_t *dir_dag,
+ svn_mergeinfo_catalog_t result_catalog,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, dir_dag,
+ scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
+ const char *kid_path;
+ dag_node_t *kid_dag;
+ svn_boolean_t has_mergeinfo, go_down;
+
+ svn_pool_clear(iterpool);
+
+ kid_path = svn_fspath__join(this_path, dirent->name, iterpool);
+ SVN_ERR(get_dag(&kid_dag, root, kid_path, TRUE, iterpool));
+
+ SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&has_mergeinfo, kid_dag));
+ SVN_ERR(svn_fs_fs__dag_has_descendants_with_mergeinfo(&go_down, kid_dag));
+
+ if (has_mergeinfo)
+ {
+ /* Save this particular node's mergeinfo. */
+ apr_hash_t *proplist;
+ svn_mergeinfo_t kid_mergeinfo;
+ svn_string_t *mergeinfo_string;
+ svn_error_t *err;
+
+ SVN_ERR(svn_fs_fs__dag_get_proplist(&proplist, kid_dag, iterpool));
+ mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
+ if (!mergeinfo_string)
+ {
+ svn_string_t *idstr = svn_fs_fs__id_unparse(dirent->id, iterpool);
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Node-revision #'%s' claims to have mergeinfo but doesn't"),
+ idstr->data);
+ }
+
+ /* Issue #3896: If a node has syntactically invalid mergeinfo, then
+ treat it as if no mergeinfo is present rather than raising a parse
+ error. */
+ err = svn_mergeinfo_parse(&kid_mergeinfo,
+ mergeinfo_string->data,
+ result_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ svn_hash_sets(result_catalog, apr_pstrdup(result_pool, kid_path),
+ kid_mergeinfo);
+ }
+ }
+
+ if (go_down)
+ SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
+ kid_path,
+ kid_dag,
+ result_catalog,
+ result_pool,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Return the cache key as a combination of REV_ROOT->REV, the inheritance
+ flags INHERIT and ADJUST_INHERITED_MERGEINFO, and the PATH. The result
+ will be allocated in POOL..
+ */
+static const char *
+mergeinfo_cache_key(const char *path,
+ svn_fs_root_t *rev_root,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *pool)
+{
+ apr_int64_t number = rev_root->rev;
+ number = number * 4
+ + (inherit == svn_mergeinfo_nearest_ancestor ? 2 : 0)
+ + (adjust_inherited_mergeinfo ? 1 : 0);
+
+ return svn_fs_fs__combine_number_and_string(number, path, pool);
+}
+
+/* Calculates the mergeinfo for PATH under REV_ROOT using inheritance
+ type INHERIT. Returns it in *MERGEINFO, or NULL if there is none.
+ The result is allocated in RESULT_POOL; SCRATCH_POOL is
+ used for temporary allocations.
+ */
+static svn_error_t *
+get_mergeinfo_for_path_internal(svn_mergeinfo_t *mergeinfo,
+ svn_fs_root_t *rev_root,
+ const char *path,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ parent_path_t *parent_path, *nearest_ancestor;
+ apr_hash_t *proplist;
+ svn_string_t *mergeinfo_string;
+
+ path = svn_fs__canonicalize_abspath(path, scratch_pool);
+
+ SVN_ERR(open_path(&parent_path, rev_root, path, 0, NULL, scratch_pool));
+
+ if (inherit == svn_mergeinfo_nearest_ancestor && ! parent_path->parent)
+ return SVN_NO_ERROR;
+
+ if (inherit == svn_mergeinfo_nearest_ancestor)
+ nearest_ancestor = parent_path->parent;
+ else
+ nearest_ancestor = parent_path;
+
+ while (TRUE)
+ {
+ svn_boolean_t has_mergeinfo;
+
+ SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&has_mergeinfo,
+ nearest_ancestor->node));
+ if (has_mergeinfo)
+ break;
+
+ /* No need to loop if we're looking for explicit mergeinfo. */
+ if (inherit == svn_mergeinfo_explicit)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ nearest_ancestor = nearest_ancestor->parent;
+
+ /* Run out? There's no mergeinfo. */
+ if (!nearest_ancestor)
+ {
+ return SVN_NO_ERROR;
+ }
+ }
+
+ SVN_ERR(svn_fs_fs__dag_get_proplist(&proplist, nearest_ancestor->node,
+ scratch_pool));
+ mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
+ if (!mergeinfo_string)
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Node-revision '%s@%ld' claims to have mergeinfo but doesn't"),
+ parent_path_path(nearest_ancestor, scratch_pool), rev_root->rev);
+
+ /* Parse the mergeinfo; store the result in *MERGEINFO. */
+ {
+ /* Issue #3896: If a node has syntactically invalid mergeinfo, then
+ treat it as if no mergeinfo is present rather than raising a parse
+ error. */
+ svn_error_t *err = svn_mergeinfo_parse(mergeinfo,
+ mergeinfo_string->data,
+ result_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ svn_error_clear(err);
+ err = NULL;
+ *mergeinfo = NULL;
+ }
+ return svn_error_trace(err);
+ }
+ }
+
+ /* If our nearest ancestor is the very path we inquired about, we
+ can return the mergeinfo results directly. Otherwise, we're
+ inheriting the mergeinfo, so we need to a) remove non-inheritable
+ ranges and b) telescope the merged-from paths. */
+ if (adjust_inherited_mergeinfo && (nearest_ancestor != parent_path))
+ {
+ svn_mergeinfo_t tmp_mergeinfo;
+
+ SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *mergeinfo,
+ NULL, SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM, TRUE,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_fs__append_to_merged_froms(mergeinfo, tmp_mergeinfo,
+ parent_path_relpath(
+ parent_path, nearest_ancestor,
+ scratch_pool),
+ result_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Caching wrapper around get_mergeinfo_for_path_internal().
+ */
+static svn_error_t *
+get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo,
+ svn_fs_root_t *rev_root,
+ const char *path,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = rev_root->fs->fsap_data;
+ const char *cache_key;
+ svn_boolean_t found = FALSE;
+ svn_stringbuf_t *mergeinfo_exists;
+
+ *mergeinfo = NULL;
+
+ cache_key = mergeinfo_cache_key(path, rev_root, inherit,
+ adjust_inherited_mergeinfo, scratch_pool);
+ if (ffd->mergeinfo_existence_cache)
+ {
+ SVN_ERR(svn_cache__get((void **)&mergeinfo_exists, &found,
+ ffd->mergeinfo_existence_cache,
+ cache_key, result_pool));
+ if (found && mergeinfo_exists->data[0] == '1')
+ SVN_ERR(svn_cache__get((void **)mergeinfo, &found,
+ ffd->mergeinfo_cache,
+ cache_key, result_pool));
+ }
+
+ if (! found)
+ {
+ SVN_ERR(get_mergeinfo_for_path_internal(mergeinfo, rev_root, path,
+ inherit,
+ adjust_inherited_mergeinfo,
+ result_pool, scratch_pool));
+ if (ffd->mergeinfo_existence_cache)
+ {
+ mergeinfo_exists = svn_stringbuf_create(*mergeinfo ? "1" : "0",
+ scratch_pool);
+ SVN_ERR(svn_cache__set(ffd->mergeinfo_existence_cache,
+ cache_key, mergeinfo_exists, scratch_pool));
+ if (*mergeinfo)
+ SVN_ERR(svn_cache__set(ffd->mergeinfo_cache,
+ cache_key, *mergeinfo, scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Adds mergeinfo for each descendant of PATH (but not PATH itself)
+ under ROOT to RESULT_CATALOG. Returned values are allocated in
+ RESULT_POOL; temporary values in POOL. */
+static svn_error_t *
+add_descendant_mergeinfo(svn_mergeinfo_catalog_t result_catalog,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ dag_node_t *this_dag;
+ svn_boolean_t go_down;
+
+ SVN_ERR(get_dag(&this_dag, root, path, TRUE, scratch_pool));
+ SVN_ERR(svn_fs_fs__dag_has_descendants_with_mergeinfo(&go_down,
+ this_dag));
+ if (go_down)
+ SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
+ path,
+ this_dag,
+ result_catalog,
+ result_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Get the mergeinfo for a set of paths, returned in
+ *MERGEINFO_CATALOG. Returned values are allocated in
+ POOL, while temporary values are allocated in a sub-pool. */
+static svn_error_t *
+get_mergeinfos_for_paths(svn_fs_root_t *root,
+ svn_mergeinfo_catalog_t *mergeinfo_catalog,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_catalog_t result_catalog = svn_hash__make(result_pool);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ svn_error_t *err;
+ svn_mergeinfo_t path_mergeinfo;
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ err = get_mergeinfo_for_path(&path_mergeinfo, root, path,
+ inherit, adjust_inherited_mergeinfo,
+ result_pool, iterpool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ svn_error_clear(err);
+ err = NULL;
+ path_mergeinfo = NULL;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ if (path_mergeinfo)
+ svn_hash_sets(result_catalog, path, path_mergeinfo);
+ if (include_descendants)
+ SVN_ERR(add_descendant_mergeinfo(result_catalog, root, path,
+ result_pool, scratch_pool));
+ }
+ svn_pool_destroy(iterpool);
+
+ *mergeinfo_catalog = result_catalog;
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_fs_get_mergeinfo. */
+static svn_error_t *
+fs_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
+ svn_fs_root_t *root,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = root->fs->fsap_data;
+
+ /* We require a revision root. */
+ if (root->is_txn_root)
+ return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
+
+ /* We have to actually be able to find the mergeinfo metadata! */
+ if (! svn_fs_fs__fs_supports_mergeinfo(root->fs))
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Querying mergeinfo requires version %d of the FSFS filesystem "
+ "schema; filesystem '%s' uses only version %d"),
+ SVN_FS_FS__MIN_MERGEINFO_FORMAT, root->fs->path, ffd->format);
+
+ /* Retrieve a path -> mergeinfo hash mapping. */
+ return get_mergeinfos_for_paths(root, catalog, paths,
+ inherit,
+ include_descendants,
+ adjust_inherited_mergeinfo,
+ result_pool, scratch_pool);
+}
+
+
+/* The vtable associated with root objects. */
+static root_vtable_t root_vtable = {
+ fs_paths_changed,
+ svn_fs_fs__check_path,
+ fs_node_history,
+ svn_fs_fs__node_id,
+ svn_fs_fs__node_created_rev,
+ fs_node_origin_rev,
+ fs_node_created_path,
+ fs_delete_node,
+ fs_copied_from,
+ fs_closest_copy,
+ fs_node_prop,
+ fs_node_proplist,
+ fs_change_node_prop,
+ fs_props_changed,
+ fs_dir_entries,
+ fs_make_dir,
+ fs_copy,
+ fs_revision_link,
+ fs_file_length,
+ fs_file_checksum,
+ fs_file_contents,
+ fs_try_process_file_contents,
+ fs_make_file,
+ fs_apply_textdelta,
+ fs_apply_text,
+ fs_contents_changed,
+ fs_get_file_delta_stream,
+ fs_merge,
+ fs_get_mergeinfo,
+};
+
+/* Construct a new root object in FS, allocated from POOL. */
+static svn_fs_root_t *
+make_root(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = apr_pcalloc(pool, sizeof(*root));
+
+ root->fs = fs;
+ root->pool = pool;
+ root->vtable = &root_vtable;
+
+ return root;
+}
+
+
+/* Construct a root object referring to the root of REVISION in FS,
+ whose root directory is ROOT_DIR. Create the new root in POOL. */
+static svn_fs_root_t *
+make_revision_root(svn_fs_t *fs,
+ svn_revnum_t rev,
+ dag_node_t *root_dir,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = make_root(fs, pool);
+ fs_rev_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd));
+
+ root->is_txn_root = FALSE;
+ root->rev = rev;
+
+ frd->root_dir = root_dir;
+ frd->copyfrom_cache = svn_hash__make(root->pool);
+
+ root->fsap_data = frd;
+
+ return root;
+}
+
+
+/* Construct a root object referring to the root of the transaction
+ named TXN and based on revision BASE_REV in FS, with FLAGS to
+ describe transaction's behavior. Create the new root in POOL. */
+static svn_error_t *
+make_txn_root(svn_fs_root_t **root_p,
+ svn_fs_t *fs,
+ const char *txn,
+ svn_revnum_t base_rev,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = make_root(fs, pool);
+ fs_txn_root_data_t *frd = apr_pcalloc(root->pool, sizeof(*frd));
+
+ root->is_txn_root = TRUE;
+ root->txn = apr_pstrdup(root->pool, txn);
+ root->txn_flags = flags;
+ root->rev = base_rev;
+
+ frd->txn_id = txn;
+
+ /* Because this cache actually tries to invalidate elements, keep
+ the number of elements per page down.
+
+ Note that since dag_node_cache_invalidate uses svn_cache__iter,
+ this *cannot* be a memcache-based cache. */
+ SVN_ERR(svn_cache__create_inprocess(&(frd->txn_node_cache),
+ svn_fs_fs__dag_serialize,
+ svn_fs_fs__dag_deserialize,
+ APR_HASH_KEY_STRING,
+ 32, 20, FALSE,
+ apr_pstrcat(pool, txn, ":TXN",
+ (char *)NULL),
+ root->pool));
+
+ /* Initialize transaction-local caches in FS.
+
+ Note that we cannot put those caches in frd because that content
+ fs root object is not available where we would need it. */
+ SVN_ERR(svn_fs_fs__initialize_txn_caches(fs, txn, pool));
+
+ root->fsap_data = frd;
+
+ *root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Verify. */
+static APR_INLINE const char *
+stringify_node(dag_node_t *node,
+ apr_pool_t *pool)
+{
+ /* ### TODO: print some PATH@REV to it, too. */
+ return svn_fs_fs__id_unparse(svn_fs_fs__dag_get_id(node), pool)->data;
+}
+
+/* Check metadata sanity on NODE, and on its children. Manually verify
+ information for DAG nodes in revision REV, and trust the metadata
+ accuracy for nodes belonging to older revisions. */
+static svn_error_t *
+verify_node(dag_node_t *node,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ svn_boolean_t has_mergeinfo;
+ apr_int64_t mergeinfo_count;
+ const svn_fs_id_t *pred_id;
+ svn_fs_t *fs = svn_fs_fs__dag_get_fs(node);
+ int pred_count;
+ svn_node_kind_t kind;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* Fetch some data. */
+ SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&has_mergeinfo, node));
+ SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_count, node));
+ SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id, node));
+ SVN_ERR(svn_fs_fs__dag_get_predecessor_count(&pred_count, node));
+ kind = svn_fs_fs__dag_node_kind(node);
+
+ /* Sanity check. */
+ if (mergeinfo_count < 0)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Negative mergeinfo-count %" APR_INT64_T_FMT
+ " on node '%s'",
+ mergeinfo_count, stringify_node(node, iterpool));
+
+ /* Issue #4129. (This check will explicitly catch non-root instances too.) */
+ if (pred_id)
+ {
+ dag_node_t *pred;
+ int pred_pred_count;
+ SVN_ERR(svn_fs_fs__dag_get_node(&pred, fs, pred_id, iterpool));
+ SVN_ERR(svn_fs_fs__dag_get_predecessor_count(&pred_pred_count, pred));
+ if (pred_pred_count+1 != pred_count)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Predecessor count mismatch: "
+ "%s has %d, but %s has %d",
+ stringify_node(node, iterpool), pred_count,
+ stringify_node(pred, iterpool),
+ pred_pred_count);
+ }
+
+ /* Kind-dependent verifications. */
+ if (kind == svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Node '%s' has kind 'none'",
+ stringify_node(node, iterpool));
+ }
+ if (kind == svn_node_file)
+ {
+ if (has_mergeinfo != mergeinfo_count) /* comparing int to bool */
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "File node '%s' has inconsistent mergeinfo: "
+ "has_mergeinfo=%d, "
+ "mergeinfo_count=%" APR_INT64_T_FMT,
+ stringify_node(node, iterpool),
+ has_mergeinfo, mergeinfo_count);
+ }
+ if (kind == svn_node_dir)
+ {
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ apr_int64_t children_mergeinfo = 0;
+
+ SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, node, pool));
+
+ /* Compute CHILDREN_MERGEINFO. */
+ for (hi = apr_hash_first(pool, entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
+ dag_node_t *child;
+ svn_revnum_t child_rev;
+ apr_int64_t child_mergeinfo;
+
+ svn_pool_clear(iterpool);
+
+ /* Compute CHILD_REV. */
+ SVN_ERR(svn_fs_fs__dag_get_node(&child, fs, dirent->id, iterpool));
+ SVN_ERR(svn_fs_fs__dag_get_revision(&child_rev, child, iterpool));
+
+ if (child_rev == rev)
+ SVN_ERR(verify_node(child, rev, iterpool));
+
+ SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&child_mergeinfo, child));
+ children_mergeinfo += child_mergeinfo;
+ }
+
+ /* Side-effect of issue #4129. */
+ if (children_mergeinfo+has_mergeinfo != mergeinfo_count)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Mergeinfo-count discrepancy on '%s': "
+ "expected %" APR_INT64_T_FMT "+%d, "
+ "counted %" APR_INT64_T_FMT,
+ stringify_node(node, iterpool),
+ mergeinfo_count, has_mergeinfo,
+ children_mergeinfo);
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__verify_root(svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = root->fs;
+ dag_node_t *root_dir;
+
+ /* Issue #4129: bogus pred-counts and minfo-cnt's on the root node-rev
+ (and elsewhere). This code makes more thorough checks than the
+ commit-time checks in validate_root_noderev(). */
+
+ /* Callers should disable caches by setting SVN_FS_CONFIG_FSFS_CACHE_NS;
+ see r1462436.
+
+ When this code is called in the library, we want to ensure we
+ use the on-disk data --- rather than some data that was read
+ in the possibly-distance past and cached since. */
+
+ if (root->is_txn_root)
+ {
+ fs_txn_root_data_t *frd = root->fsap_data;
+ SVN_ERR(svn_fs_fs__dag_txn_root(&root_dir, fs, frd->txn_id, pool));
+ }
+ else
+ {
+ fs_rev_root_data_t *frd = root->fsap_data;
+ root_dir = frd->root_dir;
+ }
+
+ /* Recursively verify ROOT_DIR. */
+ SVN_ERR(verify_node(root_dir, root->rev, pool));
+
+ /* Verify explicitly the predecessor of the root. */
+ {
+ const svn_fs_id_t *pred_id;
+
+ /* Only r0 should have no predecessor. */
+ SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id, root_dir));
+ if (! root->is_txn_root && !!pred_id != !!root->rev)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "r%ld's root node's predecessor is "
+ "unexpectedly '%s'",
+ root->rev,
+ (pred_id
+ ? svn_fs_fs__id_unparse(pred_id, pool)->data
+ : "(null)"));
+ if (root->is_txn_root && !pred_id)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Transaction '%s''s root node's predecessor is "
+ "unexpectedly NULL",
+ root->txn);
+
+ /* Check the predecessor's revision. */
+ if (pred_id)
+ {
+ svn_revnum_t pred_rev = svn_fs_fs__id_rev(pred_id);
+ if (! root->is_txn_root && pred_rev+1 != root->rev)
+ /* Issue #4129. */
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "r%ld's root node's predecessor is r%ld"
+ " but should be r%ld",
+ root->rev, pred_rev, root->rev - 1);
+ if (root->is_txn_root && pred_rev != root->rev)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Transaction '%s''s root node's predecessor"
+ " is r%ld"
+ " but should be r%ld",
+ root->txn, pred_rev, root->rev);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_fs_fs/tree.h b/contrib/subversion/subversion/libsvn_fs_fs/tree.h
new file mode 100644
index 0000000..34fa0a23b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_fs/tree.h
@@ -0,0 +1,98 @@
+/* tree.h : internal interface to tree node functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_TREE_H
+#define SVN_LIBSVN_FS_TREE_H
+
+#include "fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* In POOL, create an instance of a DAG node 1st level cache.
+ The POOL will be cleared at regular intervals. */
+fs_fs_dag_cache_t*
+svn_fs_fs__create_dag_cache(apr_pool_t *pool);
+
+/* Set *ROOT_P to the root directory of revision REV in filesystem FS.
+ Allocate the structure in POOL. */
+svn_error_t *svn_fs_fs__revision_root(svn_fs_root_t **root_p, svn_fs_t *fs,
+ svn_revnum_t rev, apr_pool_t *pool);
+
+/* Does nothing, but included for Subversion 1.0.x compatibility. */
+svn_error_t *svn_fs_fs__deltify(svn_fs_t *fs, svn_revnum_t rev,
+ apr_pool_t *pool);
+
+/* Commit the transaction TXN as a new revision. Return the new
+ revision in *NEW_REV. If the transaction conflicts with other
+ changes return SVN_ERR_FS_CONFLICT and set *CONFLICT_P to a string
+ that details the cause of the conflict. Perform temporary
+ allocations in POOL. */
+svn_error_t *svn_fs_fs__commit_txn(const char **conflict_p,
+ svn_revnum_t *new_rev, svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+/* Set ROOT_P to the root directory of transaction TXN. Allocate the
+ structure in POOL. */
+svn_error_t *svn_fs_fs__txn_root(svn_fs_root_t **root_p, svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+
+/* Set KIND_P to the node kind of the node at PATH in ROOT.
+ Allocate the structure in POOL. */
+svn_error_t *
+svn_fs_fs__check_path(svn_node_kind_t *kind_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+/* Implement root_vtable_t.node_id(). */
+svn_error_t *
+svn_fs_fs__node_id(const svn_fs_id_t **id_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+/* Set *REVISION to the revision in which PATH under ROOT was created.
+ Use POOL for any temporary allocations. If PATH is in an
+ uncommitted transaction, *REVISION will be set to
+ SVN_INVALID_REVNUM. */
+svn_error_t *
+svn_fs_fs__node_created_rev(svn_revnum_t *revision,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+/* Verify metadata for ROOT.
+ ### Currently only implemented for revision roots. */
+svn_error_t *
+svn_fs_fs__verify_root(svn_fs_root_t *root,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_TREE_H */
diff --git a/contrib/subversion/subversion/libsvn_fs_util/fs-util.c b/contrib/subversion/subversion/libsvn_fs_util/fs-util.c
new file mode 100644
index 0000000..da57bc9
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_fs_util/fs-util.c
@@ -0,0 +1,223 @@
+/* fs-util.c : internal utility functions used by both FSFS and BDB back
+ * ends.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+
+#include "svn_hash.h"
+#include "svn_fs.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+#include "../libsvn_fs/fs-loader.h"
+
+/* Return TRUE, if PATH of PATH_LEN > 0 chars starts with a '/' and does
+ * not end with a '/' and does not contain duplicate '/'.
+ */
+static svn_boolean_t
+is_canonical_abspath(const char *path, size_t path_len)
+{
+ const char *end;
+
+ /* check for leading '/' */
+ if (path[0] != '/')
+ return FALSE;
+
+ /* check for trailing '/' */
+ if (path_len == 1)
+ return TRUE;
+ if (path[path_len - 1] == '/')
+ return FALSE;
+
+ /* check for "//" */
+ end = path + path_len - 1;
+ for (; path != end; ++path)
+ if ((path[0] == '/') && (path[1] == '/'))
+ return FALSE;
+
+ return TRUE;
+}
+
+svn_boolean_t
+svn_fs__is_canonical_abspath(const char *path)
+{
+ /* No PATH? No problem. */
+ if (! path)
+ return TRUE;
+
+ /* Empty PATH? That's just "/". */
+ if (! *path)
+ return FALSE;
+
+ /* detailed checks */
+ return is_canonical_abspath(path, strlen(path));
+}
+
+const char *
+svn_fs__canonicalize_abspath(const char *path, apr_pool_t *pool)
+{
+ char *newpath;
+ size_t path_len;
+ size_t path_i = 0, newpath_i = 0;
+ svn_boolean_t eating_slashes = FALSE;
+
+ /* No PATH? No problem. */
+ if (! path)
+ return NULL;
+
+ /* Empty PATH? That's just "/". */
+ if (! *path)
+ return "/";
+
+ /* Non-trivial cases. Maybe, the path already is canonical after all? */
+ path_len = strlen(path);
+ if (is_canonical_abspath(path, path_len))
+ return apr_pstrmemdup(pool, path, path_len);
+
+ /* Now, the fun begins. Alloc enough room to hold PATH with an
+ added leading '/'. */
+ newpath = apr_palloc(pool, path_len + 2);
+
+ /* No leading slash? Fix that. */
+ if (*path != '/')
+ {
+ newpath[newpath_i++] = '/';
+ }
+
+ for (path_i = 0; path_i < path_len; path_i++)
+ {
+ if (path[path_i] == '/')
+ {
+ /* The current character is a '/'. If we are eating up
+ extra '/' characters, skip this character. Else, note
+ that we are now eating slashes. */
+ if (eating_slashes)
+ continue;
+ eating_slashes = TRUE;
+ }
+ else
+ {
+ /* The current character is NOT a '/'. If we were eating
+ slashes, we need not do that any more. */
+ if (eating_slashes)
+ eating_slashes = FALSE;
+ }
+
+ /* Copy the current character into our new buffer. */
+ newpath[newpath_i++] = path[path_i];
+ }
+
+ /* Did we leave a '/' attached to the end of NEWPATH (other than in
+ the root directory case)? */
+ if ((newpath[newpath_i - 1] == '/') && (newpath_i > 1))
+ newpath[newpath_i - 1] = '\0';
+ else
+ newpath[newpath_i] = '\0';
+
+ return newpath;
+}
+
+svn_error_t *
+svn_fs__check_fs(svn_fs_t *fs,
+ svn_boolean_t expect_open)
+{
+ if ((expect_open && fs->fsap_data)
+ || ((! expect_open) && (! fs->fsap_data)))
+ return SVN_NO_ERROR;
+ if (expect_open)
+ return svn_error_create(SVN_ERR_FS_NOT_OPEN, 0,
+ _("Filesystem object has not been opened yet"));
+ else
+ return svn_error_create(SVN_ERR_FS_ALREADY_OPEN, 0,
+ _("Filesystem object already open"));
+}
+
+char *
+svn_fs__next_entry_name(const char **next_p,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *end;
+
+ /* Find the end of the current component. */
+ end = strchr(path, '/');
+
+ if (! end)
+ {
+ /* The path contains only one component, with no trailing
+ slashes. */
+ *next_p = 0;
+ return apr_pstrdup(pool, path);
+ }
+ else
+ {
+ /* There's a slash after the first component. Skip over an arbitrary
+ number of slashes to find the next one. */
+ const char *next = end;
+ while (*next == '/')
+ next++;
+ *next_p = next;
+ return apr_pstrndup(pool, path, end - path);
+ }
+}
+
+svn_fs_path_change2_t *
+svn_fs__path_change_create_internal(const svn_fs_id_t *node_rev_id,
+ svn_fs_path_change_kind_t change_kind,
+ apr_pool_t *pool)
+{
+ svn_fs_path_change2_t *change;
+
+ change = apr_pcalloc(pool, sizeof(*change));
+ change->node_rev_id = node_rev_id;
+ change->change_kind = change_kind;
+
+ return change;
+}
+
+svn_error_t *
+svn_fs__append_to_merged_froms(svn_mergeinfo_t *output,
+ svn_mergeinfo_t input,
+ const char *rel_path,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ *output = apr_hash_make(pool);
+ for (hi = apr_hash_first(pool, input); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+
+ svn_hash_sets(*output,
+ svn_fspath__join(path, rel_path, pool),
+ svn_rangelist_dup(rangelist, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra/compat.c b/contrib/subversion/subversion/libsvn_ra/compat.c
new file mode 100644
index 0000000..b16bbef
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra/compat.c
@@ -0,0 +1,952 @@
+/*
+ * compat.c: compatibility compliance logic
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_hash.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_ra.h"
+#include "svn_io.h"
+#include "svn_compat.h"
+#include "svn_props.h"
+
+#include "private/svn_fspath.h"
+#include "ra_loader.h"
+#include "svn_private_config.h"
+
+
+
+/* This is just like svn_sort_compare_revisions, save that it sorts
+ the revisions in *ascending* order. */
+static int
+compare_revisions(const void *a, const void *b)
+{
+ svn_revnum_t a_rev = *(const svn_revnum_t *)a;
+ svn_revnum_t b_rev = *(const svn_revnum_t *)b;
+ if (a_rev == b_rev)
+ return 0;
+ return a_rev < b_rev ? -1 : 1;
+}
+
+/* Given the CHANGED_PATHS and REVISION from an instance of a
+ svn_log_message_receiver_t function, determine at which location
+ PATH may be expected in the next log message, and set *PREV_PATH_P
+ to that value. KIND is the node kind of PATH. Set *ACTION_P to a
+ character describing the change that caused this revision (as
+ listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the
+ revision PATH was copied from, or SVN_INVALID_REVNUM if it was not
+ copied. ACTION_P and COPYFROM_REV_P may be NULL, in which case
+ they are not used. Perform all allocations in POOL.
+
+ This is useful for tracking the various changes in location a
+ particular resource has undergone when performing an RA->get_logs()
+ operation on that resource.
+*/
+static svn_error_t *
+prev_log_path(const char **prev_path_p,
+ char *action_p,
+ svn_revnum_t *copyfrom_rev_p,
+ apr_hash_t *changed_paths,
+ const char *path,
+ svn_node_kind_t kind,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ svn_log_changed_path_t *change;
+ const char *prev_path = NULL;
+
+ /* It's impossible to find the predecessor path of a NULL path. */
+ SVN_ERR_ASSERT(path);
+
+ /* Initialize our return values for the action and copyfrom_rev in
+ case we have an unhandled case later on. */
+ if (action_p)
+ *action_p = 'M';
+ if (copyfrom_rev_p)
+ *copyfrom_rev_p = SVN_INVALID_REVNUM;
+
+ if (changed_paths)
+ {
+ /* See if PATH was explicitly changed in this revision. */
+ change = svn_hash_gets(changed_paths, path);
+ if (change)
+ {
+ /* If PATH was not newly added in this revision, then it may or may
+ not have also been part of a moved subtree. In this case, set a
+ default previous path, but still look through the parents of this
+ path for a possible copy event. */
+ if (change->action != 'A' && change->action != 'R')
+ {
+ prev_path = path;
+ }
+ else
+ {
+ /* PATH is new in this revision. This means it cannot have been
+ part of a copied subtree. */
+ if (change->copyfrom_path)
+ prev_path = apr_pstrdup(pool, change->copyfrom_path);
+ else
+ prev_path = NULL;
+
+ *prev_path_p = prev_path;
+ if (action_p)
+ *action_p = change->action;
+ if (copyfrom_rev_p)
+ *copyfrom_rev_p = change->copyfrom_rev;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (apr_hash_count(changed_paths))
+ {
+ /* The path was not explicitly changed in this revision. The
+ fact that we're hearing about this revision implies, then,
+ that the path was a child of some copied directory. We need
+ to find that directory, and effectively "re-base" our path on
+ that directory's copyfrom_path. */
+ int i;
+ apr_array_header_t *paths;
+
+ /* Build a sorted list of the changed paths. */
+ paths = svn_sort__hash(changed_paths,
+ svn_sort_compare_items_as_paths, pool);
+
+ /* Now, walk the list of paths backwards, looking a parent of
+ our path that has copyfrom information. */
+ for (i = paths->nelts; i > 0; i--)
+ {
+ svn_sort__item_t item = APR_ARRAY_IDX(paths,
+ i - 1, svn_sort__item_t);
+ const char *ch_path = item.key;
+ size_t len = strlen(ch_path);
+
+ /* See if our path is the child of this change path. If
+ not, keep looking. */
+ if (! ((strncmp(ch_path, path, len) == 0) && (path[len] == '/')))
+ continue;
+
+ /* Okay, our path *is* a child of this change path. If
+ this change was copied, we just need to apply the
+ portion of our path that is relative to this change's
+ path, to the change's copyfrom path. Otherwise, this
+ change isn't really interesting to us, and our search
+ continues. */
+ change = item.value;
+ if (change->copyfrom_path)
+ {
+ if (action_p)
+ *action_p = change->action;
+ if (copyfrom_rev_p)
+ *copyfrom_rev_p = change->copyfrom_rev;
+ prev_path = svn_fspath__join(change->copyfrom_path,
+ path + len + 1, pool);
+ break;
+ }
+ }
+ }
+ }
+
+ /* If we didn't find what we expected to find, return an error.
+ (Because directories bubble-up, we get a bunch of logs we might
+ not want. Be forgiving in that case.) */
+ if (! prev_path)
+ {
+ if (kind == svn_node_dir)
+ prev_path = apr_pstrdup(pool, path);
+ else
+ return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+ _("Missing changed-path information for "
+ "'%s' in revision %ld"),
+ svn_dirent_local_style(path, pool), revision);
+ }
+
+ *prev_path_p = prev_path;
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *FS_PATH_P to the absolute filesystem path associated with the
+ URL built from SESSION's URL and REL_PATH (which is relative to
+ session's URL. Use POOL for allocations. */
+static svn_error_t *
+get_fs_path(const char **fs_path_p,
+ svn_ra_session_t *session,
+ const char *rel_path,
+ apr_pool_t *pool)
+{
+ const char *url, *fs_path;
+
+ SVN_ERR(svn_ra_get_session_url(session, &url, pool));
+ SVN_ERR(svn_ra_get_path_relative_to_root(session, &fs_path, url, pool));
+ *fs_path_p = svn_fspath__canonicalize(svn_relpath_join(fs_path,
+ rel_path, pool),
+ pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Fallback implementation of svn_ra_get_locations(). ***/
+
+
+/* ### This is to support 1.0 servers. */
+struct log_receiver_baton
+{
+ /* The kind of the path we're tracing. */
+ svn_node_kind_t kind;
+
+ /* The path at which we are trying to find our versioned resource in
+ the log output. */
+ const char *last_path;
+
+ /* Input revisions and output hash; the whole point of this little game. */
+ svn_revnum_t peg_revision;
+ apr_array_header_t *location_revisions;
+ const char *peg_path;
+ apr_hash_t *locations;
+
+ /* A pool from which to allocate stuff stored in this baton. */
+ apr_pool_t *pool;
+};
+
+
+/* Implements svn_log_entry_receiver_t; helper for slow_get_locations.
+ As input, takes log_receiver_baton (defined above) and attempts to
+ "fill in" locations in the baton over the course of many
+ iterations. */
+static svn_error_t *
+log_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct log_receiver_baton *lrb = baton;
+ apr_pool_t *hash_pool = apr_hash_pool_get(lrb->locations);
+ const char *current_path = lrb->last_path;
+ const char *prev_path;
+
+ /* No paths were changed in this revision. Nothing to do. */
+ if (! log_entry->changed_paths2)
+ return SVN_NO_ERROR;
+
+ /* If we've run off the end of the path's history, there's nothing
+ to do. (This should never happen with a properly functioning
+ server, since we'd get no more log messages after the one where
+ path was created. But a malfunctioning server shouldn't cause us
+ to trigger an assertion failure.) */
+ if (! current_path)
+ return SVN_NO_ERROR;
+
+ /* If we haven't found our peg path yet, and we are now looking at a
+ revision equal to or older than the peg revision, then our
+ "current" path is our peg path. */
+ if ((! lrb->peg_path) && (log_entry->revision <= lrb->peg_revision))
+ lrb->peg_path = apr_pstrdup(lrb->pool, current_path);
+
+ /* Determine the paths for any of the revisions for which we haven't
+ gotten paths already. */
+ while (lrb->location_revisions->nelts)
+ {
+ svn_revnum_t next = APR_ARRAY_IDX(lrb->location_revisions,
+ lrb->location_revisions->nelts - 1,
+ svn_revnum_t);
+ if (log_entry->revision <= next)
+ {
+ apr_hash_set(lrb->locations,
+ apr_pmemdup(hash_pool, &next, sizeof(next)),
+ sizeof(next),
+ apr_pstrdup(hash_pool, current_path));
+ apr_array_pop(lrb->location_revisions);
+ }
+ else
+ break;
+ }
+
+ /* Figure out at which repository path our object of interest lived
+ in the previous revision. */
+ SVN_ERR(prev_log_path(&prev_path, NULL, NULL, log_entry->changed_paths2,
+ current_path, lrb->kind, log_entry->revision, pool));
+
+ /* Squirrel away our "next place to look" path (suffer the strcmp
+ hit to save on allocations). */
+ if (! prev_path)
+ lrb->last_path = NULL;
+ else if (strcmp(prev_path, current_path) != 0)
+ lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra__locations_from_log(svn_ra_session_t *session,
+ apr_hash_t **locations_p,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool)
+{
+ apr_hash_t *locations = apr_hash_make(pool);
+ struct log_receiver_baton lrb = { 0 };
+ apr_array_header_t *targets;
+ svn_revnum_t youngest_requested, oldest_requested, youngest, oldest;
+ svn_node_kind_t kind;
+ const char *fs_path;
+
+ /* Fetch the absolute FS path associated with PATH. */
+ SVN_ERR(get_fs_path(&fs_path, session, path, pool));
+
+ /* Sanity check: verify that the peg-object exists in repos. */
+ SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' doesn't exist in revision %ld"),
+ fs_path, peg_revision);
+
+ /* Easy out: no location revisions. */
+ if (! location_revisions->nelts)
+ {
+ *locations_p = locations;
+ return SVN_NO_ERROR;
+ }
+
+ /* Figure out the youngest and oldest revs (amongst the set of
+ requested revisions + the peg revision) so we can avoid
+ unnecessary log parsing. */
+ qsort(location_revisions->elts, location_revisions->nelts,
+ location_revisions->elt_size, compare_revisions);
+ oldest_requested = APR_ARRAY_IDX(location_revisions, 0, svn_revnum_t);
+ youngest_requested = APR_ARRAY_IDX(location_revisions,
+ location_revisions->nelts - 1,
+ svn_revnum_t);
+ youngest = peg_revision;
+ youngest = (oldest_requested > youngest) ? oldest_requested : youngest;
+ youngest = (youngest_requested > youngest) ? youngest_requested : youngest;
+ oldest = peg_revision;
+ oldest = (oldest_requested < oldest) ? oldest_requested : oldest;
+ oldest = (youngest_requested < oldest) ? youngest_requested : oldest;
+
+ /* Populate most of our log receiver baton structure. */
+ lrb.kind = kind;
+ lrb.last_path = fs_path;
+ lrb.location_revisions = apr_array_copy(pool, location_revisions);
+ lrb.peg_revision = peg_revision;
+ lrb.peg_path = NULL;
+ lrb.locations = locations;
+ lrb.pool = pool;
+
+ /* Let the RA layer drive our log information handler, which will do
+ the work of finding the actual locations for our resource.
+ Notice that we always run on the youngest rev of the 3 inputs. */
+ targets = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(targets, const char *) = path;
+ SVN_ERR(svn_ra_get_log2(session, targets, youngest, oldest, 0,
+ TRUE, FALSE, FALSE,
+ apr_array_make(pool, 0, sizeof(const char *)),
+ log_receiver, &lrb, pool));
+
+ /* If the received log information did not cover any of the
+ requested revisions, use the last known path. (This normally
+ just means that FS_PATH was not modified between the requested
+ revision and OLDEST. If the file was created at some point after
+ OLDEST, then lrb.last_path should be NULL.) */
+ if (! lrb.peg_path)
+ lrb.peg_path = lrb.last_path;
+ if (lrb.last_path)
+ {
+ int i;
+ for (i = 0; i < location_revisions->nelts; i++)
+ {
+ svn_revnum_t rev = APR_ARRAY_IDX(location_revisions, i,
+ svn_revnum_t);
+ if (! apr_hash_get(locations, &rev, sizeof(rev)))
+ apr_hash_set(locations, apr_pmemdup(pool, &rev, sizeof(rev)),
+ sizeof(rev), apr_pstrdup(pool, lrb.last_path));
+ }
+ }
+
+ /* Check that we got the peg path. */
+ if (! lrb.peg_path)
+ return svn_error_createf
+ (APR_EGENERAL, NULL,
+ _("Unable to find repository location for '%s' in revision %ld"),
+ fs_path, peg_revision);
+
+ /* Sanity check: make sure that our calculated peg path is the same
+ as what we expected it to be. */
+ if (strcmp(fs_path, lrb.peg_path) != 0)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+ _("'%s' in revision %ld is an unrelated object"),
+ fs_path, youngest);
+
+ *locations_p = locations;
+ return SVN_NO_ERROR;
+}
+
+
+
+
+/*** Fallback implementation of svn_ra_get_location_segments(). ***/
+
+struct gls_log_receiver_baton {
+ /* The kind of the path we're tracing. */
+ svn_node_kind_t kind;
+
+ /* Are we finished (and just listening to log entries because our
+ caller won't shut up?). */
+ svn_boolean_t done;
+
+ /* The path at which we are trying to find our versioned resource in
+ the log output. */
+ const char *last_path;
+
+ /* Input data. */
+ svn_revnum_t start_rev;
+
+ /* Output intermediate state and callback/baton. */
+ svn_revnum_t range_end;
+ svn_location_segment_receiver_t receiver;
+ void *receiver_baton;
+
+ /* A pool from which to allocate stuff stored in this baton. */
+ apr_pool_t *pool;
+};
+
+/* Build a node location segment object from PATH, RANGE_START, and
+ RANGE_END, and pass it off to RECEIVER/RECEIVER_BATON. */
+static svn_error_t *
+maybe_crop_and_send_segment(const char *path,
+ svn_revnum_t start_rev,
+ svn_revnum_t range_start,
+ svn_revnum_t range_end,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment));
+ segment->path = path ? ((*path == '/') ? path + 1 : path) : NULL;
+ segment->range_start = range_start;
+ segment->range_end = range_end;
+ if (segment->range_start <= start_rev)
+ {
+ if (segment->range_end > start_rev)
+ segment->range_end = start_rev;
+ return receiver(segment, receiver_baton, pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+gls_log_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct gls_log_receiver_baton *lrb = baton;
+ const char *current_path = lrb->last_path;
+ const char *prev_path;
+ svn_revnum_t copyfrom_rev;
+
+ /* If we're done, ignore this invocation. */
+ if (lrb->done)
+ return SVN_NO_ERROR;
+
+ /* Figure out at which repository path our object of interest lived
+ in the previous revision, and if its current location is the
+ result of copy since then. */
+ SVN_ERR(prev_log_path(&prev_path, NULL, &copyfrom_rev,
+ log_entry->changed_paths2, current_path,
+ lrb->kind, log_entry->revision, pool));
+
+ /* If we've run off the end of the path's history, we need to report
+ our final segment (and then, we're done). */
+ if (! prev_path)
+ {
+ lrb->done = TRUE;
+ return maybe_crop_and_send_segment(current_path, lrb->start_rev,
+ log_entry->revision, lrb->range_end,
+ lrb->receiver, lrb->receiver_baton,
+ pool);
+ }
+
+ /* If there was a copy operation of interest... */
+ if (SVN_IS_VALID_REVNUM(copyfrom_rev))
+ {
+ /* ...then report the segment between this revision and the
+ last-reported revision. */
+ SVN_ERR(maybe_crop_and_send_segment(current_path, lrb->start_rev,
+ log_entry->revision, lrb->range_end,
+ lrb->receiver, lrb->receiver_baton,
+ pool));
+ lrb->range_end = log_entry->revision - 1;
+
+ /* And if there was a revision gap, we need to report that, too. */
+ if (log_entry->revision - copyfrom_rev > 1)
+ {
+ SVN_ERR(maybe_crop_and_send_segment(NULL, lrb->start_rev,
+ copyfrom_rev + 1, lrb->range_end,
+ lrb->receiver,
+ lrb->receiver_baton, pool));
+ lrb->range_end = copyfrom_rev;
+ }
+
+ /* Update our state variables. */
+ lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra__location_segments_from_log(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ struct gls_log_receiver_baton lrb = { 0 };
+ apr_array_header_t *targets;
+ svn_node_kind_t kind;
+ svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
+ const char *fs_path;
+
+ /* Fetch the absolute FS path associated with PATH. */
+ SVN_ERR(get_fs_path(&fs_path, session, path, pool));
+
+ /* If PEG_REVISION is invalid, it means HEAD. If START_REV is
+ invalid, it means HEAD. If END_REV is SVN_INVALID_REVNUM, we'll
+ use 0. */
+ if (! SVN_IS_VALID_REVNUM(peg_revision))
+ {
+ SVN_ERR(svn_ra_get_latest_revnum(session, &youngest_rev, pool));
+ peg_revision = youngest_rev;
+ }
+ if (! SVN_IS_VALID_REVNUM(start_rev))
+ {
+ if (SVN_IS_VALID_REVNUM(youngest_rev))
+ start_rev = youngest_rev;
+ else
+ SVN_ERR(svn_ra_get_latest_revnum(session, &start_rev, pool));
+ }
+ if (! SVN_IS_VALID_REVNUM(end_rev))
+ {
+ end_rev = 0;
+ }
+
+ /* The API demands a certain ordering of our revision inputs. Enforce it. */
+ SVN_ERR_ASSERT((peg_revision >= start_rev) && (start_rev >= end_rev));
+
+ /* Sanity check: verify that the peg-object exists in repos. */
+ SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' doesn't exist in revision %ld"),
+ fs_path, start_rev);
+
+ /* Populate most of our log receiver baton structure. */
+ lrb.kind = kind;
+ lrb.last_path = fs_path;
+ lrb.done = FALSE;
+ lrb.start_rev = start_rev;
+ lrb.range_end = start_rev;
+ lrb.receiver = receiver;
+ lrb.receiver_baton = receiver_baton;
+ lrb.pool = pool;
+
+ /* Let the RA layer drive our log information handler, which will do
+ the work of finding the actual locations for our resource.
+ Notice that we always run on the youngest rev of the 3 inputs. */
+ targets = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(targets, const char *) = path;
+ SVN_ERR(svn_ra_get_log2(session, targets, peg_revision, end_rev, 0,
+ TRUE, FALSE, FALSE,
+ apr_array_make(pool, 0, sizeof(const char *)),
+ gls_log_receiver, &lrb, pool));
+
+ /* If we didn't finish, we need to do so with a final segment send. */
+ if (! lrb.done)
+ SVN_ERR(maybe_crop_and_send_segment(lrb.last_path, start_rev,
+ end_rev, lrb.range_end,
+ receiver, receiver_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Fallback implementation of svn_ra_get_file_revs(). ***/
+
+/* The metadata associated with a particular revision. */
+struct rev
+{
+ svn_revnum_t revision; /* the revision number */
+ const char *path; /* the absolute repository path */
+ apr_hash_t *props; /* the revprops for this revision */
+ struct rev *next; /* the next revision */
+};
+
+/* File revs log message baton. */
+struct fr_log_message_baton {
+ const char *path; /* The path to be processed */
+ struct rev *eldest; /* The eldest revision processed */
+ char action; /* The action associated with the eldest */
+ svn_revnum_t copyrev; /* The revision the eldest was copied from */
+ apr_pool_t *pool;
+};
+
+/* Callback for log messages: implements svn_log_entry_receiver_t and
+ accumulates revision metadata into a chronologically ordered list stored in
+ the baton. */
+static svn_error_t *
+fr_log_message_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct fr_log_message_baton *lmb = baton;
+ struct rev *rev;
+
+ rev = apr_palloc(lmb->pool, sizeof(*rev));
+ rev->revision = log_entry->revision;
+ rev->path = lmb->path;
+ rev->next = lmb->eldest;
+ lmb->eldest = rev;
+
+ /* Duplicate log_entry revprops into rev->props */
+ rev->props = svn_prop_hash_dup(log_entry->revprops, lmb->pool);
+
+ return prev_log_path(&lmb->path, &lmb->action,
+ &lmb->copyrev, log_entry->changed_paths2,
+ lmb->path, svn_node_file, log_entry->revision,
+ lmb->pool);
+}
+
+svn_error_t *
+svn_ra__file_revs_from_log(svn_ra_session_t *ra_session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ const char *repos_url, *session_url, *fs_path;
+ apr_array_header_t *condensed_targets;
+ struct fr_log_message_baton lmb;
+ struct rev *rev;
+ apr_hash_t *last_props;
+ svn_stream_t *last_stream;
+ apr_pool_t *currpool, *lastpool;
+
+ /* Fetch the absolute FS path associated with PATH. */
+ SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool));
+
+ /* Check to make sure we're dealing with a file. */
+ SVN_ERR(svn_ra_check_path(ra_session, path, end, &kind, pool));
+ if (kind == svn_node_dir)
+ return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
+ _("'%s' is not a file"), fs_path);
+
+ condensed_targets = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(condensed_targets, const char *) = path;
+
+ lmb.path = fs_path;
+ lmb.eldest = NULL;
+ lmb.pool = pool;
+
+ /* Accumulate revision metadata by walking the revisions
+ backwards; this allows us to follow moves/copies
+ correctly. */
+ SVN_ERR(svn_ra_get_log2(ra_session,
+ condensed_targets,
+ end, start, 0, /* no limit */
+ TRUE, FALSE, FALSE,
+ NULL, fr_log_message_receiver, &lmb,
+ pool));
+
+ /* Reparent the session while we go back through the history. */
+ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool));
+ SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool));
+
+ currpool = svn_pool_create(pool);
+ lastpool = svn_pool_create(pool);
+
+ /* We want the first txdelta to be against the empty file. */
+ last_props = apr_hash_make(lastpool);
+ last_stream = svn_stream_empty(lastpool);
+
+ /* Walk the revision list in chronological order, downloading each fulltext,
+ diffing it with its predecessor, and calling the file_revs handler for
+ each one. Use two iteration pools rather than one, because the diff
+ routines need to look at a sliding window of revisions. Two pools gives
+ us a ring buffer of sorts. */
+ for (rev = lmb.eldest; rev; rev = rev->next)
+ {
+ const char *temp_path;
+ apr_pool_t *tmppool;
+ apr_hash_t *props;
+ apr_file_t *file;
+ svn_stream_t *stream;
+ apr_array_header_t *prop_diffs;
+ svn_txdelta_stream_t *delta_stream;
+ svn_txdelta_window_handler_t delta_handler = NULL;
+ void *delta_baton = NULL;
+
+ svn_pool_clear(currpool);
+
+ /* Get the contents of the file from the repository, and put them in
+ a temporary local file. */
+ SVN_ERR(svn_stream_open_unique(&stream, &temp_path, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ currpool, currpool));
+ SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision,
+ stream, NULL, &props, currpool));
+ SVN_ERR(svn_stream_close(stream));
+
+ /* Open up a stream to the local file. */
+ SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT,
+ currpool));
+ stream = svn_stream_from_aprfile2(file, FALSE, currpool);
+
+ /* Calculate the property diff */
+ SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool));
+
+ /* Call the file_rev handler */
+ SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props,
+ FALSE, /* merged revision */
+ &delta_handler, &delta_baton, prop_diffs, lastpool));
+
+ /* Compute and send delta if client asked for it. */
+ if (delta_handler)
+ {
+ /* Get the content delta. Don't calculate checksums as we don't
+ * use them. */
+ svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool);
+
+ /* And send. */
+ SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
+ delta_baton, lastpool));
+ }
+
+ /* Switch the pools and data for the next iteration */
+ tmppool = currpool;
+ currpool = lastpool;
+ lastpool = tmppool;
+
+ SVN_ERR(svn_stream_close(last_stream));
+ last_stream = stream;
+ last_props = props;
+ }
+
+ SVN_ERR(svn_stream_close(last_stream));
+ svn_pool_destroy(currpool);
+ svn_pool_destroy(lastpool);
+
+ /* Reparent the session back to the original URL. */
+ return svn_ra_reparent(ra_session, session_url, pool);
+}
+
+
+/*** Fallback implementation of svn_ra_get_deleted_rev(). ***/
+
+/* svn_ra_get_log2() receiver_baton for svn_ra__get_deleted_rev_from_log(). */
+typedef struct log_path_del_rev_t
+{
+ /* Absolute repository path. */
+ const char *path;
+
+ /* Revision PATH was first deleted or replaced. */
+ svn_revnum_t revision_deleted;
+} log_path_del_rev_t;
+
+/* A svn_log_entry_receiver_t callback for finding the revision
+ ((log_path_del_rev_t *)BATON)->PATH was first deleted or replaced.
+ Stores that revision in ((log_path_del_rev_t *)BATON)->REVISION_DELETED.
+ */
+static svn_error_t *
+log_path_del_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ log_path_del_rev_t *b = baton;
+ apr_hash_index_t *hi;
+
+ /* No paths were changed in this revision. Nothing to do. */
+ if (! log_entry->changed_paths2)
+ return SVN_NO_ERROR;
+
+ for (hi = apr_hash_first(pool, log_entry->changed_paths2);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ void *val;
+ char *path;
+ svn_log_changed_path_t *log_item;
+
+ apr_hash_this(hi, (void *) &path, NULL, &val);
+ log_item = val;
+ if (svn_path_compare_paths(b->path, path) == 0
+ && (log_item->action == 'D' || log_item->action == 'R'))
+ {
+ /* Found the first deletion or replacement, we are done. */
+ b->revision_deleted = log_entry->revision;
+ break;
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra__get_deleted_rev_from_log(svn_ra_session_t *session,
+ const char *rel_deleted_path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool)
+{
+ const char *fs_path;
+ log_path_del_rev_t log_path_deleted_baton;
+
+ /* Fetch the absolute FS path associated with PATH. */
+ SVN_ERR(get_fs_path(&fs_path, session, rel_deleted_path, pool));
+
+ if (!SVN_IS_VALID_REVNUM(peg_revision))
+ return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Invalid peg revision %ld"), peg_revision);
+ if (!SVN_IS_VALID_REVNUM(end_revision))
+ return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Invalid end revision %ld"), end_revision);
+ if (end_revision <= peg_revision)
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Peg revision must precede end revision"));
+
+ log_path_deleted_baton.path = fs_path;
+ log_path_deleted_baton.revision_deleted = SVN_INVALID_REVNUM;
+
+ /* Examine the logs of SESSION's URL to find when DELETED_PATH was first
+ deleted or replaced. */
+ SVN_ERR(svn_ra_get_log2(session, NULL, peg_revision, end_revision, 0,
+ TRUE, TRUE, FALSE,
+ apr_array_make(pool, 0, sizeof(char *)),
+ log_path_del_receiver, &log_path_deleted_baton,
+ pool));
+ *revision_deleted = log_path_deleted_baton.revision_deleted;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra__get_inherited_props_walk(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ apr_array_header_t **inherited_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_root_url;
+ const char *session_url;
+ const char *parent_url;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ *inherited_props =
+ apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *));
+
+ /* Walk to the root of the repository getting inherited
+ props for PATH. */
+ SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, scratch_pool));
+ SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool));
+ parent_url = session_url;
+
+ while (strcmp(repos_root_url, parent_url))
+ {
+ apr_hash_index_t *hi;
+ apr_hash_t *parent_props;
+ apr_hash_t *final_hash = apr_hash_make(result_pool);
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+ parent_url = svn_uri_dirname(parent_url, scratch_pool);
+ SVN_ERR(svn_ra_reparent(session, parent_url, iterpool));
+ err = session->vtable->get_dir(session, NULL, NULL,
+ &parent_props, "",
+ revision, SVN_DIRENT_ALL,
+ iterpool);
+
+ /* If the user doesn't have read access to a parent path then
+ skip, but allow them to inherit from further up. */
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED)
+ || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN))
+ {
+ svn_error_clear(err);
+ continue;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ for (hi = apr_hash_first(scratch_pool, parent_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ apr_ssize_t klen = svn__apr_hash_index_klen(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+
+ if (svn_property_kind2(name) == svn_prop_regular_kind)
+ {
+ name = apr_pstrdup(result_pool, name);
+ value = svn_string_dup(value, result_pool);
+ apr_hash_set(final_hash, name, klen, value);
+ }
+ }
+
+ if (apr_hash_count(final_hash))
+ {
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(result_pool, sizeof(*new_iprop));
+ new_iprop->path_or_url = svn_uri_skip_ancestor(repos_root_url,
+ parent_url,
+ result_pool);
+ new_iprop->prop_hash = final_hash;
+ svn_sort__array_insert(&new_iprop, *inherited_props, 0);
+ }
+ }
+
+ /* Reparent session back to original URL. */
+ SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra/debug_reporter.c b/contrib/subversion/subversion/libsvn_ra/debug_reporter.c
new file mode 100644
index 0000000..00ec029
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra/debug_reporter.c
@@ -0,0 +1,151 @@
+/*
+ * debug_reporter.c : An reporter that writes the operations it does to stderr.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+/*** ***/
+#include "debug_reporter.h"
+
+struct report_baton
+{
+ const svn_ra_reporter3_t *wrapped_reporter;
+ void *wrapped_report_baton;
+
+ svn_stream_t *out;
+};
+
+#define BOOLEAN_TO_WORD(condition) ((condition) ? "True" : "False")
+
+
+/*** Wrappers. ***/
+
+static svn_error_t *
+set_path(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ struct report_baton *rb = report_baton;
+ SVN_ERR(svn_stream_printf(rb->out, pool, "set_path(%s, %ld, %s, %s, %s)\n",
+ path, revision, svn_depth_to_word(depth),
+ BOOLEAN_TO_WORD(start_empty), lock_token));
+ SVN_ERR(rb->wrapped_reporter->set_path(rb->wrapped_report_baton, path,
+ revision, depth,
+ start_empty, lock_token, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_path(void *report_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct report_baton *rb = report_baton;
+ SVN_ERR(svn_stream_printf(rb->out, pool, "delete_path(%s)\n", path));
+ SVN_ERR(rb->wrapped_reporter->delete_path(rb->wrapped_report_baton,
+ path, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+link_path(void *report_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ struct report_baton *rb = report_baton;
+ SVN_ERR(svn_stream_printf(rb->out, pool,
+ "link_path(%s, %s, %ld, %s, %s, %s)\n",
+ path, url, revision, svn_depth_to_word(depth),
+ BOOLEAN_TO_WORD(start_empty), lock_token));
+ SVN_ERR(rb->wrapped_reporter->link_path(rb->wrapped_report_baton, path, url,
+ revision, depth, start_empty,
+ lock_token, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+finish_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ struct report_baton *rb = report_baton;
+ SVN_ERR(svn_stream_puts(rb->out, "finish_report()\n"));
+ SVN_ERR(rb->wrapped_reporter->finish_report(rb->wrapped_report_baton, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+abort_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ struct report_baton *rb = report_baton;
+ SVN_ERR(svn_stream_puts(rb->out, "abort_report()\n"));
+ SVN_ERR(rb->wrapped_reporter->abort_report(rb->wrapped_report_baton, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/*** Public interfaces. ***/
+svn_error_t *
+svn_ra__get_debug_reporter(const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const svn_ra_reporter3_t *wrapped_reporter,
+ void *wrapped_report_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_reporter3_t *tree_reporter;
+ struct report_baton *rb;
+ apr_file_t *errfp;
+ svn_stream_t *out;
+
+ apr_status_t apr_err = apr_file_open_stderr(&errfp, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Problem opening stderr");
+
+ out = svn_stream_from_aprfile2(errfp, TRUE, pool);
+
+ /* ### svn_delta_default_editor() */
+ tree_reporter = apr_palloc(pool, sizeof(*tree_reporter));
+ rb = apr_palloc(pool, sizeof(*rb));
+
+ tree_reporter->set_path = set_path;
+ tree_reporter->delete_path = delete_path;
+ tree_reporter->link_path = link_path;
+ tree_reporter->finish_report = finish_report;
+ tree_reporter->abort_report = abort_report;
+
+ rb->wrapped_reporter = wrapped_reporter;
+ rb->wrapped_report_baton = wrapped_report_baton;
+ rb->out = out;
+
+ *reporter = tree_reporter;
+ *report_baton = rb;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra/debug_reporter.h b/contrib/subversion/subversion/libsvn_ra/debug_reporter.h
new file mode 100644
index 0000000..1728139
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra/debug_reporter.h
@@ -0,0 +1,49 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_DEBUG_REPORTER_H
+#define SVN_DEBUG_REPORTER_H
+
+#include "svn_ra.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Return a debug reporter that wraps @a wrapped_reporter.
+ *
+ * The debug reporter simply prints an indication of what callbacks are being
+ * called to @c stderr, and is only intended for use in debugging subversion
+ * reporters.
+ */
+svn_error_t *
+svn_ra__get_debug_reporter(const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const svn_ra_reporter3_t *wrapped_reporter,
+ void *wrapped_report_baton,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_DEBUG_REPORTER_H */
diff --git a/contrib/subversion/subversion/libsvn_ra/deprecated.c b/contrib/subversion/subversion/libsvn_ra/deprecated.c
new file mode 100644
index 0000000..b7e717e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra/deprecated.c
@@ -0,0 +1,509 @@
+/*
+ * deprecated.c: holding file for all deprecated APIs.
+ * "we can't lose 'em, but we can shun 'em!"
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* We define this here to remove any further warnings about the usage of
+ deprecated functions in this file. */
+#define SVN_DEPRECATED
+
+#include "svn_hash.h"
+#include "svn_ra.h"
+#include "svn_path.h"
+#include "svn_compat.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "ra_loader.h"
+#include "deprecated.h"
+
+#include "svn_private_config.h"
+
+
+
+
+/*** From ra_loader.c ***/
+/*** Compatibility Wrappers ***/
+
+/* Wrap @c svn_ra_reporter3_t in an interface that looks like
+ @c svn_ra_reporter2_t, for compatibility with functions that take
+ the latter. This shields the ra-specific implementations from
+ worrying about what kind of reporter they're dealing with.
+
+ This code does not live in wrapper_template.h because that file is
+ about the big changeover from a vtable-style to function-style
+ interface, and does not contain the post-changeover interfaces
+ that we are compatiblizing here.
+
+ This code looks like it duplicates code in libsvn_wc/adm_crawler.c,
+ but in fact it does not. That code makes old things look like new
+ things; this code makes a new thing look like an old thing. */
+
+/* Baton for abovementioned wrapping. */
+struct reporter_3in2_baton {
+ const svn_ra_reporter3_t *reporter3;
+ void *reporter3_baton;
+};
+
+/* Wrap the corresponding svn_ra_reporter3_t field in an
+ svn_ra_reporter2_t interface. @a report_baton is a
+ @c reporter_3in2_baton_t *. */
+static svn_error_t *
+set_path(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ struct reporter_3in2_baton *b = report_baton;
+ return b->reporter3->set_path(b->reporter3_baton,
+ path, revision, svn_depth_infinity,
+ start_empty, lock_token, pool);
+}
+
+/* Wrap the corresponding svn_ra_reporter3_t field in an
+ svn_ra_reporter2_t interface. @a report_baton is a
+ @c reporter_3in2_baton_t *. */
+static svn_error_t *
+delete_path(void *report_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct reporter_3in2_baton *b = report_baton;
+ return b->reporter3->delete_path(b->reporter3_baton, path, pool);
+}
+
+/* Wrap the corresponding svn_ra_reporter3_t field in an
+ svn_ra_reporter2_t interface. @a report_baton is a
+ @c reporter_3in2_baton_t *. */
+static svn_error_t *
+link_path(void *report_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ struct reporter_3in2_baton *b = report_baton;
+ return b->reporter3->link_path(b->reporter3_baton,
+ path, url, revision, svn_depth_infinity,
+ start_empty, lock_token, pool);
+
+}
+
+/* Wrap the corresponding svn_ra_reporter3_t field in an
+ svn_ra_reporter2_t interface. @a report_baton is a
+ @c reporter_3in2_baton_t *. */
+static svn_error_t *
+finish_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ struct reporter_3in2_baton *b = report_baton;
+ return b->reporter3->finish_report(b->reporter3_baton, pool);
+}
+
+/* Wrap the corresponding svn_ra_reporter3_t field in an
+ svn_ra_reporter2_t interface. @a report_baton is a
+ @c reporter_3in2_baton_t *. */
+static svn_error_t *
+abort_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ struct reporter_3in2_baton *b = report_baton;
+ return b->reporter3->abort_report(b->reporter3_baton, pool);
+}
+
+/* Wrap svn_ra_reporter3_t calls in an svn_ra_reporter2_t interface.
+
+ Note: For calls where the prototypes are exactly the same, we could
+ avoid the pass-through overhead by using the function in the
+ reporter returned from session->vtable->do_foo. But the code would
+ get a lot less readable, and the only benefit would be to shave a
+ few instructions in a network-bound operation anyway. So in
+ delete_path(), finish_report(), and abort_report(), we cheerfully
+ pass through to identical functions. */
+static svn_ra_reporter2_t reporter_3in2_wrapper = {
+ set_path,
+ delete_path,
+ link_path,
+ finish_report,
+ abort_report
+};
+
+svn_error_t *svn_ra_open3(svn_ra_session_t **session_p,
+ const char *repos_URL,
+ const char *uuid,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ return svn_ra_open4(session_p, NULL, repos_URL, uuid,
+ callbacks, callback_baton, config, pool);
+}
+
+svn_error_t *svn_ra_open2(svn_ra_session_t **session_p,
+ const char *repos_URL,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ return svn_ra_open3(session_p, repos_URL, NULL,
+ callbacks, callback_baton, config, pool);
+}
+
+svn_error_t *svn_ra_open(svn_ra_session_t **session_p,
+ const char *repos_URL,
+ const svn_ra_callbacks_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ /* Deprecated function. Copy the contents of the svn_ra_callbacks_t
+ to a new svn_ra_callbacks2_t and call svn_ra_open2(). */
+ svn_ra_callbacks2_t *callbacks2;
+ SVN_ERR(svn_ra_create_callbacks(&callbacks2, pool));
+ callbacks2->open_tmp_file = callbacks->open_tmp_file;
+ callbacks2->auth_baton = callbacks->auth_baton;
+ callbacks2->get_wc_prop = callbacks->get_wc_prop;
+ callbacks2->set_wc_prop = callbacks->set_wc_prop;
+ callbacks2->push_wc_prop = callbacks->push_wc_prop;
+ callbacks2->invalidate_wc_props = callbacks->invalidate_wc_props;
+ callbacks2->progress_func = NULL;
+ callbacks2->progress_baton = NULL;
+ return svn_ra_open2(session_p, repos_URL,
+ callbacks2, callback_baton,
+ config, pool);
+}
+
+svn_error_t *svn_ra_change_rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ return svn_ra_change_rev_prop2(session, rev, name, NULL, value, pool);
+}
+
+svn_error_t *svn_ra_get_commit_editor2(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const char *log_msg,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool)
+{
+ apr_hash_t *revprop_table = apr_hash_make(pool);
+ if (log_msg)
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
+ svn_string_create(log_msg, pool));
+ return svn_ra_get_commit_editor3(session, editor, edit_baton, revprop_table,
+ commit_callback, commit_baton,
+ lock_tokens, keep_locks, pool);
+}
+
+svn_error_t *svn_ra_get_commit_editor(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ const char *log_msg,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool)
+{
+ svn_commit_callback2_t callback2;
+ void *callback2_baton;
+
+ svn_compat_wrap_commit_callback(&callback2, &callback2_baton,
+ callback, callback_baton,
+ pool);
+
+ return svn_ra_get_commit_editor2(session, editor, edit_baton,
+ log_msg, callback2,
+ callback2_baton, lock_tokens,
+ keep_locks, pool);
+}
+
+svn_error_t *svn_ra_do_diff2(svn_ra_session_t *session,
+ const svn_ra_reporter2_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool)
+{
+ struct reporter_3in2_baton *b = apr_palloc(pool, sizeof(*b));
+ SVN_ERR_ASSERT(svn_path_is_empty(diff_target)
+ || svn_path_is_single_path_component(diff_target));
+ *reporter = &reporter_3in2_wrapper;
+ *report_baton = b;
+ return session->vtable->do_diff(session,
+ &(b->reporter3), &(b->reporter3_baton),
+ revision, diff_target,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_ancestry, text_deltas, versus_url,
+ diff_editor, diff_baton, pool);
+}
+
+svn_error_t *svn_ra_do_diff(svn_ra_session_t *session,
+ const svn_ra_reporter2_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(svn_path_is_empty(diff_target)
+ || svn_path_is_single_path_component(diff_target));
+ return svn_ra_do_diff2(session, reporter, report_baton, revision,
+ diff_target, recurse, ignore_ancestry, TRUE,
+ versus_url, diff_editor, diff_baton, pool);
+}
+
+svn_error_t *svn_ra_get_log(svn_ra_session_t *session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ svn_log_entry_receiver_t receiver2;
+ void *receiver2_baton;
+
+ if (paths)
+ {
+ int i;
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ SVN_ERR_ASSERT(*path != '/');
+ }
+ }
+
+ svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
+ receiver, receiver_baton,
+ pool);
+
+ return svn_ra_get_log2(session, paths, start, end, limit,
+ discover_changed_paths, strict_node_history,
+ FALSE, svn_compat_log_revprops_in(pool),
+ receiver2, receiver2_baton, pool);
+}
+
+svn_error_t *svn_ra_get_file_revs(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_ra_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_file_rev_handler_t handler2;
+ void *handler2_baton;
+
+ SVN_ERR_ASSERT(*path != '/');
+
+ svn_compat_wrap_file_rev_handler(&handler2, &handler2_baton,
+ handler, handler_baton,
+ pool);
+
+ return svn_ra_get_file_revs2(session, path, start, end, FALSE, handler2,
+ handler2_baton, pool);
+}
+
+svn_error_t *
+svn_ra_do_update2(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(
+ svn_ra_do_update3(session,
+ reporter, report_baton,
+ revision_to_update_to, update_target,
+ depth,
+ send_copyfrom_args,
+ FALSE /* ignore_ancestry */,
+ update_editor, update_baton,
+ pool, pool));
+}
+
+svn_error_t *svn_ra_do_update(svn_ra_session_t *session,
+ const svn_ra_reporter2_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_boolean_t recurse,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool)
+{
+ struct reporter_3in2_baton *b = apr_palloc(pool, sizeof(*b));
+ SVN_ERR_ASSERT(svn_path_is_empty(update_target)
+ || svn_path_is_single_path_component(update_target));
+ *reporter = &reporter_3in2_wrapper;
+ *report_baton = b;
+ return session->vtable->do_update(session,
+ &(b->reporter3), &(b->reporter3_baton),
+ revision_to_update_to, update_target,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ FALSE, /* no copyfrom args */
+ FALSE /* ignore_ancestry */,
+ update_editor, update_baton,
+ pool, pool);
+}
+
+
+svn_error_t *
+svn_ra_do_switch2(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_depth_t depth,
+ const char *switch_url,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(
+ svn_ra_do_switch3(session,
+ reporter, report_baton,
+ revision_to_switch_to, switch_target,
+ depth,
+ switch_url,
+ FALSE /* send_copyfrom_args */,
+ TRUE /* ignore_ancestry */,
+ switch_editor, switch_baton,
+ pool, pool));
+}
+
+svn_error_t *svn_ra_do_switch(svn_ra_session_t *session,
+ const svn_ra_reporter2_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_boolean_t recurse,
+ const char *switch_url,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *pool)
+{
+ struct reporter_3in2_baton *b = apr_palloc(pool, sizeof(*b));
+ SVN_ERR_ASSERT(svn_path_is_empty(switch_target)
+ || svn_path_is_single_path_component(switch_target));
+ *reporter = &reporter_3in2_wrapper;
+ *report_baton = b;
+ return session->vtable->do_switch(session,
+ &(b->reporter3), &(b->reporter3_baton),
+ revision_to_switch_to, switch_target,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ switch_url,
+ FALSE /* send_copyfrom_args */,
+ TRUE /* ignore_ancestry */,
+ switch_editor, switch_baton,
+ pool, pool);
+}
+
+svn_error_t *svn_ra_do_status(svn_ra_session_t *session,
+ const svn_ra_reporter2_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_boolean_t recurse,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool)
+{
+ struct reporter_3in2_baton *b = apr_palloc(pool, sizeof(*b));
+ SVN_ERR_ASSERT(svn_path_is_empty(status_target)
+ || svn_path_is_single_path_component(status_target));
+ *reporter = &reporter_3in2_wrapper;
+ *report_baton = b;
+ return session->vtable->do_status(session,
+ &(b->reporter3), &(b->reporter3_baton),
+ status_target, revision,
+ SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse),
+ status_editor, status_baton, pool);
+}
+
+svn_error_t *svn_ra_get_dir(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(*path != '/');
+ return session->vtable->get_dir(session, dirents, fetched_rev, props,
+ path, revision, SVN_DIRENT_ALL, pool);
+}
+
+svn_error_t *
+svn_ra_local__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash)
+{
+ return svn_error_trace(svn_ra_local_init(abi_version, pool, hash));
+}
+
+svn_error_t *
+svn_ra_svn__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash)
+{
+ return svn_error_trace(svn_ra_svn_init(abi_version, pool, hash));
+}
+
+svn_error_t *
+svn_ra_serf__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash)
+{
+ return svn_error_trace(svn_ra_serf_init(abi_version, pool, hash));
+}
diff --git a/contrib/subversion/subversion/libsvn_ra/deprecated.h b/contrib/subversion/subversion/libsvn_ra/deprecated.h
new file mode 100644
index 0000000..205de16
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra/deprecated.h
@@ -0,0 +1,60 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ */
+
+
+
+#ifndef DEPRECATED_H
+#define DEPRECATED_H
+
+#include <apr_hash.h>
+
+#include "private/svn_editor.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Non-deprecated wrapper around svn_ra_local_init. */
+svn_error_t *
+svn_ra_local__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash);
+
+/* Non-deprecated wrapper around svn_ra_svn_init. */
+svn_error_t *
+svn_ra_svn__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash);
+
+/* Non-deprecated wrapper around svn_ra_serf_init. */
+svn_error_t *
+svn_ra_serf__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DEPRECATED_H */
diff --git a/contrib/subversion/subversion/libsvn_ra/editor.c b/contrib/subversion/subversion/libsvn_ra/editor.c
new file mode 100644
index 0000000..ac969ca
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra/editor.c
@@ -0,0 +1,339 @@
+/*
+ * editor.c: compatibility editors
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+
+#include "private/svn_ra_private.h"
+#include "private/svn_delta_private.h"
+#include "private/svn_editor.h"
+
+#include "ra_loader.h"
+#include "svn_private_config.h"
+
+
+struct fp_baton {
+ svn_ra__provide_props_cb_t provide_props_cb;
+ void *cb_baton;
+};
+
+struct fb_baton {
+ svn_ra__provide_base_cb_t provide_base_cb;
+ void *cb_baton;
+};
+
+/* The shims currently want a callback that provides props for a given
+ REPOS_RELPATH at a given BASE_REVISION. However, the RA Ev2 interface
+ has a callback that provides properties for the REPOS_RELPATH from any
+ revision, which is returned along with the properties.
+
+ This is a little shim to map between the prototypes. The base revision
+ for the properties is discarded, and the requested revision (from the
+ shim code) is ignored.
+
+ The shim code needs to be updated to allow for an RA-style callback
+ to fetch properties. */
+static svn_error_t *
+fetch_props(apr_hash_t **props,
+ void *baton,
+ const char *repos_relpath,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct fp_baton *fpb = baton;
+ svn_revnum_t unused_revision;
+
+ /* Ignored: BASE_REVISION. */
+
+ return svn_error_trace(fpb->provide_props_cb(props, &unused_revision,
+ fpb->cb_baton,
+ repos_relpath,
+ result_pool, scratch_pool));
+}
+
+/* See note above regarding BASE_REVISION.
+ This also pulls down the entire contents of the file stream from the
+ RA layer and stores them in a local file, returning the path.
+*/
+static svn_error_t *
+fetch_base(const char **filename,
+ void *baton,
+ const char *repos_relpath,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct fb_baton *fbb = baton;
+ svn_revnum_t unused_revision;
+ svn_stream_t *contents;
+ svn_stream_t *file_stream;
+ const char *tmp_filename;
+
+ /* Ignored: BASE_REVISION. */
+
+ SVN_ERR(fbb->provide_base_cb(&contents, &unused_revision, fbb->cb_baton,
+ repos_relpath, result_pool, scratch_pool));
+
+ SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
+
+ *filename = apr_pstrdup(result_pool, tmp_filename);
+
+
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_ra__use_commit_shim(svn_editor_t **editor,
+ svn_ra_session_t *session,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
+ void *cb_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_delta_editor_t *deditor;
+ void *dedit_baton;
+ struct svn_delta__extra_baton *exb;
+ svn_delta__unlock_func_t unlock_func;
+ void *unlock_baton;
+ const char *repos_root;
+ const char *session_url;
+ const char *base_relpath;
+ svn_boolean_t *found_abs_paths;
+ struct fp_baton *fpb;
+
+ /* NOTE: PROVIDE_BASE_CB is currently unused by this shim. In the future,
+ we can pass it to the underlying Ev2/Ev1 shim to produce better
+ apply_txdelta drives (ie. against a base rather than <empty>). */
+
+ /* Fetch the RA provider's Ev1 commit editor. */
+ SVN_ERR(session->vtable->get_commit_editor(session, &deditor, &dedit_baton,
+ revprop_table,
+ commit_callback, commit_baton,
+ lock_tokens, keep_locks,
+ result_pool));
+
+ /* Get or calculate the appropriate repos root and base relpath. */
+ SVN_ERR(svn_ra_get_repos_root2(session, &repos_root, scratch_pool));
+ SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool));
+ base_relpath = svn_uri_skip_ancestor(repos_root, session_url, scratch_pool);
+
+ /* We will assume that when the underlying Ev1 editor is finally driven
+ by the shim, that we will not need to prepend "/" to the paths. Place
+ this on the heap because it is examined much later. Set to FALSE. */
+ found_abs_paths = apr_pcalloc(result_pool, sizeof(*found_abs_paths));
+
+ /* The PROVIDE_PROPS_CB callback does not match what the shims want.
+ Let's jigger things around a little bit here. */
+ fpb = apr_palloc(result_pool, sizeof(*fpb));
+ fpb->provide_props_cb = provide_props_cb;
+ fpb->cb_baton = cb_baton;
+
+ /* Create the Ev2 editor from the Ev1 editor provided by the RA layer.
+
+ Note: GET_COPYSRC_KIND_CB is compatible in type/semantics with the
+ shim's FETCH_KIND_FUNC parameter. */
+ SVN_ERR(svn_delta__editor_from_delta(editor, &exb,
+ &unlock_func, &unlock_baton,
+ deditor, dedit_baton,
+ found_abs_paths,
+ repos_root, base_relpath,
+ cancel_func, cancel_baton,
+ get_copysrc_kind_cb, cb_baton,
+ fetch_props, fpb,
+ result_pool, scratch_pool));
+
+ /* Note: UNLOCK_FUNC and UNLOCK_BATON are unused during commit drives.
+ We can safely drop them on the floor. */
+
+ /* Since we're (currently) just wrapping an existing Ev1 editor, we have
+ to call any start_edit handler it may provide (the shim uses this to
+ invoke Ev1's open_root callback). We've got a couple of options to do
+ so: Implement a wrapper editor and call the start_edit callback upon
+ the first invocation of any of the underlying editor's functions; or,
+ just assume our consumer is going to eventually use the editor it is
+ asking for, and call the start edit callback now. For simplicity's
+ sake, we do the latter. */
+ if (exb->start_edit)
+ {
+ /* Most commit drives pass SVN_INVALID_REVNUM for the revision.
+ All calls to svn_delta_path_driver() pass SVN_INVALID_REVNUM,
+ so this is fine for any commits done via that function.
+
+ Notably, the PROPSET command passes a specific revision. Before
+ PROPSET can use the RA Ev2 interface, we may need to make this
+ revision a parameter.
+ ### what are the exact semantics? what is the meaning of the
+ ### revision passed to the Ev1->open_root() callback? */
+ SVN_ERR(exb->start_edit(exb->baton, SVN_INVALID_REVNUM));
+ }
+
+ /* Note: EXB also contains a TARGET_REVISION function, but that is not
+ used during commit operations. We can safely ignore it. (ie. it is
+ in EXB for use by paired-shims) */
+
+ return SVN_NO_ERROR;
+}
+
+
+struct wrapped_replay_baton_t {
+ svn_ra__replay_revstart_ev2_callback_t revstart_func;
+ svn_ra__replay_revfinish_ev2_callback_t revfinish_func;
+ void *replay_baton;
+
+ svn_ra_session_t *session;
+
+ svn_ra__provide_base_cb_t provide_base_cb;
+ svn_ra__provide_props_cb_t provide_props_cb;
+ void *cb_baton;
+
+ /* This will be populated by the revstart wrapper. */
+ svn_editor_t *editor;
+};
+
+static svn_error_t *
+revstart_func_wrapper(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t **deditor,
+ void **dedit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *result_pool)
+{
+ struct wrapped_replay_baton_t *wrb = replay_baton;
+ const char *repos_root;
+ const char *session_url;
+ const char *base_relpath;
+ svn_boolean_t *found_abs_paths;
+ struct fp_baton *fpb;
+ struct svn_delta__extra_baton *exb;
+
+ /* Get the Ev2 editor from the original revstart func. */
+ SVN_ERR(wrb->revstart_func(revision, wrb->replay_baton, &wrb->editor,
+ rev_props, result_pool));
+
+ /* Get or calculate the appropriate repos root and base relpath. */
+ SVN_ERR(svn_ra_get_repos_root2(wrb->session, &repos_root, result_pool));
+ SVN_ERR(svn_ra_get_session_url(wrb->session, &session_url, result_pool));
+ base_relpath = svn_uri_skip_ancestor(repos_root, session_url, result_pool);
+
+ /* We will assume that when the underlying Ev1 editor is finally driven
+ by the shim, that we will not need to prepend "/" to the paths. Place
+ this on the heap because it is examined much later. Set to FALSE. */
+ found_abs_paths = apr_pcalloc(result_pool, sizeof(*found_abs_paths));
+
+ /* The PROVIDE_PROPS_CB callback does not match what the shims want.
+ Let's jigger things around a little bit here. */
+ fpb = apr_palloc(result_pool, sizeof(*fpb));
+ fpb->provide_props_cb = wrb->provide_props_cb;
+ fpb->cb_baton = wrb->cb_baton;
+
+ /* Create the extra baton. */
+ exb = apr_pcalloc(result_pool, sizeof(*exb));
+
+ /* Create the Ev1 editor from the Ev2 editor provided by the RA layer.
+
+ Note: GET_COPYSRC_KIND_CB is compatible in type/semantics with the
+ shim's FETCH_KIND_FUNC parameter. */
+ SVN_ERR(svn_delta__delta_from_editor(deditor, dedit_baton, wrb->editor,
+ NULL, NULL,
+ found_abs_paths,
+ repos_root, base_relpath,
+ fetch_props, wrb->cb_baton,
+ fetch_base, wrb->cb_baton,
+ exb, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+revfinish_func_wrapper(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ struct wrapped_replay_baton_t *wrb = replay_baton;
+
+ SVN_ERR(wrb->revfinish_func(revision, replay_baton, wrb->editor, rev_props,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra__use_replay_range_shim(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra__replay_revstart_ev2_callback_t revstart_func,
+ svn_ra__replay_revfinish_ev2_callback_t revfinish_func,
+ void *replay_baton,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ void *cb_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* The basic strategy here is to wrap the callback start and finish
+ functions to appropriately return an Ev1 editor which is itself wrapped
+ from the Ev2 one the provided callbacks will give us. */
+
+ struct wrapped_replay_baton_t *wrb = apr_pcalloc(scratch_pool, sizeof(*wrb));
+
+ wrb->revstart_func = revstart_func;
+ wrb->revfinish_func = revfinish_func;
+ wrb->replay_baton = replay_baton;
+ wrb->session = session;
+
+ wrb->provide_base_cb = provide_base_cb;
+ wrb->provide_props_cb = provide_props_cb;
+ wrb->cb_baton = cb_baton;
+
+ return svn_error_trace(svn_ra_replay_range(session, start_revision,
+ end_revision, low_water_mark,
+ send_deltas,
+ revstart_func_wrapper,
+ revfinish_func_wrapper,
+ wrb, scratch_pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_ra/ra_loader.c b/contrib/subversion/subversion/libsvn_ra/ra_loader.c
new file mode 100644
index 0000000..4afcb59
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra/ra_loader.c
@@ -0,0 +1,1576 @@
+/*
+ * ra_loader.c: logic for loading different RA library implementations
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+/*** Includes. ***/
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include <apr.h>
+#include <apr_strings.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_uri.h>
+
+#include "svn_hash.h"
+#include "svn_version.h"
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_pools.h"
+#include "svn_delta.h"
+#include "svn_ra.h"
+#include "svn_xml.h"
+#include "svn_path.h"
+#include "svn_dso.h"
+#include "svn_props.h"
+#include "svn_sorts.h"
+
+#include "svn_config.h"
+#include "ra_loader.h"
+#include "deprecated.h"
+
+#include "private/svn_ra_private.h"
+#include "svn_private_config.h"
+
+
+
+
+/* These are the URI schemes that the respective libraries *may* support.
+ * The schemes actually supported may be a subset of the schemes listed below.
+ * This can't be determine until the library is loaded.
+ * (Currently, this applies to the https scheme, which is only
+ * available if SSL is supported.) */
+static const char * const dav_schemes[] = { "http", "https", NULL };
+static const char * const svn_schemes[] = { "svn", NULL };
+static const char * const local_schemes[] = { "file", NULL };
+
+static const struct ra_lib_defn {
+ /* the name of this RA library (e.g. "neon" or "local") */
+ const char *ra_name;
+
+ const char * const *schemes;
+ /* the initialization function if linked in; otherwise, NULL */
+ svn_ra__init_func_t initfunc;
+ svn_ra_init_func_t compat_initfunc;
+} ra_libraries[] = {
+ {
+ "svn",
+ svn_schemes,
+#ifdef SVN_LIBSVN_CLIENT_LINKS_RA_SVN
+ svn_ra_svn__init,
+ svn_ra_svn__deprecated_init
+#endif
+ },
+
+ {
+ "local",
+ local_schemes,
+#ifdef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
+ svn_ra_local__init,
+ svn_ra_local__deprecated_init
+#endif
+ },
+
+ {
+ "serf",
+ dav_schemes,
+#ifdef SVN_LIBSVN_CLIENT_LINKS_RA_SERF
+ svn_ra_serf__init,
+ svn_ra_serf__deprecated_init
+#endif
+ },
+
+ /* ADD NEW RA IMPLEMENTATIONS HERE (as they're written) */
+
+ /* sentinel */
+ { NULL }
+};
+
+/* Ensure that the RA library NAME is loaded.
+ *
+ * If FUNC is non-NULL, set *FUNC to the address of the svn_ra_NAME__init
+ * function of the library.
+ *
+ * If COMPAT_FUNC is non-NULL, set *COMPAT_FUNC to the address of the
+ * svn_ra_NAME_init compatibility init function of the library.
+ *
+ * ### todo: Any RA libraries implemented from this point forward
+ * ### don't really need an svn_ra_NAME_init compatibility function.
+ * ### Currently, load_ra_module() will error if no such function is
+ * ### found, but it might be more friendly to simply set *COMPAT_FUNC
+ * ### to null (assuming COMPAT_FUNC itself is non-null).
+ */
+static svn_error_t *
+load_ra_module(svn_ra__init_func_t *func,
+ svn_ra_init_func_t *compat_func,
+ const char *ra_name, apr_pool_t *pool)
+{
+ if (func)
+ *func = NULL;
+ if (compat_func)
+ *compat_func = NULL;
+
+#if defined(SVN_USE_DSO) && APR_HAS_DSO
+ {
+ apr_dso_handle_t *dso;
+ apr_dso_handle_sym_t symbol;
+ const char *libname;
+ const char *funcname;
+ const char *compat_funcname;
+ apr_status_t status;
+
+ libname = apr_psprintf(pool, "libsvn_ra_%s-%d.so.%d",
+ ra_name, SVN_VER_MAJOR, SVN_SOVERSION);
+ funcname = apr_psprintf(pool, "svn_ra_%s__init", ra_name);
+ compat_funcname = apr_psprintf(pool, "svn_ra_%s_init", ra_name);
+
+ /* find/load the specified library */
+ SVN_ERR(svn_dso_load(&dso, libname));
+ if (! dso)
+ return SVN_NO_ERROR;
+
+ /* find the initialization routines */
+ if (func)
+ {
+ status = apr_dso_sym(&symbol, dso, funcname);
+ if (status)
+ {
+ return svn_error_wrap_apr(status,
+ _("'%s' does not define '%s()'"),
+ libname, funcname);
+ }
+
+ *func = (svn_ra__init_func_t) symbol;
+ }
+
+ if (compat_func)
+ {
+ status = apr_dso_sym(&symbol, dso, compat_funcname);
+ if (status)
+ {
+ return svn_error_wrap_apr(status,
+ _("'%s' does not define '%s()'"),
+ libname, compat_funcname);
+ }
+
+ *compat_func = (svn_ra_init_func_t) symbol;
+ }
+ }
+#endif /* APR_HAS_DSO */
+
+ return SVN_NO_ERROR;
+}
+
+/* If SCHEMES contains URL, return the scheme. Else, return NULL. */
+static const char *
+has_scheme_of(const char * const *schemes, const char *url)
+{
+ apr_size_t len;
+
+ for ( ; *schemes != NULL; ++schemes)
+ {
+ const char *scheme = *schemes;
+ len = strlen(scheme);
+ /* Case-insensitive comparison, per RFC 2396 section 3.1. Allow
+ URL to contain a trailing "+foo" section in the scheme, since
+ that's how we specify tunnel schemes in ra_svn. */
+ if (strncasecmp(scheme, url, len) == 0 &&
+ (url[len] == ':' || url[len] == '+'))
+ return scheme;
+ }
+
+ return NULL;
+}
+
+/* Return an error if RA_VERSION doesn't match the version of this library.
+ Use SCHEME in the error message to describe the library that was loaded. */
+static svn_error_t *
+check_ra_version(const svn_version_t *ra_version, const char *scheme)
+{
+ const svn_version_t *my_version = svn_ra_version();
+ if (!svn_ver_equal(my_version, ra_version))
+ return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
+ _("Mismatched RA version for '%s':"
+ " found %d.%d.%d%s,"
+ " expected %d.%d.%d%s"),
+ scheme,
+ my_version->major, my_version->minor,
+ my_version->patch, my_version->tag,
+ ra_version->major, ra_version->minor,
+ ra_version->patch, ra_version->tag);
+
+ return SVN_NO_ERROR;
+}
+
+/* -------------------------------------------------------------- */
+
+/*** Public Interfaces ***/
+
+svn_error_t *svn_ra_initialize(apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/* Please note: the implementation of svn_ra_create_callbacks is
+ * duplicated in libsvn_ra/wrapper_template.h:compat_open() . This
+ * duplication is intentional, is there to avoid a circular
+ * dependancy, and is justified in great length in the code of
+ * compat_open() in libsvn_ra/wrapper_template.h. If you modify the
+ * implementation of svn_ra_create_callbacks(), be sure to keep the
+ * code in wrapper_template.h:compat_open() in sync with your
+ * changes. */
+svn_error_t *
+svn_ra_create_callbacks(svn_ra_callbacks2_t **callbacks,
+ apr_pool_t *pool)
+{
+ *callbacks = apr_pcalloc(pool, sizeof(**callbacks));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_open4(svn_ra_session_t **session_p,
+ const char **corrected_url_p,
+ const char *repos_URL,
+ const char *uuid,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ apr_pool_t *sesspool = svn_pool_create(pool);
+ svn_ra_session_t *session;
+ const struct ra_lib_defn *defn;
+ const svn_ra__vtable_t *vtable = NULL;
+ svn_config_t *servers = NULL;
+ const char *server_group;
+ apr_uri_t repos_URI;
+ apr_status_t apr_err;
+#ifdef CHOOSABLE_DAV_MODULE
+ const char *http_library = DEFAULT_HTTP_LIBRARY;
+#endif
+ /* Auth caching parameters. */
+ svn_boolean_t store_passwords = SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS;
+ svn_boolean_t store_auth_creds = SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS;
+ const char *store_plaintext_passwords
+ = SVN_CONFIG_DEFAULT_OPTION_STORE_PLAINTEXT_PASSWORDS;
+ svn_boolean_t store_pp = SVN_CONFIG_DEFAULT_OPTION_STORE_SSL_CLIENT_CERT_PP;
+ const char *store_pp_plaintext
+ = SVN_CONFIG_DEFAULT_OPTION_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT;
+ const char *corrected_url;
+
+ /* Initialize the return variable. */
+ *session_p = NULL;
+
+ apr_err = apr_uri_parse(sesspool, repos_URL, &repos_URI);
+ /* ### Should apr_uri_parse leave hostname NULL? It doesn't
+ * for "file:///" URLs, only for bogus URLs like "bogus".
+ * If this is the right behavior for apr_uri_parse, maybe we
+ * should have a svn_uri_parse wrapper. */
+ if (apr_err != APR_SUCCESS || repos_URI.hostname == NULL)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal repository URL '%s'"),
+ repos_URL);
+
+ if (callbacks->auth_baton)
+ {
+ /* The 'store-passwords' and 'store-auth-creds' parameters used to
+ * live in SVN_CONFIG_CATEGORY_CONFIG. For backward compatibility,
+ * if values for these parameters have already been set by our
+ * callers, we use those values as defaults.
+ *
+ * Note that we can only catch the case where users explicitly set
+ * "store-passwords = no" or 'store-auth-creds = no".
+ *
+ * However, since the default value for both these options is
+ * currently (and has always been) "yes", users won't know
+ * the difference if they set "store-passwords = yes" or
+ * "store-auth-creds = yes" -- they'll get the expected behaviour.
+ */
+
+ if (svn_auth_get_parameter(callbacks->auth_baton,
+ SVN_AUTH_PARAM_DONT_STORE_PASSWORDS) != NULL)
+ store_passwords = FALSE;
+
+ if (svn_auth_get_parameter(callbacks->auth_baton,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE) != NULL)
+ store_auth_creds = FALSE;
+ }
+
+ if (config)
+ {
+ /* Grab the 'servers' config. */
+ servers = svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS);
+ if (servers)
+ {
+ /* First, look in the global section. */
+
+ SVN_ERR(svn_config_get_bool
+ (servers, &store_passwords, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_STORE_PASSWORDS,
+ store_passwords));
+
+ SVN_ERR(svn_config_get_yes_no_ask
+ (servers, &store_plaintext_passwords, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_STORE_PLAINTEXT_PASSWORDS,
+ SVN_CONFIG_DEFAULT_OPTION_STORE_PLAINTEXT_PASSWORDS));
+
+ SVN_ERR(svn_config_get_bool
+ (servers, &store_pp, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_STORE_SSL_CLIENT_CERT_PP,
+ store_pp));
+
+ SVN_ERR(svn_config_get_yes_no_ask
+ (servers, &store_pp_plaintext,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT,
+ SVN_CONFIG_DEFAULT_OPTION_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT));
+
+ SVN_ERR(svn_config_get_bool
+ (servers, &store_auth_creds, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
+ store_auth_creds));
+
+ /* Find out where we're about to connect to, and
+ * try to pick a server group based on the destination. */
+ server_group = svn_config_find_group(servers, repos_URI.hostname,
+ SVN_CONFIG_SECTION_GROUPS,
+ sesspool);
+
+ if (server_group)
+ {
+ /* Override global auth caching parameters with the ones
+ * for the server group, if any. */
+ SVN_ERR(svn_config_get_bool(servers, &store_auth_creds,
+ server_group,
+ SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
+ store_auth_creds));
+
+ SVN_ERR(svn_config_get_bool(servers, &store_passwords,
+ server_group,
+ SVN_CONFIG_OPTION_STORE_PASSWORDS,
+ store_passwords));
+
+ SVN_ERR(svn_config_get_yes_no_ask
+ (servers, &store_plaintext_passwords, server_group,
+ SVN_CONFIG_OPTION_STORE_PLAINTEXT_PASSWORDS,
+ store_plaintext_passwords));
+
+ SVN_ERR(svn_config_get_bool
+ (servers, &store_pp,
+ server_group, SVN_CONFIG_OPTION_STORE_SSL_CLIENT_CERT_PP,
+ store_pp));
+
+ SVN_ERR(svn_config_get_yes_no_ask
+ (servers, &store_pp_plaintext, server_group,
+ SVN_CONFIG_OPTION_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT,
+ store_pp_plaintext));
+ }
+#ifdef CHOOSABLE_DAV_MODULE
+ /* Now, which DAV-based RA method do we want to use today? */
+ http_library
+ = svn_config_get_server_setting(servers,
+ server_group, /* NULL is OK */
+ SVN_CONFIG_OPTION_HTTP_LIBRARY,
+ DEFAULT_HTTP_LIBRARY);
+
+ if (strcmp(http_library, "serf") != 0)
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: unknown HTTP library "
+ "'%s'"),
+ http_library);
+#endif
+ }
+ }
+
+ if (callbacks->auth_baton)
+ {
+ /* Save auth caching parameters in the auth parameter hash. */
+ if (! store_passwords)
+ svn_auth_set_parameter(callbacks->auth_baton,
+ SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, "");
+
+ svn_auth_set_parameter(callbacks->auth_baton,
+ SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS,
+ store_plaintext_passwords);
+
+ if (! store_pp)
+ svn_auth_set_parameter(callbacks->auth_baton,
+ SVN_AUTH_PARAM_DONT_STORE_SSL_CLIENT_CERT_PP,
+ "");
+
+ svn_auth_set_parameter(callbacks->auth_baton,
+ SVN_AUTH_PARAM_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT,
+ store_pp_plaintext);
+
+ if (! store_auth_creds)
+ svn_auth_set_parameter(callbacks->auth_baton,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE, "");
+ }
+
+ /* Find the library. */
+ for (defn = ra_libraries; defn->ra_name != NULL; ++defn)
+ {
+ const char *scheme;
+
+ if ((scheme = has_scheme_of(defn->schemes, repos_URL)))
+ {
+ svn_ra__init_func_t initfunc = defn->initfunc;
+
+#ifdef CHOOSABLE_DAV_MODULE
+ if (defn->schemes == dav_schemes
+ && strcmp(defn->ra_name, http_library) != 0)
+ continue;
+#endif
+
+ if (! initfunc)
+ SVN_ERR(load_ra_module(&initfunc, NULL, defn->ra_name,
+ sesspool));
+ if (! initfunc)
+ /* Library not found. */
+ continue;
+
+ SVN_ERR(initfunc(svn_ra_version(), &vtable, sesspool));
+
+ SVN_ERR(check_ra_version(vtable->get_version(), scheme));
+
+ if (! has_scheme_of(vtable->get_schemes(sesspool), repos_URL))
+ /* Library doesn't support the scheme at runtime. */
+ continue;
+
+
+ break;
+ }
+ }
+
+ if (vtable == NULL)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Unrecognized URL scheme for '%s'"),
+ repos_URL);
+
+ /* Create the session object. */
+ session = apr_pcalloc(sesspool, sizeof(*session));
+ session->cancel_func = callbacks->cancel_func;
+ session->cancel_baton = callback_baton;
+ session->vtable = vtable;
+ session->pool = sesspool;
+
+ /* Ask the library to open the session. */
+ SVN_ERR_W(vtable->open_session(session, &corrected_url, repos_URL,
+ callbacks, callback_baton, config, sesspool),
+ apr_psprintf(pool, "Unable to connect to a repository at URL '%s'",
+ repos_URL));
+
+ /* If the session open stuff detected a server-provided URL
+ correction (a 301 or 302 redirect response during the initial
+ OPTIONS request), then kill the session so the caller can decide
+ what to do. */
+ if (corrected_url_p && corrected_url)
+ {
+ if (! svn_path_is_url(corrected_url))
+ {
+ /* RFC1945 and RFC2616 state that the Location header's
+ value (from whence this CORRECTED_URL ultimately comes),
+ if present, must be an absolute URI. But some Apache
+ versions (those older than 2.2.11, it seems) transmit
+ only the path portion of the URI. See issue #3775 for
+ details. */
+ apr_uri_t corrected_URI = repos_URI;
+ corrected_URI.path = (char *)corrected_url;
+ corrected_url = apr_uri_unparse(pool, &corrected_URI, 0);
+ }
+ *corrected_url_p = svn_uri_canonicalize(corrected_url, pool);
+ svn_pool_destroy(sesspool);
+ return SVN_NO_ERROR;
+ }
+
+ /* Check the UUID. */
+ if (uuid)
+ {
+ const char *repository_uuid;
+
+ SVN_ERR(vtable->get_uuid(session, &repository_uuid, pool));
+ if (strcmp(uuid, repository_uuid) != 0)
+ {
+ /* Duplicate the uuid as it is allocated in sesspool */
+ repository_uuid = apr_pstrdup(pool, repository_uuid);
+ svn_pool_destroy(sesspool);
+ return svn_error_createf(SVN_ERR_RA_UUID_MISMATCH, NULL,
+ _("Repository UUID '%s' doesn't match "
+ "expected UUID '%s'"),
+ repository_uuid, uuid);
+ }
+ }
+
+ *session_p = session;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_reparent(svn_ra_session_t *session,
+ const char *url,
+ apr_pool_t *pool)
+{
+ const char *repos_root;
+
+ /* Make sure the new URL is in the same repository, so that the
+ implementations don't have to do it. */
+ SVN_ERR(svn_ra_get_repos_root2(session, &repos_root, pool));
+ if (! svn_uri__is_ancestor(repos_root, url))
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("'%s' isn't in the same repository as '%s'"),
+ url, repos_root);
+
+ return session->vtable->reparent(session, url, pool);
+}
+
+svn_error_t *svn_ra_get_session_url(svn_ra_session_t *session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ return session->vtable->get_session_url(session, url, pool);
+}
+
+svn_error_t *svn_ra_get_path_relative_to_session(svn_ra_session_t *session,
+ const char **rel_path,
+ const char *url,
+ apr_pool_t *pool)
+{
+ const char *sess_url;
+
+ SVN_ERR(session->vtable->get_session_url(session, &sess_url, pool));
+ *rel_path = svn_uri_skip_ancestor(sess_url, url, pool);
+ if (! *rel_path)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("'%s' isn't a child of session URL '%s'"),
+ url, sess_url);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_get_path_relative_to_root(svn_ra_session_t *session,
+ const char **rel_path,
+ const char *url,
+ apr_pool_t *pool)
+{
+ const char *root_url;
+
+ SVN_ERR(session->vtable->get_repos_root(session, &root_url, pool));
+ *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
+ if (! *rel_path)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("'%s' isn't a child of repository root "
+ "URL '%s'"),
+ url, root_url);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_get_latest_revnum(svn_ra_session_t *session,
+ svn_revnum_t *latest_revnum,
+ apr_pool_t *pool)
+{
+ return session->vtable->get_latest_revnum(session, latest_revnum, pool);
+}
+
+svn_error_t *svn_ra_get_dated_revision(svn_ra_session_t *session,
+ svn_revnum_t *revision,
+ apr_time_t tm,
+ apr_pool_t *pool)
+{
+ return session->vtable->get_dated_revision(session, revision, tm, pool);
+}
+
+svn_error_t *svn_ra_change_rev_prop2(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(rev));
+
+ /* If an old value was specified, make sure the server supports
+ * specifying it. */
+ if (old_value_p)
+ {
+ svn_boolean_t has_atomic_revprops;
+
+ SVN_ERR(svn_ra_has_capability(session, &has_atomic_revprops,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ pool));
+
+ if (!has_atomic_revprops)
+ /* API violation. (Should be an ASSERT, but gstein talked me
+ * out of it.) */
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Specifying 'old_value_p' is not allowed when "
+ "the '%s' capability is not advertised, and "
+ "could indicate a bug in your client"),
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS);
+ }
+
+ return session->vtable->change_rev_prop(session, rev, name,
+ old_value_p, value, pool);
+}
+
+svn_error_t *svn_ra_rev_proplist(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(rev));
+ return session->vtable->rev_proplist(session, rev, props, pool);
+}
+
+svn_error_t *svn_ra_rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ svn_string_t **value,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(rev));
+ return session->vtable->rev_prop(session, rev, name, value, pool);
+}
+
+struct ccw_baton
+{
+ svn_commit_callback2_t original_callback;
+ void *original_baton;
+
+ svn_ra_session_t *session;
+};
+
+/* Wrapper which populates the repos_root field of the commit_info struct */
+static svn_error_t *
+commit_callback_wrapper(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct ccw_baton *ccwb = baton;
+ svn_commit_info_t *ci = svn_commit_info_dup(commit_info, pool);
+
+ SVN_ERR(svn_ra_get_repos_root2(ccwb->session, &ci->repos_root, pool));
+
+ return ccwb->original_callback(ci, ccwb->original_baton, pool);
+}
+
+
+/* Some RA layers do not correctly fill in REPOS_ROOT in commit_info, or
+ they are third-party layers conforming to an older commit_info structure.
+ Interpose a utility function to ensure the field is valid. */
+static void
+remap_commit_callback(svn_commit_callback2_t *callback,
+ void **callback_baton,
+ svn_ra_session_t *session,
+ svn_commit_callback2_t original_callback,
+ void *original_baton,
+ apr_pool_t *result_pool)
+{
+ if (original_callback == NULL)
+ {
+ *callback = NULL;
+ *callback_baton = NULL;
+ }
+ else
+ {
+ /* Allocate this in RESULT_POOL, since the callback will be called
+ long after this function has returned. */
+ struct ccw_baton *ccwb = apr_palloc(result_pool, sizeof(*ccwb));
+
+ ccwb->session = session;
+ ccwb->original_callback = original_callback;
+ ccwb->original_baton = original_baton;
+
+ *callback = commit_callback_wrapper;
+ *callback_baton = ccwb;
+ }
+}
+
+
+svn_error_t *svn_ra_get_commit_editor3(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool)
+{
+ remap_commit_callback(&commit_callback, &commit_baton,
+ session, commit_callback, commit_baton,
+ pool);
+
+ return session->vtable->get_commit_editor(session, editor, edit_baton,
+ revprop_table,
+ commit_callback, commit_baton,
+ lock_tokens, keep_locks, pool);
+}
+
+svn_error_t *svn_ra_get_file(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ return session->vtable->get_file(session, path, revision, stream,
+ fetched_rev, props, pool);
+}
+
+svn_error_t *svn_ra_get_dir2(svn_ra_session_t *session,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ const char *path,
+ svn_revnum_t revision,
+ apr_uint32_t dirent_fields,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ return session->vtable->get_dir(session, dirents, fetched_rev, props,
+ path, revision, dirent_fields, pool);
+}
+
+svn_error_t *svn_ra_get_mergeinfo(svn_ra_session_t *session,
+ svn_mergeinfo_catalog_t *catalog,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ int i;
+
+ /* Validate path format. */
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ }
+
+ /* Check server Merge Tracking capability. */
+ err = svn_ra__assert_mergeinfo_capable_server(session, NULL, pool);
+ if (err)
+ {
+ *catalog = NULL;
+ return err;
+ }
+
+ return session->vtable->get_mergeinfo(session, catalog, paths,
+ revision, inherit,
+ include_descendants, pool);
+}
+
+svn_error_t *
+svn_ra_do_update3(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_path_is_empty(update_target)
+ || svn_path_is_single_path_component(update_target));
+ return session->vtable->do_update(session,
+ reporter, report_baton,
+ revision_to_update_to, update_target,
+ depth, send_copyfrom_args,
+ ignore_ancestry,
+ update_editor, update_baton,
+ result_pool, scratch_pool);
+}
+
+svn_error_t *
+svn_ra_do_switch3(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_depth_t depth,
+ const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_path_is_empty(switch_target)
+ || svn_path_is_single_path_component(switch_target));
+ return session->vtable->do_switch(session,
+ reporter, report_baton,
+ revision_to_switch_to, switch_target,
+ depth, switch_url,
+ send_copyfrom_args,
+ ignore_ancestry,
+ switch_editor,
+ switch_baton,
+ result_pool, scratch_pool);
+}
+
+svn_error_t *svn_ra_do_status2(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(svn_path_is_empty(status_target)
+ || svn_path_is_single_path_component(status_target));
+ return session->vtable->do_status(session,
+ reporter, report_baton,
+ status_target, revision, depth,
+ status_editor, status_baton, pool);
+}
+
+svn_error_t *svn_ra_do_diff3(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(svn_path_is_empty(diff_target)
+ || svn_path_is_single_path_component(diff_target));
+ return session->vtable->do_diff(session,
+ reporter, report_baton,
+ revision, diff_target,
+ depth, ignore_ancestry,
+ text_deltas, versus_url, diff_editor,
+ diff_baton, pool);
+}
+
+svn_error_t *svn_ra_get_log2(svn_ra_session_t *session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ if (paths)
+ {
+ int i;
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ }
+ }
+
+ if (include_merged_revisions)
+ SVN_ERR(svn_ra__assert_mergeinfo_capable_server(session, NULL, pool));
+
+ return session->vtable->get_log(session, paths, start, end, limit,
+ discover_changed_paths, strict_node_history,
+ include_merged_revisions, revprops,
+ receiver, receiver_baton, pool);
+}
+
+svn_error_t *svn_ra_check_path(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ return session->vtable->check_path(session, path, revision, kind, pool);
+}
+
+svn_error_t *svn_ra_stat(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_dirent_t **dirent,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ return session->vtable->stat(session, path, revision, dirent, pool);
+}
+
+svn_error_t *svn_ra_get_uuid2(svn_ra_session_t *session,
+ const char **uuid,
+ apr_pool_t *pool)
+{
+ SVN_ERR(session->vtable->get_uuid(session, uuid, pool));
+ *uuid = *uuid ? apr_pstrdup(pool, *uuid) : NULL;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_get_uuid(svn_ra_session_t *session,
+ const char **uuid,
+ apr_pool_t *pool)
+{
+ return session->vtable->get_uuid(session, uuid, pool);
+}
+
+svn_error_t *svn_ra_get_repos_root2(svn_ra_session_t *session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ SVN_ERR(session->vtable->get_repos_root(session, url, pool));
+ *url = *url ? apr_pstrdup(pool, *url) : NULL;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_get_repos_root(svn_ra_session_t *session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ return session->vtable->get_repos_root(session, url, pool);
+}
+
+svn_error_t *svn_ra_get_locations(svn_ra_session_t *session,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ err = session->vtable->get_locations(session, locations, path,
+ peg_revision, location_revisions, pool);
+ if (err && (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED))
+ {
+ svn_error_clear(err);
+
+ /* Do it the slow way, using get-logs, for older servers. */
+ err = svn_ra__locations_from_log(session, locations, path,
+ peg_revision, location_revisions,
+ pool);
+ }
+ return err;
+}
+
+svn_error_t *
+svn_ra_get_location_segments(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ err = session->vtable->get_location_segments(session, path, peg_revision,
+ start_rev, end_rev,
+ receiver, receiver_baton, pool);
+ if (err && (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED))
+ {
+ svn_error_clear(err);
+
+ /* Do it the slow way, using get-logs, for older servers. */
+ err = svn_ra__location_segments_from_log(session, path,
+ peg_revision, start_rev,
+ end_rev, receiver,
+ receiver_baton, pool);
+ }
+ return err;
+}
+
+svn_error_t *svn_ra_get_file_revs2(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+
+ if (include_merged_revisions)
+ SVN_ERR(svn_ra__assert_mergeinfo_capable_server(session, NULL, pool));
+
+ err = session->vtable->get_file_revs(session, path, start, end,
+ include_merged_revisions,
+ handler, handler_baton, pool);
+ if (err && (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED))
+ {
+ svn_error_clear(err);
+
+ /* Do it the slow way, using get-logs, for older servers. */
+ err = svn_ra__file_revs_from_log(session, path, start, end,
+ handler, handler_baton, pool);
+ }
+ return err;
+}
+
+svn_error_t *svn_ra_lock(svn_ra_session_t *session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ }
+
+ if (comment && ! svn_xml_is_xml_safe(comment, strlen(comment)))
+ return svn_error_create
+ (SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
+ _("Lock comment contains illegal characters"));
+
+ return session->vtable->lock(session, path_revs, comment, steal_lock,
+ lock_func, lock_baton, pool);
+}
+
+svn_error_t *svn_ra_unlock(svn_ra_session_t *session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t break_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ }
+
+ return session->vtable->unlock(session, path_tokens, break_lock,
+ lock_func, lock_baton, pool);
+}
+
+svn_error_t *svn_ra_get_lock(svn_ra_session_t *session,
+ svn_lock_t **lock,
+ const char *path,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ return session->vtable->get_lock(session, lock, path, pool);
+}
+
+svn_error_t *svn_ra_get_locks2(svn_ra_session_t *session,
+ apr_hash_t **locks,
+ const char *path,
+ svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+ SVN_ERR_ASSERT((depth == svn_depth_empty) ||
+ (depth == svn_depth_files) ||
+ (depth == svn_depth_immediates) ||
+ (depth == svn_depth_infinity));
+ return session->vtable->get_locks(session, locks, path, depth, pool);
+}
+
+svn_error_t *svn_ra_get_locks(svn_ra_session_t *session,
+ apr_hash_t **locks,
+ const char *path,
+ apr_pool_t *pool)
+{
+ return svn_ra_get_locks2(session, locks, path, svn_depth_infinity, pool);
+}
+
+svn_error_t *svn_ra_replay(svn_ra_session_t *session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t text_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool)
+{
+ return session->vtable->replay(session, revision, low_water_mark,
+ text_deltas, editor, edit_baton, pool);
+}
+
+svn_error_t *
+svn_ra__replay_ev2(svn_ra_session_t *session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_editor_t *editor,
+ apr_pool_t *scratch_pool)
+{
+ SVN__NOT_IMPLEMENTED();
+}
+
+static svn_error_t *
+replay_range_from_replays(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t text_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_revnum_t rev;
+
+ for (rev = start_revision ; rev <= end_revision ; rev++)
+ {
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ apr_hash_t *rev_props;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_ra_rev_proplist(session, rev, &rev_props, iterpool));
+
+ SVN_ERR(revstart_func(rev, replay_baton,
+ &editor, &edit_baton,
+ rev_props,
+ iterpool));
+ SVN_ERR(svn_ra_replay(session, rev, low_water_mark,
+ text_deltas, editor, edit_baton,
+ iterpool));
+ SVN_ERR(revfinish_func(rev, replay_baton,
+ editor, edit_baton,
+ rev_props,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_replay_range(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t text_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err =
+ session->vtable->replay_range(session, start_revision, end_revision,
+ low_water_mark, text_deltas,
+ revstart_func, revfinish_func,
+ replay_baton, pool);
+
+ if (!err || (err && (err->apr_err != SVN_ERR_RA_NOT_IMPLEMENTED)))
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ return svn_error_trace(replay_range_from_replays(session, start_revision,
+ end_revision,
+ low_water_mark,
+ text_deltas,
+ revstart_func,
+ revfinish_func,
+ replay_baton, pool));
+}
+
+svn_error_t *
+svn_ra__replay_range_ev2(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra__replay_revstart_ev2_callback_t revstart_func,
+ svn_ra__replay_revfinish_ev2_callback_t revfinish_func,
+ void *replay_baton,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
+ void *cb_baton,
+ apr_pool_t *scratch_pool)
+{
+ if (session->vtable->replay_range_ev2 == NULL)
+ {
+ /* The specific RA layer does not have an implementation. Use our
+ default shim over the normal replay editor. */
+
+ /* This will call the Ev1 replay range handler with modified
+ callbacks. */
+ return svn_error_trace(svn_ra__use_replay_range_shim(
+ session,
+ start_revision,
+ end_revision,
+ low_water_mark,
+ send_deltas,
+ revstart_func,
+ revfinish_func,
+ replay_baton,
+ provide_base_cb,
+ provide_props_cb,
+ cb_baton,
+ scratch_pool));
+ }
+
+ return svn_error_trace(session->vtable->replay_range_ev2(
+ session, start_revision, end_revision,
+ low_water_mark, send_deltas, revstart_func,
+ revfinish_func, replay_baton, scratch_pool));
+}
+
+svn_error_t *svn_ra_has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ return session->vtable->has_capability(session, has, capability, pool);
+}
+
+svn_error_t *
+svn_ra_get_deleted_rev(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ /* Path must be relative. */
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+
+ if (!SVN_IS_VALID_REVNUM(peg_revision))
+ return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Invalid peg revision %ld"), peg_revision);
+ if (!SVN_IS_VALID_REVNUM(end_revision))
+ return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Invalid end revision %ld"), end_revision);
+ if (end_revision <= peg_revision)
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Peg revision must precede end revision"));
+ err = session->vtable->get_deleted_rev(session, path,
+ peg_revision,
+ end_revision,
+ revision_deleted,
+ pool);
+ if (err && (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))
+ {
+ svn_error_clear(err);
+
+ /* Do it the slow way, using get-logs, for older servers. */
+ err = svn_ra__get_deleted_rev_from_log(session, path, peg_revision,
+ end_revision, revision_deleted,
+ pool);
+ }
+ return err;
+}
+
+svn_error_t *
+svn_ra_get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t iprop_capable;
+
+ /* Path must be relative. */
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(path));
+
+ SVN_ERR(svn_ra_has_capability(session, &iprop_capable,
+ SVN_RA_CAPABILITY_INHERITED_PROPS,
+ scratch_pool));
+
+ if (iprop_capable)
+ {
+ SVN_ERR(session->vtable->get_inherited_props(session, iprops, path,
+ revision, result_pool,
+ scratch_pool));
+ }
+ else
+ {
+ /* Fallback for legacy servers. */
+ SVN_ERR(svn_ra__get_inherited_props_walk(session, path, revision, iprops,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra__get_commit_ev2(svn_editor_t **editor,
+ svn_ra_session_t *session,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
+ void *cb_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (session->vtable->get_commit_ev2 == NULL)
+ {
+ /* The specific RA layer does not have an implementation. Use our
+ default shim over the normal commit editor. */
+
+ /* Remap for RA layers exposing Ev1. */
+ remap_commit_callback(&commit_callback, &commit_baton,
+ session, commit_callback, commit_baton,
+ result_pool);
+
+ return svn_error_trace(svn_ra__use_commit_shim(
+ editor,
+ session,
+ revprop_table,
+ commit_callback, commit_baton,
+ lock_tokens,
+ keep_locks,
+ provide_base_cb,
+ provide_props_cb,
+ get_copysrc_kind_cb,
+ cb_baton,
+ session->cancel_func, session->cancel_baton,
+ result_pool, scratch_pool));
+ }
+
+ /* Note: no need to remap the callback for Ev2. RA layers providing this
+ vtable entry should completely fill in commit_info. */
+
+ return svn_error_trace(session->vtable->get_commit_ev2(
+ editor,
+ session,
+ revprop_table,
+ commit_callback, commit_baton,
+ lock_tokens,
+ keep_locks,
+ provide_base_cb,
+ provide_props_cb,
+ get_copysrc_kind_cb,
+ cb_baton,
+ session->cancel_func, session->cancel_baton,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_ra_print_modules(svn_stringbuf_t *output,
+ apr_pool_t *pool)
+{
+ const struct ra_lib_defn *defn;
+ const char * const *schemes;
+ svn_ra__init_func_t initfunc;
+ const svn_ra__vtable_t *vtable;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (defn = ra_libraries; defn->ra_name != NULL; ++defn)
+ {
+ char *line;
+
+ svn_pool_clear(iterpool);
+
+ initfunc = defn->initfunc;
+ if (! initfunc)
+ SVN_ERR(load_ra_module(&initfunc, NULL, defn->ra_name,
+ iterpool));
+
+ if (initfunc)
+ {
+ SVN_ERR(initfunc(svn_ra_version(), &vtable, iterpool));
+
+ SVN_ERR(check_ra_version(vtable->get_version(), defn->ra_name));
+
+ /* Note: if you change the formatting of the description,
+ bear in mind that ra_svn's description has multiple lines when
+ built with SASL. */
+ line = apr_psprintf(iterpool, "* ra_%s : %s\n",
+ defn->ra_name,
+ vtable->get_description());
+ svn_stringbuf_appendcstr(output, line);
+
+ for (schemes = vtable->get_schemes(iterpool); *schemes != NULL;
+ ++schemes)
+ {
+ line = apr_psprintf(iterpool, _(" - handles '%s' scheme\n"),
+ *schemes);
+ svn_stringbuf_appendcstr(output, line);
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_print_ra_libraries(svn_stringbuf_t **descriptions,
+ void *ra_baton,
+ apr_pool_t *pool)
+{
+ *descriptions = svn_stringbuf_create_empty(pool);
+ return svn_ra_print_modules(*descriptions, pool);
+}
+
+
+svn_error_t *
+svn_ra__register_editor_shim_callbacks(svn_ra_session_t *session,
+ svn_delta_shim_callbacks_t *callbacks)
+{
+ SVN_ERR(session->vtable->register_editor_shim_callbacks(session, callbacks));
+ return SVN_NO_ERROR;
+}
+
+
+/* Return the library version number. */
+const svn_version_t *
+svn_ra_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+
+/*** Compatibility Interfaces **/
+svn_error_t *
+svn_ra_init_ra_libs(void **ra_baton,
+ apr_pool_t *pool)
+{
+ *ra_baton = pool;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_get_ra_library(svn_ra_plugin_t **library,
+ void *ra_baton,
+ const char *url,
+ apr_pool_t *pool)
+{
+ const struct ra_lib_defn *defn;
+ apr_pool_t *load_pool = ra_baton;
+ apr_hash_t *ht = apr_hash_make(pool);
+
+ /* Figure out which RA library key matches URL. */
+ for (defn = ra_libraries; defn->ra_name != NULL; ++defn)
+ {
+ const char *scheme;
+ if ((scheme = has_scheme_of(defn->schemes, url)))
+ {
+ svn_ra_init_func_t compat_initfunc = defn->compat_initfunc;
+
+ if (! compat_initfunc)
+ {
+ SVN_ERR(load_ra_module
+ (NULL, &compat_initfunc, defn->ra_name, load_pool));
+ }
+ if (! compat_initfunc)
+ {
+ continue;
+ }
+
+ SVN_ERR(compat_initfunc(SVN_RA_ABI_VERSION, load_pool, ht));
+
+ *library = svn_hash_gets(ht, scheme);
+
+ /* The library may support just a subset of the schemes listed,
+ so we have to check here too. */
+ if (! *library)
+ break;
+
+ return check_ra_version((*library)->get_version(), scheme);
+ }
+ }
+
+ /* Couldn't find a match... */
+ *library = NULL;
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Unrecognized URL scheme '%s'"), url);
+}
+
+/* For each libsvn_ra_foo library that is not linked in, provide a default
+ implementation for svn_ra_foo_init which returns a "not implemented"
+ error. */
+
+#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_NEON
+svn_error_t *
+svn_ra_dav_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash)
+{
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+#endif /* ! SVN_LIBSVN_CLIENT_LINKS_RA_NEON */
+
+#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_SVN
+svn_error_t *
+svn_ra_svn_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash)
+{
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+#endif /* ! SVN_LIBSVN_CLIENT_LINKS_RA_SVN */
+
+#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
+svn_error_t *
+svn_ra_local_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash)
+{
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+#endif /* ! SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL */
+
+#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_SERF
+svn_error_t *
+svn_ra_serf_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash)
+{
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+#endif /* ! SVN_LIBSVN_CLIENT_LINKS_RA_SERF */
diff --git a/contrib/subversion/subversion/libsvn_ra/ra_loader.h b/contrib/subversion/subversion/libsvn_ra/ra_loader.h
new file mode 100644
index 0000000..4d86121
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra/ra_loader.h
@@ -0,0 +1,562 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file ra_loader.h
+ * @brief structures related to repository access, private to libsvn_ra and the
+ * RA implementation libraries.
+ */
+
+
+
+#ifndef LIBSVN_RA_RA_LOADER_H
+#define LIBSVN_RA_RA_LOADER_H
+
+#include "svn_ra.h"
+
+#include "private/svn_ra_private.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The RA layer vtable. */
+typedef struct svn_ra__vtable_t {
+ /* This field should always remain first in the vtable. */
+ const svn_version_t *(*get_version)(void);
+
+ /* Return a short description of the RA implementation, as a localized
+ * string. */
+ const char *(*get_description)(void);
+
+ /* Return a list of actual URI schemes supported by this implementation.
+ * The returned array is NULL-terminated. */
+ const char * const *(*get_schemes)(apr_pool_t *pool);
+
+ /* Implementations of the public API functions. */
+
+ /* See svn_ra_open4(). */
+ /* All fields in SESSION, except priv, have been initialized by the
+ time this is called. SESSION->priv may be set by this function. */
+ svn_error_t *(*open_session)(svn_ra_session_t *session,
+ const char **corrected_url,
+ const char *session_URL,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool);
+ /* See svn_ra_reparent(). */
+ /* URL is guaranteed to have what get_repos_root() returns as a prefix. */
+ svn_error_t *(*reparent)(svn_ra_session_t *session,
+ const char *url,
+ apr_pool_t *pool);
+ /* See svn_ra_get_session_url(). */
+ svn_error_t *(*get_session_url)(svn_ra_session_t *session,
+ const char **url,
+ apr_pool_t *pool);
+ /* See svn_ra_get_latest_revnum(). */
+ svn_error_t *(*get_latest_revnum)(svn_ra_session_t *session,
+ svn_revnum_t *latest_revnum,
+ apr_pool_t *pool);
+ /* See svn_ra_get_dated_revision(). */
+ svn_error_t *(*get_dated_revision)(svn_ra_session_t *session,
+ svn_revnum_t *revision,
+ apr_time_t tm,
+ apr_pool_t *pool);
+ /* See svn_ra_change_rev_prop2(). */
+ svn_error_t *(*change_rev_prop)(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+ /* See svn_ra_rev_proplist(). */
+ svn_error_t *(*rev_proplist)(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ apr_hash_t **props,
+ apr_pool_t *pool);
+ /* See svn_ra_rev_prop(). */
+ svn_error_t *(*rev_prop)(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ svn_string_t **value,
+ apr_pool_t *pool);
+ /* See svn_ra_get_commit_editor3(). */
+ svn_error_t *(*get_commit_editor)(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool);
+ /* See svn_ra_get_file(). */
+ svn_error_t *(*get_file)(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool);
+ /* See svn_ra_get_dir2(). */
+ svn_error_t *(*get_dir)(svn_ra_session_t *session,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ const char *path,
+ svn_revnum_t revision,
+ apr_uint32_t dirent_fields,
+ apr_pool_t *pool);
+ /* See svn_ra_get_mergeinfo(). */
+ svn_error_t *(*get_mergeinfo)(svn_ra_session_t *session,
+ svn_mergeinfo_catalog_t *mergeinfo,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_merged_revisions,
+ apr_pool_t *pool);
+ /* See svn_ra_do_update3(). */
+ svn_error_t *(*do_update)(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+ /* See svn_ra_do_switch3(). */
+ svn_error_t *(*do_switch)(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_depth_t depth,
+ const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+ /* See svn_ra_do_status2(). */
+ svn_error_t *(*do_status)(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool);
+ /* See svn_ra_do_diff3(). */
+ svn_error_t *(*do_diff)(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool);
+ /* See svn_ra_get_log2(). */
+ svn_error_t *(*get_log)(svn_ra_session_t *session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+ /* See svn_ra_check_path(). */
+ svn_error_t *(*check_path)(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool);
+ /* See svn_ra_stat(). */
+ svn_error_t *(*stat)(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_dirent_t **dirent,
+ apr_pool_t *pool);
+ /* See svn_ra_get_uuid2(). */
+ svn_error_t *(*get_uuid)(svn_ra_session_t *session,
+ const char **uuid,
+ apr_pool_t *pool);
+ /* See svn_ra_get_repos_root2(). */
+ svn_error_t *(*get_repos_root)(svn_ra_session_t *session,
+ const char **url,
+ apr_pool_t *pool);
+ /* See svn_ra_get_locations(). */
+ svn_error_t *(*get_locations)(svn_ra_session_t *session,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool);
+ /* See svn_ra_get_location_segments(). */
+ svn_error_t *(*get_location_segments)(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t rcvr,
+ void *receiver_baton,
+ apr_pool_t *pool);
+ /* See svn_ra_get_file_revs2(). */
+ svn_error_t *(*get_file_revs)(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+ /* See svn_ra_lock(). */
+ svn_error_t *(*lock)(svn_ra_session_t *session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool);
+ /* See svn_ra_unlock(). */
+ svn_error_t *(*unlock)(svn_ra_session_t *session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool);
+ /* See svn_ra_get_lock(). */
+ svn_error_t *(*get_lock)(svn_ra_session_t *session,
+ svn_lock_t **lock,
+ const char *path,
+ apr_pool_t *pool);
+ /* See svn_ra_get_locks2(). */
+ svn_error_t *(*get_locks)(svn_ra_session_t *session,
+ apr_hash_t **locks,
+ const char *path,
+ svn_depth_t depth,
+ apr_pool_t *pool);
+ /* See svn_ra_replay(). */
+ svn_error_t *(*replay)(svn_ra_session_t *session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t text_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool);
+ /* See svn_ra_has_capability(). */
+ svn_error_t *(*has_capability)(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool);
+ /* See svn_ra_replay_range(). */
+ svn_error_t *
+ (*replay_range)(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t text_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *pool);
+ /* See svn_ra_get_deleted_rev(). */
+ svn_error_t *(*get_deleted_rev)(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool);
+
+ /* See svn_ra__register_editor_shim_callbacks() */
+ svn_error_t *(*register_editor_shim_callbacks)(svn_ra_session_t *session,
+ svn_delta_shim_callbacks_t *callbacks);
+ /* See svn_ra_get_inherited_props(). */
+ svn_error_t *(*get_inherited_props)(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+ /* See svn_ra__get_commit_ev2() */
+ svn_error_t *(*get_commit_ev2)(
+ svn_editor_t **editor,
+ svn_ra_session_t *session,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
+ void *cb_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+ /* See svn_ra__replay_range_ev2() */
+ svn_error_t *(*replay_range_ev2)(
+ svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra__replay_revstart_ev2_callback_t revstart_func,
+ svn_ra__replay_revfinish_ev2_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *scratch_pool);
+
+} svn_ra__vtable_t;
+
+/* The RA session object. */
+struct svn_ra_session_t {
+ const svn_ra__vtable_t *vtable;
+
+ /* Cancellation handlers consumers may want to use. */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ /* Pool used to manage this session. */
+ apr_pool_t *pool;
+
+ /* Private data for the RA implementation. */
+ void *priv;
+};
+
+/* Each libsvn_ra_foo defines a function named svn_ra_foo__init of this type.
+ *
+ * The LOADER_VERSION parameter must remain first in the list, and the
+ * function must use the C calling convention on all platforms, so that
+ * the init functions can safely read the version parameter.
+ *
+ * POOL will be available as long as this module is being used.
+ *
+ * ### need to force this to be __cdecl on Windows... how??
+ */
+typedef svn_error_t *
+(*svn_ra__init_func_t)(const svn_version_t *loader_version,
+ const svn_ra__vtable_t **vtable,
+ apr_pool_t *pool);
+
+/* Declarations of the init functions for the available RA libraries. */
+svn_error_t *svn_ra_local__init(const svn_version_t *loader_version,
+ const svn_ra__vtable_t **vtable,
+ apr_pool_t *pool);
+svn_error_t *svn_ra_svn__init(const svn_version_t *loader_version,
+ const svn_ra__vtable_t **vtable,
+ apr_pool_t *pool);
+svn_error_t *svn_ra_serf__init(const svn_version_t *loader_version,
+ const svn_ra__vtable_t **vtable,
+ apr_pool_t *pool);
+
+
+
+/*** Compat Functions ***/
+
+/**
+ * Set *LOCATIONS to the locations (at the repository revisions
+ * LOCATION_REVISIONS) of the file identified by PATH in PEG_REVISION.
+ * PATH is relative to the URL to which SESSION was opened.
+ * LOCATION_REVISIONS is an array of svn_revnum_t's. *LOCATIONS will
+ * be a mapping from the revisions to their appropriate absolute
+ * paths. If the file doesn't exist in a location_revision, that
+ * revision will be ignored.
+ *
+ * Use POOL for all allocations.
+ *
+ * NOTE: This function uses the RA get_log interfaces to do its work,
+ * as a fallback mechanism for servers which don't support the native
+ * get_locations API.
+ */
+svn_error_t *
+svn_ra__locations_from_log(svn_ra_session_t *session,
+ apr_hash_t **locations_p,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool);
+
+/**
+ * Call RECEIVER (with RECEIVER_BATON) for each segment in the
+ * location history of PATH in START_REV, working backwards in time
+ * from START_REV to END_REV.
+ *
+ * END_REV may be SVN_INVALID_REVNUM to indicate that you want to
+ * trace the history of the object to its origin.
+ *
+ * START_REV may be SVN_INVALID_REVNUM to indicate that you want to
+ * trace the history of the object beginning in the HEAD revision.
+ * Otherwise, START_REV must be younger than END_REV (unless END_REV
+ * is SVN_INVALID_REVNUM).
+ *
+ * Use POOL for all allocations.
+ *
+ * NOTE: This function uses the RA get_log interfaces to do its work,
+ * as a fallback mechanism for servers which don't support the native
+ * get_location_segments API.
+ */
+svn_error_t *
+svn_ra__location_segments_from_log(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+/**
+ * Retrieve a subset of the interesting revisions of a file PATH
+ * as seen in revision END (see svn_fs_history_prev() for a
+ * definition of "interesting revisions"). Invoke HANDLER with
+ * @a handler_baton as its first argument for each such revision.
+ * @a session is an open RA session. Use POOL for all allocations.
+ *
+ * If there is an interesting revision of the file that is less than or
+ * equal to START, the iteration will begin at that revision.
+ * Else, the iteration will begin at the first revision of the file in
+ * the repository, which has to be less than or equal to END. Note
+ * that if the function succeeds, HANDLER will have been called at
+ * least once.
+ *
+ * In a series of calls to HANDLER, the file contents for the first
+ * interesting revision will be provided as a text delta against the
+ * empty file. In the following calls, the delta will be against the
+ * fulltext contents for the previous call.
+ *
+ * NOTE: This function uses the RA get_log interfaces to do its work,
+ * as a fallback mechanism for servers which don't support the native
+ * get_location_segments API.
+ */
+svn_error_t *
+svn_ra__file_revs_from_log(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+
+/**
+ * Given a path REL_DELETED_PATH, relative to the URL of SESSION, which
+ * exists at PEG_REVISION, and an END_REVISION > PEG_REVISION at which
+ * REL_DELETED_PATH no longer exists, set *REVISION_DELETED to the revision
+ * REL_DELETED_PATH was first deleted or replaced, within the inclusive
+ * revision range defined by PEG_REVISION and END_REVISION.
+ *
+ * If REL_DELETED_PATH does not exist at PEG_REVISION or was not deleted prior
+ * to END_REVISION within the specified range, then set *REVISION_DELETED to
+ * SVN_INVALID_REVNUM. If PEG_REVISION or END_REVISION are invalid or if
+ * END_REVISION <= PEG_REVISION, then return SVN_ERR_CLIENT_BAD_REVISION.
+ *
+ * Use POOL for all allocations.
+ *
+ * NOTE: This function uses the RA get_log interfaces to do its work,
+ * as a fallback mechanism for servers which don't support the native
+ * get_deleted_rev API.
+ */
+svn_error_t *
+svn_ra__get_deleted_rev_from_log(svn_ra_session_t *session,
+ const char *rel_deleted_path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool);
+
+
+/**
+ * Fallback logic for svn_ra_get_inherited_props() when that API
+ * need to find PATH's inherited properties on a legacy server that
+ * doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS capability.
+ *
+ * All arguments are as per svn_ra_get_inherited_props().
+ */
+svn_error_t *
+svn_ra__get_inherited_props_walk(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ apr_array_header_t **inherited_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Utility function to provide a shim between a returned Ev2 and an RA
+ provider's Ev1-based commit editor.
+
+ See svn_ra__get_commit_ev2() for parameter semantics. */
+svn_error_t *
+svn_ra__use_commit_shim(svn_editor_t **editor,
+ svn_ra_session_t *session,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
+ void *cb_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Utility function to provide a shim between a returned Ev2 and an RA
+ provider's Ev1-based commit editor.
+
+ See svn_ra__replay_range_ev2() for parameter semantics. */
+svn_error_t *
+svn_ra__use_replay_range_shim(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra__replay_revstart_ev2_callback_t revstart_func,
+ svn_ra__replay_revfinish_ev2_callback_t revfinish_func,
+ void *replay_baton,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ void *cb_baton,
+ apr_pool_t *scratch_pool);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/contrib/subversion/subversion/libsvn_ra/util.c b/contrib/subversion/subversion/libsvn_ra/util.c
new file mode 100644
index 0000000..47e4865
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra/util.c
@@ -0,0 +1,242 @@
+/*
+ * util.c: Repository access utility routines.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+/*** Includes. ***/
+#include <apr_pools.h>
+#include <apr_network_io.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_ra.h"
+
+#include "svn_private_config.h"
+#include "private/svn_ra_private.h"
+
+svn_error_t *
+svn_ra__assert_mergeinfo_capable_server(svn_ra_session_t *ra_session,
+ const char *path_or_url,
+ apr_pool_t *pool)
+{
+ svn_boolean_t mergeinfo_capable;
+ SVN_ERR(svn_ra_has_capability(ra_session, &mergeinfo_capable,
+ SVN_RA_CAPABILITY_MERGEINFO, pool));
+ if (! mergeinfo_capable)
+ {
+ if (path_or_url == NULL)
+ {
+ svn_error_t *err = svn_ra_get_session_url(ra_session, &path_or_url,
+ pool);
+ if (err)
+ {
+ /* The SVN_ERR_UNSUPPORTED_FEATURE error is more important,
+ so dummy up the session's URL and chuck this error. */
+ svn_error_clear(err);
+ path_or_url = "<repository>";
+ }
+ }
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Retrieval of mergeinfo unsupported by '%s'"),
+ svn_path_is_url(path_or_url)
+ ? path_or_url
+ : svn_dirent_local_style(path_or_url, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Does ERR mean "the current value of the revprop isn't equal to
+ the *OLD_VALUE_P you gave me"?
+ */
+static svn_boolean_t is_atomicity_error(svn_error_t *err)
+{
+ return svn_error_find_cause(err, SVN_ERR_FS_PROP_BASEVALUE_MISMATCH) != NULL;
+}
+
+svn_error_t *
+svn_ra__release_operational_lock(svn_ra_session_t *session,
+ const char *lock_revprop_name,
+ const svn_string_t *mylocktoken,
+ apr_pool_t *scratch_pool)
+{
+ svn_string_t *reposlocktoken;
+ svn_boolean_t be_atomic;
+
+ SVN_ERR(svn_ra_has_capability(session, &be_atomic,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ scratch_pool));
+ SVN_ERR(svn_ra_rev_prop(session, 0, lock_revprop_name,
+ &reposlocktoken, scratch_pool));
+ if (reposlocktoken && svn_string_compare(reposlocktoken, mylocktoken))
+ {
+ svn_error_t *err;
+
+ err = svn_ra_change_rev_prop2(session, 0, lock_revprop_name,
+ be_atomic ? &mylocktoken : NULL, NULL,
+ scratch_pool);
+ if (is_atomicity_error(err))
+ {
+ return svn_error_createf(err->apr_err, err,
+ _("Lock was stolen by '%s'; unable to "
+ "remove it"), reposlocktoken->data);
+ }
+ else
+ SVN_ERR(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra__get_operational_lock(const svn_string_t **lock_string_p,
+ const svn_string_t **stolen_lock_p,
+ svn_ra_session_t *session,
+ const char *lock_revprop_name,
+ svn_boolean_t steal_lock,
+ int num_retries,
+ svn_ra__lock_retry_func_t retry_func,
+ void *retry_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ char hostname_str[APRMAXHOSTLEN + 1] = { 0 };
+ svn_string_t *mylocktoken, *reposlocktoken;
+ apr_status_t apr_err;
+ svn_boolean_t be_atomic;
+ apr_pool_t *subpool;
+ int i;
+
+ *lock_string_p = NULL;
+ if (stolen_lock_p)
+ *stolen_lock_p = NULL;
+
+ SVN_ERR(svn_ra_has_capability(session, &be_atomic,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS, pool));
+
+ /* We build a lock token from the local hostname and a UUID. */
+ apr_err = apr_gethostname(hostname_str, sizeof(hostname_str), pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Unable to determine local hostname"));
+ mylocktoken = svn_string_createf(pool, "%s:%s", hostname_str,
+ svn_uuid_generate(pool));
+
+ /* Ye Olde Retry Loope */
+ subpool = svn_pool_create(pool);
+
+ for (i = 0; i < num_retries; ++i)
+ {
+ svn_error_t *err;
+ const svn_string_t *unset = NULL;
+
+ svn_pool_clear(subpool);
+
+ /* Check for cancellation. If we're cancelled, don't leave a
+ stray lock behind! */
+ if (cancel_func)
+ {
+ err = cancel_func(cancel_baton);
+ if (err && err->apr_err == SVN_ERR_CANCELLED)
+ return svn_error_compose_create(
+ svn_ra__release_operational_lock(session,
+ lock_revprop_name,
+ mylocktoken,
+ subpool),
+ err);
+ SVN_ERR(err);
+ }
+
+ /* Ask the repository for the value of the LOCK_REVPROP_NAME. */
+ SVN_ERR(svn_ra_rev_prop(session, 0, lock_revprop_name,
+ &reposlocktoken, subpool));
+
+ /* Did we get a value from the repository? We'll check to see
+ if it matches our token. If so, we call it success. If not
+ and we're told to steal locks, we remember the existing lock
+ token and fall through to the locking code; othewise, we
+ sleep and retry. */
+ if (reposlocktoken)
+ {
+ if (svn_string_compare(reposlocktoken, mylocktoken))
+ {
+ *lock_string_p = mylocktoken;
+ return SVN_NO_ERROR;
+ }
+ else if (! steal_lock)
+ {
+ if (retry_func)
+ SVN_ERR(retry_func(retry_baton, reposlocktoken, subpool));
+ apr_sleep(apr_time_from_sec(1));
+ continue;
+ }
+ else
+ {
+ if (stolen_lock_p)
+ *stolen_lock_p = svn_string_dup(reposlocktoken, pool);
+ unset = reposlocktoken;
+ }
+ }
+
+ /* No lock value in the repository, or we plan to steal it?
+ Well, if we've got a spare iteration, we'll try to set the
+ lock. (We use the spare iteration to verify that we still
+ have the lock after setting it.) */
+ if (i < num_retries - 1)
+ {
+ /* Except in the very last iteration, try to set the lock. */
+ err = svn_ra_change_rev_prop2(session, 0, lock_revprop_name,
+ be_atomic ? &unset : NULL,
+ mylocktoken, subpool);
+
+ if (be_atomic && err && is_atomicity_error(err))
+ {
+ /* Someone else has the lock. No problem, we'll loop again. */
+ svn_error_clear(err);
+ }
+ else if (be_atomic && err == SVN_NO_ERROR)
+ {
+ /* Yay! We have the lock! However, for compatibility
+ with concurrent processes that don't support
+ atomicity, loop anyway to double-check that they
+ haven't overwritten our lock.
+ */
+ continue;
+ }
+ else
+ {
+ /* We have a genuine error, or aren't atomic and need
+ to loop. */
+ SVN_ERR(err);
+ }
+ }
+ }
+
+ return svn_error_createf(APR_EINVAL, NULL,
+ _("Couldn't get lock on destination repos "
+ "after %d attempts"), i);
+}
diff --git a/contrib/subversion/subversion/libsvn_ra/wrapper_template.h b/contrib/subversion/subversion/libsvn_ra/wrapper_template.h
new file mode 100644
index 0000000..cee3fb3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra/wrapper_template.h
@@ -0,0 +1,512 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ */
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_time.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_props.h"
+#include "svn_compat.h"
+
+/* This file is a template for a compatibility wrapper for an RA library.
+ * It contains an svn_ra_plugin_t and wrappers for all of its functions,
+ * implemented in terms of svn_ra__vtable_t functions. It also contains
+ * the implementations of an svn_ra_FOO_init for the FOO RA library.
+ *
+ * A file in the RA library includes this file, providing the
+ * following macros before inclusion:
+ *
+ * NAME The library name, e.g. "ra_local".
+ * DESCRIPTION The short library description as a string constant.
+ * VTBL The name of an svn_ra_vtable_t object for the library.
+ * INITFUNC The init function for the library, e.g. svn_ra_local__init.
+ * COMPAT_INITFUNC The compatibility init function, e.g. svn_ra_local_init.
+ */
+
+/* Check that all our "arguments" are defined. */
+#if ! defined(NAME) || ! defined(DESCRIPTION) || ! defined(VTBL) \
+ || ! defined(INITFUNC) || ! defined(COMPAT_INITFUNC)
+#error Missing define for RA compatibility wrapper.
+#endif
+
+
+static svn_error_t *compat_open(void **session_baton,
+ const char *repos_URL,
+ const svn_ra_callbacks_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ /* Here, we should be calling svn_ra_create_callbacks to initialize
+ * the svn_ra_callbacks2_t structure. However, doing that
+ * introduces a circular dependancy between libsvn_ra and
+ * libsvn_ra_{local,neon,serf,svn}, which include
+ * wrapper_template.h. In turn, circular dependancies break the
+ * build on win32 (and possibly other systems).
+ *
+ * In order to avoid this happening at all, the code of
+ * svn_ra_create_callbacks is duplicated here. This is evil, but
+ * the alternative (creating a new ra_util library) would be massive
+ * overkill for the time being. Just be sure to keep the following
+ * line and the code of svn_ra_create_callbacks in sync. */
+ apr_pool_t *sesspool = svn_pool_create(pool);
+ svn_ra_callbacks2_t *callbacks2 = apr_pcalloc(sesspool,
+ sizeof(*callbacks2));
+
+ svn_ra_session_t *sess = apr_pcalloc(sesspool, sizeof(*sess));
+ const char *session_url;
+
+ sess->vtable = &VTBL;
+ sess->pool = sesspool;
+
+ callbacks2->open_tmp_file = callbacks->open_tmp_file;
+ callbacks2->auth_baton = callbacks->auth_baton;
+ callbacks2->get_wc_prop = callbacks->get_wc_prop;
+ callbacks2->set_wc_prop = callbacks->set_wc_prop;
+ callbacks2->push_wc_prop = callbacks->push_wc_prop;
+ callbacks2->invalidate_wc_props = callbacks->invalidate_wc_props;
+ callbacks2->progress_func = NULL;
+ callbacks2->progress_baton = NULL;
+
+ SVN_ERR(VTBL.open_session(sess, &session_url, repos_URL,
+ callbacks2, callback_baton, config, sesspool));
+
+ if (strcmp(repos_URL, session_url) != 0)
+ {
+ svn_pool_destroy(sesspool);
+ return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL,
+ _("Session URL '%s' does not match requested "
+ " URL '%s', and redirection was disallowed."),
+ session_url, repos_URL);
+ }
+
+ *session_baton = sess;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *compat_get_latest_revnum(void *session_baton,
+ svn_revnum_t *latest_revnum,
+ apr_pool_t *pool)
+{
+ return VTBL.get_latest_revnum(session_baton, latest_revnum, pool);
+}
+
+static svn_error_t *compat_get_dated_revision(void *session_baton,
+ svn_revnum_t *revision,
+ apr_time_t tm,
+ apr_pool_t *pool)
+{
+ return VTBL.get_dated_revision(session_baton, revision, tm, pool);
+}
+
+static svn_error_t *compat_change_rev_prop(void *session_baton,
+ svn_revnum_t rev,
+ const char *propname,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ return VTBL.change_rev_prop(session_baton, rev, propname, NULL, value, pool);
+}
+
+static svn_error_t *compat_rev_proplist(void *session_baton,
+ svn_revnum_t rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ return VTBL.rev_proplist(session_baton, rev, props, pool);
+}
+
+static svn_error_t *compat_rev_prop(void *session_baton,
+ svn_revnum_t rev,
+ const char *propname,
+ svn_string_t **value,
+ apr_pool_t *pool)
+{
+ return VTBL.rev_prop(session_baton, rev, propname, value, pool);
+}
+
+static svn_error_t *compat_get_commit_editor(void *session_baton,
+ const svn_delta_editor_t
+ **editor,
+ void **edit_baton,
+ const char *log_msg,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ apr_pool_t *pool)
+{
+ svn_commit_callback2_t callback2;
+ void *callback2_baton;
+ apr_hash_t *revprop_table = apr_hash_make(pool);
+
+ svn_compat_wrap_commit_callback(&callback2, &callback2_baton,
+ callback, callback_baton,
+ pool);
+ apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
+ svn_string_create(log_msg, pool));
+ return VTBL.get_commit_editor(session_baton, editor, edit_baton,
+ revprop_table, callback2, callback2_baton,
+ NULL, TRUE, pool);
+}
+
+static svn_error_t *compat_get_file(void *session_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ return VTBL.get_file(session_baton, path, revision, stream, fetched_rev,
+ props, pool);
+}
+
+static svn_error_t *compat_get_dir(void *session_baton,
+ const char *path,
+ svn_revnum_t revision,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ return VTBL.get_dir(session_baton, dirents, fetched_rev, props,
+ path, revision, SVN_DIRENT_ALL, pool);
+}
+
+/** Reporter compat code. **/
+
+struct compat_report_baton {
+ const svn_ra_reporter3_t *reporter;
+ void *baton;
+};
+
+static svn_error_t *compat_set_path(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ apr_pool_t *pool)
+{
+ struct compat_report_baton *crb = report_baton;
+
+ return crb->reporter->set_path(crb->baton, path, revision,
+ svn_depth_infinity, start_empty,
+ NULL, pool);
+}
+
+static svn_error_t *compat_delete_path(void *report_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct compat_report_baton *crb = report_baton;
+
+ return crb->reporter->delete_path(crb->baton, path, pool);
+}
+
+static svn_error_t *compat_link_path(void *report_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ apr_pool_t *pool)
+{
+ struct compat_report_baton *crb = report_baton;
+
+ return crb->reporter->link_path(crb->baton, path, url, revision,
+ svn_depth_infinity, start_empty,
+ NULL, pool);
+}
+
+static svn_error_t *compat_finish_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ struct compat_report_baton *crb = report_baton;
+
+ return crb->reporter->finish_report(crb->baton, pool);
+}
+
+static svn_error_t *compat_abort_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ struct compat_report_baton *crb = report_baton;
+
+ return crb->reporter->abort_report(crb->baton, pool);
+}
+
+static const svn_ra_reporter_t compat_reporter = {
+ compat_set_path,
+ compat_delete_path,
+ compat_link_path,
+ compat_finish_report,
+ compat_abort_report
+};
+
+static void compat_wrap_reporter(const svn_ra_reporter_t **reporter,
+ void **baton,
+ const svn_ra_reporter3_t *wrapped,
+ void *wrapped_baton,
+ apr_pool_t *pool)
+{
+ struct compat_report_baton *crb = apr_palloc(pool, sizeof(*crb));
+ crb->reporter = wrapped;
+ crb->baton = wrapped_baton;
+
+ *reporter = &compat_reporter;
+ *baton = crb;
+}
+
+static svn_error_t *compat_do_update(void *session_baton,
+ const svn_ra_reporter_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_boolean_t recurse,
+ const svn_delta_editor_t *editor,
+ void *update_baton,
+ apr_pool_t *pool)
+{
+ const svn_ra_reporter3_t *reporter3;
+ void *baton3;
+ svn_depth_t depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
+
+ SVN_ERR(VTBL.do_update(session_baton, &reporter3, &baton3,
+ revision_to_update_to, update_target, depth,
+ FALSE /* send_copyfrom_args */,
+ FALSE /* ignore_ancestry */,
+ editor, update_baton,
+ pool, pool));
+ compat_wrap_reporter(reporter, report_baton, reporter3, baton3, pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *compat_do_switch(void *session_baton,
+ const svn_ra_reporter_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_boolean_t recurse,
+ const char *switch_url,
+ const svn_delta_editor_t *editor,
+ void *switch_baton,
+ apr_pool_t *pool)
+{
+ const svn_ra_reporter3_t *reporter3;
+ void *baton3;
+ svn_depth_t depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
+
+ SVN_ERR(VTBL.do_switch(session_baton, &reporter3, &baton3,
+ revision_to_switch_to, switch_target, depth,
+ switch_url,
+ FALSE /* send_copyfrom_args */,
+ TRUE /* ignore_ancestry */,
+ editor, switch_baton,
+ pool /* result_pool */, pool /* scratch_pool */));
+
+ compat_wrap_reporter(reporter, report_baton, reporter3, baton3, pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *compat_do_status(void *session_baton,
+ const svn_ra_reporter_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_boolean_t recurse,
+ const svn_delta_editor_t *editor,
+ void *status_baton,
+ apr_pool_t *pool)
+{
+ const svn_ra_reporter3_t *reporter3;
+ void *baton3;
+ svn_depth_t depth = SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse);
+
+ SVN_ERR(VTBL.do_status(session_baton, &reporter3, &baton3, status_target,
+ revision, depth, editor, status_baton, pool));
+
+ compat_wrap_reporter(reporter, report_baton, reporter3, baton3, pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *compat_do_diff(void *session_baton,
+ const svn_ra_reporter_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool)
+{
+ const svn_ra_reporter3_t *reporter3;
+ void *baton3;
+ svn_depth_t depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
+
+ SVN_ERR(VTBL.do_diff(session_baton, &reporter3, &baton3, revision,
+ diff_target, depth, ignore_ancestry, TRUE,
+ versus_url, diff_editor, diff_baton, pool));
+
+ compat_wrap_reporter(reporter, report_baton, reporter3, baton3, pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *compat_get_log(void *session_baton,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ svn_log_entry_receiver_t receiver2;
+ void *receiver2_baton;
+
+ svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
+ receiver, receiver_baton,
+ pool);
+
+ return VTBL.get_log(session_baton, paths, start, end, 0, /* limit */
+ discover_changed_paths, strict_node_history,
+ FALSE, /* include_merged_revisions */
+ svn_compat_log_revprops_in(pool), /* revprops */
+ receiver2, receiver2_baton, pool);
+}
+
+static svn_error_t *compat_check_path(void *session_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ return VTBL.check_path(session_baton, path, revision, kind, pool);
+}
+
+static svn_error_t *compat_get_uuid(void *session_baton,
+ const char **uuid,
+ apr_pool_t *pool)
+{
+ return VTBL.get_uuid(session_baton, uuid, pool);
+}
+
+static svn_error_t *compat_get_repos_root(void *session_baton,
+ const char **url,
+ apr_pool_t *pool)
+{
+ return VTBL.get_repos_root(session_baton, url, pool);
+}
+
+static svn_error_t *compat_get_locations(void *session_baton,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ apr_array_header_t *location_revs,
+ apr_pool_t *pool)
+{
+ return VTBL.get_locations(session_baton, locations, path, peg_revision,
+ location_revs, pool);
+}
+
+static svn_error_t *compat_get_file_revs(void *session_baton,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_ra_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_file_rev_handler_t handler2;
+ void *handler2_baton;
+
+ svn_compat_wrap_file_rev_handler(&handler2, &handler2_baton,
+ handler, handler_baton,
+ pool);
+
+ return VTBL.get_file_revs(session_baton, path, start, end,
+ FALSE, /* include merged revisions */
+ handler2, handler2_baton, pool);
+}
+
+static const svn_version_t *compat_get_version(void)
+{
+ return VTBL.get_version();
+}
+
+
+static const svn_ra_plugin_t compat_plugin = {
+ NAME,
+ DESCRIPTION,
+ compat_open,
+ compat_get_latest_revnum,
+ compat_get_dated_revision,
+ compat_change_rev_prop,
+ compat_rev_proplist,
+ compat_rev_prop,
+ compat_get_commit_editor,
+ compat_get_file,
+ compat_get_dir,
+ compat_do_update,
+ compat_do_switch,
+ compat_do_status,
+ compat_do_diff,
+ compat_get_log,
+ compat_check_path,
+ compat_get_uuid,
+ compat_get_repos_root,
+ compat_get_locations,
+ compat_get_file_revs,
+ compat_get_version
+};
+
+svn_error_t *
+COMPAT_INITFUNC(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash)
+{
+ const svn_ra__vtable_t *vtable;
+ const char * const * schemes;
+
+ if (abi_version < 1
+ || abi_version > SVN_RA_ABI_VERSION)
+ return svn_error_createf(SVN_ERR_RA_UNSUPPORTED_ABI_VERSION, NULL,
+ _("Unsupported RA plugin ABI version (%d) "
+ "for %s"), abi_version, NAME);
+
+ /* We call the new init function so it can check library dependencies or
+ do other initialization things. We fake the loader version, since we
+ rely on the ABI version check instead. */
+ SVN_ERR(INITFUNC(VTBL.get_version(), &vtable, pool));
+
+ schemes = VTBL.get_schemes(pool);
+
+ for (; *schemes != NULL; ++schemes)
+ apr_hash_set(hash, *schemes, APR_HASH_KEY_STRING, &compat_plugin);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_local/ra_local.h b/contrib/subversion/subversion/libsvn_ra_local/ra_local.h
new file mode 100644
index 0000000..df455da
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_local/ra_local.h
@@ -0,0 +1,97 @@
+/*
+ * ra_local.h : shared internal declarations for ra_local module
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_RA_LOCAL_H
+#define SVN_LIBSVN_RA_LOCAL_H
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_types.h"
+#include "svn_fs.h"
+#include "svn_repos.h"
+#include "svn_ra.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/** Structures **/
+
+/* A baton which represents a single ra_local session. */
+typedef struct svn_ra_local__session_baton_t
+{
+ /* The user accessing the repository. */
+ const char *username;
+
+ /* The URL of the session, split into two components. */
+ const char *repos_url;
+ svn_stringbuf_t *fs_path; /* URI-decoded, always with a leading slash. */
+
+ /* A repository object. */
+ svn_repos_t *repos;
+
+ /* The filesystem object associated with REPOS above (for
+ convenience). */
+ svn_fs_t *fs;
+
+ /* The UUID associated with REPOS above (cached) */
+ const char *uuid;
+
+ /* Callbacks/baton passed to svn_ra_open. */
+ const svn_ra_callbacks2_t *callbacks;
+ void *callback_baton;
+} svn_ra_local__session_baton_t;
+
+
+
+
+/** Private routines **/
+
+
+
+
+/* Given a `file://' URL, figure out which portion specifies a
+ repository on local disk, and return that in REPOS_URL (if not
+ NULL); URI-decode and return the remainder (the path *within* the
+ repository's filesystem) in FS_PATH. Open REPOS to the repository
+ root (if not NULL). Allocate the return values in POOL.
+ Currently, we are not expecting to handle `file://hostname/'-type
+ URLs; hostname, in this case, is expected to be the empty string or
+ "localhost". */
+svn_error_t *
+svn_ra_local__split_URL(svn_repos_t **repos,
+ const char **repos_url,
+ const char **fs_path,
+ const char *URL,
+ apr_pool_t *pool);
+
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_RA_LOCAL_H */
diff --git a/contrib/subversion/subversion/libsvn_ra_local/ra_plugin.c b/contrib/subversion/subversion/libsvn_ra_local/ra_plugin.c
new file mode 100644
index 0000000..e7e8021
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_local/ra_plugin.c
@@ -0,0 +1,1766 @@
+/*
+ * ra_plugin.c : the main RA module for local repository access
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "ra_local.h"
+#include "svn_hash.h"
+#include "svn_ra.h"
+#include "svn_fs.h"
+#include "svn_delta.h"
+#include "svn_repos.h"
+#include "svn_pools.h"
+#include "svn_time.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "svn_path.h"
+#include "svn_version.h"
+#include "svn_cache_config.h"
+
+#include "svn_private_config.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_fspath.h"
+#include "private/svn_atomic.h"
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+/*----------------------------------------------------------------*/
+
+/*** Miscellaneous helper functions ***/
+
+
+/* Pool cleanup handler: ensure that the access descriptor of the
+ filesystem (svn_fs_t *) DATA is set to NULL. */
+static apr_status_t
+cleanup_access(void *data)
+{
+ svn_error_t *serr;
+ svn_fs_t *fs = data;
+
+ serr = svn_fs_set_access(fs, NULL);
+
+ if (serr)
+ {
+ apr_status_t apr_err = serr->apr_err;
+ svn_error_clear(serr);
+ return apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Fetch a username for use with SESSION, and store it in SESSION->username.
+ *
+ * Allocate the username in SESSION->pool. Use SCRATCH_POOL for temporary
+ * allocations. */
+static svn_error_t *
+get_username(svn_ra_session_t *session,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+
+ /* If we've already found the username don't ask for it again. */
+ if (! sess->username)
+ {
+ /* Get a username somehow, so we have some svn:author property to
+ attach to a commit. */
+ if (sess->callbacks->auth_baton)
+ {
+ void *creds;
+ svn_auth_cred_username_t *username_creds;
+ svn_auth_iterstate_t *iterstate;
+
+ SVN_ERR(svn_auth_first_credentials(&creds, &iterstate,
+ SVN_AUTH_CRED_USERNAME,
+ sess->uuid, /* realmstring */
+ sess->callbacks->auth_baton,
+ scratch_pool));
+
+ /* No point in calling next_creds(), since that assumes that the
+ first_creds() somehow failed to authenticate. But there's no
+ challenge going on, so we use whatever creds we get back on
+ the first try. */
+ username_creds = creds;
+ if (username_creds && username_creds->username)
+ {
+ sess->username = apr_pstrdup(session->pool,
+ username_creds->username);
+ svn_error_clear(svn_auth_save_credentials(iterstate,
+ scratch_pool));
+ }
+ else
+ sess->username = "";
+ }
+ else
+ sess->username = "";
+ }
+
+ /* If we have a real username, attach it to the filesystem so that it can
+ be used to validate locks. Even if there already is a user context
+ associated, it may contain irrelevant lock tokens, so always create a new.
+ */
+ if (*sess->username)
+ {
+ svn_fs_access_t *access_ctx;
+
+ SVN_ERR(svn_fs_create_access(&access_ctx, sess->username,
+ session->pool));
+ SVN_ERR(svn_fs_set_access(sess->fs, access_ctx));
+
+ /* Make sure this context is disassociated when the pool gets
+ destroyed. */
+ apr_pool_cleanup_register(session->pool, sess->fs, cleanup_access,
+ apr_pool_cleanup_null);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements an svn_atomic__init_once callback. Sets the FSFS memory
+ cache size. */
+static svn_error_t *
+cache_init(void *baton, apr_pool_t *pool)
+{
+ apr_hash_t *config_hash = baton;
+ svn_config_t *config = NULL;
+ const char *memory_cache_size_str;
+
+ if (config_hash)
+ config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
+ svn_config_get(config, &memory_cache_size_str, SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_MEMORY_CACHE_SIZE, NULL);
+ if (memory_cache_size_str)
+ {
+ apr_uint64_t memory_cache_size;
+ svn_cache_config_t settings = *svn_cache_config_get();
+
+ SVN_ERR(svn_error_quick_wrap(svn_cstring_atoui64(&memory_cache_size,
+ memory_cache_size_str),
+ _("memory-cache-size invalid")));
+ settings.cache_size = 1024 * 1024 * memory_cache_size;
+ svn_cache_config_set(&settings);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*----------------------------------------------------------------*/
+
+/*** The reporter vtable needed by do_update() and friends ***/
+
+typedef struct reporter_baton_t
+{
+ svn_ra_local__session_baton_t *sess;
+ void *report_baton;
+
+} reporter_baton_t;
+
+
+static void *
+make_reporter_baton(svn_ra_local__session_baton_t *sess,
+ void *report_baton,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = apr_palloc(pool, sizeof(*rbaton));
+ rbaton->sess = sess;
+ rbaton->report_baton = report_baton;
+ return rbaton;
+}
+
+
+static svn_error_t *
+reporter_set_path(void *reporter_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = reporter_baton;
+ return svn_repos_set_path3(rbaton->report_baton, path,
+ revision, depth, start_empty, lock_token, pool);
+}
+
+
+static svn_error_t *
+reporter_delete_path(void *reporter_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = reporter_baton;
+ return svn_repos_delete_path(rbaton->report_baton, path, pool);
+}
+
+
+static svn_error_t *
+reporter_link_path(void *reporter_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = reporter_baton;
+ const char *repos_url = rbaton->sess->repos_url;
+ const char *relpath = svn_uri_skip_ancestor(repos_url, url, pool);
+ const char *fs_path;
+
+ if (!relpath)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("'%s'\n"
+ "is not the same repository as\n"
+ "'%s'"), url, rbaton->sess->repos_url);
+
+ /* Convert the relpath to an fspath */
+ if (relpath[0] == '\0')
+ fs_path = "/";
+ else
+ fs_path = apr_pstrcat(pool, "/", relpath, (char *)NULL);
+
+ return svn_repos_link_path3(rbaton->report_baton, path, fs_path, revision,
+ depth, start_empty, lock_token, pool);
+}
+
+
+static svn_error_t *
+reporter_finish_report(void *reporter_baton,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = reporter_baton;
+ return svn_repos_finish_report(rbaton->report_baton, pool);
+}
+
+
+static svn_error_t *
+reporter_abort_report(void *reporter_baton,
+ apr_pool_t *pool)
+{
+ reporter_baton_t *rbaton = reporter_baton;
+ return svn_repos_abort_report(rbaton->report_baton, pool);
+}
+
+
+static const svn_ra_reporter3_t ra_local_reporter =
+{
+ reporter_set_path,
+ reporter_delete_path,
+ reporter_link_path,
+ reporter_finish_report,
+ reporter_abort_report
+};
+
+
+/* ...
+ *
+ * Wrap a cancellation editor using SESSION's cancellation function around
+ * the supplied EDITOR. ### Some callers (via svn_ra_do_update2() etc.)
+ * don't appear to know that we do this, and are supplying an editor that
+ * they have already wrapped with the same cancellation editor, so it ends
+ * up double-wrapped.
+ *
+ * Allocate @a *reporter and @a *report_baton in @a result_pool. Use
+ * @a scratch_pool for temporary allocations.
+ */
+static svn_error_t *
+make_reporter(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *target,
+ const char *other_url,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ void *rbaton;
+ const char *other_fs_path = NULL;
+
+ /* Get the HEAD revision if one is not supplied. */
+ if (! SVN_IS_VALID_REVNUM(revision))
+ SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, scratch_pool));
+
+ /* If OTHER_URL was provided, validate it and convert it into a
+ regular filesystem path. */
+ if (other_url)
+ {
+ const char *other_relpath
+ = svn_uri_skip_ancestor(sess->repos_url, other_url, scratch_pool);
+
+ /* Sanity check: the other_url better be in the same repository as
+ the original session url! */
+ if (! other_relpath)
+ return svn_error_createf
+ (SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("'%s'\n"
+ "is not the same repository as\n"
+ "'%s'"), other_url, sess->repos_url);
+
+ other_fs_path = apr_pstrcat(scratch_pool, "/", other_relpath,
+ (char *)NULL);
+ }
+
+ /* Pass back our reporter */
+ *reporter = &ra_local_reporter;
+
+ SVN_ERR(get_username(session, scratch_pool));
+
+ if (sess->callbacks)
+ SVN_ERR(svn_delta_get_cancellation_editor(sess->callbacks->cancel_func,
+ sess->callback_baton,
+ editor,
+ edit_baton,
+ &editor,
+ &edit_baton,
+ result_pool));
+
+ /* Build a reporter baton. */
+ SVN_ERR(svn_repos_begin_report3(&rbaton,
+ revision,
+ sess->repos,
+ sess->fs_path->data,
+ target,
+ other_fs_path,
+ text_deltas,
+ depth,
+ ignore_ancestry,
+ send_copyfrom_args,
+ editor,
+ edit_baton,
+ NULL,
+ NULL,
+ 1024 * 1024, /* process-local transfers
+ should be fast */
+ result_pool));
+
+ /* Wrap the report baton given us by the repos layer with our own
+ reporter baton. */
+ *report_baton = make_reporter_baton(sess, rbaton, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/*----------------------------------------------------------------*/
+
+/*** Deltification stuff for get_commit_editor() ***/
+
+struct deltify_etc_baton
+{
+ svn_fs_t *fs; /* the fs to deltify in */
+ svn_repos_t *repos; /* repos for unlocking */
+ const char *fspath_base; /* fs-path part of split session URL */
+
+ apr_hash_t *lock_tokens; /* tokens to unlock, if any */
+
+ svn_commit_callback2_t commit_cb; /* the original callback */
+ void *commit_baton; /* the original callback's baton */
+};
+
+/* This implements 'svn_commit_callback_t'. Its invokes the original
+ (wrapped) callback, but also does deltification on the new revision and
+ possibly unlocks committed paths.
+ BATON is 'struct deltify_etc_baton *'. */
+static svn_error_t *
+deltify_etc(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct deltify_etc_baton *deb = baton;
+ svn_error_t *err1 = SVN_NO_ERROR;
+ svn_error_t *err2;
+
+ /* Invoke the original callback first, in case someone's waiting to
+ know the revision number so they can go off and annotate an
+ issue or something. */
+ if (deb->commit_cb)
+ err1 = deb->commit_cb(commit_info, deb->commit_baton, scratch_pool);
+
+ /* Maybe unlock the paths. */
+ if (deb->lock_tokens)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, deb->lock_tokens); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *relpath = svn__apr_hash_index_key(hi);
+ const char *token = svn__apr_hash_index_val(hi);
+ const char *fspath;
+
+ svn_pool_clear(iterpool);
+
+ fspath = svn_fspath__join(deb->fspath_base, relpath, iterpool);
+
+ /* We may get errors here if the lock was broken or stolen
+ after the commit succeeded. This is fine and should be
+ ignored. */
+ svn_error_clear(svn_repos_fs_unlock(deb->repos, fspath, token,
+ FALSE, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ /* But, deltification shouldn't be stopped just because someone's
+ random callback failed, so proceed unconditionally on to
+ deltification. */
+ err2 = svn_fs_deltify_revision(deb->fs, commit_info->revision, scratch_pool);
+
+ return svn_error_compose_create(err1, err2);
+}
+
+
+/* If LOCK_TOKENS is not NULL, then copy all tokens into the access context
+ of FS. The tokens' paths will be prepended with FSPATH_BASE.
+
+ ACCESS_POOL must match (or exceed) the lifetime of the access context
+ that was associated with FS. Typically, this is the session pool.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+apply_lock_tokens(svn_fs_t *fs,
+ const char *fspath_base,
+ apr_hash_t *lock_tokens,
+ apr_pool_t *access_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (lock_tokens)
+ {
+ svn_fs_access_t *access_ctx;
+
+ SVN_ERR(svn_fs_get_access(&access_ctx, fs));
+
+ /* If there is no access context, the filesystem will scream if a
+ lock is needed. */
+ if (access_ctx)
+ {
+ apr_hash_index_t *hi;
+
+ /* Note: we have no use for an iterpool here since the data
+ within the loop is copied into ACCESS_POOL. */
+
+ for (hi = apr_hash_first(scratch_pool, lock_tokens); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *relpath = svn__apr_hash_index_key(hi);
+ const char *token = svn__apr_hash_index_val(hi);
+ const char *fspath;
+
+ /* The path needs to live as long as ACCESS_CTX. */
+ fspath = svn_fspath__join(fspath_base, relpath, access_pool);
+
+ /* The token must live as long as ACCESS_CTX. */
+ token = apr_pstrdup(access_pool, token);
+
+ SVN_ERR(svn_fs_access_add_lock_token2(access_ctx, fspath,
+ token));
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*----------------------------------------------------------------*/
+
+/*** The RA vtable routines ***/
+
+#define RA_LOCAL_DESCRIPTION \
+ N_("Module for accessing a repository on local disk.")
+
+static const char *
+svn_ra_local__get_description(void)
+{
+ return _(RA_LOCAL_DESCRIPTION);
+}
+
+static const char * const *
+svn_ra_local__get_schemes(apr_pool_t *pool)
+{
+ static const char *schemes[] = { "file", NULL };
+
+ return schemes;
+}
+
+/* Do nothing.
+ *
+ * Why is this acceptable? FS warnings used to be used for only
+ * two things: failures to close BDB repositories and failures to
+ * interact with memcached in FSFS (new in 1.6). In 1.5 and earlier,
+ * we did not call svn_fs_set_warning_func in ra_local, which means
+ * that any BDB-closing failure would have led to abort()s; the fact
+ * that this hasn't led to huge hues and cries makes it seem likely
+ * that this just doesn't happen that often, at least not through
+ * ra_local. And as far as memcached goes, it seems unlikely that
+ * somebody is going to go through the trouble of setting up and
+ * running memcached servers but then use ra_local access. So we
+ * ignore errors here, so that memcached can use the FS warnings API
+ * without crashing ra_local.
+ */
+static void
+ignore_warnings(void *baton,
+ svn_error_t *err)
+{
+#ifdef SVN_DEBUG
+ SVN_DBG(("Ignoring FS warning %d\n", err ? err->apr_err : 0));
+#endif
+ return;
+}
+
+static svn_error_t *
+svn_ra_local__open(svn_ra_session_t *session,
+ const char **corrected_url,
+ const char *repos_URL,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess;
+ const char *fs_path;
+ static volatile svn_atomic_t cache_init_state = 0;
+
+ /* Initialise the FSFS memory cache size. We can only do this once
+ so one CONFIG will win the race and all others will be ignored
+ silently. */
+ SVN_ERR(svn_atomic__init_once(&cache_init_state, cache_init, config, pool));
+
+ /* We don't support redirections in ra-local. */
+ if (corrected_url)
+ *corrected_url = NULL;
+
+ /* Allocate and stash the session_sess args we have already. */
+ sess = apr_pcalloc(pool, sizeof(*sess));
+ sess->callbacks = callbacks;
+ sess->callback_baton = callback_baton;
+
+ /* Look through the URL, figure out which part points to the
+ repository, and which part is the path *within* the
+ repository. */
+ SVN_ERR_W(svn_ra_local__split_URL(&(sess->repos),
+ &(sess->repos_url),
+ &fs_path,
+ repos_URL,
+ session->pool),
+ _("Unable to open an ra_local session to URL"));
+ sess->fs_path = svn_stringbuf_create(fs_path, session->pool);
+
+ /* Cache the filesystem object from the repos here for
+ convenience. */
+ sess->fs = svn_repos_fs(sess->repos);
+
+ /* Ignore FS warnings. */
+ svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL);
+
+ /* Cache the repository UUID as well */
+ SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool));
+
+ /* Be sure username is NULL so we know to look it up / ask for it */
+ sess->username = NULL;
+
+ session->priv = sess;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__reparent(svn_ra_session_t *session,
+ const char *url,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *relpath = svn_uri_skip_ancestor(sess->repos_url, url, pool);
+
+ /* If the new URL isn't the same as our repository root URL, then
+ let's ensure that it's some child of it. */
+ if (! relpath)
+ return svn_error_createf
+ (SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' is not a child of the session's repository root "
+ "URL '%s'"), url, sess->repos_url);
+
+ /* Update our FS_PATH sess member to point to our new
+ relative-URL-turned-absolute-filesystem-path. */
+ svn_stringbuf_set(sess->fs_path,
+ svn_fspath__canonicalize(relpath, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__get_session_url(svn_ra_session_t *session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ *url = svn_path_url_add_component2(sess->repos_url,
+ sess->fs_path->data + 1,
+ pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__get_latest_revnum(svn_ra_session_t *session,
+ svn_revnum_t *latest_revnum,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ return svn_fs_youngest_rev(latest_revnum, sess->fs, pool);
+}
+
+static svn_error_t *
+svn_ra_local__get_file_revs(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+ return svn_repos_get_file_revs2(sess->repos, abs_path, start, end,
+ include_merged_revisions, NULL, NULL,
+ handler, handler_baton, pool);
+}
+
+static svn_error_t *
+svn_ra_local__get_dated_revision(svn_ra_session_t *session,
+ svn_revnum_t *revision,
+ apr_time_t tm,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ return svn_repos_dated_revision(revision, sess->repos, tm, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__change_rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+
+ SVN_ERR(get_username(session, pool));
+ return svn_repos_fs_change_rev_prop4(sess->repos, rev, sess->username,
+ name, old_value_p, value, TRUE, TRUE,
+ NULL, NULL, pool);
+}
+
+static svn_error_t *
+svn_ra_local__get_uuid(svn_ra_session_t *session,
+ const char **uuid,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ *uuid = sess->uuid;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__get_repos_root(svn_ra_session_t *session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ *url = sess->repos_url;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__rev_proplist(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ return svn_repos_fs_revision_proplist(props, sess->repos, rev,
+ NULL, NULL, pool);
+}
+
+static svn_error_t *
+svn_ra_local__rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ svn_string_t **value,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ return svn_repos_fs_revision_prop(value, sess->repos, rev, name,
+ NULL, NULL, pool);
+}
+
+static svn_error_t *
+svn_ra_local__get_commit_editor(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ struct deltify_etc_baton *deb = apr_palloc(pool, sizeof(*deb));
+
+ /* Prepare the baton for deltify_etc() */
+ deb->fs = sess->fs;
+ deb->repos = sess->repos;
+ deb->fspath_base = sess->fs_path->data;
+ if (! keep_locks)
+ deb->lock_tokens = lock_tokens;
+ else
+ deb->lock_tokens = NULL;
+ deb->commit_cb = callback;
+ deb->commit_baton = callback_baton;
+
+ SVN_ERR(get_username(session, pool));
+
+ /* If there are lock tokens to add, do so. */
+ SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens,
+ session->pool, pool));
+
+ /* Copy the revprops table so we can add the username. */
+ revprop_table = apr_hash_copy(pool, revprop_table);
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(sess->username, pool));
+ svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
+ svn_string_create(SVN_VER_NUMBER, pool));
+
+ /* Get the repos commit-editor */
+ return svn_repos_get_commit_editor5
+ (editor, edit_baton, sess->repos, NULL,
+ svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data,
+ revprop_table, deltify_etc, deb, NULL, NULL, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__get_mergeinfo(svn_ra_session_t *session,
+ svn_mergeinfo_catalog_t *catalog,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ svn_mergeinfo_catalog_t tmp_catalog;
+ int i;
+ apr_array_header_t *abs_paths =
+ apr_array_make(pool, 0, sizeof(const char *));
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
+ APR_ARRAY_PUSH(abs_paths, const char *) =
+ svn_fspath__join(sess->fs_path->data, relative_path, pool);
+ }
+
+ SVN_ERR(svn_repos_fs_get_mergeinfo(&tmp_catalog, sess->repos, abs_paths,
+ revision, inherit, include_descendants,
+ NULL, NULL, pool));
+ if (apr_hash_count(tmp_catalog) > 0)
+ SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog,
+ tmp_catalog,
+ sess->fs_path->data,
+ pool));
+ else
+ *catalog = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+svn_ra_local__do_update(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t update_revision,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return make_reporter(session,
+ reporter,
+ report_baton,
+ update_revision,
+ update_target,
+ NULL,
+ TRUE,
+ depth,
+ send_copyfrom_args,
+ ignore_ancestry,
+ update_editor,
+ update_baton,
+ result_pool, scratch_pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__do_switch(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t update_revision,
+ const char *update_target,
+ svn_depth_t depth,
+ const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return make_reporter(session,
+ reporter,
+ report_baton,
+ update_revision,
+ update_target,
+ switch_url,
+ TRUE /* text_deltas */,
+ depth,
+ send_copyfrom_args,
+ ignore_ancestry,
+ update_editor,
+ update_baton,
+ result_pool, scratch_pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__do_status(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool)
+{
+ return make_reporter(session,
+ reporter,
+ report_baton,
+ revision,
+ status_target,
+ NULL,
+ FALSE,
+ depth,
+ FALSE,
+ FALSE,
+ status_editor,
+ status_baton,
+ pool, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__do_diff(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t update_revision,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *switch_url,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool)
+{
+ return make_reporter(session,
+ reporter,
+ report_baton,
+ update_revision,
+ update_target,
+ switch_url,
+ text_deltas,
+ depth,
+ FALSE,
+ ignore_ancestry,
+ update_editor,
+ update_baton,
+ pool, pool);
+}
+
+
+struct log_baton
+{
+ svn_ra_local__session_baton_t *sess;
+ svn_log_entry_receiver_t real_cb;
+ void *real_baton;
+};
+
+static svn_error_t *
+log_receiver_wrapper(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct log_baton *b = baton;
+ svn_ra_local__session_baton_t *sess = b->sess;
+
+ if (sess->callbacks->cancel_func)
+ SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton));
+
+ /* For consistency with the other RA layers, replace an empty
+ changed-paths hash with a NULL one.
+
+ ### Should this be done by svn_ra_get_log2() instead, then? */
+ if ((log_entry->changed_paths2)
+ && (apr_hash_count(log_entry->changed_paths2) == 0))
+ {
+ log_entry->changed_paths = NULL;
+ log_entry->changed_paths2 = NULL;
+ }
+
+ return b->real_cb(b->real_baton, log_entry, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__get_log(svn_ra_session_t *session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ struct log_baton lb;
+ apr_array_header_t *abs_paths =
+ apr_array_make(pool, 0, sizeof(const char *));
+
+ if (paths)
+ {
+ int i;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
+ APR_ARRAY_PUSH(abs_paths, const char *) =
+ svn_fspath__join(sess->fs_path->data, relative_path, pool);
+ }
+ }
+
+ lb.real_cb = receiver;
+ lb.real_baton = receiver_baton;
+ lb.sess = sess;
+ receiver = log_receiver_wrapper;
+ receiver_baton = &lb;
+
+ return svn_repos_get_logs4(sess->repos,
+ abs_paths,
+ start,
+ end,
+ limit,
+ discover_changed_paths,
+ strict_node_history,
+ include_merged_revisions,
+ revprops,
+ NULL, NULL,
+ receiver,
+ receiver_baton,
+ pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__do_check_path(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ svn_fs_root_t *root;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+
+ if (! SVN_IS_VALID_REVNUM(revision))
+ SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
+ return svn_fs_check_path(kind, root, abs_path, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__stat(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_dirent_t **dirent,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ svn_fs_root_t *root;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+
+ if (! SVN_IS_VALID_REVNUM(revision))
+ SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
+
+ return svn_repos_stat(dirent, root, abs_path, pool);
+}
+
+
+
+
+static svn_error_t *
+get_node_props(apr_hash_t **props,
+ apr_array_header_t **inherited_props,
+ svn_ra_local__session_baton_t *sess,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_revnum_t cmt_rev;
+ const char *cmt_date, *cmt_author;
+
+ /* Create a hash with props attached to the fs node. */
+ if (props)
+ {
+ SVN_ERR(svn_fs_node_proplist(props, root, path, result_pool));
+ }
+
+ /* Get inherited properties if requested. */
+ if (inherited_props)
+ {
+ SVN_ERR(svn_repos_fs_get_inherited_props(inherited_props, root, path,
+ NULL, NULL, NULL,
+ result_pool, scratch_pool));
+ }
+
+ /* Now add some non-tweakable metadata to the hash as well... */
+
+ if (props)
+ {
+ /* The so-called 'entryprops' with info about CR & friends. */
+ SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date,
+ &cmt_author, root, path,
+ scratch_pool));
+
+ svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV,
+ svn_string_createf(result_pool, "%ld", cmt_rev));
+ svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, cmt_date ?
+ svn_string_create(cmt_date, result_pool) :NULL);
+ svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, cmt_author ?
+ svn_string_create(cmt_author, result_pool) :NULL);
+ svn_hash_sets(*props, SVN_PROP_ENTRY_UUID,
+ svn_string_create(sess->uuid, result_pool));
+
+ /* We have no 'wcprops' in ra_local, but might someday. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Getting just one file. */
+static svn_error_t *
+svn_ra_local__get_file(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_stream_t *contents;
+ svn_revnum_t youngest_rev;
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+ svn_node_kind_t node_kind;
+
+ /* Open the revision's root. */
+ if (! SVN_IS_VALID_REVNUM(revision))
+ {
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
+ if (fetched_rev != NULL)
+ *fetched_rev = youngest_rev;
+ }
+ else
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
+
+ SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, pool));
+ if (node_kind == svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("'%s' path not found"), abs_path);
+ }
+ else if (node_kind != svn_node_file)
+ {
+ return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
+ _("'%s' is not a file"), abs_path);
+ }
+
+ if (stream)
+ {
+ /* Get a stream representing the file's contents. */
+ SVN_ERR(svn_fs_file_contents(&contents, root, abs_path, pool));
+
+ /* Now push data from the fs stream back at the caller's stream.
+ Note that this particular RA layer does not computing a
+ checksum as we go, and confirming it against the repository's
+ checksum when done. That's because it calls
+ svn_fs_file_contents() directly, which already checks the
+ stored checksum, and all we're doing here is writing bytes in
+ a loop. Truly, Nothing Can Go Wrong :-). But RA layers that
+ go over a network should confirm the checksum.
+
+ Note: we are not supposed to close the passed-in stream, so
+ disown the thing.
+ */
+ SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(stream, pool),
+ sess->callbacks
+ ? sess->callbacks->cancel_func : NULL,
+ sess->callback_baton,
+ pool));
+ }
+
+ /* Handle props if requested. */
+ if (props)
+ SVN_ERR(get_node_props(props, NULL, sess, root, abs_path, pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Getting a directory's entries */
+static svn_error_t *
+svn_ra_local__get_dir(svn_ra_session_t *session,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ const char *path,
+ svn_revnum_t revision,
+ apr_uint32_t dirent_fields,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_revnum_t youngest_rev;
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ svn_ra_local__session_baton_t *sess = session->priv;
+ apr_pool_t *subpool;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+
+ /* Open the revision's root. */
+ if (! SVN_IS_VALID_REVNUM(revision))
+ {
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
+ if (fetched_rev != NULL)
+ *fetched_rev = youngest_rev;
+ }
+ else
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
+
+ if (dirents)
+ {
+ /* Get the dir's entries. */
+ SVN_ERR(svn_fs_dir_entries(&entries, root, abs_path, pool));
+
+ /* Loop over the fs dirents, and build a hash of general
+ svn_dirent_t's. */
+ *dirents = apr_hash_make(pool);
+ subpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ apr_hash_t *prophash;
+ const char *datestring, *entryname, *fullpath;
+ svn_fs_dirent_t *fs_entry;
+ svn_dirent_t *entry = svn_dirent_create(pool);
+
+ svn_pool_clear(subpool);
+
+ apr_hash_this(hi, &key, NULL, &val);
+ entryname = (const char *) key;
+ fs_entry = (svn_fs_dirent_t *) val;
+
+ fullpath = svn_dirent_join(abs_path, entryname, subpool);
+
+ if (dirent_fields & SVN_DIRENT_KIND)
+ {
+ /* node kind */
+ entry->kind = fs_entry->kind;
+ }
+
+ if (dirent_fields & SVN_DIRENT_SIZE)
+ {
+ /* size */
+ if (entry->kind == svn_node_dir)
+ entry->size = 0;
+ else
+ SVN_ERR(svn_fs_file_length(&(entry->size), root,
+ fullpath, subpool));
+ }
+
+ if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ /* has_props? */
+ SVN_ERR(svn_fs_node_proplist(&prophash, root, fullpath,
+ subpool));
+ entry->has_props = (apr_hash_count(prophash) != 0);
+ }
+
+ if ((dirent_fields & SVN_DIRENT_TIME)
+ || (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
+ || (dirent_fields & SVN_DIRENT_CREATED_REV))
+ {
+ /* created_rev & friends */
+ SVN_ERR(svn_repos_get_committed_info(&(entry->created_rev),
+ &datestring,
+ &(entry->last_author),
+ root, fullpath, subpool));
+ if (datestring)
+ SVN_ERR(svn_time_from_cstring(&(entry->time), datestring,
+ pool));
+ if (entry->last_author)
+ entry->last_author = apr_pstrdup(pool, entry->last_author);
+ }
+
+ /* Store. */
+ svn_hash_sets(*dirents, entryname, entry);
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ /* Handle props if requested. */
+ if (props)
+ SVN_ERR(get_node_props(props, NULL, sess, root, abs_path, pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+svn_ra_local__get_locations(svn_ra_session_t *session,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+ return svn_repos_trace_node_locations(sess->fs, locations, abs_path,
+ peg_revision, location_revisions,
+ NULL, NULL, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__get_location_segments(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+ return svn_repos_node_location_segments(sess->repos, abs_path,
+ peg_revision, start_rev, end_rev,
+ receiver, receiver_baton,
+ NULL, NULL, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__lock(svn_ra_session_t *session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* A username is absolutely required to lock a path. */
+ SVN_ERR(get_username(session, pool));
+
+ for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
+ {
+ svn_lock_t *lock;
+ const void *key;
+ const char *path;
+ void *val;
+ svn_revnum_t *revnum;
+ const char *abs_path;
+ svn_error_t *err, *callback_err = NULL;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ revnum = val;
+
+ abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool);
+
+ /* This wrapper will call pre- and post-lock hooks. */
+ err = svn_repos_fs_lock(&lock, sess->repos, abs_path, NULL, comment,
+ FALSE /* not DAV comment */,
+ 0 /* no expiration */, *revnum, force,
+ iterpool);
+
+ if (err && !SVN_ERR_IS_LOCK_ERROR(err))
+ return err;
+
+ if (lock_func)
+ callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
+ err, iterpool);
+
+ svn_error_clear(err);
+
+ if (callback_err)
+ return callback_err;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+svn_ra_local__unlock(svn_ra_session_t *session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* A username is absolutely required to unlock a path. */
+ SVN_ERR(get_username(session, pool));
+
+ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ const char *path;
+ void *val;
+ const char *abs_path, *token;
+ svn_error_t *err, *callback_err = NULL;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ /* Since we can't store NULL values in a hash, we turn "" to
+ NULL here. */
+ if (strcmp(val, "") != 0)
+ token = val;
+ else
+ token = NULL;
+
+ abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool);
+
+ /* This wrapper will call pre- and post-unlock hooks. */
+ err = svn_repos_fs_unlock(sess->repos, abs_path, token, force,
+ iterpool);
+
+ if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
+ return err;
+
+ if (lock_func)
+ callback_err = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
+
+ svn_error_clear(err);
+
+ if (callback_err)
+ return callback_err;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+static svn_error_t *
+svn_ra_local__get_lock(svn_ra_session_t *session,
+ svn_lock_t **lock,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+ return svn_fs_get_lock(lock, sess->fs, abs_path, pool);
+}
+
+
+
+static svn_error_t *
+svn_ra_local__get_locks(svn_ra_session_t *session,
+ apr_hash_t **locks,
+ const char *path,
+ svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+
+ /* Kinda silly to call the repos wrapper, since we have no authz
+ func to give it. But heck, why not. */
+ return svn_repos_fs_get_locks2(locks, sess->repos, abs_path, depth,
+ NULL, NULL, pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__replay(svn_ra_session_t *session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ svn_fs_root_t *root;
+
+ SVN_ERR(svn_fs_revision_root(&root, svn_repos_fs(sess->repos),
+ revision, pool));
+ return svn_repos_replay2(root, sess->fs_path->data, low_water_mark,
+ send_deltas, editor, edit_baton, NULL, NULL,
+ pool);
+}
+
+
+static svn_error_t *
+svn_ra_local__replay_range(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+}
+
+
+static svn_error_t *
+svn_ra_local__has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+
+ if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_INHERITED_PROPS) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE) == 0
+ )
+ {
+ *has = TRUE;
+ }
+ else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
+ {
+ /* With mergeinfo, the code's capabilities may not reflect the
+ repository's, so inquire further. */
+ SVN_ERR(svn_repos_has_capability(sess->repos, has,
+ SVN_REPOS_CAPABILITY_MERGEINFO,
+ pool));
+ }
+ else /* Don't know any other capabilities, so error. */
+ {
+ return svn_error_createf
+ (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
+ _("Don't know anything about capability '%s'"), capability);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__get_deleted_rev(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
+
+ SVN_ERR(svn_repos_deleted_rev(sess->fs,
+ abs_path,
+ peg_revision,
+ end_revision,
+ revision_deleted,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+svn_ra_local__get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_root_t *root;
+ svn_revnum_t youngest_rev;
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path,
+ scratch_pool);
+ svn_node_kind_t node_kind;
+
+ /* Open the revision's root. */
+ if (! SVN_IS_VALID_REVNUM(revision))
+ {
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, scratch_pool));
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, scratch_pool));
+ }
+
+ SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, scratch_pool));
+ if (node_kind == svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("'%s' path not found"), abs_path);
+ }
+
+ return svn_error_trace(get_node_props(NULL, iprops, sess, root, abs_path,
+ result_pool, scratch_pool));
+}
+
+static svn_error_t *
+svn_ra_local__register_editor_shim_callbacks(svn_ra_session_t *session,
+ svn_delta_shim_callbacks_t *callbacks)
+{
+ /* This is currenly a no-op, since we don't provide our own editor, just
+ use the one the libsvn_repos hands back to us. */
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+svn_ra_local__get_commit_ev2(svn_editor_t **editor,
+ svn_ra_session_t *session,
+ apr_hash_t *revprops,
+ svn_commit_callback2_t commit_cb,
+ void *commit_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
+ void *cb_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_local__session_baton_t *sess = session->priv;
+ struct deltify_etc_baton *deb = apr_palloc(result_pool, sizeof(*deb));
+
+ /* NOTE: the RA callbacks are ignored. We pass everything directly to
+ the REPOS editor. */
+
+ /* Prepare the baton for deltify_etc() */
+ deb->fs = sess->fs;
+ deb->repos = sess->repos;
+ deb->fspath_base = sess->fs_path->data;
+ if (! keep_locks)
+ deb->lock_tokens = lock_tokens;
+ else
+ deb->lock_tokens = NULL;
+ deb->commit_cb = commit_cb;
+ deb->commit_baton = commit_baton;
+
+ /* Ensure there is a username (and an FS access context) associated with
+ the session and its FS handle. */
+ SVN_ERR(get_username(session, scratch_pool));
+
+ /* If there are lock tokens to add, do so. */
+ SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens,
+ session->pool, scratch_pool));
+
+ /* Copy the REVPROPS and insert the author/username. */
+ revprops = apr_hash_copy(scratch_pool, revprops);
+ svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(sess->username, scratch_pool));
+
+ return svn_error_trace(svn_repos__get_commit_ev2(
+ editor, sess->repos, NULL /* authz */,
+ NULL /* authz_repos_name */, NULL /* authz_user */,
+ revprops,
+ deltify_etc, deb, cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+}
+
+/*----------------------------------------------------------------*/
+
+static const svn_version_t *
+ra_local_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+/** The ra_vtable **/
+
+static const svn_ra__vtable_t ra_local_vtable =
+{
+ ra_local_version,
+ svn_ra_local__get_description,
+ svn_ra_local__get_schemes,
+ svn_ra_local__open,
+ svn_ra_local__reparent,
+ svn_ra_local__get_session_url,
+ svn_ra_local__get_latest_revnum,
+ svn_ra_local__get_dated_revision,
+ svn_ra_local__change_rev_prop,
+ svn_ra_local__rev_proplist,
+ svn_ra_local__rev_prop,
+ svn_ra_local__get_commit_editor,
+ svn_ra_local__get_file,
+ svn_ra_local__get_dir,
+ svn_ra_local__get_mergeinfo,
+ svn_ra_local__do_update,
+ svn_ra_local__do_switch,
+ svn_ra_local__do_status,
+ svn_ra_local__do_diff,
+ svn_ra_local__get_log,
+ svn_ra_local__do_check_path,
+ svn_ra_local__stat,
+ svn_ra_local__get_uuid,
+ svn_ra_local__get_repos_root,
+ svn_ra_local__get_locations,
+ svn_ra_local__get_location_segments,
+ svn_ra_local__get_file_revs,
+ svn_ra_local__lock,
+ svn_ra_local__unlock,
+ svn_ra_local__get_lock,
+ svn_ra_local__get_locks,
+ svn_ra_local__replay,
+ svn_ra_local__has_capability,
+ svn_ra_local__replay_range,
+ svn_ra_local__get_deleted_rev,
+ svn_ra_local__register_editor_shim_callbacks,
+ svn_ra_local__get_inherited_props,
+ svn_ra_local__get_commit_ev2
+};
+
+
+/*----------------------------------------------------------------*/
+
+/** The One Public Routine, called by libsvn_ra **/
+
+svn_error_t *
+svn_ra_local__init(const svn_version_t *loader_version,
+ const svn_ra__vtable_t **vtable,
+ apr_pool_t *pool)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_delta", svn_delta_version },
+ { "svn_repos", svn_repos_version },
+ { "svn_fs", svn_fs_version },
+ { NULL, NULL }
+ };
+
+
+ /* Simplified version check to make sure we can safely use the
+ VTABLE parameter. The RA loader does a more exhaustive check. */
+ if (loader_version->major != SVN_VER_MAJOR)
+ return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
+ _("Unsupported RA loader version (%d) for "
+ "ra_local"),
+ loader_version->major);
+
+ SVN_ERR(svn_ver_check_list(ra_local_version(), checklist));
+
+#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
+ /* This assumes that POOL was the pool used to load the dso. */
+ SVN_ERR(svn_fs_initialize(pool));
+#endif
+
+ *vtable = &ra_local_vtable;
+
+ return SVN_NO_ERROR;
+}
+
+/* Compatibility wrapper for the 1.1 and before API. */
+#define NAME "ra_local"
+#define DESCRIPTION RA_LOCAL_DESCRIPTION
+#define VTBL ra_local_vtable
+#define INITFUNC svn_ra_local__init
+#define COMPAT_INITFUNC svn_ra_local_init
+#include "../libsvn_ra/wrapper_template.h"
diff --git a/contrib/subversion/subversion/libsvn_ra_local/split_url.c b/contrib/subversion/subversion/libsvn_ra_local/split_url.c
new file mode 100644
index 0000000..d08bb26
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_local/split_url.c
@@ -0,0 +1,97 @@
+/*
+ * checkout.c : read a repository and drive a checkout editor.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "ra_local.h"
+#include <string.h>
+#include "svn_path.h"
+#include "svn_dirent_uri.h"
+#include "svn_private_config.h"
+
+
+svn_error_t *
+svn_ra_local__split_URL(svn_repos_t **repos,
+ const char **repos_url,
+ const char **fs_path,
+ const char *URL,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ const char *repos_dirent;
+ const char *repos_root_dirent;
+ svn_stringbuf_t *urlbuf;
+
+ SVN_ERR(svn_uri_get_dirent_from_file_url(&repos_dirent, URL, pool));
+
+ /* Search for a repository in the full path. */
+ repos_root_dirent = svn_repos_find_root_path(repos_dirent, pool);
+ if (!repos_root_dirent)
+ return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED, NULL,
+ _("Unable to open repository '%s'"), URL);
+
+ /* Attempt to open a repository at URL. */
+ err = svn_repos_open2(repos, repos_root_dirent, NULL, pool);
+ if (err)
+ return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED, err,
+ _("Unable to open repository '%s'"), URL);
+
+ /* Assert capabilities directly, since client == server. */
+ {
+ apr_array_header_t *caps = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(caps, const char *) = SVN_RA_CAPABILITY_MERGEINFO;
+ SVN_ERR(svn_repos_remember_client_capabilities(*repos, caps));
+ }
+
+ /* = apr_pstrcat(pool,
+ "/",
+ svn_dirent_skip_ancestor(repos_root_dirent, repos_dirent),
+ (const char *)NULL); */
+ *fs_path = &repos_dirent[strlen(repos_root_dirent)];
+
+ if (**fs_path == '\0')
+ *fs_path = "/";
+
+ /* Remove the path components after the root dirent from the original URL,
+ to get a URL to the repository root.
+
+ We don't use svn_uri_get_file_url_from_dirent() here as that would
+ transform several uris to form a differently formed url than
+ svn_uri_canonicalize would.
+
+ E.g. file://localhost/C:/dir -> file:///C:/dir
+ (a transform that was originally supported directly by this function,
+ before the implementation moved)
+
+ On on Windows:
+ file:///dir -> file:///E:/dir (When E: is the current disk)
+ */
+ urlbuf = svn_stringbuf_create(URL, pool);
+ svn_path_remove_components(urlbuf,
+ svn_path_component_count(repos_dirent)
+ - svn_path_component_count(repos_root_dirent));
+ *repos_url = urlbuf->data;
+
+ /* Configure hook script environment variables. */
+ SVN_ERR(svn_repos_hooks_setenv(*repos, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/README b/contrib/subversion/subversion/libsvn_ra_serf/README
new file mode 100644
index 0000000..d3baf33
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/README
@@ -0,0 +1,84 @@
+ra_serf status
+==============
+
+This library is an RA-layer implementation of a WebDAV client that uses Serf.
+
+Serf's homepage is at:
+ http://code.google.com/p/serf/
+
+The latest serf releases can be fetched at:
+ http://code.google.com/p/serf/downloads/list
+
+The latest serf sources can be fetched via SVN at:
+ http://serf.googlecode.com/svn/trunk/
+
+ra_serf can be enabled with the following configure flags:
+ "--with-serf=/path/to/serf/install"
+As Neon is currently Subversion's default RA DAV layer, you also need
+to add "http-library = serf" to your ~/.subversion/servers file to
+choose ra_serf at runtime. Alternately, you can build with only
+support for ra_serf:
+ "--without-neon --with-serf=/path/to/serf/install"
+
+For more about how ra_serf/ra_neon talk WebDAV, consult notes/webdav-protocol.
+
+Working copies are interchangable between ra_serf and ra_neon. (They both use
+the svn:wc:ra_dav:version-url property to store the latest revision of a file.)
+
+Completed tasks
+---------------
+- Core functionality complete (see regression test status below)
+- https support (SSL)
+- Basic authentication
+- Update parallelization/pipelining (also for status/diff/switch/etc)
+ - Does not require inline base64-encoding of content
+ - 4 connections are open on an update (matches browser's default behavior)
+ - 1 connection is used for the REPORT; 3 are used to fetch files & props
+- Supports http-compression config flag
+- SSL client and server certificates
+- Proxy support
+- NTLM/SSPI integration for Windows folks
+- REPORT body buckets can now be read twice (#3212)
+
+Regression test status
+----------------------
+All current regression tests are known to pass on:
+ - Debian/AMD64 with APR 1.3.x
+ - Mac OS X
+ - Solaris
+ - Windows
+
+Things to do before the next release (1.6.x timeframe)
+------------------------------------------------------
+
+- Digest authentication
+
+- Fix the editor API violation (TBC, #2932)
+
+Nice to haves
+-------------
+
+- Move some of the code from ra_serf into serf. Serf doesn't have a very
+ high-level API; but the code in util.c can go a long way towards that.
+
+- Commit parallellization/pipelining
+ - Determine how to use HTTP pipelining and multiple connections for commit
+ - May need response from CHECKOUT to issue PUT/PROPPATCH
+ - ra_svn has a custom commit pipelining that may be worth investigating too
+
+- Use PROPFIND Depth: 1 when we are adding a directory locally to skip
+ fetching properties on files
+
+- Discover server's keep-alive setting via OPTIONS requests and notify serf
+
+- Fix bug in mod_dav_svn that omits remove-prop in the update-report when a
+ lock is broken and send-all is false.
+ (See upd_change_xxx_prop in mod_dav_svn/update.c)
+
+- Fix bug in mod_dav_svn/mod_deflate that causes it to hold onto the entire
+ REPORT response until it is completed. (This is why ra_serf doesn't request
+ gzip compression on the REPORT requests.)
+
+- Remove remaining abort()s - ;-) aka add better debug logging
+
+- Support for HTTP/1.0 pnly proxies.
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/blame.c b/contrib/subversion/subversion/libsvn_ra_serf/blame.c
new file mode 100644
index 0000000..fa4243c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/blame.c
@@ -0,0 +1,375 @@
+/*
+ * blame.c : entry point for blame RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_path.h"
+#include "svn_base64.h"
+#include "svn_props.h"
+
+#include "svn_private_config.h"
+
+#include "private/svn_string_private.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+typedef enum blame_state_e {
+ INITIAL = 0,
+ FILE_REVS_REPORT,
+ FILE_REV,
+ REV_PROP,
+ SET_PROP,
+ REMOVE_PROP,
+ MERGED_REVISION,
+ TXDELTA
+} blame_state_e;
+
+
+typedef struct blame_context_t {
+ /* pool passed to get_file_revs */
+ apr_pool_t *pool;
+
+ /* parameters set by our caller */
+ const char *path;
+ svn_revnum_t start;
+ svn_revnum_t end;
+ svn_boolean_t include_merged_revisions;
+
+ /* blame handler and baton */
+ svn_file_rev_handler_t file_rev;
+ void *file_rev_baton;
+
+ /* As we parse each FILE_REV, we collect data in these variables:
+ property changes and new content. STREAM is valid when we're
+ in the TXDELTA state, processing the incoming cdata. */
+ apr_hash_t *rev_props;
+ apr_array_header_t *prop_diffs;
+ apr_pool_t *state_pool; /* put property stuff in here */
+
+ svn_stream_t *stream;
+
+} blame_context_t;
+
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t blame_ttable[] = {
+ { INITIAL, S_, "file-revs-report", FILE_REVS_REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { FILE_REVS_REPORT, S_, "file-rev", FILE_REV,
+ FALSE, { "path", "rev", NULL }, TRUE },
+
+ { FILE_REV, S_, "rev-prop", REV_PROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
+
+ { FILE_REV, S_, "set-prop", SET_PROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
+
+ { FILE_REV, S_, "remove-prop", REMOVE_PROP,
+ FALSE, { "name", NULL }, TRUE },
+
+ { FILE_REV, S_, "merged-revision", MERGED_REVISION,
+ FALSE, { NULL }, TRUE },
+
+ { FILE_REV, S_, "txdelta", TXDELTA,
+ FALSE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+blame_opened(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int entered_state,
+ const svn_ra_serf__dav_props_t *tag,
+ apr_pool_t *scratch_pool)
+{
+ blame_context_t *blame_ctx = baton;
+
+ if (entered_state == FILE_REV)
+ {
+ apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
+
+ /* Child elements will store properties in these structures. */
+ blame_ctx->rev_props = apr_hash_make(state_pool);
+ blame_ctx->prop_diffs = apr_array_make(state_pool,
+ 5, sizeof(svn_prop_t));
+ blame_ctx->state_pool = state_pool;
+
+ /* Clear this, so we can detect the absence of a TXDELTA. */
+ blame_ctx->stream = NULL;
+ }
+ else if (entered_state == TXDELTA)
+ {
+ apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
+ apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV);
+ const char *path;
+ const char *rev;
+ const char *merged_revision;
+ svn_txdelta_window_handler_t txdelta;
+ void *txdelta_baton;
+
+ path = svn_hash_gets(gathered, "path");
+ rev = svn_hash_gets(gathered, "rev");
+ merged_revision = svn_hash_gets(gathered, "merged-revision");
+
+ SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
+ path, SVN_STR_TO_REV(rev),
+ blame_ctx->rev_props,
+ merged_revision != NULL,
+ &txdelta, &txdelta_baton,
+ blame_ctx->prop_diffs,
+ state_pool));
+
+ blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff(
+ txdelta, txdelta_baton,
+ TRUE /* error_on_early_close */,
+ state_pool),
+ state_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+blame_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ blame_context_t *blame_ctx = baton;
+
+ if (leaving_state == FILE_REV)
+ {
+ /* Note that we test STREAM, but any pointer is currently invalid.
+ It was closed when left the TXDELTA state. */
+ if (blame_ctx->stream == NULL)
+ {
+ const char *path;
+ const char *rev;
+
+ path = svn_hash_gets(attrs, "path");
+ rev = svn_hash_gets(attrs, "rev");
+
+ /* Send a "no content" notification. */
+ SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
+ path, SVN_STR_TO_REV(rev),
+ blame_ctx->rev_props,
+ FALSE /* result_of_merge */,
+ NULL, NULL, /* txdelta / baton */
+ blame_ctx->prop_diffs,
+ scratch_pool));
+ }
+ }
+ else if (leaving_state == MERGED_REVISION)
+ {
+ svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*");
+ }
+ else if (leaving_state == TXDELTA)
+ {
+ SVN_ERR(svn_stream_close(blame_ctx->stream));
+ }
+ else
+ {
+ const char *name;
+ const svn_string_t *value;
+
+ SVN_ERR_ASSERT(leaving_state == REV_PROP
+ || leaving_state == SET_PROP
+ || leaving_state == REMOVE_PROP);
+
+ name = apr_pstrdup(blame_ctx->state_pool,
+ svn_hash_gets(attrs, "name"));
+
+ if (leaving_state == REMOVE_PROP)
+ {
+ value = NULL;
+ }
+ else
+ {
+ const char *encoding = svn_hash_gets(attrs, "encoding");
+
+ if (encoding && strcmp(encoding, "base64") == 0)
+ value = svn_base64_decode_string(cdata, blame_ctx->state_pool);
+ else
+ value = svn_string_dup(cdata, blame_ctx->state_pool);
+ }
+
+ if (leaving_state == REV_PROP)
+ {
+ svn_hash_sets(blame_ctx->rev_props, name, value);
+ }
+ else
+ {
+ svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs);
+
+ prop->name = name;
+ prop->value = value;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_cdata_t */
+static svn_error_t *
+blame_cdata(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int current_state,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ blame_context_t *blame_ctx = baton;
+
+ if (current_state == TXDELTA)
+ {
+ SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len));
+ /* Ignore the returned LEN value. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_file_revs_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ blame_context_t *blame_ctx = baton;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc,
+ "S:file-revs-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:start-revision", apr_ltoa(pool, blame_ctx->start),
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:end-revision", apr_ltoa(pool, blame_ctx->end),
+ alloc);
+
+ if (blame_ctx->include_merged_revisions)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:include-merged-revisions", NULL,
+ alloc);
+ }
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:path", blame_ctx->path,
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc,
+ "S:file-revs-report");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_file_rev_handler_t rev_handler,
+ void *rev_handler_baton,
+ apr_pool_t *pool)
+{
+ blame_context_t *blame_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+ svn_error_t *err;
+
+ blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx));
+ blame_ctx->pool = pool;
+ blame_ctx->path = path;
+ blame_ctx->file_rev = rev_handler;
+ blame_ctx->file_rev_baton = rev_handler_baton;
+ blame_ctx->start = start;
+ blame_ctx->end = end;
+ blame_ctx->include_merged_revisions = include_merged_revisions;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, end,
+ pool, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(blame_ttable,
+ blame_opened,
+ blame_closed,
+ blame_cdata,
+ blame_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->body_delegate = create_file_revs_body;
+ handler->body_delegate_baton = blame_ctx;
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ err = svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location),
+ err);
+
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/blncache.c b/contrib/subversion/subversion/libsvn_ra_serf/blncache.c
new file mode 100644
index 0000000..d6abcdf
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/blncache.c
@@ -0,0 +1,179 @@
+/*
+ * blncache.c: DAV baseline information cache.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_hash.h"
+#include "svn_dirent_uri.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+
+#include "blncache.h"
+
+/* Baseline information cache object. */
+typedef struct baseline_info_t
+{
+ const char *bc_url; /* baseline collection URL. */
+ svn_revnum_t revision; /* revision associated with the baseline. */
+
+} baseline_info_t;
+
+/* Module-private structure used to hold the caches. */
+struct svn_ra_serf__blncache_t
+{
+ /* A hash mapping 'svn_revnum_t *' baseline revisions to 'const
+ * char *' baseline collection URLs.
+ */
+ apr_hash_t *revnum_to_bc;
+
+ /* A hash mapping 'const char *' baseline URLs to 'baseline_info_t *'
+ * structures. (Allocated from the same pool as 'revnum_to_bc'.)
+ */
+ apr_hash_t *baseline_info;
+};
+
+
+
+/* Return a pointer to an 'baseline_info_t' structure allocated from
+ * POOL and populated with BC_URL (which is duped into POOL) and
+ * REVISION.
+ */
+static baseline_info_t *
+baseline_info_make(const char *bc_url,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ baseline_info_t *result = apr_palloc(pool, sizeof(*result));
+
+ result->bc_url = apr_pstrdup(pool, bc_url);
+ result->revision = revision;
+
+ return result;
+}
+
+/* Set in HASH the value VAL for the KEY (whose key length is KLEN).
+ * KEY will be duped into HASH's pool.
+ */
+static void
+hash_set_copy(apr_hash_t *hash,
+ const void *key,
+ apr_ssize_t klen,
+ const void *val)
+{
+ if (klen == APR_HASH_KEY_STRING)
+ klen = strlen(key);
+ apr_hash_set(hash, apr_pmemdup(apr_hash_pool_get(hash), key, klen),
+ klen, val);
+}
+
+
+svn_error_t *
+svn_ra_serf__blncache_create(svn_ra_serf__blncache_t **blncache_p,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__blncache_t *blncache = apr_pcalloc(pool, sizeof(*blncache));
+ apr_pool_t *cache_pool;
+
+ /* Create subpool for cached data. It will be cleared if we reach maximum
+ * cache size.*/
+ cache_pool = svn_pool_create(pool);
+ blncache->revnum_to_bc = apr_hash_make(cache_pool);
+ blncache->baseline_info = apr_hash_make(cache_pool);
+
+ *blncache_p = blncache;
+
+ return SVN_NO_ERROR;
+}
+
+#define MAX_CACHE_SIZE 1000
+
+svn_error_t *
+svn_ra_serf__blncache_set(svn_ra_serf__blncache_t *blncache,
+ const char *baseline_url,
+ svn_revnum_t revision,
+ const char *bc_url,
+ apr_pool_t *pool)
+{
+ if (bc_url && SVN_IS_VALID_REVNUM(revision))
+ {
+ apr_pool_t *cache_pool = apr_hash_pool_get(blncache->revnum_to_bc);
+
+ /* If the caches are too big, delete and recreate 'em and move along. */
+ if (MAX_CACHE_SIZE < (apr_hash_count(blncache->baseline_info)
+ + apr_hash_count(blncache->revnum_to_bc)))
+ {
+ svn_pool_clear(cache_pool);
+ blncache->revnum_to_bc = apr_hash_make(cache_pool);
+ blncache->baseline_info = apr_hash_make(cache_pool);
+ }
+
+ hash_set_copy(blncache->revnum_to_bc, &revision, sizeof(revision),
+ apr_pstrdup(cache_pool, bc_url));
+
+ if (baseline_url)
+ {
+ hash_set_copy(blncache->baseline_info, baseline_url,
+ APR_HASH_KEY_STRING,
+ baseline_info_make(bc_url, revision, cache_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#undef MAX_CACHE_SIZE
+
+svn_error_t *
+svn_ra_serf__blncache_get_bc_url(const char **bc_url_p,
+ svn_ra_serf__blncache_t *blncache,
+ svn_revnum_t revnum,
+ apr_pool_t *pool)
+{
+ const char *value = apr_hash_get(blncache->revnum_to_bc,
+ &revnum, sizeof(revnum));
+ *bc_url_p = value ? apr_pstrdup(pool, value) : NULL;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__blncache_get_baseline_info(const char **bc_url_p,
+ svn_revnum_t *revision_p,
+ svn_ra_serf__blncache_t *blncache,
+ const char *baseline_url,
+ apr_pool_t *pool)
+{
+ baseline_info_t *info = svn_hash_gets(blncache->baseline_info, baseline_url);
+ if (info)
+ {
+ *bc_url_p = apr_pstrdup(pool, info->bc_url);
+ *revision_p = info->revision;
+ }
+ else
+ {
+ *bc_url_p = NULL;
+ *revision_p = SVN_INVALID_REVNUM;
+ }
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/blncache.h b/contrib/subversion/subversion/libsvn_ra_serf/blncache.h
new file mode 100644
index 0000000..5ad4eba
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/blncache.h
@@ -0,0 +1,90 @@
+/*
+ * blncache.h: DAV baseline information cache.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_RA_SERF_BLNCACHE_H
+#define SVN_LIBSVN_RA_SERF_BLNCACHE_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Baseline information cache. Baseline information cache holds information
+ * about DAV baseline (bln):
+ * 1. URL of the baseline (bln)
+ * 2. Revision number associated with baseline
+ * 3. URL of baseline collection (bc).
+ */
+typedef struct svn_ra_serf__blncache_t svn_ra_serf__blncache_t;
+
+/* Creates new instance of baseline cache. Sets BLNCACHE_P with
+ * a pointer to new instance, allocated in POOL.
+ */
+svn_error_t *
+svn_ra_serf__blncache_create(svn_ra_serf__blncache_t **blncache_p,
+ apr_pool_t *pool);
+
+/* Add information about baseline. BLNCACHE is a pointer to
+ * baseline cache previously created using svn_ra_serf__blncache_create
+ * function. BASELINE_URL is URL of baseline (can be NULL if unknown).
+ * REVNUM is revision number associated with baseline. Use SVN_INVALID_REVNUM
+ * to indicate that revision is unknown.
+ * BC_URL is URL of baseline collection (can be NULL if unknwon).
+ */
+svn_error_t *
+svn_ra_serf__blncache_set(svn_ra_serf__blncache_t *blncache,
+ const char *baseline_url,
+ svn_revnum_t revnum,
+ const char *bc_url,
+ apr_pool_t *pool);
+
+/* Sets *BC_URL_P with a pointer to baseline collection URL for the given
+ * REVNUM. *BC_URL_P will be NULL if cache doesn't have information about
+ * this baseline.
+ */
+svn_error_t *
+svn_ra_serf__blncache_get_bc_url(const char **bc_url_p,
+ svn_ra_serf__blncache_t *blncache,
+ svn_revnum_t revnum,
+ apr_pool_t *pool);
+
+/* Sets *BC_URL_P with pointer to baseline collection URL and *REVISION_P
+ * with revision number of baseline BASELINE_URL. *BC_URL_P will be NULL,
+ * *REVNUM_P will SVN_INVALID_REVNUM if cache doesn't have such
+ * information.
+ */
+svn_error_t *
+svn_ra_serf__blncache_get_baseline_info(const char **bc_url_p,
+ svn_revnum_t *revnum_p,
+ svn_ra_serf__blncache_t *blncache,
+ const char *baseline_url,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_RA_SERF_BLNCACHE_H*/
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/commit.c b/contrib/subversion/subversion/libsvn_ra_serf/commit.c
new file mode 100644
index 0000000..aaf1717
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/commit.c
@@ -0,0 +1,2468 @@
+/*
+ * commit.c : entry point for commit RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_base64.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+
+#include "svn_private_config.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_skel.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/* Baton passed back with the commit editor. */
+typedef struct commit_context_t {
+ /* Pool for our commit. */
+ apr_pool_t *pool;
+
+ svn_ra_serf__session_t *session;
+ svn_ra_serf__connection_t *conn;
+
+ apr_hash_t *revprop_table;
+
+ svn_commit_callback2_t callback;
+ void *callback_baton;
+
+ apr_hash_t *lock_tokens;
+ svn_boolean_t keep_locks;
+ apr_hash_t *deleted_entries; /* deleted files (for delete+add detection) */
+
+ /* HTTP v2 stuff */
+ const char *txn_url; /* txn URL (!svn/txn/TXN_NAME) */
+ const char *txn_root_url; /* commit anchor txn root URL */
+
+ /* HTTP v1 stuff (only valid when 'txn_url' is NULL) */
+ const char *activity_url; /* activity base URL... */
+ const char *baseline_url; /* the working-baseline resource */
+ const char *checked_in_url; /* checked-in root to base CHECKOUTs from */
+ const char *vcc_url; /* vcc url */
+
+} commit_context_t;
+
+#define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
+
+/* Structure associated with a PROPPATCH request. */
+typedef struct proppatch_context_t {
+ apr_pool_t *pool;
+
+ const char *relpath;
+ const char *path;
+
+ commit_context_t *commit;
+
+ /* Changed and removed properties. */
+ apr_hash_t *changed_props;
+ apr_hash_t *removed_props;
+
+ /* Same, for the old value (*old_value_p). */
+ apr_hash_t *previous_changed_props;
+ apr_hash_t *previous_removed_props;
+
+ /* In HTTP v2, this is the file/directory version we think we're changing. */
+ svn_revnum_t base_revision;
+
+} proppatch_context_t;
+
+typedef struct delete_context_t {
+ const char *path;
+
+ svn_revnum_t revision;
+
+ const char *lock_token;
+ apr_hash_t *lock_token_hash;
+ svn_boolean_t keep_locks;
+
+} delete_context_t;
+
+/* Represents a directory. */
+typedef struct dir_context_t {
+ /* Pool for our directory. */
+ apr_pool_t *pool;
+
+ /* The root commit we're in progress for. */
+ commit_context_t *commit;
+
+ /* URL to operate against (used for CHECKOUT and PROPPATCH before
+ HTTP v2, for PROPPATCH in HTTP v2). */
+ const char *url;
+
+ /* How many pending changes we have left in this directory. */
+ unsigned int ref_count;
+
+ /* Is this directory being added? (Otherwise, just opened.) */
+ svn_boolean_t added;
+
+ /* Our parent */
+ struct dir_context_t *parent_dir;
+
+ /* The directory name; if "", we're the 'root' */
+ const char *relpath;
+
+ /* The basename of the directory. "" for the 'root' */
+ const char *name;
+
+ /* The base revision of the dir. */
+ svn_revnum_t base_revision;
+
+ const char *copy_path;
+ svn_revnum_t copy_revision;
+
+ /* Changed and removed properties */
+ apr_hash_t *changed_props;
+ apr_hash_t *removed_props;
+
+ /* The checked-out working resource for this directory. May be NULL; if so
+ call checkout_dir() first. */
+ const char *working_url;
+
+} dir_context_t;
+
+/* Represents a file to be committed. */
+typedef struct file_context_t {
+ /* Pool for our file. */
+ apr_pool_t *pool;
+
+ /* The root commit we're in progress for. */
+ commit_context_t *commit;
+
+ /* Is this file being added? (Otherwise, just opened.) */
+ svn_boolean_t added;
+
+ dir_context_t *parent_dir;
+
+ const char *relpath;
+ const char *name;
+
+ /* The checked-out working resource for this file. */
+ const char *working_url;
+
+ /* The base revision of the file. */
+ svn_revnum_t base_revision;
+
+ /* Copy path and revision */
+ const char *copy_path;
+ svn_revnum_t copy_revision;
+
+ /* stream */
+ svn_stream_t *stream;
+
+ /* Temporary file containing the svndiff. */
+ apr_file_t *svndiff;
+
+ /* Our base checksum as reported by the WC. */
+ const char *base_checksum;
+
+ /* Our resulting checksum as reported by the WC. */
+ const char *result_checksum;
+
+ /* Changed and removed properties. */
+ apr_hash_t *changed_props;
+ apr_hash_t *removed_props;
+
+ /* URL to PUT the file at. */
+ const char *url;
+
+} file_context_t;
+
+
+/* Setup routines and handlers for various requests we'll invoke. */
+
+static svn_error_t *
+return_response_err(svn_ra_serf__handler_t *handler)
+{
+ svn_error_t *err;
+
+ /* We should have captured SLINE and LOCATION in the HANDLER. */
+ SVN_ERR_ASSERT(handler->handler_pool != NULL);
+
+ /* Ye Olde Fallback Error */
+ err = svn_error_compose_create(
+ handler->server_error != NULL
+ ? handler->server_error->error
+ : SVN_NO_ERROR,
+ svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("%s of '%s': %d %s"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason));
+
+ /* Try to return one of the standard errors for 301, 404, etc.,
+ then look for an error embedded in the response. */
+ return svn_error_compose_create(svn_ra_serf__error_on_status(
+ handler->sline.code,
+ handler->path,
+ handler->location),
+ err);
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_checkout_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ const char *activity_url = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
+ "xmlns:D", "DAV:",
+ NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL);
+
+ SVN_ERR_ASSERT(activity_url != NULL);
+ svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
+ activity_url,
+ strlen(activity_url));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:apply-to-version", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+
+/* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
+ given COMMIT_CTX. The resulting working resource will be returned in
+ *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
+ are performed in SCRATCH_POOL.
+
+ ### are these URLs actually repos relpath values? or fspath? or maybe
+ ### the abspath portion of the full URL.
+
+ This function operates synchronously.
+
+ Strictly speaking, we could perform "all" of the CHECKOUT requests
+ when the commit starts, and only block when we need a specific
+ answer. Or, at a minimum, send off these individual requests async
+ and block when we need the answer (eg PUT or PROPPATCH).
+
+ However: the investment to speed this up is not worthwhile, given
+ that CHECKOUT (and the related round trip) is completely obviated
+ in HTTPv2.
+*/
+static svn_error_t *
+checkout_node(const char **working_url,
+ const commit_context_t *commit_ctx,
+ const char *node_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t handler = { 0 };
+ apr_status_t status;
+ apr_uri_t uri;
+
+ /* HANDLER_POOL is the scratch pool since we don't need to remember
+ anything from the handler. We just want the working resource. */
+ handler.handler_pool = scratch_pool;
+ handler.session = commit_ctx->session;
+ handler.conn = commit_ctx->conn;
+
+ handler.body_delegate = create_checkout_body;
+ handler.body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
+ handler.body_type = "text/xml";
+
+ handler.response_handler = svn_ra_serf__expect_empty_body;
+ handler.response_baton = &handler;
+
+ handler.method = "CHECKOUT";
+ handler.path = node_url;
+
+ SVN_ERR(svn_ra_serf__context_run_one(&handler, scratch_pool));
+
+ if (handler.sline.code != 201)
+ return svn_error_trace(return_response_err(&handler));
+
+ if (handler.location == NULL)
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("No Location header received"));
+
+ /* We only want the path portion of the Location header.
+ (code.google.com sometimes returns an 'http:' scheme for an
+ 'https:' transaction ... we'll work around that by stripping the
+ scheme, host, and port here and re-adding the correct ones
+ later. */
+ status = apr_uri_parse(scratch_pool, handler.location, &uri);
+ if (status)
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Error parsing Location header value"));
+
+ *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This is a wrapper around checkout_node() (which see for
+ documentation) which simply retries the CHECKOUT request when it
+ fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
+ server.
+
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
+ details.
+*/
+static svn_error_t *
+retry_checkout_node(const char **working_url,
+ const commit_context_t *commit_ctx,
+ const char *node_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ int retry_count = 5; /* Magic, arbitrary number. */
+
+ do
+ {
+ svn_error_clear(err);
+
+ err = checkout_node(working_url, commit_ctx, node_url,
+ result_pool, scratch_pool);
+
+ /* There's a small chance of a race condition here if Apache is
+ experiencing heavy commit concurrency or if the network has
+ long latency. It's possible that the value of HEAD changed
+ between the time we fetched the latest baseline and the time
+ we try to CHECKOUT that baseline. If that happens, Apache
+ will throw us a BAD_BASELINE error (deltaV says you can only
+ checkout the latest baseline). We just ignore that specific
+ error and retry a few times, asking for the latest baseline
+ again. */
+ if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
+ return err;
+ }
+ while (err && retry_count--);
+
+ return err;
+}
+
+
+static svn_error_t *
+checkout_dir(dir_context_t *dir,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ dir_context_t *p_dir = dir;
+ const char *checkout_url;
+ const char **working;
+
+ if (dir->working_url)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* Is this directory or one of our parent dirs newly added?
+ * If so, we're already implicitly checked out. */
+ while (p_dir)
+ {
+ if (p_dir->added)
+ {
+ /* Implicitly checkout this dir now. */
+ dir->working_url = svn_path_url_add_component2(
+ dir->parent_dir->working_url,
+ dir->name, dir->pool);
+ return SVN_NO_ERROR;
+ }
+ p_dir = p_dir->parent_dir;
+ }
+
+ /* We could be called twice for the root: once to checkout the baseline;
+ * once to checkout the directory itself if we need to do so.
+ * Note: CHECKOUT_URL should live longer than HANDLER.
+ */
+ if (!dir->parent_dir && !dir->commit->baseline_url)
+ {
+ checkout_url = dir->commit->vcc_url;
+ working = &dir->commit->baseline_url;
+ }
+ else
+ {
+ checkout_url = dir->url;
+ working = &dir->working_url;
+ }
+
+ /* Checkout our directory into the activity URL now. */
+ err = retry_checkout_node(working, dir->commit, checkout_url,
+ dir->pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_CONFLICT)
+ SVN_ERR_W(err, apr_psprintf(scratch_pool,
+ _("Directory '%s' is out of date; try updating"),
+ svn_dirent_local_style(dir->relpath, scratch_pool)));
+ return err;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *CHECKED_IN_URL to the appropriate DAV version url for
+ * RELPATH (relative to the root of SESSION).
+ *
+ * Try to find this version url in three ways:
+ * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
+ * version url from the working copy properties.
+ * Second, if the version url of the parent directory PARENT_VSN_URL is
+ * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
+ * RELPATH.
+ * Else, fetch the version url for the root of SESSION using CONN and
+ * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
+ * with RELPATH.
+ *
+ * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
+ * temporary allocation.
+ */
+static svn_error_t *
+get_version_url(const char **checked_in_url,
+ svn_ra_serf__session_t *session,
+ const char *relpath,
+ svn_revnum_t base_revision,
+ const char *parent_vsn_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *root_checkout;
+
+ if (session->wc_callbacks->get_wc_prop)
+ {
+ const svn_string_t *current_version;
+
+ SVN_ERR(session->wc_callbacks->get_wc_prop(
+ session->wc_callback_baton,
+ relpath,
+ SVN_RA_SERF__WC_CHECKED_IN_URL,
+ &current_version, scratch_pool));
+
+ if (current_version)
+ {
+ *checked_in_url =
+ svn_urlpath__canonicalize(current_version->data, result_pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (parent_vsn_url)
+ {
+ root_checkout = parent_vsn_url;
+ }
+ else
+ {
+ const char *propfind_url;
+ svn_ra_serf__connection_t *conn = session->conns[0];
+
+ if (SVN_IS_VALID_REVNUM(base_revision))
+ {
+ /* mod_dav_svn can't handle the "Label:" header that
+ svn_ra_serf__deliver_props() is going to try to use for
+ this lookup, so we'll do things the hard(er) way, by
+ looking up the version URL from a resource in the
+ baseline collection. */
+ /* ### conn==NULL for session->conns[0]. same as CONN. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
+ NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, base_revision,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ propfind_url = session->session_url.path;
+ }
+
+ SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout,
+ conn, propfind_url, base_revision,
+ "checked-in",
+ scratch_pool, scratch_pool));
+ if (!root_checkout)
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Path '%s' not present"),
+ session->session_url.path);
+
+ root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
+ }
+
+ *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+checkout_file(file_context_t *file,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ dir_context_t *parent_dir = file->parent_dir;
+ const char *checkout_url;
+
+ /* Is one of our parent dirs newly added? If so, we're already
+ * implicitly checked out.
+ */
+ while (parent_dir)
+ {
+ if (parent_dir->added)
+ {
+ /* Implicitly checkout this file now. */
+ file->working_url = svn_path_url_add_component2(
+ parent_dir->working_url,
+ svn_relpath_skip_ancestor(
+ parent_dir->relpath, file->relpath),
+ file->pool);
+ return SVN_NO_ERROR;
+ }
+ parent_dir = parent_dir->parent_dir;
+ }
+
+ SVN_ERR(get_version_url(&checkout_url,
+ file->commit->session,
+ file->relpath, file->base_revision,
+ NULL, scratch_pool, scratch_pool));
+
+ /* Checkout our file into the activity URL now. */
+ err = retry_checkout_node(&file->working_url, file->commit, checkout_url,
+ file->pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_CONFLICT)
+ SVN_ERR_W(err, apr_psprintf(scratch_pool,
+ _("File '%s' is out of date; try updating"),
+ svn_dirent_local_style(file->relpath, scratch_pool)));
+ return err;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper function for proppatch_walker() below. */
+static svn_error_t *
+get_encoding_and_cdata(const char **encoding_p,
+ const svn_string_t **encoded_value_p,
+ serf_bucket_alloc_t *alloc,
+ const svn_string_t *value,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (value == NULL)
+ {
+ *encoding_p = NULL;
+ *encoded_value_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* If a property is XML-safe, XML-encode it. Else, base64-encode
+ it. */
+ if (svn_xml_is_xml_safe(value->data, value->len))
+ {
+ svn_stringbuf_t *xml_esc = NULL;
+ svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
+ *encoding_p = NULL;
+ *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
+ }
+ else
+ {
+ *encoding_p = "base64";
+ *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+typedef struct walker_baton_t {
+ serf_bucket_t *body_bkt;
+ apr_pool_t *body_pool;
+
+ apr_hash_t *previous_changed_props;
+ apr_hash_t *previous_removed_props;
+
+ const char *path;
+
+ /* Hack, since change_rev_prop(old_value_p != NULL, value = NULL) uses D:set
+ rather than D:remove... (see notes/http-and-webdav/webdav-protocol) */
+ enum {
+ filter_all_props,
+ filter_props_with_old_value,
+ filter_props_without_old_value
+ } filter;
+
+ /* Is the property being deleted? */
+ svn_boolean_t deleting;
+} walker_baton_t;
+
+/* If we have (recorded in WB) the old value of the property named NS:NAME,
+ * then set *HAVE_OLD_VAL to TRUE and set *OLD_VAL_P to that old value
+ * (which may be NULL); else set *HAVE_OLD_VAL to FALSE. */
+static svn_error_t *
+derive_old_val(svn_boolean_t *have_old_val,
+ const svn_string_t **old_val_p,
+ walker_baton_t *wb,
+ const char *ns,
+ const char *name)
+{
+ *have_old_val = FALSE;
+
+ if (wb->previous_changed_props)
+ {
+ const svn_string_t *val;
+ val = svn_ra_serf__get_prop_string(wb->previous_changed_props,
+ wb->path, ns, name);
+ if (val)
+ {
+ *have_old_val = TRUE;
+ *old_val_p = val;
+ }
+ }
+
+ if (wb->previous_removed_props)
+ {
+ const svn_string_t *val;
+ val = svn_ra_serf__get_prop_string(wb->previous_removed_props,
+ wb->path, ns, name);
+ if (val)
+ {
+ *have_old_val = TRUE;
+ *old_val_p = NULL;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+proppatch_walker(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ walker_baton_t *wb = baton;
+ serf_bucket_t *body_bkt = wb->body_bkt;
+ serf_bucket_t *cdata_bkt;
+ serf_bucket_alloc_t *alloc;
+ const char *encoding;
+ svn_boolean_t have_old_val;
+ const svn_string_t *old_val;
+ const svn_string_t *encoded_value;
+ const char *prop_name;
+
+ SVN_ERR(derive_old_val(&have_old_val, &old_val, wb, ns, name));
+
+ /* Jump through hoops to work with D:remove and its val = (""-for-NULL)
+ * representation. */
+ if (wb->filter != filter_all_props)
+ {
+ if (wb->filter == filter_props_with_old_value && ! have_old_val)
+ return SVN_NO_ERROR;
+ if (wb->filter == filter_props_without_old_value && have_old_val)
+ return SVN_NO_ERROR;
+ }
+ if (wb->deleting)
+ val = NULL;
+
+ alloc = body_bkt->allocator;
+
+ SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, val,
+ wb->body_pool, scratch_pool));
+ if (encoded_value)
+ {
+ cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
+ encoded_value->len,
+ alloc);
+ }
+ else
+ {
+ cdata_bkt = NULL;
+ }
+
+ /* Use the namespace prefix instead of adding the xmlns attribute to support
+ property names containing ':' */
+ if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
+ prop_name = apr_pstrcat(wb->body_pool, "S:", name, (char *)NULL);
+ else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
+ prop_name = apr_pstrcat(wb->body_pool, "C:", name, (char *)NULL);
+
+ if (cdata_bkt)
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
+ "V:encoding", encoding,
+ NULL);
+ else
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
+ "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
+ NULL);
+
+ if (have_old_val)
+ {
+ const char *encoding2;
+ const svn_string_t *encoded_value2;
+ serf_bucket_t *cdata_bkt2;
+
+ SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
+ alloc, old_val,
+ wb->body_pool, scratch_pool));
+
+ if (encoded_value2)
+ {
+ cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
+ encoded_value2->len,
+ alloc);
+ }
+ else
+ {
+ cdata_bkt2 = NULL;
+ }
+
+ if (cdata_bkt2)
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "V:" SVN_DAV__OLD_VALUE,
+ "V:encoding", encoding2,
+ NULL);
+ else
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "V:" SVN_DAV__OLD_VALUE,
+ "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
+ NULL);
+
+ if (cdata_bkt2)
+ serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
+ "V:" SVN_DAV__OLD_VALUE);
+ }
+ if (cdata_bkt)
+ serf_bucket_aggregate_append(body_bkt, cdata_bkt);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
+
+ return SVN_NO_ERROR;
+}
+
+/* Possible add the lock-token "If:" precondition header to HEADERS if
+ an examination of COMMIT_CTX and RELPATH indicates that this is the
+ right thing to do.
+
+ Generally speaking, if the client provided a lock token for
+ RELPATH, it's the right thing to do. There is a notable instance
+ where this is not the case, however. If the file at RELPATH was
+ explicitly deleted in this commit already, then mod_dav removed its
+ lock token when it fielded the DELETE request, so we don't want to
+ set the lock precondition again. (See
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
+*/
+static svn_error_t *
+maybe_set_lock_token_header(serf_bucket_t *headers,
+ commit_context_t *commit_ctx,
+ const char *relpath,
+ apr_pool_t *pool)
+{
+ const char *token;
+
+ if (! (relpath && commit_ctx->lock_tokens))
+ return SVN_NO_ERROR;
+
+ if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
+ {
+ token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
+ if (token)
+ {
+ const char *token_header;
+ const char *token_uri;
+ apr_uri_t uri = commit_ctx->session->session_url;
+
+ /* Supplying the optional URI affects apache response when
+ the lock is broken, see issue 4369. When present any URI
+ must be absolute (RFC 2518 9.4). */
+ uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
+ pool);
+ token_uri = apr_uri_unparse(pool, &uri, 0);
+
+ token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
+ (char *)NULL);
+ serf_bucket_headers_set(headers, "If", token_header);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_proppatch_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ proppatch_context_t *proppatch = baton;
+
+ if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
+ apr_psprintf(pool, "%ld",
+ proppatch->base_revision));
+ }
+
+ SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit,
+ proppatch->relpath, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+struct proppatch_body_baton_t {
+ proppatch_context_t *proppatch;
+
+ /* Content in the body should be allocated here, to live long enough. */
+ apr_pool_t *body_pool;
+};
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_proppatch_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *scratch_pool)
+{
+ struct proppatch_body_baton_t *pbb = baton;
+ proppatch_context_t *ctx = pbb->proppatch;
+ serf_bucket_t *body_bkt;
+ walker_baton_t wb = { 0 };
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
+ "xmlns:D", "DAV:",
+ "xmlns:V", SVN_DAV_PROP_NS_DAV,
+ "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
+ "xmlns:S", SVN_DAV_PROP_NS_SVN,
+ NULL);
+
+ wb.body_bkt = body_bkt;
+ wb.body_pool = pbb->body_pool;
+ wb.previous_changed_props = ctx->previous_changed_props;
+ wb.previous_removed_props = ctx->previous_removed_props;
+ wb.path = ctx->path;
+
+ if (apr_hash_count(ctx->changed_props) > 0)
+ {
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
+
+ wb.filter = filter_all_props;
+ wb.deleting = FALSE;
+ SVN_ERR(svn_ra_serf__walk_all_props(ctx->changed_props, ctx->path,
+ SVN_INVALID_REVNUM,
+ proppatch_walker, &wb,
+ scratch_pool));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
+ }
+
+ if (apr_hash_count(ctx->removed_props) > 0)
+ {
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
+
+ wb.filter = filter_props_with_old_value;
+ wb.deleting = TRUE;
+ SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
+ SVN_INVALID_REVNUM,
+ proppatch_walker, &wb,
+ scratch_pool));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
+ }
+
+ if (apr_hash_count(ctx->removed_props) > 0)
+ {
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
+
+ wb.filter = filter_props_without_old_value;
+ wb.deleting = TRUE;
+ SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
+ SVN_INVALID_REVNUM,
+ proppatch_walker, &wb,
+ scratch_pool));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
+ }
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t*
+proppatch_resource(proppatch_context_t *proppatch,
+ commit_context_t *commit,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__handler_t *handler;
+ struct proppatch_body_baton_t pbb;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->method = "PROPPATCH";
+ handler->path = proppatch->path;
+ handler->conn = commit->conn;
+ handler->session = commit->session;
+
+ handler->header_delegate = setup_proppatch_headers;
+ handler->header_delegate_baton = proppatch;
+
+ pbb.proppatch = proppatch;
+ pbb.body_pool = pool;
+ handler->body_delegate = create_proppatch_body;
+ handler->body_delegate_baton = &pbb;
+
+ handler->response_handler = svn_ra_serf__handle_multistatus_only;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+ if (handler->sline.code != 207
+ || (handler->server_error != NULL
+ && handler->server_error->error != NULL))
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_PROPPATCH_FAILED,
+ return_response_err(handler),
+ _("At least one property change failed; repository"
+ " is unchanged"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_put_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ file_context_t *ctx = baton;
+ apr_off_t offset;
+
+ /* We need to flush the file, make it unbuffered (so that it can be
+ * zero-copied via mmap), and reset the position before attempting to
+ * deliver the file.
+ *
+ * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
+ * and zero-copy the PUT body. However, on older APR versions, we can't
+ * check the buffer status; but serf will fall through and create a file
+ * bucket for us on the buffered svndiff handle.
+ */
+ apr_file_flush(ctx->svndiff);
+#if APR_VERSION_AT_LEAST(1, 3, 0)
+ apr_file_buffer_set(ctx->svndiff, NULL, 0);
+#endif
+ offset = 0;
+ apr_file_seek(ctx->svndiff, APR_SET, &offset);
+
+ *body_bkt = serf_bucket_file_create(ctx->svndiff, alloc);
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_empty_put_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_put_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ file_context_t *ctx = baton;
+
+ if (SVN_IS_VALID_REVNUM(ctx->base_revision))
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
+ apr_psprintf(pool, "%ld", ctx->base_revision));
+ }
+
+ if (ctx->base_checksum)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
+ ctx->base_checksum);
+ }
+
+ if (ctx->result_checksum)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
+ ctx->result_checksum);
+ }
+
+ SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit,
+ ctx->relpath, pool));
+
+ return APR_SUCCESS;
+}
+
+static svn_error_t *
+setup_copy_file_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ file_context_t *file = baton;
+ apr_uri_t uri;
+ const char *absolute_uri;
+
+ /* The Dest URI must be absolute. Bummer. */
+ uri = file->commit->session->session_url;
+ uri.path = (char*)file->url;
+ absolute_uri = apr_uri_unparse(pool, &uri, 0);
+
+ serf_bucket_headers_set(headers, "Destination", absolute_uri);
+
+ serf_bucket_headers_setn(headers, "Depth", "0");
+ serf_bucket_headers_setn(headers, "Overwrite", "T");
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_copy_dir_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = baton;
+ apr_uri_t uri;
+ const char *absolute_uri;
+
+ /* The Dest URI must be absolute. Bummer. */
+ uri = dir->commit->session->session_url;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ uri.path = (char *)dir->url;
+ }
+ else
+ {
+ uri.path = (char *)svn_path_url_add_component2(
+ dir->parent_dir->working_url,
+ dir->name, pool);
+ }
+ absolute_uri = apr_uri_unparse(pool, &uri, 0);
+
+ serf_bucket_headers_set(headers, "Destination", absolute_uri);
+
+ serf_bucket_headers_setn(headers, "Depth", "infinity");
+ serf_bucket_headers_setn(headers, "Overwrite", "T");
+
+ /* Implicitly checkout this dir now. */
+ dir->working_url = apr_pstrdup(dir->pool, uri.path);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_delete_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ delete_context_t *ctx = baton;
+
+ serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
+ apr_ltoa(pool, ctx->revision));
+
+ if (ctx->lock_token_hash)
+ {
+ ctx->lock_token = svn_hash_gets(ctx->lock_token_hash, ctx->path);
+
+ if (ctx->lock_token)
+ {
+ const char *token_header;
+
+ token_header = apr_pstrcat(pool, "<", ctx->path, "> (<",
+ ctx->lock_token, ">)", (char *)NULL);
+
+ serf_bucket_headers_set(headers, "If", token_header);
+
+ if (ctx->keep_locks)
+ serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
+ SVN_DAV_OPTION_KEEP_LOCKS);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_delete_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ delete_context_t *ctx = baton;
+ serf_bucket_t *body;
+
+ body = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(body, alloc);
+
+ svn_ra_serf__merge_lock_token_list(ctx->lock_token_hash, ctx->path,
+ body, alloc, pool);
+
+ *body_bkt = body;
+ return SVN_NO_ERROR;
+}
+
+/* Helper function to write the svndiff stream to temporary file. */
+static svn_error_t *
+svndiff_stream_write(void *file_baton,
+ const char *data,
+ apr_size_t *len)
+{
+ file_context_t *ctx = file_baton;
+ apr_status_t status;
+
+ status = apr_file_write_full(ctx->svndiff, data, *len, NULL);
+ if (status)
+ return svn_error_wrap_apr(status, _("Failed writing updated file"));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* POST against 'me' resource handlers. */
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_txn_post_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ apr_hash_t *revprops = baton;
+ svn_skel_t *request_skel;
+ svn_stringbuf_t *skel_str;
+
+ request_skel = svn_skel__make_empty_list(pool);
+ if (revprops)
+ {
+ svn_skel_t *proplist_skel;
+
+ SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
+ svn_skel__prepend(proplist_skel, request_skel);
+ svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
+ skel_str = svn_skel__unparse(request_skel, pool);
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
+ }
+ else
+ {
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_header_delegate_t */
+static svn_error_t *
+setup_post_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+#ifdef SVN_DAV_SEND_VTXN_NAME
+ /* Enable this to exercise the VTXN-NAME code based on a client
+ supplied transaction name. */
+ serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
+ svn_uuid_generate(pool));
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Handler baton for POST request. */
+typedef struct post_response_ctx_t
+{
+ svn_ra_serf__handler_t *handler;
+ commit_context_t *commit_ctx;
+} post_response_ctx_t;
+
+
+/* This implements serf_bucket_headers_do_callback_fn_t. */
+static int
+post_headers_iterator_callback(void *baton,
+ const char *key,
+ const char *val)
+{
+ post_response_ctx_t *prc = baton;
+ commit_context_t *prc_cc = prc->commit_ctx;
+ svn_ra_serf__session_t *sess = prc_cc->session;
+
+ /* If we provided a UUID to the POST request, we should get back
+ from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
+ expect the SVN_DAV_TXN_NAME_HEADER. We certainly don't expect to
+ see both. */
+
+ if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
+ {
+ /* Build out txn and txn-root URLs using the txn name we're
+ given, and store the whole lot of it in the commit context. */
+ prc_cc->txn_url =
+ svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
+ prc_cc->txn_root_url =
+ svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
+ }
+
+ if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
+ {
+ /* Build out vtxn and vtxn-root URLs using the vtxn name we're
+ given, and store the whole lot of it in the commit context. */
+ prc_cc->txn_url =
+ svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
+ prc_cc->txn_root_url =
+ svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
+ }
+
+ return 0;
+}
+
+
+/* A custom serf_response_handler_t which is mostly a wrapper around
+ svn_ra_serf__expect_empty_body -- it just notices POST response
+ headers, too.
+
+ Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+post_response_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ post_response_ctx_t *prc = baton;
+ serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
+
+ /* Then see which ones we can discover. */
+ serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
+
+ /* Execute the 'real' response handler to XML-parse the repsonse body. */
+ return svn_ra_serf__expect_empty_body(request, response,
+ prc->handler, scratch_pool);
+}
+
+
+
+/* Commit baton callbacks */
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **root_baton)
+{
+ commit_context_t *ctx = edit_baton;
+ svn_ra_serf__handler_t *handler;
+ proppatch_context_t *proppatch_ctx;
+ dir_context_t *dir;
+ apr_hash_index_t *hi;
+ const char *proppatch_target = NULL;
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session))
+ {
+ post_response_ctx_t *prc;
+ const char *rel_path;
+ svn_boolean_t post_with_revprops
+ = (NULL != svn_hash_gets(ctx->session->supported_posts,
+ "create-txn-with-props"));
+
+ /* Create our activity URL now on the server. */
+ handler = apr_pcalloc(ctx->pool, sizeof(*handler));
+ handler->handler_pool = ctx->pool;
+ handler->method = "POST";
+ handler->body_type = SVN_SKEL_MIME_TYPE;
+ handler->body_delegate = create_txn_post_body;
+ handler->body_delegate_baton =
+ post_with_revprops ? ctx->revprop_table : NULL;
+ handler->header_delegate = setup_post_headers;
+ handler->header_delegate_baton = NULL;
+ handler->path = ctx->session->me_resource;
+ handler->conn = ctx->session->conns[0];
+ handler->session = ctx->session;
+
+ prc = apr_pcalloc(ctx->pool, sizeof(*prc));
+ prc->handler = handler;
+ prc->commit_ctx = ctx;
+
+ handler->response_handler = post_response_handler;
+ handler->response_baton = prc;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
+
+ if (handler->sline.code != 201)
+ {
+ apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
+ switch (handler->sline.code)
+ {
+ case 403:
+ status = SVN_ERR_RA_DAV_FORBIDDEN;
+ break;
+ case 404:
+ status = SVN_ERR_FS_NOT_FOUND;
+ break;
+ }
+
+ return svn_error_createf(status, NULL,
+ _("%s of '%s': %d %s (%s://%s)"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason,
+ ctx->session->session_url.scheme,
+ ctx->session->session_url.hostinfo);
+ }
+ if (! (ctx->txn_root_url && ctx->txn_url))
+ {
+ return svn_error_createf(
+ SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("POST request did not return transaction information"));
+ }
+
+ /* Fixup the txn_root_url to point to the anchor of the commit. */
+ SVN_ERR(svn_ra_serf__get_relative_path(&rel_path,
+ ctx->session->session_url.path,
+ ctx->session, NULL, dir_pool));
+ ctx->txn_root_url = svn_path_url_add_component2(ctx->txn_root_url,
+ rel_path, ctx->pool);
+
+ /* Build our directory baton. */
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+ dir->pool = dir_pool;
+ dir->commit = ctx;
+ dir->base_revision = base_revision;
+ dir->relpath = "";
+ dir->name = "";
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+ dir->url = apr_pstrdup(dir->pool, ctx->txn_root_url);
+
+ /* If we included our revprops in the POST, we need not
+ PROPPATCH them. */
+ proppatch_target = post_with_revprops ? NULL : ctx->txn_url;
+ }
+ else
+ {
+ const char *activity_str = ctx->session->activity_collection_url;
+
+ if (!activity_str)
+ SVN_ERR(svn_ra_serf__v1_get_activity_collection(&activity_str,
+ ctx->session->conns[0],
+ ctx->pool,
+ ctx->pool));
+
+ /* Cache the result. */
+ if (activity_str)
+ {
+ ctx->session->activity_collection_url =
+ apr_pstrdup(ctx->session->pool, activity_str);
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS response did not include the "
+ "requested activity-collection-set value"));
+ }
+
+ ctx->activity_url =
+ svn_path_url_add_component2(activity_str, svn_uuid_generate(ctx->pool),
+ ctx->pool);
+
+ /* Create our activity URL now on the server. */
+ handler = apr_pcalloc(ctx->pool, sizeof(*handler));
+ handler->handler_pool = ctx->pool;
+ handler->method = "MKACTIVITY";
+ handler->path = ctx->activity_url;
+ handler->conn = ctx->session->conns[0];
+ handler->session = ctx->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
+
+ if (handler->sline.code != 201)
+ {
+ apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
+ switch (handler->sline.code)
+ {
+ case 403:
+ status = SVN_ERR_RA_DAV_FORBIDDEN;
+ break;
+ case 404:
+ status = SVN_ERR_FS_NOT_FOUND;
+ break;
+ }
+
+ return svn_error_createf(status, NULL,
+ _("%s of '%s': %d %s (%s://%s)"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason,
+ ctx->session->session_url.scheme,
+ ctx->session->session_url.hostinfo);
+ }
+
+ /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&(ctx->vcc_url), ctx->session,
+ ctx->conn, ctx->pool));
+
+
+ /* Build our directory baton. */
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+ dir->pool = dir_pool;
+ dir->commit = ctx;
+ dir->base_revision = base_revision;
+ dir->relpath = "";
+ dir->name = "";
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+
+ SVN_ERR(get_version_url(&dir->url, dir->commit->session,
+ dir->relpath,
+ dir->base_revision, ctx->checked_in_url,
+ dir->pool, dir->pool /* scratch_pool */));
+ ctx->checked_in_url = dir->url;
+
+ /* Checkout our root dir */
+ SVN_ERR(checkout_dir(dir, dir->pool /* scratch_pool */));
+
+ proppatch_target = ctx->baseline_url;
+ }
+
+ /* Unless this is NULL -- which means we don't need to PROPPATCH the
+ transaction with our revprops -- then, you know, PROPPATCH the
+ transaction with our revprops. */
+ if (proppatch_target)
+ {
+ proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = dir_pool;
+ proppatch_ctx->commit = ctx;
+ proppatch_ctx->path = proppatch_target;
+ proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
+
+ for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+ const char *ns;
+
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ svn_ra_serf__set_prop(proppatch_ctx->changed_props,
+ proppatch_ctx->path,
+ ns, name, value, proppatch_ctx->pool);
+ }
+
+ SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
+ }
+
+ *root_baton = dir;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = parent_baton;
+ delete_context_t *delete_ctx;
+ svn_ra_serf__handler_t *handler;
+ const char *delete_target;
+ svn_error_t *err;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ delete_target = svn_path_url_add_component2(dir->commit->txn_root_url,
+ path, dir->pool);
+ }
+ else
+ {
+ /* Ensure our directory has been checked out */
+ SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
+ delete_target = svn_path_url_add_component2(dir->working_url,
+ svn_relpath_basename(path,
+ NULL),
+ pool);
+ }
+
+ /* DELETE our entry */
+ delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
+ delete_ctx->path = apr_pstrdup(pool, path);
+ delete_ctx->revision = revision;
+ delete_ctx->lock_token_hash = dir->commit->lock_tokens;
+ delete_ctx->keep_locks = dir->commit->keep_locks;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->session = dir->commit->session;
+ handler->conn = dir->commit->conn;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ handler->header_delegate = setup_delete_headers;
+ handler->header_delegate_baton = delete_ctx;
+
+ handler->method = "DELETE";
+ handler->path = delete_target;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ if (err &&
+ (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN ||
+ err->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN ||
+ err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH ||
+ err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED))
+ {
+ svn_error_clear(err);
+
+ /* An error has been registered on the connection. Reset the thing
+ so that we can use it again. */
+ serf_connection_reset(handler->conn->conn);
+
+ handler->body_delegate = create_delete_body;
+ handler->body_delegate_baton = delete_ctx;
+ handler->body_type = "text/xml";
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+ }
+ else if (err)
+ {
+ return err;
+ }
+
+ /* 204 No Content: item successfully deleted */
+ if (handler->sline.code != 204)
+ {
+ return svn_error_trace(return_response_err(handler));
+ }
+
+ svn_hash_sets(dir->commit->deleted_entries,
+ apr_pstrdup(dir->commit->pool, path), (void *)1);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *dir_pool,
+ void **child_baton)
+{
+ dir_context_t *parent = parent_baton;
+ dir_context_t *dir;
+ svn_ra_serf__handler_t *handler;
+ apr_status_t status;
+ const char *mkcol_target;
+
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+
+ dir->pool = dir_pool;
+ dir->parent_dir = parent;
+ dir->commit = parent->commit;
+ dir->added = TRUE;
+ dir->base_revision = SVN_INVALID_REVNUM;
+ dir->copy_revision = copyfrom_revision;
+ dir->copy_path = copyfrom_path;
+ dir->relpath = apr_pstrdup(dir->pool, path);
+ dir->name = svn_relpath_basename(dir->relpath, NULL);
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
+ path, dir->pool);
+ mkcol_target = dir->url;
+ }
+ else
+ {
+ /* Ensure our parent is checked out. */
+ SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
+
+ dir->url = svn_path_url_add_component2(parent->commit->checked_in_url,
+ dir->name, dir->pool);
+ mkcol_target = svn_path_url_add_component2(
+ parent->working_url,
+ dir->name, dir->pool);
+ }
+
+ handler = apr_pcalloc(dir->pool, sizeof(*handler));
+ handler->handler_pool = dir->pool;
+ handler->conn = dir->commit->conn;
+ handler->session = dir->commit->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+ if (!dir->copy_path)
+ {
+ handler->method = "MKCOL";
+ handler->path = mkcol_target;
+ }
+ else
+ {
+ apr_uri_t uri;
+ const char *req_url;
+
+ status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unable to parse URL '%s'"),
+ dir->copy_path);
+ }
+
+ /* ### conn==NULL for session->conns[0]. same as commit->conn. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ dir->commit->session,
+ NULL /* conn */,
+ uri.path, dir->copy_revision,
+ dir_pool, dir_pool));
+
+ handler->method = "COPY";
+ handler->path = req_url;
+
+ handler->header_delegate = setup_copy_dir_headers;
+ handler->header_delegate_baton = dir;
+ }
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
+
+ switch (handler->sline.code)
+ {
+ case 201: /* Created: item was successfully copied */
+ case 204: /* No Content: item successfully replaced an existing target */
+ break;
+
+ case 403:
+ return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
+ _("Access to '%s' forbidden"),
+ handler->path);
+ default:
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Adding directory failed: %s on %s "
+ "(%d %s)"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason);
+ }
+
+ *child_baton = dir;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **child_baton)
+{
+ dir_context_t *parent = parent_baton;
+ dir_context_t *dir;
+
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+
+ dir->pool = dir_pool;
+
+ dir->parent_dir = parent;
+ dir->commit = parent->commit;
+
+ dir->added = FALSE;
+ dir->base_revision = base_revision;
+ dir->relpath = apr_pstrdup(dir->pool, path);
+ dir->name = svn_relpath_basename(dir->relpath, NULL);
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
+ path, dir->pool);
+ }
+ else
+ {
+ SVN_ERR(get_version_url(&dir->url,
+ dir->commit->session,
+ dir->relpath, dir->base_revision,
+ dir->commit->checked_in_url,
+ dir->pool, dir->pool /* scratch_pool */));
+ }
+ *child_baton = dir;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = dir_baton;
+ const char *ns;
+ const char *proppatch_target;
+
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ proppatch_target = dir->url;
+ }
+ else
+ {
+ /* Ensure we have a checked out dir. */
+ SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
+
+ proppatch_target = dir->working_url;
+ }
+
+ name = apr_pstrdup(dir->pool, name);
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ if (value)
+ {
+ value = svn_string_dup(value, dir->pool);
+ svn_ra_serf__set_prop(dir->changed_props, proppatch_target,
+ ns, name, value, dir->pool);
+ }
+ else
+ {
+ value = svn_string_create_empty(dir->pool);
+ svn_ra_serf__set_prop(dir->removed_props, proppatch_target,
+ ns, name, value, dir->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = dir_baton;
+
+ /* Huh? We're going to be called before the texts are sent. Ugh.
+ * Therefore, just wave politely at our caller.
+ */
+
+ /* PROPPATCH our prop change and pass it along. */
+ if (apr_hash_count(dir->changed_props) ||
+ apr_hash_count(dir->removed_props))
+ {
+ proppatch_context_t *proppatch_ctx;
+
+ proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = pool;
+ proppatch_ctx->commit = dir->commit;
+ proppatch_ctx->relpath = dir->relpath;
+ proppatch_ctx->changed_props = dir->changed_props;
+ proppatch_ctx->removed_props = dir->removed_props;
+ proppatch_ctx->base_revision = dir->base_revision;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ proppatch_ctx->path = dir->url;
+ }
+ else
+ {
+ proppatch_ctx->path = dir->working_url;
+ }
+
+ SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, dir->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copy_path,
+ svn_revnum_t copy_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ dir_context_t *dir = parent_baton;
+ file_context_t *new_file;
+ const char *deleted_parent = path;
+
+ new_file = apr_pcalloc(file_pool, sizeof(*new_file));
+ new_file->pool = file_pool;
+
+ dir->ref_count++;
+
+ new_file->parent_dir = dir;
+ new_file->commit = dir->commit;
+ new_file->relpath = apr_pstrdup(new_file->pool, path);
+ new_file->name = svn_relpath_basename(new_file->relpath, NULL);
+ new_file->added = TRUE;
+ new_file->base_revision = SVN_INVALID_REVNUM;
+ new_file->copy_path = copy_path;
+ new_file->copy_revision = copy_revision;
+ new_file->changed_props = apr_hash_make(new_file->pool);
+ new_file->removed_props = apr_hash_make(new_file->pool);
+
+ /* Ensure that the file doesn't exist by doing a HEAD on the
+ resource. If we're using HTTP v2, we'll just look into the
+ transaction root tree for this thing. */
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ new_file->url = svn_path_url_add_component2(dir->commit->txn_root_url,
+ path, new_file->pool);
+ }
+ else
+ {
+ /* Ensure our parent directory has been checked out */
+ SVN_ERR(checkout_dir(dir, new_file->pool /* scratch_pool */));
+
+ new_file->url =
+ svn_path_url_add_component2(dir->working_url,
+ new_file->name, new_file->pool);
+ }
+
+ while (deleted_parent && deleted_parent[0] != '\0')
+ {
+ if (svn_hash_gets(dir->commit->deleted_entries, deleted_parent))
+ {
+ break;
+ }
+ deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
+ }
+
+ if (! ((dir->added && !dir->copy_path) ||
+ (deleted_parent && deleted_parent[0] != '\0')))
+ {
+ svn_ra_serf__handler_t *handler;
+
+ handler = apr_pcalloc(new_file->pool, sizeof(*handler));
+ handler->handler_pool = new_file->pool;
+ handler->session = new_file->commit->session;
+ handler->conn = new_file->commit->conn;
+ handler->method = "HEAD";
+ handler->path = svn_path_url_add_component2(
+ dir->commit->session->session_url.path,
+ path, new_file->pool);
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, new_file->pool));
+
+ if (handler->sline.code != 404)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS, NULL,
+ _("File '%s' already exists"), path);
+ }
+ }
+
+ *file_baton = new_file;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ dir_context_t *parent = parent_baton;
+ file_context_t *new_file;
+
+ new_file = apr_pcalloc(file_pool, sizeof(*new_file));
+ new_file->pool = file_pool;
+
+ parent->ref_count++;
+
+ new_file->parent_dir = parent;
+ new_file->commit = parent->commit;
+ new_file->relpath = apr_pstrdup(new_file->pool, path);
+ new_file->name = svn_relpath_basename(new_file->relpath, NULL);
+ new_file->added = FALSE;
+ new_file->base_revision = base_revision;
+ new_file->changed_props = apr_hash_make(new_file->pool);
+ new_file->removed_props = apr_hash_make(new_file->pool);
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit))
+ {
+ new_file->url = svn_path_url_add_component2(parent->commit->txn_root_url,
+ path, new_file->pool);
+ }
+ else
+ {
+ /* CHECKOUT the file into our activity. */
+ SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
+
+ new_file->url = new_file->working_url;
+ }
+
+ *file_baton = new_file;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ file_context_t *ctx = file_baton;
+
+ /* Store the stream in a temporary file; we'll give it to serf when we
+ * close this file.
+ *
+ * TODO: There should be a way we can stream the request body instead of
+ * writing to a temporary file (ugh). A special svn stream serf bucket
+ * that returns EAGAIN until we receive the done call? But, when
+ * would we run through the serf context? Grr.
+ *
+ * ctx->pool is the same for all files in the commit that send a
+ * textdelta so this file is explicitly closed in close_file to
+ * avoid too many simultaneously open files.
+ */
+
+ SVN_ERR(svn_io_open_unique_file3(&ctx->svndiff, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ ctx->pool, pool));
+
+ ctx->stream = svn_stream_create(ctx, pool);
+ svn_stream_set_write(ctx->stream, svndiff_stream_write);
+
+ svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+
+ if (base_checksum)
+ ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ file_context_t *file = file_baton;
+ const char *ns;
+
+ name = apr_pstrdup(file->pool, name);
+
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ if (value)
+ {
+ value = svn_string_dup(value, file->pool);
+ svn_ra_serf__set_prop(file->changed_props, file->url,
+ ns, name, value, file->pool);
+ }
+ else
+ {
+ value = svn_string_create_empty(file->pool);
+
+ svn_ra_serf__set_prop(file->removed_props, file->url,
+ ns, name, value, file->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *scratch_pool)
+{
+ file_context_t *ctx = file_baton;
+ svn_boolean_t put_empty_file = FALSE;
+ apr_status_t status;
+
+ ctx->result_checksum = text_checksum;
+
+ if (ctx->copy_path)
+ {
+ svn_ra_serf__handler_t *handler;
+ apr_uri_t uri;
+ const char *req_url;
+
+ status = apr_uri_parse(scratch_pool, ctx->copy_path, &uri);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unable to parse URL '%s'"),
+ ctx->copy_path);
+ }
+
+ /* ### conn==NULL for session->conns[0]. same as commit->conn. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ ctx->commit->session,
+ NULL /* conn */,
+ uri.path, ctx->copy_revision,
+ scratch_pool, scratch_pool));
+
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+ handler->handler_pool = scratch_pool;
+ handler->method = "COPY";
+ handler->path = req_url;
+ handler->conn = ctx->commit->conn;
+ handler->session = ctx->commit->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ handler->header_delegate = setup_copy_file_headers;
+ handler->header_delegate_baton = ctx;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
+
+ if (handler->sline.code != 201 && handler->sline.code != 204)
+ {
+ return svn_error_trace(return_response_err(handler));
+ }
+ }
+
+ /* If we got no stream of changes, but this is an added-without-history
+ * file, make a note that we'll be PUTting a zero-byte file to the server.
+ */
+ if ((!ctx->stream) && ctx->added && (!ctx->copy_path))
+ put_empty_file = TRUE;
+
+ /* If we had a stream of changes, push them to the server... */
+ if (ctx->stream || put_empty_file)
+ {
+ svn_ra_serf__handler_t *handler;
+
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+ handler->handler_pool = scratch_pool;
+ handler->method = "PUT";
+ handler->path = ctx->url;
+ handler->conn = ctx->commit->conn;
+ handler->session = ctx->commit->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ if (put_empty_file)
+ {
+ handler->body_delegate = create_empty_put_body;
+ handler->body_delegate_baton = ctx;
+ handler->body_type = "text/plain";
+ }
+ else
+ {
+ handler->body_delegate = create_put_body;
+ handler->body_delegate_baton = ctx;
+ handler->body_type = SVN_SVNDIFF_MIME_TYPE;
+ }
+
+ handler->header_delegate = setup_put_headers;
+ handler->header_delegate_baton = ctx;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
+
+ if (handler->sline.code != 204 && handler->sline.code != 201)
+ {
+ return svn_error_trace(return_response_err(handler));
+ }
+ }
+
+ if (ctx->svndiff)
+ SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
+
+ /* If we had any prop changes, push them via PROPPATCH. */
+ if (apr_hash_count(ctx->changed_props) ||
+ apr_hash_count(ctx->removed_props))
+ {
+ proppatch_context_t *proppatch;
+
+ proppatch = apr_pcalloc(ctx->pool, sizeof(*proppatch));
+ proppatch->pool = ctx->pool;
+ proppatch->relpath = ctx->relpath;
+ proppatch->path = ctx->url;
+ proppatch->commit = ctx->commit;
+ proppatch->changed_props = ctx->changed_props;
+ proppatch->removed_props = ctx->removed_props;
+ proppatch->base_revision = ctx->base_revision;
+
+ SVN_ERR(proppatch_resource(proppatch, ctx->commit, ctx->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ commit_context_t *ctx = edit_baton;
+ const char *merge_target =
+ ctx->activity_url ? ctx->activity_url : ctx->txn_url;
+ const svn_commit_info_t *commit_info;
+ int response_code;
+
+ /* MERGE our activity */
+ SVN_ERR(svn_ra_serf__run_merge(&commit_info, &response_code,
+ ctx->session,
+ ctx->session->conns[0],
+ merge_target,
+ ctx->lock_tokens,
+ ctx->keep_locks,
+ pool, pool));
+
+ if (response_code != 200)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("MERGE request failed: returned %d "
+ "(during commit)"),
+ response_code);
+ }
+
+ /* Inform the WC that we did a commit. */
+ if (ctx->callback)
+ SVN_ERR(ctx->callback(commit_info, ctx->callback_baton, pool));
+
+ /* If we're using activities, DELETE our completed activity. */
+ if (ctx->activity_url)
+ {
+ svn_ra_serf__handler_t *handler;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->method = "DELETE";
+ handler->path = ctx->activity_url;
+ handler->conn = ctx->conn;
+ handler->session = ctx->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+ SVN_ERR_ASSERT(handler->sline.code == 204);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+abort_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ commit_context_t *ctx = edit_baton;
+ svn_ra_serf__handler_t *handler;
+
+ /* If an activity or transaction wasn't even created, don't bother
+ trying to delete it. */
+ if (! (ctx->activity_url || ctx->txn_url))
+ return SVN_NO_ERROR;
+
+ /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
+ had a problem. We need to reset it, in order to use it again. */
+ serf_connection_reset(ctx->session->conns[0]->conn);
+
+ /* DELETE our aborted activity */
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->method = "DELETE";
+ handler->conn = ctx->session->conns[0];
+ handler->session = ctx->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
+ handler->path = ctx->txn_url;
+ else
+ handler->path = ctx->activity_url;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+ /* 204 if deleted,
+ 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
+ 404 if the activity wasn't found. */
+ if (handler->sline.code != 204
+ && handler->sline.code != 403
+ && handler->sline.code != 404
+ )
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
+ const svn_delta_editor_t **ret_editor,
+ void **edit_baton,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_delta_editor_t *editor;
+ commit_context_t *ctx;
+ const char *repos_root;
+ const char *base_relpath;
+ svn_boolean_t supports_ephemeral_props;
+
+ ctx = apr_pcalloc(pool, sizeof(*ctx));
+
+ ctx->pool = pool;
+
+ ctx->session = session;
+ ctx->conn = session->conns[0];
+
+ ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
+
+ /* If the server supports ephemeral properties, add some carrying
+ interesting version information. */
+ SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
+ SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
+ pool));
+ if (supports_ephemeral_props)
+ {
+ svn_hash_sets(ctx->revprop_table,
+ apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
+ svn_string_create(SVN_VER_NUMBER, pool));
+ svn_hash_sets(ctx->revprop_table,
+ apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
+ svn_string_create(session->useragent, pool));
+ }
+
+ ctx->callback = callback;
+ ctx->callback_baton = callback_baton;
+
+ ctx->lock_tokens = lock_tokens;
+ ctx->keep_locks = keep_locks;
+
+ ctx->deleted_entries = apr_hash_make(ctx->pool);
+
+ editor = svn_delta_default_editor(pool);
+ editor->open_root = open_root;
+ editor->delete_entry = delete_entry;
+ editor->add_directory = add_directory;
+ editor->open_directory = open_directory;
+ editor->change_dir_prop = change_dir_prop;
+ editor->close_directory = close_directory;
+ editor->add_file = add_file;
+ editor->open_file = open_file;
+ editor->apply_textdelta = apply_textdelta;
+ editor->change_file_prop = change_file_prop;
+ editor->close_file = close_file;
+ editor->close_edit = close_edit;
+ editor->abort_edit = abort_edit;
+
+ *ret_editor = editor;
+ *edit_baton = ctx;
+
+ SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
+ base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
+ pool);
+
+ SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
+ *edit_baton, repos_root, base_relpath,
+ session->shim_callbacks, pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ proppatch_context_t *proppatch_ctx;
+ commit_context_t *commit;
+ const char *proppatch_target;
+ const char *ns;
+ svn_error_t *err;
+
+ if (old_value_p)
+ {
+ svn_boolean_t capable;
+ SVN_ERR(svn_ra_serf__has_capability(ra_session, &capable,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ pool));
+
+ /* How did you get past the same check in svn_ra_change_rev_prop2()? */
+ SVN_ERR_ASSERT(capable);
+ }
+
+ commit = apr_pcalloc(pool, sizeof(*commit));
+
+ commit->pool = pool;
+
+ commit->session = session;
+ commit->conn = session->conns[0];
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ {
+ proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
+ }
+ else
+ {
+ const char *vcc_url;
+
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, commit->session,
+ commit->conn, pool));
+
+ SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
+ commit->conn, vcc_url, rev,
+ "href",
+ pool, pool));
+ }
+
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ /* PROPPATCH our log message and pass it along. */
+ proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = pool;
+ proppatch_ctx->commit = commit;
+ proppatch_ctx->path = proppatch_target;
+ proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
+ if (old_value_p)
+ {
+ proppatch_ctx->previous_changed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->previous_removed_props = apr_hash_make(proppatch_ctx->pool);
+ }
+ proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
+
+ if (old_value_p && *old_value_p)
+ {
+ svn_ra_serf__set_prop(proppatch_ctx->previous_changed_props,
+ proppatch_ctx->path,
+ ns, name, *old_value_p, proppatch_ctx->pool);
+ }
+ else if (old_value_p)
+ {
+ svn_string_t *dummy_value = svn_string_create_empty(proppatch_ctx->pool);
+
+ svn_ra_serf__set_prop(proppatch_ctx->previous_removed_props,
+ proppatch_ctx->path,
+ ns, name, dummy_value, proppatch_ctx->pool);
+ }
+
+ if (value)
+ {
+ svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
+ ns, name, value, proppatch_ctx->pool);
+ }
+ else
+ {
+ value = svn_string_create_empty(proppatch_ctx->pool);
+
+ svn_ra_serf__set_prop(proppatch_ctx->removed_props, proppatch_ctx->path,
+ ns, name, value, proppatch_ctx->pool);
+ }
+
+ err = proppatch_resource(proppatch_ctx, commit, proppatch_ctx->pool);
+ if (err)
+ return
+ svn_error_create
+ (SVN_ERR_RA_DAV_REQUEST_FAILED, err,
+ _("DAV request failed; it's possible that the repository's "
+ "pre-revprop-change hook either failed or is non-existent"));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/get_deleted_rev.c b/contrib/subversion/subversion/libsvn_ra_serf/get_deleted_rev.c
new file mode 100644
index 0000000..40f6b1d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/get_deleted_rev.c
@@ -0,0 +1,178 @@
+/*
+ * get_deleted_rev.c : ra_serf get_deleted_rev API implementation.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include "svn_ra.h"
+#include "svn_xml.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum drev_state_e {
+ INITIAL = 0,
+ REPORT,
+ VERSION_NAME
+};
+
+typedef struct drev_context_t {
+ const char *path;
+ svn_revnum_t peg_revision;
+ svn_revnum_t end_revision;
+
+ /* What revision was PATH@PEG_REVISION first deleted within
+ the range PEG_REVISION-END-END_REVISION? */
+ svn_revnum_t *revision_deleted;
+
+} drev_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t getdrev_ttable[] = {
+ { INITIAL, S_, "get-deleted-rev-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { REPORT, D_, SVN_DAV__VERSION_NAME, VERSION_NAME,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+getdrev_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ drev_context_t *drev_ctx = baton;
+
+ SVN_ERR_ASSERT(leaving_state == VERSION_NAME);
+ SVN_ERR_ASSERT(cdata != NULL);
+
+ *drev_ctx->revision_deleted = SVN_STR_TO_REV(cdata->data);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_getdrev_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ drev_context_t *drev_ctx = baton;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc,
+ "S:get-deleted-rev-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ "xmlns:D", "DAV:",
+ NULL, NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:path", drev_ctx->path,
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:peg-revision",
+ apr_ltoa(pool, drev_ctx->peg_revision),
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:end-revision",
+ apr_ltoa(pool, drev_ctx->end_revision),
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc,
+ "S:get-deleted-rev-report");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_deleted_rev(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool)
+{
+ drev_context_t *drev_ctx;
+ svn_ra_serf__session_t *ras = session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+ svn_error_t *err;
+
+ drev_ctx = apr_pcalloc(pool, sizeof(*drev_ctx));
+ drev_ctx->path = path;
+ drev_ctx->peg_revision = peg_revision;
+ drev_ctx->end_revision = end_revision;
+ drev_ctx->revision_deleted = revision_deleted;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ ras, NULL /* conn */,
+ NULL /* url */, peg_revision,
+ pool, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(getdrev_ttable,
+ NULL, getdrev_closed, NULL,
+ drev_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->body_delegate = create_getdrev_body;
+ handler->body_delegate_baton = drev_ctx;
+ handler->conn = ras->conns[0];
+ handler->session = ras;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ /* Map status 501: Method Not Implemented to our not implemented error.
+ 1.5.x servers and older don't support this report. */
+ if (handler->sline.code == 501)
+ return svn_error_createf(SVN_ERR_RA_NOT_IMPLEMENTED, err,
+ _("'%s' REPORT not implemented"),
+ "get-deleted-rev");
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/getdate.c b/contrib/subversion/subversion/libsvn_ra_serf/getdate.c
new file mode 100644
index 0000000..867e86f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/getdate.c
@@ -0,0 +1,161 @@
+/*
+ * getdate.c : entry point for get_dated_revision for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_time.h"
+#include "svn_xml.h"
+
+#include "private/svn_dav_protocol.h"
+
+#include "svn_private_config.h"
+
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum date_state_e {
+ INITIAL = 0,
+ REPORT,
+ VERSION_NAME
+};
+
+
+typedef struct date_context_t {
+ /* The time asked about. */
+ apr_time_t time;
+
+ /* What was the youngest revision at that time? */
+ svn_revnum_t *revision;
+
+} date_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t date_ttable[] = {
+ { INITIAL, S_, "dated-rev-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { REPORT, D_, SVN_DAV__VERSION_NAME, VERSION_NAME,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+date_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ date_context_t *date_ctx = baton;
+
+ SVN_ERR_ASSERT(leaving_state == VERSION_NAME);
+ SVN_ERR_ASSERT(cdata != NULL);
+
+ *date_ctx->revision = SVN_STR_TO_REV(cdata->data);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_getdate_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ date_context_t *date_ctx = baton;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "S:dated-rev-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ "xmlns:D", "DAV:",
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "D:" SVN_DAV__CREATIONDATE,
+ svn_time_to_cstring(date_ctx->time, pool),
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "S:dated-rev-report");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_dated_revision(svn_ra_session_t *ra_session,
+ svn_revnum_t *revision,
+ apr_time_t tm,
+ apr_pool_t *pool)
+{
+ date_context_t *date_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *report_target;
+
+ date_ctx = apr_palloc(pool, sizeof(*date_ctx));
+ date_ctx->time = tm;
+ date_ctx->revision = revision;
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(date_ttable,
+ NULL, date_closed, NULL,
+ date_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = report_target;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ handler->body_delegate = create_getdate_body;
+ handler->body_delegate_baton = date_ctx;
+
+ *date_ctx->revision = SVN_INVALID_REVNUM;
+
+ /* ### use svn_ra_serf__error_on_status() ? */
+
+ return svn_error_trace(svn_ra_serf__context_run_one(handler, pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/getlocations.c b/contrib/subversion/subversion/libsvn_ra_serf/getlocations.c
new file mode 100644
index 0000000..d3e3175
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/getlocations.c
@@ -0,0 +1,201 @@
+/*
+ * getlocations.c : entry point for get_locations RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_xml.h"
+#include "svn_private_config.h"
+
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum loc_state_e {
+ INITIAL = 0,
+ REPORT,
+ LOCATION
+};
+
+typedef struct loc_context_t {
+ /* pool to allocate memory from */
+ apr_pool_t *pool;
+
+ /* parameters set by our caller */
+ const char *path;
+ const apr_array_header_t *location_revisions;
+ svn_revnum_t peg_revision;
+
+ /* Returned location hash */
+ apr_hash_t *paths;
+
+} loc_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t getloc_ttable[] = {
+ { INITIAL, S_, "get-locations-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { REPORT, S_, "location", LOCATION,
+ FALSE, { "?rev", "?path", NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+getloc_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ loc_context_t *loc_ctx = baton;
+ const char *revstr;
+ const char *path;
+
+ SVN_ERR_ASSERT(leaving_state == LOCATION);
+
+ revstr = svn_hash_gets(attrs, "rev");
+ path = svn_hash_gets(attrs, "path");
+ if (revstr != NULL && path != NULL)
+ {
+ svn_revnum_t rev = SVN_STR_TO_REV(revstr);
+ apr_hash_set(loc_ctx->paths,
+ apr_pmemdup(loc_ctx->pool, &rev, sizeof(rev)), sizeof(rev),
+ apr_pstrdup(loc_ctx->pool, path));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_get_locations_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ loc_context_t *loc_ctx = baton;
+ int i;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc,
+ "S:get-locations",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ "xmlns:D", "DAV:",
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:path", loc_ctx->path,
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:peg-revision", apr_ltoa(pool, loc_ctx->peg_revision),
+ alloc);
+
+ for (i = 0; i < loc_ctx->location_revisions->nelts; i++)
+ {
+ svn_revnum_t rev = APR_ARRAY_IDX(loc_ctx->location_revisions, i, svn_revnum_t);
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:location-revision", apr_ltoa(pool, rev),
+ alloc);
+ }
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc,
+ "S:get-locations");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_locations(svn_ra_session_t *ra_session,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool)
+{
+ loc_context_t *loc_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+ svn_error_t *err;
+
+ loc_ctx = apr_pcalloc(pool, sizeof(*loc_ctx));
+ loc_ctx->pool = pool;
+ loc_ctx->path = path;
+ loc_ctx->peg_revision = peg_revision;
+ loc_ctx->location_revisions = location_revisions;
+ loc_ctx->paths = apr_hash_make(loc_ctx->pool);
+
+ *locations = loc_ctx->paths;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, peg_revision,
+ pool, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(getloc_ttable,
+ NULL, getloc_closed, NULL,
+ loc_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_delegate = create_get_locations_body;
+ handler->body_delegate_baton = loc_ctx;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ req_url,
+ handler->location),
+ err));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/getlocationsegments.c b/contrib/subversion/subversion/libsvn_ra_serf/getlocationsegments.c
new file mode 100644
index 0000000..d2b69bf
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/getlocationsegments.c
@@ -0,0 +1,206 @@
+/*
+ * getlocationsegments.c : entry point for get_location_segments
+ * RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_xml.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_serf.h"
+
+
+
+typedef struct gls_context_t {
+ /* parameters set by our caller */
+ svn_revnum_t peg_revision;
+ svn_revnum_t start_rev;
+ svn_revnum_t end_rev;
+ const char *path;
+
+ /* location segment callback function/baton */
+ svn_location_segment_receiver_t receiver;
+ void *receiver_baton;
+
+} gls_context_t;
+
+enum {
+ INITIAL = 0,
+ REPORT,
+ SEGMENT
+};
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t gls_ttable[] = {
+ { INITIAL, S_, "get-location-segments-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { REPORT, S_, "location-segment", SEGMENT,
+ FALSE, { "?path", "range-start", "range-end", NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+gls_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ gls_context_t *gls_ctx = baton;
+ const char *path;
+ const char *start_str;
+ const char *end_str;
+ svn_location_segment_t segment;
+
+ SVN_ERR_ASSERT(leaving_state == SEGMENT);
+
+ path = svn_hash_gets(attrs, "path");
+ start_str = svn_hash_gets(attrs, "range-start");
+ end_str = svn_hash_gets(attrs, "range-end");
+
+ /* The transition table said these must exist. */
+ SVN_ERR_ASSERT(start_str && end_str);
+
+ segment.path = path; /* may be NULL */
+ segment.range_start = SVN_STR_TO_REV(start_str);
+ segment.range_end = SVN_STR_TO_REV(end_str);
+ SVN_ERR(gls_ctx->receiver(&segment, gls_ctx->receiver_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_gls_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ gls_context_t *gls_ctx = baton;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc,
+ "S:get-location-segments",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:path", gls_ctx->path,
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:peg-revision",
+ apr_ltoa(pool, gls_ctx->peg_revision),
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:start-revision",
+ apr_ltoa(pool, gls_ctx->start_rev),
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:end-revision",
+ apr_ltoa(pool, gls_ctx->end_rev),
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc,
+ "S:get-location-segments");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_location_segments(svn_ra_session_t *ra_session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ gls_context_t *gls_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+ svn_error_t *err;
+
+ gls_ctx = apr_pcalloc(pool, sizeof(*gls_ctx));
+ gls_ctx->path = path;
+ gls_ctx->peg_revision = peg_revision;
+ gls_ctx->start_rev = start_rev;
+ gls_ctx->end_rev = end_rev;
+ gls_ctx->receiver = receiver;
+ gls_ctx->receiver_baton = receiver_baton;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, peg_revision,
+ pool, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(gls_ttable,
+ NULL, gls_closed, NULL,
+ gls_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_delegate = create_gls_body;
+ handler->body_delegate_baton = gls_ctx;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ err = svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location),
+ err);
+
+ if (err && (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, NULL);
+
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/getlocks.c b/contrib/subversion/subversion/libsvn_ra_serf/getlocks.c
new file mode 100644
index 0000000..61b8b8c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/getlocks.c
@@ -0,0 +1,277 @@
+/*
+ * getlocks.c : entry point for get_locks RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_time.h"
+#include "svn_xml.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_fspath.h"
+#include "svn_private_config.h"
+
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum {
+ INITIAL = 0,
+ REPORT,
+ LOCK,
+ PATH,
+ TOKEN,
+ OWNER,
+ COMMENT,
+ CREATION_DATE,
+ EXPIRATION_DATE
+};
+
+typedef struct lock_context_t {
+ apr_pool_t *pool;
+
+ /* target and requested depth of the operation. */
+ const char *path;
+ svn_depth_t requested_depth;
+
+ /* return hash */
+ apr_hash_t *hash;
+
+} lock_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t getlocks_ttable[] = {
+ { INITIAL, S_, "get-locks-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { REPORT, S_, "lock", LOCK,
+ FALSE, { NULL }, TRUE },
+
+ { LOCK, S_, "path", PATH,
+ TRUE, { NULL }, TRUE },
+
+ { LOCK, S_, "token", TOKEN,
+ TRUE, { NULL }, TRUE },
+
+ { LOCK, S_, "owner", OWNER,
+ TRUE, { NULL }, TRUE },
+
+ { LOCK, S_, "comment", COMMENT,
+ TRUE, { NULL }, TRUE },
+
+ { LOCK, S_, SVN_DAV__CREATIONDATE, CREATION_DATE,
+ TRUE, { NULL }, TRUE },
+
+ { LOCK, S_, "expirationdate", EXPIRATION_DATE,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+getlocks_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ lock_context_t *lock_ctx = baton;
+
+ if (leaving_state == LOCK)
+ {
+ const char *path = svn_hash_gets(attrs, "path");
+ svn_boolean_t save_lock = FALSE;
+
+ /* Filter out unwanted paths. Since Subversion only allows
+ locks on files, we can treat depth=immediates the same as
+ depth=files for filtering purposes. Meaning, we'll keep
+ this lock if:
+
+ a) its path is the very path we queried, or
+ b) we've asked for a fully recursive answer, or
+ c) we've asked for depth=files or depth=immediates, and this
+ lock is on an immediate child of our query path.
+ */
+ if (strcmp(lock_ctx->path, path) == 0
+ || lock_ctx->requested_depth == svn_depth_infinity)
+ {
+ save_lock = TRUE;
+ }
+ else if (lock_ctx->requested_depth == svn_depth_files
+ || lock_ctx->requested_depth == svn_depth_immediates)
+ {
+ const char *relpath = svn_fspath__skip_ancestor(lock_ctx->path,
+ path);
+ if (relpath && (svn_path_component_count(relpath) == 1))
+ save_lock = TRUE;
+ }
+
+ if (save_lock)
+ {
+ /* We get to put the structure on the stack rather than using
+ svn_lock_create(). Bwahahaha.... */
+ svn_lock_t lock = { 0 };
+ const char *date;
+ svn_lock_t *result_lock;
+
+ /* Note: these "attributes" came from child elements. Some of
+ them may have not been sent, so the value will be NULL. */
+
+ lock.path = path;
+ lock.token = svn_hash_gets(attrs, "token");
+ lock.owner = svn_hash_gets(attrs, "owner");
+ lock.comment = svn_hash_gets(attrs, "comment");
+
+ date = svn_hash_gets(attrs, SVN_DAV__CREATIONDATE);
+ if (date)
+ SVN_ERR(svn_time_from_cstring(&lock.creation_date, date,
+ scratch_pool));
+
+ date = svn_hash_gets(attrs, "expirationdate");
+ if (date)
+ SVN_ERR(svn_time_from_cstring(&lock.expiration_date, date,
+ scratch_pool));
+
+ result_lock = svn_lock_dup(&lock, lock_ctx->pool);
+ svn_hash_sets(lock_ctx->hash, result_lock->path, result_lock);
+ }
+ }
+ else
+ {
+ const char *name;
+
+ SVN_ERR_ASSERT(cdata != NULL);
+
+ if (leaving_state == PATH)
+ name = "path";
+ else if (leaving_state == TOKEN)
+ name = "token";
+ else if (leaving_state == OWNER)
+ name = "owner";
+ else if (leaving_state == COMMENT)
+ name = "comment";
+ else if (leaving_state == CREATION_DATE)
+ name = SVN_DAV__CREATIONDATE;
+ else if (leaving_state == EXPIRATION_DATE)
+ name = "expirationdate";
+ else
+ SVN_ERR_MALFUNCTION();
+
+ /* Store the lock information onto the LOCK elemstate. */
+ svn_ra_serf__xml_note(xes, LOCK, name, cdata->data);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_getlocks_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ lock_context_t *lock_ctx = baton;
+ serf_bucket_t *buckets;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(
+ buckets, alloc, "S:get-locks-report", "xmlns:S", SVN_XML_NAMESPACE,
+ "depth", svn_depth_to_word(lock_ctx->requested_depth), NULL);
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "S:get-locks-report");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_locks(svn_ra_session_t *ra_session,
+ apr_hash_t **locks,
+ const char *path,
+ svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ lock_context_t *lock_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url, *rel_path;
+
+ req_url = svn_path_url_add_component2(session->session_url.path, path, pool);
+ SVN_ERR(svn_ra_serf__get_relative_path(&rel_path, req_url, session,
+ NULL, pool));
+
+ lock_ctx = apr_pcalloc(pool, sizeof(*lock_ctx));
+ lock_ctx->pool = pool;
+ lock_ctx->path = apr_pstrcat(pool, "/", rel_path, (char *)NULL);
+ lock_ctx->requested_depth = depth;
+ lock_ctx->hash = apr_hash_make(pool);
+
+ xmlctx = svn_ra_serf__xml_context_create(getlocks_ttable,
+ NULL, getlocks_closed, NULL,
+ lock_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ handler->body_delegate = create_getlocks_body;
+ handler->body_delegate_baton = lock_ctx;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+ /* We get a 404 when a path doesn't exist in HEAD, but it might
+ have existed earlier (E.g. 'svn ls http://s/svn/trunk/file@1' */
+ if (handler->sline.code != 404)
+ {
+ SVN_ERR(svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location));
+ }
+
+ *locks = lock_ctx->hash;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/inherited_props.c b/contrib/subversion/subversion/libsvn_ra_serf/inherited_props.c
new file mode 100644
index 0000000..9283a5a
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/inherited_props.c
@@ -0,0 +1,344 @@
+/*
+ * inherited_props.c : ra_serf implementation of svn_ra_get_inherited_props
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_tables.h>
+#include <apr_xml.h>
+
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_ra.h"
+#include "svn_string.h"
+#include "svn_xml.h"
+#include "svn_props.h"
+#include "svn_base64.h"
+
+#include "private/svn_dav_protocol.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_private_config.h"
+#include "ra_serf.h"
+
+
+/* The current state of our XML parsing. */
+typedef enum iprops_state_e {
+ NONE = 0,
+ IPROPS_REPORT,
+ IPROPS_ITEM,
+ IPROPS_PATH,
+ IPROPS_PROPNAME,
+ IPROPS_PROPVAL
+} iprops_state_e;
+
+/* Struct for accumulating inherited props. */
+typedef struct iprops_context_t {
+ /* The depth-first ordered array of svn_prop_inherited_item_t *
+ structures we are building. */
+ apr_array_header_t *iprops;
+
+ /* Pool in which to allocate elements of IPROPS. */
+ apr_pool_t *pool;
+
+ /* The repository's root URL. */
+ const char *repos_root_url;
+
+ /* Current CDATA values*/
+ svn_stringbuf_t *curr_path;
+ svn_stringbuf_t *curr_propname;
+ svn_stringbuf_t *curr_propval;
+ const char *curr_prop_val_encoding;
+
+ /* Current element in IPROPS. */
+ svn_prop_inherited_item_t *curr_iprop;
+
+ /* Serf context completion flag for svn_ra_serf__context_run_wait() */
+ svn_boolean_t done;
+
+ /* Path we are finding inherited properties for. This is relative to
+ the RA session passed to svn_ra_serf__get_inherited_props. */
+ const char *path;
+ /* The revision of PATH*/
+ svn_revnum_t revision;
+} iprops_context_t;
+
+static svn_error_t *
+start_element(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ iprops_context_t *iprops_ctx = parser->user_data;
+ iprops_state_e state;
+
+ state = parser->state->current_state;
+ if (state == NONE
+ && strcmp(name.name, SVN_DAV__INHERITED_PROPS_REPORT) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_REPORT);
+ }
+ else if (state == IPROPS_REPORT &&
+ strcmp(name.name, SVN_DAV__IPROP_ITEM) == 0)
+ {
+ svn_stringbuf_setempty(iprops_ctx->curr_path);
+ svn_stringbuf_setempty(iprops_ctx->curr_propname);
+ svn_stringbuf_setempty(iprops_ctx->curr_propval);
+ iprops_ctx->curr_prop_val_encoding = NULL;
+ iprops_ctx->curr_iprop = NULL;
+ svn_ra_serf__xml_push_state(parser, IPROPS_ITEM);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PROPVAL) == 0)
+ {
+ const char *prop_val_encoding = svn_xml_get_attr_value("encoding",
+ attrs);
+ iprops_ctx->curr_prop_val_encoding = apr_pstrdup(iprops_ctx->pool,
+ prop_val_encoding);
+ svn_ra_serf__xml_push_state(parser, IPROPS_PROPVAL);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PATH) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_PATH);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PROPNAME) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_PROPNAME);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PROPVAL) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_PROPVAL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_element(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ iprops_context_t *iprops_ctx = parser->user_data;
+ iprops_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == IPROPS_REPORT &&
+ strcmp(name.name, SVN_DAV__INHERITED_PROPS_REPORT) == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_PATH
+ && strcmp(name.name, SVN_DAV__IPROP_PATH) == 0)
+ {
+ iprops_ctx->curr_iprop = apr_palloc(
+ iprops_ctx->pool, sizeof(svn_prop_inherited_item_t));
+
+ iprops_ctx->curr_iprop->path_or_url =
+ svn_path_url_add_component2(iprops_ctx->repos_root_url,
+ iprops_ctx->curr_path->data,
+ iprops_ctx->pool);
+ iprops_ctx->curr_iprop->prop_hash = apr_hash_make(iprops_ctx->pool);
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_PROPVAL
+ && strcmp(name.name, SVN_DAV__IPROP_PROPVAL) == 0)
+ {
+ const svn_string_t *prop_val;
+
+ if (iprops_ctx->curr_prop_val_encoding)
+ {
+ svn_string_t encoded_prop_val;
+
+ if (strcmp(iprops_ctx->curr_prop_val_encoding, "base64") != 0)
+ return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
+
+ encoded_prop_val.data = iprops_ctx->curr_propval->data;
+ encoded_prop_val.len = iprops_ctx->curr_propval->len;
+ prop_val = svn_base64_decode_string(&encoded_prop_val,
+ iprops_ctx->pool);
+ }
+ else
+ {
+ prop_val = svn_string_create_from_buf(iprops_ctx->curr_propval,
+ iprops_ctx->pool);
+ }
+
+ svn_hash_sets(iprops_ctx->curr_iprop->prop_hash,
+ apr_pstrdup(iprops_ctx->pool,
+ iprops_ctx->curr_propname->data),
+ prop_val);
+ /* Clear current propname and propval in the event there are
+ multiple properties on the current path. */
+ svn_stringbuf_setempty(iprops_ctx->curr_propname);
+ svn_stringbuf_setempty(iprops_ctx->curr_propval);
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_PROPNAME
+ && strcmp(name.name, SVN_DAV__IPROP_PROPNAME) == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_ITEM
+ && strcmp(name.name, SVN_DAV__IPROP_ITEM) == 0)
+ {
+ APR_ARRAY_PUSH(iprops_ctx->iprops, svn_prop_inherited_item_t *) =
+ iprops_ctx->curr_iprop;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+cdata_handler(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ iprops_context_t *iprops_ctx = parser->user_data;
+ iprops_state_e state = parser->state->current_state;
+
+ switch (state)
+ {
+ case IPROPS_PATH:
+ svn_stringbuf_appendbytes(iprops_ctx->curr_path, data, len);
+ break;
+
+ case IPROPS_PROPNAME:
+ svn_stringbuf_appendbytes(iprops_ctx->curr_propname, data, len);
+ break;
+
+ case IPROPS_PROPVAL:
+ svn_stringbuf_appendbytes(iprops_ctx->curr_propval, data, len);
+ break;
+
+ default:
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+create_iprops_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ iprops_context_t *iprops_ctx = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__INHERITED_PROPS_REPORT,
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:" SVN_DAV__REVISION,
+ apr_ltoa(pool, iprops_ctx->revision),
+ alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "S:" SVN_DAV__PATH,
+ iprops_ctx->path, alloc);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__INHERITED_PROPS_REPORT);
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+/* Request a inherited-props-report from the URL attached to RA_SESSION,
+ and fill the IPROPS array hash with the results. */
+svn_error_t *
+svn_ra_serf__get_inherited_props(svn_ra_session_t *ra_session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ iprops_context_t *iprops_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+ const char *req_url;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url,
+ NULL /* latest_revnum */,
+ session,
+ NULL /* conn */,
+ NULL /* url */,
+ revision,
+ result_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(session->repos_root_str);
+
+ iprops_ctx = apr_pcalloc(scratch_pool, sizeof(*iprops_ctx));
+ iprops_ctx->done = FALSE;
+ iprops_ctx->repos_root_url = session->repos_root_str;
+ iprops_ctx->pool = result_pool;
+ iprops_ctx->curr_path = svn_stringbuf_create_empty(scratch_pool);
+ iprops_ctx->curr_propname = svn_stringbuf_create_empty(scratch_pool);
+ iprops_ctx->curr_propval = svn_stringbuf_create_empty(scratch_pool);
+ iprops_ctx->curr_iprop = NULL;
+ iprops_ctx->iprops = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+ iprops_ctx->path = path;
+ iprops_ctx->revision = revision;
+
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->conn = session->conns[0];
+ handler->session = session;
+ handler->body_delegate = create_iprops_body;
+ handler->body_delegate_baton = iprops_ctx;
+ handler->body_type = "text/xml";
+ handler->handler_pool = scratch_pool;
+
+ parser_ctx = apr_pcalloc(scratch_pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = scratch_pool;
+ parser_ctx->user_data = iprops_ctx;
+ parser_ctx->start = start_element;
+ parser_ctx->end = end_element;
+ parser_ctx->cdata = cdata_handler;
+ parser_ctx->done = &iprops_ctx->done;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ err = svn_ra_serf__context_run_one(handler, scratch_pool);
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location),
+ err));
+
+ if (iprops_ctx->done)
+ *iprops = iprops_ctx->iprops;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/locks.c b/contrib/subversion/subversion/libsvn_ra_serf/locks.c
new file mode 100644
index 0000000..db2d371
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/locks.c
@@ -0,0 +1,654 @@
+/*
+ * locks.c : entry point for locking RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_dav.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_config.h"
+#include "svn_path.h"
+#include "svn_time.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum {
+ INITIAL = 0,
+ MULTISTATUS,
+ RESPONSE,
+ PROPSTAT,
+ PROP,
+ LOCK_DISCOVERY,
+ ACTIVE_LOCK,
+ LOCK_TYPE,
+ LOCK_SCOPE,
+ DEPTH,
+ TIMEOUT,
+ LOCK_TOKEN,
+ OWNER,
+ HREF
+};
+
+typedef struct lock_info_t {
+ apr_pool_t *pool;
+
+ const char *path;
+
+ svn_lock_t *lock;
+
+ svn_boolean_t force;
+ svn_revnum_t revision;
+
+ svn_boolean_t read_headers;
+
+ svn_ra_serf__handler_t *handler;
+
+ /* The expat handler. We wrap this to do a bit more work. */
+ svn_ra_serf__response_handler_t inner_handler;
+ void *inner_baton;
+
+} lock_info_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t locks_ttable[] = {
+ /* The INITIAL state can transition into D:prop (LOCK) or
+ to D:multistatus (PROPFIND) */
+ { INITIAL, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+ { INITIAL, D_, "multistatus", MULTISTATUS,
+ FALSE, { NULL }, FALSE },
+
+ { MULTISTATUS, D_, "response", RESPONSE,
+ FALSE, { NULL }, FALSE },
+
+ { RESPONSE, D_, "propstat", PROPSTAT,
+ FALSE, { NULL }, FALSE },
+
+ { PROPSTAT, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+
+ { PROP, D_, "lockdiscovery", LOCK_DISCOVERY,
+ FALSE, { NULL }, FALSE },
+
+ { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK,
+ FALSE, { NULL }, FALSE },
+
+#if 0
+ /* ### we don't really need to parse locktype/lockscope. we know what
+ ### the values are going to be. we *could* validate that the only
+ ### possible children are D:write and D:exclusive. we'd need to
+ ### modify the state transition to tell us about all children
+ ### (ie. maybe support "*" for the name) and then validate. but it
+ ### just isn't important to validate, so disable this for now... */
+
+ { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE,
+ FALSE, { NULL }, FALSE },
+
+ { LOCK_TYPE, D_, "write", WRITE,
+ FALSE, { NULL }, TRUE },
+
+ { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE,
+ FALSE, { NULL }, FALSE },
+
+ { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE,
+ FALSE, { NULL }, TRUE },
+#endif /* 0 */
+
+ { ACTIVE_LOCK, D_, "timeout", TIMEOUT,
+ TRUE, { NULL }, TRUE },
+
+ { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN,
+ FALSE, { NULL }, FALSE },
+
+ { LOCK_TOKEN, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
+
+ { ACTIVE_LOCK, D_, "owner", OWNER,
+ TRUE, { NULL }, TRUE },
+
+ /* ACTIVE_LOCK has a D:depth child, but we can ignore that. */
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+locks_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ lock_info_t *lock_ctx = baton;
+
+ if (leaving_state == TIMEOUT)
+ {
+ if (strcmp(cdata->data, "Infinite") == 0)
+ lock_ctx->lock->expiration_date = 0;
+ else
+ SVN_ERR(svn_time_from_cstring(&lock_ctx->lock->creation_date,
+ cdata->data, lock_ctx->pool));
+ }
+ else if (leaving_state == HREF)
+ {
+ if (cdata->len)
+ {
+ char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len);
+
+ apr_collapse_spaces(buf, buf);
+ lock_ctx->lock->token = buf;
+ }
+ }
+ else if (leaving_state == OWNER)
+ {
+ if (cdata->len)
+ {
+ lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool,
+ cdata->data, cdata->len);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+set_lock_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ lock_info_t *lock_ctx = baton;
+
+ if (lock_ctx->force)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
+ SVN_DAV_OPTION_LOCK_STEAL);
+ }
+
+ if (SVN_IS_VALID_REVNUM(lock_ctx->revision))
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
+ apr_ltoa(pool, lock_ctx->revision));
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Register an error within the session. If something is already there,
+ then it will take precedence. */
+static svn_error_t *
+determine_error(svn_ra_serf__handler_t *handler,
+ svn_error_t *err)
+{
+ {
+ apr_status_t errcode;
+
+ if (handler->sline.code == 423)
+ errcode = SVN_ERR_FS_PATH_ALREADY_LOCKED;
+ else if (handler->sline.code == 403)
+ errcode = SVN_ERR_RA_DAV_FORBIDDEN;
+ else
+ return err;
+
+ /* Client-side or server-side error already. Return it. */
+ if (err != NULL)
+ return err;
+
+ /* The server did not send us a detailed human-readable error.
+ Provide a generic error. */
+ err = svn_error_createf(errcode, NULL,
+ _("Lock request failed: %d %s"),
+ handler->sline.code,
+ handler->sline.reason);
+ }
+
+ return err;
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+handle_lock(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ lock_info_t *ctx = handler_baton;
+
+ /* 403 (Forbidden) when a lock doesn't exist.
+ 423 (Locked) when a lock already exists. */
+ if (ctx->handler->sline.code == 403
+ || ctx->handler->sline.code == 423)
+ {
+ /* Go look in the body for a server-provided error. This will
+ reset flags for the core handler to Do The Right Thing. We
+ won't be back to this handler again. */
+ return svn_error_trace(svn_ra_serf__expect_empty_body(
+ request, response, ctx->handler, pool));
+ }
+
+ if (!ctx->read_headers)
+ {
+ serf_bucket_t *headers;
+ const char *val;
+
+ headers = serf_bucket_response_get_headers(response);
+
+ val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER);
+ if (val)
+ {
+ ctx->lock->owner = apr_pstrdup(ctx->pool, val);
+ }
+
+ val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER);
+ if (val)
+ {
+ SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val,
+ ctx->pool));
+ }
+
+ ctx->read_headers = TRUE;
+ }
+
+ return ctx->inner_handler(request, response, ctx->inner_baton, pool);
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_getlock_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(buckets, alloc);
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "propfind",
+ "xmlns", "DAV:",
+ NULL);
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "prop", NULL);
+ svn_ra_serf__add_tag_buckets(buckets, "lockdiscovery", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "prop");
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "propfind");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t*
+setup_getlock_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_headers_setn(headers, "Depth", "0");
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_lock_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ lock_info_t *ctx = baton;
+ serf_bucket_t *buckets;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(buckets, alloc);
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo",
+ "xmlns", "DAV:",
+ NULL);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockscope", NULL);
+ svn_ra_serf__add_tag_buckets(buckets, "exclusive", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockscope");
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "locktype", NULL);
+ svn_ra_serf__add_tag_buckets(buckets, "write", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "locktype");
+
+ if (ctx->lock->comment)
+ {
+ svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment,
+ alloc);
+ }
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_lock(svn_ra_session_t *ra_session,
+ svn_lock_t **lock,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ lock_info_t *lock_ctx;
+ const char *req_url;
+ svn_error_t *err;
+
+ req_url = svn_path_url_add_component2(session->session_url.path, path, pool);
+
+ lock_ctx = apr_pcalloc(pool, sizeof(*lock_ctx));
+
+ lock_ctx->pool = pool;
+ lock_ctx->path = req_url;
+ lock_ctx->lock = svn_lock_create(pool);
+ lock_ctx->lock->path = apr_pstrdup(pool, path); /* be sure */
+
+ xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
+ NULL, locks_closed, NULL,
+ lock_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "PROPFIND";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ handler->body_delegate = create_getlock_body;
+ handler->body_delegate_baton = lock_ctx;
+
+ handler->header_delegate = setup_getlock_headers;
+ handler->header_delegate_baton = lock_ctx;
+
+ lock_ctx->inner_handler = handler->response_handler;
+ lock_ctx->inner_baton = handler->response_baton;
+ handler->response_handler = handle_lock;
+ handler->response_baton = lock_ctx;
+
+ lock_ctx->handler = handler;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+ err = determine_error(handler, err);
+
+ if (handler->sline.code == 404)
+ {
+ return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, err,
+ _("Malformed URL for repository"));
+ }
+ if (err)
+ {
+ /* TODO Shh. We're telling a white lie for now. */
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
+ _("Server does not support locking features"));
+ }
+
+ *lock = lock_ctx->lock;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__lock(svn_ra_session_t *ra_session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* ### TODO for issue 2263: Send all the locks over the wire at once. This
+ ### loop is just a temporary shim.
+ ### an alternative, which is backwards-compat with all servers is to
+ ### pipeline these requests. ie. stop using run_wait/run_one. */
+
+ for (hi = apr_hash_first(scratch_pool, path_revs);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+ lock_info_t *lock_ctx;
+ svn_error_t *err;
+ svn_error_t *new_err = NULL;
+
+ svn_pool_clear(iterpool);
+
+ lock_ctx = apr_pcalloc(iterpool, sizeof(*lock_ctx));
+
+ lock_ctx->pool = iterpool;
+ lock_ctx->path = svn__apr_hash_index_key(hi);
+ lock_ctx->revision = *((svn_revnum_t*)svn__apr_hash_index_val(hi));
+ lock_ctx->lock = svn_lock_create(iterpool);
+ lock_ctx->lock->path = lock_ctx->path;
+ lock_ctx->lock->comment = comment;
+
+ lock_ctx->force = force;
+ req_url = svn_path_url_add_component2(session->session_url.path,
+ lock_ctx->path, iterpool);
+
+ xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
+ NULL, locks_closed, NULL,
+ lock_ctx,
+ iterpool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, iterpool);
+
+ handler->method = "LOCK";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ handler->header_delegate = set_lock_headers;
+ handler->header_delegate_baton = lock_ctx;
+
+ handler->body_delegate = create_lock_body;
+ handler->body_delegate_baton = lock_ctx;
+
+ lock_ctx->inner_handler = handler->response_handler;
+ lock_ctx->inner_baton = handler->response_baton;
+ handler->response_handler = handle_lock;
+ handler->response_baton = lock_ctx;
+
+ lock_ctx->handler = handler;
+
+ err = svn_ra_serf__context_run_one(handler, iterpool);
+ err = determine_error(handler, err);
+
+ if (lock_func)
+ new_err = lock_func(lock_baton, lock_ctx->path, TRUE, lock_ctx->lock,
+ err, iterpool);
+ svn_error_clear(err);
+
+ SVN_ERR(new_err);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+struct unlock_context_t {
+ const char *token;
+ svn_boolean_t force;
+};
+
+static svn_error_t *
+set_unlock_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct unlock_context_t *ctx = baton;
+
+ serf_bucket_headers_set(headers, "Lock-Token", ctx->token);
+ if (ctx->force)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
+ SVN_DAV_OPTION_LOCK_BREAK);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__unlock(svn_ra_session_t *ra_session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* ### TODO for issue 2263: Send all the locks over the wire at once. This
+ ### loop is just a temporary shim.
+ ### an alternative, which is backwards-compat with all servers is to
+ ### pipeline these requests. ie. stop using run_wait/run_one. */
+
+ for (hi = apr_hash_first(scratch_pool, path_tokens);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_ra_serf__handler_t *handler;
+ const char *req_url, *path, *token;
+ svn_lock_t *existing_lock = NULL;
+ struct unlock_context_t unlock_ctx;
+ svn_error_t *err = NULL;
+ svn_error_t *new_err = NULL;
+
+
+ svn_pool_clear(iterpool);
+
+ path = svn__apr_hash_index_key(hi);
+ token = svn__apr_hash_index_val(hi);
+
+ if (force && (!token || token[0] == '\0'))
+ {
+ SVN_ERR(svn_ra_serf__get_lock(ra_session, &existing_lock, path,
+ iterpool));
+ token = existing_lock->token;
+ if (!token)
+ {
+ err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, NULL,
+ _("'%s' is not locked in the repository"),
+ path);
+
+ if (lock_func)
+ {
+ svn_error_t *err2;
+ err2 = lock_func(lock_baton, path, FALSE, NULL, err,
+ iterpool);
+ svn_error_clear(err);
+ err = NULL;
+ if (err2)
+ return svn_error_trace(err2);
+ }
+ else
+ {
+ svn_error_clear(err);
+ err = NULL;
+ }
+ continue;
+ }
+ }
+
+ unlock_ctx.force = force;
+ unlock_ctx.token = apr_pstrcat(iterpool, "<", token, ">", (char *)NULL);
+
+ req_url = svn_path_url_add_component2(session->session_url.path, path,
+ iterpool);
+
+ handler = apr_pcalloc(iterpool, sizeof(*handler));
+
+ handler->handler_pool = iterpool;
+ handler->method = "UNLOCK";
+ handler->path = req_url;
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ handler->header_delegate = set_unlock_headers;
+ handler->header_delegate_baton = &unlock_ctx;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, iterpool));
+
+ switch (handler->sline.code)
+ {
+ case 204:
+ break; /* OK */
+ case 403:
+ /* Api users expect this specific error code to detect failures */
+ err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
+ _("Unlock request failed: %d %s"),
+ handler->sline.code,
+ handler->sline.reason);
+ break;
+ default:
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Unlock request failed: %d %s"),
+ handler->sline.code,
+ handler->sline.reason);
+ }
+
+ if (lock_func)
+ new_err = lock_func(lock_baton, path, FALSE, existing_lock, err,
+ iterpool);
+
+ svn_error_clear(err);
+ SVN_ERR(new_err);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/log.c b/contrib/subversion/subversion/libsvn_ra_serf/log.c
new file mode 100644
index 0000000..e44753b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/log.c
@@ -0,0 +1,604 @@
+/*
+ * log.c : entry point for log RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_base64.h"
+#include "svn_xml.h"
+#include "svn_config.h"
+#include "svn_path.h"
+#include "svn_props.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum {
+ INITIAL = 0,
+ REPORT,
+ ITEM,
+ VERSION,
+ CREATOR,
+ DATE,
+ COMMENT,
+ REVPROP,
+ HAS_CHILDREN,
+ ADDED_PATH,
+ REPLACED_PATH,
+ DELETED_PATH,
+ MODIFIED_PATH,
+ SUBTRACTIVE_MERGE
+};
+
+typedef struct log_context_t {
+ apr_pool_t *pool;
+
+ /* parameters set by our caller */
+ const apr_array_header_t *paths;
+ svn_revnum_t start;
+ svn_revnum_t end;
+ int limit;
+ svn_boolean_t changed_paths;
+ svn_boolean_t strict_node_history;
+ svn_boolean_t include_merged_revisions;
+ const apr_array_header_t *revprops;
+ int nest_level; /* used to track mergeinfo nesting levels */
+ int count; /* only incremented when nest_level == 0 */
+
+ /* Collect information for storage into a log entry. Most of the entry
+ members are collected by individual states. revprops and paths are
+ N datapoints per entry. */
+ apr_hash_t *collect_revprops;
+ apr_hash_t *collect_paths;
+
+ /* log receiver function and baton */
+ svn_log_entry_receiver_t receiver;
+ void *receiver_baton;
+
+ /* pre-1.5 compatibility */
+ svn_boolean_t want_author;
+ svn_boolean_t want_date;
+ svn_boolean_t want_message;
+} log_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t log_ttable[] = {
+ { INITIAL, S_, "log-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ /* Note that we have an opener here. We need to construct a new LOG_ENTRY
+ to record multiple paths. */
+ { REPORT, S_, "log-item", ITEM,
+ FALSE, { NULL }, TRUE },
+
+ { ITEM, D_, SVN_DAV__VERSION_NAME, VERSION,
+ TRUE, { NULL }, TRUE },
+
+ { ITEM, D_, "creator-displayname", CREATOR,
+ TRUE, { "?encoding", NULL }, TRUE },
+
+ { ITEM, S_, "date", DATE,
+ TRUE, { "?encoding", NULL }, TRUE },
+
+ { ITEM, D_, "comment", COMMENT,
+ TRUE, { "?encoding", NULL }, TRUE },
+
+ { ITEM, S_, "revprop", REVPROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
+
+ { ITEM, S_, "has-children", HAS_CHILDREN,
+ FALSE, { NULL }, TRUE },
+
+ { ITEM, S_, "subtractive-merge", SUBTRACTIVE_MERGE,
+ FALSE, { NULL }, TRUE },
+
+ { ITEM, S_, "added-path", ADDED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods",
+ "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { ITEM, S_, "replaced-path", REPLACED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods",
+ "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { ITEM, S_, "deleted-path", DELETED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
+
+ { ITEM, S_, "modified-path", MODIFIED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Store CDATA into REVPROPS, associated with PROPNAME. If ENCODING is not
+ NULL, then it must base "base64" and CDATA will be decoded first.
+
+ NOTE: PROPNAME must live longer than REVPROPS. */
+static svn_error_t *
+collect_revprop(apr_hash_t *revprops,
+ const char *propname,
+ const svn_string_t *cdata,
+ const char *encoding)
+{
+ apr_pool_t *result_pool = apr_hash_pool_get(revprops);
+ const svn_string_t *decoded;
+
+ if (encoding)
+ {
+ /* Check for a known encoding type. This is easy -- there's
+ only one. */
+ if (strcmp(encoding, "base64") != 0)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unsupported encoding '%s'"),
+ encoding);
+ }
+
+ decoded = svn_base64_decode_string(cdata, result_pool);
+ }
+ else
+ {
+ decoded = svn_string_dup(cdata, result_pool);
+ }
+
+ /* Caller has ensured PROPNAME has sufficient lifetime. */
+ svn_hash_sets(revprops, propname, decoded);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Record ACTION on the path in CDATA into PATHS. Other properties about
+ the action are pulled from ATTRS. */
+static svn_error_t *
+collect_path(apr_hash_t *paths,
+ char action,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs)
+{
+ apr_pool_t *result_pool = apr_hash_pool_get(paths);
+ svn_log_changed_path2_t *lcp;
+ const char *copyfrom_path;
+ const char *copyfrom_rev;
+ const char *path;
+
+ lcp = svn_log_changed_path2_create(result_pool);
+ lcp->action = action;
+ lcp->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* COPYFROM_* are only recorded for ADDED_PATH and REPLACED_PATH. */
+ copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
+ copyfrom_rev = svn_hash_gets(attrs, "copyfrom-rev");
+ if (copyfrom_path && copyfrom_rev)
+ {
+ svn_revnum_t rev = SVN_STR_TO_REV(copyfrom_rev);
+
+ if (SVN_IS_VALID_REVNUM(rev))
+ {
+ lcp->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
+ lcp->copyfrom_rev = rev;
+ }
+ }
+
+ lcp->node_kind = svn_node_kind_from_word(svn_hash_gets(attrs, "node-kind"));
+ lcp->text_modified = svn_tristate__from_word(svn_hash_gets(attrs,
+ "text-mods"));
+ lcp->props_modified = svn_tristate__from_word(svn_hash_gets(attrs,
+ "prop-mods"));
+
+ path = apr_pstrmemdup(result_pool, cdata->data, cdata->len);
+ svn_hash_sets(paths, path, lcp);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+log_opened(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int entered_state,
+ const svn_ra_serf__dav_props_t *tag,
+ apr_pool_t *scratch_pool)
+{
+ log_context_t *log_ctx = baton;
+
+ if (entered_state == ITEM)
+ {
+ apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
+
+ log_ctx->collect_revprops = apr_hash_make(state_pool);
+ log_ctx->collect_paths = apr_hash_make(state_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+log_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ log_context_t *log_ctx = baton;
+
+ if (leaving_state == ITEM)
+ {
+ svn_log_entry_t *log_entry;
+ const char *rev_str;
+
+ if (log_ctx->limit && (log_ctx->nest_level == 0)
+ && (++log_ctx->count > log_ctx->limit))
+ {
+ return SVN_NO_ERROR;
+ }
+
+ log_entry = svn_log_entry_create(scratch_pool);
+
+ /* Pick up the paths from the context. These have the same lifetime
+ as this state. That is long enough for us to pass the paths to
+ the receiver callback. */
+ if (apr_hash_count(log_ctx->collect_paths) > 0)
+ {
+ log_entry->changed_paths = log_ctx->collect_paths;
+ log_entry->changed_paths2 = log_ctx->collect_paths;
+ }
+
+ /* ... and same story for the collected revprops. */
+ log_entry->revprops = log_ctx->collect_revprops;
+
+ log_entry->has_children = svn_hash__get_bool(attrs,
+ "has-children",
+ FALSE);
+ log_entry->subtractive_merge = svn_hash__get_bool(attrs,
+ "subtractive-merge",
+ FALSE);
+
+ rev_str = svn_hash_gets(attrs, "revision");
+ if (rev_str)
+ log_entry->revision = SVN_STR_TO_REV(rev_str);
+ else
+ log_entry->revision = SVN_INVALID_REVNUM;
+
+ /* Give the info to the reporter */
+ SVN_ERR(log_ctx->receiver(log_ctx->receiver_baton,
+ log_entry,
+ scratch_pool));
+
+ if (log_entry->has_children)
+ {
+ log_ctx->nest_level++;
+ }
+ if (! SVN_IS_VALID_REVNUM(log_entry->revision))
+ {
+ SVN_ERR_ASSERT(log_ctx->nest_level);
+ log_ctx->nest_level--;
+ }
+
+ /* These hash tables are going to be unusable once this state's
+ pool is destroyed. But let's not leave stale pointers in
+ structures that have a longer life. */
+ log_ctx->collect_revprops = NULL;
+ log_ctx->collect_paths = NULL;
+ }
+ else if (leaving_state == VERSION)
+ {
+ svn_ra_serf__xml_note(xes, ITEM, "revision", cdata->data);
+ }
+ else if (leaving_state == CREATOR)
+ {
+ if (log_ctx->want_author)
+ {
+ SVN_ERR(collect_revprop(log_ctx->collect_revprops,
+ SVN_PROP_REVISION_AUTHOR,
+ cdata,
+ svn_hash_gets(attrs, "encoding")));
+ }
+ }
+ else if (leaving_state == DATE)
+ {
+ if (log_ctx->want_date)
+ {
+ SVN_ERR(collect_revprop(log_ctx->collect_revprops,
+ SVN_PROP_REVISION_DATE,
+ cdata,
+ svn_hash_gets(attrs, "encoding")));
+ }
+ }
+ else if (leaving_state == COMMENT)
+ {
+ if (log_ctx->want_message)
+ {
+ SVN_ERR(collect_revprop(log_ctx->collect_revprops,
+ SVN_PROP_REVISION_LOG,
+ cdata,
+ svn_hash_gets(attrs, "encoding")));
+ }
+ }
+ else if (leaving_state == REVPROP)
+ {
+ apr_pool_t *result_pool = apr_hash_pool_get(log_ctx->collect_revprops);
+
+ SVN_ERR(collect_revprop(
+ log_ctx->collect_revprops,
+ apr_pstrdup(result_pool,
+ svn_hash_gets(attrs, "name")),
+ cdata,
+ svn_hash_gets(attrs, "encoding")
+ ));
+ }
+ else if (leaving_state == HAS_CHILDREN)
+ {
+ svn_ra_serf__xml_note(xes, ITEM, "has-children", "yes");
+ }
+ else if (leaving_state == SUBTRACTIVE_MERGE)
+ {
+ svn_ra_serf__xml_note(xes, ITEM, "subtractive-merge", "yes");
+ }
+ else
+ {
+ char action;
+
+ if (leaving_state == ADDED_PATH)
+ action = 'A';
+ else if (leaving_state == REPLACED_PATH)
+ action = 'R';
+ else if (leaving_state == DELETED_PATH)
+ action = 'D';
+ else
+ {
+ SVN_ERR_ASSERT(leaving_state == MODIFIED_PATH);
+ action = 'M';
+ }
+
+ SVN_ERR(collect_path(log_ctx->collect_paths, action, cdata, attrs));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+create_log_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ log_context_t *log_ctx = baton;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc,
+ "S:log-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:start-revision",
+ apr_ltoa(pool, log_ctx->start),
+ alloc);
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:end-revision",
+ apr_ltoa(pool, log_ctx->end),
+ alloc);
+
+ if (log_ctx->limit)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:limit", apr_ltoa(pool, log_ctx->limit),
+ alloc);
+ }
+
+ if (log_ctx->changed_paths)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:discover-changed-paths", NULL,
+ alloc);
+ }
+
+ if (log_ctx->strict_node_history)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:strict-node-history", NULL,
+ alloc);
+ }
+
+ if (log_ctx->include_merged_revisions)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:include-merged-revisions", NULL,
+ alloc);
+ }
+
+ if (log_ctx->revprops)
+ {
+ int i;
+ for (i = 0; i < log_ctx->revprops->nelts; i++)
+ {
+ char *name = APR_ARRAY_IDX(log_ctx->revprops, i, char *);
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:revprop", name,
+ alloc);
+ }
+ if (log_ctx->revprops->nelts == 0)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:no-revprops", NULL,
+ alloc);
+ }
+ }
+ else
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:all-revprops", NULL,
+ alloc);
+ }
+
+ if (log_ctx->paths)
+ {
+ int i;
+ for (i = 0; i < log_ctx->paths->nelts; i++)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:path", APR_ARRAY_IDX(log_ctx->paths, i,
+ const char*),
+ alloc);
+ }
+ }
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:encode-binary-props", NULL,
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc,
+ "S:log-report");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_log(svn_ra_session_t *ra_session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ log_context_t *log_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ svn_boolean_t want_custom_revprops;
+ svn_revnum_t peg_rev;
+ svn_error_t *err;
+ const char *req_url;
+
+ log_ctx = apr_pcalloc(pool, sizeof(*log_ctx));
+ log_ctx->pool = pool;
+ log_ctx->receiver = receiver;
+ log_ctx->receiver_baton = receiver_baton;
+ log_ctx->paths = paths;
+ log_ctx->start = start;
+ log_ctx->end = end;
+ log_ctx->limit = limit;
+ log_ctx->changed_paths = discover_changed_paths;
+ log_ctx->strict_node_history = strict_node_history;
+ log_ctx->include_merged_revisions = include_merged_revisions;
+ log_ctx->revprops = revprops;
+ log_ctx->nest_level = 0;
+
+ want_custom_revprops = FALSE;
+ if (revprops)
+ {
+ int i;
+ for (i = 0; i < revprops->nelts; i++)
+ {
+ char *name = APR_ARRAY_IDX(revprops, i, char *);
+ if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+ log_ctx->want_author = TRUE;
+ else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+ log_ctx->want_date = TRUE;
+ else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+ log_ctx->want_message = TRUE;
+ else
+ want_custom_revprops = TRUE;
+ }
+ }
+ else
+ {
+ log_ctx->want_author = log_ctx->want_date = log_ctx->want_message = TRUE;
+ want_custom_revprops = TRUE;
+ }
+
+ if (want_custom_revprops)
+ {
+ svn_boolean_t has_log_revprops;
+ SVN_ERR(svn_ra_serf__has_capability(ra_session, &has_log_revprops,
+ SVN_RA_CAPABILITY_LOG_REVPROPS, pool));
+ if (!has_log_revprops)
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
+ _("Server does not support custom revprops"
+ " via log"));
+ }
+ /* At this point, we may have a deleted file. So, we'll match ra_neon's
+ * behavior and use the larger of start or end as our 'peg' rev.
+ */
+ peg_rev = (start > end) ? start : end;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, peg_rev,
+ pool, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(log_ttable,
+ log_opened, log_closed, NULL,
+ log_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_delegate = create_log_body;
+ handler->body_delegate_baton = log_ctx;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ req_url,
+ handler->location),
+ err));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/merge.c b/contrib/subversion/subversion/libsvn_ra_serf/merge.c
new file mode 100644
index 0000000..670e421
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/merge.c
@@ -0,0 +1,430 @@
+/*
+ * merge.c : MERGE response parsing functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "svn_config.h"
+#include "svn_dirent_uri.h"
+#include "svn_props.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_fspath.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a MERGE.
+ */
+typedef enum merge_state_e {
+ INITIAL = 0,
+ MERGE_RESPONSE,
+ UPDATED_SET,
+ RESPONSE,
+ HREF,
+ PROPSTAT,
+ PROP,
+ RESOURCE_TYPE,
+ BASELINE,
+ COLLECTION,
+ SKIP_HREF,
+ CHECKED_IN,
+ VERSION_NAME,
+ DATE,
+ AUTHOR,
+ POST_COMMIT_ERR,
+
+ PROP_VAL
+} merge_state_e;
+
+
+/* Structure associated with a MERGE request. */
+typedef struct merge_context_t
+{
+ apr_pool_t *pool;
+
+ svn_ra_serf__session_t *session;
+ svn_ra_serf__handler_t *handler;
+
+ apr_hash_t *lock_tokens;
+ svn_boolean_t keep_locks;
+
+ const char *merge_resource_url; /* URL of resource to be merged. */
+ const char *merge_url; /* URL at which the MERGE request is aimed. */
+
+ svn_commit_info_t *commit_info;
+
+} merge_context_t;
+
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t merge_ttable[] = {
+ { INITIAL, D_, "merge-response", MERGE_RESPONSE,
+ FALSE, { NULL }, FALSE },
+
+ { MERGE_RESPONSE, D_, "updated-set", UPDATED_SET,
+ FALSE, { NULL }, FALSE },
+
+ { UPDATED_SET, D_, "response", RESPONSE,
+ FALSE, { NULL }, TRUE },
+
+ { RESPONSE, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
+
+ { RESPONSE, D_, "propstat", PROPSTAT,
+ FALSE, { NULL }, FALSE },
+
+#if 0
+ /* Not needed. */
+ { PROPSTAT, D_, "status", STATUS,
+ FALSE, { NULL }, FALSE },
+#endif
+
+ { PROPSTAT, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+
+ { PROP, D_, "resourcetype", RESOURCE_TYPE,
+ FALSE, { NULL }, FALSE },
+
+ { RESOURCE_TYPE, D_, "baseline", BASELINE,
+ FALSE, { NULL }, TRUE },
+
+ { RESOURCE_TYPE, D_, "collection", COLLECTION,
+ FALSE, { NULL }, TRUE },
+
+ { PROP, D_, "checked-in", SKIP_HREF,
+ FALSE, { NULL }, FALSE },
+
+ { SKIP_HREF, D_, "href", CHECKED_IN,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, D_, SVN_DAV__VERSION_NAME, VERSION_NAME,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, D_, SVN_DAV__CREATIONDATE, DATE,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, D_, "creator-displayname", AUTHOR,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, S_, "post-commit-err", POST_COMMIT_ERR,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+merge_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ merge_context_t *merge_ctx = baton;
+
+ if (leaving_state == RESPONSE)
+ {
+ const char *rtype;
+
+ rtype = svn_hash_gets(attrs, "resourcetype");
+
+ /* rtype can only be "baseline" or "collection" (or NULL). We can
+ keep this check simple. */
+ if (rtype && *rtype == 'b')
+ {
+ const char *rev_str;
+
+ rev_str = svn_hash_gets(attrs, "revision");
+ if (rev_str)
+ merge_ctx->commit_info->revision = SVN_STR_TO_REV(rev_str);
+ else
+ merge_ctx->commit_info->revision = SVN_INVALID_REVNUM;
+
+ merge_ctx->commit_info->date =
+ apr_pstrdup(merge_ctx->pool,
+ svn_hash_gets(attrs, "date"));
+
+ merge_ctx->commit_info->author =
+ apr_pstrdup(merge_ctx->pool,
+ svn_hash_gets(attrs, "author"));
+
+ merge_ctx->commit_info->post_commit_err =
+ apr_pstrdup(merge_ctx->pool,
+ svn_hash_gets(attrs, "post-commit-err"));
+ }
+ else
+ {
+ const char *href;
+
+ href = svn_urlpath__skip_ancestor(
+ merge_ctx->merge_url,
+ svn_hash_gets(attrs, "href"));
+
+ if (href == NULL)
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("A MERGE response for '%s' is not "
+ "a child of the destination ('%s')"),
+ href, merge_ctx->merge_url);
+
+ /* We now need to dive all the way into the WC to update the
+ base VCC url. */
+ if (!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(merge_ctx->session)
+ && merge_ctx->session->wc_callbacks->push_wc_prop)
+ {
+ const char *checked_in;
+ svn_string_t checked_in_str;
+
+ checked_in = svn_hash_gets(attrs, "checked-in");
+ checked_in_str.data = checked_in;
+ checked_in_str.len = strlen(checked_in);
+
+ SVN_ERR(merge_ctx->session->wc_callbacks->push_wc_prop(
+ merge_ctx->session->wc_callback_baton,
+ href,
+ SVN_RA_SERF__WC_CHECKED_IN_URL,
+ &checked_in_str,
+ scratch_pool));
+ }
+ }
+ }
+ else if (leaving_state == BASELINE)
+ {
+ svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "baseline");
+ }
+ else if (leaving_state == COLLECTION)
+ {
+ svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "collection");
+ }
+ else
+ {
+ const char *name;
+ const char *value = cdata->data;
+
+ if (leaving_state == HREF)
+ {
+ name = "href";
+ value = svn_urlpath__canonicalize(value, scratch_pool);
+ }
+ else if (leaving_state == CHECKED_IN)
+ {
+ name = "checked-in";
+ value = svn_urlpath__canonicalize(value, scratch_pool);
+ }
+ else if (leaving_state == VERSION_NAME)
+ name = "revision";
+ else if (leaving_state == DATE)
+ name = "date";
+ else if (leaving_state == AUTHOR)
+ name = "author";
+ else if (leaving_state == POST_COMMIT_ERR)
+ name = "post-commit-err";
+ else
+ SVN_ERR_MALFUNCTION();
+
+ svn_ra_serf__xml_note(xes, RESPONSE, name, value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+setup_merge_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ merge_context_t *ctx = baton;
+
+ if (!ctx->keep_locks)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
+ SVN_DAV_OPTION_RELEASE_LOCKS);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens,
+ const char *parent,
+ serf_bucket_t *body,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ if (!lock_tokens || apr_hash_count(lock_tokens) == 0)
+ return;
+
+ svn_ra_serf__add_open_tag_buckets(body, alloc,
+ "S:lock-token-list",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ for (hi = apr_hash_first(pool, lock_tokens);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+ svn_string_t path;
+
+ apr_hash_this(hi, &key, &klen, &val);
+
+ path.data = key;
+ path.len = klen;
+
+ if (parent && !svn_relpath_skip_ancestor(parent, key))
+ continue;
+
+ svn_ra_serf__add_open_tag_buckets(body, alloc, "S:lock", NULL);
+
+ svn_ra_serf__add_open_tag_buckets(body, alloc, "lock-path", NULL);
+ svn_ra_serf__add_cdata_len_buckets(body, alloc, path.data, path.len);
+ svn_ra_serf__add_close_tag_buckets(body, alloc, "lock-path");
+
+ svn_ra_serf__add_tag_buckets(body, "lock-token", val, alloc);
+
+ svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock");
+ }
+
+ svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock-token-list");
+}
+
+static svn_error_t*
+create_merge_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ merge_context_t *ctx = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:merge",
+ "xmlns:D", "DAV:",
+ NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:source", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL);
+
+ svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
+ ctx->merge_resource_url,
+ strlen(ctx->merge_resource_url));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:source");
+
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:no-auto-merge", NULL, alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:no-checkout", NULL, alloc);
+
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:checked-in", NULL, alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:" SVN_DAV__VERSION_NAME, NULL, alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:resourcetype", NULL, alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:" SVN_DAV__CREATIONDATE, NULL, alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:creator-displayname", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
+
+ svn_ra_serf__merge_lock_token_list(ctx->lock_tokens, NULL, body_bkt, alloc,
+ pool);
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:merge");
+
+ *bkt = body_bkt;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__run_merge(const svn_commit_info_t **commit_info,
+ int *response_code,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *merge_resource_url,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ merge_context_t *merge_ctx;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+
+ merge_ctx = apr_pcalloc(scratch_pool, sizeof(*merge_ctx));
+
+ merge_ctx->pool = result_pool;
+ merge_ctx->session = session;
+
+ merge_ctx->merge_resource_url = merge_resource_url;
+
+ merge_ctx->lock_tokens = lock_tokens;
+ merge_ctx->keep_locks = keep_locks;
+
+ merge_ctx->commit_info = svn_create_commit_info(result_pool);
+
+ merge_ctx->merge_url = session->session_url.path;
+
+ xmlctx = svn_ra_serf__xml_context_create(merge_ttable,
+ NULL, merge_closed, NULL,
+ merge_ctx,
+ scratch_pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, scratch_pool);
+
+ handler->method = "MERGE";
+ handler->path = merge_ctx->merge_url;
+ handler->body_delegate = create_merge_body;
+ handler->body_delegate_baton = merge_ctx;
+ handler->conn = conn;
+ handler->session = session;
+
+ handler->header_delegate = setup_merge_headers;
+ handler->header_delegate_baton = merge_ctx;
+
+ merge_ctx->handler = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
+
+ *commit_info = merge_ctx->commit_info;
+ *response_code = handler->sline.code;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/mergeinfo.c b/contrib/subversion/subversion/libsvn_ra_serf/mergeinfo.c
new file mode 100644
index 0000000..b0bf833
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/mergeinfo.c
@@ -0,0 +1,246 @@
+/*
+ * mergeinfo.c : entry point for mergeinfo RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_tables.h>
+#include <apr_xml.h>
+
+#include "svn_hash.h"
+#include "svn_mergeinfo.h"
+#include "svn_path.h"
+#include "svn_ra.h"
+#include "svn_string.h"
+#include "svn_xml.h"
+
+#include "private/svn_dav_protocol.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_private_config.h"
+#include "ra_serf.h"
+
+
+
+
+/* The current state of our XML parsing. */
+typedef enum mergeinfo_state_e {
+ INITIAL = 0,
+ MERGEINFO_REPORT,
+ MERGEINFO_ITEM,
+ MERGEINFO_PATH,
+ MERGEINFO_INFO
+} mergeinfo_state_e;
+
+/* Baton for accumulating mergeinfo. RESULT_CATALOG stores the final
+ mergeinfo catalog result we are going to hand back to the caller of
+ get_mergeinfo. */
+typedef struct mergeinfo_context_t {
+ apr_pool_t *pool;
+ svn_mergeinfo_t result_catalog;
+ const apr_array_header_t *paths;
+ svn_revnum_t revision;
+ svn_mergeinfo_inheritance_t inherit;
+ svn_boolean_t include_descendants;
+} mergeinfo_context_t;
+
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t mergeinfo_ttable[] = {
+ { INITIAL, S_, SVN_DAV__MERGEINFO_REPORT, MERGEINFO_REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { MERGEINFO_REPORT, S_, SVN_DAV__MERGEINFO_ITEM, MERGEINFO_ITEM,
+ FALSE, { NULL }, TRUE },
+
+ { MERGEINFO_ITEM, S_, SVN_DAV__MERGEINFO_PATH, MERGEINFO_PATH,
+ TRUE, { NULL }, TRUE },
+
+ { MERGEINFO_ITEM, S_, SVN_DAV__MERGEINFO_INFO, MERGEINFO_INFO,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+mergeinfo_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ mergeinfo_context_t *mergeinfo_ctx = baton;
+
+ if (leaving_state == MERGEINFO_ITEM)
+ {
+ /* Placed here from the child elements. */
+ const char *path = svn_hash_gets(attrs, "path");
+ const char *info = svn_hash_gets(attrs, "info");
+
+ if (path != NULL && info != NULL)
+ {
+ svn_mergeinfo_t path_mergeinfo;
+
+ /* Correct for naughty servers that send "relative" paths
+ with leading slashes! */
+ if (path[0] == '/')
+ ++path;
+
+ SVN_ERR(svn_mergeinfo_parse(&path_mergeinfo, info,
+ mergeinfo_ctx->pool));
+
+ svn_hash_sets(mergeinfo_ctx->result_catalog,
+ apr_pstrdup(mergeinfo_ctx->pool, path),
+ path_mergeinfo);
+ }
+ }
+ else
+ {
+ SVN_ERR_ASSERT(leaving_state == MERGEINFO_PATH
+ || leaving_state == MERGEINFO_INFO);
+
+ /* Stash the value onto the parent MERGEINFO_ITEM. */
+ svn_ra_serf__xml_note(xes, MERGEINFO_ITEM,
+ leaving_state == MERGEINFO_PATH
+ ? "path"
+ : "info",
+ cdata->data);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+create_mergeinfo_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ mergeinfo_context_t *mergeinfo_ctx = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__MERGEINFO_REPORT,
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:" SVN_DAV__REVISION,
+ apr_ltoa(pool, mergeinfo_ctx->revision),
+ alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "S:" SVN_DAV__INHERIT,
+ svn_inheritance_to_word(mergeinfo_ctx->inherit),
+ alloc);
+ if (mergeinfo_ctx->include_descendants)
+ {
+ svn_ra_serf__add_tag_buckets(body_bkt, "S:"
+ SVN_DAV__INCLUDE_DESCENDANTS,
+ "yes", alloc);
+ }
+
+ if (mergeinfo_ctx->paths)
+ {
+ int i;
+
+ for (i = 0; i < mergeinfo_ctx->paths->nelts; i++)
+ {
+ const char *this_path = APR_ARRAY_IDX(mergeinfo_ctx->paths,
+ i, const char *);
+
+ svn_ra_serf__add_tag_buckets(body_bkt, "S:" SVN_DAV__PATH,
+ this_path, alloc);
+ }
+ }
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__MERGEINFO_REPORT);
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session,
+ svn_mergeinfo_catalog_t *catalog,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool)
+{
+ svn_error_t *err, *err2;
+ mergeinfo_context_t *mergeinfo_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *path;
+
+ *catalog = NULL;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&path, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, revision,
+ pool, pool));
+
+ mergeinfo_ctx = apr_pcalloc(pool, sizeof(*mergeinfo_ctx));
+ mergeinfo_ctx->pool = pool;
+ mergeinfo_ctx->result_catalog = apr_hash_make(pool);
+ mergeinfo_ctx->paths = paths;
+ mergeinfo_ctx->revision = revision;
+ mergeinfo_ctx->inherit = inherit;
+ mergeinfo_ctx->include_descendants = include_descendants;
+
+ xmlctx = svn_ra_serf__xml_context_create(mergeinfo_ttable,
+ NULL, mergeinfo_closed, NULL,
+ mergeinfo_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = path;
+ handler->conn = session->conns[0];
+ handler->session = session;
+ handler->body_delegate = create_mergeinfo_body;
+ handler->body_delegate_baton = mergeinfo_ctx;
+ handler->body_type = "text/xml";
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ err2 = svn_ra_serf__error_on_status(handler->sline.code, handler->path,
+ handler->location);
+ if (err2)
+ {
+ svn_error_clear(err);
+ return err2;
+ }
+
+ SVN_ERR(err);
+
+ if (handler->done && apr_hash_count(mergeinfo_ctx->result_catalog))
+ *catalog = mergeinfo_ctx->result_catalog;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/options.c b/contrib/subversion/subversion/libsvn_ra_serf/options.c
new file mode 100644
index 0000000..0af0b15
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/options.c
@@ -0,0 +1,625 @@
+/*
+ * options.c : entry point for OPTIONS RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+
+#include <serf.h>
+
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_private_config.h"
+#include "private/svn_fspath.h"
+
+#include "ra_serf.h"
+
+
+/* In a debug build, setting this environment variable to "yes" will force
+ the client to speak v1, even if the server is capable of speaking v2. */
+#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
+
+
+/*
+ * This enum represents the current state of our XML parsing for an OPTIONS.
+ */
+enum options_state_e {
+ INITIAL = 0,
+ OPTIONS,
+ ACTIVITY_COLLECTION,
+ HREF
+};
+
+typedef struct options_context_t {
+ /* pool to allocate memory from */
+ apr_pool_t *pool;
+
+ /* Have we extracted options values from the headers already? */
+ svn_boolean_t headers_processed;
+
+ svn_ra_serf__session_t *session;
+ svn_ra_serf__connection_t *conn;
+ svn_ra_serf__handler_t *handler;
+
+ svn_ra_serf__response_handler_t inner_handler;
+ void *inner_baton;
+
+ const char *activity_collection;
+ svn_revnum_t youngest_rev;
+
+} options_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t options_ttable[] = {
+ { INITIAL, D_, "options-response", OPTIONS,
+ FALSE, { NULL }, FALSE },
+
+ { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
+ FALSE, { NULL }, FALSE },
+
+ { ACTIVITY_COLLECTION, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+options_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ options_context_t *opt_ctx = baton;
+
+ SVN_ERR_ASSERT(leaving_state == HREF);
+ SVN_ERR_ASSERT(cdata != NULL);
+
+ opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
+ opt_ctx->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+create_options_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *body;
+ body = serf_bucket_aggregate_create(alloc);
+ svn_ra_serf__add_xml_header_buckets(body, alloc);
+ svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
+ "xmlns:D", "DAV:",
+ NULL);
+ svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
+
+ *body_bkt = body;
+ return SVN_NO_ERROR;
+}
+
+
+/* We use these static pointers so we can employ pointer comparison
+ * of our capabilities hash members instead of strcmp()ing all over
+ * the place.
+ */
+/* Both server and repository support the capability. */
+static const char *const capability_yes = "yes";
+/* Either server or repository does not support the capability. */
+static const char *const capability_no = "no";
+/* Server supports the capability, but don't yet know if repository does. */
+static const char *const capability_server_yes = "server-yes";
+
+
+/* This implements serf_bucket_headers_do_callback_fn_t.
+ */
+static int
+capabilities_headers_iterator_callback(void *baton,
+ const char *key,
+ const char *val)
+{
+ options_context_t *opt_ctx = baton;
+ svn_ra_serf__session_t *session = opt_ctx->session;
+
+ if (svn_cstring_casecmp(key, "dav") == 0)
+ {
+ /* Each header may contain multiple values, separated by commas, e.g.:
+ DAV: version-control,checkout,working-resource
+ DAV: merge,baseline,activity,version-controlled-collection
+ DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
+ apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
+ opt_ctx->pool);
+
+ /* Right now we only have a few capabilities to detect, so just
+ seek for them directly. This could be written slightly more
+ efficiently, but that wouldn't be worth it until we have many
+ more capabilities. */
+
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_DEPTH, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
+ {
+ /* The server doesn't know what repository we're referring
+ to, so it can't just say capability_yes. */
+ if (!svn_hash_gets(session->capabilities,
+ SVN_RA_CAPABILITY_MERGEINFO))
+ {
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_server_yes);
+ }
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
+ vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
+ capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
+ {
+ session->supports_inline_props = TRUE;
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
+ {
+ session->supports_rev_rsrc_replay = TRUE;
+ }
+ }
+
+ /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
+ else if (strncmp(key, "SVN", 3) == 0)
+ {
+ /* If we've not yet seen any information about supported POST
+ requests, we'll initialize the list/hash with "create-txn"
+ (which we know is supported by virtue of the server speaking
+ HTTPv2 at all. */
+ if (! session->supported_posts)
+ {
+ session->supported_posts = apr_hash_make(session->pool);
+ apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
+ }
+
+ if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
+ {
+ session->repos_root = session->session_url;
+ session->repos_root.path =
+ (char *)svn_fspath__canonicalize(val, session->pool);
+ session->repos_root_str =
+ svn_urlpath__canonicalize(
+ apr_uri_unparse(session->pool, &session->repos_root, 0),
+ session->pool);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
+ {
+#ifdef SVN_DEBUG
+ char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
+
+ if (!(ignore_v2_env_var
+ && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
+ session->me_resource = apr_pstrdup(session->pool, val);
+#else
+ session->me_resource = apr_pstrdup(session->pool, val);
+#endif
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
+ {
+ session->rev_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
+ {
+ session->rev_root_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
+ {
+ session->txn_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
+ {
+ session->txn_root_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
+ {
+ session->vtxn_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
+ {
+ session->vtxn_root_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
+ {
+ session->uuid = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
+ {
+ opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
+ {
+ session->server_allows_bulk = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
+ {
+ /* May contain multiple values, separated by commas. */
+ int i;
+ apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
+ opt_ctx->pool);
+
+ for (i = 0; i < vals->nelts; i++)
+ {
+ const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
+
+ svn_hash_sets(session->supported_posts, post_val, (void *)1);
+ }
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
+ {
+ if (svn_cstring_casecmp(val, "yes") == 0)
+ {
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_yes);
+ }
+ else if (svn_cstring_casecmp(val, "no") == 0)
+ {
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_no);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/* A custom serf_response_handler_t which is mostly a wrapper around
+ the expat-based response handler -- it just notices OPTIONS response
+ headers first, before handing off to the xml parser.
+ Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+options_response_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ options_context_t *opt_ctx = baton;
+
+ if (!opt_ctx->headers_processed)
+ {
+ svn_ra_serf__session_t *session = opt_ctx->session;
+ serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
+
+ /* Start out assuming all capabilities are unsupported. */
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ NULL);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
+ capability_no);
+
+ /* Then see which ones we can discover. */
+ serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
+ opt_ctx);
+
+ /* Assume mergeinfo capability unsupported, if didn't recieve information
+ about server or repository mergeinfo capability. */
+ if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_no);
+
+ opt_ctx->headers_processed = TRUE;
+ }
+
+ /* Execute the 'real' response handler to XML-parse the response body. */
+ return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
+}
+
+
+static svn_error_t *
+create_options_req(options_context_t **opt_ctx,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
+{
+ options_context_t *new_ctx;
+ svn_ra_serf__xml_context_t *xmlctx;
+ svn_ra_serf__handler_t *handler;
+
+ new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
+ new_ctx->pool = pool;
+ new_ctx->session = session;
+ new_ctx->conn = conn;
+
+ new_ctx->youngest_rev = SVN_INVALID_REVNUM;
+
+ xmlctx = svn_ra_serf__xml_context_create(options_ttable,
+ NULL, options_closed, NULL,
+ new_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "OPTIONS";
+ handler->path = session->session_url.path;
+ handler->body_delegate = create_options_body;
+ handler->body_type = "text/xml";
+ handler->conn = conn;
+ handler->session = session;
+
+ new_ctx->handler = handler;
+
+ new_ctx->inner_handler = handler->response_handler;
+ new_ctx->inner_baton = handler->response_baton;
+ handler->response_handler = options_response_handler;
+ handler->response_baton = new_ctx;
+
+ *opt_ctx = new_ctx;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = conn->session;
+ options_context_t *opt_ctx;
+
+ SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
+
+ SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
+ SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
+ SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline.code,
+ opt_ctx->handler->path,
+ opt_ctx->handler->location));
+
+ *youngest = opt_ctx->youngest_rev;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__v1_get_activity_collection(const char **activity_url,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = conn->session;
+ options_context_t *opt_ctx;
+
+ SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
+
+ SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
+ SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
+
+ SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline.code,
+ opt_ctx->handler->path,
+ opt_ctx->handler->location));
+
+ *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
+
+ return SVN_NO_ERROR;
+
+}
+
+
+
+/** Capabilities exchange. */
+
+svn_error_t *
+svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
+ const char **corrected_url,
+ apr_pool_t *pool)
+{
+ options_context_t *opt_ctx;
+ svn_error_t *err;
+
+ /* This routine automatically fills in serf_sess->capabilities */
+ SVN_ERR(create_options_req(&opt_ctx, serf_sess, serf_sess->conns[0], pool));
+
+ err = svn_ra_serf__context_run_one(opt_ctx->handler, pool);
+
+ /* If our caller cares about server redirections, and our response
+ carries such a thing, report as much. We'll disregard ERR --
+ it's most likely just a complaint about the response body not
+ successfully parsing as XML or somesuch. */
+ if (corrected_url && (opt_ctx->handler->sline.code == 301))
+ {
+ svn_error_clear(err);
+ *corrected_url = opt_ctx->handler->location;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(opt_ctx->handler->sline.code,
+ serf_sess->session_url.path,
+ opt_ctx->handler->location),
+ err));
+
+ /* Opportunistically cache any reported activity URL. (We don't
+ want to have to ask for this again later, potentially against an
+ unreadable commit anchor URL.) */
+ if (opt_ctx->activity_collection)
+ {
+ serf_sess->activity_collection_url =
+ apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *serf_sess = ra_session->priv;
+ const char *cap_result;
+
+ /* This capability doesn't rely on anything server side. */
+ if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
+ {
+ *has = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ cap_result = svn_hash_gets(serf_sess->capabilities, capability);
+
+ /* If any capability is unknown, they're all unknown, so ask. */
+ if (cap_result == NULL)
+ SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool));
+
+ /* Try again, now that we've fetched the capabilities. */
+ cap_result = svn_hash_gets(serf_sess->capabilities, capability);
+
+ /* Some capabilities depend on the repository as well as the server. */
+ if (cap_result == capability_server_yes)
+ {
+ if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
+ {
+ /* Handle mergeinfo specially. Mergeinfo depends on the
+ repository as well as the server, but the server routine
+ that answered our svn_ra_serf__exchange_capabilities() call above
+ didn't even know which repository we were interested in
+ -- it just told us whether the server supports mergeinfo.
+ If the answer was 'no', there's no point checking the
+ particular repository; but if it was 'yes', we still must
+ change it to 'no' iff the repository itself doesn't
+ support mergeinfo. */
+ svn_mergeinfo_catalog_t ignored;
+ svn_error_t *err;
+ apr_array_header_t *paths = apr_array_make(pool, 1,
+ sizeof(char *));
+ APR_ARRAY_PUSH(paths, const char *) = "";
+
+ err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
+ FALSE, FALSE, pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
+ {
+ svn_error_clear(err);
+ cap_result = capability_no;
+ }
+ else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ /* Mergeinfo requests use relative paths, and
+ anyway we're in r0, so this is a likely error,
+ but it means the repository supports mergeinfo! */
+ svn_error_clear(err);
+ cap_result = capability_yes;
+ }
+ else
+ return err;
+ }
+ else
+ cap_result = capability_yes;
+
+ svn_hash_sets(serf_sess->capabilities,
+ SVN_RA_CAPABILITY_MERGEINFO, cap_result);
+ }
+ else
+ {
+ return svn_error_createf
+ (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
+ _("Don't know how to handle '%s' for capability '%s'"),
+ capability_server_yes, capability);
+ }
+ }
+
+ if (cap_result == capability_yes)
+ {
+ *has = TRUE;
+ }
+ else if (cap_result == capability_no)
+ {
+ *has = FALSE;
+ }
+ else if (cap_result == NULL)
+ {
+ return svn_error_createf
+ (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
+ _("Don't know anything about capability '%s'"), capability);
+ }
+ else /* "can't happen" */
+ {
+ /* Well, let's hope it's a string. */
+ return svn_error_createf
+ (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("Attempt to fetch capability '%s' resulted in '%s'"),
+ capability, cap_result);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/property.c b/contrib/subversion/subversion/libsvn_ra_serf/property.c
new file mode 100644
index 0000000..63972e8
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/property.c
@@ -0,0 +1,1263 @@
+/*
+ * property.c : property routines for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_base64.h"
+#include "svn_xml.h"
+#include "svn_props.h"
+#include "svn_dirent_uri.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_fspath.h"
+#include "private/svn_string_private.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+
+
+/* Our current parsing state we're in for the PROPFIND response. */
+typedef enum prop_state_e {
+ INITIAL = 0,
+ MULTISTATUS,
+ RESPONSE,
+ HREF,
+ PROPSTAT,
+ STATUS,
+ PROP,
+ PROPVAL,
+ COLLECTION,
+ HREF_VALUE
+} prop_state_e;
+
+
+/*
+ * This structure represents a pending PROPFIND response.
+ */
+typedef struct propfind_context_t {
+ /* pool to issue allocations from */
+ apr_pool_t *pool;
+
+ svn_ra_serf__handler_t *handler;
+
+ /* associated serf session */
+ svn_ra_serf__session_t *sess;
+ svn_ra_serf__connection_t *conn;
+
+ /* the requested path */
+ const char *path;
+
+ /* the requested version (number and string form) */
+ svn_revnum_t rev;
+ const char *label;
+
+ /* the request depth */
+ const char *depth;
+
+ /* the list of requested properties */
+ const svn_ra_serf__dav_props_t *find_props;
+
+ /* hash table that will be updated with the properties
+ *
+ * This can be shared between multiple propfind_context_t
+ * structures
+ */
+ apr_hash_t *ret_props;
+
+ /* hash table containing all the properties associated with the
+ * "current" <propstat> tag. These will get copied into RET_PROPS
+ * if the status code similarly associated indicates that they are
+ * "good"; otherwise, they'll get discarded.
+ */
+ apr_hash_t *ps_props;
+
+ /* If not-NULL, add us to this list when we're done. */
+ svn_ra_serf__list_t **done_list;
+
+ svn_ra_serf__list_t done_item;
+
+} propfind_context_t;
+
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t propfind_ttable[] = {
+ { INITIAL, D_, "multistatus", MULTISTATUS,
+ FALSE, { NULL }, TRUE },
+
+ { MULTISTATUS, D_, "response", RESPONSE,
+ FALSE, { NULL }, FALSE },
+
+ { RESPONSE, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
+
+ { RESPONSE, D_, "propstat", PROPSTAT,
+ FALSE, { NULL }, TRUE },
+
+ { PROPSTAT, D_, "status", STATUS,
+ TRUE, { NULL }, TRUE },
+
+ { PROPSTAT, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+
+ { PROP, "*", "*", PROPVAL,
+ TRUE, { "?V:encoding", NULL }, TRUE },
+
+ { PROPVAL, D_, "collection", COLLECTION,
+ FALSE, { NULL }, TRUE },
+
+ { PROPVAL, D_, "href", HREF_VALUE,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Return the HTTP status code contained in STATUS_LINE, or 0 if
+ there's a problem parsing it. */
+static int parse_status_code(const char *status_line)
+{
+ /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */
+ if (status_line[0] == 'H' &&
+ status_line[1] == 'T' &&
+ status_line[2] == 'T' &&
+ status_line[3] == 'P' &&
+ status_line[4] == '/' &&
+ (status_line[5] >= '0' && status_line[5] <= '9') &&
+ status_line[6] == '.' &&
+ (status_line[7] >= '0' && status_line[7] <= '9') &&
+ status_line[8] == ' ')
+ {
+ char *reason;
+
+ return apr_strtoi64(status_line + 8, &reason, 10);
+ }
+ return 0;
+}
+
+
+/* Conforms to svn_ra_serf__path_rev_walker_t */
+static svn_error_t *
+copy_into_ret_props(void *baton,
+ const char *path, apr_ssize_t path_len,
+ const char *ns, apr_ssize_t ns_len,
+ const char *name, apr_ssize_t name_len,
+ const svn_string_t *val,
+ apr_pool_t *pool)
+{
+ propfind_context_t *ctx = baton;
+
+ svn_ra_serf__set_ver_prop(ctx->ret_props, path, ctx->rev, ns, name,
+ val, ctx->pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+propfind_opened(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int entered_state,
+ const svn_ra_serf__dav_props_t *tag,
+ apr_pool_t *scratch_pool)
+{
+ propfind_context_t *ctx = baton;
+
+ if (entered_state == PROPVAL)
+ {
+ svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->namespace);
+ svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name);
+ }
+ else if (entered_state == PROPSTAT)
+ {
+ ctx->ps_props = apr_hash_make(ctx->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+propfind_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ propfind_context_t *ctx = baton;
+
+ if (leaving_state == MULTISTATUS)
+ {
+ /* We've gathered all the data from the reponse. Add this item
+ onto the "done list". External callers will then know this
+ request has been completed (tho stray response bytes may still
+ arrive). */
+ if (ctx->done_list)
+ {
+ ctx->done_item.data = ctx->handler;
+ ctx->done_item.next = *ctx->done_list;
+ *ctx->done_list = &ctx->done_item;
+ }
+ }
+ else if (leaving_state == HREF)
+ {
+ const char *path;
+ const svn_string_t *val_str;
+
+ if (strcmp(ctx->depth, "1") == 0)
+ path = svn_urlpath__canonicalize(cdata->data, scratch_pool);
+ else
+ path = ctx->path;
+
+ svn_ra_serf__xml_note(xes, RESPONSE, "path", path);
+
+ /* Copy the value into the right pool, then save the HREF. */
+ val_str = svn_string_dup(cdata, ctx->pool);
+ svn_ra_serf__set_ver_prop(ctx->ret_props,
+ path, ctx->rev, D_, "href", val_str,
+ ctx->pool);
+ }
+ else if (leaving_state == COLLECTION)
+ {
+ svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection");
+ }
+ else if (leaving_state == HREF_VALUE)
+ {
+ svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data);
+ }
+ else if (leaving_state == STATUS)
+ {
+ /* Parse the status field, and remember if this is a property
+ that we wish to ignore. (Typically, if it's not a 200, the
+ status will be 404 to indicate that a property we
+ specifically requested from the server doesn't exist.) */
+ int status = parse_status_code(cdata->data);
+ if (status != 200)
+ svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*");
+ }
+ else if (leaving_state == PROPVAL)
+ {
+ const char *encoding = svn_hash_gets(attrs, "V:encoding");
+ const svn_string_t *val_str;
+ apr_hash_t *gathered;
+ const char *path;
+ const char *ns;
+ const char *name;
+ const char *altvalue;
+
+ if (encoding)
+ {
+ if (strcmp(encoding, "base64") != 0)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ NULL,
+ _("Got unrecognized encoding '%s'"),
+ encoding);
+
+ /* Decode into the right pool. */
+ val_str = svn_base64_decode_string(cdata, ctx->pool);
+ }
+ else
+ {
+ /* Copy into the right pool. */
+ val_str = svn_string_dup(cdata, ctx->pool);
+ }
+
+ /* The current path sits on the RESPONSE state. Gather up all the
+ state from this PROPVAL to the (grandparent) RESPONSE state,
+ and grab the path from there.
+
+ Now, it would be nice if we could, at this point, know that
+ the status code for this property indicated a problem -- then
+ we could simply bail out here and ignore the property.
+ Sadly, though, we might get the status code *after* we get
+ the property value. So we'll carry on with our processing
+ here, setting the property and value as expected. Once we
+ know for sure the status code associate with the property,
+ we'll decide its fate. */
+ gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE);
+
+ /* These will be dup'd into CTX->POOL, as necessary. */
+ path = svn_hash_gets(gathered, "path");
+ if (path == NULL)
+ path = ctx->path;
+
+ ns = svn_hash_gets(attrs, "ns");
+ name = apr_pstrdup(ctx->pool,
+ svn_hash_gets(attrs, "name"));
+
+ altvalue = svn_hash_gets(attrs, "altvalue");
+ if (altvalue != NULL)
+ val_str = svn_string_create(altvalue, ctx->pool);
+
+ svn_ra_serf__set_ver_prop(ctx->ps_props,
+ path, ctx->rev, ns, name, val_str,
+ ctx->pool);
+ }
+ else
+ {
+ apr_hash_t *gathered;
+
+ SVN_ERR_ASSERT(leaving_state == PROPSTAT);
+
+ gathered = svn_ra_serf__xml_gather_since(xes, PROPSTAT);
+
+ /* If we've squirreled away a note that says we want to ignore
+ these properties, we'll do so. Otherwise, we need to copy
+ them from the temporary hash into the ctx->ret_props hash. */
+ if (! svn_hash_gets(gathered, "ignore-prop"))
+ {
+ SVN_ERR(svn_ra_serf__walk_all_paths(ctx->ps_props, ctx->rev,
+ copy_into_ret_props, ctx,
+ scratch_pool));
+ }
+
+ ctx->ps_props = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+const svn_string_t *
+svn_ra_serf__get_ver_prop_string(apr_hash_t *props,
+ const char *path,
+ svn_revnum_t rev,
+ const char *ns,
+ const char *name)
+{
+ apr_hash_t *ver_props, *path_props, *ns_props;
+ void *val = NULL;
+
+ ver_props = apr_hash_get(props, &rev, sizeof(rev));
+ if (ver_props)
+ {
+ path_props = svn_hash_gets(ver_props, path);
+
+ if (path_props)
+ {
+ ns_props = svn_hash_gets(path_props, ns);
+ if (ns_props)
+ {
+ val = svn_hash_gets(ns_props, name);
+ }
+ }
+ }
+
+ return val;
+}
+
+const char *
+svn_ra_serf__get_ver_prop(apr_hash_t *props,
+ const char *path,
+ svn_revnum_t rev,
+ const char *ns,
+ const char *name)
+{
+ const svn_string_t *val;
+
+ val = svn_ra_serf__get_ver_prop_string(props, path, rev, ns, name);
+
+ if (val)
+ {
+ return val->data;
+ }
+
+ return NULL;
+}
+
+const svn_string_t *
+svn_ra_serf__get_prop_string(apr_hash_t *props,
+ const char *path,
+ const char *ns,
+ const char *name)
+{
+ return svn_ra_serf__get_ver_prop_string(props, path, SVN_INVALID_REVNUM,
+ ns, name);
+}
+
+const char *
+svn_ra_serf__get_prop(apr_hash_t *props,
+ const char *path,
+ const char *ns,
+ const char *name)
+{
+ return svn_ra_serf__get_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name);
+}
+
+void
+svn_ra_serf__set_ver_prop(apr_hash_t *props,
+ const char *path, svn_revnum_t rev,
+ const char *ns, const char *name,
+ const svn_string_t *val, apr_pool_t *pool)
+{
+ apr_hash_t *ver_props, *path_props, *ns_props;
+
+ ver_props = apr_hash_get(props, &rev, sizeof(rev));
+ if (!ver_props)
+ {
+ ver_props = apr_hash_make(pool);
+ apr_hash_set(props, apr_pmemdup(pool, &rev, sizeof(rev)), sizeof(rev),
+ ver_props);
+ }
+
+ path_props = svn_hash_gets(ver_props, path);
+
+ if (!path_props)
+ {
+ path_props = apr_hash_make(pool);
+ path = apr_pstrdup(pool, path);
+ svn_hash_sets(ver_props, path, path_props);
+
+ /* todo: we know that we'll fail the next check, but fall through
+ * for now for simplicity's sake.
+ */
+ }
+
+ ns_props = svn_hash_gets(path_props, ns);
+ if (!ns_props)
+ {
+ ns_props = apr_hash_make(pool);
+ ns = apr_pstrdup(pool, ns);
+ svn_hash_sets(path_props, ns, ns_props);
+ }
+
+ svn_hash_sets(ns_props, name, val);
+}
+
+void
+svn_ra_serf__set_prop(apr_hash_t *props,
+ const char *path,
+ const char *ns, const char *name,
+ const svn_string_t *val, apr_pool_t *pool)
+{
+ svn_ra_serf__set_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name,
+ val, pool);
+}
+
+
+static svn_error_t *
+setup_propfind_headers(serf_bucket_t *headers,
+ void *setup_baton,
+ apr_pool_t *pool)
+{
+ propfind_context_t *ctx = setup_baton;
+
+ serf_bucket_headers_setn(headers, "Depth", ctx->depth);
+ if (ctx->label)
+ {
+ serf_bucket_headers_setn(headers, "Label", ctx->label);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">"
+#define PROPFIND_TRAILER "</propfind>"
+
+static svn_error_t *
+create_propfind_body(serf_bucket_t **bkt,
+ void *setup_baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ propfind_context_t *ctx = setup_baton;
+
+ serf_bucket_t *body_bkt, *tmp;
+ const svn_ra_serf__dav_props_t *prop;
+ svn_boolean_t requested_allprop = FALSE;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ prop = ctx->find_props;
+ while (prop && prop->namespace)
+ {
+ /* special case the allprop case. */
+ if (strcmp(prop->name, "allprop") == 0)
+ {
+ requested_allprop = TRUE;
+ }
+
+ /* <*propname* xmlns="*propns*" /> */
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
+ sizeof(" xmlns=\"")-1,
+ alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(prop->namespace, alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
+ alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ prop++;
+ }
+
+ /* If we're not doing an allprop, add <prop> tags. */
+ if (!requested_allprop)
+ {
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
+ sizeof("<prop>")-1,
+ alloc);
+ serf_bucket_aggregate_prepend(body_bkt, tmp);
+ }
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
+ sizeof(PROPFIND_HEADER)-1,
+ alloc);
+
+ serf_bucket_aggregate_prepend(body_bkt, tmp);
+
+ if (!requested_allprop)
+ {
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
+ sizeof("</prop>")-1,
+ alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+ }
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
+ sizeof(PROPFIND_TRAILER)-1,
+ alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler,
+ apr_hash_t *ret_props,
+ svn_ra_serf__session_t *sess,
+ svn_ra_serf__connection_t *conn,
+ const char *path,
+ svn_revnum_t rev,
+ const char *depth,
+ const svn_ra_serf__dav_props_t *find_props,
+ svn_ra_serf__list_t **done_list,
+ apr_pool_t *pool)
+{
+ propfind_context_t *new_prop_ctx;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+
+ new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
+
+ new_prop_ctx->pool = apr_hash_pool_get(ret_props);
+ new_prop_ctx->path = path;
+ new_prop_ctx->find_props = find_props;
+ new_prop_ctx->ret_props = ret_props;
+ new_prop_ctx->depth = depth;
+ new_prop_ctx->sess = sess;
+ new_prop_ctx->conn = conn;
+ new_prop_ctx->rev = rev;
+ new_prop_ctx->done_list = done_list;
+
+ if (SVN_IS_VALID_REVNUM(rev))
+ {
+ new_prop_ctx->label = apr_ltoa(pool, rev);
+ }
+ else
+ {
+ new_prop_ctx->label = NULL;
+ }
+
+ xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
+ propfind_opened,
+ propfind_closed,
+ NULL,
+ new_prop_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "PROPFIND";
+ handler->path = path;
+ handler->body_delegate = create_propfind_body;
+ handler->body_type = "text/xml";
+ handler->body_delegate_baton = new_prop_ctx;
+ handler->header_delegate = setup_propfind_headers;
+ handler->header_delegate_baton = new_prop_ctx;
+
+ handler->session = new_prop_ctx->sess;
+ handler->conn = new_prop_ctx->conn;
+
+ new_prop_ctx->handler = handler;
+
+ *propfind_handler = handler;
+
+ return SVN_NO_ERROR;
+}
+
+
+/*
+ * This helper function will block until the PROP_CTX indicates that is done
+ * or another error is returned.
+ */
+svn_error_t *
+svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_error_t *err2;
+
+ err = svn_ra_serf__context_run_one(handler, scratch_pool);
+
+ err2 = svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location);
+
+ return svn_error_compose_create(err2, err);
+}
+
+/*
+ * This is a blocking version of deliver_props.
+ */
+svn_error_t *
+svn_ra_serf__retrieve_props(apr_hash_t **results,
+ svn_ra_serf__session_t *sess,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t rev,
+ const char *depth,
+ const svn_ra_serf__dav_props_t *props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler;
+
+ *results = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_ra_serf__deliver_props(&handler, *results, sess, conn, url,
+ rev, depth, props, NULL, result_pool));
+ SVN_ERR(svn_ra_serf__wait_for_props(handler, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__fetch_node_props(apr_hash_t **results,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const svn_ra_serf__dav_props_t *which_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *multiprops;
+ apr_hash_t *ver_props;
+
+ /* Note: a couple extra hash tables and whatnot get into RESULT_POOL.
+ Not a big deal at this point. Theoretically, we could fetch all
+ props into SCRATCH_POOL, then copy just the REVISION/URL props
+ into RESULT_POOL. Too much work for too little gain... */
+ SVN_ERR(svn_ra_serf__retrieve_props(&multiprops, conn->session, conn,
+ url, revision, "0", which_props,
+ result_pool, scratch_pool));
+
+ ver_props = apr_hash_get(multiprops, &revision, sizeof(revision));
+ if (ver_props != NULL)
+ {
+ *results = svn_hash_gets(ver_props, url);
+ if (*results != NULL)
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include "
+ "the requested properties"));
+}
+
+
+svn_error_t *
+svn_ra_serf__walk_node_props(apr_hash_t *props,
+ svn_ra_serf__walker_visitor_t walker,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ apr_hash_index_t *ns_hi;
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (ns_hi = apr_hash_first(scratch_pool, props); ns_hi;
+ ns_hi = apr_hash_next(ns_hi))
+ {
+ void *ns_val;
+ const void *ns_name;
+ apr_hash_index_t *name_hi;
+
+ /* NOTE: We do not clear ITERPOOL in this loop. Generally, there are
+ very few namespaces, so this loop will not have many iterations.
+ Instead, ITERPOOL is used for the inner loop. */
+
+ apr_hash_this(ns_hi, &ns_name, NULL, &ns_val);
+
+ for (name_hi = apr_hash_first(scratch_pool, ns_val); name_hi;
+ name_hi = apr_hash_next(name_hi))
+ {
+ void *prop_val;
+ const void *prop_name;
+
+ /* See note above, regarding clearing of this pool. */
+ svn_pool_clear(iterpool);
+
+ apr_hash_this(name_hi, &prop_name, NULL, &prop_val);
+
+ SVN_ERR(walker(baton, ns_name, prop_name, prop_val, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__walk_all_props(apr_hash_t *props,
+ const char *name,
+ svn_revnum_t rev,
+ svn_ra_serf__walker_visitor_t walker,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *ver_props;
+ apr_hash_t *path_props;
+
+ ver_props = apr_hash_get(props, &rev, sizeof(rev));
+ if (!ver_props)
+ return SVN_NO_ERROR;
+
+ path_props = svn_hash_gets(ver_props, name);
+ if (!path_props)
+ return SVN_NO_ERROR;
+
+ return svn_error_trace(svn_ra_serf__walk_node_props(path_props,
+ walker, baton,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_ra_serf__walk_all_paths(apr_hash_t *props,
+ svn_revnum_t rev,
+ svn_ra_serf__path_rev_walker_t walker,
+ void *baton,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *path_hi;
+ apr_hash_t *ver_props;
+
+ ver_props = apr_hash_get(props, &rev, sizeof(rev));
+
+ if (!ver_props)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ for (path_hi = apr_hash_first(pool, ver_props); path_hi;
+ path_hi = apr_hash_next(path_hi))
+ {
+ void *path_props;
+ const void *path_name;
+ apr_ssize_t path_len;
+ apr_hash_index_t *ns_hi;
+
+ apr_hash_this(path_hi, &path_name, &path_len, &path_props);
+ for (ns_hi = apr_hash_first(pool, path_props); ns_hi;
+ ns_hi = apr_hash_next(ns_hi))
+ {
+ void *ns_val;
+ const void *ns_name;
+ apr_ssize_t ns_len;
+ apr_hash_index_t *name_hi;
+ apr_hash_this(ns_hi, &ns_name, &ns_len, &ns_val);
+ for (name_hi = apr_hash_first(pool, ns_val); name_hi;
+ name_hi = apr_hash_next(name_hi))
+ {
+ void *prop_val;
+ const void *prop_name;
+ apr_ssize_t prop_len;
+
+ apr_hash_this(name_hi, &prop_name, &prop_len, &prop_val);
+ /* use a subpool? */
+ SVN_ERR(walker(baton, path_name, path_len, ns_name, ns_len,
+ prop_name, prop_len, prop_val, pool));
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+const char *
+svn_ra_serf__svnname_from_wirename(const char *ns,
+ const char *name,
+ apr_pool_t *result_pool)
+{
+ if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
+ return apr_pstrdup(result_pool, name);
+
+ if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
+ return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
+
+ if (strcmp(ns, SVN_PROP_PREFIX) == 0)
+ return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
+
+ if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
+ return SVN_PROP_ENTRY_COMMITTED_REV;
+
+ if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
+ return SVN_PROP_ENTRY_COMMITTED_DATE;
+
+ if (strcmp(name, "creator-displayname") == 0)
+ return SVN_PROP_ENTRY_LAST_AUTHOR;
+
+ if (strcmp(name, "repository-uuid") == 0)
+ return SVN_PROP_ENTRY_UUID;
+
+ if (strcmp(name, "lock-token") == 0)
+ return SVN_PROP_ENTRY_LOCK_TOKEN;
+
+ if (strcmp(name, "checked-in") == 0)
+ return SVN_RA_SERF__WC_CHECKED_IN_URL;
+
+ if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
+ {
+ /* Here DAV: properties not yet converted to svn: properties should be
+ ignored. */
+ return NULL;
+ }
+
+ /* An unknown namespace, must be a custom property. */
+ return apr_pstrcat(result_pool, ns, name, (char *)NULL);
+}
+
+
+/* Conforms to svn_ra_serf__walker_visitor_t */
+static svn_error_t *
+set_flat_props(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ apr_hash_t *props = baton;
+ apr_pool_t *result_pool = apr_hash_pool_get(props);
+ const char *prop_name;
+
+ /* ### is VAL in the proper pool? */
+
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
+ if (prop_name != NULL)
+ svn_hash_sets(props, prop_name, value);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__flatten_props(apr_hash_t **flat_props,
+ apr_hash_t *props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *flat_props = apr_hash_make(result_pool);
+
+ return svn_error_trace(svn_ra_serf__walk_node_props(
+ props,
+ set_flat_props,
+ *flat_props /* baton */,
+ scratch_pool));
+}
+
+
+static svn_error_t *
+select_revprops(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *revprops = baton;
+ apr_pool_t *result_pool = apr_hash_pool_get(revprops);
+ const char *prop_name;
+
+ /* ### copy NAME into the RESULT_POOL? */
+ /* ### copy VAL into the RESULT_POOL? */
+
+ if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
+ prop_name = name;
+ else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
+ prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
+ else if (strcmp(ns, SVN_PROP_PREFIX) == 0)
+ prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
+ else if (strcmp(ns, "") == 0)
+ prop_name = name;
+ else
+ {
+ /* do nothing for now? */
+ return SVN_NO_ERROR;
+ }
+
+ svn_hash_sets(revprops, prop_name, val);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__select_revprops(apr_hash_t **revprops,
+ const char *name,
+ svn_revnum_t rev,
+ apr_hash_t *all_revprops,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *revprops = apr_hash_make(result_pool);
+
+ return svn_error_trace(svn_ra_serf__walk_all_props(
+ all_revprops, name, rev,
+ select_revprops, *revprops,
+ scratch_pool));
+}
+
+
+/*
+ * Contact the server (using CONN) to calculate baseline
+ * information for BASELINE_URL at REVISION (which may be
+ * SVN_INVALID_REVNUM to query the HEAD revision).
+ *
+ * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
+ * retrieved from the server as part of this process (which should
+ * match REVISION when REVISION is valid). Set *BASECOLL_URL_P to the
+ * baseline collection URL.
+ */
+static svn_error_t *
+retrieve_baseline_info(svn_revnum_t *actual_revision,
+ const char **basecoll_url_p,
+ svn_ra_serf__connection_t *conn,
+ const char *baseline_url,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *props;
+ apr_hash_t *dav_props;
+ const char *basecoll_url;
+
+ SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn,
+ baseline_url, revision,
+ baseline_props,
+ scratch_pool, scratch_pool));
+ dav_props = apr_hash_get(props, "DAV:", 4);
+ /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */
+
+ basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
+ if (!basecoll_url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include "
+ "the requested baseline-collection value"));
+ }
+ *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
+
+ if (actual_revision)
+ {
+ const char *version_name;
+
+ version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
+ if (!version_name)
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include "
+ "the requested version-name value"));
+
+ *actual_revision = SVN_STR_TO_REV(version_name);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
+ revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
+ collection URL is also returned.
+
+ Do the work over CONN.
+
+ *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
+ temporary allocations will be made in SCRATCH_POOL. */
+static svn_error_t *
+v1_get_youngest_revnum(svn_revnum_t *youngest,
+ const char **basecoll_url,
+ svn_ra_serf__connection_t *conn,
+ const char *vcc_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *baseline_url;
+ const char *bc_url;
+
+ /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
+ revision) will return the latest Baseline resource's URL. */
+ SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, conn, vcc_url,
+ SVN_INVALID_REVNUM,
+ "checked-in",
+ scratch_pool, scratch_pool));
+ if (!baseline_url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS response did not include "
+ "the requested checked-in value"));
+ }
+ baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
+
+ /* From the Baseline resource, we can fetch the DAV:baseline-collection
+ and DAV:version-name properties. The latter is the revision number,
+ which is formally the name used in Label: headers. */
+
+ /* First check baseline information cache. */
+ SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
+ youngest,
+ conn->session->blncache,
+ baseline_url,
+ scratch_pool));
+ if (!bc_url)
+ {
+ SVN_ERR(retrieve_baseline_info(youngest, &bc_url, conn,
+ baseline_url, SVN_INVALID_REVNUM,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_ra_serf__blncache_set(conn->session->blncache,
+ baseline_url, *youngest,
+ bc_url, scratch_pool));
+ }
+
+ if (basecoll_url != NULL)
+ *basecoll_url = apr_pstrdup(result_pool, bc_url);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__session_t *session,
+ apr_pool_t *scratch_pool)
+{
+ const char *vcc_url;
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
+ youngest, session->conns[0], scratch_pool));
+
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, scratch_pool));
+
+ return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
+ session->conns[0], vcc_url,
+ scratch_pool, scratch_pool));
+}
+
+
+/* Set *BC_URL to the baseline collection url for REVISION. If REVISION
+ is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
+
+ *REVNUM_USED will be set to the revision used.
+
+ Uses the specified CONN, which is part of SESSION.
+
+ All allocations (results and temporary) are performed in POOL. */
+static svn_error_t *
+get_baseline_info(const char **bc_url,
+ svn_revnum_t *revnum_used,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ /* If we detected HTTP v2 support on the server, we can construct
+ the baseline collection URL ourselves, and fetch the latest
+ revision (if needed) with an OPTIONS request. */
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ {
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ *revnum_used = revision;
+ }
+ else
+ {
+ SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
+ revnum_used, conn, pool));
+ if (! SVN_IS_VALID_REVNUM(*revnum_used))
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS response did not include "
+ "the youngest revision"));
+ }
+
+ *bc_url = apr_psprintf(pool, "%s/%ld",
+ session->rev_root_stub, *revnum_used);
+ }
+
+ /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */
+ else
+ {
+ const char *vcc_url;
+
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, conn, pool));
+
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ /* First check baseline information cache. */
+ SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
+ session->blncache,
+ revision, pool));
+ if (!*bc_url)
+ {
+ SVN_ERR(retrieve_baseline_info(NULL, bc_url, conn,
+ vcc_url, revision, pool, pool));
+ SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
+ revision, *bc_url, pool));
+ }
+
+ *revnum_used = revision;
+ }
+ else
+ {
+ SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
+ conn, vcc_url,
+ pool, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__get_stable_url(const char **stable_url,
+ svn_revnum_t *latest_revnum,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *basecoll_url;
+ const char *repos_relpath;
+ svn_revnum_t revnum_used;
+
+ /* No URL? No sweat. We'll use the session URL. */
+ if (! url)
+ url = session->session_url.path;
+
+ /* If the caller didn't provide a specific connection for us to use,
+ we'll use the default connection. */
+ if (! conn)
+ conn = session->conns[0];
+
+ SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
+ session, conn, revision, scratch_pool));
+ SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
+ session, conn, scratch_pool));
+
+ *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
+ result_pool);
+ if (latest_revnum)
+ *latest_revnum = revnum_used;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__get_resource_type(svn_node_kind_t *kind,
+ apr_hash_t *props)
+{
+ apr_hash_t *dav_props;
+ const char *res_type;
+
+ dav_props = apr_hash_get(props, "DAV:", 4);
+ res_type = svn_prop_get_value(dav_props, "resourcetype");
+ if (!res_type)
+ {
+ /* How did this happen? */
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include the "
+ "requested resourcetype value"));
+ }
+
+ if (strcmp(res_type, "collection") == 0)
+ {
+ *kind = svn_node_dir;
+ }
+ else
+ {
+ *kind = svn_node_file;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__fetch_dav_prop(const char **value,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *props;
+ apr_hash_t *dav_props;
+
+ SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn, url, revision,
+ checked_in_props,
+ scratch_pool, scratch_pool));
+ dav_props = apr_hash_get(props, "DAV:", 4);
+ if (dav_props == NULL)
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include "
+ "the requested 'DAV:' properties"));
+
+ /* We wouldn't get here if the resource was not found (404), so the
+ property should be present.
+
+ Note: it is okay to call apr_pstrdup() with NULL. */
+ *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/ra_serf.h b/contrib/subversion/subversion/libsvn_ra_serf/ra_serf.h
new file mode 100644
index 0000000..3f3f3de
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/ra_serf.h
@@ -0,0 +1,1785 @@
+/*
+ * ra_serf.h : Private declarations for the Serf-based DAV RA module.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_RA_SERF_RA_SERF_H
+#define SVN_LIBSVN_RA_SERF_RA_SERF_H
+
+
+#include <serf.h>
+#include <expat.h> /* for XML_Parser */
+#include <apr_uri.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_delta.h"
+#include "svn_version.h"
+#include "svn_dav.h"
+#include "svn_dirent_uri.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_editor.h"
+
+#include "blncache.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Enforce the minimum version of serf. */
+#if !SERF_VERSION_AT_LEAST(1, 2, 1)
+#error Please update your version of serf to at least 1.2.1.
+#endif
+
+/** Use this to silence compiler warnings about unused parameters. */
+#define UNUSED_CTX(x) ((void)(x))
+
+/** Our User-Agent string. */
+#define USER_AGENT "SVN/" SVN_VER_NUMBER " (" SVN_BUILD_TARGET ")" \
+ " serf/" \
+ APR_STRINGIFY(SERF_MAJOR_VERSION) "." \
+ APR_STRINGIFY(SERF_MINOR_VERSION) "." \
+ APR_STRINGIFY(SERF_PATCH_VERSION)
+
+/** Wait duration (in microseconds) used in calls to serf_context_run() */
+#define SVN_RA_SERF__CONTEXT_RUN_DURATION 500000
+
+
+
+/* Forward declarations. */
+typedef struct svn_ra_serf__session_t svn_ra_serf__session_t;
+
+/* A serf connection and optionally associated SSL context. */
+typedef struct svn_ra_serf__connection_t {
+ /* Our connection to a server. */
+ serf_connection_t *conn;
+
+ /* Bucket allocator for this connection. */
+ serf_bucket_alloc_t *bkt_alloc;
+
+ /* Collected cert failures in chain. */
+ int server_cert_failures;
+
+ /* What was the last HTTP status code we got on this connection? */
+ int last_status_code;
+
+ /* Optional SSL context for this connection. */
+ serf_ssl_context_t *ssl_context;
+ svn_auth_iterstate_t *ssl_client_auth_state;
+ svn_auth_iterstate_t *ssl_client_pw_auth_state;
+
+ svn_ra_serf__session_t *session;
+
+} svn_ra_serf__connection_t;
+
+/** Maximum value we'll allow for the http-max-connections config option.
+ *
+ * Note: minimum 2 connections are required for ra_serf to function
+ * correctly!
+ */
+#define SVN_RA_SERF__MAX_CONNECTIONS_LIMIT 8
+
+/*
+ * The master serf RA session.
+ *
+ * This is stored in the ra session ->priv field.
+ */
+struct svn_ra_serf__session_t {
+ /* Pool for allocations during this session */
+ apr_pool_t *pool;
+
+ /* The current context */
+ serf_context_t *context;
+
+ /* The maximum number of connections we'll use for parallelized
+ fetch operations (updates, etc.) */
+ apr_int64_t max_connections;
+
+ /* Are we using ssl */
+ svn_boolean_t using_ssl;
+
+ /* Should we ask for compressed responses? */
+ svn_boolean_t using_compression;
+
+ /* The user agent string */
+ const char *useragent;
+
+ /* The current connection */
+ svn_ra_serf__connection_t *conns[SVN_RA_SERF__MAX_CONNECTIONS_LIMIT];
+ int num_conns;
+ int cur_conn;
+
+ /* The URL that was passed into _open() */
+ apr_uri_t session_url;
+ const char *session_url_str;
+
+ /* The actual discovered root; may be NULL until we know it. */
+ apr_uri_t repos_root;
+ const char *repos_root_str;
+
+ /* The server is not Apache/mod_dav_svn (directly) and only supports
+ HTTP/1.0. Thus, we cannot send chunked requests. */
+ svn_boolean_t http10;
+
+ /* Our Version-Controlled-Configuration; may be NULL until we know it. */
+ const char *vcc_url;
+
+ /* Authentication related properties. */
+ svn_auth_iterstate_t *auth_state;
+ int auth_attempts;
+
+ /* Callback functions to get info from WC */
+ const svn_ra_callbacks2_t *wc_callbacks;
+ void *wc_callback_baton;
+
+ /* Callback function to send progress info to the client */
+ svn_ra_progress_notify_func_t progress_func;
+ void *progress_baton;
+
+ /* Callback function to handle cancellation */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ /* Ev2 shim callbacks */
+ svn_delta_shim_callbacks_t *shim_callbacks;
+
+ /* Error that we've received but not yet returned upstream. */
+ svn_error_t *pending_error;
+
+ /* List of authn types supported by the client.*/
+ int authn_types;
+
+ /* Maps SVN_RA_CAPABILITY_foo keys to "yes" or "no" values.
+ If a capability is not yet discovered, it is absent from the table.
+ The table itself is allocated in the svn_ra_serf__session_t's pool;
+ keys and values must have at least that lifetime. Most likely
+ the keys and values are constants anyway (and sufficiently
+ well-informed internal code may just compare against those
+ constants' addresses, therefore). */
+ apr_hash_t *capabilities;
+
+ /* Activity collection URL. (Cached from the initial OPTIONS
+ request when run against HTTPv1 servers.) */
+ const char *activity_collection_url;
+
+ /* Are we using a proxy? */
+ int using_proxy;
+
+ const char *proxy_username;
+ const char *proxy_password;
+ int proxy_auth_attempts;
+
+ /* SSL server certificates */
+ svn_boolean_t trust_default_ca;
+ const char *ssl_authorities;
+
+ /* Repository UUID */
+ const char *uuid;
+
+ /* Connection timeout value */
+ apr_interval_time_t timeout;
+
+ /* HTTPv1 flags */
+ svn_tristate_t supports_deadprop_count;
+
+ /*** HTTP v2 protocol stuff. ***
+ *
+ * We assume that if mod_dav_svn sends one of the special v2 OPTIONs
+ * response headers, it has sent all of them. Specifically, we'll
+ * be looking at the presence of the "me resource" as a flag that
+ * the server supports v2 of our HTTP protocol.
+ */
+
+ /* The "me resource". Typically used as a target for REPORTs that
+ are path-agnostic. If we have this, we can speak HTTP v2 to the
+ server. */
+ const char *me_resource;
+
+ /* Opaque URL "stubs". If the OPTIONS response returns these, then
+ we know we're using HTTP protocol v2. */
+ const char *rev_stub; /* for accessing revisions (i.e. revprops) */
+ const char *rev_root_stub; /* for accessing REV/PATH pairs */
+ const char *txn_stub; /* for accessing transactions (i.e. txnprops) */
+ const char *txn_root_stub; /* for accessing TXN/PATH pairs */
+ const char *vtxn_stub; /* for accessing transactions (i.e. txnprops) */
+ const char *vtxn_root_stub; /* for accessing TXN/PATH pairs */
+
+ /* Hash mapping const char * server-supported POST types to
+ disinteresting-but-non-null values. */
+ apr_hash_t *supported_posts;
+
+ /*** End HTTP v2 stuff ***/
+
+ svn_ra_serf__blncache_t *blncache;
+
+ /* Trisate flag that indicates user preference for using bulk updates
+ (svn_tristate_true) with all the properties and content in the
+ update-report response. If svn_tristate_false, request a skelta
+ update-report with inlined properties. If svn_tristate_unknown then use
+ server preference. */
+ svn_tristate_t bulk_updates;
+
+ /* Indicates if the server wants bulk update requests (Prefer) or only
+ accepts skelta requests (Off). If this value is On both options are
+ allowed. */
+ const char *server_allows_bulk;
+
+ /* Indicates if the server supports sending inlined props in update editor
+ * in skelta mode (send-all == 'false'). */
+ svn_boolean_t supports_inline_props;
+
+ /* Indicates whether the server supports issuing replay REPORTs
+ against rev resources (children of `rev_stub', elsestruct). */
+ svn_boolean_t supports_rev_rsrc_replay;
+};
+
+#define SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(sess) ((sess)->me_resource != NULL)
+
+/*
+ * Structure which represents a DAV element with a NAMESPACE and NAME.
+ */
+typedef struct svn_ra_serf__dav_props_t {
+ /* Element namespace */
+ const char *namespace;
+ /* Element name */
+ const char *name;
+} svn_ra_serf__dav_props_t;
+
+/*
+ * Structure which represents an XML namespace.
+ */
+typedef struct ns_t {
+ /* The assigned name. */
+ const char *namespace;
+ /* The full URL for this namespace. */
+ const char *url;
+ /* The next namespace in our list. */
+ struct ns_t *next;
+} svn_ra_serf__ns_t;
+
+/*
+ * An incredibly simple list.
+ */
+typedef struct ra_serf_list_t {
+ void *data;
+ struct ra_serf_list_t *next;
+} svn_ra_serf__list_t;
+
+/** DAV property sets **/
+
+static const svn_ra_serf__dav_props_t base_props[] =
+{
+ { "DAV:", "version-controlled-configuration" },
+ { "DAV:", "resourcetype" },
+ { SVN_DAV_PROP_NS_DAV, "baseline-relative-path" },
+ { SVN_DAV_PROP_NS_DAV, "repository-uuid" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t checked_in_props[] =
+{
+ { "DAV:", "checked-in" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t baseline_props[] =
+{
+ { "DAV:", "baseline-collection" },
+ { "DAV:", SVN_DAV__VERSION_NAME },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t all_props[] =
+{
+ { "DAV:", "allprop" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t check_path_props[] =
+{
+ { "DAV:", "resourcetype" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t type_and_checksum_props[] =
+{
+ { "DAV:", "resourcetype" },
+ { SVN_DAV_PROP_NS_DAV, "sha1-checksum" },
+ { NULL }
+};
+
+/* WC props compatibility with ra_neon. */
+#define SVN_RA_SERF__WC_CHECKED_IN_URL SVN_PROP_WC_PREFIX "ra_dav:version-url"
+
+/** Serf utility functions **/
+
+apr_status_t
+svn_ra_serf__conn_setup(apr_socket_t *sock,
+ serf_bucket_t **read_bkt,
+ serf_bucket_t **write_bkt,
+ void *baton,
+ apr_pool_t *pool);
+
+void
+svn_ra_serf__conn_closed(serf_connection_t *conn,
+ void *closed_baton,
+ apr_status_t why,
+ apr_pool_t *pool);
+
+
+/* Helper function to provide SSL client certificates.
+ *
+ * NOTE: This function sets the session's 'pending_error' member when
+ * returning an non-success status.
+ */
+apr_status_t
+svn_ra_serf__handle_client_cert(void *data,
+ const char **cert_path);
+
+/* Helper function to provide SSL client certificate passwords.
+ *
+ * NOTE: This function sets the session's 'pending_error' member when
+ * returning an non-success status.
+ */
+apr_status_t
+svn_ra_serf__handle_client_cert_pw(void *data,
+ const char *cert_path,
+ const char **password);
+
+
+/*
+ * This function will run the serf context in SESS until *DONE is TRUE.
+ */
+svn_error_t *
+svn_ra_serf__context_run_wait(svn_boolean_t *done,
+ svn_ra_serf__session_t *sess,
+ apr_pool_t *scratch_pool);
+
+/* Callback for response handlers */
+typedef svn_error_t *
+(*svn_ra_serf__response_handler_t)(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *scratch_pool);
+
+/* Callback for when a request body is needed. */
+/* ### should pass a scratch_pool */
+typedef svn_error_t *
+(*svn_ra_serf__request_body_delegate_t)(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *request_pool);
+
+/* Callback for when request headers are needed. */
+/* ### should pass a scratch_pool */
+typedef svn_error_t *
+(*svn_ra_serf__request_header_delegate_t)(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *request_pool);
+
+/* Callback for when a response has an error. */
+typedef svn_error_t *
+(*svn_ra_serf__response_error_t)(serf_request_t *request,
+ serf_bucket_t *response,
+ int status_code,
+ void *baton);
+
+/* ### we should reorder the types in this file. */
+typedef struct svn_ra_serf__server_error_t svn_ra_serf__server_error_t;
+
+/*
+ * Structure that can be passed to our default handler to guide the
+ * execution of the request through its lifecycle.
+ */
+typedef struct svn_ra_serf__handler_t {
+ /* The HTTP method string of the request */
+ const char *method;
+
+ /* The resource to the execute the method on. */
+ const char *path;
+
+ /* The content-type of the request body. */
+ const char *body_type;
+
+ /* If TRUE then default Accept-Encoding request header is not configured for
+ request. If FALSE then 'gzip' accept encoding will be used if compression
+ enabled. */
+ svn_boolean_t custom_accept_encoding;
+
+ /* Has the request/response been completed? */
+ svn_boolean_t done;
+
+ /* If we captured an error from the server, then this will be non-NULL.
+ It will be allocated from HANDLER_POOL. */
+ svn_ra_serf__server_error_t *server_error;
+
+ /* The handler and baton pair for our handler. */
+ svn_ra_serf__response_handler_t response_handler;
+ void *response_baton;
+
+ /* When REPONSE_HANDLER is invoked, the following fields will be set
+ based on the response header. HANDLER_POOL must be non-NULL for these
+ values to be filled in. SLINE.REASON and LOCATION will be allocated
+ within HANDLER_POOL. */
+ serf_status_line sline; /* The parsed Status-Line */
+ const char *location; /* The Location: header, if any */
+
+ /* The handler and baton pair to be executed when a non-recoverable error
+ * is detected. If it is NULL in the presence of an error, an abort() may
+ * be triggered.
+ */
+ svn_ra_serf__response_error_t response_error;
+ void *response_error_baton;
+
+ /* This function and baton pair allows for custom request headers to
+ * be set.
+ *
+ * It will be executed after the request has been set up but before it is
+ * delivered.
+ */
+ svn_ra_serf__request_header_delegate_t header_delegate;
+ void *header_delegate_baton;
+
+ /* This function and baton pair allows a body to be created right before
+ * delivery.
+ *
+ * It will be executed after the request has been set up but before it is
+ * delivered.
+ *
+ * May be NULL if there is no body to send.
+ *
+ */
+ svn_ra_serf__request_body_delegate_t body_delegate;
+ void *body_delegate_baton;
+
+ /* The connection and session to be used for this request. */
+ svn_ra_serf__connection_t *conn;
+ svn_ra_serf__session_t *session;
+
+ /* Internal flag to indicate we've parsed the headers. */
+ svn_boolean_t reading_body;
+
+ /* When this flag will be set, the core handler will discard any unread
+ portion of the response body. The registered response handler will
+ no longer be called. */
+ svn_boolean_t discard_body;
+
+ /* Pool for allocating SLINE.REASON and LOCATION. If this pool is NULL,
+ then the requestor does not care about SLINE and LOCATION. */
+ apr_pool_t *handler_pool;
+
+} svn_ra_serf__handler_t;
+
+
+/* Run one request and process the response.
+
+ Similar to context_run_wait(), but this creates the request for HANDLER
+ and then waits for it to complete.
+
+ WARNING: context_run_wait() does NOT create a request, whereas this
+ function DOES. Avoid a double-create. */
+svn_error_t *
+svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool);
+
+
+/*
+ * Helper function to queue a request in the @a handler's connection.
+ */
+void svn_ra_serf__request_create(svn_ra_serf__handler_t *handler);
+
+/* XML helper callbacks. */
+
+typedef struct svn_ra_serf__xml_state_t {
+ /* A numeric value that represents the current state in parsing.
+ *
+ * Value 0 is reserved for use as the default state.
+ */
+ int current_state;
+
+ /* Private pointer set by the parsing code. */
+ void *private;
+
+ /* Allocations should be made in this pool to match the lifetime of the
+ * state.
+ */
+ apr_pool_t *pool;
+
+ /* The currently-declared namespace for this state. */
+ svn_ra_serf__ns_t *ns_list;
+
+ /* Our previous states. */
+ struct svn_ra_serf__xml_state_t *prev;
+} svn_ra_serf__xml_state_t;
+
+/* Forward declaration of the XML parser structure. */
+typedef struct svn_ra_serf__xml_parser_t svn_ra_serf__xml_parser_t;
+
+/* Callback invoked with @a baton by our XML @a parser when an element with
+ * the @a name containing @a attrs is opened.
+ */
+typedef svn_error_t *
+(*svn_ra_serf__xml_start_element_t)(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool);
+
+/* Callback invoked with @a baton by our XML @a parser when an element with
+ * the @a name is closed.
+ */
+typedef svn_error_t *
+(*svn_ra_serf__xml_end_element_t)(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool);
+
+/* Callback invoked with @a baton by our XML @a parser when a CDATA portion
+ * of @a data with size @a len is encountered.
+ *
+ * This may be invoked multiple times for the same tag.
+ */
+typedef svn_error_t *
+(*svn_ra_serf__xml_cdata_chunk_handler_t)(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool);
+
+/*
+ * Helper structure associated with handle_xml_parser handler that will
+ * specify how an XML response will be processed.
+ */
+struct svn_ra_serf__xml_parser_t {
+ /* Temporary allocations should be made in this pool. */
+ apr_pool_t *pool;
+
+ /* What kind of response are we parsing? If set, this should typically
+ define the report name. */
+ const char *response_type;
+
+ /* Caller-specific data passed to the start, end, cdata callbacks. */
+ void *user_data;
+
+ /* Callback invoked when a tag is opened. */
+ svn_ra_serf__xml_start_element_t start;
+
+ /* Callback invoked when a tag is closed. */
+ svn_ra_serf__xml_end_element_t end;
+
+ /* Callback invoked when a cdata chunk is received. */
+ svn_ra_serf__xml_cdata_chunk_handler_t cdata;
+
+ /* Our associated expat-based XML parser. */
+ XML_Parser xmlp;
+
+ /* Our current state. */
+ svn_ra_serf__xml_state_t *state;
+
+ /* Our previously used states (will be reused). */
+ svn_ra_serf__xml_state_t *free_state;
+
+ /* If non-NULL, this value will be set to TRUE when the response is
+ * completed.
+ */
+ svn_boolean_t *done;
+
+ /* If non-NULL, when this parser completes, it will add done_item to
+ * the list.
+ */
+ svn_ra_serf__list_t **done_list;
+
+ /* A pointer to the item that will be inserted into the list upon
+ * completeion.
+ */
+ svn_ra_serf__list_t *done_item;
+
+ /* If this flag is TRUE, errors during parsing will be ignored.
+ *
+ * This is mainly used when we are processing an error XML response to
+ * avoid infinite loops.
+ */
+ svn_boolean_t ignore_errors;
+
+ /* If an error occurred, this value will be non-NULL. */
+ svn_error_t *error;
+
+ /* Deciding whether to pause, or not, is performed within the parsing
+ callbacks. If a callback decides to set this flag, then the loop
+ driving the parse (generally, a series of calls to serf_context_run())
+ is going to need to coordinate the un-pausing of the parser by
+ processing pending content. Thus, deciding to pause the parser is a
+ coordinate effort rather than merely setting this flag.
+
+ When an XML parsing callback sets this flag, note that additional
+ elements may be parsed (as the current buffer is consumed). At some
+ point, the flag will be recognized and arriving network content will
+ be stashed away in the PENDING structure (see below).
+
+ At some point, the controlling loop should clear this value. The
+ underlying network processing will note the change and begin passing
+ content into the XML callbacks.
+
+ Note that the controlling loop should also process pending content
+ since the arriving network content will typically finish first. */
+ svn_boolean_t paused;
+
+ /* While the XML parser is paused, content arriving from the server
+ must be saved locally. We cannot stop reading, or the server may
+ decide to drop the connection. The content will be stored in memory
+ up to a certain limit, and will then be spilled over to disk.
+
+ See libsvn_ra_serf/util.c */
+ struct svn_ra_serf__pending_t *pending;
+
+ /* Response restart support */
+ const void *headers_baton; /* Last pointer to headers */
+ apr_off_t skip_size; /* Number of bytes to skip */
+ apr_off_t read_size; /* Number of bytes read from response */
+};
+
+
+/* v2 of the XML parsing functions */
+
+/* The XML parsing context. */
+typedef struct svn_ra_serf__xml_context_t svn_ra_serf__xml_context_t;
+
+
+/* An opaque structure for the XML parse element/state. */
+typedef struct svn_ra_serf__xml_estate_t svn_ra_serf__xml_estate_t;
+
+/* Called just after the parser moves into ENTERED_STATE. The tag causing
+ the transition is passed in TAG.
+
+ This callback is applied to a parsing context by using the
+ svn_ra_serf__xml_context_customize() function.
+
+ NOTE: this callback, when set, will be invoked on *every* transition.
+ The callback must examine ENTERED_STATE to determine if any action
+ must be taken. The original state is not provided, but must be derived
+ from ENTERED_STATE and/or the TAG causing the transition (if needed). */
+typedef svn_error_t *
+(*svn_ra_serf__xml_opened_t)(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int entered_state,
+ const svn_ra_serf__dav_props_t *tag,
+ apr_pool_t *scratch_pool);
+
+
+/* Called just before the parser leaves LEAVING_STATE.
+
+ If cdata collection was enabled for this state, then CDATA will be
+ non-NULL and contain the collected cdata.
+
+ If attribute collection was enabled for this state, then ATTRS will
+ contain the attributes collected for this element only, along with
+ any values stored via svn_ra_serf__xml_note().
+
+ Use svn_ra_serf__xml_gather_since() to gather up data from outer states.
+
+ ATTRS is char* -> char*.
+
+ Temporary allocations may be made in SCRATCH_POOL. */
+typedef svn_error_t *
+(*svn_ra_serf__xml_closed_t)(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool);
+
+
+/* Called for all states that are not using the builtin cdata collection.
+ This callback is (only) appropriate for unbounded-size cdata content.
+
+ CURRENT_STATE may be used to decide what to do with the data.
+
+ Temporary allocations may be made in SCRATCH_POOL. */
+typedef svn_error_t *
+(*svn_ra_serf__xml_cdata_t)(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int current_state,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool);
+
+
+/* State transition table.
+
+ When the XML Context is constructed, it is in state 0. User states are
+ positive integers.
+
+ In a list of transitions, use { 0 } to indicate the end. Specifically,
+ the code looks for NS == NULL.
+
+ ### more docco
+*/
+typedef struct svn_ra_serf__xml_transition_t {
+ /* This transition applies when in this state */
+ int from_state;
+
+ /* And when this tag is observed */
+ const char *ns;
+ const char *name;
+
+ /* Moving to this state */
+ int to_state;
+
+ /* Should the cdata of NAME be collected? Note that CUSTOM_CLOSE should
+ be TRUE in order to capture this cdata. */
+ svn_boolean_t collect_cdata;
+
+ /* Which attributes of NAME should be collected? Terminate with NULL.
+ Maximum of 10 attributes may be collected. Note that attribute
+ namespaces are ignored at this time.
+
+ Attribute names beginning with "?" are optional. Other names must
+ exist on the element, or SVN_ERR_XML_ATTRIB_NOT_FOUND will be raised. */
+ const char *collect_attrs[11];
+
+ /* When NAME is closed, should the callback be invoked? */
+ svn_boolean_t custom_close;
+
+} svn_ra_serf__xml_transition_t;
+
+
+/* Construct an XML parsing context, based on the TTABLE transition table.
+ As content is parsed, the CLOSED_CB callback will be invoked according
+ to the definition in the table.
+
+ If OPENED_CB is not NULL, then it will be invoked for *every* tag-open
+ event. The callback will need to use the ENTERED_STATE and TAG parameters
+ to decide what it would like to do.
+
+ If CDATA_CB is not NULL, then it will be called for all cdata that is
+ not be automatically collected (based on the transition table record's
+ COLLECT_CDATA flag). It will be called in every state, so the callback
+ must examine the CURRENT_STATE parameter to decide what to do.
+
+ The same BATON value will be passed to all three callbacks.
+
+ The context will be created within RESULT_POOL. */
+svn_ra_serf__xml_context_t *
+svn_ra_serf__xml_context_create(
+ const svn_ra_serf__xml_transition_t *ttable,
+ svn_ra_serf__xml_opened_t opened_cb,
+ svn_ra_serf__xml_closed_t closed_cb,
+ svn_ra_serf__xml_cdata_t cdata_cb,
+ void *baton,
+ apr_pool_t *result_pool);
+
+/* Destroy all subpools for this structure. */
+void
+svn_ra_serf__xml_context_destroy(
+ svn_ra_serf__xml_context_t *xmlctx);
+
+/* Construct a handler with the response function/baton set up to parse
+ a response body using the given XML context. The handler and its
+ internal structures are allocated in RESULT_POOL.
+
+ This also initializes HANDLER_POOL to the given RESULT_POOL. */
+svn_ra_serf__handler_t *
+svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx,
+ apr_pool_t *result_pool);
+
+
+/* Allocated within XES->STATE_POOL. Changes are not allowd (callers
+ should make a deep copy if they need to make changes).
+
+ The resulting hash maps char* names to char* values. */
+apr_hash_t *
+svn_ra_serf__xml_gather_since(svn_ra_serf__xml_estate_t *xes,
+ int stop_state);
+
+
+/* Attach the NAME/VALUE pair onto this/parent state identified by STATE.
+ The name and value will be copied into the target state's pool.
+
+ These values will be available to the CLOSED_CB for the target state,
+ or part of the gathered state via xml_gather_since().
+
+ Typically, this function is used by a child state's close callback,
+ or within an opening callback to store additional data.
+
+ Note: if the state is not found, then a programmer error has occurred,
+ so the function will invoke SVN_ERR_MALFUNCTION(). */
+void
+svn_ra_serf__xml_note(svn_ra_serf__xml_estate_t *xes,
+ int state,
+ const char *name,
+ const char *value);
+
+
+/* Returns XES->STATE_POOL for allocating structures that should live
+ as long as the state identified by XES.
+
+ Note: a state pool is created upon demand, so only use this function
+ when memory is required for a given state. */
+apr_pool_t *
+svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t *xes);
+
+
+/* Any XML parser may be used. When an opening tag is seen, call this
+ function to feed the information into XMLCTX. */
+svn_error_t *
+svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name,
+ const char *const *attrs);
+
+
+/* When a close tag is seen, call this function to feed the information
+ into XMLCTX. */
+svn_error_t *
+svn_ra_serf__xml_cb_end(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name);
+
+
+/* When cdata is parsed by the wrapping XML parser, call this function to
+ feed the cdata into the XMLCTX. */
+svn_error_t *
+svn_ra_serf__xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx,
+ const char *data,
+ apr_size_t len);
+
+
+/*
+ * Parses a server-side error message into a local Subversion error.
+ */
+struct svn_ra_serf__server_error_t {
+ /* Our local representation of the error. */
+ svn_error_t *error;
+
+ /* Are we done with the response? */
+ svn_boolean_t done;
+
+ /* Have we seen an error tag? */
+ svn_boolean_t in_error;
+
+ /* Have we seen a HTTP "412 Precondition Failed" error? */
+ svn_boolean_t contains_precondition_error;
+
+ /* Should we be collecting the XML cdata? */
+ svn_boolean_t collect_cdata;
+
+ /* Collected cdata. NULL if cdata not needed. */
+ svn_stringbuf_t *cdata;
+
+ /* XML parser and namespace used to parse the remote response */
+ svn_ra_serf__xml_parser_t parser;
+};
+
+
+/*
+ * Handler that discards the entire @a response body associated with a
+ * @a request. Implements svn_ra_serf__response_handler_t.
+ *
+ * If @a baton is a svn_ra_serf__server_error_t (i.e. non-NULL) and an
+ * error is detected, it will be populated for later detection.
+ *
+ * All temporary allocations will be made in a @a pool.
+ */
+svn_error_t *
+svn_ra_serf__handle_discard_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/*
+ * Handler that retrieves the embedded XML multistatus response from the
+ * the @a RESPONSE body associated with a @a REQUEST.
+ *
+ * Implements svn_ra_serf__response_handler_t.
+ *
+ * The @a BATON should be of type svn_ra_serf__handler_t. When the request
+ * is complete, the handler's DONE flag will be set to TRUE.
+ *
+ * All temporary allocations will be made in a @a scratch_pool.
+ */
+svn_error_t *
+svn_ra_serf__handle_multistatus_only(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Handler that expects an empty body.
+
+ If a body IS present, and it is text/xml, then it will be parsed for
+ a server-side error.
+
+ BATON should be the svn_ra_serf__handler_t running REQUEST.
+
+ Status line information will be in HANDLER->SLINE.
+
+ Any parsed errors will be left in HANDLER->SERVER_ERROR. That member
+ may be NULL if no body was present, or a problem occurred trying to
+ parse the body.
+
+ All temporary allocations will be made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__expect_empty_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+
+/*
+ * This function will feed the RESPONSE body into XMLP. When parsing is
+ * completed (i.e. an EOF is received), *DONE is set to TRUE.
+ * Implements svn_ra_serf__response_handler_t.
+ *
+ * If an error occurs during processing RESP_ERR is invoked with the
+ * RESP_ERR_BATON.
+ *
+ * Temporary allocations are made in POOL.
+ */
+svn_error_t *
+svn_ra_serf__handle_xml_parser(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+/* serf_response_handler_t implementation that completely discards
+ * the response.
+ *
+ * All temporary allocations will be made in @a pool.
+ */
+apr_status_t
+svn_ra_serf__response_discard_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/** XML helper functions. **/
+
+/*
+ * Advance the internal XML @a parser to the @a state.
+ */
+void
+svn_ra_serf__xml_push_state(svn_ra_serf__xml_parser_t *parser,
+ int state);
+
+/*
+ * Return to the previous internal XML @a parser state.
+ */
+void
+svn_ra_serf__xml_pop_state(svn_ra_serf__xml_parser_t *parser);
+
+
+svn_error_t *
+svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
+ svn_boolean_t *network_eof,
+ apr_pool_t *scratch_pool);
+
+
+/*
+ * Add the appropriate serf buckets to @a agg_bucket represented by
+ * the XML * @a tag and @a value.
+ *
+ * The bucket will be allocated from @a bkt_alloc.
+ */
+void
+svn_ra_serf__add_tag_buckets(serf_bucket_t *agg_bucket,
+ const char *tag,
+ const char *value,
+ serf_bucket_alloc_t *bkt_alloc);
+
+/*
+ * Add the appropriate serf buckets to AGG_BUCKET with standard XML header:
+ * <?xml version="1.0" encoding="utf-8"?>
+ *
+ * The bucket will be allocated from BKT_ALLOC.
+ */
+void
+svn_ra_serf__add_xml_header_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc);
+
+/*
+ * Add the appropriate serf buckets to AGG_BUCKET representing the XML
+ * open tag with name TAG.
+ *
+ * Take the tag's attributes from varargs, a NULL-terminated list of
+ * alternating <tt>char *</tt> key and <tt>char *</tt> val. Attribute
+ * will be ignored if it's value is NULL.
+ *
+ * NOTE: Callers are responsible for XML-escaping attribute values as
+ * necessary.
+ *
+ * The bucket will be allocated from BKT_ALLOC.
+ */
+void
+svn_ra_serf__add_open_tag_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *tag,
+ ...);
+
+/*
+ * Add the appropriate serf buckets to AGG_BUCKET representing xml tag close
+ * with name TAG.
+ *
+ * The bucket will be allocated from BKT_ALLOC.
+ */
+void
+svn_ra_serf__add_close_tag_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *tag);
+
+/*
+ * Add the appropriate serf buckets to AGG_BUCKET with xml-escaped
+ * version of DATA.
+ *
+ * The bucket will be allocated from BKT_ALLOC.
+ */
+void
+svn_ra_serf__add_cdata_len_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *data, apr_size_t len);
+/*
+ * Look up the @a attrs array for namespace definitions and add each one
+ * to the @a ns_list of namespaces.
+ *
+ * New namespaces will be allocated in RESULT_POOL.
+ */
+void
+svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
+ const char *const *attrs,
+ apr_pool_t *result_pool);
+
+/*
+ * Look up @a name in the @a ns_list list for previously declared namespace
+ * definitions.
+ *
+ * Return (in @a *returned_prop_name) a #svn_ra_serf__dav_props_t tuple
+ * representing the expanded name.
+ */
+void
+svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
+ const svn_ra_serf__ns_t *ns_list,
+ const char *name);
+
+
+/** PROPFIND-related functions **/
+
+/*
+ * This function will deliver a PROP_CTX PROPFIND request in the SESS
+ * serf context for the properties listed in LOOKUP_PROPS at URL for
+ * DEPTH ("0","1","infinity").
+ *
+ * This function will not block waiting for the response. Callers are
+ * expected to call svn_ra_serf__wait_for_props().
+ */
+svn_error_t *
+svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler,
+ apr_hash_t *prop_vals,
+ svn_ra_serf__session_t *sess,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t rev,
+ const char *depth,
+ const svn_ra_serf__dav_props_t *lookup_props,
+ svn_ra_serf__list_t **done_list,
+ apr_pool_t *pool);
+
+/*
+ * This helper function will block until PROPFIND_HANDLER indicates that is
+ * done or another error is returned.
+ */
+svn_error_t *
+svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool);
+
+/* This is a blocking version of deliver_props.
+
+ The properties are fetched and placed into RESULTS, allocated in
+ RESULT_POOL.
+
+ ### more docco about the other params.
+
+ Temporary allocations are made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_ra_serf__retrieve_props(apr_hash_t **results,
+ svn_ra_serf__session_t *sess,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t rev,
+ const char *depth,
+ const svn_ra_serf__dav_props_t *props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Using CONN, fetch the properties specified by WHICH_PROPS using CONN
+ for URL at REVISION. The resulting properties are placed into a 2-level
+ hash in RESULTS, mapping NAMESPACE -> hash<PROPNAME, PROPVALUE>, which
+ is allocated in RESULT_POOL.
+
+ If REVISION is SVN_INVALID_REVNUM, then the properties are fetched
+ from HEAD for URL.
+
+ This function performs the request synchronously.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__fetch_node_props(apr_hash_t **results,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const svn_ra_serf__dav_props_t *which_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Using CONN, fetch a DAV: property from the resource identified by URL
+ within REVISION. The PROPNAME may be one of:
+
+ "checked-in"
+ "href"
+
+ The resulting value will be allocated in RESULT_POOL, and may be NULL
+ if the property does not exist (note: "href" always exists).
+
+ This function performs the request synchronously.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__fetch_dav_prop(const char **value,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set PROPS for PATH at REV revision with a NS:NAME VAL.
+ *
+ * The POOL governs allocation.
+ */
+void
+svn_ra_serf__set_ver_prop(apr_hash_t *props,
+ const char *path, svn_revnum_t rev,
+ const char *ns, const char *name,
+ const svn_string_t *val, apr_pool_t *pool);
+#define svn_ra_serf__set_rev_prop svn_ra_serf__set_ver_prop
+
+/** Property walker functions **/
+
+typedef svn_error_t *
+(*svn_ra_serf__walker_visitor_t)(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *pool);
+
+svn_error_t *
+svn_ra_serf__walk_all_props(apr_hash_t *props,
+ const char *name,
+ svn_revnum_t rev,
+ svn_ra_serf__walker_visitor_t walker,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/* Like walk_all_props(), but a 2-level hash. */
+svn_error_t *
+svn_ra_serf__walk_node_props(apr_hash_t *props,
+ svn_ra_serf__walker_visitor_t walker,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+
+typedef svn_error_t *
+(*svn_ra_serf__path_rev_walker_t)(void *baton,
+ const char *path, apr_ssize_t path_len,
+ const char *ns, apr_ssize_t ns_len,
+ const char *name, apr_ssize_t name_len,
+ const svn_string_t *val,
+ apr_pool_t *pool);
+svn_error_t *
+svn_ra_serf__walk_all_paths(apr_hash_t *props,
+ svn_revnum_t rev,
+ svn_ra_serf__path_rev_walker_t walker,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/* Map a property name, as passed over the wire, into its corresponding
+ Subversion-internal name. The returned name will be a static value,
+ or allocated within RESULT_POOL.
+
+ If the property should be ignored (eg. some DAV properties), then NULL
+ will be returned. */
+const char *
+svn_ra_serf__svnname_from_wirename(const char *ns,
+ const char *name,
+ apr_pool_t *result_pool);
+
+
+/* Select the basic revision properties from the set of "all" properties.
+ Return these in *REVPROPS, allocated from RESULT_POOL. */
+svn_error_t *
+svn_ra_serf__select_revprops(apr_hash_t **revprops,
+ const char *name,
+ svn_revnum_t rev,
+ apr_hash_t *all_revprops,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* PROPS is nested hash tables mapping NS -> NAME -> VALUE.
+ This function takes the NS:NAME:VALUE hashes and flattens them into a set of
+ names to VALUE. The names are composed of NS:NAME, with specific
+ rewrite from wire names (DAV) to SVN names. This mapping is managed
+ by the svn_ra_serf__set_baton_props() function.
+
+ FLAT_PROPS is allocated in RESULT_POOL.
+ ### right now, we do a shallow copy from PROPS to FLAT_PROPS. therefore,
+ ### the names and values in PROPS must be in the proper pool.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__flatten_props(apr_hash_t **flat_props,
+ apr_hash_t *props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Return the property value for PATH at REV revision with a NS:NAME.
+ * PROPS is a four-level nested hash: (svn_revnum_t => char *path =>
+ * char *ns => char *name => svn_string_t *). */
+const svn_string_t *
+svn_ra_serf__get_ver_prop_string(apr_hash_t *props,
+ const char *path, svn_revnum_t rev,
+ const char *ns, const char *name);
+
+/* Same as svn_ra_serf__get_ver_prop_string(), but returns a C string. */
+const char *
+svn_ra_serf__get_ver_prop(apr_hash_t *props,
+ const char *path, svn_revnum_t rev,
+ const char *ns, const char *name);
+
+/* Same as svn_ra_serf__get_ver_prop_string(), but for the unknown revision. */
+const svn_string_t *
+svn_ra_serf__get_prop_string(apr_hash_t *props,
+ const char *path,
+ const char *ns,
+ const char *name);
+
+/* Same as svn_ra_serf__get_ver_prop(), but for the unknown revision. */
+const char *
+svn_ra_serf__get_prop(apr_hash_t *props,
+ const char *path,
+ const char *ns,
+ const char *name);
+
+/* Same as svn_ra_serf__set_rev_prop(), but for the unknown revision. */
+void
+svn_ra_serf__set_prop(apr_hash_t *props, const char *path,
+ const char *ns, const char *name,
+ const svn_string_t *val, apr_pool_t *pool);
+
+svn_error_t *
+svn_ra_serf__get_resource_type(svn_node_kind_t *kind,
+ apr_hash_t *props);
+
+
+/** MERGE-related functions **/
+
+void
+svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens,
+ const char *parent,
+ serf_bucket_t *body,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool);
+
+/* Create an MERGE request aimed at the SESSION url, requesting the
+ merge of the resource identified by MERGE_RESOURCE_URL.
+ LOCK_TOKENS is a hash mapping paths to lock tokens owned by the
+ client. If KEEP_LOCKS is set, instruct the server to not release
+ locks set on the paths included in this commit. */
+svn_error_t *
+svn_ra_serf__run_merge(const svn_commit_info_t **commit_info,
+ int *response_code,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *merge_resource_url,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** OPTIONS-related functions **/
+
+/* On HTTPv2 connections, run an OPTIONS request over CONN to fetch the
+ current youngest revnum, returning it in *YOUNGEST.
+
+ (the revnum is headers of the OPTIONS response)
+
+ This function performs the request synchronously.
+
+ All temporary allocations will be made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *scratch_pool);
+
+
+/* On HTTPv1 connections, run an OPTIONS request over CONN to fetch the
+ activity collection set and return it in *ACTIVITY_URL, allocated
+ from RESULT_POOL.
+
+ (the activity-collection-set is in the body of the OPTIONS response)
+
+ This function performs the request synchronously.
+
+ All temporary allocations will be made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__v1_get_activity_collection(const char **activity_url,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set @a VCC_URL to the default VCC for our repository based on @a
+ * ORIG_PATH for the session @a SESSION, ensuring that the VCC URL and
+ * repository root URLs are cached in @a SESSION. Use @a CONN for any
+ * required network communications if it is non-NULL; otherwise use the
+ * default connection.
+ *
+ * All temporary allocations will be made in @a POOL. */
+svn_error_t *
+svn_ra_serf__discover_vcc(const char **vcc_url,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool);
+
+/* Set @a REPORT_TARGET to the URI of the resource at which generic
+ * (path-agnostic) REPORTs should be aimed for @a SESSION. Use @a
+ * CONN for any required network communications if it is non-NULL;
+ * otherwise use the default connection.
+ *
+ * All temporary allocations will be made in @a POOL.
+ */
+svn_error_t *
+svn_ra_serf__report_resource(const char **report_target,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool);
+
+/* Set @a REL_PATH to a path (not URI-encoded) relative to the root of
+ * the repository pointed to by @a SESSION, based on original path
+ * (URI-encoded) @a ORIG_PATH. Use @a CONN for any required network
+ * communications if it is non-NULL; otherwise use the default
+ * connection. Use POOL for allocations. */
+svn_error_t *
+svn_ra_serf__get_relative_path(const char **rel_path,
+ const char *orig_path,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool);
+
+
+/* Using the default connection in SESSION (conns[0]), get the youngest
+ revnum from the server, returning it in *YOUNGEST.
+
+ This function operates synchronously.
+
+ All temporary allocations are performed in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__session_t *session,
+ apr_pool_t *scratch_pool);
+
+
+/* Generate a revision-stable URL.
+
+ The RA APIs all refer to user/public URLs that float along with the
+ youngest revision. In many cases, we do NOT want to work with that URL
+ since it can change from one moment to the next. Especially if we
+ attempt to operation against multiple floating URLs -- we could end up
+ referring to two separate revisions.
+
+ The DAV RA provider(s) solve this by generating a URL that is specific
+ to a revision by using a URL into a "baseline collection".
+
+ For a specified SESSION, with an optional CONN (if NULL, then the
+ session's default connection will be used; specifically SESSION->conns[0]),
+ generate a revision-stable URL for URL at REVISION. If REVISION is
+ SVN_INVALID_REVNUM, then the stable URL will refer to the youngest
+ revision at the time this function was called.
+
+ If URL is NULL, then the session root will be used.
+
+ The stable URL will be placed into *STABLE_URL, allocated from RESULT_POOL.
+
+ If LATEST_REVNUM is not NULL, then the revision used will be placed into
+ *LATEST_REVNUM. That will be equal to youngest, or the given REVISION.
+
+ This function operates synchronously, if any communication to the server
+ is required. Communication is needed if REVISION is SVN_INVALID_REVNUM
+ (to get the current youngest revnum), or if the specified REVISION is not
+ (yet) in our cache of baseline collections.
+
+ All temporary allocations are performed in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__get_stable_url(const char **stable_url,
+ svn_revnum_t *latest_revnum,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** RA functions **/
+
+/* Implements svn_ra__vtable_t.get_log(). */
+svn_error_t *
+svn_ra_serf__get_log(svn_ra_session_t *session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_locations(). */
+svn_error_t *
+svn_ra_serf__get_locations(svn_ra_session_t *session,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_location_segments(). */
+svn_error_t *
+svn_ra_serf__get_location_segments(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.do_diff(). */
+svn_error_t *
+svn_ra_serf__do_diff(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.do_status(). */
+svn_error_t *
+svn_ra_serf__do_status(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.do_update(). */
+svn_error_t *
+svn_ra_serf__do_update(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Implements svn_ra__vtable_t.do_switch(). */
+svn_error_t *
+svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_depth_t depth,
+ const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Implements svn_ra__vtable_t.get_file_revs(). */
+svn_error_t *
+svn_ra_serf__get_file_revs(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_dated_revision(). */
+svn_error_t *
+svn_ra_serf__get_dated_revision(svn_ra_session_t *session,
+ svn_revnum_t *revision,
+ apr_time_t tm,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_commit_editor(). */
+svn_error_t *
+svn_ra_serf__get_commit_editor(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_file(). */
+svn_error_t *
+svn_ra_serf__get_file(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.change_rev_prop(). */
+svn_error_t *
+svn_ra_serf__change_rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.replay(). */
+svn_error_t *
+svn_ra_serf__replay(svn_ra_session_t *ra_session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t text_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.replay_range(). */
+svn_error_t *
+svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.lock(). */
+svn_error_t *
+svn_ra_serf__lock(svn_ra_session_t *ra_session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.unlock(). */
+svn_error_t *
+svn_ra_serf__unlock(svn_ra_session_t *ra_session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_lock(). */
+svn_error_t *
+svn_ra_serf__get_lock(svn_ra_session_t *ra_session,
+ svn_lock_t **lock,
+ const char *path,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_locks(). */
+svn_error_t *
+svn_ra_serf__get_locks(svn_ra_session_t *ra_session,
+ apr_hash_t **locks,
+ const char *path,
+ svn_depth_t depth,
+ apr_pool_t *pool);
+
+/* Request a mergeinfo-report from the URL attached to SESSION,
+ and fill in the MERGEINFO hash with the results.
+
+ Implements svn_ra__vtable_t.get_mergeinfo().
+ */
+svn_error_t *
+svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session,
+ apr_hash_t **mergeinfo,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool);
+
+/* Exchange capabilities with the server, by sending an OPTIONS
+ * request announcing the client's capabilities, and by filling
+ * SERF_SESS->capabilities with the server's capabilities as read from
+ * the response headers. Use POOL only for temporary allocation.
+ *
+ * If the CORRECTED_URL is non-NULL, allow the OPTIONS response to
+ * report a server-dictated redirect or relocation (HTTP 301 or 302
+ * error codes), setting *CORRECTED_URL to the value of the corrected
+ * repository URL. Otherwise, such responses from the server will
+ * generate an error. (In either case, no capabilities are exchanged
+ * if there is, in fact, such a response from the server.)
+ */
+svn_error_t *
+svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
+ const char **corrected_url,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.has_capability(). */
+svn_error_t *
+svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_deleted_rev(). */
+svn_error_t *
+svn_ra_serf__get_deleted_rev(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool);
+
+/* Implements the get_inherited_props RA layer function. */
+svn_error_t * svn_ra_serf__get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Implements svn_ra__vtable_t.get_repos_root(). */
+svn_error_t *
+svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
+ const char **url,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.register_editor_shim_callbacks(). */
+svn_error_t *
+svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *session,
+ svn_delta_shim_callbacks_t *callbacks);
+
+/*** Authentication handler declarations ***/
+
+/**
+ * Callback function that loads the credentials for Basic and Digest
+ * authentications, both for server and proxy authentication.
+ */
+apr_status_t
+svn_ra_serf__credentials_callback(char **username, char **password,
+ serf_request_t *request, void *baton,
+ int code, const char *authn_type,
+ const char *realm,
+ apr_pool_t *pool);
+
+
+/*** General utility functions ***/
+
+/**
+ * Convert an HTTP STATUS_CODE resulting from a WebDAV request against
+ * PATH to the relevant error code. Use the response-supplied LOCATION
+ * where it necessary.
+ */
+svn_error_t *
+svn_ra_serf__error_on_status(int status_code,
+ const char *path,
+ const char *location);
+
+/* ###? */
+svn_error_t *
+svn_ra_serf__copy_into_spillbuf(svn_spillbuf_t **spillbuf,
+ serf_bucket_t *bkt,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* ###? */
+serf_bucket_t *
+svn_ra_serf__create_sb_bucket(svn_spillbuf_t *spillbuf,
+ serf_bucket_alloc_t *allocator,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Wrap STATUS from an serf function. If STATUS is not serf error code,
+ * this is equivalent to svn_error_wrap_apr().
+ */
+svn_error_t *
+svn_ra_serf__wrap_err(apr_status_t status,
+ const char *fmt,
+ ...);
+
+
+#if defined(SVN_DEBUG)
+/* Wrapper macros to collect file and line information */
+#define svn_ra_serf__wrap_err \
+ (svn_error__locate(__FILE__,__LINE__), (svn_ra_serf__wrap_err))
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_RA_SERF_RA_SERF_H */
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/replay.c b/contrib/subversion/subversion/libsvn_ra_serf/replay.c
new file mode 100644
index 0000000..1fcecf4
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/replay.c
@@ -0,0 +1,920 @@
+/*
+ * replay.c : entry point for replay RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_base64.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+
+#include "private/svn_string_private.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing.
+ */
+typedef enum replay_state_e {
+ NONE = 0,
+ REPORT,
+ OPEN_DIR,
+ ADD_DIR,
+ OPEN_FILE,
+ ADD_FILE,
+ DELETE_ENTRY,
+ APPLY_TEXTDELTA,
+ CHANGE_PROP
+} replay_state_e;
+
+typedef struct replay_info_t replay_info_t;
+
+struct replay_info_t {
+ apr_pool_t *pool;
+
+ void *baton;
+ svn_stream_t *stream;
+
+ replay_info_t *parent;
+};
+
+typedef svn_error_t *
+(*change_prop_t)(void *baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+typedef struct prop_info_t {
+ apr_pool_t *pool;
+
+ change_prop_t change;
+
+ const char *name;
+ svn_boolean_t del_prop;
+
+ svn_stringbuf_t *prop_value;
+
+ replay_info_t *parent;
+} prop_info_t;
+
+typedef struct replay_context_t {
+ apr_pool_t *src_rev_pool;
+ apr_pool_t *dst_rev_pool;
+ /*file_pool is cleared after completion of each file. */
+ apr_pool_t *file_pool;
+
+ /* Are we done fetching this file? */
+ svn_boolean_t done;
+ svn_ra_serf__list_t **done_list;
+ svn_ra_serf__list_t done_item;
+
+ /* callback to get an editor */
+ svn_ra_replay_revstart_callback_t revstart_func;
+ svn_ra_replay_revfinish_callback_t revfinish_func;
+ void *replay_baton;
+
+ /* replay receiver function and baton */
+ const svn_delta_editor_t *editor;
+ void *editor_baton;
+
+ /* Path and revision used to filter replayed changes. If
+ INCLUDE_PATH is non-NULL, REVISION is unnecessary and will not be
+ included in the replay REPORT. (Because the REPORT is being
+ aimed an HTTP v2 revision resource.) */
+ const char *include_path;
+ svn_revnum_t revision;
+
+ /* Information needed to create the replay report body */
+ svn_revnum_t low_water_mark;
+ svn_boolean_t send_deltas;
+
+ /* Target and revision to fetch revision properties on */
+ const char *revprop_target;
+ svn_revnum_t revprop_rev;
+
+ /* Revision properties for this revision. */
+ apr_hash_t *revs_props;
+ apr_hash_t *props;
+
+ /* Keep a reference to the XML parser ctx to report any errors. */
+ svn_ra_serf__xml_parser_t *parser_ctx;
+
+ /* Handlers for the PROPFIND and REPORT for the current revision. */
+ svn_ra_serf__handler_t *propfind_handler;
+ svn_ra_serf__handler_t *report_handler;
+
+} replay_context_t;
+
+
+static void *
+push_state(svn_ra_serf__xml_parser_t *parser,
+ replay_context_t *replay_ctx,
+ replay_state_e state)
+{
+ svn_ra_serf__xml_push_state(parser, state);
+
+ if (state == OPEN_DIR || state == ADD_DIR ||
+ state == OPEN_FILE || state == ADD_FILE)
+ {
+ replay_info_t *info;
+
+ info = apr_palloc(replay_ctx->dst_rev_pool, sizeof(*info));
+
+ info->pool = replay_ctx->dst_rev_pool;
+ info->parent = parser->state->private;
+ info->baton = NULL;
+ info->stream = NULL;
+
+ parser->state->private = info;
+ }
+ else if (state == CHANGE_PROP)
+ {
+ prop_info_t *info;
+
+ info = apr_pcalloc(replay_ctx->dst_rev_pool, sizeof(*info));
+
+ info->pool = replay_ctx->dst_rev_pool;
+ info->parent = parser->state->private;
+ info->prop_value = svn_stringbuf_create_empty(info->pool);
+
+ parser->state->private = info;
+ }
+
+ return parser->state->private;
+}
+
+static svn_error_t *
+start_replay(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ replay_context_t *ctx = parser->user_data;
+ replay_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE &&
+ strcmp(name.name, "editor-report") == 0)
+ {
+ push_state(parser, ctx, REPORT);
+
+ /* Before we can continue, we need the revision properties. */
+ SVN_ERR_ASSERT(!ctx->propfind_handler || ctx->propfind_handler->done);
+
+ /* Create a pool for the commit editor. */
+ ctx->dst_rev_pool = svn_pool_create(ctx->src_rev_pool);
+ ctx->file_pool = svn_pool_create(ctx->dst_rev_pool);
+
+ SVN_ERR(svn_ra_serf__select_revprops(&ctx->props,
+ ctx->revprop_target,
+ ctx->revprop_rev,
+ ctx->revs_props,
+ ctx->dst_rev_pool,
+ scratch_pool));
+
+ if (ctx->revstart_func)
+ {
+ SVN_ERR(ctx->revstart_func(ctx->revision, ctx->replay_baton,
+ &ctx->editor, &ctx->editor_baton,
+ ctx->props,
+ ctx->dst_rev_pool));
+ }
+ }
+ else if (state == REPORT &&
+ strcmp(name.name, "target-revision") == 0)
+ {
+ const char *rev;
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+ if (!rev)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in target-revision element"));
+ }
+
+ SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
+ SVN_STR_TO_REV(rev),
+ scratch_pool));
+ }
+ else if (state == REPORT &&
+ strcmp(name.name, "open-root") == 0)
+ {
+ const char *rev;
+ replay_info_t *info;
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+
+ if (!rev)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-root element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_DIR);
+
+ SVN_ERR(ctx->editor->open_root(ctx->editor_baton,
+ SVN_STR_TO_REV(rev),
+ ctx->dst_rev_pool,
+ &info->baton));
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "delete-entry") == 0)
+ {
+ const char *file_name, *rev;
+ replay_info_t *info;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+ if (!file_name)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in delete-entry element"));
+ }
+ rev = svn_xml_get_attr_value("rev", attrs);
+ if (!rev)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in delete-entry element"));
+ }
+
+ info = push_state(parser, ctx, DELETE_ENTRY);
+
+ SVN_ERR(ctx->editor->delete_entry(file_name, SVN_STR_TO_REV(rev),
+ info->baton, scratch_pool));
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "open-directory") == 0)
+ {
+ const char *rev, *dir_name;
+ replay_info_t *info;
+
+ dir_name = svn_xml_get_attr_value("name", attrs);
+ if (!dir_name)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in open-directory element"));
+ }
+ rev = svn_xml_get_attr_value("rev", attrs);
+ if (!rev)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-directory element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_DIR);
+
+ SVN_ERR(ctx->editor->open_directory(dir_name, info->parent->baton,
+ SVN_STR_TO_REV(rev),
+ ctx->dst_rev_pool, &info->baton));
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "add-directory") == 0)
+ {
+ const char *dir_name, *copyfrom, *copyrev;
+ svn_revnum_t rev;
+ replay_info_t *info;
+
+ dir_name = svn_xml_get_attr_value("name", attrs);
+ if (!dir_name)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in add-directory element"));
+ }
+ copyfrom = svn_xml_get_attr_value("copyfrom-path", attrs);
+ copyrev = svn_xml_get_attr_value("copyfrom-rev", attrs);
+
+ if (copyrev)
+ rev = SVN_STR_TO_REV(copyrev);
+ else
+ rev = SVN_INVALID_REVNUM;
+
+ info = push_state(parser, ctx, ADD_DIR);
+
+ SVN_ERR(ctx->editor->add_directory(dir_name, info->parent->baton,
+ copyfrom, rev,
+ ctx->dst_rev_pool, &info->baton));
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "close-directory") == 0)
+ {
+ replay_info_t *info = parser->state->private;
+
+ SVN_ERR(ctx->editor->close_directory(info->baton, scratch_pool));
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "open-file") == 0)
+ {
+ const char *file_name, *rev;
+ replay_info_t *info;
+
+ svn_pool_clear(ctx->file_pool);
+ file_name = svn_xml_get_attr_value("name", attrs);
+ if (!file_name)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in open-file element"));
+ }
+ rev = svn_xml_get_attr_value("rev", attrs);
+ if (!rev)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-file element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_FILE);
+
+ SVN_ERR(ctx->editor->open_file(file_name, info->parent->baton,
+ SVN_STR_TO_REV(rev),
+ ctx->file_pool, &info->baton));
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "add-file") == 0)
+ {
+ const char *file_name, *copyfrom, *copyrev;
+ svn_revnum_t rev;
+ replay_info_t *info;
+
+ svn_pool_clear(ctx->file_pool);
+ file_name = svn_xml_get_attr_value("name", attrs);
+ if (!file_name)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in add-file element"));
+ }
+ copyfrom = svn_xml_get_attr_value("copyfrom-path", attrs);
+ copyrev = svn_xml_get_attr_value("copyfrom-rev", attrs);
+
+ info = push_state(parser, ctx, ADD_FILE);
+
+ if (copyrev)
+ rev = SVN_STR_TO_REV(copyrev);
+ else
+ rev = SVN_INVALID_REVNUM;
+
+ SVN_ERR(ctx->editor->add_file(file_name, info->parent->baton,
+ copyfrom, rev,
+ ctx->file_pool, &info->baton));
+ }
+ else if ((state == OPEN_FILE || state == ADD_FILE) &&
+ strcmp(name.name, "apply-textdelta") == 0)
+ {
+ const char *checksum;
+ replay_info_t *info;
+ svn_txdelta_window_handler_t textdelta;
+ void *textdelta_baton;
+ svn_stream_t *delta_stream;
+
+ info = push_state(parser, ctx, APPLY_TEXTDELTA);
+
+ checksum = svn_xml_get_attr_value("checksum", attrs);
+ if (checksum)
+ {
+ checksum = apr_pstrdup(info->pool, checksum);
+ }
+
+ SVN_ERR(ctx->editor->apply_textdelta(info->baton, checksum,
+ ctx->file_pool,
+ &textdelta,
+ &textdelta_baton));
+
+ delta_stream = svn_txdelta_parse_svndiff(textdelta, textdelta_baton,
+ TRUE, info->pool);
+ info->stream = svn_base64_decode(delta_stream, info->pool);
+ }
+ else if ((state == OPEN_FILE || state == ADD_FILE) &&
+ strcmp(name.name, "close-file") == 0)
+ {
+ replay_info_t *info = parser->state->private;
+ const char *checksum;
+
+ checksum = svn_xml_get_attr_value("checksum", attrs);
+
+ SVN_ERR(ctx->editor->close_file(info->baton, checksum, scratch_pool));
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (((state == OPEN_FILE || state == ADD_FILE) &&
+ strcmp(name.name, "change-file-prop") == 0) ||
+ ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "change-dir-prop") == 0))
+ {
+ const char *prop_name;
+ prop_info_t *info;
+
+ prop_name = svn_xml_get_attr_value("name", attrs);
+ if (!prop_name)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in %s element"),
+ name.name);
+ }
+
+ info = push_state(parser, ctx, CHANGE_PROP);
+
+
+ if (svn_xml_get_attr_value("del", attrs))
+ info->del_prop = TRUE;
+ else
+ info->del_prop = FALSE;
+
+ if (state == OPEN_FILE || state == ADD_FILE)
+ {
+ info->name = apr_pstrdup(ctx->file_pool, prop_name);
+ info->change = ctx->editor->change_file_prop;
+ }
+ else
+ {
+ info->name = apr_pstrdup(ctx->dst_rev_pool, prop_name);
+ info->change = ctx->editor->change_dir_prop;
+ }
+
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_replay(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ replay_context_t *ctx = parser->user_data;
+ replay_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == REPORT &&
+ strcmp(name.name, "editor-report") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ if (ctx->revfinish_func)
+ {
+ SVN_ERR(ctx->revfinish_func(ctx->revision, ctx->replay_baton,
+ ctx->editor, ctx->editor_baton,
+ ctx->props,
+ ctx->dst_rev_pool));
+ }
+ svn_pool_destroy(ctx->dst_rev_pool);
+ }
+ else if (state == OPEN_DIR && strcmp(name.name, "open-directory") == 0)
+ {
+ /* Don't do anything. */
+ }
+ else if (state == ADD_DIR && strcmp(name.name, "add-directory") == 0)
+ {
+ /* Don't do anything. */
+ }
+ else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
+ {
+ /* Don't do anything. */
+ }
+ else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
+ {
+ /* Don't do anything. */
+ }
+ else if ((state == OPEN_FILE || state == ADD_FILE) &&
+ strcmp(name.name, "close-file") == 0)
+ {
+ /* Don't do anything. */
+ }
+ else if ((state == APPLY_TEXTDELTA) &&
+ strcmp(name.name, "apply-textdelta") == 0)
+ {
+ replay_info_t *info = parser->state->private;
+ SVN_ERR(svn_stream_close(info->stream));
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == CHANGE_PROP &&
+ (strcmp(name.name, "change-file-prop") == 0 ||
+ strcmp(name.name, "change-dir-prop") == 0))
+ {
+ prop_info_t *info = parser->state->private;
+ const svn_string_t *prop_val;
+
+ if (info->del_prop)
+ {
+ prop_val = NULL;
+ }
+ else
+ {
+ const svn_string_t *morph;
+
+ morph = svn_stringbuf__morph_into_string(info->prop_value);
+#ifdef SVN_DEBUG
+ info->prop_value = NULL; /* morph killed the stringbuf. */
+#endif
+
+ if (strcmp(name.name, "change-file-prop") == 0)
+ prop_val = svn_base64_decode_string(morph, ctx->file_pool);
+ else
+ prop_val = svn_base64_decode_string(morph, ctx->dst_rev_pool);
+ }
+
+ SVN_ERR(info->change(info->parent->baton, info->name, prop_val,
+ info->parent->pool));
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_replay(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ replay_context_t *replay_ctx = parser->user_data;
+ replay_state_e state;
+
+ UNUSED_CTX(replay_ctx);
+
+ state = parser->state->current_state;
+
+ if (state == APPLY_TEXTDELTA)
+ {
+ replay_info_t *info = parser->state->private;
+ apr_size_t written;
+
+ written = len;
+
+ SVN_ERR(svn_stream_write(info->stream, data, &written));
+
+ if (written != len)
+ return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
+ _("Error writing stream: unexpected EOF"));
+ }
+ else if (state == CHANGE_PROP)
+ {
+ prop_info_t *info = parser->state->private;
+
+ svn_stringbuf_appendbytes(info->prop_value, data, len);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+create_replay_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ replay_context_t *ctx = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "S:replay-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ /* If we have a non-NULL include path, we add it to the body and
+ omit the revision; otherwise, the reverse. */
+ if (ctx->include_path)
+ {
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:include-path",
+ ctx->include_path,
+ alloc);
+ }
+ else
+ {
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:revision",
+ apr_ltoa(ctx->src_rev_pool, ctx->revision),
+ alloc);
+ }
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:low-water-mark",
+ apr_ltoa(ctx->src_rev_pool, ctx->low_water_mark),
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:send-deltas",
+ apr_ltoa(ctx->src_rev_pool, ctx->send_deltas),
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "S:replay-report");
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__replay(svn_ra_session_t *ra_session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool)
+{
+ replay_context_t *replay_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+ svn_error_t *err;
+ const char *report_target;
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
+
+ replay_ctx = apr_pcalloc(pool, sizeof(*replay_ctx));
+ replay_ctx->src_rev_pool = pool;
+ replay_ctx->editor = editor;
+ replay_ctx->editor_baton = edit_baton;
+ replay_ctx->done = FALSE;
+ replay_ctx->revision = revision;
+ replay_ctx->low_water_mark = low_water_mark;
+ replay_ctx->send_deltas = send_deltas;
+ replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool);
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ handler->handler_pool = pool;
+ handler->method = "REPORT";
+ handler->path = session->session_url.path;
+ handler->body_delegate = create_replay_body;
+ handler->body_delegate_baton = replay_ctx;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ parser_ctx->user_data = replay_ctx;
+ parser_ctx->start = start_replay;
+ parser_ctx->end = end_replay;
+ parser_ctx->cdata = cdata_replay;
+ parser_ctx->done = &replay_ctx->done;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ /* This is only needed to handle errors during XML parsing. */
+ replay_ctx->parser_ctx = parser_ctx;
+ replay_ctx->report_handler = handler; /* unused */
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool);
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location),
+ err));
+
+ return SVN_NO_ERROR;
+}
+
+/* The maximum number of outstanding requests at any time. When this
+ * number is reached, ra_serf will stop sending requests until
+ * responses on the previous requests are received and handled.
+ *
+ * Some observations about serf which lead us to the current value.
+ * ----------------------------------------------------------------
+ *
+ * We aim to keep serf's outgoing queue filled with enough requests so
+ * the network bandwidth and server capacity is used
+ * optimally. Originally we used 5 as the max. number of outstanding
+ * requests, but this turned out to be too low.
+ *
+ * Serf doesn't exit out of the svn_ra_serf__context_run_wait loop as long as
+ * it has data to send or receive. With small responses (revs of a few
+ * kB), serf doesn't come out of this loop at all. So with
+ * MAX_OUTSTANDING_REQUESTS set to a low number, there's a big chance
+ * that serf handles those requests completely in its internal loop,
+ * and only then gives us a chance to create new requests. This
+ * results in hiccups, slowing down the whole process.
+ *
+ * With a larger MAX_OUTSTANDING_REQUESTS, like 100 or more, there's
+ * more chance that serf can come out of its internal loop so we can
+ * replenish the outgoing request queue. There's no real disadvantage
+ * of using a large number here, besides the memory used to store the
+ * message, parser and handler objects (approx. 250 bytes).
+ *
+ * In my test setup peak performance was reached at max. 30-35
+ * requests. So I added a small margin and chose 50.
+ */
+#define MAX_OUTSTANDING_REQUESTS 50
+
+svn_error_t *
+svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_revnum_t rev = start_revision;
+ const char *report_target;
+ int active_reports = 0;
+ const char *include_path;
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
+
+ /* Prior to 1.8, mod_dav_svn expect to get replay REPORT requests
+ aimed at the session URL. But that's incorrect -- these reports
+ aren't about specific resources -- they are above revisions. The
+ path-based filtering offered by this API is just that: a filter
+ applied to the full set of changes made in the revision. As
+ such, the correct target for these REPORT requests is the "me
+ resource" (or, pre-http-v2, the default VCC).
+
+ Our server should have told us if it supported this protocol
+ correction. If so, we aimed our report at the correct resource
+ and include the filtering path as metadata within the report
+ body. Otherwise, we fall back to the pre-1.8 behavior and just
+ wish for the best.
+
+ See issue #4287:
+ http://subversion.tigris.org/issues/show_bug.cgi?id=4287
+ */
+ if (session->supports_rev_rsrc_replay)
+ {
+ SVN_ERR(svn_ra_serf__get_relative_path(&include_path,
+ session->session_url.path,
+ session, session->conns[0],
+ pool));
+ }
+ else
+ {
+ include_path = NULL;
+ }
+
+ while (active_reports || rev <= end_revision)
+ {
+ svn_ra_serf__list_t *done_list;
+ svn_ra_serf__list_t *done_reports = NULL;
+ replay_context_t *replay_ctx;
+
+ if (session->cancel_func)
+ SVN_ERR(session->cancel_func(session->cancel_baton));
+
+ /* Send pending requests, if any. Limit the number of outstanding
+ requests to MAX_OUTSTANDING_REQUESTS. */
+ if (rev <= end_revision && active_reports < MAX_OUTSTANDING_REQUESTS)
+ {
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+ apr_pool_t *ctx_pool = svn_pool_create(pool);
+ const char *replay_target;
+
+ replay_ctx = apr_pcalloc(ctx_pool, sizeof(*replay_ctx));
+ replay_ctx->src_rev_pool = ctx_pool;
+ replay_ctx->revstart_func = revstart_func;
+ replay_ctx->revfinish_func = revfinish_func;
+ replay_ctx->replay_baton = replay_baton;
+ replay_ctx->done = FALSE;
+ replay_ctx->include_path = include_path;
+ replay_ctx->revision = rev;
+ replay_ctx->low_water_mark = low_water_mark;
+ replay_ctx->send_deltas = send_deltas;
+ replay_ctx->done_item.data = replay_ctx;
+
+ /* Request all properties of a certain revision. */
+ replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool);
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ {
+ replay_ctx->revprop_target = apr_psprintf(pool, "%s/%ld",
+ session->rev_stub, rev);
+ replay_ctx->revprop_rev = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ replay_ctx->revprop_target = report_target;
+ replay_ctx->revprop_rev = rev;
+ }
+
+ SVN_ERR(svn_ra_serf__deliver_props(&replay_ctx->propfind_handler,
+ replay_ctx->revs_props, session,
+ session->conns[0],
+ replay_ctx->revprop_target,
+ replay_ctx->revprop_rev,
+ "0", all_props,
+ NULL,
+ replay_ctx->src_rev_pool));
+
+ /* Spin up the serf request for the PROPFIND. */
+ svn_ra_serf__request_create(replay_ctx->propfind_handler);
+
+ /* Send the replay REPORT request. */
+ if (session->supports_rev_rsrc_replay)
+ {
+ replay_target = apr_psprintf(pool, "%s/%ld",
+ session->rev_stub, rev);
+ }
+ else
+ {
+ replay_target = session->session_url.path;
+ }
+
+ handler = apr_pcalloc(replay_ctx->src_rev_pool, sizeof(*handler));
+
+ handler->handler_pool = replay_ctx->src_rev_pool;
+ handler->method = "REPORT";
+ handler->path = replay_target;
+ handler->body_delegate = create_replay_body;
+ handler->body_delegate_baton = replay_ctx;
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ parser_ctx = apr_pcalloc(replay_ctx->src_rev_pool,
+ sizeof(*parser_ctx));
+
+ /* Setup the XML parser context.
+ Because we have not one but a list of requests, the 'done' property
+ on the replay_ctx is not of much use. Instead, use 'done_list'.
+ On each handled response (succesfully or not), the parser will add
+ done_item to done_list, so by keeping track of the state of
+ done_list we know how many requests have been handled completely.
+ */
+ parser_ctx->pool = replay_ctx->src_rev_pool;
+ parser_ctx->user_data = replay_ctx;
+ parser_ctx->start = start_replay;
+ parser_ctx->end = end_replay;
+ parser_ctx->cdata = cdata_replay;
+ parser_ctx->done = &replay_ctx->done;
+ parser_ctx->done_list = &done_reports;
+ parser_ctx->done_item = &replay_ctx->done_item;
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+ replay_ctx->report_handler = handler;
+
+ /* This is only needed to handle errors during XML parsing. */
+ replay_ctx->parser_ctx = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ rev++;
+ active_reports++;
+ }
+
+ /* Run the serf loop. */
+ SVN_ERR(svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool));
+
+ /* Substract the number of completely handled responses from our
+ total nr. of open requests', so we'll know when to stop this loop.
+ Since the message is completely handled, we can destroy its pool. */
+ done_list = done_reports;
+ while (done_list)
+ {
+ replay_context_t *ctx = (replay_context_t *)done_list->data;
+ svn_ra_serf__handler_t *done_handler = ctx->report_handler;
+
+ done_list = done_list->next;
+ SVN_ERR(svn_ra_serf__error_on_status(done_handler->sline.code,
+ done_handler->path,
+ done_handler->location));
+ svn_pool_destroy(ctx->src_rev_pool);
+ active_reports--;
+ }
+
+ done_reports = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+#undef MAX_OUTSTANDING_REQUESTS
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/sb_bucket.c b/contrib/subversion/subversion/libsvn_ra_serf/sb_bucket.c
new file mode 100644
index 0000000..df0541f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/sb_bucket.c
@@ -0,0 +1,185 @@
+/*
+ * sb_bucket.c : a serf bucket that wraps a spillbuf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <serf.h>
+#include <serf_bucket_util.h>
+
+#include "svn_private_config.h"
+#include "private/svn_subr_private.h"
+
+#include "ra_serf.h"
+
+#define SB_BLOCKSIZE 1024
+#define SB_MAXSIZE 32768
+
+
+struct sbb_baton
+{
+ svn_spillbuf_t *spillbuf;
+
+ const char *holding;
+ apr_size_t hold_len;
+
+ apr_pool_t *scratch_pool;
+};
+
+
+svn_error_t *
+svn_ra_serf__copy_into_spillbuf(svn_spillbuf_t **spillbuf,
+ serf_bucket_t *bkt,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *spillbuf = svn_spillbuf__create(SB_BLOCKSIZE, SB_MAXSIZE, result_pool);
+
+ /* Copy all data from the bucket into the spillbuf. */
+ while (TRUE)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(bkt, SERF_READ_ALL_AVAIL, &data, &len);
+
+ if (status != APR_SUCCESS && status != APR_EOF)
+ return svn_ra_serf__wrap_err(status, _("Failed to read the request"));
+
+ SVN_ERR(svn_spillbuf__write(*spillbuf, data, len, scratch_pool));
+
+ if (status == APR_EOF)
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static apr_status_t
+sb_bucket_read(serf_bucket_t *bucket, apr_size_t requested,
+ const char **data, apr_size_t *len)
+{
+ struct sbb_baton *sbb = bucket->data;
+ svn_error_t *err;
+
+ if (sbb->holding)
+ {
+ *data = sbb->holding;
+
+ if (requested < sbb->hold_len)
+ {
+ *len = requested;
+ sbb->holding += requested;
+ sbb->hold_len -= requested;
+ return APR_SUCCESS;
+ }
+
+ /* Return whatever we're holding, and then forget (consume) it. */
+ *len = sbb->hold_len;
+ sbb->holding = NULL;
+ return APR_SUCCESS;
+ }
+
+ err = svn_spillbuf__read(data, len, sbb->spillbuf, sbb->scratch_pool);
+ svn_pool_clear(sbb->scratch_pool);
+
+ /* ### do something with this */
+ svn_error_clear(err);
+
+ /* The spillbuf may have returned more than requested. Stash any extra
+ into our holding area. */
+ if (requested < *len)
+ {
+ sbb->holding = *data + requested;
+ sbb->hold_len = *len - requested;
+ *len = requested;
+ }
+
+ return *data == NULL ? APR_EOF : APR_SUCCESS;
+}
+
+
+static apr_status_t
+sb_bucket_readline(serf_bucket_t *bucket, int acceptable,
+ int *found,
+ const char **data, apr_size_t *len)
+{
+ /* ### for now, we know callers won't use this function. */
+ svn_error_clear(svn_error__malfunction(TRUE, __FILE__, __LINE__,
+ "Not implemented."));
+ return APR_ENOTIMPL;
+}
+
+
+static apr_status_t
+sb_bucket_peek(serf_bucket_t *bucket,
+ const char **data, apr_size_t *len)
+{
+ struct sbb_baton *sbb = bucket->data;
+ svn_error_t *err;
+
+ /* If we're not holding any data, then fill it. */
+ if (sbb->holding == NULL)
+ {
+ err = svn_spillbuf__read(&sbb->holding, &sbb->hold_len, sbb->spillbuf,
+ sbb->scratch_pool);
+ svn_pool_clear(sbb->scratch_pool);
+
+ /* ### do something with this */
+ svn_error_clear(err);
+ }
+
+ /* Return the data we are (now) holding. */
+ *data = sbb->holding;
+ *len = sbb->hold_len;
+
+ return *data == NULL ? APR_EOF : APR_SUCCESS;
+}
+
+
+static const serf_bucket_type_t sb_bucket_vtable = {
+ "SPILLBUF",
+ sb_bucket_read,
+ sb_bucket_readline,
+ serf_default_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_default_read_bucket,
+ sb_bucket_peek,
+ serf_default_destroy_and_data,
+};
+
+
+serf_bucket_t *
+svn_ra_serf__create_sb_bucket(svn_spillbuf_t *spillbuf,
+ serf_bucket_alloc_t *allocator,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct sbb_baton *sbb;
+
+ sbb = serf_bucket_mem_alloc(allocator, sizeof(*sbb));
+ sbb->spillbuf = spillbuf;
+ sbb->holding = NULL;
+ sbb->scratch_pool = svn_pool_create(result_pool);
+
+ return serf_bucket_create(&sb_bucket_vtable, allocator, sbb);
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/serf.c b/contrib/subversion/subversion/libsvn_ra_serf/serf.c
new file mode 100644
index 0000000..6016157
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/serf.c
@@ -0,0 +1,1246 @@
+/*
+ * serf.c : entry point for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_time.h"
+#include "svn_version.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+
+
+/* Implements svn_ra__vtable_t.get_version(). */
+static const svn_version_t *
+ra_serf_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+#define RA_SERF_DESCRIPTION \
+ N_("Module for accessing a repository via WebDAV protocol using serf.")
+
+/* Implements svn_ra__vtable_t.get_description(). */
+static const char *
+ra_serf_get_description(void)
+{
+ return _(RA_SERF_DESCRIPTION);
+}
+
+/* Implements svn_ra__vtable_t.get_schemes(). */
+static const char * const *
+ra_serf_get_schemes(apr_pool_t *pool)
+{
+ static const char *serf_ssl[] = { "http", "https", NULL };
+#if 0
+ /* ### Temporary: to shut up a warning. */
+ static const char *serf_no_ssl[] = { "http", NULL };
+#endif
+
+ /* TODO: Runtime detection. */
+ return serf_ssl;
+}
+
+/* Load the setting http-auth-types from the global or server specific
+ section, parse its value and set the types of authentication we should
+ accept from the server. */
+static svn_error_t *
+load_http_auth_types(apr_pool_t *pool, svn_config_t *config,
+ const char *server_group,
+ int *authn_types)
+{
+ const char *http_auth_types = NULL;
+ *authn_types = SERF_AUTHN_NONE;
+
+ svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
+
+ if (server_group)
+ {
+ svn_config_get(config, &http_auth_types, server_group,
+ SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types);
+ }
+
+ if (http_auth_types)
+ {
+ char *token;
+ char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
+ apr_collapse_spaces(auth_types_list, http_auth_types);
+ while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL)
+ {
+ if (svn_cstring_casecmp("basic", token) == 0)
+ *authn_types |= SERF_AUTHN_BASIC;
+ else if (svn_cstring_casecmp("digest", token) == 0)
+ *authn_types |= SERF_AUTHN_DIGEST;
+ else if (svn_cstring_casecmp("ntlm", token) == 0)
+ *authn_types |= SERF_AUTHN_NTLM;
+ else if (svn_cstring_casecmp("negotiate", token) == 0)
+ *authn_types |= SERF_AUTHN_NEGOTIATE;
+ else
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: unknown %s "
+ "'%s'"),
+ SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token);
+ }
+ }
+ else
+ {
+ /* Nothing specified by the user, so accept all types. */
+ *authn_types = SERF_AUTHN_ALL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Default HTTP timeout (in seconds); overridden by the 'http-timeout'
+ runtime configuration variable. */
+#define DEFAULT_HTTP_TIMEOUT 600
+
+static svn_error_t *
+load_config(svn_ra_serf__session_t *session,
+ apr_hash_t *config_hash,
+ apr_pool_t *pool)
+{
+ svn_config_t *config, *config_client;
+ const char *server_group;
+ const char *proxy_host = NULL;
+ const char *port_str = NULL;
+ const char *timeout_str = NULL;
+ const char *exceptions;
+ apr_port_t proxy_port;
+
+ if (config_hash)
+ {
+ config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS);
+ config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
+ }
+ else
+ {
+ config = NULL;
+ config_client = NULL;
+ }
+
+ SVN_ERR(svn_config_get_bool(config, &session->using_compression,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
+ svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
+
+ if (session->wc_callbacks->auth_baton)
+ {
+ if (config_client)
+ {
+ svn_auth_set_parameter(session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
+ config_client);
+ }
+ if (config)
+ {
+ svn_auth_set_parameter(session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS,
+ config);
+ }
+ }
+
+ /* Use the default proxy-specific settings if and only if
+ "http-proxy-exceptions" is not set to exclude this host. */
+ svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
+ if (! svn_cstring_match_glob_list(session->session_url.hostname,
+ svn_cstring_split(exceptions, ",",
+ TRUE, pool)))
+ {
+ svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
+ svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
+ svn_config_get(config, &session->proxy_username,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
+ svn_config_get(config, &session->proxy_password,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
+ }
+
+ /* Load the global ssl settings, if set. */
+ SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
+ TRUE));
+ svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
+
+ /* If set, read the flag that tells us to do bulk updates or not. Defaults
+ to skelta updates. */
+ SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
+ "auto",
+ svn_tristate_unknown));
+
+ /* Load the maximum number of parallel session connections. */
+ SVN_ERR(svn_config_get_int64(config, &session->max_connections,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
+ SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS));
+
+ if (config)
+ server_group = svn_config_find_group(config,
+ session->session_url.hostname,
+ SVN_CONFIG_SECTION_GROUPS, pool);
+ else
+ server_group = NULL;
+
+ if (server_group)
+ {
+ SVN_ERR(svn_config_get_bool(config, &session->using_compression,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_COMPRESSION,
+ session->using_compression));
+ svn_config_get(config, &timeout_str, server_group,
+ SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
+
+ svn_auth_set_parameter(session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SERVER_GROUP, server_group);
+
+ /* Load the group proxy server settings, overriding global
+ settings. We intentionally ignore 'http-proxy-exceptions'
+ here because, well, if this site was an exception, why is
+ there a per-server proxy configuration for it? */
+ svn_config_get(config, &proxy_host, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host);
+ svn_config_get(config, &port_str, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
+ svn_config_get(config, &session->proxy_username, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME,
+ session->proxy_username);
+ svn_config_get(config, &session->proxy_password, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD,
+ session->proxy_password);
+
+ /* Load the group ssl settings. */
+ SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
+ server_group,
+ SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
+ session->trust_default_ca));
+ svn_config_get(config, &session->ssl_authorities, server_group,
+ SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
+ session->ssl_authorities);
+
+ /* Load the group bulk updates flag. */
+ SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
+ "auto",
+ session->bulk_updates));
+
+ /* Load the maximum number of parallel session connections,
+ overriding global values. */
+ SVN_ERR(svn_config_get_int64(config, &session->max_connections,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
+ session->max_connections));
+ }
+
+ /* Don't allow the http-max-connections value to be larger than our
+ compiled-in limit, or to be too small to operate. Broken
+ functionality and angry administrators are equally undesirable. */
+ if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT)
+ session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT;
+ if (session->max_connections < 2)
+ session->max_connections = 2;
+
+ /* Parse the connection timeout value, if any. */
+ session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
+ if (timeout_str)
+ {
+ char *endstr;
+ const long int timeout = strtol(timeout_str, &endstr, 10);
+
+ if (*endstr)
+ return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: illegal character in "
+ "timeout value"));
+ if (timeout < 0)
+ return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: negative timeout value"));
+ session->timeout = apr_time_from_sec(timeout);
+ }
+ SVN_ERR_ASSERT(session->timeout >= 0);
+
+ /* Convert the proxy port value, if any. */
+ if (port_str)
+ {
+ char *endstr;
+ const long int port = strtol(port_str, &endstr, 10);
+
+ if (*endstr)
+ return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Invalid URL: illegal character in proxy "
+ "port number"));
+ if (port < 0)
+ return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Invalid URL: negative proxy port number"));
+ if (port > 65535)
+ return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Invalid URL: proxy port number greater "
+ "than maximum TCP port number 65535"));
+ proxy_port = (apr_port_t) port;
+ }
+ else
+ {
+ proxy_port = 80;
+ }
+
+ if (proxy_host)
+ {
+ apr_sockaddr_t *proxy_addr;
+ apr_status_t status;
+
+ status = apr_sockaddr_info_get(&proxy_addr, proxy_host,
+ APR_UNSPEC, proxy_port, 0,
+ session->pool);
+ if (status)
+ {
+ return svn_ra_serf__wrap_err(
+ status, _("Could not resolve proxy server '%s'"),
+ proxy_host);
+ }
+ session->using_proxy = TRUE;
+ serf_config_proxy(session->context, proxy_addr);
+ }
+ else
+ {
+ session->using_proxy = FALSE;
+ }
+
+ /* Setup authentication. */
+ SVN_ERR(load_http_auth_types(pool, config, server_group,
+ &session->authn_types));
+ serf_config_authn_types(session->context, session->authn_types);
+ serf_config_credentials_callback(session->context,
+ svn_ra_serf__credentials_callback);
+
+ return SVN_NO_ERROR;
+}
+#undef DEFAULT_HTTP_TIMEOUT
+
+static void
+svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
+{
+ const svn_ra_serf__session_t *serf_sess = progress_baton;
+ if (serf_sess->progress_func)
+ {
+ serf_sess->progress_func(read + written, -1,
+ serf_sess->progress_baton,
+ serf_sess->pool);
+ }
+}
+
+/* Implements svn_ra__vtable_t.open_session(). */
+static svn_error_t *
+svn_ra_serf__open(svn_ra_session_t *session,
+ const char **corrected_url,
+ const char *session_URL,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ svn_ra_serf__session_t *serf_sess;
+ apr_uri_t url;
+ const char *client_string = NULL;
+
+ if (corrected_url)
+ *corrected_url = NULL;
+
+ serf_sess = apr_pcalloc(pool, sizeof(*serf_sess));
+ serf_sess->pool = svn_pool_create(pool);
+ serf_sess->wc_callbacks = callbacks;
+ serf_sess->wc_callback_baton = callback_baton;
+ serf_sess->progress_func = callbacks->progress_func;
+ serf_sess->progress_baton = callbacks->progress_baton;
+ serf_sess->cancel_func = callbacks->cancel_func;
+ serf_sess->cancel_baton = callback_baton;
+
+ /* todo: reuse serf context across sessions */
+ serf_sess->context = serf_context_create(serf_sess->pool);
+
+ SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache,
+ serf_sess->pool));
+
+
+ status = apr_uri_parse(serf_sess->pool, session_URL, &url);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal URL '%s'"),
+ session_URL);
+ }
+ /* Depending the version of apr-util in use, for root paths url.path
+ will be NULL or "", where serf requires "/". */
+ if (url.path == NULL || url.path[0] == '\0')
+ {
+ url.path = apr_pstrdup(serf_sess->pool, "/");
+ }
+ if (!url.port)
+ {
+ url.port = apr_uri_port_of_scheme(url.scheme);
+ }
+ serf_sess->session_url = url;
+ serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL);
+ serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0);
+
+ serf_sess->supports_deadprop_count = svn_tristate_unknown;
+
+ serf_sess->capabilities = apr_hash_make(serf_sess->pool);
+
+ /* We have to assume that the server only supports HTTP/1.0. Once it's clear
+ HTTP/1.1 is supported, we can upgrade. */
+ serf_sess->http10 = TRUE;
+
+ SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
+
+ serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
+ sizeof(*serf_sess->conns[0]));
+ serf_sess->conns[0]->bkt_alloc =
+ serf_bucket_allocator_create(serf_sess->pool, NULL, NULL);
+ serf_sess->conns[0]->session = serf_sess;
+ serf_sess->conns[0]->last_status_code = -1;
+
+ /* create the user agent string */
+ if (callbacks->get_client_string)
+ SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool));
+
+ if (client_string)
+ serf_sess->useragent = apr_pstrcat(pool, USER_AGENT, " ",
+ client_string, (char *)NULL);
+ else
+ serf_sess->useragent = USER_AGENT;
+
+ /* go ahead and tell serf about the connection. */
+ status =
+ serf_connection_create2(&serf_sess->conns[0]->conn,
+ serf_sess->context,
+ url,
+ svn_ra_serf__conn_setup, serf_sess->conns[0],
+ svn_ra_serf__conn_closed, serf_sess->conns[0],
+ serf_sess->pool);
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+
+ /* Set the progress callback. */
+ serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
+ serf_sess);
+
+ serf_sess->num_conns = 1;
+
+ session->priv = serf_sess;
+
+ return svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool);
+}
+
+/* Implements svn_ra__vtable_t.reparent(). */
+static svn_error_t *
+svn_ra_serf__reparent(svn_ra_session_t *ra_session,
+ const char *url,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_uri_t new_url;
+ apr_status_t status;
+
+ /* If it's the URL we already have, wave our hands and do nothing. */
+ if (strcmp(session->session_url_str, url) == 0)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ if (!session->repos_root_str)
+ {
+ const char *vcc_url;
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
+ }
+
+ if (!svn_uri__is_ancestor(session->repos_root_str, url))
+ {
+ return svn_error_createf(
+ SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' is not a child of the session's repository root "
+ "URL '%s'"), url, session->repos_root_str);
+ }
+
+ status = apr_uri_parse(pool, url, &new_url);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal repository URL '%s'"), url);
+ }
+
+ /* Depending the version of apr-util in use, for root paths url.path
+ will be NULL or "", where serf requires "/". */
+ /* ### Maybe we should use a string buffer for these strings so we
+ ### don't allocate memory in the session on every reparent? */
+ if (new_url.path == NULL || new_url.path[0] == '\0')
+ {
+ session->session_url.path = apr_pstrdup(session->pool, "/");
+ }
+ else
+ {
+ session->session_url.path = apr_pstrdup(session->pool, new_url.path);
+ }
+ session->session_url_str = apr_pstrdup(session->pool, url);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.get_session_url(). */
+static svn_error_t *
+svn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ *url = apr_pstrdup(pool, session->session_url_str);
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.get_latest_revnum(). */
+static svn_error_t *
+svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
+ svn_revnum_t *latest_revnum,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ return svn_error_trace(svn_ra_serf__get_youngest_revnum(
+ latest_revnum, session, pool));
+}
+
+/* Implements svn_ra__vtable_t.rev_proplist(). */
+static svn_error_t *
+svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
+ svn_revnum_t rev,
+ apr_hash_t **ret_props,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_t *props;
+ const char *propfind_path;
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ {
+ propfind_path = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
+
+ /* svn_ra_serf__retrieve_props() wants to added the revision as
+ a Label to the PROPFIND, which isn't really necessary when
+ querying a rev-stub URI. *Shrug* Probably okay to leave the
+ Label, but whatever. */
+ rev = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ /* Use the VCC as the propfind target path. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool));
+ }
+
+ /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash. */
+ SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
+ propfind_path, rev, "0", all_props,
+ pool, pool));
+
+ SVN_ERR(svn_ra_serf__select_revprops(ret_props, propfind_path, rev, props,
+ pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.rev_prop(). */
+static svn_error_t *
+svn_ra_serf__rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ svn_string_t **value,
+ apr_pool_t *pool)
+{
+ apr_hash_t *props;
+
+ SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool));
+
+ *value = svn_hash_gets(props, name);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_path_props(apr_hash_t **props,
+ svn_ra_serf__session_t *session,
+ const char *session_relpath,
+ svn_revnum_t revision,
+ const svn_ra_serf__dav_props_t *desired_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *url;
+
+ url = session->session_url.path;
+
+ /* If we have a relative path, append it. */
+ if (session_relpath)
+ url = svn_path_url_add_component2(url, session_relpath, scratch_pool);
+
+ /* If we were given a specific revision, get a URL that refers to that
+ specific revision (rather than floating with HEAD). */
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ url, revision,
+ scratch_pool, scratch_pool));
+ }
+
+ /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
+ Or we started with SVN_INVALID_REVNUM and URL may be floating. */
+ SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0],
+ url, SVN_INVALID_REVNUM,
+ desired_props,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.check_path(). */
+static svn_error_t *
+svn_ra_serf__check_path(svn_ra_session_t *ra_session,
+ const char *rel_path,
+ svn_revnum_t revision,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_t *props;
+
+ svn_error_t *err = fetch_path_props(&props, session, rel_path,
+ revision, check_path_props,
+ pool, pool);
+
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *kind = svn_node_none;
+ }
+ else
+ {
+ /* Any other error, raise to caller. */
+ if (err)
+ return svn_error_trace(err);
+
+ SVN_ERR(svn_ra_serf__get_resource_type(kind, props));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct dirent_walker_baton_t {
+ /* Update the fields in this entry. */
+ svn_dirent_t *entry;
+
+ svn_tristate_t *supports_deadprop_count;
+
+ /* If allocations are necessary, then use this pool. */
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+dirent_walker(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ struct dirent_walker_baton_t *dwb = baton;
+
+ if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
+ {
+ dwb->entry->has_props = TRUE;
+ }
+ else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
+ {
+ dwb->entry->has_props = TRUE;
+ }
+ else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
+ {
+ if(strcmp(name, "deadprop-count") == 0)
+ {
+ if (*val->data)
+ {
+ apr_int64_t deadprop_count;
+ SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
+ dwb->entry->has_props = deadprop_count > 0;
+ if (dwb->supports_deadprop_count)
+ *dwb->supports_deadprop_count = svn_tristate_true;
+ }
+ else if (dwb->supports_deadprop_count)
+ *dwb->supports_deadprop_count = svn_tristate_false;
+ }
+ }
+ else if (strcmp(ns, "DAV:") == 0)
+ {
+ if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
+ {
+ dwb->entry->created_rev = SVN_STR_TO_REV(val->data);
+ }
+ else if (strcmp(name, "creator-displayname") == 0)
+ {
+ dwb->entry->last_author = val->data;
+ }
+ else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
+ {
+ SVN_ERR(svn_time_from_cstring(&dwb->entry->time,
+ val->data,
+ dwb->result_pool));
+ }
+ else if (strcmp(name, "getcontentlength") == 0)
+ {
+ /* 'getcontentlength' property is empty for directories. */
+ if (val->len)
+ {
+ SVN_ERR(svn_cstring_atoi64(&dwb->entry->size, val->data));
+ }
+ }
+ else if (strcmp(name, "resourcetype") == 0)
+ {
+ if (strcmp(val->data, "collection") == 0)
+ {
+ dwb->entry->kind = svn_node_dir;
+ }
+ else
+ {
+ dwb->entry->kind = svn_node_file;
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct path_dirent_visitor_t {
+ apr_hash_t *full_paths;
+ apr_hash_t *base_paths;
+ const char *orig_path;
+ svn_tristate_t supports_deadprop_count;
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+path_dirent_walker(void *baton,
+ const char *path, apr_ssize_t path_len,
+ const char *ns, apr_ssize_t ns_len,
+ const char *name, apr_ssize_t name_len,
+ const svn_string_t *val,
+ apr_pool_t *pool)
+{
+ struct path_dirent_visitor_t *dirents = baton;
+ struct dirent_walker_baton_t dwb;
+ svn_dirent_t *entry;
+
+ /* Skip our original path. */
+ if (strcmp(path, dirents->orig_path) == 0)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ entry = apr_hash_get(dirents->full_paths, path, path_len);
+
+ if (!entry)
+ {
+ const char *base_name;
+
+ entry = svn_dirent_create(pool);
+
+ apr_hash_set(dirents->full_paths, path, path_len, entry);
+
+ base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool),
+ pool);
+
+ svn_hash_sets(dirents->base_paths, base_name, entry);
+ }
+
+ dwb.entry = entry;
+ dwb.supports_deadprop_count = &dirents->supports_deadprop_count;
+ dwb.result_pool = dirents->result_pool;
+ return svn_error_trace(dirent_walker(&dwb, ns, name, val, pool));
+}
+
+static const svn_ra_serf__dav_props_t *
+get_dirent_props(apr_uint32_t dirent_fields,
+ svn_ra_serf__session_t *session,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__dav_props_t *prop;
+ apr_array_header_t *props = apr_array_make
+ (pool, 7, sizeof(svn_ra_serf__dav_props_t));
+
+ if (session->supports_deadprop_count != svn_tristate_false
+ || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
+ {
+ if (dirent_fields & SVN_DIRENT_KIND)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "resourcetype";
+ }
+
+ if (dirent_fields & SVN_DIRENT_SIZE)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "getcontentlength";
+ }
+
+ if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = SVN_DAV_PROP_NS_DAV;
+ prop->name = "deadprop-count";
+ }
+
+ if (dirent_fields & SVN_DIRENT_CREATED_REV)
+ {
+ svn_ra_serf__dav_props_t *p = apr_array_push(props);
+ p->namespace = "DAV:";
+ p->name = SVN_DAV__VERSION_NAME;
+ }
+
+ if (dirent_fields & SVN_DIRENT_TIME)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = SVN_DAV__CREATIONDATE;
+ }
+
+ if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "creator-displayname";
+ }
+ }
+ else
+ {
+ /* We found an old subversion server that can't handle
+ the deadprop-count property in the way we expect.
+
+ The neon behavior is to retrieve all properties in this case */
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "allprop";
+ }
+
+ prop = apr_array_push(props);
+ prop->namespace = NULL;
+ prop->name = NULL;
+
+ return (svn_ra_serf__dav_props_t *) props->elts;
+}
+
+/* Implements svn_ra__vtable_t.stat(). */
+static svn_error_t *
+svn_ra_serf__stat(svn_ra_session_t *ra_session,
+ const char *rel_path,
+ svn_revnum_t revision,
+ svn_dirent_t **dirent,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_t *props;
+ svn_error_t *err;
+ struct dirent_walker_baton_t dwb;
+ svn_tristate_t deadprop_count = svn_tristate_unknown;
+
+ err = fetch_path_props(&props,
+ session, rel_path, revision,
+ get_dirent_props(SVN_DIRENT_ALL, session, pool),
+ pool, pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *dirent = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ dwb.entry = svn_dirent_create(pool);
+ dwb.supports_deadprop_count = &deadprop_count;
+ dwb.result_pool = pool;
+ SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
+
+ if (deadprop_count == svn_tristate_false
+ && session->supports_deadprop_count == svn_tristate_unknown
+ && !dwb.entry->has_props)
+ {
+ /* We have to requery as the server didn't give us the right
+ information */
+ session->supports_deadprop_count = svn_tristate_false;
+
+ SVN_ERR(fetch_path_props(&props,
+ session, rel_path, SVN_INVALID_REVNUM,
+ get_dirent_props(SVN_DIRENT_ALL, session, pool),
+ pool, pool));
+
+ SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
+ }
+
+ if (deadprop_count != svn_tristate_unknown)
+ session->supports_deadprop_count = deadprop_count;
+
+ *dirent = dwb.entry;
+
+ return SVN_NO_ERROR;
+}
+
+/* Reads the 'resourcetype' property from the list PROPS and checks if the
+ * resource at PATH@REVISION really is a directory. Returns
+ * SVN_ERR_FS_NOT_DIRECTORY if not.
+ */
+static svn_error_t *
+resource_is_directory(apr_hash_t *props)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_ra_serf__get_resource_type(&kind, props));
+
+ if (kind != svn_node_dir)
+ {
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Can't get entries of non-directory"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.get_dir(). */
+static svn_error_t *
+svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **ret_props,
+ const char *rel_path,
+ svn_revnum_t revision,
+ apr_uint32_t dirent_fields,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ const char *path;
+
+ path = session->session_url.path;
+
+ /* If we have a relative path, URI encode and append it. */
+ if (rel_path)
+ {
+ path = svn_path_url_add_component2(path, rel_path, pool);
+ }
+
+ /* If the user specified a peg revision other than HEAD, we have to fetch
+ the baseline collection url for that revision. If not, we can use the
+ public url. */
+ if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
+ {
+ SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
+ session, NULL /* conn */,
+ path, revision,
+ pool, pool));
+ revision = SVN_INVALID_REVNUM;
+ }
+ /* REVISION is always SVN_INVALID_REVNUM */
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
+
+ /* If we're asked for children, fetch them now. */
+ if (dirents)
+ {
+ struct path_dirent_visitor_t dirent_walk;
+ apr_hash_t *props;
+ const char *rtype;
+
+ /* Always request node kind to check that path is really a
+ * directory.
+ */
+ dirent_fields |= SVN_DIRENT_KIND;
+ SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
+ path, SVN_INVALID_REVNUM, "1",
+ get_dirent_props(dirent_fields,
+ session, pool),
+ pool, pool));
+
+ /* Check if the path is really a directory. */
+ rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype");
+ if (rtype == NULL || strcmp(rtype, "collection") != 0)
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Can't get entries of non-directory"));
+
+ /* We're going to create two hashes to help the walker along.
+ * We're going to return the 2nd one back to the caller as it
+ * will have the basenames it expects.
+ */
+ dirent_walk.full_paths = apr_hash_make(pool);
+ dirent_walk.base_paths = apr_hash_make(pool);
+ dirent_walk.orig_path = svn_urlpath__canonicalize(path, pool);
+ dirent_walk.supports_deadprop_count = svn_tristate_unknown;
+ dirent_walk.result_pool = pool;
+
+ SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
+ path_dirent_walker, &dirent_walk,
+ pool));
+
+ if (dirent_walk.supports_deadprop_count == svn_tristate_false
+ && session->supports_deadprop_count == svn_tristate_unknown
+ && dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ /* We have to requery as the server didn't give us the right
+ information */
+ session->supports_deadprop_count = svn_tristate_false;
+ SVN_ERR(svn_ra_serf__retrieve_props(&props, session,
+ session->conns[0],
+ path, SVN_INVALID_REVNUM, "1",
+ get_dirent_props(dirent_fields,
+ session, pool),
+ pool, pool));
+
+ apr_hash_clear(dirent_walk.full_paths);
+ apr_hash_clear(dirent_walk.base_paths);
+
+ SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
+ path_dirent_walker,
+ &dirent_walk, pool));
+ }
+
+ *dirents = dirent_walk.base_paths;
+
+ if (dirent_walk.supports_deadprop_count != svn_tristate_unknown)
+ session->supports_deadprop_count = dirent_walk.supports_deadprop_count;
+ }
+
+ /* If we're asked for the directory properties, fetch them too. */
+ if (ret_props)
+ {
+ apr_hash_t *props;
+
+ SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0],
+ path, SVN_INVALID_REVNUM,
+ all_props,
+ pool, pool));
+
+ /* Check if the path is really a directory. */
+ SVN_ERR(resource_is_directory(props));
+
+ /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
+ ### put them into POOL, so we're okay. */
+ SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ if (!session->repos_root_str)
+ {
+ const char *vcc_url;
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
+ }
+
+ *url = session->repos_root_str;
+ return SVN_NO_ERROR;
+}
+
+/* TODO: to fetch the uuid from the repository, we need:
+ 1. a path that exists in HEAD
+ 2. a path that's readable
+
+ get_uuid handles the case where a path doesn't exist in HEAD and also the
+ case where the root of the repository is not readable.
+ However, it does not handle the case where we're fetching path not existing
+ in HEAD of a repository with unreadable root directory.
+
+ Implements svn_ra__vtable_t.get_uuid().
+ */
+static svn_error_t *
+svn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
+ const char **uuid,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ if (!session->uuid)
+ {
+ const char *vcc_url;
+
+ /* We should never get here if we have HTTP v2 support, because
+ any server with that support should be transmitting the
+ UUID in the initial OPTIONS response. */
+ SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
+
+ /* We're not interested in vcc_url and relative_url, but this call also
+ stores the repository's uuid in the session. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
+ if (!session->uuid)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
+ _("The UUID property was not found on the "
+ "resource or any of its parents"));
+ }
+ }
+
+ *uuid = session->uuid;
+
+ return SVN_NO_ERROR;
+}
+
+
+static const svn_ra__vtable_t serf_vtable = {
+ ra_serf_version,
+ ra_serf_get_description,
+ ra_serf_get_schemes,
+ svn_ra_serf__open,
+ svn_ra_serf__reparent,
+ svn_ra_serf__get_session_url,
+ svn_ra_serf__get_latest_revnum,
+ svn_ra_serf__get_dated_revision,
+ svn_ra_serf__change_rev_prop,
+ svn_ra_serf__rev_proplist,
+ svn_ra_serf__rev_prop,
+ svn_ra_serf__get_commit_editor,
+ svn_ra_serf__get_file,
+ svn_ra_serf__get_dir,
+ svn_ra_serf__get_mergeinfo,
+ svn_ra_serf__do_update,
+ svn_ra_serf__do_switch,
+ svn_ra_serf__do_status,
+ svn_ra_serf__do_diff,
+ svn_ra_serf__get_log,
+ svn_ra_serf__check_path,
+ svn_ra_serf__stat,
+ svn_ra_serf__get_uuid,
+ svn_ra_serf__get_repos_root,
+ svn_ra_serf__get_locations,
+ svn_ra_serf__get_location_segments,
+ svn_ra_serf__get_file_revs,
+ svn_ra_serf__lock,
+ svn_ra_serf__unlock,
+ svn_ra_serf__get_lock,
+ svn_ra_serf__get_locks,
+ svn_ra_serf__replay,
+ svn_ra_serf__has_capability,
+ svn_ra_serf__replay_range,
+ svn_ra_serf__get_deleted_rev,
+ svn_ra_serf__register_editor_shim_callbacks,
+ svn_ra_serf__get_inherited_props
+};
+
+svn_error_t *
+svn_ra_serf__init(const svn_version_t *loader_version,
+ const svn_ra__vtable_t **vtable,
+ apr_pool_t *pool)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+ int serf_major;
+ int serf_minor;
+ int serf_patch;
+
+ SVN_ERR(svn_ver_check_list(ra_serf_version(), checklist));
+
+ /* Simplified version check to make sure we can safely use the
+ VTABLE parameter. The RA loader does a more exhaustive check. */
+ if (loader_version->major != SVN_VER_MAJOR)
+ {
+ return svn_error_createf(
+ SVN_ERR_VERSION_MISMATCH, NULL,
+ _("Unsupported RA loader version (%d) for ra_serf"),
+ loader_version->major);
+ }
+
+ /* Make sure that we have loaded a compatible library: the MAJOR must
+ match, and the minor must be at *least* what we compiled against.
+ The patch level is simply ignored. */
+ serf_lib_version(&serf_major, &serf_minor, &serf_patch);
+ if (serf_major != SERF_MAJOR_VERSION
+ || serf_minor < SERF_MINOR_VERSION)
+ {
+ return svn_error_createf(
+ /* ### should return a unique error */
+ SVN_ERR_VERSION_MISMATCH, NULL,
+ _("ra_serf was compiled for serf %d.%d.%d but loaded "
+ "an incompatible %d.%d.%d library"),
+ SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION,
+ serf_major, serf_minor, serf_patch);
+ }
+
+ *vtable = &serf_vtable;
+
+ return SVN_NO_ERROR;
+}
+
+/* Compatibility wrapper for pre-1.2 subversions. Needed? */
+#define NAME "ra_serf"
+#define DESCRIPTION RA_SERF_DESCRIPTION
+#define VTBL serf_vtable
+#define INITFUNC svn_ra_serf__init
+#define COMPAT_INITFUNC svn_ra_serf_init
+#include "../libsvn_ra/wrapper_template.h"
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/update.c b/contrib/subversion/subversion/libsvn_ra_serf/update.c
new file mode 100644
index 0000000..21ed2df
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/update.c
@@ -0,0 +1,3639 @@
+/*
+ * update.c : entry point for update RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_version.h>
+#include <apr_want.h>
+
+#include <apr_uri.h>
+
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "svn_delta.h"
+#include "svn_path.h"
+#include "svn_base64.h"
+#include "svn_props.h"
+
+#include "svn_private_config.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_string_private.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ *
+ * A little explanation of how the parsing works. Every time we see
+ * an open-directory tag, we enter the OPEN_DIR state. Likewise, for
+ * add-directory, open-file, etc. When we see the closing variant of the
+ * open-directory tag, we'll 'pop' out of that state.
+ *
+ * Each state has a pool associated with it that can have temporary
+ * allocations that will live as long as the tag is opened. Once
+ * the tag is 'closed', the pool will be reused.
+ */
+typedef enum report_state_e {
+ NONE = 0,
+ INITIAL = 0,
+ UPDATE_REPORT,
+ TARGET_REVISION,
+ OPEN_DIR,
+ ADD_DIR,
+ ABSENT_DIR,
+ OPEN_FILE,
+ ADD_FILE,
+ ABSENT_FILE,
+ PROP,
+ IGNORE_PROP_NAME,
+ NEED_PROP_NAME,
+ TXDELTA
+} report_state_e;
+
+
+/* While we process the REPORT response, we will queue up GET and PROPFIND
+ requests. For a very large checkout, it is very easy to queue requests
+ faster than they are resolved. Thus, we need to pause the XML processing
+ (which queues more requests) to avoid queueing too many, with their
+ attendant memory costs. When the queue count drops low enough, we will
+ resume XML processing.
+
+ Note that we don't want the count to drop to zero. We have multiple
+ connections that we want to keep busy. These are also heuristic numbers
+ since network and parsing behavior (ie. it doesn't pause immediately)
+ can make the measurements quite imprecise.
+
+ We measure outstanding requests as the sum of NUM_ACTIVE_FETCHES and
+ NUM_ACTIVE_PROPFINDS in the report_context_t structure. */
+#define REQUEST_COUNT_TO_PAUSE 50
+#define REQUEST_COUNT_TO_RESUME 40
+
+
+/* Forward-declare our report context. */
+typedef struct report_context_t report_context_t;
+
+/*
+ * This structure represents the information for a directory.
+ */
+typedef struct report_dir_t
+{
+ /* Our parent directory.
+ *
+ * This value is NULL when we are the root.
+ */
+ struct report_dir_t *parent_dir;
+
+ apr_pool_t *pool;
+
+ /* Pointer back to our original report context. */
+ report_context_t *report_context;
+
+ /* Our name sans any parents. */
+ const char *base_name;
+
+ /* the expanded directory name (including all parent names) */
+ const char *name;
+
+ /* the canonical url for this directory after updating. (received) */
+ const char *url;
+
+ /* The original repos_relpath of this url (from the working copy)
+ or NULL if the repos_relpath can be calculated from the edit root. */
+ const char *repos_relpath;
+
+ /* Our base revision - SVN_INVALID_REVNUM if we're adding this dir. */
+ svn_revnum_t base_rev;
+
+ /* controlling dir baton - this is only created in ensure_dir_opened() */
+ void *dir_baton;
+ apr_pool_t *dir_baton_pool;
+
+ /* How many references to this directory do we still have open? */
+ apr_size_t ref_count;
+
+ /* Namespace list allocated out of this ->pool. */
+ svn_ra_serf__ns_t *ns_list;
+
+ /* hashtable for all of the properties (shared within a dir) */
+ apr_hash_t *props;
+
+ /* hashtable for all to-be-removed properties (shared within a dir) */
+ apr_hash_t *removed_props;
+
+ /* The propfind request for our current directory */
+ svn_ra_serf__handler_t *propfind_handler;
+
+ /* Has the server told us to fetch the dir props? */
+ svn_boolean_t fetch_props;
+
+ /* Have we closed the directory tag (meaning no more additions)? */
+ svn_boolean_t tag_closed;
+
+ /* The children of this directory */
+ struct report_dir_t *children;
+
+ /* The next sibling of this directory */
+ struct report_dir_t *sibling;
+} report_dir_t;
+
+/*
+ * This structure represents the information for a file.
+ *
+ * A directory may have a report_info_t associated with it as well.
+ *
+ * This structure is created as we parse the REPORT response and
+ * once the element is completed, we create a report_fetch_t structure
+ * to give to serf to retrieve this file.
+ */
+typedef struct report_info_t
+{
+ apr_pool_t *pool;
+
+ /* The enclosing directory.
+ *
+ * If this structure refers to a directory, the dir it points to will be
+ * itself.
+ */
+ report_dir_t *dir;
+
+ /* Our name sans any directory info. */
+ const char *base_name;
+
+ /* the expanded file name (including all parent directory names) */
+ const char *name;
+
+ /* the canonical url for this file. */
+ const char *url;
+
+ /* lock token, if we had one to start off with. */
+ const char *lock_token;
+
+ /* Our base revision - SVN_INVALID_REVNUM if we're adding this file. */
+ svn_revnum_t base_rev;
+
+ /* our delta base, if present (NULL if we're adding the file) */
+ const char *delta_base;
+
+ /* Path of original item if add with history */
+ const char *copyfrom_path;
+
+ /* Revision of original item if add with history */
+ svn_revnum_t copyfrom_rev;
+
+ /* The propfind request for our current file (if present) */
+ svn_ra_serf__handler_t *propfind_handler;
+
+ /* Has the server told us to fetch the file props? */
+ svn_boolean_t fetch_props;
+
+ /* Has the server told us to go fetch - only valid if we had it already */
+ svn_boolean_t fetch_file;
+
+ /* The properties for this file */
+ apr_hash_t *props;
+
+ /* pool passed to update->add_file, etc. */
+ apr_pool_t *editor_pool;
+
+ /* controlling file_baton and textdelta handler */
+ void *file_baton;
+ const char *base_checksum;
+ const char *final_sha1_checksum;
+ svn_txdelta_window_handler_t textdelta;
+ void *textdelta_baton;
+ svn_stream_t *svndiff_decoder;
+ svn_stream_t *base64_decoder;
+
+ /* Checksum for close_file */
+ const char *final_checksum;
+
+ /* Stream containing file contents already cached in the working
+ copy (which may be used to avoid a GET request for the same). */
+ svn_stream_t *cached_contents;
+
+ /* temporary property for this file which is currently being parsed
+ * It will eventually be stored in our parent directory's property hash.
+ */
+ const char *prop_ns;
+ const char *prop_name;
+ svn_stringbuf_t *prop_value;
+ const char *prop_encoding;
+} report_info_t;
+
+/*
+ * This structure represents a single request to GET (fetch) a file with
+ * its associated Serf session/connection.
+ */
+typedef struct report_fetch_t {
+
+ /* The handler representing this particular fetch. */
+ svn_ra_serf__handler_t *handler;
+
+ /* The session we should use to fetch the file. */
+ svn_ra_serf__session_t *sess;
+
+ /* The connection we should use to fetch file. */
+ svn_ra_serf__connection_t *conn;
+
+ /* Stores the information for the file we want to fetch. */
+ report_info_t *info;
+
+ /* Have we read our response headers yet? */
+ svn_boolean_t read_headers;
+
+ /* This flag is set when our response is aborted before we reach the
+ * end and we decide to requeue this request.
+ */
+ svn_boolean_t aborted_read;
+ apr_off_t aborted_read_size;
+
+ /* This is the amount of data that we have read so far. */
+ apr_off_t read_size;
+
+ /* If we're receiving an svndiff, this will be non-NULL. */
+ svn_stream_t *delta_stream;
+
+ /* If we're writing this file to a stream, this will be non-NULL. */
+ svn_stream_t *target_stream;
+
+ /* Are we done fetching this file? */
+ svn_boolean_t done;
+
+ /* Discard the rest of the content? */
+ svn_boolean_t discard;
+
+ svn_ra_serf__list_t **done_list;
+ svn_ra_serf__list_t done_item;
+
+} report_fetch_t;
+
+/*
+ * The master structure for a REPORT request and response.
+ */
+struct report_context_t {
+ apr_pool_t *pool;
+
+ svn_ra_serf__session_t *sess;
+ svn_ra_serf__connection_t *conn;
+
+ /* Source path and destination path */
+ const char *source;
+ const char *destination;
+
+ /* Our update target. */
+ const char *update_target;
+
+ /* What is the target revision that we want for this REPORT? */
+ svn_revnum_t target_rev;
+
+ /* Have we been asked to ignore ancestry or textdeltas? */
+ svn_boolean_t ignore_ancestry;
+ svn_boolean_t text_deltas;
+
+ /* Do we want the server to send copyfrom args or not? */
+ svn_boolean_t send_copyfrom_args;
+
+ /* Is the server sending everything in one response? */
+ svn_boolean_t send_all_mode;
+
+ /* Is the server including properties inline for newly added
+ files/dirs? */
+ svn_boolean_t add_props_included;
+
+ /* Path -> lock token mapping. */
+ apr_hash_t *lock_path_tokens;
+
+ /* Path -> const char *repos_relpath mapping */
+ apr_hash_t *switched_paths;
+
+ /* Boolean indicating whether "" is switched.
+ (This indicates that the we are updating a single file) */
+ svn_boolean_t root_is_switched;
+
+ /* Our master update editor and baton. */
+ const svn_delta_editor_t *update_editor;
+ void *update_baton;
+
+ /* The file holding request body for the REPORT.
+ *
+ * ### todo: It will be better for performance to store small
+ * request bodies (like 4k) in memory and bigger bodies on disk.
+ */
+ apr_file_t *body_file;
+
+ /* root directory object */
+ report_dir_t *root_dir;
+
+ /* number of pending GET requests */
+ unsigned int num_active_fetches;
+
+ /* completed fetches (contains report_fetch_t) */
+ svn_ra_serf__list_t *done_fetches;
+
+ /* number of pending PROPFIND requests */
+ unsigned int num_active_propfinds;
+
+ /* completed PROPFIND requests (contains svn_ra_serf__handler_t) */
+ svn_ra_serf__list_t *done_propfinds;
+ svn_ra_serf__list_t *done_dir_propfinds;
+
+ /* list of outstanding prop changes (contains report_dir_t) */
+ svn_ra_serf__list_t *active_dir_propfinds;
+
+ /* list of files that only have prop changes (contains report_info_t) */
+ svn_ra_serf__list_t *file_propchanges_only;
+
+ /* The path to the REPORT request */
+ const char *path;
+
+ /* Are we done parsing the REPORT response? */
+ svn_boolean_t done;
+
+ /* Did we receive all data from the network? */
+ svn_boolean_t report_received;
+
+ /* Did we get a complete (non-truncated) report? */
+ svn_boolean_t report_completed;
+
+ /* The XML parser context for the REPORT response. */
+ svn_ra_serf__xml_parser_t *parser_ctx;
+
+ /* Did we close the root directory? */
+ svn_boolean_t closed_root;
+};
+
+
+#ifdef NOT_USED_YET
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t update_ttable[] = {
+ { INITIAL, S_, "update-report", UPDATE_REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
+ FALSE, { "rev", NULL }, TRUE },
+
+ { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
+ FALSE, { "rev", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "open-directory", OPEN_DIR,
+ FALSE, { "rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "add-directory", ADD_DIR,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { ADD_DIR, S_, "add-directory", ADD_DIR,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "open-file", OPEN_FILE,
+ FALSE, { "rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "add-file", ADD_FILE,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { ADD_DIR, S_, "add-file", ADD_FILE,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "delete-entry", OPEN_FILE,
+ FALSE, { "?rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
+ FALSE, { "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
+ FALSE, { "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
+ FALSE, { "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "absent-file", ABSENT_FILE,
+ FALSE, { "name", NULL }, TRUE },
+
+ { 0 }
+};
+
+
+
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+update_opened(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int entered_state,
+ const svn_ra_serf__dav_props_t *tag,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = baton;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+update_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = baton;
+
+ if (leaving_state == TARGET_REVISION)
+ {
+ const char *rev = svn_hash_gets(attrs, "rev");
+
+ SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
+ SVN_STR_TO_REV(rev),
+ ctx->sess->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_cdata_t */
+static svn_error_t *
+update_cdata(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int current_state,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = baton;
+
+ return SVN_NO_ERROR;
+}
+
+#endif /* NOT_USED_YET */
+
+
+/* Returns best connection for fetching files/properties. */
+static svn_ra_serf__connection_t *
+get_best_connection(report_context_t *ctx)
+{
+ svn_ra_serf__connection_t *conn;
+ int first_conn = 1;
+
+ /* Skip the first connection if the REPORT response hasn't been completely
+ received yet or if we're being told to limit our connections to
+ 2 (because this could be an attempt to ensure that we do all our
+ auxiliary GETs/PROPFINDs on a single connection).
+
+ ### FIXME: This latter requirement (max_connections > 2) is
+ ### really just a hack to work around the fact that some update
+ ### editor implementations (such as svnrdump's dump editor)
+ ### simply can't handle the way ra_serf violates the editor v1
+ ### drive ordering requirements.
+ ###
+ ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
+ */
+ if (ctx->report_received && (ctx->sess->max_connections > 2))
+ first_conn = 0;
+
+ /* Currently, we just cycle connections. In the future we could
+ store the number of pending requests on each connection, or
+ perform other heuristics, to achieve better connection usage.
+ (As an optimization, if there's only one available auxiliary
+ connection to use, don't bother doing all the cur_conn math --
+ just return that one connection.) */
+ if (ctx->sess->num_conns - first_conn == 1)
+ {
+ conn = ctx->sess->conns[first_conn];
+ }
+ else
+ {
+ conn = ctx->sess->conns[ctx->sess->cur_conn];
+ ctx->sess->cur_conn++;
+ if (ctx->sess->cur_conn >= ctx->sess->num_conns)
+ ctx->sess->cur_conn = first_conn;
+ }
+ return conn;
+}
+
+
+/** Report state management helper **/
+
+static report_info_t *
+push_state(svn_ra_serf__xml_parser_t *parser,
+ report_context_t *ctx,
+ report_state_e state)
+{
+ report_info_t *info;
+ apr_pool_t *info_parent_pool;
+
+ svn_ra_serf__xml_push_state(parser, state);
+
+ info = parser->state->private;
+
+ /* Our private pool needs to be disjoint from the state pool. */
+ if (!info)
+ {
+ info_parent_pool = ctx->pool;
+ }
+ else
+ {
+ info_parent_pool = info->pool;
+ }
+
+ if (state == OPEN_DIR || state == ADD_DIR)
+ {
+ report_info_t *new_info;
+
+ new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
+ new_info->pool = svn_pool_create(info_parent_pool);
+ new_info->lock_token = NULL;
+ new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
+
+ new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir));
+ new_info->dir->pool = new_info->pool;
+
+ /* Create the root property tree. */
+ new_info->dir->props = apr_hash_make(new_info->pool);
+ new_info->props = new_info->dir->props;
+ new_info->dir->removed_props = apr_hash_make(new_info->pool);
+
+ new_info->dir->report_context = ctx;
+
+ if (info)
+ {
+ info->dir->ref_count++;
+
+ new_info->dir->parent_dir = info->dir;
+
+ /* Point our ns_list at our parents to try to reuse it. */
+ new_info->dir->ns_list = info->dir->ns_list;
+
+ /* Add ourselves to our parent's list */
+ new_info->dir->sibling = info->dir->children;
+ info->dir->children = new_info->dir;
+ }
+ else
+ {
+ /* Allow us to be found later. */
+ ctx->root_dir = new_info->dir;
+ }
+
+ parser->state->private = new_info;
+ }
+ else if (state == OPEN_FILE || state == ADD_FILE)
+ {
+ report_info_t *new_info;
+
+ new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
+ new_info->pool = svn_pool_create(info_parent_pool);
+ new_info->file_baton = NULL;
+ new_info->lock_token = NULL;
+ new_info->fetch_file = FALSE;
+ new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
+
+ /* Point at our parent's directory state. */
+ new_info->dir = info->dir;
+ info->dir->ref_count++;
+
+ new_info->props = apr_hash_make(new_info->pool);
+
+ parser->state->private = new_info;
+ }
+
+ return parser->state->private;
+}
+
+
+/** Wrappers around our various property walkers **/
+
+static svn_error_t *
+set_file_props(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ report_info_t *info = baton;
+ const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
+ const char *prop_name;
+
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
+ if (prop_name != NULL)
+ return svn_error_trace(editor->change_file_prop(info->file_baton,
+ prop_name,
+ val,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+set_dir_props(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ report_dir_t *dir = baton;
+ const svn_delta_editor_t *editor = dir->report_context->update_editor;
+ const char *prop_name;
+
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
+ if (prop_name != NULL)
+ return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
+ prop_name,
+ val,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+remove_file_props(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ report_info_t *info = baton;
+ const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
+ const char *prop_name;
+
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
+ if (prop_name != NULL)
+ return svn_error_trace(editor->change_file_prop(info->file_baton,
+ prop_name,
+ NULL,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+remove_dir_props(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ report_dir_t *dir = baton;
+ const svn_delta_editor_t *editor = dir->report_context->update_editor;
+ const char *prop_name;
+
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
+ if (prop_name != NULL)
+ return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
+ prop_name,
+ NULL,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+/** Helpers to open and close directories */
+
+static svn_error_t*
+ensure_dir_opened(report_dir_t *dir)
+{
+ report_context_t *ctx = dir->report_context;
+
+ /* if we're already open, return now */
+ if (dir->dir_baton)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ if (dir->base_name[0] == '\0')
+ {
+ dir->dir_baton_pool = svn_pool_create(dir->pool);
+
+ if (ctx->destination
+ && ctx->sess->wc_callbacks->invalidate_wc_props)
+ {
+ SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
+ ctx->sess->wc_callback_baton,
+ ctx->update_target,
+ SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool));
+ }
+
+ SVN_ERR(ctx->update_editor->open_root(ctx->update_baton, dir->base_rev,
+ dir->dir_baton_pool,
+ &dir->dir_baton));
+ }
+ else
+ {
+ SVN_ERR(ensure_dir_opened(dir->parent_dir));
+
+ dir->dir_baton_pool = svn_pool_create(dir->parent_dir->dir_baton_pool);
+
+ if (SVN_IS_VALID_REVNUM(dir->base_rev))
+ {
+ SVN_ERR(ctx->update_editor->open_directory(dir->name,
+ dir->parent_dir->dir_baton,
+ dir->base_rev,
+ dir->dir_baton_pool,
+ &dir->dir_baton));
+ }
+ else
+ {
+ SVN_ERR(ctx->update_editor->add_directory(dir->name,
+ dir->parent_dir->dir_baton,
+ NULL, SVN_INVALID_REVNUM,
+ dir->dir_baton_pool,
+ &dir->dir_baton));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_dir(report_dir_t *dir)
+{
+ report_dir_t *prev;
+ report_dir_t *sibling;
+
+ /* ### is there a better pool... this is tossed at end-of-func */
+ apr_pool_t *scratch_pool = dir->dir_baton_pool;
+
+ SVN_ERR_ASSERT(! dir->ref_count);
+
+ SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->base_name,
+ dir->base_rev,
+ set_dir_props, dir,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_serf__walk_all_props(dir->removed_props, dir->base_name,
+ dir->base_rev, remove_dir_props, dir,
+ scratch_pool));
+
+ if (dir->fetch_props)
+ {
+ SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->url,
+ dir->report_context->target_rev,
+ set_dir_props, dir,
+ scratch_pool));
+ }
+
+ SVN_ERR(dir->report_context->update_editor->close_directory(
+ dir->dir_baton, scratch_pool));
+
+ /* remove us from our parent's children list */
+ if (dir->parent_dir)
+ {
+ prev = NULL;
+ sibling = dir->parent_dir->children;
+
+ while (sibling != dir)
+ {
+ prev = sibling;
+ sibling = sibling->sibling;
+ if (!sibling)
+ SVN_ERR_MALFUNCTION();
+ }
+
+ if (!prev)
+ {
+ dir->parent_dir->children = dir->sibling;
+ }
+ else
+ {
+ prev->sibling = dir->sibling;
+ }
+ }
+
+ svn_pool_destroy(dir->dir_baton_pool);
+ svn_pool_destroy(dir->pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *close_all_dirs(report_dir_t *dir)
+{
+ while (dir->children)
+ {
+ SVN_ERR(close_all_dirs(dir->children));
+ dir->ref_count--;
+ }
+
+ SVN_ERR_ASSERT(! dir->ref_count);
+
+ SVN_ERR(ensure_dir_opened(dir));
+
+ return close_dir(dir);
+}
+
+
+/** Routines called when we are fetching a file */
+
+/* This function works around a bug in some older versions of
+ * mod_dav_svn in that it will not send remove-prop in the update
+ * report when a lock property disappears when send-all is false.
+ *
+ * Therefore, we'll try to look at our properties and see if there's
+ * an active lock. If not, then we'll assume there isn't a lock
+ * anymore.
+ */
+static void
+check_lock(report_info_t *info)
+{
+ const char *lock_val;
+
+ lock_val = svn_ra_serf__get_ver_prop(info->props, info->url,
+ info->dir->report_context->target_rev,
+ "DAV:", "lockdiscovery");
+
+ if (lock_val)
+ {
+ char *new_lock;
+ new_lock = apr_pstrdup(info->editor_pool, lock_val);
+ apr_collapse_spaces(new_lock, new_lock);
+ lock_val = new_lock;
+ }
+
+ if (!lock_val || lock_val[0] == '\0')
+ {
+ svn_string_t *str;
+
+ str = svn_string_ncreate("", 1, info->editor_pool);
+
+ svn_ra_serf__set_ver_prop(info->dir->removed_props, info->base_name,
+ info->base_rev, "DAV:", "lock-token",
+ str, info->dir->pool);
+ }
+}
+
+static svn_error_t *
+headers_fetch(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ report_fetch_t *fetch_ctx = baton;
+
+ /* note that we have old VC URL */
+ if (SVN_IS_VALID_REVNUM(fetch_ctx->info->base_rev) &&
+ fetch_ctx->info->delta_base)
+ {
+ serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
+ fetch_ctx->info->delta_base);
+ serf_bucket_headers_setn(headers, "Accept-Encoding",
+ "svndiff1;q=0.9,svndiff;q=0.8");
+ }
+ else if (fetch_ctx->sess->using_compression)
+ {
+ serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cancel_fetch(serf_request_t *request,
+ serf_bucket_t *response,
+ int status_code,
+ void *baton)
+{
+ report_fetch_t *fetch_ctx = baton;
+
+ /* Uh-oh. Our connection died on us.
+ *
+ * The core ra_serf layer will requeue our request - we just need to note
+ * that we got cut off in the middle of our song.
+ */
+ if (!response)
+ {
+ /* If we already started the fetch and opened the file handle, we need
+ * to hold subsequent read() ops until we get back to where we were
+ * before the close and we can then resume the textdelta() calls.
+ */
+ if (fetch_ctx->read_headers)
+ {
+ if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
+ {
+ fetch_ctx->aborted_read = TRUE;
+ fetch_ctx->aborted_read_size = fetch_ctx->read_size;
+ }
+ fetch_ctx->read_size = 0;
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* We have no idea what went wrong. */
+ SVN_ERR_MALFUNCTION();
+}
+
+static svn_error_t *
+error_fetch(serf_request_t *request,
+ report_fetch_t *fetch_ctx,
+ svn_error_t *err)
+{
+ fetch_ctx->done = TRUE;
+
+ fetch_ctx->done_item.data = fetch_ctx;
+ fetch_ctx->done_item.next = *fetch_ctx->done_list;
+ *fetch_ctx->done_list = &fetch_ctx->done_item;
+
+ /* Discard the rest of this request
+ (This makes sure it doesn't error when the request is aborted later) */
+ serf_request_set_handler(request,
+ svn_ra_serf__response_discard_handler, NULL);
+
+ /* Some errors would be handled by serf; make sure they really make
+ the update fail by wrapping it in a different error. */
+ if (!SERF_BUCKET_READ_ERROR(err->apr_err))
+ return svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ return err;
+}
+
+/* Wield the editor referenced by INFO to open (or add) the file
+ file also associated with INFO, setting properties on the file and
+ calling the editor's apply_textdelta() function on it if necessary
+ (or if FORCE_APPLY_TEXTDELTA is set).
+
+ Callers will probably want to also see the function that serves
+ the opposite purpose of this one, close_updated_file(). */
+static svn_error_t *
+open_updated_file(report_info_t *info,
+ svn_boolean_t force_apply_textdelta,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = info->dir->report_context;
+ const svn_delta_editor_t *update_editor = ctx->update_editor;
+
+ /* Ensure our parent is open. */
+ SVN_ERR(ensure_dir_opened(info->dir));
+ info->editor_pool = svn_pool_create(info->dir->dir_baton_pool);
+
+ /* Expand our full name now if we haven't done so yet. */
+ if (!info->name)
+ {
+ info->name = svn_relpath_join(info->dir->name, info->base_name,
+ info->editor_pool);
+ }
+
+ /* Open (or add) the file. */
+ if (SVN_IS_VALID_REVNUM(info->base_rev))
+ {
+ SVN_ERR(update_editor->open_file(info->name,
+ info->dir->dir_baton,
+ info->base_rev,
+ info->editor_pool,
+ &info->file_baton));
+ }
+ else
+ {
+ SVN_ERR(update_editor->add_file(info->name,
+ info->dir->dir_baton,
+ info->copyfrom_path,
+ info->copyfrom_rev,
+ info->editor_pool,
+ &info->file_baton));
+ }
+
+ /* Check for lock information. */
+ if (info->lock_token)
+ check_lock(info);
+
+ /* Get (maybe) a textdelta window handler for transmitting file
+ content changes. */
+ if (info->fetch_file || force_apply_textdelta)
+ {
+ SVN_ERR(update_editor->apply_textdelta(info->file_baton,
+ info->base_checksum,
+ info->editor_pool,
+ &info->textdelta,
+ &info->textdelta_baton));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Close the file associated with INFO->file_baton, and cleanup other
+ bits of that structure managed by open_updated_file(). */
+static svn_error_t *
+close_updated_file(report_info_t *info,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = info->dir->report_context;
+
+ /* Set all of the properties we received */
+ SVN_ERR(svn_ra_serf__walk_all_props(info->props,
+ info->base_name,
+ info->base_rev,
+ set_file_props, info,
+ scratch_pool));
+ SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props,
+ info->base_name,
+ info->base_rev,
+ remove_file_props, info,
+ scratch_pool));
+ if (info->fetch_props)
+ {
+ SVN_ERR(svn_ra_serf__walk_all_props(info->props,
+ info->url,
+ ctx->target_rev,
+ set_file_props, info,
+ scratch_pool));
+ }
+
+ /* Close the file via the editor. */
+ SVN_ERR(info->dir->report_context->update_editor->close_file(
+ info->file_baton, info->final_checksum, scratch_pool));
+
+ /* We're done with our editor pool. */
+ svn_pool_destroy(info->editor_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+handle_fetch(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ const char *data;
+ apr_size_t len;
+ apr_status_t status;
+ report_fetch_t *fetch_ctx = handler_baton;
+ svn_error_t *err;
+
+ /* ### new field. make sure we didn't miss some initialization. */
+ SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
+
+ if (!fetch_ctx->read_headers)
+ {
+ serf_bucket_t *hdrs;
+ const char *val;
+ report_info_t *info;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ info = fetch_ctx->info;
+
+ if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
+ {
+ fetch_ctx->delta_stream =
+ svn_txdelta_parse_svndiff(info->textdelta,
+ info->textdelta_baton,
+ TRUE, info->editor_pool);
+
+ /* Validate the delta base claimed by the server matches
+ what we asked for! */
+ val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
+ if (val && (strcmp(val, info->delta_base) != 0))
+ {
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("GET request returned unexpected "
+ "delta base: %s"), val);
+ return error_fetch(request, fetch_ctx, err);
+ }
+ }
+ else
+ {
+ fetch_ctx->delta_stream = NULL;
+ }
+
+ fetch_ctx->read_headers = TRUE;
+ }
+
+ /* If the error code wasn't 200, something went wrong. Don't use the returned
+ data as its probably an error message. Just bail out instead. */
+ if (fetch_ctx->handler->sline.code != 200)
+ {
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("GET request failed: %d %s"),
+ fetch_ctx->handler->sline.code,
+ fetch_ctx->handler->sline.reason);
+ return error_fetch(request, fetch_ctx, err);
+ }
+
+ while (1)
+ {
+ svn_txdelta_window_t delta_window = { 0 };
+ svn_txdelta_op_t delta_op;
+ svn_string_t window_data;
+
+ status = serf_bucket_read(response, 8000, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ fetch_ctx->read_size += len;
+
+ if (fetch_ctx->aborted_read)
+ {
+ apr_off_t skip;
+ /* We haven't caught up to where we were before. */
+ if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
+ {
+ /* Eek. What did the file shrink or something? */
+ if (APR_STATUS_IS_EOF(status))
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ /* Skip on to the next iteration of this loop. */
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ continue;
+ }
+
+ /* Woo-hoo. We're back. */
+ fetch_ctx->aborted_read = FALSE;
+
+ /* Update data and len to just provide the new data. */
+ skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
+ data += skip;
+ len -= skip;
+ }
+
+ if (fetch_ctx->delta_stream)
+ {
+ err = svn_stream_write(fetch_ctx->delta_stream, data, &len);
+ if (err)
+ {
+ return error_fetch(request, fetch_ctx, err);
+ }
+ }
+ /* otherwise, manually construct the text delta window. */
+ else if (len)
+ {
+ window_data.data = data;
+ window_data.len = len;
+
+ delta_op.action_code = svn_txdelta_new;
+ delta_op.offset = 0;
+ delta_op.length = len;
+
+ delta_window.tview_len = len;
+ delta_window.num_ops = 1;
+ delta_window.ops = &delta_op;
+ delta_window.new_data = &window_data;
+
+ /* write to the file located in the info. */
+ err = fetch_ctx->info->textdelta(&delta_window,
+ fetch_ctx->info->textdelta_baton);
+ if (err)
+ {
+ return error_fetch(request, fetch_ctx, err);
+ }
+ }
+
+ if (APR_STATUS_IS_EOF(status))
+ {
+ report_info_t *info = fetch_ctx->info;
+
+ if (fetch_ctx->delta_stream)
+ err = svn_error_trace(svn_stream_close(fetch_ctx->delta_stream));
+ else
+ err = svn_error_trace(info->textdelta(NULL,
+ info->textdelta_baton));
+ if (err)
+ {
+ return error_fetch(request, fetch_ctx, err);
+ }
+
+ err = close_updated_file(info, info->pool);
+ if (err)
+ {
+ return svn_error_trace(error_fetch(request, fetch_ctx, err));
+ }
+
+ fetch_ctx->done = TRUE;
+
+ fetch_ctx->done_item.data = fetch_ctx;
+ fetch_ctx->done_item.next = *fetch_ctx->done_list;
+ *fetch_ctx->done_list = &fetch_ctx->done_item;
+
+ /* We're done with our pool. */
+ svn_pool_destroy(info->pool);
+
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ }
+ /* not reached */
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+handle_stream(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ report_fetch_t *fetch_ctx = handler_baton;
+ svn_error_t *err;
+ apr_status_t status;
+
+ /* ### new field. make sure we didn't miss some initialization. */
+ SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
+
+ err = svn_ra_serf__error_on_status(fetch_ctx->handler->sline.code,
+ fetch_ctx->info->name,
+ fetch_ctx->handler->location);
+ if (err)
+ {
+ fetch_ctx->handler->done = TRUE;
+
+ err = svn_error_compose_create(
+ err,
+ svn_ra_serf__handle_discard_body(request, response, NULL, pool));
+
+ return svn_error_trace(err);
+ }
+
+ while (1)
+ {
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(response, 8000, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ fetch_ctx->read_size += len;
+
+ if (fetch_ctx->aborted_read)
+ {
+ /* We haven't caught up to where we were before. */
+ if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
+ {
+ /* Eek. What did the file shrink or something? */
+ if (APR_STATUS_IS_EOF(status))
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ /* Skip on to the next iteration of this loop. */
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ continue;
+ }
+
+ /* Woo-hoo. We're back. */
+ fetch_ctx->aborted_read = FALSE;
+
+ /* Increment data and len by the difference. */
+ data += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
+ len += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
+ }
+
+ if (len)
+ {
+ apr_size_t written_len;
+
+ written_len = len;
+
+ SVN_ERR(svn_stream_write(fetch_ctx->target_stream, data,
+ &written_len));
+ }
+
+ if (APR_STATUS_IS_EOF(status))
+ {
+ fetch_ctx->done = TRUE;
+ }
+
+ if (status)
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ }
+ /* not reached */
+}
+
+/* Close the directory represented by DIR -- and any suitable parents
+ thereof -- if we are able to do so. This is the case whenever:
+
+ - there are no remaining open items within the directory, and
+ - the directory's XML close tag has been processed (so we know
+ there are no more children to worry about in the future), and
+ - either:
+ - we aren't fetching properties for this directory, or
+ - we've already finished fetching those properties.
+*/
+static svn_error_t *
+maybe_close_dir_chain(report_dir_t *dir)
+{
+ report_dir_t *cur_dir = dir;
+
+ SVN_ERR(ensure_dir_opened(cur_dir));
+
+ while (cur_dir
+ && !cur_dir->ref_count
+ && cur_dir->tag_closed
+ && (!cur_dir->fetch_props || cur_dir->propfind_handler->done))
+ {
+ report_dir_t *parent = cur_dir->parent_dir;
+ report_context_t *report_context = cur_dir->report_context;
+ svn_boolean_t propfind_in_done_list = FALSE;
+ svn_ra_serf__list_t *done_list;
+
+ /* Make sure there are no references to this dir in the
+ active_dir_propfinds list. If there are, don't close the
+ directory -- which would delete the pool from which the
+ relevant active_dir_propfinds list item is allocated -- and
+ of course don't crawl upward to check the parents for
+ a closure opportunity, either. */
+ done_list = report_context->active_dir_propfinds;
+ while (done_list)
+ {
+ if (done_list->data == cur_dir)
+ {
+ propfind_in_done_list = TRUE;
+ break;
+ }
+ done_list = done_list->next;
+ }
+ if (propfind_in_done_list)
+ break;
+
+ SVN_ERR(close_dir(cur_dir));
+ if (parent)
+ {
+ parent->ref_count--;
+ }
+ else
+ {
+ report_context->closed_root = TRUE;
+ }
+ cur_dir = parent;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Open the file associated with INFO for editing, pass along any
+ propchanges we've recorded for it, and then close the file. */
+static svn_error_t *
+handle_propchange_only(report_info_t *info,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(open_updated_file(info, FALSE, scratch_pool));
+ SVN_ERR(close_updated_file(info, scratch_pool));
+
+ /* We're done with our pool. */
+ svn_pool_destroy(info->pool);
+
+ info->dir->ref_count--;
+
+ /* See if the parent directory of this file (and perhaps even
+ parents of that) can be closed now. */
+ SVN_ERR(maybe_close_dir_chain(info->dir));
+
+ return SVN_NO_ERROR;
+}
+
+/* "Fetch" a file whose contents were made available via the
+ get_wc_contents() callback (as opposed to requiring a GET to the
+ server), and feed the information through the associated update
+ editor. In editor-speak, this will add/open the file, transmit any
+ property changes, handle the contents, and then close the file. */
+static svn_error_t *
+handle_local_content(report_info_t *info,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_txdelta_send_stream(info->cached_contents, info->textdelta,
+ info->textdelta_baton, NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(info->cached_contents));
+ info->cached_contents = NULL;
+ SVN_ERR(close_updated_file(info, scratch_pool));
+
+ /* We're done with our pool. */
+ svn_pool_destroy(info->pool);
+
+ info->dir->ref_count--;
+
+ /* See if the parent directory of this fetched item (and
+ perhaps even parents of that) can be closed now. */
+ SVN_ERR(maybe_close_dir_chain(info->dir));
+
+ return SVN_NO_ERROR;
+}
+
+/* --------------------------------------------------------- */
+
+static svn_error_t *
+fetch_file(report_context_t *ctx, report_info_t *info)
+{
+ svn_ra_serf__connection_t *conn;
+ svn_ra_serf__handler_t *handler;
+
+ /* What connection should we go on? */
+ conn = get_best_connection(ctx);
+
+ /* If needed, create the PROPFIND to retrieve the file's properties. */
+ info->propfind_handler = NULL;
+ if (info->fetch_props)
+ {
+ SVN_ERR(svn_ra_serf__deliver_props(&info->propfind_handler, info->props,
+ ctx->sess, conn, info->url,
+ ctx->target_rev, "0", all_props,
+ &ctx->done_propfinds,
+ info->dir->pool));
+ SVN_ERR_ASSERT(info->propfind_handler);
+
+ /* Create a serf request for the PROPFIND. */
+ svn_ra_serf__request_create(info->propfind_handler);
+
+ ctx->num_active_propfinds++;
+ }
+
+ /* If we've been asked to fetch the file or it's an add, do so.
+ * Otherwise, handle the case where only the properties changed.
+ */
+ if (info->fetch_file && ctx->text_deltas)
+ {
+ svn_stream_t *contents = NULL;
+
+ /* Open the file for editing. */
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
+
+ if (info->textdelta == svn_delta_noop_window_handler)
+ {
+ /* There is nobody looking for an actual stream.
+
+ Just report an empty stream instead of fetching
+ to be ingored data */
+ info->cached_contents = svn_stream_empty(info->pool);
+ }
+ else if (ctx->sess->wc_callbacks->get_wc_contents
+ && info->final_sha1_checksum)
+ {
+ svn_error_t *err = NULL;
+ svn_checksum_t *checksum = NULL;
+
+ /* Parse the optional SHA1 checksum (1.7+) */
+ err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
+ info->final_sha1_checksum,
+ info->pool);
+
+ /* Okay so far? Let's try to get a stream on some readily
+ available matching content. */
+ if (!err && checksum)
+ {
+ err = ctx->sess->wc_callbacks->get_wc_contents(
+ ctx->sess->wc_callback_baton, &contents,
+ checksum, info->pool);
+
+ if (! err)
+ info->cached_contents = contents;
+ }
+
+ if (err)
+ {
+ /* Meh. Maybe we'll care one day why we're in an
+ errorful state, but this codepath is optional. */
+ svn_error_clear(err);
+ }
+ }
+
+ /* If the working copy can provide cached contents for this
+ file, we don't have to fetch them from the server. */
+ if (info->cached_contents)
+ {
+ /* If we'll be doing a PROPFIND for this file... */
+ if (info->propfind_handler)
+ {
+ /* ... then we'll just leave ourselves a little "todo"
+ about that fact (and we'll deal with the file content
+ stuff later, after we've handled that PROPFIND
+ response. */
+ svn_ra_serf__list_t *list_item;
+
+ list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
+ list_item->data = info;
+ list_item->next = ctx->file_propchanges_only;
+ ctx->file_propchanges_only = list_item;
+ }
+ else
+ {
+ /* Otherwise, if we've no PROPFIND to do, we might as
+ well take care of those locally accessible file
+ contents now. */
+ SVN_ERR(handle_local_content(info, info->pool));
+ }
+ }
+ else
+ {
+ /* Otherwise, we use a GET request for the file's contents. */
+ report_fetch_t *fetch_ctx;
+
+ fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx));
+ fetch_ctx->info = info;
+ fetch_ctx->done_list = &ctx->done_fetches;
+ fetch_ctx->sess = ctx->sess;
+ fetch_ctx->conn = conn;
+
+ handler = apr_pcalloc(info->dir->pool, sizeof(*handler));
+
+ handler->handler_pool = info->dir->pool;
+ handler->method = "GET";
+ handler->path = fetch_ctx->info->url;
+
+ handler->conn = conn;
+ handler->session = ctx->sess;
+
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = headers_fetch;
+ handler->header_delegate_baton = fetch_ctx;
+
+ handler->response_handler = handle_fetch;
+ handler->response_baton = fetch_ctx;
+
+ handler->response_error = cancel_fetch;
+ handler->response_error_baton = fetch_ctx;
+
+ fetch_ctx->handler = handler;
+
+ svn_ra_serf__request_create(handler);
+
+ ctx->num_active_fetches++;
+ }
+ }
+ else if (info->propfind_handler)
+ {
+ svn_ra_serf__list_t *list_item;
+
+ list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
+ list_item->data = info;
+ list_item->next = ctx->file_propchanges_only;
+ ctx->file_propchanges_only = list_item;
+ }
+ else
+ {
+ /* No propfind or GET request. Just handle the prop changes now. */
+ SVN_ERR(handle_propchange_only(info, info->pool));
+ }
+
+ if (ctx->num_active_fetches + ctx->num_active_propfinds
+ > REQUEST_COUNT_TO_PAUSE)
+ ctx->parser_ctx->paused = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/** XML callbacks for our update-report response parsing */
+
+static svn_error_t *
+start_report(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = parser->user_data;
+ report_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE && strcmp(name.name, "update-report") == 0)
+ {
+ const char *val;
+
+ val = svn_xml_get_attr_value("inline-props", attrs);
+ if (val && (strcmp(val, "true") == 0))
+ ctx->add_props_included = TRUE;
+
+ val = svn_xml_get_attr_value("send-all", attrs);
+ if (val && (strcmp(val, "true") == 0))
+ {
+ ctx->send_all_mode = TRUE;
+
+ /* All properties are included in send-all mode. */
+ ctx->add_props_included = TRUE;
+ }
+ }
+ else if (state == NONE && strcmp(name.name, "target-revision") == 0)
+ {
+ const char *rev;
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+
+ if (!rev)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in target-revision element"));
+ }
+
+ SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
+ SVN_STR_TO_REV(rev),
+ ctx->sess->pool));
+ }
+ else if (state == NONE && strcmp(name.name, "open-directory") == 0)
+ {
+ const char *rev;
+ report_info_t *info;
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+
+ if (!rev)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-directory element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_DIR);
+
+ info->base_rev = SVN_STR_TO_REV(rev);
+ info->dir->base_rev = info->base_rev;
+ info->fetch_props = TRUE;
+
+ info->dir->base_name = "";
+ info->dir->name = "";
+
+ info->base_name = info->dir->base_name;
+ info->name = info->dir->name;
+
+ info->dir->repos_relpath = svn_hash_gets(ctx->switched_paths, "");
+
+ if (!info->dir->repos_relpath)
+ SVN_ERR(svn_ra_serf__get_relative_path(&info->dir->repos_relpath,
+ ctx->sess->session_url.path,
+ ctx->sess, ctx->conn,
+ info->dir->pool));
+ }
+ else if (state == NONE)
+ {
+ /* do nothing as we haven't seen our valid start tag yet. */
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "open-directory") == 0)
+ {
+ const char *rev, *dirname;
+ report_dir_t *dir;
+ report_info_t *info;
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+
+ if (!rev)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-directory element"));
+ }
+
+ dirname = svn_xml_get_attr_value("name", attrs);
+
+ if (!dirname)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in open-directory element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_DIR);
+
+ dir = info->dir;
+
+ info->base_rev = SVN_STR_TO_REV(rev);
+ dir->base_rev = info->base_rev;
+
+ info->fetch_props = FALSE;
+
+ dir->base_name = apr_pstrdup(dir->pool, dirname);
+ info->base_name = dir->base_name;
+
+ /* Expand our name. */
+ dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
+ dir->pool);
+ info->name = dir->name;
+
+ dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->name);
+
+ if (!dir->repos_relpath)
+ dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
+ dir->base_name, dir->pool);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "add-directory") == 0)
+ {
+ const char *dir_name, *cf, *cr;
+ report_dir_t *dir;
+ report_info_t *info;
+
+ dir_name = svn_xml_get_attr_value("name", attrs);
+ if (!dir_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in add-directory element"));
+ }
+ cf = svn_xml_get_attr_value("copyfrom-path", attrs);
+ cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
+
+ info = push_state(parser, ctx, ADD_DIR);
+
+ dir = info->dir;
+
+ dir->base_name = apr_pstrdup(dir->pool, dir_name);
+ info->base_name = dir->base_name;
+
+ /* Expand our name. */
+ dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
+ dir->pool);
+ info->name = dir->name;
+
+ info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
+ info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
+
+ /* Mark that we don't have a base. */
+ info->base_rev = SVN_INVALID_REVNUM;
+ dir->base_rev = info->base_rev;
+
+ /* If the server isn't included properties for added items,
+ we'll need to fetch them ourselves. */
+ if (! ctx->add_props_included)
+ dir->fetch_props = TRUE;
+
+ dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
+ dir->base_name, dir->pool);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "open-file") == 0)
+ {
+ const char *file_name, *rev;
+ report_info_t *info;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+
+ if (!file_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in open-file element"));
+ }
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+
+ if (!rev)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-file element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_FILE);
+
+ info->base_rev = SVN_STR_TO_REV(rev);
+ info->fetch_props = FALSE;
+
+ info->base_name = apr_pstrdup(info->pool, file_name);
+ info->name = NULL;
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "add-file") == 0)
+ {
+ const char *file_name, *cf, *cr;
+ report_info_t *info;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+ cf = svn_xml_get_attr_value("copyfrom-path", attrs);
+ cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
+
+ if (!file_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in add-file element"));
+ }
+
+ info = push_state(parser, ctx, ADD_FILE);
+
+ info->base_rev = SVN_INVALID_REVNUM;
+
+ /* If the server isn't in "send-all" mode, we should expect to
+ fetch contents for added files. */
+ if (! ctx->send_all_mode)
+ info->fetch_file = TRUE;
+
+ /* If the server isn't included properties for added items,
+ we'll need to fetch them ourselves. */
+ if (! ctx->add_props_included)
+ info->fetch_props = TRUE;
+
+ info->base_name = apr_pstrdup(info->pool, file_name);
+ info->name = NULL;
+
+ info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
+ info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
+
+ info->final_sha1_checksum =
+ svn_xml_get_attr_value("sha1-checksum", attrs);
+ if (info->final_sha1_checksum)
+ info->final_sha1_checksum = apr_pstrdup(info->pool,
+ info->final_sha1_checksum);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "delete-entry") == 0)
+ {
+ const char *file_name;
+ const char *rev_str;
+ report_info_t *info;
+ apr_pool_t *tmppool;
+ const char *full_path;
+ svn_revnum_t delete_rev = SVN_INVALID_REVNUM;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+
+ if (!file_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in delete-entry element"));
+ }
+
+ rev_str = svn_xml_get_attr_value("rev", attrs);
+ if (rev_str) /* Not available on older repositories! */
+ delete_rev = SVN_STR_TO_REV(rev_str);
+
+ info = parser->state->private;
+
+ SVN_ERR(ensure_dir_opened(info->dir));
+
+ tmppool = svn_pool_create(info->dir->dir_baton_pool);
+
+ full_path = svn_relpath_join(info->dir->name, file_name, tmppool);
+
+ SVN_ERR(ctx->update_editor->delete_entry(full_path,
+ delete_rev,
+ info->dir->dir_baton,
+ tmppool));
+
+ svn_pool_destroy(tmppool);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "absent-directory") == 0)
+ {
+ const char *file_name;
+ report_info_t *info;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+
+ if (!file_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in absent-directory element"));
+ }
+
+ info = parser->state->private;
+
+ SVN_ERR(ensure_dir_opened(info->dir));
+
+ SVN_ERR(ctx->update_editor->absent_directory(
+ svn_relpath_join(info->name, file_name,
+ info->dir->pool),
+ info->dir->dir_baton,
+ info->dir->pool));
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "absent-file") == 0)
+ {
+ const char *file_name;
+ report_info_t *info;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+
+ if (!file_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in absent-file element"));
+ }
+
+ info = parser->state->private;
+
+ SVN_ERR(ensure_dir_opened(info->dir));
+
+ SVN_ERR(ctx->update_editor->absent_file(
+ svn_relpath_join(info->name, file_name,
+ info->dir->pool),
+ info->dir->dir_baton,
+ info->dir->pool));
+ }
+ else if (state == OPEN_DIR || state == ADD_DIR)
+ {
+ report_info_t *info;
+
+ if (strcmp(name.name, "checked-in") == 0)
+ {
+ info = push_state(parser, ctx, IGNORE_PROP_NAME);
+ info->prop_ns = name.namespace;
+ info->prop_name = apr_pstrdup(parser->state->pool, name.name);
+ info->prop_encoding = NULL;
+ svn_stringbuf_setempty(info->prop_value);
+ }
+ else if (strcmp(name.name, "set-prop") == 0 ||
+ strcmp(name.name, "remove-prop") == 0)
+ {
+ const char *full_prop_name;
+ const char *colon;
+
+ info = push_state(parser, ctx, PROP);
+
+ full_prop_name = svn_xml_get_attr_value("name", attrs);
+ if (!full_prop_name)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in %s element"),
+ name.name);
+ }
+
+ colon = strchr(full_prop_name, ':');
+
+ if (colon)
+ colon++;
+ else
+ colon = full_prop_name;
+
+ info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
+ colon - full_prop_name);
+ info->prop_name = apr_pstrdup(parser->state->pool, colon);
+ info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
+ svn_stringbuf_setempty(info->prop_value);
+ }
+ else if (strcmp(name.name, "prop") == 0)
+ {
+ /* need to fetch it. */
+ push_state(parser, ctx, NEED_PROP_NAME);
+ }
+ else if (strcmp(name.name, "fetch-props") == 0)
+ {
+ info = parser->state->private;
+
+ info->dir->fetch_props = TRUE;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unknown tag '%s' while at state %d"),
+ name.name, state);
+ }
+
+ }
+ else if (state == OPEN_FILE || state == ADD_FILE)
+ {
+ report_info_t *info;
+
+ if (strcmp(name.name, "checked-in") == 0)
+ {
+ info = push_state(parser, ctx, IGNORE_PROP_NAME);
+ info->prop_ns = name.namespace;
+ info->prop_name = apr_pstrdup(parser->state->pool, name.name);
+ info->prop_encoding = NULL;
+ svn_stringbuf_setempty(info->prop_value);
+ }
+ else if (strcmp(name.name, "prop") == 0)
+ {
+ /* need to fetch it. */
+ push_state(parser, ctx, NEED_PROP_NAME);
+ }
+ else if (strcmp(name.name, "fetch-props") == 0)
+ {
+ info = parser->state->private;
+
+ info->fetch_props = TRUE;
+ }
+ else if (strcmp(name.name, "fetch-file") == 0)
+ {
+ info = parser->state->private;
+ info->base_checksum = svn_xml_get_attr_value("base-checksum", attrs);
+
+ if (info->base_checksum)
+ info->base_checksum = apr_pstrdup(info->pool, info->base_checksum);
+
+ info->final_sha1_checksum =
+ svn_xml_get_attr_value("sha1-checksum", attrs);
+ if (info->final_sha1_checksum)
+ info->final_sha1_checksum = apr_pstrdup(info->pool,
+ info->final_sha1_checksum);
+
+ info->fetch_file = TRUE;
+ }
+ else if (strcmp(name.name, "set-prop") == 0 ||
+ strcmp(name.name, "remove-prop") == 0)
+ {
+ const char *full_prop_name;
+ const char *colon;
+
+ info = push_state(parser, ctx, PROP);
+
+ full_prop_name = svn_xml_get_attr_value("name", attrs);
+ if (!full_prop_name)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in %s element"),
+ name.name);
+ }
+ colon = strchr(full_prop_name, ':');
+
+ if (colon)
+ colon++;
+ else
+ colon = full_prop_name;
+
+ info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
+ colon - full_prop_name);
+ info->prop_name = apr_pstrdup(parser->state->pool, colon);
+ info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
+ svn_stringbuf_setempty(info->prop_value);
+ }
+ else if (strcmp(name.name, "txdelta") == 0)
+ {
+ /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in
+ addition to <fetch-file>s and such) when *not* in
+ "send-all" mode. As a client, we're smart enough to know
+ that's wrong, so we'll just ignore these tags. */
+ if (ctx->send_all_mode)
+ {
+ const svn_delta_editor_t *update_editor = ctx->update_editor;
+
+ info = push_state(parser, ctx, TXDELTA);
+
+ if (! info->file_baton)
+ {
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
+ }
+
+ info->base_checksum = svn_xml_get_attr_value("base-checksum",
+ attrs);
+ SVN_ERR(update_editor->apply_textdelta(info->file_baton,
+ info->base_checksum,
+ info->editor_pool,
+ &info->textdelta,
+ &info->textdelta_baton));
+ info->svndiff_decoder = svn_txdelta_parse_svndiff(
+ info->textdelta,
+ info->textdelta_baton,
+ TRUE, info->pool);
+ info->base64_decoder = svn_base64_decode(info->svndiff_decoder,
+ info->pool);
+ }
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unknown tag '%s' while at state %d"),
+ name.name, state);
+ }
+ }
+ else if (state == IGNORE_PROP_NAME)
+ {
+ report_info_t *info = push_state(parser, ctx, PROP);
+ info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
+ }
+ else if (state == NEED_PROP_NAME)
+ {
+ report_info_t *info;
+
+ info = push_state(parser, ctx, PROP);
+
+ info->prop_ns = name.namespace;
+ info->prop_name = apr_pstrdup(parser->state->pool, name.name);
+ info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
+ svn_stringbuf_setempty(info->prop_value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_report(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = parser->user_data;
+ report_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE)
+ {
+ if (strcmp(name.name, "update-report") == 0)
+ {
+ ctx->report_completed = TRUE;
+ }
+ else
+ {
+ /* nothing to close yet. */
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) ||
+ (state == ADD_DIR && (strcmp(name.name, "add-directory") == 0))))
+ {
+ const char *checked_in_url;
+ report_info_t *info = parser->state->private;
+
+ /* We've now closed this directory; note it. */
+ info->dir->tag_closed = TRUE;
+
+ /* go fetch info->file_name from DAV:checked-in */
+ checked_in_url =
+ svn_ra_serf__get_ver_prop(info->dir->props, info->base_name,
+ info->base_rev, "DAV:", "checked-in");
+
+ /* If we were expecting to have the properties and we aren't able to
+ * get it, bail.
+ */
+ if (!checked_in_url &&
+ (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props))
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT or PROPFIND response did not "
+ "include the requested checked-in value"));
+ }
+
+ info->dir->url = checked_in_url;
+
+ /* At this point, we should have the checked-in href.
+ * If needed, create the PROPFIND to retrieve the dir's properties.
+ */
+ if (info->dir->fetch_props)
+ {
+ svn_ra_serf__list_t *list_item;
+
+ SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind_handler,
+ info->dir->props, ctx->sess,
+ get_best_connection(ctx),
+ info->dir->url,
+ ctx->target_rev, "0",
+ all_props,
+ &ctx->done_dir_propfinds,
+ info->dir->pool));
+ SVN_ERR_ASSERT(info->dir->propfind_handler);
+
+ /* Create a serf request for the PROPFIND. */
+ svn_ra_serf__request_create(info->dir->propfind_handler);
+
+ ctx->num_active_propfinds++;
+
+ list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
+ list_item->data = info->dir;
+ list_item->next = ctx->active_dir_propfinds;
+ ctx->active_dir_propfinds = list_item;
+
+ if (ctx->num_active_fetches + ctx->num_active_propfinds
+ > REQUEST_COUNT_TO_PAUSE)
+ ctx->parser_ctx->paused = TRUE;
+ }
+ else
+ {
+ info->dir->propfind_handler = NULL;
+ }
+
+ /* See if this directory (and perhaps even parents of that) can
+ be closed now. This is likely to be the case only if we
+ didn't need to contact the server for supplemental
+ information required to handle any of this directory's
+ children. */
+ SVN_ERR(maybe_close_dir_chain(info->dir));
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
+ {
+ report_info_t *info = parser->state->private;
+
+ /* Expand our full name now if we haven't done so yet. */
+ if (!info->name)
+ {
+ info->name = svn_relpath_join(info->dir->name, info->base_name,
+ info->pool);
+ }
+
+ info->lock_token = svn_hash_gets(ctx->lock_path_tokens, info->name);
+
+ if (info->lock_token && !info->fetch_props)
+ info->fetch_props = TRUE;
+
+ /* If possible, we'd like to fetch only a delta against a
+ * version of the file we already have in our working copy,
+ * rather than fetching a fulltext.
+ *
+ * In HTTP v2, we can simply construct the URL we need given the
+ * repos_relpath and base revision number.
+ */
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
+ {
+ const char *repos_relpath;
+
+ /* If this file is switched vs the editor root we should provide
+ its real url instead of the one calculated from the session root.
+ */
+ repos_relpath = svn_hash_gets(ctx->switched_paths, info->name);
+
+ if (!repos_relpath)
+ {
+ if (ctx->root_is_switched)
+ {
+ /* We are updating a direct target (most likely a file)
+ that is switched vs its parent url */
+ SVN_ERR_ASSERT(*svn_relpath_dirname(info->name, info->pool)
+ == '\0');
+
+ repos_relpath = svn_hash_gets(ctx->switched_paths, "");
+ }
+ else
+ repos_relpath = svn_relpath_join(info->dir->repos_relpath,
+ info->base_name, info->pool);
+ }
+
+ info->delta_base = apr_psprintf(info->pool, "%s/%ld/%s",
+ ctx->sess->rev_root_stub,
+ info->base_rev,
+ svn_path_uri_encode(repos_relpath,
+ info->pool));
+ }
+ else if (ctx->sess->wc_callbacks->get_wc_prop)
+ {
+ /* If we have a WC, we might be able to dive all the way into the WC
+ * to get the previous URL so we can do a differential GET with the
+ * base URL.
+ */
+ const svn_string_t *value = NULL;
+ SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
+ ctx->sess->wc_callback_baton, info->name,
+ SVN_RA_SERF__WC_CHECKED_IN_URL, &value, info->pool));
+
+ info->delta_base = value ? value->data : NULL;
+ }
+
+ /* go fetch info->name from DAV:checked-in */
+ info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
+ info->base_rev, "DAV:", "checked-in");
+ if (!info->url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT or PROPFIND response did not "
+ "include the requested checked-in value"));
+ }
+
+ /* If the server is in "send-all" mode, we might have opened the
+ file when we started seeing content for it. If we didn't get
+ any content for it, we still need to open the file. But in
+ any case, we can then immediately close it. */
+ if (ctx->send_all_mode)
+ {
+ if (! info->file_baton)
+ {
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
+ }
+ SVN_ERR(close_updated_file(info, info->pool));
+ info->dir->ref_count--;
+ }
+ /* Otherwise, if the server is *not* in "send-all" mode, we
+ should be at a point where we can queue up any auxiliary
+ content-fetching requests. */
+ else
+ {
+ SVN_ERR(fetch_file(ctx, info));
+ }
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
+ {
+ report_info_t *info = parser->state->private;
+
+ /* go fetch info->name from DAV:checked-in */
+ info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
+ info->base_rev, "DAV:", "checked-in");
+ if (!info->url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT or PROPFIND response did not "
+ "include the requested checked-in value"));
+ }
+
+ /* If the server is in "send-all" mode, we might have opened the
+ file when we started seeing content for it. If we didn't get
+ any content for it, we still need to open the file. But in
+ any case, we can then immediately close it. */
+ if (ctx->send_all_mode)
+ {
+ if (! info->file_baton)
+ {
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
+ }
+ SVN_ERR(close_updated_file(info, info->pool));
+ info->dir->ref_count--;
+ }
+ /* Otherwise, if the server is *not* in "send-all" mode, we
+ should be at a point where we can queue up any auxiliary
+ content-fetching requests. */
+ else
+ {
+ SVN_ERR(fetch_file(ctx, info));
+ }
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == TXDELTA && strcmp(name.name, "txdelta") == 0)
+ {
+ report_info_t *info = parser->state->private;
+
+ /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
+ <fetch-file>s and such) when *not* in "send-all" mode. As a
+ client, we're smart enough to know that's wrong, so when not
+ in "receiving-all" mode, we'll ignore these tags. */
+ if (ctx->send_all_mode)
+ {
+ SVN_ERR(svn_stream_close(info->base64_decoder));
+ }
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == PROP)
+ {
+ /* We need to move the prop_ns, prop_name, and prop_value into the
+ * same lifetime as the dir->pool.
+ */
+ svn_ra_serf__ns_t *ns, *ns_name_match;
+ svn_boolean_t found = FALSE;
+ report_info_t *info;
+ report_dir_t *dir;
+ apr_hash_t *props;
+ const svn_string_t *set_val_str;
+ apr_pool_t *pool;
+
+ info = parser->state->private;
+ dir = info->dir;
+
+ /* We're going to be slightly tricky. We don't care what the ->url
+ * field is here at this point. So, we're going to stick a single
+ * copy of the property name inside of the ->url field.
+ */
+ ns_name_match = NULL;
+ for (ns = dir->ns_list; ns; ns = ns->next)
+ {
+ if (strcmp(ns->namespace, info->prop_ns) == 0)
+ {
+ ns_name_match = ns;
+ if (strcmp(ns->url, info->prop_name) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ {
+ ns = apr_palloc(dir->pool, sizeof(*ns));
+ if (!ns_name_match)
+ {
+ ns->namespace = apr_pstrdup(dir->pool, info->prop_ns);
+ }
+ else
+ {
+ ns->namespace = ns_name_match->namespace;
+ }
+ ns->url = apr_pstrdup(dir->pool, info->prop_name);
+
+ ns->next = dir->ns_list;
+ dir->ns_list = ns;
+ }
+
+ if (strcmp(name.name, "remove-prop") != 0)
+ {
+ props = info->props;
+ pool = info->pool;
+ }
+ else
+ {
+ props = dir->removed_props;
+ pool = dir->pool;
+ svn_stringbuf_setempty(info->prop_value);
+ }
+
+ if (info->prop_encoding)
+ {
+ if (strcmp(info->prop_encoding, "base64") == 0)
+ {
+ svn_string_t tmp;
+
+ /* Don't use morph_info_string cuz we need prop_value to
+ remain usable. */
+ tmp.data = info->prop_value->data;
+ tmp.len = info->prop_value->len;
+
+ set_val_str = svn_base64_decode_string(&tmp, pool);
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ NULL,
+ _("Got unrecognized encoding '%s'"),
+ info->prop_encoding);
+ }
+ }
+ else
+ {
+ set_val_str = svn_string_create_from_buf(info->prop_value, pool);
+ }
+
+ svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev,
+ ns->namespace, ns->url, set_val_str, pool);
+
+ /* Advance handling: if we spotted the md5-checksum property on
+ the wire, remember it's value. */
+ if (strcmp(ns->url, "md5-checksum") == 0
+ && strcmp(ns->namespace, SVN_DAV_PROP_NS_DAV) == 0)
+ info->final_checksum = apr_pstrdup(info->pool, set_val_str->data);
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_report(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = parser->user_data;
+
+ UNUSED_CTX(ctx);
+
+ if (parser->state->current_state == PROP)
+ {
+ report_info_t *info = parser->state->private;
+
+ svn_stringbuf_appendbytes(info->prop_value, data, len);
+ }
+ else if (parser->state->current_state == TXDELTA)
+ {
+ /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
+ <fetch-file>s and such) when *not* in "send-all" mode. As a
+ client, we're smart enough to know that's wrong, so when not
+ in "receiving-all" mode, we'll ignore these tags. */
+ if (ctx->send_all_mode)
+ {
+ apr_size_t nlen = len;
+ report_info_t *info = parser->state->private;
+
+ SVN_ERR(svn_stream_write(info->base64_decoder, data, &nlen));
+ if (nlen != len)
+ {
+ /* Short write without associated error? "Can't happen." */
+ return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
+ _("Error writing to '%s': unexpected EOF"),
+ info->name);
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/** Editor callbacks given to callers to create request body */
+
+/* Helper to create simple xml tag without attributes. */
+static void
+make_simple_xml_tag(svn_stringbuf_t **buf_p,
+ const char *tagname,
+ const char *cdata,
+ apr_pool_t *pool)
+{
+ svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, NULL);
+ svn_xml_escape_cdata_cstring(buf_p, cdata, pool);
+ svn_xml_make_close_tag(buf_p, pool, tagname);
+}
+
+static svn_error_t *
+set_path(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ report_context_t *report = report_baton;
+ svn_stringbuf_t *buf = NULL;
+
+ svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
+ "rev", apr_ltoa(pool, revision),
+ "lock-token", lock_token,
+ "depth", svn_depth_to_word(depth),
+ "start-empty", start_empty ? "true" : NULL,
+ NULL);
+ svn_xml_escape_cdata_cstring(&buf, path, pool);
+ svn_xml_make_close_tag(&buf, pool, "S:entry");
+
+ SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
+ NULL, pool));
+
+ if (lock_token)
+ {
+ svn_hash_sets(report->lock_path_tokens,
+ apr_pstrdup(report->pool, path),
+ apr_pstrdup(report->pool, lock_token));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_path(void *report_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ report_context_t *report = report_baton;
+ svn_stringbuf_t *buf = NULL;
+
+ make_simple_xml_tag(&buf, "S:missing", path, pool);
+
+ SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
+ NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+link_path(void *report_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ report_context_t *report = report_baton;
+ const char *link, *report_target;
+ apr_uri_t uri;
+ apr_status_t status;
+ svn_stringbuf_t *buf = NULL;
+
+ /* We need to pass in the baseline relative path.
+ *
+ * TODO Confirm that it's on the same server?
+ */
+ status = apr_uri_parse(pool, url, &uri);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unable to parse URL '%s'"), url);
+ }
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess,
+ NULL, pool));
+ SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess,
+ NULL, pool));
+
+ link = apr_pstrcat(pool, "/", link, (char *)NULL);
+
+ svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
+ "rev", apr_ltoa(pool, revision),
+ "lock-token", lock_token,
+ "depth", svn_depth_to_word(depth),
+ "linkpath", link,
+ "start-empty", start_empty ? "true" : NULL,
+ NULL);
+ svn_xml_escape_cdata_cstring(&buf, path, pool);
+ svn_xml_make_close_tag(&buf, pool, "S:entry");
+
+ SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
+ NULL, pool));
+
+ /* Store the switch roots to allow generating repos_relpaths from just
+ the working copy paths. (Needed for HTTPv2) */
+ path = apr_pstrdup(report->pool, path);
+ svn_hash_sets(report->switched_paths,
+ path, apr_pstrdup(report->pool, link + 1));
+
+ if (!*path)
+ report->root_is_switched = TRUE;
+
+ if (lock_token)
+ {
+ svn_hash_sets(report->lock_path_tokens,
+ path, apr_pstrdup(report->pool, lock_token));
+ }
+
+ return APR_SUCCESS;
+}
+
+/** Minimum nr. of outstanding requests needed before a new connection is
+ * opened. */
+#define REQS_PER_CONN 8
+
+/** This function creates a new connection for this serf session, but only
+ * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
+ * only one main connection open.
+ */
+static svn_error_t *
+open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
+{
+ /* For each REQS_PER_CONN outstanding requests open a new connection, with
+ * a minimum of 1 extra connection. */
+ if (sess->num_conns == 1 ||
+ ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
+ {
+ int cur = sess->num_conns;
+ apr_status_t status;
+
+ sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
+ sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
+ NULL, NULL);
+ sess->conns[cur]->last_status_code = -1;
+ sess->conns[cur]->session = sess;
+ status = serf_connection_create2(&sess->conns[cur]->conn,
+ sess->context,
+ sess->session_url,
+ svn_ra_serf__conn_setup,
+ sess->conns[cur],
+ svn_ra_serf__conn_closed,
+ sess->conns[cur],
+ sess->pool);
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+
+ sess->num_conns++;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Serf callback to create update request body bucket. */
+static svn_error_t *
+create_update_report_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ report_context_t *report = baton;
+ apr_off_t offset;
+
+ offset = 0;
+ apr_file_seek(report->body_file, APR_SET, &offset);
+
+ *body_bkt = serf_bucket_file_create(report->body_file, alloc);
+
+ return SVN_NO_ERROR;
+}
+
+/* Serf callback to setup update request headers. */
+static svn_error_t *
+setup_update_report_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ report_context_t *report = baton;
+
+ if (report->sess->using_compression)
+ {
+ serf_bucket_headers_setn(headers, "Accept-Encoding",
+ "gzip;svndiff1;q=0.9,svndiff;q=0.8");
+ }
+ else
+ {
+ serf_bucket_headers_setn(headers, "Accept-Encoding",
+ "svndiff1;q=0.9,svndiff;q=0.8");
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+finish_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ report_context_t *report = report_baton;
+ svn_ra_serf__session_t *sess = report->sess;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+ const char *report_target;
+ svn_stringbuf_t *buf = NULL;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_error_t *err;
+ apr_interval_time_t waittime_left = sess->timeout;
+
+ svn_xml_make_close_tag(&buf, iterpool, "S:update-report");
+ SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
+ NULL, iterpool));
+
+ /* We need to flush the file, make it unbuffered (so that it can be
+ * zero-copied via mmap), and reset the position before attempting to
+ * deliver the file.
+ *
+ * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
+ * and zero-copy the PUT body. However, on older APR versions, we can't
+ * check the buffer status; but serf will fall through and create a file
+ * bucket for us on the buffered svndiff handle.
+ */
+ apr_file_flush(report->body_file);
+#if APR_VERSION_AT_LEAST(1, 3, 0)
+ apr_file_buffer_set(report->body_file, NULL, 0);
+#endif
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, NULL, pool));
+
+ /* create and deliver request */
+ report->path = report_target;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ handler->handler_pool = pool;
+ handler->method = "REPORT";
+ handler->path = report->path;
+ handler->body_delegate = create_update_report_body;
+ handler->body_delegate_baton = report;
+ handler->body_type = "text/xml";
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = setup_update_report_headers;
+ handler->header_delegate_baton = report;
+ handler->conn = sess->conns[0];
+ handler->session = sess;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ parser_ctx->response_type = "update-report";
+ parser_ctx->user_data = report;
+ parser_ctx->start = start_report;
+ parser_ctx->end = end_report;
+ parser_ctx->cdata = cdata_report;
+ parser_ctx->done = &report->done;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ report->parser_ctx = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ /* Open the first extra connection. */
+ SVN_ERR(open_connection_if_needed(sess, 0));
+
+ sess->cur_conn = 1;
+
+ /* Note that we may have no active GET or PROPFIND requests, yet the
+ processing has not been completed. This could be from a delay on the
+ network or because we've spooled the entire response into our "pending"
+ content of the XML parser. The DONE flag will get set when all the
+ XML content has been received *and* parsed. */
+ while (!report->done
+ || report->num_active_fetches
+ || report->num_active_propfinds)
+ {
+ apr_pool_t *iterpool_inner;
+ svn_ra_serf__list_t *done_list;
+ int i;
+ apr_status_t status;
+
+ /* Note: this throws out the old ITERPOOL_INNER. */
+ svn_pool_clear(iterpool);
+
+ if (sess->cancel_func)
+ SVN_ERR(sess->cancel_func(sess->cancel_baton));
+
+ /* We need to be careful between the outer and inner ITERPOOLs,
+ and what items are allocated within. */
+ iterpool_inner = svn_pool_create(iterpool);
+
+ status = serf_context_run(sess->context,
+ SVN_RA_SERF__CONTEXT_RUN_DURATION,
+ iterpool_inner);
+
+ err = sess->pending_error;
+ sess->pending_error = SVN_NO_ERROR;
+
+ if (!err && handler->done && handler->server_error)
+ {
+ err = handler->server_error->error;
+ }
+
+ /* If the context duration timeout is up, we'll subtract that
+ duration from the total time alloted for such things. If
+ there's no time left, we fail with a message indicating that
+ the connection timed out. */
+ if (APR_STATUS_IS_TIMEUP(status))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ status = 0;
+
+ if (sess->timeout)
+ {
+ if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
+ {
+ waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
+ _("Connection timed out"));
+ }
+ }
+ }
+ else
+ {
+ waittime_left = sess->timeout;
+ }
+
+ if (status && handler->sline.code != 200)
+ {
+ return svn_error_trace(
+ svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location),
+ err));
+ }
+ SVN_ERR(err);
+ if (status)
+ {
+ return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT"));
+ }
+
+ /* Open extra connections if we have enough requests to send. */
+ if (sess->num_conns < sess->max_connections)
+ SVN_ERR(open_connection_if_needed(sess, report->num_active_fetches +
+ report->num_active_propfinds));
+
+ /* Prune completed file PROPFINDs. */
+ done_list = report->done_propfinds;
+ while (done_list)
+ {
+ svn_ra_serf__list_t *next_done = done_list->next;
+
+ svn_pool_clear(iterpool_inner);
+
+ report->num_active_propfinds--;
+
+ /* If we have some files that we won't be fetching the content
+ * for, ensure that we update the file with any altered props.
+ */
+ if (report->file_propchanges_only)
+ {
+ svn_ra_serf__list_t *cur, *prev;
+
+ prev = NULL;
+ cur = report->file_propchanges_only;
+
+ while (cur)
+ {
+ report_info_t *item = cur->data;
+
+ if (item->propfind_handler == done_list->data)
+ {
+ break;
+ }
+
+ prev = cur;
+ cur = cur->next;
+ }
+
+ /* If we found a match, set the new props and remove this
+ * propchange from our list.
+ */
+ if (cur)
+ {
+ report_info_t *info = cur->data;
+
+ if (!prev)
+ {
+ report->file_propchanges_only = cur->next;
+ }
+ else
+ {
+ prev->next = cur->next;
+ }
+
+ /* If we've got cached file content for this file,
+ take care of the locally collected properties and
+ file content at once. Otherwise, just deal with
+ the collected properties.
+
+ NOTE: These functions below could delete
+ info->dir->pool (via maybe_close_dir_chain()),
+ from which is allocated the list item in
+ report->file_propchanges_only.
+ */
+ if (info->cached_contents)
+ {
+ SVN_ERR(handle_local_content(info, iterpool_inner));
+ }
+ else
+ {
+ SVN_ERR(handle_propchange_only(info, iterpool_inner));
+ }
+ }
+ }
+
+ done_list = next_done;
+ }
+ report->done_propfinds = NULL;
+
+ /* Prune completed fetches from our list. */
+ done_list = report->done_fetches;
+ while (done_list)
+ {
+ report_fetch_t *done_fetch = done_list->data;
+ svn_ra_serf__list_t *next_done = done_list->next;
+ report_dir_t *cur_dir;
+
+ /* Decrease the refcount in the parent directory of the file
+ whose fetch has completed. */
+ cur_dir = done_fetch->info->dir;
+ cur_dir->ref_count--;
+
+ /* Decrement our active fetch count. */
+ report->num_active_fetches--;
+
+ /* See if the parent directory of this fetched item (and
+ perhaps even parents of that) can be closed now.
+
+ NOTE: This could delete cur_dir->pool, from which is
+ allocated the list item in report->done_fetches.
+ */
+ SVN_ERR(maybe_close_dir_chain(cur_dir));
+
+ done_list = next_done;
+ }
+ report->done_fetches = NULL;
+
+ /* Prune completed directory PROPFINDs. */
+ done_list = report->done_dir_propfinds;
+ while (done_list)
+ {
+ svn_ra_serf__list_t *next_done = done_list->next;
+
+ report->num_active_propfinds--;
+
+ if (report->active_dir_propfinds)
+ {
+ svn_ra_serf__list_t *cur, *prev;
+
+ prev = NULL;
+ cur = report->active_dir_propfinds;
+
+ while (cur)
+ {
+ report_dir_t *item = cur->data;
+
+ if (item->propfind_handler == done_list->data)
+ {
+ break;
+ }
+
+ prev = cur;
+ cur = cur->next;
+ }
+ SVN_ERR_ASSERT(cur); /* we expect to find a matching propfind! */
+
+ /* If we found a match, set the new props and remove this
+ * propchange from our list.
+ */
+ if (cur)
+ {
+ report_dir_t *cur_dir = cur->data;
+
+ if (!prev)
+ {
+ report->active_dir_propfinds = cur->next;
+ }
+ else
+ {
+ prev->next = cur->next;
+ }
+
+ /* See if this directory (and perhaps even parents of that)
+ can be closed now.
+
+ NOTE: This could delete cur_dir->pool, from which is
+ allocated the list item in report->active_dir_propfinds.
+ */
+ SVN_ERR(maybe_close_dir_chain(cur_dir));
+ }
+ }
+
+ done_list = next_done;
+ }
+ report->done_dir_propfinds = NULL;
+
+ /* If the parser is paused, and the number of active requests has
+ dropped far enough, then resume parsing. */
+ if (parser_ctx->paused
+ && (report->num_active_fetches + report->num_active_propfinds
+ < REQUEST_COUNT_TO_RESUME))
+ parser_ctx->paused = FALSE;
+
+ /* If we have not paused the parser and it looks like data MAY be
+ present (we can't know for sure because of the private structure),
+ then go process the pending content. */
+ if (!parser_ctx->paused && parser_ctx->pending != NULL)
+ SVN_ERR(svn_ra_serf__process_pending(parser_ctx,
+ &report->report_received,
+ iterpool_inner));
+
+ /* Debugging purposes only! */
+ for (i = 0; i < sess->num_conns; i++)
+ {
+ serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
+ }
+ }
+
+ /* If we got a complete report, close the edit. Otherwise, abort it. */
+ if (report->report_completed)
+ {
+ /* Ensure that we opened and closed our root dir and that we closed
+ * all of our children. */
+ if (!report->closed_root && report->root_dir != NULL)
+ {
+ SVN_ERR(close_all_dirs(report->root_dir));
+ }
+
+ err = report->update_editor->close_edit(report->update_baton, iterpool);
+ }
+ else
+ {
+ /* Tell the editor that something failed */
+ err = report->update_editor->abort_edit(report->update_baton, iterpool);
+
+ err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
+ _("Missing update-report close tag"));
+ }
+
+ svn_pool_destroy(iterpool);
+ return svn_error_trace(err);
+}
+
+
+static svn_error_t *
+abort_report(void *report_baton,
+ apr_pool_t *pool)
+{
+#if 0
+ report_context_t *report = report_baton;
+#endif
+
+ /* Should we perform some cleanup here? */
+
+ return SVN_NO_ERROR;
+}
+
+static const svn_ra_reporter3_t ra_serf_reporter = {
+ set_path,
+ delete_path,
+ link_path,
+ finish_report,
+ abort_report
+};
+
+
+/** RA function implementations and body */
+
+static svn_error_t *
+make_update_reporter(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *src_path,
+ const char *dest_path,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *report;
+ const svn_delta_editor_t *filter_editor;
+ void *filter_baton;
+ svn_boolean_t has_target = *update_target != '\0';
+ svn_boolean_t server_supports_depth;
+ svn_ra_serf__session_t *sess = ra_session->priv;
+ svn_stringbuf_t *buf = NULL;
+ svn_boolean_t use_bulk_updates;
+
+ SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, scratch_pool));
+ /* We can skip the depth filtering when the user requested
+ depth_files or depth_infinity because the server will
+ transmit the right stuff anyway. */
+ if ((depth != svn_depth_files)
+ && (depth != svn_depth_infinity)
+ && ! server_supports_depth)
+ {
+ SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
+ &filter_baton,
+ update_editor,
+ update_baton,
+ depth, has_target,
+ sess->pool));
+ update_editor = filter_editor;
+ update_baton = filter_baton;
+ }
+
+ report = apr_pcalloc(result_pool, sizeof(*report));
+ report->pool = result_pool;
+ report->sess = sess;
+ report->conn = report->sess->conns[0];
+ report->target_rev = revision;
+ report->ignore_ancestry = ignore_ancestry;
+ report->send_copyfrom_args = send_copyfrom_args;
+ report->text_deltas = text_deltas;
+ report->lock_path_tokens = apr_hash_make(report->pool);
+ report->switched_paths = apr_hash_make(report->pool);
+
+ report->source = src_path;
+ report->destination = dest_path;
+ report->update_target = update_target;
+
+ report->update_editor = update_editor;
+ report->update_baton = update_baton;
+ report->done = FALSE;
+
+ *reporter = &ra_serf_reporter;
+ *report_baton = report;
+
+ SVN_ERR(svn_io_open_unique_file3(&report->body_file, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ report->pool, scratch_pool));
+
+ if (sess->bulk_updates == svn_tristate_true)
+ {
+ /* User would like to use bulk updates. */
+ use_bulk_updates = TRUE;
+ }
+ else if (sess->bulk_updates == svn_tristate_false)
+ {
+ /* User doesn't want bulk updates. */
+ use_bulk_updates = FALSE;
+ }
+ else
+ {
+ /* User doesn't have any preferences on bulk updates. Decide on server
+ preferences and capabilities. */
+ if (sess->server_allows_bulk)
+ {
+ if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
+ {
+ /* Server doesn't want bulk updates */
+ use_bulk_updates = FALSE;
+ }
+ else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
+ {
+ /* Server prefers bulk updates, and we respect that */
+ use_bulk_updates = TRUE;
+ }
+ else
+ {
+ /* Server allows bulk updates, but doesn't dictate its use. Do
+ whatever is the default. */
+ use_bulk_updates = FALSE;
+ }
+ }
+ else
+ {
+ /* Pre-1.8 server didn't send the bulk_updates header. Check if server
+ supports inlining properties in update editor report. */
+ if (sess->supports_inline_props)
+ {
+ /* Inline props supported: do not use bulk updates. */
+ use_bulk_updates = FALSE;
+ }
+ else
+ {
+ /* Inline props are not supported: use bulk updates to avoid
+ * PROPFINDs for every added node. */
+ use_bulk_updates = TRUE;
+ }
+ }
+ }
+
+ if (use_bulk_updates)
+ {
+ svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
+ "S:update-report",
+ "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
+ NULL);
+ }
+ else
+ {
+ svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
+ "S:update-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+ /* Subversion 1.8+ servers can be told to send properties for newly
+ added items inline even when doing a skelta response. */
+ make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
+ }
+
+ make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool);
+
+ if (SVN_IS_VALID_REVNUM(report->target_rev))
+ {
+ make_simple_xml_tag(&buf, "S:target-revision",
+ apr_ltoa(scratch_pool, report->target_rev),
+ scratch_pool);
+ }
+
+ if (report->destination && *report->destination)
+ {
+ make_simple_xml_tag(&buf, "S:dst-path", report->destination,
+ scratch_pool);
+ }
+
+ if (report->update_target && *report->update_target)
+ {
+ make_simple_xml_tag(&buf, "S:update-target", report->update_target,
+ scratch_pool);
+ }
+
+ if (report->ignore_ancestry)
+ {
+ make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool);
+ }
+
+ if (report->send_copyfrom_args)
+ {
+ make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool);
+ }
+
+ /* Old servers know "recursive" but not "depth"; help them DTRT. */
+ if (depth == svn_depth_files || depth == svn_depth_empty)
+ {
+ make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
+ }
+
+ /* When in 'send-all' mode, mod_dav_svn will assume that it should
+ calculate and transmit real text-deltas (instead of empty windows
+ that merely indicate "text is changed") unless it finds this
+ element.
+
+ NOTE: Do NOT count on servers actually obeying this, as some exist
+ which obey send-all, but do not check for this directive at all!
+
+ NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to
+ override our request and send text-deltas. */
+ if (! text_deltas)
+ {
+ make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool);
+ }
+
+ make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
+
+ SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
+ NULL, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__do_update(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
+ revision_to_update_to,
+ session->session_url.path, NULL, update_target,
+ depth, ignore_ancestry, TRUE /* text_deltas */,
+ send_copyfrom_args,
+ update_editor, update_baton,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
+ revision,
+ session->session_url.path, versus_url, diff_target,
+ depth, ignore_ancestry, text_deltas, FALSE,
+ diff_editor, diff_baton,
+ pool, scratch_pool));
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__do_status(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
+ revision,
+ session->session_url.path, NULL, status_target,
+ depth, FALSE, FALSE, FALSE,
+ status_editor, status_baton,
+ pool, scratch_pool));
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_depth_t depth,
+ const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ return make_update_reporter(ra_session, reporter, report_baton,
+ revision_to_switch_to,
+ session->session_url.path,
+ switch_url, switch_target,
+ depth,
+ ignore_ancestry,
+ TRUE /* text_deltas */,
+ send_copyfrom_args,
+ switch_editor, switch_baton,
+ result_pool, scratch_pool);
+}
+
+/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents
+ * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is
+ * present in PROPS.
+ *
+ * Sets *FOUND_P to TRUE if file contents was successfuly fetched.
+ *
+ * Performs all temporary allocations in POOL.
+ */
+static svn_error_t *
+try_get_wc_contents(svn_boolean_t *found_p,
+ svn_ra_serf__session_t *session,
+ apr_hash_t *props,
+ svn_stream_t *dst_stream,
+ apr_pool_t *pool)
+{
+ apr_hash_t *svn_props;
+ const char *sha1_checksum_prop;
+ svn_checksum_t *checksum;
+ svn_stream_t *wc_stream;
+ svn_error_t *err;
+
+ /* No contents found by default. */
+ *found_p = FALSE;
+
+ if (!session->wc_callbacks->get_wc_contents)
+ {
+ /* No callback, nothing to do. */
+ return SVN_NO_ERROR;
+ }
+
+
+ svn_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
+ if (!svn_props)
+ {
+ /* No properties -- therefore no checksum property -- in response. */
+ return SVN_NO_ERROR;
+ }
+
+ sha1_checksum_prop = svn_prop_get_value(svn_props, "sha1-checksum");
+ if (sha1_checksum_prop == NULL)
+ {
+ /* No checksum property in response. */
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
+ sha1_checksum_prop, pool));
+
+ err = session->wc_callbacks->get_wc_contents(
+ session->wc_callback_baton, &wc_stream, checksum, pool);
+
+ if (err)
+ {
+ svn_error_clear(err);
+
+ /* Ignore errors for now. */
+ return SVN_NO_ERROR;
+ }
+
+ if (wc_stream)
+ {
+ SVN_ERR(svn_stream_copy3(wc_stream,
+ svn_stream_disown(dst_stream, pool),
+ NULL, NULL, pool));
+ *found_p = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_file(svn_ra_session_t *ra_session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__connection_t *conn;
+ const char *fetch_url;
+ apr_hash_t *fetch_props;
+ svn_node_kind_t res_kind;
+ const svn_ra_serf__dav_props_t *which_props;
+
+ /* What connection should we go on? */
+ conn = session->conns[session->cur_conn];
+
+ /* Fetch properties. */
+
+ fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool);
+
+ /* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
+ *
+ * Otherwise, we need to get the baseline version for this particular
+ * revision and then fetch that file.
+ */
+ if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
+ {
+ SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
+ session, conn,
+ fetch_url, revision,
+ pool, pool));
+ revision = SVN_INVALID_REVNUM;
+ }
+ /* REVISION is always SVN_INVALID_REVNUM */
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
+
+ if (props)
+ {
+ which_props = all_props;
+ }
+ else if (stream && session->wc_callbacks->get_wc_contents)
+ {
+ which_props = type_and_checksum_props;
+ }
+ else
+ {
+ which_props = check_path_props;
+ }
+
+ SVN_ERR(svn_ra_serf__fetch_node_props(&fetch_props, conn, fetch_url,
+ SVN_INVALID_REVNUM,
+ which_props,
+ pool, pool));
+
+ /* Verify that resource type is not collection. */
+ SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props));
+ if (res_kind != svn_node_file)
+ {
+ return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
+ _("Can't get text contents of a directory"));
+ }
+
+ /* TODO Filter out all of our props into a usable format. */
+ if (props)
+ {
+ /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
+ ### put them into POOL, so we're okay. */
+ SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props,
+ pool, pool));
+ }
+
+ if (stream)
+ {
+ svn_boolean_t found;
+ SVN_ERR(try_get_wc_contents(&found, session, fetch_props, stream, pool));
+
+ /* No contents found in the WC, let's fetch from server. */
+ if (!found)
+ {
+ report_fetch_t *stream_ctx;
+ svn_ra_serf__handler_t *handler;
+
+ /* Create the fetch context. */
+ stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
+ stream_ctx->target_stream = stream;
+ stream_ctx->sess = session;
+ stream_ctx->conn = conn;
+ stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info));
+ stream_ctx->info->name = fetch_url;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ handler->handler_pool = pool;
+ handler->method = "GET";
+ handler->path = fetch_url;
+ handler->conn = conn;
+ handler->session = session;
+
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = headers_fetch;
+ handler->header_delegate_baton = stream_ctx;
+
+ handler->response_handler = handle_stream;
+ handler->response_baton = stream_ctx;
+
+ handler->response_error = cancel_fetch;
+ handler->response_error_baton = stream_ctx;
+
+ stream_ctx->handler = handler;
+
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/util.c b/contrib/subversion/subversion/libsvn_ra_serf/util.c
new file mode 100644
index 0000000..c7a1716
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/util.c
@@ -0,0 +1,2614 @@
+/*
+ * util.c : serf utility routines for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <assert.h>
+
+#define APR_WANT_STRFUNC
+#include <apr.h>
+#include <apr_want.h>
+#include <apr_fnmatch.h>
+
+#include <serf.h>
+#include <serf_bucket_types.h>
+
+#include <expat.h>
+
+#include "svn_hash.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+#include "svn_string.h"
+#include "svn_xml.h"
+#include "svn_props.h"
+#include "svn_dirent_uri.h"
+
+#include "../libsvn_ra/ra_loader.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
+
+#include "ra_serf.h"
+
+
+/* Fix for older expat 1.95.x's that do not define
+ * XML_STATUS_OK/XML_STATUS_ERROR
+ */
+#ifndef XML_STATUS_OK
+#define XML_STATUS_OK 1
+#define XML_STATUS_ERROR 0
+#endif
+
+#ifndef XML_VERSION_AT_LEAST
+#define XML_VERSION_AT_LEAST(major,minor,patch) \
+(((major) < XML_MAJOR_VERSION) \
+ || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION) \
+ || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
+ (patch) <= XML_MICRO_VERSION))
+#endif /* APR_VERSION_AT_LEAST */
+
+#if XML_VERSION_AT_LEAST(1, 95, 8)
+#define EXPAT_HAS_STOPPARSER
+#endif
+
+/* Read/write chunks of this size into the spillbuf. */
+#define PARSE_CHUNK_SIZE 8000
+
+/* We will store one megabyte in memory, before switching to store content
+ into a temporary file. */
+#define SPILL_SIZE 1000000
+
+
+/* This structure records pending data for the parser in memory blocks,
+ and possibly into a temporary file if "too much" content arrives. */
+struct svn_ra_serf__pending_t {
+ /* The spillbuf where we record the pending data. */
+ svn_spillbuf_t *buf;
+
+ /* This flag is set when the network has reached EOF. The PENDING
+ processing can then properly detect when parsing has completed. */
+ svn_boolean_t network_eof;
+};
+
+#define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \
+ && svn_spillbuf__get_size((p)->buf) != 0)
+
+
+struct expat_ctx_t {
+ svn_ra_serf__xml_context_t *xmlctx;
+ XML_Parser parser;
+ svn_ra_serf__handler_t *handler;
+
+ svn_error_t *inner_error;
+
+ /* Do not use this pool for allocation. It is merely recorded for running
+ the cleanup handler. */
+ apr_pool_t *cleanup_pool;
+};
+
+
+static const apr_uint32_t serf_failure_map[][2] =
+{
+ { SERF_SSL_CERT_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID },
+ { SERF_SSL_CERT_EXPIRED, SVN_AUTH_SSL_EXPIRED },
+ { SERF_SSL_CERT_SELF_SIGNED, SVN_AUTH_SSL_UNKNOWNCA },
+ { SERF_SSL_CERT_UNKNOWNCA, SVN_AUTH_SSL_UNKNOWNCA }
+};
+
+/* Return a Subversion failure mask based on FAILURES, a serf SSL
+ failure mask. If anything in FAILURES is not directly mappable to
+ Subversion failures, set SVN_AUTH_SSL_OTHER in the returned mask. */
+static apr_uint32_t
+ssl_convert_serf_failures(int failures)
+{
+ apr_uint32_t svn_failures = 0;
+ apr_size_t i;
+
+ for (i = 0; i < sizeof(serf_failure_map) / (2 * sizeof(apr_uint32_t)); ++i)
+ {
+ if (failures & serf_failure_map[i][0])
+ {
+ svn_failures |= serf_failure_map[i][1];
+ failures &= ~serf_failure_map[i][0];
+ }
+ }
+
+ /* Map any remaining failure bits to our OTHER bit. */
+ if (failures)
+ {
+ svn_failures |= SVN_AUTH_SSL_OTHER;
+ }
+
+ return svn_failures;
+}
+
+
+static apr_status_t
+save_error(svn_ra_serf__session_t *session,
+ svn_error_t *err)
+{
+ if (err || session->pending_error)
+ {
+ session->pending_error = svn_error_compose_create(
+ session->pending_error,
+ err);
+ return session->pending_error->apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Construct the realmstring, e.g. https://svn.collab.net:443. */
+static const char *
+construct_realm(svn_ra_serf__session_t *session,
+ apr_pool_t *pool)
+{
+ const char *realm;
+ apr_port_t port;
+
+ if (session->session_url.port_str)
+ {
+ port = session->session_url.port;
+ }
+ else
+ {
+ port = apr_uri_port_of_scheme(session->session_url.scheme);
+ }
+
+ realm = apr_psprintf(pool, "%s://%s:%d",
+ session->session_url.scheme,
+ session->session_url.hostname,
+ port);
+
+ return realm;
+}
+
+/* Convert a hash table containing the fields (as documented in X.509) of an
+ organisation to a string ORG, allocated in POOL. ORG is as returned by
+ serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */
+static char *
+convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "%s, %s, %s, %s, %s (%s)",
+ (char*)svn_hash_gets(org, "OU"),
+ (char*)svn_hash_gets(org, "O"),
+ (char*)svn_hash_gets(org, "L"),
+ (char*)svn_hash_gets(org, "ST"),
+ (char*)svn_hash_gets(org, "C"),
+ (char*)svn_hash_gets(org, "E"));
+}
+
+/* This function is called on receiving a ssl certificate of a server when
+ opening a https connection. It allows Subversion to override the initial
+ validation done by serf.
+ Serf provides us the @a baton as provided in the call to
+ serf_ssl_server_cert_callback_set. The result of serf's initial validation
+ of the certificate @a CERT is returned as a bitmask in FAILURES. */
+static svn_error_t *
+ssl_server_cert(void *baton, int failures,
+ const serf_ssl_certificate_t *cert,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__connection_t *conn = baton;
+ svn_auth_ssl_server_cert_info_t cert_info;
+ svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
+ svn_auth_iterstate_t *state;
+ const char *realmstring;
+ apr_uint32_t svn_failures;
+ apr_hash_t *issuer, *subject, *serf_cert;
+ apr_array_header_t *san;
+ void *creds;
+ int found_matching_hostname = 0;
+
+ /* Implicitly approve any non-server certs. */
+ if (serf_ssl_cert_depth(cert) > 0)
+ {
+ if (failures)
+ conn->server_cert_failures |= ssl_convert_serf_failures(failures);
+ return APR_SUCCESS;
+ }
+
+ /* Extract the info from the certificate */
+ subject = serf_ssl_cert_subject(cert, scratch_pool);
+ issuer = serf_ssl_cert_issuer(cert, scratch_pool);
+ serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
+
+ cert_info.hostname = svn_hash_gets(subject, "CN");
+ san = svn_hash_gets(serf_cert, "subjectAltName");
+ cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
+ if (! cert_info.fingerprint)
+ cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
+ cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
+ if (! cert_info.valid_from)
+ cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
+ cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
+ if (! cert_info.valid_until)
+ cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
+ cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
+ cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
+
+ svn_failures = (ssl_convert_serf_failures(failures)
+ | conn->server_cert_failures);
+
+ /* Try to find matching server name via subjectAltName first... */
+ if (san) {
+ int i;
+ for (i = 0; i < san->nelts; i++) {
+ char *s = APR_ARRAY_IDX(san, i, char*);
+ if (apr_fnmatch(s, conn->session->session_url.hostname,
+ APR_FNM_PERIOD) == APR_SUCCESS) {
+ found_matching_hostname = 1;
+ cert_info.hostname = s;
+ break;
+ }
+ }
+ }
+
+ /* Match server certificate CN with the hostname of the server */
+ if (!found_matching_hostname && cert_info.hostname)
+ {
+ if (apr_fnmatch(cert_info.hostname, conn->session->session_url.hostname,
+ APR_FNM_PERIOD) == APR_FNM_NOMATCH)
+ {
+ svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
+ }
+ }
+
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
+ &svn_failures);
+
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
+ &cert_info);
+
+ realmstring = construct_realm(conn->session, conn->session->pool);
+
+ SVN_ERR(svn_auth_first_credentials(&creds, &state,
+ SVN_AUTH_CRED_SSL_SERVER_TRUST,
+ realmstring,
+ conn->session->wc_callbacks->auth_baton,
+ scratch_pool));
+ if (creds)
+ {
+ server_creds = creds;
+ SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
+ }
+
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
+
+ if (!server_creds)
+ return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
+static apr_status_t
+ssl_server_cert_cb(void *baton, int failures,
+ const serf_ssl_certificate_t *cert)
+{
+ svn_ra_serf__connection_t *conn = baton;
+ svn_ra_serf__session_t *session = conn->session;
+ apr_pool_t *subpool;
+ svn_error_t *err;
+
+ subpool = svn_pool_create(session->pool);
+ err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
+ svn_pool_destroy(subpool);
+
+ return save_error(session, err);
+}
+
+static svn_error_t *
+load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *files = svn_cstring_split(authorities, ";",
+ TRUE /* chop_whitespace */,
+ pool);
+ int i;
+
+ for (i = 0; i < files->nelts; ++i)
+ {
+ const char *file = APR_ARRAY_IDX(files, i, const char *);
+ serf_ssl_certificate_t *ca_cert;
+ apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
+
+ if (status == APR_SUCCESS)
+ status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
+
+ if (status != APR_SUCCESS)
+ {
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: unable to load certificate file '%s'"),
+ svn_dirent_local_style(file, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+conn_setup(apr_socket_t *sock,
+ serf_bucket_t **read_bkt,
+ serf_bucket_t **write_bkt,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = baton;
+
+ *read_bkt = serf_context_bucket_socket_create(conn->session->context,
+ sock, conn->bkt_alloc);
+
+ if (conn->session->using_ssl)
+ {
+ /* input stream */
+ *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
+ conn->bkt_alloc);
+ if (!conn->ssl_context)
+ {
+ conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
+
+#if SERF_VERSION_AT_LEAST(1,0,0)
+ serf_ssl_set_hostname(conn->ssl_context,
+ conn->session->session_url.hostname);
+#endif
+
+ serf_ssl_client_cert_provider_set(conn->ssl_context,
+ svn_ra_serf__handle_client_cert,
+ conn, conn->session->pool);
+ serf_ssl_client_cert_password_set(conn->ssl_context,
+ svn_ra_serf__handle_client_cert_pw,
+ conn, conn->session->pool);
+ serf_ssl_server_cert_callback_set(conn->ssl_context,
+ ssl_server_cert_cb,
+ conn);
+
+ /* See if the user wants us to trust "default" openssl CAs. */
+ if (conn->session->trust_default_ca)
+ {
+ serf_ssl_use_default_certificates(conn->ssl_context);
+ }
+ /* Are there custom CAs to load? */
+ if (conn->session->ssl_authorities)
+ {
+ SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
+ conn->session->pool));
+ }
+ }
+
+ if (write_bkt)
+ {
+ /* output stream */
+ *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
+ conn->ssl_context,
+ conn->bkt_alloc);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_ra_serf__conn_setup is a callback for serf. This function
+ creates a read bucket and will wrap the write bucket if SSL
+ is needed. */
+apr_status_t
+svn_ra_serf__conn_setup(apr_socket_t *sock,
+ serf_bucket_t **read_bkt,
+ serf_bucket_t **write_bkt,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = baton;
+ svn_ra_serf__session_t *session = conn->session;
+ svn_error_t *err;
+
+ err = svn_error_trace(conn_setup(sock,
+ read_bkt,
+ write_bkt,
+ baton,
+ pool));
+ return save_error(session, err);
+}
+
+
+/* Our default serf response acceptor. */
+static serf_bucket_t *
+accept_response(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *c;
+ serf_bucket_alloc_t *bkt_alloc;
+
+ bkt_alloc = serf_request_get_alloc(request);
+ c = serf_bucket_barrier_create(stream, bkt_alloc);
+
+ return serf_bucket_response_create(c, bkt_alloc);
+}
+
+
+/* Custom response acceptor for HEAD requests. */
+static serf_bucket_t *
+accept_head(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *response;
+
+ response = accept_response(request, stream, acceptor_baton, pool);
+
+ /* We know we shouldn't get a response body. */
+ serf_bucket_response_set_head(response);
+
+ return response;
+}
+
+static svn_error_t *
+connection_closed(svn_ra_serf__connection_t *conn,
+ apr_status_t why,
+ apr_pool_t *pool)
+{
+ if (why)
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ if (conn->session->using_ssl)
+ conn->ssl_context = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_ra_serf__conn_closed(serf_connection_t *conn,
+ void *closed_baton,
+ apr_status_t why,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *ra_conn = closed_baton;
+ svn_error_t *err;
+
+ err = svn_error_trace(connection_closed(ra_conn, why, pool));
+
+ (void) save_error(ra_conn->session, err);
+}
+
+
+/* Implementation of svn_ra_serf__handle_client_cert */
+static svn_error_t *
+handle_client_cert(void *data,
+ const char **cert_path,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ const char *realm;
+ void *creds;
+
+ *cert_path = NULL;
+
+ realm = construct_realm(session, session->pool);
+
+ if (!conn->ssl_client_auth_state)
+ {
+ SVN_ERR(svn_auth_first_credentials(&creds,
+ &conn->ssl_client_auth_state,
+ SVN_AUTH_CRED_SSL_CLIENT_CERT,
+ realm,
+ session->wc_callbacks->auth_baton,
+ pool));
+ }
+ else
+ {
+ SVN_ERR(svn_auth_next_credentials(&creds,
+ conn->ssl_client_auth_state,
+ session->pool));
+ }
+
+ if (creds)
+ {
+ svn_auth_cred_ssl_client_cert_t *client_creds;
+ client_creds = creds;
+ *cert_path = client_creds->cert_file;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements serf_ssl_need_client_cert_t for handle_client_cert */
+apr_status_t svn_ra_serf__handle_client_cert(void *data,
+ const char **cert_path)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ svn_error_t *err;
+
+ err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
+
+ return save_error(session, err);
+}
+
+/* Implementation for svn_ra_serf__handle_client_cert_pw */
+static svn_error_t *
+handle_client_cert_pw(void *data,
+ const char *cert_path,
+ const char **password,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ void *creds;
+
+ *password = NULL;
+
+ if (!conn->ssl_client_pw_auth_state)
+ {
+ SVN_ERR(svn_auth_first_credentials(&creds,
+ &conn->ssl_client_pw_auth_state,
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ cert_path,
+ session->wc_callbacks->auth_baton,
+ pool));
+ }
+ else
+ {
+ SVN_ERR(svn_auth_next_credentials(&creds,
+ conn->ssl_client_pw_auth_state,
+ pool));
+ }
+
+ if (creds)
+ {
+ svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
+ pw_creds = creds;
+ *password = pw_creds->password;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
+apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
+ const char *cert_path,
+ const char **password)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ svn_error_t *err;
+
+ err = svn_error_trace(handle_client_cert_pw(data,
+ cert_path,
+ password,
+ session->pool));
+
+ return save_error(session, err);
+}
+
+
+/*
+ * Given a REQUEST on connection CONN, construct a request bucket for it,
+ * returning the bucket in *REQ_BKT.
+ *
+ * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
+ * corresponds to the new request.
+ *
+ * The request will be METHOD at URL.
+ *
+ * If BODY_BKT is not-NULL, it will be sent as the request body.
+ *
+ * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
+ *
+ * REQUEST_POOL should live for the duration of the request. Serf will
+ * construct this and provide it to the request_setup callback, so we
+ * should just use that one.
+ */
+static svn_error_t *
+setup_serf_req(serf_request_t *request,
+ serf_bucket_t **req_bkt,
+ serf_bucket_t **hdrs_bkt,
+ svn_ra_serf__session_t *session,
+ const char *method, const char *url,
+ serf_bucket_t *body_bkt, const char *content_type,
+ const char *accept_encoding,
+ apr_pool_t *request_pool,
+ apr_pool_t *scratch_pool)
+{
+ serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
+
+#if SERF_VERSION_AT_LEAST(1, 1, 0)
+ svn_spillbuf_t *buf;
+
+ if (session->http10 && body_bkt != NULL)
+ {
+ /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
+ it speaks HTTP/1.1 (and thus, chunked requests), or because the
+ server actually responded as only supporting HTTP/1.0.
+
+ We'll take the existing body_bkt, spool it into a spillbuf, and
+ then wrap a bucket around that spillbuf. The spillbuf will give
+ us the Content-Length value. */
+ SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
+ request_pool,
+ scratch_pool));
+ /* Destroy original bucket since it content is already copied
+ to spillbuf. */
+ serf_bucket_destroy(body_bkt);
+
+ body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
+ request_pool,
+ scratch_pool);
+ }
+#endif
+
+ /* Create a request bucket. Note that this sucker is kind enough to
+ add a "Host" header for us. */
+ *req_bkt = serf_request_bucket_request_create(request, method, url,
+ body_bkt, allocator);
+
+ /* Set the Content-Length value. This will also trigger an HTTP/1.0
+ request (rather than the default chunked request). */
+#if SERF_VERSION_AT_LEAST(1, 1, 0)
+ if (session->http10)
+ {
+ if (body_bkt == NULL)
+ serf_bucket_request_set_CL(*req_bkt, 0);
+ else
+ serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
+ }
+#endif
+
+ *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
+
+ /* We use serf_bucket_headers_setn() because the USERAGENT has a
+ lifetime longer than this bucket. Thus, there is no need to copy
+ the header values. */
+ serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
+
+ if (content_type)
+ {
+ serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
+ }
+
+#if SERF_VERSION_AT_LEAST(1, 1, 0)
+ if (session->http10)
+ serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
+#endif
+
+ if (accept_encoding)
+ {
+ serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
+ }
+
+ /* These headers need to be sent with every request; see issue #3255
+ ("mod_dav_svn does not pass client capabilities to start-commit
+ hooks") for why. */
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__context_run_wait(svn_boolean_t *done,
+ svn_ra_serf__session_t *sess,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ apr_interval_time_t waittime_left = sess->timeout;
+
+ assert(sess->pending_error == SVN_NO_ERROR);
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (!*done)
+ {
+ apr_status_t status;
+ svn_error_t *err;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ if (sess->cancel_func)
+ SVN_ERR((*sess->cancel_func)(sess->cancel_baton));
+
+ status = serf_context_run(sess->context,
+ SVN_RA_SERF__CONTEXT_RUN_DURATION,
+ iterpool);
+
+ err = sess->pending_error;
+ sess->pending_error = SVN_NO_ERROR;
+
+ /* If the context duration timeout is up, we'll subtract that
+ duration from the total time alloted for such things. If
+ there's no time left, we fail with a message indicating that
+ the connection timed out. */
+ if (APR_STATUS_IS_TIMEUP(status))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ status = 0;
+
+ if (sess->timeout)
+ {
+ if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
+ {
+ waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
+ _("Connection timed out"));
+ }
+ }
+ }
+ else
+ {
+ waittime_left = sess->timeout;
+ }
+
+ SVN_ERR(err);
+ if (status)
+ {
+ if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
+ {
+ /* apr can't translate subversion errors to text */
+ SVN_ERR_W(svn_error_create(status, NULL, NULL),
+ _("Error running context"));
+ }
+
+ return svn_ra_serf__wrap_err(status, _("Error running context"));
+ }
+
+ /* Debugging purposes only! */
+ for (i = 0; i < sess->num_conns; i++)
+ {
+ serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ /* Create a serf request based on HANDLER. */
+ svn_ra_serf__request_create(handler);
+
+ /* Wait until the response logic marks its DONE status. */
+ err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
+ scratch_pool);
+ if (handler->server_error)
+ {
+ err = svn_error_compose_create(err, handler->server_error->error);
+ handler->server_error = NULL;
+ }
+
+ return svn_error_trace(err);
+}
+
+
+/*
+ * Expat callback invoked on a start element tag for an error response.
+ */
+static svn_error_t *
+start_error(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (!ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "error") == 0)
+ {
+ ctx->in_error = TRUE;
+ }
+ else if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
+ {
+ const char *err_code;
+
+ err_code = svn_xml_get_attr_value("errcode", attrs);
+ if (err_code)
+ {
+ apr_int64_t val;
+
+ SVN_ERR(svn_cstring_atoi64(&val, err_code));
+ ctx->error->apr_err = (apr_status_t)val;
+ }
+
+ /* If there's no error code provided, or if the provided code is
+ 0 (which can happen sometimes depending on how the error is
+ constructed on the server-side), just pick a generic error
+ code to run with. */
+ if (! ctx->error->apr_err)
+ {
+ ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
+ }
+
+ /* Start collecting cdata. */
+ svn_stringbuf_setempty(ctx->cdata);
+ ctx->collect_cdata = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on an end element tag for a PROPFIND response.
+ */
+static svn_error_t *
+end_error(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "error") == 0)
+ {
+ ctx->in_error = FALSE;
+ }
+ if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
+ {
+ /* On the server dav_error_response_tag() will add a leading
+ and trailing newline if DEBUG_CR is defined in mod_dav.h,
+ so remove any such characters here. */
+ svn_stringbuf_strip_whitespace(ctx->cdata);
+
+ ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
+ ctx->cdata->len);
+ ctx->collect_cdata = FALSE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on CDATA elements in an error response.
+ *
+ * This callback can be called multiple times.
+ */
+static svn_error_t *
+cdata_error(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->collect_cdata)
+ {
+ svn_stringbuf_appendbytes(ctx->cdata, data, len);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static apr_status_t
+drain_bucket(serf_bucket_t *bucket)
+{
+ /* Read whatever is in the bucket, and just drop it. */
+ while (1)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
+ if (status)
+ return status;
+ }
+}
+
+
+static svn_ra_serf__server_error_t *
+begin_error_parsing(svn_ra_serf__xml_start_element_t start,
+ svn_ra_serf__xml_end_element_t end,
+ svn_ra_serf__xml_cdata_chunk_handler_t cdata,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = apr_pcalloc(result_pool, sizeof(*server_err));
+ server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
+ server_err->contains_precondition_error = FALSE;
+ server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool);
+ server_err->collect_cdata = FALSE;
+ server_err->parser.pool = server_err->error->pool;
+ server_err->parser.user_data = server_err;
+ server_err->parser.start = start;
+ server_err->parser.end = end;
+ server_err->parser.cdata = cdata;
+ server_err->parser.ignore_errors = TRUE;
+
+ return server_err;
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__handle_discard_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+
+ status = drain_bucket(response);
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+apr_status_t
+svn_ra_serf__response_discard_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ return drain_bucket(response);
+}
+
+
+/* Return the value of the RESPONSE's Location header if any, or NULL
+ otherwise. */
+static const char *
+response_get_location(serf_bucket_t *response,
+ const char *base_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ serf_bucket_t *headers;
+ const char *location;
+
+ headers = serf_bucket_response_get_headers(response);
+ location = serf_bucket_headers_get(headers, "Location");
+ if (location == NULL)
+ return NULL;
+
+ /* The RFCs say we should have received a full url in LOCATION, but
+ older apache versions and many custom web handlers just return a
+ relative path here...
+
+ And we can't trust anything because it is network data.
+ */
+ if (*location == '/')
+ {
+ apr_uri_t uri;
+ apr_status_t status;
+
+ status = apr_uri_parse(scratch_pool, base_url, &uri);
+
+ if (status != APR_SUCCESS)
+ return NULL;
+
+ /* Replace the path path with what we got */
+ uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool);
+
+ /* And make APR produce a proper full url for us */
+ location = apr_uri_unparse(scratch_pool, &uri, 0);
+
+ /* Fall through to ensure our canonicalization rules */
+ }
+ else if (!svn_path_is_url(location))
+ {
+ return NULL; /* Any other formats we should support? */
+ }
+
+ return svn_uri_canonicalize(location, result_pool);
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__expect_empty_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ /* This function is just like handle_multistatus_only() except for the
+ XML parsing callbacks. We want to look for the human-readable element. */
+
+ /* We should see this just once, in order to initialize SERVER_ERROR.
+ At that point, the core error processing will take over. If we choose
+ not to parse an error, then we'll never return here (because we
+ change the response handler). */
+ SVN_ERR_ASSERT(handler->server_error == NULL);
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = begin_error_parsing(start_error, end_error, cdata_error,
+ handler->handler_pool);
+
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
+
+ handler->server_error = server_err;
+ }
+ else
+ {
+ /* The body was not text/xml, so we don't know what to do with it.
+ Toss anything that arrives. */
+ handler->discard_body = TRUE;
+ }
+
+ /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
+ to call the response handler again. That will start up the XML parsing,
+ or it will be dropped on the floor (per the decision above). */
+ return SVN_NO_ERROR;
+}
+
+
+/* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric
+ status code into *STATUS_CODE_OUT. Ignores leading whitespace. */
+static svn_error_t *
+parse_dav_status(int *status_code_out, svn_stringbuf_t *buf,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ const char *token;
+ char *tok_status;
+ svn_stringbuf_t *temp_buf = svn_stringbuf_dup(buf, scratch_pool);
+
+ svn_stringbuf_strip_whitespace(temp_buf);
+ token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status);
+ if (token)
+ token = apr_strtok(NULL, " \t\r\n", &tok_status);
+ if (!token)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Malformed DAV:status CDATA '%s'"),
+ buf->data);
+ err = svn_cstring_atoi(status_code_out, token);
+ if (err)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
+ _("Malformed DAV:status CDATA '%s'"),
+ buf->data);
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on a start element tag for a 207 response.
+ */
+static svn_error_t *
+start_207(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (!ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "multistatus") == 0)
+ {
+ ctx->in_error = TRUE;
+ }
+ else if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
+ {
+ /* Start collecting cdata. */
+ svn_stringbuf_setempty(ctx->cdata);
+ ctx->collect_cdata = TRUE;
+ }
+ else if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "status") == 0)
+ {
+ /* Start collecting cdata. */
+ svn_stringbuf_setempty(ctx->cdata);
+ ctx->collect_cdata = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on an end element tag for a 207 response.
+ */
+static svn_error_t *
+end_207(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "multistatus") == 0)
+ {
+ ctx->in_error = FALSE;
+ }
+ if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
+ {
+ /* Remove leading newline added by DEBUG_CR on server */
+ svn_stringbuf_strip_whitespace(ctx->cdata);
+
+ ctx->collect_cdata = FALSE;
+ ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
+ ctx->cdata->len);
+ if (ctx->contains_precondition_error)
+ ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
+ else
+ ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
+ }
+ else if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "status") == 0)
+ {
+ int status_code;
+
+ ctx->collect_cdata = FALSE;
+
+ SVN_ERR(parse_dav_status(&status_code, ctx->cdata, parser->pool));
+ if (status_code == 412)
+ ctx->contains_precondition_error = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on CDATA elements in a 207 response.
+ *
+ * This callback can be called multiple times.
+ */
+static svn_error_t *
+cdata_207(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->collect_cdata)
+ {
+ svn_stringbuf_appendbytes(ctx->cdata, data, len);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__handle_multistatus_only(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+
+ /* This function is just like expect_empty_body() except for the
+ XML parsing callbacks. We are looking for very limited pieces of
+ the multistatus response. */
+
+ /* We should see this just once, in order to initialize SERVER_ERROR.
+ At that point, the core error processing will take over. If we choose
+ not to parse an error, then we'll never return here (because we
+ change the response handler). */
+ SVN_ERR_ASSERT(handler->server_error == NULL);
+
+ {
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = begin_error_parsing(start_207, end_207, cdata_207,
+ handler->handler_pool);
+
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
+
+ handler->server_error = server_err;
+ }
+ else
+ {
+ /* The body was not text/xml, so we don't know what to do with it.
+ Toss anything that arrives. */
+ handler->discard_body = TRUE;
+ }
+ }
+
+ /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
+ to call the response handler again. That will start up the XML parsing,
+ or it will be dropped on the floor (per the decision above). */
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to Expat's XML_StartElementHandler */
+static void
+start_xml(void *userData, const char *raw_name, const char **attrs)
+{
+ svn_ra_serf__xml_parser_t *parser = userData;
+ svn_ra_serf__dav_props_t name;
+ apr_pool_t *scratch_pool;
+ svn_error_t *err;
+
+ if (parser->error)
+ return;
+
+ if (!parser->state)
+ svn_ra_serf__xml_push_state(parser, 0);
+
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
+ svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool);
+
+ svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
+
+ err = parser->start(parser, name, attrs, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
+}
+
+
+/* Conforms to Expat's XML_EndElementHandler */
+static void
+end_xml(void *userData, const char *raw_name)
+{
+ svn_ra_serf__xml_parser_t *parser = userData;
+ svn_ra_serf__dav_props_t name;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool;
+
+ if (parser->error)
+ return;
+
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
+ svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
+
+ err = parser->end(parser, name, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
+}
+
+
+/* Conforms to Expat's XML_CharacterDataHandler */
+static void
+cdata_xml(void *userData, const char *data, int len)
+{
+ svn_ra_serf__xml_parser_t *parser = userData;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool;
+
+ if (parser->error)
+ return;
+
+ if (!parser->state)
+ svn_ra_serf__xml_push_state(parser, 0);
+
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
+ err = parser->cdata(parser, data, len, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
+}
+
+/* Flip the requisite bits in CTX to indicate that processing of the
+ response is complete, adding the current "done item" to the list of
+ completed items. */
+static void
+add_done_item(svn_ra_serf__xml_parser_t *ctx)
+{
+ /* Make sure we don't add to DONE_LIST twice. */
+ if (!*ctx->done)
+ {
+ *ctx->done = TRUE;
+ if (ctx->done_list)
+ {
+ ctx->done_item->data = ctx->user_data;
+ ctx->done_item->next = *ctx->done_list;
+ *ctx->done_list = ctx->done_item;
+ }
+ }
+}
+
+
+static svn_error_t *
+write_to_pending(svn_ra_serf__xml_parser_t *ctx,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ if (ctx->pending == NULL)
+ {
+ ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
+ ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE,
+ SPILL_SIZE,
+ ctx->pool);
+ }
+
+ /* Copy the data into one or more chunks in the spill buffer. */
+ return svn_error_trace(svn_spillbuf__write(ctx->pending->buf,
+ data, len,
+ scratch_pool));
+}
+
+
+static svn_error_t *
+inject_to_parser(svn_ra_serf__xml_parser_t *ctx,
+ const char *data,
+ apr_size_t len,
+ const serf_status_line *sl)
+{
+ int xml_status;
+
+ xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0);
+ if (xml_status == XML_STATUS_ERROR && !ctx->ignore_errors)
+ {
+ if (sl == NULL)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("XML parsing failed"));
+
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("XML parsing failed: (%d %s)"),
+ sl->code, sl->reason);
+ }
+
+ if (ctx->error && !ctx->ignore_errors)
+ return svn_error_trace(ctx->error);
+
+ return SVN_NO_ERROR;
+}
+
+/* Apr pool cleanup handler to release an XML_Parser in success and error
+ conditions */
+static apr_status_t
+xml_parser_cleanup(void *baton)
+{
+ XML_Parser *xmlp = baton;
+
+ if (*xmlp)
+ {
+ (void) XML_ParserFree(*xmlp);
+ *xmlp = NULL;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Limit the amount of pending content to parse at once to < 100KB per
+ iteration. This number is chosen somewhat arbitrarely. Making it lower
+ will have a drastical negative impact on performance, whereas increasing it
+ increases the risk for connection timeouts.
+ */
+#define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5
+
+svn_error_t *
+svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
+ svn_boolean_t *network_eof,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t pending_empty = FALSE;
+ apr_size_t cur_read = 0;
+
+ /* Fast path exit: already paused, nothing to do, or already done. */
+ if (parser->paused || parser->pending == NULL || *parser->done)
+ {
+ *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Parsing the pending conten in the spillbuf will result in many disc i/o
+ operations. This can be so slow that we don't run the network event
+ processing loop often enough, resulting in timed out connections.
+
+ So we limit the amounts of bytes parsed per iteration.
+ */
+ while (cur_read < PENDING_TO_PARSE)
+ {
+ const char *data;
+ apr_size_t len;
+
+ /* Get a block of content, stopping the loop when we run out. */
+ SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf,
+ scratch_pool));
+ if (data)
+ {
+ /* Inject the content into the XML parser. */
+ SVN_ERR(inject_to_parser(parser, data, len, NULL));
+
+ /* If the XML parsing callbacks paused us, then we're done for now. */
+ if (parser->paused)
+ break;
+
+ cur_read += len;
+ }
+ else
+ {
+ /* The buffer is empty. */
+ pending_empty = TRUE;
+ break;
+ }
+ }
+
+ /* If the PENDING structures are empty *and* we consumed all content from
+ the network, then we're completely done with the parsing. */
+ if (pending_empty &&
+ parser->pending->network_eof)
+ {
+ SVN_ERR_ASSERT(parser->xmlp != NULL);
+
+ /* Tell the parser that no more content will be parsed. Ignore the
+ return status. We just don't care. */
+ (void) XML_Parse(parser->xmlp, NULL, 0, 1);
+
+ apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup);
+ parser->xmlp = NULL;
+ add_done_item(parser);
+ }
+
+ *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
+
+ return SVN_NO_ERROR;
+}
+#undef PENDING_TO_PARSE
+
+
+/* ### this is still broken conceptually. just shifting incrementally... */
+static svn_error_t *
+handle_server_error(serf_request_t *request,
+ serf_bucket_t *response,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t server_err = { 0 };
+ serf_bucket_t *hdrs;
+ const char *val;
+ apr_status_t err;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ /* ### we should figure out how to reuse begin_error_parsing */
+
+ server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL);
+ server_err.contains_precondition_error = FALSE;
+ server_err.cdata = svn_stringbuf_create_empty(scratch_pool);
+ server_err.collect_cdata = FALSE;
+ server_err.parser.pool = server_err.error->pool;
+ server_err.parser.user_data = &server_err;
+ server_err.parser.start = start_error;
+ server_err.parser.end = end_error;
+ server_err.parser.cdata = cdata_error;
+ server_err.parser.done = &server_err.done;
+ server_err.parser.ignore_errors = TRUE;
+
+ /* We don't care about any errors except for SERVER_ERR.ERROR */
+ svn_error_clear(svn_ra_serf__handle_xml_parser(request,
+ response,
+ &server_err.parser,
+ scratch_pool));
+
+ /* ### checking DONE is silly. the above only parses whatever has
+ ### been received at the network interface. totally wrong. but
+ ### it is what we have for now (maintaining historical code),
+ ### until we fully migrate. */
+ if (server_err.done && server_err.error->apr_err == APR_SUCCESS)
+ {
+ svn_error_clear(server_err.error);
+ server_err.error = SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(server_err.error);
+ }
+
+ /* The only error that we will return is from the XML response body.
+ Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to
+ surface. */
+ err = drain_bucket(response);
+ if (err && !SERF_BUCKET_READ_ERROR(err))
+ return svn_ra_serf__wrap_err(err, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__handle_xml_parser(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ serf_status_line sl;
+ apr_status_t status;
+ svn_ra_serf__xml_parser_t *ctx = baton;
+ svn_error_t *err;
+
+ /* ### get the HANDLER rather than fetching this. */
+ status = serf_bucket_response_status(response, &sl);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ /* Woo-hoo. Nothing here to see. */
+ if (sl.code == 404 && !ctx->ignore_errors)
+ {
+ err = handle_server_error(request, response, pool);
+
+ if (err && APR_STATUS_IS_EOF(err->apr_err))
+ add_done_item(ctx);
+
+ return svn_error_trace(err);
+ }
+
+ if (ctx->headers_baton == NULL)
+ ctx->headers_baton = serf_bucket_response_get_headers(response);
+ else if (ctx->headers_baton != serf_bucket_response_get_headers(response))
+ {
+ /* We got a new response to an existing parser...
+ This tells us the connection has restarted and we should continue
+ where we stopped last time.
+ */
+
+ /* Is this a second attempt?? */
+ if (!ctx->skip_size)
+ ctx->skip_size = ctx->read_size;
+
+ ctx->read_size = 0; /* New request, nothing read */
+ }
+
+ if (!ctx->xmlp)
+ {
+ ctx->xmlp = XML_ParserCreate(NULL);
+ apr_pool_cleanup_register(ctx->pool, &ctx->xmlp, xml_parser_cleanup,
+ apr_pool_cleanup_null);
+ XML_SetUserData(ctx->xmlp, ctx);
+ XML_SetElementHandler(ctx->xmlp, start_xml, end_xml);
+ if (ctx->cdata)
+ {
+ XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml);
+ }
+ }
+
+ while (1)
+ {
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
+
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ ctx->read_size += len;
+
+ if (ctx->skip_size)
+ {
+ /* Handle restarted requests correctly: Skip what we already read */
+ apr_size_t skip;
+
+ if (ctx->skip_size >= ctx->read_size)
+ {
+ /* Eek. What did the file shrink or something? */
+ if (APR_STATUS_IS_EOF(status))
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ /* Skip on to the next iteration of this loop. */
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ continue;
+ }
+
+ skip = (apr_size_t)(len - (ctx->read_size - ctx->skip_size));
+ data += skip;
+ len -= skip;
+ ctx->skip_size = 0;
+ }
+
+ /* Note: once the callbacks invoked by inject_to_parser() sets the
+ PAUSED flag, then it will not be cleared. write_to_pending() will
+ only save the content. Logic outside of serf_context_run() will
+ clear that flag, as appropriate, along with processing the
+ content that we have placed into the PENDING buffer.
+
+ We want to save arriving content into the PENDING structures if
+ the parser has been paused, or we already have data in there (so
+ the arriving data is appended, rather than injected out of order) */
+ if (ctx->paused || HAS_PENDING_DATA(ctx->pending))
+ {
+ err = write_to_pending(ctx, data, len, pool);
+ }
+ else
+ {
+ err = inject_to_parser(ctx, data, len, &sl);
+ if (err)
+ {
+ /* Should have no errors if IGNORE_ERRORS is set. */
+ SVN_ERR_ASSERT(!ctx->ignore_errors);
+ }
+ }
+ if (err)
+ {
+ SVN_ERR_ASSERT(ctx->xmlp != NULL);
+
+ apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
+ add_done_item(ctx);
+ return svn_error_trace(err);
+ }
+
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ if (APR_STATUS_IS_EOF(status))
+ {
+ if (ctx->pending != NULL)
+ ctx->pending->network_eof = TRUE;
+
+ /* We just hit the end of the network content. If we have nothing
+ in the PENDING structures, then we're completely done. */
+ if (!HAS_PENDING_DATA(ctx->pending))
+ {
+ SVN_ERR_ASSERT(ctx->xmlp != NULL);
+
+ /* Ignore the return status. We just don't care. */
+ (void) XML_Parse(ctx->xmlp, NULL, 0, 1);
+
+ apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
+ add_done_item(ctx);
+ }
+
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ /* feed me! */
+ }
+ /* not reached */
+}
+
+
+apr_status_t
+svn_ra_serf__credentials_callback(char **username, char **password,
+ serf_request_t *request, void *baton,
+ int code, const char *authn_type,
+ const char *realm,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+ svn_ra_serf__session_t *session = handler->session;
+ void *creds;
+ svn_auth_cred_simple_t *simple_creds;
+ svn_error_t *err;
+
+ if (code == 401)
+ {
+ /* Use svn_auth_first_credentials if this is the first time we ask for
+ credentials during this session OR if the last time we asked
+ session->auth_state wasn't set (eg. if the credentials provider was
+ cancelled by the user). */
+ if (!session->auth_state)
+ {
+ err = svn_auth_first_credentials(&creds,
+ &session->auth_state,
+ SVN_AUTH_CRED_SIMPLE,
+ realm,
+ session->wc_callbacks->auth_baton,
+ session->pool);
+ }
+ else
+ {
+ err = svn_auth_next_credentials(&creds,
+ session->auth_state,
+ session->pool);
+ }
+
+ if (err)
+ {
+ (void) save_error(session, err);
+ return err->apr_err;
+ }
+
+ session->auth_attempts++;
+
+ if (!creds || session->auth_attempts > 4)
+ {
+ /* No more credentials. */
+ (void) save_error(session,
+ svn_error_create(
+ SVN_ERR_AUTHN_FAILED, NULL,
+ _("No more credentials or we tried too many "
+ "times.\nAuthentication failed")));
+ return SVN_ERR_AUTHN_FAILED;
+ }
+
+ simple_creds = creds;
+ *username = apr_pstrdup(pool, simple_creds->username);
+ *password = apr_pstrdup(pool, simple_creds->password);
+ }
+ else
+ {
+ *username = apr_pstrdup(pool, session->proxy_username);
+ *password = apr_pstrdup(pool, session->proxy_password);
+
+ session->proxy_auth_attempts++;
+
+ if (!session->proxy_username || session->proxy_auth_attempts > 4)
+ {
+ /* No more credentials. */
+ (void) save_error(session,
+ svn_error_create(
+ SVN_ERR_AUTHN_FAILED, NULL,
+ _("Proxy authentication failed")));
+ return SVN_ERR_AUTHN_FAILED;
+ }
+ }
+
+ handler->conn->last_status_code = code;
+
+ return APR_SUCCESS;
+}
+
+/* Wait for HTTP response status and headers, and invoke HANDLER->
+ response_handler() to carry out operation-specific processing.
+ Afterwards, check for connection close.
+
+ SERF_STATUS allows returning errors to serf without creating a
+ subversion error object.
+ */
+static svn_error_t *
+handle_response(serf_request_t *request,
+ serf_bucket_t *response,
+ svn_ra_serf__handler_t *handler,
+ apr_status_t *serf_status,
+ apr_pool_t *scratch_pool)
+{
+ apr_status_t status;
+ svn_error_t *err;
+
+ /* ### need to verify whether this already gets init'd on every
+ ### successful exit. for an error-exit, it will (properly) be
+ ### ignored by the caller. */
+ *serf_status = APR_SUCCESS;
+
+ if (!response)
+ {
+ /* Uh-oh. Our connection died. */
+ if (handler->response_error)
+ SVN_ERR(handler->response_error(request, response, 0,
+ handler->response_error_baton));
+
+ /* Requeue another request for this handler.
+ ### how do we know if the handler can deal with this?! */
+ svn_ra_serf__request_create(handler);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* If we're reading the body, then skip all this preparation. */
+ if (handler->reading_body)
+ goto process_body;
+
+ /* Copy the Status-Line info into HANDLER, if we don't yet have it. */
+ if (handler->sline.version == 0)
+ {
+ serf_status_line sl;
+
+ status = serf_bucket_response_status(response, &sl);
+ if (status != APR_SUCCESS)
+ {
+ /* The response line is not (yet) ready, or some other error. */
+ *serf_status = status;
+ return SVN_NO_ERROR; /* Handled by serf */
+ }
+
+ /* If we got APR_SUCCESS, then we should have Status-Line info. */
+ SVN_ERR_ASSERT(sl.version != 0);
+
+ handler->sline = sl;
+ handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
+
+ /* HTTP/1.1? (or later) */
+ if (sl.version != SERF_HTTP_10)
+ handler->session->http10 = FALSE;
+ }
+
+ /* Keep reading from the network until we've read all the headers. */
+ status = serf_bucket_response_wait_for_headers(response);
+ if (status)
+ {
+ /* The typical "error" will be APR_EAGAIN, meaning that more input
+ from the network is required to complete the reading of the
+ headers. */
+ if (!APR_STATUS_IS_EOF(status))
+ {
+ /* Either the headers are not (yet) complete, or there really
+ was an error. */
+ *serf_status = status;
+ return SVN_NO_ERROR;
+ }
+
+ /* wait_for_headers() will return EOF if there is no body in this
+ response, or if we completely read the body. The latter is not
+ true since we would have set READING_BODY to get the body read,
+ and we would not be back to this code block.
+
+ It can also return EOF if we truly hit EOF while (say) processing
+ the headers. aka Badness. */
+
+ /* Cases where a lack of a response body (via EOF) is okay:
+ * - A HEAD request
+ * - 204/304 response
+ *
+ * Otherwise, if we get an EOF here, something went really wrong: either
+ * the server closed on us early or we're reading too much. Either way,
+ * scream loudly.
+ */
+ if (strcmp(handler->method, "HEAD") != 0
+ && handler->sline.code != 204
+ && handler->sline.code != 304)
+ {
+ err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ svn_ra_serf__wrap_err(status, NULL),
+ _("Premature EOF seen from server"
+ " (http status=%d)"),
+ handler->sline.code);
+
+ /* In case anything else arrives... discard it. */
+ handler->discard_body = TRUE;
+
+ return err;
+ }
+ }
+
+ /* ... and set up the header fields in HANDLER. */
+ handler->location = response_get_location(response,
+ handler->session->session_url_str,
+ handler->handler_pool,
+ scratch_pool);
+
+ /* On the last request, we failed authentication. We succeeded this time,
+ so let's save away these credentials. */
+ if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
+ {
+ SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
+ handler->session->pool));
+ handler->session->auth_attempts = 0;
+ handler->session->auth_state = NULL;
+ }
+ handler->conn->last_status_code = handler->sline.code;
+
+ if (handler->sline.code == 405
+ || handler->sline.code == 408
+ || handler->sline.code == 409
+ || handler->sline.code >= 500)
+ {
+ /* 405 Method Not allowed.
+ 408 Request Timeout
+ 409 Conflict: can indicate a hook error.
+ 5xx (Internal) Server error. */
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = begin_error_parsing(start_error, end_error, cdata_error,
+ handler->handler_pool);
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
+
+ handler->server_error = server_err;
+ }
+ else
+ {
+ handler->discard_body = TRUE;
+
+ if (!handler->session->pending_error)
+ {
+ apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
+ /* 405 == Method Not Allowed (Occurs when trying to lock a working
+ copy path which no longer exists at HEAD in the repository. */
+ if (handler->sline.code == 405
+ && strcmp(handler->method, "LOCK") == 0)
+ apr_err = SVN_ERR_FS_OUT_OF_DATE;
+
+ handler->session->pending_error =
+ svn_error_createf(apr_err, NULL,
+ _("%s request on '%s' failed: %d %s"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason);
+ }
+ }
+ }
+
+ /* Stop processing the above, on every packet arrival. */
+ handler->reading_body = TRUE;
+
+ process_body:
+
+ /* We've been instructed to ignore the body. Drain whatever is present. */
+ if (handler->discard_body)
+ {
+ *serf_status = drain_bucket(response);
+
+ /* If the handler hasn't set done (which it shouldn't have) and
+ we now have the EOF, go ahead and set it so that we can stop
+ our context loops.
+ */
+ if (!handler->done && APR_STATUS_IS_EOF(*serf_status))
+ handler->done = TRUE;
+
+ return SVN_NO_ERROR;
+ }
+
+ /* If we are supposed to parse the body as a server_error, then do
+ that now. */
+ if (handler->server_error != NULL)
+ {
+ err = svn_ra_serf__handle_xml_parser(request, response,
+ &handler->server_error->parser,
+ scratch_pool);
+
+ /* If we do not receive an error or it is a non-transient error, return
+ immediately.
+
+ APR_EOF will be returned when parsing is complete.
+
+ APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through
+ parsing and the network has no more data right now. If we receive that,
+ clear the error and return - allowing serf to wait for more data.
+ */
+ if (!err || SERF_BUCKET_READ_ERROR(err->apr_err))
+ return svn_error_trace(err);
+
+ if (!APR_STATUS_IS_EOF(err->apr_err))
+ {
+ *serf_status = err->apr_err;
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ /* Clear the EOF. We don't need it. */
+ svn_error_clear(err);
+
+ /* If the parsing is done, and we did not extract an error, then
+ simply toss everything, and anything else that might arrive.
+ The higher-level code will need to investigate HANDLER->SLINE,
+ as we have no further information for them. */
+ if (handler->done
+ && handler->server_error->error->apr_err == APR_SUCCESS)
+ {
+ svn_error_clear(handler->server_error->error);
+
+ /* Stop parsing for a server error. */
+ handler->server_error = NULL;
+
+ /* If anything arrives after this, then just discard it. */
+ handler->discard_body = TRUE;
+ }
+
+ *serf_status = APR_EOF;
+ return SVN_NO_ERROR;
+ }
+
+ /* Pass the body along to the registered response handler. */
+ err = handler->response_handler(request, response,
+ handler->response_baton,
+ scratch_pool);
+
+ if (err
+ && (!SERF_BUCKET_READ_ERROR(err->apr_err)
+ || APR_STATUS_IS_ECONNRESET(err->apr_err)
+ || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
+ {
+ /* These errors are special cased in serf
+ ### We hope no handler returns these by accident. */
+ *serf_status = err->apr_err;
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+}
+
+
+/* Implements serf_response_handler_t for handle_response. Storing
+ errors in handler->session->pending_error if appropriate. */
+static apr_status_t
+handle_response_cb(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+ svn_error_t *err;
+ apr_status_t inner_status;
+ apr_status_t outer_status;
+
+ err = svn_error_trace(handle_response(request, response,
+ handler, &inner_status,
+ scratch_pool));
+
+ /* Select the right status value to return. */
+ outer_status = save_error(handler->session, err);
+ if (!outer_status)
+ outer_status = inner_status;
+
+ /* Make sure the DONE flag is set properly. */
+ if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
+ handler->done = TRUE;
+
+ return outer_status;
+}
+
+/* Perform basic request setup, with special handling for HEAD requests,
+ and finer-grained callbacks invoked (if non-NULL) to produce the request
+ headers and body. */
+static svn_error_t *
+setup_request(serf_request_t *request,
+ svn_ra_serf__handler_t *handler,
+ serf_bucket_t **req_bkt,
+ apr_pool_t *request_pool,
+ apr_pool_t *scratch_pool)
+{
+ serf_bucket_t *body_bkt;
+ serf_bucket_t *headers_bkt;
+ const char *accept_encoding;
+
+ if (handler->body_delegate)
+ {
+ serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
+
+ /* ### should pass the scratch_pool */
+ SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
+ bkt_alloc, request_pool));
+ }
+ else
+ {
+ body_bkt = NULL;
+ }
+
+ if (handler->custom_accept_encoding)
+ {
+ accept_encoding = NULL;
+ }
+ else if (handler->session->using_compression)
+ {
+ /* Accept gzip compression if enabled. */
+ accept_encoding = "gzip";
+ }
+ else
+ {
+ accept_encoding = NULL;
+ }
+
+ SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
+ handler->session, handler->method, handler->path,
+ body_bkt, handler->body_type, accept_encoding,
+ request_pool, scratch_pool));
+
+ if (handler->header_delegate)
+ {
+ /* ### should pass the scratch_pool */
+ SVN_ERR(handler->header_delegate(headers_bkt,
+ handler->header_delegate_baton,
+ request_pool));
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Implements the serf_request_setup_t interface (which sets up both a
+ request and its response handler callback). Handles errors for
+ setup_request_cb */
+static apr_status_t
+setup_request_cb(serf_request_t *request,
+ void *setup_baton,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ serf_response_handler_t *s_handler,
+ void **s_handler_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__handler_t *handler = setup_baton;
+ svn_error_t *err;
+
+ /* ### construct a scratch_pool? serf gives us a pool that will live for
+ ### the duration of the request. */
+ apr_pool_t *scratch_pool = pool;
+
+ if (strcmp(handler->method, "HEAD") == 0)
+ *acceptor = accept_head;
+ else
+ *acceptor = accept_response;
+ *acceptor_baton = handler->session;
+
+ *s_handler = handle_response_cb;
+ *s_handler_baton = handler;
+
+ err = svn_error_trace(setup_request(request, handler, req_bkt,
+ pool /* request_pool */, scratch_pool));
+
+ return save_error(handler->session, err);
+}
+
+void
+svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
+{
+ SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL);
+
+ /* In case HANDLER is re-queued, reset the various transient fields.
+
+ ### prior to recent changes, HANDLER was constant. maybe we should
+ ### break out these processing fields, apart from the request
+ ### definition. */
+ handler->done = FALSE;
+ handler->server_error = NULL;
+ handler->sline.version = 0;
+ handler->location = NULL;
+ handler->reading_body = FALSE;
+ handler->discard_body = FALSE;
+
+ /* ### do we ever alter the >response_handler? */
+
+ /* ### do we need to hold onto the returned request object, or just
+ ### not worry about it (the serf ctx will manage it). */
+ (void) serf_connection_request_create(handler->conn->conn,
+ setup_request_cb, handler);
+}
+
+
+svn_error_t *
+svn_ra_serf__discover_vcc(const char **vcc_url,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
+{
+ const char *path;
+ const char *relative_path;
+ const char *uuid;
+
+ /* If we've already got the information our caller seeks, just return it. */
+ if (session->vcc_url && session->repos_root_str)
+ {
+ *vcc_url = session->vcc_url;
+ return SVN_NO_ERROR;
+ }
+
+ /* If no connection is provided, use the default one. */
+ if (! conn)
+ {
+ conn = session->conns[0];
+ }
+
+ path = session->session_url.path;
+ *vcc_url = NULL;
+ uuid = NULL;
+
+ do
+ {
+ apr_hash_t *props;
+ svn_error_t *err;
+
+ err = svn_ra_serf__fetch_node_props(&props, conn,
+ path, SVN_INVALID_REVNUM,
+ base_props, pool, pool);
+ if (! err)
+ {
+ apr_hash_t *ns_props;
+
+ ns_props = apr_hash_get(props, "DAV:", 4);
+ *vcc_url = svn_prop_get_value(ns_props,
+ "version-controlled-configuration");
+
+ ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
+ relative_path = svn_prop_get_value(ns_props,
+ "baseline-relative-path");
+ uuid = svn_prop_get_value(ns_props, "repository-uuid");
+ break;
+ }
+ else
+ {
+ if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
+ (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
+ {
+ return svn_error_trace(err); /* found a _real_ error */
+ }
+ else
+ {
+ /* This happens when the file is missing in HEAD. */
+ svn_error_clear(err);
+
+ /* Okay, strip off a component from PATH. */
+ path = svn_urlpath__dirname(path, pool);
+
+ /* An error occurred on conns. serf 0.4.0 remembers that
+ the connection had a problem. We need to reset it, in
+ order to use it again. */
+ serf_connection_reset(conn->conn);
+ }
+ }
+ }
+ while ((path[0] != '\0')
+ && (! (path[0] == '/' && path[1] == '\0')));
+
+ if (!*vcc_url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The PROPFIND response did not include the "
+ "requested version-controlled-configuration "
+ "value"));
+ }
+
+ /* Store our VCC in our cache. */
+ if (!session->vcc_url)
+ {
+ session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
+ }
+
+ /* Update our cached repository root URL. */
+ if (!session->repos_root_str)
+ {
+ svn_stringbuf_t *url_buf;
+
+ url_buf = svn_stringbuf_create(path, pool);
+
+ svn_path_remove_components(url_buf,
+ svn_path_component_count(relative_path));
+
+ /* Now recreate the root_url. */
+ session->repos_root = session->session_url;
+ session->repos_root.path =
+ (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
+ session->repos_root_str =
+ svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
+ &session->repos_root, 0),
+ session->pool);
+ }
+
+ /* Store the repository UUID in the cache. */
+ if (!session->uuid)
+ {
+ session->uuid = apr_pstrdup(session->pool, uuid);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_relative_path(const char **rel_path,
+ const char *orig_path,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
+{
+ const char *decoded_root, *decoded_orig;
+
+ if (! session->repos_root.path)
+ {
+ const char *vcc_url;
+
+ /* This should only happen if we haven't detected HTTP v2
+ support from the server. */
+ assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
+
+ /* We don't actually care about the VCC_URL, but this API
+ promises to populate the session's root-url cache, and that's
+ what we really want. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session,
+ conn ? conn : session->conns[0],
+ pool));
+ }
+
+ decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
+ decoded_orig = svn_path_uri_decode(orig_path, pool);
+ *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
+ SVN_ERR_ASSERT(*rel_path != NULL);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__report_resource(const char **report_target,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
+{
+ /* If we have HTTP v2 support, we want to report against the 'me'
+ resource. */
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ *report_target = apr_pstrdup(pool, session->me_resource);
+
+ /* Otherwise, we'll use the default VCC. */
+ else
+ SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__error_on_status(int status_code,
+ const char *path,
+ const char *location)
+{
+ switch(status_code)
+ {
+ case 301:
+ case 302:
+ case 307:
+ return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
+ (status_code == 301)
+ ? _("Repository moved permanently to '%s';"
+ " please relocate")
+ : _("Repository moved temporarily to '%s';"
+ " please relocate"), location);
+ case 403:
+ return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
+ _("Access to '%s' forbidden"), path);
+
+ case 404:
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("'%s' path not found"), path);
+ case 423:
+ return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
+ _("'%s': no lock token available"), path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
+ svn_delta_shim_callbacks_t *callbacks)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ session->shim_callbacks = callbacks;
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to Expat's XML_StartElementHandler */
+static void
+expat_start(void *userData, const char *raw_name, const char **attrs)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_start(ectx->xmlctx,
+ raw_name, attrs));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Conforms to Expat's XML_EndElementHandler */
+static void
+expat_end(void *userData, const char *raw_name)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Conforms to Expat's XML_CharacterDataHandler */
+static void
+expat_cdata(void *userData, const char *data, int len)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+expat_response_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct expat_ctx_t *ectx = baton;
+
+ if (!ectx->parser)
+ {
+ ectx->parser = XML_ParserCreate(NULL);
+ apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup, apr_pool_cleanup_null);
+ XML_SetUserData(ectx->parser, ectx);
+ XML_SetElementHandler(ectx->parser, expat_start, expat_end);
+ XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
+ }
+
+ /* ### TODO: sline.code < 200 should really be handled by the core */
+ if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300))
+ {
+ /* By deferring to expect_empty_body(), it will make a choice on
+ how to handle the body. Whatever the decision, the core handler
+ will take over, and we will not be called again. */
+ return svn_error_trace(svn_ra_serf__expect_empty_body(
+ request, response, ectx->handler,
+ scratch_pool));
+ }
+
+ while (1)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+ int expat_status;
+
+ status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return svn_ra_serf__wrap_err(status, NULL);
+
+#if 0
+ /* ### move restart/skip into the core handler */
+ ectx->handler->read_size += len;
+#endif
+
+ /* ### move PAUSED behavior to a new response handler that can feed
+ ### an inner handler, or can pause for a while. */
+
+ /* ### should we have an IGNORE_ERRORS flag like the v1 parser? */
+
+ expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */);
+
+ /* We need to check INNER_ERROR first. This is an error from the
+ callbacks that has been "dropped off" for us to retrieve. On
+ current Expat parsers, we stop the parser when an error occurs,
+ so we want to ignore EXPAT_STATUS (which reports the stoppage).
+
+ If an error is not present, THEN we go ahead and look for parsing
+ errors. */
+ if (ectx->inner_error)
+ {
+ apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup);
+ return svn_error_trace(ectx->inner_error);
+ }
+ if (expat_status == XML_STATUS_ERROR)
+ return svn_error_createf(SVN_ERR_XML_MALFORMED,
+ ectx->inner_error,
+ _("The %s response contains invalid XML"
+ " (%d %s)"),
+ ectx->handler->method,
+ ectx->handler->sline.code,
+ ectx->handler->sline.reason);
+
+ /* The parsing went fine. What has the bucket told us? */
+
+ if (APR_STATUS_IS_EOF(status))
+ {
+ /* Tell expat we've reached the end of the content. Ignore the
+ return status. We just don't care. */
+ (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */);
+
+ svn_ra_serf__xml_context_destroy(ectx->xmlctx);
+ apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup);
+
+ /* ### should check XMLCTX to see if it has returned to the
+ ### INITIAL state. we may have ended early... */
+ }
+
+ if (status && !SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ }
+
+ /* NOTREACHED */
+}
+
+
+svn_ra_serf__handler_t *
+svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__handler_t *handler;
+ struct expat_ctx_t *ectx;
+
+ ectx = apr_pcalloc(result_pool, sizeof(*ectx));
+ ectx->xmlctx = xmlctx;
+ ectx->parser = NULL;
+ ectx->cleanup_pool = result_pool;
+
+
+ handler = apr_pcalloc(result_pool, sizeof(*handler));
+ handler->handler_pool = result_pool;
+ handler->response_handler = expat_response_handler;
+ handler->response_baton = ectx;
+
+ ectx->handler = handler;
+
+ return handler;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/util_error.c b/contrib/subversion/subversion/libsvn_ra_serf/util_error.c
new file mode 100644
index 0000000..da66091
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/util_error.c
@@ -0,0 +1,100 @@
+/*
+ * util_error.c : serf utility routines for wrapping serf status codes
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+#include <serf.h>
+
+#include "svn_utf.h"
+#include "private/svn_error_private.h"
+
+#include "ra_serf.h"
+
+/*
+ * Undefine the helpers for creating errors.
+ *
+ * *NOTE*: Any use of these functions in any other function may need
+ * to call svn_error__locate() because the macro that would otherwise
+ * do this is being undefined and the filename and line number will
+ * not be properly set in the static error_file and error_line
+ * variables.
+ */
+#undef svn_error_create
+#undef svn_error_createf
+#undef svn_error_quick_wrap
+#undef svn_error_wrap_apr
+#undef svn_ra_serf__wrap_err
+
+svn_error_t *
+svn_ra_serf__wrap_err(apr_status_t status,
+ const char *fmt,
+ ...)
+{
+ const char *serf_err_msg = serf_error_string(status);
+ svn_error_t *err;
+ va_list ap;
+
+ err = svn_error_create(status, NULL, NULL);
+
+ if (serf_err_msg || fmt)
+ {
+ const char *msg;
+ const char *err_msg;
+ char errbuf[255]; /* Buffer for APR error message. */
+
+ if (serf_err_msg)
+ {
+ err_msg = serf_err_msg;
+ }
+ else
+ {
+ svn_error_t *utf8_err;
+
+ /* Grab the APR error message. */
+ apr_strerror(status, errbuf, sizeof(errbuf));
+ utf8_err = svn_utf_cstring_to_utf8(&err_msg, errbuf, err->pool);
+ if (utf8_err)
+ err_msg = NULL;
+ svn_error_clear(utf8_err);
+ }
+
+ /* Append it to the formatted message. */
+ if (fmt)
+ {
+ va_start(ap, fmt);
+ msg = apr_pvsprintf(err->pool, fmt, ap);
+ va_end(ap);
+ }
+ else
+ {
+ msg = "ra_serf";
+ }
+ if (err_msg)
+ {
+ err->message = apr_pstrcat(err->pool, msg, ": ", err_msg, NULL);
+ }
+ else
+ {
+ err->message = msg;
+ }
+ }
+
+ return err;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_serf/xml.c b/contrib/subversion/subversion/libsvn_ra_serf/xml.c
new file mode 100644
index 0000000..95017d5
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_serf/xml.c
@@ -0,0 +1,825 @@
+/*
+ * xml.c : standard XML parsing routines for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_path.h"
+
+#include "svn_private_config.h"
+#include "private/svn_string_private.h"
+
+#include "ra_serf.h"
+
+
+struct svn_ra_serf__xml_context_t {
+ /* Current state information. */
+ svn_ra_serf__xml_estate_t *current;
+
+ /* If WAITING.NAMESPACE != NULL, wait for NAMESPACE:NAME element to be
+ closed before looking for transitions from CURRENT->STATE. */
+ svn_ra_serf__dav_props_t waiting;
+
+ /* The transition table. */
+ const svn_ra_serf__xml_transition_t *ttable;
+
+ /* The callback information. */
+ svn_ra_serf__xml_opened_t opened_cb;
+ svn_ra_serf__xml_closed_t closed_cb;
+ svn_ra_serf__xml_cdata_t cdata_cb;
+ void *baton;
+
+ /* Linked list of free states. */
+ svn_ra_serf__xml_estate_t *free_states;
+
+#ifdef SVN_DEBUG
+ /* Used to verify we are not re-entering a callback, specifically to
+ ensure SCRATCH_POOL is not cleared while an outer callback is
+ trying to use it. */
+ svn_boolean_t within_callback;
+#define START_CALLBACK(xmlctx) \
+ do { \
+ svn_ra_serf__xml_context_t *xmlctx__tmp = (xmlctx); \
+ SVN_ERR_ASSERT(!xmlctx__tmp->within_callback); \
+ xmlctx__tmp->within_callback = TRUE; \
+ } while (0)
+#define END_CALLBACK(xmlctx) ((xmlctx)->within_callback = FALSE)
+#else
+#define START_CALLBACK(xmlctx) /* empty */
+#define END_CALLBACK(xmlctx) /* empty */
+#endif /* SVN_DEBUG */
+
+ apr_pool_t *scratch_pool;
+
+};
+
+struct svn_ra_serf__xml_estate_t {
+ /* The current state value. */
+ int state;
+
+ /* The xml tag that opened this state. Waiting for the tag to close. */
+ svn_ra_serf__dav_props_t tag;
+
+ /* Should the CLOSED_CB function be called for custom processing when
+ this tag is closed? */
+ svn_boolean_t custom_close;
+
+ /* A pool may be constructed for this state. */
+ apr_pool_t *state_pool;
+
+ /* The namespaces extent for this state/element. This will start with
+ the parent's NS_LIST, and we will push new namespaces into our
+ local list. The parent will be unaffected by our locally-scoped data. */
+ svn_ra_serf__ns_t *ns_list;
+
+ /* Any collected attribute values. char * -> svn_string_t *. May be NULL
+ if no attributes have been collected. */
+ apr_hash_t *attrs;
+
+ /* Any collected cdata. May be NULL if no cdata is being collected. */
+ svn_stringbuf_t *cdata;
+
+ /* Previous/outer state. */
+ svn_ra_serf__xml_estate_t *prev;
+
+};
+
+
+static void
+define_namespaces(svn_ra_serf__ns_t **ns_list,
+ const char *const *attrs,
+ apr_pool_t *(*get_pool)(void *baton),
+ void *baton)
+{
+ const char *const *tmp_attrs = attrs;
+
+ for (tmp_attrs = attrs; *tmp_attrs != NULL; tmp_attrs += 2)
+ {
+ if (strncmp(*tmp_attrs, "xmlns", 5) == 0)
+ {
+ const svn_ra_serf__ns_t *cur_ns;
+ svn_boolean_t found = FALSE;
+ const char *prefix;
+
+ /* The empty prefix, or a named-prefix. */
+ if (tmp_attrs[0][5] == ':')
+ prefix = &tmp_attrs[0][6];
+ else
+ prefix = "";
+
+ /* Have we already defined this ns previously? */
+ for (cur_ns = *ns_list; cur_ns; cur_ns = cur_ns->next)
+ {
+ if (strcmp(cur_ns->namespace, prefix) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ apr_pool_t *pool;
+ svn_ra_serf__ns_t *new_ns;
+
+ if (get_pool)
+ pool = get_pool(baton);
+ else
+ pool = baton;
+ new_ns = apr_palloc(pool, sizeof(*new_ns));
+ new_ns->namespace = apr_pstrdup(pool, prefix);
+ new_ns->url = apr_pstrdup(pool, tmp_attrs[1]);
+
+ /* Push into the front of NS_LIST. Parent states will point
+ to later in the chain, so will be unaffected by
+ shadowing/other namespaces pushed onto NS_LIST. */
+ new_ns->next = *ns_list;
+ *ns_list = new_ns;
+ }
+ }
+ }
+}
+
+
+void
+svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
+ const char *const *attrs,
+ apr_pool_t *result_pool)
+{
+ define_namespaces(ns_list, attrs, NULL /* get_pool */, result_pool);
+}
+
+
+/*
+ * Look up NAME in the NS_LIST list for previously declared namespace
+ * definitions and return a DAV_PROPS_T-tuple that has values.
+ */
+void
+svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
+ const svn_ra_serf__ns_t *ns_list,
+ const char *name)
+{
+ const char *colon;
+
+ colon = strchr(name, ':');
+ if (colon)
+ {
+ const svn_ra_serf__ns_t *ns;
+
+ for (ns = ns_list; ns; ns = ns->next)
+ {
+ if (strncmp(ns->namespace, name, colon - name) == 0)
+ {
+ returned_prop_name->namespace = ns->url;
+ returned_prop_name->name = colon + 1;
+ return;
+ }
+ }
+ }
+ else
+ {
+ const svn_ra_serf__ns_t *ns;
+
+ for (ns = ns_list; ns; ns = ns->next)
+ {
+ if (! ns->namespace[0])
+ {
+ returned_prop_name->namespace = ns->url;
+ returned_prop_name->name = name;
+ return;
+ }
+ }
+ }
+
+ /* If the prefix is not found, then the name is NOT within a
+ namespace. */
+ returned_prop_name->namespace = "";
+ returned_prop_name->name = name;
+}
+
+
+#define XML_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+
+void
+svn_ra_serf__add_xml_header_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc)
+{
+ serf_bucket_t *tmp;
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(XML_HEADER, sizeof(XML_HEADER) - 1,
+ bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+}
+
+void
+svn_ra_serf__add_open_tag_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *tag, ...)
+{
+ va_list ap;
+ const char *key;
+ serf_bucket_t *tmp;
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(tag, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ va_start(ap, tag);
+ while ((key = va_arg(ap, char *)) != NULL)
+ {
+ const char *val = va_arg(ap, const char *);
+ if (val)
+ {
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" ", 1, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(key, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("=\"", 2, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(val, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", 1, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+ }
+ }
+ va_end(ap);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(">", 1, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+}
+
+void
+svn_ra_serf__add_close_tag_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *tag)
+{
+ serf_bucket_t *tmp;
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</", 2, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(tag, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(">", 1, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+}
+
+void
+svn_ra_serf__add_cdata_len_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *data, apr_size_t len)
+{
+ const char *end = data + len;
+ const char *p = data, *q;
+ serf_bucket_t *tmp_bkt;
+
+ while (1)
+ {
+ /* Find a character which needs to be quoted and append bytes up
+ to that point. Strictly speaking, '>' only needs to be
+ quoted if it follows "]]", but it's easier to quote it all
+ the time.
+
+ So, why are we escaping '\r' here? Well, according to the
+ XML spec, '\r\n' gets converted to '\n' during XML parsing.
+ Also, any '\r' not followed by '\n' is converted to '\n'. By
+ golly, if we say we want to escape a '\r', we want to make
+ sure it remains a '\r'! */
+ q = p;
+ while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '\r')
+ q++;
+
+
+ tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(p, q - p, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
+
+ /* We may already be a winner. */
+ if (q == end)
+ break;
+
+ /* Append the entity reference for the character. */
+ if (*q == '&')
+ {
+ tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&amp;", sizeof("&amp;") - 1,
+ bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
+ }
+ else if (*q == '<')
+ {
+ tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&lt;", sizeof("&lt;") - 1,
+ bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
+ }
+ else if (*q == '>')
+ {
+ tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&gt;", sizeof("&gt;") - 1,
+ bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
+ }
+ else if (*q == '\r')
+ {
+ tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&#13;", sizeof("&#13;") - 1,
+ bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
+ }
+
+ p = q + 1;
+ }
+}
+
+void svn_ra_serf__add_tag_buckets(serf_bucket_t *agg_bucket, const char *tag,
+ const char *value,
+ serf_bucket_alloc_t *bkt_alloc)
+{
+ svn_ra_serf__add_open_tag_buckets(agg_bucket, bkt_alloc, tag, NULL);
+
+ if (value)
+ {
+ svn_ra_serf__add_cdata_len_buckets(agg_bucket, bkt_alloc,
+ value, strlen(value));
+ }
+
+ svn_ra_serf__add_close_tag_buckets(agg_bucket, bkt_alloc, tag);
+}
+
+void
+svn_ra_serf__xml_push_state(svn_ra_serf__xml_parser_t *parser,
+ int state)
+{
+ svn_ra_serf__xml_state_t *new_state;
+
+ if (!parser->free_state)
+ {
+ new_state = apr_palloc(parser->pool, sizeof(*new_state));
+ new_state->pool = svn_pool_create(parser->pool);
+ }
+ else
+ {
+ new_state = parser->free_state;
+ parser->free_state = parser->free_state->prev;
+
+ svn_pool_clear(new_state->pool);
+ }
+
+ if (parser->state)
+ {
+ new_state->private = parser->state->private;
+ new_state->ns_list = parser->state->ns_list;
+ }
+ else
+ {
+ new_state->private = NULL;
+ new_state->ns_list = NULL;
+ }
+
+ new_state->current_state = state;
+
+ /* Add it to the state chain. */
+ new_state->prev = parser->state;
+ parser->state = new_state;
+}
+
+void svn_ra_serf__xml_pop_state(svn_ra_serf__xml_parser_t *parser)
+{
+ svn_ra_serf__xml_state_t *cur_state;
+
+ cur_state = parser->state;
+ parser->state = cur_state->prev;
+ cur_state->prev = parser->free_state;
+ parser->free_state = cur_state;
+}
+
+
+/* Return a pool for XES to use for self-alloc (and other specifics). */
+static apr_pool_t *
+xes_pool(const svn_ra_serf__xml_estate_t *xes)
+{
+ /* Move up through parent states looking for one with a pool. This
+ will always terminate since the initial state has a pool. */
+ while (xes->state_pool == NULL)
+ xes = xes->prev;
+ return xes->state_pool;
+}
+
+
+static void
+ensure_pool(svn_ra_serf__xml_estate_t *xes)
+{
+ if (xes->state_pool == NULL)
+ xes->state_pool = svn_pool_create(xes_pool(xes));
+}
+
+
+/* This callback is used by define_namespaces() to wait until a pool is
+ required before constructing it. */
+static apr_pool_t *
+lazy_create_pool(void *baton)
+{
+ svn_ra_serf__xml_estate_t *xes = baton;
+
+ ensure_pool(xes);
+ return xes->state_pool;
+}
+
+void
+svn_ra_serf__xml_context_destroy(
+ svn_ra_serf__xml_context_t *xmlctx)
+{
+ svn_pool_destroy(xmlctx->scratch_pool);
+}
+
+svn_ra_serf__xml_context_t *
+svn_ra_serf__xml_context_create(
+ const svn_ra_serf__xml_transition_t *ttable,
+ svn_ra_serf__xml_opened_t opened_cb,
+ svn_ra_serf__xml_closed_t closed_cb,
+ svn_ra_serf__xml_cdata_t cdata_cb,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__xml_context_t *xmlctx;
+ svn_ra_serf__xml_estate_t *xes;
+
+ xmlctx = apr_pcalloc(result_pool, sizeof(*xmlctx));
+ xmlctx->ttable = ttable;
+ xmlctx->opened_cb = opened_cb;
+ xmlctx->closed_cb = closed_cb;
+ xmlctx->cdata_cb = cdata_cb;
+ xmlctx->baton = baton;
+ xmlctx->scratch_pool = svn_pool_create(result_pool);
+
+ xes = apr_pcalloc(result_pool, sizeof(*xes));
+ /* XES->STATE == 0 */
+
+ /* Child states may use this pool to allocate themselves. If a child
+ needs to collect information, then it will construct a subpool and
+ will use that to allocate itself and its collected data. */
+ xes->state_pool = result_pool;
+
+ xmlctx->current = xes;
+
+ return xmlctx;
+}
+
+
+apr_hash_t *
+svn_ra_serf__xml_gather_since(svn_ra_serf__xml_estate_t *xes,
+ int stop_state)
+{
+ apr_hash_t *data;
+ apr_pool_t *pool;
+
+ ensure_pool(xes);
+ pool = xes->state_pool;
+
+ data = apr_hash_make(pool);
+
+ for (; xes != NULL; xes = xes->prev)
+ {
+ if (xes->attrs != NULL)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, xes->attrs); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+
+ /* Parent name/value lifetimes are at least as long as POOL. */
+ apr_hash_this(hi, &key, &klen, &val);
+ apr_hash_set(data, key, klen, val);
+ }
+ }
+
+ if (xes->state == stop_state)
+ break;
+ }
+
+ return data;
+}
+
+
+void
+svn_ra_serf__xml_note(svn_ra_serf__xml_estate_t *xes,
+ int state,
+ const char *name,
+ const char *value)
+{
+ svn_ra_serf__xml_estate_t *scan;
+
+ for (scan = xes; scan != NULL && scan->state != state; scan = scan->prev)
+ /* pass */ ;
+
+ SVN_ERR_ASSERT_NO_RETURN(scan != NULL);
+
+ /* Make sure the target state has a pool. */
+ ensure_pool(scan);
+
+ /* ... and attribute storage. */
+ if (scan->attrs == NULL)
+ scan->attrs = apr_hash_make(scan->state_pool);
+
+ /* In all likelihood, NAME is a string constant. But we can't really
+ be sure. And it isn't like we're storing a billion of these into
+ the state pool. */
+ svn_hash_sets(scan->attrs,
+ apr_pstrdup(scan->state_pool, name),
+ apr_pstrdup(scan->state_pool, value));
+}
+
+
+apr_pool_t *
+svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t *xes)
+{
+ /* If they asked for a pool, then ensure that we have one to provide. */
+ ensure_pool(xes);
+
+ return xes->state_pool;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name,
+ const char *const *attrs)
+{
+ svn_ra_serf__xml_estate_t *current = xmlctx->current;
+ svn_ra_serf__dav_props_t elemname;
+ const svn_ra_serf__xml_transition_t *scan;
+ apr_pool_t *new_pool;
+ svn_ra_serf__xml_estate_t *new_xes;
+
+ /* If we're waiting for an element to close, then just ignore all
+ other element-opens. */
+ if (xmlctx->waiting.namespace != NULL)
+ return SVN_NO_ERROR;
+
+ /* Look for xmlns: attributes. Lazily create the state pool if any
+ were found. */
+ define_namespaces(&current->ns_list, attrs, lazy_create_pool, current);
+
+ svn_ra_serf__expand_ns(&elemname, current->ns_list, raw_name);
+
+ for (scan = xmlctx->ttable; scan->ns != NULL; ++scan)
+ {
+ if (scan->from_state != current->state)
+ continue;
+
+ /* Wildcard tag match. */
+ if (*scan->name == '*')
+ break;
+
+ /* Found a specific transition. */
+ if (strcmp(elemname.name, scan->name) == 0
+ && strcmp(elemname.namespace, scan->ns) == 0)
+ break;
+ }
+ if (scan->ns == NULL)
+ {
+ xmlctx->waiting = elemname;
+ /* ### return? */
+ return SVN_NO_ERROR;
+ }
+
+ /* We should not be told to collect cdata if the closed_cb will not
+ be called. */
+ SVN_ERR_ASSERT(!scan->collect_cdata || scan->custom_close);
+
+ /* Found a transition. Make it happen. */
+
+ /* ### todo. push state */
+
+ /* ### how to use free states? */
+ /* This state should be allocated in the extent pool. If we will be
+ collecting information for this state, then construct a subpool.
+
+ ### potentially optimize away the subpool if none of the
+ ### attributes are present. subpools are cheap, tho... */
+ new_pool = xes_pool(current);
+ if (scan->collect_cdata || scan->collect_attrs[0])
+ {
+ new_pool = svn_pool_create(new_pool);
+
+ /* Prep the new state. */
+ new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
+ new_xes->state_pool = new_pool;
+
+ /* If we're supposed to collect cdata, then set up a buffer for
+ this. The existence of this buffer will instruct our cdata
+ callback to collect the cdata. */
+ if (scan->collect_cdata)
+ new_xes->cdata = svn_stringbuf_create_empty(new_pool);
+
+ if (scan->collect_attrs[0] != NULL)
+ {
+ const char *const *saveattr = &scan->collect_attrs[0];
+
+ new_xes->attrs = apr_hash_make(new_pool);
+ for (; *saveattr != NULL; ++saveattr)
+ {
+ const char *name;
+ const char *value;
+
+ if (**saveattr == '?')
+ {
+ name = *saveattr + 1;
+ value = svn_xml_get_attr_value(name, attrs);
+ }
+ else
+ {
+ name = *saveattr;
+ value = svn_xml_get_attr_value(name, attrs);
+ if (value == NULL)
+ return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
+ NULL,
+ _("Missing XML attribute: '%s'"),
+ name);
+ }
+
+ if (value)
+ svn_hash_sets(new_xes->attrs, name,
+ apr_pstrdup(new_pool, value));
+ }
+ }
+ }
+ else
+ {
+ /* Prep the new state. */
+ new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
+ /* STATE_POOL remains NULL. */
+ }
+
+ /* Some basic copies to set up the new estate. */
+ new_xes->state = scan->to_state;
+ new_xes->tag.name = apr_pstrdup(new_pool, elemname.name);
+ new_xes->tag.namespace = apr_pstrdup(new_pool, elemname.namespace);
+ new_xes->custom_close = scan->custom_close;
+
+ /* Start with the parent's namespace set. */
+ new_xes->ns_list = current->ns_list;
+
+ /* The new state is prepared. Make it current. */
+ new_xes->prev = current;
+ xmlctx->current = new_xes;
+
+ if (xmlctx->opened_cb)
+ {
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->opened_cb(new_xes, xmlctx->baton,
+ new_xes->state, &new_xes->tag,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_end(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name)
+{
+ svn_ra_serf__xml_estate_t *xes = xmlctx->current;
+ svn_ra_serf__dav_props_t elemname;
+
+ svn_ra_serf__expand_ns(&elemname, xes->ns_list, raw_name);
+
+ if (xmlctx->waiting.namespace != NULL)
+ {
+ /* If this element is not the closer, then keep waiting... */
+ if (strcmp(elemname.name, xmlctx->waiting.name) != 0
+ || strcmp(elemname.namespace, xmlctx->waiting.namespace) != 0)
+ return SVN_NO_ERROR;
+
+ /* Found it. Stop waiting, and go back for more. */
+ xmlctx->waiting.namespace = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* We should be looking at the same tag that opened the current state.
+
+ Unknown elements are simply skipped, so we wouldn't reach this check.
+
+ Known elements push a new state for a given tag. Some other elemname
+ would imply closing an ancestor tag (where did ours go?) or a spurious
+ tag closure. */
+ if (strcmp(elemname.name, xes->tag.name) != 0
+ || strcmp(elemname.namespace, xes->tag.namespace) != 0)
+ return svn_error_create(SVN_ERR_XML_MALFORMED, NULL,
+ _("The response contains invalid XML"));
+
+ if (xes->custom_close)
+ {
+ const svn_string_t *cdata;
+
+ if (xes->cdata)
+ {
+ cdata = svn_stringbuf__morph_into_string(xes->cdata);
+#ifdef SVN_DEBUG
+ /* We might toss the pool holding this structure, but it could also
+ be within a parent pool. In any case, for safety's sake, disable
+ the stringbuf against future Badness. */
+ xes->cdata->pool = NULL;
+#endif
+ }
+ else
+ cdata = NULL;
+
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->closed_cb(xes, xmlctx->baton, xes->state,
+ cdata, xes->attrs,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ /* Pop the state. */
+ xmlctx->current = xes->prev;
+
+ /* ### not everything should go on the free state list. XES may go
+ ### away with the state pool. */
+ xes->prev = xmlctx->free_states;
+ xmlctx->free_states = xes;
+
+ /* If there is a STATE_POOL, then toss it. This will get rid of as much
+ memory as possible. Potentially the XES (if we didn't create a pool
+ right away, then XES may be in a parent pool). */
+ if (xes->state_pool)
+ svn_pool_destroy(xes->state_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx,
+ const char *data,
+ apr_size_t len)
+{
+ /* If we are waiting for a closing tag, then we are uninterested in
+ the cdata. Just return. */
+ if (xmlctx->waiting.namespace != NULL)
+ return SVN_NO_ERROR;
+
+ /* If the current state is collecting cdata, then copy the cdata. */
+ if (xmlctx->current->cdata != NULL)
+ {
+ svn_stringbuf_appendbytes(xmlctx->current->cdata, data, len);
+ }
+ /* ... else if a CDATA_CB has been supplied, then invoke it for
+ all states. */
+ else if (xmlctx->cdata_cb != NULL)
+ {
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->cdata_cb(xmlctx->current,
+ xmlctx->baton,
+ xmlctx->current->state,
+ data, len,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_ra_svn/client.c b/contrib/subversion/subversion/libsvn_ra_svn/client.c
new file mode 100644
index 0000000..4b3762c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_svn/client.c
@@ -0,0 +1,2739 @@
+/*
+ * client.c : Functions for repository access via the Subversion protocol
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_private_config.h"
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+#include <apr_network_io.h>
+#include <apr_uri.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_time.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_config.h"
+#include "svn_ra.h"
+#include "svn_ra_svn.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "svn_version.h"
+
+#include "svn_private_config.h"
+
+#include "private/svn_fspath.h"
+
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_svn.h"
+
+#ifdef SVN_HAVE_SASL
+#define DO_AUTH svn_ra_svn__do_cyrus_auth
+#else
+#define DO_AUTH svn_ra_svn__do_internal_auth
+#endif
+
+/* We aren't using SVN_DEPTH_IS_RECURSIVE here because that macro (for
+ whatever reason) deems svn_depth_immediates as non-recursive, which
+ is ... kinda true, but not true enough for our purposes. We need
+ our requested recursion level to be *at least* as recursive as the
+ real depth we're looking for.
+ */
+#define DEPTH_TO_RECURSE(d) \
+ ((d) == svn_depth_unknown || (d) > svn_depth_files)
+
+typedef struct ra_svn_commit_callback_baton_t {
+ svn_ra_svn__session_baton_t *sess_baton;
+ apr_pool_t *pool;
+ svn_revnum_t *new_rev;
+ svn_commit_callback2_t callback;
+ void *callback_baton;
+} ra_svn_commit_callback_baton_t;
+
+typedef struct ra_svn_reporter_baton_t {
+ svn_ra_svn__session_baton_t *sess_baton;
+ svn_ra_svn_conn_t *conn;
+ apr_pool_t *pool;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+} ra_svn_reporter_baton_t;
+
+/* Parse an svn URL's tunnel portion into tunnel, if there is a tunnel
+ portion. */
+static void parse_tunnel(const char *url, const char **tunnel,
+ apr_pool_t *pool)
+{
+ *tunnel = NULL;
+
+ if (strncasecmp(url, "svn", 3) != 0)
+ return;
+ url += 3;
+
+ /* Get the tunnel specification, if any. */
+ if (*url == '+')
+ {
+ const char *p;
+
+ url++;
+ p = strchr(url, ':');
+ if (!p)
+ return;
+ *tunnel = apr_pstrmemdup(pool, url, p - url);
+ }
+}
+
+static svn_error_t *make_connection(const char *hostname, unsigned short port,
+ apr_socket_t **sock, apr_pool_t *pool)
+{
+ apr_sockaddr_t *sa;
+ apr_status_t status;
+ int family = APR_INET;
+
+ /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
+ APR_UNSPEC, because it may give us back an IPV6 address even if we can't
+ create IPV6 sockets. */
+
+#if APR_HAVE_IPV6
+#ifdef MAX_SECS_TO_LINGER
+ status = apr_socket_create(sock, APR_INET6, SOCK_STREAM, pool);
+#else
+ status = apr_socket_create(sock, APR_INET6, SOCK_STREAM,
+ APR_PROTO_TCP, pool);
+#endif
+ if (status == 0)
+ {
+ apr_socket_close(*sock);
+ family = APR_UNSPEC;
+ }
+#endif
+
+ /* Resolve the hostname. */
+ status = apr_sockaddr_info_get(&sa, hostname, family, port, 0, pool);
+ if (status)
+ return svn_error_createf(status, NULL, _("Unknown hostname '%s'"),
+ hostname);
+ /* Iterate through the returned list of addresses attempting to
+ * connect to each in turn. */
+ do
+ {
+ /* Create the socket. */
+#ifdef MAX_SECS_TO_LINGER
+ /* ### old APR interface */
+ status = apr_socket_create(sock, sa->family, SOCK_STREAM, pool);
+#else
+ status = apr_socket_create(sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
+ pool);
+#endif
+ if (status == APR_SUCCESS)
+ {
+ status = apr_socket_connect(*sock, sa);
+ if (status != APR_SUCCESS)
+ apr_socket_close(*sock);
+ }
+ sa = sa->next;
+ }
+ while (status != APR_SUCCESS && sa);
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't connect to host '%s'"),
+ hostname);
+
+ /* Enable TCP keep-alives on the socket so we time out when
+ * the connection breaks due to network-layer problems.
+ * If the peer has dropped the connection due to a network partition
+ * or a crash, or if the peer no longer considers the connection
+ * valid because we are behind a NAT and our public IP has changed,
+ * it will respond to the keep-alive probe with a RST instead of an
+ * acknowledgment segment, which will cause svn to abort the session
+ * even while it is currently blocked waiting for data from the peer.
+ * See issue #3347. */
+ status = apr_socket_opt_set(*sock, APR_SO_KEEPALIVE, 1);
+ if (status)
+ {
+ /* It's not a fatal error if we cannot enable keep-alives. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *DIFFS to an array of svn_prop_t, allocated in POOL, based on the
+ property diffs in LIST, received from the server. */
+static svn_error_t *parse_prop_diffs(const apr_array_header_t *list,
+ apr_pool_t *pool,
+ apr_array_header_t **diffs)
+{
+ int i;
+
+ *diffs = apr_array_make(pool, list->nelts, sizeof(svn_prop_t));
+
+ for (i = 0; i < list->nelts; i++)
+ {
+ svn_prop_t *prop;
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Prop diffs element not a list"));
+ prop = apr_array_push(*diffs);
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "c(?s)", &prop->name,
+ &prop->value));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Parse a lockdesc, provided in LIST as specified by the protocol into
+ LOCK, allocated in POOL. */
+static svn_error_t *parse_lock(const apr_array_header_t *list, apr_pool_t *pool,
+ svn_lock_t **lock)
+{
+ const char *cdate, *edate;
+ *lock = svn_lock_create(pool);
+ SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "ccc(?c)c(?c)", &(*lock)->path,
+ &(*lock)->token, &(*lock)->owner,
+ &(*lock)->comment, &cdate, &edate));
+ (*lock)->path = svn_fspath__canonicalize((*lock)->path, pool);
+ SVN_ERR(svn_time_from_cstring(&(*lock)->creation_date, cdate, pool));
+ if (edate)
+ SVN_ERR(svn_time_from_cstring(&(*lock)->expiration_date, edate, pool));
+ return SVN_NO_ERROR;
+}
+
+/* --- AUTHENTICATION ROUTINES --- */
+
+svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *mech, const char *mech_arg)
+{
+ return svn_ra_svn__write_tuple(conn, pool, "w(?c)", mech, mech_arg);
+}
+
+static svn_error_t *handle_auth_request(svn_ra_svn__session_baton_t *sess,
+ apr_pool_t *pool)
+{
+ svn_ra_svn_conn_t *conn = sess->conn;
+ apr_array_header_t *mechlist;
+ const char *realm;
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "lc", &mechlist, &realm));
+ if (mechlist->nelts == 0)
+ return SVN_NO_ERROR;
+ return DO_AUTH(sess, mechlist, realm, pool);
+}
+
+/* --- REPORTER IMPLEMENTATION --- */
+
+static svn_error_t *ra_svn_set_path(void *baton, const char *path,
+ svn_revnum_t rev,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ ra_svn_reporter_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__write_cmd_set_path(b->conn, pool, path, rev,
+ start_empty, lock_token, depth));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_delete_path(void *baton, const char *path,
+ apr_pool_t *pool)
+{
+ ra_svn_reporter_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__write_cmd_delete_path(b->conn, pool, path));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_link_path(void *baton, const char *path,
+ const char *url,
+ svn_revnum_t rev,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ ra_svn_reporter_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__write_cmd_link_path(b->conn, pool, path, url, rev,
+ start_empty, lock_token, depth));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_finish_report(void *baton,
+ apr_pool_t *pool)
+{
+ ra_svn_reporter_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__write_cmd_finish_report(b->conn, b->pool));
+ SVN_ERR(handle_auth_request(b->sess_baton, b->pool));
+ SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton,
+ NULL, FALSE));
+ SVN_ERR(svn_ra_svn__read_cmd_response(b->conn, b->pool, ""));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_abort_report(void *baton,
+ apr_pool_t *pool)
+{
+ ra_svn_reporter_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__write_cmd_abort_report(b->conn, b->pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_ra_reporter3_t ra_svn_reporter = {
+ ra_svn_set_path,
+ ra_svn_delete_path,
+ ra_svn_link_path,
+ ra_svn_finish_report,
+ ra_svn_abort_report
+};
+
+/* Set *REPORTER and *REPORT_BATON to a new reporter which will drive
+ * EDITOR/EDIT_BATON when it gets the finish_report() call.
+ *
+ * Allocate the new reporter in POOL.
+ */
+static svn_error_t *
+ra_svn_get_reporter(svn_ra_svn__session_baton_t *sess_baton,
+ apr_pool_t *pool,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ const char *target,
+ svn_depth_t depth,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton)
+{
+ ra_svn_reporter_baton_t *b;
+ const svn_delta_editor_t *filter_editor;
+ void *filter_baton;
+
+ /* We can skip the depth filtering when the user requested
+ depth_files or depth_infinity because the server will
+ transmit the right stuff anyway. */
+ if ((depth != svn_depth_files) && (depth != svn_depth_infinity)
+ && ! svn_ra_svn_has_capability(sess_baton->conn, SVN_RA_SVN_CAP_DEPTH))
+ {
+ SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
+ &filter_baton,
+ editor, edit_baton, depth,
+ *target != '\0',
+ pool));
+ editor = filter_editor;
+ edit_baton = filter_baton;
+ }
+
+ b = apr_palloc(pool, sizeof(*b));
+ b->sess_baton = sess_baton;
+ b->conn = sess_baton->conn;
+ b->pool = pool;
+ b->editor = editor;
+ b->edit_baton = edit_baton;
+
+ *reporter = &ra_svn_reporter;
+ *report_baton = b;
+
+ return SVN_NO_ERROR;
+}
+
+/* --- RA LAYER IMPLEMENTATION --- */
+
+/* (Note: *ARGV is an output parameter.) */
+static svn_error_t *find_tunnel_agent(const char *tunnel,
+ const char *hostinfo,
+ const char ***argv,
+ apr_hash_t *config, apr_pool_t *pool)
+{
+ svn_config_t *cfg;
+ const char *val, *var, *cmd;
+ char **cmd_argv;
+ apr_size_t len;
+ apr_status_t status;
+ int n;
+
+ /* Look up the tunnel specification in config. */
+ cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
+ svn_config_get(cfg, &val, SVN_CONFIG_SECTION_TUNNELS, tunnel, NULL);
+
+ /* We have one predefined tunnel scheme, if it isn't overridden by config. */
+ if (!val && strcmp(tunnel, "ssh") == 0)
+ {
+ /* Killing the tunnel agent with SIGTERM leads to unsightly
+ * stderr output from ssh, unless we pass -q.
+ * The "-q" option to ssh is widely supported: all versions of
+ * OpenSSH have it, the old ssh-1.x and the 2.x, 3.x ssh.com
+ * versions have it too. If the user is using some other ssh
+ * implementation that doesn't accept it, they can override it
+ * in the [tunnels] section of the config. */
+ val = "$SVN_SSH ssh -q";
+ }
+
+ if (!val || !*val)
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ _("Undefined tunnel scheme '%s'"), tunnel);
+
+ /* If the scheme definition begins with "$varname", it means there
+ * is an environment variable which can override the command. */
+ if (*val == '$')
+ {
+ val++;
+ len = strcspn(val, " ");
+ var = apr_pstrmemdup(pool, val, len);
+ cmd = getenv(var);
+ if (!cmd)
+ {
+ cmd = val + len;
+ while (*cmd == ' ')
+ cmd++;
+ if (!*cmd)
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ _("Tunnel scheme %s requires environment "
+ "variable %s to be defined"), tunnel,
+ var);
+ }
+ }
+ else
+ cmd = val;
+
+ /* Tokenize the command into a list of arguments. */
+ status = apr_tokenize_to_argv(cmd, &cmd_argv, pool);
+ if (status != APR_SUCCESS)
+ return svn_error_wrap_apr(status, _("Can't tokenize command '%s'"), cmd);
+
+ /* Append the fixed arguments to the result. */
+ for (n = 0; cmd_argv[n] != NULL; n++)
+ ;
+ *argv = apr_palloc(pool, (n + 4) * sizeof(char *));
+ memcpy((void *) *argv, cmd_argv, n * sizeof(char *));
+ (*argv)[n++] = svn_path_uri_decode(hostinfo, pool);
+ (*argv)[n++] = "svnserve";
+ (*argv)[n++] = "-t";
+ (*argv)[n] = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* This function handles any errors which occur in the child process
+ * created for a tunnel agent. We write the error out as a command
+ * failure; the code in ra_svn_open() to read the server's greeting
+ * will see the error and return it to the caller. */
+static void handle_child_process_error(apr_pool_t *pool, apr_status_t status,
+ const char *desc)
+{
+ svn_ra_svn_conn_t *conn;
+ apr_file_t *in_file, *out_file;
+ svn_error_t *err;
+
+ if (apr_file_open_stdin(&in_file, pool)
+ || apr_file_open_stdout(&out_file, pool))
+ return;
+
+ conn = svn_ra_svn_create_conn3(NULL, in_file, out_file,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
+ 0, pool);
+ err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
+ svn_error_clear(svn_ra_svn__write_cmd_failure(conn, pool, err));
+ svn_error_clear(err);
+ svn_error_clear(svn_ra_svn__flush(conn, pool));
+}
+
+/* (Note: *CONN is an output parameter.) */
+static svn_error_t *make_tunnel(const char **args, svn_ra_svn_conn_t **conn,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ apr_proc_t *proc;
+ apr_procattr_t *attr;
+ svn_error_t *err;
+
+ status = apr_procattr_create(&attr, pool);
+ if (status == APR_SUCCESS)
+ status = apr_procattr_io_set(attr, 1, 1, 0);
+ if (status == APR_SUCCESS)
+ status = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);
+ if (status == APR_SUCCESS)
+ status = apr_procattr_child_errfn_set(attr, handle_child_process_error);
+ proc = apr_palloc(pool, sizeof(*proc));
+ if (status == APR_SUCCESS)
+ status = apr_proc_create(proc, *args, args, NULL, attr, pool);
+ if (status != APR_SUCCESS)
+ return svn_error_create(SVN_ERR_RA_CANNOT_CREATE_TUNNEL,
+ svn_error_wrap_apr(status,
+ _("Can't create tunnel")), NULL);
+
+ /* Arrange for the tunnel agent to get a SIGTERM on pool
+ * cleanup. This is a little extreme, but the alternatives
+ * weren't working out.
+ *
+ * Closing the pipes and waiting for the process to die
+ * was prone to mysterious hangs which are difficult to
+ * diagnose (e.g. svnserve dumps core due to unrelated bug;
+ * sshd goes into zombie state; ssh connection is never
+ * closed; ssh never terminates).
+ * See also the long dicussion in issue #2580 if you really
+ * want to know various reasons for these problems and
+ * the different opinions on this issue.
+ *
+ * On Win32, APR does not support KILL_ONLY_ONCE. It only has
+ * KILL_ALWAYS and KILL_NEVER. Other modes are converted to
+ * KILL_ALWAYS, which immediately calls TerminateProcess().
+ * This instantly kills the tunnel, leaving sshd and svnserve
+ * on a remote machine running indefinitely. These processes
+ * accumulate. The problem is most often seen with a fast client
+ * machine and a modest internet connection, as the tunnel
+ * is killed before being able to gracefully complete the
+ * session. In that case, svn is unusable 100% of the time on
+ * the windows machine. Thus, on Win32, we use KILL_NEVER and
+ * take the lesser of two evils.
+ */
+#ifdef WIN32
+ apr_pool_note_subprocess(pool, proc, APR_KILL_NEVER);
+#else
+ apr_pool_note_subprocess(pool, proc, APR_KILL_ONLY_ONCE);
+#endif
+
+ /* APR pipe objects inherit by default. But we don't want the
+ * tunnel agent's pipes held open by future child processes
+ * (such as other ra_svn sessions), so turn that off. */
+ apr_file_inherit_unset(proc->in);
+ apr_file_inherit_unset(proc->out);
+
+ /* Guard against dotfile output to stdout on the server. */
+ *conn = svn_ra_svn_create_conn3(NULL, proc->out, proc->in,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
+ 0, 0, pool);
+ err = svn_ra_svn__skip_leading_garbage(*conn, pool);
+ if (err)
+ return svn_error_quick_wrap(
+ err,
+ _("To better debug SSH connection problems, remove the -q "
+ "option from 'ssh' in the [tunnels] section of your "
+ "Subversion configuration file."));
+
+ return SVN_NO_ERROR;
+}
+
+/* Parse URL inot URI, validating it and setting the default port if none
+ was given. Allocate the URI fileds out of POOL. */
+static svn_error_t *parse_url(const char *url, apr_uri_t *uri,
+ apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+
+ apr_err = apr_uri_parse(pool, url, uri);
+
+ if (apr_err != 0)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal svn repository URL '%s'"), url);
+
+ if (! uri->port)
+ uri->port = SVN_RA_SVN_PORT;
+
+ return SVN_NO_ERROR;
+}
+
+/* Open a session to URL, returning it in *SESS_P, allocating it in POOL.
+ URI is a parsed version of URL. CALLBACKS and CALLBACKS_BATON
+ are provided by the caller of ra_svn_open. If tunnel_argv is non-null,
+ it points to a program argument list to use when invoking the tunnel agent.
+*/
+static svn_error_t *open_session(svn_ra_svn__session_baton_t **sess_p,
+ const char *url,
+ const apr_uri_t *uri,
+ const char **tunnel_argv,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callbacks_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess;
+ svn_ra_svn_conn_t *conn;
+ apr_socket_t *sock;
+ apr_uint64_t minver, maxver;
+ apr_array_header_t *mechlist, *server_caplist, *repos_caplist;
+ const char *client_string = NULL;
+
+ sess = apr_palloc(pool, sizeof(*sess));
+ sess->pool = pool;
+ sess->is_tunneled = (tunnel_argv != NULL);
+ sess->url = apr_pstrdup(pool, url);
+ sess->user = uri->user;
+ sess->hostname = uri->hostname;
+ sess->realm_prefix = apr_psprintf(pool, "<svn://%s:%d>", uri->hostname,
+ uri->port);
+ sess->tunnel_argv = tunnel_argv;
+ sess->callbacks = callbacks;
+ sess->callbacks_baton = callbacks_baton;
+ sess->bytes_read = sess->bytes_written = 0;
+
+ if (tunnel_argv)
+ SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
+ else
+ {
+ SVN_ERR(make_connection(uri->hostname, uri->port, &sock, pool));
+ conn = svn_ra_svn_create_conn3(sock, NULL, NULL,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
+ 0, 0, pool);
+ }
+
+ /* Build the useragent string, querying the client for any
+ customizations it wishes to note. For historical reasons, we
+ still deliver the hard-coded client version info
+ (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
+ separately in the protocol/capabilities handshake below. But the
+ commit logic wants the combined form for use with the
+ SVN_PROP_TXN_USER_AGENT ephemeral property because that's
+ consistent with our DAV approach. */
+ if (sess->callbacks->get_client_string != NULL)
+ SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
+ &client_string, pool));
+ if (client_string)
+ sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
+ client_string, (char *)NULL);
+ else
+ sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
+
+ /* Make sure we set conn->session before reading from it,
+ * because the reader and writer functions expect a non-NULL value. */
+ sess->conn = conn;
+ conn->session = sess;
+
+ /* Read server's greeting. */
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "nnll", &minver, &maxver,
+ &mechlist, &server_caplist));
+
+ /* We support protocol version 2. */
+ if (minver > 2)
+ return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
+ _("Server requires minimum version %d"),
+ (int) minver);
+ if (maxver < 2)
+ return svn_error_createf(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
+ _("Server only supports versions up to %d"),
+ (int) maxver);
+ SVN_ERR(svn_ra_svn_set_capabilities(conn, server_caplist));
+
+ /* All released versions of Subversion support edit-pipeline,
+ * so we do not support servers that do not. */
+ if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
+ return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
+ _("Server does not support edit pipelining"));
+
+ /* In protocol version 2, we send back our protocol version, our
+ * capability list, and the URL, and subsequently there is an auth
+ * request. */
+ /* Client-side capabilities list: */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "n(wwwwww)cc(?c)",
+ (apr_uint64_t) 2,
+ SVN_RA_SVN_CAP_EDIT_PIPELINE,
+ SVN_RA_SVN_CAP_SVNDIFF1,
+ SVN_RA_SVN_CAP_ABSENT_ENTRIES,
+ SVN_RA_SVN_CAP_DEPTH,
+ SVN_RA_SVN_CAP_MERGEINFO,
+ SVN_RA_SVN_CAP_LOG_REVPROPS,
+ url,
+ SVN_RA_SVN__DEFAULT_USERAGENT,
+ client_string));
+ SVN_ERR(handle_auth_request(sess, pool));
+
+ /* This is where the security layer would go into effect if we
+ * supported security layers, which is a ways off. */
+
+ /* Read the repository's uuid and root URL, and perhaps learn more
+ capabilities that weren't available before now. */
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "c?c?l", &conn->uuid,
+ &conn->repos_root, &repos_caplist));
+ if (repos_caplist)
+ SVN_ERR(svn_ra_svn_set_capabilities(conn, repos_caplist));
+
+ if (conn->repos_root)
+ {
+ conn->repos_root = svn_uri_canonicalize(conn->repos_root, pool);
+ /* We should check that the returned string is a prefix of url, since
+ that's the API guarantee, but this isn't true for 1.0 servers.
+ Checking the length prevents client crashes. */
+ if (strlen(conn->repos_root) > strlen(url))
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Impossibly long repository root from "
+ "server"));
+ }
+
+ *sess_p = sess;
+
+ return SVN_NO_ERROR;
+}
+
+
+#ifdef SVN_HAVE_SASL
+#define RA_SVN_DESCRIPTION \
+ N_("Module for accessing a repository using the svn network protocol.\n" \
+ " - with Cyrus SASL authentication")
+#else
+#define RA_SVN_DESCRIPTION \
+ N_("Module for accessing a repository using the svn network protocol.")
+#endif
+
+static const char *ra_svn_get_description(void)
+{
+ return _(RA_SVN_DESCRIPTION);
+}
+
+static const char * const *
+ra_svn_get_schemes(apr_pool_t *pool)
+{
+ static const char *schemes[] = { "svn", NULL };
+
+ return schemes;
+}
+
+
+
+static svn_error_t *ra_svn_open(svn_ra_session_t *session,
+ const char **corrected_url,
+ const char *url,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ apr_pool_t *sess_pool = svn_pool_create(pool);
+ svn_ra_svn__session_baton_t *sess;
+ const char *tunnel, **tunnel_argv;
+ apr_uri_t uri;
+ svn_config_t *cfg, *cfg_client;
+
+ /* We don't support server-prescribed redirections in ra-svn. */
+ if (corrected_url)
+ *corrected_url = NULL;
+
+ SVN_ERR(parse_url(url, &uri, sess_pool));
+
+ parse_tunnel(url, &tunnel, pool);
+
+ if (tunnel)
+ SVN_ERR(find_tunnel_agent(tunnel, uri.hostinfo, &tunnel_argv, config,
+ pool));
+ else
+ tunnel_argv = NULL;
+
+ cfg_client = config
+ ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+ cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_SERVERS) : NULL;
+ svn_auth_set_parameter(callbacks->auth_baton,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, cfg_client);
+ svn_auth_set_parameter(callbacks->auth_baton,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, cfg);
+
+ /* We open the session in a subpool so we can get rid of it if we
+ reparent with a server that doesn't support reparenting. */
+ SVN_ERR(open_session(&sess, url, &uri, tunnel_argv,
+ callbacks, callback_baton, sess_pool));
+ session->priv = sess;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_reparent(svn_ra_session_t *ra_session,
+ const char *url,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = ra_session->priv;
+ svn_ra_svn_conn_t *conn = sess->conn;
+ svn_error_t *err;
+ apr_pool_t *sess_pool;
+ svn_ra_svn__session_baton_t *new_sess;
+ apr_uri_t uri;
+
+ SVN_ERR(svn_ra_svn__write_cmd_reparent(conn, pool, url));
+ err = handle_auth_request(sess, pool);
+ if (! err)
+ {
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+ sess->url = apr_pstrdup(sess->pool, url);
+ return SVN_NO_ERROR;
+ }
+ else if (err->apr_err != SVN_ERR_RA_SVN_UNKNOWN_CMD)
+ return err;
+
+ /* Servers before 1.4 doesn't support this command; try to reconnect
+ instead. */
+ svn_error_clear(err);
+ /* Create a new subpool of the RA session pool. */
+ sess_pool = svn_pool_create(ra_session->pool);
+ err = parse_url(url, &uri, sess_pool);
+ if (! err)
+ err = open_session(&new_sess, url, &uri, sess->tunnel_argv,
+ sess->callbacks, sess->callbacks_baton, sess_pool);
+ /* We destroy the new session pool on error, since it is allocated in
+ the main session pool. */
+ if (err)
+ {
+ svn_pool_destroy(sess_pool);
+ return err;
+ }
+
+ /* We have a new connection, assign it and destroy the old. */
+ ra_session->priv = new_sess;
+ svn_pool_destroy(sess->pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_session_url(svn_ra_session_t *session,
+ const char **url, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ *url = apr_pstrdup(pool, sess->url);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_latest_rev(svn_ra_session_t *session,
+ svn_revnum_t *rev, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_latest_rev(conn, pool));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_dated_rev(svn_ra_session_t *session,
+ svn_revnum_t *rev, apr_time_t tm,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_dated_rev(conn, pool, tm));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "r", rev));
+ return SVN_NO_ERROR;
+}
+
+/* Forward declaration. */
+static svn_error_t *ra_svn_has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool);
+
+static svn_error_t *ra_svn_change_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t dont_care;
+ const svn_string_t *old_value;
+ svn_boolean_t has_atomic_revprops;
+
+ SVN_ERR(ra_svn_has_capability(session, &has_atomic_revprops,
+ SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
+ pool));
+
+ if (old_value_p)
+ {
+ /* How did you get past the same check in svn_ra_change_rev_prop2()? */
+ SVN_ERR_ASSERT(has_atomic_revprops);
+
+ dont_care = FALSE;
+ old_value = *old_value_p;
+ }
+ else
+ {
+ dont_care = TRUE;
+ old_value = NULL;
+ }
+
+ if (has_atomic_revprops)
+ SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop2(conn, pool, rev, name,
+ value, dont_care,
+ old_value));
+ else
+ SVN_ERR(svn_ra_svn__write_cmd_change_rev_prop(conn, pool, rev, name,
+ value));
+
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_uuid(svn_ra_session_t *session, const char **uuid,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ *uuid = conn->uuid;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_repos_root(svn_ra_session_t *session, const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ if (!conn->repos_root)
+ return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
+ _("Server did not send repository root"));
+ *url = conn->repos_root;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_rev_proplist(svn_ra_session_t *session, svn_revnum_t rev,
+ apr_hash_t **props, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_array_header_t *proplist;
+
+ SVN_ERR(svn_ra_svn__write_cmd_rev_proplist(conn, pool, rev));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &proplist));
+ SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_rev_prop(svn_ra_session_t *session, svn_revnum_t rev,
+ const char *name,
+ svn_string_t **value, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ SVN_ERR(svn_ra_svn__write_cmd_rev_prop(conn, pool, rev, name));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?s)", value));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_end_commit(void *baton)
+{
+ ra_svn_commit_callback_baton_t *ccb = baton;
+ svn_commit_info_t *commit_info = svn_create_commit_info(ccb->pool);
+
+ SVN_ERR(handle_auth_request(ccb->sess_baton, ccb->pool));
+ SVN_ERR(svn_ra_svn__read_tuple(ccb->sess_baton->conn, ccb->pool,
+ "r(?c)(?c)?(?c)",
+ &(commit_info->revision),
+ &(commit_info->date),
+ &(commit_info->author),
+ &(commit_info->post_commit_err)));
+
+ if (ccb->callback)
+ SVN_ERR(ccb->callback(commit_info, ccb->callback_baton, ccb->pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_commit(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ ra_svn_commit_callback_baton_t *ccb;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ const svn_string_t *log_msg = svn_hash_gets(revprop_table,
+ SVN_PROP_REVISION_LOG);
+
+ /* If we're sending revprops other than svn:log, make sure the server won't
+ silently ignore them. */
+ if (apr_hash_count(revprop_table) > 1 &&
+ ! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS))
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
+ _("Server doesn't support setting arbitrary "
+ "revision properties during commit"));
+
+ /* If the server supports ephemeral txnprops, add the one that
+ reports the client's version level string. */
+ if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
+ svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
+ {
+ svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
+ svn_string_create(SVN_VER_NUMBER, pool));
+ svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
+ svn_string_create(sess_baton->useragent, pool));
+ }
+
+ /* Tell the server we're starting the commit.
+ Send log message here for backwards compatibility with servers
+ before 1.5. */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(!", "commit",
+ log_msg->data));
+ if (lock_tokens)
+ {
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ const char *path, *token;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ token = val;
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cc", path, token));
+ }
+ svn_pool_destroy(iterpool);
+ }
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b(!", keep_locks));
+ SVN_ERR(svn_ra_svn__write_proplist(conn, pool, revprop_table));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+
+ /* Remember a few arguments for when the commit is over. */
+ ccb = apr_palloc(pool, sizeof(*ccb));
+ ccb->sess_baton = sess_baton;
+ ccb->pool = pool;
+ ccb->new_rev = NULL;
+ ccb->callback = callback;
+ ccb->callback_baton = callback_baton;
+
+ /* Fetch an editor for the caller to drive. The editor will call
+ * ra_svn_end_commit() upon close_edit(), at which point we'll fill
+ * in the new_rev, committed_date, and committed_author values. */
+ svn_ra_svn_get_editor(editor, edit_baton, conn, pool,
+ ra_svn_end_commit, ccb);
+ return SVN_NO_ERROR;
+}
+
+/* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of
+ const char * repos relative paths and properties for those paths, storing
+ the result as an array of svn_prop_inherited_item_t *items. */
+static svn_error_t *
+parse_iproplist(apr_array_header_t **inherited_props,
+ const apr_array_header_t *iproplist,
+ svn_ra_session_t *session,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+
+{
+ int i;
+ const char *repos_root_url;
+ apr_pool_t *iterpool;
+
+ if (iproplist == NULL)
+ {
+ /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
+ capability we shouldn't be asking for inherited props, but if we
+ did and the server sent back nothing then we'll want to handle
+ that. */
+ *inherited_props = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(ra_svn_get_repos_root(session, &repos_root_url, scratch_pool));
+
+ *inherited_props = apr_array_make(
+ result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < iproplist->nelts; i++)
+ {
+ apr_array_header_t *iprop_list;
+ char *parent_rel_path;
+ apr_hash_t *iprops;
+ apr_hash_index_t *hi;
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(result_pool, sizeof(*new_iprop));
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
+ svn_ra_svn_item_t);
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(
+ SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Inherited proplist element not a list"));
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "cl",
+ &parent_rel_path, &iprop_list));
+ SVN_ERR(svn_ra_svn__parse_proplist(iprop_list, iterpool, &iprops));
+ new_iprop->path_or_url = svn_path_url_add_component2(repos_root_url,
+ parent_rel_path,
+ result_pool);
+ new_iprop->prop_hash = apr_hash_make(result_pool);
+ for (hi = apr_hash_first(iterpool, iprops);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+ svn_hash_sets(new_iprop->prop_hash,
+ apr_pstrdup(result_pool, name),
+ svn_string_dup(value, result_pool));
+ }
+ APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
+ new_iprop;
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
+ svn_revnum_t rev, svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_array_header_t *proplist;
+ const char *expected_digest;
+ svn_checksum_t *expected_checksum = NULL;
+ svn_checksum_ctx_t *checksum_ctx;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_file(conn, pool, path, rev,
+ (props != NULL), (stream != NULL)));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?c)rl",
+ &expected_digest,
+ &rev, &proplist));
+
+ if (fetched_rev)
+ *fetched_rev = rev;
+ if (props)
+ SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
+
+ /* We're done if the contents weren't wanted. */
+ if (!stream)
+ return SVN_NO_ERROR;
+
+ if (expected_digest)
+ {
+ SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
+ expected_digest, pool));
+ checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+ }
+
+ /* Read the file's contents. */
+ iterpool = svn_pool_create(pool);
+ while (1)
+ {
+ svn_ra_svn_item_t *item;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
+ if (item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Non-string as part of file contents"));
+ if (item->u.string->len == 0)
+ break;
+
+ if (expected_checksum)
+ SVN_ERR(svn_checksum_update(checksum_ctx, item->u.string->data,
+ item->u.string->len));
+
+ SVN_ERR(svn_stream_write(stream, item->u.string->data,
+ &item->u.string->len));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+
+ if (expected_checksum)
+ {
+ svn_checksum_t *checksum;
+
+ SVN_ERR(svn_checksum_final(&checksum, checksum_ctx, pool));
+ if (!svn_checksum_match(checksum, expected_checksum))
+ return svn_checksum_mismatch_err(expected_checksum, checksum, pool,
+ _("Checksum mismatch for '%s'"),
+ path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_dir(svn_ra_session_t *session,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ const char *path,
+ svn_revnum_t rev,
+ apr_uint32_t dirent_fields,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_array_header_t *proplist, *dirlist;
+ int i;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)bb(!", "get-dir", path,
+ rev, (props != NULL), (dirents != NULL)));
+ if (dirent_fields & SVN_DIRENT_KIND)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_KIND));
+ if (dirent_fields & SVN_DIRENT_SIZE)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_SIZE));
+ if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_HAS_PROPS));
+ if (dirent_fields & SVN_DIRENT_CREATED_REV)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_CREATED_REV));
+ if (dirent_fields & SVN_DIRENT_TIME)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_TIME));
+ if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_DIRENT_LAST_AUTHOR));
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "rll", &rev, &proplist,
+ &dirlist));
+
+ if (fetched_rev)
+ *fetched_rev = rev;
+ if (props)
+ SVN_ERR(svn_ra_svn__parse_proplist(proplist, pool, props));
+
+ /* We're done if dirents aren't wanted. */
+ if (!dirents)
+ return SVN_NO_ERROR;
+
+ /* Interpret the directory list. */
+ *dirents = apr_hash_make(pool);
+ for (i = 0; i < dirlist->nelts; i++)
+ {
+ const char *name, *kind, *cdate, *cauthor;
+ svn_boolean_t has_props;
+ svn_dirent_t *dirent;
+ apr_uint64_t size;
+ svn_revnum_t crev;
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(dirlist, i, svn_ra_svn_item_t);
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Dirlist element not a list"));
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cwnbr(?c)(?c)",
+ &name, &kind, &size, &has_props,
+ &crev, &cdate, &cauthor));
+ name = svn_relpath_canonicalize(name, pool);
+ dirent = svn_dirent_create(pool);
+ dirent->kind = svn_node_kind_from_word(kind);
+ dirent->size = size;/* FIXME: svn_filesize_t */
+ dirent->has_props = has_props;
+ dirent->created_rev = crev;
+ /* NOTE: the tuple's format string says CDATE may be NULL. But this
+ function does not allow that. The server has always sent us some
+ random date, however, so this just happens to work. But let's
+ be wary of servers that are (improperly) fixed to send NULL.
+
+ Note: they should NOT be "fixed" to send NULL, as that would break
+ any older clients which received that NULL. But we may as well
+ be defensive against a malicous server. */
+ if (cdate == NULL)
+ dirent->time = 0;
+ else
+ SVN_ERR(svn_time_from_cstring(&dirent->time, cdate, pool));
+ dirent->last_author = cauthor;
+ svn_hash_sets(*dirents, name, dirent);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Converts a apr_uint64_t with values TRUE, FALSE or
+ SVN_RA_SVN_UNSPECIFIED_NUMBER as provided by svn_ra_svn__parse_tuple
+ to a svn_tristate_t */
+static svn_tristate_t
+optbool_to_tristate(apr_uint64_t v)
+{
+ if (v == TRUE) /* not just non-zero but exactly equal to 'TRUE' */
+ return svn_tristate_true;
+ if (v == FALSE)
+ return svn_tristate_false;
+
+ return svn_tristate_unknown; /* Contains SVN_RA_SVN_UNSPECIFIED_NUMBER */
+}
+
+/* If REVISION is SVN_INVALID_REVNUM, no value is sent to the
+ server, which defaults to youngest. */
+static svn_error_t *ra_svn_get_mergeinfo(svn_ra_session_t *session,
+ svn_mergeinfo_catalog_t *catalog,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ int i;
+ apr_array_header_t *mergeinfo_tuple;
+ svn_ra_svn_item_t *elt;
+ const char *path;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "get-mergeinfo"));
+ for (i = 0; i < paths->nelts; i++)
+ {
+ path = APR_ARRAY_IDX(paths, i, const char *);
+ SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
+ }
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)wb)", revision,
+ svn_inheritance_to_word(inherit),
+ include_descendants));
+
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &mergeinfo_tuple));
+
+ *catalog = NULL;
+ if (mergeinfo_tuple->nelts > 0)
+ {
+ *catalog = apr_hash_make(pool);
+ for (i = 0; i < mergeinfo_tuple->nelts; i++)
+ {
+ svn_mergeinfo_t for_path;
+ const char *to_parse;
+
+ elt = &((svn_ra_svn_item_t *) mergeinfo_tuple->elts)[i];
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Mergeinfo element is not a list"));
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cc",
+ &path, &to_parse));
+ SVN_ERR(svn_mergeinfo_parse(&for_path, to_parse, pool));
+ /* Correct for naughty servers that send "relative" paths
+ with leading slashes! */
+ svn_hash_sets(*catalog, path[0] == '/' ? path + 1 :path, for_path);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_update(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton, svn_revnum_t rev,
+ const char *target, svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
+
+ /* Tell the server we want to start an update. */
+ SVN_ERR(svn_ra_svn__write_cmd_update(conn, pool, rev, target, recurse,
+ depth, send_copyfrom_args,
+ ignore_ancestry));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+
+ /* Fetch a reporter for the caller to drive. The reporter will drive
+ * update_editor upon finish_report(). */
+ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
+ target, depth, reporter, report_baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ra_svn_switch(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton, svn_revnum_t rev,
+ const char *target, svn_depth_t depth,
+ const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *pool = result_pool;
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
+
+ /* Tell the server we want to start a switch. */
+ SVN_ERR(svn_ra_svn__write_cmd_switch(conn, pool, rev, target, recurse,
+ switch_url, depth,
+ send_copyfrom_args, ignore_ancestry));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+
+ /* Fetch a reporter for the caller to drive. The reporter will drive
+ * update_editor upon finish_report(). */
+ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, update_editor, update_baton,
+ target, depth, reporter, report_baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_status(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const char *target, svn_revnum_t rev,
+ svn_depth_t depth,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
+
+ /* Tell the server we want to start a status operation. */
+ SVN_ERR(svn_ra_svn__write_cmd_status(conn, pool, target, recurse, rev,
+ depth));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+
+ /* Fetch a reporter for the caller to drive. The reporter will drive
+ * status_editor upon finish_report(). */
+ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, status_editor, status_baton,
+ target, depth, reporter, report_baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_diff(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t rev, const char *target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
+
+ /* Tell the server we want to start a diff. */
+ SVN_ERR(svn_ra_svn__write_cmd_diff(conn, pool, rev, target, recurse,
+ ignore_ancestry, versus_url,
+ text_deltas, depth));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+
+ /* Fetch a reporter for the caller to drive. The reporter will drive
+ * diff_editor upon finish_report(). */
+ SVN_ERR(ra_svn_get_reporter(sess_baton, pool, diff_editor, diff_baton,
+ target, depth, reporter, report_baton));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *ra_svn_log(svn_ra_session_t *session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start, svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_pool_t *iterpool;
+ int i;
+ int nest_level = 0;
+ const char *path;
+ char *name;
+ svn_boolean_t want_custom_revprops;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "log"));
+ if (paths)
+ {
+ for (i = 0; i < paths->nelts; i++)
+ {
+ path = APR_ARRAY_IDX(paths, i, const char *);
+ SVN_ERR(svn_ra_svn__write_cstring(conn, pool, path));
+ }
+ }
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?r)(?r)bbnb!", start, end,
+ discover_changed_paths, strict_node_history,
+ (apr_uint64_t) limit,
+ include_merged_revisions));
+ if (revprops)
+ {
+ want_custom_revprops = FALSE;
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w(!", "revprops"));
+ for (i = 0; i < revprops->nelts; i++)
+ {
+ name = APR_ARRAY_IDX(revprops, i, char *);
+ SVN_ERR(svn_ra_svn__write_cstring(conn, pool, name));
+ if (!want_custom_revprops
+ && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
+ && strcmp(name, SVN_PROP_REVISION_DATE) != 0
+ && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
+ want_custom_revprops = TRUE;
+ }
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+ }
+ else
+ {
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!w())", "all-revprops"));
+ want_custom_revprops = TRUE;
+ }
+
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+
+ /* Read the log messages. */
+ iterpool = svn_pool_create(pool);
+ while (1)
+ {
+ apr_uint64_t has_children_param, invalid_revnum_param;
+ apr_uint64_t has_subtractive_merge_param;
+ svn_string_t *author, *date, *message;
+ apr_array_header_t *cplist, *rplist;
+ svn_log_entry_t *log_entry;
+ svn_boolean_t has_children;
+ svn_boolean_t subtractive_merge = FALSE;
+ apr_uint64_t revprop_count;
+ svn_ra_svn_item_t *item;
+ apr_hash_t *cphash;
+ svn_revnum_t rev;
+ int nreceived;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
+ if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
+ break;
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Log entry not a list"));
+ SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool,
+ "lr(?s)(?s)(?s)?BBnl?B",
+ &cplist, &rev, &author, &date,
+ &message, &has_children_param,
+ &invalid_revnum_param,
+ &revprop_count, &rplist,
+ &has_subtractive_merge_param));
+ if (want_custom_revprops && rplist == NULL)
+ {
+ /* Caller asked for custom revprops, but server is too old. */
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
+ _("Server does not support custom revprops"
+ " via log"));
+ }
+
+ if (has_children_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
+ has_children = FALSE;
+ else
+ has_children = (svn_boolean_t) has_children_param;
+
+ if (has_subtractive_merge_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
+ subtractive_merge = FALSE;
+ else
+ subtractive_merge = (svn_boolean_t) has_subtractive_merge_param;
+
+ /* Because the svn protocol won't let us send an invalid revnum, we have
+ to recover that fact using the extra parameter. */
+ if (invalid_revnum_param != SVN_RA_SVN_UNSPECIFIED_NUMBER
+ && invalid_revnum_param)
+ rev = SVN_INVALID_REVNUM;
+
+ if (cplist->nelts > 0)
+ {
+ /* Interpret the changed-paths list. */
+ cphash = apr_hash_make(iterpool);
+ for (i = 0; i < cplist->nelts; i++)
+ {
+ svn_log_changed_path2_t *change;
+ const char *copy_path, *action, *cpath, *kind_str;
+ apr_uint64_t text_mods, prop_mods;
+ svn_revnum_t copy_rev;
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(cplist, i,
+ svn_ra_svn_item_t);
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Changed-path entry not a list"));
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool,
+ "cw(?cr)?(?c?BB)",
+ &cpath, &action, &copy_path,
+ &copy_rev, &kind_str,
+ &text_mods, &prop_mods));
+ cpath = svn_fspath__canonicalize(cpath, iterpool);
+ if (copy_path)
+ copy_path = svn_fspath__canonicalize(copy_path, iterpool);
+ change = svn_log_changed_path2_create(iterpool);
+ change->action = *action;
+ change->copyfrom_path = copy_path;
+ change->copyfrom_rev = copy_rev;
+ change->node_kind = svn_node_kind_from_word(kind_str);
+ change->text_modified = optbool_to_tristate(text_mods);
+ change->props_modified = optbool_to_tristate(prop_mods);
+ svn_hash_sets(cphash, cpath, change);
+ }
+ }
+ else
+ cphash = NULL;
+
+ nreceived = 0;
+ if (! (limit && (nest_level == 0) && (++nreceived > limit)))
+ {
+ log_entry = svn_log_entry_create(iterpool);
+
+ log_entry->changed_paths = cphash;
+ log_entry->changed_paths2 = cphash;
+ log_entry->revision = rev;
+ log_entry->has_children = has_children;
+ log_entry->subtractive_merge = subtractive_merge;
+ if (rplist)
+ SVN_ERR(svn_ra_svn__parse_proplist(rplist, iterpool,
+ &log_entry->revprops));
+ if (log_entry->revprops == NULL)
+ log_entry->revprops = apr_hash_make(iterpool);
+ if (revprops == NULL)
+ {
+ /* Caller requested all revprops; set author/date/log. */
+ if (author)
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+ author);
+ if (date)
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
+ date);
+ if (message)
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG,
+ message);
+ }
+ else
+ {
+ /* Caller requested some; maybe set author/date/log. */
+ for (i = 0; i < revprops->nelts; i++)
+ {
+ name = APR_ARRAY_IDX(revprops, i, char *);
+ if (author && strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+ svn_hash_sets(log_entry->revprops,
+ SVN_PROP_REVISION_AUTHOR, author);
+ if (date && strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+ svn_hash_sets(log_entry->revprops,
+ SVN_PROP_REVISION_DATE, date);
+ if (message && strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+ svn_hash_sets(log_entry->revprops,
+ SVN_PROP_REVISION_LOG, message);
+ }
+ }
+ SVN_ERR(receiver(receiver_baton, log_entry, iterpool));
+ if (log_entry->has_children)
+ {
+ nest_level++;
+ }
+ if (! SVN_IS_VALID_REVNUM(log_entry->revision))
+ {
+ SVN_ERR_ASSERT(nest_level);
+ nest_level--;
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Read the response. */
+ return svn_ra_svn__read_cmd_response(conn, pool, "");
+}
+
+
+static svn_error_t *ra_svn_check_path(svn_ra_session_t *session,
+ const char *path, svn_revnum_t rev,
+ svn_node_kind_t *kind, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ const char *kind_word;
+
+ SVN_ERR(svn_ra_svn__write_cmd_check_path(conn, pool, path, rev));
+ SVN_ERR(handle_auth_request(sess_baton, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "w", &kind_word));
+ *kind = svn_node_kind_from_word(kind_word);
+ return SVN_NO_ERROR;
+}
+
+
+/* If ERR is a command not supported error, wrap it in a
+ SVN_ERR_RA_NOT_IMPLEMENTED with error message MSG. Else, return err. */
+static svn_error_t *handle_unsupported_cmd(svn_error_t *err,
+ const char *msg)
+{
+ if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
+ _(msg));
+ return err;
+}
+
+
+static svn_error_t *ra_svn_stat(svn_ra_session_t *session,
+ const char *path, svn_revnum_t rev,
+ svn_dirent_t **dirent, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_array_header_t *list = NULL;
+ svn_dirent_t *the_dirent;
+
+ SVN_ERR(svn_ra_svn__write_cmd_stat(conn, pool, path, rev));
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
+ N_("'stat' not implemented")));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
+
+ if (! list)
+ {
+ *dirent = NULL;
+ }
+ else
+ {
+ const char *kind, *cdate, *cauthor;
+ svn_boolean_t has_props;
+ svn_revnum_t crev;
+ apr_uint64_t size;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(list, pool, "wnbr(?c)(?c)",
+ &kind, &size, &has_props,
+ &crev, &cdate, &cauthor));
+
+ the_dirent = svn_dirent_create(pool);
+ the_dirent->kind = svn_node_kind_from_word(kind);
+ the_dirent->size = size;/* FIXME: svn_filesize_t */
+ the_dirent->has_props = has_props;
+ the_dirent->created_rev = crev;
+ SVN_ERR(svn_time_from_cstring(&the_dirent->time, cdate, pool));
+ the_dirent->last_author = cauthor;
+
+ *dirent = the_dirent;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *ra_svn_get_locations(svn_ra_session_t *session,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_revnum_t revision;
+ svn_boolean_t is_done;
+ int i;
+
+ /* Transmit the parameters. */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cr(!",
+ "get-locations", path, peg_revision));
+ for (i = 0; i < location_revisions->nelts; i++)
+ {
+ revision = APR_ARRAY_IDX(location_revisions, i, svn_revnum_t);
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!r!", revision));
+ }
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ /* Servers before 1.1 don't support this command. Check for this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
+ N_("'get-locations' not implemented")));
+
+ /* Read the hash items. */
+ is_done = FALSE;
+ *locations = apr_hash_make(pool);
+ while (!is_done)
+ {
+ svn_ra_svn_item_t *item;
+ const char *ret_path;
+
+ SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
+ if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
+ is_done = 1;
+ else if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Location entry not a list"));
+ else
+ {
+ SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "rc",
+ &revision, &ret_path));
+ ret_path = svn_fspath__canonicalize(ret_path, pool);
+ apr_hash_set(*locations, apr_pmemdup(pool, &revision,
+ sizeof(revision)),
+ sizeof(revision), ret_path);
+ }
+ }
+
+ /* Read the response. This is so the server would have a chance to
+ * report an error. */
+ return svn_ra_svn__read_cmd_response(conn, pool, "");
+}
+
+static svn_error_t *
+ra_svn_get_location_segments(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ svn_boolean_t is_done;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* Transmit the parameters. */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c(?r)(?r)(?r))",
+ "get-location-segments",
+ path, peg_revision, start_rev, end_rev));
+
+ /* Servers before 1.5 don't support this command. Check for this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
+ N_("'get-location-segments'"
+ " not implemented")));
+
+ /* Parse the response. */
+ is_done = FALSE;
+ while (!is_done)
+ {
+ svn_revnum_t range_start, range_end;
+ svn_ra_svn_item_t *item;
+ const char *ret_path;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &item));
+ if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
+ is_done = 1;
+ else if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Location segment entry not a list"));
+ else
+ {
+ svn_location_segment_t *segment = apr_pcalloc(iterpool,
+ sizeof(*segment));
+ SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, iterpool, "rr(?c)",
+ &range_start, &range_end, &ret_path));
+ if (! (SVN_IS_VALID_REVNUM(range_start)
+ && SVN_IS_VALID_REVNUM(range_end)))
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Expected valid revision range"));
+ if (ret_path)
+ ret_path = svn_relpath_canonicalize(ret_path, iterpool);
+ segment->path = ret_path;
+ segment->range_start = range_start;
+ segment->range_end = range_end;
+ SVN_ERR(receiver(segment, receiver_baton, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Read the response. This is so the server would have a chance to
+ * report an error. */
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_file_revs(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t start, svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_file_rev_handler_t handler,
+ void *handler_baton, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ apr_pool_t *rev_pool, *chunk_pool;
+ svn_boolean_t has_txdelta;
+ svn_boolean_t had_revision = FALSE;
+
+ /* One sub-pool for each revision and one for each txdelta chunk.
+ Note that the rev_pool must live during the following txdelta. */
+ rev_pool = svn_pool_create(pool);
+ chunk_pool = svn_pool_create(pool);
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_file_revs(sess_baton->conn, pool,
+ path, start, end,
+ include_merged_revisions));
+
+ /* Servers before 1.1 don't support this command. Check for this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
+ N_("'get-file-revs' not implemented")));
+
+ while (1)
+ {
+ apr_array_header_t *rev_proplist, *proplist;
+ apr_uint64_t merged_rev_param;
+ apr_array_header_t *props;
+ svn_ra_svn_item_t *item;
+ apr_hash_t *rev_props;
+ svn_revnum_t rev;
+ const char *p;
+ svn_boolean_t merged_rev;
+ svn_txdelta_window_handler_t d_handler;
+ void *d_baton;
+
+ svn_pool_clear(rev_pool);
+ svn_pool_clear(chunk_pool);
+ SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, rev_pool, &item));
+ if (item->kind == SVN_RA_SVN_WORD && strcmp(item->u.word, "done") == 0)
+ break;
+ /* Either we've got a correct revision or we will error out below. */
+ had_revision = TRUE;
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Revision entry not a list"));
+
+ SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, rev_pool,
+ "crll?B", &p, &rev, &rev_proplist,
+ &proplist, &merged_rev_param));
+ p = svn_fspath__canonicalize(p, rev_pool);
+ SVN_ERR(svn_ra_svn__parse_proplist(rev_proplist, rev_pool, &rev_props));
+ SVN_ERR(parse_prop_diffs(proplist, rev_pool, &props));
+ if (merged_rev_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
+ merged_rev = FALSE;
+ else
+ merged_rev = (svn_boolean_t) merged_rev_param;
+
+ /* Get the first delta chunk so we know if there is a delta. */
+ SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool, &item));
+ if (item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Text delta chunk not a string"));
+ has_txdelta = item->u.string->len > 0;
+
+ SVN_ERR(handler(handler_baton, p, rev, rev_props, merged_rev,
+ has_txdelta ? &d_handler : NULL, &d_baton,
+ props, rev_pool));
+
+ /* Process the text delta if any. */
+ if (has_txdelta)
+ {
+ svn_stream_t *stream;
+
+ if (d_handler)
+ stream = svn_txdelta_parse_svndiff(d_handler, d_baton, TRUE,
+ rev_pool);
+ else
+ stream = NULL;
+ while (item->u.string->len > 0)
+ {
+ apr_size_t size;
+
+ size = item->u.string->len;
+ if (stream)
+ SVN_ERR(svn_stream_write(stream, item->u.string->data, &size));
+ svn_pool_clear(chunk_pool);
+
+ SVN_ERR(svn_ra_svn__read_item(sess_baton->conn, chunk_pool,
+ &item));
+ if (item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Text delta chunk not a string"));
+ }
+ if (stream)
+ SVN_ERR(svn_stream_close(stream));
+ }
+ }
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(sess_baton->conn, pool, ""));
+
+ /* Return error if we didn't get any revisions. */
+ if (!had_revision)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("The get-file-revs command didn't return "
+ "any revisions"));
+
+ svn_pool_destroy(chunk_pool);
+ svn_pool_destroy(rev_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* For each path in PATH_REVS, send a 'lock' command to the server.
+ Used with 1.2.x series servers which support locking, but of only
+ one path at a time. ra_svn_lock(), which supports 'lock-many'
+ is now the default. See svn_ra_lock() docstring for interface details. */
+static svn_error_t *ra_svn_lock_compat(svn_ra_session_t *session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t* conn = sess->conn;
+ apr_array_header_t *list;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
+ {
+ svn_lock_t *lock;
+ const void *key;
+ const char *path;
+ void *val;
+ svn_revnum_t *revnum;
+ svn_error_t *err, *callback_err = NULL;
+
+ svn_pool_clear(iterpool);
+
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ revnum = val;
+
+ SVN_ERR(svn_ra_svn__write_cmd_lock(conn, iterpool, path, comment,
+ steal_lock, *revnum));
+
+ /* Servers before 1.2 doesn't support locking. Check this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
+ N_("Server doesn't support "
+ "the lock command")));
+
+ err = svn_ra_svn__read_cmd_response(conn, iterpool, "l", &list);
+
+ if (!err)
+ SVN_ERR(parse_lock(list, iterpool, &lock));
+
+ if (err && !SVN_ERR_IS_LOCK_ERROR(err))
+ return err;
+
+ if (lock_func)
+ callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
+ err, iterpool);
+
+ svn_error_clear(err);
+
+ if (callback_err)
+ return callback_err;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* For each path in PATH_TOKENS, send an 'unlock' command to the server.
+ Used with 1.2.x series servers which support unlocking, but of only
+ one path at a time. ra_svn_unlock(), which supports 'unlock-many' is
+ now the default. See svn_ra_unlock() docstring for interface details. */
+static svn_error_t *ra_svn_unlock_compat(svn_ra_session_t *session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t break_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t* conn = sess->conn;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ const char *path;
+ void *val;
+ const char *token;
+ svn_error_t *err, *callback_err = NULL;
+
+ svn_pool_clear(iterpool);
+
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ if (strcmp(val, "") != 0)
+ token = val;
+ else
+ token = NULL;
+
+ SVN_ERR(svn_ra_svn__write_cmd_unlock(conn, iterpool, path, token,
+ break_lock));
+
+ /* Servers before 1.2 don't support locking. Check this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
+ N_("Server doesn't support the unlock "
+ "command")));
+
+ err = svn_ra_svn__read_cmd_response(conn, iterpool, "");
+
+ if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
+ return err;
+
+ if (lock_func)
+ callback_err = lock_func(lock_baton, path, FALSE, NULL, err, pool);
+
+ svn_error_clear(err);
+
+ if (callback_err)
+ return callback_err;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Tell the server to lock all paths in PATH_REVS.
+ See svn_ra_lock() for interface details. */
+static svn_error_t *ra_svn_lock(svn_ra_session_t *session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t *conn = sess->conn;
+ apr_hash_index_t *hi;
+ svn_error_t *err;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)b(!", "lock-many",
+ comment, steal_lock));
+
+ for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ const char *path;
+ void *val;
+ svn_revnum_t *revnum;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ revnum = val;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?r)", path, *revnum));
+ }
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ err = handle_auth_request(sess, pool);
+
+ /* Pre-1.3 servers don't support 'lock-many'. If that fails, fall back
+ * to 'lock'. */
+ if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
+ {
+ svn_error_clear(err);
+ return ra_svn_lock_compat(session, path_revs, comment, steal_lock,
+ lock_func, lock_baton, pool);
+ }
+
+ if (err)
+ return err;
+
+ /* Loop over responses to get lock information. */
+ for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
+ {
+ svn_ra_svn_item_t *elt;
+ const void *key;
+ const char *path;
+ svn_error_t *callback_err;
+ const char *status;
+ svn_lock_t *lock;
+ apr_array_header_t *list;
+
+ apr_hash_this(hi, &key, NULL, NULL);
+ path = key;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
+
+ /* The server might have encountered some sort of fatal error in
+ the middle of the request list. If this happens, it will
+ transmit "done" to end the lock-info early, and then the
+ overall command response will talk about the fatal error. */
+ if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, "done") == 0)
+ break;
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Lock response not a list"));
+
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
+ &list));
+
+ if (strcmp(status, "failure") == 0)
+ err = svn_ra_svn__handle_failure_status(list, iterpool);
+ else if (strcmp(status, "success") == 0)
+ {
+ SVN_ERR(parse_lock(list, iterpool, &lock));
+ err = NULL;
+ }
+ else
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Unknown status for lock command"));
+
+ if (lock_func)
+ callback_err = lock_func(lock_baton, path, TRUE,
+ err ? NULL : lock,
+ err, iterpool);
+ else
+ callback_err = SVN_NO_ERROR;
+
+ svn_error_clear(err);
+
+ if (callback_err)
+ return callback_err;
+ }
+
+ /* If we didn't break early above, and the whole hash was traversed,
+ read the final "done" from the server. */
+ if (!hi)
+ {
+ svn_ra_svn_item_t *elt;
+
+ SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
+ if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Didn't receive end marker for lock "
+ "responses"));
+ }
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Tell the server to unlock all paths in PATH_TOKENS.
+ See svn_ra_unlock() for interface details. */
+static svn_error_t *ra_svn_unlock(svn_ra_session_t *session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t break_lock,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t *conn = sess->conn;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_error_t *err;
+ const char *path;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(b(!", "unlock-many",
+ break_lock));
+
+ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
+ {
+ void *val;
+ const void *key;
+ const char *token;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+
+ if (strcmp(val, "") != 0)
+ token = val;
+ else
+ token = NULL;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "c(?c)", path, token));
+ }
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ err = handle_auth_request(sess, pool);
+
+ /* Pre-1.3 servers don't support 'unlock-many'. If unknown, fall back
+ * to 'unlock'.
+ */
+ if (err && err->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD)
+ {
+ svn_error_clear(err);
+ return ra_svn_unlock_compat(session, path_tokens, break_lock, lock_func,
+ lock_baton, pool);
+ }
+
+ if (err)
+ return err;
+
+ /* Loop over responses to unlock files. */
+ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
+ {
+ svn_ra_svn_item_t *elt;
+ const void *key;
+ svn_error_t *callback_err;
+ const char *status;
+ apr_array_header_t *list;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_ra_svn__read_item(conn, iterpool, &elt));
+
+ /* The server might have encountered some sort of fatal error in
+ the middle of the request list. If this happens, it will
+ transmit "done" to end the lock-info early, and then the
+ overall command response will talk about the fatal error. */
+ if (elt->kind == SVN_RA_SVN_WORD && (strcmp(elt->u.word, "done") == 0))
+ break;
+
+ apr_hash_this(hi, &key, NULL, NULL);
+ path = key;
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Unlock response not a list"));
+
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, iterpool, "wl", &status,
+ &list));
+
+ if (strcmp(status, "failure") == 0)
+ err = svn_ra_svn__handle_failure_status(list, iterpool);
+ else if (strcmp(status, "success") == 0)
+ {
+ SVN_ERR(svn_ra_svn__parse_tuple(list, iterpool, "c", &path));
+ err = SVN_NO_ERROR;
+ }
+ else
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Unknown status for unlock command"));
+
+ if (lock_func)
+ callback_err = lock_func(lock_baton, path, FALSE, NULL, err,
+ iterpool);
+ else
+ callback_err = SVN_NO_ERROR;
+
+ svn_error_clear(err);
+
+ if (callback_err)
+ return callback_err;
+ }
+
+ /* If we didn't break early above, and the whole hash was traversed,
+ read the final "done" from the server. */
+ if (!hi)
+ {
+ svn_ra_svn_item_t *elt;
+
+ SVN_ERR(svn_ra_svn__read_item(conn, pool, &elt));
+ if (elt->kind != SVN_RA_SVN_WORD || strcmp(elt->u.word, "done") != 0)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Didn't receive end marker for unlock "
+ "responses"));
+ }
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, ""));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_lock(svn_ra_session_t *session,
+ svn_lock_t **lock,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t* conn = sess->conn;
+ apr_array_header_t *list;
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_lock(conn, pool, path));
+
+ /* Servers before 1.2 doesn't support locking. Check this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
+ N_("Server doesn't support the get-lock "
+ "command")));
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "(?l)", &list));
+ if (list)
+ SVN_ERR(parse_lock(list, pool, lock));
+ else
+ *lock = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* Copied from svn_ra_get_path_relative_to_root() and de-vtable-ized
+ to prevent a dependency cycle. */
+static svn_error_t *path_relative_to_root(svn_ra_session_t *session,
+ const char **rel_path,
+ const char *url,
+ apr_pool_t *pool)
+{
+ const char *root_url;
+
+ SVN_ERR(ra_svn_get_repos_root(session, &root_url, pool));
+ *rel_path = svn_uri_skip_ancestor(root_url, url, pool);
+ if (! *rel_path)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("'%s' isn't a child of repository root "
+ "URL '%s'"),
+ url, root_url);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_get_locks(svn_ra_session_t *session,
+ apr_hash_t **locks,
+ const char *path,
+ svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ svn_ra_svn_conn_t* conn = sess->conn;
+ apr_array_header_t *list;
+ const char *full_url, *abs_path;
+ int i;
+
+ /* Figure out the repository abspath from PATH. */
+ full_url = svn_path_url_add_component2(sess->url, path, pool);
+ SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
+ abs_path = svn_fspath__canonicalize(abs_path, pool);
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_locks(conn, pool, path, depth));
+
+ /* Servers before 1.2 doesn't support locking. Check this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
+ N_("Server doesn't support the get-lock "
+ "command")));
+
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, pool, "l", &list));
+
+ *locks = apr_hash_make(pool);
+
+ for (i = 0; i < list->nelts; ++i)
+ {
+ svn_lock_t *lock;
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
+
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Lock element not a list"));
+ SVN_ERR(parse_lock(elt->u.list, pool, &lock));
+
+ /* Filter out unwanted paths. Since Subversion only allows
+ locks on files, we can treat depth=immediates the same as
+ depth=files for filtering purposes. Meaning, we'll keep
+ this lock if:
+
+ a) its path is the very path we queried, or
+ b) we've asked for a fully recursive answer, or
+ c) we've asked for depth=files or depth=immediates, and this
+ lock is on an immediate child of our query path.
+ */
+ if ((strcmp(abs_path, lock->path) == 0) || (depth == svn_depth_infinity))
+ {
+ svn_hash_sets(*locks, lock->path, lock);
+ }
+ else if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
+ {
+ const char *relpath = svn_fspath__skip_ancestor(abs_path, lock->path);
+ if (relpath && (svn_path_component_count(relpath) == 1))
+ svn_hash_sets(*locks, lock->path, lock);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *ra_svn_replay(svn_ra_session_t *session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+
+ SVN_ERR(svn_ra_svn__write_cmd_replay(sess->conn, pool, revision,
+ low_water_mark, send_deltas));
+
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
+ N_("Server doesn't support the replay "
+ "command")));
+
+ SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
+ NULL, TRUE));
+
+ return svn_ra_svn__read_cmd_response(sess->conn, pool, "");
+}
+
+
+static svn_error_t *
+ra_svn_replay_range(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ apr_pool_t *iterpool;
+ svn_revnum_t rev;
+ svn_boolean_t drive_aborted = FALSE;
+
+ SVN_ERR(svn_ra_svn__write_cmd_replay_range(sess->conn, pool,
+ start_revision, end_revision,
+ low_water_mark, send_deltas));
+
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
+ N_("Server doesn't support the "
+ "replay-range command")));
+
+ iterpool = svn_pool_create(pool);
+ for (rev = start_revision; rev <= end_revision; rev++)
+ {
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ apr_hash_t *rev_props;
+ const char *word;
+ apr_array_header_t *list;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_ra_svn__read_tuple(sess->conn, iterpool,
+ "wl", &word, &list));
+ if (strcmp(word, "revprops") != 0)
+ return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Expected 'revprops', found '%s'"),
+ word);
+
+ SVN_ERR(svn_ra_svn__parse_proplist(list, iterpool, &rev_props));
+
+ SVN_ERR(revstart_func(rev, replay_baton,
+ &editor, &edit_baton,
+ rev_props,
+ iterpool));
+ SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, iterpool,
+ editor, edit_baton,
+ &drive_aborted, TRUE));
+ /* If drive_editor2() aborted the commit, do NOT try to call
+ revfinish_func and commit the transaction! */
+ if (drive_aborted) {
+ svn_pool_destroy(iterpool);
+ return svn_error_create(SVN_ERR_RA_SVN_EDIT_ABORTED, NULL,
+ _("Error while replaying commit"));
+ }
+ SVN_ERR(revfinish_func(rev, replay_baton,
+ editor, edit_baton,
+ rev_props,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return svn_ra_svn__read_cmd_response(sess->conn, pool, "");
+}
+
+
+static svn_error_t *
+ra_svn_has_capability(svn_ra_session_t *session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *sess = session->priv;
+ static const char* capabilities[][2] =
+ {
+ /* { ra capability string, svn:// wire capability string} */
+ {SVN_RA_CAPABILITY_DEPTH, SVN_RA_SVN_CAP_DEPTH},
+ {SVN_RA_CAPABILITY_MERGEINFO, SVN_RA_SVN_CAP_MERGEINFO},
+ {SVN_RA_CAPABILITY_LOG_REVPROPS, SVN_RA_SVN_CAP_LOG_REVPROPS},
+ {SVN_RA_CAPABILITY_PARTIAL_REPLAY, SVN_RA_SVN_CAP_PARTIAL_REPLAY},
+ {SVN_RA_CAPABILITY_COMMIT_REVPROPS, SVN_RA_SVN_CAP_COMMIT_REVPROPS},
+ {SVN_RA_CAPABILITY_ATOMIC_REVPROPS, SVN_RA_SVN_CAP_ATOMIC_REVPROPS},
+ {SVN_RA_CAPABILITY_INHERITED_PROPS, SVN_RA_SVN_CAP_INHERITED_PROPS},
+ {SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
+ SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS},
+ {SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
+ SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
+
+ {NULL, NULL} /* End of list marker */
+ };
+ int i;
+
+ *has = FALSE;
+
+ for (i = 0; capabilities[i][0]; i++)
+ {
+ if (strcmp(capability, capabilities[i][0]) == 0)
+ {
+ *has = svn_ra_svn_has_capability(sess->conn, capabilities[i][1]);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
+ _("Don't know anything about capability '%s'"),
+ capability);
+}
+
+static svn_error_t *
+ra_svn_get_deleted_rev(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool)
+
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ /* Transmit the parameters. */
+ SVN_ERR(svn_ra_svn__write_cmd_get_deleted_rev(conn, pool, path,
+ peg_revision, end_revision));
+
+ /* Servers before 1.6 don't support this command. Check for this here. */
+ SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
+ N_("'get-deleted-rev' not implemented")));
+
+ return svn_ra_svn__read_cmd_response(conn, pool, "r", revision_deleted);
+}
+
+static svn_error_t *
+ra_svn_register_editor_shim_callbacks(svn_ra_session_t *session,
+ svn_delta_shim_callbacks_t *callbacks)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+
+ conn->shim_callbacks = callbacks;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ra_svn_get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_array_header_t *iproplist;
+
+ SVN_ERR(svn_ra_svn__write_cmd_get_iprops(conn, scratch_pool,
+ path, revision));
+ SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(conn, scratch_pool, "l", &iproplist));
+ SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static const svn_ra__vtable_t ra_svn_vtable = {
+ svn_ra_svn_version,
+ ra_svn_get_description,
+ ra_svn_get_schemes,
+ ra_svn_open,
+ ra_svn_reparent,
+ ra_svn_get_session_url,
+ ra_svn_get_latest_rev,
+ ra_svn_get_dated_rev,
+ ra_svn_change_rev_prop,
+ ra_svn_rev_proplist,
+ ra_svn_rev_prop,
+ ra_svn_commit,
+ ra_svn_get_file,
+ ra_svn_get_dir,
+ ra_svn_get_mergeinfo,
+ ra_svn_update,
+ ra_svn_switch,
+ ra_svn_status,
+ ra_svn_diff,
+ ra_svn_log,
+ ra_svn_check_path,
+ ra_svn_stat,
+ ra_svn_get_uuid,
+ ra_svn_get_repos_root,
+ ra_svn_get_locations,
+ ra_svn_get_location_segments,
+ ra_svn_get_file_revs,
+ ra_svn_lock,
+ ra_svn_unlock,
+ ra_svn_get_lock,
+ ra_svn_get_locks,
+ ra_svn_replay,
+ ra_svn_has_capability,
+ ra_svn_replay_range,
+ ra_svn_get_deleted_rev,
+ ra_svn_register_editor_shim_callbacks,
+ ra_svn_get_inherited_props
+};
+
+svn_error_t *
+svn_ra_svn__init(const svn_version_t *loader_version,
+ const svn_ra__vtable_t **vtable,
+ apr_pool_t *pool)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+
+ SVN_ERR(svn_ver_check_list(svn_ra_svn_version(), checklist));
+
+ /* Simplified version check to make sure we can safely use the
+ VTABLE parameter. The RA loader does a more exhaustive check. */
+ if (loader_version->major != SVN_VER_MAJOR)
+ {
+ return svn_error_createf
+ (SVN_ERR_VERSION_MISMATCH, NULL,
+ _("Unsupported RA loader version (%d) for ra_svn"),
+ loader_version->major);
+ }
+
+ *vtable = &ra_svn_vtable;
+
+#ifdef SVN_HAVE_SASL
+ SVN_ERR(svn_ra_svn__sasl_init());
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+/* Compatibility wrapper for the 1.1 and before API. */
+#define NAME "ra_svn"
+#define DESCRIPTION RA_SVN_DESCRIPTION
+#define VTBL ra_svn_vtable
+#define INITFUNC svn_ra_svn__init
+#define COMPAT_INITFUNC svn_ra_svn_init
+#include "../libsvn_ra/wrapper_template.h"
diff --git a/contrib/subversion/subversion/libsvn_ra_svn/cram.c b/contrib/subversion/subversion/libsvn_ra_svn/cram.c
new file mode 100644
index 0000000..1e54ac8
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_svn/cram.c
@@ -0,0 +1,221 @@
+/*
+ * cram.c : Minimal standalone CRAM-MD5 implementation
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#define APR_WANT_STDIO
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+#include <apr_network_io.h>
+#include <apr_time.h>
+#include <apr_md5.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_ra_svn.h"
+#include "svn_config.h"
+#include "svn_private_config.h"
+
+#include "ra_svn.h"
+
+static int hex_to_int(char c)
+{
+ return (c >= '0' && c <= '9') ? c - '0'
+ : (c >= 'a' && c <= 'f') ? c - 'a' + 10
+ : -1;
+}
+
+static char int_to_hex(int v)
+{
+ return (char)((v < 10) ? '0' + v : 'a' + (v - 10));
+}
+
+static svn_boolean_t hex_decode(unsigned char *hashval, const char *hexval)
+{
+ int i, h1, h2;
+
+ for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
+ {
+ h1 = hex_to_int(hexval[2 * i]);
+ h2 = hex_to_int(hexval[2 * i + 1]);
+ if (h1 == -1 || h2 == -1)
+ return FALSE;
+ hashval[i] = (unsigned char)((h1 << 4) | h2);
+ }
+ return TRUE;
+}
+
+static void hex_encode(char *hexval, const unsigned char *hashval)
+{
+ int i;
+
+ for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
+ {
+ hexval[2 * i] = int_to_hex((hashval[i] >> 4) & 0xf);
+ hexval[2 * i + 1] = int_to_hex(hashval[i] & 0xf);
+ }
+}
+
+static void compute_digest(unsigned char *digest, const char *challenge,
+ const char *password)
+{
+ unsigned char secret[64];
+ apr_size_t len = strlen(password), i;
+ apr_md5_ctx_t ctx;
+
+ /* Munge the password into a 64-byte secret. */
+ memset(secret, 0, sizeof(secret));
+ if (len <= sizeof(secret))
+ memcpy(secret, password, len);
+ else
+ apr_md5(secret, password, len);
+
+ /* Compute MD5(secret XOR opad, MD5(secret XOR ipad, challenge)),
+ * where ipad is a string of 0x36 and opad is a string of 0x5c. */
+ for (i = 0; i < sizeof(secret); i++)
+ secret[i] ^= 0x36;
+ apr_md5_init(&ctx);
+ apr_md5_update(&ctx, secret, sizeof(secret));
+ apr_md5_update(&ctx, challenge, strlen(challenge));
+ apr_md5_final(digest, &ctx);
+ for (i = 0; i < sizeof(secret); i++)
+ secret[i] ^= (0x36 ^ 0x5c);
+ apr_md5_init(&ctx);
+ apr_md5_update(&ctx, secret, sizeof(secret));
+ apr_md5_update(&ctx, digest, APR_MD5_DIGESTSIZE);
+ apr_md5_final(digest, &ctx);
+}
+
+/* Fail the authentication, from the server's perspective. */
+static svn_error_t *fail(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char *msg)
+{
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", msg));
+ return svn_ra_svn__flush(conn, pool);
+}
+
+/* If we can, make the nonce with random bytes. If we can't... well,
+ * it just has to be different each time. The current time isn't
+ * absolutely guaranteed to be different for each connection, but it
+ * should prevent replay attacks in practice. */
+static apr_status_t make_nonce(apr_uint64_t *nonce)
+{
+#if APR_HAS_RANDOM
+ return apr_generate_random_bytes((unsigned char *) nonce, sizeof(*nonce));
+#else
+ *nonce = apr_time_now();
+ return APR_SUCCESS;
+#endif
+}
+
+svn_error_t *svn_ra_svn_cram_server(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ svn_config_t *pwdb, const char **user,
+ svn_boolean_t *success)
+{
+ apr_status_t status;
+ apr_uint64_t nonce;
+ char hostbuf[APRMAXHOSTLEN + 1];
+ unsigned char cdigest[APR_MD5_DIGESTSIZE], sdigest[APR_MD5_DIGESTSIZE];
+ const char *challenge, *sep, *password;
+ svn_ra_svn_item_t *item;
+ svn_string_t *resp;
+
+ *success = FALSE;
+
+ /* Send a challenge. */
+ status = make_nonce(&nonce);
+ if (!status)
+ status = apr_gethostname(hostbuf, sizeof(hostbuf), pool);
+ if (status)
+ return fail(conn, pool, "Internal server error in authentication");
+ challenge = apr_psprintf(pool,
+ "<%" APR_UINT64_T_FMT ".%" APR_TIME_T_FMT "@%s>",
+ nonce, apr_time_now(), hostbuf);
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "step", challenge));
+
+ /* Read the client's response and decode it into *user and cdigest. */
+ SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
+ if (item->kind != SVN_RA_SVN_STRING) /* Very wrong; don't report failure */
+ return SVN_NO_ERROR;
+ resp = item->u.string;
+ sep = strrchr(resp->data, ' ');
+ if (!sep || resp->len - (sep + 1 - resp->data) != APR_MD5_DIGESTSIZE * 2
+ || !hex_decode(cdigest, sep + 1))
+ return fail(conn, pool, "Malformed client response in authentication");
+ *user = apr_pstrmemdup(pool, resp->data, sep - resp->data);
+
+ /* Verify the digest against the password in pwfile. */
+ svn_config_get(pwdb, &password, SVN_CONFIG_SECTION_USERS, *user, NULL);
+ if (!password)
+ return fail(conn, pool, "Username not found");
+ compute_digest(sdigest, challenge, password);
+ if (memcmp(cdigest, sdigest, sizeof(sdigest)) != 0)
+ return fail(conn, pool, "Password incorrect");
+
+ *success = TRUE;
+ return svn_ra_svn__write_tuple(conn, pool, "w()", "success");
+}
+
+svn_error_t *svn_ra_svn__cram_client(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char *user, const char *password,
+ const char **message)
+{
+ const char *status, *str, *reply;
+ unsigned char digest[APR_MD5_DIGESTSIZE];
+ char hex[2 * APR_MD5_DIGESTSIZE + 1];
+
+ /* Read the server challenge. */
+ SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &status, &str));
+ if (strcmp(status, "failure") == 0 && str)
+ {
+ *message = str;
+ return SVN_NO_ERROR;
+ }
+ else if (strcmp(status, "step") != 0 || !str)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Unexpected server response to authentication"));
+
+ /* Write our response. */
+ compute_digest(digest, str, password);
+ hex_encode(hex, digest);
+ hex[sizeof(hex) - 1] = '\0';
+ reply = apr_psprintf(pool, "%s %s", user, hex);
+ SVN_ERR(svn_ra_svn__write_cstring(conn, pool, reply));
+
+ /* Read the success or failure response from the server. */
+ SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &status, &str));
+ if (strcmp(status, "failure") == 0 && str)
+ {
+ *message = str;
+ return SVN_NO_ERROR;
+ }
+ else if (strcmp(status, "success") != 0 || str)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Unexpected server response to authentication"));
+
+ *message = NULL;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_svn/cyrus_auth.c b/contrib/subversion/subversion/libsvn_ra_svn/cyrus_auth.c
new file mode 100644
index 0000000..82e33d3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_svn/cyrus_auth.c
@@ -0,0 +1,954 @@
+/*
+ * cyrus_auth.c : functions for Cyrus SASL-based authentication
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_private_config.h"
+#ifdef SVN_HAVE_SASL
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+#include <apr_version.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_ra_svn.h"
+#include "svn_base64.h"
+
+#include "private/svn_atomic.h"
+#include "private/ra_svn_sasl.h"
+#include "private/svn_mutex.h"
+
+#include "ra_svn.h"
+
+/* Note: In addition to being used via svn_atomic__init_once to control
+ * initialization of the SASL code this will also be referenced in
+ * the various functions that work with sasl mutexes to determine
+ * if the sasl pool has been destroyed. This should be safe, since
+ * it is only set back to zero in the sasl pool's cleanups, which
+ * only happens during apr_terminate, which we assume is occurring
+ * in atexit processing, at which point we are already running in
+ * single threaded mode.
+ */
+volatile svn_atomic_t svn_ra_svn__sasl_status = 0;
+
+/* Initialized by svn_ra_svn__sasl_common_init(). */
+static volatile svn_atomic_t sasl_ctx_count;
+
+static apr_pool_t *sasl_pool = NULL;
+
+
+/* Pool cleanup called when sasl_pool is destroyed. */
+static apr_status_t sasl_done_cb(void *data)
+{
+ /* Reset svn_ra_svn__sasl_status, in case the client calls
+ apr_initialize()/apr_terminate() more than once. */
+ svn_ra_svn__sasl_status = 0;
+ if (svn_atomic_dec(&sasl_ctx_count) == 0)
+ sasl_done();
+ return APR_SUCCESS;
+}
+
+#if APR_HAS_THREADS
+/* Cyrus SASL is thread-safe only if we supply it with mutex functions
+ * (with sasl_set_mutex()). To make this work with APR, we need to use the
+ * global sasl_pool for the mutex allocations. Freeing a mutex actually
+ * returns it to a global array. We allocate mutexes from this
+ * array if it is non-empty, or directly from the pool otherwise.
+ * We also need a mutex to serialize accesses to the array itself.
+ */
+
+/* An array of allocated, but unused, apr_thread_mutex_t's. */
+static apr_array_header_t *free_mutexes = NULL;
+
+/* A mutex to serialize access to the array. */
+static svn_mutex__t *array_mutex = NULL;
+
+/* Callbacks we pass to sasl_set_mutex(). */
+
+static svn_error_t *
+sasl_mutex_alloc_cb_internal(svn_mutex__t **mutex)
+{
+ if (apr_is_empty_array(free_mutexes))
+ return svn_mutex__init(mutex, TRUE, sasl_pool);
+ else
+ *mutex = *((svn_mutex__t**)apr_array_pop(free_mutexes));
+
+ return SVN_NO_ERROR;
+}
+
+static void *sasl_mutex_alloc_cb(void)
+{
+ svn_mutex__t *mutex = NULL;
+ svn_error_t *err;
+
+ if (!svn_ra_svn__sasl_status)
+ return NULL;
+
+ err = svn_mutex__lock(array_mutex);
+ if (err)
+ svn_error_clear(err);
+ else
+ svn_error_clear(svn_mutex__unlock(array_mutex,
+ sasl_mutex_alloc_cb_internal(&mutex)));
+
+ return mutex;
+}
+
+static int check_result(svn_error_t *err)
+{
+ if (err)
+ {
+ svn_error_clear(err);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int sasl_mutex_lock_cb(void *mutex)
+{
+ if (!svn_ra_svn__sasl_status)
+ return 0;
+ return check_result(svn_mutex__lock(mutex));
+}
+
+static int sasl_mutex_unlock_cb(void *mutex)
+{
+ if (!svn_ra_svn__sasl_status)
+ return 0;
+ return check_result(svn_mutex__unlock(mutex, SVN_NO_ERROR));
+}
+
+static svn_error_t *
+sasl_mutex_free_cb_internal(void *mutex)
+{
+ APR_ARRAY_PUSH(free_mutexes, svn_mutex__t*) = mutex;
+ return SVN_NO_ERROR;
+}
+
+static void sasl_mutex_free_cb(void *mutex)
+{
+ svn_error_t *err;
+
+ if (!svn_ra_svn__sasl_status)
+ return;
+
+ err = svn_mutex__lock(array_mutex);
+ if (err)
+ svn_error_clear(err);
+ else
+ svn_error_clear(svn_mutex__unlock(array_mutex,
+ sasl_mutex_free_cb_internal(mutex)));
+}
+#endif /* APR_HAS_THREADS */
+
+svn_error_t *
+svn_ra_svn__sasl_common_init(apr_pool_t *pool)
+{
+ sasl_pool = svn_pool_create(pool);
+ sasl_ctx_count = 1;
+ apr_pool_cleanup_register(sasl_pool, NULL, sasl_done_cb,
+ apr_pool_cleanup_null);
+#if APR_HAS_THREADS
+ sasl_set_mutex(sasl_mutex_alloc_cb,
+ sasl_mutex_lock_cb,
+ sasl_mutex_unlock_cb,
+ sasl_mutex_free_cb);
+ free_mutexes = apr_array_make(sasl_pool, 0, sizeof(svn_mutex__t *));
+ SVN_ERR(svn_mutex__init(&array_mutex, TRUE, sasl_pool));
+
+#endif /* APR_HAS_THREADS */
+
+ return SVN_NO_ERROR;
+}
+
+/* We are going to look at errno when we get SASL_FAIL but we don't
+ know for sure whether SASL always sets errno. Clearing errno
+ before calling SASL functions helps in cases where SASL does
+ nothing to set errno. */
+#ifdef apr_set_os_error
+#define clear_sasl_errno() apr_set_os_error(APR_SUCCESS)
+#else
+#define clear_sasl_errno() (void)0
+#endif
+
+/* Sometimes SASL returns SASL_FAIL as RESULT and sets errno.
+ * SASL_FAIL translates to "generic error" which is quite unhelpful.
+ * Try to append a more informative error message based on errno so
+ * should be called before doing anything that may change errno. */
+static const char *
+get_sasl_errno_msg(int result, apr_pool_t *result_pool)
+{
+#ifdef apr_get_os_error
+ char buf[1024];
+
+ if (result == SASL_FAIL && apr_get_os_error() != 0)
+ return apr_psprintf(result_pool, ": %s",
+ svn_strerror(apr_get_os_error(), buf, sizeof(buf)));
+#endif
+ return "";
+}
+
+/* Wrap an error message from SASL with a prefix that allows users
+ * to tell that the error message came from SASL. Queries errno and
+ * so should be called before doing anything that may change errno. */
+static const char *
+get_sasl_error(sasl_conn_t *sasl_ctx, int result, apr_pool_t *result_pool)
+{
+ const char *sasl_errno_msg = get_sasl_errno_msg(result, result_pool);
+
+ return apr_psprintf(result_pool,
+ _("SASL authentication error: %s%s"),
+ sasl_errdetail(sasl_ctx), sasl_errno_msg);
+}
+
+static svn_error_t *sasl_init_cb(void *baton, apr_pool_t *pool)
+{
+ int result;
+
+ SVN_ERR(svn_ra_svn__sasl_common_init(pool));
+ clear_sasl_errno();
+ result = sasl_client_init(NULL);
+ if (result != SASL_OK)
+ {
+ const char *sasl_errno_msg = get_sasl_errno_msg(result, pool);
+
+ return svn_error_createf
+ (SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Could not initialized the SASL library: %s%s"),
+ sasl_errstring(result, NULL, NULL),
+ sasl_errno_msg);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_svn__sasl_init(void)
+{
+ SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status,
+ sasl_init_cb, NULL, NULL));
+ return SVN_NO_ERROR;
+}
+
+static apr_status_t sasl_dispose_cb(void *data)
+{
+ sasl_conn_t *sasl_ctx = data;
+ sasl_dispose(&sasl_ctx);
+ if (svn_atomic_dec(&sasl_ctx_count) == 0)
+ sasl_done();
+ return APR_SUCCESS;
+}
+
+void svn_ra_svn__default_secprops(sasl_security_properties_t *secprops)
+{
+ /* The minimum and maximum security strength factors that the chosen
+ SASL mechanism should provide. 0 means 'no encryption', 256 means
+ '256-bit encryption', which is about the best that any SASL
+ mechanism can provide. Using these values effectively means 'use
+ whatever encryption the other side wants'. Note that SASL will try
+ to use better encryption whenever possible, so if both the server and
+ the client use these values the highest possible encryption strength
+ will be used. */
+ secprops->min_ssf = 0;
+ secprops->max_ssf = 256;
+
+ /* Set maxbufsize to the maximum amount of data we can read at any one time.
+ This value needs to be commmunicated to the peer if a security layer
+ is negotiated. */
+ secprops->maxbufsize = SVN_RA_SVN__READBUF_SIZE;
+
+ secprops->security_flags = 0;
+ secprops->property_names = secprops->property_values = NULL;
+}
+
+/* A baton type used by the SASL username and password callbacks. */
+typedef struct cred_baton {
+ svn_auth_baton_t *auth_baton;
+ svn_auth_iterstate_t *iterstate;
+ const char *realmstring;
+
+ /* Unfortunately SASL uses two separate callbacks for the username and
+ password, but we must fetch both of them at the same time. So we cache
+ their values in the baton, set them to NULL individually when SASL
+ demands them, and fetch the next pair when both are NULL. */
+ const char *username;
+ const char *password;
+
+ /* Any errors we receive from svn_auth_{first,next}_credentials
+ are saved here. */
+ svn_error_t *err;
+
+ /* This flag is set when we run out of credential providers. */
+ svn_boolean_t no_more_creds;
+
+ /* Were the auth callbacks ever called? */
+ svn_boolean_t was_used;
+
+ apr_pool_t *pool;
+} cred_baton_t;
+
+/* Call svn_auth_{first,next}_credentials. If successful, set BATON->username
+ and BATON->password to the new username and password and return TRUE,
+ otherwise return FALSE. If there are no more credentials, set
+ BATON->no_more_creds to TRUE. Any errors are saved in BATON->err. */
+static svn_boolean_t
+get_credentials(cred_baton_t *baton)
+{
+ void *creds;
+
+ if (baton->iterstate)
+ baton->err = svn_auth_next_credentials(&creds, baton->iterstate,
+ baton->pool);
+ else
+ baton->err = svn_auth_first_credentials(&creds, &baton->iterstate,
+ SVN_AUTH_CRED_SIMPLE,
+ baton->realmstring,
+ baton->auth_baton, baton->pool);
+ if (baton->err)
+ return FALSE;
+
+ if (! creds)
+ {
+ baton->no_more_creds = TRUE;
+ return FALSE;
+ }
+
+ baton->username = ((svn_auth_cred_simple_t *)creds)->username;
+ baton->password = ((svn_auth_cred_simple_t *)creds)->password;
+ baton->was_used = TRUE;
+
+ return TRUE;
+}
+
+/* The username callback. Implements the sasl_getsimple_t interface. */
+static int
+get_username_cb(void *b, int id, const char **username, size_t *len)
+{
+ cred_baton_t *baton = b;
+
+ if (baton->username || get_credentials(baton))
+ {
+ *username = baton->username;
+ if (len)
+ *len = strlen(baton->username);
+ baton->username = NULL;
+
+ return SASL_OK;
+ }
+
+ return SASL_FAIL;
+}
+
+/* The password callback. Implements the sasl_getsecret_t interface. */
+static int
+get_password_cb(sasl_conn_t *conn, void *b, int id, sasl_secret_t **psecret)
+{
+ cred_baton_t *baton = b;
+
+ if (baton->password || get_credentials(baton))
+ {
+ sasl_secret_t *secret;
+ size_t len = strlen(baton->password);
+
+ /* sasl_secret_t is a struct with a variable-sized array as a final
+ member, which means we need to allocate len-1 supplementary bytes
+ (one byte is part of sasl_secret_t, and we don't need a NULL
+ terminator). */
+ secret = apr_palloc(baton->pool, sizeof(*secret) + len - 1);
+ secret->len = len;
+ memcpy(secret->data, baton->password, len);
+ baton->password = NULL;
+ *psecret = secret;
+
+ return SASL_OK;
+ }
+
+ return SASL_FAIL;
+}
+
+/* Create a new SASL context. */
+static svn_error_t *new_sasl_ctx(sasl_conn_t **sasl_ctx,
+ svn_boolean_t is_tunneled,
+ const char *hostname,
+ const char *local_addrport,
+ const char *remote_addrport,
+ sasl_callback_t *callbacks,
+ apr_pool_t *pool)
+{
+ sasl_security_properties_t secprops;
+ int result;
+
+ clear_sasl_errno();
+ result = sasl_client_new(SVN_RA_SVN_SASL_NAME,
+ hostname, local_addrport, remote_addrport,
+ callbacks, SASL_SUCCESS_DATA,
+ sasl_ctx);
+ if (result != SASL_OK)
+ {
+ const char *sasl_errno_msg = get_sasl_errno_msg(result, pool);
+
+ return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Could not create SASL context: %s%s"),
+ sasl_errstring(result, NULL, NULL),
+ sasl_errno_msg);
+ }
+ svn_atomic_inc(&sasl_ctx_count);
+ apr_pool_cleanup_register(pool, *sasl_ctx, sasl_dispose_cb,
+ apr_pool_cleanup_null);
+
+ if (is_tunneled)
+ {
+ /* We need to tell SASL that this connection is tunneled,
+ otherwise it will ignore EXTERNAL. The third parameter
+ should be the username, but since SASL doesn't seem
+ to use it on the client side, any non-empty string will do. */
+ clear_sasl_errno();
+ result = sasl_setprop(*sasl_ctx,
+ SASL_AUTH_EXTERNAL, " ");
+ if (result != SASL_OK)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ get_sasl_error(*sasl_ctx, result, pool));
+ }
+
+ /* Set security properties. */
+ svn_ra_svn__default_secprops(&secprops);
+ sasl_setprop(*sasl_ctx, SASL_SEC_PROPS, &secprops);
+
+ return SVN_NO_ERROR;
+}
+
+/* Perform an authentication exchange */
+static svn_error_t *try_auth(svn_ra_svn__session_baton_t *sess,
+ sasl_conn_t *sasl_ctx,
+ svn_boolean_t *success,
+ const char **last_err,
+ const char *mechstring,
+ apr_pool_t *pool)
+{
+ sasl_interact_t *client_interact = NULL;
+ const char *out, *mech, *status = NULL;
+ const svn_string_t *arg = NULL, *in;
+ int result;
+ unsigned int outlen;
+ svn_boolean_t again;
+
+ do
+ {
+ again = FALSE;
+ clear_sasl_errno();
+ result = sasl_client_start(sasl_ctx,
+ mechstring,
+ &client_interact,
+ &out,
+ &outlen,
+ &mech);
+ switch (result)
+ {
+ case SASL_OK:
+ case SASL_CONTINUE:
+ /* Success. */
+ break;
+ case SASL_NOMECH:
+ return svn_error_create(SVN_ERR_RA_SVN_NO_MECHANISMS, NULL, NULL);
+ case SASL_BADPARAM:
+ case SASL_NOMEM:
+ /* Fatal error. Fail the authentication. */
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ get_sasl_error(sasl_ctx, result, pool));
+ default:
+ /* For anything else, delete the mech from the list
+ and try again. */
+ {
+ const char *pmech = strstr(mechstring, mech);
+ const char *head = apr_pstrndup(pool, mechstring,
+ pmech - mechstring);
+ const char *tail = pmech + strlen(mech);
+
+ mechstring = apr_pstrcat(pool, head, tail, (char *)NULL);
+ again = TRUE;
+ }
+ }
+ }
+ while (again);
+
+ /* Prepare the initial authentication token. */
+ if (outlen > 0 || strcmp(mech, "EXTERNAL") == 0)
+ arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool),
+ TRUE, pool);
+
+ /* Send the initial client response */
+ SVN_ERR(svn_ra_svn__auth_response(sess->conn, pool, mech,
+ arg ? arg->data : NULL));
+
+ while (result == SASL_CONTINUE)
+ {
+ /* Read the server response */
+ SVN_ERR(svn_ra_svn__read_tuple(sess->conn, pool, "w(?s)",
+ &status, &in));
+
+ if (strcmp(status, "failure") == 0)
+ {
+ /* Authentication failed. Use the next set of credentials */
+ *success = FALSE;
+ /* Remember the message sent by the server because we'll want to
+ return a meaningful error if we run out of auth providers. */
+ *last_err = in ? in->data : "";
+ return SVN_NO_ERROR;
+ }
+
+ if ((strcmp(status, "success") != 0 && strcmp(status, "step") != 0)
+ || in == NULL)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Unexpected server response"
+ " to authentication"));
+
+ /* If the mech is CRAM-MD5 we don't base64-decode the server response. */
+ if (strcmp(mech, "CRAM-MD5") != 0)
+ in = svn_base64_decode_string(in, pool);
+
+ clear_sasl_errno();
+ result = sasl_client_step(sasl_ctx,
+ in->data,
+ (const unsigned int) in->len,
+ &client_interact,
+ &out, /* Filled in by SASL. */
+ &outlen);
+
+ if (result != SASL_OK && result != SASL_CONTINUE)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ get_sasl_error(sasl_ctx, result, pool));
+
+ /* If the server thinks we're done, then don't send any response. */
+ if (strcmp(status, "success") == 0)
+ break;
+
+ if (outlen > 0)
+ {
+ arg = svn_string_ncreate(out, outlen, pool);
+ /* Write our response. */
+ /* For CRAM-MD5, we don't use base64-encoding. */
+ if (strcmp(mech, "CRAM-MD5") != 0)
+ arg = svn_base64_encode_string2(arg, TRUE, pool);
+ SVN_ERR(svn_ra_svn__write_cstring(sess->conn, pool, arg->data));
+ }
+ else
+ {
+ SVN_ERR(svn_ra_svn__write_cstring(sess->conn, pool, ""));
+ }
+ }
+
+ if (!status || strcmp(status, "step") == 0)
+ {
+ /* This is a client-send-last mech. Read the last server response. */
+ SVN_ERR(svn_ra_svn__read_tuple(sess->conn, pool, "w(?s)",
+ &status, &in));
+
+ if (strcmp(status, "failure") == 0)
+ {
+ *success = FALSE;
+ *last_err = in ? in->data : "";
+ }
+ else if (strcmp(status, "success") == 0)
+ {
+ /* We're done */
+ *success = TRUE;
+ }
+ else
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Unexpected server response"
+ " to authentication"));
+ }
+ else
+ *success = TRUE;
+ return SVN_NO_ERROR;
+}
+
+/* Baton for a SASL encrypted svn_ra_svn__stream_t. */
+typedef struct sasl_baton {
+ svn_ra_svn__stream_t *stream; /* Inherited stream. */
+ sasl_conn_t *ctx; /* The SASL context for this connection. */
+ unsigned int maxsize; /* The maximum amount of data we can encode. */
+ const char *read_buf; /* The buffer returned by sasl_decode. */
+ unsigned int read_len; /* Its current length. */
+ const char *write_buf; /* The buffer returned by sasl_encode. */
+ unsigned int write_len; /* Its length. */
+ apr_pool_t *scratch_pool;
+} sasl_baton_t;
+
+/* Functions to implement a SASL encrypted svn_ra_svn__stream_t. */
+
+/* Implements svn_read_fn_t. */
+static svn_error_t *sasl_read_cb(void *baton, char *buffer, apr_size_t *len)
+{
+ sasl_baton_t *sasl_baton = baton;
+ int result;
+ /* A copy of *len, used by the wrapped stream. */
+ apr_size_t len2 = *len;
+
+ /* sasl_decode might need more data than a single read can provide,
+ hence the need to put a loop around the decoding. */
+ while (! sasl_baton->read_buf || sasl_baton->read_len == 0)
+ {
+ SVN_ERR(svn_ra_svn__stream_read(sasl_baton->stream, buffer, &len2));
+ if (len2 == 0)
+ {
+ *len = 0;
+ return SVN_NO_ERROR;
+ }
+ clear_sasl_errno();
+ result = sasl_decode(sasl_baton->ctx, buffer, (unsigned int) len2,
+ &sasl_baton->read_buf,
+ &sasl_baton->read_len);
+ if (result != SASL_OK)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ get_sasl_error(sasl_baton->ctx, result,
+ sasl_baton->scratch_pool));
+ }
+
+ /* The buffer returned by sasl_decode might be larger than what the
+ caller wants. If this is the case, we only copy back *len bytes now
+ (the rest will be returned by subsequent calls to this function).
+ If not, we just copy back the whole thing. */
+ if (*len >= sasl_baton->read_len)
+ {
+ memcpy(buffer, sasl_baton->read_buf, sasl_baton->read_len);
+ *len = sasl_baton->read_len;
+ sasl_baton->read_buf = NULL;
+ sasl_baton->read_len = 0;
+ }
+ else
+ {
+ memcpy(buffer, sasl_baton->read_buf, *len);
+ sasl_baton->read_len -= *len;
+ sasl_baton->read_buf += *len;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_write_fn_t. */
+static svn_error_t *
+sasl_write_cb(void *baton, const char *buffer, apr_size_t *len)
+{
+ sasl_baton_t *sasl_baton = baton;
+ int result;
+
+ if (! sasl_baton->write_buf || sasl_baton->write_len == 0)
+ {
+ /* Make sure we don't write too much. */
+ *len = (*len > sasl_baton->maxsize) ? sasl_baton->maxsize : *len;
+ clear_sasl_errno();
+ result = sasl_encode(sasl_baton->ctx, buffer, (unsigned int) *len,
+ &sasl_baton->write_buf,
+ &sasl_baton->write_len);
+
+ if (result != SASL_OK)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ get_sasl_error(sasl_baton->ctx, result,
+ sasl_baton->scratch_pool));
+ }
+
+ do
+ {
+ apr_size_t tmplen = sasl_baton->write_len;
+ SVN_ERR(svn_ra_svn__stream_write(sasl_baton->stream,
+ sasl_baton->write_buf,
+ &tmplen));
+ if (tmplen == 0)
+ {
+ /* The output buffer and its length will be preserved in sasl_baton
+ and will be written out during the next call to this function
+ (which will have the same arguments). */
+ *len = 0;
+ return SVN_NO_ERROR;
+ }
+ sasl_baton->write_len -= (unsigned int) tmplen;
+ sasl_baton->write_buf += tmplen;
+ }
+ while (sasl_baton->write_len > 0);
+
+ sasl_baton->write_buf = NULL;
+ sasl_baton->write_len = 0;
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements ra_svn_timeout_fn_t. */
+static void sasl_timeout_cb(void *baton, apr_interval_time_t interval)
+{
+ sasl_baton_t *sasl_baton = baton;
+ svn_ra_svn__stream_timeout(sasl_baton->stream, interval);
+}
+
+/* Implements ra_svn_pending_fn_t. */
+static svn_boolean_t sasl_pending_cb(void *baton)
+{
+ sasl_baton_t *sasl_baton = baton;
+ return svn_ra_svn__stream_pending(sasl_baton->stream);
+}
+
+svn_error_t *svn_ra_svn__enable_sasl_encryption(svn_ra_svn_conn_t *conn,
+ sasl_conn_t *sasl_ctx,
+ apr_pool_t *pool)
+{
+ const sasl_ssf_t *ssfp;
+
+ if (! conn->encrypted)
+ {
+ int result;
+
+ /* Get the strength of the security layer. */
+ clear_sasl_errno();
+ result = sasl_getprop(sasl_ctx, SASL_SSF, (void*) &ssfp);
+ if (result != SASL_OK)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ get_sasl_error(sasl_ctx, result, pool));
+
+ if (*ssfp > 0)
+ {
+ sasl_baton_t *sasl_baton;
+ const void *maxsize;
+
+ /* Flush the connection, as we're about to replace its stream. */
+ SVN_ERR(svn_ra_svn__flush(conn, pool));
+
+ /* Create and initialize the stream baton. */
+ sasl_baton = apr_pcalloc(conn->pool, sizeof(*sasl_baton));
+ sasl_baton->ctx = sasl_ctx;
+ sasl_baton->scratch_pool = conn->pool;
+
+ /* Find out the maximum input size for sasl_encode. */
+ clear_sasl_errno();
+ result = sasl_getprop(sasl_ctx, SASL_MAXOUTBUF, &maxsize);
+ if (result != SASL_OK)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ get_sasl_error(sasl_ctx, result, pool));
+ sasl_baton->maxsize = *((const unsigned int *) maxsize);
+
+ /* If there is any data left in the read buffer at this point,
+ we need to decrypt it. */
+ if (conn->read_end > conn->read_ptr)
+ {
+ clear_sasl_errno();
+ result = sasl_decode(sasl_ctx, conn->read_ptr,
+ (unsigned int) (conn->read_end - conn->read_ptr),
+ &sasl_baton->read_buf, &sasl_baton->read_len);
+ if (result != SASL_OK)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ get_sasl_error(sasl_ctx, result, pool));
+ conn->read_end = conn->read_ptr;
+ }
+
+ /* Wrap the existing stream. */
+ sasl_baton->stream = conn->stream;
+
+ conn->stream = svn_ra_svn__stream_create(sasl_baton, sasl_read_cb,
+ sasl_write_cb,
+ sasl_timeout_cb,
+ sasl_pending_cb, conn->pool);
+ /* Yay, we have a security layer! */
+ conn->encrypted = TRUE;
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_svn__get_addresses(const char **local_addrport,
+ const char **remote_addrport,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ if (conn->sock)
+ {
+ apr_status_t apr_err;
+ apr_sockaddr_t *local_sa, *remote_sa;
+ char *local_addr, *remote_addr;
+
+ apr_err = apr_socket_addr_get(&local_sa, APR_LOCAL, conn->sock);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, NULL);
+
+ apr_err = apr_socket_addr_get(&remote_sa, APR_REMOTE, conn->sock);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, NULL);
+
+ apr_err = apr_sockaddr_ip_get(&local_addr, local_sa);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, NULL);
+
+ apr_err = apr_sockaddr_ip_get(&remote_addr, remote_sa);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, NULL);
+
+ /* Format the IP address and port number like this: a.b.c.d;port */
+ *local_addrport = apr_pstrcat(pool, local_addr, ";",
+ apr_itoa(pool, (int)local_sa->port),
+ (char *)NULL);
+ *remote_addrport = apr_pstrcat(pool, remote_addr, ";",
+ apr_itoa(pool, (int)remote_sa->port),
+ (char *)NULL);
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t *sess,
+ const apr_array_header_t *mechlist,
+ const char *realm, apr_pool_t *pool)
+{
+ apr_pool_t *subpool;
+ sasl_conn_t *sasl_ctx;
+ const char *mechstring = "", *last_err = "", *realmstring;
+ const char *local_addrport = NULL, *remote_addrport = NULL;
+ svn_boolean_t success;
+ sasl_callback_t *callbacks;
+ cred_baton_t cred_baton = { 0 };
+ int i;
+
+ if (!sess->is_tunneled)
+ {
+ SVN_ERR(svn_ra_svn__get_addresses(&local_addrport, &remote_addrport,
+ sess->conn, pool));
+ }
+
+ /* Prefer EXTERNAL, then ANONYMOUS, then let SASL decide. */
+ if (svn_ra_svn__find_mech(mechlist, "EXTERNAL"))
+ mechstring = "EXTERNAL";
+ else if (svn_ra_svn__find_mech(mechlist, "ANONYMOUS"))
+ mechstring = "ANONYMOUS";
+ else
+ {
+ /* Create a string containing the list of mechanisms, separated by spaces. */
+ for (i = 0; i < mechlist->nelts; i++)
+ {
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(mechlist, i, svn_ra_svn_item_t);
+ mechstring = apr_pstrcat(pool,
+ mechstring,
+ i == 0 ? "" : " ",
+ elt->u.word, (char *)NULL);
+ }
+ }
+
+ realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm);
+
+ /* Initialize the credential baton. */
+ cred_baton.auth_baton = sess->callbacks->auth_baton;
+ cred_baton.realmstring = realmstring;
+ cred_baton.pool = pool;
+
+ /* Reserve space for 3 callbacks (for the username, password and the
+ array terminator). These structures must persist until the
+ disposal of the SASL context at pool cleanup, however the
+ callback functions will not be invoked outside this function so
+ other structures can have a shorter lifetime. */
+ callbacks = apr_palloc(sess->conn->pool, sizeof(*callbacks) * 3);
+
+ /* Initialize the callbacks array. */
+
+ /* The username callback. */
+ callbacks[0].id = SASL_CB_AUTHNAME;
+ callbacks[0].proc = (int (*)(void))get_username_cb;
+ callbacks[0].context = &cred_baton;
+
+ /* The password callback. */
+ callbacks[1].id = SASL_CB_PASS;
+ callbacks[1].proc = (int (*)(void))get_password_cb;
+ callbacks[1].context = &cred_baton;
+
+ /* Mark the end of the array. */
+ callbacks[2].id = SASL_CB_LIST_END;
+ callbacks[2].proc = NULL;
+ callbacks[2].context = NULL;
+
+ subpool = svn_pool_create(pool);
+ do
+ {
+ svn_error_t *err;
+
+ /* If last_err was set to a non-empty string, it needs to be duplicated
+ to the parent pool before the subpool is cleared. */
+ if (*last_err)
+ last_err = apr_pstrdup(pool, last_err);
+ svn_pool_clear(subpool);
+
+ SVN_ERR(new_sasl_ctx(&sasl_ctx, sess->is_tunneled,
+ sess->hostname, local_addrport, remote_addrport,
+ callbacks, sess->conn->pool));
+ err = try_auth(sess, sasl_ctx, &success, &last_err, mechstring,
+ subpool);
+
+ /* If we encountered an error while fetching credentials, that error
+ has priority. */
+ if (cred_baton.err)
+ {
+ svn_error_clear(err);
+ return cred_baton.err;
+ }
+ if (cred_baton.no_more_creds
+ || (! err && ! success && ! cred_baton.was_used))
+ {
+ svn_error_clear(err);
+ /* If we ran out of authentication providers, or if we got a server
+ error and our callbacks were never called, there's no point in
+ retrying authentication. Return the last error sent by the
+ server. */
+ if (*last_err)
+ return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Authentication error from server: %s"),
+ last_err);
+ /* Hmm, we don't have a server error. Return a generic error. */
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Can't get username or password"));
+ }
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_RA_SVN_NO_MECHANISMS)
+ {
+ svn_error_clear(err);
+
+ /* We could not find a supported mechanism in the list sent by the
+ server. In many cases this happens because the client is missing
+ the CRAM-MD5 or ANONYMOUS plugins, in which case we can simply use
+ the built-in implementation. In all other cases this call will be
+ useless, but hey, at least we'll get consistent error messages. */
+ return svn_ra_svn__do_internal_auth(sess, mechlist,
+ realm, pool);
+ }
+ return err;
+ }
+ }
+ while (!success);
+ svn_pool_destroy(subpool);
+
+ SVN_ERR(svn_ra_svn__enable_sasl_encryption(sess->conn, sasl_ctx, pool));
+
+ SVN_ERR(svn_auth_save_credentials(cred_baton.iterstate, pool));
+
+ return SVN_NO_ERROR;
+}
+
+#endif /* SVN_HAVE_SASL */
diff --git a/contrib/subversion/subversion/libsvn_ra_svn/deprecated.c b/contrib/subversion/subversion/libsvn_ra_svn/deprecated.c
new file mode 100644
index 0000000..8182a4d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_svn/deprecated.c
@@ -0,0 +1,234 @@
+/*
+ * deprecated.c : Public, deprecated wrappers to our private ra_svn API
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_ra_svn.h"
+
+#include "private/svn_ra_svn_private.h"
+
+svn_error_t *
+svn_ra_svn_write_number(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_uint64_t number)
+{
+ return svn_error_trace(svn_ra_svn__write_number(conn, pool, number));
+}
+
+svn_error_t *
+svn_ra_svn_write_string(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_string_t *str)
+{
+ return svn_error_trace(svn_ra_svn__write_string(conn, pool, str));
+}
+
+svn_error_t *
+svn_ra_svn_write_cstring(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *s)
+{
+ return svn_error_trace(svn_ra_svn__write_cstring(conn, pool, s));
+}
+
+svn_error_t *
+svn_ra_svn_write_word(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *word)
+{
+ return svn_error_trace(svn_ra_svn__write_word(conn, pool, word));
+}
+
+svn_error_t *
+svn_ra_svn_write_proplist(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_hash_t *props)
+{
+ return svn_error_trace(svn_ra_svn__write_proplist(conn, pool, props));
+}
+
+svn_error_t *
+svn_ra_svn_start_list(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_ra_svn__start_list(conn, pool));
+}
+
+svn_error_t *
+svn_ra_svn_end_list(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_ra_svn__end_list(conn, pool));
+}
+
+svn_error_t *
+svn_ra_svn_flush(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_ra_svn__flush(conn, pool));
+}
+
+svn_error_t *
+svn_ra_svn_write_tuple(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ va_list va;
+ svn_error_t *err;
+
+ va_start(va, fmt);
+ err = svn_ra_svn__write_tuple(conn, pool, fmt, va);
+ va_end(va);
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_ra_svn_read_item(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_ra_svn_item_t **item)
+{
+ return svn_error_trace(svn_ra_svn__read_item(conn, pool, item));
+}
+
+svn_error_t *
+svn_ra_svn_skip_leading_garbage(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_ra_svn__skip_leading_garbage(conn, pool));
+}
+
+svn_error_t *
+svn_ra_svn_parse_tuple(const apr_array_header_t *list,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ va_list va;
+ svn_error_t *err;
+
+ va_start(va, fmt);
+ err = svn_ra_svn__parse_tuple(list, pool, fmt, va);
+ va_end(va);
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_ra_svn_read_tuple(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ va_list va;
+ svn_error_t *err;
+
+ va_start(va, fmt);
+ err = svn_ra_svn__read_tuple(conn, pool, fmt, va);
+ va_end(va);
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_ra_svn_parse_proplist(const apr_array_header_t *list,
+ apr_pool_t *pool,
+ apr_hash_t **props)
+{
+ return svn_error_trace(svn_ra_svn__parse_proplist(list, pool, props));
+}
+
+svn_error_t *
+svn_ra_svn_read_cmd_response(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ va_list va;
+ svn_error_t *err;
+
+ va_start(va, fmt);
+ err = svn_ra_svn__read_cmd_response(conn, pool, fmt, va);
+ va_end(va);
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_ra_svn_handle_commands2(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_ra_svn_cmd_entry_t *commands,
+ void *baton,
+ svn_boolean_t error_on_disconnect)
+{
+ return svn_error_trace(svn_ra_svn__handle_commands2(conn, pool,
+ commands, baton,
+ error_on_disconnect));
+}
+
+svn_error_t *
+svn_ra_svn_handle_commands(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_ra_svn_cmd_entry_t *commands,
+ void *baton)
+{
+ return svn_error_trace(svn_ra_svn__handle_commands2(conn, pool,
+ commands, baton,
+ FALSE));
+}
+
+svn_error_t *
+svn_ra_svn_write_cmd(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *cmdname,
+ const char *fmt, ...)
+{
+ va_list va;
+ svn_error_t *err;
+
+ SVN_ERR(svn_ra_svn__start_list(conn, pool));
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, cmdname));
+ va_start(va, fmt);
+ err = svn_ra_svn__write_tuple(conn, pool, fmt, va);
+ va_end(va);
+ return err ? svn_error_trace(err) : svn_ra_svn__end_list(conn, pool);
+}
+
+svn_error_t *
+svn_ra_svn_write_cmd_response(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ va_list va;
+ svn_error_t *err;
+
+ va_start(va, fmt);
+ err = svn_ra_svn__write_cmd_response(conn, pool, fmt, va);
+ va_end(va);
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_ra_svn_write_cmd_failure(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_error_t *err)
+{
+ return svn_error_trace(svn_ra_svn__write_cmd_failure(conn, pool, err));
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_svn/editorp.c b/contrib/subversion/subversion/libsvn_ra_svn/editorp.c
new file mode 100644
index 0000000..cc1d8ab
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_svn/editorp.c
@@ -0,0 +1,1044 @@
+/*
+ * editorp.c : Driving and consuming an editor across an svn connection
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+#include "svn_ra_svn.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_private_config.h"
+
+#include "private/svn_fspath.h"
+#include "private/svn_editor.h"
+
+#include "ra_svn.h"
+
+/*
+ * Both the client and server in the svn protocol need to drive and
+ * consume editors. For a commit, the client drives and the server
+ * consumes; for an update/switch/status/diff, the server drives and
+ * the client consumes. This file provides a generic framework for
+ * marshalling and unmarshalling editor operations over an svn
+ * connection; both ends are useful for both server and client.
+ */
+
+typedef struct ra_svn_edit_baton_t {
+ svn_ra_svn_conn_t *conn;
+ svn_ra_svn_edit_callback callback; /* Called on successful completion. */
+ void *callback_baton;
+ int next_token;
+ svn_boolean_t got_status;
+} ra_svn_edit_baton_t;
+
+/* Works for both directories and files. */
+typedef struct ra_svn_baton_t {
+ svn_ra_svn_conn_t *conn;
+ apr_pool_t *pool;
+ ra_svn_edit_baton_t *eb;
+ const char *token;
+} ra_svn_baton_t;
+
+typedef struct ra_svn_driver_state_t {
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ apr_hash_t *tokens;
+ svn_boolean_t *aborted;
+ svn_boolean_t done;
+ apr_pool_t *pool;
+ apr_pool_t *file_pool;
+ int file_refs;
+ svn_boolean_t for_replay;
+} ra_svn_driver_state_t;
+
+/* Works for both directories and files; however, the pool handling is
+ different for files. To save space during commits (where file
+ batons generally last until the end of the commit), token entries
+ for files are all created in a single reference-counted pool (the
+ file_pool member of the driver state structure), which is cleared
+ at close_file time when the reference count hits zero. So the pool
+ field in this structure is vestigial for files, and we use it for a
+ different purpose instead: at apply-textdelta time, we set it to a
+ subpool of the file pool, which is destroyed in textdelta-end. */
+typedef struct ra_svn_token_entry_t {
+ const char *token;
+ void *baton;
+ svn_boolean_t is_file;
+ svn_stream_t *dstream; /* svndiff stream for apply_textdelta */
+ apr_pool_t *pool;
+} ra_svn_token_entry_t;
+
+/* --- CONSUMING AN EDITOR BY PASSING EDIT OPERATIONS OVER THE NET --- */
+
+static const char *make_token(char type, ra_svn_edit_baton_t *eb,
+ apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "%c%d", type, eb->next_token++);
+}
+
+static ra_svn_baton_t *ra_svn_make_baton(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ ra_svn_edit_baton_t *eb,
+ const char *token)
+{
+ ra_svn_baton_t *b;
+
+ b = apr_palloc(pool, sizeof(*b));
+ b->conn = conn;
+ b->pool = pool;
+ b->eb = eb;
+ b->token = token;
+ return b;
+}
+
+/* Check for an early error status report from the consumer. If we
+ * get one, abort the edit and return the error. */
+static svn_error_t *
+check_for_error_internal(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(!eb->got_status);
+
+ /* reset TX counter */
+ eb->conn->written_since_error_check = 0;
+
+ /* if we weren't asked to always check, wait for at least the next TX */
+ eb->conn->may_check_for_error = eb->conn->error_check_interval == 0;
+
+ /* any incoming data? */
+ if (svn_ra_svn__input_waiting(eb->conn, pool))
+ {
+ eb->got_status = TRUE;
+ SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
+ /* We shouldn't get here if the consumer is doing its job. */
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Successful edit status returned too soon"));
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
+{
+ return eb->conn->may_check_for_error
+ ? check_for_error_internal(eb, pool)
+ : SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ ra_svn_edit_baton_t *eb = edit_baton;
+
+ SVN_ERR(check_for_error(eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_target_rev(eb->conn, pool, rev));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_open_root(void *edit_baton, svn_revnum_t rev,
+ apr_pool_t *pool, void **root_baton)
+{
+ ra_svn_edit_baton_t *eb = edit_baton;
+ const char *token = make_token('d', eb, pool);
+
+ SVN_ERR(check_for_error(eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_open_root(eb->conn, pool, rev, token));
+ *root_baton = ra_svn_make_baton(eb->conn, pool, eb, token);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_delete_entry(const char *path, svn_revnum_t rev,
+ void *parent_baton, apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = parent_baton;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_delete_entry(b->conn, pool,
+ path, rev, b->token));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_add_dir(const char *path, void *parent_baton,
+ const char *copy_path,
+ svn_revnum_t copy_rev,
+ apr_pool_t *pool, void **child_baton)
+{
+ ra_svn_baton_t *b = parent_baton;
+ const char *token = make_token('d', b->eb, pool);
+
+ SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
+ || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_add_dir(b->conn, pool, path, b->token,
+ token, copy_path, copy_rev));
+ *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_open_dir(const char *path, void *parent_baton,
+ svn_revnum_t rev, apr_pool_t *pool,
+ void **child_baton)
+{
+ ra_svn_baton_t *b = parent_baton;
+ const char *token = make_token('d', b->eb, pool);
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_open_dir(b->conn, pool, path, b->token,
+ token, rev));
+ *child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_change_dir_prop(void *dir_baton, const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = dir_baton;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_change_dir_prop(b->conn, pool, b->token,
+ name, value));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_close_dir(void *dir_baton, apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = dir_baton;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_close_dir(b->conn, pool, b->token));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_absent_dir(const char *path,
+ void *parent_baton, apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = parent_baton;
+
+ /* Avoid sending an unknown command if the other end doesn't support
+ absent-dir. */
+ if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_absent_dir(b->conn, pool, path, b->token));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_add_file(const char *path,
+ void *parent_baton,
+ const char *copy_path,
+ svn_revnum_t copy_rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ ra_svn_baton_t *b = parent_baton;
+ const char *token = make_token('c', b->eb, pool);
+
+ SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
+ || (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_add_file(b->conn, pool, path, b->token,
+ token, copy_path, copy_rev));
+ *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ ra_svn_baton_t *b = parent_baton;
+ const char *token = make_token('c', b->eb, pool);
+
+ SVN_ERR(check_for_error(b->eb, b->pool));
+ SVN_ERR(svn_ra_svn__write_cmd_open_file(b->conn, pool, path, b->token,
+ token, rev));
+ *file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_svndiff_handler(void *baton, const char *data,
+ apr_size_t *len)
+{
+ ra_svn_baton_t *b = baton;
+ svn_string_t str;
+
+ SVN_ERR(check_for_error(b->eb, b->pool));
+ str.data = data;
+ str.len = *len;
+ return svn_ra_svn__write_cmd_textdelta_chunk(b->conn, b->pool,
+ b->token, &str);
+}
+
+static svn_error_t *ra_svn_svndiff_close_handler(void *baton)
+{
+ ra_svn_baton_t *b = baton;
+
+ SVN_ERR(check_for_error(b->eb, b->pool));
+ SVN_ERR(svn_ra_svn__write_cmd_textdelta_end(b->conn, b->pool, b->token));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *wh,
+ void **wh_baton)
+{
+ ra_svn_baton_t *b = file_baton;
+ svn_stream_t *diff_stream;
+
+ /* Tell the other side we're starting a text delta. */
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_apply_textdelta(b->conn, pool, b->token,
+ base_checksum));
+
+ /* Transform the window stream to an svndiff stream. Reuse the
+ * file baton for the stream handler, since it has all the
+ * needed information. */
+ diff_stream = svn_stream_create(b, pool);
+ svn_stream_set_write(diff_stream, ra_svn_svndiff_handler);
+ svn_stream_set_close(diff_stream, ra_svn_svndiff_close_handler);
+
+ /* If the connection does not support SVNDIFF1 or if we don't want to use
+ * compression, use the non-compressing "version 0" implementation */
+ if ( svn_ra_svn_compression_level(b->conn) > 0
+ && svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_SVNDIFF1))
+ svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 1,
+ b->conn->compression_level, pool);
+ else
+ svn_txdelta_to_svndiff3(wh, wh_baton, diff_stream, 0,
+ b->conn->compression_level, pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = file_baton;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_change_file_prop(b->conn, pool,
+ b->token, name, value));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = file_baton;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_close_file(b->conn, pool,
+ b->token, text_checksum));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_absent_file(const char *path,
+ void *parent_baton, apr_pool_t *pool)
+{
+ ra_svn_baton_t *b = parent_baton;
+
+ /* Avoid sending an unknown command if the other end doesn't support
+ absent-file. */
+ if (! svn_ra_svn_has_capability(b->conn, SVN_RA_SVN_CAP_ABSENT_ENTRIES))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(check_for_error(b->eb, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_absent_file(b->conn, pool, path, b->token));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_close_edit(void *edit_baton, apr_pool_t *pool)
+{
+ ra_svn_edit_baton_t *eb = edit_baton;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(!eb->got_status);
+ eb->got_status = TRUE;
+ SVN_ERR(svn_ra_svn__write_cmd_close_edit(eb->conn, pool));
+ err = svn_ra_svn__read_cmd_response(eb->conn, pool, "");
+ if (err)
+ {
+ svn_error_clear(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
+ return err;
+ }
+ if (eb->callback)
+ SVN_ERR(eb->callback(eb->callback_baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_abort_edit(void *edit_baton, apr_pool_t *pool)
+{
+ ra_svn_edit_baton_t *eb = edit_baton;
+
+ if (eb->got_status)
+ return SVN_NO_ERROR;
+ SVN_ERR(svn_ra_svn__write_cmd_abort_edit(eb->conn, pool));
+ SVN_ERR(svn_ra_svn__read_cmd_response(eb->conn, pool, ""));
+ return SVN_NO_ERROR;
+}
+
+void svn_ra_svn_get_editor(const svn_delta_editor_t **editor,
+ void **edit_baton, svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_ra_svn_edit_callback callback,
+ void *callback_baton)
+{
+ svn_delta_editor_t *ra_svn_editor = svn_delta_default_editor(pool);
+ ra_svn_edit_baton_t *eb;
+
+ eb = apr_palloc(pool, sizeof(*eb));
+ eb->conn = conn;
+ eb->callback = callback;
+ eb->callback_baton = callback_baton;
+ eb->next_token = 0;
+ eb->got_status = FALSE;
+
+ ra_svn_editor->set_target_revision = ra_svn_target_rev;
+ ra_svn_editor->open_root = ra_svn_open_root;
+ ra_svn_editor->delete_entry = ra_svn_delete_entry;
+ ra_svn_editor->add_directory = ra_svn_add_dir;
+ ra_svn_editor->open_directory = ra_svn_open_dir;
+ ra_svn_editor->change_dir_prop = ra_svn_change_dir_prop;
+ ra_svn_editor->close_directory = ra_svn_close_dir;
+ ra_svn_editor->absent_directory = ra_svn_absent_dir;
+ ra_svn_editor->add_file = ra_svn_add_file;
+ ra_svn_editor->open_file = ra_svn_open_file;
+ ra_svn_editor->apply_textdelta = ra_svn_apply_textdelta;
+ ra_svn_editor->change_file_prop = ra_svn_change_file_prop;
+ ra_svn_editor->close_file = ra_svn_close_file;
+ ra_svn_editor->absent_file = ra_svn_absent_file;
+ ra_svn_editor->close_edit = ra_svn_close_edit;
+ ra_svn_editor->abort_edit = ra_svn_abort_edit;
+
+ *editor = ra_svn_editor;
+ *edit_baton = eb;
+
+ svn_error_clear(svn_editor__insert_shims(editor, edit_baton, *editor,
+ *edit_baton, NULL, NULL,
+ conn->shim_callbacks,
+ pool, pool));
+}
+
+/* --- DRIVING AN EDITOR --- */
+
+/* Store a token entry. The token string will be copied into pool. */
+static ra_svn_token_entry_t *store_token(ra_svn_driver_state_t *ds,
+ void *baton, const char *token,
+ svn_boolean_t is_file,
+ apr_pool_t *pool)
+{
+ ra_svn_token_entry_t *entry;
+
+ entry = apr_palloc(pool, sizeof(*entry));
+ entry->token = apr_pstrdup(pool, token);
+ entry->baton = baton;
+ entry->is_file = is_file;
+ entry->dstream = NULL;
+ entry->pool = pool;
+ svn_hash_sets(ds->tokens, entry->token, entry);
+ return entry;
+}
+
+static svn_error_t *lookup_token(ra_svn_driver_state_t *ds, const char *token,
+ svn_boolean_t is_file,
+ ra_svn_token_entry_t **entry)
+{
+ *entry = svn_hash_gets(ds->tokens, token);
+ if (!*entry || (*entry)->is_file != is_file)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Invalid file or dir token during edit"));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_target_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ svn_revnum_t rev;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev));
+ SVN_CMD_ERR(ds->editor->set_target_revision(ds->edit_baton, rev, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_open_root(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ svn_revnum_t rev;
+ apr_pool_t *subpool;
+ const char *token;
+ void *root_baton;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)c", &rev, &token));
+ subpool = svn_pool_create(ds->pool);
+ SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton, rev, subpool,
+ &root_baton));
+ store_token(ds, root_baton, token, FALSE, subpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_delete_entry(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path, *token;
+ svn_revnum_t rev;
+ ra_svn_token_entry_t *entry;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)c",
+ &path, &rev, &token));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ path = svn_relpath_canonicalize(path, pool);
+ SVN_CMD_ERR(ds->editor->delete_entry(path, rev, entry->baton, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_add_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path, *token, *child_token, *copy_path;
+ svn_revnum_t copy_rev;
+ ra_svn_token_entry_t *entry;
+ apr_pool_t *subpool;
+ void *child_baton;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?cr)", &path, &token,
+ &child_token, &copy_path, &copy_rev));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ subpool = svn_pool_create(entry->pool);
+ path = svn_relpath_canonicalize(path, pool);
+
+ /* Some operations pass COPY_PATH as a full URL (commits, etc.).
+ Others (replay, e.g.) deliver an fspath. That's ... annoying. */
+ if (copy_path)
+ {
+ if (svn_path_is_url(copy_path))
+ copy_path = svn_uri_canonicalize(copy_path, pool);
+ else
+ copy_path = svn_fspath__canonicalize(copy_path, pool);
+ }
+
+ SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path,
+ copy_rev, subpool, &child_baton));
+ store_token(ds, child_baton, child_token, FALSE, subpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_open_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path, *token, *child_token;
+ svn_revnum_t rev;
+ ra_svn_token_entry_t *entry;
+ apr_pool_t *subpool;
+ void *child_baton;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?r)", &path, &token,
+ &child_token, &rev));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ subpool = svn_pool_create(entry->pool);
+ path = svn_relpath_canonicalize(path, pool);
+ SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton, rev, subpool,
+ &child_baton));
+ store_token(ds, child_baton, child_token, FALSE, subpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_change_dir_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token, *name;
+ svn_string_t *value;
+ ra_svn_token_entry_t *entry;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc(?s)", &token, &name,
+ &value));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value,
+ entry->pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_close_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token;
+ ra_svn_token_entry_t *entry;
+
+ /* Parse and look up the directory token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &token));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+
+ /* Close the directory and destroy the baton. */
+ SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool));
+ svn_hash_sets(ds->tokens, token, NULL);
+ svn_pool_destroy(entry->pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_absent_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path;
+ const char *token;
+ ra_svn_token_entry_t *entry;
+
+ /* Parse parameters and look up the directory token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc", &path, &token));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+
+ /* Call the editor. */
+ SVN_CMD_ERR(ds->editor->absent_directory(path, entry->baton, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_add_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path, *token, *file_token, *copy_path;
+ svn_revnum_t copy_rev;
+ ra_svn_token_entry_t *entry, *file_entry;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?cr)", &path, &token,
+ &file_token, &copy_path, &copy_rev));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ ds->file_refs++;
+ path = svn_relpath_canonicalize(path, pool);
+
+ /* Some operations pass COPY_PATH as a full URL (commits, etc.).
+ Others (replay, e.g.) deliver an fspath. That's ... annoying. */
+ if (copy_path)
+ {
+ if (svn_path_is_url(copy_path))
+ copy_path = svn_uri_canonicalize(copy_path, pool);
+ else
+ copy_path = svn_fspath__canonicalize(copy_path, pool);
+ }
+
+ file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
+ SVN_CMD_ERR(ds->editor->add_file(path, entry->baton, copy_path, copy_rev,
+ ds->file_pool, &file_entry->baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_open_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path, *token, *file_token;
+ svn_revnum_t rev;
+ ra_svn_token_entry_t *entry, *file_entry;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccc(?r)", &path, &token,
+ &file_token, &rev));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+ ds->file_refs++;
+ path = svn_relpath_canonicalize(path, pool);
+ file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
+ SVN_CMD_ERR(ds->editor->open_file(path, entry->baton, rev, ds->file_pool,
+ &file_entry->baton));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_apply_textdelta(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token;
+ ra_svn_token_entry_t *entry;
+ svn_txdelta_window_handler_t wh;
+ void *wh_baton;
+ char *base_checksum;
+
+ /* Parse arguments and look up the token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)",
+ &token, &base_checksum));
+ SVN_ERR(lookup_token(ds, token, TRUE, &entry));
+ if (entry->dstream)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Apply-textdelta already active"));
+ entry->pool = svn_pool_create(ds->file_pool);
+ SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum,
+ entry->pool, &wh, &wh_baton));
+ entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_textdelta_chunk(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token;
+ ra_svn_token_entry_t *entry;
+ svn_string_t *str;
+
+ /* Parse arguments and look up the token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cs", &token, &str));
+ SVN_ERR(lookup_token(ds, token, TRUE, &entry));
+ if (!entry->dstream)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Apply-textdelta not active"));
+ SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_textdelta_end(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token;
+ ra_svn_token_entry_t *entry;
+
+ /* Parse arguments and look up the token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &token));
+ SVN_ERR(lookup_token(ds, token, TRUE, &entry));
+ if (!entry->dstream)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Apply-textdelta not active"));
+ SVN_CMD_ERR(svn_stream_close(entry->dstream));
+ entry->dstream = NULL;
+ svn_pool_destroy(entry->pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_change_file_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token, *name;
+ svn_string_t *value;
+ ra_svn_token_entry_t *entry;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc(?s)", &token, &name,
+ &value));
+ SVN_ERR(lookup_token(ds, token, TRUE, &entry));
+ SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_close_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *token;
+ ra_svn_token_entry_t *entry;
+ const char *text_checksum;
+
+ /* Parse arguments and look up the file token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)",
+ &token, &text_checksum));
+ SVN_ERR(lookup_token(ds, token, TRUE, &entry));
+
+ /* Close the file and destroy the baton. */
+ SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool));
+ svn_hash_sets(ds->tokens, token, NULL);
+ if (--ds->file_refs == 0)
+ svn_pool_clear(ds->file_pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_absent_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ const char *path;
+ const char *token;
+ ra_svn_token_entry_t *entry;
+
+ /* Parse parameters and look up the parent directory token. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cc", &path, &token));
+ SVN_ERR(lookup_token(ds, token, FALSE, &entry));
+
+ /* Call the editor. */
+ SVN_CMD_ERR(ds->editor->absent_file(path, entry->baton, pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *ra_svn_handle_close_edit(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool));
+ ds->done = TRUE;
+ if (ds->aborted)
+ *ds->aborted = FALSE;
+ return svn_ra_svn__write_cmd_response(conn, pool, "");
+}
+
+static svn_error_t *ra_svn_handle_abort_edit(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ ds->done = TRUE;
+ if (ds->aborted)
+ *ds->aborted = TRUE;
+ SVN_CMD_ERR(ds->editor->abort_edit(ds->edit_baton, pool));
+ return svn_ra_svn__write_cmd_response(conn, pool, "");
+}
+
+static svn_error_t *ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds)
+{
+ if (!ds->for_replay)
+ return svn_error_createf
+ (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
+ _("Command 'finish-replay' invalid outside of replays"));
+ ds->done = TRUE;
+ if (ds->aborted)
+ *ds->aborted = FALSE;
+ return SVN_NO_ERROR;
+}
+
+static const struct {
+ const char *cmd;
+ svn_error_t *(*handler)(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const apr_array_header_t *params,
+ ra_svn_driver_state_t *ds);
+} ra_svn_edit_cmds[] = {
+ { "change-file-prop", ra_svn_handle_change_file_prop },
+ { "open-file", ra_svn_handle_open_file },
+ { "apply-textdelta", ra_svn_handle_apply_textdelta },
+ { "textdelta-chunk", ra_svn_handle_textdelta_chunk },
+ { "close-file", ra_svn_handle_close_file },
+ { "add-dir", ra_svn_handle_add_dir },
+ { "open-dir", ra_svn_handle_open_dir },
+ { "change-dir-prop", ra_svn_handle_change_dir_prop },
+ { "delete-entry", ra_svn_handle_delete_entry },
+ { "close-dir", ra_svn_handle_close_dir },
+ { "absent-dir", ra_svn_handle_absent_dir },
+ { "add-file", ra_svn_handle_add_file },
+ { "textdelta-end", ra_svn_handle_textdelta_end },
+ { "absent-file", ra_svn_handle_absent_file },
+ { "abort-edit", ra_svn_handle_abort_edit },
+ { "finish-replay", ra_svn_handle_finish_replay },
+ { "target-rev", ra_svn_handle_target_rev },
+ { "open-root", ra_svn_handle_open_root },
+ { "close-edit", ra_svn_handle_close_edit },
+ { NULL }
+};
+
+static svn_error_t *blocked_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ void *baton)
+{
+ ra_svn_driver_state_t *ds = baton;
+ const char *cmd;
+ apr_array_header_t *params;
+
+ /* We blocked trying to send an error. Read and discard an editing
+ * command in order to avoid deadlock. */
+ SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "wl", &cmd, &params));
+ if (strcmp(cmd, "abort-edit") == 0)
+ {
+ ds->done = TRUE;
+ svn_ra_svn__set_block_handler(conn, NULL, NULL);
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_boolean_t *aborted,
+ svn_boolean_t for_replay)
+{
+ ra_svn_driver_state_t state;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ const char *cmd;
+ int i;
+ svn_error_t *err, *write_err;
+ apr_array_header_t *params;
+
+ state.editor = editor;
+ state.edit_baton = edit_baton;
+ state.tokens = apr_hash_make(pool);
+ state.aborted = aborted;
+ state.done = FALSE;
+ state.pool = pool;
+ state.file_pool = svn_pool_create(pool);
+ state.file_refs = 0;
+ state.for_replay = for_replay;
+
+ while (!state.done)
+ {
+ svn_pool_clear(subpool);
+ if (editor)
+ {
+ SVN_ERR(svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, &params));
+ for (i = 0; ra_svn_edit_cmds[i].cmd; i++)
+ if (strcmp(cmd, ra_svn_edit_cmds[i].cmd) == 0)
+ break;
+
+ if (ra_svn_edit_cmds[i].cmd)
+ err = (*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state);
+ else if (strcmp(cmd, "failure") == 0)
+ {
+ /* While not really an editor command this can occur when
+ reporter->finish_report() fails before the first editor
+ command */
+ if (aborted)
+ *aborted = TRUE;
+ err = svn_ra_svn__handle_failure_status(params, pool);
+ return svn_error_compose_create(
+ err,
+ editor->abort_edit(edit_baton, subpool));
+ }
+ else
+ {
+ err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
+ _("Unknown editor command '%s'"), cmd);
+ err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
+ }
+ }
+ else
+ {
+ const char* command = NULL;
+ SVN_ERR(svn_ra_svn__read_command_only(conn, subpool, &command));
+ if (strcmp(command, "close-edit") == 0)
+ {
+ state.done = TRUE;
+ if (aborted)
+ *aborted = FALSE;
+ err = svn_ra_svn__write_cmd_response(conn, pool, "");
+ }
+ else
+ err = NULL;
+ }
+
+ if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR)
+ {
+ if (aborted)
+ *aborted = TRUE;
+ if (!state.done)
+ {
+ /* Abort the edit and use non-blocking I/O to write the error. */
+ if (editor)
+ svn_error_clear(editor->abort_edit(edit_baton, subpool));
+ svn_ra_svn__set_block_handler(conn, blocked_write, &state);
+ }
+ write_err = svn_ra_svn__write_cmd_failure(
+ conn, subpool,
+ svn_ra_svn__locate_real_error_child(err));
+ if (!write_err)
+ write_err = svn_ra_svn__flush(conn, subpool);
+ svn_ra_svn__set_block_handler(conn, NULL, NULL);
+ svn_error_clear(err);
+ SVN_ERR(write_err);
+ break;
+ }
+ SVN_ERR(err);
+ }
+
+ /* Read and discard editing commands until the edit is complete.
+ Hopefully, the other side will call another editor command, run
+ check_for_error, notice the error, write "abort-edit" at us, and
+ throw the error up a few levels on its side (possibly even
+ tossing it right back at us, which is why we can return
+ SVN_NO_ERROR below).
+
+ However, if the other side is way ahead of us, it might
+ completely finish the edit (or sequence of edit/revprops, for
+ "replay-range") before we send over our "failure". So we should
+ also stop if we see "success". (Then the other side will try to
+ interpret our "failure" as a command, which will itself fail...
+ The net effect is that whatever error we wrote to the other side
+ will be replaced with SVN_ERR_RA_SVN_UNKNOWN_CMD.)
+ */
+ while (!state.done)
+ {
+ svn_pool_clear(subpool);
+ err = svn_ra_svn__read_tuple(conn, subpool, "wl", &cmd, &params);
+ if (err && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED)
+ {
+ /* Other side disconnected; that's no error. */
+ svn_error_clear(err);
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+ }
+ svn_error_clear(err);
+ if (strcmp(cmd, "abort-edit") == 0
+ || strcmp(cmd, "success") == 0)
+ state.done = TRUE;
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_boolean_t *aborted)
+{
+ return svn_ra_svn_drive_editor2(conn,
+ pool,
+ editor,
+ edit_baton,
+ aborted,
+ FALSE);
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_svn/internal_auth.c b/contrib/subversion/subversion/libsvn_ra_svn/internal_auth.c
new file mode 100644
index 0000000..eac2ccd
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_svn/internal_auth.c
@@ -0,0 +1,121 @@
+/*
+ * simple_auth.c : Simple SASL-based authentication, used in case
+ * Cyrus SASL isn't available.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_private_config.h"
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_ra.h"
+#include "svn_ra_svn.h"
+
+#include "ra_svn.h"
+
+svn_boolean_t svn_ra_svn__find_mech(const apr_array_header_t *mechlist,
+ const char *mech)
+{
+ int i;
+ svn_ra_svn_item_t *elt;
+
+ for (i = 0; i < mechlist->nelts; i++)
+ {
+ elt = &APR_ARRAY_IDX(mechlist, i, svn_ra_svn_item_t);
+ if (elt->kind == SVN_RA_SVN_WORD && strcmp(elt->u.word, mech) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Read the "success" response to ANONYMOUS or EXTERNAL authentication. */
+static svn_error_t *read_success(svn_ra_svn_conn_t *conn, apr_pool_t *pool)
+{
+ const char *status, *arg;
+
+ SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &status, &arg));
+ if (strcmp(status, "failure") == 0 && arg)
+ return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Authentication error from server: %s"), arg);
+ else if (strcmp(status, "success") != 0 || arg)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Unexpected server response to authentication"));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__do_internal_auth(svn_ra_svn__session_baton_t *sess,
+ const apr_array_header_t *mechlist,
+ const char *realm, apr_pool_t *pool)
+{
+ svn_ra_svn_conn_t *conn = sess->conn;
+ const char *realmstring, *user, *password, *msg;
+ svn_auth_iterstate_t *iterstate;
+ void *creds;
+
+ realmstring = apr_psprintf(pool, "%s %s", sess->realm_prefix, realm);
+
+ if (sess->is_tunneled && svn_ra_svn__find_mech(mechlist, "EXTERNAL"))
+ {
+ /* Ask the server to use the tunnel connection environment (on
+ * Unix, that means uid) to determine the authentication name. */
+ SVN_ERR(svn_ra_svn__auth_response(conn, pool, "EXTERNAL", ""));
+ return read_success(conn, pool);
+ }
+ else if (svn_ra_svn__find_mech(mechlist, "ANONYMOUS"))
+ {
+ SVN_ERR(svn_ra_svn__auth_response(conn, pool, "ANONYMOUS", ""));
+ return read_success(conn, pool);
+ }
+ else if (svn_ra_svn__find_mech(mechlist, "CRAM-MD5"))
+ {
+ SVN_ERR(svn_auth_first_credentials(&creds, &iterstate,
+ SVN_AUTH_CRED_SIMPLE, realmstring,
+ sess->callbacks->auth_baton, pool));
+ if (!creds)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Can't get password"));
+ while (creds)
+ {
+ user = ((svn_auth_cred_simple_t *) creds)->username;
+ password = ((svn_auth_cred_simple_t *) creds)->password;
+ SVN_ERR(svn_ra_svn__auth_response(conn, pool, "CRAM-MD5", NULL));
+ SVN_ERR(svn_ra_svn__cram_client(conn, pool, user, password, &msg));
+ if (!msg)
+ break;
+ SVN_ERR(svn_auth_next_credentials(&creds, iterstate, pool));
+ }
+ if (!creds)
+ return svn_error_createf(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Authentication error from server: %s"),
+ msg);
+ SVN_ERR(svn_auth_save_credentials(iterstate, pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ return svn_error_create(SVN_ERR_RA_SVN_NO_MECHANISMS, NULL, NULL);
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_svn/marshal.c b/contrib/subversion/subversion/libsvn_ra_svn/marshal.c
new file mode 100644
index 0000000..7cf483f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_svn/marshal.c
@@ -0,0 +1,2289 @@
+/*
+ * marshal.c : Marshalling routines for Subversion protocol
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <assert.h>
+#include <stdlib.h>
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_ra_svn.h"
+#include "svn_private_config.h"
+#include "svn_ctype.h"
+#include "svn_time.h"
+
+#include "ra_svn.h"
+
+#include "private/svn_string_private.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_error_private.h"
+
+#define svn_iswhitespace(c) ((c) == ' ' || (c) == '\n')
+
+/* If we receive data that *claims* to be followed by a very long string,
+ * we should not trust that claim right away. But everything up to 1 MB
+ * should be too small to be instrumental for a DOS attack. */
+
+#define SUSPICIOUSLY_HUGE_STRING_SIZE_THRESHOLD (0x100000)
+
+/* Return the APR socket timeout to be used for the connection depending
+ * on whether there is a blockage handler or zero copy has been activated. */
+static apr_interval_time_t
+get_timeout(svn_ra_svn_conn_t *conn)
+{
+ return conn->block_handler ? 0 : -1;
+}
+
+/* --- CONNECTION INITIALIZATION --- */
+
+svn_ra_svn_conn_t *svn_ra_svn_create_conn3(apr_socket_t *sock,
+ apr_file_t *in_file,
+ apr_file_t *out_file,
+ int compression_level,
+ apr_size_t zero_copy_limit,
+ apr_size_t error_check_interval,
+ apr_pool_t *pool)
+{
+ svn_ra_svn_conn_t *conn;
+ void *mem = apr_palloc(pool, sizeof(*conn) + SVN_RA_SVN__PAGE_SIZE);
+ conn = (void*)APR_ALIGN((apr_uintptr_t)mem, SVN_RA_SVN__PAGE_SIZE);
+
+ assert((sock && !in_file && !out_file) || (!sock && in_file && out_file));
+#ifdef SVN_HAVE_SASL
+ conn->sock = sock;
+ conn->encrypted = FALSE;
+#endif
+ conn->session = NULL;
+ conn->read_ptr = conn->read_buf;
+ conn->read_end = conn->read_buf;
+ conn->write_pos = 0;
+ conn->written_since_error_check = 0;
+ conn->error_check_interval = error_check_interval;
+ conn->may_check_for_error = error_check_interval == 0;
+ conn->block_handler = NULL;
+ conn->block_baton = NULL;
+ conn->capabilities = apr_hash_make(pool);
+ conn->compression_level = compression_level;
+ conn->zero_copy_limit = zero_copy_limit;
+ conn->pool = pool;
+
+ if (sock != NULL)
+ {
+ apr_sockaddr_t *sa;
+ conn->stream = svn_ra_svn__stream_from_sock(sock, pool);
+ if (!(apr_socket_addr_get(&sa, APR_REMOTE, sock) == APR_SUCCESS
+ && apr_sockaddr_ip_get(&conn->remote_ip, sa) == APR_SUCCESS))
+ conn->remote_ip = NULL;
+ svn_ra_svn__stream_timeout(conn->stream, get_timeout(conn));
+ }
+ else
+ {
+ conn->stream = svn_ra_svn__stream_from_files(in_file, out_file, pool);
+ conn->remote_ip = NULL;
+ }
+
+ return conn;
+}
+
+svn_ra_svn_conn_t *svn_ra_svn_create_conn2(apr_socket_t *sock,
+ apr_file_t *in_file,
+ apr_file_t *out_file,
+ int compression_level,
+ apr_pool_t *pool)
+{
+ return svn_ra_svn_create_conn3(sock, in_file, out_file,
+ compression_level, 0, 0, pool);
+}
+
+/* backward-compatible implementation using the default compression level */
+svn_ra_svn_conn_t *svn_ra_svn_create_conn(apr_socket_t *sock,
+ apr_file_t *in_file,
+ apr_file_t *out_file,
+ apr_pool_t *pool)
+{
+ return svn_ra_svn_create_conn3(sock, in_file, out_file,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0, 0,
+ pool);
+}
+
+svn_error_t *svn_ra_svn_set_capabilities(svn_ra_svn_conn_t *conn,
+ const apr_array_header_t *list)
+{
+ int i;
+ svn_ra_svn_item_t *item;
+ const char *word;
+
+ for (i = 0; i < list->nelts; i++)
+ {
+ item = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
+ if (item->kind != SVN_RA_SVN_WORD)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Capability entry is not a word"));
+ word = apr_pstrdup(conn->pool, item->u.word);
+ svn_hash_sets(conn->capabilities, word, word);
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__set_shim_callbacks(svn_ra_svn_conn_t *conn,
+ svn_delta_shim_callbacks_t *shim_callbacks)
+{
+ conn->shim_callbacks = shim_callbacks;
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t svn_ra_svn_has_capability(svn_ra_svn_conn_t *conn,
+ const char *capability)
+{
+ return (svn_hash_gets(conn->capabilities, capability) != NULL);
+}
+
+int
+svn_ra_svn_compression_level(svn_ra_svn_conn_t *conn)
+{
+ return conn->compression_level;
+}
+
+apr_size_t
+svn_ra_svn_zero_copy_limit(svn_ra_svn_conn_t *conn)
+{
+ return conn->zero_copy_limit;
+}
+
+const char *svn_ra_svn_conn_remote_host(svn_ra_svn_conn_t *conn)
+{
+ return conn->remote_ip;
+}
+
+void
+svn_ra_svn__set_block_handler(svn_ra_svn_conn_t *conn,
+ ra_svn_block_handler_t handler,
+ void *baton)
+{
+ conn->block_handler = handler;
+ conn->block_baton = baton;
+ svn_ra_svn__stream_timeout(conn->stream, get_timeout(conn));
+}
+
+svn_boolean_t svn_ra_svn__input_waiting(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return svn_ra_svn__stream_pending(conn->stream);
+}
+
+/* --- WRITE BUFFER MANAGEMENT --- */
+
+/* Write data to socket or output file as appropriate. */
+static svn_error_t *writebuf_output(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char *data, apr_size_t len)
+{
+ const char *end = data + len;
+ apr_size_t count;
+ apr_pool_t *subpool = NULL;
+ svn_ra_svn__session_baton_t *session = conn->session;
+
+ while (data < end)
+ {
+ count = end - data;
+
+ if (session && session->callbacks && session->callbacks->cancel_func)
+ SVN_ERR((session->callbacks->cancel_func)(session->callbacks_baton));
+
+ SVN_ERR(svn_ra_svn__stream_write(conn->stream, data, &count));
+ if (count == 0)
+ {
+ if (!subpool)
+ subpool = svn_pool_create(pool);
+ else
+ svn_pool_clear(subpool);
+ SVN_ERR(conn->block_handler(conn, subpool, conn->block_baton));
+ }
+ data += count;
+
+ if (session)
+ {
+ const svn_ra_callbacks2_t *cb = session->callbacks;
+ session->bytes_written += count;
+
+ if (cb && cb->progress_func)
+ (cb->progress_func)(session->bytes_written + session->bytes_read,
+ -1, cb->progress_baton, subpool);
+ }
+ }
+
+ conn->written_since_error_check += len;
+ conn->may_check_for_error
+ = conn->written_since_error_check >= conn->error_check_interval;
+
+ if (subpool)
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* Write data from the write buffer out to the socket. */
+static svn_error_t *writebuf_flush(svn_ra_svn_conn_t *conn, apr_pool_t *pool)
+{
+ apr_size_t write_pos = conn->write_pos;
+
+ /* Clear conn->write_pos first in case the block handler does a read. */
+ conn->write_pos = 0;
+ SVN_ERR(writebuf_output(conn, pool, conn->write_buf, write_pos));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *writebuf_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char *data, apr_size_t len)
+{
+ /* data >= 8k is sent immediately */
+ if (len >= sizeof(conn->write_buf) / 2)
+ {
+ if (conn->write_pos > 0)
+ SVN_ERR(writebuf_flush(conn, pool));
+
+ return writebuf_output(conn, pool, data, len);
+ }
+
+ /* ensure room for the data to add */
+ if (conn->write_pos + len > sizeof(conn->write_buf))
+ SVN_ERR(writebuf_flush(conn, pool));
+
+ /* buffer the new data block as well */
+ memcpy(conn->write_buf + conn->write_pos, data, len);
+ conn->write_pos += len;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+writebuf_write_short_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char *data, apr_size_t len)
+{
+ apr_size_t left = sizeof(conn->write_buf) - conn->write_pos;
+ if (len <= left)
+ {
+ memcpy(conn->write_buf + conn->write_pos, data, len);
+ conn->write_pos += len;
+ return SVN_NO_ERROR;
+ }
+ else
+ return writebuf_write(conn, pool, data, len);
+}
+
+static APR_INLINE svn_error_t *
+writebuf_writechar(svn_ra_svn_conn_t *conn, apr_pool_t *pool, char data)
+{
+ if (conn->write_pos < sizeof(conn->write_buf))
+ {
+ conn->write_buf[conn->write_pos] = data;
+ conn->write_pos++;
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ char temp = data;
+ return writebuf_write(conn, pool, &temp, 1);
+ }
+}
+
+/* --- READ BUFFER MANAGEMENT --- */
+
+/* Read bytes into DATA until either the read buffer is empty or
+ * we reach END. */
+static char *readbuf_drain(svn_ra_svn_conn_t *conn, char *data, char *end)
+{
+ apr_ssize_t buflen, copylen;
+
+ buflen = conn->read_end - conn->read_ptr;
+ copylen = (buflen < end - data) ? buflen : end - data;
+ memcpy(data, conn->read_ptr, copylen);
+ conn->read_ptr += copylen;
+ return data + copylen;
+}
+
+/* Read data from socket or input file as appropriate. */
+static svn_error_t *readbuf_input(svn_ra_svn_conn_t *conn, char *data,
+ apr_size_t *len, apr_pool_t *pool)
+{
+ svn_ra_svn__session_baton_t *session = conn->session;
+
+ if (session && session->callbacks && session->callbacks->cancel_func)
+ SVN_ERR((session->callbacks->cancel_func)(session->callbacks_baton));
+
+ SVN_ERR(svn_ra_svn__stream_read(conn->stream, data, len));
+ if (*len == 0)
+ return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, NULL);
+
+ if (session)
+ {
+ const svn_ra_callbacks2_t *cb = session->callbacks;
+ session->bytes_read += *len;
+
+ if (cb && cb->progress_func)
+ (cb->progress_func)(session->bytes_read + session->bytes_written,
+ -1, cb->progress_baton, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Treat the next LEN input bytes from CONN as "read" */
+static svn_error_t *readbuf_skip(svn_ra_svn_conn_t *conn, apr_uint64_t len)
+{
+ do
+ {
+ apr_size_t buflen = conn->read_end - conn->read_ptr;
+ apr_size_t copylen = (buflen < len) ? buflen : (apr_size_t)len;
+ conn->read_ptr += copylen;
+ len -= copylen;
+ if (len == 0)
+ break;
+
+ buflen = sizeof(conn->read_buf);
+ SVN_ERR(svn_ra_svn__stream_read(conn->stream, conn->read_buf, &buflen));
+ if (buflen == 0)
+ return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, NULL);
+
+ conn->read_end = conn->read_buf + buflen;
+ conn->read_ptr = conn->read_buf;
+ }
+ while (len > 0);
+
+ return SVN_NO_ERROR;
+}
+
+/* Read data from the socket into the read buffer, which must be empty. */
+static svn_error_t *readbuf_fill(svn_ra_svn_conn_t *conn, apr_pool_t *pool)
+{
+ apr_size_t len;
+
+ SVN_ERR_ASSERT(conn->read_ptr == conn->read_end);
+ SVN_ERR(writebuf_flush(conn, pool));
+ len = sizeof(conn->read_buf);
+ SVN_ERR(readbuf_input(conn, conn->read_buf, &len, pool));
+ conn->read_ptr = conn->read_buf;
+ conn->read_end = conn->read_buf + len;
+ return SVN_NO_ERROR;
+}
+
+static APR_INLINE svn_error_t *
+readbuf_getchar(svn_ra_svn_conn_t *conn, apr_pool_t *pool, char *result)
+{
+ if (conn->read_ptr == conn->read_end)
+ SVN_ERR(readbuf_fill(conn, pool));
+ *result = *conn->read_ptr++;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *readbuf_getchar_skip_whitespace(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ char *result)
+{
+ do
+ SVN_ERR(readbuf_getchar(conn, pool, result));
+ while (svn_iswhitespace(*result));
+ return SVN_NO_ERROR;
+}
+
+/* Read the next LEN bytes from CONN and copy them to *DATA. */
+static svn_error_t *readbuf_read(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ char *data, apr_size_t len)
+{
+ char *end = data + len;
+ apr_size_t count;
+
+ /* Copy in an appropriate amount of data from the buffer. */
+ data = readbuf_drain(conn, data, end);
+
+ /* Read large chunks directly into buffer. */
+ while (end - data > (apr_ssize_t)sizeof(conn->read_buf))
+ {
+ SVN_ERR(writebuf_flush(conn, pool));
+ count = end - data;
+ SVN_ERR(readbuf_input(conn, data, &count, pool));
+ data += count;
+ }
+
+ while (end > data)
+ {
+ /* The remaining amount to read is small; fill the buffer and
+ * copy from that. */
+ SVN_ERR(readbuf_fill(conn, pool));
+ data = readbuf_drain(conn, data, end);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *readbuf_skip_leading_garbage(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ char buf[256]; /* Must be smaller than sizeof(conn->read_buf) - 1. */
+ const char *p, *end;
+ apr_size_t len;
+ svn_boolean_t lparen = FALSE;
+
+ SVN_ERR_ASSERT(conn->read_ptr == conn->read_end);
+ while (1)
+ {
+ /* Read some data directly from the connection input source. */
+ len = sizeof(buf);
+ SVN_ERR(readbuf_input(conn, buf, &len, pool));
+ end = buf + len;
+
+ /* Scan the data for '(' WS with a very simple state machine. */
+ for (p = buf; p < end; p++)
+ {
+ if (lparen && svn_iswhitespace(*p))
+ break;
+ else
+ lparen = (*p == '(');
+ }
+ if (p < end)
+ break;
+ }
+
+ /* p now points to the whitespace just after the left paren. Fake
+ * up the left paren and then copy what we have into the read
+ * buffer. */
+ conn->read_buf[0] = '(';
+ memcpy(conn->read_buf + 1, p, end - p);
+ conn->read_ptr = conn->read_buf;
+ conn->read_end = conn->read_buf + 1 + (end - p);
+ return SVN_NO_ERROR;
+}
+
+/* --- WRITING DATA ITEMS --- */
+
+static svn_error_t *write_number(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_uint64_t number, char follow)
+{
+ apr_size_t written;
+
+ /* SVN_INT64_BUFFER_SIZE includes space for a terminating NUL that
+ * svn__ui64toa will always append. */
+ if (conn->write_pos + SVN_INT64_BUFFER_SIZE >= sizeof(conn->write_buf))
+ SVN_ERR(writebuf_flush(conn, pool));
+
+ written = svn__ui64toa(conn->write_buf + conn->write_pos, number);
+ conn->write_buf[conn->write_pos + written] = follow;
+ conn->write_pos += written + 1;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_number(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_uint64_t number)
+{
+ return write_number(conn, pool, number, ' ');
+}
+
+svn_error_t *
+svn_ra_svn__write_string(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_string_t *str)
+{
+ if (str->len < 10)
+ {
+ SVN_ERR(writebuf_writechar(conn, pool, (char)(str->len + '0')));
+ SVN_ERR(writebuf_writechar(conn, pool, ':'));
+ }
+ else
+ SVN_ERR(write_number(conn, pool, str->len, ':'));
+
+ SVN_ERR(writebuf_write(conn, pool, str->data, str->len));
+ SVN_ERR(writebuf_writechar(conn, pool, ' '));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cstring(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *s)
+{
+ apr_size_t len = strlen(s);
+
+ if (len < 10)
+ {
+ SVN_ERR(writebuf_writechar(conn, pool, (char)(len + '0')));
+ SVN_ERR(writebuf_writechar(conn, pool, ':'));
+ }
+ else
+ SVN_ERR(write_number(conn, pool, len, ':'));
+
+ SVN_ERR(writebuf_write(conn, pool, s, len));
+ SVN_ERR(writebuf_writechar(conn, pool, ' '));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_word(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *word)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, word, strlen(word)));
+ SVN_ERR(writebuf_writechar(conn, pool, ' '));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_proplist(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_hash_t *props)
+{
+ apr_pool_t *iterpool;
+ apr_hash_index_t *hi;
+ const void *key;
+ void *val;
+ const char *propname;
+ svn_string_t *propval;
+
+ if (props)
+ {
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
+ {
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ propname = key;
+ propval = val;
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cs",
+ propname, propval));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__start_list(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ if (conn->write_pos + 2 <= sizeof(conn->write_buf))
+ {
+ conn->write_buf[conn->write_pos] = '(';
+ conn->write_buf[conn->write_pos+1] = ' ';
+ conn->write_pos += 2;
+ return SVN_NO_ERROR;
+ }
+
+ return writebuf_write(conn, pool, "( ", 2);
+}
+
+svn_error_t *
+svn_ra_svn__end_list(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ if (conn->write_pos + 2 <= sizeof(conn->write_buf))
+ {
+ conn->write_buf[conn->write_pos] = ')';
+ conn->write_buf[conn->write_pos+1] = ' ';
+ conn->write_pos += 2;
+ return SVN_NO_ERROR;
+ }
+
+ return writebuf_write(conn, pool, ") ", 2);
+}
+
+svn_error_t *
+svn_ra_svn__flush(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ SVN_ERR(writebuf_flush(conn, pool));
+ conn->may_check_for_error = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* --- WRITING TUPLES --- */
+
+static svn_error_t *
+vwrite_tuple_cstring(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const char *cstr = va_arg(*ap, const char *);
+ SVN_ERR_ASSERT(cstr);
+ return svn_ra_svn__write_cstring(conn, pool, cstr);
+}
+
+static svn_error_t *
+vwrite_tuple_cstring_opt(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const char *cstr = va_arg(*ap, const char *);
+ return cstr ? svn_ra_svn__write_cstring(conn, pool, cstr) : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_tuple_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const svn_string_t *str = va_arg(*ap, const svn_string_t *);
+ SVN_ERR_ASSERT(str);
+ return svn_ra_svn__write_string(conn, pool, str);
+}
+
+static svn_error_t *
+vwrite_tuple_string_opt(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const svn_string_t *str = va_arg(*ap, const svn_string_t *);
+ return str ? svn_ra_svn__write_string(conn, pool, str) : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_tuple_word(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const char *cstr = va_arg(*ap, const char *);
+ SVN_ERR_ASSERT(cstr);
+ return svn_ra_svn__write_word(conn, pool, cstr);
+}
+
+static svn_error_t *
+vwrite_tuple_word_opt(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const char *cstr = va_arg(*ap, const char *);
+ return cstr ? svn_ra_svn__write_word(conn, pool, cstr) : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_tuple_revision(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ svn_revnum_t rev = va_arg(*ap, svn_revnum_t);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(rev));
+ return svn_ra_svn__write_number(conn, pool, rev);
+}
+
+static svn_error_t *
+vwrite_tuple_revision_opt(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ svn_revnum_t rev = va_arg(*ap, svn_revnum_t);
+ return SVN_IS_VALID_REVNUM(rev)
+ ? svn_ra_svn__write_number(conn, pool, rev)
+ : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_tuple_number(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ return svn_ra_svn__write_number(conn, pool, va_arg(*ap, apr_uint64_t));
+}
+
+static svn_error_t *
+vwrite_tuple_boolean(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const char *cstr = va_arg(*ap, svn_boolean_t) ? "true" : "false";
+ return svn_ra_svn__write_word(conn, pool, cstr);
+}
+
+static svn_error_t *
+write_tuple_cstring(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *cstr)
+{
+ SVN_ERR_ASSERT(cstr);
+ return svn_ra_svn__write_cstring(conn, pool, cstr);
+}
+
+static svn_error_t *
+write_tuple_cstring_opt(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *cstr)
+{
+ return cstr ? svn_ra_svn__write_cstring(conn, pool, cstr) : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_tuple_string(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_string_t *str)
+{
+ SVN_ERR_ASSERT(str);
+ return svn_ra_svn__write_string(conn, pool, str);
+}
+
+static svn_error_t *
+write_tuple_string_opt(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_string_t *str)
+{
+ return str ? svn_ra_svn__write_string(conn, pool, str) : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_tuple_start_list(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return svn_ra_svn__start_list(conn, pool);
+}
+
+static svn_error_t *
+write_tuple_end_list(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return svn_ra_svn__end_list(conn, pool);
+}
+
+static svn_error_t *
+write_tuple_revision(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev)
+{
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(rev));
+ return svn_ra_svn__write_number(conn, pool, rev);
+}
+
+static svn_error_t *
+write_tuple_revision_opt(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev)
+{
+ return SVN_IS_VALID_REVNUM(rev)
+ ? svn_ra_svn__write_number(conn, pool, rev)
+ : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_tuple_boolean(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_boolean_t value)
+{
+ const char *cstr = value ? "true" : "false";
+ return svn_ra_svn__write_word(conn, pool, cstr);
+}
+
+static svn_error_t *
+write_tuple_depth(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_depth_t depth)
+{
+ return svn_ra_svn__write_word(conn, pool, svn_depth_to_word(depth));
+}
+
+
+static svn_error_t *
+write_cmd_add_node(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token,
+ const char *token,
+ const char *copy_path,
+ svn_revnum_t copy_rev)
+{
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_cstring(conn, pool, parent_token));
+ SVN_ERR(write_tuple_cstring(conn, pool, token));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_cstring_opt(conn, pool, copy_path));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, copy_rev));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_cmd_open_node(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token,
+ const char *token,
+ svn_revnum_t rev)
+{
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_cstring(conn, pool, parent_token));
+ SVN_ERR(write_tuple_cstring(conn, pool, token));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, rev));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_cmd_change_node_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token,
+ const char *name,
+ const svn_string_t *value)
+{
+ SVN_ERR(write_tuple_cstring(conn, pool, token));
+ SVN_ERR(write_tuple_cstring(conn, pool, name));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_string_opt(conn, pool, value));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_cmd_absent_node(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *token)
+{
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_cstring(conn, pool, token));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+static svn_error_t *vwrite_tuple(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char *fmt, va_list *ap)
+{
+ svn_boolean_t opt = FALSE;
+
+ if (*fmt == '!')
+ fmt++;
+ else
+ SVN_ERR(svn_ra_svn__start_list(conn, pool));
+ for (; *fmt; fmt++)
+ {
+ if (*fmt == 'c')
+ SVN_ERR(opt ? vwrite_tuple_cstring_opt(conn, pool, ap)
+ : vwrite_tuple_cstring(conn, pool, ap));
+ else if (*fmt == 's')
+ SVN_ERR(opt ? vwrite_tuple_string_opt(conn, pool, ap)
+ : vwrite_tuple_string(conn, pool, ap));
+ else if (*fmt == '(' && !opt)
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ else if (*fmt == ')')
+ {
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ opt = FALSE;
+ }
+ else if (*fmt == '?')
+ opt = TRUE;
+ else if (*fmt == 'w')
+ SVN_ERR(opt ? vwrite_tuple_word_opt(conn, pool, ap)
+ : vwrite_tuple_word(conn, pool, ap));
+ else if (*fmt == 'r')
+ SVN_ERR(opt ? vwrite_tuple_revision_opt(conn, pool, ap)
+ : vwrite_tuple_revision(conn, pool, ap));
+ else if (*fmt == 'n' && !opt)
+ SVN_ERR(vwrite_tuple_number(conn, pool, ap));
+ else if (*fmt == 'b' && !opt)
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ else if (*fmt == '!' && !*(fmt + 1))
+ return SVN_NO_ERROR;
+ else
+ SVN_ERR_MALFUNCTION();
+ }
+ SVN_ERR(svn_ra_svn__end_list(conn, pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_tuple(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ svn_error_t *err;
+ va_list ap;
+
+ va_start(ap, fmt);
+ err = vwrite_tuple(conn, pool, fmt, &ap);
+ va_end(ap);
+ return err;
+}
+
+/* --- READING DATA ITEMS --- */
+
+/* Read LEN bytes from CONN into already-allocated structure ITEM.
+ * Afterwards, *ITEM is of type 'SVN_RA_SVN_STRING', and its string
+ * data is allocated in POOL. */
+static svn_error_t *read_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ svn_ra_svn_item_t *item, apr_uint64_t len64)
+{
+ svn_stringbuf_t *stringbuf;
+ apr_size_t len = (apr_size_t)len64;
+ apr_size_t readbuf_len;
+ char *dest;
+
+ /* We can't store strings longer than the maximum size of apr_size_t,
+ * so check for wrapping */
+ if (len64 > APR_SIZE_MAX)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("String length larger than maximum"));
+
+ /* Read the string in chunks. The chunk size is large enough to avoid
+ * re-allocation in typical cases, and small enough to ensure we do not
+ * pre-allocate an unreasonable amount of memory if (perhaps due to
+ * network data corruption or a DOS attack), we receive a bogus claim that
+ * a very long string is going to follow. In that case, we start small
+ * and wait for all that data to actually show up. This does not fully
+ * prevent DOS attacks but makes them harder (you have to actually send
+ * gigabytes of data). */
+ readbuf_len = len < SUSPICIOUSLY_HUGE_STRING_SIZE_THRESHOLD
+ ? len
+ : SUSPICIOUSLY_HUGE_STRING_SIZE_THRESHOLD;
+ stringbuf = svn_stringbuf_create_ensure(readbuf_len, pool);
+ dest = stringbuf->data;
+
+ /* Read remaining string data directly into the string structure.
+ * Do it iteratively, if necessary. */
+ while (readbuf_len)
+ {
+ SVN_ERR(readbuf_read(conn, pool, dest, readbuf_len));
+
+ stringbuf->len += readbuf_len;
+ len -= readbuf_len;
+
+ /* Early exit. In most cases, strings can be read in the first
+ * iteration. */
+ if (len == 0)
+ break;
+
+ /* Prepare next iteration: determine length of chunk to read
+ * and re-alloc the string buffer. */
+ readbuf_len
+ = len < SUSPICIOUSLY_HUGE_STRING_SIZE_THRESHOLD
+ ? len
+ : SUSPICIOUSLY_HUGE_STRING_SIZE_THRESHOLD;
+
+ svn_stringbuf_ensure(stringbuf, stringbuf->len + readbuf_len);
+ dest = stringbuf->data + stringbuf->len;
+ }
+
+ /* zero-terminate the string */
+ stringbuf->data[stringbuf->len] = '\0';
+
+ /* Return the string properly wrapped into an RA_SVN item. */
+ item->kind = SVN_RA_SVN_STRING;
+ item->u.string = svn_stringbuf__morph_into_string(stringbuf);
+
+ return SVN_NO_ERROR;
+}
+
+/* Given the first non-whitespace character FIRST_CHAR, read an item
+ * into the already allocated structure ITEM. LEVEL should be set
+ * to 0 for the first call and is used to enforce a recurssion limit
+ * on the parser. */
+static svn_error_t *read_item(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ svn_ra_svn_item_t *item, char first_char,
+ int level)
+{
+ char c = first_char;
+ apr_uint64_t val;
+ svn_stringbuf_t *str;
+ svn_ra_svn_item_t *listitem;
+
+ if (++level >= 64)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Too many nested items"));
+
+
+ /* Determine the item type and read it in. Make sure that c is the
+ * first character at the end of the item so we can test to make
+ * sure it's whitespace. */
+ if (svn_ctype_isdigit(c))
+ {
+ /* It's a number or a string. Read the number part, either way. */
+ val = c - '0';
+ while (1)
+ {
+ apr_uint64_t prev_val = val;
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ if (!svn_ctype_isdigit(c))
+ break;
+ val = val * 10 + (c - '0');
+ /* val wrapped past maximum value? */
+ if (prev_val >= (APR_UINT64_MAX / 10) && (val / 10) != prev_val)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Number is larger than maximum"));
+ }
+ if (c == ':')
+ {
+ /* It's a string. */
+ SVN_ERR(read_string(conn, pool, item, val));
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ }
+ else
+ {
+ /* It's a number. */
+ item->kind = SVN_RA_SVN_NUMBER;
+ item->u.number = val;
+ }
+ }
+ else if (svn_ctype_isalpha(c))
+ {
+ /* It's a word. */
+ str = svn_stringbuf_create_ensure(16, pool);
+ svn_stringbuf_appendbyte(str, c);
+ while (1)
+ {
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ if (!svn_ctype_isalnum(c) && c != '-')
+ break;
+ svn_stringbuf_appendbyte(str, c);
+ }
+ item->kind = SVN_RA_SVN_WORD;
+ item->u.word = str->data;
+ }
+ else if (c == '(')
+ {
+ /* Read in the list items. */
+ item->kind = SVN_RA_SVN_LIST;
+ item->u.list = apr_array_make(pool, 4, sizeof(svn_ra_svn_item_t));
+ while (1)
+ {
+ SVN_ERR(readbuf_getchar_skip_whitespace(conn, pool, &c));
+ if (c == ')')
+ break;
+ listitem = apr_array_push(item->u.list);
+ SVN_ERR(read_item(conn, pool, listitem, c, level));
+ }
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ }
+
+ if (!svn_iswhitespace(c))
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Malformed network data"));
+ return SVN_NO_ERROR;
+}
+
+/* Given the first non-whitespace character FIRST_CHAR, read the first
+ * command (word) encountered in CONN into *ITEM. If ITEM is NULL, skip
+ * to the end of the current list. Use POOL for allocations. */
+static svn_error_t *
+read_command_only(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char **item, char first_char)
+{
+ char c = first_char;
+
+ /* Determine the item type and read it in. Make sure that c is the
+ * first character at the end of the item so we can test to make
+ * sure it's whitespace. */
+ if (svn_ctype_isdigit(c))
+ {
+ /* It's a number or a string. Read the number part, either way. */
+ apr_uint64_t val, prev_val=0;
+ val = c - '0';
+ while (1)
+ {
+ prev_val = val;
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ if (!svn_ctype_isdigit(c))
+ break;
+ val = val * 10 + (c - '0');
+ if (prev_val >= (APR_UINT64_MAX / 10)) /* > maximum value? */
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Number is larger than maximum"));
+ }
+ if (c == ':')
+ {
+ /* It's a string. */
+ SVN_ERR(readbuf_skip(conn, val));
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ }
+ }
+ else if (svn_ctype_isalpha(c))
+ {
+ /* It's a word. */
+ if (item)
+ {
+ /* This is the word we want to read */
+
+ char *buf = apr_palloc(pool, 32);
+ apr_size_t len = 1;
+ buf[0] = c;
+
+ while (1)
+ {
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ if (!svn_ctype_isalnum(c) && c != '-')
+ break;
+ buf[len] = c;
+ if (++len == 32)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Word too long"));
+ }
+ buf[len] = 0;
+ *item = buf;
+ }
+ else
+ {
+ /* we don't need the actual word, just skip it */
+ do
+ {
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ }
+ while (svn_ctype_isalnum(c) || c == '-');
+ }
+ }
+ else if (c == '(')
+ {
+ /* Read in the list items. */
+ while (1)
+ {
+ SVN_ERR(readbuf_getchar_skip_whitespace(conn, pool, &c));
+ if (c == ')')
+ break;
+
+ if (item && *item == NULL)
+ SVN_ERR(read_command_only(conn, pool, item, c));
+ else
+ SVN_ERR(read_command_only(conn, pool, NULL, c));
+ }
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__read_item(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_ra_svn_item_t **item)
+{
+ char c;
+
+ /* Allocate space, read the first character, and then do the rest of
+ * the work. This makes sense because of the way lists are read. */
+ *item = apr_palloc(pool, sizeof(**item));
+ SVN_ERR(readbuf_getchar_skip_whitespace(conn, pool, &c));
+ return read_item(conn, pool, *item, c, 0);
+}
+
+svn_error_t *
+svn_ra_svn__skip_leading_garbage(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return readbuf_skip_leading_garbage(conn, pool);
+}
+
+/* --- READING AND PARSING TUPLES --- */
+
+/* Parse a tuple of svn_ra_svn_item_t *'s. Advance *FMT to the end of the
+ * tuple specification and advance AP by the corresponding arguments. */
+static svn_error_t *vparse_tuple(const apr_array_header_t *items, apr_pool_t *pool,
+ const char **fmt, va_list *ap)
+{
+ int count, nesting_level;
+ svn_ra_svn_item_t *elt;
+
+ for (count = 0; **fmt && count < items->nelts; (*fmt)++, count++)
+ {
+ /* '?' just means the tuple may stop; skip past it. */
+ if (**fmt == '?')
+ (*fmt)++;
+ elt = &APR_ARRAY_IDX(items, count, svn_ra_svn_item_t);
+ if (**fmt == 'n' && elt->kind == SVN_RA_SVN_NUMBER)
+ *va_arg(*ap, apr_uint64_t *) = elt->u.number;
+ else if (**fmt == 'r' && elt->kind == SVN_RA_SVN_NUMBER)
+ *va_arg(*ap, svn_revnum_t *) = (svn_revnum_t) elt->u.number;
+ else if (**fmt == 's' && elt->kind == SVN_RA_SVN_STRING)
+ *va_arg(*ap, svn_string_t **) = elt->u.string;
+ else if (**fmt == 'c' && elt->kind == SVN_RA_SVN_STRING)
+ *va_arg(*ap, const char **) = elt->u.string->data;
+ else if (**fmt == 'w' && elt->kind == SVN_RA_SVN_WORD)
+ *va_arg(*ap, const char **) = elt->u.word;
+ else if (**fmt == 'b' && elt->kind == SVN_RA_SVN_WORD)
+ {
+ if (strcmp(elt->u.word, "true") == 0)
+ *va_arg(*ap, svn_boolean_t *) = TRUE;
+ else if (strcmp(elt->u.word, "false") == 0)
+ *va_arg(*ap, svn_boolean_t *) = FALSE;
+ else
+ break;
+ }
+ else if (**fmt == 'B' && elt->kind == SVN_RA_SVN_WORD)
+ {
+ if (strcmp(elt->u.word, "true") == 0)
+ *va_arg(*ap, apr_uint64_t *) = TRUE;
+ else if (strcmp(elt->u.word, "false") == 0)
+ *va_arg(*ap, apr_uint64_t *) = FALSE;
+ else
+ break;
+ }
+ else if (**fmt == 'l' && elt->kind == SVN_RA_SVN_LIST)
+ *va_arg(*ap, apr_array_header_t **) = elt->u.list;
+ else if (**fmt == '(' && elt->kind == SVN_RA_SVN_LIST)
+ {
+ (*fmt)++;
+ SVN_ERR(vparse_tuple(elt->u.list, pool, fmt, ap));
+ }
+ else if (**fmt == ')')
+ return SVN_NO_ERROR;
+ else
+ break;
+ }
+ if (**fmt == '?')
+ {
+ nesting_level = 0;
+ for (; **fmt; (*fmt)++)
+ {
+ switch (**fmt)
+ {
+ case '?':
+ break;
+ case 'r':
+ *va_arg(*ap, svn_revnum_t *) = SVN_INVALID_REVNUM;
+ break;
+ case 's':
+ *va_arg(*ap, svn_string_t **) = NULL;
+ break;
+ case 'c':
+ case 'w':
+ *va_arg(*ap, const char **) = NULL;
+ break;
+ case 'l':
+ *va_arg(*ap, apr_array_header_t **) = NULL;
+ break;
+ case 'B':
+ case 'n':
+ *va_arg(*ap, apr_uint64_t *) = SVN_RA_SVN_UNSPECIFIED_NUMBER;
+ break;
+ case '(':
+ nesting_level++;
+ break;
+ case ')':
+ if (--nesting_level < 0)
+ return SVN_NO_ERROR;
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+ }
+ }
+ if (**fmt && **fmt != ')')
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Malformed network data"));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__parse_tuple(const apr_array_header_t *list,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ svn_error_t *err;
+ va_list ap;
+
+ va_start(ap, fmt);
+ err = vparse_tuple(list, pool, &fmt, &ap);
+ va_end(ap);
+ return err;
+}
+
+svn_error_t *
+svn_ra_svn__read_tuple(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ va_list ap;
+ svn_ra_svn_item_t *item;
+ svn_error_t *err;
+
+ SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Malformed network data"));
+ va_start(ap, fmt);
+ err = vparse_tuple(item->u.list, pool, &fmt, &ap);
+ va_end(ap);
+ return err;
+}
+
+svn_error_t *
+svn_ra_svn__read_command_only(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char **command)
+{
+ char c;
+ SVN_ERR(readbuf_getchar_skip_whitespace(conn, pool, &c));
+
+ *command = NULL;
+ return read_command_only(conn, pool, command, c);
+}
+
+
+svn_error_t *
+svn_ra_svn__parse_proplist(const apr_array_header_t *list,
+ apr_pool_t *pool,
+ apr_hash_t **props)
+{
+ char *name;
+ svn_string_t *value;
+ svn_ra_svn_item_t *elt;
+ int i;
+
+ *props = apr_hash_make(pool);
+ for (i = 0; i < list->nelts; i++)
+ {
+ elt = &APR_ARRAY_IDX(list, i, svn_ra_svn_item_t);
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Proplist element not a list"));
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, pool, "cs",
+ &name, &value));
+ svn_hash_sets(*props, name, value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* --- READING AND WRITING COMMANDS AND RESPONSES --- */
+
+svn_error_t *svn_ra_svn__locate_real_error_child(svn_error_t *err)
+{
+ svn_error_t *this_link;
+
+ SVN_ERR_ASSERT(err);
+
+ for (this_link = err;
+ this_link && (this_link->apr_err == SVN_ERR_RA_SVN_CMD_ERR);
+ this_link = this_link->child)
+ ;
+
+ SVN_ERR_ASSERT(this_link);
+ return this_link;
+}
+
+svn_error_t *svn_ra_svn__handle_failure_status(const apr_array_header_t *params,
+ apr_pool_t *pool)
+{
+ const char *message, *file;
+ svn_error_t *err = NULL;
+ svn_ra_svn_item_t *elt;
+ int i;
+ apr_uint64_t apr_err, line;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ if (params->nelts == 0)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Empty error list"));
+
+ /* Rebuild the error list from the end, to avoid reversing the order. */
+ for (i = params->nelts - 1; i >= 0; i--)
+ {
+ svn_pool_clear(subpool);
+ elt = &APR_ARRAY_IDX(params, i, svn_ra_svn_item_t);
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Malformed error list"));
+ SVN_ERR(svn_ra_svn__parse_tuple(elt->u.list, subpool, "nccn",
+ &apr_err, &message, &file, &line));
+ /* The message field should have been optional, but we can't
+ easily change that, so "" means a nonexistent message. */
+ if (!*message)
+ message = NULL;
+
+ /* Skip over links in the error chain that were intended only to
+ exist on the server (to wrap real errors intended for the
+ client) but accidentally got included in the server's actual
+ response. */
+ if ((apr_status_t)apr_err != SVN_ERR_RA_SVN_CMD_ERR)
+ {
+ err = svn_error_create((apr_status_t)apr_err, err, message);
+ err->file = apr_pstrdup(err->pool, file);
+ err->line = (long)line;
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ /* If we get here, then we failed to find a real error in the error
+ chain that the server proported to be sending us. That's bad. */
+ if (! err)
+ err = svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Malformed error list"));
+
+ return err;
+}
+
+svn_error_t *
+svn_ra_svn__read_cmd_response(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ va_list ap;
+ const char *status;
+ apr_array_header_t *params;
+ svn_error_t *err;
+
+ SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "wl", &status, &params));
+ if (strcmp(status, "success") == 0)
+ {
+ va_start(ap, fmt);
+ err = vparse_tuple(params, pool, &fmt, &ap);
+ va_end(ap);
+ return err;
+ }
+ else if (strcmp(status, "failure") == 0)
+ {
+ return svn_ra_svn__handle_failure_status(params, pool);
+ }
+
+ return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Unknown status '%s' in command response"),
+ status);
+}
+
+svn_error_t *
+svn_ra_svn__handle_commands2(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const svn_ra_svn_cmd_entry_t *commands,
+ void *baton,
+ svn_boolean_t error_on_disconnect)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_pool_t *iterpool = svn_pool_create(subpool);
+ const char *cmdname;
+ const svn_ra_svn_cmd_entry_t *command;
+ svn_error_t *err, *write_err;
+ apr_array_header_t *params;
+ apr_hash_t *cmd_hash = apr_hash_make(subpool);
+
+ for (command = commands; command->cmdname; command++)
+ svn_hash_sets(cmd_hash, command->cmdname, command);
+
+ while (1)
+ {
+ svn_pool_clear(iterpool);
+ err = svn_ra_svn__read_tuple(conn, iterpool, "wl", &cmdname, &params);
+ if (err)
+ {
+ if (!error_on_disconnect
+ && err->apr_err == SVN_ERR_RA_SVN_CONNECTION_CLOSED)
+ {
+ svn_error_clear(err);
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+ }
+ return err;
+ }
+ command = svn_hash_gets(cmd_hash, cmdname);
+
+ if (command)
+ err = (*command->handler)(conn, iterpool, params, baton);
+ else
+ {
+ err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
+ _("Unknown editor command '%s'"), cmdname);
+ err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
+ }
+
+ if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR)
+ {
+ write_err = svn_ra_svn__write_cmd_failure(
+ conn, iterpool,
+ svn_ra_svn__locate_real_error_child(err));
+ svn_error_clear(err);
+ if (write_err)
+ return write_err;
+ }
+ else if (err)
+ return err;
+
+ if (command && command->terminate)
+ break;
+ }
+ svn_pool_destroy(iterpool);
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_target_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( target-rev ( ", 15));
+ SVN_ERR(write_tuple_revision(conn, pool, rev));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_open_root(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *token)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( open-root ( ", 14));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, rev));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_cstring(conn, pool, token));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_delete_entry(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t rev,
+ const char *token)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( delete-entry ( ", 17));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, rev));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_cstring(conn, pool, token));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_add_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token,
+ const char *token,
+ const char *copy_path,
+ svn_revnum_t copy_rev)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( add-dir ( ", 12));
+ SVN_ERR(write_cmd_add_node(conn, pool, path, parent_token, token,
+ copy_path, copy_rev));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_open_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token,
+ const char *token,
+ svn_revnum_t rev)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( open-dir ( ", 13));
+ SVN_ERR(write_cmd_open_node(conn, pool, path, parent_token, token, rev));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_change_dir_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token,
+ const char *name,
+ const svn_string_t *value)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( change-dir-prop ( ", 20));
+ SVN_ERR(write_cmd_change_node_prop(conn, pool, token, name, value));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_close_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( close-dir ( ", 14));
+ SVN_ERR(write_tuple_cstring(conn, pool, token));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_absent_dir(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( absent-dir ( ", 15));
+ SVN_ERR(write_cmd_absent_node(conn, pool, path, parent_token));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_add_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token,
+ const char *token,
+ const char *copy_path,
+ svn_revnum_t copy_rev)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( add-file ( ", 13));
+ SVN_ERR(write_cmd_add_node(conn, pool, path, parent_token, token,
+ copy_path, copy_rev));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_open_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token,
+ const char *token,
+ svn_revnum_t rev)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( open-file ( ", 14));
+ SVN_ERR(write_cmd_open_node(conn, pool, path, parent_token, token, rev));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_change_file_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token,
+ const char *name,
+ const svn_string_t *value)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( change-file-prop ( ", 21));
+ SVN_ERR(write_cmd_change_node_prop(conn, pool, token, name, value));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_close_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token,
+ const char *text_checksum)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( close-file ( ", 15));
+ SVN_ERR(write_tuple_cstring(conn, pool, token));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_cstring_opt(conn, pool, text_checksum));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_absent_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *parent_token)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( absent-file ( ", 16));
+ SVN_ERR(write_cmd_absent_node(conn, pool, path, parent_token));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_textdelta_chunk(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token,
+ const svn_string_t *chunk)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( textdelta-chunk ( ", 20));
+ SVN_ERR(write_tuple_cstring(conn, pool, token));
+ SVN_ERR(write_tuple_string(conn, pool, chunk));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_textdelta_end(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( textdelta-end ( ", 18));
+ SVN_ERR(write_tuple_cstring(conn, pool, token));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_apply_textdelta(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *token,
+ const char *base_checksum)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( apply-textdelta ( ", 20));
+ SVN_ERR(write_tuple_cstring(conn, pool, token));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_cstring_opt(conn, pool, base_checksum));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_close_edit(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return writebuf_write_short_string(conn, pool, "( close-edit ( ) ) ", 19);
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_abort_edit(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return writebuf_write_short_string(conn, pool, "( abort-edit ( ) ) ", 19);
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_set_path(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t rev,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ svn_depth_t depth)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( set-path ( ", 13));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_revision(conn, pool, rev));
+ SVN_ERR(write_tuple_boolean(conn, pool, start_empty));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_cstring_opt(conn, pool, lock_token));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_depth(conn, pool, depth));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_delete_path(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( delete-path ( ", 16));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_link_path(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *url,
+ svn_revnum_t rev,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ svn_depth_t depth)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( link-path ( ", 14));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_cstring(conn, pool, url));
+ SVN_ERR(write_tuple_revision(conn, pool, rev));
+ SVN_ERR(write_tuple_boolean(conn, pool, start_empty));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_cstring_opt(conn, pool,lock_token));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_depth(conn, pool, depth));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_finish_report(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return writebuf_write_short_string(conn, pool, "( finish-report ( ) ) ", 22);
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_abort_report(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return writebuf_write_short_string(conn, pool, "( abort-report ( ) ) ", 21);
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_reparent(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *url)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( reparent ( ", 13));
+ SVN_ERR(write_tuple_cstring(conn, pool, url));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_get_latest_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return writebuf_write_short_string(conn, pool, "( get-latest-rev ( ) ) ", 23);
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_get_dated_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_time_t tm)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( get-dated-rev ( ", 18));
+ SVN_ERR(write_tuple_cstring(conn, pool, svn_time_to_cstring(tm, pool)));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_change_rev_prop2(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *value,
+ svn_boolean_t dont_care,
+ const svn_string_t *old_value)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( change-rev-prop2 ( ", 21));
+ SVN_ERR(write_tuple_revision(conn, pool, rev));
+ SVN_ERR(write_tuple_cstring(conn, pool, name));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_string_opt(conn, pool, value));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_boolean(conn, pool, dont_care));
+ SVN_ERR(write_tuple_string_opt(conn, pool, old_value));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_change_rev_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *value)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( change-rev-prop ( ", 20));
+ SVN_ERR(write_tuple_revision(conn, pool, rev));
+ SVN_ERR(write_tuple_cstring(conn, pool, name));
+ SVN_ERR(write_tuple_string_opt(conn, pool, value));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_rev_proplist(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( rev-proplist ( ", 17));
+ SVN_ERR(write_tuple_revision(conn, pool, rev));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_rev_prop(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *name)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( rev-prop ( ", 13));
+ SVN_ERR(write_tuple_revision(conn, pool, rev));
+ SVN_ERR(write_tuple_cstring(conn, pool, name));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_get_file(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t rev,
+ svn_boolean_t props,
+ svn_boolean_t stream)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( get-file ( ", 13));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, rev));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_boolean(conn, pool, props));
+ SVN_ERR(write_tuple_boolean(conn, pool, stream));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_update(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *target,
+ svn_boolean_t recurse,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( update ( ", 11));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, rev));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_cstring(conn, pool, target));
+ SVN_ERR(write_tuple_boolean(conn, pool, recurse));
+ SVN_ERR(write_tuple_depth(conn, pool, depth));
+ SVN_ERR(write_tuple_boolean(conn, pool, send_copyfrom_args));
+ SVN_ERR(write_tuple_boolean(conn, pool, ignore_ancestry));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_switch(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *target,
+ svn_boolean_t recurse,
+ const char *switch_url,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( switch ( ", 11));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, rev));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_cstring(conn, pool, target));
+ SVN_ERR(write_tuple_boolean(conn, pool, recurse));
+ SVN_ERR(write_tuple_cstring(conn, pool, switch_url));
+ SVN_ERR(write_tuple_depth(conn, pool, depth));
+ SVN_ERR(write_tuple_boolean(conn, pool, send_copyfrom_args));
+ SVN_ERR(write_tuple_boolean(conn, pool, ignore_ancestry));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_status(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *target,
+ svn_boolean_t recurse,
+ svn_revnum_t rev,
+ svn_depth_t depth)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( status ( ", 11));
+ SVN_ERR(write_tuple_cstring(conn, pool, target));
+ SVN_ERR(write_tuple_boolean(conn, pool, recurse));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, rev));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_depth(conn, pool, depth));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_diff(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ const char *target,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ const char *versus_url,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( diff ( ", 9));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, rev));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_cstring(conn, pool, target));
+ SVN_ERR(write_tuple_boolean(conn, pool, recurse));
+ SVN_ERR(write_tuple_boolean(conn, pool, ignore_ancestry));
+ SVN_ERR(write_tuple_cstring(conn, pool, versus_url));
+ SVN_ERR(write_tuple_boolean(conn, pool, text_deltas));
+ SVN_ERR(write_tuple_depth(conn, pool, depth));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_check_path(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t rev)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( check-path ( ", 15));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, rev));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_stat(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t rev)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( stat ( ", 9));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, rev));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_get_file_revs(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( get-file-revs ( ", 18));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, start));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, end));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_boolean(conn, pool, include_merged_revisions));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_lock(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ svn_revnum_t revnum)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( lock ( ", 9));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_cstring_opt(conn, pool, comment));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_boolean(conn, pool, steal_lock));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, revnum));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_unlock(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( unlock ( ", 11));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_cstring_opt(conn, pool, token));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(write_tuple_boolean(conn, pool, break_lock));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_get_lock(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( get-lock ( ", 13));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_get_locks(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_depth_t depth)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( get-locks ( ", 14));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_depth(conn, pool, depth));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_replay(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t rev,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( replay ( ", 11));
+ SVN_ERR(write_tuple_revision(conn, pool, rev));
+ SVN_ERR(write_tuple_revision(conn, pool, low_water_mark));
+ SVN_ERR(write_tuple_boolean(conn, pool, send_deltas));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_replay_range(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( replay-range ( ", 17));
+ SVN_ERR(write_tuple_revision(conn, pool, start_revision));
+ SVN_ERR(write_tuple_revision(conn, pool, end_revision));
+ SVN_ERR(write_tuple_revision(conn, pool, low_water_mark));
+ SVN_ERR(write_tuple_boolean(conn, pool, send_deltas));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_get_deleted_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( get-deleted-rev ( ", 20));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_revision(conn, pool, peg_revision));
+ SVN_ERR(write_tuple_revision(conn, pool, end_revision));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_get_iprops(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *path,
+ svn_revnum_t revision)
+{
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( get-iprops ( ", 15));
+ SVN_ERR(write_tuple_cstring(conn, pool, path));
+ SVN_ERR(write_tuple_start_list(conn, pool));
+ SVN_ERR(write_tuple_revision_opt(conn, pool, revision));
+ SVN_ERR(write_tuple_end_list(conn, pool));
+ SVN_ERR(writebuf_write_short_string(conn, pool, ") ) ", 4));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_svn__write_cmd_finish_replay(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ return writebuf_write_short_string(conn, pool, "( finish-replay ( ) ) ", 22);
+}
+
+svn_error_t *svn_ra_svn__write_cmd_response(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ va_list ap;
+ svn_error_t *err;
+
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( success ", 10));
+ va_start(ap, fmt);
+ err = vwrite_tuple(conn, pool, fmt, &ap);
+ va_end(ap);
+ return err ? svn_error_trace(err) : svn_ra_svn__end_list(conn, pool);
+}
+
+svn_error_t *svn_ra_svn__write_cmd_failure(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool, svn_error_t *err)
+{
+ char buffer[128];
+ SVN_ERR(writebuf_write_short_string(conn, pool, "( failure ( ", 12));
+ for (; err; err = err->child)
+ {
+ const char *msg;
+
+#ifdef SVN_ERR__TRACING
+ if (svn_error__is_tracing_link(err))
+ msg = err->message;
+ else
+#endif
+ msg = svn_err_best_message(err, buffer, sizeof(buffer));
+
+ /* The message string should have been optional, but we can't
+ easily change that, so marshal nonexistent messages as "". */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "nccn",
+ (apr_uint64_t) err->apr_err,
+ msg ? msg : "",
+ err->file ? err->file : "",
+ (apr_uint64_t) err->line));
+ }
+ return writebuf_write_short_string(conn, pool, ") ) ", 4);
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_svn/protocol b/contrib/subversion/subversion/libsvn_ra_svn/protocol
new file mode 100644
index 0000000..059873c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_svn/protocol
@@ -0,0 +1,625 @@
+This file documents version 2 of the svn protocol.
+
+1. Syntactic structure
+----------------------
+
+The Subversion protocol is specified in terms of the following
+syntactic elements, specified using ABNF [RFC 2234]:
+
+ item = word / number / string / list
+ word = ALPHA *(ALPHA / DIGIT / "-") space
+ number = 1*DIGIT space
+ string = 1*DIGIT ":" *OCTET space
+ ; digits give the byte count of the *OCTET portion
+ list = "(" space *item ")" space
+ space = 1*(SP / LF)
+
+Here is an example item showing each of the syntactic elements:
+
+ ( word 22 6:string ( sublist ) )
+
+All items end with mandatory whitespace. (In the above example, a
+newline provides the terminating whitespace for the outer list.) It
+is possible to parse an item without knowing its type in advance.
+
+Lists are not constrained to contain items of the same type. Lists
+can be used for tuples, optional tuples, or arrays. A tuple is a list
+expected to contain a fixed number of items, generally of differing
+types. An optional tuple is a list containing either zero or a fixed
+number of items (thus "optional" here does not refer to the list's
+presence or absence, but to the presence or absence of its contents).
+An array is a list containing zero or more items of the same type.
+
+Words are used for enumerated protocol values, while strings are used
+for text or binary data of interest to the Subversion client or
+server. Words are case-sensitive.
+
+For convenience, this specification will define prototypes for data
+items using a syntax like:
+
+ example: ( literal ( data:string ... ) )
+
+A simple word such as "literal", with no colon, denotes a literal
+word. A choice of words may be given with "|" separating the choices.
+"name:type" specifies a parameter with the given type.
+
+A type is "word", "number", "string", "list", or the name of another
+prototype. Parentheses denote a tuple, unless the parentheses contain
+ellipses, in which case the parentheses denote an array containing
+zero or more elements matching the prototype preceding the ellipses.
+
+If a tuple has an optional part after the fixed part, a '?' marks
+places where the tuple is allowed to end. The following tuple could
+contain one, three, or four or more items:
+
+ example: ( fixed:string ? opt1:number opt2:string ? opt3:number )
+
+Brackets denote an optional tuple; they are equivalent to parentheses
+and a leading '?'. For example, this:
+
+ example: ( literal (? rev:number ) ( data:string ... ) )
+
+can be written more compactly like this:
+
+ example: ( literal [ rev:number ] ( data:string ... ) )
+
+For extensibility, implementations must treat a list as matching a
+prototype's tuple even if the list contains extra elements. The extra
+elements must be ignored.
+
+In some cases, a prototype may need to match two different kinds of
+data items. This case will be written using "|" to separate the
+alternatives; for example:
+
+ example: ( first-kind rev:number )
+ | second-kind
+
+The "command response" prototype is used in several contexts of this
+specification to indicate the success or failure of an operation. It
+is defined as follows:
+
+ command-response: ( success params:list )
+ | ( failure ( err:error ... ) )
+ error: ( apr-err:number message:string file:string line:number )
+
+The interpretation of parameters in a successful command response is
+context-dependent.
+
+URLs and repository paths are represented as strings. They should be in
+canonical form when sent over the protocol. However, as a matter of input
+validation, an implementation should always canonicalize received paths if it
+needs them in canonicalized form.
+
+2. Connection establishment and protocol setup
+----------------------------------------------
+
+By default, the client connects to the server on port 3690.
+
+Upon receiving a connection, the server sends a greeting, using a
+command response whose parameters match the prototype:
+
+ greeting: ( minver:number maxver:number mechs:list ( cap:word ... ) )
+
+minver and maxver give the minimum and maximum Subversion protocol
+versions supported by the server. mechs is present for historical
+reasons, and is ignored by the client. The cap values give a list of
+server capabilities (see section 2.1).
+
+If the client does not support a protocol version within the specified
+range, it closes the connection. Otherwise, the client responds to
+the greeting with an item matching the prototype:
+
+ response: ( version:number ( cap:word ... ) url:string
+ ? ra-client:string ( ? client:string ) )
+
+version gives the protocol version selected by the client. The cap
+values give a list of client capabilities (see section 2.1). url
+gives the URL the client is accessing. ra-client is a string
+identifying the RA implementation, e.g. "SVN/1.6.0" or "SVNKit 1.1.4".
+client is the string returned by svn_ra_callbacks2_t.get_client_string;
+that callback may not be implemented, so this is optional.
+
+Upon receiving the client's response to the greeting, the server sends
+an authentication request, which is a command response whose arguments
+match the prototype:
+
+ auth-request: ( ( mech:word ... ) realm:string )
+
+The mech values give a list of SASL mechanisms supported by the
+server. The realm string is similar to an HTTP authentication realm
+as defined in [RFC 2617]; it allows the server to indicate which of
+several protection spaces the server wishes to authenticate in. If
+the mechanism list is empty, then no authentication is required and no
+further action takes place as part of the authentication challenge;
+otherwise, the client responds with a tuple matching the prototype:
+
+ auth-response: ( mech:word [ token:string ] )
+
+mech specifies the SASL mechanism and token, if present, gives the
+"initial response" of the authentication exchange. The client may
+specify an empty mechanism to decline authentication; otherwise, upon
+receiving the client's auth-response, the server sends a series of
+challenges, each a tuple matching the prototype:
+
+ challenge: ( step ( token:string ) )
+ | ( failure ( message:string ) )
+ | ( success [ token:string ] )
+
+If the first word of the challenge is "step", then the token is
+interpreted by the authentication mechanism, and the response token
+transmitted to the server as a string. The server then proceeds with
+another challenge. If the client wishes to abort the authentication
+exchange, it may do so by closing the connection.
+
+If the first word of the challenge is "success", the authentication is
+successful. If a token is provided, it should be interpreted by the
+authentication mechanism, but there is no response.
+
+If the first word of the challenge is "failure", the authentication
+exchange is unsuccessful. The client may then give up, or make
+another auth-response and restart the authentication process.
+
+RFC 2222 requires that a protocol profile define a service name for
+the sake of the GSSAPI mechanism. The service name for this protocol
+is "svn".
+
+After a successful authentication exchange, the server sends a command
+response whose parameters match the prototype:
+
+ repos-info: ( uuid:string repos-url:string ( cap:word ... ) )
+
+uuid gives the universal unique identifier of the repository,
+repos-url gives the URL of the repository's root directory, and the
+cap values list the repository capabilities (that is, capabilities
+that require both server and repository support before the server can
+claim them as capabilities, e.g., SVN_RA_SVN_CAP_MERGEINFO).
+
+The client can now begin sending commands from the main command set.
+
+2.1 Capabilities
+
+The following capabilities are currently defined (S indicates a server
+capability and C indicates a client capability):
+
+[CS] edit-pipeline Every released version of Subversion since 1.0
+ announces the edit-pipeline capability; starting
+ in Subversion 1.5, both client and server
+ *require* the other side to announce edit-pipeline.
+[CS] svndiff1 If both the client and server support svndiff version
+ 1, this will be used as the on-the-wire format for
+ svndiff instead of svndiff version 0.
+[CS] absent-entries If the remote end announces support for this capability,
+ it will accept the absent-dir and absent-file editor
+ commands.
+[S] commit-revprops If the server presents this capability, it supports the
+ rev-props parameter of the commit command.
+ See section 3.1.1.
+[S] mergeinfo If the server presents this capability, it supports the
+ get-mergeinfo command. See section 3.1.1.
+[S] depth If the server presents this capability, it understands
+ requested operational depth (see section 3.1.1) and
+ per-path ambient depth (see section 3.1.3).
+[S] atomic-revprops If the server presents this capability, it
+ supports the change-rev-prop2 command.
+ See section 3.1.1.
+[S] inherited-props If the server presents this capability, it supports the
+ retrieval of inherited properties via the get-dir and
+ get-file commands and also supports the get-iprops
+ command (see section 3.1.1).
+
+3. Commands
+-----------
+
+Commands match the prototypes:
+
+ command: ( command-name:word params:list )
+
+The interpretation of command parameters is different from command to
+command.
+
+Initially, the client initiates commands from the main command set,
+and the server responds. Some commands in the main command set can
+temporarily change the set of commands which may be issued, or change
+the flow of control so that the server issues commands and the client
+responds.
+
+Here are some miscellaneous prototypes used by the command sets:
+
+ proplist: ( ( name:string value:string ) ... )
+ iproplist: ( ( name:string proplist ) ... )
+ propdelta: ( ( name:string [ value:string ] ) ... )
+ node-kind: none|file|dir|unknown
+ bool: true|false
+ lockdesc: ( path:string token:string owner:string [ comment:string ]
+ created:string [ expires:string ] )
+
+3.1. Command Sets
+
+There are three command sets: the main command set, the editor command
+set, and the report command set. Initially, the protocol begins in
+the main command set with the client sending commands; some commands
+can change the command set and possibly the direction of control.
+
+3.1.1. Main Command Set
+
+The main command set corresponds to the svn_ra interfaces. After each
+main command is issued by the client, the server sends an auth-request
+as described in section 2. (If no new authentication is required, the
+auth-request contains an empty mechanism list, and the server proceeds
+immediately to sending the command response.) Some commands include a
+second place for auth-request point as noted below.
+
+ reparent
+ params: ( url:string )
+ response: ( )
+
+ get-latest-rev
+ params: ( )
+ response: ( rev:number )
+
+ get-dated-rev
+ params: ( date:string )
+ response: ( rev:number )
+
+ change-rev-prop
+ params: ( rev:number name:string ? value:string )
+ response: ( )
+ If value is not specified, the rev-prop is removed.
+ (Originally the value was required; for minimum impact, it was
+ changed to be optional without creating an optional tuple for
+ that one parameter as we normally do.)
+
+ change-rev-prop2
+ params: ( rev:number name:string [ value:string ]
+ ( dont-care:bool ? previous-value:string ) )
+ response: ( )
+ If value is not specified, the rev-prop is removed. If dont-care is false,
+ then the rev-prop is changed only if it is currently set as previous-value
+ indicates. (If dont-care is false and previous-value is unspecified, then
+ the revision property must be previously unset.) If dont-care is true,
+ then previous-value must not be specified.
+
+ rev-proplist
+ params: ( rev:number )
+ response: ( props:proplist )
+
+ rev-prop
+ params: ( rev:number name:string )
+ response: ( [ value:string ] )
+
+ commit
+ params: ( logmsg:string ? ( ( lock-path:string lock-token:string ) ... )
+ keep-locks:bool ? rev-props:proplist )
+ response: ( )
+ Upon receiving response, client switches to editor command set.
+ Upon successful completion of edit, server sends auth-request.
+ After auth exchange completes, server sends commit-info.
+ commit-info: ( new-rev:number date:string author:string
+ ? ( post-commit-err:string ) )
+
+ get-file
+ params: ( path:string [ rev:number ] want-props:bool want-contents:bool
+ [ want-iprops:bool ] )
+ response: ( [ checksum:string ] rev:number props:proplist
+ [ inherited-props:iproplist ] )
+ If want-contents is specified, then after sending response, server
+ sends file contents as a series of strings, terminated by the empty
+ string, followed by a second empty command response to indicate
+ whether an error occurred during the sending of the file.
+
+ get-dir
+ params: ( path:string [ rev:number ] want-props:bool want-contents:bool
+ ? ( field:dirent-field ... ) [ want-iprops:bool ] )
+ response: ( rev:number props:proplist ( entry:dirent ... )
+ [ inherited-props:iproplist ] )]
+ dirent: ( name:string kind:node-kind size:number has-props:bool
+ created-rev:number [ created-date:string ]
+ [ last-author:string ] )
+ dirent-field: kind | size | has-props | created-rev | time | last-author
+ | word
+
+ check-path
+ params: ( path:string [ rev:number ] )
+ response: ( kind:node-kind )
+ If path is non-existent, 'svn_node_none' kind is returned.
+
+ stat
+ params: ( path:string [ rev:number ] )
+ response: ( ? entry:dirent )
+ dirent: ( name:string kind:node-kind size:number has-props:bool
+ created-rev:number [ created-date:string ]
+ [ last-author:string ] )
+ New in svn 1.2. If path is non-existent, an empty response is returned.
+
+ get-mergeinfo
+ params: ( ( path:string ... ) [ rev:number ] inherit:word
+ descendents:bool)
+ response: ( ( ( path:string merge-info:string ) ... ) )
+ New in svn 1.5. If no paths are specified, an empty response is
+ returned. If rev is not specified, the youngest revision is used.
+
+ update
+ params: ( [ rev:number ] target:string recurse:bool
+ ? depth:word send_copyfrom_args:bool ? ignore_ancestry:bool )
+ Client switches to report command set.
+ Upon finish-report, server sends auth-request.
+ After auth exchange completes, server switches to editor command set.
+ After edit completes, server sends response.
+ response: ( )
+
+ switch
+ params: ( [ rev:number ] target:string recurse:bool url:string
+ ? depth:word ? send_copyfrom_args:bool ignore_ancestry:bool )
+ Client switches to report command set.
+ Upon finish-report, server sends auth-request.
+ After auth exchange completes, server switches to editor command set.
+ After edit completes, server sends response.
+ response: ( )
+
+ status
+ params: ( target:string recurse:bool ? [ rev:number ] ? depth:word )
+ Client switches to report command set.
+ Upon finish-report, server sends auth-request.
+ After auth exchange completes, server switches to editor command set.
+ After edit completes, server sends response.
+ response: ( )
+
+ diff
+ params: ( [ rev:number ] target:string recurse:bool ignore-ancestry:bool
+ url:string ? text-deltas:bool ? depth:word )
+ Client switches to report command set.
+ Upon finish-report, server sends auth-request.
+ After auth exchange completes, server switches to editor command set.
+ After edit completes, server sends response.
+ response: ( )
+
+ log
+ params: ( ( target-path:string ... ) [ start-rev:number ]
+ [ end-rev:number ] changed-paths:bool strict-node:bool
+ ? limit:number
+ ? include-merged-revisions:bool
+ all-revprops | revprops ( revprop:string ... ) )
+ Before sending response, server sends log entries, ending with "done".
+ If a client does not want to specify a limit, it should send 0 as the
+ limit parameter. rev-props excludes author, date, and log; they are
+ sent separately for backwards-compatibility.
+ log-entry: ( ( change:changed-path-entry ... ) rev:number
+ [ author:string ] [ date:string ] [ message:string ]
+ ? has-children:bool invalid-revnum:bool
+ revprop-count:number rev-props:proplist
+ ? subtractive-merge:bool )
+ | done
+ changed-path-entry: ( path:string A|D|R|M
+ ? ( ? copy-path:string copy-rev:number )
+ ? ( ? node-kind:string ? text-mods:bool prop-mods:bool ) )
+ response: ( )
+
+ get-locations
+ params: ( path:string peg-rev:number ( rev:number ... ) )
+ Before sending response, server sends location entries, ending with "done".
+ location-entry: ( rev:number abs-path:number ) | done
+ response: ( )
+
+ get-location-segments
+ params: ( path:string [ start-rev:number ] [ end-rev:number ] )
+ Before sending response, server sends location entries, ending with "done".
+ location-entry: ( range-start:number range-end:number [ abs-path:string ] ) | done
+ response: ( )
+
+ get-file-revs
+ params: ( path:string [ start-rev:number ] [ end-rev:number ]
+ ? include-merged-revisions:bool )
+ Before sending response, server sends file-rev entries, ending with "done".
+ file-rev: ( path:string rev:number rev-props:proplist
+ file-props:propdelta ? merged-revision:bool )
+ | done
+ After each file-rev, the file delta is sent as one or more strings,
+ terminated by the empty string. If there is no delta, server just sends
+ the terminator.
+ response: ( )
+
+ lock
+ params: ( path:string [ comment:string ] steal-lock:bool
+ [ current-rev:number ] )
+ response: ( lock:lockdesc )
+
+ lock-many
+ params: ( [ comment:string ] steal-lock:bool ( ( path:string
+ [ current-rev:number ] ) ... ) )
+ Before sending response, server sends lock cmd status and descriptions,
+ ending with "done".
+ lock-info: ( success ( lock:lockdesc ) ) | ( failure ( err:error ) )
+ | done
+ response: ( )
+
+ unlock
+ params: ( path:string [ token:string ] break-lock:bool )
+ response: ( )
+
+ unlock-many
+ params: ( break-lock:bool ( ( path:string [ token:string ] ) ... ) )
+ Before sending response, server sends unlocked paths, ending with "done".
+ pre-response: ( success ( path:string ) ) | ( failure ( err:error ) )
+ | done
+ response: ( )
+
+ get-lock
+ params: ( path:string )
+ response: ( [ lock:lockdesc ] )
+
+ get-locks
+ params: ( path:string ? [ depth:word ] )
+ response ( ( lock:lockdesc ... ) )
+
+ replay
+ params: ( revision:number low-water-mark:number send-deltas:bool )
+ After auth exchange completes, server switches to editor command set.
+ After edit completes, server sends response.
+ response ( )
+
+ replay-range
+ params: ( start-rev:number end-rev:number low-water-mark:number
+ send-deltas:bool )
+ After auth exchange completes, server sends each revision
+ from start-rev to end-rev, alternating between sending 'revprops'
+ entries and sending the revision in the editor command set.
+ After all revisions are complete, server sends response.
+ revprops: ( revprops:word props:proplist )
+ (revprops here is the literal word "revprops".)
+ response ( )
+
+ get-deleted-rev
+ params: ( path:string peg-rev:number end-rev:number )
+ response: ( deleted-rev:number )
+
+ get-iprops
+ params: ( path:string [ rev:number ] )
+ response: ( inherited-props:iproplist )
+ New in svn 1.8. If rev is not specified, the youngest revision is used.
+
+3.1.2. Editor Command Set
+
+An edit operation produces only one response, at close-edit or
+abort-edit time. However, the consumer may write an error response at
+any time during the edit in order to terminate the edit operation
+early; the driver must notice that input is waiting on the connection,
+read the error, and send an abort-edit operation. After an error is
+returned, the consumer must read and discard editing operations until
+the abort-edit. In order to prevent TCP deadlock, the consumer must
+use non-blocking I/O to send an early error response; if writing
+blocks, the consumer must read and discard edit operations until
+writing unblocks or it reads an abort-edit.
+
+ target-rev
+ params: ( rev:number )
+
+ open-root
+ params: ( [ rev:number ] root-token:string )
+
+ delete-entry
+ params: ( path:string rev:number dir-token:string )
+
+ add-dir
+ params: ( path:string parent-token:string child-token:string
+ [ copy-path:string copy-rev:number ] )
+
+ open-dir
+ params: ( path:string parent-token:string child-token:string rev:number )
+
+ change-dir-prop
+ params: ( dir-token:string name:string [ value:string ] )
+
+ close-dir
+ params: ( dir-token:string )
+
+ absent-dir
+ params: ( path:string parent-token:string )
+
+ add-file
+ params: ( path:string dir-token:string file-token:string
+ [ copy-path:string copy-rev:number ] )
+
+ open-file
+ params: ( path:string dir-token:string file-token:string rev:number )
+
+ apply-textdelta
+ params: ( file-token:string [ base-checksum:string ] )
+
+ textdelta-chunk
+ params: ( file-token:string chunk:string )
+
+ textdelta-end
+ params: ( file-token:string )
+
+ change-file-prop
+ params: ( file-token:string name:string [ value:string ] )
+
+ close-file
+ params: ( file-token:string [ text-checksum:string ] )
+
+ absent-file
+ params: ( path:string parent-token:string )
+
+ close-edit
+ params: ( )
+ response: ( )
+
+ abort-edit
+ params: ( )
+ response: ( )
+
+ finish-replay
+ params: ( )
+ Only delivered from server to client, at the end of a replay.
+
+3.1.3. Report Command Set
+
+To reduce round-trip delays, report commands do not return responses.
+Any errors resulting from a report call will be returned to the client
+by the command which invoked the report (following an abort-edit
+call). Errors resulting from an abort-report call are ignored.
+
+ set-path:
+ params: ( path:string rev:number start-empty:bool
+ ? [ lock-token:string ] ? depth:word )
+
+ delete-path:
+ params: ( path:string )
+
+ link-path:
+ params: ( path:string url:string rev:number start-empty:bool
+ ? [ lock-token:string ] ? depth:word )
+
+ finish-report:
+ params: ( )
+
+ abort-report
+ params: ( )
+
+4. Extensibility
+----------------
+
+This protocol may be extended in three ways, in decreasing order of
+desirability:
+
+ * Items may be added to any tuple. An old implementation will
+ ignore the extra items.
+
+ * Named extensions may be expressed at connection initiation time
+ by the client or server.
+
+ * The protocol version may be bumped. Clients and servers can then
+ choose to any range of protocol versions.
+
+4.1. Extending existing commands
+
+Extending an existing command is normally done by indicating that its
+tuple is allowed to end where it currently ends, for backwards
+compatibility, and then tacking on a new, possibly optional, item.
+
+For example, diff was extended to include a new mandatory text-deltas
+parameter like this:
+
+ /* OLD */ diff:
+ params: ( [ rev:number ] target:string recurse:bool ignore-ancestry:bool
+ url:string )
+ /* NEW */ diff:
+ params: ( [ rev:number ] target:string recurse:bool ignore-ancestry:bool
+ url:string ? text-deltas:bool )
+
+The "?" says that the tuple is allowed to end here, because an old
+client or server wouldn't know to send the new item.
+
+For optional parameters, a slightly different approach must be used.
+set-path was extended to include lock-tokens like this:
+
+ /* OLD */ set-path:
+ params: ( path:string rev:number start-empty:bool )
+
+ /* NEW */ set-path:
+ params: ( path:string rev:number start-empty:bool ? [ lock-token:string ] )
+
+The new item appears in brackets because, even in the new protocol,
+the lock-token is still optional. However, if there's no lock-token
+to send, an empty tuple must still be transmitted so that future
+extensions to this command remain possible.
diff --git a/contrib/subversion/subversion/libsvn_ra_svn/ra_svn.h b/contrib/subversion/subversion/libsvn_ra_svn/ra_svn.h
new file mode 100644
index 0000000..dc70eb7
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_svn/ra_svn.h
@@ -0,0 +1,249 @@
+/*
+ * ra_svn.h : private declarations for the ra_svn module
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#ifndef RA_SVN_H
+#define RA_SVN_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include <apr_network_io.h>
+#include <apr_file_io.h>
+#include <apr_thread_proc.h>
+#include "svn_ra.h"
+#include "svn_ra_svn.h"
+
+#include "private/svn_ra_svn_private.h"
+
+/* Callback function that indicates if a svn_ra_svn__stream_t has pending
+ * data.
+ */
+typedef svn_boolean_t (*ra_svn_pending_fn_t)(void *baton);
+
+/* Callback function that sets the timeout value for a svn_ra_svn__stream_t. */
+typedef void (*ra_svn_timeout_fn_t)(void *baton, apr_interval_time_t timeout);
+
+/* A stream abstraction for ra_svn.
+ *
+ * This is different from svn_stream_t in that it provides timeouts and
+ * the ability to check for pending data.
+ */
+typedef struct svn_ra_svn__stream_st svn_ra_svn__stream_t;
+
+/* Handler for blocked writes. */
+typedef svn_error_t *(*ra_svn_block_handler_t)(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ void *baton);
+
+/* The default "user agent". */
+#define SVN_RA_SVN__DEFAULT_USERAGENT "SVN/" SVN_VER_NUMBER\
+ " (" SVN_BUILD_TARGET ")"
+
+/* The size of our per-connection read and write buffers. */
+#define SVN_RA_SVN__PAGE_SIZE 4096
+#define SVN_RA_SVN__READBUF_SIZE (4 * SVN_RA_SVN__PAGE_SIZE)
+#define SVN_RA_SVN__WRITEBUF_SIZE (4 * SVN_RA_SVN__PAGE_SIZE)
+
+/* Create forward reference */
+typedef struct svn_ra_svn__session_baton_t svn_ra_svn__session_baton_t;
+
+/* This structure is opaque to the server. The client pokes at the
+ * first few fields during setup and cleanup. */
+struct svn_ra_svn_conn_st {
+
+ /* I/O buffers */
+ char write_buf[SVN_RA_SVN__WRITEBUF_SIZE];
+ char read_buf[SVN_RA_SVN__READBUF_SIZE];
+ char *read_ptr;
+ char *read_end;
+ apr_size_t write_pos;
+
+ svn_ra_svn__stream_t *stream;
+ svn_ra_svn__session_baton_t *session;
+#ifdef SVN_HAVE_SASL
+ /* Although all reads and writes go through the svn_ra_svn__stream_t
+ interface, SASL still needs direct access to the underlying socket
+ for stuff like IP addresses and port numbers. */
+ apr_socket_t *sock;
+ svn_boolean_t encrypted;
+#endif
+
+ /* abortion check control */
+ apr_size_t written_since_error_check;
+ apr_size_t error_check_interval;
+ svn_boolean_t may_check_for_error;
+
+ /* repository info */
+ const char *uuid;
+ const char *repos_root;
+
+ /* TX block notification target */
+ ra_svn_block_handler_t block_handler;
+ void *block_baton;
+
+ /* server settings */
+ apr_hash_t *capabilities;
+ int compression_level;
+ apr_size_t zero_copy_limit;
+
+ /* who's on the other side of the connection? */
+ char *remote_ip;
+
+ /* EV2 support*/
+ svn_delta_shim_callbacks_t *shim_callbacks;
+
+ /* our pool */
+ apr_pool_t *pool;
+};
+
+struct svn_ra_svn__session_baton_t {
+ apr_pool_t *pool;
+ svn_ra_svn_conn_t *conn;
+ svn_boolean_t is_tunneled;
+ const char *url;
+ const char *user;
+ const char *hostname; /* The remote hostname. */
+ const char *realm_prefix;
+ const char **tunnel_argv;
+ const svn_ra_callbacks2_t *callbacks;
+ void *callbacks_baton;
+ apr_off_t bytes_read, bytes_written; /* apr_off_t's because that's what
+ the callback interface uses */
+ const char *useragent;
+};
+
+/* Set a callback for blocked writes on conn. This handler may
+ * perform reads on the connection in order to prevent deadlock due to
+ * pipelining. If callback is NULL, the connection goes back to
+ * normal blocking I/O for writes.
+ */
+void svn_ra_svn__set_block_handler(svn_ra_svn_conn_t *conn,
+ ra_svn_block_handler_t callback,
+ void *baton);
+
+/* Return true if there is input waiting on conn. */
+svn_boolean_t svn_ra_svn__input_waiting(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/* CRAM-MD5 client implementation. */
+svn_error_t *svn_ra_svn__cram_client(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char *user, const char *password,
+ const char **message);
+
+/* Return a pointer to the error chain child of ERR which contains the
+ * first "real" error message, not merely one of the
+ * SVN_ERR_RA_SVN_CMD_ERR wrapper errors. */
+svn_error_t *svn_ra_svn__locate_real_error_child(svn_error_t *err);
+
+/* Return an error chain based on @a params (which contains a
+ * command response indicating failure). The error chain will be
+ * in the same order as the errors indicated in @a params. Use
+ * @a pool for temporary allocations. */
+svn_error_t *svn_ra_svn__handle_failure_status(const apr_array_header_t *params,
+ apr_pool_t *pool);
+
+/* Returns a stream that reads/writes from/to SOCK. */
+svn_ra_svn__stream_t *svn_ra_svn__stream_from_sock(apr_socket_t *sock,
+ apr_pool_t *pool);
+
+/* Returns a stream that reads from IN_FILE and writes to OUT_FILE. */
+svn_ra_svn__stream_t *svn_ra_svn__stream_from_files(apr_file_t *in_file,
+ apr_file_t *out_file,
+ apr_pool_t *pool);
+
+/* Create an svn_ra_svn__stream_t using READ_CB, WRITE_CB, TIMEOUT_CB,
+ * PENDING_CB, and BATON.
+ */
+svn_ra_svn__stream_t *svn_ra_svn__stream_create(void *baton,
+ svn_read_fn_t read_cb,
+ svn_write_fn_t write_cb,
+ ra_svn_timeout_fn_t timeout_cb,
+ ra_svn_pending_fn_t pending_cb,
+ apr_pool_t *pool);
+
+/* Write *LEN bytes from DATA to STREAM, returning the number of bytes
+ * written in *LEN.
+ */
+svn_error_t *svn_ra_svn__stream_write(svn_ra_svn__stream_t *stream,
+ const char *data, apr_size_t *len);
+
+/* Read *LEN bytes from STREAM into DATA, returning the number of bytes
+ * read in *LEN.
+ */
+svn_error_t *svn_ra_svn__stream_read(svn_ra_svn__stream_t *stream,
+ char *data, apr_size_t *len);
+
+/* Read the command word from CONN, return it in *COMMAND and skip to the
+ * end of the command. Allocate data in POOL.
+ */
+svn_error_t *svn_ra_svn__read_command_only(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char **command);
+
+/* Set the timeout for operations on STREAM to INTERVAL. */
+void svn_ra_svn__stream_timeout(svn_ra_svn__stream_t *stream,
+ apr_interval_time_t interval);
+
+/* Return whether or not there is data pending on STREAM. */
+svn_boolean_t svn_ra_svn__stream_pending(svn_ra_svn__stream_t *stream);
+
+/* Respond to an auth request and perform authentication. Use the Cyrus
+ * SASL library for mechanism negotiation and for creating authentication
+ * tokens. */
+svn_error_t *
+svn_ra_svn__do_cyrus_auth(svn_ra_svn__session_baton_t *sess,
+ const apr_array_header_t *mechlist,
+ const char *realm, apr_pool_t *pool);
+
+/* Same as svn_ra_svn__do_cyrus_auth, but uses the built-in implementation of
+ * the CRAM-MD5, ANONYMOUS and EXTERNAL mechanisms. Return the error
+ * SVN_ERR_RA_SVN_NO_MECHANSIMS if we cannot negotiate an authentication
+ * mechanism with the server. */
+svn_error_t *
+svn_ra_svn__do_internal_auth(svn_ra_svn__session_baton_t *sess,
+ const apr_array_header_t *mechlist,
+ const char *realm, apr_pool_t *pool);
+
+/* Having picked a mechanism, start authentication by writing out an
+ * auth response. MECH_ARG may be NULL for mechanisms with no
+ * initial client response. */
+svn_error_t *svn_ra_svn__auth_response(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *mech, const char *mech_arg);
+
+/* Looks for MECH as a word in MECHLIST (an array of svn_ra_svn_item_t). */
+svn_boolean_t svn_ra_svn__find_mech(const apr_array_header_t *mechlist,
+ const char *mech);
+
+/* Initialize the SASL library. */
+svn_error_t *svn_ra_svn__sasl_init(void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* RA_SVN_H */
diff --git a/contrib/subversion/subversion/libsvn_ra_svn/streams.c b/contrib/subversion/subversion/libsvn_ra_svn/streams.c
new file mode 100644
index 0000000..4ae93d5
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_svn/streams.c
@@ -0,0 +1,255 @@
+/*
+ * streams.c : stream encapsulation routines for the ra_svn protocol
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_general.h>
+#include <apr_network_io.h>
+#include <apr_poll.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_io.h"
+#include "svn_private_config.h"
+
+#include "ra_svn.h"
+
+struct svn_ra_svn__stream_st {
+ svn_stream_t *stream;
+ void *baton;
+ ra_svn_pending_fn_t pending_fn;
+ ra_svn_timeout_fn_t timeout_fn;
+};
+
+typedef struct sock_baton_t {
+ apr_socket_t *sock;
+ apr_pool_t *pool;
+} sock_baton_t;
+
+typedef struct file_baton_t {
+ apr_file_t *in_file;
+ apr_file_t *out_file;
+ apr_pool_t *pool;
+} file_baton_t;
+
+/* Returns TRUE if PFD has pending data, FALSE otherwise. */
+static svn_boolean_t pending(apr_pollfd_t *pfd, apr_pool_t *pool)
+{
+ apr_status_t status;
+ int n;
+
+ pfd->p = pool;
+ pfd->reqevents = APR_POLLIN;
+ status = apr_poll(pfd, 1, &n, 0);
+ return (status == APR_SUCCESS && n);
+}
+
+/* Functions to implement a file backed svn_ra_svn__stream_t. */
+
+/* Implements svn_read_fn_t */
+static svn_error_t *
+file_read_cb(void *baton, char *buffer, apr_size_t *len)
+{
+ file_baton_t *b = baton;
+ apr_status_t status = apr_file_read(b->in_file, buffer, len);
+
+ if (status && !APR_STATUS_IS_EOF(status))
+ return svn_error_wrap_apr(status, _("Can't read from connection"));
+ if (*len == 0)
+ return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, NULL);
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_write_fn_t */
+static svn_error_t *
+file_write_cb(void *baton, const char *buffer, apr_size_t *len)
+{
+ file_baton_t *b = baton;
+ apr_status_t status = apr_file_write(b->out_file, buffer, len);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't write to connection"));
+ return SVN_NO_ERROR;
+}
+
+/* Implements ra_svn_timeout_fn_t */
+static void
+file_timeout_cb(void *baton, apr_interval_time_t interval)
+{
+ file_baton_t *b = baton;
+ apr_file_pipe_timeout_set(b->out_file, interval);
+}
+
+/* Implements ra_svn_pending_fn_t */
+static svn_boolean_t
+file_pending_cb(void *baton)
+{
+ file_baton_t *b = baton;
+ apr_pollfd_t pfd;
+
+ pfd.desc_type = APR_POLL_FILE;
+ pfd.desc.f = b->in_file;
+
+ return pending(&pfd, b->pool);
+}
+
+svn_ra_svn__stream_t *
+svn_ra_svn__stream_from_files(apr_file_t *in_file,
+ apr_file_t *out_file,
+ apr_pool_t *pool)
+{
+ file_baton_t *b = apr_palloc(pool, sizeof(*b));
+
+ b->in_file = in_file;
+ b->out_file = out_file;
+ b->pool = pool;
+
+ return svn_ra_svn__stream_create(b, file_read_cb, file_write_cb,
+ file_timeout_cb, file_pending_cb,
+ pool);
+}
+
+/* Functions to implement a socket backed svn_ra_svn__stream_t. */
+
+/* Implements svn_read_fn_t */
+static svn_error_t *
+sock_read_cb(void *baton, char *buffer, apr_size_t *len)
+{
+ sock_baton_t *b = baton;
+ apr_status_t status;
+ apr_interval_time_t interval;
+
+ status = apr_socket_timeout_get(b->sock, &interval);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't get socket timeout"));
+
+ /* Always block on read.
+ * During pipelining, we set the timeout to 0 for some write
+ * operations so that we can try them without blocking. If APR had
+ * separate timeouts for read and write, we would only set the
+ * write timeout, but it doesn't. So here, we revert back to blocking.
+ */
+ apr_socket_timeout_set(b->sock, -1);
+ status = apr_socket_recv(b->sock, buffer, len);
+ apr_socket_timeout_set(b->sock, interval);
+
+ if (status && !APR_STATUS_IS_EOF(status))
+ return svn_error_wrap_apr(status, _("Can't read from connection"));
+ if (*len == 0)
+ return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, NULL);
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_write_fn_t */
+static svn_error_t *
+sock_write_cb(void *baton, const char *buffer, apr_size_t *len)
+{
+ sock_baton_t *b = baton;
+ apr_status_t status = apr_socket_send(b->sock, buffer, len);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't write to connection"));
+ return SVN_NO_ERROR;
+}
+
+/* Implements ra_svn_timeout_fn_t */
+static void
+sock_timeout_cb(void *baton, apr_interval_time_t interval)
+{
+ sock_baton_t *b = baton;
+ apr_socket_timeout_set(b->sock, interval);
+}
+
+/* Implements ra_svn_pending_fn_t */
+static svn_boolean_t
+sock_pending_cb(void *baton)
+{
+ sock_baton_t *b = baton;
+ apr_pollfd_t pfd;
+
+ pfd.desc_type = APR_POLL_SOCKET;
+ pfd.desc.s = b->sock;
+
+ return pending(&pfd, b->pool);
+}
+
+svn_ra_svn__stream_t *
+svn_ra_svn__stream_from_sock(apr_socket_t *sock,
+ apr_pool_t *pool)
+{
+ sock_baton_t *b = apr_palloc(pool, sizeof(*b));
+
+ b->sock = sock;
+ b->pool = pool;
+
+ return svn_ra_svn__stream_create(b, sock_read_cb, sock_write_cb,
+ sock_timeout_cb, sock_pending_cb,
+ pool);
+}
+
+svn_ra_svn__stream_t *
+svn_ra_svn__stream_create(void *baton,
+ svn_read_fn_t read_cb,
+ svn_write_fn_t write_cb,
+ ra_svn_timeout_fn_t timeout_cb,
+ ra_svn_pending_fn_t pending_cb,
+ apr_pool_t *pool)
+{
+ svn_ra_svn__stream_t *s = apr_palloc(pool, sizeof(*s));
+ s->stream = svn_stream_empty(pool);
+ svn_stream_set_baton(s->stream, baton);
+ if (read_cb)
+ svn_stream_set_read(s->stream, read_cb);
+ if (write_cb)
+ svn_stream_set_write(s->stream, write_cb);
+ s->baton = baton;
+ s->timeout_fn = timeout_cb;
+ s->pending_fn = pending_cb;
+ return s;
+}
+
+svn_error_t *
+svn_ra_svn__stream_write(svn_ra_svn__stream_t *stream,
+ const char *data, apr_size_t *len)
+{
+ return svn_stream_write(stream->stream, data, len);
+}
+
+svn_error_t *
+svn_ra_svn__stream_read(svn_ra_svn__stream_t *stream, char *data,
+ apr_size_t *len)
+{
+ return svn_stream_read(stream->stream, data, len);
+}
+
+void
+svn_ra_svn__stream_timeout(svn_ra_svn__stream_t *stream,
+ apr_interval_time_t interval)
+{
+ stream->timeout_fn(stream->baton, interval);
+}
+
+svn_boolean_t
+svn_ra_svn__stream_pending(svn_ra_svn__stream_t *stream)
+{
+ return stream->pending_fn(stream->baton);
+}
diff --git a/contrib/subversion/subversion/libsvn_ra_svn/version.c b/contrib/subversion/subversion/libsvn_ra_svn/version.c
new file mode 100644
index 0000000..251a4c1
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_ra_svn/version.c
@@ -0,0 +1,33 @@
+/*
+ * version.c: library version number
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_version.h"
+#include "svn_ra_svn.h"
+
+const svn_version_t *
+svn_ra_svn_version(void)
+{
+ SVN_VERSION_BODY;
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/authz.c b/contrib/subversion/subversion/libsvn_repos/authz.c
new file mode 100644
index 0000000..af4a1f2
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/authz.c
@@ -0,0 +1,1075 @@
+/* authz.c : path-based access control
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+/*** Includes. ***/
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_repos.h"
+#include "svn_config.h"
+#include "svn_ctype.h"
+#include "private/svn_fspath.h"
+#include "repos.h"
+
+
+/*** Structures. ***/
+
+/* Information for the config enumerators called during authz
+ lookup. */
+struct authz_lookup_baton {
+ /* The authz configuration. */
+ svn_config_t *config;
+
+ /* The user to authorize. */
+ const char *user;
+
+ /* Explicitly granted rights. */
+ svn_repos_authz_access_t allow;
+ /* Explicitly denied rights. */
+ svn_repos_authz_access_t deny;
+
+ /* The rights required by the caller of the lookup. */
+ svn_repos_authz_access_t required_access;
+
+ /* The following are used exclusively in recursive lookups. */
+
+ /* The path in the repository (an fspath) to authorize. */
+ const char *repos_path;
+ /* repos_path prefixed by the repository name and a colon. */
+ const char *qualified_repos_path;
+
+ /* Whether, at the end of a recursive lookup, access is granted. */
+ svn_boolean_t access;
+};
+
+/* Information for the config enumeration functions called during the
+ validation process. */
+struct authz_validate_baton {
+ svn_config_t *config; /* The configuration file being validated. */
+ svn_error_t *err; /* The error being thrown out of the
+ enumerator, if any. */
+};
+
+/* Currently this structure is just a wrapper around a
+ svn_config_t. */
+struct svn_authz_t
+{
+ svn_config_t *cfg;
+};
+
+
+
+/*** Checking access. ***/
+
+/* Determine whether the REQUIRED access is granted given what authz
+ * to ALLOW or DENY. Return TRUE if the REQUIRED access is
+ * granted.
+ *
+ * Access is granted either when no required access is explicitly
+ * denied (implicit grant), or when the required access is explicitly
+ * granted, overriding any denials.
+ */
+static svn_boolean_t
+authz_access_is_granted(svn_repos_authz_access_t allow,
+ svn_repos_authz_access_t deny,
+ svn_repos_authz_access_t required)
+{
+ svn_repos_authz_access_t stripped_req =
+ required & (svn_authz_read | svn_authz_write);
+
+ if ((deny & required) == svn_authz_none)
+ return TRUE;
+ else if ((allow & required) == stripped_req)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+
+/* Decide whether the REQUIRED access has been conclusively
+ * determined. Return TRUE if the given ALLOW/DENY authz are
+ * conclusive regarding the REQUIRED authz.
+ *
+ * Conclusive determination occurs when any of the REQUIRED authz are
+ * granted or denied by ALLOW/DENY.
+ */
+static svn_boolean_t
+authz_access_is_determined(svn_repos_authz_access_t allow,
+ svn_repos_authz_access_t deny,
+ svn_repos_authz_access_t required)
+{
+ if ((deny & required) || (allow & required))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+/* Return TRUE is USER equals ALIAS. The alias definitions are in the
+ "aliases" sections of CFG. Use POOL for temporary allocations during
+ the lookup. */
+static svn_boolean_t
+authz_alias_is_user(svn_config_t *cfg,
+ const char *alias,
+ const char *user,
+ apr_pool_t *pool)
+{
+ const char *value;
+
+ svn_config_get(cfg, &value, "aliases", alias, NULL);
+ if (!value)
+ return FALSE;
+
+ if (strcmp(value, user) == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+/* Return TRUE if USER is in GROUP. The group definitions are in the
+ "groups" section of CFG. Use POOL for temporary allocations during
+ the lookup. */
+static svn_boolean_t
+authz_group_contains_user(svn_config_t *cfg,
+ const char *group,
+ const char *user,
+ apr_pool_t *pool)
+{
+ const char *value;
+ apr_array_header_t *list;
+ int i;
+
+ svn_config_get(cfg, &value, "groups", group, NULL);
+
+ list = svn_cstring_split(value, ",", TRUE, pool);
+
+ for (i = 0; i < list->nelts; i++)
+ {
+ const char *group_user = APR_ARRAY_IDX(list, i, char *);
+
+ /* If the 'user' is a subgroup, recurse into it. */
+ if (*group_user == '@')
+ {
+ if (authz_group_contains_user(cfg, &group_user[1],
+ user, pool))
+ return TRUE;
+ }
+
+ /* If the 'user' is an alias, verify it. */
+ else if (*group_user == '&')
+ {
+ if (authz_alias_is_user(cfg, &group_user[1],
+ user, pool))
+ return TRUE;
+ }
+
+ /* If the user matches, stop. */
+ else if (strcmp(user, group_user) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/* Determines whether an authz rule applies to the current
+ * user, given the name part of the rule's name-value pair
+ * in RULE_MATCH_STRING and the authz_lookup_baton object
+ * B with the username in question.
+ */
+static svn_boolean_t
+authz_line_applies_to_user(const char *rule_match_string,
+ struct authz_lookup_baton *b,
+ apr_pool_t *pool)
+{
+ /* If the rule has an inversion, recurse and invert the result. */
+ if (rule_match_string[0] == '~')
+ return !authz_line_applies_to_user(&rule_match_string[1], b, pool);
+
+ /* Check for special tokens. */
+ if (strcmp(rule_match_string, "$anonymous") == 0)
+ return (b->user == NULL);
+ if (strcmp(rule_match_string, "$authenticated") == 0)
+ return (b->user != NULL);
+
+ /* Check for a wildcard rule. */
+ if (strcmp(rule_match_string, "*") == 0)
+ return TRUE;
+
+ /* If we get here, then the rule is:
+ * - Not an inversion rule.
+ * - Not an authz token rule.
+ * - Not a wildcard rule.
+ *
+ * All that's left over is regular user or group specifications.
+ */
+
+ /* If the session is anonymous, then a user/group
+ * rule definitely won't match.
+ */
+ if (b->user == NULL)
+ return FALSE;
+
+ /* Process the rule depending on whether it is
+ * a user, alias or group rule.
+ */
+ if (rule_match_string[0] == '@')
+ return authz_group_contains_user(
+ b->config, &rule_match_string[1], b->user, pool);
+ else if (rule_match_string[0] == '&')
+ return authz_alias_is_user(
+ b->config, &rule_match_string[1], b->user, pool);
+ else
+ return (strcmp(b->user, rule_match_string) == 0);
+}
+
+
+/* Callback to parse one line of an authz file and update the
+ * authz_baton accordingly.
+ */
+static svn_boolean_t
+authz_parse_line(const char *name, const char *value,
+ void *baton, apr_pool_t *pool)
+{
+ struct authz_lookup_baton *b = baton;
+
+ /* Stop if the rule doesn't apply to this user. */
+ if (!authz_line_applies_to_user(name, b, pool))
+ return TRUE;
+
+ /* Set the access grants for the rule. */
+ if (strchr(value, 'r'))
+ b->allow |= svn_authz_read;
+ else
+ b->deny |= svn_authz_read;
+
+ if (strchr(value, 'w'))
+ b->allow |= svn_authz_write;
+ else
+ b->deny |= svn_authz_write;
+
+ return TRUE;
+}
+
+
+/* Return TRUE iff the access rules in SECTION_NAME apply to PATH_SPEC
+ * (which is a repository name, colon, and repository fspath, such as
+ * "myrepos:/trunk/foo").
+ */
+static svn_boolean_t
+is_applicable_section(const char *path_spec,
+ const char *section_name)
+{
+ apr_size_t path_spec_len = strlen(path_spec);
+
+ return ((strncmp(path_spec, section_name, path_spec_len) == 0)
+ && (path_spec[path_spec_len - 1] == '/'
+ || section_name[path_spec_len] == '/'
+ || section_name[path_spec_len] == '\0'));
+}
+
+
+/* Callback to parse a section and update the authz_baton if the
+ * section denies access to the subtree the baton describes.
+ */
+static svn_boolean_t
+authz_parse_section(const char *section_name, void *baton, apr_pool_t *pool)
+{
+ struct authz_lookup_baton *b = baton;
+ svn_boolean_t conclusive;
+
+ /* Does the section apply to us? */
+ if (!is_applicable_section(b->qualified_repos_path, section_name)
+ && !is_applicable_section(b->repos_path, section_name))
+ return TRUE;
+
+ /* Work out what this section grants. */
+ b->allow = b->deny = 0;
+ svn_config_enumerate2(b->config, section_name,
+ authz_parse_line, b, pool);
+
+ /* Has the section explicitly determined an access? */
+ conclusive = authz_access_is_determined(b->allow, b->deny,
+ b->required_access);
+
+ /* Is access granted OR inconclusive? */
+ b->access = authz_access_is_granted(b->allow, b->deny,
+ b->required_access)
+ || !conclusive;
+
+ /* As long as access isn't conclusively denied, carry on. */
+ return b->access;
+}
+
+
+/* Validate access to the given user for the given path. This
+ * function checks rules for exactly the given path, and first tries
+ * to access a section specific to the given repository before falling
+ * back to pan-repository rules.
+ *
+ * Update *access_granted to inform the caller of the outcome of the
+ * lookup. Return a boolean indicating whether the access rights were
+ * successfully determined.
+ */
+static svn_boolean_t
+authz_get_path_access(svn_config_t *cfg, const char *repos_name,
+ const char *path, const char *user,
+ svn_repos_authz_access_t required_access,
+ svn_boolean_t *access_granted,
+ apr_pool_t *pool)
+{
+ const char *qualified_path;
+ struct authz_lookup_baton baton = { 0 };
+
+ baton.config = cfg;
+ baton.user = user;
+
+ /* Try to locate a repository-specific block first. */
+ qualified_path = apr_pstrcat(pool, repos_name, ":", path, (char *)NULL);
+ svn_config_enumerate2(cfg, qualified_path,
+ authz_parse_line, &baton, pool);
+
+ *access_granted = authz_access_is_granted(baton.allow, baton.deny,
+ required_access);
+
+ /* If the first test has determined access, stop now. */
+ if (authz_access_is_determined(baton.allow, baton.deny,
+ required_access))
+ return TRUE;
+
+ /* No repository specific rule, try pan-repository rules. */
+ svn_config_enumerate2(cfg, path, authz_parse_line, &baton, pool);
+
+ *access_granted = authz_access_is_granted(baton.allow, baton.deny,
+ required_access);
+ return authz_access_is_determined(baton.allow, baton.deny,
+ required_access);
+}
+
+
+/* Validate access to the given user for the subtree starting at the
+ * given path. This function walks the whole authz file in search of
+ * rules applying to paths in the requested subtree which deny the
+ * requested access.
+ *
+ * As soon as one is found, or else when the whole ACL file has been
+ * searched, return the updated authorization status.
+ */
+static svn_boolean_t
+authz_get_tree_access(svn_config_t *cfg, const char *repos_name,
+ const char *path, const char *user,
+ svn_repos_authz_access_t required_access,
+ apr_pool_t *pool)
+{
+ struct authz_lookup_baton baton = { 0 };
+
+ baton.config = cfg;
+ baton.user = user;
+ baton.required_access = required_access;
+ baton.repos_path = path;
+ baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
+ ":", path, (char *)NULL);
+ /* Default to access granted if no rules say otherwise. */
+ baton.access = TRUE;
+
+ svn_config_enumerate_sections2(cfg, authz_parse_section,
+ &baton, pool);
+
+ return baton.access;
+}
+
+
+/* Callback to parse sections of the configuration file, looking for
+ any kind of granted access. Implements the
+ svn_config_section_enumerator2_t interface. */
+static svn_boolean_t
+authz_get_any_access_parser_cb(const char *section_name, void *baton,
+ apr_pool_t *pool)
+{
+ struct authz_lookup_baton *b = baton;
+
+ /* Does the section apply to the query? */
+ if (section_name[0] == '/'
+ || strncmp(section_name, b->qualified_repos_path,
+ strlen(b->qualified_repos_path)) == 0)
+ {
+ b->allow = b->deny = svn_authz_none;
+
+ svn_config_enumerate2(b->config, section_name,
+ authz_parse_line, baton, pool);
+ b->access = authz_access_is_granted(b->allow, b->deny,
+ b->required_access);
+
+ /* Continue as long as we don't find a determined, granted access. */
+ return !(b->access
+ && authz_access_is_determined(b->allow, b->deny,
+ b->required_access));
+ }
+
+ return TRUE;
+}
+
+
+/* Walk through the authz CFG to check if USER has the REQUIRED_ACCESS
+ * to any path within the REPOSITORY. Return TRUE if so. Use POOL
+ * for temporary allocations. */
+static svn_boolean_t
+authz_get_any_access(svn_config_t *cfg, const char *repos_name,
+ const char *user,
+ svn_repos_authz_access_t required_access,
+ apr_pool_t *pool)
+{
+ struct authz_lookup_baton baton = { 0 };
+
+ baton.config = cfg;
+ baton.user = user;
+ baton.required_access = required_access;
+ baton.access = FALSE; /* Deny access by default. */
+ baton.repos_path = "/";
+ baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
+ ":/", (char *)NULL);
+
+ /* We could have used svn_config_enumerate2 for "repos_name:/".
+ * However, this requires access for root explicitly (which the user
+ * may not always have). So we end up enumerating the sections in
+ * the authz CFG and stop on the first match with some access for
+ * this user. */
+ svn_config_enumerate_sections2(cfg, authz_get_any_access_parser_cb,
+ &baton, pool);
+
+ /* If walking the configuration was inconclusive, deny access. */
+ if (!authz_access_is_determined(baton.allow,
+ baton.deny, baton.required_access))
+ return FALSE;
+
+ return baton.access;
+}
+
+
+
+/*** Validating the authz file. ***/
+
+/* Check for errors in GROUP's definition of CFG. The errors
+ * detected are references to non-existent groups and circular
+ * dependencies between groups. If an error is found, return
+ * SVN_ERR_AUTHZ_INVALID_CONFIG. Use POOL for temporary
+ * allocations only.
+ *
+ * CHECKED_GROUPS should be an empty (it is used for recursive calls).
+ */
+static svn_error_t *
+authz_group_walk(svn_config_t *cfg,
+ const char *group,
+ apr_hash_t *checked_groups,
+ apr_pool_t *pool)
+{
+ const char *value;
+ apr_array_header_t *list;
+ int i;
+
+ svn_config_get(cfg, &value, "groups", group, NULL);
+ /* Having a non-existent group in the ACL configuration might be the
+ sign of a typo. Refuse to perform authz on uncertain rules. */
+ if (!value)
+ return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "An authz rule refers to group '%s', "
+ "which is undefined",
+ group);
+
+ list = svn_cstring_split(value, ",", TRUE, pool);
+
+ for (i = 0; i < list->nelts; i++)
+ {
+ const char *group_user = APR_ARRAY_IDX(list, i, char *);
+
+ /* If the 'user' is a subgroup, recurse into it. */
+ if (*group_user == '@')
+ {
+ /* A circular dependency between groups is a Bad Thing. We
+ don't do authz with invalid ACL files. */
+ if (svn_hash_gets(checked_groups, &group_user[1]))
+ return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG,
+ NULL,
+ "Circular dependency between "
+ "groups '%s' and '%s'",
+ &group_user[1], group);
+
+ /* Add group to hash of checked groups. */
+ svn_hash_sets(checked_groups, &group_user[1], "");
+
+ /* Recurse on that group. */
+ SVN_ERR(authz_group_walk(cfg, &group_user[1],
+ checked_groups, pool));
+
+ /* Remove group from hash of checked groups, so that we don't
+ incorrectly report an error if we see it again as part of
+ another group. */
+ svn_hash_sets(checked_groups, &group_user[1], NULL);
+ }
+ else if (*group_user == '&')
+ {
+ const char *alias;
+
+ svn_config_get(cfg, &alias, "aliases", &group_user[1], NULL);
+ /* Having a non-existent alias in the ACL configuration might be the
+ sign of a typo. Refuse to perform authz on uncertain rules. */
+ if (!alias)
+ return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "An authz rule refers to alias '%s', "
+ "which is undefined",
+ &group_user[1]);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Callback to perform some simple sanity checks on an authz rule.
+ *
+ * - If RULE_MATCH_STRING references a group or an alias, verify that
+ * the group or alias definition exists.
+ * - If RULE_MATCH_STRING specifies a token (starts with $), verify
+ * that the token name is valid.
+ * - If RULE_MATCH_STRING is using inversion, verify that it isn't
+ * doing it more than once within the one rule, and that it isn't
+ * "~*", as that would never match.
+ * - Check that VALUE part of the rule specifies only allowed rule
+ * flag characters ('r' and 'w').
+ *
+ * Return TRUE if the rule has no errors. Use BATON for context and
+ * error reporting.
+ */
+static svn_boolean_t authz_validate_rule(const char *rule_match_string,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const char *val;
+ const char *match = rule_match_string;
+ struct authz_validate_baton *b = baton;
+
+ /* Make sure the user isn't using double-negatives. */
+ if (match[0] == '~')
+ {
+ /* Bump the pointer past the inversion for the other checks. */
+ match++;
+
+ /* Another inversion is a double negative; we can't not stop. */
+ if (match[0] == '~')
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Rule '%s' has more than one "
+ "inversion; double negatives are "
+ "not permitted.",
+ rule_match_string);
+ return FALSE;
+ }
+
+ /* Make sure that the rule isn't "~*", which won't ever match. */
+ if (strcmp(match, "*") == 0)
+ {
+ b->err = svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Authz rules with match string '~*' "
+ "are not allowed, because they never "
+ "match anyone.");
+ return FALSE;
+ }
+ }
+
+ /* If the rule applies to a group, check its existence. */
+ if (match[0] == '@')
+ {
+ const char *group = &match[1];
+
+ svn_config_get(b->config, &val, "groups", group, NULL);
+
+ /* Having a non-existent group in the ACL configuration might be
+ the sign of a typo. Refuse to perform authz on uncertain
+ rules. */
+ if (!val)
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "An authz rule refers to group "
+ "'%s', which is undefined",
+ rule_match_string);
+ return FALSE;
+ }
+ }
+
+ /* If the rule applies to an alias, check its existence. */
+ if (match[0] == '&')
+ {
+ const char *alias = &match[1];
+
+ svn_config_get(b->config, &val, "aliases", alias, NULL);
+
+ if (!val)
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "An authz rule refers to alias "
+ "'%s', which is undefined",
+ rule_match_string);
+ return FALSE;
+ }
+ }
+
+ /* If the rule specifies a token, check its validity. */
+ if (match[0] == '$')
+ {
+ const char *token_name = &match[1];
+
+ if ((strcmp(token_name, "anonymous") != 0)
+ && (strcmp(token_name, "authenticated") != 0))
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Unrecognized authz token '%s'.",
+ rule_match_string);
+ return FALSE;
+ }
+ }
+
+ val = value;
+
+ while (*val)
+ {
+ if (*val != 'r' && *val != 'w' && ! svn_ctype_isspace(*val))
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "The character '%c' in rule '%s' is not "
+ "allowed in authz rules", *val,
+ rule_match_string);
+ return FALSE;
+ }
+
+ ++val;
+ }
+
+ return TRUE;
+}
+
+/* Callback to check ALIAS's definition for validity. Use
+ BATON for context and error reporting. */
+static svn_boolean_t authz_validate_alias(const char *alias,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ /* No checking at the moment, every alias is valid */
+ return TRUE;
+}
+
+
+/* Callback to check GROUP's definition for cyclic dependancies. Use
+ BATON for context and error reporting. */
+static svn_boolean_t authz_validate_group(const char *group,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct authz_validate_baton *b = baton;
+
+ b->err = authz_group_walk(b->config, group, apr_hash_make(pool), pool);
+ if (b->err)
+ return FALSE;
+
+ return TRUE;
+}
+
+
+/* Callback to check the contents of the configuration section given
+ by NAME. Use BATON for context and error reporting. */
+static svn_boolean_t authz_validate_section(const char *name,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct authz_validate_baton *b = baton;
+
+ /* Use the group checking callback for the "groups" section... */
+ if (strcmp(name, "groups") == 0)
+ svn_config_enumerate2(b->config, name, authz_validate_group,
+ baton, pool);
+ /* ...and the alias checking callback for "aliases"... */
+ else if (strcmp(name, "aliases") == 0)
+ svn_config_enumerate2(b->config, name, authz_validate_alias,
+ baton, pool);
+ /* ...but for everything else use the rule checking callback. */
+ else
+ {
+ /* Validate the section's name. Skip the optional REPOS_NAME. */
+ const char *fspath = strchr(name, ':');
+ if (fspath)
+ fspath++;
+ else
+ fspath = name;
+ if (! svn_fspath__is_canonical(fspath))
+ {
+ b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Section name '%s' contains non-canonical "
+ "fspath '%s'",
+ name, fspath);
+ return FALSE;
+ }
+
+ svn_config_enumerate2(b->config, name, authz_validate_rule,
+ baton, pool);
+ }
+
+ if (b->err)
+ return FALSE;
+
+ return TRUE;
+}
+
+
+/* Walk the configuration in AUTHZ looking for any errors. */
+static svn_error_t *
+authz_validate(svn_authz_t *authz, apr_pool_t *pool)
+{
+ struct authz_validate_baton baton = { 0 };
+
+ baton.err = SVN_NO_ERROR;
+ baton.config = authz->cfg;
+
+ /* Step through the entire rule file stopping on error. */
+ svn_config_enumerate_sections2(authz->cfg, authz_validate_section,
+ &baton, pool);
+ SVN_ERR(baton.err);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Retrieve the file at DIRENT (contained in a repo) then parse it as a config
+ * file placing the result into CFG_P allocated in POOL.
+ *
+ * If DIRENT cannot be parsed as a config file then an error is returned. The
+ * contents of CFG_P is then undefined. If MUST_EXIST is TRUE, a missing
+ * authz file is also an error.
+ *
+ * SCRATCH_POOL will be used for temporary allocations. */
+static svn_error_t *
+authz_retrieve_config_repo(svn_config_t **cfg_p, const char *dirent,
+ svn_boolean_t must_exist,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_repos_t *repos;
+ const char *repos_root_dirent;
+ const char *fs_path;
+ svn_fs_t *fs;
+ svn_fs_root_t *root;
+ svn_revnum_t youngest_rev;
+ svn_node_kind_t node_kind;
+ svn_stream_t *contents;
+
+ /* Search for a repository in the full path. */
+ repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
+ if (!repos_root_dirent)
+ return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_NOT_FOUND, NULL,
+ "Unable to find repository at '%s'", dirent);
+
+ /* Attempt to open a repository at repos_root_dirent. */
+ SVN_ERR(svn_repos_open2(&repos, repos_root_dirent, NULL, scratch_pool));
+
+ fs_path = &dirent[strlen(repos_root_dirent)];
+
+ /* Root path is always a directory so no reason to go any further */
+ if (*fs_path == '\0')
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'/' is not a file in repo '%s'",
+ repos_root_dirent);
+
+ /* We skip some things that are non-important for how we're going to use
+ * this repo connection. We do not set any capabilities since none of
+ * the current ones are important for what we're doing. We also do not
+ * setup the environment that repos hooks would run under since we won't
+ * be triggering any. */
+
+ /* Get the filesystem. */
+ fs = svn_repos_fs(repos);
+
+ /* Find HEAD and the revision root */
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
+ SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool));
+
+ SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
+ if (node_kind == svn_node_none)
+ {
+ if (!must_exist)
+ {
+ SVN_ERR(svn_config_create2(cfg_p, TRUE, TRUE, result_pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'%s' path not found in repo '%s'", fs_path,
+ repos_root_dirent);
+ }
+ }
+ else if (node_kind != svn_node_file)
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ "'%s' is not a file in repo '%s'", fs_path,
+ repos_root_dirent);
+ }
+
+ SVN_ERR(svn_fs_file_contents(&contents, root, fs_path, scratch_pool));
+ err = svn_config_parse(cfg_p, contents, TRUE, TRUE, result_pool);
+
+ /* Add the URL to the error stack since the parser doesn't have it. */
+ if (err != SVN_NO_ERROR)
+ return svn_error_createf(err->apr_err, err,
+ "Error while parsing config file: '%s' in repo '%s':",
+ fs_path, repos_root_dirent);
+
+ return SVN_NO_ERROR;
+}
+
+/* Given a PATH which might be a relative repo URL (^/), an absolute
+ * local repo URL (file://), an absolute path outside of the repo
+ * or a location in the Windows registry.
+ *
+ * Retrieve the configuration data that PATH points at and parse it into
+ * CFG_P allocated in POOL.
+ *
+ * If PATH cannot be parsed as a config file then an error is returned. The
+ * contents of CFG_P is then undefined. If MUST_EXIST is TRUE, a missing
+ * authz file is also an error.
+ *
+ * REPOS_ROOT points at the root of the repos you are
+ * going to apply the authz against, can be NULL if you are sure that you
+ * don't have a repos relative URL in PATH. */
+static svn_error_t *
+authz_retrieve_config(svn_config_t **cfg_p, const char *path,
+ svn_boolean_t must_exist, apr_pool_t *pool)
+{
+ if (svn_path_is_url(path))
+ {
+ const char *dirent;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ err = svn_uri_get_dirent_from_file_url(&dirent, path, scratch_pool);
+
+ if (err == SVN_NO_ERROR)
+ err = authz_retrieve_config_repo(cfg_p, dirent, must_exist, pool,
+ scratch_pool);
+
+ /* Close the repos and streams we opened. */
+ svn_pool_destroy(scratch_pool);
+
+ return err;
+ }
+ else
+ {
+ /* Outside of repo file or Windows registry*/
+ SVN_ERR(svn_config_read3(cfg_p, path, must_exist, TRUE, TRUE, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Callback to copy (name, value) group into the "groups" section
+ of another configuration. */
+static svn_boolean_t
+authz_copy_group(const char *name, const char *value,
+ void *baton, apr_pool_t *pool)
+{
+ svn_config_t *authz_cfg = baton;
+
+ svn_config_set(authz_cfg, SVN_CONFIG_SECTION_GROUPS, name, value);
+
+ return TRUE;
+}
+
+/* Copy group definitions from GROUPS_CFG to the resulting AUTHZ.
+ * If AUTHZ already contains any group definition, report an error.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+authz_copy_groups(svn_authz_t *authz, svn_config_t *groups_cfg,
+ apr_pool_t *pool)
+{
+ /* Easy out: we prohibit local groups in the authz file when global
+ groups are being used. */
+ if (svn_config_has_section(authz->cfg, SVN_CONFIG_SECTION_GROUPS))
+ {
+ return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
+ "Authz file cannot contain any groups "
+ "when global groups are being used.");
+ }
+
+ svn_config_enumerate2(groups_cfg, SVN_CONFIG_SECTION_GROUPS,
+ authz_copy_group, authz->cfg, pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__authz_read(svn_authz_t **authz_p, const char *path,
+ const char *groups_path, svn_boolean_t must_exist,
+ svn_boolean_t accept_urls, apr_pool_t *pool)
+{
+ svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
+
+ /* Load the authz file */
+ if (accept_urls)
+ SVN_ERR(authz_retrieve_config(&authz->cfg, path, must_exist, pool));
+ else
+ SVN_ERR(svn_config_read3(&authz->cfg, path, must_exist, TRUE, TRUE, pool));
+
+ if (groups_path)
+ {
+ svn_config_t *groups_cfg;
+ svn_error_t *err;
+
+ /* Load the groups file */
+ if (accept_urls)
+ SVN_ERR(authz_retrieve_config(&groups_cfg, groups_path, must_exist,
+ pool));
+ else
+ SVN_ERR(svn_config_read3(&groups_cfg, groups_path, must_exist,
+ TRUE, TRUE, pool));
+
+ /* Copy the groups from groups_cfg into authz. */
+ err = authz_copy_groups(authz, groups_cfg, pool);
+
+ /* Add the paths to the error stack since the authz_copy_groups
+ routine knows nothing about them. */
+ if (err != SVN_NO_ERROR)
+ return svn_error_createf(err->apr_err, err,
+ "Error reading authz file '%s' with "
+ "groups file '%s':", path, groups_path);
+ }
+
+ /* Make sure there are no errors in the configuration. */
+ SVN_ERR(authz_validate(authz, pool));
+
+ *authz_p = authz;
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Public functions. ***/
+
+svn_error_t *
+svn_repos_authz_read2(svn_authz_t **authz_p, const char *path,
+ const char *groups_path, svn_boolean_t must_exist,
+ apr_pool_t *pool)
+{
+ return svn_repos__authz_read(authz_p, path, groups_path, must_exist,
+ TRUE, pool);
+}
+
+
+svn_error_t *
+svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream,
+ svn_stream_t *groups_stream, apr_pool_t *pool)
+{
+ svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
+
+ /* Parse the authz stream */
+ SVN_ERR(svn_config_parse(&authz->cfg, stream, TRUE, TRUE, pool));
+
+ if (groups_stream)
+ {
+ svn_config_t *groups_cfg;
+
+ /* Parse the groups stream */
+ SVN_ERR(svn_config_parse(&groups_cfg, groups_stream, TRUE, TRUE, pool));
+
+ SVN_ERR(authz_copy_groups(authz, groups_cfg, pool));
+ }
+
+ /* Make sure there are no errors in the configuration. */
+ SVN_ERR(authz_validate(authz, pool));
+
+ *authz_p = authz;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name,
+ const char *path, const char *user,
+ svn_repos_authz_access_t required_access,
+ svn_boolean_t *access_granted,
+ apr_pool_t *pool)
+{
+ const char *current_path;
+
+ if (!repos_name)
+ repos_name = "";
+
+ /* If PATH is NULL, check if the user has *any* access. */
+ if (!path)
+ {
+ *access_granted = authz_get_any_access(authz->cfg, repos_name,
+ user, required_access, pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* Sanity check. */
+ SVN_ERR_ASSERT(path[0] == '/');
+
+ /* Determine the granted access for the requested path. */
+ path = svn_fspath__canonicalize(path, pool);
+ current_path = path;
+
+ while (!authz_get_path_access(authz->cfg, repos_name,
+ current_path, user,
+ required_access,
+ access_granted,
+ pool))
+ {
+ /* Stop if the loop hits the repository root with no
+ results. */
+ if (current_path[0] == '/' && current_path[1] == '\0')
+ {
+ /* Deny access by default. */
+ *access_granted = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Work back to the parent path. */
+ current_path = svn_fspath__dirname(current_path, pool);
+ }
+
+ /* If the caller requested recursive access, we need to walk through
+ the entire authz config to see whether any child paths are denied
+ to the requested user. */
+ if (*access_granted && (required_access & svn_authz_recursive))
+ *access_granted = authz_get_tree_access(authz->cfg, repos_name, path,
+ user, required_access, pool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/commit.c b/contrib/subversion/subversion/libsvn_repos/commit.c
new file mode 100644
index 0000000..c4606ab
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/commit.c
@@ -0,0 +1,1381 @@
+/* commit.c --- editor for committing changes to a filesystem.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+
+#include "svn_hash.h"
+#include "svn_compat.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_delta.h"
+#include "svn_fs.h"
+#include "svn_repos.h"
+#include "svn_checksum.h"
+#include "svn_ctype.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "svn_private_config.h"
+
+#include "repos.h"
+
+#include "private/svn_fspath.h"
+#include "private/svn_fs_private.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_editor.h"
+
+
+
+/*** Editor batons. ***/
+
+struct edit_baton
+{
+ apr_pool_t *pool;
+
+ /** Supplied when the editor is created: **/
+
+ /* Revision properties to set for this commit. */
+ apr_hash_t *revprop_table;
+
+ /* Callback to run when the commit is done. */
+ svn_commit_callback2_t commit_callback;
+ void *commit_callback_baton;
+
+ /* Callback to check authorizations on paths. */
+ svn_repos_authz_callback_t authz_callback;
+ void *authz_baton;
+
+ /* The already-open svn repository to commit to. */
+ svn_repos_t *repos;
+
+ /* URL to the root of the open repository. */
+ const char *repos_url;
+
+ /* The name of the repository (here for convenience). */
+ const char *repos_name;
+
+ /* The filesystem associated with the REPOS above (here for
+ convenience). */
+ svn_fs_t *fs;
+
+ /* Location in fs where the edit will begin. */
+ const char *base_path;
+
+ /* Does this set of interfaces 'own' the commit transaction? */
+ svn_boolean_t txn_owner;
+
+ /* svn transaction associated with this edit (created in
+ open_root, or supplied by the public API caller). */
+ svn_fs_txn_t *txn;
+
+ /** Filled in during open_root: **/
+
+ /* The name of the transaction. */
+ const char *txn_name;
+
+ /* The object representing the root directory of the svn txn. */
+ svn_fs_root_t *txn_root;
+
+ /* Avoid aborting an fs transaction more than once */
+ svn_boolean_t txn_aborted;
+
+ /** Filled in when the edit is closed: **/
+
+ /* The new revision created by this commit. */
+ svn_revnum_t *new_rev;
+
+ /* The date (according to the repository) of this commit. */
+ const char **committed_date;
+
+ /* The author (also according to the repository) of this commit. */
+ const char **committed_author;
+};
+
+
+struct dir_baton
+{
+ struct edit_baton *edit_baton;
+ struct dir_baton *parent;
+ const char *path; /* the -absolute- path to this dir in the fs */
+ svn_revnum_t base_rev; /* the revision I'm based on */
+ svn_boolean_t was_copied; /* was this directory added with history? */
+ apr_pool_t *pool; /* my personal pool, in which I am allocated. */
+};
+
+
+struct file_baton
+{
+ struct edit_baton *edit_baton;
+ const char *path; /* the -absolute- path to this file in the fs */
+};
+
+
+struct ev2_baton
+{
+ /* The repository we are editing. */
+ svn_repos_t *repos;
+
+ /* The authz baton for checks; NULL to skip authz. */
+ svn_authz_t *authz;
+
+ /* The repository name and user for performing authz checks. */
+ const char *authz_repos_name;
+ const char *authz_user;
+
+ /* Callback to provide info about the committed revision. */
+ svn_commit_callback2_t commit_cb;
+ void *commit_baton;
+
+ /* The FS txn editor */
+ svn_editor_t *inner;
+
+ /* The name of the open transaction (so we know what to commit) */
+ const char *txn_name;
+};
+
+
+/* Create and return a generic out-of-dateness error. */
+static svn_error_t *
+out_of_date(const char *path, svn_node_kind_t kind)
+{
+ return svn_error_createf(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
+ (kind == svn_node_dir
+ ? _("Directory '%s' is out of date")
+ : kind == svn_node_file
+ ? _("File '%s' is out of date")
+ : _("'%s' is out of date")),
+ path);
+}
+
+
+static svn_error_t *
+invoke_commit_cb(svn_commit_callback2_t commit_cb,
+ void *commit_baton,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ const char *post_commit_errstr,
+ apr_pool_t *scratch_pool)
+{
+ /* FS interface returns non-const values. */
+ /* const */ svn_string_t *date;
+ /* const */ svn_string_t *author;
+ svn_commit_info_t *commit_info;
+
+ if (commit_cb == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_revision_prop(&date, fs, revision, SVN_PROP_REVISION_DATE,
+ scratch_pool));
+ SVN_ERR(svn_fs_revision_prop(&author, fs, revision,
+ SVN_PROP_REVISION_AUTHOR,
+ scratch_pool));
+
+ commit_info = svn_create_commit_info(scratch_pool);
+
+ /* fill up the svn_commit_info structure */
+ commit_info->revision = revision;
+ commit_info->date = date ? date->data : NULL;
+ commit_info->author = author ? author->data : NULL;
+ commit_info->post_commit_err = post_commit_errstr;
+
+ return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool));
+}
+
+
+
+/* If EDITOR_BATON contains a valid authz callback, verify that the
+ REQUIRED access to PATH in ROOT is authorized. Return an error
+ appropriate for throwing out of the commit editor with SVN_ERR. If
+ no authz callback is present in EDITOR_BATON, then authorize all
+ paths. Use POOL for temporary allocation only. */
+static svn_error_t *
+check_authz(struct edit_baton *editor_baton, const char *path,
+ svn_fs_root_t *root, svn_repos_authz_access_t required,
+ apr_pool_t *pool)
+{
+ if (editor_baton->authz_callback)
+ {
+ svn_boolean_t allowed;
+
+ SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path,
+ editor_baton->authz_baton, pool));
+ if (!allowed)
+ return svn_error_create(required & svn_authz_write ?
+ SVN_ERR_AUTHZ_UNWRITABLE :
+ SVN_ERR_AUTHZ_UNREADABLE,
+ NULL, "Access denied");
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return a directory baton allocated in POOL which represents
+ FULL_PATH, which is the immediate directory child of the directory
+ represented by PARENT_BATON. EDIT_BATON is the commit editor
+ baton. WAS_COPIED reveals whether or not this directory is the
+ result of a copy operation. BASE_REVISION is the base revision of
+ the directory. */
+static struct dir_baton *
+make_dir_baton(struct edit_baton *edit_baton,
+ struct dir_baton *parent_baton,
+ const char *full_path,
+ svn_boolean_t was_copied,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db;
+ db = apr_pcalloc(pool, sizeof(*db));
+ db->edit_baton = edit_baton;
+ db->parent = parent_baton;
+ db->pool = pool;
+ db->path = full_path;
+ db->was_copied = was_copied;
+ db->base_rev = base_revision;
+ return db;
+}
+
+/* This function is the shared guts of add_file() and add_directory(),
+ which see for the meanings of the parameters. The only extra
+ parameter here is IS_DIR, which is TRUE when adding a directory,
+ and FALSE when adding a file. */
+static svn_error_t *
+add_file_or_directory(const char *path,
+ void *parent_baton,
+ const char *copy_path,
+ svn_revnum_t copy_revision,
+ svn_boolean_t is_dir,
+ apr_pool_t *pool,
+ void **return_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_boolean_t was_copied = FALSE;
+ const char *full_path;
+
+ /* Reject paths which contain control characters (related to issue #4340). */
+ SVN_ERR(svn_path_check_valid(path, pool));
+
+ full_path = svn_fspath__join(eb->base_path,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Sanity check. */
+ if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision)))
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Got source path but no source revision for '%s'"), full_path);
+
+ if (copy_path)
+ {
+ const char *fs_path;
+ svn_fs_root_t *copy_root;
+ svn_node_kind_t kind;
+ size_t repos_url_len;
+ svn_repos_authz_access_t required;
+
+ /* Copy requires recursive write access to the destination path
+ and write access to the parent path. */
+ required = svn_authz_write | (is_dir ? svn_authz_recursive : 0);
+ SVN_ERR(check_authz(eb, full_path, eb->txn_root,
+ required, subpool));
+ SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
+ svn_authz_write, subpool));
+
+ /* Check PATH in our transaction. Make sure it does not exist
+ unless its parent directory was copied (in which case, the
+ thing might have been copied in as well), else return an
+ out-of-dateness error. */
+ SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool));
+ if ((kind != svn_node_none) && (! pb->was_copied))
+ return svn_error_trace(out_of_date(full_path, kind));
+
+ /* For now, require that the url come from the same repository
+ that this commit is operating on. */
+ copy_path = svn_path_uri_decode(copy_path, subpool);
+ repos_url_len = strlen(eb->repos_url);
+ if (strncmp(copy_path, eb->repos_url, repos_url_len) != 0)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Source url '%s' is from different repository"), copy_path);
+
+ fs_path = apr_pstrdup(subpool, copy_path + repos_url_len);
+
+ /* Now use the "fs_path" as an absolute path within the
+ repository to make the copy from. */
+ SVN_ERR(svn_fs_revision_root(&copy_root, eb->fs,
+ copy_revision, subpool));
+
+ /* Copy also requires (recursive) read access to the source */
+ required = svn_authz_read | (is_dir ? svn_authz_recursive : 0);
+ SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool));
+
+ SVN_ERR(svn_fs_copy(copy_root, fs_path,
+ eb->txn_root, full_path, subpool));
+ was_copied = TRUE;
+ }
+ else
+ {
+ /* No ancestry given, just make a new directory or empty file.
+ Note that we don't perform an existence check here like the
+ copy-from case does -- that's because svn_fs_make_*()
+ already errors out if the file already exists. Verify write
+ access to the full path and to the parent. */
+ SVN_ERR(check_authz(eb, full_path, eb->txn_root,
+ svn_authz_write, subpool));
+ SVN_ERR(check_authz(eb, pb->path, eb->txn_root,
+ svn_authz_write, subpool));
+ if (is_dir)
+ SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool));
+ else
+ SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool));
+ }
+
+ /* Cleanup our temporary subpool. */
+ svn_pool_destroy(subpool);
+
+ /* Build a new child baton. */
+ if (is_dir)
+ {
+ *return_baton = make_dir_baton(eb, pb, full_path, was_copied,
+ SVN_INVALID_REVNUM, pool);
+ }
+ else
+ {
+ struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
+ new_fb->edit_baton = eb;
+ new_fb->path = full_path;
+ *return_baton = new_fb;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Editor functions ***/
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct dir_baton *dirb;
+ struct edit_baton *eb = edit_baton;
+ svn_revnum_t youngest;
+
+ /* Ignore BASE_REVISION. We always build our transaction against
+ HEAD. However, we will keep it in our dir baton for out of
+ dateness checks. */
+ SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));
+
+ /* Unless we've been instructed to use a specific transaction, we'll
+ make our own. */
+ if (eb->txn_owner)
+ {
+ SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
+ eb->repos,
+ youngest,
+ eb->revprop_table,
+ eb->pool));
+ }
+ else /* Even if we aren't the owner of the transaction, we might
+ have been instructed to set some properties. */
+ {
+ apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
+ pool);
+ SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
+ }
+ SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool));
+ SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool));
+
+ /* Create a root dir baton. The `base_path' field is an -absolute-
+ path in the filesystem, upon which all further editor paths are
+ based. */
+ dirb = apr_pcalloc(pool, sizeof(*dirb));
+ dirb->edit_baton = edit_baton;
+ dirb->parent = NULL;
+ dirb->pool = pool;
+ dirb->was_copied = FALSE;
+ dirb->path = apr_pstrdup(pool, eb->base_path);
+ dirb->base_rev = base_revision;
+
+ *root_baton = dirb;
+ return SVN_NO_ERROR;
+}
+
+
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *parent = parent_baton;
+ struct edit_baton *eb = parent->edit_baton;
+ svn_node_kind_t kind;
+ svn_revnum_t cr_rev;
+ svn_repos_authz_access_t required = svn_authz_write;
+ const char *full_path;
+
+ full_path = svn_fspath__join(eb->base_path,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Check PATH in our transaction. */
+ SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
+
+ /* Deletion requires a recursive write access, as well as write
+ access to the parent directory. */
+ if (kind == svn_node_dir)
+ required |= svn_authz_recursive;
+ SVN_ERR(check_authz(eb, full_path, eb->txn_root,
+ required, pool));
+ SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
+ svn_authz_write, pool));
+
+ /* If PATH doesn't exist in the txn, the working copy is out of date. */
+ if (kind == svn_node_none)
+ return svn_error_trace(out_of_date(full_path, kind));
+
+ /* Now, make sure we're deleting the node we *think* we're
+ deleting, else return an out-of-dateness error. */
+ SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
+ if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev))
+ return svn_error_trace(out_of_date(full_path, kind));
+
+ /* This routine is a mindless wrapper. We call svn_fs_delete()
+ because that will delete files and recursively delete
+ directories. */
+ return svn_fs_delete(eb->txn_root, full_path, pool);
+}
+
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copy_path,
+ svn_revnum_t copy_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
+ TRUE /* is_dir */, pool, child_baton);
+}
+
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ svn_node_kind_t kind;
+ const char *full_path;
+
+ full_path = svn_fspath__join(eb->base_path,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Check PATH in our transaction. If it does not exist,
+ return a 'Path not present' error. */
+ SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Path '%s' not present"),
+ path);
+
+ /* Build a new dir baton for this directory. */
+ *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied,
+ base_revision, pool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+
+ /* Check for write authorization. */
+ SVN_ERR(check_authz(fb->edit_baton, fb->path,
+ fb->edit_baton->txn_root,
+ svn_authz_write, pool));
+
+ return svn_fs_apply_textdelta(handler, handler_baton,
+ fb->edit_baton->txn_root,
+ fb->path,
+ base_checksum,
+ NULL,
+ pool);
+}
+
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copy_path,
+ svn_revnum_t copy_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ return add_file_or_directory(path, parent_baton, copy_path, copy_revision,
+ FALSE /* is_dir */, pool, file_baton);
+}
+
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct file_baton *new_fb;
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ svn_revnum_t cr_rev;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ const char *full_path;
+
+ full_path = svn_fspath__join(eb->base_path,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Check for read authorization. */
+ SVN_ERR(check_authz(eb, full_path, eb->txn_root,
+ svn_authz_read, subpool));
+
+ /* Get this node's creation revision (doubles as an existence check). */
+ SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path,
+ subpool));
+
+ /* If the node our caller has is an older revision number than the
+ one in our transaction, return an out-of-dateness error. */
+ if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev))
+ return svn_error_trace(out_of_date(full_path, svn_node_file));
+
+ /* Build a new file baton */
+ new_fb = apr_pcalloc(pool, sizeof(*new_fb));
+ new_fb->edit_baton = eb;
+ new_fb->path = full_path;
+
+ *file_baton = new_fb;
+
+ /* Destory the work subpool. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ /* Check for write authorization. */
+ SVN_ERR(check_authz(eb, fb->path, eb->txn_root,
+ svn_authz_write, pool));
+
+ return svn_repos_fs_change_node_prop(eb->txn_root, fb->path,
+ name, value, pool);
+}
+
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_digest,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+
+ if (text_digest)
+ {
+ svn_checksum_t *checksum;
+ svn_checksum_t *text_checksum;
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
+ fb->edit_baton->txn_root, fb->path,
+ TRUE, pool));
+ SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5,
+ text_digest, pool));
+
+ if (!svn_checksum_match(text_checksum, checksum))
+ return svn_checksum_mismatch_err(text_checksum, checksum, pool,
+ _("Checksum mismatch for resulting fulltext\n(%s)"),
+ fb->path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ /* Check for write authorization. */
+ SVN_ERR(check_authz(eb, db->path, eb->txn_root,
+ svn_authz_write, pool));
+
+ if (SVN_IS_VALID_REVNUM(db->base_rev))
+ {
+ /* Subversion rule: propchanges can only happen on a directory
+ which is up-to-date. */
+ svn_revnum_t created_rev;
+ SVN_ERR(svn_fs_node_created_rev(&created_rev,
+ eb->txn_root, db->path, pool));
+
+ if (db->base_rev < created_rev)
+ return svn_error_trace(out_of_date(db->path, svn_node_dir));
+ }
+
+ return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
+ name, value, pool);
+}
+
+const char *
+svn_repos__post_commit_error_str(svn_error_t *err,
+ apr_pool_t *pool)
+{
+ svn_error_t *hook_err1, *hook_err2;
+ const char *msg;
+
+ if (! err)
+ return _("(no error)");
+
+ err = svn_error_purge_tracing(err);
+
+ /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped
+ error from the post-commit script, if any, and hook_err2 should
+ be the original error, but be defensive and handle a case where
+ SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */
+ hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED);
+ if (hook_err1 && hook_err1->child)
+ hook_err2 = hook_err1->child;
+ else
+ hook_err2 = hook_err1;
+
+ /* This implementation counts on svn_repos_fs_commit_txn() and
+ libsvn_repos/commit.c:complete_cb() returning
+ svn_fs_commit_txn() as the parent error with a child
+ SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error. If the parent error
+ is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error
+ in svn_fs_commit_txn().
+
+ The post-commit hook error message is already self describing, so
+ it can be dropped into an error message without any additional
+ text. */
+ if (hook_err1)
+ {
+ if (err == hook_err1)
+ {
+ if (hook_err2->message)
+ msg = apr_pstrdup(pool, hook_err2->message);
+ else
+ msg = _("post-commit hook failed with no error message.");
+ }
+ else
+ {
+ msg = hook_err2->message
+ ? apr_pstrdup(pool, hook_err2->message)
+ : _("post-commit hook failed with no error message.");
+ msg = apr_psprintf(
+ pool,
+ _("post commit FS processing had error:\n%s\n%s"),
+ err->message ? err->message : _("(no error message)"),
+ msg);
+ }
+ }
+ else
+ {
+ msg = apr_psprintf(pool,
+ _("post commit FS processing had error:\n%s"),
+ err->message ? err->message
+ : _("(no error message)"));
+ }
+
+ return msg;
+}
+
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+ svn_revnum_t new_revision = SVN_INVALID_REVNUM;
+ svn_error_t *err;
+ const char *conflict;
+ const char *post_commit_err = NULL;
+
+ /* If no transaction has been created (ie. if open_root wasn't
+ called before close_edit), abort the operation here with an
+ error. */
+ if (! eb->txn)
+ return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ "No valid transaction supplied to close_edit");
+
+ /* Commit. */
+ err = svn_repos_fs_commit_txn(&conflict, eb->repos,
+ &new_revision, eb->txn, pool);
+
+ if (SVN_IS_VALID_REVNUM(new_revision))
+ {
+ if (err)
+ {
+ /* If the error was in post-commit, then the commit itself
+ succeeded. In which case, save the post-commit warning
+ (to be reported back to the client, who will probably
+ display it as a warning) and clear the error. */
+ post_commit_err = svn_repos__post_commit_error_str(err, pool);
+ svn_error_clear(err);
+ }
+ }
+ else
+ {
+ /* ### todo: we should check whether it really was a conflict,
+ and return the conflict info if so? */
+
+ /* If the commit failed, it's *probably* due to a conflict --
+ that is, the txn being out-of-date. The filesystem gives us
+ the ability to continue diddling the transaction and try
+ again; but let's face it: that's not how the cvs or svn works
+ from a user interface standpoint. Thus we don't make use of
+ this fs feature (for now, at least.)
+
+ So, in a nutshell: svn commits are an all-or-nothing deal.
+ Each commit creates a new fs txn which either succeeds or is
+ aborted completely. No second chances; the user simply
+ needs to update and commit again :) */
+
+ eb->txn_aborted = TRUE;
+
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_fs_abort_txn(eb->txn, pool)));
+ }
+
+ /* At this point, the post-commit error has been converted to a string.
+ That information will be passed to a callback, if provided. If the
+ callback invocation fails in some way, that failure is returned here.
+ IOW, the post-commit error information is low priority compared to
+ other gunk here. */
+
+ /* Pass new revision information to the caller's callback. */
+ return svn_error_trace(invoke_commit_cb(eb->commit_callback,
+ eb->commit_callback_baton,
+ eb->repos->fs,
+ new_revision,
+ post_commit_err,
+ pool));
+}
+
+
+static svn_error_t *
+abort_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+ if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted)
+ return SVN_NO_ERROR;
+
+ eb->txn_aborted = TRUE;
+
+ return svn_error_trace(svn_fs_abort_txn(eb->txn, pool));
+}
+
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_fs_root_t *fs_root;
+ svn_error_t *err;
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs,
+ svn_fs_txn_base_revision(eb->txn),
+ scratch_pool));
+ err = svn_fs_node_proplist(props, fs_root, path, result_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *props = apr_hash_make(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_fs_root_t *fs_root;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_txn_base_revision(eb->txn);
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
+
+ SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_stream_t *contents;
+ svn_stream_t *file_stream;
+ const char *tmp_filename;
+ svn_fs_root_t *fs_root;
+ svn_error_t *err;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_txn_base_revision(eb->txn);
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
+
+ err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+ SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
+
+ *filename = apr_pstrdup(result_pool, tmp_filename);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Public interfaces. ***/
+
+svn_error_t *
+svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url,
+ const char *base_path,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_repos_authz_callback_t authz_callback,
+ void *authz_baton,
+ apr_pool_t *pool)
+{
+ svn_delta_editor_t *e;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ struct edit_baton *eb;
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(pool);
+
+ /* Do a global authz access lookup. Users with no write access
+ whatsoever to the repository don't get a commit editor. */
+ if (authz_callback)
+ {
+ svn_boolean_t allowed;
+
+ SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL,
+ authz_baton, pool));
+ if (!allowed)
+ return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL,
+ "Not authorized to open a commit editor.");
+ }
+
+ /* Allocate the structures. */
+ e = svn_delta_default_editor(pool);
+ eb = apr_pcalloc(subpool, sizeof(*eb));
+
+ /* Set up the editor. */
+ e->open_root = open_root;
+ e->delete_entry = delete_entry;
+ e->add_directory = add_directory;
+ e->open_directory = open_directory;
+ e->change_dir_prop = change_dir_prop;
+ e->add_file = add_file;
+ e->open_file = open_file;
+ e->close_file = close_file;
+ e->apply_textdelta = apply_textdelta;
+ e->change_file_prop = change_file_prop;
+ e->close_edit = close_edit;
+ e->abort_edit = abort_edit;
+
+ /* Set up the edit baton. */
+ eb->pool = subpool;
+ eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
+ eb->commit_callback = commit_callback;
+ eb->commit_callback_baton = commit_baton;
+ eb->authz_callback = authz_callback;
+ eb->authz_baton = authz_baton;
+ eb->base_path = svn_fspath__canonicalize(base_path, subpool);
+ eb->repos = repos;
+ eb->repos_url = repos_url;
+ eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool),
+ subpool);
+ eb->fs = svn_repos_fs(repos);
+ eb->txn = txn;
+ eb->txn_owner = txn == NULL;
+
+ *edit_baton = eb;
+ *editor = e;
+
+ shim_callbacks->fetch_props_func = fetch_props_func;
+ shim_callbacks->fetch_kind_func = fetch_kind_func;
+ shim_callbacks->fetch_base_func = fetch_base_func;
+ shim_callbacks->fetch_baton = eb;
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ eb->repos_url, eb->base_path,
+ shim_callbacks, pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+#if 0
+static svn_error_t *
+ev2_check_authz(const struct ev2_baton *eb,
+ const char *relpath,
+ svn_repos_authz_access_t required,
+ apr_pool_t *scratch_pool)
+{
+ const char *fspath;
+ svn_boolean_t allowed;
+
+ if (eb->authz == NULL)
+ return SVN_NO_ERROR;
+
+ if (relpath)
+ fspath = apr_pstrcat(scratch_pool, "/", relpath, NULL);
+ else
+ fspath = NULL;
+
+ SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath,
+ eb->authz_user, required,
+ &allowed, scratch_pool));
+ if (!allowed)
+ return svn_error_create(required & svn_authz_write
+ ? SVN_ERR_AUTHZ_UNWRITABLE
+ : SVN_ERR_AUTHZ_UNREADABLE,
+ NULL, "Access denied");
+
+ return SVN_NO_ERROR;
+}
+#endif
+
+
+/* This implements svn_editor_cb_add_directory_t */
+static svn_error_t *
+add_directory_cb(void *baton,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props,
+ replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_add_file_t */
+static svn_error_t *
+add_file_cb(void *baton,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props,
+ replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_add_symlink_t */
+static svn_error_t *
+add_symlink_cb(void *baton,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props,
+ replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_add_absent_t */
+static svn_error_t *
+add_absent_cb(void *baton,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_alter_directory_t */
+static svn_error_t *
+alter_directory_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision,
+ children, props));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_alter_file_t */
+static svn_error_t *
+alter_file_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, props,
+ checksum, contents));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_alter_symlink_t */
+static svn_error_t *
+alter_symlink_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, props,
+ target));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_delete_t */
+static svn_error_t *
+delete_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_delete(eb->inner, relpath, revision));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_copy_t */
+static svn_error_t *
+copy_cb(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath,
+ replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_move_t */
+static svn_error_t *
+move_cb(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath,
+ replaces_rev));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_rotate_t */
+static svn_error_t *
+rotate_cb(void *baton,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_rotate(eb->inner, relpaths, revisions));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_complete_t */
+static svn_error_t *
+complete_cb(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+ svn_revnum_t revision;
+ svn_error_t *post_commit_err;
+ const char *conflict_path;
+ svn_error_t *err;
+ const char *post_commit_errstr;
+ apr_hash_t *hooks_env;
+
+ /* Parse the hooks-env file (if any). */
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path,
+ scratch_pool, scratch_pool));
+
+ /* The transaction has been fully edited. Let the pre-commit hook
+ have a look at the thing. */
+ SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env,
+ eb->txn_name, scratch_pool));
+
+ /* Hook is done. Let's do the actual commit. */
+ SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path,
+ eb->inner, scratch_pool, scratch_pool));
+
+ /* Did a conflict occur during the commit process? */
+ if (conflict_path != NULL)
+ return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
+ _("Conflict at '%s'"), conflict_path);
+
+ /* Since did not receive an error during the commit process, and no
+ conflict was specified... we committed a revision. Run the hooks.
+ Other errors may have occurred within the FS (specified by the
+ POST_COMMIT_ERR localvar), but we need to run the hooks. */
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision,
+ eb->txn_name, scratch_pool);
+ if (err)
+ err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
+ _("Commit succeeded, but post-commit hook failed"));
+
+ /* Combine the FS errors with the hook errors, and stringify. */
+ err = svn_error_compose_create(post_commit_err, err);
+ if (err)
+ {
+ post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool);
+ svn_error_clear(err);
+ }
+ else
+ {
+ post_commit_errstr = NULL;
+ }
+
+ return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton,
+ eb->repos->fs, revision,
+ post_commit_errstr,
+ scratch_pool));
+}
+
+
+/* This implements svn_editor_cb_abort_t */
+static svn_error_t *
+abort_cb(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_baton *eb = baton;
+
+ SVN_ERR(svn_editor_abort(eb->inner));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+apply_revprops(svn_fs_t *fs,
+ const char *txn_name,
+ apr_hash_t *revprops,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_txn_t *txn;
+ const apr_array_header_t *revprops_array;
+
+ /* The FS editor has a TXN inside it, but we can't access it. Open another
+ based on the TXN_NAME. */
+ SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool));
+
+ /* Validate and apply the revision properties. */
+ revprops_array = svn_prop_hash_to_array(revprops, scratch_pool);
+ SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool));
+
+ /* ### do we need to force the txn to close, or is it enough to wait
+ ### for the pool to be cleared? */
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos__get_commit_ev2(svn_editor_t **editor,
+ svn_repos_t *repos,
+ svn_authz_t *authz,
+ const char *authz_repos_name,
+ const char *authz_user,
+ apr_hash_t *revprops,
+ svn_commit_callback2_t commit_cb,
+ void *commit_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ static const svn_editor_cb_many_t editor_cbs = {
+ add_directory_cb,
+ add_file_cb,
+ add_symlink_cb,
+ add_absent_cb,
+ alter_directory_cb,
+ alter_file_cb,
+ alter_symlink_cb,
+ delete_cb,
+ copy_cb,
+ move_cb,
+ rotate_cb,
+ complete_cb,
+ abort_cb
+ };
+ struct ev2_baton *eb;
+ const svn_string_t *author;
+ apr_hash_t *hooks_env;
+
+ /* Parse the hooks-env file (if any). */
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
+ scratch_pool, scratch_pool));
+
+ /* Can the user modify the repository at all? */
+ /* ### check against AUTHZ. */
+
+ author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
+
+ eb = apr_palloc(result_pool, sizeof(*eb));
+ eb->repos = repos;
+ eb->authz = authz;
+ eb->authz_repos_name = authz_repos_name;
+ eb->authz_user = authz_user;
+ eb->commit_cb = commit_cb;
+ eb->commit_baton = commit_baton;
+
+ SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
+ repos->fs, SVN_FS_TXN_CHECK_LOCKS,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+
+ /* The TXN has been created. Go ahead and apply all revision properties. */
+ SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
+
+ /* Okay... some access is allowed. Let's run the start-commit hook. */
+ SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
+ author ? author->data : NULL,
+ repos->client_capabilities,
+ eb->txn_name, scratch_pool));
+
+ /* Wrap the FS editor within our editor. */
+ SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/delta.c b/contrib/subversion/subversion/libsvn_repos/delta.c
new file mode 100644
index 0000000..51cfda7
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/delta.c
@@ -0,0 +1,1074 @@
+/*
+ * delta.c: an editor driver for expressing differences between two trees
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_hash.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_delta.h"
+#include "svn_fs.h"
+#include "svn_checksum.h"
+#include "svn_path.h"
+#include "svn_repos.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_private_config.h"
+#include "repos.h"
+
+
+
+/* THINGS TODO: Currently the code herein gives only a slight nod to
+ fully supporting directory deltas that involve renames, copies, and
+ such. */
+
+
+/* Some datatypes and declarations used throughout the file. */
+
+
+/* Parameters which remain constant throughout a delta traversal.
+ At the top of the recursion, we initialize one of these structures.
+ Then we pass it down to every call. This way, functions invoked
+ deep in the recursion can get access to this traversal's global
+ parameters, without using global variables. */
+struct context {
+ const svn_delta_editor_t *editor;
+ const char *edit_base_path;
+ svn_fs_root_t *source_root;
+ svn_fs_root_t *target_root;
+ svn_repos_authz_func_t authz_read_func;
+ void *authz_read_baton;
+ svn_boolean_t text_deltas;
+ svn_boolean_t entry_props;
+ svn_boolean_t ignore_ancestry;
+};
+
+
+/* The type of a function that accepts changes to an object's property
+ list. OBJECT is the object whose properties are being changed.
+ NAME is the name of the property to change. VALUE is the new value
+ for the property, or zero if the property should be deleted. */
+typedef svn_error_t *proplist_change_fn_t(struct context *c,
+ void *object,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+
+
+/* Some prototypes for functions used throughout. See each individual
+ function for information about what it does. */
+
+
+/* Retrieving the base revision from the path/revision hash. */
+static svn_revnum_t get_path_revision(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool);
+
+
+/* proplist_change_fn_t property changing functions. */
+static svn_error_t *change_dir_prop(struct context *c,
+ void *object,
+ const char *path,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+static svn_error_t *change_file_prop(struct context *c,
+ void *object,
+ const char *path,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+
+/* Constructing deltas for properties of files and directories. */
+static svn_error_t *delta_proplists(struct context *c,
+ const char *source_path,
+ const char *target_path,
+ proplist_change_fn_t *change_fn,
+ void *object,
+ apr_pool_t *pool);
+
+
+/* Constructing deltas for file constents. */
+static svn_error_t *send_text_delta(struct context *c,
+ void *file_baton,
+ const char *base_checksum,
+ svn_txdelta_stream_t *delta_stream,
+ apr_pool_t *pool);
+
+static svn_error_t *delta_files(struct context *c,
+ void *file_baton,
+ const char *source_path,
+ const char *target_path,
+ apr_pool_t *pool);
+
+
+/* Generic directory deltafication routines. */
+static svn_error_t *delete(struct context *c,
+ void *dir_baton,
+ const char *edit_path,
+ apr_pool_t *pool);
+
+static svn_error_t *add_file_or_dir(struct context *c,
+ void *dir_baton,
+ svn_depth_t depth,
+ const char *target_path,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool);
+
+static svn_error_t *replace_file_or_dir(struct context *c,
+ void *dir_baton,
+ svn_depth_t depth,
+ const char *source_path,
+ const char *target_path,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool);
+
+static svn_error_t *absent_file_or_dir(struct context *c,
+ void *dir_baton,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool);
+
+static svn_error_t *delta_dirs(struct context *c,
+ void *dir_baton,
+ svn_depth_t depth,
+ const char *source_path,
+ const char *target_path,
+ const char *edit_path,
+ apr_pool_t *pool);
+
+
+
+#define MAYBE_DEMOTE_DEPTH(depth) \
+ (((depth) == svn_depth_immediates || (depth) == svn_depth_files) \
+ ? svn_depth_empty \
+ : (depth))
+
+
+/* Return the error 'SVN_ERR_AUTHZ_ROOT_UNREADABLE' if PATH in ROOT is
+ * unreadable according to AUTHZ_READ_FUNC with AUTHZ_READ_BATON.
+ *
+ * PATH should be the implicit root path of an editor drive, that is,
+ * the path used by editor->open_root().
+ */
+static svn_error_t *
+authz_root_check(svn_fs_root_t *root,
+ const char *path,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ svn_boolean_t allowed;
+
+ if (authz_read_func)
+ {
+ SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, pool));
+
+ if (! allowed)
+ return svn_error_create(SVN_ERR_AUTHZ_ROOT_UNREADABLE, 0,
+ _("Unable to open root of edit"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+not_a_dir_error(const char *role,
+ const char *path)
+{
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_DIRECTORY, 0,
+ "Invalid %s directory '%s'",
+ role, path ? path : "(null)");
+}
+
+
+/* Public interface to computing directory deltas. */
+svn_error_t *
+svn_repos_dir_delta2(svn_fs_root_t *src_root,
+ const char *src_parent_dir,
+ const char *src_entry,
+ svn_fs_root_t *tgt_root,
+ const char *tgt_fullpath,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t entry_props,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool)
+{
+ void *root_baton = NULL;
+ struct context c;
+ const char *src_fullpath;
+ const svn_fs_id_t *src_id, *tgt_id;
+ svn_node_kind_t src_kind, tgt_kind;
+ svn_revnum_t rootrev;
+ int distance;
+ const char *authz_root_path;
+
+ /* SRC_PARENT_DIR must be valid. */
+ if (src_parent_dir)
+ src_parent_dir = svn_relpath_canonicalize(src_parent_dir, pool);
+ else
+ return not_a_dir_error("source parent", src_parent_dir);
+
+ /* TGT_FULLPATH must be valid. */
+ if (tgt_fullpath)
+ tgt_fullpath = svn_relpath_canonicalize(tgt_fullpath, pool);
+ else
+ return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, 0,
+ _("Invalid target path"));
+
+ if (depth == svn_depth_exclude)
+ return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("Delta depth 'exclude' not supported"));
+
+ /* Calculate the fs path implicitly used for editor->open_root, so
+ we can do an authz check on that path first. */
+ if (*src_entry)
+ authz_root_path = svn_relpath_dirname(tgt_fullpath, pool);
+ else
+ authz_root_path = tgt_fullpath;
+
+ /* Construct the full path of the source item. */
+ src_fullpath = svn_relpath_join(src_parent_dir, src_entry, pool);
+
+ /* Get the node kinds for the source and target paths. */
+ SVN_ERR(svn_fs_check_path(&tgt_kind, tgt_root, tgt_fullpath, pool));
+ SVN_ERR(svn_fs_check_path(&src_kind, src_root, src_fullpath, pool));
+
+ /* If neither of our paths exists, we don't really have anything to do. */
+ if ((tgt_kind == svn_node_none) && (src_kind == svn_node_none))
+ goto cleanup;
+
+ /* If either the source or the target is a non-directory, we
+ require that a SRC_ENTRY be supplied. */
+ if ((! *src_entry) && ((src_kind != svn_node_dir)
+ || tgt_kind != svn_node_dir))
+ return svn_error_create
+ (SVN_ERR_FS_PATH_SYNTAX, 0,
+ _("Invalid editor anchoring; at least one of the "
+ "input paths is not a directory and there was no source entry"));
+
+ /* Set the global target revision if one can be determined. */
+ if (svn_fs_is_revision_root(tgt_root))
+ {
+ SVN_ERR(editor->set_target_revision
+ (edit_baton, svn_fs_revision_root_revision(tgt_root), pool));
+ }
+ else if (svn_fs_is_txn_root(tgt_root))
+ {
+ SVN_ERR(editor->set_target_revision
+ (edit_baton, svn_fs_txn_root_base_revision(tgt_root), pool));
+ }
+
+ /* Setup our pseudo-global structure here. We need these variables
+ throughout the deltafication process, so pass them around by
+ reference to all the helper functions. */
+ c.editor = editor;
+ c.source_root = src_root;
+ c.target_root = tgt_root;
+ c.authz_read_func = authz_read_func;
+ c.authz_read_baton = authz_read_baton;
+ c.text_deltas = text_deltas;
+ c.entry_props = entry_props;
+ c.ignore_ancestry = ignore_ancestry;
+
+ /* Get our editor root's revision. */
+ rootrev = get_path_revision(src_root, src_parent_dir, pool);
+
+ /* If one or the other of our paths doesn't exist, we have to handle
+ those cases specially. */
+ if (tgt_kind == svn_node_none)
+ {
+ /* Caller thinks that target still exists, but it doesn't.
+ So transform their source path to "nothing" by deleting it. */
+ SVN_ERR(authz_root_check(tgt_root, authz_root_path,
+ authz_read_func, authz_read_baton, pool));
+ SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
+ SVN_ERR(delete(&c, root_baton, src_entry, pool));
+ goto cleanup;
+ }
+ if (src_kind == svn_node_none)
+ {
+ /* The source path no longer exists, but the target does.
+ So transform "nothing" into "something" by adding. */
+ SVN_ERR(authz_root_check(tgt_root, authz_root_path,
+ authz_read_func, authz_read_baton, pool));
+ SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
+ SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
+ src_entry, tgt_kind, pool));
+ goto cleanup;
+ }
+
+ /* Get and compare the node IDs for the source and target. */
+ SVN_ERR(svn_fs_node_id(&tgt_id, tgt_root, tgt_fullpath, pool));
+ SVN_ERR(svn_fs_node_id(&src_id, src_root, src_fullpath, pool));
+ distance = svn_fs_compare_ids(src_id, tgt_id);
+
+ if (distance == 0)
+ {
+ /* They are the same node! No-op (you gotta love those). */
+ goto cleanup;
+ }
+ else if (*src_entry)
+ {
+ /* If the nodes have different kinds, we must delete the one and
+ add the other. Also, if they are completely unrelated and
+ our caller is interested in relatedness, we do the same thing. */
+ if ((src_kind != tgt_kind)
+ || ((distance == -1) && (! ignore_ancestry)))
+ {
+ SVN_ERR(authz_root_check(tgt_root, authz_root_path,
+ authz_read_func, authz_read_baton, pool));
+ SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
+ SVN_ERR(delete(&c, root_baton, src_entry, pool));
+ SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath,
+ src_entry, tgt_kind, pool));
+ }
+ /* Otherwise, we just replace the one with the other. */
+ else
+ {
+ SVN_ERR(authz_root_check(tgt_root, authz_root_path,
+ authz_read_func, authz_read_baton, pool));
+ SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
+ SVN_ERR(replace_file_or_dir(&c, root_baton, depth, src_fullpath,
+ tgt_fullpath, src_entry,
+ tgt_kind, pool));
+ }
+ }
+ else
+ {
+ /* There is no entry given, so delta the whole parent directory. */
+ SVN_ERR(authz_root_check(tgt_root, authz_root_path,
+ authz_read_func, authz_read_baton, pool));
+ SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton));
+ SVN_ERR(delta_dirs(&c, root_baton, depth, src_fullpath,
+ tgt_fullpath, "", pool));
+ }
+
+ cleanup:
+
+ /* Make sure we close the root directory if we opened one above. */
+ if (root_baton)
+ SVN_ERR(editor->close_directory(root_baton, pool));
+
+ /* Close the edit. */
+ return editor->close_edit(edit_baton, pool);
+}
+
+
+/* Retrieving the base revision from the path/revision hash. */
+
+
+static svn_revnum_t
+get_path_revision(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_revnum_t revision;
+ svn_error_t *err;
+
+ /* Easy out -- if ROOT is a revision root, we can use the revision
+ that it's a root of. */
+ if (svn_fs_is_revision_root(root))
+ return svn_fs_revision_root_revision(root);
+
+ /* Else, this must be a transaction root, so ask the filesystem in
+ what revision this path was created. */
+ if ((err = svn_fs_node_created_rev(&revision, root, path, pool)))
+ {
+ revision = SVN_INVALID_REVNUM;
+ svn_error_clear(err);
+ }
+
+ /* If we don't get back a valid revision, this path is mutable in
+ the transaction. We should probably examine the node on which it
+ is based, doable by querying for the node-id of the path, and
+ then examining that node-id's predecessor. ### This predecessor
+ determination isn't exposed via the FS public API right now, so
+ for now, we'll just return the SVN_INVALID_REVNUM. */
+ return revision;
+}
+
+
+/* proplist_change_fn_t property changing functions. */
+
+
+/* Call the directory property-setting function of C->editor to set
+ the property NAME to given VALUE on the OBJECT passed to this
+ function. */
+static svn_error_t *
+change_dir_prop(struct context *c,
+ void *object,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ return c->editor->change_dir_prop(object, name, value, pool);
+}
+
+
+/* Call the file property-setting function of C->editor to set the
+ property NAME to given VALUE on the OBJECT passed to this
+ function. */
+static svn_error_t *
+change_file_prop(struct context *c,
+ void *object,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ return c->editor->change_file_prop(object, name, value, pool);
+}
+
+
+
+
+/* Constructing deltas for properties of files and directories. */
+
+
+/* Generate the appropriate property editing calls to turn the
+ properties of SOURCE_PATH into those of TARGET_PATH. If
+ SOURCE_PATH is NULL, this is an add, so assume the target starts
+ with no properties. Pass OBJECT on to the editor function wrapper
+ CHANGE_FN. */
+static svn_error_t *
+delta_proplists(struct context *c,
+ const char *source_path,
+ const char *target_path,
+ proplist_change_fn_t *change_fn,
+ void *object,
+ apr_pool_t *pool)
+{
+ apr_hash_t *s_props = 0;
+ apr_hash_t *t_props = 0;
+ apr_pool_t *subpool;
+ apr_array_header_t *prop_diffs;
+ int i;
+
+ SVN_ERR_ASSERT(target_path);
+
+ /* Make a subpool for local allocations. */
+ subpool = svn_pool_create(pool);
+
+ /* If we're supposed to send entry props for all non-deleted items,
+ here we go! */
+ if (c->entry_props)
+ {
+ svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
+ svn_string_t *cr_str = NULL;
+ svn_string_t *committed_date = NULL;
+ svn_string_t *last_author = NULL;
+
+ /* Get the CR and two derivative props. ### check for error returns. */
+ SVN_ERR(svn_fs_node_created_rev(&committed_rev, c->target_root,
+ target_path, subpool));
+ if (SVN_IS_VALID_REVNUM(committed_rev))
+ {
+ svn_fs_t *fs = svn_fs_root_fs(c->target_root);
+ apr_hash_t *r_props;
+ const char *uuid;
+
+ /* Transmit the committed-rev. */
+ cr_str = svn_string_createf(subpool, "%ld",
+ committed_rev);
+ SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_REV,
+ cr_str, subpool));
+
+ SVN_ERR(svn_fs_revision_proplist(&r_props, fs, committed_rev,
+ pool));
+
+ /* Transmit the committed-date. */
+ committed_date = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
+ if (committed_date || source_path)
+ {
+ SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_DATE,
+ committed_date, subpool));
+ }
+
+ /* Transmit the last-author. */
+ last_author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR);
+ if (last_author || source_path)
+ {
+ SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_LAST_AUTHOR,
+ last_author, subpool));
+ }
+
+ /* Transmit the UUID. */
+ SVN_ERR(svn_fs_get_uuid(fs, &uuid, subpool));
+ SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_UUID,
+ svn_string_create(uuid, subpool),
+ subpool));
+ }
+ }
+
+ if (source_path)
+ {
+ svn_boolean_t changed;
+
+ /* Is this deltification worth our time? */
+ SVN_ERR(svn_fs_props_changed(&changed, c->target_root, target_path,
+ c->source_root, source_path, subpool));
+ if (! changed)
+ goto cleanup;
+
+ /* If so, go ahead and get the source path's properties. */
+ SVN_ERR(svn_fs_node_proplist(&s_props, c->source_root,
+ source_path, subpool));
+ }
+ else
+ {
+ s_props = apr_hash_make(subpool);
+ }
+
+ /* Get the target path's properties */
+ SVN_ERR(svn_fs_node_proplist(&t_props, c->target_root,
+ target_path, subpool));
+
+ /* Now transmit the differences. */
+ SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, subpool));
+ for (i = 0; i < prop_diffs->nelts; i++)
+ {
+ const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
+ SVN_ERR(change_fn(c, object, pc->name, pc->value, subpool));
+ }
+
+ cleanup:
+ /* Destroy local subpool. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+/* Constructing deltas for file contents. */
+
+
+/* Change the contents of FILE_BATON in C->editor, according to the
+ text delta from DELTA_STREAM. Pass BASE_CHECKSUM along to
+ C->editor->apply_textdelta. */
+static svn_error_t *
+send_text_delta(struct context *c,
+ void *file_baton,
+ const char *base_checksum,
+ svn_txdelta_stream_t *delta_stream,
+ apr_pool_t *pool)
+{
+ svn_txdelta_window_handler_t delta_handler;
+ void *delta_handler_baton;
+
+ /* Get a handler that will apply the delta to the file. */
+ SVN_ERR(c->editor->apply_textdelta
+ (file_baton, base_checksum, pool,
+ &delta_handler, &delta_handler_baton));
+
+ if (c->text_deltas && delta_stream)
+ {
+ /* Deliver the delta stream to the file. */
+ return svn_txdelta_send_txstream(delta_stream,
+ delta_handler,
+ delta_handler_baton,
+ pool);
+ }
+ else
+ {
+ /* The caller doesn't want text delta data. Just send a single
+ NULL window. */
+ return delta_handler(NULL, delta_handler_baton);
+ }
+}
+
+svn_error_t *
+svn_repos__compare_files(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ svn_filesize_t size1, size2;
+ svn_checksum_t *checksum1, *checksum2;
+ svn_stream_t *stream1, *stream2;
+ svn_boolean_t same;
+
+ /* If the filesystem claims the things haven't changed, then they
+ haven't changed. */
+ SVN_ERR(svn_fs_contents_changed(changed_p, root1, path1,
+ root2, path2, pool));
+ if (!*changed_p)
+ return SVN_NO_ERROR;
+
+ /* If the SHA1 checksums match for these things, we'll claim they
+ have the same contents. (We don't give quite as much weight to
+ MD5 checksums.) */
+ SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_sha1,
+ root1, path1, FALSE, pool));
+ SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_sha1,
+ root2, path2, FALSE, pool));
+ if (checksum1 && checksum2)
+ {
+ *changed_p = !svn_checksum_match(checksum1, checksum2);
+ return SVN_NO_ERROR;
+ }
+
+ /* From this point on, our default answer is "Nothing's changed". */
+ *changed_p = FALSE;
+
+ /* Different filesizes means the contents are different. */
+ SVN_ERR(svn_fs_file_length(&size1, root1, path1, pool));
+ SVN_ERR(svn_fs_file_length(&size2, root2, path2, pool));
+ if (size1 != size2)
+ {
+ *changed_p = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Different MD5 checksums means the contents are different. */
+ SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_md5, root1, path1,
+ FALSE, pool));
+ SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_md5, root2, path2,
+ FALSE, pool));
+ if (! svn_checksum_match(checksum1, checksum2))
+ {
+ *changed_p = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* And finally, different contents means the ... uh ... contents are
+ different. */
+ SVN_ERR(svn_fs_file_contents(&stream1, root1, path1, pool));
+ SVN_ERR(svn_fs_file_contents(&stream2, root2, path2, pool));
+ SVN_ERR(svn_stream_contents_same2(&same, stream1, stream2, pool));
+ *changed_p = !same;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Make the appropriate edits on FILE_BATON to change its contents and
+ properties from those in SOURCE_PATH to those in TARGET_PATH. */
+static svn_error_t *
+delta_files(struct context *c,
+ void *file_baton,
+ const char *source_path,
+ const char *target_path,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool;
+ svn_boolean_t changed = TRUE;
+
+ SVN_ERR_ASSERT(target_path);
+
+ /* Make a subpool for local allocations. */
+ subpool = svn_pool_create(pool);
+
+ /* Compare the files' property lists. */
+ SVN_ERR(delta_proplists(c, source_path, target_path,
+ change_file_prop, file_baton, subpool));
+
+ if (source_path)
+ {
+ /* Is this delta calculation worth our time? If we are ignoring
+ ancestry, then our editor implementor isn't concerned by the
+ theoretical differences between "has contents which have not
+ changed with respect to" and "has the same actual contents
+ as". We'll do everything we can to avoid transmitting even
+ an empty text-delta in that case. */
+ if (c->ignore_ancestry)
+ SVN_ERR(svn_repos__compare_files(&changed,
+ c->target_root, target_path,
+ c->source_root, source_path,
+ subpool));
+ else
+ SVN_ERR(svn_fs_contents_changed(&changed,
+ c->target_root, target_path,
+ c->source_root, source_path,
+ subpool));
+ }
+ else
+ {
+ /* If there isn't a source path, this is an add, which
+ necessarily has textual mods. */
+ }
+
+ /* If there is a change, and the context indicates that we should
+ care about it, then hand it off to a delta stream. */
+ if (changed)
+ {
+ svn_txdelta_stream_t *delta_stream = NULL;
+ svn_checksum_t *source_checksum;
+ const char *source_hex_digest = NULL;
+
+ if (c->text_deltas)
+ {
+ /* Get a delta stream turning an empty file into one having
+ TARGET_PATH's contents. */
+ SVN_ERR(svn_fs_get_file_delta_stream
+ (&delta_stream,
+ source_path ? c->source_root : NULL,
+ source_path ? source_path : NULL,
+ c->target_root, target_path, subpool));
+ }
+
+ if (source_path)
+ {
+ SVN_ERR(svn_fs_file_checksum(&source_checksum, svn_checksum_md5,
+ c->source_root, source_path, TRUE,
+ subpool));
+
+ source_hex_digest = svn_checksum_to_cstring(source_checksum,
+ subpool);
+ }
+
+ SVN_ERR(send_text_delta(c, file_baton, source_hex_digest,
+ delta_stream, subpool));
+ }
+
+ /* Cleanup. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+/* Generic directory deltafication routines. */
+
+
+/* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON. */
+static svn_error_t *
+delete(struct context *c,
+ void *dir_baton,
+ const char *edit_path,
+ apr_pool_t *pool)
+{
+ return c->editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
+ dir_baton, pool);
+}
+
+
+/* If authorized, emit a delta to create the entry named TARGET_ENTRY
+ at the location EDIT_PATH. If not authorized, indicate that
+ EDIT_PATH is absent. Pass DIR_BATON through to editor functions
+ that require it. DEPTH is the depth from this point downward. */
+static svn_error_t *
+add_file_or_dir(struct context *c, void *dir_baton,
+ svn_depth_t depth,
+ const char *target_path,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool)
+{
+ struct context *context = c;
+ svn_boolean_t allowed;
+
+ SVN_ERR_ASSERT(target_path && edit_path);
+
+ if (c->authz_read_func)
+ {
+ SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
+ c->authz_read_baton, pool));
+ if (!allowed)
+ return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
+ }
+
+ if (tgt_kind == svn_node_dir)
+ {
+ void *subdir_baton;
+
+ SVN_ERR(context->editor->add_directory(edit_path, dir_baton, NULL,
+ SVN_INVALID_REVNUM, pool,
+ &subdir_baton));
+ SVN_ERR(delta_dirs(context, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
+ NULL, target_path, edit_path, pool));
+ return context->editor->close_directory(subdir_baton, pool);
+ }
+ else
+ {
+ void *file_baton;
+ svn_checksum_t *checksum;
+
+ SVN_ERR(context->editor->add_file(edit_path, dir_baton,
+ NULL, SVN_INVALID_REVNUM, pool,
+ &file_baton));
+ SVN_ERR(delta_files(context, file_baton, NULL, target_path, pool));
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
+ context->target_root, target_path,
+ TRUE, pool));
+ return context->editor->close_file
+ (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
+ }
+}
+
+
+/* If authorized, emit a delta to modify EDIT_PATH with the changes
+ from SOURCE_PATH to TARGET_PATH. If not authorized, indicate that
+ EDIT_PATH is absent. Pass DIR_BATON through to editor functions
+ that require it. DEPTH is the depth from this point downward. */
+static svn_error_t *
+replace_file_or_dir(struct context *c,
+ void *dir_baton,
+ svn_depth_t depth,
+ const char *source_path,
+ const char *target_path,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool)
+{
+ svn_revnum_t base_revision = SVN_INVALID_REVNUM;
+ svn_boolean_t allowed;
+
+ SVN_ERR_ASSERT(target_path && source_path && edit_path);
+
+ if (c->authz_read_func)
+ {
+ SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path,
+ c->authz_read_baton, pool));
+ if (!allowed)
+ return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool);
+ }
+
+ /* Get the base revision for the entry from the hash. */
+ base_revision = get_path_revision(c->source_root, source_path, pool);
+
+ if (tgt_kind == svn_node_dir)
+ {
+ void *subdir_baton;
+
+ SVN_ERR(c->editor->open_directory(edit_path, dir_baton,
+ base_revision, pool,
+ &subdir_baton));
+ SVN_ERR(delta_dirs(c, subdir_baton, MAYBE_DEMOTE_DEPTH(depth),
+ source_path, target_path, edit_path, pool));
+ return c->editor->close_directory(subdir_baton, pool);
+ }
+ else
+ {
+ void *file_baton;
+ svn_checksum_t *checksum;
+
+ SVN_ERR(c->editor->open_file(edit_path, dir_baton, base_revision,
+ pool, &file_baton));
+ SVN_ERR(delta_files(c, file_baton, source_path, target_path, pool));
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
+ c->target_root, target_path, TRUE,
+ pool));
+ return c->editor->close_file
+ (file_baton, svn_checksum_to_cstring(checksum, pool), pool);
+ }
+}
+
+
+/* In directory DIR_BATON, indicate that EDIT_PATH (relative to the
+ edit root) is absent by invoking C->editor->absent_directory or
+ C->editor->absent_file (depending on TGT_KIND). */
+static svn_error_t *
+absent_file_or_dir(struct context *c,
+ void *dir_baton,
+ const char *edit_path,
+ svn_node_kind_t tgt_kind,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(edit_path);
+
+ if (tgt_kind == svn_node_dir)
+ return c->editor->absent_directory(edit_path, dir_baton, pool);
+ else
+ return c->editor->absent_file(edit_path, dir_baton, pool);
+}
+
+
+/* Emit deltas to turn SOURCE_PATH into TARGET_PATH. Assume that
+ DIR_BATON represents the directory we're constructing to the editor
+ in the context C. */
+static svn_error_t *
+delta_dirs(struct context *c,
+ void *dir_baton,
+ svn_depth_t depth,
+ const char *source_path,
+ const char *target_path,
+ const char *edit_path,
+ apr_pool_t *pool)
+{
+ apr_hash_t *s_entries = 0, *t_entries = 0;
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool;
+
+ SVN_ERR_ASSERT(target_path);
+
+ /* Compare the property lists. */
+ SVN_ERR(delta_proplists(c, source_path, target_path,
+ change_dir_prop, dir_baton, pool));
+
+ /* Get the list of entries in each of source and target. */
+ SVN_ERR(svn_fs_dir_entries(&t_entries, c->target_root,
+ target_path, pool));
+ if (source_path)
+ SVN_ERR(svn_fs_dir_entries(&s_entries, c->source_root,
+ source_path, pool));
+
+ /* Make a subpool for local allocations. */
+ subpool = svn_pool_create(pool);
+
+ /* Loop over the hash of entries in the target, searching for its
+ partner in the source. If we find the matching partner entry,
+ use editor calls to replace the one in target with a new version
+ if necessary, then remove that entry from the source entries
+ hash. If we can't find a related node in the source, we use
+ editor calls to add the entry as a new item in the target.
+ Having handled all the entries that exist in target, any entries
+ still remaining the source entries hash represent entries that no
+ longer exist in target. Use editor calls to delete those entries
+ from the target tree. */
+ for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi))
+ {
+ const svn_fs_dirent_t *s_entry, *t_entry;
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+ const char *t_fullpath;
+ const char *e_fullpath;
+ const char *s_fullpath;
+ svn_node_kind_t tgt_kind;
+
+ /* Clear out our subpool for the next iteration... */
+ svn_pool_clear(subpool);
+
+ /* KEY is the entry name in target, VAL the dirent */
+ apr_hash_this(hi, &key, &klen, &val);
+ t_entry = val;
+ tgt_kind = t_entry->kind;
+ t_fullpath = svn_relpath_join(target_path, t_entry->name, subpool);
+ e_fullpath = svn_relpath_join(edit_path, t_entry->name, subpool);
+
+ /* Can we find something with the same name in the source
+ entries hash? */
+ if (s_entries && ((s_entry = apr_hash_get(s_entries, key, klen)) != 0))
+ {
+ svn_node_kind_t src_kind;
+
+ s_fullpath = svn_relpath_join(source_path, t_entry->name, subpool);
+ src_kind = s_entry->kind;
+
+ if (depth == svn_depth_infinity
+ || src_kind != svn_node_dir
+ || (src_kind == svn_node_dir
+ && depth == svn_depth_immediates))
+ {
+ /* Use svn_fs_compare_ids() to compare our current
+ source and target ids.
+
+ 0: means they are the same id, and this is a noop.
+ -1: means they are unrelated, so we have to delete the
+ old one and add the new one.
+ 1: means the nodes are related through ancestry, so go
+ ahead and do the replace directly. */
+ int distance = svn_fs_compare_ids(s_entry->id, t_entry->id);
+ if (distance == 0)
+ {
+ /* no-op */
+ }
+ else if ((src_kind != tgt_kind)
+ || ((distance == -1) && (! c->ignore_ancestry)))
+ {
+ SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
+ SVN_ERR(add_file_or_dir(c, dir_baton,
+ MAYBE_DEMOTE_DEPTH(depth),
+ t_fullpath, e_fullpath, tgt_kind,
+ subpool));
+ }
+ else
+ {
+ SVN_ERR(replace_file_or_dir(c, dir_baton,
+ MAYBE_DEMOTE_DEPTH(depth),
+ s_fullpath, t_fullpath,
+ e_fullpath, tgt_kind,
+ subpool));
+ }
+ }
+
+ /* Remove the entry from the source_hash. */
+ svn_hash_sets(s_entries, key, NULL);
+ }
+ else
+ {
+ if (depth == svn_depth_infinity
+ || tgt_kind != svn_node_dir
+ || (tgt_kind == svn_node_dir
+ && depth == svn_depth_immediates))
+ {
+ SVN_ERR(add_file_or_dir(c, dir_baton,
+ MAYBE_DEMOTE_DEPTH(depth),
+ t_fullpath, e_fullpath, tgt_kind,
+ subpool));
+ }
+ }
+ }
+
+ /* All that is left in the source entries hash are things that need
+ to be deleted. Delete them. */
+ if (s_entries)
+ {
+ for (hi = apr_hash_first(pool, s_entries); hi; hi = apr_hash_next(hi))
+ {
+ const svn_fs_dirent_t *s_entry;
+ void *val;
+ const char *e_fullpath;
+ svn_node_kind_t src_kind;
+
+ /* Clear out our subpool for the next iteration... */
+ svn_pool_clear(subpool);
+
+ /* KEY is the entry name in source, VAL the dirent */
+ apr_hash_this(hi, NULL, NULL, &val);
+ s_entry = val;
+ src_kind = s_entry->kind;
+ e_fullpath = svn_relpath_join(edit_path, s_entry->name, subpool);
+
+ /* Do we actually want to delete the dir if we're non-recursive? */
+ if (depth == svn_depth_infinity
+ || src_kind != svn_node_dir
+ || (src_kind == svn_node_dir
+ && depth == svn_depth_immediates))
+ {
+ SVN_ERR(delete(c, dir_baton, e_fullpath, subpool));
+ }
+ }
+ }
+
+ /* Destroy local allocation subpool. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/deprecated.c b/contrib/subversion/subversion/libsvn_repos/deprecated.c
new file mode 100644
index 0000000..7208ba6
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/deprecated.c
@@ -0,0 +1,1017 @@
+/*
+ * deprecated.c: holding file for all deprecated APIs.
+ * "we can't lose 'em, but we can shun 'em!"
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* We define this here to remove any further warnings about the usage of
+ deprecated functions in this file. */
+#define SVN_DEPRECATED
+
+#include "svn_repos.h"
+#include "svn_compat.h"
+#include "svn_hash.h"
+#include "svn_props.h"
+
+#include "svn_private_config.h"
+
+#include "repos.h"
+
+
+
+
+/*** From commit.c ***/
+
+svn_error_t *
+svn_repos_get_commit_editor4(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url,
+ const char *base_path,
+ const char *user,
+ const char *log_msg,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_repos_authz_callback_t authz_callback,
+ void *authz_baton,
+ apr_pool_t *pool)
+{
+ apr_hash_t *revprop_table = apr_hash_make(pool);
+ if (user)
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(user, pool));
+ if (log_msg)
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
+ svn_string_create(log_msg, pool));
+ return svn_repos_get_commit_editor5(editor, edit_baton, repos, txn,
+ repos_url, base_path, revprop_table,
+ commit_callback, commit_baton,
+ authz_callback, authz_baton, pool);
+}
+
+
+svn_error_t *
+svn_repos_get_commit_editor3(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url,
+ const char *base_path,
+ const char *user,
+ const char *log_msg,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ svn_repos_authz_callback_t authz_callback,
+ void *authz_baton,
+ apr_pool_t *pool)
+{
+ svn_commit_callback2_t callback2;
+ void *callback2_baton;
+
+ svn_compat_wrap_commit_callback(&callback2, &callback2_baton,
+ callback, callback_baton,
+ pool);
+
+ return svn_repos_get_commit_editor4(editor, edit_baton, repos, txn,
+ repos_url, base_path, user,
+ log_msg, callback2,
+ callback2_baton, authz_callback,
+ authz_baton, pool);
+}
+
+
+svn_error_t *
+svn_repos_get_commit_editor2(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url,
+ const char *base_path,
+ const char *user,
+ const char *log_msg,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_get_commit_editor3(editor, edit_baton, repos, txn,
+ repos_url, base_path, user,
+ log_msg, callback, callback_baton,
+ NULL, NULL, pool);
+}
+
+
+svn_error_t *
+svn_repos_get_commit_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ const char *repos_url,
+ const char *base_path,
+ const char *user,
+ const char *log_msg,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_get_commit_editor2(editor, edit_baton, repos, NULL,
+ repos_url, base_path, user,
+ log_msg, callback,
+ callback_baton, pool);
+}
+
+svn_error_t *
+svn_repos_open(svn_repos_t **repos_p,
+ const char *path,
+ apr_pool_t *pool)
+{
+ return svn_repos_open2(repos_p, path, NULL, pool);
+}
+
+
+/*** From repos.c ***/
+struct recover_baton
+{
+ svn_error_t *(*start_callback)(void *baton);
+ void *start_callback_baton;
+};
+
+static void
+recovery_started(void *baton,
+ const svn_repos_notify_t *notify,
+ apr_pool_t *scratch_pool)
+{
+ struct recover_baton *rb = baton;
+
+ if (notify->action == svn_repos_notify_mutex_acquired
+ && rb->start_callback != NULL)
+ svn_error_clear(rb->start_callback(rb->start_callback_baton));
+}
+
+svn_error_t *
+svn_repos_recover3(const char *path,
+ svn_boolean_t nonblocking,
+ svn_error_t *(*start_callback)(void *baton),
+ void *start_callback_baton,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool)
+{
+ struct recover_baton rb;
+
+ rb.start_callback = start_callback;
+ rb.start_callback_baton = start_callback_baton;
+
+ return svn_repos_recover4(path, nonblocking, recovery_started, &rb,
+ cancel_func, cancel_baton, pool);
+}
+
+svn_error_t *
+svn_repos_recover2(const char *path,
+ svn_boolean_t nonblocking,
+ svn_error_t *(*start_callback)(void *baton),
+ void *start_callback_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_recover3(path, nonblocking,
+ start_callback, start_callback_baton,
+ NULL, NULL,
+ pool);
+}
+
+svn_error_t *
+svn_repos_recover(const char *path,
+ apr_pool_t *pool)
+{
+ return svn_repos_recover2(path, FALSE, NULL, NULL, pool);
+}
+
+svn_error_t *
+svn_repos_upgrade(const char *path,
+ svn_boolean_t nonblocking,
+ svn_error_t *(*start_callback)(void *baton),
+ void *start_callback_baton,
+ apr_pool_t *pool)
+{
+ struct recover_baton rb;
+
+ rb.start_callback = start_callback;
+ rb.start_callback_baton = start_callback_baton;
+
+ return svn_repos_upgrade2(path, nonblocking, recovery_started, &rb, pool);
+}
+
+/*** From reporter.c ***/
+svn_error_t *
+svn_repos_begin_report(void **report_baton,
+ svn_revnum_t revnum,
+ const char *username,
+ svn_repos_t *repos,
+ const char *fs_base,
+ const char *s_operand,
+ const char *switch_path,
+ svn_boolean_t text_deltas,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_begin_report2(report_baton,
+ revnum,
+ repos,
+ fs_base,
+ s_operand,
+ switch_path,
+ text_deltas,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_ancestry,
+ FALSE, /* don't send copyfrom args */
+ editor,
+ edit_baton,
+ authz_read_func,
+ authz_read_baton,
+ pool);
+}
+
+svn_error_t *
+svn_repos_begin_report2(void **report_baton,
+ svn_revnum_t revnum,
+ svn_repos_t *repos,
+ const char *fs_base,
+ const char *target,
+ const char *tgt_path,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_begin_report3(report_baton,
+ revnum,
+ repos,
+ fs_base,
+ target,
+ tgt_path,
+ text_deltas,
+ depth,
+ ignore_ancestry,
+ send_copyfrom_args,
+ editor,
+ edit_baton,
+ authz_read_func,
+ authz_read_baton,
+ 0, /* disable zero-copy code path */
+ pool);
+}
+
+svn_error_t *
+svn_repos_set_path2(void *baton, const char *path, svn_revnum_t rev,
+ svn_boolean_t start_empty, const char *lock_token,
+ apr_pool_t *pool)
+{
+ return svn_repos_set_path3(baton, path, rev, svn_depth_infinity,
+ start_empty, lock_token, pool);
+}
+
+svn_error_t *
+svn_repos_set_path(void *baton, const char *path, svn_revnum_t rev,
+ svn_boolean_t start_empty, apr_pool_t *pool)
+{
+ return svn_repos_set_path2(baton, path, rev, start_empty, NULL, pool);
+}
+
+svn_error_t *
+svn_repos_link_path2(void *baton, const char *path, const char *link_path,
+ svn_revnum_t rev, svn_boolean_t start_empty,
+ const char *lock_token, apr_pool_t *pool)
+{
+ return svn_repos_link_path3(baton, path, link_path, rev, svn_depth_infinity,
+ start_empty, lock_token, pool);
+}
+
+svn_error_t *
+svn_repos_link_path(void *baton, const char *path, const char *link_path,
+ svn_revnum_t rev, svn_boolean_t start_empty,
+ apr_pool_t *pool)
+{
+ return svn_repos_link_path2(baton, path, link_path, rev, start_empty,
+ NULL, pool);
+}
+
+/*** From dir-delta.c ***/
+svn_error_t *
+svn_repos_dir_delta(svn_fs_root_t *src_root,
+ const char *src_parent_dir,
+ const char *src_entry,
+ svn_fs_root_t *tgt_root,
+ const char *tgt_fullpath,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_boolean_t text_deltas,
+ svn_boolean_t recurse,
+ svn_boolean_t entry_props,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool)
+{
+ return svn_repos_dir_delta2(src_root,
+ src_parent_dir,
+ src_entry,
+ tgt_root,
+ tgt_fullpath,
+ editor,
+ edit_baton,
+ authz_read_func,
+ authz_read_baton,
+ text_deltas,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ entry_props,
+ ignore_ancestry,
+ pool);
+}
+
+/*** From replay.c ***/
+svn_error_t *
+svn_repos_replay(svn_fs_root_t *root,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_replay2(root,
+ "" /* the whole tree */,
+ SVN_INVALID_REVNUM, /* no low water mark */
+ FALSE /* no text deltas */,
+ editor, edit_baton,
+ NULL /* no authz func */,
+ NULL /* no authz baton */,
+ pool);
+}
+
+/*** From fs-wrap.c ***/
+svn_error_t *
+svn_repos_fs_change_rev_prop3(svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *new_value,
+ svn_boolean_t use_pre_revprop_change_hook,
+ svn_boolean_t use_post_revprop_change_hook,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_fs_change_rev_prop4(repos, rev, author, name, NULL,
+ new_value,
+ use_pre_revprop_change_hook,
+ use_post_revprop_change_hook,
+ authz_read_func,
+ authz_read_baton, pool);
+}
+
+svn_error_t *
+svn_repos_fs_change_rev_prop2(svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *new_value,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_fs_change_rev_prop3(repos, rev, author, name, new_value,
+ TRUE, TRUE, authz_read_func,
+ authz_read_baton, pool);
+}
+
+
+
+svn_error_t *
+svn_repos_fs_change_rev_prop(svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *new_value,
+ apr_pool_t *pool)
+{
+ return svn_repos_fs_change_rev_prop2(repos, rev, author, name, new_value,
+ NULL, NULL, pool);
+}
+
+struct pack_notify_wrapper_baton
+{
+ svn_fs_pack_notify_t notify_func;
+ void *notify_baton;
+};
+
+static void
+pack_notify_wrapper_func(void *baton,
+ const svn_repos_notify_t *notify,
+ apr_pool_t *scratch_pool)
+{
+ struct pack_notify_wrapper_baton *pnwb = baton;
+
+ svn_error_clear(pnwb->notify_func(pnwb->notify_baton, notify->shard,
+ notify->action - 3, scratch_pool));
+}
+
+svn_error_t *
+svn_repos_fs_pack(svn_repos_t *repos,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ struct pack_notify_wrapper_baton pnwb;
+
+ pnwb.notify_func = notify_func;
+ pnwb.notify_baton = notify_baton;
+
+ return svn_repos_fs_pack2(repos, pack_notify_wrapper_func, &pnwb,
+ cancel_func, cancel_baton, pool);
+}
+
+
+svn_error_t *
+svn_repos_fs_get_locks(apr_hash_t **locks,
+ svn_repos_t *repos,
+ const char *path,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_repos_fs_get_locks2(locks, repos, path,
+ svn_depth_infinity,
+ authz_read_func,
+ authz_read_baton, pool));
+}
+
+
+/*** From logs.c ***/
+svn_error_t *
+svn_repos_get_logs3(svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ svn_log_entry_receiver_t receiver2;
+ void *receiver2_baton;
+
+ svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
+ receiver, receiver_baton,
+ pool);
+
+ return svn_repos_get_logs4(repos, paths, start, end, limit,
+ discover_changed_paths, strict_node_history,
+ FALSE, svn_compat_log_revprops_in(pool),
+ authz_read_func, authz_read_baton,
+ receiver2, receiver2_baton,
+ pool);
+}
+
+svn_error_t *
+svn_repos_get_logs2(svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_get_logs3(repos, paths, start, end, 0,
+ discover_changed_paths, strict_node_history,
+ authz_read_func, authz_read_baton, receiver,
+ receiver_baton, pool);
+}
+
+
+svn_error_t *
+svn_repos_get_logs(svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_get_logs3(repos, paths, start, end, 0,
+ discover_changed_paths, strict_node_history,
+ NULL, NULL, /* no authz stuff */
+ receiver, receiver_baton, pool);
+}
+
+/*** From rev_hunt.c ***/
+svn_error_t *
+svn_repos_history(svn_fs_t *fs,
+ const char *path,
+ svn_repos_history_func_t history_func,
+ void *history_baton,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t cross_copies,
+ apr_pool_t *pool)
+{
+ return svn_repos_history2(fs, path, history_func, history_baton,
+ NULL, NULL,
+ start, end, cross_copies, pool);
+}
+
+svn_error_t *
+svn_repos_get_file_revs(svn_repos_t *repos,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_repos_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_file_rev_handler_t handler2;
+ void *handler2_baton;
+
+ svn_compat_wrap_file_rev_handler(&handler2, &handler2_baton, handler,
+ handler_baton, pool);
+
+ return svn_repos_get_file_revs2(repos, path, start, end, FALSE,
+ authz_read_func, authz_read_baton,
+ handler2, handler2_baton, pool);
+}
+
+/*** From dump.c ***/
+svn_error_t *
+svn_repos_dump_fs(svn_repos_t *repos,
+ svn_stream_t *stream,
+ svn_stream_t *feedback_stream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_dump_fs2(repos, stream, feedback_stream, start_rev,
+ end_rev, incremental, FALSE, cancel_func,
+ cancel_baton, pool);
+}
+
+/* Implementation of svn_repos_notify_func_t to wrap the output to a
+ response stream for svn_repos_dump_fs2() and svn_repos_verify_fs() */
+static void
+repos_notify_handler(void *baton,
+ const svn_repos_notify_t *notify,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *feedback_stream = baton;
+ apr_size_t len;
+
+ switch (notify->action)
+ {
+ case svn_repos_notify_warning:
+ svn_error_clear(svn_stream_puts(feedback_stream, notify->warning_str));
+ return;
+
+ case svn_repos_notify_dump_rev_end:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("* Dumped revision %ld.\n"),
+ notify->revision));
+ return;
+
+ case svn_repos_notify_verify_rev_end:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("* Verified revision %ld.\n"),
+ notify->revision));
+ return;
+
+ case svn_repos_notify_load_txn_committed:
+ if (notify->old_revision == SVN_INVALID_REVNUM)
+ {
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("\n------- Committed revision %ld >>>\n\n"),
+ notify->new_revision));
+ }
+ else
+ {
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("\n------- Committed new rev %ld"
+ " (loaded from original rev %ld"
+ ") >>>\n\n"), notify->new_revision,
+ notify->old_revision));
+ }
+ return;
+
+ case svn_repos_notify_load_node_start:
+ {
+ switch (notify->node_action)
+ {
+ case svn_node_action_change:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _(" * editing path : %s ..."),
+ notify->path));
+ break;
+
+ case svn_node_action_delete:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _(" * deleting path : %s ..."),
+ notify->path));
+ break;
+
+ case svn_node_action_add:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _(" * adding path : %s ..."),
+ notify->path));
+ break;
+
+ case svn_node_action_replace:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _(" * replacing path : %s ..."),
+ notify->path));
+ break;
+
+ }
+ }
+ return;
+
+ case svn_repos_notify_load_node_done:
+ len = 7;
+ svn_error_clear(svn_stream_write(feedback_stream, _(" done.\n"), &len));
+ return;
+
+ case svn_repos_notify_load_copied_node:
+ len = 9;
+ svn_error_clear(svn_stream_write(feedback_stream, "COPIED...", &len));
+ return;
+
+ case svn_repos_notify_load_txn_start:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("<<< Started new transaction, based on "
+ "original revision %ld\n"),
+ notify->old_revision));
+ return;
+
+ case svn_repos_notify_load_normalized_mergeinfo:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _(" removing '\\r' from %s ..."),
+ SVN_PROP_MERGEINFO));
+ return;
+
+ default:
+ return;
+ }
+}
+
+
+svn_error_t *
+svn_repos_dump_fs2(svn_repos_t *repos,
+ svn_stream_t *stream,
+ svn_stream_t *feedback_stream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t incremental,
+ svn_boolean_t use_deltas,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_repos_dump_fs3(repos,
+ stream,
+ start_rev,
+ end_rev,
+ incremental,
+ use_deltas,
+ feedback_stream
+ ? repos_notify_handler
+ : NULL,
+ feedback_stream,
+ cancel_func,
+ cancel_baton,
+ pool));
+}
+
+svn_error_t *
+svn_repos_verify_fs(svn_repos_t *repos,
+ svn_stream_t *feedback_stream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_repos_verify_fs2(repos,
+ start_rev,
+ end_rev,
+ feedback_stream
+ ? repos_notify_handler
+ : NULL,
+ feedback_stream,
+ cancel_func,
+ cancel_baton,
+ pool));
+}
+
+/*** From load.c ***/
+
+svn_error_t *
+svn_repos_load_fs3(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_boolean_t use_pre_commit_hook,
+ svn_boolean_t use_post_commit_hook,
+ svn_boolean_t validate_props,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_load_fs4(repos, dumpstream,
+ SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
+ uuid_action, parent_dir,
+ use_pre_commit_hook, use_post_commit_hook,
+ validate_props, notify_func, notify_baton,
+ cancel_func, cancel_baton, pool);
+}
+
+svn_error_t *
+svn_repos_load_fs2(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_stream_t *feedback_stream,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_boolean_t use_pre_commit_hook,
+ svn_boolean_t use_post_commit_hook,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_load_fs3(repos, dumpstream, uuid_action, parent_dir,
+ use_pre_commit_hook, use_post_commit_hook, FALSE,
+ feedback_stream ? repos_notify_handler : NULL,
+ feedback_stream, cancel_func, cancel_baton, pool);
+}
+
+
+static svn_repos_parser_fns_t *
+fns_from_fns2(const svn_repos_parse_fns2_t *fns2,
+ apr_pool_t *pool)
+{
+ svn_repos_parser_fns_t *fns;
+
+ fns = apr_palloc(pool, sizeof(*fns));
+ fns->new_revision_record = fns2->new_revision_record;
+ fns->uuid_record = fns2->uuid_record;
+ fns->new_node_record = fns2->new_node_record;
+ fns->set_revision_property = fns2->set_revision_property;
+ fns->set_node_property = fns2->set_node_property;
+ fns->remove_node_props = fns2->remove_node_props;
+ fns->set_fulltext = fns2->set_fulltext;
+ fns->close_node = fns2->close_node;
+ fns->close_revision = fns2->close_revision;
+ return fns;
+}
+
+static svn_repos_parser_fns2_t *
+fns2_from_fns3(const svn_repos_parse_fns3_t *fns3,
+ apr_pool_t *pool)
+{
+ svn_repos_parser_fns2_t *fns2;
+
+ fns2 = apr_palloc(pool, sizeof(*fns2));
+ fns2->new_revision_record = fns3->new_revision_record;
+ fns2->uuid_record = fns3->uuid_record;
+ fns2->new_node_record = fns3->new_node_record;
+ fns2->set_revision_property = fns3->set_revision_property;
+ fns2->set_node_property = fns3->set_node_property;
+ fns2->remove_node_props = fns3->remove_node_props;
+ fns2->set_fulltext = fns3->set_fulltext;
+ fns2->close_node = fns3->close_node;
+ fns2->close_revision = fns3->close_revision;
+ fns2->delete_node_property = fns3->delete_node_property;
+ fns2->apply_textdelta = fns3->apply_textdelta;
+ return fns2;
+}
+
+static svn_repos_parse_fns2_t *
+fns2_from_fns(const svn_repos_parser_fns_t *fns,
+ apr_pool_t *pool)
+{
+ svn_repos_parse_fns2_t *fns2;
+
+ fns2 = apr_palloc(pool, sizeof(*fns2));
+ fns2->new_revision_record = fns->new_revision_record;
+ fns2->uuid_record = fns->uuid_record;
+ fns2->new_node_record = fns->new_node_record;
+ fns2->set_revision_property = fns->set_revision_property;
+ fns2->set_node_property = fns->set_node_property;
+ fns2->remove_node_props = fns->remove_node_props;
+ fns2->set_fulltext = fns->set_fulltext;
+ fns2->close_node = fns->close_node;
+ fns2->close_revision = fns->close_revision;
+ fns2->delete_node_property = NULL;
+ fns2->apply_textdelta = NULL;
+ return fns2;
+}
+
+static svn_repos_parse_fns3_t *
+fns3_from_fns2(const svn_repos_parser_fns2_t *fns2,
+ apr_pool_t *pool)
+{
+ svn_repos_parse_fns3_t *fns3;
+
+ fns3 = apr_palloc(pool, sizeof(*fns3));
+ fns3->magic_header_record = NULL;
+ fns3->uuid_record = fns2->uuid_record;
+ fns3->new_revision_record = fns2->new_revision_record;
+ fns3->new_node_record = fns2->new_node_record;
+ fns3->set_revision_property = fns2->set_revision_property;
+ fns3->set_node_property = fns2->set_node_property;
+ fns3->remove_node_props = fns2->remove_node_props;
+ fns3->set_fulltext = fns2->set_fulltext;
+ fns3->close_node = fns2->close_node;
+ fns3->close_revision = fns2->close_revision;
+ fns3->delete_node_property = fns2->delete_node_property;
+ fns3->apply_textdelta = fns2->apply_textdelta;
+ return fns3;
+}
+
+svn_error_t *
+svn_repos_parse_dumpstream2(svn_stream_t *stream,
+ const svn_repos_parser_fns2_t *parse_fns,
+ void *parse_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_parse_fns3_t *fns3 = fns3_from_fns2(parse_fns, pool);
+
+ return svn_repos_parse_dumpstream3(stream, fns3, parse_baton, FALSE,
+ cancel_func, cancel_baton, pool);
+}
+
+svn_error_t *
+svn_repos_parse_dumpstream(svn_stream_t *stream,
+ const svn_repos_parser_fns_t *parse_fns,
+ void *parse_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_parse_fns2_t *fns2 = fns2_from_fns(parse_fns, pool);
+
+ return svn_repos_parse_dumpstream2(stream, fns2, parse_baton,
+ cancel_func, cancel_baton, pool);
+}
+
+svn_error_t *
+svn_repos_load_fs(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_stream_t *feedback_stream,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_load_fs2(repos, dumpstream, feedback_stream,
+ uuid_action, parent_dir, FALSE, FALSE,
+ cancel_func, cancel_baton, pool);
+}
+
+svn_error_t *
+svn_repos_get_fs_build_parser3(const svn_repos_parse_fns2_t **callbacks,
+ void **parse_baton,
+ svn_repos_t *repos,
+ svn_boolean_t use_history,
+ svn_boolean_t validate_props,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ const svn_repos_parse_fns3_t *fns3;
+
+ SVN_ERR(svn_repos_get_fs_build_parser4(&fns3, parse_baton, repos,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ use_history, validate_props,
+ uuid_action, parent_dir,
+ notify_func, notify_baton, pool));
+
+ *callbacks = fns2_from_fns3(fns3, pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_get_fs_build_parser2(const svn_repos_parse_fns2_t **parser,
+ void **parse_baton,
+ svn_repos_t *repos,
+ svn_boolean_t use_history,
+ enum svn_repos_load_uuid uuid_action,
+ svn_stream_t *outstream,
+ const char *parent_dir,
+ apr_pool_t *pool)
+{
+ return svn_repos_get_fs_build_parser3(parser, parse_baton, repos, use_history,
+ FALSE, uuid_action, parent_dir,
+ outstream ? repos_notify_handler : NULL,
+ outstream, pool);
+}
+
+svn_error_t *
+svn_repos_get_fs_build_parser(const svn_repos_parser_fns_t **parser_callbacks,
+ void **parse_baton,
+ svn_repos_t *repos,
+ svn_boolean_t use_history,
+ enum svn_repos_load_uuid uuid_action,
+ svn_stream_t *outstream,
+ const char *parent_dir,
+ apr_pool_t *pool)
+{
+ const svn_repos_parse_fns2_t *fns2;
+
+ SVN_ERR(svn_repos_get_fs_build_parser2(&fns2, parse_baton, repos,
+ use_history, uuid_action, outstream,
+ parent_dir, pool));
+
+ *parser_callbacks = fns_from_fns2(fns2, pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_fs_begin_txn_for_update(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ apr_pool_t *pool)
+{
+ /* ### someday, we might run a read-hook here. */
+
+ /* Begin the transaction. */
+ SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev, 0, pool));
+
+ /* We pass the author to the filesystem by adding it as a property
+ on the txn. */
+
+ /* User (author). */
+ if (author)
+ {
+ svn_string_t val;
+ val.data = author;
+ val.len = strlen(author);
+ SVN_ERR(svn_fs_change_txn_prop(*txn_p, SVN_PROP_REVISION_AUTHOR,
+ &val, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*** From authz.c ***/
+
+svn_error_t *
+svn_repos_authz_read(svn_authz_t **authz_p, const char *file,
+ svn_boolean_t must_exist, apr_pool_t *pool)
+{
+ return svn_repos__authz_read(authz_p, file, NULL, must_exist,
+ FALSE, pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/dump.c b/contrib/subversion/subversion/libsvn_repos/dump.c
new file mode 100644
index 0000000..75843d7
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/dump.c
@@ -0,0 +1,1503 @@
+/* dump.c --- writing filesystem contents into a portable 'dumpfile' format.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include "svn_private_config.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_hash.h"
+#include "svn_iter.h"
+#include "svn_repos.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_time.h"
+#include "svn_checksum.h"
+#include "svn_props.h"
+#include "svn_sorts.h"
+
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_fs_private.h"
+
+#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
+
+/*----------------------------------------------------------------------*/
+
+
+
+/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
+ store it into a new temporary file *TEMPFILE. OLDROOT may be NULL,
+ in which case the delta will be computed against an empty file, as
+ per the svn_fs_get_file_delta_stream docstring. Record the length
+ of the temporary file in *LEN, and rewind the file before
+ returning. */
+static svn_error_t *
+store_delta(apr_file_t **tempfile, svn_filesize_t *len,
+ svn_fs_root_t *oldroot, const char *oldpath,
+ svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
+{
+ svn_stream_t *temp_stream;
+ apr_off_t offset = 0;
+ svn_txdelta_stream_t *delta_stream;
+ svn_txdelta_window_handler_t wh;
+ void *whb;
+
+ /* Create a temporary file and open a stream to it. Note that we need
+ the file handle in order to rewind it. */
+ SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ pool, pool));
+ temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool);
+
+ /* Compute the delta and send it to the temporary file. */
+ SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
+ newroot, newpath, pool));
+ svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+ SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
+
+ /* Get the length of the temporary file and rewind it. */
+ SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool));
+ *len = offset;
+ offset = 0;
+ return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
+}
+
+
+/*----------------------------------------------------------------------*/
+
+/** An editor which dumps node-data in 'dumpfile format' to a file. **/
+
+/* Look, mom! No file batons! */
+
+struct edit_baton
+{
+ /* The relpath which implicitly prepends all full paths coming into
+ this editor. This will almost always be "". */
+ const char *path;
+
+ /* The stream to dump to. */
+ svn_stream_t *stream;
+
+ /* Send feedback here, if non-NULL */
+ svn_repos_notify_func_t notify_func;
+ void *notify_baton;
+
+ /* The fs revision root, so we can read the contents of paths. */
+ svn_fs_root_t *fs_root;
+ svn_revnum_t current_rev;
+
+ /* The fs, so we can grab historic information if needed. */
+ svn_fs_t *fs;
+
+ /* True if dumped nodes should output deltas instead of full text. */
+ svn_boolean_t use_deltas;
+
+ /* True if this "dump" is in fact a verify. */
+ svn_boolean_t verify;
+
+ /* The first revision dumped in this dumpstream. */
+ svn_revnum_t oldest_dumped_rev;
+
+ /* If not NULL, set to true if any references to revisions older than
+ OLDEST_DUMPED_REV were found in the dumpstream. */
+ svn_boolean_t *found_old_reference;
+
+ /* If not NULL, set to true if any mergeinfo was dumped which contains
+ revisions older than OLDEST_DUMPED_REV. */
+ svn_boolean_t *found_old_mergeinfo;
+
+ /* reusable buffer for writing file contents */
+ char buffer[SVN__STREAM_CHUNK_SIZE];
+ apr_size_t bufsize;
+};
+
+struct dir_baton
+{
+ struct edit_baton *edit_baton;
+ struct dir_baton *parent_dir_baton;
+
+ /* is this directory a new addition to this revision? */
+ svn_boolean_t added;
+
+ /* has this directory been written to the output stream? */
+ svn_boolean_t written_out;
+
+ /* the repository relpath associated with this directory */
+ const char *path;
+
+ /* The comparison repository relpath and revision of this directory.
+ If both of these are valid, use them as a source against which to
+ compare the directory instead of the default comparison source of
+ PATH in the previous revision. */
+ const char *cmp_path;
+ svn_revnum_t cmp_rev;
+
+ /* hash of paths that need to be deleted, though some -might- be
+ replaced. maps const char * paths to this dir_baton. (they're
+ full paths, because that's what the editor driver gives us. but
+ really, they're all within this directory.) */
+ apr_hash_t *deleted_entries;
+
+ /* pool to be used for deleting the hash items */
+ apr_pool_t *pool;
+};
+
+
+/* Make a directory baton to represent the directory was path
+ (relative to EDIT_BATON's path) is PATH.
+
+ CMP_PATH/CMP_REV are the path/revision against which this directory
+ should be compared for changes. If either is omitted (NULL for the
+ path, SVN_INVALID_REVNUM for the rev), just compare this directory
+ PATH against itself in the previous revision.
+
+ PARENT_DIR_BATON is the directory baton of this directory's parent,
+ or NULL if this is the top-level directory of the edit. ADDED
+ indicated if this directory is newly added in this revision.
+ Perform all allocations in POOL. */
+static struct dir_baton *
+make_dir_baton(const char *path,
+ const char *cmp_path,
+ svn_revnum_t cmp_rev,
+ void *edit_baton,
+ void *parent_dir_baton,
+ svn_boolean_t added,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *pb = parent_dir_baton;
+ struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
+ const char *full_path;
+
+ /* A path relative to nothing? I don't think so. */
+ SVN_ERR_ASSERT_NO_RETURN(!path || pb);
+
+ /* Construct the full path of this node. */
+ if (pb)
+ full_path = svn_relpath_join(eb->path, path, pool);
+ else
+ full_path = apr_pstrdup(pool, eb->path);
+
+ /* Remove leading slashes from copyfrom paths. */
+ if (cmp_path)
+ cmp_path = svn_relpath_canonicalize(cmp_path, pool);
+
+ new_db->edit_baton = eb;
+ new_db->parent_dir_baton = pb;
+ new_db->path = full_path;
+ new_db->cmp_path = cmp_path;
+ new_db->cmp_rev = cmp_rev;
+ new_db->added = added;
+ new_db->written_out = FALSE;
+ new_db->deleted_entries = apr_hash_make(pool);
+ new_db->pool = pool;
+
+ return new_db;
+}
+
+
+/* This helper is the main "meat" of the editor -- it does all the
+ work of writing a node record.
+
+ Write out a node record for PATH of type KIND under EB->FS_ROOT.
+ ACTION describes what is happening to the node (see enum svn_node_action).
+ Write record to writable EB->STREAM, using EB->BUFFER to write in chunks.
+
+ If the node was itself copied, IS_COPY is TRUE and the
+ path/revision of the copy source are in CMP_PATH/CMP_REV. If
+ IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
+ of a copied subtree.
+ */
+static svn_error_t *
+dump_node(struct edit_baton *eb,
+ const char *path,
+ svn_node_kind_t kind,
+ enum svn_node_action action,
+ svn_boolean_t is_copy,
+ const char *cmp_path,
+ svn_revnum_t cmp_rev,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *propstring;
+ svn_filesize_t content_length = 0;
+ apr_size_t len;
+ svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
+ const char *compare_path = path;
+ svn_revnum_t compare_rev = eb->current_rev - 1;
+ svn_fs_root_t *compare_root = NULL;
+ apr_file_t *delta_file = NULL;
+
+ /* Maybe validate the path. */
+ if (eb->verify || eb->notify_func)
+ {
+ svn_error_t *err = svn_fs__path_valid(path, pool);
+
+ if (err)
+ {
+ if (eb->notify_func)
+ {
+ char errbuf[512]; /* ### svn_strerror() magic number */
+ svn_repos_notify_t *notify;
+ notify = svn_repos_notify_create(svn_repos_notify_warning, pool);
+
+ notify->warning = svn_repos_notify_warning_invalid_fspath;
+ notify->warning_str = apr_psprintf(
+ pool,
+ _("E%06d: While validating fspath '%s': %s"),
+ err->apr_err, path,
+ svn_err_best_message(err, errbuf, sizeof(errbuf)));
+
+ eb->notify_func(eb->notify_baton, notify, pool);
+ }
+
+ /* Return the error in addition to notifying about it. */
+ if (eb->verify)
+ return svn_error_trace(err);
+ else
+ svn_error_clear(err);
+ }
+ }
+
+ /* Write out metadata headers for this file node. */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
+ path));
+ if (kind == svn_node_file)
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
+ else if (kind == svn_node_dir)
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
+
+ /* Remove leading slashes from copyfrom paths. */
+ if (cmp_path)
+ cmp_path = svn_relpath_canonicalize(cmp_path, pool);
+
+ /* Validate the comparison path/rev. */
+ if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
+ {
+ compare_path = cmp_path;
+ compare_rev = cmp_rev;
+ }
+
+ if (action == svn_node_action_change)
+ {
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n"));
+
+ /* either the text or props changed, or possibly both. */
+ SVN_ERR(svn_fs_revision_root(&compare_root,
+ svn_fs_root_fs(eb->fs_root),
+ compare_rev, pool));
+
+ SVN_ERR(svn_fs_props_changed(&must_dump_props,
+ compare_root, compare_path,
+ eb->fs_root, path, pool));
+ if (kind == svn_node_file)
+ SVN_ERR(svn_fs_contents_changed(&must_dump_text,
+ compare_root, compare_path,
+ eb->fs_root, path, pool));
+ }
+ else if (action == svn_node_action_replace)
+ {
+ if (! is_copy)
+ {
+ /* a simple delete+add, implied by a single 'replace' action. */
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION
+ ": replace\n"));
+
+ /* definitely need to dump all content for a replace. */
+ if (kind == svn_node_file)
+ must_dump_text = TRUE;
+ must_dump_props = TRUE;
+ }
+ else
+ {
+ /* more complex: delete original, then add-with-history. */
+
+ /* the path & kind headers have already been printed; just
+ add a delete action, and end the current record.*/
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION
+ ": delete\n\n"));
+
+ /* recurse: print an additional add-with-history record. */
+ SVN_ERR(dump_node(eb, path, kind, svn_node_action_add,
+ is_copy, compare_path, compare_rev, pool));
+
+ /* we can leave this routine quietly now, don't need to dump
+ any content; that was already done in the second record. */
+ must_dump_text = FALSE;
+ must_dump_props = FALSE;
+ }
+ }
+ else if (action == svn_node_action_delete)
+ {
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n"));
+
+ /* we can leave this routine quietly now, don't need to dump
+ any content. */
+ must_dump_text = FALSE;
+ must_dump_props = FALSE;
+ }
+ else if (action == svn_node_action_add)
+ {
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
+
+ if (! is_copy)
+ {
+ /* Dump all contents for a simple 'add'. */
+ if (kind == svn_node_file)
+ must_dump_text = TRUE;
+ must_dump_props = TRUE;
+ }
+ else
+ {
+ if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
+ && eb->notify_func)
+ {
+ svn_repos_notify_t *notify =
+ svn_repos_notify_create(svn_repos_notify_warning, pool);
+
+ notify->warning = svn_repos_notify_warning_found_old_reference;
+ notify->warning_str = apr_psprintf(
+ pool,
+ _("Referencing data in revision %ld,"
+ " which is older than the oldest"
+ " dumped revision (r%ld). Loading this dump"
+ " into an empty repository"
+ " will fail."),
+ cmp_rev, eb->oldest_dumped_rev);
+ if (eb->found_old_reference)
+ *eb->found_old_reference = TRUE;
+ eb->notify_func(eb->notify_baton, notify, pool);
+ }
+
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
+ ": %ld\n"
+ SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
+ ": %s\n",
+ cmp_rev, cmp_path));
+
+ SVN_ERR(svn_fs_revision_root(&compare_root,
+ svn_fs_root_fs(eb->fs_root),
+ compare_rev, pool));
+
+ /* Need to decide if the copied node had any extra textual or
+ property mods as well. */
+ SVN_ERR(svn_fs_props_changed(&must_dump_props,
+ compare_root, compare_path,
+ eb->fs_root, path, pool));
+ if (kind == svn_node_file)
+ {
+ svn_checksum_t *checksum;
+ const char *hex_digest;
+ SVN_ERR(svn_fs_contents_changed(&must_dump_text,
+ compare_root, compare_path,
+ eb->fs_root, path, pool));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
+ compare_root, compare_path,
+ FALSE, pool));
+ hex_digest = svn_checksum_to_cstring(checksum, pool);
+ if (hex_digest)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5
+ ": %s\n", hex_digest));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
+ compare_root, compare_path,
+ FALSE, pool));
+ hex_digest = svn_checksum_to_cstring(checksum, pool);
+ if (hex_digest)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1
+ ": %s\n", hex_digest));
+ }
+ }
+ }
+
+ if ((! must_dump_text) && (! must_dump_props))
+ {
+ /* If we're not supposed to dump text or props, so be it, we can
+ just go home. However, if either one needs to be dumped,
+ then our dumpstream format demands that at a *minimum*, we
+ see a lone "PROPS-END" as a divider between text and props
+ content within the content-block. */
+ len = 2;
+ return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
+ }
+
+ /*** Start prepping content to dump... ***/
+
+ /* If we are supposed to dump properties, write out a property
+ length header and generate a stringbuf that contains those
+ property values here. */
+ if (must_dump_props)
+ {
+ apr_hash_t *prophash, *oldhash = NULL;
+ apr_size_t proplen;
+ svn_stream_t *propstream;
+
+ SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
+
+ /* If this is a partial dump, then issue a warning if we dump mergeinfo
+ properties that refer to revisions older than the first revision
+ dumped. */
+ if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
+ {
+ svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
+ SVN_PROP_MERGEINFO);
+ if (mergeinfo_str)
+ {
+ svn_mergeinfo_t mergeinfo, old_mergeinfo;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str->data,
+ pool));
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &old_mergeinfo, mergeinfo,
+ eb->oldest_dumped_rev - 1, 0,
+ TRUE, pool, pool));
+ if (apr_hash_count(old_mergeinfo))
+ {
+ svn_repos_notify_t *notify =
+ svn_repos_notify_create(svn_repos_notify_warning, pool);
+
+ notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
+ notify->warning_str = apr_psprintf(
+ pool,
+ _("Mergeinfo referencing revision(s) prior "
+ "to the oldest dumped revision (r%ld). "
+ "Loading this dump may result in invalid "
+ "mergeinfo."),
+ eb->oldest_dumped_rev);
+
+ if (eb->found_old_mergeinfo)
+ *eb->found_old_mergeinfo = TRUE;
+ eb->notify_func(eb->notify_baton, notify, pool);
+ }
+ }
+ }
+
+ if (eb->use_deltas && compare_root)
+ {
+ /* Fetch the old property hash to diff against and output a header
+ saying that our property contents are a delta. */
+ SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
+ pool));
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n"));
+ }
+ else
+ oldhash = apr_hash_make(pool);
+ propstring = svn_stringbuf_create_ensure(0, pool);
+ propstream = svn_stream_from_stringbuf(propstring, pool);
+ SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
+ "PROPS-END", pool));
+ SVN_ERR(svn_stream_close(propstream));
+ proplen = propstring->len;
+ content_length += proplen;
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n", proplen));
+ }
+
+ /* If we are supposed to dump text, write out a text length header
+ here, and an MD5 checksum (if available). */
+ if (must_dump_text && (kind == svn_node_file))
+ {
+ svn_checksum_t *checksum;
+ const char *hex_digest;
+ svn_filesize_t textlen;
+
+ if (eb->use_deltas)
+ {
+ /* Compute the text delta now and write it into a temporary
+ file, so that we can find its length. Output a header
+ saying our text contents are a delta. */
+ SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
+ compare_path, eb->fs_root, path, pool));
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n"));
+
+ if (compare_root)
+ {
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
+ compare_root, compare_path,
+ FALSE, pool));
+ hex_digest = svn_checksum_to_cstring(checksum, pool);
+ if (hex_digest)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
+ ": %s\n", hex_digest));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
+ compare_root, compare_path,
+ FALSE, pool));
+ hex_digest = svn_checksum_to_cstring(checksum, pool);
+ if (hex_digest)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1
+ ": %s\n", hex_digest));
+ }
+ }
+ else
+ {
+ /* Just fetch the length of the file. */
+ SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
+ }
+
+ content_length += textlen;
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
+ ": %" SVN_FILESIZE_T_FMT "\n", textlen));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
+ eb->fs_root, path, FALSE, pool));
+ hex_digest = svn_checksum_to_cstring(checksum, pool);
+ if (hex_digest)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
+ ": %s\n", hex_digest));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
+ eb->fs_root, path, FALSE, pool));
+ hex_digest = svn_checksum_to_cstring(checksum, pool);
+ if (hex_digest)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1
+ ": %s\n", hex_digest));
+ }
+
+ /* 'Content-length:' is the last header before we dump the content,
+ and is the sum of the text and prop contents lengths. We write
+ this only for the benefit of non-Subversion RFC-822 parsers. */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" SVN_FILESIZE_T_FMT "\n\n",
+ content_length));
+
+ /* Dump property content if we're supposed to do so. */
+ if (must_dump_props)
+ {
+ len = propstring->len;
+ SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len));
+ }
+
+ /* Dump text content */
+ if (must_dump_text && (kind == svn_node_file))
+ {
+ svn_stream_t *contents;
+
+ if (delta_file)
+ {
+ /* Make sure to close the underlying file when the stream is
+ closed. */
+ contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
+ }
+ else
+ SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
+
+ SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
+ NULL, NULL, pool));
+ }
+
+ len = 2;
+ return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
+}
+
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
+ edit_baton, NULL, FALSE, pool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ const char *mypath = apr_pstrdup(pb->pool, path);
+
+ /* remember this path needs to be deleted. */
+ svn_hash_sets(pb->deleted_entries, mypath, pb);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ void *val;
+ svn_boolean_t is_copy = FALSE;
+ struct dir_baton *new_db
+ = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool);
+
+ /* This might be a replacement -- is the path already deleted? */
+ val = svn_hash_gets(pb->deleted_entries, path);
+
+ /* Detect an add-with-history. */
+ is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
+
+ /* Dump the node. */
+ SVN_ERR(dump_node(eb, path,
+ svn_node_dir,
+ val ? svn_node_action_replace : svn_node_action_add,
+ is_copy,
+ is_copy ? copyfrom_path : NULL,
+ is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
+ pool));
+
+ if (val)
+ /* Delete the path, it's now been dumped. */
+ svn_hash_sets(pb->deleted_entries, path, NULL);
+
+ new_db->written_out = TRUE;
+
+ *child_baton = new_db;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *new_db;
+ const char *cmp_path = NULL;
+ svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
+
+ /* If the parent directory has explicit comparison path and rev,
+ record the same for this one. */
+ if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
+ {
+ cmp_path = svn_relpath_join(pb->cmp_path,
+ svn_relpath_basename(path, pool), pool);
+ cmp_rev = pb->cmp_rev;
+ }
+
+ new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool);
+ *child_baton = new_db;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ int i;
+ apr_array_header_t *sorted_entries;
+
+ /* Sort entries lexically instead of as paths. Even though the entries
+ * are full paths they're all in the same directory (see comment in struct
+ * dir_baton definition). So we really want to sort by basename, in which
+ * case the lexical sort function is more efficient. */
+ sorted_entries = svn_sort__hash(db->deleted_entries,
+ svn_sort_compare_items_lexically, pool);
+ for (i = 0; i < sorted_entries->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(sorted_entries, i,
+ svn_sort__item_t).key;
+
+ svn_pool_clear(subpool);
+
+ /* By sending 'svn_node_unknown', the Node-kind: header simply won't
+ be written out. No big deal at all, really. The loader
+ shouldn't care. */
+ SVN_ERR(dump_node(eb, path,
+ svn_node_unknown, svn_node_action_delete,
+ FALSE, NULL, SVN_INVALID_REVNUM, subpool));
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ void *val;
+ svn_boolean_t is_copy = FALSE;
+
+ /* This might be a replacement -- is the path already deleted? */
+ val = svn_hash_gets(pb->deleted_entries, path);
+
+ /* Detect add-with-history. */
+ is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
+
+ /* Dump the node. */
+ SVN_ERR(dump_node(eb, path,
+ svn_node_file,
+ val ? svn_node_action_replace : svn_node_action_add,
+ is_copy,
+ is_copy ? copyfrom_path : NULL,
+ is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
+ pool));
+
+ if (val)
+ /* delete the path, it's now been dumped. */
+ svn_hash_sets(pb->deleted_entries, path, NULL);
+
+ *file_baton = NULL; /* muhahahaha */
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t ancestor_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ const char *cmp_path = NULL;
+ svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
+
+ /* If the parent directory has explicit comparison path and rev,
+ record the same for this one. */
+ if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
+ {
+ cmp_path = svn_relpath_join(pb->cmp_path,
+ svn_relpath_basename(path, pool), pool);
+ cmp_rev = pb->cmp_rev;
+ }
+
+ SVN_ERR(dump_node(eb, path,
+ svn_node_file, svn_node_action_change,
+ FALSE, cmp_path, cmp_rev, pool));
+
+ *file_baton = NULL; /* muhahahaha again */
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+change_dir_prop(void *parent_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = parent_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ /* This function is what distinguishes between a directory that is
+ opened to merely get somewhere, vs. one that is opened because it
+ *actually* changed by itself. */
+ if (! db->written_out)
+ {
+ SVN_ERR(dump_node(eb, db->path,
+ svn_node_dir, svn_node_action_change,
+ FALSE, db->cmp_path, db->cmp_rev, pool));
+ db->written_out = TRUE;
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_error_t *err;
+ svn_fs_root_t *fs_root;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->current_rev - 1;
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
+
+ err = svn_fs_node_proplist(props, fs_root, path, result_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *props = apr_hash_make(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_fs_root_t *fs_root;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->current_rev - 1;
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
+
+ SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = baton;
+ svn_stream_t *contents;
+ svn_stream_t *file_stream;
+ const char *tmp_filename;
+ svn_error_t *err;
+ svn_fs_root_t *fs_root;
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->current_rev - 1;
+
+ SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
+
+ err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+ SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
+
+ *filename = apr_pstrdup(result_pool, tmp_filename);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+get_dump_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_fs_t *fs,
+ svn_revnum_t to_rev,
+ const char *root_path,
+ svn_stream_t *stream,
+ svn_boolean_t *found_old_reference,
+ svn_boolean_t *found_old_mergeinfo,
+ svn_error_t *(*custom_close_directory)(void *dir_baton,
+ apr_pool_t *scratch_pool),
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_revnum_t oldest_dumped_rev,
+ svn_boolean_t use_deltas,
+ svn_boolean_t verify,
+ apr_pool_t *pool)
+{
+ /* Allocate an edit baton to be stored in every directory baton.
+ Set it up for the directory baton we create here, which is the
+ root baton. */
+ struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
+ svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(pool);
+
+ /* Set up the edit baton. */
+ eb->stream = stream;
+ eb->notify_func = notify_func;
+ eb->notify_baton = notify_baton;
+ eb->oldest_dumped_rev = oldest_dumped_rev;
+ eb->bufsize = sizeof(eb->buffer);
+ eb->path = apr_pstrdup(pool, root_path);
+ SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
+ eb->fs = fs;
+ eb->current_rev = to_rev;
+ eb->use_deltas = use_deltas;
+ eb->verify = verify;
+ eb->found_old_reference = found_old_reference;
+ eb->found_old_mergeinfo = found_old_mergeinfo;
+
+ /* Set up the editor. */
+ dump_editor->open_root = open_root;
+ dump_editor->delete_entry = delete_entry;
+ dump_editor->add_directory = add_directory;
+ dump_editor->open_directory = open_directory;
+ if (custom_close_directory)
+ dump_editor->close_directory = custom_close_directory;
+ else
+ dump_editor->close_directory = close_directory;
+ dump_editor->change_dir_prop = change_dir_prop;
+ dump_editor->add_file = add_file;
+ dump_editor->open_file = open_file;
+
+ *edit_baton = eb;
+ *editor = dump_editor;
+
+ shim_callbacks->fetch_kind_func = fetch_kind_func;
+ shim_callbacks->fetch_props_func = fetch_props_func;
+ shim_callbacks->fetch_base_func = fetch_base_func;
+ shim_callbacks->fetch_baton = eb;
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ NULL, NULL, shim_callbacks, pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/*----------------------------------------------------------------------*/
+
+/** The main dumping routine, svn_repos_dump_fs. **/
+
+
+/* Helper for svn_repos_dump_fs.
+
+ Write a revision record of REV in FS to writable STREAM, using POOL.
+ */
+static svn_error_t *
+write_revision_record(svn_stream_t *stream,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ apr_size_t len;
+ apr_hash_t *props;
+ svn_stringbuf_t *encoded_prophash;
+ apr_time_t timetemp;
+ svn_string_t *datevalue;
+ svn_stream_t *propstream;
+
+ /* Read the revision props even if we're aren't going to dump
+ them for verification purposes */
+ SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
+
+ /* Run revision date properties through the time conversion to
+ canonicalize them. */
+ /* ### Remove this when it is no longer needed for sure. */
+ datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
+ if (datevalue)
+ {
+ SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
+ datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
+ pool);
+ svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
+ }
+
+ encoded_prophash = svn_stringbuf_create_ensure(0, pool);
+ propstream = svn_stream_from_stringbuf(encoded_prophash, pool);
+ SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool));
+ SVN_ERR(svn_stream_close(propstream));
+
+ /* ### someday write a revision-content-checksum */
+
+ SVN_ERR(svn_stream_printf(stream, pool,
+ SVN_REPOS_DUMPFILE_REVISION_NUMBER
+ ": %ld\n", rev));
+ SVN_ERR(svn_stream_printf(stream, pool,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n",
+ encoded_prophash->len));
+
+ /* Write out a regular Content-length header for the benefit of
+ non-Subversion RFC-822 parsers. */
+ SVN_ERR(svn_stream_printf(stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n\n",
+ encoded_prophash->len));
+
+ len = encoded_prophash->len;
+ SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len));
+
+ len = 1;
+ return svn_stream_write(stream, "\n", &len);
+}
+
+
+
+/* The main dumper. */
+svn_error_t *
+svn_repos_dump_fs3(svn_repos_t *repos,
+ svn_stream_t *stream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t incremental,
+ svn_boolean_t use_deltas,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *dump_editor;
+ void *dump_edit_baton = NULL;
+ svn_revnum_t i;
+ svn_fs_t *fs = svn_repos_fs(repos);
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_revnum_t youngest;
+ const char *uuid;
+ int version;
+ svn_boolean_t found_old_reference = FALSE;
+ svn_boolean_t found_old_mergeinfo = FALSE;
+ svn_repos_notify_t *notify;
+
+ /* Determine the current youngest revision of the filesystem. */
+ SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
+
+ /* Use default vals if necessary. */
+ if (! SVN_IS_VALID_REVNUM(start_rev))
+ start_rev = 0;
+ if (! SVN_IS_VALID_REVNUM(end_rev))
+ end_rev = youngest;
+ if (! stream)
+ stream = svn_stream_empty(pool);
+
+ /* Validate the revisions. */
+ if (start_rev > end_rev)
+ return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("Start revision %ld"
+ " is greater than end revision %ld"),
+ start_rev, end_rev);
+ if (end_rev > youngest)
+ return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("End revision %ld is invalid "
+ "(youngest revision is %ld)"),
+ end_rev, youngest);
+ if ((start_rev == 0) && incremental)
+ incremental = FALSE; /* revision 0 looks the same regardless of
+ whether or not this is an incremental
+ dump, so just simplify things. */
+
+ /* Write out the UUID. */
+ SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
+
+ /* If we're not using deltas, use the previous version, for
+ compatibility with svn 1.0.x. */
+ version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
+ if (!use_deltas)
+ version--;
+
+ /* Write out "general" metadata for the dumpfile. In this case, a
+ magic header followed by a dumpfile format version. */
+ SVN_ERR(svn_stream_printf(stream, pool,
+ SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
+ version));
+ SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
+ ": %s\n\n", uuid));
+
+ /* Create a notify object that we can reuse in the loop. */
+ if (notify_func)
+ notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
+ pool);
+
+ /* Main loop: we're going to dump revision i. */
+ for (i = start_rev; i <= end_rev; i++)
+ {
+ svn_revnum_t from_rev, to_rev;
+ svn_fs_root_t *to_root;
+ svn_boolean_t use_deltas_for_rev;
+
+ svn_pool_clear(subpool);
+
+ /* Check for cancellation. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Special-case the initial revision dump: it needs to contain
+ *all* nodes, because it's the foundation of all future
+ revisions in the dumpfile. */
+ if ((i == start_rev) && (! incremental))
+ {
+ /* Special-special-case a dump of revision 0. */
+ if (i == 0)
+ {
+ /* Just write out the one revision 0 record and move on.
+ The parser might want to use its properties. */
+ SVN_ERR(write_revision_record(stream, fs, 0, subpool));
+ to_rev = 0;
+ goto loop_end;
+ }
+
+ /* Compare START_REV to revision 0, so that everything
+ appears to be added. */
+ from_rev = 0;
+ to_rev = i;
+ }
+ else
+ {
+ /* In the normal case, we want to compare consecutive revs. */
+ from_rev = i - 1;
+ to_rev = i;
+ }
+
+ /* Write the revision record. */
+ SVN_ERR(write_revision_record(stream, fs, to_rev, subpool));
+
+ /* Fetch the editor which dumps nodes to a file. Regardless of
+ what we've been told, don't use deltas for the first rev of a
+ non-incremental dump. */
+ use_deltas_for_rev = use_deltas && (incremental || i != start_rev);
+ SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev,
+ "", stream, &found_old_reference,
+ &found_old_mergeinfo, NULL,
+ notify_func, notify_baton,
+ start_rev, use_deltas_for_rev, FALSE, subpool));
+
+ /* Drive the editor in one way or another. */
+ SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool));
+
+ /* If this is the first revision of a non-incremental dump,
+ we're in for a full tree dump. Otherwise, we want to simply
+ replay the revision. */
+ if ((i == start_rev) && (! incremental))
+ {
+ svn_fs_root_t *from_root;
+ SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool));
+ SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
+ to_root, "",
+ dump_editor, dump_edit_baton,
+ NULL,
+ NULL,
+ FALSE, /* don't send text-deltas */
+ svn_depth_infinity,
+ FALSE, /* don't send entry props */
+ FALSE, /* don't ignore ancestry */
+ subpool));
+ }
+ else
+ {
+ SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
+ dump_editor, dump_edit_baton,
+ NULL, NULL, subpool));
+
+ /* While our editor close_edit implementation is a no-op, we still
+ do this for completeness. */
+ SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool));
+ }
+
+ loop_end:
+ if (notify_func)
+ {
+ notify->revision = to_rev;
+ notify_func(notify_baton, notify, subpool);
+ }
+ }
+
+ if (notify_func)
+ {
+ /* Did we issue any warnings about references to revisions older than
+ the oldest dumped revision? If so, then issue a final generic
+ warning, since the inline warnings already issued might easily be
+ missed. */
+
+ notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool);
+ notify_func(notify_baton, notify, subpool);
+
+ if (found_old_reference)
+ {
+ notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
+
+ notify->warning = svn_repos_notify_warning_found_old_reference;
+ notify->warning_str = _("The range of revisions dumped "
+ "contained references to "
+ "copy sources outside that "
+ "range.");
+ notify_func(notify_baton, notify, subpool);
+ }
+
+ /* Ditto if we issued any warnings about old revisions referenced
+ in dumped mergeinfo. */
+ if (found_old_mergeinfo)
+ {
+ notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
+
+ notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
+ notify->warning_str = _("The range of revisions dumped "
+ "contained mergeinfo "
+ "which reference revisions outside "
+ "that range.");
+ notify_func(notify_baton, notify, subpool);
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/*----------------------------------------------------------------------*/
+
+/* verify, based on dump */
+
+
+/* Creating a new revision that changes /A/B/E/bravo means creating new
+ directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
+ each entry not changed in the new revision a link back to the entry in a
+ previous revision. svn_repos_replay()ing a revision does not verify that
+ those links are correct.
+
+ For paths actually changed in the revision we verify, we get directory
+ contents or file length twice: once in the dump editor, and once here.
+ We could create a new verify baton, store in it the changed paths, and
+ skip those here, but that means building an entire wrapper editor and
+ managing two levels of batons. The impact from checking these entries
+ twice should be minimal, while the code to avoid it is not.
+*/
+
+static svn_error_t *
+verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
+ void *val, apr_pool_t *pool)
+{
+ struct dir_baton *db = baton;
+ svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
+ char *path = svn_relpath_join(db->path, (const char *)key, pool);
+ apr_hash_t *dirents;
+ svn_filesize_t len;
+
+ /* since we can't access the directory entries directly by their ID,
+ we need to navigate from the FS_ROOT to them (relatively expensive
+ because we may start at a never rev than the last change to node). */
+ switch (dirent->kind) {
+ case svn_node_dir:
+ /* Getting this directory's contents is enough to ensure that our
+ link to it is correct. */
+ SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool));
+ break;
+ case svn_node_file:
+ /* Getting this file's size is enough to ensure that our link to it
+ is correct. */
+ SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool));
+ break;
+ default:
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Unexpected node kind %d for '%s'"),
+ dirent->kind, path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+verify_close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ apr_hash_t *dirents;
+ SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
+ db->path, pool));
+ SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
+ dir_baton, pool));
+ return close_directory(dir_baton, pool);
+}
+
+/* Baton type used for forwarding notifications from FS API to REPOS API. */
+struct verify_fs2_notify_func_baton_t
+{
+ /* notification function to call (must not be NULL) */
+ svn_repos_notify_func_t notify_func;
+
+ /* baton to use for it */
+ void *notify_baton;
+
+ /* type of notification to send (we will simply plug in the revision) */
+ svn_repos_notify_t *notify;
+};
+
+/* Forward the notification to BATON. */
+static void
+verify_fs2_notify_func(svn_revnum_t revision,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct verify_fs2_notify_func_baton_t *notify_baton = baton;
+
+ notify_baton->notify->revision = revision;
+ notify_baton->notify_func(notify_baton->notify_baton,
+ notify_baton->notify, pool);
+}
+
+svn_error_t *
+svn_repos_verify_fs2(svn_repos_t *repos,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = svn_repos_fs(repos);
+ svn_revnum_t youngest;
+ svn_revnum_t rev;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_repos_notify_t *notify;
+ svn_fs_progress_notify_func_t verify_notify = NULL;
+ struct verify_fs2_notify_func_baton_t *verify_notify_baton = NULL;
+
+ /* Determine the current youngest revision of the filesystem. */
+ SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
+
+ /* Use default vals if necessary. */
+ if (! SVN_IS_VALID_REVNUM(start_rev))
+ start_rev = 0;
+ if (! SVN_IS_VALID_REVNUM(end_rev))
+ end_rev = youngest;
+
+ /* Validate the revisions. */
+ if (start_rev > end_rev)
+ return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("Start revision %ld"
+ " is greater than end revision %ld"),
+ start_rev, end_rev);
+ if (end_rev > youngest)
+ return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("End revision %ld is invalid "
+ "(youngest revision is %ld)"),
+ end_rev, youngest);
+
+ /* Create a notify object that we can reuse within the loop and a
+ forwarding structure for notifications from inside svn_fs_verify(). */
+ if (notify_func)
+ {
+ notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end,
+ pool);
+
+ verify_notify = verify_fs2_notify_func;
+ verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
+ verify_notify_baton->notify_func = notify_func;
+ verify_notify_baton->notify_baton = notify_baton;
+ verify_notify_baton->notify
+ = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
+ }
+
+ /* Verify global metadata and backend-specific data first. */
+ SVN_ERR(svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
+ start_rev, end_rev,
+ verify_notify, verify_notify_baton,
+ cancel_func, cancel_baton, pool));
+
+ for (rev = start_rev; rev <= end_rev; rev++)
+ {
+ const svn_delta_editor_t *dump_editor;
+ void *dump_edit_baton;
+ const svn_delta_editor_t *cancel_editor;
+ void *cancel_edit_baton;
+ svn_fs_root_t *to_root;
+ apr_hash_t *props;
+
+ svn_pool_clear(iterpool);
+
+ /* Get cancellable dump editor, but with our close_directory handler. */
+ SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
+ fs, rev, "",
+ svn_stream_empty(iterpool),
+ NULL, NULL,
+ verify_close_directory,
+ notify_func, notify_baton,
+ start_rev,
+ FALSE, TRUE, /* use_deltas, verify */
+ iterpool));
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
+ dump_editor, dump_edit_baton,
+ &cancel_editor,
+ &cancel_edit_baton,
+ iterpool));
+
+ SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool));
+ SVN_ERR(svn_fs_verify_root(to_root, iterpool));
+
+ SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
+ cancel_editor, cancel_edit_baton,
+ NULL, NULL, iterpool));
+ /* While our editor close_edit implementation is a no-op, we still
+ do this for completeness. */
+ SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, iterpool));
+
+ SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, iterpool));
+
+ if (notify_func)
+ {
+ notify->revision = rev;
+ notify_func(notify_baton, notify, iterpool);
+ }
+ }
+
+ /* We're done. */
+ if (notify_func)
+ {
+ notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
+ notify_func(notify_baton, notify, iterpool);
+ }
+
+ /* Per-backend verification. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/fs-wrap.c b/contrib/subversion/subversion/libsvn_repos/fs-wrap.c
new file mode 100644
index 0000000..b759c57
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/fs-wrap.c
@@ -0,0 +1,844 @@
+/* fs-wrap.c --- filesystem interface wrappers.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_repos.h"
+#include "svn_time.h"
+#include "svn_sorts.h"
+#include "repos.h"
+#include "svn_private_config.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_utf_private.h"
+#include "private/svn_fspath.h"
+
+
+/*** Commit wrappers ***/
+
+svn_error_t *
+svn_repos_fs_commit_txn(const char **conflict_p,
+ svn_repos_t *repos,
+ svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ svn_error_t *err, *err2;
+ const char *txn_name;
+ apr_hash_t *props;
+ apr_pool_t *iterpool;
+ apr_hash_index_t *hi;
+ apr_hash_t *hooks_env;
+
+ *new_rev = SVN_INVALID_REVNUM;
+
+ /* Parse the hooks-env file (if any). */
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
+ pool, pool));
+
+ /* Run pre-commit hooks. */
+ SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
+ SVN_ERR(svn_repos__hooks_pre_commit(repos, hooks_env, txn_name, pool));
+
+ /* Remove any ephemeral transaction properties. */
+ SVN_ERR(svn_fs_txn_proplist(&props, txn, pool));
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_hash_this(hi, &key, NULL, NULL);
+
+ svn_pool_clear(iterpool);
+
+ if (strncmp(key, SVN_PROP_TXN_PREFIX,
+ (sizeof(SVN_PROP_TXN_PREFIX) - 1)) == 0)
+ {
+ SVN_ERR(svn_fs_change_txn_prop(txn, key, NULL, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Commit. */
+ err = svn_fs_commit_txn(conflict_p, new_rev, txn, pool);
+ if (! SVN_IS_VALID_REVNUM(*new_rev))
+ return err;
+
+ /* Run post-commit hooks. */
+ if ((err2 = svn_repos__hooks_post_commit(repos, hooks_env,
+ *new_rev, txn_name, pool)))
+ {
+ err2 = svn_error_create
+ (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err2,
+ _("Commit succeeded, but post-commit hook failed"));
+ }
+
+ return svn_error_compose_create(err, err2);
+}
+
+
+
+/*** Transaction creation wrappers. ***/
+
+
+svn_error_t *
+svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ apr_hash_t *revprop_table,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *revprops;
+ const char *txn_name;
+ svn_string_t *author = svn_hash_gets(revprop_table, SVN_PROP_REVISION_AUTHOR);
+ apr_hash_t *hooks_env;
+
+ /* Parse the hooks-env file (if any). */
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
+ pool, pool));
+
+ /* Begin the transaction, ask for the fs to do on-the-fly lock checks.
+ We fetch its name, too, so the start-commit hook can use it. */
+ SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev,
+ SVN_FS_TXN_CHECK_LOCKS, pool));
+ SVN_ERR(svn_fs_txn_name(&txn_name, *txn_p, pool));
+
+ /* We pass the revision properties to the filesystem by adding them
+ as properties on the txn. Later, when we commit the txn, these
+ properties will be copied into the newly created revision. */
+ revprops = svn_prop_hash_to_array(revprop_table, pool);
+ SVN_ERR(svn_repos_fs_change_txn_props(*txn_p, revprops, pool));
+
+ /* Run start-commit hooks. */
+ SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
+ author ? author->data : NULL,
+ repos->client_capabilities, txn_name,
+ pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *log_msg,
+ apr_pool_t *pool)
+{
+ apr_hash_t *revprop_table = apr_hash_make(pool);
+ if (author)
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(author, pool));
+ if (log_msg)
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
+ svn_string_create(log_msg, pool));
+ return svn_repos_fs_begin_txn_for_commit2(txn_p, repos, rev, revprop_table,
+ pool);
+}
+
+
+/*** Property wrappers ***/
+
+svn_error_t *
+svn_repos__validate_prop(const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ svn_prop_kind_t kind = svn_property_kind2(name);
+
+ /* Disallow setting non-regular properties. */
+ if (kind != svn_prop_regular_kind)
+ return svn_error_createf
+ (SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("Storage of non-regular property '%s' is disallowed through the "
+ "repository interface, and could indicate a bug in your client"),
+ name);
+
+ /* Validate "svn:" properties. */
+ if (svn_prop_is_svn_prop(name) && value != NULL)
+ {
+ /* Validate that translated props (e.g., svn:log) are UTF-8 with
+ * LF line endings. */
+ if (svn_prop_needs_translation(name))
+ {
+ if (!svn_utf__is_valid(value->data, value->len))
+ {
+ return svn_error_createf
+ (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
+ _("Cannot accept '%s' property because it is not encoded in "
+ "UTF-8"), name);
+ }
+
+ /* Disallow inconsistent line ending style, by simply looking for
+ * carriage return characters ('\r'). */
+ if (strchr(value->data, '\r') != NULL)
+ {
+ return svn_error_createf
+ (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
+ _("Cannot accept non-LF line endings in '%s' property"),
+ name);
+ }
+ }
+
+ /* "svn:date" should be a valid date. */
+ if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+ {
+ apr_time_t temp;
+ svn_error_t *err;
+
+ err = svn_time_from_cstring(&temp, value->data, pool);
+ if (err)
+ return svn_error_create(SVN_ERR_BAD_PROPERTY_VALUE,
+ err, NULL);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Verify the mergeinfo property value VALUE and return an error if it
+ * is invalid. The PATH on which that property is set is used for error
+ * messages only. Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+verify_mergeinfo(const svn_string_t *value,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_mergeinfo_t mergeinfo;
+
+ /* It's okay to delete svn:mergeinfo. */
+ if (value == NULL)
+ return SVN_NO_ERROR;
+
+ /* Mergeinfo is UTF-8 encoded so the number of bytes returned by strlen()
+ * should match VALUE->LEN. Prevents trailing garbage in the property. */
+ if (strlen(value->data) != value->len)
+ return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Commit rejected because mergeinfo on '%s' "
+ "contains unexpected string terminator"),
+ path);
+
+ err = svn_mergeinfo_parse(&mergeinfo, value->data, scratch_pool);
+ if (err)
+ return svn_error_createf(err->apr_err, err,
+ _("Commit rejected because mergeinfo on '%s' "
+ "is syntactically invalid"),
+ path);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_fs_change_node_prop(svn_fs_root_t *root,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
+ SVN_ERR(verify_mergeinfo(value, path, pool));
+
+ /* Validate the property, then call the wrapped function. */
+ SVN_ERR(svn_repos__validate_prop(name, value, pool));
+ return svn_fs_change_node_prop(root, path, name, value, pool);
+}
+
+
+svn_error_t *
+svn_repos_fs_change_txn_props(svn_fs_txn_t *txn,
+ const apr_array_header_t *txnprops,
+ apr_pool_t *pool)
+{
+ int i;
+
+ for (i = 0; i < txnprops->nelts; i++)
+ {
+ svn_prop_t *prop = &APR_ARRAY_IDX(txnprops, i, svn_prop_t);
+ SVN_ERR(svn_repos__validate_prop(prop->name, prop->value, pool));
+ }
+
+ return svn_fs_change_txn_props(txn, txnprops, pool);
+}
+
+
+svn_error_t *
+svn_repos_fs_change_txn_prop(svn_fs_txn_t *txn,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
+ svn_prop_t prop;
+
+ prop.name = name;
+ prop.value = value;
+ APR_ARRAY_PUSH(props, svn_prop_t) = prop;
+
+ return svn_repos_fs_change_txn_props(txn, props, pool);
+}
+
+
+svn_error_t *
+svn_repos_fs_change_rev_prop4(svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *new_value,
+ svn_boolean_t use_pre_revprop_change_hook,
+ svn_boolean_t use_post_revprop_change_hook,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_revision_access_level_t readability;
+
+ SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
+ authz_read_func, authz_read_baton,
+ pool));
+
+ if (readability == svn_repos_revision_access_full)
+ {
+ const svn_string_t *old_value;
+ char action;
+ apr_hash_t *hooks_env;
+
+ SVN_ERR(svn_repos__validate_prop(name, new_value, pool));
+
+ /* Fetch OLD_VALUE for svn_fs_change_rev_prop2(). */
+ if (old_value_p)
+ {
+ old_value = *old_value_p;
+ }
+ else
+ {
+ /* Get OLD_VALUE anyway, in order for ACTION and OLD_VALUE arguments
+ * to the hooks to be accurate. */
+ svn_string_t *old_value2;
+
+ SVN_ERR(svn_fs_revision_prop(&old_value2, repos->fs, rev, name, pool));
+ old_value = old_value2;
+ }
+
+ /* Prepare ACTION. */
+ if (! new_value)
+ action = 'D';
+ else if (! old_value)
+ action = 'A';
+ else
+ action = 'M';
+
+ /* Parse the hooks-env file (if any, and if to be used). */
+ if (use_pre_revprop_change_hook || use_post_revprop_change_hook)
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
+ pool, pool));
+
+ /* ### currently not passing the old_value to hooks */
+ if (use_pre_revprop_change_hook)
+ SVN_ERR(svn_repos__hooks_pre_revprop_change(repos, hooks_env, rev,
+ author, name, new_value,
+ action, pool));
+
+ SVN_ERR(svn_fs_change_rev_prop2(repos->fs, rev, name,
+ &old_value, new_value, pool));
+
+ if (use_post_revprop_change_hook)
+ SVN_ERR(svn_repos__hooks_post_revprop_change(repos, hooks_env, rev,
+ author, name, old_value,
+ action, pool));
+ }
+ else /* rev is either unreadable or only partially readable */
+ {
+ return svn_error_createf
+ (SVN_ERR_AUTHZ_UNREADABLE, NULL,
+ _("Write denied: not authorized to read all of revision %ld"), rev);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_fs_revision_prop(svn_string_t **value_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *propname,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_revision_access_level_t readability;
+
+ SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
+ authz_read_func, authz_read_baton,
+ pool));
+
+ if (readability == svn_repos_revision_access_none)
+ {
+ /* Property? What property? */
+ *value_p = NULL;
+ }
+ else if (readability == svn_repos_revision_access_partial)
+ {
+ /* Only svn:author and svn:date are fetchable. */
+ if ((strcmp(propname, SVN_PROP_REVISION_AUTHOR) != 0)
+ && (strcmp(propname, SVN_PROP_REVISION_DATE) != 0))
+ *value_p = NULL;
+
+ else
+ SVN_ERR(svn_fs_revision_prop(value_p, repos->fs,
+ rev, propname, pool));
+ }
+ else /* wholly readable revision */
+ {
+ SVN_ERR(svn_fs_revision_prop(value_p, repos->fs, rev, propname, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_repos_fs_revision_proplist(apr_hash_t **table_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_revision_access_level_t readability;
+
+ SVN_ERR(svn_repos_check_revision_access(&readability, repos, rev,
+ authz_read_func, authz_read_baton,
+ pool));
+
+ if (readability == svn_repos_revision_access_none)
+ {
+ /* Return an empty hash. */
+ *table_p = apr_hash_make(pool);
+ }
+ else if (readability == svn_repos_revision_access_partial)
+ {
+ apr_hash_t *tmphash;
+ svn_string_t *value;
+
+ /* Produce two property hashtables, both in POOL. */
+ SVN_ERR(svn_fs_revision_proplist(&tmphash, repos->fs, rev, pool));
+ *table_p = apr_hash_make(pool);
+
+ /* If they exist, we only copy svn:author and svn:date into the
+ 'real' hashtable being returned. */
+ value = svn_hash_gets(tmphash, SVN_PROP_REVISION_AUTHOR);
+ if (value)
+ svn_hash_sets(*table_p, SVN_PROP_REVISION_AUTHOR, value);
+
+ value = svn_hash_gets(tmphash, SVN_PROP_REVISION_DATE);
+ if (value)
+ svn_hash_sets(*table_p, SVN_PROP_REVISION_DATE, value);
+ }
+ else /* wholly readable revision */
+ {
+ SVN_ERR(svn_fs_revision_proplist(table_p, repos->fs, rev, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_fs_lock(svn_lock_t **lock,
+ svn_repos_t *repos,
+ const char *path,
+ const char *token,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_revnum_t current_rev,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_fs_access_t *access_ctx = NULL;
+ const char *username = NULL;
+ const char *new_token;
+ apr_array_header_t *paths;
+ apr_hash_t *hooks_env;
+
+ /* Parse the hooks-env file (if any). */
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
+ pool, pool));
+
+ /* Setup an array of paths in anticipation of the ra layers handling
+ multiple locks in one request (1.3 most likely). This is only
+ used by svn_repos__hooks_post_lock. */
+ paths = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(paths, const char *) = path;
+
+ SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
+ if (access_ctx)
+ SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
+
+ if (! username)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_USER, NULL,
+ "Cannot lock path '%s', no authenticated username available.", path);
+
+ /* Run pre-lock hook. This could throw error, preventing
+ svn_fs_lock() from happening. */
+ SVN_ERR(svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
+ username, comment, steal_lock, pool));
+ if (*new_token)
+ token = new_token;
+
+ /* Lock. */
+ SVN_ERR(svn_fs_lock(lock, repos->fs, path, token, comment, is_dav_comment,
+ expiration_date, current_rev, steal_lock, pool));
+
+ /* Run post-lock hook. */
+ if ((err = svn_repos__hooks_post_lock(repos, hooks_env,
+ paths, username, pool)))
+ return svn_error_create
+ (SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, err,
+ "Lock succeeded, but post-lock hook failed");
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_fs_unlock(svn_repos_t *repos,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_fs_access_t *access_ctx = NULL;
+ const char *username = NULL;
+ apr_array_header_t *paths;
+ apr_hash_t *hooks_env;
+
+ /* Parse the hooks-env file (if any). */
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
+ pool, pool));
+
+ /* Setup an array of paths in anticipation of the ra layers handling
+ multiple locks in one request (1.3 most likely). This is only
+ used by svn_repos__hooks_post_lock. */
+ paths = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(paths, const char *) = path;
+
+ SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
+ if (access_ctx)
+ SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
+
+ if (! break_lock && ! username)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_USER, NULL,
+ _("Cannot unlock path '%s', no authenticated username available"),
+ path);
+
+ /* Run pre-unlock hook. This could throw error, preventing
+ svn_fs_unlock() from happening. */
+ SVN_ERR(svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
+ break_lock, pool));
+
+ /* Unlock. */
+ SVN_ERR(svn_fs_unlock(repos->fs, path, token, break_lock, pool));
+
+ /* Run post-unlock hook. */
+ if ((err = svn_repos__hooks_post_unlock(repos, hooks_env, paths,
+ username, pool)))
+ return svn_error_create
+ (SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, err,
+ _("Unlock succeeded, but post-unlock hook failed"));
+
+ return SVN_NO_ERROR;
+}
+
+
+struct get_locks_baton_t
+{
+ svn_fs_t *fs;
+ svn_fs_root_t *head_root;
+ svn_repos_authz_func_t authz_read_func;
+ void *authz_read_baton;
+ apr_hash_t *locks;
+};
+
+
+/* This implements the svn_fs_get_locks_callback_t interface. */
+static svn_error_t *
+get_locks_callback(void *baton,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ struct get_locks_baton_t *b = baton;
+ svn_boolean_t readable = TRUE;
+ apr_pool_t *hash_pool = apr_hash_pool_get(b->locks);
+
+ /* If there's auth to deal with, deal with it. */
+ if (b->authz_read_func)
+ SVN_ERR(b->authz_read_func(&readable, b->head_root, lock->path,
+ b->authz_read_baton, pool));
+
+ /* If we can read this lock path, add the lock to the return hash. */
+ if (readable)
+ svn_hash_sets(b->locks, apr_pstrdup(hash_pool, lock->path),
+ svn_lock_dup(lock, hash_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_fs_get_locks2(apr_hash_t **locks,
+ svn_repos_t *repos,
+ const char *path,
+ svn_depth_t depth,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ apr_hash_t *all_locks = apr_hash_make(pool);
+ svn_revnum_t head_rev;
+ struct get_locks_baton_t baton;
+
+ SVN_ERR_ASSERT((depth == svn_depth_empty) ||
+ (depth == svn_depth_files) ||
+ (depth == svn_depth_immediates) ||
+ (depth == svn_depth_infinity));
+
+ SVN_ERR(svn_fs_youngest_rev(&head_rev, repos->fs, pool));
+
+ /* Populate our callback baton. */
+ baton.fs = repos->fs;
+ baton.locks = all_locks;
+ baton.authz_read_func = authz_read_func;
+ baton.authz_read_baton = authz_read_baton;
+ SVN_ERR(svn_fs_revision_root(&(baton.head_root), repos->fs,
+ head_rev, pool));
+
+ /* Get all the locks. */
+ SVN_ERR(svn_fs_get_locks2(repos->fs, path, depth,
+ get_locks_callback, &baton, pool));
+
+ *locks = baton.locks;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo,
+ svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t rev,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ /* Here we cast away 'const', but won't try to write through this pointer
+ * without first allocating a new array. */
+ apr_array_header_t *readable_paths = (apr_array_header_t *) paths;
+ svn_fs_root_t *root;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_ERR(svn_fs_youngest_rev(&rev, repos->fs, pool));
+ SVN_ERR(svn_fs_revision_root(&root, repos->fs, rev, pool));
+
+ /* Filter out unreadable paths before divining merge tracking info. */
+ if (authz_read_func)
+ {
+ int i;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ svn_boolean_t readable;
+ const char *path = APR_ARRAY_IDX(paths, i, char *);
+ svn_pool_clear(iterpool);
+ SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton,
+ iterpool));
+ if (readable && readable_paths != paths)
+ APR_ARRAY_PUSH(readable_paths, const char *) = path;
+ else if (!readable && readable_paths == paths)
+ {
+ /* Requested paths differ from readable paths. Fork
+ list of readable paths from requested paths. */
+ int j;
+ readable_paths = apr_array_make(pool, paths->nelts - 1,
+ sizeof(char *));
+ for (j = 0; j < i; j++)
+ {
+ path = APR_ARRAY_IDX(paths, j, char *);
+ APR_ARRAY_PUSH(readable_paths, const char *) = path;
+ }
+ }
+ }
+ }
+
+ /* We consciously do not perform authz checks on the paths returned
+ in *MERGEINFO, avoiding massive authz overhead which would allow
+ us to protect the name of where a change was merged from, but not
+ the change itself. */
+ /* ### TODO(reint): ... but how about descendant merged-to paths? */
+ if (readable_paths->nelts > 0)
+ SVN_ERR(svn_fs_get_mergeinfo2(mergeinfo, root, readable_paths, inherit,
+ include_descendants, TRUE, pool, pool));
+ else
+ *mergeinfo = apr_hash_make(pool);
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+struct pack_notify_baton
+{
+ svn_repos_notify_func_t notify_func;
+ void *notify_baton;
+};
+
+/* Implements svn_fs_pack_notify_t. */
+static svn_error_t *
+pack_notify_func(void *baton,
+ apr_int64_t shard,
+ svn_fs_pack_notify_action_t pack_action,
+ apr_pool_t *pool)
+{
+ struct pack_notify_baton *pnb = baton;
+ svn_repos_notify_t *notify;
+
+ /* Simple conversion works for these values. */
+ SVN_ERR_ASSERT(pack_action >= svn_fs_pack_notify_start
+ && pack_action <= svn_fs_pack_notify_end_revprop);
+
+ notify = svn_repos_notify_create(pack_action
+ + svn_repos_notify_pack_shard_start
+ - svn_fs_pack_notify_start,
+ pool);
+ notify->shard = shard;
+ pnb->notify_func(pnb->notify_baton, notify, pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_fs_pack2(svn_repos_t *repos,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ struct pack_notify_baton pnb;
+
+ pnb.notify_func = notify_func;
+ pnb.notify_baton = notify_baton;
+
+ return svn_fs_pack(repos->db_path,
+ notify_func ? pack_notify_func : NULL,
+ notify_func ? &pnb : NULL,
+ cancel_func, cancel_baton, pool);
+}
+
+svn_error_t *
+svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props_p,
+ svn_fs_root_t *root,
+ const char *path,
+ const char *propname,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *inherited_props;
+ const char *parent_path = path;
+
+ inherited_props = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+ while (!(parent_path[0] == '/' && parent_path[1] == '\0'))
+ {
+ svn_boolean_t allowed = TRUE;
+ apr_hash_t *parent_properties = NULL;
+
+ svn_pool_clear(iterpool);
+ parent_path = svn_fspath__dirname(parent_path, scratch_pool);
+
+ if (authz_read_func)
+ SVN_ERR(authz_read_func(&allowed, root, parent_path,
+ authz_read_baton, iterpool));
+ if (allowed)
+ {
+ if (propname)
+ {
+ svn_string_t *propval;
+
+ SVN_ERR(svn_fs_node_prop(&propval, root, parent_path, propname,
+ result_pool));
+ if (propval)
+ {
+ parent_properties = apr_hash_make(result_pool);
+ svn_hash_sets(parent_properties, propname, propval);
+ }
+ }
+ else
+ {
+ SVN_ERR(svn_fs_node_proplist(&parent_properties, root,
+ parent_path, result_pool));
+ }
+
+ if (parent_properties && apr_hash_count(parent_properties))
+ {
+ svn_prop_inherited_item_t *i_props =
+ apr_pcalloc(result_pool, sizeof(*i_props));
+ i_props->path_or_url =
+ apr_pstrdup(result_pool, parent_path + 1);
+ i_props->prop_hash = parent_properties;
+ /* Build the output array in depth-first order. */
+ svn_sort__array_insert(&i_props, inherited_props, 0);
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ *inherited_props_p = inherited_props;
+ return SVN_NO_ERROR;
+}
+
+/*
+ * vim:ts=4:sw=2:expandtab:tw=80:fo=tcroq
+ * vim:isk=a-z,A-Z,48-57,_,.,-,>
+ * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0
+ */
diff --git a/contrib/subversion/subversion/libsvn_repos/hooks.c b/contrib/subversion/subversion/libsvn_repos/hooks.c
new file mode 100644
index 0000000..9727599
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/hooks.c
@@ -0,0 +1,890 @@
+/* hooks.c : running repository hooks
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+
+#include "svn_config.h"
+#include "svn_hash.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_repos.h"
+#include "svn_utf.h"
+#include "repos.h"
+#include "svn_private_config.h"
+#include "private/svn_fs_private.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_string_private.h"
+
+
+
+/*** Hook drivers. ***/
+
+/* Helper function for run_hook_cmd(). Wait for a hook to finish
+ executing and return either SVN_NO_ERROR if the hook script completed
+ without error, or an error describing the reason for failure.
+
+ NAME and CMD are the name and path of the hook program, CMD_PROC
+ is a pointer to the structure representing the running process,
+ and READ_ERRHANDLE is an open handle to the hook's stderr.
+
+ Hooks are considered to have failed if we are unable to wait for the
+ process, if we are unable to read from the hook's stderr, if the
+ process has failed to exit cleanly (due to a coredump, for example),
+ or if the process returned a non-zero return code.
+
+ Any error output returned by the hook's stderr will be included in an
+ error message, though the presence of output on stderr is not itself
+ a reason to fail a hook. */
+static svn_error_t *
+check_hook_result(const char *name, const char *cmd, apr_proc_t *cmd_proc,
+ apr_file_t *read_errhandle, apr_pool_t *pool)
+{
+ svn_error_t *err, *err2;
+ svn_stringbuf_t *native_stderr, *failure_message;
+ const char *utf8_stderr;
+ int exitcode;
+ apr_exit_why_e exitwhy;
+
+ err2 = svn_stringbuf_from_aprfile(&native_stderr, read_errhandle, pool);
+
+ err = svn_io_wait_for_cmd(cmd_proc, cmd, &exitcode, &exitwhy, pool);
+ if (err)
+ {
+ svn_error_clear(err2);
+ return svn_error_trace(err);
+ }
+
+ if (APR_PROC_CHECK_EXIT(exitwhy) && exitcode == 0)
+ {
+ /* The hook exited cleanly. However, if we got an error reading
+ the hook's stderr, fail the hook anyway, because this might be
+ symptomatic of a more important problem. */
+ if (err2)
+ {
+ return svn_error_createf
+ (SVN_ERR_REPOS_HOOK_FAILURE, err2,
+ _("'%s' hook succeeded, but error output could not be read"),
+ name);
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* The hook script failed. */
+
+ /* If we got the stderr output okay, try to translate it into UTF-8.
+ Ensure there is something sensible in the UTF-8 string regardless. */
+ if (!err2)
+ {
+ err2 = svn_utf_cstring_to_utf8(&utf8_stderr, native_stderr->data, pool);
+ if (err2)
+ utf8_stderr = _("[Error output could not be translated from the "
+ "native locale to UTF-8.]");
+ }
+ else
+ {
+ utf8_stderr = _("[Error output could not be read.]");
+ }
+ /*### It would be nice to include the text of any translation or read
+ error in the messages above before we clear it here. */
+ svn_error_clear(err2);
+
+ if (!APR_PROC_CHECK_EXIT(exitwhy))
+ {
+ failure_message = svn_stringbuf_createf(pool,
+ _("'%s' hook failed (did not exit cleanly: "
+ "apr_exit_why_e was %d, exitcode was %d). "),
+ name, exitwhy, exitcode);
+ }
+ else
+ {
+ const char *action;
+ if (strcmp(name, "start-commit") == 0
+ || strcmp(name, "pre-commit") == 0)
+ action = _("Commit");
+ else if (strcmp(name, "pre-revprop-change") == 0)
+ action = _("Revprop change");
+ else if (strcmp(name, "pre-lock") == 0)
+ action = _("Lock");
+ else if (strcmp(name, "pre-unlock") == 0)
+ action = _("Unlock");
+ else
+ action = NULL;
+ if (action == NULL)
+ failure_message = svn_stringbuf_createf(
+ pool, _("%s hook failed (exit code %d)"),
+ name, exitcode);
+ else
+ failure_message = svn_stringbuf_createf(
+ pool, _("%s blocked by %s hook (exit code %d)"),
+ action, name, exitcode);
+ }
+
+ if (utf8_stderr[0])
+ {
+ svn_stringbuf_appendcstr(failure_message,
+ _(" with output:\n"));
+ svn_stringbuf_appendcstr(failure_message, utf8_stderr);
+ }
+ else
+ {
+ svn_stringbuf_appendcstr(failure_message,
+ _(" with no output."));
+ }
+
+ return svn_error_create(SVN_ERR_REPOS_HOOK_FAILURE, err,
+ failure_message->data);
+}
+
+/* Copy the environment given as key/value pairs of ENV_HASH into
+ * an array of C strings allocated in RESULT_POOL.
+ * If the hook environment is empty, return NULL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static const char **
+env_from_env_hash(apr_hash_t *env_hash,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ const char **env;
+ const char **envp;
+
+ if (!env_hash)
+ return NULL;
+
+ env = apr_palloc(result_pool,
+ sizeof(const char *) * (apr_hash_count(env_hash) + 1));
+ envp = env;
+ for (hi = apr_hash_first(scratch_pool, env_hash); hi; hi = apr_hash_next(hi))
+ {
+ *envp = apr_psprintf(result_pool, "%s=%s",
+ (const char *)svn__apr_hash_index_key(hi),
+ (const char *)svn__apr_hash_index_val(hi));
+ envp++;
+ }
+ *envp = NULL;
+
+ return env;
+}
+
+/* NAME, CMD and ARGS are the name, path to and arguments for the hook
+ program that is to be run. The hook's exit status will be checked,
+ and if an error occurred the hook's stderr output will be added to
+ the returned error.
+
+ If STDIN_HANDLE is non-null, pass it as the hook's stdin, else pass
+ no stdin to the hook.
+
+ If RESULT is non-null, set *RESULT to the stdout of the hook or to
+ a zero-length string if the hook generates no output on stdout. */
+static svn_error_t *
+run_hook_cmd(svn_string_t **result,
+ const char *name,
+ const char *cmd,
+ const char **args,
+ apr_hash_t *hooks_env,
+ apr_file_t *stdin_handle,
+ apr_pool_t *pool)
+{
+ apr_file_t *null_handle;
+ apr_status_t apr_err;
+ svn_error_t *err;
+ apr_proc_t cmd_proc = {0};
+ apr_pool_t *cmd_pool;
+ apr_hash_t *hook_env = NULL;
+
+ if (result)
+ {
+ null_handle = NULL;
+ }
+ else
+ {
+ /* Redirect stdout to the null device */
+ apr_err = apr_file_open(&null_handle, SVN_NULL_DEVICE_NAME, APR_WRITE,
+ APR_OS_DEFAULT, pool);
+ if (apr_err)
+ return svn_error_wrap_apr
+ (apr_err, _("Can't create null stdout for hook '%s'"), cmd);
+ }
+
+ /* Tie resources allocated for the command to a special pool which we can
+ * destroy in order to clean up the stderr pipe opened for the process. */
+ cmd_pool = svn_pool_create(pool);
+
+ /* Check if a custom environment is defined for this hook, or else
+ * whether a default environment is defined. */
+ if (hooks_env)
+ {
+ hook_env = svn_hash_gets(hooks_env, name);
+ if (hook_env == NULL)
+ hook_env = svn_hash_gets(hooks_env,
+ SVN_REPOS__HOOKS_ENV_DEFAULT_SECTION);
+ }
+
+ err = svn_io_start_cmd3(&cmd_proc, ".", cmd, args,
+ env_from_env_hash(hook_env, pool, pool),
+ FALSE, FALSE, stdin_handle, result != NULL,
+ null_handle, TRUE, NULL, cmd_pool);
+ if (!err)
+ err = check_hook_result(name, cmd, &cmd_proc, cmd_proc.err, pool);
+ else
+ {
+ /* The command could not be started for some reason. */
+ err = svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, err,
+ _("Failed to start '%s' hook"), cmd);
+ }
+
+ /* Hooks are fallible, and so hook failure is "expected" to occur at
+ times. When such a failure happens we still want to close the pipe
+ and null file */
+ if (!err && result)
+ {
+ svn_stringbuf_t *native_stdout;
+ err = svn_stringbuf_from_aprfile(&native_stdout, cmd_proc.out, pool);
+ if (!err)
+ *result = svn_stringbuf__morph_into_string(native_stdout);
+ }
+
+ /* Close resources allocated by svn_io_start_cmd3(), such as the pipe. */
+ svn_pool_destroy(cmd_pool);
+
+ /* Close the null handle. */
+ if (null_handle)
+ {
+ apr_err = apr_file_close(null_handle);
+ if (!err && apr_err)
+ return svn_error_wrap_apr(apr_err, _("Error closing null file"));
+ }
+
+ return svn_error_trace(err);
+}
+
+
+/* Create a temporary file F that will automatically be deleted when the
+ pool is cleaned up. Fill it with VALUE, and leave it open and rewound,
+ ready to be read from. */
+static svn_error_t *
+create_temp_file(apr_file_t **f, const svn_string_t *value, apr_pool_t *pool)
+{
+ apr_off_t offset = 0;
+
+ SVN_ERR(svn_io_open_unique_file3(f, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ pool, pool));
+ SVN_ERR(svn_io_file_write_full(*f, value->data, value->len, NULL, pool));
+ return svn_io_file_seek(*f, APR_SET, &offset, pool);
+}
+
+
+/* Check if the HOOK program exists and is a file or a symbolic link, using
+ POOL for temporary allocations.
+
+ If the hook exists but is a broken symbolic link, set *BROKEN_LINK
+ to TRUE, else if the hook program exists set *BROKEN_LINK to FALSE.
+
+ Return the hook program if found, else return NULL and don't touch
+ *BROKEN_LINK.
+*/
+static const char*
+check_hook_cmd(const char *hook, svn_boolean_t *broken_link, apr_pool_t *pool)
+{
+ static const char* const check_extns[] = {
+#ifdef WIN32
+ /* For WIN32, we need to check with file name extension(s) added.
+
+ As Windows Scripting Host (.wsf) files can accomodate (at least)
+ JavaScript (.js) and VB Script (.vbs) code, extensions for the
+ corresponding file types need not be enumerated explicitly. */
+ ".exe", ".cmd", ".bat", ".wsf", /* ### Any other extensions? */
+#else
+ "",
+#endif
+ NULL
+ };
+
+ const char *const *extn;
+ svn_error_t *err = NULL;
+ svn_boolean_t is_special;
+ for (extn = check_extns; *extn; ++extn)
+ {
+ const char *const hook_path =
+ (**extn ? apr_pstrcat(pool, hook, *extn, (char *)NULL) : hook);
+
+ svn_node_kind_t kind;
+ if (!(err = svn_io_check_resolved_path(hook_path, &kind, pool))
+ && kind == svn_node_file)
+ {
+ *broken_link = FALSE;
+ return hook_path;
+ }
+ svn_error_clear(err);
+ if (!(err = svn_io_check_special_path(hook_path, &kind, &is_special,
+ pool))
+ && is_special)
+ {
+ *broken_link = TRUE;
+ return hook_path;
+ }
+ svn_error_clear(err);
+ }
+ return NULL;
+}
+
+/* Baton for parse_hooks_env_option. */
+struct parse_hooks_env_option_baton {
+ /* The name of the section being parsed. If not the default section,
+ * the section name should match the name of a hook to which the
+ * options apply. */
+ const char *section;
+ apr_hash_t *hooks_env;
+} parse_hooks_env_option_baton;
+
+/* An implementation of svn_config_enumerator2_t.
+ * Set environment variable NAME to value VALUE in the environment for
+ * all hooks (in case the current section is the default section),
+ * or the hook with the name corresponding to the current section's name. */
+static svn_boolean_t
+parse_hooks_env_option(const char *name, const char *value,
+ void *baton, apr_pool_t *pool)
+{
+ struct parse_hooks_env_option_baton *bo = baton;
+ apr_pool_t *result_pool = apr_hash_pool_get(bo->hooks_env);
+ apr_hash_t *hook_env;
+
+ hook_env = svn_hash_gets(bo->hooks_env, bo->section);
+ if (hook_env == NULL)
+ {
+ hook_env = apr_hash_make(result_pool);
+ svn_hash_sets(bo->hooks_env, apr_pstrdup(result_pool, bo->section),
+ hook_env);
+ }
+ svn_hash_sets(hook_env, apr_pstrdup(result_pool, name),
+ apr_pstrdup(result_pool, value));
+
+ return TRUE;
+}
+
+struct parse_hooks_env_section_baton {
+ svn_config_t *cfg;
+ apr_hash_t *hooks_env;
+} parse_hooks_env_section_baton;
+
+/* An implementation of svn_config_section_enumerator2_t. */
+static svn_boolean_t
+parse_hooks_env_section(const char *name, void *baton, apr_pool_t *pool)
+{
+ struct parse_hooks_env_section_baton *b = baton;
+ struct parse_hooks_env_option_baton bo;
+
+ bo.section = name;
+ bo.hooks_env = b->hooks_env;
+
+ (void)svn_config_enumerate2(b->cfg, name, parse_hooks_env_option, &bo, pool);
+
+ return TRUE;
+}
+
+svn_error_t *
+svn_repos__parse_hooks_env(apr_hash_t **hooks_env_p,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_config_t *cfg;
+ struct parse_hooks_env_section_baton b;
+
+ if (local_abspath)
+ {
+ SVN_ERR(svn_config_read3(&cfg, local_abspath, FALSE,
+ TRUE, TRUE, scratch_pool));
+ b.cfg = cfg;
+ b.hooks_env = apr_hash_make(result_pool);
+ (void)svn_config_enumerate_sections2(cfg, parse_hooks_env_section, &b,
+ scratch_pool);
+ *hooks_env_p = b.hooks_env;
+ }
+ else
+ {
+ *hooks_env_p = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return an error for the failure of HOOK due to a broken symlink. */
+static svn_error_t *
+hook_symlink_error(const char *hook)
+{
+ return svn_error_createf
+ (SVN_ERR_REPOS_HOOK_FAILURE, NULL,
+ _("Failed to run '%s' hook; broken symlink"), hook);
+}
+
+svn_error_t *
+svn_repos__hooks_start_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const char *user,
+ const apr_array_header_t *capabilities,
+ const char *txn_name,
+ apr_pool_t *pool)
+{
+ const char *hook = svn_repos_start_commit_hook(repos, pool);
+ svn_boolean_t broken_link;
+
+ if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
+ {
+ return hook_symlink_error(hook);
+ }
+ else if (hook)
+ {
+ const char *args[6];
+ char *capabilities_string;
+
+ if (capabilities)
+ {
+ capabilities_string = svn_cstring_join(capabilities, ":", pool);
+
+ /* Get rid of that annoying final colon. */
+ if (capabilities_string[0])
+ capabilities_string[strlen(capabilities_string) - 1] = '\0';
+ }
+ else
+ {
+ capabilities_string = apr_pstrdup(pool, "");
+ }
+
+ args[0] = hook;
+ args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
+ args[2] = user ? user : "";
+ args[3] = capabilities_string;
+ args[4] = txn_name;
+ args[5] = NULL;
+
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_START_COMMIT, hook, args,
+ hooks_env, NULL, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *HANDLE to an open filehandle for a temporary file (i.e.,
+ automatically deleted when closed), into which the LOCK_TOKENS have
+ been written out in the format described in the pre-commit hook
+ template.
+
+ LOCK_TOKENS is as returned by svn_fs__access_get_lock_tokens().
+
+ Allocate *HANDLE in POOL, and use POOL for temporary allocations. */
+static svn_error_t *
+lock_token_content(apr_file_t **handle, apr_hash_t *lock_tokens,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *lock_str = svn_stringbuf_create("LOCK-TOKENS:\n", pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, lock_tokens); hi;
+ hi = apr_hash_next(hi))
+ {
+ void *val;
+ const char *path, *token;
+
+ apr_hash_this(hi, (void *)&token, NULL, &val);
+ path = val;
+ svn_stringbuf_appendstr(lock_str,
+ svn_stringbuf_createf(pool, "%s|%s\n",
+ svn_path_uri_autoescape(path, pool),
+ token));
+ }
+
+ svn_stringbuf_appendcstr(lock_str, "\n");
+ return create_temp_file(handle,
+ svn_stringbuf__morph_into_string(lock_str), pool);
+}
+
+
+
+svn_error_t *
+svn_repos__hooks_pre_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const char *txn_name,
+ apr_pool_t *pool)
+{
+ const char *hook = svn_repos_pre_commit_hook(repos, pool);
+ svn_boolean_t broken_link;
+
+ if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
+ {
+ return hook_symlink_error(hook);
+ }
+ else if (hook)
+ {
+ const char *args[4];
+ svn_fs_access_t *access_ctx;
+ apr_file_t *stdin_handle = NULL;
+
+ args[0] = hook;
+ args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
+ args[2] = txn_name;
+ args[3] = NULL;
+
+ SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
+ if (access_ctx)
+ {
+ apr_hash_t *lock_tokens = svn_fs__access_get_lock_tokens(access_ctx);
+ if (apr_hash_count(lock_tokens)) {
+ SVN_ERR(lock_token_content(&stdin_handle, lock_tokens, pool));
+ }
+ }
+
+ if (!stdin_handle)
+ SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
+ APR_READ, APR_OS_DEFAULT, pool));
+
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_COMMIT, hook, args,
+ hooks_env, stdin_handle, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos__hooks_post_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ svn_revnum_t rev,
+ const char *txn_name,
+ apr_pool_t *pool)
+{
+ const char *hook = svn_repos_post_commit_hook(repos, pool);
+ svn_boolean_t broken_link;
+
+ if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
+ {
+ return hook_symlink_error(hook);
+ }
+ else if (hook)
+ {
+ const char *args[5];
+
+ args[0] = hook;
+ args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
+ args[2] = apr_psprintf(pool, "%ld", rev);
+ args[3] = txn_name;
+ args[4] = NULL;
+
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_COMMIT, hook, args,
+ hooks_env, NULL, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos__hooks_pre_revprop_change(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *new_value,
+ char action,
+ apr_pool_t *pool)
+{
+ const char *hook = svn_repos_pre_revprop_change_hook(repos, pool);
+ svn_boolean_t broken_link;
+
+ if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
+ {
+ return hook_symlink_error(hook);
+ }
+ else if (hook)
+ {
+ const char *args[7];
+ apr_file_t *stdin_handle = NULL;
+ char action_string[2];
+
+ /* Pass the new value as stdin to hook */
+ if (new_value)
+ SVN_ERR(create_temp_file(&stdin_handle, new_value, pool));
+ else
+ SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
+ APR_READ, APR_OS_DEFAULT, pool));
+
+ action_string[0] = action;
+ action_string[1] = '\0';
+
+ args[0] = hook;
+ args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
+ args[2] = apr_psprintf(pool, "%ld", rev);
+ args[3] = author ? author : "";
+ args[4] = name;
+ args[5] = action_string;
+ args[6] = NULL;
+
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE, hook,
+ args, hooks_env, stdin_handle, pool));
+
+ SVN_ERR(svn_io_file_close(stdin_handle, pool));
+ }
+ else
+ {
+ /* If the pre- hook doesn't exist at all, then default to
+ MASSIVE PARANOIA. Changing revision properties is a lossy
+ operation; so unless the repository admininstrator has
+ *deliberately* created the pre-hook, disallow all changes. */
+ return
+ svn_error_create
+ (SVN_ERR_REPOS_DISABLED_FEATURE, NULL,
+ _("Repository has not been enabled to accept revision propchanges;\n"
+ "ask the administrator to create a pre-revprop-change hook"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos__hooks_post_revprop_change(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *old_value,
+ char action,
+ apr_pool_t *pool)
+{
+ const char *hook = svn_repos_post_revprop_change_hook(repos, pool);
+ svn_boolean_t broken_link;
+
+ if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
+ {
+ return hook_symlink_error(hook);
+ }
+ else if (hook)
+ {
+ const char *args[7];
+ apr_file_t *stdin_handle = NULL;
+ char action_string[2];
+
+ /* Pass the old value as stdin to hook */
+ if (old_value)
+ SVN_ERR(create_temp_file(&stdin_handle, old_value, pool));
+ else
+ SVN_ERR(svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
+ APR_READ, APR_OS_DEFAULT, pool));
+
+ action_string[0] = action;
+ action_string[1] = '\0';
+
+ args[0] = hook;
+ args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
+ args[2] = apr_psprintf(pool, "%ld", rev);
+ args[3] = author ? author : "";
+ args[4] = name;
+ args[5] = action_string;
+ args[6] = NULL;
+
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_REVPROP_CHANGE, hook,
+ args, hooks_env, stdin_handle, pool));
+
+ SVN_ERR(svn_io_file_close(stdin_handle, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos__hooks_pre_lock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const char **token,
+ const char *path,
+ const char *username,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool)
+{
+ const char *hook = svn_repos_pre_lock_hook(repos, pool);
+ svn_boolean_t broken_link;
+
+ if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
+ {
+ return hook_symlink_error(hook);
+ }
+ else if (hook)
+ {
+ const char *args[7];
+ svn_string_t *buf;
+
+
+ args[0] = hook;
+ args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
+ args[2] = path;
+ args[3] = username;
+ args[4] = comment ? comment : "";
+ args[5] = steal_lock ? "1" : "0";
+ args[6] = NULL;
+
+ SVN_ERR(run_hook_cmd(&buf, SVN_REPOS__HOOK_PRE_LOCK, hook, args,
+ hooks_env, NULL, pool));
+
+ if (token)
+ /* No validation here; the FS will take care of that. */
+ *token = buf->data;
+
+ }
+ else if (token)
+ *token = "";
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos__hooks_post_lock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const apr_array_header_t *paths,
+ const char *username,
+ apr_pool_t *pool)
+{
+ const char *hook = svn_repos_post_lock_hook(repos, pool);
+ svn_boolean_t broken_link;
+
+ if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
+ {
+ return hook_symlink_error(hook);
+ }
+ else if (hook)
+ {
+ const char *args[5];
+ apr_file_t *stdin_handle = NULL;
+ svn_string_t *paths_str = svn_string_create(svn_cstring_join
+ (paths, "\n", pool),
+ pool);
+
+ SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));
+
+ args[0] = hook;
+ args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
+ args[2] = username;
+ args[3] = NULL;
+ args[4] = NULL;
+
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_LOCK, hook, args,
+ hooks_env, stdin_handle, pool));
+
+ SVN_ERR(svn_io_file_close(stdin_handle, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos__hooks_pre_unlock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const char *path,
+ const char *username,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool)
+{
+ const char *hook = svn_repos_pre_unlock_hook(repos, pool);
+ svn_boolean_t broken_link;
+
+ if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
+ {
+ return hook_symlink_error(hook);
+ }
+ else if (hook)
+ {
+ const char *args[7];
+
+ args[0] = hook;
+ args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
+ args[2] = path;
+ args[3] = username ? username : "";
+ args[4] = token ? token : "";
+ args[5] = break_lock ? "1" : "0";
+ args[6] = NULL;
+
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_PRE_UNLOCK, hook, args,
+ hooks_env, NULL, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos__hooks_post_unlock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const apr_array_header_t *paths,
+ const char *username,
+ apr_pool_t *pool)
+{
+ const char *hook = svn_repos_post_unlock_hook(repos, pool);
+ svn_boolean_t broken_link;
+
+ if ((hook = check_hook_cmd(hook, &broken_link, pool)) && broken_link)
+ {
+ return hook_symlink_error(hook);
+ }
+ else if (hook)
+ {
+ const char *args[5];
+ apr_file_t *stdin_handle = NULL;
+ svn_string_t *paths_str = svn_string_create(svn_cstring_join
+ (paths, "\n", pool),
+ pool);
+
+ SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));
+
+ args[0] = hook;
+ args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
+ args[2] = username ? username : "";
+ args[3] = NULL;
+ args[4] = NULL;
+
+ SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_POST_UNLOCK, hook, args,
+ hooks_env, stdin_handle, pool));
+
+ SVN_ERR(svn_io_file_close(stdin_handle, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*
+ * vim:ts=4:sw=4:expandtab:tw=80:fo=tcroq
+ * vim:isk=a-z,A-Z,48-57,_,.,-,>
+ * vim:cino=>1s,e0,n0,f0,{.5s,}0,^-.5s,=.5s,t0,+1s,c3,(0,u0,\:0
+ */
diff --git a/contrib/subversion/subversion/libsvn_repos/load-fs-vtable.c b/contrib/subversion/subversion/libsvn_repos/load-fs-vtable.c
new file mode 100644
index 0000000..c8c5e95
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/load-fs-vtable.c
@@ -0,0 +1,1140 @@
+/* load-fs-vtable.c --- dumpstream loader vtable for committing into a
+ * Subversion filesystem.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include "svn_private_config.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_repos.h"
+#include "svn_string.h"
+#include "svn_props.h"
+#include "repos.h"
+#include "svn_private_config.h"
+#include "svn_mergeinfo.h"
+#include "svn_checksum.h"
+#include "svn_subst.h"
+#include "svn_ctype.h"
+#include "svn_dirent_uri.h"
+
+#include <apr_lib.h>
+
+#include "private/svn_fspath.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_mergeinfo_private.h"
+
+/*----------------------------------------------------------------------*/
+
+/** Batons used herein **/
+
+struct parse_baton
+{
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+
+ svn_boolean_t use_history;
+ svn_boolean_t validate_props;
+ svn_boolean_t use_pre_commit_hook;
+ svn_boolean_t use_post_commit_hook;
+ enum svn_repos_load_uuid uuid_action;
+ const char *parent_dir; /* repository relpath, or NULL */
+ svn_repos_notify_func_t notify_func;
+ void *notify_baton;
+ svn_repos_notify_t *notify;
+ apr_pool_t *pool;
+
+ /* Start and end (inclusive) of revision range we'll pay attention
+ to, or a pair of SVN_INVALID_REVNUMs if we're not filtering by
+ revisions. */
+ svn_revnum_t start_rev;
+ svn_revnum_t end_rev;
+
+ /* A hash mapping copy-from revisions and mergeinfo range revisions
+ (svn_revnum_t *) in the dump stream to their corresponding revisions
+ (svn_revnum_t *) in the loaded repository. The hash and its
+ contents are allocated in POOL. */
+ /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903
+ ### for discussion about improving the memory costs of this mapping. */
+ apr_hash_t *rev_map;
+
+ /* The most recent (youngest) revision from the dump stream mapped in
+ REV_MAP. If no revisions have been mapped yet, this is set to
+ SVN_INVALID_REVNUM. */
+ svn_revnum_t last_rev_mapped;
+
+ /* The oldest old revision loaded from the dump stream. If no revisions
+ have been loaded yet, this is set to SVN_INVALID_REVNUM. */
+ svn_revnum_t oldest_old_rev;
+};
+
+struct revision_baton
+{
+ svn_revnum_t rev;
+ svn_fs_txn_t *txn;
+ svn_fs_root_t *txn_root;
+
+ const svn_string_t *datestamp;
+
+ apr_int32_t rev_offset;
+ svn_boolean_t skipped;
+
+ struct parse_baton *pb;
+ apr_pool_t *pool;
+};
+
+struct node_baton
+{
+ const char *path;
+ svn_node_kind_t kind;
+ enum svn_node_action action;
+ svn_checksum_t *base_checksum; /* null, if not available */
+ svn_checksum_t *result_checksum; /* null, if not available */
+ svn_checksum_t *copy_source_checksum; /* null, if not available */
+
+ svn_revnum_t copyfrom_rev;
+ const char *copyfrom_path;
+
+ struct revision_baton *rb;
+ apr_pool_t *pool;
+};
+
+
+/*----------------------------------------------------------------------*/
+
+/* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
+ anything added to the hash is allocated in the hash's pool. */
+static void
+set_revision_mapping(apr_hash_t *rev_map,
+ svn_revnum_t from_rev,
+ svn_revnum_t to_rev)
+{
+ svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
+ sizeof(svn_revnum_t) * 2);
+ mapped_revs[0] = from_rev;
+ mapped_revs[1] = to_rev;
+ apr_hash_set(rev_map, mapped_revs,
+ sizeof(svn_revnum_t), mapped_revs + 1);
+}
+
+/* Return the revision to which FROM_REV maps in REV_MAP, or
+ SVN_INVALID_REVNUM if no such mapping exists. */
+static svn_revnum_t
+get_revision_mapping(apr_hash_t *rev_map,
+ svn_revnum_t from_rev)
+{
+ svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
+ sizeof(from_rev));
+ return to_rev ? *to_rev : SVN_INVALID_REVNUM;
+}
+
+
+/* Change revision property NAME to VALUE for REVISION in REPOS. If
+ VALIDATE_PROPS is set, use functions which perform validation of
+ the property value. Otherwise, bypass those checks. */
+static svn_error_t *
+change_rev_prop(svn_repos_t *repos,
+ svn_revnum_t revision,
+ const char *name,
+ const svn_string_t *value,
+ svn_boolean_t validate_props,
+ apr_pool_t *pool)
+{
+ if (validate_props)
+ return svn_repos_fs_change_rev_prop4(repos, revision, NULL, name,
+ NULL, value, FALSE, FALSE,
+ NULL, NULL, pool);
+ else
+ return svn_fs_change_rev_prop2(svn_repos_fs(repos), revision, name,
+ NULL, value, pool);
+}
+
+/* Change property NAME to VALUE for PATH in TXN_ROOT. If
+ VALIDATE_PROPS is set, use functions which perform validation of
+ the property value. Otherwise, bypass those checks. */
+static svn_error_t *
+change_node_prop(svn_fs_root_t *txn_root,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ svn_boolean_t validate_props,
+ apr_pool_t *pool)
+{
+ if (validate_props)
+ return svn_repos_fs_change_node_prop(txn_root, path, name, value, pool);
+ else
+ return svn_fs_change_node_prop(txn_root, path, name, value, pool);
+}
+
+/* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and
+ return it in *MERGEINFO_VAL. */
+/* ### FIXME: Consider somehow sharing code with
+ ### svnrdump/load_editor.c:prefix_mergeinfo_paths() */
+static svn_error_t *
+prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
+ const svn_string_t *mergeinfo_orig,
+ const char *parent_dir,
+ apr_pool_t *pool)
+{
+ apr_hash_t *prefixed_mergeinfo, *mergeinfo;
+ apr_hash_index_t *hi;
+ void *rangelist;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
+ prefixed_mergeinfo = apr_hash_make(pool);
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ const char *path, *merge_source;
+
+ apr_hash_this(hi, &key, NULL, &rangelist);
+ merge_source = svn_relpath_canonicalize(key, pool);
+
+ /* The svn:mergeinfo property syntax demands a repos abspath */
+ path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
+ merge_source, pool),
+ pool);
+ svn_hash_sets(prefixed_mergeinfo, path, rangelist);
+ }
+ return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
+}
+
+
+/* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
+ as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
+ (allocated from POOL). */
+/* ### FIXME: Consider somehow sharing code with
+ ### svnrdump/load_editor.c:renumber_mergeinfo_revs() */
+static svn_error_t *
+renumber_mergeinfo_revs(svn_string_t **final_val,
+ const svn_string_t *initial_val,
+ struct revision_baton *rb,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
+ svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
+
+ /* Issue #3020
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
+ Remove mergeinfo older than the oldest revision in the dump stream
+ and adjust its revisions by the difference between the head rev of
+ the target repository and the current dump stream rev. */
+ if (rb->pb->oldest_old_rev > 1)
+ {
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &predates_stream_mergeinfo, mergeinfo,
+ rb->pb->oldest_old_rev - 1, 0,
+ TRUE, subpool, subpool));
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &mergeinfo, mergeinfo,
+ rb->pb->oldest_old_rev - 1, 0,
+ FALSE, subpool, subpool));
+ SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
+ &predates_stream_mergeinfo, predates_stream_mergeinfo,
+ -rb->rev_offset, subpool, subpool));
+ }
+ else
+ {
+ predates_stream_mergeinfo = NULL;
+ }
+
+ for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ const char *merge_source;
+ svn_rangelist_t *rangelist;
+ struct parse_baton *pb = rb->pb;
+ int i;
+ const void *key;
+ void *val;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ merge_source = key;
+ rangelist = val;
+
+ /* Possibly renumber revisions in merge source's rangelist. */
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_revnum_t rev_from_map;
+ svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
+ svn_merge_range_t *);
+ rev_from_map = get_revision_mapping(pb->rev_map, range->start);
+ if (SVN_IS_VALID_REVNUM(rev_from_map))
+ {
+ range->start = rev_from_map;
+ }
+ else if (range->start == pb->oldest_old_rev - 1)
+ {
+ /* Since the start revision of svn_merge_range_t are not
+ inclusive there is one possible valid start revision that
+ won't be found in the PB->REV_MAP mapping of load stream
+ revsions to loaded revisions: The revision immediately
+ preceeding the oldest revision from the load stream.
+ This is a valid revision for mergeinfo, but not a valid
+ copy from revision (which PB->REV_MAP also maps for) so it
+ will never be in the mapping.
+
+ If that is what we have here, then find the mapping for the
+ oldest rev from the load stream and subtract 1 to get the
+ renumbered, non-inclusive, start revision. */
+ rev_from_map = get_revision_mapping(pb->rev_map,
+ pb->oldest_old_rev);
+ if (SVN_IS_VALID_REVNUM(rev_from_map))
+ range->start = rev_from_map - 1;
+ }
+ else
+ {
+ /* If we can't remap the start revision then don't even bother
+ trying to remap the end revision. It's possible we might
+ actually succeed at the latter, which can result in invalid
+ mergeinfo with a start rev > end rev. If that gets into the
+ repository then a world of bustage breaks loose anytime that
+ bogus mergeinfo is parsed. See
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
+ */
+ continue;
+ }
+
+ rev_from_map = get_revision_mapping(pb->rev_map, range->end);
+ if (SVN_IS_VALID_REVNUM(rev_from_map))
+ range->end = rev_from_map;
+ }
+ svn_hash_sets(final_mergeinfo, merge_source, rangelist);
+ }
+
+ if (predates_stream_mergeinfo)
+ SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
+ subpool, subpool));
+
+ SVN_ERR(svn_mergeinfo_sort(final_mergeinfo, subpool));
+
+ /* Mergeinfo revision sources for r0 and r1 are invalid; you can't merge r0
+ or r1. However, svndumpfilter can be abused to produce r1 merge source
+ revs. So if we encounter any, then strip them out, no need to put them
+ into the load target. */
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(&final_mergeinfo,
+ final_mergeinfo,
+ 1, 0, FALSE,
+ subpool, subpool));
+
+ SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+/*----------------------------------------------------------------------*/
+
+/** vtable for doing commits to a fs **/
+
+
+static svn_error_t *
+make_node_baton(struct node_baton **node_baton_p,
+ apr_hash_t *headers,
+ struct revision_baton *rb,
+ apr_pool_t *pool)
+{
+ struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
+ const char *val;
+
+ /* Start with sensible defaults. */
+ nb->rb = rb;
+ nb->pool = pool;
+ nb->kind = svn_node_unknown;
+
+ /* Then add info from the headers. */
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH)))
+ {
+ val = svn_relpath_canonicalize(val, pool);
+ if (rb->pb->parent_dir)
+ nb->path = svn_relpath_join(rb->pb->parent_dir, val, pool);
+ else
+ nb->path = val;
+ }
+
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND)))
+ {
+ if (! strcmp(val, "file"))
+ nb->kind = svn_node_file;
+ else if (! strcmp(val, "dir"))
+ nb->kind = svn_node_dir;
+ }
+
+ nb->action = (enum svn_node_action)(-1); /* an invalid action code */
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION)))
+ {
+ if (! strcmp(val, "change"))
+ nb->action = svn_node_action_change;
+ else if (! strcmp(val, "add"))
+ nb->action = svn_node_action_add;
+ else if (! strcmp(val, "delete"))
+ nb->action = svn_node_action_delete;
+ else if (! strcmp(val, "replace"))
+ nb->action = svn_node_action_replace;
+ }
+
+ nb->copyfrom_rev = SVN_INVALID_REVNUM;
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
+ {
+ nb->copyfrom_rev = SVN_STR_TO_REV(val);
+ }
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH)))
+ {
+ val = svn_relpath_canonicalize(val, pool);
+ if (rb->pb->parent_dir)
+ nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, val, pool);
+ else
+ nb->copyfrom_path = val;
+ }
+
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM)))
+ {
+ SVN_ERR(svn_checksum_parse_hex(&nb->result_checksum, svn_checksum_md5,
+ val, pool));
+ }
+
+ if ((val = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM)))
+ {
+ SVN_ERR(svn_checksum_parse_hex(&nb->base_checksum, svn_checksum_md5, val,
+ pool));
+ }
+
+ if ((val = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM)))
+ {
+ SVN_ERR(svn_checksum_parse_hex(&nb->copy_source_checksum,
+ svn_checksum_md5, val, pool));
+ }
+
+ /* What's cool about this dump format is that the parser just
+ ignores any unrecognized headers. :-) */
+
+ *node_baton_p = nb;
+ return SVN_NO_ERROR;
+}
+
+static struct revision_baton *
+make_revision_baton(apr_hash_t *headers,
+ struct parse_baton *pb,
+ apr_pool_t *pool)
+{
+ struct revision_baton *rb = apr_pcalloc(pool, sizeof(*rb));
+ const char *val;
+
+ rb->pb = pb;
+ rb->pool = pool;
+ rb->rev = SVN_INVALID_REVNUM;
+
+ if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
+ {
+ rb->rev = SVN_STR_TO_REV(val);
+
+ /* If we're filtering revisions, is this one we'll skip? */
+ rb->skipped = (SVN_IS_VALID_REVNUM(pb->start_rev)
+ && ((rb->rev < pb->start_rev) ||
+ (rb->rev > pb->end_rev)));
+ }
+
+ return rb;
+}
+
+
+static svn_error_t *
+new_revision_record(void **revision_baton,
+ apr_hash_t *headers,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ struct parse_baton *pb = parse_baton;
+ struct revision_baton *rb;
+ svn_revnum_t head_rev;
+
+ rb = make_revision_baton(headers, pb, pool);
+
+ /* ### If we're filtering revisions, and this is one we've skipped,
+ ### and we've skipped it because it has a revision number younger
+ ### than the youngest in our acceptable range, then should we
+ ### just bail out here? */
+ /*
+ if (rb->skipped && (rb->rev > pb->end_rev))
+ return svn_error_createf(SVN_ERR_CEASE_INVOCATION, 0,
+ _("Finished processing acceptable load "
+ "revision range"));
+ */
+
+ SVN_ERR(svn_fs_youngest_rev(&head_rev, pb->fs, pool));
+
+ /* FIXME: This is a lame fallback loading multiple segments of dump in
+ several separate operations. It is highly susceptible to race conditions.
+ Calculate the revision 'offset' for finding copyfrom sources.
+ It might be positive or negative. */
+ rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
+
+ if ((rb->rev > 0) && (! rb->skipped))
+ {
+ /* Create a new fs txn. */
+ SVN_ERR(svn_fs_begin_txn2(&(rb->txn), pb->fs, head_rev, 0, pool));
+ SVN_ERR(svn_fs_txn_root(&(rb->txn_root), rb->txn, pool));
+
+ if (pb->notify_func)
+ {
+ pb->notify->action = svn_repos_notify_load_txn_start;
+ pb->notify->old_revision = rb->rev;
+ pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
+ }
+
+ /* Stash the oldest "old" revision committed from the load stream. */
+ if (!SVN_IS_VALID_REVNUM(pb->oldest_old_rev))
+ pb->oldest_old_rev = rb->rev;
+ }
+
+ /* If we're skipping this revision, try to notify someone. */
+ if (rb->skipped && pb->notify_func)
+ {
+ pb->notify->action = svn_repos_notify_load_skipped_rev;
+ pb->notify->old_revision = rb->rev;
+ pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
+ }
+
+ /* If we're parsing revision 0, only the revision are (possibly)
+ interesting to us: when loading the stream into an empty
+ filesystem, then we want new filesystem's revision 0 to have the
+ same props. Otherwise, we just ignore revision 0 in the stream. */
+
+ *revision_baton = rb;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Factorized helper func for new_node_record() */
+static svn_error_t *
+maybe_add_with_history(struct node_baton *nb,
+ struct revision_baton *rb,
+ apr_pool_t *pool)
+{
+ struct parse_baton *pb = rb->pb;
+
+ if ((nb->copyfrom_path == NULL) || (! pb->use_history))
+ {
+ /* Add empty file or dir, without history. */
+ if (nb->kind == svn_node_file)
+ SVN_ERR(svn_fs_make_file(rb->txn_root, nb->path, pool));
+
+ else if (nb->kind == svn_node_dir)
+ SVN_ERR(svn_fs_make_dir(rb->txn_root, nb->path, pool));
+ }
+ else
+ {
+ /* Hunt down the source revision in this fs. */
+ svn_fs_root_t *copy_root;
+ svn_revnum_t copyfrom_rev;
+
+ /* Try to find the copyfrom revision in the revision map;
+ failing that, fall back to the revision offset approach. */
+ copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
+ if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
+ copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
+
+ if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("Relative source revision %ld is not"
+ " available in current repository"),
+ copyfrom_rev);
+
+ SVN_ERR(svn_fs_revision_root(&copy_root, pb->fs, copyfrom_rev, pool));
+
+ if (nb->copy_source_checksum)
+ {
+ svn_checksum_t *checksum;
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, copy_root,
+ nb->copyfrom_path, TRUE, pool));
+ if (!svn_checksum_match(nb->copy_source_checksum, checksum))
+ return svn_checksum_mismatch_err(nb->copy_source_checksum,
+ checksum, pool,
+ _("Copy source checksum mismatch on copy from '%s'@%ld\n"
+ "to '%s' in rev based on r%ld"),
+ nb->copyfrom_path, copyfrom_rev, nb->path, rb->rev);
+ }
+
+ SVN_ERR(svn_fs_copy(copy_root, nb->copyfrom_path,
+ rb->txn_root, nb->path, pool));
+
+ if (pb->notify_func)
+ {
+ pb->notify->action = svn_repos_notify_load_copied_node;
+ pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+magic_header_record(int version,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+uuid_record(const char *uuid,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ struct parse_baton *pb = parse_baton;
+ svn_revnum_t youngest_rev;
+
+ if (pb->uuid_action == svn_repos_load_uuid_ignore)
+ return SVN_NO_ERROR;
+
+ if (pb->uuid_action != svn_repos_load_uuid_force)
+ {
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, pool));
+ if (youngest_rev != 0)
+ return SVN_NO_ERROR;
+ }
+
+ return svn_fs_set_uuid(pb->fs, uuid, pool);
+}
+
+static svn_error_t *
+new_node_record(void **node_baton,
+ apr_hash_t *headers,
+ void *revision_baton,
+ apr_pool_t *pool)
+{
+ struct revision_baton *rb = revision_baton;
+ struct parse_baton *pb = rb->pb;
+ struct node_baton *nb;
+
+ if (rb->rev == 0)
+ return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+ _("Malformed dumpstream: "
+ "Revision 0 must not contain node records"));
+
+ SVN_ERR(make_node_baton(&nb, headers, rb, pool));
+
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ {
+ *node_baton = nb;
+ return SVN_NO_ERROR;
+ }
+
+ /* Make sure we have an action we recognize. */
+ if (nb->action < svn_node_action_change
+ || nb->action > svn_node_action_replace)
+ return svn_error_createf(SVN_ERR_STREAM_UNRECOGNIZED_DATA, NULL,
+ _("Unrecognized node-action on node '%s'"),
+ nb->path);
+
+ if (pb->notify_func)
+ {
+ pb->notify->action = svn_repos_notify_load_node_start;
+ pb->notify->node_action = nb->action;
+ pb->notify->path = nb->path;
+ pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
+ }
+
+ switch (nb->action)
+ {
+ case svn_node_action_change:
+ break;
+
+ case svn_node_action_delete:
+ SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
+ break;
+
+ case svn_node_action_add:
+ SVN_ERR(maybe_add_with_history(nb, rb, pool));
+ break;
+
+ case svn_node_action_replace:
+ SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
+ SVN_ERR(maybe_add_with_history(nb, rb, pool));
+ break;
+ }
+
+ *node_baton = nb;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+set_revision_property(void *baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ struct revision_baton *rb = baton;
+
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ return SVN_NO_ERROR;
+
+ if (rb->rev > 0)
+ {
+ if (rb->pb->validate_props)
+ SVN_ERR(svn_repos_fs_change_txn_prop(rb->txn, name, value, rb->pool));
+ else
+ SVN_ERR(svn_fs_change_txn_prop(rb->txn, name, value, rb->pool));
+
+ /* Remember any datestamp that passes through! (See comment in
+ close_revision() below.) */
+ if (! strcmp(name, SVN_PROP_REVISION_DATE))
+ rb->datestamp = svn_string_dup(value, rb->pool);
+ }
+ else if (rb->rev == 0)
+ {
+ /* Special case: set revision 0 properties when loading into an
+ 'empty' filesystem. */
+ struct parse_baton *pb = rb->pb;
+ svn_revnum_t youngest_rev;
+
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool));
+
+ if (youngest_rev == 0)
+ SVN_ERR(change_rev_prop(pb->repos, 0, name, value,
+ pb->validate_props, rb->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+set_node_property(void *baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ struct node_baton *nb = baton;
+ struct revision_baton *rb = nb->rb;
+ struct parse_baton *pb = rb->pb;
+
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ return SVN_NO_ERROR;
+
+ if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
+ {
+ svn_string_t *renumbered_mergeinfo;
+ /* ### Need to cast away const. We cannot change the declaration of
+ * ### this function since it is part of svn_repos_parse_fns2_t. */
+ svn_string_t *prop_val = (svn_string_t *)value;
+
+ /* Tolerate mergeinfo with "\r\n" line endings because some
+ dumpstream sources might contain as much. If so normalize
+ the line endings to '\n' and make a notification to
+ PARSE_BATON->FEEDBACK_STREAM that we have made this
+ correction. */
+ if (strstr(prop_val->data, "\r"))
+ {
+ const char *prop_eol_normalized;
+
+ SVN_ERR(svn_subst_translate_cstring2(prop_val->data,
+ &prop_eol_normalized,
+ "\n", /* translate to LF */
+ FALSE, /* no repair */
+ NULL, /* no keywords */
+ FALSE, /* no expansion */
+ nb->pool));
+ prop_val->data = prop_eol_normalized;
+ prop_val->len = strlen(prop_eol_normalized);
+
+ if (pb->notify_func)
+ {
+ pb->notify->action = svn_repos_notify_load_normalized_mergeinfo;
+ pb->notify_func(pb->notify_baton, pb->notify, nb->pool);
+ }
+ }
+
+ /* Renumber mergeinfo as appropriate. */
+ SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo, prop_val, rb,
+ nb->pool));
+ value = renumbered_mergeinfo;
+ if (pb->parent_dir)
+ {
+ /* Prefix the merge source paths with PB->parent_dir. */
+ /* ASSUMPTION: All source paths are included in the dump stream. */
+ svn_string_t *mergeinfo_val;
+ SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val, value,
+ pb->parent_dir, nb->pool));
+ value = mergeinfo_val;
+ }
+ }
+
+ return change_node_prop(rb->txn_root, nb->path, name, value,
+ pb->validate_props, nb->pool);
+}
+
+
+static svn_error_t *
+delete_node_property(void *baton,
+ const char *name)
+{
+ struct node_baton *nb = baton;
+ struct revision_baton *rb = nb->rb;
+
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ return SVN_NO_ERROR;
+
+ return change_node_prop(rb->txn_root, nb->path, name, NULL,
+ rb->pb->validate_props, nb->pool);
+}
+
+
+static svn_error_t *
+remove_node_props(void *baton)
+{
+ struct node_baton *nb = baton;
+ struct revision_baton *rb = nb->rb;
+ apr_hash_t *proplist;
+ apr_hash_index_t *hi;
+
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_node_proplist(&proplist,
+ rb->txn_root, nb->path, nb->pool));
+
+ for (hi = apr_hash_first(nb->pool, proplist); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+
+ apr_hash_this(hi, &key, NULL, NULL);
+ SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL,
+ rb->pb->validate_props, nb->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+apply_textdelta(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ void *node_baton)
+{
+ struct node_baton *nb = node_baton;
+ struct revision_baton *rb = nb->rb;
+
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ {
+ *handler = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_fs_apply_textdelta(handler, handler_baton,
+ rb->txn_root, nb->path,
+ svn_checksum_to_cstring(nb->base_checksum,
+ nb->pool),
+ svn_checksum_to_cstring(nb->result_checksum,
+ nb->pool),
+ nb->pool);
+}
+
+
+static svn_error_t *
+set_fulltext(svn_stream_t **stream,
+ void *node_baton)
+{
+ struct node_baton *nb = node_baton;
+ struct revision_baton *rb = nb->rb;
+
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ {
+ *stream = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_fs_apply_text(stream,
+ rb->txn_root, nb->path,
+ svn_checksum_to_cstring(nb->result_checksum,
+ nb->pool),
+ nb->pool);
+}
+
+
+static svn_error_t *
+close_node(void *baton)
+{
+ struct node_baton *nb = baton;
+ struct revision_baton *rb = nb->rb;
+ struct parse_baton *pb = rb->pb;
+
+ /* If we're skipping this revision, we're done here. */
+ if (rb->skipped)
+ return SVN_NO_ERROR;
+
+ if (pb->notify_func)
+ {
+ pb->notify->action = svn_repos_notify_load_node_done;
+ pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+close_revision(void *baton)
+{
+ struct revision_baton *rb = baton;
+ struct parse_baton *pb = rb->pb;
+ const char *conflict_msg = NULL;
+ svn_revnum_t committed_rev;
+ svn_error_t *err;
+ const char *txn_name = NULL;
+ apr_hash_t *hooks_env;
+
+ /* If we're skipping this revision or it has an invalid revision
+ number, we're done here. */
+ if (rb->skipped || (rb->rev <= 0))
+ return SVN_NO_ERROR;
+
+ /* Get the txn name and hooks environment if they will be needed. */
+ if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
+ {
+ SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path,
+ rb->pool, rb->pool));
+
+ err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
+ if (err)
+ {
+ svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
+ return svn_error_trace(err);
+ }
+ }
+
+ /* Run the pre-commit hook, if so commanded. */
+ if (pb->use_pre_commit_hook)
+ {
+ err = svn_repos__hooks_pre_commit(pb->repos, hooks_env,
+ txn_name, rb->pool);
+ if (err)
+ {
+ svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
+ return svn_error_trace(err);
+ }
+ }
+
+ /* Commit. */
+ err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool);
+ if (SVN_IS_VALID_REVNUM(committed_rev))
+ {
+ if (err)
+ {
+ /* ### Log any error, but better yet is to rev
+ ### close_revision()'s API to allow both committed_rev and err
+ ### to be returned, see #3768. */
+ svn_error_clear(err);
+ }
+ }
+ else
+ {
+ svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
+ if (conflict_msg)
+ return svn_error_quick_wrap(err, conflict_msg);
+ else
+ return svn_error_trace(err);
+ }
+
+ /* Run post-commit hook, if so commanded. */
+ if (pb->use_post_commit_hook)
+ {
+ if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env,
+ committed_rev, txn_name,
+ rb->pool)))
+ return svn_error_create
+ (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
+ _("Commit succeeded, but post-commit hook failed"));
+ }
+
+ /* After a successful commit, must record the dump-rev -> in-repos-rev
+ mapping, so that copyfrom instructions in the dump file can look up the
+ correct repository revision to copy from. */
+ set_revision_mapping(pb->rev_map, rb->rev, committed_rev);
+
+ /* If the incoming dump stream has non-contiguous revisions (e.g. from
+ using svndumpfilter --drop-empty-revs without --renumber-revs) then
+ we must account for the missing gaps in PB->REV_MAP. Otherwise we
+ might not be able to map all mergeinfo source revisions to the correct
+ revisions in the target repos. */
+ if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
+ && (rb->rev != pb->last_rev_mapped + 1))
+ {
+ svn_revnum_t i;
+
+ for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
+ {
+ set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
+ }
+ }
+
+ /* Update our "last revision mapped". */
+ pb->last_rev_mapped = rb->rev;
+
+ /* Deltify the predecessors of paths changed in this revision. */
+ SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool));
+
+ /* Grrr, svn_fs_commit_txn rewrites the datestamp property to the
+ current clock-time. We don't want that, we want to preserve
+ history exactly. Good thing revision props aren't versioned!
+ Note that if rb->datestamp is NULL, that's fine -- if the dump
+ data doesn't carry a datestamp, we want to preserve that fact in
+ the load. */
+ SVN_ERR(change_rev_prop(pb->repos, committed_rev, SVN_PROP_REVISION_DATE,
+ rb->datestamp, pb->validate_props, rb->pool));
+
+ if (pb->notify_func)
+ {
+ pb->notify->action = svn_repos_notify_load_txn_committed;
+ pb->notify->new_revision = committed_rev;
+ pb->notify->old_revision = ((committed_rev == rb->rev)
+ ? SVN_INVALID_REVNUM
+ : rb->rev);
+ pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*----------------------------------------------------------------------*/
+
+/** The public routines **/
+
+
+svn_error_t *
+svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks,
+ void **parse_baton,
+ svn_repos_t *repos,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_boolean_t use_history,
+ svn_boolean_t validate_props,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_parse_fns3_t *parser = apr_pcalloc(pool, sizeof(*parser));
+ struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb));
+
+ if (parent_dir)
+ parent_dir = svn_relpath_canonicalize(parent_dir, pool);
+
+ SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
+ SVN_IS_VALID_REVNUM(end_rev))
+ || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
+ (! SVN_IS_VALID_REVNUM(end_rev))));
+ if (SVN_IS_VALID_REVNUM(start_rev))
+ SVN_ERR_ASSERT(start_rev <= end_rev);
+
+ parser->magic_header_record = magic_header_record;
+ parser->uuid_record = uuid_record;
+ parser->new_revision_record = new_revision_record;
+ parser->new_node_record = new_node_record;
+ parser->set_revision_property = set_revision_property;
+ parser->set_node_property = set_node_property;
+ parser->remove_node_props = remove_node_props;
+ parser->set_fulltext = set_fulltext;
+ parser->close_node = close_node;
+ parser->close_revision = close_revision;
+ parser->delete_node_property = delete_node_property;
+ parser->apply_textdelta = apply_textdelta;
+
+ pb->repos = repos;
+ pb->fs = svn_repos_fs(repos);
+ pb->use_history = use_history;
+ pb->validate_props = validate_props;
+ pb->notify_func = notify_func;
+ pb->notify_baton = notify_baton;
+ pb->notify = svn_repos_notify_create(svn_repos_notify_load_txn_start, pool);
+ pb->uuid_action = uuid_action;
+ pb->parent_dir = parent_dir;
+ pb->pool = pool;
+ pb->rev_map = apr_hash_make(pool);
+ pb->oldest_old_rev = SVN_INVALID_REVNUM;
+ pb->last_rev_mapped = SVN_INVALID_REVNUM;
+ pb->start_rev = start_rev;
+ pb->end_rev = end_rev;
+
+ *callbacks = parser;
+ *parse_baton = pb;
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_repos_load_fs4(svn_repos_t *repos,
+ svn_stream_t *dumpstream,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ enum svn_repos_load_uuid uuid_action,
+ const char *parent_dir,
+ svn_boolean_t use_pre_commit_hook,
+ svn_boolean_t use_post_commit_hook,
+ svn_boolean_t validate_props,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const svn_repos_parse_fns3_t *parser;
+ void *parse_baton;
+ struct parse_baton *pb;
+
+ /* This is really simple. */
+
+ SVN_ERR(svn_repos_get_fs_build_parser4(&parser, &parse_baton,
+ repos,
+ start_rev, end_rev,
+ TRUE, /* look for copyfrom revs */
+ validate_props,
+ uuid_action,
+ parent_dir,
+ notify_func,
+ notify_baton,
+ pool));
+
+ /* Heh. We know this is a parse_baton. This file made it. So
+ cast away, and set our hook booleans. */
+ pb = parse_baton;
+ pb->use_pre_commit_hook = use_pre_commit_hook;
+ pb->use_post_commit_hook = use_post_commit_hook;
+
+ return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
+ cancel_func, cancel_baton, pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/load.c b/contrib/subversion/subversion/libsvn_repos/load.c
new file mode 100644
index 0000000..691ff92
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/load.c
@@ -0,0 +1,684 @@
+/* load.c --- parsing a 'dumpfile'-formatted stream.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include "svn_private_config.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_repos.h"
+#include "svn_string.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "repos.h"
+#include "svn_private_config.h"
+#include "svn_mergeinfo.h"
+#include "svn_checksum.h"
+#include "svn_subst.h"
+#include "svn_ctype.h"
+
+#include <apr_lib.h>
+
+#include "private/svn_dep_compat.h"
+#include "private/svn_mergeinfo_private.h"
+
+/*----------------------------------------------------------------------*/
+
+/** The parser and related helper funcs **/
+
+
+static svn_error_t *
+stream_ran_dry(void)
+{
+ return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
+ _("Premature end of content data in dumpstream"));
+}
+
+static svn_error_t *
+stream_malformed(void)
+{
+ return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+ _("Dumpstream data appears to be malformed"));
+}
+
+/* Allocate a new hash *HEADERS in POOL, and read a series of
+ RFC822-style headers from STREAM. Duplicate each header's name and
+ value into POOL and store in hash as a const char * ==> const char *.
+
+ The headers are assumed to be terminated by a single blank line,
+ which will be permanently sucked from the stream and tossed.
+
+ If the caller has already read in the first header line, it should
+ be passed in as FIRST_HEADER. If not, pass NULL instead.
+ */
+static svn_error_t *
+read_header_block(svn_stream_t *stream,
+ svn_stringbuf_t *first_header,
+ apr_hash_t **headers,
+ apr_pool_t *pool)
+{
+ *headers = apr_hash_make(pool);
+
+ while (1)
+ {
+ svn_stringbuf_t *header_str;
+ const char *name, *value;
+ svn_boolean_t eof;
+ apr_size_t i = 0;
+
+ if (first_header != NULL)
+ {
+ header_str = first_header;
+ first_header = NULL; /* so we never visit this block again. */
+ eof = FALSE;
+ }
+
+ else
+ /* Read the next line into a stringbuf. */
+ SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
+
+ if (svn_stringbuf_isempty(header_str))
+ break; /* end of header block */
+ else if (eof)
+ return stream_ran_dry();
+
+ /* Find the next colon in the stringbuf. */
+ while (header_str->data[i] != ':')
+ {
+ if (header_str->data[i] == '\0')
+ return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+ _("Dump stream contains a malformed "
+ "header (with no ':') at '%.20s'"),
+ header_str->data);
+ i++;
+ }
+ /* Create a 'name' string and point to it. */
+ header_str->data[i] = '\0';
+ name = header_str->data;
+
+ /* Skip over the NULL byte and the space following it. */
+ i += 2;
+ if (i > header_str->len)
+ return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+ _("Dump stream contains a malformed "
+ "header (with no value) at '%.20s'"),
+ header_str->data);
+
+ /* Point to the 'value' string. */
+ value = header_str->data + i;
+
+ /* Store name/value in hash. */
+ svn_hash_sets(*headers, name, value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *PBUF to a string of length LEN, allocated in POOL, read from STREAM.
+ Also read a newline from STREAM and increase *ACTUAL_LEN by the total
+ number of bytes read from STREAM. */
+static svn_error_t *
+read_key_or_val(char **pbuf,
+ svn_filesize_t *actual_length,
+ svn_stream_t *stream,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ char *buf = apr_pcalloc(pool, len + 1);
+ apr_size_t numread;
+ char c;
+
+ numread = len;
+ SVN_ERR(svn_stream_read(stream, buf, &numread));
+ *actual_length += numread;
+ if (numread != len)
+ return svn_error_trace(stream_ran_dry());
+ buf[len] = '\0';
+
+ /* Suck up extra newline after key data */
+ numread = 1;
+ SVN_ERR(svn_stream_read(stream, &c, &numread));
+ *actual_length += numread;
+ if (numread != 1)
+ return svn_error_trace(stream_ran_dry());
+ if (c != '\n')
+ return svn_error_trace(stream_malformed());
+
+ *pbuf = buf;
+ return SVN_NO_ERROR;
+}
+
+
+/* Read CONTENT_LENGTH bytes from STREAM, parsing the bytes as an
+ encoded Subversion properties hash, and making multiple calls to
+ PARSE_FNS->set_*_property on RECORD_BATON (depending on the value
+ of IS_NODE.)
+
+ Set *ACTUAL_LENGTH to the number of bytes consumed from STREAM.
+ If an error is returned, the value of *ACTUAL_LENGTH is undefined.
+
+ Use POOL for all allocations. */
+static svn_error_t *
+parse_property_block(svn_stream_t *stream,
+ svn_filesize_t content_length,
+ const svn_repos_parse_fns3_t *parse_fns,
+ void *record_baton,
+ void *parse_baton,
+ svn_boolean_t is_node,
+ svn_filesize_t *actual_length,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *strbuf;
+ apr_pool_t *proppool = svn_pool_create(pool);
+
+ *actual_length = 0;
+ while (content_length != *actual_length)
+ {
+ char *buf; /* a pointer into the stringbuf's data */
+ svn_boolean_t eof;
+
+ svn_pool_clear(proppool);
+
+ /* Read a key length line. (Actually, it might be PROPS_END). */
+ SVN_ERR(svn_stream_readline(stream, &strbuf, "\n", &eof, proppool));
+
+ if (eof)
+ {
+ /* We could just use stream_ran_dry() or stream_malformed(),
+ but better to give a non-generic property block error. */
+ return svn_error_create
+ (SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+ _("Incomplete or unterminated property block"));
+ }
+
+ *actual_length += (strbuf->len + 1); /* +1 because we read a \n too. */
+ buf = strbuf->data;
+
+ if (! strcmp(buf, "PROPS-END"))
+ break; /* no more properties. */
+
+ else if ((buf[0] == 'K') && (buf[1] == ' '))
+ {
+ char *keybuf;
+ apr_uint64_t len;
+
+ SVN_ERR(svn_cstring_strtoui64(&len, buf + 2, 0, APR_SIZE_MAX, 10));
+ SVN_ERR(read_key_or_val(&keybuf, actual_length,
+ stream, (apr_size_t)len, proppool));
+
+ /* Read a val length line */
+ SVN_ERR(svn_stream_readline(stream, &strbuf, "\n", &eof, proppool));
+ if (eof)
+ return stream_ran_dry();
+
+ *actual_length += (strbuf->len + 1); /* +1 because we read \n too */
+ buf = strbuf->data;
+
+ if ((buf[0] == 'V') && (buf[1] == ' '))
+ {
+ svn_string_t propstring;
+ char *valbuf;
+ apr_int64_t val;
+
+ SVN_ERR(svn_cstring_atoi64(&val, buf + 2));
+ propstring.len = (apr_size_t)val;
+ SVN_ERR(read_key_or_val(&valbuf, actual_length,
+ stream, propstring.len, proppool));
+ propstring.data = valbuf;
+
+ /* Now, send the property pair to the vtable! */
+ if (is_node)
+ {
+ SVN_ERR(parse_fns->set_node_property(record_baton,
+ keybuf,
+ &propstring));
+ }
+ else
+ {
+ SVN_ERR(parse_fns->set_revision_property(record_baton,
+ keybuf,
+ &propstring));
+ }
+ }
+ else
+ return stream_malformed(); /* didn't find expected 'V' line */
+ }
+ else if ((buf[0] == 'D') && (buf[1] == ' '))
+ {
+ char *keybuf;
+ apr_uint64_t len;
+
+ SVN_ERR(svn_cstring_strtoui64(&len, buf + 2, 0, APR_SIZE_MAX, 10));
+ SVN_ERR(read_key_or_val(&keybuf, actual_length,
+ stream, (apr_size_t)len, proppool));
+
+ /* We don't expect these in revision properties, and if we see
+ one when we don't have a delete_node_property callback,
+ then we're seeing a v3 feature in a v2 dump. */
+ if (!is_node || !parse_fns->delete_node_property)
+ return stream_malformed();
+
+ SVN_ERR(parse_fns->delete_node_property(record_baton, keybuf));
+ }
+ else
+ return stream_malformed(); /* didn't find expected 'K' line */
+
+ } /* while (1) */
+
+ svn_pool_destroy(proppool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Read CONTENT_LENGTH bytes from STREAM, and use
+ PARSE_FNS->set_fulltext to push those bytes as replace fulltext for
+ a node. Use BUFFER/BUFLEN to push the fulltext in "chunks".
+
+ Use POOL for all allocations. */
+static svn_error_t *
+parse_text_block(svn_stream_t *stream,
+ svn_filesize_t content_length,
+ svn_boolean_t is_delta,
+ const svn_repos_parse_fns3_t *parse_fns,
+ void *record_baton,
+ char *buffer,
+ apr_size_t buflen,
+ apr_pool_t *pool)
+{
+ svn_stream_t *text_stream = NULL;
+ apr_size_t num_to_read, rlen, wlen;
+
+ if (is_delta)
+ {
+ svn_txdelta_window_handler_t wh;
+ void *whb;
+
+ SVN_ERR(parse_fns->apply_textdelta(&wh, &whb, record_baton));
+ if (wh)
+ text_stream = svn_txdelta_parse_svndiff(wh, whb, TRUE, pool);
+ }
+ else
+ {
+ /* Get a stream to which we can push the data. */
+ SVN_ERR(parse_fns->set_fulltext(&text_stream, record_baton));
+ }
+
+ /* If there are no contents to read, just write an empty buffer
+ through our callback. */
+ if (content_length == 0)
+ {
+ wlen = 0;
+ if (text_stream)
+ SVN_ERR(svn_stream_write(text_stream, "", &wlen));
+ }
+
+ /* Regardless of whether or not we have a sink for our data, we
+ need to read it. */
+ while (content_length)
+ {
+ if (content_length >= (svn_filesize_t)buflen)
+ rlen = buflen;
+ else
+ rlen = (apr_size_t) content_length;
+
+ num_to_read = rlen;
+ SVN_ERR(svn_stream_read(stream, buffer, &rlen));
+ content_length -= rlen;
+ if (rlen != num_to_read)
+ return stream_ran_dry();
+
+ if (text_stream)
+ {
+ /* write however many bytes you read. */
+ wlen = rlen;
+ SVN_ERR(svn_stream_write(text_stream, buffer, &wlen));
+ if (wlen != rlen)
+ {
+ /* Uh oh, didn't write as many bytes as we read. */
+ return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
+ _("Unexpected EOF writing contents"));
+ }
+ }
+ }
+
+ /* If we opened a stream, we must close it. */
+ if (text_stream)
+ SVN_ERR(svn_stream_close(text_stream));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Parse VERSIONSTRING and verify that we support the dumpfile format
+ version number, setting *VERSION appropriately. */
+static svn_error_t *
+parse_format_version(int *version,
+ const char *versionstring)
+{
+ static const int magic_len = sizeof(SVN_REPOS_DUMPFILE_MAGIC_HEADER) - 1;
+ const char *p = strchr(versionstring, ':');
+ int value;
+
+ if (p == NULL
+ || p != (versionstring + magic_len)
+ || strncmp(versionstring,
+ SVN_REPOS_DUMPFILE_MAGIC_HEADER,
+ magic_len))
+ return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+ _("Malformed dumpfile header '%s'"),
+ versionstring);
+
+ SVN_ERR(svn_cstring_atoi(&value, p + 1));
+
+ if (value > SVN_REPOS_DUMPFILE_FORMAT_VERSION)
+ return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+ _("Unsupported dumpfile version: %d"),
+ value);
+
+ *version = value;
+ return SVN_NO_ERROR;
+}
+
+
+
+/*----------------------------------------------------------------------*/
+
+/** The public routines **/
+
+svn_error_t *
+svn_repos_parse_dumpstream3(svn_stream_t *stream,
+ const svn_repos_parse_fns3_t *parse_fns,
+ void *parse_baton,
+ svn_boolean_t deltas_are_text,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_boolean_t eof;
+ svn_stringbuf_t *linebuf;
+ void *rev_baton = NULL;
+ char *buffer = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
+ apr_size_t buflen = SVN__STREAM_CHUNK_SIZE;
+ apr_pool_t *linepool = svn_pool_create(pool);
+ apr_pool_t *revpool = svn_pool_create(pool);
+ apr_pool_t *nodepool = svn_pool_create(pool);
+ int version;
+
+ SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));
+ if (eof)
+ return stream_ran_dry();
+
+ /* The first two lines of the stream are the dumpfile-format version
+ number, and a blank line. To preserve backward compatibility,
+ don't assume the existence of newer parser-vtable functions. */
+ SVN_ERR(parse_format_version(&version, linebuf->data));
+ if (parse_fns->magic_header_record != NULL)
+ SVN_ERR(parse_fns->magic_header_record(version, parse_baton, pool));
+
+ /* A dumpfile "record" is defined to be a header-block of
+ rfc822-style headers, possibly followed by a content-block.
+
+ - A header-block is always terminated by a single blank line (\n\n)
+
+ - We know whether the record has a content-block by looking for
+ a 'Content-length:' header. The content-block will always be
+ of a specific length, plus an extra newline.
+
+ Once a record is fully sucked from the stream, an indeterminate
+ number of blank lines (or lines that begin with whitespace) may
+ follow before the next record (or the end of the stream.)
+ */
+
+ while (1)
+ {
+ apr_hash_t *headers;
+ void *node_baton;
+ svn_boolean_t found_node = FALSE;
+ svn_boolean_t old_v1_with_cl = FALSE;
+ const char *content_length;
+ const char *prop_cl;
+ const char *text_cl;
+ const char *value;
+ svn_filesize_t actual_prop_length;
+
+ /* Clear our per-line pool. */
+ svn_pool_clear(linepool);
+
+ /* Check for cancellation. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Keep reading blank lines until we discover a new record, or until
+ the stream runs out. */
+ SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));
+
+ if (eof)
+ {
+ if (svn_stringbuf_isempty(linebuf))
+ break; /* end of stream, go home. */
+ else
+ return stream_ran_dry();
+ }
+
+ if ((linebuf->len == 0) || (svn_ctype_isspace(linebuf->data[0])))
+ continue; /* empty line ... loop */
+
+ /*** Found the beginning of a new record. ***/
+
+ /* The last line we read better be a header of some sort.
+ Read the whole header-block into a hash. */
+ SVN_ERR(read_header_block(stream, linebuf, &headers, linepool));
+
+ /*** Handle the various header blocks. ***/
+
+ /* Is this a revision record? */
+ if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER))
+ {
+ /* If we already have a rev_baton open, we need to close it
+ and clear the per-revision subpool. */
+ if (rev_baton != NULL)
+ {
+ SVN_ERR(parse_fns->close_revision(rev_baton));
+ svn_pool_clear(revpool);
+ }
+
+ SVN_ERR(parse_fns->new_revision_record(&rev_baton,
+ headers, parse_baton,
+ revpool));
+ }
+ /* Or is this, perhaps, a node record? */
+ else if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH))
+ {
+ SVN_ERR(parse_fns->new_node_record(&node_baton,
+ headers,
+ rev_baton,
+ nodepool));
+ found_node = TRUE;
+ }
+ /* Or is this the repos UUID? */
+ else if ((value = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_UUID)))
+ {
+ SVN_ERR(parse_fns->uuid_record(value, parse_baton, pool));
+ }
+ /* Or perhaps a dumpfile format? */
+ /* ### TODO: use parse_format_version */
+ else if ((value = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_MAGIC_HEADER)))
+ {
+ /* ### someday, switch modes of operation here. */
+ SVN_ERR(svn_cstring_atoi(&version, value));
+ }
+ /* Or is this bogosity?! */
+ else
+ {
+ /* What the heck is this record?!? */
+ return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+ _("Unrecognized record type in stream"));
+ }
+
+ /* Need 3 values below to determine v1 dump type
+
+ Old (pre 0.14?) v1 dumps don't have Prop-content-length
+ and Text-content-length fields, but always have a properties
+ block in a block with Content-Length > 0 */
+
+ content_length = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
+ prop_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
+ text_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
+ old_v1_with_cl =
+ version == 1 && content_length && ! prop_cl && ! text_cl;
+
+ /* Is there a props content-block to parse? */
+ if (prop_cl || old_v1_with_cl)
+ {
+ const char *delta = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_PROP_DELTA);
+ svn_boolean_t is_delta = (delta && strcmp(delta, "true") == 0);
+
+ /* First, remove all node properties, unless this is a delta
+ property block. */
+ if (found_node && !is_delta)
+ SVN_ERR(parse_fns->remove_node_props(node_baton));
+
+ SVN_ERR(parse_property_block
+ (stream,
+ svn__atoui64(prop_cl ? prop_cl : content_length),
+ parse_fns,
+ found_node ? node_baton : rev_baton,
+ parse_baton,
+ found_node,
+ &actual_prop_length,
+ found_node ? nodepool : revpool));
+ }
+
+ /* Is there a text content-block to parse? */
+ if (text_cl)
+ {
+ const char *delta = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_TEXT_DELTA);
+ svn_boolean_t is_delta = FALSE;
+ if (! deltas_are_text)
+ is_delta = (delta && strcmp(delta, "true") == 0);
+
+ SVN_ERR(parse_text_block(stream,
+ svn__atoui64(text_cl),
+ is_delta,
+ parse_fns,
+ found_node ? node_baton : rev_baton,
+ buffer,
+ buflen,
+ found_node ? nodepool : revpool));
+ }
+ else if (old_v1_with_cl)
+ {
+ /* An old-v1 block with a Content-length might have a text block.
+ If the property block did not consume all the bytes of the
+ Content-length, then it clearly does have a text block.
+ If not, then we must deduce whether we have an *empty* text
+ block or an *absent* text block. The rules are:
+ - "Node-kind: file" blocks have an empty (i.e. present, but
+ zero-length) text block, since they represent a file
+ modification. Note that file-copied-text-unmodified blocks
+ have no Content-length - even if they should have contained
+ a modified property block, the pre-0.14 dumper forgets to
+ dump the modified properties.
+ - If it is not a file node, then it is a revision or directory,
+ and so has an absent text block.
+ */
+ const char *node_kind;
+ svn_filesize_t cl_value = svn__atoui64(content_length)
+ - actual_prop_length;
+
+ if (cl_value ||
+ ((node_kind = svn_hash_gets(headers,
+ SVN_REPOS_DUMPFILE_NODE_KIND))
+ && strcmp(node_kind, "file") == 0)
+ )
+ SVN_ERR(parse_text_block(stream,
+ cl_value,
+ FALSE,
+ parse_fns,
+ found_node ? node_baton : rev_baton,
+ buffer,
+ buflen,
+ found_node ? nodepool : revpool));
+ }
+
+ /* if we have a content-length header, did we read all of it?
+ in case of an old v1, we *always* read all of it, because
+ text-content-length == content-length - prop-content-length
+ */
+ if (content_length && ! old_v1_with_cl)
+ {
+ apr_size_t rlen, num_to_read;
+ svn_filesize_t remaining =
+ svn__atoui64(content_length) -
+ (prop_cl ? svn__atoui64(prop_cl) : 0) -
+ (text_cl ? svn__atoui64(text_cl) : 0);
+
+
+ if (remaining < 0)
+ return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+ _("Sum of subblock sizes larger than "
+ "total block content length"));
+
+ /* Consume remaining bytes in this content block */
+ while (remaining > 0)
+ {
+ if (remaining >= (svn_filesize_t)buflen)
+ rlen = buflen;
+ else
+ rlen = (apr_size_t) remaining;
+
+ num_to_read = rlen;
+ SVN_ERR(svn_stream_read(stream, buffer, &rlen));
+ remaining -= rlen;
+ if (rlen != num_to_read)
+ return stream_ran_dry();
+ }
+ }
+
+ /* If we just finished processing a node record, we need to
+ close the node record and clear the per-node subpool. */
+ if (found_node)
+ {
+ SVN_ERR(parse_fns->close_node(node_baton));
+ svn_pool_clear(nodepool);
+ }
+
+ /*** End of processing for one record. ***/
+
+ } /* end of stream */
+
+ /* Close out whatever revision we're in. */
+ if (rev_baton != NULL)
+ SVN_ERR(parse_fns->close_revision(rev_baton));
+
+ svn_pool_destroy(linepool);
+ svn_pool_destroy(revpool);
+ svn_pool_destroy(nodepool);
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/log.c b/contrib/subversion/subversion/libsvn_repos/log.c
new file mode 100644
index 0000000..8ca870b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/log.c
@@ -0,0 +1,2369 @@
+/* log.c --- retrieving log messages
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <stdlib.h>
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include "svn_compat.h"
+#include "svn_private_config.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "svn_fs.h"
+#include "svn_repos.h"
+#include "svn_string.h"
+#include "svn_sorts.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "repos.h"
+#include "private/svn_fspath.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_subr_private.h"
+
+
+
+svn_error_t *
+svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
+ svn_repos_t *repos,
+ svn_revnum_t revision,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = svn_repos_fs(repos);
+ svn_fs_root_t *rev_root;
+ apr_hash_t *changes;
+ apr_hash_index_t *hi;
+ svn_boolean_t found_readable = FALSE;
+ svn_boolean_t found_unreadable = FALSE;
+ apr_pool_t *subpool;
+
+ /* By default, we'll grant full read access to REVISION. */
+ *access_level = svn_repos_revision_access_full;
+
+ /* No auth-checking function? We're done. */
+ if (! authz_read_func)
+ return SVN_NO_ERROR;
+
+ /* Fetch the changes associated with REVISION. */
+ SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
+ SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool));
+
+ /* No changed paths? We're done. */
+ if (apr_hash_count(changes) == 0)
+ return SVN_NO_ERROR;
+
+ /* Otherwise, we have to check the readability of each changed
+ path, or at least enough to answer the question asked. */
+ subpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ svn_fs_path_change2_t *change;
+ svn_boolean_t readable;
+
+ svn_pool_clear(subpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ change = val;
+
+ SVN_ERR(authz_read_func(&readable, rev_root, key,
+ authz_read_baton, subpool));
+ if (! readable)
+ found_unreadable = TRUE;
+ else
+ found_readable = TRUE;
+
+ /* If we have at least one of each (readable/unreadable), we
+ have our answer. */
+ if (found_readable && found_unreadable)
+ goto decision;
+
+ switch (change->change_kind)
+ {
+ case svn_fs_path_change_add:
+ case svn_fs_path_change_replace:
+ {
+ const char *copyfrom_path;
+ svn_revnum_t copyfrom_rev;
+
+ SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
+ rev_root, key, subpool));
+ if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
+ {
+ svn_fs_root_t *copyfrom_root;
+ SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
+ copyfrom_rev, subpool));
+ SVN_ERR(authz_read_func(&readable,
+ copyfrom_root, copyfrom_path,
+ authz_read_baton, subpool));
+ if (! readable)
+ found_unreadable = TRUE;
+
+ /* If we have at least one of each (readable/unreadable), we
+ have our answer. */
+ if (found_readable && found_unreadable)
+ goto decision;
+ }
+ }
+ break;
+
+ case svn_fs_path_change_delete:
+ case svn_fs_path_change_modify:
+ default:
+ break;
+ }
+ }
+
+ decision:
+ svn_pool_destroy(subpool);
+
+ /* Either every changed path was unreadable... */
+ if (! found_readable)
+ *access_level = svn_repos_revision_access_none;
+
+ /* ... or some changed path was unreadable... */
+ else if (found_unreadable)
+ *access_level = svn_repos_revision_access_partial;
+
+ /* ... or every changed path was readable (the default). */
+ return SVN_NO_ERROR;
+}
+
+
+/* Store as keys in CHANGED the paths of all node in ROOT that show a
+ * significant change. "Significant" means that the text or
+ * properties of the node were changed, or that the node was added or
+ * deleted.
+ *
+ * The CHANGED hash set and its keys and values are allocated in POOL;
+ * keys are const char * paths and values are svn_log_changed_path_t.
+ *
+ * To prevent changes from being processed over and over again, the
+ * changed paths for ROOT may be passed in PREFETCHED_CHANGES. If the
+ * latter is NULL, we will request the list inside this function.
+ *
+ * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
+ * AUTHZ_READ_BATON and FS) to check whether each changed-path (and
+ * copyfrom_path) is readable:
+ *
+ * - If some paths are readable and some are not, then silently
+ * omit the unreadable paths from the CHANGED hash, and return
+ * SVN_ERR_AUTHZ_PARTIALLY_READABLE.
+ *
+ * - If absolutely every changed-path (and copyfrom_path) is
+ * unreadable, then return an empty CHANGED hash and
+ * SVN_ERR_AUTHZ_UNREADABLE. (This is to distinguish a revision
+ * which truly has no changed paths from a revision in which all
+ * paths are unreadable.)
+ */
+static svn_error_t *
+detect_changed(apr_hash_t **changed,
+ svn_fs_root_t *root,
+ svn_fs_t *fs,
+ apr_hash_t *prefetched_changes,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ apr_hash_t *changes = prefetched_changes;
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool;
+ svn_boolean_t found_readable = FALSE;
+ svn_boolean_t found_unreadable = FALSE;
+
+ *changed = svn_hash__make(pool);
+ if (changes == NULL)
+ SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
+
+ if (apr_hash_count(changes) == 0)
+ /* No paths changed in this revision? Uh, sure, I guess the
+ revision is readable, then. */
+ return SVN_NO_ERROR;
+
+ subpool = svn_pool_create(pool);
+
+ for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
+ {
+ /* NOTE: Much of this loop is going to look quite similar to
+ svn_repos_check_revision_access(), but we have to do more things
+ here, so we'll live with the duplication. */
+ const void *key;
+ void *val;
+ svn_fs_path_change2_t *change;
+ const char *path;
+ char action;
+ svn_log_changed_path2_t *item;
+
+ svn_pool_clear(subpool);
+
+ /* KEY will be the path, VAL the change. */
+ apr_hash_this(hi, &key, NULL, &val);
+ path = (const char *) key;
+ change = val;
+
+ /* Skip path if unreadable. */
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+ SVN_ERR(authz_read_func(&readable,
+ root, path,
+ authz_read_baton, subpool));
+ if (! readable)
+ {
+ found_unreadable = TRUE;
+ continue;
+ }
+ }
+
+ /* At least one changed-path was readable. */
+ found_readable = TRUE;
+
+ switch (change->change_kind)
+ {
+ case svn_fs_path_change_reset:
+ continue;
+
+ case svn_fs_path_change_add:
+ action = 'A';
+ break;
+
+ case svn_fs_path_change_replace:
+ action = 'R';
+ break;
+
+ case svn_fs_path_change_delete:
+ action = 'D';
+ break;
+
+ case svn_fs_path_change_modify:
+ default:
+ action = 'M';
+ break;
+ }
+
+ item = svn_log_changed_path2_create(pool);
+ item->action = action;
+ item->node_kind = change->node_kind;
+ item->copyfrom_rev = SVN_INVALID_REVNUM;
+ item->text_modified = change->text_mod ? svn_tristate_true
+ : svn_tristate_false;
+ item->props_modified = change->prop_mod ? svn_tristate_true
+ : svn_tristate_false;
+
+ /* Pre-1.6 revision files don't store the change path kind, so fetch
+ it manually. */
+ if (item->node_kind == svn_node_unknown)
+ {
+ svn_fs_root_t *check_root = root;
+ const char *check_path = path;
+
+ /* Deleted items don't exist so check earlier revision. We
+ know the parent must exist and could be a copy */
+ if (change->change_kind == svn_fs_path_change_delete)
+ {
+ svn_fs_history_t *history;
+ svn_revnum_t prev_rev;
+ const char *parent_path, *name;
+
+ svn_fspath__split(&parent_path, &name, path, subpool);
+
+ SVN_ERR(svn_fs_node_history(&history, root, parent_path,
+ subpool));
+
+ /* Two calls because the first call returns the original
+ revision as the deleted child means it is 'interesting' */
+ SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
+ SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
+
+ SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history,
+ subpool));
+ SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, subpool));
+ check_path = svn_fspath__join(parent_path, name, subpool);
+ }
+
+ SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path,
+ subpool));
+ }
+
+
+ if ((action == 'A') || (action == 'R'))
+ {
+ const char *copyfrom_path = change->copyfrom_path;
+ svn_revnum_t copyfrom_rev = change->copyfrom_rev;
+
+ /* the following is a potentially expensive operation since on FSFS
+ we will follow the DAG from ROOT to PATH and that requires
+ actually reading the directories along the way. */
+ if (!change->copyfrom_known)
+ SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
+ root, path, subpool));
+
+ if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
+ {
+ svn_boolean_t readable = TRUE;
+
+ if (authz_read_func)
+ {
+ svn_fs_root_t *copyfrom_root;
+
+ SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
+ copyfrom_rev, subpool));
+ SVN_ERR(authz_read_func(&readable,
+ copyfrom_root, copyfrom_path,
+ authz_read_baton, subpool));
+ if (! readable)
+ found_unreadable = TRUE;
+ }
+
+ if (readable)
+ {
+ item->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
+ item->copyfrom_rev = copyfrom_rev;
+ }
+ }
+ }
+ svn_hash_sets(*changed, apr_pstrdup(pool, path), item);
+ }
+
+ svn_pool_destroy(subpool);
+
+ if (! found_readable)
+ /* Every changed-path was unreadable. */
+ return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE,
+ NULL, NULL);
+
+ if (found_unreadable)
+ /* At least one changed-path was unreadable. */
+ return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
+ NULL, NULL);
+
+ /* Every changed-path was readable. */
+ return SVN_NO_ERROR;
+}
+
+/* This is used by svn_repos_get_logs to keep track of multiple
+ * path history information while working through history.
+ *
+ * The two pools are swapped after each iteration through history because
+ * to get the next history requires the previous one.
+ */
+struct path_info
+{
+ svn_stringbuf_t *path;
+ svn_revnum_t history_rev;
+ svn_boolean_t done;
+ svn_boolean_t first_time;
+
+ /* If possible, we like to keep open the history object for each path,
+ since it avoids needed to open and close it many times as we walk
+ backwards in time. To do so we need two pools, so that we can clear
+ one each time through. If we're not holding the history open for
+ this path then these three pointers will be NULL. */
+ svn_fs_history_t *hist;
+ apr_pool_t *newpool;
+ apr_pool_t *oldpool;
+};
+
+/* Advance to the next history for the path.
+ *
+ * If INFO->HIST is not NULL we do this using that existing history object,
+ * otherwise we open a new one.
+ *
+ * If no more history is available or the history revision is less
+ * (earlier) than START, or the history is not available due
+ * to authorization, then INFO->DONE is set to TRUE.
+ *
+ * A STRICT value of FALSE will indicate to follow history across copied
+ * paths.
+ *
+ * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
+ * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if
+ * we do indeed find more history for the path.
+ */
+static svn_error_t *
+get_history(struct path_info *info,
+ svn_fs_t *fs,
+ svn_boolean_t strict,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_revnum_t start,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *history_root = NULL;
+ svn_fs_history_t *hist;
+ apr_pool_t *subpool;
+ const char *path;
+
+ if (info->hist)
+ {
+ subpool = info->newpool;
+
+ SVN_ERR(svn_fs_history_prev(&info->hist, info->hist, ! strict, subpool));
+
+ hist = info->hist;
+ }
+ else
+ {
+ subpool = svn_pool_create(pool);
+
+ /* Open the history located at the last rev we were at. */
+ SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
+ subpool));
+
+ SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data,
+ subpool));
+
+ SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
+
+ if (info->first_time)
+ info->first_time = FALSE;
+ else
+ SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
+ }
+
+ if (! hist)
+ {
+ svn_pool_destroy(subpool);
+ if (info->oldpool)
+ svn_pool_destroy(info->oldpool);
+ info->done = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Fetch the location information for this history step. */
+ SVN_ERR(svn_fs_history_location(&path, &info->history_rev,
+ hist, subpool));
+
+ svn_stringbuf_set(info->path, path);
+
+ /* If this history item predates our START revision then
+ don't fetch any more for this path. */
+ if (info->history_rev < start)
+ {
+ svn_pool_destroy(subpool);
+ if (info->oldpool)
+ svn_pool_destroy(info->oldpool);
+ info->done = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Is the history item readable? If not, done with path. */
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+ SVN_ERR(svn_fs_revision_root(&history_root, fs,
+ info->history_rev,
+ subpool));
+ SVN_ERR(authz_read_func(&readable, history_root,
+ info->path->data,
+ authz_read_baton,
+ subpool));
+ if (! readable)
+ info->done = TRUE;
+ }
+
+ if (! info->hist)
+ {
+ svn_pool_destroy(subpool);
+ }
+ else
+ {
+ apr_pool_t *temppool = info->oldpool;
+ info->oldpool = info->newpool;
+ svn_pool_clear(temppool);
+ info->newpool = temppool;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set INFO->HIST to the next history for the path *if* there is history
+ * available and INFO->HISTORY_REV is equal to or greater than CURRENT.
+ *
+ * *CHANGED is set to TRUE if the path has history in the CURRENT revision,
+ * otherwise it is not touched.
+ *
+ * If we do need to get the next history revision for the path, call
+ * get_history to do it -- see it for details.
+ */
+static svn_error_t *
+check_history(svn_boolean_t *changed,
+ struct path_info *info,
+ svn_fs_t *fs,
+ svn_revnum_t current,
+ svn_boolean_t strict,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_revnum_t start,
+ apr_pool_t *pool)
+{
+ /* If we're already done with histories for this path,
+ don't try to fetch any more. */
+ if (info->done)
+ return SVN_NO_ERROR;
+
+ /* If the last rev we got for this path is less than CURRENT,
+ then just return and don't fetch history for this path.
+ The caller will get to this rev eventually or else reach
+ the limit. */
+ if (info->history_rev < current)
+ return SVN_NO_ERROR;
+
+ /* If the last rev we got for this path is equal to CURRENT
+ then set *CHANGED to true and get the next history
+ rev where this path was changed. */
+ *changed = TRUE;
+ return get_history(info, fs, strict, authz_read_func,
+ authz_read_baton, start, pool);
+}
+
+/* Return the next interesting revision in our list of HISTORIES. */
+static svn_revnum_t
+next_history_rev(const apr_array_header_t *histories)
+{
+ svn_revnum_t next_rev = SVN_INVALID_REVNUM;
+ int i;
+
+ for (i = 0; i < histories->nelts; ++i)
+ {
+ struct path_info *info = APR_ARRAY_IDX(histories, i,
+ struct path_info *);
+ if (info->done)
+ continue;
+ if (info->history_rev > next_rev)
+ next_rev = info->history_rev;
+ }
+
+ return next_rev;
+}
+
+/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
+ catalogs describing how mergeinfo values on paths (which are the
+ keys of those catalogs) were changed in REV. If *PREFETCHED_CAHNGES
+ already contains the changed paths for REV, use that. Otherwise,
+ request that data and return it in *PREFETCHED_CHANGES. */
+/* ### TODO: This would make a *great*, useful public function,
+ ### svn_repos_fs_mergeinfo_changed()! -- cmpilato */
+static svn_error_t *
+fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
+ svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
+ apr_hash_t **prefetched_changes,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+
+{
+ svn_fs_root_t *root;
+ apr_pool_t *iterpool;
+ apr_hash_index_t *hi;
+
+ /* Initialize return variables. */
+ *deleted_mergeinfo_catalog = svn_hash__make(result_pool);
+ *added_mergeinfo_catalog = svn_hash__make(result_pool);
+
+ /* Revision 0 has no mergeinfo and no mergeinfo changes. */
+ if (rev == 0)
+ return SVN_NO_ERROR;
+
+ /* We're going to use the changed-paths information for REV to
+ narrow down our search. */
+ SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
+ if (*prefetched_changes == NULL)
+ SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool));
+
+ /* No changed paths? We're done. */
+ if (apr_hash_count(*prefetched_changes) == 0)
+ return SVN_NO_ERROR;
+
+ /* Loop over changes, looking for anything that might carry an
+ svn:mergeinfo change and is one of our paths of interest, or a
+ child or [grand]parent directory thereof. */
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ svn_fs_path_change2_t *change;
+ const char *changed_path, *base_path = NULL;
+ svn_revnum_t base_rev = SVN_INVALID_REVNUM;
+ svn_fs_root_t *base_root = NULL;
+ svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value;
+
+ svn_pool_clear(iterpool);
+
+ /* KEY will be the path, VAL the change. */
+ apr_hash_this(hi, &key, NULL, &val);
+ changed_path = key;
+ change = val;
+
+ /* If there was no property change on this item, ignore it. */
+ if (! change->prop_mod)
+ continue;
+
+ switch (change->change_kind)
+ {
+
+ /* ### TODO: Can the add, replace, and modify cases be joined
+ ### together to all use svn_repos__prev_location()? The
+ ### difference would be the fallback case (path/rev-1 for
+ ### modifies, NULL otherwise). -- cmpilato */
+
+ /* If the path was added or replaced, see if it was created via
+ copy. If so, that will tell us where its previous location
+ was. If not, there's no previous location to examine. */
+ case svn_fs_path_change_add:
+ case svn_fs_path_change_replace:
+ {
+ const char *copyfrom_path;
+ svn_revnum_t copyfrom_rev;
+
+ SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
+ root, changed_path, iterpool));
+ if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
+ {
+ base_path = apr_pstrdup(scratch_pool, copyfrom_path);
+ base_rev = copyfrom_rev;
+ }
+ break;
+ }
+
+ /* If the path was merely modified, see if its previous
+ location was affected by a copy which happened in this
+ revision before assuming it holds the same path it did the
+ previous revision. */
+ case svn_fs_path_change_modify:
+ {
+ svn_revnum_t appeared_rev;
+
+ SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path,
+ &base_rev, fs, rev,
+ changed_path, iterpool));
+
+ /* If this path isn't the result of a copy that occurred
+ in this revision, we can find the previous version of
+ it in REV - 1 at the same path. */
+ if (! (base_path && SVN_IS_VALID_REVNUM(base_rev)
+ && (appeared_rev == rev)))
+ {
+ base_path = changed_path;
+ base_rev = rev - 1;
+ }
+ break;
+ }
+
+ /* We don't care about any of the other cases. */
+ case svn_fs_path_change_delete:
+ case svn_fs_path_change_reset:
+ default:
+ continue;
+ }
+
+ /* If there was a base location, fetch its mergeinfo property value. */
+ if (base_path && SVN_IS_VALID_REVNUM(base_rev))
+ {
+ SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, iterpool));
+ SVN_ERR(svn_fs_node_prop(&prev_mergeinfo_value, base_root, base_path,
+ SVN_PROP_MERGEINFO, iterpool));
+ }
+
+ /* Now fetch the current (as of REV) mergeinfo property value. */
+ SVN_ERR(svn_fs_node_prop(&mergeinfo_value, root, changed_path,
+ SVN_PROP_MERGEINFO, iterpool));
+
+ /* No mergeinfo on either the new or previous location? Just
+ skip it. (If there *was* a change, it would have been in
+ inherited mergeinfo only, which should be picked up by the
+ iteration of this loop that finds the parent paths that
+ really got changed.) */
+ if (! (mergeinfo_value || prev_mergeinfo_value))
+ continue;
+
+ /* If mergeinfo was explicitly added or removed on this path, we
+ need to check to see if that was a real semantic change of
+ meaning. So, fill in the "missing" mergeinfo value with the
+ inherited mergeinfo for that path/revision. */
+ if (prev_mergeinfo_value && (! mergeinfo_value))
+ {
+ apr_array_header_t *query_paths =
+ apr_array_make(iterpool, 1, sizeof(const char *));
+ svn_mergeinfo_t tmp_mergeinfo;
+ svn_mergeinfo_catalog_t tmp_catalog;
+
+ APR_ARRAY_PUSH(query_paths, const char *) = changed_path;
+ SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root,
+ query_paths, svn_mergeinfo_inherited,
+ FALSE, TRUE, iterpool, iterpool));
+ tmp_mergeinfo = svn_hash_gets(tmp_catalog, changed_path);
+ if (tmp_mergeinfo)
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value,
+ tmp_mergeinfo,
+ iterpool));
+ }
+ else if (mergeinfo_value && (! prev_mergeinfo_value)
+ && base_path && SVN_IS_VALID_REVNUM(base_rev))
+ {
+ apr_array_header_t *query_paths =
+ apr_array_make(iterpool, 1, sizeof(const char *));
+ svn_mergeinfo_t tmp_mergeinfo;
+ svn_mergeinfo_catalog_t tmp_catalog;
+
+ APR_ARRAY_PUSH(query_paths, const char *) = base_path;
+ SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, base_root,
+ query_paths, svn_mergeinfo_inherited,
+ FALSE, TRUE, iterpool, iterpool));
+ tmp_mergeinfo = svn_hash_gets(tmp_catalog, base_path);
+ if (tmp_mergeinfo)
+ SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value,
+ tmp_mergeinfo,
+ iterpool));
+ }
+
+ /* If the old and new mergeinfo differ in any way, store the
+ before and after mergeinfo values in our return hashes. */
+ if ((prev_mergeinfo_value && (! mergeinfo_value))
+ || ((! prev_mergeinfo_value) && mergeinfo_value)
+ || (prev_mergeinfo_value && mergeinfo_value
+ && (! svn_string_compare(mergeinfo_value,
+ prev_mergeinfo_value))))
+ {
+ svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL;
+ svn_mergeinfo_t deleted, added;
+ const char *hash_path;
+
+ if (mergeinfo_value)
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo,
+ mergeinfo_value->data, iterpool));
+ if (prev_mergeinfo_value)
+ SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo,
+ prev_mergeinfo_value->data, iterpool));
+ SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
+ mergeinfo, FALSE, result_pool,
+ iterpool));
+
+ /* Toss interesting stuff into our return catalogs. */
+ hash_path = apr_pstrdup(result_pool, changed_path);
+ svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted);
+ svn_hash_sets(*added_mergeinfo_catalog, hash_path, added);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Determine what (if any) mergeinfo for PATHS was modified in
+ revision REV, returning the differences for added mergeinfo in
+ *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO.
+ If *PREFETCHED_CAHNGES already contains the changed paths for
+ REV, use that. Otherwise, request that data and return it in
+ *PREFETCHED_CHANGES.
+ Use POOL for all allocations. */
+static svn_error_t *
+get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
+ svn_mergeinfo_t *deleted_mergeinfo,
+ apr_hash_t **prefetched_changes,
+ svn_fs_t *fs,
+ const apr_array_header_t *paths,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_catalog_t added_mergeinfo_catalog, deleted_mergeinfo_catalog;
+ apr_hash_index_t *hi;
+ svn_fs_root_t *root;
+ apr_pool_t *iterpool;
+ int i;
+ svn_error_t *err;
+
+ /* Initialize return value. */
+ *added_mergeinfo = svn_hash__make(result_pool);
+ *deleted_mergeinfo = svn_hash__make(result_pool);
+
+ /* If we're asking about revision 0, there's no mergeinfo to be found. */
+ if (rev == 0)
+ return SVN_NO_ERROR;
+
+ /* No paths? No mergeinfo. */
+ if (! paths->nelts)
+ return SVN_NO_ERROR;
+
+ /* Create a work subpool and get a root for REV. */
+ SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
+
+ /* Fetch the mergeinfo changes for REV. */
+ err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
+ &added_mergeinfo_catalog,
+ prefetched_changes,
+ fs, rev, scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ /* Issue #3896: If invalid mergeinfo is encountered the
+ best we can do is ignore it and act as if there were
+ no mergeinfo modifications. */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ /* In most revisions, there will be no mergeinfo change at all. */
+ if ( apr_hash_count(deleted_mergeinfo_catalog) == 0
+ && apr_hash_count(added_mergeinfo_catalog) == 0)
+ return SVN_NO_ERROR;
+
+ /* Check our PATHS for any changes to their inherited mergeinfo.
+ (We deal with changes to mergeinfo directly *on* the paths in the
+ following loop.) */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ const char *prev_path;
+ apr_ssize_t klen;
+ svn_revnum_t appeared_rev, prev_rev;
+ svn_fs_root_t *prev_root;
+ svn_mergeinfo_catalog_t catalog, inherited_catalog;
+ svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added,
+ prev_inherited_mergeinfo, inherited_mergeinfo;
+ apr_array_header_t *query_paths;
+
+ svn_pool_clear(iterpool);
+
+ /* If this path is represented in the changed-mergeinfo hashes,
+ we'll deal with it in the loop below. */
+ if (svn_hash_gets(deleted_mergeinfo_catalog, path))
+ continue;
+
+ /* Figure out what path/rev to compare against. Ignore
+ not-found errors returned by the filesystem. */
+ err = svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
+ fs, rev, path, iterpool);
+ if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
+ err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ continue;
+ }
+ SVN_ERR(err);
+
+ /* If this path isn't the result of a copy that occurred in this
+ revision, we can find the previous version of it in REV - 1
+ at the same path. */
+ if (! (prev_path && SVN_IS_VALID_REVNUM(prev_rev)
+ && (appeared_rev == rev)))
+ {
+ prev_path = path;
+ prev_rev = rev - 1;
+ }
+
+ /* Fetch the previous mergeinfo (including inherited stuff) for
+ this path. Ignore not-found errors returned by the
+ filesystem or invalid mergeinfo (Issue #3896).*/
+ SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool));
+ query_paths = apr_array_make(iterpool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(query_paths, const char *) = prev_path;
+ err = svn_fs_get_mergeinfo2(&catalog, prev_root, query_paths,
+ svn_mergeinfo_inherited, FALSE, TRUE,
+ iterpool, iterpool);
+ if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
+ err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
+ err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ continue;
+ }
+ SVN_ERR(err);
+
+ /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due
+ to move as a merge': A copy where the source and destination inherit
+ mergeinfo from the same parent means the inherited mergeinfo of the
+ source and destination will differ, but this diffrence is not
+ indicative of a merge unless the mergeinfo on the inherited parent
+ has actually changed.
+
+ To check for this we must fetch the "raw" previous inherited
+ mergeinfo and the "raw" mergeinfo @REV then compare these. */
+ SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, prev_root, query_paths,
+ svn_mergeinfo_nearest_ancestor, FALSE,
+ FALSE, /* adjust_inherited_mergeinfo */
+ iterpool, iterpool));
+
+ klen = strlen(prev_path);
+ prev_mergeinfo = apr_hash_get(catalog, prev_path, klen);
+ prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, klen);
+
+ /* Fetch the current mergeinfo (as of REV, and including
+ inherited stuff) for this path. */
+ APR_ARRAY_IDX(query_paths, 0, const char *) = path;
+ SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, query_paths,
+ svn_mergeinfo_inherited, FALSE, TRUE,
+ iterpool, iterpool));
+
+ /* Issue #4022 again, fetch the raw inherited mergeinfo. */
+ SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, root, query_paths,
+ svn_mergeinfo_nearest_ancestor, FALSE,
+ FALSE, /* adjust_inherited_mergeinfo */
+ iterpool, iterpool));
+
+ klen = strlen(path);
+ mergeinfo = apr_hash_get(catalog, path, klen);
+ inherited_mergeinfo = apr_hash_get(inherited_catalog, path, klen);
+
+ if (!prev_mergeinfo && !mergeinfo)
+ continue;
+
+ /* Last bit of issue #4022 checking. */
+ if (prev_inherited_mergeinfo && inherited_mergeinfo)
+ {
+ svn_boolean_t inherits_same_mergeinfo;
+
+ SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo,
+ prev_inherited_mergeinfo,
+ inherited_mergeinfo,
+ TRUE, iterpool));
+ /* If a copy rather than an actual merge brought about an
+ inherited mergeinfo change then we are finished. */
+ if (inherits_same_mergeinfo)
+ continue;
+ }
+ else
+ {
+ svn_boolean_t same_mergeinfo;
+ SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo,
+ prev_inherited_mergeinfo,
+ FALSE,
+ TRUE, iterpool));
+ if (same_mergeinfo)
+ continue;
+ }
+
+ /* Compare, constrast, and combine the results. */
+ SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
+ mergeinfo, FALSE, result_pool, iterpool));
+ SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted,
+ result_pool, iterpool));
+ SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added,
+ result_pool, iterpool));
+ }
+
+ /* Merge all the mergeinfos which are, or are children of, one of
+ our paths of interest into one giant delta mergeinfo. */
+ for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog);
+ hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+ const char *changed_path;
+ svn_mergeinfo_t added, deleted;
+
+ /* The path is the key, the mergeinfo delta is the value. */
+ apr_hash_this(hi, &key, &klen, &val);
+ changed_path = key;
+ added = val;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ if (! svn_fspath__skip_ancestor(path, changed_path))
+ continue;
+ svn_pool_clear(iterpool);
+ deleted = apr_hash_get(deleted_mergeinfo_catalog, key, klen);
+ SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo,
+ svn_mergeinfo_dup(deleted, result_pool),
+ result_pool, iterpool));
+ SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo,
+ svn_mergeinfo_dup(added, result_pool),
+ result_pool, iterpool));
+
+ break;
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Fill LOG_ENTRY with history information in FS at REV. */
+static svn_error_t *
+fill_log_entry(svn_log_entry_t *log_entry,
+ svn_revnum_t rev,
+ svn_fs_t *fs,
+ apr_hash_t *prefetched_changes,
+ svn_boolean_t discover_changed_paths,
+ const apr_array_header_t *revprops,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ apr_hash_t *r_props, *changed_paths = NULL;
+ svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
+
+ /* Discover changed paths if the user requested them
+ or if we need to check that they are readable. */
+ if ((rev > 0)
+ && (authz_read_func || discover_changed_paths))
+ {
+ svn_fs_root_t *newroot;
+ svn_error_t *patherr;
+
+ SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
+ patherr = detect_changed(&changed_paths,
+ newroot, fs, prefetched_changes,
+ authz_read_func, authz_read_baton,
+ pool);
+
+ if (patherr
+ && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE)
+ {
+ /* All changed-paths are unreadable, so clear all fields. */
+ svn_error_clear(patherr);
+ changed_paths = NULL;
+ get_revprops = FALSE;
+ }
+ else if (patherr
+ && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
+ {
+ /* At least one changed-path was unreadable, so censor all
+ but author and date. (The unreadable paths are already
+ missing from the hash.) */
+ svn_error_clear(patherr);
+ censor_revprops = TRUE;
+ }
+ else if (patherr)
+ return patherr;
+
+ /* It may be the case that an authz func was passed in, but
+ the user still doesn't want to see any changed-paths. */
+ if (! discover_changed_paths)
+ changed_paths = NULL;
+ }
+
+ if (get_revprops)
+ {
+ /* User is allowed to see at least some revprops. */
+ SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
+ if (revprops == NULL)
+ {
+ /* Requested all revprops... */
+ if (censor_revprops)
+ {
+ /* ... but we can only return author/date. */
+ log_entry->revprops = svn_hash__make(pool);
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+ svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR));
+ svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
+ svn_hash_gets(r_props, SVN_PROP_REVISION_DATE));
+ }
+ else
+ /* ... so return all we got. */
+ log_entry->revprops = r_props;
+ }
+ else
+ {
+ /* Requested only some revprops... */
+ int i;
+ for (i = 0; i < revprops->nelts; i++)
+ {
+ char *name = APR_ARRAY_IDX(revprops, i, char *);
+ svn_string_t *value = svn_hash_gets(r_props, name);
+ if (censor_revprops
+ && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0
+ || strcmp(name, SVN_PROP_REVISION_DATE) == 0))
+ /* ... but we can only return author/date. */
+ continue;
+ if (log_entry->revprops == NULL)
+ log_entry->revprops = svn_hash__make(pool);
+ svn_hash_sets(log_entry->revprops, name, value);
+ }
+ }
+ }
+
+ log_entry->changed_paths = changed_paths;
+ log_entry->changed_paths2 = changed_paths;
+ log_entry->revision = rev;
+
+ return SVN_NO_ERROR;
+}
+
+/* Send a log message for REV to RECEIVER with its RECEIVER_BATON.
+
+ FS is used with REV to fetch the interesting history information,
+ such as changed paths, revprops, etc.
+
+ The detect_changed function is used if either AUTHZ_READ_FUNC is
+ not NULL, or if DISCOVER_CHANGED_PATHS is TRUE. See it for details.
+
+ If DESCENDING_ORDER is true, send child messages in descending order.
+
+ If REVPROPS is NULL, retrieve all revision properties; else, retrieve
+ only the revision properties named by the (const char *) array elements
+ (i.e. retrieve none if the array is empty).
+
+ LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and
+ NESTED_MERGES are as per the arguments of the same name to DO_LOGS. If
+ HANDLING_MERGED_REVISION is true and *all* changed paths within REV are
+ already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send
+ the log message for REV. If SUBTRACTIVE_MERGE is true, then REV was
+ reverse merged.
+
+ If HANDLING_MERGED_REVISIONS is FALSE then ignore NESTED_MERGES. Otherwise
+ if NESTED_MERGES is not NULL and REV is contained in it, then don't send
+ the log for REV, otherwise send it normally and add REV to
+ NESTED_MERGES. */
+static svn_error_t *
+send_log(svn_revnum_t rev,
+ svn_fs_t *fs,
+ apr_hash_t *prefetched_changes,
+ svn_mergeinfo_t log_target_history_as_mergeinfo,
+ apr_hash_t *nested_merges,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t subtractive_merge,
+ svn_boolean_t handling_merged_revision,
+ const apr_array_header_t *revprops,
+ svn_boolean_t has_children,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ svn_log_entry_t *log_entry;
+ /* Assume we want to send the log for REV. */
+ svn_boolean_t found_rev_of_interest = TRUE;
+
+ log_entry = svn_log_entry_create(pool);
+ SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes,
+ discover_changed_paths || handling_merged_revision,
+ revprops, authz_read_func, authz_read_baton,
+ pool));
+ log_entry->has_children = has_children;
+ log_entry->subtractive_merge = subtractive_merge;
+
+ /* Is REV a merged revision that is already part of
+ LOG_TARGET_HISTORY_AS_MERGEINFO? If so then there is no
+ need to send it, since it already was (or will be) sent. */
+ if (handling_merged_revision
+ && log_entry->changed_paths2
+ && log_target_history_as_mergeinfo
+ && apr_hash_count(log_target_history_as_mergeinfo))
+ {
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* REV was merged in, but it might already be part of the log target's
+ natural history, so change our starting assumption. */
+ found_rev_of_interest = FALSE;
+
+ /* Look at each changed path in REV. */
+ for (hi = apr_hash_first(subpool, log_entry->changed_paths2);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_boolean_t path_is_in_history = FALSE;
+ const char *changed_path = svn__apr_hash_index_key(hi);
+ apr_hash_index_t *hi2;
+ apr_pool_t *inner_subpool = svn_pool_create(subpool);
+
+ /* Look at each path on the log target's mergeinfo. */
+ for (hi2 = apr_hash_first(inner_subpool,
+ log_target_history_as_mergeinfo);
+ hi2;
+ hi2 = apr_hash_next(hi2))
+ {
+ const char *mergeinfo_path =
+ svn__apr_hash_index_key(hi2);
+ svn_rangelist_t *rangelist =
+ svn__apr_hash_index_val(hi2);
+
+ /* Check whether CHANGED_PATH at revision REV is a child of
+ a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
+ if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path))
+ {
+ int i;
+
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_merge_range_t *range =
+ APR_ARRAY_IDX(rangelist, i,
+ svn_merge_range_t *);
+ if (rev > range->start && rev <= range->end)
+ {
+ path_is_in_history = TRUE;
+ break;
+ }
+ }
+ }
+ if (path_is_in_history)
+ break;
+ }
+ svn_pool_destroy(inner_subpool);
+
+ if (!path_is_in_history)
+ {
+ /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of
+ LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the
+ log for REV. */
+ found_rev_of_interest = TRUE;
+ break;
+ }
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ /* If we only got changed paths the sake of detecting redundant merged
+ revisions, then be sure we don't send that info to the receiver. */
+ if (!discover_changed_paths && handling_merged_revision)
+ log_entry->changed_paths = log_entry->changed_paths2 = NULL;
+
+ /* Send the entry to the receiver, unless it is a redundant merged
+ revision. */
+ if (found_rev_of_interest)
+ {
+ /* Is REV a merged revision we've already sent? */
+ if (nested_merges && handling_merged_revision)
+ {
+ svn_revnum_t *merged_rev = apr_hash_get(nested_merges, &rev,
+ sizeof(svn_revnum_t *));
+
+ if (merged_rev)
+ {
+ /* We already sent REV. */
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* NESTED_REVS needs to last across all the send_log, do_logs,
+ handle_merged_revisions() recursions, so use the pool it
+ was created in at the top of the recursion. */
+ apr_pool_t *hash_pool = apr_hash_pool_get(nested_merges);
+ svn_revnum_t *long_lived_rev = apr_palloc(hash_pool,
+ sizeof(svn_revnum_t));
+ *long_lived_rev = rev;
+ apr_hash_set(nested_merges, long_lived_rev,
+ sizeof(svn_revnum_t *), long_lived_rev);
+ }
+ }
+
+ return (*receiver)(receiver_baton, log_entry, pool);
+ }
+ else
+ {
+ return SVN_NO_ERROR;
+ }
+}
+
+/* This controls how many history objects we keep open. For any targets
+ over this number we have to open and close their histories as needed,
+ which is CPU intensive, but keeps us from using an unbounded amount of
+ memory. */
+#define MAX_OPEN_HISTORIES 32
+
+/* Get the histories for PATHS, and store them in *HISTORIES.
+
+ If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
+ repository locations as fatal -- just ignore them. */
+static svn_error_t *
+get_path_histories(apr_array_header_t **histories,
+ svn_fs_t *fs,
+ const apr_array_header_t *paths,
+ svn_revnum_t hist_start,
+ svn_revnum_t hist_end,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t ignore_missing_locations,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ apr_pool_t *iterpool;
+ svn_error_t *err;
+ int i;
+
+ /* Create a history object for each path so we can walk through
+ them all at the same time until we have all changes or LIMIT
+ is reached.
+
+ There is some pool fun going on due to the fact that we have
+ to hold on to the old pool with the history before we can
+ get the next history.
+ */
+ *histories = apr_array_make(pool, paths->nelts,
+ sizeof(struct path_info *));
+
+ SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool));
+
+ iterpool = svn_pool_create(pool);
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
+ struct path_info *info = apr_palloc(pool,
+ sizeof(struct path_info));
+
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(authz_read_func(&readable, root, this_path,
+ authz_read_baton, iterpool));
+ if (! readable)
+ return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
+ }
+
+ info->path = svn_stringbuf_create(this_path, pool);
+ info->done = FALSE;
+ info->history_rev = hist_end;
+ info->first_time = TRUE;
+
+ if (i < MAX_OPEN_HISTORIES)
+ {
+ err = svn_fs_node_history(&info->hist, root, this_path, pool);
+ if (err
+ && ignore_missing_locations
+ && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
+ err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
+ err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
+ {
+ svn_error_clear(err);
+ continue;
+ }
+ SVN_ERR(err);
+ info->newpool = svn_pool_create(pool);
+ info->oldpool = svn_pool_create(pool);
+ }
+ else
+ {
+ info->hist = NULL;
+ info->oldpool = NULL;
+ info->newpool = NULL;
+ }
+
+ err = get_history(info, fs,
+ strict_node_history,
+ authz_read_func, authz_read_baton,
+ hist_start, pool);
+ if (err
+ && ignore_missing_locations
+ && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
+ err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
+ err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
+ {
+ svn_error_clear(err);
+ continue;
+ }
+ SVN_ERR(err);
+ APR_ARRAY_PUSH(*histories, struct path_info *) = info;
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Remove and return the first item from ARR. */
+static void *
+array_pop_front(apr_array_header_t *arr)
+{
+ void *item = arr->elts;
+
+ if (apr_is_empty_array(arr))
+ return NULL;
+
+ arr->elts += arr->elt_size;
+ arr->nelts -= 1;
+ arr->nalloc -= 1;
+ return item;
+}
+
+/* A struct which represents a single revision range, and the paths which
+ have mergeinfo in that range. */
+struct path_list_range
+{
+ apr_array_header_t *paths;
+ svn_merge_range_t range;
+
+ /* Is RANGE the result of a reverse merge? */
+ svn_boolean_t reverse_merge;
+};
+
+/* A struct which represents "inverse mergeinfo", that is, instead of having
+ a path->revision_range_list mapping, which is the way mergeinfo is commonly
+ represented, this struct enables a revision_range_list,path tuple, where
+ the paths can be accessed by revision. */
+struct rangelist_path
+{
+ svn_rangelist_t *rangelist;
+ const char *path;
+};
+
+/* Comparator function for combine_mergeinfo_path_lists(). Sorts
+ rangelist_path structs in increasing order based upon starting revision,
+ then ending revision of the first element in the rangelist.
+
+ This does not sort rangelists based upon subsequent elements, only the
+ first range. We'll sort any subsequent ranges in the correct order
+ when they get bumped up to the front by removal of earlier ones, so we
+ don't really have to sort them here. See combine_mergeinfo_path_lists()
+ for details. */
+static int
+compare_rangelist_paths(const void *a, const void *b)
+{
+ struct rangelist_path *rpa = *((struct rangelist_path *const *) a);
+ struct rangelist_path *rpb = *((struct rangelist_path *const *) b);
+ svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0,
+ svn_merge_range_t *);
+ svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0,
+ svn_merge_range_t *);
+
+ if (mra->start < mrb->start)
+ return -1;
+ if (mra->start > mrb->start)
+ return 1;
+ if (mra->end < mrb->end)
+ return -1;
+ if (mra->end > mrb->end)
+ return 1;
+
+ return 0;
+}
+
+/* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of
+ 'struct path_list_range's. This list represents the rangelists in
+ MERGEINFO and each path which has mergeinfo in that range.
+ If REVERSE_MERGE is true, then MERGEINFO represents mergeinfo removed
+ as the result of a reverse merge. */
+static svn_error_t *
+combine_mergeinfo_path_lists(apr_array_header_t **combined_list,
+ svn_mergeinfo_t mergeinfo,
+ svn_boolean_t reverse_merge,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_array_header_t *rangelist_paths;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Create a list of (revision range, path) tuples from MERGEINFO. */
+ rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo),
+ sizeof(struct rangelist_path *));
+ for (hi = apr_hash_first(subpool, mergeinfo); hi;
+ hi = apr_hash_next(hi))
+ {
+ int i;
+ struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp));
+ apr_hash_this(hi, (void *) &rp->path, NULL,
+ (void *) &rp->rangelist);
+ APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp;
+
+ /* We need to make local copies of the rangelist, since we will be
+ modifying it, below. */
+ rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool);
+
+ /* Make all of the rangelists inclusive, both start and end. */
+ for (i = 0; i < rp->rangelist->nelts; i++)
+ APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1;
+ }
+
+ /* Loop over the (revision range, path) tuples, chopping them into
+ (revision range, paths) tuples, and appending those to the output
+ list. */
+ if (! *combined_list)
+ *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *));
+
+ while (rangelist_paths->nelts > 1)
+ {
+ svn_revnum_t youngest, next_youngest, tail, youngest_end;
+ struct path_list_range *plr;
+ struct rangelist_path *rp;
+ int num_revs;
+ int i;
+
+ /* First, sort the list such that the start revision of the first
+ revision arrays are sorted. */
+ qsort(rangelist_paths->elts, rangelist_paths->nelts,
+ rangelist_paths->elt_size, compare_rangelist_paths);
+
+ /* Next, find the number of revision ranges which start with the same
+ revision. */
+ rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
+ youngest =
+ APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start;
+ next_youngest = youngest;
+ for (num_revs = 1; next_youngest == youngest; num_revs++)
+ {
+ if (num_revs == rangelist_paths->nelts)
+ {
+ num_revs += 1;
+ break;
+ }
+ rp = APR_ARRAY_IDX(rangelist_paths, num_revs,
+ struct rangelist_path *);
+ next_youngest = APR_ARRAY_IDX(rp->rangelist, 0,
+ struct svn_merge_range_t *)->start;
+ }
+ num_revs -= 1;
+
+ /* The start of the new range will be YOUNGEST, and we now find the end
+ of the new range, which should be either one less than the next
+ earliest start of a rangelist, or the end of the first rangelist. */
+ youngest_end =
+ APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0,
+ struct rangelist_path *)->rangelist,
+ 0, svn_merge_range_t *)->end;
+ if ( (next_youngest == youngest) || (youngest_end < next_youngest) )
+ tail = youngest_end;
+ else
+ tail = next_youngest - 1;
+
+ /* Insert the (earliest, tail) tuple into the output list, along with
+ a list of paths which match it. */
+ plr = apr_palloc(pool, sizeof(*plr));
+ plr->reverse_merge = reverse_merge;
+ plr->range.start = youngest;
+ plr->range.end = tail;
+ plr->paths = apr_array_make(pool, num_revs, sizeof(const char *));
+ for (i = 0; i < num_revs; i++)
+ APR_ARRAY_PUSH(plr->paths, const char *) =
+ APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path;
+ APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
+
+ /* Now, check to see which (rangelist path) combinations we can remove,
+ and do so. */
+ for (i = 0; i < num_revs; i++)
+ {
+ svn_merge_range_t *range;
+ rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *);
+ range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *);
+
+ /* Set the start of the range to beyond the end of the range we
+ just built. If the range is now "inverted", we can get pop it
+ off the list. */
+ range->start = tail + 1;
+ if (range->start > range->end)
+ {
+ if (rp->rangelist->nelts == 1)
+ {
+ /* The range is the only on its list, so we should remove
+ the entire rangelist_path, adjusting our loop control
+ variables appropriately. */
+ array_pop_front(rangelist_paths);
+ i--;
+ num_revs--;
+ }
+ else
+ {
+ /* We have more than one range on the list, so just remove
+ the first one. */
+ array_pop_front(rp->rangelist);
+ }
+ }
+ }
+ }
+
+ /* Finally, add the last remaining (revision range, path) to the output
+ list. */
+ if (rangelist_paths->nelts > 0)
+ {
+ struct rangelist_path *first_rp =
+ APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
+ while (first_rp->rangelist->nelts > 0)
+ {
+ struct path_list_range *plr = apr_palloc(pool, sizeof(*plr));
+
+ plr->reverse_merge = reverse_merge;
+ plr->paths = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path;
+ plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0,
+ svn_merge_range_t *);
+ array_pop_front(first_rp->rangelist);
+ APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Pity that C is so ... linear. */
+static svn_error_t *
+do_logs(svn_fs_t *fs,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_t log_target_history_as_mergeinfo,
+ svn_mergeinfo_t processed,
+ apr_hash_t *nested_merges,
+ svn_revnum_t hist_start,
+ svn_revnum_t hist_end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ svn_boolean_t handling_merged_revisions,
+ svn_boolean_t subtractive_merge,
+ svn_boolean_t ignore_missing_locations,
+ const apr_array_header_t *revprops,
+ svn_boolean_t descending_order,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool);
+
+/* Comparator function for handle_merged_revisions(). Sorts path_list_range
+ structs in increasing order based on the struct's RANGE.START revision,
+ then RANGE.END revision. */
+static int
+compare_path_list_range(const void *a, const void *b)
+{
+ struct path_list_range *plr_a = *((struct path_list_range *const *) a);
+ struct path_list_range *plr_b = *((struct path_list_range *const *) b);
+
+ if (plr_a->range.start < plr_b->range.start)
+ return -1;
+ if (plr_a->range.start > plr_b->range.start)
+ return 1;
+ if (plr_a->range.end < plr_b->range.end)
+ return -1;
+ if (plr_a->range.end > plr_b->range.end)
+ return 1;
+
+ return 0;
+}
+
+/* Examine the ADDED_MERGEINFO and DELETED_MERGEINFO for revision REV in FS
+ (as collected by examining paths of interest to a log operation), and
+ determine which revisions to report as having been merged or reverse-merged
+ via the commit resulting in REV.
+
+ Silently ignore some failures to find the revisions mentioned in the
+ added/deleted mergeinfos, as might happen if there is invalid mergeinfo.
+
+ Other parameters are as described by do_logs(), around which this
+ is a recursion wrapper. */
+static svn_error_t *
+handle_merged_revisions(svn_revnum_t rev,
+ svn_fs_t *fs,
+ svn_mergeinfo_t log_target_history_as_mergeinfo,
+ apr_hash_t *nested_merges,
+ svn_mergeinfo_t processed,
+ svn_mergeinfo_t added_mergeinfo,
+ svn_mergeinfo_t deleted_mergeinfo,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *combined_list = NULL;
+ svn_log_entry_t *empty_log_entry;
+ apr_pool_t *iterpool;
+ int i;
+
+ if (apr_hash_count(added_mergeinfo) == 0
+ && apr_hash_count(deleted_mergeinfo) == 0)
+ return SVN_NO_ERROR;
+
+ if (apr_hash_count(added_mergeinfo))
+ SVN_ERR(combine_mergeinfo_path_lists(&combined_list, added_mergeinfo,
+ FALSE, pool));
+
+ if (apr_hash_count(deleted_mergeinfo))
+ SVN_ERR(combine_mergeinfo_path_lists(&combined_list, deleted_mergeinfo,
+ TRUE, pool));
+
+ SVN_ERR_ASSERT(combined_list != NULL);
+ qsort(combined_list->elts, combined_list->nelts,
+ combined_list->elt_size, compare_path_list_range);
+
+ /* Because the combined_lists are ordered youngest to oldest,
+ iterate over them in reverse. */
+ iterpool = svn_pool_create(pool);
+ for (i = combined_list->nelts - 1; i >= 0; i--)
+ {
+ struct path_list_range *pl_range
+ = APR_ARRAY_IDX(combined_list, i, struct path_list_range *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo,
+ processed, nested_merges,
+ pl_range->range.start, pl_range->range.end, 0,
+ discover_changed_paths, strict_node_history,
+ TRUE, pl_range->reverse_merge, TRUE, TRUE,
+ revprops, TRUE, receiver, receiver_baton,
+ authz_read_func, authz_read_baton, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Send the empty revision. */
+ empty_log_entry = svn_log_entry_create(pool);
+ empty_log_entry->revision = SVN_INVALID_REVNUM;
+ return (*receiver)(receiver_baton, empty_log_entry, pool);
+}
+
+/* This is used by do_logs to differentiate between forward and
+ reverse merges. */
+struct added_deleted_mergeinfo
+{
+ svn_mergeinfo_t added_mergeinfo;
+ svn_mergeinfo_t deleted_mergeinfo;
+};
+
+/* Reduce the search range PATHS, HIST_START, HIST_END by removing
+ parts already covered by PROCESSED. If reduction is possible
+ elements may be removed from PATHS and *START_REDUCED and
+ *END_REDUCED may be set to a narrower range. */
+static svn_error_t *
+reduce_search(apr_array_header_t *paths,
+ svn_revnum_t *hist_start,
+ svn_revnum_t *hist_end,
+ svn_mergeinfo_t processed,
+ apr_pool_t *scratch_pool)
+{
+ /* We add 1 to end to compensate for store_search */
+ svn_revnum_t start = *hist_start <= *hist_end ? *hist_start : *hist_end;
+ svn_revnum_t end = *hist_start <= *hist_end ? *hist_end + 1 : *hist_start + 1;
+ int i;
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ svn_rangelist_t *ranges = svn_hash_gets(processed, path);
+ int j;
+
+ if (!ranges)
+ continue;
+
+ /* ranges is ordered, could we use some sort of binary search
+ rather than iterating? */
+ for (j = 0; j < ranges->nelts; ++j)
+ {
+ svn_merge_range_t *range = APR_ARRAY_IDX(ranges, j,
+ svn_merge_range_t *);
+ if (range->start <= start && range->end >= end)
+ {
+ for (j = i; j < paths->nelts - 1; ++j)
+ APR_ARRAY_IDX(paths, j, const char *)
+ = APR_ARRAY_IDX(paths, j + 1, const char *);
+
+ --paths->nelts;
+ --i;
+ break;
+ }
+
+ /* If there is only one path then we also check for a
+ partial overlap rather than the full overlap above, and
+ reduce the [hist_start, hist_end] range rather than
+ dropping the path. */
+ if (paths->nelts == 1)
+ {
+ if (range->start <= start && range->end > start)
+ {
+ if (start == *hist_start)
+ *hist_start = range->end - 1;
+ else
+ *hist_end = range->end - 1;
+ break;
+ }
+ if (range->start < end && range->end >= end)
+ {
+ if (start == *hist_start)
+ *hist_end = range->start;
+ else
+ *hist_start = range->start;
+ break;
+ }
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Extend PROCESSED to cover PATHS from HIST_START to HIST_END */
+static svn_error_t *
+store_search(svn_mergeinfo_t processed,
+ const apr_array_header_t *paths,
+ svn_revnum_t hist_start,
+ svn_revnum_t hist_end,
+ apr_pool_t *scratch_pool)
+{
+ /* We add 1 to end so that we can use the mergeinfo API to handle
+ singe revisions where HIST_START is equal to HIST_END. */
+ svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end;
+ svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1;
+ svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool);
+ apr_pool_t *processed_pool = apr_hash_pool_get(processed);
+ int i;
+
+ for (i = 0; i < paths->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ svn_rangelist_t *ranges = apr_array_make(processed_pool, 1,
+ sizeof(svn_merge_range_t*));
+ svn_merge_range_t *range = apr_palloc(processed_pool,
+ sizeof(svn_merge_range_t));
+
+ range->start = start;
+ range->end = end;
+ range->inheritable = TRUE;
+ APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range;
+ svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges);
+ }
+ SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo,
+ apr_hash_pool_get(processed), scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke
+ RECEIVER with RECEIVER_BATON on them. If DESCENDING_ORDER is TRUE, send
+ the logs back as we find them, else buffer the logs and send them back
+ in youngest->oldest order.
+
+ If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
+ repository locations as fatal -- just ignore them.
+
+ If LOG_TARGET_HISTORY_AS_MERGEINFO is not NULL then it contains mergeinfo
+ representing the history of PATHS between HIST_START and HIST_END.
+
+ If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for
+ merged revisions, see INCLUDE_MERGED_REVISIONS argument to
+ svn_repos_get_logs4(). If SUBTRACTIVE_MERGE is true, then this is a
+ recursive call for reverse merged revisions.
+
+ If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t *
+ mapped to svn_revnum_t *) for logs that were previously sent. On the first
+ call to do_logs it should always be NULL. If INCLUDE_MERGED_REVISIONS is
+ TRUE, then NESTED_MERGES will be created on the first call to do_logs,
+ allocated in POOL. It is then shared across
+ do_logs()/send_logs()/handle_merge_revisions() recursions, see also the
+ argument of the same name in send_logs().
+
+ PROCESSED is a mergeinfo hash that represents the paths and
+ revisions that have already been searched. Allocated like
+ NESTED_MERGES above.
+
+ All other parameters are the same as svn_repos_get_logs4().
+ */
+static svn_error_t *
+do_logs(svn_fs_t *fs,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_t log_target_history_as_mergeinfo,
+ svn_mergeinfo_t processed,
+ apr_hash_t *nested_merges,
+ svn_revnum_t hist_start,
+ svn_revnum_t hist_end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ svn_boolean_t subtractive_merge,
+ svn_boolean_t handling_merged_revisions,
+ svn_boolean_t ignore_missing_locations,
+ const apr_array_header_t *revprops,
+ svn_boolean_t descending_order,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool;
+ apr_pool_t *subpool = NULL;
+ apr_array_header_t *revs = NULL;
+ apr_hash_t *rev_mergeinfo = NULL;
+ svn_revnum_t current;
+ apr_array_header_t *histories;
+ svn_boolean_t any_histories_left = TRUE;
+ int send_count = 0;
+ int i;
+
+ if (processed)
+ {
+ /* Casting away const. This only happens on recursive calls when
+ it is known to be safe because we allocated paths. */
+ SVN_ERR(reduce_search((apr_array_header_t *)paths, &hist_start, &hist_end,
+ processed, pool));
+ }
+
+ if (!paths->nelts)
+ return SVN_NO_ERROR;
+
+ if (processed)
+ SVN_ERR(store_search(processed, paths, hist_start, hist_end, pool));
+
+ /* We have a list of paths and a revision range. But we don't care
+ about all the revisions in the range -- only the ones in which
+ one of our paths was changed. So let's go figure out which
+ revisions contain real changes to at least one of our paths. */
+ SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
+ strict_node_history, ignore_missing_locations,
+ authz_read_func, authz_read_baton, pool));
+
+ /* Loop through all the revisions in the range and add any
+ where a path was changed to the array, or if they wanted
+ history in reverse order just send it to them right away. */
+ iterpool = svn_pool_create(pool);
+ for (current = hist_end;
+ any_histories_left;
+ current = next_history_rev(histories))
+ {
+ svn_boolean_t changed = FALSE;
+ any_histories_left = FALSE;
+ svn_pool_clear(iterpool);
+
+ for (i = 0; i < histories->nelts; i++)
+ {
+ struct path_info *info = APR_ARRAY_IDX(histories, i,
+ struct path_info *);
+
+ /* Check history for this path in current rev. */
+ SVN_ERR(check_history(&changed, info, fs, current,
+ strict_node_history, authz_read_func,
+ authz_read_baton, hist_start, pool));
+ if (! info->done)
+ any_histories_left = TRUE;
+ }
+
+ /* If any of the paths changed in this rev then add or send it. */
+ if (changed)
+ {
+ svn_mergeinfo_t added_mergeinfo = NULL;
+ svn_mergeinfo_t deleted_mergeinfo = NULL;
+ svn_boolean_t has_children = FALSE;
+ apr_hash_t *changes = NULL;
+
+ /* If we're including merged revisions, we need to calculate
+ the mergeinfo deltas committed in this revision to our
+ various paths. */
+ if (include_merged_revisions)
+ {
+ apr_array_header_t *cur_paths =
+ apr_array_make(iterpool, paths->nelts, sizeof(const char *));
+
+ /* Get the current paths of our history objects so we can
+ query mergeinfo. */
+ /* ### TODO: Should this be ignoring depleted history items? */
+ for (i = 0; i < histories->nelts; i++)
+ {
+ struct path_info *info = APR_ARRAY_IDX(histories, i,
+ struct path_info *);
+ APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data;
+ }
+ SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
+ &deleted_mergeinfo,
+ &changes,
+ fs, cur_paths,
+ current, iterpool,
+ iterpool));
+ has_children = (apr_hash_count(added_mergeinfo) > 0
+ || apr_hash_count(deleted_mergeinfo) > 0);
+ }
+
+ /* If our caller wants logs in descending order, we can send
+ 'em now (because that's the order we're crawling history
+ in anyway). */
+ if (descending_order)
+ {
+ SVN_ERR(send_log(current, fs, changes,
+ log_target_history_as_mergeinfo, nested_merges,
+ discover_changed_paths,
+ subtractive_merge, handling_merged_revisions,
+ revprops, has_children,
+ receiver, receiver_baton,
+ authz_read_func, authz_read_baton, iterpool));
+
+ if (has_children) /* Implies include_merged_revisions == TRUE */
+ {
+ if (!nested_merges)
+ {
+ /* We're at the start of the recursion stack, create a
+ single hash to be shared across all of the merged
+ recursions so we can track and squelch duplicates. */
+ subpool = svn_pool_create(pool);
+ nested_merges = svn_hash__make(subpool);
+ processed = svn_hash__make(subpool);
+ }
+
+ SVN_ERR(handle_merged_revisions(
+ current, fs,
+ log_target_history_as_mergeinfo, nested_merges,
+ processed,
+ added_mergeinfo, deleted_mergeinfo,
+ discover_changed_paths,
+ strict_node_history,
+ revprops,
+ receiver, receiver_baton,
+ authz_read_func,
+ authz_read_baton,
+ iterpool));
+ }
+ if (limit && ++send_count >= limit)
+ break;
+ }
+ /* Otherwise, the caller wanted logs in ascending order, so
+ we have to buffer up a list of revs and (if doing
+ mergeinfo) a hash of related mergeinfo deltas, and
+ process them later. */
+ else
+ {
+ if (! revs)
+ revs = apr_array_make(pool, 64, sizeof(svn_revnum_t));
+ APR_ARRAY_PUSH(revs, svn_revnum_t) = current;
+
+ if (added_mergeinfo || deleted_mergeinfo)
+ {
+ svn_revnum_t *cur_rev = apr_pcalloc(pool, sizeof(*cur_rev));
+ struct added_deleted_mergeinfo *add_and_del_mergeinfo =
+ apr_palloc(pool, sizeof(*add_and_del_mergeinfo));
+
+ if (added_mergeinfo)
+ add_and_del_mergeinfo->added_mergeinfo =
+ svn_mergeinfo_dup(added_mergeinfo, pool);
+
+ if (deleted_mergeinfo)
+ add_and_del_mergeinfo->deleted_mergeinfo =
+ svn_mergeinfo_dup(deleted_mergeinfo, pool);
+
+ *cur_rev = current;
+ if (! rev_mergeinfo)
+ rev_mergeinfo = svn_hash__make(pool);
+ apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev),
+ add_and_del_mergeinfo);
+ }
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ if (subpool)
+ {
+ nested_merges = NULL;
+ svn_pool_destroy(subpool);
+ }
+
+ if (revs)
+ {
+ /* Work loop for processing the revisions we found since they wanted
+ history in forward order. */
+ iterpool = svn_pool_create(pool);
+ for (i = 0; i < revs->nelts; ++i)
+ {
+ svn_mergeinfo_t added_mergeinfo;
+ svn_mergeinfo_t deleted_mergeinfo;
+ svn_boolean_t has_children = FALSE;
+
+ svn_pool_clear(iterpool);
+ current = APR_ARRAY_IDX(revs, revs->nelts - i - 1, svn_revnum_t);
+
+ /* If we've got a hash of revision mergeinfo (which can only
+ happen if INCLUDE_MERGED_REVISIONS was set), we check to
+ see if this revision is one which merged in other
+ revisions we need to handle recursively. */
+ if (rev_mergeinfo)
+ {
+ struct added_deleted_mergeinfo *add_and_del_mergeinfo =
+ apr_hash_get(rev_mergeinfo, &current, sizeof(svn_revnum_t));
+ added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo;
+ deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo;
+ has_children = (apr_hash_count(added_mergeinfo) > 0
+ || apr_hash_count(deleted_mergeinfo) > 0);
+ }
+
+ SVN_ERR(send_log(current, fs, NULL,
+ log_target_history_as_mergeinfo, nested_merges,
+ discover_changed_paths, subtractive_merge,
+ handling_merged_revisions, revprops, has_children,
+ receiver, receiver_baton, authz_read_func,
+ authz_read_baton, iterpool));
+ if (has_children)
+ {
+ if (!nested_merges)
+ {
+ subpool = svn_pool_create(pool);
+ nested_merges = svn_hash__make(subpool);
+ }
+
+ SVN_ERR(handle_merged_revisions(current, fs,
+ log_target_history_as_mergeinfo,
+ nested_merges,
+ processed,
+ added_mergeinfo,
+ deleted_mergeinfo,
+ discover_changed_paths,
+ strict_node_history, revprops,
+ receiver, receiver_baton,
+ authz_read_func,
+ authz_read_baton,
+ iterpool));
+ }
+ if (limit && i + 1 >= limit)
+ break;
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct location_segment_baton
+{
+ apr_array_header_t *history_segments;
+ apr_pool_t *pool;
+};
+
+/* svn_location_segment_receiver_t implementation for svn_repos_get_logs4. */
+static svn_error_t *
+location_segment_receiver(svn_location_segment_t *segment,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct location_segment_baton *b = baton;
+
+ APR_ARRAY_PUSH(b->history_segments, svn_location_segment_t *) =
+ svn_location_segment_dup(segment, b->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Populate *PATHS_HISTORY_MERGEINFO with mergeinfo representing the combined
+ history of each path in PATHS between START_REV and END_REV in REPOS's
+ filesystem. START_REV and END_REV must be valid revisions. RESULT_POOL
+ is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all
+ other (temporary) allocations. Other parameters are the same as
+ svn_repos_get_logs4(). */
+static svn_error_t *
+get_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
+ svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_mergeinfo_t path_history_mergeinfo;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_rev));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_rev));
+
+ /* Ensure START_REV is the youngest revision, as required by
+ svn_repos_node_location_segments, for which this is an iterative
+ wrapper. */
+ if (start_rev < end_rev)
+ {
+ svn_revnum_t tmp_rev = start_rev;
+ start_rev = end_rev;
+ end_rev = tmp_rev;
+ }
+
+ *paths_history_mergeinfo = svn_hash__make(result_pool);
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
+ struct location_segment_baton loc_seg_baton;
+
+ svn_pool_clear(iterpool);
+ loc_seg_baton.pool = scratch_pool;
+ loc_seg_baton.history_segments =
+ apr_array_make(iterpool, 4, sizeof(svn_location_segment_t *));
+
+ SVN_ERR(svn_repos_node_location_segments(repos, this_path, start_rev,
+ start_rev, end_rev,
+ location_segment_receiver,
+ &loc_seg_baton,
+ authz_read_func,
+ authz_read_baton,
+ iterpool));
+
+ SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(
+ &path_history_mergeinfo, loc_seg_baton.history_segments, iterpool));
+ SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo,
+ svn_mergeinfo_dup(path_history_mergeinfo,
+ result_pool),
+ result_pool, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_get_logs4(svn_repos_t *repos,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ svn_revnum_t head = SVN_INVALID_REVNUM;
+ svn_fs_t *fs = repos->fs;
+ svn_boolean_t descending_order;
+ svn_mergeinfo_t paths_history_mergeinfo = NULL;
+
+ /* Setup log range. */
+ SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
+
+ if (! SVN_IS_VALID_REVNUM(start))
+ start = head;
+
+ if (! SVN_IS_VALID_REVNUM(end))
+ end = head;
+
+ /* Check that revisions are sane before ever invoking receiver. */
+ if (start > head)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ _("No such revision %ld"), start);
+ if (end > head)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ _("No such revision %ld"), end);
+
+ /* Ensure a youngest-to-oldest revision crawl ordering using our
+ (possibly sanitized) range values. */
+ descending_order = start >= end;
+ if (descending_order)
+ {
+ svn_revnum_t tmp_rev = start;
+ start = end;
+ end = tmp_rev;
+ }
+
+ if (! paths)
+ paths = apr_array_make(pool, 0, sizeof(const char *));
+
+ /* If we're not including merged revisions, and we were given no
+ paths or a single empty (or "/") path, then we can bypass a bunch
+ of complexity because we already know in which revisions the root
+ directory was changed -- all of them. */
+ if ((! include_merged_revisions)
+ && ((! paths->nelts)
+ || ((paths->nelts == 1)
+ && (svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *))
+ || (strcmp(APR_ARRAY_IDX(paths, 0, const char *),
+ "/") == 0)))))
+ {
+ apr_uint64_t send_count = 0;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* If we are provided an authz callback function, use it to
+ verify that the user has read access to the root path in the
+ first of our revisions.
+
+ ### FIXME: Strictly speaking, we should be checking this
+ ### access in every revision along the line. But currently,
+ ### there are no known authz implementations which concern
+ ### themselves with per-revision access. */
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+ svn_fs_root_t *rev_root;
+
+ SVN_ERR(svn_fs_revision_root(&rev_root, fs,
+ descending_order ? end : start, pool));
+ SVN_ERR(authz_read_func(&readable, rev_root, "",
+ authz_read_baton, pool));
+ if (! readable)
+ return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
+ }
+
+ send_count = end - start + 1;
+ if (limit && send_count > limit)
+ send_count = limit;
+ for (i = 0; i < send_count; ++i)
+ {
+ svn_revnum_t rev;
+
+ svn_pool_clear(iterpool);
+
+ if (descending_order)
+ rev = end - i;
+ else
+ rev = start + i;
+ SVN_ERR(send_log(rev, fs, NULL, NULL, NULL,
+ discover_changed_paths, FALSE,
+ FALSE, revprops, FALSE, receiver,
+ receiver_baton, authz_read_func,
+ authz_read_baton, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* If we are including merged revisions, then create mergeinfo that
+ represents all of PATHS' history between START and END. We will use
+ this later to squelch duplicate log revisions that might exist in
+ both natural history and merged-in history. See
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */
+ if (include_merged_revisions)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo,
+ repos, paths, start, end,
+ authz_read_func,
+ authz_read_baton,
+ pool, subpool));
+ svn_pool_destroy(subpool);
+ }
+
+ return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end,
+ limit, discover_changed_paths, strict_node_history,
+ include_merged_revisions, FALSE, FALSE, FALSE, revprops,
+ descending_order, receiver, receiver_baton,
+ authz_read_func, authz_read_baton, pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/node_tree.c b/contrib/subversion/subversion/libsvn_repos/node_tree.c
new file mode 100644
index 0000000..bd947b0
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/node_tree.c
@@ -0,0 +1,431 @@
+/*
+ * node_tree.c: an editor for tracking repository deltas changes
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+
+#include <stdio.h>
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_delta.h"
+#include "svn_fs.h"
+#include "svn_repos.h"
+#include "repos.h"
+#include "svn_private_config.h"
+#include "private/svn_fspath.h"
+
+/*** NOTE: This editor is unique in that it currently is hard-coded to
+ be anchored at the root directory of the filesystem. This
+ affords us the ability to use the same paths for filesystem
+ locations and editor paths. ***/
+
+
+
+/*** Node creation and assembly structures and routines. ***/
+static svn_repos_node_t *
+create_node(const char *name,
+ svn_repos_node_t *parent,
+ apr_pool_t *pool)
+{
+ svn_repos_node_t *node = apr_pcalloc(pool, sizeof(*node));
+ node->action = 'R';
+ node->kind = svn_node_unknown;
+ node->name = apr_pstrdup(pool, name);
+ node->parent = parent;
+ return node;
+}
+
+
+static svn_repos_node_t *
+create_sibling_node(svn_repos_node_t *elder,
+ const char *name,
+ apr_pool_t *pool)
+{
+ svn_repos_node_t *tmp_node;
+
+ /* No ELDER sibling? That's just not gonna work out. */
+ if (! elder)
+ return NULL;
+
+ /* Run to the end of the list of siblings of ELDER. */
+ tmp_node = elder;
+ while (tmp_node->sibling)
+ tmp_node = tmp_node->sibling;
+
+ /* Create a new youngest sibling and return that. */
+ return (tmp_node->sibling = create_node(name, elder->parent, pool));
+}
+
+
+static svn_repos_node_t *
+create_child_node(svn_repos_node_t *parent,
+ const char *name,
+ apr_pool_t *pool)
+{
+ /* No PARENT node? That's just not gonna work out. */
+ if (! parent)
+ return NULL;
+
+ /* If PARENT has no children, create its first one and return that. */
+ if (! parent->child)
+ return (parent->child = create_node(name, parent, pool));
+
+ /* If PARENT already has a child, create a new sibling for its first
+ child and return that. */
+ return create_sibling_node(parent->child, name, pool);
+}
+
+
+static svn_repos_node_t *
+find_child_by_name(svn_repos_node_t *parent,
+ const char *name)
+{
+ svn_repos_node_t *tmp_node;
+
+ /* No PARENT node, or a barren PARENT? Nothing to find. */
+ if ((! parent) || (! parent->child))
+ return NULL;
+
+ /* Look through the children for a node with a matching name. */
+ tmp_node = parent->child;
+ while (1)
+ {
+ if (! strcmp(tmp_node->name, name))
+ {
+ return tmp_node;
+ }
+ else
+ {
+ if (tmp_node->sibling)
+ tmp_node = tmp_node->sibling;
+ else
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+
+static void
+find_real_base_location(const char **path_p,
+ svn_revnum_t *rev_p,
+ svn_repos_node_t *node,
+ apr_pool_t *pool)
+{
+ /* If NODE is an add-with-history, then its real base location is
+ the copy source. */
+ if ((node->action == 'A')
+ && node->copyfrom_path
+ && SVN_IS_VALID_REVNUM(node->copyfrom_rev))
+ {
+ *path_p = node->copyfrom_path;
+ *rev_p = node->copyfrom_rev;
+ return;
+ }
+
+ /* Otherwise, if NODE has a parent, we'll recurse, and add NODE's
+ name to whatever the parent's real base path turns out to be (and
+ pass the base revision on through). */
+ if (node->parent)
+ {
+ const char *path;
+ svn_revnum_t rev;
+
+ find_real_base_location(&path, &rev, node->parent, pool);
+ *path_p = svn_fspath__join(path, node->name, pool);
+ *rev_p = rev;
+ return;
+ }
+
+ /* Finally, if the node has no parent, then its name is "/", and it
+ has no interesting base revision. */
+ *path_p = "/";
+ *rev_p = SVN_INVALID_REVNUM;
+ return;
+}
+
+
+
+
+/*** Editor functions and batons. ***/
+
+struct edit_baton
+{
+ svn_fs_t *fs;
+ svn_fs_root_t *root;
+ svn_fs_root_t *base_root;
+ apr_pool_t *node_pool;
+ svn_repos_node_t *node;
+};
+
+
+struct node_baton
+{
+ struct edit_baton *edit_baton;
+ struct node_baton *parent_baton;
+ svn_repos_node_t *node;
+};
+
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct node_baton *d = parent_baton;
+ struct edit_baton *eb = d->edit_baton;
+ svn_repos_node_t *node;
+ const char *name;
+ const char *base_path;
+ svn_revnum_t base_rev;
+ svn_fs_root_t *base_root;
+ svn_node_kind_t kind;
+
+ /* Get (or create) the change node and update it. */
+ name = svn_relpath_basename(path, pool);
+ node = find_child_by_name(d->node, name);
+ if (! node)
+ node = create_child_node(d->node, name, eb->node_pool);
+ node->action = 'D';
+
+ /* We need to look up this node's parents to see what its original
+ path in the filesystem was. Why? Because if this deletion
+ occurred underneath a copied path, the thing that was deleted
+ probably lived at a different location (relative to the copy
+ source). */
+ find_real_base_location(&base_path, &base_rev, node, pool);
+ if (! SVN_IS_VALID_REVNUM(base_rev))
+ {
+ /* No interesting base revision? We'll just look for the path
+ in our base root. */
+ base_root = eb->base_root;
+ }
+ else
+ {
+ /* Oh. Perhaps some copy goodness happened somewhere? */
+ SVN_ERR(svn_fs_revision_root(&base_root, eb->fs, base_rev, pool));
+ }
+
+ /* Now figure out if this thing was a file or a dir. */
+ SVN_ERR(svn_fs_check_path(&kind, base_root, base_path, pool));
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("'%s' not found in filesystem"), path);
+ node->kind = kind;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+static svn_error_t *
+add_open_helper(const char *path,
+ char action,
+ svn_node_kind_t kind,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct node_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
+
+ SVN_ERR_ASSERT(parent_baton && path);
+
+ nb->edit_baton = eb;
+ nb->parent_baton = pb;
+
+ /* Create and populate the node. */
+ nb->node = create_child_node(pb->node, svn_relpath_basename(path, NULL),
+ eb->node_pool);
+ nb->node->kind = kind;
+ nb->node->action = action;
+ nb->node->copyfrom_rev = copyfrom_rev;
+ nb->node->copyfrom_path =
+ copyfrom_path ? apr_pstrdup(eb->node_pool, copyfrom_path) : NULL;
+
+ *child_baton = nb;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ struct node_baton *d = apr_pcalloc(pool, sizeof(*d));
+
+ d->edit_baton = eb;
+ d->parent_baton = NULL;
+ d->node = (eb->node = create_node("", NULL, eb->node_pool));
+ d->node->kind = svn_node_dir;
+ d->node->action = 'R';
+ *root_baton = d;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ return add_open_helper(path, 'R', svn_node_dir, parent_baton,
+ NULL, SVN_INVALID_REVNUM,
+ pool, child_baton);
+}
+
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ return add_open_helper(path, 'A', svn_node_dir, parent_baton,
+ copyfrom_path, copyfrom_revision,
+ pool, child_baton);
+}
+
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ return add_open_helper(path, 'R', svn_node_file, parent_baton,
+ NULL, SVN_INVALID_REVNUM,
+ pool, file_baton);
+}
+
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ return add_open_helper(path, 'A', svn_node_file, parent_baton,
+ copyfrom_path, copyfrom_revision,
+ pool, file_baton);
+}
+
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct node_baton *fb = file_baton;
+ fb->node->text_mod = TRUE;
+ *handler = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+
+
+static svn_error_t *
+change_node_prop(void *node_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct node_baton *nb = node_baton;
+ nb->node->prop_mod = TRUE;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_node_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_root_t *base_root,
+ svn_fs_root_t *root,
+ apr_pool_t *node_pool,
+ apr_pool_t *pool)
+{
+ svn_delta_editor_t *my_editor;
+ struct edit_baton *my_edit_baton;
+
+ /* Set up the editor. */
+ my_editor = svn_delta_default_editor(pool);
+ my_editor->open_root = open_root;
+ my_editor->delete_entry = delete_entry;
+ my_editor->add_directory = add_directory;
+ my_editor->open_directory = open_directory;
+ my_editor->add_file = add_file;
+ my_editor->open_file = open_file;
+ my_editor->apply_textdelta = apply_textdelta;
+ my_editor->change_file_prop = change_node_prop;
+ my_editor->change_dir_prop = change_node_prop;
+
+ /* Set up the edit baton. */
+ my_edit_baton = apr_pcalloc(pool, sizeof(*my_edit_baton));
+ my_edit_baton->node_pool = node_pool;
+ my_edit_baton->fs = repos->fs;
+ my_edit_baton->root = root;
+ my_edit_baton->base_root = base_root;
+
+ *editor = my_editor;
+ *edit_baton = my_edit_baton;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_repos_node_t *
+svn_repos_node_from_baton(void *edit_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ return eb->node;
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/notify.c b/contrib/subversion/subversion/libsvn_repos/notify.c
new file mode 100644
index 0000000..ac2bca4
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/notify.c
@@ -0,0 +1,44 @@
+/* notify.c --- notifcation system
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "svn_pools.h"
+#include "svn_repos.h"
+#include "repos.h"
+#include "svn_private_config.h"
+#include "private/svn_utf_private.h"
+
+
+
+svn_repos_notify_t *
+svn_repos_notify_create(svn_repos_notify_action_t action,
+ apr_pool_t *result_pool)
+{
+ svn_repos_notify_t *notify = apr_pcalloc(result_pool, sizeof(*notify));
+
+ notify->action = action;
+
+ return notify;
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/replay.c b/contrib/subversion/subversion/libsvn_repos/replay.c
new file mode 100644
index 0000000..985a673
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/replay.c
@@ -0,0 +1,1591 @@
+/*
+ * replay.c: an editor driver for changes made in a given revision
+ * or transaction
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_delta.h"
+#include "svn_hash.h"
+#include "svn_fs.h"
+#include "svn_checksum.h"
+#include "svn_repos.h"
+#include "svn_sorts.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+#include "private/svn_fspath.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_delta_private.h"
+
+
+/*** Backstory ***/
+
+/* The year was 2003. Subversion usage was rampant in the world, and
+ there was a rapidly growing issues database to prove it. To make
+ matters worse, svn_repos_dir_delta() had simply outgrown itself.
+ No longer content to simply describe the differences between two
+ trees, the function had been slowly bearing the added
+ responsibility of representing the actions that had been taken to
+ cause those differences -- a burden it was never meant to bear.
+ Now grown into a twisted mess of razor-sharp metal and glass, and
+ trembling with a sort of momentarily stayed spring force,
+ svn_repos_dir_delta was a timebomb poised for total annihilation of
+ the American Midwest.
+
+ Subversion needed a change.
+
+ Changes, in fact. And not just in the literary segue sense. What
+ Subversion desperately needed was a new mechanism solely
+ responsible for replaying repository actions back to some
+ interested party -- to translate and retransmit the contents of the
+ Berkeley 'changes' database file. */
+
+/*** Overview ***/
+
+/* The filesystem keeps a record of high-level actions that affect the
+ files and directories in itself. The 'changes' table records
+ additions, deletions, textual and property modifications, and so
+ on. The goal of the functions in this file is to examine those
+ change records, and use them to drive an editor interface in such a
+ way as to effectively replay those actions.
+
+ This is critically different than what svn_repos_dir_delta() was
+ designed to do. That function describes, in the simplest way it
+ can, how to transform one tree into another. It doesn't care
+ whether or not this was the same way a user might have done this
+ transformation. More to the point, it doesn't care if this is how
+ those differences *did* come into being. And it is for this reason
+ that it cannot be relied upon for tasks such as the repository
+ dumpfile-generation code, which is supposed to represent not
+ changes, but actions that cause changes.
+
+ So, what's the plan here?
+
+ First, we fetch the changes for a particular revision or
+ transaction. We get these as an array, sorted chronologically.
+ From this array we will build a hash, keyed on the path associated
+ with each change item, and whose values are arrays of changes made
+ to that path, again preserving the chronological ordering.
+
+ Once our hash is built, we then sort all the keys of the hash (the
+ paths) using a depth-first directory sort routine.
+
+ Finally, we drive an editor, moving down our list of sorted paths,
+ and manufacturing any intermediate editor calls (directory openings
+ and closures) needed to navigate between each successive path. For
+ each path, we replay the sorted actions that occurred at that path.
+
+ When we've finished the editor drive, we should have fully replayed
+ the filesystem events that occurred in that revision or transaction
+ (though not necessarily in the same order in which they
+ occurred). */
+
+/* #define USE_EV2_IMPL */
+
+
+/*** Helper functions. ***/
+
+
+/* Information for an active copy, that is a directory which we are currently
+ working on and which was added with history. */
+struct copy_info
+{
+ /* Destination relpath (relative to the root of the . */
+ const char *path;
+
+ /* Copy source path (expressed as an absolute FS path) or revision.
+ NULL and SVN_INVALID_REVNUM if this is an add without history,
+ nested inside an add with history. */
+ const char *copyfrom_path;
+ svn_revnum_t copyfrom_rev;
+};
+
+struct path_driver_cb_baton
+{
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+
+ /* The root of the revision we're replaying. */
+ svn_fs_root_t *root;
+
+ /* The root of the previous revision. If this is non-NULL it means that
+ we are supposed to generate props and text deltas relative to it. */
+ svn_fs_root_t *compare_root;
+
+ apr_hash_t *changed_paths;
+
+ svn_repos_authz_func_t authz_read_func;
+ void *authz_read_baton;
+
+ const char *base_path; /* relpath */
+
+ svn_revnum_t low_water_mark;
+ /* Stack of active copy operations. */
+ apr_array_header_t *copies;
+
+ /* The global pool for this replay operation. */
+ apr_pool_t *pool;
+};
+
+/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
+ the appropriate editor calls to add it and its children without any
+ history. This is meant to be used when either a subset of the tree
+ has been ignored and we need to copy something from that subset to
+ the part of the tree we do care about, or if a subset of the tree is
+ unavailable because of authz and we need to use it as the source of
+ a copy. */
+static svn_error_t *
+add_subdir(svn_fs_root_t *source_root,
+ svn_fs_root_t *target_root,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ const char *edit_path,
+ void *parent_baton,
+ const char *source_fspath,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_hash_t *changed_paths,
+ apr_pool_t *pool,
+ void **dir_baton)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_index_t *hi, *phi;
+ apr_hash_t *dirents;
+ apr_hash_t *props;
+
+ SVN_ERR(editor->add_directory(edit_path, parent_baton, NULL,
+ SVN_INVALID_REVNUM, pool, dir_baton));
+
+ SVN_ERR(svn_fs_node_proplist(&props, target_root, edit_path, pool));
+
+ for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
+ {
+ const void *key;
+ void *val;
+
+ svn_pool_clear(subpool);
+ apr_hash_this(phi, &key, NULL, &val);
+ SVN_ERR(editor->change_dir_prop(*dir_baton, key, val, subpool));
+ }
+
+ /* We have to get the dirents from the source path, not the target,
+ because we want nested copies from *readable* paths to be handled by
+ path_driver_cb_func, not add_subdir (in order to preserve history). */
+ SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath, pool));
+
+ for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_path_change2_t *change;
+ svn_boolean_t readable = TRUE;
+ svn_fs_dirent_t *dent;
+ const char *copyfrom_path = NULL;
+ svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+ const char *new_edit_path;
+ void *val;
+
+ svn_pool_clear(subpool);
+
+ apr_hash_this(hi, NULL, NULL, &val);
+
+ dent = val;
+
+ new_edit_path = svn_relpath_join(edit_path, dent->name, subpool);
+
+ /* If a file or subdirectory of the copied directory is listed as a
+ changed path (because it was modified after the copy but before the
+ commit), we remove it from the changed_paths hash so that future
+ calls to path_driver_cb_func will ignore it. */
+ change = svn_hash_gets(changed_paths, new_edit_path);
+ if (change)
+ {
+ svn_hash_sets(changed_paths, new_edit_path, NULL);
+
+ /* If it's a delete, skip this entry. */
+ if (change->change_kind == svn_fs_path_change_delete)
+ continue;
+
+ /* If it's a replacement, check for copyfrom info (if we
+ don't have it already. */
+ if (change->change_kind == svn_fs_path_change_replace)
+ {
+ if (! change->copyfrom_known)
+ {
+ SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
+ &change->copyfrom_path,
+ target_root, new_edit_path, pool));
+ change->copyfrom_known = TRUE;
+ }
+ copyfrom_path = change->copyfrom_path;
+ copyfrom_rev = change->copyfrom_rev;
+ }
+ }
+
+ if (authz_read_func)
+ SVN_ERR(authz_read_func(&readable, target_root, new_edit_path,
+ authz_read_baton, pool));
+
+ if (! readable)
+ continue;
+
+ if (dent->kind == svn_node_dir)
+ {
+ svn_fs_root_t *new_source_root;
+ const char *new_source_fspath;
+ void *new_dir_baton;
+
+ if (copyfrom_path)
+ {
+ svn_fs_t *fs = svn_fs_root_fs(source_root);
+ SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
+ copyfrom_rev, pool));
+ new_source_fspath = copyfrom_path;
+ }
+ else
+ {
+ new_source_root = source_root;
+ new_source_fspath = svn_fspath__join(source_fspath, dent->name,
+ subpool);
+ }
+
+ /* ### authz considerations?
+ *
+ * I think not; when path_driver_cb_func() calls add_subdir(), it
+ * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
+ */
+ if (change && change->change_kind == svn_fs_path_change_replace
+ && copyfrom_path == NULL)
+ {
+ SVN_ERR(editor->add_directory(new_edit_path, *dir_baton,
+ NULL, SVN_INVALID_REVNUM,
+ subpool, &new_dir_baton));
+ }
+ else
+ {
+ SVN_ERR(add_subdir(new_source_root, target_root,
+ editor, edit_baton, new_edit_path,
+ *dir_baton, new_source_fspath,
+ authz_read_func, authz_read_baton,
+ changed_paths, subpool, &new_dir_baton));
+ }
+
+ SVN_ERR(editor->close_directory(new_dir_baton, subpool));
+ }
+ else if (dent->kind == svn_node_file)
+ {
+ svn_txdelta_window_handler_t delta_handler;
+ void *delta_handler_baton, *file_baton;
+ svn_txdelta_stream_t *delta_stream;
+ svn_checksum_t *checksum;
+
+ SVN_ERR(editor->add_file(new_edit_path, *dir_baton, NULL,
+ SVN_INVALID_REVNUM, pool, &file_baton));
+
+ SVN_ERR(svn_fs_node_proplist(&props, target_root,
+ new_edit_path, subpool));
+
+ for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi))
+ {
+ const void *key;
+
+ apr_hash_this(phi, &key, NULL, &val);
+ SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool));
+ }
+
+ SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool,
+ &delta_handler,
+ &delta_handler_baton));
+
+ SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, NULL, NULL,
+ target_root, new_edit_path,
+ pool));
+
+ SVN_ERR(svn_txdelta_send_txstream(delta_stream,
+ delta_handler,
+ delta_handler_baton,
+ pool));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, target_root,
+ new_edit_path, TRUE, pool));
+ SVN_ERR(editor->close_file(file_baton,
+ svn_checksum_to_cstring(checksum, pool),
+ pool));
+ }
+ else
+ SVN_ERR_MALFUNCTION();
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Given PATH deleted under ROOT, return in READABLE whether the path was
+ readable prior to the deletion. Consult COPIES (a stack of 'struct
+ copy_info') and AUTHZ_READ_FUNC. */
+static svn_error_t *
+was_readable(svn_boolean_t *readable,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_array_header_t *copies,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_root_t *inquire_root;
+ const char *inquire_path;
+ struct copy_info *info = NULL;
+ const char *relpath;
+
+ /* Short circuit. */
+ if (! authz_read_func)
+ {
+ *readable = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ if (copies->nelts != 0)
+ info = APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info *);
+
+ /* Are we under a copy? */
+ if (info && (relpath = svn_relpath_skip_ancestor(info->path, path)))
+ {
+ SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root),
+ info->copyfrom_rev, scratch_pool));
+ inquire_path = svn_fspath__join(info->copyfrom_path, relpath,
+ scratch_pool);
+ }
+ else
+ {
+ /* Compute the revision that ROOT is based on. (Note that ROOT is not
+ r0's root, since this function is only called for deletions.)
+ ### Need a more succinct way to express this */
+ svn_revnum_t inquire_rev = SVN_INVALID_REVNUM;
+ if (svn_fs_is_txn_root(root))
+ inquire_rev = svn_fs_txn_root_base_revision(root);
+ if (svn_fs_is_revision_root(root))
+ inquire_rev = svn_fs_revision_root_revision(root)-1;
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(inquire_rev));
+
+ SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root),
+ inquire_rev, scratch_pool));
+ inquire_path = path;
+ }
+
+ SVN_ERR(authz_read_func(readable, inquire_root, inquire_path,
+ authz_read_baton, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Initialize COPYFROM_ROOT, COPYFROM_PATH, and COPYFROM_REV with the
+ revision root, fspath, and revnum of the copyfrom of CHANGE, which
+ corresponds to PATH under ROOT. If the copyfrom info is valid
+ (i.e., is not (NULL, SVN_INVALID_REVNUM)), then initialize SRC_READABLE
+ too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided.
+
+ NOTE: If the copyfrom information in CHANGE is marked as unknown
+ (meaning, its ->copyfrom_rev and ->copyfrom_path cannot be
+ trusted), this function will also update those members of the
+ CHANGE structure to carry accurate copyfrom information. */
+static svn_error_t *
+fill_copyfrom(svn_fs_root_t **copyfrom_root,
+ const char **copyfrom_path,
+ svn_revnum_t *copyfrom_rev,
+ svn_boolean_t *src_readable,
+ svn_fs_root_t *root,
+ svn_fs_path_change2_t *change,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (! change->copyfrom_known)
+ {
+ SVN_ERR(svn_fs_copied_from(&(change->copyfrom_rev),
+ &(change->copyfrom_path),
+ root, path, result_pool));
+ change->copyfrom_known = TRUE;
+ }
+ *copyfrom_rev = change->copyfrom_rev;
+ *copyfrom_path = change->copyfrom_path;
+
+ if (*copyfrom_path && SVN_IS_VALID_REVNUM(*copyfrom_rev))
+ {
+ SVN_ERR(svn_fs_revision_root(copyfrom_root,
+ svn_fs_root_fs(root),
+ *copyfrom_rev, result_pool));
+
+ if (authz_read_func)
+ {
+ SVN_ERR(authz_read_func(src_readable, *copyfrom_root,
+ *copyfrom_path,
+ authz_read_baton, result_pool));
+ }
+ else
+ *src_readable = TRUE;
+ }
+ else
+ {
+ *copyfrom_root = NULL;
+ /* SRC_READABLE left uninitialized */
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+path_driver_cb_func(void **dir_baton,
+ void *parent_baton,
+ void *callback_baton,
+ const char *edit_path,
+ apr_pool_t *pool)
+{
+ struct path_driver_cb_baton *cb = callback_baton;
+ const svn_delta_editor_t *editor = cb->editor;
+ void *edit_baton = cb->edit_baton;
+ svn_fs_root_t *root = cb->root;
+ svn_fs_path_change2_t *change;
+ svn_boolean_t do_add = FALSE, do_delete = FALSE;
+ void *file_baton = NULL;
+ svn_revnum_t copyfrom_rev;
+ const char *copyfrom_path;
+ svn_fs_root_t *source_root = cb->compare_root;
+ const char *source_fspath = NULL;
+ const char *base_path = cb->base_path;
+
+ *dir_baton = NULL;
+
+ /* Initialize SOURCE_FSPATH. */
+ if (source_root)
+ source_fspath = svn_fspath__canonicalize(edit_path, pool);
+
+ /* First, flush the copies stack so it only contains ancestors of path. */
+ while (cb->copies->nelts > 0
+ && ! svn_dirent_is_ancestor(APR_ARRAY_IDX(cb->copies,
+ cb->copies->nelts - 1,
+ struct copy_info *)->path,
+ edit_path))
+ apr_array_pop(cb->copies);
+
+ change = svn_hash_gets(cb->changed_paths, edit_path);
+ if (! change)
+ {
+ /* This can only happen if the path was removed from cb->changed_paths
+ by an earlier call to add_subdir, which means the path was already
+ handled and we should simply ignore it. */
+ return SVN_NO_ERROR;
+ }
+ switch (change->change_kind)
+ {
+ case svn_fs_path_change_add:
+ do_add = TRUE;
+ break;
+
+ case svn_fs_path_change_delete:
+ do_delete = TRUE;
+ break;
+
+ case svn_fs_path_change_replace:
+ do_add = TRUE;
+ do_delete = TRUE;
+ break;
+
+ case svn_fs_path_change_modify:
+ default:
+ /* do nothing */
+ break;
+ }
+
+ /* Handle any deletions. */
+ if (do_delete)
+ {
+ svn_boolean_t readable;
+
+ /* Issue #4121: delete under under a copy, of a path that was unreadable
+ at its pre-copy location. */
+ SVN_ERR(was_readable(&readable, root, edit_path, cb->copies,
+ cb->authz_read_func, cb->authz_read_baton,
+ pool, pool));
+ if (readable)
+ SVN_ERR(editor->delete_entry(edit_path, SVN_INVALID_REVNUM,
+ parent_baton, pool));
+ }
+
+ /* Fetch the node kind if it makes sense to do so. */
+ if (! do_delete || do_add)
+ {
+ if (change->node_kind == svn_node_unknown)
+ SVN_ERR(svn_fs_check_path(&(change->node_kind), root, edit_path, pool));
+ if ((change->node_kind != svn_node_dir) &&
+ (change->node_kind != svn_node_file))
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Filesystem path '%s' is neither a file "
+ "nor a directory"), edit_path);
+ }
+
+ /* Handle any adds/opens. */
+ if (do_add)
+ {
+ svn_boolean_t src_readable;
+ svn_fs_root_t *copyfrom_root;
+
+ /* Was this node copied? */
+ SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
+ &src_readable, root, change,
+ cb->authz_read_func, cb->authz_read_baton,
+ edit_path, pool, pool));
+
+ /* If we have a copyfrom path, and we can't read it or we're just
+ ignoring it, or the copyfrom rev is prior to the low water mark
+ then we just null them out and do a raw add with no history at
+ all. */
+ if (copyfrom_path
+ && ((! src_readable)
+ || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL)
+ || (cb->low_water_mark > copyfrom_rev)))
+ {
+ copyfrom_path = NULL;
+ copyfrom_rev = SVN_INVALID_REVNUM;
+ }
+
+ /* Do the right thing based on the path KIND. */
+ if (change->node_kind == svn_node_dir)
+ {
+ /* If this is a copy, but we can't represent it as such,
+ then we just do a recursive add of the source path
+ contents. */
+ if (change->copyfrom_path && ! copyfrom_path)
+ {
+ SVN_ERR(add_subdir(copyfrom_root, root, editor, edit_baton,
+ edit_path, parent_baton, change->copyfrom_path,
+ cb->authz_read_func, cb->authz_read_baton,
+ cb->changed_paths, pool, dir_baton));
+ }
+ else
+ {
+ SVN_ERR(editor->add_directory(edit_path, parent_baton,
+ copyfrom_path, copyfrom_rev,
+ pool, dir_baton));
+ }
+ }
+ else
+ {
+ SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path,
+ copyfrom_rev, pool, &file_baton));
+ }
+
+ /* If we represent this as a copy... */
+ if (copyfrom_path)
+ {
+ /* If it is a directory, make sure descendants get the correct
+ delta source by remembering that we are operating inside a
+ (possibly nested) copy operation. */
+ if (change->node_kind == svn_node_dir)
+ {
+ struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
+
+ info->path = apr_pstrdup(cb->pool, edit_path);
+ info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path);
+ info->copyfrom_rev = copyfrom_rev;
+
+ APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
+ }
+
+ /* Save the source so that we can use it later, when we
+ need to generate text and prop deltas. */
+ source_root = copyfrom_root;
+ source_fspath = copyfrom_path;
+ }
+ else
+ /* Else, we are an add without history... */
+ {
+ /* If an ancestor is added with history, we need to forget about
+ that here, go on with life and repeat all the mistakes of our
+ past... */
+ if (change->node_kind == svn_node_dir && cb->copies->nelts > 0)
+ {
+ struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
+
+ info->path = apr_pstrdup(cb->pool, edit_path);
+ info->copyfrom_path = NULL;
+ info->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
+ }
+ source_root = NULL;
+ source_fspath = NULL;
+ }
+ }
+ else if (! do_delete)
+ {
+ /* Do the right thing based on the path KIND (and the presence
+ of a PARENT_BATON). */
+ if (change->node_kind == svn_node_dir)
+ {
+ if (parent_baton)
+ {
+ SVN_ERR(editor->open_directory(edit_path, parent_baton,
+ SVN_INVALID_REVNUM,
+ pool, dir_baton));
+ }
+ else
+ {
+ SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
+ pool, dir_baton));
+ }
+ }
+ else
+ {
+ SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM,
+ pool, &file_baton));
+ }
+ /* If we are inside an add with history, we need to adjust the
+ delta source. */
+ if (cb->copies->nelts > 0)
+ {
+ struct copy_info *info = APR_ARRAY_IDX(cb->copies,
+ cb->copies->nelts - 1,
+ struct copy_info *);
+ if (info->copyfrom_path)
+ {
+ const char *relpath = svn_relpath_skip_ancestor(info->path,
+ edit_path);
+ SVN_ERR_ASSERT(relpath && *relpath);
+ SVN_ERR(svn_fs_revision_root(&source_root,
+ svn_fs_root_fs(root),
+ info->copyfrom_rev, pool));
+ source_fspath = svn_fspath__join(info->copyfrom_path,
+ relpath, pool);
+ }
+ else
+ {
+ /* This is an add without history, nested inside an
+ add with history. We have no delta source in this case. */
+ source_root = NULL;
+ source_fspath = NULL;
+ }
+ }
+ }
+
+ if (! do_delete || do_add)
+ {
+ /* Is this a copy that was downgraded to a raw add? (If so,
+ we'll need to transmit properties and file contents and such
+ for it regardless of what the CHANGE structure's text_mod
+ and prop_mod flags say.) */
+ svn_boolean_t downgraded_copy = (change->copyfrom_known
+ && change->copyfrom_path
+ && (! copyfrom_path));
+
+ /* Handle property modifications. */
+ if (change->prop_mod || downgraded_copy)
+ {
+ if (cb->compare_root)
+ {
+ apr_array_header_t *prop_diffs;
+ apr_hash_t *old_props;
+ apr_hash_t *new_props;
+ int i;
+
+ if (source_root)
+ SVN_ERR(svn_fs_node_proplist(&old_props, source_root,
+ source_fspath, pool));
+ else
+ old_props = apr_hash_make(pool);
+
+ SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool));
+
+ SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props,
+ pool));
+
+ for (i = 0; i < prop_diffs->nelts; ++i)
+ {
+ svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
+ if (change->node_kind == svn_node_dir)
+ SVN_ERR(editor->change_dir_prop(*dir_baton, pc->name,
+ pc->value, pool));
+ else if (change->node_kind == svn_node_file)
+ SVN_ERR(editor->change_file_prop(file_baton, pc->name,
+ pc->value, pool));
+ }
+ }
+ else
+ {
+ /* Just do a dummy prop change to signal that there are *any*
+ propmods. */
+ if (change->node_kind == svn_node_dir)
+ SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL,
+ pool));
+ else if (change->node_kind == svn_node_file)
+ SVN_ERR(editor->change_file_prop(file_baton, "", NULL,
+ pool));
+ }
+ }
+
+ /* Handle textual modifications. */
+ if (change->node_kind == svn_node_file
+ && (change->text_mod || downgraded_copy))
+ {
+ svn_txdelta_window_handler_t delta_handler;
+ void *delta_handler_baton;
+ const char *hex_digest = NULL;
+
+ if (cb->compare_root && source_root && source_fspath)
+ {
+ svn_checksum_t *checksum;
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
+ source_root, source_fspath, TRUE,
+ pool));
+ hex_digest = svn_checksum_to_cstring(checksum, pool);
+ }
+
+ SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool,
+ &delta_handler,
+ &delta_handler_baton));
+ if (cb->compare_root)
+ {
+ svn_txdelta_stream_t *delta_stream;
+
+ SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root,
+ source_fspath, root,
+ edit_path, pool));
+ SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
+ delta_handler_baton, pool));
+ }
+ else
+ SVN_ERR(delta_handler(NULL, delta_handler_baton));
+ }
+ }
+
+ /* Close the file baton if we opened it. */
+ if (file_baton)
+ {
+ svn_checksum_t *checksum;
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path,
+ TRUE, pool));
+ SVN_ERR(editor->close_file(file_baton,
+ svn_checksum_to_cstring(checksum, pool),
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#ifdef USE_EV2_IMPL
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_root_t *root = baton;
+ svn_fs_root_t *prev_root;
+ svn_fs_t *fs = svn_fs_root_fs(root);
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_revision_root_revision(root) - 1;
+
+ SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
+ SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_root_t *root = baton;
+ svn_fs_root_t *prev_root;
+ svn_fs_t *fs = svn_fs_root_fs(root);
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_revision_root_revision(root) - 1;
+
+ SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
+ SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool));
+
+ return SVN_NO_ERROR;
+}
+#endif
+
+
+
+
+svn_error_t *
+svn_repos_replay2(svn_fs_root_t *root,
+ const char *base_path,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+#ifndef USE_EV2_IMPL
+ apr_hash_t *fs_changes;
+ apr_hash_t *changed_paths;
+ apr_hash_index_t *hi;
+ apr_array_header_t *paths;
+ struct path_driver_cb_baton cb_baton;
+
+ /* Special-case r0, which we know is an empty revision; if we don't
+ special-case it we might end up trying to compare it to "r-1". */
+ if (svn_fs_is_revision_root(root) && svn_fs_revision_root_revision(root) == 0)
+ {
+ SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
+ return SVN_NO_ERROR;
+ }
+
+ /* Fetch the paths changed under ROOT. */
+ SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, pool));
+
+ if (! base_path)
+ base_path = "";
+ else if (base_path[0] == '/')
+ ++base_path;
+
+ /* Make an array from the keys of our CHANGED_PATHS hash, and copy
+ the values into a new hash whose keys have no leading slashes. */
+ paths = apr_array_make(pool, apr_hash_count(fs_changes),
+ sizeof(const char *));
+ changed_paths = apr_hash_make(pool);
+ for (hi = apr_hash_first(pool, fs_changes); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ apr_ssize_t keylen;
+ const char *path;
+ svn_fs_path_change2_t *change;
+ svn_boolean_t allowed = TRUE;
+
+ apr_hash_this(hi, &key, &keylen, &val);
+ path = key;
+ change = val;
+
+ if (authz_read_func)
+ SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
+ pool));
+
+ if (allowed)
+ {
+ if (path[0] == '/')
+ {
+ path++;
+ keylen--;
+ }
+
+ /* If the base_path doesn't match the top directory of this path
+ we don't want anything to do with it... */
+ if (svn_relpath_skip_ancestor(base_path, path) != NULL)
+ {
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ apr_hash_set(changed_paths, path, keylen, change);
+ }
+ /* ...unless this was a change to one of the parent directories of
+ base_path. */
+ else if (svn_relpath_skip_ancestor(path, base_path) != NULL)
+ {
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ apr_hash_set(changed_paths, path, keylen, change);
+ }
+ }
+ }
+
+ /* If we were not given a low water mark, assume that everything is there,
+ all the way back to revision 0. */
+ if (! SVN_IS_VALID_REVNUM(low_water_mark))
+ low_water_mark = 0;
+
+ /* Initialize our callback baton. */
+ cb_baton.editor = editor;
+ cb_baton.edit_baton = edit_baton;
+ cb_baton.root = root;
+ cb_baton.changed_paths = changed_paths;
+ cb_baton.authz_read_func = authz_read_func;
+ cb_baton.authz_read_baton = authz_read_baton;
+ cb_baton.base_path = base_path;
+ cb_baton.low_water_mark = low_water_mark;
+ cb_baton.compare_root = NULL;
+
+ if (send_deltas)
+ {
+ SVN_ERR(svn_fs_revision_root(&cb_baton.compare_root,
+ svn_fs_root_fs(root),
+ svn_fs_is_revision_root(root)
+ ? svn_fs_revision_root_revision(root) - 1
+ : svn_fs_txn_root_base_revision(root),
+ pool));
+ }
+
+ cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info *));
+ cb_baton.pool = pool;
+
+ /* Determine the revision to use throughout the edit, and call
+ EDITOR's set_target_revision() function. */
+ if (svn_fs_is_revision_root(root))
+ {
+ svn_revnum_t revision = svn_fs_revision_root_revision(root);
+ SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
+ }
+
+ /* Call the path-based editor driver. */
+ return svn_delta_path_driver2(editor, edit_baton,
+ paths, TRUE,
+ path_driver_cb_func, &cb_baton, pool);
+#else
+ svn_editor_t *editorv2;
+ struct svn_delta__extra_baton *exb;
+ svn_delta__unlock_func_t unlock_func;
+ svn_boolean_t send_abs_paths;
+ const char *repos_root = "";
+ void *unlock_baton;
+
+ /* Special-case r0, which we know is an empty revision; if we don't
+ special-case it we might end up trying to compare it to "r-1". */
+ if (svn_fs_is_revision_root(root)
+ && svn_fs_revision_root_revision(root) == 0)
+ {
+ SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
+ return SVN_NO_ERROR;
+ }
+
+ /* Determine the revision to use throughout the edit, and call
+ EDITOR's set_target_revision() function. */
+ if (svn_fs_is_revision_root(root))
+ {
+ svn_revnum_t revision = svn_fs_revision_root_revision(root);
+ SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
+ }
+
+ if (! base_path)
+ base_path = "";
+ else if (base_path[0] == '/')
+ ++base_path;
+
+ /* Use the shim to convert our editor to an Ev2 editor, and pass it down
+ the stack. */
+ SVN_ERR(svn_delta__editor_from_delta(&editorv2, &exb,
+ &unlock_func, &unlock_baton,
+ editor, edit_baton,
+ &send_abs_paths,
+ repos_root, "",
+ NULL, NULL,
+ fetch_kind_func, root,
+ fetch_props_func, root,
+ pool, pool));
+
+ /* Tell the shim that we're starting the process. */
+ SVN_ERR(exb->start_edit(exb->baton, svn_fs_revision_root_revision(root)));
+
+ /* ### We're ignoring SEND_DELTAS here. */
+ SVN_ERR(svn_repos__replay_ev2(root, base_path, low_water_mark,
+ editorv2, authz_read_func, authz_read_baton,
+ pool));
+
+ return SVN_NO_ERROR;
+#endif
+}
+
+
+/*****************************************************************
+ * Ev2 Implementation *
+ *****************************************************************/
+
+/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
+ the appropriate editor calls to add it and its children without any
+ history. This is meant to be used when either a subset of the tree
+ has been ignored and we need to copy something from that subset to
+ the part of the tree we do care about, or if a subset of the tree is
+ unavailable because of authz and we need to use it as the source of
+ a copy. */
+static svn_error_t *
+add_subdir_ev2(svn_fs_root_t *source_root,
+ svn_fs_root_t *target_root,
+ svn_editor_t *editor,
+ const char *repos_relpath,
+ const char *source_fspath,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_hash_t *changed_paths,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_hash_t *dirents;
+ apr_hash_t *props = NULL;
+ apr_array_header_t *children = NULL;
+
+ SVN_ERR(svn_fs_node_proplist(&props, target_root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_editor_add_directory(editor, repos_relpath, children,
+ props, SVN_INVALID_REVNUM));
+
+ /* We have to get the dirents from the source path, not the target,
+ because we want nested copies from *readable* paths to be handled by
+ path_driver_cb_func, not add_subdir (in order to preserve history). */
+ SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath,
+ scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_path_change2_t *change;
+ svn_boolean_t readable = TRUE;
+ svn_fs_dirent_t *dent = svn__apr_hash_index_val(hi);
+ const char *copyfrom_path = NULL;
+ svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+ const char *child_relpath;
+
+ svn_pool_clear(iterpool);
+
+ child_relpath = svn_relpath_join(repos_relpath, dent->name, iterpool);
+
+ /* If a file or subdirectory of the copied directory is listed as a
+ changed path (because it was modified after the copy but before the
+ commit), we remove it from the changed_paths hash so that future
+ calls to path_driver_cb_func will ignore it. */
+ change = svn_hash_gets(changed_paths, child_relpath);
+ if (change)
+ {
+ svn_hash_sets(changed_paths, child_relpath, NULL);
+
+ /* If it's a delete, skip this entry. */
+ if (change->change_kind == svn_fs_path_change_delete)
+ continue;
+
+ /* If it's a replacement, check for copyfrom info (if we
+ don't have it already. */
+ if (change->change_kind == svn_fs_path_change_replace)
+ {
+ if (! change->copyfrom_known)
+ {
+ SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
+ &change->copyfrom_path,
+ target_root, child_relpath,
+ result_pool));
+ change->copyfrom_known = TRUE;
+ }
+ copyfrom_path = change->copyfrom_path;
+ copyfrom_rev = change->copyfrom_rev;
+ }
+ }
+
+ if (authz_read_func)
+ SVN_ERR(authz_read_func(&readable, target_root, child_relpath,
+ authz_read_baton, iterpool));
+
+ if (! readable)
+ continue;
+
+ if (dent->kind == svn_node_dir)
+ {
+ svn_fs_root_t *new_source_root;
+ const char *new_source_fspath;
+
+ if (copyfrom_path)
+ {
+ svn_fs_t *fs = svn_fs_root_fs(source_root);
+ SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
+ copyfrom_rev, result_pool));
+ new_source_fspath = copyfrom_path;
+ }
+ else
+ {
+ new_source_root = source_root;
+ new_source_fspath = svn_fspath__join(source_fspath, dent->name,
+ iterpool);
+ }
+
+ /* ### authz considerations?
+ *
+ * I think not; when path_driver_cb_func() calls add_subdir(), it
+ * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
+ */
+ if (change && change->change_kind == svn_fs_path_change_replace
+ && copyfrom_path == NULL)
+ {
+ SVN_ERR(svn_editor_add_directory(editor, child_relpath,
+ children, props,
+ SVN_INVALID_REVNUM));
+ }
+ else
+ {
+ SVN_ERR(add_subdir_ev2(new_source_root, target_root,
+ editor, child_relpath,
+ new_source_fspath,
+ authz_read_func, authz_read_baton,
+ changed_paths, result_pool, iterpool));
+ }
+ }
+ else if (dent->kind == svn_node_file)
+ {
+ svn_checksum_t *checksum;
+ svn_stream_t *contents;
+
+ SVN_ERR(svn_fs_node_proplist(&props, target_root,
+ child_relpath, iterpool));
+
+ SVN_ERR(svn_fs_file_contents(&contents, target_root,
+ child_relpath, iterpool));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
+ target_root,
+ child_relpath, TRUE, iterpool));
+
+ SVN_ERR(svn_editor_add_file(editor, child_relpath, checksum,
+ contents, props, SVN_INVALID_REVNUM));
+ }
+ else
+ SVN_ERR_MALFUNCTION();
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+replay_node(svn_fs_root_t *root,
+ const char *repos_relpath,
+ svn_editor_t *editor,
+ svn_revnum_t low_water_mark,
+ const char *base_repos_relpath,
+ apr_array_header_t *copies,
+ apr_hash_t *changed_paths,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_path_change2_t *change;
+ svn_boolean_t do_add = FALSE;
+ svn_boolean_t do_delete = FALSE;
+ svn_revnum_t copyfrom_rev;
+ const char *copyfrom_path;
+ svn_revnum_t replaces_rev;
+
+ /* First, flush the copies stack so it only contains ancestors of path. */
+ while (copies->nelts > 0
+ && (svn_relpath_skip_ancestor(APR_ARRAY_IDX(copies,
+ copies->nelts - 1,
+ struct copy_info *)->path,
+ repos_relpath) == NULL) )
+ apr_array_pop(copies);
+
+ change = svn_hash_gets(changed_paths, repos_relpath);
+ if (! change)
+ {
+ /* This can only happen if the path was removed from changed_paths
+ by an earlier call to add_subdir, which means the path was already
+ handled and we should simply ignore it. */
+ return SVN_NO_ERROR;
+ }
+ switch (change->change_kind)
+ {
+ case svn_fs_path_change_add:
+ do_add = TRUE;
+ break;
+
+ case svn_fs_path_change_delete:
+ do_delete = TRUE;
+ break;
+
+ case svn_fs_path_change_replace:
+ do_add = TRUE;
+ do_delete = TRUE;
+ break;
+
+ case svn_fs_path_change_modify:
+ default:
+ /* do nothing */
+ break;
+ }
+
+ /* Handle any deletions. */
+ if (do_delete && ! do_add)
+ {
+ svn_boolean_t readable;
+
+ /* Issue #4121: delete under under a copy, of a path that was unreadable
+ at its pre-copy location. */
+ SVN_ERR(was_readable(&readable, root, repos_relpath, copies,
+ authz_read_func, authz_read_baton,
+ scratch_pool, scratch_pool));
+ if (readable)
+ SVN_ERR(svn_editor_delete(editor, repos_relpath, SVN_INVALID_REVNUM));
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Handle replacements. */
+ if (do_delete && do_add)
+ replaces_rev = svn_fs_revision_root_revision(root);
+ else
+ replaces_rev = SVN_INVALID_REVNUM;
+
+ /* Fetch the node kind if it makes sense to do so. */
+ if (! do_delete || do_add)
+ {
+ if (change->node_kind == svn_node_unknown)
+ SVN_ERR(svn_fs_check_path(&(change->node_kind), root, repos_relpath,
+ scratch_pool));
+ if ((change->node_kind != svn_node_dir) &&
+ (change->node_kind != svn_node_file))
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Filesystem path '%s' is neither a file "
+ "nor a directory"), repos_relpath);
+ }
+
+ /* Handle any adds/opens. */
+ if (do_add)
+ {
+ svn_boolean_t src_readable;
+ svn_fs_root_t *copyfrom_root;
+
+ /* Was this node copied? */
+ SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
+ &src_readable, root, change,
+ authz_read_func, authz_read_baton,
+ repos_relpath, scratch_pool, scratch_pool));
+
+ /* If we have a copyfrom path, and we can't read it or we're just
+ ignoring it, or the copyfrom rev is prior to the low water mark
+ then we just null them out and do a raw add with no history at
+ all. */
+ if (copyfrom_path
+ && ((! src_readable)
+ || (svn_relpath_skip_ancestor(base_repos_relpath,
+ copyfrom_path + 1) == NULL)
+ || (low_water_mark > copyfrom_rev)))
+ {
+ copyfrom_path = NULL;
+ copyfrom_rev = SVN_INVALID_REVNUM;
+ }
+
+ /* Do the right thing based on the path KIND. */
+ if (change->node_kind == svn_node_dir)
+ {
+ /* If this is a copy, but we can't represent it as such,
+ then we just do a recursive add of the source path
+ contents. */
+ if (change->copyfrom_path && ! copyfrom_path)
+ {
+ SVN_ERR(add_subdir_ev2(copyfrom_root, root, editor,
+ repos_relpath, change->copyfrom_path,
+ authz_read_func, authz_read_baton,
+ changed_paths, result_pool,
+ scratch_pool));
+ }
+ else
+ {
+ if (copyfrom_path)
+ {
+ if (copyfrom_path[0] == '/')
+ ++copyfrom_path;
+ SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
+ repos_relpath, replaces_rev));
+ }
+ else
+ {
+ apr_array_header_t *children;
+ apr_hash_t *props;
+ apr_hash_t *dirents;
+
+ SVN_ERR(svn_fs_dir_entries(&dirents, root, repos_relpath,
+ scratch_pool));
+ SVN_ERR(svn_hash_keys(&children, dirents, scratch_pool));
+
+ SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_editor_add_directory(editor, repos_relpath,
+ children, props,
+ replaces_rev));
+ }
+ }
+ }
+ else
+ {
+ if (copyfrom_path)
+ {
+ if (copyfrom_path[0] == '/')
+ ++copyfrom_path;
+ SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
+ repos_relpath, replaces_rev));
+ }
+ else
+ {
+ apr_hash_t *props;
+ svn_checksum_t *checksum;
+ svn_stream_t *contents;
+
+ SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root,
+ repos_relpath, TRUE, scratch_pool));
+
+ SVN_ERR(svn_editor_add_file(editor, repos_relpath, checksum,
+ contents, props, replaces_rev));
+ }
+ }
+
+ /* If we represent this as a copy... */
+ if (copyfrom_path)
+ {
+ /* If it is a directory, make sure descendants get the correct
+ delta source by remembering that we are operating inside a
+ (possibly nested) copy operation. */
+ if (change->node_kind == svn_node_dir)
+ {
+ struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
+
+ info->path = apr_pstrdup(result_pool, repos_relpath);
+ info->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
+ info->copyfrom_rev = copyfrom_rev;
+
+ APR_ARRAY_PUSH(copies, struct copy_info *) = info;
+ }
+ }
+ else
+ /* Else, we are an add without history... */
+ {
+ /* If an ancestor is added with history, we need to forget about
+ that here, go on with life and repeat all the mistakes of our
+ past... */
+ if (change->node_kind == svn_node_dir && copies->nelts > 0)
+ {
+ struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
+
+ info->path = apr_pstrdup(result_pool, repos_relpath);
+ info->copyfrom_path = NULL;
+ info->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ APR_ARRAY_PUSH(copies, struct copy_info *) = info;
+ }
+ }
+ }
+ else if (! do_delete)
+ {
+ /* If we are inside an add with history, we need to adjust the
+ delta source. */
+ if (copies->nelts > 0)
+ {
+ struct copy_info *info = APR_ARRAY_IDX(copies,
+ copies->nelts - 1,
+ struct copy_info *);
+ if (info->copyfrom_path)
+ {
+ const char *relpath = svn_relpath_skip_ancestor(info->path,
+ repos_relpath);
+ SVN_ERR_ASSERT(relpath && *relpath);
+ repos_relpath = svn_relpath_join(info->copyfrom_path,
+ relpath, scratch_pool);
+ }
+ }
+ }
+
+ if (! do_delete && !do_add)
+ {
+ apr_hash_t *props = NULL;
+
+ /* Is this a copy that was downgraded to a raw add? (If so,
+ we'll need to transmit properties and file contents and such
+ for it regardless of what the CHANGE structure's text_mod
+ and prop_mod flags say.) */
+ svn_boolean_t downgraded_copy = (change->copyfrom_known
+ && change->copyfrom_path
+ && (! copyfrom_path));
+
+ /* Handle property modifications. */
+ if (change->prop_mod || downgraded_copy)
+ {
+ SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
+ scratch_pool));
+ }
+
+ /* Handle textual modifications. */
+ if (change->node_kind == svn_node_file
+ && (change->text_mod || change->prop_mod || downgraded_copy))
+ {
+ svn_checksum_t *checksum = NULL;
+ svn_stream_t *contents = NULL;
+
+ if (change->text_mod)
+ {
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
+ root, repos_relpath, TRUE,
+ scratch_pool));
+
+ SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_editor_alter_file(editor, repos_relpath,
+ SVN_INVALID_REVNUM, props, checksum,
+ contents));
+ }
+
+ if (change->node_kind == svn_node_dir
+ && (change->prop_mod || downgraded_copy))
+ {
+ apr_array_header_t *children = NULL;
+
+ SVN_ERR(svn_editor_alter_directory(editor, repos_relpath,
+ SVN_INVALID_REVNUM, children,
+ props));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__replay_ev2(svn_fs_root_t *root,
+ const char *base_repos_relpath,
+ svn_revnum_t low_water_mark,
+ svn_editor_t *editor,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *fs_changes;
+ apr_hash_t *changed_paths;
+ apr_hash_index_t *hi;
+ apr_array_header_t *paths;
+ apr_array_header_t *copies;
+ apr_pool_t *iterpool;
+ svn_error_t *err = SVN_NO_ERROR;
+ int i;
+
+ SVN_ERR_ASSERT(!svn_dirent_is_absolute(base_repos_relpath));
+
+ /* Special-case r0, which we know is an empty revision; if we don't
+ special-case it we might end up trying to compare it to "r-1". */
+ if (svn_fs_is_revision_root(root)
+ && svn_fs_revision_root_revision(root) == 0)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* Fetch the paths changed under ROOT. */
+ SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, scratch_pool));
+
+ /* Make an array from the keys of our CHANGED_PATHS hash, and copy
+ the values into a new hash whose keys have no leading slashes. */
+ paths = apr_array_make(scratch_pool, apr_hash_count(fs_changes),
+ sizeof(const char *));
+ changed_paths = apr_hash_make(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, fs_changes); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ apr_ssize_t keylen;
+ const char *path;
+ svn_fs_path_change2_t *change;
+ svn_boolean_t allowed = TRUE;
+
+ apr_hash_this(hi, &key, &keylen, &val);
+ path = key;
+ change = val;
+
+ if (authz_read_func)
+ SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
+ scratch_pool));
+
+ if (allowed)
+ {
+ if (path[0] == '/')
+ {
+ path++;
+ keylen--;
+ }
+
+ /* If the base_path doesn't match the top directory of this path
+ we don't want anything to do with it... */
+ if (svn_relpath_skip_ancestor(base_repos_relpath, path) != NULL)
+ {
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ apr_hash_set(changed_paths, path, keylen, change);
+ }
+ /* ...unless this was a change to one of the parent directories of
+ base_path. */
+ else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL)
+ {
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ apr_hash_set(changed_paths, path, keylen, change);
+ }
+ }
+ }
+
+ /* If we were not given a low water mark, assume that everything is there,
+ all the way back to revision 0. */
+ if (! SVN_IS_VALID_REVNUM(low_water_mark))
+ low_water_mark = 0;
+
+ copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *));
+
+ /* Sort the paths. Although not strictly required by the API, this has
+ the pleasant side effect of maintaining a consistent ordering of
+ dumpfile contents. */
+ qsort(paths->elts, paths->nelts, paths->elt_size, svn_sort_compare_paths);
+
+ /* Now actually handle the various paths. */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *);
+
+ svn_pool_clear(iterpool);
+ err = replay_node(root, repos_relpath, editor, low_water_mark,
+ base_repos_relpath, copies, changed_paths,
+ authz_read_func, authz_read_baton,
+ scratch_pool, iterpool);
+ if (err)
+ break;
+ }
+
+ if (err)
+ return svn_error_compose_create(err, svn_editor_abort(editor));
+ else
+ SVN_ERR(svn_editor_complete(editor));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/reporter.c b/contrib/subversion/subversion/libsvn_repos/reporter.c
new file mode 100644
index 0000000..a9d1eff
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/reporter.c
@@ -0,0 +1,1610 @@
+/*
+ * reporter.c : `reporter' vtable routines for updates.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_fs.h"
+#include "svn_repos.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "repos.h"
+#include "svn_private_config.h"
+
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_string_private.h"
+
+#define NUM_CACHED_SOURCE_ROOTS 4
+
+/* Theory of operation: we write report operations out to a spill-buffer
+ as we receive them. When the report is finished, we read the
+ operations back out again, using them to guide the progression of
+ the delta between the source and target revs.
+
+ Spill-buffer content format: we use a simple ad-hoc format to store the
+ report operations. Each report operation is the concatention of
+ the following ("+/-" indicates the single character '+' or '-';
+ <length> and <revnum> are written out as decimal strings):
+
+ +/- '-' marks the end of the report
+ If previous is +:
+ <length>:<bytes> Length-counted path string
+ +/- '+' indicates the presence of link_path
+ If previous is +:
+ <length>:<bytes> Length-counted link_path string
+ +/- '+' indicates presence of revnum
+ If previous is +:
+ <revnum>: Revnum of set_path or link_path
+ +/- '+' indicates depth other than svn_depth_infinity
+ If previous is +:
+ <depth>: "X","E","F","M" =>
+ svn_depth_{exclude,empty,files,immediates}
+ +/- '+' indicates start_empty field set
+ +/- '+' indicates presence of lock_token field.
+ If previous is +:
+ <length>:<bytes> Length-counted lock_token string
+
+ Terminology: for brevity, this file frequently uses the prefixes
+ "s_" for source, "t_" for target, and "e_" for editor. Also, to
+ avoid overloading the word "target", we talk about the source
+ "anchor and operand", rather than the usual "anchor and target". */
+
+/* Describes the state of a working copy subtree, as given by a
+ report. Because we keep a lookahead pathinfo, we need to allocate
+ each one of these things in a subpool of the report baton and free
+ it when done. */
+typedef struct path_info_t
+{
+ const char *path; /* path, munged to be anchor-relative */
+ const char *link_path; /* NULL for set_path or delete_path */
+ svn_revnum_t rev; /* SVN_INVALID_REVNUM for delete_path */
+ svn_depth_t depth; /* Depth of this path, meaningless for files */
+ svn_boolean_t start_empty; /* Meaningless for delete_path */
+ const char *lock_token; /* NULL if no token */
+ apr_pool_t *pool; /* Container pool */
+} path_info_t;
+
+/* Describes the standard revision properties that are relevant for
+ reports. Since a particular revision will often show up more than
+ once in the report, we cache these properties for the time of the
+ report generation. */
+typedef struct revision_info_t
+{
+ svn_revnum_t rev; /* revision number */
+ svn_string_t* date; /* revision timestamp */
+ svn_string_t* author; /* name of the revisions' author */
+} revision_info_t;
+
+/* A structure used by the routines within the `reporter' vtable,
+ driven by the client as it describes its working copy revisions. */
+typedef struct report_baton_t
+{
+ /* Parameters remembered from svn_repos_begin_report3 */
+ svn_repos_t *repos;
+ const char *fs_base; /* fspath corresponding to wc anchor */
+ const char *s_operand; /* anchor-relative wc target (may be empty) */
+ svn_revnum_t t_rev; /* Revnum which the edit will bring the wc to */
+ const char *t_path; /* FS path the edit will bring the wc to */
+ svn_boolean_t text_deltas; /* Whether to report text deltas */
+ apr_size_t zero_copy_limit; /* Max item size that will be sent using
+ the zero-copy code path. */
+
+ /* If the client requested a specific depth, record it here; if the
+ client did not, then this is svn_depth_unknown, and the depth of
+ information transmitted from server to client will be governed
+ strictly by the path-associated depths recorded in the report. */
+ svn_depth_t requested_depth;
+
+ svn_boolean_t ignore_ancestry;
+ svn_boolean_t send_copyfrom_args;
+ svn_boolean_t is_switch;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ svn_repos_authz_func_t authz_read_func;
+ void *authz_read_baton;
+
+ /* The spill-buffer holding the report. */
+ svn_spillbuf_reader_t *reader;
+
+ /* For the actual editor drive, we'll need a lookahead path info
+ entry, a cache of FS roots, and a pool to store them. */
+ path_info_t *lookahead;
+ svn_fs_root_t *t_root;
+ svn_fs_root_t *s_roots[NUM_CACHED_SOURCE_ROOTS];
+
+ /* Cache for revision properties. This is used to eliminate redundant
+ revprop fetching. */
+ apr_hash_t *revision_infos;
+
+ /* This will not change. So, fetch it once and reuse it. */
+ svn_string_t *repos_uuid;
+ apr_pool_t *pool;
+} report_baton_t;
+
+/* The type of a function that accepts changes to an object's property
+ list. OBJECT is the object whose properties are being changed.
+ NAME is the name of the property to change. VALUE is the new value
+ for the property, or zero if the property should be deleted. */
+typedef svn_error_t *proplist_change_fn_t(report_baton_t *b, void *object,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+static svn_error_t *delta_dirs(report_baton_t *b, svn_revnum_t s_rev,
+ const char *s_path, const char *t_path,
+ void *dir_baton, const char *e_path,
+ svn_boolean_t start_empty,
+ svn_depth_t wc_depth,
+ svn_depth_t requested_depth,
+ apr_pool_t *pool);
+
+/* --- READING PREVIOUSLY STORED REPORT INFORMATION --- */
+
+static svn_error_t *
+read_number(apr_uint64_t *num, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
+{
+ char c;
+
+ *num = 0;
+ while (1)
+ {
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
+ if (c == ':')
+ break;
+ *num = *num * 10 + (c - '0');
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_string(const char **str, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
+{
+ apr_uint64_t len;
+ apr_size_t size;
+ apr_size_t amt;
+ char *buf;
+
+ SVN_ERR(read_number(&len, reader, pool));
+
+ /* Len can never be less than zero. But could len be so large that
+ len + 1 wraps around and we end up passing 0 to apr_palloc(),
+ thus getting a pointer to no storage? Probably not (16 exabyte
+ string, anyone?) but let's be future-proof anyway. */
+ if (len + 1 < len || len + 1 > APR_SIZE_MAX)
+ {
+ /* xgettext doesn't expand preprocessor definitions, so we must
+ pass translatable string to apr_psprintf() function to create
+ intermediate string with appropriate format specifier. */
+ return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
+ apr_psprintf(pool,
+ _("Invalid length (%%%s) when "
+ "about to read a string"),
+ APR_UINT64_T_FMT),
+ len);
+ }
+
+ size = (apr_size_t)len;
+ buf = apr_palloc(pool, size+1);
+ if (size > 0)
+ {
+ SVN_ERR(svn_spillbuf__reader_read(&amt, reader, buf, size, pool));
+ SVN_ERR_ASSERT(amt == size);
+ }
+ buf[len] = 0;
+ *str = buf;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_rev(svn_revnum_t *rev, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
+{
+ char c;
+ apr_uint64_t num;
+
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
+ if (c == '+')
+ {
+ SVN_ERR(read_number(&num, reader, pool));
+ *rev = (svn_revnum_t) num;
+ }
+ else
+ *rev = SVN_INVALID_REVNUM;
+ return SVN_NO_ERROR;
+}
+
+/* Read a single character to set *DEPTH (having already read '+')
+ from READER. PATH is the path to which the depth applies, and is
+ used for error reporting only. */
+static svn_error_t *
+read_depth(svn_depth_t *depth, svn_spillbuf_reader_t *reader, const char *path,
+ apr_pool_t *pool)
+{
+ char c;
+
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
+ switch (c)
+ {
+ case 'X':
+ *depth = svn_depth_exclude;
+ break;
+ case 'E':
+ *depth = svn_depth_empty;
+ break;
+ case 'F':
+ *depth = svn_depth_files;
+ break;
+ case 'M':
+ *depth = svn_depth_immediates;
+ break;
+
+ /* Note that we do not tolerate explicit representation of
+ svn_depth_infinity here, because that's not how
+ write_path_info() writes it. */
+ default:
+ return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
+ _("Invalid depth (%c) for path '%s'"), c, path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read a report operation *PI out of READER. Set *PI to NULL if we
+ have reached the end of the report. */
+static svn_error_t *
+read_path_info(path_info_t **pi,
+ svn_spillbuf_reader_t *reader,
+ apr_pool_t *pool)
+{
+ char c;
+
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
+ if (c == '-')
+ {
+ *pi = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ *pi = apr_palloc(pool, sizeof(**pi));
+ SVN_ERR(read_string(&(*pi)->path, reader, pool));
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
+ if (c == '+')
+ SVN_ERR(read_string(&(*pi)->link_path, reader, pool));
+ else
+ (*pi)->link_path = NULL;
+ SVN_ERR(read_rev(&(*pi)->rev, reader, pool));
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
+ if (c == '+')
+ SVN_ERR(read_depth(&((*pi)->depth), reader, (*pi)->path, pool));
+ else
+ (*pi)->depth = svn_depth_infinity;
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
+ (*pi)->start_empty = (c == '+');
+ SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
+ if (c == '+')
+ SVN_ERR(read_string(&(*pi)->lock_token, reader, pool));
+ else
+ (*pi)->lock_token = NULL;
+ (*pi)->pool = pool;
+ return SVN_NO_ERROR;
+}
+
+/* Return true if PI's path is a child of PREFIX (which has length PLEN). */
+static svn_boolean_t
+relevant(path_info_t *pi, const char *prefix, apr_size_t plen)
+{
+ return (pi && strncmp(pi->path, prefix, plen) == 0 &&
+ (!*prefix || pi->path[plen] == '/'));
+}
+
+/* Fetch the next pathinfo from B->reader for a descendant of
+ PREFIX. If the next pathinfo is for an immediate child of PREFIX,
+ set *ENTRY to the path component of the report information and
+ *INFO to the path information for that entry. If the next pathinfo
+ is for a grandchild or other more remote descendant of PREFIX, set
+ *ENTRY to the immediate child corresponding to that descendant and
+ set *INFO to NULL. If the next pathinfo is not for a descendant of
+ PREFIX, or if we reach the end of the report, set both *ENTRY and
+ *INFO to NULL.
+
+ At all times, B->lookahead is presumed to be the next pathinfo not
+ yet returned as an immediate child, or NULL if we have reached the
+ end of the report. Because we use a lookahead element, we can't
+ rely on the usual nested pool lifetimes, so allocate each pathinfo
+ in a subpool of the report baton's pool. The caller should delete
+ (*INFO)->pool when it is done with the information. */
+static svn_error_t *
+fetch_path_info(report_baton_t *b, const char **entry, path_info_t **info,
+ const char *prefix, apr_pool_t *pool)
+{
+ apr_size_t plen = strlen(prefix);
+ const char *relpath, *sep;
+ apr_pool_t *subpool;
+
+ if (!relevant(b->lookahead, prefix, plen))
+ {
+ /* No more entries relevant to prefix. */
+ *entry = NULL;
+ *info = NULL;
+ }
+ else
+ {
+ /* Take a look at the prefix-relative part of the path. */
+ relpath = b->lookahead->path + (*prefix ? plen + 1 : 0);
+ sep = strchr(relpath, '/');
+ if (sep)
+ {
+ /* Return the immediate child part; do not advance. */
+ *entry = apr_pstrmemdup(pool, relpath, sep - relpath);
+ *info = NULL;
+ }
+ else
+ {
+ /* This is an immediate child; return it and advance. */
+ *entry = relpath;
+ *info = b->lookahead;
+ subpool = svn_pool_create(b->pool);
+ SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Skip all path info entries relevant to *PREFIX. Call this when the
+ editor drive skips a directory. */
+static svn_error_t *
+skip_path_info(report_baton_t *b, const char *prefix)
+{
+ apr_size_t plen = strlen(prefix);
+ apr_pool_t *subpool;
+
+ while (relevant(b->lookahead, prefix, plen))
+ {
+ svn_pool_destroy(b->lookahead->pool);
+ subpool = svn_pool_create(b->pool);
+ SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Return true if there is at least one path info entry relevant to *PREFIX. */
+static svn_boolean_t
+any_path_info(report_baton_t *b, const char *prefix)
+{
+ return relevant(b->lookahead, prefix, strlen(prefix));
+}
+
+/* --- DRIVING THE EDITOR ONCE THE REPORT IS FINISHED --- */
+
+/* While driving the editor, the target root will remain constant, but
+ we may have to jump around between source roots depending on the
+ state of the working copy. If we were to open a root each time we
+ revisit a rev, we would get no benefit from node-id caching; on the
+ other hand, if we hold open all the roots we ever visit, we'll use
+ an unbounded amount of memory. As a compromise, we maintain a
+ fixed-size LRU cache of source roots. get_source_root retrieves a
+ root from the cache, using POOL to allocate the new root if
+ necessary. Be careful not to hold onto the root for too long,
+ particularly after recursing, since another call to get_source_root
+ can close it. */
+static svn_error_t *
+get_source_root(report_baton_t *b, svn_fs_root_t **s_root, svn_revnum_t rev)
+{
+ int i;
+ svn_fs_root_t *root, *prev = NULL;
+
+ /* Look for the desired root in the cache, sliding all the unmatched
+ entries backwards a slot to make room for the right one. */
+ for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++)
+ {
+ root = b->s_roots[i];
+ b->s_roots[i] = prev;
+ if (root && svn_fs_revision_root_revision(root) == rev)
+ break;
+ prev = root;
+ }
+
+ /* If we didn't find it, throw out the oldest root and open a new one. */
+ if (i == NUM_CACHED_SOURCE_ROOTS)
+ {
+ if (prev)
+ svn_fs_close_root(prev);
+ SVN_ERR(svn_fs_revision_root(&root, b->repos->fs, rev, b->pool));
+ }
+
+ /* Assign the desired root to the first cache slot and hand it back. */
+ b->s_roots[0] = root;
+ *s_root = root;
+ return SVN_NO_ERROR;
+}
+
+/* Call the directory property-setting function of B->editor to set
+ the property NAME to VALUE on DIR_BATON. */
+static svn_error_t *
+change_dir_prop(report_baton_t *b, void *dir_baton, const char *name,
+ const svn_string_t *value, apr_pool_t *pool)
+{
+ return svn_error_trace(b->editor->change_dir_prop(dir_baton, name, value,
+ pool));
+}
+
+/* Call the file property-setting function of B->editor to set the
+ property NAME to VALUE on FILE_BATON. */
+static svn_error_t *
+change_file_prop(report_baton_t *b, void *file_baton, const char *name,
+ const svn_string_t *value, apr_pool_t *pool)
+{
+ return svn_error_trace(b->editor->change_file_prop(file_baton, name, value,
+ pool));
+}
+
+/* For the report B, return the relevant revprop data of revision REV in
+ REVISION_INFO. The revision info will be allocated in b->pool.
+ Temporaries get allocated on SCRATCH_POOL. */
+static svn_error_t *
+get_revision_info(report_baton_t *b,
+ svn_revnum_t rev,
+ revision_info_t** revision_info,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *r_props;
+ svn_string_t *cdate, *author;
+ revision_info_t* info;
+
+ /* Try to find the info in the report's cache */
+ info = apr_hash_get(b->revision_infos, &rev, sizeof(rev));
+ if (!info)
+ {
+ /* Info is not available, yet.
+ Get all revprops. */
+ SVN_ERR(svn_fs_revision_proplist(&r_props,
+ b->repos->fs,
+ rev,
+ scratch_pool));
+
+ /* Extract the committed-date. */
+ cdate = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
+
+ /* Extract the last-author. */
+ author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR);
+
+ /* Create a result object */
+ info = apr_palloc(b->pool, sizeof(*info));
+ info->rev = rev;
+ info->date = cdate ? svn_string_dup(cdate, b->pool) : NULL;
+ info->author = author ? svn_string_dup(author, b->pool) : NULL;
+
+ /* Cache it */
+ apr_hash_set(b->revision_infos, &info->rev, sizeof(rev), info);
+ }
+
+ *revision_info = info;
+ return SVN_NO_ERROR;
+}
+
+
+/* Generate the appropriate property editing calls to turn the
+ properties of S_REV/S_PATH into those of B->t_root/T_PATH. If
+ S_PATH is NULL, this is an add, so assume the target starts with no
+ properties. Pass OBJECT on to the editor function wrapper
+ CHANGE_FN. */
+static svn_error_t *
+delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
+ const char *t_path, const char *lock_token,
+ proplist_change_fn_t *change_fn,
+ void *object, apr_pool_t *pool)
+{
+ svn_fs_root_t *s_root;
+ apr_hash_t *s_props = NULL, *t_props;
+ apr_array_header_t *prop_diffs;
+ int i;
+ svn_revnum_t crev;
+ revision_info_t *revision_info;
+ svn_boolean_t changed;
+ const svn_prop_t *pc;
+ svn_lock_t *lock;
+ apr_hash_index_t *hi;
+
+ /* Fetch the created-rev and send entry props. */
+ SVN_ERR(svn_fs_node_created_rev(&crev, b->t_root, t_path, pool));
+ if (SVN_IS_VALID_REVNUM(crev))
+ {
+ /* convert committed-rev to string */
+ char buf[SVN_INT64_BUFFER_SIZE];
+ svn_string_t cr_str;
+ cr_str.data = buf;
+ cr_str.len = svn__i64toa(buf, crev);
+
+ /* Transmit the committed-rev. */
+ SVN_ERR(change_fn(b, object,
+ SVN_PROP_ENTRY_COMMITTED_REV, &cr_str, pool));
+
+ SVN_ERR(get_revision_info(b, crev, &revision_info, pool));
+
+ /* Transmit the committed-date. */
+ if (revision_info->date || s_path)
+ SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_COMMITTED_DATE,
+ revision_info->date, pool));
+
+ /* Transmit the last-author. */
+ if (revision_info->author || s_path)
+ SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_LAST_AUTHOR,
+ revision_info->author, pool));
+
+ /* Transmit the UUID. */
+ SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_UUID,
+ b->repos_uuid, pool));
+ }
+
+ /* Update lock properties. */
+ if (lock_token)
+ {
+ SVN_ERR(svn_fs_get_lock(&lock, b->repos->fs, t_path, pool));
+
+ /* Delete a defunct lock. */
+ if (! lock || strcmp(lock_token, lock->token) != 0)
+ SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_LOCK_TOKEN,
+ NULL, pool));
+ }
+
+ if (s_path)
+ {
+ SVN_ERR(get_source_root(b, &s_root, s_rev));
+
+ /* Is this deltification worth our time? */
+ SVN_ERR(svn_fs_props_changed(&changed, b->t_root, t_path, s_root,
+ s_path, pool));
+ if (! changed)
+ return SVN_NO_ERROR;
+
+ /* If so, go ahead and get the source path's properties. */
+ SVN_ERR(svn_fs_node_proplist(&s_props, s_root, s_path, pool));
+ }
+
+ /* Get the target path's properties */
+ SVN_ERR(svn_fs_node_proplist(&t_props, b->t_root, t_path, pool));
+
+ if (s_props && apr_hash_count(s_props))
+ {
+ /* Now transmit the differences. */
+ SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool));
+ for (i = 0; i < prop_diffs->nelts; i++)
+ {
+ pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
+ SVN_ERR(change_fn(b, object, pc->name, pc->value, pool));
+ }
+ }
+ else if (apr_hash_count(t_props))
+ {
+ /* So source, i.e. all new. Transmit all target props. */
+ for (hi = apr_hash_first(pool, t_props); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ SVN_ERR(change_fn(b, object, key, val, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton type to be passed into send_zero_copy_delta.
+ */
+typedef struct zero_copy_baton_t
+{
+ /* don't process data larger than this limit */
+ apr_size_t zero_copy_limit;
+
+ /* window handler and baton to send the data to */
+ svn_txdelta_window_handler_t dhandler;
+ void *dbaton;
+
+ /* return value: will be set to TRUE, if the data was processed. */
+ svn_boolean_t zero_copy_succeeded;
+} zero_copy_baton_t;
+
+/* Implement svn_fs_process_contents_func_t. If LEN is smaller than the
+ * limit given in *BATON, send the CONTENTS as an delta windows to the
+ * handler given in BATON and set the ZERO_COPY_SUCCEEDED flag in that
+ * BATON. Otherwise, reset it to FALSE.
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+send_zero_copy_delta(const unsigned char *contents,
+ apr_size_t len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ zero_copy_baton_t *zero_copy_baton = baton;
+
+ /* if the item is too large, the caller must revert to traditional
+ streaming code. */
+ if (len > zero_copy_baton->zero_copy_limit)
+ {
+ zero_copy_baton->zero_copy_succeeded = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_txdelta_send_contents(contents, len,
+ zero_copy_baton->dhandler,
+ zero_copy_baton->dbaton, pool));
+
+ /* all fine now */
+ zero_copy_baton->zero_copy_succeeded = TRUE;
+ return SVN_NO_ERROR;
+}
+
+
+/* Make the appropriate edits on FILE_BATON to change its contents and
+ properties from those in S_REV/S_PATH to those in B->t_root/T_PATH,
+ possibly using LOCK_TOKEN to determine if the client's lock on the file
+ is defunct. */
+static svn_error_t *
+delta_files(report_baton_t *b, void *file_baton, svn_revnum_t s_rev,
+ const char *s_path, const char *t_path, const char *lock_token,
+ apr_pool_t *pool)
+{
+ svn_boolean_t changed;
+ svn_fs_root_t *s_root = NULL;
+ svn_txdelta_stream_t *dstream = NULL;
+ svn_checksum_t *s_checksum;
+ const char *s_hex_digest = NULL;
+ svn_txdelta_window_handler_t dhandler;
+ void *dbaton;
+
+ /* Compare the files' property lists. */
+ SVN_ERR(delta_proplists(b, s_rev, s_path, t_path, lock_token,
+ change_file_prop, file_baton, pool));
+
+ if (s_path)
+ {
+ SVN_ERR(get_source_root(b, &s_root, s_rev));
+
+ /* We're not interested in the theoretical difference between "has
+ contents which have not changed with respect to" and "has the same
+ actual contents as" when sending text-deltas. If we know the
+ delta is an empty one, we avoiding sending it in either case. */
+ SVN_ERR(svn_repos__compare_files(&changed, b->t_root, t_path,
+ s_root, s_path, pool));
+
+ if (!changed)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_file_checksum(&s_checksum, svn_checksum_md5, s_root,
+ s_path, TRUE, pool));
+ s_hex_digest = svn_checksum_to_cstring(s_checksum, pool);
+ }
+
+ /* Send the delta stream if desired, or just a NULL window if not. */
+ SVN_ERR(b->editor->apply_textdelta(file_baton, s_hex_digest, pool,
+ &dhandler, &dbaton));
+
+ if (dhandler != svn_delta_noop_window_handler)
+ {
+ if (b->text_deltas)
+ {
+ /* if we send deltas against empty streams, we may use our
+ zero-copy code. */
+ if (b->zero_copy_limit > 0 && s_path == NULL)
+ {
+ zero_copy_baton_t baton;
+ svn_boolean_t called = FALSE;
+
+ baton.zero_copy_limit = b->zero_copy_limit;
+ baton.dhandler = dhandler;
+ baton.dbaton = dbaton;
+ baton.zero_copy_succeeded = FALSE;
+ SVN_ERR(svn_fs_try_process_file_contents(&called,
+ b->t_root, t_path,
+ send_zero_copy_delta,
+ &baton, pool));
+
+ /* data has been available and small enough,
+ i.e. been processed? */
+ if (called && baton.zero_copy_succeeded)
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_fs_get_file_delta_stream(&dstream, s_root, s_path,
+ b->t_root, t_path, pool));
+ SVN_ERR(svn_txdelta_send_txstream(dstream, dhandler, dbaton, pool));
+ }
+ else
+ SVN_ERR(dhandler(NULL, dbaton));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Determine if the user is authorized to view B->t_root/PATH. */
+static svn_error_t *
+check_auth(report_baton_t *b, svn_boolean_t *allowed, const char *path,
+ apr_pool_t *pool)
+{
+ if (b->authz_read_func)
+ return svn_error_trace(b->authz_read_func(allowed, b->t_root, path,
+ b->authz_read_baton, pool));
+ *allowed = TRUE;
+ return SVN_NO_ERROR;
+}
+
+/* Create a dirent in *ENTRY for the given ROOT and PATH. We use this to
+ replace the source or target dirent when a report pathinfo tells us to
+ change paths or revisions. */
+static svn_error_t *
+fake_dirent(const svn_fs_dirent_t **entry, svn_fs_root_t *root,
+ const char *path, apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ svn_fs_dirent_t *ent;
+
+ SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
+ if (kind == svn_node_none)
+ *entry = NULL;
+ else
+ {
+ ent = apr_palloc(pool, sizeof(**entry));
+ /* ### All callers should be updated to pass just one of these
+ formats */
+ ent->name = (*path == '/') ? svn_fspath__basename(path, pool)
+ : svn_relpath_basename(path, pool);
+ SVN_ERR(svn_fs_node_id(&ent->id, root, path, pool));
+ ent->kind = kind;
+ *entry = ent;
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* Given REQUESTED_DEPTH, WC_DEPTH and the current entry's KIND,
+ determine whether we need to send the whole entry, not just deltas.
+ Please refer to delta_dirs' docstring for an explanation of the
+ conditionals below. */
+static svn_boolean_t
+is_depth_upgrade(svn_depth_t wc_depth,
+ svn_depth_t requested_depth,
+ svn_node_kind_t kind)
+{
+ if (requested_depth == svn_depth_unknown
+ || requested_depth <= wc_depth
+ || wc_depth == svn_depth_immediates)
+ return FALSE;
+
+ if (kind == svn_node_file
+ && wc_depth == svn_depth_files)
+ return FALSE;
+
+ if (kind == svn_node_dir
+ && wc_depth == svn_depth_empty
+ && requested_depth == svn_depth_files)
+ return FALSE;
+
+ return TRUE;
+}
+
+
+/* Call the B->editor's add_file() function to create PATH as a child
+ of PARENT_BATON, returning a new baton in *NEW_FILE_BATON.
+ However, make an attempt to send 'copyfrom' arguments if they're
+ available, by examining the closest copy of the original file
+ O_PATH within B->t_root. If any copyfrom args are discovered,
+ return those in *COPYFROM_PATH and *COPYFROM_REV; otherwise leave
+ those return args untouched. */
+static svn_error_t *
+add_file_smartly(report_baton_t *b,
+ const char *path,
+ void *parent_baton,
+ const char *o_path,
+ void **new_file_baton,
+ const char **copyfrom_path,
+ svn_revnum_t *copyfrom_rev,
+ apr_pool_t *pool)
+{
+ /* ### TODO: use a subpool to do this work, clear it at the end? */
+ svn_fs_t *fs = svn_repos_fs(b->repos);
+ svn_fs_root_t *closest_copy_root = NULL;
+ const char *closest_copy_path = NULL;
+
+ /* Pre-emptively assume no copyfrom args exist. */
+ *copyfrom_path = NULL;
+ *copyfrom_rev = SVN_INVALID_REVNUM;
+
+ if (b->send_copyfrom_args)
+ {
+ /* Find the destination of the nearest 'copy event' which may have
+ caused o_path@t_root to exist. svn_fs_closest_copy only returns paths
+ starting with '/', so make sure o_path always starts with a '/'
+ too. */
+ if (*o_path != '/')
+ o_path = apr_pstrcat(pool, "/", o_path, (char *)NULL);
+
+ SVN_ERR(svn_fs_closest_copy(&closest_copy_root, &closest_copy_path,
+ b->t_root, o_path, pool));
+ if (closest_copy_root != NULL)
+ {
+ /* If the destination of the copy event is the same path as
+ o_path, then we've found something interesting that should
+ have 'copyfrom' history. */
+ if (strcmp(closest_copy_path, o_path) == 0)
+ {
+ SVN_ERR(svn_fs_copied_from(copyfrom_rev, copyfrom_path,
+ closest_copy_root, closest_copy_path,
+ pool));
+ if (b->authz_read_func)
+ {
+ svn_boolean_t allowed;
+ svn_fs_root_t *copyfrom_root;
+ SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
+ *copyfrom_rev, pool));
+ SVN_ERR(b->authz_read_func(&allowed, copyfrom_root,
+ *copyfrom_path, b->authz_read_baton,
+ pool));
+ if (! allowed)
+ {
+ *copyfrom_path = NULL;
+ *copyfrom_rev = SVN_INVALID_REVNUM;
+ }
+ }
+ }
+ }
+ }
+
+ return svn_error_trace(b->editor->add_file(path, parent_baton,
+ *copyfrom_path, *copyfrom_rev,
+ pool, new_file_baton));
+}
+
+
+/* Emit a series of editing operations to transform a source entry to
+ a target entry.
+
+ S_REV and S_PATH specify the source entry. S_ENTRY contains the
+ already-looked-up information about the node-revision existing at
+ that location. S_PATH and S_ENTRY may be NULL if the entry does
+ not exist in the source. S_PATH may be non-NULL and S_ENTRY may be
+ NULL if the caller expects INFO to modify the source to an existing
+ location.
+
+ B->t_root and T_PATH specify the target entry. T_ENTRY contains
+ the already-looked-up information about the node-revision existing
+ at that location. T_PATH and T_ENTRY may be NULL if the entry does
+ not exist in the target.
+
+ DIR_BATON and E_PATH contain the parameters which should be passed
+ to the editor calls--DIR_BATON for the parent directory baton and
+ E_PATH for the pathname. (E_PATH is the anchor-relative working
+ copy pathname, which may differ from the source and target
+ pathnames if the report contains a link_path.)
+
+ INFO contains the report information for this working copy path, or
+ NULL if there is none. This function will internally modify the
+ source and target entries as appropriate based on the report
+ information.
+
+ WC_DEPTH and REQUESTED_DEPTH are propagated to delta_dirs() if
+ necessary. Refer to delta_dirs' docstring to find out what
+ should happen for various combinations of WC_DEPTH/REQUESTED_DEPTH. */
+static svn_error_t *
+update_entry(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
+ const svn_fs_dirent_t *s_entry, const char *t_path,
+ const svn_fs_dirent_t *t_entry, void *dir_baton,
+ const char *e_path, path_info_t *info, svn_depth_t wc_depth,
+ svn_depth_t requested_depth, apr_pool_t *pool)
+{
+ svn_fs_root_t *s_root;
+ svn_boolean_t allowed, related;
+ void *new_baton;
+ svn_checksum_t *checksum;
+ const char *hex_digest;
+
+ /* For non-switch operations, follow link_path in the target. */
+ if (info && info->link_path && !b->is_switch)
+ {
+ t_path = info->link_path;
+ SVN_ERR(fake_dirent(&t_entry, b->t_root, t_path, pool));
+ }
+
+ if (info && !SVN_IS_VALID_REVNUM(info->rev))
+ {
+ /* Delete this entry in the source. */
+ s_path = NULL;
+ s_entry = NULL;
+ }
+ else if (info && s_path)
+ {
+ /* Follow the rev and possibly path in this entry. */
+ s_path = (info->link_path) ? info->link_path : s_path;
+ s_rev = info->rev;
+ SVN_ERR(get_source_root(b, &s_root, s_rev));
+ SVN_ERR(fake_dirent(&s_entry, s_root, s_path, pool));
+ }
+
+ /* Don't let the report carry us somewhere nonexistent. */
+ if (s_path && !s_entry)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Working copy path '%s' does not exist in "
+ "repository"), e_path);
+
+ /* If the source and target both exist and are of the same kind,
+ then find out whether they're related. If they're exactly the
+ same, then we don't have to do anything (unless the report has
+ changes to the source). If we're ignoring ancestry, then any two
+ nodes of the same type are related enough for us. */
+ related = FALSE;
+ if (s_entry && t_entry && s_entry->kind == t_entry->kind)
+ {
+ int distance = svn_fs_compare_ids(s_entry->id, t_entry->id);
+ if (distance == 0 && !any_path_info(b, e_path)
+ && (requested_depth <= wc_depth || t_entry->kind == svn_node_file))
+ {
+ if (!info)
+ return SVN_NO_ERROR;
+
+ if (!info->start_empty)
+ {
+ svn_lock_t *lock;
+
+ if (!info->lock_token)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_get_lock(&lock, b->repos->fs, t_path, pool));
+ if (lock && (strcmp(lock->token, info->lock_token) == 0))
+ return SVN_NO_ERROR;
+ }
+ }
+
+ related = (distance != -1 || b->ignore_ancestry);
+ }
+
+ /* If there's a source and it's not related to the target, nuke it. */
+ if (s_entry && !related)
+ {
+ svn_revnum_t deleted_rev;
+
+ SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root), t_path,
+ s_rev, b->t_rev, &deleted_rev,
+ pool));
+
+ if (!SVN_IS_VALID_REVNUM(deleted_rev))
+ {
+ /* Two possibilities: either the thing doesn't exist in S_REV; or
+ it wasn't deleted between S_REV and B->T_REV. In the first case,
+ I think we should leave DELETED_REV as SVN_INVALID_REVNUM, but
+ in the second, it should be set to B->T_REV-1 for the call to
+ delete_entry() below. */
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_fs_check_path(&kind, b->t_root, t_path, pool));
+ if (kind != svn_node_none)
+ deleted_rev = b->t_rev - 1;
+ }
+
+ SVN_ERR(b->editor->delete_entry(e_path, deleted_rev, dir_baton,
+ pool));
+ s_path = NULL;
+ }
+
+ /* If there's no target, we have nothing more to do. */
+ if (!t_entry)
+ return svn_error_trace(skip_path_info(b, e_path));
+
+ /* Check if the user is authorized to find out about the target. */
+ SVN_ERR(check_auth(b, &allowed, t_path, pool));
+ if (!allowed)
+ {
+ if (t_entry->kind == svn_node_dir)
+ SVN_ERR(b->editor->absent_directory(e_path, dir_baton, pool));
+ else
+ SVN_ERR(b->editor->absent_file(e_path, dir_baton, pool));
+ return svn_error_trace(skip_path_info(b, e_path));
+ }
+
+ if (t_entry->kind == svn_node_dir)
+ {
+ if (related)
+ SVN_ERR(b->editor->open_directory(e_path, dir_baton, s_rev, pool,
+ &new_baton));
+ else
+ SVN_ERR(b->editor->add_directory(e_path, dir_baton, NULL,
+ SVN_INVALID_REVNUM, pool,
+ &new_baton));
+
+ SVN_ERR(delta_dirs(b, s_rev, s_path, t_path, new_baton, e_path,
+ info ? info->start_empty : FALSE,
+ wc_depth, requested_depth, pool));
+ return svn_error_trace(b->editor->close_directory(new_baton, pool));
+ }
+ else
+ {
+ if (related)
+ {
+ SVN_ERR(b->editor->open_file(e_path, dir_baton, s_rev, pool,
+ &new_baton));
+ SVN_ERR(delta_files(b, new_baton, s_rev, s_path, t_path,
+ info ? info->lock_token : NULL, pool));
+ }
+ else
+ {
+ svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+ const char *copyfrom_path = NULL;
+ SVN_ERR(add_file_smartly(b, e_path, dir_baton, t_path, &new_baton,
+ &copyfrom_path, &copyfrom_rev, pool));
+ if (! copyfrom_path)
+ /* Send txdelta between empty file (s_path@s_rev doesn't
+ exist) and added file (t_path@t_root). */
+ SVN_ERR(delta_files(b, new_baton, s_rev, s_path, t_path,
+ info ? info->lock_token : NULL, pool));
+ else
+ /* Send txdelta between copied file (copyfrom_path@copyfrom_rev)
+ and added file (tpath@t_root). */
+ SVN_ERR(delta_files(b, new_baton, copyfrom_rev, copyfrom_path,
+ t_path, info ? info->lock_token : NULL, pool));
+ }
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, b->t_root,
+ t_path, TRUE, pool));
+ hex_digest = svn_checksum_to_cstring(checksum, pool);
+ return svn_error_trace(b->editor->close_file(new_baton, hex_digest,
+ pool));
+ }
+}
+
+/* A helper macro for when we have to recurse into subdirectories. */
+#define DEPTH_BELOW_HERE(depth) ((depth) == svn_depth_immediates) ? \
+ svn_depth_empty : (depth)
+
+/* Emit edits within directory DIR_BATON (with corresponding path
+ E_PATH) with the changes from the directory S_REV/S_PATH to the
+ directory B->t_rev/T_PATH. S_PATH may be NULL if the entry does
+ not exist in the source.
+
+ WC_DEPTH is this path's depth as reported by set_path/link_path.
+ REQUESTED_DEPTH is derived from the depth set by
+ svn_repos_begin_report().
+
+ When iterating over this directory's entries, the following tables
+ describe what happens for all possible combinations
+ of WC_DEPTH/REQUESTED_DEPTH (rows represent WC_DEPTH, columns
+ represent REQUESTED_DEPTH):
+
+ Legend:
+ X: ignore this entry (it's either below the requested depth, or
+ if the requested depth is svn_depth_unknown, below the working
+ copy depth)
+ o: handle this entry normally
+ U: handle the entry as if it were a newly added repository path
+ (the client is upgrading to a deeper wc and doesn't currently
+ have this entry, but it should be there after the upgrade, so we
+ need to send the whole thing, not just deltas)
+
+ For files:
+ ______________________________________________________________
+ | req. depth| unknown | empty | files | immediates | infinity |
+ |wc. depth | | | | | |
+ |___________|_________|_______|_______|____________|__________|
+ |empty | X | X | U | U | U |
+ |___________|_________|_______|_______|____________|__________|
+ |files | o | X | o | o | o |
+ |___________|_________|_______|_______|____________|__________|
+ |immediates | o | X | o | o | o |
+ |___________|_________|_______|_______|____________|__________|
+ |infinity | o | X | o | o | o |
+ |___________|_________|_______|_______|____________|__________|
+
+ For directories:
+ ______________________________________________________________
+ | req. depth| unknown | empty | files | immediates | infinity |
+ |wc. depth | | | | | |
+ |___________|_________|_______|_______|____________|__________|
+ |empty | X | X | X | U | U |
+ |___________|_________|_______|_______|____________|__________|
+ |files | X | X | X | U | U |
+ |___________|_________|_______|_______|____________|__________|
+ |immediates | o | X | X | o | o |
+ |___________|_________|_______|_______|____________|__________|
+ |infinity | o | X | X | o | o |
+ |___________|_________|_______|_______|____________|__________|
+
+ These rules are enforced by the is_depth_upgrade() function and by
+ various other checks below.
+*/
+static svn_error_t *
+delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
+ const char *t_path, void *dir_baton, const char *e_path,
+ svn_boolean_t start_empty, svn_depth_t wc_depth,
+ svn_depth_t requested_depth, apr_pool_t *pool)
+{
+ svn_fs_root_t *s_root;
+ apr_hash_t *s_entries = NULL, *t_entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool;
+ const char *name, *s_fullpath, *t_fullpath, *e_fullpath;
+ path_info_t *info;
+
+ /* Compare the property lists. If we're starting empty, pass a NULL
+ source path so that we add all the properties.
+
+ When we support directory locks, we must pass the lock token here. */
+ SVN_ERR(delta_proplists(b, s_rev, start_empty ? NULL : s_path, t_path,
+ NULL, change_dir_prop, dir_baton, pool));
+
+ if (requested_depth > svn_depth_empty
+ || requested_depth == svn_depth_unknown)
+ {
+ /* Get the list of entries in each of source and target. */
+ if (s_path && !start_empty)
+ {
+ SVN_ERR(get_source_root(b, &s_root, s_rev));
+ SVN_ERR(svn_fs_dir_entries(&s_entries, s_root, s_path, pool));
+ }
+ SVN_ERR(svn_fs_dir_entries(&t_entries, b->t_root, t_path, pool));
+
+ /* Iterate over the report information for this directory. */
+ subpool = svn_pool_create(pool);
+
+ while (1)
+ {
+ const svn_fs_dirent_t *s_entry, *t_entry;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(fetch_path_info(b, &name, &info, e_path, subpool));
+ if (!name)
+ break;
+
+ /* Invalid revnum means we should delete, unless this is
+ just an excluded subpath. */
+ if (info
+ && !SVN_IS_VALID_REVNUM(info->rev)
+ && info->depth != svn_depth_exclude)
+ {
+ /* We want to perform deletes before non-replacement adds,
+ for graceful handling of case-only renames on
+ case-insensitive client filesystems. So, if the report
+ item is a delete, remove the entry from the source hash,
+ but don't update the entry yet. */
+ if (s_entries)
+ svn_hash_sets(s_entries, name, NULL);
+ continue;
+ }
+
+ e_fullpath = svn_relpath_join(e_path, name, subpool);
+ t_fullpath = svn_fspath__join(t_path, name, subpool);
+ t_entry = svn_hash_gets(t_entries, name);
+ s_fullpath = s_path ? svn_fspath__join(s_path, name, subpool) : NULL;
+ s_entry = s_entries ?
+ svn_hash_gets(s_entries, name) : NULL;
+
+ /* The only special cases here are
+
+ - When requested_depth is files but the reported path is
+ a directory. This is technically a client error, but we
+ handle it anyway, by skipping the entry.
+
+ - When the reported depth is svn_depth_exclude.
+ */
+ if ((! info || info->depth != svn_depth_exclude)
+ && (requested_depth != svn_depth_files
+ || ((! t_entry || t_entry->kind != svn_node_dir)
+ && (! s_entry || s_entry->kind != svn_node_dir))))
+ SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath,
+ t_entry, dir_baton, e_fullpath, info,
+ info ? info->depth
+ : DEPTH_BELOW_HERE(wc_depth),
+ DEPTH_BELOW_HERE(requested_depth), subpool));
+
+ /* Don't revisit this name in the target or source entries. */
+ svn_hash_sets(t_entries, name, NULL);
+ if (s_entries
+ /* Keep the entry for later process if it is reported as
+ excluded and got deleted in repos. */
+ && (! info || info->depth != svn_depth_exclude || t_entry))
+ svn_hash_sets(s_entries, name, NULL);
+
+ /* pathinfo entries live in their own subpools due to lookahead,
+ so we need to clear each one out as we finish with it. */
+ if (info)
+ svn_pool_destroy(info->pool);
+ }
+
+ /* Remove any deleted entries. Do this before processing the
+ target, for graceful handling of case-only renames. */
+ if (s_entries)
+ {
+ for (hi = apr_hash_first(pool, s_entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const svn_fs_dirent_t *s_entry;
+
+ svn_pool_clear(subpool);
+ s_entry = svn__apr_hash_index_val(hi);
+
+ if (svn_hash_gets(t_entries, s_entry->name) == NULL)
+ {
+ svn_revnum_t deleted_rev;
+
+ if (s_entry->kind == svn_node_file
+ && wc_depth < svn_depth_files)
+ continue;
+
+ if (s_entry->kind == svn_node_dir
+ && (wc_depth < svn_depth_immediates
+ || requested_depth == svn_depth_files))
+ continue;
+
+ /* There is no corresponding target entry, so delete. */
+ e_fullpath = svn_relpath_join(e_path, s_entry->name, subpool);
+ SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root),
+ svn_fspath__join(t_path,
+ s_entry->name,
+ subpool),
+ s_rev, b->t_rev,
+ &deleted_rev, subpool));
+
+ SVN_ERR(b->editor->delete_entry(e_fullpath,
+ deleted_rev,
+ dir_baton, subpool));
+ }
+ }
+ }
+
+ /* Loop over the dirents in the target. */
+ for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi))
+ {
+ const svn_fs_dirent_t *s_entry, *t_entry;
+
+ svn_pool_clear(subpool);
+ t_entry = svn__apr_hash_index_val(hi);
+
+ if (is_depth_upgrade(wc_depth, requested_depth, t_entry->kind))
+ {
+ /* We're making the working copy deeper, pretend the source
+ doesn't exist. */
+ s_entry = NULL;
+ s_fullpath = NULL;
+ }
+ else
+ {
+ if (t_entry->kind == svn_node_file
+ && requested_depth == svn_depth_unknown
+ && wc_depth < svn_depth_files)
+ continue;
+
+ if (t_entry->kind == svn_node_dir
+ && (wc_depth < svn_depth_immediates
+ || requested_depth == svn_depth_files))
+ continue;
+
+ /* Look for an entry with the same name
+ in the source dirents. */
+ s_entry = s_entries ?
+ svn_hash_gets(s_entries, t_entry->name)
+ : NULL;
+ s_fullpath = s_entry ?
+ svn_fspath__join(s_path, t_entry->name, subpool) : NULL;
+ }
+
+ /* Compose the report, editor, and target paths for this entry. */
+ e_fullpath = svn_relpath_join(e_path, t_entry->name, subpool);
+ t_fullpath = svn_fspath__join(t_path, t_entry->name, subpool);
+
+ SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath,
+ t_entry, dir_baton, e_fullpath, NULL,
+ DEPTH_BELOW_HERE(wc_depth),
+ DEPTH_BELOW_HERE(requested_depth),
+ subpool));
+ }
+
+
+ /* Destroy iteration subpool. */
+ svn_pool_destroy(subpool);
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+drive(report_baton_t *b, svn_revnum_t s_rev, path_info_t *info,
+ apr_pool_t *pool)
+{
+ const char *t_anchor, *s_fullpath;
+ svn_boolean_t allowed, info_is_set_path;
+ svn_fs_root_t *s_root;
+ const svn_fs_dirent_t *s_entry, *t_entry;
+ void *root_baton;
+
+ /* Compute the target path corresponding to the working copy anchor,
+ and check its authorization. */
+ t_anchor = *b->s_operand ? svn_fspath__dirname(b->t_path, pool) : b->t_path;
+ SVN_ERR(check_auth(b, &allowed, t_anchor, pool));
+ if (!allowed)
+ return svn_error_create
+ (SVN_ERR_AUTHZ_ROOT_UNREADABLE, NULL,
+ _("Not authorized to open root of edit operation"));
+
+ /* Collect information about the source and target nodes. */
+ s_fullpath = svn_fspath__join(b->fs_base, b->s_operand, pool);
+ SVN_ERR(get_source_root(b, &s_root, s_rev));
+ SVN_ERR(fake_dirent(&s_entry, s_root, s_fullpath, pool));
+ SVN_ERR(fake_dirent(&t_entry, b->t_root, b->t_path, pool));
+
+ /* If the operand is a locally added file or directory, it won't
+ exist in the source, so accept that. */
+ info_is_set_path = (SVN_IS_VALID_REVNUM(info->rev) && !info->link_path);
+ if (info_is_set_path && !s_entry)
+ s_fullpath = NULL;
+
+ /* Check if the target path exists first. */
+ if (!*b->s_operand && !(t_entry))
+ return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL,
+ _("Target path '%s' does not exist"),
+ b->t_path);
+
+ /* If the anchor is the operand, the source and target must be dirs.
+ Check this before opening the root to avoid modifying the wc. */
+ else if (!*b->s_operand && (!s_entry || s_entry->kind != svn_node_dir
+ || t_entry->kind != svn_node_dir))
+ return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, NULL,
+ _("Cannot replace a directory from within"));
+
+ SVN_ERR(b->editor->set_target_revision(b->edit_baton, b->t_rev, pool));
+ SVN_ERR(b->editor->open_root(b->edit_baton, s_rev, pool, &root_baton));
+
+ /* If the anchor is the operand, diff the two directories; otherwise
+ update the operand within the anchor directory. */
+ if (!*b->s_operand)
+ SVN_ERR(delta_dirs(b, s_rev, s_fullpath, b->t_path, root_baton,
+ "", info->start_empty, info->depth, b->requested_depth,
+ pool));
+ else
+ SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, b->t_path,
+ t_entry, root_baton, b->s_operand, info,
+ info->depth, b->requested_depth, pool));
+
+ return svn_error_trace(b->editor->close_directory(root_baton, pool));
+}
+
+/* Initialize the baton fields for editor-driving, and drive the editor. */
+static svn_error_t *
+finish_report(report_baton_t *b, apr_pool_t *pool)
+{
+ path_info_t *info;
+ apr_pool_t *subpool;
+ svn_revnum_t s_rev;
+ int i;
+
+ /* Save our pool to manage the lookahead and fs_root cache with. */
+ b->pool = pool;
+
+ /* Add the end marker. */
+ SVN_ERR(svn_spillbuf__reader_write(b->reader, "-", 1, pool));
+
+ /* Read the first pathinfo from the report and verify that it is a top-level
+ set_path entry. */
+ SVN_ERR(read_path_info(&info, b->reader, pool));
+ if (!info || strcmp(info->path, b->s_operand) != 0
+ || info->link_path || !SVN_IS_VALID_REVNUM(info->rev))
+ return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
+ _("Invalid report for top level of working copy"));
+ s_rev = info->rev;
+
+ /* Initialize the lookahead pathinfo. */
+ subpool = svn_pool_create(pool);
+ SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
+
+ if (b->lookahead && strcmp(b->lookahead->path, b->s_operand) == 0)
+ {
+ /* If the operand of the wc operation is switched or deleted,
+ then info above is just a place-holder, and the only thing we
+ have to do is pass the revision it contains to open_root.
+ The next pathinfo actually describes the target. */
+ if (!*b->s_operand)
+ return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
+ _("Two top-level reports with no target"));
+ /* If the client issued a set-path followed by a delete-path, we need
+ to respect the depth set by the initial set-path. */
+ if (! SVN_IS_VALID_REVNUM(b->lookahead->rev))
+ {
+ b->lookahead->depth = info->depth;
+ }
+ info = b->lookahead;
+ SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool));
+ }
+
+ /* Open the target root and initialize the source root cache. */
+ SVN_ERR(svn_fs_revision_root(&b->t_root, b->repos->fs, b->t_rev, pool));
+ for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++)
+ b->s_roots[i] = NULL;
+
+ {
+ svn_error_t *err = svn_error_trace(drive(b, s_rev, info, pool));
+
+ if (err == SVN_NO_ERROR)
+ return svn_error_trace(b->editor->close_edit(b->edit_baton, pool));
+
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ b->editor->abort_edit(b->edit_baton,
+ pool)));
+ }
+}
+
+/* --- COLLECTING THE REPORT INFORMATION --- */
+
+/* Record a report operation into the spill buffer. Return an error
+ if DEPTH is svn_depth_unknown. */
+static svn_error_t *
+write_path_info(report_baton_t *b, const char *path, const char *lpath,
+ svn_revnum_t rev, svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token, apr_pool_t *pool)
+{
+ const char *lrep, *rrep, *drep, *ltrep, *rep;
+
+ /* Munge the path to be anchor-relative, so that we can use edit paths
+ as report paths. */
+ path = svn_relpath_join(b->s_operand, path, pool);
+
+ lrep = lpath ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s",
+ strlen(lpath), lpath) : "-";
+ rrep = (SVN_IS_VALID_REVNUM(rev)) ?
+ apr_psprintf(pool, "+%ld:", rev) : "-";
+
+ if (depth == svn_depth_exclude)
+ drep = "+X";
+ else if (depth == svn_depth_empty)
+ drep = "+E";
+ else if (depth == svn_depth_files)
+ drep = "+F";
+ else if (depth == svn_depth_immediates)
+ drep = "+M";
+ else if (depth == svn_depth_infinity)
+ drep = "-";
+ else
+ return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("Unsupported report depth '%s'"),
+ svn_depth_to_word(depth));
+
+ ltrep = lock_token ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s",
+ strlen(lock_token), lock_token) : "-";
+ rep = apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s%s%s%s%c%s",
+ strlen(path), path, lrep, rrep, drep,
+ start_empty ? '+' : '-', ltrep);
+ return svn_error_trace(
+ svn_spillbuf__reader_write(b->reader, rep, strlen(rep), pool));
+}
+
+svn_error_t *
+svn_repos_set_path3(void *baton, const char *path, svn_revnum_t rev,
+ svn_depth_t depth, svn_boolean_t start_empty,
+ const char *lock_token, apr_pool_t *pool)
+{
+ return svn_error_trace(
+ write_path_info(baton, path, NULL, rev, depth, start_empty,
+ lock_token, pool));
+}
+
+svn_error_t *
+svn_repos_link_path3(void *baton, const char *path, const char *link_path,
+ svn_revnum_t rev, svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token, apr_pool_t *pool)
+{
+ if (depth == svn_depth_exclude)
+ return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("Depth 'exclude' not supported for link"));
+
+ return svn_error_trace(
+ write_path_info(baton, path, link_path, rev, depth,
+ start_empty, lock_token, pool));
+}
+
+svn_error_t *
+svn_repos_delete_path(void *baton, const char *path, apr_pool_t *pool)
+{
+ /* We pass svn_depth_infinity because deletion of a path always
+ deletes everything underneath it. */
+ return svn_error_trace(
+ write_path_info(baton, path, NULL, SVN_INVALID_REVNUM,
+ svn_depth_infinity, FALSE, NULL, pool));
+}
+
+svn_error_t *
+svn_repos_finish_report(void *baton, apr_pool_t *pool)
+{
+ report_baton_t *b = baton;
+
+ return svn_error_trace(finish_report(b, pool));
+}
+
+svn_error_t *
+svn_repos_abort_report(void *baton, apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/* --- BEGINNING THE REPORT --- */
+
+
+svn_error_t *
+svn_repos_begin_report3(void **report_baton,
+ svn_revnum_t revnum,
+ svn_repos_t *repos,
+ const char *fs_base,
+ const char *s_operand,
+ const char *switch_path,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_size_t zero_copy_limit,
+ apr_pool_t *pool)
+{
+ report_baton_t *b;
+ const char *uuid;
+
+ if (depth == svn_depth_exclude)
+ return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("Request depth 'exclude' not supported"));
+
+ SVN_ERR(svn_fs_get_uuid(repos->fs, &uuid, pool));
+
+ /* Build a reporter baton. Copy strings in case the caller doesn't
+ keep track of them. */
+ b = apr_palloc(pool, sizeof(*b));
+ b->repos = repos;
+ b->fs_base = svn_fspath__canonicalize(fs_base, pool);
+ b->s_operand = apr_pstrdup(pool, s_operand);
+ b->t_rev = revnum;
+ b->t_path = switch_path ? svn_fspath__canonicalize(switch_path, pool)
+ : svn_fspath__join(b->fs_base, s_operand, pool);
+ b->text_deltas = text_deltas;
+ b->zero_copy_limit = zero_copy_limit;
+ b->requested_depth = depth;
+ b->ignore_ancestry = ignore_ancestry;
+ b->send_copyfrom_args = send_copyfrom_args;
+ b->is_switch = (switch_path != NULL);
+ b->editor = editor;
+ b->edit_baton = edit_baton;
+ b->authz_read_func = authz_read_func;
+ b->authz_read_baton = authz_read_baton;
+ b->revision_infos = apr_hash_make(pool);
+ b->pool = pool;
+ b->reader = svn_spillbuf__reader_create(1000 /* blocksize */,
+ 1000000 /* maxsize */,
+ pool);
+ b->repos_uuid = svn_string_create(uuid, pool);
+
+ /* Hand reporter back to client. */
+ *report_baton = b;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/repos.c b/contrib/subversion/subversion/libsvn_repos/repos.c
new file mode 100644
index 0000000..9f10c06
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/repos.c
@@ -0,0 +1,2132 @@
+/* repos.c : repository creation; shared and exclusive repository locking
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_time.h"
+#include "svn_fs.h"
+#include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */
+#include "svn_repos.h"
+#include "svn_hash.h"
+#include "svn_version.h"
+#include "svn_config.h"
+
+#include "private/svn_repos_private.h"
+#include "private/svn_subr_private.h"
+#include "svn_private_config.h" /* for SVN_TEMPLATE_ROOT_DIR */
+
+#include "repos.h"
+
+/* Used to terminate lines in large multi-line string literals. */
+#define NL APR_EOL_STR
+
+
+/* Path accessor functions. */
+
+
+const char *
+svn_repos_path(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return apr_pstrdup(pool, repos->path);
+}
+
+
+const char *
+svn_repos_db_env(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return apr_pstrdup(pool, repos->db_path);
+}
+
+
+const char *
+svn_repos_conf_dir(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return apr_pstrdup(pool, repos->conf_path);
+}
+
+
+const char *
+svn_repos_svnserve_conf(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->conf_path, SVN_REPOS__CONF_SVNSERVE_CONF, pool);
+}
+
+
+const char *
+svn_repos_lock_dir(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return apr_pstrdup(pool, repos->lock_path);
+}
+
+
+const char *
+svn_repos_db_lockfile(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->lock_path, SVN_REPOS__DB_LOCKFILE, pool);
+}
+
+
+const char *
+svn_repos_db_logs_lockfile(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->lock_path, SVN_REPOS__DB_LOGS_LOCKFILE, pool);
+}
+
+const char *
+svn_repos_hook_dir(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return apr_pstrdup(pool, repos->hook_path);
+}
+
+
+const char *
+svn_repos_start_commit_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_START_COMMIT, pool);
+}
+
+
+const char *
+svn_repos_pre_commit_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_COMMIT, pool);
+}
+
+
+const char *
+svn_repos_pre_lock_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_LOCK, pool);
+}
+
+
+const char *
+svn_repos_pre_unlock_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_UNLOCK, pool);
+}
+
+const char *
+svn_repos_post_lock_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_LOCK, pool);
+}
+
+
+const char *
+svn_repos_post_unlock_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_UNLOCK, pool);
+}
+
+
+const char *
+svn_repos_post_commit_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_COMMIT, pool);
+}
+
+
+const char *
+svn_repos_pre_revprop_change_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_PRE_REVPROP_CHANGE,
+ pool);
+}
+
+
+const char *
+svn_repos_post_revprop_change_hook(svn_repos_t *repos, apr_pool_t *pool)
+{
+ return svn_dirent_join(repos->hook_path, SVN_REPOS__HOOK_POST_REVPROP_CHANGE,
+ pool);
+}
+
+static svn_error_t *
+create_repos_dir(const char *path, apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
+ if (err && (APR_STATUS_IS_EEXIST(err->apr_err)))
+ {
+ svn_boolean_t is_empty;
+
+ svn_error_clear(err);
+
+ SVN_ERR(svn_io_dir_empty(&is_empty, path, pool));
+
+ if (is_empty)
+ err = NULL;
+ else
+ err = svn_error_createf(SVN_ERR_DIR_NOT_EMPTY, 0,
+ _("'%s' exists and is non-empty"),
+ svn_dirent_local_style(path, pool));
+ }
+
+ return svn_error_trace(err);
+}
+
+static const char * bdb_lock_file_contents =
+ "DB lock file, representing locks on the versioned filesystem." NL
+ "" NL
+ "All accessors -- both readers and writers -- of the repository's" NL
+ "Berkeley DB environment take out shared locks on this file, and" NL
+ "each accessor removes its lock when done. If and when the DB" NL
+ "recovery procedure is run, the recovery code takes out an" NL
+ "exclusive lock on this file, so we can be sure no one else is" NL
+ "using the DB during the recovery." NL
+ "" NL
+ "You should never have to edit or remove this file." NL;
+
+static const char * bdb_logs_lock_file_contents =
+ "DB logs lock file, representing locks on the versioned filesystem logs." NL
+ "" NL
+ "All log manipulators of the repository's Berkeley DB environment" NL
+ "take out exclusive locks on this file to ensure that only one" NL
+ "accessor manipulates the logs at a time." NL
+ "" NL
+ "You should never have to edit or remove this file." NL;
+
+static const char * pre12_compat_unneeded_file_contents =
+ "This file is not used by Subversion 1.3.x or later." NL
+ "However, its existence is required for compatibility with" NL
+ "Subversion 1.2.x or earlier." NL;
+
+/* Create the DB logs lockfile. */
+static svn_error_t *
+create_db_logs_lock(svn_repos_t *repos, apr_pool_t *pool) {
+ const char *contents;
+ const char *lockfile_path;
+
+ lockfile_path = svn_repos_db_logs_lockfile(repos, pool);
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ contents = bdb_logs_lock_file_contents;
+ else
+ contents = pre12_compat_unneeded_file_contents;
+
+ SVN_ERR_W(svn_io_file_create(lockfile_path, contents, pool),
+ _("Creating db logs lock file"));
+
+ return SVN_NO_ERROR;
+}
+
+/* Create the DB lockfile. */
+static svn_error_t *
+create_db_lock(svn_repos_t *repos, apr_pool_t *pool) {
+ const char *contents;
+ const char *lockfile_path;
+
+ lockfile_path = svn_repos_db_lockfile(repos, pool);
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ contents = bdb_lock_file_contents;
+ else
+ contents = pre12_compat_unneeded_file_contents;
+
+ SVN_ERR_W(svn_io_file_create(lockfile_path, contents, pool),
+ _("Creating db lock file"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+create_locks(svn_repos_t *repos, apr_pool_t *pool)
+{
+ /* Create the locks directory. */
+ SVN_ERR_W(create_repos_dir(repos->lock_path, pool),
+ _("Creating lock dir"));
+
+ SVN_ERR(create_db_lock(repos, pool));
+ return create_db_logs_lock(repos, pool);
+}
+
+
+#define HOOKS_ENVIRONMENT_TEXT \
+ "# The hook program typically does not inherit the environment of" NL \
+ "# its parent process. For example, a common problem is for the" NL \
+ "# PATH environment variable to not be set to its usual value, so" NL \
+ "# that subprograms fail to launch unless invoked via absolute path." NL \
+ "# If you're having unexpected problems with a hook program, the" NL \
+ "# culprit may be unusual (or missing) environment variables." NL
+
+#define PREWRITTEN_HOOKS_TEXT \
+ "# For more examples and pre-written hooks, see those in" NL \
+ "# the Subversion repository at" NL \
+ "# http://svn.apache.org/repos/asf/subversion/trunk/tools/hook-scripts/ and" NL \
+ "# http://svn.apache.org/repos/asf/subversion/trunk/contrib/hook-scripts/" NL
+
+
+static svn_error_t *
+create_hooks(svn_repos_t *repos, apr_pool_t *pool)
+{
+ const char *this_path, *contents;
+
+ /* Create the hook directory. */
+ SVN_ERR_W(create_repos_dir(repos->hook_path, pool),
+ _("Creating hook directory"));
+
+ /*** Write a default template for each standard hook file. */
+
+ /* Start-commit hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_start_commit_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_START_COMMIT
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# START-COMMIT HOOK" NL
+"#" NL
+"# The start-commit hook is invoked immediately after a Subversion txn is" NL
+"# created and populated with initial revprops in the process of doing a" NL
+"# commit. Subversion runs this hook by invoking a program (script, " NL
+"# executable, binary, etc.) named '"SCRIPT_NAME"' (for which this file" NL
+"# is a template) with the following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] USER (the authenticated user attempting to commit)" NL
+"# [3] CAPABILITIES (a colon-separated list of capabilities reported" NL
+"# by the client; see note below)" NL
+"# [4] TXN-NAME (the name of the commit txn just created)" NL
+"#" NL
+"# Note: The CAPABILITIES parameter is new in Subversion 1.5, and 1.5" NL
+"# clients will typically report at least the \"" \
+ SVN_RA_CAPABILITY_MERGEINFO "\" capability." NL
+"# If there are other capabilities, then the list is colon-separated," NL
+"# e.g.: \"" SVN_RA_CAPABILITY_MERGEINFO ":some-other-capability\" " \
+ "(the order is undefined)." NL
+"#" NL
+"# Note: The TXN-NAME parameter is new in Subversion 1.8. Prior to version" NL
+"# 1.8, the start-commit hook was invoked before the commit txn was even" NL
+"# created, so the ability to inspect the commit txn and its metadata from" NL
+"# within the start-commit hook was not possible." NL
+"# " NL
+"# The list is self-reported by the client. Therefore, you should not" NL
+"# make security assumptions based on the capabilities list, nor should" NL
+"# you assume that clients reliably report every capability they have." NL
+"#" NL
+"# The working directory for this hook program's invocation is undefined," NL
+"# so the program should set one explicitly if it cares." NL
+"#" NL
+"# If the hook program exits with success, the commit continues; but" NL
+"# if it exits with failure (non-zero), the commit is stopped before" NL
+"# a Subversion txn is created, and STDERR is returned to the client." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"# " NL
+HOOKS_ENVIRONMENT_TEXT
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
+PREWRITTEN_HOOKS_TEXT
+"" NL
+"" NL
+"REPOS=\"$1\"" NL
+"USER=\"$2\"" NL
+"" NL
+"commit-allower.pl --repository \"$REPOS\" --user \"$USER\" || exit 1" NL
+"special-auth-check.py --user \"$USER\" --auth-level 3 || exit 1" NL
+"" NL
+"# All checks passed, so allow the commit." NL
+"exit 0" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ _("Creating start-commit hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end start-commit hook */
+
+ /* Pre-commit hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_pre_commit_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_COMMIT
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# PRE-COMMIT HOOK" NL
+"#" NL
+"# The pre-commit hook is invoked before a Subversion txn is" NL
+"# committed. Subversion runs this hook by invoking a program" NL
+"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which" NL
+"# this file is a template), with the following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] TXN-NAME (the name of the txn about to be committed)" NL
+"#" NL
+"# [STDIN] LOCK-TOKENS ** the lock tokens are passed via STDIN." NL
+"#" NL
+"# If STDIN contains the line \"LOCK-TOKENS:\\n\" (the \"\\n\" denotes a" NL
+"# single newline), the lines following it are the lock tokens for" NL
+"# this commit. The end of the list is marked by a line containing" NL
+"# only a newline character." NL
+"#" NL
+"# Each lock token line consists of a URI-escaped path, followed" NL
+"# by the separator character '|', followed by the lock token string," NL
+"# followed by a newline." NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# If the hook program exits with success, the txn is committed; but" NL
+"# if it exits with failure (non-zero), the txn is aborted, no commit" NL
+"# takes place, and STDERR is returned to the client. The hook" NL
+"# program can use the 'svnlook' utility to help it examine the txn." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# *** NOTE: THE HOOK PROGRAM MUST NOT MODIFY THE TXN, EXCEPT ***" NL
+"# *** FOR REVISION PROPERTIES (like svn:log or svn:author). ***" NL
+"#" NL
+"# This is why we recommend using the read-only 'svnlook' utility." NL
+"# In the future, Subversion may enforce the rule that pre-commit" NL
+"# hooks should not modify the versioned data in txns, or else come" NL
+"# up with a mechanism to make it safe to do so (by informing the" NL
+"# committing client of the changes). However, right now neither" NL
+"# mechanism is implemented, so hook writers just have to be careful." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"#" NL
+HOOKS_ENVIRONMENT_TEXT
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
+PREWRITTEN_HOOKS_TEXT
+"" NL
+"" NL
+"REPOS=\"$1\"" NL
+"TXN=\"$2\"" NL
+"" NL
+"# Make sure that the log message contains some text." NL
+"SVNLOOK=" SVN_BINDIR "/svnlook" NL
+"$SVNLOOK log -t \"$TXN\" \"$REPOS\" | \\" NL
+" grep \"[a-zA-Z0-9]\" > /dev/null || exit 1" NL
+"" NL
+"# Check that the author of this commit has the rights to perform" NL
+"# the commit on the files and directories being modified." NL
+"commit-access-control.pl \"$REPOS\" \"$TXN\" commit-access-control.cfg || exit 1"
+ NL
+"" NL
+"# All checks passed, so allow the commit." NL
+"exit 0" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ _("Creating pre-commit hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end pre-commit hook */
+
+
+ /* Pre-revprop-change hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_pre_revprop_change_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_REVPROP_CHANGE
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# PRE-REVPROP-CHANGE HOOK" NL
+"#" NL
+"# The pre-revprop-change hook is invoked before a revision property" NL
+"# is added, modified or deleted. Subversion runs this hook by invoking" NL
+"# a program (script, executable, binary, etc.) named '"SCRIPT_NAME"'" NL
+"# (for which this file is a template), with the following ordered" NL
+"# arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] REV (the revision being tweaked)" NL
+"# [3] USER (the username of the person tweaking the property)" NL
+"# [4] PROPNAME (the property being set on the revision)" NL
+"# [5] ACTION (the property is being 'A'dded, 'M'odified, or 'D'eleted)"
+ NL
+"#" NL
+"# [STDIN] PROPVAL ** the new property value is passed via STDIN." NL
+"#" NL
+"# If the hook program exits with success, the propchange happens; but" NL
+"# if it exits with failure (non-zero), the propchange doesn't happen." NL
+"# The hook program can use the 'svnlook' utility to examine the " NL
+"# existing value of the revision property." NL
+"#" NL
+"# WARNING: unlike other hooks, this hook MUST exist for revision" NL
+"# properties to be changed. If the hook does not exist, Subversion " NL
+"# will behave as if the hook were present, but failed. The reason" NL
+"# for this is that revision properties are UNVERSIONED, meaning that" NL
+"# a successful propchange is destructive; the old value is gone" NL
+"# forever. We recommend the hook back up the old value somewhere." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"#" NL
+HOOKS_ENVIRONMENT_TEXT
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
+PREWRITTEN_HOOKS_TEXT
+"" NL
+"" NL
+"REPOS=\"$1\"" NL
+"REV=\"$2\"" NL
+"USER=\"$3\"" NL
+"PROPNAME=\"$4\"" NL
+"ACTION=\"$5\"" NL
+"" NL
+"if [ \"$ACTION\" = \"M\" -a \"$PROPNAME\" = \"svn:log\" ]; then exit 0; fi" NL
+"" NL
+"echo \"Changing revision properties other than svn:log is prohibited\" >&2" NL
+"exit 1" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ _("Creating pre-revprop-change hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end pre-revprop-change hook */
+
+
+ /* Pre-lock hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_pre_lock_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_LOCK
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# PRE-LOCK HOOK" NL
+"#" NL
+"# The pre-lock hook is invoked before an exclusive lock is" NL
+"# created. Subversion runs this hook by invoking a program " NL
+"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which" NL
+"# this file is a template), with the following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] PATH (the path in the repository about to be locked)" NL
+"# [3] USER (the user creating the lock)" NL
+"# [4] COMMENT (the comment of the lock)" NL
+"# [5] STEAL-LOCK (1 if the user is trying to steal the lock, else 0)" NL
+"#" NL
+"# If the hook program outputs anything on stdout, the output string will" NL
+"# be used as the lock token for this lock operation. If you choose to use" NL
+"# this feature, you must guarantee the tokens generated are unique across" NL
+"# the repository each time." NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# If the hook program exits with success, the lock is created; but" NL
+"# if it exits with failure (non-zero), the lock action is aborted" NL
+"# and STDERR is returned to the client." NL
+"" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"#" NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
+"" NL
+"REPOS=\"$1\"" NL
+"PATH=\"$2\"" NL
+"USER=\"$3\"" NL
+"COMMENT=\"$4\"" NL
+"STEAL=\"$5\"" NL
+"" NL
+"# If a lock exists and is owned by a different person, don't allow it" NL
+"# to be stolen (e.g., with 'svn lock --force ...')." NL
+"" NL
+"# (Maybe this script could send email to the lock owner?)" NL
+"SVNLOOK=" SVN_BINDIR "/svnlook" NL
+"GREP=/bin/grep" NL
+"SED=/bin/sed" NL
+"" NL
+"LOCK_OWNER=`$SVNLOOK lock \"$REPOS\" \"$PATH\" | \\" NL
+" $GREP '^Owner: ' | $SED 's/Owner: //'`" NL
+"" NL
+"# If we get no result from svnlook, there's no lock, allow the lock to" NL
+"# happen:" NL
+"if [ \"$LOCK_OWNER\" = \"\" ]; then" NL
+" exit 0" NL
+"fi" NL
+"" NL
+"# If the person locking matches the lock's owner, allow the lock to" NL
+"# happen:" NL
+"if [ \"$LOCK_OWNER\" = \"$USER\" ]; then" NL
+" exit 0" NL
+"fi" NL
+"" NL
+"# Otherwise, we've got an owner mismatch, so return failure:" NL
+"echo \"Error: $PATH already locked by ${LOCK_OWNER}.\" 1>&2" NL
+"exit 1" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ "Creating pre-lock hook");
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end pre-lock hook */
+
+
+ /* Pre-unlock hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_pre_unlock_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_PRE_UNLOCK
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# PRE-UNLOCK HOOK" NL
+"#" NL
+"# The pre-unlock hook is invoked before an exclusive lock is" NL
+"# destroyed. Subversion runs this hook by invoking a program " NL
+"# (script, executable, binary, etc.) named '"SCRIPT_NAME"' (for which" NL
+"# this file is a template), with the following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] PATH (the path in the repository about to be unlocked)" NL
+"# [3] USER (the user destroying the lock)" NL
+"# [4] TOKEN (the lock token to be destroyed)" NL
+"# [5] BREAK-UNLOCK (1 if the user is breaking the lock, else 0)" NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# If the hook program exits with success, the lock is destroyed; but" NL
+"# if it exits with failure (non-zero), the unlock action is aborted" NL
+"# and STDERR is returned to the client." NL
+"" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"#" NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
+"" NL
+"REPOS=\"$1\"" NL
+"PATH=\"$2\"" NL
+"USER=\"$3\"" NL
+"TOKEN=\"$4\"" NL
+"BREAK=\"$5\"" NL
+"" NL
+"# If a lock is owned by a different person, don't allow it be broken." NL
+"# (Maybe this script could send email to the lock owner?)" NL
+"" NL
+"SVNLOOK=" SVN_BINDIR "/svnlook" NL
+"GREP=/bin/grep" NL
+"SED=/bin/sed" NL
+"" NL
+"LOCK_OWNER=`$SVNLOOK lock \"$REPOS\" \"$PATH\" | \\" NL
+" $GREP '^Owner: ' | $SED 's/Owner: //'`" NL
+"" NL
+"# If we get no result from svnlook, there's no lock, return success:" NL
+"if [ \"$LOCK_OWNER\" = \"\" ]; then" NL
+" exit 0" NL
+"fi" NL
+"" NL
+"# If the person unlocking matches the lock's owner, return success:" NL
+"if [ \"$LOCK_OWNER\" = \"$USER\" ]; then" NL
+" exit 0" NL
+"fi" NL
+"" NL
+"# Otherwise, we've got an owner mismatch, so return failure:" NL
+"echo \"Error: $PATH locked by ${LOCK_OWNER}.\" 1>&2" NL
+"exit 1" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ "Creating pre-unlock hook");
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end pre-unlock hook */
+
+
+
+ /* Post-commit hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_post_commit_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_POST_COMMIT
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# POST-COMMIT HOOK" NL
+"#" NL
+"# The post-commit hook is invoked after a commit. Subversion runs" NL
+"# this hook by invoking a program (script, executable, binary, etc.)" NL
+"# named '"SCRIPT_NAME"' (for which this file is a template) with the " NL
+"# following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] REV (the number of the revision just committed)" NL
+"# [3] TXN-NAME (the name of the transaction that has become REV)" NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# Because the commit has already completed and cannot be undone," NL
+"# the exit code of the hook program is ignored. The hook program" NL
+"# can use the 'svnlook' utility to help it examine the" NL
+"# newly-committed tree." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"# " NL
+HOOKS_ENVIRONMENT_TEXT
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
+PREWRITTEN_HOOKS_TEXT
+"" NL
+"" NL
+"REPOS=\"$1\"" NL
+"REV=\"$2\"" NL
+"TXN_NAME=\"$3\"" NL
+ NL
+"mailer.py commit \"$REPOS\" \"$REV\" /path/to/mailer.conf" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ _("Creating post-commit hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end post-commit hook */
+
+
+ /* Post-lock hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_post_lock_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_POST_LOCK
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# POST-LOCK HOOK" NL
+"#" NL
+"# The post-lock hook is run after a path is locked. Subversion runs" NL
+"# this hook by invoking a program (script, executable, binary, etc.)" NL
+"# named '"SCRIPT_NAME"' (for which this file is a template) with the " NL
+"# following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] USER (the user who created the lock)" NL
+"#" NL
+"# The paths that were just locked are passed to the hook via STDIN (as" NL
+"# of Subversion 1.2, only one path is passed per invocation, but the" NL
+"# plan is to pass all locked paths at once, so the hook program" NL
+"# should be written accordingly)." NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# Because the lock has already been created and cannot be undone," NL
+"# the exit code of the hook program is ignored. The hook program" NL
+"# can use the 'svnlook' utility to help it examine the" NL
+"# newly-created lock." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
+"" NL
+"REPOS=\"$1\"" NL
+"USER=\"$2\"" NL
+"" NL
+"# Send email to interested parties, let them know a lock was created:" NL
+"mailer.py lock \"$REPOS\" \"$USER\" /path/to/mailer.conf" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ "Creating post-lock hook");
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end post-lock hook */
+
+
+ /* Post-unlock hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_post_unlock_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_POST_UNLOCK
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# POST-UNLOCK HOOK" NL
+"#" NL
+"# The post-unlock hook runs after a path is unlocked. Subversion runs" NL
+"# this hook by invoking a program (script, executable, binary, etc.)" NL
+"# named '"SCRIPT_NAME"' (for which this file is a template) with the " NL
+"# following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] USER (the user who destroyed the lock)" NL
+"#" NL
+"# The paths that were just unlocked are passed to the hook via STDIN" NL
+"# (as of Subversion 1.2, only one path is passed per invocation, but" NL
+"# the plan is to pass all unlocked paths at once, so the hook program" NL
+"# should be written accordingly)." NL
+"#" NL
+"# The default working directory for the invocation is undefined, so" NL
+"# the program should set one explicitly if it cares." NL
+"#" NL
+"# Because the lock has already been destroyed and cannot be undone," NL
+"# the exit code of the hook program is ignored." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter:" NL
+"" NL
+"REPOS=\"$1\"" NL
+"USER=\"$2\"" NL
+"" NL
+"# Send email to interested parties, let them know a lock was removed:" NL
+"mailer.py unlock \"$REPOS\" \"$USER\" /path/to/mailer.conf" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ "Creating post-unlock hook");
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end post-unlock hook */
+
+
+ /* Post-revprop-change hook. */
+ {
+ this_path = apr_psprintf(pool, "%s%s",
+ svn_repos_post_revprop_change_hook(repos, pool),
+ SVN_REPOS__HOOK_DESC_EXT);
+
+#define SCRIPT_NAME SVN_REPOS__HOOK_POST_REVPROP_CHANGE
+
+ contents =
+"#!/bin/sh" NL
+"" NL
+"# POST-REVPROP-CHANGE HOOK" NL
+"#" NL
+"# The post-revprop-change hook is invoked after a revision property" NL
+"# has been added, modified or deleted. Subversion runs this hook by" NL
+"# invoking a program (script, executable, binary, etc.) named" NL
+"# '"SCRIPT_NAME"' (for which this file is a template), with the" NL
+"# following ordered arguments:" NL
+"#" NL
+"# [1] REPOS-PATH (the path to this repository)" NL
+"# [2] REV (the revision that was tweaked)" NL
+"# [3] USER (the username of the person tweaking the property)" NL
+"# [4] PROPNAME (the property that was changed)" NL
+"# [5] ACTION (the property was 'A'dded, 'M'odified, or 'D'eleted)" NL
+"#" NL
+"# [STDIN] PROPVAL ** the old property value is passed via STDIN." NL
+"#" NL
+"# Because the propchange has already completed and cannot be undone," NL
+"# the exit code of the hook program is ignored. The hook program" NL
+"# can use the 'svnlook' utility to help it examine the" NL
+"# new property value." NL
+"#" NL
+"# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'" NL
+"# invoke other programs to do the real work, though it may do the" NL
+"# work itself too." NL
+"#" NL
+"# Note that '"SCRIPT_NAME"' must be executable by the user(s) who will" NL
+"# invoke it (typically the user httpd runs as), and that user must" NL
+"# have filesystem-level permission to access the repository." NL
+"#" NL
+"# On a Windows system, you should name the hook program" NL
+"# '"SCRIPT_NAME".bat' or '"SCRIPT_NAME".exe'," NL
+"# but the basic idea is the same." NL
+"# " NL
+HOOKS_ENVIRONMENT_TEXT
+"# " NL
+"# Here is an example hook script, for a Unix /bin/sh interpreter." NL
+PREWRITTEN_HOOKS_TEXT
+"" NL
+"" NL
+"REPOS=\"$1\"" NL
+"REV=\"$2\"" NL
+"USER=\"$3\"" NL
+"PROPNAME=\"$4\"" NL
+"ACTION=\"$5\"" NL
+"" NL
+"mailer.py propchange2 \"$REPOS\" \"$REV\" \"$USER\" \"$PROPNAME\" "
+"\"$ACTION\" /path/to/mailer.conf" NL;
+
+#undef SCRIPT_NAME
+
+ SVN_ERR_W(svn_io_file_create(this_path, contents, pool),
+ _("Creating post-revprop-change hook"));
+
+ SVN_ERR(svn_io_set_file_executable(this_path, TRUE, FALSE, pool));
+ } /* end post-revprop-change hook */
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+create_conf(svn_repos_t *repos, apr_pool_t *pool)
+{
+ SVN_ERR_W(create_repos_dir(repos->conf_path, pool),
+ _("Creating conf directory"));
+
+ /* Write a default template for svnserve.conf. */
+ {
+ static const char * const svnserve_conf_contents =
+"### This file controls the configuration of the svnserve daemon, if you" NL
+"### use it to allow access to this repository. (If you only allow" NL
+"### access through http: and/or file: URLs, then this file is" NL
+"### irrelevant.)" NL
+"" NL
+"### Visit http://subversion.apache.org/ for more information." NL
+"" NL
+"[general]" NL
+"### The anon-access and auth-access options control access to the" NL
+"### repository for unauthenticated (a.k.a. anonymous) users and" NL
+"### authenticated users, respectively." NL
+"### Valid values are \"write\", \"read\", and \"none\"." NL
+"### Setting the value to \"none\" prohibits both reading and writing;" NL
+"### \"read\" allows read-only access, and \"write\" allows complete " NL
+"### read/write access to the repository." NL
+"### The sample settings below are the defaults and specify that anonymous" NL
+"### users have read-only access to the repository, while authenticated" NL
+"### users have read and write access to the repository." NL
+"# anon-access = read" NL
+"# auth-access = write" NL
+"### The password-db option controls the location of the password" NL
+"### database file. Unless you specify a path starting with a /," NL
+"### the file's location is relative to the directory containing" NL
+"### this configuration file." NL
+"### If SASL is enabled (see below), this file will NOT be used." NL
+"### Uncomment the line below to use the default password file." NL
+"# password-db = passwd" NL
+"### The authz-db option controls the location of the authorization" NL
+"### rules for path-based access control. Unless you specify a path" NL
+"### starting with a /, the file's location is relative to the" NL
+"### directory containing this file. The specified path may be a" NL
+"### repository relative URL (^/) or an absolute file:// URL to a text" NL
+"### file in a Subversion repository. If you don't specify an authz-db," NL
+"### no path-based access control is done." NL
+"### Uncomment the line below to use the default authorization file." NL
+"# authz-db = " SVN_REPOS__CONF_AUTHZ NL
+"### The groups-db option controls the location of the groups file." NL
+"### Unless you specify a path starting with a /, the file's location is" NL
+"### relative to the directory containing this file. The specified path" NL
+"### may be a repository relative URL (^/) or an absolute file:// URL to a" NL
+"### text file in a Subversion repository." NL
+"# groups-db = " SVN_REPOS__CONF_GROUPS NL
+"### This option specifies the authentication realm of the repository." NL
+"### If two repositories have the same authentication realm, they should" NL
+"### have the same password database, and vice versa. The default realm" NL
+"### is repository's uuid." NL
+"# realm = My First Repository" NL
+"### The force-username-case option causes svnserve to case-normalize" NL
+"### usernames before comparing them against the authorization rules in the" NL
+"### authz-db file configured above. Valid values are \"upper\" (to upper-" NL
+"### case the usernames), \"lower\" (to lowercase the usernames), and" NL
+"### \"none\" (to compare usernames as-is without case conversion, which" NL
+"### is the default behavior)." NL
+"# force-username-case = none" NL
+"### The hooks-env options specifies a path to the hook script environment " NL
+"### configuration file. This option overrides the per-repository default" NL
+"### and can be used to configure the hook script environment for multiple " NL
+"### repositories in a single file, if an absolute path is specified." NL
+"### Unless you specify an absolute path, the file's location is relative" NL
+"### to the directory containing this file." NL
+"# hooks-env = " SVN_REPOS__CONF_HOOKS_ENV NL
+"" NL
+"[sasl]" NL
+"### This option specifies whether you want to use the Cyrus SASL" NL
+"### library for authentication. Default is false." NL
+"### This section will be ignored if svnserve is not built with Cyrus" NL
+"### SASL support; to check, run 'svnserve --version' and look for a line" NL
+"### reading 'Cyrus SASL authentication is available.'" NL
+"# use-sasl = true" NL
+"### These options specify the desired strength of the security layer" NL
+"### that you want SASL to provide. 0 means no encryption, 1 means" NL
+"### integrity-checking only, values larger than 1 are correlated" NL
+"### to the effective key length for encryption (e.g. 128 means 128-bit" NL
+"### encryption). The values below are the defaults." NL
+"# min-encryption = 0" NL
+"# max-encryption = 256" NL;
+
+ SVN_ERR_W(svn_io_file_create(svn_repos_svnserve_conf(repos, pool),
+ svnserve_conf_contents, pool),
+ _("Creating svnserve.conf file"));
+ }
+
+ {
+ static const char * const passwd_contents =
+"### This file is an example password file for svnserve." NL
+"### Its format is similar to that of svnserve.conf. As shown in the" NL
+"### example below it contains one section labelled [users]." NL
+"### The name and password for each user follow, one account per line." NL
+"" NL
+"[users]" NL
+"# harry = harryssecret" NL
+"# sally = sallyssecret" NL;
+
+ SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_PASSWD,
+ pool),
+ passwd_contents, pool),
+ _("Creating passwd file"));
+ }
+
+ {
+ static const char * const authz_contents =
+"### This file is an example authorization file for svnserve." NL
+"### Its format is identical to that of mod_authz_svn authorization" NL
+"### files." NL
+"### As shown below each section defines authorizations for the path and" NL
+"### (optional) repository specified by the section name." NL
+"### The authorizations follow. An authorization line can refer to:" NL
+"### - a single user," NL
+"### - a group of users defined in a special [groups] section," NL
+"### - an alias defined in a special [aliases] section," NL
+"### - all authenticated users, using the '$authenticated' token," NL
+"### - only anonymous users, using the '$anonymous' token," NL
+"### - anyone, using the '*' wildcard." NL
+"###" NL
+"### A match can be inverted by prefixing the rule with '~'. Rules can" NL
+"### grant read ('r') access, read-write ('rw') access, or no access" NL
+"### ('')." NL
+"" NL
+"[aliases]" NL
+"# joe = /C=XZ/ST=Dessert/L=Snake City/O=Snake Oil, Ltd./OU=Research Institute/CN=Joe Average" NL
+"" NL
+"[groups]" NL
+"# harry_and_sally = harry,sally" NL
+"# harry_sally_and_joe = harry,sally,&joe" NL
+"" NL
+"# [/foo/bar]" NL
+"# harry = rw" NL
+"# &joe = r" NL
+"# * =" NL
+"" NL
+"# [repository:/baz/fuz]" NL
+"# @harry_and_sally = rw" NL
+"# * = r" NL;
+
+ SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_AUTHZ,
+ pool),
+ authz_contents, pool),
+ _("Creating authz file"));
+ }
+
+ {
+ static const char * const hooks_env_contents =
+"### This file is an example hook script environment configuration file." NL
+"### Hook scripts run in an empty environment by default." NL
+"### As shown below each section defines environment variables for a" NL
+"### particular hook script. The [default] section defines environment" NL
+"### variables for all hook scripts, unless overridden by a hook-specific" NL
+"### section." NL
+"" NL
+"### This example configures a UTF-8 locale for all hook scripts, so that " NL
+"### special characters, such as umlauts, may be printed to stderr." NL
+"### If UTF-8 is used with a mod_dav_svn server, the SVNUseUTF8 option must" NL
+"### also be set to 'yes' in httpd.conf." NL
+"### With svnserve, the LANG environment variable of the svnserve process" NL
+"### must be set to the same value as given here." NL
+"[default]" NL
+"LANG = en_US.UTF-8" NL
+"" NL
+"### This sets the PATH environment variable for the pre-commit hook." NL
+"[pre-commit]" NL
+"PATH = /usr/local/bin:/usr/bin:/usr/sbin" NL;
+
+ SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_HOOKS_ENV \
+ SVN_REPOS__HOOK_DESC_EXT,
+ pool),
+ hooks_env_contents, pool),
+ _("Creating hooks-env file"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_hooks_setenv(svn_repos_t *repos,
+ const char *hooks_env_path,
+ apr_pool_t *scratch_pool)
+{
+ if (hooks_env_path == NULL)
+ repos->hooks_env_path = svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_HOOKS_ENV,
+ repos->pool);
+ else if (!svn_dirent_is_absolute(hooks_env_path))
+ repos->hooks_env_path = svn_dirent_join(repos->conf_path,
+ hooks_env_path,
+ repos->pool);
+ else
+ repos->hooks_env_path = apr_pstrdup(repos->pool, hooks_env_path);
+
+ return SVN_NO_ERROR;
+}
+
+/* Allocate and return a new svn_repos_t * object, initializing the
+ directory pathname members based on PATH, and initializing the
+ REPOSITORY_CAPABILITIES member.
+ The members FS, FORMAT, and FS_TYPE are *not* initialized (they are null),
+ and it is the caller's responsibility to fill them in if needed. */
+static svn_repos_t *
+create_svn_repos_t(const char *path, apr_pool_t *pool)
+{
+ svn_repos_t *repos = apr_pcalloc(pool, sizeof(*repos));
+
+ repos->path = apr_pstrdup(pool, path);
+ repos->db_path = svn_dirent_join(path, SVN_REPOS__DB_DIR, pool);
+ repos->conf_path = svn_dirent_join(path, SVN_REPOS__CONF_DIR, pool);
+ repos->hook_path = svn_dirent_join(path, SVN_REPOS__HOOK_DIR, pool);
+ repos->lock_path = svn_dirent_join(path, SVN_REPOS__LOCK_DIR, pool);
+ repos->hooks_env_path = NULL;
+ repos->repository_capabilities = apr_hash_make(pool);
+ repos->pool = pool;
+
+ return repos;
+}
+
+
+static svn_error_t *
+create_repos_structure(svn_repos_t *repos,
+ const char *path,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool)
+{
+ /* Create the top-level repository directory. */
+ SVN_ERR_W(create_repos_dir(path, pool),
+ _("Could not create top-level directory"));
+
+ /* Create the DAV sandbox directory if pre-1.4 or pre-1.5-compatible. */
+ if (fs_config
+ && (svn_hash_gets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE)
+ || svn_hash_gets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE)))
+ {
+ const char *dav_path = svn_dirent_join(repos->path,
+ SVN_REPOS__DAV_DIR, pool);
+ SVN_ERR_W(create_repos_dir(dav_path, pool),
+ _("Creating DAV sandbox dir"));
+ }
+
+ /* Create the lock directory. */
+ SVN_ERR(create_locks(repos, pool));
+
+ /* Create the hooks directory. */
+ SVN_ERR(create_hooks(repos, pool));
+
+ /* Create the conf directory. */
+ SVN_ERR(create_conf(repos, pool));
+
+ /* Write the top-level README file. */
+ {
+ const char * const readme_header =
+ "This is a Subversion repository; use the 'svnadmin' and 'svnlook' " NL
+ "tools to examine it. Do not add, delete, or modify files here " NL
+ "unless you know how to avoid corrupting the repository." NL
+ "" NL;
+ const char * const readme_bdb_insert =
+ "The directory \"" SVN_REPOS__DB_DIR "\" contains a Berkeley DB environment." NL
+ "you may need to tweak the values in \"" SVN_REPOS__DB_DIR "/DB_CONFIG\" to match the" NL
+ "requirements of your site." NL
+ "" NL;
+ const char * const readme_footer =
+ "Visit http://subversion.apache.org/ for more information." NL;
+ apr_file_t *f;
+ apr_size_t written;
+
+ SVN_ERR(svn_io_file_open(&f,
+ svn_dirent_join(path, SVN_REPOS__README, pool),
+ (APR_WRITE | APR_CREATE | APR_EXCL),
+ APR_OS_DEFAULT, pool));
+
+ SVN_ERR(svn_io_file_write_full(f, readme_header, strlen(readme_header),
+ &written, pool));
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ SVN_ERR(svn_io_file_write_full(f, readme_bdb_insert,
+ strlen(readme_bdb_insert),
+ &written, pool));
+ SVN_ERR(svn_io_file_write_full(f, readme_footer, strlen(readme_footer),
+ &written, pool));
+
+ return svn_io_file_close(f, pool);
+ }
+}
+
+
+/* There is, at present, nothing within the direct responsibility
+ of libsvn_repos which requires locking. For historical compatibility
+ reasons, the BDB libsvn_fs backend does not do its own locking, expecting
+ libsvn_repos to do the locking for it. Here we take care of that
+ backend-specific requirement.
+ The kind of lock is controlled by EXCLUSIVE and NONBLOCKING.
+ The lock is scoped to POOL. */
+static svn_error_t *
+lock_repos(svn_repos_t *repos,
+ svn_boolean_t exclusive,
+ svn_boolean_t nonblocking,
+ apr_pool_t *pool)
+{
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ {
+ svn_error_t *err;
+ const char *lockfile_path = svn_repos_db_lockfile(repos, pool);
+
+ err = svn_io_file_lock2(lockfile_path, exclusive, nonblocking, pool);
+ if (err != NULL && APR_STATUS_IS_EAGAIN(err->apr_err))
+ return svn_error_trace(err);
+ SVN_ERR_W(err, _("Error opening db lockfile"));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_create(svn_repos_t **repos_p,
+ const char *path,
+ const char *unused_1,
+ const char *unused_2,
+ apr_hash_t *config,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+ svn_error_t *err;
+ const char *root_path;
+ const char *local_abspath;
+
+ /* Allocate a repository object, filling in the format we will create. */
+ repos = create_svn_repos_t(path, pool);
+ repos->format = SVN_REPOS__FORMAT_NUMBER;
+
+ /* Discover the type of the filesystem we are about to create. */
+ repos->fs_type = svn_hash__get_cstring(fs_config, SVN_FS_CONFIG_FS_TYPE,
+ DEFAULT_FS_TYPE);
+ if (svn_hash__get_bool(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, FALSE))
+ repos->format = SVN_REPOS__FORMAT_NUMBER_LEGACY;
+
+ /* Don't create a repository inside another repository. */
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ root_path = svn_repos_find_root_path(local_abspath, pool);
+ if (root_path != NULL)
+ {
+ if (strcmp(root_path, local_abspath) == 0)
+ return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("'%s' is an existing repository"),
+ svn_dirent_local_style(root_path, pool));
+ else
+ return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
+ _("'%s' is a subdirectory of an existing "
+ "repository " "rooted at '%s'"),
+ svn_dirent_local_style(local_abspath, pool),
+ svn_dirent_local_style(root_path, pool));
+ }
+
+ /* Create the various files and subdirectories for the repository. */
+ SVN_ERR_W(create_repos_structure(repos, path, fs_config, pool),
+ _("Repository creation failed"));
+
+ /* Lock if needed. */
+ SVN_ERR(lock_repos(repos, FALSE, FALSE, pool));
+
+ /* Create an environment for the filesystem. */
+ if ((err = svn_fs_create(&repos->fs, repos->db_path, fs_config, pool)))
+ {
+ /* If there was an error making the filesytem, e.g. unknown/supported
+ * filesystem type. Clean up after ourselves. Yes this is safe because
+ * create_repos_structure will fail if the path existed before we started
+ * so we can't accidentally remove a directory that previously existed.
+ */
+
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_io_remove_dir2(path, FALSE, NULL, NULL, pool)));
+ }
+
+ /* This repository is ready. Stamp it with a format number. */
+ SVN_ERR(svn_io_write_version_file
+ (svn_dirent_join(path, SVN_REPOS__FORMAT, pool),
+ repos->format, pool));
+
+ *repos_p = repos;
+ return SVN_NO_ERROR;
+}
+
+
+/* Check if @a path is the root of a repository by checking if the
+ * path contains the expected files and directories. Return TRUE
+ * on errors (which would be permission errors, probably) so that
+ * we the user will see them after we try to open the repository
+ * for real. */
+static svn_boolean_t
+check_repos_path(const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ err = svn_io_check_path(svn_dirent_join(path, SVN_REPOS__FORMAT, pool),
+ &kind, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return TRUE;
+ }
+ if (kind != svn_node_file)
+ return FALSE;
+
+ /* Check the db/ subdir, but allow it to be a symlink (Subversion
+ works just fine if it's a symlink). */
+ err = svn_io_check_resolved_path
+ (svn_dirent_join(path, SVN_REPOS__DB_DIR, pool), &kind, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return TRUE;
+ }
+ if (kind != svn_node_dir)
+ return FALSE;
+
+ return TRUE;
+}
+
+
+/* Verify that REPOS's format is suitable.
+ Use POOL for temporary allocation. */
+static svn_error_t *
+check_repos_format(svn_repos_t *repos,
+ apr_pool_t *pool)
+{
+ int format;
+ const char *format_path;
+
+ format_path = svn_dirent_join(repos->path, SVN_REPOS__FORMAT, pool);
+ SVN_ERR(svn_io_read_version_file(&format, format_path, pool));
+
+ if (format != SVN_REPOS__FORMAT_NUMBER &&
+ format != SVN_REPOS__FORMAT_NUMBER_LEGACY)
+ {
+ return svn_error_createf
+ (SVN_ERR_REPOS_UNSUPPORTED_VERSION, NULL,
+ _("Expected repository format '%d' or '%d'; found format '%d'"),
+ SVN_REPOS__FORMAT_NUMBER_LEGACY, SVN_REPOS__FORMAT_NUMBER,
+ format);
+ }
+
+ repos->format = format;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *REPOS_P to a repository at PATH which has been opened.
+ See lock_repos() above regarding EXCLUSIVE and NONBLOCKING.
+ OPEN_FS indicates whether the Subversion filesystem should be opened,
+ the handle being placed into repos->fs.
+ Do all allocation in POOL. */
+static svn_error_t *
+get_repos(svn_repos_t **repos_p,
+ const char *path,
+ svn_boolean_t exclusive,
+ svn_boolean_t nonblocking,
+ svn_boolean_t open_fs,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+
+ /* Allocate a repository object. */
+ repos = create_svn_repos_t(path, pool);
+
+ /* Verify the validity of our repository format. */
+ SVN_ERR(check_repos_format(repos, pool));
+
+ /* Discover the FS type. */
+ SVN_ERR(svn_fs_type(&repos->fs_type, repos->db_path, pool));
+
+ /* Lock if needed. */
+ SVN_ERR(lock_repos(repos, exclusive, nonblocking, pool));
+
+ /* Open up the filesystem only after obtaining the lock. */
+ if (open_fs)
+ SVN_ERR(svn_fs_open(&repos->fs, repos->db_path, fs_config, pool));
+
+#ifdef SVN_DEBUG_CRASH_AT_REPOS_OPEN
+ /* If $PATH/config/debug-abort exists, crash the server here.
+ This debugging feature can be used to test client recovery
+ when the server crashes.
+
+ See: Issue #4274 */
+ {
+ svn_node_kind_t kind;
+ svn_error_t *err = svn_io_check_path(
+ svn_dirent_join(repos->conf_path, "debug-abort", pool),
+ &kind, pool);
+ svn_error_clear(err);
+ if (!err && kind == svn_node_file)
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ }
+#endif /* SVN_DEBUG_CRASH_AT_REPOS_OPEN */
+
+ *repos_p = repos;
+ return SVN_NO_ERROR;
+}
+
+
+
+const char *
+svn_repos_find_root_path(const char *path,
+ apr_pool_t *pool)
+{
+ const char *candidate = path;
+ const char *decoded;
+ svn_error_t *err;
+
+ while (1)
+ {
+ /* Try to decode the path, so we don't fail if it contains characters
+ that aren't supported by the OS filesystem. The subversion fs
+ isn't restricted by the OS filesystem character set. */
+ err = svn_path_cstring_from_utf8(&decoded, candidate, pool);
+ if (!err && check_repos_path(candidate, pool))
+ break;
+ svn_error_clear(err);
+
+ if (svn_path_is_empty(candidate) ||
+ svn_dirent_is_root(candidate, strlen(candidate)))
+ return NULL;
+
+ candidate = svn_dirent_dirname(candidate, pool);
+ }
+
+ return candidate;
+}
+
+
+svn_error_t *
+svn_repos_open2(svn_repos_t **repos_p,
+ const char *path,
+ apr_hash_t *fs_config,
+ apr_pool_t *pool)
+{
+ /* Fetch a repository object initialized with a shared read/write
+ lock on the database. */
+
+ return get_repos(repos_p, path, FALSE, FALSE, TRUE, fs_config, pool);
+}
+
+
+svn_error_t *
+svn_repos_upgrade2(const char *path,
+ svn_boolean_t nonblocking,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+ const char *format_path;
+ int format;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Fetch a repository object; for the Berkeley DB backend, it is
+ initialized with an EXCLUSIVE lock on the database. This will at
+ least prevent others from trying to read or write to it while we
+ run recovery. (Other backends should do their own locking; see
+ lock_repos.) */
+ SVN_ERR(get_repos(&repos, path, TRUE, nonblocking, FALSE, NULL, subpool));
+
+ if (notify_func)
+ {
+ /* We notify *twice* here, because there are two different logistical
+ actions occuring. */
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_mutex_acquired, subpool);
+ notify_func(notify_baton, notify, subpool);
+
+ notify->action = svn_repos_notify_upgrade_start;
+ notify_func(notify_baton, notify, subpool);
+ }
+
+ /* Try to overwrite with its own contents. We do this only to
+ verify that we can, because we don't want to actually bump the
+ format of the repository until our underlying filesystem claims
+ to have been upgraded correctly. */
+ format_path = svn_dirent_join(repos->path, SVN_REPOS__FORMAT, subpool);
+ SVN_ERR(svn_io_read_version_file(&format, format_path, subpool));
+ SVN_ERR(svn_io_write_version_file(format_path, format, subpool));
+
+ /* Try to upgrade the filesystem. */
+ SVN_ERR(svn_fs_upgrade(repos->db_path, subpool));
+
+ /* Now overwrite our format file with the latest version. */
+ SVN_ERR(svn_io_write_version_file(format_path, SVN_REPOS__FORMAT_NUMBER,
+ subpool));
+
+ /* Close shop and free the subpool, to release the exclusive lock. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_delete(const char *path,
+ apr_pool_t *pool)
+{
+ const char *db_path = svn_dirent_join(path, SVN_REPOS__DB_DIR, pool);
+
+ /* Delete the filesystem environment... */
+ SVN_ERR(svn_fs_delete_fs(db_path, pool));
+
+ /* ...then blow away everything else. */
+ return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
+}
+
+
+/* Repository supports the capability. */
+static const char *capability_yes = "yes";
+/* Repository does not support the capability. */
+static const char *capability_no = "no";
+
+svn_error_t *
+svn_repos_has_capability(svn_repos_t *repos,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ const char *val = svn_hash_gets(repos->repository_capabilities, capability);
+
+ if (val == capability_yes)
+ {
+ *has = TRUE;
+ }
+ else if (val == capability_no)
+ {
+ *has = FALSE;
+ }
+ /* Else don't know, so investigate. */
+ else if (strcmp(capability, SVN_REPOS_CAPABILITY_MERGEINFO) == 0)
+ {
+ svn_error_t *err;
+ svn_fs_root_t *root;
+ svn_mergeinfo_catalog_t ignored;
+ apr_array_header_t *paths = apr_array_make(pool, 1,
+ sizeof(char *));
+
+ SVN_ERR(svn_fs_revision_root(&root, repos->fs, 0, pool));
+ APR_ARRAY_PUSH(paths, const char *) = "";
+ err = svn_fs_get_mergeinfo2(&ignored, root, paths, FALSE, FALSE,
+ TRUE, pool, pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
+ {
+ svn_error_clear(err);
+ svn_hash_sets(repos->repository_capabilities,
+ SVN_REPOS_CAPABILITY_MERGEINFO, capability_no);
+ *has = FALSE;
+ }
+ else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ /* Mergeinfo requests use relative paths, and anyway we're
+ in r0, so we're likely to get this error -- but it
+ means the repository supports mergeinfo! */
+ svn_error_clear(err);
+ svn_hash_sets(repos->repository_capabilities,
+ SVN_REPOS_CAPABILITY_MERGEINFO, capability_yes);
+ *has = TRUE;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+ else
+ {
+ svn_hash_sets(repos->repository_capabilities,
+ SVN_REPOS_CAPABILITY_MERGEINFO, capability_yes);
+ *has = TRUE;
+ }
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_UNKNOWN_CAPABILITY, 0,
+ _("unknown capability '%s'"), capability);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_fs_t *
+svn_repos_fs(svn_repos_t *repos)
+{
+ if (! repos)
+ return NULL;
+ return repos->fs;
+}
+
+
+/* For historical reasons, for the Berkeley DB backend, this code uses
+ * repository locking, which is motivated by the need to support the
+ * Berkeley DB error DB_RUN_RECOVERY. (FSFS takes care of locking
+ * itself, inside its implementation of svn_fs_recover.) Here's how
+ * it works:
+ *
+ * Every accessor of a repository's database takes out a shared lock
+ * on the repository -- both readers and writers get shared locks, and
+ * there can be an unlimited number of shared locks simultaneously.
+ *
+ * Sometimes, a db access returns the error DB_RUN_RECOVERY. When
+ * this happens, we need to run svn_fs_recover() on the db
+ * with no other accessors present. So we take out an exclusive lock
+ * on the repository. From the moment we request the exclusive lock,
+ * no more shared locks are granted, and when the last shared lock
+ * disappears, the exclusive lock is granted. As soon as we get it,
+ * we can run recovery.
+ *
+ * We assume that once any berkeley call returns DB_RUN_RECOVERY, they
+ * all do, until recovery is run.
+ */
+
+svn_error_t *
+svn_repos_recover4(const char *path,
+ svn_boolean_t nonblocking,
+ svn_repos_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void * cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Fetch a repository object; for the Berkeley DB backend, it is
+ initialized with an EXCLUSIVE lock on the database. This will at
+ least prevent others from trying to read or write to it while we
+ run recovery. (Other backends should do their own locking; see
+ lock_repos.) */
+ SVN_ERR(get_repos(&repos, path, TRUE, nonblocking,
+ FALSE, /* don't try to open the db yet. */
+ NULL,
+ subpool));
+
+ if (notify_func)
+ {
+ /* We notify *twice* here, because there are two different logistical
+ actions occuring. */
+ svn_repos_notify_t *notify = svn_repos_notify_create(
+ svn_repos_notify_mutex_acquired, subpool);
+ notify_func(notify_baton, notify, subpool);
+
+ notify->action = svn_repos_notify_recover_start;
+ notify_func(notify_baton, notify, subpool);
+ }
+
+ /* Recover the database to a consistent state. */
+ SVN_ERR(svn_fs_recover(repos->db_path, cancel_func, cancel_baton, subpool));
+
+ /* Close shop and free the subpool, to release the exclusive lock. */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+struct freeze_baton_t {
+ apr_array_header_t *paths;
+ int counter;
+ svn_repos_freeze_func_t freeze_func;
+ void *freeze_baton;
+};
+
+static svn_error_t *
+multi_freeze(void *baton,
+ apr_pool_t *pool)
+{
+ struct freeze_baton_t *fb = baton;
+
+ if (fb->counter == fb->paths->nelts)
+ {
+ SVN_ERR(fb->freeze_func(fb->freeze_baton, pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* Using a subpool as the only way to unlock the repos lock used
+ by BDB is to clear the pool used to take the lock. */
+ apr_pool_t *subpool = svn_pool_create(pool);
+ const char *path = APR_ARRAY_IDX(fb->paths, fb->counter, const char *);
+ svn_repos_t *repos;
+
+ ++fb->counter;
+
+ SVN_ERR(get_repos(&repos, path,
+ TRUE /* exclusive (only applies to BDB) */,
+ FALSE /* non-blocking */,
+ FALSE /* open-fs */,
+ NULL, subpool));
+
+
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ {
+ svn_error_t *err = multi_freeze(fb, subpool);
+
+ svn_pool_destroy(subpool);
+
+ return err;
+ }
+ else
+ {
+ SVN_ERR(svn_fs_open(&repos->fs, repos->db_path, NULL, subpool));
+ SVN_ERR(svn_fs_freeze(svn_repos_fs(repos), multi_freeze, fb,
+ subpool));
+ }
+
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* For BDB we fall back on BDB's repos layer lock which means that the
+ repository is unreadable while frozen.
+
+ For FSFS we delegate to the FS layer which uses the FSFS write-lock
+ and an SQLite reserved lock which means the repository is readable
+ while frozen. */
+svn_error_t *
+svn_repos_freeze(apr_array_header_t *paths,
+ svn_repos_freeze_func_t freeze_func,
+ void *freeze_baton,
+ apr_pool_t *pool)
+{
+ struct freeze_baton_t fb;
+
+ fb.paths = paths;
+ fb.counter = 0;
+ fb.freeze_func = freeze_func;
+ fb.freeze_baton = freeze_baton;
+
+ SVN_ERR(multi_freeze(&fb, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_repos_db_logfiles(apr_array_header_t **logfiles,
+ const char *path,
+ svn_boolean_t only_unused,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+ int i;
+
+ SVN_ERR(get_repos(&repos, path,
+ FALSE, FALSE,
+ FALSE, /* Do not open fs. */
+ NULL,
+ pool));
+
+ SVN_ERR(svn_fs_berkeley_logfiles(logfiles,
+ svn_repos_db_env(repos, pool),
+ only_unused,
+ pool));
+
+ /* Loop, printing log files. */
+ for (i = 0; i < (*logfiles)->nelts; i++)
+ {
+ const char ** log_file = &(APR_ARRAY_IDX(*logfiles, i, const char *));
+ *log_file = svn_dirent_join(SVN_REPOS__DB_DIR, *log_file, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for hotcopy_structure(). */
+struct hotcopy_ctx_t {
+ const char *dest; /* target location to construct */
+ size_t src_len; /* len of the source path*/
+
+ /* As in svn_repos_hotcopy2() */
+ svn_boolean_t incremental;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+};
+
+/* Copy the repository structure of PATH to BATON->DEST, with exception of
+ * @c SVN_REPOS__DB_DIR, @c SVN_REPOS__LOCK_DIR and @c SVN_REPOS__FORMAT;
+ * those directories and files are handled separately.
+ *
+ * BATON is a (struct hotcopy_ctx_t *). BATON->SRC_LEN is the length
+ * of PATH.
+ *
+ * Implements svn_io_walk_func_t.
+ */
+static svn_error_t *hotcopy_structure(void *baton,
+ const char *path,
+ const apr_finfo_t *finfo,
+ apr_pool_t *pool)
+{
+ const struct hotcopy_ctx_t *ctx = baton;
+ const char *sub_path;
+ const char *target;
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ if (strlen(path) == ctx->src_len)
+ {
+ sub_path = "";
+ }
+ else
+ {
+ sub_path = &path[ctx->src_len+1];
+
+ /* Check if we are inside db directory and if so skip it */
+ if (svn_path_compare_paths
+ (svn_dirent_get_longest_ancestor(SVN_REPOS__DB_DIR, sub_path, pool),
+ SVN_REPOS__DB_DIR) == 0)
+ return SVN_NO_ERROR;
+
+ if (svn_path_compare_paths
+ (svn_dirent_get_longest_ancestor(SVN_REPOS__LOCK_DIR, sub_path,
+ pool),
+ SVN_REPOS__LOCK_DIR) == 0)
+ return SVN_NO_ERROR;
+
+ if (svn_path_compare_paths
+ (svn_dirent_get_longest_ancestor(SVN_REPOS__FORMAT, sub_path, pool),
+ SVN_REPOS__FORMAT) == 0)
+ return SVN_NO_ERROR;
+ }
+
+ target = svn_dirent_join(ctx->dest, sub_path, pool);
+
+ if (finfo->filetype == APR_DIR)
+ {
+ svn_error_t *err;
+
+ err = create_repos_dir(target, pool);
+ if (ctx->incremental && err && err->apr_err == SVN_ERR_DIR_NOT_EMPTY)
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+ }
+ else if (finfo->filetype == APR_REG)
+ return svn_io_copy_file(path, target, TRUE, pool);
+ else if (finfo->filetype == APR_LNK)
+ return svn_io_copy_link(path, target, pool);
+ else
+ return SVN_NO_ERROR;
+}
+
+
+/** Obtain a lock on db logs lock file. Create one if it does not exist.
+ */
+static svn_error_t *
+lock_db_logs_file(svn_repos_t *repos,
+ svn_boolean_t exclusive,
+ apr_pool_t *pool)
+{
+ const char * lock_file = svn_repos_db_logs_lockfile(repos, pool);
+
+ /* Try to create a lock file, in case if it is missing. As in case of the
+ repositories created before hotcopy functionality. */
+ svn_error_clear(create_db_logs_lock(repos, pool));
+
+ return svn_io_file_lock2(lock_file, exclusive, FALSE, pool);
+}
+
+
+/* Make a copy of a repository with hot backup of fs. */
+svn_error_t *
+svn_repos_hotcopy2(const char *src_path,
+ const char *dst_path,
+ svn_boolean_t clean_logs,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_t *src_repos;
+ svn_repos_t *dst_repos;
+ struct hotcopy_ctx_t hotcopy_context;
+ svn_error_t *err;
+ const char *src_abspath;
+ const char *dst_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&src_abspath, src_path, pool));
+ SVN_ERR(svn_dirent_get_absolute(&dst_abspath, dst_path, pool));
+ if (strcmp(src_abspath, dst_abspath) == 0)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Hotcopy source and destination are equal"));
+
+ /* Try to open original repository */
+ SVN_ERR(get_repos(&src_repos, src_abspath,
+ FALSE, FALSE,
+ FALSE, /* don't try to open the db yet. */
+ NULL,
+ pool));
+
+ /* If we are going to clean logs, then get an exclusive lock on
+ db-logs.lock, to ensure that no one else will work with logs.
+
+ If we are just copying, then get a shared lock to ensure that
+ no one else will clean logs while we copying them */
+
+ SVN_ERR(lock_db_logs_file(src_repos, clean_logs, pool));
+
+ /* Copy the repository to a new path, with exception of
+ specially handled directories */
+
+ hotcopy_context.dest = dst_abspath;
+ hotcopy_context.src_len = strlen(src_abspath);
+ hotcopy_context.incremental = incremental;
+ hotcopy_context.cancel_func = cancel_func;
+ hotcopy_context.cancel_baton = cancel_baton;
+ SVN_ERR(svn_io_dir_walk2(src_abspath,
+ 0,
+ hotcopy_structure,
+ &hotcopy_context,
+ pool));
+
+ /* Prepare dst_repos object so that we may create locks,
+ so that we may open repository */
+
+ dst_repos = create_svn_repos_t(dst_abspath, pool);
+ dst_repos->fs_type = src_repos->fs_type;
+ dst_repos->format = src_repos->format;
+
+ err = create_locks(dst_repos, pool);
+ if (err)
+ {
+ if (incremental && err->apr_err == SVN_ERR_DIR_NOT_EMPTY)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+
+ err = svn_io_dir_make_sgid(dst_repos->db_path, APR_OS_DEFAULT, pool);
+ if (err)
+ {
+ if (incremental && APR_STATUS_IS_EEXIST(err->apr_err))
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+
+ /* Exclusively lock the new repository.
+ No one should be accessing it at the moment */
+ SVN_ERR(lock_repos(dst_repos, TRUE, FALSE, pool));
+
+ SVN_ERR(svn_fs_hotcopy2(src_repos->db_path, dst_repos->db_path,
+ clean_logs, incremental,
+ cancel_func, cancel_baton, pool));
+
+ /* Destination repository is ready. Stamp it with a format number. */
+ return svn_io_write_version_file
+ (svn_dirent_join(dst_repos->path, SVN_REPOS__FORMAT, pool),
+ dst_repos->format, pool);
+}
+
+svn_error_t *
+svn_repos_hotcopy(const char *src_path,
+ const char *dst_path,
+ svn_boolean_t clean_logs,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_repos_hotcopy2(src_path, dst_path, clean_logs,
+ FALSE, NULL, NULL, pool));
+}
+
+/* Return the library version number. */
+const svn_version_t *
+svn_repos_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+
+
+svn_error_t *
+svn_repos_stat(svn_dirent_t **dirent,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ svn_dirent_t *ent;
+ const char *datestring;
+ apr_hash_t *prophash;
+
+ SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
+
+ if (kind == svn_node_none)
+ {
+ *dirent = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ ent = svn_dirent_create(pool);
+ ent->kind = kind;
+
+ if (kind == svn_node_file)
+ SVN_ERR(svn_fs_file_length(&(ent->size), root, path, pool));
+
+ SVN_ERR(svn_fs_node_proplist(&prophash, root, path, pool));
+ if (apr_hash_count(prophash) > 0)
+ ent->has_props = TRUE;
+
+ SVN_ERR(svn_repos_get_committed_info(&(ent->created_rev),
+ &datestring,
+ &(ent->last_author),
+ root, path, pool));
+ if (datestring)
+ SVN_ERR(svn_time_from_cstring(&(ent->time), datestring, pool));
+
+ *dirent = ent;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_remember_client_capabilities(svn_repos_t *repos,
+ const apr_array_header_t *capabilities)
+{
+ repos->client_capabilities = capabilities;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__fs_type(const char **fs_type,
+ const char *repos_path,
+ apr_pool_t *pool)
+{
+ svn_repos_t repos;
+ repos.path = (char*)repos_path;
+
+ SVN_ERR(check_repos_format(&repos, pool));
+
+ return svn_fs_type(fs_type,
+ svn_dirent_join(repos_path, SVN_REPOS__DB_DIR, pool),
+ pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_repos/repos.h b/contrib/subversion/subversion/libsvn_repos/repos.h
new file mode 100644
index 0000000..fd5b0b4
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/repos.h
@@ -0,0 +1,425 @@
+/* repos.h : interface to Subversion repository, private to libsvn_repos
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_REPOS_H
+#define SVN_LIBSVN_REPOS_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Repository format number.
+
+ Formats 0, 1 and 2 were pre-1.0.
+
+ Format 3 was current for 1.0 through to 1.3.
+
+ Format 4 was an abortive experiment during the development of the
+ locking feature in the lead up to 1.2.
+
+ Format 5 was new in 1.4, and is the first format which may contain
+ BDB or FSFS filesystems with a FS format other than 1, since prior
+ formats are accepted by some versions of Subversion which do not
+ pay attention to the FS format number.
+*/
+#define SVN_REPOS__FORMAT_NUMBER SVN_REPOS__FORMAT_NUMBER_1_4
+#define SVN_REPOS__FORMAT_NUMBER_1_4 5
+#define SVN_REPOS__FORMAT_NUMBER_LEGACY 3
+
+
+/*** Repository layout. ***/
+
+/* The top-level repository dir contains a README and various
+ subdirectories. */
+#define SVN_REPOS__README "README.txt" /* Explanation for trespassers. */
+#define SVN_REPOS__FORMAT "format" /* Stores the current version
+ of the repository. */
+#define SVN_REPOS__DB_DIR "db" /* Where Berkeley lives. */
+#define SVN_REPOS__DAV_DIR "dav" /* DAV sandbox, for pre-1.5 */
+#define SVN_REPOS__LOCK_DIR "locks" /* Lock files live here. */
+#define SVN_REPOS__HOOK_DIR "hooks" /* Hook programs. */
+#define SVN_REPOS__CONF_DIR "conf" /* Configuration files. */
+
+/* Things for which we keep lockfiles. */
+#define SVN_REPOS__DB_LOCKFILE "db.lock" /* Our Berkeley lockfile. */
+#define SVN_REPOS__DB_LOGS_LOCKFILE "db-logs.lock" /* BDB logs lockfile. */
+
+/* In the repository hooks directory, look for these files. */
+#define SVN_REPOS__HOOK_START_COMMIT "start-commit"
+#define SVN_REPOS__HOOK_PRE_COMMIT "pre-commit"
+#define SVN_REPOS__HOOK_POST_COMMIT "post-commit"
+#define SVN_REPOS__HOOK_READ_SENTINEL "read-sentinels"
+#define SVN_REPOS__HOOK_WRITE_SENTINEL "write-sentinels"
+#define SVN_REPOS__HOOK_PRE_REVPROP_CHANGE "pre-revprop-change"
+#define SVN_REPOS__HOOK_POST_REVPROP_CHANGE "post-revprop-change"
+#define SVN_REPOS__HOOK_PRE_LOCK "pre-lock"
+#define SVN_REPOS__HOOK_POST_LOCK "post-lock"
+#define SVN_REPOS__HOOK_PRE_UNLOCK "pre-unlock"
+#define SVN_REPOS__HOOK_POST_UNLOCK "post-unlock"
+
+
+/* The extension added to the names of example hook scripts. */
+#define SVN_REPOS__HOOK_DESC_EXT ".tmpl"
+
+/* The file which contains a custom set of environment variables
+ * passed inherited to hook scripts, in the repository conf directory. */
+#define SVN_REPOS__CONF_HOOKS_ENV "hooks-env"
+/* The name of the default section in the hooks-env config file. */
+#define SVN_REPOS__HOOKS_ENV_DEFAULT_SECTION "default"
+
+/* The configuration file for svnserve, in the repository conf directory. */
+#define SVN_REPOS__CONF_SVNSERVE_CONF "svnserve.conf"
+
+/* In the svnserve default configuration, these are the suggested
+ locations for the passwd, authz and groups files (in the repository
+ conf directory), and we put example templates there. */
+#define SVN_REPOS__CONF_PASSWD "passwd"
+#define SVN_REPOS__CONF_AUTHZ "authz"
+#define SVN_REPOS__CONF_GROUPS "groups"
+
+/* The Repository object, created by svn_repos_open2() and
+ svn_repos_create(). */
+struct svn_repos_t
+{
+ /* A Subversion filesystem object. */
+ svn_fs_t *fs;
+
+ /* The path to the repository's top-level directory. */
+ char *path;
+
+ /* The path to the repository's conf directory. */
+ char *conf_path;
+
+ /* The path to the repository's hooks directory. */
+ char *hook_path;
+
+ /* The path to the repository's locks directory. */
+ char *lock_path;
+
+ /* The path to the Berkeley DB filesystem environment. */
+ char *db_path;
+
+ /* The format number of this repository. */
+ int format;
+
+ /* The path to the repository's hooks enviroment file. If NULL, hooks run
+ * in an empty environment. */
+ const char *hooks_env_path;
+
+ /* The FS backend in use within this repository. */
+ const char *fs_type;
+
+ /* If non-null, a list of all the capabilities the client (on the
+ current connection) has self-reported. Each element is a
+ 'const char *', one of SVN_RA_CAPABILITY_*.
+
+ Note: it is somewhat counterintuitive that we store the client's
+ capabilities, which are session-specific, on the repository
+ object. You'd think the capabilities here would represent the
+ *repository's* capabilities, but no, they represent the
+ client's -- we just don't have any other place to persist them. */
+ const apr_array_header_t *client_capabilities;
+
+ /* Maps SVN_REPOS_CAPABILITY_foo keys to "yes" or "no" values.
+ If a capability is not yet discovered, it is absent from the table.
+ Most likely the keys and values are constants anyway (and
+ sufficiently well-informed internal code may just compare against
+ those constants' addresses, therefore). */
+ apr_hash_t *repository_capabilities;
+
+ /* Pool from which this structure was allocated. Also used for
+ auxiliary repository-related data that requires a matching
+ lifespan. (As the svn_repos_t structure tends to be relatively
+ long-lived, please be careful regarding this pool's usage.) */
+ apr_pool_t *pool;
+};
+
+
+/*** Hook-running Functions ***/
+
+/* Set *HOOKS_ENV_P to the parsed contents of the hooks-env file
+ LOCAL_ABSPATH, allocated in RESULT_POOL. (This result is suitable
+ for delivery to the various hook wrapper functions which accept a
+ 'hooks_env' parameter.) If LOCAL_ABSPATH is NULL, set *HOOKS_ENV_P
+ to NULL.
+
+ Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_repos__parse_hooks_env(apr_hash_t **hooks_env_p,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Run the start-commit hook for REPOS. Use POOL for any temporary
+ allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
+ USER is the authenticated name of the user starting the commit.
+
+ CAPABILITIES is a list of 'const char *' capability names (using
+ SVN_RA_CAPABILITY_*) that the client has self-reported. Note that
+ there is no guarantee the client is telling the truth: the hook
+ should not make security assumptions based on the capabilities.
+
+ TXN_NAME is the name of the commit transaction that's just been
+ created. */
+svn_error_t *
+svn_repos__hooks_start_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const char *user,
+ const apr_array_header_t *capabilities,
+ const char *txn_name,
+ apr_pool_t *pool);
+
+/* Run the pre-commit hook for REPOS. Use POOL for any temporary
+ allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
+ TXN_NAME is the name of the transaction that is being committed. */
+svn_error_t *
+svn_repos__hooks_pre_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const char *txn_name,
+ apr_pool_t *pool);
+
+/* Run the post-commit hook for REPOS. Use POOL for any temporary
+ allocations. If the hook fails, run SVN_ERR_REPOS_HOOK_FAILURE.
+
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
+ REV is the revision that was created as a result of the commit. */
+svn_error_t *
+svn_repos__hooks_post_commit(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ svn_revnum_t rev,
+ const char *txn_name,
+ apr_pool_t *pool);
+
+/* Run the pre-revprop-change hook for REPOS. Use POOL for any
+ temporary allocations. If the hook fails, return
+ SVN_ERR_REPOS_HOOK_FAILURE.
+
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
+ REV is the revision whose property is being changed.
+ AUTHOR is the authenticated name of the user changing the prop.
+ NAME is the name of the property being changed.
+ NEW_VALUE is the new value of the property.
+ ACTION is indicates if the property is being 'A'dded, 'M'odified,
+ or 'D'eleted.
+
+ The pre-revprop-change hook will have the new property value
+ written to its stdin. If the property is being deleted, no data
+ will be written. */
+svn_error_t *
+svn_repos__hooks_pre_revprop_change(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *new_value,
+ char action,
+ apr_pool_t *pool);
+
+/* Run the pre-revprop-change hook for REPOS. Use POOL for any
+ temporary allocations. If the hook fails, return
+ SVN_ERR_REPOS_HOOK_FAILURE.
+
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
+ REV is the revision whose property was changed.
+ AUTHOR is the authenticated name of the user who changed the prop.
+ NAME is the name of the property that was changed, and OLD_VALUE is
+ that property's value immediately before the change, or null if
+ none. ACTION indicates if the property was 'A'dded, 'M'odified,
+ or 'D'eleted.
+
+ The old value will be passed to the post-revprop hook on stdin. If
+ the property is being created, no data will be written. */
+svn_error_t *
+svn_repos__hooks_post_revprop_change(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ svn_revnum_t rev,
+ const char *author,
+ const char *name,
+ const svn_string_t *old_value,
+ char action,
+ apr_pool_t *pool);
+
+/* Run the pre-lock hook for REPOS. Use POOL for any temporary
+ allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
+ PATH is the path being locked, USERNAME is the person doing it,
+ COMMENT is the comment of the lock, and is treated as an empty
+ string when NULL is given. STEAL-LOCK is a flag if the user is
+ stealing the lock.
+
+ If TOKEN is non-null, set *TOKEN to a new lock token generated by
+ the pre-lock hook, if any (see the pre-lock hook template for more
+ information). If TOKEN is non-null but the hook does not return
+ any token, then set *TOKEN to empty string. */
+
+svn_error_t *
+svn_repos__hooks_pre_lock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const char **token,
+ const char *path,
+ const char *username,
+ const char *comment,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool);
+
+/* Run the post-lock hook for REPOS. Use POOL for any temporary
+ allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
+ PATHS is an array of paths being locked, USERNAME is the person
+ who did it. */
+svn_error_t *
+svn_repos__hooks_post_lock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const apr_array_header_t *paths,
+ const char *username,
+ apr_pool_t *pool);
+
+/* Run the pre-unlock hook for REPOS. Use POOL for any temporary
+ allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
+ PATH is the path being unlocked, USERNAME is the person doing it,
+ TOKEN is the lock token to be unlocked which should not be NULL,
+ and BREAK-LOCK is a flag if the user is breaking the lock. */
+svn_error_t *
+svn_repos__hooks_pre_unlock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const char *path,
+ const char *username,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool);
+
+/* Run the post-unlock hook for REPOS. Use POOL for any temporary
+ allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
+
+ HOOKS_ENV is a hash of hook script environment information returned
+ via svn_repos__parse_hooks_env() (or NULL if no such information is
+ available).
+
+ PATHS is an array of paths being unlocked, USERNAME is the person
+ who did it. */
+svn_error_t *
+svn_repos__hooks_post_unlock(svn_repos_t *repos,
+ apr_hash_t *hooks_env,
+ const apr_array_header_t *paths,
+ const char *username,
+ apr_pool_t *pool);
+
+
+/*** Authz Functions ***/
+
+/* Read authz configuration data from PATH into *AUTHZ_P, allocated
+ in POOL. If GROUPS_PATH is set, use the global groups parsed from it.
+
+ PATH and GROUPS_PATH may be a dirent or a registry path and iff ACCEPT_URLS
+ is set it may also be an absolute file url.
+
+ If PATH or GROUPS_PATH is not a valid authz rule file, then return
+ SVN_AUTHZ_INVALID_CONFIG. The contents of *AUTHZ_P is then
+ undefined. If MUST_EXIST is TRUE, a missing authz or global groups file
+ is also an error. */
+svn_error_t *
+svn_repos__authz_read(svn_authz_t **authz_p,
+ const char *path,
+ const char *groups_path,
+ svn_boolean_t must_exist,
+ svn_boolean_t accept_urls,
+ apr_pool_t *pool);
+
+
+/*** Utility Functions ***/
+
+/* Set *CHANGED_P to TRUE if ROOT1/PATH1 and ROOT2/PATH2 have
+ different contents, FALSE if they have the same contents.
+ Use POOL for temporary allocation. */
+svn_error_t *
+svn_repos__compare_files(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool);
+
+/* Set *PREV_PATH and *PREV_REV to the path and revision which
+ represent the location at which PATH in FS was located immediately
+ prior to REVISION iff there was a copy operation (to PATH or one of
+ its parent directories) between that previous location and
+ PATH@REVISION, and set *APPEARED_REV to the first revision in which
+ PATH@REVISION appeared at PATH as a result of that copy operation.
+
+ If there was no such copy operation in that portion
+ of PATH's history, set *PREV_PATH to NULL, and set *PREV_REV and
+ *APPEARED_REV to SVN_INVALID_REVNUM.
+
+ NOTE: Any of PREV_PATH, PREV_REV, and APPEARED_REV may be NULL to
+ if that information is of no interest to the caller. */
+svn_error_t *
+svn_repos__prev_location(svn_revnum_t *appeared_rev,
+ const char **prev_path,
+ svn_revnum_t *prev_rev,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ const char *path,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_REPOS_H */
diff --git a/contrib/subversion/subversion/libsvn_repos/rev_hunt.c b/contrib/subversion/subversion/libsvn_repos/rev_hunt.c
new file mode 100644
index 0000000..77b1f2a
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_repos/rev_hunt.c
@@ -0,0 +1,1699 @@
+/* rev_hunt.c --- routines to hunt down particular fs revisions and
+ * their properties.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <string.h>
+#include "svn_compat.h"
+#include "svn_private_config.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_fs.h"
+#include "svn_repos.h"
+#include "svn_string.h"
+#include "svn_time.h"
+#include "svn_sorts.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "repos.h"
+#include "private/svn_fspath.h"
+
+
+/* Note: this binary search assumes that the datestamp properties on
+ each revision are in chronological order. That is if revision A >
+ revision B, then A's datestamp is younger then B's datestamp.
+
+ If someone comes along and sets a bogus datestamp, this routine
+ might not work right.
+
+ ### todo: you know, we *could* have svn_fs_change_rev_prop() do
+ some semantic checking when it's asked to change special reserved
+ svn: properties. It could prevent such a problem. */
+
+
+/* helper for svn_repos_dated_revision().
+
+ Set *TM to the apr_time_t datestamp on revision REV in FS. */
+static svn_error_t *
+get_time(apr_time_t *tm,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ svn_string_t *date_str;
+
+ SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
+ pool));
+ if (! date_str)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Failed to find time on revision %ld"), rev);
+
+ return svn_time_from_cstring(tm, date_str->data, pool);
+}
+
+
+svn_error_t *
+svn_repos_dated_revision(svn_revnum_t *revision,
+ svn_repos_t *repos,
+ apr_time_t tm,
+ apr_pool_t *pool)
+{
+ svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest;
+ apr_time_t this_time;
+ svn_fs_t *fs = repos->fs;
+
+ /* Initialize top and bottom values of binary search. */
+ SVN_ERR(svn_fs_youngest_rev(&rev_latest, fs, pool));
+ rev_bot = 0;
+ rev_top = rev_latest;
+
+ while (rev_bot <= rev_top)
+ {
+ rev_mid = (rev_top + rev_bot) / 2;
+ SVN_ERR(get_time(&this_time, fs, rev_mid, pool));
+
+ if (this_time > tm)/* we've overshot */
+ {
+ apr_time_t previous_time;
+
+ if ((rev_mid - 1) < 0)
+ {
+ *revision = 0;
+ break;
+ }
+
+ /* see if time falls between rev_mid and rev_mid-1: */
+ SVN_ERR(get_time(&previous_time, fs, rev_mid - 1, pool));
+ if (previous_time <= tm)
+ {
+ *revision = rev_mid - 1;
+ break;
+ }
+
+ rev_top = rev_mid - 1;
+ }
+
+ else if (this_time < tm) /* we've undershot */
+ {
+ apr_time_t next_time;
+
+ if ((rev_mid + 1) > rev_latest)
+ {
+ *revision = rev_latest;
+ break;
+ }
+
+ /* see if time falls between rev_mid and rev_mid+1: */
+ SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool));
+ if (next_time > tm)
+ {
+ *revision = rev_mid;
+ break;
+ }
+
+ rev_bot = rev_mid + 1;
+ }
+
+ else
+ {
+ *revision = rev_mid; /* exact match! */
+ break;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_get_committed_info(svn_revnum_t *committed_rev,
+ const char **committed_date,
+ const char **last_author,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_hash_t *revprops;
+
+ svn_fs_t *fs = svn_fs_root_fs(root);
+
+ /* ### It might be simpler just to declare that revision
+ properties have char * (i.e., UTF-8) values, not arbitrary
+ binary values, hmmm. */
+ svn_string_t *committed_date_s, *last_author_s;
+
+ /* Get the CR field out of the node's skel. */
+ SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool));
+
+ /* Get the revision properties of this revision. */
+ SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool));
+
+ /* Extract date and author from these revprops. */
+ committed_date_s = apr_hash_get(revprops,
+ SVN_PROP_REVISION_DATE,
+ sizeof(SVN_PROP_REVISION_DATE)-1);
+ last_author_s = apr_hash_get(revprops,
+ SVN_PROP_REVISION_AUTHOR,
+ sizeof(SVN_PROP_REVISION_AUTHOR)-1);
+
+ *committed_date = committed_date_s ? committed_date_s->data : NULL;
+ *last_author = last_author_s ? last_author_s->data : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_history2(svn_fs_t *fs,
+ const char *path,
+ svn_repos_history_func_t history_func,
+ void *history_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t cross_copies,
+ apr_pool_t *pool)
+{
+ svn_fs_history_t *history;
+ apr_pool_t *oldpool = svn_pool_create(pool);
+ apr_pool_t *newpool = svn_pool_create(pool);
+ const char *history_path;
+ svn_revnum_t history_rev;
+ svn_fs_root_t *root;
+
+ /* Validate the revisions. */
+ if (! SVN_IS_VALID_REVNUM(start))
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ _("Invalid start revision %ld"), start);
+ if (! SVN_IS_VALID_REVNUM(end))
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ _("Invalid end revision %ld"), end);
+
+ /* Ensure that the input is ordered. */
+ if (start > end)
+ {
+ svn_revnum_t tmprev = start;
+ start = end;
+ end = tmprev;
+ }
+
+ /* Get a revision root for END, and an initial HISTORY baton. */
+ SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
+
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+ SVN_ERR(authz_read_func(&readable, root, path,
+ authz_read_baton, pool));
+ if (! readable)
+ return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
+ }
+
+ SVN_ERR(svn_fs_node_history(&history, root, path, oldpool));
+
+ /* Now, we loop over the history items, calling svn_fs_history_prev(). */
+ do
+ {
+ /* Note that we have to do some crazy pool work here. We can't
+ get rid of the old history until we use it to get the new, so
+ we alternate back and forth between our subpools. */
+ apr_pool_t *tmppool;
+ svn_error_t *err;
+
+ SVN_ERR(svn_fs_history_prev(&history, history, cross_copies, newpool));
+
+ /* Only continue if there is further history to deal with. */
+ if (! history)
+ break;
+
+ /* Fetch the location information for this history step. */
+ SVN_ERR(svn_fs_history_location(&history_path, &history_rev,
+ history, newpool));
+
+ /* If this history item predates our START revision, quit
+ here. */
+ if (history_rev < start)
+ break;
+
+ /* Is the history item readable? If not, quit. */
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+ svn_fs_root_t *history_root;
+ SVN_ERR(svn_fs_revision_root(&history_root, fs,
+ history_rev, newpool));
+ SVN_ERR(authz_read_func(&readable, history_root, history_path,
+ authz_read_baton, newpool));
+ if (! readable)
+ break;
+ }
+
+ /* Call the user-provided callback function. */
+ err = history_func(history_baton, history_path, history_rev, newpool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
+ {
+ svn_error_clear(err);
+ goto cleanup;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ /* We're done with the old history item, so we can clear its
+ pool, and then toggle our notion of "the old pool". */
+ svn_pool_clear(oldpool);
+ tmppool = oldpool;
+ oldpool = newpool;
+ newpool = tmppool;
+ }
+ while (history); /* shouldn't hit this */
+
+ cleanup:
+ svn_pool_destroy(oldpool);
+ svn_pool_destroy(newpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_deleted_rev(svn_fs_t *fs,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_revnum_t *deleted,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool;
+ svn_fs_root_t *root, *copy_root;
+ const char *copy_path;
+ svn_revnum_t mid_rev;
+ const svn_fs_id_t *start_node_id, *curr_node_id;
+ svn_error_t *err;
+
+ /* Validate the revision range. */
+ if (! SVN_IS_VALID_REVNUM(start))
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ _("Invalid start revision %ld"), start);
+ if (! SVN_IS_VALID_REVNUM(end))
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ _("Invalid end revision %ld"), end);
+
+ /* Ensure that the input is ordered. */
+ if (start > end)
+ {
+ svn_revnum_t tmprev = start;
+ start = end;
+ end = tmprev;
+ }
+
+ /* Ensure path exists in fs at start revision. */
+ SVN_ERR(svn_fs_revision_root(&root, fs, start, pool));
+ err = svn_fs_node_id(&start_node_id, root, path, pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ /* Path must exist in fs at start rev. */
+ *deleted = SVN_INVALID_REVNUM;
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+ }
+
+ /* Ensure path was deleted at or before end revision. */
+ SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
+ err = svn_fs_node_id(&curr_node_id, root, path, pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ }
+ else if (err)
+ {
+ return svn_error_trace(err);
+ }
+ else
+ {
+ /* path exists in the end node and the end node is equivalent
+ or otherwise equivalent to the start node. This can mean
+ a few things:
+
+ 1) The end node *is* simply the start node, uncopied
+ and unmodified in the start to end range.
+
+ 2) The start node was modified, but never copied.
+
+ 3) The start node was copied, but this copy occurred at
+ start or some rev *previous* to start, this is
+ effectively the same situation as 1 if the node was
+ never modified or 2 if it was.
+
+ In the first three cases the path was not deleted in
+ the specified range and we are done, but in the following
+ cases the start node must have been deleted at least once:
+
+ 4) The start node was deleted and replaced by a copy of
+ itself at some rev between start and end. This copy
+ may itself have been replaced with copies of itself.
+
+ 5) The start node was deleted and replaced by a node which
+ it does not share any history with.
+ */
+ SVN_ERR(svn_fs_node_id(&curr_node_id, root, path, pool));
+ if (svn_fs_compare_ids(start_node_id, curr_node_id) != -1)
+ {
+ SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root,
+ path, pool));
+ if (!copy_root ||
+ (svn_fs_revision_root_revision(copy_root) <= start))
+ {
+ /* Case 1,2 or 3, nothing more to do. */
+ *deleted = SVN_INVALID_REVNUM;
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+
+ /* If we get here we know that path exists in rev start and was deleted
+ at least once before rev end. To find the revision path was first
+ deleted we use a binary search. The rules for the determining if
+ the deletion comes before or after a given median revision are
+ described by this matrix:
+
+ | Most recent copy event that
+ | caused mid node to exist.
+ |-----------------------------------------------------
+ Compare path | | | |
+ at start and | Copied at | Copied at | Never copied |
+ mid nodes. | rev > start | rev <= start | |
+ | | | |
+ -------------------------------------------------------------------|
+ Mid node is | A) Start node | |
+ equivalent to | replaced with | E) Mid node == start node, |
+ start node | an unmodified | look HIGHER. |
+ | copy of | |
+ | itself, | |
+ | look LOWER. | |
+ -------------------------------------------------------------------|
+ Mid node is | B) Start node | |
+ otherwise | replaced with | F) Mid node is a modified |
+ related to | a modified | version of start node, |
+ start node | copy of | look HIGHER. |
+ | itself, | |
+ | look LOWER. | |
+ -------------------------------------------------------------------|
+ Mid node is | |
+ unrelated to | C) Start node replaced with unrelated mid node, |
+ start node | look LOWER. |
+ | |
+ -------------------------------------------------------------------|
+ Path doesn't | |
+ exist at mid | D) Start node deleted before mid node, |
+ node | look LOWER |
+ | |
+ --------------------------------------------------------------------
+ */
+
+ mid_rev = (start + end) / 2;
+ subpool = svn_pool_create(pool);
+
+ while (1)
+ {
+ svn_pool_clear(subpool);
+
+ /* Get revision root and node id for mid_rev at that revision. */
+ SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, subpool));
+ err = svn_fs_node_id(&curr_node_id, root, path, subpool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ /* Case D: Look lower in the range. */
+ svn_error_clear(err);
+ end = mid_rev;
+ mid_rev = (start + mid_rev) / 2;
+ }
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ /* Determine the relationship between the start node
+ and the current node. */
+ int cmp = svn_fs_compare_ids(start_node_id, curr_node_id);
+ SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root,
+ path, subpool));
+ if (cmp == -1 ||
+ (copy_root &&
+ (svn_fs_revision_root_revision(copy_root) > start)))
+ {
+ /* Cases A, B, C: Look at lower revs. */
+ end = mid_rev;
+ mid_rev = (start + mid_rev) / 2;
+ }
+ else if (end - mid_rev == 1)
+ {
+ /* Found the node path was deleted. */
+ *deleted = end;
+ break;
+ }
+ else
+ {
+ /* Cases E, F: Look at higher revs. */
+ start = mid_rev;
+ mid_rev = (start + end) / 2;
+ }
+ }
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
+ unreadable. */
+static svn_error_t *
+check_readability(svn_fs_root_t *root,
+ const char *path,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ svn_boolean_t readable;
+ SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool));
+ if (! readable)
+ return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL,
+ _("Unreadable path encountered; access denied"));
+ return SVN_NO_ERROR;
+}
+
+
+/* The purpose of this function is to discover if fs_path@future_rev
+ * is derived from fs_path@peg_rev. The return is placed in *is_ancestor. */
+
+static svn_error_t *
+check_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
+ svn_fs_t *fs,
+ const char *fs_path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t future_revision,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_fs_history_t *history;
+ const char *path = NULL;
+ svn_revnum_t revision;
+ apr_pool_t *lastpool, *currpool;
+
+ lastpool = svn_pool_create(pool);
+ currpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_fs_revision_root(&root, fs, future_revision, pool));
+
+ SVN_ERR(svn_fs_node_history(&history, root, fs_path, lastpool));
+
+ /* Since paths that are different according to strcmp may still be
+ equivalent (due to number of consecutive slashes and the fact that
+ "" is the same as "/"), we get the "canonical" path in the first
+ iteration below so that the comparison after the loop will work
+ correctly. */
+ fs_path = NULL;
+
+ while (1)
+ {
+ apr_pool_t *tmppool;
+
+ SVN_ERR(svn_fs_history_prev(&history, history, TRUE, currpool));
+
+ if (!history)
+ break;
+
+ SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool));
+
+ if (!fs_path)
+ fs_path = apr_pstrdup(pool, path);
+
+ if (revision <= peg_revision)
+ break;
+
+ /* Clear old pool and flip. */
+ svn_pool_clear(lastpool);
+ tmppool = lastpool;
+ lastpool = currpool;
+ currpool = tmppool;
+ }
+
+ /* We must have had at least one iteration above where we
+ reassigned fs_path. Else, the path wouldn't have existed at
+ future_revision and svn_fs_history would have thrown. */
+ SVN_ERR_ASSERT(fs_path != NULL);
+
+ *is_ancestor = (history && strcmp(path, fs_path) == 0);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos__prev_location(svn_revnum_t *appeared_rev,
+ const char **prev_path,
+ svn_revnum_t *prev_rev,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root, *copy_root;
+ const char *copy_path, *copy_src_path, *remainder;
+ svn_revnum_t copy_src_rev;
+
+ /* Initialize return variables. */
+ if (appeared_rev)
+ *appeared_rev = SVN_INVALID_REVNUM;
+ if (prev_rev)
+ *prev_rev = SVN_INVALID_REVNUM;
+ if (prev_path)
+ *prev_path = NULL;
+
+ /* Ask about the most recent copy which affected PATH@REVISION. If
+ there was no such copy, we're done. */
+ SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
+ SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root, path, pool));
+ if (! copy_root)
+ return SVN_NO_ERROR;
+
+ /* Ultimately, it's not the path of the closest copy's source that
+ we care about -- it's our own path's location in the copy source
+ revision. So we'll tack the relative path that expresses the
+ difference between the copy destination and our path in the copy
+ revision onto the copy source path to determine this information.
+
+ In other words, if our path is "/branches/my-branch/foo/bar", and
+ we know that the closest relevant copy was a copy of "/trunk" to
+ "/branches/my-branch", then that relative path under the copy
+ destination is "/foo/bar". Tacking that onto the copy source
+ path tells us that our path was located at "/trunk/foo/bar"
+ before the copy.
+ */
+ SVN_ERR(svn_fs_copied_from(&copy_src_rev, &copy_src_path,
+ copy_root, copy_path, pool));
+ remainder = svn_fspath__skip_ancestor(copy_path, path);
+ if (prev_path)
+ *prev_path = svn_fspath__join(copy_src_path, remainder, pool);
+ if (appeared_rev)
+ *appeared_rev = svn_fs_revision_root_revision(copy_root);
+ if (prev_rev)
+ *prev_rev = copy_src_rev;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_trace_node_locations(svn_fs_t *fs,
+ apr_hash_t **locations,
+ const char *fs_path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions_orig,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *location_revisions;
+ svn_revnum_t *revision_ptr, *revision_ptr_end;
+ svn_fs_root_t *root;
+ const char *path;
+ svn_revnum_t revision;
+ svn_boolean_t is_ancestor;
+ apr_pool_t *lastpool, *currpool;
+ const svn_fs_id_t *id;
+
+ SVN_ERR_ASSERT(location_revisions_orig->elt_size == sizeof(svn_revnum_t));
+
+ /* Ensure that FS_PATH is absolute, because our path-math below will
+ depend on that being the case. */
+ if (*fs_path != '/')
+ fs_path = apr_pstrcat(pool, "/", fs_path, (char *)NULL);
+
+ /* Another sanity check. */
+ if (authz_read_func)
+ {
+ svn_fs_root_t *peg_root;
+ SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
+ SVN_ERR(check_readability(peg_root, fs_path,
+ authz_read_func, authz_read_baton, pool));
+ }
+
+ *locations = apr_hash_make(pool);
+
+ /* We flip between two pools in the second loop below. */
+ lastpool = svn_pool_create(pool);
+ currpool = svn_pool_create(pool);
+
+ /* First - let's sort the array of the revisions from the greatest revision
+ * downward, so it will be easier to search on. */
+ location_revisions = apr_array_copy(pool, location_revisions_orig);
+ qsort(location_revisions->elts, location_revisions->nelts,
+ sizeof(*revision_ptr), svn_sort_compare_revisions);
+
+ revision_ptr = (svn_revnum_t *)location_revisions->elts;
+ revision_ptr_end = revision_ptr + location_revisions->nelts;
+
+ /* Ignore revisions R that are younger than the peg_revisions where
+ path@peg_revision is not an ancestor of path@R. */
+ is_ancestor = FALSE;
+ while (revision_ptr < revision_ptr_end && *revision_ptr > peg_revision)
+ {
+ svn_pool_clear(currpool);
+ SVN_ERR(check_ancestry_of_peg_path(&is_ancestor, fs, fs_path,
+ peg_revision, *revision_ptr,
+ currpool));
+ if (is_ancestor)
+ break;
+ ++revision_ptr;
+ }
+
+ revision = is_ancestor ? *revision_ptr : peg_revision;
+ path = fs_path;
+ if (authz_read_func)
+ {
+ SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
+ SVN_ERR(check_readability(root, fs_path, authz_read_func,
+ authz_read_baton, pool));
+ }
+
+ while (revision_ptr < revision_ptr_end)
+ {
+ apr_pool_t *tmppool;
+ svn_revnum_t appeared_rev, prev_rev;
+ const char *prev_path;
+
+ /* Find the target of the innermost copy relevant to path@revision.
+ The copy may be of path itself, or of a parent directory. */
+ SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
+ fs, revision, path, currpool));
+ if (! prev_path)
+ break;
+
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+ svn_fs_root_t *tmp_root;
+
+ SVN_ERR(svn_fs_revision_root(&tmp_root, fs, revision, currpool));
+ SVN_ERR(authz_read_func(&readable, tmp_root, path,
+ authz_read_baton, currpool));
+ if (! readable)
+ {
+ svn_pool_destroy(lastpool);
+ svn_pool_destroy(currpool);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Assign the current path to all younger revisions until we reach
+ the copy target rev. */
+ while ((revision_ptr < revision_ptr_end)
+ && (*revision_ptr >= appeared_rev))
+ {
+ /* *revision_ptr is allocated out of pool, so we can point
+ to in the hash table. */
+ apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
+ apr_pstrdup(pool, path));
+ revision_ptr++;
+ }
+
+ /* Ignore all revs between the copy target rev and the copy
+ source rev (non-inclusive). */
+ while ((revision_ptr < revision_ptr_end)
+ && (*revision_ptr > prev_rev))
+ revision_ptr++;
+
+ /* State update. */
+ path = prev_path;
+ revision = prev_rev;
+
+ /* Clear last pool and switch. */
+ svn_pool_clear(lastpool);
+ tmppool = lastpool;
+ lastpool = currpool;
+ currpool = tmppool;
+ }
+
+ /* There are no copies relevant to path@revision. So any remaining
+ revisions either predate the creation of path@revision or have
+ the node existing at the same path. We will look up path@lrev
+ for each remaining location-revision and make sure it is related
+ to path@revision. */
+ SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
+ SVN_ERR(svn_fs_node_id(&id, root, path, pool));
+ while (revision_ptr < revision_ptr_end)
+ {
+ svn_node_kind_t kind;
+ const svn_fs_id_t *lrev_id;
+
+ svn_pool_clear(currpool);
+ SVN_ERR(svn_fs_revision_root(&root, fs, *revision_ptr, currpool));
+ SVN_ERR(svn_fs_check_path(&kind, root, path, currpool));
+ if (kind == svn_node_none)
+ break;
+ SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool));
+ if (! svn_fs_check_related(id, lrev_id))
+ break;
+
+ /* The node exists at the same path; record that and advance. */
+ apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
+ apr_pstrdup(pool, path));
+ revision_ptr++;
+ }
+
+ /* Ignore any remaining location-revisions; they predate the
+ creation of path@revision. */
+
+ svn_pool_destroy(lastpool);
+ svn_pool_destroy(currpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of
+ its revision range fits between END_REV and START_REV, possibly
+ cropping the range so that it fits *entirely* in that range. */
+static svn_error_t *
+maybe_crop_and_send_segment(svn_location_segment_t *segment,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ /* We only want to transmit this segment if some portion of it
+ is between our END_REV and START_REV. */
+ if (! ((segment->range_start > start_rev)
+ || (segment->range_end < end_rev)))
+ {
+ /* Correct our segment range when the range straddles one of
+ our requested revision boundaries. */
+ if (segment->range_start < end_rev)
+ segment->range_start = end_rev;
+ if (segment->range_end > start_rev)
+ segment->range_end = start_rev;
+ SVN_ERR(receiver(segment, receiver_baton, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos_node_location_segments(svn_repos_t *repos,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = svn_repos_fs(repos);
+ svn_stringbuf_t *current_path;
+ svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev;
+ apr_pool_t *subpool;
+
+ /* No PEG_REVISION? We'll use HEAD. */
+ if (! SVN_IS_VALID_REVNUM(peg_revision))
+ {
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
+ peg_revision = youngest_rev;
+ }
+
+ /* No START_REV? We'll use HEAD (which we may have already fetched). */
+ if (! SVN_IS_VALID_REVNUM(start_rev))
+ {
+ if (SVN_IS_VALID_REVNUM(youngest_rev))
+ start_rev = youngest_rev;
+ else
+ SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool));
+ }
+
+ /* No END_REV? We'll use 0. */
+ end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0;
+
+ /* Are the revision properly ordered? They better be -- the API
+ demands it. */
+ SVN_ERR_ASSERT(end_rev <= start_rev);
+ SVN_ERR_ASSERT(start_rev <= peg_revision);
+
+ /* Ensure that PATH is absolute, because our path-math will depend
+ on that being the case. */
+ if (*path != '/')
+ path = apr_pstrcat(pool, "/", path, (char *)NULL);
+
+ /* Auth check. */
+ if (authz_read_func)
+ {
+ svn_fs_root_t *peg_root;
+ SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
+ SVN_ERR(check_readability(peg_root, path,
+ authz_read_func, authz_read_baton, pool));
+ }
+
+ /* Okay, let's get searching! */
+ subpool = svn_pool_create(pool);
+ current_rev = peg_revision;
+ current_path = svn_stringbuf_create(path, pool);
+ while (current_rev >= end_rev)
+ {
+ svn_revnum_t appeared_rev, prev_rev;
+ const char *cur_path, *prev_path;
+ svn_location_segment_t *segment;
+
+ svn_pool_clear(subpool);
+
+ cur_path = apr_pstrmemdup(subpool, current_path->data,
+ current_path->len);
+ segment = apr_pcalloc(subpool, sizeof(*segment));
+ segment->range_end = current_rev;
+ segment->range_start = end_rev;
+ /* segment path should be absolute without leading '/'. */
+ segment->path = cur_path + 1;
+
+ SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
+ fs, current_rev, cur_path, subpool));
+
+ /* If there are no previous locations for this thing (meaning,
+ it originated at the current path), then we simply need to
+ find its revision of origin to populate our final segment.
+ Otherwise, the APPEARED_REV is the start of current segment's
+ range. */
+ if (! prev_path)
+ {
+ svn_fs_root_t *revroot;
+ SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool));
+ SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot,
+ cur_path, subpool));
+ if (segment->range_start < end_rev)
+ segment->range_start = end_rev;
+ current_rev = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ segment->range_start = appeared_rev;
+ svn_stringbuf_set(current_path, prev_path);
+ current_rev = prev_rev;
+ }
+
+ /* Report our segment, providing it passes authz muster. */
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+ svn_fs_root_t *cur_rev_root;
+
+ /* authz_read_func requires path to have a leading slash. */
+ const char *abs_path = apr_pstrcat(subpool, "/", segment->path,
+ (char *)NULL);
+
+ SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs,
+ segment->range_end, subpool));
+ SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path,
+ authz_read_baton, subpool));
+ if (! readable)
+ return SVN_NO_ERROR;
+ }
+
+ /* Transmit the segment (if it's within the scope of our concern). */
+ SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev,
+ receiver, receiver_baton, subpool));
+
+ /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done
+ (and didn't ever reach END_REV). */
+ if (! SVN_IS_VALID_REVNUM(current_rev))
+ break;
+
+ /* If there's a gap in the history, we need to report as much
+ (if the gap is within the scope of our concern). */
+ if (segment->range_start - current_rev > 1)
+ {
+ svn_location_segment_t *gap_segment;
+ gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment));
+ gap_segment->range_end = segment->range_start - 1;
+ gap_segment->range_start = current_rev + 1;
+ gap_segment->path = NULL;
+ SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev,
+ receiver, receiver_baton,
+ subpool));
+ }
+ }
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */
+static svn_error_t *
+get_path_mergeinfo(apr_hash_t **mergeinfo,
+ svn_fs_t *fs,
+ const char *path,
+ svn_revnum_t revnum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_catalog_t tmp_catalog;
+ svn_fs_root_t *root;
+ apr_array_header_t *paths = apr_array_make(scratch_pool, 1,
+ sizeof(const char *));
+
+ APR_ARRAY_PUSH(paths, const char *) = path;
+
+ SVN_ERR(svn_fs_revision_root(&root, fs, revnum, scratch_pool));
+ /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
+ because we will filter out unreadable revisions in
+ find_interesting_revision(), above */
+ SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, paths,
+ svn_mergeinfo_inherited, FALSE, TRUE,
+ result_pool, scratch_pool));
+
+ *mergeinfo = svn_hash_gets(tmp_catalog, path);
+ if (!*mergeinfo)
+ *mergeinfo = apr_hash_make(result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+static APR_INLINE svn_boolean_t
+is_path_in_hash(apr_hash_t *duplicate_path_revs,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ const char *key = apr_psprintf(pool, "%s:%ld", path, revision);
+ void *ptr;
+
+ ptr = svn_hash_gets(duplicate_path_revs, key);
+ return ptr != NULL;
+}
+
+struct path_revision
+{
+ svn_revnum_t revnum;
+ const char *path;
+
+ /* Does this path_rev have merges to also be included? */
+ apr_hash_t *merged_mergeinfo;
+
+ /* Is this a merged revision? */
+ svn_boolean_t merged;
+};
+
+/* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store
+ the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL. The
+ returned *MERGED_MERGEINFO will be NULL if there are no changes. */
+static svn_error_t *
+get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
+ svn_repos_t *repos,
+ struct path_revision *old_path_rev,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
+ svn_error_t *err;
+ svn_fs_root_t *root;
+ apr_hash_t *changed_paths;
+ const char *path = old_path_rev->path;
+
+ /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
+ if there is a property change. */
+ SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
+ scratch_pool));
+ SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool));
+ while (1)
+ {
+ svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
+ if (changed_path && changed_path->prop_mod)
+ break;
+ if (svn_fspath__is_root(path, strlen(path)))
+ {
+ *merged_mergeinfo = NULL;
+ return SVN_NO_ERROR;
+ }
+ path = svn_fspath__dirname(path, scratch_pool);
+ }
+
+ /* First, find the mergeinfo difference for old_path_rev->revnum, and
+ old_path_rev->revnum - 1. */
+ err = get_path_mergeinfo(&curr_mergeinfo, repos->fs, old_path_rev->path,
+ old_path_rev->revnum, scratch_pool,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ /* Issue #3896: If invalid mergeinfo is encountered the
+ best we can do is ignore it and act is if there are
+ no mergeinfo differences. */
+ svn_error_clear(err);
+ *merged_mergeinfo = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ err = get_path_mergeinfo(&prev_mergeinfo, repos->fs, old_path_rev->path,
+ old_path_rev->revnum - 1, scratch_pool,
+ scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND
+ || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
+ {
+ /* If the path doesn't exist in the previous revision or it does exist
+ but has invalid mergeinfo (Issue #3896), assume no merges. */
+ svn_error_clear(err);
+ *merged_mergeinfo = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ /* Then calculate and merge the differences. */
+ SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo,
+ curr_mergeinfo, FALSE, result_pool,
+ scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool));
+
+ /* Store the result. */
+ if (apr_hash_count(changed))
+ *merged_mergeinfo = changed;
+ else
+ *merged_mergeinfo = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+find_interesting_revisions(apr_array_header_t *path_revisions,
+ svn_repos_t *repos,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_boolean_t mark_as_merged,
+ apr_hash_t *duplicate_path_revs,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool, *last_pool;
+ svn_fs_history_t *history;
+ svn_fs_root_t *root;
+ svn_node_kind_t kind;
+
+ /* We switch between two pools while looping, since we need information from
+ the last iteration to be available. */
+ iterpool = svn_pool_create(scratch_pool);
+ last_pool = svn_pool_create(scratch_pool);
+
+ /* The path had better be a file in this revision. */
+ SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
+ SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"),
+ path, end);
+
+ /* Open a history object. */
+ SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
+ while (1)
+ {
+ struct path_revision *path_rev;
+ svn_revnum_t tmp_revnum;
+ const char *tmp_path;
+ apr_pool_t *tmp_pool;
+
+ svn_pool_clear(iterpool);
+
+ /* Fetch the history object to walk through. */
+ SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
+ if (!history)
+ break;
+ SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
+ history, iterpool));
+
+ /* Check to see if we already saw this path (and it's ancestors) */
+ if (include_merged_revisions
+ && is_path_in_hash(duplicate_path_revs, tmp_path,
+ tmp_revnum, iterpool))
+ break;
+
+ /* Check authorization. */
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+ svn_fs_root_t *tmp_root;
+
+ SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
+ iterpool));
+ SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
+ authz_read_baton, iterpool));
+ if (! readable)
+ break;
+ }
+
+ /* We didn't break, so we must really want this path-rev. */
+ path_rev = apr_palloc(result_pool, sizeof(*path_rev));
+ path_rev->path = apr_pstrdup(result_pool, tmp_path);
+ path_rev->revnum = tmp_revnum;
+ path_rev->merged = mark_as_merged;
+ APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev;
+
+ if (include_merged_revisions)
+ SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos,
+ path_rev, result_pool, iterpool));
+ else
+ path_rev->merged_mergeinfo = NULL;
+
+ /* Add the path/rev pair to the hash, so we can filter out future
+ occurrences of it. We only care about this if including merged
+ revisions, 'cause that's the only time we can have duplicates. */
+ svn_hash_sets(duplicate_path_revs,
+ apr_psprintf(result_pool, "%s:%ld", path_rev->path,
+ path_rev->revnum),
+ (void *)0xdeadbeef);
+
+ if (path_rev->revnum <= start)
+ break;
+
+ /* Swap pools. */
+ tmp_pool = iterpool;
+ iterpool = last_pool;
+ last_pool = tmp_pool;
+ }
+
+ svn_pool_destroy(iterpool);
+ svn_pool_destroy(last_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Comparison function to sort path/revisions in increasing order */
+static int
+compare_path_revisions(const void *a, const void *b)
+{
+ struct path_revision *a_pr = *(struct path_revision *const *)a;
+ struct path_revision *b_pr = *(struct path_revision *const *)b;
+
+ if (a_pr->revnum == b_pr->revnum)
+ return 0;
+
+ return a_pr->revnum < b_pr->revnum ? 1 : -1;
+}
+
+static svn_error_t *
+find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
+ svn_revnum_t start,
+ const apr_array_header_t *mainline_path_revisions,
+ svn_repos_t *repos,
+ apr_hash_t *duplicate_path_revs,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *old;
+ apr_array_header_t *new_merged_path_revs;
+ apr_pool_t *iterpool, *last_pool;
+ apr_array_header_t *merged_path_revisions =
+ apr_array_make(scratch_pool, 0, sizeof(struct path_revision *));
+
+ old = mainline_path_revisions;
+ iterpool = svn_pool_create(scratch_pool);
+ last_pool = svn_pool_create(scratch_pool);
+
+ do
+ {
+ int i;
+ apr_pool_t *temp_pool;
+
+ svn_pool_clear(iterpool);
+ new_merged_path_revs = apr_array_make(iterpool, 0,
+ sizeof(struct path_revision *));
+
+ /* Iterate over OLD, checking for non-empty mergeinfo. If found, gather
+ path_revisions for any merged revisions, and store those in NEW. */
+ for (i = 0; i < old->nelts; i++)
+ {
+ apr_pool_t *iterpool2;
+ apr_hash_index_t *hi;
+ struct path_revision *old_pr = APR_ARRAY_IDX(old, i,
+ struct path_revision *);
+ if (!old_pr->merged_mergeinfo)
+ continue;
+
+ iterpool2 = svn_pool_create(iterpool);
+
+ /* Determine and trace the merge sources. */
+ for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi;
+ hi = apr_hash_next(hi))
+ {
+ apr_pool_t *iterpool3;
+ svn_rangelist_t *rangelist;
+ const char *path;
+ int j;
+
+ svn_pool_clear(iterpool2);
+ iterpool3 = svn_pool_create(iterpool2);
+
+ apr_hash_this(hi, (void *) &path, NULL, (void *) &rangelist);
+
+ for (j = 0; j < rangelist->nelts; j++)
+ {
+ svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j,
+ svn_merge_range_t *);
+ svn_node_kind_t kind;
+ svn_fs_root_t *root;
+
+ if (range->end < start)
+ continue;
+
+ svn_pool_clear(iterpool3);
+
+ SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end,
+ iterpool3));
+ SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3));
+ if (kind != svn_node_file)
+ continue;
+
+ /* Search and find revisions to add to the NEW list. */
+ SVN_ERR(find_interesting_revisions(new_merged_path_revs,
+ repos, path,
+ range->start, range->end,
+ TRUE, TRUE,
+ duplicate_path_revs,
+ authz_read_func,
+ authz_read_baton,
+ result_pool, iterpool3));
+ }
+ svn_pool_destroy(iterpool3);
+ }
+ svn_pool_destroy(iterpool2);
+ }
+
+ /* Append the newly found path revisions with the old ones. */
+ merged_path_revisions = apr_array_append(iterpool, merged_path_revisions,
+ new_merged_path_revs);
+
+ /* Swap data structures */
+ old = new_merged_path_revs;
+ temp_pool = last_pool;
+ last_pool = iterpool;
+ iterpool = temp_pool;
+ }
+ while (new_merged_path_revs->nelts > 0);
+
+ /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */
+ qsort(merged_path_revisions->elts, merged_path_revisions->nelts,
+ sizeof(struct path_revision *), compare_path_revisions);
+
+ /* Copy to the output array. */
+ *merged_path_revisions_out = apr_array_copy(result_pool,
+ merged_path_revisions);
+
+ svn_pool_destroy(iterpool);
+ svn_pool_destroy(last_pool);
+
+ return SVN_NO_ERROR;
+}
+
+struct send_baton
+{
+ apr_pool_t *iterpool;
+ apr_pool_t *last_pool;
+ apr_hash_t *last_props;
+ const char *last_path;
+ svn_fs_root_t *last_root;
+};
+
+/* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by
+ SB. */
+static svn_error_t *
+send_path_revision(struct path_revision *path_rev,
+ svn_repos_t *repos,
+ struct send_baton *sb,
+ svn_file_rev_handler_t handler,
+ void *handler_baton)
+{
+ apr_hash_t *rev_props;
+ apr_hash_t *props;
+ apr_array_header_t *prop_diffs;
+ svn_fs_root_t *root;
+ svn_txdelta_stream_t *delta_stream;
+ svn_txdelta_window_handler_t delta_handler = NULL;
+ void *delta_baton = NULL;
+ apr_pool_t *tmp_pool; /* For swapping */
+ svn_boolean_t contents_changed;
+
+ svn_pool_clear(sb->iterpool);
+
+ /* Get the revision properties. */
+ SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs,
+ path_rev->revnum, sb->iterpool));
+
+ /* Open the revision root. */
+ SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum,
+ sb->iterpool));
+
+ /* Get the file's properties for this revision and compute the diffs. */
+ SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
+ sb->iterpool));
+ SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
+ sb->iterpool));
+
+ /* Check if the contents changed. */
+ /* Special case: In the first revision, we always provide a delta. */
+ if (sb->last_root)
+ SVN_ERR(svn_fs_contents_changed(&contents_changed, sb->last_root,
+ sb->last_path, root, path_rev->path,
+ sb->iterpool));
+ else
+ contents_changed = TRUE;
+
+ /* We have all we need, give to the handler. */
+ SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum,
+ rev_props, path_rev->merged,
+ contents_changed ? &delta_handler : NULL,
+ contents_changed ? &delta_baton : NULL,
+ prop_diffs, sb->iterpool));
+
+ /* Compute and send delta if client asked for it.
+ Note that this was initialized to NULL, so if !contents_changed,
+ no deltas will be computed. */
+ if (delta_handler)
+ {
+ /* Get the content delta. */
+ SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
+ sb->last_root, sb->last_path,
+ root, path_rev->path,
+ sb->iterpool));
+ /* And send. */
+ SVN_ERR(svn_txdelta_send_txstream(delta_stream,
+ delta_handler, delta_baton,
+ sb->iterpool));
+ }
+
+ /* Remember root, path and props for next iteration. */
+ sb->last_root = root;
+ sb->last_path = path_rev->path;
+ sb->last_props = props;
+
+ /* Swap the pools. */
+ tmp_pool = sb->iterpool;
+ sb->iterpool = sb->last_pool;
+ sb->last_pool = tmp_pool;
+
+ return SVN_NO_ERROR;
+}
+
+/* Similar to svn_repos_get_file_revs2() but returns paths while walking
+ history instead of after collecting all history.
+
+ This allows implementing clients to immediately start processing and
+ stop when they got the information they need. (E.g. all or a specific set
+ of lines were modified) */
+static svn_error_t *
+get_file_revs_backwards(svn_repos_t *repos,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool, *last_pool;
+ svn_fs_history_t *history;
+ svn_fs_root_t *root;
+ svn_node_kind_t kind;
+ struct send_baton sb;
+
+ /* We switch between two pools while looping and so does the path-rev
+ handler for actually reported revisions. We do this as we
+ need just information from last iteration to be available. */
+
+ iterpool = svn_pool_create(scratch_pool);
+ last_pool = svn_pool_create(scratch_pool);
+ sb.iterpool = svn_pool_create(scratch_pool);
+ sb.last_pool = svn_pool_create(scratch_pool);
+
+ /* We want the first txdelta to be against the empty file. */
+ sb.last_root = NULL;
+ sb.last_path = NULL;
+
+ /* Create an empty hash table for the first property diff. */
+ sb.last_props = apr_hash_make(sb.last_pool);
+
+ /* The path had better be a file in this revision. */
+ SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
+ SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_FS_NOT_FILE,
+ NULL, _("'%s' is not a file in revision %ld"),
+ path, end);
+
+ /* Open a history object. */
+ SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
+ while (1)
+ {
+ struct path_revision *path_rev;
+ svn_revnum_t tmp_revnum;
+ const char *tmp_path;
+
+ svn_pool_clear(iterpool);
+
+ /* Fetch the history object to walk through. */
+ SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
+ if (!history)
+ break;
+ SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
+ history, iterpool));
+
+ /* Check authorization. */
+ if (authz_read_func)
+ {
+ svn_boolean_t readable;
+ svn_fs_root_t *tmp_root;
+
+ SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
+ iterpool));
+ SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
+ authz_read_baton, iterpool));
+ if (! readable)
+ break;
+ }
+
+ /* We didn't break, so we must really want this path-rev. */
+ path_rev = apr_palloc(iterpool, sizeof(*path_rev));
+ path_rev->path = tmp_path;
+ path_rev->revnum = tmp_revnum;
+ path_rev->merged = FALSE;
+
+ SVN_ERR(send_path_revision(path_rev, repos, &sb,
+ handler, handler_baton));
+
+ if (path_rev->revnum <= start)
+ break;
+
+ /* Swap pools. */
+ {
+ apr_pool_t *tmp_pool = iterpool;
+ iterpool = last_pool;
+ last_pool = tmp_pool;
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ svn_pool_destroy(last_pool);
+ svn_pool_destroy(sb.last_pool);
+ svn_pool_destroy(sb.iterpool);
+
+ return SVN_NO_ERROR;
+
+}
+
+
+/* We don't yet support sending revisions in reverse order; the caller wait
+ * until we've traced back through the entire history, and then accept
+ * them from oldest to youngest. Someday this may change, but in the meantime,
+ * the general algorithm is thus:
+ *
+ * 1) Trace back through the history of an object, adding each revision
+ * found to the MAINLINE_PATH_REVISIONS array, marking any which were
+ * merges.
+ * 2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the
+ * merged revisions, including them in the MERGED_PATH_REVISIONS, and using
+ * DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple
+ * times.
+ * 3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from
+ * oldest to youngest, interleaving as appropriate. This is implemented
+ * similar to an insertion sort, but instead of inserting into another
+ * array, we just call the appropriate handler.
+ *
+ * 2013-02: Added a very simple reverse for mainline only changes. Before this,
+ * this would return an error (path not found) or just the first
+ * revision before end.
+ */
+svn_error_t *
+svn_repos_get_file_revs2(svn_repos_t *repos,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *mainline_path_revisions, *merged_path_revisions;
+ apr_hash_t *duplicate_path_revs;
+ struct send_baton sb;
+ int mainline_pos, merged_pos;
+
+ if (end < start)
+ {
+ if (include_merged_revisions)
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+
+ return svn_error_trace(
+ get_file_revs_backwards(repos, path,
+ end, start,
+ authz_read_func,
+ authz_read_baton,
+ handler,
+ handler_baton,
+ scratch_pool));
+ }
+
+ /* We switch between two pools while looping, since we need information from
+ the last iteration to be available. */
+ sb.iterpool = svn_pool_create(scratch_pool);
+ sb.last_pool = svn_pool_create(scratch_pool);
+
+ /* We want the first txdelta to be against the empty file. */
+ sb.last_root = NULL;
+ sb.last_path = NULL;
+
+ /* Create an empty hash table for the first property diff. */
+ sb.last_props = apr_hash_make(sb.last_pool);
+
+
+ /* Get the revisions we are interested in. */
+ duplicate_path_revs = apr_hash_make(scratch_pool);
+ mainline_path_revisions = apr_array_make(scratch_pool, 100,
+ sizeof(struct path_revision *));
+ SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path,
+ start, end, include_merged_revisions,
+ FALSE, duplicate_path_revs,
+ authz_read_func, authz_read_baton,
+ scratch_pool, sb.iterpool));
+
+ /* If we are including merged revisions, go get those, too. */
+ if (include_merged_revisions)
+ SVN_ERR(find_merged_revisions(&merged_path_revisions, start,
+ mainline_path_revisions, repos,
+ duplicate_path_revs, authz_read_func,
+ authz_read_baton,
+ scratch_pool, sb.iterpool));
+ else
+ merged_path_revisions = apr_array_make(scratch_pool, 0,
+ sizeof(struct path_revision *));
+
+ /* We must have at least one revision to get. */
+ SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0);
+
+ /* Walk through both mainline and merged revisions, and send them in
+ reverse chronological order, interleaving as appropriate. */
+ mainline_pos = mainline_path_revisions->nelts - 1;
+ merged_pos = merged_path_revisions->nelts - 1;
+ while (mainline_pos >= 0 && merged_pos >= 0)
+ {
+ struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
+ mainline_pos,
+ struct path_revision *);
+ struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
+ merged_pos,
+ struct path_revision *);
+
+ if (main_pr->revnum <= merged_pr->revnum)
+ {
+ SVN_ERR(send_path_revision(main_pr, repos, &sb, handler,
+ handler_baton));
+ mainline_pos -= 1;
+ }
+ else
+ {
+ SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
+ handler_baton));
+ merged_pos -= 1;
+ }
+ }
+
+ /* Send any remaining revisions from the mainline list. */
+ for (; mainline_pos >= 0; mainline_pos -= 1)
+ {
+ struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
+ mainline_pos,
+ struct path_revision *);
+ SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton));
+ }
+
+ /* Ditto for the merged list. */
+ for (; merged_pos >= 0; merged_pos -= 1)
+ {
+ struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
+ merged_pos,
+ struct path_revision *);
+ SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
+ handler_baton));
+ }
+
+ svn_pool_destroy(sb.last_pool);
+ svn_pool_destroy(sb.iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/adler32.c b/contrib/subversion/subversion/libsvn_subr/adler32.c
new file mode 100644
index 0000000..e290e68
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/adler32.c
@@ -0,0 +1,101 @@
+/*
+ * adler32.c : routines for handling Adler-32 checksums
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr.h>
+#include <zlib.h>
+
+#include "private/svn_adler32.h"
+
+/**
+ * An Adler-32 implementation per RFC1950.
+ *
+ * "The Adler-32 algorithm is much faster than the CRC32 algorithm yet
+ * still provides an extremely low probability of undetected errors"
+ */
+
+/*
+ * 65521 is the largest prime less than 65536.
+ * "That 65521 is prime is important to avoid a possible large class of
+ * two-byte errors that leave the check unchanged."
+ */
+#define ADLER_MOD_BASE 65521
+
+/*
+ * Start with CHECKSUM and update the checksum by processing a chunk
+ * of DATA sized LEN.
+ */
+apr_uint32_t
+svn__adler32(apr_uint32_t checksum, const char *data, apr_off_t len)
+{
+ /* The actual limit can be set somewhat higher but should
+ * not be lower because the SIMD code would not be used
+ * in that case.
+ *
+ * However, it must be lower than 5552 to make sure our local
+ * implementation does not suffer from overflows.
+ */
+ if (len >= 80)
+ {
+ /* Larger buffers can be effiently handled by Marc Adler's
+ * optimized code. Also, new zlib versions will come with
+ * SIMD code for x86 and x64.
+ */
+ return (apr_uint32_t)adler32(checksum,
+ (const Bytef *)data,
+ (uInt)len);
+ }
+ else
+ {
+ const unsigned char *input = (const unsigned char *)data;
+ apr_uint32_t s1 = checksum & 0xFFFF;
+ apr_uint32_t s2 = checksum >> 16;
+ apr_uint32_t b;
+
+ /* Some loop unrolling
+ * (approx. one clock tick per byte + 2 ticks loop overhead)
+ */
+ for (; len >= 8; len -= 8, input += 8)
+ {
+ s1 += input[0]; s2 += s1;
+ s1 += input[1]; s2 += s1;
+ s1 += input[2]; s2 += s1;
+ s1 += input[3]; s2 += s1;
+ s1 += input[4]; s2 += s1;
+ s1 += input[5]; s2 += s1;
+ s1 += input[6]; s2 += s1;
+ s1 += input[7]; s2 += s1;
+ }
+
+ /* Adler-32 calculation as a simple two ticks per iteration loop.
+ */
+ while (len--)
+ {
+ b = *input++;
+ s1 += b;
+ s2 += s1;
+ }
+
+ return ((s2 % ADLER_MOD_BASE) << 16) | (s1 % ADLER_MOD_BASE);
+ }
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/atomic.c b/contrib/subversion/subversion/libsvn_subr/atomic.c
new file mode 100644
index 0000000..b760da4
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/atomic.c
@@ -0,0 +1,85 @@
+/* atomic.c : perform atomic initialization
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_time.h>
+#include "private/svn_atomic.h"
+
+/* Magic values for atomic initialization */
+#define SVN_ATOMIC_UNINITIALIZED 0
+#define SVN_ATOMIC_START_INIT 1
+#define SVN_ATOMIC_INIT_FAILED 2
+#define SVN_ATOMIC_INITIALIZED 3
+
+svn_error_t*
+svn_atomic__init_once(volatile svn_atomic_t *global_status,
+ svn_error_t *(*init_func)(void*,apr_pool_t*),
+ void *baton,
+ apr_pool_t* pool)
+{
+ /* !! Don't use localizable strings in this function, because these
+ !! might cause deadlocks. This function can be used to initialize
+ !! libraries that are used for generating error messages. */
+
+ /* We have to call init_func exactly once. Because APR
+ doesn't have statically-initialized mutexes, we implement a poor
+ man's spinlock using svn_atomic_cas. */
+ svn_atomic_t status = svn_atomic_cas(global_status,
+ SVN_ATOMIC_START_INIT,
+ SVN_ATOMIC_UNINITIALIZED);
+
+ if (status == SVN_ATOMIC_UNINITIALIZED)
+ {
+ svn_error_t *err = init_func(baton, pool);
+ if (err)
+ {
+#if APR_HAS_THREADS
+ /* Tell other threads that the initialization failed. */
+ svn_atomic_cas(global_status,
+ SVN_ATOMIC_INIT_FAILED,
+ SVN_ATOMIC_START_INIT);
+#endif
+ return svn_error_create(SVN_ERR_ATOMIC_INIT_FAILURE, err,
+ "Couldn't perform atomic initialization");
+ }
+ svn_atomic_cas(global_status,
+ SVN_ATOMIC_INITIALIZED,
+ SVN_ATOMIC_START_INIT);
+ }
+#if APR_HAS_THREADS
+ /* Wait for whichever thread is performing initialization to finish. */
+ /* XXX FIXME: Should we have a maximum wait here, like we have in
+ the Windows file IO spinner? */
+ else while (status != SVN_ATOMIC_INITIALIZED)
+ {
+ if (status == SVN_ATOMIC_INIT_FAILED)
+ return svn_error_create(SVN_ERR_ATOMIC_INIT_FAILURE, NULL,
+ "Couldn't perform atomic initialization");
+
+ apr_sleep(APR_USEC_PER_SEC / 1000);
+ status = svn_atomic_cas(global_status,
+ SVN_ATOMIC_UNINITIALIZED,
+ SVN_ATOMIC_UNINITIALIZED);
+ }
+#endif /* APR_HAS_THREADS */
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/auth.c b/contrib/subversion/subversion/libsvn_subr/auth.c
new file mode 100644
index 0000000..5406358
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/auth.c
@@ -0,0 +1,652 @@
+/*
+ * auth.c: authentication support functions for Subversion
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+#include <apr_strings.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_auth.h"
+#include "svn_config.h"
+#include "svn_private_config.h"
+#include "svn_dso.h"
+#include "svn_version.h"
+#include "private/svn_dep_compat.h"
+
+#include "auth.h"
+
+/* AN OVERVIEW
+ ===========
+
+ A good way to think of this machinery is as a set of tables.
+
+ - Each type of credentials selects a single table.
+
+ - In a given table, each row is a 'provider' capable of returning
+ the same type of credentials. Each column represents a
+ provider's repeated attempts to provide credentials.
+
+
+ Fetching Credentials from Providers
+ -----------------------------------
+
+ When the caller asks for a particular type of credentials, the
+ machinery in this file walks over the appropriate table. It starts
+ with the first provider (first row), and calls first_credentials()
+ to get the first set of credentials (first column). If the caller
+ is unhappy with the credentials, then each subsequent call to
+ next_credentials() traverses the row from left to right. If the
+ provider returns error at any point, then we go to the next provider
+ (row). We continue this way until every provider fails, or
+ until the client is happy with the returned credentials.
+
+ Note that the caller cannot see the table traversal, and thus has
+ no idea when we switch providers.
+
+
+ Storing Credentials with Providers
+ ----------------------------------
+
+ When the server has validated a set of credentials, and when
+ credential caching is enabled, we have the chance to store those
+ credentials for later use. The provider which provided the working
+ credentials is the first one given the opportunity to (re)cache
+ those credentials. Its save_credentials() function is invoked with
+ the working credentials. If that provider reports that it
+ successfully stored the credentials, we're done. Otherwise, we
+ walk the providers (rows) for that type of credentials in order
+ from the top of the table, allowing each in turn the opportunity to
+ store the credentials. When one reports that it has done so
+ successfully -- or when we run out of providers (rows) to try --
+ the table walk ends.
+*/
+
+
+
+/* This effectively defines a single table. Every provider in this
+ array returns the same kind of credentials. */
+typedef struct provider_set_t
+{
+ /* ordered array of svn_auth_provider_object_t */
+ apr_array_header_t *providers;
+
+} provider_set_t;
+
+
+/* The main auth baton. */
+struct svn_auth_baton_t
+{
+ /* a collection of tables. maps cred_kind -> provider_set */
+ apr_hash_t *tables;
+
+ /* the pool I'm allocated in. */
+ apr_pool_t *pool;
+
+ /* run-time parameters needed by providers. */
+ apr_hash_t *parameters;
+
+ /* run-time credentials cache. */
+ apr_hash_t *creds_cache;
+
+};
+
+/* Abstracted iteration baton */
+struct svn_auth_iterstate_t
+{
+ provider_set_t *table; /* the table being searched */
+ int provider_idx; /* the current provider (row) */
+ svn_boolean_t got_first; /* did we get the provider's first creds? */
+ void *provider_iter_baton; /* the provider's own iteration context */
+ const char *realmstring; /* The original realmstring passed in */
+ const char *cache_key; /* key to use in auth_baton's creds_cache */
+ svn_auth_baton_t *auth_baton; /* the original auth_baton. */
+};
+
+
+
+void
+svn_auth_open(svn_auth_baton_t **auth_baton,
+ const apr_array_header_t *providers,
+ apr_pool_t *pool)
+{
+ svn_auth_baton_t *ab;
+ svn_auth_provider_object_t *provider;
+ int i;
+
+ /* Build the auth_baton. */
+ ab = apr_pcalloc(pool, sizeof(*ab));
+ ab->tables = apr_hash_make(pool);
+ ab->parameters = apr_hash_make(pool);
+ ab->creds_cache = apr_hash_make(pool);
+ ab->pool = pool;
+
+ /* Register each provider in order. Providers of different
+ credentials will be automatically sorted into different tables by
+ register_provider(). */
+ for (i = 0; i < providers->nelts; i++)
+ {
+ provider_set_t *table;
+ provider = APR_ARRAY_IDX(providers, i, svn_auth_provider_object_t *);
+
+ /* Add it to the appropriate table in the auth_baton */
+ table = svn_hash_gets(ab->tables, provider->vtable->cred_kind);
+ if (! table)
+ {
+ table = apr_pcalloc(pool, sizeof(*table));
+ table->providers
+ = apr_array_make(pool, 1, sizeof(svn_auth_provider_object_t *));
+
+ svn_hash_sets(ab->tables, provider->vtable->cred_kind, table);
+ }
+ APR_ARRAY_PUSH(table->providers, svn_auth_provider_object_t *)
+ = provider;
+ }
+
+ *auth_baton = ab;
+}
+
+
+
+void
+svn_auth_set_parameter(svn_auth_baton_t *auth_baton,
+ const char *name,
+ const void *value)
+{
+ svn_hash_sets(auth_baton->parameters, name, value);
+}
+
+const void *
+svn_auth_get_parameter(svn_auth_baton_t *auth_baton,
+ const char *name)
+{
+ return svn_hash_gets(auth_baton->parameters, name);
+}
+
+
+/* Return the key used to address the in-memory cache of auth
+ credentials of type CRED_KIND and associated with REALMSTRING. */
+static const char *
+make_cache_key(const char *cred_kind,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return apr_pstrcat(pool, cred_kind, ":", realmstring, (char *)NULL);
+}
+
+svn_error_t *
+svn_auth_first_credentials(void **credentials,
+ svn_auth_iterstate_t **state,
+ const char *cred_kind,
+ const char *realmstring,
+ svn_auth_baton_t *auth_baton,
+ apr_pool_t *pool)
+{
+ int i = 0;
+ provider_set_t *table;
+ svn_auth_provider_object_t *provider = NULL;
+ void *creds = NULL;
+ void *iter_baton = NULL;
+ svn_boolean_t got_first = FALSE;
+ svn_auth_iterstate_t *iterstate;
+ const char *cache_key;
+
+ /* Get the appropriate table of providers for CRED_KIND. */
+ table = svn_hash_gets(auth_baton->tables, cred_kind);
+ if (! table)
+ return svn_error_createf(SVN_ERR_AUTHN_NO_PROVIDER, NULL,
+ _("No provider registered for '%s' credentials"),
+ cred_kind);
+
+ /* First, see if we have cached creds in the auth_baton. */
+ cache_key = make_cache_key(cred_kind, realmstring, pool);
+ creds = svn_hash_gets(auth_baton->creds_cache, cache_key);
+ if (creds)
+ {
+ got_first = FALSE;
+ }
+ else
+ /* If not, find a provider that can give "first" credentials. */
+ {
+ /* Find a provider that can give "first" credentials. */
+ for (i = 0; i < table->providers->nelts; i++)
+ {
+ provider = APR_ARRAY_IDX(table->providers, i,
+ svn_auth_provider_object_t *);
+ SVN_ERR(provider->vtable->first_credentials(&creds, &iter_baton,
+ provider->provider_baton,
+ auth_baton->parameters,
+ realmstring,
+ auth_baton->pool));
+
+ if (creds != NULL)
+ {
+ got_first = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (! creds)
+ *state = NULL;
+ else
+ {
+ /* Build an abstract iteration state. */
+ iterstate = apr_pcalloc(pool, sizeof(*iterstate));
+ iterstate->table = table;
+ iterstate->provider_idx = i;
+ iterstate->got_first = got_first;
+ iterstate->provider_iter_baton = iter_baton;
+ iterstate->realmstring = apr_pstrdup(pool, realmstring);
+ iterstate->cache_key = cache_key;
+ iterstate->auth_baton = auth_baton;
+ *state = iterstate;
+
+ /* Put the creds in the cache */
+ svn_hash_sets(auth_baton->creds_cache,
+ apr_pstrdup(auth_baton->pool, cache_key),
+ creds);
+ }
+
+ *credentials = creds;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_auth_next_credentials(void **credentials,
+ svn_auth_iterstate_t *state,
+ apr_pool_t *pool)
+{
+ svn_auth_baton_t *auth_baton = state->auth_baton;
+ svn_auth_provider_object_t *provider;
+ provider_set_t *table = state->table;
+ void *creds = NULL;
+
+ /* Continue traversing the table from where we left off. */
+ for (/* no init */;
+ state->provider_idx < table->providers->nelts;
+ state->provider_idx++)
+ {
+ provider = APR_ARRAY_IDX(table->providers,
+ state->provider_idx,
+ svn_auth_provider_object_t *);
+ if (! state->got_first)
+ {
+ SVN_ERR(provider->vtable->first_credentials(
+ &creds, &(state->provider_iter_baton),
+ provider->provider_baton, auth_baton->parameters,
+ state->realmstring, auth_baton->pool));
+ state->got_first = TRUE;
+ }
+ else if (provider->vtable->next_credentials)
+ {
+ SVN_ERR(provider->vtable->next_credentials(
+ &creds, state->provider_iter_baton,
+ provider->provider_baton, auth_baton->parameters,
+ state->realmstring, auth_baton->pool));
+ }
+
+ if (creds != NULL)
+ {
+ /* Put the creds in the cache */
+ svn_hash_sets(auth_baton->creds_cache, state->cache_key, creds);
+ break;
+ }
+
+ state->got_first = FALSE;
+ }
+
+ *credentials = creds;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_auth_save_credentials(svn_auth_iterstate_t *state,
+ apr_pool_t *pool)
+{
+ int i;
+ svn_auth_provider_object_t *provider;
+ svn_boolean_t save_succeeded = FALSE;
+ const char *no_auth_cache;
+ svn_auth_baton_t *auth_baton;
+ void *creds;
+
+ if (! state || state->table->providers->nelts <= state->provider_idx)
+ return SVN_NO_ERROR;
+
+ auth_baton = state->auth_baton;
+ creds = svn_hash_gets(state->auth_baton->creds_cache, state->cache_key);
+ if (! creds)
+ return SVN_NO_ERROR;
+
+ /* Do not save the creds if SVN_AUTH_PARAM_NO_AUTH_CACHE is set */
+ no_auth_cache = svn_hash_gets(auth_baton->parameters,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE);
+ if (no_auth_cache)
+ return SVN_NO_ERROR;
+
+ /* First, try to save the creds using the provider that produced them. */
+ provider = APR_ARRAY_IDX(state->table->providers,
+ state->provider_idx,
+ svn_auth_provider_object_t *);
+ if (provider->vtable->save_credentials)
+ SVN_ERR(provider->vtable->save_credentials(&save_succeeded,
+ creds,
+ provider->provider_baton,
+ auth_baton->parameters,
+ state->realmstring,
+ pool));
+ if (save_succeeded)
+ return SVN_NO_ERROR;
+
+ /* Otherwise, loop from the top of the list, asking every provider
+ to attempt a save. ### todo: someday optimize so we don't
+ necessarily start from the top of the list. */
+ for (i = 0; i < state->table->providers->nelts; i++)
+ {
+ provider = APR_ARRAY_IDX(state->table->providers, i,
+ svn_auth_provider_object_t *);
+ if (provider->vtable->save_credentials)
+ SVN_ERR(provider->vtable->save_credentials
+ (&save_succeeded, creds,
+ provider->provider_baton,
+ auth_baton->parameters,
+ state->realmstring,
+ pool));
+
+ if (save_succeeded)
+ break;
+ }
+
+ /* ### notice that at the moment, if no provider can save, there's
+ no way the caller will know. */
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_auth_forget_credentials(svn_auth_baton_t *auth_baton,
+ const char *cred_kind,
+ const char *realmstring,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT((cred_kind && realmstring) || (!cred_kind && !realmstring));
+
+ /* If we have a CRED_KIND and REALMSTRING, we clear out just the
+ cached item (if any). Otherwise, empty the whole hash. */
+ if (cred_kind)
+ {
+ svn_hash_sets(auth_baton->creds_cache,
+ make_cache_key(cred_kind, realmstring, scratch_pool),
+ NULL);
+ }
+ else
+ {
+ apr_hash_clear(auth_baton->creds_cache);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_auth_ssl_server_cert_info_t *
+svn_auth_ssl_server_cert_info_dup
+ (const svn_auth_ssl_server_cert_info_t *info, apr_pool_t *pool)
+{
+ svn_auth_ssl_server_cert_info_t *new_info
+ = apr_palloc(pool, sizeof(*new_info));
+
+ *new_info = *info;
+
+ new_info->hostname = apr_pstrdup(pool, new_info->hostname);
+ new_info->fingerprint = apr_pstrdup(pool, new_info->fingerprint);
+ new_info->valid_from = apr_pstrdup(pool, new_info->valid_from);
+ new_info->valid_until = apr_pstrdup(pool, new_info->valid_until);
+ new_info->issuer_dname = apr_pstrdup(pool, new_info->issuer_dname);
+ new_info->ascii_cert = apr_pstrdup(pool, new_info->ascii_cert);
+
+ return new_info;
+}
+
+svn_error_t *
+svn_auth_get_platform_specific_provider(svn_auth_provider_object_t **provider,
+ const char *provider_name,
+ const char *provider_type,
+ apr_pool_t *pool)
+{
+ *provider = NULL;
+
+ if (apr_strnatcmp(provider_name, "gnome_keyring") == 0 ||
+ apr_strnatcmp(provider_name, "kwallet") == 0)
+ {
+#if defined(SVN_HAVE_GNOME_KEYRING) || defined(SVN_HAVE_KWALLET)
+ apr_dso_handle_t *dso;
+ apr_dso_handle_sym_t provider_function_symbol, version_function_symbol;
+ const char *library_label, *library_name;
+ const char *provider_function_name, *version_function_name;
+ library_name = apr_psprintf(pool,
+ "libsvn_auth_%s-%d.so.%d",
+ provider_name,
+ SVN_VER_MAJOR, SVN_SOVERSION);
+ library_label = apr_psprintf(pool, "svn_%s", provider_name);
+ provider_function_name = apr_psprintf(pool,
+ "svn_auth_get_%s_%s_provider",
+ provider_name, provider_type);
+ version_function_name = apr_psprintf(pool,
+ "svn_auth_%s_version",
+ provider_name);
+ SVN_ERR(svn_dso_load(&dso, library_name));
+ if (dso)
+ {
+ if (apr_dso_sym(&version_function_symbol,
+ dso,
+ version_function_name) == 0)
+ {
+ svn_version_func_t version_function
+ = version_function_symbol;
+ svn_version_checklist_t check_list[2];
+
+ check_list[0].label = library_label;
+ check_list[0].version_query = version_function;
+ check_list[1].label = NULL;
+ check_list[1].version_query = NULL;
+ SVN_ERR(svn_ver_check_list(svn_subr_version(), check_list));
+ }
+ if (apr_dso_sym(&provider_function_symbol,
+ dso,
+ provider_function_name) == 0)
+ {
+ if (strcmp(provider_type, "simple") == 0)
+ {
+ svn_auth_simple_provider_func_t provider_function
+ = provider_function_symbol;
+ provider_function(provider, pool);
+ }
+ else if (strcmp(provider_type, "ssl_client_cert_pw") == 0)
+ {
+ svn_auth_ssl_client_cert_pw_provider_func_t provider_function
+ = provider_function_symbol;
+ provider_function(provider, pool);
+ }
+ }
+ }
+#endif
+ }
+ else
+ {
+#if defined(SVN_HAVE_GPG_AGENT)
+ if (strcmp(provider_name, "gpg_agent") == 0 &&
+ strcmp(provider_type, "simple") == 0)
+ {
+ svn_auth_get_gpg_agent_simple_provider(provider, pool);
+ }
+#endif
+#ifdef SVN_HAVE_KEYCHAIN_SERVICES
+ if (strcmp(provider_name, "keychain") == 0 &&
+ strcmp(provider_type, "simple") == 0)
+ {
+ svn_auth_get_keychain_simple_provider(provider, pool);
+ }
+ else if (strcmp(provider_name, "keychain") == 0 &&
+ strcmp(provider_type, "ssl_client_cert_pw") == 0)
+ {
+ svn_auth_get_keychain_ssl_client_cert_pw_provider(provider, pool);
+ }
+#endif
+
+#if defined(WIN32) && !defined(__MINGW32__)
+ if (strcmp(provider_name, "windows") == 0 &&
+ strcmp(provider_type, "simple") == 0)
+ {
+ svn_auth_get_windows_simple_provider(provider, pool);
+ }
+ else if (strcmp(provider_name, "windows") == 0 &&
+ strcmp(provider_type, "ssl_client_cert_pw") == 0)
+ {
+ svn_auth_get_windows_ssl_client_cert_pw_provider(provider, pool);
+ }
+ else if (strcmp(provider_name, "windows") == 0 &&
+ strcmp(provider_type, "ssl_server_trust") == 0)
+ {
+ svn_auth_get_windows_ssl_server_trust_provider(provider, pool);
+ }
+#endif
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_auth_get_platform_specific_client_providers(apr_array_header_t **providers,
+ svn_config_t *config,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *provider;
+ const char *password_stores_config_option;
+ apr_array_header_t *password_stores;
+ int i;
+
+#define SVN__MAYBE_ADD_PROVIDER(list, p) \
+ { if (p) APR_ARRAY_PUSH(list, svn_auth_provider_object_t *) = p; }
+
+#define SVN__DEFAULT_AUTH_PROVIDER_LIST \
+ "gnome-keyring,kwallet,keychain,gpg-agent,windows-cryptoapi"
+
+ *providers = apr_array_make(pool, 12, sizeof(svn_auth_provider_object_t *));
+
+ /* Fetch the configured list of password stores, and split them into
+ an array. */
+ svn_config_get(config,
+ &password_stores_config_option,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_PASSWORD_STORES,
+ SVN__DEFAULT_AUTH_PROVIDER_LIST);
+ password_stores = svn_cstring_split(password_stores_config_option,
+ " ,", TRUE, pool);
+
+ for (i = 0; i < password_stores->nelts; i++)
+ {
+ const char *password_store = APR_ARRAY_IDX(password_stores, i,
+ const char *);
+
+ /* GNOME Keyring */
+ if (apr_strnatcmp(password_store, "gnome-keyring") == 0)
+ {
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
+ "gnome_keyring",
+ "simple",
+ pool));
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
+
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
+ "gnome_keyring",
+ "ssl_client_cert_pw",
+ pool));
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
+ }
+ /* GPG-AGENT */
+ else if (apr_strnatcmp(password_store, "gpg-agent") == 0)
+ {
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
+ "gpg_agent",
+ "simple",
+ pool));
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
+ }
+ /* KWallet */
+ else if (apr_strnatcmp(password_store, "kwallet") == 0)
+ {
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
+ "kwallet",
+ "simple",
+ pool));
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
+
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
+ "kwallet",
+ "ssl_client_cert_pw",
+ pool));
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
+ }
+ /* Keychain */
+ else if (apr_strnatcmp(password_store, "keychain") == 0)
+ {
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
+ "keychain",
+ "simple",
+ pool));
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
+
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
+ "keychain",
+ "ssl_client_cert_pw",
+ pool));
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
+ }
+ /* Windows */
+ else if (apr_strnatcmp(password_store, "windows-cryptoapi") == 0)
+ {
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
+ "windows",
+ "simple",
+ pool));
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
+
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
+ "windows",
+ "ssl_client_cert_pw",
+ pool));
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/auth.h b/contrib/subversion/subversion/libsvn_subr/auth.h
new file mode 100644
index 0000000..0885f6d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/auth.h
@@ -0,0 +1,49 @@
+/*
+ * auth.h : shared stuff internal to the subr library.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_SUBR_AUTH_H
+#define SVN_LIBSVN_SUBR_AUTH_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include "svn_auth.h"
+
+/* Helper for svn_config_{read|write}_auth_data. Return a path to a
+ file within ~/.subversion/auth/ that holds CRED_KIND credentials
+ within REALMSTRING. If no path is available *PATH will be set to
+ NULL. */
+svn_error_t *
+svn_auth__file_path(const char **path,
+ const char *cred_kind,
+ const char *realmstring,
+ const char *config_dir,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_SUBR_AUTH_H */
diff --git a/contrib/subversion/subversion/libsvn_subr/base64.c b/contrib/subversion/subversion/libsvn_subr/base64.c
new file mode 100644
index 0000000..97ee3d2
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/base64.c
@@ -0,0 +1,567 @@
+/*
+ * base64.c: base64 encoding and decoding functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_general.h> /* for APR_INLINE */
+
+#include "svn_pools.h"
+#include "svn_io.h"
+#include "svn_error.h"
+#include "svn_base64.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+
+/* When asked to format the base64-encoded output as multiple lines,
+ we put this many chars in each line (plus one new line char) unless
+ we run out of data.
+ It is vital for some of the optimizations below that this value is
+ a multiple of 4. */
+#define BASE64_LINELEN 76
+
+/* This number of bytes is encoded in a line of base64 chars. */
+#define BYTES_PER_LINE (BASE64_LINELEN / 4 * 3)
+
+/* Value -> base64 char mapping table (2^6 entries) */
+static const char base64tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ "abcdefghijklmnopqrstuvwxyz0123456789+/";
+
+
+/* Binary input --> base64-encoded output */
+
+struct encode_baton {
+ svn_stream_t *output;
+ unsigned char buf[3]; /* Bytes waiting to be encoded */
+ size_t buflen; /* Number of bytes waiting */
+ size_t linelen; /* Bytes output so far on this line */
+ apr_pool_t *scratch_pool;
+};
+
+
+/* Base64-encode a group. IN needs to have three bytes and OUT needs
+ to have room for four bytes. The input group is treated as four
+ six-bit units which are treated as lookups into base64tab for the
+ bytes of the output group. */
+static APR_INLINE void
+encode_group(const unsigned char *in, char *out)
+{
+ /* Expand input bytes to machine word length (with zero extra cost
+ on x86/x64) ... */
+ apr_size_t part0 = in[0];
+ apr_size_t part1 = in[1];
+ apr_size_t part2 = in[2];
+
+ /* ... to prevent these arithmetic operations from being limited to
+ byte size. This saves non-zero cost conversions of the result when
+ calculating the addresses within base64tab. */
+ out[0] = base64tab[part0 >> 2];
+ out[1] = base64tab[((part0 & 3) << 4) | (part1 >> 4)];
+ out[2] = base64tab[((part1 & 0xf) << 2) | (part2 >> 6)];
+ out[3] = base64tab[part2 & 0x3f];
+}
+
+/* Base64-encode a line, i.e. BYTES_PER_LINE bytes from DATA into
+ BASE64_LINELEN chars and append it to STR. It does not assume that
+ a new line char will be appended, though.
+ The code in this function will simply transform the data without
+ performing any boundary checks. Therefore, DATA must have at least
+ BYTES_PER_LINE left and space for at least another BASE64_LINELEN
+ chars must have been pre-allocated in STR before calling this
+ function. */
+static void
+encode_line(svn_stringbuf_t *str, const char *data)
+{
+ /* Translate directly from DATA to STR->DATA. */
+ const unsigned char *in = (const unsigned char *)data;
+ char *out = str->data + str->len;
+ char *end = out + BASE64_LINELEN;
+
+ /* We assume that BYTES_PER_LINE is a multiple of 3 and BASE64_LINELEN
+ a multiple of 4. */
+ for ( ; out != end; in += 3, out += 4)
+ encode_group(in, out);
+
+ /* Expand and terminate the string. */
+ *out = '\0';
+ str->len += BASE64_LINELEN;
+}
+
+/* (Continue to) Base64-encode the byte string DATA (of length LEN)
+ into STR. Include newlines every so often if BREAK_LINES is true.
+ INBUF, INBUFLEN, and LINELEN are used internally; the caller shall
+ make INBUF have room for three characters and initialize *INBUFLEN
+ and *LINELEN to 0.
+
+ INBUF and *INBUFLEN carry the leftover data from call to call, and
+ *LINELEN carries the length of the current output line. */
+static void
+encode_bytes(svn_stringbuf_t *str, const void *data, apr_size_t len,
+ unsigned char *inbuf, size_t *inbuflen, size_t *linelen,
+ svn_boolean_t break_lines)
+{
+ char group[4];
+ const char *p = data, *end = p + len;
+ apr_size_t buflen;
+
+ /* Resize the stringbuf to make room for the (approximate) size of
+ output, to avoid repeated resizes later.
+ Please note that our optimized code relies on the fact that STR
+ never needs to be resized until we leave this function. */
+ buflen = len * 4 / 3 + 4;
+ if (break_lines)
+ {
+ /* Add an extra space for line breaks. */
+ buflen += buflen / BASE64_LINELEN;
+ }
+ svn_stringbuf_ensure(str, str->len + buflen);
+
+ /* Keep encoding three-byte groups until we run out. */
+ while (*inbuflen + (end - p) >= 3)
+ {
+ /* May we encode BYTES_PER_LINE bytes without caring about
+ line breaks, data in the temporary INBUF or running out
+ of data? */
+ if ( *inbuflen == 0
+ && (*linelen == 0 || !break_lines)
+ && (end - p >= BYTES_PER_LINE))
+ {
+ /* Yes, we can encode a whole chunk of data at once. */
+ encode_line(str, p);
+ p += BYTES_PER_LINE;
+ *linelen += BASE64_LINELEN;
+ }
+ else
+ {
+ /* No, this is one of a number of special cases.
+ Encode the data byte by byte. */
+ memcpy(inbuf + *inbuflen, p, 3 - *inbuflen);
+ p += (3 - *inbuflen);
+ encode_group(inbuf, group);
+ svn_stringbuf_appendbytes(str, group, 4);
+ *inbuflen = 0;
+ *linelen += 4;
+ }
+
+ /* Add line breaks as necessary. */
+ if (break_lines && *linelen == BASE64_LINELEN)
+ {
+ svn_stringbuf_appendbyte(str, '\n');
+ *linelen = 0;
+ }
+ }
+
+ /* Tack any extra input onto *INBUF. */
+ memcpy(inbuf + *inbuflen, p, end - p);
+ *inbuflen += (end - p);
+}
+
+
+/* Encode leftover data, if any, and possibly a final newline (if
+ there has been any data and BREAK_LINES is set), appending to STR.
+ LEN must be in the range 0..2. */
+static void
+encode_partial_group(svn_stringbuf_t *str, const unsigned char *extra,
+ size_t len, size_t linelen, svn_boolean_t break_lines)
+{
+ unsigned char ingroup[3];
+ char outgroup[4];
+
+ if (len > 0)
+ {
+ memcpy(ingroup, extra, len);
+ memset(ingroup + len, 0, 3 - len);
+ encode_group(ingroup, outgroup);
+ memset(outgroup + (len + 1), '=', 4 - (len + 1));
+ svn_stringbuf_appendbytes(str, outgroup, 4);
+ linelen += 4;
+ }
+ if (break_lines && linelen > 0)
+ svn_stringbuf_appendbyte(str, '\n');
+}
+
+
+/* Write handler for svn_base64_encode. */
+static svn_error_t *
+encode_data(void *baton, const char *data, apr_size_t *len)
+{
+ struct encode_baton *eb = baton;
+ svn_stringbuf_t *encoded = svn_stringbuf_create_empty(eb->scratch_pool);
+ apr_size_t enclen;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ /* Encode this block of data and write it out. */
+ encode_bytes(encoded, data, *len, eb->buf, &eb->buflen, &eb->linelen, TRUE);
+ enclen = encoded->len;
+ if (enclen != 0)
+ err = svn_stream_write(eb->output, encoded->data, &enclen);
+ svn_pool_clear(eb->scratch_pool);
+ return err;
+}
+
+
+/* Close handler for svn_base64_encode(). */
+static svn_error_t *
+finish_encoding_data(void *baton)
+{
+ struct encode_baton *eb = baton;
+ svn_stringbuf_t *encoded = svn_stringbuf_create_empty(eb->scratch_pool);
+ apr_size_t enclen;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ /* Encode a partial group at the end if necessary, and write it out. */
+ encode_partial_group(encoded, eb->buf, eb->buflen, eb->linelen, TRUE);
+ enclen = encoded->len;
+ if (enclen != 0)
+ err = svn_stream_write(eb->output, encoded->data, &enclen);
+
+ /* Pass on the close request and clean up the baton. */
+ if (err == SVN_NO_ERROR)
+ err = svn_stream_close(eb->output);
+ svn_pool_destroy(eb->scratch_pool);
+ return err;
+}
+
+
+svn_stream_t *
+svn_base64_encode(svn_stream_t *output, apr_pool_t *pool)
+{
+ struct encode_baton *eb = apr_palloc(pool, sizeof(*eb));
+ svn_stream_t *stream;
+
+ eb->output = output;
+ eb->buflen = 0;
+ eb->linelen = 0;
+ eb->scratch_pool = svn_pool_create(pool);
+ stream = svn_stream_create(eb, pool);
+ svn_stream_set_write(stream, encode_data);
+ svn_stream_set_close(stream, finish_encoding_data);
+ return stream;
+}
+
+
+const svn_string_t *
+svn_base64_encode_string2(const svn_string_t *str,
+ svn_boolean_t break_lines,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *encoded = svn_stringbuf_create_empty(pool);
+ unsigned char ingroup[3];
+ size_t ingrouplen = 0;
+ size_t linelen = 0;
+
+ encode_bytes(encoded, str->data, str->len, ingroup, &ingrouplen, &linelen,
+ break_lines);
+ encode_partial_group(encoded, ingroup, ingrouplen, linelen,
+ break_lines);
+ return svn_stringbuf__morph_into_string(encoded);
+}
+
+const svn_string_t *
+svn_base64_encode_string(const svn_string_t *str, apr_pool_t *pool)
+{
+ return svn_base64_encode_string2(str, TRUE, pool);
+}
+
+
+
+/* Base64-encoded input --> binary output */
+
+struct decode_baton {
+ svn_stream_t *output;
+ unsigned char buf[4]; /* Bytes waiting to be decoded */
+ int buflen; /* Number of bytes waiting */
+ svn_boolean_t done; /* True if we already saw an '=' */
+ apr_pool_t *scratch_pool;
+};
+
+
+/* Base64-decode a group. IN needs to have four bytes and OUT needs
+ to have room for three bytes. The input bytes must already have
+ been decoded from base64tab into the range 0..63. The four
+ six-bit values are pasted together to form three eight-bit bytes. */
+static APR_INLINE void
+decode_group(const unsigned char *in, char *out)
+{
+ out[0] = (char)((in[0] << 2) | (in[1] >> 4));
+ out[1] = (char)(((in[1] & 0xf) << 4) | (in[2] >> 2));
+ out[2] = (char)(((in[2] & 0x3) << 6) | in[3]);
+}
+
+/* Lookup table for base64 characters; reverse_base64[ch] gives a
+ negative value if ch is not a valid base64 character, or otherwise
+ the value of the byte represented; 'A' => 0 etc. */
+static const signed char reverse_base64[256] = {
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+/* Similar to decode_group but this function also translates the
+ 6-bit values from the IN buffer before translating them.
+ Return FALSE if a non-base64 char (e.g. '=' or new line)
+ has been encountered. */
+static APR_INLINE svn_boolean_t
+decode_group_directly(const unsigned char *in, char *out)
+{
+ /* Translate the base64 chars in values [0..63, 0xff] */
+ apr_size_t part0 = (unsigned char)reverse_base64[(unsigned char)in[0]];
+ apr_size_t part1 = (unsigned char)reverse_base64[(unsigned char)in[1]];
+ apr_size_t part2 = (unsigned char)reverse_base64[(unsigned char)in[2]];
+ apr_size_t part3 = (unsigned char)reverse_base64[(unsigned char)in[3]];
+
+ /* Pack 4x6 bits into 3x8.*/
+ out[0] = (char)((part0 << 2) | (part1 >> 4));
+ out[1] = (char)(((part1 & 0xf) << 4) | (part2 >> 2));
+ out[2] = (char)(((part2 & 0x3) << 6) | part3);
+
+ /* FALSE, iff any part is 0xff. */
+ return (part0 | part1 | part2 | part3) != (unsigned char)(-1);
+}
+
+/* Base64-encode up to BASE64_LINELEN chars from *DATA and append it to
+ STR. After the function returns, *DATA will point to the first char
+ that has not been translated, yet. Returns TRUE if all BASE64_LINELEN
+ chars could be translated, i.e. no special char has been encountered
+ in between.
+ The code in this function will simply transform the data without
+ performing any boundary checks. Therefore, DATA must have at least
+ BASE64_LINELEN left and space for at least another BYTES_PER_LINE
+ chars must have been pre-allocated in STR before calling this
+ function. */
+static svn_boolean_t
+decode_line(svn_stringbuf_t *str, const char **data)
+{
+ /* Decode up to BYTES_PER_LINE bytes directly from *DATA into STR->DATA. */
+ const unsigned char *p = *(const unsigned char **)data;
+ char *out = str->data + str->len;
+ char *end = out + BYTES_PER_LINE;
+
+ /* We assume that BYTES_PER_LINE is a multiple of 3 and BASE64_LINELEN
+ a multiple of 4. Stop translation as soon as we encounter a special
+ char. Leave the entire group untouched in that case. */
+ for (; out < end; p += 4, out += 3)
+ if (!decode_group_directly(p, out))
+ break;
+
+ /* Update string sizes and positions. */
+ str->len = out - str->data;
+ *out = '\0';
+ *data = (const char *)p;
+
+ /* Return FALSE, if the caller should continue the decoding process
+ using the slow standard method. */
+ return out == end;
+}
+
+
+/* (Continue to) Base64-decode the byte string DATA (of length LEN)
+ into STR. INBUF, INBUFLEN, and DONE are used internally; the
+ caller shall have room for four bytes in INBUF and initialize
+ *INBUFLEN to 0 and *DONE to FALSE.
+
+ INBUF and *INBUFLEN carry the leftover bytes from call to call, and
+ *DONE keeps track of whether we've seen an '=' which terminates the
+ encoded data. */
+static void
+decode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len,
+ unsigned char *inbuf, int *inbuflen, svn_boolean_t *done)
+{
+ const char *p = data;
+ char group[3];
+ signed char find;
+ const char *end = data + len;
+
+ /* Resize the stringbuf to make room for the maximum size of output,
+ to avoid repeated resizes later. The optimizations in
+ decode_line rely on no resizes being necessary!
+
+ (*inbuflen+len) is encoded data length
+ (*inbuflen+len)/4 is the number of complete 4-bytes sets
+ (*inbuflen+len)/4*3 is the number of decoded bytes
+ svn_stringbuf_ensure will add an additional byte for the terminating 0.
+ */
+ svn_stringbuf_ensure(str, str->len + ((*inbuflen + len) / 4) * 3);
+
+ while ( !*done && p < end )
+ {
+ /* If no data is left in temporary INBUF and there is at least
+ one line-sized chunk left to decode, we may use the optimized
+ code path. */
+ if ((*inbuflen == 0) && (p + BASE64_LINELEN <= end))
+ if (decode_line(str, &p))
+ continue;
+
+ /* A special case or decode_line encountered a special char. */
+ if (*p == '=')
+ {
+ /* We are at the end and have to decode a partial group. */
+ if (*inbuflen >= 2)
+ {
+ memset(inbuf + *inbuflen, 0, 4 - *inbuflen);
+ decode_group(inbuf, group);
+ svn_stringbuf_appendbytes(str, group, *inbuflen - 1);
+ }
+ *done = TRUE;
+ }
+ else
+ {
+ find = reverse_base64[(unsigned char)*p];
+ ++p;
+
+ if (find >= 0)
+ inbuf[(*inbuflen)++] = find;
+ if (*inbuflen == 4)
+ {
+ decode_group(inbuf, group);
+ svn_stringbuf_appendbytes(str, group, 3);
+ *inbuflen = 0;
+ }
+ }
+ }
+}
+
+
+/* Write handler for svn_base64_decode. */
+static svn_error_t *
+decode_data(void *baton, const char *data, apr_size_t *len)
+{
+ struct decode_baton *db = baton;
+ svn_stringbuf_t *decoded;
+ apr_size_t declen;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ /* Decode this block of data. */
+ decoded = svn_stringbuf_create_empty(db->scratch_pool);
+ decode_bytes(decoded, data, *len, db->buf, &db->buflen, &db->done);
+
+ /* Write the output, clean up, go home. */
+ declen = decoded->len;
+ if (declen != 0)
+ err = svn_stream_write(db->output, decoded->data, &declen);
+ svn_pool_clear(db->scratch_pool);
+ return err;
+}
+
+
+/* Close handler for svn_base64_decode(). */
+static svn_error_t *
+finish_decoding_data(void *baton)
+{
+ struct decode_baton *db = baton;
+ svn_error_t *err;
+
+ /* Pass on the close request and clean up the baton. */
+ err = svn_stream_close(db->output);
+ svn_pool_destroy(db->scratch_pool);
+ return err;
+}
+
+
+svn_stream_t *
+svn_base64_decode(svn_stream_t *output, apr_pool_t *pool)
+{
+ struct decode_baton *db = apr_palloc(pool, sizeof(*db));
+ svn_stream_t *stream;
+
+ db->output = output;
+ db->buflen = 0;
+ db->done = FALSE;
+ db->scratch_pool = svn_pool_create(pool);
+ stream = svn_stream_create(db, pool);
+ svn_stream_set_write(stream, decode_data);
+ svn_stream_set_close(stream, finish_decoding_data);
+ return stream;
+}
+
+
+const svn_string_t *
+svn_base64_decode_string(const svn_string_t *str, apr_pool_t *pool)
+{
+ svn_stringbuf_t *decoded = svn_stringbuf_create_empty(pool);
+ unsigned char ingroup[4];
+ int ingrouplen = 0;
+ svn_boolean_t done = FALSE;
+
+ decode_bytes(decoded, str->data, str->len, ingroup, &ingrouplen, &done);
+ return svn_stringbuf__morph_into_string(decoded);
+}
+
+
+/* Return a base64-encoded representation of CHECKSUM, allocated in POOL.
+ If CHECKSUM->kind is not recognized, return NULL.
+ ### That 'NULL' claim was in the header file when this was public, but
+ doesn't look true in the implementation.
+
+ ### This is now only used as a new implementation of svn_base64_from_md5();
+ it would probably be safer to revert that to its old implementation. */
+static svn_stringbuf_t *
+base64_from_checksum(const svn_checksum_t *checksum, apr_pool_t *pool)
+{
+ svn_stringbuf_t *checksum_str;
+ unsigned char ingroup[3];
+ size_t ingrouplen = 0;
+ size_t linelen = 0;
+ checksum_str = svn_stringbuf_create_empty(pool);
+
+ encode_bytes(checksum_str, checksum->digest,
+ svn_checksum_size(checksum), ingroup, &ingrouplen,
+ &linelen, TRUE);
+ encode_partial_group(checksum_str, ingroup, ingrouplen, linelen, TRUE);
+
+ /* Our base64-encoding routines append a final newline if any data
+ was created at all, so let's hack that off. */
+ if (checksum_str->len)
+ {
+ checksum_str->len--;
+ checksum_str->data[checksum_str->len] = 0;
+ }
+
+ return checksum_str;
+}
+
+
+svn_stringbuf_t *
+svn_base64_from_md5(unsigned char digest[], apr_pool_t *pool)
+{
+ svn_checksum_t *checksum
+ = svn_checksum__from_digest_md5(digest, pool);
+
+ return base64_from_checksum(checksum, pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/cache-inprocess.c b/contrib/subversion/subversion/libsvn_subr/cache-inprocess.c
new file mode 100644
index 0000000..6401f9f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/cache-inprocess.c
@@ -0,0 +1,648 @@
+/*
+ * cache-inprocess.c: in-memory caching for Subversion
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <assert.h>
+
+#include <apr_thread_mutex.h>
+
+#include "svn_pools.h"
+
+#include "svn_private_config.h"
+
+#include "cache.h"
+#include "private/svn_mutex.h"
+
+/* The (internal) cache object. */
+typedef struct inprocess_cache_t {
+ /* A user-defined identifier for this cache instance. */
+ const char *id;
+
+ /* HASH maps a key (of size KLEN) to a struct cache_entry. */
+ apr_hash_t *hash;
+ apr_ssize_t klen;
+
+ /* Used to copy values into the cache. */
+ svn_cache__serialize_func_t serialize_func;
+
+ /* Used to copy values out of the cache. */
+ svn_cache__deserialize_func_t deserialize_func;
+
+ /* Maximum number of pages that this cache instance may allocate */
+ apr_uint64_t total_pages;
+ /* The number of pages we're allowed to allocate before having to
+ * try to reuse one. */
+ apr_uint64_t unallocated_pages;
+ /* Number of cache entries stored on each page. Must be at least 1. */
+ apr_uint64_t items_per_page;
+
+ /* A dummy cache_page serving as the head of a circular doubly
+ * linked list of cache_pages. SENTINEL->NEXT is the most recently
+ * used page, and SENTINEL->PREV is the least recently used page.
+ * All pages in this list are "full"; the page currently being
+ * filled (PARTIAL_PAGE) is not in the list. */
+ struct cache_page *sentinel;
+
+ /* A page currently being filled with entries, or NULL if there's no
+ * partially-filled page. This page is not in SENTINEL's list. */
+ struct cache_page *partial_page;
+ /* If PARTIAL_PAGE is not null, this is the number of entries
+ * currently on PARTIAL_PAGE. */
+ apr_uint64_t partial_page_number_filled;
+
+ /* The pool that the svn_cache__t itself, HASH, and all pages are
+ * allocated in; subpools of this pool are used for the cache_entry
+ * structs, as well as the dup'd values and hash keys.
+ */
+ apr_pool_t *cache_pool;
+
+ /* Sum of the SIZE members of all cache_entry elements that are
+ * accessible from HASH. This is used to make statistics available
+ * even if the sub-pools have already been destroyed.
+ */
+ apr_size_t data_size;
+
+ /* A lock for intra-process synchronization to the cache, or NULL if
+ * the cache's creator doesn't feel the cache needs to be
+ * thread-safe. */
+ svn_mutex__t *mutex;
+} inprocess_cache_t;
+
+/* A cache page; all items on the page are allocated from the same
+ * pool. */
+struct cache_page {
+ /* Pointers for the LRU list anchored at the cache's SENTINEL.
+ * (NULL for the PARTIAL_PAGE.) */
+ struct cache_page *prev;
+ struct cache_page *next;
+
+ /* The pool in which cache_entry structs, hash keys, and dup'd
+ * values are allocated. The CACHE_PAGE structs are allocated
+ * in CACHE_POOL and have the same lifetime as the cache itself.
+ * (The cache will never allocate more than TOTAL_PAGES page
+ * structs (inclusive of the sentinel) from CACHE_POOL.)
+ */
+ apr_pool_t *page_pool;
+
+ /* A singly linked list of the entries on this page; used to remove
+ * them from the cache's HASH before reusing the page. */
+ struct cache_entry *first_entry;
+};
+
+/* An cache entry. */
+struct cache_entry {
+ const void *key;
+
+ /* serialized value */
+ void *value;
+
+ /* length of the serialized value in bytes */
+ apr_size_t size;
+
+ /* The page it's on (needed so that the LRU list can be
+ * maintained). */
+ struct cache_page *page;
+
+ /* Next entry on the page. */
+ struct cache_entry *next_entry;
+};
+
+
+/* Removes PAGE from the doubly-linked list it is in (leaving its PREV
+ * and NEXT fields undefined). */
+static void
+remove_page_from_list(struct cache_page *page)
+{
+ page->prev->next = page->next;
+ page->next->prev = page->prev;
+}
+
+/* Inserts PAGE after CACHE's sentinel. */
+static void
+insert_page(inprocess_cache_t *cache,
+ struct cache_page *page)
+{
+ struct cache_page *pred = cache->sentinel;
+
+ page->prev = pred;
+ page->next = pred->next;
+ page->prev->next = page;
+ page->next->prev = page;
+}
+
+/* If PAGE is in the circularly linked list (eg, its NEXT isn't NULL),
+ * move it to the front of the list. */
+static svn_error_t *
+move_page_to_front(inprocess_cache_t *cache,
+ struct cache_page *page)
+{
+ /* This function is called whilst CACHE is locked. */
+
+ SVN_ERR_ASSERT(page != cache->sentinel);
+
+ if (! page->next)
+ return SVN_NO_ERROR;
+
+ remove_page_from_list(page);
+ insert_page(cache, page);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return a copy of KEY inside POOL, using CACHE->KLEN to figure out
+ * how. */
+static const void *
+duplicate_key(inprocess_cache_t *cache,
+ const void *key,
+ apr_pool_t *pool)
+{
+ if (cache->klen == APR_HASH_KEY_STRING)
+ return apr_pstrdup(pool, key);
+ else
+ return apr_pmemdup(pool, key, cache->klen);
+}
+
+static svn_error_t *
+inprocess_cache_get_internal(char **buffer,
+ apr_size_t *size,
+ inprocess_cache_t *cache,
+ const void *key,
+ apr_pool_t *result_pool)
+{
+ struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
+
+ *buffer = NULL;
+ if (entry)
+ {
+ SVN_ERR(move_page_to_front(cache, entry->page));
+
+ /* duplicate the buffer entry */
+ *buffer = apr_palloc(result_pool, entry->size);
+ memcpy(*buffer, entry->value, entry->size);
+
+ *size = entry->size;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+inprocess_cache_get(void **value_p,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ apr_pool_t *result_pool)
+{
+ inprocess_cache_t *cache = cache_void;
+ char* buffer = NULL;
+ apr_size_t size;
+
+ if (key)
+ SVN_MUTEX__WITH_LOCK(cache->mutex,
+ inprocess_cache_get_internal(&buffer,
+ &size,
+ cache,
+ key,
+ result_pool));
+
+ /* deserialize the buffer content. Usually, this will directly
+ modify the buffer content directly.
+ */
+ *value_p = NULL;
+ *found = buffer != NULL;
+ return buffer && size
+ ? cache->deserialize_func(value_p, buffer, size, result_pool)
+ : SVN_NO_ERROR;
+}
+
+/* Removes PAGE from the LRU list, removes all of its entries from
+ * CACHE's hash, clears its pool, and sets its entry pointer to NULL.
+ * Finally, puts it in the "partial page" slot in the cache and sets
+ * partial_page_number_filled to 0. Must be called on a page actually
+ * in the list. */
+static void
+erase_page(inprocess_cache_t *cache,
+ struct cache_page *page)
+{
+ struct cache_entry *e;
+
+ remove_page_from_list(page);
+
+ for (e = page->first_entry;
+ e;
+ e = e->next_entry)
+ {
+ cache->data_size -= e->size;
+ apr_hash_set(cache->hash, e->key, cache->klen, NULL);
+ }
+
+ svn_pool_clear(page->page_pool);
+
+ page->first_entry = NULL;
+ page->prev = NULL;
+ page->next = NULL;
+
+ cache->partial_page = page;
+ cache->partial_page_number_filled = 0;
+}
+
+
+static svn_error_t *
+inprocess_cache_set_internal(inprocess_cache_t *cache,
+ const void *key,
+ void *value,
+ apr_pool_t *scratch_pool)
+{
+ struct cache_entry *existing_entry;
+
+ existing_entry = apr_hash_get(cache->hash, key, cache->klen);
+
+ /* Is it already here, but we can do the one-item-per-page
+ * optimization? */
+ if (existing_entry && cache->items_per_page == 1)
+ {
+ /* Special case! ENTRY is the *only* entry on this page, so
+ * why not wipe it (so as not to leak the previous value).
+ */
+ struct cache_page *page = existing_entry->page;
+
+ /* This can't be the partial page: items_per_page == 1
+ * *never* has a partial page (except for in the temporary state
+ * that we're about to fake). */
+ SVN_ERR_ASSERT(page->next != NULL);
+ SVN_ERR_ASSERT(cache->partial_page == NULL);
+
+ erase_page(cache, page);
+ existing_entry = NULL;
+ }
+
+ /* Is it already here, and we just have to leak the old value? */
+ if (existing_entry)
+ {
+ struct cache_page *page = existing_entry->page;
+
+ SVN_ERR(move_page_to_front(cache, page));
+ cache->data_size -= existing_entry->size;
+ if (value)
+ {
+ SVN_ERR(cache->serialize_func(&existing_entry->value,
+ &existing_entry->size,
+ value,
+ page->page_pool));
+ cache->data_size += existing_entry->size;
+ if (existing_entry->size == 0)
+ existing_entry->value = NULL;
+ }
+ else
+ {
+ existing_entry->value = NULL;
+ existing_entry->size = 0;
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Do we not have a partial page to put it on, but we are allowed to
+ * allocate more? */
+ if (cache->partial_page == NULL && cache->unallocated_pages > 0)
+ {
+ cache->partial_page = apr_pcalloc(cache->cache_pool,
+ sizeof(*(cache->partial_page)));
+ cache->partial_page->page_pool = svn_pool_create(cache->cache_pool);
+ cache->partial_page_number_filled = 0;
+ (cache->unallocated_pages)--;
+ }
+
+ /* Do we really not have a partial page to put it on, even after the
+ * one-item-per-page optimization and checking the unallocated page
+ * count? */
+ if (cache->partial_page == NULL)
+ {
+ struct cache_page *oldest_page = cache->sentinel->prev;
+
+ SVN_ERR_ASSERT(oldest_page != cache->sentinel);
+
+ /* Erase the page and put it in cache->partial_page. */
+ erase_page(cache, oldest_page);
+ }
+
+ SVN_ERR_ASSERT(cache->partial_page != NULL);
+
+ {
+ struct cache_page *page = cache->partial_page;
+ struct cache_entry *new_entry = apr_pcalloc(page->page_pool,
+ sizeof(*new_entry));
+
+ /* Copy the key and value into the page's pool. */
+ new_entry->key = duplicate_key(cache, key, page->page_pool);
+ if (value)
+ {
+ SVN_ERR(cache->serialize_func(&new_entry->value,
+ &new_entry->size,
+ value,
+ page->page_pool));
+ cache->data_size += new_entry->size;
+ if (new_entry->size == 0)
+ new_entry->value = NULL;
+ }
+ else
+ {
+ new_entry->value = NULL;
+ new_entry->size = 0;
+ }
+
+ /* Add the entry to the page's list. */
+ new_entry->page = page;
+ new_entry->next_entry = page->first_entry;
+ page->first_entry = new_entry;
+
+ /* Add the entry to the hash, using the *entry's* copy of the
+ * key. */
+ apr_hash_set(cache->hash, new_entry->key, cache->klen, new_entry);
+
+ /* We've added something else to the partial page. */
+ (cache->partial_page_number_filled)++;
+
+ /* Is it full? */
+ if (cache->partial_page_number_filled >= cache->items_per_page)
+ {
+ insert_page(cache, page);
+ cache->partial_page = NULL;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+inprocess_cache_set(void *cache_void,
+ const void *key,
+ void *value,
+ apr_pool_t *scratch_pool)
+{
+ inprocess_cache_t *cache = cache_void;
+
+ if (key)
+ SVN_MUTEX__WITH_LOCK(cache->mutex,
+ inprocess_cache_set_internal(cache,
+ key,
+ value,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton type for svn_cache__iter. */
+struct cache_iter_baton {
+ svn_iter_apr_hash_cb_t user_cb;
+ void *user_baton;
+};
+
+/* Call the user's callback with the actual value, not the
+ cache_entry. Implements the svn_iter_apr_hash_cb_t
+ prototype. */
+static svn_error_t *
+iter_cb(void *baton,
+ const void *key,
+ apr_ssize_t klen,
+ void *val,
+ apr_pool_t *pool)
+{
+ struct cache_iter_baton *b = baton;
+ struct cache_entry *entry = val;
+ return (b->user_cb)(b->user_baton, key, klen, entry->value, pool);
+}
+
+static svn_error_t *
+inprocess_cache_iter(svn_boolean_t *completed,
+ void *cache_void,
+ svn_iter_apr_hash_cb_t user_cb,
+ void *user_baton,
+ apr_pool_t *scratch_pool)
+{
+ inprocess_cache_t *cache = cache_void;
+ struct cache_iter_baton b;
+ b.user_cb = user_cb;
+ b.user_baton = user_baton;
+
+ SVN_MUTEX__WITH_LOCK(cache->mutex,
+ svn_iter_apr_hash(completed, cache->hash,
+ iter_cb, &b, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+inprocess_cache_get_partial_internal(void **value_p,
+ svn_boolean_t *found,
+ inprocess_cache_t *cache,
+ const void *key,
+ svn_cache__partial_getter_func_t func,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
+ if (! entry)
+ {
+ *found = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(move_page_to_front(cache, entry->page));
+
+ *found = TRUE;
+ return func(value_p, entry->value, entry->size, baton, result_pool);
+}
+
+static svn_error_t *
+inprocess_cache_get_partial(void **value_p,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ svn_cache__partial_getter_func_t func,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ inprocess_cache_t *cache = cache_void;
+
+ if (key)
+ SVN_MUTEX__WITH_LOCK(cache->mutex,
+ inprocess_cache_get_partial_internal(value_p,
+ found,
+ cache,
+ key,
+ func,
+ baton,
+ result_pool));
+ else
+ *found = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+inprocess_cache_set_partial_internal(inprocess_cache_t *cache,
+ const void *key,
+ svn_cache__partial_setter_func_t func,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
+ if (entry)
+ {
+ SVN_ERR(move_page_to_front(cache, entry->page));
+
+ cache->data_size -= entry->size;
+ SVN_ERR(func(&entry->value,
+ &entry->size,
+ baton,
+ entry->page->page_pool));
+ cache->data_size += entry->size;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+inprocess_cache_set_partial(void *cache_void,
+ const void *key,
+ svn_cache__partial_setter_func_t func,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ inprocess_cache_t *cache = cache_void;
+
+ if (key)
+ SVN_MUTEX__WITH_LOCK(cache->mutex,
+ inprocess_cache_set_partial_internal(cache,
+ key,
+ func,
+ baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_boolean_t
+inprocess_cache_is_cachable(void *cache_void, apr_size_t size)
+{
+ /* Be relatively strict: per page we should not allocate more than
+ * we could spare as "unused" memory.
+ * But in most cases, nobody will ask anyway. And in no case, this
+ * will be used for checks _inside_ the cache code.
+ */
+ inprocess_cache_t *cache = cache_void;
+ return size < SVN_ALLOCATOR_RECOMMENDED_MAX_FREE / cache->items_per_page;
+}
+
+static svn_error_t *
+inprocess_cache_get_info_internal(inprocess_cache_t *cache,
+ svn_cache__info_t *info,
+ apr_pool_t *result_pool)
+{
+ info->id = apr_pstrdup(result_pool, cache->id);
+
+ info->used_entries = apr_hash_count(cache->hash);
+ info->total_entries = cache->items_per_page * cache->total_pages;
+
+ info->used_size = cache->data_size;
+ info->data_size = cache->data_size;
+ info->total_size = cache->data_size
+ + cache->items_per_page * sizeof(struct cache_page)
+ + info->used_entries * sizeof(struct cache_entry);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+inprocess_cache_get_info(void *cache_void,
+ svn_cache__info_t *info,
+ svn_boolean_t reset,
+ apr_pool_t *result_pool)
+{
+ inprocess_cache_t *cache = cache_void;
+
+ SVN_MUTEX__WITH_LOCK(cache->mutex,
+ inprocess_cache_get_info_internal(cache,
+ info,
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_cache__vtable_t inprocess_cache_vtable = {
+ inprocess_cache_get,
+ inprocess_cache_set,
+ inprocess_cache_iter,
+ inprocess_cache_is_cachable,
+ inprocess_cache_get_partial,
+ inprocess_cache_set_partial,
+ inprocess_cache_get_info
+};
+
+svn_error_t *
+svn_cache__create_inprocess(svn_cache__t **cache_p,
+ svn_cache__serialize_func_t serialize,
+ svn_cache__deserialize_func_t deserialize,
+ apr_ssize_t klen,
+ apr_int64_t pages,
+ apr_int64_t items_per_page,
+ svn_boolean_t thread_safe,
+ const char *id,
+ apr_pool_t *pool)
+{
+ svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper));
+ inprocess_cache_t *cache = apr_pcalloc(pool, sizeof(*cache));
+
+ cache->id = apr_pstrdup(pool, id);
+
+ SVN_ERR_ASSERT(klen == APR_HASH_KEY_STRING || klen >= 1);
+
+ cache->hash = apr_hash_make(pool);
+ cache->klen = klen;
+
+ cache->serialize_func = serialize;
+ cache->deserialize_func = deserialize;
+
+ SVN_ERR_ASSERT(pages >= 1);
+ cache->total_pages = pages;
+ cache->unallocated_pages = pages;
+ SVN_ERR_ASSERT(items_per_page >= 1);
+ cache->items_per_page = items_per_page;
+
+ cache->sentinel = apr_pcalloc(pool, sizeof(*(cache->sentinel)));
+ cache->sentinel->prev = cache->sentinel;
+ cache->sentinel->next = cache->sentinel;
+ /* The sentinel doesn't need a pool. (We're happy to crash if we
+ * accidentally try to treat it like a real page.) */
+
+ SVN_ERR(svn_mutex__init(&cache->mutex, thread_safe, pool));
+
+ cache->cache_pool = pool;
+
+ wrapper->vtable = &inprocess_cache_vtable;
+ wrapper->cache_internal = cache;
+
+ *cache_p = wrapper;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/cache-membuffer.c b/contrib/subversion/subversion/libsvn_subr/cache-membuffer.c
new file mode 100644
index 0000000..5f447b3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/cache-membuffer.c
@@ -0,0 +1,2369 @@
+/*
+ * cache-membuffer.c: in-memory caching for Subversion
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <assert.h>
+#include <apr_md5.h>
+#include <apr_thread_rwlock.h>
+
+#include "svn_pools.h"
+#include "svn_checksum.h"
+#include "md5.h"
+#include "svn_private_config.h"
+#include "cache.h"
+#include "svn_string.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_mutex.h"
+#include "private/svn_pseudo_md5.h"
+
+/*
+ * This svn_cache__t implementation actually consists of two parts:
+ * a shared (per-process) singleton membuffer cache instance and shallow
+ * svn_cache__t front-end instances that each use different key spaces.
+ * For data management, they all forward to the singleton membuffer cache.
+ *
+ * A membuffer cache consists of two parts:
+ *
+ * 1. A linear data buffer containing cached items in a serialized
+ * representation. There may be arbitrary gaps between entries.
+ * 2. A directory of cache entries. This is organized similar to CPU
+ * data caches: for every possible key, there is exactly one group
+ * of entries that may contain the header info for an item with
+ * that given key. The result is a GROUP_SIZE-way associative cache.
+ *
+ * Only the start address of these two data parts are given as a native
+ * pointer. All other references are expressed as offsets to these pointers.
+ * With that design, it is relatively easy to share the same data structure
+ * between different processes and / or to persist them on disk. These
+ * out-of-process features have not been implemented, yet.
+ *
+ * The data buffer usage information is implicitly given by the directory
+ * entries. Every USED entry has a reference to the previous and the next
+ * used dictionary entry and this double-linked list is ordered by the
+ * offsets of their item data within the data buffer. So removing data,
+ * for instance, is done simply by unlinking it from the chain, implicitly
+ * marking the entry as well as the data buffer section previously
+ * associated to it as unused.
+ *
+ * Insertion can occur at only one, sliding position. It is marked by its
+ * offset in the data buffer plus the index of the first used entry at or
+ * behind that position. If this gap is too small to accommodate the new
+ * item, the insertion window is extended as described below. The new entry
+ * will always be inserted at the bottom end of the window and since the
+ * next used entry is known, properly sorted insertion is possible.
+ *
+ * To make the cache perform robustly in a wide range of usage scenarios,
+ * a randomized variant of LFU is used (see ensure_data_insertable for
+ * details). Every item holds a read hit counter and there is a global read
+ * hit counter. The more hits an entry has in relation to the average, the
+ * more it is likely to be kept using a rand()-based condition. The test is
+ * applied only to the entry following the insertion window. If it doesn't
+ * get evicted, it is moved to the begin of that window and the window is
+ * moved.
+ *
+ * Moreover, the entry's hits get halved to make that entry more likely to
+ * be removed the next time the sliding insertion / removal window comes by.
+ * As a result, frequently used entries are likely not to be dropped until
+ * they get not used for a while. Also, even a cache thrashing situation
+ * about 50% of the content survives every 50% of the cache being re-written
+ * with new entries. For details on the fine-tuning involved, see the
+ * comments in ensure_data_insertable().
+ *
+ * To limit the entry size and management overhead, not the actual item keys
+ * but only their MD5 checksums will not be stored. This is reasonably safe
+ * to do since users have only limited control over the full keys, even if
+ * these contain folder paths. So, it is very hard to deliberately construct
+ * colliding keys. Random checksum collisions can be shown to be extremely
+ * unlikely.
+ *
+ * All access to the cached data needs to be serialized. Because we want
+ * to scale well despite that bottleneck, we simply segment the cache into
+ * a number of independent caches (segments). Items will be multiplexed based
+ * on their hash key.
+ */
+
+/* A 16-way associative cache seems to be a good compromise between
+ * performance (worst-case lookups) and efficiency-loss due to collisions.
+ *
+ * This value may be changed to any positive integer.
+ */
+#define GROUP_SIZE 16
+
+/* For more efficient copy operations, let's align all data items properly.
+ * Must be a power of 2.
+ */
+#define ITEM_ALIGNMENT 16
+
+/* By default, don't create cache segments smaller than this value unless
+ * the total cache size itself is smaller.
+ */
+#define DEFAULT_MIN_SEGMENT_SIZE APR_UINT64_C(0x2000000)
+
+/* The minimum segment size we will allow for multi-segmented caches
+ */
+#define MIN_SEGMENT_SIZE APR_UINT64_C(0x10000)
+
+/* The maximum number of segments allowed. Larger numbers reduce the size
+ * of each segment, in turn reducing the max size of a cachable item.
+ * Also, each segment gets its own lock object. The actual number supported
+ * by the OS may therefore be lower and svn_cache__membuffer_cache_create
+ * may return an error.
+ */
+#define MAX_SEGMENT_COUNT 0x10000
+
+/* As of today, APR won't allocate chunks of 4GB or more. So, limit the
+ * segment size to slightly below that.
+ */
+#define MAX_SEGMENT_SIZE APR_UINT64_C(0xffff0000)
+
+/* We don't mark the initialization status for every group but initialize
+ * a number of groups at once. That will allow for a very small init flags
+ * vector that is likely to fit into the CPU caches even for fairly large
+ * membuffer caches. For instance, the default of 32 means 8x32 groups per
+ * byte, i.e. 8 flags/byte x 32 groups/flag x 8 entries/group x 40 index
+ * bytes/entry x 8 cache bytes/index byte = 1kB init vector / 640MB cache.
+ */
+#define GROUP_INIT_GRANULARITY 32
+
+/* Invalid index reference value. Equivalent to APR_UINT32_T(-1)
+ */
+#define NO_INDEX APR_UINT32_MAX
+
+/* To save space in our group structure, we only use 32 bit size values
+ * and, therefore, limit the size of each entry to just below 4GB.
+ * Supporting larger items is not a good idea as the data transfer
+ * to and from the cache would block other threads for a very long time.
+ */
+#define MAX_ITEM_SIZE ((apr_uint32_t)(0 - ITEM_ALIGNMENT))
+
+/* A 16 byte key type. We use that to identify cache entries.
+ * The notation as just two integer values will cause many compilers
+ * to create better code.
+ */
+typedef apr_uint64_t entry_key_t[2];
+
+/* Debugging / corruption detection support.
+ * If you define this macro, the getter functions will performed expensive
+ * checks on the item data, requested keys and entry types. If there is
+ * a mismatch found in any of them when being compared with the values
+ * remembered in the setter function, an error will be returned.
+ */
+#ifdef SVN_DEBUG_CACHE_MEMBUFFER
+
+/* The prefix passed to svn_cache__create_membuffer_cache() effectively
+ * defines the type of all items stored by that cache instance. We'll take
+ * the last 7 bytes + \0 as plaintext for easy identification by the dev.
+ */
+#define PREFIX_TAIL_LEN 8
+
+/* This record will be attached to any cache entry. It tracks item data
+ * (content), key and type as hash values and is the baseline against which
+ * the getters will compare their results to detect inconsistencies.
+ */
+typedef struct entry_tag_t
+{
+ /* MD5 checksum over the serialized the item data.
+ */
+ unsigned char content_hash [APR_MD5_DIGESTSIZE];
+
+ /* Hash value of the svn_cache_t instance that wrote the item
+ * (i.e. a combination of type and repository)
+ */
+ unsigned char prefix_hash [APR_MD5_DIGESTSIZE];
+
+ /* Note that this only covers the variable part of the key,
+ * i.e. it will be different from the full key hash used for
+ * cache indexing.
+ */
+ unsigned char key_hash [APR_MD5_DIGESTSIZE];
+
+ /* Last letters from of the key in human readable format
+ * (ends with the type identifier, e.g. "DAG")
+ */
+ char prefix_tail[PREFIX_TAIL_LEN];
+
+ /* Length of the variable key part.
+ */
+ apr_size_t key_len;
+
+} entry_tag_t;
+
+/* Per svn_cache_t instance initialization helper.
+ */
+static void get_prefix_tail(const char *prefix, char *prefix_tail)
+{
+ apr_size_t len = strlen(prefix);
+ apr_size_t to_copy = len > PREFIX_TAIL_LEN-1 ? PREFIX_TAIL_LEN-1 : len;
+
+ memset(prefix_tail, 0, PREFIX_TAIL_LEN);
+ memcpy(prefix_tail, prefix + len - to_copy, to_copy);
+}
+
+/* Initialize all members of TAG except for the content hash.
+ */
+static svn_error_t *store_key_part(entry_tag_t *tag,
+ entry_key_t prefix_hash,
+ char *prefix_tail,
+ const void *key,
+ apr_size_t key_len,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *checksum;
+ SVN_ERR(svn_checksum(&checksum,
+ svn_checksum_md5,
+ key,
+ key_len,
+ pool));
+
+ memcpy(tag->prefix_hash, prefix_hash, sizeof(tag->prefix_hash));
+ memcpy(tag->key_hash, checksum->digest, sizeof(tag->key_hash));
+ memcpy(tag->prefix_tail, prefix_tail, sizeof(tag->prefix_tail));
+
+ tag->key_len = key_len;
+
+ return SVN_NO_ERROR;
+}
+
+/* Initialize the content hash member of TAG.
+ */
+static svn_error_t* store_content_part(entry_tag_t *tag,
+ const char *data,
+ apr_size_t size,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *checksum;
+ SVN_ERR(svn_checksum(&checksum,
+ svn_checksum_md5,
+ data,
+ size,
+ pool));
+
+ memcpy(tag->content_hash, checksum->digest, sizeof(tag->content_hash));
+
+ return SVN_NO_ERROR;
+}
+
+/* Compare two tags and fail with an assertion upon differences.
+ */
+static svn_error_t* assert_equal_tags(const entry_tag_t *lhs,
+ const entry_tag_t *rhs)
+{
+ SVN_ERR_ASSERT(memcmp(lhs->content_hash, rhs->content_hash,
+ sizeof(lhs->content_hash)) == 0);
+ SVN_ERR_ASSERT(memcmp(lhs->prefix_hash, rhs->prefix_hash,
+ sizeof(lhs->prefix_hash)) == 0);
+ SVN_ERR_ASSERT(memcmp(lhs->key_hash, rhs->key_hash,
+ sizeof(lhs->key_hash)) == 0);
+ SVN_ERR_ASSERT(memcmp(lhs->prefix_tail, rhs->prefix_tail,
+ sizeof(lhs->prefix_tail)) == 0);
+
+ SVN_ERR_ASSERT(lhs->key_len == rhs->key_len);
+
+ return SVN_NO_ERROR;
+}
+
+/* Reoccurring code snippets.
+ */
+
+#define DEBUG_CACHE_MEMBUFFER_TAG_ARG entry_tag_t *tag,
+
+#define DEBUG_CACHE_MEMBUFFER_TAG tag,
+
+#define DEBUG_CACHE_MEMBUFFER_INIT_TAG \
+ entry_tag_t _tag; \
+ entry_tag_t *tag = &_tag; \
+ SVN_ERR(store_key_part(tag, \
+ cache->prefix, \
+ cache->prefix_tail, \
+ key, \
+ cache->key_len == APR_HASH_KEY_STRING \
+ ? strlen((const char *) key) \
+ : cache->key_len, \
+ cache->pool));
+
+#else
+
+/* Don't generate any checks if consistency checks have not been enabled.
+ */
+#define DEBUG_CACHE_MEMBUFFER_TAG_ARG
+#define DEBUG_CACHE_MEMBUFFER_TAG
+#define DEBUG_CACHE_MEMBUFFER_INIT_TAG
+
+#endif /* SVN_DEBUG_CACHE_MEMBUFFER */
+
+/* A single dictionary entry. Since all entries will be allocated once
+ * during cache creation, those entries might be either used or unused.
+ * An entry is used if and only if it is contained in the doubly-linked
+ * list of used entries.
+ */
+typedef struct entry_t
+{
+ /* Identifying the data item. Only valid for used entries.
+ */
+ entry_key_t key;
+
+ /* The offset of the cached item's serialized data within the data buffer.
+ */
+ apr_uint64_t offset;
+
+ /* Size of the serialized item data. May be 0.
+ * Only valid for used entries.
+ */
+ apr_size_t size;
+
+ /* Number of (read) hits for this entry. Will be reset upon write.
+ * Only valid for used entries.
+ */
+ apr_uint32_t hit_count;
+
+ /* Reference to the next used entry in the order defined by offset.
+ * NO_INDEX indicates the end of the list; this entry must be referenced
+ * by the caches membuffer_cache_t.last member. NO_INDEX also implies
+ * that the data buffer is not used beyond offset+size.
+ * Only valid for used entries.
+ */
+ apr_uint32_t next;
+
+ /* Reference to the previous used entry in the order defined by offset.
+ * NO_INDEX indicates the end of the list; this entry must be referenced
+ * by the caches membuffer_cache_t.first member.
+ * Only valid for used entries.
+ */
+ apr_uint32_t previous;
+
+#ifdef SVN_DEBUG_CACHE_MEMBUFFER
+ /* Remember type, content and key hashes.
+ */
+ entry_tag_t tag;
+#endif
+} entry_t;
+
+/* We group dictionary entries to make this GROUP-SIZE-way associative.
+ */
+typedef struct entry_group_t
+{
+ /* number of entries used [0 .. USED-1] */
+ apr_uint32_t used;
+
+ /* the actual entries */
+ entry_t entries[GROUP_SIZE];
+} entry_group_t;
+
+/* The cache header structure.
+ */
+struct svn_membuffer_t
+{
+ /* Number of cache segments. Must be a power of 2.
+ Please note that this structure represents only one such segment
+ and that all segments must / will report the same values here. */
+ apr_uint32_t segment_count;
+
+ /* The dictionary, GROUP_SIZE * group_count entries long. Never NULL.
+ */
+ entry_group_t *directory;
+
+ /* Flag array with group_count / GROUP_INIT_GRANULARITY _bit_ elements.
+ * Allows for efficiently marking groups as "not initialized".
+ */
+ unsigned char *group_initialized;
+
+ /* Size of dictionary in groups. Must be > 0.
+ */
+ apr_uint32_t group_count;
+
+ /* Reference to the first (defined by the order content in the data
+ * buffer) dictionary entry used by any data item.
+ * NO_INDEX for an empty cache.
+ */
+ apr_uint32_t first;
+
+ /* Reference to the last (defined by the order content in the data
+ * buffer) dictionary entry used by any data item.
+ * NO_INDEX for an empty cache.
+ */
+ apr_uint32_t last;
+
+ /* Reference to the first (defined by the order content in the data
+ * buffer) used dictionary entry behind the insertion position
+ * (current_data). If NO_INDEX, the data buffer is free starting at the
+ * current_data offset.
+ */
+ apr_uint32_t next;
+
+
+ /* Pointer to the data buffer, data_size bytes long. Never NULL.
+ */
+ unsigned char *data;
+
+ /* Size of data buffer in bytes. Must be > 0.
+ */
+ apr_uint64_t data_size;
+
+ /* Offset in the data buffer where the next insertion shall occur.
+ */
+ apr_uint64_t current_data;
+
+ /* Total number of data buffer bytes in use. This is for statistics only.
+ */
+ apr_uint64_t data_used;
+
+ /* Largest entry size that we would accept. For total cache sizes
+ * less than 4TB (sic!), this is determined by the total cache size.
+ */
+ apr_uint64_t max_entry_size;
+
+
+ /* Number of used dictionary entries, i.e. number of cached items.
+ * In conjunction with hit_count, this is used calculate the average
+ * hit count as part of the randomized LFU algorithm.
+ */
+ apr_uint32_t used_entries;
+
+ /* Sum of (read) hit counts of all used dictionary entries.
+ * In conjunction used_entries used_entries, this is used calculate
+ * the average hit count as part of the randomized LFU algorithm.
+ */
+ apr_uint64_t hit_count;
+
+
+ /* Total number of calls to membuffer_cache_get.
+ * Purely statistical information that may be used for profiling.
+ */
+ apr_uint64_t total_reads;
+
+ /* Total number of calls to membuffer_cache_set.
+ * Purely statistical information that may be used for profiling.
+ */
+ apr_uint64_t total_writes;
+
+ /* Total number of hits since the cache's creation.
+ * Purely statistical information that may be used for profiling.
+ */
+ apr_uint64_t total_hits;
+
+#if APR_HAS_THREADS
+ /* A lock for intra-process synchronization to the cache, or NULL if
+ * the cache's creator doesn't feel the cache needs to be
+ * thread-safe.
+ */
+ apr_thread_rwlock_t *lock;
+
+ /* If set, write access will wait until they get exclusive access.
+ * Otherwise, they will become no-ops if the segment is currently
+ * read-locked.
+ */
+ svn_boolean_t allow_blocking_writes;
+#endif
+};
+
+/* Align integer VALUE to the next ITEM_ALIGNMENT boundary.
+ */
+#define ALIGN_VALUE(value) (((value) + ITEM_ALIGNMENT-1) & -ITEM_ALIGNMENT)
+
+/* Align POINTER value to the next ITEM_ALIGNMENT boundary.
+ */
+#define ALIGN_POINTER(pointer) ((void*)ALIGN_VALUE((apr_size_t)(char*)(pointer)))
+
+/* If locking is supported for CACHE, acquire a read lock for it.
+ */
+static svn_error_t *
+read_lock_cache(svn_membuffer_t *cache)
+{
+#if APR_HAS_THREADS
+ if (cache->lock)
+ {
+ apr_status_t status = apr_thread_rwlock_rdlock(cache->lock);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't lock cache mutex"));
+ }
+#endif
+ return SVN_NO_ERROR;
+}
+
+/* If locking is supported for CACHE, acquire a write lock for it.
+ */
+static svn_error_t *
+write_lock_cache(svn_membuffer_t *cache, svn_boolean_t *success)
+{
+#if APR_HAS_THREADS
+ if (cache->lock)
+ {
+ apr_status_t status;
+ if (cache->allow_blocking_writes)
+ {
+ status = apr_thread_rwlock_wrlock(cache->lock);
+ }
+ else
+ {
+ status = apr_thread_rwlock_trywrlock(cache->lock);
+ if (SVN_LOCK_IS_BUSY(status))
+ {
+ *success = FALSE;
+ status = APR_SUCCESS;
+ }
+ }
+
+ if (status)
+ return svn_error_wrap_apr(status,
+ _("Can't write-lock cache mutex"));
+ }
+#endif
+ return SVN_NO_ERROR;
+}
+
+/* If locking is supported for CACHE, acquire an unconditional write lock
+ * for it.
+ */
+static svn_error_t *
+force_write_lock_cache(svn_membuffer_t *cache)
+{
+#if APR_HAS_THREADS
+ apr_status_t status = apr_thread_rwlock_wrlock(cache->lock);
+ if (status)
+ return svn_error_wrap_apr(status,
+ _("Can't write-lock cache mutex"));
+#endif
+ return SVN_NO_ERROR;
+}
+
+/* If locking is supported for CACHE, release the current lock
+ * (read or write).
+ */
+static svn_error_t *
+unlock_cache(svn_membuffer_t *cache, svn_error_t *err)
+{
+#if APR_HAS_THREADS
+ if (cache->lock)
+ {
+ apr_status_t status = apr_thread_rwlock_unlock(cache->lock);
+ if (err)
+ return err;
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't unlock cache mutex"));
+ }
+#endif
+ return err;
+}
+
+/* If supported, guard the execution of EXPR with a read lock to cache.
+ * Macro has been modeled after SVN_MUTEX__WITH_LOCK.
+ */
+#define WITH_READ_LOCK(cache, expr) \
+do { \
+ SVN_ERR(read_lock_cache(cache)); \
+ SVN_ERR(unlock_cache(cache, (expr))); \
+} while (0)
+
+/* If supported, guard the execution of EXPR with a write lock to cache.
+ * Macro has been modeled after SVN_MUTEX__WITH_LOCK.
+ *
+ * The write lock process is complicated if we don't allow to wait for
+ * the lock: If we didn't get the lock, we may still need to remove an
+ * existing entry for the given key because that content is now stale.
+ * Once we discovered such an entry, we unconditionally do a blocking
+ * wait for the write lock. In case no old content could be found, a
+ * failing lock attempt is simply a no-op and we exit the macro.
+ */
+#define WITH_WRITE_LOCK(cache, expr) \
+do { \
+ svn_boolean_t got_lock = TRUE; \
+ SVN_ERR(write_lock_cache(cache, &got_lock)); \
+ if (!got_lock) \
+ { \
+ svn_boolean_t exists; \
+ SVN_ERR(entry_exists(cache, group_index, key, &exists)); \
+ if (exists) \
+ SVN_ERR(force_write_lock_cache(cache)); \
+ else \
+ break; \
+ } \
+ SVN_ERR(unlock_cache(cache, (expr))); \
+} while (0)
+
+/* Resolve a dictionary entry reference, i.e. return the entry
+ * for the given IDX.
+ */
+static APR_INLINE entry_t *
+get_entry(svn_membuffer_t *cache, apr_uint32_t idx)
+{
+ return &cache->directory[idx / GROUP_SIZE].entries[idx % GROUP_SIZE];
+}
+
+/* Get the entry references for the given ENTRY.
+ */
+static APR_INLINE apr_uint32_t
+get_index(svn_membuffer_t *cache, entry_t *entry)
+{
+ apr_size_t group_index
+ = ((char *)entry - (char *)cache->directory) / sizeof(entry_group_t);
+
+ return (apr_uint32_t)group_index * GROUP_SIZE
+ + (apr_uint32_t)(entry - cache->directory[group_index].entries);
+}
+
+/* Remove the used ENTRY from the CACHE, i.e. make it "unused".
+ * In contrast to insertion, removal is possible for any entry.
+ */
+static void
+drop_entry(svn_membuffer_t *cache, entry_t *entry)
+{
+ /* the group that ENTRY belongs to plus a number of useful index values
+ */
+ apr_uint32_t idx = get_index(cache, entry);
+ apr_uint32_t group_index = idx / GROUP_SIZE;
+ entry_group_t *group = &cache->directory[group_index];
+ apr_uint32_t last_in_group = group_index * GROUP_SIZE + group->used - 1;
+
+ /* Only valid to be called for used entries.
+ */
+ assert(idx <= last_in_group);
+
+ /* update global cache usage counters
+ */
+ cache->used_entries--;
+ cache->hit_count -= entry->hit_count;
+ cache->data_used -= entry->size;
+
+ /* extend the insertion window, if the entry happens to border it
+ */
+ if (idx == cache->next)
+ cache->next = entry->next;
+ else
+ if (entry->next == cache->next)
+ {
+ /* insertion window starts right behind the entry to remove
+ */
+ if (entry->previous == NO_INDEX)
+ {
+ /* remove the first entry -> insertion may start at pos 0, now */
+ cache->current_data = 0;
+ }
+ else
+ {
+ /* insertion may start right behind the previous entry */
+ entry_t *previous = get_entry(cache, entry->previous);
+ cache->current_data = ALIGN_VALUE( previous->offset
+ + previous->size);
+ }
+ }
+
+ /* unlink it from the chain of used entries
+ */
+ if (entry->previous == NO_INDEX)
+ cache->first = entry->next;
+ else
+ get_entry(cache, entry->previous)->next = entry->next;
+
+ if (entry->next == NO_INDEX)
+ cache->last = entry->previous;
+ else
+ get_entry(cache, entry->next)->previous = entry->previous;
+
+ /* Move last entry into hole (if the removed one is not the last used).
+ * We need to do this since all used entries are at the beginning of
+ * the group's entries array.
+ */
+ if (idx < last_in_group)
+ {
+ /* copy the last used entry to the removed entry's index
+ */
+ *entry = group->entries[group->used-1];
+
+ /* update foreign links to new index
+ */
+ if (last_in_group == cache->next)
+ cache->next = idx;
+
+ if (entry->previous == NO_INDEX)
+ cache->first = idx;
+ else
+ get_entry(cache, entry->previous)->next = idx;
+
+ if (entry->next == NO_INDEX)
+ cache->last = idx;
+ else
+ get_entry(cache, entry->next)->previous = idx;
+ }
+
+ /* Update the number of used entries.
+ */
+ group->used--;
+}
+
+/* Insert ENTRY into the chain of used dictionary entries. The entry's
+ * offset and size members must already have been initialized. Also,
+ * the offset must match the beginning of the insertion window.
+ */
+static void
+insert_entry(svn_membuffer_t *cache, entry_t *entry)
+{
+ /* the group that ENTRY belongs to plus a number of useful index values
+ */
+ apr_uint32_t idx = get_index(cache, entry);
+ apr_uint32_t group_index = idx / GROUP_SIZE;
+ entry_group_t *group = &cache->directory[group_index];
+ entry_t *next = cache->next == NO_INDEX
+ ? NULL
+ : get_entry(cache, cache->next);
+
+ /* The entry must start at the beginning of the insertion window.
+ * It must also be the first unused entry in the group.
+ */
+ assert(entry->offset == cache->current_data);
+ assert(idx == group_index * GROUP_SIZE + group->used);
+ cache->current_data = ALIGN_VALUE(entry->offset + entry->size);
+
+ /* update usage counters
+ */
+ cache->used_entries++;
+ cache->data_used += entry->size;
+ entry->hit_count = 0;
+ group->used++;
+
+ /* update entry chain
+ */
+ entry->next = cache->next;
+ if (cache->first == NO_INDEX)
+ {
+ /* insert as the first entry and only in the chain
+ */
+ entry->previous = NO_INDEX;
+ cache->last = idx;
+ cache->first = idx;
+ }
+ else if (next == NULL)
+ {
+ /* insert as the last entry in the chain.
+ * Note that it cannot also be at the beginning of the chain.
+ */
+ entry->previous = cache->last;
+ get_entry(cache, cache->last)->next = idx;
+ cache->last = idx;
+ }
+ else
+ {
+ /* insert either at the start of a non-empty list or
+ * somewhere in the middle
+ */
+ entry->previous = next->previous;
+ next->previous = idx;
+
+ if (entry->previous != NO_INDEX)
+ get_entry(cache, entry->previous)->next = idx;
+ else
+ cache->first = idx;
+ }
+
+ /* The current insertion position must never point outside our
+ * data buffer.
+ */
+ assert(cache->current_data <= cache->data_size);
+}
+
+/* Map a KEY of 16 bytes to the CACHE and group that shall contain the
+ * respective item.
+ */
+static apr_uint32_t
+get_group_index(svn_membuffer_t **cache,
+ entry_key_t key)
+{
+ svn_membuffer_t *segment0 = *cache;
+
+ /* select the cache segment to use. they have all the same group_count */
+ *cache = &segment0[key[0] & (segment0->segment_count -1)];
+ return key[1] % segment0->group_count;
+}
+
+/* Reduce the hit count of ENTRY and update the accumulated hit info
+ * in CACHE accordingly.
+ */
+static APR_INLINE void
+let_entry_age(svn_membuffer_t *cache, entry_t *entry)
+{
+ apr_uint32_t hits_removed = (entry->hit_count + 1) >> 1;
+
+ cache->hit_count -= hits_removed;
+ entry->hit_count -= hits_removed;
+}
+
+/* Returns 0 if the entry group identified by GROUP_INDEX in CACHE has not
+ * been initialized, yet. In that case, this group can not data. Otherwise,
+ * a non-zero value is returned.
+ */
+static APR_INLINE unsigned char
+is_group_initialized(svn_membuffer_t *cache, apr_uint32_t group_index)
+{
+ unsigned char flags
+ = cache->group_initialized[group_index / (8 * GROUP_INIT_GRANULARITY)];
+ unsigned char bit_mask
+ = (unsigned char)(1 << ((group_index / GROUP_INIT_GRANULARITY) % 8));
+
+ return flags & bit_mask;
+}
+
+/* Initializes the section of the directory in CACHE that contains
+ * the entry group identified by GROUP_INDEX. */
+static void
+initialize_group(svn_membuffer_t *cache, apr_uint32_t group_index)
+{
+ unsigned char bit_mask;
+ apr_uint32_t i;
+
+ /* range of groups to initialize due to GROUP_INIT_GRANULARITY */
+ apr_uint32_t first_index =
+ (group_index / GROUP_INIT_GRANULARITY) * GROUP_INIT_GRANULARITY;
+ apr_uint32_t last_index = first_index + GROUP_INIT_GRANULARITY;
+ if (last_index > cache->group_count)
+ last_index = cache->group_count;
+
+ for (i = first_index; i < last_index; ++i)
+ cache->directory[i].used = 0;
+
+ /* set the "initialized" bit for these groups */
+ bit_mask
+ = (unsigned char)(1 << ((group_index / GROUP_INIT_GRANULARITY) % 8));
+ cache->group_initialized[group_index / (8 * GROUP_INIT_GRANULARITY)]
+ |= bit_mask;
+}
+
+/* Given the GROUP_INDEX that shall contain an entry with the hash key
+ * TO_FIND, find that entry in the specified group.
+ *
+ * If FIND_EMPTY is not set, this function will return the one used entry
+ * that actually matches the hash or NULL, if no such entry exists.
+ *
+ * If FIND_EMPTY has been set, this function will drop the one used entry
+ * that actually matches the hash (i.e. make it fit to be replaced with
+ * new content), an unused entry or a forcibly removed entry (if all
+ * group entries are currently in use). The entries' hash value will be
+ * initialized with TO_FIND.
+ */
+static entry_t *
+find_entry(svn_membuffer_t *cache,
+ apr_uint32_t group_index,
+ const apr_uint64_t to_find[2],
+ svn_boolean_t find_empty)
+{
+ entry_group_t *group;
+ entry_t *entry = NULL;
+ apr_size_t i;
+
+ /* get the group that *must* contain the entry
+ */
+ group = &cache->directory[group_index];
+
+ /* If the entry group has not been initialized, yet, there is no data.
+ */
+ if (! is_group_initialized(cache, group_index))
+ {
+ if (find_empty)
+ {
+ initialize_group(cache, group_index);
+ entry = &group->entries[0];
+
+ /* initialize entry for the new key */
+ entry->key[0] = to_find[0];
+ entry->key[1] = to_find[1];
+ }
+
+ return entry;
+ }
+
+ /* try to find the matching entry
+ */
+ for (i = 0; i < group->used; ++i)
+ if ( to_find[0] == group->entries[i].key[0]
+ && to_find[1] == group->entries[i].key[1])
+ {
+ /* found it
+ */
+ entry = &group->entries[i];
+ if (find_empty)
+ drop_entry(cache, entry);
+ else
+ return entry;
+ }
+
+ /* None found. Are we looking for a free entry?
+ */
+ if (find_empty)
+ {
+ /* if there is no empty entry, delete the oldest entry
+ */
+ if (group->used == GROUP_SIZE)
+ {
+ /* every entry gets the same chance of being removed.
+ * Otherwise, we free the first entry, fill it and
+ * remove it again on the next occasion without considering
+ * the other entries in this group.
+ */
+ entry = &group->entries[rand() % GROUP_SIZE];
+ for (i = 1; i < GROUP_SIZE; ++i)
+ if (entry->hit_count > group->entries[i].hit_count)
+ entry = &group->entries[i];
+
+ /* for the entries that don't have been removed,
+ * reduce their hit counts to put them at a relative
+ * disadvantage the next time.
+ */
+ for (i = 0; i < GROUP_SIZE; ++i)
+ if (entry != &group->entries[i])
+ let_entry_age(cache, entry);
+
+ drop_entry(cache, entry);
+ }
+
+ /* initialize entry for the new key
+ */
+ entry = &group->entries[group->used];
+ entry->key[0] = to_find[0];
+ entry->key[1] = to_find[1];
+ }
+
+ return entry;
+}
+
+/* Move a surviving ENTRY from just behind the insertion window to
+ * its beginning and move the insertion window up accordingly.
+ */
+static void
+move_entry(svn_membuffer_t *cache, entry_t *entry)
+{
+ apr_size_t size = ALIGN_VALUE(entry->size);
+
+ /* This entry survived this cleansing run. Reset half of its
+ * hit count so that its removal gets more likely in the next
+ * run unless someone read / hit this entry in the meantime.
+ */
+ let_entry_age(cache, entry);
+
+ /* Move the entry to the start of the empty / insertion section
+ * (if it isn't there already). Size-aligned moves are legal
+ * since all offsets and block sizes share this same alignment.
+ * Size-aligned moves tend to be faster than non-aligned ones
+ * because no "odd" bytes at the end need to special treatment.
+ */
+ if (entry->offset != cache->current_data)
+ {
+ memmove(cache->data + cache->current_data,
+ cache->data + entry->offset,
+ size);
+ entry->offset = cache->current_data;
+ }
+
+ /* The insertion position is now directly behind this entry.
+ */
+ cache->current_data = entry->offset + size;
+ cache->next = entry->next;
+
+ /* The current insertion position must never point outside our
+ * data buffer.
+ */
+ assert(cache->current_data <= cache->data_size);
+}
+
+/* If necessary, enlarge the insertion window until it is at least
+ * SIZE bytes long. SIZE must not exceed the data buffer size.
+ * Return TRUE if enough room could be found or made. A FALSE result
+ * indicates that the respective item shall not be added.
+ */
+static svn_boolean_t
+ensure_data_insertable(svn_membuffer_t *cache, apr_size_t size)
+{
+ entry_t *entry;
+ apr_uint64_t average_hit_value;
+ apr_uint64_t threshold;
+
+ /* accumulated size of the entries that have been removed to make
+ * room for the new one.
+ */
+ apr_size_t drop_size = 0;
+
+ /* This loop will eventually terminate because every cache entry
+ * would get dropped eventually:
+ * - hit counts become 0 after the got kept for 32 full scans
+ * - larger elements get dropped as soon as their hit count is 0
+ * - smaller and smaller elements get removed as the average
+ * entry size drops (average drops by a factor of 8 per scan)
+ * - after no more than 43 full scans, all elements would be removed
+ *
+ * Since size is < 4th of the cache size and about 50% of all
+ * entries get removed by a scan, it is very unlikely that more
+ * than a fractional scan will be necessary.
+ */
+ while (1)
+ {
+ /* first offset behind the insertion window
+ */
+ apr_uint64_t end = cache->next == NO_INDEX
+ ? cache->data_size
+ : get_entry(cache, cache->next)->offset;
+
+ /* leave function as soon as the insertion window is large enough
+ */
+ if (end >= size + cache->current_data)
+ return TRUE;
+
+ /* Don't be too eager to cache data. Smaller items will fit into
+ * the cache after dropping a single item. Of the larger ones, we
+ * will only accept about 50%. They are also likely to get evicted
+ * soon due to their notoriously low hit counts.
+ *
+ * As long as enough similarly or even larger sized entries already
+ * exist in the cache, much less insert requests will be rejected.
+ */
+ if (2 * drop_size > size)
+ return FALSE;
+
+ /* try to enlarge the insertion window
+ */
+ if (cache->next == NO_INDEX)
+ {
+ /* We reached the end of the data buffer; restart at the beginning.
+ * Due to the randomized nature of our LFU implementation, very
+ * large data items may require multiple passes. Therefore, SIZE
+ * should be restricted to significantly less than data_size.
+ */
+ cache->current_data = 0;
+ cache->next = cache->first;
+ }
+ else
+ {
+ entry = get_entry(cache, cache->next);
+
+ /* Keep entries that are very small. Those are likely to be data
+ * headers or similar management structures. So, they are probably
+ * important while not occupying much space.
+ * But keep them only as long as they are a minority.
+ */
+ if ( (apr_uint64_t)entry->size * cache->used_entries
+ < cache->data_used / 8)
+ {
+ move_entry(cache, entry);
+ }
+ else
+ {
+ svn_boolean_t keep;
+
+ if (cache->hit_count > cache->used_entries)
+ {
+ /* Roll the dice and determine a threshold somewhere from 0 up
+ * to 2 times the average hit count.
+ */
+ average_hit_value = cache->hit_count / cache->used_entries;
+ threshold = (average_hit_value+1) * (rand() % 4096) / 2048;
+
+ keep = entry->hit_count >= threshold;
+ }
+ else
+ {
+ /* general hit count is low. Keep everything that got hit
+ * at all and assign some 50% survival chance to everything
+ * else.
+ */
+ keep = (entry->hit_count > 0) || (rand() & 1);
+ }
+
+ /* keepers or destroyers? */
+ if (keep)
+ {
+ move_entry(cache, entry);
+ }
+ else
+ {
+ /* Drop the entry from the end of the insertion window, if it
+ * has been hit less than the threshold. Otherwise, keep it and
+ * move the insertion window one entry further.
+ */
+ drop_size += entry->size;
+ drop_entry(cache, entry);
+ }
+ }
+ }
+ }
+
+ /* This will never be reached. But if it was, "can't insert" was the
+ * right answer. */
+}
+
+/* Mimic apr_pcalloc in APR_POOL_DEBUG mode, i.e. handle failed allocations
+ * (e.g. OOM) properly: Allocate at least SIZE bytes from POOL and zero
+ * the content of the allocated memory if ZERO has been set. Return NULL
+ * upon failed allocations.
+ *
+ * Also, satisfy our buffer alignment needs for performance reasons.
+ */
+static void* secure_aligned_alloc(apr_pool_t *pool,
+ apr_size_t size,
+ svn_boolean_t zero)
+{
+ void* memory = apr_palloc(pool, size + ITEM_ALIGNMENT);
+ if (memory != NULL)
+ {
+ memory = ALIGN_POINTER(memory);
+ if (zero)
+ memset(memory, 0, size);
+ }
+
+ return memory;
+}
+
+svn_error_t *
+svn_cache__membuffer_cache_create(svn_membuffer_t **cache,
+ apr_size_t total_size,
+ apr_size_t directory_size,
+ apr_size_t segment_count,
+ svn_boolean_t thread_safe,
+ svn_boolean_t allow_blocking_writes,
+ apr_pool_t *pool)
+{
+ svn_membuffer_t *c;
+
+ apr_uint32_t seg;
+ apr_uint32_t group_count;
+ apr_uint32_t group_init_size;
+ apr_uint64_t data_size;
+ apr_uint64_t max_entry_size;
+
+ /* Limit the total size (only relevant if we can address > 4GB)
+ */
+#if APR_SIZEOF_VOIDP > 4
+ if (total_size > MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT)
+ total_size = MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT;
+#endif
+
+ /* Limit the segment count
+ */
+ if (segment_count > MAX_SEGMENT_COUNT)
+ segment_count = MAX_SEGMENT_COUNT;
+ if (segment_count * MIN_SEGMENT_SIZE > total_size)
+ segment_count = total_size / MIN_SEGMENT_SIZE;
+
+ /* The segment count must be a power of two. Round it down as necessary.
+ */
+ while ((segment_count & (segment_count-1)) != 0)
+ segment_count &= segment_count-1;
+
+ /* if the caller hasn't provided a reasonable segment count or the above
+ * limitations set it to 0, derive one from the absolute cache size
+ */
+ if (segment_count < 1)
+ {
+ /* Determine a reasonable number of cache segments. Segmentation is
+ * only useful for multi-threaded / multi-core servers as it reduces
+ * lock contention on these systems.
+ *
+ * But on these systems, we can assume that ample memory has been
+ * allocated to this cache. Smaller caches should not be segmented
+ * as this severely limits the maximum size of cachable items.
+ *
+ * Segments should not be smaller than 32MB and max. cachable item
+ * size should grow as fast as segmentation.
+ */
+
+ apr_uint32_t segment_count_shift = 0;
+ while (((2 * DEFAULT_MIN_SEGMENT_SIZE) << (2 * segment_count_shift))
+ < total_size)
+ ++segment_count_shift;
+
+ segment_count = (apr_size_t)1 << segment_count_shift;
+ }
+
+ /* If we have an extremely large cache (>512 GB), the default segment
+ * size may exceed the amount allocatable as one chunk. In that case,
+ * increase segmentation until we are under the threshold.
+ */
+ while ( total_size / segment_count > MAX_SEGMENT_SIZE
+ && segment_count < MAX_SEGMENT_COUNT)
+ segment_count *= 2;
+
+ /* allocate cache as an array of segments / cache objects */
+ c = apr_palloc(pool, segment_count * sizeof(*c));
+
+ /* Split total cache size into segments of equal size
+ */
+ total_size /= segment_count;
+ directory_size /= segment_count;
+
+ /* prevent pathological conditions: ensure a certain minimum cache size
+ */
+ if (total_size < 2 * sizeof(entry_group_t))
+ total_size = 2 * sizeof(entry_group_t);
+
+ /* adapt the dictionary size accordingly, if necessary:
+ * It must hold at least one group and must not exceed the cache size.
+ */
+ if (directory_size > total_size - sizeof(entry_group_t))
+ directory_size = total_size - sizeof(entry_group_t);
+ if (directory_size < sizeof(entry_group_t))
+ directory_size = sizeof(entry_group_t);
+
+ /* limit the data size to what we can address.
+ * Note that this cannot overflow since all values are of size_t.
+ * Also, make it a multiple of the item placement granularity to
+ * prevent subtle overflows.
+ */
+ data_size = ALIGN_VALUE(total_size - directory_size + 1) - ITEM_ALIGNMENT;
+
+ /* For cache sizes > 4TB, individual cache segments will be larger
+ * than 16GB allowing for >4GB entries. But caching chunks larger
+ * than 4GB is simply not supported.
+ */
+ max_entry_size = data_size / 4 > MAX_ITEM_SIZE
+ ? MAX_ITEM_SIZE
+ : data_size / 4;
+
+ /* to keep the entries small, we use 32 bit indexes only
+ * -> we need to ensure that no more then 4G entries exist.
+ *
+ * Note, that this limit could only be exceeded in a very
+ * theoretical setup with about 1EB of cache.
+ */
+ group_count = directory_size / sizeof(entry_group_t)
+ >= (APR_UINT32_MAX / GROUP_SIZE)
+ ? (APR_UINT32_MAX / GROUP_SIZE) - 1
+ : (apr_uint32_t)(directory_size / sizeof(entry_group_t));
+
+ group_init_size = 1 + group_count / (8 * GROUP_INIT_GRANULARITY);
+ for (seg = 0; seg < segment_count; ++seg)
+ {
+ /* allocate buffers and initialize cache members
+ */
+ c[seg].segment_count = (apr_uint32_t)segment_count;
+
+ c[seg].group_count = group_count;
+ c[seg].directory = apr_pcalloc(pool,
+ group_count * sizeof(entry_group_t));
+
+ /* Allocate and initialize directory entries as "not initialized",
+ hence "unused" */
+ c[seg].group_initialized = apr_pcalloc(pool, group_init_size);
+
+ c[seg].first = NO_INDEX;
+ c[seg].last = NO_INDEX;
+ c[seg].next = NO_INDEX;
+
+ c[seg].data_size = data_size;
+ c[seg].data = secure_aligned_alloc(pool, (apr_size_t)data_size, FALSE);
+ c[seg].current_data = 0;
+ c[seg].data_used = 0;
+ c[seg].max_entry_size = max_entry_size;
+
+ c[seg].used_entries = 0;
+ c[seg].hit_count = 0;
+ c[seg].total_reads = 0;
+ c[seg].total_writes = 0;
+ c[seg].total_hits = 0;
+
+ /* were allocations successful?
+ * If not, initialize a minimal cache structure.
+ */
+ if (c[seg].data == NULL || c[seg].directory == NULL)
+ {
+ /* We are OOM. There is no need to proceed with "half a cache".
+ */
+ return svn_error_wrap_apr(APR_ENOMEM, "OOM");
+ }
+
+#if APR_HAS_THREADS
+ /* A lock for intra-process synchronization to the cache, or NULL if
+ * the cache's creator doesn't feel the cache needs to be
+ * thread-safe.
+ */
+ c[seg].lock = NULL;
+ if (thread_safe)
+ {
+ apr_status_t status =
+ apr_thread_rwlock_create(&(c[seg].lock), pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't create cache mutex"));
+ }
+
+ /* Select the behavior of write operations.
+ */
+ c[seg].allow_blocking_writes = allow_blocking_writes;
+#endif
+ }
+
+ /* done here
+ */
+ *cache = c;
+ return SVN_NO_ERROR;
+}
+
+/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
+ * by the hash value TO_FIND and set *FOUND accordingly.
+ *
+ * Note: This function requires the caller to serialize access.
+ * Don't call it directly, call entry_exists instead.
+ */
+static svn_error_t *
+entry_exists_internal(svn_membuffer_t *cache,
+ apr_uint32_t group_index,
+ entry_key_t to_find,
+ svn_boolean_t *found)
+{
+ *found = find_entry(cache, group_index, to_find, FALSE) != NULL;
+ return SVN_NO_ERROR;
+}
+
+/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
+ * by the hash value TO_FIND and set *FOUND accordingly.
+ */
+static svn_error_t *
+entry_exists(svn_membuffer_t *cache,
+ apr_uint32_t group_index,
+ entry_key_t to_find,
+ svn_boolean_t *found)
+{
+ WITH_READ_LOCK(cache,
+ entry_exists_internal(cache,
+ group_index,
+ to_find,
+ found));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Try to insert the serialized item given in BUFFER with SIZE into
+ * the group GROUP_INDEX of CACHE and uniquely identify it by hash
+ * value TO_FIND.
+ *
+ * However, there is no guarantee that it will actually be put into
+ * the cache. If there is already some data associated with TO_FIND,
+ * it will be removed from the cache even if the new data cannot
+ * be inserted.
+ *
+ * Note: This function requires the caller to serialization access.
+ * Don't call it directly, call membuffer_cache_get_partial instead.
+ */
+static svn_error_t *
+membuffer_cache_set_internal(svn_membuffer_t *cache,
+ entry_key_t to_find,
+ apr_uint32_t group_index,
+ char *buffer,
+ apr_size_t size,
+ DEBUG_CACHE_MEMBUFFER_TAG_ARG
+ apr_pool_t *scratch_pool)
+{
+ /* first, look for a previous entry for the given key */
+ entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
+
+ /* if there is an old version of that entry and the new data fits into
+ * the old spot, just re-use that space. */
+ if (entry && ALIGN_VALUE(entry->size) >= size && buffer)
+ {
+ cache->data_used += size - entry->size;
+ entry->size = size;
+
+#ifdef SVN_DEBUG_CACHE_MEMBUFFER
+
+ /* Remember original content, type and key (hashes)
+ */
+ SVN_ERR(store_content_part(tag, buffer, size, scratch_pool));
+ memcpy(&entry->tag, tag, sizeof(*tag));
+
+#endif
+
+ if (size)
+ memcpy(cache->data + entry->offset, buffer, size);
+
+ cache->total_writes++;
+ return SVN_NO_ERROR;
+ }
+
+ /* if necessary, enlarge the insertion window.
+ */
+ if ( buffer != NULL
+ && cache->max_entry_size >= size
+ && ensure_data_insertable(cache, size))
+ {
+ /* Remove old data for this key, if that exists.
+ * Get an unused entry for the key and and initialize it with
+ * the serialized item's (future) position within data buffer.
+ */
+ entry = find_entry(cache, group_index, to_find, TRUE);
+ entry->size = size;
+ entry->offset = cache->current_data;
+
+#ifdef SVN_DEBUG_CACHE_MEMBUFFER
+
+ /* Remember original content, type and key (hashes)
+ */
+ SVN_ERR(store_content_part(tag, buffer, size, scratch_pool));
+ memcpy(&entry->tag, tag, sizeof(*tag));
+
+#endif
+
+ /* Link the entry properly.
+ */
+ insert_entry(cache, entry);
+
+ /* Copy the serialized item data into the cache.
+ */
+ if (size)
+ memcpy(cache->data + entry->offset, buffer, size);
+
+ cache->total_writes++;
+ }
+ else
+ {
+ /* if there is already an entry for this key, drop it.
+ * Since ensure_data_insertable may have removed entries from
+ * ENTRY's group, re-do the lookup.
+ */
+ entry = find_entry(cache, group_index, to_find, FALSE);
+ if (entry)
+ drop_entry(cache, entry);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Try to insert the ITEM and use the KEY to uniquely identify it.
+ * However, there is no guarantee that it will actually be put into
+ * the cache. If there is already some data associated to the KEY,
+ * it will be removed from the cache even if the new data cannot
+ * be inserted.
+ *
+ * The SERIALIZER is called to transform the ITEM into a single,
+ * flat data buffer. Temporary allocations may be done in POOL.
+ */
+static svn_error_t *
+membuffer_cache_set(svn_membuffer_t *cache,
+ entry_key_t key,
+ void *item,
+ svn_cache__serialize_func_t serializer,
+ DEBUG_CACHE_MEMBUFFER_TAG_ARG
+ apr_pool_t *scratch_pool)
+{
+ apr_uint32_t group_index;
+ void *buffer = NULL;
+ apr_size_t size = 0;
+
+ /* find the entry group that will hold the key.
+ */
+ group_index = get_group_index(&cache, key);
+
+ /* Serialize data data.
+ */
+ if (item)
+ SVN_ERR(serializer(&buffer, &size, item, scratch_pool));
+
+ /* The actual cache data access needs to sync'ed
+ */
+ WITH_WRITE_LOCK(cache,
+ membuffer_cache_set_internal(cache,
+ key,
+ group_index,
+ buffer,
+ size,
+ DEBUG_CACHE_MEMBUFFER_TAG
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
+ * by the hash value TO_FIND. If no item has been stored for KEY,
+ * *BUFFER will be NULL. Otherwise, return a copy of the serialized
+ * data in *BUFFER and return its size in *ITEM_SIZE. Allocations will
+ * be done in POOL.
+ *
+ * Note: This function requires the caller to serialization access.
+ * Don't call it directly, call membuffer_cache_get_partial instead.
+ */
+static svn_error_t *
+membuffer_cache_get_internal(svn_membuffer_t *cache,
+ apr_uint32_t group_index,
+ entry_key_t to_find,
+ char **buffer,
+ apr_size_t *item_size,
+ DEBUG_CACHE_MEMBUFFER_TAG_ARG
+ apr_pool_t *result_pool)
+{
+ entry_t *entry;
+ apr_size_t size;
+
+ /* The actual cache data access needs to sync'ed
+ */
+ entry = find_entry(cache, group_index, to_find, FALSE);
+ cache->total_reads++;
+ if (entry == NULL)
+ {
+ /* no such entry found.
+ */
+ *buffer = NULL;
+ *item_size = 0;
+
+ return SVN_NO_ERROR;
+ }
+
+ size = ALIGN_VALUE(entry->size);
+ *buffer = ALIGN_POINTER(apr_palloc(result_pool, size + ITEM_ALIGNMENT-1));
+ memcpy(*buffer, (const char*)cache->data + entry->offset, size);
+
+#ifdef SVN_DEBUG_CACHE_MEMBUFFER
+
+ /* Check for overlapping entries.
+ */
+ SVN_ERR_ASSERT(entry->next == NO_INDEX ||
+ entry->offset + size
+ <= get_entry(cache, entry->next)->offset);
+
+ /* Compare original content, type and key (hashes)
+ */
+ SVN_ERR(store_content_part(tag, *buffer, entry->size, result_pool));
+ SVN_ERR(assert_equal_tags(&entry->tag, tag));
+
+#endif
+
+ /* update hit statistics
+ */
+ entry->hit_count++;
+ cache->hit_count++;
+ cache->total_hits++;
+
+ *item_size = entry->size;
+
+ return SVN_NO_ERROR;
+}
+
+/* Look for the *ITEM identified by KEY. If no item has been stored
+ * for KEY, *ITEM will be NULL. Otherwise, the DESERIALIZER is called
+ * re-construct the proper object from the serialized data.
+ * Allocations will be done in POOL.
+ */
+static svn_error_t *
+membuffer_cache_get(svn_membuffer_t *cache,
+ entry_key_t key,
+ void **item,
+ svn_cache__deserialize_func_t deserializer,
+ DEBUG_CACHE_MEMBUFFER_TAG_ARG
+ apr_pool_t *result_pool)
+{
+ apr_uint32_t group_index;
+ char *buffer;
+ apr_size_t size;
+
+ /* find the entry group that will hold the key.
+ */
+ group_index = get_group_index(&cache, key);
+ WITH_READ_LOCK(cache,
+ membuffer_cache_get_internal(cache,
+ group_index,
+ key,
+ &buffer,
+ &size,
+ DEBUG_CACHE_MEMBUFFER_TAG
+ result_pool));
+
+ /* re-construct the original data object from its serialized form.
+ */
+ if (buffer == NULL)
+ {
+ *item = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ return deserializer(item, buffer, size, result_pool);
+}
+
+/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
+ * by the hash value TO_FIND. FOUND indicates whether that entry exists.
+ * If not found, *ITEM will be NULL.
+ *
+ * Otherwise, the DESERIALIZER is called with that entry and the BATON
+ * provided and will extract the desired information. The result is set
+ * in *ITEM. Allocations will be done in POOL.
+ *
+ * Note: This function requires the caller to serialization access.
+ * Don't call it directly, call membuffer_cache_get_partial instead.
+ */
+static svn_error_t *
+membuffer_cache_get_partial_internal(svn_membuffer_t *cache,
+ apr_uint32_t group_index,
+ entry_key_t to_find,
+ void **item,
+ svn_boolean_t *found,
+ svn_cache__partial_getter_func_t deserializer,
+ void *baton,
+ DEBUG_CACHE_MEMBUFFER_TAG_ARG
+ apr_pool_t *result_pool)
+{
+ entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
+ cache->total_reads++;
+ if (entry == NULL)
+ {
+ *item = NULL;
+ *found = FALSE;
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ *found = TRUE;
+
+ entry->hit_count++;
+ cache->hit_count++;
+ cache->total_hits++;
+
+#ifdef SVN_DEBUG_CACHE_MEMBUFFER
+
+ /* Check for overlapping entries.
+ */
+ SVN_ERR_ASSERT(entry->next == NO_INDEX ||
+ entry->offset + entry->size
+ <= get_entry(cache, entry->next)->offset);
+
+ /* Compare original content, type and key (hashes)
+ */
+ SVN_ERR(store_content_part(tag,
+ (const char*)cache->data + entry->offset,
+ entry->size,
+ result_pool));
+ SVN_ERR(assert_equal_tags(&entry->tag, tag));
+
+#endif
+
+ return deserializer(item,
+ (const char*)cache->data + entry->offset,
+ entry->size,
+ baton,
+ result_pool);
+ }
+}
+
+/* Look for the cache entry identified by KEY. FOUND indicates
+ * whether that entry exists. If not found, *ITEM will be NULL. Otherwise,
+ * the DESERIALIZER is called with that entry and the BATON provided
+ * and will extract the desired information. The result is set in *ITEM.
+ * Allocations will be done in POOL.
+ */
+static svn_error_t *
+membuffer_cache_get_partial(svn_membuffer_t *cache,
+ entry_key_t key,
+ void **item,
+ svn_boolean_t *found,
+ svn_cache__partial_getter_func_t deserializer,
+ void *baton,
+ DEBUG_CACHE_MEMBUFFER_TAG_ARG
+ apr_pool_t *result_pool)
+{
+ apr_uint32_t group_index = get_group_index(&cache, key);
+
+ WITH_READ_LOCK(cache,
+ membuffer_cache_get_partial_internal
+ (cache, group_index, key, item, found,
+ deserializer, baton, DEBUG_CACHE_MEMBUFFER_TAG
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
+ * by the hash value TO_FIND. If no entry has been found, the function
+ * returns without modifying the cache.
+ *
+ * Otherwise, FUNC is called with that entry and the BATON provided
+ * and may modify the cache entry. Allocations will be done in POOL.
+ *
+ * Note: This function requires the caller to serialization access.
+ * Don't call it directly, call membuffer_cache_set_partial instead.
+ */
+static svn_error_t *
+membuffer_cache_set_partial_internal(svn_membuffer_t *cache,
+ apr_uint32_t group_index,
+ entry_key_t to_find,
+ svn_cache__partial_setter_func_t func,
+ void *baton,
+ DEBUG_CACHE_MEMBUFFER_TAG_ARG
+ apr_pool_t *scratch_pool)
+{
+ /* cache item lookup
+ */
+ entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
+ cache->total_reads++;
+
+ /* this function is a no-op if the item is not in cache
+ */
+ if (entry != NULL)
+ {
+ svn_error_t *err;
+
+ /* access the serialized cache item */
+ char *data = (char*)cache->data + entry->offset;
+ char *orig_data = data;
+ apr_size_t size = entry->size;
+
+ entry->hit_count++;
+ cache->hit_count++;
+ cache->total_writes++;
+
+#ifdef SVN_DEBUG_CACHE_MEMBUFFER
+
+ /* Check for overlapping entries.
+ */
+ SVN_ERR_ASSERT(entry->next == NO_INDEX ||
+ entry->offset + size
+ <= get_entry(cache, entry->next)->offset);
+
+ /* Compare original content, type and key (hashes)
+ */
+ SVN_ERR(store_content_part(tag, data, size, scratch_pool));
+ SVN_ERR(assert_equal_tags(&entry->tag, tag));
+
+#endif
+
+ /* modify it, preferably in-situ.
+ */
+ err = func((void **)&data, &size, baton, scratch_pool);
+
+ if (err)
+ {
+ /* Something somewhere when wrong while FUNC was modifying the
+ * changed item. Thus, it might have become invalid /corrupted.
+ * We better drop that.
+ */
+ drop_entry(cache, entry);
+ }
+ else
+ {
+ /* if the modification caused a re-allocation, we need to remove
+ * the old entry and to copy the new data back into cache.
+ */
+ if (data != orig_data)
+ {
+ /* Remove the old entry and try to make space for the new one.
+ */
+ drop_entry(cache, entry);
+ if ( (cache->max_entry_size >= size)
+ && ensure_data_insertable(cache, size))
+ {
+ /* Write the new entry.
+ */
+ entry = find_entry(cache, group_index, to_find, TRUE);
+ entry->size = size;
+ entry->offset = cache->current_data;
+ if (size)
+ memcpy(cache->data + entry->offset, data, size);
+
+ /* Link the entry properly.
+ */
+ insert_entry(cache, entry);
+ }
+ }
+
+#ifdef SVN_DEBUG_CACHE_MEMBUFFER
+
+ /* Remember original content, type and key (hashes)
+ */
+ SVN_ERR(store_content_part(tag, data, size, scratch_pool));
+ memcpy(&entry->tag, tag, sizeof(*tag));
+
+#endif
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Look for the cache entry identified by KEY. If no entry
+ * has been found, the function returns without modifying the cache.
+ * Otherwise, FUNC is called with that entry and the BATON provided
+ * and may modify the cache entry. Allocations will be done in POOL.
+ */
+static svn_error_t *
+membuffer_cache_set_partial(svn_membuffer_t *cache,
+ entry_key_t key,
+ svn_cache__partial_setter_func_t func,
+ void *baton,
+ DEBUG_CACHE_MEMBUFFER_TAG_ARG
+ apr_pool_t *scratch_pool)
+{
+ /* cache item lookup
+ */
+ apr_uint32_t group_index = get_group_index(&cache, key);
+ WITH_WRITE_LOCK(cache,
+ membuffer_cache_set_partial_internal
+ (cache, group_index, key, func, baton,
+ DEBUG_CACHE_MEMBUFFER_TAG
+ scratch_pool));
+
+ /* done here -> unlock the cache
+ */
+ return SVN_NO_ERROR;
+}
+
+/* Implement the svn_cache__t interface on top of a shared membuffer cache.
+ *
+ * Because membuffer caches tend to be very large, there will be rather few
+ * of them (usually only one). Thus, the same instance shall be used as the
+ * backend to many application-visible svn_cache__t instances. This should
+ * also achieve global resource usage fairness.
+ *
+ * To accommodate items from multiple resources, the individual keys must be
+ * unique over all sources. This is achieved by simply adding a prefix key
+ * that unambiguously identifies the item's context (e.g. path to the
+ * respective repository). The prefix will be set upon construction of the
+ * svn_cache__t instance.
+ */
+
+/* Internal cache structure (used in svn_cache__t.cache_internal) basically
+ * holding the additional parameters needed to call the respective membuffer
+ * functions.
+ */
+typedef struct svn_membuffer_cache_t
+{
+ /* this is where all our data will end up in
+ */
+ svn_membuffer_t *membuffer;
+
+ /* use this conversion function when inserting an item into the memcache
+ */
+ svn_cache__serialize_func_t serializer;
+
+ /* use this conversion function when reading an item from the memcache
+ */
+ svn_cache__deserialize_func_t deserializer;
+
+ /* Prepend this byte sequence to any key passed to us.
+ * This makes (very likely) our keys different from all keys used
+ * by other svn_membuffer_cache_t instances.
+ */
+ entry_key_t prefix;
+
+ /* A copy of the unmodified prefix. It is being used as a user-visible
+ * ID for this cache instance.
+ */
+ const char* full_prefix;
+
+ /* length of the keys that will be passed to us through the
+ * svn_cache_t interface. May be APR_HASH_KEY_STRING.
+ */
+ apr_ssize_t key_len;
+
+ /* Temporary buffer containing the hash key for the current access
+ */
+ entry_key_t combined_key;
+
+ /* a pool for temporary allocations during get() and set()
+ */
+ apr_pool_t *pool;
+
+ /* an internal counter that is used to clear the pool from time to time
+ * but not too frequently.
+ */
+ int alloc_counter;
+
+ /* if enabled, this will serialize the access to this instance.
+ */
+ svn_mutex__t *mutex;
+#ifdef SVN_DEBUG_CACHE_MEMBUFFER
+
+ /* Invariant tag info for all items stored by this cache instance.
+ */
+ char prefix_tail[PREFIX_TAIL_LEN];
+
+#endif
+} svn_membuffer_cache_t;
+
+/* After an estimated ALLOCATIONS_PER_POOL_CLEAR allocations, we should
+ * clear the svn_membuffer_cache_t.pool to keep memory consumption in check.
+ */
+#define ALLOCATIONS_PER_POOL_CLEAR 10
+
+
+/* Basically calculate a hash value for KEY of length KEY_LEN, combine it
+ * with the CACHE->PREFIX and write the result in CACHE->COMBINED_KEY.
+ */
+static void
+combine_key(svn_membuffer_cache_t *cache,
+ const void *key,
+ apr_ssize_t key_len)
+{
+ if (key_len == APR_HASH_KEY_STRING)
+ key_len = strlen((const char *) key);
+
+ if (key_len < 16)
+ {
+ apr_uint32_t data[4] = { 0 };
+ memcpy(data, key, key_len);
+
+ svn__pseudo_md5_15((apr_uint32_t *)cache->combined_key, data);
+ }
+ else if (key_len < 32)
+ {
+ apr_uint32_t data[8] = { 0 };
+ memcpy(data, key, key_len);
+
+ svn__pseudo_md5_31((apr_uint32_t *)cache->combined_key, data);
+ }
+ else if (key_len < 64)
+ {
+ apr_uint32_t data[16] = { 0 };
+ memcpy(data, key, key_len);
+
+ svn__pseudo_md5_63((apr_uint32_t *)cache->combined_key, data);
+ }
+ else
+ {
+ apr_md5((unsigned char*)cache->combined_key, key, key_len);
+ }
+
+ cache->combined_key[0] ^= cache->prefix[0];
+ cache->combined_key[1] ^= cache->prefix[1];
+}
+
+/* Implement svn_cache__vtable_t.get (not thread-safe)
+ */
+static svn_error_t *
+svn_membuffer_cache_get(void **value_p,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ apr_pool_t *result_pool)
+{
+ svn_membuffer_cache_t *cache = cache_void;
+
+ DEBUG_CACHE_MEMBUFFER_INIT_TAG
+
+ /* special case */
+ if (key == NULL)
+ {
+ *value_p = NULL;
+ *found = FALSE;
+
+ return SVN_NO_ERROR;
+ }
+
+ /* construct the full, i.e. globally unique, key by adding
+ * this cache instances' prefix
+ */
+ combine_key(cache, key, cache->key_len);
+
+ /* Look the item up. */
+ SVN_ERR(membuffer_cache_get(cache->membuffer,
+ cache->combined_key,
+ value_p,
+ cache->deserializer,
+ DEBUG_CACHE_MEMBUFFER_TAG
+ result_pool));
+
+ /* return result */
+ *found = *value_p != NULL;
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_cache__vtable_t.set (not thread-safe)
+ */
+static svn_error_t *
+svn_membuffer_cache_set(void *cache_void,
+ const void *key,
+ void *value,
+ apr_pool_t *scratch_pool)
+{
+ svn_membuffer_cache_t *cache = cache_void;
+
+ DEBUG_CACHE_MEMBUFFER_INIT_TAG
+
+ /* special case */
+ if (key == NULL)
+ return SVN_NO_ERROR;
+
+ /* we do some allocations below, so increase the allocation counter
+ * by a slightly larger amount. Free allocated memory every now and then.
+ */
+ cache->alloc_counter += 3;
+ if (cache->alloc_counter > ALLOCATIONS_PER_POOL_CLEAR)
+ {
+ svn_pool_clear(cache->pool);
+ cache->alloc_counter = 0;
+ }
+
+ /* construct the full, i.e. globally unique, key by adding
+ * this cache instances' prefix
+ */
+ combine_key(cache, key, cache->key_len);
+
+ /* (probably) add the item to the cache. But there is no real guarantee
+ * that the item will actually be cached afterwards.
+ */
+ return membuffer_cache_set(cache->membuffer,
+ cache->combined_key,
+ value,
+ cache->serializer,
+ DEBUG_CACHE_MEMBUFFER_TAG
+ cache->pool);
+}
+
+/* Implement svn_cache__vtable_t.iter as "not implemented"
+ */
+static svn_error_t *
+svn_membuffer_cache_iter(svn_boolean_t *completed,
+ void *cache_void,
+ svn_iter_apr_hash_cb_t user_cb,
+ void *user_baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Can't iterate a membuffer-based cache"));
+}
+
+/* Implement svn_cache__vtable_t.get_partial (not thread-safe)
+ */
+static svn_error_t *
+svn_membuffer_cache_get_partial(void **value_p,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ svn_cache__partial_getter_func_t func,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ svn_membuffer_cache_t *cache = cache_void;
+
+ DEBUG_CACHE_MEMBUFFER_INIT_TAG
+
+ if (key == NULL)
+ {
+ *value_p = NULL;
+ *found = FALSE;
+
+ return SVN_NO_ERROR;
+ }
+
+ combine_key(cache, key, cache->key_len);
+ SVN_ERR(membuffer_cache_get_partial(cache->membuffer,
+ cache->combined_key,
+ value_p,
+ found,
+ func,
+ baton,
+ DEBUG_CACHE_MEMBUFFER_TAG
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_cache__vtable_t.set_partial (not thread-safe)
+ */
+static svn_error_t *
+svn_membuffer_cache_set_partial(void *cache_void,
+ const void *key,
+ svn_cache__partial_setter_func_t func,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_membuffer_cache_t *cache = cache_void;
+
+ DEBUG_CACHE_MEMBUFFER_INIT_TAG
+
+ if (key != NULL)
+ {
+ combine_key(cache, key, cache->key_len);
+ SVN_ERR(membuffer_cache_set_partial(cache->membuffer,
+ cache->combined_key,
+ func,
+ baton,
+ DEBUG_CACHE_MEMBUFFER_TAG
+ scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_cache__vtable_t.is_cachable
+ * (thread-safe even without mutex)
+ */
+static svn_boolean_t
+svn_membuffer_cache_is_cachable(void *cache_void, apr_size_t size)
+{
+ /* Don't allow extremely large element sizes. Otherwise, the cache
+ * might by thrashed by a few extremely large entries. And the size
+ * must be small enough to be stored in a 32 bit value.
+ */
+ svn_membuffer_cache_t *cache = cache_void;
+ return size <= cache->membuffer->max_entry_size;
+}
+
+/* Add statistics of SEGMENT to INFO.
+ */
+static svn_error_t *
+svn_membuffer_get_segment_info(svn_membuffer_t *segment,
+ svn_cache__info_t *info)
+{
+ info->data_size += segment->data_size;
+ info->used_size += segment->data_used;
+ info->total_size += segment->data_size +
+ segment->group_count * GROUP_SIZE * sizeof(entry_t);
+
+ info->used_entries += segment->used_entries;
+ info->total_entries += segment->group_count * GROUP_SIZE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_cache__vtable_t.get_info
+ * (thread-safe even without mutex)
+ */
+static svn_error_t *
+svn_membuffer_cache_get_info(void *cache_void,
+ svn_cache__info_t *info,
+ svn_boolean_t reset,
+ apr_pool_t *result_pool)
+{
+ svn_membuffer_cache_t *cache = cache_void;
+ apr_uint32_t i;
+
+ /* cache front-end specific data */
+
+ info->id = apr_pstrdup(result_pool, cache->full_prefix);
+
+ /* collect info from shared cache back-end */
+
+ info->data_size = 0;
+ info->used_size = 0;
+ info->total_size = 0;
+
+ info->used_entries = 0;
+ info->total_entries = 0;
+
+ for (i = 0; i < cache->membuffer->segment_count; ++i)
+ {
+ svn_membuffer_t *segment = cache->membuffer + i;
+ WITH_READ_LOCK(segment,
+ svn_membuffer_get_segment_info(segment, info));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* the v-table for membuffer-based caches (single-threaded access)
+ */
+static svn_cache__vtable_t membuffer_cache_vtable = {
+ svn_membuffer_cache_get,
+ svn_membuffer_cache_set,
+ svn_membuffer_cache_iter,
+ svn_membuffer_cache_is_cachable,
+ svn_membuffer_cache_get_partial,
+ svn_membuffer_cache_set_partial,
+ svn_membuffer_cache_get_info
+};
+
+/* Implement svn_cache__vtable_t.get and serialize all cache access.
+ */
+static svn_error_t *
+svn_membuffer_cache_get_synced(void **value_p,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ apr_pool_t *result_pool)
+{
+ svn_membuffer_cache_t *cache = cache_void;
+ SVN_MUTEX__WITH_LOCK(cache->mutex,
+ svn_membuffer_cache_get(value_p,
+ found,
+ cache_void,
+ key,
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_cache__vtable_t.set and serialize all cache access.
+ */
+static svn_error_t *
+svn_membuffer_cache_set_synced(void *cache_void,
+ const void *key,
+ void *value,
+ apr_pool_t *scratch_pool)
+{
+ svn_membuffer_cache_t *cache = cache_void;
+ SVN_MUTEX__WITH_LOCK(cache->mutex,
+ svn_membuffer_cache_set(cache_void,
+ key,
+ value,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_cache__vtable_t.get_partial and serialize all cache access.
+ */
+static svn_error_t *
+svn_membuffer_cache_get_partial_synced(void **value_p,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ svn_cache__partial_getter_func_t func,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ svn_membuffer_cache_t *cache = cache_void;
+ SVN_MUTEX__WITH_LOCK(cache->mutex,
+ svn_membuffer_cache_get_partial(value_p,
+ found,
+ cache_void,
+ key,
+ func,
+ baton,
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implement svn_cache__vtable_t.set_partial and serialize all cache access.
+ */
+static svn_error_t *
+svn_membuffer_cache_set_partial_synced(void *cache_void,
+ const void *key,
+ svn_cache__partial_setter_func_t func,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_membuffer_cache_t *cache = cache_void;
+ SVN_MUTEX__WITH_LOCK(cache->mutex,
+ svn_membuffer_cache_set_partial(cache_void,
+ key,
+ func,
+ baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* the v-table for membuffer-based caches with multi-threading support)
+ */
+static svn_cache__vtable_t membuffer_cache_synced_vtable = {
+ svn_membuffer_cache_get_synced,
+ svn_membuffer_cache_set_synced,
+ svn_membuffer_cache_iter, /* no sync required */
+ svn_membuffer_cache_is_cachable, /* no sync required */
+ svn_membuffer_cache_get_partial_synced,
+ svn_membuffer_cache_set_partial_synced,
+ svn_membuffer_cache_get_info /* no sync required */
+};
+
+/* standard serialization function for svn_stringbuf_t items.
+ * Implements svn_cache__serialize_func_t.
+ */
+static svn_error_t *
+serialize_svn_stringbuf(void **buffer,
+ apr_size_t *buffer_size,
+ void *item,
+ apr_pool_t *result_pool)
+{
+ svn_stringbuf_t *value_str = item;
+
+ *buffer = value_str->data;
+ *buffer_size = value_str->len + 1;
+
+ return SVN_NO_ERROR;
+}
+
+/* standard de-serialization function for svn_stringbuf_t items.
+ * Implements svn_cache__deserialize_func_t.
+ */
+static svn_error_t *
+deserialize_svn_stringbuf(void **item,
+ void *buffer,
+ apr_size_t buffer_size,
+ apr_pool_t *result_pool)
+{
+ svn_stringbuf_t *value_str = apr_palloc(result_pool, sizeof(svn_stringbuf_t));
+
+ value_str->pool = result_pool;
+ value_str->blocksize = buffer_size;
+ value_str->data = buffer;
+ value_str->len = buffer_size-1;
+ *item = value_str;
+
+ return SVN_NO_ERROR;
+}
+
+/* Construct a svn_cache__t object on top of a shared memcache.
+ */
+svn_error_t *
+svn_cache__create_membuffer_cache(svn_cache__t **cache_p,
+ svn_membuffer_t *membuffer,
+ svn_cache__serialize_func_t serializer,
+ svn_cache__deserialize_func_t deserializer,
+ apr_ssize_t klen,
+ const char *prefix,
+ svn_boolean_t thread_safe,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *checksum;
+
+ /* allocate the cache header structures
+ */
+ svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper));
+ svn_membuffer_cache_t *cache = apr_palloc(pool, sizeof(*cache));
+
+ /* initialize our internal cache header
+ */
+ cache->membuffer = membuffer;
+ cache->serializer = serializer
+ ? serializer
+ : serialize_svn_stringbuf;
+ cache->deserializer = deserializer
+ ? deserializer
+ : deserialize_svn_stringbuf;
+ cache->full_prefix = apr_pstrdup(pool, prefix);
+ cache->key_len = klen;
+ cache->pool = svn_pool_create(pool);
+ cache->alloc_counter = 0;
+
+ SVN_ERR(svn_mutex__init(&cache->mutex, thread_safe, pool));
+
+ /* for performance reasons, we don't actually store the full prefix but a
+ * hash value of it
+ */
+ SVN_ERR(svn_checksum(&checksum,
+ svn_checksum_md5,
+ prefix,
+ strlen(prefix),
+ pool));
+ memcpy(cache->prefix, checksum->digest, sizeof(cache->prefix));
+
+#ifdef SVN_DEBUG_CACHE_MEMBUFFER
+
+ /* Initialize cache debugging support.
+ */
+ get_prefix_tail(prefix, cache->prefix_tail);
+
+#endif
+
+ /* initialize the generic cache wrapper
+ */
+ wrapper->vtable = thread_safe ? &membuffer_cache_synced_vtable
+ : &membuffer_cache_vtable;
+ wrapper->cache_internal = cache;
+ wrapper->error_handler = 0;
+ wrapper->error_baton = 0;
+
+ *cache_p = wrapper;
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_subr/cache-memcache.c b/contrib/subversion/subversion/libsvn_subr/cache-memcache.c
new file mode 100644
index 0000000..5332d04
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/cache-memcache.c
@@ -0,0 +1,583 @@
+/*
+ * cache-memcache.c: memcached caching for Subversion
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_md5.h>
+
+#include "svn_pools.h"
+#include "svn_base64.h"
+#include "svn_path.h"
+
+#include "svn_private_config.h"
+#include "private/svn_cache.h"
+#include "private/svn_dep_compat.h"
+
+#include "cache.h"
+
+#ifdef SVN_HAVE_MEMCACHE
+
+#include <apr_memcache.h>
+
+/* A note on thread safety:
+
+ The apr_memcache_t object does its own mutex handling, and nothing
+ else in memcache_t is ever modified, so this implementation should
+ be fully thread-safe.
+*/
+
+/* The (internal) cache object. */
+typedef struct memcache_t {
+ /* The memcached server set we're using. */
+ apr_memcache_t *memcache;
+
+ /* A prefix used to differentiate our data from any other data in
+ * the memcached (URI-encoded). */
+ const char *prefix;
+
+ /* The size of the key: either a fixed number of bytes or
+ * APR_HASH_KEY_STRING. */
+ apr_ssize_t klen;
+
+
+ /* Used to marshal values in and out of the cache. */
+ svn_cache__serialize_func_t serialize_func;
+ svn_cache__deserialize_func_t deserialize_func;
+} memcache_t;
+
+/* The wrapper around apr_memcache_t. */
+struct svn_memcache_t {
+ apr_memcache_t *c;
+};
+
+
+/* The memcached protocol says the maximum key length is 250. Let's
+ just say 249, to be safe. */
+#define MAX_MEMCACHED_KEY_LEN 249
+#define MEMCACHED_KEY_UNHASHED_LEN (MAX_MEMCACHED_KEY_LEN - \
+ 2 * APR_MD5_DIGESTSIZE)
+
+
+/* Set *MC_KEY to a memcache key for the given key KEY for CACHE, allocated
+ in POOL. */
+static svn_error_t *
+build_key(const char **mc_key,
+ memcache_t *cache,
+ const void *raw_key,
+ apr_pool_t *pool)
+{
+ const char *encoded_suffix;
+ const char *long_key;
+ apr_size_t long_key_len;
+
+ if (cache->klen == APR_HASH_KEY_STRING)
+ encoded_suffix = svn_path_uri_encode(raw_key, pool);
+ else
+ {
+ const svn_string_t *raw = svn_string_ncreate(raw_key, cache->klen, pool);
+ const svn_string_t *encoded = svn_base64_encode_string2(raw, FALSE,
+ pool);
+ encoded_suffix = encoded->data;
+ }
+
+ long_key = apr_pstrcat(pool, "SVN:", cache->prefix, ":", encoded_suffix,
+ (char *)NULL);
+ long_key_len = strlen(long_key);
+
+ /* We don't want to have a key that's too big. If it was going to
+ be too big, we MD5 the entire string, then replace the last bit
+ with the checksum. Note that APR_MD5_DIGESTSIZE is for the pure
+ binary digest; we have to double that when we convert to hex.
+
+ Every key we use will either be at most
+ MEMCACHED_KEY_UNHASHED_LEN bytes long, or be exactly
+ MAX_MEMCACHED_KEY_LEN bytes long. */
+ if (long_key_len > MEMCACHED_KEY_UNHASHED_LEN)
+ {
+ svn_checksum_t *checksum;
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, long_key, long_key_len,
+ pool));
+
+ long_key = apr_pstrcat(pool,
+ apr_pstrmemdup(pool, long_key,
+ MEMCACHED_KEY_UNHASHED_LEN),
+ svn_checksum_to_cstring_display(checksum, pool),
+ (char *)NULL);
+ }
+
+ *mc_key = long_key;
+ return SVN_NO_ERROR;
+}
+
+/* Core functionality of our getter functions: fetch DATA from the memcached
+ * given by CACHE_VOID and identified by KEY. Indicate success in FOUND and
+ * use a tempoary sub-pool of POOL for allocations.
+ */
+static svn_error_t *
+memcache_internal_get(char **data,
+ apr_size_t *size,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ apr_pool_t *pool)
+{
+ memcache_t *cache = cache_void;
+ apr_status_t apr_err;
+ const char *mc_key;
+ apr_pool_t *subpool;
+
+ if (key == NULL)
+ {
+ *found = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ subpool = svn_pool_create(pool);
+ SVN_ERR(build_key(&mc_key, cache, key, subpool));
+
+ apr_err = apr_memcache_getp(cache->memcache,
+ pool,
+ mc_key,
+ data,
+ size,
+ NULL /* ignore flags */);
+ if (apr_err == APR_NOTFOUND)
+ {
+ *found = FALSE;
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+ }
+ else if (apr_err != APR_SUCCESS || !*data)
+ return svn_error_wrap_apr(apr_err,
+ _("Unknown memcached error while reading"));
+
+ *found = TRUE;
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+memcache_get(void **value_p,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ apr_pool_t *result_pool)
+{
+ memcache_t *cache = cache_void;
+ char *data;
+ apr_size_t data_len;
+ SVN_ERR(memcache_internal_get(&data,
+ &data_len,
+ found,
+ cache_void,
+ key,
+ result_pool));
+
+ /* If we found it, de-serialize it. */
+ if (*found)
+ {
+ if (cache->deserialize_func)
+ {
+ SVN_ERR((cache->deserialize_func)(value_p, data, data_len,
+ result_pool));
+ }
+ else
+ {
+ svn_string_t *value = apr_pcalloc(result_pool, sizeof(*value));
+ value->data = data;
+ value->len = data_len;
+ *value_p = value;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Core functionality of our setter functions: store LENGH bytes of DATA
+ * to be identified by KEY in the memcached given by CACHE_VOID. Use POOL
+ * for temporary allocations.
+ */
+static svn_error_t *
+memcache_internal_set(void *cache_void,
+ const void *key,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ memcache_t *cache = cache_void;
+ const char *mc_key;
+ apr_status_t apr_err;
+
+ SVN_ERR(build_key(&mc_key, cache, key, scratch_pool));
+ apr_err = apr_memcache_set(cache->memcache, mc_key, (char *)data, len, 0, 0);
+
+ /* ### Maybe write failures should be ignored (but logged)? */
+ if (apr_err != APR_SUCCESS)
+ return svn_error_wrap_apr(apr_err,
+ _("Unknown memcached error while writing"));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+memcache_set(void *cache_void,
+ const void *key,
+ void *value,
+ apr_pool_t *scratch_pool)
+{
+ memcache_t *cache = cache_void;
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+ void *data;
+ apr_size_t data_len;
+ svn_error_t *err;
+
+ if (key == NULL)
+ return SVN_NO_ERROR;
+
+ if (cache->serialize_func)
+ {
+ SVN_ERR((cache->serialize_func)(&data, &data_len, value, subpool));
+ }
+ else
+ {
+ svn_stringbuf_t *value_str = value;
+ data = value_str->data;
+ data_len = value_str->len;
+ }
+
+ err = memcache_internal_set(cache_void, key, data, data_len, subpool);
+
+ svn_pool_destroy(subpool);
+ return err;
+}
+
+static svn_error_t *
+memcache_get_partial(void **value_p,
+ svn_boolean_t *found,
+ void *cache_void,
+ const void *key,
+ svn_cache__partial_getter_func_t func,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ char *data;
+ apr_size_t size;
+ SVN_ERR(memcache_internal_get(&data,
+ &size,
+ found,
+ cache_void,
+ key,
+ result_pool));
+
+ /* If we found it, de-serialize it. */
+ return *found
+ ? func(value_p, data, size, baton, result_pool)
+ : err;
+}
+
+
+static svn_error_t *
+memcache_set_partial(void *cache_void,
+ const void *key,
+ svn_cache__partial_setter_func_t func,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ void *data;
+ apr_size_t size;
+ svn_boolean_t found = FALSE;
+
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+ SVN_ERR(memcache_internal_get((char **)&data,
+ &size,
+ &found,
+ cache_void,
+ key,
+ subpool));
+
+ /* If we found it, modify it and write it back to cache */
+ if (found)
+ {
+ SVN_ERR(func(&data, &size, baton, subpool));
+ err = memcache_internal_set(cache_void, key, data, size, subpool);
+ }
+
+ svn_pool_destroy(subpool);
+ return err;
+}
+
+
+static svn_error_t *
+memcache_iter(svn_boolean_t *completed,
+ void *cache_void,
+ svn_iter_apr_hash_cb_t user_cb,
+ void *user_baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Can't iterate a memcached cache"));
+}
+
+static svn_boolean_t
+memcache_is_cachable(void *unused, apr_size_t size)
+{
+ (void)unused; /* silence gcc warning. */
+
+ /* The memcached cutoff seems to be a bit (header length?) under a megabyte.
+ * We round down a little to be safe.
+ */
+ return size < 1000000;
+}
+
+static svn_error_t *
+memcache_get_info(void *cache_void,
+ svn_cache__info_t *info,
+ svn_boolean_t reset,
+ apr_pool_t *result_pool)
+{
+ memcache_t *cache = cache_void;
+
+ info->id = apr_pstrdup(result_pool, cache->prefix);
+
+ /* we don't have any memory allocation info */
+
+ info->used_size = 0;
+ info->total_size = 0;
+ info->data_size = 0;
+ info->used_entries = 0;
+ info->total_entries = 0;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_cache__vtable_t memcache_vtable = {
+ memcache_get,
+ memcache_set,
+ memcache_iter,
+ memcache_is_cachable,
+ memcache_get_partial,
+ memcache_set_partial,
+ memcache_get_info
+};
+
+svn_error_t *
+svn_cache__create_memcache(svn_cache__t **cache_p,
+ svn_memcache_t *memcache,
+ svn_cache__serialize_func_t serialize_func,
+ svn_cache__deserialize_func_t deserialize_func,
+ apr_ssize_t klen,
+ const char *prefix,
+ apr_pool_t *pool)
+{
+ svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper));
+ memcache_t *cache = apr_pcalloc(pool, sizeof(*cache));
+
+ cache->serialize_func = serialize_func;
+ cache->deserialize_func = deserialize_func;
+ cache->klen = klen;
+ cache->prefix = svn_path_uri_encode(prefix, pool);
+ cache->memcache = memcache->c;
+
+ wrapper->vtable = &memcache_vtable;
+ wrapper->cache_internal = cache;
+ wrapper->error_handler = 0;
+ wrapper->error_baton = 0;
+
+ *cache_p = wrapper;
+ return SVN_NO_ERROR;
+}
+
+
+/*** Creating apr_memcache_t from svn_config_t. ***/
+
+/* Baton for add_memcache_server. */
+struct ams_baton {
+ apr_memcache_t *memcache;
+ apr_pool_t *memcache_pool;
+ svn_error_t *err;
+};
+
+/* Implements svn_config_enumerator2_t. */
+static svn_boolean_t
+add_memcache_server(const char *name,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct ams_baton *b = baton;
+ char *host, *scope;
+ apr_port_t port;
+ apr_status_t apr_err;
+ apr_memcache_server_t *server;
+
+ apr_err = apr_parse_addr_port(&host, &scope, &port,
+ value, pool);
+ if (apr_err != APR_SUCCESS)
+ {
+ b->err = svn_error_wrap_apr(apr_err,
+ _("Error parsing memcache server '%s'"),
+ name);
+ return FALSE;
+ }
+
+ if (scope)
+ {
+ b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL,
+ _("Scope not allowed in memcache server "
+ "'%s'"),
+ name);
+ return FALSE;
+ }
+ if (!host || !port)
+ {
+ b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL,
+ _("Must specify host and port for memcache "
+ "server '%s'"),
+ name);
+ return FALSE;
+ }
+
+ /* Note: the four numbers here are only relevant when an
+ apr_memcache_t is being shared by multiple threads. */
+ apr_err = apr_memcache_server_create(b->memcache_pool,
+ host,
+ port,
+ 0, /* min connections */
+ 5, /* soft max connections */
+ 10, /* hard max connections */
+ /* time to live (in microseconds) */
+ apr_time_from_sec(50),
+ &server);
+ if (apr_err != APR_SUCCESS)
+ {
+ b->err = svn_error_wrap_apr(apr_err,
+ _("Unknown error creating memcache server"));
+ return FALSE;
+ }
+
+ apr_err = apr_memcache_add_server(b->memcache, server);
+ if (apr_err != APR_SUCCESS)
+ {
+ b->err = svn_error_wrap_apr(apr_err,
+ _("Unknown error adding server to memcache"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#else /* ! SVN_HAVE_MEMCACHE */
+
+/* Stubs for no apr memcache library. */
+
+struct svn_memcache_t {
+ void *unused; /* Let's not have a size-zero struct. */
+};
+
+svn_error_t *
+svn_cache__create_memcache(svn_cache__t **cache_p,
+ svn_memcache_t *memcache,
+ svn_cache__serialize_func_t serialize_func,
+ svn_cache__deserialize_func_t deserialize_func,
+ apr_ssize_t klen,
+ const char *prefix,
+ apr_pool_t *pool)
+{
+ return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL);
+}
+
+#endif /* SVN_HAVE_MEMCACHE */
+
+/* Implements svn_config_enumerator2_t. Just used for the
+ entry-counting return value of svn_config_enumerate2. */
+static svn_boolean_t
+nop_enumerator(const char *name,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ return TRUE;
+}
+
+svn_error_t *
+svn_cache__make_memcache_from_config(svn_memcache_t **memcache_p,
+ svn_config_t *config,
+ apr_pool_t *pool)
+{
+ int server_count;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ server_count =
+ svn_config_enumerate2(config,
+ SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS,
+ nop_enumerator, NULL, subpool);
+
+ if (server_count == 0)
+ {
+ *memcache_p = NULL;
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+ }
+
+ if (server_count > APR_INT16_MAX)
+ return svn_error_create(SVN_ERR_TOO_MANY_MEMCACHED_SERVERS, NULL, NULL);
+
+#ifdef SVN_HAVE_MEMCACHE
+ {
+ struct ams_baton b;
+ svn_memcache_t *memcache = apr_pcalloc(pool, sizeof(*memcache));
+ apr_status_t apr_err = apr_memcache_create(pool,
+ (apr_uint16_t)server_count,
+ 0, /* flags */
+ &(memcache->c));
+ if (apr_err != APR_SUCCESS)
+ return svn_error_wrap_apr(apr_err,
+ _("Unknown error creating apr_memcache_t"));
+
+ b.memcache = memcache->c;
+ b.memcache_pool = pool;
+ b.err = SVN_NO_ERROR;
+ svn_config_enumerate2(config,
+ SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS,
+ add_memcache_server, &b,
+ subpool);
+
+ if (b.err)
+ return b.err;
+
+ *memcache_p = memcache;
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+ }
+#else /* ! SVN_HAVE_MEMCACHE */
+ {
+ return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL);
+ }
+#endif /* SVN_HAVE_MEMCACHE */
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/cache.c b/contrib/subversion/subversion/libsvn_subr/cache.c
new file mode 100644
index 0000000..70e189f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/cache.c
@@ -0,0 +1,265 @@
+/*
+ * cache.c: cache interface for Subversion
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "cache.h"
+
+svn_error_t *
+svn_cache__set_error_handler(svn_cache__t *cache,
+ svn_cache__error_handler_t handler,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ cache->error_handler = handler;
+ cache->error_baton = baton;
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_cache__is_cachable(svn_cache__t *cache,
+ apr_size_t size)
+{
+ /* having no cache means we can't cache anything */
+ if (cache == NULL)
+ return FALSE;
+
+ return cache->vtable->is_cachable(cache->cache_internal, size);
+}
+
+/* Give the error handler callback a chance to replace or ignore the
+ error. */
+static svn_error_t *
+handle_error(svn_cache__t *cache,
+ svn_error_t *err,
+ apr_pool_t *pool)
+{
+ if (err)
+ {
+ cache->failures++;
+ if (cache->error_handler)
+ err = (cache->error_handler)(err, cache->error_baton, pool);
+ }
+
+ return err;
+}
+
+
+svn_error_t *
+svn_cache__get(void **value_p,
+ svn_boolean_t *found,
+ svn_cache__t *cache,
+ const void *key,
+ apr_pool_t *result_pool)
+{
+ svn_error_t *err;
+
+ /* In case any errors happen and are quelched, make sure we start
+ out with FOUND set to false. */
+ *found = FALSE;
+#ifdef SVN_DEBUG
+ if (getenv("SVN_X_DOES_NOT_MARK_THE_SPOT"))
+ return SVN_NO_ERROR;
+#endif
+
+ cache->reads++;
+ err = handle_error(cache,
+ (cache->vtable->get)(value_p,
+ found,
+ cache->cache_internal,
+ key,
+ result_pool),
+ result_pool);
+
+ if (*found)
+ cache->hits++;
+
+ return err;
+}
+
+svn_error_t *
+svn_cache__set(svn_cache__t *cache,
+ const void *key,
+ void *value,
+ apr_pool_t *scratch_pool)
+{
+ cache->writes++;
+ return handle_error(cache,
+ (cache->vtable->set)(cache->cache_internal,
+ key,
+ value,
+ scratch_pool),
+ scratch_pool);
+}
+
+
+svn_error_t *
+svn_cache__iter(svn_boolean_t *completed,
+ svn_cache__t *cache,
+ svn_iter_apr_hash_cb_t user_cb,
+ void *user_baton,
+ apr_pool_t *scratch_pool)
+{
+#ifdef SVN_DEBUG
+ if (getenv("SVN_X_DOES_NOT_MARK_THE_SPOT"))
+ /* Pretend CACHE is empty. */
+ return SVN_NO_ERROR;
+#endif
+
+ return (cache->vtable->iter)(completed,
+ cache->cache_internal,
+ user_cb,
+ user_baton,
+ scratch_pool);
+}
+
+svn_error_t *
+svn_cache__get_partial(void **value,
+ svn_boolean_t *found,
+ svn_cache__t *cache,
+ const void *key,
+ svn_cache__partial_getter_func_t func,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ svn_error_t *err;
+
+ /* In case any errors happen and are quelched, make sure we start
+ out with FOUND set to false. */
+ *found = FALSE;
+#ifdef SVN_DEBUG
+ if (getenv("SVN_X_DOES_NOT_MARK_THE_SPOT"))
+ return SVN_NO_ERROR;
+#endif
+
+ cache->reads++;
+ err = handle_error(cache,
+ (cache->vtable->get_partial)(value,
+ found,
+ cache->cache_internal,
+ key,
+ func,
+ baton,
+ result_pool),
+ result_pool);
+
+ if (*found)
+ cache->hits++;
+
+ return err;
+}
+
+svn_error_t *
+svn_cache__set_partial(svn_cache__t *cache,
+ const void *key,
+ svn_cache__partial_setter_func_t func,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ cache->writes++;
+ return handle_error(cache,
+ (cache->vtable->set_partial)(cache->cache_internal,
+ key,
+ func,
+ baton,
+ scratch_pool),
+ scratch_pool);
+}
+
+svn_error_t *
+svn_cache__get_info(svn_cache__t *cache,
+ svn_cache__info_t *info,
+ svn_boolean_t reset,
+ apr_pool_t *result_pool)
+{
+ /* write general statistics */
+
+ info->gets = cache->reads;
+ info->hits = cache->hits;
+ info->sets = cache->writes;
+ info->failures = cache->failures;
+
+ /* Call the cache implementation for filling the blanks.
+ * It might also replace some of the general stats but
+ * this is currently not done.
+ */
+ SVN_ERR((cache->vtable->get_info)(cache->cache_internal,
+ info,
+ reset,
+ result_pool));
+
+ /* reset statistics */
+
+ if (reset)
+ {
+ cache->reads = 0;
+ cache->hits = 0;
+ cache->writes = 0;
+ cache->failures = 0;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_string_t *
+svn_cache__format_info(const svn_cache__info_t *info,
+ apr_pool_t *result_pool)
+{
+ enum { _1MB = 1024 * 1024 };
+
+ apr_uint64_t misses = info->gets - info->hits;
+ double hit_rate = (100.0 * (double)info->hits)
+ / (double)(info->gets ? info->gets : 1);
+ double write_rate = (100.0 * (double)info->sets)
+ / (double)(misses ? misses : 1);
+ double data_usage_rate = (100.0 * (double)info->used_size)
+ / (double)(info->data_size ? info->data_size : 1);
+ double data_entry_rate = (100.0 * (double)info->used_entries)
+ / (double)(info->total_entries ? info->total_entries : 1);
+
+ return svn_string_createf(result_pool,
+
+ "prefix : %s\n"
+ "gets : %" APR_UINT64_T_FMT
+ ", %" APR_UINT64_T_FMT " hits (%5.2f%%)\n"
+ "sets : %" APR_UINT64_T_FMT
+ " (%5.2f%% of misses)\n"
+ "failures: %" APR_UINT64_T_FMT "\n"
+ "used : %" APR_UINT64_T_FMT " MB (%5.2f%%)"
+ " of %" APR_UINT64_T_FMT " MB data cache"
+ " / %" APR_UINT64_T_FMT " MB total cache memory\n"
+ " %" APR_UINT64_T_FMT " entries (%5.2f%%)"
+ " of %" APR_UINT64_T_FMT " total\n",
+
+ info->id,
+
+ info->gets,
+ info->hits, hit_rate,
+ info->sets, write_rate,
+ info->failures,
+
+ info->used_size / _1MB, data_usage_rate,
+ info->data_size / _1MB,
+ info->total_size / _1MB,
+
+ info->used_entries, data_entry_rate,
+ info->total_entries);
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/cache.h b/contrib/subversion/subversion/libsvn_subr/cache.h
new file mode 100644
index 0000000..5029cef
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/cache.h
@@ -0,0 +1,109 @@
+/*
+ * cache.h: cache vtable interface
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_SUBR_CACHE_H
+#define SVN_LIBSVN_SUBR_CACHE_H
+
+#include "private/svn_cache.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct svn_cache__vtable_t {
+ /* See svn_cache__get(). */
+ svn_error_t *(*get)(void **value,
+ svn_boolean_t *found,
+ void *cache_implementation,
+ const void *key,
+ apr_pool_t *result_pool);
+
+ /* See svn_cache__set(). */
+ svn_error_t *(*set)(void *cache_implementation,
+ const void *key,
+ void *value,
+ apr_pool_t *scratch_pool);
+
+ /* See svn_cache__iter(). */
+ svn_error_t *(*iter)(svn_boolean_t *completed,
+ void *cache_implementation,
+ svn_iter_apr_hash_cb_t func,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+ /* See svn_cache__is_cachable(). */
+ svn_boolean_t (*is_cachable)(void *cache_implementation,
+ apr_size_t size);
+
+ /* See svn_cache__get_partial(). */
+ svn_error_t *(*get_partial)(void **value,
+ svn_boolean_t *found,
+ void *cache_implementation,
+ const void *key,
+ svn_cache__partial_getter_func_t func,
+ void *baton,
+ apr_pool_t *result_pool);
+
+ /* See svn_cache__set_partial(). */
+ svn_error_t *(*set_partial)(void *cache_implementation,
+ const void *key,
+ svn_cache__partial_setter_func_t func,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+ /* See svn_cache__get_info(). */
+ svn_error_t *(*get_info)(void *cache_implementation,
+ svn_cache__info_t *info,
+ svn_boolean_t reset,
+ apr_pool_t *result_pool);
+} svn_cache__vtable_t;
+
+struct svn_cache__t {
+ const svn_cache__vtable_t *vtable;
+
+ /* See svn_cache__set_error_handler(). */
+ svn_cache__error_handler_t error_handler;
+ void *error_baton;
+
+ /* Private data for the cache implementation. */
+ void *cache_internal;
+
+ /* Total number of calls to getters. */
+ apr_uint64_t reads;
+
+ /* Total number of calls to set(). */
+ apr_uint64_t writes;
+
+ /* Total number of getter calls that returned a cached item. */
+ apr_uint64_t hits;
+
+ /* Total number of function calls that returned an error. */
+ apr_uint64_t failures;
+};
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_SUBR_CACHE_H */
diff --git a/contrib/subversion/subversion/libsvn_subr/cache_config.c b/contrib/subversion/subversion/libsvn_subr/cache_config.c
new file mode 100644
index 0000000..86c12dc
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/cache_config.c
@@ -0,0 +1,169 @@
+/* svn_cache_config.c : configuration of internal caches
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_atomic.h>
+
+#include "svn_cache_config.h"
+#include "private/svn_cache.h"
+
+#include "svn_pools.h"
+
+/* The cache settings as a process-wide singleton.
+ */
+static svn_cache_config_t cache_settings =
+ {
+ /* default configuration:
+ *
+ * Please note that the resources listed below will be allocated
+ * PER PROCESS. Thus, the defaults chosen here are kept deliberately
+ * low to still make a difference yet to ensure that pre-fork servers
+ * on machines with small amounts of RAM aren't severely impacted.
+ */
+ 0x1000000, /* 16 MB for caches.
+ * If you are running a single server process,
+ * you may easily increase that to 50+% of your RAM
+ * using svn_fs_set_cache_config().
+ */
+ 16, /* up to 16 files kept open.
+ * Most OS restrict the number of open file handles to
+ * about 1000. To minimize I/O and OS overhead, values
+ * of 500+ can be beneficial (use svn_fs_set_cache_config()
+ * to change the configuration).
+ * When running with a huge in-process cache, this number
+ * has little impact on performance and a more modest
+ * value (< 100) may be more suitable.
+ */
+#if APR_HAS_THREADS
+ FALSE /* assume multi-threaded operation.
+ * Because this simply activates proper synchronization
+ * between threads, it is a safe default.
+ */
+#else
+ TRUE /* single-threaded is the only supported mode of operation */
+#endif
+};
+
+/* Get the current FSFS cache configuration. */
+const svn_cache_config_t *
+svn_cache_config_get(void)
+{
+ return &cache_settings;
+}
+
+/* Access the process-global (singleton) membuffer cache. The first call
+ * will automatically allocate the cache using the current cache config.
+ * NULL will be returned if the desired cache size is 0 or if the cache
+ * could not be created for some reason.
+ */
+svn_membuffer_t *
+svn_cache__get_global_membuffer_cache(void)
+{
+ static svn_membuffer_t * volatile cache = NULL;
+
+ apr_uint64_t cache_size = cache_settings.cache_size;
+ if (!cache && cache_size)
+ {
+ svn_error_t *err;
+
+ svn_membuffer_t *old_cache = NULL;
+ svn_membuffer_t *new_cache = NULL;
+
+ /* auto-allocate cache */
+ apr_allocator_t *allocator = NULL;
+ apr_pool_t *pool = NULL;
+
+ if (apr_allocator_create(&allocator))
+ return NULL;
+
+ /* Ensure that we free partially allocated data if we run OOM
+ * before the cache is complete: If the cache cannot be allocated
+ * in its full size, the create() function will clear the pool
+ * explicitly. The allocator will make sure that any memory no
+ * longer used by the pool will actually be returned to the OS.
+ *
+ * Please note that this pool and allocator is used *only* to
+ * allocate the large membuffer. All later dynamic allocations
+ * come from other, temporary pools and allocators.
+ */
+ apr_allocator_max_free_set(allocator, 1);
+
+ /* don't terminate upon OOM but make pool return a NULL pointer
+ * instead so we can disable caching gracefully and continue
+ * operation without membuffer caches.
+ */
+ apr_pool_create_ex(&pool, NULL, NULL, allocator);
+ if (pool == NULL)
+ return NULL;
+ apr_allocator_owner_set(allocator, pool);
+
+ err = svn_cache__membuffer_cache_create(
+ &new_cache,
+ (apr_size_t)cache_size,
+ (apr_size_t)(cache_size / 10),
+ 0,
+ ! svn_cache_config_get()->single_threaded,
+ FALSE,
+ pool);
+
+ /* Some error occurred. Most likely it's an OOM error but we don't
+ * really care. Simply release all cache memory and disable caching
+ */
+ if (err)
+ {
+ /* Memory and error cleanup */
+ svn_error_clear(err);
+ svn_pool_destroy(pool);
+
+ /* Prevent future attempts to create the cache. However, an
+ * existing cache instance (see next comment) remains valid.
+ */
+ cache_settings.cache_size = 0;
+
+ /* The current caller won't get the cache object.
+ * However, a concurrent call might have succeeded in creating
+ * the cache object. That call and all following ones will then
+ * use the successfully created cache instance.
+ */
+ return NULL;
+ }
+
+ /* Handle race condition: if we are the first to create a
+ * cache object, make it our global singleton. Otherwise,
+ * discard the new cache and keep the existing one.
+ *
+ * Cast is necessary because of APR bug:
+ * https://issues.apache.org/bugzilla/show_bug.cgi?id=50731
+ */
+ old_cache = apr_atomic_casptr((volatile void **)&cache, new_cache, NULL);
+ if (old_cache != NULL)
+ svn_pool_destroy(pool);
+ }
+
+ return cache;
+}
+
+void
+svn_cache_config_set(const svn_cache_config_t *settings)
+{
+ cache_settings = *settings;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_subr/checksum.c b/contrib/subversion/subversion/libsvn_subr/checksum.c
new file mode 100644
index 0000000..e5d6a62
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/checksum.c
@@ -0,0 +1,500 @@
+/*
+ * checksum.c: checksum routines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <ctype.h>
+
+#include <apr_md5.h>
+#include <apr_sha1.h>
+
+#include "svn_checksum.h"
+#include "svn_error.h"
+#include "svn_ctype.h"
+
+#include "sha1.h"
+#include "md5.h"
+
+#include "private/svn_subr_private.h"
+
+#include "svn_private_config.h"
+
+
+
+/* Returns the digest size of it's argument. */
+#define DIGESTSIZE(k) ((k) == svn_checksum_md5 ? APR_MD5_DIGESTSIZE : \
+ (k) == svn_checksum_sha1 ? APR_SHA1_DIGESTSIZE : 0)
+
+
+/* Check to see if KIND is something we recognize. If not, return
+ * SVN_ERR_BAD_CHECKSUM_KIND */
+static svn_error_t *
+validate_kind(svn_checksum_kind_t kind)
+{
+ if (kind == svn_checksum_md5 || kind == svn_checksum_sha1)
+ return SVN_NO_ERROR;
+ else
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
+}
+
+/* Create a svn_checksum_t with everything but the contents of the
+ digest populated. */
+static svn_checksum_t *
+checksum_create_without_digest(svn_checksum_kind_t kind,
+ apr_size_t digest_size,
+ apr_pool_t *pool)
+{
+ /* Use apr_palloc() instead of apr_pcalloc() so that the digest
+ * contents are only set once by the caller. */
+ svn_checksum_t *checksum = apr_palloc(pool, sizeof(*checksum) + digest_size);
+ checksum->digest = (unsigned char *)checksum + sizeof(*checksum);
+ checksum->kind = kind;
+ return checksum;
+}
+
+static svn_checksum_t *
+checksum_create(svn_checksum_kind_t kind,
+ apr_size_t digest_size,
+ const unsigned char *digest,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *checksum = checksum_create_without_digest(kind, digest_size,
+ pool);
+ memcpy((unsigned char *)checksum->digest, digest, digest_size);
+ return checksum;
+}
+
+svn_checksum_t *
+svn_checksum_create(svn_checksum_kind_t kind,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *checksum;
+ apr_size_t digest_size;
+
+ switch (kind)
+ {
+ case svn_checksum_md5:
+ digest_size = APR_MD5_DIGESTSIZE;
+ break;
+ case svn_checksum_sha1:
+ digest_size = APR_SHA1_DIGESTSIZE;
+ break;
+ default:
+ return NULL;
+ }
+
+ checksum = checksum_create_without_digest(kind, digest_size, pool);
+ memset((unsigned char *) checksum->digest, 0, digest_size);
+ return checksum;
+}
+
+svn_checksum_t *
+svn_checksum__from_digest_md5(const unsigned char *digest,
+ apr_pool_t *result_pool)
+{
+ return checksum_create(svn_checksum_md5, APR_MD5_DIGESTSIZE, digest,
+ result_pool);
+}
+
+svn_checksum_t *
+svn_checksum__from_digest_sha1(const unsigned char *digest,
+ apr_pool_t *result_pool)
+{
+ return checksum_create(svn_checksum_sha1, APR_SHA1_DIGESTSIZE, digest,
+ result_pool);
+}
+
+svn_error_t *
+svn_checksum_clear(svn_checksum_t *checksum)
+{
+ SVN_ERR(validate_kind(checksum->kind));
+
+ memset((unsigned char *) checksum->digest, 0, DIGESTSIZE(checksum->kind));
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_checksum_match(const svn_checksum_t *checksum1,
+ const svn_checksum_t *checksum2)
+{
+ if (checksum1 == NULL || checksum2 == NULL)
+ return TRUE;
+
+ if (checksum1->kind != checksum2->kind)
+ return FALSE;
+
+ switch (checksum1->kind)
+ {
+ case svn_checksum_md5:
+ return svn_md5__digests_match(checksum1->digest, checksum2->digest);
+ case svn_checksum_sha1:
+ return svn_sha1__digests_match(checksum1->digest, checksum2->digest);
+ default:
+ /* We really shouldn't get here, but if we do... */
+ return FALSE;
+ }
+}
+
+const char *
+svn_checksum_to_cstring_display(const svn_checksum_t *checksum,
+ apr_pool_t *pool)
+{
+ switch (checksum->kind)
+ {
+ case svn_checksum_md5:
+ return svn_md5__digest_to_cstring_display(checksum->digest, pool);
+ case svn_checksum_sha1:
+ return svn_sha1__digest_to_cstring_display(checksum->digest, pool);
+ default:
+ /* We really shouldn't get here, but if we do... */
+ return NULL;
+ }
+}
+
+const char *
+svn_checksum_to_cstring(const svn_checksum_t *checksum,
+ apr_pool_t *pool)
+{
+ if (checksum == NULL)
+ return NULL;
+
+ switch (checksum->kind)
+ {
+ case svn_checksum_md5:
+ return svn_md5__digest_to_cstring(checksum->digest, pool);
+ case svn_checksum_sha1:
+ return svn_sha1__digest_to_cstring(checksum->digest, pool);
+ default:
+ /* We really shouldn't get here, but if we do... */
+ return NULL;
+ }
+}
+
+
+const char *
+svn_checksum_serialize(const svn_checksum_t *checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *ckind_str;
+
+ SVN_ERR_ASSERT_NO_RETURN(checksum->kind == svn_checksum_md5
+ || checksum->kind == svn_checksum_sha1);
+ ckind_str = (checksum->kind == svn_checksum_md5 ? "$md5 $" : "$sha1$");
+ return apr_pstrcat(result_pool,
+ ckind_str,
+ svn_checksum_to_cstring(checksum, scratch_pool),
+ (char *)NULL);
+}
+
+
+svn_error_t *
+svn_checksum_deserialize(const svn_checksum_t **checksum,
+ const char *data,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_checksum_kind_t ckind;
+ svn_checksum_t *parsed_checksum;
+
+ /* "$md5 $..." or "$sha1$..." */
+ SVN_ERR_ASSERT(strlen(data) > 6);
+
+ ckind = (data[1] == 'm' ? svn_checksum_md5 : svn_checksum_sha1);
+ SVN_ERR(svn_checksum_parse_hex(&parsed_checksum, ckind,
+ data + 6, result_pool));
+ *checksum = parsed_checksum;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_checksum_parse_hex(svn_checksum_t **checksum,
+ svn_checksum_kind_t kind,
+ const char *hex,
+ apr_pool_t *pool)
+{
+ int i, len;
+ char is_nonzero = '\0';
+ char *digest;
+ static const char xdigitval[256] =
+ {
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, /* 0-9 */
+ -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* A-F */
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* a-f */
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ };
+
+ if (hex == NULL)
+ {
+ *checksum = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(validate_kind(kind));
+
+ *checksum = svn_checksum_create(kind, pool);
+ digest = (char *)(*checksum)->digest;
+ len = DIGESTSIZE(kind);
+
+ for (i = 0; i < len; i++)
+ {
+ char x1 = xdigitval[(unsigned char)hex[i * 2]];
+ char x2 = xdigitval[(unsigned char)hex[i * 2 + 1]];
+ if (x1 == (char)-1 || x2 == (char)-1)
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_PARSE, NULL, NULL);
+
+ digest[i] = (char)((x1 << 4) | x2);
+ is_nonzero |= (char)((x1 << 4) | x2);
+ }
+
+ if (!is_nonzero)
+ *checksum = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+svn_checksum_t *
+svn_checksum_dup(const svn_checksum_t *checksum,
+ apr_pool_t *pool)
+{
+ /* The duplicate of a NULL checksum is a NULL... */
+ if (checksum == NULL)
+ return NULL;
+
+ /* Without this check on valid checksum kind a NULL svn_checksum_t
+ * pointer is returned which could cause a core dump at an
+ * indeterminate time in the future because callers are not
+ * expecting a NULL pointer. This commit forces an early abort() so
+ * it's easier to track down where the issue arose. */
+ switch (checksum->kind)
+ {
+ case svn_checksum_md5:
+ return svn_checksum__from_digest_md5(checksum->digest, pool);
+ break;
+ case svn_checksum_sha1:
+ return svn_checksum__from_digest_sha1(checksum->digest, pool);
+ break;
+ default:
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ break;
+ }
+}
+
+svn_error_t *
+svn_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t kind,
+ const void *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ apr_sha1_ctx_t sha1_ctx;
+
+ SVN_ERR(validate_kind(kind));
+ *checksum = svn_checksum_create(kind, pool);
+
+ switch (kind)
+ {
+ case svn_checksum_md5:
+ apr_md5((unsigned char *)(*checksum)->digest, data, len);
+ break;
+
+ case svn_checksum_sha1:
+ apr_sha1_init(&sha1_ctx);
+ apr_sha1_update(&sha1_ctx, data, (unsigned int)len);
+ apr_sha1_final((unsigned char *)(*checksum)->digest, &sha1_ctx);
+ break;
+
+ default:
+ /* We really shouldn't get here, but if we do... */
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_checksum_t *
+svn_checksum_empty_checksum(svn_checksum_kind_t kind,
+ apr_pool_t *pool)
+{
+ switch (kind)
+ {
+ case svn_checksum_md5:
+ return svn_checksum__from_digest_md5(svn_md5__empty_string_digest(),
+ pool);
+
+ case svn_checksum_sha1:
+ return svn_checksum__from_digest_sha1(svn_sha1__empty_string_digest(),
+ pool);
+
+ default:
+ /* We really shouldn't get here, but if we do... */
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ }
+}
+
+struct svn_checksum_ctx_t
+{
+ void *apr_ctx;
+ svn_checksum_kind_t kind;
+};
+
+svn_checksum_ctx_t *
+svn_checksum_ctx_create(svn_checksum_kind_t kind,
+ apr_pool_t *pool)
+{
+ svn_checksum_ctx_t *ctx = apr_palloc(pool, sizeof(*ctx));
+
+ ctx->kind = kind;
+ switch (kind)
+ {
+ case svn_checksum_md5:
+ ctx->apr_ctx = apr_palloc(pool, sizeof(apr_md5_ctx_t));
+ apr_md5_init(ctx->apr_ctx);
+ break;
+
+ case svn_checksum_sha1:
+ ctx->apr_ctx = apr_palloc(pool, sizeof(apr_sha1_ctx_t));
+ apr_sha1_init(ctx->apr_ctx);
+ break;
+
+ default:
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ }
+
+ return ctx;
+}
+
+svn_error_t *
+svn_checksum_update(svn_checksum_ctx_t *ctx,
+ const void *data,
+ apr_size_t len)
+{
+ switch (ctx->kind)
+ {
+ case svn_checksum_md5:
+ apr_md5_update(ctx->apr_ctx, data, len);
+ break;
+
+ case svn_checksum_sha1:
+ apr_sha1_update(ctx->apr_ctx, data, (unsigned int)len);
+ break;
+
+ default:
+ /* We really shouldn't get here, but if we do... */
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_checksum_final(svn_checksum_t **checksum,
+ const svn_checksum_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ *checksum = svn_checksum_create(ctx->kind, pool);
+
+ switch (ctx->kind)
+ {
+ case svn_checksum_md5:
+ apr_md5_final((unsigned char *)(*checksum)->digest, ctx->apr_ctx);
+ break;
+
+ case svn_checksum_sha1:
+ apr_sha1_final((unsigned char *)(*checksum)->digest, ctx->apr_ctx);
+ break;
+
+ default:
+ /* We really shouldn't get here, but if we do... */
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+apr_size_t
+svn_checksum_size(const svn_checksum_t *checksum)
+{
+ return DIGESTSIZE(checksum->kind);
+}
+
+svn_error_t *
+svn_checksum_mismatch_err(const svn_checksum_t *expected,
+ const svn_checksum_t *actual,
+ apr_pool_t *scratch_pool,
+ const char *fmt,
+ ...)
+{
+ va_list ap;
+ const char *desc;
+
+ va_start(ap, fmt);
+ desc = apr_pvsprintf(scratch_pool, fmt, ap);
+ va_end(ap);
+
+ return svn_error_createf(SVN_ERR_CHECKSUM_MISMATCH, NULL,
+ _("%s:\n"
+ " expected: %s\n"
+ " actual: %s\n"),
+ desc,
+ svn_checksum_to_cstring_display(expected, scratch_pool),
+ svn_checksum_to_cstring_display(actual, scratch_pool));
+}
+
+svn_boolean_t
+svn_checksum_is_empty_checksum(svn_checksum_t *checksum)
+{
+ /* By definition, the NULL checksum matches all others, including the
+ empty one. */
+ if (!checksum)
+ return TRUE;
+
+ switch (checksum->kind)
+ {
+ case svn_checksum_md5:
+ return svn_md5__digests_match(checksum->digest,
+ svn_md5__empty_string_digest());
+
+ case svn_checksum_sha1:
+ return svn_sha1__digests_match(checksum->digest,
+ svn_sha1__empty_string_digest());
+
+ default:
+ /* We really shouldn't get here, but if we do... */
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ }
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/cmdline.c b/contrib/subversion/subversion/libsvn_subr/cmdline.c
new file mode 100644
index 0000000..8484c96
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/cmdline.c
@@ -0,0 +1,1312 @@
+/*
+ * cmdline.c : Helpers for command-line programs.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <stdlib.h> /* for atexit() */
+#include <stdio.h> /* for setvbuf() */
+#include <locale.h> /* for setlocale() */
+
+#ifndef WIN32
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#else
+#include <crtdbg.h>
+#include <io.h>
+#endif
+
+#include <apr.h> /* for STDIN_FILENO */
+#include <apr_errno.h> /* for apr_strerror */
+#include <apr_general.h> /* for apr_initialize/apr_terminate */
+#include <apr_strings.h> /* for apr_snprintf */
+#include <apr_pools.h>
+
+#include "svn_cmdline.h"
+#include "svn_ctype.h"
+#include "svn_dso.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_nls.h"
+#include "svn_utf.h"
+#include "svn_auth.h"
+#include "svn_xml.h"
+#include "svn_base64.h"
+#include "svn_config.h"
+#include "svn_sorts.h"
+#include "svn_props.h"
+#include "svn_subst.h"
+
+#include "private/svn_cmdline_private.h"
+#include "private/svn_utf_private.h"
+#include "private/svn_string_private.h"
+
+#include "svn_private_config.h"
+
+#include "win32_crashrpt.h"
+
+/* The stdin encoding. If null, it's the same as the native encoding. */
+static const char *input_encoding = NULL;
+
+/* The stdout encoding. If null, it's the same as the native encoding. */
+static const char *output_encoding = NULL;
+
+
+int
+svn_cmdline_init(const char *progname, FILE *error_stream)
+{
+ apr_status_t status;
+ apr_pool_t *pool;
+ svn_error_t *err;
+ char prefix_buf[64]; /* 64 is probably bigger than most program names */
+
+#ifndef WIN32
+ {
+ struct stat st;
+
+ /* The following makes sure that file descriptors 0 (stdin), 1
+ (stdout) and 2 (stderr) will not be "reused", because if
+ e.g. file descriptor 2 would be reused when opening a file, a
+ write to stderr would write to that file and most likely
+ corrupt it. */
+ if ((fstat(0, &st) == -1 && open("/dev/null", O_RDONLY) == -1) ||
+ (fstat(1, &st) == -1 && open("/dev/null", O_WRONLY) == -1) ||
+ (fstat(2, &st) == -1 && open("/dev/null", O_WRONLY) == -1))
+ {
+ if (error_stream)
+ fprintf(error_stream, "%s: error: cannot open '/dev/null'\n",
+ progname);
+ return EXIT_FAILURE;
+ }
+ }
+#endif
+
+ /* Ignore any errors encountered while attempting to change stream
+ buffering, as the streams should retain their default buffering
+ modes. */
+ if (error_stream)
+ setvbuf(error_stream, NULL, _IONBF, 0);
+#ifndef WIN32
+ setvbuf(stdout, NULL, _IOLBF, 0);
+#endif
+
+#ifdef WIN32
+#if _MSC_VER < 1400
+ /* Initialize the input and output encodings. */
+ {
+ static char input_encoding_buffer[16];
+ static char output_encoding_buffer[16];
+
+ apr_snprintf(input_encoding_buffer, sizeof input_encoding_buffer,
+ "CP%u", (unsigned) GetConsoleCP());
+ input_encoding = input_encoding_buffer;
+
+ apr_snprintf(output_encoding_buffer, sizeof output_encoding_buffer,
+ "CP%u", (unsigned) GetConsoleOutputCP());
+ output_encoding = output_encoding_buffer;
+ }
+#endif /* _MSC_VER < 1400 */
+
+#ifdef SVN_USE_WIN32_CRASHHANDLER
+ /* Attach (but don't load) the crash handler */
+ SetUnhandledExceptionFilter(svn__unhandled_exception_filter);
+
+#if _MSC_VER >= 1400
+ /* ### This should work for VC++ 2002 (=1300) and later */
+ /* Show the abort message on STDERR instead of a dialog to allow
+ scripts (e.g. our testsuite) to continue after an abort without
+ user intervention. Allow overriding for easier debugging. */
+ if (!getenv("SVN_CMDLINE_USE_DIALOG_FOR_ABORT"))
+ {
+ /* In release mode: Redirect abort() errors to stderr */
+ _set_error_mode(_OUT_TO_STDERR);
+
+ /* In _DEBUG mode: Redirect all debug output (E.g. assert() to stderr.
+ (Ignored in release builds) */
+ _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR);
+ _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR);
+ _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR);
+ _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+ _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+ _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+ }
+#endif /* _MSC_VER >= 1400 */
+
+#endif /* SVN_USE_WIN32_CRASHHANDLER */
+
+#endif /* WIN32 */
+
+ /* C programs default to the "C" locale. But because svn is supposed
+ to be i18n-aware, it should inherit the default locale of its
+ environment. */
+ if (!setlocale(LC_ALL, "")
+ && !setlocale(LC_CTYPE, ""))
+ {
+ if (error_stream)
+ {
+ const char *env_vars[] = { "LC_ALL", "LC_CTYPE", "LANG", NULL };
+ const char **env_var = &env_vars[0], *env_val = NULL;
+ while (*env_var)
+ {
+ env_val = getenv(*env_var);
+ if (env_val && env_val[0])
+ break;
+ ++env_var;
+ }
+
+ if (!*env_var)
+ {
+ /* Unlikely. Can setlocale fail if no env vars are set? */
+ --env_var;
+ env_val = "not set";
+ }
+
+ fprintf(error_stream,
+ "%s: warning: cannot set LC_CTYPE locale\n"
+ "%s: warning: environment variable %s is %s\n"
+ "%s: warning: please check that your locale name is correct\n",
+ progname, progname, *env_var, env_val, progname);
+ }
+ }
+
+ /* Initialize the APR subsystem, and register an atexit() function
+ to Uninitialize that subsystem at program exit. */
+ status = apr_initialize();
+ if (status)
+ {
+ if (error_stream)
+ {
+ char buf[1024];
+ apr_strerror(status, buf, sizeof(buf) - 1);
+ fprintf(error_stream,
+ "%s: error: cannot initialize APR: %s\n",
+ progname, buf);
+ }
+ return EXIT_FAILURE;
+ }
+
+ strncpy(prefix_buf, progname, sizeof(prefix_buf) - 3);
+ prefix_buf[sizeof(prefix_buf) - 3] = '\0';
+ strcat(prefix_buf, ": ");
+
+ /* DSO pool must be created before any other pools used by the
+ application so that pool cleanup doesn't unload DSOs too
+ early. See docstring of svn_dso_initialize2(). */
+ if ((err = svn_dso_initialize2()))
+ {
+ if (error_stream)
+ svn_handle_error2(err, error_stream, TRUE, prefix_buf);
+
+ svn_error_clear(err);
+ return EXIT_FAILURE;
+ }
+
+ if (0 > atexit(apr_terminate))
+ {
+ if (error_stream)
+ fprintf(error_stream,
+ "%s: error: atexit registration failed\n",
+ progname);
+ return EXIT_FAILURE;
+ }
+
+ /* Create a pool for use by the UTF-8 routines. It will be cleaned
+ up by APR at exit time. */
+ pool = svn_pool_create(NULL);
+ svn_utf_initialize2(FALSE, pool);
+
+ if ((err = svn_nls_init()))
+ {
+ if (error_stream)
+ svn_handle_error2(err, error_stream, TRUE, prefix_buf);
+
+ svn_error_clear(err);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+
+svn_error_t *
+svn_cmdline_cstring_from_utf8(const char **dest,
+ const char *src,
+ apr_pool_t *pool)
+{
+ if (output_encoding == NULL)
+ return svn_utf_cstring_from_utf8(dest, src, pool);
+ else
+ return svn_utf_cstring_from_utf8_ex2(dest, src, output_encoding, pool);
+}
+
+
+const char *
+svn_cmdline_cstring_from_utf8_fuzzy(const char *src,
+ apr_pool_t *pool)
+{
+ return svn_utf__cstring_from_utf8_fuzzy(src, pool,
+ svn_cmdline_cstring_from_utf8);
+}
+
+
+svn_error_t *
+svn_cmdline_cstring_to_utf8(const char **dest,
+ const char *src,
+ apr_pool_t *pool)
+{
+ if (input_encoding == NULL)
+ return svn_utf_cstring_to_utf8(dest, src, pool);
+ else
+ return svn_utf_cstring_to_utf8_ex2(dest, src, input_encoding, pool);
+}
+
+
+svn_error_t *
+svn_cmdline_path_local_style_from_utf8(const char **dest,
+ const char *src,
+ apr_pool_t *pool)
+{
+ return svn_cmdline_cstring_from_utf8(dest,
+ svn_dirent_local_style(src, pool),
+ pool);
+}
+
+svn_error_t *
+svn_cmdline_printf(apr_pool_t *pool, const char *fmt, ...)
+{
+ const char *message;
+ va_list ap;
+
+ /* A note about encoding issues:
+ * APR uses the execution character set, but here we give it UTF-8 strings,
+ * both the fmt argument and any other string arguments. Since apr_pvsprintf
+ * only cares about and produces ASCII characters, this works under the
+ * assumption that all supported platforms use an execution character set
+ * with ASCII as a subset.
+ */
+
+ va_start(ap, fmt);
+ message = apr_pvsprintf(pool, fmt, ap);
+ va_end(ap);
+
+ return svn_cmdline_fputs(message, stdout, pool);
+}
+
+svn_error_t *
+svn_cmdline_fprintf(FILE *stream, apr_pool_t *pool, const char *fmt, ...)
+{
+ const char *message;
+ va_list ap;
+
+ /* See svn_cmdline_printf () for a note about character encoding issues. */
+
+ va_start(ap, fmt);
+ message = apr_pvsprintf(pool, fmt, ap);
+ va_end(ap);
+
+ return svn_cmdline_fputs(message, stream, pool);
+}
+
+svn_error_t *
+svn_cmdline_fputs(const char *string, FILE* stream, apr_pool_t *pool)
+{
+ svn_error_t *err;
+ const char *out;
+
+ err = svn_cmdline_cstring_from_utf8(&out, string, pool);
+
+ if (err)
+ {
+ svn_error_clear(err);
+ out = svn_cmdline_cstring_from_utf8_fuzzy(string, pool);
+ }
+
+ /* On POSIX systems, errno will be set on an error in fputs, but this might
+ not be the case on other platforms. We reset errno and only
+ use it if it was set by the below fputs call. Else, we just return
+ a generic error. */
+ errno = 0;
+
+ if (fputs(out, stream) == EOF)
+ {
+ if (apr_get_os_error()) /* is errno on POSIX */
+ {
+ /* ### Issue #3014: Return a specific error for broken pipes,
+ * ### with a single element in the error chain. */
+ if (APR_STATUS_IS_EPIPE(apr_get_os_error()))
+ return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
+ else
+ return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
+ }
+ else
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cmdline_fflush(FILE *stream)
+{
+ /* See comment in svn_cmdline_fputs about use of errno and stdio. */
+ errno = 0;
+ if (fflush(stream) == EOF)
+ {
+ if (apr_get_os_error()) /* is errno on POSIX */
+ {
+ /* ### Issue #3014: Return a specific error for broken pipes,
+ * ### with a single element in the error chain. */
+ if (APR_STATUS_IS_EPIPE(apr_get_os_error()))
+ return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
+ else
+ return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
+ }
+ else
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+const char *svn_cmdline_output_encoding(apr_pool_t *pool)
+{
+ if (output_encoding)
+ return apr_pstrdup(pool, output_encoding);
+ else
+ return SVN_APR_LOCALE_CHARSET;
+}
+
+int
+svn_cmdline_handle_exit_error(svn_error_t *err,
+ apr_pool_t *pool,
+ const char *prefix)
+{
+ /* Issue #3014:
+ * Don't print anything on broken pipes. The pipe was likely
+ * closed by the process at the other end. We expect that
+ * process to perform error reporting as necessary.
+ *
+ * ### This assumes that there is only one error in a chain for
+ * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
+ if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
+ svn_handle_error2(err, stderr, FALSE, prefix);
+ svn_error_clear(err);
+ if (pool)
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+}
+
+/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'.
+
+ Don't actually prompt. Instead, set *CRED_P to valid credentials
+ iff FAILURES is empty or is exactly SVN_AUTH_SSL_UNKNOWNCA. If
+ there are any other failure bits, then set *CRED_P to null (that
+ is, reject the cert).
+
+ Ignore MAY_SAVE; we don't save certs we never prompted for.
+
+ Ignore BATON, REALM, and CERT_INFO,
+
+ Ignore any further films by George Lucas. */
+static svn_error_t *
+ssl_trust_unknown_server_cert
+ (svn_auth_cred_ssl_server_trust_t **cred_p,
+ void *baton,
+ const char *realm,
+ apr_uint32_t failures,
+ const svn_auth_ssl_server_cert_info_t *cert_info,
+ svn_boolean_t may_save,
+ apr_pool_t *pool)
+{
+ *cred_p = NULL;
+
+ if (failures == 0 || failures == SVN_AUTH_SSL_UNKNOWNCA)
+ {
+ *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
+ (*cred_p)->may_save = FALSE;
+ (*cred_p)->accepted_failures = failures;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cmdline_create_auth_baton(svn_auth_baton_t **ab,
+ svn_boolean_t non_interactive,
+ const char *auth_username,
+ const char *auth_password,
+ const char *config_dir,
+ svn_boolean_t no_auth_cache,
+ svn_boolean_t trust_server_cert,
+ svn_config_t *cfg,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_boolean_t store_password_val = TRUE;
+ svn_boolean_t store_auth_creds_val = TRUE;
+ svn_auth_provider_object_t *provider;
+ svn_cmdline_prompt_baton2_t *pb = NULL;
+
+ /* The whole list of registered providers */
+ apr_array_header_t *providers;
+
+ /* Populate the registered providers with the platform-specific providers */
+ SVN_ERR(svn_auth_get_platform_specific_client_providers(&providers,
+ cfg, pool));
+
+ /* If we have a cancellation function, cram it and the stuff it
+ needs into the prompt baton. */
+ if (cancel_func)
+ {
+ pb = apr_palloc(pool, sizeof(*pb));
+ pb->cancel_func = cancel_func;
+ pb->cancel_baton = cancel_baton;
+ pb->config_dir = config_dir;
+ }
+
+ if (!non_interactive)
+ {
+ /* This provider doesn't prompt the user in order to get creds;
+ it prompts the user regarding the caching of creds. */
+ svn_auth_get_simple_provider2(&provider,
+ svn_cmdline_auth_plaintext_prompt,
+ pb, pool);
+ }
+ else
+ {
+ svn_auth_get_simple_provider2(&provider, NULL, NULL, pool);
+ }
+
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+ svn_auth_get_username_provider(&provider, pool);
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+
+ /* The server-cert, client-cert, and client-cert-password providers. */
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
+ "windows",
+ "ssl_server_trust",
+ pool));
+
+ if (provider)
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+
+ svn_auth_get_ssl_server_trust_file_provider(&provider, pool);
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+ svn_auth_get_ssl_client_cert_file_provider(&provider, pool);
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+
+ if (!non_interactive)
+ {
+ /* This provider doesn't prompt the user in order to get creds;
+ it prompts the user regarding the caching of creds. */
+ svn_auth_get_ssl_client_cert_pw_file_provider2
+ (&provider, svn_cmdline_auth_plaintext_passphrase_prompt,
+ pb, pool);
+ }
+ else
+ {
+ svn_auth_get_ssl_client_cert_pw_file_provider2(&provider, NULL, NULL,
+ pool);
+ }
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+
+ if (!non_interactive)
+ {
+ svn_boolean_t ssl_client_cert_file_prompt;
+
+ SVN_ERR(svn_config_get_bool(cfg, &ssl_client_cert_file_prompt,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT,
+ FALSE));
+
+ /* Two basic prompt providers: username/password, and just username. */
+ svn_auth_get_simple_prompt_provider(&provider,
+ svn_cmdline_auth_simple_prompt,
+ pb,
+ 2, /* retry limit */
+ pool);
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+
+ svn_auth_get_username_prompt_provider
+ (&provider, svn_cmdline_auth_username_prompt, pb,
+ 2, /* retry limit */ pool);
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+
+ /* SSL prompt providers: server-certs and client-cert-passphrases. */
+ svn_auth_get_ssl_server_trust_prompt_provider
+ (&provider, svn_cmdline_auth_ssl_server_trust_prompt, pb, pool);
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+
+ svn_auth_get_ssl_client_cert_pw_prompt_provider
+ (&provider, svn_cmdline_auth_ssl_client_cert_pw_prompt, pb, 2, pool);
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+
+ /* If configuration allows, add a provider for client-cert path
+ prompting, too. */
+ if (ssl_client_cert_file_prompt)
+ {
+ svn_auth_get_ssl_client_cert_prompt_provider
+ (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool);
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+ }
+ }
+ else if (trust_server_cert)
+ {
+ /* Remember, only register this provider if non_interactive. */
+ svn_auth_get_ssl_server_trust_prompt_provider
+ (&provider, ssl_trust_unknown_server_cert, NULL, pool);
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+ }
+
+ /* Build an authentication baton to give to libsvn_client. */
+ svn_auth_open(ab, providers, pool);
+
+ /* Place any default --username or --password credentials into the
+ auth_baton's run-time parameter hash. */
+ if (auth_username)
+ svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_USERNAME,
+ auth_username);
+ if (auth_password)
+ svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DEFAULT_PASSWORD,
+ auth_password);
+
+ /* Same with the --non-interactive option. */
+ if (non_interactive)
+ svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NON_INTERACTIVE, "");
+
+ if (config_dir)
+ svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_CONFIG_DIR,
+ config_dir);
+
+ /* Determine whether storing passwords in any form is allowed.
+ * This is the deprecated location for this option, the new
+ * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
+ * override the value we set here. */
+ SVN_ERR(svn_config_get_bool(cfg, &store_password_val,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_STORE_PASSWORDS,
+ SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS));
+
+ if (! store_password_val)
+ svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, "");
+
+ /* Determine whether we are allowed to write to the auth/ area.
+ * This is the deprecated location for this option, the new
+ * location is SVN_CONFIG_CATEGORY_SERVERS. The RA layer may
+ * override the value we set here. */
+ SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds_val,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
+ SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS));
+
+ if (no_auth_cache || ! store_auth_creds_val)
+ svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_NO_AUTH_CACHE, "");
+
+#ifdef SVN_HAVE_GNOME_KEYRING
+ svn_auth_set_parameter(*ab, SVN_AUTH_PARAM_GNOME_KEYRING_UNLOCK_PROMPT_FUNC,
+ &svn_cmdline__auth_gnome_keyring_unlock_prompt);
+#endif /* SVN_HAVE_GNOME_KEYRING */
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cmdline__getopt_init(apr_getopt_t **os,
+ int argc,
+ const char *argv[],
+ apr_pool_t *pool)
+{
+ apr_status_t apr_err = apr_getopt_init(os, pool, argc, argv);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Error initializing command line arguments"));
+ return SVN_NO_ERROR;
+}
+
+
+void
+svn_cmdline__print_xml_prop(svn_stringbuf_t **outstr,
+ const char* propname,
+ svn_string_t *propval,
+ svn_boolean_t inherited_prop,
+ apr_pool_t *pool)
+{
+ const char *xml_safe;
+ const char *encoding = NULL;
+
+ if (*outstr == NULL)
+ *outstr = svn_stringbuf_create_empty(pool);
+
+ if (svn_xml_is_xml_safe(propval->data, propval->len))
+ {
+ svn_stringbuf_t *xml_esc = NULL;
+ svn_xml_escape_cdata_string(&xml_esc, propval, pool);
+ xml_safe = xml_esc->data;
+ }
+ else
+ {
+ const svn_string_t *base64ed = svn_base64_encode_string2(propval, TRUE,
+ pool);
+ encoding = "base64";
+ xml_safe = base64ed->data;
+ }
+
+ if (encoding)
+ svn_xml_make_open_tag(
+ outstr, pool, svn_xml_protect_pcdata,
+ inherited_prop ? "inherited_property" : "property",
+ "name", propname,
+ "encoding", encoding, NULL);
+ else
+ svn_xml_make_open_tag(
+ outstr, pool, svn_xml_protect_pcdata,
+ inherited_prop ? "inherited_property" : "property",
+ "name", propname, NULL);
+
+ svn_stringbuf_appendcstr(*outstr, xml_safe);
+
+ svn_xml_make_close_tag(
+ outstr, pool,
+ inherited_prop ? "inherited_property" : "property");
+
+ return;
+}
+
+svn_error_t *
+svn_cmdline__parse_config_option(apr_array_header_t *config_options,
+ const char *opt_arg,
+ apr_pool_t *pool)
+{
+ svn_cmdline__config_argument_t *config_option;
+ const char *first_colon, *second_colon, *equals_sign;
+ apr_size_t len = strlen(opt_arg);
+ if ((first_colon = strchr(opt_arg, ':')) && (first_colon != opt_arg))
+ {
+ if ((second_colon = strchr(first_colon + 1, ':')) &&
+ (second_colon != first_colon + 1))
+ {
+ if ((equals_sign = strchr(second_colon + 1, '=')) &&
+ (equals_sign != second_colon + 1))
+ {
+ config_option = apr_pcalloc(pool, sizeof(*config_option));
+ config_option->file = apr_pstrndup(pool, opt_arg,
+ first_colon - opt_arg);
+ config_option->section = apr_pstrndup(pool, first_colon + 1,
+ second_colon - first_colon - 1);
+ config_option->option = apr_pstrndup(pool, second_colon + 1,
+ equals_sign - second_colon - 1);
+
+ if (! (strchr(config_option->option, ':')))
+ {
+ config_option->value = apr_pstrndup(pool, equals_sign + 1,
+ opt_arg + len - equals_sign - 1);
+ APR_ARRAY_PUSH(config_options, svn_cmdline__config_argument_t *)
+ = config_option;
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+ }
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid syntax of argument of --config-option"));
+}
+
+svn_error_t *
+svn_cmdline__apply_config_options(apr_hash_t *config,
+ const apr_array_header_t *config_options,
+ const char *prefix,
+ const char *argument_name)
+{
+ int i;
+
+ for (i = 0; i < config_options->nelts; i++)
+ {
+ svn_config_t *cfg;
+ svn_cmdline__config_argument_t *arg =
+ APR_ARRAY_IDX(config_options, i,
+ svn_cmdline__config_argument_t *);
+
+ cfg = svn_hash_gets(config, arg->file);
+
+ if (cfg)
+ {
+ svn_config_set(cfg, arg->section, arg->option, arg->value);
+ }
+ else
+ {
+ svn_error_t *err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Unrecognized file in argument of %s"), argument_name);
+
+ svn_handle_warning2(stderr, err, prefix);
+ svn_error_clear(err);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return a copy, allocated in POOL, of the next line of text from *STR
+ * up to and including a CR and/or an LF. Change *STR to point to the
+ * remainder of the string after the returned part. If there are no
+ * characters to be returned, return NULL; never return an empty string.
+ */
+static const char *
+next_line(const char **str, apr_pool_t *pool)
+{
+ const char *start = *str;
+ const char *p = *str;
+
+ /* n.b. Throughout this fn, we never read any character after a '\0'. */
+ /* Skip over all non-EOL characters, if any. */
+ while (*p != '\r' && *p != '\n' && *p != '\0')
+ p++;
+ /* Skip over \r\n or \n\r or \r or \n, if any. */
+ if (*p == '\r' || *p == '\n')
+ {
+ char c = *p++;
+
+ if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r'))
+ p++;
+ }
+
+ /* Now p points after at most one '\n' and/or '\r'. */
+ *str = p;
+
+ if (p == start)
+ return NULL;
+
+ return svn_string_ncreate(start, p - start, pool)->data;
+}
+
+const char *
+svn_cmdline__indent_string(const char *str,
+ const char *indent,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *out = svn_stringbuf_create_empty(pool);
+ const char *line;
+
+ while ((line = next_line(&str, pool)))
+ {
+ svn_stringbuf_appendcstr(out, indent);
+ svn_stringbuf_appendcstr(out, line);
+ }
+ return out->data;
+}
+
+svn_error_t *
+svn_cmdline__print_prop_hash(svn_stream_t *out,
+ apr_hash_t *prop_hash,
+ svn_boolean_t names_only,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *sorted_props;
+ int i;
+
+ sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
+ pool);
+ for (i = 0; i < sorted_props->nelts; i++)
+ {
+ svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
+ const char *pname = item.key;
+ svn_string_t *propval = item.value;
+ const char *pname_stdout;
+
+ if (svn_prop_needs_translation(pname))
+ SVN_ERR(svn_subst_detranslate_string(&propval, propval,
+ TRUE, pool));
+
+ SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool));
+
+ if (out)
+ {
+ pname_stdout = apr_psprintf(pool, " %s\n", pname_stdout);
+ SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout,
+ APR_EOL_STR, /* 'native' eol */
+ FALSE, /* no repair */
+ NULL, /* no keywords */
+ FALSE, /* no expansion */
+ pool));
+
+ SVN_ERR(svn_stream_puts(out, pname_stdout));
+ }
+ else
+ {
+ /* ### We leave these printfs for now, since if propval wasn't
+ translated above, we don't know anything about its encoding.
+ In fact, it might be binary data... */
+ printf(" %s\n", pname_stdout);
+ }
+
+ if (!names_only)
+ {
+ /* Add an extra newline to the value before indenting, so that
+ * every line of output has the indentation whether the value
+ * already ended in a newline or not. */
+ const char *newval = apr_psprintf(pool, "%s\n", propval->data);
+ const char *indented_newval = svn_cmdline__indent_string(newval,
+ " ",
+ pool);
+ if (out)
+ {
+ SVN_ERR(svn_stream_puts(out, indented_newval));
+ }
+ else
+ {
+ printf("%s", indented_newval);
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cmdline__print_xml_prop_hash(svn_stringbuf_t **outstr,
+ apr_hash_t *prop_hash,
+ svn_boolean_t names_only,
+ svn_boolean_t inherited_props,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *sorted_props;
+ int i;
+
+ if (*outstr == NULL)
+ *outstr = svn_stringbuf_create_empty(pool);
+
+ sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
+ pool);
+ for (i = 0; i < sorted_props->nelts; i++)
+ {
+ svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
+ const char *pname = item.key;
+ svn_string_t *propval = item.value;
+
+ if (names_only)
+ {
+ svn_xml_make_open_tag(
+ outstr, pool, svn_xml_self_closing,
+ inherited_props ? "inherited_property" : "property",
+ "name", pname, NULL);
+ }
+ else
+ {
+ const char *pname_out;
+
+ if (svn_prop_needs_translation(pname))
+ SVN_ERR(svn_subst_detranslate_string(&propval, propval,
+ TRUE, pool));
+
+ SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool));
+
+ svn_cmdline__print_xml_prop(outstr, pname_out, propval,
+ inherited_props, pool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_cmdline__be_interactive(svn_boolean_t non_interactive,
+ svn_boolean_t force_interactive)
+{
+ /* If neither --non-interactive nor --force-interactive was passed,
+ * be interactive if stdin is a terminal.
+ * If --force-interactive was passed, always be interactive. */
+ if (!force_interactive && !non_interactive)
+ {
+#ifdef WIN32
+ return (_isatty(STDIN_FILENO) != 0);
+#else
+ return (isatty(STDIN_FILENO) != 0);
+#endif
+ }
+ else if (force_interactive)
+ return TRUE;
+
+ return !non_interactive;
+}
+
+
+/* Helper for the next two functions. Set *EDITOR to some path to an
+ editor binary. Sources to search include: the EDITOR_CMD argument
+ (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG
+ is not NULL), $VISUAL, $EDITOR. Return
+ SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */
+static svn_error_t *
+find_editor_binary(const char **editor,
+ const char *editor_cmd,
+ apr_hash_t *config)
+{
+ const char *e;
+ struct svn_config_t *cfg;
+
+ /* Use the editor specified on the command line via --editor-cmd, if any. */
+ e = editor_cmd;
+
+ /* Otherwise look for the Subversion-specific environment variable. */
+ if (! e)
+ e = getenv("SVN_EDITOR");
+
+ /* If not found then fall back on the config file. */
+ if (! e)
+ {
+ cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
+ svn_config_get(cfg, &e, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_EDITOR_CMD, NULL);
+ }
+
+ /* If not found yet then try general purpose environment variables. */
+ if (! e)
+ e = getenv("VISUAL");
+ if (! e)
+ e = getenv("EDITOR");
+
+#ifdef SVN_CLIENT_EDITOR
+ /* If still not found then fall back on the hard-coded default. */
+ if (! e)
+ e = SVN_CLIENT_EDITOR;
+#endif
+
+ /* Error if there is no editor specified */
+ if (e)
+ {
+ const char *c;
+
+ for (c = e; *c; c++)
+ if (!svn_ctype_isspace(*c))
+ break;
+
+ if (! *c)
+ return svn_error_create
+ (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
+ _("The EDITOR, SVN_EDITOR or VISUAL environment variable or "
+ "'editor-cmd' run-time configuration option is empty or "
+ "consists solely of whitespace. Expected a shell command."));
+ }
+ else
+ return svn_error_create
+ (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
+ _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are "
+ "set, and no 'editor-cmd' run-time configuration option was found"));
+
+ *editor = e;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_cmdline__edit_file_externally(const char *path,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr;
+ char *old_cwd;
+ int sys_err;
+ apr_status_t apr_err;
+
+ svn_dirent_split(&base_dir, &file_name, path, pool);
+
+ SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
+
+ apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
+
+ /* APR doesn't like "" directories */
+ if (base_dir[0] == '\0')
+ base_dir_apr = ".";
+ else
+ SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
+
+ apr_err = apr_filepath_set(base_dir_apr, pool);
+ if (apr_err)
+ return svn_error_wrap_apr
+ (apr_err, _("Can't change working directory to '%s'"), base_dir);
+
+ cmd = apr_psprintf(pool, "%s %s", editor, file_name);
+ sys_err = system(cmd);
+
+ apr_err = apr_filepath_set(old_cwd, pool);
+ if (apr_err)
+ svn_handle_error2(svn_error_wrap_apr
+ (apr_err, _("Can't restore working directory")),
+ stderr, TRUE /* fatal */, "svn: ");
+
+ if (sys_err)
+ /* Extracting any meaning from sys_err is platform specific, so just
+ use the raw value. */
+ return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("system('%s') returned %d"), cmd, sys_err);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */,
+ const char **tmpfile_left /* UTF-8! */,
+ const char *editor_cmd,
+ const char *base_dir /* UTF-8! */,
+ const svn_string_t *contents /* UTF-8! */,
+ const char *filename,
+ apr_hash_t *config,
+ svn_boolean_t as_text,
+ const char *encoding,
+ apr_pool_t *pool)
+{
+ const char *editor;
+ const char *cmd;
+ apr_file_t *tmp_file;
+ const char *tmpfile_name;
+ const char *tmpfile_native;
+ const char *tmpfile_apr, *base_dir_apr;
+ svn_string_t *translated_contents;
+ apr_status_t apr_err, apr_err2;
+ apr_size_t written;
+ apr_finfo_t finfo_before, finfo_after;
+ svn_error_t *err = SVN_NO_ERROR, *err2;
+ char *old_cwd;
+ int sys_err;
+ svn_boolean_t remove_file = TRUE;
+
+ SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
+
+ /* Convert file contents from UTF-8/LF if desired. */
+ if (as_text)
+ {
+ const char *translated;
+ SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated,
+ APR_EOL_STR, FALSE,
+ NULL, FALSE, pool));
+ translated_contents = svn_string_create_empty(pool);
+ if (encoding)
+ SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data,
+ translated, encoding, pool));
+ else
+ SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data,
+ translated, pool));
+ translated_contents->len = strlen(translated_contents->data);
+ }
+ else
+ translated_contents = svn_string_dup(contents, pool);
+
+ /* Move to BASE_DIR to avoid getting characters that need quoting
+ into tmpfile_name */
+ apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
+
+ /* APR doesn't like "" directories */
+ if (base_dir[0] == '\0')
+ base_dir_apr = ".";
+ else
+ SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
+ apr_err = apr_filepath_set(base_dir_apr, pool);
+ if (apr_err)
+ {
+ return svn_error_wrap_apr
+ (apr_err, _("Can't change working directory to '%s'"), base_dir);
+ }
+
+ /*** From here on, any problems that occur require us to cd back!! ***/
+
+ /* Ask the working copy for a temporary file named FILENAME-something. */
+ err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
+ "" /* dirpath */,
+ filename,
+ ".tmp",
+ svn_io_file_del_none, pool, pool);
+
+ if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS))
+ {
+ const char *temp_dir_apr;
+
+ svn_error_clear(err);
+
+ SVN_ERR(svn_io_temp_dir(&base_dir, pool));
+
+ SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool));
+ apr_err = apr_filepath_set(temp_dir_apr, pool);
+ if (apr_err)
+ {
+ return svn_error_wrap_apr
+ (apr_err, _("Can't change working directory to '%s'"), base_dir);
+ }
+
+ err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
+ "" /* dirpath */,
+ filename,
+ ".tmp",
+ svn_io_file_del_none, pool, pool);
+ }
+
+ if (err)
+ goto cleanup2;
+
+ /*** From here on, any problems that occur require us to cleanup
+ the file we just created!! ***/
+
+ /* Dump initial CONTENTS to TMP_FILE. */
+ apr_err = apr_file_write_full(tmp_file, translated_contents->data,
+ translated_contents->len, &written);
+
+ apr_err2 = apr_file_close(tmp_file);
+ if (! apr_err)
+ apr_err = apr_err2;
+
+ /* Make sure the whole CONTENTS were written, else return an error. */
+ if (apr_err)
+ {
+ err = svn_error_wrap_apr(apr_err, _("Can't write to '%s'"),
+ tmpfile_name);
+ goto cleanup;
+ }
+
+ err = svn_path_cstring_from_utf8(&tmpfile_apr, tmpfile_name, pool);
+ if (err)
+ goto cleanup;
+
+ /* Get information about the temporary file before the user has
+ been allowed to edit its contents. */
+ apr_err = apr_stat(&finfo_before, tmpfile_apr,
+ APR_FINFO_MTIME, pool);
+ if (apr_err)
+ {
+ err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
+ goto cleanup;
+ }
+
+ /* Backdate the file a little bit in case the editor is very fast
+ and doesn't change the size. (Use two seconds, since some
+ filesystems have coarse granularity.) It's OK if this call
+ fails, so we don't check its return value.*/
+ apr_file_mtime_set(tmpfile_apr, finfo_before.mtime - 2000, pool);
+
+ /* Stat it again to get the mtime we actually set. */
+ apr_err = apr_stat(&finfo_before, tmpfile_apr,
+ APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
+ if (apr_err)
+ {
+ err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
+ goto cleanup;
+ }
+
+ /* Prepare the editor command line. */
+ err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool);
+ if (err)
+ goto cleanup;
+ cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native);
+
+ /* If the caller wants us to leave the file around, return the path
+ of the file we'll use, and make a note not to destroy it. */
+ if (tmpfile_left)
+ {
+ *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool);
+ remove_file = FALSE;
+ }
+
+ /* Now, run the editor command line. */
+ sys_err = system(cmd);
+ if (sys_err != 0)
+ {
+ /* Extracting any meaning from sys_err is platform specific, so just
+ use the raw value. */
+ err = svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("system('%s') returned %d"), cmd, sys_err);
+ goto cleanup;
+ }
+
+ /* Get information about the temporary file after the assumed editing. */
+ apr_err = apr_stat(&finfo_after, tmpfile_apr,
+ APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
+ if (apr_err)
+ {
+ err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
+ goto cleanup;
+ }
+
+ /* If the file looks changed... */
+ if ((finfo_before.mtime != finfo_after.mtime) ||
+ (finfo_before.size != finfo_after.size))
+ {
+ svn_stringbuf_t *edited_contents_s;
+ err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool);
+ if (err)
+ goto cleanup;
+
+ *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s);
+
+ /* Translate back to UTF8/LF if desired. */
+ if (as_text)
+ {
+ err = svn_subst_translate_string2(edited_contents, FALSE, FALSE,
+ *edited_contents, encoding, FALSE,
+ pool, pool);
+ if (err)
+ {
+ err = svn_error_quick_wrap
+ (err,
+ _("Error normalizing edited contents to internal format"));
+ goto cleanup;
+ }
+ }
+ }
+ else
+ {
+ /* No edits seem to have been made */
+ *edited_contents = NULL;
+ }
+
+ cleanup:
+ if (remove_file)
+ {
+ /* Remove the file from disk. */
+ err2 = svn_io_remove_file2(tmpfile_name, FALSE, pool);
+
+ /* Only report remove error if there was no previous error. */
+ if (! err && err2)
+ err = err2;
+ else
+ svn_error_clear(err2);
+ }
+
+ cleanup2:
+ /* If we against all probability can't cd back, all further relative
+ file references would be screwed up, so we have to abort. */
+ apr_err = apr_filepath_set(old_cwd, pool);
+ if (apr_err)
+ {
+ svn_handle_error2(svn_error_wrap_apr
+ (apr_err, _("Can't restore working directory")),
+ stderr, TRUE /* fatal */, "svn: ");
+ }
+
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/compat.c b/contrib/subversion/subversion/libsvn_subr/compat.c
new file mode 100644
index 0000000..2089828
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/compat.c
@@ -0,0 +1,159 @@
+/*
+ * compat.c : Wrappers and callbacks for compatibility.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_compat.h"
+#include "svn_props.h"
+
+
+/* Baton for use with svn_compat_wrap_commit_callback */
+struct commit_wrapper_baton {
+ void *baton;
+ svn_commit_callback_t callback;
+};
+
+/* This implements svn_commit_callback2_t. */
+static svn_error_t *
+commit_wrapper_callback(const svn_commit_info_t *commit_info,
+ void *baton, apr_pool_t *pool)
+{
+ struct commit_wrapper_baton *cwb = baton;
+
+ if (cwb->callback)
+ return cwb->callback(commit_info->revision,
+ commit_info->date,
+ commit_info->author,
+ cwb->baton);
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_compat_wrap_commit_callback(svn_commit_callback2_t *callback2,
+ void **callback2_baton,
+ svn_commit_callback_t callback,
+ void *callback_baton,
+ apr_pool_t *pool)
+{
+ struct commit_wrapper_baton *cwb = apr_palloc(pool, sizeof(*cwb));
+
+ /* Set the user provided old format callback in the baton */
+ cwb->baton = callback_baton;
+ cwb->callback = callback;
+
+ *callback2_baton = cwb;
+ *callback2 = commit_wrapper_callback;
+}
+
+
+void
+svn_compat_log_revprops_clear(apr_hash_t *revprops)
+{
+ if (revprops)
+ {
+ svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR, NULL);
+ svn_hash_sets(revprops, SVN_PROP_REVISION_DATE, NULL);
+ svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, NULL);
+ }
+}
+
+apr_array_header_t *
+svn_compat_log_revprops_in(apr_pool_t *pool)
+{
+ apr_array_header_t *revprops = apr_array_make(pool, 3, sizeof(char *));
+
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+
+ return revprops;
+}
+
+void
+svn_compat_log_revprops_out(const char **author, const char **date,
+ const char **message, apr_hash_t *revprops)
+{
+ svn_string_t *author_s, *date_s, *message_s;
+
+ *author = *date = *message = NULL;
+ if (revprops)
+ {
+ if ((author_s = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR)))
+ *author = author_s->data;
+ if ((date_s = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE)))
+ *date = date_s->data;
+ if ((message_s = svn_hash_gets(revprops, SVN_PROP_REVISION_LOG)))
+ *message = message_s->data;
+ }
+}
+
+/* Baton for use with svn_compat_wrap_log_receiver */
+struct log_wrapper_baton {
+ void *baton;
+ svn_log_message_receiver_t receiver;
+};
+
+/* This implements svn_log_entry_receiver_t. */
+static svn_error_t *
+log_wrapper_callback(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct log_wrapper_baton *lwb = baton;
+
+ if (lwb->receiver && SVN_IS_VALID_REVNUM(log_entry->revision))
+ {
+ const char *author, *date, *message;
+ svn_compat_log_revprops_out(&author, &date, &message,
+ log_entry->revprops);
+ return lwb->receiver(lwb->baton,
+ log_entry->changed_paths2,
+ log_entry->revision,
+ author, date, message,
+ pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_compat_wrap_log_receiver(svn_log_entry_receiver_t *receiver2,
+ void **receiver2_baton,
+ svn_log_message_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ struct log_wrapper_baton *lwb = apr_palloc(pool, sizeof(*lwb));
+
+ /* Set the user provided old format callback in the baton. */
+ lwb->baton = receiver_baton;
+ lwb->receiver = receiver;
+
+ *receiver2_baton = lwb;
+ *receiver2 = log_wrapper_callback;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/config.c b/contrib/subversion/subversion/libsvn_subr/config.c
new file mode 100644
index 0000000..94aecd3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/config.c
@@ -0,0 +1,1208 @@
+/*
+ * config.c : reading configuration information
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#define APR_WANT_MEMFUNC
+#include <apr_want.h>
+
+#include <apr_general.h>
+#include <apr_lib.h>
+#include "svn_hash.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "config_impl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_dep_compat.h"
+
+
+
+
+/* Section table entries. */
+typedef struct cfg_section_t cfg_section_t;
+struct cfg_section_t
+{
+ /* The section name. */
+ const char *name;
+
+ /* Table of cfg_option_t's. */
+ apr_hash_t *options;
+};
+
+
+/* Option table entries. */
+typedef struct cfg_option_t cfg_option_t;
+struct cfg_option_t
+{
+ /* The option name. */
+ const char *name;
+
+ /* The option name, converted into a hash key. */
+ const char *hash_key;
+
+ /* The unexpanded option value. */
+ const char *value;
+
+ /* The expanded option value. */
+ const char *x_value;
+
+ /* Expansion flag. If this is TRUE, this value has already been expanded.
+ In this case, if x_value is NULL, no expansions were necessary,
+ and value should be used directly. */
+ svn_boolean_t expanded;
+};
+
+
+
+svn_error_t *
+svn_config_create2(svn_config_t **cfgp,
+ svn_boolean_t section_names_case_sensitive,
+ svn_boolean_t option_names_case_sensitive,
+ apr_pool_t *result_pool)
+{
+ svn_config_t *cfg = apr_palloc(result_pool, sizeof(*cfg));
+
+ cfg->sections = apr_hash_make(result_pool);
+ cfg->pool = result_pool;
+ cfg->x_pool = svn_pool_create(result_pool);
+ cfg->x_values = FALSE;
+ cfg->tmp_key = svn_stringbuf_create_empty(result_pool);
+ cfg->tmp_value = svn_stringbuf_create_empty(result_pool);
+ cfg->section_names_case_sensitive = section_names_case_sensitive;
+ cfg->option_names_case_sensitive = option_names_case_sensitive;
+
+ *cfgp = cfg;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_config_read3(svn_config_t **cfgp, const char *file,
+ svn_boolean_t must_exist,
+ svn_boolean_t section_names_case_sensitive,
+ svn_boolean_t option_names_case_sensitive,
+ apr_pool_t *result_pool)
+{
+ svn_config_t *cfg;
+ svn_error_t *err;
+
+ SVN_ERR(svn_config_create2(&cfg,
+ section_names_case_sensitive,
+ option_names_case_sensitive,
+ result_pool));
+
+ /* Yes, this is platform-specific code in Subversion, but there's no
+ practical way to migrate it into APR, as it's simultaneously
+ Subversion-specific and Windows-specific. Even if we eventually
+ want to have APR offer a generic config-reading interface, it
+ makes sense to test it here first and migrate it later. */
+#ifdef WIN32
+ if (0 == strncmp(file, SVN_REGISTRY_PREFIX, SVN_REGISTRY_PREFIX_LEN))
+ err = svn_config__parse_registry(cfg, file + SVN_REGISTRY_PREFIX_LEN,
+ must_exist, result_pool);
+ else
+#endif /* WIN32 */
+ err = svn_config__parse_file(cfg, file, must_exist, result_pool);
+
+ if (err != SVN_NO_ERROR)
+ return err;
+ else
+ *cfgp = cfg;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_config_parse(svn_config_t **cfgp, svn_stream_t *stream,
+ svn_boolean_t section_names_case_sensitive,
+ svn_boolean_t option_names_case_sensitive,
+ apr_pool_t *result_pool)
+{
+ svn_config_t *cfg;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool = svn_pool_create(result_pool);
+
+ err = svn_config_create2(&cfg,
+ section_names_case_sensitive,
+ option_names_case_sensitive,
+ result_pool);
+
+ if (err == SVN_NO_ERROR)
+ err = svn_config__parse_stream(cfg, stream, result_pool, scratch_pool);
+
+ if (err == SVN_NO_ERROR)
+ *cfgp = cfg;
+
+ svn_pool_destroy(scratch_pool);
+
+ return err;
+}
+
+/* Read various configuration sources into *CFGP, in this order, with
+ * later reads overriding the results of earlier ones:
+ *
+ * 1. SYS_REGISTRY_PATH (only on Win32, but ignored if NULL)
+ *
+ * 2. SYS_FILE_PATH (everywhere, but ignored if NULL)
+ *
+ * 3. USR_REGISTRY_PATH (only on Win32, but ignored if NULL)
+ *
+ * 4. USR_FILE_PATH (everywhere, but ignored if NULL)
+ *
+ * Allocate *CFGP in POOL. Even if no configurations are read,
+ * allocate an empty *CFGP.
+ */
+static svn_error_t *
+read_all(svn_config_t **cfgp,
+ const char *sys_registry_path,
+ const char *usr_registry_path,
+ const char *sys_file_path,
+ const char *usr_file_path,
+ apr_pool_t *pool)
+{
+ svn_boolean_t red_config = FALSE; /* "red" is the past tense of "read" */
+
+ /*** Read system-wide configurations first... ***/
+
+#ifdef WIN32
+ if (sys_registry_path)
+ {
+ SVN_ERR(svn_config_read2(cfgp, sys_registry_path, FALSE, FALSE, pool));
+ red_config = TRUE;
+ }
+#endif /* WIN32 */
+
+ if (sys_file_path)
+ {
+ if (red_config)
+ SVN_ERR(svn_config_merge(*cfgp, sys_file_path, FALSE));
+ else
+ {
+ SVN_ERR(svn_config_read3(cfgp, sys_file_path,
+ FALSE, FALSE, FALSE, pool));
+ red_config = TRUE;
+ }
+ }
+
+ /*** ...followed by per-user configurations. ***/
+
+#ifdef WIN32
+ if (usr_registry_path)
+ {
+ if (red_config)
+ SVN_ERR(svn_config_merge(*cfgp, usr_registry_path, FALSE));
+ else
+ {
+ SVN_ERR(svn_config_read2(cfgp, usr_registry_path,
+ FALSE, FALSE, pool));
+ red_config = TRUE;
+ }
+ }
+#endif /* WIN32 */
+
+ if (usr_file_path)
+ {
+ if (red_config)
+ SVN_ERR(svn_config_merge(*cfgp, usr_file_path, FALSE));
+ else
+ {
+ SVN_ERR(svn_config_read3(cfgp, usr_file_path,
+ FALSE, FALSE, FALSE, pool));
+ red_config = TRUE;
+ }
+ }
+
+ if (! red_config)
+ SVN_ERR(svn_config_create2(cfgp, FALSE, FALSE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* CONFIG_DIR provides an override for the default behavior of reading
+ the default set of overlay files described by read_all()'s doc
+ string. */
+static svn_error_t *
+get_category_config(svn_config_t **cfg,
+ const char *config_dir,
+ const char *category,
+ apr_pool_t *pool)
+{
+ const char *usr_reg_path = NULL, *sys_reg_path = NULL;
+ const char *usr_cfg_path, *sys_cfg_path;
+ svn_error_t *err = NULL;
+
+ *cfg = NULL;
+
+ if (! config_dir)
+ {
+#ifdef WIN32
+ sys_reg_path = apr_pstrcat(pool, SVN_REGISTRY_SYS_CONFIG_PATH,
+ category, NULL);
+ usr_reg_path = apr_pstrcat(pool, SVN_REGISTRY_USR_CONFIG_PATH,
+ category, NULL);
+#endif /* WIN32 */
+
+ err = svn_config__sys_config_path(&sys_cfg_path, category, pool);
+ if ((err) && (err->apr_err == SVN_ERR_BAD_FILENAME))
+ {
+ sys_cfg_path = NULL;
+ svn_error_clear(err);
+ }
+ else if (err)
+ return err;
+ }
+ else
+ sys_cfg_path = NULL;
+
+ SVN_ERR(svn_config_get_user_config_path(&usr_cfg_path, config_dir, category,
+ pool));
+ return read_all(cfg, sys_reg_path, usr_reg_path,
+ sys_cfg_path, usr_cfg_path, pool);
+}
+
+
+svn_error_t *
+svn_config_get_config(apr_hash_t **cfg_hash,
+ const char *config_dir,
+ apr_pool_t *pool)
+{
+ svn_config_t *cfg;
+ *cfg_hash = apr_hash_make(pool);
+
+#define CATLEN (sizeof(SVN_CONFIG_CATEGORY_SERVERS) - 1)
+ SVN_ERR(get_category_config(&cfg, config_dir, SVN_CONFIG_CATEGORY_SERVERS,
+ pool));
+ if (cfg)
+ apr_hash_set(*cfg_hash, SVN_CONFIG_CATEGORY_SERVERS, CATLEN, cfg);
+#undef CATLEN
+
+#define CATLEN (sizeof(SVN_CONFIG_CATEGORY_CONFIG) - 1)
+ SVN_ERR(get_category_config(&cfg, config_dir, SVN_CONFIG_CATEGORY_CONFIG,
+ pool));
+ if (cfg)
+ apr_hash_set(*cfg_hash, SVN_CONFIG_CATEGORY_CONFIG, CATLEN, cfg);
+#undef CATLEN
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Iterate through CFG, passing BATON to CALLBACK for every (SECTION, OPTION)
+ pair. Stop if CALLBACK returns TRUE. Allocate from POOL. */
+static void
+for_each_option(svn_config_t *cfg, void *baton, apr_pool_t *pool,
+ svn_boolean_t callback(void *same_baton,
+ cfg_section_t *section,
+ cfg_option_t *option))
+{
+ apr_hash_index_t *sec_ndx;
+ for (sec_ndx = apr_hash_first(pool, cfg->sections);
+ sec_ndx != NULL;
+ sec_ndx = apr_hash_next(sec_ndx))
+ {
+ void *sec_ptr;
+ cfg_section_t *sec;
+ apr_hash_index_t *opt_ndx;
+
+ apr_hash_this(sec_ndx, NULL, NULL, &sec_ptr);
+ sec = sec_ptr;
+
+ for (opt_ndx = apr_hash_first(pool, sec->options);
+ opt_ndx != NULL;
+ opt_ndx = apr_hash_next(opt_ndx))
+ {
+ void *opt_ptr;
+ cfg_option_t *opt;
+
+ apr_hash_this(opt_ndx, NULL, NULL, &opt_ptr);
+ opt = opt_ptr;
+
+ if (callback(baton, sec, opt))
+ return;
+ }
+ }
+}
+
+
+
+static svn_boolean_t
+merge_callback(void *baton, cfg_section_t *section, cfg_option_t *option)
+{
+ svn_config_set(baton, section->name, option->name, option->value);
+ return FALSE;
+}
+
+svn_error_t *
+svn_config_merge(svn_config_t *cfg, const char *file,
+ svn_boolean_t must_exist)
+{
+ /* The original config hash shouldn't change if there's an error
+ while reading the confguration, so read into a temporary table.
+ ### We could use a tmp subpool for this, since merge_cfg is going
+ to be tossed afterwards. Premature optimization, though? */
+ svn_config_t *merge_cfg;
+ SVN_ERR(svn_config_read3(&merge_cfg, file, must_exist,
+ cfg->section_names_case_sensitive,
+ cfg->option_names_case_sensitive,
+ cfg->pool));
+
+ /* Now copy the new options into the original table. */
+ for_each_option(merge_cfg, cfg, merge_cfg->pool, merge_callback);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Remove variable expansions from CFG. Walk through the options tree,
+ killing all expanded values, then clear the expanded value pool. */
+static svn_boolean_t
+rmex_callback(void *baton, cfg_section_t *section, cfg_option_t *option)
+{
+ /* Only clear the `expanded' flag if the value actually contains
+ variable expansions. */
+ if (option->expanded && option->x_value != NULL)
+ {
+ option->x_value = NULL;
+ option->expanded = FALSE;
+ }
+
+ return FALSE;
+}
+
+static void
+remove_expansions(svn_config_t *cfg)
+{
+ if (!cfg->x_values)
+ return;
+
+ for_each_option(cfg, NULL, cfg->x_pool, rmex_callback);
+ svn_pool_clear(cfg->x_pool);
+ cfg->x_values = FALSE;
+}
+
+
+
+/* Canonicalize a string for hashing. Modifies KEY in place. */
+static APR_INLINE char *
+make_hash_key(char *key)
+{
+ register char *p;
+ for (p = key; *p != 0; ++p)
+ *p = (char)apr_tolower(*p);
+ return key;
+}
+
+
+/* Return a pointer to an option in CFG, or NULL if it doesn't exist.
+ if SECTIONP is non-null, return a pointer to the option's section.
+ OPTION may be NULL. */
+static cfg_option_t *
+find_option(svn_config_t *cfg, const char *section, const char *option,
+ cfg_section_t **sectionp)
+{
+ void *sec_ptr;
+
+ /* Canonicalize the hash key */
+ svn_stringbuf_set(cfg->tmp_key, section);
+ if (! cfg->section_names_case_sensitive)
+ make_hash_key(cfg->tmp_key->data);
+
+ sec_ptr = apr_hash_get(cfg->sections, cfg->tmp_key->data,
+ cfg->tmp_key->len);
+ if (sectionp != NULL)
+ *sectionp = sec_ptr;
+
+ if (sec_ptr != NULL && option != NULL)
+ {
+ cfg_section_t *sec = sec_ptr;
+ cfg_option_t *opt;
+
+ /* Canonicalize the option key */
+ svn_stringbuf_set(cfg->tmp_key, option);
+ if (! cfg->option_names_case_sensitive)
+ make_hash_key(cfg->tmp_key->data);
+
+ opt = apr_hash_get(sec->options, cfg->tmp_key->data,
+ cfg->tmp_key->len);
+ /* NOTE: ConfigParser's sections are case sensitive. */
+ if (opt == NULL
+ && apr_strnatcasecmp(section, SVN_CONFIG__DEFAULT_SECTION) != 0)
+ /* Options which aren't found in the requested section are
+ also sought after in the default section. */
+ opt = find_option(cfg, SVN_CONFIG__DEFAULT_SECTION, option, &sec);
+ return opt;
+ }
+
+ return NULL;
+}
+
+
+/* Has a bi-directional dependency with make_string_from_option(). */
+static void
+expand_option_value(svn_config_t *cfg, cfg_section_t *section,
+ const char *opt_value, const char **opt_x_valuep,
+ apr_pool_t *x_pool);
+
+
+/* Set *VALUEP according to the OPT's value. A value for X_POOL must
+ only ever be passed into this function by expand_option_value(). */
+static void
+make_string_from_option(const char **valuep, svn_config_t *cfg,
+ cfg_section_t *section, cfg_option_t *opt,
+ apr_pool_t* x_pool)
+{
+ /* Expand the option value if necessary. */
+ if (!opt->expanded)
+ {
+ /* before attempting to expand an option, check for the placeholder.
+ * If none is there, there is no point in calling expand_option_value.
+ */
+ if (opt->value && strchr(opt->value, '%'))
+ {
+ apr_pool_t *tmp_pool = (x_pool ? x_pool : svn_pool_create(cfg->x_pool));
+
+ expand_option_value(cfg, section, opt->value, &opt->x_value, tmp_pool);
+ opt->expanded = TRUE;
+
+ if (!x_pool)
+ {
+ /* Grab the fully expanded value from tmp_pool before its
+ disappearing act. */
+ if (opt->x_value)
+ opt->x_value = apr_pstrmemdup(cfg->x_pool, opt->x_value,
+ strlen(opt->x_value));
+ svn_pool_destroy(tmp_pool);
+ }
+ }
+ else
+ {
+ opt->expanded = TRUE;
+ }
+ }
+
+ if (opt->x_value)
+ *valuep = opt->x_value;
+ else
+ *valuep = opt->value;
+}
+
+
+/* Start of variable-replacement placeholder */
+#define FMT_START "%("
+#define FMT_START_LEN (sizeof(FMT_START) - 1)
+
+/* End of variable-replacement placeholder */
+#define FMT_END ")s"
+#define FMT_END_LEN (sizeof(FMT_END) - 1)
+
+
+/* Expand OPT_VALUE (which may be NULL) in SECTION into *OPT_X_VALUEP.
+ If no variable replacements are done, set *OPT_X_VALUEP to
+ NULL. Allocate from X_POOL. */
+static void
+expand_option_value(svn_config_t *cfg, cfg_section_t *section,
+ const char *opt_value, const char **opt_x_valuep,
+ apr_pool_t *x_pool)
+{
+ svn_stringbuf_t *buf = NULL;
+ const char *parse_from = opt_value;
+ const char *copy_from = parse_from;
+ const char *name_start, *name_end;
+
+ while (parse_from != NULL
+ && *parse_from != '\0'
+ && (name_start = strstr(parse_from, FMT_START)) != NULL)
+ {
+ name_start += FMT_START_LEN;
+ if (*name_start == '\0')
+ /* FMT_START at end of opt_value. */
+ break;
+
+ name_end = strstr(name_start, FMT_END);
+ if (name_end != NULL)
+ {
+ cfg_option_t *x_opt;
+ apr_size_t len = name_end - name_start;
+ char *name = apr_pstrmemdup(x_pool, name_start, len);
+
+ x_opt = find_option(cfg, section->name, name, NULL);
+
+ if (x_opt != NULL)
+ {
+ const char *cstring;
+
+ /* Pass back the sub-pool originally provided by
+ make_string_from_option() as an indication of when it
+ should terminate. */
+ make_string_from_option(&cstring, cfg, section, x_opt, x_pool);
+
+ /* Append the plain text preceding the expansion. */
+ len = name_start - FMT_START_LEN - copy_from;
+ if (buf == NULL)
+ {
+ buf = svn_stringbuf_ncreate(copy_from, len, x_pool);
+ cfg->x_values = TRUE;
+ }
+ else
+ svn_stringbuf_appendbytes(buf, copy_from, len);
+
+ /* Append the expansion and adjust parse pointers. */
+ svn_stringbuf_appendcstr(buf, cstring);
+ parse_from = name_end + FMT_END_LEN;
+ copy_from = parse_from;
+ }
+ else
+ /* Though ConfigParser considers the failure to resolve
+ the requested expansion an exception condition, we
+ consider it to be plain text, and look for the start of
+ the next one. */
+ parse_from = name_end + FMT_END_LEN;
+ }
+ else
+ /* Though ConfigParser treats unterminated format specifiers
+ as an exception condition, we consider them to be plain
+ text. The fact that there are no more format specifier
+ endings means we're done parsing. */
+ parse_from = NULL;
+ }
+
+ if (buf != NULL)
+ {
+ /* Copy the remainder of the plain text. */
+ svn_stringbuf_appendcstr(buf, copy_from);
+ *opt_x_valuep = buf->data;
+ }
+ else
+ *opt_x_valuep = NULL;
+}
+
+static cfg_section_t *
+svn_config_addsection(svn_config_t *cfg,
+ const char *section)
+{
+ cfg_section_t *s;
+ const char *hash_key;
+
+ s = apr_palloc(cfg->pool, sizeof(cfg_section_t));
+ s->name = apr_pstrdup(cfg->pool, section);
+ if(cfg->section_names_case_sensitive)
+ hash_key = s->name;
+ else
+ hash_key = make_hash_key(apr_pstrdup(cfg->pool, section));
+ s->options = apr_hash_make(cfg->pool);
+ svn_hash_sets(cfg->sections, hash_key, s);
+
+ return s;
+}
+
+static void
+svn_config_create_option(cfg_option_t **opt,
+ const char *option,
+ const char *value,
+ svn_boolean_t option_names_case_sensitive,
+ apr_pool_t *pool)
+{
+ cfg_option_t *o;
+
+ o = apr_palloc(pool, sizeof(cfg_option_t));
+ o->name = apr_pstrdup(pool, option);
+ if(option_names_case_sensitive)
+ o->hash_key = o->name;
+ else
+ o->hash_key = make_hash_key(apr_pstrdup(pool, option));
+
+ o->value = apr_pstrdup(pool, value);
+ o->x_value = NULL;
+ o->expanded = FALSE;
+
+ *opt = o;
+}
+
+
+void
+svn_config_get(svn_config_t *cfg, const char **valuep,
+ const char *section, const char *option,
+ const char *default_value)
+{
+ *valuep = default_value;
+ if (cfg)
+ {
+ cfg_section_t *sec;
+ cfg_option_t *opt = find_option(cfg, section, option, &sec);
+ if (opt != NULL)
+ {
+ make_string_from_option(valuep, cfg, sec, opt, NULL);
+ }
+ else
+ /* before attempting to expand an option, check for the placeholder.
+ * If none is there, there is no point in calling expand_option_value.
+ */
+ if (default_value && strchr(default_value, '%'))
+ {
+ apr_pool_t *tmp_pool = svn_pool_create(cfg->x_pool);
+ const char *x_default;
+ expand_option_value(cfg, sec, default_value, &x_default, tmp_pool);
+ if (x_default)
+ {
+ svn_stringbuf_set(cfg->tmp_value, x_default);
+ *valuep = cfg->tmp_value->data;
+ }
+ svn_pool_destroy(tmp_pool);
+ }
+ }
+}
+
+
+
+void
+svn_config_set(svn_config_t *cfg,
+ const char *section, const char *option,
+ const char *value)
+{
+ cfg_section_t *sec;
+ cfg_option_t *opt;
+
+ remove_expansions(cfg);
+
+ opt = find_option(cfg, section, option, &sec);
+ if (opt != NULL)
+ {
+ /* Replace the option's value. */
+ opt->value = apr_pstrdup(cfg->pool, value);
+ opt->expanded = FALSE;
+ return;
+ }
+
+ /* Create a new option */
+ svn_config_create_option(&opt, option, value,
+ cfg->option_names_case_sensitive,
+ cfg->pool);
+
+ if (sec == NULL)
+ {
+ /* Even the section doesn't exist. Create it. */
+ sec = svn_config_addsection(cfg, section);
+ }
+
+ svn_hash_sets(sec->options, opt->hash_key, opt);
+}
+
+
+
+/* Set *BOOLP to true or false depending (case-insensitively) on INPUT.
+ If INPUT is null, set *BOOLP to DEFAULT_VALUE.
+
+ INPUT is a string indicating truth or falsehood in any of the usual
+ ways: "true"/"yes"/"on"/etc, "false"/"no"/"off"/etc.
+
+ If INPUT is neither NULL nor a recognized string, return an error
+ with code SVN_ERR_BAD_CONFIG_VALUE; use SECTION and OPTION in
+ constructing the error string. */
+static svn_error_t *
+get_bool(svn_boolean_t *boolp, const char *input, svn_boolean_t default_value,
+ const char *section, const char *option)
+{
+ svn_tristate_t value = svn_tristate__from_word(input);
+
+ if (value == svn_tristate_true)
+ *boolp = TRUE;
+ else if (value == svn_tristate_false)
+ *boolp = FALSE;
+ else if (input == NULL) /* no value provided */
+ *boolp = default_value;
+
+ else if (section) /* unrecognized value */
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Config error: invalid boolean "
+ "value '%s' for '[%s] %s'"),
+ input, section, option);
+ else
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Config error: invalid boolean "
+ "value '%s' for '%s'"),
+ input, option);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_config_get_bool(svn_config_t *cfg, svn_boolean_t *valuep,
+ const char *section, const char *option,
+ svn_boolean_t default_value)
+{
+ const char *tmp_value;
+ svn_config_get(cfg, &tmp_value, section, option, NULL);
+ return get_bool(valuep, tmp_value, default_value, section, option);
+}
+
+
+
+void
+svn_config_set_bool(svn_config_t *cfg,
+ const char *section, const char *option,
+ svn_boolean_t value)
+{
+ svn_config_set(cfg, section, option,
+ (value ? SVN_CONFIG_TRUE : SVN_CONFIG_FALSE));
+}
+
+svn_error_t *
+svn_config_get_int64(svn_config_t *cfg,
+ apr_int64_t *valuep,
+ const char *section,
+ const char *option,
+ apr_int64_t default_value)
+{
+ const char *tmp_value;
+ svn_config_get(cfg, &tmp_value, section, option, NULL);
+ if (tmp_value)
+ return svn_cstring_strtoi64(valuep, tmp_value,
+ APR_INT64_MIN, APR_INT64_MAX, 10);
+
+ *valuep = default_value;
+ return SVN_NO_ERROR;
+}
+
+void
+svn_config_set_int64(svn_config_t *cfg,
+ const char *section,
+ const char *option,
+ apr_int64_t value)
+{
+ svn_config_set(cfg, section, option,
+ apr_psprintf(cfg->pool, "%" APR_INT64_T_FMT, value));
+}
+
+svn_error_t *
+svn_config_get_yes_no_ask(svn_config_t *cfg, const char **valuep,
+ const char *section, const char *option,
+ const char* default_value)
+{
+ const char *tmp_value;
+
+ svn_config_get(cfg, &tmp_value, section, option, NULL);
+
+ if (! tmp_value)
+ tmp_value = default_value;
+
+ if (tmp_value && (0 == svn_cstring_casecmp(tmp_value, SVN_CONFIG_ASK)))
+ {
+ *valuep = SVN_CONFIG_ASK;
+ }
+ else
+ {
+ svn_boolean_t bool_val;
+ /* We already incorporated default_value into tmp_value if
+ necessary, so the FALSE below will be ignored unless the
+ caller is doing something it shouldn't be doing. */
+ SVN_ERR(get_bool(&bool_val, tmp_value, FALSE, section, option));
+ *valuep = bool_val ? SVN_CONFIG_TRUE : SVN_CONFIG_FALSE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_config_get_tristate(svn_config_t *cfg, svn_tristate_t *valuep,
+ const char *section, const char *option,
+ const char *unknown_value,
+ svn_tristate_t default_value)
+{
+ const char *tmp_value;
+
+ svn_config_get(cfg, &tmp_value, section, option, NULL);
+
+ if (! tmp_value)
+ {
+ *valuep = default_value;
+ }
+ else if (0 == svn_cstring_casecmp(tmp_value, unknown_value))
+ {
+ *valuep = svn_tristate_unknown;
+ }
+ else
+ {
+ svn_boolean_t bool_val;
+ /* We already incorporated default_value into tmp_value if
+ necessary, so the FALSE below will be ignored unless the
+ caller is doing something it shouldn't be doing. */
+ SVN_ERR(get_bool(&bool_val, tmp_value, FALSE, section, option));
+ *valuep = bool_val ? svn_tristate_true : svn_tristate_false;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+int
+svn_config_enumerate_sections(svn_config_t *cfg,
+ svn_config_section_enumerator_t callback,
+ void *baton)
+{
+ apr_hash_index_t *sec_ndx;
+ int count = 0;
+ apr_pool_t *subpool = svn_pool_create(cfg->x_pool);
+
+ for (sec_ndx = apr_hash_first(subpool, cfg->sections);
+ sec_ndx != NULL;
+ sec_ndx = apr_hash_next(sec_ndx))
+ {
+ void *sec_ptr;
+ cfg_section_t *sec;
+
+ apr_hash_this(sec_ndx, NULL, NULL, &sec_ptr);
+ sec = sec_ptr;
+ ++count;
+ if (!callback(sec->name, baton))
+ break;
+ }
+
+ svn_pool_destroy(subpool);
+ return count;
+}
+
+
+int
+svn_config_enumerate_sections2(svn_config_t *cfg,
+ svn_config_section_enumerator2_t callback,
+ void *baton, apr_pool_t *pool)
+{
+ apr_hash_index_t *sec_ndx;
+ apr_pool_t *iteration_pool;
+ int count = 0;
+
+ iteration_pool = svn_pool_create(pool);
+ for (sec_ndx = apr_hash_first(pool, cfg->sections);
+ sec_ndx != NULL;
+ sec_ndx = apr_hash_next(sec_ndx))
+ {
+ void *sec_ptr;
+ cfg_section_t *sec;
+
+ apr_hash_this(sec_ndx, NULL, NULL, &sec_ptr);
+ sec = sec_ptr;
+ ++count;
+ svn_pool_clear(iteration_pool);
+ if (!callback(sec->name, baton, iteration_pool))
+ break;
+ }
+ svn_pool_destroy(iteration_pool);
+
+ return count;
+}
+
+
+
+int
+svn_config_enumerate(svn_config_t *cfg, const char *section,
+ svn_config_enumerator_t callback, void *baton)
+{
+ cfg_section_t *sec;
+ apr_hash_index_t *opt_ndx;
+ int count;
+ apr_pool_t *subpool;
+
+ find_option(cfg, section, NULL, &sec);
+ if (sec == NULL)
+ return 0;
+
+ subpool = svn_pool_create(cfg->x_pool);
+ count = 0;
+ for (opt_ndx = apr_hash_first(subpool, sec->options);
+ opt_ndx != NULL;
+ opt_ndx = apr_hash_next(opt_ndx))
+ {
+ void *opt_ptr;
+ cfg_option_t *opt;
+ const char *temp_value;
+
+ apr_hash_this(opt_ndx, NULL, NULL, &opt_ptr);
+ opt = opt_ptr;
+
+ ++count;
+ make_string_from_option(&temp_value, cfg, sec, opt, NULL);
+ if (!callback(opt->name, temp_value, baton))
+ break;
+ }
+
+ svn_pool_destroy(subpool);
+ return count;
+}
+
+
+int
+svn_config_enumerate2(svn_config_t *cfg, const char *section,
+ svn_config_enumerator2_t callback, void *baton,
+ apr_pool_t *pool)
+{
+ cfg_section_t *sec;
+ apr_hash_index_t *opt_ndx;
+ apr_pool_t *iteration_pool;
+ int count;
+
+ find_option(cfg, section, NULL, &sec);
+ if (sec == NULL)
+ return 0;
+
+ iteration_pool = svn_pool_create(pool);
+ count = 0;
+ for (opt_ndx = apr_hash_first(pool, sec->options);
+ opt_ndx != NULL;
+ opt_ndx = apr_hash_next(opt_ndx))
+ {
+ void *opt_ptr;
+ cfg_option_t *opt;
+ const char *temp_value;
+
+ apr_hash_this(opt_ndx, NULL, NULL, &opt_ptr);
+ opt = opt_ptr;
+
+ ++count;
+ make_string_from_option(&temp_value, cfg, sec, opt, NULL);
+ svn_pool_clear(iteration_pool);
+ if (!callback(opt->name, temp_value, baton, iteration_pool))
+ break;
+ }
+ svn_pool_destroy(iteration_pool);
+
+ return count;
+}
+
+
+
+/* Baton for search_groups() */
+struct search_groups_baton
+{
+ const char *key; /* Provided by caller of svn_config_find_group */
+ const char *match; /* Filled in by search_groups */
+ apr_pool_t *pool;
+};
+
+
+/* This is an `svn_config_enumerator_t' function, and BATON is a
+ * `struct search_groups_baton *'.
+ */
+static svn_boolean_t search_groups(const char *name,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct search_groups_baton *b = baton;
+ apr_array_header_t *list;
+
+ list = svn_cstring_split(value, ",", TRUE, pool);
+ if (svn_cstring_match_glob_list(b->key, list))
+ {
+ /* Fill in the match and return false, to stop enumerating. */
+ b->match = apr_pstrdup(b->pool, name);
+ return FALSE;
+ }
+ else
+ return TRUE;
+}
+
+
+const char *svn_config_find_group(svn_config_t *cfg, const char *key,
+ const char *master_section,
+ apr_pool_t *pool)
+{
+ struct search_groups_baton gb;
+
+ gb.key = key;
+ gb.match = NULL;
+ gb.pool = pool;
+ (void) svn_config_enumerate2(cfg, master_section, search_groups, &gb, pool);
+ return gb.match;
+}
+
+
+const char*
+svn_config_get_server_setting(svn_config_t *cfg,
+ const char* server_group,
+ const char* option_name,
+ const char* default_value)
+{
+ const char *retval;
+ svn_config_get(cfg, &retval, SVN_CONFIG_SECTION_GLOBAL,
+ option_name, default_value);
+ if (server_group)
+ {
+ svn_config_get(cfg, &retval, server_group, option_name, retval);
+ }
+ return retval;
+}
+
+
+svn_error_t *
+svn_config_dup(svn_config_t **cfgp,
+ svn_config_t *src,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *sectidx;
+ apr_hash_index_t *optidx;
+
+ *cfgp = 0;
+ SVN_ERR(svn_config_create2(cfgp, FALSE, FALSE, pool));
+
+ (*cfgp)->x_values = src->x_values;
+ (*cfgp)->section_names_case_sensitive = src->section_names_case_sensitive;
+ (*cfgp)->option_names_case_sensitive = src->option_names_case_sensitive;
+
+ for (sectidx = apr_hash_first(pool, src->sections);
+ sectidx != NULL;
+ sectidx = apr_hash_next(sectidx))
+ {
+ const void *sectkey;
+ void *sectval;
+ apr_ssize_t sectkeyLength;
+ cfg_section_t * srcsect;
+ cfg_section_t * destsec;
+
+ apr_hash_this(sectidx, &sectkey, &sectkeyLength, &sectval);
+ srcsect = sectval;
+
+ destsec = svn_config_addsection(*cfgp, srcsect->name);
+
+ for (optidx = apr_hash_first(pool, srcsect->options);
+ optidx != NULL;
+ optidx = apr_hash_next(optidx))
+ {
+ const void *optkey;
+ void *optval;
+ apr_ssize_t optkeyLength;
+ cfg_option_t *srcopt;
+ cfg_option_t *destopt;
+
+ apr_hash_this(optidx, &optkey, &optkeyLength, &optval);
+ srcopt = optval;
+
+ svn_config_create_option(&destopt, srcopt->name, srcopt->value,
+ (*cfgp)->option_names_case_sensitive,
+ pool);
+
+ destopt->value = apr_pstrdup(pool, srcopt->value);
+ destopt->x_value = apr_pstrdup(pool, srcopt->x_value);
+ destopt->expanded = srcopt->expanded;
+ apr_hash_set(destsec->options,
+ apr_pstrdup(pool, (const char*)optkey),
+ optkeyLength, destopt);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_config_copy_config(apr_hash_t **cfg_hash,
+ apr_hash_t *src_hash,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *cidx;
+
+ *cfg_hash = apr_hash_make(pool);
+ for (cidx = apr_hash_first(pool, src_hash);
+ cidx != NULL;
+ cidx = apr_hash_next(cidx))
+ {
+ const void *ckey;
+ void *cval;
+ apr_ssize_t ckeyLength;
+ svn_config_t * srcconfig;
+ svn_config_t * destconfig;
+
+ apr_hash_this(cidx, &ckey, &ckeyLength, &cval);
+ srcconfig = cval;
+
+ SVN_ERR(svn_config_dup(&destconfig, srcconfig, pool));
+
+ apr_hash_set(*cfg_hash,
+ apr_pstrdup(pool, (const char*)ckey),
+ ckeyLength, destconfig);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t*
+svn_config_get_server_setting_int(svn_config_t *cfg,
+ const char *server_group,
+ const char *option_name,
+ apr_int64_t default_value,
+ apr_int64_t *result_value,
+ apr_pool_t *pool)
+{
+ const char* tmp_value;
+ char *end_pos;
+
+ tmp_value = svn_config_get_server_setting(cfg, server_group,
+ option_name, NULL);
+ if (tmp_value == NULL)
+ *result_value = default_value;
+ else
+ {
+ /* read tmp_value as an int now */
+ *result_value = apr_strtoi64(tmp_value, &end_pos, 0);
+
+ if (*end_pos != 0)
+ {
+ return svn_error_createf
+ (SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Config error: invalid integer value '%s'"),
+ tmp_value);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_config_get_server_setting_bool(svn_config_t *cfg,
+ svn_boolean_t *valuep,
+ const char *server_group,
+ const char *option_name,
+ svn_boolean_t default_value)
+{
+ const char* tmp_value;
+ tmp_value = svn_config_get_server_setting(cfg, server_group,
+ option_name, NULL);
+ return get_bool(valuep, tmp_value, default_value,
+ server_group, option_name);
+}
+
+
+svn_boolean_t
+svn_config_has_section(svn_config_t *cfg, const char *section)
+{
+ cfg_section_t *sec;
+
+ /* Canonicalize the hash key */
+ svn_stringbuf_set(cfg->tmp_key, section);
+ if (! cfg->section_names_case_sensitive)
+ make_hash_key(cfg->tmp_key->data);
+
+ sec = svn_hash_gets(cfg->sections, cfg->tmp_key->data);
+ return sec != NULL;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/config_auth.c b/contrib/subversion/subversion/libsvn_subr/config_auth.c
new file mode 100644
index 0000000..d53403c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/config_auth.c
@@ -0,0 +1,277 @@
+/*
+ * config_auth.c : authentication files in the user config area
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "config_impl.h"
+
+#include "auth.h"
+
+#include "svn_private_config.h"
+
+#include "private/svn_auth_private.h"
+
+/* Helper for svn_config_{read|write}_auth_data. Return a path to a
+ file within ~/.subversion/auth/ that holds CRED_KIND credentials
+ within REALMSTRING. If no path is available *PATH will be set to
+ NULL. */
+svn_error_t *
+svn_auth__file_path(const char **path,
+ const char *cred_kind,
+ const char *realmstring,
+ const char *config_dir,
+ apr_pool_t *pool)
+{
+ const char *authdir_path, *hexname;
+ svn_checksum_t *checksum;
+
+ /* Construct the path to the directory containing the creds files,
+ e.g. "~/.subversion/auth/svn.simple". The last component is
+ simply the cred_kind. */
+ SVN_ERR(svn_config_get_user_config_path(&authdir_path, config_dir,
+ SVN_CONFIG__AUTH_SUBDIR, pool));
+ if (authdir_path)
+ {
+ authdir_path = svn_dirent_join(authdir_path, cred_kind, pool);
+
+ /* Construct the basename of the creds file. It's just the
+ realmstring converted into an md5 hex string. */
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, realmstring,
+ strlen(realmstring), pool));
+ hexname = svn_checksum_to_cstring(checksum, pool);
+
+ *path = svn_dirent_join(authdir_path, hexname, pool);
+ }
+ else
+ *path = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_config_read_auth_data(apr_hash_t **hash,
+ const char *cred_kind,
+ const char *realmstring,
+ const char *config_dir,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ const char *auth_path;
+
+ *hash = NULL;
+
+ SVN_ERR(svn_auth__file_path(&auth_path, cred_kind, realmstring, config_dir,
+ pool));
+ if (! auth_path)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_io_check_path(auth_path, &kind, pool));
+ if (kind == svn_node_file)
+ {
+ svn_stream_t *stream;
+
+ SVN_ERR_W(svn_stream_open_readonly(&stream, auth_path, pool, pool),
+ _("Unable to open auth file for reading"));
+
+ *hash = apr_hash_make(pool);
+
+ SVN_ERR_W(svn_hash_read2(*hash, stream, SVN_HASH_TERMINATOR, pool),
+ apr_psprintf(pool, _("Error parsing '%s'"),
+ svn_dirent_local_style(auth_path, pool)));
+
+ SVN_ERR(svn_stream_close(stream));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_config_write_auth_data(apr_hash_t *hash,
+ const char *cred_kind,
+ const char *realmstring,
+ const char *config_dir,
+ apr_pool_t *pool)
+{
+ apr_file_t *authfile = NULL;
+ svn_stream_t *stream;
+ const char *auth_path;
+
+ SVN_ERR(svn_auth__file_path(&auth_path, cred_kind, realmstring, config_dir,
+ pool));
+ if (! auth_path)
+ return svn_error_create(SVN_ERR_NO_AUTH_FILE_PATH, NULL,
+ _("Unable to locate auth file"));
+
+ /* Add the realmstring to the hash, so programs (or users) can
+ verify exactly which set of credentials this file holds. */
+ svn_hash_sets(hash, SVN_CONFIG_REALMSTRING_KEY,
+ svn_string_create(realmstring, pool));
+
+ SVN_ERR_W(svn_io_file_open(&authfile, auth_path,
+ (APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BUFFERED),
+ APR_OS_DEFAULT, pool),
+ _("Unable to open auth file for writing"));
+
+ stream = svn_stream_from_aprfile2(authfile, FALSE, pool);
+ SVN_ERR_W(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool),
+ apr_psprintf(pool, _("Error writing hash to '%s'"),
+ svn_dirent_local_style(auth_path, pool)));
+
+ SVN_ERR(svn_stream_close(stream));
+
+ /* To be nice, remove the realmstring from the hash again, just in
+ case the caller wants their hash unchanged. */
+ svn_hash_sets(hash, SVN_CONFIG_REALMSTRING_KEY, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_config_walk_auth_data(const char *config_dir,
+ svn_config_auth_walk_func_t walk_func,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_pool_t *iterpool;
+ svn_boolean_t finished = FALSE;
+ const char *cred_kinds[] =
+ {
+ SVN_AUTH_CRED_SIMPLE,
+ SVN_AUTH_CRED_USERNAME,
+ SVN_AUTH_CRED_SSL_CLIENT_CERT,
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ SVN_AUTH_CRED_SSL_SERVER_TRUST,
+ NULL
+ };
+
+ if (! config_dir)
+ {
+ /* Can't locate the cache to clear */
+ return SVN_NO_ERROR;
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; cred_kinds[i]; i++)
+ {
+ const char *item_path;
+ const char *dir_path;
+ apr_hash_t *nodes;
+ svn_error_t *err;
+ apr_pool_t *itempool;
+ apr_hash_index_t *hi;
+
+ svn_pool_clear(iterpool);
+
+ if (finished)
+ break;
+
+ SVN_ERR(svn_auth__file_path(&item_path, cred_kinds[i], "!", config_dir,
+ iterpool));
+
+ dir_path = svn_dirent_dirname(item_path, iterpool);
+
+ err = svn_io_get_dirents3(&nodes, dir_path, TRUE, iterpool, iterpool);
+ if (err)
+ {
+ if (!APR_STATUS_IS_ENOENT(err->apr_err)
+ && !SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ continue;
+ }
+
+ itempool = svn_pool_create(iterpool);
+ for (hi = apr_hash_first(iterpool, nodes); hi; hi = apr_hash_next(hi))
+ {
+ svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi);
+ svn_stream_t *stream;
+ apr_hash_t *creds_hash;
+ const svn_string_t *realm;
+ svn_boolean_t delete_file = FALSE;
+
+ if (finished)
+ break;
+
+ if (dirent->kind != svn_node_file)
+ continue;
+
+ svn_pool_clear(itempool);
+
+ item_path = svn_dirent_join(dir_path, svn__apr_hash_index_key(hi),
+ itempool);
+
+ err = svn_stream_open_readonly(&stream, item_path,
+ itempool, itempool);
+ if (err)
+ {
+ /* Ignore this file. There are no credentials in it anyway */
+ svn_error_clear(err);
+ continue;
+ }
+
+ creds_hash = apr_hash_make(itempool);
+ err = svn_hash_read2(creds_hash, stream,
+ SVN_HASH_TERMINATOR, itempool);
+ err = svn_error_compose_create(err, svn_stream_close(stream));
+ if (err)
+ {
+ /* Ignore this file. There are no credentials in it anyway */
+ svn_error_clear(err);
+ continue;
+ }
+
+ realm = svn_hash_gets(creds_hash, SVN_CONFIG_REALMSTRING_KEY);
+ if (! realm)
+ continue; /* Not an auth file */
+
+ err = walk_func(&delete_file, walk_baton, cred_kinds[i],
+ realm->data, creds_hash, itempool);
+ if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ finished = TRUE;
+ }
+ SVN_ERR(err);
+
+ if (delete_file)
+ {
+ /* Delete the file on disk */
+ SVN_ERR(svn_io_remove_file2(item_path, TRUE, itempool));
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/config_file.c b/contrib/subversion/subversion/libsvn_subr/config_file.c
new file mode 100644
index 0000000..9d15f6b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/config_file.c
@@ -0,0 +1,1260 @@
+/*
+ * config_file.c : parsing configuration files
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_lib.h>
+#include <apr_env.h>
+#include "config_impl.h"
+#include "svn_io.h"
+#include "svn_types.h"
+#include "svn_dirent_uri.h"
+#include "svn_auth.h"
+#include "svn_subst.h"
+#include "svn_utf.h"
+#include "svn_pools.h"
+#include "svn_user.h"
+#include "svn_ctype.h"
+
+#include "svn_private_config.h"
+
+#ifdef __HAIKU__
+# include <FindDirectory.h>
+# include <StorageDefs.h>
+#endif
+
+/* Used to terminate lines in large multi-line string literals. */
+#define NL APR_EOL_STR
+
+
+/* File parsing context */
+typedef struct parse_context_t
+{
+ /* This config struct */
+ svn_config_t *cfg;
+
+ /* The stream struct */
+ svn_stream_t *stream;
+
+ /* The current line in the file */
+ int line;
+
+ /* Emulate an ungetc */
+ int ungotten_char;
+
+ /* Temporary strings */
+ svn_stringbuf_t *section;
+ svn_stringbuf_t *option;
+ svn_stringbuf_t *value;
+
+ /* Parser buffer for getc() to avoid call overhead into several libraries
+ for every character */
+ char parser_buffer[SVN_STREAM_CHUNK_SIZE]; /* Larger than most config files */
+ size_t buffer_pos; /* Current position within parser_buffer */
+ size_t buffer_size; /* parser_buffer contains this many bytes */
+} parse_context_t;
+
+
+
+/* Emulate getc() because streams don't support it.
+ *
+ * In order to be able to ungetc(), use the CXT instead of the stream
+ * to be able to store the 'ungotton' character.
+ *
+ */
+static APR_INLINE svn_error_t *
+parser_getc(parse_context_t *ctx, int *c)
+{
+ do
+ {
+ if (ctx->ungotten_char != EOF)
+ {
+ *c = ctx->ungotten_char;
+ ctx->ungotten_char = EOF;
+ }
+ else if (ctx->buffer_pos < ctx->buffer_size)
+ {
+ *c = ctx->parser_buffer[ctx->buffer_pos];
+ ctx->buffer_pos++;
+ }
+ else
+ {
+ ctx->buffer_pos = 0;
+ ctx->buffer_size = sizeof(ctx->parser_buffer);
+
+ SVN_ERR(svn_stream_read(ctx->stream, ctx->parser_buffer,
+ &(ctx->buffer_size)));
+
+ if (ctx->buffer_pos < ctx->buffer_size)
+ {
+ *c = ctx->parser_buffer[ctx->buffer_pos];
+ ctx->buffer_pos++;
+ }
+ else
+ *c = EOF;
+ }
+ }
+ while (*c == '\r');
+
+ return SVN_NO_ERROR;
+}
+
+/* Simplified version of parser_getc() to be used inside skipping loops.
+ * It will not check for 'ungotton' chars and may or may not ignore '\r'.
+ *
+ * In a 'while(cond) getc();' loop, the first iteration must call
+ * parser_getc to handle all the special cases. Later iterations should
+ * use parser_getc_plain for maximum performance.
+ */
+static APR_INLINE svn_error_t *
+parser_getc_plain(parse_context_t *ctx, int *c)
+{
+ if (ctx->buffer_pos < ctx->buffer_size)
+ {
+ *c = ctx->parser_buffer[ctx->buffer_pos];
+ ctx->buffer_pos++;
+
+ return SVN_NO_ERROR;
+ }
+
+ return parser_getc(ctx, c);
+}
+
+/* Emulate ungetc() because streams don't support it.
+ *
+ * Use CTX to store the ungotten character C.
+ */
+static APR_INLINE svn_error_t *
+parser_ungetc(parse_context_t *ctx, int c)
+{
+ ctx->ungotten_char = c;
+
+ return SVN_NO_ERROR;
+}
+
+/* Eat chars from STREAM until encounter non-whitespace, newline, or EOF.
+ Set *PCOUNT to the number of characters eaten, not counting the
+ last one, and return the last char read (the one that caused the
+ break). */
+static APR_INLINE svn_error_t *
+skip_whitespace(parse_context_t *ctx, int *c, int *pcount)
+{
+ int ch = 0;
+ int count = 0;
+
+ SVN_ERR(parser_getc(ctx, &ch));
+ while (svn_ctype_isspace(ch) && ch != '\n' && ch != EOF)
+ {
+ ++count;
+ SVN_ERR(parser_getc_plain(ctx, &ch));
+ }
+ *pcount = count;
+ *c = ch;
+ return SVN_NO_ERROR;
+}
+
+
+/* Skip to the end of the line (or file). Returns the char that ended
+ the line; the char is either EOF or newline. */
+static APR_INLINE svn_error_t *
+skip_to_eoln(parse_context_t *ctx, int *c)
+{
+ int ch;
+
+ SVN_ERR(parser_getc(ctx, &ch));
+ while (ch != '\n' && ch != EOF)
+ SVN_ERR(parser_getc_plain(ctx, &ch));
+
+ *c = ch;
+ return SVN_NO_ERROR;
+}
+
+
+/* Parse a single option value */
+static svn_error_t *
+parse_value(int *pch, parse_context_t *ctx)
+{
+ svn_boolean_t end_of_val = FALSE;
+ int ch;
+
+ /* Read the first line of the value */
+ svn_stringbuf_setempty(ctx->value);
+ SVN_ERR(parser_getc(ctx, &ch));
+ while (ch != EOF && ch != '\n')
+ /* last ch seen was ':' or '=' in parse_option. */
+ {
+ const char char_from_int = (char)ch;
+ svn_stringbuf_appendbyte(ctx->value, char_from_int);
+ SVN_ERR(parser_getc(ctx, &ch));
+ }
+ /* Leading and trailing whitespace is ignored. */
+ svn_stringbuf_strip_whitespace(ctx->value);
+
+ /* Look for any continuation lines. */
+ for (;;)
+ {
+
+ if (ch == EOF || end_of_val)
+ {
+ /* At end of file. The value is complete, there can't be
+ any continuation lines. */
+ svn_config_set(ctx->cfg, ctx->section->data,
+ ctx->option->data, ctx->value->data);
+ break;
+ }
+ else
+ {
+ int count;
+ ++ctx->line;
+ SVN_ERR(skip_whitespace(ctx, &ch, &count));
+
+ switch (ch)
+ {
+ case '\n':
+ /* The next line was empty. Ergo, it can't be a
+ continuation line. */
+ ++ctx->line;
+ end_of_val = TRUE;
+ continue;
+
+ case EOF:
+ /* This is also an empty line. */
+ end_of_val = TRUE;
+ continue;
+
+ default:
+ if (count == 0)
+ {
+ /* This line starts in the first column. That means
+ it's either a section, option or comment. Put
+ the char back into the stream, because it doesn't
+ belong to us. */
+ SVN_ERR(parser_ungetc(ctx, ch));
+ end_of_val = TRUE;
+ }
+ else
+ {
+ /* This is a continuation line. Read it. */
+ svn_stringbuf_appendbyte(ctx->value, ' ');
+
+ while (ch != EOF && ch != '\n')
+ {
+ const char char_from_int = (char)ch;
+ svn_stringbuf_appendbyte(ctx->value, char_from_int);
+ SVN_ERR(parser_getc(ctx, &ch));
+ }
+ /* Trailing whitespace is ignored. */
+ svn_stringbuf_strip_whitespace(ctx->value);
+ }
+ }
+ }
+ }
+
+ *pch = ch;
+ return SVN_NO_ERROR;
+}
+
+
+/* Parse a single option */
+static svn_error_t *
+parse_option(int *pch, parse_context_t *ctx, apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ int ch;
+
+ svn_stringbuf_setempty(ctx->option);
+ ch = *pch; /* Yes, the first char is relevant. */
+ while (ch != EOF && ch != ':' && ch != '=' && ch != '\n')
+ {
+ const char char_from_int = (char)ch;
+ svn_stringbuf_appendbyte(ctx->option, char_from_int);
+ SVN_ERR(parser_getc(ctx, &ch));
+ }
+
+ if (ch != ':' && ch != '=')
+ {
+ ch = EOF;
+ err = svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
+ "line %d: Option must end with ':' or '='",
+ ctx->line);
+ }
+ else
+ {
+ /* Whitespace around the name separator is ignored. */
+ svn_stringbuf_strip_whitespace(ctx->option);
+ err = parse_value(&ch, ctx);
+ }
+
+ *pch = ch;
+ return err;
+}
+
+
+/* Read chars until enounter ']', then skip everything to the end of
+ * the line. Set *PCH to the character that ended the line (either
+ * newline or EOF), and set CTX->section to the string of characters
+ * seen before ']'.
+ *
+ * This is meant to be called immediately after reading the '[' that
+ * starts a section name.
+ */
+static svn_error_t *
+parse_section_name(int *pch, parse_context_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ int ch;
+
+ svn_stringbuf_setempty(ctx->section);
+ SVN_ERR(parser_getc(ctx, &ch));
+ while (ch != EOF && ch != ']' && ch != '\n')
+ {
+ const char char_from_int = (char)ch;
+ svn_stringbuf_appendbyte(ctx->section, char_from_int);
+ SVN_ERR(parser_getc(ctx, &ch));
+ }
+
+ if (ch != ']')
+ {
+ ch = EOF;
+ err = svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
+ "line %d: Section header must end with ']'",
+ ctx->line);
+ }
+ else
+ {
+ /* Everything from the ']' to the end of the line is ignored. */
+ SVN_ERR(skip_to_eoln(ctx, &ch));
+ if (ch != EOF)
+ ++ctx->line;
+ }
+
+ *pch = ch;
+ return err;
+}
+
+
+svn_error_t *
+svn_config__sys_config_path(const char **path_p,
+ const char *fname,
+ apr_pool_t *pool)
+{
+ *path_p = NULL;
+
+ /* Note that even if fname is null, svn_dirent_join_many will DTRT. */
+
+#ifdef WIN32
+ {
+ const char *folder;
+ SVN_ERR(svn_config__win_config_path(&folder, TRUE, pool));
+ *path_p = svn_dirent_join_many(pool, folder,
+ SVN_CONFIG__SUBDIRECTORY, fname, NULL);
+ }
+
+#elif defined(__HAIKU__)
+ {
+ char folder[B_PATH_NAME_LENGTH];
+
+ status_t error = find_directory(B_COMMON_SETTINGS_DIRECTORY, -1, false,
+ folder, sizeof(folder));
+ if (error)
+ return SVN_NO_ERROR;
+
+ *path_p = svn_dirent_join_many(pool, folder,
+ SVN_CONFIG__SYS_DIRECTORY, fname, NULL);
+ }
+#else /* ! WIN32 && !__HAIKU__ */
+
+ *path_p = svn_dirent_join_many(pool, SVN_CONFIG__SYS_DIRECTORY, fname, NULL);
+
+#endif /* WIN32 */
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Exported interfaces. ***/
+
+
+svn_error_t *
+svn_config__parse_file(svn_config_t *cfg, const char *file,
+ svn_boolean_t must_exist, apr_pool_t *result_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_stream_t *stream;
+ apr_pool_t *scratch_pool = svn_pool_create(result_pool);
+
+ err = svn_stream_open_readonly(&stream, file, scratch_pool, scratch_pool);
+
+ if (! must_exist && err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ err = svn_config__parse_stream(cfg, stream, result_pool, scratch_pool);
+
+ if (err != SVN_NO_ERROR)
+ {
+ /* Add the filename to the error stack. */
+ err = svn_error_createf(err->apr_err, err,
+ "Error while parsing config file: %s:",
+ svn_dirent_local_style(file, scratch_pool));
+ }
+
+ /* Close the streams (and other cleanup): */
+ svn_pool_destroy(scratch_pool);
+
+ return err;
+}
+
+svn_error_t *
+svn_config__parse_stream(svn_config_t *cfg, svn_stream_t *stream,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ parse_context_t *ctx;
+ int ch, count;
+
+ ctx = apr_palloc(scratch_pool, sizeof(*ctx));
+
+ ctx->cfg = cfg;
+ ctx->stream = stream;
+ ctx->line = 1;
+ ctx->ungotten_char = EOF;
+ ctx->section = svn_stringbuf_create_empty(scratch_pool);
+ ctx->option = svn_stringbuf_create_empty(scratch_pool);
+ ctx->value = svn_stringbuf_create_empty(scratch_pool);
+ ctx->buffer_pos = 0;
+ ctx->buffer_size = 0;
+
+ do
+ {
+ SVN_ERR(skip_whitespace(ctx, &ch, &count));
+
+ switch (ch)
+ {
+ case '[': /* Start of section header */
+ if (count == 0)
+ SVN_ERR(parse_section_name(&ch, ctx, scratch_pool));
+ else
+ return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
+ "line %d: Section header"
+ " must start in the first column",
+ ctx->line);
+ break;
+
+ case '#': /* Comment */
+ if (count == 0)
+ {
+ SVN_ERR(skip_to_eoln(ctx, &ch));
+ ++(ctx->line);
+ }
+ else
+ return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
+ "line %d: Comment"
+ " must start in the first column",
+ ctx->line);
+ break;
+
+ case '\n': /* Empty line */
+ ++(ctx->line);
+ break;
+
+ case EOF: /* End of file or read error */
+ break;
+
+ default:
+ if (svn_stringbuf_isempty(ctx->section))
+ return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
+ "line %d: Section header expected",
+ ctx->line);
+ else if (count != 0)
+ return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
+ "line %d: Option expected",
+ ctx->line);
+ else
+ SVN_ERR(parse_option(&ch, ctx, scratch_pool));
+ break;
+ }
+ }
+ while (ch != EOF);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for ensure_auth_dirs: create SUBDIR under AUTH_DIR, iff
+ SUBDIR does not already exist, but ignore any errors. Use POOL for
+ temporary allocation. */
+static void
+ensure_auth_subdir(const char *auth_dir,
+ const char *subdir,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ const char *subdir_full_path;
+ svn_node_kind_t kind;
+
+ subdir_full_path = svn_dirent_join(auth_dir, subdir, pool);
+ err = svn_io_check_path(subdir_full_path, &kind, pool);
+ if (err || kind == svn_node_none)
+ {
+ svn_error_clear(err);
+ svn_error_clear(svn_io_dir_make(subdir_full_path, APR_OS_DEFAULT, pool));
+ }
+}
+
+/* Helper for svn_config_ensure: see if ~/.subversion/auth/ and its
+ subdirs exist, try to create them, but don't throw errors on
+ failure. PATH is assumed to be a path to the user's private config
+ directory. */
+static void
+ensure_auth_dirs(const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ const char *auth_dir;
+ svn_error_t *err;
+
+ /* Ensure ~/.subversion/auth/ */
+ auth_dir = svn_dirent_join(path, SVN_CONFIG__AUTH_SUBDIR, pool);
+ err = svn_io_check_path(auth_dir, &kind, pool);
+ if (err || kind == svn_node_none)
+ {
+ svn_error_clear(err);
+ /* 'chmod 700' permissions: */
+ err = svn_io_dir_make(auth_dir,
+ (APR_UREAD | APR_UWRITE | APR_UEXECUTE),
+ pool);
+ if (err)
+ {
+ /* Don't try making subdirs if we can't make the top-level dir. */
+ svn_error_clear(err);
+ return;
+ }
+ }
+
+ /* If a provider exists that wants to store credentials in
+ ~/.subversion, a subdirectory for the cred_kind must exist. */
+ ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_SIMPLE, pool);
+ ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_USERNAME, pool);
+ ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_SSL_SERVER_TRUST, pool);
+ ensure_auth_subdir(auth_dir, SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, pool);
+}
+
+
+svn_error_t *
+svn_config_ensure(const char *config_dir, apr_pool_t *pool)
+{
+ const char *path;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ /* Ensure that the user-specific config directory exists. */
+ SVN_ERR(svn_config_get_user_config_path(&path, config_dir, NULL, pool));
+
+ if (! path)
+ return SVN_NO_ERROR;
+
+ err = svn_io_check_resolved_path(path, &kind, pool);
+ if (err)
+ {
+ /* Don't throw an error, but don't continue. */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ if (kind == svn_node_none)
+ {
+ err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
+ if (err)
+ {
+ /* Don't throw an error, but don't continue. */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ }
+ else if (kind == svn_node_file)
+ {
+ /* Somebody put a file where the config directory should be.
+ Wacky. Let's bail. */
+ return SVN_NO_ERROR;
+ }
+
+ /* Else, there's a configuration directory. */
+
+ /* If we get errors trying to do things below, just stop and return
+ success. There's no _need_ to init a config directory if
+ something's preventing it. */
+
+ /** If non-existent, try to create a number of auth/ subdirectories. */
+ ensure_auth_dirs(path, pool);
+
+ /** Ensure that the `README.txt' file exists. **/
+ SVN_ERR(svn_config_get_user_config_path
+ (&path, config_dir, SVN_CONFIG__USR_README_FILE, pool));
+
+ if (! path) /* highly unlikely, since a previous call succeeded */
+ return SVN_NO_ERROR;
+
+ err = svn_io_check_path(path, &kind, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ if (kind == svn_node_none)
+ {
+ apr_file_t *f;
+ const char *contents =
+ "This directory holds run-time configuration information for Subversion" NL
+ "clients. The configuration files all share the same syntax, but you" NL
+ "should examine a particular file to learn what configuration" NL
+ "directives are valid for that file." NL
+ "" NL
+ "The syntax is standard INI format:" NL
+ "" NL
+ " - Empty lines, and lines starting with '#', are ignored." NL
+ " The first significant line in a file must be a section header." NL
+ "" NL
+ " - A section starts with a section header, which must start in" NL
+ " the first column:" NL
+ "" NL
+ " [section-name]" NL
+ "" NL
+ " - An option, which must always appear within a section, is a pair" NL
+ " (name, value). There are two valid forms for defining an" NL
+ " option, both of which must start in the first column:" NL
+ "" NL
+ " name: value" NL
+ " name = value" NL
+ "" NL
+ " Whitespace around the separator (:, =) is optional." NL
+ "" NL
+ " - Section and option names are case-insensitive, but case is" NL
+ " preserved." NL
+ "" NL
+ " - An option's value may be broken into several lines. The value" NL
+ " continuation lines must start with at least one whitespace." NL
+ " Trailing whitespace in the previous line, the newline character" NL
+ " and the leading whitespace in the continuation line is compressed" NL
+ " into a single space character." NL
+ "" NL
+ " - All leading and trailing whitespace around a value is trimmed," NL
+ " but the whitespace within a value is preserved, with the" NL
+ " exception of whitespace around line continuations, as" NL
+ " described above." NL
+ "" NL
+ " - When a value is a boolean, any of the following strings are" NL
+ " recognised as truth values (case does not matter):" NL
+ "" NL
+ " true false" NL
+ " yes no" NL
+ " on off" NL
+ " 1 0" NL
+ "" NL
+ " - When a value is a list, it is comma-separated. Again, the" NL
+ " whitespace around each element of the list is trimmed." NL
+ "" NL
+ " - Option values may be expanded within a value by enclosing the" NL
+ " option name in parentheses, preceded by a percent sign and" NL
+ " followed by an 's':" NL
+ "" NL
+ " %(name)s" NL
+ "" NL
+ " The expansion is performed recursively and on demand, during" NL
+ " svn_option_get. The name is first searched for in the same" NL
+ " section, then in the special [DEFAULT] section. If the name" NL
+ " is not found, the whole '%(name)s' placeholder is left" NL
+ " unchanged." NL
+ "" NL
+ " Any modifications to the configuration data invalidate all" NL
+ " previously expanded values, so that the next svn_option_get" NL
+ " will take the modifications into account." NL
+ "" NL
+ "The syntax of the configuration files is a subset of the one used by" NL
+ "Python's ConfigParser module; see" NL
+ "" NL
+ " http://www.python.org/doc/current/lib/module-ConfigParser.html" NL
+ "" NL
+ "Configuration data in the Windows registry" NL
+ "==========================================" NL
+ "" NL
+ "On Windows, configuration data may also be stored in the registry. The" NL
+ "functions svn_config_read and svn_config_merge will read from the" NL
+ "registry when passed file names of the form:" NL
+ "" NL
+ " REGISTRY:<hive>/path/to/config-key" NL
+ "" NL
+ "The REGISTRY: prefix must be in upper case. The <hive> part must be" NL
+ "one of:" NL
+ "" NL
+ " HKLM for HKEY_LOCAL_MACHINE" NL
+ " HKCU for HKEY_CURRENT_USER" NL
+ "" NL
+ "The values in config-key represent the options in the [DEFAULT] section."NL
+ "The keys below config-key represent other sections, and their values" NL
+ "represent the options. Only values of type REG_SZ whose name doesn't" NL
+ "start with a '#' will be used; other values, as well as the keys'" NL
+ "default values, will be ignored." NL
+ "" NL
+ "" NL
+ "File locations" NL
+ "==============" NL
+ "" NL
+ "Typically, Subversion uses two config directories, one for site-wide" NL
+ "configuration," NL
+ "" NL
+ " Unix:" NL
+ " /etc/subversion/servers" NL
+ " /etc/subversion/config" NL
+ " /etc/subversion/hairstyles" NL
+ " Windows:" NL
+ " %ALLUSERSPROFILE%\\Application Data\\Subversion\\servers" NL
+ " %ALLUSERSPROFILE%\\Application Data\\Subversion\\config" NL
+ " %ALLUSERSPROFILE%\\Application Data\\Subversion\\hairstyles" NL
+ " REGISTRY:HKLM\\Software\\Tigris.org\\Subversion\\Servers" NL
+ " REGISTRY:HKLM\\Software\\Tigris.org\\Subversion\\Config" NL
+ " REGISTRY:HKLM\\Software\\Tigris.org\\Subversion\\Hairstyles" NL
+ "" NL
+ "and one for per-user configuration:" NL
+ "" NL
+ " Unix:" NL
+ " ~/.subversion/servers" NL
+ " ~/.subversion/config" NL
+ " ~/.subversion/hairstyles" NL
+ " Windows:" NL
+ " %APPDATA%\\Subversion\\servers" NL
+ " %APPDATA%\\Subversion\\config" NL
+ " %APPDATA%\\Subversion\\hairstyles" NL
+ " REGISTRY:HKCU\\Software\\Tigris.org\\Subversion\\Servers" NL
+ " REGISTRY:HKCU\\Software\\Tigris.org\\Subversion\\Config" NL
+ " REGISTRY:HKCU\\Software\\Tigris.org\\Subversion\\Hairstyles" NL
+ "" NL;
+
+ err = svn_io_file_open(&f, path,
+ (APR_WRITE | APR_CREATE | APR_EXCL),
+ APR_OS_DEFAULT,
+ pool);
+
+ if (! err)
+ {
+ SVN_ERR(svn_io_file_write_full(f, contents,
+ strlen(contents), NULL, pool));
+ SVN_ERR(svn_io_file_close(f, pool));
+ }
+
+ svn_error_clear(err);
+ }
+
+ /** Ensure that the `servers' file exists. **/
+ SVN_ERR(svn_config_get_user_config_path
+ (&path, config_dir, SVN_CONFIG_CATEGORY_SERVERS, pool));
+
+ if (! path) /* highly unlikely, since a previous call succeeded */
+ return SVN_NO_ERROR;
+
+ err = svn_io_check_path(path, &kind, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ if (kind == svn_node_none)
+ {
+ apr_file_t *f;
+ const char *contents =
+ "### This file specifies server-specific parameters," NL
+ "### including HTTP proxy information, HTTP timeout settings," NL
+ "### and authentication settings." NL
+ "###" NL
+ "### The currently defined server options are:" NL
+ "### http-proxy-host Proxy host for HTTP connection" NL
+ "### http-proxy-port Port number of proxy host service" NL
+ "### http-proxy-username Username for auth to proxy service"NL
+ "### http-proxy-password Password for auth to proxy service"NL
+ "### http-proxy-exceptions List of sites that do not use proxy"
+ NL
+ "### http-timeout Timeout for HTTP requests in seconds"
+ NL
+ "### http-compression Whether to compress HTTP requests" NL
+ "### http-max-connections Maximum number of parallel server" NL
+ "### connections to use for any given" NL
+ "### HTTP operation." NL
+ "### neon-debug-mask Debug mask for Neon HTTP library" NL
+ "### ssl-authority-files List of files, each of a trusted CA"
+ NL
+ "### ssl-trust-default-ca Trust the system 'default' CAs" NL
+ "### ssl-client-cert-file PKCS#12 format client certificate file"
+ NL
+ "### ssl-client-cert-password Client Key password, if needed." NL
+ "### ssl-pkcs11-provider Name of PKCS#11 provider to use." NL
+ "### http-library Which library to use for http/https"
+ NL
+ "### connections." NL
+ "### http-bulk-updates Whether to request bulk update" NL
+ "### responses or to fetch each file" NL
+ "### in an individual request. " NL
+ "### store-passwords Specifies whether passwords used" NL
+ "### to authenticate against a" NL
+ "### Subversion server may be cached" NL
+ "### to disk in any way." NL
+#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
+ "### store-plaintext-passwords Specifies whether passwords may" NL
+ "### be cached on disk unencrypted." NL
+#endif
+ "### store-ssl-client-cert-pp Specifies whether passphrase used" NL
+ "### to authenticate against a client" NL
+ "### certificate may be cached to disk" NL
+ "### in any way" NL
+#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
+ "### store-ssl-client-cert-pp-plaintext" NL
+ "### Specifies whether client cert" NL
+ "### passphrases may be cached on disk" NL
+ "### unencrypted (i.e., as plaintext)." NL
+#endif
+ "### store-auth-creds Specifies whether any auth info" NL
+ "### (passwords, server certs, etc.)" NL
+ "### may be cached to disk." NL
+ "### username Specifies the default username." NL
+ "###" NL
+ "### Set store-passwords to 'no' to avoid storing passwords on disk" NL
+ "### in any way, including in password stores. It defaults to" NL
+ "### 'yes', but Subversion will never save your password to disk in" NL
+ "### plaintext unless explicitly configured to do so." NL
+ "### Note that this option only prevents saving of *new* passwords;" NL
+ "### it doesn't invalidate existing passwords. (To do that, remove" NL
+ "### the cache files by hand as described in the Subversion book.)" NL
+ "###" NL
+#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
+ "### Set store-plaintext-passwords to 'no' to avoid storing" NL
+ "### passwords in unencrypted form in the auth/ area of your config" NL
+ "### directory. Set it to 'yes' to allow Subversion to store" NL
+ "### unencrypted passwords in the auth/ area. The default is" NL
+ "### 'ask', which means that Subversion will ask you before" NL
+ "### saving a password to disk in unencrypted form. Note that" NL
+ "### this option has no effect if either 'store-passwords' or " NL
+ "### 'store-auth-creds' is set to 'no'." NL
+ "###" NL
+#endif
+ "### Set store-ssl-client-cert-pp to 'no' to avoid storing ssl" NL
+ "### client certificate passphrases in the auth/ area of your" NL
+ "### config directory. It defaults to 'yes', but Subversion will" NL
+ "### never save your passphrase to disk in plaintext unless" NL
+ "### explicitly configured to do so." NL
+ "###" NL
+ "### Note store-ssl-client-cert-pp only prevents the saving of *new*"NL
+ "### passphrases; it doesn't invalidate existing passphrases. To do"NL
+ "### that, remove the cache files by hand as described in the" NL
+ "### Subversion book at http://svnbook.red-bean.com/nightly/en/\\" NL
+ "### svn.serverconfig.netmodel.html\\" NL
+ "### #svn.serverconfig.netmodel.credcache" NL
+ "###" NL
+#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
+ "### Set store-ssl-client-cert-pp-plaintext to 'no' to avoid storing"NL
+ "### passphrases in unencrypted form in the auth/ area of your" NL
+ "### config directory. Set it to 'yes' to allow Subversion to" NL
+ "### store unencrypted passphrases in the auth/ area. The default" NL
+ "### is 'ask', which means that Subversion will prompt before" NL
+ "### saving a passphrase to disk in unencrypted form. Note that" NL
+ "### this option has no effect if either 'store-auth-creds' or " NL
+ "### 'store-ssl-client-cert-pp' is set to 'no'." NL
+ "###" NL
+#endif
+ "### Set store-auth-creds to 'no' to avoid storing any Subversion" NL
+ "### credentials in the auth/ area of your config directory." NL
+ "### Note that this includes SSL server certificates." NL
+ "### It defaults to 'yes'. Note that this option only prevents" NL
+ "### saving of *new* credentials; it doesn't invalidate existing" NL
+ "### caches. (To do that, remove the cache files by hand.)" NL
+ "###" NL
+ "### HTTP timeouts, if given, are specified in seconds. A timeout" NL
+ "### of 0, i.e. zero, causes a builtin default to be used." NL
+ "###" NL
+ "### Most users will not need to explicitly set the http-library" NL
+ "### option, but valid values for the option include:" NL
+ "### 'serf': Serf-based module (Subversion 1.5 - present)" NL
+ "### 'neon': Neon-based module (Subversion 1.0 - 1.7)" NL
+ "### Availability of these modules may depend on your specific" NL
+ "### Subversion distribution." NL
+ "###" NL
+ "### The commented-out examples below are intended only to" NL
+ "### demonstrate how to use this file; any resemblance to actual" NL
+ "### servers, living or dead, is entirely coincidental." NL
+ "" NL
+ "### In the 'groups' section, the URL of the repository you're" NL
+ "### trying to access is matched against the patterns on the right." NL
+ "### If a match is found, the server options are taken from the" NL
+ "### section with the corresponding name on the left." NL
+ "" NL
+ "[groups]" NL
+ "# group1 = *.collab.net" NL
+ "# othergroup = repository.blarggitywhoomph.com" NL
+ "# thirdgroup = *.example.com" NL
+ "" NL
+ "### Information for the first group:" NL
+ "# [group1]" NL
+ "# http-proxy-host = proxy1.some-domain-name.com" NL
+ "# http-proxy-port = 80" NL
+ "# http-proxy-username = blah" NL
+ "# http-proxy-password = doubleblah" NL
+ "# http-timeout = 60" NL
+ "# neon-debug-mask = 130" NL
+#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
+ "# store-plaintext-passwords = no" NL
+#endif
+ "# username = harry" NL
+ "" NL
+ "### Information for the second group:" NL
+ "# [othergroup]" NL
+ "# http-proxy-host = proxy2.some-domain-name.com" NL
+ "# http-proxy-port = 9000" NL
+ "# No username and password for the proxy, so use the defaults below."
+ NL
+ "" NL
+ "### You can set default parameters in the 'global' section." NL
+ "### These parameters apply if no corresponding parameter is set in" NL
+ "### a specifically matched group as shown above. Thus, if you go" NL
+ "### through the same proxy server to reach every site on the" NL
+ "### Internet, you probably just want to put that server's" NL
+ "### information in the 'global' section and not bother with" NL
+ "### 'groups' or any other sections." NL
+ "###" NL
+ "### Most people might want to configure password caching" NL
+ "### parameters here, but you can also configure them per server" NL
+ "### group (per-group settings override global settings)." NL
+ "###" NL
+ "### If you go through a proxy for all but a few sites, you can" NL
+ "### list those exceptions under 'http-proxy-exceptions'. This only"NL
+ "### overrides defaults, not explicitly matched server names." NL
+ "###" NL
+ "### 'ssl-authority-files' is a semicolon-delimited list of files," NL
+ "### each pointing to a PEM-encoded Certificate Authority (CA) " NL
+ "### SSL certificate. See details above for overriding security " NL
+ "### due to SSL." NL
+ "[global]" NL
+ "# http-proxy-exceptions = *.exception.com, www.internal-site.org" NL
+ "# http-proxy-host = defaultproxy.whatever.com" NL
+ "# http-proxy-port = 7000" NL
+ "# http-proxy-username = defaultusername" NL
+ "# http-proxy-password = defaultpassword" NL
+ "# http-compression = no" NL
+ "# No http-timeout, so just use the builtin default." NL
+ "# No neon-debug-mask, so neon debugging is disabled." NL
+ "# ssl-authority-files = /path/to/CAcert.pem;/path/to/CAcert2.pem" NL
+ "#" NL
+ "# Password / passphrase caching parameters:" NL
+ "# store-passwords = no" NL
+ "# store-ssl-client-cert-pp = no" NL
+#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
+ "# store-plaintext-passwords = no" NL
+ "# store-ssl-client-cert-pp-plaintext = no" NL
+#endif
+ ;
+
+ err = svn_io_file_open(&f, path,
+ (APR_WRITE | APR_CREATE | APR_EXCL),
+ APR_OS_DEFAULT,
+ pool);
+
+ if (! err)
+ {
+ SVN_ERR(svn_io_file_write_full(f, contents,
+ strlen(contents), NULL, pool));
+ SVN_ERR(svn_io_file_close(f, pool));
+ }
+
+ svn_error_clear(err);
+ }
+
+ /** Ensure that the `config' file exists. **/
+ SVN_ERR(svn_config_get_user_config_path
+ (&path, config_dir, SVN_CONFIG_CATEGORY_CONFIG, pool));
+
+ if (! path) /* highly unlikely, since a previous call succeeded */
+ return SVN_NO_ERROR;
+
+ err = svn_io_check_path(path, &kind, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ if (kind == svn_node_none)
+ {
+ apr_file_t *f;
+ const char *contents =
+ "### This file configures various client-side behaviors." NL
+ "###" NL
+ "### The commented-out examples below are intended to demonstrate" NL
+ "### how to use this file." NL
+ "" NL
+ "### Section for authentication and authorization customizations." NL
+ "[auth]" NL
+ "### Set password stores used by Subversion. They should be" NL
+ "### delimited by spaces or commas. The order of values determines" NL
+ "### the order in which password stores are used." NL
+ "### Valid password stores:" NL
+ "### gnome-keyring (Unix-like systems)" NL
+ "### kwallet (Unix-like systems)" NL
+ "### gpg-agent (Unix-like systems)" NL
+ "### keychain (Mac OS X)" NL
+ "### windows-cryptoapi (Windows)" NL
+#ifdef SVN_HAVE_KEYCHAIN_SERVICES
+ "# password-stores = keychain" NL
+#elif defined(WIN32) && !defined(__MINGW32__)
+ "# password-stores = windows-cryptoapi" NL
+#else
+ "# password-stores = gpg-agent,gnome-keyring,kwallet" NL
+#endif
+ "### To disable all password stores, use an empty list:" NL
+ "# password-stores =" NL
+#ifdef SVN_HAVE_KWALLET
+ "###" NL
+ "### Set KWallet wallet used by Subversion. If empty or unset," NL
+ "### then the default network wallet will be used." NL
+ "# kwallet-wallet =" NL
+ "###" NL
+ "### Include PID (Process ID) in Subversion application name when" NL
+ "### using KWallet. It defaults to 'no'." NL
+ "# kwallet-svn-application-name-with-pid = yes" NL
+#endif
+ "###" NL
+ "### Set ssl-client-cert-file-prompt to 'yes' to cause the client" NL
+ "### to prompt for a path to a client cert file when the server" NL
+ "### requests a client cert but no client cert file is found in the" NL
+ "### expected place (see the 'ssl-client-cert-file' option in the" NL
+ "### 'servers' configuration file). Defaults to 'no'." NL
+ "# ssl-client-cert-file-prompt = no" NL
+ "###" NL
+ "### The rest of the [auth] section in this file has been deprecated."
+ NL
+ "### Both 'store-passwords' and 'store-auth-creds' can now be" NL
+ "### specified in the 'servers' file in your config directory" NL
+ "### and are documented there. Anything specified in this section " NL
+ "### is overridden by settings specified in the 'servers' file." NL
+ "# store-passwords = no" NL
+ "# store-auth-creds = no" NL
+ "" NL
+ "### Section for configuring external helper applications." NL
+ "[helpers]" NL
+ "### Set editor-cmd to the command used to invoke your text editor." NL
+ "### This will override the environment variables that Subversion" NL
+ "### examines by default to find this information ($EDITOR, " NL
+ "### et al)." NL
+ "# editor-cmd = editor (vi, emacs, notepad, etc.)" NL
+ "### Set diff-cmd to the absolute path of your 'diff' program." NL
+ "### This will override the compile-time default, which is to use" NL
+ "### Subversion's internal diff implementation." NL
+ "# diff-cmd = diff_program (diff, gdiff, etc.)" NL
+ "### Diff-extensions are arguments passed to an external diff" NL
+ "### program or to Subversion's internal diff implementation." NL
+ "### Set diff-extensions to override the default arguments ('-u')." NL
+ "# diff-extensions = -u -p" NL
+ "### Set diff3-cmd to the absolute path of your 'diff3' program." NL
+ "### This will override the compile-time default, which is to use" NL
+ "### Subversion's internal diff3 implementation." NL
+ "# diff3-cmd = diff3_program (diff3, gdiff3, etc.)" NL
+ "### Set diff3-has-program-arg to 'yes' if your 'diff3' program" NL
+ "### accepts the '--diff-program' option." NL
+ "# diff3-has-program-arg = [yes | no]" NL
+ "### Set merge-tool-cmd to the command used to invoke your external" NL
+ "### merging tool of choice. Subversion will pass 5 arguments to" NL
+ "### the specified command: base theirs mine merged wcfile" NL
+ "# merge-tool-cmd = merge_command" NL
+ "" NL
+ "### Section for configuring tunnel agents." NL
+ "[tunnels]" NL
+ "### Configure svn protocol tunnel schemes here. By default, only" NL
+ "### the 'ssh' scheme is defined. You can define other schemes to" NL
+ "### be used with 'svn+scheme://hostname/path' URLs. A scheme" NL
+ "### definition is simply a command, optionally prefixed by an" NL
+ "### environment variable name which can override the command if it" NL
+ "### is defined. The command (or environment variable) may contain" NL
+ "### arguments, using standard shell quoting for arguments with" NL
+ "### spaces. The command will be invoked as:" NL
+ "### <command> <hostname> svnserve -t" NL
+ "### (If the URL includes a username, then the hostname will be" NL
+ "### passed to the tunnel agent as <user>@<hostname>.) If the" NL
+ "### built-in ssh scheme were not predefined, it could be defined" NL
+ "### as:" NL
+ "# ssh = $SVN_SSH ssh -q" NL
+ "### If you wanted to define a new 'rsh' scheme, to be used with" NL
+ "### 'svn+rsh:' URLs, you could do so as follows:" NL
+ "# rsh = rsh" NL
+ "### Or, if you wanted to specify a full path and arguments:" NL
+ "# rsh = /path/to/rsh -l myusername" NL
+ "### On Windows, if you are specifying a full path to a command," NL
+ "### use a forward slash (/) or a paired backslash (\\\\) as the" NL
+ "### path separator. A single backslash will be treated as an" NL
+ "### escape for the following character." NL
+ "" NL
+ "### Section for configuring miscellaneous Subversion options." NL
+ "[miscellany]" NL
+ "### Set global-ignores to a set of whitespace-delimited globs" NL
+ "### which Subversion will ignore in its 'status' output, and" NL
+ "### while importing or adding files and directories." NL
+ "### '*' matches leading dots, e.g. '*.rej' matches '.foo.rej'." NL
+ "# global-ignores = " SVN_CONFIG__DEFAULT_GLOBAL_IGNORES_LINE_1 NL
+ "# " SVN_CONFIG__DEFAULT_GLOBAL_IGNORES_LINE_2 NL
+ "### Set log-encoding to the default encoding for log messages" NL
+ "# log-encoding = latin1" NL
+ "### Set use-commit-times to make checkout/update/switch/revert" NL
+ "### put last-committed timestamps on every file touched." NL
+ "# use-commit-times = yes" NL
+ "### Set no-unlock to prevent 'svn commit' from automatically" NL
+ "### releasing locks on files." NL
+ "# no-unlock = yes" NL
+ "### Set mime-types-file to a MIME type registry file, used to" NL
+ "### provide hints to Subversion's MIME type auto-detection" NL
+ "### algorithm." NL
+ "# mime-types-file = /path/to/mime.types" NL
+ "### Set preserved-conflict-file-exts to a whitespace-delimited" NL
+ "### list of patterns matching file extensions which should be" NL
+ "### preserved in generated conflict file names. By default," NL
+ "### conflict files use custom extensions." NL
+ "# preserved-conflict-file-exts = doc ppt xls od?" NL
+ "### Set enable-auto-props to 'yes' to enable automatic properties" NL
+ "### for 'svn add' and 'svn import', it defaults to 'no'." NL
+ "### Automatic properties are defined in the section 'auto-props'." NL
+ "# enable-auto-props = yes" NL
+ "### Set interactive-conflicts to 'no' to disable interactive" NL
+ "### conflict resolution prompting. It defaults to 'yes'." NL
+ "# interactive-conflicts = no" NL
+ "### Set memory-cache-size to define the size of the memory cache" NL
+ "### used by the client when accessing a FSFS repository via" NL
+ "### ra_local (the file:// scheme). The value represents the number" NL
+ "### of MB used by the cache." NL
+ "# memory-cache-size = 16" NL
+ "" NL
+ "### Section for configuring automatic properties." NL
+ "[auto-props]" NL
+ "### The format of the entries is:" NL
+ "### file-name-pattern = propname[=value][;propname[=value]...]" NL
+ "### The file-name-pattern can contain wildcards (such as '*' and" NL
+ "### '?'). All entries which match (case-insensitively) will be" NL
+ "### applied to the file. Note that auto-props functionality" NL
+ "### must be enabled, which is typically done by setting the" NL
+ "### 'enable-auto-props' option." NL
+ "# *.c = svn:eol-style=native" NL
+ "# *.cpp = svn:eol-style=native" NL
+ "# *.h = svn:keywords=Author Date Id Rev URL;svn:eol-style=native" NL
+ "# *.dsp = svn:eol-style=CRLF" NL
+ "# *.dsw = svn:eol-style=CRLF" NL
+ "# *.sh = svn:eol-style=native;svn:executable" NL
+ "# *.txt = svn:eol-style=native;svn:keywords=Author Date Id Rev URL;"NL
+ "# *.png = svn:mime-type=image/png" NL
+ "# *.jpg = svn:mime-type=image/jpeg" NL
+ "# Makefile = svn:eol-style=native" NL
+ "" NL
+ "### Section for configuring working copies." NL
+ "[working-copy]" NL
+ "### Set to a list of the names of specific clients that should use" NL
+ "### exclusive SQLite locking of working copies. This increases the"NL
+ "### performance of the client but prevents concurrent access by" NL
+ "### other clients. Third-party clients may also support this" NL
+ "### option." NL
+ "### Possible values:" NL
+ "### svn (the command line client)" NL
+ "# exclusive-locking-clients =" NL
+ "### Set to true to enable exclusive SQLite locking of working" NL
+ "### copies by all clients using the 1.8 APIs. Enabling this may" NL
+ "### cause some clients to fail to work properly. This does not have"NL
+ "### to be set for exclusive-locking-clients to work." NL
+ "# exclusive-locking = false" NL;
+
+ err = svn_io_file_open(&f, path,
+ (APR_WRITE | APR_CREATE | APR_EXCL),
+ APR_OS_DEFAULT,
+ pool);
+
+ if (! err)
+ {
+ SVN_ERR(svn_io_file_write_full(f, contents,
+ strlen(contents), NULL, pool));
+ SVN_ERR(svn_io_file_close(f, pool));
+ }
+
+ svn_error_clear(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_config_get_user_config_path(const char **path,
+ const char *config_dir,
+ const char *fname,
+ apr_pool_t *pool)
+{
+ *path= NULL;
+
+ /* Note that even if fname is null, svn_dirent_join_many will DTRT. */
+
+ if (config_dir)
+ {
+ *path = svn_dirent_join_many(pool, config_dir, fname, NULL);
+ return SVN_NO_ERROR;
+ }
+
+#ifdef WIN32
+ {
+ const char *folder;
+ SVN_ERR(svn_config__win_config_path(&folder, FALSE, pool));
+ *path = svn_dirent_join_many(pool, folder,
+ SVN_CONFIG__SUBDIRECTORY, fname, NULL);
+ }
+
+#elif defined(__HAIKU__)
+ {
+ char folder[B_PATH_NAME_LENGTH];
+
+ status_t error = find_directory(B_USER_SETTINGS_DIRECTORY, -1, false,
+ folder, sizeof(folder));
+ if (error)
+ return SVN_NO_ERROR;
+
+ *path = svn_dirent_join_many(pool, folder,
+ SVN_CONFIG__USR_DIRECTORY, fname, NULL);
+ }
+#else /* ! WIN32 && !__HAIKU__ */
+
+ {
+ const char *homedir = svn_user_get_homedir(pool);
+ if (! homedir)
+ return SVN_NO_ERROR;
+ *path = svn_dirent_join_many(pool,
+ svn_dirent_canonicalize(homedir, pool),
+ SVN_CONFIG__USR_DIRECTORY, fname, NULL);
+ }
+#endif /* WIN32 */
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_subr/config_impl.h b/contrib/subversion/subversion/libsvn_subr/config_impl.h
new file mode 100644
index 0000000..a3ab8fa
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/config_impl.h
@@ -0,0 +1,161 @@
+/*
+ * config_impl.h : private header for the config file implementation.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#ifndef SVN_LIBSVN_SUBR_CONFIG_IMPL_H
+#define SVN_LIBSVN_SUBR_CONFIG_IMPL_H
+
+#define APR_WANT_STDIO
+#include <apr_want.h>
+
+#include <apr_hash.h>
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_io.h"
+#include "svn_config.h"
+#include "svn_private_config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* The configuration data. This is a superhash of sections and options. */
+struct svn_config_t
+{
+ /* Table of cfg_section_t's. */
+ apr_hash_t *sections;
+
+ /* Pool for hash tables, table entries and unexpanded values */
+ apr_pool_t *pool;
+
+ /* Pool for expanded values -- this is separate, so that we can
+ clear it when modifying the config data. */
+ apr_pool_t *x_pool;
+
+ /* Indicates that some values in the configuration have been expanded. */
+ svn_boolean_t x_values;
+
+ /* Temporary string used for lookups. (Using a stringbuf so that
+ frequent resetting is efficient.) */
+ svn_stringbuf_t *tmp_key;
+
+ /* Temporary value used for expanded default values in svn_config_get.
+ (Using a stringbuf so that frequent resetting is efficient.) */
+ svn_stringbuf_t *tmp_value;
+
+ /* Specifies whether section names are populated case sensitively. */
+ svn_boolean_t section_names_case_sensitive;
+
+ /* Specifies whether option names are populated case sensitively. */
+ svn_boolean_t option_names_case_sensitive;
+};
+
+
+/* Read sections and options from a file. */
+svn_error_t *svn_config__parse_file(svn_config_t *cfg,
+ const char *file,
+ svn_boolean_t must_exist,
+ apr_pool_t *pool);
+
+/* Read sections and options from a stream. */
+svn_error_t *svn_config__parse_stream(svn_config_t *cfg,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* The name of the magic [DEFAULT] section. */
+#define SVN_CONFIG__DEFAULT_SECTION "DEFAULT"
+
+
+#ifdef WIN32
+/* Get the common or user-specific AppData folder */
+svn_error_t *svn_config__win_config_path(const char **folder,
+ int system_path,
+ apr_pool_t *pool);
+
+/* Read sections and options from the Windows Registry. */
+svn_error_t *svn_config__parse_registry(svn_config_t *cfg,
+ const char *file,
+ svn_boolean_t must_exist,
+ apr_pool_t *pool);
+
+/* ### It's unclear to me whether this registry stuff should get the
+ double underscore or not, and if so, where the extra underscore
+ would go. Thoughts? -kff */
+# define SVN_REGISTRY_PREFIX "REGISTRY:"
+# define SVN_REGISTRY_PREFIX_LEN ((sizeof(SVN_REGISTRY_PREFIX)) - 1)
+# define SVN_REGISTRY_HKLM "HKLM\\"
+# define SVN_REGISTRY_HKLM_LEN ((sizeof(SVN_REGISTRY_HKLM)) - 1)
+# define SVN_REGISTRY_HKCU "HKCU\\"
+# define SVN_REGISTRY_HKCU_LEN ((sizeof(SVN_REGISTRY_HKCU)) - 1)
+# define SVN_REGISTRY_PATH "Software\\Tigris.org\\Subversion\\"
+# define SVN_REGISTRY_PATH_LEN ((sizeof(SVN_REGISTRY_PATH)) - 1)
+# define SVN_REGISTRY_SYS_CONFIG_PATH \
+ SVN_REGISTRY_PREFIX \
+ SVN_REGISTRY_HKLM \
+ SVN_REGISTRY_PATH
+# define SVN_REGISTRY_USR_CONFIG_PATH \
+ SVN_REGISTRY_PREFIX \
+ SVN_REGISTRY_HKCU \
+ SVN_REGISTRY_PATH
+#endif /* WIN32 */
+
+/* System-wide and configuration subdirectory names.
+ NOTE: Don't use these directly; call svn_config__sys_config_path()
+ or svn_config_get_user_config_path() instead. */
+#ifdef WIN32
+# define SVN_CONFIG__SUBDIRECTORY "Subversion"
+#elif defined __HAIKU__ /* HAIKU */
+# define SVN_CONFIG__SYS_DIRECTORY "subversion"
+# define SVN_CONFIG__USR_DIRECTORY "subversion"
+#else /* ! WIN32 && ! __HAIKU__ */
+# define SVN_CONFIG__SYS_DIRECTORY "/etc/subversion"
+# define SVN_CONFIG__USR_DIRECTORY ".subversion"
+#endif /* WIN32 */
+
+/* The description/instructions file in the config directory. */
+#define SVN_CONFIG__USR_README_FILE "README.txt"
+
+/* The name of the main authentication subdir in the config directory */
+#define SVN_CONFIG__AUTH_SUBDIR "auth"
+
+/* Set *PATH_P to the path to config file FNAME in the system
+ configuration area, allocated in POOL. If FNAME is NULL, set
+ *PATH_P to the directory name of the system config area, either
+ allocated in POOL or a static constant string.
+
+ If the system configuration area cannot be located (possible under
+ Win32), set *PATH_P to NULL regardless of FNAME. */
+svn_error_t *
+svn_config__sys_config_path(const char **path_p,
+ const char *fname,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_SUBR_CONFIG_IMPL_H */
diff --git a/contrib/subversion/subversion/libsvn_subr/config_win.c b/contrib/subversion/subversion/libsvn_subr/config_win.c
new file mode 100644
index 0000000..0a15129
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/config_win.c
@@ -0,0 +1,259 @@
+/*
+ * config_win.c : parsing configuration data from the registry
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_private_config.h"
+
+#ifdef WIN32
+/* We must include windows.h ourselves or apr.h includes it for us with
+ many ignore options set. Including Winsock is required to resolve IPv6
+ compilation errors. APR_HAVE_IPV6 is only defined after including
+ apr.h, so we can't detect this case here. */
+
+#define WIN32_LEAN_AND_MEAN
+/* winsock2.h includes windows.h */
+#include <winsock2.h>
+#include <Ws2tcpip.h>
+
+#include <shlobj.h>
+
+#include <apr_file_info.h>
+
+#include "svn_error.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_utf.h"
+
+svn_error_t *
+svn_config__win_config_path(const char **folder, int system_path,
+ apr_pool_t *pool)
+{
+ /* ### Adding CSIDL_FLAG_CREATE here, because those folders really
+ must exist. I'm not too sure about the SHGFP_TYPE_CURRENT
+ semancics, though; maybe we should use ..._DEFAULT instead? */
+ const int csidl = ((system_path ? CSIDL_COMMON_APPDATA : CSIDL_APPDATA)
+ | CSIDL_FLAG_CREATE);
+
+ WCHAR folder_ucs2[MAX_PATH];
+ int inwords, outbytes, outlength;
+ char *folder_utf8;
+
+ if (S_OK != SHGetFolderPathW(NULL, csidl, NULL, SHGFP_TYPE_CURRENT,
+ folder_ucs2))
+ return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
+ (system_path
+ ? "Can't determine the system config path"
+ : "Can't determine the user's config path"));
+
+ /* ### When mapping from UCS-2 to UTF-8, we need at most 3 bytes
+ per wide char, plus extra space for the nul terminator. */
+ inwords = lstrlenW(folder_ucs2);
+ outbytes = outlength = 3 * (inwords + 1);
+
+ folder_utf8 = apr_palloc(pool, outlength);
+
+ outbytes = WideCharToMultiByte(CP_UTF8, 0, folder_ucs2, inwords,
+ folder_utf8, outbytes, NULL, NULL);
+
+ if (outbytes == 0)
+ return svn_error_wrap_apr(apr_get_os_error(),
+ "Can't convert config path to UTF-8");
+
+ /* Note that WideCharToMultiByte does _not_ terminate the
+ outgoing buffer. */
+ folder_utf8[outbytes] = '\0';
+ *folder = folder_utf8;
+
+ return SVN_NO_ERROR;
+}
+
+
+#include "config_impl.h"
+
+/* ### These constants are insanely large, but (a) we want to avoid
+ reallocating strings if possible, and (b) the realloc logic might
+ not actually work -- you never know with Win32 ... */
+#define SVN_REG_DEFAULT_NAME_SIZE 2048
+#define SVN_REG_DEFAULT_VALUE_SIZE 8192
+
+static svn_error_t *
+parse_section(svn_config_t *cfg, HKEY hkey, const char *section,
+ svn_stringbuf_t *option, svn_stringbuf_t *value)
+{
+ DWORD option_len, type, index;
+ LONG err;
+
+ /* Start with a reasonable size for the buffers. */
+ svn_stringbuf_ensure(option, SVN_REG_DEFAULT_NAME_SIZE);
+ svn_stringbuf_ensure(value, SVN_REG_DEFAULT_VALUE_SIZE);
+ for (index = 0; ; ++index)
+ {
+ option_len = (DWORD)option->blocksize;
+ err = RegEnumValue(hkey, index, option->data, &option_len,
+ NULL, &type, NULL, NULL);
+ if (err == ERROR_NO_MORE_ITEMS)
+ break;
+ if (err == ERROR_INSUFFICIENT_BUFFER)
+ {
+ svn_stringbuf_ensure(option, option_len);
+ err = RegEnumValue(hkey, index, option->data, &option_len,
+ NULL, &type, NULL, NULL);
+ }
+ if (err != ERROR_SUCCESS)
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ "Can't enumerate registry values");
+
+ /* Ignore option names that start with '#', see
+ http://subversion.tigris.org/issues/show_bug.cgi?id=671 */
+ if (type == REG_SZ && option->data[0] != '#')
+ {
+ DWORD value_len = (DWORD)value->blocksize;
+ err = RegQueryValueEx(hkey, option->data, NULL, NULL,
+ (LPBYTE)value->data, &value_len);
+ if (err == ERROR_MORE_DATA)
+ {
+ svn_stringbuf_ensure(value, value_len);
+ err = RegQueryValueEx(hkey, option->data, NULL, NULL,
+ (LPBYTE)value->data, &value_len);
+ }
+ if (err != ERROR_SUCCESS)
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ "Can't read registry value data");
+
+ svn_config_set(cfg, section, option->data, value->data);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Exported interface. ***/
+
+svn_error_t *
+svn_config__parse_registry(svn_config_t *cfg, const char *file,
+ svn_boolean_t must_exist, apr_pool_t *pool)
+{
+ apr_pool_t *subpool;
+ svn_stringbuf_t *section, *option, *value;
+ svn_error_t *svn_err = SVN_NO_ERROR;
+ HKEY base_hkey, hkey;
+ DWORD index;
+ LONG err;
+
+ if (0 == strncmp(file, SVN_REGISTRY_HKLM, SVN_REGISTRY_HKLM_LEN))
+ {
+ base_hkey = HKEY_LOCAL_MACHINE;
+ file += SVN_REGISTRY_HKLM_LEN;
+ }
+ else if (0 == strncmp(file, SVN_REGISTRY_HKCU, SVN_REGISTRY_HKCU_LEN))
+ {
+ base_hkey = HKEY_CURRENT_USER;
+ file += SVN_REGISTRY_HKCU_LEN;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
+ "Unrecognised registry path '%s'",
+ svn_dirent_local_style(file, pool));
+ }
+
+ err = RegOpenKeyEx(base_hkey, file, 0,
+ KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE,
+ &hkey);
+ if (err != ERROR_SUCCESS)
+ {
+ const int is_enoent = APR_STATUS_IS_ENOENT(APR_FROM_OS_ERROR(err));
+ if (!is_enoent)
+ return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
+ "Can't open registry key '%s'",
+ svn_dirent_local_style(file, pool));
+ else if (must_exist && is_enoent)
+ return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
+ "Can't find registry key '%s'",
+ svn_dirent_local_style(file, pool));
+ else
+ return SVN_NO_ERROR;
+ }
+
+
+ subpool = svn_pool_create(pool);
+ section = svn_stringbuf_create_empty(subpool);
+ option = svn_stringbuf_create_empty(subpool);
+ value = svn_stringbuf_create_empty(subpool);
+
+ /* The top-level values belong to the [DEFAULT] section */
+ svn_err = parse_section(cfg, hkey, SVN_CONFIG__DEFAULT_SECTION,
+ option, value);
+ if (svn_err)
+ goto cleanup;
+
+ /* Now enumerate the rest of the keys. */
+ svn_stringbuf_ensure(section, SVN_REG_DEFAULT_NAME_SIZE);
+ for (index = 0; ; ++index)
+ {
+ DWORD section_len = (DWORD)section->blocksize;
+ HKEY sub_hkey;
+
+ err = RegEnumKeyEx(hkey, index, section->data, &section_len,
+ NULL, NULL, NULL, NULL);
+ if (err == ERROR_NO_MORE_ITEMS)
+ break;
+ if (err == ERROR_MORE_DATA)
+ {
+ svn_stringbuf_ensure(section, section_len);
+ err = RegEnumKeyEx(hkey, index, section->data, &section_len,
+ NULL, NULL, NULL, NULL);
+ }
+ if (err != ERROR_SUCCESS)
+ {
+ svn_err = svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ "Can't enumerate registry keys");
+ goto cleanup;
+ }
+
+ err = RegOpenKeyEx(hkey, section->data, 0,
+ KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE,
+ &sub_hkey);
+ if (err != ERROR_SUCCESS)
+ {
+ svn_err = svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ "Can't open existing subkey");
+ goto cleanup;
+ }
+
+ svn_err = parse_section(cfg, sub_hkey, section->data, option, value);
+ RegCloseKey(sub_hkey);
+ if (svn_err)
+ goto cleanup;
+ }
+
+ cleanup:
+ RegCloseKey(hkey);
+ svn_pool_destroy(subpool);
+ return svn_err;
+}
+
+#endif /* WIN32 */
diff --git a/contrib/subversion/subversion/libsvn_subr/crypto.c b/contrib/subversion/subversion/libsvn_subr/crypto.c
new file mode 100644
index 0000000..f3611a1e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/crypto.c
@@ -0,0 +1,705 @@
+/*
+ * crypto.c : cryptographic routines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "crypto.h"
+
+#ifdef SVN_HAVE_CRYPTO
+#include <apr_random.h>
+#include <apr_crypto.h>
+#endif /* SVN_HAVE_CRYPTO */
+
+#include "svn_types.h"
+#include "svn_checksum.h"
+
+#include "svn_private_config.h"
+#include "private/svn_atomic.h"
+
+
+/* 1000 iterations is the recommended minimum, per RFC 2898, section 4.2. */
+#define NUM_ITERATIONS 1000
+
+
+/* Size (in bytes) of the random data we'll prepend to encrypted data. */
+#define RANDOM_PREFIX_LEN 4
+
+
+/* A structure for containing Subversion's cryptography-related bits
+ (so we can avoid passing around APR-isms outside this module). */
+struct svn_crypto__ctx_t {
+#ifdef SVN_HAVE_CRYPTO
+ apr_crypto_t *crypto; /* APR cryptography context. */
+
+#if 0
+ /* ### For now, we will use apr_generate_random_bytes(). If we need
+ ### more strength, then we can set this member using
+ ### apr_random_standard_new(), then use
+ ### apr_generate_random_bytes() to generate entropy for seeding
+ ### apr_random_t. See httpd/server/core.c:ap_init_rng() */
+ apr_random_t *rand;
+#endif /* 0 */
+#else /* SVN_HAVE_CRYPTO */
+ int unused_but_required_to_satisfy_c_compilers;
+#endif /* SVN_HAVE_CRYPTO */
+};
+
+
+
+/*** Helper Functions ***/
+#ifdef SVN_HAVE_CRYPTO
+
+
+/* One-time initialization of the cryptography subsystem. */
+static volatile svn_atomic_t crypto_init_state = 0;
+
+
+#define CRYPTO_INIT(scratch_pool) \
+ SVN_ERR(svn_atomic__init_once(&crypto_init_state, \
+ crypto_init, NULL, (scratch_pool)))
+
+
+/* Initialize the APR cryptography subsystem (if available), using
+ ANY_POOL's ancestor root pool for the registration of cleanups,
+ shutdowns, etc. */
+/* Don't call this function directly! Use svn_atomic__init_once(). */
+static svn_error_t *
+crypto_init(void *baton, apr_pool_t *any_pool)
+{
+ /* NOTE: this function will locate the topmost ancestor of ANY_POOL
+ for its cleanup handlers. We don't have to worry about ANY_POOL
+ being cleared. */
+ apr_status_t apr_err = apr_crypto_init(any_pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Failed to initialize cryptography "
+ "subsystem"));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* If APU_ERR is non-NULL, create and return a Subversion error using
+ APR_ERR and APU_ERR. */
+static svn_error_t *
+err_from_apu_err(apr_status_t apr_err,
+ const apu_err_t *apu_err)
+{
+ if (apu_err)
+ return svn_error_createf(apr_err, NULL,
+ _("code (%d), reason (\"%s\"), msg (\"%s\")"),
+ apu_err->rc,
+ apu_err->reason ? apu_err->reason : "",
+ apu_err->msg ? apu_err->msg : "");
+ return SVN_NO_ERROR;
+}
+
+
+/* Generate a Subversion error which describes the state reflected by
+ APR_ERR and any crypto errors registered with CTX. */
+static svn_error_t *
+crypto_error_create(svn_crypto__ctx_t *ctx,
+ apr_status_t apr_err,
+ const char *msg)
+{
+ const apu_err_t *apu_err;
+ apr_status_t rv = apr_crypto_error(&apu_err, ctx->crypto);
+ svn_error_t *child;
+
+ /* Ugh. The APIs are a bit slippery, so be wary. */
+ if (apr_err == APR_SUCCESS)
+ apr_err = APR_EGENERAL;
+
+ if (rv == APR_SUCCESS)
+ child = err_from_apu_err(apr_err, apu_err);
+ else
+ child = svn_error_wrap_apr(rv, _("Fetching error from APR"));
+
+ return svn_error_create(apr_err, child, msg);
+}
+
+
+/* Set RAND_BYTES to a block of bytes containing random data RAND_LEN
+ long and allocated from RESULT_POOL. */
+static svn_error_t *
+get_random_bytes(const unsigned char **rand_bytes,
+ svn_crypto__ctx_t *ctx,
+ apr_size_t rand_len,
+ apr_pool_t *result_pool)
+{
+ apr_status_t apr_err;
+ unsigned char *bytes;
+
+ bytes = apr_palloc(result_pool, rand_len);
+ apr_err = apr_generate_random_bytes(bytes, rand_len);
+ if (apr_err != APR_SUCCESS)
+ return svn_error_wrap_apr(apr_err, _("Error obtaining random data"));
+
+ *rand_bytes = bytes;
+ return SVN_NO_ERROR;
+}
+
+
+/* Return an svn_string_t allocated from RESULT_POOL, with its .data
+ and .len members set to DATA and LEN, respective.
+
+ WARNING: No lifetime management of DATA is offered here, so you
+ probably want to ensure that that information is allocated in a
+ sufficiently long-lived pool (such as, for example, RESULT_POOL). */
+static const svn_string_t *
+wrap_as_string(const unsigned char *data,
+ apr_size_t len,
+ apr_pool_t *result_pool)
+{
+ svn_string_t *s = apr_palloc(result_pool, sizeof(*s));
+
+ s->data = (const char *)data; /* better already be in RESULT_POOL */
+ s->len = len;
+ return s;
+}
+
+
+#endif /* SVN_HAVE_CRYPTO */
+
+
+
+/*** Semi-public APIs ***/
+
+/* Return TRUE iff Subversion's cryptographic support is available. */
+svn_boolean_t svn_crypto__is_available(void)
+{
+#ifdef SVN_HAVE_CRYPTO
+ return TRUE;
+#else /* SVN_HAVE_CRYPTO */
+ return FALSE;
+#endif /* SVN_HAVE_CRYPTO */
+}
+
+
+/* Set CTX to a Subversion cryptography context allocated from
+ RESULT_POOL. */
+svn_error_t *
+svn_crypto__context_create(svn_crypto__ctx_t **ctx,
+ apr_pool_t *result_pool)
+{
+#ifdef SVN_HAVE_CRYPTO
+ apr_status_t apr_err;
+ const apu_err_t *apu_err = NULL;
+ apr_crypto_t *apr_crypto;
+ const apr_crypto_driver_t *driver;
+
+ CRYPTO_INIT(result_pool);
+
+ /* Load the crypto driver.
+
+ ### TODO: For the sake of flexibility, should we use
+ ### APU_CRYPTO_RECOMMENDED_DRIVER instead of hard coding
+ ### "openssl" here?
+
+ NOTE: Potential bugs in get_driver() imply we might get
+ APR_SUCCESS and NULL. Sigh. Just be a little more careful in
+ error generation here. */
+ apr_err = apr_crypto_get_driver(&driver, "openssl", NULL, &apu_err,
+ result_pool);
+ if (apr_err != APR_SUCCESS)
+ return svn_error_create(apr_err, err_from_apu_err(apr_err, apu_err),
+ _("OpenSSL crypto driver error"));
+ if (driver == NULL)
+ return svn_error_create(APR_EGENERAL,
+ err_from_apu_err(APR_EGENERAL, apu_err),
+ _("Bad return value while loading crypto "
+ "driver"));
+
+ apr_err = apr_crypto_make(&apr_crypto, driver, NULL, result_pool);
+ if (apr_err != APR_SUCCESS || apr_crypto == NULL)
+ return svn_error_create(apr_err, NULL,
+ _("Error creating OpenSSL crypto context"));
+
+ /* Allocate and initialize our crypto context. */
+ *ctx = apr_palloc(result_pool, sizeof(**ctx));
+ (*ctx)->crypto = apr_crypto;
+
+ return SVN_NO_ERROR;
+#else /* SVN_HAVE_CRYPTO */
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ "Cryptographic support is not available");
+#endif /* SVN_HAVE_CRYPTO */
+}
+
+
+svn_error_t *
+svn_crypto__encrypt_password(const svn_string_t **ciphertext,
+ const svn_string_t **iv,
+ const svn_string_t **salt,
+ svn_crypto__ctx_t *ctx,
+ const char *password,
+ const svn_string_t *master,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+#ifdef SVN_HAVE_CRYPTO
+ svn_error_t *err = SVN_NO_ERROR;
+ const unsigned char *salt_vector;
+ const unsigned char *iv_vector;
+ apr_size_t iv_len;
+ apr_crypto_key_t *key = NULL;
+ apr_status_t apr_err;
+ const unsigned char *prefix;
+ apr_crypto_block_t *block_ctx = NULL;
+ apr_size_t block_size;
+ unsigned char *assembled;
+ apr_size_t password_len, assembled_len = 0;
+ apr_size_t result_len;
+ unsigned char *result;
+ apr_size_t ignored_result_len = 0;
+
+ SVN_ERR_ASSERT(ctx != NULL);
+
+ /* Generate the salt. */
+#define SALT_LEN 8
+ SVN_ERR(get_random_bytes(&salt_vector, ctx, SALT_LEN, result_pool));
+
+ /* Initialize the passphrase. */
+ apr_err = apr_crypto_passphrase(&key, &iv_len,
+ master->data, master->len,
+ salt_vector, SALT_LEN,
+ APR_KEY_AES_256, APR_MODE_CBC,
+ FALSE /* doPad */, NUM_ITERATIONS,
+ ctx->crypto,
+ scratch_pool);
+ if (apr_err != APR_SUCCESS)
+ return svn_error_trace(crypto_error_create(
+ ctx, apr_err,
+ _("Error creating derived key")));
+ if (! key)
+ return svn_error_create(APR_EGENERAL, NULL,
+ _("Error creating derived key"));
+ if (iv_len == 0)
+ return svn_error_create(APR_EGENERAL, NULL,
+ _("Unexpected IV length returned"));
+
+ /* Generate the proper length IV. */
+ SVN_ERR(get_random_bytes(&iv_vector, ctx, iv_len, result_pool));
+
+ /* Initialize block encryption. */
+ apr_err = apr_crypto_block_encrypt_init(&block_ctx, &iv_vector, key,
+ &block_size, scratch_pool);
+ if ((apr_err != APR_SUCCESS) || (! block_ctx))
+ return svn_error_trace(crypto_error_create(
+ ctx, apr_err,
+ _("Error initializing block encryption")));
+
+ /* Generate a 4-byte prefix. */
+ SVN_ERR(get_random_bytes(&prefix, ctx, RANDOM_PREFIX_LEN, scratch_pool));
+
+ /* Combine our prefix, original password, and appropriate padding.
+ We won't bother padding if the prefix and password combined
+ perfectly align on the block boundary. If they don't,
+ however, we'll drop a NUL byte after the password and pad with
+ random stuff after that to the block boundary. */
+ password_len = strlen(password);
+ assembled_len = RANDOM_PREFIX_LEN + password_len;
+ if ((assembled_len % block_size) == 0)
+ {
+ assembled = apr_palloc(scratch_pool, assembled_len);
+ memcpy(assembled, prefix, RANDOM_PREFIX_LEN);
+ memcpy(assembled + RANDOM_PREFIX_LEN, password, password_len);
+ }
+ else
+ {
+ const unsigned char *padding;
+ apr_size_t pad_len = block_size - (assembled_len % block_size) - 1;
+
+ SVN_ERR(get_random_bytes(&padding, ctx, pad_len, scratch_pool));
+ assembled_len = assembled_len + 1 + pad_len;
+ assembled = apr_palloc(scratch_pool, assembled_len);
+ memcpy(assembled, prefix, RANDOM_PREFIX_LEN);
+ memcpy(assembled + RANDOM_PREFIX_LEN, password, password_len);
+ *(assembled + RANDOM_PREFIX_LEN + password_len) = '\0';
+ memcpy(assembled + RANDOM_PREFIX_LEN + password_len + 1,
+ padding, pad_len);
+ }
+
+ /* Get the length that we need to allocate. */
+ apr_err = apr_crypto_block_encrypt(NULL, &result_len, assembled,
+ assembled_len, block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error fetching result length"));
+ goto cleanup;
+ }
+
+ /* Allocate our result buffer. */
+ result = apr_palloc(result_pool, result_len);
+
+ /* Encrypt the block. */
+ apr_err = apr_crypto_block_encrypt(&result, &result_len, assembled,
+ assembled_len, block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error during block encryption"));
+ goto cleanup;
+ }
+
+ /* Finalize the block encryption. Since we padded everything, this should
+ not produce any more encrypted output. */
+ apr_err = apr_crypto_block_encrypt_finish(NULL,
+ &ignored_result_len,
+ block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error finalizing block encryption"));
+ goto cleanup;
+ }
+
+ *ciphertext = wrap_as_string(result, result_len, result_pool);
+ *iv = wrap_as_string(iv_vector, iv_len, result_pool);
+ *salt = wrap_as_string(salt_vector, SALT_LEN, result_pool);
+
+ cleanup:
+ apr_crypto_block_cleanup(block_ctx);
+ return err;
+#else /* SVN_HAVE_CRYPTO */
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ "Cryptographic support is not available");
+#endif /* SVN_HAVE_CRYPTO */
+}
+
+
+svn_error_t *
+svn_crypto__decrypt_password(const char **plaintext,
+ svn_crypto__ctx_t *ctx,
+ const svn_string_t *ciphertext,
+ const svn_string_t *iv,
+ const svn_string_t *salt,
+ const svn_string_t *master,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+#ifdef SVN_HAVE_CRYPTO
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_status_t apr_err;
+ apr_crypto_block_t *block_ctx = NULL;
+ apr_size_t block_size, iv_len;
+ apr_crypto_key_t *key = NULL;
+ unsigned char *result;
+ apr_size_t result_len = 0, final_len = 0;
+
+ /* Initialize the passphrase. */
+ apr_err = apr_crypto_passphrase(&key, &iv_len,
+ master->data, master->len,
+ (unsigned char *)salt->data, salt->len,
+ APR_KEY_AES_256, APR_MODE_CBC,
+ FALSE /* doPad */, NUM_ITERATIONS,
+ ctx->crypto, scratch_pool);
+ if (apr_err != APR_SUCCESS)
+ return svn_error_trace(crypto_error_create(
+ ctx, apr_err,
+ _("Error creating derived key")));
+ if (! key)
+ return svn_error_create(APR_EGENERAL, NULL,
+ _("Error creating derived key"));
+ if (iv_len == 0)
+ return svn_error_create(APR_EGENERAL, NULL,
+ _("Unexpected IV length returned"));
+ if (iv_len != iv->len)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Provided IV has incorrect length"));
+
+ apr_err = apr_crypto_block_decrypt_init(&block_ctx, &block_size,
+ (unsigned char *)iv->data,
+ key, scratch_pool);
+ if ((apr_err != APR_SUCCESS) || (! block_ctx))
+ return svn_error_trace(crypto_error_create(
+ ctx, apr_err,
+ _("Error initializing block decryption")));
+
+ apr_err = apr_crypto_block_decrypt(NULL, &result_len,
+ (unsigned char *)ciphertext->data,
+ ciphertext->len, block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error fetching result length"));
+ goto cleanup;
+ }
+
+ result = apr_palloc(scratch_pool, result_len);
+ apr_err = apr_crypto_block_decrypt(&result, &result_len,
+ (unsigned char *)ciphertext->data,
+ ciphertext->len, block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error during block decryption"));
+ goto cleanup;
+ }
+
+ apr_err = apr_crypto_block_decrypt_finish(result + result_len, &final_len,
+ block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error finalizing block decryption"));
+ goto cleanup;
+ }
+
+ /* Copy the non-random bits of the resulting plaintext, skipping the
+ prefix and ignoring any trailing padding. */
+ *plaintext = apr_pstrndup(result_pool,
+ (const char *)(result + RANDOM_PREFIX_LEN),
+ result_len + final_len - RANDOM_PREFIX_LEN);
+
+ cleanup:
+ apr_crypto_block_cleanup(block_ctx);
+ return err;
+#else /* SVN_HAVE_CRYPTO */
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ "Cryptographic support is not available");
+#endif /* SVN_HAVE_CRYPTO */
+}
+
+
+svn_error_t *
+svn_crypto__generate_secret_checktext(const svn_string_t **ciphertext,
+ const svn_string_t **iv,
+ const svn_string_t **salt,
+ const char **checktext,
+ svn_crypto__ctx_t *ctx,
+ const svn_string_t *master,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+#ifdef SVN_HAVE_CRYPTO
+ svn_error_t *err = SVN_NO_ERROR;
+ const unsigned char *salt_vector;
+ const unsigned char *iv_vector;
+ const unsigned char *stuff_vector;
+ apr_size_t iv_len;
+ apr_crypto_key_t *key = NULL;
+ apr_status_t apr_err;
+ apr_crypto_block_t *block_ctx = NULL;
+ apr_size_t block_size;
+ apr_size_t result_len;
+ unsigned char *result;
+ apr_size_t ignored_result_len = 0;
+ apr_size_t stuff_len;
+ svn_checksum_t *stuff_sum;
+
+ SVN_ERR_ASSERT(ctx != NULL);
+
+ /* Generate the salt. */
+ SVN_ERR(get_random_bytes(&salt_vector, ctx, SALT_LEN, result_pool));
+
+ /* Initialize the passphrase. */
+ apr_err = apr_crypto_passphrase(&key, &iv_len,
+ master->data, master->len,
+ salt_vector, SALT_LEN,
+ APR_KEY_AES_256, APR_MODE_CBC,
+ FALSE /* doPad */, NUM_ITERATIONS,
+ ctx->crypto,
+ scratch_pool);
+ if (apr_err != APR_SUCCESS)
+ return svn_error_trace(crypto_error_create(
+ ctx, apr_err,
+ _("Error creating derived key")));
+ if (! key)
+ return svn_error_create(APR_EGENERAL, NULL,
+ _("Error creating derived key"));
+ if (iv_len == 0)
+ return svn_error_create(APR_EGENERAL, NULL,
+ _("Unexpected IV length returned"));
+
+ /* Generate the proper length IV. */
+ SVN_ERR(get_random_bytes(&iv_vector, ctx, iv_len, result_pool));
+
+ /* Initialize block encryption. */
+ apr_err = apr_crypto_block_encrypt_init(&block_ctx, &iv_vector, key,
+ &block_size, scratch_pool);
+ if ((apr_err != APR_SUCCESS) || (! block_ctx))
+ return svn_error_trace(crypto_error_create(
+ ctx, apr_err,
+ _("Error initializing block encryption")));
+
+ /* Generate a blob of random data, block-aligned per the
+ requirements of the encryption algorithm, but with a minimum size
+ of our choosing. */
+#define MIN_STUFF_LEN 32
+ if (MIN_STUFF_LEN % block_size)
+ stuff_len = MIN_STUFF_LEN + (block_size - (MIN_STUFF_LEN % block_size));
+ else
+ stuff_len = MIN_STUFF_LEN;
+ SVN_ERR(get_random_bytes(&stuff_vector, ctx, stuff_len, scratch_pool));
+
+ /* ### FIXME: This should be a SHA-256. */
+ SVN_ERR(svn_checksum(&stuff_sum, svn_checksum_sha1, stuff_vector,
+ stuff_len, scratch_pool));
+
+ /* Get the length that we need to allocate. */
+ apr_err = apr_crypto_block_encrypt(NULL, &result_len, stuff_vector,
+ stuff_len, block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error fetching result length"));
+ goto cleanup;
+ }
+
+ /* Allocate our result buffer. */
+ result = apr_palloc(result_pool, result_len);
+
+ /* Encrypt the block. */
+ apr_err = apr_crypto_block_encrypt(&result, &result_len, stuff_vector,
+ stuff_len, block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error during block encryption"));
+ goto cleanup;
+ }
+
+ /* Finalize the block encryption. Since we padded everything, this should
+ not produce any more encrypted output. */
+ apr_err = apr_crypto_block_encrypt_finish(NULL,
+ &ignored_result_len,
+ block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error finalizing block encryption"));
+ goto cleanup;
+ }
+
+ *ciphertext = wrap_as_string(result, result_len, result_pool);
+ *iv = wrap_as_string(iv_vector, iv_len, result_pool);
+ *salt = wrap_as_string(salt_vector, SALT_LEN, result_pool);
+ *checktext = svn_checksum_to_cstring(stuff_sum, result_pool);
+
+ cleanup:
+ apr_crypto_block_cleanup(block_ctx);
+ return err;
+#else /* SVN_HAVE_CRYPTO */
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ "Cryptographic support is not available");
+#endif /* SVN_HAVE_CRYPTO */
+}
+
+
+svn_error_t *
+svn_crypto__verify_secret(svn_boolean_t *is_valid,
+ svn_crypto__ctx_t *ctx,
+ const svn_string_t *master,
+ const svn_string_t *ciphertext,
+ const svn_string_t *iv,
+ const svn_string_t *salt,
+ const char *checktext,
+ apr_pool_t *scratch_pool)
+{
+#ifdef SVN_HAVE_CRYPTO
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_status_t apr_err;
+ apr_crypto_block_t *block_ctx = NULL;
+ apr_size_t block_size, iv_len;
+ apr_crypto_key_t *key = NULL;
+ unsigned char *result;
+ apr_size_t result_len = 0, final_len = 0;
+ svn_checksum_t *result_sum;
+
+ *is_valid = FALSE;
+
+ /* Initialize the passphrase. */
+ apr_err = apr_crypto_passphrase(&key, &iv_len,
+ master->data, master->len,
+ (unsigned char *)salt->data, salt->len,
+ APR_KEY_AES_256, APR_MODE_CBC,
+ FALSE /* doPad */, NUM_ITERATIONS,
+ ctx->crypto, scratch_pool);
+ if (apr_err != APR_SUCCESS)
+ return svn_error_trace(crypto_error_create(
+ ctx, apr_err,
+ _("Error creating derived key")));
+ if (! key)
+ return svn_error_create(APR_EGENERAL, NULL,
+ _("Error creating derived key"));
+ if (iv_len == 0)
+ return svn_error_create(APR_EGENERAL, NULL,
+ _("Unexpected IV length returned"));
+ if (iv_len != iv->len)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Provided IV has incorrect length"));
+
+ apr_err = apr_crypto_block_decrypt_init(&block_ctx, &block_size,
+ (unsigned char *)iv->data,
+ key, scratch_pool);
+ if ((apr_err != APR_SUCCESS) || (! block_ctx))
+ return svn_error_trace(crypto_error_create(
+ ctx, apr_err,
+ _("Error initializing block decryption")));
+
+ apr_err = apr_crypto_block_decrypt(NULL, &result_len,
+ (unsigned char *)ciphertext->data,
+ ciphertext->len, block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error fetching result length"));
+ goto cleanup;
+ }
+
+ result = apr_palloc(scratch_pool, result_len);
+ apr_err = apr_crypto_block_decrypt(&result, &result_len,
+ (unsigned char *)ciphertext->data,
+ ciphertext->len, block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error during block decryption"));
+ goto cleanup;
+ }
+
+ apr_err = apr_crypto_block_decrypt_finish(result + result_len, &final_len,
+ block_ctx);
+ if (apr_err != APR_SUCCESS)
+ {
+ err = crypto_error_create(ctx, apr_err,
+ _("Error finalizing block decryption"));
+ goto cleanup;
+ }
+
+ /* ### FIXME: This should be a SHA-256. */
+ SVN_ERR(svn_checksum(&result_sum, svn_checksum_sha1, result,
+ result_len + final_len, scratch_pool));
+
+ *is_valid = strcmp(checktext,
+ svn_checksum_to_cstring(result_sum, scratch_pool)) == 0;
+
+ cleanup:
+ apr_crypto_block_cleanup(block_ctx);
+ return err;
+#else /* SVN_HAVE_CRYPTO */
+ *is_valid = FALSE;
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ "Cryptographic support is not available");
+#endif /* SVN_HAVE_CRYPTO */
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/crypto.h b/contrib/subversion/subversion/libsvn_subr/crypto.h
new file mode 100644
index 0000000..5e7be86
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/crypto.h
@@ -0,0 +1,141 @@
+/*
+ * crypto.h : cryptographic routines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_SUBR_CRYPTO_H
+#define SVN_LIBSVN_SUBR_CRYPTO_H
+
+/* Test for APR crypto and RNG support */
+#undef SVN_HAVE_CRYPTO
+#include <apr.h>
+#include <apu.h>
+#if APR_HAS_RANDOM
+#if defined(APU_HAVE_CRYPTO) && APU_HAVE_CRYPTO
+#define SVN_HAVE_CRYPTO
+#endif
+#endif
+
+#include "svn_types.h"
+#include "svn_string.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Opaque context for cryptographic operations. */
+typedef struct svn_crypto__ctx_t svn_crypto__ctx_t;
+
+
+/* Return TRUE iff Subversion's cryptographic support is available. */
+svn_boolean_t svn_crypto__is_available(void);
+
+
+/* Set *CTX to new Subversion cryptographic context, based on an
+ APR-managed OpenSSL cryptography context object allocated
+ within RESULT_POOL. */
+/* ### TODO: Should this be something done once with the resulting
+ ### svn_crypto__ctx_t object stored in svn_client_ctx_t? */
+svn_error_t *
+svn_crypto__context_create(svn_crypto__ctx_t **ctx,
+ apr_pool_t *result_pool);
+
+
+/* Using a PBKDF2 derivative key based on MASTER, encrypt PLAINTEXT.
+ The salt used for PBKDF2 is returned in SALT, and the IV used for
+ the (AES-256/CBC) encryption is returned in IV. The resulting
+ encrypted data is returned in CIPHERTEXT.
+
+ Note that MASTER may be the plaintext obtained from the user or
+ some other OS-provided cryptographic store, or it can be a derivation
+ such as SHA1(plaintext). As long as the same octets are passed to
+ the decryption function, everything works just fine. (the SHA1
+ approach is suggested, to avoid keeping the plaintext master in
+ the process' memory space) */
+svn_error_t *
+svn_crypto__encrypt_password(const svn_string_t **ciphertext,
+ const svn_string_t **iv,
+ const svn_string_t **salt,
+ svn_crypto__ctx_t *ctx,
+ const char *plaintext,
+ const svn_string_t *master,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Given the CIPHERTEXT which was encrypted using (AES-256/CBC) with
+ initialization vector given by IV, and a key derived using PBKDF2
+ with SALT and MASTER... return the decrypted password in PLAINTEXT. */
+svn_error_t *
+svn_crypto__decrypt_password(const char **plaintext,
+ svn_crypto__ctx_t *ctx,
+ const svn_string_t *ciphertext,
+ const svn_string_t *iv,
+ const svn_string_t *salt,
+ const svn_string_t *master,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Generate the stuff Subversion needs to store in order to validate a
+ user-provided MASTER password:
+
+ Set *CIPHERTEXT to a block of encrypted data.
+
+ Set *IV and *SALT to the initialization vector and salt used for
+ encryption.
+
+ Set *CHECKTEXT to the check text used for validation.
+
+ CTX is a Subversion cryptographic context. MASTER is the
+ encryption secret.
+*/
+svn_error_t *
+svn_crypto__generate_secret_checktext(const svn_string_t **ciphertext,
+ const svn_string_t **iv,
+ const svn_string_t **salt,
+ const char **checktext,
+ svn_crypto__ctx_t *ctx,
+ const svn_string_t *master,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *IS_VALID to TRUE iff the encryption secret MASTER successfully
+ validates using Subversion cryptographic context CTX against
+ CIPHERTEXT, IV, SALT, and CHECKTEXT (which where probably generated
+ via previous call to svn_crypto__generate_secret_checktext()).
+
+ Use SCRATCH_POOL for necessary allocations. */
+svn_error_t *
+svn_crypto__verify_secret(svn_boolean_t *is_valid,
+ svn_crypto__ctx_t *ctx,
+ const svn_string_t *master,
+ const svn_string_t *ciphertext,
+ const svn_string_t *iv,
+ const svn_string_t *salt,
+ const char *checktext,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_SUBR_CRYPTO_H */
diff --git a/contrib/subversion/subversion/libsvn_subr/ctype.c b/contrib/subversion/subversion/libsvn_subr/ctype.c
new file mode 100644
index 0000000..0dd5d5b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/ctype.c
@@ -0,0 +1,319 @@
+/*
+ * ctype.c: Character classification routines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_ctype.h"
+
+const apr_uint32_t svn_ctype_table_internal[256] =
+ {
+ /* **** DO NOT EDIT! ****
+ This table was generated by genctype.py, make changes there. */
+ /* nul */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* soh */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* stx */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* etx */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* eot */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* enq */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* ack */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* bel */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* bs */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* ht */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL | SVN_CTYPE_SPACE,
+ /* nl */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL | SVN_CTYPE_SPACE,
+ /* vt */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL | SVN_CTYPE_SPACE,
+ /* np */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL | SVN_CTYPE_SPACE,
+ /* cr */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL | SVN_CTYPE_SPACE,
+ /* so */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* si */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* dle */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* dc1 */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* dc2 */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* dc3 */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* dc4 */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* nak */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* syn */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* etb */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* can */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* em */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* sub */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* esc */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* fs */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* gs */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* rs */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* us */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* sp */ SVN_CTYPE_ASCII | SVN_CTYPE_SPACE,
+ /* ! */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* " */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* # */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* $ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* % */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* & */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* ' */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* ( */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* ) */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* * */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* + */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* , */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* - */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* . */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* / */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* 0 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT,
+ /* 1 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT,
+ /* 2 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT,
+ /* 3 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT,
+ /* 4 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT,
+ /* 5 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT,
+ /* 6 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT,
+ /* 7 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT,
+ /* 8 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT,
+ /* 9 */ SVN_CTYPE_ASCII | SVN_CTYPE_DIGIT,
+ /* : */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* ; */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* < */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* = */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* > */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* ? */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* @ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* A */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA,
+ /* B */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA,
+ /* C */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA,
+ /* D */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA,
+ /* E */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA,
+ /* F */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER | SVN_CTYPE_XALPHA,
+ /* G */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* H */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* I */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* J */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* K */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* L */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* M */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* N */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* O */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* P */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* Q */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* R */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* S */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* T */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* U */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* V */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* W */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* X */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* Y */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* Z */ SVN_CTYPE_ASCII | SVN_CTYPE_UPPER,
+ /* [ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* \ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* ] */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* ^ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* _ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* ` */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* a */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA,
+ /* b */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA,
+ /* c */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA,
+ /* d */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA,
+ /* e */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA,
+ /* f */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER | SVN_CTYPE_XALPHA,
+ /* g */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* h */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* i */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* j */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* k */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* l */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* m */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* n */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* o */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* p */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* q */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* r */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* s */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* t */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* u */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* v */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* w */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* x */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* y */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* z */ SVN_CTYPE_ASCII | SVN_CTYPE_LOWER,
+ /* { */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* | */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* } */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* ~ */ SVN_CTYPE_ASCII | SVN_CTYPE_PUNCT,
+ /* del */ SVN_CTYPE_ASCII | SVN_CTYPE_CNTRL,
+ /* x80 */ SVN_CTYPE_UTF8CONT,
+ /* x81 */ SVN_CTYPE_UTF8CONT,
+ /* x82 */ SVN_CTYPE_UTF8CONT,
+ /* x83 */ SVN_CTYPE_UTF8CONT,
+ /* x84 */ SVN_CTYPE_UTF8CONT,
+ /* x85 */ SVN_CTYPE_UTF8CONT,
+ /* x86 */ SVN_CTYPE_UTF8CONT,
+ /* x87 */ SVN_CTYPE_UTF8CONT,
+ /* x88 */ SVN_CTYPE_UTF8CONT,
+ /* x89 */ SVN_CTYPE_UTF8CONT,
+ /* x8a */ SVN_CTYPE_UTF8CONT,
+ /* x8b */ SVN_CTYPE_UTF8CONT,
+ /* x8c */ SVN_CTYPE_UTF8CONT,
+ /* x8d */ SVN_CTYPE_UTF8CONT,
+ /* x8e */ SVN_CTYPE_UTF8CONT,
+ /* x8f */ SVN_CTYPE_UTF8CONT,
+ /* x90 */ SVN_CTYPE_UTF8CONT,
+ /* x91 */ SVN_CTYPE_UTF8CONT,
+ /* x92 */ SVN_CTYPE_UTF8CONT,
+ /* x93 */ SVN_CTYPE_UTF8CONT,
+ /* x94 */ SVN_CTYPE_UTF8CONT,
+ /* x95 */ SVN_CTYPE_UTF8CONT,
+ /* x96 */ SVN_CTYPE_UTF8CONT,
+ /* x97 */ SVN_CTYPE_UTF8CONT,
+ /* x98 */ SVN_CTYPE_UTF8CONT,
+ /* x99 */ SVN_CTYPE_UTF8CONT,
+ /* x9a */ SVN_CTYPE_UTF8CONT,
+ /* x9b */ SVN_CTYPE_UTF8CONT,
+ /* x9c */ SVN_CTYPE_UTF8CONT,
+ /* x9d */ SVN_CTYPE_UTF8CONT,
+ /* x9e */ SVN_CTYPE_UTF8CONT,
+ /* x9f */ SVN_CTYPE_UTF8CONT,
+ /* xa0 */ SVN_CTYPE_UTF8CONT,
+ /* xa1 */ SVN_CTYPE_UTF8CONT,
+ /* xa2 */ SVN_CTYPE_UTF8CONT,
+ /* xa3 */ SVN_CTYPE_UTF8CONT,
+ /* xa4 */ SVN_CTYPE_UTF8CONT,
+ /* xa5 */ SVN_CTYPE_UTF8CONT,
+ /* xa6 */ SVN_CTYPE_UTF8CONT,
+ /* xa7 */ SVN_CTYPE_UTF8CONT,
+ /* xa8 */ SVN_CTYPE_UTF8CONT,
+ /* xa9 */ SVN_CTYPE_UTF8CONT,
+ /* xaa */ SVN_CTYPE_UTF8CONT,
+ /* xab */ SVN_CTYPE_UTF8CONT,
+ /* xac */ SVN_CTYPE_UTF8CONT,
+ /* xad */ SVN_CTYPE_UTF8CONT,
+ /* xae */ SVN_CTYPE_UTF8CONT,
+ /* xaf */ SVN_CTYPE_UTF8CONT,
+ /* xb0 */ SVN_CTYPE_UTF8CONT,
+ /* xb1 */ SVN_CTYPE_UTF8CONT,
+ /* xb2 */ SVN_CTYPE_UTF8CONT,
+ /* xb3 */ SVN_CTYPE_UTF8CONT,
+ /* xb4 */ SVN_CTYPE_UTF8CONT,
+ /* xb5 */ SVN_CTYPE_UTF8CONT,
+ /* xb6 */ SVN_CTYPE_UTF8CONT,
+ /* xb7 */ SVN_CTYPE_UTF8CONT,
+ /* xb8 */ SVN_CTYPE_UTF8CONT,
+ /* xb9 */ SVN_CTYPE_UTF8CONT,
+ /* xba */ SVN_CTYPE_UTF8CONT,
+ /* xbb */ SVN_CTYPE_UTF8CONT,
+ /* xbc */ SVN_CTYPE_UTF8CONT,
+ /* xbd */ SVN_CTYPE_UTF8CONT,
+ /* xbe */ SVN_CTYPE_UTF8CONT,
+ /* xbf */ SVN_CTYPE_UTF8CONT,
+ /* xc0 */ 0,
+ /* xc1 */ SVN_CTYPE_UTF8LEAD,
+ /* xc2 */ SVN_CTYPE_UTF8LEAD,
+ /* xc3 */ SVN_CTYPE_UTF8LEAD,
+ /* xc4 */ SVN_CTYPE_UTF8LEAD,
+ /* xc5 */ SVN_CTYPE_UTF8LEAD,
+ /* xc6 */ SVN_CTYPE_UTF8LEAD,
+ /* xc7 */ SVN_CTYPE_UTF8LEAD,
+ /* xc8 */ SVN_CTYPE_UTF8LEAD,
+ /* xc9 */ SVN_CTYPE_UTF8LEAD,
+ /* xca */ SVN_CTYPE_UTF8LEAD,
+ /* xcb */ SVN_CTYPE_UTF8LEAD,
+ /* xcc */ SVN_CTYPE_UTF8LEAD,
+ /* xcd */ SVN_CTYPE_UTF8LEAD,
+ /* xce */ SVN_CTYPE_UTF8LEAD,
+ /* xcf */ SVN_CTYPE_UTF8LEAD,
+ /* xd0 */ SVN_CTYPE_UTF8LEAD,
+ /* xd1 */ SVN_CTYPE_UTF8LEAD,
+ /* xd2 */ SVN_CTYPE_UTF8LEAD,
+ /* xd3 */ SVN_CTYPE_UTF8LEAD,
+ /* xd4 */ SVN_CTYPE_UTF8LEAD,
+ /* xd5 */ SVN_CTYPE_UTF8LEAD,
+ /* xd6 */ SVN_CTYPE_UTF8LEAD,
+ /* xd7 */ SVN_CTYPE_UTF8LEAD,
+ /* xd8 */ SVN_CTYPE_UTF8LEAD,
+ /* xd9 */ SVN_CTYPE_UTF8LEAD,
+ /* xda */ SVN_CTYPE_UTF8LEAD,
+ /* xdb */ SVN_CTYPE_UTF8LEAD,
+ /* xdc */ SVN_CTYPE_UTF8LEAD,
+ /* xdd */ SVN_CTYPE_UTF8LEAD,
+ /* xde */ SVN_CTYPE_UTF8LEAD,
+ /* xdf */ SVN_CTYPE_UTF8LEAD,
+ /* xe0 */ 0,
+ /* xe1 */ SVN_CTYPE_UTF8LEAD,
+ /* xe2 */ SVN_CTYPE_UTF8LEAD,
+ /* xe3 */ SVN_CTYPE_UTF8LEAD,
+ /* xe4 */ SVN_CTYPE_UTF8LEAD,
+ /* xe5 */ SVN_CTYPE_UTF8LEAD,
+ /* xe6 */ SVN_CTYPE_UTF8LEAD,
+ /* xe7 */ SVN_CTYPE_UTF8LEAD,
+ /* xe8 */ SVN_CTYPE_UTF8LEAD,
+ /* xe9 */ SVN_CTYPE_UTF8LEAD,
+ /* xea */ SVN_CTYPE_UTF8LEAD,
+ /* xeb */ SVN_CTYPE_UTF8LEAD,
+ /* xec */ SVN_CTYPE_UTF8LEAD,
+ /* xed */ SVN_CTYPE_UTF8LEAD,
+ /* xee */ SVN_CTYPE_UTF8LEAD,
+ /* xef */ SVN_CTYPE_UTF8LEAD,
+ /* xf0 */ 0,
+ /* xf1 */ SVN_CTYPE_UTF8LEAD,
+ /* xf2 */ SVN_CTYPE_UTF8LEAD,
+ /* xf3 */ SVN_CTYPE_UTF8LEAD,
+ /* xf4 */ SVN_CTYPE_UTF8LEAD,
+ /* xf5 */ SVN_CTYPE_UTF8LEAD,
+ /* xf6 */ SVN_CTYPE_UTF8LEAD,
+ /* xf7 */ SVN_CTYPE_UTF8LEAD,
+ /* xf8 */ 0,
+ /* xf9 */ SVN_CTYPE_UTF8LEAD,
+ /* xfa */ SVN_CTYPE_UTF8LEAD,
+ /* xfb */ SVN_CTYPE_UTF8LEAD,
+ /* xfc */ 0,
+ /* xfd */ SVN_CTYPE_UTF8LEAD,
+ /* xfe */ 0,
+ /* xff */ 0
+ };
+
+const apr_uint32_t *const svn_ctype_table = svn_ctype_table_internal;
+
+static const unsigned char casefold_table[256] =
+ {
+ /* Identity, except {97:122} => {65:90} */
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,123,124,125,126,127,
+ 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
+ 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
+ 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
+ 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
+ 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
+ 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
+ 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
+ 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
+ };
+
+int
+svn_ctype_casecmp(int a, int b)
+{
+ const int A = casefold_table[(unsigned char)a];
+ const int B = casefold_table[(unsigned char)b];
+ return A - B;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/date.c b/contrib/subversion/subversion/libsvn_subr/date.c
new file mode 100644
index 0000000..6035645
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/date.c
@@ -0,0 +1,393 @@
+/* date.c: date parsing for Subversion
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_time.h"
+#include "svn_error.h"
+#include "svn_string.h"
+
+#include "svn_private_config.h"
+#include "private/svn_token.h"
+
+/* Valid rule actions */
+enum rule_action {
+ ACCUM, /* Accumulate a decimal value */
+ MICRO, /* Accumulate microseconds */
+ TZIND, /* Handle +, -, Z */
+ NOOP, /* Do nothing */
+ SKIPFROM, /* If at end-of-value, accept the match. Otherwise,
+ if the next template character matches the current
+ value character, continue processing as normal.
+ Otherwise, attempt to complete matching starting
+ immediately after the first subsequent occurrance of
+ ']' in the template. */
+ SKIP, /* Ignore this template character */
+ ACCEPT /* Accept the value */
+};
+
+/* How to handle a particular character in a template */
+typedef struct rule
+{
+ char key; /* The template char that this rule matches */
+ const char *valid; /* String of valid chars for this rule */
+ enum rule_action action; /* What action to take when the rule is matched */
+ int offset; /* Where to store the any results of the action,
+ expressed in terms of bytes relative to the
+ base of a match_state object. */
+} rule;
+
+/* The parsed values, before localtime/gmt processing */
+typedef struct match_state
+{
+ apr_time_exp_t base;
+ apr_int32_t offhours;
+ apr_int32_t offminutes;
+} match_state;
+
+#define DIGITS "0123456789"
+
+/* A declarative specification of how each template character
+ should be processed, using a rule for each valid symbol. */
+static const rule
+rules[] =
+{
+ { 'Y', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_year) },
+ { 'M', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_mon) },
+ { 'D', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_mday) },
+ { 'h', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_hour) },
+ { 'm', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_min) },
+ { 's', DIGITS, ACCUM, APR_OFFSETOF(match_state, base.tm_sec) },
+ { 'u', DIGITS, MICRO, APR_OFFSETOF(match_state, base.tm_usec) },
+ { 'O', DIGITS, ACCUM, APR_OFFSETOF(match_state, offhours) },
+ { 'o', DIGITS, ACCUM, APR_OFFSETOF(match_state, offminutes) },
+ { '+', "-+", TZIND, 0 },
+ { 'Z', "Z", TZIND, 0 },
+ { ':', ":", NOOP, 0 },
+ { '-', "-", NOOP, 0 },
+ { 'T', "T", NOOP, 0 },
+ { ' ', " ", NOOP, 0 },
+ { '.', ".,", NOOP, 0 },
+ { '[', NULL, SKIPFROM, 0 },
+ { ']', NULL, SKIP, 0 },
+ { '\0', NULL, ACCEPT, 0 },
+};
+
+/* Return the rule associated with TCHAR, or NULL if there
+ is no such rule. */
+static const rule *
+find_rule(char tchar)
+{
+ int i = sizeof(rules)/sizeof(rules[0]);
+ while (i--)
+ if (rules[i].key == tchar)
+ return &rules[i];
+ return NULL;
+}
+
+/* Attempt to match the date-string in VALUE to the provided TEMPLATE,
+ using the rules defined above. Return TRUE on successful match,
+ FALSE otherwise. On successful match, fill in *EXP with the
+ matched values and set *LOCALTZ to TRUE if the local time zone
+ should be used to interpret the match (i.e. if no time zone
+ information was provided), or FALSE if not. */
+static svn_boolean_t
+template_match(apr_time_exp_t *expt, svn_boolean_t *localtz,
+ const char *template, const char *value)
+{
+ int multiplier = 100000;
+ int tzind = 0;
+ match_state ms;
+ char *base = (char *)&ms;
+
+ memset(&ms, 0, sizeof(ms));
+
+ for (;;)
+ {
+ const rule *match = find_rule(*template++);
+ char vchar = *value++;
+ apr_int32_t *place;
+
+ if (!match || (match->valid
+ && (!vchar || !strchr(match->valid, vchar))))
+ return FALSE;
+
+ /* Compute the address of memory location affected by this
+ rule by adding match->offset bytes to the address of ms.
+ Because this is a byte-quantity, it is necessary to cast
+ &ms to char *. */
+ place = (apr_int32_t *)(base + match->offset);
+ switch (match->action)
+ {
+ case ACCUM:
+ *place = *place * 10 + vchar - '0';
+ continue;
+ case MICRO:
+ *place += (vchar - '0') * multiplier;
+ multiplier /= 10;
+ continue;
+ case TZIND:
+ tzind = vchar;
+ continue;
+ case SKIP:
+ value--;
+ continue;
+ case NOOP:
+ continue;
+ case SKIPFROM:
+ if (!vchar)
+ break;
+ match = find_rule(*template);
+ if (!strchr(match->valid, vchar))
+ template = strchr(template, ']') + 1;
+ value--;
+ continue;
+ case ACCEPT:
+ if (vchar)
+ return FALSE;
+ break;
+ }
+
+ break;
+ }
+
+ /* Validate gmt offset here, since we can't reliably do it later. */
+ if (ms.offhours > 23 || ms.offminutes > 59)
+ return FALSE;
+
+ /* tzind will be '+' or '-' for an explicit time zone, 'Z' to
+ indicate UTC, or 0 to indicate local time. */
+ switch (tzind)
+ {
+ case '+':
+ ms.base.tm_gmtoff = ms.offhours * 3600 + ms.offminutes * 60;
+ break;
+ case '-':
+ ms.base.tm_gmtoff = -(ms.offhours * 3600 + ms.offminutes * 60);
+ break;
+ }
+
+ *expt = ms.base;
+ *localtz = (tzind == 0);
+ return TRUE;
+}
+
+static struct unit_words_table {
+ const char *word;
+ apr_time_t value;
+} unit_words_table[] = {
+ /* Word matching does not concern itself with exact days of the month
+ * or leap years so these amounts are always fixed. */
+ { "years", apr_time_from_sec(60 * 60 * 24 * 365) },
+ { "months", apr_time_from_sec(60 * 60 * 24 * 30) },
+ { "weeks", apr_time_from_sec(60 * 60 * 24 * 7) },
+ { "days", apr_time_from_sec(60 * 60 * 24) },
+ { "hours", apr_time_from_sec(60 * 60) },
+ { "minutes", apr_time_from_sec(60) },
+ { "mins", apr_time_from_sec(60) },
+ { NULL , 0 }
+};
+
+static svn_token_map_t number_words_map[] = {
+ { "zero", 0 }, { "one", 1 }, { "two", 2 }, { "three", 3 }, { "four", 4 },
+ { "five", 5 }, { "six", 6 }, { "seven", 7 }, { "eight", 8 }, { "nine", 9 },
+ { "ten", 10 }, { "eleven", 11 }, { "twelve", 12 }, { NULL, 0 }
+};
+
+/* Attempt to match the date-string in TEXT according to the following rules:
+ *
+ * "N years|months|weeks|days|hours|minutes ago" resolve to the most recent
+ * revision prior to the specified time. N may either be a word from
+ * NUMBER_WORDS_TABLE defined above, or a non-negative digit.
+ *
+ * Return TRUE on successful match, FALSE otherwise. On successful match,
+ * fill in *EXP with the matched value and set *LOCALTZ to TRUE (this
+ * function always uses local time). Use POOL for temporary allocations. */
+static svn_boolean_t
+words_match(apr_time_exp_t *expt, svn_boolean_t *localtz,
+ apr_time_t now, const char *text, apr_pool_t *pool)
+{
+ apr_time_t t = -1;
+ const char *word;
+ apr_array_header_t *words;
+ int i;
+ int n = -1;
+ const char *unit_str;
+
+ words = svn_cstring_split(text, " ", TRUE /* chop_whitespace */, pool);
+
+ if (words->nelts != 3)
+ return FALSE;
+
+ word = APR_ARRAY_IDX(words, 0, const char *);
+
+ /* Try to parse a number word. */
+ n = svn_token__from_word(number_words_map, word);
+
+ if (n == SVN_TOKEN_UNKNOWN)
+ {
+ svn_error_t *err;
+
+ /* Try to parse a digit. */
+ err = svn_cstring_atoi(&n, word);
+ if (err)
+ {
+ svn_error_clear(err);
+ return FALSE;
+ }
+ if (n < 0)
+ return FALSE;
+ }
+
+ /* Try to parse a unit. */
+ word = APR_ARRAY_IDX(words, 1, const char *);
+ for (i = 0, unit_str = unit_words_table[i].word;
+ unit_str = unit_words_table[i].word, unit_str != NULL; i++)
+ {
+ /* Tolerate missing trailing 's' from unit. */
+ if (!strcmp(word, unit_str) ||
+ !strncmp(word, unit_str, strlen(unit_str) - 1))
+ {
+ t = now - (n * unit_words_table[i].value);
+ break;
+ }
+ }
+
+ if (t < 0)
+ return FALSE;
+
+ /* Require trailing "ago". */
+ word = APR_ARRAY_IDX(words, 2, const char *);
+ if (strcmp(word, "ago"))
+ return FALSE;
+
+ if (apr_time_exp_lt(expt, t) != APR_SUCCESS)
+ return FALSE;
+
+ *localtz = TRUE;
+ return TRUE;
+}
+
+static int
+valid_days_by_month[] = {
+ 31, 29, 31, 30,
+ 31, 30, 31, 31,
+ 30, 31, 30, 31
+};
+
+svn_error_t *
+svn_parse_date(svn_boolean_t *matched, apr_time_t *result, const char *text,
+ apr_time_t now, apr_pool_t *pool)
+{
+ apr_time_exp_t expt, expnow;
+ apr_status_t apr_err;
+ svn_boolean_t localtz;
+
+ *matched = FALSE;
+
+ apr_err = apr_time_exp_lt(&expnow, now);
+ if (apr_err != APR_SUCCESS)
+ return svn_error_wrap_apr(apr_err, _("Can't manipulate current date"));
+
+ if (template_match(&expt, &localtz, /* ISO-8601 extended, date only */
+ "YYYY-M[M]-D[D]",
+ text)
+ || template_match(&expt, &localtz, /* ISO-8601 extended, UTC */
+ "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u][Z]",
+ text)
+ || template_match(&expt, &localtz, /* ISO-8601 extended, with offset */
+ "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[:oo]",
+ text)
+ || template_match(&expt, &localtz, /* ISO-8601 basic, date only */
+ "YYYYMMDD",
+ text)
+ || template_match(&expt, &localtz, /* ISO-8601 basic, UTC */
+ "YYYYMMDDThhmm[ss[.u[u[u[u[u[u][Z]",
+ text)
+ || template_match(&expt, &localtz, /* ISO-8601 basic, with offset */
+ "YYYYMMDDThhmm[ss[.u[u[u[u[u[u]+OO[oo]",
+ text)
+ || template_match(&expt, &localtz, /* "svn log" format */
+ "YYYY-M[M]-D[D] h[h]:mm[:ss[.u[u[u[u[u[u][ +OO[oo]",
+ text)
+ || template_match(&expt, &localtz, /* GNU date's iso-8601 */
+ "YYYY-M[M]-D[D]Th[h]:mm[:ss[.u[u[u[u[u[u]+OO[oo]",
+ text))
+ {
+ expt.tm_year -= 1900;
+ expt.tm_mon -= 1;
+ }
+ else if (template_match(&expt, &localtz, /* Just a time */
+ "h[h]:mm[:ss[.u[u[u[u[u[u]",
+ text))
+ {
+ expt.tm_year = expnow.tm_year;
+ expt.tm_mon = expnow.tm_mon;
+ expt.tm_mday = expnow.tm_mday;
+ }
+ else if (!words_match(&expt, &localtz, now, text, pool))
+ return SVN_NO_ERROR;
+
+ /* Range validation, allowing for leap seconds */
+ if (expt.tm_mon < 0 || expt.tm_mon > 11
+ || expt.tm_mday > valid_days_by_month[expt.tm_mon]
+ || expt.tm_mday < 1
+ || expt.tm_hour > 23
+ || expt.tm_min > 59
+ || expt.tm_sec > 60)
+ return SVN_NO_ERROR;
+
+ /* february/leap-year day checking. tm_year is bias-1900, so centuries
+ that equal 100 (mod 400) are multiples of 400. */
+ if (expt.tm_mon == 1
+ && expt.tm_mday == 29
+ && (expt.tm_year % 4 != 0
+ || (expt.tm_year % 100 == 0 && expt.tm_year % 400 != 100)))
+ return SVN_NO_ERROR;
+
+ if (localtz)
+ {
+ apr_time_t candidate;
+ apr_time_exp_t expthen;
+
+ /* We need to know the GMT offset of the requested time, not the
+ current time. In some cases, that quantity is ambiguous,
+ since at the end of daylight saving's time, an hour's worth
+ of local time happens twice. For those cases, we should
+ prefer DST if we are currently in DST, and standard time if
+ not. So, calculate the time value using the current time's
+ GMT offset and use the GMT offset of the resulting time. */
+ expt.tm_gmtoff = expnow.tm_gmtoff;
+ apr_err = apr_time_exp_gmt_get(&candidate, &expt);
+ if (apr_err != APR_SUCCESS)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't calculate requested date"));
+ apr_err = apr_time_exp_lt(&expthen, candidate);
+ if (apr_err != APR_SUCCESS)
+ return svn_error_wrap_apr(apr_err, _("Can't expand time"));
+ expt.tm_gmtoff = expthen.tm_gmtoff;
+ }
+ apr_err = apr_time_exp_gmt_get(result, &expt);
+ if (apr_err != APR_SUCCESS)
+ return svn_error_wrap_apr(apr_err, _("Can't calculate requested date"));
+
+ *matched = TRUE;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/debug.c b/contrib/subversion/subversion/libsvn_subr/debug.c
new file mode 100644
index 0000000..be331ed
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/debug.c
@@ -0,0 +1,155 @@
+/*
+ * debug.c : small functions to help SVN developers
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* These functions are only available to SVN developers and should never
+ be used in release code. One of the reasons to avoid this code in release
+ builds is that this code is not thread-safe. */
+#include <stdarg.h>
+#include <assert.h>
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+#include "svn_types.h"
+#include "svn_string.h"
+
+#ifndef SVN_DBG__PROTOTYPES
+#define SVN_DBG__PROTOTYPES
+#endif
+#include "private/svn_debug.h"
+
+
+#define DBG_FLAG "DBG: "
+
+/* This will be tweaked by the preamble code. */
+static const char *debug_file = NULL;
+static long debug_line = 0;
+static FILE * volatile debug_output = NULL;
+
+
+static svn_boolean_t
+quiet_mode(void)
+{
+ return getenv("SVN_DBG_QUIET") != NULL;
+}
+
+
+void
+svn_dbg__preamble(const char *file, long line, FILE *output)
+{
+ debug_output = output;
+
+ if (output != NULL && !quiet_mode())
+ {
+ /* Quick and dirty basename() code. */
+ const char *slash = strrchr(file, '/');
+
+ if (slash == NULL)
+ slash = strrchr(file, '\\');
+ if (slash)
+ debug_file = slash + 1;
+ else
+ debug_file = file;
+ }
+ debug_line = line;
+}
+
+
+/* Print a formatted string using format FMT and argument-list AP,
+ * prefixing each line of output with a debug header. */
+static void
+debug_vprintf(const char *fmt, va_list ap)
+{
+ FILE *output = debug_output;
+ char prefix[80], buffer[1000];
+ char *s = buffer;
+ int n;
+
+ if (output == NULL || quiet_mode())
+ return;
+
+ n = apr_snprintf(prefix, sizeof(prefix), DBG_FLAG "%s:%4ld: ",
+ debug_file, debug_line);
+ assert(n < sizeof(prefix) - 1);
+ n = apr_vsnprintf(buffer, sizeof(buffer), fmt, ap);
+ assert(n < sizeof(buffer) - 1);
+ do
+ {
+ char *newline = strchr(s, '\n');
+ if (newline)
+ *newline = '\0';
+
+ fputs(prefix, output);
+ fputs(s, output);
+ fputc('\n', output);
+
+ if (! newline)
+ break;
+ s = newline + 1;
+ }
+ while (*s); /* print another line, except after a final newline */
+}
+
+
+void
+svn_dbg__printf(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ debug_vprintf(fmt, ap);
+ va_end(ap);
+}
+
+
+void
+svn_dbg__print_props(apr_hash_t *props,
+ const char *header_fmt,
+ ...)
+{
+/* We only build this code if SVN_DEBUG is defined. */
+#ifdef SVN_DEBUG
+
+ apr_hash_index_t *hi;
+ va_list ap;
+
+ va_start(ap, header_fmt);
+ debug_vprintf(header_fmt, ap);
+ va_end(ap);
+
+ if (props == NULL)
+ {
+ svn_dbg__printf(" (null)\n");
+ return;
+ }
+
+ for (hi = apr_hash_first(apr_hash_pool_get(props), props); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_string_t *val = svn__apr_hash_index_val(hi);
+
+ svn_dbg__printf(" '%s' -> '%s'\n", name, val->data);
+ }
+#endif /* SVN_DEBUG */
+}
+
diff --git a/contrib/subversion/subversion/libsvn_subr/deprecated.c b/contrib/subversion/subversion/libsvn_subr/deprecated.c
new file mode 100644
index 0000000..378b3f8
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/deprecated.c
@@ -0,0 +1,1304 @@
+/*
+ * deprecated.c: holding file for all deprecated APIs.
+ * "we can't lose 'em, but we can shun 'em!"
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#include <assert.h>
+
+/* We define this here to remove any further warnings about the usage of
+ deprecated functions in this file. */
+#define SVN_DEPRECATED
+
+#include "svn_hash.h"
+#include "svn_subst.h"
+#include "svn_path.h"
+#include "svn_opt.h"
+#include "svn_cmdline.h"
+#include "svn_version.h"
+#include "svn_pools.h"
+#include "svn_dso.h"
+#include "svn_mergeinfo.h"
+#include "svn_utf.h"
+#include "svn_xml.h"
+
+#include "opt.h"
+#include "private/svn_opt_private.h"
+#include "private/svn_mergeinfo_private.h"
+
+#include "svn_private_config.h"
+
+
+
+
+/*** Code. ***/
+
+/*** From subst.c ***/
+/* Convert an old-style svn_subst_keywords_t struct * into a new-style
+ * keywords hash. Keyword values are shallow copies, so the produced
+ * hash must not be assumed to have lifetime longer than the struct it
+ * is based on. A NULL input causes a NULL output. */
+static apr_hash_t *
+kwstruct_to_kwhash(const svn_subst_keywords_t *kwstruct,
+ apr_pool_t *pool)
+{
+ apr_hash_t *kwhash;
+
+ if (kwstruct == NULL)
+ return NULL;
+
+ kwhash = apr_hash_make(pool);
+
+ if (kwstruct->revision)
+ {
+ svn_hash_sets(kwhash, SVN_KEYWORD_REVISION_LONG, kwstruct->revision);
+ svn_hash_sets(kwhash, SVN_KEYWORD_REVISION_MEDIUM, kwstruct->revision);
+ svn_hash_sets(kwhash, SVN_KEYWORD_REVISION_SHORT, kwstruct->revision);
+ }
+ if (kwstruct->date)
+ {
+ svn_hash_sets(kwhash, SVN_KEYWORD_DATE_LONG, kwstruct->date);
+ svn_hash_sets(kwhash, SVN_KEYWORD_DATE_SHORT, kwstruct->date);
+ }
+ if (kwstruct->author)
+ {
+ svn_hash_sets(kwhash, SVN_KEYWORD_AUTHOR_LONG, kwstruct->author);
+ svn_hash_sets(kwhash, SVN_KEYWORD_AUTHOR_SHORT, kwstruct->author);
+ }
+ if (kwstruct->url)
+ {
+ svn_hash_sets(kwhash, SVN_KEYWORD_URL_LONG, kwstruct->url);
+ svn_hash_sets(kwhash, SVN_KEYWORD_URL_SHORT, kwstruct->url);
+ }
+ if (kwstruct->id)
+ {
+ svn_hash_sets(kwhash, SVN_KEYWORD_ID, kwstruct->id);
+ }
+
+ return kwhash;
+}
+
+
+svn_error_t *
+svn_subst_translate_stream3(svn_stream_t *src_stream,
+ svn_stream_t *dst_stream,
+ const char *eol_str,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *pool)
+{
+ /* The docstring requires that *some* translation be requested. */
+ SVN_ERR_ASSERT(eol_str || keywords);
+
+ /* We don't want the copy3 to close the provided streams. */
+ src_stream = svn_stream_disown(src_stream, pool);
+ dst_stream = svn_stream_disown(dst_stream, pool);
+
+ /* Wrap the destination stream with our translation stream. It is more
+ efficient than wrapping the source stream. */
+ dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair,
+ keywords, expand, pool);
+
+ return svn_error_trace(svn_stream_copy3(src_stream, dst_stream,
+ NULL, NULL, pool));
+}
+
+svn_error_t *
+svn_subst_translate_stream2(svn_stream_t *s, /* src stream */
+ svn_stream_t *d, /* dst stream */
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *pool)
+{
+ apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool);
+
+ return svn_error_trace(svn_subst_translate_stream3(s, d, eol_str, repair,
+ kh, expand, pool));
+}
+
+svn_error_t *
+svn_subst_translate_stream(svn_stream_t *s, /* src stream */
+ svn_stream_t *d, /* dst stream */
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand)
+{
+ apr_pool_t *pool = svn_pool_create(NULL);
+ svn_error_t *err = svn_subst_translate_stream2(s, d, eol_str, repair,
+ keywords, expand, pool);
+ svn_pool_destroy(pool);
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_subst_translate_cstring(const char *src,
+ const char **dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *pool)
+{
+ apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool);
+
+ return svn_error_trace(svn_subst_translate_cstring2(src, dst, eol_str,
+ repair, kh, expand,
+ pool));
+}
+
+svn_error_t *
+svn_subst_copy_and_translate(const char *src,
+ const char *dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_subst_copy_and_translate2(src, dst, eol_str,
+ repair, keywords,
+ expand, FALSE, pool));
+}
+
+svn_error_t *
+svn_subst_copy_and_translate2(const char *src,
+ const char *dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand,
+ svn_boolean_t special,
+ apr_pool_t *pool)
+{
+ apr_hash_t *kh = kwstruct_to_kwhash(keywords, pool);
+
+ return svn_error_trace(svn_subst_copy_and_translate3(src, dst, eol_str,
+ repair, kh, expand,
+ special, pool));
+}
+
+svn_error_t *
+svn_subst_copy_and_translate3(const char *src,
+ const char *dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ svn_boolean_t special,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_subst_copy_and_translate4(src, dst, eol_str,
+ repair, keywords,
+ expand, special,
+ NULL, NULL,
+ pool));
+}
+
+
+svn_error_t *
+svn_subst_stream_translated_to_normal_form(svn_stream_t **stream,
+ svn_stream_t *source,
+ svn_subst_eol_style_t eol_style,
+ const char *eol_str,
+ svn_boolean_t always_repair_eols,
+ apr_hash_t *keywords,
+ apr_pool_t *pool)
+{
+ if (eol_style == svn_subst_eol_style_native)
+ eol_str = SVN_SUBST_NATIVE_EOL_STR;
+ else if (! (eol_style == svn_subst_eol_style_fixed
+ || eol_style == svn_subst_eol_style_none))
+ return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
+
+ *stream = svn_subst_stream_translated(source, eol_str,
+ eol_style == svn_subst_eol_style_fixed
+ || always_repair_eols,
+ keywords, FALSE, pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_subst_translate_string(svn_string_t **new_value,
+ const svn_string_t *value,
+ const char *encoding,
+ apr_pool_t *pool)
+{
+ return svn_subst_translate_string2(new_value, NULL, NULL, value,
+ encoding, FALSE, pool, pool);
+}
+
+svn_error_t *
+svn_subst_stream_detranslated(svn_stream_t **stream_p,
+ const char *src,
+ svn_subst_eol_style_t eol_style,
+ const char *eol_str,
+ svn_boolean_t always_repair_eols,
+ apr_hash_t *keywords,
+ svn_boolean_t special,
+ apr_pool_t *pool)
+{
+ svn_stream_t *src_stream;
+
+ if (special)
+ return svn_subst_read_specialfile(stream_p, src, pool, pool);
+
+ /* This will be closed by svn_subst_stream_translated_to_normal_form
+ when the returned stream is closed. */
+ SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
+
+ return svn_error_trace(svn_subst_stream_translated_to_normal_form(
+ stream_p, src_stream,
+ eol_style, eol_str,
+ always_repair_eols,
+ keywords, pool));
+}
+
+svn_error_t *
+svn_subst_translate_to_normal_form(const char *src,
+ const char *dst,
+ svn_subst_eol_style_t eol_style,
+ const char *eol_str,
+ svn_boolean_t always_repair_eols,
+ apr_hash_t *keywords,
+ svn_boolean_t special,
+ apr_pool_t *pool)
+{
+
+ if (eol_style == svn_subst_eol_style_native)
+ eol_str = SVN_SUBST_NATIVE_EOL_STR;
+ else if (! (eol_style == svn_subst_eol_style_fixed
+ || eol_style == svn_subst_eol_style_none))
+ return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
+
+ return svn_error_trace(svn_subst_copy_and_translate3(
+ src, dst, eol_str,
+ eol_style == svn_subst_eol_style_fixed
+ || always_repair_eols,
+ keywords,
+ FALSE /* contract keywords */,
+ special,
+ pool));
+}
+
+
+/*** From opt.c ***/
+/* Same as print_command_info2(), but with deprecated struct revision. */
+static svn_error_t *
+print_command_info(const svn_opt_subcommand_desc_t *cmd,
+ const apr_getopt_option_t *options_table,
+ svn_boolean_t help,
+ apr_pool_t *pool,
+ FILE *stream)
+{
+ svn_boolean_t first_time;
+ apr_size_t i;
+
+ /* Print the canonical command name. */
+ SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
+
+ /* Print the list of aliases. */
+ first_time = TRUE;
+ for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
+ {
+ if (cmd->aliases[i] == NULL)
+ break;
+
+ if (first_time) {
+ SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
+ first_time = FALSE;
+ }
+ else
+ SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
+
+ SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
+ }
+
+ if (! first_time)
+ SVN_ERR(svn_cmdline_fputs(")", stream, pool));
+
+ if (help)
+ {
+ const apr_getopt_option_t *option;
+ svn_boolean_t have_options = FALSE;
+
+ SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help)));
+
+ /* Loop over all valid option codes attached to the subcommand */
+ for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
+ {
+ if (cmd->valid_options[i])
+ {
+ if (!have_options)
+ {
+ SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
+ stream, pool));
+ have_options = TRUE;
+ }
+
+ /* convert each option code into an option */
+ option =
+ svn_opt_get_option_from_code2(cmd->valid_options[i],
+ options_table, NULL, pool);
+
+ /* print the option's docstring */
+ if (option && option->description)
+ {
+ const char *optstr;
+ svn_opt_format_option(&optstr, option, TRUE, pool);
+ SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n",
+ optstr));
+ }
+ }
+ }
+
+ if (have_options)
+ SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+const svn_opt_subcommand_desc_t *
+svn_opt_get_canonical_subcommand(const svn_opt_subcommand_desc_t *table,
+ const char *cmd_name)
+{
+ int i = 0;
+
+ if (cmd_name == NULL)
+ return NULL;
+
+ while (table[i].name) {
+ int j;
+ if (strcmp(cmd_name, table[i].name) == 0)
+ return table + i;
+ for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
+ if (strcmp(cmd_name, table[i].aliases[j]) == 0)
+ return table + i;
+
+ i++;
+ }
+
+ /* If we get here, there was no matching subcommand name or alias. */
+ return NULL;
+}
+
+void
+svn_opt_subcommand_help2(const char *subcommand,
+ const svn_opt_subcommand_desc2_t *table,
+ const apr_getopt_option_t *options_table,
+ apr_pool_t *pool)
+{
+ svn_opt_subcommand_help3(subcommand, table, options_table,
+ NULL, pool);
+}
+
+void
+svn_opt_subcommand_help(const char *subcommand,
+ const svn_opt_subcommand_desc_t *table,
+ const apr_getopt_option_t *options_table,
+ apr_pool_t *pool)
+{
+ const svn_opt_subcommand_desc_t *cmd =
+ svn_opt_get_canonical_subcommand(table, subcommand);
+ svn_error_t *err;
+
+ if (cmd)
+ err = print_command_info(cmd, options_table, TRUE, pool, stdout);
+ else
+ err = svn_cmdline_fprintf(stderr, pool,
+ _("\"%s\": unknown command.\n\n"), subcommand);
+
+ if (err) {
+ svn_handle_error2(err, stderr, FALSE, "svn: ");
+ svn_error_clear(err);
+ }
+}
+
+svn_error_t *
+svn_opt_args_to_target_array3(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_opt__args_to_target_array(targets_p, os,
+ known_targets, pool));
+}
+
+svn_error_t *
+svn_opt_args_to_target_array2(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_opt_args_to_target_array3(targets_p, os,
+ known_targets, pool);
+
+ if (err && err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return err;
+}
+
+svn_error_t *
+svn_opt_args_to_target_array(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_opt_revision_t *start_revision,
+ svn_opt_revision_t *end_revision,
+ svn_boolean_t extract_revisions,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *output_targets;
+
+ SVN_ERR(svn_opt_args_to_target_array2(&output_targets, os,
+ known_targets, pool));
+
+ if (extract_revisions)
+ {
+ svn_opt_revision_t temprev;
+ const char *path;
+
+ if (output_targets->nelts > 0)
+ {
+ path = APR_ARRAY_IDX(output_targets, 0, const char *);
+ SVN_ERR(svn_opt_parse_path(&temprev, &path, path, pool));
+ if (temprev.kind != svn_opt_revision_unspecified)
+ {
+ APR_ARRAY_IDX(output_targets, 0, const char *) = path;
+ start_revision->kind = temprev.kind;
+ start_revision->value = temprev.value;
+ }
+ }
+ if (output_targets->nelts > 1)
+ {
+ path = APR_ARRAY_IDX(output_targets, 1, const char *);
+ SVN_ERR(svn_opt_parse_path(&temprev, &path, path, pool));
+ if (temprev.kind != svn_opt_revision_unspecified)
+ {
+ APR_ARRAY_IDX(output_targets, 1, const char *) = path;
+ end_revision->kind = temprev.kind;
+ end_revision->value = temprev.value;
+ }
+ }
+ }
+
+ *targets_p = output_targets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_opt_print_help3(apr_getopt_t *os,
+ const char *pgm_name,
+ svn_boolean_t print_version,
+ svn_boolean_t quiet,
+ const char *version_footer,
+ const char *header,
+ const svn_opt_subcommand_desc2_t *cmd_table,
+ const apr_getopt_option_t *option_table,
+ const int *global_options,
+ const char *footer,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_opt_print_help4(os,
+ pgm_name,
+ print_version,
+ quiet,
+ FALSE,
+ version_footer,
+ header,
+ cmd_table,
+ option_table,
+ global_options,
+ footer,
+ pool));
+}
+
+svn_error_t *
+svn_opt_print_help2(apr_getopt_t *os,
+ const char *pgm_name,
+ svn_boolean_t print_version,
+ svn_boolean_t quiet,
+ const char *version_footer,
+ const char *header,
+ const svn_opt_subcommand_desc2_t *cmd_table,
+ const apr_getopt_option_t *option_table,
+ const char *footer,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_opt_print_help4(os,
+ pgm_name,
+ print_version,
+ quiet,
+ FALSE,
+ version_footer,
+ header,
+ cmd_table,
+ option_table,
+ NULL,
+ footer,
+ pool));
+}
+
+svn_error_t *
+svn_opt_print_help(apr_getopt_t *os,
+ const char *pgm_name,
+ svn_boolean_t print_version,
+ svn_boolean_t quiet,
+ const char *version_footer,
+ const char *header,
+ const svn_opt_subcommand_desc_t *cmd_table,
+ const apr_getopt_option_t *option_table,
+ const char *footer,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *targets = NULL;
+
+ if (os)
+ SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
+
+ if (os && targets->nelts) /* help on subcommand(s) requested */
+ {
+ int i;
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ svn_opt_subcommand_help(APR_ARRAY_IDX(targets, i, const char *),
+ cmd_table, option_table, pool);
+ }
+ }
+ else if (print_version) /* just --version */
+ {
+ SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
+ svn_version_extended(FALSE, pool),
+ quiet, FALSE, pool));
+ }
+ else if (os && !targets->nelts) /* `-h', `--help', or `help' */
+ svn_opt_print_generic_help(header,
+ cmd_table,
+ option_table,
+ footer,
+ pool,
+ stdout);
+ else /* unknown option or cmd */
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool,
+ _("Type '%s help' for usage.\n"), pgm_name));
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_opt_print_generic_help(const char *header,
+ const svn_opt_subcommand_desc_t *cmd_table,
+ const apr_getopt_option_t *opt_table,
+ const char *footer,
+ apr_pool_t *pool, FILE *stream)
+{
+ int i = 0;
+ svn_error_t *err;
+
+ if (header)
+ if ((err = svn_cmdline_fputs(header, stream, pool)))
+ goto print_error;
+
+ while (cmd_table[i].name)
+ {
+ if ((err = svn_cmdline_fputs(" ", stream, pool))
+ || (err = print_command_info(cmd_table + i, opt_table, FALSE,
+ pool, stream))
+ || (err = svn_cmdline_fputs("\n", stream, pool)))
+ goto print_error;
+ i++;
+ }
+
+ if ((err = svn_cmdline_fputs("\n", stream, pool)))
+ goto print_error;
+
+ if (footer)
+ if ((err = svn_cmdline_fputs(footer, stream, pool)))
+ goto print_error;
+
+ return;
+
+ print_error:
+ svn_handle_error2(err, stderr, FALSE, "svn: ");
+ svn_error_clear(err);
+}
+
+/*** From io.c ***/
+svn_error_t *
+svn_io_open_unique_file2(apr_file_t **file,
+ const char **temp_path,
+ const char *path,
+ const char *suffix,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *pool)
+{
+ const char *dirpath;
+ const char *filename;
+
+ svn_path_split(path, &dirpath, &filename, pool);
+ return svn_error_trace(svn_io_open_uniquely_named(file, temp_path,
+ dirpath, filename, suffix,
+ delete_when,
+ pool, pool));
+}
+
+svn_error_t *
+svn_io_open_unique_file(apr_file_t **file,
+ const char **temp_path,
+ const char *path,
+ const char *suffix,
+ svn_boolean_t delete_on_close,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_io_open_unique_file2(file, temp_path,
+ path, suffix,
+ delete_on_close
+ ? svn_io_file_del_on_close
+ : svn_io_file_del_none,
+ pool));
+}
+
+svn_error_t *
+svn_io_run_diff(const char *dir,
+ const char *const *user_args,
+ int num_user_args,
+ const char *label1,
+ const char *label2,
+ const char *from,
+ const char *to,
+ int *pexitcode,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const char *diff_cmd,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_path_cstring_to_utf8(&diff_cmd, diff_cmd, pool));
+
+ return svn_error_trace(svn_io_run_diff2(dir, user_args, num_user_args,
+ label1, label2,
+ from, to, pexitcode,
+ outfile, errfile, diff_cmd,
+ pool));
+}
+
+svn_error_t *
+svn_io_run_diff3_2(int *exitcode,
+ const char *dir,
+ const char *mine,
+ const char *older,
+ const char *yours,
+ const char *mine_label,
+ const char *older_label,
+ const char *yours_label,
+ apr_file_t *merged,
+ const char *diff3_cmd,
+ const apr_array_header_t *user_args,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool));
+
+ return svn_error_trace(svn_io_run_diff3_3(exitcode, dir,
+ mine, older, yours,
+ mine_label, older_label,
+ yours_label, merged,
+ diff3_cmd, user_args, pool));
+}
+
+svn_error_t *
+svn_io_run_diff3(const char *dir,
+ const char *mine,
+ const char *older,
+ const char *yours,
+ const char *mine_label,
+ const char *older_label,
+ const char *yours_label,
+ apr_file_t *merged,
+ int *exitcode,
+ const char *diff3_cmd,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_io_run_diff3_2(exitcode, dir, mine, older, yours,
+ mine_label, older_label,
+ yours_label,
+ merged, diff3_cmd, NULL, pool));
+}
+
+svn_error_t *
+svn_io_remove_file(const char *path,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_io_remove_file2(path, FALSE, scratch_pool));
+}
+
+svn_error_t *svn_io_file_lock(const char *lock_file,
+ svn_boolean_t exclusive,
+ apr_pool_t *pool)
+{
+ return svn_io_file_lock2(lock_file, exclusive, FALSE, pool);
+}
+
+svn_error_t *
+svn_io_get_dirents2(apr_hash_t **dirents,
+ const char *path,
+ apr_pool_t *pool)
+{
+ /* Note that the first part of svn_io_dirent2_t is identical
+ to svn_io_dirent_t to allow this construct */
+ return svn_error_trace(
+ svn_io_get_dirents3(dirents, path, FALSE, pool, pool));
+}
+
+svn_error_t *
+svn_io_get_dirents(apr_hash_t **dirents,
+ const char *path,
+ apr_pool_t *pool)
+{
+ /* Note that in C, padding is not allowed at the beginning of structs,
+ so this is actually portable, since the kind field of svn_io_dirent_t
+ is first in that struct. */
+ return svn_io_get_dirents2(dirents, path, pool);
+}
+
+svn_error_t *
+svn_io_start_cmd2(apr_proc_t *cmd_proc,
+ const char *path,
+ const char *cmd,
+ const char *const *args,
+ svn_boolean_t inherit,
+ svn_boolean_t infile_pipe,
+ apr_file_t *infile,
+ svn_boolean_t outfile_pipe,
+ apr_file_t *outfile,
+ svn_boolean_t errfile_pipe,
+ apr_file_t *errfile,
+ apr_pool_t *pool)
+{
+ return svn_io_start_cmd3(cmd_proc, path, cmd, args, NULL, inherit,
+ infile_pipe, infile, outfile_pipe, outfile,
+ errfile_pipe, errfile, pool);
+}
+
+svn_error_t *
+svn_io_start_cmd(apr_proc_t *cmd_proc,
+ const char *path,
+ const char *cmd,
+ const char *const *args,
+ svn_boolean_t inherit,
+ apr_file_t *infile,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ apr_pool_t *pool)
+{
+ return svn_io_start_cmd2(cmd_proc, path, cmd, args, inherit, FALSE,
+ infile, FALSE, outfile, FALSE, errfile, pool);
+}
+
+svn_error_t *
+svn_io_file_read_full(apr_file_t *file, void *buf,
+ apr_size_t nbytes, apr_size_t *bytes_read,
+ apr_pool_t *pool)
+{
+ return svn_io_file_read_full2(file, buf, nbytes, bytes_read, NULL, pool);
+}
+
+struct walk_func_filter_baton_t
+{
+ svn_io_walk_func_t walk_func;
+ void *walk_baton;
+};
+
+/* Implements svn_io_walk_func_t, but only allows APR_DIR and APR_REG
+ finfo types through to the wrapped function/baton. */
+static svn_error_t *
+walk_func_filter_func(void *baton,
+ const char *path,
+ const apr_finfo_t *finfo,
+ apr_pool_t *pool)
+{
+ struct walk_func_filter_baton_t *b = baton;
+
+ if (finfo->filetype == APR_DIR || finfo->filetype == APR_REG)
+ SVN_ERR(b->walk_func(b->walk_baton, path, finfo, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_dir_walk(const char *dirname,
+ apr_int32_t wanted,
+ svn_io_walk_func_t walk_func,
+ void *walk_baton,
+ apr_pool_t *pool)
+{
+ struct walk_func_filter_baton_t baton;
+ baton.walk_func = walk_func;
+ baton.walk_baton = walk_baton;
+ return svn_error_trace(svn_io_dir_walk2(dirname, wanted,
+ walk_func_filter_func,
+ &baton, pool));
+}
+
+svn_error_t *
+svn_io_stat_dirent(const svn_io_dirent2_t **dirent_p,
+ const char *path,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_io_stat_dirent2(dirent_p,
+ path,
+ FALSE,
+ ignore_enoent,
+ result_pool,
+ scratch_pool));
+}
+
+/*** From constructors.c ***/
+svn_log_changed_path_t *
+svn_log_changed_path_dup(const svn_log_changed_path_t *changed_path,
+ apr_pool_t *pool)
+{
+ svn_log_changed_path_t *new_changed_path
+ = apr_palloc(pool, sizeof(*new_changed_path));
+
+ *new_changed_path = *changed_path;
+
+ if (new_changed_path->copyfrom_path)
+ new_changed_path->copyfrom_path =
+ apr_pstrdup(pool, new_changed_path->copyfrom_path);
+
+ return new_changed_path;
+}
+
+/*** From cmdline.c ***/
+svn_error_t *
+svn_cmdline_prompt_user(const char **result,
+ const char *prompt_str,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_cmdline_prompt_user2(result, prompt_str, NULL,
+ pool));
+}
+
+svn_error_t *
+svn_cmdline_setup_auth_baton(svn_auth_baton_t **ab,
+ svn_boolean_t non_interactive,
+ const char *auth_username,
+ const char *auth_password,
+ const char *config_dir,
+ svn_boolean_t no_auth_cache,
+ svn_config_t *cfg,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_cmdline_create_auth_baton(
+ ab, non_interactive,
+ auth_username, auth_password,
+ config_dir, no_auth_cache, FALSE,
+ cfg, cancel_func, cancel_baton, pool));
+}
+
+/*** From dso.c ***/
+void
+svn_dso_initialize(void)
+{
+ svn_error_t *err = svn_dso_initialize2();
+ if (err)
+ {
+ svn_error_clear(err);
+ abort();
+ }
+}
+
+/*** From simple_providers.c ***/
+void
+svn_auth_get_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_get_simple_provider2(provider, NULL, NULL, pool);
+}
+
+/*** From ssl_client_cert_pw_providers.c ***/
+void
+svn_auth_get_ssl_client_cert_pw_file_provider
+ (svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_get_ssl_client_cert_pw_file_provider2(provider, NULL, NULL, pool);
+}
+
+/*** From path.c ***/
+
+#define SVN_EMPTY_PATH ""
+
+const char *
+svn_path_url_add_component(const char *url,
+ const char *component,
+ apr_pool_t *pool)
+{
+ /* URL can have trailing '/' */
+ url = svn_path_canonicalize(url, pool);
+
+ return svn_path_url_add_component2(url, component, pool);
+}
+
+void
+svn_path_split(const char *path,
+ const char **dirpath,
+ const char **base_name,
+ apr_pool_t *pool)
+{
+ assert(dirpath != base_name);
+
+ if (dirpath)
+ *dirpath = svn_path_dirname(path, pool);
+
+ if (base_name)
+ *base_name = svn_path_basename(path, pool);
+}
+
+
+svn_error_t *
+svn_path_split_if_file(const char *path,
+ const char **pdirectory,
+ const char **pfile,
+ apr_pool_t *pool)
+{
+ apr_finfo_t finfo;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_path_is_canonical(path, pool));
+
+ err = svn_io_stat(&finfo, path, APR_FINFO_TYPE, pool);
+ if (err && ! APR_STATUS_IS_ENOENT(err->apr_err))
+ return err;
+
+ if (err || finfo.filetype == APR_REG)
+ {
+ svn_error_clear(err);
+ svn_path_split(path, pdirectory, pfile, pool);
+ }
+ else if (finfo.filetype == APR_DIR)
+ {
+ *pdirectory = path;
+ *pfile = SVN_EMPTY_PATH;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
+ _("'%s' is neither a file nor a directory name"),
+ svn_path_local_style(path, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*** From stream.c ***/
+svn_error_t *svn_stream_copy2(svn_stream_t *from, svn_stream_t *to,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_stream_copy3(
+ svn_stream_disown(from, scratch_pool),
+ svn_stream_disown(to, scratch_pool),
+ cancel_func, cancel_baton, scratch_pool));
+}
+
+svn_error_t *svn_stream_copy(svn_stream_t *from, svn_stream_t *to,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_stream_copy3(
+ svn_stream_disown(from, scratch_pool),
+ svn_stream_disown(to, scratch_pool),
+ NULL, NULL, scratch_pool));
+}
+
+svn_stream_t *
+svn_stream_from_aprfile(apr_file_t *file, apr_pool_t *pool)
+{
+ return svn_stream_from_aprfile2(file, TRUE, pool);
+}
+
+svn_error_t *
+svn_stream_contents_same(svn_boolean_t *same,
+ svn_stream_t *stream1,
+ svn_stream_t *stream2,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_stream_contents_same2(
+ same,
+ svn_stream_disown(stream1, pool),
+ svn_stream_disown(stream2, pool),
+ pool));
+}
+
+/*** From path.c ***/
+
+const char *
+svn_path_internal_style(const char *path, apr_pool_t *pool)
+{
+ if (svn_path_is_url(path))
+ return svn_uri_canonicalize(path, pool);
+ else
+ return svn_dirent_internal_style(path, pool);
+}
+
+
+const char *
+svn_path_local_style(const char *path, apr_pool_t *pool)
+{
+ if (svn_path_is_url(path))
+ return apr_pstrdup(pool, path);
+ else
+ return svn_dirent_local_style(path, pool);
+}
+
+const char *
+svn_path_canonicalize(const char *path, apr_pool_t *pool)
+{
+ if (svn_path_is_url(path))
+ return svn_uri_canonicalize(path, pool);
+ else
+ return svn_dirent_canonicalize(path, pool);
+}
+
+
+/*** From mergeinfo.c ***/
+
+svn_error_t *
+svn_mergeinfo_inheritable(svn_mergeinfo_t *output,
+ svn_mergeinfo_t mergeinfo,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_mergeinfo_inheritable2(output, mergeinfo, path,
+ start, end,
+ TRUE, pool, pool));
+}
+
+svn_error_t *
+svn_rangelist_inheritable(svn_rangelist_t **inheritable_rangelist,
+ const svn_rangelist_t *rangelist,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_rangelist_inheritable2(inheritable_rangelist,
+ rangelist,
+ start, end, TRUE,
+ pool, pool));
+}
+
+svn_error_t *
+svn_rangelist_merge(svn_rangelist_t **rangelist,
+ const svn_rangelist_t *changes,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_rangelist_merge2(*rangelist, changes,
+ pool, pool));
+
+ return svn_error_trace(
+ svn_rangelist__combine_adjacent_ranges(*rangelist, pool));
+}
+
+svn_error_t *
+svn_mergeinfo_diff(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
+ svn_mergeinfo_t from, svn_mergeinfo_t to,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_mergeinfo_diff2(deleted, added, from, to,
+ consider_inheritance, pool,
+ pool));
+}
+
+svn_error_t *
+svn_mergeinfo_merge(svn_mergeinfo_t mergeinfo,
+ svn_mergeinfo_t changes,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_mergeinfo_merge2(mergeinfo, changes, pool,
+ pool));
+}
+
+svn_error_t *
+svn_mergeinfo_remove(svn_mergeinfo_t *mergeinfo, svn_mergeinfo_t eraser,
+ svn_mergeinfo_t whiteboard, apr_pool_t *pool)
+{
+ return svn_mergeinfo_remove2(mergeinfo, eraser, whiteboard, TRUE, pool,
+ pool);
+}
+
+svn_error_t *
+svn_mergeinfo_intersect(svn_mergeinfo_t *mergeinfo,
+ svn_mergeinfo_t mergeinfo1,
+ svn_mergeinfo_t mergeinfo2,
+ apr_pool_t *pool)
+{
+ return svn_mergeinfo_intersect2(mergeinfo, mergeinfo1, mergeinfo2,
+ TRUE, pool, pool);
+}
+
+/*** From config.c ***/
+svn_error_t *
+svn_config_create(svn_config_t **cfgp,
+ svn_boolean_t section_names_case_sensitive,
+ apr_pool_t *result_pool)
+{
+ return svn_error_trace(svn_config_create2(cfgp,
+ section_names_case_sensitive,
+ FALSE,
+ result_pool));
+}
+
+svn_error_t *
+svn_config_read2(svn_config_t **cfgp, const char *file,
+ svn_boolean_t must_exist,
+ svn_boolean_t section_names_case_sensitive,
+ apr_pool_t *result_pool)
+{
+ return svn_error_trace(svn_config_read3(cfgp, file,
+ must_exist,
+ section_names_case_sensitive,
+ FALSE,
+ result_pool));
+}
+
+svn_error_t *
+svn_config_read(svn_config_t **cfgp, const char *file,
+ svn_boolean_t must_exist,
+ apr_pool_t *result_pool)
+{
+ return svn_error_trace(svn_config_read3(cfgp, file,
+ must_exist,
+ FALSE, FALSE,
+ result_pool));
+}
+
+#ifdef SVN_DISABLE_FULL_VERSION_MATCH
+/* This double underscore name is used by the 1.6 command line client.
+ Keeping this name is sufficient for the 1.6 client to use the 1.7
+ libraries at runtime. */
+svn_error_t *
+svn_opt__eat_peg_revisions(apr_array_header_t **true_targets_p,
+ apr_array_header_t *targets,
+ apr_pool_t *pool);
+svn_error_t *
+svn_opt__eat_peg_revisions(apr_array_header_t **true_targets_p,
+ apr_array_header_t *targets,
+ apr_pool_t *pool)
+{
+ unsigned int i;
+ apr_array_header_t *true_targets;
+
+ true_targets = apr_array_make(pool, 5, sizeof(const char *));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *true_target;
+
+ SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, NULL,
+ target, pool));
+ APR_ARRAY_PUSH(true_targets, const char *) = true_target;
+ }
+
+ SVN_ERR_ASSERT(true_targets_p);
+ *true_targets_p = true_targets;
+
+ return SVN_NO_ERROR;
+}
+#endif
+
+void
+svn_xml_make_header(svn_stringbuf_t **str, apr_pool_t *pool)
+{
+ svn_xml_make_header2(str, NULL, pool);
+}
+
+void
+svn_utf_initialize(apr_pool_t *pool)
+{
+ svn_utf_initialize2(FALSE, pool);
+}
+
+svn_error_t *
+svn_subst_build_keywords(svn_subst_keywords_t *kw,
+ const char *keywords_val,
+ const char *rev,
+ const char *url,
+ apr_time_t date,
+ const char *author,
+ apr_pool_t *pool)
+{
+ apr_hash_t *kwhash;
+ const svn_string_t *val;
+
+ SVN_ERR(svn_subst_build_keywords2(&kwhash, keywords_val, rev,
+ url, date, author, pool));
+
+ /* The behaviour of pre-1.3 svn_subst_build_keywords, which we are
+ * replicating here, is to write to a slot in the svn_subst_keywords_t
+ * only if the relevant keyword was present in keywords_val, otherwise
+ * leaving that slot untouched. */
+
+ val = svn_hash_gets(kwhash, SVN_KEYWORD_REVISION_LONG);
+ if (val)
+ kw->revision = val;
+
+ val = svn_hash_gets(kwhash, SVN_KEYWORD_DATE_LONG);
+ if (val)
+ kw->date = val;
+
+ val = svn_hash_gets(kwhash, SVN_KEYWORD_AUTHOR_LONG);
+ if (val)
+ kw->author = val;
+
+ val = svn_hash_gets(kwhash, SVN_KEYWORD_URL_LONG);
+ if (val)
+ kw->url = val;
+
+ val = svn_hash_gets(kwhash, SVN_KEYWORD_ID);
+ if (val)
+ kw->id = val;
+
+ return SVN_NO_ERROR;
+}
+
+
diff --git a/contrib/subversion/subversion/libsvn_subr/dirent_uri.c b/contrib/subversion/subversion/libsvn_subr/dirent_uri.c
new file mode 100644
index 0000000..2b51e7a
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/dirent_uri.c
@@ -0,0 +1,2597 @@
+/*
+ * dirent_uri.c: a library to manipulate URIs and directory entries.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include <apr_uri.h>
+#include <apr_lib.h>
+
+#include "svn_private_config.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_ctype.h"
+
+#include "dirent_uri.h"
+#include "private/svn_fspath.h"
+
+/* The canonical empty path. Can this be changed? Well, change the empty
+ test below and the path library will work, not so sure about the fs/wc
+ libraries. */
+#define SVN_EMPTY_PATH ""
+
+/* TRUE if s is the canonical empty path, FALSE otherwise */
+#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
+
+/* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can
+ this be changed? Well, the path library will work, not so sure about
+ the OS! */
+#define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.')
+
+/* This check must match the check on top of dirent_uri-tests.c and
+ path-tests.c */
+#if defined(WIN32) || defined(__CYGWIN__) || defined(__OS2__)
+#define SVN_USE_DOS_PATHS
+#endif
+
+/* Path type definition. Used only by internal functions. */
+typedef enum path_type_t {
+ type_uri,
+ type_dirent,
+ type_relpath
+} path_type_t;
+
+
+/**** Forward declarations *****/
+
+static svn_boolean_t
+relpath_is_canonical(const char *relpath);
+
+
+/**** Internal implementation functions *****/
+
+/* Return an internal-style new path based on PATH, allocated in POOL.
+ *
+ * "Internal-style" means that separators are all '/'.
+ */
+static const char *
+internal_style(const char *path, apr_pool_t *pool)
+{
+#if '/' != SVN_PATH_LOCAL_SEPARATOR
+ {
+ char *p = apr_pstrdup(pool, path);
+ path = p;
+
+ /* Convert all local-style separators to the canonical ones. */
+ for (; *p != '\0'; ++p)
+ if (*p == SVN_PATH_LOCAL_SEPARATOR)
+ *p = '/';
+ }
+#endif
+
+ return path;
+}
+
+/* Locale insensitive tolower() for converting parts of dirents and urls
+ while canonicalizing */
+static char
+canonicalize_to_lower(char c)
+{
+ if (c < 'A' || c > 'Z')
+ return c;
+ else
+ return (char)(c - 'A' + 'a');
+}
+
+/* Locale insensitive toupper() for converting parts of dirents and urls
+ while canonicalizing */
+static char
+canonicalize_to_upper(char c)
+{
+ if (c < 'a' || c > 'z')
+ return c;
+ else
+ return (char)(c - 'a' + 'A');
+}
+
+/* Calculates the length of the dirent absolute or non absolute root in
+ DIRENT, return 0 if dirent is not rooted */
+static apr_size_t
+dirent_root_length(const char *dirent, apr_size_t len)
+{
+#ifdef SVN_USE_DOS_PATHS
+ if (len >= 2 && dirent[1] == ':' &&
+ ((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
+ (dirent[0] >= 'a' && dirent[0] <= 'z')))
+ {
+ return (len > 2 && dirent[2] == '/') ? 3 : 2;
+ }
+
+ if (len > 2 && dirent[0] == '/' && dirent[1] == '/')
+ {
+ apr_size_t i = 2;
+
+ while (i < len && dirent[i] != '/')
+ i++;
+
+ if (i == len)
+ return len; /* Cygwin drive alias, invalid path on WIN32 */
+
+ i++; /* Skip '/' */
+
+ while (i < len && dirent[i] != '/')
+ i++;
+
+ return i;
+ }
+#endif /* SVN_USE_DOS_PATHS */
+ if (len >= 1 && dirent[0] == '/')
+ return 1;
+
+ return 0;
+}
+
+
+/* Return the length of substring necessary to encompass the entire
+ * previous dirent segment in DIRENT, which should be a LEN byte string.
+ *
+ * A trailing slash will not be included in the returned length except
+ * in the case in which DIRENT is absolute and there are no more
+ * previous segments.
+ */
+static apr_size_t
+dirent_previous_segment(const char *dirent,
+ apr_size_t len)
+{
+ if (len == 0)
+ return 0;
+
+ --len;
+ while (len > 0 && dirent[len] != '/'
+#ifdef SVN_USE_DOS_PATHS
+ && (dirent[len] != ':' || len != 1)
+#endif /* SVN_USE_DOS_PATHS */
+ )
+ --len;
+
+ /* check if the remaining segment including trailing '/' is a root dirent */
+ if (dirent_root_length(dirent, len+1) == len + 1)
+ return len + 1;
+ else
+ return len;
+}
+
+/* Calculates the length occupied by the schema defined root of URI */
+static apr_size_t
+uri_schema_root_length(const char *uri, apr_size_t len)
+{
+ apr_size_t i;
+
+ for (i = 0; i < len; i++)
+ {
+ if (uri[i] == '/')
+ {
+ if (i > 0 && uri[i-1] == ':' && i < len-1 && uri[i+1] == '/')
+ {
+ /* We have an absolute uri */
+ if (i == 5 && strncmp("file", uri, 4) == 0)
+ return 7; /* file:// */
+ else
+ {
+ for (i += 2; i < len; i++)
+ if (uri[i] == '/')
+ return i;
+
+ return len; /* Only a hostname is found */
+ }
+ }
+ else
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+/* Returns TRUE if svn_dirent_is_absolute(dirent) or when dirent has
+ a non absolute root. (E.g. '/' or 'F:' on Windows) */
+static svn_boolean_t
+dirent_is_rooted(const char *dirent)
+{
+ if (! dirent)
+ return FALSE;
+
+ /* Root on all systems */
+ if (dirent[0] == '/')
+ return TRUE;
+
+ /* On Windows, dirent is also absolute when it starts with 'H:' or 'H:/'
+ where 'H' is any letter. */
+#ifdef SVN_USE_DOS_PATHS
+ if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
+ (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
+ (dirent[1] == ':'))
+ return TRUE;
+#endif /* SVN_USE_DOS_PATHS */
+
+ return FALSE;
+}
+
+/* Return the length of substring necessary to encompass the entire
+ * previous relpath segment in RELPATH, which should be a LEN byte string.
+ *
+ * A trailing slash will not be included in the returned length.
+ */
+static apr_size_t
+relpath_previous_segment(const char *relpath,
+ apr_size_t len)
+{
+ if (len == 0)
+ return 0;
+
+ --len;
+ while (len > 0 && relpath[len] != '/')
+ --len;
+
+ return len;
+}
+
+/* Return the length of substring necessary to encompass the entire
+ * previous uri segment in URI, which should be a LEN byte string.
+ *
+ * A trailing slash will not be included in the returned length except
+ * in the case in which URI is absolute and there are no more
+ * previous segments.
+ */
+static apr_size_t
+uri_previous_segment(const char *uri,
+ apr_size_t len)
+{
+ apr_size_t root_length;
+ apr_size_t i = len;
+ if (len == 0)
+ return 0;
+
+ root_length = uri_schema_root_length(uri, len);
+
+ --i;
+ while (len > root_length && uri[i] != '/')
+ --i;
+
+ if (i == 0 && len > 1 && *uri == '/')
+ return 1;
+
+ return i;
+}
+
+/* Return the canonicalized version of PATH, of type TYPE, allocated in
+ * POOL.
+ */
+static const char *
+canonicalize(path_type_t type, const char *path, apr_pool_t *pool)
+{
+ char *canon, *dst;
+ const char *src;
+ apr_size_t seglen;
+ apr_size_t schemelen = 0;
+ apr_size_t canon_segments = 0;
+ svn_boolean_t url = FALSE;
+ char *schema_data = NULL;
+
+ /* "" is already canonical, so just return it; note that later code
+ depends on path not being zero-length. */
+ if (SVN_PATH_IS_EMPTY(path))
+ {
+ assert(type != type_uri);
+ return "";
+ }
+
+ dst = canon = apr_pcalloc(pool, strlen(path) + 1);
+
+ /* If this is supposed to be an URI, it should start with
+ "scheme://". We'll copy the scheme, host name, etc. to DST and
+ set URL = TRUE. */
+ src = path;
+ if (type == type_uri)
+ {
+ assert(*src != '/');
+
+ while (*src && (*src != '/') && (*src != ':'))
+ src++;
+
+ if (*src == ':' && *(src+1) == '/' && *(src+2) == '/')
+ {
+ const char *seg;
+
+ url = TRUE;
+
+ /* Found a scheme, convert to lowercase and copy to dst. */
+ src = path;
+ while (*src != ':')
+ {
+ *(dst++) = canonicalize_to_lower((*src++));
+ schemelen++;
+ }
+ *(dst++) = ':';
+ *(dst++) = '/';
+ *(dst++) = '/';
+ src += 3;
+ schemelen += 3;
+
+ /* This might be the hostname */
+ seg = src;
+ while (*src && (*src != '/') && (*src != '@'))
+ src++;
+
+ if (*src == '@')
+ {
+ /* Copy the username & password. */
+ seglen = src - seg + 1;
+ memcpy(dst, seg, seglen);
+ dst += seglen;
+ src++;
+ }
+ else
+ src = seg;
+
+ /* Found a hostname, convert to lowercase and copy to dst. */
+ if (*src == '[')
+ {
+ *(dst++) = *(src++); /* Copy '[' */
+
+ while (*src == ':'
+ || (*src >= '0' && (*src <= '9'))
+ || (*src >= 'a' && (*src <= 'f'))
+ || (*src >= 'A' && (*src <= 'F')))
+ {
+ *(dst++) = canonicalize_to_lower((*src++));
+ }
+
+ if (*src == ']')
+ *(dst++) = *(src++); /* Copy ']' */
+ }
+ else
+ while (*src && (*src != '/') && (*src != ':'))
+ *(dst++) = canonicalize_to_lower((*src++));
+
+ if (*src == ':')
+ {
+ /* We probably have a port number: Is it a default portnumber
+ which doesn't belong in a canonical url? */
+ if (src[1] == '8' && src[2] == '0'
+ && (src[3]== '/'|| !src[3])
+ && !strncmp(canon, "http:", 5))
+ {
+ src += 3;
+ }
+ else if (src[1] == '4' && src[2] == '4' && src[3] == '3'
+ && (src[4]== '/'|| !src[4])
+ && !strncmp(canon, "https:", 6))
+ {
+ src += 4;
+ }
+ else if (src[1] == '3' && src[2] == '6'
+ && src[3] == '9' && src[4] == '0'
+ && (src[5]== '/'|| !src[5])
+ && !strncmp(canon, "svn:", 4))
+ {
+ src += 5;
+ }
+ else if (src[1] == '/' || !src[1])
+ {
+ src += 1;
+ }
+
+ while (*src && (*src != '/'))
+ *(dst++) = canonicalize_to_lower((*src++));
+ }
+
+ /* Copy trailing slash, or null-terminator. */
+ *(dst) = *(src);
+
+ /* Move src and dst forward only if we are not
+ * at null-terminator yet. */
+ if (*src)
+ {
+ src++;
+ dst++;
+ schema_data = dst;
+ }
+
+ canon_segments = 1;
+ }
+ }
+
+ /* Copy to DST any separator or drive letter that must come before the
+ first regular path segment. */
+ if (! url && type != type_relpath)
+ {
+ src = path;
+ /* If this is an absolute path, then just copy over the initial
+ separator character. */
+ if (*src == '/')
+ {
+ *(dst++) = *(src++);
+
+#ifdef SVN_USE_DOS_PATHS
+ /* On Windows permit two leading separator characters which means an
+ * UNC path. */
+ if ((type == type_dirent) && *src == '/')
+ *(dst++) = *(src++);
+#endif /* SVN_USE_DOS_PATHS */
+ }
+#ifdef SVN_USE_DOS_PATHS
+ /* On Windows the first segment can be a drive letter, which we normalize
+ to upper case. */
+ else if (type == type_dirent &&
+ ((*src >= 'a' && *src <= 'z') ||
+ (*src >= 'A' && *src <= 'Z')) &&
+ (src[1] == ':'))
+ {
+ *(dst++) = canonicalize_to_upper(*(src++));
+ /* Leave the ':' to be processed as (or as part of) a path segment
+ by the following code block, so we need not care whether it has
+ a slash after it. */
+ }
+#endif /* SVN_USE_DOS_PATHS */
+ }
+
+ while (*src)
+ {
+ /* Parse each segment, finding the closing '/' (which might look
+ like '%2F' for URIs). */
+ const char *next = src;
+ apr_size_t slash_len = 0;
+
+ while (*next
+ && (next[0] != '/')
+ && (! (type == type_uri && next[0] == '%' && next[1] == '2' &&
+ canonicalize_to_upper(next[2]) == 'F')))
+ {
+ ++next;
+ }
+
+ /* Record how long our "slash" is. */
+ if (next[0] == '/')
+ slash_len = 1;
+ else if (type == type_uri && next[0] == '%')
+ slash_len = 3;
+
+ seglen = next - src;
+
+ if (seglen == 0
+ || (seglen == 1 && src[0] == '.')
+ || (type == type_uri && seglen == 3 && src[0] == '%' && src[1] == '2'
+ && canonicalize_to_upper(src[2]) == 'E'))
+ {
+ /* Empty or noop segment, so do nothing. (For URIs, '%2E'
+ is equivalent to '.'). */
+ }
+#ifdef SVN_USE_DOS_PATHS
+ /* If this is the first path segment of a file:// URI and it contains a
+ windows drive letter, convert the drive letter to upper case. */
+ else if (url && canon_segments == 1 && seglen == 2 &&
+ (strncmp(canon, "file:", 5) == 0) &&
+ src[0] >= 'a' && src[0] <= 'z' && src[1] == ':')
+ {
+ *(dst++) = canonicalize_to_upper(src[0]);
+ *(dst++) = ':';
+ if (*next)
+ *(dst++) = *next;
+ canon_segments++;
+ }
+#endif /* SVN_USE_DOS_PATHS */
+ else
+ {
+ /* An actual segment, append it to the destination path */
+ memcpy(dst, src, seglen);
+ dst += seglen;
+ if (slash_len)
+ *(dst++) = '/';
+ canon_segments++;
+ }
+
+ /* Skip over trailing slash to the next segment. */
+ src = next + slash_len;
+ }
+
+ /* Remove the trailing slash if there was at least one
+ * canonical segment and the last segment ends with a slash.
+ *
+ * But keep in mind that, for URLs, the scheme counts as a
+ * canonical segment -- so if path is ONLY a scheme (such
+ * as "https://") we should NOT remove the trailing slash. */
+ if ((canon_segments > 0 && *(dst - 1) == '/')
+ && ! (url && path[schemelen] == '\0'))
+ {
+ dst --;
+ }
+
+ *dst = '\0';
+
+#ifdef SVN_USE_DOS_PATHS
+ /* Skip leading double slashes when there are less than 2
+ * canon segments. UNC paths *MUST* have two segments. */
+ if ((type == type_dirent) && canon[0] == '/' && canon[1] == '/')
+ {
+ if (canon_segments < 2)
+ return canon + 1;
+ else
+ {
+ /* Now we're sure this is a valid UNC path, convert the server name
+ (the first path segment) to lowercase as Windows treats it as case
+ insensitive.
+ Note: normally the share name is treated as case insensitive too,
+ but it seems to be possible to configure Samba to treat those as
+ case sensitive, so better leave that alone. */
+ for (dst = canon + 2; *dst && *dst != '/'; dst++)
+ *dst = canonicalize_to_lower(*dst);
+ }
+ }
+#endif /* SVN_USE_DOS_PATHS */
+
+ /* Check the normalization of characters in a uri */
+ if (schema_data)
+ {
+ int need_extra = 0;
+ src = schema_data;
+
+ while (*src)
+ {
+ switch (*src)
+ {
+ case '/':
+ break;
+ case '%':
+ if (!svn_ctype_isxdigit(*(src+1)) ||
+ !svn_ctype_isxdigit(*(src+2)))
+ need_extra += 2;
+ else
+ src += 2;
+ break;
+ default:
+ if (!svn_uri__char_validity[(unsigned char)*src])
+ need_extra += 2;
+ break;
+ }
+ src++;
+ }
+
+ if (need_extra > 0)
+ {
+ apr_size_t pre_schema_size = (apr_size_t)(schema_data - canon);
+
+ dst = apr_palloc(pool, (apr_size_t)(src - canon) + need_extra + 1);
+ memcpy(dst, canon, pre_schema_size);
+ canon = dst;
+
+ dst += pre_schema_size;
+ }
+ else
+ dst = schema_data;
+
+ src = schema_data;
+
+ while (*src)
+ {
+ switch (*src)
+ {
+ case '/':
+ *(dst++) = '/';
+ break;
+ case '%':
+ if (!svn_ctype_isxdigit(*(src+1)) ||
+ !svn_ctype_isxdigit(*(src+2)))
+ {
+ *(dst++) = '%';
+ *(dst++) = '2';
+ *(dst++) = '5';
+ }
+ else
+ {
+ char digitz[3];
+ int val;
+
+ digitz[0] = *(++src);
+ digitz[1] = *(++src);
+ digitz[2] = 0;
+
+ val = (int)strtol(digitz, NULL, 16);
+
+ if (svn_uri__char_validity[(unsigned char)val])
+ *(dst++) = (char)val;
+ else
+ {
+ *(dst++) = '%';
+ *(dst++) = canonicalize_to_upper(digitz[0]);
+ *(dst++) = canonicalize_to_upper(digitz[1]);
+ }
+ }
+ break;
+ default:
+ if (!svn_uri__char_validity[(unsigned char)*src])
+ {
+ apr_snprintf(dst, 4, "%%%02X", (unsigned char)*src);
+ dst += 3;
+ }
+ else
+ *(dst++) = *src;
+ break;
+ }
+ src++;
+ }
+ *dst = '\0';
+ }
+
+ return canon;
+}
+
+/* Return the string length of the longest common ancestor of PATH1 and PATH2.
+ * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if
+ * PATH1 and PATH2 are regular paths.
+ *
+ * If the two paths do not share a common ancestor, return 0.
+ *
+ * New strings are allocated in POOL.
+ */
+static apr_size_t
+get_longest_ancestor_length(path_type_t types,
+ const char *path1,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ apr_size_t path1_len, path2_len;
+ apr_size_t i = 0;
+ apr_size_t last_dirsep = 0;
+#ifdef SVN_USE_DOS_PATHS
+ svn_boolean_t unc = FALSE;
+#endif
+
+ path1_len = strlen(path1);
+ path2_len = strlen(path2);
+
+ if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2))
+ return 0;
+
+ while (path1[i] == path2[i])
+ {
+ /* Keep track of the last directory separator we hit. */
+ if (path1[i] == '/')
+ last_dirsep = i;
+
+ i++;
+
+ /* If we get to the end of either path, break out. */
+ if ((i == path1_len) || (i == path2_len))
+ break;
+ }
+
+ /* two special cases:
+ 1. '/' is the longest common ancestor of '/' and '/foo' */
+ if (i == 1 && path1[0] == '/' && path2[0] == '/')
+ return 1;
+ /* 2. '' is the longest common ancestor of any non-matching
+ * strings 'foo' and 'bar' */
+ if (types == type_dirent && i == 0)
+ return 0;
+
+ /* Handle some windows specific cases */
+#ifdef SVN_USE_DOS_PATHS
+ if (types == type_dirent)
+ {
+ /* don't count the '//' from UNC paths */
+ if (last_dirsep == 1 && path1[0] == '/' && path1[1] == '/')
+ {
+ last_dirsep = 0;
+ unc = TRUE;
+ }
+
+ /* X:/ and X:/foo */
+ if (i == 3 && path1[2] == '/' && path1[1] == ':')
+ return i;
+
+ /* Cannot use SVN_ERR_ASSERT here, so we'll have to crash, sorry.
+ * Note that this assertion triggers only if the code above has
+ * been broken. The code below relies on this assertion, because
+ * it uses [i - 1] as index. */
+ assert(i > 0);
+
+ /* X: and X:/ */
+ if ((path1[i - 1] == ':' && path2[i] == '/') ||
+ (path2[i - 1] == ':' && path1[i] == '/'))
+ return 0;
+ /* X: and X:foo */
+ if (path1[i - 1] == ':' || path2[i - 1] == ':')
+ return i;
+ }
+#endif /* SVN_USE_DOS_PATHS */
+
+ /* last_dirsep is now the offset of the last directory separator we
+ crossed before reaching a non-matching byte. i is the offset of
+ that non-matching byte, and is guaranteed to be <= the length of
+ whichever path is shorter.
+ If one of the paths is the common part return that. */
+ if (((i == path1_len) && (path2[i] == '/'))
+ || ((i == path2_len) && (path1[i] == '/'))
+ || ((i == path1_len) && (i == path2_len)))
+ return i;
+ else
+ {
+ /* Nothing in common but the root folder '/' or 'X:/' for Windows
+ dirents. */
+#ifdef SVN_USE_DOS_PATHS
+ if (! unc)
+ {
+ /* X:/foo and X:/bar returns X:/ */
+ if ((types == type_dirent) &&
+ last_dirsep == 2 && path1[1] == ':' && path1[2] == '/'
+ && path2[1] == ':' && path2[2] == '/')
+ return 3;
+#endif /* SVN_USE_DOS_PATHS */
+ if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/')
+ return 1;
+#ifdef SVN_USE_DOS_PATHS
+ }
+#endif
+ }
+
+ return last_dirsep;
+}
+
+/* Determine whether PATH2 is a child of PATH1.
+ *
+ * PATH2 is a child of PATH1 if
+ * 1) PATH1 is empty, and PATH2 is not empty and not an absolute path.
+ * or
+ * 2) PATH2 is has n components, PATH1 has x < n components,
+ * and PATH1 matches PATH2 in all its x components.
+ * Components are separated by a slash, '/'.
+ *
+ * Pass type_uri for TYPE if PATH1 and PATH2 are URIs, and type_dirent if
+ * PATH1 and PATH2 are regular paths.
+ *
+ * If PATH2 is not a child of PATH1, return NULL.
+ *
+ * If PATH2 is a child of PATH1, and POOL is not NULL, allocate a copy
+ * of the child part of PATH2 in POOL and return a pointer to the
+ * newly allocated child part.
+ *
+ * If PATH2 is a child of PATH1, and POOL is NULL, return a pointer
+ * pointing to the child part of PATH2.
+ * */
+static const char *
+is_child(path_type_t type, const char *path1, const char *path2,
+ apr_pool_t *pool)
+{
+ apr_size_t i;
+
+ /* Allow "" and "foo" or "H:foo" to be parent/child */
+ if (SVN_PATH_IS_EMPTY(path1)) /* "" is the parent */
+ {
+ if (SVN_PATH_IS_EMPTY(path2)) /* "" not a child */
+ return NULL;
+
+ /* check if this is an absolute path */
+ if ((type == type_uri) ||
+ (type == type_dirent && dirent_is_rooted(path2)))
+ return NULL;
+ else
+ /* everything else is child */
+ return pool ? apr_pstrdup(pool, path2) : path2;
+ }
+
+ /* Reach the end of at least one of the paths. How should we handle
+ things like path1:"foo///bar" and path2:"foo/bar/baz"? It doesn't
+ appear to arise in the current Subversion code, it's not clear to me
+ if they should be parent/child or not. */
+ /* Hmmm... aren't paths assumed to be canonical in this function?
+ * How can "foo///bar" even happen if the paths are canonical? */
+ for (i = 0; path1[i] && path2[i]; i++)
+ if (path1[i] != path2[i])
+ return NULL;
+
+ /* FIXME: This comment does not really match
+ * the checks made in the code it refers to: */
+ /* There are two cases that are parent/child
+ ... path1[i] == '\0'
+ .../foo path2[i] == '/'
+ or
+ / path1[i] == '\0'
+ /foo path2[i] != '/'
+
+ Other root paths (like X:/) fall under the former case:
+ X:/ path1[i] == '\0'
+ X:/foo path2[i] != '/'
+
+ Check for '//' to avoid matching '/' and '//srv'.
+ */
+ if (path1[i] == '\0' && path2[i])
+ {
+ if (path1[i - 1] == '/'
+#ifdef SVN_USE_DOS_PATHS
+ || ((type == type_dirent) && path1[i - 1] == ':')
+#endif
+ )
+ {
+ if (path2[i] == '/')
+ /* .../
+ * ..../
+ * i */
+ return NULL;
+ else
+ /* .../
+ * .../foo
+ * i */
+ return pool ? apr_pstrdup(pool, path2 + i) : path2 + i;
+ }
+ else if (path2[i] == '/')
+ {
+ if (path2[i + 1])
+ /* ...
+ * .../foo
+ * i */
+ return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1;
+ else
+ /* ...
+ * .../
+ * i */
+ return NULL;
+ }
+ }
+
+ /* Otherwise, path2 isn't a child. */
+ return NULL;
+}
+
+
+/**** Public API functions ****/
+
+const char *
+svn_dirent_internal_style(const char *dirent, apr_pool_t *pool)
+{
+ return svn_dirent_canonicalize(internal_style(dirent, pool), pool);
+}
+
+const char *
+svn_dirent_local_style(const char *dirent, apr_pool_t *pool)
+{
+ /* Internally, Subversion represents the current directory with the
+ empty string. But users like to see "." . */
+ if (SVN_PATH_IS_EMPTY(dirent))
+ return ".";
+
+#if '/' != SVN_PATH_LOCAL_SEPARATOR
+ {
+ char *p = apr_pstrdup(pool, dirent);
+ dirent = p;
+
+ /* Convert all canonical separators to the local-style ones. */
+ for (; *p != '\0'; ++p)
+ if (*p == '/')
+ *p = SVN_PATH_LOCAL_SEPARATOR;
+ }
+#endif
+
+ return dirent;
+}
+
+const char *
+svn_relpath__internal_style(const char *relpath,
+ apr_pool_t *pool)
+{
+ return svn_relpath_canonicalize(internal_style(relpath, pool), pool);
+}
+
+
+/* We decided against using apr_filepath_root here because of the negative
+ performance impact (creating a pool and converting strings ). */
+svn_boolean_t
+svn_dirent_is_root(const char *dirent, apr_size_t len)
+{
+#ifdef SVN_USE_DOS_PATHS
+ /* On Windows and Cygwin, 'H:' or 'H:/' (where 'H' is any letter)
+ are also root directories */
+ if ((len == 2 || ((len == 3) && (dirent[2] == '/'))) &&
+ (dirent[1] == ':') &&
+ ((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
+ (dirent[0] >= 'a' && dirent[0] <= 'z')))
+ return TRUE;
+
+ /* On Windows and Cygwin //server/share is a root directory,
+ and on Cygwin //drive is a drive alias */
+ if (len >= 2 && dirent[0] == '/' && dirent[1] == '/'
+ && dirent[len - 1] != '/')
+ {
+ int segments = 0;
+ apr_size_t i;
+ for (i = len; i >= 2; i--)
+ {
+ if (dirent[i] == '/')
+ {
+ segments ++;
+ if (segments > 1)
+ return FALSE;
+ }
+ }
+#ifdef __CYGWIN__
+ return (segments <= 1);
+#else
+ return (segments == 1); /* //drive is invalid on plain Windows */
+#endif
+ }
+#endif
+
+ /* directory is root if it's equal to '/' */
+ if (len == 1 && dirent[0] == '/')
+ return TRUE;
+
+ return FALSE;
+}
+
+svn_boolean_t
+svn_uri_is_root(const char *uri, apr_size_t len)
+{
+ assert(svn_uri_is_canonical(uri, NULL));
+ return (len == uri_schema_root_length(uri, len));
+}
+
+char *svn_dirent_join(const char *base,
+ const char *component,
+ apr_pool_t *pool)
+{
+ apr_size_t blen = strlen(base);
+ apr_size_t clen = strlen(component);
+ char *dirent;
+ int add_separator;
+
+ assert(svn_dirent_is_canonical(base, pool));
+ assert(svn_dirent_is_canonical(component, pool));
+
+ /* If the component is absolute, then return it. */
+ if (svn_dirent_is_absolute(component))
+ return apr_pmemdup(pool, component, clen + 1);
+
+ /* If either is empty return the other */
+ if (SVN_PATH_IS_EMPTY(base))
+ return apr_pmemdup(pool, component, clen + 1);
+ if (SVN_PATH_IS_EMPTY(component))
+ return apr_pmemdup(pool, base, blen + 1);
+
+#ifdef SVN_USE_DOS_PATHS
+ if (component[0] == '/')
+ {
+ /* '/' is drive relative on Windows, not absolute like on Posix */
+ if (dirent_is_rooted(base))
+ {
+ /* Join component without '/' to root-of(base) */
+ blen = dirent_root_length(base, blen);
+ component++;
+ clen--;
+
+ if (blen == 2 && base[1] == ':') /* "C:" case */
+ {
+ char *root = apr_pmemdup(pool, base, 3);
+ root[2] = '/'; /* We don't need the final '\0' */
+
+ base = root;
+ blen = 3;
+ }
+
+ if (clen == 0)
+ return apr_pstrndup(pool, base, blen);
+ }
+ else
+ return apr_pmemdup(pool, component, clen + 1);
+ }
+ else if (dirent_is_rooted(component))
+ return apr_pmemdup(pool, component, clen + 1);
+#endif /* SVN_USE_DOS_PATHS */
+
+ /* if last character of base is already a separator, don't add a '/' */
+ add_separator = 1;
+ if (base[blen - 1] == '/'
+#ifdef SVN_USE_DOS_PATHS
+ || base[blen - 1] == ':'
+#endif
+ )
+ add_separator = 0;
+
+ /* Construct the new, combined dirent. */
+ dirent = apr_palloc(pool, blen + add_separator + clen + 1);
+ memcpy(dirent, base, blen);
+ if (add_separator)
+ dirent[blen] = '/';
+ memcpy(dirent + blen + add_separator, component, clen + 1);
+
+ return dirent;
+}
+
+char *svn_dirent_join_many(apr_pool_t *pool, const char *base, ...)
+{
+#define MAX_SAVED_LENGTHS 10
+ apr_size_t saved_lengths[MAX_SAVED_LENGTHS];
+ apr_size_t total_len;
+ int nargs;
+ va_list va;
+ const char *s;
+ apr_size_t len;
+ char *dirent;
+ char *p;
+ int add_separator;
+ int base_arg = 0;
+
+ total_len = strlen(base);
+
+ assert(svn_dirent_is_canonical(base, pool));
+
+ /* if last character of base is already a separator, don't add a '/' */
+ add_separator = 1;
+ if (total_len == 0
+ || base[total_len - 1] == '/'
+#ifdef SVN_USE_DOS_PATHS
+ || base[total_len - 1] == ':'
+#endif
+ )
+ add_separator = 0;
+
+ saved_lengths[0] = total_len;
+
+ /* Compute the length of the resulting string. */
+
+ nargs = 0;
+ va_start(va, base);
+ while ((s = va_arg(va, const char *)) != NULL)
+ {
+ len = strlen(s);
+
+ assert(svn_dirent_is_canonical(s, pool));
+
+ if (SVN_PATH_IS_EMPTY(s))
+ continue;
+
+ if (nargs++ < MAX_SAVED_LENGTHS)
+ saved_lengths[nargs] = len;
+
+ if (dirent_is_rooted(s))
+ {
+ total_len = len;
+ base_arg = nargs;
+
+#ifdef SVN_USE_DOS_PATHS
+ if (!svn_dirent_is_absolute(s)) /* Handle non absolute roots */
+ {
+ /* Set new base and skip the current argument */
+ base = s = svn_dirent_join(base, s, pool);
+ base_arg++;
+ saved_lengths[0] = total_len = len = strlen(s);
+ }
+ else
+#endif /* SVN_USE_DOS_PATHS */
+ {
+ base = ""; /* Don't add base */
+ saved_lengths[0] = 0;
+ }
+
+ add_separator = 1;
+ if (s[len - 1] == '/'
+#ifdef SVN_USE_DOS_PATHS
+ || s[len - 1] == ':'
+#endif
+ )
+ add_separator = 0;
+ }
+ else if (nargs <= base_arg + 1)
+ {
+ total_len += add_separator + len;
+ }
+ else
+ {
+ total_len += 1 + len;
+ }
+ }
+ va_end(va);
+
+ /* base == "/" and no further components. just return that. */
+ if (add_separator == 0 && total_len == 1)
+ return apr_pmemdup(pool, "/", 2);
+
+ /* we got the total size. allocate it, with room for a NULL character. */
+ dirent = p = apr_palloc(pool, total_len + 1);
+
+ /* if we aren't supposed to skip forward to an absolute component, and if
+ this is not an empty base that we are skipping, then copy the base
+ into the output. */
+ if (! SVN_PATH_IS_EMPTY(base))
+ {
+ memcpy(p, base, len = saved_lengths[0]);
+ p += len;
+ }
+
+ nargs = 0;
+ va_start(va, base);
+ while ((s = va_arg(va, const char *)) != NULL)
+ {
+ if (SVN_PATH_IS_EMPTY(s))
+ continue;
+
+ if (++nargs < base_arg)
+ continue;
+
+ if (nargs < MAX_SAVED_LENGTHS)
+ len = saved_lengths[nargs];
+ else
+ len = strlen(s);
+
+ /* insert a separator if we aren't copying in the first component
+ (which can happen when base_arg is set). also, don't put in a slash
+ if the prior character is a slash (occurs when prior component
+ is "/"). */
+ if (p != dirent &&
+ ( ! (nargs - 1 <= base_arg) || add_separator))
+ *p++ = '/';
+
+ /* copy the new component and advance the pointer */
+ memcpy(p, s, len);
+ p += len;
+ }
+ va_end(va);
+
+ *p = '\0';
+ assert((apr_size_t)(p - dirent) == total_len);
+
+ return dirent;
+}
+
+char *
+svn_relpath_join(const char *base,
+ const char *component,
+ apr_pool_t *pool)
+{
+ apr_size_t blen = strlen(base);
+ apr_size_t clen = strlen(component);
+ char *path;
+
+ assert(relpath_is_canonical(base));
+ assert(relpath_is_canonical(component));
+
+ /* If either is empty return the other */
+ if (blen == 0)
+ return apr_pmemdup(pool, component, clen + 1);
+ if (clen == 0)
+ return apr_pmemdup(pool, base, blen + 1);
+
+ path = apr_palloc(pool, blen + 1 + clen + 1);
+ memcpy(path, base, blen);
+ path[blen] = '/';
+ memcpy(path + blen + 1, component, clen + 1);
+
+ return path;
+}
+
+char *
+svn_dirent_dirname(const char *dirent, apr_pool_t *pool)
+{
+ apr_size_t len = strlen(dirent);
+
+ assert(svn_dirent_is_canonical(dirent, pool));
+
+ if (len == dirent_root_length(dirent, len))
+ return apr_pstrmemdup(pool, dirent, len);
+ else
+ return apr_pstrmemdup(pool, dirent, dirent_previous_segment(dirent, len));
+}
+
+const char *
+svn_dirent_basename(const char *dirent, apr_pool_t *pool)
+{
+ apr_size_t len = strlen(dirent);
+ apr_size_t start;
+
+ assert(!pool || svn_dirent_is_canonical(dirent, pool));
+
+ if (svn_dirent_is_root(dirent, len))
+ return "";
+ else
+ {
+ start = len;
+ while (start > 0 && dirent[start - 1] != '/'
+#ifdef SVN_USE_DOS_PATHS
+ && dirent[start - 1] != ':'
+#endif
+ )
+ --start;
+ }
+
+ if (pool)
+ return apr_pstrmemdup(pool, dirent + start, len - start);
+ else
+ return dirent + start;
+}
+
+void
+svn_dirent_split(const char **dirpath,
+ const char **base_name,
+ const char *dirent,
+ apr_pool_t *pool)
+{
+ assert(dirpath != base_name);
+
+ if (dirpath)
+ *dirpath = svn_dirent_dirname(dirent, pool);
+
+ if (base_name)
+ *base_name = svn_dirent_basename(dirent, pool);
+}
+
+char *
+svn_relpath_dirname(const char *relpath,
+ apr_pool_t *pool)
+{
+ apr_size_t len = strlen(relpath);
+
+ assert(relpath_is_canonical(relpath));
+
+ return apr_pstrmemdup(pool, relpath,
+ relpath_previous_segment(relpath, len));
+}
+
+const char *
+svn_relpath_basename(const char *relpath,
+ apr_pool_t *pool)
+{
+ apr_size_t len = strlen(relpath);
+ apr_size_t start;
+
+ assert(relpath_is_canonical(relpath));
+
+ start = len;
+ while (start > 0 && relpath[start - 1] != '/')
+ --start;
+
+ if (pool)
+ return apr_pstrmemdup(pool, relpath + start, len - start);
+ else
+ return relpath + start;
+}
+
+void
+svn_relpath_split(const char **dirpath,
+ const char **base_name,
+ const char *relpath,
+ apr_pool_t *pool)
+{
+ assert(dirpath != base_name);
+
+ if (dirpath)
+ *dirpath = svn_relpath_dirname(relpath, pool);
+
+ if (base_name)
+ *base_name = svn_relpath_basename(relpath, pool);
+}
+
+char *
+svn_uri_dirname(const char *uri, apr_pool_t *pool)
+{
+ apr_size_t len = strlen(uri);
+
+ assert(svn_uri_is_canonical(uri, pool));
+
+ if (svn_uri_is_root(uri, len))
+ return apr_pstrmemdup(pool, uri, len);
+ else
+ return apr_pstrmemdup(pool, uri, uri_previous_segment(uri, len));
+}
+
+const char *
+svn_uri_basename(const char *uri, apr_pool_t *pool)
+{
+ apr_size_t len = strlen(uri);
+ apr_size_t start;
+
+ assert(svn_uri_is_canonical(uri, NULL));
+
+ if (svn_uri_is_root(uri, len))
+ return "";
+
+ start = len;
+ while (start > 0 && uri[start - 1] != '/')
+ --start;
+
+ return svn_path_uri_decode(uri + start, pool);
+}
+
+void
+svn_uri_split(const char **dirpath,
+ const char **base_name,
+ const char *uri,
+ apr_pool_t *pool)
+{
+ assert(dirpath != base_name);
+
+ if (dirpath)
+ *dirpath = svn_uri_dirname(uri, pool);
+
+ if (base_name)
+ *base_name = svn_uri_basename(uri, pool);
+}
+
+char *
+svn_dirent_get_longest_ancestor(const char *dirent1,
+ const char *dirent2,
+ apr_pool_t *pool)
+{
+ return apr_pstrndup(pool, dirent1,
+ get_longest_ancestor_length(type_dirent, dirent1,
+ dirent2, pool));
+}
+
+char *
+svn_relpath_get_longest_ancestor(const char *relpath1,
+ const char *relpath2,
+ apr_pool_t *pool)
+{
+ assert(relpath_is_canonical(relpath1));
+ assert(relpath_is_canonical(relpath2));
+
+ return apr_pstrndup(pool, relpath1,
+ get_longest_ancestor_length(type_relpath, relpath1,
+ relpath2, pool));
+}
+
+char *
+svn_uri_get_longest_ancestor(const char *uri1,
+ const char *uri2,
+ apr_pool_t *pool)
+{
+ apr_size_t uri_ancestor_len;
+ apr_size_t i = 0;
+
+ assert(svn_uri_is_canonical(uri1, NULL));
+ assert(svn_uri_is_canonical(uri2, NULL));
+
+ /* Find ':' */
+ while (1)
+ {
+ /* No shared protocol => no common prefix */
+ if (uri1[i] != uri2[i])
+ return apr_pmemdup(pool, SVN_EMPTY_PATH,
+ sizeof(SVN_EMPTY_PATH));
+
+ if (uri1[i] == ':')
+ break;
+
+ /* They're both URLs, so EOS can't come before ':' */
+ assert((uri1[i] != '\0') && (uri2[i] != '\0'));
+
+ i++;
+ }
+
+ i += 3; /* Advance past '://' */
+
+ uri_ancestor_len = get_longest_ancestor_length(type_uri, uri1 + i,
+ uri2 + i, pool);
+
+ if (uri_ancestor_len == 0 ||
+ (uri_ancestor_len == 1 && (uri1 + i)[0] == '/'))
+ return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));
+ else
+ return apr_pstrndup(pool, uri1, uri_ancestor_len + i);
+}
+
+const char *
+svn_dirent_is_child(const char *parent_dirent,
+ const char *child_dirent,
+ apr_pool_t *pool)
+{
+ return is_child(type_dirent, parent_dirent, child_dirent, pool);
+}
+
+const char *
+svn_dirent_skip_ancestor(const char *parent_dirent,
+ const char *child_dirent)
+{
+ apr_size_t len = strlen(parent_dirent);
+ apr_size_t root_len;
+
+ if (0 != strncmp(parent_dirent, child_dirent, len))
+ return NULL; /* parent_dirent is no ancestor of child_dirent */
+
+ if (child_dirent[len] == 0)
+ return ""; /* parent_dirent == child_dirent */
+
+ /* Child == parent + more-characters */
+
+ root_len = dirent_root_length(child_dirent, strlen(child_dirent));
+ if (root_len > len)
+ /* Different root, e.g. ("" "/...") or ("//z" "//z/share") */
+ return NULL;
+
+ /* Now, child == [root-of-parent] + [rest-of-parent] + more-characters.
+ * It must be one of the following forms.
+ *
+ * rlen parent child bad? rlen=len? c[len]=/?
+ * 0 "" "foo" *
+ * 0 "b" "bad" !
+ * 0 "b" "b/foo" *
+ * 1 "/" "/foo" *
+ * 1 "/b" "/bad" !
+ * 1 "/b" "/b/foo" *
+ * 2 "a:" "a:foo" *
+ * 2 "a:b" "a:bad" !
+ * 2 "a:b" "a:b/foo" *
+ * 3 "a:/" "a:/foo" *
+ * 3 "a:/b" "a:/bad" !
+ * 3 "a:/b" "a:/b/foo" *
+ * 5 "//s/s" "//s/s/foo" * *
+ * 5 "//s/s/b" "//s/s/bad" !
+ * 5 "//s/s/b" "//s/s/b/foo" *
+ */
+
+ if (child_dirent[len] == '/')
+ /* "parent|child" is one of:
+ * "[a:]b|/foo" "[a:]/b|/foo" "//s/s|/foo" "//s/s/b|/foo" */
+ return child_dirent + len + 1;
+
+ if (root_len == len)
+ /* "parent|child" is "|foo" "/|foo" "a:|foo" "a:/|foo" "//s/s|/foo" */
+ return child_dirent + len;
+
+ return NULL;
+}
+
+const char *
+svn_relpath_skip_ancestor(const char *parent_relpath,
+ const char *child_relpath)
+{
+ apr_size_t len = strlen(parent_relpath);
+
+ assert(relpath_is_canonical(parent_relpath));
+ assert(relpath_is_canonical(child_relpath));
+
+ if (len == 0)
+ return child_relpath;
+
+ if (0 != strncmp(parent_relpath, child_relpath, len))
+ return NULL; /* parent_relpath is no ancestor of child_relpath */
+
+ if (child_relpath[len] == 0)
+ return ""; /* parent_relpath == child_relpath */
+
+ if (child_relpath[len] == '/')
+ return child_relpath + len + 1;
+
+ return NULL;
+}
+
+
+/* */
+static const char *
+uri_skip_ancestor(const char *parent_uri,
+ const char *child_uri)
+{
+ apr_size_t len = strlen(parent_uri);
+
+ assert(svn_uri_is_canonical(parent_uri, NULL));
+ assert(svn_uri_is_canonical(child_uri, NULL));
+
+ if (0 != strncmp(parent_uri, child_uri, len))
+ return NULL; /* parent_uri is no ancestor of child_uri */
+
+ if (child_uri[len] == 0)
+ return ""; /* parent_uri == child_uri */
+
+ if (child_uri[len] == '/')
+ return child_uri + len + 1;
+
+ return NULL;
+}
+
+const char *
+svn_uri_skip_ancestor(const char *parent_uri,
+ const char *child_uri,
+ apr_pool_t *result_pool)
+{
+ const char *result = uri_skip_ancestor(parent_uri, child_uri);
+
+ return result ? svn_path_uri_decode(result, result_pool) : NULL;
+}
+
+svn_boolean_t
+svn_dirent_is_ancestor(const char *parent_dirent, const char *child_dirent)
+{
+ return svn_dirent_skip_ancestor(parent_dirent, child_dirent) != NULL;
+}
+
+svn_boolean_t
+svn_uri__is_ancestor(const char *parent_uri, const char *child_uri)
+{
+ return uri_skip_ancestor(parent_uri, child_uri) != NULL;
+}
+
+
+svn_boolean_t
+svn_dirent_is_absolute(const char *dirent)
+{
+ if (! dirent)
+ return FALSE;
+
+ /* dirent is absolute if it starts with '/' on non-Windows platforms
+ or with '//' on Windows platforms */
+ if (dirent[0] == '/'
+#ifdef SVN_USE_DOS_PATHS
+ && dirent[1] == '/' /* Single '/' depends on current drive */
+#endif
+ )
+ return TRUE;
+
+ /* On Windows, dirent is also absolute when it starts with 'H:/'
+ where 'H' is any letter. */
+#ifdef SVN_USE_DOS_PATHS
+ if (((dirent[0] >= 'A' && dirent[0] <= 'Z')) &&
+ (dirent[1] == ':') && (dirent[2] == '/'))
+ return TRUE;
+#endif /* SVN_USE_DOS_PATHS */
+
+ return FALSE;
+}
+
+svn_error_t *
+svn_dirent_get_absolute(const char **pabsolute,
+ const char *relative,
+ apr_pool_t *pool)
+{
+ char *buffer;
+ apr_status_t apr_err;
+ const char *path_apr;
+
+ SVN_ERR_ASSERT(! svn_path_is_url(relative));
+
+ /* Merge the current working directory with the relative dirent. */
+ SVN_ERR(svn_path_cstring_from_utf8(&path_apr, relative, pool));
+
+ apr_err = apr_filepath_merge(&buffer, NULL,
+ path_apr,
+ APR_FILEPATH_NOTRELATIVE,
+ pool);
+ if (apr_err)
+ {
+ /* In some cases when the passed path or its ancestor(s) do not exist
+ or no longer exist apr returns an error.
+
+ In many of these cases we would like to return a path anyway, when the
+ passed path was already a safe absolute path. So check for that now to
+ avoid an error.
+
+ svn_dirent_is_absolute() doesn't perform the necessary checks to see
+ if the path doesn't need post processing to be in the canonical absolute
+ format.
+ */
+
+ if (svn_dirent_is_absolute(relative)
+ && svn_dirent_is_canonical(relative, pool)
+ && !svn_path_is_backpath_present(relative))
+ {
+ *pabsolute = apr_pstrdup(pool, relative);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_createf(SVN_ERR_BAD_FILENAME,
+ svn_error_create(apr_err, NULL, NULL),
+ _("Couldn't determine absolute path of '%s'"),
+ svn_dirent_local_style(relative, pool));
+ }
+
+ SVN_ERR(svn_path_cstring_to_utf8(pabsolute, buffer, pool));
+ *pabsolute = svn_dirent_canonicalize(*pabsolute, pool);
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_uri_canonicalize(const char *uri, apr_pool_t *pool)
+{
+ return canonicalize(type_uri, uri, pool);
+}
+
+const char *
+svn_relpath_canonicalize(const char *relpath, apr_pool_t *pool)
+{
+ return canonicalize(type_relpath, relpath, pool);
+}
+
+const char *
+svn_dirent_canonicalize(const char *dirent, apr_pool_t *pool)
+{
+ const char *dst = canonicalize(type_dirent, dirent, pool);
+
+#ifdef SVN_USE_DOS_PATHS
+ /* Handle a specific case on Windows where path == "X:/". Here we have to
+ append the final '/', as svn_path_canonicalize will chop this of. */
+ if (((dirent[0] >= 'A' && dirent[0] <= 'Z') ||
+ (dirent[0] >= 'a' && dirent[0] <= 'z')) &&
+ dirent[1] == ':' && dirent[2] == '/' &&
+ dst[3] == '\0')
+ {
+ char *dst_slash = apr_pcalloc(pool, 4);
+ dst_slash[0] = canonicalize_to_upper(dirent[0]);
+ dst_slash[1] = ':';
+ dst_slash[2] = '/';
+ dst_slash[3] = '\0';
+
+ return dst_slash;
+ }
+#endif /* SVN_USE_DOS_PATHS */
+
+ return dst;
+}
+
+svn_boolean_t
+svn_dirent_is_canonical(const char *dirent, apr_pool_t *scratch_pool)
+{
+ const char *ptr = dirent;
+ if (*ptr == '/')
+ {
+ ptr++;
+#ifdef SVN_USE_DOS_PATHS
+ /* Check for UNC paths */
+ if (*ptr == '/')
+ {
+ /* TODO: Scan hostname and sharename and fall back to part code */
+
+ /* ### Fall back to old implementation */
+ return (strcmp(dirent, svn_dirent_canonicalize(dirent, scratch_pool))
+ == 0);
+ }
+#endif /* SVN_USE_DOS_PATHS */
+ }
+#ifdef SVN_USE_DOS_PATHS
+ else if (((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z')) &&
+ (ptr[1] == ':'))
+ {
+ /* The only canonical drive names are "A:"..."Z:", no lower case */
+ if (*ptr < 'A' || *ptr > 'Z')
+ return FALSE;
+
+ ptr += 2;
+
+ if (*ptr == '/')
+ ptr++;
+ }
+#endif /* SVN_USE_DOS_PATHS */
+
+ return relpath_is_canonical(ptr);
+}
+
+static svn_boolean_t
+relpath_is_canonical(const char *relpath)
+{
+ const char *ptr = relpath, *seg = relpath;
+
+ /* RELPATH is canonical if it has:
+ * - no '.' segments
+ * - no start and closing '/'
+ * - no '//'
+ */
+
+ if (*relpath == '\0')
+ return TRUE;
+
+ if (*ptr == '/')
+ return FALSE;
+
+ /* Now validate the rest of the path. */
+ while(1)
+ {
+ apr_size_t seglen = ptr - seg;
+
+ if (seglen == 1 && *seg == '.')
+ return FALSE; /* /./ */
+
+ if (*ptr == '/' && *(ptr+1) == '/')
+ return FALSE; /* // */
+
+ if (! *ptr && *(ptr - 1) == '/')
+ return FALSE; /* foo/ */
+
+ if (! *ptr)
+ break;
+
+ if (*ptr == '/')
+ ptr++;
+ seg = ptr;
+
+ while (*ptr && (*ptr != '/'))
+ ptr++;
+ }
+
+ return TRUE;
+}
+
+svn_boolean_t
+svn_relpath_is_canonical(const char *relpath)
+{
+ return relpath_is_canonical(relpath);
+}
+
+svn_boolean_t
+svn_uri_is_canonical(const char *uri, apr_pool_t *scratch_pool)
+{
+ const char *ptr = uri, *seg = uri;
+ const char *schema_data = NULL;
+
+ /* URI is canonical if it has:
+ * - lowercase URL scheme
+ * - lowercase URL hostname
+ * - no '.' segments
+ * - no closing '/'
+ * - no '//'
+ * - uppercase hex-encoded pair digits ("%AB", not "%ab")
+ */
+
+ if (*uri == '\0')
+ return FALSE;
+
+ if (! svn_path_is_url(uri))
+ return FALSE;
+
+ /* Skip the scheme. */
+ while (*ptr && (*ptr != '/') && (*ptr != ':'))
+ ptr++;
+
+ /* No scheme? No good. */
+ if (! (*ptr == ':' && *(ptr+1) == '/' && *(ptr+2) == '/'))
+ return FALSE;
+
+ /* Found a scheme, check that it's all lowercase. */
+ ptr = uri;
+ while (*ptr != ':')
+ {
+ if (*ptr >= 'A' && *ptr <= 'Z')
+ return FALSE;
+ ptr++;
+ }
+ /* Skip :// */
+ ptr += 3;
+
+ /* Scheme only? That works. */
+ if (! *ptr)
+ return TRUE;
+
+ /* This might be the hostname */
+ seg = ptr;
+ while (*ptr && (*ptr != '/') && (*ptr != '@'))
+ ptr++;
+
+ if (*ptr == '@')
+ seg = ptr + 1;
+
+ /* Found a hostname, check that it's all lowercase. */
+ ptr = seg;
+
+ if (*ptr == '[')
+ {
+ ptr++;
+ while (*ptr == ':'
+ || (*ptr >= '0' && *ptr <= '9')
+ || (*ptr >= 'a' && *ptr <= 'f'))
+ {
+ ptr++;
+ }
+
+ if (*ptr != ']')
+ return FALSE;
+ ptr++;
+ }
+ else
+ while (*ptr && *ptr != '/' && *ptr != ':')
+ {
+ if (*ptr >= 'A' && *ptr <= 'Z')
+ return FALSE;
+ ptr++;
+ }
+
+ /* Found a portnumber */
+ if (*ptr == ':')
+ {
+ apr_int64_t port = 0;
+
+ ptr++;
+ schema_data = ptr;
+
+ while (*ptr >= '0' && *ptr <= '9')
+ {
+ port = 10 * port + (*ptr - '0');
+ ptr++;
+ }
+
+ if (ptr == schema_data)
+ return FALSE; /* Fail on "http://host:" */
+
+ if (*ptr && *ptr != '/')
+ return FALSE; /* Not a port number */
+
+ if (port == 80 && strncmp(uri, "http:", 5) == 0)
+ return FALSE;
+ else if (port == 443 && strncmp(uri, "https:", 6) == 0)
+ return FALSE;
+ else if (port == 3690 && strncmp(uri, "svn:", 4) == 0)
+ return FALSE;
+ }
+
+ schema_data = ptr;
+
+#ifdef SVN_USE_DOS_PATHS
+ if (schema_data && *ptr == '/')
+ {
+ /* If this is a file url, ptr now points to the third '/' in
+ file:///C:/path. Check that if we have such a URL the drive
+ letter is in uppercase. */
+ if (strncmp(uri, "file:", 5) == 0 &&
+ ! (*(ptr+1) >= 'A' && *(ptr+1) <= 'Z') &&
+ *(ptr+2) == ':')
+ return FALSE;
+ }
+#endif /* SVN_USE_DOS_PATHS */
+
+ /* Now validate the rest of the URI. */
+ while(1)
+ {
+ apr_size_t seglen = ptr - seg;
+
+ if (seglen == 1 && *seg == '.')
+ return FALSE; /* /./ */
+
+ if (*ptr == '/' && *(ptr+1) == '/')
+ return FALSE; /* // */
+
+ if (! *ptr && *(ptr - 1) == '/' && ptr - 1 != uri)
+ return FALSE; /* foo/ */
+
+ if (! *ptr)
+ break;
+
+ if (*ptr == '/')
+ ptr++;
+ seg = ptr;
+
+
+ while (*ptr && (*ptr != '/'))
+ ptr++;
+ }
+
+ ptr = schema_data;
+
+ while (*ptr)
+ {
+ if (*ptr == '%')
+ {
+ char digitz[3];
+ int val;
+
+ /* Can't usesvn_ctype_isxdigit() because lower case letters are
+ not in our canonical format */
+ if (((*(ptr+1) < '0' || *(ptr+1) > '9'))
+ && (*(ptr+1) < 'A' || *(ptr+1) > 'F'))
+ return FALSE;
+ else if (((*(ptr+2) < '0' || *(ptr+2) > '9'))
+ && (*(ptr+2) < 'A' || *(ptr+2) > 'F'))
+ return FALSE;
+
+ digitz[0] = *(++ptr);
+ digitz[1] = *(++ptr);
+ digitz[2] = '\0';
+ val = (int)strtol(digitz, NULL, 16);
+
+ if (svn_uri__char_validity[val])
+ return FALSE; /* Should not have been escaped */
+ }
+ else if (*ptr != '/' && !svn_uri__char_validity[(unsigned char)*ptr])
+ return FALSE; /* Character should have been escaped */
+ ptr++;
+ }
+
+ return TRUE;
+}
+
+svn_error_t *
+svn_dirent_condense_targets(const char **pcommon,
+ apr_array_header_t **pcondensed_targets,
+ const apr_array_header_t *targets,
+ svn_boolean_t remove_redundancies,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i, num_condensed = targets->nelts;
+ svn_boolean_t *removed;
+ apr_array_header_t *abs_targets;
+
+ /* Early exit when there's no data to work on. */
+ if (targets->nelts <= 0)
+ {
+ *pcommon = NULL;
+ if (pcondensed_targets)
+ *pcondensed_targets = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Get the absolute path of the first target. */
+ SVN_ERR(svn_dirent_get_absolute(pcommon,
+ APR_ARRAY_IDX(targets, 0, const char *),
+ scratch_pool));
+
+ /* Early exit when there's only one dirent to work on. */
+ if (targets->nelts == 1)
+ {
+ *pcommon = apr_pstrdup(result_pool, *pcommon);
+ if (pcondensed_targets)
+ *pcondensed_targets = apr_array_make(result_pool, 0,
+ sizeof(const char *));
+ return SVN_NO_ERROR;
+ }
+
+ /* Copy the targets array, but with absolute dirents instead of
+ relative. Also, find the pcommon argument by finding what is
+ common in all of the absolute dirents. NOTE: This is not as
+ efficient as it could be. The calculation of the basedir could
+ be done in the loop below, which would save some calls to
+ svn_dirent_get_longest_ancestor. I decided to do it this way
+ because I thought it would be simpler, since this way, we don't
+ even do the loop if we don't need to condense the targets. */
+
+ removed = apr_pcalloc(scratch_pool, (targets->nelts *
+ sizeof(svn_boolean_t)));
+ abs_targets = apr_array_make(scratch_pool, targets->nelts,
+ sizeof(const char *));
+
+ APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon;
+
+ for (i = 1; i < targets->nelts; ++i)
+ {
+ const char *rel = APR_ARRAY_IDX(targets, i, const char *);
+ const char *absolute;
+ SVN_ERR(svn_dirent_get_absolute(&absolute, rel, scratch_pool));
+ APR_ARRAY_PUSH(abs_targets, const char *) = absolute;
+ *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute,
+ scratch_pool);
+ }
+
+ *pcommon = apr_pstrdup(result_pool, *pcommon);
+
+ if (pcondensed_targets != NULL)
+ {
+ size_t basedir_len;
+
+ if (remove_redundancies)
+ {
+ /* Find the common part of each pair of targets. If
+ common part is equal to one of the dirents, the other
+ is a child of it, and can be removed. If a target is
+ equal to *pcommon, it can also be removed. */
+
+ /* First pass: when one non-removed target is a child of
+ another non-removed target, remove the child. */
+ for (i = 0; i < abs_targets->nelts; ++i)
+ {
+ int j;
+
+ if (removed[i])
+ continue;
+
+ for (j = i + 1; j < abs_targets->nelts; ++j)
+ {
+ const char *abs_targets_i;
+ const char *abs_targets_j;
+ const char *ancestor;
+
+ if (removed[j])
+ continue;
+
+ abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *);
+ abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *);
+
+ ancestor = svn_dirent_get_longest_ancestor
+ (abs_targets_i, abs_targets_j, scratch_pool);
+
+ if (*ancestor == '\0')
+ continue;
+
+ if (strcmp(ancestor, abs_targets_i) == 0)
+ {
+ removed[j] = TRUE;
+ num_condensed--;
+ }
+ else if (strcmp(ancestor, abs_targets_j) == 0)
+ {
+ removed[i] = TRUE;
+ num_condensed--;
+ }
+ }
+ }
+
+ /* Second pass: when a target is the same as *pcommon,
+ remove the target. */
+ for (i = 0; i < abs_targets->nelts; ++i)
+ {
+ const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i,
+ const char *);
+
+ if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i]))
+ {
+ removed[i] = TRUE;
+ num_condensed--;
+ }
+ }
+ }
+
+ /* Now create the return array, and copy the non-removed items */
+ basedir_len = strlen(*pcommon);
+ *pcondensed_targets = apr_array_make(result_pool, num_condensed,
+ sizeof(const char *));
+
+ for (i = 0; i < abs_targets->nelts; ++i)
+ {
+ const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *);
+
+ /* Skip this if it's been removed. */
+ if (removed[i])
+ continue;
+
+ /* If a common prefix was found, condensed_targets are given
+ relative to that prefix. */
+ if (basedir_len > 0)
+ {
+ /* Only advance our pointer past a dirent separator if
+ REL_ITEM isn't the same as *PCOMMON.
+
+ If *PCOMMON is a root dirent, basedir_len will already
+ include the closing '/', so never advance the pointer
+ here.
+ */
+ rel_item += basedir_len;
+ if (rel_item[0] &&
+ ! svn_dirent_is_root(*pcommon, basedir_len))
+ rel_item++;
+ }
+
+ APR_ARRAY_PUSH(*pcondensed_targets, const char *)
+ = apr_pstrdup(result_pool, rel_item);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_uri_condense_targets(const char **pcommon,
+ apr_array_header_t **pcondensed_targets,
+ const apr_array_header_t *targets,
+ svn_boolean_t remove_redundancies,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i, num_condensed = targets->nelts;
+ apr_array_header_t *uri_targets;
+ svn_boolean_t *removed;
+
+ /* Early exit when there's no data to work on. */
+ if (targets->nelts <= 0)
+ {
+ *pcommon = NULL;
+ if (pcondensed_targets)
+ *pcondensed_targets = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ *pcommon = svn_uri_canonicalize(APR_ARRAY_IDX(targets, 0, const char *),
+ scratch_pool);
+
+ /* Early exit when there's only one uri to work on. */
+ if (targets->nelts == 1)
+ {
+ *pcommon = apr_pstrdup(result_pool, *pcommon);
+ if (pcondensed_targets)
+ *pcondensed_targets = apr_array_make(result_pool, 0,
+ sizeof(const char *));
+ return SVN_NO_ERROR;
+ }
+
+ /* Find the pcommon argument by finding what is common in all of the
+ uris. NOTE: This is not as efficient as it could be. The calculation
+ of the basedir could be done in the loop below, which would
+ save some calls to svn_uri_get_longest_ancestor. I decided to do it
+ this way because I thought it would be simpler, since this way, we don't
+ even do the loop if we don't need to condense the targets. */
+
+ removed = apr_pcalloc(scratch_pool, (targets->nelts *
+ sizeof(svn_boolean_t)));
+ uri_targets = apr_array_make(scratch_pool, targets->nelts,
+ sizeof(const char *));
+
+ APR_ARRAY_PUSH(uri_targets, const char *) = *pcommon;
+
+ for (i = 1; i < targets->nelts; ++i)
+ {
+ const char *uri = svn_uri_canonicalize(
+ APR_ARRAY_IDX(targets, i, const char *),
+ scratch_pool);
+ APR_ARRAY_PUSH(uri_targets, const char *) = uri;
+
+ /* If the commonmost ancestor so far is empty, there's no point
+ in continuing to search for a common ancestor at all. But
+ we'll keep looping for the sake of canonicalizing the
+ targets, I suppose. */
+ if (**pcommon != '\0')
+ *pcommon = svn_uri_get_longest_ancestor(*pcommon, uri,
+ scratch_pool);
+ }
+
+ *pcommon = apr_pstrdup(result_pool, *pcommon);
+
+ if (pcondensed_targets != NULL)
+ {
+ size_t basedir_len;
+
+ if (remove_redundancies)
+ {
+ /* Find the common part of each pair of targets. If
+ common part is equal to one of the dirents, the other
+ is a child of it, and can be removed. If a target is
+ equal to *pcommon, it can also be removed. */
+
+ /* First pass: when one non-removed target is a child of
+ another non-removed target, remove the child. */
+ for (i = 0; i < uri_targets->nelts; ++i)
+ {
+ int j;
+
+ if (removed[i])
+ continue;
+
+ for (j = i + 1; j < uri_targets->nelts; ++j)
+ {
+ const char *uri_i;
+ const char *uri_j;
+ const char *ancestor;
+
+ if (removed[j])
+ continue;
+
+ uri_i = APR_ARRAY_IDX(uri_targets, i, const char *);
+ uri_j = APR_ARRAY_IDX(uri_targets, j, const char *);
+
+ ancestor = svn_uri_get_longest_ancestor(uri_i,
+ uri_j,
+ scratch_pool);
+
+ if (*ancestor == '\0')
+ continue;
+
+ if (strcmp(ancestor, uri_i) == 0)
+ {
+ removed[j] = TRUE;
+ num_condensed--;
+ }
+ else if (strcmp(ancestor, uri_j) == 0)
+ {
+ removed[i] = TRUE;
+ num_condensed--;
+ }
+ }
+ }
+
+ /* Second pass: when a target is the same as *pcommon,
+ remove the target. */
+ for (i = 0; i < uri_targets->nelts; ++i)
+ {
+ const char *uri_targets_i = APR_ARRAY_IDX(uri_targets, i,
+ const char *);
+
+ if ((strcmp(uri_targets_i, *pcommon) == 0) && (! removed[i]))
+ {
+ removed[i] = TRUE;
+ num_condensed--;
+ }
+ }
+ }
+
+ /* Now create the return array, and copy the non-removed items */
+ basedir_len = strlen(*pcommon);
+ *pcondensed_targets = apr_array_make(result_pool, num_condensed,
+ sizeof(const char *));
+
+ for (i = 0; i < uri_targets->nelts; ++i)
+ {
+ const char *rel_item = APR_ARRAY_IDX(uri_targets, i, const char *);
+
+ /* Skip this if it's been removed. */
+ if (removed[i])
+ continue;
+
+ /* If a common prefix was found, condensed_targets are given
+ relative to that prefix. */
+ if (basedir_len > 0)
+ {
+ /* Only advance our pointer past a dirent separator if
+ REL_ITEM isn't the same as *PCOMMON.
+
+ If *PCOMMON is a root dirent, basedir_len will already
+ include the closing '/', so never advance the pointer
+ here.
+ */
+ rel_item += basedir_len;
+ if ((rel_item[0] == '/') ||
+ (rel_item[0] && !svn_uri_is_root(*pcommon, basedir_len)))
+ {
+ rel_item++;
+ }
+ }
+
+ APR_ARRAY_PUSH(*pcondensed_targets, const char *)
+ = svn_path_uri_decode(rel_item, result_pool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_dirent_is_under_root(svn_boolean_t *under_root,
+ const char **result_path,
+ const char *base_path,
+ const char *path,
+ apr_pool_t *result_pool)
+{
+ apr_status_t status;
+ char *full_path;
+
+ *under_root = FALSE;
+ if (result_path)
+ *result_path = NULL;
+
+ status = apr_filepath_merge(&full_path,
+ base_path,
+ path,
+ APR_FILEPATH_NOTABOVEROOT
+ | APR_FILEPATH_SECUREROOTTEST,
+ result_pool);
+
+ if (status == APR_SUCCESS)
+ {
+ if (result_path)
+ *result_path = svn_dirent_canonicalize(full_path, result_pool);
+ *under_root = TRUE;
+ return SVN_NO_ERROR;
+ }
+ else if (status == APR_EABOVEROOT)
+ {
+ *under_root = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_wrap_apr(status, NULL);
+}
+
+svn_error_t *
+svn_uri_get_dirent_from_file_url(const char **dirent,
+ const char *url,
+ apr_pool_t *pool)
+{
+ const char *hostname, *path;
+
+ SVN_ERR_ASSERT(svn_uri_is_canonical(url, pool));
+
+ /* Verify that the URL is well-formed (loosely) */
+
+ /* First, check for the "file://" prefix. */
+ if (strncmp(url, "file://", 7) != 0)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Local URL '%s' does not contain 'file://' "
+ "prefix"), url);
+
+ /* Find the HOSTNAME portion and the PATH portion of the URL. The host
+ name is between the "file://" prefix and the next occurence of '/'. We
+ are considering everything from that '/' until the end of the URL to be
+ the absolute path portion of the URL.
+ If we got just "file://", treat it the same as "file:///". */
+ hostname = url + 7;
+ path = strchr(hostname, '/');
+ if (path)
+ hostname = apr_pstrmemdup(pool, hostname, path - hostname);
+ else
+ path = "/";
+
+ /* URI-decode HOSTNAME, and set it to NULL if it is "" or "localhost". */
+ if (*hostname == '\0')
+ hostname = NULL;
+ else
+ {
+ hostname = svn_path_uri_decode(hostname, pool);
+ if (strcmp(hostname, "localhost") == 0)
+ hostname = NULL;
+ }
+
+ /* Duplicate the URL, starting at the top of the path.
+ At the same time, we URI-decode the path. */
+#ifdef SVN_USE_DOS_PATHS
+ /* On Windows, we'll typically have to skip the leading / if the
+ path starts with a drive letter. Like most Web browsers, We
+ support two variants of this scheme:
+
+ file:///X:/path and
+ file:///X|/path
+
+ Note that, at least on WinNT and above, file:////./X:/path will
+ also work, so we must make sure the transformation doesn't break
+ that, and file:///path (that looks within the current drive
+ only) should also keep working.
+ If we got a non-empty hostname other than localhost, we convert this
+ into an UNC path. In this case, we obviously don't strip the slash
+ even if the path looks like it starts with a drive letter.
+ */
+ {
+ static const char valid_drive_letters[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ /* Casting away const! */
+ char *dup_path = (char *)svn_path_uri_decode(path, pool);
+
+ /* This check assumes ':' and '|' are already decoded! */
+ if (!hostname && dup_path[1] && strchr(valid_drive_letters, dup_path[1])
+ && (dup_path[2] == ':' || dup_path[2] == '|'))
+ {
+ /* Skip the leading slash. */
+ ++dup_path;
+
+ if (dup_path[1] == '|')
+ dup_path[1] = ':';
+
+ if (dup_path[2] == '/' || dup_path[2] == '\0')
+ {
+ if (dup_path[2] == '\0')
+ {
+ /* A valid dirent for the driveroot must be like "C:/" instead of
+ just "C:" or svn_dirent_join() will use the current directory
+ on the drive instead */
+ char *new_path = apr_pcalloc(pool, 4);
+ new_path[0] = dup_path[0];
+ new_path[1] = ':';
+ new_path[2] = '/';
+ new_path[3] = '\0';
+ dup_path = new_path;
+ }
+ }
+ }
+ if (hostname)
+ {
+ if (dup_path[0] == '/' && dup_path[1] == '\0')
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Local URL '%s' contains only a hostname, "
+ "no path"), url);
+
+ /* We still know that the path starts with a slash. */
+ *dirent = apr_pstrcat(pool, "//", hostname, dup_path, NULL);
+ }
+ else
+ *dirent = dup_path;
+ }
+#else /* !SVN_USE_DOS_PATHS */
+ /* Currently, the only hostnames we are allowing on non-Win32 platforms
+ are the empty string and 'localhost'. */
+ if (hostname)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Local URL '%s' contains unsupported hostname"),
+ url);
+
+ *dirent = svn_path_uri_decode(path, pool);
+#endif /* SVN_USE_DOS_PATHS */
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_uri_get_file_url_from_dirent(const char **url,
+ const char *dirent,
+ apr_pool_t *pool)
+{
+ assert(svn_dirent_is_canonical(dirent, pool));
+
+ SVN_ERR(svn_dirent_get_absolute(&dirent, dirent, pool));
+
+ dirent = svn_path_uri_encode(dirent, pool);
+
+#ifndef SVN_USE_DOS_PATHS
+ if (dirent[0] == '/' && dirent[1] == '\0')
+ dirent = NULL; /* "file://" is the canonical form of "file:///" */
+
+ *url = apr_pstrcat(pool, "file://", dirent, (char *)NULL);
+#else
+ if (dirent[0] == '/')
+ {
+ /* Handle UNC paths //server/share -> file://server/share */
+ assert(dirent[1] == '/'); /* Expect UNC, not non-absolute */
+
+ *url = apr_pstrcat(pool, "file:", dirent, NULL);
+ }
+ else
+ {
+ char *uri = apr_pstrcat(pool, "file:///", dirent, NULL);
+ apr_size_t len = 8 /* strlen("file:///") */ + strlen(dirent);
+
+ /* "C:/" is a canonical dirent on Windows,
+ but "file:///C:/" is not a canonical uri */
+ if (uri[len-1] == '/')
+ uri[len-1] = '\0';
+
+ *url = uri;
+ }
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* -------------- The fspath API (see private/svn_fspath.h) -------------- */
+
+svn_boolean_t
+svn_fspath__is_canonical(const char *fspath)
+{
+ return fspath[0] == '/' && relpath_is_canonical(fspath + 1);
+}
+
+
+const char *
+svn_fspath__canonicalize(const char *fspath,
+ apr_pool_t *pool)
+{
+ if ((fspath[0] == '/') && (fspath[1] == '\0'))
+ return "/";
+
+ return apr_pstrcat(pool, "/", svn_relpath_canonicalize(fspath, pool),
+ (char *)NULL);
+}
+
+
+svn_boolean_t
+svn_fspath__is_root(const char *fspath, apr_size_t len)
+{
+ /* directory is root if it's equal to '/' */
+ return (len == 1 && fspath[0] == '/');
+}
+
+
+const char *
+svn_fspath__skip_ancestor(const char *parent_fspath,
+ const char *child_fspath)
+{
+ assert(svn_fspath__is_canonical(parent_fspath));
+ assert(svn_fspath__is_canonical(child_fspath));
+
+ return svn_relpath_skip_ancestor(parent_fspath + 1, child_fspath + 1);
+}
+
+
+const char *
+svn_fspath__dirname(const char *fspath,
+ apr_pool_t *pool)
+{
+ assert(svn_fspath__is_canonical(fspath));
+
+ if (fspath[0] == '/' && fspath[1] == '\0')
+ return apr_pstrdup(pool, fspath);
+ else
+ return apr_pstrcat(pool, "/", svn_relpath_dirname(fspath + 1, pool),
+ (char *)NULL);
+}
+
+
+const char *
+svn_fspath__basename(const char *fspath,
+ apr_pool_t *pool)
+{
+ const char *result;
+ assert(svn_fspath__is_canonical(fspath));
+
+ result = svn_relpath_basename(fspath + 1, pool);
+
+ assert(strchr(result, '/') == NULL);
+ return result;
+}
+
+void
+svn_fspath__split(const char **dirpath,
+ const char **base_name,
+ const char *fspath,
+ apr_pool_t *result_pool)
+{
+ assert(dirpath != base_name);
+
+ if (dirpath)
+ *dirpath = svn_fspath__dirname(fspath, result_pool);
+
+ if (base_name)
+ *base_name = svn_fspath__basename(fspath, result_pool);
+}
+
+char *
+svn_fspath__join(const char *fspath,
+ const char *relpath,
+ apr_pool_t *result_pool)
+{
+ char *result;
+ assert(svn_fspath__is_canonical(fspath));
+ assert(svn_relpath_is_canonical(relpath));
+
+ if (relpath[0] == '\0')
+ result = apr_pstrdup(result_pool, fspath);
+ else if (fspath[1] == '\0')
+ result = apr_pstrcat(result_pool, "/", relpath, (char *)NULL);
+ else
+ result = apr_pstrcat(result_pool, fspath, "/", relpath, (char *)NULL);
+
+ assert(svn_fspath__is_canonical(result));
+ return result;
+}
+
+char *
+svn_fspath__get_longest_ancestor(const char *fspath1,
+ const char *fspath2,
+ apr_pool_t *result_pool)
+{
+ char *result;
+ assert(svn_fspath__is_canonical(fspath1));
+ assert(svn_fspath__is_canonical(fspath2));
+
+ result = apr_pstrcat(result_pool, "/",
+ svn_relpath_get_longest_ancestor(fspath1 + 1,
+ fspath2 + 1,
+ result_pool),
+ (char *)NULL);
+
+ assert(svn_fspath__is_canonical(result));
+ return result;
+}
+
+
+
+
+/* -------------- The urlpath API (see private/svn_fspath.h) ------------- */
+
+const char *
+svn_urlpath__canonicalize(const char *uri,
+ apr_pool_t *pool)
+{
+ if (svn_path_is_url(uri))
+ {
+ uri = svn_uri_canonicalize(uri, pool);
+ }
+ else
+ {
+ uri = svn_fspath__canonicalize(uri, pool);
+ /* Do a little dance to normalize hex encoding. */
+ uri = svn_path_uri_decode(uri, pool);
+ uri = svn_path_uri_encode(uri, pool);
+ }
+ return uri;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/dirent_uri.h b/contrib/subversion/subversion/libsvn_subr/dirent_uri.h
new file mode 100644
index 0000000..660fb34
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/dirent_uri.h
@@ -0,0 +1,40 @@
+/*
+ * dirent_uri.h : private header for the dirent, uri, relpath implementation.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_SUBR_DIRENT_URI_H
+#define SVN_LIBSVN_SUBR_DIRENT_URI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Character map used to check which characters need escaping when
+ used in a uri. See path.c for more details */
+extern const char svn_uri__char_validity[256];
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_SUBR_DIRENT_URI_H */
diff --git a/contrib/subversion/subversion/libsvn_subr/dso.c b/contrib/subversion/subversion/libsvn_subr/dso.c
new file mode 100644
index 0000000..3fa2517
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/dso.c
@@ -0,0 +1,117 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_thread_mutex.h>
+#include <apr_hash.h>
+
+#include "svn_hash.h"
+#include "svn_dso.h"
+#include "svn_pools.h"
+#include "svn_private_config.h"
+
+#include "private/svn_mutex.h"
+
+/* A mutex to protect our global pool and cache. */
+static svn_mutex__t *dso_mutex = NULL;
+
+/* Global pool to allocate DSOs in. */
+static apr_pool_t *dso_pool;
+
+/* Global cache for storing DSO objects. */
+static apr_hash_t *dso_cache;
+
+/* Just an arbitrary location in memory... */
+static int not_there_sentinel;
+
+/* A specific value we store in the dso_cache to indicate that the
+ library wasn't found. This keeps us from allocating extra memory
+ from dso_pool when trying to find libraries we already know aren't
+ there. */
+#define NOT_THERE ((void *) &not_there_sentinel)
+
+svn_error_t *
+svn_dso_initialize2(void)
+{
+ if (dso_pool)
+ return SVN_NO_ERROR;
+
+ dso_pool = svn_pool_create(NULL);
+
+ SVN_ERR(svn_mutex__init(&dso_mutex, TRUE, dso_pool));
+
+ dso_cache = apr_hash_make(dso_pool);
+ return SVN_NO_ERROR;
+}
+
+#if APR_HAS_DSO
+static svn_error_t *
+svn_dso_load_internal(apr_dso_handle_t **dso, const char *fname)
+{
+ *dso = svn_hash_gets(dso_cache, fname);
+
+ /* First check to see if we've been through this before... We do this
+ to avoid calling apr_dso_load multiple times for a given library,
+ which would result in wasting small amounts of memory each time. */
+ if (*dso == NOT_THERE)
+ {
+ *dso = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* If we got nothing back from the cache, try and load the library. */
+ if (! *dso)
+ {
+ apr_status_t status = apr_dso_load(dso, fname, dso_pool);
+ if (status)
+ {
+#ifdef SVN_DEBUG_DSO
+ char buf[1024];
+ fprintf(stderr,
+ "Dynamic loading of '%s' failed with the following error:\n%s\n",
+ fname,
+ apr_dso_error(*dso, buf, 1024));
+#endif
+ *dso = NULL;
+
+ /* It wasn't found, so set the special "we didn't find it" value. */
+ svn_hash_sets(dso_cache, apr_pstrdup(dso_pool, fname), NOT_THERE);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Stash the dso so we can use it next time. */
+ svn_hash_sets(dso_cache, apr_pstrdup(dso_pool, fname), *dso);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_dso_load(apr_dso_handle_t **dso, const char *fname)
+{
+ if (! dso_pool)
+ SVN_ERR(svn_dso_initialize2());
+
+ SVN_MUTEX__WITH_LOCK(dso_mutex, svn_dso_load_internal(dso, fname));
+
+ return SVN_NO_ERROR;
+}
+#endif /* APR_HAS_DSO */
diff --git a/contrib/subversion/subversion/libsvn_subr/eol.c b/contrib/subversion/subversion/libsvn_subr/eol.c
new file mode 100644
index 0000000..88a6a37
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/eol.c
@@ -0,0 +1,108 @@
+/*
+ * eol.c : generic eol/keyword routines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+
+#include <apr_file_io.h>
+#include "svn_io.h"
+#include "private/svn_eol_private.h"
+#include "private/svn_dep_compat.h"
+
+/* Machine-word-sized masks used in svn_eol__find_eol_start.
+ */
+char *
+svn_eol__find_eol_start(char *buf, apr_size_t len)
+{
+#if !SVN_UNALIGNED_ACCESS_IS_OK
+
+ /* On some systems, we need to make sure that buf is properly aligned
+ * for chunky data access. This overhead is still justified because
+ * only lines tend to be tens of chars long.
+ */
+ for (; (len > 0) && ((apr_uintptr_t)buf) & (sizeof(apr_uintptr_t)-1)
+ ; ++buf, --len)
+ {
+ if (*buf == '\n' || *buf == '\r')
+ return buf;
+ }
+
+#endif
+
+ /* Scan the input one machine word at a time. */
+ for (; len > sizeof(apr_uintptr_t)
+ ; buf += sizeof(apr_uintptr_t), len -= sizeof(apr_uintptr_t))
+ {
+ /* This is a variant of the well-known strlen test: */
+ apr_uintptr_t chunk = *(const apr_uintptr_t *)buf;
+
+ /* A byte in SVN__R_TEST is \0, iff it was \r in *BUF.
+ * Similarly, SVN__N_TEST is an indicator for \n. */
+ apr_uintptr_t r_test = chunk ^ SVN__R_MASK;
+ apr_uintptr_t n_test = chunk ^ SVN__N_MASK;
+
+ /* A byte in SVN__R_TEST can by < 0x80, iff it has been \0 before
+ * (i.e. \r in *BUF). Dito for SVN__N_TEST. */
+ r_test |= (r_test & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET;
+ n_test |= (n_test & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET;
+
+ /* Check whether at least one of the words contains a byte <0x80
+ * (if one is detected, there was a \r or \n in CHUNK). */
+ if ((r_test & n_test & SVN__BIT_7_SET) != SVN__BIT_7_SET)
+ break;
+ }
+
+ /* The remaining odd bytes will be examined the naive way: */
+ for (; len > 0; ++buf, --len)
+ {
+ if (*buf == '\n' || *buf == '\r')
+ return buf;
+ }
+
+ return NULL;
+}
+
+const char *
+svn_eol__detect_eol(char *buf, apr_size_t len, char **eolp)
+{
+ char *eol;
+
+ eol = svn_eol__find_eol_start(buf, len);
+ if (eol)
+ {
+ if (eolp)
+ *eolp = eol;
+
+ if (*eol == '\n')
+ return "\n";
+
+ /* We found a CR. */
+ ++eol;
+ if (eol == buf + len || *eol != '\n')
+ return "\r";
+ return "\r\n";
+ }
+
+ return NULL;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/error.c b/contrib/subversion/subversion/libsvn_subr/error.c
new file mode 100644
index 0000000..2f04320
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/error.c
@@ -0,0 +1,800 @@
+/* error.c: common exception handling for Subversion
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdarg.h>
+
+#include <apr_general.h>
+#include <apr_pools.h>
+#include <apr_strings.h>
+
+#include <zlib.h>
+
+#ifndef SVN_ERR__TRACING
+#define SVN_ERR__TRACING
+#endif
+#include "svn_cmdline.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_utf.h"
+
+#ifdef SVN_DEBUG
+/* XXX FIXME: These should be protected by a thread mutex.
+ svn_error__locate and make_error_internal should cooperate
+ in locking and unlocking it. */
+
+/* XXX TODO: Define mutex here #if APR_HAS_THREADS */
+static const char * volatile error_file = NULL;
+static long volatile error_line = -1;
+
+/* file_line for the non-debug case. */
+static const char SVN_FILE_LINE_UNDEFINED[] = "svn:<undefined>";
+#endif /* SVN_DEBUG */
+
+#include "svn_private_config.h"
+#include "private/svn_error_private.h"
+
+
+/*
+ * Undefine the helpers for creating errors.
+ *
+ * *NOTE*: Any use of these functions in any other function may need
+ * to call svn_error__locate() because the macro that would otherwise
+ * do this is being undefined and the filename and line number will
+ * not be properly set in the static error_file and error_line
+ * variables.
+ */
+#undef svn_error_create
+#undef svn_error_createf
+#undef svn_error_quick_wrap
+#undef svn_error_wrap_apr
+
+/* Note: Although this is a "__" function, it was historically in the
+ * public ABI, so we can never change it or remove its signature, even
+ * though it is now only used in SVN_DEBUG mode. */
+void
+svn_error__locate(const char *file, long line)
+{
+#if defined(SVN_DEBUG)
+ /* XXX TODO: Lock mutex here */
+ error_file = file;
+ error_line = line;
+#endif
+}
+
+
+/* Cleanup function for errors. svn_error_clear () removes this so
+ errors that are properly handled *don't* hit this code. */
+#if defined(SVN_DEBUG)
+static apr_status_t err_abort(void *data)
+{
+ svn_error_t *err = data; /* For easy viewing in a debugger */
+ err = err; /* Fake a use for the variable to avoid compiler warnings */
+
+ if (!getenv("SVN_DBG_NO_ABORT_ON_ERROR_LEAK"))
+ abort();
+ return APR_SUCCESS;
+}
+#endif
+
+
+static svn_error_t *
+make_error_internal(apr_status_t apr_err,
+ svn_error_t *child)
+{
+ apr_pool_t *pool;
+ svn_error_t *new_error;
+
+ /* Reuse the child's pool, or create our own. */
+ if (child)
+ pool = child->pool;
+ else
+ {
+ if (apr_pool_create(&pool, NULL))
+ abort();
+ }
+
+ /* Create the new error structure */
+ new_error = apr_pcalloc(pool, sizeof(*new_error));
+
+ /* Fill 'er up. */
+ new_error->apr_err = apr_err;
+ new_error->child = child;
+ new_error->pool = pool;
+#if defined(SVN_DEBUG)
+ new_error->file = error_file;
+ new_error->line = error_line;
+ /* XXX TODO: Unlock mutex here */
+
+ if (! child)
+ apr_pool_cleanup_register(pool, new_error,
+ err_abort,
+ apr_pool_cleanup_null);
+#endif
+
+ return new_error;
+}
+
+
+
+/*** Creating and destroying errors. ***/
+
+svn_error_t *
+svn_error_create(apr_status_t apr_err,
+ svn_error_t *child,
+ const char *message)
+{
+ svn_error_t *err;
+
+ err = make_error_internal(apr_err, child);
+
+ if (message)
+ err->message = apr_pstrdup(err->pool, message);
+
+ return err;
+}
+
+
+svn_error_t *
+svn_error_createf(apr_status_t apr_err,
+ svn_error_t *child,
+ const char *fmt,
+ ...)
+{
+ svn_error_t *err;
+ va_list ap;
+
+ err = make_error_internal(apr_err, child);
+
+ va_start(ap, fmt);
+ err->message = apr_pvsprintf(err->pool, fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+svn_error_t *
+svn_error_wrap_apr(apr_status_t status,
+ const char *fmt,
+ ...)
+{
+ svn_error_t *err, *utf8_err;
+ va_list ap;
+ char errbuf[255];
+ const char *msg_apr, *msg;
+
+ err = make_error_internal(status, NULL);
+
+ if (fmt)
+ {
+ /* Grab the APR error message. */
+ apr_strerror(status, errbuf, sizeof(errbuf));
+ utf8_err = svn_utf_cstring_to_utf8(&msg_apr, errbuf, err->pool);
+ if (utf8_err)
+ msg_apr = NULL;
+ svn_error_clear(utf8_err);
+
+ /* Append it to the formatted message. */
+ va_start(ap, fmt);
+ msg = apr_pvsprintf(err->pool, fmt, ap);
+ va_end(ap);
+ if (msg_apr)
+ {
+ err->message = apr_pstrcat(err->pool, msg, ": ", msg_apr, NULL);
+ }
+ else
+ {
+ err->message = msg;
+ }
+ }
+
+ return err;
+}
+
+
+svn_error_t *
+svn_error_quick_wrap(svn_error_t *child, const char *new_msg)
+{
+ if (child == SVN_NO_ERROR)
+ return SVN_NO_ERROR;
+
+ return svn_error_create(child->apr_err,
+ child,
+ new_msg);
+}
+
+/* Messages in tracing errors all point to this static string. */
+static const char error_tracing_link[] = "traced call";
+
+svn_error_t *
+svn_error__trace(const char *file, long line, svn_error_t *err)
+{
+#ifndef SVN_DEBUG
+
+ /* We shouldn't even be here, but whatever. Just return the error as-is. */
+ return err;
+
+#else
+
+ /* Only do the work when an error occurs. */
+ if (err)
+ {
+ svn_error_t *trace;
+ svn_error__locate(file, line);
+ trace = make_error_internal(err->apr_err, err);
+ trace->message = error_tracing_link;
+ return trace;
+ }
+ return SVN_NO_ERROR;
+
+#endif
+}
+
+
+svn_error_t *
+svn_error_compose_create(svn_error_t *err1,
+ svn_error_t *err2)
+{
+ if (err1 && err2)
+ {
+ svn_error_compose(err1,
+ svn_error_quick_wrap(err2,
+ _("Additional errors:")));
+ return err1;
+ }
+ return err1 ? err1 : err2;
+}
+
+
+void
+svn_error_compose(svn_error_t *chain, svn_error_t *new_err)
+{
+ apr_pool_t *pool = chain->pool;
+ apr_pool_t *oldpool = new_err->pool;
+
+ while (chain->child)
+ chain = chain->child;
+
+#if defined(SVN_DEBUG)
+ /* Kill existing handler since the end of the chain is going to change */
+ apr_pool_cleanup_kill(pool, chain, err_abort);
+#endif
+
+ /* Copy the new error chain into the old chain's pool. */
+ while (new_err)
+ {
+ chain->child = apr_palloc(pool, sizeof(*chain->child));
+ chain = chain->child;
+ *chain = *new_err;
+ if (chain->message)
+ chain->message = apr_pstrdup(pool, new_err->message);
+ chain->pool = pool;
+#if defined(SVN_DEBUG)
+ if (! new_err->child)
+ apr_pool_cleanup_kill(oldpool, new_err, err_abort);
+#endif
+ new_err = new_err->child;
+ }
+
+#if defined(SVN_DEBUG)
+ apr_pool_cleanup_register(pool, chain,
+ err_abort,
+ apr_pool_cleanup_null);
+#endif
+
+ /* Destroy the new error chain. */
+ svn_pool_destroy(oldpool);
+}
+
+svn_error_t *
+svn_error_root_cause(svn_error_t *err)
+{
+ while (err)
+ {
+ if (err->child)
+ err = err->child;
+ else
+ break;
+ }
+
+ return err;
+}
+
+svn_error_t *
+svn_error_find_cause(svn_error_t *err, apr_status_t apr_err)
+{
+ svn_error_t *child;
+
+ for (child = err; child; child = child->child)
+ if (child->apr_err == apr_err)
+ return child;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_error_dup(svn_error_t *err)
+{
+ apr_pool_t *pool;
+ svn_error_t *new_err = NULL, *tmp_err = NULL;
+
+ if (apr_pool_create(&pool, NULL))
+ abort();
+
+ for (; err; err = err->child)
+ {
+ if (! new_err)
+ {
+ new_err = apr_palloc(pool, sizeof(*new_err));
+ tmp_err = new_err;
+ }
+ else
+ {
+ tmp_err->child = apr_palloc(pool, sizeof(*tmp_err->child));
+ tmp_err = tmp_err->child;
+ }
+ *tmp_err = *err;
+ tmp_err->pool = pool;
+ if (tmp_err->message)
+ tmp_err->message = apr_pstrdup(pool, tmp_err->message);
+ }
+
+#if defined(SVN_DEBUG)
+ apr_pool_cleanup_register(pool, tmp_err,
+ err_abort,
+ apr_pool_cleanup_null);
+#endif
+
+ return new_err;
+}
+
+void
+svn_error_clear(svn_error_t *err)
+{
+ if (err)
+ {
+#if defined(SVN_DEBUG)
+ while (err->child)
+ err = err->child;
+ apr_pool_cleanup_kill(err->pool, err, err_abort);
+#endif
+ svn_pool_destroy(err->pool);
+ }
+}
+
+svn_boolean_t
+svn_error__is_tracing_link(svn_error_t *err)
+{
+#ifdef SVN_ERR__TRACING
+ /* ### A strcmp()? Really? I think it's the best we can do unless
+ ### we add a boolean field to svn_error_t that's set only for
+ ### these "placeholder error chain" items. Not such a bad idea,
+ ### really... */
+ return (err && err->message && !strcmp(err->message, error_tracing_link));
+#else
+ return FALSE;
+#endif
+}
+
+svn_error_t *
+svn_error_purge_tracing(svn_error_t *err)
+{
+#ifdef SVN_ERR__TRACING
+ svn_error_t *new_err = NULL, *new_err_leaf = NULL;
+
+ if (! err)
+ return SVN_NO_ERROR;
+
+ do
+ {
+ svn_error_t *tmp_err;
+
+ /* Skip over any trace-only links. */
+ while (err && svn_error__is_tracing_link(err))
+ err = err->child;
+
+ /* The link must be a real link in the error chain, otherwise an
+ error chain with trace only links would map into SVN_NO_ERROR. */
+ if (! err)
+ return svn_error_create(
+ SVN_ERR_ASSERTION_ONLY_TRACING_LINKS,
+ svn_error_compose_create(
+ svn_error__malfunction(TRUE, __FILE__, __LINE__,
+ NULL /* ### say something? */),
+ err),
+ NULL);
+
+ /* Copy the current error except for its child error pointer
+ into the new error. Share any message and source filename
+ strings from the error. */
+ tmp_err = apr_palloc(err->pool, sizeof(*tmp_err));
+ *tmp_err = *err;
+ tmp_err->child = NULL;
+
+ /* Add a new link to the new chain (creating the chain if necessary). */
+ if (! new_err)
+ {
+ new_err = tmp_err;
+ new_err_leaf = tmp_err;
+ }
+ else
+ {
+ new_err_leaf->child = tmp_err;
+ new_err_leaf = tmp_err;
+ }
+
+ /* Advance to the next link in the original chain. */
+ err = err->child;
+ } while (err);
+
+ return new_err;
+#else /* SVN_ERR__TRACING */
+ return err;
+#endif /* SVN_ERR__TRACING */
+}
+
+/* ### The logic around omitting (sic) apr_err= in maintainer mode is tightly
+ ### coupled to the current sole caller.*/
+static void
+print_error(svn_error_t *err, FILE *stream, const char *prefix)
+{
+ char errbuf[256];
+ const char *err_string;
+ svn_error_t *temp_err = NULL; /* ensure initialized even if
+ err->file == NULL */
+ /* Pretty-print the error */
+ /* Note: we can also log errors here someday. */
+
+#ifdef SVN_DEBUG
+ /* Note: err->file is _not_ in UTF-8, because it's expanded from
+ the __FILE__ preprocessor macro. */
+ const char *file_utf8;
+
+ if (err->file
+ && !(temp_err = svn_utf_cstring_to_utf8(&file_utf8, err->file,
+ err->pool)))
+ svn_error_clear(svn_cmdline_fprintf(stream, err->pool,
+ "%s:%ld", err->file, err->line));
+ else
+ {
+ svn_error_clear(svn_cmdline_fputs(SVN_FILE_LINE_UNDEFINED,
+ stream, err->pool));
+ svn_error_clear(temp_err);
+ }
+
+ {
+ const char *symbolic_name;
+ if (svn_error__is_tracing_link(err))
+ /* Skip it; the error code will be printed by the real link. */
+ svn_error_clear(svn_cmdline_fprintf(stream, err->pool, ",\n"));
+ else if ((symbolic_name = svn_error_symbolic_name(err->apr_err)))
+ svn_error_clear(svn_cmdline_fprintf(stream, err->pool,
+ ": (apr_err=%s)\n", symbolic_name));
+ else
+ svn_error_clear(svn_cmdline_fprintf(stream, err->pool,
+ ": (apr_err=%d)\n", err->apr_err));
+ }
+#endif /* SVN_DEBUG */
+
+ /* "traced call" */
+ if (svn_error__is_tracing_link(err))
+ {
+ /* Skip it. We already printed the file-line coordinates. */
+ }
+ /* Only print the same APR error string once. */
+ else if (err->message)
+ {
+ svn_error_clear(svn_cmdline_fprintf(stream, err->pool,
+ "%sE%06d: %s\n",
+ prefix, err->apr_err, err->message));
+ }
+ else
+ {
+ /* Is this a Subversion-specific error code? */
+ if ((err->apr_err > APR_OS_START_USEERR)
+ && (err->apr_err <= APR_OS_START_CANONERR))
+ err_string = svn_strerror(err->apr_err, errbuf, sizeof(errbuf));
+ /* Otherwise, this must be an APR error code. */
+ else if ((temp_err = svn_utf_cstring_to_utf8
+ (&err_string, apr_strerror(err->apr_err, errbuf,
+ sizeof(errbuf)), err->pool)))
+ {
+ svn_error_clear(temp_err);
+ err_string = _("Can't recode error string from APR");
+ }
+
+ svn_error_clear(svn_cmdline_fprintf(stream, err->pool,
+ "%sE%06d: %s\n",
+ prefix, err->apr_err, err_string));
+ }
+}
+
+void
+svn_handle_error(svn_error_t *err, FILE *stream, svn_boolean_t fatal)
+{
+ svn_handle_error2(err, stream, fatal, "svn: ");
+}
+
+void
+svn_handle_error2(svn_error_t *err,
+ FILE *stream,
+ svn_boolean_t fatal,
+ const char *prefix)
+{
+ /* In a long error chain, there may be multiple errors with the same
+ error code and no custom message. We only want to print the
+ default message for that code once; printing it multiple times
+ would add no useful information. The 'empties' array below
+ remembers the codes of empty errors already seen in the chain.
+
+ We could allocate it in err->pool, but there's no telling how
+ long err will live or how many times it will get handled. So we
+ use a subpool. */
+ apr_pool_t *subpool;
+ apr_array_header_t *empties;
+ svn_error_t *tmp_err;
+
+ /* ### The rest of this file carefully avoids using svn_pool_*(),
+ preferring apr_pool_*() instead. I can't remember why -- it may
+ be an artifact of r843793, or it may be for some deeper reason --
+ but I'm playing it safe and using apr_pool_*() here too. */
+ apr_pool_create(&subpool, err->pool);
+ empties = apr_array_make(subpool, 0, sizeof(apr_status_t));
+
+ tmp_err = err;
+ while (tmp_err)
+ {
+ svn_boolean_t printed_already = FALSE;
+
+ if (! tmp_err->message)
+ {
+ int i;
+
+ for (i = 0; i < empties->nelts; i++)
+ {
+ if (tmp_err->apr_err == APR_ARRAY_IDX(empties, i, apr_status_t) )
+ {
+ printed_already = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (! printed_already)
+ {
+ print_error(tmp_err, stream, prefix);
+ if (! tmp_err->message)
+ {
+ APR_ARRAY_PUSH(empties, apr_status_t) = tmp_err->apr_err;
+ }
+ }
+
+ tmp_err = tmp_err->child;
+ }
+
+ svn_pool_destroy(subpool);
+
+ fflush(stream);
+ if (fatal)
+ {
+ /* Avoid abort()s in maintainer mode. */
+ svn_error_clear(err);
+
+ /* We exit(1) here instead of abort()ing so that atexit handlers
+ get called. */
+ exit(EXIT_FAILURE);
+ }
+}
+
+
+void
+svn_handle_warning(FILE *stream, svn_error_t *err)
+{
+ svn_handle_warning2(stream, err, "svn: ");
+}
+
+void
+svn_handle_warning2(FILE *stream, svn_error_t *err, const char *prefix)
+{
+ char buf[256];
+
+ svn_error_clear(svn_cmdline_fprintf
+ (stream, err->pool,
+ _("%swarning: W%06d: %s\n"),
+ prefix, err->apr_err,
+ svn_err_best_message(err, buf, sizeof(buf))));
+ fflush(stream);
+}
+
+const char *
+svn_err_best_message(svn_error_t *err, char *buf, apr_size_t bufsize)
+{
+ /* Skip over any trace records. */
+ while (svn_error__is_tracing_link(err))
+ err = err->child;
+ if (err->message)
+ return err->message;
+ else
+ return svn_strerror(err->apr_err, buf, bufsize);
+}
+
+
+/* svn_strerror() and helpers */
+
+/* Duplicate of the same typedef in tests/libsvn_subr/error-code-test.c */
+typedef struct err_defn {
+ svn_errno_t errcode; /* 160004 */
+ const char *errname; /* SVN_ERR_FS_CORRUPT */
+ const char *errdesc; /* default message */
+} err_defn;
+
+/* To understand what is going on here, read svn_error_codes.h. */
+#define SVN_ERROR_BUILD_ARRAY
+#include "svn_error_codes.h"
+
+char *
+svn_strerror(apr_status_t statcode, char *buf, apr_size_t bufsize)
+{
+ const err_defn *defn;
+
+ for (defn = error_table; defn->errdesc != NULL; ++defn)
+ if (defn->errcode == (svn_errno_t)statcode)
+ {
+ apr_cpystrn(buf, _(defn->errdesc), bufsize);
+ return buf;
+ }
+
+ return apr_strerror(statcode, buf, bufsize);
+}
+
+const char *
+svn_error_symbolic_name(apr_status_t statcode)
+{
+ const err_defn *defn;
+
+ for (defn = error_table; defn->errdesc != NULL; ++defn)
+ if (defn->errcode == (svn_errno_t)statcode)
+ return defn->errname;
+
+ /* "No error" is not in error_table. */
+ if (statcode == SVN_NO_ERROR)
+ return "SVN_NO_ERROR";
+
+ return NULL;
+}
+
+
+
+/* Malfunctions. */
+
+svn_error_t *
+svn_error_raise_on_malfunction(svn_boolean_t can_return,
+ const char *file, int line,
+ const char *expr)
+{
+ if (!can_return)
+ abort(); /* Nothing else we can do as a library */
+
+ /* The filename and line number of the error source needs to be set
+ here because svn_error_createf() is not the macro defined in
+ svn_error.h but the real function. */
+ svn_error__locate(file, line);
+
+ if (expr)
+ return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL,
+ _("In file '%s' line %d: assertion failed (%s)"),
+ file, line, expr);
+ else
+ return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL,
+ _("In file '%s' line %d: internal malfunction"),
+ file, line);
+}
+
+svn_error_t *
+svn_error_abort_on_malfunction(svn_boolean_t can_return,
+ const char *file, int line,
+ const char *expr)
+{
+ svn_error_t *err = svn_error_raise_on_malfunction(TRUE, file, line, expr);
+
+ svn_handle_error2(err, stderr, FALSE, "svn: ");
+ abort();
+ return err; /* Not reached. */
+}
+
+/* The current handler for reporting malfunctions, and its default setting. */
+static svn_error_malfunction_handler_t malfunction_handler
+ = svn_error_abort_on_malfunction;
+
+svn_error_malfunction_handler_t
+svn_error_set_malfunction_handler(svn_error_malfunction_handler_t func)
+{
+ svn_error_malfunction_handler_t old_malfunction_handler
+ = malfunction_handler;
+
+ malfunction_handler = func;
+ return old_malfunction_handler;
+}
+
+/* Note: Although this is a "__" function, it is in the public ABI, so
+ * we can never remove it or change its signature. */
+svn_error_t *
+svn_error__malfunction(svn_boolean_t can_return,
+ const char *file, int line,
+ const char *expr)
+{
+ return malfunction_handler(can_return, file, line, expr);
+}
+
+
+/* Misc. */
+
+svn_error_t *
+svn_error__wrap_zlib(int zerr, const char *function, const char *message)
+{
+ apr_status_t status;
+ const char *zmsg;
+
+ if (zerr == Z_OK)
+ return SVN_NO_ERROR;
+
+ switch (zerr)
+ {
+ case Z_STREAM_ERROR:
+ status = SVN_ERR_STREAM_MALFORMED_DATA;
+ zmsg = _("stream error");
+ break;
+
+ case Z_MEM_ERROR:
+ status = APR_ENOMEM;
+ zmsg = _("out of memory");
+ break;
+
+ case Z_BUF_ERROR:
+ status = APR_ENOMEM;
+ zmsg = _("buffer error");
+ break;
+
+ case Z_VERSION_ERROR:
+ status = SVN_ERR_STREAM_UNRECOGNIZED_DATA;
+ zmsg = _("version error");
+ break;
+
+ case Z_DATA_ERROR:
+ status = SVN_ERR_STREAM_MALFORMED_DATA;
+ zmsg = _("corrupt data");
+ break;
+
+ default:
+ status = SVN_ERR_STREAM_UNRECOGNIZED_DATA;
+ zmsg = _("unknown error");
+ break;
+ }
+
+ if (message != NULL)
+ return svn_error_createf(status, NULL, "zlib (%s): %s: %s", function,
+ zmsg, message);
+ else
+ return svn_error_createf(status, NULL, "zlib (%s): %s", function, zmsg);
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/genctype.py b/contrib/subversion/subversion/libsvn_subr/genctype.py
new file mode 100755
index 0000000..21638ba
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/genctype.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+"""getctype.py - Generate the svn_ctype character classification table.
+"""
+
+# Table of ASCII character names
+names = ('nul', 'soh', 'stx', 'etx', 'eot', 'enq', 'ack', 'bel',
+ 'bs', 'ht', 'nl', 'vt', 'np', 'cr', 'so', 'si',
+ 'dle', 'dc1', 'dc2', 'dc3', 'dc4', 'nak', 'syn', 'etb',
+ 'can', 'em', 'sub', 'esc', 'fs', 'gs', 'rs', 'us',
+ 'sp', '!', '"', '#', '$', '%', '&', '\'',
+ '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', ':', ';', '<', '=', '>', '?',
+ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
+ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+ 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+ 'x', 'y', 'z', '{', '|', '}', '~', 'del')
+
+# All whitespace characters:
+# horizontal tab, vertical tab, new line, form feed, carriage return, space
+whitespace = (9, 10, 11, 12, 13, 32)
+
+# Bytes not valid in UTF-8 sequences
+utf8_invalid = (0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF)
+
+print(' /* **** DO NOT EDIT! ****')
+print(' This table was generated by genctype.py, make changes there. */')
+
+for c in range(256):
+ bits = []
+
+ # Ascii subrange
+ if c < 128:
+ bits.append('SVN_CTYPE_ASCII')
+
+ if len(names[c]) == 1:
+ name = names[c].center(3)
+ else:
+ name = names[c].ljust(3)
+
+ # Control characters
+ if c < 32 or c == 127:
+ bits.append('SVN_CTYPE_CNTRL')
+
+ # Whitespace characters
+ if c in whitespace:
+ bits.append('SVN_CTYPE_SPACE')
+
+ # Punctuation marks
+ if c >= 33 and c < 48 \
+ or c >= 58 and c < 65 \
+ or c >= 91 and c < 97 \
+ or c >= 123 and c < 127:
+ bits.append('SVN_CTYPE_PUNCT')
+
+ # Decimal digits
+ elif c >= 48 and c < 58:
+ bits.append('SVN_CTYPE_DIGIT')
+
+ # Uppercase letters
+ elif c >= 65 and c < 91:
+ bits.append('SVN_CTYPE_UPPER')
+ # Hexadecimal digits
+ if c <= 70:
+ bits.append('SVN_CTYPE_XALPHA')
+
+ # Lowercase letters
+ elif c >= 97 and c < 123:
+ bits.append('SVN_CTYPE_LOWER')
+ # Hexadecimal digits
+ if c <= 102:
+ bits.append('SVN_CTYPE_XALPHA')
+
+ # UTF-8 multibyte sequences
+ else:
+ name = hex(c)[1:]
+
+ # Lead bytes (start of sequence)
+ if c > 0xC0 and c < 0xFE and c not in utf8_invalid:
+ bits.append('SVN_CTYPE_UTF8LEAD')
+
+ # Continuation bytes
+ elif (c & 0xC0) == 0x80:
+ bits.append('SVN_CTYPE_UTF8CONT')
+
+ if len(bits) == 0:
+ flags = '0'
+ else:
+ flags = ' | '.join(bits)
+ print(' /* %s */ %s,' % (name, flags))
diff --git a/contrib/subversion/subversion/libsvn_subr/gpg_agent.c b/contrib/subversion/subversion/libsvn_subr/gpg_agent.c
new file mode 100644
index 0000000..f0395c0
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/gpg_agent.c
@@ -0,0 +1,463 @@
+/*
+ * gpg_agent.c: GPG Agent provider for SVN_AUTH_CRED_*
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+/* This auth provider stores a plaintext password in memory managed by
+ * a running gpg-agent. In contrast to other password store providers
+ * it does not save the password to disk.
+ *
+ * Prompting is performed by the gpg-agent using a "pinentry" program
+ * which needs to be installed separately. There are several pinentry
+ * implementations with different front-ends (e.g. qt, gtk, ncurses).
+ *
+ * The gpg-agent will let the password time out after a while,
+ * or immediately when it receives the SIGHUP signal.
+ * When the password has timed out it will automatically prompt the
+ * user for the password again. This is transparent to Subversion.
+ *
+ * SECURITY CONSIDERATIONS:
+ *
+ * Communication to the agent happens over a UNIX socket, which is located
+ * in a directory which only the user running Subversion can access.
+ * However, any program the user runs could access this socket and get
+ * the Subversion password if the program knows the "cache ID" Subversion
+ * uses for the password.
+ * The cache ID is very easy to obtain for programs running as the same user.
+ * Subversion uses the MD5 of the realmstring as cache ID, and these checksums
+ * are also used as filenames within ~/.subversion/auth/svn.simple.
+ * Unlike GNOME Keyring or KDE Wallet, the user is not prompted for
+ * permission if another program attempts to access the password.
+ *
+ * Therefore, while the gpg-agent is running and has the password cached,
+ * this provider is no more secure than a file storing the password in
+ * plaintext.
+ */
+
+
+/*** Includes. ***/
+
+#ifndef WIN32
+
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <apr_pools.h>
+#include "svn_auth.h"
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_cmdline.h"
+#include "svn_checksum.h"
+#include "svn_string.h"
+
+#include "private/svn_auth_private.h"
+
+#include "svn_private_config.h"
+
+#ifdef SVN_HAVE_GPG_AGENT
+
+#define BUFFER_SIZE 1024
+
+/* Modify STR in-place such that blanks are escaped as required by the
+ * gpg-agent protocol. Return a pointer to STR. */
+static char *
+escape_blanks(char *str)
+{
+ char *s = str;
+
+ while (*s)
+ {
+ if (*s == ' ')
+ *s = '+';
+ s++;
+ }
+
+ return str;
+}
+
+/* Attempt to read a gpg-agent response message from the socket SD into
+ * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response
+ * message could be read that fits into the buffer. Else return FALSE.
+ * If a message could be read it will always be NUL-terminated and the
+ * trailing newline is retained. */
+static svn_boolean_t
+receive_from_gpg_agent(int sd, char *buf, size_t n)
+{
+ int i = 0;
+ size_t recvd;
+ char c;
+
+ /* Clear existing buffer content before reading response. */
+ if (n > 0)
+ *buf = '\0';
+
+ /* Require the message to fit into the buffer and be terminated
+ * with a newline. */
+ while (i < n)
+ {
+ recvd = read(sd, &c, 1);
+ if (recvd == -1)
+ return FALSE;
+ buf[i] = c;
+ i++;
+ if (i < n && c == '\n')
+ {
+ buf[i] = '\0';
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* Using socket SD, send the option OPTION with the specified VALUE
+ * to the gpg agent. Store the response in BUF, assumed to be N bytes
+ * in size, and evaluate the response. Return TRUE if the agent liked
+ * the smell of the option, if there is such a thing, and doesn't feel
+ * saturated by it. Else return FALSE.
+ * Do temporary allocations in scratch_pool. */
+static svn_boolean_t
+send_option(int sd, char *buf, size_t n, const char *option, const char *value,
+ apr_pool_t *scratch_pool)
+{
+ const char *request;
+
+ request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value);
+
+ if (write(sd, request, strlen(request)) == -1)
+ return FALSE;
+
+ if (!receive_from_gpg_agent(sd, buf, n))
+ return FALSE;
+
+ return (strncmp(buf, "OK", 2) == 0);
+}
+
+/* Implementation of svn_auth__password_get_t that retrieves the password
+ from gpg-agent */
+static svn_error_t *
+password_get_gpg_agent(svn_boolean_t *done,
+ const char **password,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ int sd;
+ char *gpg_agent_info = NULL;
+ const char *p = NULL;
+ char *ep = NULL;
+ char *buffer;
+
+ apr_array_header_t *socket_details;
+ const char *request = NULL;
+ const char *cache_id = NULL;
+ struct sockaddr_un addr;
+ const char *tty_name;
+ const char *tty_type;
+ const char *lc_ctype;
+ const char *display;
+ const char *socket_name = NULL;
+ svn_checksum_t *digest = NULL;
+ char *password_prompt;
+ char *realm_prompt;
+
+ *done = FALSE;
+
+ gpg_agent_info = getenv("GPG_AGENT_INFO");
+ if (gpg_agent_info != NULL)
+ {
+ socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE,
+ pool);
+ socket_name = APR_ARRAY_IDX(socket_details, 0, const char *);
+ }
+ else
+ return SVN_NO_ERROR;
+
+ if (socket_name != NULL)
+ {
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1);
+ addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
+
+ sd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sd == -1)
+ return SVN_NO_ERROR;
+
+ if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ return SVN_NO_ERROR;
+
+ /* Receive the connection status from the gpg-agent daemon. */
+ buffer = apr_palloc(pool, BUFFER_SIZE);
+ if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+
+ if (strncmp(buffer, "OK", 2) != 0)
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+
+ /* The GPG-Agent documentation says:
+ * "Clients should deny to access an agent with a socket name which does
+ * not match its own configuration". */
+ request = "GETINFO socket_name\n";
+ if (write(sd, request, strlen(request)) == -1)
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+ if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+ if (strncmp(buffer, "D", 1) == 0)
+ p = &buffer[2];
+ if (!p)
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+ ep = strchr(p, '\n');
+ if (ep != NULL)
+ *ep = '\0';
+ if (strcmp(socket_name, p) != 0)
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+ /* The agent will terminate its response with "OK". */
+ if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+ if (strncmp(buffer, "OK", 2) != 0)
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+
+ /* Send TTY_NAME to the gpg-agent daemon. */
+ tty_name = getenv("GPG_TTY");
+ if (tty_name != NULL)
+ {
+ if (!send_option(sd, buffer, BUFFER_SIZE, "ttyname", tty_name, pool))
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+
+ /* Send TTY_TYPE to the gpg-agent daemon. */
+ tty_type = getenv("TERM");
+ if (tty_type != NULL)
+ {
+ if (!send_option(sd, buffer, BUFFER_SIZE, "ttytype", tty_type, pool))
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+
+ /* Compute LC_CTYPE. */
+ lc_ctype = getenv("LC_ALL");
+ if (lc_ctype == NULL)
+ lc_ctype = getenv("LC_CTYPE");
+ if (lc_ctype == NULL)
+ lc_ctype = getenv("LANG");
+
+ /* Send LC_CTYPE to the gpg-agent daemon. */
+ if (lc_ctype != NULL)
+ {
+ if (!send_option(sd, buffer, BUFFER_SIZE, "lc-ctype", lc_ctype, pool))
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Send DISPLAY to the gpg-agent daemon. */
+ display = getenv("DISPLAY");
+ if (display != NULL)
+ {
+ if (!send_option(sd, buffer, BUFFER_SIZE, "display", display, pool))
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Create the CACHE_ID which will be generated based on REALMSTRING similar
+ to other password caching mechanisms. */
+ SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring,
+ strlen(realmstring), pool));
+ cache_id = svn_checksum_to_cstring(digest, pool);
+
+ password_prompt = apr_psprintf(pool, _("Password for '%s': "), username);
+ realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"),
+ realmstring);
+ request = apr_psprintf(pool,
+ "GET_PASSPHRASE --data %s--repeat=1 "
+ "%s X %s %s\n",
+ non_interactive ? "--no-ask " : "",
+ cache_id,
+ escape_blanks(password_prompt),
+ escape_blanks(realm_prompt));
+
+ if (write(sd, request, strlen(request)) == -1)
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+ if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
+ {
+ close(sd);
+ return SVN_NO_ERROR;
+ }
+
+ close(sd);
+
+ if (strncmp(buffer, "ERR", 3) == 0)
+ return SVN_NO_ERROR;
+
+ p = NULL;
+ if (strncmp(buffer, "D", 1) == 0)
+ p = &buffer[2];
+
+ if (!p)
+ return SVN_NO_ERROR;
+
+ ep = strchr(p, '\n');
+ if (ep != NULL)
+ *ep = '\0';
+
+ *password = p;
+
+ *done = TRUE;
+ return SVN_NO_ERROR;
+}
+
+
+/* Implementation of svn_auth__password_set_t that would store the
+ password in GPG Agent if that's how this particular integration
+ worked. But it isn't. GPG Agent stores the password provided by
+ the user via the pinentry program immediately upon its provision
+ (and regardless of its accuracy as passwords go), so there's
+ nothing really to do here. */
+static svn_error_t *
+password_set_gpg_agent(svn_boolean_t *done,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ const char *password,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ *done = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An implementation of svn_auth_provider_t::first_credentials() */
+static svn_error_t *
+simple_gpg_agent_first_creds(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_get(credentials, iter_baton,
+ provider_baton, parameters,
+ realmstring, password_get_gpg_agent,
+ SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
+ pool);
+}
+
+
+/* An implementation of svn_auth_provider_t::save_credentials() */
+static svn_error_t *
+simple_gpg_agent_save_creds(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_set(saved, credentials,
+ provider_baton, parameters,
+ realmstring, password_set_gpg_agent,
+ SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
+ pool);
+}
+
+
+static const svn_auth_provider_t gpg_agent_simple_provider = {
+ SVN_AUTH_CRED_SIMPLE,
+ simple_gpg_agent_first_creds,
+ NULL,
+ simple_gpg_agent_save_creds
+};
+
+
+/* Public API */
+void
+svn_auth_get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+
+ po->vtable = &gpg_agent_simple_provider;
+ *provider = po;
+}
+
+#endif /* SVN_HAVE_GPG_AGENT */
+#endif /* !WIN32 */
diff --git a/contrib/subversion/subversion/libsvn_subr/hash.c b/contrib/subversion/subversion/libsvn_subr/hash.c
new file mode 100644
index 0000000..7868cac
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/hash.c
@@ -0,0 +1,642 @@
+/*
+ * hash.c : dumping and reading hash tables to/from files.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <limits.h>
+
+#include <apr_version.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_file_io.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_sorts.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+
+#include "private/svn_dep_compat.h"
+#include "private/svn_subr_private.h"
+
+#include "svn_private_config.h"
+
+
+
+
+/*
+ * The format of a dumped hash table is:
+ *
+ * K <nlength>
+ * name (a string of <nlength> bytes, followed by a newline)
+ * V <vlength>
+ * val (a string of <vlength> bytes, followed by a newline)
+ * [... etc, etc ...]
+ * END
+ *
+ *
+ * (Yes, there is a newline after END.)
+ *
+ * For example:
+ *
+ * K 5
+ * color
+ * V 3
+ * red
+ * K 11
+ * wine review
+ * V 376
+ * A forthright entrance, yet coquettish on the tongue, its deceptively
+ * fruity exterior hides the warm mahagony undercurrent that is the
+ * hallmark of Chateau Fraisant-Pitre. Connoisseurs of the region will
+ * be pleased to note the familiar, subtle hints of mulberries and
+ * carburator fluid. Its confident finish is marred only by a barely
+ * detectable suggestion of rancid squid ink.
+ * K 5
+ * price
+ * V 8
+ * US $6.50
+ * END
+ *
+ */
+
+
+
+
+/*** Dumping and loading hash files. */
+
+/* Implements svn_hash_read2 and svn_hash_read_incremental. */
+static svn_error_t *
+hash_read(apr_hash_t *hash, svn_stream_t *stream, const char *terminator,
+ svn_boolean_t incremental, apr_pool_t *pool)
+{
+ svn_stringbuf_t *buf;
+ svn_boolean_t eof;
+ apr_size_t len, keylen, vallen;
+ char c, *keybuf, *valbuf;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ while (1)
+ {
+ svn_error_t *err;
+ apr_uint64_t ui64;
+
+ svn_pool_clear(iterpool);
+
+ /* Read a key length line. Might be END, though. */
+ SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));
+
+ /* Check for the end of the hash. */
+ if ((!terminator && eof && buf->len == 0)
+ || (terminator && (strcmp(buf->data, terminator) == 0)))
+ break;
+
+ /* Check for unexpected end of stream */
+ if (eof)
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ _("Serialized hash missing terminator"));
+
+ if ((buf->len >= 3) && (buf->data[0] == 'K') && (buf->data[1] == ' '))
+ {
+ /* Get the length of the key */
+ err = svn_cstring_strtoui64(&ui64, buf->data + 2,
+ 0, APR_SIZE_MAX, 10);
+ if (err)
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, err,
+ _("Serialized hash malformed"));
+ keylen = (apr_size_t)ui64;
+
+ /* Now read that much into a buffer. */
+ keybuf = apr_palloc(pool, keylen + 1);
+ SVN_ERR(svn_stream_read(stream, keybuf, &keylen));
+ keybuf[keylen] = '\0';
+
+ /* Suck up extra newline after key data */
+ len = 1;
+ SVN_ERR(svn_stream_read(stream, &c, &len));
+ if (c != '\n')
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ _("Serialized hash malformed"));
+
+ /* Read a val length line */
+ SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));
+
+ if ((buf->data[0] == 'V') && (buf->data[1] == ' '))
+ {
+ err = svn_cstring_strtoui64(&ui64, buf->data + 2,
+ 0, APR_SIZE_MAX, 10);
+ if (err)
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, err,
+ _("Serialized hash malformed"));
+ vallen = (apr_size_t)ui64;
+
+ valbuf = apr_palloc(iterpool, vallen + 1);
+ SVN_ERR(svn_stream_read(stream, valbuf, &vallen));
+ valbuf[vallen] = '\0';
+
+ /* Suck up extra newline after val data */
+ len = 1;
+ SVN_ERR(svn_stream_read(stream, &c, &len));
+ if (c != '\n')
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ _("Serialized hash malformed"));
+
+ /* Add a new hash entry. */
+ apr_hash_set(hash, keybuf, keylen,
+ svn_string_ncreate(valbuf, vallen, pool));
+ }
+ else
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ _("Serialized hash malformed"));
+ }
+ else if (incremental && (buf->len >= 3)
+ && (buf->data[0] == 'D') && (buf->data[1] == ' '))
+ {
+ /* Get the length of the key */
+ err = svn_cstring_strtoui64(&ui64, buf->data + 2,
+ 0, APR_SIZE_MAX, 10);
+ if (err)
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, err,
+ _("Serialized hash malformed"));
+ keylen = (apr_size_t)ui64;
+
+ /* Now read that much into a buffer. */
+ keybuf = apr_palloc(iterpool, keylen + 1);
+ SVN_ERR(svn_stream_read(stream, keybuf, &keylen));
+ keybuf[keylen] = '\0';
+
+ /* Suck up extra newline after key data */
+ len = 1;
+ SVN_ERR(svn_stream_read(stream, &c, &len));
+ if (c != '\n')
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ _("Serialized hash malformed"));
+
+ /* Remove this hash entry. */
+ apr_hash_set(hash, keybuf, keylen, NULL);
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ _("Serialized hash malformed"));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_hash_write2 and svn_hash_write_incremental. */
+static svn_error_t *
+hash_write(apr_hash_t *hash, apr_hash_t *oldhash, svn_stream_t *stream,
+ const char *terminator, apr_pool_t *pool)
+{
+ apr_pool_t *subpool;
+ apr_size_t len;
+ apr_array_header_t *list;
+ int i;
+
+ subpool = svn_pool_create(pool);
+
+ list = svn_sort__hash(hash, svn_sort_compare_items_lexically, pool);
+ for (i = 0; i < list->nelts; i++)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(list, i, svn_sort__item_t);
+ svn_string_t *valstr = item->value;
+
+ svn_pool_clear(subpool);
+
+ /* Don't output entries equal to the ones in oldhash, if present. */
+ if (oldhash)
+ {
+ svn_string_t *oldstr = apr_hash_get(oldhash, item->key, item->klen);
+
+ if (oldstr && svn_string_compare(valstr, oldstr))
+ continue;
+ }
+
+ if (item->klen < 0)
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ _("Cannot serialize negative length"));
+
+ /* Write it out. */
+ SVN_ERR(svn_stream_printf(stream, subpool,
+ "K %" APR_SIZE_T_FMT "\n%s\n"
+ "V %" APR_SIZE_T_FMT "\n",
+ (apr_size_t) item->klen,
+ (const char *) item->key,
+ valstr->len));
+ len = valstr->len;
+ SVN_ERR(svn_stream_write(stream, valstr->data, &len));
+ SVN_ERR(svn_stream_puts(stream, "\n"));
+ }
+
+ if (oldhash)
+ {
+ /* Output a deletion entry for each property in oldhash but not hash. */
+ list = svn_sort__hash(oldhash, svn_sort_compare_items_lexically,
+ pool);
+ for (i = 0; i < list->nelts; i++)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(list, i, svn_sort__item_t);
+
+ svn_pool_clear(subpool);
+
+ /* If it's not present in the new hash, write out a D entry. */
+ if (! apr_hash_get(hash, item->key, item->klen))
+ SVN_ERR(svn_stream_printf(stream, subpool,
+ "D %" APR_SSIZE_T_FMT "\n%s\n",
+ item->klen, (const char *) item->key));
+ }
+ }
+
+ if (terminator)
+ SVN_ERR(svn_stream_printf(stream, subpool, "%s\n", terminator));
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *svn_hash_read2(apr_hash_t *hash, svn_stream_t *stream,
+ const char *terminator, apr_pool_t *pool)
+{
+ return hash_read(hash, stream, terminator, FALSE, pool);
+}
+
+
+svn_error_t *svn_hash_read_incremental(apr_hash_t *hash,
+ svn_stream_t *stream,
+ const char *terminator,
+ apr_pool_t *pool)
+{
+ return hash_read(hash, stream, terminator, TRUE, pool);
+}
+
+
+svn_error_t *
+svn_hash_write2(apr_hash_t *hash, svn_stream_t *stream,
+ const char *terminator, apr_pool_t *pool)
+{
+ return hash_write(hash, NULL, stream, terminator, pool);
+}
+
+
+svn_error_t *
+svn_hash_write_incremental(apr_hash_t *hash, apr_hash_t *oldhash,
+ svn_stream_t *stream, const char *terminator,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(oldhash != NULL);
+ return hash_write(hash, oldhash, stream, terminator, pool);
+}
+
+
+svn_error_t *
+svn_hash_write(apr_hash_t *hash, apr_file_t *destfile, apr_pool_t *pool)
+{
+ return hash_write(hash, NULL, svn_stream_from_aprfile2(destfile, TRUE, pool),
+ SVN_HASH_TERMINATOR, pool);
+}
+
+
+/* There are enough quirks in the deprecated svn_hash_read that we
+ should just preserve its implementation. */
+svn_error_t *
+svn_hash_read(apr_hash_t *hash,
+ apr_file_t *srcfile,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ char buf[SVN_KEYLINE_MAXLEN];
+ apr_size_t num_read;
+ char c;
+ int first_time = 1;
+
+
+ while (1)
+ {
+ /* Read a key length line. Might be END, though. */
+ apr_size_t len = sizeof(buf);
+
+ err = svn_io_read_length_line(srcfile, buf, &len, pool);
+ if (err && APR_STATUS_IS_EOF(err->apr_err) && first_time)
+ {
+ /* We got an EOF on our very first attempt to read, which
+ means it's a zero-byte file. No problem, just go home. */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ /* Any other circumstance is a genuine error. */
+ return err;
+
+ first_time = 0;
+
+ if (((len == 3) && (buf[0] == 'E') && (buf[1] == 'N') && (buf[2] == 'D'))
+ || ((len == 9)
+ && (buf[0] == 'P')
+ && (buf[1] == 'R') /* We formerly used just "END" to */
+ && (buf[2] == 'O') /* end a property hash, but later */
+ && (buf[3] == 'P') /* we added "PROPS-END", so that */
+ && (buf[4] == 'S') /* the fs dump format would be */
+ && (buf[5] == '-') /* more human-readable. That's */
+ && (buf[6] == 'E') /* why we accept either way here. */
+ && (buf[7] == 'N')
+ && (buf[8] == 'D')))
+ {
+ /* We've reached the end of the dumped hash table, so leave. */
+ return SVN_NO_ERROR;
+ }
+ else if ((buf[0] == 'K') && (buf[1] == ' '))
+ {
+ size_t keylen;
+ int parsed_len;
+ void *keybuf;
+
+ /* Get the length of the key */
+ SVN_ERR(svn_cstring_atoi(&parsed_len, buf + 2));
+ keylen = parsed_len;
+
+ /* Now read that much into a buffer, + 1 byte for null terminator */
+ keybuf = apr_palloc(pool, keylen + 1);
+ SVN_ERR(svn_io_file_read_full2(srcfile,
+ keybuf, keylen,
+ &num_read, NULL, pool));
+ ((char *) keybuf)[keylen] = '\0';
+
+ /* Suck up extra newline after key data */
+ SVN_ERR(svn_io_file_getc(&c, srcfile, pool));
+ if (c != '\n')
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
+
+ /* Read a val length line */
+ len = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(srcfile, buf, &len, pool));
+
+ if ((buf[0] == 'V') && (buf[1] == ' '))
+ {
+ svn_string_t *value = apr_palloc(pool, sizeof(*value));
+ apr_size_t vallen;
+ void *valbuf;
+
+ /* Get the length of the value */
+ SVN_ERR(svn_cstring_atoi(&parsed_len, buf + 2));
+ vallen = parsed_len;
+
+ /* Again, 1 extra byte for the null termination. */
+ valbuf = apr_palloc(pool, vallen + 1);
+ SVN_ERR(svn_io_file_read_full2(srcfile,
+ valbuf, vallen,
+ &num_read, NULL, pool));
+ ((char *) valbuf)[vallen] = '\0';
+
+ /* Suck up extra newline after val data */
+ SVN_ERR(svn_io_file_getc(&c, srcfile, pool));
+ if (c != '\n')
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
+
+ value->data = valbuf;
+ value->len = vallen;
+
+ /* The Grand Moment: add a new hash entry! */
+ apr_hash_set(hash, keybuf, keylen, value);
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
+ }
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
+ }
+ } /* while (1) */
+}
+
+
+
+/*** Diffing hashes ***/
+
+svn_error_t *
+svn_hash_diff(apr_hash_t *hash_a,
+ apr_hash_t *hash_b,
+ svn_hash_diff_func_t diff_func,
+ void *diff_func_baton,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ if (hash_a)
+ for (hi = apr_hash_first(pool, hash_a); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+
+ apr_hash_this(hi, &key, &klen, NULL);
+
+ if (hash_b && (apr_hash_get(hash_b, key, klen)))
+ SVN_ERR((*diff_func)(key, klen, svn_hash_diff_key_both,
+ diff_func_baton));
+ else
+ SVN_ERR((*diff_func)(key, klen, svn_hash_diff_key_a,
+ diff_func_baton));
+ }
+
+ if (hash_b)
+ for (hi = apr_hash_first(pool, hash_b); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+
+ apr_hash_this(hi, &key, &klen, NULL);
+
+ if (! (hash_a && apr_hash_get(hash_a, key, klen)))
+ SVN_ERR((*diff_func)(key, klen, svn_hash_diff_key_b,
+ diff_func_baton));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Misc. hash APIs ***/
+
+svn_error_t *
+svn_hash_keys(apr_array_header_t **array,
+ apr_hash_t *hash,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ *array = apr_array_make(pool, apr_hash_count(hash), sizeof(const char *));
+
+ for (hi = apr_hash_first(pool, hash); hi; hi = apr_hash_next(hi))
+ {
+ APR_ARRAY_PUSH(*array, const char *) = svn__apr_hash_index_key(hi);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_hash_from_cstring_keys(apr_hash_t **hash_p,
+ const apr_array_header_t *keys,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_hash_t *hash = svn_hash__make(pool);
+ for (i = 0; i < keys->nelts; i++)
+ {
+ const char *key =
+ apr_pstrdup(pool, APR_ARRAY_IDX(keys, i, const char *));
+ svn_hash_sets(hash, key, key);
+ }
+ *hash_p = hash;
+ return SVN_NO_ERROR;
+}
+
+
+#if !APR_VERSION_AT_LEAST(1, 3, 0)
+void
+svn_hash__clear(apr_hash_t *hash)
+{
+ apr_hash_index_t *hi;
+ const void *key;
+ apr_ssize_t klen;
+
+ for (hi = apr_hash_first(NULL, hash); hi; hi = apr_hash_next(hi))
+ {
+ apr_hash_this(hi, &key, &klen, NULL);
+ apr_hash_set(hash, key, klen, NULL);
+ }
+}
+#endif
+
+
+
+/*** Specialized getter APIs ***/
+
+const char *
+svn_hash__get_cstring(apr_hash_t *hash,
+ const char *key,
+ const char *default_value)
+{
+ if (hash)
+ {
+ const char *value = svn_hash_gets(hash, key);
+ return value ? value : default_value;
+ }
+
+ return default_value;
+}
+
+
+svn_boolean_t
+svn_hash__get_bool(apr_hash_t *hash, const char *key,
+ svn_boolean_t default_value)
+{
+ const char *tmp_value = svn_hash__get_cstring(hash, key, NULL);
+ svn_tristate_t value = svn_tristate__from_word(tmp_value);
+
+ if (value == svn_tristate_true)
+ return TRUE;
+ else if (value == svn_tristate_false)
+ return FALSE;
+
+ return default_value;
+}
+
+
+
+/*** Optimized hash function ***/
+
+/* Optimized version of apr_hashfunc_default in APR 1.4.5 and earlier.
+ * It assumes that the CPU has 32-bit multiplications with high throughput
+ * of at least 1 operation every 3 cycles. Latency is not an issue. Another
+ * optimization is a mildly unrolled main loop and breaking the dependency
+ * chain within the loop.
+ *
+ * Note that most CPUs including Intel Atom, VIA Nano, ARM feature the
+ * assumed pipelined multiplication circuitry. They can do one MUL every
+ * or every other cycle.
+ *
+ * The performance is ultimately limited by the fact that most CPUs can
+ * do only one LOAD and only one BRANCH operation per cycle. The best we
+ * can do is to process one character per cycle - provided the processor
+ * is wide enough to do 1 LOAD, COMPARE, BRANCH, MUL and ADD per cycle.
+ */
+static unsigned int
+hashfunc_compatible(const char *char_key, apr_ssize_t *klen)
+{
+ unsigned int hash = 0;
+ const unsigned char *key = (const unsigned char *)char_key;
+ const unsigned char *p;
+ apr_ssize_t i;
+
+ if (*klen == APR_HASH_KEY_STRING)
+ {
+ for (p = key; ; p+=4)
+ {
+ unsigned int new_hash = hash * 33 * 33 * 33 * 33;
+ if (!p[0]) break;
+ new_hash += p[0] * 33 * 33 * 33;
+ if (!p[1]) break;
+ new_hash += p[1] * 33 * 33;
+ if (!p[2]) break;
+ new_hash += p[2] * 33;
+ if (!p[3]) break;
+ hash = new_hash + p[3];
+ }
+ for (; *p; p++)
+ hash = hash * 33 + *p;
+
+ *klen = p - key;
+ }
+ else
+ {
+ for (p = key, i = *klen; i >= 4; i-=4, p+=4)
+ {
+ hash = hash * 33 * 33 * 33 * 33
+ + p[0] * 33 * 33 * 33
+ + p[1] * 33 * 33
+ + p[2] * 33
+ + p[3];
+ }
+ for (; i; i--, p++)
+ hash = hash * 33 + *p;
+ }
+
+ return hash;
+}
+
+apr_hash_t *
+svn_hash__make(apr_pool_t *pool)
+{
+ return apr_hash_make_custom(pool, hashfunc_compatible);
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/internal_statements.h b/contrib/subversion/subversion/libsvn_subr/internal_statements.h
new file mode 100644
index 0000000..fc429b3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/internal_statements.h
@@ -0,0 +1,76 @@
+/* This file is automatically generated from internal_statements.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_subr/token-map.h.
+ * Do not edit this file -- edit the source and rerun gen-make.py */
+
+#define STMT_INTERNAL_SAVEPOINT_SVN 0
+#define STMT_0_INFO {"STMT_INTERNAL_SAVEPOINT_SVN", NULL}
+#define STMT_0 \
+ "SAVEPOINT svn " \
+ ""
+
+#define STMT_INTERNAL_RELEASE_SAVEPOINT_SVN 1
+#define STMT_1_INFO {"STMT_INTERNAL_RELEASE_SAVEPOINT_SVN", NULL}
+#define STMT_1 \
+ "RELEASE SAVEPOINT svn " \
+ ""
+
+#define STMT_INTERNAL_ROLLBACK_TO_SAVEPOINT_SVN 2
+#define STMT_2_INFO {"STMT_INTERNAL_ROLLBACK_TO_SAVEPOINT_SVN", NULL}
+#define STMT_2 \
+ "ROLLBACK TO SAVEPOINT svn " \
+ ""
+
+#define STMT_INTERNAL_BEGIN_TRANSACTION 3
+#define STMT_3_INFO {"STMT_INTERNAL_BEGIN_TRANSACTION", NULL}
+#define STMT_3 \
+ "BEGIN TRANSACTION " \
+ ""
+
+#define STMT_INTERNAL_BEGIN_IMMEDIATE_TRANSACTION 4
+#define STMT_4_INFO {"STMT_INTERNAL_BEGIN_IMMEDIATE_TRANSACTION", NULL}
+#define STMT_4 \
+ "BEGIN IMMEDIATE TRANSACTION " \
+ ""
+
+#define STMT_INTERNAL_COMMIT_TRANSACTION 5
+#define STMT_5_INFO {"STMT_INTERNAL_COMMIT_TRANSACTION", NULL}
+#define STMT_5 \
+ "COMMIT TRANSACTION " \
+ ""
+
+#define STMT_INTERNAL_ROLLBACK_TRANSACTION 6
+#define STMT_6_INFO {"STMT_INTERNAL_ROLLBACK_TRANSACTION", NULL}
+#define STMT_6 \
+ "ROLLBACK TRANSACTION " \
+ ""
+
+#define STMT_INTERNAL_LAST 7
+#define STMT_7_INFO {"STMT_INTERNAL_LAST", NULL}
+#define STMT_7 \
+ "; " \
+ ""
+
+#define INTERNAL_STATEMENTS_SQL_DECLARE_STATEMENTS(varname) \
+ static const char * const varname[] = { \
+ STMT_0, \
+ STMT_1, \
+ STMT_2, \
+ STMT_3, \
+ STMT_4, \
+ STMT_5, \
+ STMT_6, \
+ STMT_7, \
+ NULL \
+ }
+
+#define INTERNAL_STATEMENTS_SQL_DECLARE_STATEMENT_INFO(varname) \
+ static const char * const varname[][2] = { \
+ STMT_0_INFO, \
+ STMT_1_INFO, \
+ STMT_2_INFO, \
+ STMT_3_INFO, \
+ STMT_4_INFO, \
+ STMT_5_INFO, \
+ STMT_6_INFO, \
+ STMT_7_INFO, \
+ {NULL, NULL} \
+ }
diff --git a/contrib/subversion/subversion/libsvn_subr/internal_statements.sql b/contrib/subversion/subversion/libsvn_subr/internal_statements.sql
new file mode 100644
index 0000000..c78e855
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/internal_statements.sql
@@ -0,0 +1,47 @@
+/* sqlite.sql -- queries used by the Subversion SQLite interface
+ * This is intended for use with SQLite 3
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+-- STMT_INTERNAL_SAVEPOINT_SVN
+SAVEPOINT svn
+
+-- STMT_INTERNAL_RELEASE_SAVEPOINT_SVN
+RELEASE SAVEPOINT svn
+
+-- STMT_INTERNAL_ROLLBACK_TO_SAVEPOINT_SVN
+ROLLBACK TO SAVEPOINT svn
+
+-- STMT_INTERNAL_BEGIN_TRANSACTION
+BEGIN TRANSACTION
+
+-- STMT_INTERNAL_BEGIN_IMMEDIATE_TRANSACTION
+BEGIN IMMEDIATE TRANSACTION
+
+-- STMT_INTERNAL_COMMIT_TRANSACTION
+COMMIT TRANSACTION
+
+-- STMT_INTERNAL_ROLLBACK_TRANSACTION
+ROLLBACK TRANSACTION
+
+/* Dummmy statement to determine the number of internal statements */
+-- STMT_INTERNAL_LAST
+;
diff --git a/contrib/subversion/subversion/libsvn_subr/io.c b/contrib/subversion/subversion/libsvn_subr/io.c
new file mode 100644
index 0000000..58bc540
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/io.c
@@ -0,0 +1,4768 @@
+/*
+ * io.c: shared file reading, writing, and probing code.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdio.h>
+
+#ifndef WIN32
+#include <unistd.h>
+#endif
+
+#ifndef APR_STATUS_IS_EPERM
+#include <errno.h>
+#ifdef EPERM
+#define APR_STATUS_IS_EPERM(s) ((s) == EPERM)
+#else
+#define APR_STATUS_IS_EPERM(s) (0)
+#endif
+#endif
+
+#include <apr_lib.h>
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_file_info.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+#include <apr_portable.h>
+#include <apr_md5.h>
+
+#ifdef WIN32
+#include <arch/win32/apr_arch_file_io.h>
+#endif
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "svn_utf.h"
+#include "svn_config.h"
+#include "svn_private_config.h"
+#include "svn_ctype.h"
+
+#include "private/svn_atomic.h"
+#include "private/svn_io_private.h"
+
+#define SVN_SLEEP_ENV_VAR "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS"
+
+/*
+ Windows is 'aided' by a number of types of applications that
+ follow other applications around and open up files they have
+ changed for various reasons (the most intrusive are virus
+ scanners). So, if one of these other apps has glommed onto
+ our file we may get an 'access denied' error.
+
+ This retry loop does not completely solve the problem (who
+ knows how long the other app is going to hold onto it for), but
+ goes a long way towards minimizing it. It is not an infinite
+ loop because there might really be an error.
+
+ Another reason for retrying delete operations on Windows
+ is that they are asynchronous -- the file or directory is not
+ actually deleted until the last handle to it is closed. The
+ retry loop cannot completely solve this problem either, but can
+ help mitigate it.
+*/
+#define RETRY_MAX_ATTEMPTS 100
+#define RETRY_INITIAL_SLEEP 1000
+#define RETRY_MAX_SLEEP 128000
+
+#define RETRY_LOOP(err, expr, retry_test, sleep_test) \
+ do \
+ { \
+ apr_status_t os_err = APR_TO_OS_ERROR(err); \
+ int sleep_count = RETRY_INITIAL_SLEEP; \
+ int retries; \
+ for (retries = 0; \
+ retries < RETRY_MAX_ATTEMPTS && (retry_test); \
+ os_err = APR_TO_OS_ERROR(err)) \
+ { \
+ if (sleep_test) \
+ { \
+ ++retries; \
+ apr_sleep(sleep_count); \
+ if (sleep_count < RETRY_MAX_SLEEP) \
+ sleep_count *= 2; \
+ } \
+ (err) = (expr); \
+ } \
+ } \
+ while (0)
+
+#if defined(EDEADLK) && APR_HAS_THREADS
+#define FILE_LOCK_RETRY_LOOP(err, expr) \
+ RETRY_LOOP(err, \
+ expr, \
+ (APR_STATUS_IS_EINTR(err) || os_err == EDEADLK), \
+ (!APR_STATUS_IS_EINTR(err)))
+#else
+#define FILE_LOCK_RETRY_LOOP(err, expr) \
+ RETRY_LOOP(err, \
+ expr, \
+ (APR_STATUS_IS_EINTR(err)), \
+ 0)
+#endif
+
+#ifndef WIN32_RETRY_LOOP
+#if defined(WIN32) && !defined(SVN_NO_WIN32_RETRY_LOOP)
+#define WIN32_RETRY_LOOP(err, expr) \
+ RETRY_LOOP(err, expr, (os_err == ERROR_ACCESS_DENIED \
+ || os_err == ERROR_SHARING_VIOLATION \
+ || os_err == ERROR_DIR_NOT_EMPTY), \
+ 1)
+#else
+#define WIN32_RETRY_LOOP(err, expr) ((void)0)
+#endif
+#endif
+
+/* Forward declaration */
+static apr_status_t
+dir_is_empty(const char *dir, apr_pool_t *pool);
+static APR_INLINE svn_error_t *
+do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
+ const char *msg, const char *msg_no_name,
+ apr_pool_t *pool);
+
+/* Local wrapper of svn_path_cstring_to_utf8() that does no copying on
+ * operating systems where APR always uses utf-8 as native path format */
+static svn_error_t *
+cstring_to_utf8(const char **path_utf8,
+ const char *path_apr,
+ apr_pool_t *pool)
+{
+#if defined(WIN32) || defined(DARWIN)
+ *path_utf8 = path_apr;
+ return SVN_NO_ERROR;
+#else
+ return svn_path_cstring_to_utf8(path_utf8, path_apr, pool);
+#endif
+}
+
+/* Local wrapper of svn_path_cstring_from_utf8() that does no copying on
+ * operating systems where APR always uses utf-8 as native path format */
+static svn_error_t *
+cstring_from_utf8(const char **path_apr,
+ const char *path_utf8,
+ apr_pool_t *pool)
+{
+#if defined(WIN32) || defined(DARWIN)
+ *path_apr = path_utf8;
+ return SVN_NO_ERROR;
+#else
+ return svn_path_cstring_from_utf8(path_apr, path_utf8, pool);
+#endif
+}
+
+/* Helper function that allows to convert an APR-level PATH to something
+ * that we can pass the svn_error_wrap_apr. Since we use it in context
+ * of error reporting, having *some* path info may be more useful than
+ * having none. Therefore, we use a best effort approach here.
+ *
+ * This is different from svn_io_file_name_get in that it uses a different
+ * signature style and will never fail.
+ */
+static const char *
+try_utf8_from_internal_style(const char *path, apr_pool_t *pool)
+{
+ svn_error_t *error;
+ const char *path_utf8;
+
+ /* Special case. */
+ if (path == NULL)
+ return "(NULL)";
+
+ /* (try to) convert PATH to UTF-8. If that fails, continue with the plain
+ * PATH because it is the best we have. It may actually be UTF-8 already.
+ */
+ error = cstring_to_utf8(&path_utf8, path, pool);
+ if (error)
+ {
+ /* fallback to best representation we have */
+
+ svn_error_clear(error);
+ path_utf8 = path;
+ }
+
+ /* Toggle (back-)slashes etc. as necessary.
+ */
+ return svn_dirent_local_style(path_utf8, pool);
+}
+
+
+/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
+ * NAME is in the internal encoding used by APR; PARENT is in
+ * UTF-8 and in internal (not local) style.
+ *
+ * Use PARENT only for generating an error string if the conversion
+ * fails because NAME could not be represented in UTF-8. In that
+ * case, return a two-level error in which the outer error's message
+ * mentions PARENT, but the inner error's message does not mention
+ * NAME (except possibly in hex) since NAME may not be printable.
+ * Such a compound error at least allows the user to go looking in the
+ * right directory for the problem.
+ *
+ * If there is any other error, just return that error directly.
+ *
+ * If there is any error, the effect on *NAME_P is undefined.
+ *
+ * *NAME_P and NAME may refer to the same storage.
+ */
+static svn_error_t *
+entry_name_to_utf8(const char **name_p,
+ const char *name,
+ const char *parent,
+ apr_pool_t *pool)
+{
+#if defined(WIN32) || defined(DARWIN)
+ *name_p = apr_pstrdup(pool, name);
+ return SVN_NO_ERROR;
+#else
+ svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
+ if (err && err->apr_err == APR_EINVAL)
+ {
+ return svn_error_createf(err->apr_err, err,
+ _("Error converting entry "
+ "in directory '%s' to UTF-8"),
+ svn_dirent_local_style(parent, pool));
+ }
+ return err;
+#endif
+}
+
+
+
+static void
+map_apr_finfo_to_node_kind(svn_node_kind_t *kind,
+ svn_boolean_t *is_special,
+ apr_finfo_t *finfo)
+{
+ *is_special = FALSE;
+
+ if (finfo->filetype == APR_REG)
+ *kind = svn_node_file;
+ else if (finfo->filetype == APR_DIR)
+ *kind = svn_node_dir;
+ else if (finfo->filetype == APR_LNK)
+ {
+ *is_special = TRUE;
+ *kind = svn_node_file;
+ }
+ else
+ *kind = svn_node_unknown;
+}
+
+/* Helper for svn_io_check_path() and svn_io_check_resolved_path();
+ essentially the same semantics as those two, with the obvious
+ interpretation for RESOLVE_SYMLINKS. */
+static svn_error_t *
+io_check_path(const char *path,
+ svn_boolean_t resolve_symlinks,
+ svn_boolean_t *is_special_p,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ apr_int32_t flags;
+ apr_finfo_t finfo;
+ apr_status_t apr_err;
+ const char *path_apr;
+ svn_boolean_t is_special = FALSE;
+
+ if (path[0] == '\0')
+ path = ".";
+
+ /* Not using svn_io_stat() here because we want to check the
+ apr_err return explicitly. */
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ flags = resolve_symlinks ? APR_FINFO_MIN : (APR_FINFO_MIN | APR_FINFO_LINK);
+ apr_err = apr_stat(&finfo, path_apr, flags, pool);
+
+ if (APR_STATUS_IS_ENOENT(apr_err))
+ *kind = svn_node_none;
+ else if (SVN__APR_STATUS_IS_ENOTDIR(apr_err))
+ *kind = svn_node_none;
+ else if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't check path '%s'"),
+ svn_dirent_local_style(path, pool));
+ else
+ map_apr_finfo_to_node_kind(kind, &is_special, &finfo);
+
+ *is_special_p = is_special;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Wrapper for apr_file_open(), taking an APR-encoded filename. */
+static apr_status_t
+file_open(apr_file_t **f,
+ const char *fname_apr,
+ apr_int32_t flag,
+ apr_fileperms_t perm,
+ svn_boolean_t retry_on_failure,
+ apr_pool_t *pool)
+{
+ apr_status_t status = apr_file_open(f, fname_apr, flag, perm, pool);
+
+ if (retry_on_failure)
+ {
+ WIN32_RETRY_LOOP(status, apr_file_open(f, fname_apr, flag, perm, pool));
+ }
+ return status;
+}
+
+
+svn_error_t *
+svn_io_check_resolved_path(const char *path,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ svn_boolean_t ignored;
+ return io_check_path(path, TRUE, &ignored, kind, pool);
+}
+
+svn_error_t *
+svn_io_check_path(const char *path,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ svn_boolean_t ignored;
+ return io_check_path(path, FALSE, &ignored, kind, pool);
+}
+
+svn_error_t *
+svn_io_check_special_path(const char *path,
+ svn_node_kind_t *kind,
+ svn_boolean_t *is_special,
+ apr_pool_t *pool)
+{
+ return io_check_path(path, FALSE, is_special, kind, pool);
+}
+
+struct temp_file_cleanup_s
+{
+ apr_pool_t *pool;
+ /* The (APR-encoded) full path of the file to be removed, or NULL if
+ * nothing to do. */
+ const char *fname_apr;
+};
+
+
+static apr_status_t
+temp_file_plain_cleanup_handler(void *baton)
+{
+ struct temp_file_cleanup_s *b = baton;
+ apr_status_t apr_err = APR_SUCCESS;
+
+ if (b->fname_apr)
+ {
+ apr_err = apr_file_remove(b->fname_apr, b->pool);
+ WIN32_RETRY_LOOP(apr_err, apr_file_remove(b->fname_apr, b->pool));
+ }
+
+ return apr_err;
+}
+
+
+static apr_status_t
+temp_file_child_cleanup_handler(void *baton)
+{
+ struct temp_file_cleanup_s *b = baton;
+
+ apr_pool_cleanup_kill(b->pool, b,
+ temp_file_plain_cleanup_handler);
+
+ return APR_SUCCESS;
+}
+
+
+svn_error_t *
+svn_io_open_uniquely_named(apr_file_t **file,
+ const char **unique_path,
+ const char *dirpath,
+ const char *filename,
+ const char *suffix,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *path;
+ unsigned int i;
+ struct temp_file_cleanup_s *baton = NULL;
+
+ /* At the beginning, we don't know whether unique_path will need
+ UTF8 conversion */
+ svn_boolean_t needs_utf8_conversion = TRUE;
+
+ SVN_ERR_ASSERT(file || unique_path);
+
+ if (dirpath == NULL)
+ SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
+ if (filename == NULL)
+ filename = "tempfile";
+ if (suffix == NULL)
+ suffix = ".tmp";
+
+ path = svn_dirent_join(dirpath, filename, scratch_pool);
+
+ if (delete_when == svn_io_file_del_on_pool_cleanup)
+ {
+ baton = apr_palloc(result_pool, sizeof(*baton));
+
+ baton->pool = result_pool;
+ baton->fname_apr = NULL;
+
+ /* Because cleanups are run LIFO, we need to make sure to register
+ our cleanup before the apr_file_close cleanup:
+
+ On Windows, you can't remove an open file.
+ */
+ apr_pool_cleanup_register(result_pool, baton,
+ temp_file_plain_cleanup_handler,
+ temp_file_child_cleanup_handler);
+ }
+
+ for (i = 1; i <= 99999; i++)
+ {
+ const char *unique_name;
+ const char *unique_name_apr;
+ apr_file_t *try_file;
+ apr_status_t apr_err;
+ apr_int32_t flag = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL
+ | APR_BUFFERED | APR_BINARY);
+
+ if (delete_when == svn_io_file_del_on_close)
+ flag |= APR_DELONCLOSE;
+
+ /* Special case the first attempt -- if we can avoid having a
+ generated numeric portion at all, that's best. So first we
+ try with just the suffix; then future tries add a number
+ before the suffix. (A do-while loop could avoid the repeated
+ conditional, but it's not worth the clarity loss.)
+
+ If the first attempt fails, the first number will be "2".
+ This is good, since "1" would misleadingly imply that
+ the second attempt was actually the first... and if someone's
+ got conflicts on their conflicts, we probably don't want to
+ add to their confusion :-). */
+ if (i == 1)
+ unique_name = apr_psprintf(scratch_pool, "%s%s", path, suffix);
+ else
+ unique_name = apr_psprintf(scratch_pool, "%s.%u%s", path, i, suffix);
+
+ /* Hmmm. Ideally, we would append to a native-encoding buf
+ before starting iteration, then convert back to UTF-8 for
+ return. But I suppose that would make the appending code
+ sensitive to i18n in a way it shouldn't be... Oh well. */
+ if (needs_utf8_conversion)
+ {
+ SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name,
+ scratch_pool));
+ if (i == 1)
+ {
+ /* The variable parts of unique_name will not require UTF8
+ conversion. Therefore, if UTF8 conversion had no effect
+ on it in the first iteration, it won't require conversion
+ in any future iteration. */
+ needs_utf8_conversion = strcmp(unique_name_apr, unique_name);
+ }
+ }
+ else
+ unique_name_apr = unique_name;
+
+ apr_err = file_open(&try_file, unique_name_apr, flag,
+ APR_OS_DEFAULT, FALSE, result_pool);
+
+ if (APR_STATUS_IS_EEXIST(apr_err))
+ continue;
+ else if (apr_err)
+ {
+ /* On Win32, CreateFile fails with an "Access Denied" error
+ code, rather than "File Already Exists", if the colliding
+ name belongs to a directory. */
+ if (APR_STATUS_IS_EACCES(apr_err))
+ {
+ apr_finfo_t finfo;
+ apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
+ APR_FINFO_TYPE, scratch_pool);
+
+ if (!apr_err_2 && finfo.filetype == APR_DIR)
+ continue;
+
+#ifdef WIN32
+ apr_err_2 = APR_TO_OS_ERROR(apr_err);
+
+ if (apr_err_2 == ERROR_ACCESS_DENIED ||
+ apr_err_2 == ERROR_SHARING_VIOLATION)
+ {
+ /* The file is in use by another process or is hidden;
+ create a new name, but don't do this 99999 times in
+ case the folder is not writable */
+ i += 797;
+ continue;
+ }
+#endif
+
+ /* Else fall through and return the original error. */
+ }
+
+ if (file)
+ *file = NULL;
+ if (unique_path)
+ *unique_path = NULL;
+ return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
+ svn_dirent_local_style(unique_name,
+ scratch_pool));
+ }
+ else
+ {
+ if (delete_when == svn_io_file_del_on_pool_cleanup)
+ baton->fname_apr = apr_pstrdup(result_pool, unique_name_apr);
+
+ if (file)
+ *file = try_file;
+ else
+ apr_file_close(try_file);
+ if (unique_path)
+ *unique_path = apr_pstrdup(result_pool, unique_name);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (file)
+ *file = NULL;
+ if (unique_path)
+ *unique_path = NULL;
+ return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
+ NULL,
+ _("Unable to make name for '%s'"),
+ svn_dirent_local_style(path, scratch_pool));
+}
+
+svn_error_t *
+svn_io_create_unique_link(const char **unique_name_p,
+ const char *path,
+ const char *dest,
+ const char *suffix,
+ apr_pool_t *pool)
+{
+#ifdef HAVE_SYMLINK
+ unsigned int i;
+ const char *unique_name;
+ const char *unique_name_apr;
+ const char *dest_apr;
+ int rv;
+
+ SVN_ERR(cstring_from_utf8(&dest_apr, dest, pool));
+ for (i = 1; i <= 99999; i++)
+ {
+ apr_status_t apr_err;
+
+ /* Special case the first attempt -- if we can avoid having a
+ generated numeric portion at all, that's best. So first we
+ try with just the suffix; then future tries add a number
+ before the suffix. (A do-while loop could avoid the repeated
+ conditional, but it's not worth the clarity loss.)
+
+ If the first attempt fails, the first number will be "2".
+ This is good, since "1" would misleadingly imply that
+ the second attempt was actually the first... and if someone's
+ got conflicts on their conflicts, we probably don't want to
+ add to their confusion :-). */
+ if (i == 1)
+ unique_name = apr_psprintf(pool, "%s%s", path, suffix);
+ else
+ unique_name = apr_psprintf(pool, "%s.%u%s", path, i, suffix);
+
+ /* Hmmm. Ideally, we would append to a native-encoding buf
+ before starting iteration, then convert back to UTF-8 for
+ return. But I suppose that would make the appending code
+ sensitive to i18n in a way it shouldn't be... Oh well. */
+ SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, pool));
+ do {
+ rv = symlink(dest_apr, unique_name_apr);
+ } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
+
+ apr_err = apr_get_os_error();
+
+ if (rv == -1 && APR_STATUS_IS_EEXIST(apr_err))
+ continue;
+ else if (rv == -1 && apr_err)
+ {
+ /* On Win32, CreateFile fails with an "Access Denied" error
+ code, rather than "File Already Exists", if the colliding
+ name belongs to a directory. */
+ if (APR_STATUS_IS_EACCES(apr_err))
+ {
+ apr_finfo_t finfo;
+ apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
+ APR_FINFO_TYPE, pool);
+
+ if (!apr_err_2
+ && (finfo.filetype == APR_DIR))
+ continue;
+
+ /* Else ignore apr_err_2; better to fall through and
+ return the original error. */
+ }
+
+ *unique_name_p = NULL;
+ return svn_error_wrap_apr(apr_err,
+ _("Can't create symbolic link '%s'"),
+ svn_dirent_local_style(unique_name, pool));
+ }
+ else
+ {
+ *unique_name_p = unique_name;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ *unique_name_p = NULL;
+ return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
+ NULL,
+ _("Unable to make name for '%s'"),
+ svn_dirent_local_style(path, pool));
+#else
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Symbolic links are not supported on this "
+ "platform"));
+#endif
+}
+
+svn_error_t *
+svn_io_read_link(svn_string_t **dest,
+ const char *path,
+ apr_pool_t *pool)
+{
+#ifdef HAVE_READLINK
+ svn_string_t dest_apr;
+ const char *path_apr;
+ char buf[1025];
+ ssize_t rv;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+ do {
+ rv = readlink(path_apr, buf, sizeof(buf) - 1);
+ } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
+
+ if (rv == -1)
+ return svn_error_wrap_apr(apr_get_os_error(),
+ _("Can't read contents of link"));
+
+ buf[rv] = '\0';
+ dest_apr.data = buf;
+ dest_apr.len = rv;
+
+ /* ### Cast needed, one of these interfaces is wrong */
+ return svn_utf_string_to_utf8((const svn_string_t **)dest, &dest_apr, pool);
+#else
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Symbolic links are not supported on this "
+ "platform"));
+#endif
+}
+
+
+svn_error_t *
+svn_io_copy_link(const char *src,
+ const char *dst,
+ apr_pool_t *pool)
+
+{
+#ifdef HAVE_READLINK
+ svn_string_t *link_dest;
+ const char *dst_tmp;
+
+ /* Notice what the link is pointing at... */
+ SVN_ERR(svn_io_read_link(&link_dest, src, pool));
+
+ /* Make a tmp-link pointing at the same thing. */
+ SVN_ERR(svn_io_create_unique_link(&dst_tmp, dst, link_dest->data,
+ ".tmp", pool));
+
+ /* Move the tmp-link to link. */
+ return svn_io_file_rename(dst_tmp, dst, pool);
+
+#else
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Symbolic links are not supported on this "
+ "platform"));
+#endif
+}
+
+/* Temporary directory name cache for svn_io_temp_dir() */
+static volatile svn_atomic_t temp_dir_init_state = 0;
+static const char *temp_dir;
+
+/* Helper function to initialize temp dir. Passed to svn_atomic__init_once */
+static svn_error_t *
+init_temp_dir(void *baton, apr_pool_t *scratch_pool)
+{
+ /* Global pool for the temp path */
+ apr_pool_t *global_pool = svn_pool_create(NULL);
+ const char *dir;
+
+ apr_status_t apr_err = apr_temp_dir_get(&dir, scratch_pool);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't find a temporary directory"));
+
+ SVN_ERR(cstring_to_utf8(&dir, dir, scratch_pool));
+
+ dir = svn_dirent_internal_style(dir, scratch_pool);
+
+ SVN_ERR(svn_dirent_get_absolute(&temp_dir, dir, global_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_temp_dir(const char **dir,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_atomic__init_once(&temp_dir_init_state,
+ init_temp_dir, NULL, pool));
+
+ *dir = apr_pstrdup(pool, temp_dir);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+/*** Creating, copying and appending files. ***/
+
+/* Transfer the contents of FROM_FILE to TO_FILE, using POOL for temporary
+ * allocations.
+ *
+ * NOTE: We don't use apr_copy_file() for this, since it takes filenames
+ * as parameters. Since we want to copy to a temporary file
+ * and rename for atomicity (see below), this would require an extra
+ * close/open pair, which can be expensive, especially on
+ * remote file systems.
+ */
+static apr_status_t
+copy_contents(apr_file_t *from_file,
+ apr_file_t *to_file,
+ apr_pool_t *pool)
+{
+ /* Copy bytes till the cows come home. */
+ while (1)
+ {
+ char buf[SVN__STREAM_CHUNK_SIZE];
+ apr_size_t bytes_this_time = sizeof(buf);
+ apr_status_t read_err;
+ apr_status_t write_err;
+
+ /* Read 'em. */
+ read_err = apr_file_read(from_file, buf, &bytes_this_time);
+ if (read_err && !APR_STATUS_IS_EOF(read_err))
+ {
+ return read_err;
+ }
+
+ /* Write 'em. */
+ write_err = apr_file_write_full(to_file, buf, bytes_this_time, NULL);
+ if (write_err)
+ {
+ return write_err;
+ }
+
+ if (read_err && APR_STATUS_IS_EOF(read_err))
+ {
+ /* Return the results of this close: an error, or success. */
+ return APR_SUCCESS;
+ }
+ }
+ /* NOTREACHED */
+}
+
+
+svn_error_t *
+svn_io_copy_file(const char *src,
+ const char *dst,
+ svn_boolean_t copy_perms,
+ apr_pool_t *pool)
+{
+ apr_file_t *from_file, *to_file;
+ apr_status_t apr_err;
+ const char *dst_tmp;
+ svn_error_t *err;
+
+ /* ### NOTE: sometimes src == dst. In this case, because we copy to a
+ ### temporary file, and then rename over the top of the destination,
+ ### the net result is resetting the permissions on src/dst.
+ ###
+ ### Note: specifically, this can happen during a switch when the desired
+ ### permissions for a file change from one branch to another. See
+ ### switch_tests 17.
+ ###
+ ### ... yes, we should avoid copying to the same file, and we should
+ ### make the "reset perms" explicit. The switch *happens* to work
+ ### because of this copy-to-temp-then-rename implementation. If it
+ ### weren't for that, the switch would break.
+ */
+#ifdef CHECK_FOR_SAME_FILE
+ if (strcmp(src, dst) == 0)
+ return SVN_NO_ERROR;
+#endif
+
+ SVN_ERR(svn_io_file_open(&from_file, src, APR_READ,
+ APR_OS_DEFAULT, pool));
+
+ /* For atomicity, we copy to a tmp file and then rename the tmp
+ file over the real destination. */
+
+ SVN_ERR(svn_io_open_unique_file3(&to_file, &dst_tmp,
+ svn_dirent_dirname(dst, pool),
+ svn_io_file_del_none, pool, pool));
+
+ apr_err = copy_contents(from_file, to_file, pool);
+
+ if (apr_err)
+ {
+ err = svn_error_wrap_apr(apr_err, _("Can't copy '%s' to '%s'"),
+ svn_dirent_local_style(src, pool),
+ svn_dirent_local_style(dst_tmp, pool));
+ }
+ else
+ err = NULL;
+
+ err = svn_error_compose_create(err,
+ svn_io_file_close(from_file, pool));
+
+ err = svn_error_compose_create(err,
+ svn_io_file_close(to_file, pool));
+
+ if (err)
+ {
+ return svn_error_compose_create(
+ err,
+ svn_io_remove_file2(dst_tmp, TRUE, pool));
+ }
+
+ /* If copying perms, set the perms on dst_tmp now, so they will be
+ atomically inherited in the upcoming rename. But note that we
+ had to wait until now to set perms, because if they say
+ read-only, then we'd have failed filling dst_tmp's contents. */
+ if (copy_perms)
+ SVN_ERR(svn_io_copy_perms(src, dst_tmp, pool));
+
+ return svn_error_trace(svn_io_file_rename(dst_tmp, dst, pool));
+}
+
+#if !defined(WIN32) && !defined(__OS2__)
+/* Wrapper for apr_file_perms_set(), taking a UTF8-encoded filename. */
+static svn_error_t *
+file_perms_set(const char *fname, apr_fileperms_t perms,
+ apr_pool_t *pool)
+{
+ const char *fname_apr;
+ apr_status_t status;
+
+ SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
+
+ status = apr_file_perms_set(fname_apr, perms);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"),
+ fname);
+ else
+ return SVN_NO_ERROR;
+}
+
+/* Set permissions PERMS on the FILE. This is a cheaper variant of the
+ * file_perms_set wrapper() function because no locale-dependent string
+ * conversion is required. POOL will be used for allocations.
+ */
+static svn_error_t *
+file_perms_set2(apr_file_t* file, apr_fileperms_t perms, apr_pool_t *pool)
+{
+ const char *fname_apr;
+ apr_status_t status;
+
+ status = apr_file_name_get(&fname_apr, file);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't get file name"));
+
+ status = apr_file_perms_set(fname_apr, perms);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't set permissions on '%s'"),
+ try_utf8_from_internal_style(fname_apr, pool));
+ else
+ return SVN_NO_ERROR;
+}
+
+#endif /* !WIN32 && !__OS2__ */
+
+svn_error_t *
+svn_io_copy_perms(const char *src,
+ const char *dst,
+ apr_pool_t *pool)
+{
+ /* ### On Windows or OS/2, apr_file_perms_set always returns APR_ENOTIMPL,
+ and the path passed to apr_file_perms_set must be encoded
+ in the platform-specific path encoding; not necessary UTF-8.
+ We need a platform-specific implementation to get the
+ permissions right. */
+
+#if !defined(WIN32) && !defined(__OS2__)
+ {
+ apr_finfo_t finfo;
+ svn_node_kind_t kind;
+ svn_boolean_t is_special;
+ svn_error_t *err;
+
+ /* If DST is a symlink, don't bother copying permissions. */
+ SVN_ERR(svn_io_check_special_path(dst, &kind, &is_special, pool));
+ if (is_special)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_PROT, pool));
+ err = file_perms_set(dst, finfo.protection, pool);
+ if (err)
+ {
+ /* We shouldn't be able to get APR_INCOMPLETE or APR_ENOTIMPL
+ here under normal circumstances, because the perms themselves
+ came from a call to apr_file_info_get(), and we already know
+ this is the non-Win32 case. But if it does happen, it's not
+ an error. */
+ if (APR_STATUS_IS_INCOMPLETE(err->apr_err) ||
+ APR_STATUS_IS_ENOTIMPL(err->apr_err))
+ svn_error_clear(err);
+ else
+ {
+ const char *message;
+ message = apr_psprintf(pool, _("Can't set permissions on '%s'"),
+ svn_dirent_local_style(dst, pool));
+ return svn_error_quick_wrap(err, message);
+ }
+ }
+ }
+#endif /* !WIN32 && !__OS2__ */
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_append_file(const char *src, const char *dst, apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ const char *src_apr, *dst_apr;
+
+ SVN_ERR(cstring_from_utf8(&src_apr, src, pool));
+ SVN_ERR(cstring_from_utf8(&dst_apr, dst, pool));
+
+ apr_err = apr_file_append(src_apr, dst_apr, APR_OS_DEFAULT, pool);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't append '%s' to '%s'"),
+ svn_dirent_local_style(src, pool),
+ svn_dirent_local_style(dst, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *svn_io_copy_dir_recursively(const char *src,
+ const char *dst_parent,
+ const char *dst_basename,
+ svn_boolean_t copy_perms,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ apr_status_t status;
+ const char *dst_path;
+ apr_dir_t *this_dir;
+ apr_finfo_t this_entry;
+ apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
+
+ /* Make a subpool for recursion */
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* The 'dst_path' is simply dst_parent/dst_basename */
+ dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
+
+ /* Sanity checks: SRC and DST_PARENT are directories, and
+ DST_BASENAME doesn't already exist in DST_PARENT. */
+ SVN_ERR(svn_io_check_path(src, &kind, subpool));
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Source '%s' is not a directory"),
+ svn_dirent_local_style(src, pool));
+
+ SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Destination '%s' is not a directory"),
+ svn_dirent_local_style(dst_parent, pool));
+
+ SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
+ if (kind != svn_node_none)
+ return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
+ _("Destination '%s' already exists"),
+ svn_dirent_local_style(dst_path, pool));
+
+ /* Create the new directory. */
+ /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
+ SVN_ERR(svn_io_dir_make(dst_path, APR_OS_DEFAULT, pool));
+
+ /* Loop over the dirents in SRC. ('.' and '..' are auto-excluded) */
+ SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
+
+ for (status = apr_dir_read(&this_entry, flags, this_dir);
+ status == APR_SUCCESS;
+ status = apr_dir_read(&this_entry, flags, this_dir))
+ {
+ if ((this_entry.name[0] == '.')
+ && ((this_entry.name[1] == '\0')
+ || ((this_entry.name[1] == '.')
+ && (this_entry.name[2] == '\0'))))
+ {
+ continue;
+ }
+ else
+ {
+ const char *src_target, *entryname_utf8;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
+ src, subpool));
+ src_target = svn_dirent_join(src, entryname_utf8, subpool);
+
+ if (this_entry.filetype == APR_REG) /* regular file */
+ {
+ const char *dst_target = svn_dirent_join(dst_path,
+ entryname_utf8,
+ subpool);
+ SVN_ERR(svn_io_copy_file(src_target, dst_target,
+ copy_perms, subpool));
+ }
+ else if (this_entry.filetype == APR_LNK) /* symlink */
+ {
+ const char *dst_target = svn_dirent_join(dst_path,
+ entryname_utf8,
+ subpool);
+ SVN_ERR(svn_io_copy_link(src_target, dst_target,
+ subpool));
+ }
+ else if (this_entry.filetype == APR_DIR) /* recurse */
+ {
+ /* Prevent infinite recursion by filtering off our
+ newly created destination path. */
+ if (strcmp(src, dst_parent) == 0
+ && strcmp(entryname_utf8, dst_basename) == 0)
+ continue;
+
+ SVN_ERR(svn_io_copy_dir_recursively
+ (src_target,
+ dst_path,
+ entryname_utf8,
+ copy_perms,
+ cancel_func,
+ cancel_baton,
+ subpool));
+ }
+ /* ### support other APR node types someday?? */
+
+ }
+ }
+
+ if (! (APR_STATUS_IS_ENOENT(status)))
+ return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
+ svn_dirent_local_style(src, pool));
+
+ status = apr_dir_close(this_dir);
+ if (status)
+ return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
+ svn_dirent_local_style(src, pool));
+
+ /* Free any memory used by recursion */
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_make_dir_recursively(const char *path, apr_pool_t *pool)
+{
+ const char *path_apr;
+ apr_status_t apr_err;
+
+ if (svn_path_is_empty(path))
+ /* Empty path (current dir) is assumed to always exist,
+ so we do nothing, per docs. */
+ return SVN_NO_ERROR;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ apr_err = apr_dir_make_recursive(path_apr, APR_OS_DEFAULT, pool);
+ WIN32_RETRY_LOOP(apr_err, apr_dir_make_recursive(path_apr,
+ APR_OS_DEFAULT, pool));
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't make directory '%s'"),
+ svn_dirent_local_style(path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_io_file_create(const char *file,
+ const char *contents,
+ apr_pool_t *pool)
+{
+ apr_file_t *f;
+ apr_size_t written;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_io_file_open(&f, file,
+ (APR_WRITE | APR_CREATE | APR_EXCL),
+ APR_OS_DEFAULT,
+ pool));
+ if (contents && *contents)
+ err = svn_io_file_write_full(f, contents, strlen(contents),
+ &written, pool);
+
+
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_io_file_close(f, pool)));
+}
+
+svn_error_t *svn_io_dir_file_copy(const char *src_path,
+ const char *dest_path,
+ const char *file,
+ apr_pool_t *pool)
+{
+ const char *file_dest_path = svn_dirent_join(dest_path, file, pool);
+ const char *file_src_path = svn_dirent_join(src_path, file, pool);
+
+ return svn_io_copy_file(file_src_path, file_dest_path, TRUE, pool);
+}
+
+
+/*** Modtime checking. ***/
+
+svn_error_t *
+svn_io_file_affected_time(apr_time_t *apr_time,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_finfo_t finfo;
+
+ SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK, pool));
+
+ *apr_time = finfo.mtime;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_set_file_affected_time(apr_time_t apr_time,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *native_path;
+
+ SVN_ERR(cstring_from_utf8(&native_path, path, pool));
+ status = apr_file_mtime_set(native_path, apr_time, pool);
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't set access time of '%s'"),
+ svn_dirent_local_style(path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+void
+svn_io_sleep_for_timestamps(const char *path, apr_pool_t *pool)
+{
+ apr_time_t now, then;
+ svn_error_t *err;
+ char *sleep_env_var;
+
+ sleep_env_var = getenv(SVN_SLEEP_ENV_VAR);
+
+ if (sleep_env_var && apr_strnatcasecmp(sleep_env_var, "yes") == 0)
+ return; /* Allow skipping for testing */
+
+ now = apr_time_now();
+
+ /* Calculate 0.02 seconds after the next second wallclock tick. */
+ then = apr_time_make(apr_time_sec(now) + 1, APR_USEC_PER_SEC / 50);
+
+ /* Worst case is waiting one second, so we can use that time to determine
+ if we can sleep shorter than that */
+ if (path)
+ {
+ apr_finfo_t finfo;
+
+ err = svn_io_stat(&finfo, path, APR_FINFO_MTIME | APR_FINFO_LINK, pool);
+
+ if (err)
+ {
+ svn_error_clear(err); /* Fall back on original behavior */
+ }
+ else if (finfo.mtime % APR_USEC_PER_SEC)
+ {
+ /* Very simplistic but safe approach:
+ If the filesystem has < sec mtime we can be reasonably sure
+ that the filesystem has <= millisecond precision.
+
+ ## Perhaps find a better algorithm here. This will fail once
+ in every 1000 cases on a millisecond precision filesystem.
+
+ But better to fail once in every thousand cases than every
+ time, like we did before.
+ (All tested filesystems I know have at least microsecond precision.)
+
+ Note for further research on algorithm:
+ FAT32 has < 1 sec precision on ctime, but 2 sec on mtime */
+
+ /* Sleep for at least 1 millisecond.
+ (t < 1000 will be round to 0 in apr) */
+ apr_sleep(1000);
+
+ return;
+ }
+
+ now = apr_time_now(); /* Extract the time used for the path stat */
+
+ if (now >= then)
+ return; /* Passing negative values may suspend indefinitely (Windows) */
+ }
+
+ apr_sleep(then - now);
+}
+
+
+svn_error_t *
+svn_io_filesizes_different_p(svn_boolean_t *different_p,
+ const char *file1,
+ const char *file2,
+ apr_pool_t *pool)
+{
+ apr_finfo_t finfo1;
+ apr_finfo_t finfo2;
+ apr_status_t status;
+ const char *file1_apr, *file2_apr;
+
+ /* Not using svn_io_stat() because don't want to generate
+ svn_error_t objects for non-error conditions. */
+
+ SVN_ERR(cstring_from_utf8(&file1_apr, file1, pool));
+ SVN_ERR(cstring_from_utf8(&file2_apr, file2, pool));
+
+ /* Stat both files */
+ status = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, pool);
+ if (status)
+ {
+ /* If we got an error stat'ing a file, it could be because the
+ file was removed... or who knows. Whatever the case, we
+ don't know if the filesizes are definitely different, so
+ assume that they're not. */
+ *different_p = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ status = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, pool);
+ if (status)
+ {
+ /* See previous comment. */
+ *different_p = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Examine file sizes */
+ if (finfo1.size == finfo2.size)
+ *different_p = FALSE;
+ else
+ *different_p = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_filesizes_three_different_p(svn_boolean_t *different_p12,
+ svn_boolean_t *different_p23,
+ svn_boolean_t *different_p13,
+ const char *file1,
+ const char *file2,
+ const char *file3,
+ apr_pool_t *scratch_pool)
+{
+ apr_finfo_t finfo1, finfo2, finfo3;
+ apr_status_t status1, status2, status3;
+ const char *file1_apr, *file2_apr, *file3_apr;
+
+ /* Not using svn_io_stat() because don't want to generate
+ svn_error_t objects for non-error conditions. */
+
+ SVN_ERR(cstring_from_utf8(&file1_apr, file1, scratch_pool));
+ SVN_ERR(cstring_from_utf8(&file2_apr, file2, scratch_pool));
+ SVN_ERR(cstring_from_utf8(&file3_apr, file3, scratch_pool));
+
+ /* Stat all three files */
+ status1 = apr_stat(&finfo1, file1_apr, APR_FINFO_MIN, scratch_pool);
+ status2 = apr_stat(&finfo2, file2_apr, APR_FINFO_MIN, scratch_pool);
+ status3 = apr_stat(&finfo3, file3_apr, APR_FINFO_MIN, scratch_pool);
+
+ /* If we got an error stat'ing a file, it could be because the
+ file was removed... or who knows. Whatever the case, we
+ don't know if the filesizes are definitely different, so
+ assume that they're not. */
+ *different_p12 = !status1 && !status2 && finfo1.size != finfo2.size;
+ *different_p23 = !status2 && !status3 && finfo2.size != finfo3.size;
+ *different_p13 = !status1 && !status3 && finfo1.size != finfo3.size;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_file_checksum2(svn_checksum_t **checksum,
+ const char *file,
+ svn_checksum_kind_t kind,
+ apr_pool_t *pool)
+{
+ svn_stream_t *file_stream;
+ svn_stream_t *checksum_stream;
+ apr_file_t* f;
+
+ SVN_ERR(svn_io_file_open(&f, file, APR_READ, APR_OS_DEFAULT, pool));
+ file_stream = svn_stream_from_aprfile2(f, FALSE, pool);
+ checksum_stream = svn_stream_checksummed2(file_stream, checksum, NULL, kind,
+ TRUE, pool);
+
+ /* Because the checksummed stream will force the reading (and
+ checksumming) of all the file's bytes, we can just close the stream
+ and let its magic work. */
+ return svn_stream_close(checksum_stream);
+}
+
+
+svn_error_t *
+svn_io_file_checksum(unsigned char digest[],
+ const char *file,
+ apr_pool_t *pool)
+{
+ svn_checksum_t *checksum;
+
+ SVN_ERR(svn_io_file_checksum2(&checksum, file, svn_checksum_md5, pool));
+ memcpy(digest, checksum->digest, APR_MD5_DIGESTSIZE);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Permissions and modes. ***/
+
+#if !defined(WIN32) && !defined(__OS2__)
+/* Given the file specified by PATH, attempt to create an
+ identical version of it owned by the current user. This is done by
+ moving it to a temporary location, copying the file back to its old
+ path, then deleting the temporarily moved version. All temporary
+ allocations are done in POOL. */
+static svn_error_t *
+reown_file(const char *path,
+ apr_pool_t *pool)
+{
+ const char *unique_name;
+
+ SVN_ERR(svn_io_open_unique_file3(NULL, &unique_name,
+ svn_dirent_dirname(path, pool),
+ svn_io_file_del_none, pool, pool));
+ SVN_ERR(svn_io_file_rename(path, unique_name, pool));
+ SVN_ERR(svn_io_copy_file(unique_name, path, TRUE, pool));
+ return svn_error_trace(svn_io_remove_file2(unique_name, FALSE, pool));
+}
+
+/* Determine what the PERMS for a new file should be by looking at the
+ permissions of a temporary file that we create.
+ Unfortunately, umask() as defined in POSIX provides no thread-safe way
+ to get at the current value of the umask, so what we're doing here is
+ the only way we have to determine which combination of write bits
+ (User/Group/World) should be set by default.
+ Make temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+get_default_file_perms(apr_fileperms_t *perms, apr_pool_t *scratch_pool)
+{
+ /* the default permissions as read from the temp folder */
+ static apr_fileperms_t default_perms = 0;
+
+ /* Technically, this "racy": Multiple threads may use enter here and
+ try to figure out the default permission concurrently. That's fine
+ since they will end up with the same results. Even more technical,
+ apr_fileperms_t is an atomic type on 32+ bit machines.
+ */
+ if (default_perms == 0)
+ {
+ apr_finfo_t finfo;
+ apr_file_t *fd;
+ const char *fname_base, *fname;
+ apr_uint32_t randomish;
+ svn_error_t *err;
+
+ /* Get the perms for a newly created file to find out what bits
+ should be set.
+
+ Explictly delete the file because we want this file to be as
+ short-lived as possible since its presence means other
+ processes may have to try multiple names.
+
+ Using svn_io_open_uniquely_named() here because other tempfile
+ creation functions tweak the permission bits of files they create.
+ */
+ randomish = ((apr_uint32_t)(apr_uintptr_t)scratch_pool
+ + (apr_uint32_t)apr_time_now());
+ fname_base = apr_psprintf(scratch_pool, "svn-%08x", randomish);
+
+ SVN_ERR(svn_io_open_uniquely_named(&fd, &fname, NULL, fname_base,
+ NULL, svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ err = svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool);
+ err = svn_error_compose_create(err, svn_io_file_close(fd, scratch_pool));
+ err = svn_error_compose_create(err, svn_io_remove_file2(fname, TRUE,
+ scratch_pool));
+ SVN_ERR(err);
+ *perms = finfo.protection;
+ default_perms = finfo.protection;
+ }
+ else
+ *perms = default_perms;
+
+ return SVN_NO_ERROR;
+}
+
+/* OR together permission bits of the file FD and the default permissions
+ of a file as determined by get_default_file_perms(). Do temporary
+ allocations in SCRATCH_POOL. */
+static svn_error_t *
+merge_default_file_perms(apr_file_t *fd, apr_fileperms_t *perms,
+ apr_pool_t *scratch_pool)
+{
+ apr_finfo_t finfo;
+ apr_fileperms_t default_perms;
+
+ SVN_ERR(get_default_file_perms(&default_perms, scratch_pool));
+ SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_PROT, fd, scratch_pool));
+
+ /* Glom the perms together. */
+ *perms = default_perms | finfo.protection;
+ return SVN_NO_ERROR;
+}
+
+/* This is a helper function for the svn_io_set_file_read* functions
+ that attempts to honor the users umask when dealing with
+ permission changes. It is a no-op when invoked on a symlink. */
+static svn_error_t *
+io_set_file_perms(const char *path,
+ svn_boolean_t change_readwrite,
+ svn_boolean_t enable_write,
+ svn_boolean_t change_executable,
+ svn_boolean_t executable,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *path_apr;
+ apr_finfo_t finfo;
+ apr_fileperms_t perms_to_set;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ /* Try to change only a minimal amount of the perms first
+ by getting the current perms and adding bits
+ only on where read perms are granted. If this fails
+ fall through to just setting file attributes. */
+ status = apr_stat(&finfo, path_apr, APR_FINFO_PROT | APR_FINFO_LINK, pool);
+ if (status)
+ {
+ if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
+ return SVN_NO_ERROR;
+ else if (status != APR_ENOTIMPL)
+ return svn_error_wrap_apr(status,
+ _("Can't change perms of file '%s'"),
+ svn_dirent_local_style(path, pool));
+ return SVN_NO_ERROR;
+ }
+
+ if (finfo.filetype == APR_LNK)
+ return SVN_NO_ERROR;
+
+ perms_to_set = finfo.protection;
+ if (change_readwrite)
+ {
+ if (enable_write) /* Make read-write. */
+ {
+ apr_file_t *fd;
+
+ /* Get the perms for the original file so we'll have any other bits
+ * that were already set (like the execute bits, for example). */
+ SVN_ERR(svn_io_file_open(&fd, path, APR_READ,
+ APR_OS_DEFAULT, pool));
+ SVN_ERR(merge_default_file_perms(fd, &perms_to_set, pool));
+ SVN_ERR(svn_io_file_close(fd, pool));
+ }
+ else
+ {
+ if (finfo.protection & APR_UREAD)
+ perms_to_set &= ~APR_UWRITE;
+ if (finfo.protection & APR_GREAD)
+ perms_to_set &= ~APR_GWRITE;
+ if (finfo.protection & APR_WREAD)
+ perms_to_set &= ~APR_WWRITE;
+ }
+ }
+
+ if (change_executable)
+ {
+ if (executable)
+ {
+ if (finfo.protection & APR_UREAD)
+ perms_to_set |= APR_UEXECUTE;
+ if (finfo.protection & APR_GREAD)
+ perms_to_set |= APR_GEXECUTE;
+ if (finfo.protection & APR_WREAD)
+ perms_to_set |= APR_WEXECUTE;
+ }
+ else
+ {
+ if (finfo.protection & APR_UREAD)
+ perms_to_set &= ~APR_UEXECUTE;
+ if (finfo.protection & APR_GREAD)
+ perms_to_set &= ~APR_GEXECUTE;
+ if (finfo.protection & APR_WREAD)
+ perms_to_set &= ~APR_WEXECUTE;
+ }
+ }
+
+ /* If we aren't changing anything then just return, this saves
+ some system calls and helps with shared working copies */
+ if (perms_to_set == finfo.protection)
+ return SVN_NO_ERROR;
+
+ status = apr_file_perms_set(path_apr, perms_to_set);
+ if (!status)
+ return SVN_NO_ERROR;
+
+ if (APR_STATUS_IS_EPERM(status))
+ {
+ /* We don't have permissions to change the
+ permissions! Try a move, copy, and delete
+ workaround to see if we can get the file owned by
+ us. If these succeed, try the permissions set
+ again.
+
+ Note that we only attempt this in the
+ stat-available path. This assumes that the
+ move-copy workaround will only be helpful on
+ platforms that implement apr_stat. */
+ SVN_ERR(reown_file(path, pool));
+ status = apr_file_perms_set(path_apr, perms_to_set);
+ }
+
+ if (!status)
+ return SVN_NO_ERROR;
+
+ if (ignore_enoent && APR_STATUS_IS_ENOENT(status))
+ return SVN_NO_ERROR;
+ else if (status == APR_ENOTIMPL)
+ {
+ /* At least try to set the attributes. */
+ apr_fileattrs_t attrs = 0;
+ apr_fileattrs_t attrs_values = 0;
+
+ if (change_readwrite)
+ {
+ attrs = APR_FILE_ATTR_READONLY;
+ if (!enable_write)
+ attrs_values = APR_FILE_ATTR_READONLY;
+ }
+ if (change_executable)
+ {
+ attrs = APR_FILE_ATTR_EXECUTABLE;
+ if (executable)
+ attrs_values = APR_FILE_ATTR_EXECUTABLE;
+ }
+ status = apr_file_attrs_set(path_apr, attrs, attrs_values, pool);
+ }
+
+ return svn_error_wrap_apr(status,
+ _("Can't change perms of file '%s'"),
+ svn_dirent_local_style(path, pool));
+}
+#endif /* !WIN32 && !__OS2__ */
+
+#ifdef WIN32
+#if APR_HAS_UNICODE_FS
+/* copy of the apr function utf8_to_unicode_path since apr doesn't export this one */
+static apr_status_t io_utf8_to_unicode_path(apr_wchar_t* retstr, apr_size_t retlen,
+ const char* srcstr)
+{
+ /* TODO: The computations could preconvert the string to determine
+ * the true size of the retstr, but that's a memory over speed
+ * tradeoff that isn't appropriate this early in development.
+ *
+ * Allocate the maximum string length based on leading 4
+ * characters of \\?\ (allowing nearly unlimited path lengths)
+ * plus the trailing null, then transform /'s into \\'s since
+ * the \\?\ form doesn't allow '/' path separators.
+ *
+ * Note that the \\?\ form only works for local drive paths, and
+ * \\?\UNC\ is needed UNC paths.
+ */
+ apr_size_t srcremains = strlen(srcstr) + 1;
+ apr_wchar_t *t = retstr;
+ apr_status_t rv;
+
+ /* This is correct, we don't twist the filename if it will
+ * definitely be shorter than 248 characters. It merits some
+ * performance testing to see if this has any effect, but there
+ * seem to be applications that get confused by the resulting
+ * Unicode \\?\ style file names, especially if they use argv[0]
+ * or call the Win32 API functions such as GetModuleName, etc.
+ * Not every application is prepared to handle such names.
+ *
+ * Note also this is shorter than MAX_PATH, as directory paths
+ * are actually limited to 248 characters.
+ *
+ * Note that a utf-8 name can never result in more wide chars
+ * than the original number of utf-8 narrow chars.
+ */
+ if (srcremains > 248) {
+ if (srcstr[1] == ':' && (srcstr[2] == '/' || srcstr[2] == '\\')) {
+ wcscpy (retstr, L"\\\\?\\");
+ retlen -= 4;
+ t += 4;
+ }
+ else if ((srcstr[0] == '/' || srcstr[0] == '\\')
+ && (srcstr[1] == '/' || srcstr[1] == '\\')
+ && (srcstr[2] != '?')) {
+ /* Skip the slashes */
+ srcstr += 2;
+ srcremains -= 2;
+ wcscpy (retstr, L"\\\\?\\UNC\\");
+ retlen -= 8;
+ t += 8;
+ }
+ }
+
+ if (rv = apr_conv_utf8_to_ucs2(srcstr, &srcremains, t, &retlen)) {
+ return (rv == APR_INCOMPLETE) ? APR_EINVAL : rv;
+ }
+ if (srcremains) {
+ return APR_ENAMETOOLONG;
+ }
+ for (; *t; ++t)
+ if (*t == L'/')
+ *t = L'\\';
+ return APR_SUCCESS;
+}
+#endif
+
+static apr_status_t io_win_file_attrs_set(const char *fname,
+ DWORD attributes,
+ DWORD attr_mask,
+ apr_pool_t *pool)
+{
+ /* this is an implementation of apr_file_attrs_set() but one
+ that uses the proper Windows attributes instead of the apr
+ attributes. This way, we can apply any Windows file and
+ folder attributes even if apr doesn't implement them */
+ DWORD flags;
+ apr_status_t rv;
+#if APR_HAS_UNICODE_FS
+ apr_wchar_t wfname[APR_PATH_MAX];
+#endif
+
+#if APR_HAS_UNICODE_FS
+ IF_WIN_OS_IS_UNICODE
+ {
+ if (rv = io_utf8_to_unicode_path(wfname,
+ sizeof(wfname) / sizeof(wfname[0]),
+ fname))
+ return rv;
+ flags = GetFileAttributesW(wfname);
+ }
+#endif
+#if APR_HAS_ANSI_FS
+ ELSE_WIN_OS_IS_ANSI
+ {
+ flags = GetFileAttributesA(fname);
+ }
+#endif
+
+ if (flags == 0xFFFFFFFF)
+ return apr_get_os_error();
+
+ flags &= ~attr_mask;
+ flags |= (attributes & attr_mask);
+
+#if APR_HAS_UNICODE_FS
+ IF_WIN_OS_IS_UNICODE
+ {
+ rv = SetFileAttributesW(wfname, flags);
+ }
+#endif
+#if APR_HAS_ANSI_FS
+ ELSE_WIN_OS_IS_ANSI
+ {
+ rv = SetFileAttributesA(fname, flags);
+ }
+#endif
+
+ if (rv == 0)
+ return apr_get_os_error();
+
+ return APR_SUCCESS;
+}
+
+#endif
+
+svn_error_t *
+svn_io_set_file_read_write_carefully(const char *path,
+ svn_boolean_t enable_write,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool)
+{
+ if (enable_write)
+ return svn_io_set_file_read_write(path, ignore_enoent, pool);
+ return svn_io_set_file_read_only(path, ignore_enoent, pool);
+}
+
+svn_error_t *
+svn_io_set_file_read_only(const char *path,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool)
+{
+ /* On Windows and OS/2, just set the file attributes -- on unix call
+ our internal function which attempts to honor the umask. */
+#if !defined(WIN32) && !defined(__OS2__)
+ return io_set_file_perms(path, TRUE, FALSE, FALSE, FALSE,
+ ignore_enoent, pool);
+#else
+ apr_status_t status;
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ status = apr_file_attrs_set(path_apr,
+ APR_FILE_ATTR_READONLY,
+ APR_FILE_ATTR_READONLY,
+ pool);
+
+ if (status && status != APR_ENOTIMPL)
+ if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
+ return svn_error_wrap_apr(status,
+ _("Can't set file '%s' read-only"),
+ svn_dirent_local_style(path, pool));
+
+ return SVN_NO_ERROR;
+#endif
+}
+
+
+svn_error_t *
+svn_io_set_file_read_write(const char *path,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool)
+{
+ /* On Windows and OS/2, just set the file attributes -- on unix call
+ our internal function which attempts to honor the umask. */
+#if !defined(WIN32) && !defined(__OS2__)
+ return io_set_file_perms(path, TRUE, TRUE, FALSE, FALSE,
+ ignore_enoent, pool);
+#else
+ apr_status_t status;
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ status = apr_file_attrs_set(path_apr,
+ 0,
+ APR_FILE_ATTR_READONLY,
+ pool);
+
+ if (status && status != APR_ENOTIMPL)
+ if (!ignore_enoent || !APR_STATUS_IS_ENOENT(status))
+ return svn_error_wrap_apr(status,
+ _("Can't set file '%s' read-write"),
+ svn_dirent_local_style(path, pool));
+
+ return SVN_NO_ERROR;
+#endif
+}
+
+svn_error_t *
+svn_io_set_file_executable(const char *path,
+ svn_boolean_t executable,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *pool)
+{
+ /* On Windows and OS/2, just exit -- on unix call our internal function
+ which attempts to honor the umask. */
+#if (!defined(WIN32) && !defined(__OS2__))
+ return io_set_file_perms(path, FALSE, FALSE, TRUE, executable,
+ ignore_enoent, pool);
+#else
+ return SVN_NO_ERROR;
+#endif
+}
+
+
+svn_error_t *
+svn_io__is_finfo_read_only(svn_boolean_t *read_only,
+ apr_finfo_t *file_info,
+ apr_pool_t *pool)
+{
+#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
+ apr_status_t apr_err;
+ apr_uid_t uid;
+ apr_gid_t gid;
+
+ *read_only = FALSE;
+
+ apr_err = apr_uid_current(&uid, &gid, pool);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
+
+ /* Check write bit for current user. */
+ if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS)
+ *read_only = !(file_info->protection & APR_UWRITE);
+
+ else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS)
+ *read_only = !(file_info->protection & APR_GWRITE);
+
+ else
+ *read_only = !(file_info->protection & APR_WWRITE);
+
+#else /* WIN32 || __OS2__ || !APR_HAS_USER */
+ *read_only = (file_info->protection & APR_FREADONLY);
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io__is_finfo_executable(svn_boolean_t *executable,
+ apr_finfo_t *file_info,
+ apr_pool_t *pool)
+{
+#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
+ apr_status_t apr_err;
+ apr_uid_t uid;
+ apr_gid_t gid;
+
+ *executable = FALSE;
+
+ apr_err = apr_uid_current(&uid, &gid, pool);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Error getting UID of process"));
+
+ /* Check executable bit for current user. */
+ if (apr_uid_compare(uid, file_info->user) == APR_SUCCESS)
+ *executable = (file_info->protection & APR_UEXECUTE);
+
+ else if (apr_gid_compare(gid, file_info->group) == APR_SUCCESS)
+ *executable = (file_info->protection & APR_GEXECUTE);
+
+ else
+ *executable = (file_info->protection & APR_WEXECUTE);
+
+#else /* WIN32 || __OS2__ || !APR_HAS_USER */
+ *executable = FALSE;
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_is_file_executable(svn_boolean_t *executable,
+ const char *path,
+ apr_pool_t *pool)
+{
+#if defined(APR_HAS_USER) && !defined(WIN32) &&!defined(__OS2__)
+ apr_finfo_t file_info;
+
+ SVN_ERR(svn_io_stat(&file_info, path, APR_FINFO_PROT | APR_FINFO_OWNER,
+ pool));
+ SVN_ERR(svn_io__is_finfo_executable(executable, &file_info, pool));
+
+#else /* WIN32 || __OS2__ || !APR_HAS_USER */
+ *executable = FALSE;
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** File locking. ***/
+#if !defined(WIN32) && !defined(__OS2__)
+/* Clear all outstanding locks on ARG, an open apr_file_t *. */
+static apr_status_t
+file_clear_locks(void *arg)
+{
+ apr_status_t apr_err;
+ apr_file_t *f = arg;
+
+ /* Remove locks. */
+ apr_err = apr_file_unlock(f);
+ if (apr_err)
+ return apr_err;
+
+ return 0;
+}
+#endif
+
+svn_error_t *
+svn_io_lock_open_file(apr_file_t *lockfile_handle,
+ svn_boolean_t exclusive,
+ svn_boolean_t nonblocking,
+ apr_pool_t *pool)
+{
+ int locktype = APR_FLOCK_SHARED;
+ apr_status_t apr_err;
+ const char *fname;
+
+ if (exclusive)
+ locktype = APR_FLOCK_EXCLUSIVE;
+ if (nonblocking)
+ locktype |= APR_FLOCK_NONBLOCK;
+
+ /* We need this only in case of an error but this is cheap to get -
+ * so we do it here for clarity. */
+ apr_err = apr_file_name_get(&fname, lockfile_handle);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't get file name"));
+
+ /* Get lock on the filehandle. */
+ apr_err = apr_file_lock(lockfile_handle, locktype);
+
+ /* In deployments with two or more multithreaded servers running on
+ the same system serving two or more fsfs repositories it is
+ possible for a deadlock to occur when getting a write lock on
+ db/txn-current-lock:
+
+ Process 1 Process 2
+ --------- ---------
+ thread 1: get lock in repos A
+ thread 1: get lock in repos B
+ thread 2: block getting lock in repos A
+ thread 2: try to get lock in B *** deadlock ***
+
+ Retry for a while for the deadlock to clear. */
+ FILE_LOCK_RETRY_LOOP(apr_err, apr_file_lock(lockfile_handle, locktype));
+
+ if (apr_err)
+ {
+ switch (locktype & APR_FLOCK_TYPEMASK)
+ {
+ case APR_FLOCK_SHARED:
+ return svn_error_wrap_apr(apr_err,
+ _("Can't get shared lock on file '%s'"),
+ try_utf8_from_internal_style(fname, pool));
+ case APR_FLOCK_EXCLUSIVE:
+ return svn_error_wrap_apr(apr_err,
+ _("Can't get exclusive lock on file '%s'"),
+ try_utf8_from_internal_style(fname, pool));
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+ }
+
+/* On Windows and OS/2 file locks are automatically released when
+ the file handle closes */
+#if !defined(WIN32) && !defined(__OS2__)
+ apr_pool_cleanup_register(pool, lockfile_handle,
+ file_clear_locks,
+ apr_pool_cleanup_null);
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_unlock_open_file(apr_file_t *lockfile_handle,
+ apr_pool_t *pool)
+{
+ const char *fname;
+ apr_status_t apr_err;
+
+ /* We need this only in case of an error but this is cheap to get -
+ * so we do it here for clarity. */
+ apr_err = apr_file_name_get(&fname, lockfile_handle);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't get file name"));
+
+ /* The actual unlock attempt. */
+ apr_err = apr_file_unlock(lockfile_handle);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't unlock file '%s'"),
+ try_utf8_from_internal_style(fname, pool));
+
+/* On Windows and OS/2 file locks are automatically released when
+ the file handle closes */
+#if !defined(WIN32) && !defined(__OS2__)
+ apr_pool_cleanup_kill(pool, lockfile_handle, file_clear_locks);
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_file_lock2(const char *lock_file,
+ svn_boolean_t exclusive,
+ svn_boolean_t nonblocking,
+ apr_pool_t *pool)
+{
+ int locktype = APR_FLOCK_SHARED;
+ apr_file_t *lockfile_handle;
+ apr_int32_t flags;
+
+ if (exclusive)
+ locktype = APR_FLOCK_EXCLUSIVE;
+
+ flags = APR_READ;
+ if (locktype == APR_FLOCK_EXCLUSIVE)
+ flags |= APR_WRITE;
+
+ /* locktype is never read after this block, so we don't need to bother
+ setting it. If that were to ever change, uncomment the following
+ block.
+ if (nonblocking)
+ locktype |= APR_FLOCK_NONBLOCK;
+ */
+
+ SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags,
+ APR_OS_DEFAULT,
+ pool));
+
+ /* Get lock on the filehandle. */
+ return svn_io_lock_open_file(lockfile_handle, exclusive, nonblocking, pool);
+}
+
+
+
+/* Data consistency/coherency operations. */
+
+svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file,
+ apr_pool_t *pool)
+{
+ apr_os_file_t filehand;
+
+ /* First make sure that any user-space buffered data is flushed. */
+ SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file),
+ N_("Can't flush file '%s'"),
+ N_("Can't flush stream"),
+ pool));
+
+ apr_os_file_get(&filehand, file);
+
+ /* Call the operating system specific function to actually force the
+ data to disk. */
+ {
+#ifdef WIN32
+
+ if (! FlushFileBuffers(filehand))
+ return svn_error_wrap_apr(apr_get_os_error(),
+ _("Can't flush file to disk"));
+
+#else
+ int rv;
+
+ do {
+ rv = fsync(filehand);
+ } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error()));
+
+ /* If the file is in a memory filesystem, fsync() may return
+ EINVAL. Presumably the user knows the risks, and we can just
+ ignore the error. */
+ if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error()))
+ return SVN_NO_ERROR;
+
+ if (rv == -1)
+ return svn_error_wrap_apr(apr_get_os_error(),
+ _("Can't flush file to disk"));
+
+#endif
+ }
+ return SVN_NO_ERROR;
+}
+
+
+
+/* TODO write test for these two functions, then refactor. */
+
+/* Set RESULT to an svn_stringbuf_t containing the contents of FILE.
+ FILENAME is the FILE's on-disk APR-safe name, or NULL if that name
+ isn't known. If CHECK_SIZE is TRUE, the function will attempt to
+ first stat() the file to determine it's size before sucking its
+ contents into the stringbuf. (Doing so can prevent unnecessary
+ memory usage, an unwanted side effect of the stringbuf growth and
+ reallocation mechanism.) */
+static svn_error_t *
+stringbuf_from_aprfile(svn_stringbuf_t **result,
+ const char *filename,
+ apr_file_t *file,
+ svn_boolean_t check_size,
+ apr_pool_t *pool)
+{
+ apr_size_t len;
+ svn_error_t *err;
+ svn_stringbuf_t *res = NULL;
+ apr_size_t res_initial_len = SVN__STREAM_CHUNK_SIZE;
+ char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
+
+ /* If our caller wants us to check the size of the file for
+ efficient memory handling, we'll try to do so. */
+ if (check_size)
+ {
+ apr_status_t status;
+
+ /* If our caller didn't tell us the file's name, we'll ask APR
+ if it knows the name. No problem if we can't figure it out. */
+ if (! filename)
+ {
+ const char *filename_apr;
+ if (! (status = apr_file_name_get(&filename_apr, file)))
+ filename = filename_apr;
+ }
+
+ /* If we now know the filename, try to stat(). If we succeed,
+ we know how to allocate our stringbuf. */
+ if (filename)
+ {
+ apr_finfo_t finfo;
+ if (! (status = apr_stat(&finfo, filename, APR_FINFO_MIN, pool)))
+ res_initial_len = (apr_size_t)finfo.size;
+ }
+ }
+
+
+ /* XXX: We should check the incoming data for being of type binary. */
+
+ res = svn_stringbuf_create_ensure(res_initial_len, pool);
+
+ /* apr_file_read will not return data and eof in the same call. So this loop
+ * is safe from missing read data. */
+ len = SVN__STREAM_CHUNK_SIZE;
+ err = svn_io_file_read(file, buf, &len, pool);
+ while (! err)
+ {
+ svn_stringbuf_appendbytes(res, buf, len);
+ len = SVN__STREAM_CHUNK_SIZE;
+ err = svn_io_file_read(file, buf, &len, pool);
+ }
+
+ /* Having read all the data we *expect* EOF */
+ if (err && !APR_STATUS_IS_EOF(err->apr_err))
+ return err;
+ svn_error_clear(err);
+
+ *result = res;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_stringbuf_from_file2(svn_stringbuf_t **result,
+ const char *filename,
+ apr_pool_t *pool)
+{
+ apr_file_t *f;
+
+ if (filename[0] == '-' && filename[1] == '\0')
+ {
+ apr_status_t apr_err;
+ if ((apr_err = apr_file_open_stdin(&f, pool)))
+ return svn_error_wrap_apr(apr_err, _("Can't open stdin"));
+ SVN_ERR(stringbuf_from_aprfile(result, NULL, f, FALSE, pool));
+ }
+ else
+ {
+ SVN_ERR(svn_io_file_open(&f, filename, APR_READ, APR_OS_DEFAULT, pool));
+ SVN_ERR(stringbuf_from_aprfile(result, filename, f, TRUE, pool));
+ }
+ return svn_io_file_close(f, pool);
+}
+
+
+svn_error_t *
+svn_stringbuf_from_file(svn_stringbuf_t **result,
+ const char *filename,
+ apr_pool_t *pool)
+{
+ if (filename[0] == '-' && filename[1] == '\0')
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Reading from stdin is disallowed"));
+ return svn_stringbuf_from_file2(result, filename, pool);
+}
+
+svn_error_t *
+svn_stringbuf_from_aprfile(svn_stringbuf_t **result,
+ apr_file_t *file,
+ apr_pool_t *pool)
+{
+ return stringbuf_from_aprfile(result, NULL, file, TRUE, pool);
+}
+
+
+
+/* Deletion. */
+
+svn_error_t *
+svn_io_remove_file2(const char *path,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *scratch_pool)
+{
+ apr_status_t apr_err;
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, scratch_pool));
+
+ apr_err = apr_file_remove(path_apr, scratch_pool);
+ if (!apr_err
+ || (ignore_enoent
+ && (APR_STATUS_IS_ENOENT(apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(apr_err))))
+ return SVN_NO_ERROR;
+
+#ifdef WIN32
+ /* If the target is read only NTFS reports EACCESS and FAT/FAT32
+ reports EEXIST */
+ if (APR_STATUS_IS_EACCES(apr_err) || APR_STATUS_IS_EEXIST(apr_err))
+ {
+ /* Set the destination file writable because Windows will not
+ allow us to delete when path is read-only */
+ SVN_ERR(svn_io_set_file_read_write(path, ignore_enoent, scratch_pool));
+ apr_err = apr_file_remove(path_apr, scratch_pool);
+
+ if (!apr_err)
+ return SVN_NO_ERROR;
+ }
+
+ {
+ apr_status_t os_err = APR_TO_OS_ERROR(apr_err);
+ /* Check to make sure we aren't trying to delete a directory */
+ if (os_err == ERROR_ACCESS_DENIED || os_err == ERROR_SHARING_VIOLATION)
+ {
+ apr_finfo_t finfo;
+
+ if (!apr_stat(&finfo, path_apr, APR_FINFO_TYPE, scratch_pool)
+ && finfo.filetype == APR_REG)
+ {
+ WIN32_RETRY_LOOP(apr_err, apr_file_remove(path_apr,
+ scratch_pool));
+ }
+ }
+
+ /* Just return the delete error */
+ }
+#endif
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't remove file '%s'"),
+ svn_dirent_local_style(path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_remove_dir(const char *path, apr_pool_t *pool)
+{
+ return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
+}
+
+/*
+ Mac OS X has a bug where if you're reading the contents of a
+ directory via readdir in a loop, and you remove one of the entries in
+ the directory and the directory has 338 or more files in it you will
+ skip over some of the entries in the directory. Needless to say,
+ this causes problems if you are using this kind of loop inside a
+ function that is recursively deleting a directory, because when you
+ get around to removing the directory it will still have something in
+ it. A similar problem has been observed in other BSDs. This bug has
+ since been fixed. See http://www.vnode.ch/fixing_seekdir for details.
+
+ The workaround is to delete the files only _after_ the initial
+ directory scan. A previous workaround involving rewinddir is
+ problematic on Win32 and some NFS clients, notably NetBSD.
+
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=1896 and
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3501.
+*/
+
+/* Neither windows nor unix allows us to delete a non-empty
+ directory.
+
+ This is a function to perform the equivalent of 'rm -rf'. */
+svn_error_t *
+svn_io_remove_dir2(const char *path, svn_boolean_t ignore_enoent,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_pool_t *subpool;
+ apr_hash_t *dirents;
+ apr_hash_index_t *hi;
+
+ /* Check for pending cancellation request.
+ If we need to bail out, do so early. */
+
+ if (cancel_func)
+ SVN_ERR((*cancel_func)(cancel_baton));
+
+ subpool = svn_pool_create(pool);
+
+ err = svn_io_get_dirents3(&dirents, path, TRUE, subpool, subpool);
+ if (err)
+ {
+ /* if the directory doesn't exist, our mission is accomplished */
+ if (ignore_enoent && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+ }
+
+ for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi);
+ const char *fullpath;
+
+ fullpath = svn_dirent_join(path, name, subpool);
+ if (dirent->kind == svn_node_dir)
+ {
+ /* Don't check for cancellation, the callee will immediately do so */
+ SVN_ERR(svn_io_remove_dir2(fullpath, FALSE, cancel_func,
+ cancel_baton, subpool));
+ }
+ else
+ {
+ if (cancel_func)
+ SVN_ERR((*cancel_func)(cancel_baton));
+
+ err = svn_io_remove_file2(fullpath, FALSE, subpool);
+ if (err)
+ return svn_error_createf
+ (err->apr_err, err, _("Can't remove '%s'"),
+ svn_dirent_local_style(fullpath, subpool));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ return svn_io_dir_remove_nonrecursive(path, pool);
+}
+
+svn_error_t *
+svn_io_get_dir_filenames(apr_hash_t **dirents,
+ const char *path,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_io_get_dirents3(dirents, path, TRUE,
+ pool, pool));
+}
+
+svn_io_dirent2_t *
+svn_io_dirent2_create(apr_pool_t *result_pool)
+{
+ svn_io_dirent2_t *dirent = apr_pcalloc(result_pool, sizeof(*dirent));
+
+ /*dirent->kind = svn_node_none;
+ dirent->special = FALSE;*/
+ dirent->filesize = SVN_INVALID_FILESIZE;
+ /*dirent->mtime = 0;*/
+
+ return dirent;
+}
+
+svn_io_dirent2_t *
+svn_io_dirent2_dup(const svn_io_dirent2_t *item,
+ apr_pool_t *result_pool)
+{
+ return apr_pmemdup(result_pool,
+ item,
+ sizeof(*item));
+}
+
+svn_error_t *
+svn_io_get_dirents3(apr_hash_t **dirents,
+ const char *path,
+ svn_boolean_t only_check_type,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_status_t status;
+ apr_dir_t *this_dir;
+ apr_finfo_t this_entry;
+ apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
+
+ if (!only_check_type)
+ flags |= APR_FINFO_SIZE | APR_FINFO_MTIME;
+
+ *dirents = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_io_dir_open(&this_dir, path, scratch_pool));
+
+ for (status = apr_dir_read(&this_entry, flags, this_dir);
+ status == APR_SUCCESS;
+ status = apr_dir_read(&this_entry, flags, this_dir))
+ {
+ if ((this_entry.name[0] == '.')
+ && ((this_entry.name[1] == '\0')
+ || ((this_entry.name[1] == '.')
+ && (this_entry.name[2] == '\0'))))
+ {
+ continue;
+ }
+ else
+ {
+ const char *name;
+ svn_io_dirent2_t *dirent = svn_io_dirent2_create(result_pool);
+
+ SVN_ERR(entry_name_to_utf8(&name, this_entry.name, path, result_pool));
+
+ map_apr_finfo_to_node_kind(&(dirent->kind),
+ &(dirent->special),
+ &this_entry);
+
+ if (!only_check_type)
+ {
+ dirent->filesize = this_entry.size;
+ dirent->mtime = this_entry.mtime;
+ }
+
+ svn_hash_sets(*dirents, name, dirent);
+ }
+ }
+
+ if (! (APR_STATUS_IS_ENOENT(status)))
+ return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
+ svn_dirent_local_style(path, scratch_pool));
+
+ status = apr_dir_close(this_dir);
+ if (status)
+ return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
+ svn_dirent_local_style(path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_stat_dirent2(const svn_io_dirent2_t **dirent_p,
+ const char *path,
+ svn_boolean_t verify_truename,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_finfo_t finfo;
+ svn_io_dirent2_t *dirent;
+ svn_error_t *err;
+ apr_int32_t wanted = APR_FINFO_TYPE | APR_FINFO_LINK
+ | APR_FINFO_SIZE | APR_FINFO_MTIME;
+
+#if defined(WIN32) || defined(__OS2__)
+ if (verify_truename)
+ wanted |= APR_FINFO_NAME;
+#endif
+
+ err = svn_io_stat(&finfo, path, wanted, scratch_pool);
+
+ if (err && ignore_enoent &&
+ (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ {
+ svn_error_clear(err);
+ dirent = svn_io_dirent2_create(result_pool);
+ SVN_ERR_ASSERT(dirent->kind == svn_node_none);
+
+ *dirent_p = dirent;
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+#if defined(WIN32) || defined(__OS2__) || defined(DARWIN)
+ if (verify_truename)
+ {
+ const char *requested_name = svn_dirent_basename(path, NULL);
+
+ if (requested_name[0] == '\0')
+ {
+ /* No parent directory. No need to stat/verify */
+ }
+#if defined(WIN32) || defined(__OS2__)
+ else if (finfo.name)
+ {
+ const char *name_on_disk;
+ SVN_ERR(entry_name_to_utf8(&name_on_disk, finfo.name, path,
+ scratch_pool));
+
+ if (strcmp(name_on_disk, requested_name) /* != 0 */)
+ {
+ if (ignore_enoent)
+ {
+ *dirent_p = svn_io_dirent2_create(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ return svn_error_createf(APR_ENOENT, NULL,
+ _("Path '%s' not found, case obstructed by '%s'"),
+ svn_dirent_local_style(path, scratch_pool),
+ name_on_disk);
+ }
+ }
+#elif defined(DARWIN)
+ /* Currently apr doesn't set finfo.name on DARWIN, returning
+ APR_INCOMPLETE.
+ ### Can we optimize this in another way? */
+ else
+ {
+ apr_hash_t *dirents;
+
+ err = svn_io_get_dirents3(&dirents,
+ svn_dirent_dirname(path, scratch_pool),
+ TRUE /* only_check_type */,
+ scratch_pool, scratch_pool);
+
+ if (err && ignore_enoent
+ && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ {
+ svn_error_clear(err);
+
+ *dirent_p = svn_io_dirent2_create(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ if (! svn_hash_gets(dirents, requested_name))
+ {
+ if (ignore_enoent)
+ {
+ *dirent_p = svn_io_dirent2_create(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ return svn_error_createf(APR_ENOENT, NULL,
+ _("Path '%s' not found"),
+ svn_dirent_local_style(path, scratch_pool));
+ }
+ }
+#endif
+ }
+#endif
+
+ dirent = svn_io_dirent2_create(result_pool);
+ map_apr_finfo_to_node_kind(&(dirent->kind), &(dirent->special), &finfo);
+
+ dirent->filesize = finfo.size;
+ dirent->mtime = finfo.mtime;
+
+ *dirent_p = dirent;
+
+ return SVN_NO_ERROR;
+}
+
+/* Pool userdata key for the error file passed to svn_io_start_cmd(). */
+#define ERRFILE_KEY "svn-io-start-cmd-errfile"
+
+/* Handle an error from the child process (before command execution) by
+ printing DESC and the error string corresponding to STATUS to stderr. */
+static void
+handle_child_process_error(apr_pool_t *pool, apr_status_t status,
+ const char *desc)
+{
+ char errbuf[256];
+ apr_file_t *errfile;
+ void *p;
+
+ /* We can't do anything if we get an error here, so just return. */
+ if (apr_pool_userdata_get(&p, ERRFILE_KEY, pool))
+ return;
+ errfile = p;
+
+ if (errfile)
+ /* What we get from APR is in native encoding. */
+ apr_file_printf(errfile, "%s: %s",
+ desc, apr_strerror(status, errbuf,
+ sizeof(errbuf)));
+}
+
+
+svn_error_t *
+svn_io_start_cmd3(apr_proc_t *cmd_proc,
+ const char *path,
+ const char *cmd,
+ const char *const *args,
+ const char *const *env,
+ svn_boolean_t inherit,
+ svn_boolean_t infile_pipe,
+ apr_file_t *infile,
+ svn_boolean_t outfile_pipe,
+ apr_file_t *outfile,
+ svn_boolean_t errfile_pipe,
+ apr_file_t *errfile,
+ apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ apr_procattr_t *cmdproc_attr;
+ int num_args;
+ const char **args_native;
+ const char *cmd_apr;
+
+ SVN_ERR_ASSERT(!((infile != NULL) && infile_pipe));
+ SVN_ERR_ASSERT(!((outfile != NULL) && outfile_pipe));
+ SVN_ERR_ASSERT(!((errfile != NULL) && errfile_pipe));
+
+ /* Create the process attributes. */
+ apr_err = apr_procattr_create(&cmdproc_attr, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't create process '%s' attributes"),
+ cmd);
+
+ /* Make sure we invoke cmd directly, not through a shell. */
+ apr_err = apr_procattr_cmdtype_set(cmdproc_attr,
+ inherit ? APR_PROGRAM_PATH : APR_PROGRAM);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't set process '%s' cmdtype"),
+ cmd);
+
+ /* Set the process's working directory. */
+ if (path)
+ {
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+ apr_err = apr_procattr_dir_set(cmdproc_attr, path_apr);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' directory"),
+ cmd);
+ }
+
+ /* Use requested inputs and outputs.
+
+ ### Unfortunately each of these apr functions creates a pipe and then
+ overwrites the pipe file descriptor with the descriptor we pass
+ in. The pipes can then never be closed. This is an APR bug. */
+ if (infile)
+ {
+ apr_err = apr_procattr_child_in_set(cmdproc_attr, infile, NULL);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' child input"),
+ cmd);
+ }
+ if (outfile)
+ {
+ apr_err = apr_procattr_child_out_set(cmdproc_attr, outfile, NULL);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' child outfile"),
+ cmd);
+ }
+ if (errfile)
+ {
+ apr_err = apr_procattr_child_err_set(cmdproc_attr, errfile, NULL);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' child errfile"),
+ cmd);
+ }
+
+ /* Forward request for pipes to APR. */
+ if (infile_pipe || outfile_pipe || errfile_pipe)
+ {
+ apr_err = apr_procattr_io_set(cmdproc_attr,
+ infile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE,
+ outfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE,
+ errfile_pipe ? APR_FULL_BLOCK : APR_NO_PIPE);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' stdio pipes"),
+ cmd);
+ }
+
+ /* Have the child print any problems executing its program to errfile. */
+ apr_err = apr_pool_userdata_set(errfile, ERRFILE_KEY, NULL, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' child errfile for "
+ "error handler"),
+ cmd);
+ apr_err = apr_procattr_child_errfn_set(cmdproc_attr,
+ handle_child_process_error);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't set process '%s' error handler"),
+ cmd);
+
+ /* Convert cmd and args from UTF-8 */
+ SVN_ERR(cstring_from_utf8(&cmd_apr, cmd, pool));
+ for (num_args = 0; args[num_args]; num_args++)
+ ;
+ args_native = apr_palloc(pool, (num_args + 1) * sizeof(char *));
+ args_native[num_args] = NULL;
+ while (num_args--)
+ {
+ /* ### Well, it turns out that on APR on Windows expects all
+ program args to be in UTF-8. Callers of svn_io_run_cmd
+ should be aware of that. */
+ SVN_ERR(cstring_from_utf8(&args_native[num_args],
+ args[num_args], pool));
+ }
+
+
+ /* Start the cmd command. */
+ apr_err = apr_proc_create(cmd_proc, cmd_apr, args_native,
+ inherit ? NULL : env, cmdproc_attr, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't start process '%s'"), cmd);
+
+ return SVN_NO_ERROR;
+}
+
+#undef ERRFILE_KEY
+
+svn_error_t *
+svn_io_wait_for_cmd(apr_proc_t *cmd_proc,
+ const char *cmd,
+ int *exitcode,
+ apr_exit_why_e *exitwhy,
+ apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ apr_exit_why_e exitwhy_val;
+ int exitcode_val;
+
+ /* The Win32 apr_proc_wait doesn't set this... */
+ exitwhy_val = APR_PROC_EXIT;
+
+ /* Wait for the cmd command to finish. */
+ apr_err = apr_proc_wait(cmd_proc, &exitcode_val, &exitwhy_val, APR_WAIT);
+ if (!APR_STATUS_IS_CHILD_DONE(apr_err))
+ return svn_error_wrap_apr(apr_err, _("Error waiting for process '%s'"),
+ cmd);
+
+ if (exitwhy)
+ *exitwhy = exitwhy_val;
+ else if (APR_PROC_CHECK_SIGNALED(exitwhy_val)
+ && APR_PROC_CHECK_CORE_DUMP(exitwhy_val))
+ return svn_error_createf
+ (SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("Process '%s' failed (signal %d, core dumped)"),
+ cmd, exitcode_val);
+ else if (APR_PROC_CHECK_SIGNALED(exitwhy_val))
+ return svn_error_createf
+ (SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("Process '%s' failed (signal %d)"),
+ cmd, exitcode_val);
+ else if (! APR_PROC_CHECK_EXIT(exitwhy_val))
+ /* Don't really know what happened here. */
+ return svn_error_createf
+ (SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("Process '%s' failed (exitwhy %d, exitcode %d)"),
+ cmd, exitwhy_val, exitcode_val);
+
+ if (exitcode)
+ *exitcode = exitcode_val;
+ else if (exitcode_val != 0)
+ return svn_error_createf
+ (SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("Process '%s' returned error exitcode %d"), cmd, exitcode_val);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_run_cmd(const char *path,
+ const char *cmd,
+ const char *const *args,
+ int *exitcode,
+ apr_exit_why_e *exitwhy,
+ svn_boolean_t inherit,
+ apr_file_t *infile,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ apr_pool_t *pool)
+{
+ apr_proc_t cmd_proc;
+
+ SVN_ERR(svn_io_start_cmd3(&cmd_proc, path, cmd, args, NULL, inherit,
+ FALSE, infile, FALSE, outfile, FALSE, errfile,
+ pool));
+
+ return svn_io_wait_for_cmd(&cmd_proc, cmd, exitcode, exitwhy, pool);
+}
+
+
+svn_error_t *
+svn_io_run_diff2(const char *dir,
+ const char *const *user_args,
+ int num_user_args,
+ const char *label1,
+ const char *label2,
+ const char *from,
+ const char *to,
+ int *pexitcode,
+ apr_file_t *outfile,
+ apr_file_t *errfile,
+ const char *diff_cmd,
+ apr_pool_t *pool)
+{
+ const char **args;
+ int i;
+ int exitcode;
+ int nargs = 4; /* the diff command itself, two paths, plus a trailing NULL */
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ if (pexitcode == NULL)
+ pexitcode = &exitcode;
+
+ if (user_args != NULL)
+ nargs += num_user_args;
+ else
+ nargs += 1; /* -u */
+
+ if (label1 != NULL)
+ nargs += 2; /* the -L and the label itself */
+ if (label2 != NULL)
+ nargs += 2; /* the -L and the label itself */
+
+ args = apr_palloc(subpool, nargs * sizeof(char *));
+
+ i = 0;
+ args[i++] = diff_cmd;
+
+ if (user_args != NULL)
+ {
+ int j;
+ for (j = 0; j < num_user_args; ++j)
+ args[i++] = user_args[j];
+ }
+ else
+ args[i++] = "-u"; /* assume -u if the user didn't give us any args */
+
+ if (label1 != NULL)
+ {
+ args[i++] = "-L";
+ args[i++] = label1;
+ }
+ if (label2 != NULL)
+ {
+ args[i++] = "-L";
+ args[i++] = label2;
+ }
+
+ args[i++] = svn_dirent_local_style(from, subpool);
+ args[i++] = svn_dirent_local_style(to, subpool);
+ args[i++] = NULL;
+
+ SVN_ERR_ASSERT(i == nargs);
+
+ SVN_ERR(svn_io_run_cmd(dir, diff_cmd, args, pexitcode, NULL, TRUE,
+ NULL, outfile, errfile, subpool));
+
+ /* The man page for (GNU) diff describes the return value as:
+
+ "An exit status of 0 means no differences were found, 1 means
+ some differences were found, and 2 means trouble."
+
+ A return value of 2 typically occurs when diff cannot read its input
+ or write to its output, but in any case we probably ought to return an
+ error for anything other than 0 or 1 as the output is likely to be
+ corrupt.
+ */
+ if (*pexitcode != 0 && *pexitcode != 1)
+ return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("'%s' returned %d"),
+ svn_dirent_local_style(diff_cmd, pool),
+ *pexitcode);
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_run_diff3_3(int *exitcode,
+ const char *dir,
+ const char *mine,
+ const char *older,
+ const char *yours,
+ const char *mine_label,
+ const char *older_label,
+ const char *yours_label,
+ apr_file_t *merged,
+ const char *diff3_cmd,
+ const apr_array_header_t *user_args,
+ apr_pool_t *pool)
+{
+ const char **args = apr_palloc(pool,
+ sizeof(char*) * (13
+ + (user_args
+ ? user_args->nelts
+ : 1)));
+#ifndef NDEBUG
+ int nargs = 12;
+#endif
+ int i = 0;
+
+ /* Labels fall back to sensible defaults if not specified. */
+ if (mine_label == NULL)
+ mine_label = ".working";
+ if (older_label == NULL)
+ older_label = ".old";
+ if (yours_label == NULL)
+ yours_label = ".new";
+
+ /* Set up diff3 command line. */
+ args[i++] = diff3_cmd;
+ if (user_args)
+ {
+ int j;
+ for (j = 0; j < user_args->nelts; ++j)
+ args[i++] = APR_ARRAY_IDX(user_args, j, const char *);
+#ifndef NDEBUG
+ nargs += user_args->nelts;
+#endif
+ }
+ else
+ {
+ args[i++] = "-E"; /* We tried "-A" here, but that caused
+ overlapping identical changes to
+ conflict. See issue #682. */
+#ifndef NDEBUG
+ ++nargs;
+#endif
+ }
+ args[i++] = "-m";
+ args[i++] = "-L";
+ args[i++] = mine_label;
+ args[i++] = "-L";
+ args[i++] = older_label; /* note: this label is ignored if
+ using 2-part markers, which is the
+ case with "-E". */
+ args[i++] = "-L";
+ args[i++] = yours_label;
+#ifdef SVN_DIFF3_HAS_DIFF_PROGRAM_ARG
+ {
+ svn_boolean_t has_arg;
+
+ /* ### FIXME: we really shouldn't be reading the config here;
+ instead, the necessary bits should be passed in by the caller.
+ But should we add another parameter to this function, when the
+ whole external diff3 thing might eventually go away? */
+ apr_hash_t *config;
+ svn_config_t *cfg;
+
+ SVN_ERR(svn_config_get_config(&config, pool));
+ cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
+ SVN_ERR(svn_config_get_bool(cfg, &has_arg, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF3_HAS_PROGRAM_ARG,
+ TRUE));
+ if (has_arg)
+ {
+ const char *diff_cmd, *diff_utf8;
+ svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF_CMD, SVN_CLIENT_DIFF);
+ SVN_ERR(cstring_to_utf8(&diff_utf8, diff_cmd, pool));
+ args[i++] = apr_pstrcat(pool, "--diff-program=", diff_utf8, NULL);
+#ifndef NDEBUG
+ ++nargs;
+#endif
+ }
+ }
+#endif
+ args[i++] = svn_dirent_local_style(mine, pool);
+ args[i++] = svn_dirent_local_style(older, pool);
+ args[i++] = svn_dirent_local_style(yours, pool);
+ args[i++] = NULL;
+#ifndef NDEBUG
+ SVN_ERR_ASSERT(i == nargs);
+#endif
+
+ /* Run diff3, output the merged text into the scratch file. */
+ SVN_ERR(svn_io_run_cmd(dir, diff3_cmd, args,
+ exitcode, NULL,
+ TRUE, /* keep environment */
+ NULL, merged, NULL,
+ pool));
+
+ /* According to the diff3 docs, a '0' means the merge was clean, and
+ '1' means conflict markers were found. Anything else is real
+ error. */
+ if ((*exitcode != 0) && (*exitcode != 1))
+ return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("Error running '%s': exitcode was %d, "
+ "args were:"
+ "\nin directory '%s', basenames:\n%s\n%s\n%s"),
+ svn_dirent_local_style(diff3_cmd, pool),
+ *exitcode,
+ svn_dirent_local_style(dir, pool),
+ /* Don't call svn_path_local_style() on
+ the basenames. We don't want them to
+ be absolute, and we don't need the
+ separator conversion. */
+ mine, older, yours);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Canonicalize a string for hashing. Modifies KEY in place. */
+static APR_INLINE char *
+fileext_tolower(char *key)
+{
+ register char *p;
+ for (p = key; *p != 0; ++p)
+ *p = (char)apr_tolower(*p);
+ return key;
+}
+
+
+svn_error_t *
+svn_io_parse_mimetypes_file(apr_hash_t **type_map,
+ const char *mimetypes_file,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_hash_t *types = apr_hash_make(pool);
+ svn_boolean_t eof = FALSE;
+ svn_stringbuf_t *buf;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_file_t *types_file;
+ svn_stream_t *mimetypes_stream;
+
+ SVN_ERR(svn_io_file_open(&types_file, mimetypes_file,
+ APR_READ, APR_OS_DEFAULT, pool));
+ mimetypes_stream = svn_stream_from_aprfile2(types_file, FALSE, pool);
+
+ while (1)
+ {
+ apr_array_header_t *tokens;
+ const char *type;
+
+ svn_pool_clear(subpool);
+
+ /* Read a line. */
+ if ((err = svn_stream_readline(mimetypes_stream, &buf,
+ APR_EOL_STR, &eof, subpool)))
+ break;
+
+ /* Only pay attention to non-empty, non-comment lines. */
+ if (buf->len)
+ {
+ int i;
+
+ if (buf->data[0] == '#')
+ continue;
+
+ /* Tokenize (into our return pool). */
+ tokens = svn_cstring_split(buf->data, " \t", TRUE, pool);
+ if (tokens->nelts < 2)
+ continue;
+
+ /* The first token in a multi-token line is the media type.
+ Subsequent tokens are filename extensions associated with
+ that media type. */
+ type = APR_ARRAY_IDX(tokens, 0, const char *);
+ for (i = 1; i < tokens->nelts; i++)
+ {
+ /* We can safely address 'ext' as a non-const string because
+ * we know svn_cstring_split() allocated it in 'pool' for us. */
+ char *ext = APR_ARRAY_IDX(tokens, i, char *);
+ fileext_tolower(ext);
+ svn_hash_sets(types, ext, type);
+ }
+ }
+ if (eof)
+ break;
+ }
+ svn_pool_destroy(subpool);
+
+ /* If there was an error above, close the file (ignoring any error
+ from *that*) and return the originally error. */
+ if (err)
+ {
+ svn_error_clear(svn_stream_close(mimetypes_stream));
+ return err;
+ }
+
+ /* Close the stream (which closes the underlying file, too). */
+ SVN_ERR(svn_stream_close(mimetypes_stream));
+
+ *type_map = types;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_detect_mimetype2(const char **mimetype,
+ const char *file,
+ apr_hash_t *mimetype_map,
+ apr_pool_t *pool)
+{
+ static const char * const generic_binary = "application/octet-stream";
+
+ svn_node_kind_t kind;
+ apr_file_t *fh;
+ svn_error_t *err;
+ unsigned char block[1024];
+ apr_size_t amt_read = sizeof(block);
+
+ /* Default return value is NULL. */
+ *mimetype = NULL;
+
+ /* If there is a mimetype_map provided, we'll first try to look up
+ our file's extension in the map. Failing that, we'll run the
+ heuristic. */
+ if (mimetype_map)
+ {
+ const char *type_from_map;
+ char *path_ext; /* Can point to physical const memory but only when
+ svn_path_splitext sets it to "". */
+ svn_path_splitext(NULL, (const char **)&path_ext, file, pool);
+ fileext_tolower(path_ext);
+ if ((type_from_map = svn_hash_gets(mimetype_map, path_ext)))
+ {
+ *mimetype = type_from_map;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* See if this file even exists, and make sure it really is a file. */
+ SVN_ERR(svn_io_check_path(file, &kind, pool));
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
+ _("Can't detect MIME type of non-file '%s'"),
+ svn_dirent_local_style(file, pool));
+
+ SVN_ERR(svn_io_file_open(&fh, file, APR_READ, 0, pool));
+
+ /* Read a block of data from FILE. */
+ err = svn_io_file_read(fh, block, &amt_read, pool);
+ if (err && ! APR_STATUS_IS_EOF(err->apr_err))
+ return err;
+ svn_error_clear(err);
+
+ /* Now close the file. No use keeping it open any more. */
+ SVN_ERR(svn_io_file_close(fh, pool));
+
+ if (svn_io_is_binary_data(block, amt_read))
+ *mimetype = generic_binary;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_boolean_t
+svn_io_is_binary_data(const void *data, apr_size_t len)
+{
+ const unsigned char *buf = data;
+
+ if (len == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF)
+ {
+ /* This is an empty UTF-8 file which only contains the UTF-8 BOM.
+ * Treat it as plain text. */
+ return FALSE;
+ }
+
+ /* Right now, this function is going to be really stupid. It's
+ going to examine the block of data, and make sure that 15%
+ of the bytes are such that their value is in the ranges 0x07-0x0D
+ or 0x20-0x7F, and that none of those bytes is 0x00. If those
+ criteria are not met, we're calling it binary.
+
+ NOTE: Originally, I intended to target 85% of the bytes being in
+ the specified ranges, but I flubbed the condition. At any rate,
+ folks aren't complaining, so I'm not sure that it's worth
+ adjusting this retroactively now. --cmpilato */
+ if (len > 0)
+ {
+ apr_size_t i;
+ apr_size_t binary_count = 0;
+
+ /* Run through the data we've read, counting the 'binary-ish'
+ bytes. HINT: If we see a 0x00 byte, we'll set our count to its
+ max and stop reading the file. */
+ for (i = 0; i < len; i++)
+ {
+ if (buf[i] == 0)
+ {
+ binary_count = len;
+ break;
+ }
+ if ((buf[i] < 0x07)
+ || ((buf[i] > 0x0D) && (buf[i] < 0x20))
+ || (buf[i] > 0x7F))
+ {
+ binary_count++;
+ }
+ }
+
+ return (((binary_count * 1000) / len) > 850);
+ }
+
+ return FALSE;
+}
+
+
+svn_error_t *
+svn_io_detect_mimetype(const char **mimetype,
+ const char *file,
+ apr_pool_t *pool)
+{
+ return svn_io_detect_mimetype2(mimetype, file, NULL, pool);
+}
+
+
+svn_error_t *
+svn_io_file_open(apr_file_t **new_file, const char *fname,
+ apr_int32_t flag, apr_fileperms_t perm,
+ apr_pool_t *pool)
+{
+ const char *fname_apr;
+ apr_status_t status;
+
+ SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
+ status = file_open(new_file, fname_apr, flag | APR_BINARY, perm, TRUE,
+ pool);
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't open file '%s'"),
+ svn_dirent_local_style(fname, pool));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+static APR_INLINE svn_error_t *
+do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
+ const char *msg, const char *msg_no_name,
+ apr_pool_t *pool)
+{
+ const char *name;
+ svn_error_t *err;
+
+ if (! status)
+ return SVN_NO_ERROR;
+
+ err = svn_io_file_name_get(&name, file, pool);
+ if (err)
+ name = NULL;
+ svn_error_clear(err);
+
+ /* ### Issue #3014: Return a specific error for broken pipes,
+ * ### with a single element in the error chain. */
+ if (APR_STATUS_IS_EPIPE(status))
+ return svn_error_create(SVN_ERR_IO_PIPE_WRITE_ERROR, NULL, NULL);
+
+ if (name)
+ return svn_error_wrap_apr(status, _(msg),
+ try_utf8_from_internal_style(name, pool));
+ else
+ return svn_error_wrap_apr(status, "%s", _(msg_no_name));
+}
+
+
+svn_error_t *
+svn_io_file_close(apr_file_t *file, apr_pool_t *pool)
+{
+ return do_io_file_wrapper_cleanup(file, apr_file_close(file),
+ N_("Can't close file '%s'"),
+ N_("Can't close stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_getc(char *ch, apr_file_t *file, apr_pool_t *pool)
+{
+ return do_io_file_wrapper_cleanup(file, apr_file_getc(ch, file),
+ N_("Can't read file '%s'"),
+ N_("Can't read stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_putc(char ch, apr_file_t *file, apr_pool_t *pool)
+{
+ return do_io_file_wrapper_cleanup(file, apr_file_putc(ch, file),
+ N_("Can't write file '%s'"),
+ N_("Can't write stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_info_get(apr_finfo_t *finfo, apr_int32_t wanted,
+ apr_file_t *file, apr_pool_t *pool)
+{
+ /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
+ wanted &= ~SVN__APR_FINFO_MASK_OUT;
+
+ return do_io_file_wrapper_cleanup(
+ file, apr_file_info_get(finfo, wanted, file),
+ N_("Can't get attribute information from file '%s'"),
+ N_("Can't get attribute information from stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_read(apr_file_t *file, void *buf,
+ apr_size_t *nbytes, apr_pool_t *pool)
+{
+ return do_io_file_wrapper_cleanup(file, apr_file_read(file, buf, nbytes),
+ N_("Can't read file '%s'"),
+ N_("Can't read stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_read_full2(apr_file_t *file, void *buf,
+ apr_size_t nbytes, apr_size_t *bytes_read,
+ svn_boolean_t *hit_eof,
+ apr_pool_t *pool)
+{
+ apr_status_t status = apr_file_read_full(file, buf, nbytes, bytes_read);
+ if (hit_eof)
+ {
+ if (APR_STATUS_IS_EOF(status))
+ {
+ *hit_eof = TRUE;
+ return SVN_NO_ERROR;
+ }
+ else
+ *hit_eof = FALSE;
+ }
+
+ return do_io_file_wrapper_cleanup(file, status,
+ N_("Can't read file '%s'"),
+ N_("Can't read stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_seek(apr_file_t *file, apr_seek_where_t where,
+ apr_off_t *offset, apr_pool_t *pool)
+{
+ return do_io_file_wrapper_cleanup(
+ file, apr_file_seek(file, where, offset),
+ N_("Can't set position pointer in file '%s'"),
+ N_("Can't set position pointer in stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_file_write(apr_file_t *file, const void *buf,
+ apr_size_t *nbytes, apr_pool_t *pool)
+{
+ return svn_error_trace(do_io_file_wrapper_cleanup(
+ file, apr_file_write(file, buf, nbytes),
+ N_("Can't write to file '%s'"),
+ N_("Can't write to stream"),
+ pool));
+}
+
+
+svn_error_t *
+svn_io_file_write_full(apr_file_t *file, const void *buf,
+ apr_size_t nbytes, apr_size_t *bytes_written,
+ apr_pool_t *pool)
+{
+ /* We cannot simply call apr_file_write_full on Win32 as it may fail
+ for larger values of NBYTES. In that case, we have to emulate the
+ "_full" part here. Thus, always call apr_file_write directly on
+ Win32 as this minimizes overhead for small data buffers. */
+#ifdef WIN32
+#define MAXBUFSIZE 30*1024
+ apr_size_t bw = nbytes;
+ apr_size_t to_write = nbytes;
+
+ /* try a simple "write everything at once" first */
+ apr_status_t rv = apr_file_write(file, buf, &bw);
+ buf = (char *)buf + bw;
+ to_write -= bw;
+
+ /* if the OS cannot handle that, use smaller chunks */
+ if (rv == APR_FROM_OS_ERROR(ERROR_NOT_ENOUGH_MEMORY)
+ && nbytes > MAXBUFSIZE)
+ {
+ do {
+ bw = to_write > MAXBUFSIZE ? MAXBUFSIZE : to_write;
+ rv = apr_file_write(file, buf, &bw);
+ buf = (char *)buf + bw;
+ to_write -= bw;
+ } while (rv == APR_SUCCESS && to_write > 0);
+ }
+
+ /* bytes_written may actually be NULL */
+ if (bytes_written)
+ *bytes_written = nbytes - to_write;
+#undef MAXBUFSIZE
+#else
+ apr_status_t rv = apr_file_write_full(file, buf, nbytes, bytes_written);
+#endif
+
+ return svn_error_trace(do_io_file_wrapper_cleanup(
+ file, rv,
+ N_("Can't write to file '%s'"),
+ N_("Can't write to stream"),
+ pool));
+}
+
+
+svn_error_t *
+svn_io_write_unique(const char **tmp_path,
+ const char *dirpath,
+ const void *buf,
+ apr_size_t nbytes,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *pool)
+{
+ apr_file_t *new_file;
+ svn_error_t *err;
+
+ SVN_ERR(svn_io_open_unique_file3(&new_file, tmp_path, dirpath,
+ delete_when, pool, pool));
+
+ err = svn_io_file_write_full(new_file, buf, nbytes, NULL, pool);
+
+ if (!err)
+ err = svn_io_file_flush_to_disk(new_file, pool);
+
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_io_file_close(new_file, pool)));
+}
+
+
+svn_error_t *
+svn_io_file_trunc(apr_file_t *file, apr_off_t offset, apr_pool_t *pool)
+{
+ /* This is a work-around. APR would flush the write buffer
+ _after_ truncating the file causing now invalid buffered
+ data to be written behind OFFSET. */
+ SVN_ERR(do_io_file_wrapper_cleanup(file, apr_file_flush(file),
+ N_("Can't flush file '%s'"),
+ N_("Can't flush stream"),
+ pool));
+
+ return do_io_file_wrapper_cleanup(file, apr_file_trunc(file, offset),
+ N_("Can't truncate file '%s'"),
+ N_("Can't truncate stream"),
+ pool);
+}
+
+
+svn_error_t *
+svn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit,
+ apr_pool_t *pool)
+{
+ /* variables */
+ apr_size_t total_read = 0;
+ svn_boolean_t eof = FALSE;
+ const char *name;
+ svn_error_t *err;
+ apr_size_t buf_size = *limit;
+
+ while (buf_size > 0)
+ {
+ /* read a fair chunk of data at once. But don't get too ambitious
+ * as that would result in too much waste. Also make sure we can
+ * put a NUL after the last byte read.
+ */
+ apr_size_t to_read = buf_size < 129 ? buf_size - 1 : 128;
+ apr_size_t bytes_read = 0;
+ char *eol;
+
+ /* read data block (or just a part of it) */
+ SVN_ERR(svn_io_file_read_full2(file, buf, to_read,
+ &bytes_read, &eof, pool));
+
+ /* look or a newline char */
+ buf[bytes_read] = 0;
+ eol = strchr(buf, '\n');
+ if (eol)
+ {
+ apr_off_t offset = (eol + 1 - buf) - (apr_off_t)bytes_read;
+
+ *eol = 0;
+ *limit = total_read + (eol - buf);
+
+ /* correct the file pointer:
+ * appear as though we just had read the newline char
+ */
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
+
+ return SVN_NO_ERROR;
+ }
+ else if (eof)
+ {
+ /* no EOL found but we hit the end of the file.
+ * Generate a nice EOF error object and return it.
+ */
+ char dummy;
+ SVN_ERR(svn_io_file_getc(&dummy, file, pool));
+ }
+
+ /* next data chunk */
+ buf_size -= bytes_read;
+ buf += bytes_read;
+ total_read += bytes_read;
+ }
+
+ /* buffer limit has been exceeded without finding the EOL */
+ err = svn_io_file_name_get(&name, file, pool);
+ if (err)
+ name = NULL;
+ svn_error_clear(err);
+
+ if (name)
+ return svn_error_createf(SVN_ERR_MALFORMED_FILE, NULL,
+ _("Can't read length line in file '%s'"),
+ svn_dirent_local_style(name, pool));
+ else
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+ _("Can't read length line in stream"));
+}
+
+
+svn_error_t *
+svn_io_stat(apr_finfo_t *finfo, const char *fname,
+ apr_int32_t wanted, apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *fname_apr;
+
+ /* APR doesn't like "" directories */
+ if (fname[0] == '\0')
+ fname = ".";
+
+ SVN_ERR(cstring_from_utf8(&fname_apr, fname, pool));
+
+ /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
+ wanted &= ~SVN__APR_FINFO_MASK_OUT;
+
+ status = apr_stat(finfo, fname_apr, wanted, pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't stat '%s'"),
+ svn_dirent_local_style(fname, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_file_rename(const char *from_path, const char *to_path,
+ apr_pool_t *pool)
+{
+ apr_status_t status = APR_SUCCESS;
+ const char *from_path_apr, *to_path_apr;
+
+ SVN_ERR(cstring_from_utf8(&from_path_apr, from_path, pool));
+ SVN_ERR(cstring_from_utf8(&to_path_apr, to_path, pool));
+
+ status = apr_file_rename(from_path_apr, to_path_apr, pool);
+
+#if defined(WIN32) || defined(__OS2__)
+ /* If the target file is read only NTFS reports EACCESS and
+ FAT/FAT32 reports EEXIST */
+ if (APR_STATUS_IS_EACCES(status) || APR_STATUS_IS_EEXIST(status))
+ {
+ /* Set the destination file writable because Windows will not
+ allow us to rename when to_path is read-only, but will
+ allow renaming when from_path is read only. */
+ SVN_ERR(svn_io_set_file_read_write(to_path, TRUE, pool));
+
+ status = apr_file_rename(from_path_apr, to_path_apr, pool);
+ }
+ WIN32_RETRY_LOOP(status, apr_file_rename(from_path_apr, to_path_apr, pool));
+#endif /* WIN32 || __OS2__ */
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't move '%s' to '%s'"),
+ svn_dirent_local_style(from_path, pool),
+ svn_dirent_local_style(to_path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_file_move(const char *from_path, const char *to_path,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_io_file_rename(from_path, to_path, pool);
+
+ if (err && APR_STATUS_IS_EXDEV(err->apr_err))
+ {
+ const char *tmp_to_path;
+
+ svn_error_clear(err);
+
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_to_path,
+ svn_dirent_dirname(to_path, pool),
+ svn_io_file_del_none,
+ pool, pool));
+
+ err = svn_io_copy_file(from_path, tmp_to_path, TRUE, pool);
+ if (err)
+ goto failed_tmp;
+
+ err = svn_io_file_rename(tmp_to_path, to_path, pool);
+ if (err)
+ goto failed_tmp;
+
+ err = svn_io_remove_file2(from_path, FALSE, pool);
+ if (! err)
+ return SVN_NO_ERROR;
+
+ svn_error_clear(svn_io_remove_file2(to_path, FALSE, pool));
+
+ return err;
+
+ failed_tmp:
+ svn_error_clear(svn_io_remove_file2(tmp_to_path, FALSE, pool));
+ }
+
+ return err;
+}
+
+/* Common implementation of svn_io_dir_make and svn_io_dir_make_hidden.
+ HIDDEN determines if the hidden attribute
+ should be set on the newly created directory. */
+static svn_error_t *
+dir_make(const char *path, apr_fileperms_t perm,
+ svn_boolean_t hidden, svn_boolean_t sgid, apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ /* APR doesn't like "" directories */
+ if (path_apr[0] == '\0')
+ path_apr = ".";
+
+#if (APR_OS_DEFAULT & APR_WSTICKY)
+ /* The APR shipped with httpd 2.0.50 contains a bug where
+ APR_OS_DEFAULT encompasses the setuid, setgid, and sticky bits.
+ There is a special case for file creation, but not directory
+ creation, so directories wind up getting created with the sticky
+ bit set. (There is no such thing as a setuid directory, and the
+ setgid bit is apparently ignored at mkdir() time.) If we detect
+ this problem, work around it by unsetting those bits if we are
+ passed APR_OS_DEFAULT. */
+ if (perm == APR_OS_DEFAULT)
+ perm &= ~(APR_USETID | APR_GSETID | APR_WSTICKY);
+#endif
+
+ status = apr_dir_make(path_apr, perm, pool);
+ WIN32_RETRY_LOOP(status, apr_dir_make(path_apr, perm, pool));
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't create directory '%s'"),
+ svn_dirent_local_style(path, pool));
+
+#ifdef APR_FILE_ATTR_HIDDEN
+ if (hidden)
+ {
+#ifndef WIN32
+ status = apr_file_attrs_set(path_apr,
+ APR_FILE_ATTR_HIDDEN,
+ APR_FILE_ATTR_HIDDEN,
+ pool);
+#else
+ /* on Windows, use our wrapper so we can also set the
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED attribute */
+ status = io_win_file_attrs_set(path_apr,
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
+ pool);
+
+#endif
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't hide directory '%s'"),
+ svn_dirent_local_style(path, pool));
+ }
+#endif
+
+/* Windows does not implement sgid. Skip here because retrieving
+ the file permissions via APR_FINFO_PROT | APR_FINFO_OWNER is documented
+ to be 'incredibly expensive'. */
+#ifndef WIN32
+ if (sgid)
+ {
+ apr_finfo_t finfo;
+
+ /* Per our contract, don't do error-checking. Some filesystems
+ * don't support the sgid bit, and that's okay. */
+ status = apr_stat(&finfo, path_apr, APR_FINFO_PROT, pool);
+
+ if (!status)
+ apr_file_perms_set(path_apr, finfo.protection | APR_GSETID);
+ }
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_dir_make(const char *path, apr_fileperms_t perm, apr_pool_t *pool)
+{
+ return dir_make(path, perm, FALSE, FALSE, pool);
+}
+
+svn_error_t *
+svn_io_dir_make_hidden(const char *path, apr_fileperms_t perm,
+ apr_pool_t *pool)
+{
+ return dir_make(path, perm, TRUE, FALSE, pool);
+}
+
+svn_error_t *
+svn_io_dir_make_sgid(const char *path, apr_fileperms_t perm,
+ apr_pool_t *pool)
+{
+ return dir_make(path, perm, FALSE, TRUE, pool);
+}
+
+
+svn_error_t *
+svn_io_dir_open(apr_dir_t **new_dir, const char *dirname, apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *dirname_apr;
+
+ /* APR doesn't like "" directories */
+ if (dirname[0] == '\0')
+ dirname = ".";
+
+ SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
+
+ status = apr_dir_open(new_dir, dirname_apr, pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't open directory '%s'"),
+ svn_dirent_local_style(dirname, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *dirname_apr;
+
+ SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
+
+ status = apr_dir_remove(dirname_apr, pool);
+
+#ifdef WIN32
+ {
+ svn_boolean_t retry = TRUE;
+
+ if (APR_TO_OS_ERROR(status) == ERROR_DIR_NOT_EMPTY)
+ {
+ apr_status_t empty_status = dir_is_empty(dirname_apr, pool);
+
+ if (APR_STATUS_IS_ENOTEMPTY(empty_status))
+ retry = FALSE;
+ }
+
+ if (retry)
+ {
+ WIN32_RETRY_LOOP(status, apr_dir_remove(dirname_apr, pool));
+ }
+ }
+#endif
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't remove directory '%s'"),
+ svn_dirent_local_style(dirname, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_dir_read(apr_finfo_t *finfo,
+ apr_int32_t wanted,
+ apr_dir_t *thedir,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+
+ status = apr_dir_read(finfo, wanted, thedir);
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't read directory"));
+
+ /* It would be nice to use entry_name_to_utf8() below, but can we
+ get the dir's path out of an apr_dir_t? I don't see a reliable
+ way to do it. */
+
+ if (finfo->fname)
+ SVN_ERR(svn_path_cstring_to_utf8(&finfo->fname, finfo->fname, pool));
+
+ if (finfo->name)
+ SVN_ERR(svn_path_cstring_to_utf8(&finfo->name, finfo->name, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_dir_close(apr_dir_t *thedir)
+{
+ apr_status_t apr_err = apr_dir_close(thedir);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Error closing directory"));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_dir_walk2(const char *dirname,
+ apr_int32_t wanted,
+ svn_io_walk_func_t walk_func,
+ void *walk_baton,
+ apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ apr_dir_t *handle;
+ apr_pool_t *subpool;
+ const char *dirname_apr;
+ apr_finfo_t finfo;
+
+ wanted |= APR_FINFO_TYPE | APR_FINFO_NAME;
+
+ /* Quoting APR: On NT this request is incredibly expensive, but accurate. */
+ wanted &= ~SVN__APR_FINFO_MASK_OUT;
+
+ /* The documentation for apr_dir_read used to state that "." and ".."
+ will be returned as the first two files, but it doesn't
+ work that way in practice, in particular ext3 on Linux-2.6 doesn't
+ follow the rules. For details see
+ http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=56666
+
+ If APR ever does implement "dot-first" then it would be possible to
+ remove the svn_io_stat and walk_func calls and use the walk_func
+ inside the loop.
+
+ Note: apr_stat doesn't handle FINFO_NAME but svn_io_dir_walk is
+ documented to provide it, so we have to do a bit extra. */
+ SVN_ERR(svn_io_stat(&finfo, dirname, wanted & ~APR_FINFO_NAME, pool));
+ SVN_ERR(cstring_from_utf8(&finfo.name,
+ svn_dirent_basename(dirname, pool),
+ pool));
+ finfo.valid |= APR_FINFO_NAME;
+ SVN_ERR((*walk_func)(walk_baton, dirname, &finfo, pool));
+
+ SVN_ERR(cstring_from_utf8(&dirname_apr, dirname, pool));
+
+ /* APR doesn't like "" directories */
+ if (dirname_apr[0] == '\0')
+ dirname_apr = ".";
+
+ apr_err = apr_dir_open(&handle, dirname_apr, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't open directory '%s'"),
+ svn_dirent_local_style(dirname, pool));
+
+ /* iteration subpool */
+ subpool = svn_pool_create(pool);
+
+ while (1)
+ {
+ const char *name_utf8;
+ const char *full_path;
+
+ svn_pool_clear(subpool);
+
+ apr_err = apr_dir_read(&finfo, wanted, handle);
+ if (APR_STATUS_IS_ENOENT(apr_err))
+ break;
+ else if (apr_err)
+ {
+ return svn_error_wrap_apr(apr_err,
+ _("Can't read directory entry in '%s'"),
+ svn_dirent_local_style(dirname, pool));
+ }
+
+ if (finfo.filetype == APR_DIR)
+ {
+ if (finfo.name[0] == '.'
+ && (finfo.name[1] == '\0'
+ || (finfo.name[1] == '.' && finfo.name[2] == '\0')))
+ /* skip "." and ".." */
+ continue;
+
+ /* some other directory. recurse. it will be passed to the
+ callback inside the recursion. */
+ SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
+ subpool));
+ full_path = svn_dirent_join(dirname, name_utf8, subpool);
+ SVN_ERR(svn_io_dir_walk2(full_path,
+ wanted,
+ walk_func,
+ walk_baton,
+ subpool));
+ }
+ else if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK)
+ {
+ /* some other directory. pass it to the callback. */
+ SVN_ERR(entry_name_to_utf8(&name_utf8, finfo.name, dirname,
+ subpool));
+ full_path = svn_dirent_join(dirname, name_utf8, subpool);
+ SVN_ERR((*walk_func)(walk_baton,
+ full_path,
+ &finfo,
+ subpool));
+ }
+ /* else:
+ Some other type of file; skip it for now. We've reserved the
+ right to expand our coverage here in the future, though,
+ without revving this API.
+ */
+ }
+
+ svn_pool_destroy(subpool);
+
+ apr_err = apr_dir_close(handle);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Error closing directory '%s'"),
+ svn_dirent_local_style(dirname, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/**
+ * Determine if a directory is empty or not.
+ * @param Return APR_SUCCESS if the dir is empty, else APR_ENOTEMPTY if not.
+ * @param path The directory.
+ * @param pool Used for temporary allocation.
+ * @remark If path is not a directory, or some other error occurs,
+ * then return the appropriate apr status code.
+ *
+ * (This function is written in APR style, in anticipation of
+ * perhaps someday being moved to APR as 'apr_dir_is_empty'.)
+ */
+static apr_status_t
+dir_is_empty(const char *dir, apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ apr_dir_t *dir_handle;
+ apr_finfo_t finfo;
+ apr_status_t retval = APR_SUCCESS;
+
+ /* APR doesn't like "" directories */
+ if (dir[0] == '\0')
+ dir = ".";
+
+ apr_err = apr_dir_open(&dir_handle, dir, pool);
+ if (apr_err != APR_SUCCESS)
+ return apr_err;
+
+ for (apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle);
+ apr_err == APR_SUCCESS;
+ apr_err = apr_dir_read(&finfo, APR_FINFO_NAME, dir_handle))
+ {
+ /* Ignore entries for this dir and its parent, robustly.
+ (APR promises that they'll come first, so technically
+ this guard could be moved outside the loop. But Ryan Bloom
+ says he doesn't believe it, and I believe him. */
+ if (! (finfo.name[0] == '.'
+ && (finfo.name[1] == '\0'
+ || (finfo.name[1] == '.' && finfo.name[2] == '\0'))))
+ {
+ retval = APR_ENOTEMPTY;
+ break;
+ }
+ }
+
+ /* Make sure we broke out of the loop for the right reason. */
+ if (apr_err && ! APR_STATUS_IS_ENOENT(apr_err))
+ return apr_err;
+
+ apr_err = apr_dir_close(dir_handle);
+ if (apr_err != APR_SUCCESS)
+ return apr_err;
+
+ return retval;
+}
+
+
+svn_error_t *
+svn_io_dir_empty(svn_boolean_t *is_empty_p,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ const char *path_apr;
+
+ SVN_ERR(cstring_from_utf8(&path_apr, path, pool));
+
+ status = dir_is_empty(path_apr, pool);
+
+ if (!status)
+ *is_empty_p = TRUE;
+ else if (APR_STATUS_IS_ENOTEMPTY(status))
+ *is_empty_p = FALSE;
+ else
+ return svn_error_wrap_apr(status, _("Can't check directory '%s'"),
+ svn_dirent_local_style(path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Version/format files ***/
+
+svn_error_t *
+svn_io_write_version_file(const char *path,
+ int version,
+ apr_pool_t *pool)
+{
+ const char *path_tmp;
+ const char *format_contents = apr_psprintf(pool, "%d\n", version);
+
+ SVN_ERR_ASSERT(version >= 0);
+
+ SVN_ERR(svn_io_write_unique(&path_tmp,
+ svn_dirent_dirname(path, pool),
+ format_contents, strlen(format_contents),
+ svn_io_file_del_none, pool));
+
+#if defined(WIN32) || defined(__OS2__)
+ /* make the destination writable, but only on Windows, because
+ Windows does not let us replace read-only files. */
+ SVN_ERR(svn_io_set_file_read_write(path, TRUE, pool));
+#endif /* WIN32 || __OS2__ */
+
+ /* rename the temp file as the real destination */
+ SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
+
+ /* And finally remove the perms to make it read only */
+ return svn_io_set_file_read_only(path, FALSE, pool);
+}
+
+
+svn_error_t *
+svn_io_read_version_file(int *version,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_file_t *format_file;
+ char buf[80];
+ apr_size_t len;
+ svn_error_t *err;
+
+ /* Read a chunk of data from PATH */
+ SVN_ERR(svn_io_file_open(&format_file, path, APR_READ,
+ APR_OS_DEFAULT, pool));
+ len = sizeof(buf);
+ err = svn_io_file_read(format_file, buf, &len, pool);
+
+ /* Close the file. */
+ SVN_ERR(svn_error_compose_create(err,
+ svn_io_file_close(format_file, pool)));
+
+ /* If there was no data in PATH, return an error. */
+ if (len == 0)
+ return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
+ _("Reading '%s'"),
+ svn_dirent_local_style(path, pool));
+
+ /* Check that the first line contains only digits. */
+ {
+ apr_size_t i;
+
+ for (i = 0; i < len; ++i)
+ {
+ char c = buf[i];
+
+ if (i > 0 && (c == '\r' || c == '\n'))
+ {
+ buf[i] = '\0';
+ break;
+ }
+ if (! svn_ctype_isdigit(c))
+ return svn_error_createf
+ (SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("First line of '%s' contains non-digit"),
+ svn_dirent_local_style(path, pool));
+ }
+ }
+
+ /* Convert to integer. */
+ SVN_ERR(svn_cstring_atoi(version, buf));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Do a byte-for-byte comparison of FILE1 and FILE2. */
+static svn_error_t *
+contents_identical_p(svn_boolean_t *identical_p,
+ const char *file1,
+ const char *file2,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_size_t bytes_read1, bytes_read2;
+ char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
+ char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
+ apr_file_t *file1_h;
+ apr_file_t *file2_h;
+ svn_boolean_t eof1 = FALSE;
+ svn_boolean_t eof2 = FALSE;
+
+ SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
+ pool));
+
+ err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
+ pool);
+
+ if (err)
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_io_file_close(file1_h, pool)));
+
+ *identical_p = TRUE; /* assume TRUE, until disproved below */
+ while (!err && !eof1 && !eof2)
+ {
+ err = svn_io_file_read_full2(file1_h, buf1,
+ SVN__STREAM_CHUNK_SIZE, &bytes_read1,
+ &eof1, pool);
+ if (err)
+ break;
+
+ err = svn_io_file_read_full2(file2_h, buf2,
+ SVN__STREAM_CHUNK_SIZE, &bytes_read2,
+ &eof2, pool);
+ if (err)
+ break;
+
+ if ((bytes_read1 != bytes_read2) || memcmp(buf1, buf2, bytes_read1))
+ {
+ *identical_p = FALSE;
+ break;
+ }
+ }
+
+ /* Special case: one file being a prefix of the other and the shorter
+ * file's size is a multiple of SVN__STREAM_CHUNK_SIZE. */
+ if (!err && (eof1 != eof2))
+ *identical_p = FALSE;
+
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_error_compose_create(svn_io_file_close(file1_h, pool),
+ svn_io_file_close(file2_h, pool))));
+}
+
+
+
+/* Do a byte-for-byte comparison of FILE1, FILE2 and FILE3. */
+static svn_error_t *
+contents_three_identical_p(svn_boolean_t *identical_p12,
+ svn_boolean_t *identical_p23,
+ svn_boolean_t *identical_p13,
+ const char *file1,
+ const char *file2,
+ const char *file3,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ apr_size_t bytes_read1, bytes_read2, bytes_read3;
+ char *buf1 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
+ char *buf2 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
+ char *buf3 = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
+ apr_file_t *file1_h;
+ apr_file_t *file2_h;
+ apr_file_t *file3_h;
+ svn_boolean_t eof1 = FALSE;
+ svn_boolean_t eof2 = FALSE;
+ svn_boolean_t eof3 = FALSE;
+ svn_boolean_t read_1, read_2, read_3;
+
+ SVN_ERR(svn_io_file_open(&file1_h, file1, APR_READ, APR_OS_DEFAULT,
+ scratch_pool));
+
+ err = svn_io_file_open(&file2_h, file2, APR_READ, APR_OS_DEFAULT,
+ scratch_pool);
+
+ if (err)
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_io_file_close(file1_h, scratch_pool)));
+
+ err = svn_io_file_open(&file3_h, file3, APR_READ, APR_OS_DEFAULT,
+ scratch_pool);
+
+ if (err)
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_error_compose_create(svn_io_file_close(file1_h,
+ scratch_pool),
+ svn_io_file_close(file2_h,
+ scratch_pool))));
+
+ /* assume TRUE, until disproved below */
+ *identical_p12 = *identical_p23 = *identical_p13 = TRUE;
+ /* We need to read as long as no error occurs, and as long as one of the
+ * flags could still change due to a read operation */
+ while (!err
+ && ((*identical_p12 && !eof1 && !eof2)
+ || (*identical_p23 && !eof2 && !eof3)
+ || (*identical_p13 && !eof1 && !eof3)))
+ {
+ read_1 = read_2 = read_3 = FALSE;
+
+ /* As long as a file is not at the end yet, and it is still
+ * potentially identical to another file, we read the next chunk.*/
+ if (!eof1 && (identical_p12 || identical_p13))
+ {
+ err = svn_io_file_read_full2(file1_h, buf1,
+ SVN__STREAM_CHUNK_SIZE, &bytes_read1,
+ &eof1, scratch_pool);
+ if (err)
+ break;
+ read_1 = TRUE;
+ }
+
+ if (!eof2 && (identical_p12 || identical_p23))
+ {
+ err = svn_io_file_read_full2(file2_h, buf2,
+ SVN__STREAM_CHUNK_SIZE, &bytes_read2,
+ &eof2, scratch_pool);
+ if (err)
+ break;
+ read_2 = TRUE;
+ }
+
+ if (!eof3 && (identical_p13 || identical_p23))
+ {
+ err = svn_io_file_read_full2(file3_h, buf3,
+ SVN__STREAM_CHUNK_SIZE, &bytes_read3,
+ &eof3, scratch_pool);
+ if (err)
+ break;
+ read_3 = TRUE;
+ }
+
+ /* If the files are still marked identical, and at least one of them
+ * is not at the end of file, we check whether they differ, and set
+ * their flag to false then. */
+ if (*identical_p12
+ && (read_1 || read_2)
+ && ((eof1 != eof2)
+ || (bytes_read1 != bytes_read2)
+ || memcmp(buf1, buf2, bytes_read1)))
+ {
+ *identical_p12 = FALSE;
+ }
+
+ if (*identical_p23
+ && (read_2 || read_3)
+ && ((eof2 != eof3)
+ || (bytes_read2 != bytes_read3)
+ || memcmp(buf2, buf3, bytes_read2)))
+ {
+ *identical_p23 = FALSE;
+ }
+
+ if (*identical_p13
+ && (read_1 || read_3)
+ && ((eof1 != eof3)
+ || (bytes_read1 != bytes_read3)
+ || memcmp(buf1, buf3, bytes_read3)))
+ {
+ *identical_p13 = FALSE;
+ }
+ }
+
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_error_compose_create(
+ svn_io_file_close(file1_h, scratch_pool),
+ svn_error_compose_create(
+ svn_io_file_close(file2_h, scratch_pool),
+ svn_io_file_close(file3_h, scratch_pool)))));
+}
+
+
+
+svn_error_t *
+svn_io_files_contents_same_p(svn_boolean_t *same,
+ const char *file1,
+ const char *file2,
+ apr_pool_t *pool)
+{
+ svn_boolean_t q;
+
+ SVN_ERR(svn_io_filesizes_different_p(&q, file1, file2, pool));
+
+ if (q)
+ {
+ *same = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(contents_identical_p(&q, file1, file2, pool));
+
+ if (q)
+ *same = TRUE;
+ else
+ *same = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_files_contents_three_same_p(svn_boolean_t *same12,
+ svn_boolean_t *same23,
+ svn_boolean_t *same13,
+ const char *file1,
+ const char *file2,
+ const char *file3,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t diff_size12, diff_size23, diff_size13;
+
+ SVN_ERR(svn_io_filesizes_three_different_p(&diff_size12,
+ &diff_size23,
+ &diff_size13,
+ file1,
+ file2,
+ file3,
+ scratch_pool));
+
+ if (diff_size12 && diff_size23 && diff_size13)
+ {
+ *same12 = *same23 = *same13 = FALSE;
+ }
+ else if (diff_size12 && diff_size23)
+ {
+ *same12 = *same23 = FALSE;
+ SVN_ERR(contents_identical_p(same13, file1, file3, scratch_pool));
+ }
+ else if (diff_size23 && diff_size13)
+ {
+ *same23 = *same13 = FALSE;
+ SVN_ERR(contents_identical_p(same12, file1, file2, scratch_pool));
+ }
+ else if (diff_size12 && diff_size13)
+ {
+ *same12 = *same13 = FALSE;
+ SVN_ERR(contents_identical_p(same23, file2, file3, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR_ASSERT(!diff_size12 && !diff_size23 && !diff_size13);
+ SVN_ERR(contents_three_identical_p(same12, same23, same13,
+ file1, file2, file3,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#ifdef WIN32
+/* Counter value of file_mktemp request (used in a threadsafe way), to make
+ sure that a single process normally never generates the same tempname
+ twice */
+static volatile apr_uint32_t tempname_counter = 0;
+#endif
+
+/* Creates a new temporary file in DIRECTORY with apr flags FLAGS.
+ Set *NEW_FILE to the file handle and *NEW_FILE_NAME to its name.
+ Perform temporary allocations in SCRATCH_POOL and the result in
+ RESULT_POOL. */
+static svn_error_t *
+temp_file_create(apr_file_t **new_file,
+ const char **new_file_name,
+ const char *directory,
+ apr_int32_t flags,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+#ifndef WIN32
+ const char *templ = svn_dirent_join(directory, "svn-XXXXXX", scratch_pool);
+ const char *templ_apr;
+ apr_status_t status;
+
+ SVN_ERR(svn_path_cstring_from_utf8(&templ_apr, templ, scratch_pool));
+
+ /* ### svn_path_cstring_from_utf8() guarantees to make a copy of the
+ data available in POOL and we need a non-const pointer here,
+ as apr changes the template to return the new filename. */
+ status = apr_file_mktemp(new_file, (char *)templ_apr, flags, result_pool);
+
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't create temporary file from "
+ "template '%s'"), templ);
+
+ /* Translate the returned path back to utf-8 before returning it */
+ return svn_error_trace(svn_path_cstring_to_utf8(new_file_name,
+ templ_apr,
+ result_pool));
+#else
+ /* The Windows implementation of apr_file_mktemp doesn't handle access
+ denied errors correctly. Therefore we implement our own temp file
+ creation function here. */
+
+ /* ### Most of this is borrowed from the svn_io_open_uniquely_named(),
+ ### the function we used before. But we try to guess a more unique
+ ### name before trying if it exists. */
+
+ /* Offset by some time value and a unique request nr to make the number
+ +- unique for both this process and on the computer */
+ int baseNr = (GetTickCount() << 11) + 7 * svn_atomic_inc(&tempname_counter)
+ + GetCurrentProcessId();
+ int i;
+
+ /* ### Maybe use an iterpool? */
+ for (i = 0; i <= 99999; i++)
+ {
+ apr_uint32_t unique_nr;
+ const char *unique_name;
+ const char *unique_name_apr;
+ apr_file_t *try_file;
+ apr_status_t apr_err;
+
+ /* Generate a number that should be unique for this application and
+ usually for the entire computer to reduce the number of cycles
+ through this loop. (A bit of calculation is much cheaper then
+ disk io) */
+ unique_nr = baseNr + 3 * i;
+
+ unique_name = svn_dirent_join(directory,
+ apr_psprintf(scratch_pool, "svn-%X",
+ unique_nr),
+ scratch_pool);
+
+ SVN_ERR(cstring_from_utf8(&unique_name_apr, unique_name, scratch_pool));
+
+ apr_err = file_open(&try_file, unique_name_apr, flags,
+ APR_OS_DEFAULT, FALSE, scratch_pool);
+
+ if (APR_STATUS_IS_EEXIST(apr_err))
+ continue;
+ else if (apr_err)
+ {
+ /* On Win32, CreateFile fails with an "Access Denied" error
+ code, rather than "File Already Exists", if the colliding
+ name belongs to a directory. */
+
+ if (APR_STATUS_IS_EACCES(apr_err))
+ {
+ apr_finfo_t finfo;
+ apr_status_t apr_err_2 = apr_stat(&finfo, unique_name_apr,
+ APR_FINFO_TYPE, scratch_pool);
+
+ if (!apr_err_2 && finfo.filetype == APR_DIR)
+ continue;
+
+ apr_err_2 = APR_TO_OS_ERROR(apr_err);
+
+ if (apr_err_2 == ERROR_ACCESS_DENIED ||
+ apr_err_2 == ERROR_SHARING_VIOLATION)
+ {
+ /* The file is in use by another process or is hidden;
+ create a new name, but don't do this 99999 times in
+ case the folder is not writable */
+ i += 797;
+ continue;
+ }
+
+ /* Else fall through and return the original error. */
+ }
+
+ return svn_error_wrap_apr(apr_err, _("Can't open '%s'"),
+ svn_dirent_local_style(unique_name,
+ scratch_pool));
+ }
+ else
+ {
+ /* Move file to the right pool */
+ apr_err = apr_file_setaside(new_file, try_file, result_pool);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't set aside '%s'"),
+ svn_dirent_local_style(unique_name,
+ scratch_pool));
+
+ *new_file_name = apr_pstrdup(result_pool, unique_name);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
+ NULL,
+ _("Unable to make name in '%s'"),
+ svn_dirent_local_style(directory, scratch_pool));
+#endif
+}
+
+/* Wrapper for apr_file_name_get(), passing out a UTF8-encoded filename. */
+svn_error_t *
+svn_io_file_name_get(const char **filename,
+ apr_file_t *file,
+ apr_pool_t *pool)
+{
+ const char *fname_apr;
+ apr_status_t status;
+
+ status = apr_file_name_get(&fname_apr, file);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't get file name"));
+
+ if (fname_apr)
+ SVN_ERR(svn_path_cstring_to_utf8(filename, fname_apr, pool));
+ else
+ *filename = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_io_open_unique_file3(apr_file_t **file,
+ const char **unique_path,
+ const char *dirpath,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *tempfile;
+ const char *tempname;
+ struct temp_file_cleanup_s *baton = NULL;
+ apr_int32_t flags = (APR_READ | APR_WRITE | APR_CREATE | APR_EXCL |
+ APR_BUFFERED | APR_BINARY);
+#if !defined(WIN32) && !defined(__OS2__)
+ apr_fileperms_t perms;
+ svn_boolean_t using_system_temp_dir = FALSE;
+#endif
+
+ SVN_ERR_ASSERT(file || unique_path);
+ if (file)
+ *file = NULL;
+ if (unique_path)
+ *unique_path = NULL;
+
+ if (dirpath == NULL)
+ {
+#if !defined(WIN32) && !defined(__OS2__)
+ using_system_temp_dir = TRUE;
+#endif
+ SVN_ERR(svn_io_temp_dir(&dirpath, scratch_pool));
+ }
+
+ switch (delete_when)
+ {
+ case svn_io_file_del_on_pool_cleanup:
+ baton = apr_palloc(result_pool, sizeof(*baton));
+ baton->pool = result_pool;
+ baton->fname_apr = NULL;
+
+ /* Because cleanups are run LIFO, we need to make sure to register
+ our cleanup before the apr_file_close cleanup:
+
+ On Windows, you can't remove an open file.
+ */
+ apr_pool_cleanup_register(result_pool, baton,
+ temp_file_plain_cleanup_handler,
+ temp_file_child_cleanup_handler);
+
+ break;
+ case svn_io_file_del_on_close:
+ flags |= APR_DELONCLOSE;
+ break;
+ default:
+ break;
+ }
+
+ SVN_ERR(temp_file_create(&tempfile, &tempname, dirpath, flags,
+ result_pool, scratch_pool));
+
+#if !defined(WIN32) && !defined(__OS2__)
+ /* apr_file_mktemp() creates files with mode 0600.
+ * This is appropriate if we're using a system temp dir since we don't
+ * want to leak sensitive data into temp files other users can read.
+ * If we're not using a system temp dir we're probably using the
+ * .svn/tmp area and it's likely that the tempfile will end up being
+ * copied or renamed into the working copy.
+ * This would cause working files having mode 0600 while users might
+ * expect to see 0644 or 0664. So we tweak perms of the tempfile in this
+ * case, but only if the umask allows it. */
+ if (!using_system_temp_dir)
+ {
+ SVN_ERR(merge_default_file_perms(tempfile, &perms, scratch_pool));
+ SVN_ERR(file_perms_set2(tempfile, perms, scratch_pool));
+ }
+#endif
+
+ if (file)
+ *file = tempfile;
+ else
+ SVN_ERR(svn_io_file_close(tempfile, scratch_pool));
+
+ if (unique_path)
+ *unique_path = tempname; /* Was allocated in result_pool */
+
+ if (baton)
+ SVN_ERR(cstring_from_utf8(&baton->fname_apr, tempname, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_io_file_readline(apr_file_t *file,
+ svn_stringbuf_t **stringbuf,
+ const char **eol,
+ svn_boolean_t *eof,
+ apr_size_t max_len,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *str;
+ const char *eol_str;
+ apr_size_t numbytes;
+ char c;
+ apr_size_t len;
+ svn_boolean_t found_eof;
+
+ str = svn_stringbuf_create_ensure(80, result_pool);
+
+ /* Read bytes into STR up to and including, but not storing,
+ * the next EOL sequence. */
+ eol_str = NULL;
+ numbytes = 1;
+ len = 0;
+ found_eof = FALSE;
+ while (!found_eof)
+ {
+ if (len < max_len)
+ SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
+ &found_eof, scratch_pool));
+ len++;
+ if (numbytes != 1 || len > max_len)
+ {
+ found_eof = TRUE;
+ break;
+ }
+
+ if (c == '\n')
+ {
+ eol_str = "\n";
+ }
+ else if (c == '\r')
+ {
+ eol_str = "\r";
+
+ if (!found_eof && len < max_len)
+ {
+ apr_off_t pos;
+
+ /* Check for "\r\n" by peeking at the next byte. */
+ pos = 0;
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
+ SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
+ &found_eof, scratch_pool));
+ if (numbytes == 1 && c == '\n')
+ {
+ eol_str = "\r\n";
+ len++;
+ }
+ else
+ {
+ /* Pretend we never peeked. */
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
+ found_eof = FALSE;
+ numbytes = 1;
+ }
+ }
+ }
+ else
+ svn_stringbuf_appendbyte(str, c);
+
+ if (eol_str)
+ break;
+ }
+
+ if (eol)
+ *eol = eol_str;
+ if (eof)
+ *eof = found_eof;
+ *stringbuf = str;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/iter.c b/contrib/subversion/subversion/libsvn_subr/iter.c
new file mode 100644
index 0000000..45ec489
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/iter.c
@@ -0,0 +1,216 @@
+/* iter.c : iteration drivers
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include "svn_iter.h"
+#include "svn_pools.h"
+#include "private/svn_dep_compat.h"
+
+#include "svn_error_codes.h"
+
+static svn_error_t internal_break_error =
+ {
+ SVN_ERR_ITER_BREAK, /* APR status */
+ NULL, /* message */
+ NULL, /* child error */
+ NULL, /* pool */
+ __FILE__, /* file name */
+ __LINE__ /* line number */
+ };
+
+#if APR_VERSION_AT_LEAST(1, 4, 0)
+struct hash_do_baton
+{
+ void *baton;
+ svn_iter_apr_hash_cb_t func;
+ svn_error_t *err;
+ apr_pool_t *iterpool;
+};
+
+static
+int hash_do_callback(void *baton,
+ const void *key,
+ apr_ssize_t klen,
+ const void *value)
+{
+ struct hash_do_baton *hdb = baton;
+
+ svn_pool_clear(hdb->iterpool);
+ hdb->err = (*hdb->func)(hdb->baton, key, klen, (void *)value, hdb->iterpool);
+
+ return hdb->err == SVN_NO_ERROR;
+}
+#endif
+
+svn_error_t *
+svn_iter_apr_hash(svn_boolean_t *completed,
+ apr_hash_t *hash,
+ svn_iter_apr_hash_cb_t func,
+ void *baton,
+ apr_pool_t *pool)
+{
+#if APR_VERSION_AT_LEAST(1, 4, 0)
+ struct hash_do_baton hdb;
+ svn_boolean_t error_received;
+
+ hdb.func = func;
+ hdb.baton = baton;
+ hdb.iterpool = svn_pool_create(pool);
+
+ error_received = !apr_hash_do(hash_do_callback, &hdb, hash);
+
+ svn_pool_destroy(hdb.iterpool);
+
+ if (completed)
+ *completed = !error_received;
+
+ if (!error_received)
+ return SVN_NO_ERROR;
+
+ if (hdb.err->apr_err == SVN_ERR_ITER_BREAK
+ && hdb.err != &internal_break_error)
+ {
+ /* Errors - except those created by svn_iter_break() -
+ need to be cleared when not further propagated. */
+ svn_error_clear(hdb.err);
+
+ hdb.err = SVN_NO_ERROR;
+ }
+
+ return hdb.err;
+#else
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, hash);
+ ! err && hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ apr_ssize_t len;
+
+ svn_pool_clear(iterpool);
+
+ apr_hash_this(hi, &key, &len, &val);
+ err = (*func)(baton, key, len, val, iterpool);
+ }
+
+ if (completed)
+ *completed = ! err;
+
+ if (err && err->apr_err == SVN_ERR_ITER_BREAK)
+ {
+ if (err != &internal_break_error)
+ /* Errors - except those created by svn_iter_break() -
+ need to be cleared when not further propagated. */
+ svn_error_clear(err);
+
+ err = SVN_NO_ERROR;
+ }
+
+ /* Clear iterpool, because callers may clear the error but have no way
+ to clear the iterpool with potentially lots of allocated memory */
+ svn_pool_destroy(iterpool);
+
+ return err;
+#endif
+}
+
+svn_error_t *
+svn_iter_apr_array(svn_boolean_t *completed,
+ const apr_array_header_t *array,
+ svn_iter_apr_array_cb_t func,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ for (i = 0; (! err) && i < array->nelts; ++i)
+ {
+ void *item = array->elts + array->elt_size*i;
+
+ svn_pool_clear(iterpool);
+
+ err = (*func)(baton, item, iterpool);
+ }
+
+ if (completed)
+ *completed = ! err;
+
+ if (err && err->apr_err == SVN_ERR_ITER_BREAK)
+ {
+ if (err != &internal_break_error)
+ /* Errors - except those created by svn_iter_break() -
+ need to be cleared when not further propagated. */
+ svn_error_clear(err);
+
+ err = SVN_NO_ERROR;
+ }
+
+ /* Clear iterpool, because callers may clear the error but have no way
+ to clear the iterpool with potentially lots of allocated memory */
+ svn_pool_destroy(iterpool);
+
+ return err;
+}
+
+/* Note: Although this is a "__" function, it is in the public ABI, so
+ * we can never remove it or change its signature. */
+svn_error_t *
+svn_iter__break(void)
+{
+ return &internal_break_error;
+}
+
+/* Note about the type casts: apr_hash_this() does not expect a const hash
+ * index pointer even though it does not modify the hash index. In
+ * Subversion we're trying to be const-correct, so these functions all take
+ * a const hash index and we cast away the const when passing it down to
+ * APR. (A compiler may warn about casting away 'const', but at least this
+ * cast is explicit and gathered in one place.) */
+
+const void *svn__apr_hash_index_key(const apr_hash_index_t *hi)
+{
+ const void *key;
+
+ apr_hash_this((apr_hash_index_t *)hi, &key, NULL, NULL);
+ return key;
+}
+
+apr_ssize_t svn__apr_hash_index_klen(const apr_hash_index_t *hi)
+{
+ apr_ssize_t klen;
+
+ apr_hash_this((apr_hash_index_t *)hi, NULL, &klen, NULL);
+ return klen;
+}
+
+void *svn__apr_hash_index_val(const apr_hash_index_t *hi)
+{
+ void *val;
+
+ apr_hash_this((apr_hash_index_t *)hi, NULL, NULL, &val);
+ return val;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/lock.c b/contrib/subversion/subversion/libsvn_subr/lock.c
new file mode 100644
index 0000000..71dd8c7
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/lock.c
@@ -0,0 +1,60 @@
+/*
+ * lock.c: routines for svn_lock_t objects.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_strings.h>
+
+#include "svn_types.h"
+
+
+/*** Code. ***/
+
+svn_lock_t *
+svn_lock_create(apr_pool_t *pool)
+{
+ return apr_pcalloc(pool, sizeof(svn_lock_t));
+}
+
+svn_lock_t *
+svn_lock_dup(const svn_lock_t *lock, apr_pool_t *pool)
+{
+ svn_lock_t *new_l;
+
+ if (lock == NULL)
+ return NULL;
+
+ new_l = apr_palloc(pool, sizeof(*new_l));
+ *new_l = *lock;
+
+ new_l->path = apr_pstrdup(pool, new_l->path);
+ new_l->token = apr_pstrdup(pool, new_l->token);
+ new_l->owner = apr_pstrdup(pool, new_l->owner);
+ new_l->comment = apr_pstrdup(pool, new_l->comment);
+
+ return new_l;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/log.c b/contrib/subversion/subversion/libsvn_subr/log.c
new file mode 100644
index 0000000..9e0b22a
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/log.c
@@ -0,0 +1,396 @@
+/*
+ * log.c : Functions for logging Subversion operations
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+
+#include <stdarg.h>
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_strings.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_mergeinfo.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+
+#include "private/svn_log.h"
+
+
+static const char *
+log_depth(svn_depth_t depth, apr_pool_t *pool)
+{
+ if (depth == svn_depth_unknown)
+ return "";
+ return apr_pstrcat(pool, " depth=", svn_depth_to_word(depth), (char *)NULL);
+}
+
+static const char *
+log_include_merged_revisions(svn_boolean_t include_merged_revisions)
+{
+ if (include_merged_revisions)
+ return " include-merged-revisions";
+ return "";
+}
+
+
+const char *
+svn_log__reparent(const char *path, apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "reparent %s", svn_path_uri_encode(path, pool));
+
+}
+
+const char *
+svn_log__change_rev_prop(svn_revnum_t rev, const char *name, apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "change-rev-prop r%ld %s", rev,
+ svn_path_uri_encode(name, pool));
+}
+
+const char *
+svn_log__rev_proplist(svn_revnum_t rev, apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "rev-proplist r%ld", rev);
+}
+
+const char *
+svn_log__rev_prop(svn_revnum_t rev, const char *name, apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "rev-prop r%ld %s", rev,
+ svn_path_uri_encode(name, pool));
+}
+
+const char *
+svn_log__commit(svn_revnum_t rev, apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "commit r%ld", rev);
+}
+
+const char *
+svn_log__get_file(const char *path, svn_revnum_t rev,
+ svn_boolean_t want_contents, svn_boolean_t want_props,
+ apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "get-file %s r%ld%s%s",
+ svn_path_uri_encode(path, pool), rev,
+ want_contents ? " text" : "",
+ want_props ? " props" : "");
+}
+
+const char *
+svn_log__get_dir(const char *path, svn_revnum_t rev,
+ svn_boolean_t want_contents, svn_boolean_t want_props,
+ apr_uint64_t dirent_fields,
+ apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "get-dir %s r%ld%s%s",
+ svn_path_uri_encode(path, pool), rev,
+ want_contents ? " text" : "",
+ want_props ? " props" : "");
+}
+
+const char *
+svn_log__get_mergeinfo(const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool);
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ svn_pool_clear(iterpool);
+ if (i != 0)
+ svn_stringbuf_appendcstr(space_separated_paths, " ");
+ svn_stringbuf_appendcstr(space_separated_paths,
+ svn_path_uri_encode(path, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return apr_psprintf(pool, "get-mergeinfo (%s) %s%s",
+ space_separated_paths->data,
+ svn_inheritance_to_word(inherit),
+ include_descendants ? " include-descendants" : "");
+}
+
+const char *
+svn_log__checkout(const char *path, svn_revnum_t rev, svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "checkout-or-export %s r%ld%s",
+ svn_path_uri_encode(path, pool), rev,
+ log_depth(depth, pool));
+}
+
+const char *
+svn_log__update(const char *path, svn_revnum_t rev, svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "update %s r%ld%s%s",
+ svn_path_uri_encode(path, pool), rev,
+ log_depth(depth, pool),
+ (send_copyfrom_args
+ ? " send-copyfrom-args"
+ : ""));
+}
+
+const char *
+svn_log__switch(const char *path, const char *dst_path, svn_revnum_t revnum,
+ svn_depth_t depth, apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "switch %s %s@%ld%s",
+ svn_path_uri_encode(path, pool),
+ svn_path_uri_encode(dst_path, pool), revnum,
+ log_depth(depth, pool));
+}
+
+const char *
+svn_log__status(const char *path, svn_revnum_t rev, svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "status %s r%ld%s",
+ svn_path_uri_encode(path, pool), rev,
+ log_depth(depth, pool));
+}
+
+const char *
+svn_log__diff(const char *path, svn_revnum_t from_revnum,
+ const char *dst_path, svn_revnum_t revnum,
+ svn_depth_t depth, svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool)
+{
+ const char *log_ignore_ancestry = (ignore_ancestry
+ ? " ignore-ancestry"
+ : "");
+ if (strcmp(path, dst_path) == 0)
+ return apr_psprintf(pool, "diff %s r%ld:%ld%s%s",
+ svn_path_uri_encode(path, pool), from_revnum, revnum,
+ log_depth(depth, pool), log_ignore_ancestry);
+ return apr_psprintf(pool, "diff %s@%ld %s@%ld%s%s",
+ svn_path_uri_encode(path, pool), from_revnum,
+ svn_path_uri_encode(dst_path, pool), revnum,
+ log_depth(depth, pool), log_ignore_ancestry);
+}
+
+const char *
+svn_log__log(const apr_array_header_t *paths,
+ svn_revnum_t start, svn_revnum_t end,
+ int limit, svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops, apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool);
+ svn_stringbuf_t *options = svn_stringbuf_create_empty(pool);
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ svn_pool_clear(iterpool);
+ if (i != 0)
+ svn_stringbuf_appendcstr(space_separated_paths, " ");
+ svn_stringbuf_appendcstr(space_separated_paths,
+ svn_path_uri_encode(path, iterpool));
+ }
+
+ if (limit)
+ {
+ const char *tmp = apr_psprintf(pool, " limit=%d", limit);
+ svn_stringbuf_appendcstr(options, tmp);
+ }
+ if (discover_changed_paths)
+ svn_stringbuf_appendcstr(options, " discover-changed-paths");
+ if (strict_node_history)
+ svn_stringbuf_appendcstr(options, " strict");
+ if (include_merged_revisions)
+ svn_stringbuf_appendcstr(options,
+ log_include_merged_revisions(include_merged_revisions));
+ if (revprops == NULL)
+ svn_stringbuf_appendcstr(options, " revprops=all");
+ else if (revprops->nelts > 0)
+ {
+ svn_stringbuf_appendcstr(options, " revprops=(");
+ for (i = 0; i < revprops->nelts; i++)
+ {
+ const char *name = APR_ARRAY_IDX(revprops, i, const char *);
+ svn_pool_clear(iterpool);
+ if (i != 0)
+ svn_stringbuf_appendcstr(options, " ");
+ svn_stringbuf_appendcstr(options, svn_path_uri_encode(name,
+ iterpool));
+ }
+ svn_stringbuf_appendcstr(options, ")");
+ }
+ svn_pool_destroy(iterpool);
+ return apr_psprintf(pool, "log (%s) r%ld:%ld%s",
+ space_separated_paths->data, start, end,
+ options->data);
+}
+
+const char *
+svn_log__get_locations(const char *path, svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool)
+{
+ const svn_revnum_t *revision_ptr, *revision_ptr_start, *revision_ptr_end;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_stringbuf_t *space_separated_revnums = svn_stringbuf_create_empty(pool);
+
+ revision_ptr_start = (const svn_revnum_t *)location_revisions->elts;
+ revision_ptr = revision_ptr_start;
+ revision_ptr_end = revision_ptr + location_revisions->nelts;
+ while (revision_ptr < revision_ptr_end)
+ {
+ svn_pool_clear(iterpool);
+ if (revision_ptr != revision_ptr_start)
+ svn_stringbuf_appendcstr(space_separated_revnums, " ");
+ svn_stringbuf_appendcstr(space_separated_revnums,
+ apr_psprintf(iterpool, "%ld", *revision_ptr));
+ ++revision_ptr;
+ }
+ svn_pool_destroy(iterpool);
+
+ return apr_psprintf(pool, "get-locations %s@%ld (%s)",
+ svn_path_uri_encode(path, pool),
+ peg_revision, space_separated_revnums->data);
+}
+
+const char *
+svn_log__get_location_segments(const char *path, svn_revnum_t peg_revision,
+ svn_revnum_t start, svn_revnum_t end,
+ apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "get-location-segments %s@%ld r%ld:%ld",
+ svn_path_uri_encode(path, pool),
+ peg_revision, start, end);
+}
+
+const char *
+svn_log__get_file_revs(const char *path, svn_revnum_t start, svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "get-file-revs %s r%ld:%ld%s",
+ svn_path_uri_encode(path, pool), start, end,
+ log_include_merged_revisions(include_merged_revisions));
+}
+
+const char *
+svn_log__lock(const apr_array_header_t *paths,
+ svn_boolean_t steal, apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool);
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ svn_pool_clear(iterpool);
+ if (i != 0)
+ svn_stringbuf_appendcstr(space_separated_paths, " ");
+ svn_stringbuf_appendcstr(space_separated_paths,
+ svn_path_uri_encode(path, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return apr_psprintf(pool, "lock (%s)%s", space_separated_paths->data,
+ steal ? " steal" : "");
+}
+
+const char *
+svn_log__unlock(const apr_array_header_t *paths,
+ svn_boolean_t break_lock, apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool);
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+ svn_pool_clear(iterpool);
+ if (i != 0)
+ svn_stringbuf_appendcstr(space_separated_paths, " ");
+ svn_stringbuf_appendcstr(space_separated_paths,
+ svn_path_uri_encode(path, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return apr_psprintf(pool, "unlock (%s)%s", space_separated_paths->data,
+ break_lock ? " break" : "");
+}
+
+const char *
+svn_log__lock_one_path(const char *path, svn_boolean_t steal,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(path));
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ return svn_log__lock(paths, steal, pool);
+}
+
+const char *
+svn_log__unlock_one_path(const char *path, svn_boolean_t break_lock,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(path));
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ return svn_log__unlock(paths, break_lock, pool);
+}
+
+const char *
+svn_log__replay(const char *path, svn_revnum_t rev, apr_pool_t *pool)
+{
+ const char *log_path;
+
+ if (path && path[0] != '\0')
+ log_path = svn_path_uri_encode(path, pool);
+ else
+ log_path = "/";
+ return apr_psprintf(pool, "replay %s r%ld", log_path, rev);
+}
+
+const char *
+svn_log__get_inherited_props(const char *path,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ const char *log_path;
+
+ if (path && path[0] != '\0')
+ log_path = svn_path_uri_encode(path, pool);
+ else
+ log_path = "/";
+ return apr_psprintf(pool, "get-inherited-props %s r%ld", log_path, rev);
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/macos_keychain.c b/contrib/subversion/subversion/libsvn_subr/macos_keychain.c
new file mode 100644
index 0000000..f15324e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/macos_keychain.c
@@ -0,0 +1,263 @@
+/*
+ * macos_keychain.c: Mac OS keychain providers for SVN_AUTH_*
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+/*** Includes. ***/
+
+#include <apr_pools.h>
+#include "svn_auth.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_config.h"
+#include "svn_user.h"
+
+#include "private/svn_auth_private.h"
+
+#include "svn_private_config.h"
+
+#ifdef SVN_HAVE_KEYCHAIN_SERVICES
+
+#include <Security/Security.h>
+
+/*-----------------------------------------------------------------------*/
+/* keychain simple provider, puts passwords in the KeyChain */
+/*-----------------------------------------------------------------------*/
+
+/*
+ * XXX (2005-12-07): If no GUI is available (e.g. over a SSH session),
+ * you won't be prompted for credentials with which to unlock your
+ * keychain. Apple recognizes lack of TTY prompting as a known
+ * problem.
+ *
+ *
+ * XXX (2005-12-07): SecKeychainSetUserInteractionAllowed(FALSE) does
+ * not appear to actually prevent all user interaction. Specifically,
+ * if the executable changes (for example, if it is rebuilt), the
+ * system prompts the user to okay the use of the new executable.
+ *
+ * Worse than that, the interactivity setting is global per app (not
+ * process/thread), meaning that there is a race condition in the
+ * implementation below between calls to
+ * SecKeychainSetUserInteractionAllowed() when multiple instances of
+ * the same Subversion auth provider-based app run concurrently.
+ */
+
+/* Implementation of svn_auth__password_set_t that stores
+ the password in the OS X KeyChain. */
+static svn_error_t *
+keychain_password_set(svn_boolean_t *done,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ const char *password,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ OSStatus status;
+ SecKeychainItemRef item;
+
+ if (non_interactive)
+ SecKeychainSetUserInteractionAllowed(FALSE);
+
+ status = SecKeychainFindGenericPassword(NULL, (int) strlen(realmstring),
+ realmstring, username == NULL
+ ? 0
+ : (int) strlen(username),
+ username, 0, NULL, &item);
+ if (status)
+ {
+ if (status == errSecItemNotFound)
+ status = SecKeychainAddGenericPassword(NULL, (int) strlen(realmstring),
+ realmstring, username == NULL
+ ? 0
+ : (int) strlen(username),
+ username, (int) strlen(password),
+ password, NULL);
+ }
+ else
+ {
+ status = SecKeychainItemModifyAttributesAndData(item, NULL,
+ (int) strlen(password),
+ password);
+ CFRelease(item);
+ }
+
+ if (non_interactive)
+ SecKeychainSetUserInteractionAllowed(TRUE);
+
+ *done = (status == 0);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implementation of svn_auth__password_get_t that retrieves
+ the password from the OS X KeyChain. */
+static svn_error_t *
+keychain_password_get(svn_boolean_t *done,
+ const char **password,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ OSStatus status;
+ UInt32 length;
+ void *data;
+
+ *done = FALSE;
+
+ if (non_interactive)
+ SecKeychainSetUserInteractionAllowed(FALSE);
+
+ status = SecKeychainFindGenericPassword(NULL, (int) strlen(realmstring),
+ realmstring, username == NULL
+ ? 0
+ : (int) strlen(username),
+ username, &length, &data, NULL);
+
+ if (non_interactive)
+ SecKeychainSetUserInteractionAllowed(TRUE);
+
+ if (status != 0)
+ return SVN_NO_ERROR;
+
+ *password = apr_pstrmemdup(pool, data, length);
+ SecKeychainItemFreeContent(NULL, data);
+ *done = TRUE;
+ return SVN_NO_ERROR;
+}
+
+/* Get cached encrypted credentials from the simple provider's cache. */
+static svn_error_t *
+keychain_simple_first_creds(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_get(credentials,
+ iter_baton,
+ provider_baton,
+ parameters,
+ realmstring,
+ keychain_password_get,
+ SVN_AUTH__KEYCHAIN_PASSWORD_TYPE,
+ pool);
+}
+
+/* Save encrypted credentials to the simple provider's cache. */
+static svn_error_t *
+keychain_simple_save_creds(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_set(saved, credentials,
+ provider_baton,
+ parameters,
+ realmstring,
+ keychain_password_set,
+ SVN_AUTH__KEYCHAIN_PASSWORD_TYPE,
+ pool);
+}
+
+static const svn_auth_provider_t keychain_simple_provider = {
+ SVN_AUTH_CRED_SIMPLE,
+ keychain_simple_first_creds,
+ NULL,
+ keychain_simple_save_creds
+};
+
+/* Get cached encrypted credentials from the ssl client cert password
+ provider's cache. */
+static svn_error_t *
+keychain_ssl_client_cert_pw_first_creds(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__ssl_client_cert_pw_cache_get(credentials,
+ iter_baton, provider_baton,
+ parameters, realmstring,
+ keychain_password_get,
+ SVN_AUTH__KEYCHAIN_PASSWORD_TYPE,
+ pool);
+}
+
+/* Save encrypted credentials to the ssl client cert password provider's
+ cache. */
+static svn_error_t *
+keychain_ssl_client_cert_pw_save_creds(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__ssl_client_cert_pw_cache_set(saved, credentials,
+ provider_baton, parameters,
+ realmstring,
+ keychain_password_set,
+ SVN_AUTH__KEYCHAIN_PASSWORD_TYPE,
+ pool);
+}
+
+static const svn_auth_provider_t keychain_ssl_client_cert_pw_provider = {
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ keychain_ssl_client_cert_pw_first_creds,
+ NULL,
+ keychain_ssl_client_cert_pw_save_creds
+};
+
+
+/* Public API */
+void
+svn_auth_get_keychain_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+
+ po->vtable = &keychain_simple_provider;
+ *provider = po;
+}
+
+void
+svn_auth_get_keychain_ssl_client_cert_pw_provider
+ (svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+
+ po->vtable = &keychain_ssl_client_cert_pw_provider;
+ *provider = po;
+}
+#endif /* SVN_HAVE_KEYCHAIN_SERVICES */
diff --git a/contrib/subversion/subversion/libsvn_subr/magic.c b/contrib/subversion/subversion/libsvn_subr/magic.c
new file mode 100644
index 0000000..812a263
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/magic.c
@@ -0,0 +1,161 @@
+/*
+ * magic.c: wrappers around libmagic
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+/*** Includes. ***/
+
+#include <apr_lib.h>
+#include <apr_file_info.h>
+
+#include "svn_io.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+
+#include "svn_private_config.h"
+
+#include "private/svn_magic.h"
+
+#ifdef SVN_HAVE_LIBMAGIC
+#include <magic.h>
+#endif
+
+struct svn_magic__cookie_t {
+#ifdef SVN_HAVE_LIBMAGIC
+ magic_t magic;
+#else
+ char dummy;
+#endif
+};
+
+#ifdef SVN_HAVE_LIBMAGIC
+/* Close the magic database. */
+static apr_status_t
+close_magic_cookie(void *baton)
+{
+ svn_magic__cookie_t *mc = (svn_magic__cookie_t*)baton;
+ magic_close(mc->magic);
+ return APR_SUCCESS;
+}
+#endif
+
+void
+svn_magic__init(svn_magic__cookie_t **magic_cookie,
+ apr_pool_t *result_pool)
+{
+
+ svn_magic__cookie_t *mc = NULL;
+
+#ifdef SVN_HAVE_LIBMAGIC
+ mc = apr_palloc(result_pool, sizeof(*mc));
+
+ /* Initialise libmagic. */
+#ifndef MAGIC_MIME_TYPE
+ /* Some old versions of libmagic don't support MAGIC_MIME_TYPE.
+ * We can use MAGIC_MIME instead. It returns more than we need
+ * but we can work around that (see below). */
+ mc->magic = magic_open(MAGIC_MIME | MAGIC_ERROR);
+#else
+ mc->magic = magic_open(MAGIC_MIME_TYPE | MAGIC_ERROR);
+#endif
+ if (mc->magic)
+ {
+ /* This loads the default magic database.
+ * Point the MAGIC environment variable at your favourite .mgc
+ * file to load a non-default database. */
+ if (magic_load(mc->magic, NULL) == -1)
+ {
+ magic_close(mc->magic);
+ mc = NULL;
+ }
+ else
+ apr_pool_cleanup_register(result_pool, mc, close_magic_cookie,
+ apr_pool_cleanup_null);
+ }
+#endif
+
+ *magic_cookie = mc;
+}
+
+svn_error_t *
+svn_magic__detect_binary_mimetype(const char **mimetype,
+ const char *local_abspath,
+ svn_magic__cookie_t *magic_cookie,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *magic_mimetype = NULL;
+#ifdef SVN_HAVE_LIBMAGIC
+ apr_finfo_t finfo;
+
+ /* Do not ask libmagic for the mime-types of empty files.
+ * This prevents mime-types like "application/x-empty" from making
+ * Subversion treat empty files as binary. */
+ SVN_ERR(svn_io_stat(&finfo, local_abspath, APR_FINFO_SIZE, scratch_pool));
+ if (finfo.size > 0)
+ {
+ magic_mimetype = magic_file(magic_cookie->magic, local_abspath);
+ if (magic_mimetype)
+ {
+ /* Only return binary mime-types. */
+ if (strncmp(magic_mimetype, "text/", 5) == 0)
+ magic_mimetype = NULL;
+ else
+ {
+ svn_error_t *err;
+#ifndef MAGIC_MIME_TYPE
+ char *p;
+
+ /* Strip off trailing stuff like " charset=ascii". */
+ p = strchr(magic_mimetype, ' ');
+ if (p)
+ *p = '\0';
+#endif
+ /* Make sure we got a valid mime type. */
+ err = svn_mime_type_validate(magic_mimetype, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_BAD_MIME_TYPE)
+ {
+ svn_error_clear(err);
+ magic_mimetype = NULL;
+ }
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ /* The string is allocated from memory managed by libmagic
+ * so we must copy it to the result pool. */
+ magic_mimetype = apr_pstrdup(result_pool, magic_mimetype);
+ }
+ }
+ }
+ }
+#endif
+
+ *mimetype = magic_mimetype;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/md5.c b/contrib/subversion/subversion/libsvn_subr/md5.c
new file mode 100644
index 0000000..a707a71
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/md5.c
@@ -0,0 +1,110 @@
+/*
+ * md5.c: checksum routines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_md5.h>
+#include "md5.h"
+#include "svn_md5.h"
+
+
+
+/* The MD5 digest for the empty string. */
+static const unsigned char svn_md5__empty_string_digest_array[] = {
+ 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04,
+ 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e
+};
+
+const unsigned char *
+svn_md5__empty_string_digest(void)
+{
+ return svn_md5__empty_string_digest_array;
+}
+
+
+const char *
+svn_md5__digest_to_cstring_display(const unsigned char digest[],
+ apr_pool_t *pool)
+{
+ static const char *hex = "0123456789abcdef";
+ char *str = apr_palloc(pool, (APR_MD5_DIGESTSIZE * 2) + 1);
+ int i;
+
+ for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
+ {
+ str[i*2] = hex[digest[i] >> 4];
+ str[i*2+1] = hex[digest[i] & 0x0f];
+ }
+ str[i*2] = '\0';
+
+ return str;
+}
+
+
+const char *
+svn_md5__digest_to_cstring(const unsigned char digest[], apr_pool_t *pool)
+{
+ static const unsigned char zeros_digest[APR_MD5_DIGESTSIZE] = { 0 };
+
+ if (memcmp(digest, zeros_digest, APR_MD5_DIGESTSIZE) != 0)
+ return svn_md5__digest_to_cstring_display(digest, pool);
+ else
+ return NULL;
+}
+
+
+svn_boolean_t
+svn_md5__digests_match(const unsigned char d1[], const unsigned char d2[])
+{
+ static const unsigned char zeros[APR_MD5_DIGESTSIZE] = { 0 };
+
+ return ((memcmp(d1, zeros, APR_MD5_DIGESTSIZE) == 0)
+ || (memcmp(d2, zeros, APR_MD5_DIGESTSIZE) == 0)
+ || (memcmp(d1, d2, APR_MD5_DIGESTSIZE) == 0));
+}
+
+/* These are all deprecated, and just wrap the internal functions defined
+ above. */
+const unsigned char *
+svn_md5_empty_string_digest(void)
+{
+ return svn_md5__empty_string_digest();
+}
+
+const char *
+svn_md5_digest_to_cstring_display(const unsigned char digest[],
+ apr_pool_t *pool)
+{
+ return svn_md5__digest_to_cstring_display(digest, pool);
+}
+
+const char *
+svn_md5_digest_to_cstring(const unsigned char digest[], apr_pool_t *pool)
+{
+ return svn_md5__digest_to_cstring(digest, pool);
+}
+
+svn_boolean_t
+svn_md5_digests_match(const unsigned char d1[], const unsigned char d2[])
+{
+ return svn_md5__digests_match(d1, d2);
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/md5.h b/contrib/subversion/subversion/libsvn_subr/md5.h
new file mode 100644
index 0000000..0d83539
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/md5.h
@@ -0,0 +1,71 @@
+/*
+ * md5.h: Converting and comparing MD5 checksums
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_SUBR_MD5_H
+#define SVN_LIBSVN_SUBR_MD5_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* The MD5 digest for the empty string. */
+const unsigned char *
+svn_md5__empty_string_digest(void);
+
+
+/* Return the hex representation of DIGEST, which must be
+ * APR_MD5_DIGESTSIZE bytes long, allocating the string in POOL.
+ */
+const char *
+svn_md5__digest_to_cstring_display(const unsigned char digest[],
+ apr_pool_t *pool);
+
+
+/* Return the hex representation of DIGEST, which must be
+ * APR_MD5_DIGESTSIZE bytes long, allocating the string in POOL.
+ * If DIGEST is all zeros, then return NULL.
+ */
+const char *
+svn_md5__digest_to_cstring(const unsigned char digest[],
+ apr_pool_t *pool);
+
+
+/** Compare digests D1 and D2, each APR_MD5_DIGESTSIZE bytes long.
+ * If neither is all zeros, and they do not match, then return FALSE;
+ * else return TRUE.
+ */
+svn_boolean_t
+svn_md5__digests_match(const unsigned char d1[],
+ const unsigned char d2[]);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_SUBR_MD5_H */
diff --git a/contrib/subversion/subversion/libsvn_subr/mergeinfo.c b/contrib/subversion/subversion/libsvn_subr/mergeinfo.c
new file mode 100644
index 0000000..63496ae
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/mergeinfo.c
@@ -0,0 +1,2631 @@
+/*
+ * mergeinfo.c: Mergeinfo parsing and handling
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+#include <assert.h>
+#include <ctype.h>
+
+#include "svn_path.h"
+#include "svn_types.h"
+#include "svn_ctype.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_string.h"
+#include "svn_mergeinfo.h"
+#include "private/svn_fspath.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+#include "svn_private_config.h"
+#include "svn_hash.h"
+#include "private/svn_dep_compat.h"
+
+/* Attempt to combine two ranges, IN1 and IN2. If they are adjacent or
+ overlapping, and their inheritability allows them to be combined, put
+ the result in OUTPUT and return TRUE, otherwise return FALSE.
+
+ CONSIDER_INHERITANCE determines how to account for the inheritability
+ of IN1 and IN2 when trying to combine ranges. If ranges with different
+ inheritability are combined (CONSIDER_INHERITANCE must be FALSE for this
+ to happen) the result is inheritable. If both ranges are inheritable the
+ result is inheritable. Only and if both ranges are non-inheritable is
+ the result is non-inheritable.
+
+ Range overlapping detection algorithm from
+ http://c2.com/cgi-bin/wiki/fullSearch?TestIfDateRangesOverlap
+*/
+static svn_boolean_t
+combine_ranges(svn_merge_range_t *output,
+ const svn_merge_range_t *in1,
+ const svn_merge_range_t *in2,
+ svn_boolean_t consider_inheritance)
+{
+ if (in1->start <= in2->end && in2->start <= in1->end)
+ {
+ if (!consider_inheritance
+ || (consider_inheritance
+ && (in1->inheritable == in2->inheritable)))
+ {
+ output->start = MIN(in1->start, in2->start);
+ output->end = MAX(in1->end, in2->end);
+ output->inheritable = (in1->inheritable || in2->inheritable);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/* pathname -> PATHNAME */
+static svn_error_t *
+parse_pathname(const char **input,
+ const char *end,
+ const char **pathname,
+ apr_pool_t *pool)
+{
+ const char *curr = *input;
+ const char *last_colon = NULL;
+
+ /* A pathname may contain colons, so find the last colon before END
+ or newline. We'll consider this the divider between the pathname
+ and the revisionlist. */
+ while (curr < end && *curr != '\n')
+ {
+ if (*curr == ':')
+ last_colon = curr;
+ curr++;
+ }
+
+ if (!last_colon)
+ return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Pathname not terminated by ':'"));
+ if (last_colon == *input)
+ return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("No pathname preceding ':'"));
+
+ /* Tolerate relative repository paths, but convert them to absolute.
+ ### Efficiency? 1 string duplication here, 2 in canonicalize. */
+ *pathname = svn_fspath__canonicalize(apr_pstrndup(pool, *input,
+ last_colon - *input),
+ pool);
+
+ *input = last_colon;
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE iff (svn_merge_range_t *) RANGE describes a valid, forward
+ * revision range.
+ *
+ * Note: The smallest valid value of RANGE->start is 0 because it is an
+ * exclusive endpoint, being one less than the revision number of the first
+ * change described by the range, and the oldest possible change is "r1" as
+ * there cannot be a change "r0". */
+#define IS_VALID_FORWARD_RANGE(range) \
+ (SVN_IS_VALID_REVNUM((range)->start) && ((range)->start < (range)->end))
+
+/* Ways in which two svn_merge_range_t can intersect or adjoin, if at all. */
+typedef enum intersection_type_t
+{
+ /* Ranges don't intersect and don't adjoin. */
+ svn__no_intersection,
+
+ /* Ranges are equal. */
+ svn__equal_intersection,
+
+ /* Ranges adjoin but don't overlap. */
+ svn__adjoining_intersection,
+
+ /* Ranges overlap but neither is a subset of the other. */
+ svn__overlapping_intersection,
+
+ /* One range is a proper subset of the other. */
+ svn__proper_subset_intersection
+} intersection_type_t;
+
+/* Given ranges R1 and R2, both of which must be forward merge ranges,
+ set *INTERSECTION_TYPE to describe how the ranges intersect, if they
+ do at all. The inheritance type of the ranges is not considered. */
+static svn_error_t *
+get_type_of_intersection(const svn_merge_range_t *r1,
+ const svn_merge_range_t *r2,
+ intersection_type_t *intersection_type)
+{
+ SVN_ERR_ASSERT(r1);
+ SVN_ERR_ASSERT(r2);
+ SVN_ERR_ASSERT(IS_VALID_FORWARD_RANGE(r1));
+ SVN_ERR_ASSERT(IS_VALID_FORWARD_RANGE(r2));
+
+ if (!(r1->start <= r2->end && r2->start <= r1->end))
+ *intersection_type = svn__no_intersection;
+ else if (r1->start == r2->start && r1->end == r2->end)
+ *intersection_type = svn__equal_intersection;
+ else if (r1->end == r2->start || r2->end == r1->start)
+ *intersection_type = svn__adjoining_intersection;
+ else if (r1->start <= r2->start && r1->end >= r2->end)
+ *intersection_type = svn__proper_subset_intersection;
+ else if (r2->start <= r1->start && r2->end >= r1->end)
+ *intersection_type = svn__proper_subset_intersection;
+ else
+ *intersection_type = svn__overlapping_intersection;
+
+ return SVN_NO_ERROR;
+}
+
+/* Modify or extend RANGELIST (a list of merge ranges) to incorporate
+ NEW_RANGE. RANGELIST is a "rangelist" as defined in svn_mergeinfo.h.
+
+ OVERVIEW
+
+ Determine the minimal set of non-overlapping merge ranges required to
+ represent the combination of RANGELIST and NEW_RANGE. The result depends
+ on whether and how NEW_RANGE overlaps any merge range[*] in RANGELIST,
+ and also on any differences in the inheritability of each range,
+ according to the rules described below. Modify RANGELIST to represent
+ this result, by adjusting the last range in it and/or appending one or
+ two more ranges.
+
+ ([*] Due to the simplifying assumption below, only the last range in
+ RANGELIST is considered.)
+
+ DETAILS
+
+ If RANGELIST is not empty assume NEW_RANGE does not intersect with any
+ range before the last one in RANGELIST.
+
+ If RANGELIST is empty or NEW_RANGE does not intersect with the lastrange
+ in RANGELIST, then append a copy of NEW_RANGE, allocated in RESULT_POOL,
+ to RANGELIST.
+
+ If NEW_RANGE intersects with the last range in RANGELIST then combine
+ these two ranges as described below:
+
+ If the intersecting ranges have the same inheritability then simply
+ combine the ranges in place. Otherwise, if the ranges intersect but
+ differ in inheritability, then merge the ranges as dictated by
+ CONSIDER_INHERITANCE:
+
+ If CONSIDER_INHERITANCE is false then intersecting ranges are combined
+ into a single range. The inheritability of the resulting range is
+ non-inheritable *only* if both ranges are non-inheritable, otherwise the
+ combined range is inheritable, e.g.:
+
+ Last range in NEW_RANGE RESULTING RANGES
+ RANGELIST
+ ------------- --------- ----------------
+ 4-10* 6-13 4-13
+ 4-10 6-13* 4-13
+ 4-10* 6-13* 4-13*
+
+ If CONSIDER_INHERITANCE is true, then only the intersection between the
+ two ranges is combined, with the inheritability of the resulting range
+ non-inheritable only if both ranges were non-inheritable. The
+ non-intersecting portions are added as separate ranges allocated in
+ RESULT_POOL, e.g.:
+
+ Last range in NEW_RANGE RESULTING RANGES
+ RANGELIST
+ ------------- --------- ----------------
+ 4-10* 6 4-5*, 6, 7-10*
+ 4-10* 6-12 4-5*, 6-12
+
+ Note that the standard rules for rangelists still apply and overlapping
+ ranges are not allowed. So if the above would result in overlapping
+ ranges of the same inheritance, the overlapping ranges are merged into a
+ single range, e.g.:
+
+ Last range in NEW_RANGE RESULTING RANGES
+ RANGELIST
+ ------------- --------- ----------------
+ 4-10 6* 4-10 (Not 4-5, 6, 7-10)
+
+ When replacing the last range in RANGELIST, either allocate a new range in
+ RESULT_POOL or modify the existing range in place. Any new ranges added
+ to RANGELIST are allocated in RESULT_POOL.
+*/
+static svn_error_t *
+combine_with_lastrange(const svn_merge_range_t *new_range,
+ svn_rangelist_t *rangelist,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *result_pool)
+{
+ svn_merge_range_t *lastrange;
+ svn_merge_range_t combined_range;
+
+ /* We don't accept a NULL RANGELIST. */
+ SVN_ERR_ASSERT(rangelist);
+
+ if (rangelist->nelts > 0)
+ lastrange = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1, svn_merge_range_t *);
+ else
+ lastrange = NULL;
+
+ if (!lastrange)
+ {
+ /* No *LASTRANGE so push NEW_RANGE onto RANGELIST and we are done. */
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) =
+ svn_merge_range_dup(new_range, result_pool);
+ }
+ else if (!consider_inheritance)
+ {
+ /* We are not considering inheritance so we can merge intersecting
+ ranges of different inheritability. Of course if the ranges
+ don't intersect at all we simply push NEW_RANGE only RANGELIST. */
+ if (combine_ranges(&combined_range, lastrange, new_range, FALSE))
+ {
+ *lastrange = combined_range;
+ }
+ else
+ {
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) =
+ svn_merge_range_dup(new_range, result_pool);
+ }
+ }
+ else /* Considering inheritance */
+ {
+ if (combine_ranges(&combined_range, lastrange, new_range, TRUE))
+ {
+ /* Even when considering inheritance two intersection ranges
+ of the same inheritability can simply be combined. */
+ *lastrange = combined_range;
+ }
+ else
+ {
+ /* If we are here then the ranges either don't intersect or do
+ intersect but have differing inheritability. Check for the
+ first case as that is easy to handle. */
+ intersection_type_t intersection_type;
+ svn_boolean_t sorted = FALSE;
+
+ SVN_ERR(get_type_of_intersection(new_range, lastrange,
+ &intersection_type));
+
+ switch (intersection_type)
+ {
+ case svn__no_intersection:
+ /* NEW_RANGE and *LASTRANGE *really* don't intersect so
+ just push NEW_RANGE only RANGELIST. */
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) =
+ svn_merge_range_dup(new_range, result_pool);
+ sorted = (svn_sort_compare_ranges(&lastrange,
+ &new_range) < 0);
+ break;
+
+ case svn__equal_intersection:
+ /* They range are equal so all we do is force the
+ inheritability of lastrange to true. */
+ lastrange->inheritable = TRUE;
+ sorted = TRUE;
+ break;
+
+ case svn__adjoining_intersection:
+ /* They adjoin but don't overlap so just push NEW_RANGE
+ onto RANGELIST. */
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) =
+ svn_merge_range_dup(new_range, result_pool);
+ sorted = (svn_sort_compare_ranges(&lastrange,
+ &new_range) < 0);
+ break;
+
+ case svn__overlapping_intersection:
+ /* They ranges overlap but neither is a proper subset of
+ the other. We'll end up pusing two new ranges onto
+ RANGELIST, the intersecting part and the part unique to
+ NEW_RANGE.*/
+ {
+ svn_merge_range_t *r1 = svn_merge_range_dup(lastrange,
+ result_pool);
+ svn_merge_range_t *r2 = svn_merge_range_dup(new_range,
+ result_pool);
+
+ /* Pop off *LASTRANGE to make our manipulations
+ easier. */
+ apr_array_pop(rangelist);
+
+ /* Ensure R1 is the older range. */
+ if (r2->start < r1->start)
+ {
+ /* Swap R1 and R2. */
+ *r2 = *r1;
+ *r1 = *new_range;
+ }
+
+ /* Absorb the intersecting ranges into the
+ inheritable range. */
+ if (r1->inheritable)
+ r2->start = r1->end;
+ else
+ r1->end = r2->start;
+
+ /* Push everything back onto RANGELIST. */
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r1;
+ sorted = (svn_sort_compare_ranges(&lastrange,
+ &r1) < 0);
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r2;
+ if (sorted)
+ sorted = (svn_sort_compare_ranges(&r1, &r2) < 0);
+ break;
+ }
+
+ default: /* svn__proper_subset_intersection */
+ {
+ /* One range is a proper subset of the other. */
+ svn_merge_range_t *r1 = svn_merge_range_dup(lastrange,
+ result_pool);
+ svn_merge_range_t *r2 = svn_merge_range_dup(new_range,
+ result_pool);
+ svn_merge_range_t *r3 = NULL;
+
+ /* Pop off *LASTRANGE to make our manipulations
+ easier. */
+ apr_array_pop(rangelist);
+
+ /* Ensure R1 is the superset. */
+ if (r2->start < r1->start || r2->end > r1->end)
+ {
+ /* Swap R1 and R2. */
+ *r2 = *r1;
+ *r1 = *new_range;
+ }
+
+ if (r1->inheritable)
+ {
+ /* The simple case: The superset is inheritable, so
+ just combine r1 and r2. */
+ r1->start = MIN(r1->start, r2->start);
+ r1->end = MAX(r1->end, r2->end);
+ r2 = NULL;
+ }
+ else if (r1->start == r2->start)
+ {
+ svn_revnum_t tmp_revnum;
+
+ /* *LASTRANGE and NEW_RANGE share an end point. */
+ tmp_revnum = r1->end;
+ r1->end = r2->end;
+ r2->inheritable = r1->inheritable;
+ r1->inheritable = TRUE;
+ r2->start = r1->end;
+ r2->end = tmp_revnum;
+ }
+ else if (r1->end == r2->end)
+ {
+ /* *LASTRANGE and NEW_RANGE share an end point. */
+ r1->end = r2->start;
+ r2->inheritable = TRUE;
+ }
+ else
+ {
+ /* NEW_RANGE and *LASTRANGE share neither start
+ nor end points. */
+ r3 = apr_pcalloc(result_pool, sizeof(*r3));
+ r3->start = r2->end;
+ r3->end = r1->end;
+ r3->inheritable = r1->inheritable;
+ r2->inheritable = TRUE;
+ r1->end = r2->start;
+ }
+
+ /* Push everything back onto RANGELIST. */
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r1;
+ sorted = (svn_sort_compare_ranges(&lastrange, &r1) < 0);
+ if (r2)
+ {
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r2;
+ if (sorted)
+ sorted = (svn_sort_compare_ranges(&r1, &r2) < 0);
+ }
+ if (r3)
+ {
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r3;
+ if (sorted)
+ {
+ if (r2)
+ sorted = (svn_sort_compare_ranges(&r2,
+ &r3) < 0);
+ else
+ sorted = (svn_sort_compare_ranges(&r1,
+ &r3) < 0);
+ }
+ }
+ break;
+ }
+ }
+
+ /* Some of the above cases might have put *RANGELIST out of
+ order, so re-sort.*/
+ if (!sorted)
+ qsort(rangelist->elts, rangelist->nelts, rangelist->elt_size,
+ svn_sort_compare_ranges);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Convert a single svn_merge_range_t *RANGE back into a string. */
+static char *
+range_to_string(const svn_merge_range_t *range,
+ apr_pool_t *pool)
+{
+ const char *mark
+ = range->inheritable ? "" : SVN_MERGEINFO_NONINHERITABLE_STR;
+
+ if (range->start == range->end - 1)
+ return apr_psprintf(pool, "%ld%s", range->end, mark);
+ else if (range->start - 1 == range->end)
+ return apr_psprintf(pool, "-%ld%s", range->start, mark);
+ else if (range->start < range->end)
+ return apr_psprintf(pool, "%ld-%ld%s", range->start + 1, range->end, mark);
+ else
+ return apr_psprintf(pool, "%ld-%ld%s", range->start, range->end + 1, mark);
+}
+
+/* Helper for svn_mergeinfo_parse()
+ Append revision ranges onto the array RANGELIST to represent the range
+ descriptions found in the string *INPUT. Read only as far as a newline
+ or the position END, whichever comes first. Set *INPUT to the position
+ after the last character of INPUT that was used.
+
+ revisionlist -> (revisionelement)(COMMA revisionelement)*
+ revisionrange -> REVISION "-" REVISION("*")
+ revisionelement -> revisionrange | REVISION("*")
+*/
+static svn_error_t *
+parse_rangelist(const char **input, const char *end,
+ svn_rangelist_t *rangelist,
+ apr_pool_t *pool)
+{
+ const char *curr = *input;
+
+ /* Eat any leading horizontal white-space before the rangelist. */
+ while (curr < end && *curr != '\n' && isspace(*curr))
+ curr++;
+
+ if (*curr == '\n' || curr == end)
+ {
+ /* Empty range list. */
+ *input = curr;
+ return SVN_NO_ERROR;
+ }
+
+ while (curr < end && *curr != '\n')
+ {
+ /* Parse individual revisions or revision ranges. */
+ svn_merge_range_t *mrange = apr_pcalloc(pool, sizeof(*mrange));
+ svn_revnum_t firstrev;
+
+ SVN_ERR(svn_revnum_parse(&firstrev, curr, &curr));
+ if (*curr != '-' && *curr != '\n' && *curr != ',' && *curr != '*'
+ && curr != end)
+ return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Invalid character '%c' found in revision "
+ "list"), *curr);
+ mrange->start = firstrev - 1;
+ mrange->end = firstrev;
+ mrange->inheritable = TRUE;
+
+ if (firstrev == 0)
+ return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Invalid revision number '0' found in "
+ "range list"));
+
+ if (*curr == '-')
+ {
+ svn_revnum_t secondrev;
+
+ curr++;
+ SVN_ERR(svn_revnum_parse(&secondrev, curr, &curr));
+ if (firstrev > secondrev)
+ return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Unable to parse reversed revision "
+ "range '%ld-%ld'"),
+ firstrev, secondrev);
+ else if (firstrev == secondrev)
+ return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Unable to parse revision range "
+ "'%ld-%ld' with same start and end "
+ "revisions"), firstrev, secondrev);
+ mrange->end = secondrev;
+ }
+
+ if (*curr == '\n' || curr == end)
+ {
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = mrange;
+ *input = curr;
+ return SVN_NO_ERROR;
+ }
+ else if (*curr == ',')
+ {
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = mrange;
+ curr++;
+ }
+ else if (*curr == '*')
+ {
+ mrange->inheritable = FALSE;
+ curr++;
+ if (*curr == ',' || *curr == '\n' || curr == end)
+ {
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = mrange;
+ if (*curr == ',')
+ {
+ curr++;
+ }
+ else
+ {
+ *input = curr;
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Invalid character '%c' found in "
+ "range list"), *curr);
+ }
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Invalid character '%c' found in "
+ "range list"), *curr);
+ }
+
+ }
+ if (*curr != '\n')
+ return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Range list parsing ended before hitting "
+ "newline"));
+ *input = curr;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_rangelist__parse(svn_rangelist_t **rangelist,
+ const char *str,
+ apr_pool_t *result_pool)
+{
+ const char *s = str;
+
+ *rangelist = apr_array_make(result_pool, 1, sizeof(svn_merge_range_t *));
+ SVN_ERR(parse_rangelist(&s, s + strlen(s), *rangelist, result_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_rangelist__combine_adjacent_ranges(svn_rangelist_t *rangelist,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_merge_range_t *range, *lastrange;
+
+ lastrange = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *);
+
+ for (i = 1; i < rangelist->nelts; i++)
+ {
+ range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ if (lastrange->start <= range->end
+ && range->start <= lastrange->end)
+ {
+ /* The ranges are adjacent or intersect. */
+
+ /* svn_mergeinfo_parse promises to combine overlapping
+ ranges as long as their inheritability is the same. */
+ if (range->start < lastrange->end
+ && range->inheritable != lastrange->inheritable)
+ {
+ return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Unable to parse overlapping "
+ "revision ranges '%s' and '%s' "
+ "with different inheritance "
+ "types"),
+ range_to_string(lastrange,
+ scratch_pool),
+ range_to_string(range,
+ scratch_pool));
+ }
+
+ /* Combine overlapping or adjacent ranges with the
+ same inheritability. */
+ if (lastrange->inheritable == range->inheritable)
+ {
+ lastrange->end = MAX(range->end, lastrange->end);
+ svn_sort__array_delete(rangelist, i, 1);
+ i--;
+ }
+ }
+ lastrange = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* revisionline -> PATHNAME COLON revisionlist */
+static svn_error_t *
+parse_revision_line(const char **input, const char *end, svn_mergeinfo_t hash,
+ apr_pool_t *scratch_pool)
+{
+ const char *pathname = "";
+ apr_ssize_t klen;
+ svn_rangelist_t *existing_rangelist;
+ svn_rangelist_t *rangelist = apr_array_make(scratch_pool, 1,
+ sizeof(svn_merge_range_t *));
+
+ SVN_ERR(parse_pathname(input, end, &pathname, scratch_pool));
+
+ if (*(*input) != ':')
+ return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Pathname not terminated by ':'"));
+
+ *input = *input + 1;
+
+ SVN_ERR(parse_rangelist(input, end, rangelist, scratch_pool));
+
+ if (rangelist->nelts == 0)
+ return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Mergeinfo for '%s' maps to an "
+ "empty revision range"), pathname);
+ if (*input != end && *(*input) != '\n')
+ return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Could not find end of line in range list line "
+ "in '%s'"), *input);
+
+ if (*input != end)
+ *input = *input + 1;
+
+ /* Sort the rangelist, combine adjacent ranges into single ranges,
+ and make sure there are no overlapping ranges. */
+ if (rangelist->nelts > 1)
+ {
+ qsort(rangelist->elts, rangelist->nelts, rangelist->elt_size,
+ svn_sort_compare_ranges);
+
+ SVN_ERR(svn_rangelist__combine_adjacent_ranges(rangelist, scratch_pool));
+ }
+
+ /* Handle any funky mergeinfo with relative merge source paths that
+ might exist due to issue #3547. It's possible that this issue allowed
+ the creation of mergeinfo with path keys that differ only by a
+ leading slash, e.g. "trunk:4033\n/trunk:4039-4995". In the event
+ we encounter this we merge the rangelists together under a single
+ absolute path key. */
+ klen = strlen(pathname);
+ existing_rangelist = apr_hash_get(hash, pathname, klen);
+ if (existing_rangelist)
+ SVN_ERR(svn_rangelist_merge2(rangelist, existing_rangelist,
+ scratch_pool, scratch_pool));
+
+ apr_hash_set(hash, apr_pstrmemdup(apr_hash_pool_get(hash), pathname, klen),
+ klen, svn_rangelist_dup(rangelist, apr_hash_pool_get(hash)));
+
+ return SVN_NO_ERROR;
+}
+
+/* top -> revisionline (NEWLINE revisionline)* */
+static svn_error_t *
+parse_top(const char **input, const char *end, svn_mergeinfo_t hash,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ while (*input < end)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(parse_revision_line(input, end, hash, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo_parse(svn_mergeinfo_t *mergeinfo,
+ const char *input,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ *mergeinfo = svn_hash__make(pool);
+ err = parse_top(&input, input + strlen(input), *mergeinfo, pool);
+
+ /* Always return SVN_ERR_MERGEINFO_PARSE_ERROR as the topmost error. */
+ if (err && err->apr_err != SVN_ERR_MERGEINFO_PARSE_ERROR)
+ err = svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, err,
+ _("Could not parse mergeinfo string '%s'"),
+ input);
+ return err;
+}
+
+/* Cleanup after svn_rangelist_merge2 when it modifies the ending range of
+ a single rangelist element in-place.
+
+ If *RANGE_INDEX is not a valid element in RANGELIST do nothing. Otherwise
+ ensure that RANGELIST[*RANGE_INDEX]->END does not adjoin or overlap any
+ subsequent ranges in RANGELIST.
+
+ If overlap is found, then remove, modify, and/or add elements to RANGELIST
+ as per the invariants for rangelists documented in svn_mergeinfo.h. If
+ RANGELIST[*RANGE_INDEX]->END adjoins a subsequent element then combine the
+ elements if their inheritability permits -- The inheritance of intersecting
+ and adjoining ranges is handled as per svn_mergeinfo_merge2. Upon return
+ set *RANGE_INDEX to the index of the youngest element modified, added, or
+ adjoined to RANGELIST[*RANGE_INDEX].
+
+ Note: Adjoining rangelist elements are those where the end rev of the older
+ element is equal to the start rev of the younger element.
+
+ Any new elements inserted into RANGELIST are allocated in RESULT_POOL.*/
+static void
+adjust_remaining_ranges(svn_rangelist_t *rangelist,
+ int *range_index,
+ apr_pool_t *result_pool)
+{
+ int i;
+ int starting_index;
+ int elements_to_delete = 0;
+ svn_merge_range_t *modified_range;
+
+ if (*range_index >= rangelist->nelts)
+ return;
+
+ starting_index = *range_index + 1;
+ modified_range = APR_ARRAY_IDX(rangelist, *range_index, svn_merge_range_t *);
+
+ for (i = *range_index + 1; i < rangelist->nelts; i++)
+ {
+ svn_merge_range_t *next_range = APR_ARRAY_IDX(rangelist, i,
+ svn_merge_range_t *);
+
+ /* If MODIFIED_RANGE doesn't adjoin or overlap the next range in
+ RANGELIST then we are finished. */
+ if (modified_range->end < next_range->start)
+ break;
+
+ /* Does MODIFIED_RANGE adjoin NEXT_RANGE? */
+ if (modified_range->end == next_range->start)
+ {
+ if (modified_range->inheritable == next_range->inheritable)
+ {
+ /* Combine adjoining ranges with the same inheritability. */
+ modified_range->end = next_range->end;
+ elements_to_delete++;
+ }
+ else
+ {
+ /* Cannot join because inheritance differs. */
+ (*range_index)++;
+ }
+ break;
+ }
+
+ /* Alright, we know MODIFIED_RANGE overlaps NEXT_RANGE, but how? */
+ if (modified_range->end > next_range->end)
+ {
+ /* NEXT_RANGE is a proper subset of MODIFIED_RANGE and the two
+ don't share the same end range. */
+ if (modified_range->inheritable
+ || (modified_range->inheritable == next_range->inheritable))
+ {
+ /* MODIFIED_RANGE absorbs NEXT_RANGE. */
+ elements_to_delete++;
+ }
+ else
+ {
+ /* NEXT_RANGE is a proper subset MODIFIED_RANGE but
+ MODIFIED_RANGE is non-inheritable and NEXT_RANGE is
+ inheritable. This means MODIFIED_RANGE is truncated,
+ NEXT_RANGE remains, and the portion of MODIFIED_RANGE
+ younger than NEXT_RANGE is added as a separate range:
+ ______________________________________________
+ | |
+ M MODIFIED_RANGE N
+ | (!inhertiable) |
+ |______________________________________________|
+ | |
+ O NEXT_RANGE P
+ | (inheritable)|
+ |______________|
+ |
+ V
+ _______________________________________________
+ | | | |
+ M MODIFIED_RANGE O NEXT_RANGE P NEW_RANGE N
+ | (!inhertiable) | (inheritable)| (!inheritable)|
+ |________________|______________|_______________|
+ */
+ svn_merge_range_t *new_modified_range =
+ apr_palloc(result_pool, sizeof(*new_modified_range));
+ new_modified_range->start = next_range->end;
+ new_modified_range->end = modified_range->end;
+ new_modified_range->inheritable = FALSE;
+ modified_range->end = next_range->start;
+ (*range_index)+=2;
+ svn_sort__array_insert(&new_modified_range, rangelist,
+ *range_index);
+ /* Recurse with the new range. */
+ adjust_remaining_ranges(rangelist, range_index, result_pool);
+ break;
+ }
+ }
+ else if (modified_range->end == next_range->end)
+ {
+ /* NEXT_RANGE is a proper subset MODIFIED_RANGE and share
+ the same end range. */
+ if (modified_range->inheritable
+ || (modified_range->inheritable == next_range->inheritable))
+ {
+ /* MODIFIED_RANGE absorbs NEXT_RANGE. */
+ elements_to_delete++;
+ }
+ else
+ {
+ /* The intersection between MODIFIED_RANGE and NEXT_RANGE is
+ absorbed by the latter. */
+ modified_range->end = next_range->start;
+ (*range_index)++;
+ }
+ break;
+ }
+ else
+ {
+ /* NEXT_RANGE and MODIFIED_RANGE intersect but NEXT_RANGE is not
+ a proper subset of MODIFIED_RANGE, nor do the two share the
+ same end revision, i.e. they overlap. */
+ if (modified_range->inheritable == next_range->inheritable)
+ {
+ /* Combine overlapping ranges with the same inheritability. */
+ modified_range->end = next_range->end;
+ elements_to_delete++;
+ }
+ else if (modified_range->inheritable)
+ {
+ /* MODIFIED_RANGE absorbs the portion of NEXT_RANGE it overlaps
+ and NEXT_RANGE is truncated. */
+ next_range->start = modified_range->end;
+ (*range_index)++;
+ }
+ else
+ {
+ /* NEXT_RANGE absorbs the portion of MODIFIED_RANGE it overlaps
+ and MODIFIED_RANGE is truncated. */
+ modified_range->end = next_range->start;
+ (*range_index)++;
+ }
+ break;
+ }
+ }
+
+ if (elements_to_delete)
+ svn_sort__array_delete(rangelist, starting_index, elements_to_delete);
+}
+
+svn_error_t *
+svn_rangelist_merge2(svn_rangelist_t *rangelist,
+ const svn_rangelist_t *changes,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i = 0;
+ int j = 0;
+
+ /* We may modify CHANGES, so make a copy in SCRATCH_POOL. */
+ changes = svn_rangelist_dup(changes, scratch_pool);
+
+ while (i < rangelist->nelts && j < changes->nelts)
+ {
+ svn_merge_range_t *range =
+ APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ svn_merge_range_t *change =
+ APR_ARRAY_IDX(changes, j, svn_merge_range_t *);
+ int res = svn_sort_compare_ranges(&range, &change);
+
+ if (res == 0)
+ {
+ /* Only when merging two non-inheritable ranges is the result also
+ non-inheritable. In all other cases ensure an inheritiable
+ result. */
+ if (range->inheritable || change->inheritable)
+ range->inheritable = TRUE;
+ i++;
+ j++;
+ }
+ else if (res < 0) /* CHANGE is younger than RANGE */
+ {
+ if (range->end < change->start)
+ {
+ /* RANGE is older than CHANGE and the two do not
+ adjoin or overlap */
+ i++;
+ }
+ else if (range->end == change->start)
+ {
+ /* RANGE and CHANGE adjoin */
+ if (range->inheritable == change->inheritable)
+ {
+ /* RANGE and CHANGE have the same inheritability so
+ RANGE expands to absord CHANGE. */
+ range->end = change->end;
+ adjust_remaining_ranges(rangelist, &i, result_pool);
+ j++;
+ }
+ else
+ {
+ /* RANGE and CHANGE adjoin, but have different
+ inheritability. Since RANGE is older, just
+ move on to the next RANGE. */
+ i++;
+ }
+ }
+ else
+ {
+ /* RANGE and CHANGE overlap, but how? */
+ if ((range->inheritable == change->inheritable)
+ || range->inheritable)
+ {
+ /* If CHANGE is a proper subset of RANGE, it absorbs RANGE
+ with no adjustment otherwise only the intersection is
+ absorbed and CHANGE is truncated. */
+ if (range->end >= change->end)
+ j++;
+ else
+ change->start = range->end;
+ }
+ else
+ {
+ /* RANGE is non-inheritable and CHANGE is inheritable. */
+ if (range->start < change->start)
+ {
+ /* CHANGE absorbs intersection with RANGE and RANGE
+ is truncated. */
+ svn_merge_range_t *range_copy =
+ svn_merge_range_dup(range, result_pool);
+ range_copy->end = change->start;
+ range->start = change->start;
+ svn_sort__array_insert(&range_copy, rangelist, i++);
+ }
+ else
+ {
+ /* CHANGE and RANGE share the same start rev, but
+ RANGE is considered older because its end rev
+ is older. */
+ range->inheritable = TRUE;
+ change->start = range->end;
+ }
+ }
+ }
+ }
+ else /* res > 0, CHANGE is older than RANGE */
+ {
+ if (change->end < range->start)
+ {
+ /* CHANGE is older than RANGE and the two do not
+ adjoin or overlap, so insert a copy of CHANGE
+ into RANGELIST. */
+ svn_merge_range_t *change_copy =
+ svn_merge_range_dup(change, result_pool);
+ svn_sort__array_insert(&change_copy, rangelist, i++);
+ j++;
+ }
+ else if (change->end == range->start)
+ {
+ /* RANGE and CHANGE adjoin */
+ if (range->inheritable == change->inheritable)
+ {
+ /* RANGE and CHANGE have the same inheritability so we
+ can simply combine the two in place. */
+ range->start = change->start;
+ j++;
+ }
+ else
+ {
+ /* RANGE and CHANGE have different inheritability so insert
+ a copy of CHANGE into RANGELIST. */
+ svn_merge_range_t *change_copy =
+ svn_merge_range_dup(change, result_pool);
+ svn_sort__array_insert(&change_copy, rangelist, i);
+ j++;
+ }
+ }
+ else
+ {
+ /* RANGE and CHANGE overlap. */
+ if (range->inheritable == change->inheritable)
+ {
+ /* RANGE and CHANGE have the same inheritability so we
+ can simply combine the two in place... */
+ range->start = change->start;
+ if (range->end < change->end)
+ {
+ /* ...but if RANGE is expanded ensure that we don't
+ violate any rangelist invariants. */
+ range->end = change->end;
+ adjust_remaining_ranges(rangelist, &i, result_pool);
+ }
+ j++;
+ }
+ else if (range->inheritable)
+ {
+ if (change->start < range->start)
+ {
+ /* RANGE is inheritable so absorbs any part of CHANGE
+ it overlaps. CHANGE is truncated and the remainder
+ inserted into RANGELIST. */
+ svn_merge_range_t *change_copy =
+ svn_merge_range_dup(change, result_pool);
+ change_copy->end = range->start;
+ change->start = range->start;
+ svn_sort__array_insert(&change_copy, rangelist, i++);
+ }
+ else
+ {
+ /* CHANGE and RANGE share the same start rev, but
+ CHANGE is considered older because CHANGE->END is
+ older than RANGE->END. */
+ j++;
+ }
+ }
+ else
+ {
+ /* RANGE is non-inheritable and CHANGE is inheritable. */
+ if (change->start < range->start)
+ {
+ if (change->end == range->end)
+ {
+ /* RANGE is a proper subset of CHANGE and share the
+ same end revision, so set RANGE equal to CHANGE. */
+ range->start = change->start;
+ range->inheritable = TRUE;
+ j++;
+ }
+ else if (change->end > range->end)
+ {
+ /* RANGE is a proper subset of CHANGE and CHANGE has
+ a younger end revision, so set RANGE equal to its
+ intersection with CHANGE and truncate CHANGE. */
+ range->start = change->start;
+ range->inheritable = TRUE;
+ change->start = range->end;
+ }
+ else
+ {
+ /* CHANGE and RANGE overlap. Set RANGE equal to its
+ intersection with CHANGE and take the remainder
+ of RANGE and insert it into RANGELIST. */
+ svn_merge_range_t *range_copy =
+ svn_merge_range_dup(range, result_pool);
+ range_copy->start = change->end;
+ range->start = change->start;
+ range->end = change->end;
+ range->inheritable = TRUE;
+ svn_sort__array_insert(&range_copy, rangelist, ++i);
+ j++;
+ }
+ }
+ else
+ {
+ /* CHANGE and RANGE share the same start rev, but
+ CHANGE is considered older because its end rev
+ is older.
+
+ Insert the intersection of RANGE and CHANGE into
+ RANGELIST and then set RANGE to the non-intersecting
+ portion of RANGE. */
+ svn_merge_range_t *range_copy =
+ svn_merge_range_dup(range, result_pool);
+ range_copy->end = change->end;
+ range_copy->inheritable = TRUE;
+ range->start = change->end;
+ svn_sort__array_insert(&range_copy, rangelist, i++);
+ j++;
+ }
+ }
+ }
+ }
+ }
+
+ /* Copy any remaining elements in CHANGES into RANGELIST. */
+ for (; j < (changes)->nelts; j++)
+ {
+ svn_merge_range_t *change =
+ APR_ARRAY_IDX(changes, j, svn_merge_range_t *);
+ svn_merge_range_t *change_copy = svn_merge_range_dup(change,
+ result_pool);
+ svn_sort__array_insert(&change_copy, rangelist, rangelist->nelts);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE iff the forward revision ranges FIRST and SECOND overlap and
+ * (if CONSIDER_INHERITANCE is TRUE) have the same inheritability. */
+static svn_boolean_t
+range_intersect(const svn_merge_range_t *first, const svn_merge_range_t *second,
+ svn_boolean_t consider_inheritance)
+{
+ SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(first));
+ SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(second));
+
+ return (first->start + 1 <= second->end)
+ && (second->start + 1 <= first->end)
+ && (!consider_inheritance
+ || (!(first->inheritable) == !(second->inheritable)));
+}
+
+/* Return TRUE iff the forward revision range FIRST wholly contains the
+ * forward revision range SECOND and (if CONSIDER_INHERITANCE is TRUE) has
+ * the same inheritability. */
+static svn_boolean_t
+range_contains(const svn_merge_range_t *first, const svn_merge_range_t *second,
+ svn_boolean_t consider_inheritance)
+{
+ SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(first));
+ SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(second));
+
+ return (first->start <= second->start) && (second->end <= first->end)
+ && (!consider_inheritance
+ || (!(first->inheritable) == !(second->inheritable)));
+}
+
+/* Swap start and end fields of RANGE. */
+static void
+range_swap_endpoints(svn_merge_range_t *range)
+{
+ svn_revnum_t swap = range->start;
+ range->start = range->end;
+ range->end = swap;
+}
+
+svn_error_t *
+svn_rangelist_reverse(svn_rangelist_t *rangelist, apr_pool_t *pool)
+{
+ int i;
+
+ svn_sort__array_reverse(rangelist, pool);
+
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ range_swap_endpoints(APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_rangelist__set_inheritance(svn_rangelist_t *rangelist,
+ svn_boolean_t inheritable)
+{
+ if (rangelist)
+ {
+ int i;
+ svn_merge_range_t *range;
+
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ range->inheritable = inheritable;
+ }
+ }
+ return;
+}
+
+void
+svn_mergeinfo__set_inheritance(svn_mergeinfo_t mergeinfo,
+ svn_boolean_t inheritable,
+ apr_pool_t *scratch_pool)
+{
+ if (mergeinfo)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+
+ if (rangelist)
+ svn_rangelist__set_inheritance(rangelist, inheritable);
+ }
+ }
+ return;
+}
+
+/* If DO_REMOVE is true, then remove any overlapping ranges described by
+ RANGELIST1 from RANGELIST2 and place the results in *OUTPUT. When
+ DO_REMOVE is true, RANGELIST1 is effectively the "eraser" and RANGELIST2
+ the "whiteboard".
+
+ If DO_REMOVE is false, then capture the intersection between RANGELIST1
+ and RANGELIST2 and place the results in *OUTPUT. The ordering of
+ RANGELIST1 and RANGELIST2 doesn't matter when DO_REMOVE is false.
+
+ If CONSIDER_INHERITANCE is true, then take the inheritance of the
+ ranges in RANGELIST1 and RANGELIST2 into account when comparing them
+ for intersection, see the doc string for svn_rangelist_intersect().
+
+ If CONSIDER_INHERITANCE is false, then ranges with differing inheritance
+ may intersect, but the resulting intersection is non-inheritable only
+ if both ranges were non-inheritable, e.g.:
+
+ RANGELIST1 RANGELIST2 CONSIDER DO_REMOVE *OUTPUT
+ INHERITANCE
+ ---------- ------ ----------- --------- -------
+
+ 90-420* 1-100 TRUE FALSE Empty Rangelist
+ 90-420 1-100* TRUE FALSE Empty Rangelist
+ 90-420 1-100 TRUE FALSE 90-100
+ 90-420* 1-100* TRUE FALSE 90-100*
+
+ 90-420* 1-100 FALSE FALSE 90-100
+ 90-420 1-100* FALSE FALSE 90-100
+ 90-420 1-100 FALSE FALSE 90-100
+ 90-420* 1-100* FALSE FALSE 90-100*
+
+ Allocate the contents of *OUTPUT in POOL. */
+static svn_error_t *
+rangelist_intersect_or_remove(svn_rangelist_t **output,
+ const svn_rangelist_t *rangelist1,
+ const svn_rangelist_t *rangelist2,
+ svn_boolean_t do_remove,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *pool)
+{
+ int i1, i2, lasti2;
+ svn_merge_range_t working_elt2;
+
+ *output = apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
+
+ i1 = 0;
+ i2 = 0;
+ lasti2 = -1; /* Initialized to a value that "i2" will never be. */
+
+ while (i1 < rangelist1->nelts && i2 < rangelist2->nelts)
+ {
+ svn_merge_range_t *elt1, *elt2;
+
+ elt1 = APR_ARRAY_IDX(rangelist1, i1, svn_merge_range_t *);
+
+ /* Instead of making a copy of the entire array of rangelist2
+ elements, we just keep a copy of the current rangelist2 element
+ that needs to be used, and modify our copy if necessary. */
+ if (i2 != lasti2)
+ {
+ working_elt2 =
+ *(APR_ARRAY_IDX(rangelist2, i2, svn_merge_range_t *));
+ lasti2 = i2;
+ }
+
+ elt2 = &working_elt2;
+
+ /* If the rangelist2 range is contained completely in the
+ rangelist1, we increment the rangelist2.
+ If the ranges intersect, and match exactly, we increment both
+ rangelist1 and rangelist2.
+ Otherwise, we have to generate a range for the left part of
+ the removal of rangelist1 from rangelist2, and possibly change
+ the rangelist2 to the remaining portion of the right part of
+ the removal, to test against. */
+ if (range_contains(elt1, elt2, consider_inheritance))
+ {
+ if (!do_remove)
+ {
+ svn_merge_range_t tmp_range;
+ tmp_range.start = elt2->start;
+ tmp_range.end = elt2->end;
+ /* The intersection of two ranges is non-inheritable only
+ if both ranges are non-inheritable. */
+ tmp_range.inheritable =
+ (elt2->inheritable || elt1->inheritable);
+ SVN_ERR(combine_with_lastrange(&tmp_range, *output,
+ consider_inheritance,
+ pool));
+ }
+
+ i2++;
+
+ if (elt2->start == elt1->start && elt2->end == elt1->end)
+ i1++;
+ }
+ else if (range_intersect(elt1, elt2, consider_inheritance))
+ {
+ if (elt2->start < elt1->start)
+ {
+ /* The rangelist2 range starts before the rangelist1 range. */
+ svn_merge_range_t tmp_range;
+ if (do_remove)
+ {
+ /* Retain the range that falls before the rangelist1
+ start. */
+ tmp_range.start = elt2->start;
+ tmp_range.end = elt1->start;
+ tmp_range.inheritable = elt2->inheritable;
+ }
+ else
+ {
+ /* Retain the range that falls between the rangelist1
+ start and rangelist2 end. */
+ tmp_range.start = elt1->start;
+ tmp_range.end = MIN(elt2->end, elt1->end);
+ /* The intersection of two ranges is non-inheritable only
+ if both ranges are non-inheritable. */
+ tmp_range.inheritable =
+ (elt2->inheritable || elt1->inheritable);
+ }
+
+ SVN_ERR(combine_with_lastrange(&tmp_range,
+ *output, consider_inheritance,
+ pool));
+ }
+
+ /* Set up the rest of the rangelist2 range for further
+ processing. */
+ if (elt2->end > elt1->end)
+ {
+ /* The rangelist2 range ends after the rangelist1 range. */
+ if (!do_remove)
+ {
+ /* Partial overlap. */
+ svn_merge_range_t tmp_range;
+ tmp_range.start = MAX(elt2->start, elt1->start);
+ tmp_range.end = elt1->end;
+ /* The intersection of two ranges is non-inheritable only
+ if both ranges are non-inheritable. */
+ tmp_range.inheritable =
+ (elt2->inheritable || elt1->inheritable);
+ SVN_ERR(combine_with_lastrange(&tmp_range,
+ *output,
+ consider_inheritance,
+ pool));
+ }
+
+ working_elt2.start = elt1->end;
+ working_elt2.end = elt2->end;
+ }
+ else
+ i2++;
+ }
+ else /* ranges don't intersect */
+ {
+ /* See which side of the rangelist2 the rangelist1 is on. If it
+ is on the left side, we need to move the rangelist1.
+
+ If it is on past the rangelist2 on the right side, we
+ need to output the rangelist2 and increment the
+ rangelist2. */
+ if (svn_sort_compare_ranges(&elt1, &elt2) < 0)
+ i1++;
+ else
+ {
+ svn_merge_range_t *lastrange;
+
+ if ((*output)->nelts > 0)
+ lastrange = APR_ARRAY_IDX(*output, (*output)->nelts - 1,
+ svn_merge_range_t *);
+ else
+ lastrange = NULL;
+
+ if (do_remove && !(lastrange &&
+ combine_ranges(lastrange, lastrange, elt2,
+ consider_inheritance)))
+ {
+ lastrange = svn_merge_range_dup(elt2, pool);
+ APR_ARRAY_PUSH(*output, svn_merge_range_t *) = lastrange;
+ }
+ i2++;
+ }
+ }
+ }
+
+ if (do_remove)
+ {
+ /* Copy the current rangelist2 element if we didn't hit the end
+ of the rangelist2, and we still had it around. This element
+ may have been touched, so we can't just walk the rangelist2
+ array, we have to use our copy. This case only happens when
+ we ran out of rangelist1 before rangelist2, *and* we had changed
+ the rangelist2 element. */
+ if (i2 == lasti2 && i2 < rangelist2->nelts)
+ {
+ SVN_ERR(combine_with_lastrange(&working_elt2, *output,
+ consider_inheritance, pool));
+ i2++;
+ }
+
+ /* Copy any other remaining untouched rangelist2 elements. */
+ for (; i2 < rangelist2->nelts; i2++)
+ {
+ svn_merge_range_t *elt = APR_ARRAY_IDX(rangelist2, i2,
+ svn_merge_range_t *);
+
+ SVN_ERR(combine_with_lastrange(elt, *output,
+ consider_inheritance, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_rangelist_intersect(svn_rangelist_t **output,
+ const svn_rangelist_t *rangelist1,
+ const svn_rangelist_t *rangelist2,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *pool)
+{
+ return rangelist_intersect_or_remove(output, rangelist1, rangelist2, FALSE,
+ consider_inheritance, pool);
+}
+
+svn_error_t *
+svn_rangelist_remove(svn_rangelist_t **output,
+ const svn_rangelist_t *eraser,
+ const svn_rangelist_t *whiteboard,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *pool)
+{
+ return rangelist_intersect_or_remove(output, eraser, whiteboard, TRUE,
+ consider_inheritance, pool);
+}
+
+svn_error_t *
+svn_rangelist_diff(svn_rangelist_t **deleted, svn_rangelist_t **added,
+ const svn_rangelist_t *from, const svn_rangelist_t *to,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *pool)
+{
+ /* The following diagrams illustrate some common range delta scenarios:
+
+ (from) deleted
+ r0 <===========(=========)============[=========]===========> rHEAD
+ [to] added
+
+ (from) deleted deleted
+ r0 <===========(=========[============]=========)===========> rHEAD
+ [to]
+
+ (from) deleted
+ r0 <===========(=========[============)=========]===========> rHEAD
+ [to] added
+
+ (from) deleted
+ r0 <===========[=========(============]=========)===========> rHEAD
+ [to] added
+
+ (from)
+ r0 <===========[=========(============)=========]===========> rHEAD
+ [to] added added
+
+ (from) d d d
+ r0 <===(=[=)=]=[==]=[=(=)=]=[=]=[=(===|===(=)==|=|==[=(=]=)=> rHEAD
+ [to] a a a a a a a
+ */
+
+ /* The items that are present in from, but not in to, must have been
+ deleted. */
+ SVN_ERR(svn_rangelist_remove(deleted, to, from, consider_inheritance,
+ pool));
+ /* The items that are present in to, but not in from, must have been
+ added. */
+ return svn_rangelist_remove(added, from, to, consider_inheritance, pool);
+}
+
+struct mergeinfo_diff_baton
+{
+ svn_mergeinfo_t from;
+ svn_mergeinfo_t to;
+ svn_mergeinfo_t deleted;
+ svn_mergeinfo_t added;
+ svn_boolean_t consider_inheritance;
+ apr_pool_t *pool;
+};
+
+/* This implements the 'svn_hash_diff_func_t' interface.
+ BATON is of type 'struct mergeinfo_diff_baton *'.
+*/
+static svn_error_t *
+mergeinfo_hash_diff_cb(const void *key, apr_ssize_t klen,
+ enum svn_hash_diff_key_status status,
+ void *baton)
+{
+ /* hash_a is FROM mergeinfo,
+ hash_b is TO mergeinfo. */
+ struct mergeinfo_diff_baton *cb = baton;
+ svn_rangelist_t *from_rangelist, *to_rangelist;
+ const char *path = key;
+ if (status == svn_hash_diff_key_both)
+ {
+ /* Record any deltas (additions or deletions). */
+ svn_rangelist_t *deleted_rangelist, *added_rangelist;
+ from_rangelist = apr_hash_get(cb->from, path, klen);
+ to_rangelist = apr_hash_get(cb->to, path, klen);
+ SVN_ERR(svn_rangelist_diff(&deleted_rangelist, &added_rangelist,
+ from_rangelist, to_rangelist,
+ cb->consider_inheritance, cb->pool));
+ if (cb->deleted && deleted_rangelist->nelts > 0)
+ apr_hash_set(cb->deleted, apr_pstrmemdup(cb->pool, path, klen),
+ klen, deleted_rangelist);
+ if (cb->added && added_rangelist->nelts > 0)
+ apr_hash_set(cb->added, apr_pstrmemdup(cb->pool, path, klen),
+ klen, added_rangelist);
+ }
+ else if ((status == svn_hash_diff_key_a) && cb->deleted)
+ {
+ from_rangelist = apr_hash_get(cb->from, path, klen);
+ apr_hash_set(cb->deleted, apr_pstrmemdup(cb->pool, path, klen), klen,
+ svn_rangelist_dup(from_rangelist, cb->pool));
+ }
+ else if ((status == svn_hash_diff_key_b) && cb->added)
+ {
+ to_rangelist = apr_hash_get(cb->to, path, klen);
+ apr_hash_set(cb->added, apr_pstrmemdup(cb->pool, path, klen), klen,
+ svn_rangelist_dup(to_rangelist, cb->pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Record deletions and additions of entire range lists (by path
+ presence), and delegate to svn_rangelist_diff() for delta
+ calculations on a specific path. */
+static svn_error_t *
+walk_mergeinfo_hash_for_diff(svn_mergeinfo_t from, svn_mergeinfo_t to,
+ svn_mergeinfo_t deleted, svn_mergeinfo_t added,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct mergeinfo_diff_baton mdb;
+ mdb.from = from;
+ mdb.to = to;
+ mdb.deleted = deleted;
+ mdb.added = added;
+ mdb.consider_inheritance = consider_inheritance;
+ mdb.pool = result_pool;
+
+ return svn_hash_diff(from, to, mergeinfo_hash_diff_cb, &mdb, scratch_pool);
+}
+
+svn_error_t *
+svn_mergeinfo_diff2(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
+ svn_mergeinfo_t from, svn_mergeinfo_t to,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (from && to == NULL)
+ {
+ *deleted = svn_mergeinfo_dup(from, result_pool);
+ *added = svn_hash__make(result_pool);
+ }
+ else if (from == NULL && to)
+ {
+ *deleted = svn_hash__make(result_pool);
+ *added = svn_mergeinfo_dup(to, result_pool);
+ }
+ else
+ {
+ *deleted = svn_hash__make(result_pool);
+ *added = svn_hash__make(result_pool);
+
+ if (from && to)
+ {
+ SVN_ERR(walk_mergeinfo_hash_for_diff(from, to, *deleted, *added,
+ consider_inheritance,
+ result_pool, scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo__equals(svn_boolean_t *is_equal,
+ svn_mergeinfo_t info1,
+ svn_mergeinfo_t info2,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ *is_equal = FALSE;
+
+ /* special cases: at least one side has no merge info */
+ if (info1 == NULL && info2 == NULL)
+ {
+ *is_equal = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ if (info1 == NULL || info2 == NULL)
+ return SVN_NO_ERROR;
+
+ /* trivial case: different number of paths -> unequal */
+ if (apr_hash_count(info1) != apr_hash_count(info2))
+ return SVN_NO_ERROR;
+
+ /* compare range lists for all paths */
+ for (hi = apr_hash_first(pool, info1); hi; hi = apr_hash_next(hi))
+ {
+ const char *key;
+ apr_ssize_t key_length;
+ svn_rangelist_t *lhs, *rhs;
+ int i;
+ svn_rangelist_t *deleted, *added;
+
+ /* get both path lists */
+ apr_hash_this(hi, (const void**)&key, &key_length, (void **)&lhs);
+ rhs = apr_hash_get(info2, key, key_length);
+
+ /* missing on one side? */
+ if (rhs == NULL)
+ return SVN_NO_ERROR;
+
+ /* quick compare: the range lists will often be a perfect match */
+ if (lhs->nelts == rhs->nelts)
+ {
+ for (i = 0; i < lhs->nelts; ++i)
+ {
+ svn_merge_range_t *lrange
+ = APR_ARRAY_IDX(lhs, i, svn_merge_range_t *);
+ svn_merge_range_t *rrange
+ = APR_ARRAY_IDX(rhs, i, svn_merge_range_t *);
+
+ /* range mismatch? -> needs detailed comparison */
+ if ( lrange->start != rrange->start
+ || lrange->end != rrange->end)
+ break;
+
+ /* inheritance mismatch? -> merge info differs */
+ if ( consider_inheritance
+ && lrange->inheritable != rrange->inheritable)
+ return SVN_NO_ERROR;
+ }
+
+ /* all ranges found to match -> next path */
+ if (i == lhs->nelts)
+ continue;
+ }
+
+ /* range lists differ but there are many ways to sort and aggregate
+ revisions into ranges. Do a full diff on them. */
+ SVN_ERR(svn_rangelist_diff(&deleted, &added, lhs, rhs,
+ consider_inheritance, pool));
+ if (deleted->nelts || added->nelts)
+ return SVN_NO_ERROR;
+ }
+
+ /* no mismatch found */
+ *is_equal = TRUE;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo_merge2(svn_mergeinfo_t mergeinfo,
+ svn_mergeinfo_t changes,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ if (!apr_hash_count(changes))
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, changes); hi; hi = apr_hash_next(hi))
+ {
+ const char *key;
+ apr_ssize_t klen;
+ svn_rangelist_t *to_insert;
+ svn_rangelist_t *target;
+
+ /* get ranges to insert and the target ranges list of that insertion */
+ apr_hash_this(hi, (const void**)&key, &klen, (void*)&to_insert);
+ target = apr_hash_get(mergeinfo, key, klen);
+
+ /* if range list exists, just expand on it.
+ * Otherwise, add new hash entry. */
+ if (target)
+ {
+ SVN_ERR(svn_rangelist_merge2(target, to_insert, result_pool,
+ iterpool));
+ svn_pool_clear(iterpool);
+ }
+ else
+ apr_hash_set(mergeinfo, key, klen, to_insert);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo_catalog_merge(svn_mergeinfo_catalog_t mergeinfo_cat,
+ svn_mergeinfo_catalog_t changes_cat,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i = 0;
+ int j = 0;
+ apr_array_header_t *sorted_cat =
+ svn_sort__hash(mergeinfo_cat, svn_sort_compare_items_as_paths,
+ scratch_pool);
+ apr_array_header_t *sorted_changes =
+ svn_sort__hash(changes_cat, svn_sort_compare_items_as_paths,
+ scratch_pool);
+
+ while (i < sorted_cat->nelts && j < sorted_changes->nelts)
+ {
+ svn_sort__item_t cat_elt, change_elt;
+ int res;
+
+ cat_elt = APR_ARRAY_IDX(sorted_cat, i, svn_sort__item_t);
+ change_elt = APR_ARRAY_IDX(sorted_changes, j, svn_sort__item_t);
+ res = svn_sort_compare_items_as_paths(&cat_elt, &change_elt);
+
+ if (res == 0) /* Both catalogs have mergeinfo for a given path. */
+ {
+ svn_mergeinfo_t mergeinfo = cat_elt.value;
+ svn_mergeinfo_t changes_mergeinfo = change_elt.value;
+
+ SVN_ERR(svn_mergeinfo_merge2(mergeinfo, changes_mergeinfo,
+ result_pool, scratch_pool));
+ apr_hash_set(mergeinfo_cat, cat_elt.key, cat_elt.klen, mergeinfo);
+ i++;
+ j++;
+ }
+ else if (res < 0) /* Only MERGEINFO_CAT has mergeinfo for this path. */
+ {
+ i++;
+ }
+ else /* Only CHANGES_CAT has mergeinfo for this path. */
+ {
+ apr_hash_set(mergeinfo_cat,
+ apr_pstrdup(result_pool, change_elt.key),
+ change_elt.klen,
+ svn_mergeinfo_dup(change_elt.value, result_pool));
+ j++;
+ }
+ }
+
+ /* Copy back any remaining elements from the CHANGES_CAT catalog. */
+ for (; j < sorted_changes->nelts; j++)
+ {
+ svn_sort__item_t elt = APR_ARRAY_IDX(sorted_changes, j,
+ svn_sort__item_t);
+ apr_hash_set(mergeinfo_cat,
+ apr_pstrdup(result_pool, elt.key),
+ elt.klen,
+ svn_mergeinfo_dup(elt.value, result_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo_intersect2(svn_mergeinfo_t *mergeinfo,
+ svn_mergeinfo_t mergeinfo1,
+ svn_mergeinfo_t mergeinfo2,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ *mergeinfo = apr_hash_make(result_pool);
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* ### TODO(reint): Do we care about the case when a path in one
+ ### mergeinfo hash has inheritable mergeinfo, and in the other
+ ### has non-inhertiable mergeinfo? It seems like that path
+ ### itself should really be an intersection, while child paths
+ ### should not be... */
+ for (hi = apr_hash_first(scratch_pool, mergeinfo1);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *rangelist1 = svn__apr_hash_index_val(hi);
+ svn_rangelist_t *rangelist2;
+
+ svn_pool_clear(iterpool);
+ rangelist2 = svn_hash_gets(mergeinfo2, path);
+ if (rangelist2)
+ {
+ SVN_ERR(svn_rangelist_intersect(&rangelist2, rangelist1, rangelist2,
+ consider_inheritance, iterpool));
+ if (rangelist2->nelts > 0)
+ svn_hash_sets(*mergeinfo, apr_pstrdup(result_pool, path),
+ svn_rangelist_dup(rangelist2, result_pool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo_remove2(svn_mergeinfo_t *mergeinfo,
+ svn_mergeinfo_t eraser,
+ svn_mergeinfo_t whiteboard,
+ svn_boolean_t consider_inheritance,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *mergeinfo = apr_hash_make(result_pool);
+ return walk_mergeinfo_hash_for_diff(whiteboard, eraser, *mergeinfo, NULL,
+ consider_inheritance, result_pool,
+ scratch_pool);
+}
+
+svn_error_t *
+svn_rangelist_to_string(svn_string_t **output,
+ const svn_rangelist_t *rangelist,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool);
+
+ if (rangelist->nelts > 0)
+ {
+ int i;
+ svn_merge_range_t *range;
+
+ /* Handle the elements that need commas at the end. */
+ for (i = 0; i < rangelist->nelts - 1; i++)
+ {
+ range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ svn_stringbuf_appendcstr(buf, range_to_string(range, pool));
+ svn_stringbuf_appendcstr(buf, ",");
+ }
+
+ /* Now handle the last element, which needs no comma. */
+ range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ svn_stringbuf_appendcstr(buf, range_to_string(range, pool));
+ }
+
+ *output = svn_stringbuf__morph_into_string(buf);
+
+ return SVN_NO_ERROR;
+}
+
+/* Converts a mergeinfo INPUT to an unparsed mergeinfo in OUTPUT. If PREFIX
+ is not NULL then prepend PREFIX to each line in OUTPUT. If INPUT contains
+ no elements, return the empty string. If INPUT contains any merge source
+ path keys that are relative then convert these to absolute paths in
+ *OUTPUT.
+ */
+static svn_error_t *
+mergeinfo_to_stringbuf(svn_stringbuf_t **output,
+ svn_mergeinfo_t input,
+ const char *prefix,
+ apr_pool_t *pool)
+{
+ *output = svn_stringbuf_create_empty(pool);
+
+ if (apr_hash_count(input) > 0)
+ {
+ apr_array_header_t *sorted =
+ svn_sort__hash(input, svn_sort_compare_items_as_paths, pool);
+ int i;
+
+ for (i = 0; i < sorted->nelts; i++)
+ {
+ svn_sort__item_t elt = APR_ARRAY_IDX(sorted, i, svn_sort__item_t);
+ svn_string_t *revlist;
+
+ SVN_ERR(svn_rangelist_to_string(&revlist, elt.value, pool));
+ svn_stringbuf_appendcstr(
+ *output,
+ apr_psprintf(pool, "%s%s%s:%s",
+ prefix ? prefix : "",
+ *((const char *) elt.key) == '/' ? "" : "/",
+ (const char *) elt.key,
+ revlist->data));
+ if (i < sorted->nelts - 1)
+ svn_stringbuf_appendcstr(*output, "\n");
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo_to_string(svn_string_t **output, svn_mergeinfo_t input,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *mergeinfo_buf;
+
+ SVN_ERR(mergeinfo_to_stringbuf(&mergeinfo_buf, input, NULL, pool));
+ *output = svn_stringbuf__morph_into_string(mergeinfo_buf);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo_sort(svn_mergeinfo_t input, apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, input); hi; hi = apr_hash_next(hi))
+ {
+ apr_array_header_t *rl = svn__apr_hash_index_val(hi);
+
+ qsort(rl->elts, rl->nelts, rl->elt_size, svn_sort_compare_ranges);
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_mergeinfo_catalog_t
+svn_mergeinfo_catalog_dup(svn_mergeinfo_catalog_t mergeinfo_catalog,
+ apr_pool_t *pool)
+{
+ svn_mergeinfo_t new_mergeinfo_catalog = apr_hash_make(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, mergeinfo_catalog);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *key = svn__apr_hash_index_key(hi);
+ svn_mergeinfo_t val = svn__apr_hash_index_val(hi);
+
+ svn_hash_sets(new_mergeinfo_catalog, apr_pstrdup(pool, key),
+ svn_mergeinfo_dup(val, pool));
+ }
+
+ return new_mergeinfo_catalog;
+}
+
+svn_mergeinfo_t
+svn_mergeinfo_dup(svn_mergeinfo_t mergeinfo, apr_pool_t *pool)
+{
+ svn_mergeinfo_t new_mergeinfo = svn_hash__make(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ apr_ssize_t pathlen = svn__apr_hash_index_klen(hi);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+
+ apr_hash_set(new_mergeinfo, apr_pstrmemdup(pool, path, pathlen), pathlen,
+ svn_rangelist_dup(rangelist, pool));
+ }
+
+ return new_mergeinfo;
+}
+
+svn_error_t *
+svn_mergeinfo_inheritable2(svn_mergeinfo_t *output,
+ svn_mergeinfo_t mergeinfo,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t inheritable,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ svn_mergeinfo_t inheritable_mergeinfo = apr_hash_make(result_pool);
+
+ for (hi = apr_hash_first(scratch_pool, mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *key = svn__apr_hash_index_key(hi);
+ apr_ssize_t keylen = svn__apr_hash_index_klen(hi);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+ svn_rangelist_t *inheritable_rangelist;
+
+ if (!path || svn_path_compare_paths(path, key) == 0)
+ SVN_ERR(svn_rangelist_inheritable2(&inheritable_rangelist, rangelist,
+ start, end, inheritable,
+ result_pool, scratch_pool));
+ else
+ inheritable_rangelist = svn_rangelist_dup(rangelist, result_pool);
+
+ /* Only add this rangelist if some ranges remain. A rangelist with
+ a path mapped to an empty rangelist is not syntactically valid */
+ if (inheritable_rangelist->nelts)
+ apr_hash_set(inheritable_mergeinfo,
+ apr_pstrmemdup(result_pool, key, keylen), keylen,
+ inheritable_rangelist);
+ }
+ *output = inheritable_mergeinfo;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_rangelist_inheritable2(svn_rangelist_t **inheritable_rangelist,
+ const svn_rangelist_t *rangelist,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t inheritable,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *inheritable_rangelist = apr_array_make(result_pool, 1,
+ sizeof(svn_merge_range_t *));
+ if (rangelist->nelts)
+ {
+ if (!SVN_IS_VALID_REVNUM(start)
+ || !SVN_IS_VALID_REVNUM(end)
+ || end < start)
+ {
+ int i;
+ /* We want all non-inheritable ranges removed. */
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
+ svn_merge_range_t *);
+ if (range->inheritable == inheritable)
+ {
+ svn_merge_range_t *inheritable_range =
+ apr_palloc(result_pool, sizeof(*inheritable_range));
+ inheritable_range->start = range->start;
+ inheritable_range->end = range->end;
+ inheritable_range->inheritable = TRUE;
+ APR_ARRAY_PUSH(*inheritable_rangelist,
+ svn_merge_range_t *) = range;
+ }
+ }
+ }
+ else
+ {
+ /* We want only the non-inheritable ranges bound by START
+ and END removed. */
+ svn_rangelist_t *ranges_inheritable =
+ svn_rangelist__initialize(start, end, inheritable, scratch_pool);
+
+ if (rangelist->nelts)
+ SVN_ERR(svn_rangelist_remove(inheritable_rangelist,
+ ranges_inheritable,
+ rangelist,
+ TRUE,
+ result_pool));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_mergeinfo__remove_empty_rangelists(svn_mergeinfo_t mergeinfo,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ svn_boolean_t removed_some_ranges = FALSE;
+
+ if (mergeinfo)
+ {
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+
+ if (rangelist->nelts == 0)
+ {
+ svn_hash_sets(mergeinfo, path, NULL);
+ removed_some_ranges = TRUE;
+ }
+ }
+ }
+ return removed_some_ranges;
+}
+
+svn_error_t *
+svn_mergeinfo__remove_prefix_from_catalog(svn_mergeinfo_catalog_t *out_catalog,
+ svn_mergeinfo_catalog_t in_catalog,
+ const char *prefix_path,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ SVN_ERR_ASSERT(prefix_path[0] == '/');
+
+ *out_catalog = apr_hash_make(pool);
+
+ for (hi = apr_hash_first(pool, in_catalog); hi; hi = apr_hash_next(hi))
+ {
+ const char *original_path = svn__apr_hash_index_key(hi);
+ svn_mergeinfo_t value = svn__apr_hash_index_val(hi);
+ const char *new_path;
+
+ new_path = svn_fspath__skip_ancestor(prefix_path, original_path);
+ SVN_ERR_ASSERT(new_path);
+
+ svn_hash_sets(*out_catalog, new_path, value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo__add_prefix_to_catalog(svn_mergeinfo_catalog_t *out_catalog,
+ svn_mergeinfo_catalog_t in_catalog,
+ const char *prefix_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ *out_catalog = apr_hash_make(result_pool);
+
+ for (hi = apr_hash_first(scratch_pool, in_catalog);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *original_path = svn__apr_hash_index_key(hi);
+ svn_mergeinfo_t value = svn__apr_hash_index_val(hi);
+
+ if (original_path[0] == '/')
+ original_path++;
+
+ svn_hash_sets(*out_catalog,
+ svn_dirent_join(prefix_path, original_path, result_pool),
+ value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo__add_suffix_to_mergeinfo(svn_mergeinfo_t *out_mergeinfo,
+ svn_mergeinfo_t mergeinfo,
+ const char *suffix_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ SVN_ERR_ASSERT(suffix_relpath && svn_relpath_is_canonical(suffix_relpath));
+
+ *out_mergeinfo = apr_hash_make(result_pool);
+
+ for (hi = apr_hash_first(scratch_pool, mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *fspath = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+
+ svn_hash_sets(*out_mergeinfo,
+ svn_fspath__join(fspath, suffix_relpath, result_pool),
+ rangelist);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_rangelist_t *
+svn_rangelist_dup(const svn_rangelist_t *rangelist, apr_pool_t *pool)
+{
+ svn_rangelist_t *new_rl = apr_array_make(pool, rangelist->nelts,
+ sizeof(svn_merge_range_t *));
+
+ /* allocate target range buffer with a single operation */
+ svn_merge_range_t *copy = apr_palloc(pool, sizeof(*copy) * rangelist->nelts);
+ int i;
+
+ /* fill it iteratively and link it into the range list */
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ memcpy(copy + i,
+ APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *),
+ sizeof(*copy));
+ APR_ARRAY_PUSH(new_rl, svn_merge_range_t *) = copy + i;
+ }
+
+ return new_rl;
+}
+
+svn_merge_range_t *
+svn_merge_range_dup(const svn_merge_range_t *range, apr_pool_t *pool)
+{
+ svn_merge_range_t *new_range = apr_palloc(pool, sizeof(*new_range));
+ memcpy(new_range, range, sizeof(*new_range));
+ return new_range;
+}
+
+svn_boolean_t
+svn_merge_range_contains_rev(const svn_merge_range_t *range, svn_revnum_t rev)
+{
+ assert(SVN_IS_VALID_REVNUM(range->start));
+ assert(SVN_IS_VALID_REVNUM(range->end));
+ assert(range->start != range->end);
+
+ if (range->start < range->end)
+ return rev > range->start && rev <= range->end;
+ else
+ return rev > range->end && rev <= range->start;
+}
+
+svn_error_t *
+svn_mergeinfo__catalog_to_formatted_string(svn_string_t **output,
+ svn_mergeinfo_catalog_t catalog,
+ const char *key_prefix,
+ const char *val_prefix,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *output_buf = NULL;
+
+ if (catalog && apr_hash_count(catalog))
+ {
+ int i;
+ apr_array_header_t *sorted_catalog =
+ svn_sort__hash(catalog, svn_sort_compare_items_as_paths, pool);
+
+ output_buf = svn_stringbuf_create_empty(pool);
+ for (i = 0; i < sorted_catalog->nelts; i++)
+ {
+ svn_sort__item_t elt =
+ APR_ARRAY_IDX(sorted_catalog, i, svn_sort__item_t);
+ const char *path1;
+ svn_mergeinfo_t mergeinfo;
+ svn_stringbuf_t *mergeinfo_output_buf;
+
+ path1 = elt.key;
+ mergeinfo = elt.value;
+ if (key_prefix)
+ svn_stringbuf_appendcstr(output_buf, key_prefix);
+ svn_stringbuf_appendcstr(output_buf, path1);
+ svn_stringbuf_appendcstr(output_buf, "\n");
+ SVN_ERR(mergeinfo_to_stringbuf(&mergeinfo_output_buf, mergeinfo,
+ val_prefix ? val_prefix : "", pool));
+ svn_stringbuf_appendstr(output_buf, mergeinfo_output_buf);
+ svn_stringbuf_appendcstr(output_buf, "\n");
+ }
+ }
+#if SVN_DEBUG
+ else if (!catalog)
+ {
+ output_buf = svn_stringbuf_create(key_prefix ? key_prefix : "", pool);
+ svn_stringbuf_appendcstr(output_buf, _("NULL mergeinfo catalog\n"));
+ }
+ else if (apr_hash_count(catalog) == 0)
+ {
+ output_buf = svn_stringbuf_create(key_prefix ? key_prefix : "", pool);
+ svn_stringbuf_appendcstr(output_buf, _("empty mergeinfo catalog\n"));
+ }
+#endif
+
+ /* If we have an output_buf, convert it to an svn_string_t;
+ otherwise, return a new string containing only a newline
+ character. */
+ if (output_buf)
+ *output = svn_stringbuf__morph_into_string(output_buf);
+ else
+ *output = svn_string_create("\n", pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo__get_range_endpoints(svn_revnum_t *youngest_rev,
+ svn_revnum_t *oldest_rev,
+ svn_mergeinfo_t mergeinfo,
+ apr_pool_t *pool)
+{
+ *youngest_rev = *oldest_rev = SVN_INVALID_REVNUM;
+ if (mergeinfo)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+
+ if (rangelist->nelts)
+ {
+ svn_merge_range_t *range = APR_ARRAY_IDX(rangelist,
+ rangelist->nelts - 1,
+ svn_merge_range_t *);
+ if (!SVN_IS_VALID_REVNUM(*youngest_rev)
+ || (range->end > *youngest_rev))
+ *youngest_rev = range->end;
+
+ range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *);
+ if (!SVN_IS_VALID_REVNUM(*oldest_rev)
+ || (range->start < *oldest_rev))
+ *oldest_rev = range->start;
+ }
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo__filter_catalog_by_ranges(svn_mergeinfo_catalog_t *filtered_cat,
+ svn_mergeinfo_catalog_t catalog,
+ svn_revnum_t youngest_rev,
+ svn_revnum_t oldest_rev,
+ svn_boolean_t include_range,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ *filtered_cat = apr_hash_make(result_pool);
+ for (hi = apr_hash_first(scratch_pool, catalog);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_mergeinfo_t mergeinfo = svn__apr_hash_index_val(hi);
+ svn_mergeinfo_t filtered_mergeinfo;
+
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(&filtered_mergeinfo,
+ mergeinfo,
+ youngest_rev,
+ oldest_rev,
+ include_range,
+ result_pool,
+ scratch_pool));
+ if (apr_hash_count(filtered_mergeinfo))
+ svn_hash_sets(*filtered_cat,
+ apr_pstrdup(result_pool, path), filtered_mergeinfo);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo__filter_mergeinfo_by_ranges(svn_mergeinfo_t *filtered_mergeinfo,
+ svn_mergeinfo_t mergeinfo,
+ svn_revnum_t youngest_rev,
+ svn_revnum_t oldest_rev,
+ svn_boolean_t include_range,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(oldest_rev));
+ SVN_ERR_ASSERT(oldest_rev < youngest_rev);
+
+ *filtered_mergeinfo = apr_hash_make(result_pool);
+
+ if (mergeinfo)
+ {
+ apr_hash_index_t *hi;
+ svn_rangelist_t *filter_rangelist =
+ svn_rangelist__initialize(oldest_rev, youngest_rev, TRUE,
+ scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+
+ if (rangelist->nelts)
+ {
+ svn_rangelist_t *new_rangelist;
+
+ SVN_ERR(rangelist_intersect_or_remove(
+ &new_rangelist, filter_rangelist, rangelist,
+ ! include_range, FALSE, result_pool));
+
+ if (new_rangelist->nelts)
+ svn_hash_sets(*filtered_mergeinfo,
+ apr_pstrdup(result_pool, path), new_rangelist);
+ }
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mergeinfo__adjust_mergeinfo_rangelists(svn_mergeinfo_t *adjusted_mergeinfo,
+ svn_mergeinfo_t mergeinfo,
+ svn_revnum_t offset,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ *adjusted_mergeinfo = apr_hash_make(result_pool);
+
+ if (mergeinfo)
+ {
+ for (hi = apr_hash_first(scratch_pool, mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ int i;
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+ svn_rangelist_t *adjusted_rangelist =
+ apr_array_make(result_pool, rangelist->nelts,
+ sizeof(svn_merge_range_t *));
+
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_merge_range_t *range =
+ APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+
+ if (range->start + offset > 0 && range->end + offset > 0)
+ {
+ if (range->start + offset < 0)
+ range->start = 0;
+ else
+ range->start = range->start + offset;
+
+ if (range->end + offset < 0)
+ range->end = 0;
+ else
+ range->end = range->end + offset;
+ APR_ARRAY_PUSH(adjusted_rangelist, svn_merge_range_t *) =
+ range;
+ }
+ }
+
+ if (adjusted_rangelist->nelts)
+ svn_hash_sets(*adjusted_mergeinfo, apr_pstrdup(result_pool, path),
+ adjusted_rangelist);
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_mergeinfo__is_noninheritable(svn_mergeinfo_t mergeinfo,
+ apr_pool_t *scratch_pool)
+{
+ if (mergeinfo)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+ int i;
+
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
+ svn_merge_range_t *);
+ if (!range->inheritable)
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+svn_rangelist_t *
+svn_rangelist__initialize(svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t inheritable,
+ apr_pool_t *result_pool)
+{
+ svn_rangelist_t *rangelist =
+ apr_array_make(result_pool, 1, sizeof(svn_merge_range_t *));
+ svn_merge_range_t *range = apr_pcalloc(result_pool, sizeof(*range));
+
+ range->start = start;
+ range->end = end;
+ range->inheritable = inheritable;
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = range;
+ return rangelist;
+}
+
+svn_error_t *
+svn_mergeinfo__mergeinfo_from_segments(svn_mergeinfo_t *mergeinfo_p,
+ const apr_array_header_t *segments,
+ apr_pool_t *pool)
+{
+ svn_mergeinfo_t mergeinfo = apr_hash_make(pool);
+ int i;
+
+ /* Translate location segments into merge sources and ranges. */
+ for (i = 0; i < segments->nelts; i++)
+ {
+ svn_location_segment_t *segment =
+ APR_ARRAY_IDX(segments, i, svn_location_segment_t *);
+ svn_rangelist_t *path_ranges;
+ svn_merge_range_t *range;
+ const char *source_path;
+
+ /* No path segment? Skip it. */
+ if (! segment->path)
+ continue;
+
+ /* Prepend a leading slash to our path. */
+ source_path = apr_pstrcat(pool, "/", segment->path, (char *)NULL);
+
+ /* See if we already stored ranges for this path. If not, make
+ a new list. */
+ path_ranges = svn_hash_gets(mergeinfo, source_path);
+ if (! path_ranges)
+ path_ranges = apr_array_make(pool, 1, sizeof(range));
+
+ /* A svn_location_segment_t may have legitimately describe only
+ revision 0, but there is no corresponding representation for
+ this in a svn_merge_range_t. */
+ if (segment->range_start == 0 && segment->range_end == 0)
+ continue;
+
+ /* Build a merge range, push it onto the list of ranges, and for
+ good measure, (re)store it in the hash. */
+ range = apr_pcalloc(pool, sizeof(*range));
+ range->start = MAX(segment->range_start - 1, 0);
+ range->end = segment->range_end;
+ range->inheritable = TRUE;
+ APR_ARRAY_PUSH(path_ranges, svn_merge_range_t *) = range;
+ svn_hash_sets(mergeinfo, source_path, path_ranges);
+ }
+
+ *mergeinfo_p = mergeinfo;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_rangelist__merge_many(svn_rangelist_t *merged_rangelist,
+ svn_mergeinfo_t merge_history,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (apr_hash_count(merge_history))
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, merge_history);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_rangelist_t *subtree_rangelist = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_rangelist_merge2(merged_rangelist, subtree_rangelist,
+ result_pool, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+const char *
+svn_inheritance_to_word(svn_mergeinfo_inheritance_t inherit)
+{
+ switch (inherit)
+ {
+ case svn_mergeinfo_inherited:
+ return "inherited";
+ case svn_mergeinfo_nearest_ancestor:
+ return "nearest-ancestor";
+ default:
+ return "explicit";
+ }
+}
+
+svn_mergeinfo_inheritance_t
+svn_inheritance_from_word(const char *word)
+{
+ if (strcmp(word, "inherited") == 0)
+ return svn_mergeinfo_inherited;
+ if (strcmp(word, "nearest-ancestor") == 0)
+ return svn_mergeinfo_nearest_ancestor;
+ return svn_mergeinfo_explicit;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/mutex.c b/contrib/subversion/subversion/libsvn_subr/mutex.c
new file mode 100644
index 0000000..04988eb
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/mutex.c
@@ -0,0 +1,83 @@
+/*
+ * svn_mutex.c: routines for mutual exclusion.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_private_config.h"
+#include "private/svn_mutex.h"
+
+svn_error_t *
+svn_mutex__init(svn_mutex__t **mutex_p,
+ svn_boolean_t mutex_required,
+ apr_pool_t *result_pool)
+{
+ /* always initialize the mutex pointer, even though it is not
+ strictly necessary if APR_HAS_THREADS has not been set */
+ *mutex_p = NULL;
+
+#if APR_HAS_THREADS
+ if (mutex_required)
+ {
+ apr_thread_mutex_t *apr_mutex;
+ apr_status_t status =
+ apr_thread_mutex_create(&apr_mutex,
+ APR_THREAD_MUTEX_DEFAULT,
+ result_pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't create mutex"));
+
+ *mutex_p = apr_mutex;
+ }
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mutex__lock(svn_mutex__t *mutex)
+{
+#if APR_HAS_THREADS
+ if (mutex)
+ {
+ apr_status_t status = apr_thread_mutex_lock(mutex);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't lock mutex"));
+ }
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_mutex__unlock(svn_mutex__t *mutex,
+ svn_error_t *err)
+{
+#if APR_HAS_THREADS
+ if (mutex)
+ {
+ apr_status_t status = apr_thread_mutex_unlock(mutex);
+ if (status && !err)
+ return svn_error_wrap_apr(status, _("Can't unlock mutex"));
+ }
+#endif
+
+ return err;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/named_atomic.c b/contrib/subversion/subversion/libsvn_subr/named_atomic.c
new file mode 100644
index 0000000..d07e742
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/named_atomic.c
@@ -0,0 +1,655 @@
+/*
+ * svn_named_atomic.c: routines for machine-wide named atomics.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "private/svn_named_atomic.h"
+
+#include <apr_global_mutex.h>
+#include <apr_mmap.h>
+
+#include "svn_private_config.h"
+#include "private/svn_atomic.h"
+#include "private/svn_mutex.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_io.h"
+
+/* Implementation aspects.
+ *
+ * We use a single shared memory block (memory mapped file) that will be
+ * created by the first user and merely mapped by all subsequent ones.
+ * The memory block contains an short header followed by a fixed-capacity
+ * array of named atomics. The number of entries currently in use is stored
+ * in the header part.
+ *
+ * Finding / creating the MMAP object as well as adding new array entries
+ * is being guarded by an APR global mutex. Since releasing the MMAP
+ * structure and closing the underlying does not affect other users of the
+ * same, cleanup will not be synchronized.
+ *
+ * The array is append-only. Once a process mapped the block into its
+ * address space, it may freely access any of the used entries. However,
+ * it must synchronize access to the volatile data within the entries.
+ * On Windows and where otherwise supported by GCC, lightweight "lock-free"
+ * synchronization will be used. Other targets serialize all access using
+ * a global mutex.
+ *
+ * Atomics will be identified by their name (a short string) and lookup
+ * takes linear time. But even that takes only about 10 microseconds for a
+ * full array scan -- which is in the same order of magnitude than e.g. a
+ * single global mutex lock / unlock pair.
+ */
+
+/* Capacity of our shared memory object, i.e. max number of named atomics
+ * that may be created. Should have the form 2**N - 1.
+ */
+#define MAX_ATOMIC_COUNT 1023
+
+/* We choose the size of a single named atomic object to fill a complete
+ * cache line (on most architectures). Thereby, we minimize the cache
+ * sync. overhead between different CPU cores.
+ */
+#define CACHE_LINE_LENGTH 64
+
+/* We need 8 bytes for the actual value and the remainder is used to
+ * store the NUL-terminated name.
+ *
+ * Must not be smaller than SVN_NAMED_ATOMIC__MAX_NAME_LENGTH.
+ */
+#define MAX_NAME_LENGTH (CACHE_LINE_LENGTH - sizeof(apr_int64_t) - 1)
+
+/* Particle that will be appended to the namespace name to form the
+ * name of the mutex / lock file used for that namespace.
+ */
+#define MUTEX_NAME_SUFFIX ".mutex"
+
+/* Particle that will be appended to the namespace name to form the
+ * name of the shared memory file that backs that namespace.
+ */
+#define SHM_NAME_SUFFIX ".shm"
+
+/* Platform-dependent implementations of our basic atomic operations.
+ * NA_SYNCHRONIZE(op) will ensure that the OP gets executed atomically.
+ * This will be zero-overhead if OP itself is already atomic.
+ *
+ * (We don't call it SYNCHRONIZE because Windows has a preprocess macro by
+ * that name.)
+ *
+ * The default implementation will use the same mutex for initialization
+ * as well as any type of data access. This is quite expensive and we
+ * can do much better on most platforms.
+ */
+#if defined(WIN32) && ((_WIN32_WINNT >= 0x0502) || defined(InterlockedExchangeAdd64))
+
+/* Interlocked API / intrinsics guarantee full data synchronization
+ */
+#define synched_read(mem) *mem
+#define synched_write(mem, value) InterlockedExchange64(mem, value)
+#define synched_add(mem, delta) InterlockedExchangeAdd64(mem, delta)
+#define synched_cmpxchg(mem, value, comperand) \
+ InterlockedCompareExchange64(mem, value, comperand)
+
+#define NA_SYNCHRONIZE(_atomic,op) op;
+#define NA_SYNCHRONIZE_IS_FAST TRUE
+
+#elif SVN_HAS_ATOMIC_BUILTINS
+
+/* GCC provides atomic intrinsics for most common CPU types
+ */
+#define synched_read(mem) *mem
+#define synched_write(mem, value) __sync_lock_test_and_set(mem, value)
+#define synched_add(mem, delta) __sync_add_and_fetch(mem, delta)
+#define synched_cmpxchg(mem, value, comperand) \
+ __sync_val_compare_and_swap(mem, comperand, value)
+
+#define NA_SYNCHRONIZE(_atomic,op) op;
+#define NA_SYNCHRONIZE_IS_FAST TRUE
+
+#else
+
+/* Default implementation
+ */
+static apr_int64_t
+synched_read(volatile apr_int64_t *mem)
+{
+ return *mem;
+}
+
+static apr_int64_t
+synched_write(volatile apr_int64_t *mem, apr_int64_t value)
+{
+ apr_int64_t old_value = *mem;
+ *mem = value;
+
+ return old_value;
+}
+
+static apr_int64_t
+synched_add(volatile apr_int64_t *mem, apr_int64_t delta)
+{
+ return *mem += delta;
+}
+
+static apr_int64_t
+synched_cmpxchg(volatile apr_int64_t *mem,
+ apr_int64_t value,
+ apr_int64_t comperand)
+{
+ apr_int64_t old_value = *mem;
+ if (old_value == comperand)
+ *mem = value;
+
+ return old_value;
+}
+
+#define NA_SYNCHRONIZE(_atomic,op)\
+ do{\
+ SVN_ERR(lock(_atomic->mutex));\
+ op;\
+ SVN_ERR(unlock(_atomic->mutex,SVN_NO_ERROR));\
+ }while(0)
+
+#define NA_SYNCHRONIZE_IS_FAST FALSE
+
+#endif
+
+/* Structure describing a single atomic: its VALUE and NAME.
+ */
+struct named_atomic_data_t
+{
+ volatile apr_int64_t value;
+ char name[MAX_NAME_LENGTH + 1];
+};
+
+/* Content of our shared memory buffer. COUNT is the number
+ * of used entries in ATOMICS. Insertion is append-only.
+ * PADDING is used to align the header information with the
+ * atomics to create a favorable data alignment.
+ */
+struct shared_data_t
+{
+ volatile apr_uint32_t count;
+ char padding [sizeof(struct named_atomic_data_t) - sizeof(apr_uint32_t)];
+
+ struct named_atomic_data_t atomics[MAX_ATOMIC_COUNT];
+};
+
+/* Structure combining all objects that we need for access serialization.
+ */
+struct mutex_t
+{
+ /* Inter-process sync. is handled by through lock file. */
+ apr_file_t *lock_file;
+
+ /* Pool to be used with lock / unlock functions */
+ apr_pool_t *pool;
+};
+
+/* API structure combining the atomic data and the access mutex
+ */
+struct svn_named_atomic__t
+{
+ /* pointer into the shared memory */
+ struct named_atomic_data_t *data;
+
+ /* sync. object; never NULL (even if unused) */
+ struct mutex_t *mutex;
+};
+
+/* This is intended to be a singleton struct. It contains all
+ * information necessary to initialize and access the shared
+ * memory.
+ */
+struct svn_atomic_namespace__t
+{
+ /* Pointer to the shared data mapped into our process */
+ struct shared_data_t *data;
+
+ /* Last time we checked, this was the number of used
+ * (i.e. fully initialized) items. I.e. we can read
+ * their names without further sync. */
+ volatile svn_atomic_t min_used;
+
+ /* for each atomic in the shared memory, we hand out
+ * at most one API-level object. */
+ struct svn_named_atomic__t atomics[MAX_ATOMIC_COUNT];
+
+ /* Synchronization object for this namespace */
+ struct mutex_t mutex;
+};
+
+/* On most operating systems APR implements file locks per process, not
+ * per file. I.e. the lock file will only sync. among processes but within
+ * a process, we must use a mutex to sync the threads. */
+/* Compare ../libsvn_fs_fs/fs.h:SVN_FS_FS__USE_LOCK_MUTEX */
+#if APR_HAS_THREADS && !defined(WIN32)
+#define USE_THREAD_MUTEX 1
+#else
+#define USE_THREAD_MUTEX 0
+#endif
+
+/* Used for process-local thread sync.
+ */
+static svn_mutex__t *thread_mutex = NULL;
+
+/* Initialization flag for the above used by svn_atomic__init_once.
+ */
+static volatile svn_atomic_t mutex_initialized = FALSE;
+
+/* Initialize the thread sync. structures.
+ * To be called by svn_atomic__init_once.
+ */
+static svn_error_t *
+init_thread_mutex(void *baton, apr_pool_t *pool)
+{
+ /* let the mutex live as long as the APR */
+ apr_pool_t *global_pool = svn_pool_create(NULL);
+
+ return svn_mutex__init(&thread_mutex, USE_THREAD_MUTEX, global_pool);
+}
+
+/* Utility that acquires our global mutex and converts error types.
+ */
+static svn_error_t *
+lock(struct mutex_t *mutex)
+{
+ svn_error_t *err;
+
+ /* Get lock on the filehandle. */
+ SVN_ERR(svn_mutex__lock(thread_mutex));
+ err = svn_io_lock_open_file(mutex->lock_file, TRUE, FALSE, mutex->pool);
+
+ return err
+ ? svn_mutex__unlock(thread_mutex, err)
+ : err;
+}
+
+/* Utility that releases the lock previously acquired via lock(). If the
+ * unlock succeeds and OUTER_ERR is not NULL, OUTER_ERR will be returned.
+ * Otherwise, return the result of the unlock operation.
+ */
+static svn_error_t *
+unlock(struct mutex_t *mutex, svn_error_t * outer_err)
+{
+ svn_error_t *unlock_err
+ = svn_io_unlock_open_file(mutex->lock_file, mutex->pool);
+ return svn_mutex__unlock(thread_mutex,
+ svn_error_compose_create(outer_err,
+ unlock_err));
+}
+
+/* The last user to close a particular namespace should also remove the
+ * lock file. Failure to do so, however, does not affect further uses
+ * of the same namespace.
+ */
+static apr_status_t
+delete_lock_file(void *arg)
+{
+ struct mutex_t *mutex = arg;
+ const char *lock_name = NULL;
+
+ /* locks have already been cleaned up. Simply close the file */
+ apr_status_t status = apr_file_close(mutex->lock_file);
+
+ /* Remove the file from disk. This will fail if there ares still other
+ * users of this lock file, i.e. namespace. */
+ apr_file_name_get(&lock_name, mutex->lock_file);
+ if (lock_name)
+ apr_file_remove(lock_name, mutex->pool);
+
+ return status;
+}
+
+/* Validate the ATOMIC parameter, i.e it's address. Correct code will
+ * never need this but if someone should accidentally to use a NULL or
+ * incomplete structure, let's catch that here instead of segfaulting.
+ */
+static svn_error_t *
+validate(svn_named_atomic__t *atomic)
+{
+ return atomic && atomic->data && atomic->mutex
+ ? SVN_NO_ERROR
+ : svn_error_create(SVN_ERR_BAD_ATOMIC, 0, _("Not a valid atomic"));
+}
+
+/* Auto-initialize and return in *ATOMIC the API-level object for the
+ * atomic with index I within NS. */
+static void
+return_atomic(svn_named_atomic__t **atomic,
+ svn_atomic_namespace__t *ns,
+ int i)
+{
+ *atomic = &ns->atomics[i];
+ if (ns->atomics[i].data == NULL)
+ {
+ (*atomic)->mutex = &ns->mutex;
+ (*atomic)->data = &ns->data->atomics[i];
+ }
+}
+
+/* Implement API */
+
+svn_boolean_t
+svn_named_atomic__is_supported(void)
+{
+#ifdef _WIN32
+ static svn_tristate_t result = svn_tristate_unknown;
+
+ if (result == svn_tristate_unknown)
+ {
+ /* APR SHM implementation requires the creation of global objects */
+ HANDLE handle = CreateFileMappingA(INVALID_HANDLE_VALUE,
+ NULL,
+ PAGE_READONLY,
+ 0,
+ 1,
+ "Global\\__RandomXZY_svn");
+ if (handle != NULL)
+ {
+ CloseHandle(handle);
+ result = svn_tristate_true;
+ }
+ else
+ result = svn_tristate_false;
+ }
+
+ return result == svn_tristate_true;
+#else
+ return TRUE;
+#endif
+}
+
+svn_boolean_t
+svn_named_atomic__is_efficient(void)
+{
+ return NA_SYNCHRONIZE_IS_FAST;
+}
+
+svn_error_t *
+svn_atomic_namespace__create(svn_atomic_namespace__t **ns,
+ const char *name,
+ apr_pool_t *result_pool)
+{
+ apr_status_t apr_err;
+ svn_error_t *err;
+ apr_file_t *file;
+ apr_mmap_t *mmap;
+ const char *shm_name, *lock_name;
+ apr_finfo_t finfo;
+
+ apr_pool_t *subpool = svn_pool_create(result_pool);
+
+ /* allocate the namespace data structure
+ */
+ svn_atomic_namespace__t *new_ns = apr_pcalloc(result_pool, sizeof(**ns));
+
+ /* construct the names of the system objects that we need
+ */
+ shm_name = apr_pstrcat(subpool, name, SHM_NAME_SUFFIX, NULL);
+ lock_name = apr_pstrcat(subpool, name, MUTEX_NAME_SUFFIX, NULL);
+
+ /* initialize the lock objects
+ */
+ SVN_ERR(svn_atomic__init_once(&mutex_initialized, init_thread_mutex, NULL,
+ result_pool));
+
+ new_ns->mutex.pool = result_pool;
+ SVN_ERR(svn_io_file_open(&new_ns->mutex.lock_file, lock_name,
+ APR_READ | APR_WRITE | APR_CREATE,
+ APR_OS_DEFAULT,
+ result_pool));
+
+ /* Make sure the last user of our lock file will actually remove it.
+ * Please note that only the last file handle begin closed will actually
+ * remove the underlying file (see docstring for apr_file_remove).
+ */
+ apr_pool_cleanup_register(result_pool, &new_ns->mutex,
+ delete_lock_file,
+ apr_pool_cleanup_null);
+
+ /* Prevent concurrent initialization.
+ */
+ SVN_ERR(lock(&new_ns->mutex));
+
+ /* First, make sure that the underlying file exists. If it doesn't
+ * exist, create one and initialize its content.
+ */
+ err = svn_io_file_open(&file, shm_name,
+ APR_READ | APR_WRITE | APR_CREATE,
+ APR_OS_DEFAULT,
+ result_pool);
+ if (!err)
+ {
+ err = svn_io_stat(&finfo, shm_name, APR_FINFO_SIZE, subpool);
+ if (!err && finfo.size < sizeof(struct shared_data_t))
+ {
+ /* Zero all counters, values and names.
+ */
+ struct shared_data_t initial_data;
+ memset(&initial_data, 0, sizeof(initial_data));
+ err = svn_io_file_write_full(file, &initial_data,
+ sizeof(initial_data), NULL,
+ subpool);
+ }
+ }
+
+ /* Now, map it into memory.
+ */
+ if (!err)
+ {
+ apr_err = apr_mmap_create(&mmap, file, 0, sizeof(*new_ns->data),
+ APR_MMAP_READ | APR_MMAP_WRITE , result_pool);
+ if (!apr_err)
+ new_ns->data = mmap->mm;
+ else
+ err = svn_error_createf(apr_err, NULL,
+ _("MMAP failed for file '%s'"), shm_name);
+ }
+
+ svn_pool_destroy(subpool);
+
+ if (!err && new_ns->data)
+ {
+ /* Detect severe cases of corruption (i.e. when some outsider messed
+ * with our data file)
+ */
+ if (new_ns->data->count > MAX_ATOMIC_COUNT)
+ return svn_error_create(SVN_ERR_CORRUPTED_ATOMIC_STORAGE, 0,
+ _("Number of atomics in namespace is too large."));
+
+ /* Cache the number of existing, complete entries. There can't be
+ * incomplete ones from other processes because we hold the mutex.
+ * Our process will also not access this information since we are
+ * either being called from within svn_atomic__init_once or by
+ * svn_atomic_namespace__create for a new object.
+ */
+ new_ns->min_used = new_ns->data->count;
+ *ns = new_ns;
+ }
+
+ /* Unlock to allow other processes may access the shared memory as well.
+ */
+ return unlock(&new_ns->mutex, err);
+}
+
+svn_error_t *
+svn_atomic_namespace__cleanup(const char *name,
+ apr_pool_t *pool)
+{
+ const char *shm_name, *lock_name;
+
+ /* file names used for the specified namespace */
+ shm_name = apr_pstrcat(pool, name, SHM_NAME_SUFFIX, NULL);
+ lock_name = apr_pstrcat(pool, name, MUTEX_NAME_SUFFIX, NULL);
+
+ /* remove these files if they exist */
+ SVN_ERR(svn_io_remove_file2(shm_name, TRUE, pool));
+ SVN_ERR(svn_io_remove_file2(lock_name, TRUE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_named_atomic__get(svn_named_atomic__t **atomic,
+ svn_atomic_namespace__t *ns,
+ const char *name,
+ svn_boolean_t auto_create)
+{
+ apr_uint32_t i, count;
+ svn_error_t *error = SVN_NO_ERROR;
+ apr_size_t len = strlen(name);
+
+ /* Check parameters and make sure we return a NULL atomic
+ * in case of failure.
+ */
+ *atomic = NULL;
+ if (len > SVN_NAMED_ATOMIC__MAX_NAME_LENGTH)
+ return svn_error_create(SVN_ERR_BAD_ATOMIC, 0,
+ _("Atomic's name is too long."));
+
+ /* If no namespace has been provided, bail out.
+ */
+ if (ns == NULL || ns->data == NULL)
+ return svn_error_create(SVN_ERR_BAD_ATOMIC, 0,
+ _("Namespace has not been initialized."));
+
+ /* Optimistic lookup.
+ * Because we never change the name of existing atomics and may only
+ * append new ones, we can safely compare the name of existing ones
+ * with the name that we are looking for.
+ */
+ for (i = 0, count = svn_atomic_read(&ns->min_used); i < count; ++i)
+ if (strncmp(ns->data->atomics[i].name, name, len + 1) == 0)
+ {
+ return_atomic(atomic, ns, i);
+ return SVN_NO_ERROR;
+ }
+
+ /* Try harder:
+ * Serialize all lookup and insert the item, if necessary and allowed.
+ */
+ SVN_ERR(lock(&ns->mutex));
+
+ /* We only need to check for new entries.
+ */
+ for (i = count; i < ns->data->count; ++i)
+ if (strncmp(ns->data->atomics[i].name, name, len + 1) == 0)
+ {
+ return_atomic(atomic, ns, i);
+
+ /* Update our cached number of complete entries. */
+ svn_atomic_set(&ns->min_used, ns->data->count);
+
+ return unlock(&ns->mutex, error);
+ }
+
+ /* Not found. Append a new entry, if allowed & possible.
+ */
+ if (auto_create)
+ {
+ if (ns->data->count < MAX_ATOMIC_COUNT)
+ {
+ ns->data->atomics[ns->data->count].value = 0;
+ memcpy(ns->data->atomics[ns->data->count].name,
+ name,
+ len+1);
+
+ return_atomic(atomic, ns, ns->data->count);
+ ++ns->data->count;
+ }
+ else
+ error = svn_error_create(SVN_ERR_BAD_ATOMIC, 0,
+ _("Out of slots for named atomic."));
+ }
+
+ /* We are mainly done here. Let others continue their work.
+ */
+ SVN_ERR(unlock(&ns->mutex, error));
+
+ /* Only now can we be sure that a full memory barrier has been set
+ * and that the new entry has been written to memory in full.
+ */
+ svn_atomic_set(&ns->min_used, ns->data->count);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_named_atomic__read(apr_int64_t *value,
+ svn_named_atomic__t *atomic)
+{
+ SVN_ERR(validate(atomic));
+ NA_SYNCHRONIZE(atomic, *value = synched_read(&atomic->data->value));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_named_atomic__write(apr_int64_t *old_value,
+ apr_int64_t new_value,
+ svn_named_atomic__t *atomic)
+{
+ apr_int64_t temp;
+
+ SVN_ERR(validate(atomic));
+ NA_SYNCHRONIZE(atomic, temp = synched_write(&atomic->data->value, new_value));
+
+ if (old_value)
+ *old_value = temp;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_named_atomic__add(apr_int64_t *new_value,
+ apr_int64_t delta,
+ svn_named_atomic__t *atomic)
+{
+ apr_int64_t temp;
+
+ SVN_ERR(validate(atomic));
+ NA_SYNCHRONIZE(atomic, temp = synched_add(&atomic->data->value, delta));
+
+ if (new_value)
+ *new_value = temp;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_named_atomic__cmpxchg(apr_int64_t *old_value,
+ apr_int64_t new_value,
+ apr_int64_t comperand,
+ svn_named_atomic__t *atomic)
+{
+ apr_int64_t temp;
+
+ SVN_ERR(validate(atomic));
+ NA_SYNCHRONIZE(atomic, temp = synched_cmpxchg(&atomic->data->value,
+ new_value,
+ comperand));
+
+ if (old_value)
+ *old_value = temp;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/nls.c b/contrib/subversion/subversion/libsvn_subr/nls.c
new file mode 100644
index 0000000..b026e39
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/nls.c
@@ -0,0 +1,132 @@
+/*
+ * nls.c : Helpers for NLS programs.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <stdlib.h>
+
+#ifndef WIN32
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+
+#include <apr_errno.h>
+
+#include "svn_nls.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_path.h"
+
+#include "svn_private_config.h"
+
+svn_error_t *
+svn_nls_init(void)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+#ifdef ENABLE_NLS
+ if (getenv("SVN_LOCALE_DIR"))
+ {
+ bindtextdomain(PACKAGE_NAME, getenv("SVN_LOCALE_DIR"));
+ }
+ else
+ {
+#ifdef WIN32
+ WCHAR ucs2_path[MAX_PATH];
+ char* utf8_path;
+ const char* internal_path;
+ apr_pool_t* pool;
+ apr_size_t inwords, outbytes, outlength;
+
+ apr_pool_create(&pool, 0);
+ /* get exe name - our locale info will be in '../share/locale' */
+ inwords = GetModuleFileNameW(0, ucs2_path,
+ sizeof(ucs2_path) / sizeof(ucs2_path[0]));
+ if (! inwords)
+ {
+ /* We must be on a Win9x machine, so attempt to get an ANSI path,
+ and convert it to Unicode. */
+ CHAR ansi_path[MAX_PATH];
+
+ if (GetModuleFileNameA(0, ansi_path, sizeof(ansi_path)))
+ {
+ inwords =
+ MultiByteToWideChar(CP_ACP, 0, ansi_path, -1, ucs2_path,
+ sizeof(ucs2_path) / sizeof(ucs2_path[0]));
+ if (! inwords)
+ {
+ err =
+ svn_error_createf(APR_EINVAL, NULL,
+ _("Can't convert string to UCS-2: '%s'"),
+ ansi_path);
+ }
+ }
+ else
+ {
+ err = svn_error_create(APR_EINVAL, NULL,
+ _("Can't get module file name"));
+ }
+ }
+
+ if (! err)
+ {
+ outbytes = outlength = 3 * (inwords + 1);
+ utf8_path = apr_palloc(pool, outlength);
+
+ outbytes = WideCharToMultiByte(CP_UTF8, 0, ucs2_path, inwords,
+ utf8_path, outbytes, NULL, NULL);
+
+ if (outbytes == 0)
+ {
+ err = svn_error_wrap_apr(apr_get_os_error(),
+ _("Can't convert module path "
+ "to UTF-8 from UCS-2: '%s'"),
+ ucs2_path);
+ }
+ else
+ {
+ utf8_path[outlength - outbytes] = '\0';
+ internal_path = svn_dirent_internal_style(utf8_path, pool);
+ /* get base path name */
+ internal_path = svn_dirent_dirname(internal_path, pool);
+ internal_path = svn_dirent_join(internal_path,
+ SVN_LOCALE_RELATIVE_PATH,
+ pool);
+ bindtextdomain(PACKAGE_NAME, internal_path);
+ }
+ }
+ svn_pool_destroy(pool);
+ }
+#else /* ! WIN32 */
+ bindtextdomain(PACKAGE_NAME, SVN_LOCALE_DIR);
+ }
+#endif /* WIN32 */
+
+#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
+ bind_textdomain_codeset(PACKAGE_NAME, "UTF-8");
+#endif /* HAVE_BIND_TEXTDOMAIN_CODESET */
+
+#endif /* ENABLE_NLS */
+
+ return err;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/opt.c b/contrib/subversion/subversion/libsvn_subr/opt.c
new file mode 100644
index 0000000..28ffed1
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/opt.c
@@ -0,0 +1,1240 @@
+/*
+ * opt.c : option and argument parsing for Subversion command lines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <apr_pools.h>
+#include <apr_general.h>
+#include <apr_lib.h>
+#include <apr_file_info.h>
+
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_version.h"
+#include "svn_types.h"
+#include "svn_opt.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_time.h"
+#include "svn_props.h"
+#include "svn_ctype.h"
+
+#include "private/svn_opt_private.h"
+
+#include "opt.h"
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+const svn_opt_subcommand_desc2_t *
+svn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table,
+ const char *cmd_name)
+{
+ int i = 0;
+
+ if (cmd_name == NULL)
+ return NULL;
+
+ while (table[i].name) {
+ int j;
+ if (strcmp(cmd_name, table[i].name) == 0)
+ return table + i;
+ for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
+ if (strcmp(cmd_name, table[i].aliases[j]) == 0)
+ return table + i;
+
+ i++;
+ }
+
+ /* If we get here, there was no matching subcommand name or alias. */
+ return NULL;
+}
+
+const apr_getopt_option_t *
+svn_opt_get_option_from_code2(int code,
+ const apr_getopt_option_t *option_table,
+ const svn_opt_subcommand_desc2_t *command,
+ apr_pool_t *pool)
+{
+ apr_size_t i;
+
+ for (i = 0; option_table[i].optch; i++)
+ if (option_table[i].optch == code)
+ {
+ if (command)
+ {
+ int j;
+
+ for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) &&
+ command->desc_overrides[j].optch); j++)
+ if (command->desc_overrides[j].optch == code)
+ {
+ apr_getopt_option_t *tmpopt =
+ apr_palloc(pool, sizeof(*tmpopt));
+ *tmpopt = option_table[i];
+ tmpopt->description = command->desc_overrides[j].desc;
+ return tmpopt;
+ }
+ }
+ return &(option_table[i]);
+ }
+
+ return NULL;
+}
+
+
+const apr_getopt_option_t *
+svn_opt_get_option_from_code(int code,
+ const apr_getopt_option_t *option_table)
+{
+ apr_size_t i;
+
+ for (i = 0; option_table[i].optch; i++)
+ if (option_table[i].optch == code)
+ return &(option_table[i]);
+
+ return NULL;
+}
+
+
+/* Like svn_opt_get_option_from_code2(), but also, if CODE appears a second
+ * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that
+ * second name, else set it to NULL. */
+static const apr_getopt_option_t *
+get_option_from_code(const char **long_alias,
+ int code,
+ const apr_getopt_option_t *option_table,
+ const svn_opt_subcommand_desc2_t *command,
+ apr_pool_t *pool)
+{
+ const apr_getopt_option_t *i;
+ const apr_getopt_option_t *opt
+ = svn_opt_get_option_from_code2(code, option_table, command, pool);
+
+ /* Find a long alias in the table, if there is one. */
+ *long_alias = NULL;
+ for (i = option_table; i->optch; i++)
+ {
+ if (i->optch == code && i->name != opt->name)
+ {
+ *long_alias = i->name;
+ break;
+ }
+ }
+
+ return opt;
+}
+
+
+/* Print an option OPT nicely into a STRING allocated in POOL.
+ * If OPT has a single-character short form, then print OPT->name (if not
+ * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias.
+ * If DOC is set, include the generic documentation string of OPT,
+ * localized to the current locale if a translation is available.
+ */
+static void
+format_option(const char **string,
+ const apr_getopt_option_t *opt,
+ const char *long_alias,
+ svn_boolean_t doc,
+ apr_pool_t *pool)
+{
+ char *opts;
+
+ if (opt == NULL)
+ {
+ *string = "?";
+ return;
+ }
+
+ /* We have a valid option which may or may not have a "short
+ name" (a single-character alias for the long option). */
+ if (opt->optch <= 255)
+ opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name);
+ else if (long_alias)
+ opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias);
+ else
+ opts = apr_psprintf(pool, "--%s", opt->name);
+
+ if (opt->has_arg)
+ opts = apr_pstrcat(pool, opts, _(" ARG"), (char *)NULL);
+
+ if (doc)
+ opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description));
+
+ *string = opts;
+}
+
+void
+svn_opt_format_option(const char **string,
+ const apr_getopt_option_t *opt,
+ svn_boolean_t doc,
+ apr_pool_t *pool)
+{
+ format_option(string, opt, NULL, doc, pool);
+}
+
+
+svn_boolean_t
+svn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command,
+ int option_code,
+ const int *global_options)
+{
+ apr_size_t i;
+
+ for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
+ if (command->valid_options[i] == option_code)
+ return TRUE;
+
+ if (global_options)
+ for (i = 0; global_options[i]; i++)
+ if (global_options[i] == option_code)
+ return TRUE;
+
+ return FALSE;
+}
+
+svn_boolean_t
+svn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command,
+ int option_code)
+{
+ return svn_opt_subcommand_takes_option3(command,
+ option_code,
+ NULL);
+}
+
+
+svn_boolean_t
+svn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command,
+ int option_code)
+{
+ apr_size_t i;
+
+ for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
+ if (command->valid_options[i] == option_code)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+/* Print the canonical command name for CMD, and all its aliases, to
+ STREAM. If HELP is set, print CMD's help string too, in which case
+ obtain option usage from OPTIONS_TABLE. */
+static svn_error_t *
+print_command_info2(const svn_opt_subcommand_desc2_t *cmd,
+ const apr_getopt_option_t *options_table,
+ const int *global_options,
+ svn_boolean_t help,
+ apr_pool_t *pool,
+ FILE *stream)
+{
+ svn_boolean_t first_time;
+ apr_size_t i;
+
+ /* Print the canonical command name. */
+ SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
+
+ /* Print the list of aliases. */
+ first_time = TRUE;
+ for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
+ {
+ if (cmd->aliases[i] == NULL)
+ break;
+
+ if (first_time) {
+ SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
+ first_time = FALSE;
+ }
+ else
+ SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
+
+ SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
+ }
+
+ if (! first_time)
+ SVN_ERR(svn_cmdline_fputs(")", stream, pool));
+
+ if (help)
+ {
+ const apr_getopt_option_t *option;
+ const char *long_alias;
+ svn_boolean_t have_options = FALSE;
+
+ SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help)));
+
+ /* Loop over all valid option codes attached to the subcommand */
+ for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
+ {
+ if (cmd->valid_options[i])
+ {
+ if (!have_options)
+ {
+ SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
+ stream, pool));
+ have_options = TRUE;
+ }
+
+ /* convert each option code into an option */
+ option = get_option_from_code(&long_alias, cmd->valid_options[i],
+ options_table, cmd, pool);
+
+ /* print the option's docstring */
+ if (option && option->description)
+ {
+ const char *optstr;
+ format_option(&optstr, option, long_alias, TRUE, pool);
+ SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n",
+ optstr));
+ }
+ }
+ }
+ /* And global options too */
+ if (global_options && *global_options)
+ {
+ SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"),
+ stream, pool));
+ have_options = TRUE;
+
+ for (i = 0; global_options[i]; i++)
+ {
+
+ /* convert each option code into an option */
+ option = get_option_from_code(&long_alias, global_options[i],
+ options_table, cmd, pool);
+
+ /* print the option's docstring */
+ if (option && option->description)
+ {
+ const char *optstr;
+ format_option(&optstr, option, long_alias, TRUE, pool);
+ SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n",
+ optstr));
+ }
+ }
+ }
+
+ if (have_options)
+ SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_opt_print_generic_help2(const char *header,
+ const svn_opt_subcommand_desc2_t *cmd_table,
+ const apr_getopt_option_t *opt_table,
+ const char *footer,
+ apr_pool_t *pool, FILE *stream)
+{
+ int i = 0;
+ svn_error_t *err;
+
+ if (header)
+ if ((err = svn_cmdline_fputs(header, stream, pool)))
+ goto print_error;
+
+ while (cmd_table[i].name)
+ {
+ if ((err = svn_cmdline_fputs(" ", stream, pool))
+ || (err = print_command_info2(cmd_table + i, opt_table,
+ NULL, FALSE,
+ pool, stream))
+ || (err = svn_cmdline_fputs("\n", stream, pool)))
+ goto print_error;
+ i++;
+ }
+
+ if ((err = svn_cmdline_fputs("\n", stream, pool)))
+ goto print_error;
+
+ if (footer)
+ if ((err = svn_cmdline_fputs(footer, stream, pool)))
+ goto print_error;
+
+ return;
+
+ print_error:
+ /* Issue #3014:
+ * Don't print anything on broken pipes. The pipe was likely
+ * closed by the process at the other end. We expect that
+ * process to perform error reporting as necessary.
+ *
+ * ### This assumes that there is only one error in a chain for
+ * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
+ if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
+ svn_handle_error2(err, stderr, FALSE, "svn: ");
+ svn_error_clear(err);
+}
+
+
+void
+svn_opt_subcommand_help3(const char *subcommand,
+ const svn_opt_subcommand_desc2_t *table,
+ const apr_getopt_option_t *options_table,
+ const int *global_options,
+ apr_pool_t *pool)
+{
+ const svn_opt_subcommand_desc2_t *cmd =
+ svn_opt_get_canonical_subcommand2(table, subcommand);
+ svn_error_t *err;
+
+ if (cmd)
+ err = print_command_info2(cmd, options_table, global_options,
+ TRUE, pool, stdout);
+ else
+ err = svn_cmdline_fprintf(stderr, pool,
+ _("\"%s\": unknown command.\n\n"), subcommand);
+
+ if (err) {
+ svn_handle_error2(err, stderr, FALSE, "svn: ");
+ svn_error_clear(err);
+ }
+}
+
+
+
+/*** Parsing revision and date options. ***/
+
+
+/** Parsing "X:Y"-style arguments. **/
+
+/* If WORD matches one of the special revision descriptors,
+ * case-insensitively, set *REVISION accordingly:
+ *
+ * - For "head", set REVISION->kind to svn_opt_revision_head.
+ *
+ * - For "prev", set REVISION->kind to svn_opt_revision_previous.
+ *
+ * - For "base", set REVISION->kind to svn_opt_revision_base.
+ *
+ * - For "committed", set REVISION->kind to svn_opt_revision_committed.
+ *
+ * If match, return 0, else return -1 and don't touch REVISION.
+ */
+static int
+revision_from_word(svn_opt_revision_t *revision, const char *word)
+{
+ if (svn_cstring_casecmp(word, "head") == 0)
+ {
+ revision->kind = svn_opt_revision_head;
+ }
+ else if (svn_cstring_casecmp(word, "prev") == 0)
+ {
+ revision->kind = svn_opt_revision_previous;
+ }
+ else if (svn_cstring_casecmp(word, "base") == 0)
+ {
+ revision->kind = svn_opt_revision_base;
+ }
+ else if (svn_cstring_casecmp(word, "committed") == 0)
+ {
+ revision->kind = svn_opt_revision_committed;
+ }
+ else
+ return -1;
+
+ return 0;
+}
+
+
+/* Parse one revision specification. Return pointer to character
+ after revision, or NULL if the revision is invalid. Modifies
+ str, so make sure to pass a copy of anything precious. Uses
+ POOL for temporary allocation. */
+static char *parse_one_rev(svn_opt_revision_t *revision, char *str,
+ apr_pool_t *pool)
+{
+ char *end, save;
+
+ /* Allow any number of 'r's to prefix a revision number, because
+ that way if a script pastes svn output into another svn command
+ (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work,
+ even when compounded.
+
+ As it happens, none of our special revision words begins with
+ "r". If any ever do, then this code will have to get smarter.
+
+ Incidentally, this allows "r{DATE}". We could avoid that with
+ some trivial code rearrangement, but it's not clear what would
+ be gained by doing so. */
+ while (*str == 'r')
+ str++;
+
+ if (*str == '{')
+ {
+ svn_boolean_t matched;
+ apr_time_t tm;
+ svn_error_t *err;
+
+ /* Brackets denote a date. */
+ str++;
+ end = strchr(str, '}');
+ if (!end)
+ return NULL;
+ *end = '\0';
+ err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+ if (!matched)
+ return NULL;
+ revision->kind = svn_opt_revision_date;
+ revision->value.date = tm;
+ return end + 1;
+ }
+ else if (svn_ctype_isdigit(*str))
+ {
+ /* It's a number. */
+ end = str + 1;
+ while (svn_ctype_isdigit(*end))
+ end++;
+ save = *end;
+ *end = '\0';
+ revision->kind = svn_opt_revision_number;
+ revision->value.number = SVN_STR_TO_REV(str);
+ *end = save;
+ return end;
+ }
+ else if (svn_ctype_isalpha(*str))
+ {
+ end = str + 1;
+ while (svn_ctype_isalpha(*end))
+ end++;
+ save = *end;
+ *end = '\0';
+ if (revision_from_word(revision, str) != 0)
+ return NULL;
+ *end = save;
+ return end;
+ }
+ else
+ return NULL;
+}
+
+
+int
+svn_opt_parse_revision(svn_opt_revision_t *start_revision,
+ svn_opt_revision_t *end_revision,
+ const char *arg,
+ apr_pool_t *pool)
+{
+ char *left_rev, *right_rev, *end;
+
+ /* Operate on a copy of the argument. */
+ left_rev = apr_pstrdup(pool, arg);
+
+ right_rev = parse_one_rev(start_revision, left_rev, pool);
+ if (right_rev && *right_rev == ':')
+ {
+ right_rev++;
+ end = parse_one_rev(end_revision, right_rev, pool);
+ if (!end || *end != '\0')
+ return -1;
+ }
+ else if (!right_rev || *right_rev != '\0')
+ return -1;
+
+ return 0;
+}
+
+
+int
+svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
+ const char *arg,
+ apr_pool_t *pool)
+{
+ svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
+
+ range->start.kind = svn_opt_revision_unspecified;
+ range->end.kind = svn_opt_revision_unspecified;
+
+ if (svn_opt_parse_revision(&(range->start), &(range->end),
+ arg, pool) == -1)
+ return -1;
+
+ APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range;
+ return 0;
+}
+
+svn_error_t *
+svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
+ svn_opt_revision_t *op_rev,
+ svn_boolean_t is_url,
+ svn_boolean_t notice_local_mods,
+ apr_pool_t *pool)
+{
+ if (peg_rev->kind == svn_opt_revision_unspecified)
+ {
+ if (is_url)
+ {
+ peg_rev->kind = svn_opt_revision_head;
+ }
+ else
+ {
+ if (notice_local_mods)
+ peg_rev->kind = svn_opt_revision_working;
+ else
+ peg_rev->kind = svn_opt_revision_base;
+ }
+ }
+
+ if (op_rev->kind == svn_opt_revision_unspecified)
+ *op_rev = *peg_rev;
+
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_opt__revision_to_string(const svn_opt_revision_t *revision,
+ apr_pool_t *result_pool)
+{
+ switch (revision->kind)
+ {
+ case svn_opt_revision_unspecified:
+ return "unspecified";
+ case svn_opt_revision_number:
+ return apr_psprintf(result_pool, "%ld", revision->value.number);
+ case svn_opt_revision_date:
+ /* ### svn_time_to_human_cstring()? */
+ return svn_time_to_cstring(revision->value.date, result_pool);
+ case svn_opt_revision_committed:
+ return "committed";
+ case svn_opt_revision_previous:
+ return "previous";
+ case svn_opt_revision_base:
+ return "base";
+ case svn_opt_revision_working:
+ return "working";
+ case svn_opt_revision_head:
+ return "head";
+ default:
+ return NULL;
+ }
+}
+
+svn_opt_revision_range_t *
+svn_opt__revision_range_create(const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ apr_pool_t *result_pool)
+{
+ svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
+
+ range->start = *start_revision;
+ range->end = *end_revision;
+ return range;
+}
+
+svn_opt_revision_range_t *
+svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,
+ svn_revnum_t end_revnum,
+ apr_pool_t *result_pool)
+{
+ svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
+
+ range->start.kind = svn_opt_revision_number;
+ range->start.value.number = start_revnum;
+ range->end.kind = svn_opt_revision_number;
+ range->end.value.number = end_revnum;
+ return range;
+}
+
+
+
+/*** Parsing arguments. ***/
+#define DEFAULT_ARRAY_SIZE 5
+
+
+/* Copy STR into POOL and push the copy onto ARRAY. */
+static void
+array_push_str(apr_array_header_t *array,
+ const char *str,
+ apr_pool_t *pool)
+{
+ /* ### Not sure if this function is still necessary. It used to
+ convert str to svn_stringbuf_t * and push it, but now it just
+ dups str in pool and pushes the copy. So its only effect is
+ transfer str's lifetime to pool. Is that something callers are
+ depending on? */
+
+ APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str);
+}
+
+
+void
+svn_opt_push_implicit_dot_target(apr_array_header_t *targets,
+ apr_pool_t *pool)
+{
+ if (targets->nelts == 0)
+ APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */
+ assert(targets->nelts);
+}
+
+
+svn_error_t *
+svn_opt_parse_num_args(apr_array_header_t **args_p,
+ apr_getopt_t *os,
+ int num_args,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_array_header_t *args
+ = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
+
+ /* loop for num_args and add each arg to the args array */
+ for (i = 0; i < num_args; i++)
+ {
+ if (os->ind >= os->argc)
+ {
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ }
+ array_push_str(args, os->argv[os->ind++], pool);
+ }
+
+ *args_p = args;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_opt_parse_all_args(apr_array_header_t **args_p,
+ apr_getopt_t *os,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *args
+ = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
+
+ if (os->ind > os->argc)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+ }
+ while (os->ind < os->argc)
+ {
+ array_push_str(args, os->argv[os->ind++], pool);
+ }
+
+ *args_p = args;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_opt_parse_path(svn_opt_revision_t *rev,
+ const char **truepath,
+ const char *path /* UTF-8! */,
+ apr_pool_t *pool)
+{
+ const char *peg_rev;
+
+ SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool));
+
+ /* Parse the peg revision, if one was found */
+ if (strlen(peg_rev))
+ {
+ int ret;
+ svn_opt_revision_t start_revision, end_revision;
+
+ end_revision.kind = svn_opt_revision_unspecified;
+
+ if (peg_rev[1] == '\0') /* looking at empty peg revision */
+ {
+ ret = 0;
+ start_revision.kind = svn_opt_revision_unspecified;
+ start_revision.value.number = 0;
+ }
+ else /* looking at non-empty peg revision */
+ {
+ const char *rev_str = &peg_rev[1];
+
+ /* URLs get treated differently from wc paths. */
+ if (svn_path_is_url(path))
+ {
+ /* URLs are URI-encoded, so we look for dates with
+ URI-encoded delimeters. */
+ size_t rev_len = strlen(rev_str);
+ if (rev_len > 6
+ && rev_str[0] == '%'
+ && rev_str[1] == '7'
+ && (rev_str[2] == 'B'
+ || rev_str[2] == 'b')
+ && rev_str[rev_len-3] == '%'
+ && rev_str[rev_len-2] == '7'
+ && (rev_str[rev_len-1] == 'D'
+ || rev_str[rev_len-1] == 'd'))
+ {
+ rev_str = svn_path_uri_decode(rev_str, pool);
+ }
+ }
+ ret = svn_opt_parse_revision(&start_revision,
+ &end_revision,
+ rev_str, pool);
+ }
+
+ if (ret || end_revision.kind != svn_opt_revision_unspecified)
+ {
+ /* If an svn+ssh URL was used and it contains only one @,
+ * provide an error message that presents a possible solution
+ * to the parsing error (issue #2349). */
+ if (strncmp(path, "svn+ssh://", 10) == 0)
+ {
+ const char *at;
+
+ at = strchr(path, '@');
+ if (at && strrchr(path, '@') == at)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error parsing peg revision "
+ "'%s'; did you mean '%s@'?"),
+ &peg_rev[1], path);
+ }
+
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error parsing peg revision '%s'"),
+ &peg_rev[1]);
+ }
+ rev->kind = start_revision.kind;
+ rev->value = start_revision.value;
+ }
+ else
+ {
+ /* Didn't find a peg revision. */
+ rev->kind = svn_opt_revision_unspecified;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Note: This is substantially copied into svn_client_args_to_target_array() in
+ * order to move to libsvn_client while maintaining backward compatibility. */
+svn_error_t *
+svn_opt__args_to_target_array(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ apr_pool_t *pool)
+{
+ int i;
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_array_header_t *input_targets =
+ apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
+ apr_array_header_t *output_targets =
+ apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
+
+ /* Step 1: create a master array of targets that are in UTF-8
+ encoding, and come from concatenating the targets left by apr_getopt,
+ plus any extra targets (e.g., from the --targets switch.) */
+
+ for (; os->ind < os->argc; os->ind++)
+ {
+ /* The apr_getopt targets are still in native encoding. */
+ const char *raw_target = os->argv[os->ind];
+ SVN_ERR(svn_utf_cstring_to_utf8
+ ((const char **) apr_array_push(input_targets),
+ raw_target, pool));
+ }
+
+ if (known_targets)
+ {
+ for (i = 0; i < known_targets->nelts; i++)
+ {
+ /* The --targets array have already been converted to UTF-8,
+ because we needed to split up the list with svn_cstring_split. */
+ const char *utf8_target = APR_ARRAY_IDX(known_targets,
+ i, const char *);
+ APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
+ }
+ }
+
+ /* Step 2: process each target. */
+
+ for (i = 0; i < input_targets->nelts; i++)
+ {
+ const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
+ const char *true_target;
+ const char *target; /* after all processing is finished */
+ const char *peg_rev;
+
+ /*
+ * This is needed so that the target can be properly canonicalized,
+ * otherwise the canonicalization does not treat a ".@BASE" as a "."
+ * with a BASE peg revision, and it is not canonicalized to "@BASE".
+ * If any peg revision exists, it is appended to the final
+ * canonicalized path or URL. Do not use svn_opt_parse_path()
+ * because the resulting peg revision is a structure that would have
+ * to be converted back into a string. Converting from a string date
+ * to the apr_time_t field in the svn_opt_revision_value_t and back to
+ * a string would not necessarily preserve the exact bytes of the
+ * input date, so its easier just to keep it in string form.
+ */
+ SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
+ utf8_target, pool));
+
+ /* URLs and wc-paths get treated differently. */
+ if (svn_path_is_url(true_target))
+ {
+ SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target,
+ pool));
+ }
+ else /* not a url, so treat as a path */
+ {
+ const char *base_name;
+
+ SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target,
+ pool));
+
+ /* If the target has the same name as a Subversion
+ working copy administrative dir, skip it. */
+ base_name = svn_dirent_basename(true_target, pool);
+
+ /* FIXME:
+ The canonical list of administrative directory names is
+ maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir().
+ That list can't be used here, because that use would
+ create a circular dependency between libsvn_wc and
+ libsvn_subr. Make sure changes to the lists are always
+ synchronized! */
+ if (0 == strcmp(base_name, ".svn")
+ || 0 == strcmp(base_name, "_svn"))
+ {
+ err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
+ err, _("'%s' ends in a reserved name"),
+ utf8_target);
+ continue;
+ }
+ }
+
+ target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL);
+
+ APR_ARRAY_PUSH(output_targets, const char *) = target;
+ }
+
+
+ /* kff todo: need to remove redundancies from targets before
+ passing it to the cmd_func. */
+
+ *targets_p = output_targets;
+
+ return err;
+}
+
+svn_error_t *
+svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec,
+ apr_pool_t *pool)
+{
+ const char *sep, *propname;
+ svn_string_t *propval;
+
+ if (! *revprop_spec)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Revision property pair is empty"));
+
+ if (! *revprop_table_p)
+ *revprop_table_p = apr_hash_make(pool);
+
+ sep = strchr(revprop_spec, '=');
+ if (sep)
+ {
+ propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec);
+ SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool));
+ propval = svn_string_create(sep + 1, pool);
+ }
+ else
+ {
+ SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool));
+ propval = svn_string_create_empty(pool);
+ }
+
+ if (!svn_prop_name_is_valid(propname))
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid Subversion property name"),
+ propname);
+
+ svn_hash_sets(*revprop_table_p, propname, propval);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_opt__split_arg_at_peg_revision(const char **true_target,
+ const char **peg_revision,
+ const char *utf8_target,
+ apr_pool_t *pool)
+{
+ const char *peg_start = NULL; /* pointer to the peg revision, if any */
+ const char *ptr;
+
+ for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target;
+ --ptr)
+ {
+ /* If we hit a path separator, stop looking. This is OK
+ only because our revision specifiers can't contain '/'. */
+ if (*ptr == '/')
+ break;
+
+ if (*ptr == '@')
+ {
+ peg_start = ptr;
+ break;
+ }
+ }
+
+ if (peg_start)
+ {
+ /* Error out if target is the empty string. */
+ if (ptr == utf8_target)
+ return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
+ _("'%s' is just a peg revision. "
+ "Maybe try '%s@' instead?"),
+ utf8_target, utf8_target);
+
+ *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target);
+ if (peg_revision)
+ *peg_revision = apr_pstrdup(pool, peg_start);
+ }
+ else
+ {
+ *true_target = utf8_target;
+ if (peg_revision)
+ *peg_revision = "";
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in,
+ apr_pool_t *pool)
+{
+ const char *target;
+
+ /* Convert to URI. */
+ target = svn_path_uri_from_iri(url_in, pool);
+ /* Auto-escape some ASCII characters. */
+ target = svn_path_uri_autoescape(target, pool);
+
+#if '/' != SVN_PATH_LOCAL_SEPARATOR
+ /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */
+ if (strchr(target, SVN_PATH_LOCAL_SEPARATOR))
+ {
+ char *p = apr_pstrdup(pool, target);
+ target = p;
+
+ /* Convert all local-style separators to the canonical ones. */
+ for (; *p != '\0'; ++p)
+ if (*p == SVN_PATH_LOCAL_SEPARATOR)
+ *p = '/';
+ }
+#endif
+
+ /* Verify that no backpaths are present in the URL. */
+ if (svn_path_is_backpath_present(target))
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("URL '%s' contains a '..' element"),
+ target);
+
+ /* Strip any trailing '/' and collapse other redundant elements. */
+ target = svn_uri_canonicalize(target, pool);
+
+ *url_out = target;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in,
+ apr_pool_t *pool)
+{
+ const char *apr_target;
+ char *truenamed_target; /* APR-encoded */
+ apr_status_t apr_err;
+
+ /* canonicalize case, and change all separators to '/'. */
+ SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool));
+ apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
+ APR_FILEPATH_TRUENAME, pool);
+
+ if (!apr_err)
+ /* We have a canonicalized APR-encoded target now. */
+ apr_target = truenamed_target;
+ else if (APR_STATUS_IS_ENOENT(apr_err))
+ /* It's okay for the file to not exist, that just means we
+ have to accept the case given to the client. We'll use
+ the original APR-encoded target. */
+ ;
+ else
+ return svn_error_createf(apr_err, NULL,
+ _("Error resolving case of '%s'"),
+ svn_dirent_local_style(path_in, pool));
+
+ /* convert back to UTF-8. */
+ SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool));
+ *path_out = svn_dirent_canonicalize(*path_out, pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_opt__print_version_info(const char *pgm_name,
+ const char *footer,
+ const svn_version_extended_t *info,
+ svn_boolean_t quiet,
+ svn_boolean_t verbose,
+ apr_pool_t *pool)
+{
+ if (quiet)
+ return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER);
+
+ SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
+ " compiled %s, %s on %s\n\n"),
+ pgm_name, SVN_VERSION,
+ svn_version_ext_build_date(info),
+ svn_version_ext_build_time(info),
+ svn_version_ext_build_host(info)));
+ SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info)));
+
+ if (footer)
+ {
+ SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer));
+ }
+
+ if (verbose)
+ {
+ const apr_array_header_t *libs;
+
+ SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool));
+ SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"),
+ svn_version_ext_runtime_host(info)));
+ if (svn_version_ext_runtime_osname(info))
+ {
+ SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"),
+ svn_version_ext_runtime_osname(info)));
+ }
+
+ libs = svn_version_ext_linked_libs(info);
+ if (libs && libs->nelts)
+ {
+ const svn_version_ext_linked_lib_t *lib;
+ int i;
+
+ SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"),
+ stdout, pool));
+ for (i = 0; i < libs->nelts; ++i)
+ {
+ lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t);
+ if (lib->runtime_version)
+ SVN_ERR(svn_cmdline_printf(pool,
+ " - %s %s (compiled with %s)\n",
+ lib->name,
+ lib->runtime_version,
+ lib->compiled_version));
+ else
+ SVN_ERR(svn_cmdline_printf(pool,
+ " - %s %s (static)\n",
+ lib->name,
+ lib->compiled_version));
+ }
+ }
+
+ libs = svn_version_ext_loaded_libs(info);
+ if (libs && libs->nelts)
+ {
+ const svn_version_ext_loaded_lib_t *lib;
+ int i;
+
+ SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"),
+ stdout, pool));
+ for (i = 0; i < libs->nelts; ++i)
+ {
+ lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t);
+ if (lib->version)
+ SVN_ERR(svn_cmdline_printf(pool,
+ " - %s (%s)\n",
+ lib->name, lib->version));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name));
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_opt_print_help4(apr_getopt_t *os,
+ const char *pgm_name,
+ svn_boolean_t print_version,
+ svn_boolean_t quiet,
+ svn_boolean_t verbose,
+ const char *version_footer,
+ const char *header,
+ const svn_opt_subcommand_desc2_t *cmd_table,
+ const apr_getopt_option_t *option_table,
+ const int *global_options,
+ const char *footer,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *targets = NULL;
+
+ if (os)
+ SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
+
+ if (os && targets->nelts) /* help on subcommand(s) requested */
+ {
+ int i;
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *),
+ cmd_table, option_table,
+ global_options, pool);
+ }
+ }
+ else if (print_version) /* just --version */
+ {
+ SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
+ svn_version_extended(verbose, pool),
+ quiet, verbose, pool));
+ }
+ else if (os && !targets->nelts) /* `-h', `--help', or `help' */
+ svn_opt_print_generic_help2(header,
+ cmd_table,
+ option_table,
+ footer,
+ pool,
+ stdout);
+ else /* unknown option or cmd */
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool,
+ _("Type '%s help' for usage.\n"), pgm_name));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/opt.h b/contrib/subversion/subversion/libsvn_subr/opt.h
new file mode 100644
index 0000000..ddf3984
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/opt.h
@@ -0,0 +1,54 @@
+/*
+ * opt.h: share svn_opt__* functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_SUBR_OPT_H
+#define SVN_LIBSVN_SUBR_OPT_H
+
+#include "svn_version.h"
+#include "svn_opt.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Print version version info for PGM_NAME to the console. If QUIET is
+ * true, print in brief. Else if QUIET is not true, print the version
+ * more verbosely, and if FOOTER is non-null, print it following the
+ * version information. If VERBOSE is true, print running system info.
+ *
+ * Use POOL for temporary allocations.
+ */
+svn_error_t *
+svn_opt__print_version_info(const char *pgm_name,
+ const char *footer,
+ const svn_version_extended_t *info,
+ svn_boolean_t quiet,
+ svn_boolean_t verbose,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_SUBR_OPT_H */
diff --git a/contrib/subversion/subversion/libsvn_subr/path.c b/contrib/subversion/subversion/libsvn_subr/path.c
new file mode 100644
index 0000000..84368f3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/path.c
@@ -0,0 +1,1315 @@
+/*
+ * paths.c: a path manipulation library using svn_stringbuf_t
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+#include <assert.h>
+
+#include <apr_file_info.h>
+#include <apr_lib.h>
+#include <apr_uri.h>
+
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_private_config.h" /* for SVN_PATH_LOCAL_SEPARATOR */
+#include "svn_utf.h"
+#include "svn_io.h" /* for svn_io_stat() */
+#include "svn_ctype.h"
+
+#include "dirent_uri.h"
+
+
+/* The canonical empty path. Can this be changed? Well, change the empty
+ test below and the path library will work, not so sure about the fs/wc
+ libraries. */
+#define SVN_EMPTY_PATH ""
+
+/* TRUE if s is the canonical empty path, FALSE otherwise */
+#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0')
+
+/* TRUE if s,n is the platform's empty path ("."), FALSE otherwise. Can
+ this be changed? Well, the path library will work, not so sure about
+ the OS! */
+#define SVN_PATH_IS_PLATFORM_EMPTY(s,n) ((n) == 1 && (s)[0] == '.')
+
+
+
+
+#ifndef NDEBUG
+/* This function is an approximation of svn_path_is_canonical.
+ * It is supposed to be used in functions that do not have access
+ * to a pool, but still want to assert that a path is canonical.
+ *
+ * PATH with length LEN is assumed to be canonical if it isn't
+ * the platform's empty path (see definition of SVN_PATH_IS_PLATFORM_EMPTY),
+ * and does not contain "/./", and any one of the following
+ * conditions is also met:
+ *
+ * 1. PATH has zero length
+ * 2. PATH is the root directory (what exactly a root directory is
+ * depends on the platform)
+ * 3. PATH is not a root directory and does not end with '/'
+ *
+ * If possible, please use svn_path_is_canonical instead.
+ */
+static svn_boolean_t
+is_canonical(const char *path,
+ apr_size_t len)
+{
+ return (! SVN_PATH_IS_PLATFORM_EMPTY(path, len)
+ && strstr(path, "/./") == NULL
+ && (len == 0
+ || (len == 1 && path[0] == '/')
+ || (path[len-1] != '/')
+#if defined(WIN32) || defined(__CYGWIN__)
+ || svn_dirent_is_root(path, len)
+#endif
+ ));
+}
+#endif
+
+
+/* functionality of svn_path_is_canonical but without the deprecation */
+static svn_boolean_t
+svn_path_is_canonical_internal(const char *path, apr_pool_t *pool)
+{
+ return svn_uri_is_canonical(path, pool) ||
+ svn_dirent_is_canonical(path, pool) ||
+ svn_relpath_is_canonical(path);
+}
+
+svn_boolean_t
+svn_path_is_canonical(const char *path, apr_pool_t *pool)
+{
+ return svn_path_is_canonical_internal(path, pool);
+}
+
+/* functionality of svn_path_join but without the deprecation */
+static char *
+svn_path_join_internal(const char *base,
+ const char *component,
+ apr_pool_t *pool)
+{
+ apr_size_t blen = strlen(base);
+ apr_size_t clen = strlen(component);
+ char *path;
+
+ assert(svn_path_is_canonical_internal(base, pool));
+ assert(svn_path_is_canonical_internal(component, pool));
+
+ /* If the component is absolute, then return it. */
+ if (*component == '/')
+ return apr_pmemdup(pool, component, clen + 1);
+
+ /* If either is empty return the other */
+ if (SVN_PATH_IS_EMPTY(base))
+ return apr_pmemdup(pool, component, clen + 1);
+ if (SVN_PATH_IS_EMPTY(component))
+ return apr_pmemdup(pool, base, blen + 1);
+
+ if (blen == 1 && base[0] == '/')
+ blen = 0; /* Ignore base, just return separator + component */
+
+ /* Construct the new, combined path. */
+ path = apr_palloc(pool, blen + 1 + clen + 1);
+ memcpy(path, base, blen);
+ path[blen] = '/';
+ memcpy(path + blen + 1, component, clen + 1);
+
+ return path;
+}
+
+char *svn_path_join(const char *base,
+ const char *component,
+ apr_pool_t *pool)
+{
+ return svn_path_join_internal(base, component, pool);
+}
+
+char *svn_path_join_many(apr_pool_t *pool, const char *base, ...)
+{
+#define MAX_SAVED_LENGTHS 10
+ apr_size_t saved_lengths[MAX_SAVED_LENGTHS];
+ apr_size_t total_len;
+ int nargs;
+ va_list va;
+ const char *s;
+ apr_size_t len;
+ char *path;
+ char *p;
+ svn_boolean_t base_is_empty = FALSE, base_is_root = FALSE;
+ int base_arg = 0;
+
+ total_len = strlen(base);
+
+ assert(svn_path_is_canonical_internal(base, pool));
+
+ if (total_len == 1 && *base == '/')
+ base_is_root = TRUE;
+ else if (SVN_PATH_IS_EMPTY(base))
+ {
+ total_len = sizeof(SVN_EMPTY_PATH) - 1;
+ base_is_empty = TRUE;
+ }
+
+ saved_lengths[0] = total_len;
+
+ /* Compute the length of the resulting string. */
+
+ nargs = 0;
+ va_start(va, base);
+ while ((s = va_arg(va, const char *)) != NULL)
+ {
+ len = strlen(s);
+
+ assert(svn_path_is_canonical_internal(s, pool));
+
+ if (SVN_PATH_IS_EMPTY(s))
+ continue;
+
+ if (nargs++ < MAX_SAVED_LENGTHS)
+ saved_lengths[nargs] = len;
+
+ if (*s == '/')
+ {
+ /* an absolute path. skip all components to this point and reset
+ the total length. */
+ total_len = len;
+ base_arg = nargs;
+ base_is_root = len == 1;
+ base_is_empty = FALSE;
+ }
+ else if (nargs == base_arg
+ || (nargs == base_arg + 1 && base_is_root)
+ || base_is_empty)
+ {
+ /* if we have skipped everything up to this arg, then the base
+ and all prior components are empty. just set the length to
+ this component; do not add a separator. If the base is empty
+ we can now ignore it. */
+ if (base_is_empty)
+ {
+ base_is_empty = FALSE;
+ total_len = 0;
+ }
+ total_len += len;
+ }
+ else
+ {
+ total_len += 1 + len;
+ }
+ }
+ va_end(va);
+
+ /* base == "/" and no further components. just return that. */
+ if (base_is_root && total_len == 1)
+ return apr_pmemdup(pool, "/", 2);
+
+ /* we got the total size. allocate it, with room for a NULL character. */
+ path = p = apr_palloc(pool, total_len + 1);
+
+ /* if we aren't supposed to skip forward to an absolute component, and if
+ this is not an empty base that we are skipping, then copy the base
+ into the output. */
+ if (base_arg == 0 && ! (SVN_PATH_IS_EMPTY(base) && ! base_is_empty))
+ {
+ if (SVN_PATH_IS_EMPTY(base))
+ memcpy(p, SVN_EMPTY_PATH, len = saved_lengths[0]);
+ else
+ memcpy(p, base, len = saved_lengths[0]);
+ p += len;
+ }
+
+ nargs = 0;
+ va_start(va, base);
+ while ((s = va_arg(va, const char *)) != NULL)
+ {
+ if (SVN_PATH_IS_EMPTY(s))
+ continue;
+
+ if (++nargs < base_arg)
+ continue;
+
+ if (nargs < MAX_SAVED_LENGTHS)
+ len = saved_lengths[nargs];
+ else
+ len = strlen(s);
+
+ /* insert a separator if we aren't copying in the first component
+ (which can happen when base_arg is set). also, don't put in a slash
+ if the prior character is a slash (occurs when prior component
+ is "/"). */
+ if (p != path && p[-1] != '/')
+ *p++ = '/';
+
+ /* copy the new component and advance the pointer */
+ memcpy(p, s, len);
+ p += len;
+ }
+ va_end(va);
+
+ *p = '\0';
+ assert((apr_size_t)(p - path) == total_len);
+
+ return path;
+}
+
+
+
+apr_size_t
+svn_path_component_count(const char *path)
+{
+ apr_size_t count = 0;
+
+ assert(is_canonical(path, strlen(path)));
+
+ while (*path)
+ {
+ const char *start;
+
+ while (*path == '/')
+ ++path;
+
+ start = path;
+
+ while (*path && *path != '/')
+ ++path;
+
+ if (path != start)
+ ++count;
+ }
+
+ return count;
+}
+
+
+/* Return the length of substring necessary to encompass the entire
+ * previous path segment in PATH, which should be a LEN byte string.
+ *
+ * A trailing slash will not be included in the returned length except
+ * in the case in which PATH is absolute and there are no more
+ * previous segments.
+ */
+static apr_size_t
+previous_segment(const char *path,
+ apr_size_t len)
+{
+ if (len == 0)
+ return 0;
+
+ while (len > 0 && path[--len] != '/')
+ ;
+
+ if (len == 0 && path[0] == '/')
+ return 1;
+ else
+ return len;
+}
+
+
+void
+svn_path_add_component(svn_stringbuf_t *path,
+ const char *component)
+{
+ apr_size_t len = strlen(component);
+
+ assert(is_canonical(path->data, path->len));
+ assert(is_canonical(component, strlen(component)));
+
+ /* Append a dir separator, but only if this path is neither empty
+ nor consists of a single dir separator already. */
+ if ((! SVN_PATH_IS_EMPTY(path->data))
+ && (! ((path->len == 1) && (*(path->data) == '/'))))
+ {
+ char dirsep = '/';
+ svn_stringbuf_appendbytes(path, &dirsep, sizeof(dirsep));
+ }
+
+ svn_stringbuf_appendbytes(path, component, len);
+}
+
+
+void
+svn_path_remove_component(svn_stringbuf_t *path)
+{
+ assert(is_canonical(path->data, path->len));
+
+ path->len = previous_segment(path->data, path->len);
+ path->data[path->len] = '\0';
+}
+
+
+void
+svn_path_remove_components(svn_stringbuf_t *path, apr_size_t n)
+{
+ while (n > 0)
+ {
+ svn_path_remove_component(path);
+ n--;
+ }
+}
+
+
+char *
+svn_path_dirname(const char *path, apr_pool_t *pool)
+{
+ apr_size_t len = strlen(path);
+
+ assert(svn_path_is_canonical_internal(path, pool));
+
+ return apr_pstrmemdup(pool, path, previous_segment(path, len));
+}
+
+
+char *
+svn_path_basename(const char *path, apr_pool_t *pool)
+{
+ apr_size_t len = strlen(path);
+ apr_size_t start;
+
+ assert(svn_path_is_canonical_internal(path, pool));
+
+ if (len == 1 && path[0] == '/')
+ start = 0;
+ else
+ {
+ start = len;
+ while (start > 0 && path[start - 1] != '/')
+ --start;
+ }
+
+ return apr_pstrmemdup(pool, path + start, len - start);
+}
+
+int
+svn_path_is_empty(const char *path)
+{
+ assert(is_canonical(path, strlen(path)));
+
+ if (SVN_PATH_IS_EMPTY(path))
+ return 1;
+
+ return 0;
+}
+
+int
+svn_path_compare_paths(const char *path1,
+ const char *path2)
+{
+ apr_size_t path1_len = strlen(path1);
+ apr_size_t path2_len = strlen(path2);
+ apr_size_t min_len = ((path1_len < path2_len) ? path1_len : path2_len);
+ apr_size_t i = 0;
+
+ assert(is_canonical(path1, path1_len));
+ assert(is_canonical(path2, path2_len));
+
+ /* Skip past common prefix. */
+ while (i < min_len && path1[i] == path2[i])
+ ++i;
+
+ /* Are the paths exactly the same? */
+ if ((path1_len == path2_len) && (i >= min_len))
+ return 0;
+
+ /* Children of paths are greater than their parents, but less than
+ greater siblings of their parents. */
+ if ((path1[i] == '/') && (path2[i] == 0))
+ return 1;
+ if ((path2[i] == '/') && (path1[i] == 0))
+ return -1;
+ if (path1[i] == '/')
+ return -1;
+ if (path2[i] == '/')
+ return 1;
+
+ /* Common prefix was skipped above, next character is compared to
+ determine order. We need to use an unsigned comparison, though,
+ so a "next character" of NULL (0x00) sorts numerically
+ smallest. */
+ return (unsigned char)(path1[i]) < (unsigned char)(path2[i]) ? -1 : 1;
+}
+
+/* Return the string length of the longest common ancestor of PATH1 and PATH2.
+ *
+ * This function handles everything except the URL-handling logic
+ * of svn_path_get_longest_ancestor, and assumes that PATH1 and
+ * PATH2 are *not* URLs.
+ *
+ * If the two paths do not share a common ancestor, return 0.
+ *
+ * New strings are allocated in POOL.
+ */
+static apr_size_t
+get_path_ancestor_length(const char *path1,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ apr_size_t path1_len, path2_len;
+ apr_size_t i = 0;
+ apr_size_t last_dirsep = 0;
+
+ path1_len = strlen(path1);
+ path2_len = strlen(path2);
+
+ if (SVN_PATH_IS_EMPTY(path1) || SVN_PATH_IS_EMPTY(path2))
+ return 0;
+
+ while (path1[i] == path2[i])
+ {
+ /* Keep track of the last directory separator we hit. */
+ if (path1[i] == '/')
+ last_dirsep = i;
+
+ i++;
+
+ /* If we get to the end of either path, break out. */
+ if ((i == path1_len) || (i == path2_len))
+ break;
+ }
+
+ /* two special cases:
+ 1. '/' is the longest common ancestor of '/' and '/foo'
+ 2. '/' is the longest common ancestor of '/rif' and '/raf' */
+ if (i == 1 && path1[0] == '/' && path2[0] == '/')
+ return 1;
+
+ /* last_dirsep is now the offset of the last directory separator we
+ crossed before reaching a non-matching byte. i is the offset of
+ that non-matching byte. */
+ if (((i == path1_len) && (path2[i] == '/'))
+ || ((i == path2_len) && (path1[i] == '/'))
+ || ((i == path1_len) && (i == path2_len)))
+ return i;
+ else
+ if (last_dirsep == 0 && path1[0] == '/' && path2[0] == '/')
+ return 1;
+ return last_dirsep;
+}
+
+
+char *
+svn_path_get_longest_ancestor(const char *path1,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ svn_boolean_t path1_is_url = svn_path_is_url(path1);
+ svn_boolean_t path2_is_url = svn_path_is_url(path2);
+
+ /* Are we messing with URLs? If we have a mix of URLs and non-URLs,
+ there's nothing common between them. */
+ if (path1_is_url && path2_is_url)
+ {
+ return svn_uri_get_longest_ancestor(path1, path2, pool);
+ }
+ else if ((! path1_is_url) && (! path2_is_url))
+ {
+ return apr_pstrndup(pool, path1,
+ get_path_ancestor_length(path1, path2, pool));
+ }
+ else
+ {
+ /* A URL and a non-URL => no common prefix */
+ return apr_pmemdup(pool, SVN_EMPTY_PATH, sizeof(SVN_EMPTY_PATH));
+ }
+}
+
+const char *
+svn_path_is_child(const char *path1,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ apr_size_t i;
+
+ /* assert (is_canonical (path1, strlen (path1))); ### Expensive strlen */
+ /* assert (is_canonical (path2, strlen (path2))); ### Expensive strlen */
+
+ /* Allow "" and "foo" to be parent/child */
+ if (SVN_PATH_IS_EMPTY(path1)) /* "" is the parent */
+ {
+ if (SVN_PATH_IS_EMPTY(path2) /* "" not a child */
+ || path2[0] == '/') /* "/foo" not a child */
+ return NULL;
+ else
+ /* everything else is child */
+ return pool ? apr_pstrdup(pool, path2) : path2;
+ }
+
+ /* Reach the end of at least one of the paths. How should we handle
+ things like path1:"foo///bar" and path2:"foo/bar/baz"? It doesn't
+ appear to arise in the current Subversion code, it's not clear to me
+ if they should be parent/child or not. */
+ for (i = 0; path1[i] && path2[i]; i++)
+ if (path1[i] != path2[i])
+ return NULL;
+
+ /* There are two cases that are parent/child
+ ... path1[i] == '\0'
+ .../foo path2[i] == '/'
+ or
+ / path1[i] == '\0'
+ /foo path2[i] != '/'
+ */
+ if (path1[i] == '\0' && path2[i])
+ {
+ if (path2[i] == '/')
+ return pool ? apr_pstrdup(pool, path2 + i + 1) : path2 + i + 1;
+ else if (i == 1 && path1[0] == '/')
+ return pool ? apr_pstrdup(pool, path2 + 1) : path2 + 1;
+ }
+
+ /* Otherwise, path2 isn't a child. */
+ return NULL;
+}
+
+
+svn_boolean_t
+svn_path_is_ancestor(const char *path1, const char *path2)
+{
+ apr_size_t path1_len = strlen(path1);
+
+ /* If path1 is empty and path2 is not absoulte, then path1 is an ancestor. */
+ if (SVN_PATH_IS_EMPTY(path1))
+ return *path2 != '/';
+
+ /* If path1 is a prefix of path2, then:
+ - If path1 ends in a path separator,
+ - If the paths are of the same length
+ OR
+ - path2 starts a new path component after the common prefix,
+ then path1 is an ancestor. */
+ if (strncmp(path1, path2, path1_len) == 0)
+ return path1[path1_len - 1] == '/'
+ || (path2[path1_len] == '/' || path2[path1_len] == '\0');
+
+ return FALSE;
+}
+
+
+apr_array_header_t *
+svn_path_decompose(const char *path,
+ apr_pool_t *pool)
+{
+ apr_size_t i, oldi;
+
+ apr_array_header_t *components =
+ apr_array_make(pool, 1, sizeof(const char *));
+
+ assert(svn_path_is_canonical_internal(path, pool));
+
+ if (SVN_PATH_IS_EMPTY(path))
+ return components; /* ### Should we return a "" component? */
+
+ /* If PATH is absolute, store the '/' as the first component. */
+ i = oldi = 0;
+ if (path[i] == '/')
+ {
+ char dirsep = '/';
+
+ APR_ARRAY_PUSH(components, const char *)
+ = apr_pstrmemdup(pool, &dirsep, sizeof(dirsep));
+
+ i++;
+ oldi++;
+ if (path[i] == '\0') /* path is a single '/' */
+ return components;
+ }
+
+ do
+ {
+ if ((path[i] == '/') || (path[i] == '\0'))
+ {
+ if (SVN_PATH_IS_PLATFORM_EMPTY(path + oldi, i - oldi))
+ APR_ARRAY_PUSH(components, const char *) = SVN_EMPTY_PATH;
+ else
+ APR_ARRAY_PUSH(components, const char *)
+ = apr_pstrmemdup(pool, path + oldi, i - oldi);
+
+ i++;
+ oldi = i; /* skipping past the dirsep */
+ continue;
+ }
+ i++;
+ }
+ while (path[i-1]);
+
+ return components;
+}
+
+
+const char *
+svn_path_compose(const apr_array_header_t *components,
+ apr_pool_t *pool)
+{
+ apr_size_t *lengths = apr_palloc(pool, components->nelts*sizeof(*lengths));
+ apr_size_t max_length = components->nelts;
+ char *path;
+ char *p;
+ int i;
+
+ /* Get the length of each component so a total length can be
+ calculated. */
+ for (i = 0; i < components->nelts; ++i)
+ {
+ apr_size_t l = strlen(APR_ARRAY_IDX(components, i, const char *));
+ lengths[i] = l;
+ max_length += l;
+ }
+
+ path = apr_palloc(pool, max_length + 1);
+ p = path;
+
+ for (i = 0; i < components->nelts; ++i)
+ {
+ /* Append a '/' to the path. Handle the case with an absolute
+ path where a '/' appears in the first component. Only append
+ a '/' if the component is the second component that does not
+ follow a "/" first component; or it is the third or later
+ component. */
+ if (i > 1 ||
+ (i == 1 && strcmp("/", APR_ARRAY_IDX(components,
+ 0,
+ const char *)) != 0))
+ {
+ *p++ = '/';
+ }
+
+ memcpy(p, APR_ARRAY_IDX(components, i, const char *), lengths[i]);
+ p += lengths[i];
+ }
+
+ *p = '\0';
+
+ return path;
+}
+
+
+svn_boolean_t
+svn_path_is_single_path_component(const char *name)
+{
+ assert(is_canonical(name, strlen(name)));
+
+ /* Can't be empty or `..' */
+ if (SVN_PATH_IS_EMPTY(name)
+ || (name[0] == '.' && name[1] == '.' && name[2] == '\0'))
+ return FALSE;
+
+ /* Slashes are bad, m'kay... */
+ if (strchr(name, '/') != NULL)
+ return FALSE;
+
+ /* It is valid. */
+ return TRUE;
+}
+
+
+svn_boolean_t
+svn_path_is_dotpath_present(const char *path)
+{
+ size_t len;
+
+ /* The empty string does not have a dotpath */
+ if (path[0] == '\0')
+ return FALSE;
+
+ /* Handle "." or a leading "./" */
+ if (path[0] == '.' && (path[1] == '\0' || path[1] == '/'))
+ return TRUE;
+
+ /* Paths of length 1 (at this point) have no dotpath present. */
+ if (path[1] == '\0')
+ return FALSE;
+
+ /* If any segment is "/./", then a dotpath is present. */
+ if (strstr(path, "/./") != NULL)
+ return TRUE;
+
+ /* Does the path end in "/." ? */
+ len = strlen(path);
+ return path[len - 2] == '/' && path[len - 1] == '.';
+}
+
+svn_boolean_t
+svn_path_is_backpath_present(const char *path)
+{
+ size_t len;
+
+ /* 0 and 1-length paths do not have a backpath */
+ if (path[0] == '\0' || path[1] == '\0')
+ return FALSE;
+
+ /* Handle ".." or a leading "../" */
+ if (path[0] == '.' && path[1] == '.' && (path[2] == '\0' || path[2] == '/'))
+ return TRUE;
+
+ /* Paths of length 2 (at this point) have no backpath present. */
+ if (path[2] == '\0')
+ return FALSE;
+
+ /* If any segment is "..", then a backpath is present. */
+ if (strstr(path, "/../") != NULL)
+ return TRUE;
+
+ /* Does the path end in "/.." ? */
+ len = strlen(path);
+ return path[len - 3] == '/' && path[len - 2] == '.' && path[len - 1] == '.';
+}
+
+
+/*** URI Stuff ***/
+
+/* Examine PATH as a potential URI, and return a substring of PATH
+ that immediately follows the (scheme):// portion of the URI, or
+ NULL if PATH doesn't appear to be a valid URI. The returned value
+ is not alloced -- it shares memory with PATH. */
+static const char *
+skip_uri_scheme(const char *path)
+{
+ apr_size_t j;
+
+ /* A scheme is terminated by a : and cannot contain any /'s. */
+ for (j = 0; path[j] && path[j] != ':'; ++j)
+ if (path[j] == '/')
+ return NULL;
+
+ if (j > 0 && path[j] == ':' && path[j+1] == '/' && path[j+2] == '/')
+ return path + j + 3;
+
+ return NULL;
+}
+
+
+svn_boolean_t
+svn_path_is_url(const char *path)
+{
+ /* ### This function is reaaaaaaaaaaaaaally stupid right now.
+ We're just going to look for:
+
+ (scheme)://(optional_stuff)
+
+ Where (scheme) has no ':' or '/' characters.
+
+ Someday it might be nice to have an actual URI parser here.
+ */
+ return skip_uri_scheme(path) != NULL;
+}
+
+
+
+/* Here is the BNF for path components in a URI. "pchar" is a
+ character in a path component.
+
+ pchar = unreserved | escaped |
+ ":" | "@" | "&" | "=" | "+" | "$" | ","
+ unreserved = alphanum | mark
+ mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+
+ Note that "escaped" doesn't really apply to what users can put in
+ their paths, so that really means the set of characters is:
+
+ alphanum | mark | ":" | "@" | "&" | "=" | "+" | "$" | ","
+*/
+const char svn_uri__char_validity[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
+
+ /* 64 */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
+
+ /* 128 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ /* 192 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+
+svn_boolean_t
+svn_path_is_uri_safe(const char *path)
+{
+ apr_size_t i;
+
+ /* Skip the URI scheme. */
+ path = skip_uri_scheme(path);
+
+ /* No scheme? Get outta here. */
+ if (! path)
+ return FALSE;
+
+ /* Skip to the first slash that's after the URI scheme. */
+ path = strchr(path, '/');
+
+ /* If there's no first slash, then there's only a host portion;
+ therefore there couldn't be any uri-unsafe characters after the
+ host... so return true. */
+ if (path == NULL)
+ return TRUE;
+
+ for (i = 0; path[i]; i++)
+ {
+ /* Allow '%XX' (where each X is a hex digit) */
+ if (path[i] == '%')
+ {
+ if (svn_ctype_isxdigit(path[i + 1]) &&
+ svn_ctype_isxdigit(path[i + 2]))
+ {
+ i += 2;
+ continue;
+ }
+ return FALSE;
+ }
+ else if (! svn_uri__char_validity[((unsigned char)path[i])])
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+
+/* URI-encode each character c in PATH for which TABLE[c] is 0.
+ If no encoding was needed, return PATH, else return a new string allocated
+ in POOL. */
+static const char *
+uri_escape(const char *path, const char table[], apr_pool_t *pool)
+{
+ svn_stringbuf_t *retstr;
+ apr_size_t i, copied = 0;
+ int c;
+
+ retstr = svn_stringbuf_create_ensure(strlen(path), pool);
+ for (i = 0; path[i]; i++)
+ {
+ c = (unsigned char)path[i];
+ if (table[c])
+ continue;
+
+ /* If we got here, we're looking at a character that isn't
+ supported by the (or at least, our) URI encoding scheme. We
+ need to escape this character. */
+
+ /* First things first, copy all the good stuff that we haven't
+ yet copied into our output buffer. */
+ if (i - copied)
+ svn_stringbuf_appendbytes(retstr, path + copied,
+ i - copied);
+
+ /* Now, write in our escaped character, consisting of the
+ '%' and two digits. We cast the C to unsigned char here because
+ the 'X' format character will be tempted to treat it as an unsigned
+ int...which causes problem when messing with 0x80-0xFF chars.
+ We also need space for a null as apr_snprintf will write one. */
+ svn_stringbuf_ensure(retstr, retstr->len + 4);
+ apr_snprintf(retstr->data + retstr->len, 4, "%%%02X", (unsigned char)c);
+ retstr->len += 3;
+
+ /* Finally, update our copy counter. */
+ copied = i + 1;
+ }
+
+ /* If we didn't encode anything, we don't need to duplicate the string. */
+ if (retstr->len == 0)
+ return path;
+
+ /* Anything left to copy? */
+ if (i - copied)
+ svn_stringbuf_appendbytes(retstr, path + copied, i - copied);
+
+ /* retstr is null-terminated either by apr_snprintf or the svn_stringbuf
+ functions. */
+
+ return retstr->data;
+}
+
+
+const char *
+svn_path_uri_encode(const char *path, apr_pool_t *pool)
+{
+ const char *ret;
+
+ ret = uri_escape(path, svn_uri__char_validity, pool);
+
+ /* Our interface guarantees a copy. */
+ if (ret == path)
+ return apr_pstrdup(pool, path);
+ else
+ return ret;
+}
+
+static const char iri_escape_chars[256] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ /* 128 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+const char *
+svn_path_uri_from_iri(const char *iri, apr_pool_t *pool)
+{
+ return uri_escape(iri, iri_escape_chars, pool);
+}
+
+static const char uri_autoescape_chars[256] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
+
+ /* 64 */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
+
+ /* 128 */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ /* 192 */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+};
+
+const char *
+svn_path_uri_autoescape(const char *uri, apr_pool_t *pool)
+{
+ return uri_escape(uri, uri_autoescape_chars, pool);
+}
+
+const char *
+svn_path_uri_decode(const char *path, apr_pool_t *pool)
+{
+ svn_stringbuf_t *retstr;
+ apr_size_t i;
+ svn_boolean_t query_start = FALSE;
+
+ /* avoid repeated realloc */
+ retstr = svn_stringbuf_create_ensure(strlen(path) + 1, pool);
+
+ retstr->len = 0;
+ for (i = 0; path[i]; i++)
+ {
+ char c = path[i];
+
+ if (c == '?')
+ {
+ /* Mark the start of the query string, if it exists. */
+ query_start = TRUE;
+ }
+ else if (c == '+' && query_start)
+ {
+ /* Only do this if we are into the query string.
+ * RFC 2396, section 3.3 */
+ c = ' ';
+ }
+ else if (c == '%' && svn_ctype_isxdigit(path[i + 1])
+ && svn_ctype_isxdigit(path[i+2]))
+ {
+ char digitz[3];
+ digitz[0] = path[++i];
+ digitz[1] = path[++i];
+ digitz[2] = '\0';
+ c = (char)(strtol(digitz, NULL, 16));
+ }
+
+ retstr->data[retstr->len++] = c;
+ }
+
+ /* Null-terminate this bad-boy. */
+ retstr->data[retstr->len] = 0;
+
+ return retstr->data;
+}
+
+
+const char *
+svn_path_url_add_component2(const char *url,
+ const char *component,
+ apr_pool_t *pool)
+{
+ /* = svn_path_uri_encode() but without always copying */
+ component = uri_escape(component, svn_uri__char_validity, pool);
+
+ return svn_path_join_internal(url, component, pool);
+}
+
+svn_error_t *
+svn_path_get_absolute(const char **pabsolute,
+ const char *relative,
+ apr_pool_t *pool)
+{
+ if (svn_path_is_url(relative))
+ {
+ *pabsolute = apr_pstrdup(pool, relative);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_dirent_get_absolute(pabsolute, relative, pool);
+}
+
+
+#if !defined(WIN32) && !defined(DARWIN)
+/** Get APR's internal path encoding. */
+static svn_error_t *
+get_path_encoding(svn_boolean_t *path_is_utf8, apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ int encoding_style;
+
+ apr_err = apr_filepath_encoding(&encoding_style, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err,
+ _("Can't determine the native path encoding"));
+
+ /* ### What to do about APR_FILEPATH_ENCODING_UNKNOWN?
+ Well, for now we'll just punt to the svn_utf_ functions;
+ those will at least do the ASCII-subset check. */
+ *path_is_utf8 = (encoding_style == APR_FILEPATH_ENCODING_UTF8);
+ return SVN_NO_ERROR;
+}
+#endif
+
+
+svn_error_t *
+svn_path_cstring_from_utf8(const char **path_apr,
+ const char *path_utf8,
+ apr_pool_t *pool)
+{
+#if !defined(WIN32) && !defined(DARWIN)
+ svn_boolean_t path_is_utf8;
+ SVN_ERR(get_path_encoding(&path_is_utf8, pool));
+ if (path_is_utf8)
+#endif
+ {
+ *path_apr = apr_pstrdup(pool, path_utf8);
+ return SVN_NO_ERROR;
+ }
+#if !defined(WIN32) && !defined(DARWIN)
+ else
+ return svn_utf_cstring_from_utf8(path_apr, path_utf8, pool);
+#endif
+}
+
+
+svn_error_t *
+svn_path_cstring_to_utf8(const char **path_utf8,
+ const char *path_apr,
+ apr_pool_t *pool)
+{
+#if !defined(WIN32) && !defined(DARWIN)
+ svn_boolean_t path_is_utf8;
+ SVN_ERR(get_path_encoding(&path_is_utf8, pool));
+ if (path_is_utf8)
+#endif
+ {
+ *path_utf8 = apr_pstrdup(pool, path_apr);
+ return SVN_NO_ERROR;
+ }
+#if !defined(WIN32) && !defined(DARWIN)
+ else
+ return svn_utf_cstring_to_utf8(path_utf8, path_apr, pool);
+#endif
+}
+
+
+/* Return a copy of PATH, allocated from POOL, for which control
+ characters have been escaped using the form \NNN (where NNN is the
+ octal representation of the byte's ordinal value). */
+const char *
+svn_path_illegal_path_escape(const char *path, apr_pool_t *pool)
+{
+ svn_stringbuf_t *retstr;
+ apr_size_t i, copied = 0;
+ int c;
+
+ /* At least one control character:
+ strlen - 1 (control) + \ + N + N + N + null . */
+ retstr = svn_stringbuf_create_ensure(strlen(path) + 4, pool);
+ for (i = 0; path[i]; i++)
+ {
+ c = (unsigned char)path[i];
+ if (! svn_ctype_iscntrl(c))
+ continue;
+
+ /* If we got here, we're looking at a character that isn't
+ supported by the (or at least, our) URI encoding scheme. We
+ need to escape this character. */
+
+ /* First things first, copy all the good stuff that we haven't
+ yet copied into our output buffer. */
+ if (i - copied)
+ svn_stringbuf_appendbytes(retstr, path + copied,
+ i - copied);
+
+ /* Make sure buffer is big enough for '\' 'N' 'N' 'N' (and NUL) */
+ svn_stringbuf_ensure(retstr, retstr->len + 5);
+ /*### The backslash separator doesn't work too great with Windows,
+ but it's what we'll use for consistency with invalid utf8
+ formatting (until someone has a better idea) */
+ apr_snprintf(retstr->data + retstr->len, 5, "\\%03o", (unsigned char)c);
+ retstr->len += 4;
+
+ /* Finally, update our copy counter. */
+ copied = i + 1;
+ }
+
+ /* If we didn't encode anything, we don't need to duplicate the string. */
+ if (retstr->len == 0)
+ return path;
+
+ /* Anything left to copy? */
+ if (i - copied)
+ svn_stringbuf_appendbytes(retstr, path + copied, i - copied);
+
+ /* retstr is null-terminated either by apr_snprintf or the svn_stringbuf
+ functions. */
+
+ return retstr->data;
+}
+
+svn_error_t *
+svn_path_check_valid(const char *path, apr_pool_t *pool)
+{
+ const char *c;
+
+ for (c = path; *c; c++)
+ {
+ if (svn_ctype_iscntrl(*c))
+ {
+ return svn_error_createf
+ (SVN_ERR_FS_PATH_SYNTAX, NULL,
+ _("Invalid control character '0x%02x' in path '%s'"),
+ (unsigned char)*c,
+ svn_path_illegal_path_escape(svn_dirent_local_style(path, pool),
+ pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_path_splitext(const char **path_root,
+ const char **path_ext,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *last_dot, *last_slash;
+
+ /* Easy out -- why do all the work when there's no way to report it? */
+ if (! (path_root || path_ext))
+ return;
+
+ /* Do we even have a period in this thing? And if so, is there
+ anything after it? We look for the "rightmost" period in the
+ string. */
+ last_dot = strrchr(path, '.');
+ if (last_dot && (last_dot + 1 != '\0'))
+ {
+ /* If we have a period, we need to make sure it occurs in the
+ final path component -- that there's no path separator
+ between the last period and the end of the PATH -- otherwise,
+ it doesn't count. Also, we want to make sure that our period
+ isn't the first character of the last component. */
+ last_slash = strrchr(path, '/');
+ if ((last_slash && (last_dot > (last_slash + 1)))
+ || ((! last_slash) && (last_dot > path)))
+ {
+ if (path_root)
+ *path_root = apr_pstrmemdup(pool, path,
+ (last_dot - path + 1) * sizeof(*path));
+ if (path_ext)
+ *path_ext = apr_pstrdup(pool, last_dot + 1);
+ return;
+ }
+ }
+ /* If we get here, we never found a suitable separator character, so
+ there's no split. */
+ if (path_root)
+ *path_root = apr_pstrdup(pool, path);
+ if (path_ext)
+ *path_ext = "";
+}
+
+
+/* Repository relative URLs (^/). */
+
+svn_boolean_t
+svn_path_is_repos_relative_url(const char *path)
+{
+ return (0 == strncmp("^/", path, 2));
+}
+
+svn_error_t *
+svn_path_resolve_repos_relative_url(const char **absolute_url,
+ const char *relative_url,
+ const char *repos_root_url,
+ apr_pool_t *pool)
+{
+ if (! svn_path_is_repos_relative_url(relative_url))
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ _("Improper relative URL '%s'"),
+ relative_url);
+
+ /* No assumptions are made about the canonicalization of the inut
+ * arguments, it is presumed that the output will be canonicalized after
+ * this function, which will remove any duplicate path separator.
+ */
+ *absolute_url = apr_pstrcat(pool, repos_root_url, relative_url + 1,
+ (char *)NULL);
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_subr/pool.c b/contrib/subversion/subversion/libsvn_subr/pool.c
new file mode 100644
index 0000000..179ef79
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/pool.c
@@ -0,0 +1,142 @@
+/* pool.c: pool wrappers for Subversion
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <apr_general.h>
+#include <apr_pools.h>
+#include <apr_thread_mutex.h>
+
+#include "svn_pools.h"
+
+
+#if APR_POOL_DEBUG
+/* file_line for the non-debug case. */
+static const char SVN_FILE_LINE_UNDEFINED[] = "svn:<undefined>";
+#endif /* APR_POOL_DEBUG */
+
+
+
+/*-----------------------------------------------------------------*/
+
+
+/* Pool allocation handler which just aborts, since we aren't generally
+ prepared to deal with out-of-memory errors.
+ */
+static int
+abort_on_pool_failure(int retcode)
+{
+ /* Don't translate this string! It requires memory allocation to do so!
+ And we don't have any of it... */
+ printf("Out of memory - terminating application.\n");
+ abort();
+ return 0; /* not reached */
+}
+
+
+#if APR_POOL_DEBUG
+#undef svn_pool_create_ex
+#endif /* APR_POOL_DEBUG */
+
+#if !APR_POOL_DEBUG
+
+apr_pool_t *
+svn_pool_create_ex(apr_pool_t *parent_pool, apr_allocator_t *allocator)
+{
+ apr_pool_t *pool;
+ apr_pool_create_ex(&pool, parent_pool, abort_on_pool_failure, allocator);
+ return pool;
+}
+
+/* Wrapper that ensures binary compatibility */
+apr_pool_t *
+svn_pool_create_ex_debug(apr_pool_t *pool, apr_allocator_t *allocator,
+ const char *file_line)
+{
+ return svn_pool_create_ex(pool, allocator);
+}
+
+#else /* APR_POOL_DEBUG */
+
+apr_pool_t *
+svn_pool_create_ex_debug(apr_pool_t *parent_pool, apr_allocator_t *allocator,
+ const char *file_line)
+{
+ apr_pool_t *pool;
+ apr_pool_create_ex_debug(&pool, parent_pool, abort_on_pool_failure,
+ allocator, file_line);
+ return pool;
+}
+
+/* Wrapper that ensures binary compatibility */
+apr_pool_t *
+svn_pool_create_ex(apr_pool_t *pool, apr_allocator_t *allocator)
+{
+ return svn_pool_create_ex_debug(pool, allocator, SVN_FILE_LINE_UNDEFINED);
+}
+
+#endif /* APR_POOL_DEBUG */
+
+apr_allocator_t *
+svn_pool_create_allocator(svn_boolean_t thread_safe)
+{
+ apr_allocator_t *allocator;
+ apr_pool_t *pool;
+
+ /* create the allocator and limit it's internal free list to keep
+ * memory usage in check */
+
+ if (apr_allocator_create(&allocator))
+ abort_on_pool_failure(EXIT_FAILURE);
+
+ apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE);
+
+ /* create the root pool */
+
+ pool = svn_pool_create_ex(NULL, allocator);
+ apr_allocator_owner_set(allocator, pool);
+
+#if APR_POOL_DEBUG
+ apr_pool_tag (pool, "svn root pool");
+#endif
+
+ /* By default, allocators are *not* thread-safe. We must provide a mutex
+ * if we want thread-safety for that mutex. */
+
+#if APR_HAS_THREADS
+ if (thread_safe)
+ {
+ apr_thread_mutex_t *mutex;
+ apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pool);
+ apr_allocator_mutex_set(allocator, mutex);
+ }
+#endif
+
+ /* better safe than sorry */
+ SVN_ERR_ASSERT_NO_RETURN(allocator != NULL);
+
+ return allocator;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/prompt.c b/contrib/subversion/subversion/libsvn_subr/prompt.c
new file mode 100644
index 0000000..92ee6a2
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/prompt.c
@@ -0,0 +1,954 @@
+/*
+ * prompt.c -- ask the user for authentication information.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_lib.h>
+#include <apr_poll.h>
+#include <apr_portable.h>
+
+#include "svn_cmdline.h"
+#include "svn_ctype.h"
+#include "svn_string.h"
+#include "svn_auth.h"
+#include "svn_error.h"
+#include "svn_path.h"
+
+#include "private/svn_cmdline_private.h"
+#include "svn_private_config.h"
+
+#ifdef WIN32
+#include <conio.h>
+#elif defined(HAVE_TERMIOS_H)
+#include <signal.h>
+#include <termios.h>
+#endif
+
+
+
+/* Descriptor of an open terminal */
+typedef struct terminal_handle_t terminal_handle_t;
+struct terminal_handle_t
+{
+ apr_file_t *infd; /* input file handle */
+ apr_file_t *outfd; /* output file handle */
+ svn_boolean_t noecho; /* terminal echo was turned off */
+ svn_boolean_t close_handles; /* close handles when closing the terminal */
+ apr_pool_t *pool; /* pool associated with the file handles */
+
+#ifdef HAVE_TERMIOS_H
+ svn_boolean_t restore_state; /* terminal state was changed */
+ apr_os_file_t osinfd; /* OS-specific handle for infd */
+ struct termios attr; /* saved terminal attributes */
+#endif
+};
+
+/* Initialize safe state of terminal_handle_t. */
+static void
+terminal_handle_init(terminal_handle_t *terminal,
+ apr_file_t *infd, apr_file_t *outfd,
+ svn_boolean_t noecho, svn_boolean_t close_handles,
+ apr_pool_t *pool)
+{
+ memset(terminal, 0, sizeof(*terminal));
+ terminal->infd = infd;
+ terminal->outfd = outfd;
+ terminal->noecho = noecho;
+ terminal->close_handles = close_handles;
+ terminal->pool = pool;
+}
+
+/*
+ * Common pool cleanup handler for terminal_handle_t. Closes TERMINAL.
+ * If CLOSE_HANDLES is TRUE, close the terminal file handles.
+ * If RESTORE_STATE is TRUE, restores the TERMIOS flags of the terminal.
+ */
+static apr_status_t
+terminal_cleanup_handler(terminal_handle_t *terminal,
+ svn_boolean_t close_handles,
+ svn_boolean_t restore_state)
+{
+ apr_status_t status = APR_SUCCESS;
+
+#ifdef HAVE_TERMIOS_H
+ /* Restore terminal state flags. */
+ if (restore_state && terminal->restore_state)
+ tcsetattr(terminal->osinfd, TCSANOW, &terminal->attr);
+#endif
+
+ /* Close terminal handles. */
+ if (close_handles && terminal->close_handles)
+ {
+ apr_file_t *const infd = terminal->infd;
+ apr_file_t *const outfd = terminal->outfd;
+
+ if (infd)
+ {
+ terminal->infd = NULL;
+ status = apr_file_close(infd);
+ }
+
+ if (!status && outfd && outfd != infd)
+ {
+ terminal->outfd = NULL;
+ status = apr_file_close(terminal->outfd);
+ }
+ }
+ return status;
+}
+
+/* Normal pool cleanup for a terminal. */
+static apr_status_t terminal_plain_cleanup(void *baton)
+{
+ return terminal_cleanup_handler(baton, FALSE, TRUE);
+}
+
+/* Child pool cleanup for a terminal -- does not restore echo state. */
+static apr_status_t terminal_child_cleanup(void *baton)
+{
+ return terminal_cleanup_handler(baton, FALSE, FALSE);
+}
+
+/* Explicitly close the terminal, removing its cleanup handlers. */
+static svn_error_t *
+terminal_close(terminal_handle_t *terminal)
+{
+ apr_status_t status;
+
+ /* apr_pool_cleanup_kill() removes both normal and child cleanup */
+ apr_pool_cleanup_kill(terminal->pool, terminal, terminal_plain_cleanup);
+
+ status = terminal_cleanup_handler(terminal, TRUE, TRUE);
+ if (status)
+ return svn_error_create(status, NULL, _("Can't close terminal"));
+ return SVN_NO_ERROR;
+}
+
+/* Allocate and open *TERMINAL. If NOECHO is TRUE, try to turn off
+ terminal echo. Use POOL for all allocations.*/
+static svn_error_t *
+terminal_open(terminal_handle_t **terminal, svn_boolean_t noecho,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+
+#ifdef WIN32
+ /* On Windows, we'll use the console API directly if the process has
+ a console attached; otherwise we'll just use stdin and stderr. */
+ const HANDLE conin = CreateFileW(L"CONIN$", GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ *terminal = apr_palloc(pool, sizeof(terminal_handle_t));
+ if (conin != INVALID_HANDLE_VALUE)
+ {
+ /* The process has a console. */
+ CloseHandle(conin);
+ terminal_handle_init(*terminal, NULL, NULL, noecho, FALSE, NULL);
+ return SVN_NO_ERROR;
+ }
+#else /* !WIN32 */
+ /* Without evidence to the contrary, we'll assume this is *nix and
+ try to open /dev/tty. If that fails, we'll use stdin for input
+ and stderr for prompting. */
+ apr_file_t *tmpfd;
+ status = apr_file_open(&tmpfd, "/dev/tty",
+ APR_FOPEN_READ | APR_FOPEN_WRITE,
+ APR_OS_DEFAULT, pool);
+ *terminal = apr_palloc(pool, sizeof(terminal_handle_t));
+ if (!status)
+ {
+ /* We have a terminal handle that we can use for input and output. */
+ terminal_handle_init(*terminal, tmpfd, tmpfd, FALSE, TRUE, pool);
+ }
+#endif /* !WIN32 */
+ else
+ {
+ /* There is no terminal. Sigh. */
+ apr_file_t *infd;
+ apr_file_t *outfd;
+
+ status = apr_file_open_stdin(&infd, pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't open stdin"));
+ status = apr_file_open_stderr(&outfd, pool);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't open stderr"));
+ terminal_handle_init(*terminal, infd, outfd, FALSE, FALSE, pool);
+ }
+
+#ifdef HAVE_TERMIOS_H
+ /* Set terminal state */
+ if (0 == apr_os_file_get(&(*terminal)->osinfd, (*terminal)->infd))
+ {
+ if (0 == tcgetattr((*terminal)->osinfd, &(*terminal)->attr))
+ {
+ struct termios attr = (*terminal)->attr;
+ /* Turn off signal handling and canonical input mode */
+ attr.c_lflag &= ~(ISIG | ICANON);
+ attr.c_cc[VMIN] = 1; /* Read one byte at a time */
+ attr.c_cc[VTIME] = 0; /* No timeout, wait indefinitely */
+ attr.c_lflag &= ~(ECHO); /* Turn off echo */
+ if (0 == tcsetattr((*terminal)->osinfd, TCSAFLUSH, &attr))
+ {
+ (*terminal)->noecho = noecho;
+ (*terminal)->restore_state = TRUE;
+ }
+ }
+ }
+#endif /* HAVE_TERMIOS_H */
+
+ /* Register pool cleanup to close handles and restore echo state. */
+ apr_pool_cleanup_register((*terminal)->pool, *terminal,
+ terminal_plain_cleanup,
+ terminal_child_cleanup);
+ return SVN_NO_ERROR;
+}
+
+/* Write a null-terminated STRING to TERMINAL.
+ Use POOL for allocations related to converting STRING from UTF-8. */
+static svn_error_t *
+terminal_puts(const char *string, terminal_handle_t *terminal,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_status_t status;
+ const char *converted;
+
+ err = svn_cmdline_cstring_from_utf8(&converted, string, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ converted = svn_cmdline_cstring_from_utf8_fuzzy(string, pool);
+ }
+
+#ifdef WIN32
+ if (!terminal->outfd)
+ {
+ /* See terminal_open; we're using Console I/O. */
+ _cputs(converted);
+ return SVN_NO_ERROR;
+ }
+#endif
+
+ status = apr_file_write_full(terminal->outfd, converted,
+ strlen(converted), NULL);
+ if (!status)
+ status = apr_file_flush(terminal->outfd);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't write to terminal"));
+ return SVN_NO_ERROR;
+}
+
+/* These codes can be returned from terminal_getc instead of a character. */
+#define TERMINAL_NONE 0x80000 /* no character read, retry */
+#define TERMINAL_DEL (TERMINAL_NONE + 1) /* the input was a deleteion */
+#define TERMINAL_EOL (TERMINAL_NONE + 2) /* end of input/end of line */
+#define TERMINAL_EOF (TERMINAL_NONE + 3) /* end of file during input */
+
+/* Helper for terminal_getc: writes CH to OUTFD as a control char. */
+#ifndef WIN32
+static void
+echo_control_char(char ch, apr_file_t *outfd)
+{
+ if (svn_ctype_iscntrl(ch))
+ {
+ const char substitute = (ch < 32? '@' + ch : '?');
+ apr_file_putc('^', outfd);
+ apr_file_putc(substitute, outfd);
+ }
+ else if (svn_ctype_isprint(ch))
+ {
+ /* Pass printable characters unchanged. */
+ apr_file_putc(ch, outfd);
+ }
+ else
+ {
+ /* Everything else is strange. */
+ apr_file_putc('^', outfd);
+ apr_file_putc('!', outfd);
+ }
+}
+#endif /* WIN32 */
+
+/* Read one character or control code from TERMINAL, returning it in CODE.
+ if CAN_ERASE and the input was a deletion, emit codes to erase the
+ last character displayed on the terminal.
+ Use POOL for all allocations. */
+static svn_error_t *
+terminal_getc(int *code, terminal_handle_t *terminal,
+ svn_boolean_t can_erase, apr_pool_t *pool)
+{
+ const svn_boolean_t echo = !terminal->noecho;
+ apr_status_t status = APR_SUCCESS;
+ char ch;
+
+#ifdef WIN32
+ if (!terminal->infd)
+ {
+ /* See terminal_open; we're using Console I/O. */
+
+ /* The following was hoisted from APR's getpass for Windows. */
+ int concode = _getch();
+ switch (concode)
+ {
+ case '\r': /* end-of-line */
+ *code = TERMINAL_EOL;
+ if (echo)
+ _cputs("\r\n");
+ break;
+
+ case EOF: /* end-of-file */
+ case 26: /* Ctrl+Z */
+ *code = TERMINAL_EOF;
+ if (echo)
+ _cputs((concode == EOF ? "[EOF]\r\n" : "^Z\r\n"));
+ break;
+
+ case 3: /* Ctrl+C, Ctrl+Break */
+ /* _getch() bypasses Ctrl+C but not Ctrl+Break detection! */
+ if (echo)
+ _cputs("^C\r\n");
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
+
+ case 0: /* Function code prefix */
+ case 0xE0:
+ concode = (concode << 4) | _getch();
+ /* Catch {DELETE}, {<--}, Num{DEL} and Num{<--} */
+ if (concode == 0xE53 || concode == 0xE4B
+ || concode == 0x053 || concode == 0x04B)
+ {
+ *code = TERMINAL_DEL;
+ if (can_erase)
+ _cputs("\b \b");
+ }
+ else
+ {
+ *code = TERMINAL_NONE;
+ _putch('\a');
+ }
+ break;
+
+ case '\b': /* BS */
+ case 127: /* DEL */
+ *code = TERMINAL_DEL;
+ if (can_erase)
+ _cputs("\b \b");
+ break;
+
+ default:
+ if (!apr_iscntrl(concode))
+ {
+ *code = (int)(unsigned char)concode;
+ _putch(echo ? concode : '*');
+ }
+ else
+ {
+ *code = TERMINAL_NONE;
+ _putch('\a');
+ }
+ }
+ return SVN_NO_ERROR;
+ }
+#elif defined(HAVE_TERMIOS_H)
+ if (terminal->restore_state)
+ {
+ /* We're using a bytewise-immediate termios input */
+ const struct termios *const attr = &terminal->attr;
+
+ status = apr_file_getc(&ch, terminal->infd);
+ if (status)
+ return svn_error_wrap_apr(status, _("Can't read from terminal"));
+
+ if (ch == attr->c_cc[VINTR] || ch == attr->c_cc[VQUIT])
+ {
+ /* Break */
+ echo_control_char(ch, terminal->outfd);
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
+ }
+ else if (ch == '\r' || ch == '\n' || ch == attr->c_cc[VEOL])
+ {
+ /* Newline */
+ *code = TERMINAL_EOL;
+ apr_file_putc('\n', terminal->outfd);
+ }
+ else if (ch == '\b' || ch == attr->c_cc[VERASE])
+ {
+ /* Delete */
+ *code = TERMINAL_DEL;
+ if (can_erase)
+ {
+ apr_file_putc('\b', terminal->outfd);
+ apr_file_putc(' ', terminal->outfd);
+ apr_file_putc('\b', terminal->outfd);
+ }
+ }
+ else if (ch == attr->c_cc[VEOF])
+ {
+ /* End of input */
+ *code = TERMINAL_EOF;
+ echo_control_char(ch, terminal->outfd);
+ }
+ else if (ch == attr->c_cc[VSUSP])
+ {
+ /* Suspend */
+ *code = TERMINAL_NONE;
+ kill(0, SIGTSTP);
+ }
+ else if (!apr_iscntrl(ch))
+ {
+ /* Normal character */
+ *code = (int)(unsigned char)ch;
+ apr_file_putc((echo ? ch : '*'), terminal->outfd);
+ }
+ else
+ {
+ /* Ignored character */
+ *code = TERMINAL_NONE;
+ apr_file_putc('\a', terminal->outfd);
+ }
+ return SVN_NO_ERROR;
+ }
+#endif /* HAVE_TERMIOS_H */
+
+ /* Fall back to plain stream-based I/O. */
+#ifndef WIN32
+ /* Wait for input on termin. This code is based on
+ apr_wait_for_io_or_timeout().
+ Note that this will return an EINTR on a signal. */
+ {
+ apr_pollfd_t pollset;
+ int n;
+
+ pollset.desc_type = APR_POLL_FILE;
+ pollset.desc.f = terminal->infd;
+ pollset.p = pool;
+ pollset.reqevents = APR_POLLIN;
+
+ status = apr_poll(&pollset, 1, &n, -1);
+
+ if (n == 1 && pollset.rtnevents & APR_POLLIN)
+ status = APR_SUCCESS;
+ }
+#endif /* !WIN32 */
+
+ if (!status)
+ status = apr_file_getc(&ch, terminal->infd);
+ if (APR_STATUS_IS_EINTR(status))
+ {
+ *code = TERMINAL_NONE;
+ return SVN_NO_ERROR;
+ }
+ else if (APR_STATUS_IS_EOF(status))
+ {
+ *code = TERMINAL_EOF;
+ return SVN_NO_ERROR;
+ }
+ else if (status)
+ return svn_error_wrap_apr(status, _("Can't read from terminal"));
+
+ *code = (int)(unsigned char)ch;
+ return SVN_NO_ERROR;
+}
+
+
+/* Set @a *result to the result of prompting the user with @a
+ * prompt_msg. Use @ *pb to get the cancel_func and cancel_baton.
+ * Do not call the cancel_func if @a *pb is NULL.
+ * Allocate @a *result in @a pool.
+ *
+ * If @a hide is true, then try to avoid displaying the user's input.
+ */
+static svn_error_t *
+prompt(const char **result,
+ const char *prompt_msg,
+ svn_boolean_t hide,
+ svn_cmdline_prompt_baton2_t *pb,
+ apr_pool_t *pool)
+{
+ /* XXX: If this functions ever starts using members of *pb
+ * which were not included in svn_cmdline_prompt_baton_t,
+ * we need to update svn_cmdline_prompt_user2 and its callers. */
+
+ svn_boolean_t saw_first_half_of_eol = FALSE;
+ svn_stringbuf_t *strbuf = svn_stringbuf_create_empty(pool);
+ terminal_handle_t *terminal;
+ int code;
+ char c;
+
+ SVN_ERR(terminal_open(&terminal, hide, pool));
+ SVN_ERR(terminal_puts(prompt_msg, terminal, pool));
+
+ while (1)
+ {
+ SVN_ERR(terminal_getc(&code, terminal, (strbuf->len > 0), pool));
+
+ /* Check for cancellation after a character has been read, some
+ input processing modes may eat ^C and we'll only notice a
+ cancellation signal after characters have been read --
+ sometimes even after a newline. */
+ if (pb)
+ SVN_ERR(pb->cancel_func(pb->cancel_baton));
+
+ switch (code)
+ {
+ case TERMINAL_NONE:
+ /* Nothing useful happened; retry. */
+ continue;
+
+ case TERMINAL_DEL:
+ /* Delete the last input character. terminal_getc takes care
+ of erasing the feedback from the terminal, if applicable. */
+ svn_stringbuf_chop(strbuf, 1);
+ continue;
+
+ case TERMINAL_EOL:
+ /* End-of-line means end of input. Trick the EOL-detection code
+ below to stop reading. */
+ saw_first_half_of_eol = TRUE;
+ c = APR_EOL_STR[1]; /* Could be \0 but still stops reading. */
+ break;
+
+ case TERMINAL_EOF:
+ return svn_error_create(
+ APR_EOF,
+ terminal_close(terminal),
+ _("End of file while reading from terminal"));
+
+ default:
+ /* Convert the returned code back to the character. */
+ c = (char)code;
+ }
+
+ if (saw_first_half_of_eol)
+ {
+ if (c == APR_EOL_STR[1])
+ break;
+ else
+ saw_first_half_of_eol = FALSE;
+ }
+ else if (c == APR_EOL_STR[0])
+ {
+ /* GCC might complain here: "warning: will never be executed"
+ * That's fine. This is a compile-time check for "\r\n\0" */
+ if (sizeof(APR_EOL_STR) == 3)
+ {
+ saw_first_half_of_eol = TRUE;
+ continue;
+ }
+ else if (sizeof(APR_EOL_STR) == 2)
+ break;
+ else
+ /* ### APR_EOL_STR holds more than two chars? Who
+ ever heard of such a thing? */
+ SVN_ERR_MALFUNCTION();
+ }
+
+ svn_stringbuf_appendbyte(strbuf, c);
+ }
+
+ if (terminal->noecho)
+ {
+ /* If terminal echo was turned off, make sure future output
+ to the terminal starts on a new line, as expected. */
+ SVN_ERR(terminal_puts(APR_EOL_STR, terminal, pool));
+ }
+ SVN_ERR(terminal_close(terminal));
+
+ return svn_cmdline_cstring_to_utf8(result, strbuf->data, pool);
+}
+
+
+
+/** Prompt functions for auth providers. **/
+
+/* Helper function for auth provider prompters: mention the
+ * authentication @a realm on stderr, in a manner appropriate for
+ * preceding a prompt; or if @a realm is null, then do nothing.
+ */
+static svn_error_t *
+maybe_print_realm(const char *realm, apr_pool_t *pool)
+{
+ if (realm)
+ {
+ terminal_handle_t *terminal;
+ SVN_ERR(terminal_open(&terminal, FALSE, pool));
+ SVN_ERR(terminal_puts(
+ apr_psprintf(pool,
+ _("Authentication realm: %s\n"), realm),
+ terminal, pool));
+ SVN_ERR(terminal_close(terminal));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements 'svn_auth_simple_prompt_func_t'. */
+svn_error_t *
+svn_cmdline_auth_simple_prompt(svn_auth_cred_simple_t **cred_p,
+ void *baton,
+ const char *realm,
+ const char *username,
+ svn_boolean_t may_save,
+ apr_pool_t *pool)
+{
+ svn_auth_cred_simple_t *ret = apr_pcalloc(pool, sizeof(*ret));
+ const char *pass_prompt;
+ svn_cmdline_prompt_baton2_t *pb = baton;
+
+ SVN_ERR(maybe_print_realm(realm, pool));
+
+ if (username)
+ ret->username = apr_pstrdup(pool, username);
+ else
+ SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool));
+
+ pass_prompt = apr_psprintf(pool, _("Password for '%s': "), ret->username);
+ SVN_ERR(prompt(&(ret->password), pass_prompt, TRUE, pb, pool));
+ ret->may_save = may_save;
+ *cred_p = ret;
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements 'svn_auth_username_prompt_func_t'. */
+svn_error_t *
+svn_cmdline_auth_username_prompt(svn_auth_cred_username_t **cred_p,
+ void *baton,
+ const char *realm,
+ svn_boolean_t may_save,
+ apr_pool_t *pool)
+{
+ svn_auth_cred_username_t *ret = apr_pcalloc(pool, sizeof(*ret));
+ svn_cmdline_prompt_baton2_t *pb = baton;
+
+ SVN_ERR(maybe_print_realm(realm, pool));
+
+ SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool));
+ ret->may_save = may_save;
+ *cred_p = ret;
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'. */
+svn_error_t *
+svn_cmdline_auth_ssl_server_trust_prompt
+ (svn_auth_cred_ssl_server_trust_t **cred_p,
+ void *baton,
+ const char *realm,
+ apr_uint32_t failures,
+ const svn_auth_ssl_server_cert_info_t *cert_info,
+ svn_boolean_t may_save,
+ apr_pool_t *pool)
+{
+ const char *choice;
+ svn_stringbuf_t *msg;
+ svn_cmdline_prompt_baton2_t *pb = baton;
+ svn_stringbuf_t *buf = svn_stringbuf_createf
+ (pool, _("Error validating server certificate for '%s':\n"), realm);
+
+ if (failures & SVN_AUTH_SSL_UNKNOWNCA)
+ {
+ svn_stringbuf_appendcstr
+ (buf,
+ _(" - The certificate is not issued by a trusted authority. Use the\n"
+ " fingerprint to validate the certificate manually!\n"));
+ }
+
+ if (failures & SVN_AUTH_SSL_CNMISMATCH)
+ {
+ svn_stringbuf_appendcstr
+ (buf, _(" - The certificate hostname does not match.\n"));
+ }
+
+ if (failures & SVN_AUTH_SSL_NOTYETVALID)
+ {
+ svn_stringbuf_appendcstr
+ (buf, _(" - The certificate is not yet valid.\n"));
+ }
+
+ if (failures & SVN_AUTH_SSL_EXPIRED)
+ {
+ svn_stringbuf_appendcstr
+ (buf, _(" - The certificate has expired.\n"));
+ }
+
+ if (failures & SVN_AUTH_SSL_OTHER)
+ {
+ svn_stringbuf_appendcstr
+ (buf, _(" - The certificate has an unknown error.\n"));
+ }
+
+ msg = svn_stringbuf_createf
+ (pool,
+ _("Certificate information:\n"
+ " - Hostname: %s\n"
+ " - Valid: from %s until %s\n"
+ " - Issuer: %s\n"
+ " - Fingerprint: %s\n"),
+ cert_info->hostname,
+ cert_info->valid_from,
+ cert_info->valid_until,
+ cert_info->issuer_dname,
+ cert_info->fingerprint);
+ svn_stringbuf_appendstr(buf, msg);
+
+ if (may_save)
+ {
+ svn_stringbuf_appendcstr
+ (buf, _("(R)eject, accept (t)emporarily or accept (p)ermanently? "));
+ }
+ else
+ {
+ svn_stringbuf_appendcstr(buf, _("(R)eject or accept (t)emporarily? "));
+ }
+ SVN_ERR(prompt(&choice, buf->data, FALSE, pb, pool));
+
+ if (choice[0] == 't' || choice[0] == 'T')
+ {
+ *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
+ (*cred_p)->may_save = FALSE;
+ (*cred_p)->accepted_failures = failures;
+ }
+ else if (may_save && (choice[0] == 'p' || choice[0] == 'P'))
+ {
+ *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
+ (*cred_p)->may_save = TRUE;
+ (*cred_p)->accepted_failures = failures;
+ }
+ else
+ {
+ *cred_p = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements 'svn_auth_ssl_client_cert_prompt_func_t'. */
+svn_error_t *
+svn_cmdline_auth_ssl_client_cert_prompt
+ (svn_auth_cred_ssl_client_cert_t **cred_p,
+ void *baton,
+ const char *realm,
+ svn_boolean_t may_save,
+ apr_pool_t *pool)
+{
+ svn_auth_cred_ssl_client_cert_t *cred = NULL;
+ const char *cert_file = NULL;
+ const char *abs_cert_file = NULL;
+ svn_cmdline_prompt_baton2_t *pb = baton;
+
+ SVN_ERR(maybe_print_realm(realm, pool));
+ SVN_ERR(prompt(&cert_file, _("Client certificate filename: "),
+ FALSE, pb, pool));
+ SVN_ERR(svn_dirent_get_absolute(&abs_cert_file, cert_file, pool));
+
+ cred = apr_palloc(pool, sizeof(*cred));
+ cred->cert_file = abs_cert_file;
+ cred->may_save = may_save;
+ *cred_p = cred;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements 'svn_auth_ssl_client_cert_pw_prompt_func_t'. */
+svn_error_t *
+svn_cmdline_auth_ssl_client_cert_pw_prompt
+ (svn_auth_cred_ssl_client_cert_pw_t **cred_p,
+ void *baton,
+ const char *realm,
+ svn_boolean_t may_save,
+ apr_pool_t *pool)
+{
+ svn_auth_cred_ssl_client_cert_pw_t *cred = NULL;
+ const char *result;
+ const char *text = apr_psprintf(pool, _("Passphrase for '%s': "), realm);
+ svn_cmdline_prompt_baton2_t *pb = baton;
+
+ SVN_ERR(prompt(&result, text, TRUE, pb, pool));
+
+ cred = apr_pcalloc(pool, sizeof(*cred));
+ cred->password = result;
+ cred->may_save = may_save;
+ *cred_p = cred;
+
+ return SVN_NO_ERROR;
+}
+
+/* This is a helper for plaintext prompt functions. */
+static svn_error_t *
+plaintext_prompt_helper(svn_boolean_t *may_save_plaintext,
+ const char *realmstring,
+ const char *prompt_string,
+ const char *prompt_text,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const char *answer = NULL;
+ svn_boolean_t answered = FALSE;
+ svn_cmdline_prompt_baton2_t *pb = baton;
+ const char *config_path = NULL;
+ terminal_handle_t *terminal;
+
+ if (pb)
+ SVN_ERR(svn_config_get_user_config_path(&config_path, pb->config_dir,
+ SVN_CONFIG_CATEGORY_SERVERS, pool));
+
+ SVN_ERR(terminal_open(&terminal, FALSE, pool));
+ SVN_ERR(terminal_puts(apr_psprintf(pool, prompt_text,
+ realmstring, config_path),
+ terminal, pool));
+ SVN_ERR(terminal_close(terminal));
+
+ do
+ {
+ svn_error_t *err = prompt(&answer, prompt_string, FALSE, pb, pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_CANCELLED)
+ {
+ svn_error_clear(err);
+ *may_save_plaintext = FALSE;
+ return SVN_NO_ERROR;
+ }
+ else
+ return err;
+ }
+ if (apr_strnatcasecmp(answer, _("yes")) == 0 ||
+ apr_strnatcasecmp(answer, _("y")) == 0)
+ {
+ *may_save_plaintext = TRUE;
+ answered = TRUE;
+ }
+ else if (apr_strnatcasecmp(answer, _("no")) == 0 ||
+ apr_strnatcasecmp(answer, _("n")) == 0)
+ {
+ *may_save_plaintext = FALSE;
+ answered = TRUE;
+ }
+ else
+ prompt_string = _("Please type 'yes' or 'no': ");
+ }
+ while (! answered);
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements 'svn_auth_plaintext_prompt_func_t'. */
+svn_error_t *
+svn_cmdline_auth_plaintext_prompt(svn_boolean_t *may_save_plaintext,
+ const char *realmstring,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const char *prompt_string = _("Store password unencrypted (yes/no)? ");
+ const char *prompt_text =
+ _("\n-----------------------------------------------------------------------"
+ "\nATTENTION! Your password for authentication realm:\n"
+ "\n"
+ " %s\n"
+ "\n"
+ "can only be stored to disk unencrypted! You are advised to configure\n"
+ "your system so that Subversion can store passwords encrypted, if\n"
+ "possible. See the documentation for details.\n"
+ "\n"
+ "You can avoid future appearances of this warning by setting the value\n"
+ "of the 'store-plaintext-passwords' option to either 'yes' or 'no' in\n"
+ "'%s'.\n"
+ "-----------------------------------------------------------------------\n"
+ );
+
+ return plaintext_prompt_helper(may_save_plaintext, realmstring,
+ prompt_string, prompt_text, baton,
+ pool);
+}
+
+/* This implements 'svn_auth_plaintext_passphrase_prompt_func_t'. */
+svn_error_t *
+svn_cmdline_auth_plaintext_passphrase_prompt(svn_boolean_t *may_save_plaintext,
+ const char *realmstring,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const char *prompt_string = _("Store passphrase unencrypted (yes/no)? ");
+ const char *prompt_text =
+ _("\n-----------------------------------------------------------------------\n"
+ "ATTENTION! Your passphrase for client certificate:\n"
+ "\n"
+ " %s\n"
+ "\n"
+ "can only be stored to disk unencrypted! You are advised to configure\n"
+ "your system so that Subversion can store passphrase encrypted, if\n"
+ "possible. See the documentation for details.\n"
+ "\n"
+ "You can avoid future appearances of this warning by setting the value\n"
+ "of the 'store-ssl-client-cert-pp-plaintext' option to either 'yes' or\n"
+ "'no' in '%s'.\n"
+ "-----------------------------------------------------------------------\n"
+ );
+
+ return plaintext_prompt_helper(may_save_plaintext, realmstring,
+ prompt_string, prompt_text, baton,
+ pool);
+}
+
+
+/** Generic prompting. **/
+
+svn_error_t *
+svn_cmdline_prompt_user2(const char **result,
+ const char *prompt_str,
+ svn_cmdline_prompt_baton_t *baton,
+ apr_pool_t *pool)
+{
+ /* XXX: We know prompt doesn't use the new members
+ * of svn_cmdline_prompt_baton2_t. */
+ return prompt(result, prompt_str, FALSE /* don't hide input */,
+ (svn_cmdline_prompt_baton2_t *)baton, pool);
+}
+
+/* This implements 'svn_auth_gnome_keyring_unlock_prompt_func_t'. */
+svn_error_t *
+svn_cmdline__auth_gnome_keyring_unlock_prompt(char **keyring_password,
+ const char *keyring_name,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const char *password;
+ const char *pass_prompt;
+ svn_cmdline_prompt_baton2_t *pb = baton;
+
+ pass_prompt = apr_psprintf(pool, _("Password for '%s' GNOME keyring: "),
+ keyring_name);
+ SVN_ERR(prompt(&password, pass_prompt, TRUE, pb, pool));
+ *keyring_password = apr_pstrdup(pool, password);
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/properties.c b/contrib/subversion/subversion/libsvn_subr/properties.c
new file mode 100644
index 0000000..738d00f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/properties.c
@@ -0,0 +1,507 @@
+/*
+ * properties.c: stuff related to Subversion properties
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <string.h> /* for strncmp() */
+#include "svn_hash.h"
+#include "svn_string.h"
+#include "svn_props.h"
+#include "svn_error.h"
+#include "svn_ctype.h"
+#include "private/svn_subr_private.h"
+
+
+/* All Subversion-specific versioned node properties
+ * known to this client, that are applicable to both a file and a dir.
+ */
+#define SVN_PROP__NODE_COMMON_PROPS SVN_PROP_MERGEINFO, \
+ SVN_PROP_TEXT_TIME, \
+ SVN_PROP_OWNER, \
+ SVN_PROP_GROUP, \
+ SVN_PROP_UNIX_MODE,
+
+/* All Subversion-specific versioned node properties
+ * known to this client, that are applicable to a dir only.
+ */
+#define SVN_PROP__NODE_DIR_ONLY_PROPS SVN_PROP_IGNORE, \
+ SVN_PROP_INHERITABLE_IGNORES, \
+ SVN_PROP_INHERITABLE_AUTO_PROPS, \
+ SVN_PROP_EXTERNALS,
+
+/* All Subversion-specific versioned node properties
+ * known to this client, that are applicable to a file only.
+ */
+#define SVN_PROP__NODE_FILE_ONLY_PROPS SVN_PROP_MIME_TYPE, \
+ SVN_PROP_EOL_STYLE, \
+ SVN_PROP_KEYWORDS, \
+ SVN_PROP_EXECUTABLE, \
+ SVN_PROP_NEEDS_LOCK, \
+ SVN_PROP_SPECIAL,
+
+static const char *const known_rev_props[]
+ = { SVN_PROP_REVISION_ALL_PROPS
+ NULL };
+
+static const char *const known_node_props[]
+ = { SVN_PROP__NODE_COMMON_PROPS
+ SVN_PROP__NODE_DIR_ONLY_PROPS
+ SVN_PROP__NODE_FILE_ONLY_PROPS
+ NULL };
+
+static const char *const known_dir_props[]
+ = { SVN_PROP__NODE_COMMON_PROPS
+ SVN_PROP__NODE_DIR_ONLY_PROPS
+ NULL };
+
+static const char *const known_file_props[]
+ = { SVN_PROP__NODE_COMMON_PROPS
+ SVN_PROP__NODE_FILE_ONLY_PROPS
+ NULL };
+
+static svn_boolean_t
+is_known_prop(const char *prop_name,
+ const char *const *known_props)
+{
+ while (*known_props)
+ {
+ if (strcmp(prop_name, *known_props++) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+svn_boolean_t
+svn_prop_is_known_svn_rev_prop(const char *prop_name)
+{
+ return is_known_prop(prop_name, known_rev_props);
+}
+
+svn_boolean_t
+svn_prop_is_known_svn_node_prop(const char *prop_name)
+{
+ return is_known_prop(prop_name, known_node_props);
+}
+
+svn_boolean_t
+svn_prop_is_known_svn_file_prop(const char *prop_name)
+{
+ return is_known_prop(prop_name, known_file_props);
+}
+
+svn_boolean_t
+svn_prop_is_known_svn_dir_prop(const char *prop_name)
+{
+ return is_known_prop(prop_name, known_dir_props);
+}
+
+
+svn_boolean_t
+svn_prop_is_svn_prop(const char *prop_name)
+{
+ return strncmp(prop_name, SVN_PROP_PREFIX, (sizeof(SVN_PROP_PREFIX) - 1))
+ == 0;
+}
+
+
+svn_boolean_t
+svn_prop_has_svn_prop(const apr_hash_t *props, apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ const void *prop_name;
+
+ if (! props)
+ return FALSE;
+
+ for (hi = apr_hash_first(pool, (apr_hash_t *)props); hi;
+ hi = apr_hash_next(hi))
+ {
+ apr_hash_this(hi, &prop_name, NULL, NULL);
+ if (svn_prop_is_svn_prop((const char *) prop_name))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+#define SIZEOF_WC_PREFIX (sizeof(SVN_PROP_WC_PREFIX) - 1)
+#define SIZEOF_ENTRY_PREFIX (sizeof(SVN_PROP_ENTRY_PREFIX) - 1)
+
+svn_prop_kind_t
+svn_property_kind2(const char *prop_name)
+{
+
+ if (strncmp(prop_name, SVN_PROP_WC_PREFIX, SIZEOF_WC_PREFIX) == 0)
+ return svn_prop_wc_kind;
+
+ if (strncmp(prop_name, SVN_PROP_ENTRY_PREFIX, SIZEOF_ENTRY_PREFIX) == 0)
+ return svn_prop_entry_kind;
+
+ return svn_prop_regular_kind;
+}
+
+
+/* NOTE: this function is deprecated, but we cannot move it to deprecated.c
+ because we need the SIZEOF_*_PREFIX constant symbols defined above. */
+svn_prop_kind_t
+svn_property_kind(int *prefix_len,
+ const char *prop_name)
+{
+ svn_prop_kind_t kind = svn_property_kind2(prop_name);
+
+ if (prefix_len)
+ {
+ if (kind == svn_prop_wc_kind)
+ *prefix_len = SIZEOF_WC_PREFIX;
+ else if (kind == svn_prop_entry_kind)
+ *prefix_len = SIZEOF_ENTRY_PREFIX;
+ else
+ *prefix_len = 0;
+ }
+
+ return kind;
+}
+
+
+svn_error_t *
+svn_categorize_props(const apr_array_header_t *proplist,
+ apr_array_header_t **entry_props,
+ apr_array_header_t **wc_props,
+ apr_array_header_t **regular_props,
+ apr_pool_t *pool)
+{
+ int i;
+ if (entry_props)
+ *entry_props = apr_array_make(pool, 1, sizeof(svn_prop_t));
+ if (wc_props)
+ *wc_props = apr_array_make(pool, 1, sizeof(svn_prop_t));
+ if (regular_props)
+ *regular_props = apr_array_make(pool, 1, sizeof(svn_prop_t));
+
+ for (i = 0; i < proplist->nelts; i++)
+ {
+ svn_prop_t *prop, *newprop;
+ enum svn_prop_kind kind;
+
+ prop = &APR_ARRAY_IDX(proplist, i, svn_prop_t);
+ kind = svn_property_kind2(prop->name);
+ newprop = NULL;
+
+ if (kind == svn_prop_regular_kind)
+ {
+ if (regular_props)
+ newprop = apr_array_push(*regular_props);
+ }
+ else if (kind == svn_prop_wc_kind)
+ {
+ if (wc_props)
+ newprop = apr_array_push(*wc_props);
+ }
+ else if (kind == svn_prop_entry_kind)
+ {
+ if (entry_props)
+ newprop = apr_array_push(*entry_props);
+ }
+ else
+ /* Technically this can't happen, but might as well have the
+ code ready in case that ever changes. */
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ "Bad property kind for property '%s'",
+ prop->name);
+
+ if (newprop)
+ {
+ newprop->name = prop->name;
+ newprop->value = prop->value;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_prop_diffs(apr_array_header_t **propdiffs,
+ const apr_hash_t *target_props,
+ const apr_hash_t *source_props,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_array_header_t *ary = apr_array_make(pool, 1, sizeof(svn_prop_t));
+
+ /* Note: we will be storing the pointers to the keys (from the hashes)
+ into the propdiffs array. It is acceptable for us to
+ reference the same memory as the base/target_props hash. */
+
+ /* Loop over SOURCE_PROPS and examine each key. This will allow us to
+ detect any `deletion' events or `set-modification' events. */
+ for (hi = apr_hash_first(pool, (apr_hash_t *)source_props); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+ const svn_string_t *propval1, *propval2;
+
+ /* Get next property */
+ apr_hash_this(hi, &key, &klen, &val);
+ propval1 = val;
+
+ /* Does property name exist in TARGET_PROPS? */
+ propval2 = apr_hash_get((apr_hash_t *)target_props, key, klen);
+
+ if (propval2 == NULL)
+ {
+ /* Add a delete event to the array */
+ svn_prop_t *p = apr_array_push(ary);
+ p->name = key;
+ p->value = NULL;
+ }
+ else if (! svn_string_compare(propval1, propval2))
+ {
+ /* Add a set (modification) event to the array */
+ svn_prop_t *p = apr_array_push(ary);
+ p->name = key;
+ p->value = svn_string_dup(propval2, pool);
+ }
+ }
+
+ /* Loop over TARGET_PROPS and examine each key. This allows us to
+ detect `set-creation' events */
+ for (hi = apr_hash_first(pool, (apr_hash_t *)target_props); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+ const svn_string_t *propval;
+
+ /* Get next property */
+ apr_hash_this(hi, &key, &klen, &val);
+ propval = val;
+
+ /* Does property name exist in SOURCE_PROPS? */
+ if (NULL == apr_hash_get((apr_hash_t *)source_props, key, klen))
+ {
+ /* Add a set (creation) event to the array */
+ svn_prop_t *p = apr_array_push(ary);
+ p->name = key;
+ p->value = svn_string_dup(propval, pool);
+ }
+ }
+
+ /* Done building our array of user events. */
+ *propdiffs = ary;
+
+ return SVN_NO_ERROR;
+}
+
+apr_hash_t *
+svn_prop__patch(const apr_hash_t *original_props,
+ const apr_array_header_t *prop_changes,
+ apr_pool_t *pool)
+{
+ apr_hash_t *props = apr_hash_copy(pool, original_props);
+ int i;
+
+ for (i = 0; i < prop_changes->nelts; i++)
+ {
+ const svn_prop_t *p = &APR_ARRAY_IDX(prop_changes, i, svn_prop_t);
+
+ svn_hash_sets(props, p->name, p->value);
+ }
+ return props;
+}
+
+/**
+ * Reallocate the members of PROP using POOL.
+ */
+static void
+svn_prop__members_dup(svn_prop_t *prop, apr_pool_t *pool)
+{
+ if (prop->name)
+ prop->name = apr_pstrdup(pool, prop->name);
+ if (prop->value)
+ prop->value = svn_string_dup(prop->value, pool);
+}
+
+svn_prop_t *
+svn_prop_dup(const svn_prop_t *prop, apr_pool_t *pool)
+{
+ svn_prop_t *new_prop = apr_palloc(pool, sizeof(*new_prop));
+
+ *new_prop = *prop;
+
+ svn_prop__members_dup(new_prop, pool);
+
+ return new_prop;
+}
+
+apr_array_header_t *
+svn_prop_array_dup(const apr_array_header_t *array, apr_pool_t *pool)
+{
+ int i;
+ apr_array_header_t *new_array = apr_array_copy(pool, array);
+ for (i = 0; i < new_array->nelts; ++i)
+ {
+ svn_prop_t *elt = &APR_ARRAY_IDX(new_array, i, svn_prop_t);
+ svn_prop__members_dup(elt, pool);
+ }
+ return new_array;
+}
+
+apr_array_header_t *
+svn_prop_hash_to_array(const apr_hash_t *hash,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_array_header_t *array = apr_array_make(pool,
+ apr_hash_count((apr_hash_t *)hash),
+ sizeof(svn_prop_t));
+
+ for (hi = apr_hash_first(pool, (apr_hash_t *)hash); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ svn_prop_t prop;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ prop.name = key;
+ prop.value = val;
+ APR_ARRAY_PUSH(array, svn_prop_t) = prop;
+ }
+
+ return array;
+}
+
+apr_hash_t *
+svn_prop_hash_dup(const apr_hash_t *hash,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_hash_t *new_hash = apr_hash_make(pool);
+
+ for (hi = apr_hash_first(pool, (apr_hash_t *)hash); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *prop;
+
+ apr_hash_this(hi, &key, &klen, &prop);
+ apr_hash_set(new_hash, apr_pstrmemdup(pool, key, klen), klen,
+ svn_string_dup(prop, pool));
+ }
+ return new_hash;
+}
+
+apr_hash_t *
+svn_prop_array_to_hash(const apr_array_header_t *properties,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_hash_t *prop_hash = apr_hash_make(pool);
+
+ for (i = 0; i < properties->nelts; i++)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(properties, i, svn_prop_t);
+ svn_hash_sets(prop_hash, prop->name, prop->value);
+ }
+
+ return prop_hash;
+}
+
+svn_boolean_t
+svn_prop_is_boolean(const char *prop_name)
+{
+ /* If we end up with more than 3 of these, we should probably put
+ them in a table and use bsearch. With only three, it doesn't
+ make any speed difference. */
+ if (strcmp(prop_name, SVN_PROP_EXECUTABLE) == 0
+ || strcmp(prop_name, SVN_PROP_NEEDS_LOCK) == 0
+ || strcmp(prop_name, SVN_PROP_SPECIAL) == 0)
+ return TRUE;
+ return FALSE;
+}
+
+
+svn_boolean_t
+svn_prop_needs_translation(const char *propname)
+{
+ /* ### Someday, we may want to be picky and choosy about which
+ properties require UTF8 and EOL conversion. For now, all "svn:"
+ props need it. */
+
+ return svn_prop_is_svn_prop(propname);
+}
+
+
+svn_boolean_t
+svn_prop_name_is_valid(const char *prop_name)
+{
+ const char *p = prop_name;
+
+ /* The characters we allow use identical representations in UTF8
+ and ASCII, so we can just test for the appropriate ASCII codes.
+ But we can't use standard C character notation ('A', 'B', etc)
+ because there's no guarantee that this C environment is using
+ ASCII. */
+
+ if (!(svn_ctype_isalpha(*p)
+ || *p == SVN_CTYPE_ASCII_COLON
+ || *p == SVN_CTYPE_ASCII_UNDERSCORE))
+ return FALSE;
+ p++;
+ for (; *p; p++)
+ {
+ if (!(svn_ctype_isalnum(*p)
+ || *p == SVN_CTYPE_ASCII_MINUS
+ || *p == SVN_CTYPE_ASCII_DOT
+ || *p == SVN_CTYPE_ASCII_COLON
+ || *p == SVN_CTYPE_ASCII_UNDERSCORE))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+const char *
+svn_prop_get_value(const apr_hash_t *props,
+ const char *prop_name)
+{
+ svn_string_t *str;
+
+ if (!props)
+ return NULL;
+
+ str = svn_hash_gets((apr_hash_t *)props, prop_name);
+
+ if (str)
+ return str->data;
+
+ return NULL;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/pseudo_md5.c b/contrib/subversion/subversion/libsvn_subr/pseudo_md5.c
new file mode 100644
index 0000000..8c194f7
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/pseudo_md5.c
@@ -0,0 +1,422 @@
+/*
+ * This is work is derived from material Copyright RSA Data Security, Inc.
+ *
+ * The RSA copyright statement and Licence for that original material is
+ * included below. This is followed by the Apache copyright statement and
+ * licence for the modifications made to that material.
+ */
+
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ rights reserved.
+
+ License to copy and use this software is granted provided that it
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ Algorithm" in all material mentioning or referencing this software
+ or this function.
+
+ License is also granted to make and use derivative works provided
+ that such works are identified as "derived from the RSA Data
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
+ mentioning or referencing the derived work.
+
+ RSA Data Security, Inc. makes no representations concerning either
+ the merchantability of this software or the suitability of this
+ software for any particular purpose. It is provided "as is"
+ without express or implied warranty of any kind.
+
+ These notices must be retained in any copies of any part of this
+ documentation and/or software.
+ */
+
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
+ * MD5 crypt() function, which is licenced as follows:
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ */
+
+/*
+ * pseudo_md5.c: md5-esque hash sum calculation for short data blocks.
+ * Code taken and adapted from the APR (see licenses above).
+ */
+#include "private/svn_pseudo_md5.h"
+
+/* Constants for MD5 calculation.
+ */
+
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#if defined(_MSC_VER) && _MSC_VER >= 1310
+#pragma intrinsic(_rotl)
+#define ROTATE_LEFT(x, n) (_rotl(x,n))
+#else
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+#endif
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+ * Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* The idea of the functions below is as follows:
+ *
+ * - The core MD5 algorithm does not assume that the "important" data
+ * is at the begin of the encryption block, followed by e.g. 0.
+ * Instead, all bits are equally relevant.
+ *
+ * - If some bytes in the input are known to be 0, we may hard-code them.
+ * With the previous property, it is safe to move them to the upper end
+ * of the encryption block to maximize the number of steps that can be
+ * pre-calculated.
+ *
+ * - Variable-length streams will use the upper 8 byte of the last
+ * encryption block to store the stream length in bits (to make 0, 00,
+ * 000, ... etc. produce different hash sums).
+ *
+ * - We will hash at most 63 bytes, i.e. 504 bits. In the standard stream
+ * implementation, the upper 6 bytes of the last encryption block would
+ * be 0. We will put at least one non-NULL value in the last 4 bytes.
+ * Therefore, our input will always be different to a standard MD5 stream
+ * implementation in either block count, content or both.
+ *
+ * - Our length indicator also varies with the number bytes in the input.
+ * Hence, different pseudo-MD5 input length produces different output
+ * (with "cryptographic probability") even if the content is all 0 or
+ * otherwise identical.
+ *
+ * - Collisions between pseudo-MD5 and pseudo-MD5 as well as pseudo-MD5
+ * and standard MD5 are as likely as any other MD5 collision.
+ */
+
+void svn__pseudo_md5_15(apr_uint32_t digest[4],
+ const apr_uint32_t x[4])
+{
+ apr_uint32_t a = 0x67452301;
+ apr_uint32_t b = 0xefcdab89;
+ apr_uint32_t c = 0x98badcfe;
+ apr_uint32_t d = 0x10325476;
+
+ /* make sure byte 63 gets the marker independently of BE / LE */
+ apr_uint32_t x3n = x[3] ^ 0xffffffff;
+
+ /* Round 1 */
+ FF(a, b, c, d, 0, S11, 0xd76aa478); /* 1 */
+ FF(d, a, b, c, 0, S12, 0xe8c7b756); /* 2 */
+ FF(c, d, a, b, 0, S13, 0x242070db); /* 3 */
+ FF(b, c, d, a, 0, S14, 0xc1bdceee); /* 4 */
+ FF(a, b, c, d, 0, S11, 0xf57c0faf); /* 5 */
+ FF(d, a, b, c, 0, S12, 0x4787c62a); /* 6 */
+ FF(c, d, a, b, 0, S13, 0xa8304613); /* 7 */
+ FF(b, c, d, a, 0, S14, 0xfd469501); /* 8 */
+ FF(a, b, c, d, 0, S11, 0x698098d8); /* 9 */
+ FF(d, a, b, c, 0, S12, 0x8b44f7af); /* 10 */
+ FF(c, d, a, b, 0, S13, 0xffff5bb1); /* 11 */
+ FF(b, c, d, a, 0, S14, 0x895cd7be); /* 12 */
+ FF(a, b, c, d, x[0], S11, 0x6b901122); /* 13 */
+ FF(d, a, b, c, x[1], S12, 0xfd987193); /* 14 */
+ FF(c, d, a, b, x[2], S13, 0xa679438e); /* 15 */
+ FF(b, c, d, a, x3n, S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG(a, b, c, d, 0, S21, 0xf61e2562); /* 17 */
+ GG(d, a, b, c, 0, S22, 0xc040b340); /* 18 */
+ GG(c, d, a, b, 0, S23, 0x265e5a51); /* 19 */
+ GG(b, c, d, a, 0, S24, 0xe9b6c7aa); /* 20 */
+ GG(a, b, c, d, 0, S21, 0xd62f105d); /* 21 */
+ GG(d, a, b, c, 0, S22, 0x2441453); /* 22 */
+ GG(c, d, a, b, x3n, S23, 0xd8a1e681); /* 23 */
+ GG(b, c, d, a, 0, S24, 0xe7d3fbc8); /* 24 */
+ GG(a, b, c, d, 0, S21, 0x21e1cde6); /* 25 */
+ GG(d, a, b, c, x[2], S22, 0xc33707d6); /* 26 */
+ GG(c, d, a, b, 0, S23, 0xf4d50d87); /* 27 */
+ GG(b, c, d, a, 0, S24, 0x455a14ed); /* 28 */
+ GG(a, b, c, d, x[1], S21, 0xa9e3e905); /* 29 */
+ GG(d, a, b, c, 0, S22, 0xfcefa3f8); /* 30 */
+ GG(c, d, a, b, 0, S23, 0x676f02d9); /* 31 */
+ GG(b, c, d, a, x[0], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH(a, b, c, d, 0, S31, 0xfffa3942); /* 33 */
+ HH(d, a, b, c, 0, S32, 0x8771f681); /* 34 */
+ HH(c, d, a, b, 0, S33, 0x6d9d6122); /* 35 */
+ HH(b, c, d, a, x[2], S34, 0xfde5380c); /* 36 */
+ HH(a, b, c, d, 0, S31, 0xa4beea44); /* 37 */
+ HH(d, a, b, c, 0, S32, 0x4bdecfa9); /* 38 */
+ HH(c, d, a, b, 0, S33, 0xf6bb4b60); /* 39 */
+ HH(b, c, d, a, 0, S34, 0xbebfbc70); /* 40 */
+ HH(a, b, c, d, x[1], S31, 0x289b7ec6); /* 41 */
+ HH(d, a, b, c, 0, S32, 0xeaa127fa); /* 42 */
+ HH(c, d, a, b, 0, S33, 0xd4ef3085); /* 43 */
+ HH(b, c, d, a, 0, S34, 0x4881d05); /* 44 */
+ HH(a, b, c, d, 0, S31, 0xd9d4d039); /* 45 */
+ HH(d, a, b, c, x[0], S32, 0xe6db99e5); /* 46 */
+ HH(c, d, a, b, x3n, S33, 0x1fa27cf8); /* 47 */
+ HH(b, c, d, a, 0, S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II(a, b, c, d, 0, S41, 0xf4292244); /* 49 */
+ II(d, a, b, c, 0, S42, 0x432aff97); /* 50 */
+ II(c, d, a, b, x[2], S43, 0xab9423a7); /* 51 */
+ II(b, c, d, a, 0, S44, 0xfc93a039); /* 52 */
+ II(a, b, c, d, x[0], S41, 0x655b59c3); /* 53 */
+ II(d, a, b, c, 0, S42, 0x8f0ccc92); /* 54 */
+ II(c, d, a, b, 0, S43, 0xffeff47d); /* 55 */
+ II(b, c, d, a, 0, S44, 0x85845dd1); /* 56 */
+ II(a, b, c, d, 0, S41, 0x6fa87e4f); /* 57 */
+ II(d, a, b, c, x3n, S42, 0xfe2ce6e0); /* 58 */
+ II(c, d, a, b, 0, S43, 0xa3014314); /* 59 */
+ II(b, c, d, a, x[1], S44, 0x4e0811a1); /* 60 */
+ II(a, b, c, d, 0, S41, 0xf7537e82); /* 61 */
+ II(d, a, b, c, 0, S42, 0xbd3af235); /* 62 */
+ II(c, d, a, b, 0, S43, 0x2ad7d2bb); /* 63 */
+ II(b, c, d, a, 0, S44, 0xeb86d391); /* 64 */
+
+ digest[0] = a;
+ digest[1] = b;
+ digest[2] = c;
+ digest[3] = d;
+}
+
+void svn__pseudo_md5_31(apr_uint32_t digest[4],
+ const apr_uint32_t x[8])
+{
+ apr_uint32_t a = 0x67452301;
+ apr_uint32_t b = 0xefcdab89;
+ apr_uint32_t c = 0x98badcfe;
+ apr_uint32_t d = 0x10325476;
+
+ /* make sure byte 63 gets the marker independently of BE / LE */
+ apr_uint32_t x7n = x[7] ^ 0xfefefefe;
+
+ /* Round 1 */
+ FF(a, b, c, d, 0, S11, 0xd76aa478); /* 1 */
+ FF(d, a, b, c, 0, S12, 0xe8c7b756); /* 2 */
+ FF(c, d, a, b, 0, S13, 0x242070db); /* 3 */
+ FF(b, c, d, a, 0, S14, 0xc1bdceee); /* 4 */
+ FF(a, b, c, d, 0, S11, 0xf57c0faf); /* 5 */
+ FF(d, a, b, c, 0, S12, 0x4787c62a); /* 6 */
+ FF(c, d, a, b, 0, S13, 0xa8304613); /* 7 */
+ FF(b, c, d, a, 0, S14, 0xfd469501); /* 8 */
+ FF(a, b, c, d, x[0], S11, 0x698098d8); /* 9 */
+ FF(d, a, b, c, x[1], S12, 0x8b44f7af); /* 10 */
+ FF(c, d, a, b, x[2], S13, 0xffff5bb1); /* 11 */
+ FF(b, c, d, a, x[3], S14, 0x895cd7be); /* 12 */
+ FF(a, b, c, d, x[4], S11, 0x6b901122); /* 13 */
+ FF(d, a, b, c, x[5], S12, 0xfd987193); /* 14 */
+ FF(c, d, a, b, x[6], S13, 0xa679438e); /* 15 */
+ FF(b, c, d, a, x7n, S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG(a, b, c, d, 0, S21, 0xf61e2562); /* 17 */
+ GG(d, a, b, c, 0, S22, 0xc040b340); /* 18 */
+ GG(c, d, a, b, x[3], S23, 0x265e5a51); /* 19 */
+ GG(b, c, d, a, 0, S24, 0xe9b6c7aa); /* 20 */
+ GG(a, b, c, d, 0, S21, 0xd62f105d); /* 21 */
+ GG(d, a, b, c, x[2], S22, 0x2441453); /* 22 */
+ GG(c, d, a, b, x7n, S23, 0xd8a1e681); /* 23 */
+ GG(b, c, d, a, 0, S24, 0xe7d3fbc8); /* 24 */
+ GG(a, b, c, d, x[1], S21, 0x21e1cde6); /* 25 */
+ GG(d, a, b, c, x[6], S22, 0xc33707d6); /* 26 */
+ GG(c, d, a, b, 0, S23, 0xf4d50d87); /* 27 */
+ GG(b, c, d, a, x[0], S24, 0x455a14ed); /* 28 */
+ GG(a, b, c, d, x[5], S21, 0xa9e3e905); /* 29 */
+ GG(d, a, b, c, 0, S22, 0xfcefa3f8); /* 30 */
+ GG(c, d, a, b, 0, S23, 0x676f02d9); /* 31 */
+ GG(b, c, d, a, x[4], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH(a, b, c, d, 0, S31, 0xfffa3942); /* 33 */
+ HH(d, a, b, c, x[0], S32, 0x8771f681); /* 34 */
+ HH(c, d, a, b, x[3], S33, 0x6d9d6122); /* 35 */
+ HH(b, c, d, a, x[6], S34, 0xfde5380c); /* 36 */
+ HH(a, b, c, d, 0, S31, 0xa4beea44); /* 37 */
+ HH(d, a, b, c, 0, S32, 0x4bdecfa9); /* 38 */
+ HH(c, d, a, b, 0, S33, 0xf6bb4b60); /* 39 */
+ HH(b, c, d, a, x[2], S34, 0xbebfbc70); /* 40 */
+ HH(a, b, c, d, x[5], S31, 0x289b7ec6); /* 41 */
+ HH(d, a, b, c, 0, S32, 0xeaa127fa); /* 42 */
+ HH(c, d, a, b, 0, S33, 0xd4ef3085); /* 43 */
+ HH(b, c, d, a, 0, S34, 0x4881d05); /* 44 */
+ HH(a, b, c, d, x[1], S31, 0xd9d4d039); /* 45 */
+ HH(d, a, b, c, x[4], S32, 0xe6db99e5); /* 46 */
+ HH(c, d, a, b, x7n, S33, 0x1fa27cf8); /* 47 */
+ HH(b, c, d, a, 0, S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II(a, b, c, d, 0, S41, 0xf4292244); /* 49 */
+ II(d, a, b, c, 0, S42, 0x432aff97); /* 50 */
+ II(c, d, a, b, x[6], S43, 0xab9423a7); /* 51 */
+ II(b, c, d, a, 0, S44, 0xfc93a039); /* 52 */
+ II(a, b, c, d, x[4], S41, 0x655b59c3); /* 53 */
+ II(d, a, b, c, 0, S42, 0x8f0ccc92); /* 54 */
+ II(c, d, a, b, x[2], S43, 0xffeff47d); /* 55 */
+ II(b, c, d, a, 0, S44, 0x85845dd1); /* 56 */
+ II(a, b, c, d, x[0], S41, 0x6fa87e4f); /* 57 */
+ II(d, a, b, c, x7n, S42, 0xfe2ce6e0); /* 58 */
+ II(c, d, a, b, 0, S43, 0xa3014314); /* 59 */
+ II(b, c, d, a, x[5], S44, 0x4e0811a1); /* 60 */
+ II(a, b, c, d, 0, S41, 0xf7537e82); /* 61 */
+ II(d, a, b, c, x[3], S42, 0xbd3af235); /* 62 */
+ II(c, d, a, b, 0, S43, 0x2ad7d2bb); /* 63 */
+ II(b, c, d, a, x[1], S44, 0xeb86d391); /* 64 */
+
+ digest[0] = a;
+ digest[1] = b;
+ digest[2] = c;
+ digest[3] = d;
+}
+
+void svn__pseudo_md5_63(apr_uint32_t digest[4],
+ const apr_uint32_t x[16])
+{
+ apr_uint32_t a = 0x67452301;
+ apr_uint32_t b = 0xefcdab89;
+ apr_uint32_t c = 0x98badcfe;
+ apr_uint32_t d = 0x10325476;
+
+ /* make sure byte 63 gets the marker independently of BE / LE */
+ apr_uint32_t x15n = x[15] ^ 0xfcfcfcfc;
+
+ /* Round 1 */
+ FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */
+ FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */
+ FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */
+ FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */
+ FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */
+ FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */
+ FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */
+ FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */
+ FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */
+ FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */
+ FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF(b, c, d, a, x15n, S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */
+ GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */
+ GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */
+ GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */
+ GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG(c, d, a, b, x15n, S23, 0xd8a1e681); /* 23 */
+ GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */
+ GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */
+ GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */
+ GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */
+ GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */
+ GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */
+ GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */
+ HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */
+ HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */
+ HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */
+ HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */
+ HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */
+ HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */
+ HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */
+ HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */
+ HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH(c, d, a, b, x15n, S33, 0x1fa27cf8); /* 47 */
+ HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */
+ II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */
+ II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */
+ II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */
+ II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */
+ II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */
+ II(d, a, b, c, x15n, S42, 0xfe2ce6e0); /* 58 */
+ II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */
+ II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */
+ II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */
+ II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */
+
+ digest[0] = a;
+ digest[1] = b;
+ digest[2] = c;
+ digest[3] = d;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/quoprint.c b/contrib/subversion/subversion/libsvn_subr/quoprint.c
new file mode 100644
index 0000000..bb9869d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/quoprint.c
@@ -0,0 +1,309 @@
+/*
+ * quoprint.c: quoted-printable encoding and decoding functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_general.h> /* for APR_INLINE */
+
+#include "svn_pools.h"
+#include "svn_io.h"
+#include "svn_error.h"
+#include "svn_quoprint.h"
+
+
+/* Caveats:
+
+ (1) This code is for the encoding and decoding of binary data
+ only. Thus, CRLF sequences are encoded as =0D=0A, and we
+ don't have to worry about tabs and spaces coming before
+ hard newlines, since there aren't any.
+
+ (2) The decoder does no error reporting, and instead throws
+ away invalid sequences. It also discards CRLF sequences,
+ since those can only appear in the encoding of text data.
+
+ (3) The decoder does not strip whitespace at the end of a
+ line, so it is not actually compliant with RFC 2045.
+ (Such whitespace should never occur, even in the encoding
+ of text data, but RFC 2045 requires a decoder to detect
+ that a transport agent has added trailing whitespace).
+
+ (4) The encoder is tailored to make output embeddable in XML,
+ which means it quotes <>'"& as well as the characters
+ required by RFC 2045. */
+
+#define QUOPRINT_LINELEN 76
+#define VALID_LITERAL(c) ((c) == '\t' || ((c) >= ' ' && (c) <= '~' \
+ && (c) != '='))
+#define ENCODE_AS_LITERAL(c) (VALID_LITERAL(c) && (c) != '\t' && (c) != '<' \
+ && (c) != '>' && (c) != '\'' && (c) != '"' \
+ && (c) != '&')
+static const char hextab[] = "0123456789ABCDEF";
+
+
+
+/* Binary input --> quoted-printable-encoded output */
+
+struct encode_baton {
+ svn_stream_t *output;
+ int linelen; /* Bytes output so far on this line */
+ apr_pool_t *pool;
+};
+
+
+/* Quoted-printable-encode a byte string which may or may not be the
+ totality of the data being encoded. *LINELEN carries the length of
+ the current output line; initialize it to 0. Output will be
+ appended to STR. */
+static void
+encode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len,
+ int *linelen)
+{
+ char buf[3];
+ const char *p;
+
+ /* Keep encoding three-byte groups until we run out. */
+ for (p = data; p < data + len; p++)
+ {
+ /* Encode this character. */
+ if (ENCODE_AS_LITERAL(*p))
+ {
+ svn_stringbuf_appendbyte(str, *p);
+ (*linelen)++;
+ }
+ else
+ {
+ buf[0] = '=';
+ buf[1] = hextab[(*p >> 4) & 0xf];
+ buf[2] = hextab[*p & 0xf];
+ svn_stringbuf_appendbytes(str, buf, 3);
+ *linelen += 3;
+ }
+
+ /* Make sure our output lines don't exceed QUOPRINT_LINELEN. */
+ if (*linelen + 3 > QUOPRINT_LINELEN)
+ {
+ svn_stringbuf_appendcstr(str, "=\n");
+ *linelen = 0;
+ }
+ }
+}
+
+
+/* Write handler for svn_quoprint_encode. */
+static svn_error_t *
+encode_data(void *baton, const char *data, apr_size_t *len)
+{
+ struct encode_baton *eb = baton;
+ apr_pool_t *subpool = svn_pool_create(eb->pool);
+ svn_stringbuf_t *encoded = svn_stringbuf_create_empty(subpool);
+ apr_size_t enclen;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ /* Encode this block of data and write it out. */
+ encode_bytes(encoded, data, *len, &eb->linelen);
+ enclen = encoded->len;
+ if (enclen != 0)
+ err = svn_stream_write(eb->output, encoded->data, &enclen);
+ svn_pool_destroy(subpool);
+ return err;
+}
+
+
+/* Close handler for svn_quoprint_encode(). */
+static svn_error_t *
+finish_encoding_data(void *baton)
+{
+ struct encode_baton *eb = baton;
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_size_t len;
+
+ /* Terminate the current output line if it's not empty. */
+ if (eb->linelen > 0)
+ {
+ len = 2;
+ err = svn_stream_write(eb->output, "=\n", &len);
+ }
+
+ /* Pass on the close request and clean up the baton. */
+ if (err == SVN_NO_ERROR)
+ err = svn_stream_close(eb->output);
+ svn_pool_destroy(eb->pool);
+ return err;
+}
+
+
+svn_stream_t *
+svn_quoprint_encode(svn_stream_t *output, apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ struct encode_baton *eb = apr_palloc(subpool, sizeof(*eb));
+ svn_stream_t *stream;
+
+ eb->output = output;
+ eb->linelen = 0;
+ eb->pool = subpool;
+ stream = svn_stream_create(eb, pool);
+ svn_stream_set_write(stream, encode_data);
+ svn_stream_set_close(stream, finish_encoding_data);
+ return stream;
+}
+
+
+svn_stringbuf_t *
+svn_quoprint_encode_string(const svn_stringbuf_t *str, apr_pool_t *pool)
+{
+ svn_stringbuf_t *encoded = svn_stringbuf_create_empty(pool);
+ int linelen = 0;
+
+ encode_bytes(encoded, str->data, str->len, &linelen);
+ if (linelen > 0)
+ svn_stringbuf_appendcstr(encoded, "=\n");
+ return encoded;
+}
+
+
+
+/* Quoted-printable-encoded input --> binary output */
+
+struct decode_baton {
+ svn_stream_t *output;
+ char buf[3]; /* Bytes waiting to be decoded */
+ int buflen; /* Number of bytes waiting */
+ apr_pool_t *pool;
+};
+
+
+/* Decode a byte string which may or may not be the total amount of
+ data being decoded. INBUF and *INBUFLEN carry the leftover bytes
+ from call to call. Have room for four bytes in INBUF and
+ initialize *INBUFLEN to 0 and *DONE to FALSE. Output will be
+ appended to STR. */
+static void
+decode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len,
+ char *inbuf, int *inbuflen)
+{
+ const char *p, *find1, *find2;
+ char c;
+
+ for (p = data; p <= data + len; p++)
+ {
+ /* Append this byte to the buffer and see what we have. */
+ inbuf[(*inbuflen)++] = *p;
+ if (*inbuf != '=')
+ {
+ /* Literal character; append it if it's valid as such. */
+ if (VALID_LITERAL(*inbuf))
+ svn_stringbuf_appendbyte(str, *inbuf);
+ *inbuflen = 0;
+ }
+ else if (*inbuf == '=' && *inbuflen == 2 && inbuf[1] == '\n')
+ {
+ /* Soft newline; ignore. */
+ *inbuflen = 0;
+ }
+ else if (*inbuf == '=' && *inbuflen == 3)
+ {
+ /* Encoded character; decode it and append. */
+ find1 = strchr(hextab, inbuf[1]);
+ find2 = strchr(hextab, inbuf[2]);
+ if (find1 != NULL && find2 != NULL)
+ {
+ c = (char)(((find1 - hextab) << 4) | (find2 - hextab));
+ svn_stringbuf_appendbyte(str, c);
+ }
+ *inbuflen = 0;
+ }
+ }
+}
+
+
+/* Write handler for svn_quoprint_decode. */
+static svn_error_t *
+decode_data(void *baton, const char *data, apr_size_t *len)
+{
+ struct decode_baton *db = baton;
+ apr_pool_t *subpool;
+ svn_stringbuf_t *decoded;
+ apr_size_t declen;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ /* Decode this block of data. */
+ subpool = svn_pool_create(db->pool);
+ decoded = svn_stringbuf_create_empty(subpool);
+ decode_bytes(decoded, data, *len, db->buf, &db->buflen);
+
+ /* Write the output, clean up, go home. */
+ declen = decoded->len;
+ if (declen != 0)
+ err = svn_stream_write(db->output, decoded->data, &declen);
+ svn_pool_destroy(subpool);
+ return err;
+}
+
+
+/* Close handler for svn_quoprint_decode(). */
+static svn_error_t *
+finish_decoding_data(void *baton)
+{
+ struct decode_baton *db = baton;
+ svn_error_t *err;
+
+ /* Pass on the close request and clean up the baton. */
+ err = svn_stream_close(db->output);
+ svn_pool_destroy(db->pool);
+ return err;
+}
+
+
+svn_stream_t *
+svn_quoprint_decode(svn_stream_t *output, apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ struct decode_baton *db = apr_palloc(subpool, sizeof(*db));
+ svn_stream_t *stream;
+
+ db->output = output;
+ db->buflen = 0;
+ db->pool = subpool;
+ stream = svn_stream_create(db, pool);
+ svn_stream_set_write(stream, decode_data);
+ svn_stream_set_close(stream, finish_decoding_data);
+ return stream;
+}
+
+
+svn_stringbuf_t *
+svn_quoprint_decode_string(const svn_stringbuf_t *str, apr_pool_t *pool)
+{
+ svn_stringbuf_t *decoded = svn_stringbuf_create_empty(pool);
+ char ingroup[4];
+ int ingrouplen = 0;
+
+ decode_bytes(decoded, str->data, str->len, ingroup, &ingrouplen);
+ return decoded;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/sha1.c b/contrib/subversion/subversion/libsvn_subr/sha1.c
new file mode 100644
index 0000000..45470cb
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/sha1.c
@@ -0,0 +1,82 @@
+/*
+ * sha1.c: SHA1 checksum routines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_sha1.h>
+
+#include "sha1.h"
+
+
+
+/* The SHA1 digest for the empty string. */
+static const unsigned char svn_sha1__empty_string_digest_array[] = {
+ 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55,
+ 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09
+};
+
+const unsigned char *
+svn_sha1__empty_string_digest(void)
+{
+ return svn_sha1__empty_string_digest_array;
+}
+
+
+const char *
+svn_sha1__digest_to_cstring_display(const unsigned char digest[],
+ apr_pool_t *pool)
+{
+ static const char *hex = "0123456789abcdef";
+ char *str = apr_palloc(pool, (APR_SHA1_DIGESTSIZE * 2) + 1);
+ int i;
+
+ for (i = 0; i < APR_SHA1_DIGESTSIZE; i++)
+ {
+ str[i*2] = hex[digest[i] >> 4];
+ str[i*2+1] = hex[digest[i] & 0x0f];
+ }
+ str[i*2] = '\0';
+
+ return str;
+}
+
+
+const char *
+svn_sha1__digest_to_cstring(const unsigned char digest[], apr_pool_t *pool)
+{
+ static const unsigned char zeros_digest[APR_SHA1_DIGESTSIZE] = { 0 };
+
+ if (memcmp(digest, zeros_digest, APR_SHA1_DIGESTSIZE) != 0)
+ return svn_sha1__digest_to_cstring_display(digest, pool);
+ else
+ return NULL;
+}
+
+
+svn_boolean_t
+svn_sha1__digests_match(const unsigned char d1[], const unsigned char d2[])
+{
+ static const unsigned char zeros[APR_SHA1_DIGESTSIZE] = { 0 };
+
+ return ((memcmp(d1, zeros, APR_SHA1_DIGESTSIZE) == 0)
+ || (memcmp(d2, zeros, APR_SHA1_DIGESTSIZE) == 0)
+ || (memcmp(d1, d2, APR_SHA1_DIGESTSIZE) == 0));
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/sha1.h b/contrib/subversion/subversion/libsvn_subr/sha1.h
new file mode 100644
index 0000000..976810b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/sha1.h
@@ -0,0 +1,70 @@
+/*
+ * sha1.h: Converting and comparing SHA1 checksums
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_SUBR_SHA1_H
+#define SVN_LIBSVN_SUBR_SHA1_H
+
+#include <apr_pools.h>
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* The SHA1 digest for the empty string. */
+const unsigned char *
+svn_sha1__empty_string_digest(void);
+
+
+/* Return the hex representation of DIGEST, which must be
+ * APR_SHA1_DIGESTSIZE bytes long, allocating the string in POOL.
+ */
+const char *
+svn_sha1__digest_to_cstring_display(const unsigned char digest[],
+ apr_pool_t *pool);
+
+
+/* Return the hex representation of DIGEST, which must be
+ * APR_SHA1_DIGESTSIZE bytes long, allocating the string in POOL.
+ * If DIGEST is all zeros, then return NULL.
+ */
+const char *
+svn_sha1__digest_to_cstring(const unsigned char digest[],
+ apr_pool_t *pool);
+
+
+/** Compare digests D1 and D2, each APR_SHA1_DIGESTSIZE bytes long.
+ * If neither is all zeros, and they do not match, then return FALSE;
+ * else return TRUE.
+ */
+svn_boolean_t
+svn_sha1__digests_match(const unsigned char d1[],
+ const unsigned char d2[]);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_SUBR_SHA1_H */
diff --git a/contrib/subversion/subversion/libsvn_subr/simple_providers.c b/contrib/subversion/subversion/libsvn_subr/simple_providers.c
new file mode 100644
index 0000000..e70770a
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/simple_providers.c
@@ -0,0 +1,734 @@
+/*
+ * simple_providers.c: providers for SVN_AUTH_CRED_SIMPLE
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_pools.h>
+#include "svn_auth.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_config.h"
+#include "svn_user.h"
+
+#include "private/svn_auth_private.h"
+
+#include "svn_private_config.h"
+
+#include "auth.h"
+
+/*-----------------------------------------------------------------------*/
+/* File provider */
+/*-----------------------------------------------------------------------*/
+
+/* The keys that will be stored on disk. These serve the same role as
+ similar constants in other providers. */
+#define AUTHN_USERNAME_KEY "username"
+#define AUTHN_PASSWORD_KEY "password"
+#define AUTHN_PASSTYPE_KEY "passtype"
+
+/* Baton type for the simple provider. */
+typedef struct simple_provider_baton_t
+{
+ svn_auth_plaintext_prompt_func_t plaintext_prompt_func;
+ void *prompt_baton;
+ /* We cache the user's answer to the plaintext prompt, keyed
+ * by realm, in case we'll be called multiple times for the
+ * same realm. */
+ apr_hash_t *plaintext_answers;
+} simple_provider_baton_t;
+
+
+/* Implementation of svn_auth__password_get_t that retrieves
+ the plaintext password from CREDS. */
+svn_error_t *
+svn_auth__simple_password_get(svn_boolean_t *done,
+ const char **password,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ svn_string_t *str;
+
+ *done = FALSE;
+
+ str = svn_hash_gets(creds, AUTHN_USERNAME_KEY);
+ if (str && username && strcmp(str->data, username) == 0)
+ {
+ str = svn_hash_gets(creds, AUTHN_PASSWORD_KEY);
+ if (str && str->data)
+ {
+ *password = str->data;
+ *done = TRUE;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implementation of svn_auth__password_set_t that stores
+ the plaintext password in CREDS. */
+svn_error_t *
+svn_auth__simple_password_set(svn_boolean_t *done,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ const char *password,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ svn_hash_sets(creds, AUTHN_PASSWORD_KEY, svn_string_create(password, pool));
+ *done = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Set **USERNAME to the username retrieved from CREDS; ignore
+ other parameters. *USERNAME will have the same lifetime as CREDS. */
+static svn_boolean_t
+simple_username_get(const char **username,
+ apr_hash_t *creds,
+ const char *realmstring,
+ svn_boolean_t non_interactive)
+{
+ svn_string_t *str;
+ str = svn_hash_gets(creds, AUTHN_USERNAME_KEY);
+ if (str && str->data)
+ {
+ *username = str->data;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+svn_error_t *
+svn_auth__simple_creds_cache_get(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ svn_auth__password_get_t password_get,
+ const char *passtype,
+ apr_pool_t *pool)
+{
+ const char *config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR);
+ svn_config_t *cfg = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS);
+ const char *server_group = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_SERVER_GROUP);
+ const char *username = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_DEFAULT_USERNAME);
+ const char *password = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_DEFAULT_PASSWORD);
+ svn_boolean_t non_interactive = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NON_INTERACTIVE)
+ != NULL;
+ const char *default_username = NULL; /* Default username from cache. */
+ const char *default_password = NULL; /* Default password from cache. */
+
+ /* This checks if we should save the CREDS, iff saving the credentials is
+ allowed by the run-time configuration. */
+ svn_boolean_t need_to_save = FALSE;
+ apr_hash_t *creds_hash = NULL;
+ svn_error_t *err;
+ svn_string_t *str;
+
+ /* Try to load credentials from a file on disk, based on the
+ realmstring. Don't throw an error, though: if something went
+ wrong reading the file, no big deal. What really matters is that
+ we failed to get the creds, so allow the auth system to try the
+ next provider. */
+ err = svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_SIMPLE,
+ realmstring, config_dir, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ err = NULL;
+ }
+ else if (creds_hash)
+ {
+ /* We have something in the auth cache for this realm. */
+ svn_boolean_t have_passtype = FALSE;
+
+ /* The password type in the auth data must match the
+ mangler's type, otherwise the password must be
+ interpreted by another provider. */
+ str = svn_hash_gets(creds_hash, AUTHN_PASSTYPE_KEY);
+ if (str && str->data)
+ if (passtype && (0 == strcmp(str->data, passtype)))
+ have_passtype = TRUE;
+
+ /* See if we need to save this username if it is not present in
+ auth cache. */
+ if (username)
+ {
+ if (!simple_username_get(&default_username, creds_hash, realmstring,
+ non_interactive))
+ {
+ need_to_save = TRUE;
+ }
+ else
+ {
+ if (strcmp(default_username, username) != 0)
+ need_to_save = TRUE;
+ }
+ }
+
+ /* See if we need to save this password if it is not present in
+ auth cache. */
+ if (password)
+ {
+ if (have_passtype)
+ {
+ svn_boolean_t done;
+
+ SVN_ERR(password_get(&done, &default_password, creds_hash,
+ realmstring, username, parameters,
+ non_interactive, pool));
+ if (!done)
+ {
+ need_to_save = TRUE;
+ }
+ else
+ {
+ if (strcmp(default_password, password) != 0)
+ need_to_save = TRUE;
+ }
+ }
+ }
+
+ /* If we don't have a username and a password yet, we try the
+ auth cache */
+ if (! (username && password))
+ {
+ if (! username)
+ if (!simple_username_get(&username, creds_hash, realmstring,
+ non_interactive))
+ username = NULL;
+
+ if (username && ! password)
+ {
+ if (! have_passtype)
+ password = NULL;
+ else
+ {
+ svn_boolean_t done;
+
+ SVN_ERR(password_get(&done, &password, creds_hash,
+ realmstring, username, parameters,
+ non_interactive, pool));
+ if (!done)
+ password = NULL;
+
+ /* If the auth data didn't contain a password type,
+ force a write to upgrade the format of the auth
+ data file. */
+ if (password && ! have_passtype)
+ need_to_save = TRUE;
+ }
+ }
+ }
+ }
+ else
+ {
+ /* Nothing was present in the auth cache, so indicate that these
+ credentials should be saved. */
+ need_to_save = TRUE;
+ }
+
+ /* If we don't have a username yet, check the 'servers' file */
+ if (! username)
+ {
+ username = svn_config_get_server_setting(cfg, server_group,
+ SVN_CONFIG_OPTION_USERNAME,
+ NULL);
+ }
+
+ /* Ask the OS for the username if we have a password but no
+ username. */
+ if (password && ! username)
+ username = svn_user_get_name(pool);
+
+ if (username && password)
+ {
+ svn_auth_cred_simple_t *creds = apr_pcalloc(pool, sizeof(*creds));
+ creds->username = username;
+ creds->password = password;
+ creds->may_save = need_to_save;
+ *credentials = creds;
+ }
+ else
+ *credentials = NULL;
+
+ *iter_baton = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_auth__simple_creds_cache_set(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ svn_auth__password_set_t password_set,
+ const char *passtype,
+ apr_pool_t *pool)
+{
+ svn_auth_cred_simple_t *creds = credentials;
+ apr_hash_t *creds_hash = NULL;
+ const char *config_dir;
+ svn_error_t *err;
+ svn_boolean_t dont_store_passwords =
+ svn_hash_gets(parameters, SVN_AUTH_PARAM_DONT_STORE_PASSWORDS) != NULL;
+ svn_boolean_t non_interactive = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NON_INTERACTIVE)
+ != NULL;
+ svn_boolean_t no_auth_cache =
+ (! creds->may_save) || (svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE)
+ != NULL);
+
+ /* Make sure we've been passed a passtype. */
+ SVN_ERR_ASSERT(passtype != NULL);
+
+ *saved = FALSE;
+
+ if (no_auth_cache)
+ return SVN_NO_ERROR;
+
+ config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR);
+
+ /* Put the username into the credentials hash. */
+ creds_hash = apr_hash_make(pool);
+ svn_hash_sets(creds_hash, AUTHN_USERNAME_KEY,
+ svn_string_create(creds->username, pool));
+
+ /* Don't store passwords in any form if the user has told
+ * us not to do so. */
+ if (! dont_store_passwords)
+ {
+ svn_boolean_t may_save_password = FALSE;
+
+ /* If the password is going to be stored encrypted, go right
+ * ahead and store it to disk. Else determine whether saving
+ * in plaintext is OK. */
+ if (passtype &&
+ (strcmp(passtype, SVN_AUTH__WINCRYPT_PASSWORD_TYPE) == 0
+ || strcmp(passtype, SVN_AUTH__KEYCHAIN_PASSWORD_TYPE) == 0
+ || strcmp(passtype, SVN_AUTH__KWALLET_PASSWORD_TYPE) == 0
+ || strcmp(passtype, SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE) == 0
+ || strcmp(passtype, SVN_AUTH__GPG_AGENT_PASSWORD_TYPE) == 0))
+ {
+ may_save_password = TRUE;
+ }
+ else
+ {
+#ifdef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
+ may_save_password = FALSE;
+#else
+ const char *store_plaintext_passwords =
+ svn_hash_gets(parameters, SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS);
+ simple_provider_baton_t *b =
+ (simple_provider_baton_t *)provider_baton;
+
+ if (store_plaintext_passwords
+ && svn_cstring_casecmp(store_plaintext_passwords,
+ SVN_CONFIG_ASK) == 0)
+ {
+ if (non_interactive)
+ /* In non-interactive mode, the default behaviour is
+ * to not store the password, because it is usually
+ * passed on the command line. */
+ may_save_password = FALSE;
+ else if (b->plaintext_prompt_func)
+ {
+ /* We're interactive, and the client provided a
+ * prompt callback. So we can ask the user.
+ *
+ * Check for a cached answer before prompting. */
+ svn_boolean_t *cached_answer;
+ cached_answer = svn_hash_gets(b->plaintext_answers,
+ realmstring);
+ if (cached_answer != NULL)
+ may_save_password = *cached_answer;
+ else
+ {
+ apr_pool_t *cached_answer_pool;
+
+ /* Nothing cached for this realm, prompt the user. */
+ SVN_ERR((*b->plaintext_prompt_func)(&may_save_password,
+ realmstring,
+ b->prompt_baton,
+ pool));
+
+ /* Cache the user's answer in case we're called again
+ * for the same realm.
+ *
+ * We allocate the answer cache in the hash table's pool
+ * to make sure that is has the same life time as the
+ * hash table itself. This means that the answer will
+ * survive across RA sessions -- which is important,
+ * because otherwise we'd prompt users once per RA session.
+ */
+ cached_answer_pool = apr_hash_pool_get(b->plaintext_answers);
+ cached_answer = apr_palloc(cached_answer_pool,
+ sizeof(svn_boolean_t));
+ *cached_answer = may_save_password;
+ svn_hash_sets(b->plaintext_answers, realmstring,
+ cached_answer);
+ }
+ }
+ else
+ {
+ /* TODO: We might want to default to not storing if the
+ * prompt callback is NULL, i.e. have may_save_password
+ * default to FALSE here, in order to force clients to
+ * implement the callback.
+ *
+ * This would change the semantics of old API though.
+ *
+ * So for now, clients that don't implement the callback
+ * and provide no explicit value for
+ * SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS
+ * cause unencrypted passwords to be stored by default.
+ * Needless to say, our own client is sane, but who knows
+ * what other clients are doing.
+ */
+ may_save_password = TRUE;
+ }
+ }
+ else if (store_plaintext_passwords
+ && svn_cstring_casecmp(store_plaintext_passwords,
+ SVN_CONFIG_FALSE) == 0)
+ {
+ may_save_password = FALSE;
+ }
+ else if (!store_plaintext_passwords
+ || svn_cstring_casecmp(store_plaintext_passwords,
+ SVN_CONFIG_TRUE) == 0)
+ {
+ may_save_password = TRUE;
+ }
+ else
+ {
+ return svn_error_createf
+ (SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Config error: invalid value '%s' for option '%s'"),
+ store_plaintext_passwords,
+ SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS);
+ }
+#endif
+ }
+
+ if (may_save_password)
+ {
+ SVN_ERR(password_set(saved, creds_hash, realmstring,
+ creds->username, creds->password,
+ parameters, non_interactive, pool));
+ if (*saved && passtype)
+ /* Store the password type with the auth data, so that we
+ know which provider owns the password. */
+ svn_hash_sets(creds_hash, AUTHN_PASSTYPE_KEY,
+ svn_string_create(passtype, pool));
+ }
+ }
+
+ /* Save credentials to disk. */
+ err = svn_config_write_auth_data(creds_hash, SVN_AUTH_CRED_SIMPLE,
+ realmstring, config_dir, pool);
+ if (err)
+ *saved = FALSE;
+
+ /* ### return error? */
+ svn_error_clear(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* Get cached (unencrypted) credentials from the simple provider's cache. */
+static svn_error_t *
+simple_first_creds(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_get(credentials, iter_baton,
+ provider_baton, parameters,
+ realmstring,
+ svn_auth__simple_password_get,
+ SVN_AUTH__SIMPLE_PASSWORD_TYPE,
+ pool);
+}
+
+/* Save (unencrypted) credentials to the simple provider's cache. */
+static svn_error_t *
+simple_save_creds(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_set(saved, credentials, provider_baton,
+ parameters, realmstring,
+ svn_auth__simple_password_set,
+ SVN_AUTH__SIMPLE_PASSWORD_TYPE,
+ pool);
+}
+
+static const svn_auth_provider_t simple_provider = {
+ SVN_AUTH_CRED_SIMPLE,
+ simple_first_creds,
+ NULL,
+ simple_save_creds
+};
+
+
+/* Public API */
+void
+svn_auth_get_simple_provider2
+ (svn_auth_provider_object_t **provider,
+ svn_auth_plaintext_prompt_func_t plaintext_prompt_func,
+ void* prompt_baton,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+ simple_provider_baton_t *pb = apr_pcalloc(pool, sizeof(*pb));
+
+ pb->plaintext_prompt_func = plaintext_prompt_func;
+ pb->prompt_baton = prompt_baton;
+ pb->plaintext_answers = apr_hash_make(pool);
+
+ po->vtable = &simple_provider;
+ po->provider_baton = pb;
+ *provider = po;
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* Prompt provider */
+/*-----------------------------------------------------------------------*/
+
+/* Baton type for username/password prompting. */
+typedef struct simple_prompt_provider_baton_t
+{
+ svn_auth_simple_prompt_func_t prompt_func;
+ void *prompt_baton;
+
+ /* how many times to re-prompt after the first one fails */
+ int retry_limit;
+} simple_prompt_provider_baton_t;
+
+
+/* Iteration baton type for username/password prompting. */
+typedef struct simple_prompt_iter_baton_t
+{
+ /* how many times we've reprompted */
+ int retries;
+} simple_prompt_iter_baton_t;
+
+
+
+/*** Helper Functions ***/
+static svn_error_t *
+prompt_for_simple_creds(svn_auth_cred_simple_t **cred_p,
+ simple_prompt_provider_baton_t *pb,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ svn_boolean_t first_time,
+ svn_boolean_t may_save,
+ apr_pool_t *pool)
+{
+ const char *default_username = NULL;
+ const char *default_password = NULL;
+
+ *cred_p = NULL;
+
+ /* If we're allowed to check for default usernames and passwords, do
+ so. */
+ if (first_time)
+ {
+ default_username = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_DEFAULT_USERNAME);
+
+ /* No default username? Try the auth cache. */
+ if (! default_username)
+ {
+ const char *config_dir = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_CONFIG_DIR);
+ apr_hash_t *creds_hash = NULL;
+ svn_string_t *str;
+ svn_error_t *err;
+
+ err = svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_SIMPLE,
+ realmstring, config_dir, pool);
+ svn_error_clear(err);
+ if (! err && creds_hash)
+ {
+ str = svn_hash_gets(creds_hash, AUTHN_USERNAME_KEY);
+ if (str && str->data)
+ default_username = str->data;
+ }
+ }
+
+ /* Still no default username? Try the 'servers' file. */
+ if (! default_username)
+ {
+ svn_config_t *cfg = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS);
+ const char *server_group = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_SERVER_GROUP);
+ default_username =
+ svn_config_get_server_setting(cfg, server_group,
+ SVN_CONFIG_OPTION_USERNAME,
+ NULL);
+ }
+
+ /* Still no default username? Try the UID. */
+ if (! default_username)
+ default_username = svn_user_get_name(pool);
+
+ default_password = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_DEFAULT_PASSWORD);
+ }
+
+ /* If we have defaults, just build the cred here and return it.
+ *
+ * ### I do wonder why this is here instead of in a separate
+ * ### 'defaults' provider that would run before the prompt
+ * ### provider... Hmmm.
+ */
+ if (default_username && default_password)
+ {
+ *cred_p = apr_palloc(pool, sizeof(**cred_p));
+ (*cred_p)->username = apr_pstrdup(pool, default_username);
+ (*cred_p)->password = apr_pstrdup(pool, default_password);
+ (*cred_p)->may_save = TRUE;
+ }
+ else
+ {
+ SVN_ERR(pb->prompt_func(cred_p, pb->prompt_baton, realmstring,
+ default_username, may_save, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Our first attempt will use any default username/password passed
+ in, and prompt for the remaining stuff. */
+static svn_error_t *
+simple_prompt_first_creds(void **credentials_p,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ simple_prompt_provider_baton_t *pb = provider_baton;
+ simple_prompt_iter_baton_t *ibaton = apr_pcalloc(pool, sizeof(*ibaton));
+ const char *no_auth_cache = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE);
+
+ SVN_ERR(prompt_for_simple_creds((svn_auth_cred_simple_t **) credentials_p,
+ pb, parameters, realmstring, TRUE,
+ ! no_auth_cache, pool));
+
+ ibaton->retries = 0;
+ *iter_baton = ibaton;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Subsequent attempts to fetch will ignore the default values, and
+ simply re-prompt for both, up to a maximum of ib->pb->retry_limit. */
+static svn_error_t *
+simple_prompt_next_creds(void **credentials_p,
+ void *iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ simple_prompt_iter_baton_t *ib = iter_baton;
+ simple_prompt_provider_baton_t *pb = provider_baton;
+ const char *no_auth_cache = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE);
+
+ if ((pb->retry_limit >= 0) && (ib->retries >= pb->retry_limit))
+ {
+ /* give up, go on to next provider. */
+ *credentials_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ ib->retries++;
+
+ return prompt_for_simple_creds((svn_auth_cred_simple_t **) credentials_p,
+ pb, parameters, realmstring, FALSE,
+ ! no_auth_cache, pool);
+}
+
+static const svn_auth_provider_t simple_prompt_provider = {
+ SVN_AUTH_CRED_SIMPLE,
+ simple_prompt_first_creds,
+ simple_prompt_next_creds,
+ NULL,
+};
+
+
+/* Public API */
+void
+svn_auth_get_simple_prompt_provider
+ (svn_auth_provider_object_t **provider,
+ svn_auth_simple_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+ simple_prompt_provider_baton_t *pb = apr_pcalloc(pool, sizeof(*pb));
+
+ pb->prompt_func = prompt_func;
+ pb->prompt_baton = prompt_baton;
+ pb->retry_limit = retry_limit;
+
+ po->vtable = &simple_prompt_provider;
+ po->provider_baton = pb;
+ *provider = po;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/skel.c b/contrib/subversion/subversion/libsvn_subr/skel.c
new file mode 100644
index 0000000..ed12db0
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/skel.c
@@ -0,0 +1,881 @@
+/* skel.c --- parsing and unparsing skeletons
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+#include "private/svn_skel.h"
+#include "private/svn_string_private.h"
+
+
+/* Parsing skeletons. */
+
+enum char_type {
+ type_nothing = 0,
+ type_space = 1,
+ type_digit = 2,
+ type_paren = 3,
+ type_name = 4
+};
+
+
+/* We can't use the <ctype.h> macros here, because they are locale-
+ dependent. The syntax of a skel is specified directly in terms of
+ byte values, and is independent of locale. */
+
+static const enum char_type skel_char_type[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0,
+
+ /* 64 */
+ 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 0, 3, 0, 0,
+ 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0,
+
+ /* 128 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ /* 192 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+
+
+/* ### WTF? since when is number conversion LOCALE DEPENDENT? */
+/* stsp: In C99, various numerical string properties such as decimal point,
+ * thousands separator, and the plus/minus sign are locale dependent. */
+
+/* Converting text to numbers. */
+
+/* Return the value of the string of digits at DATA as an ASCII
+ decimal number. The string is at most LEN bytes long. The value
+ of the number is at most MAX. Set *END to the address of the first
+ byte after the number, or zero if an error occurred while
+ converting the number (overflow, for example).
+
+ We would like to use strtoul, but that family of functions is
+ locale-dependent, whereas we're trying to parse data in a
+ locale-independent format. */
+static apr_size_t
+getsize(const char *data, apr_size_t len,
+ const char **endptr, apr_size_t max)
+{
+ /* We can't detect overflow by simply comparing value against max,
+ since multiplying value by ten can overflow in strange ways if
+ max is close to the limits of apr_size_t. For example, suppose
+ that max is 54, and apr_size_t is six bits long; its range is
+ 0..63. If we're parsing the number "502", then value will be 50
+ after parsing the first two digits. 50 * 10 = 500. But 500
+ doesn't fit in an apr_size_t, so it'll be truncated to 500 mod 64
+ = 52, which is less than max, so we'd fail to recognize the
+ overflow. Furthermore, it *is* greater than 50, so you can't
+ detect overflow by checking whether value actually increased
+ after each multiplication --- sometimes it does increase, but
+ it's still wrong.
+
+ So we do the check for overflow before we multiply value and add
+ in the new digit. */
+ apr_size_t max_prefix = max / 10;
+ apr_size_t max_digit = max % 10;
+ apr_size_t i;
+ apr_size_t value = 0;
+
+ for (i = 0; i < len && '0' <= data[i] && data[i] <= '9'; i++)
+ {
+ apr_size_t digit = data[i] - '0';
+
+ /* Check for overflow. */
+ if (value > max_prefix
+ || (value == max_prefix && digit > max_digit))
+ {
+ *endptr = 0;
+ return 0;
+ }
+
+ value = (value * 10) + digit;
+ }
+
+ /* There must be at least one digit there. */
+ if (i == 0)
+ {
+ *endptr = 0;
+ return 0;
+ }
+ else
+ {
+ *endptr = data + i;
+ return value;
+ }
+}
+
+
+/* Checking validity of skels. */
+static svn_error_t *
+skel_err(const char *skel_type)
+{
+ return svn_error_createf(SVN_ERR_FS_MALFORMED_SKEL, NULL,
+ "Malformed%s%s skeleton",
+ skel_type ? " " : "",
+ skel_type ? skel_type : "");
+}
+
+
+static svn_boolean_t
+is_valid_proplist_skel(const svn_skel_t *skel)
+{
+ int len = svn_skel__list_length(skel);
+
+ if ((len >= 0) && (len & 1) == 0)
+ {
+ svn_skel_t *elt;
+
+ for (elt = skel->children; elt; elt = elt->next)
+ if (! elt->is_atom)
+ return FALSE;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static svn_boolean_t
+is_valid_iproplist_skel(const svn_skel_t *skel)
+{
+ int len = svn_skel__list_length(skel);
+
+ if ((len >= 0) && (len & 1) == 0)
+ {
+ svn_skel_t *elt;
+
+ for (elt = skel->children; elt; elt = elt->next)
+ {
+ if (!elt->is_atom)
+ return FALSE;
+
+ if (elt->next == NULL)
+ return FALSE;
+
+ elt = elt->next;
+
+ if (! is_valid_proplist_skel(elt))
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static svn_skel_t *parse(const char *data, apr_size_t len,
+ apr_pool_t *pool);
+static svn_skel_t *list(const char *data, apr_size_t len,
+ apr_pool_t *pool);
+static svn_skel_t *implicit_atom(const char *data, apr_size_t len,
+ apr_pool_t *pool);
+static svn_skel_t *explicit_atom(const char *data, apr_size_t len,
+ apr_pool_t *pool);
+
+
+svn_skel_t *
+svn_skel__parse(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ return parse(data, len, pool);
+}
+
+
+/* Parse any kind of skel object --- atom, or list. */
+static svn_skel_t *
+parse(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ char c;
+
+ /* The empty string isn't a valid skel. */
+ if (len <= 0)
+ return NULL;
+
+ c = *data;
+
+ /* Is it a list, or an atom? */
+ if (c == '(')
+ return list(data, len, pool);
+
+ /* Is it a string with an implicit length? */
+ if (skel_char_type[(unsigned char) c] == type_name)
+ return implicit_atom(data, len, pool);
+
+ /* Otherwise, we assume it's a string with an explicit length;
+ svn_skel__getsize will catch the error. */
+ else
+ return explicit_atom(data, len, pool);
+}
+
+
+static svn_skel_t *
+list(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ const char *end = data + len;
+ const char *list_start;
+
+ /* Verify that the list starts with an opening paren. At the
+ moment, all callers have checked this already, but it's more
+ robust this way. */
+ if (data >= end || *data != '(')
+ return NULL;
+
+ /* Mark where the list starts. */
+ list_start = data;
+
+ /* Skip the opening paren. */
+ data++;
+
+ /* Parse the children. */
+ {
+ svn_skel_t *children = NULL;
+ svn_skel_t **tail = &children;
+
+ for (;;)
+ {
+ svn_skel_t *element;
+
+ /* Skip any whitespace. */
+ while (data < end
+ && skel_char_type[(unsigned char) *data] == type_space)
+ data++;
+
+ /* End of data, but no closing paren? */
+ if (data >= end)
+ return NULL;
+
+ /* End of list? */
+ if (*data == ')')
+ {
+ data++;
+ break;
+ }
+
+ /* Parse the next element in the list. */
+ element = parse(data, end - data, pool);
+ if (! element)
+ return NULL;
+
+ /* Link that element into our list. */
+ element->next = NULL;
+ *tail = element;
+ tail = &element->next;
+
+ /* Advance past that element. */
+ data = element->data + element->len;
+ }
+
+ /* Construct the return value. */
+ {
+ svn_skel_t *s = apr_pcalloc(pool, sizeof(*s));
+
+ s->is_atom = FALSE;
+ s->data = list_start;
+ s->len = data - list_start;
+ s->children = children;
+
+ return s;
+ }
+ }
+}
+
+
+/* Parse an atom with implicit length --- one that starts with a name
+ character, terminated by whitespace, '(', ')', or end-of-data. */
+static svn_skel_t *
+implicit_atom(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ const char *start = data;
+ const char *end = data + len;
+ svn_skel_t *s;
+
+ /* Verify that the atom starts with a name character. At the
+ moment, all callers have checked this already, but it's more
+ robust this way. */
+ if (data >= end || skel_char_type[(unsigned char) *data] != type_name)
+ return NULL;
+
+ /* Find the end of the string. */
+ while (++data < end
+ && skel_char_type[(unsigned char) *data] != type_space
+ && skel_char_type[(unsigned char) *data] != type_paren)
+ ;
+
+ /* Allocate the skel representing this string. */
+ s = apr_pcalloc(pool, sizeof(*s));
+ s->is_atom = TRUE;
+ s->data = start;
+ s->len = data - start;
+
+ return s;
+}
+
+
+/* Parse an atom with explicit length --- one that starts with a byte
+ length, as a decimal ASCII number. */
+static svn_skel_t *
+explicit_atom(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ const char *end = data + len;
+ const char *next;
+ apr_size_t size;
+ svn_skel_t *s;
+
+ /* Parse the length. */
+ size = getsize(data, end - data, &next, end - data);
+ data = next;
+
+ /* Exit if we overflowed, or there wasn't a valid number there. */
+ if (! data)
+ return NULL;
+
+ /* Skip the whitespace character after the length. */
+ if (data >= end || skel_char_type[(unsigned char) *data] != type_space)
+ return NULL;
+ data++;
+
+ /* Check the length. */
+ if (data + size > end)
+ return NULL;
+
+ /* Allocate the skel representing this string. */
+ s = apr_pcalloc(pool, sizeof(*s));
+ s->is_atom = TRUE;
+ s->data = data;
+ s->len = size;
+
+ return s;
+}
+
+
+
+/* Unparsing skeletons. */
+
+static apr_size_t estimate_unparsed_size(const svn_skel_t *skel);
+static svn_stringbuf_t *unparse(const svn_skel_t *skel,
+ svn_stringbuf_t *str);
+
+
+svn_stringbuf_t *
+svn_skel__unparse(const svn_skel_t *skel, apr_pool_t *pool)
+{
+ svn_stringbuf_t *str
+ = svn_stringbuf_create_ensure(estimate_unparsed_size(skel) + 200, pool);
+
+ return unparse(skel, str);
+}
+
+
+/* Return an estimate of the number of bytes that the external
+ representation of SKEL will occupy. Since reallocing is expensive
+ in pools, it's worth trying to get the buffer size right the first
+ time. */
+static apr_size_t
+estimate_unparsed_size(const svn_skel_t *skel)
+{
+ if (skel->is_atom)
+ {
+ if (skel->len < 100)
+ /* If we have to use the explicit-length form, that'll be
+ two bytes for the length, one byte for the space, and
+ the contents. */
+ return skel->len + 3;
+ else
+ return skel->len + 30;
+ }
+ else
+ {
+ apr_size_t total_len;
+ svn_skel_t *child;
+
+ /* Allow space for opening and closing parens, and a space
+ between each pair of elements. */
+ total_len = 2;
+ for (child = skel->children; child; child = child->next)
+ total_len += estimate_unparsed_size(child) + 1;
+
+ return total_len;
+ }
+}
+
+
+/* Return non-zero iff we should use the implicit-length form for SKEL.
+ Assume that SKEL is an atom. */
+static svn_boolean_t
+use_implicit(const svn_skel_t *skel)
+{
+ /* If it's null, or long, we should use explicit-length form. */
+ if (skel->len == 0
+ || skel->len >= 100)
+ return FALSE;
+
+ /* If it doesn't start with a name character, we must use
+ explicit-length form. */
+ if (skel_char_type[(unsigned char) skel->data[0]] != type_name)
+ return FALSE;
+
+ /* If it contains any whitespace or parens, then we must use
+ explicit-length form. */
+ {
+ apr_size_t i;
+
+ for (i = 1; i < skel->len; i++)
+ if (skel_char_type[(unsigned char) skel->data[i]] == type_space
+ || skel_char_type[(unsigned char) skel->data[i]] == type_paren)
+ return FALSE;
+ }
+
+ /* If we can't reject it for any of the above reasons, then we can
+ use implicit-length form. */
+ return TRUE;
+}
+
+
+/* Append the concrete representation of SKEL to the string STR. */
+static svn_stringbuf_t *
+unparse(const svn_skel_t *skel, svn_stringbuf_t *str)
+{
+ if (skel->is_atom)
+ {
+ /* Append an atom to STR. */
+ if (use_implicit(skel))
+ svn_stringbuf_appendbytes(str, skel->data, skel->len);
+ else
+ {
+ /* Append the length to STR. Ensure enough space for at least
+ * one 64 bit int. */
+ char buf[200 + SVN_INT64_BUFFER_SIZE];
+ apr_size_t length_len;
+
+ length_len = svn__ui64toa(buf, skel->len);
+
+ SVN_ERR_ASSERT_NO_RETURN(length_len > 0);
+
+ /* Make sure we have room for the length, the space, and the
+ atom's contents. */
+ svn_stringbuf_ensure(str, str->len + length_len + 1 + skel->len);
+ svn_stringbuf_appendbytes(str, buf, length_len);
+ svn_stringbuf_appendbyte(str, ' ');
+ svn_stringbuf_appendbytes(str, skel->data, skel->len);
+ }
+ }
+ else
+ {
+ /* Append a list to STR: an opening parenthesis, the list elements
+ * separated by a space, and a closing parenthesis. */
+ svn_skel_t *child;
+
+ svn_stringbuf_appendbyte(str, '(');
+
+ for (child = skel->children; child; child = child->next)
+ {
+ unparse(child, str);
+ if (child->next)
+ svn_stringbuf_appendbyte(str, ' ');
+ }
+
+ svn_stringbuf_appendbyte(str, ')');
+ }
+
+ return str;
+}
+
+
+
+/* Building skels. */
+
+
+svn_skel_t *
+svn_skel__str_atom(const char *str, apr_pool_t *pool)
+{
+ svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel));
+ skel->is_atom = TRUE;
+ skel->data = str;
+ skel->len = strlen(str);
+
+ return skel;
+}
+
+
+svn_skel_t *
+svn_skel__mem_atom(const void *addr,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel));
+ skel->is_atom = TRUE;
+ skel->data = addr;
+ skel->len = len;
+
+ return skel;
+}
+
+
+svn_skel_t *
+svn_skel__make_empty_list(apr_pool_t *pool)
+{
+ svn_skel_t *skel = apr_pcalloc(pool, sizeof(*skel));
+ return skel;
+}
+
+svn_skel_t *svn_skel__dup(const svn_skel_t *src_skel, svn_boolean_t dup_data,
+ apr_pool_t *result_pool)
+{
+ svn_skel_t *skel = apr_pmemdup(result_pool, src_skel, sizeof(svn_skel_t));
+
+ if (dup_data && skel->data)
+ {
+ if (skel->is_atom)
+ skel->data = apr_pmemdup(result_pool, skel->data, skel->len);
+ else
+ {
+ /* When creating a skel this would be NULL, 0 for a list.
+ When parsing a string to a skel this might point to real data
+ delimiting the sublist. We don't copy that from here. */
+ skel->data = NULL;
+ skel->len = 0;
+ }
+ }
+
+ if (skel->children)
+ skel->children = svn_skel__dup(skel->children, dup_data, result_pool);
+
+ if (skel->next)
+ skel->next = svn_skel__dup(skel->next, dup_data, result_pool);
+
+ return skel;
+}
+
+void
+svn_skel__prepend(svn_skel_t *skel, svn_skel_t *list_skel)
+{
+ /* If list_skel isn't even a list, somebody's not using this
+ function properly. */
+ SVN_ERR_ASSERT_NO_RETURN(! list_skel->is_atom);
+
+ skel->next = list_skel->children;
+ list_skel->children = skel;
+}
+
+
+void svn_skel__prepend_int(apr_int64_t value,
+ svn_skel_t *skel,
+ apr_pool_t *result_pool)
+{
+ char *val_string = apr_palloc(result_pool, SVN_INT64_BUFFER_SIZE);
+ svn__i64toa(val_string, value);
+
+ svn_skel__prepend_str(val_string, skel, result_pool);
+}
+
+
+void svn_skel__prepend_str(const char *value,
+ svn_skel_t *skel,
+ apr_pool_t *result_pool)
+{
+ svn_skel_t *atom = svn_skel__str_atom(value, result_pool);
+
+ svn_skel__prepend(atom, skel);
+}
+
+
+void svn_skel__append(svn_skel_t *list_skel, svn_skel_t *skel)
+{
+ SVN_ERR_ASSERT_NO_RETURN(list_skel != NULL && !list_skel->is_atom);
+
+ if (list_skel->children == NULL)
+ {
+ list_skel->children = skel;
+ }
+ else
+ {
+ list_skel = list_skel->children;
+ while (list_skel->next != NULL)
+ list_skel = list_skel->next;
+ list_skel->next = skel;
+ }
+}
+
+
+/* Examining skels. */
+
+
+svn_boolean_t
+svn_skel__matches_atom(const svn_skel_t *skel, const char *str)
+{
+ if (skel && skel->is_atom)
+ {
+ apr_size_t len = strlen(str);
+
+ return (skel->len == len
+ && ! memcmp(skel->data, str, len));
+ }
+ return FALSE;
+}
+
+
+int
+svn_skel__list_length(const svn_skel_t *skel)
+{
+ int len = 0;
+ const svn_skel_t *child;
+
+ if ((! skel) || skel->is_atom)
+ return -1;
+
+ for (child = skel->children; child; child = child->next)
+ len++;
+
+ return len;
+}
+
+
+
+/* Parsing and unparsing into high-level types. */
+
+svn_error_t *
+svn_skel__parse_int(apr_int64_t *n, const svn_skel_t *skel,
+ apr_pool_t *scratch_pool)
+{
+ const char *str;
+
+ /* We need to duplicate the SKEL contents in order to get a NUL-terminated
+ version of it. The SKEL may not have valid memory at DATA[LEN]. */
+ str = apr_pstrmemdup(scratch_pool, skel->data, skel->len);
+ return svn_error_trace(svn_cstring_atoi64(n, str));
+}
+
+
+svn_error_t *
+svn_skel__parse_proplist(apr_hash_t **proplist_p,
+ const svn_skel_t *skel,
+ apr_pool_t *pool /* result_pool */)
+{
+ apr_hash_t *proplist = NULL;
+ svn_skel_t *elt;
+
+ /* Validate the skel. */
+ if (! is_valid_proplist_skel(skel))
+ return skel_err("proplist");
+
+ /* Create the returned structure */
+ proplist = apr_hash_make(pool);
+ for (elt = skel->children; elt; elt = elt->next->next)
+ {
+ svn_string_t *value = svn_string_ncreate(elt->next->data,
+ elt->next->len, pool);
+ apr_hash_set(proplist,
+ apr_pstrmemdup(pool, elt->data, elt->len),
+ elt->len,
+ value);
+ }
+
+ /* Return the structure. */
+ *proplist_p = proplist;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_skel__parse_iprops(apr_array_header_t **iprops,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool)
+{
+ svn_skel_t *elt;
+
+ /* Validate the skel. */
+ if (! is_valid_iproplist_skel(skel))
+ return skel_err("iprops");
+
+ /* Create the returned structure */
+ *iprops = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+
+ for (elt = skel->children; elt; elt = elt->next->next)
+ {
+ svn_prop_inherited_item_t *new_iprop = apr_palloc(result_pool,
+ sizeof(*new_iprop));
+ svn_string_t *repos_parent = svn_string_ncreate(elt->data, elt->len,
+ result_pool);
+ SVN_ERR(svn_skel__parse_proplist(&(new_iprop->prop_hash), elt->next,
+ result_pool));
+ new_iprop->path_or_url = repos_parent->data;
+ APR_ARRAY_PUSH(*iprops, svn_prop_inherited_item_t *) = new_iprop;
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_skel__parse_prop(svn_string_t **propval,
+ const svn_skel_t *skel,
+ const char *propname,
+ apr_pool_t *pool /* result_pool */)
+{
+ svn_skel_t *elt;
+
+ *propval = NULL;
+
+ /* Validate the skel. */
+ if (! is_valid_proplist_skel(skel))
+ return skel_err("proplist");
+
+ /* Look for PROPNAME in SKEL. */
+ for (elt = skel->children; elt; elt = elt->next->next)
+ {
+ if (elt->len == strlen(propname)
+ && strncmp(propname, elt->data, elt->len) == 0)
+ {
+ *propval = svn_string_ncreate(elt->next->data, elt->next->len,
+ pool);
+ break;
+ }
+ else
+ {
+ continue;
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_skel__unparse_proplist(svn_skel_t **skel_p,
+ const apr_hash_t *proplist,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(pool);
+ apr_hash_index_t *hi;
+
+ /* Create the skel. */
+ if (proplist)
+ {
+ /* Loop over hash entries */
+ for (hi = apr_hash_first(pool, (apr_hash_t *)proplist); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+ svn_string_t *value;
+
+ apr_hash_this(hi, &key, &klen, &val);
+ value = val;
+
+ /* VALUE */
+ svn_skel__prepend(svn_skel__mem_atom(value->data, value->len, pool),
+ skel);
+
+ /* NAME */
+ svn_skel__prepend(svn_skel__mem_atom(key, klen, pool), skel);
+ }
+ }
+
+ /* Validate and return the skel. */
+ if (! is_valid_proplist_skel(skel))
+ return skel_err("proplist");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_skel__unparse_iproplist(svn_skel_t **skel_p,
+ const apr_array_header_t *inherited_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(result_pool);
+
+ /* Create the skel. */
+ if (inherited_props)
+ {
+ int i;
+ apr_hash_index_t *hi;
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_skel_t *skel_list = svn_skel__make_empty_list(result_pool);
+ svn_skel_t *skel_atom;
+
+ /* Loop over hash entries */
+ for (hi = apr_hash_first(scratch_pool, iprop->prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+ svn_string_t *value;
+
+ apr_hash_this(hi, &key, &klen, &val);
+ value = val;
+
+ /* VALUE */
+ svn_skel__prepend(svn_skel__mem_atom(value->data, value->len,
+ result_pool), skel_list);
+
+ /* NAME */
+ svn_skel__prepend(svn_skel__mem_atom(key, klen, result_pool),
+ skel_list);
+ }
+
+ skel_atom = svn_skel__str_atom(
+ apr_pstrdup(result_pool, iprop->path_or_url), result_pool);
+ svn_skel__append(skel, skel_atom);
+ svn_skel__append(skel, skel_list);
+ }
+ }
+
+ /* Validate and return the skel. */
+ if (! is_valid_iproplist_skel(skel))
+ return skel_err("iproplist");
+
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/sorts.c b/contrib/subversion/subversion/libsvn_subr/sorts.c
new file mode 100644
index 0000000..bdec8e4
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/sorts.c
@@ -0,0 +1,309 @@
+/*
+ * sorts.c: all sorts of sorts
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <stdlib.h> /* for qsort() */
+#include <assert.h>
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_sorts.h"
+#include "svn_error.h"
+
+
+
+/*** svn_sort__hash() ***/
+
+/* (Should this be a permanent part of APR?)
+
+ OK, folks, here's what's going on. APR hash tables hash on
+ key/klen objects, and store associated generic values. They work
+ great, but they have no ordering.
+
+ The point of this exercise is to somehow arrange a hash's keys into
+ an "ordered list" of some kind -- in this case, a nicely sorted
+ one.
+
+ We're using APR arrays, therefore, because that's what they are:
+ ordered lists. However, what "keys" should we put in the array?
+ Clearly, (const char *) objects aren't general enough. Or rather,
+ they're not as general as APR's hash implementation, which stores
+ (void *)/length as keys. We don't want to lose this information.
+
+ Therefore, it makes sense to store pointers to {void *, size_t}
+ structures in our array. No such apr object exists... BUT... if we
+ can use a new type svn_sort__item_t which contains {char *, size_t, void
+ *}. If store these objects in our array, we get the hash value
+ *for free*. When looping over the final array, we don't need to
+ call apr_hash_get(). Major bonus!
+ */
+
+
+int
+svn_sort_compare_items_as_paths(const svn_sort__item_t *a,
+ const svn_sort__item_t *b)
+{
+ const char *astr, *bstr;
+
+ astr = a->key;
+ bstr = b->key;
+ assert(astr[a->klen] == '\0');
+ assert(bstr[b->klen] == '\0');
+ return svn_path_compare_paths(astr, bstr);
+}
+
+
+int
+svn_sort_compare_items_lexically(const svn_sort__item_t *a,
+ const svn_sort__item_t *b)
+{
+ int val;
+ apr_size_t len;
+
+ /* Compare bytes of a's key and b's key up to the common length. */
+ len = (a->klen < b->klen) ? a->klen : b->klen;
+ val = memcmp(a->key, b->key, len);
+ if (val != 0)
+ return val;
+
+ /* They match up until one of them ends; whichever is longer is greater. */
+ return (a->klen < b->klen) ? -1 : (a->klen > b->klen) ? 1 : 0;
+}
+
+
+int
+svn_sort_compare_revisions(const void *a, const void *b)
+{
+ svn_revnum_t a_rev = *(const svn_revnum_t *)a;
+ svn_revnum_t b_rev = *(const svn_revnum_t *)b;
+
+ if (a_rev == b_rev)
+ return 0;
+
+ return a_rev < b_rev ? 1 : -1;
+}
+
+
+int
+svn_sort_compare_paths(const void *a, const void *b)
+{
+ const char *item1 = *((const char * const *) a);
+ const char *item2 = *((const char * const *) b);
+
+ return svn_path_compare_paths(item1, item2);
+}
+
+
+int
+svn_sort_compare_ranges(const void *a, const void *b)
+{
+ const svn_merge_range_t *item1 = *((const svn_merge_range_t * const *) a);
+ const svn_merge_range_t *item2 = *((const svn_merge_range_t * const *) b);
+
+ if (item1->start == item2->start
+ && item1->end == item2->end)
+ return 0;
+
+ if (item1->start == item2->start)
+ return item1->end < item2->end ? -1 : 1;
+
+ return item1->start < item2->start ? -1 : 1;
+}
+
+apr_array_header_t *
+svn_sort__hash(apr_hash_t *ht,
+ int (*comparison_func)(const svn_sort__item_t *,
+ const svn_sort__item_t *),
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_array_header_t *ary;
+ svn_boolean_t sorted;
+ svn_sort__item_t *prev_item;
+
+ /* allocate an array with enough elements to hold all the keys. */
+ ary = apr_array_make(pool, apr_hash_count(ht), sizeof(svn_sort__item_t));
+
+ /* loop over hash table and push all keys into the array */
+ sorted = TRUE;
+ prev_item = NULL;
+ for (hi = apr_hash_first(pool, ht); hi; hi = apr_hash_next(hi))
+ {
+ svn_sort__item_t *item = apr_array_push(ary);
+
+ apr_hash_this(hi, &item->key, &item->klen, &item->value);
+
+ if (prev_item == NULL)
+ {
+ prev_item = item;
+ continue;
+ }
+
+ if (sorted)
+ {
+ sorted = (comparison_func(prev_item, item) < 0);
+ prev_item = item;
+ }
+ }
+
+ /* quicksort the array if it isn't already sorted. */
+ if (!sorted)
+ qsort(ary->elts, ary->nelts, ary->elt_size,
+ (int (*)(const void *, const void *))comparison_func);
+
+ return ary;
+}
+
+/* Return the lowest index at which the element *KEY should be inserted into
+ the array at BASE which has NELTS elements of size ELT_SIZE bytes each,
+ according to the ordering defined by COMPARE_FUNC.
+ 0 <= NELTS <= INT_MAX, 1 <= ELT_SIZE <= INT_MAX.
+ The array must already be sorted in the ordering defined by COMPARE_FUNC.
+ COMPARE_FUNC is defined as for the C stdlib function bsearch().
+ Note: This function is modeled on bsearch() and on lower_bound() in the
+ C++ STL.
+ */
+static int
+bsearch_lower_bound(const void *key,
+ const void *base,
+ int nelts,
+ int elt_size,
+ int (*compare_func)(const void *, const void *))
+{
+ int lower = 0;
+ int upper = nelts - 1;
+
+ /* Binary search for the lowest position at which to insert KEY. */
+ while (lower <= upper)
+ {
+ int try = lower + (upper - lower) / 2; /* careful to avoid overflow */
+ int cmp = compare_func((const char *)base + try * elt_size, key);
+
+ if (cmp < 0)
+ lower = try + 1;
+ else
+ upper = try - 1;
+ }
+ assert(lower == upper + 1);
+
+ return lower;
+}
+
+int
+svn_sort__bsearch_lower_bound(const void *key,
+ const apr_array_header_t *array,
+ int (*compare_func)(const void *, const void *))
+{
+ return bsearch_lower_bound(key,
+ array->elts, array->nelts, array->elt_size,
+ compare_func);
+}
+
+void
+svn_sort__array_insert(const void *new_element,
+ apr_array_header_t *array,
+ int insert_index)
+{
+ int elements_to_move;
+ char *new_position;
+
+ assert(0 <= insert_index && insert_index <= array->nelts);
+ elements_to_move = array->nelts - insert_index; /* before bumping nelts */
+
+ /* Grow the array, allocating a new space at the end. Note: this can
+ reallocate the array's "elts" at a different address. */
+ apr_array_push(array);
+
+ /* Move the elements after INSERT_INDEX along. (When elements_to_move == 0,
+ this is a no-op.) */
+ new_position = (char *)array->elts + insert_index * array->elt_size;
+ memmove(new_position + array->elt_size, new_position,
+ array->elt_size * elements_to_move);
+
+ /* Copy in the new element */
+ memcpy(new_position, new_element, array->elt_size);
+}
+
+void
+svn_sort__array_delete(apr_array_header_t *arr,
+ int delete_index,
+ int elements_to_delete)
+{
+ /* Do we have a valid index and are there enough elements? */
+ if (delete_index >= 0
+ && delete_index < arr->nelts
+ && elements_to_delete > 0
+ && (elements_to_delete + delete_index) <= arr->nelts)
+ {
+ /* If we are not deleting a block of elements that extends to the end
+ of the array, then we need to move the remaining elements to keep
+ the array contiguous. */
+ if ((elements_to_delete + delete_index) < arr->nelts)
+ memmove(
+ arr->elts + arr->elt_size * delete_index,
+ arr->elts + (arr->elt_size * (delete_index + elements_to_delete)),
+ arr->elt_size * (arr->nelts - elements_to_delete - delete_index));
+
+ /* Delete the last ELEMENTS_TO_DELETE elements. */
+ arr->nelts -= elements_to_delete;
+ }
+}
+
+void
+svn_sort__array_reverse(apr_array_header_t *array,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ if (array->elt_size == sizeof(void *))
+ {
+ for (i = 0; i < array->nelts / 2; i++)
+ {
+ int swap_index = array->nelts - i - 1;
+ void *tmp = APR_ARRAY_IDX(array, i, void *);
+
+ APR_ARRAY_IDX(array, i, void *) =
+ APR_ARRAY_IDX(array, swap_index, void *);
+ APR_ARRAY_IDX(array, swap_index, void *) = tmp;
+ }
+ }
+ else
+ {
+ apr_size_t sz = array->elt_size;
+ char *tmp = apr_palloc(scratch_pool, sz);
+
+ for (i = 0; i < array->nelts / 2; i++)
+ {
+ int swap_index = array->nelts - i - 1;
+ char *x = array->elts + (sz * i);
+ char *y = array->elts + (sz * swap_index);
+
+ memcpy(tmp, x, sz);
+ memcpy(x, y, sz);
+ memcpy(y, tmp, sz);
+ }
+ }
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/spillbuf.c b/contrib/subversion/subversion/libsvn_subr/spillbuf.c
new file mode 100644
index 0000000..e028741
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/spillbuf.c
@@ -0,0 +1,615 @@
+/*
+ * spillbuf.c : an in-memory buffer that can spill to disk
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_file_io.h>
+
+#include "svn_io.h"
+#include "svn_pools.h"
+
+#include "private/svn_subr_private.h"
+
+
+struct memblock_t {
+ apr_size_t size;
+ char *data;
+
+ struct memblock_t *next;
+};
+
+
+struct svn_spillbuf_t {
+ /* Pool for allocating blocks and the spill file. */
+ apr_pool_t *pool;
+
+ /* Size of in-memory blocks. */
+ apr_size_t blocksize;
+
+ /* Maximum in-memory size; start spilling when we reach this size. */
+ apr_size_t maxsize;
+
+ /* The amount of content in memory. */
+ apr_size_t memory_size;
+
+ /* HEAD points to the first block of the linked list of buffers.
+ TAIL points to the last block, for quickly appending more blocks
+ to the overall list. */
+ struct memblock_t *head;
+ struct memblock_t *tail;
+
+ /* Available blocks for storing pending data. These were allocated
+ previously, then the data consumed and returned to this list. */
+ struct memblock_t *avail;
+
+ /* When a block is borrowed for reading, it is listed here. */
+ struct memblock_t *out_for_reading;
+
+ /* Once MEMORY_SIZE exceeds SPILL_SIZE, then arriving content will be
+ appended to the (temporary) file indicated by SPILL. */
+ apr_file_t *spill;
+
+ /* As we consume content from SPILL, this value indicates where we
+ will begin reading. */
+ apr_off_t spill_start;
+
+ /* How much content remains in SPILL. */
+ svn_filesize_t spill_size;
+};
+
+
+struct svn_spillbuf_reader_t {
+ /* Embed the spill-buffer within the reader. */
+ struct svn_spillbuf_t buf;
+
+ /* When we read content from the underlying spillbuf, these fields store
+ the ptr/len pair. The ptr will be incremented as we "read" out of this
+ buffer since we don't have to retain the original pointer (it is
+ managed inside of the spillbuf). */
+ const char *sb_ptr;
+ apr_size_t sb_len;
+
+ /* If a write comes in, then we may need to save content from our
+ borrowed buffer (since that buffer may be destroyed by our call into
+ the spillbuf code). Note that we retain the original pointer since
+ this buffer is allocated by the reader code and re-used. The SAVE_POS
+ field indicates the current position within this save buffer. The
+ SAVE_LEN field describes how much content is present. */
+ char *save_ptr;
+ apr_size_t save_len;
+ apr_size_t save_pos;
+};
+
+
+svn_spillbuf_t *
+svn_spillbuf__create(apr_size_t blocksize,
+ apr_size_t maxsize,
+ apr_pool_t *result_pool)
+{
+ svn_spillbuf_t *buf = apr_pcalloc(result_pool, sizeof(*buf));
+
+ buf->pool = result_pool;
+ buf->blocksize = blocksize;
+ buf->maxsize = maxsize;
+ /* Note: changes here should also go into svn_spillbuf__reader_create() */
+
+ return buf;
+}
+
+
+svn_filesize_t
+svn_spillbuf__get_size(const svn_spillbuf_t *buf)
+{
+ return buf->memory_size + buf->spill_size;
+}
+
+
+/* Get a memblock from the spill-buffer. It will be the block that we
+ passed out for reading, come from the free list, or allocated. */
+static struct memblock_t *
+get_buffer(svn_spillbuf_t *buf)
+{
+ struct memblock_t *mem = buf->out_for_reading;
+
+ if (mem != NULL)
+ {
+ buf->out_for_reading = NULL;
+ return mem;
+ }
+
+ if (buf->avail == NULL)
+ {
+ mem = apr_palloc(buf->pool, sizeof(*mem));
+ mem->data = apr_palloc(buf->pool, buf->blocksize);
+ return mem;
+ }
+
+ mem = buf->avail;
+ buf->avail = mem->next;
+ return mem;
+}
+
+
+/* Return MEM to the list of available buffers in BUF. */
+static void
+return_buffer(svn_spillbuf_t *buf,
+ struct memblock_t *mem)
+{
+ mem->next = buf->avail;
+ buf->avail = mem;
+}
+
+
+svn_error_t *
+svn_spillbuf__write(svn_spillbuf_t *buf,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ struct memblock_t *mem;
+
+ /* We do not (yet) have a spill file, but the amount stored in memory
+ will grow too large. Create the file and place the pending data into
+ the temporary file. */
+ if (buf->spill == NULL
+ && (buf->memory_size + len) > buf->maxsize)
+ {
+ SVN_ERR(svn_io_open_unique_file3(&buf->spill,
+ NULL /* temp_path */,
+ NULL /* dirpath */,
+ svn_io_file_del_on_close,
+ buf->pool, scratch_pool));
+ }
+
+ /* Once a spill file has been constructed, then we need to put all
+ arriving data into the file. We will no longer attempt to hold it
+ in memory. */
+ if (buf->spill != NULL)
+ {
+ apr_off_t output_unused = 0; /* ### stupid API */
+
+ /* Seek to the end of the spill file. We don't know if a read has
+ occurred since our last write, and moved the file position. */
+ SVN_ERR(svn_io_file_seek(buf->spill,
+ APR_END, &output_unused,
+ scratch_pool));
+
+ SVN_ERR(svn_io_file_write_full(buf->spill, data, len,
+ NULL, scratch_pool));
+ buf->spill_size += len;
+
+ return SVN_NO_ERROR;
+ }
+
+ while (len > 0)
+ {
+ apr_size_t amt;
+
+ if (buf->tail == NULL || buf->tail->size == buf->blocksize)
+ {
+ /* There is no existing memblock (that may have space), or the
+ tail memblock has no space, so we need a new memblock. */
+ mem = get_buffer(buf);
+ mem->size = 0;
+ mem->next = NULL;
+ }
+ else
+ {
+ mem = buf->tail;
+ }
+
+ /* Compute how much to write into the memblock. */
+ amt = buf->blocksize - mem->size;
+ if (amt > len)
+ amt = len;
+
+ /* Copy some data into this memblock. */
+ memcpy(&mem->data[mem->size], data, amt);
+ mem->size += amt;
+ data += amt;
+ len -= amt;
+
+ /* We need to record how much is buffered in memory. Once we reach
+ buf->maxsize (or thereabouts, it doesn't have to be precise), then
+ we'll switch to putting the content into a file. */
+ buf->memory_size += amt;
+
+ /* Start a list of buffers, or (if we're not writing into the tail)
+ append to the end of the linked list of buffers. */
+ if (buf->tail == NULL)
+ {
+ buf->head = mem;
+ buf->tail = mem;
+ }
+ else if (mem != buf->tail)
+ {
+ buf->tail->next = mem;
+ buf->tail = mem;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return a memblock of content, if any is available. *mem will be NULL if
+ no further content is available. The memblock should eventually be
+ passed to return_buffer() (or stored into buf->out_for_reading which
+ will grab that block at the next get_buffer() call). */
+static svn_error_t *
+read_data(struct memblock_t **mem,
+ svn_spillbuf_t *buf,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ /* If we have some in-memory blocks, then return one. */
+ if (buf->head != NULL)
+ {
+ *mem = buf->head;
+ if (buf->tail == *mem)
+ buf->head = buf->tail = NULL;
+ else
+ buf->head = (*mem)->next;
+
+ /* We're using less memory now. If we haven't hit the spill file,
+ then we may be able to keep using memory. */
+ buf->memory_size -= (*mem)->size;
+
+ return SVN_NO_ERROR;
+ }
+
+ /* No file? Done. */
+ if (buf->spill == NULL)
+ {
+ *mem = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Assume that the caller has seeked the spill file to the correct pos. */
+
+ /* Get a buffer that we can read content into. */
+ *mem = get_buffer(buf);
+ /* NOTE: mem's size/next are uninitialized. */
+
+ if ((apr_uint64_t)buf->spill_size < (apr_uint64_t)buf->blocksize)
+ (*mem)->size = (apr_size_t)buf->spill_size;
+ else
+ (*mem)->size = buf->blocksize; /* The size of (*mem)->data */
+ (*mem)->next = NULL;
+
+ /* Read some data from the spill file into the memblock. */
+ err = svn_io_file_read(buf->spill, (*mem)->data, &(*mem)->size,
+ scratch_pool);
+ if (err)
+ {
+ return_buffer(buf, *mem);
+ return svn_error_trace(err);
+ }
+
+ /* Mark the data that we consumed from the spill file. */
+ buf->spill_start += (*mem)->size;
+
+ /* Did we consume all the data from the spill file? */
+ if ((buf->spill_size -= (*mem)->size) == 0)
+ {
+ /* Close and reset our spill file information. */
+ SVN_ERR(svn_io_file_close(buf->spill, scratch_pool));
+ buf->spill = NULL;
+ buf->spill_start = 0;
+ }
+
+ /* *mem has been initialized. Done. */
+ return SVN_NO_ERROR;
+}
+
+
+/* If the next read would consume data from the file, then seek to the
+ correct position. */
+static svn_error_t *
+maybe_seek(svn_boolean_t *seeked,
+ const svn_spillbuf_t *buf,
+ apr_pool_t *scratch_pool)
+{
+ if (buf->head == NULL && buf->spill != NULL)
+ {
+ apr_off_t output_unused;
+
+ /* Seek to where we left off reading. */
+ output_unused = buf->spill_start; /* ### stupid API */
+ SVN_ERR(svn_io_file_seek(buf->spill,
+ APR_SET, &output_unused,
+ scratch_pool));
+ if (seeked != NULL)
+ *seeked = TRUE;
+ }
+ else if (seeked != NULL)
+ {
+ *seeked = FALSE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_spillbuf__read(const char **data,
+ apr_size_t *len,
+ svn_spillbuf_t *buf,
+ apr_pool_t *scratch_pool)
+{
+ struct memblock_t *mem;
+
+ /* Possibly seek... */
+ SVN_ERR(maybe_seek(NULL, buf, scratch_pool));
+
+ SVN_ERR(read_data(&mem, buf, scratch_pool));
+ if (mem == NULL)
+ {
+ *data = NULL;
+ *len = 0;
+ }
+ else
+ {
+ *data = mem->data;
+ *len = mem->size;
+
+ /* If a block was out for reading, then return it. */
+ if (buf->out_for_reading != NULL)
+ return_buffer(buf, buf->out_for_reading);
+
+ /* Remember that we've passed this block out for reading. */
+ buf->out_for_reading = mem;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_spillbuf__process(svn_boolean_t *exhausted,
+ svn_spillbuf_t *buf,
+ svn_spillbuf_read_t read_func,
+ void *read_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t has_seeked = FALSE;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ *exhausted = FALSE;
+
+ while (TRUE)
+ {
+ struct memblock_t *mem;
+ svn_error_t *err;
+ svn_boolean_t stop;
+
+ svn_pool_clear(iterpool);
+
+ /* If this call to read_data() will read from the spill file, and we
+ have not seek'd the file... then do it now. */
+ if (!has_seeked)
+ SVN_ERR(maybe_seek(&has_seeked, buf, iterpool));
+
+ /* Get some content to pass to the read callback. */
+ SVN_ERR(read_data(&mem, buf, iterpool));
+ if (mem == NULL)
+ {
+ *exhausted = TRUE;
+ break;
+ }
+
+ err = read_func(&stop, read_baton, mem->data, mem->size, iterpool);
+
+ return_buffer(buf, mem);
+
+ if (err)
+ return svn_error_trace(err);
+
+ /* If the callbacks told us to stop, then we're done for now. */
+ if (stop)
+ break;
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_spillbuf_reader_t *
+svn_spillbuf__reader_create(apr_size_t blocksize,
+ apr_size_t maxsize,
+ apr_pool_t *result_pool)
+{
+ svn_spillbuf_reader_t *sbr = apr_pcalloc(result_pool, sizeof(*sbr));
+
+ /* See svn_spillbuf__create() */
+ sbr->buf.pool = result_pool;
+ sbr->buf.blocksize = blocksize;
+ sbr->buf.maxsize = maxsize;
+
+ return sbr;
+}
+
+
+svn_error_t *
+svn_spillbuf__reader_read(apr_size_t *amt,
+ svn_spillbuf_reader_t *reader,
+ char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ if (len == 0)
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, NULL);
+
+ *amt = 0;
+
+ while (len > 0)
+ {
+ apr_size_t copy_amt;
+
+ if (reader->save_len > 0)
+ {
+ /* We have some saved content, so use this first. */
+
+ if (len < reader->save_len)
+ copy_amt = len;
+ else
+ copy_amt = reader->save_len;
+
+ memcpy(data, reader->save_ptr + reader->save_pos, copy_amt);
+ reader->save_pos += copy_amt;
+ reader->save_len -= copy_amt;
+ }
+ else
+ {
+ /* No saved content. We should now copy from spillbuf-provided
+ buffers of content. */
+
+ /* We may need more content from the spillbuf. */
+ if (reader->sb_len == 0)
+ {
+ SVN_ERR(svn_spillbuf__read(&reader->sb_ptr, &reader->sb_len,
+ &reader->buf,
+ scratch_pool));
+
+ /* We've run out of content, so return with whatever has
+ been copied into DATA and stored into AMT. */
+ if (reader->sb_ptr == NULL)
+ {
+ /* For safety, read() may not have set SB_LEN. We use it
+ as an indicator, so it needs to be cleared. */
+ reader->sb_len = 0;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (len < reader->sb_len)
+ copy_amt = len;
+ else
+ copy_amt = reader->sb_len;
+
+ memcpy(data, reader->sb_ptr, copy_amt);
+ reader->sb_ptr += copy_amt;
+ reader->sb_len -= copy_amt;
+ }
+
+ data += copy_amt;
+ len -= copy_amt;
+ (*amt) += copy_amt;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_spillbuf__reader_getc(char *c,
+ svn_spillbuf_reader_t *reader,
+ apr_pool_t *scratch_pool)
+{
+ apr_size_t amt;
+
+ SVN_ERR(svn_spillbuf__reader_read(&amt, reader, c, 1, scratch_pool));
+ if (amt == 0)
+ return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_spillbuf__reader_write(svn_spillbuf_reader_t *reader,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ /* If we have a buffer of content from the spillbuf, then we need to
+ move that content to a safe place. */
+ if (reader->sb_len > 0)
+ {
+ if (reader->save_ptr == NULL)
+ reader->save_ptr = apr_palloc(reader->buf.pool, reader->buf.blocksize);
+
+ memcpy(reader->save_ptr, reader->sb_ptr, reader->sb_len);
+ reader->save_len = reader->sb_len;
+ reader->save_pos = 0;
+
+ /* No more content in the spillbuf-borrowed buffer. */
+ reader->sb_len = 0;
+ }
+
+ return svn_error_trace(svn_spillbuf__write(&reader->buf, data, len,
+ scratch_pool));
+}
+
+
+struct spillbuf_baton
+{
+ svn_spillbuf_reader_t *reader;
+ apr_pool_t *scratch_pool;
+};
+
+
+static svn_error_t *
+read_handler_spillbuf(void *baton, char *buffer, apr_size_t *len)
+{
+ struct spillbuf_baton *sb = baton;
+
+ SVN_ERR(svn_spillbuf__reader_read(len, sb->reader, buffer, *len,
+ sb->scratch_pool));
+
+ svn_pool_clear(sb->scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+write_handler_spillbuf(void *baton, const char *data, apr_size_t *len)
+{
+ struct spillbuf_baton *sb = baton;
+
+ SVN_ERR(svn_spillbuf__reader_write(sb->reader, data, *len,
+ sb->scratch_pool));
+
+ svn_pool_clear(sb->scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_stream_t *
+svn_stream__from_spillbuf(apr_size_t blocksize,
+ apr_size_t maxsize,
+ apr_pool_t *result_pool)
+{
+ svn_stream_t *stream;
+ struct spillbuf_baton *sb = apr_palloc(result_pool, sizeof(*sb));
+
+ sb->reader = svn_spillbuf__reader_create(blocksize, maxsize, result_pool);
+ sb->scratch_pool = svn_pool_create(result_pool);
+
+ stream = svn_stream_create(sb, result_pool);
+
+ svn_stream_set_read(stream, read_handler_spillbuf);
+ svn_stream_set_write(stream, write_handler_spillbuf);
+
+ return stream;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/sqlite.c b/contrib/subversion/subversion/libsvn_subr/sqlite.c
new file mode 100644
index 0000000..0afceff
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/sqlite.c
@@ -0,0 +1,1294 @@
+/* sqlite.c
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_io.h"
+#include "svn_dirent_uri.h"
+#include "svn_checksum.h"
+
+#include "internal_statements.h"
+
+#include "private/svn_sqlite.h"
+#include "svn_private_config.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_atomic.h"
+#include "private/svn_skel.h"
+#include "private/svn_token.h"
+
+#ifdef SQLITE3_DEBUG
+#include "private/svn_debug.h"
+#endif
+
+#ifdef SVN_SQLITE_INLINE
+/* Import the sqlite3 API vtable from sqlite3wrapper.c */
+# define SQLITE_OMIT_DEPRECATED
+# include <sqlite3ext.h>
+extern const sqlite3_api_routines *const svn_sqlite3__api_funcs;
+extern int (*const svn_sqlite3__api_initialize)(void);
+extern int (*const svn_sqlite3__api_config)(int, ...);
+# define sqlite3_api svn_sqlite3__api_funcs
+# define sqlite3_initialize svn_sqlite3__api_initialize
+# define sqlite3_config svn_sqlite3__api_config
+#else
+# include <sqlite3.h>
+#endif
+
+#if !SQLITE_VERSION_AT_LEAST(3,7,12)
+#error SQLite is too old -- version 3.7.12 is the minimum required version
+#endif
+
+const char *
+svn_sqlite__compiled_version(void)
+{
+ static const char sqlite_version[] = SQLITE_VERSION;
+ return sqlite_version;
+}
+
+const char *
+svn_sqlite__runtime_version(void)
+{
+ return sqlite3_libversion();
+}
+
+
+INTERNAL_STATEMENTS_SQL_DECLARE_STATEMENTS(internal_statements);
+
+
+#ifdef SQLITE3_DEBUG
+/* An sqlite query execution callback. */
+static void
+sqlite_tracer(void *data, const char *sql)
+{
+ /* sqlite3 *db3 = data; */
+ SVN_DBG(("sql=\"%s\"\n", sql));
+}
+#endif
+
+#ifdef SQLITE3_PROFILE
+/* An sqlite execution timing callback. */
+static void
+sqlite_profiler(void *data, const char *sql, sqlite3_uint64 duration)
+{
+ /* sqlite3 *db3 = data; */
+ SVN_DBG(("[%.3f] sql=\"%s\"\n", 1e-9 * duration, sql));
+}
+#endif
+
+struct svn_sqlite__db_t
+{
+ sqlite3 *db3;
+ const char * const *statement_strings;
+ int nbr_statements;
+ svn_sqlite__stmt_t **prepared_stmts;
+ apr_pool_t *state_pool;
+};
+
+struct svn_sqlite__stmt_t
+{
+ sqlite3_stmt *s3stmt;
+ svn_sqlite__db_t *db;
+ svn_boolean_t needs_reset;
+};
+
+struct svn_sqlite__context_t
+{
+ sqlite3_context *context;
+};
+
+struct svn_sqlite__value_t
+{
+ sqlite3_value *value;
+};
+
+
+/* Convert SQLite error codes to SVN. Evaluates X multiple times */
+#define SQLITE_ERROR_CODE(x) ((x) == SQLITE_READONLY \
+ ? SVN_ERR_SQLITE_READONLY \
+ : ((x) == SQLITE_BUSY \
+ ? SVN_ERR_SQLITE_BUSY \
+ : ((x) == SQLITE_CONSTRAINT \
+ ? SVN_ERR_SQLITE_CONSTRAINT \
+ : SVN_ERR_SQLITE_ERROR)))
+
+
+/* SQLITE->SVN quick error wrap, much like SVN_ERR. */
+#define SQLITE_ERR(x, db) do \
+{ \
+ int sqlite_err__temp = (x); \
+ if (sqlite_err__temp != SQLITE_OK) \
+ return svn_error_createf(SQLITE_ERROR_CODE(sqlite_err__temp), \
+ NULL, "sqlite: %s (S%d)", \
+ sqlite3_errmsg((db)->db3), \
+ sqlite_err__temp); \
+} while (0)
+
+#define SQLITE_ERR_MSG(x, msg) do \
+{ \
+ int sqlite_err__temp = (x); \
+ if (sqlite_err__temp != SQLITE_OK) \
+ return svn_error_createf(SQLITE_ERROR_CODE(sqlite_err__temp), \
+ NULL, "sqlite: %s (S%d)", (msg), \
+ sqlite_err__temp); \
+} while (0)
+
+
+/* Time (in milliseconds) to wait for sqlite locks before giving up. */
+#define BUSY_TIMEOUT 10000
+
+
+/* Convenience wrapper around exec_sql2(). */
+#define exec_sql(db, sql) exec_sql2((db), (sql), SQLITE_OK)
+
+/* Run the statement SQL on DB, ignoring SQLITE_OK and IGNORED_ERR.
+ (Note: the IGNORED_ERR parameter itself is not ignored.) */
+static svn_error_t *
+exec_sql2(svn_sqlite__db_t *db, const char *sql, int ignored_err)
+{
+ char *err_msg;
+ int sqlite_err = sqlite3_exec(db->db3, sql, NULL, NULL, &err_msg);
+
+ if (sqlite_err != SQLITE_OK && sqlite_err != ignored_err)
+ {
+ svn_error_t *err = svn_error_createf(SQLITE_ERROR_CODE(sqlite_err), NULL,
+ _("sqlite: %s (S%d),"
+ " executing statement '%s'"),
+ err_msg, sqlite_err, sql);
+ sqlite3_free(err_msg);
+ return err;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+prepare_statement(svn_sqlite__stmt_t **stmt, svn_sqlite__db_t *db,
+ const char *text, apr_pool_t *result_pool)
+{
+ *stmt = apr_palloc(result_pool, sizeof(**stmt));
+ (*stmt)->db = db;
+ (*stmt)->needs_reset = FALSE;
+
+ SQLITE_ERR(sqlite3_prepare_v2(db->db3, text, -1, &(*stmt)->s3stmt, NULL), db);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_sqlite__exec_statements(svn_sqlite__db_t *db, int stmt_idx)
+{
+ SVN_ERR_ASSERT(stmt_idx < db->nbr_statements);
+
+ return svn_error_trace(exec_sql(db, db->statement_strings[stmt_idx]));
+}
+
+
+svn_error_t *
+svn_sqlite__get_statement(svn_sqlite__stmt_t **stmt, svn_sqlite__db_t *db,
+ int stmt_idx)
+{
+ SVN_ERR_ASSERT(stmt_idx < db->nbr_statements);
+
+ if (db->prepared_stmts[stmt_idx] == NULL)
+ SVN_ERR(prepare_statement(&db->prepared_stmts[stmt_idx], db,
+ db->statement_strings[stmt_idx],
+ db->state_pool));
+
+ *stmt = db->prepared_stmts[stmt_idx];
+
+ if ((*stmt)->needs_reset)
+ return svn_error_trace(svn_sqlite__reset(*stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Like svn_sqlite__get_statement but gets an internal statement.
+
+ All internal statements that use this api are executed with step_done(),
+ so we don't need the fallback reset handling here or in the pool cleanup */
+static svn_error_t *
+get_internal_statement(svn_sqlite__stmt_t **stmt, svn_sqlite__db_t *db,
+ int stmt_idx)
+{
+ /* The internal statements are stored after the registered statements */
+ int prep_idx = db->nbr_statements + stmt_idx;
+ SVN_ERR_ASSERT(stmt_idx < STMT_INTERNAL_LAST);
+
+ if (db->prepared_stmts[prep_idx] == NULL)
+ SVN_ERR(prepare_statement(&db->prepared_stmts[prep_idx], db,
+ internal_statements[stmt_idx],
+ db->state_pool));
+
+ *stmt = db->prepared_stmts[prep_idx];
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+step_with_expectation(svn_sqlite__stmt_t* stmt,
+ svn_boolean_t expecting_row)
+{
+ svn_boolean_t got_row;
+
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ if ((got_row && !expecting_row)
+ ||
+ (!got_row && expecting_row))
+ return svn_error_create(SVN_ERR_SQLITE_ERROR,
+ svn_sqlite__reset(stmt),
+ expecting_row
+ ? _("sqlite: Expected database row missing")
+ : _("sqlite: Extra database row found"));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__step_done(svn_sqlite__stmt_t *stmt)
+{
+ SVN_ERR(step_with_expectation(stmt, FALSE));
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_sqlite__step_row(svn_sqlite__stmt_t *stmt)
+{
+ return svn_error_trace(step_with_expectation(stmt, TRUE));
+}
+
+
+svn_error_t *
+svn_sqlite__step(svn_boolean_t *got_row, svn_sqlite__stmt_t *stmt)
+{
+ int sqlite_result = sqlite3_step(stmt->s3stmt);
+
+ if (sqlite_result != SQLITE_DONE && sqlite_result != SQLITE_ROW)
+ {
+ svn_error_t *err1, *err2;
+
+ err1 = svn_error_createf(SQLITE_ERROR_CODE(sqlite_result), NULL,
+ "sqlite: %s (S%d)",
+ sqlite3_errmsg(stmt->db->db3), sqlite_result);
+ err2 = svn_sqlite__reset(stmt);
+ return svn_error_compose_create(err1, err2);
+ }
+
+ *got_row = (sqlite_result == SQLITE_ROW);
+ stmt->needs_reset = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__insert(apr_int64_t *row_id, svn_sqlite__stmt_t *stmt)
+{
+ svn_boolean_t got_row;
+
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ if (row_id)
+ *row_id = sqlite3_last_insert_rowid(stmt->db->db3);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_sqlite__update(int *affected_rows, svn_sqlite__stmt_t *stmt)
+{
+ SVN_ERR(step_with_expectation(stmt, FALSE));
+
+ if (affected_rows)
+ *affected_rows = sqlite3_changes(stmt->db->db3);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+static svn_error_t *
+vbindf(svn_sqlite__stmt_t *stmt, const char *fmt, va_list ap)
+{
+ int count;
+
+ for (count = 1; *fmt; fmt++, count++)
+ {
+ const void *blob;
+ apr_size_t blob_size;
+ const svn_token_map_t *map;
+
+ switch (*fmt)
+ {
+ case 's':
+ SVN_ERR(svn_sqlite__bind_text(stmt, count,
+ va_arg(ap, const char *)));
+ break;
+
+ case 'd':
+ SVN_ERR(svn_sqlite__bind_int(stmt, count,
+ va_arg(ap, int)));
+ break;
+
+ case 'i':
+ case 'L':
+ SVN_ERR(svn_sqlite__bind_int64(stmt, count,
+ va_arg(ap, apr_int64_t)));
+ break;
+
+ case 'b':
+ blob = va_arg(ap, const void *);
+ blob_size = va_arg(ap, apr_size_t);
+ SVN_ERR(svn_sqlite__bind_blob(stmt, count, blob, blob_size));
+ break;
+
+ case 'r':
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, count,
+ va_arg(ap, svn_revnum_t)));
+ break;
+
+ case 't':
+ map = va_arg(ap, const svn_token_map_t *);
+ SVN_ERR(svn_sqlite__bind_token(stmt, count, map, va_arg(ap, int)));
+ break;
+
+ case 'n':
+ /* Skip this column: no binding */
+ break;
+
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__bindf(svn_sqlite__stmt_t *stmt, const char *fmt, ...)
+{
+ svn_error_t *err;
+ va_list ap;
+
+ va_start(ap, fmt);
+ err = vbindf(stmt, fmt, ap);
+ va_end(ap);
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_sqlite__bind_int(svn_sqlite__stmt_t *stmt,
+ int slot,
+ int val)
+{
+ SQLITE_ERR(sqlite3_bind_int(stmt->s3stmt, slot, val), stmt->db);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__bind_int64(svn_sqlite__stmt_t *stmt,
+ int slot,
+ apr_int64_t val)
+{
+ SQLITE_ERR(sqlite3_bind_int64(stmt->s3stmt, slot, val), stmt->db);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__bind_text(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const char *val)
+{
+ SQLITE_ERR(sqlite3_bind_text(stmt->s3stmt, slot, val, -1, SQLITE_TRANSIENT),
+ stmt->db);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__bind_blob(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const void *val,
+ apr_size_t len)
+{
+ SQLITE_ERR(sqlite3_bind_blob(stmt->s3stmt, slot, val, (int) len,
+ SQLITE_TRANSIENT),
+ stmt->db);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__bind_token(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const svn_token_map_t *map,
+ int value)
+{
+ const char *word = svn_token__to_word(map, value);
+
+ SQLITE_ERR(sqlite3_bind_text(stmt->s3stmt, slot, word, -1, SQLITE_STATIC),
+ stmt->db);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__bind_revnum(svn_sqlite__stmt_t *stmt,
+ int slot,
+ svn_revnum_t value)
+{
+ if (SVN_IS_VALID_REVNUM(value))
+ SQLITE_ERR(sqlite3_bind_int64(stmt->s3stmt, slot,
+ (sqlite_int64)value), stmt->db);
+ else
+ SQLITE_ERR(sqlite3_bind_null(stmt->s3stmt, slot), stmt->db);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__bind_properties(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *skel;
+ svn_stringbuf_t *properties;
+
+ if (props == NULL)
+ return svn_error_trace(svn_sqlite__bind_blob(stmt, slot, NULL, 0));
+
+ SVN_ERR(svn_skel__unparse_proplist(&skel, props, scratch_pool));
+ properties = svn_skel__unparse(skel, scratch_pool);
+ return svn_error_trace(svn_sqlite__bind_blob(stmt,
+ slot,
+ properties->data,
+ properties->len));
+}
+
+svn_error_t *
+svn_sqlite__bind_iprops(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const apr_array_header_t *inherited_props,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *skel;
+ svn_stringbuf_t *properties;
+
+ if (inherited_props == NULL)
+ return svn_error_trace(svn_sqlite__bind_blob(stmt, slot, NULL, 0));
+
+ SVN_ERR(svn_skel__unparse_iproplist(&skel, inherited_props,
+ scratch_pool, scratch_pool));
+ properties = svn_skel__unparse(skel, scratch_pool);
+ return svn_error_trace(svn_sqlite__bind_blob(stmt,
+ slot,
+ properties->data,
+ properties->len));
+}
+
+svn_error_t *
+svn_sqlite__bind_checksum(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const svn_checksum_t *checksum,
+ apr_pool_t *scratch_pool)
+{
+ const char *csum_str;
+
+ if (checksum == NULL)
+ csum_str = NULL;
+ else
+ csum_str = svn_checksum_serialize(checksum, scratch_pool, scratch_pool);
+
+ return svn_error_trace(svn_sqlite__bind_text(stmt, slot, csum_str));
+}
+
+
+const void *
+svn_sqlite__column_blob(svn_sqlite__stmt_t *stmt, int column,
+ apr_size_t *len, apr_pool_t *result_pool)
+{
+ const void *val = sqlite3_column_blob(stmt->s3stmt, column);
+ *len = sqlite3_column_bytes(stmt->s3stmt, column);
+
+ if (result_pool && val != NULL)
+ val = apr_pmemdup(result_pool, val, *len);
+
+ return val;
+}
+
+const char *
+svn_sqlite__column_text(svn_sqlite__stmt_t *stmt, int column,
+ apr_pool_t *result_pool)
+{
+ /* cast from 'unsigned char' to regular 'char' */
+ const char *result = (const char *)sqlite3_column_text(stmt->s3stmt, column);
+
+ if (result_pool && result != NULL)
+ result = apr_pstrdup(result_pool, result);
+
+ return result;
+}
+
+svn_revnum_t
+svn_sqlite__column_revnum(svn_sqlite__stmt_t *stmt, int column)
+{
+ if (svn_sqlite__column_is_null(stmt, column))
+ return SVN_INVALID_REVNUM;
+ return (svn_revnum_t) sqlite3_column_int64(stmt->s3stmt, column);
+}
+
+svn_boolean_t
+svn_sqlite__column_boolean(svn_sqlite__stmt_t *stmt, int column)
+{
+ return sqlite3_column_int64(stmt->s3stmt, column) != 0;
+}
+
+int
+svn_sqlite__column_int(svn_sqlite__stmt_t *stmt, int column)
+{
+ return sqlite3_column_int(stmt->s3stmt, column);
+}
+
+apr_int64_t
+svn_sqlite__column_int64(svn_sqlite__stmt_t *stmt, int column)
+{
+ return sqlite3_column_int64(stmt->s3stmt, column);
+}
+
+int
+svn_sqlite__column_token(svn_sqlite__stmt_t *stmt,
+ int column,
+ const svn_token_map_t *map)
+{
+ /* cast from 'unsigned char' to regular 'char' */
+ const char *word = (const char *)sqlite3_column_text(stmt->s3stmt, column);
+
+ return svn_token__from_word_strict(map, word);
+}
+
+int
+svn_sqlite__column_token_null(svn_sqlite__stmt_t *stmt,
+ int column,
+ const svn_token_map_t *map,
+ int null_val)
+{
+ /* cast from 'unsigned char' to regular 'char' */
+ const char *word = (const char *)sqlite3_column_text(stmt->s3stmt, column);
+
+ if (!word)
+ return null_val;
+
+ return svn_token__from_word_strict(map, word);
+}
+
+svn_error_t *
+svn_sqlite__column_properties(apr_hash_t **props,
+ svn_sqlite__stmt_t *stmt,
+ int column,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_size_t len;
+ const void *val;
+
+ /* svn_skel__parse_proplist copies everything needed to result_pool */
+ val = svn_sqlite__column_blob(stmt, column, &len, NULL);
+ if (val == NULL)
+ {
+ *props = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_skel__parse_proplist(props,
+ svn_skel__parse(val, len, scratch_pool),
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__column_iprops(apr_array_header_t **iprops,
+ svn_sqlite__stmt_t *stmt,
+ int column,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_size_t len;
+ const void *val;
+
+ /* svn_skel__parse_iprops copies everything needed to result_pool */
+ val = svn_sqlite__column_blob(stmt, column, &len, NULL);
+ if (val == NULL)
+ {
+ *iprops = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_skel__parse_iprops(iprops,
+ svn_skel__parse(val, len, scratch_pool),
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__column_checksum(const svn_checksum_t **checksum,
+ svn_sqlite__stmt_t *stmt, int column,
+ apr_pool_t *result_pool)
+{
+ const char *digest = svn_sqlite__column_text(stmt, column, NULL);
+
+ if (digest == NULL)
+ *checksum = NULL;
+ else
+ SVN_ERR(svn_checksum_deserialize(checksum, digest,
+ result_pool, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_sqlite__column_is_null(svn_sqlite__stmt_t *stmt, int column)
+{
+ return sqlite3_column_type(stmt->s3stmt, column) == SQLITE_NULL;
+}
+
+int
+svn_sqlite__column_bytes(svn_sqlite__stmt_t *stmt, int column)
+{
+ return sqlite3_column_bytes(stmt->s3stmt, column);
+}
+
+svn_error_t *
+svn_sqlite__finalize(svn_sqlite__stmt_t *stmt)
+{
+ SQLITE_ERR(sqlite3_finalize(stmt->s3stmt), stmt->db);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__reset(svn_sqlite__stmt_t *stmt)
+{
+ SQLITE_ERR(sqlite3_reset(stmt->s3stmt), stmt->db);
+ SQLITE_ERR(sqlite3_clear_bindings(stmt->s3stmt), stmt->db);
+ stmt->needs_reset = FALSE;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_sqlite__read_schema_version(int *version,
+ svn_sqlite__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(prepare_statement(&stmt, db, "PRAGMA user_version;", scratch_pool));
+ SVN_ERR(svn_sqlite__step_row(stmt));
+
+ *version = svn_sqlite__column_int(stmt, 0);
+
+ return svn_error_trace(svn_sqlite__finalize(stmt));
+}
+
+
+static volatile svn_atomic_t sqlite_init_state = 0;
+
+/* If possible, verify that SQLite was compiled in a thread-safe
+ manner. */
+/* Don't call this function directly! Use svn_atomic__init_once(). */
+static svn_error_t *
+init_sqlite(void *baton, apr_pool_t *pool)
+{
+ if (sqlite3_libversion_number() < SVN_SQLITE_MIN_VERSION_NUMBER)
+ {
+ return svn_error_createf(
+ SVN_ERR_SQLITE_ERROR, NULL,
+ _("SQLite compiled for %s, but running with %s"),
+ SVN_SQLITE_MIN_VERSION, sqlite3_libversion());
+ }
+
+#if APR_HAS_THREADS
+
+ /* SQLite 3.5 allows verification of its thread-safety at runtime.
+ Older versions are simply expected to have been configured with
+ --enable-threadsafe, which compiles with -DSQLITE_THREADSAFE=1
+ (or -DTHREADSAFE, for older versions). */
+ if (! sqlite3_threadsafe())
+ return svn_error_create(SVN_ERR_SQLITE_ERROR, NULL,
+ _("SQLite is required to be compiled and run in "
+ "thread-safe mode"));
+
+ /* If SQLite has been already initialized, sqlite3_config() returns
+ SQLITE_MISUSE. */
+ {
+ int err = sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
+ if (err != SQLITE_OK && err != SQLITE_MISUSE)
+ return svn_error_createf(SQLITE_ERROR_CODE(err), NULL,
+ _("Could not configure SQLite (S%d)"), err);
+ }
+ SQLITE_ERR_MSG(sqlite3_initialize(), _("Could not initialize SQLite"));
+
+#endif /* APR_HAS_THRADS */
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+internal_open(sqlite3 **db3, const char *path, svn_sqlite__mode_t mode,
+ apr_pool_t *scratch_pool)
+{
+ {
+ int flags;
+
+ if (mode == svn_sqlite__mode_readonly)
+ flags = SQLITE_OPEN_READONLY;
+ else if (mode == svn_sqlite__mode_readwrite)
+ flags = SQLITE_OPEN_READWRITE;
+ else if (mode == svn_sqlite__mode_rwcreate)
+ flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
+ else
+ SVN_ERR_MALFUNCTION();
+
+ /* Turn off SQLite's mutexes. All svn objects are single-threaded,
+ so we can already guarantee that our use of the SQLite handle
+ will be serialized properly.
+
+ Note: in 3.6.x, we've already config'd SQLite into MULTITHREAD mode,
+ so this is probably redundant, but if we are running in a process where
+ somebody initialized SQLite before us it is needed anyway. */
+ flags |= SQLITE_OPEN_NOMUTEX;
+
+ /* Open the database. Note that a handle is returned, even when an error
+ occurs (except for out-of-memory); thus, we can safely use it to
+ extract an error message and construct an svn_error_t. */
+ {
+ /* We'd like to use SQLITE_ERR here, but we can't since it would
+ just return an error and leave the database open. So, we need to
+ do this manually. */
+ /* ### SQLITE_CANTOPEN */
+ int err_code = sqlite3_open_v2(path, db3, flags, NULL);
+ if (err_code != SQLITE_OK)
+ {
+ /* Save the error message before closing the SQLite handle. */
+ char *msg = apr_pstrdup(scratch_pool, sqlite3_errmsg(*db3));
+
+ /* We don't catch the error here, since we care more about the open
+ error than the close error at this point. */
+ sqlite3_close(*db3);
+
+ SQLITE_ERR_MSG(err_code, msg);
+ }
+ }
+ }
+
+ /* Retry until timeout when database is busy. */
+ SQLITE_ERR_MSG(sqlite3_busy_timeout(*db3, BUSY_TIMEOUT),
+ sqlite3_errmsg(*db3));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* APR cleanup function used to close the database when its pool is destroyed.
+ DATA should be the svn_sqlite__db_t handle for the database. */
+static apr_status_t
+close_apr(void *data)
+{
+ svn_sqlite__db_t *db = data;
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_status_t result;
+ int i;
+
+ /* Check to see if we've already closed this database. */
+ if (db->db3 == NULL)
+ return APR_SUCCESS;
+
+ /* Finalize any existing prepared statements. */
+ for (i = 0; i < db->nbr_statements; i++)
+ {
+ if (db->prepared_stmts[i])
+ {
+ if (db->prepared_stmts[i]->needs_reset)
+ {
+#ifdef SVN_DEBUG
+ const char *stmt_text = db->statement_strings[i];
+ stmt_text = stmt_text; /* Provide value for debugger */
+
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+#else
+ err = svn_error_compose_create(
+ err,
+ svn_sqlite__reset(db->prepared_stmts[i]));
+#endif
+ }
+ err = svn_error_compose_create(
+ svn_sqlite__finalize(db->prepared_stmts[i]), err);
+ }
+ }
+ /* And finalize any used internal statements */
+ for (; i < db->nbr_statements + STMT_INTERNAL_LAST; i++)
+ {
+ if (db->prepared_stmts[i])
+ {
+ err = svn_error_compose_create(
+ svn_sqlite__finalize(db->prepared_stmts[i]), err);
+ }
+ }
+
+ result = sqlite3_close(db->db3);
+
+ /* If there's a pre-existing error, return it. */
+ if (err)
+ {
+ result = err->apr_err;
+ svn_error_clear(err);
+ return result;
+ }
+
+ if (result != SQLITE_OK)
+ return SQLITE_ERROR_CODE(result); /* ### lossy */
+
+ db->db3 = NULL;
+
+ return APR_SUCCESS;
+}
+
+
+svn_error_t *
+svn_sqlite__open(svn_sqlite__db_t **db, const char *path,
+ svn_sqlite__mode_t mode, const char * const statements[],
+ int unused1, const char * const *unused2,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_atomic__init_once(&sqlite_init_state,
+ init_sqlite, NULL, scratch_pool));
+
+ *db = apr_pcalloc(result_pool, sizeof(**db));
+
+ SVN_ERR(internal_open(&(*db)->db3, path, mode, scratch_pool));
+
+#ifdef SQLITE3_DEBUG
+ sqlite3_trace((*db)->db3, sqlite_tracer, (*db)->db3);
+#endif
+#ifdef SQLITE3_PROFILE
+ sqlite3_profile((*db)->db3, sqlite_profiler, (*db)->db3);
+#endif
+
+ /* ### simplify this. remnants of some old SQLite compat code. */
+ {
+ int ignored_err = SQLITE_OK;
+
+ SVN_ERR(exec_sql2(*db, "PRAGMA case_sensitive_like=1;", ignored_err));
+ }
+
+ SVN_ERR(exec_sql(*db,
+ /* Disable synchronization to disable the explicit disk flushes
+ that make Sqlite up to 50 times slower; especially on small
+ transactions.
+
+ This removes some stability guarantees on specific hardware
+ and power failures, but still guarantees atomic commits on
+ application crashes. With our dependency on external data
+ like pristine files (Wc) and revision files (repository),
+ we can't keep up these additional guarantees anyway.
+
+ ### Maybe switch to NORMAL(1) when we use larger transaction
+ scopes */
+ "PRAGMA synchronous=OFF;"
+ /* Enable recursive triggers so that a user trigger will fire
+ in the deletion phase of an INSERT OR REPLACE statement.
+ Requires SQLite >= 3.6.18 */
+ "PRAGMA recursive_triggers=ON;"));
+
+#if defined(SVN_DEBUG)
+ /* When running in debug mode, enable the checking of foreign key
+ constraints. This has possible performance implications, so we don't
+ bother to do it for production...for now. */
+ SVN_ERR(exec_sql(*db, "PRAGMA foreign_keys=ON;"));
+#endif
+
+ /* Store temporary tables in RAM instead of in temporary files, but don't
+ fail on this if this option is disabled in the sqlite compilation by
+ setting SQLITE_TEMP_STORE to 0 (always to disk) */
+ svn_error_clear(exec_sql(*db, "PRAGMA temp_store = MEMORY;"));
+
+ /* Store the provided statements. */
+ if (statements)
+ {
+ (*db)->statement_strings = statements;
+ (*db)->nbr_statements = 0;
+ while (*statements != NULL)
+ {
+ statements++;
+ (*db)->nbr_statements++;
+ }
+
+ (*db)->prepared_stmts = apr_pcalloc(
+ result_pool,
+ ((*db)->nbr_statements + STMT_INTERNAL_LAST)
+ * sizeof(svn_sqlite__stmt_t *));
+ }
+ else
+ {
+ (*db)->nbr_statements = 0;
+ (*db)->prepared_stmts = apr_pcalloc(result_pool,
+ (0 + STMT_INTERNAL_LAST)
+ * sizeof(svn_sqlite__stmt_t *));
+ }
+
+ (*db)->state_pool = result_pool;
+ apr_pool_cleanup_register(result_pool, *db, close_apr, apr_pool_cleanup_null);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__close(svn_sqlite__db_t *db)
+{
+ apr_status_t result = apr_pool_cleanup_run(db->state_pool, db, close_apr);
+
+ if (result == APR_SUCCESS)
+ return SVN_NO_ERROR;
+
+ return svn_error_wrap_apr(result, NULL);
+}
+
+static svn_error_t *
+reset_all_statements(svn_sqlite__db_t *db,
+ svn_error_t *error_to_wrap)
+{
+ int i;
+ svn_error_t *err;
+
+ /* ### Should we reorder the errors in this specific case
+ ### to avoid returning the normal error as top level error? */
+
+ err = svn_error_compose_create(error_to_wrap,
+ svn_error_create(SVN_ERR_SQLITE_RESETTING_FOR_ROLLBACK,
+ NULL, NULL));
+
+ for (i = 0; i < db->nbr_statements; i++)
+ if (db->prepared_stmts[i] && db->prepared_stmts[i]->needs_reset)
+ err = svn_error_compose_create(err,
+ svn_sqlite__reset(db->prepared_stmts[i]));
+
+ return err;
+}
+
+svn_error_t *
+svn_sqlite__begin_transaction(svn_sqlite__db_t *db)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(get_internal_statement(&stmt, db,
+ STMT_INTERNAL_BEGIN_TRANSACTION));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__begin_immediate_transaction(svn_sqlite__db_t *db)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(get_internal_statement(&stmt, db,
+ STMT_INTERNAL_BEGIN_IMMEDIATE_TRANSACTION));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__begin_savepoint(svn_sqlite__db_t *db)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(get_internal_statement(&stmt, db,
+ STMT_INTERNAL_SAVEPOINT_SVN));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__finish_transaction(svn_sqlite__db_t *db,
+ svn_error_t *err)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ /* Commit or rollback the sqlite transaction. */
+ if (err)
+ {
+ svn_error_t *err2;
+
+ err2 = get_internal_statement(&stmt, db,
+ STMT_INTERNAL_ROLLBACK_TRANSACTION);
+ if (!err2)
+ err2 = svn_sqlite__step_done(stmt);
+
+ if (err2 && err2->apr_err == SVN_ERR_SQLITE_BUSY)
+ {
+ /* ### Houston, we have a problem!
+
+ We are trying to rollback but we can't because some
+ statements are still busy. This leaves the database
+ unusable for future transactions as the current transaction
+ is still open.
+
+ As we are returning the actual error as the most relevant
+ error in the chain, our caller might assume that it can
+ retry/compensate on this error (e.g. SVN_WC_LOCKED), while
+ in fact the SQLite database is unusable until the statements
+ started within this transaction are reset and the transaction
+ aborted.
+
+ We try to compensate by resetting all prepared but unreset
+ statements; but we leave the busy error in the chain anyway to
+ help diagnosing the original error and help in finding where
+ a reset statement is missing. */
+
+ err2 = reset_all_statements(db, err2);
+ err2 = svn_error_compose_create(
+ svn_sqlite__step_done(stmt),
+ err2);
+ }
+
+ return svn_error_compose_create(err,
+ err2);
+ }
+
+ SVN_ERR(get_internal_statement(&stmt, db, STMT_INTERNAL_COMMIT_TRANSACTION));
+ return svn_error_trace(svn_sqlite__step_done(stmt));
+}
+
+svn_error_t *
+svn_sqlite__finish_savepoint(svn_sqlite__db_t *db,
+ svn_error_t *err)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ if (err)
+ {
+ svn_error_t *err2;
+
+ err2 = get_internal_statement(&stmt, db,
+ STMT_INTERNAL_ROLLBACK_TO_SAVEPOINT_SVN);
+
+ if (!err2)
+ err2 = svn_sqlite__step_done(stmt);
+
+ if (err2 && err2->apr_err == SVN_ERR_SQLITE_BUSY)
+ {
+ /* Ok, we have a major problem. Some statement is still open, which
+ makes it impossible to release this savepoint.
+
+ ### See huge comment in svn_sqlite__finish_transaction for
+ further details */
+
+ err2 = reset_all_statements(db, err2);
+ err2 = svn_error_compose_create(svn_sqlite__step_done(stmt), err2);
+ }
+
+ err = svn_error_compose_create(err, err2);
+ err2 = get_internal_statement(&stmt, db,
+ STMT_INTERNAL_RELEASE_SAVEPOINT_SVN);
+
+ if (!err2)
+ err2 = svn_sqlite__step_done(stmt);
+
+ return svn_error_trace(svn_error_compose_create(err, err2));
+ }
+
+ SVN_ERR(get_internal_statement(&stmt, db,
+ STMT_INTERNAL_RELEASE_SAVEPOINT_SVN));
+
+ return svn_error_trace(svn_sqlite__step_done(stmt));
+}
+
+svn_error_t *
+svn_sqlite__with_transaction(svn_sqlite__db_t *db,
+ svn_sqlite__transaction_callback_t cb_func,
+ void *cb_baton,
+ apr_pool_t *scratch_pool /* NULL allowed */)
+{
+ SVN_SQLITE__WITH_TXN(cb_func(cb_baton, db, scratch_pool), db);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__with_immediate_transaction(
+ svn_sqlite__db_t *db,
+ svn_sqlite__transaction_callback_t cb_func,
+ void *cb_baton,
+ apr_pool_t *scratch_pool /* NULL allowed */)
+{
+ SVN_SQLITE__WITH_IMMEDIATE_TXN(cb_func(cb_baton, db, scratch_pool), db);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__with_lock(svn_sqlite__db_t *db,
+ svn_sqlite__transaction_callback_t cb_func,
+ void *cb_baton,
+ apr_pool_t *scratch_pool /* NULL allowed */)
+{
+ SVN_SQLITE__WITH_LOCK(cb_func(cb_baton, db, scratch_pool), db);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_sqlite__hotcopy(const char *src_path,
+ const char *dst_path,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__db_t *src_db;
+
+ SVN_ERR(svn_sqlite__open(&src_db, src_path, svn_sqlite__mode_readonly,
+ NULL, 0, NULL,
+ scratch_pool, scratch_pool));
+
+ {
+ svn_sqlite__db_t *dst_db;
+ sqlite3_backup *backup;
+ int rc1, rc2;
+
+ SVN_ERR(svn_sqlite__open(&dst_db, dst_path, svn_sqlite__mode_rwcreate,
+ NULL, 0, NULL, scratch_pool, scratch_pool));
+ backup = sqlite3_backup_init(dst_db->db3, "main", src_db->db3, "main");
+ if (!backup)
+ return svn_error_createf(SVN_ERR_SQLITE_ERROR, NULL,
+ _("SQLite hotcopy failed for %s"), src_path);
+ do
+ {
+ /* Pages are usually 1024 byte (SQLite docs). On my laptop
+ copying gets faster as the number of pages is increased up
+ to about 64, beyond that speed levels off. Lets put the
+ number of pages an order of magnitude higher, this is still
+ likely to be a fraction of large databases. */
+ rc1 = sqlite3_backup_step(backup, 1024);
+
+ /* Should we sleep on SQLITE_OK? That would make copying a
+ large database take much longer. When we do sleep how,
+ long should we sleep? Should the sleep get longer if we
+ keep getting BUSY/LOCKED? I have no real reason for
+ choosing 25. */
+ if (rc1 == SQLITE_BUSY || rc1 == SQLITE_LOCKED)
+ sqlite3_sleep(25);
+ }
+ while (rc1 == SQLITE_OK || rc1 == SQLITE_BUSY || rc1 == SQLITE_LOCKED);
+ rc2 = sqlite3_backup_finish(backup);
+ if (rc1 != SQLITE_DONE)
+ SQLITE_ERR(rc1, dst_db);
+ SQLITE_ERR(rc2, dst_db);
+ SVN_ERR(svn_sqlite__close(dst_db));
+ }
+
+ SVN_ERR(svn_sqlite__close(src_db));
+
+ return SVN_NO_ERROR;
+}
+
+struct function_wrapper_baton_t
+{
+ svn_sqlite__func_t func;
+ void *baton;
+
+ apr_pool_t *scratch_pool;
+};
+
+static void
+wrapped_func(sqlite3_context *context,
+ int argc,
+ sqlite3_value *values[])
+{
+ struct function_wrapper_baton_t *fwb = sqlite3_user_data(context);
+ svn_sqlite__context_t sctx;
+ svn_sqlite__value_t **local_vals =
+ apr_palloc(fwb->scratch_pool,
+ sizeof(svn_sqlite__value_t *) * argc);
+ svn_error_t *err;
+ int i;
+
+ sctx.context = context;
+
+ for (i = 0; i < argc; i++)
+ {
+ local_vals[i] = apr_palloc(fwb->scratch_pool, sizeof(*local_vals[i]));
+ local_vals[i]->value = values[i];
+ }
+
+ err = fwb->func(&sctx, argc, local_vals, fwb->scratch_pool);
+ svn_pool_clear(fwb->scratch_pool);
+
+ if (err)
+ {
+ char buf[256];
+ sqlite3_result_error(context,
+ svn_err_best_message(err, buf, sizeof(buf)),
+ -1);
+ svn_error_clear(err);
+ }
+}
+
+svn_error_t *
+svn_sqlite__create_scalar_function(svn_sqlite__db_t *db,
+ const char *func_name,
+ int argc,
+ svn_sqlite__func_t func,
+ void *baton)
+{
+ struct function_wrapper_baton_t *fwb = apr_pcalloc(db->state_pool,
+ sizeof(*fwb));
+
+ fwb->scratch_pool = svn_pool_create(db->state_pool);
+ fwb->func = func;
+ fwb->baton = baton;
+
+ SQLITE_ERR(sqlite3_create_function(db->db3, func_name, argc, SQLITE_ANY,
+ fwb, wrapped_func, NULL, NULL),
+ db);
+
+ return SVN_NO_ERROR;
+}
+
+int
+svn_sqlite__value_type(svn_sqlite__value_t *val)
+{
+ return sqlite3_value_type(val->value);
+}
+
+const char *
+svn_sqlite__value_text(svn_sqlite__value_t *val)
+{
+ return (const char *) sqlite3_value_text(val->value);
+}
+
+void
+svn_sqlite__result_null(svn_sqlite__context_t *sctx)
+{
+ sqlite3_result_null(sctx->context);
+}
+
+void
+svn_sqlite__result_int64(svn_sqlite__context_t *sctx, apr_int64_t val)
+{
+ sqlite3_result_int64(sctx->context, val);
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/sqlite3wrapper.c b/contrib/subversion/subversion/libsvn_subr/sqlite3wrapper.c
new file mode 100644
index 0000000..d1941aa
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/sqlite3wrapper.c
@@ -0,0 +1,62 @@
+/* sqlite3wrapper.c
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_private_config.h"
+
+/* Include sqlite3 inline, making all symbols private. */
+#ifdef SVN_SQLITE_INLINE
+# define SQLITE_OMIT_DEPRECATED
+# define SQLITE_API static
+# if __GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 6 || __APPLE_CC__))
+# if !__APPLE_CC__ || __GNUC_MINOR__ >= 6
+# pragma GCC diagnostic push
+# endif
+# pragma GCC diagnostic ignored "-Wunreachable-code"
+# pragma GCC diagnostic ignored "-Wunused-function"
+# pragma GCC diagnostic ignored "-Wcast-qual"
+# pragma GCC diagnostic ignored "-Wunused"
+# pragma GCC diagnostic ignored "-Wshadow"
+# if __APPLE_CC__
+# pragma GCC diagnostic ignored "-Wshorten-64-to-32"
+# endif
+# endif
+# ifdef __APPLE__
+# include <Availability.h>
+# if __MAC_OS_X_VERSION_MIN_REQUIRED < 1060
+ /* <libkern/OSAtomic.h> is included on OS X by sqlite3.c, and
+ on old systems (Leopard or older), it cannot be compiled
+ with -std=c89 because it uses inline. This is a work-around. */
+# define inline __inline__
+# include <libkern/OSAtomic.h>
+# undef inline
+# endif
+# endif
+# include <sqlite3.c>
+# if __GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 6))
+# pragma GCC diagnostic pop
+# endif
+
+/* Expose the sqlite API vtable and the two missing functions */
+const sqlite3_api_routines *const svn_sqlite3__api_funcs = &sqlite3Apis;
+int (*const svn_sqlite3__api_initialize)(void) = sqlite3_initialize;
+int (*const svn_sqlite3__api_config)(int, ...) = sqlite3_config;
+#endif
diff --git a/contrib/subversion/subversion/libsvn_subr/ssl_client_cert_providers.c b/contrib/subversion/subversion/libsvn_subr/ssl_client_cert_providers.c
new file mode 100644
index 0000000..cf86fa1
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/ssl_client_cert_providers.c
@@ -0,0 +1,209 @@
+/*
+ * ssl_client_cert_providers.c: providers for
+ * SVN_AUTH_CRED_SSL_CLIENT_CERT
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_pools.h>
+#include "svn_hash.h"
+#include "svn_auth.h"
+#include "svn_error.h"
+#include "svn_config.h"
+
+
+/*-----------------------------------------------------------------------*/
+/* File provider */
+/*-----------------------------------------------------------------------*/
+
+/* retrieve and load the ssl client certificate file from servers
+ config */
+static svn_error_t *
+ssl_client_cert_file_first_credentials(void **credentials_p,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ svn_config_t *cfg = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS);
+ const char *server_group = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_SERVER_GROUP);
+ const char *cert_file;
+
+ cert_file =
+ svn_config_get_server_setting(cfg, server_group,
+ SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE,
+ NULL);
+
+ if (cert_file != NULL)
+ {
+ svn_auth_cred_ssl_client_cert_t *cred =
+ apr_palloc(pool, sizeof(*cred));
+
+ cred->cert_file = cert_file;
+ cred->may_save = FALSE;
+ *credentials_p = cred;
+ }
+ else
+ {
+ *credentials_p = NULL;
+ }
+
+ *iter_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+
+static const svn_auth_provider_t ssl_client_cert_file_provider =
+ {
+ SVN_AUTH_CRED_SSL_CLIENT_CERT,
+ ssl_client_cert_file_first_credentials,
+ NULL,
+ NULL
+ };
+
+
+/*** Public API to SSL file providers. ***/
+void svn_auth_get_ssl_client_cert_file_provider
+ (svn_auth_provider_object_t **provider, apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+ po->vtable = &ssl_client_cert_file_provider;
+ *provider = po;
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* Prompt provider */
+/*-----------------------------------------------------------------------*/
+
+/* Baton type for prompting to send client ssl creds.
+ There is no iteration baton type. */
+typedef struct ssl_client_cert_prompt_provider_baton_t
+{
+ svn_auth_ssl_client_cert_prompt_func_t prompt_func;
+ void *prompt_baton;
+
+ /* how many times to re-prompt after the first one fails */
+ int retry_limit;
+} ssl_client_cert_prompt_provider_baton_t;
+
+/* Iteration baton. */
+typedef struct ssl_client_cert_prompt_iter_baton_t
+{
+ /* The original provider baton */
+ ssl_client_cert_prompt_provider_baton_t *pb;
+
+ /* The original realmstring */
+ const char *realmstring;
+
+ /* how many times we've reprompted */
+ int retries;
+} ssl_client_cert_prompt_iter_baton_t;
+
+
+static svn_error_t *
+ssl_client_cert_prompt_first_cred(void **credentials_p,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ ssl_client_cert_prompt_provider_baton_t *pb = provider_baton;
+ ssl_client_cert_prompt_iter_baton_t *ib =
+ apr_pcalloc(pool, sizeof(*ib));
+ const char *no_auth_cache = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE);
+
+ SVN_ERR(pb->prompt_func((svn_auth_cred_ssl_client_cert_t **) credentials_p,
+ pb->prompt_baton, realmstring, ! no_auth_cache,
+ pool));
+
+ ib->pb = pb;
+ ib->realmstring = apr_pstrdup(pool, realmstring);
+ ib->retries = 0;
+ *iter_baton = ib;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+ssl_client_cert_prompt_next_cred(void **credentials_p,
+ void *iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ ssl_client_cert_prompt_iter_baton_t *ib = iter_baton;
+ const char *no_auth_cache = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE);
+
+ if ((ib->pb->retry_limit >= 0) && (ib->retries >= ib->pb->retry_limit))
+ {
+ /* give up, go on to next provider. */
+ *credentials_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ ib->retries++;
+
+ return ib->pb->prompt_func((svn_auth_cred_ssl_client_cert_t **)
+ credentials_p, ib->pb->prompt_baton,
+ ib->realmstring, ! no_auth_cache, pool);
+}
+
+
+static const svn_auth_provider_t ssl_client_cert_prompt_provider = {
+ SVN_AUTH_CRED_SSL_CLIENT_CERT,
+ ssl_client_cert_prompt_first_cred,
+ ssl_client_cert_prompt_next_cred,
+ NULL
+};
+
+
+/*** Public API to SSL prompting providers. ***/
+void svn_auth_get_ssl_client_cert_prompt_provider
+ (svn_auth_provider_object_t **provider,
+ svn_auth_ssl_client_cert_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+ ssl_client_cert_prompt_provider_baton_t *pb = apr_palloc(pool, sizeof(*pb));
+
+ pb->prompt_func = prompt_func;
+ pb->prompt_baton = prompt_baton;
+ pb->retry_limit = retry_limit;
+
+ po->vtable = &ssl_client_cert_prompt_provider;
+ po->provider_baton = pb;
+ *provider = po;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/ssl_client_cert_pw_providers.c b/contrib/subversion/subversion/libsvn_subr/ssl_client_cert_pw_providers.c
new file mode 100644
index 0000000..6c1bcf1
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/ssl_client_cert_pw_providers.c
@@ -0,0 +1,506 @@
+/*
+ * ssl_client_cert_pw_providers.c: providers for
+ * SVN_AUTH_CRED_SSL_CLIENT_CERT_PW
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_pools.h>
+
+#include "svn_hash.h"
+#include "svn_auth.h"
+#include "svn_error.h"
+#include "svn_config.h"
+#include "svn_string.h"
+
+#include "private/svn_auth_private.h"
+
+#include "svn_private_config.h"
+
+/*-----------------------------------------------------------------------*/
+/* File provider */
+/*-----------------------------------------------------------------------*/
+
+/* The keys that will be stored on disk. These serve the same role as
+ * similar constants in other providers.
+ *
+ * AUTHN_PASSTYPE_KEY just records the passphrase type next to the
+ * passphrase, so that anyone who is manually editing their authn
+ * files can know which provider owns the password.
+ */
+#define AUTHN_PASSPHRASE_KEY "passphrase"
+#define AUTHN_PASSTYPE_KEY "passtype"
+
+/* Baton type for the ssl client cert passphrase provider. */
+typedef struct ssl_client_cert_pw_file_provider_baton_t
+{
+ svn_auth_plaintext_passphrase_prompt_func_t plaintext_passphrase_prompt_func;
+ void *prompt_baton;
+ /* We cache the user's answer to the plaintext prompt, keyed
+ by realm, in case we'll be called multiple times for the
+ same realm. So: keys are 'const char *' realm strings, and
+ values are 'svn_boolean_t *'. */
+ apr_hash_t *plaintext_answers;
+} ssl_client_cert_pw_file_provider_baton_t;
+
+/* This implements the svn_auth__password_get_t interface.
+ Set **PASSPHRASE to the plaintext passphrase retrieved from CREDS;
+ ignore other parameters. */
+svn_error_t *
+svn_auth__ssl_client_cert_pw_get(svn_boolean_t *done,
+ const char **passphrase,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ svn_string_t *str;
+ str = svn_hash_gets(creds, AUTHN_PASSPHRASE_KEY);
+ if (str && str->data)
+ {
+ *passphrase = str->data;
+ *done = TRUE;
+ return SVN_NO_ERROR;
+ }
+ *done = FALSE;
+ return SVN_NO_ERROR;
+}
+
+/* This implements the svn_auth__password_set_t interface.
+ Store PASSPHRASE in CREDS; ignore other parameters. */
+svn_error_t *
+svn_auth__ssl_client_cert_pw_set(svn_boolean_t *done,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ const char *passphrase,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ svn_hash_sets(creds, AUTHN_PASSPHRASE_KEY,
+ svn_string_create(passphrase, pool));
+ *done = TRUE;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_auth__ssl_client_cert_pw_cache_get(void **credentials_p,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ svn_auth__password_get_t passphrase_get,
+ const char *passtype,
+ apr_pool_t *pool)
+{
+ svn_config_t *cfg = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS);
+ const char *server_group = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_SERVER_GROUP);
+ svn_boolean_t non_interactive = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NON_INTERACTIVE)
+ != NULL;
+ const char *password =
+ svn_config_get_server_setting(cfg, server_group,
+ SVN_CONFIG_OPTION_SSL_CLIENT_CERT_PASSWORD,
+ NULL);
+ if (! password)
+ {
+ svn_error_t *err;
+ apr_hash_t *creds_hash = NULL;
+ const char *config_dir = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_CONFIG_DIR);
+
+ /* Try to load passphrase from the auth/ cache. */
+ err = svn_config_read_auth_data(&creds_hash,
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ realmstring, config_dir, pool);
+ svn_error_clear(err);
+ if (! err && creds_hash)
+ {
+ svn_boolean_t done;
+
+ SVN_ERR(passphrase_get(&done, &password, creds_hash, realmstring,
+ NULL, parameters, non_interactive, pool));
+ if (!done)
+ password = NULL;
+ }
+ }
+
+ if (password)
+ {
+ svn_auth_cred_ssl_client_cert_pw_t *cred
+ = apr_palloc(pool, sizeof(*cred));
+ cred->password = password;
+ cred->may_save = FALSE;
+ *credentials_p = cred;
+ }
+ else *credentials_p = NULL;
+ *iter_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_auth__ssl_client_cert_pw_cache_set(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ svn_auth__password_set_t passphrase_set,
+ const char *passtype,
+ apr_pool_t *pool)
+{
+ svn_auth_cred_ssl_client_cert_pw_t *creds = credentials;
+ apr_hash_t *creds_hash = NULL;
+ const char *config_dir;
+ svn_error_t *err;
+ svn_boolean_t dont_store_passphrase =
+ svn_hash_gets(parameters, SVN_AUTH_PARAM_DONT_STORE_SSL_CLIENT_CERT_PP)
+ != NULL;
+ svn_boolean_t non_interactive =
+ svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE) != NULL;
+ svn_boolean_t no_auth_cache =
+ (! creds->may_save)
+ || (svn_hash_gets(parameters, SVN_AUTH_PARAM_NO_AUTH_CACHE) != NULL);
+
+ *saved = FALSE;
+
+ if (no_auth_cache)
+ return SVN_NO_ERROR;
+
+ config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR);
+ creds_hash = apr_hash_make(pool);
+
+ /* Don't store passphrase in any form if the user has told
+ us not to do so. */
+ if (! dont_store_passphrase)
+ {
+ svn_boolean_t may_save_passphrase = FALSE;
+
+ /* If the passphrase is going to be stored encrypted, go right
+ ahead and store it to disk. Else determine whether saving
+ in plaintext is OK. */
+ if (strcmp(passtype, SVN_AUTH__WINCRYPT_PASSWORD_TYPE) == 0
+ || strcmp(passtype, SVN_AUTH__KWALLET_PASSWORD_TYPE) == 0
+ || strcmp(passtype, SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE) == 0
+ || strcmp(passtype, SVN_AUTH__KEYCHAIN_PASSWORD_TYPE) == 0)
+ {
+ may_save_passphrase = TRUE;
+ }
+ else
+ {
+#ifdef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
+ may_save_passphrase = FALSE;
+#else
+ const char *store_ssl_client_cert_pp_plaintext =
+ svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT);
+ ssl_client_cert_pw_file_provider_baton_t *b =
+ (ssl_client_cert_pw_file_provider_baton_t *)provider_baton;
+
+ if (svn_cstring_casecmp(store_ssl_client_cert_pp_plaintext,
+ SVN_CONFIG_ASK) == 0)
+ {
+ if (non_interactive)
+ {
+ /* In non-interactive mode, the default behaviour is
+ to not store the passphrase */
+ may_save_passphrase = FALSE;
+ }
+ else if (b->plaintext_passphrase_prompt_func)
+ {
+ /* We're interactive, and the client provided a
+ prompt callback. So we can ask the user.
+ Check for a cached answer before prompting.
+
+ This is a pointer-to-boolean, rather than just a
+ boolean, because we must distinguish between
+ "cached answer is no" and "no answer has been
+ cached yet". */
+ svn_boolean_t *cached_answer =
+ svn_hash_gets(b->plaintext_answers, realmstring);
+
+ if (cached_answer != NULL)
+ {
+ may_save_passphrase = *cached_answer;
+ }
+ else
+ {
+ apr_pool_t *cached_answer_pool;
+
+ /* Nothing cached for this realm, prompt the user. */
+ SVN_ERR((*b->plaintext_passphrase_prompt_func)(
+ &may_save_passphrase,
+ realmstring,
+ b->prompt_baton,
+ pool));
+
+ /* Cache the user's answer in case we're called again
+ * for the same realm.
+ *
+ * We allocate the answer cache in the hash table's pool
+ * to make sure that is has the same life time as the
+ * hash table itself. This means that the answer will
+ * survive across RA sessions -- which is important,
+ * because otherwise we'd prompt users once per RA session.
+ */
+ cached_answer_pool = apr_hash_pool_get(b->plaintext_answers);
+ cached_answer = apr_palloc(cached_answer_pool,
+ sizeof(*cached_answer));
+ *cached_answer = may_save_passphrase;
+ svn_hash_sets(b->plaintext_answers, realmstring,
+ cached_answer);
+ }
+ }
+ else
+ {
+ may_save_passphrase = FALSE;
+ }
+ }
+ else if (svn_cstring_casecmp(store_ssl_client_cert_pp_plaintext,
+ SVN_CONFIG_FALSE) == 0)
+ {
+ may_save_passphrase = FALSE;
+ }
+ else if (svn_cstring_casecmp(store_ssl_client_cert_pp_plaintext,
+ SVN_CONFIG_TRUE) == 0)
+ {
+ may_save_passphrase = TRUE;
+ }
+ else
+ {
+ return svn_error_createf
+ (SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
+ _("Config error: invalid value '%s' for option '%s'"),
+ store_ssl_client_cert_pp_plaintext,
+ SVN_AUTH_PARAM_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT);
+ }
+#endif
+ }
+
+ if (may_save_passphrase)
+ {
+ SVN_ERR(passphrase_set(saved, creds_hash, realmstring,
+ NULL, creds->password, parameters,
+ non_interactive, pool));
+
+ if (*saved && passtype)
+ {
+ svn_hash_sets(creds_hash, AUTHN_PASSTYPE_KEY,
+ svn_string_create(passtype, pool));
+ }
+
+ /* Save credentials to disk. */
+ err = svn_config_write_auth_data(creds_hash,
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ realmstring, config_dir, pool);
+ svn_error_clear(err);
+ *saved = ! err;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the svn_auth_provider_t.first_credentials API.
+ It gets cached (unencrypted) credentials from the ssl client cert
+ password provider's cache. */
+static svn_error_t *
+ssl_client_cert_pw_file_first_credentials(void **credentials_p,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__ssl_client_cert_pw_cache_get(credentials_p, iter_baton,
+ provider_baton, parameters,
+ realmstring,
+ svn_auth__ssl_client_cert_pw_get,
+ SVN_AUTH__SIMPLE_PASSWORD_TYPE,
+ pool);
+}
+
+
+/* This implements the svn_auth_provider_t.save_credentials API.
+ It saves the credentials unencrypted. */
+static svn_error_t *
+ssl_client_cert_pw_file_save_credentials(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__ssl_client_cert_pw_cache_set(saved, credentials,
+ provider_baton,
+ parameters,
+ realmstring,
+ svn_auth__ssl_client_cert_pw_set,
+ SVN_AUTH__SIMPLE_PASSWORD_TYPE,
+ pool);
+}
+
+
+static const svn_auth_provider_t ssl_client_cert_pw_file_provider = {
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ ssl_client_cert_pw_file_first_credentials,
+ NULL,
+ ssl_client_cert_pw_file_save_credentials
+};
+
+
+/*** Public API to SSL file providers. ***/
+void
+svn_auth_get_ssl_client_cert_pw_file_provider2
+ (svn_auth_provider_object_t **provider,
+ svn_auth_plaintext_passphrase_prompt_func_t plaintext_passphrase_prompt_func,
+ void *prompt_baton,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+ ssl_client_cert_pw_file_provider_baton_t *pb = apr_pcalloc(pool,
+ sizeof(*pb));
+
+ pb->plaintext_passphrase_prompt_func = plaintext_passphrase_prompt_func;
+ pb->prompt_baton = prompt_baton;
+ pb->plaintext_answers = apr_hash_make(pool);
+
+ po->vtable = &ssl_client_cert_pw_file_provider;
+ po->provider_baton = pb;
+ *provider = po;
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* Prompt provider */
+/*-----------------------------------------------------------------------*/
+
+/* Baton type for client passphrase prompting.
+ There is no iteration baton type. */
+typedef struct ssl_client_cert_pw_prompt_provider_baton_t
+{
+ svn_auth_ssl_client_cert_pw_prompt_func_t prompt_func;
+ void *prompt_baton;
+
+ /* how many times to re-prompt after the first one fails */
+ int retry_limit;
+} ssl_client_cert_pw_prompt_provider_baton_t;
+
+/* Iteration baton. */
+typedef struct ssl_client_cert_pw_prompt_iter_baton_t
+{
+ /* The original provider baton */
+ ssl_client_cert_pw_prompt_provider_baton_t *pb;
+
+ /* The original realmstring */
+ const char *realmstring;
+
+ /* how many times we've reprompted */
+ int retries;
+} ssl_client_cert_pw_prompt_iter_baton_t;
+
+
+static svn_error_t *
+ssl_client_cert_pw_prompt_first_cred(void **credentials_p,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ ssl_client_cert_pw_prompt_provider_baton_t *pb = provider_baton;
+ ssl_client_cert_pw_prompt_iter_baton_t *ib =
+ apr_pcalloc(pool, sizeof(*ib));
+ const char *no_auth_cache = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE);
+
+ SVN_ERR(pb->prompt_func((svn_auth_cred_ssl_client_cert_pw_t **)
+ credentials_p, pb->prompt_baton, realmstring,
+ ! no_auth_cache, pool));
+
+ ib->pb = pb;
+ ib->realmstring = apr_pstrdup(pool, realmstring);
+ ib->retries = 0;
+ *iter_baton = ib;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+ssl_client_cert_pw_prompt_next_cred(void **credentials_p,
+ void *iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ ssl_client_cert_pw_prompt_iter_baton_t *ib = iter_baton;
+ const char *no_auth_cache = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE);
+
+ if ((ib->pb->retry_limit >= 0) && (ib->retries >= ib->pb->retry_limit))
+ {
+ /* give up, go on to next provider. */
+ *credentials_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ ib->retries++;
+
+ return ib->pb->prompt_func((svn_auth_cred_ssl_client_cert_pw_t **)
+ credentials_p, ib->pb->prompt_baton,
+ ib->realmstring, ! no_auth_cache, pool);
+}
+
+
+static const svn_auth_provider_t client_cert_pw_prompt_provider = {
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ ssl_client_cert_pw_prompt_first_cred,
+ ssl_client_cert_pw_prompt_next_cred,
+ NULL
+};
+
+
+void svn_auth_get_ssl_client_cert_pw_prompt_provider
+ (svn_auth_provider_object_t **provider,
+ svn_auth_ssl_client_cert_pw_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+ ssl_client_cert_pw_prompt_provider_baton_t *pb =
+ apr_palloc(pool, sizeof(*pb));
+
+ pb->prompt_func = prompt_func;
+ pb->prompt_baton = prompt_baton;
+ pb->retry_limit = retry_limit;
+
+ po->vtable = &client_cert_pw_prompt_provider;
+ po->provider_baton = pb;
+ *provider = po;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/ssl_server_trust_providers.c b/contrib/subversion/subversion/libsvn_subr/ssl_server_trust_providers.c
new file mode 100644
index 0000000..c69be77
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/ssl_server_trust_providers.c
@@ -0,0 +1,234 @@
+/*
+ * ssl_server_trust_providers.c: providers for
+ * SVN_AUTH_CRED_SSL_SERVER_TRUST
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_hash.h"
+#include "svn_auth.h"
+#include "svn_error.h"
+#include "svn_config.h"
+#include "svn_string.h"
+
+
+/*-----------------------------------------------------------------------*/
+/* File provider */
+/*-----------------------------------------------------------------------*/
+
+/* The keys that will be stored on disk. These serve the same role as
+ similar constants in other providers. */
+#define AUTHN_ASCII_CERT_KEY "ascii_cert"
+#define AUTHN_FAILURES_KEY "failures"
+
+
+/* retrieve ssl server CA failure overrides (if any) from servers
+ config */
+static svn_error_t *
+ssl_server_trust_file_first_credentials(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ apr_uint32_t *failures = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_SSL_SERVER_FAILURES);
+ const svn_auth_ssl_server_cert_info_t *cert_info =
+ svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO);
+ apr_hash_t *creds_hash = NULL;
+ const char *config_dir;
+ svn_error_t *error = SVN_NO_ERROR;
+
+ *credentials = NULL;
+ *iter_baton = NULL;
+
+ /* Check if this is a permanently accepted certificate */
+ config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR);
+ error =
+ svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_SSL_SERVER_TRUST,
+ realmstring, config_dir, pool);
+ svn_error_clear(error);
+ if (! error && creds_hash)
+ {
+ svn_string_t *trusted_cert, *this_cert, *failstr;
+ apr_uint32_t last_failures = 0;
+
+ trusted_cert = svn_hash_gets(creds_hash, AUTHN_ASCII_CERT_KEY);
+ this_cert = svn_string_create(cert_info->ascii_cert, pool);
+ failstr = svn_hash_gets(creds_hash, AUTHN_FAILURES_KEY);
+
+ if (failstr)
+ {
+ char *endptr;
+ unsigned long tmp_ulong = strtoul(failstr->data, &endptr, 10);
+
+ if (*endptr == '\0')
+ last_failures = (apr_uint32_t) tmp_ulong;
+ }
+
+ /* If the cert is trusted and there are no new failures, we
+ * accept it by clearing all failures. */
+ if (trusted_cert &&
+ svn_string_compare(this_cert, trusted_cert) &&
+ (*failures & ~last_failures) == 0)
+ {
+ *failures = 0;
+ }
+ }
+
+ /* If all failures are cleared now, we return the creds */
+ if (! *failures)
+ {
+ svn_auth_cred_ssl_server_trust_t *creds =
+ apr_pcalloc(pool, sizeof(*creds));
+ creds->may_save = FALSE; /* No need to save it again... */
+ *credentials = creds;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+ssl_server_trust_file_save_credentials(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ svn_auth_cred_ssl_server_trust_t *creds = credentials;
+ const svn_auth_ssl_server_cert_info_t *cert_info;
+ apr_hash_t *creds_hash = NULL;
+ const char *config_dir;
+
+ if (! creds->may_save)
+ return SVN_NO_ERROR;
+
+ config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR);
+
+ cert_info = svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO);
+
+ creds_hash = apr_hash_make(pool);
+ svn_hash_sets(creds_hash, AUTHN_ASCII_CERT_KEY,
+ svn_string_create(cert_info->ascii_cert, pool));
+ svn_hash_sets(creds_hash,
+ AUTHN_FAILURES_KEY,
+ svn_string_createf(pool, "%lu",
+ (unsigned long)creds->accepted_failures));
+
+ SVN_ERR(svn_config_write_auth_data(creds_hash,
+ SVN_AUTH_CRED_SSL_SERVER_TRUST,
+ realmstring,
+ config_dir,
+ pool));
+ *saved = TRUE;
+ return SVN_NO_ERROR;
+}
+
+
+static const svn_auth_provider_t ssl_server_trust_file_provider = {
+ SVN_AUTH_CRED_SSL_SERVER_TRUST,
+ &ssl_server_trust_file_first_credentials,
+ NULL,
+ &ssl_server_trust_file_save_credentials,
+};
+
+
+/*** Public API to SSL file providers. ***/
+void
+svn_auth_get_ssl_server_trust_file_provider
+ (svn_auth_provider_object_t **provider, apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+
+ po->vtable = &ssl_server_trust_file_provider;
+ *provider = po;
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* Prompt provider */
+/*-----------------------------------------------------------------------*/
+
+/* Baton type for prompting to verify server ssl creds.
+ There is no iteration baton type. */
+typedef struct ssl_server_trust_prompt_provider_baton_t
+{
+ svn_auth_ssl_server_trust_prompt_func_t prompt_func;
+ void *prompt_baton;
+} ssl_server_trust_prompt_provider_baton_t;
+
+
+static svn_error_t *
+ssl_server_trust_prompt_first_cred(void **credentials_p,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ ssl_server_trust_prompt_provider_baton_t *pb = provider_baton;
+ apr_uint32_t *failures = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_SSL_SERVER_FAILURES);
+ const char *no_auth_cache = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE);
+ const svn_auth_ssl_server_cert_info_t *cert_info =
+ svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO);
+ svn_boolean_t may_save = (!no_auth_cache
+ && !(*failures & SVN_AUTH_SSL_OTHER));
+
+ SVN_ERR(pb->prompt_func((svn_auth_cred_ssl_server_trust_t **)credentials_p,
+ pb->prompt_baton, realmstring, *failures, cert_info,
+ may_save, pool));
+
+ *iter_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+
+static const svn_auth_provider_t ssl_server_trust_prompt_provider = {
+ SVN_AUTH_CRED_SSL_SERVER_TRUST,
+ ssl_server_trust_prompt_first_cred,
+ NULL,
+ NULL
+};
+
+
+/*** Public API to SSL prompting providers. ***/
+void
+svn_auth_get_ssl_server_trust_prompt_provider
+ (svn_auth_provider_object_t **provider,
+ svn_auth_ssl_server_trust_prompt_func_t prompt_func,
+ void *prompt_baton,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+ ssl_server_trust_prompt_provider_baton_t *pb =
+ apr_palloc(pool, sizeof(*pb));
+ pb->prompt_func = prompt_func;
+ pb->prompt_baton = prompt_baton;
+ po->vtable = &ssl_server_trust_prompt_provider;
+ po->provider_baton = pb;
+ *provider = po;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/stream.c b/contrib/subversion/subversion/libsvn_subr/stream.c
new file mode 100644
index 0000000..e2529c7
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/stream.c
@@ -0,0 +1,1826 @@
+/*
+ * stream.c: svn_stream operations
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_strings.h>
+#include <apr_file_io.h>
+#include <apr_errno.h>
+#include <apr_md5.h>
+
+#include <zlib.h>
+
+#include "svn_pools.h"
+#include "svn_io.h"
+#include "svn_error.h"
+#include "svn_string.h"
+#include "svn_utf.h"
+#include "svn_checksum.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+#include "private/svn_error_private.h"
+#include "private/svn_eol_private.h"
+#include "private/svn_io_private.h"
+#include "private/svn_subr_private.h"
+
+
+struct svn_stream_t {
+ void *baton;
+ svn_read_fn_t read_fn;
+ svn_stream_skip_fn_t skip_fn;
+ svn_write_fn_t write_fn;
+ svn_close_fn_t close_fn;
+ svn_stream_mark_fn_t mark_fn;
+ svn_stream_seek_fn_t seek_fn;
+ svn_stream__is_buffered_fn_t is_buffered_fn;
+};
+
+
+/*** Forward declarations. ***/
+
+static svn_error_t *
+skip_default_handler(void *baton, apr_size_t len, svn_read_fn_t read_fn);
+
+
+/*** Generic streams. ***/
+
+svn_stream_t *
+svn_stream_create(void *baton, apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+
+ stream = apr_palloc(pool, sizeof(*stream));
+ stream->baton = baton;
+ stream->read_fn = NULL;
+ stream->skip_fn = NULL;
+ stream->write_fn = NULL;
+ stream->close_fn = NULL;
+ stream->mark_fn = NULL;
+ stream->seek_fn = NULL;
+ stream->is_buffered_fn = NULL;
+ return stream;
+}
+
+
+void
+svn_stream_set_baton(svn_stream_t *stream, void *baton)
+{
+ stream->baton = baton;
+}
+
+
+void
+svn_stream_set_read(svn_stream_t *stream, svn_read_fn_t read_fn)
+{
+ stream->read_fn = read_fn;
+}
+
+void
+svn_stream_set_skip(svn_stream_t *stream, svn_stream_skip_fn_t skip_fn)
+{
+ stream->skip_fn = skip_fn;
+}
+
+void
+svn_stream_set_write(svn_stream_t *stream, svn_write_fn_t write_fn)
+{
+ stream->write_fn = write_fn;
+}
+
+void
+svn_stream_set_close(svn_stream_t *stream, svn_close_fn_t close_fn)
+{
+ stream->close_fn = close_fn;
+}
+
+void
+svn_stream_set_mark(svn_stream_t *stream, svn_stream_mark_fn_t mark_fn)
+{
+ stream->mark_fn = mark_fn;
+}
+
+void
+svn_stream_set_seek(svn_stream_t *stream, svn_stream_seek_fn_t seek_fn)
+{
+ stream->seek_fn = seek_fn;
+}
+
+void
+svn_stream__set_is_buffered(svn_stream_t *stream,
+ svn_stream__is_buffered_fn_t is_buffered_fn)
+{
+ stream->is_buffered_fn = is_buffered_fn;
+}
+
+svn_error_t *
+svn_stream_read(svn_stream_t *stream, char *buffer, apr_size_t *len)
+{
+ SVN_ERR_ASSERT(stream->read_fn != NULL);
+ return svn_error_trace(stream->read_fn(stream->baton, buffer, len));
+}
+
+
+svn_error_t *
+svn_stream_skip(svn_stream_t *stream, apr_size_t len)
+{
+ if (stream->skip_fn == NULL)
+ return svn_error_trace(
+ skip_default_handler(stream->baton, len, stream->read_fn));
+
+ return svn_error_trace(stream->skip_fn(stream->baton, len));
+}
+
+
+svn_error_t *
+svn_stream_write(svn_stream_t *stream, const char *data, apr_size_t *len)
+{
+ SVN_ERR_ASSERT(stream->write_fn != NULL);
+ return svn_error_trace(stream->write_fn(stream->baton, data, len));
+}
+
+
+svn_error_t *
+svn_stream_reset(svn_stream_t *stream)
+{
+ return svn_error_trace(
+ svn_stream_seek(stream, NULL));
+}
+
+svn_boolean_t
+svn_stream_supports_mark(svn_stream_t *stream)
+{
+ return stream->mark_fn != NULL;
+}
+
+svn_error_t *
+svn_stream_mark(svn_stream_t *stream, svn_stream_mark_t **mark,
+ apr_pool_t *pool)
+{
+ if (stream->mark_fn == NULL)
+ return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED, NULL, NULL);
+
+ return svn_error_trace(stream->mark_fn(stream->baton, mark, pool));
+}
+
+svn_error_t *
+svn_stream_seek(svn_stream_t *stream, const svn_stream_mark_t *mark)
+{
+ if (stream->seek_fn == NULL)
+ return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED, NULL, NULL);
+
+ return svn_error_trace(stream->seek_fn(stream->baton, mark));
+}
+
+svn_boolean_t
+svn_stream__is_buffered(svn_stream_t *stream)
+{
+ if (stream->is_buffered_fn == NULL)
+ return FALSE;
+
+ return stream->is_buffered_fn(stream->baton);
+}
+
+svn_error_t *
+svn_stream_close(svn_stream_t *stream)
+{
+ if (stream->close_fn == NULL)
+ return SVN_NO_ERROR;
+ return svn_error_trace(stream->close_fn(stream->baton));
+}
+
+svn_error_t *
+svn_stream_puts(svn_stream_t *stream,
+ const char *str)
+{
+ apr_size_t len;
+ len = strlen(str);
+ return svn_error_trace(svn_stream_write(stream, str, &len));
+}
+
+svn_error_t *
+svn_stream_printf(svn_stream_t *stream,
+ apr_pool_t *pool,
+ const char *fmt,
+ ...)
+{
+ const char *message;
+ va_list ap;
+
+ va_start(ap, fmt);
+ message = apr_pvsprintf(pool, fmt, ap);
+ va_end(ap);
+
+ return svn_error_trace(svn_stream_puts(stream, message));
+}
+
+
+svn_error_t *
+svn_stream_printf_from_utf8(svn_stream_t *stream,
+ const char *encoding,
+ apr_pool_t *pool,
+ const char *fmt,
+ ...)
+{
+ const char *message, *translated;
+ va_list ap;
+
+ va_start(ap, fmt);
+ message = apr_pvsprintf(pool, fmt, ap);
+ va_end(ap);
+
+ SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated, message, encoding,
+ pool));
+
+ return svn_error_trace(svn_stream_puts(stream, translated));
+}
+
+/* Size that 90% of the lines we encounter will be not longer than.
+ used by stream_readline_bytewise() and stream_readline_chunky().
+ */
+#define LINE_CHUNK_SIZE 80
+
+/* Guts of svn_stream_readline().
+ * Returns the line read from STREAM in *STRINGBUF, and indicates
+ * end-of-file in *EOF. If DETECT_EOL is TRUE, the end-of-line indicator
+ * is detected automatically and returned in *EOL.
+ * If DETECT_EOL is FALSE, *EOL must point to the desired end-of-line
+ * indicator. STRINGBUF is allocated in POOL. */
+static svn_error_t *
+stream_readline_bytewise(svn_stringbuf_t **stringbuf,
+ svn_boolean_t *eof,
+ const char *eol,
+ svn_stream_t *stream,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *str;
+ apr_size_t numbytes;
+ const char *match;
+ char c;
+
+ /* Since we're reading one character at a time, let's at least
+ optimize for the 90% case. 90% of the time, we can avoid the
+ stringbuf ever having to realloc() itself if we start it out at
+ 80 chars. */
+ str = svn_stringbuf_create_ensure(LINE_CHUNK_SIZE, pool);
+
+ /* Read into STR up to and including the next EOL sequence. */
+ match = eol;
+ while (*match)
+ {
+ numbytes = 1;
+ SVN_ERR(svn_stream_read(stream, &c, &numbytes));
+ if (numbytes != 1)
+ {
+ /* a 'short' read means the stream has run out. */
+ *eof = TRUE;
+ *stringbuf = str;
+ return SVN_NO_ERROR;
+ }
+
+ if (c == *match)
+ match++;
+ else
+ match = eol;
+
+ svn_stringbuf_appendbyte(str, c);
+ }
+
+ *eof = FALSE;
+ svn_stringbuf_chop(str, match - eol);
+ *stringbuf = str;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+stream_readline_chunky(svn_stringbuf_t **stringbuf,
+ svn_boolean_t *eof,
+ const char *eol,
+ svn_stream_t *stream,
+ apr_pool_t *pool)
+{
+ /* Read larger chunks of data at once into this buffer and scan
+ * that for EOL. A good chunk size should be about 80 chars since
+ * most text lines will be shorter. However, don't use a much
+ * larger value because filling the buffer from the stream takes
+ * time as well.
+ */
+ char buffer[LINE_CHUNK_SIZE+1];
+
+ /* variables */
+ svn_stream_mark_t *mark;
+ apr_size_t numbytes;
+ const char *eol_pos;
+ apr_size_t total_parsed = 0;
+
+ /* invariant for this call */
+ const size_t eol_len = strlen(eol);
+
+ /* Remember the line start so this plus the line length will be
+ * the position to move to at the end of this function.
+ */
+ SVN_ERR(svn_stream_mark(stream, &mark, pool));
+
+ /* Read the first chunk. */
+ numbytes = LINE_CHUNK_SIZE;
+ SVN_ERR(svn_stream_read(stream, buffer, &numbytes));
+ buffer[numbytes] = '\0';
+
+ /* Look for the EOL in this first chunk. If we find it, we are done here.
+ */
+ eol_pos = strstr(buffer, eol);
+ if (eol_pos != NULL)
+ {
+ *stringbuf = svn_stringbuf_ncreate(buffer, eol_pos - buffer, pool);
+ total_parsed = eol_pos - buffer + eol_len;
+ }
+ else if (numbytes < LINE_CHUNK_SIZE)
+ {
+ /* We hit EOF but not EOL.
+ */
+ *stringbuf = svn_stringbuf_ncreate(buffer, numbytes, pool);
+ *eof = TRUE;
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* A larger buffer for the string is needed. */
+ svn_stringbuf_t *str;
+ str = svn_stringbuf_create_ensure(2*LINE_CHUNK_SIZE, pool);
+ svn_stringbuf_appendbytes(str, buffer, numbytes);
+ *stringbuf = str;
+
+ /* Loop reading chunks until an EOL was found. If we hit EOF, fall
+ * back to the standard implementation. */
+ do
+ {
+ /* Append the next chunk to the string read so far.
+ */
+ svn_stringbuf_ensure(str, str->len + LINE_CHUNK_SIZE);
+ numbytes = LINE_CHUNK_SIZE;
+ SVN_ERR(svn_stream_read(stream, str->data + str->len, &numbytes));
+ str->len += numbytes;
+ str->data[str->len] = '\0';
+
+ /* Look for the EOL in the new data plus the last part of the
+ * previous chunk because the EOL may span over the boundary
+ * between both chunks.
+ */
+ eol_pos = strstr(str->data + str->len - numbytes - (eol_len-1), eol);
+
+ if ((numbytes < LINE_CHUNK_SIZE) && (eol_pos == NULL))
+ {
+ /* We hit EOF instead of EOL. */
+ *eof = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+ while (eol_pos == NULL);
+
+ /* Number of bytes we actually consumed (i.e. line + EOF).
+ * We need to "return" the rest to the stream by moving its
+ * read pointer.
+ */
+ total_parsed = eol_pos - str->data + eol_len;
+
+ /* Terminate the string at the EOL postion and return it. */
+ str->len = eol_pos - str->data;
+ str->data[str->len] = 0;
+ }
+
+ /* Move the stream read pointer to the first position behind the EOL.
+ */
+ SVN_ERR(svn_stream_seek(stream, mark));
+ return svn_error_trace(svn_stream_skip(stream, total_parsed));
+}
+
+/* Guts of svn_stream_readline().
+ * Returns the line read from STREAM in *STRINGBUF, and indicates
+ * end-of-file in *EOF. EOL must point to the desired end-of-line
+ * indicator. STRINGBUF is allocated in POOL. */
+static svn_error_t *
+stream_readline(svn_stringbuf_t **stringbuf,
+ svn_boolean_t *eof,
+ const char *eol,
+ svn_stream_t *stream,
+ apr_pool_t *pool)
+{
+ *eof = FALSE;
+
+ /* Often, we operate on APR file or string-based streams and know what
+ * EOL we are looking for. Optimize that common case.
+ */
+ if (svn_stream_supports_mark(stream) &&
+ svn_stream__is_buffered(stream))
+ {
+ /* We can efficiently read chunks speculatively and reposition the
+ * stream pointer to the end of the line once we found that.
+ */
+ SVN_ERR(stream_readline_chunky(stringbuf,
+ eof,
+ eol,
+ stream,
+ pool));
+ }
+ else
+ {
+ /* Use the standard byte-byte implementation.
+ */
+ SVN_ERR(stream_readline_bytewise(stringbuf,
+ eof,
+ eol,
+ stream,
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_stream_readline(svn_stream_t *stream,
+ svn_stringbuf_t **stringbuf,
+ const char *eol,
+ svn_boolean_t *eof,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(stream_readline(stringbuf, eof, eol, stream,
+ pool));
+}
+
+svn_error_t *svn_stream_copy3(svn_stream_t *from, svn_stream_t *to,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ char *buf = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
+ svn_error_t *err;
+ svn_error_t *err2;
+
+ /* Read and write chunks until we get a short read, indicating the
+ end of the stream. (We can't get a short write without an
+ associated error.) */
+ while (1)
+ {
+ apr_size_t len = SVN__STREAM_CHUNK_SIZE;
+
+ if (cancel_func)
+ {
+ err = cancel_func(cancel_baton);
+ if (err)
+ break;
+ }
+
+ err = svn_stream_read(from, buf, &len);
+ if (err)
+ break;
+
+ if (len > 0)
+ err = svn_stream_write(to, buf, &len);
+
+ if (err || (len != SVN__STREAM_CHUNK_SIZE))
+ break;
+ }
+
+ err2 = svn_error_compose_create(svn_stream_close(from),
+ svn_stream_close(to));
+
+ return svn_error_compose_create(err, err2);
+}
+
+svn_error_t *
+svn_stream_contents_same2(svn_boolean_t *same,
+ svn_stream_t *stream1,
+ svn_stream_t *stream2,
+ apr_pool_t *pool)
+{
+ char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
+ char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
+ apr_size_t bytes_read1 = SVN__STREAM_CHUNK_SIZE;
+ apr_size_t bytes_read2 = SVN__STREAM_CHUNK_SIZE;
+ svn_error_t *err = NULL;
+
+ *same = TRUE; /* assume TRUE, until disproved below */
+ while (bytes_read1 == SVN__STREAM_CHUNK_SIZE
+ && bytes_read2 == SVN__STREAM_CHUNK_SIZE)
+ {
+ err = svn_stream_read(stream1, buf1, &bytes_read1);
+ if (err)
+ break;
+ err = svn_stream_read(stream2, buf2, &bytes_read2);
+ if (err)
+ break;
+
+ if ((bytes_read1 != bytes_read2)
+ || (memcmp(buf1, buf2, bytes_read1)))
+ {
+ *same = FALSE;
+ break;
+ }
+ }
+
+ return svn_error_compose_create(err,
+ svn_error_compose_create(
+ svn_stream_close(stream1),
+ svn_stream_close(stream2)));
+}
+
+
+/*** Stream implementation utilities ***/
+
+/* Skip data from any stream by reading and simply discarding it. */
+static svn_error_t *
+skip_default_handler(void *baton, apr_size_t len, svn_read_fn_t read_fn)
+{
+ apr_size_t bytes_read = 1;
+ char buffer[4096];
+ apr_size_t to_read = len;
+
+ while ((to_read > 0) && (bytes_read > 0))
+ {
+ bytes_read = sizeof(buffer) < to_read ? sizeof(buffer) : to_read;
+ SVN_ERR(read_fn(baton, buffer, &bytes_read));
+ to_read -= bytes_read;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Generic readable empty stream ***/
+
+static svn_error_t *
+read_handler_empty(void *baton, char *buffer, apr_size_t *len)
+{
+ *len = 0;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_handler_empty(void *baton, const char *data, apr_size_t *len)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+mark_handler_empty(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
+{
+ *mark = NULL; /* Seek to start of stream marker */
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+seek_handler_empty(void *baton, const svn_stream_mark_t *mark)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_boolean_t
+is_buffered_handler_empty(void *baton)
+{
+ return FALSE;
+}
+
+
+svn_stream_t *
+svn_stream_empty(apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+
+ stream = svn_stream_create(NULL, pool);
+ svn_stream_set_read(stream, read_handler_empty);
+ svn_stream_set_write(stream, write_handler_empty);
+ svn_stream_set_mark(stream, mark_handler_empty);
+ svn_stream_set_seek(stream, seek_handler_empty);
+ svn_stream__set_is_buffered(stream, is_buffered_handler_empty);
+ return stream;
+}
+
+
+
+/*** Stream duplication support ***/
+struct baton_tee {
+ svn_stream_t *out1;
+ svn_stream_t *out2;
+};
+
+
+static svn_error_t *
+write_handler_tee(void *baton, const char *data, apr_size_t *len)
+{
+ struct baton_tee *bt = baton;
+
+ SVN_ERR(svn_stream_write(bt->out1, data, len));
+ SVN_ERR(svn_stream_write(bt->out2, data, len));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+close_handler_tee(void *baton)
+{
+ struct baton_tee *bt = baton;
+
+ SVN_ERR(svn_stream_close(bt->out1));
+ SVN_ERR(svn_stream_close(bt->out2));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_stream_t *
+svn_stream_tee(svn_stream_t *out1,
+ svn_stream_t *out2,
+ apr_pool_t *pool)
+{
+ struct baton_tee *baton;
+ svn_stream_t *stream;
+
+ if (out1 == NULL)
+ return out2;
+
+ if (out2 == NULL)
+ return out1;
+
+ baton = apr_palloc(pool, sizeof(*baton));
+ baton->out1 = out1;
+ baton->out2 = out2;
+ stream = svn_stream_create(baton, pool);
+ svn_stream_set_write(stream, write_handler_tee);
+ svn_stream_set_close(stream, close_handler_tee);
+
+ return stream;
+}
+
+
+
+/*** Ownership detaching stream ***/
+
+static svn_error_t *
+read_handler_disown(void *baton, char *buffer, apr_size_t *len)
+{
+ return svn_error_trace(svn_stream_read(baton, buffer, len));
+}
+
+static svn_error_t *
+skip_handler_disown(void *baton, apr_size_t len)
+{
+ return svn_error_trace(svn_stream_skip(baton, len));
+}
+
+static svn_error_t *
+write_handler_disown(void *baton, const char *buffer, apr_size_t *len)
+{
+ return svn_error_trace(svn_stream_write(baton, buffer, len));
+}
+
+static svn_error_t *
+mark_handler_disown(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
+{
+ return svn_error_trace(svn_stream_mark(baton, mark, pool));
+}
+
+static svn_error_t *
+seek_handler_disown(void *baton, const svn_stream_mark_t *mark)
+{
+ return svn_error_trace(svn_stream_seek(baton, mark));
+}
+
+static svn_boolean_t
+is_buffered_handler_disown(void *baton)
+{
+ return svn_stream__is_buffered(baton);
+}
+
+svn_stream_t *
+svn_stream_disown(svn_stream_t *stream, apr_pool_t *pool)
+{
+ svn_stream_t *s = svn_stream_create(stream, pool);
+
+ svn_stream_set_read(s, read_handler_disown);
+ svn_stream_set_skip(s, skip_handler_disown);
+ svn_stream_set_write(s, write_handler_disown);
+ svn_stream_set_mark(s, mark_handler_disown);
+ svn_stream_set_seek(s, seek_handler_disown);
+ svn_stream__set_is_buffered(s, is_buffered_handler_disown);
+
+ return s;
+}
+
+
+
+/*** Generic stream for APR files ***/
+struct baton_apr {
+ apr_file_t *file;
+ apr_pool_t *pool;
+};
+
+/* svn_stream_mark_t for streams backed by APR files. */
+struct mark_apr {
+ apr_off_t off;
+};
+
+static svn_error_t *
+read_handler_apr(void *baton, char *buffer, apr_size_t *len)
+{
+ struct baton_apr *btn = baton;
+ svn_error_t *err;
+ svn_boolean_t eof;
+
+ if (*len == 1)
+ {
+ err = svn_io_file_getc(buffer, btn->file, btn->pool);
+ if (err)
+ {
+ *len = 0;
+ if (APR_STATUS_IS_EOF(err->apr_err))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ }
+ }
+ else
+ err = svn_io_file_read_full2(btn->file, buffer, *len, len,
+ &eof, btn->pool);
+
+ return svn_error_trace(err);
+}
+
+static svn_error_t *
+skip_handler_apr(void *baton, apr_size_t len)
+{
+ struct baton_apr *btn = baton;
+ apr_off_t offset = len;
+
+ return svn_error_trace(
+ svn_io_file_seek(btn->file, APR_CUR, &offset, btn->pool));
+}
+
+static svn_error_t *
+write_handler_apr(void *baton, const char *data, apr_size_t *len)
+{
+ struct baton_apr *btn = baton;
+ svn_error_t *err;
+
+ if (*len == 1)
+ {
+ err = svn_io_file_putc(*data, btn->file, btn->pool);
+ if (err)
+ *len = 0;
+ }
+ else
+ err = svn_io_file_write_full(btn->file, data, *len, len, btn->pool);
+
+ return svn_error_trace(err);
+}
+
+static svn_error_t *
+close_handler_apr(void *baton)
+{
+ struct baton_apr *btn = baton;
+
+ return svn_error_trace(svn_io_file_close(btn->file, btn->pool));
+}
+
+static svn_error_t *
+mark_handler_apr(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
+{
+ struct baton_apr *btn = baton;
+ struct mark_apr *mark_apr;
+
+ mark_apr = apr_palloc(pool, sizeof(*mark_apr));
+ mark_apr->off = 0;
+ SVN_ERR(svn_io_file_seek(btn->file, APR_CUR, &mark_apr->off, btn->pool));
+ *mark = (svn_stream_mark_t *)mark_apr;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+seek_handler_apr(void *baton, const svn_stream_mark_t *mark)
+{
+ struct baton_apr *btn = baton;
+ apr_off_t offset = (mark != NULL) ? ((const struct mark_apr *)mark)->off : 0;
+
+ SVN_ERR(svn_io_file_seek(btn->file, APR_SET, &offset, btn->pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_boolean_t
+is_buffered_handler_apr(void *baton)
+{
+ struct baton_apr *btn = baton;
+ return (apr_file_flags_get(btn->file) & APR_BUFFERED) != 0;
+}
+
+svn_error_t *
+svn_stream_open_readonly(svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *file;
+
+ SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, result_pool));
+ *stream = svn_stream_from_aprfile2(file, FALSE, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_stream_open_writable(svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *file;
+
+ SVN_ERR(svn_io_file_open(&file, path,
+ APR_WRITE
+ | APR_BUFFERED
+ | APR_CREATE
+ | APR_EXCL,
+ APR_OS_DEFAULT, result_pool));
+ *stream = svn_stream_from_aprfile2(file, FALSE, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_stream_open_unique(svn_stream_t **stream,
+ const char **temp_path,
+ const char *dirpath,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *file;
+
+ SVN_ERR(svn_io_open_unique_file3(&file, temp_path, dirpath,
+ delete_when, result_pool, scratch_pool));
+ *stream = svn_stream_from_aprfile2(file, FALSE, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_stream_t *
+svn_stream_from_aprfile2(apr_file_t *file,
+ svn_boolean_t disown,
+ apr_pool_t *pool)
+{
+ struct baton_apr *baton;
+ svn_stream_t *stream;
+
+ if (file == NULL)
+ return svn_stream_empty(pool);
+
+ baton = apr_palloc(pool, sizeof(*baton));
+ baton->file = file;
+ baton->pool = pool;
+ stream = svn_stream_create(baton, pool);
+ svn_stream_set_read(stream, read_handler_apr);
+ svn_stream_set_write(stream, write_handler_apr);
+ svn_stream_set_skip(stream, skip_handler_apr);
+ svn_stream_set_mark(stream, mark_handler_apr);
+ svn_stream_set_seek(stream, seek_handler_apr);
+ svn_stream__set_is_buffered(stream, is_buffered_handler_apr);
+
+ if (! disown)
+ svn_stream_set_close(stream, close_handler_apr);
+
+ return stream;
+}
+
+
+/* Compressed stream support */
+
+#define ZBUFFER_SIZE 4096 /* The size of the buffer the
+ compressed stream uses to read from
+ the substream. Basically an
+ arbitrary value, picked to be about
+ page-sized. */
+
+struct zbaton {
+ z_stream *in; /* compressed stream for reading */
+ z_stream *out; /* compressed stream for writing */
+ svn_read_fn_t read; /* substream's read function */
+ svn_write_fn_t write; /* substream's write function */
+ svn_close_fn_t close; /* substream's close function */
+ void *read_buffer; /* buffer used for reading from
+ substream */
+ int read_flush; /* what flush mode to use while
+ reading */
+ apr_pool_t *pool; /* The pool this baton is allocated
+ on */
+ void *subbaton; /* The substream's baton */
+};
+
+/* zlib alloc function. opaque is the pool we need. */
+static voidpf
+zalloc(voidpf opaque, uInt items, uInt size)
+{
+ apr_pool_t *pool = opaque;
+
+ return apr_palloc(pool, items * size);
+}
+
+/* zlib free function */
+static void
+zfree(voidpf opaque, voidpf address)
+{
+ /* Empty, since we allocate on the pool */
+}
+
+/* Helper function to figure out the sync mode */
+static svn_error_t *
+read_helper_gz(svn_read_fn_t read_fn,
+ void *baton,
+ char *buffer,
+ uInt *len, int *zflush)
+{
+ uInt orig_len = *len;
+
+ /* There's no reason this value should grow bigger than the range of
+ uInt, but Subversion's API requires apr_size_t. */
+ apr_size_t apr_len = (apr_size_t) *len;
+
+ SVN_ERR((*read_fn)(baton, buffer, &apr_len));
+
+ /* Type cast back to uInt type that zlib uses. On LP64 platforms
+ apr_size_t will be bigger than uInt. */
+ *len = (uInt) apr_len;
+
+ /* I wanted to use Z_FINISH here, but we need to know our buffer is
+ big enough */
+ *zflush = (*len) < orig_len ? Z_SYNC_FLUSH : Z_SYNC_FLUSH;
+
+ return SVN_NO_ERROR;
+}
+
+/* Handle reading from a compressed stream */
+static svn_error_t *
+read_handler_gz(void *baton, char *buffer, apr_size_t *len)
+{
+ struct zbaton *btn = baton;
+ int zerr;
+
+ if (btn->in == NULL)
+ {
+ btn->in = apr_palloc(btn->pool, sizeof(z_stream));
+ btn->in->zalloc = zalloc;
+ btn->in->zfree = zfree;
+ btn->in->opaque = btn->pool;
+ btn->read_buffer = apr_palloc(btn->pool, ZBUFFER_SIZE);
+ btn->in->next_in = btn->read_buffer;
+ btn->in->avail_in = ZBUFFER_SIZE;
+
+ SVN_ERR(read_helper_gz(btn->read, btn->subbaton, btn->read_buffer,
+ &btn->in->avail_in, &btn->read_flush));
+
+ zerr = inflateInit(btn->in);
+ SVN_ERR(svn_error__wrap_zlib(zerr, "inflateInit", btn->in->msg));
+ }
+
+ btn->in->next_out = (Bytef *) buffer;
+ btn->in->avail_out = (uInt) *len;
+
+ while (btn->in->avail_out > 0)
+ {
+ if (btn->in->avail_in <= 0)
+ {
+ btn->in->avail_in = ZBUFFER_SIZE;
+ btn->in->next_in = btn->read_buffer;
+ SVN_ERR(read_helper_gz(btn->read, btn->subbaton, btn->read_buffer,
+ &btn->in->avail_in, &btn->read_flush));
+ }
+
+ /* Short read means underlying stream has run out. */
+ if (btn->in->avail_in == 0)
+ {
+ *len = 0;
+ return SVN_NO_ERROR;
+ }
+
+ zerr = inflate(btn->in, btn->read_flush);
+ if (zerr == Z_STREAM_END)
+ break;
+ else if (zerr != Z_OK)
+ return svn_error_trace(svn_error__wrap_zlib(zerr, "inflate",
+ btn->in->msg));
+ }
+
+ *len -= btn->in->avail_out;
+ return SVN_NO_ERROR;
+}
+
+/* Compress data and write it to the substream */
+static svn_error_t *
+write_handler_gz(void *baton, const char *buffer, apr_size_t *len)
+{
+ struct zbaton *btn = baton;
+ apr_pool_t *subpool;
+ void *write_buf;
+ apr_size_t buf_size, write_len;
+ int zerr;
+
+ if (btn->out == NULL)
+ {
+ btn->out = apr_palloc(btn->pool, sizeof(z_stream));
+ btn->out->zalloc = zalloc;
+ btn->out->zfree = zfree;
+ btn->out->opaque = btn->pool;
+
+ zerr = deflateInit(btn->out, Z_DEFAULT_COMPRESSION);
+ SVN_ERR(svn_error__wrap_zlib(zerr, "deflateInit", btn->out->msg));
+ }
+
+ /* The largest buffer we should need is 0.1% larger than the
+ compressed data, + 12 bytes. This info comes from zlib.h. */
+ buf_size = *len + (*len / 1000) + 13;
+ subpool = svn_pool_create(btn->pool);
+ write_buf = apr_palloc(subpool, buf_size);
+
+ btn->out->next_in = (Bytef *) buffer; /* Casting away const! */
+ btn->out->avail_in = (uInt) *len;
+
+ while (btn->out->avail_in > 0)
+ {
+ btn->out->next_out = write_buf;
+ btn->out->avail_out = (uInt) buf_size;
+
+ zerr = deflate(btn->out, Z_NO_FLUSH);
+ SVN_ERR(svn_error__wrap_zlib(zerr, "deflate", btn->out->msg));
+ write_len = buf_size - btn->out->avail_out;
+ if (write_len > 0)
+ SVN_ERR(btn->write(btn->subbaton, write_buf, &write_len));
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Handle flushing and closing the stream */
+static svn_error_t *
+close_handler_gz(void *baton)
+{
+ struct zbaton *btn = baton;
+ int zerr;
+
+ if (btn->in != NULL)
+ {
+ zerr = inflateEnd(btn->in);
+ SVN_ERR(svn_error__wrap_zlib(zerr, "inflateEnd", btn->in->msg));
+ }
+
+ if (btn->out != NULL)
+ {
+ void *buf;
+ apr_size_t write_len;
+
+ buf = apr_palloc(btn->pool, ZBUFFER_SIZE);
+
+ while (TRUE)
+ {
+ btn->out->next_out = buf;
+ btn->out->avail_out = ZBUFFER_SIZE;
+
+ zerr = deflate(btn->out, Z_FINISH);
+ if (zerr != Z_STREAM_END && zerr != Z_OK)
+ return svn_error_trace(svn_error__wrap_zlib(zerr, "deflate",
+ btn->out->msg));
+ write_len = ZBUFFER_SIZE - btn->out->avail_out;
+ if (write_len > 0)
+ SVN_ERR(btn->write(btn->subbaton, buf, &write_len));
+ if (zerr == Z_STREAM_END)
+ break;
+ }
+
+ zerr = deflateEnd(btn->out);
+ SVN_ERR(svn_error__wrap_zlib(zerr, "deflateEnd", btn->out->msg));
+ }
+
+ if (btn->close != NULL)
+ return svn_error_trace(btn->close(btn->subbaton));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+svn_stream_t *
+svn_stream_compressed(svn_stream_t *stream, apr_pool_t *pool)
+{
+ struct svn_stream_t *zstream;
+ struct zbaton *baton;
+
+ assert(stream != NULL);
+
+ baton = apr_palloc(pool, sizeof(*baton));
+ baton->in = baton->out = NULL;
+ baton->read = stream->read_fn;
+ baton->write = stream->write_fn;
+ baton->close = stream->close_fn;
+ baton->subbaton = stream->baton;
+ baton->pool = pool;
+ baton->read_buffer = NULL;
+ baton->read_flush = Z_SYNC_FLUSH;
+
+ zstream = svn_stream_create(baton, pool);
+ svn_stream_set_read(zstream, read_handler_gz);
+ svn_stream_set_write(zstream, write_handler_gz);
+ svn_stream_set_close(zstream, close_handler_gz);
+
+ return zstream;
+}
+
+
+/* Checksummed stream support */
+
+struct checksum_stream_baton
+{
+ svn_checksum_ctx_t *read_ctx, *write_ctx;
+ svn_checksum_t **read_checksum; /* Output value. */
+ svn_checksum_t **write_checksum; /* Output value. */
+ svn_stream_t *proxy;
+
+ /* True if more data should be read when closing the stream. */
+ svn_boolean_t read_more;
+
+ /* Pool to allocate read buffer and output values from. */
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+read_handler_checksum(void *baton, char *buffer, apr_size_t *len)
+{
+ struct checksum_stream_baton *btn = baton;
+ apr_size_t saved_len = *len;
+
+ SVN_ERR(svn_stream_read(btn->proxy, buffer, len));
+
+ if (btn->read_checksum)
+ SVN_ERR(svn_checksum_update(btn->read_ctx, buffer, *len));
+
+ if (saved_len != *len)
+ btn->read_more = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+write_handler_checksum(void *baton, const char *buffer, apr_size_t *len)
+{
+ struct checksum_stream_baton *btn = baton;
+
+ if (btn->write_checksum && *len > 0)
+ SVN_ERR(svn_checksum_update(btn->write_ctx, buffer, *len));
+
+ return svn_error_trace(svn_stream_write(btn->proxy, buffer, len));
+}
+
+
+static svn_error_t *
+close_handler_checksum(void *baton)
+{
+ struct checksum_stream_baton *btn = baton;
+
+ /* If we're supposed to drain the stream, do so before finalizing the
+ checksum. */
+ if (btn->read_more)
+ {
+ char *buf = apr_palloc(btn->pool, SVN__STREAM_CHUNK_SIZE);
+ apr_size_t len = SVN__STREAM_CHUNK_SIZE;
+
+ do
+ {
+ SVN_ERR(read_handler_checksum(baton, buf, &len));
+ }
+ while (btn->read_more);
+ }
+
+ if (btn->read_ctx)
+ SVN_ERR(svn_checksum_final(btn->read_checksum, btn->read_ctx, btn->pool));
+
+ if (btn->write_ctx)
+ SVN_ERR(svn_checksum_final(btn->write_checksum, btn->write_ctx, btn->pool));
+
+ return svn_error_trace(svn_stream_close(btn->proxy));
+}
+
+
+svn_stream_t *
+svn_stream_checksummed2(svn_stream_t *stream,
+ svn_checksum_t **read_checksum,
+ svn_checksum_t **write_checksum,
+ svn_checksum_kind_t checksum_kind,
+ svn_boolean_t read_all,
+ apr_pool_t *pool)
+{
+ svn_stream_t *s;
+ struct checksum_stream_baton *baton;
+
+ if (read_checksum == NULL && write_checksum == NULL)
+ return stream;
+
+ baton = apr_palloc(pool, sizeof(*baton));
+ if (read_checksum)
+ baton->read_ctx = svn_checksum_ctx_create(checksum_kind, pool);
+ else
+ baton->read_ctx = NULL;
+
+ if (write_checksum)
+ baton->write_ctx = svn_checksum_ctx_create(checksum_kind, pool);
+ else
+ baton->write_ctx = NULL;
+
+ baton->read_checksum = read_checksum;
+ baton->write_checksum = write_checksum;
+ baton->proxy = stream;
+ baton->read_more = read_all;
+ baton->pool = pool;
+
+ s = svn_stream_create(baton, pool);
+ svn_stream_set_read(s, read_handler_checksum);
+ svn_stream_set_write(s, write_handler_checksum);
+ svn_stream_set_close(s, close_handler_checksum);
+ return s;
+}
+
+struct md5_stream_baton
+{
+ const unsigned char **read_digest;
+ const unsigned char **write_digest;
+ svn_checksum_t *read_checksum;
+ svn_checksum_t *write_checksum;
+ svn_stream_t *proxy;
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+read_handler_md5(void *baton, char *buffer, apr_size_t *len)
+{
+ struct md5_stream_baton *btn = baton;
+ return svn_error_trace(svn_stream_read(btn->proxy, buffer, len));
+}
+
+static svn_error_t *
+skip_handler_md5(void *baton, apr_size_t len)
+{
+ struct md5_stream_baton *btn = baton;
+ return svn_error_trace(svn_stream_skip(btn->proxy, len));
+}
+
+static svn_error_t *
+write_handler_md5(void *baton, const char *buffer, apr_size_t *len)
+{
+ struct md5_stream_baton *btn = baton;
+ return svn_error_trace(svn_stream_write(btn->proxy, buffer, len));
+}
+
+static svn_error_t *
+close_handler_md5(void *baton)
+{
+ struct md5_stream_baton *btn = baton;
+
+ SVN_ERR(svn_stream_close(btn->proxy));
+
+ if (btn->read_digest)
+ *btn->read_digest
+ = apr_pmemdup(btn->pool, btn->read_checksum->digest,
+ APR_MD5_DIGESTSIZE);
+
+ if (btn->write_digest)
+ *btn->write_digest
+ = apr_pmemdup(btn->pool, btn->write_checksum->digest,
+ APR_MD5_DIGESTSIZE);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_stream_t *
+svn_stream_checksummed(svn_stream_t *stream,
+ const unsigned char **read_digest,
+ const unsigned char **write_digest,
+ svn_boolean_t read_all,
+ apr_pool_t *pool)
+{
+ svn_stream_t *s;
+ struct md5_stream_baton *baton;
+
+ if (! read_digest && ! write_digest)
+ return stream;
+
+ baton = apr_palloc(pool, sizeof(*baton));
+ baton->read_digest = read_digest;
+ baton->write_digest = write_digest;
+ baton->pool = pool;
+
+ /* Set BATON->proxy to a stream that will fill in BATON->read_checksum
+ * and BATON->write_checksum (if we want them) when it is closed. */
+ baton->proxy
+ = svn_stream_checksummed2(stream,
+ read_digest ? &baton->read_checksum : NULL,
+ write_digest ? &baton->write_checksum : NULL,
+ svn_checksum_md5,
+ read_all, pool);
+
+ /* Create a stream that will forward its read/write/close operations to
+ * BATON->proxy and will fill in *READ_DIGEST and *WRITE_DIGEST (if we
+ * want them) after it closes BATON->proxy. */
+ s = svn_stream_create(baton, pool);
+ svn_stream_set_read(s, read_handler_md5);
+ svn_stream_set_skip(s, skip_handler_md5);
+ svn_stream_set_write(s, write_handler_md5);
+ svn_stream_set_close(s, close_handler_md5);
+ return s;
+}
+
+
+
+
+/* Miscellaneous stream functions. */
+struct stringbuf_stream_baton
+{
+ svn_stringbuf_t *str;
+ apr_size_t amt_read;
+};
+
+/* svn_stream_mark_t for streams backed by stringbufs. */
+struct stringbuf_stream_mark {
+ apr_size_t pos;
+};
+
+static svn_error_t *
+read_handler_stringbuf(void *baton, char *buffer, apr_size_t *len)
+{
+ struct stringbuf_stream_baton *btn = baton;
+ apr_size_t left_to_read = btn->str->len - btn->amt_read;
+
+ *len = (*len > left_to_read) ? left_to_read : *len;
+ memcpy(buffer, btn->str->data + btn->amt_read, *len);
+ btn->amt_read += *len;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+skip_handler_stringbuf(void *baton, apr_size_t len)
+{
+ struct stringbuf_stream_baton *btn = baton;
+ apr_size_t left_to_read = btn->str->len - btn->amt_read;
+
+ len = (len > left_to_read) ? left_to_read : len;
+ btn->amt_read += len;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_handler_stringbuf(void *baton, const char *data, apr_size_t *len)
+{
+ struct stringbuf_stream_baton *btn = baton;
+
+ svn_stringbuf_appendbytes(btn->str, data, *len);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+mark_handler_stringbuf(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
+{
+ struct stringbuf_stream_baton *btn;
+ struct stringbuf_stream_mark *stringbuf_stream_mark;
+
+ btn = baton;
+
+ stringbuf_stream_mark = apr_palloc(pool, sizeof(*stringbuf_stream_mark));
+ stringbuf_stream_mark->pos = btn->amt_read;
+ *mark = (svn_stream_mark_t *)stringbuf_stream_mark;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+seek_handler_stringbuf(void *baton, const svn_stream_mark_t *mark)
+{
+ struct stringbuf_stream_baton *btn = baton;
+
+ if (mark != NULL)
+ {
+ const struct stringbuf_stream_mark *stringbuf_stream_mark;
+
+ stringbuf_stream_mark = (const struct stringbuf_stream_mark *)mark;
+ btn->amt_read = stringbuf_stream_mark->pos;
+ }
+ else
+ btn->amt_read = 0;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_boolean_t
+is_buffered_handler_stringbuf(void *baton)
+{
+ return TRUE;
+}
+
+svn_stream_t *
+svn_stream_from_stringbuf(svn_stringbuf_t *str,
+ apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+ struct stringbuf_stream_baton *baton;
+
+ if (! str)
+ return svn_stream_empty(pool);
+
+ baton = apr_palloc(pool, sizeof(*baton));
+ baton->str = str;
+ baton->amt_read = 0;
+ stream = svn_stream_create(baton, pool);
+ svn_stream_set_read(stream, read_handler_stringbuf);
+ svn_stream_set_skip(stream, skip_handler_stringbuf);
+ svn_stream_set_write(stream, write_handler_stringbuf);
+ svn_stream_set_mark(stream, mark_handler_stringbuf);
+ svn_stream_set_seek(stream, seek_handler_stringbuf);
+ svn_stream__set_is_buffered(stream, is_buffered_handler_stringbuf);
+ return stream;
+}
+
+struct string_stream_baton
+{
+ const svn_string_t *str;
+ apr_size_t amt_read;
+};
+
+/* svn_stream_mark_t for streams backed by stringbufs. */
+struct string_stream_mark {
+ apr_size_t pos;
+};
+
+static svn_error_t *
+read_handler_string(void *baton, char *buffer, apr_size_t *len)
+{
+ struct string_stream_baton *btn = baton;
+ apr_size_t left_to_read = btn->str->len - btn->amt_read;
+
+ *len = (*len > left_to_read) ? left_to_read : *len;
+ memcpy(buffer, btn->str->data + btn->amt_read, *len);
+ btn->amt_read += *len;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+mark_handler_string(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
+{
+ struct string_stream_baton *btn;
+ struct string_stream_mark *marker;
+
+ btn = baton;
+
+ marker = apr_palloc(pool, sizeof(*marker));
+ marker->pos = btn->amt_read;
+ *mark = (svn_stream_mark_t *)marker;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+seek_handler_string(void *baton, const svn_stream_mark_t *mark)
+{
+ struct string_stream_baton *btn = baton;
+
+ if (mark != NULL)
+ {
+ const struct string_stream_mark *marker;
+
+ marker = (const struct string_stream_mark *)mark;
+ btn->amt_read = marker->pos;
+ }
+ else
+ btn->amt_read = 0;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+skip_handler_string(void *baton, apr_size_t len)
+{
+ struct string_stream_baton *btn = baton;
+ apr_size_t left_to_read = btn->str->len - btn->amt_read;
+
+ len = (len > left_to_read) ? left_to_read : len;
+ btn->amt_read += len;
+ return SVN_NO_ERROR;
+}
+
+static svn_boolean_t
+is_buffered_handler_string(void *baton)
+{
+ return TRUE;
+}
+
+svn_stream_t *
+svn_stream_from_string(const svn_string_t *str,
+ apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+ struct string_stream_baton *baton;
+
+ if (! str)
+ return svn_stream_empty(pool);
+
+ baton = apr_palloc(pool, sizeof(*baton));
+ baton->str = str;
+ baton->amt_read = 0;
+ stream = svn_stream_create(baton, pool);
+ svn_stream_set_read(stream, read_handler_string);
+ svn_stream_set_mark(stream, mark_handler_string);
+ svn_stream_set_seek(stream, seek_handler_string);
+ svn_stream_set_skip(stream, skip_handler_string);
+ svn_stream__set_is_buffered(stream, is_buffered_handler_string);
+ return stream;
+}
+
+
+svn_error_t *
+svn_stream_for_stdin(svn_stream_t **in, apr_pool_t *pool)
+{
+ apr_file_t *stdin_file;
+ apr_status_t apr_err;
+
+ apr_err = apr_file_open_stdin(&stdin_file, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stdin");
+
+ *in = svn_stream_from_aprfile2(stdin_file, TRUE, pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_stream_for_stdout(svn_stream_t **out, apr_pool_t *pool)
+{
+ apr_file_t *stdout_file;
+ apr_status_t apr_err;
+
+ apr_err = apr_file_open_stdout(&stdout_file, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stdout");
+
+ *out = svn_stream_from_aprfile2(stdout_file, TRUE, pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_stream_for_stderr(svn_stream_t **err, apr_pool_t *pool)
+{
+ apr_file_t *stderr_file;
+ apr_status_t apr_err;
+
+ apr_err = apr_file_open_stderr(&stderr_file, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stderr");
+
+ *err = svn_stream_from_aprfile2(stderr_file, TRUE, pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_string_from_stream(svn_string_t **result,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *work = svn_stringbuf_create_ensure(SVN__STREAM_CHUNK_SIZE,
+ result_pool);
+ char *buffer = apr_palloc(scratch_pool, SVN__STREAM_CHUNK_SIZE);
+
+ while (1)
+ {
+ apr_size_t len = SVN__STREAM_CHUNK_SIZE;
+
+ SVN_ERR(svn_stream_read(stream, buffer, &len));
+ svn_stringbuf_appendbytes(work, buffer, len);
+
+ if (len < SVN__STREAM_CHUNK_SIZE)
+ break;
+ }
+
+ SVN_ERR(svn_stream_close(stream));
+
+ *result = apr_palloc(result_pool, sizeof(**result));
+ (*result)->data = work->data;
+ (*result)->len = work->len;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* These are somewhat arbirary, if we ever get good empirical data as to
+ actually valid values, feel free to update them. */
+#define BUFFER_BLOCK_SIZE 1024
+#define BUFFER_MAX_SIZE 100000
+
+svn_stream_t *
+svn_stream_buffered(apr_pool_t *result_pool)
+{
+ return svn_stream__from_spillbuf(BUFFER_BLOCK_SIZE, BUFFER_MAX_SIZE,
+ result_pool);
+}
+
+
+
+/*** Lazyopen Streams ***/
+
+/* Custom baton for lazyopen-style wrapper streams. */
+typedef struct lazyopen_baton_t {
+
+ /* Callback function and baton for opening the wrapped stream. */
+ svn_stream_lazyopen_func_t open_func;
+ void *open_baton;
+
+ /* The wrapped stream, or NULL if the stream hasn't yet been
+ opened. */
+ svn_stream_t *real_stream;
+ apr_pool_t *pool;
+
+ /* Whether to open the wrapped stream on a close call. */
+ svn_boolean_t open_on_close;
+
+} lazyopen_baton_t;
+
+
+/* Use B->open_func/baton to create and set B->real_stream iff it
+ isn't already set. */
+static svn_error_t *
+lazyopen_if_unopened(lazyopen_baton_t *b)
+{
+ if (b->real_stream == NULL)
+ {
+ svn_stream_t *stream;
+ apr_pool_t *scratch_pool = svn_pool_create(b->pool);
+
+ SVN_ERR(b->open_func(&stream, b->open_baton,
+ b->pool, scratch_pool));
+
+ svn_pool_destroy(scratch_pool);
+
+ b->real_stream = stream;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_read_fn_t */
+static svn_error_t *
+read_handler_lazyopen(void *baton,
+ char *buffer,
+ apr_size_t *len)
+{
+ lazyopen_baton_t *b = baton;
+
+ SVN_ERR(lazyopen_if_unopened(b));
+ SVN_ERR(svn_stream_read(b->real_stream, buffer, len));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_stream_skip_fn_t */
+static svn_error_t *
+skip_handler_lazyopen(void *baton,
+ apr_size_t len)
+{
+ lazyopen_baton_t *b = baton;
+
+ SVN_ERR(lazyopen_if_unopened(b));
+ SVN_ERR(svn_stream_skip(b->real_stream, len));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_write_fn_t */
+static svn_error_t *
+write_handler_lazyopen(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ lazyopen_baton_t *b = baton;
+
+ SVN_ERR(lazyopen_if_unopened(b));
+ SVN_ERR(svn_stream_write(b->real_stream, data, len));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_close_fn_t */
+static svn_error_t *
+close_handler_lazyopen(void *baton)
+{
+ lazyopen_baton_t *b = baton;
+
+ if (b->open_on_close)
+ SVN_ERR(lazyopen_if_unopened(b));
+ if (b->real_stream)
+ SVN_ERR(svn_stream_close(b->real_stream));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_stream_mark_fn_t */
+static svn_error_t *
+mark_handler_lazyopen(void *baton,
+ svn_stream_mark_t **mark,
+ apr_pool_t *pool)
+{
+ lazyopen_baton_t *b = baton;
+
+ SVN_ERR(lazyopen_if_unopened(b));
+ SVN_ERR(svn_stream_mark(b->real_stream, mark, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_stream_seek_fn_t */
+static svn_error_t *
+seek_handler_lazyopen(void *baton,
+ const svn_stream_mark_t *mark)
+{
+ lazyopen_baton_t *b = baton;
+
+ SVN_ERR(lazyopen_if_unopened(b));
+ SVN_ERR(svn_stream_seek(b->real_stream, mark));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_stream__is_buffered_fn_t */
+static svn_boolean_t
+is_buffered_lazyopen(void *baton)
+{
+ lazyopen_baton_t *b = baton;
+
+ /* No lazy open as we cannot handle an open error. */
+ if (!b->real_stream)
+ return FALSE;
+
+ return svn_stream__is_buffered(b->real_stream);
+}
+
+svn_stream_t *
+svn_stream_lazyopen_create(svn_stream_lazyopen_func_t open_func,
+ void *open_baton,
+ svn_boolean_t open_on_close,
+ apr_pool_t *result_pool)
+{
+ lazyopen_baton_t *lob = apr_pcalloc(result_pool, sizeof(*lob));
+ svn_stream_t *stream;
+
+ lob->open_func = open_func;
+ lob->open_baton = open_baton;
+ lob->real_stream = NULL;
+ lob->pool = result_pool;
+ lob->open_on_close = open_on_close;
+
+ stream = svn_stream_create(lob, result_pool);
+ svn_stream_set_read(stream, read_handler_lazyopen);
+ svn_stream_set_skip(stream, skip_handler_lazyopen);
+ svn_stream_set_write(stream, write_handler_lazyopen);
+ svn_stream_set_close(stream, close_handler_lazyopen);
+ svn_stream_set_mark(stream, mark_handler_lazyopen);
+ svn_stream_set_seek(stream, seek_handler_lazyopen);
+ svn_stream__set_is_buffered(stream, is_buffered_lazyopen);
+
+ return stream;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/string.c b/contrib/subversion/subversion/libsvn_subr/string.c
new file mode 100644
index 0000000..20b0f24
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/string.c
@@ -0,0 +1,1273 @@
+/*
+ * string.c: routines to manipulate counted-length strings
+ * (svn_stringbuf_t and svn_string_t) and C strings.
+ *
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr.h>
+
+#include <string.h> /* for memcpy(), memcmp(), strlen() */
+#include <apr_fnmatch.h>
+#include "svn_string.h" /* loads "svn_types.h" and <apr_pools.h> */
+#include "svn_ctype.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_string_private.h"
+
+#include "svn_private_config.h"
+
+
+
+/* Allocate the space for a memory buffer from POOL.
+ * Return a pointer to the new buffer in *DATA and its size in *SIZE.
+ * The buffer size will be at least MINIMUM_SIZE.
+ *
+ * N.B.: The stringbuf creation functions use this, but since stringbufs
+ * always consume at least 1 byte for the NUL terminator, the
+ * resulting data pointers will never be NULL.
+ */
+static APR_INLINE void
+membuf_create(void **data, apr_size_t *size,
+ apr_size_t minimum_size, apr_pool_t *pool)
+{
+ /* apr_palloc will allocate multiples of 8.
+ * Thus, we would waste some of that memory if we stuck to the
+ * smaller size. Note that this is safe even if apr_palloc would
+ * use some other aligment or none at all. */
+ minimum_size = APR_ALIGN_DEFAULT(minimum_size);
+ *data = (!minimum_size ? NULL : apr_palloc(pool, minimum_size));
+ *size = minimum_size;
+}
+
+/* Ensure that the size of a given memory buffer is at least MINIMUM_SIZE
+ * bytes. If *SIZE is already greater than or equal to MINIMUM_SIZE,
+ * this function does nothing.
+ *
+ * If *SIZE is 0, the allocated buffer size will be MINIMUM_SIZE
+ * rounded up to the nearest APR alignment boundary. Otherwse, *SIZE
+ * will be multiplied by a power of two such that the result is
+ * greater or equal to MINIMUM_SIZE. The pointer to the new buffer
+ * will be returned in *DATA, and its size in *SIZE.
+ */
+static APR_INLINE void
+membuf_ensure(void **data, apr_size_t *size,
+ apr_size_t minimum_size, apr_pool_t *pool)
+{
+ if (minimum_size > *size)
+ {
+ apr_size_t new_size = *size;
+
+ if (new_size == 0)
+ /* APR will increase odd allocation sizes to the next
+ * multiple for 8, for instance. Take advantage of that
+ * knowledge and allow for the extra size to be used. */
+ new_size = minimum_size;
+ else
+ while (new_size < minimum_size)
+ {
+ /* new_size is aligned; doubling it should keep it aligned */
+ const apr_size_t prev_size = new_size;
+ new_size *= 2;
+
+ /* check for apr_size_t overflow */
+ if (prev_size > new_size)
+ {
+ new_size = minimum_size;
+ break;
+ }
+ }
+
+ membuf_create(data, size, new_size, pool);
+ }
+}
+
+void
+svn_membuf__create(svn_membuf_t *membuf, apr_size_t size, apr_pool_t *pool)
+{
+ membuf_create(&membuf->data, &membuf->size, size, pool);
+ membuf->pool = pool;
+}
+
+void
+svn_membuf__ensure(svn_membuf_t *membuf, apr_size_t size)
+{
+ membuf_ensure(&membuf->data, &membuf->size, size, membuf->pool);
+}
+
+void
+svn_membuf__resize(svn_membuf_t *membuf, apr_size_t size)
+{
+ const void *const old_data = membuf->data;
+ const apr_size_t old_size = membuf->size;
+
+ membuf_ensure(&membuf->data, &membuf->size, size, membuf->pool);
+ if (membuf->data && old_data && old_data != membuf->data)
+ memcpy(membuf->data, old_data, old_size);
+}
+
+/* Always provide an out-of-line implementation of svn_membuf__zero */
+#undef svn_membuf__zero
+void
+svn_membuf__zero(svn_membuf_t *membuf)
+{
+ SVN_MEMBUF__ZERO(membuf);
+}
+
+/* Always provide an out-of-line implementation of svn_membuf__nzero */
+#undef svn_membuf__nzero
+void
+svn_membuf__nzero(svn_membuf_t *membuf, apr_size_t size)
+{
+ SVN_MEMBUF__NZERO(membuf, size);
+}
+
+static APR_INLINE svn_boolean_t
+string_compare(const char *str1,
+ const char *str2,
+ apr_size_t len1,
+ apr_size_t len2)
+{
+ /* easy way out :) */
+ if (len1 != len2)
+ return FALSE;
+
+ /* now the strings must have identical lenghths */
+
+ if ((memcmp(str1, str2, len1)) == 0)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static APR_INLINE apr_size_t
+string_first_non_whitespace(const char *str, apr_size_t len)
+{
+ apr_size_t i;
+
+ for (i = 0; i < len; i++)
+ {
+ if (! svn_ctype_isspace(str[i]))
+ return i;
+ }
+
+ /* if we get here, then the string must be entirely whitespace */
+ return len;
+}
+
+static APR_INLINE apr_size_t
+find_char_backward(const char *str, apr_size_t len, char ch)
+{
+ apr_size_t i = len;
+
+ while (i != 0)
+ {
+ if (str[--i] == ch)
+ return i;
+ }
+
+ /* char was not found, return len */
+ return len;
+}
+
+
+/* svn_string functions */
+
+/* Return a new svn_string_t object, allocated in POOL, initialized with
+ * DATA and SIZE. Do not copy the contents of DATA, just store the pointer.
+ * SIZE is the length in bytes of DATA, excluding the required NUL
+ * terminator. */
+static svn_string_t *
+create_string(const char *data, apr_size_t size,
+ apr_pool_t *pool)
+{
+ svn_string_t *new_string;
+
+ new_string = apr_palloc(pool, sizeof(*new_string));
+
+ new_string->data = data;
+ new_string->len = size;
+
+ return new_string;
+}
+
+/* A data buffer for a zero-length string (just a null terminator). Many
+ * svn_string_t instances may share this same buffer. */
+static const char empty_buffer[1] = {0};
+
+svn_string_t *
+svn_string_create_empty(apr_pool_t *pool)
+{
+ svn_string_t *new_string = apr_palloc(pool, sizeof(*new_string));
+ new_string->data = empty_buffer;
+ new_string->len = 0;
+
+ return new_string;
+}
+
+
+svn_string_t *
+svn_string_ncreate(const char *bytes, apr_size_t size, apr_pool_t *pool)
+{
+ void *mem;
+ char *data;
+ svn_string_t *new_string;
+
+ /* Allocate memory for svn_string_t and data in one chunk. */
+ mem = apr_palloc(pool, sizeof(*new_string) + size + 1);
+ data = (char*)mem + sizeof(*new_string);
+
+ new_string = mem;
+ new_string->data = data;
+ new_string->len = size;
+
+ memcpy(data, bytes, size);
+
+ /* Null termination is the convention -- even if we suspect the data
+ to be binary, it's not up to us to decide, it's the caller's
+ call. Heck, that's why they call it the caller! */
+ data[size] = '\0';
+
+ return new_string;
+}
+
+
+svn_string_t *
+svn_string_create(const char *cstring, apr_pool_t *pool)
+{
+ return svn_string_ncreate(cstring, strlen(cstring), pool);
+}
+
+
+svn_string_t *
+svn_string_create_from_buf(const svn_stringbuf_t *strbuf, apr_pool_t *pool)
+{
+ return svn_string_ncreate(strbuf->data, strbuf->len, pool);
+}
+
+
+svn_string_t *
+svn_string_createv(apr_pool_t *pool, const char *fmt, va_list ap)
+{
+ char *data = apr_pvsprintf(pool, fmt, ap);
+
+ /* wrap an svn_string_t around the new data */
+ return create_string(data, strlen(data), pool);
+}
+
+
+svn_string_t *
+svn_string_createf(apr_pool_t *pool, const char *fmt, ...)
+{
+ svn_string_t *str;
+
+ va_list ap;
+ va_start(ap, fmt);
+ str = svn_string_createv(pool, fmt, ap);
+ va_end(ap);
+
+ return str;
+}
+
+
+svn_boolean_t
+svn_string_isempty(const svn_string_t *str)
+{
+ return (str->len == 0);
+}
+
+
+svn_string_t *
+svn_string_dup(const svn_string_t *original_string, apr_pool_t *pool)
+{
+ return (svn_string_ncreate(original_string->data,
+ original_string->len, pool));
+}
+
+
+
+svn_boolean_t
+svn_string_compare(const svn_string_t *str1, const svn_string_t *str2)
+{
+ return
+ string_compare(str1->data, str2->data, str1->len, str2->len);
+}
+
+
+
+apr_size_t
+svn_string_first_non_whitespace(const svn_string_t *str)
+{
+ return
+ string_first_non_whitespace(str->data, str->len);
+}
+
+
+apr_size_t
+svn_string_find_char_backward(const svn_string_t *str, char ch)
+{
+ return find_char_backward(str->data, str->len, ch);
+}
+
+svn_string_t *
+svn_stringbuf__morph_into_string(svn_stringbuf_t *strbuf)
+{
+ /* In debug mode, detect attempts to modify the original STRBUF object.
+ */
+#ifdef SVN_DEBUG
+ strbuf->pool = NULL;
+ strbuf->blocksize = strbuf->len + 1;
+#endif
+
+ /* Both, svn_string_t and svn_stringbuf_t are public API structures
+ * since the svn epoch. Thus, we can rely on their precise layout not
+ * to change.
+ *
+ * It just so happens that svn_string_t is structurally equivalent
+ * to the (data, len) sub-set of svn_stringbuf_t. There is also no
+ * difference in alignment and padding. So, we can just re-interpret
+ * that part of STRBUF as a svn_string_t.
+ *
+ * However, since svn_string_t does not know about the blocksize
+ * member in svn_stringbuf_t, any attempt to re-size the returned
+ * svn_string_t might invalidate the STRBUF struct. Hence, we consider
+ * the source STRBUF "consumed".
+ *
+ * Modifying the string character content is fine, though.
+ */
+ return (svn_string_t *)&strbuf->data;
+}
+
+
+
+/* svn_stringbuf functions */
+
+svn_stringbuf_t *
+svn_stringbuf_create_empty(apr_pool_t *pool)
+{
+ return svn_stringbuf_create_ensure(0, pool);
+}
+
+svn_stringbuf_t *
+svn_stringbuf_create_ensure(apr_size_t blocksize, apr_pool_t *pool)
+{
+ void *mem;
+ svn_stringbuf_t *new_string;
+
+ ++blocksize; /* + space for '\0' */
+
+ /* Allocate memory for svn_string_t and data in one chunk. */
+ membuf_create(&mem, &blocksize, blocksize + sizeof(*new_string), pool);
+
+ /* Initialize header and string */
+ new_string = mem;
+ new_string->data = (char*)mem + sizeof(*new_string);
+ new_string->data[0] = '\0';
+ new_string->len = 0;
+ new_string->blocksize = blocksize - sizeof(*new_string);
+ new_string->pool = pool;
+
+ return new_string;
+}
+
+svn_stringbuf_t *
+svn_stringbuf_ncreate(const char *bytes, apr_size_t size, apr_pool_t *pool)
+{
+ svn_stringbuf_t *strbuf = svn_stringbuf_create_ensure(size, pool);
+ memcpy(strbuf->data, bytes, size);
+
+ /* Null termination is the convention -- even if we suspect the data
+ to be binary, it's not up to us to decide, it's the caller's
+ call. Heck, that's why they call it the caller! */
+ strbuf->data[size] = '\0';
+ strbuf->len = size;
+
+ return strbuf;
+}
+
+
+svn_stringbuf_t *
+svn_stringbuf_create(const char *cstring, apr_pool_t *pool)
+{
+ return svn_stringbuf_ncreate(cstring, strlen(cstring), pool);
+}
+
+
+svn_stringbuf_t *
+svn_stringbuf_create_from_string(const svn_string_t *str, apr_pool_t *pool)
+{
+ return svn_stringbuf_ncreate(str->data, str->len, pool);
+}
+
+
+svn_stringbuf_t *
+svn_stringbuf_createv(apr_pool_t *pool, const char *fmt, va_list ap)
+{
+ char *data = apr_pvsprintf(pool, fmt, ap);
+ apr_size_t size = strlen(data);
+ svn_stringbuf_t *new_string;
+
+ new_string = apr_palloc(pool, sizeof(*new_string));
+ new_string->data = data;
+ new_string->len = size;
+ new_string->blocksize = size + 1;
+ new_string->pool = pool;
+
+ return new_string;
+}
+
+
+svn_stringbuf_t *
+svn_stringbuf_createf(apr_pool_t *pool, const char *fmt, ...)
+{
+ svn_stringbuf_t *str;
+
+ va_list ap;
+ va_start(ap, fmt);
+ str = svn_stringbuf_createv(pool, fmt, ap);
+ va_end(ap);
+
+ return str;
+}
+
+
+void
+svn_stringbuf_fillchar(svn_stringbuf_t *str, unsigned char c)
+{
+ memset(str->data, c, str->len);
+}
+
+
+void
+svn_stringbuf_set(svn_stringbuf_t *str, const char *value)
+{
+ apr_size_t amt = strlen(value);
+
+ svn_stringbuf_ensure(str, amt);
+ memcpy(str->data, value, amt + 1);
+ str->len = amt;
+}
+
+void
+svn_stringbuf_setempty(svn_stringbuf_t *str)
+{
+ if (str->len > 0)
+ str->data[0] = '\0';
+
+ str->len = 0;
+}
+
+
+void
+svn_stringbuf_chop(svn_stringbuf_t *str, apr_size_t nbytes)
+{
+ if (nbytes > str->len)
+ str->len = 0;
+ else
+ str->len -= nbytes;
+
+ str->data[str->len] = '\0';
+}
+
+
+svn_boolean_t
+svn_stringbuf_isempty(const svn_stringbuf_t *str)
+{
+ return (str->len == 0);
+}
+
+
+void
+svn_stringbuf_ensure(svn_stringbuf_t *str, apr_size_t minimum_size)
+{
+ void *mem = NULL;
+ ++minimum_size; /* + space for '\0' */
+
+ membuf_ensure(&mem, &str->blocksize, minimum_size, str->pool);
+ if (mem && mem != str->data)
+ {
+ if (str->data)
+ memcpy(mem, str->data, str->len + 1);
+ str->data = mem;
+ }
+}
+
+
+/* WARNING - Optimized code ahead!
+ * This function has been hand-tuned for performance. Please read
+ * the comments below before modifying the code.
+ */
+void
+svn_stringbuf_appendbyte(svn_stringbuf_t *str, char byte)
+{
+ char *dest;
+ apr_size_t old_len = str->len;
+
+ /* In most cases, there will be pre-allocated memory left
+ * to just write the new byte at the end of the used section
+ * and terminate the string properly.
+ */
+ if (str->blocksize > old_len + 1)
+ {
+ /* The following read does not depend this write, so we
+ * can issue the write first to minimize register pressure:
+ * The value of old_len+1 is no longer needed; on most processors,
+ * dest[old_len+1] will be calculated implicitly as part of
+ * the addressing scheme.
+ */
+ str->len = old_len+1;
+
+ /* Since the compiler cannot be sure that *src->data and *src
+ * don't overlap, we read src->data *once* before writing
+ * to *src->data. Replacing dest with str->data would force
+ * the compiler to read it again after the first byte.
+ */
+ dest = str->data;
+
+ /* If not already available in a register as per ABI, load
+ * "byte" into the register (e.g. the one freed from old_len+1),
+ * then write it to the string buffer and terminate it properly.
+ *
+ * Including the "byte" fetch, all operations so far could be
+ * issued at once and be scheduled at the CPU's descression.
+ * Most likely, no-one will soon depend on the data that will be
+ * written in this function. So, no stalls there, either.
+ */
+ dest[old_len] = byte;
+ dest[old_len+1] = '\0';
+ }
+ else
+ {
+ /* we need to re-allocate the string buffer
+ * -> let the more generic implementation take care of that part
+ */
+
+ /* Depending on the ABI, "byte" is a register value. If we were
+ * to take its address directly, the compiler might decide to
+ * put in on the stack *unconditionally*, even if that would
+ * only be necessary for this block.
+ */
+ char b = byte;
+ svn_stringbuf_appendbytes(str, &b, 1);
+ }
+}
+
+
+void
+svn_stringbuf_appendbytes(svn_stringbuf_t *str, const char *bytes,
+ apr_size_t count)
+{
+ apr_size_t total_len;
+ void *start_address;
+
+ total_len = str->len + count; /* total size needed */
+
+ /* svn_stringbuf_ensure adds 1 for null terminator. */
+ svn_stringbuf_ensure(str, total_len);
+
+ /* get address 1 byte beyond end of original bytestring */
+ start_address = (str->data + str->len);
+
+ memcpy(start_address, bytes, count);
+ str->len = total_len;
+
+ str->data[str->len] = '\0'; /* We don't know if this is binary
+ data or not, but convention is
+ to null-terminate. */
+}
+
+
+void
+svn_stringbuf_appendstr(svn_stringbuf_t *targetstr,
+ const svn_stringbuf_t *appendstr)
+{
+ svn_stringbuf_appendbytes(targetstr, appendstr->data, appendstr->len);
+}
+
+
+void
+svn_stringbuf_appendcstr(svn_stringbuf_t *targetstr, const char *cstr)
+{
+ svn_stringbuf_appendbytes(targetstr, cstr, strlen(cstr));
+}
+
+void
+svn_stringbuf_insert(svn_stringbuf_t *str,
+ apr_size_t pos,
+ const char *bytes,
+ apr_size_t count)
+{
+ if (bytes + count > str->data && bytes < str->data + str->blocksize)
+ {
+ /* special case: BYTES overlaps with this string -> copy the source */
+ const char *temp = apr_pstrndup(str->pool, bytes, count);
+ svn_stringbuf_insert(str, pos, temp, count);
+ }
+ else
+ {
+ if (pos > str->len)
+ pos = str->len;
+
+ svn_stringbuf_ensure(str, str->len + count);
+ memmove(str->data + pos + count, str->data + pos, str->len - pos + 1);
+ memcpy(str->data + pos, bytes, count);
+
+ str->len += count;
+ }
+}
+
+void
+svn_stringbuf_remove(svn_stringbuf_t *str,
+ apr_size_t pos,
+ apr_size_t count)
+{
+ if (pos > str->len)
+ pos = str->len;
+ if (pos + count > str->len)
+ count = str->len - pos;
+
+ memmove(str->data + pos, str->data + pos + count, str->len - pos - count + 1);
+ str->len -= count;
+}
+
+void
+svn_stringbuf_replace(svn_stringbuf_t *str,
+ apr_size_t pos,
+ apr_size_t old_count,
+ const char *bytes,
+ apr_size_t new_count)
+{
+ if (bytes + new_count > str->data && bytes < str->data + str->blocksize)
+ {
+ /* special case: BYTES overlaps with this string -> copy the source */
+ const char *temp = apr_pstrndup(str->pool, bytes, new_count);
+ svn_stringbuf_replace(str, pos, old_count, temp, new_count);
+ }
+ else
+ {
+ if (pos > str->len)
+ pos = str->len;
+ if (pos + old_count > str->len)
+ old_count = str->len - pos;
+
+ if (old_count < new_count)
+ {
+ apr_size_t delta = new_count - old_count;
+ svn_stringbuf_ensure(str, str->len + delta);
+ }
+
+ if (old_count != new_count)
+ memmove(str->data + pos + new_count, str->data + pos + old_count,
+ str->len - pos - old_count + 1);
+
+ memcpy(str->data + pos, bytes, new_count);
+ str->len += new_count - old_count;
+ }
+}
+
+
+svn_stringbuf_t *
+svn_stringbuf_dup(const svn_stringbuf_t *original_string, apr_pool_t *pool)
+{
+ return (svn_stringbuf_ncreate(original_string->data,
+ original_string->len, pool));
+}
+
+
+
+svn_boolean_t
+svn_stringbuf_compare(const svn_stringbuf_t *str1,
+ const svn_stringbuf_t *str2)
+{
+ return string_compare(str1->data, str2->data, str1->len, str2->len);
+}
+
+
+
+apr_size_t
+svn_stringbuf_first_non_whitespace(const svn_stringbuf_t *str)
+{
+ return string_first_non_whitespace(str->data, str->len);
+}
+
+
+void
+svn_stringbuf_strip_whitespace(svn_stringbuf_t *str)
+{
+ /* Find first non-whitespace character */
+ apr_size_t offset = svn_stringbuf_first_non_whitespace(str);
+
+ /* Go ahead! Waste some RAM, we've got pools! :) */
+ str->data += offset;
+ str->len -= offset;
+ str->blocksize -= offset;
+
+ /* Now that we've trimmed the front, trim the end, wasting more RAM. */
+ while ((str->len > 0) && svn_ctype_isspace(str->data[str->len - 1]))
+ str->len--;
+ str->data[str->len] = '\0';
+}
+
+
+apr_size_t
+svn_stringbuf_find_char_backward(const svn_stringbuf_t *str, char ch)
+{
+ return find_char_backward(str->data, str->len, ch);
+}
+
+
+svn_boolean_t
+svn_string_compare_stringbuf(const svn_string_t *str1,
+ const svn_stringbuf_t *str2)
+{
+ return string_compare(str1->data, str2->data, str1->len, str2->len);
+}
+
+
+
+/*** C string stuff. ***/
+
+void
+svn_cstring_split_append(apr_array_header_t *array,
+ const char *input,
+ const char *sep_chars,
+ svn_boolean_t chop_whitespace,
+ apr_pool_t *pool)
+{
+ char *pats;
+ char *p;
+
+ pats = apr_pstrdup(pool, input); /* strtok wants non-const data */
+ p = svn_cstring_tokenize(sep_chars, &pats);
+
+ while (p)
+ {
+ if (chop_whitespace)
+ {
+ while (svn_ctype_isspace(*p))
+ p++;
+
+ {
+ char *e = p + (strlen(p) - 1);
+ while ((e >= p) && (svn_ctype_isspace(*e)))
+ e--;
+ *(++e) = '\0';
+ }
+ }
+
+ if (p[0] != '\0')
+ APR_ARRAY_PUSH(array, const char *) = p;
+
+ p = svn_cstring_tokenize(sep_chars, &pats);
+ }
+
+ return;
+}
+
+
+apr_array_header_t *
+svn_cstring_split(const char *input,
+ const char *sep_chars,
+ svn_boolean_t chop_whitespace,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *a = apr_array_make(pool, 5, sizeof(input));
+ svn_cstring_split_append(a, input, sep_chars, chop_whitespace, pool);
+ return a;
+}
+
+
+svn_boolean_t svn_cstring_match_glob_list(const char *str,
+ const apr_array_header_t *list)
+{
+ int i;
+
+ for (i = 0; i < list->nelts; i++)
+ {
+ const char *this_pattern = APR_ARRAY_IDX(list, i, char *);
+
+ if (apr_fnmatch(this_pattern, str, 0) == APR_SUCCESS)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+svn_boolean_t
+svn_cstring_match_list(const char *str, const apr_array_header_t *list)
+{
+ int i;
+
+ for (i = 0; i < list->nelts; i++)
+ {
+ const char *this_str = APR_ARRAY_IDX(list, i, char *);
+
+ if (strcmp(this_str, str) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+char *
+svn_cstring_tokenize(const char *sep, char **str)
+{
+ char *token;
+ const char * next;
+ char csep;
+
+ /* check parameters */
+ if ((sep == NULL) || (str == NULL) || (*str == NULL))
+ return NULL;
+
+ /* let APR handle edge cases and multiple separators */
+ csep = *sep;
+ if (csep == '\0' || sep[1] != '\0')
+ return apr_strtok(NULL, sep, str);
+
+ /* skip characters in sep (will terminate at '\0') */
+ token = *str;
+ while (*token == csep)
+ ++token;
+
+ if (!*token) /* no more tokens */
+ return NULL;
+
+ /* skip valid token characters to terminate token and
+ * prepare for the next call (will terminate at '\0)
+ */
+ next = strchr(token, csep);
+ if (next == NULL)
+ {
+ *str = token + strlen(token);
+ }
+ else
+ {
+ *(char *)next = '\0';
+ *str = (char *)next + 1;
+ }
+
+ return token;
+}
+
+int svn_cstring_count_newlines(const char *msg)
+{
+ int count = 0;
+ const char *p;
+
+ for (p = msg; *p; p++)
+ {
+ if (*p == '\n')
+ {
+ count++;
+ if (*(p + 1) == '\r')
+ p++;
+ }
+ else if (*p == '\r')
+ {
+ count++;
+ if (*(p + 1) == '\n')
+ p++;
+ }
+ }
+
+ return count;
+}
+
+char *
+svn_cstring_join(const apr_array_header_t *strings,
+ const char *separator,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *new_str = svn_stringbuf_create_empty(pool);
+ size_t sep_len = strlen(separator);
+ int i;
+
+ for (i = 0; i < strings->nelts; i++)
+ {
+ const char *string = APR_ARRAY_IDX(strings, i, const char *);
+ svn_stringbuf_appendbytes(new_str, string, strlen(string));
+ svn_stringbuf_appendbytes(new_str, separator, sep_len);
+ }
+ return new_str->data;
+}
+
+int
+svn_cstring_casecmp(const char *str1, const char *str2)
+{
+ for (;;)
+ {
+ const int a = *str1++;
+ const int b = *str2++;
+ const int cmp = svn_ctype_casecmp(a, b);
+ if (cmp || !a || !b)
+ return cmp;
+ }
+}
+
+svn_error_t *
+svn_cstring_strtoui64(apr_uint64_t *n, const char *str,
+ apr_uint64_t minval, apr_uint64_t maxval,
+ int base)
+{
+ apr_int64_t val;
+ char *endptr;
+
+ /* We assume errno is thread-safe. */
+ errno = 0; /* APR-0.9 doesn't always set errno */
+
+ /* ### We're throwing away half the number range here.
+ * ### APR needs a apr_strtoui64() function. */
+ val = apr_strtoi64(str, &endptr, base);
+ if (errno == EINVAL || endptr == str || str[0] == '\0' || *endptr != '\0')
+ return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Could not convert '%s' into a number"),
+ str);
+ if ((errno == ERANGE && (val == APR_INT64_MIN || val == APR_INT64_MAX)) ||
+ val < 0 || (apr_uint64_t)val < minval || (apr_uint64_t)val > maxval)
+ /* ### Mark this for translation when gettext doesn't choke on macros. */
+ return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "Number '%s' is out of range "
+ "'[%" APR_UINT64_T_FMT ", %" APR_UINT64_T_FMT "]'",
+ str, minval, maxval);
+ *n = val;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cstring_atoui64(apr_uint64_t *n, const char *str)
+{
+ return svn_error_trace(svn_cstring_strtoui64(n, str, 0,
+ APR_UINT64_MAX, 10));
+}
+
+svn_error_t *
+svn_cstring_atoui(unsigned int *n, const char *str)
+{
+ apr_uint64_t val;
+
+ SVN_ERR(svn_cstring_strtoui64(&val, str, 0, APR_UINT32_MAX, 10));
+ *n = (unsigned int)val;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cstring_strtoi64(apr_int64_t *n, const char *str,
+ apr_int64_t minval, apr_int64_t maxval,
+ int base)
+{
+ apr_int64_t val;
+ char *endptr;
+
+ /* We assume errno is thread-safe. */
+ errno = 0; /* APR-0.9 doesn't always set errno */
+
+ val = apr_strtoi64(str, &endptr, base);
+ if (errno == EINVAL || endptr == str || str[0] == '\0' || *endptr != '\0')
+ return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Could not convert '%s' into a number"),
+ str);
+ if ((errno == ERANGE && (val == APR_INT64_MIN || val == APR_INT64_MAX)) ||
+ val < minval || val > maxval)
+ /* ### Mark this for translation when gettext doesn't choke on macros. */
+ return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "Number '%s' is out of range "
+ "'[%" APR_INT64_T_FMT ", %" APR_INT64_T_FMT "]'",
+ str, minval, maxval);
+ *n = val;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cstring_atoi64(apr_int64_t *n, const char *str)
+{
+ return svn_error_trace(svn_cstring_strtoi64(n, str, APR_INT64_MIN,
+ APR_INT64_MAX, 10));
+}
+
+svn_error_t *
+svn_cstring_atoi(int *n, const char *str)
+{
+ apr_int64_t val;
+
+ SVN_ERR(svn_cstring_strtoi64(&val, str, APR_INT32_MIN, APR_INT32_MAX, 10));
+ *n = (int)val;
+ return SVN_NO_ERROR;
+}
+
+
+apr_status_t
+svn__strtoff(apr_off_t *offset, const char *buf, char **end, int base)
+{
+#if !APR_VERSION_AT_LEAST(1,0,0)
+ errno = 0;
+ *offset = strtol(buf, end, base);
+ return APR_FROM_OS_ERROR(errno);
+#else
+ return apr_strtoff(offset, buf, end, base);
+#endif
+}
+
+/* "Precalculated" itoa values for 2 places (including leading zeros).
+ * For maximum performance, make sure all table entries are word-aligned.
+ */
+static const char decimal_table[100][4]
+ = { "00", "01", "02", "03", "04", "05", "06", "07", "08", "09"
+ , "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"
+ , "20", "21", "22", "23", "24", "25", "26", "27", "28", "29"
+ , "30", "31", "32", "33", "34", "35", "36", "37", "38", "39"
+ , "40", "41", "42", "43", "44", "45", "46", "47", "48", "49"
+ , "50", "51", "52", "53", "54", "55", "56", "57", "58", "59"
+ , "60", "61", "62", "63", "64", "65", "66", "67", "68", "69"
+ , "70", "71", "72", "73", "74", "75", "76", "77", "78", "79"
+ , "80", "81", "82", "83", "84", "85", "86", "87", "88", "89"
+ , "90", "91", "92", "93", "94", "95", "96", "97", "98", "99"};
+
+/* Copy the two bytes at SOURCE[0] and SOURCE[1] to DEST[0] and DEST[1] */
+#define COPY_TWO_BYTES(dest,source)\
+ memcpy((dest), (source), 2)
+
+apr_size_t
+svn__ui64toa(char * dest, apr_uint64_t number)
+{
+ char buffer[SVN_INT64_BUFFER_SIZE];
+ apr_uint32_t reduced; /* used for 32 bit DIV */
+ char* target;
+
+ /* Small numbers are by far the most common case.
+ * Therefore, we use special code.
+ */
+ if (number < 100)
+ {
+ if (number < 10)
+ {
+ dest[0] = (char)('0' + number);
+ dest[1] = 0;
+ return 1;
+ }
+ else
+ {
+ COPY_TWO_BYTES(dest, decimal_table[(apr_size_t)number]);
+ dest[2] = 0;
+ return 2;
+ }
+ }
+
+ /* Standard code. Write string in pairs of chars back-to-front */
+ buffer[SVN_INT64_BUFFER_SIZE - 1] = 0;
+ target = &buffer[SVN_INT64_BUFFER_SIZE - 3];
+
+ /* Loop may be executed 0 .. 2 times. */
+ while (number >= 100000000)
+ {
+ /* Number is larger than 100^4, i.e. we can write 4x2 chars.
+ * Also, use 32 bit DIVs as these are about twice as fast.
+ */
+ reduced = (apr_uint32_t)(number % 100000000);
+ number /= 100000000;
+
+ COPY_TWO_BYTES(target - 0, decimal_table[reduced % 100]);
+ reduced /= 100;
+ COPY_TWO_BYTES(target - 2, decimal_table[reduced % 100]);
+ reduced /= 100;
+ COPY_TWO_BYTES(target - 4, decimal_table[reduced % 100]);
+ reduced /= 100;
+ COPY_TWO_BYTES(target - 6, decimal_table[reduced % 100]);
+ target -= 8;
+ }
+
+ /* Now, the number fits into 32 bits, but may still be larger than 99 */
+ reduced = (apr_uint32_t)(number);
+ while (reduced >= 100)
+ {
+ COPY_TWO_BYTES(target, decimal_table[reduced % 100]);
+ reduced /= 100;
+ target -= 2;
+ }
+
+ /* The number is now smaller than 100 but larger than 1 */
+ COPY_TWO_BYTES(target, decimal_table[reduced]);
+
+ /* Correction for uneven count of places. */
+ if (reduced < 10)
+ ++target;
+
+ /* Copy to target */
+ memcpy(dest, target, &buffer[SVN_INT64_BUFFER_SIZE] - target);
+ return &buffer[SVN_INT64_BUFFER_SIZE] - target - 1;
+}
+
+apr_size_t
+svn__i64toa(char * dest, apr_int64_t number)
+{
+ if (number >= 0)
+ return svn__ui64toa(dest, (apr_uint64_t)number);
+
+ *dest = '-';
+ return svn__ui64toa(dest + 1, (apr_uint64_t)(0-number)) + 1;
+}
+
+static void
+ui64toa_sep(apr_uint64_t number, char seperator, char *buffer)
+{
+ apr_size_t length = svn__ui64toa(buffer, number);
+ apr_size_t i;
+
+ for (i = length; i > 3; i -= 3)
+ {
+ memmove(&buffer[i - 2], &buffer[i - 3], length - i + 3);
+ buffer[i-3] = seperator;
+ length++;
+ }
+
+ buffer[length] = 0;
+}
+
+char *
+svn__ui64toa_sep(apr_uint64_t number, char seperator, apr_pool_t *pool)
+{
+ char buffer[2 * SVN_INT64_BUFFER_SIZE];
+ ui64toa_sep(number, seperator, buffer);
+
+ return apr_pstrdup(pool, buffer);
+}
+
+char *
+svn__i64toa_sep(apr_int64_t number, char seperator, apr_pool_t *pool)
+{
+ char buffer[2 * SVN_INT64_BUFFER_SIZE];
+ if (number < 0)
+ {
+ buffer[0] = '-';
+ ui64toa_sep((apr_uint64_t)(-number), seperator, &buffer[1]);
+ }
+ else
+ ui64toa_sep((apr_uint64_t)(number), seperator, buffer);
+
+ return apr_pstrdup(pool, buffer);
+}
+
+unsigned int
+svn_cstring__similarity(const char *stra, const char *strb,
+ svn_membuf_t *buffer, apr_size_t *rlcs)
+{
+ svn_string_t stringa, stringb;
+ stringa.data = stra;
+ stringa.len = strlen(stra);
+ stringb.data = strb;
+ stringb.len = strlen(strb);
+ return svn_string__similarity(&stringa, &stringb, buffer, rlcs);
+}
+
+unsigned int
+svn_string__similarity(const svn_string_t *stringa,
+ const svn_string_t *stringb,
+ svn_membuf_t *buffer, apr_size_t *rlcs)
+{
+ const char *stra = stringa->data;
+ const char *strb = stringb->data;
+ const apr_size_t lena = stringa->len;
+ const apr_size_t lenb = stringb->len;
+ const apr_size_t total = lena + lenb;
+ const char *enda = stra + lena;
+ const char *endb = strb + lenb;
+ apr_size_t lcs = 0;
+
+ /* Skip the common prefix ... */
+ while (stra < enda && strb < endb && *stra == *strb)
+ {
+ ++stra; ++strb;
+ ++lcs;
+ }
+
+ /* ... and the common suffix */
+ while (stra < enda && strb < endb)
+ {
+ --enda; --endb;
+ if (*enda != *endb)
+ {
+ ++enda; ++endb;
+ break;
+ }
+
+ ++lcs;
+ }
+
+ if (stra < enda && strb < endb)
+ {
+ const apr_size_t resta = enda - stra;
+ const apr_size_t restb = endb - strb;
+ const apr_size_t slots = (resta > restb ? restb : resta);
+ apr_size_t *curr, *prev;
+ const char *pstr;
+
+ /* The outer loop must iterate on the longer string. */
+ if (resta < restb)
+ {
+ pstr = stra;
+ stra = strb;
+ strb = pstr;
+
+ pstr = enda;
+ enda = endb;
+ endb = pstr;
+ }
+
+ /* Allocate two columns in the LCS matrix
+ ### Optimize this to (slots + 2) instesd of 2 * (slots + 1) */
+ svn_membuf__ensure(buffer, 2 * (slots + 1) * sizeof(apr_size_t));
+ svn_membuf__nzero(buffer, (slots + 2) * sizeof(apr_size_t));
+ prev = buffer->data;
+ curr = prev + slots + 1;
+
+ /* Calculate LCS length of the remainder */
+ for (pstr = stra; pstr < enda; ++pstr)
+ {
+ int i;
+ for (i = 1; i <= slots; ++i)
+ {
+ if (*pstr == strb[i-1])
+ curr[i] = prev[i-1] + 1;
+ else
+ curr[i] = (curr[i-1] > prev[i] ? curr[i-1] : prev[i]);
+ }
+
+ /* Swap the buffers, making the previous one current */
+ {
+ apr_size_t *const temp = prev;
+ prev = curr;
+ curr = temp;
+ }
+ }
+
+ lcs += prev[slots];
+ }
+
+ if (rlcs)
+ *rlcs = lcs;
+
+ /* Return similarity ratio rounded to 4 significant digits */
+ if (total)
+ return(unsigned int)((2000 * lcs + total/2) / total);
+ else
+ return 1000;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/subst.c b/contrib/subversion/subversion/libsvn_subr/subst.c
new file mode 100644
index 0000000..f69dcf8
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/subst.c
@@ -0,0 +1,2025 @@
+/*
+ * subst.c : generic eol/keyword substitution routines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include <stdlib.h>
+#include <assert.h>
+#include <apr_pools.h>
+#include <apr_tables.h>
+#include <apr_file_io.h>
+#include <apr_strings.h>
+
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_time.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_io.h"
+#include "svn_subst.h"
+#include "svn_pools.h"
+#include "private/svn_io_private.h"
+
+#include "svn_private_config.h"
+
+#include "private/svn_string_private.h"
+
+/**
+ * The textual elements of a detranslated special file. One of these
+ * strings must appear as the first element of any special file as it
+ * exists in the repository or the text base.
+ */
+#define SVN_SUBST__SPECIAL_LINK_STR "link"
+
+void
+svn_subst_eol_style_from_value(svn_subst_eol_style_t *style,
+ const char **eol,
+ const char *value)
+{
+ if (value == NULL)
+ {
+ /* property doesn't exist. */
+ *eol = NULL;
+ if (style)
+ *style = svn_subst_eol_style_none;
+ }
+ else if (! strcmp("native", value))
+ {
+ *eol = APR_EOL_STR; /* whee, a portability library! */
+ if (style)
+ *style = svn_subst_eol_style_native;
+ }
+ else if (! strcmp("LF", value))
+ {
+ *eol = "\n";
+ if (style)
+ *style = svn_subst_eol_style_fixed;
+ }
+ else if (! strcmp("CR", value))
+ {
+ *eol = "\r";
+ if (style)
+ *style = svn_subst_eol_style_fixed;
+ }
+ else if (! strcmp("CRLF", value))
+ {
+ *eol = "\r\n";
+ if (style)
+ *style = svn_subst_eol_style_fixed;
+ }
+ else
+ {
+ *eol = NULL;
+ if (style)
+ *style = svn_subst_eol_style_unknown;
+ }
+}
+
+
+svn_boolean_t
+svn_subst_translation_required(svn_subst_eol_style_t style,
+ const char *eol,
+ apr_hash_t *keywords,
+ svn_boolean_t special,
+ svn_boolean_t force_eol_check)
+{
+ return (special || keywords
+ || (style != svn_subst_eol_style_none && force_eol_check)
+ || (style == svn_subst_eol_style_native &&
+ strcmp(APR_EOL_STR, SVN_SUBST_NATIVE_EOL_STR) != 0)
+ || (style == svn_subst_eol_style_fixed &&
+ strcmp(APR_EOL_STR, eol) != 0));
+}
+
+
+
+/* Helper function for svn_subst_build_keywords */
+
+/* Given a printf-like format string, return a string with proper
+ * information filled in.
+ *
+ * Important API note: This function is the core of the implementation of
+ * svn_subst_build_keywords (all versions), and as such must implement the
+ * tolerance of NULL and zero inputs that that function's documention
+ * stipulates.
+ *
+ * The format codes:
+ *
+ * %a author of this revision
+ * %b basename of the URL of this file
+ * %d short format of date of this revision
+ * %D long format of date of this revision
+ * %P path relative to root of repos
+ * %r number of this revision
+ * %R root url of repository
+ * %u URL of this file
+ * %_ a space
+ * %% a literal %
+ *
+ * The following special format codes are also recognized:
+ * %H is equivalent to %P%_%r%_%d%_%a
+ * %I is equivalent to %b%_%r%_%d%_%a
+ *
+ * All memory is allocated out of @a pool.
+ */
+static svn_string_t *
+keyword_printf(const char *fmt,
+ const char *rev,
+ const char *url,
+ const char *repos_root_url,
+ apr_time_t date,
+ const char *author,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *value = svn_stringbuf_ncreate("", 0, pool);
+ const char *cur;
+ size_t n;
+
+ for (;;)
+ {
+ cur = fmt;
+
+ while (*cur != '\0' && *cur != '%')
+ cur++;
+
+ if ((n = cur - fmt) > 0) /* Do we have an as-is string? */
+ svn_stringbuf_appendbytes(value, fmt, n);
+
+ if (*cur == '\0')
+ break;
+
+ switch (cur[1])
+ {
+ case 'a': /* author of this revision */
+ if (author)
+ svn_stringbuf_appendcstr(value, author);
+ break;
+ case 'b': /* basename of this file */
+ if (url && *url)
+ {
+ const char *base_name = svn_uri_basename(url, pool);
+ svn_stringbuf_appendcstr(value, base_name);
+ }
+ break;
+ case 'd': /* short format of date of this revision */
+ if (date)
+ {
+ apr_time_exp_t exploded_time;
+ const char *human;
+
+ apr_time_exp_gmt(&exploded_time, date);
+
+ human = apr_psprintf(pool, "%04d-%02d-%02d %02d:%02d:%02dZ",
+ exploded_time.tm_year + 1900,
+ exploded_time.tm_mon + 1,
+ exploded_time.tm_mday,
+ exploded_time.tm_hour,
+ exploded_time.tm_min,
+ exploded_time.tm_sec);
+
+ svn_stringbuf_appendcstr(value, human);
+ }
+ break;
+ case 'D': /* long format of date of this revision */
+ if (date)
+ svn_stringbuf_appendcstr(value,
+ svn_time_to_human_cstring(date, pool));
+ break;
+ case 'P': /* relative path of this file */
+ if (repos_root_url && *repos_root_url != '\0' && url && *url != '\0')
+ {
+ const char *repos_relpath;
+
+ repos_relpath = svn_uri_skip_ancestor(repos_root_url, url, pool);
+ if (repos_relpath)
+ svn_stringbuf_appendcstr(value, repos_relpath);
+ }
+ break;
+ case 'R': /* root of repos */
+ if (repos_root_url && *repos_root_url != '\0')
+ svn_stringbuf_appendcstr(value, repos_root_url);
+ break;
+ case 'r': /* number of this revision */
+ if (rev)
+ svn_stringbuf_appendcstr(value, rev);
+ break;
+ case 'u': /* URL of this file */
+ if (url)
+ svn_stringbuf_appendcstr(value, url);
+ break;
+ case '_': /* '%_' => a space */
+ svn_stringbuf_appendbyte(value, ' ');
+ break;
+ case '%': /* '%%' => a literal % */
+ svn_stringbuf_appendbyte(value, *cur);
+ break;
+ case '\0': /* '%' as the last character of the string. */
+ svn_stringbuf_appendbyte(value, *cur);
+ /* Now go back one character, since this was just a one character
+ * sequence, whereas all others are two characters, and we do not
+ * want to skip the null terminator entirely and carry on
+ * formatting random memory contents. */
+ cur--;
+ break;
+ case 'H':
+ {
+ svn_string_t *s = keyword_printf("%P%_%r%_%d%_%a", rev, url,
+ repos_root_url, date, author,
+ pool);
+ svn_stringbuf_appendcstr(value, s->data);
+ }
+ break;
+ case 'I':
+ {
+ svn_string_t *s = keyword_printf("%b%_%r%_%d%_%a", rev, url,
+ repos_root_url, date, author,
+ pool);
+ svn_stringbuf_appendcstr(value, s->data);
+ }
+ break;
+ default: /* Unrecognized code, just print it literally. */
+ svn_stringbuf_appendbytes(value, cur, 2);
+ break;
+ }
+
+ /* Format code is processed - skip it, and get ready for next chunk. */
+ fmt = cur + 2;
+ }
+
+ return svn_stringbuf__morph_into_string(value);
+}
+
+static svn_error_t *
+build_keywords(apr_hash_t **kw,
+ svn_boolean_t expand_custom_keywords,
+ const char *keywords_val,
+ const char *rev,
+ const char *url,
+ const char *repos_root_url,
+ apr_time_t date,
+ const char *author,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *keyword_tokens;
+ int i;
+ *kw = apr_hash_make(pool);
+
+ keyword_tokens = svn_cstring_split(keywords_val, " \t\v\n\b\r\f",
+ TRUE /* chop */, pool);
+
+ for (i = 0; i < keyword_tokens->nelts; ++i)
+ {
+ const char *keyword = APR_ARRAY_IDX(keyword_tokens, i, const char *);
+ const char *custom_fmt = NULL;
+
+ if (expand_custom_keywords)
+ {
+ char *sep;
+
+ /* Check if there is a custom keyword definition, started by '='. */
+ sep = strchr(keyword, '=');
+ if (sep)
+ {
+ *sep = '\0'; /* Split keyword's name from custom format. */
+ custom_fmt = sep + 1;
+ }
+ }
+
+ if (custom_fmt)
+ {
+ svn_string_t *custom_val;
+
+ /* Custom keywords must be allowed to match the name of an
+ * existing fixed keyword. This is for compatibility purposes,
+ * in case new fixed keywords are added to Subversion which
+ * happen to match a custom keyword defined somewhere.
+ * There is only one global namespace for keyword names. */
+ custom_val = keyword_printf(custom_fmt, rev, url, repos_root_url,
+ date, author, pool);
+ svn_hash_sets(*kw, keyword, custom_val);
+ }
+ else if ((! strcmp(keyword, SVN_KEYWORD_REVISION_LONG))
+ || (! strcmp(keyword, SVN_KEYWORD_REVISION_MEDIUM))
+ || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_REVISION_SHORT)))
+ {
+ svn_string_t *revision_val;
+
+ revision_val = keyword_printf("%r", rev, url, repos_root_url,
+ date, author, pool);
+ svn_hash_sets(*kw, SVN_KEYWORD_REVISION_LONG, revision_val);
+ svn_hash_sets(*kw, SVN_KEYWORD_REVISION_MEDIUM, revision_val);
+ svn_hash_sets(*kw, SVN_KEYWORD_REVISION_SHORT, revision_val);
+ }
+ else if ((! strcmp(keyword, SVN_KEYWORD_DATE_LONG))
+ || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_DATE_SHORT)))
+ {
+ svn_string_t *date_val;
+
+ date_val = keyword_printf("%D", rev, url, repos_root_url, date,
+ author, pool);
+ svn_hash_sets(*kw, SVN_KEYWORD_DATE_LONG, date_val);
+ svn_hash_sets(*kw, SVN_KEYWORD_DATE_SHORT, date_val);
+ }
+ else if ((! strcmp(keyword, SVN_KEYWORD_AUTHOR_LONG))
+ || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_AUTHOR_SHORT)))
+ {
+ svn_string_t *author_val;
+
+ author_val = keyword_printf("%a", rev, url, repos_root_url, date,
+ author, pool);
+ svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_LONG, author_val);
+ svn_hash_sets(*kw, SVN_KEYWORD_AUTHOR_SHORT, author_val);
+ }
+ else if ((! strcmp(keyword, SVN_KEYWORD_URL_LONG))
+ || (! svn_cstring_casecmp(keyword, SVN_KEYWORD_URL_SHORT)))
+ {
+ svn_string_t *url_val;
+
+ url_val = keyword_printf("%u", rev, url, repos_root_url, date,
+ author, pool);
+ svn_hash_sets(*kw, SVN_KEYWORD_URL_LONG, url_val);
+ svn_hash_sets(*kw, SVN_KEYWORD_URL_SHORT, url_val);
+ }
+ else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_ID)))
+ {
+ svn_string_t *id_val;
+
+ id_val = keyword_printf("%b %r %d %a", rev, url, repos_root_url,
+ date, author, pool);
+ svn_hash_sets(*kw, SVN_KEYWORD_ID, id_val);
+ }
+ else if ((! svn_cstring_casecmp(keyword, SVN_KEYWORD_HEADER)))
+ {
+ svn_string_t *header_val;
+
+ header_val = keyword_printf("%u %r %d %a", rev, url, repos_root_url,
+ date, author, pool);
+ svn_hash_sets(*kw, SVN_KEYWORD_HEADER, header_val);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_subst_build_keywords2(apr_hash_t **kw,
+ const char *keywords_val,
+ const char *rev,
+ const char *url,
+ apr_time_t date,
+ const char *author,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(build_keywords(kw, FALSE, keywords_val, rev, url,
+ NULL, date, author, pool));
+}
+
+
+svn_error_t *
+svn_subst_build_keywords3(apr_hash_t **kw,
+ const char *keywords_val,
+ const char *rev,
+ const char *url,
+ const char *repos_root_url,
+ apr_time_t date,
+ const char *author,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(build_keywords(kw, TRUE, keywords_val,
+ rev, url, repos_root_url,
+ date, author, pool));
+}
+
+
+/*** Helpers for svn_subst_translate_stream2 ***/
+
+
+/* Write out LEN bytes of BUF into STREAM. */
+/* ### TODO: 'stream_write()' would be a better name for this. */
+static svn_error_t *
+translate_write(svn_stream_t *stream,
+ const void *buf,
+ apr_size_t len)
+{
+ SVN_ERR(svn_stream_write(stream, buf, &len));
+ /* (No need to check LEN, as a short write always produces an error.) */
+ return SVN_NO_ERROR;
+}
+
+
+/* Perform the substitution of VALUE into keyword string BUF (with len
+ *LEN), given a pre-parsed KEYWORD (and KEYWORD_LEN), and updating
+ *LEN to the new size of the substituted result. Return TRUE if all
+ goes well, FALSE otherwise. If VALUE is NULL, keyword will be
+ contracted, else it will be expanded. */
+static svn_boolean_t
+translate_keyword_subst(char *buf,
+ apr_size_t *len,
+ const char *keyword,
+ apr_size_t keyword_len,
+ const svn_string_t *value)
+{
+ char *buf_ptr;
+
+ /* Make sure we gotz good stuffs. */
+ assert(*len <= SVN_KEYWORD_MAX_LEN);
+ assert((buf[0] == '$') && (buf[*len - 1] == '$'));
+
+ /* Need at least a keyword and two $'s. */
+ if (*len < keyword_len + 2)
+ return FALSE;
+
+ /* Need at least space for two $'s, two spaces and a colon, and that
+ leaves zero space for the value itself. */
+ if (keyword_len > SVN_KEYWORD_MAX_LEN - 5)
+ return FALSE;
+
+ /* The keyword needs to match what we're looking for. */
+ if (strncmp(buf + 1, keyword, keyword_len))
+ return FALSE;
+
+ buf_ptr = buf + 1 + keyword_len;
+
+ /* Check for fixed-length expansion.
+ * The format of fixed length keyword and its data is
+ * Unexpanded keyword: "$keyword:: $"
+ * Expanded keyword: "$keyword:: value $"
+ * Expanded kw with filling: "$keyword:: value $"
+ * Truncated keyword: "$keyword:: longval#$"
+ */
+ if ((buf_ptr[0] == ':') /* first char after keyword is ':' */
+ && (buf_ptr[1] == ':') /* second char after keyword is ':' */
+ && (buf_ptr[2] == ' ') /* third char after keyword is ' ' */
+ && ((buf[*len - 2] == ' ') /* has ' ' for next to last character */
+ || (buf[*len - 2] == '#')) /* .. or has '#' for next to last
+ character */
+ && ((6 + keyword_len) < *len)) /* holds "$kw:: x $" at least */
+ {
+ /* This is fixed length keyword, so *len remains unchanged */
+ apr_size_t max_value_len = *len - (6 + keyword_len);
+
+ if (! value)
+ {
+ /* no value, so unexpand */
+ buf_ptr += 2;
+ while (*buf_ptr != '$')
+ *(buf_ptr++) = ' ';
+ }
+ else
+ {
+ if (value->len <= max_value_len)
+ { /* replacement not as long as template, pad with spaces */
+ strncpy(buf_ptr + 3, value->data, value->len);
+ buf_ptr += 3 + value->len;
+ while (*buf_ptr != '$')
+ *(buf_ptr++) = ' ';
+ }
+ else
+ {
+ /* replacement needs truncating */
+ strncpy(buf_ptr + 3, value->data, max_value_len);
+ buf[*len - 2] = '#';
+ buf[*len - 1] = '$';
+ }
+ }
+ return TRUE;
+ }
+
+ /* Check for unexpanded keyword. */
+ else if (buf_ptr[0] == '$') /* "$keyword$" */
+ {
+ /* unexpanded... */
+ if (value)
+ {
+ /* ...so expand. */
+ buf_ptr[0] = ':';
+ buf_ptr[1] = ' ';
+ if (value->len)
+ {
+ apr_size_t vallen = value->len;
+
+ /* "$keyword: value $" */
+ if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
+ vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
+ strncpy(buf_ptr + 2, value->data, vallen);
+ buf_ptr[2 + vallen] = ' ';
+ buf_ptr[2 + vallen + 1] = '$';
+ *len = 5 + keyword_len + vallen;
+ }
+ else
+ {
+ /* "$keyword: $" */
+ buf_ptr[2] = '$';
+ *len = 4 + keyword_len;
+ }
+ }
+ else
+ {
+ /* ...but do nothing. */
+ }
+ return TRUE;
+ }
+
+ /* Check for expanded keyword. */
+ else if (((*len >= 4 + keyword_len ) /* holds at least "$keyword: $" */
+ && (buf_ptr[0] == ':') /* first char after keyword is ':' */
+ && (buf_ptr[1] == ' ') /* second char after keyword is ' ' */
+ && (buf[*len - 2] == ' '))
+ || ((*len >= 3 + keyword_len ) /* holds at least "$keyword:$" */
+ && (buf_ptr[0] == ':') /* first char after keyword is ':' */
+ && (buf_ptr[1] == '$'))) /* second char after keyword is '$' */
+ {
+ /* expanded... */
+ if (! value)
+ {
+ /* ...so unexpand. */
+ buf_ptr[0] = '$';
+ *len = 2 + keyword_len;
+ }
+ else
+ {
+ /* ...so re-expand. */
+ buf_ptr[0] = ':';
+ buf_ptr[1] = ' ';
+ if (value->len)
+ {
+ apr_size_t vallen = value->len;
+
+ /* "$keyword: value $" */
+ if (vallen > (SVN_KEYWORD_MAX_LEN - 5 - keyword_len))
+ vallen = SVN_KEYWORD_MAX_LEN - 5 - keyword_len;
+ strncpy(buf_ptr + 2, value->data, vallen);
+ buf_ptr[2 + vallen] = ' ';
+ buf_ptr[2 + vallen + 1] = '$';
+ *len = 5 + keyword_len + vallen;
+ }
+ else
+ {
+ /* "$keyword: $" */
+ buf_ptr[2] = '$';
+ *len = 4 + keyword_len;
+ }
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Parse BUF (whose length is LEN, and which starts and ends with '$'),
+ trying to match one of the keyword names in KEYWORDS. If such a
+ keyword is found, update *KEYWORD_NAME with the keyword name and
+ return TRUE. */
+static svn_boolean_t
+match_keyword(char *buf,
+ apr_size_t len,
+ char *keyword_name,
+ apr_hash_t *keywords)
+{
+ apr_size_t i;
+
+ /* Early return for ignored keywords */
+ if (! keywords)
+ return FALSE;
+
+ /* Extract the name of the keyword */
+ for (i = 0; i < len - 2 && buf[i + 1] != ':'; i++)
+ keyword_name[i] = buf[i + 1];
+ keyword_name[i] = '\0';
+
+ return svn_hash_gets(keywords, keyword_name) != NULL;
+}
+
+/* Try to translate keyword *KEYWORD_NAME in BUF (whose length is LEN):
+ optionally perform the substitution in place, update *LEN with
+ the new length of the translated keyword string, and return TRUE.
+ If this buffer doesn't contain a known keyword pattern, leave BUF
+ and *LEN untouched and return FALSE.
+
+ See the docstring for svn_subst_copy_and_translate for how the
+ EXPAND and KEYWORDS parameters work.
+
+ NOTE: It is assumed that BUF has been allocated to be at least
+ SVN_KEYWORD_MAX_LEN bytes longs, and that the data in BUF is less
+ than or equal SVN_KEYWORD_MAX_LEN in length. Also, any expansions
+ which would result in a keyword string which is greater than
+ SVN_KEYWORD_MAX_LEN will have their values truncated in such a way
+ that the resultant keyword string is still valid (begins with
+ "$Keyword:", ends in " $" and is SVN_KEYWORD_MAX_LEN bytes long). */
+static svn_boolean_t
+translate_keyword(char *buf,
+ apr_size_t *len,
+ const char *keyword_name,
+ svn_boolean_t expand,
+ apr_hash_t *keywords)
+{
+ const svn_string_t *value;
+
+ /* Make sure we gotz good stuffs. */
+ assert(*len <= SVN_KEYWORD_MAX_LEN);
+ assert((buf[0] == '$') && (buf[*len - 1] == '$'));
+
+ /* Early return for ignored keywords */
+ if (! keywords)
+ return FALSE;
+
+ value = svn_hash_gets(keywords, keyword_name);
+
+ if (value)
+ {
+ return translate_keyword_subst(buf, len,
+ keyword_name, strlen(keyword_name),
+ expand ? value : NULL);
+ }
+
+ return FALSE;
+}
+
+/* A boolean expression that evaluates to true if the first STR_LEN characters
+ of the string STR are one of the end-of-line strings LF, CR, or CRLF;
+ to false otherwise. */
+#define STRING_IS_EOL(str, str_len) \
+ (((str_len) == 2 && (str)[0] == '\r' && (str)[1] == '\n') || \
+ ((str_len) == 1 && ((str)[0] == '\n' || (str)[0] == '\r')))
+
+/* A boolean expression that evaluates to true if the end-of-line string EOL1,
+ having length EOL1_LEN, and the end-of-line string EOL2, having length
+ EOL2_LEN, are different, assuming that EOL1 and EOL2 are both from the
+ set {"\n", "\r", "\r\n"}; to false otherwise.
+
+ Given that EOL1 and EOL2 are either "\n", "\r", or "\r\n", then if
+ EOL1_LEN is not the same as EOL2_LEN, then EOL1 and EOL2 are of course
+ different. If EOL1_LEN and EOL2_LEN are both 2 then EOL1 and EOL2 are both
+ "\r\n" and *EOL1 == *EOL2. Otherwise, EOL1_LEN and EOL2_LEN are both 1.
+ We need only check the one character for equality to determine whether
+ EOL1 and EOL2 are different in that case. */
+#define DIFFERENT_EOL_STRINGS(eol1, eol1_len, eol2, eol2_len) \
+ (((eol1_len) != (eol2_len)) || (*(eol1) != *(eol2)))
+
+
+/* Translate the newline string NEWLINE_BUF (of length NEWLINE_LEN) to
+ the newline string EOL_STR (of length EOL_STR_LEN), writing the
+ result (which is always EOL_STR) to the stream DST.
+
+ This function assumes that NEWLINE_BUF is either "\n", "\r", or "\r\n".
+
+ Also check for consistency of the source newline strings across
+ multiple calls, using SRC_FORMAT (length *SRC_FORMAT_LEN) as a cache
+ of the first newline found. If the current newline is not the same
+ as SRC_FORMAT, look to the REPAIR parameter. If REPAIR is TRUE,
+ ignore the inconsistency, else return an SVN_ERR_IO_INCONSISTENT_EOL
+ error. If *SRC_FORMAT_LEN is 0, assume we are examining the first
+ newline in the file, and copy it to {SRC_FORMAT, *SRC_FORMAT_LEN} to
+ use for later consistency checks.
+
+ If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if the
+ newline string that was written (EOL_STR) is not the same as the newline
+ string that was translated (NEWLINE_BUF), otherwise leave *TRANSLATED_EOL
+ untouched.
+
+ Note: all parameters are required even if REPAIR is TRUE.
+ ### We could require that REPAIR must not change across a sequence of
+ calls, and could then optimize by not using SRC_FORMAT at all if
+ REPAIR is TRUE.
+*/
+static svn_error_t *
+translate_newline(const char *eol_str,
+ apr_size_t eol_str_len,
+ char *src_format,
+ apr_size_t *src_format_len,
+ const char *newline_buf,
+ apr_size_t newline_len,
+ svn_stream_t *dst,
+ svn_boolean_t *translated_eol,
+ svn_boolean_t repair)
+{
+ SVN_ERR_ASSERT(STRING_IS_EOL(newline_buf, newline_len));
+
+ /* If we've seen a newline before, compare it with our cache to
+ check for consistency, else cache it for future comparisons. */
+ if (*src_format_len)
+ {
+ /* Comparing with cache. If we are inconsistent and
+ we are NOT repairing the file, generate an error! */
+ if ((! repair) && DIFFERENT_EOL_STRINGS(src_format, *src_format_len,
+ newline_buf, newline_len))
+ return svn_error_create(SVN_ERR_IO_INCONSISTENT_EOL, NULL, NULL);
+ }
+ else
+ {
+ /* This is our first line ending, so cache it before
+ handling it. */
+ strncpy(src_format, newline_buf, newline_len);
+ *src_format_len = newline_len;
+ }
+
+ /* Write the desired newline */
+ SVN_ERR(translate_write(dst, eol_str, eol_str_len));
+
+ /* Report whether we translated it. Note: Not using DIFFERENT_EOL_STRINGS()
+ * because EOL_STR may not be a valid EOL sequence. */
+ if (translated_eol != NULL &&
+ (eol_str_len != newline_len ||
+ memcmp(eol_str, newline_buf, eol_str_len) != 0))
+ *translated_eol = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Public interfaces. ***/
+
+svn_boolean_t
+svn_subst_keywords_differ(const svn_subst_keywords_t *a,
+ const svn_subst_keywords_t *b,
+ svn_boolean_t compare_values)
+{
+ if (((a == NULL) && (b == NULL)) /* no A or B */
+ /* no A, and B has no contents */
+ || ((a == NULL)
+ && (b->revision == NULL)
+ && (b->date == NULL)
+ && (b->author == NULL)
+ && (b->url == NULL))
+ /* no B, and A has no contents */
+ || ((b == NULL) && (a->revision == NULL)
+ && (a->date == NULL)
+ && (a->author == NULL)
+ && (a->url == NULL))
+ /* neither A nor B has any contents */
+ || ((a != NULL) && (b != NULL)
+ && (b->revision == NULL)
+ && (b->date == NULL)
+ && (b->author == NULL)
+ && (b->url == NULL)
+ && (a->revision == NULL)
+ && (a->date == NULL)
+ && (a->author == NULL)
+ && (a->url == NULL)))
+ {
+ return FALSE;
+ }
+ else if ((a == NULL) || (b == NULL))
+ return TRUE;
+
+ /* Else both A and B have some keywords. */
+
+ if ((! a->revision) != (! b->revision))
+ return TRUE;
+ else if ((compare_values && (a->revision != NULL))
+ && (strcmp(a->revision->data, b->revision->data) != 0))
+ return TRUE;
+
+ if ((! a->date) != (! b->date))
+ return TRUE;
+ else if ((compare_values && (a->date != NULL))
+ && (strcmp(a->date->data, b->date->data) != 0))
+ return TRUE;
+
+ if ((! a->author) != (! b->author))
+ return TRUE;
+ else if ((compare_values && (a->author != NULL))
+ && (strcmp(a->author->data, b->author->data) != 0))
+ return TRUE;
+
+ if ((! a->url) != (! b->url))
+ return TRUE;
+ else if ((compare_values && (a->url != NULL))
+ && (strcmp(a->url->data, b->url->data) != 0))
+ return TRUE;
+
+ /* Else we never found a difference, so they must be the same. */
+
+ return FALSE;
+}
+
+svn_boolean_t
+svn_subst_keywords_differ2(apr_hash_t *a,
+ apr_hash_t *b,
+ svn_boolean_t compare_values,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ unsigned int a_count, b_count;
+
+ /* An empty hash is logically equal to a NULL,
+ * as far as this API is concerned. */
+ a_count = (a == NULL) ? 0 : apr_hash_count(a);
+ b_count = (b == NULL) ? 0 : apr_hash_count(b);
+
+ if (a_count != b_count)
+ return TRUE;
+
+ if (a_count == 0)
+ return FALSE;
+
+ /* The hashes are both non-NULL, and have the same number of items.
+ * We must check that every item of A is present in B. */
+ for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *void_a_val;
+ svn_string_t *a_val, *b_val;
+
+ apr_hash_this(hi, &key, &klen, &void_a_val);
+ a_val = void_a_val;
+ b_val = apr_hash_get(b, key, klen);
+
+ if (!b_val || (compare_values && !svn_string_compare(a_val, b_val)))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/* Baton for translate_chunk() to store its state in. */
+struct translation_baton
+{
+ const char *eol_str;
+ svn_boolean_t *translated_eol;
+ svn_boolean_t repair;
+ apr_hash_t *keywords;
+ svn_boolean_t expand;
+
+ /* 'short boolean' array that encodes what character values
+ may trigger a translation action, hence are 'interesting' */
+ char interesting[256];
+
+ /* Length of the string EOL_STR points to. */
+ apr_size_t eol_str_len;
+
+ /* Buffer to cache any newline state between translation chunks */
+ char newline_buf[2];
+
+ /* Offset (within newline_buf) of the first *unused* character */
+ apr_size_t newline_off;
+
+ /* Buffer to cache keyword-parsing state between translation chunks */
+ char keyword_buf[SVN_KEYWORD_MAX_LEN];
+
+ /* Offset (within keyword-buf) to the first *unused* character */
+ apr_size_t keyword_off;
+
+ /* EOL style used in the chunk-source */
+ char src_format[2];
+
+ /* Length of the EOL style string found in the chunk-source,
+ or zero if none encountered yet */
+ apr_size_t src_format_len;
+
+ /* If this is svn_tristate_false, translate_newline() will be called
+ for every newline in the file */
+ svn_tristate_t nl_translation_skippable;
+};
+
+
+/* Allocate a baton for use with translate_chunk() in POOL and
+ * initialize it for the first iteration.
+ *
+ * The caller must assure that EOL_STR and KEYWORDS at least
+ * have the same life time as that of POOL.
+ */
+static struct translation_baton *
+create_translation_baton(const char *eol_str,
+ svn_boolean_t *translated_eol,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *pool)
+{
+ struct translation_baton *b = apr_palloc(pool, sizeof(*b));
+
+ /* For efficiency, convert an empty set of keywords to NULL. */
+ if (keywords && (apr_hash_count(keywords) == 0))
+ keywords = NULL;
+
+ b->eol_str = eol_str;
+ b->eol_str_len = eol_str ? strlen(eol_str) : 0;
+ b->translated_eol = translated_eol;
+ b->repair = repair;
+ b->keywords = keywords;
+ b->expand = expand;
+ b->newline_off = 0;
+ b->keyword_off = 0;
+ b->src_format_len = 0;
+ b->nl_translation_skippable = svn_tristate_unknown;
+
+ /* Most characters don't start translation actions.
+ * Mark those that do depending on the parameters we got. */
+ memset(b->interesting, FALSE, sizeof(b->interesting));
+ if (keywords)
+ b->interesting['$'] = TRUE;
+ if (eol_str)
+ {
+ b->interesting['\r'] = TRUE;
+ b->interesting['\n'] = TRUE;
+ }
+
+ return b;
+}
+
+/* Return TRUE if the EOL starting at BUF matches the eol_str member of B.
+ * Be aware of special cases like "\n\r\n" and "\n\n\r". For sequences like
+ * "\n$" (an EOL followed by a keyword), the result will be FALSE since it is
+ * more efficient to handle that special case implicitly in the calling code
+ * by exiting the quick scan loop.
+ * The caller must ensure that buf[0] and buf[1] refer to valid memory
+ * locations.
+ */
+static APR_INLINE svn_boolean_t
+eol_unchanged(struct translation_baton *b,
+ const char *buf)
+{
+ /* If the first byte doesn't match, the whole EOL won't.
+ * This does also handle the (certainly invalid) case that
+ * eol_str would be an empty string.
+ */
+ if (buf[0] != b->eol_str[0])
+ return FALSE;
+
+ /* two-char EOLs must be a full match */
+ if (b->eol_str_len == 2)
+ return buf[1] == b->eol_str[1];
+
+ /* The first char matches the required 1-byte EOL.
+ * But maybe, buf[] contains a 2-byte EOL?
+ * In that case, the second byte will be interesting
+ * and not be another EOL of its own.
+ */
+ return !b->interesting[(unsigned char)buf[1]] || buf[0] == buf[1];
+}
+
+
+/* Translate eols and keywords of a 'chunk' of characters BUF of size BUFLEN
+ * according to the settings and state stored in baton B.
+ *
+ * Write output to stream DST.
+ *
+ * To finish a series of chunk translations, flush all buffers by calling
+ * this routine with a NULL value for BUF.
+ *
+ * If B->translated_eol is not NULL, then set *B->translated_eol to TRUE if
+ * an end-of-line sequence was changed, otherwise leave it untouched.
+ *
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+translate_chunk(svn_stream_t *dst,
+ struct translation_baton *b,
+ const char *buf,
+ apr_size_t buflen,
+ apr_pool_t *pool)
+{
+ const char *p;
+ apr_size_t len;
+
+ if (buf)
+ {
+ /* precalculate some oft-used values */
+ const char *end = buf + buflen;
+ const char *interesting = b->interesting;
+ apr_size_t next_sign_off = 0;
+
+ /* At the beginning of this loop, assume that we might be in an
+ * interesting state, i.e. with data in the newline or keyword
+ * buffer. First try to get to the boring state so we can copy
+ * a run of boring characters; then try to get back to the
+ * interesting state by processing an interesting character,
+ * and repeat. */
+ for (p = buf; p < end;)
+ {
+ /* Try to get to the boring state, if necessary. */
+ if (b->newline_off)
+ {
+ if (*p == '\n')
+ b->newline_buf[b->newline_off++] = *p++;
+
+ SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
+ b->src_format,
+ &b->src_format_len, b->newline_buf,
+ b->newline_off, dst, b->translated_eol,
+ b->repair));
+
+ b->newline_off = 0;
+ }
+ else if (b->keyword_off && *p == '$')
+ {
+ svn_boolean_t keyword_matches;
+ char keyword_name[SVN_KEYWORD_MAX_LEN + 1];
+
+ /* If keyword is matched, but not correctly translated, try to
+ * look for the next ending '$'. */
+ b->keyword_buf[b->keyword_off++] = *p++;
+ keyword_matches = match_keyword(b->keyword_buf, b->keyword_off,
+ keyword_name, b->keywords);
+ if (!keyword_matches)
+ {
+ /* reuse the ending '$' */
+ p--;
+ b->keyword_off--;
+ }
+
+ if (!keyword_matches ||
+ translate_keyword(b->keyword_buf, &b->keyword_off,
+ keyword_name, b->expand, b->keywords) ||
+ b->keyword_off >= SVN_KEYWORD_MAX_LEN)
+ {
+ /* write out non-matching text or translated keyword */
+ SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
+
+ next_sign_off = 0;
+ b->keyword_off = 0;
+ }
+ else
+ {
+ if (next_sign_off == 0)
+ next_sign_off = b->keyword_off - 1;
+
+ continue;
+ }
+ }
+ else if (b->keyword_off == SVN_KEYWORD_MAX_LEN - 1
+ || (b->keyword_off && (*p == '\r' || *p == '\n')))
+ {
+ if (next_sign_off > 0)
+ {
+ /* rolling back, continue with next '$' in keyword_buf */
+ p -= (b->keyword_off - next_sign_off);
+ b->keyword_off = next_sign_off;
+ next_sign_off = 0;
+ }
+ /* No closing '$' found; flush the keyword buffer. */
+ SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
+
+ b->keyword_off = 0;
+ }
+ else if (b->keyword_off)
+ {
+ b->keyword_buf[b->keyword_off++] = *p++;
+ continue;
+ }
+
+ /* translate_newline will modify the baton for src_format_len==0
+ or may return an error if b->repair is FALSE. In all other
+ cases, we can skip the newline translation as long as source
+ EOL format and actual EOL format match. If there is a
+ mismatch, translate_newline will be called regardless of
+ nl_translation_skippable.
+ */
+ if (b->nl_translation_skippable == svn_tristate_unknown &&
+ b->src_format_len > 0)
+ {
+ /* test whether translate_newline may return an error */
+ if (b->eol_str_len == b->src_format_len &&
+ strncmp(b->eol_str, b->src_format, b->eol_str_len) == 0)
+ b->nl_translation_skippable = svn_tristate_true;
+ else if (b->repair)
+ b->nl_translation_skippable = svn_tristate_true;
+ else
+ b->nl_translation_skippable = svn_tristate_false;
+ }
+
+ /* We're in the boring state; look for interesting characters.
+ Offset len such that it will become 0 in the first iteration.
+ */
+ len = 0 - b->eol_str_len;
+
+ /* Look for the next EOL (or $) that actually needs translation.
+ Stop there or at EOF, whichever is encountered first.
+ */
+ do
+ {
+ /* skip current EOL */
+ len += b->eol_str_len;
+
+ /* Check 4 bytes at once to allow for efficient pipelining
+ and to reduce loop condition overhead. */
+ while ((p + len + 4) <= end)
+ {
+ if (interesting[(unsigned char)p[len]]
+ || interesting[(unsigned char)p[len+1]]
+ || interesting[(unsigned char)p[len+2]]
+ || interesting[(unsigned char)p[len+3]])
+ break;
+
+ len += 4;
+ }
+
+ /* Found an interesting char or EOF in the next 4 bytes.
+ Find its exact position. */
+ while ((p + len) < end && !interesting[(unsigned char)p[len]])
+ ++len;
+ }
+ while (b->nl_translation_skippable ==
+ svn_tristate_true && /* can potentially skip EOLs */
+ p + len + 2 < end && /* not too close to EOF */
+ eol_unchanged (b, p + len)); /* EOL format already ok */
+
+ while ((p + len) < end && !interesting[(unsigned char)p[len]])
+ len++;
+
+ if (len)
+ {
+ SVN_ERR(translate_write(dst, p, len));
+ p += len;
+ }
+
+ /* Set up state according to the interesting character, if any. */
+ if (p < end)
+ {
+ switch (*p)
+ {
+ case '$':
+ b->keyword_buf[b->keyword_off++] = *p++;
+ break;
+ case '\r':
+ b->newline_buf[b->newline_off++] = *p++;
+ break;
+ case '\n':
+ b->newline_buf[b->newline_off++] = *p++;
+
+ SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
+ b->src_format,
+ &b->src_format_len,
+ b->newline_buf,
+ b->newline_off, dst,
+ b->translated_eol, b->repair));
+
+ b->newline_off = 0;
+ break;
+
+ }
+ }
+ }
+ }
+ else
+ {
+ if (b->newline_off)
+ {
+ SVN_ERR(translate_newline(b->eol_str, b->eol_str_len,
+ b->src_format, &b->src_format_len,
+ b->newline_buf, b->newline_off,
+ dst, b->translated_eol, b->repair));
+ b->newline_off = 0;
+ }
+
+ if (b->keyword_off)
+ {
+ SVN_ERR(translate_write(dst, b->keyword_buf, b->keyword_off));
+ b->keyword_off = 0;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for use with translated stream callbacks. */
+struct translated_stream_baton
+{
+ /* Stream to take input from (before translation) on read
+ /write output to (after translation) on write. */
+ svn_stream_t *stream;
+
+ /* Input/Output translation batons to make them separate chunk streams. */
+ struct translation_baton *in_baton, *out_baton;
+
+ /* Remembers whether any write operations have taken place;
+ if so, we need to flush the output chunk stream. */
+ svn_boolean_t written;
+
+ /* Buffer to hold translated read data. */
+ svn_stringbuf_t *readbuf;
+
+ /* Offset of the first non-read character in readbuf. */
+ apr_size_t readbuf_off;
+
+ /* Buffer to hold read data
+ between svn_stream_read() and translate_chunk(). */
+ char *buf;
+#define SVN__TRANSLATION_BUF_SIZE (SVN__STREAM_CHUNK_SIZE + 1)
+
+ /* Pool for callback iterations */
+ apr_pool_t *iterpool;
+};
+
+
+/* Implements svn_read_fn_t. */
+static svn_error_t *
+translated_stream_read(void *baton,
+ char *buffer,
+ apr_size_t *len)
+{
+ struct translated_stream_baton *b = baton;
+ apr_size_t readlen = SVN__STREAM_CHUNK_SIZE;
+ apr_size_t unsatisfied = *len;
+ apr_size_t off = 0;
+
+ /* Optimization for a frequent special case. The configuration parser (and
+ a few others) reads the stream one byte at a time. All the memcpy, pool
+ clearing etc. imposes a huge overhead in that case. In most cases, we
+ can just take that single byte directly from the read buffer.
+
+ Since *len > 1 requires lots of code to be run anyways, we can afford
+ the extra overhead of checking for *len == 1.
+
+ See <http://mail-archives.apache.org/mod_mbox/subversion-dev/201003.mbox/%3C4B94011E.1070207@alice-dsl.de%3E>.
+ */
+ if (unsatisfied == 1 && b->readbuf_off < b->readbuf->len)
+ {
+ /* Just take it from the read buffer */
+ *buffer = b->readbuf->data[b->readbuf_off++];
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Standard code path. */
+ while (readlen == SVN__STREAM_CHUNK_SIZE && unsatisfied > 0)
+ {
+ apr_size_t to_copy;
+ apr_size_t buffer_remainder;
+
+ svn_pool_clear(b->iterpool);
+ /* fill read buffer, if necessary */
+ if (! (b->readbuf_off < b->readbuf->len))
+ {
+ svn_stream_t *buf_stream;
+
+ svn_stringbuf_setempty(b->readbuf);
+ b->readbuf_off = 0;
+ SVN_ERR(svn_stream_read(b->stream, b->buf, &readlen));
+ buf_stream = svn_stream_from_stringbuf(b->readbuf, b->iterpool);
+
+ SVN_ERR(translate_chunk(buf_stream, b->in_baton, b->buf,
+ readlen, b->iterpool));
+
+ if (readlen != SVN__STREAM_CHUNK_SIZE)
+ SVN_ERR(translate_chunk(buf_stream, b->in_baton, NULL, 0,
+ b->iterpool));
+
+ SVN_ERR(svn_stream_close(buf_stream));
+ }
+
+ /* Satisfy from the read buffer */
+ buffer_remainder = b->readbuf->len - b->readbuf_off;
+ to_copy = (buffer_remainder > unsatisfied)
+ ? unsatisfied : buffer_remainder;
+ memcpy(buffer + off, b->readbuf->data + b->readbuf_off, to_copy);
+ off += to_copy;
+ b->readbuf_off += to_copy;
+ unsatisfied -= to_copy;
+ }
+
+ *len -= unsatisfied;
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_write_fn_t. */
+static svn_error_t *
+translated_stream_write(void *baton,
+ const char *buffer,
+ apr_size_t *len)
+{
+ struct translated_stream_baton *b = baton;
+ svn_pool_clear(b->iterpool);
+
+ b->written = TRUE;
+ return translate_chunk(b->stream, b->out_baton, buffer, *len, b->iterpool);
+}
+
+/* Implements svn_close_fn_t. */
+static svn_error_t *
+translated_stream_close(void *baton)
+{
+ struct translated_stream_baton *b = baton;
+ svn_error_t *err = NULL;
+
+ if (b->written)
+ err = translate_chunk(b->stream, b->out_baton, NULL, 0, b->iterpool);
+
+ err = svn_error_compose_create(err, svn_stream_close(b->stream));
+
+ svn_pool_destroy(b->iterpool);
+
+ return svn_error_trace(err);
+}
+
+
+/* svn_stream_mark_t for translation streams. */
+typedef struct mark_translated_t
+{
+ /* Saved translation state. */
+ struct translated_stream_baton saved_baton;
+
+ /* Mark set on the underlying stream. */
+ svn_stream_mark_t *mark;
+} mark_translated_t;
+
+/* Implements svn_stream_mark_fn_t. */
+static svn_error_t *
+translated_stream_mark(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
+{
+ mark_translated_t *mt;
+ struct translated_stream_baton *b = baton;
+
+ mt = apr_palloc(pool, sizeof(*mt));
+ SVN_ERR(svn_stream_mark(b->stream, &mt->mark, pool));
+
+ /* Save translation state. */
+ mt->saved_baton.in_baton = apr_pmemdup(pool, b->in_baton,
+ sizeof(*mt->saved_baton.in_baton));
+ mt->saved_baton.out_baton = apr_pmemdup(pool, b->out_baton,
+ sizeof(*mt->saved_baton.out_baton));
+ mt->saved_baton.written = b->written;
+ mt->saved_baton.readbuf = svn_stringbuf_dup(b->readbuf, pool);
+ mt->saved_baton.readbuf_off = b->readbuf_off;
+ mt->saved_baton.buf = apr_pmemdup(pool, b->buf, SVN__TRANSLATION_BUF_SIZE);
+
+ *mark = (svn_stream_mark_t *)mt;
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_stream_seek_fn_t. */
+static svn_error_t *
+translated_stream_seek(void *baton, const svn_stream_mark_t *mark)
+{
+ struct translated_stream_baton *b = baton;
+
+ if (mark != NULL)
+ {
+ const mark_translated_t *mt = (const mark_translated_t *)mark;
+
+ /* Flush output buffer if necessary. */
+ if (b->written)
+ SVN_ERR(translate_chunk(b->stream, b->out_baton, NULL, 0,
+ b->iterpool));
+
+ SVN_ERR(svn_stream_seek(b->stream, mt->mark));
+
+ /* Restore translation state, avoiding new allocations. */
+ *b->in_baton = *mt->saved_baton.in_baton;
+ *b->out_baton = *mt->saved_baton.out_baton;
+ b->written = mt->saved_baton.written;
+ svn_stringbuf_setempty(b->readbuf);
+ svn_stringbuf_appendbytes(b->readbuf, mt->saved_baton.readbuf->data,
+ mt->saved_baton.readbuf->len);
+ b->readbuf_off = mt->saved_baton.readbuf_off;
+ memcpy(b->buf, mt->saved_baton.buf, SVN__TRANSLATION_BUF_SIZE);
+ }
+ else
+ {
+ SVN_ERR(svn_stream_reset(b->stream));
+
+ b->in_baton->newline_off = 0;
+ b->in_baton->keyword_off = 0;
+ b->in_baton->src_format_len = 0;
+ b->out_baton->newline_off = 0;
+ b->out_baton->keyword_off = 0;
+ b->out_baton->src_format_len = 0;
+
+ b->written = FALSE;
+ svn_stringbuf_setempty(b->readbuf);
+ b->readbuf_off = 0;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_stream__is_buffered_fn_t. */
+static svn_boolean_t
+translated_stream_is_buffered(void *baton)
+{
+ struct translated_stream_baton *b = baton;
+ return svn_stream__is_buffered(b->stream);
+}
+
+svn_error_t *
+svn_subst_read_specialfile(svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_finfo_t finfo;
+ svn_string_t *buf;
+
+ /* First determine what type of special file we are
+ detranslating. */
+ SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_MIN | APR_FINFO_LINK,
+ scratch_pool));
+
+ switch (finfo.filetype) {
+ case APR_REG:
+ /* Nothing special to do here, just create stream from the original
+ file's contents. */
+ SVN_ERR(svn_stream_open_readonly(stream, path, result_pool, scratch_pool));
+ break;
+
+ case APR_LNK:
+ /* Determine the destination of the link. */
+ SVN_ERR(svn_io_read_link(&buf, path, scratch_pool));
+ *stream = svn_stream_from_string(svn_string_createf(result_pool,
+ "link %s",
+ buf->data),
+ result_pool);
+ break;
+
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Same as svn_subst_stream_translated(), except for the following.
+ *
+ * If TRANSLATED_EOL is not NULL, then reading and/or writing to the stream
+ * will set *TRANSLATED_EOL to TRUE if an end-of-line sequence was changed,
+ * otherwise leave it untouched.
+ */
+static svn_stream_t *
+stream_translated(svn_stream_t *stream,
+ const char *eol_str,
+ svn_boolean_t *translated_eol,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *result_pool)
+{
+ struct translated_stream_baton *baton
+ = apr_palloc(result_pool, sizeof(*baton));
+ svn_stream_t *s = svn_stream_create(baton, result_pool);
+
+ /* Make sure EOL_STR and KEYWORDS are allocated in RESULT_POOL
+ so they have the same lifetime as the stream. */
+ if (eol_str)
+ eol_str = apr_pstrdup(result_pool, eol_str);
+ if (keywords)
+ {
+ if (apr_hash_count(keywords) == 0)
+ keywords = NULL;
+ else
+ {
+ /* deep copy the hash to make sure it's allocated in RESULT_POOL */
+ apr_hash_t *copy = apr_hash_make(result_pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool;
+
+ subpool = svn_pool_create(result_pool);
+ for (hi = apr_hash_first(subpool, keywords);
+ hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ svn_hash_sets(copy, apr_pstrdup(result_pool, key),
+ svn_string_dup(val, result_pool));
+ }
+ svn_pool_destroy(subpool);
+
+ keywords = copy;
+ }
+ }
+
+ /* Setup the baton fields */
+ baton->stream = stream;
+ baton->in_baton
+ = create_translation_baton(eol_str, translated_eol, repair, keywords,
+ expand, result_pool);
+ baton->out_baton
+ = create_translation_baton(eol_str, translated_eol, repair, keywords,
+ expand, result_pool);
+ baton->written = FALSE;
+ baton->readbuf = svn_stringbuf_create_empty(result_pool);
+ baton->readbuf_off = 0;
+ baton->iterpool = svn_pool_create(result_pool);
+ baton->buf = apr_palloc(result_pool, SVN__TRANSLATION_BUF_SIZE);
+
+ /* Setup the stream methods */
+ svn_stream_set_read(s, translated_stream_read);
+ svn_stream_set_write(s, translated_stream_write);
+ svn_stream_set_close(s, translated_stream_close);
+ svn_stream_set_mark(s, translated_stream_mark);
+ svn_stream_set_seek(s, translated_stream_seek);
+ svn_stream__set_is_buffered(s, translated_stream_is_buffered);
+
+ return s;
+}
+
+svn_stream_t *
+svn_subst_stream_translated(svn_stream_t *stream,
+ const char *eol_str,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *result_pool)
+{
+ return stream_translated(stream, eol_str, NULL, repair, keywords, expand,
+ result_pool);
+}
+
+/* Same as svn_subst_translate_cstring2(), except for the following.
+ *
+ * If TRANSLATED_EOL is not NULL, then set *TRANSLATED_EOL to TRUE if an
+ * end-of-line sequence was changed, or to FALSE otherwise.
+ */
+static svn_error_t *
+translate_cstring(const char **dst,
+ svn_boolean_t *translated_eol,
+ const char *src,
+ const char *eol_str,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *dst_stringbuf;
+ svn_stream_t *dst_stream;
+ apr_size_t len = strlen(src);
+
+ /* The easy way out: no translation needed, just copy. */
+ if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
+ {
+ *dst = apr_pstrmemdup(pool, src, len);
+ return SVN_NO_ERROR;
+ }
+
+ /* Create a stringbuf and wrapper stream to hold the output. */
+ dst_stringbuf = svn_stringbuf_create_empty(pool);
+ dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool);
+
+ if (translated_eol)
+ *translated_eol = FALSE;
+
+ /* Another wrapper to translate the content. */
+ dst_stream = stream_translated(dst_stream, eol_str, translated_eol, repair,
+ keywords, expand, pool);
+
+ /* Jam the text into the destination stream (to translate it). */
+ SVN_ERR(svn_stream_write(dst_stream, src, &len));
+
+ /* Close the destination stream to flush unwritten data. */
+ SVN_ERR(svn_stream_close(dst_stream));
+
+ *dst = dst_stringbuf->data;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_subst_translate_cstring2(const char *src,
+ const char **dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *pool)
+{
+ return translate_cstring(dst, NULL, src, eol_str, repair, keywords, expand,
+ pool);
+}
+
+/* Given a special file at SRC, generate a textual representation of
+ it in a normal file at DST. Perform all allocations in POOL. */
+/* ### this should be folded into svn_subst_copy_and_translate3 */
+static svn_error_t *
+detranslate_special_file(const char *src, const char *dst,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *dst_tmp;
+ svn_stream_t *src_stream;
+ svn_stream_t *dst_stream;
+
+ /* Open a temporary destination that we will eventually atomically
+ rename into place. */
+ SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
+ svn_dirent_dirname(dst, scratch_pool),
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_subst_read_specialfile(&src_stream, src,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
+ cancel_func, cancel_baton, scratch_pool));
+
+ /* Do the atomic rename from our temporary location. */
+ return svn_error_trace(svn_io_file_rename(dst_tmp, dst, scratch_pool));
+}
+
+/* Creates a special file DST from the "normal form" located in SOURCE.
+ *
+ * All temporary allocations will be done in POOL.
+ */
+static svn_error_t *
+create_special_file_from_stream(svn_stream_t *source, const char *dst,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *contents;
+ svn_boolean_t eof;
+ const char *identifier;
+ const char *remainder;
+ const char *dst_tmp;
+ svn_boolean_t create_using_internal_representation = FALSE;
+
+ SVN_ERR(svn_stream_readline(source, &contents, "\n", &eof, pool));
+
+ /* Separate off the identifier. The first space character delimits
+ the identifier, after which any remaining characters are specific
+ to the actual special file type being created. */
+ identifier = contents->data;
+ for (remainder = identifier; *remainder; remainder++)
+ {
+ if (*remainder == ' ')
+ {
+ remainder++;
+ break;
+ }
+ }
+
+ if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ",
+ sizeof(SVN_SUBST__SPECIAL_LINK_STR " ")-1))
+ {
+ /* For symlinks, the type specific data is just a filesystem
+ path that the symlink should reference. */
+ svn_error_t *err = svn_io_create_unique_link(&dst_tmp, dst, remainder,
+ ".tmp", pool);
+
+ /* If we had an error, check to see if it was because symlinks are
+ not supported on the platform. If so, fall back
+ to using the internal representation. */
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
+ {
+ svn_error_clear(err);
+ create_using_internal_representation = TRUE;
+ }
+ else
+ return err;
+ }
+ }
+ else
+ {
+ /* Just create a normal file using the internal special file
+ representation. We don't want a commit of an unknown special
+ file type to DoS all the clients. */
+ create_using_internal_representation = TRUE;
+ }
+
+ /* If nothing else worked, write out the internal representation to
+ a file that can be edited by the user.
+
+ ### this only writes the first line!
+ */
+ if (create_using_internal_representation)
+ SVN_ERR(svn_io_write_unique(&dst_tmp, svn_dirent_dirname(dst, pool),
+ contents->data, contents->len,
+ svn_io_file_del_none, pool));
+
+ /* Do the atomic rename from our temporary location. */
+ return svn_io_file_rename(dst_tmp, dst, pool);
+}
+
+
+svn_error_t *
+svn_subst_copy_and_translate4(const char *src,
+ const char *dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ apr_hash_t *keywords,
+ svn_boolean_t expand,
+ svn_boolean_t special,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_stream_t *src_stream;
+ svn_stream_t *dst_stream;
+ const char *dst_tmp;
+ svn_error_t *err;
+ svn_node_kind_t kind;
+ svn_boolean_t path_special;
+
+ SVN_ERR(svn_io_check_special_path(src, &kind, &path_special, pool));
+
+ /* If this is a 'special' file, we may need to create it or
+ detranslate it. */
+ if (special || path_special)
+ {
+ if (expand)
+ {
+ if (path_special)
+ {
+ /* We are being asked to create a special file from a special
+ file. Do a temporary detranslation and work from there. */
+
+ /* ### woah. this section just undoes all the work we already did
+ ### to read the contents of the special file. shoot... the
+ ### svn_subst_read_specialfile even checks the file type
+ ### for us! */
+
+ SVN_ERR(svn_subst_read_specialfile(&src_stream, src, pool, pool));
+ }
+ else
+ {
+ SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
+ }
+
+ return svn_error_trace(create_special_file_from_stream(src_stream,
+ dst, pool));
+ }
+ /* else !expand */
+
+ return svn_error_trace(detranslate_special_file(src, dst,
+ cancel_func,
+ cancel_baton,
+ pool));
+ }
+
+ /* The easy way out: no translation needed, just copy. */
+ if (! (eol_str || (keywords && (apr_hash_count(keywords) > 0))))
+ return svn_error_trace(svn_io_copy_file(src, dst, FALSE, pool));
+
+ /* Open source file. */
+ SVN_ERR(svn_stream_open_readonly(&src_stream, src, pool, pool));
+
+ /* For atomicity, we translate to a tmp file and then rename the tmp file
+ over the real destination. */
+ SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
+ svn_dirent_dirname(dst, pool),
+ svn_io_file_del_none, pool, pool));
+
+ dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair,
+ keywords, expand, pool);
+
+ /* ###: use cancel func/baton in place of NULL/NULL below. */
+ err = svn_stream_copy3(src_stream, dst_stream, cancel_func, cancel_baton,
+ pool);
+ if (err)
+ {
+ /* On errors, we have a pathname available. */
+ if (err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
+ err = svn_error_createf(SVN_ERR_IO_INCONSISTENT_EOL, err,
+ _("File '%s' has inconsistent newlines"),
+ svn_dirent_local_style(src, pool));
+ return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp,
+ FALSE, pool));
+ }
+
+ /* Now that dst_tmp contains the translated data, do the atomic rename. */
+ SVN_ERR(svn_io_file_rename(dst_tmp, dst, pool));
+
+ /* Preserve the source file's permission bits. */
+ SVN_ERR(svn_io_copy_perms(src, dst, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** 'Special file' stream support */
+
+struct special_stream_baton
+{
+ svn_stream_t *read_stream;
+ svn_stringbuf_t *write_content;
+ svn_stream_t *write_stream;
+ const char *path;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+read_handler_special(void *baton, char *buffer, apr_size_t *len)
+{
+ struct special_stream_baton *btn = baton;
+
+ if (btn->read_stream)
+ /* We actually found a file to read from */
+ return svn_stream_read(btn->read_stream, buffer, len);
+ else
+ return svn_error_createf(APR_ENOENT, NULL,
+ "Can't read special file: File '%s' not found",
+ svn_dirent_local_style(btn->path, btn->pool));
+}
+
+static svn_error_t *
+write_handler_special(void *baton, const char *buffer, apr_size_t *len)
+{
+ struct special_stream_baton *btn = baton;
+
+ return svn_stream_write(btn->write_stream, buffer, len);
+}
+
+
+static svn_error_t *
+close_handler_special(void *baton)
+{
+ struct special_stream_baton *btn = baton;
+
+ if (btn->write_content->len)
+ {
+ /* yeay! we received data and need to create a special file! */
+
+ svn_stream_t *source = svn_stream_from_stringbuf(btn->write_content,
+ btn->pool);
+ SVN_ERR(create_special_file_from_stream(source, btn->path, btn->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_subst_create_specialfile(svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct special_stream_baton *baton = apr_palloc(result_pool, sizeof(*baton));
+
+ baton->path = apr_pstrdup(result_pool, path);
+
+ /* SCRATCH_POOL may not exist after the function returns. */
+ baton->pool = result_pool;
+
+ baton->write_content = svn_stringbuf_create_empty(result_pool);
+ baton->write_stream = svn_stream_from_stringbuf(baton->write_content,
+ result_pool);
+
+ *stream = svn_stream_create(baton, result_pool);
+ svn_stream_set_write(*stream, write_handler_special);
+ svn_stream_set_close(*stream, close_handler_special);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* NOTE: this function is deprecated, but we cannot move it over to
+ deprecated.c because it uses stuff private to this file, and it is
+ not easily rebuilt in terms of "new" functions. */
+svn_error_t *
+svn_subst_stream_from_specialfile(svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct special_stream_baton *baton = apr_palloc(pool, sizeof(*baton));
+ svn_error_t *err;
+
+ baton->pool = pool;
+ baton->path = apr_pstrdup(pool, path);
+
+ err = svn_subst_read_specialfile(&baton->read_stream, path, pool, pool);
+
+ /* File might not exist because we intend to create it upon close. */
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+
+ /* Note: the special file is missing. the caller won't find out
+ until the first read. Oh well. This function is deprecated anyways,
+ so they can just deal with the weird behavior. */
+ baton->read_stream = NULL;
+ }
+
+ baton->write_content = svn_stringbuf_create_empty(pool);
+ baton->write_stream = svn_stream_from_stringbuf(baton->write_content, pool);
+
+ *stream = svn_stream_create(baton, pool);
+ svn_stream_set_read(*stream, read_handler_special);
+ svn_stream_set_write(*stream, write_handler_special);
+ svn_stream_set_close(*stream, close_handler_special);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** String translation */
+svn_error_t *
+svn_subst_translate_string2(svn_string_t **new_value,
+ svn_boolean_t *translated_to_utf8,
+ svn_boolean_t *translated_line_endings,
+ const svn_string_t *value,
+ const char *encoding,
+ svn_boolean_t repair,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *val_utf8;
+ const char *val_utf8_lf;
+
+ if (value == NULL)
+ {
+ *new_value = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (encoding)
+ {
+ SVN_ERR(svn_utf_cstring_to_utf8_ex2(&val_utf8, value->data,
+ encoding, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_utf_cstring_to_utf8(&val_utf8, value->data, scratch_pool));
+ }
+
+ if (translated_to_utf8)
+ *translated_to_utf8 = (strcmp(value->data, val_utf8) != 0);
+
+ SVN_ERR(translate_cstring(&val_utf8_lf,
+ translated_line_endings,
+ val_utf8,
+ "\n", /* translate to LF */
+ repair,
+ NULL, /* no keywords */
+ FALSE, /* no expansion */
+ scratch_pool));
+
+ *new_value = svn_string_create(val_utf8_lf, result_pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_subst_detranslate_string(svn_string_t **new_value,
+ const svn_string_t *value,
+ svn_boolean_t for_output,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ const char *val_neol;
+ const char *val_nlocale_neol;
+
+ if (value == NULL)
+ {
+ *new_value = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_subst_translate_cstring2(value->data,
+ &val_neol,
+ APR_EOL_STR, /* 'native' eol */
+ FALSE, /* no repair */
+ NULL, /* no keywords */
+ FALSE, /* no expansion */
+ pool));
+
+ if (for_output)
+ {
+ err = svn_cmdline_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
+ if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
+ {
+ val_nlocale_neol =
+ svn_cmdline_cstring_from_utf8_fuzzy(val_neol, pool);
+ svn_error_clear(err);
+ }
+ else if (err)
+ return err;
+ }
+ else
+ {
+ err = svn_utf_cstring_from_utf8(&val_nlocale_neol, val_neol, pool);
+ if (err && (APR_STATUS_IS_EINVAL(err->apr_err)))
+ {
+ val_nlocale_neol = svn_utf_cstring_from_utf8_fuzzy(val_neol, pool);
+ svn_error_clear(err);
+ }
+ else if (err)
+ return err;
+ }
+
+ *new_value = svn_string_create(val_nlocale_neol, pool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/sysinfo.c b/contrib/subversion/subversion/libsvn_subr/sysinfo.c
new file mode 100644
index 0000000..455dca4
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/sysinfo.c
@@ -0,0 +1,1132 @@
+/*
+ * sysinfo.c : information about the running system
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#define PSAPI_VERSION 1
+#include <windows.h>
+#include <psapi.h>
+#include <Ws2tcpip.h>
+#endif
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include <apr_lib.h>
+#include <apr_pools.h>
+#include <apr_file_info.h>
+#include <apr_signal.h>
+#include <apr_strings.h>
+#include <apr_thread_proc.h>
+#include <apr_version.h>
+#include <apu_version.h>
+
+#include "svn_pools.h"
+#include "svn_ctype.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_string.h"
+#include "svn_utf.h"
+#include "svn_version.h"
+
+#include "private/svn_sqlite.h"
+
+#include "sysinfo.h"
+#include "svn_private_config.h"
+
+#if HAVE_SYS_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+
+#ifdef SVN_HAVE_MACOS_PLIST
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#ifdef SVN_HAVE_MACHO_ITERATE
+#include <mach-o/dyld.h>
+#include <mach-o/loader.h>
+#endif
+
+#if HAVE_UNAME
+static const char *canonical_host_from_uname(apr_pool_t *pool);
+# ifndef SVN_HAVE_MACOS_PLIST
+static const char *release_name_from_uname(apr_pool_t *pool);
+# endif
+#endif
+
+#ifdef WIN32
+static const char *win32_canonical_host(apr_pool_t *pool);
+static const char *win32_release_name(apr_pool_t *pool);
+static const apr_array_header_t *win32_shared_libs(apr_pool_t *pool);
+#endif /* WIN32 */
+
+#ifdef SVN_HAVE_MACOS_PLIST
+static const char *macos_release_name(apr_pool_t *pool);
+#endif
+
+#ifdef SVN_HAVE_MACHO_ITERATE
+static const apr_array_header_t *macos_shared_libs(apr_pool_t *pool);
+#endif
+
+
+#if __linux__
+static const char *linux_release_name(apr_pool_t *pool);
+#endif
+
+const char *
+svn_sysinfo__canonical_host(apr_pool_t *pool)
+{
+#ifdef WIN32
+ return win32_canonical_host(pool);
+#elif HAVE_UNAME
+ return canonical_host_from_uname(pool);
+#else
+ return "unknown-unknown-unknown";
+#endif
+}
+
+
+const char *
+svn_sysinfo__release_name(apr_pool_t *pool)
+{
+#ifdef WIN32
+ return win32_release_name(pool);
+#elif defined(SVN_HAVE_MACOS_PLIST)
+ return macos_release_name(pool);
+#elif __linux__
+ return linux_release_name(pool);
+#elif HAVE_UNAME
+ return release_name_from_uname(pool);
+#else
+ return NULL;
+#endif
+}
+
+const apr_array_header_t *
+svn_sysinfo__linked_libs(apr_pool_t *pool)
+{
+ svn_version_ext_linked_lib_t *lib;
+ apr_array_header_t *array = apr_array_make(pool, 3, sizeof(*lib));
+
+ lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
+ lib->name = "APR";
+ lib->compiled_version = APR_VERSION_STRING;
+ lib->runtime_version = apr_pstrdup(pool, apr_version_string());
+
+/* Don't list APR-Util if it isn't linked in, which it may not be if
+ * we're using APR 2.x+ which combined APR-Util into APR. */
+#ifdef APU_VERSION_STRING
+ lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
+ lib->name = "APR-Util";
+ lib->compiled_version = APU_VERSION_STRING;
+ lib->runtime_version = apr_pstrdup(pool, apu_version_string());
+#endif
+
+ lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
+ lib->name = "SQLite";
+ lib->compiled_version = apr_pstrdup(pool, svn_sqlite__compiled_version());
+#ifdef SVN_SQLITE_INLINE
+ lib->runtime_version = NULL;
+#else
+ lib->runtime_version = apr_pstrdup(pool, svn_sqlite__runtime_version());
+#endif
+
+ return array;
+}
+
+const apr_array_header_t *
+svn_sysinfo__loaded_libs(apr_pool_t *pool)
+{
+#ifdef WIN32
+ return win32_shared_libs(pool);
+#elif defined(SVN_HAVE_MACHO_ITERATE)
+ return macos_shared_libs(pool);
+#else
+ return NULL;
+#endif
+}
+
+
+#if HAVE_UNAME
+static const char*
+canonical_host_from_uname(apr_pool_t *pool)
+{
+ const char *machine = "unknown";
+ const char *vendor = "unknown";
+ const char *sysname = "unknown";
+ const char *sysver = "";
+ struct utsname info;
+
+ if (0 <= uname(&info))
+ {
+ svn_error_t *err;
+ const char *tmp;
+
+ err = svn_utf_cstring_to_utf8(&tmp, info.machine, pool);
+ if (err)
+ svn_error_clear(err);
+ else
+ machine = tmp;
+
+ err = svn_utf_cstring_to_utf8(&tmp, info.sysname, pool);
+ if (err)
+ svn_error_clear(err);
+ else
+ {
+ char *lwr = apr_pstrdup(pool, tmp);
+ char *it = lwr;
+ while (*it)
+ {
+ if (svn_ctype_isupper(*it))
+ *it = apr_tolower(*it);
+ ++it;
+ }
+ sysname = lwr;
+ }
+
+ if (0 == strcmp(sysname, "darwin"))
+ vendor = "apple";
+ if (0 == strcmp(sysname, "linux"))
+ sysver = "-gnu";
+ else
+ {
+ err = svn_utf_cstring_to_utf8(&tmp, info.release, pool);
+ if (err)
+ svn_error_clear(err);
+ else
+ {
+ apr_size_t n = strspn(tmp, ".0123456789");
+ if (n > 0)
+ {
+ char *ver = apr_pstrdup(pool, tmp);
+ ver[n] = 0;
+ sysver = ver;
+ }
+ else
+ sysver = tmp;
+ }
+ }
+ }
+
+ return apr_psprintf(pool, "%s-%s-%s%s", machine, vendor, sysname, sysver);
+}
+
+# ifndef SVN_HAVE_MACOS_PLIST
+/* Generate a release name from the uname(3) info, effectively
+ returning "`uname -s` `uname -r`". */
+static const char *
+release_name_from_uname(apr_pool_t *pool)
+{
+ struct utsname info;
+ if (0 <= uname(&info))
+ {
+ svn_error_t *err;
+ const char *sysname;
+ const char *sysver;
+
+ err = svn_utf_cstring_to_utf8(&sysname, info.sysname, pool);
+ if (err)
+ {
+ sysname = NULL;
+ svn_error_clear(err);
+ }
+
+
+ err = svn_utf_cstring_to_utf8(&sysver, info.release, pool);
+ if (err)
+ {
+ sysver = NULL;
+ svn_error_clear(err);
+ }
+
+ if (sysname || sysver)
+ {
+ return apr_psprintf(pool, "%s%s%s",
+ (sysname ? sysname : ""),
+ (sysver ? (sysname ? " " : "") : ""),
+ (sysver ? sysver : ""));
+ }
+ }
+ return NULL;
+}
+# endif /* !SVN_HAVE_MACOS_PLIST */
+#endif /* HAVE_UNAME */
+
+
+#if __linux__
+/* Split a stringbuf into a key/value pair.
+ Return the key, leaving the striped value in the stringbuf. */
+static const char *
+stringbuf_split_key(svn_stringbuf_t *buffer, char delim)
+{
+ char *key;
+ char *end;
+
+ end = strchr(buffer->data, delim);
+ if (!end)
+ return NULL;
+
+ svn_stringbuf_strip_whitespace(buffer);
+ key = buffer->data;
+ end = strchr(key, delim);
+ *end = '\0';
+ buffer->len = 1 + end - key;
+ buffer->data = end + 1;
+ svn_stringbuf_strip_whitespace(buffer);
+
+ return key;
+}
+
+/* Parse `/usr/bin/lsb_rlease --all` */
+static const char *
+lsb_release(apr_pool_t *pool)
+{
+ static const char *const args[3] =
+ {
+ "/usr/bin/lsb_release",
+ "--all",
+ NULL
+ };
+
+ const char *distributor = NULL;
+ const char *description = NULL;
+ const char *release = NULL;
+ const char *codename = NULL;
+
+ apr_proc_t lsbproc;
+ svn_stream_t *lsbinfo;
+ svn_error_t *err;
+
+ /* Run /usr/bin/lsb_release --all < /dev/null 2>/dev/null */
+ {
+ apr_file_t *stdin_handle;
+ apr_file_t *stdout_handle;
+
+ err = svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
+ APR_READ, APR_OS_DEFAULT, pool);
+ if (!err)
+ err = svn_io_file_open(&stdout_handle, SVN_NULL_DEVICE_NAME,
+ APR_WRITE, APR_OS_DEFAULT, pool);
+ if (!err)
+ err = svn_io_start_cmd3(&lsbproc, NULL, args[0], args, NULL, FALSE,
+ FALSE, stdin_handle,
+ TRUE, NULL,
+ FALSE, stdout_handle,
+ pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+ }
+
+ /* Parse the output and try to populate the */
+ lsbinfo = svn_stream_from_aprfile2(lsbproc.out, TRUE, pool);
+ if (lsbinfo)
+ {
+ for (;;)
+ {
+ svn_boolean_t eof = FALSE;
+ svn_stringbuf_t *line;
+ const char *key;
+
+ err = svn_stream_readline(lsbinfo, &line, "\n", &eof, pool);
+ if (err || eof)
+ break;
+
+ key = stringbuf_split_key(line, ':');
+ if (!key)
+ continue;
+
+ if (0 == svn_cstring_casecmp(key, "Distributor ID"))
+ distributor = line->data;
+ else if (0 == svn_cstring_casecmp(key, "Description"))
+ description = line->data;
+ else if (0 == svn_cstring_casecmp(key, "Release"))
+ release = line->data;
+ else if (0 == svn_cstring_casecmp(key, "Codename"))
+ codename = line->data;
+ }
+ err = svn_error_compose_create(err,
+ svn_stream_close(lsbinfo));
+ if (err)
+ {
+ svn_error_clear(err);
+ apr_proc_kill(&lsbproc, SIGKILL);
+ return NULL;
+ }
+ }
+
+ /* Reap the child process */
+ err = svn_io_wait_for_cmd(&lsbproc, "", NULL, NULL, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+
+ if (description)
+ return apr_psprintf(pool, "%s%s%s%s", description,
+ (codename ? " (" : ""),
+ (codename ? codename : ""),
+ (codename ? ")" : ""));
+ if (distributor)
+ return apr_psprintf(pool, "%s%s%s%s%s%s", distributor,
+ (release ? " " : ""),
+ (release ? release : ""),
+ (codename ? " (" : ""),
+ (codename ? codename : ""),
+ (codename ? ")" : ""));
+
+ return NULL;
+}
+
+/* Read the whole contents of a file. */
+static svn_stringbuf_t *
+read_file_contents(const char *filename, apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_stringbuf_t *buffer;
+
+ err = svn_stringbuf_from_file2(&buffer, filename, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+
+ return buffer;
+}
+
+/* Strip everything but the first line from a stringbuf. */
+static void
+stringbuf_first_line_only(svn_stringbuf_t *buffer)
+{
+ char *eol = strchr(buffer->data, '\n');
+ if (eol)
+ {
+ *eol = '\0';
+ buffer->len = 1 + eol - buffer->data;
+ }
+ svn_stringbuf_strip_whitespace(buffer);
+}
+
+/* Look at /etc/redhat_release to detect RHEL/Fedora/CentOS. */
+static const char *
+redhat_release(apr_pool_t *pool)
+{
+ svn_stringbuf_t *buffer = read_file_contents("/etc/redhat-release", pool);
+ if (buffer)
+ {
+ stringbuf_first_line_only(buffer);
+ return buffer->data;
+ }
+ return NULL;
+}
+
+/* Look at /etc/SuSE-release to detect non-LSB SuSE. */
+static const char *
+suse_release(apr_pool_t *pool)
+{
+ const char *release = NULL;
+ const char *codename = NULL;
+
+ svn_stringbuf_t *buffer = read_file_contents("/etc/SuSE-release", pool);
+ svn_stringbuf_t *line;
+ svn_stream_t *stream;
+ svn_boolean_t eof;
+ svn_error_t *err;
+ if (!buffer)
+ return NULL;
+
+ stream = svn_stream_from_stringbuf(buffer, pool);
+ err = svn_stream_readline(stream, &line, "\n", &eof, pool);
+ if (err || eof)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+
+ svn_stringbuf_strip_whitespace(line);
+ release = line->data;
+
+ for (;;)
+ {
+ const char *key;
+
+ err = svn_stream_readline(stream, &line, "\n", &eof, pool);
+ if (err || eof)
+ {
+ svn_error_clear(err);
+ break;
+ }
+
+ key = stringbuf_split_key(line, '=');
+ if (!key)
+ continue;
+
+ if (0 == strncmp(key, "CODENAME", 8))
+ codename = line->data;
+ }
+
+ return apr_psprintf(pool, "%s%s%s%s",
+ release,
+ (codename ? " (" : ""),
+ (codename ? codename : ""),
+ (codename ? ")" : ""));
+}
+
+/* Look at /etc/debian_version to detect non-LSB Debian. */
+static const char *
+debian_release(apr_pool_t *pool)
+{
+ svn_stringbuf_t *buffer = read_file_contents("/etc/debian_version", pool);
+ if (!buffer)
+ return NULL;
+
+ stringbuf_first_line_only(buffer);
+ return apr_pstrcat(pool, "Debian ", buffer->data, NULL);
+}
+
+/* Try to find the Linux distribution name, or return info from uname. */
+static const char *
+linux_release_name(apr_pool_t *pool)
+{
+ const char *uname_release = release_name_from_uname(pool);
+
+ /* Try anything that has /usr/bin/lsb_release.
+ Covers, for example, Debian, Ubuntu and SuSE. */
+ const char *release_name = lsb_release(pool);
+
+ /* Try RHEL/Fedora/CentOS */
+ if (!release_name)
+ release_name = redhat_release(pool);
+
+ /* Try Non-LSB SuSE */
+ if (!release_name)
+ release_name = suse_release(pool);
+
+ /* Try non-LSB Debian */
+ if (!release_name)
+ release_name = debian_release(pool);
+
+ if (!release_name)
+ return uname_release;
+
+ if (!uname_release)
+ return release_name;
+
+ return apr_psprintf(pool, "%s [%s]", release_name, uname_release);
+}
+#endif /* __linux__ */
+
+
+#ifdef WIN32
+typedef DWORD (WINAPI *FNGETNATIVESYSTEMINFO)(LPSYSTEM_INFO);
+typedef BOOL (WINAPI *FNENUMPROCESSMODULES) (HANDLE, HMODULE, DWORD, LPDWORD);
+
+/* Get system and version info, and try to tell the difference
+ between the native system type and the runtime environment of the
+ current process. Populate results in SYSINFO, LOCAL_SYSINFO
+ (optional) and OSINFO. */
+static BOOL
+system_info(SYSTEM_INFO *sysinfo,
+ SYSTEM_INFO *local_sysinfo,
+ OSVERSIONINFOEXW *osinfo)
+{
+ FNGETNATIVESYSTEMINFO GetNativeSystemInfo_ = (FNGETNATIVESYSTEMINFO)
+ GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetNativeSystemInfo");
+
+ ZeroMemory(sysinfo, sizeof *sysinfo);
+ if (local_sysinfo)
+ {
+ ZeroMemory(local_sysinfo, sizeof *local_sysinfo);
+ GetSystemInfo(local_sysinfo);
+ if (GetNativeSystemInfo_)
+ GetNativeSystemInfo_(sysinfo);
+ else
+ memcpy(sysinfo, local_sysinfo, sizeof *sysinfo);
+ }
+ else
+ GetSystemInfo(sysinfo);
+
+ ZeroMemory(osinfo, sizeof *osinfo);
+ osinfo->dwOSVersionInfoSize = sizeof *osinfo;
+ if (!GetVersionExW((LPVOID)osinfo))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Map the proccessor type from SYSINFO to a string. */
+static const char *
+processor_name(SYSTEM_INFO *sysinfo)
+{
+ switch (sysinfo->wProcessorArchitecture)
+ {
+ case PROCESSOR_ARCHITECTURE_AMD64: return "x86_64";
+ case PROCESSOR_ARCHITECTURE_IA64: return "ia64";
+ case PROCESSOR_ARCHITECTURE_INTEL: return "x86";
+ case PROCESSOR_ARCHITECTURE_MIPS: return "mips";
+ case PROCESSOR_ARCHITECTURE_ALPHA: return "alpha32";
+ case PROCESSOR_ARCHITECTURE_PPC: return "powerpc";
+ case PROCESSOR_ARCHITECTURE_SHX: return "shx";
+ case PROCESSOR_ARCHITECTURE_ARM: return "arm";
+ case PROCESSOR_ARCHITECTURE_ALPHA64: return "alpha";
+ case PROCESSOR_ARCHITECTURE_MSIL: return "msil";
+ case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: return "x86_wow64";
+ default: return "unknown";
+ }
+}
+
+/* Return the Windows-specific canonical host name. */
+static const char *
+win32_canonical_host(apr_pool_t *pool)
+{
+ SYSTEM_INFO sysinfo;
+ SYSTEM_INFO local_sysinfo;
+ OSVERSIONINFOEXW osinfo;
+
+ if (system_info(&sysinfo, &local_sysinfo, &osinfo))
+ {
+ const char *arch = processor_name(&local_sysinfo);
+ const char *machine = processor_name(&sysinfo);
+ const char *vendor = "microsoft";
+ const char *sysname = "windows";
+ const char *sysver = apr_psprintf(pool, "%u.%u.%u",
+ (unsigned int)osinfo.dwMajorVersion,
+ (unsigned int)osinfo.dwMinorVersion,
+ (unsigned int)osinfo.dwBuildNumber);
+
+ if (sysinfo.wProcessorArchitecture
+ == local_sysinfo.wProcessorArchitecture)
+ return apr_psprintf(pool, "%s-%s-%s%s",
+ machine, vendor, sysname, sysver);
+ return apr_psprintf(pool, "%s/%s-%s-%s%s",
+ arch, machine, vendor, sysname, sysver);
+ }
+
+ return "unknown-microsoft-windows";
+}
+
+/* Convert a Unicode string to UTF-8. */
+static char *
+wcs_to_utf8(const wchar_t *wcs, apr_pool_t *pool)
+{
+ const int bufsize = WideCharToMultiByte(CP_UTF8, 0, wcs, -1,
+ NULL, 0, NULL, NULL);
+ if (bufsize > 0)
+ {
+ char *const utf8 = apr_palloc(pool, bufsize + 1);
+ WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf8, bufsize, NULL, NULL);
+ return utf8;
+ }
+ return NULL;
+}
+
+/* Query the value called NAME of the registry key HKEY. */
+static char *
+registry_value(HKEY hkey, wchar_t *name, apr_pool_t *pool)
+{
+ DWORD size;
+ wchar_t *value;
+
+ if (RegQueryValueExW(hkey, name, NULL, NULL, NULL, &size))
+ return NULL;
+
+ value = apr_palloc(pool, size + sizeof *value);
+ if (RegQueryValueExW(hkey, name, NULL, NULL, (void*)value, &size))
+ return NULL;
+ value[size / sizeof *value] = 0;
+ return wcs_to_utf8(value, pool);
+}
+
+/* Try to glean the Windows release name and associated info from the
+ registry. Failing that, construct a release name from the version
+ info. */
+static const char *
+win32_release_name(apr_pool_t *pool)
+{
+ SYSTEM_INFO sysinfo;
+ OSVERSIONINFOEXW osinfo;
+ HKEY hkcv;
+
+ if (!system_info(&sysinfo, NULL, &osinfo))
+ return NULL;
+
+ if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
+ 0, KEY_QUERY_VALUE, &hkcv))
+ {
+ const char *release = registry_value(hkcv, L"ProductName", pool);
+ const char *spack = registry_value(hkcv, L"CSDVersion", pool);
+ const char *curver = registry_value(hkcv, L"CurrentVersion", pool);
+ const char *curtype = registry_value(hkcv, L"CurrentType", pool);
+ const char *install = registry_value(hkcv, L"InstallationType", pool);
+ const char *curbuild = registry_value(hkcv, L"CurrentBuildNumber", pool);
+
+ if (!spack && *osinfo.szCSDVersion)
+ spack = wcs_to_utf8(osinfo.szCSDVersion, pool);
+
+ if (!curbuild)
+ curbuild = registry_value(hkcv, L"CurrentBuild", pool);
+
+ if (release || spack || curver || curtype || curbuild)
+ {
+ const char *bootinfo = "";
+ if (curver || install || curtype)
+ {
+ bootinfo = apr_psprintf(pool, "[%s%s%s%s%s]",
+ (curver ? curver : ""),
+ (install ? (curver ? " " : "") : ""),
+ (install ? install : ""),
+ (curtype
+ ? (curver||install ? " " : "")
+ : ""),
+ (curtype ? curtype : ""));
+ }
+
+ return apr_psprintf(pool, "%s%s%s%s%s%s%s",
+ (release ? release : ""),
+ (spack ? (release ? ", " : "") : ""),
+ (spack ? spack : ""),
+ (curbuild
+ ? (release||spack ? ", build " : "build ")
+ : ""),
+ (curbuild ? curbuild : ""),
+ (bootinfo
+ ? (release||spack||curbuild ? " " : "")
+ : ""),
+ (bootinfo ? bootinfo : ""));
+ }
+ }
+
+ if (*osinfo.szCSDVersion)
+ {
+ const char *servicepack = wcs_to_utf8(osinfo.szCSDVersion, pool);
+
+ if (servicepack)
+ return apr_psprintf(pool, "Windows NT %u.%u, %s, build %u",
+ (unsigned int)osinfo.dwMajorVersion,
+ (unsigned int)osinfo.dwMinorVersion,
+ servicepack,
+ (unsigned int)osinfo.dwBuildNumber);
+
+ /* Assume wServicePackMajor > 0 if szCSDVersion is not empty */
+ if (osinfo.wServicePackMinor)
+ return apr_psprintf(pool, "Windows NT %u.%u SP%u.%u, build %u",
+ (unsigned int)osinfo.dwMajorVersion,
+ (unsigned int)osinfo.dwMinorVersion,
+ (unsigned int)osinfo.wServicePackMajor,
+ (unsigned int)osinfo.wServicePackMinor,
+ (unsigned int)osinfo.dwBuildNumber);
+
+ return apr_psprintf(pool, "Windows NT %u.%u SP%u, build %u",
+ (unsigned int)osinfo.dwMajorVersion,
+ (unsigned int)osinfo.dwMinorVersion,
+ (unsigned int)osinfo.wServicePackMajor,
+ (unsigned int)osinfo.dwBuildNumber);
+ }
+
+ return apr_psprintf(pool, "Windows NT %u.%u, build %u",
+ (unsigned int)osinfo.dwMajorVersion,
+ (unsigned int)osinfo.dwMinorVersion,
+ (unsigned int)osinfo.dwBuildNumber);
+}
+
+
+/* Get a list of handles of shared libs loaded by the current
+ process. Returns a NULL-terminated array alocated from POOL. */
+static HMODULE *
+enum_loaded_modules(apr_pool_t *pool)
+{
+ HANDLE current = GetCurrentProcess();
+ HMODULE dummy[1];
+ HMODULE *handles;
+ DWORD size;
+
+ if (!EnumProcessModules(current, dummy, sizeof(dummy), &size))
+ return NULL;
+
+ handles = apr_palloc(pool, size + sizeof *handles);
+ if (!EnumProcessModules(current, handles, size, &size))
+ return NULL;
+ handles[size / sizeof *handles] = NULL;
+ return handles;
+}
+
+/* Find the version number, if any, embedded in FILENAME. */
+static const char *
+file_version_number(const wchar_t *filename, apr_pool_t *pool)
+{
+ VS_FIXEDFILEINFO info;
+ unsigned int major, minor, micro, nano;
+ void *data;
+ DWORD data_size = GetFileVersionInfoSizeW(filename, NULL);
+ void *vinfo;
+ UINT vinfo_size;
+
+ if (!data_size)
+ return NULL;
+
+ data = apr_palloc(pool, data_size);
+ if (!GetFileVersionInfoW(filename, 0, data_size, data))
+ return NULL;
+
+ if (!VerQueryValueW(data, L"\\", &vinfo, &vinfo_size))
+ return NULL;
+
+ if (vinfo_size != sizeof info)
+ return NULL;
+
+ memcpy(&info, vinfo, sizeof info);
+ major = (info.dwFileVersionMS >> 16) & 0xFFFF;
+ minor = info.dwFileVersionMS & 0xFFFF;
+ micro = (info.dwFileVersionLS >> 16) & 0xFFFF;
+ nano = info.dwFileVersionLS & 0xFFFF;
+
+ if (!nano)
+ {
+ if (!micro)
+ return apr_psprintf(pool, "%u.%u", major, minor);
+ else
+ return apr_psprintf(pool, "%u.%u.%u", major, minor, micro);
+ }
+ return apr_psprintf(pool, "%u.%u.%u.%u", major, minor, micro, nano);
+}
+
+/* List the shared libraries loaded by the current process. */
+static const apr_array_header_t *
+win32_shared_libs(apr_pool_t *pool)
+{
+ apr_array_header_t *array = NULL;
+ wchar_t buffer[MAX_PATH + 1];
+ HMODULE *handles = enum_loaded_modules(pool);
+ HMODULE *module;
+
+ for (module = handles; module && *module; ++module)
+ {
+ const char *filename;
+ const char *version;
+ if (GetModuleFileNameW(*module, buffer, MAX_PATH))
+ {
+ buffer[MAX_PATH] = 0;
+
+ version = file_version_number(buffer, pool);
+ filename = wcs_to_utf8(buffer, pool);
+ if (filename)
+ {
+ svn_version_ext_loaded_lib_t *lib;
+
+ if (!array)
+ {
+ array = apr_array_make(pool, 32, sizeof(*lib));
+ }
+ lib = &APR_ARRAY_PUSH(array, svn_version_ext_loaded_lib_t);
+ lib->name = svn_dirent_local_style(filename, pool);
+ lib->version = version;
+ }
+ }
+ }
+
+ return array;
+}
+#endif /* WIN32 */
+
+
+#ifdef SVN_HAVE_MACOS_PLIST
+/* Load the SystemVersion.plist or ServerVersion.plist file into a
+ property list. Set SERVER to TRUE if the file read was
+ ServerVersion.plist. */
+static CFDictionaryRef
+system_version_plist(svn_boolean_t *server, apr_pool_t *pool)
+{
+ static const UInt8 server_version[] =
+ "/System/Library/CoreServices/ServerVersion.plist";
+ static const UInt8 system_version[] =
+ "/System/Library/CoreServices/SystemVersion.plist";
+
+ CFPropertyListRef plist = NULL;
+ CFDataRef resource = NULL;
+ CFStringRef errstr = NULL;
+ CFURLRef url = NULL;
+ SInt32 errcode;
+
+ url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
+ server_version,
+ sizeof(server_version) - 1,
+ FALSE);
+ if (!url)
+ return NULL;
+
+ if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault,
+ url, &resource,
+ NULL, NULL, &errcode))
+ {
+ CFRelease(url);
+ url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
+ system_version,
+ sizeof(system_version) - 1,
+ FALSE);
+ if (!url)
+ return NULL;
+
+ if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault,
+ url, &resource,
+ NULL, NULL, &errcode))
+ {
+ CFRelease(url);
+ return NULL;
+ }
+ else
+ {
+ CFRelease(url);
+ *server = FALSE;
+ }
+ }
+ else
+ {
+ CFRelease(url);
+ *server = TRUE;
+ }
+
+ /* ### CFPropertyListCreateFromXMLData is obsolete, but its
+ replacement CFPropertyListCreateWithData is only available
+ from Mac OS 1.6 onward. */
+ plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resource,
+ kCFPropertyListImmutable,
+ &errstr);
+ if (resource)
+ CFRelease(resource);
+ if (errstr)
+ CFRelease(errstr);
+
+ if (CFDictionaryGetTypeID() != CFGetTypeID(plist))
+ {
+ /* Oops ... this really should be a dict. */
+ CFRelease(plist);
+ return NULL;
+ }
+
+ return plist;
+}
+
+/* Return the value for KEY from PLIST, or NULL if not available. */
+static const char *
+value_from_dict(CFDictionaryRef plist, CFStringRef key, apr_pool_t *pool)
+{
+ CFStringRef valref;
+ CFIndex bufsize;
+ const void *valptr;
+ const char *value;
+
+ if (!CFDictionaryGetValueIfPresent(plist, key, &valptr))
+ return NULL;
+
+ valref = valptr;
+ if (CFStringGetTypeID() != CFGetTypeID(valref))
+ return NULL;
+
+ value = CFStringGetCStringPtr(valref, kCFStringEncodingUTF8);
+ if (value)
+ return apr_pstrdup(pool, value);
+
+ bufsize = 5 * CFStringGetLength(valref) + 1;
+ value = apr_palloc(pool, bufsize);
+ if (!CFStringGetCString(valref, (char*)value, bufsize,
+ kCFStringEncodingUTF8))
+ value = NULL;
+
+ return value;
+}
+
+/* Return the commercial name of the OS, given the version number in
+ a format that matches the regular expression /^10\.\d+(\..*)?$/ */
+static const char *
+release_name_from_version(const char *osver)
+{
+ char *end = NULL;
+ unsigned long num = strtoul(osver, &end, 10);
+
+ if (!end || *end != '.' || num != 10)
+ return NULL;
+
+ osver = end + 1;
+ end = NULL;
+ num = strtoul(osver, &end, 10);
+ if (!end || (*end && *end != '.'))
+ return NULL;
+
+ /* See http://en.wikipedia.org/wiki/History_of_OS_X#Release_timeline */
+ switch(num)
+ {
+ case 0: return "Cheetah";
+ case 1: return "Puma";
+ case 2: return "Jaguar";
+ case 3: return "Panther";
+ case 4: return "Tiger";
+ case 5: return "Leopard";
+ case 6: return "Snow Leopard";
+ case 7: return "Lion";
+ case 8: return "Mountain Lion";
+ }
+
+ return NULL;
+}
+
+/* Construct the release name from information stored in the Mac OS X
+ "SystemVersion.plist" file (or ServerVersion.plist, for Mac Os
+ Server. */
+static const char *
+macos_release_name(apr_pool_t *pool)
+{
+ svn_boolean_t server;
+ CFDictionaryRef plist = system_version_plist(&server, pool);
+
+ if (plist)
+ {
+ const char *osname = value_from_dict(plist, CFSTR("ProductName"), pool);
+ const char *osver = value_from_dict(plist,
+ CFSTR("ProductUserVisibleVersion"),
+ pool);
+ const char *build = value_from_dict(plist,
+ CFSTR("ProductBuildVersion"),
+ pool);
+ const char *release;
+
+ if (!osver)
+ osver = value_from_dict(plist, CFSTR("ProductVersion"), pool);
+ release = release_name_from_version(osver);
+
+ CFRelease(plist);
+ return apr_psprintf(pool, "%s%s%s%s%s%s%s%s",
+ (osname ? osname : ""),
+ (osver ? (osname ? " " : "") : ""),
+ (osver ? osver : ""),
+ (release ? (osname||osver ? " " : "") : ""),
+ (release ? release : ""),
+ (build
+ ? (osname||osver||release ? ", " : "")
+ : ""),
+ (build
+ ? (server ? "server build " : "build ")
+ : ""),
+ (build ? build : ""));
+ }
+
+ return NULL;
+}
+#endif /* SVN_HAVE_MACOS_PLIST */
+
+#ifdef SVN_HAVE_MACHO_ITERATE
+/* List the shared libraries loaded by the current process.
+ Ignore frameworks and system libraries, they're just clutter. */
+static const apr_array_header_t *
+macos_shared_libs(apr_pool_t *pool)
+{
+ static const char slb_prefix[] = "/usr/lib/system/";
+ static const char fwk_prefix[] = "/System/Library/Frameworks/";
+ static const char pfk_prefix[] = "/System/Library/PrivateFrameworks/";
+
+ const size_t slb_prefix_len = strlen(slb_prefix);
+ const size_t fwk_prefix_len = strlen(fwk_prefix);
+ const size_t pfk_prefix_len = strlen(pfk_prefix);
+
+ apr_array_header_t *result = NULL;
+ apr_array_header_t *dylibs = NULL;
+
+ uint32_t i;
+ for (i = 0;; ++i)
+ {
+ const struct mach_header *header = _dyld_get_image_header(i);
+ const char *filename = _dyld_get_image_name(i);
+ const char *version;
+ char *truename;
+ svn_version_ext_loaded_lib_t *lib;
+
+ if (!(header && filename))
+ break;
+
+ switch (header->cputype)
+ {
+ case CPU_TYPE_I386: version = _("Intel"); break;
+ case CPU_TYPE_X86_64: version = _("Intel 64-bit"); break;
+ case CPU_TYPE_POWERPC: version = _("PowerPC"); break;
+ case CPU_TYPE_POWERPC64: version = _("PowerPC 64-bit"); break;
+ default:
+ version = NULL;
+ }
+
+ if (0 == apr_filepath_merge(&truename, "", filename,
+ APR_FILEPATH_NATIVE
+ | APR_FILEPATH_TRUENAME,
+ pool))
+ filename = truename;
+ else
+ filename = apr_pstrdup(pool, filename);
+
+ if (0 == strncmp(filename, slb_prefix, slb_prefix_len)
+ || 0 == strncmp(filename, fwk_prefix, fwk_prefix_len)
+ || 0 == strncmp(filename, pfk_prefix, pfk_prefix_len))
+ {
+ /* Ignore frameworks and system libraries. */
+ continue;
+ }
+
+ if (header->filetype == MH_EXECUTE)
+ {
+ /* Make sure the program filename is first in the list */
+ if (!result)
+ {
+ result = apr_array_make(pool, 32, sizeof(*lib));
+ }
+ lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t);
+ }
+ else
+ {
+ if (!dylibs)
+ {
+ dylibs = apr_array_make(pool, 32, sizeof(*lib));
+ }
+ lib = &APR_ARRAY_PUSH(dylibs, svn_version_ext_loaded_lib_t);
+ }
+
+ lib->name = filename;
+ lib->version = version;
+ }
+
+ /* Gather results into one array. */
+ if (dylibs)
+ {
+ if (result)
+ apr_array_cat(result, dylibs);
+ else
+ result = dylibs;
+ }
+
+ return result;
+}
+#endif /* SVN_HAVE_MACHO_ITERATE */
diff --git a/contrib/subversion/subversion/libsvn_subr/sysinfo.h b/contrib/subversion/subversion/libsvn_subr/sysinfo.h
new file mode 100644
index 0000000..6a6e74d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/sysinfo.h
@@ -0,0 +1,69 @@
+/*
+ * sysinfo.h: share svn_sysinfo__* functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_SUBR_SYSINFO_H
+#define SVN_LIBSVN_SUBR_SYSINFO_H
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Return a canonical name similar to the output of config.guess,
+ * identifying the running system.
+ *
+ * All allocations are done in POOL.
+ */
+const char *svn_sysinfo__canonical_host(apr_pool_t *pool);
+
+/* Return the release name (i.e., marketing name) of the running
+ * system, or NULL if it's not available.
+ *
+ * All allocations are done in POOL.
+ */
+const char *svn_sysinfo__release_name(apr_pool_t *pool);
+
+/* Return an array of svn_version_linked_lib_t of descriptions of the
+ * link-time and run-time versions of dependent libraries, or NULL of
+ * the info is not available.
+ *
+ * All allocations are done in POOL.
+ */
+const apr_array_header_t *svn_sysinfo__linked_libs(apr_pool_t *pool);
+
+/* Return an array of svn_version_loaded_lib_t of descriptions of
+ * shared libraries loaded by the running process, including their
+ * versions where applicable, or NULL if the information is not
+ * available.
+ *
+ * All allocations are done in POOL.
+ */
+const apr_array_header_t *svn_sysinfo__loaded_libs(apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_SUBR_SYSINFO_H */
diff --git a/contrib/subversion/subversion/libsvn_subr/target.c b/contrib/subversion/subversion/libsvn_subr/target.c
new file mode 100644
index 0000000..3525167
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/target.c
@@ -0,0 +1,335 @@
+/*
+ * target.c: functions which operate on a list of targets supplied to
+ * a subversion subcommand.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+
+
+/*** Code. ***/
+
+svn_error_t *
+svn_path_condense_targets(const char **pcommon,
+ apr_array_header_t **pcondensed_targets,
+ const apr_array_header_t *targets,
+ svn_boolean_t remove_redundancies,
+ apr_pool_t *pool)
+{
+ int i, j, num_condensed = targets->nelts;
+ svn_boolean_t *removed;
+ apr_array_header_t *abs_targets;
+ size_t basedir_len;
+ const char *first_target;
+ svn_boolean_t first_target_is_url;
+
+ /* Early exit when there's no data to work on. */
+ if (targets->nelts <= 0)
+ {
+ *pcommon = NULL;
+ if (pcondensed_targets)
+ *pcondensed_targets = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Get the absolute path of the first target. */
+ first_target = APR_ARRAY_IDX(targets, 0, const char *);
+ first_target_is_url = svn_path_is_url(first_target);
+ if (first_target_is_url)
+ {
+ first_target = apr_pstrdup(pool, first_target);
+ *pcommon = first_target;
+ }
+ else
+ SVN_ERR(svn_dirent_get_absolute(pcommon, first_target, pool));
+
+ /* Early exit when there's only one path to work on. */
+ if (targets->nelts == 1)
+ {
+ if (pcondensed_targets)
+ *pcondensed_targets = apr_array_make(pool, 0, sizeof(const char *));
+ return SVN_NO_ERROR;
+ }
+
+ /* Copy the targets array, but with absolute paths instead of
+ relative. Also, find the pcommon argument by finding what is
+ common in all of the absolute paths. NOTE: This is not as
+ efficient as it could be. The calculation of the basedir could
+ be done in the loop below, which would save some calls to
+ svn_path_get_longest_ancestor. I decided to do it this way
+ because I thought it would be simpler, since this way, we don't
+ even do the loop if we don't need to condense the targets. */
+
+ removed = apr_pcalloc(pool, (targets->nelts * sizeof(svn_boolean_t)));
+ abs_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
+
+ APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon;
+
+ for (i = 1; i < targets->nelts; ++i)
+ {
+ const char *rel = APR_ARRAY_IDX(targets, i, const char *);
+ const char *absolute;
+ svn_boolean_t is_url = svn_path_is_url(rel);
+
+ if (is_url)
+ absolute = apr_pstrdup(pool, rel); /* ### TODO: avoid pool dup? */
+ else
+ SVN_ERR(svn_dirent_get_absolute(&absolute, rel, pool));
+
+ APR_ARRAY_PUSH(abs_targets, const char *) = absolute;
+
+ /* If we've not already determined that there's no common
+ parent, then continue trying to do so. */
+ if (*pcommon && **pcommon)
+ {
+ /* If the is-url-ness of this target doesn't match that of
+ the first target, our search for a common ancestor can
+ end right here. Otherwise, use the appropriate
+ get-longest-ancestor function per the path type. */
+ if (is_url != first_target_is_url)
+ *pcommon = "";
+ else if (first_target_is_url)
+ *pcommon = svn_uri_get_longest_ancestor(*pcommon, absolute, pool);
+ else
+ *pcommon = svn_dirent_get_longest_ancestor(*pcommon, absolute,
+ pool);
+ }
+ }
+
+ if (pcondensed_targets != NULL)
+ {
+ if (remove_redundancies)
+ {
+ /* Find the common part of each pair of targets. If
+ common part is equal to one of the paths, the other
+ is a child of it, and can be removed. If a target is
+ equal to *pcommon, it can also be removed. */
+
+ /* First pass: when one non-removed target is a child of
+ another non-removed target, remove the child. */
+ for (i = 0; i < abs_targets->nelts; ++i)
+ {
+ if (removed[i])
+ continue;
+
+ for (j = i + 1; j < abs_targets->nelts; ++j)
+ {
+ const char *abs_targets_i;
+ const char *abs_targets_j;
+ svn_boolean_t i_is_url, j_is_url;
+ const char *ancestor;
+
+ if (removed[j])
+ continue;
+
+ abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *);
+ abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *);
+ i_is_url = svn_path_is_url(abs_targets_i);
+ j_is_url = svn_path_is_url(abs_targets_j);
+
+ if (i_is_url != j_is_url)
+ continue;
+
+ if (i_is_url)
+ ancestor = svn_uri_get_longest_ancestor(abs_targets_i,
+ abs_targets_j,
+ pool);
+ else
+ ancestor = svn_dirent_get_longest_ancestor(abs_targets_i,
+ abs_targets_j,
+ pool);
+
+ if (*ancestor == '\0')
+ continue;
+
+ if (strcmp(ancestor, abs_targets_i) == 0)
+ {
+ removed[j] = TRUE;
+ num_condensed--;
+ }
+ else if (strcmp(ancestor, abs_targets_j) == 0)
+ {
+ removed[i] = TRUE;
+ num_condensed--;
+ }
+ }
+ }
+
+ /* Second pass: when a target is the same as *pcommon,
+ remove the target. */
+ for (i = 0; i < abs_targets->nelts; ++i)
+ {
+ const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i,
+ const char *);
+
+ if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i]))
+ {
+ removed[i] = TRUE;
+ num_condensed--;
+ }
+ }
+ }
+
+ /* Now create the return array, and copy the non-removed items */
+ basedir_len = strlen(*pcommon);
+ *pcondensed_targets = apr_array_make(pool, num_condensed,
+ sizeof(const char *));
+
+ for (i = 0; i < abs_targets->nelts; ++i)
+ {
+ const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *);
+
+ /* Skip this if it's been removed. */
+ if (removed[i])
+ continue;
+
+ /* If a common prefix was found, condensed_targets are given
+ relative to that prefix. */
+ if (basedir_len > 0)
+ {
+ /* Only advance our pointer past a path separator if
+ REL_ITEM isn't the same as *PCOMMON.
+
+ If *PCOMMON is a root path, basedir_len will already
+ include the closing '/', so never advance the pointer
+ here.
+ */
+ rel_item += basedir_len;
+ if (rel_item[0] &&
+ ! svn_dirent_is_root(*pcommon, basedir_len))
+ rel_item++;
+ }
+
+ APR_ARRAY_PUSH(*pcondensed_targets, const char *)
+ = apr_pstrdup(pool, rel_item);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_path_remove_redundancies(apr_array_header_t **pcondensed_targets,
+ const apr_array_header_t *targets,
+ apr_pool_t *pool)
+{
+ apr_pool_t *temp_pool;
+ apr_array_header_t *abs_targets;
+ apr_array_header_t *rel_targets;
+ int i;
+
+ if ((targets->nelts <= 0) || (! pcondensed_targets))
+ {
+ /* No targets or no place to store our work means this function
+ really has nothing to do. */
+ if (pcondensed_targets)
+ *pcondensed_targets = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Initialize our temporary pool. */
+ temp_pool = svn_pool_create(pool);
+
+ /* Create our list of absolute paths for our "keepers" */
+ abs_targets = apr_array_make(temp_pool, targets->nelts,
+ sizeof(const char *));
+
+ /* Create our list of untainted paths for our "keepers" */
+ rel_targets = apr_array_make(pool, targets->nelts,
+ sizeof(const char *));
+
+ /* For each target in our list we do the following:
+
+ 1. Calculate its absolute path (ABS_PATH).
+ 2. See if any of the keepers in ABS_TARGETS is a parent of, or
+ is the same path as, ABS_PATH. If so, we ignore this
+ target. If not, however, add this target's absolute path to
+ ABS_TARGETS and its original path to REL_TARGETS.
+ */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *rel_path = APR_ARRAY_IDX(targets, i, const char *);
+ const char *abs_path;
+ int j;
+ svn_boolean_t is_url, keep_me;
+
+ /* Get the absolute path for this target. */
+ is_url = svn_path_is_url(rel_path);
+ if (is_url)
+ abs_path = rel_path;
+ else
+ SVN_ERR(svn_dirent_get_absolute(&abs_path, rel_path, temp_pool));
+
+ /* For each keeper in ABS_TARGETS, see if this target is the
+ same as or a child of that keeper. */
+ keep_me = TRUE;
+ for (j = 0; j < abs_targets->nelts; j++)
+ {
+ const char *keeper = APR_ARRAY_IDX(abs_targets, j, const char *);
+ svn_boolean_t keeper_is_url = svn_path_is_url(keeper);
+ const char *child_relpath;
+
+ /* If KEEPER hasn't the same is-url-ness as ABS_PATH, we
+ know they aren't equal and that one isn't the child of
+ the other. */
+ if (is_url != keeper_is_url)
+ continue;
+
+ /* Quit here if this path is the same as or a child of one of the
+ keepers. */
+ if (is_url)
+ child_relpath = svn_uri_skip_ancestor(keeper, abs_path, temp_pool);
+ else
+ child_relpath = svn_dirent_skip_ancestor(keeper, abs_path);
+ if (child_relpath)
+ {
+ keep_me = FALSE;
+ break;
+ }
+ }
+
+ /* If this is a new keeper, add its absolute path to ABS_TARGETS
+ and its original path to REL_TARGETS. */
+ if (keep_me)
+ {
+ APR_ARRAY_PUSH(abs_targets, const char *) = abs_path;
+ APR_ARRAY_PUSH(rel_targets, const char *) = rel_path;
+ }
+ }
+
+ /* Destroy our temporary pool. */
+ svn_pool_destroy(temp_pool);
+
+ /* Make sure we return the list of untainted keeper paths. */
+ *pcondensed_targets = rel_targets;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/temp_serializer.c b/contrib/subversion/subversion/libsvn_subr/temp_serializer.c
new file mode 100644
index 0000000..261267a
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/temp_serializer.c
@@ -0,0 +1,382 @@
+/*
+ * svn_temp_serializer.c: implement the tempoary structure serialization API
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <assert.h>
+#include "private/svn_temp_serializer.h"
+#include "svn_string.h"
+
+/* This is a very efficient serialization and especially efficient
+ * deserialization framework. The idea is just to concatenate all sub-
+ * structures and strings into a single buffer while preserving proper
+ * member alignment. Pointers will be replaced by the respective data
+ * offsets in the buffer when that target that it pointed to gets
+ * serialized, i.e. appended to the data buffer written so far.
+ *
+ * Hence, deserialization can be simply done by copying the buffer and
+ * adjusting the pointers. No fine-grained allocation and copying is
+ * necessary.
+ */
+
+/* An element in the structure stack. It contains a pointer to the source
+ * structure so that the relative offset of sub-structure or string
+ * references can be determined properly. It also contains the corresponding
+ * position within the serialized data. Thus, pointers can be serialized
+ * as offsets within the target buffer.
+ */
+typedef struct source_stack_t
+{
+ /* the source structure passed in to *_init or *_push */
+ const void *source_struct;
+
+ /* offset within the target buffer to where the structure got copied */
+ apr_size_t target_offset;
+
+ /* parent stack entry. Will be NULL for the root entry.
+ * Items in the svn_temp_serializer__context_t recycler will use this
+ * to link to the next unused item. */
+ struct source_stack_t *upper;
+} source_stack_t;
+
+/* Serialization context info. It basically consists of the buffer holding
+ * the serialized result and the stack of source structure information.
+ */
+struct svn_temp_serializer__context_t
+{
+ /* allocations are made from this pool */
+ apr_pool_t *pool;
+
+ /* the buffer holding all serialized data */
+ svn_stringbuf_t *buffer;
+
+ /* the stack of structures being serialized. If NULL, the serialization
+ * process has been finished. However, it is not necessarily NULL when
+ * the application end serialization. */
+ source_stack_t *source;
+
+ /* unused stack elements will be put here for later reuse. */
+ source_stack_t *recycler;
+};
+
+/* Make sure the serialized data len is a multiple of the default alignment,
+ * i.e. structures may be appended without violating member alignment
+ * guarantees.
+ */
+static void
+align_buffer_end(svn_temp_serializer__context_t *context)
+{
+ apr_size_t current_len = context->buffer->len;
+ apr_size_t aligned_len = APR_ALIGN_DEFAULT(current_len);
+
+ if (aligned_len + 1 > context->buffer->blocksize)
+ svn_stringbuf_ensure(context->buffer, aligned_len);
+
+ context->buffer->len = aligned_len;
+}
+
+/* Begin the serialization process for the SOURCE_STRUCT and all objects
+ * referenced from it. STRUCT_SIZE must match the result of sizeof() of
+ * the actual structure. You may suggest a larger initial buffer size
+ * in SUGGESTED_BUFFER_SIZE to minimize the number of internal buffer
+ * re-allocations during the serialization process. All allocations will
+ * be made from POOL.
+ */
+svn_temp_serializer__context_t *
+svn_temp_serializer__init(const void *source_struct,
+ apr_size_t struct_size,
+ apr_size_t suggested_buffer_size,
+ apr_pool_t *pool)
+{
+ /* select a meaningful initial memory buffer capacity */
+ apr_size_t init_size = suggested_buffer_size < struct_size
+ ? struct_size
+ : suggested_buffer_size;
+
+ /* create the serialization context and initialize it */
+ svn_temp_serializer__context_t *context = apr_palloc(pool, sizeof(*context));
+ context->pool = pool;
+ context->buffer = svn_stringbuf_create_ensure(init_size, pool);
+ context->recycler = NULL;
+
+ /* If a source struct has been given, make it the root struct. */
+ if (source_struct)
+ {
+ context->source = apr_palloc(pool, sizeof(*context->source));
+ context->source->source_struct = source_struct;
+ context->source->target_offset = 0;
+ context->source->upper = NULL;
+
+ /* serialize, i.e. append, the content of the first structure */
+ svn_stringbuf_appendbytes(context->buffer, source_struct, struct_size);
+ }
+ else
+ {
+ /* The root struct will be set with the first push() op, or not at all
+ * (in case of a plain string). */
+ context->source = NULL;
+ }
+
+ /* done */
+ return context;
+}
+
+/* Continue the serialization process of the SOURCE_STRUCT that has already
+ * been serialized to BUFFER but contains references to new objects yet to
+ * serialize. The current size of the serialized data is given in
+ * CURRENTLY_USED. If the allocated data buffer is actually larger, you may
+ * specifiy that in CURRENTLY_ALLOCATED to prevent unnecessary allocations.
+ * Otherwise, set it to 0. All allocations will be made from POOl.
+ */
+svn_temp_serializer__context_t *
+svn_temp_serializer__init_append(void *buffer,
+ void *source_struct,
+ apr_size_t currently_used,
+ apr_size_t currently_allocated,
+ apr_pool_t *pool)
+{
+ /* determine the current memory buffer capacity */
+ apr_size_t init_size = currently_allocated < currently_used
+ ? currently_used
+ : currently_allocated;
+
+ /* create the serialization context and initialize it */
+ svn_temp_serializer__context_t *context = apr_palloc(pool, sizeof(*context));
+ context->pool = pool;
+
+ /* use BUFFER as serialization target */
+ context->buffer = svn_stringbuf_create_ensure(0, pool);
+ context->buffer->data = buffer;
+ context->buffer->len = currently_used;
+ context->buffer->blocksize = init_size;
+
+ /* SOURCE_STRUCT is our serialization root */
+ context->source = apr_palloc(pool, sizeof(*context->source));
+ context->source->source_struct = source_struct;
+ context->source->target_offset = (char *)source_struct - (char *)buffer;
+ context->source->upper = NULL;
+
+ /* initialize the RECYCLER */
+ context->recycler = NULL;
+
+ /* done */
+ return context;
+}
+
+/* Utility function replacing the serialized pointer corresponding to
+ * *SOURCE_POINTER with the offset that it will be put when being append
+ * right after this function call.
+ */
+static void
+store_current_end_pointer(svn_temp_serializer__context_t *context,
+ const void * const * source_pointer)
+{
+ apr_size_t ptr_offset;
+ apr_size_t *target_ptr;
+
+ /* if *source_pointer is the root struct, there will be no parent structure
+ * to relate it to */
+ if (context->source == NULL)
+ return;
+
+ /* position of the serialized pointer relative to the begin of the buffer */
+ ptr_offset = (const char *)source_pointer
+ - (const char *)context->source->source_struct
+ + context->source->target_offset;
+
+ /* the offset must be within the serialized data. Otherwise, you forgot
+ * to serialize the respective sub-struct. */
+ assert(context->buffer->len > ptr_offset);
+
+ /* use the serialized pointer as a storage for the offset */
+ target_ptr = (apr_size_t*)(context->buffer->data + ptr_offset);
+
+ /* store the current buffer length because that's where we will append
+ * the serialized data of the sub-struct or string */
+ *target_ptr = *source_pointer == NULL
+ ? 0
+ : context->buffer->len - context->source->target_offset;
+}
+
+/* Begin serialization of a referenced sub-structure within the
+ * serialization CONTEXT. SOURCE_STRUCT must be a reference to the pointer
+ * in the original parent structure so that the correspondence in the
+ * serialized structure can be established. STRUCT_SIZE must match the
+ * result of sizeof() of the actual structure.
+ */
+void
+svn_temp_serializer__push(svn_temp_serializer__context_t *context,
+ const void * const * source_struct,
+ apr_size_t struct_size)
+{
+ const void *source = *source_struct;
+ source_stack_t *new;
+
+ /* recycle an old entry or create a new one for the structure stack */
+ if (context->recycler)
+ {
+ new = context->recycler;
+ context->recycler = new->upper;
+ }
+ else
+ new = apr_palloc(context->pool, sizeof(*new));
+
+ /* the serialized structure must be properly aligned */
+ if (source)
+ align_buffer_end(context);
+
+ /* Store the offset at which the struct data that will the appended.
+ * Write 0 for NULL pointers. */
+ store_current_end_pointer(context, source_struct);
+
+ /* store source and target information */
+ new->source_struct = source;
+ new->target_offset = context->buffer->len;
+
+ /* put the new entry onto the stack*/
+ new->upper = context->source;
+ context->source = new;
+
+ /* finally, actually append the new struct
+ * (so we can now manipulate pointers within it) */
+ if (*source_struct)
+ svn_stringbuf_appendbytes(context->buffer, source, struct_size);
+}
+
+/* Remove the lastest structure from the stack.
+ */
+void
+svn_temp_serializer__pop(svn_temp_serializer__context_t *context)
+{
+ source_stack_t *old = context->source;
+
+ /* we may pop the original struct but not further */
+ assert(context->source);
+
+ /* one level up the structure stack */
+ context->source = context->source->upper;
+
+ /* put the old stack element into the recycler for later reuse */
+ old->upper = context->recycler;
+ context->recycler = old;
+}
+
+/* Serialize a string referenced from the current structure within the
+ * serialization CONTEXT. S must be a reference to the char* pointer in
+ * the original structure so that the correspondence in the serialized
+ * structure can be established.
+ */
+void
+svn_temp_serializer__add_string(svn_temp_serializer__context_t *context,
+ const char * const * s)
+{
+ const char *string = *s;
+
+ /* Store the offset at which the string data that will the appended.
+ * Write 0 for NULL pointers. Strings don't need special alignment. */
+ store_current_end_pointer(context, (const void **)s);
+
+ /* append the string data */
+ if (string)
+ svn_stringbuf_appendbytes(context->buffer, string, strlen(string) + 1);
+}
+
+/* Set the serialized representation of the pointer PTR inside the current
+ * structure within the serialization CONTEXT to NULL. This is particularly
+ * useful if the pointer is not NULL in the source structure.
+ */
+void
+svn_temp_serializer__set_null(svn_temp_serializer__context_t *context,
+ const void * const * ptr)
+{
+ apr_size_t offset;
+
+ /* there must be a parent structure */
+ assert(context->source);
+
+ /* position of the serialized pointer relative to the begin of the buffer */
+ offset = (const char *)ptr
+ - (const char *)context->source->source_struct
+ + context->source->target_offset;
+
+ /* the offset must be within the serialized data. Otherwise, you forgot
+ * to serialize the respective sub-struct. */
+ assert(context->buffer->len > offset);
+
+ /* use the serialized pointer as a storage for the offset */
+ *(apr_size_t*)(context->buffer->data + offset) = 0;
+}
+
+/* Return the number of bytes currently used in the serialization buffer
+ * of the given serialization CONTEXT.*/
+apr_size_t
+svn_temp_serializer__get_length(svn_temp_serializer__context_t *context)
+{
+ return context->buffer->len;
+}
+
+/* Return the data buffer that receives the serialized data from
+ * the given serialization CONTEXT.
+ */
+svn_stringbuf_t *
+svn_temp_serializer__get(svn_temp_serializer__context_t *context)
+{
+ return context->buffer;
+}
+
+/* Replace the deserialized pointer value at PTR inside BUFFER with a
+ * proper pointer value.
+ */
+void
+svn_temp_deserializer__resolve(void *buffer, void **ptr)
+{
+ /* All pointers are stored as offsets to the buffer start
+ * (of the respective serialized sub-struct). */
+ apr_size_t ptr_offset = *(apr_size_t *)ptr;
+ if (ptr_offset)
+ {
+ /* Reconstruct the original pointer value */
+ const char *target = (const char *)buffer + ptr_offset;
+
+ /* All sub-structs are written _after_ their respective parent.
+ * Thus, all offsets are > 0. If the following assertion is not met,
+ * the data is either corrupt or you tried to resolve the pointer
+ * more than once. */
+ assert(target > (const char *)buffer);
+
+ /* replace the PTR_OFFSET in *ptr with the pointer to TARGET */
+ (*(const char **)ptr) = target;
+ }
+ else
+ {
+ /* NULL pointers are stored as 0 which might have a different
+ * binary representation. */
+ *ptr = NULL;
+ }
+}
+
+const void *
+svn_temp_deserializer__ptr(const void *buffer, const void *const *ptr)
+{
+ return (apr_size_t)*ptr == 0
+ ? NULL
+ : (const char*)buffer + (apr_size_t)*ptr;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/time.c b/contrib/subversion/subversion/libsvn_subr/time.c
new file mode 100644
index 0000000..ccd6269
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/time.c
@@ -0,0 +1,277 @@
+/*
+ * time.c: time/date utilities
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+#include <stdlib.h>
+#include <apr_pools.h>
+#include <apr_time.h>
+#include <apr_strings.h>
+#include "svn_io.h"
+#include "svn_time.h"
+#include "svn_utf.h"
+#include "svn_error.h"
+#include "svn_private_config.h"
+
+
+
+/*** Code. ***/
+
+/* Our timestamp strings look like this:
+ *
+ * "2002-05-07Thh:mm:ss.uuuuuuZ"
+ *
+ * The format is conformant with ISO-8601 and the date format required
+ * by RFC2518 for creationdate. It is a direct conversion between
+ * apr_time_t and a string, so converting to string and back retains
+ * the exact value.
+ */
+#define TIMESTAMP_FORMAT "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ"
+
+/* Our old timestamp strings looked like this:
+ *
+ * "Tue 3 Oct 2000 HH:MM:SS.UUU (day 277, dst 1, gmt_off -18000)"
+ *
+ * The idea is that they are conventionally human-readable for the
+ * first part, and then in parentheses comes everything else required
+ * to completely fill in an apr_time_exp_t: tm_yday, tm_isdst,
+ * and tm_gmtoff.
+ *
+ * This format is still recognized on input, for backward
+ * compatibility, but no longer generated.
+ */
+#define OLD_TIMESTAMP_FORMAT \
+ "%3s %d %3s %d %02d:%02d:%02d.%06d (day %03d, dst %d, gmt_off %06d)"
+
+/* Our human representation of dates looks like this:
+ *
+ * "2002-06-23 11:13:02 +0300 (Sun, 23 Jun 2002)"
+ *
+ * This format is used whenever time is shown to the user. It consists
+ * of a machine parseable, almost ISO-8601, part in the beginning -
+ * and a human explanatory part at the end. The machine parseable part
+ * is generated strictly by APR and our code, with a apr_snprintf. The
+ * human explanatory part is generated by apr_strftime, which means
+ * that its generation can be affected by locale, it can fail and it
+ * doesn't need to be constant in size. In other words, perfect to be
+ * converted to a configuration option later on.
+ */
+/* Maximum length for the date string. */
+#define SVN_TIME__MAX_LENGTH 80
+/* Machine parseable part, generated by apr_snprintf. */
+#define HUMAN_TIMESTAMP_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %+.2d%.2d"
+/* Human explanatory part, generated by apr_strftime as "Sat, 01 Jan 2000" */
+#define human_timestamp_format_suffix _(" (%a, %d %b %Y)")
+
+const char *
+svn_time_to_cstring(apr_time_t when, apr_pool_t *pool)
+{
+ apr_time_exp_t exploded_time;
+
+ /* We toss apr_status_t return value here -- for one thing, caller
+ should pass in good information. But also, where APR's own code
+ calls these functions it tosses the return values, and
+ furthermore their current implementations can only return success
+ anyway. */
+
+ /* We get the date in GMT now -- and expect the tm_gmtoff and
+ tm_isdst to be not set. We also ignore the weekday and yearday,
+ since those are not needed. */
+
+ apr_time_exp_gmt(&exploded_time, when);
+
+ /* It would be nice to use apr_strftime(), but APR doesn't give a
+ way to convert back, so we wouldn't be able to share the format
+ string between the writer and reader. */
+ return apr_psprintf(pool,
+ TIMESTAMP_FORMAT,
+ exploded_time.tm_year + 1900,
+ exploded_time.tm_mon + 1,
+ exploded_time.tm_mday,
+ exploded_time.tm_hour,
+ exploded_time.tm_min,
+ exploded_time.tm_sec,
+ exploded_time.tm_usec);
+}
+
+
+static apr_int32_t
+find_matching_string(char *str, apr_size_t size, const char strings[][4])
+{
+ apr_size_t i;
+
+ for (i = 0; i < size; i++)
+ if (strings[i] && (strcmp(str, strings[i]) == 0))
+ return (apr_int32_t) i;
+
+ return -1;
+}
+
+
+svn_error_t *
+svn_time_from_cstring(apr_time_t *when, const char *data, apr_pool_t *pool)
+{
+ apr_time_exp_t exploded_time;
+ apr_status_t apr_err;
+ char wday[4], month[4];
+ char *c;
+
+ /* Open-code parsing of the new timestamp format, as this
+ is a hot path for reading the entries file. This format looks
+ like: "2001-08-31T04:24:14.966996Z" */
+ exploded_time.tm_year = (apr_int32_t) strtol(data, &c, 10);
+ if (*c++ != '-') goto fail;
+ exploded_time.tm_mon = (apr_int32_t) strtol(c, &c, 10);
+ if (*c++ != '-') goto fail;
+ exploded_time.tm_mday = (apr_int32_t) strtol(c, &c, 10);
+ if (*c++ != 'T') goto fail;
+ exploded_time.tm_hour = (apr_int32_t) strtol(c, &c, 10);
+ if (*c++ != ':') goto fail;
+ exploded_time.tm_min = (apr_int32_t) strtol(c, &c, 10);
+ if (*c++ != ':') goto fail;
+ exploded_time.tm_sec = (apr_int32_t) strtol(c, &c, 10);
+ if (*c++ != '.') goto fail;
+ exploded_time.tm_usec = (apr_int32_t) strtol(c, &c, 10);
+ if (*c++ != 'Z') goto fail;
+
+ exploded_time.tm_year -= 1900;
+ exploded_time.tm_mon -= 1;
+ exploded_time.tm_wday = 0;
+ exploded_time.tm_yday = 0;
+ exploded_time.tm_isdst = 0;
+ exploded_time.tm_gmtoff = 0;
+
+ apr_err = apr_time_exp_gmt_get(when, &exploded_time);
+ if (apr_err == APR_SUCCESS)
+ return SVN_NO_ERROR;
+
+ return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL);
+
+ fail:
+ /* Try the compatibility option. This does not need to be fast,
+ as this format is no longer generated and the client will convert
+ an old-format entries file the first time it reads it. */
+ if (sscanf(data,
+ OLD_TIMESTAMP_FORMAT,
+ wday,
+ &exploded_time.tm_mday,
+ month,
+ &exploded_time.tm_year,
+ &exploded_time.tm_hour,
+ &exploded_time.tm_min,
+ &exploded_time.tm_sec,
+ &exploded_time.tm_usec,
+ &exploded_time.tm_yday,
+ &exploded_time.tm_isdst,
+ &exploded_time.tm_gmtoff) == 11)
+ {
+ exploded_time.tm_year -= 1900;
+ exploded_time.tm_yday -= 1;
+ /* Using hard coded limits for the arrays - they are going away
+ soon in any case. */
+ exploded_time.tm_wday = find_matching_string(wday, 7, apr_day_snames);
+ exploded_time.tm_mon = find_matching_string(month, 12, apr_month_snames);
+
+ apr_err = apr_time_exp_gmt_get(when, &exploded_time);
+ if (apr_err != APR_SUCCESS)
+ return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL);
+
+ return SVN_NO_ERROR;
+ }
+ /* Timestamp is something we do not recognize. */
+ else
+ return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL);
+}
+
+
+const char *
+svn_time_to_human_cstring(apr_time_t when, apr_pool_t *pool)
+{
+ apr_time_exp_t exploded_time;
+ apr_size_t len, retlen;
+ apr_status_t ret;
+ char *datestr, *curptr, human_datestr[SVN_TIME__MAX_LENGTH];
+
+ /* Get the time into parts */
+ ret = apr_time_exp_lt(&exploded_time, when);
+ if (ret)
+ return NULL;
+
+ /* Make room for datestring */
+ datestr = apr_palloc(pool, SVN_TIME__MAX_LENGTH);
+
+ /* Put in machine parseable part */
+ len = apr_snprintf(datestr,
+ SVN_TIME__MAX_LENGTH,
+ HUMAN_TIMESTAMP_FORMAT,
+ exploded_time.tm_year + 1900,
+ exploded_time.tm_mon + 1,
+ exploded_time.tm_mday,
+ exploded_time.tm_hour,
+ exploded_time.tm_min,
+ exploded_time.tm_sec,
+ exploded_time.tm_gmtoff / (60 * 60),
+ (abs(exploded_time.tm_gmtoff) / 60) % 60);
+
+ /* If we overfilled the buffer, just return what we got. */
+ if (len >= SVN_TIME__MAX_LENGTH)
+ return datestr;
+
+ /* Calculate offset to the end of the machine parseable part. */
+ curptr = datestr + len;
+
+ /* Put in human explanatory part */
+ ret = apr_strftime(human_datestr,
+ &retlen,
+ SVN_TIME__MAX_LENGTH - len,
+ human_timestamp_format_suffix,
+ &exploded_time);
+
+ /* If there was an error, ensure that the string is zero-terminated. */
+ if (ret || retlen == 0)
+ *curptr = '\0';
+ else
+ {
+ const char *utf8_string;
+ svn_error_t *err;
+
+ err = svn_utf_cstring_to_utf8(&utf8_string, human_datestr, pool);
+ if (err)
+ {
+ *curptr = '\0';
+ svn_error_clear(err);
+ }
+ else
+ apr_cpystrn(curptr, utf8_string, SVN_TIME__MAX_LENGTH - len);
+ }
+
+ return datestr;
+}
+
+
+void
+svn_sleep_for_timestamps(void)
+{
+ svn_io_sleep_for_timestamps(NULL, NULL);
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/token.c b/contrib/subversion/subversion/libsvn_subr/token.c
new file mode 100644
index 0000000..d4fc725
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/token.c
@@ -0,0 +1,98 @@
+/*
+ * token.c : value/string-token functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_types.h"
+#include "svn_error.h"
+
+#include "private/svn_token.h"
+#include "svn_private_config.h"
+
+
+const char *
+svn_token__to_word(const svn_token_map_t *map,
+ int value)
+{
+ for (; map->str != NULL; ++map)
+ if (map->val == value)
+ return map->str;
+
+ /* Internal, numeric values should always be found. */
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+}
+
+
+int
+svn_token__from_word_strict(const svn_token_map_t *map,
+ const char *word)
+{
+ int value = svn_token__from_word(map, word);
+
+ if (value == SVN_TOKEN_UNKNOWN)
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+
+ return value;
+}
+
+
+svn_error_t *
+svn_token__from_word_err(int *value,
+ const svn_token_map_t *map,
+ const char *word)
+{
+ *value = svn_token__from_word(map, word);
+
+ if (*value == SVN_TOKEN_UNKNOWN)
+ return svn_error_createf(SVN_ERR_BAD_TOKEN, NULL,
+ _("Token '%s' is unrecognized"),
+ word);
+
+ return SVN_NO_ERROR;
+}
+
+
+int
+svn_token__from_word(const svn_token_map_t *map,
+ const char *word)
+{
+ if (word == NULL)
+ return SVN_TOKEN_UNKNOWN;
+
+ for (; map->str != NULL; ++map)
+ if (strcmp(map->str, word) == 0)
+ return map->val;
+
+ return SVN_TOKEN_UNKNOWN;
+}
+
+
+int
+svn_token__from_mem(const svn_token_map_t *map,
+ const char *word,
+ apr_size_t len)
+{
+ for (; map->str != NULL; ++map)
+ if (strncmp(map->str, word, len) == 0 && map->str[len] == '\0')
+ return map->val;
+
+ return SVN_TOKEN_UNKNOWN;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/types.c b/contrib/subversion/subversion/libsvn_subr/types.c
new file mode 100644
index 0000000..30ebb65
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/types.c
@@ -0,0 +1,340 @@
+/*
+ * svn_types.c : Implementation for Subversion's data types.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+#include <apr_uuid.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_string.h"
+#include "svn_props.h"
+#include "svn_private_config.h"
+
+svn_error_t *
+svn_revnum_parse(svn_revnum_t *rev,
+ const char *str,
+ const char **endptr)
+{
+ char *end;
+
+ svn_revnum_t result = strtol(str, &end, 10);
+
+ if (endptr)
+ *endptr = end;
+
+ if (str == end)
+ return svn_error_createf(SVN_ERR_REVNUM_PARSE_FAILURE, NULL,
+ _("Invalid revision number found parsing '%s'"),
+ str);
+
+ if (result < 0)
+ {
+ /* The end pointer from strtol() is valid, but a negative revision
+ number is invalid, so move the end pointer back to the
+ beginning of the string. */
+ if (endptr)
+ *endptr = str;
+
+ return svn_error_createf(SVN_ERR_REVNUM_PARSE_FAILURE, NULL,
+ _("Negative revision number found parsing '%s'"),
+ str);
+ }
+
+ *rev = result;
+
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_uuid_generate(apr_pool_t *pool)
+{
+ apr_uuid_t uuid;
+ char *uuid_str = apr_pcalloc(pool, APR_UUID_FORMATTED_LENGTH + 1);
+ apr_uuid_get(&uuid);
+ apr_uuid_format(uuid_str, &uuid);
+ return uuid_str;
+}
+
+const char *
+svn_depth_to_word(svn_depth_t depth)
+{
+ switch (depth)
+ {
+ case svn_depth_exclude:
+ return "exclude";
+ case svn_depth_unknown:
+ return "unknown";
+ case svn_depth_empty:
+ return "empty";
+ case svn_depth_files:
+ return "files";
+ case svn_depth_immediates:
+ return "immediates";
+ case svn_depth_infinity:
+ return "infinity";
+ default:
+ return "INVALID-DEPTH";
+ }
+}
+
+
+svn_depth_t
+svn_depth_from_word(const char *word)
+{
+ if (strcmp(word, "exclude") == 0)
+ return svn_depth_exclude;
+ if (strcmp(word, "unknown") == 0)
+ return svn_depth_unknown;
+ if (strcmp(word, "empty") == 0)
+ return svn_depth_empty;
+ if (strcmp(word, "files") == 0)
+ return svn_depth_files;
+ if (strcmp(word, "immediates") == 0)
+ return svn_depth_immediates;
+ if (strcmp(word, "infinity") == 0)
+ return svn_depth_infinity;
+ /* There's no special value for invalid depth, and no convincing
+ reason to make one yet, so just fall back to unknown depth.
+ If you ever change that convention, check callers to make sure
+ they're not depending on it (e.g., option parsing in main() ).
+ */
+ return svn_depth_unknown;
+}
+
+const char *
+svn_node_kind_to_word(svn_node_kind_t kind)
+{
+ switch (kind)
+ {
+ case svn_node_none:
+ return "none";
+ case svn_node_file:
+ return "file";
+ case svn_node_dir:
+ return "dir";
+ case svn_node_symlink:
+ return "symlink";
+ case svn_node_unknown:
+ default:
+ return "unknown";
+ }
+}
+
+
+svn_node_kind_t
+svn_node_kind_from_word(const char *word)
+{
+ if (word == NULL)
+ return svn_node_unknown;
+
+ if (strcmp(word, "none") == 0)
+ return svn_node_none;
+ else if (strcmp(word, "file") == 0)
+ return svn_node_file;
+ else if (strcmp(word, "dir") == 0)
+ return svn_node_dir;
+ else if (strcmp(word, "symlink") == 0)
+ return svn_node_symlink;
+ else
+ /* This also handles word == "unknown" */
+ return svn_node_unknown;
+}
+
+const char *
+svn_tristate__to_word(svn_tristate_t tristate)
+{
+ switch (tristate)
+ {
+ case svn_tristate_false:
+ return "false";
+ case svn_tristate_true:
+ return "true";
+ case svn_tristate_unknown:
+ default:
+ return NULL;
+ }
+}
+
+svn_tristate_t
+svn_tristate__from_word(const char *word)
+{
+ if (word == NULL)
+ return svn_tristate_unknown;
+ else if (0 == svn_cstring_casecmp(word, "true")
+ || 0 == svn_cstring_casecmp(word, "yes")
+ || 0 == svn_cstring_casecmp(word, "on")
+ || 0 == strcmp(word, "1"))
+ return svn_tristate_true;
+ else if (0 == svn_cstring_casecmp(word, "false")
+ || 0 == svn_cstring_casecmp(word, "no")
+ || 0 == svn_cstring_casecmp(word, "off")
+ || 0 == strcmp(word, "0"))
+ return svn_tristate_false;
+
+ return svn_tristate_unknown;
+}
+
+svn_commit_info_t *
+svn_create_commit_info(apr_pool_t *pool)
+{
+ svn_commit_info_t *commit_info
+ = apr_pcalloc(pool, sizeof(*commit_info));
+
+ commit_info->revision = SVN_INVALID_REVNUM;
+ /* All other fields were initialized to NULL above. */
+
+ return commit_info;
+}
+
+svn_commit_info_t *
+svn_commit_info_dup(const svn_commit_info_t *src_commit_info,
+ apr_pool_t *pool)
+{
+ svn_commit_info_t *dst_commit_info
+ = apr_palloc(pool, sizeof(*dst_commit_info));
+
+ dst_commit_info->date = src_commit_info->date
+ ? apr_pstrdup(pool, src_commit_info->date) : NULL;
+ dst_commit_info->author = src_commit_info->author
+ ? apr_pstrdup(pool, src_commit_info->author) : NULL;
+ dst_commit_info->revision = src_commit_info->revision;
+ dst_commit_info->post_commit_err = src_commit_info->post_commit_err
+ ? apr_pstrdup(pool, src_commit_info->post_commit_err) : NULL;
+ dst_commit_info->repos_root = src_commit_info->repos_root
+ ? apr_pstrdup(pool, src_commit_info->repos_root) : NULL;
+
+ return dst_commit_info;
+}
+
+svn_log_changed_path2_t *
+svn_log_changed_path2_create(apr_pool_t *pool)
+{
+ svn_log_changed_path2_t *new_changed_path
+ = apr_pcalloc(pool, sizeof(*new_changed_path));
+
+ new_changed_path->text_modified = svn_tristate_unknown;
+ new_changed_path->props_modified = svn_tristate_unknown;
+
+ return new_changed_path;
+}
+
+svn_log_changed_path2_t *
+svn_log_changed_path2_dup(const svn_log_changed_path2_t *changed_path,
+ apr_pool_t *pool)
+{
+ svn_log_changed_path2_t *new_changed_path
+ = apr_palloc(pool, sizeof(*new_changed_path));
+
+ *new_changed_path = *changed_path;
+
+ if (new_changed_path->copyfrom_path)
+ new_changed_path->copyfrom_path =
+ apr_pstrdup(pool, new_changed_path->copyfrom_path);
+
+ return new_changed_path;
+}
+
+svn_dirent_t *
+svn_dirent_create(apr_pool_t *result_pool)
+{
+ svn_dirent_t *new_dirent = apr_pcalloc(result_pool, sizeof(*new_dirent));
+
+ new_dirent->kind = svn_node_unknown;
+ new_dirent->size = SVN_INVALID_FILESIZE;
+ new_dirent->created_rev = SVN_INVALID_REVNUM;
+ new_dirent->time = 0;
+ new_dirent->last_author = NULL;
+
+ return new_dirent;
+}
+
+svn_dirent_t *
+svn_dirent_dup(const svn_dirent_t *dirent,
+ apr_pool_t *pool)
+{
+ svn_dirent_t *new_dirent = apr_palloc(pool, sizeof(*new_dirent));
+
+ *new_dirent = *dirent;
+
+ new_dirent->last_author = apr_pstrdup(pool, dirent->last_author);
+
+ return new_dirent;
+}
+
+svn_log_entry_t *
+svn_log_entry_create(apr_pool_t *pool)
+{
+ svn_log_entry_t *log_entry = apr_pcalloc(pool, sizeof(*log_entry));
+
+ return log_entry;
+}
+
+svn_log_entry_t *
+svn_log_entry_dup(const svn_log_entry_t *log_entry, apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ svn_log_entry_t *new_entry = apr_palloc(pool, sizeof(*new_entry));
+
+ *new_entry = *log_entry;
+
+ if (log_entry->revprops)
+ new_entry->revprops = svn_prop_hash_dup(log_entry->revprops, pool);
+
+ if (log_entry->changed_paths2)
+ {
+ new_entry->changed_paths2 = apr_hash_make(pool);
+
+ for (hi = apr_hash_first(pool, log_entry->changed_paths2);
+ hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *change;
+
+ apr_hash_this(hi, &key, NULL, &change);
+
+ svn_hash_sets(new_entry->changed_paths2, apr_pstrdup(pool, key),
+ svn_log_changed_path2_dup(change, pool));
+ }
+ }
+
+ /* We can't copy changed_paths by itself without using deprecated code,
+ but we don't have to, as this function was new after the introduction
+ of the changed_paths2 field. */
+ new_entry->changed_paths = new_entry->changed_paths2;
+
+ return new_entry;
+}
+
+svn_location_segment_t *
+svn_location_segment_dup(const svn_location_segment_t *segment,
+ apr_pool_t *pool)
+{
+ svn_location_segment_t *new_segment =
+ apr_palloc(pool, sizeof(*new_segment));
+
+ *new_segment = *segment;
+ if (segment->path)
+ new_segment->path = apr_pstrdup(pool, segment->path);
+ return new_segment;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/user.c b/contrib/subversion/subversion/libsvn_subr/user.c
new file mode 100644
index 0000000..7d6d0bf
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/user.c
@@ -0,0 +1,86 @@
+/*
+ * user.c: APR wrapper functions for Subversion
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_pools.h>
+#include <apr_user.h>
+#include <apr_env.h>
+
+#include "svn_user.h"
+#include "svn_utf.h"
+
+/* Get the current user's name from the OS */
+static const char *
+get_os_username(apr_pool_t *pool)
+{
+#if APR_HAS_USER
+ char *username;
+ apr_uid_t uid;
+ apr_gid_t gid;
+
+ if (apr_uid_current(&uid, &gid, pool) == APR_SUCCESS &&
+ apr_uid_name_get(&username, uid, pool) == APR_SUCCESS)
+ return username;
+#endif
+
+ return NULL;
+}
+
+/* Return a UTF8 version of STR, or NULL on error.
+ Use POOL for any necessary allocation. */
+static const char *
+utf8_or_nothing(const char *str, apr_pool_t *pool) {
+ if (str)
+ {
+ const char *utf8_str;
+ svn_error_t *err = svn_utf_cstring_to_utf8(&utf8_str, str, pool);
+ if (! err)
+ return utf8_str;
+ svn_error_clear(err);
+ }
+ return NULL;
+}
+
+const char *
+svn_user_get_name(apr_pool_t *pool)
+{
+ const char *username = get_os_username(pool);
+ return utf8_or_nothing(username, pool);
+}
+
+const char *
+svn_user_get_homedir(apr_pool_t *pool)
+{
+ const char *username;
+ char *homedir;
+
+ if (apr_env_get(&homedir, "HOME", pool) == APR_SUCCESS)
+ return utf8_or_nothing(homedir, pool);
+
+ username = get_os_username(pool);
+ if (username != NULL &&
+ apr_uid_homepath_get(&homedir, username, pool) == APR_SUCCESS)
+ return utf8_or_nothing(homedir, pool);
+
+ return NULL;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/username_providers.c b/contrib/subversion/subversion/libsvn_subr/username_providers.c
new file mode 100644
index 0000000..a6ef5b3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/username_providers.c
@@ -0,0 +1,306 @@
+/*
+ * username_providers.c: providers for SVN_AUTH_CRED_USERNAME
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_pools.h>
+#include "svn_hash.h"
+#include "svn_auth.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_config.h"
+#include "svn_user.h"
+
+
+/*-----------------------------------------------------------------------*/
+/* File provider */
+/*-----------------------------------------------------------------------*/
+
+/* The key that will be stored on disk. Serves the same role as similar
+ constants in other providers. */
+#define AUTHN_USERNAME_KEY "username"
+
+
+
+/*** Username-only Provider ***/
+static svn_error_t *
+username_first_creds(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ const char *config_dir = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_CONFIG_DIR);
+ const char *username = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_DEFAULT_USERNAME);
+ svn_boolean_t may_save = !! username;
+ svn_error_t *err;
+
+ /* If we don't have a usename yet, try the auth cache */
+ if (! username)
+ {
+ apr_hash_t *creds_hash = NULL;
+
+ /* Try to load credentials from a file on disk, based on the
+ realmstring. Don't throw an error, though: if something went
+ wrong reading the file, no big deal. What really matters is that
+ we failed to get the creds, so allow the auth system to try the
+ next provider. */
+ err = svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_USERNAME,
+ realmstring, config_dir, pool);
+ svn_error_clear(err);
+ if (! err && creds_hash)
+ {
+ svn_string_t *str = svn_hash_gets(creds_hash, AUTHN_USERNAME_KEY);
+ if (str && str->data)
+ username = str->data;
+ }
+ }
+
+ /* If that failed, ask the OS for the username */
+ if (! username)
+ username = svn_user_get_name(pool);
+
+ if (username)
+ {
+ svn_auth_cred_simple_t *creds = apr_pcalloc(pool, sizeof(*creds));
+ creds->username = username;
+ creds->may_save = may_save;
+ *credentials = creds;
+ }
+ else
+ *credentials = NULL;
+
+ *iter_baton = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+username_save_creds(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ svn_auth_cred_simple_t *creds = credentials;
+ apr_hash_t *creds_hash = NULL;
+ const char *config_dir;
+ svn_error_t *err;
+
+ *saved = FALSE;
+
+ if (! creds->may_save)
+ return SVN_NO_ERROR;
+
+ config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR);
+
+ /* Put the credentials in a hash and save it to disk */
+ creds_hash = apr_hash_make(pool);
+ svn_hash_sets(creds_hash, AUTHN_USERNAME_KEY,
+ svn_string_create(creds->username, pool));
+ err = svn_config_write_auth_data(creds_hash, SVN_AUTH_CRED_USERNAME,
+ realmstring, config_dir, pool);
+ svn_error_clear(err);
+ *saved = ! err;
+
+ return SVN_NO_ERROR;
+}
+
+
+static const svn_auth_provider_t username_provider = {
+ SVN_AUTH_CRED_USERNAME,
+ username_first_creds,
+ NULL,
+ username_save_creds
+};
+
+
+/* Public API */
+void
+svn_auth_get_username_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+
+ po->vtable = &username_provider;
+ *provider = po;
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* Prompt provider */
+/*-----------------------------------------------------------------------*/
+
+/* Baton type for username-only prompting. */
+typedef struct username_prompt_provider_baton_t
+{
+ svn_auth_username_prompt_func_t prompt_func;
+ void *prompt_baton;
+
+ /* how many times to re-prompt after the first one fails */
+ int retry_limit;
+} username_prompt_provider_baton_t;
+
+
+/* Iteration baton type for username-only prompting. */
+typedef struct username_prompt_iter_baton_t
+{
+ /* how many times we've reprompted */
+ int retries;
+
+} username_prompt_iter_baton_t;
+
+
+/*** Helper Functions ***/
+static svn_error_t *
+prompt_for_username_creds(svn_auth_cred_username_t **cred_p,
+ username_prompt_provider_baton_t *pb,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ svn_boolean_t first_time,
+ svn_boolean_t may_save,
+ apr_pool_t *pool)
+{
+ const char *def_username = NULL;
+
+ *cred_p = NULL;
+
+ /* If we're allowed to check for default usernames, do so. */
+ if (first_time)
+ def_username = svn_hash_gets(parameters, SVN_AUTH_PARAM_DEFAULT_USERNAME);
+
+ /* If we have defaults, just build the cred here and return it.
+ *
+ * ### I do wonder why this is here instead of in a separate
+ * ### 'defaults' provider that would run before the prompt
+ * ### provider... Hmmm.
+ */
+ if (def_username)
+ {
+ *cred_p = apr_palloc(pool, sizeof(**cred_p));
+ (*cred_p)->username = apr_pstrdup(pool, def_username);
+ (*cred_p)->may_save = TRUE;
+ }
+ else
+ {
+ SVN_ERR(pb->prompt_func(cred_p, pb->prompt_baton, realmstring,
+ may_save, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Our first attempt will use any default username passed
+ in, and prompt for the remaining stuff. */
+static svn_error_t *
+username_prompt_first_creds(void **credentials_p,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ username_prompt_provider_baton_t *pb = provider_baton;
+ username_prompt_iter_baton_t *ibaton = apr_pcalloc(pool, sizeof(*ibaton));
+ const char *no_auth_cache = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE);
+
+ SVN_ERR(prompt_for_username_creds
+ ((svn_auth_cred_username_t **) credentials_p, pb,
+ parameters, realmstring, TRUE, ! no_auth_cache, pool));
+
+ ibaton->retries = 0;
+ *iter_baton = ibaton;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Subsequent attempts to fetch will ignore the default username
+ value, and simply re-prompt for the username, up to a maximum of
+ ib->pb->retry_limit. */
+static svn_error_t *
+username_prompt_next_creds(void **credentials_p,
+ void *iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ username_prompt_iter_baton_t *ib = iter_baton;
+ username_prompt_provider_baton_t *pb = provider_baton;
+ const char *no_auth_cache = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_NO_AUTH_CACHE);
+
+ if ((pb->retry_limit >= 0) && (ib->retries >= pb->retry_limit))
+ {
+ /* give up, go on to next provider. */
+ *credentials_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ ib->retries++;
+
+ return prompt_for_username_creds
+ ((svn_auth_cred_username_t **) credentials_p, pb,
+ parameters, realmstring, FALSE, ! no_auth_cache, pool);
+}
+
+
+static const svn_auth_provider_t username_prompt_provider = {
+ SVN_AUTH_CRED_USERNAME,
+ username_prompt_first_creds,
+ username_prompt_next_creds,
+ NULL,
+};
+
+
+/* Public API */
+void
+svn_auth_get_username_prompt_provider
+ (svn_auth_provider_object_t **provider,
+ svn_auth_username_prompt_func_t prompt_func,
+ void *prompt_baton,
+ int retry_limit,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+ username_prompt_provider_baton_t *pb = apr_pcalloc(pool, sizeof(*pb));
+
+ pb->prompt_func = prompt_func;
+ pb->prompt_baton = prompt_baton;
+ pb->retry_limit = retry_limit;
+
+ po->vtable = &username_prompt_provider;
+ po->provider_baton = pb;
+ *provider = po;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/utf.c b/contrib/subversion/subversion/libsvn_subr/utf.c
new file mode 100644
index 0000000..355e068
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/utf.c
@@ -0,0 +1,1075 @@
+/*
+ * utf.c: UTF-8 conversion routines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <apr_strings.h>
+#include <apr_lib.h>
+#include <apr_xlate.h>
+#include <apr_atomic.h>
+
+#include "svn_hash.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_ctype.h"
+#include "svn_utf.h"
+#include "svn_private_config.h"
+#include "win32_xlate.h"
+
+#include "private/svn_utf_private.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_string_private.h"
+#include "private/svn_mutex.h"
+
+
+
+/* Use these static strings to maximize performance on standard conversions.
+ * Any strings on other locations are still valid, however.
+ */
+static const char *SVN_UTF_NTOU_XLATE_HANDLE = "svn-utf-ntou-xlate-handle";
+static const char *SVN_UTF_UTON_XLATE_HANDLE = "svn-utf-uton-xlate-handle";
+
+static const char *SVN_APR_UTF8_CHARSET = "UTF-8";
+
+static svn_mutex__t *xlate_handle_mutex = NULL;
+static svn_boolean_t assume_native_charset_is_utf8 = FALSE;
+
+/* The xlate handle cache is a global hash table with linked lists of xlate
+ * handles. In multi-threaded environments, a thread "borrows" an xlate
+ * handle from the cache during a translation and puts it back afterwards.
+ * This avoids holding a global lock for all translations.
+ * If there is no handle for a particular key when needed, a new is
+ * handle is created and put in the cache after use.
+ * This means that there will be at most N handles open for a key, where N
+ * is the number of simultanous handles in use for that key. */
+
+typedef struct xlate_handle_node_t {
+ apr_xlate_t *handle;
+ /* FALSE if the handle is not valid, since its pool is being
+ destroyed. */
+ svn_boolean_t valid;
+ /* The name of a char encoding or APR_LOCALE_CHARSET. */
+ const char *frompage, *topage;
+ struct xlate_handle_node_t *next;
+} xlate_handle_node_t;
+
+/* This maps const char * userdata_key strings to xlate_handle_node_t **
+ handles to the first entry in the linked list of xlate handles. We don't
+ store the pointer to the list head directly in the hash table, since we
+ remove/insert entries at the head in the list in the code below, and
+ we can't use apr_hash_set() in each character translation because that
+ function allocates memory in each call where the value is non-NULL.
+ Since these allocations take place in a global pool, this would be a
+ memory leak. */
+static apr_hash_t *xlate_handle_hash = NULL;
+
+/* "1st level cache" to standard conversion maps. We may access these
+ * using atomic xchange ops, i.e. without further thread synchronization.
+ * If the respective item is NULL, fallback to hash lookup.
+ */
+static void * volatile xlat_ntou_static_handle = NULL;
+static void * volatile xlat_uton_static_handle = NULL;
+
+/* Clean up the xlate handle cache. */
+static apr_status_t
+xlate_cleanup(void *arg)
+{
+ /* We set the cache variables to NULL so that translation works in other
+ cleanup functions, even if it isn't cached then. */
+ xlate_handle_hash = NULL;
+
+ /* ensure no stale objects get accessed */
+ xlat_ntou_static_handle = NULL;
+ xlat_uton_static_handle = NULL;
+
+ return APR_SUCCESS;
+}
+
+/* Set the handle of ARG to NULL. */
+static apr_status_t
+xlate_handle_node_cleanup(void *arg)
+{
+ xlate_handle_node_t *node = arg;
+
+ node->valid = FALSE;
+ return APR_SUCCESS;
+}
+
+void
+svn_utf_initialize2(svn_boolean_t assume_native_utf8,
+ apr_pool_t *pool)
+{
+ if (!xlate_handle_hash)
+ {
+ /* We create our own subpool, which we protect with the mutex.
+ We can't use the pool passed to us by the caller, since we will
+ use it for xlate handle allocations, possibly in multiple threads,
+ and pool allocation is not thread-safe. */
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_mutex__t *mutex;
+ svn_error_t *err = svn_mutex__init(&mutex, TRUE, subpool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return;
+ }
+
+ xlate_handle_mutex = mutex;
+ xlate_handle_hash = apr_hash_make(subpool);
+
+ apr_pool_cleanup_register(subpool, NULL, xlate_cleanup,
+ apr_pool_cleanup_null);
+ }
+
+ if (!assume_native_charset_is_utf8)
+ assume_native_charset_is_utf8 = assume_native_utf8;
+}
+
+/* Return a unique string key based on TOPAGE and FROMPAGE. TOPAGE and
+ * FROMPAGE can be any valid arguments of the same name to
+ * apr_xlate_open(). Allocate the returned string in POOL. */
+static const char*
+get_xlate_key(const char *topage,
+ const char *frompage,
+ apr_pool_t *pool)
+{
+ /* In the cases of SVN_APR_LOCALE_CHARSET and SVN_APR_DEFAULT_CHARSET
+ * topage/frompage is really an int, not a valid string. So generate a
+ * unique key accordingly. */
+ if (frompage == SVN_APR_LOCALE_CHARSET)
+ frompage = "APR_LOCALE_CHARSET";
+ else if (frompage == SVN_APR_DEFAULT_CHARSET)
+ frompage = "APR_DEFAULT_CHARSET";
+
+ if (topage == SVN_APR_LOCALE_CHARSET)
+ topage = "APR_LOCALE_CHARSET";
+ else if (topage == SVN_APR_DEFAULT_CHARSET)
+ topage = "APR_DEFAULT_CHARSET";
+
+ return apr_pstrcat(pool, "svn-utf-", frompage, "to", topage,
+ "-xlate-handle", (char *)NULL);
+}
+
+/* Atomically replace the content in *MEM with NEW_VALUE and return
+ * the previous content of *MEM. If atomicy cannot be guaranteed,
+ * *MEM will not be modified and NEW_VALUE is simply returned to
+ * the caller.
+ */
+static APR_INLINE void*
+atomic_swap(void * volatile * mem, void *new_value)
+{
+#if APR_HAS_THREADS
+#if APR_VERSION_AT_LEAST(1,3,0)
+ /* Cast is necessary because of APR bug:
+ https://issues.apache.org/bugzilla/show_bug.cgi?id=50731 */
+ return apr_atomic_xchgptr((volatile void **)mem, new_value);
+#else
+ /* old APRs don't support atomic swaps. Simply return the
+ * input to the caller for further proccessing. */
+ return new_value;
+#endif
+#else
+ /* no threads - no sync. necessary */
+ void *old_value = (void*)*mem;
+ *mem = new_value;
+ return old_value;
+#endif
+}
+
+/* Set *RET to a newly created handle node for converting from FROMPAGE
+ to TOPAGE, If apr_xlate_open() returns APR_EINVAL or APR_ENOTIMPL, set
+ (*RET)->handle to NULL. If fail for any other reason, return the error.
+ Allocate *RET and its xlate handle in POOL. */
+static svn_error_t *
+xlate_alloc_handle(xlate_handle_node_t **ret,
+ const char *topage, const char *frompage,
+ apr_pool_t *pool)
+{
+ apr_status_t apr_err;
+ apr_xlate_t *handle;
+
+ /* The error handling doesn't support the following cases, since we don't
+ use them currently. Catch this here. */
+ SVN_ERR_ASSERT(frompage != SVN_APR_DEFAULT_CHARSET
+ && topage != SVN_APR_DEFAULT_CHARSET
+ && (frompage != SVN_APR_LOCALE_CHARSET
+ || topage != SVN_APR_LOCALE_CHARSET));
+
+ /* Try to create a handle. */
+#if defined(WIN32)
+ apr_err = svn_subr__win32_xlate_open((win32_xlate_t **)&handle, topage,
+ frompage, pool);
+#else
+ apr_err = apr_xlate_open(&handle, topage, frompage, pool);
+#endif
+
+ if (APR_STATUS_IS_EINVAL(apr_err) || APR_STATUS_IS_ENOTIMPL(apr_err))
+ handle = NULL;
+ else if (apr_err != APR_SUCCESS)
+ {
+ const char *errstr;
+ /* Can't use svn_error_wrap_apr here because it calls functions in
+ this file, leading to infinite recursion. */
+ if (frompage == SVN_APR_LOCALE_CHARSET)
+ errstr = apr_psprintf(pool,
+ _("Can't create a character converter from "
+ "native encoding to '%s'"), topage);
+ else if (topage == SVN_APR_LOCALE_CHARSET)
+ errstr = apr_psprintf(pool,
+ _("Can't create a character converter from "
+ "'%s' to native encoding"), frompage);
+ else
+ errstr = apr_psprintf(pool,
+ _("Can't create a character converter from "
+ "'%s' to '%s'"), frompage, topage);
+
+ return svn_error_create(apr_err, NULL, errstr);
+ }
+
+ /* Allocate and initialize the node. */
+ *ret = apr_palloc(pool, sizeof(xlate_handle_node_t));
+ (*ret)->handle = handle;
+ (*ret)->valid = TRUE;
+ (*ret)->frompage = ((frompage != SVN_APR_LOCALE_CHARSET)
+ ? apr_pstrdup(pool, frompage) : frompage);
+ (*ret)->topage = ((topage != SVN_APR_LOCALE_CHARSET)
+ ? apr_pstrdup(pool, topage) : topage);
+ (*ret)->next = NULL;
+
+ /* If we are called from inside a pool cleanup handler, the just created
+ xlate handle will be closed when that handler returns by a newly
+ registered cleanup handler, however, the handle is still cached by us.
+ To prevent this, we register a cleanup handler that will reset the valid
+ flag of our node, so we don't use an invalid handle. */
+ if (handle)
+ apr_pool_cleanup_register(pool, *ret, xlate_handle_node_cleanup,
+ apr_pool_cleanup_null);
+
+ return SVN_NO_ERROR;
+}
+
+/* Extend xlate_alloc_handle by using USERDATA_KEY as a key in our
+ global hash map, if available.
+
+ Allocate *RET and its xlate handle in POOL if svn_utf_initialize()
+ hasn't been called or USERDATA_KEY is NULL. Else, allocate them
+ in the pool of xlate_handle_hash.
+
+ Note: this function is not thread-safe. Call get_xlate_handle_node
+ instead. */
+static svn_error_t *
+get_xlate_handle_node_internal(xlate_handle_node_t **ret,
+ const char *topage, const char *frompage,
+ const char *userdata_key, apr_pool_t *pool)
+{
+ /* If we already have a handle, just return it. */
+ if (userdata_key && xlate_handle_hash)
+ {
+ xlate_handle_node_t *old_node = NULL;
+
+ /* 2nd level: hash lookup */
+ xlate_handle_node_t **old_node_p = svn_hash_gets(xlate_handle_hash,
+ userdata_key);
+ if (old_node_p)
+ old_node = *old_node_p;
+ if (old_node)
+ {
+ /* Ensure that the handle is still valid. */
+ if (old_node->valid)
+ {
+ /* Remove from the list. */
+ *old_node_p = old_node->next;
+ old_node->next = NULL;
+ *ret = old_node;
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+
+ /* Note that we still have the mutex locked (if it is initialized), so we
+ can use the global pool for creating the new xlate handle. */
+
+ /* Use the correct pool for creating the handle. */
+ pool = apr_hash_pool_get(xlate_handle_hash);
+
+ return xlate_alloc_handle(ret, topage, frompage, pool);
+}
+
+/* Set *RET to a handle node for converting from FROMPAGE to TOPAGE,
+ creating the handle node if it doesn't exist in USERDATA_KEY.
+ If a node is not cached and apr_xlate_open() returns APR_EINVAL or
+ APR_ENOTIMPL, set (*RET)->handle to NULL. If fail for any other
+ reason, return the error.
+
+ Allocate *RET and its xlate handle in POOL if svn_utf_initialize()
+ hasn't been called or USERDATA_KEY is NULL. Else, allocate them
+ in the pool of xlate_handle_hash. */
+static svn_error_t *
+get_xlate_handle_node(xlate_handle_node_t **ret,
+ const char *topage, const char *frompage,
+ const char *userdata_key, apr_pool_t *pool)
+{
+ xlate_handle_node_t *old_node = NULL;
+
+ /* If we already have a handle, just return it. */
+ if (userdata_key)
+ {
+ if (xlate_handle_hash)
+ {
+ /* 1st level: global, static items */
+ if (userdata_key == SVN_UTF_NTOU_XLATE_HANDLE)
+ old_node = atomic_swap(&xlat_ntou_static_handle, NULL);
+ else if (userdata_key == SVN_UTF_UTON_XLATE_HANDLE)
+ old_node = atomic_swap(&xlat_uton_static_handle, NULL);
+
+ if (old_node && old_node->valid)
+ {
+ *ret = old_node;
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ {
+ void *p;
+ /* We fall back on a per-pool cache instead. */
+ apr_pool_userdata_get(&p, userdata_key, pool);
+ old_node = p;
+ /* Ensure that the handle is still valid. */
+ if (old_node && old_node->valid)
+ {
+ *ret = old_node;
+ return SVN_NO_ERROR;
+ }
+
+ return xlate_alloc_handle(ret, topage, frompage, pool);
+ }
+ }
+
+ SVN_MUTEX__WITH_LOCK(xlate_handle_mutex,
+ get_xlate_handle_node_internal(ret,
+ topage,
+ frompage,
+ userdata_key,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Put back NODE into the xlate handle cache for use by other calls.
+
+ Note: this function is not thread-safe. Call put_xlate_handle_node
+ instead. */
+static svn_error_t *
+put_xlate_handle_node_internal(xlate_handle_node_t *node,
+ const char *userdata_key)
+{
+ xlate_handle_node_t **node_p = svn_hash_gets(xlate_handle_hash, userdata_key);
+ if (node_p == NULL)
+ {
+ userdata_key = apr_pstrdup(apr_hash_pool_get(xlate_handle_hash),
+ userdata_key);
+ node_p = apr_palloc(apr_hash_pool_get(xlate_handle_hash),
+ sizeof(*node_p));
+ *node_p = NULL;
+ svn_hash_sets(xlate_handle_hash, userdata_key, node_p);
+ }
+ node->next = *node_p;
+ *node_p = node;
+
+ return SVN_NO_ERROR;
+}
+
+/* Put back NODE into the xlate handle cache for use by other calls.
+ If there is no global cache, store the handle in POOL.
+ Ignore errors related to locking/unlocking the mutex. */
+static svn_error_t *
+put_xlate_handle_node(xlate_handle_node_t *node,
+ const char *userdata_key,
+ apr_pool_t *pool)
+{
+ assert(node->next == NULL);
+ if (!userdata_key)
+ return SVN_NO_ERROR;
+
+ /* push previous global node to the hash */
+ if (xlate_handle_hash)
+ {
+ /* 1st level: global, static items */
+ if (userdata_key == SVN_UTF_NTOU_XLATE_HANDLE)
+ node = atomic_swap(&xlat_ntou_static_handle, node);
+ else if (userdata_key == SVN_UTF_UTON_XLATE_HANDLE)
+ node = atomic_swap(&xlat_uton_static_handle, node);
+ if (node == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_MUTEX__WITH_LOCK(xlate_handle_mutex,
+ put_xlate_handle_node_internal(node,
+ userdata_key));
+ }
+ else
+ {
+ /* Store it in the per-pool cache. */
+ apr_pool_userdata_set(node, userdata_key, apr_pool_cleanup_null, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the apr_xlate handle for converting native characters to UTF-8. */
+static svn_error_t *
+get_ntou_xlate_handle_node(xlate_handle_node_t **ret, apr_pool_t *pool)
+{
+ return get_xlate_handle_node(ret, SVN_APR_UTF8_CHARSET,
+ assume_native_charset_is_utf8
+ ? SVN_APR_UTF8_CHARSET
+ : SVN_APR_LOCALE_CHARSET,
+ SVN_UTF_NTOU_XLATE_HANDLE, pool);
+}
+
+
+/* Return the apr_xlate handle for converting UTF-8 to native characters.
+ Create one if it doesn't exist. If unable to find a handle, or
+ unable to create one because apr_xlate_open returned APR_EINVAL, then
+ set *RET to null and return SVN_NO_ERROR; if fail for some other
+ reason, return error. */
+static svn_error_t *
+get_uton_xlate_handle_node(xlate_handle_node_t **ret, apr_pool_t *pool)
+{
+ return get_xlate_handle_node(ret,
+ assume_native_charset_is_utf8
+ ? SVN_APR_UTF8_CHARSET
+ : SVN_APR_LOCALE_CHARSET,
+ SVN_APR_UTF8_CHARSET,
+ SVN_UTF_UTON_XLATE_HANDLE, pool);
+}
+
+
+/* Copy LEN bytes of SRC, converting non-ASCII and zero bytes to ?\nnn
+ sequences, allocating the result in POOL. */
+static const char *
+fuzzy_escape(const char *src, apr_size_t len, apr_pool_t *pool)
+{
+ const char *src_orig = src, *src_end = src + len;
+ apr_size_t new_len = 0;
+ char *new;
+ const char *new_orig;
+
+ /* First count how big a dest string we'll need. */
+ while (src < src_end)
+ {
+ if (! svn_ctype_isascii(*src) || *src == '\0')
+ new_len += 5; /* 5 slots, for "?\XXX" */
+ else
+ new_len += 1; /* one slot for the 7-bit char */
+
+ src++;
+ }
+
+ /* Allocate that amount, plus one slot for '\0' character. */
+ new = apr_palloc(pool, new_len + 1);
+
+ new_orig = new;
+
+ /* And fill it up. */
+ while (src_orig < src_end)
+ {
+ if (! svn_ctype_isascii(*src_orig) || src_orig == '\0')
+ {
+ /* This is the same format as svn_xml_fuzzy_escape uses, but that
+ function escapes different characters. Please keep in sync!
+ ### If we add another fuzzy escape somewhere, we should abstract
+ ### this out to a common function. */
+ apr_snprintf(new, 6, "?\\%03u", (unsigned char) *src_orig);
+ new += 5;
+ }
+ else
+ {
+ *new = *src_orig;
+ new += 1;
+ }
+
+ src_orig++;
+ }
+
+ *new = '\0';
+
+ return new_orig;
+}
+
+/* Convert SRC_LENGTH bytes of SRC_DATA in NODE->handle, store the result
+ in *DEST, which is allocated in POOL. */
+static svn_error_t *
+convert_to_stringbuf(xlate_handle_node_t *node,
+ const char *src_data,
+ apr_size_t src_length,
+ svn_stringbuf_t **dest,
+ apr_pool_t *pool)
+{
+#ifdef WIN32
+ apr_status_t apr_err;
+
+ apr_err = svn_subr__win32_xlate_to_stringbuf((win32_xlate_t *) node->handle,
+ src_data, src_length,
+ dest, pool);
+#else
+ apr_size_t buflen = src_length * 2;
+ apr_status_t apr_err;
+ apr_size_t srclen = src_length;
+ apr_size_t destlen = buflen;
+
+ /* Initialize *DEST to an empty stringbuf.
+ A 1:2 ratio of input bytes to output bytes (as assigned above)
+ should be enough for most translations, and if it turns out not
+ to be enough, we'll grow the buffer again, sizing it based on a
+ 1:3 ratio of the remainder of the string. */
+ *dest = svn_stringbuf_create_ensure(buflen + 1, pool);
+
+ /* Not only does it not make sense to convert an empty string, but
+ apr-iconv is quite unreasonable about not allowing that. */
+ if (src_length == 0)
+ return SVN_NO_ERROR;
+
+ do
+ {
+ /* Set up state variables for xlate. */
+ destlen = buflen - (*dest)->len;
+
+ /* Attempt the conversion. */
+ apr_err = apr_xlate_conv_buffer(node->handle,
+ src_data + (src_length - srclen),
+ &srclen,
+ (*dest)->data + (*dest)->len,
+ &destlen);
+
+ /* Now, update the *DEST->len to track the amount of output data
+ churned out so far from this loop. */
+ (*dest)->len += ((buflen - (*dest)->len) - destlen);
+ buflen += srclen * 3; /* 3 is middle ground, 2 wasn't enough
+ for all characters in the buffer, 4 is
+ maximum character size (currently) */
+
+
+ } while (apr_err == APR_SUCCESS && srclen != 0);
+#endif
+
+ /* If we exited the loop with an error, return the error. */
+ if (apr_err)
+ {
+ const char *errstr;
+ svn_error_t *err;
+
+ /* Can't use svn_error_wrap_apr here because it calls functions in
+ this file, leading to infinite recursion. */
+ if (node->frompage == SVN_APR_LOCALE_CHARSET)
+ errstr = apr_psprintf
+ (pool, _("Can't convert string from native encoding to '%s':"),
+ node->topage);
+ else if (node->topage == SVN_APR_LOCALE_CHARSET)
+ errstr = apr_psprintf
+ (pool, _("Can't convert string from '%s' to native encoding:"),
+ node->frompage);
+ else
+ errstr = apr_psprintf
+ (pool, _("Can't convert string from '%s' to '%s':"),
+ node->frompage, node->topage);
+
+ err = svn_error_create(apr_err, NULL, fuzzy_escape(src_data,
+ src_length, pool));
+ return svn_error_create(apr_err, err, errstr);
+ }
+ /* Else, exited due to success. Trim the result buffer down to the
+ right length. */
+ (*dest)->data[(*dest)->len] = '\0';
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return APR_EINVAL if the first LEN bytes of DATA contain anything
+ other than seven-bit, non-control (except for whitespace) ASCII
+ characters, finding the error pool from POOL. Otherwise, return
+ SVN_NO_ERROR. */
+static svn_error_t *
+check_non_ascii(const char *data, apr_size_t len, apr_pool_t *pool)
+{
+ const char *data_start = data;
+
+ for (; len > 0; --len, data++)
+ {
+ if ((! svn_ctype_isascii(*data))
+ || ((! svn_ctype_isspace(*data))
+ && svn_ctype_iscntrl(*data)))
+ {
+ /* Show the printable part of the data, followed by the
+ decimal code of the questionable character. Because if a
+ user ever gets this error, she's going to have to spend
+ time tracking down the non-ASCII data, so we want to help
+ as much as possible. And yes, we just call the unsafe
+ data "non-ASCII", even though the actual constraint is
+ somewhat more complex than that. */
+
+ if (data - data_start)
+ {
+ const char *error_data
+ = apr_pstrndup(pool, data_start, (data - data_start));
+
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Safe data '%s' was followed by non-ASCII byte %d: "
+ "unable to convert to/from UTF-8"),
+ error_data, *((const unsigned char *) data));
+ }
+ else
+ {
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Non-ASCII character (code %d) detected, "
+ "and unable to convert to/from UTF-8"),
+ *((const unsigned char *) data));
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Construct an error with code APR_EINVAL and with a suitable message
+ * to describe the invalid UTF-8 sequence DATA of length LEN (which
+ * may have embedded NULLs). We can't simply print the data, almost
+ * by definition we don't really know how it is encoded.
+ */
+static svn_error_t *
+invalid_utf8(const char *data, apr_size_t len, apr_pool_t *pool)
+{
+ const char *last = svn_utf__last_valid(data, len);
+ const char *valid_txt = "", *invalid_txt = "";
+ apr_size_t i;
+ size_t valid, invalid;
+
+ /* We will display at most 24 valid octets (this may split a leading
+ multi-byte character) as that should fit on one 80 character line. */
+ valid = last - data;
+ if (valid > 24)
+ valid = 24;
+ for (i = 0; i < valid; ++i)
+ valid_txt = apr_pstrcat(pool, valid_txt,
+ apr_psprintf(pool, " %02x",
+ (unsigned char)last[i-valid]),
+ (char *)NULL);
+
+ /* 4 invalid octets will guarantee that the faulty octet is displayed */
+ invalid = data + len - last;
+ if (invalid > 4)
+ invalid = 4;
+ for (i = 0; i < invalid; ++i)
+ invalid_txt = apr_pstrcat(pool, invalid_txt,
+ apr_psprintf(pool, " %02x",
+ (unsigned char)last[i]),
+ (char *)NULL);
+
+ return svn_error_createf(APR_EINVAL, NULL,
+ _("Valid UTF-8 data\n(hex:%s)\n"
+ "followed by invalid UTF-8 sequence\n(hex:%s)"),
+ valid_txt, invalid_txt);
+}
+
+/* Verify that the sequence DATA of length LEN is valid UTF-8.
+ If it is not, return an error with code APR_EINVAL. */
+static svn_error_t *
+check_utf8(const char *data, apr_size_t len, apr_pool_t *pool)
+{
+ if (! svn_utf__is_valid(data, len))
+ return invalid_utf8(data, len, pool);
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the NULL terminated sequence DATA is valid UTF-8.
+ If it is not, return an error with code APR_EINVAL. */
+static svn_error_t *
+check_cstring_utf8(const char *data, apr_pool_t *pool)
+{
+
+ if (! svn_utf__cstring_is_valid(data))
+ return invalid_utf8(data, strlen(data), pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_utf_stringbuf_to_utf8(svn_stringbuf_t **dest,
+ const svn_stringbuf_t *src,
+ apr_pool_t *pool)
+{
+ xlate_handle_node_t *node;
+ svn_error_t *err;
+
+ SVN_ERR(get_ntou_xlate_handle_node(&node, pool));
+
+ if (node->handle)
+ {
+ err = convert_to_stringbuf(node, src->data, src->len, dest, pool);
+ if (! err)
+ err = check_utf8((*dest)->data, (*dest)->len, pool);
+ }
+ else
+ {
+ err = check_non_ascii(src->data, src->len, pool);
+ if (! err)
+ *dest = svn_stringbuf_dup(src, pool);
+ }
+
+ return svn_error_compose_create(err,
+ put_xlate_handle_node
+ (node,
+ SVN_UTF_NTOU_XLATE_HANDLE,
+ pool));
+}
+
+
+svn_error_t *
+svn_utf_string_to_utf8(const svn_string_t **dest,
+ const svn_string_t *src,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *destbuf;
+ xlate_handle_node_t *node;
+ svn_error_t *err;
+
+ SVN_ERR(get_ntou_xlate_handle_node(&node, pool));
+
+ if (node->handle)
+ {
+ err = convert_to_stringbuf(node, src->data, src->len, &destbuf, pool);
+ if (! err)
+ err = check_utf8(destbuf->data, destbuf->len, pool);
+ if (! err)
+ *dest = svn_stringbuf__morph_into_string(destbuf);
+ }
+ else
+ {
+ err = check_non_ascii(src->data, src->len, pool);
+ if (! err)
+ *dest = svn_string_dup(src, pool);
+ }
+
+ return svn_error_compose_create(err,
+ put_xlate_handle_node
+ (node,
+ SVN_UTF_NTOU_XLATE_HANDLE,
+ pool));
+}
+
+
+/* Common implementation for svn_utf_cstring_to_utf8,
+ svn_utf_cstring_to_utf8_ex, svn_utf_cstring_from_utf8 and
+ svn_utf_cstring_from_utf8_ex. Convert SRC to DEST using NODE->handle as
+ the translator and allocating from POOL. */
+static svn_error_t *
+convert_cstring(const char **dest,
+ const char *src,
+ xlate_handle_node_t *node,
+ apr_pool_t *pool)
+{
+ if (node->handle)
+ {
+ svn_stringbuf_t *destbuf;
+ SVN_ERR(convert_to_stringbuf(node, src, strlen(src),
+ &destbuf, pool));
+ *dest = destbuf->data;
+ }
+ else
+ {
+ apr_size_t len = strlen(src);
+ SVN_ERR(check_non_ascii(src, len, pool));
+ *dest = apr_pstrmemdup(pool, src, len);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_utf_cstring_to_utf8(const char **dest,
+ const char *src,
+ apr_pool_t *pool)
+{
+ xlate_handle_node_t *node;
+ svn_error_t *err;
+
+ SVN_ERR(get_ntou_xlate_handle_node(&node, pool));
+ err = convert_cstring(dest, src, node, pool);
+ SVN_ERR(svn_error_compose_create(err,
+ put_xlate_handle_node
+ (node,
+ SVN_UTF_NTOU_XLATE_HANDLE,
+ pool)));
+ return check_cstring_utf8(*dest, pool);
+}
+
+
+svn_error_t *
+svn_utf_cstring_to_utf8_ex2(const char **dest,
+ const char *src,
+ const char *frompage,
+ apr_pool_t *pool)
+{
+ xlate_handle_node_t *node;
+ svn_error_t *err;
+ const char *convset_key = get_xlate_key(SVN_APR_UTF8_CHARSET, frompage,
+ pool);
+
+ SVN_ERR(get_xlate_handle_node(&node, SVN_APR_UTF8_CHARSET, frompage,
+ convset_key, pool));
+ err = convert_cstring(dest, src, node, pool);
+ SVN_ERR(svn_error_compose_create(err,
+ put_xlate_handle_node
+ (node,
+ SVN_UTF_NTOU_XLATE_HANDLE,
+ pool)));
+
+ return check_cstring_utf8(*dest, pool);
+}
+
+
+svn_error_t *
+svn_utf_cstring_to_utf8_ex(const char **dest,
+ const char *src,
+ const char *frompage,
+ const char *convset_key,
+ apr_pool_t *pool)
+{
+ return svn_utf_cstring_to_utf8_ex2(dest, src, frompage, pool);
+}
+
+
+svn_error_t *
+svn_utf_stringbuf_from_utf8(svn_stringbuf_t **dest,
+ const svn_stringbuf_t *src,
+ apr_pool_t *pool)
+{
+ xlate_handle_node_t *node;
+ svn_error_t *err;
+
+ SVN_ERR(get_uton_xlate_handle_node(&node, pool));
+
+ if (node->handle)
+ {
+ err = check_utf8(src->data, src->len, pool);
+ if (! err)
+ err = convert_to_stringbuf(node, src->data, src->len, dest, pool);
+ }
+ else
+ {
+ err = check_non_ascii(src->data, src->len, pool);
+ if (! err)
+ *dest = svn_stringbuf_dup(src, pool);
+ }
+
+ err = svn_error_compose_create(
+ err,
+ put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool));
+
+ return err;
+}
+
+
+svn_error_t *
+svn_utf_string_from_utf8(const svn_string_t **dest,
+ const svn_string_t *src,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *dbuf;
+ xlate_handle_node_t *node;
+ svn_error_t *err;
+
+ SVN_ERR(get_uton_xlate_handle_node(&node, pool));
+
+ if (node->handle)
+ {
+ err = check_utf8(src->data, src->len, pool);
+ if (! err)
+ err = convert_to_stringbuf(node, src->data, src->len,
+ &dbuf, pool);
+ if (! err)
+ *dest = svn_stringbuf__morph_into_string(dbuf);
+ }
+ else
+ {
+ err = check_non_ascii(src->data, src->len, pool);
+ if (! err)
+ *dest = svn_string_dup(src, pool);
+ }
+
+ err = svn_error_compose_create(
+ err,
+ put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool));
+
+ return err;
+}
+
+
+svn_error_t *
+svn_utf_cstring_from_utf8(const char **dest,
+ const char *src,
+ apr_pool_t *pool)
+{
+ xlate_handle_node_t *node;
+ svn_error_t *err;
+
+ SVN_ERR(check_cstring_utf8(src, pool));
+
+ SVN_ERR(get_uton_xlate_handle_node(&node, pool));
+ err = convert_cstring(dest, src, node, pool);
+ err = svn_error_compose_create(
+ err,
+ put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool));
+
+ return err;
+}
+
+
+svn_error_t *
+svn_utf_cstring_from_utf8_ex2(const char **dest,
+ const char *src,
+ const char *topage,
+ apr_pool_t *pool)
+{
+ xlate_handle_node_t *node;
+ svn_error_t *err;
+ const char *convset_key = get_xlate_key(topage, SVN_APR_UTF8_CHARSET,
+ pool);
+
+ SVN_ERR(check_cstring_utf8(src, pool));
+
+ SVN_ERR(get_xlate_handle_node(&node, topage, SVN_APR_UTF8_CHARSET,
+ convset_key, pool));
+ err = convert_cstring(dest, src, node, pool);
+ err = svn_error_compose_create(
+ err,
+ put_xlate_handle_node(node, convset_key, pool));
+
+ return err;
+}
+
+
+svn_error_t *
+svn_utf_cstring_from_utf8_ex(const char **dest,
+ const char *src,
+ const char *topage,
+ const char *convset_key,
+ apr_pool_t *pool)
+{
+ return svn_utf_cstring_from_utf8_ex2(dest, src, topage, pool);
+}
+
+
+const char *
+svn_utf__cstring_from_utf8_fuzzy(const char *src,
+ apr_pool_t *pool,
+ svn_error_t *(*convert_from_utf8)
+ (const char **, const char *, apr_pool_t *))
+{
+ const char *escaped, *converted;
+ svn_error_t *err;
+
+ escaped = fuzzy_escape(src, strlen(src), pool);
+
+ /* Okay, now we have a *new* UTF-8 string, one that's guaranteed to
+ contain only 7-bit bytes :-). Recode to native... */
+ err = convert_from_utf8(((const char **) &converted), escaped, pool);
+
+ if (err)
+ {
+ svn_error_clear(err);
+ return escaped;
+ }
+ else
+ return converted;
+
+ /* ### Check the client locale, maybe we can avoid that second
+ * conversion! See Ulrich Drepper's patch at
+ * http://subversion.tigris.org/issues/show_bug.cgi?id=807.
+ */
+}
+
+
+const char *
+svn_utf_cstring_from_utf8_fuzzy(const char *src,
+ apr_pool_t *pool)
+{
+ return svn_utf__cstring_from_utf8_fuzzy(src, pool,
+ svn_utf_cstring_from_utf8);
+}
+
+
+svn_error_t *
+svn_utf_cstring_from_utf8_stringbuf(const char **dest,
+ const svn_stringbuf_t *src,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *destbuf;
+
+ SVN_ERR(svn_utf_stringbuf_from_utf8(&destbuf, src, pool));
+ *dest = destbuf->data;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_utf_cstring_from_utf8_string(const char **dest,
+ const svn_string_t *src,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *dbuf;
+ xlate_handle_node_t *node;
+ svn_error_t *err;
+
+ SVN_ERR(get_uton_xlate_handle_node(&node, pool));
+
+ if (node->handle)
+ {
+ err = check_utf8(src->data, src->len, pool);
+ if (! err)
+ err = convert_to_stringbuf(node, src->data, src->len,
+ &dbuf, pool);
+ if (! err)
+ *dest = dbuf->data;
+ }
+ else
+ {
+ err = check_non_ascii(src->data, src->len, pool);
+ if (! err)
+ *dest = apr_pstrmemdup(pool, src->data, src->len);
+ }
+
+ err = svn_error_compose_create(
+ err,
+ put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool));
+
+ return err;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/utf_validate.c b/contrib/subversion/subversion/libsvn_subr/utf_validate.c
new file mode 100644
index 0000000..8311fd7
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/utf_validate.c
@@ -0,0 +1,485 @@
+/*
+ * utf_validate.c: Validate a UTF-8 string
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* Validate a UTF-8 string according to the rules in
+ *
+ * Table 3-6. Well-Formed UTF-8 Bytes Sequences
+ *
+ * in
+ *
+ * The Unicode Standard, Version 4.0
+ *
+ * which is available at
+ *
+ * http://www.unicode.org/
+ *
+ * UTF-8 was originally defined in RFC-2279, Unicode's "well-formed UTF-8"
+ * is a subset of that enconding. The Unicode enconding prohibits things
+ * like non-shortest encodings (some characters can be represented by more
+ * than one multi-byte encoding) and the encodings for the surrogate code
+ * points. RFC-3629 superceeds RFC-2279 and adopts the same well-formed
+ * rules as Unicode. This is the ABNF in RFC-3629 that describes
+ * well-formed UTF-8 rules:
+ *
+ * UTF8-octets = *( UTF8-char )
+ * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
+ * UTF8-1 = %x00-7F
+ * UTF8-2 = %xC2-DF UTF8-tail
+ * UTF8-3 = %xE0 %xA0-BF UTF8-tail /
+ * %xE1-EC 2( UTF8-tail ) /
+ * %xED %x80-9F UTF8-tail /
+ * %xEE-EF 2( UTF8-tail )
+ * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) /
+ * %xF1-F3 3( UTF8-tail ) /
+ * %xF4 %x80-8F 2( UTF8-tail )
+ * UTF8-tail = %x80-BF
+ *
+ */
+
+#include "private/svn_utf_private.h"
+#include "private/svn_eol_private.h"
+#include "private/svn_dep_compat.h"
+
+/* Lookup table to categorise each octet in the string. */
+static const char octet_category[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00-0x7f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80-0x8f */
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x90-0x9f */
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xa0-0xbf */
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, /* 0xc0-0xc1 */
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, /* 0xc2-0xdf */
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, /* 0xe0 */
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, /* 0xe1-0xec */
+ 8, /* 0xed */
+ 9, 9, /* 0xee-0xef */
+ 10, /* 0xf0 */
+ 11, 11, 11, /* 0xf1-0xf3 */
+ 12, /* 0xf4 */
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 /* 0xf5-0xff */
+};
+
+/* Machine states */
+#define FSM_START 0
+#define FSM_80BF 1
+#define FSM_A0BF 2
+#define FSM_80BF80BF 3
+#define FSM_809F 4
+#define FSM_90BF 5
+#define FSM_80BF80BF80BF 6
+#define FSM_808F 7
+#define FSM_ERROR 8
+
+/* In the FSM it appears that categories 0xc0-0xc1 and 0xf5-0xff make the
+ same transitions, as do categories 0xe1-0xec and 0xee-0xef. I wonder if
+ there is any great benefit in combining categories? It would reduce the
+ memory footprint of the transition table by 16 bytes, but might it be
+ harder to understand? */
+
+/* Machine transition table */
+static const char machine [9][14] = {
+ /* FSM_START */
+ {FSM_START, /* 0x00-0x7f */
+ FSM_ERROR, /* 0x80-0x8f */
+ FSM_ERROR, /* 0x90-0x9f */
+ FSM_ERROR, /* 0xa0-0xbf */
+ FSM_ERROR, /* 0xc0-0xc1 */
+ FSM_80BF, /* 0xc2-0xdf */
+ FSM_A0BF, /* 0xe0 */
+ FSM_80BF80BF, /* 0xe1-0xec */
+ FSM_809F, /* 0xed */
+ FSM_80BF80BF, /* 0xee-0xef */
+ FSM_90BF, /* 0xf0 */
+ FSM_80BF80BF80BF, /* 0xf1-0xf3 */
+ FSM_808F, /* 0xf4 */
+ FSM_ERROR}, /* 0xf5-0xff */
+
+ /* FSM_80BF */
+ {FSM_ERROR, /* 0x00-0x7f */
+ FSM_START, /* 0x80-0x8f */
+ FSM_START, /* 0x90-0x9f */
+ FSM_START, /* 0xa0-0xbf */
+ FSM_ERROR, /* 0xc0-0xc1 */
+ FSM_ERROR, /* 0xc2-0xdf */
+ FSM_ERROR, /* 0xe0 */
+ FSM_ERROR, /* 0xe1-0xec */
+ FSM_ERROR, /* 0xed */
+ FSM_ERROR, /* 0xee-0xef */
+ FSM_ERROR, /* 0xf0 */
+ FSM_ERROR, /* 0xf1-0xf3 */
+ FSM_ERROR, /* 0xf4 */
+ FSM_ERROR}, /* 0xf5-0xff */
+
+ /* FSM_A0BF */
+ {FSM_ERROR, /* 0x00-0x7f */
+ FSM_ERROR, /* 0x80-0x8f */
+ FSM_ERROR, /* 0x90-0x9f */
+ FSM_80BF, /* 0xa0-0xbf */
+ FSM_ERROR, /* 0xc0-0xc1 */
+ FSM_ERROR, /* 0xc2-0xdf */
+ FSM_ERROR, /* 0xe0 */
+ FSM_ERROR, /* 0xe1-0xec */
+ FSM_ERROR, /* 0xed */
+ FSM_ERROR, /* 0xee-0xef */
+ FSM_ERROR, /* 0xf0 */
+ FSM_ERROR, /* 0xf1-0xf3 */
+ FSM_ERROR, /* 0xf4 */
+ FSM_ERROR}, /* 0xf5-0xff */
+
+ /* FSM_80BF80BF */
+ {FSM_ERROR, /* 0x00-0x7f */
+ FSM_80BF, /* 0x80-0x8f */
+ FSM_80BF, /* 0x90-0x9f */
+ FSM_80BF, /* 0xa0-0xbf */
+ FSM_ERROR, /* 0xc0-0xc1 */
+ FSM_ERROR, /* 0xc2-0xdf */
+ FSM_ERROR, /* 0xe0 */
+ FSM_ERROR, /* 0xe1-0xec */
+ FSM_ERROR, /* 0xed */
+ FSM_ERROR, /* 0xee-0xef */
+ FSM_ERROR, /* 0xf0 */
+ FSM_ERROR, /* 0xf1-0xf3 */
+ FSM_ERROR, /* 0xf4 */
+ FSM_ERROR}, /* 0xf5-0xff */
+
+ /* FSM_809F */
+ {FSM_ERROR, /* 0x00-0x7f */
+ FSM_80BF, /* 0x80-0x8f */
+ FSM_80BF, /* 0x90-0x9f */
+ FSM_ERROR, /* 0xa0-0xbf */
+ FSM_ERROR, /* 0xc0-0xc1 */
+ FSM_ERROR, /* 0xc2-0xdf */
+ FSM_ERROR, /* 0xe0 */
+ FSM_ERROR, /* 0xe1-0xec */
+ FSM_ERROR, /* 0xed */
+ FSM_ERROR, /* 0xee-0xef */
+ FSM_ERROR, /* 0xf0 */
+ FSM_ERROR, /* 0xf1-0xf3 */
+ FSM_ERROR, /* 0xf4 */
+ FSM_ERROR}, /* 0xf5-0xff */
+
+ /* FSM_90BF */
+ {FSM_ERROR, /* 0x00-0x7f */
+ FSM_ERROR, /* 0x80-0x8f */
+ FSM_80BF80BF, /* 0x90-0x9f */
+ FSM_80BF80BF, /* 0xa0-0xbf */
+ FSM_ERROR, /* 0xc0-0xc1 */
+ FSM_ERROR, /* 0xc2-0xdf */
+ FSM_ERROR, /* 0xe0 */
+ FSM_ERROR, /* 0xe1-0xec */
+ FSM_ERROR, /* 0xed */
+ FSM_ERROR, /* 0xee-0xef */
+ FSM_ERROR, /* 0xf0 */
+ FSM_ERROR, /* 0xf1-0xf3 */
+ FSM_ERROR, /* 0xf4 */
+ FSM_ERROR}, /* 0xf5-0xff */
+
+ /* FSM_80BF80BF80BF */
+ {FSM_ERROR, /* 0x00-0x7f */
+ FSM_80BF80BF, /* 0x80-0x8f */
+ FSM_80BF80BF, /* 0x90-0x9f */
+ FSM_80BF80BF, /* 0xa0-0xbf */
+ FSM_ERROR, /* 0xc0-0xc1 */
+ FSM_ERROR, /* 0xc2-0xdf */
+ FSM_ERROR, /* 0xe0 */
+ FSM_ERROR, /* 0xe1-0xec */
+ FSM_ERROR, /* 0xed */
+ FSM_ERROR, /* 0xee-0xef */
+ FSM_ERROR, /* 0xf0 */
+ FSM_ERROR, /* 0xf1-0xf3 */
+ FSM_ERROR, /* 0xf4 */
+ FSM_ERROR}, /* 0xf5-0xff */
+
+ /* FSM_808F */
+ {FSM_ERROR, /* 0x00-0x7f */
+ FSM_80BF80BF, /* 0x80-0x8f */
+ FSM_ERROR, /* 0x90-0x9f */
+ FSM_ERROR, /* 0xa0-0xbf */
+ FSM_ERROR, /* 0xc0-0xc1 */
+ FSM_ERROR, /* 0xc2-0xdf */
+ FSM_ERROR, /* 0xe0 */
+ FSM_ERROR, /* 0xe1-0xec */
+ FSM_ERROR, /* 0xed */
+ FSM_ERROR, /* 0xee-0xef */
+ FSM_ERROR, /* 0xf0 */
+ FSM_ERROR, /* 0xf1-0xf3 */
+ FSM_ERROR, /* 0xf4 */
+ FSM_ERROR}, /* 0xf5-0xff */
+
+ /* FSM_ERROR */
+ {FSM_ERROR, /* 0x00-0x7f */
+ FSM_ERROR, /* 0x80-0x8f */
+ FSM_ERROR, /* 0x90-0x9f */
+ FSM_ERROR, /* 0xa0-0xbf */
+ FSM_ERROR, /* 0xc0-0xc1 */
+ FSM_ERROR, /* 0xc2-0xdf */
+ FSM_ERROR, /* 0xe0 */
+ FSM_ERROR, /* 0xe1-0xec */
+ FSM_ERROR, /* 0xed */
+ FSM_ERROR, /* 0xee-0xef */
+ FSM_ERROR, /* 0xf0 */
+ FSM_ERROR, /* 0xf1-0xf3 */
+ FSM_ERROR, /* 0xf4 */
+ FSM_ERROR}, /* 0xf5-0xff */
+};
+
+/* Scan MAX_LEN bytes in *DATA for chars that are not in the octet
+ * category 0 (FSM_START). Return the position of the first such char
+ * or DATA + MAX_LEN if all were cat 0.
+ */
+static const char *
+first_non_fsm_start_char(const char *data, apr_size_t max_len)
+{
+#if !SVN_UNALIGNED_ACCESS_IS_OK
+
+ /* On some systems, we need to make sure that buf is properly aligned
+ * for chunky data access.
+ */
+ if ((apr_uintptr_t)data & (sizeof(apr_uintptr_t)-1))
+ {
+ apr_size_t len = (~(apr_uintptr_t)data) & (sizeof(apr_uintptr_t)-1);
+ if (len > max_len)
+ len = max_len;
+ max_len -= len;
+
+ for (; len > 0; ++data, --len)
+ if (*data < 0 || *data >= 0x80)
+ return data;
+ }
+
+#endif
+
+ /* Scan the input one machine word at a time. */
+ for (; max_len > sizeof(apr_uintptr_t)
+ ; data += sizeof(apr_uintptr_t), max_len -= sizeof(apr_uintptr_t))
+ if (*(const apr_uintptr_t *)data & SVN__BIT_7_SET)
+ break;
+
+ /* The remaining odd bytes will be examined the naive way: */
+ for (; max_len > 0; ++data, --max_len)
+ if (*data < 0 || *data >= 0x80)
+ break;
+
+ return data;
+}
+
+/* Scan the C string in *DATA for chars that are not in the octet
+ * category 0 (FSM_START). Return the position of either the such
+ * char or of the terminating NUL.
+ */
+static const char *
+first_non_fsm_start_char_cstring(const char *data)
+{
+ /* We need to make sure that BUF is properly aligned for chunky data
+ * access because we don't know the string's length. Unaligned chunk
+ * read access beyond the NUL terminator could therefore result in a
+ * segfault.
+ */
+ for (; (apr_uintptr_t)data & (sizeof(apr_uintptr_t)-1); ++data)
+ if (*data <= 0 || *data >= 0x80)
+ return data;
+
+ /* Scan the input one machine word at a time. */
+#ifndef SVN_UTF_NO_UNINITIALISED_ACCESS
+ /* This may read allocated but initialised bytes beyond the
+ terminating null. Any such bytes are always readable and this
+ code operates correctly whatever the uninitialised values happen
+ to be. However memory checking tools such as valgrind and GCC
+ 4.8's address santitizer will object so this bit of code can be
+ disabled at compile time. */
+ for (; ; data += sizeof(apr_uintptr_t))
+ {
+ /* Check for non-ASCII chars: */
+ apr_uintptr_t chunk = *(const apr_uintptr_t *)data;
+ if (chunk & SVN__BIT_7_SET)
+ break;
+
+ /* This is the well-known strlen test: */
+ chunk |= (chunk & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET;
+ if ((chunk & SVN__BIT_7_SET) != SVN__BIT_7_SET)
+ break;
+ }
+#endif
+
+ /* The remaining odd bytes will be examined the naive way: */
+ for (; ; ++data)
+ if (*data <= 0 || *data >= 0x80)
+ break;
+
+ return data;
+}
+
+const char *
+svn_utf__last_valid(const char *data, apr_size_t len)
+{
+ const char *start = first_non_fsm_start_char(data, len);
+ const char *end = data + len;
+ int state = FSM_START;
+
+ data = start;
+ while (data < end)
+ {
+ unsigned char octet = *data++;
+ int category = octet_category[octet];
+ state = machine[state][category];
+ if (state == FSM_START)
+ start = data;
+ }
+ return start;
+}
+
+svn_boolean_t
+svn_utf__cstring_is_valid(const char *data)
+{
+ int state = FSM_START;
+
+ if (!data)
+ return FALSE;
+
+ data = first_non_fsm_start_char_cstring(data);
+
+ while (*data)
+ {
+ unsigned char octet = *data++;
+ int category = octet_category[octet];
+ state = machine[state][category];
+ }
+ return state == FSM_START;
+}
+
+svn_boolean_t
+svn_utf__is_valid(const char *data, apr_size_t len)
+{
+ const char *end = data + len;
+ int state = FSM_START;
+
+ if (!data)
+ return FALSE;
+
+ data = first_non_fsm_start_char(data, len);
+
+ while (data < end)
+ {
+ unsigned char octet = *data++;
+ int category = octet_category[octet];
+ state = machine[state][category];
+ }
+ return state == FSM_START;
+}
+
+const char *
+svn_utf__last_valid2(const char *data, apr_size_t len)
+{
+ const char *start = first_non_fsm_start_char(data, len);
+ const char *end = data + len;
+ int state = FSM_START;
+
+ data = start;
+ while (data < end)
+ {
+ unsigned char octet = *data++;
+ switch (state)
+ {
+ case FSM_START:
+ if (octet <= 0x7F)
+ break;
+ else if (octet <= 0xC1)
+ state = FSM_ERROR;
+ else if (octet <= 0xDF)
+ state = FSM_80BF;
+ else if (octet == 0xE0)
+ state = FSM_A0BF;
+ else if (octet <= 0xEC)
+ state = FSM_80BF80BF;
+ else if (octet == 0xED)
+ state = FSM_809F;
+ else if (octet <= 0xEF)
+ state = FSM_80BF80BF;
+ else if (octet == 0xF0)
+ state = FSM_90BF;
+ else if (octet <= 0xF3)
+ state = FSM_80BF80BF80BF;
+ else if (octet <= 0xF4)
+ state = FSM_808F;
+ else
+ state = FSM_ERROR;
+ break;
+ case FSM_80BF:
+ if (octet >= 0x80 && octet <= 0xBF)
+ state = FSM_START;
+ else
+ state = FSM_ERROR;
+ break;
+ case FSM_A0BF:
+ if (octet >= 0xA0 && octet <= 0xBF)
+ state = FSM_80BF;
+ else
+ state = FSM_ERROR;
+ break;
+ case FSM_80BF80BF:
+ if (octet >= 0x80 && octet <= 0xBF)
+ state = FSM_80BF;
+ else
+ state = FSM_ERROR;
+ break;
+ case FSM_809F:
+ if (octet >= 0x80 && octet <= 0x9F)
+ state = FSM_80BF;
+ else
+ state = FSM_ERROR;
+ break;
+ case FSM_90BF:
+ if (octet >= 0x90 && octet <= 0xBF)
+ state = FSM_80BF80BF;
+ else
+ state = FSM_ERROR;
+ break;
+ case FSM_80BF80BF80BF:
+ if (octet >= 0x80 && octet <= 0xBF)
+ state = FSM_80BF80BF;
+ else
+ state = FSM_ERROR;
+ break;
+ case FSM_808F:
+ if (octet >= 0x80 && octet <= 0x8F)
+ state = FSM_80BF80BF;
+ else
+ state = FSM_ERROR;
+ break;
+ default:
+ case FSM_ERROR:
+ return start;
+ }
+ if (state == FSM_START)
+ start = data;
+ }
+ return start;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/utf_width.c b/contrib/subversion/subversion/libsvn_subr/utf_width.c
new file mode 100644
index 0000000..3adff4c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/utf_width.c
@@ -0,0 +1,283 @@
+/*
+ * This is an implementation of wcwidth() and wcswidth() (defined in
+ * IEEE Std 1002.1-2001) for Unicode.
+ *
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
+ *
+ * In fixed-width output devices, Latin characters all occupy a single
+ * "cell" position of equal width, whereas ideographic CJK characters
+ * occupy two such cells. Interoperability between terminal-line
+ * applications and (teletype-style) character terminals using the
+ * UTF-8 encoding requires agreement on which character should advance
+ * the cursor by how many cell positions. No established formal
+ * standards exist at present on which Unicode character shall occupy
+ * how many cell positions on character terminals. These routines are
+ * a first attempt of defining such behavior based on simple rules
+ * applied to data provided by the Unicode Consortium.
+ *
+ * For some graphical characters, the Unicode standard explicitly
+ * defines a character-cell width via the definition of the East Asian
+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
+ * In all these cases, there is no ambiguity about which width a
+ * terminal shall use. For characters in the East Asian Ambiguous (A)
+ * class, the width choice depends purely on a preference of backward
+ * compatibility with either historic CJK or Western practice.
+ * Choosing single-width for these characters is easy to justify as
+ * the appropriate long-term solution, as the CJK practice of
+ * displaying these characters as double-width comes from historic
+ * implementation simplicity (8-bit encoded characters were displayed
+ * single-width and 16-bit ones double-width, even for Greek,
+ * Cyrillic, etc.) and not any typographic considerations.
+ *
+ * Much less clear is the choice of width for the Not East Asian
+ * (Neutral) class. Existing practice does not dictate a width for any
+ * of these characters. It would nevertheless make sense
+ * typographically to allocate two character cells to characters such
+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
+ * represented adequately with a single-width glyph. The following
+ * routines at present merely assign a single-cell width to all
+ * neutral characters, in the interest of simplicity. This is not
+ * entirely satisfactory and should be reconsidered before
+ * establishing a formal standard in this area. At the moment, the
+ * decision which Not East Asian (Neutral) characters should be
+ * represented by double-width glyphs cannot yet be answered by
+ * applying a simple rule from the Unicode database content. Setting
+ * up a proper standard for the behavior of UTF-8 character terminals
+ * will require a careful analysis not only of each Unicode character,
+ * but also of each presentation form, something the author of these
+ * routines has avoided to do so far.
+ *
+ * http://www.unicode.org/unicode/reports/tr11/
+ *
+ * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ *
+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ */
+
+#include <apr_lib.h>
+
+#include "svn_utf.h"
+#include "private/svn_utf_private.h"
+
+#include "svn_private_config.h"
+
+struct interval {
+ apr_uint32_t first;
+ apr_uint32_t last;
+};
+
+/* auxiliary function for binary search in interval table */
+static int
+bisearch(apr_uint32_t ucs, const struct interval *table, apr_uint32_t max)
+{
+ apr_uint32_t min = 0;
+ apr_uint32_t mid;
+
+ if (ucs < table[0].first || ucs > table[max].last)
+ return 0;
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if (ucs > table[mid].last)
+ min = mid + 1;
+ else if (ucs < table[mid].first)
+ max = mid - 1; /* this is safe because ucs >= table[0].first */
+ else
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ * - Other format characters (general category code Cf in the Unicode
+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ * have a column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * Full-width (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2.
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+static int
+mk_wcwidth(apr_uint32_t ucs)
+{
+ /* sorted list of non-overlapping intervals of non-spacing characters */
+ /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
+ static const struct interval combining[] = {
+ { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
+ { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+ { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
+ { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
+ { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
+ { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
+ { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
+ { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
+ { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
+ { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
+ { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
+ { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
+ { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
+ { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
+ { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
+ { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
+ { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
+ { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
+ { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
+ { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
+ { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
+ { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
+ { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
+ { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
+ { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
+ { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
+ { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
+ { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
+ { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
+ { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
+ { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
+ { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
+ { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
+ { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
+ { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
+ { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
+ { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
+ { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
+ { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
+ { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
+ { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
+ { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
+ { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
+ { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
+ { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
+ { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
+ { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
+ { 0xE0100, 0xE01EF }
+ };
+
+ /* test for 8-bit control characters */
+ if (ucs == 0)
+ return 0;
+ if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
+ return -1;
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, combining,
+ sizeof(combining) / sizeof(struct interval) - 1))
+ return 0;
+
+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
+
+ return 1 +
+ (ucs >= 0x1100 &&
+ (ucs <= 0x115f || /* Hangul Jamo init. consonants */
+ ucs == 0x2329 || ucs == 0x232a ||
+ (ucs >= 0x2e80 && ucs <= 0xa4cf &&
+ ucs != 0x303f) || /* CJK ... Yi */
+ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
+ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
+ (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
+ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
+ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
+ (ucs >= 0xffe0 && ucs <= 0xffe6) ||
+ (ucs >= 0x20000 && ucs <= 0x2fffd) ||
+ (ucs >= 0x30000 && ucs <= 0x3fffd)));
+}
+
+int
+svn_utf_cstring_utf8_width(const char *cstr)
+{
+ int width = 0;
+
+ if (*cstr == '\0')
+ return 0;
+
+ /* Ensure the conversion below doesn't fail because of encoding errors. */
+ if (!svn_utf__cstring_is_valid(cstr))
+ return -1;
+
+ /* Convert the UTF-8 string to UTF-32 (UCS4) which is the format
+ * mk_wcwidth() expects, and get the width of each character.
+ * We don't need much error checking since the input is valid UTF-8. */
+ while (*cstr)
+ {
+ apr_uint32_t ucs;
+ int nbytes;
+ int lead_mask;
+ int w;
+ int i;
+
+ if ((*cstr & 0x80) == 0)
+ {
+ nbytes = 1;
+ lead_mask = 0x7f;
+ }
+ else if ((*cstr & 0xe0) == 0xc0)
+ {
+ nbytes = 2;
+ lead_mask = 0x1f;
+ }
+ else if ((*cstr & 0xf0) == 0xe0)
+ {
+ nbytes = 3;
+ lead_mask = 0x0f;
+ }
+ else if ((*cstr & 0xf8) == 0xf0)
+ {
+ nbytes = 4;
+ lead_mask = 0x07;
+ }
+ else
+ {
+ /* RFC 3629 restricts UTF-8 to max 4 bytes per character. */
+ return -1;
+ }
+
+ /* Parse character data from leading byte. */
+ ucs = (apr_uint32_t)(*cstr & lead_mask);
+
+ /* Parse character data from continuation bytes. */
+ for (i = 1; i < nbytes; i++)
+ {
+ ucs <<= 6;
+ ucs |= (cstr[i] & 0x3f);
+ }
+
+ cstr += nbytes;
+
+ /* Determine the width of this character and add it to the total. */
+ w = mk_wcwidth(ucs);
+ if (w == -1)
+ return -1;
+ width += w;
+ }
+
+ return width;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/validate.c b/contrib/subversion/subversion/libsvn_subr/validate.c
new file mode 100644
index 0000000..2052339
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/validate.c
@@ -0,0 +1,102 @@
+/*
+ * validate.c: validation routines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include "svn_error.h"
+#include "svn_ctype.h"
+#include "svn_private_config.h"
+
+
+
+/*** Code. ***/
+
+svn_error_t *
+svn_mime_type_validate(const char *mime_type, apr_pool_t *pool)
+{
+ /* Since svn:mime-type can actually contain a full content type
+ specification, e.g., "text/html; charset=UTF-8", make sure we're
+ only looking at the media type here. */
+ const apr_size_t len = strcspn(mime_type, "; ");
+ const apr_size_t len2 = strlen(mime_type);
+ const char *const slash_pos = strchr(mime_type, '/');
+ apr_size_t i;
+ const char *tspecials = "()<>@,;:\\\"/[]?=";
+
+ if (len == 0)
+ return svn_error_createf
+ (SVN_ERR_BAD_MIME_TYPE, NULL,
+ _("MIME type '%s' has empty media type"), mime_type);
+
+ if (slash_pos == NULL || slash_pos >= &mime_type[len])
+ return svn_error_createf
+ (SVN_ERR_BAD_MIME_TYPE, NULL,
+ _("MIME type '%s' does not contain '/'"), mime_type);
+
+ /* Check the mime type for illegal characters. See RFC 1521. */
+ for (i = 0; i < len; i++)
+ {
+ if (&mime_type[i] != slash_pos
+ && (! svn_ctype_isascii(mime_type[i])
+ || svn_ctype_iscntrl(mime_type[i])
+ || svn_ctype_isspace(mime_type[i])
+ || (strchr(tspecials, mime_type[i]) != NULL)))
+ return svn_error_createf
+ (SVN_ERR_BAD_MIME_TYPE, NULL,
+ _("MIME type '%s' contains invalid character '%c' "
+ "in media type"),
+ mime_type, mime_type[i]);
+ }
+
+ /* Check the whole string for unsafe characters. (issue #2872) */
+ for (i = 0; i < len2; i++)
+ {
+ if (svn_ctype_iscntrl(mime_type[i]) && mime_type[i] != '\t')
+ return svn_error_createf(
+ SVN_ERR_BAD_MIME_TYPE, NULL,
+ _("MIME type '%s' contains invalid character '0x%02x' "
+ "in postfix"),
+ mime_type, mime_type[i]);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_boolean_t
+svn_mime_type_is_binary(const char *mime_type)
+{
+ /* See comment in svn_mime_type_validate() above. */
+ const apr_size_t len = strcspn(mime_type, "; ");
+ return ((strncmp(mime_type, "text/", 5) != 0)
+ && (len != 15 || strncmp(mime_type, "image/x-xbitmap", len) != 0)
+ && (len != 15 || strncmp(mime_type, "image/x-xpixmap", len) != 0)
+ );
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/version.c b/contrib/subversion/subversion/libsvn_subr/version.c
new file mode 100644
index 0000000..ad2a270
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/version.c
@@ -0,0 +1,291 @@
+/*
+ * version.c: library version number and utilities
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_error.h"
+#include "svn_version.h"
+
+#include "sysinfo.h"
+#include "svn_private_config.h"
+#include "private/svn_subr_private.h"
+
+const svn_version_t *
+svn_subr_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+
+svn_boolean_t svn_ver_compatible(const svn_version_t *my_version,
+ const svn_version_t *lib_version)
+{
+ /* With normal development builds the matching rules are strict, to
+ avoid inadvertantly using the wrong libraries. For backward
+ compatibility testing use --disable-full-version-match to
+ configure 1.7 and then the libraries that get built can be used
+ to replace those in 1.6 or earlier builds. */
+
+#ifndef SVN_DISABLE_FULL_VERSION_MATCH
+ if (lib_version->tag[0] != '\0')
+ /* Development library; require exact match. */
+ return svn_ver_equal(my_version, lib_version);
+ else if (my_version->tag[0] != '\0')
+ /* Development client; must be newer than the library
+ and have the same major and minor version. */
+ return (my_version->major == lib_version->major
+ && my_version->minor == lib_version->minor
+ && my_version->patch > lib_version->patch);
+#endif
+
+ /* General compatibility rules for released versions. */
+ return (my_version->major == lib_version->major
+ && my_version->minor <= lib_version->minor);
+}
+
+
+svn_boolean_t svn_ver_equal(const svn_version_t *my_version,
+ const svn_version_t *lib_version)
+{
+ return (my_version->major == lib_version->major
+ && my_version->minor == lib_version->minor
+ && my_version->patch == lib_version->patch
+ && 0 == strcmp(my_version->tag, lib_version->tag));
+}
+
+
+svn_error_t *
+svn_ver_check_list(const svn_version_t *my_version,
+ const svn_version_checklist_t *checklist)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ int i;
+
+ for (i = 0; checklist[i].label != NULL; ++i)
+ {
+ const svn_version_t *lib_version = checklist[i].version_query();
+ if (!svn_ver_compatible(my_version, lib_version))
+ err = svn_error_createf(SVN_ERR_VERSION_MISMATCH, err,
+ _("Version mismatch in '%s':"
+ " found %d.%d.%d%s,"
+ " expected %d.%d.%d%s"),
+ checklist[i].label,
+ lib_version->major, lib_version->minor,
+ lib_version->patch, lib_version->tag,
+ my_version->major, my_version->minor,
+ my_version->patch, my_version->tag);
+ }
+
+ return err;
+}
+
+
+struct svn_version_extended_t
+{
+ const char *build_date; /* Compilation date */
+ const char *build_time; /* Compilation time */
+ const char *build_host; /* Build canonical host name */
+ const char *copyright; /* Copyright notice (localized) */
+ const char *runtime_host; /* Runtime canonical host name */
+ const char *runtime_osname; /* Running OS release name */
+
+ /* Array of svn_version_ext_linked_lib_t describing dependent
+ libraries. */
+ const apr_array_header_t *linked_libs;
+
+ /* Array of svn_version_ext_loaded_lib_t describing loaded shared
+ libraries. */
+ const apr_array_header_t *loaded_libs;
+};
+
+
+const svn_version_extended_t *
+svn_version_extended(svn_boolean_t verbose,
+ apr_pool_t *pool)
+{
+ svn_version_extended_t *info = apr_pcalloc(pool, sizeof(*info));
+
+ info->build_date = __DATE__;
+ info->build_time = __TIME__;
+ info->build_host = SVN_BUILD_HOST;
+ info->copyright = apr_pstrdup
+ (pool, _("Copyright (C) 2013 The Apache Software Foundation.\n"
+ "This software consists of contributions made by many people;\n"
+ "see the NOTICE file for more information.\n"
+ "Subversion is open source software, see "
+ "http://subversion.apache.org/\n"));
+
+ if (verbose)
+ {
+ info->runtime_host = svn_sysinfo__canonical_host(pool);
+ info->runtime_osname = svn_sysinfo__release_name(pool);
+ info->linked_libs = svn_sysinfo__linked_libs(pool);
+ info->loaded_libs = svn_sysinfo__loaded_libs(pool);
+ }
+
+ return info;
+}
+
+
+const char *
+svn_version_ext_build_date(const svn_version_extended_t *ext_info)
+{
+ return ext_info->build_date;
+}
+
+const char *
+svn_version_ext_build_time(const svn_version_extended_t *ext_info)
+{
+ return ext_info->build_time;
+}
+
+const char *
+svn_version_ext_build_host(const svn_version_extended_t *ext_info)
+{
+ return ext_info->build_host;
+}
+
+const char *
+svn_version_ext_copyright(const svn_version_extended_t *ext_info)
+{
+ return ext_info->copyright;
+}
+
+const char *
+svn_version_ext_runtime_host(const svn_version_extended_t *ext_info)
+{
+ return ext_info->runtime_host;
+}
+
+const char *
+svn_version_ext_runtime_osname(const svn_version_extended_t *ext_info)
+{
+ return ext_info->runtime_osname;
+}
+
+const apr_array_header_t *
+svn_version_ext_linked_libs(const svn_version_extended_t *ext_info)
+{
+ return ext_info->linked_libs;
+}
+
+const apr_array_header_t *
+svn_version_ext_loaded_libs(const svn_version_extended_t *ext_info)
+{
+ return ext_info->loaded_libs;
+}
+
+svn_error_t *
+svn_version__parse_version_string(svn_version_t **version_p,
+ const char *version_string,
+ apr_pool_t *result_pool)
+{
+ svn_error_t *err;
+ svn_version_t *version;
+ apr_array_header_t *pieces =
+ svn_cstring_split(version_string, ".", FALSE, result_pool);
+
+ if ((pieces->nelts < 2) || (pieces->nelts > 3))
+ return svn_error_createf(SVN_ERR_MALFORMED_VERSION_STRING, NULL,
+ _("Failed to parse version number string '%s'"),
+ version_string);
+
+ version = apr_pcalloc(result_pool, sizeof(*version));
+ version->tag = "";
+
+ /* Parse the major and minor integers strictly. */
+ err = svn_cstring_atoi(&(version->major),
+ APR_ARRAY_IDX(pieces, 0, const char *));
+ if (err)
+ return svn_error_createf(SVN_ERR_MALFORMED_VERSION_STRING, err,
+ _("Failed to parse version number string '%s'"),
+ version_string);
+ err = svn_cstring_atoi(&(version->minor),
+ APR_ARRAY_IDX(pieces, 1, const char *));
+ if (err)
+ return svn_error_createf(SVN_ERR_MALFORMED_VERSION_STRING, err,
+ _("Failed to parse version number string '%s'"),
+ version_string);
+
+ /* If there's a third component, we'll parse it, too. But we don't
+ require that it be present. */
+ if (pieces->nelts == 3)
+ {
+ const char *piece = APR_ARRAY_IDX(pieces, 2, const char *);
+ char *hyphen = strchr(piece, '-');
+ if (hyphen)
+ {
+ version->tag = apr_pstrdup(result_pool, hyphen + 1);
+ *hyphen = '\0';
+ }
+ err = svn_cstring_atoi(&(version->patch), piece);
+ if (err)
+ return svn_error_createf(SVN_ERR_MALFORMED_VERSION_STRING, err,
+ _("Failed to parse version number string '%s'"
+ ),
+ version_string);
+ }
+
+ if (version->major < 0 || version->minor < 0 || version->patch < 0)
+ return svn_error_createf(SVN_ERR_MALFORMED_VERSION_STRING, err,
+ _("Failed to parse version number string '%s'"),
+ version_string);
+
+ *version_p = version;
+ return SVN_NO_ERROR;
+}
+
+
+svn_boolean_t
+svn_version__at_least(svn_version_t *version,
+ int major,
+ int minor,
+ int patch)
+{
+ /* Compare major versions. */
+ if (version->major < major)
+ return FALSE;
+ if (version->major > major)
+ return TRUE;
+
+ /* Major versions are the same. Compare minor versions. */
+ if (version->minor < minor)
+ return FALSE;
+ if (version->minor > minor)
+ return TRUE;
+
+ /* Major and minor versions are the same. Compare patch
+ versions. */
+ if (version->patch < patch)
+ return FALSE;
+ if (version->patch > patch)
+ return TRUE;
+
+ /* Major, minor, and patch versions are identical matches. But tags
+ in our schema are always used for versions not yet quite at the
+ given patch level. */
+ if (version->tag && version->tag[0])
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/contrib/subversion/subversion/libsvn_subr/win32_crashrpt.c b/contrib/subversion/subversion/libsvn_subr/win32_crashrpt.c
new file mode 100644
index 0000000..6becc96
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/win32_crashrpt.c
@@ -0,0 +1,805 @@
+/*
+ * win32_crashrpt.c : provides information after a crash
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* prevent "empty compilation unit" warning on e.g. UNIX */
+typedef int win32_crashrpt__dummy;
+
+#ifdef WIN32
+#ifdef SVN_USE_WIN32_CRASHHANDLER
+
+/*** Includes. ***/
+#include <apr.h>
+#include <dbghelp.h>
+#include <direct.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "svn_version.h"
+
+#include "win32_crashrpt.h"
+#include "win32_crashrpt_dll.h"
+
+/*** Global variables ***/
+HANDLE dbghelp_dll = INVALID_HANDLE_VALUE;
+
+/* Email address where the crash reports should be sent too. */
+#define CRASHREPORT_EMAIL "users@subversion.apache.org"
+
+#define DBGHELP_DLL "dbghelp.dll"
+
+#define LOGFILE_PREFIX "svn-crash-log"
+
+#if defined(_M_IX86)
+#define FORMAT_PTR "0x%08x"
+#elif defined(_M_X64)
+#define FORMAT_PTR "0x%016I64x"
+#endif
+
+/*** Code. ***/
+
+/* Convert a wide-character string to utf-8. This function will create a buffer
+ * large enough to hold the result string, the caller should free this buffer.
+ * If the string can't be converted, NULL is returned.
+ */
+static char *
+convert_wbcs_to_utf8(const wchar_t *str)
+{
+ size_t len = wcslen(str);
+ char *utf8_str = malloc(sizeof(wchar_t) * len + 1);
+ len = wcstombs(utf8_str, str, len);
+
+ if (len == -1)
+ return NULL;
+
+ utf8_str[len] = '\0';
+
+ return utf8_str;
+}
+
+/* Convert the exception code to a string */
+static const char *
+exception_string(int exception)
+{
+#define EXCEPTION(x) case EXCEPTION_##x: return (#x);
+
+ switch (exception)
+ {
+ EXCEPTION(ACCESS_VIOLATION)
+ EXCEPTION(DATATYPE_MISALIGNMENT)
+ EXCEPTION(BREAKPOINT)
+ EXCEPTION(SINGLE_STEP)
+ EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
+ EXCEPTION(FLT_DENORMAL_OPERAND)
+ EXCEPTION(FLT_DIVIDE_BY_ZERO)
+ EXCEPTION(FLT_INEXACT_RESULT)
+ EXCEPTION(FLT_INVALID_OPERATION)
+ EXCEPTION(FLT_OVERFLOW)
+ EXCEPTION(FLT_STACK_CHECK)
+ EXCEPTION(FLT_UNDERFLOW)
+ EXCEPTION(INT_DIVIDE_BY_ZERO)
+ EXCEPTION(INT_OVERFLOW)
+ EXCEPTION(PRIV_INSTRUCTION)
+ EXCEPTION(IN_PAGE_ERROR)
+ EXCEPTION(ILLEGAL_INSTRUCTION)
+ EXCEPTION(NONCONTINUABLE_EXCEPTION)
+ EXCEPTION(STACK_OVERFLOW)
+ EXCEPTION(INVALID_DISPOSITION)
+ EXCEPTION(GUARD_PAGE)
+ EXCEPTION(INVALID_HANDLE)
+
+ default:
+ return "UNKNOWN_ERROR";
+ }
+#undef EXCEPTION
+}
+
+/* Write the minidump to file. The callback function will at the same time
+ write the list of modules to the log file. */
+static BOOL
+write_minidump_file(const char *file, PEXCEPTION_POINTERS ptrs,
+ MINIDUMP_CALLBACK_ROUTINE module_callback,
+ void *data)
+{
+ /* open minidump file */
+ HANDLE minidump_file = CreateFile(file, GENERIC_WRITE, 0, NULL,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+
+ if (minidump_file != INVALID_HANDLE_VALUE)
+ {
+ MINIDUMP_EXCEPTION_INFORMATION expt_info;
+ MINIDUMP_CALLBACK_INFORMATION dump_cb_info;
+
+ expt_info.ThreadId = GetCurrentThreadId();
+ expt_info.ExceptionPointers = ptrs;
+ expt_info.ClientPointers = FALSE;
+
+ dump_cb_info.CallbackRoutine = module_callback;
+ dump_cb_info.CallbackParam = data;
+
+ MiniDumpWriteDump_(GetCurrentProcess(),
+ GetCurrentProcessId(),
+ minidump_file,
+ MiniDumpNormal,
+ ptrs ? &expt_info : NULL,
+ NULL,
+ &dump_cb_info);
+
+ CloseHandle(minidump_file);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Write module information to the log file */
+static BOOL CALLBACK
+write_module_info_callback(void *data,
+ CONST PMINIDUMP_CALLBACK_INPUT callback_input,
+ PMINIDUMP_CALLBACK_OUTPUT callback_output)
+{
+ if (data != NULL &&
+ callback_input != NULL &&
+ callback_input->CallbackType == ModuleCallback)
+ {
+ FILE *log_file = (FILE *)data;
+ MINIDUMP_MODULE_CALLBACK module = callback_input->Module;
+
+ char *buf = convert_wbcs_to_utf8(module.FullPath);
+ fprintf(log_file, FORMAT_PTR, module.BaseOfImage);
+ fprintf(log_file, " %s", buf);
+ free(buf);
+
+ fprintf(log_file, " (%d.%d.%d.%d, %d bytes)\n",
+ HIWORD(module.VersionInfo.dwFileVersionMS),
+ LOWORD(module.VersionInfo.dwFileVersionMS),
+ HIWORD(module.VersionInfo.dwFileVersionLS),
+ LOWORD(module.VersionInfo.dwFileVersionLS),
+ module.SizeOfImage);
+ }
+
+ return TRUE;
+}
+
+/* Write details about the current process, platform and the exception */
+static void
+write_process_info(EXCEPTION_RECORD *exception, CONTEXT *context,
+ FILE *log_file)
+{
+ OSVERSIONINFO oi;
+ const char *cmd_line;
+ char workingdir[8192];
+
+ /* write the command line */
+ cmd_line = GetCommandLine();
+ fprintf(log_file,
+ "Cmd line: %s\n", cmd_line);
+
+ _getcwd(workingdir, sizeof(workingdir));
+ fprintf(log_file,
+ "Working Dir: %s\n", workingdir);
+
+ /* write the svn version number info. */
+ fprintf(log_file,
+ "Version: %s, compiled %s, %s\n",
+ SVN_VERSION, __DATE__, __TIME__);
+
+ /* write information about the OS */
+ oi.dwOSVersionInfoSize = sizeof(oi);
+ GetVersionEx(&oi);
+
+ fprintf(log_file,
+ "Platform: Windows OS version %d.%d build %d %s\n\n",
+ oi.dwMajorVersion, oi.dwMinorVersion, oi.dwBuildNumber,
+ oi.szCSDVersion);
+
+ /* write the exception code */
+ fprintf(log_file,
+ "Exception: %s\n\n",
+ exception_string(exception->ExceptionCode));
+
+ /* write the register info. */
+ fprintf(log_file,
+ "Registers:\n");
+#if defined(_M_IX86)
+ fprintf(log_file,
+ "eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\n",
+ context->Eax, context->Ebx, context->Ecx,
+ context->Edx, context->Esi, context->Edi);
+ fprintf(log_file,
+ "eip=%08x esp=%08x ebp=%08x efl=%08x\n",
+ context->Eip, context->Esp,
+ context->Ebp, context->EFlags);
+ fprintf(log_file,
+ "cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x\n",
+ context->SegCs, context->SegSs, context->SegDs,
+ context->SegEs, context->SegFs, context->SegGs);
+#elif defined(_M_X64)
+ fprintf(log_file,
+ "Rax=%016I64x Rcx=%016I64x Rdx=%016I64x Rbx=%016I64x\n",
+ context->Rax, context->Rcx, context->Rdx, context->Rbx);
+ fprintf(log_file,
+ "Rsp=%016I64x Rbp=%016I64x Rsi=%016I64x Rdi=%016I64x\n",
+ context->Rsp, context->Rbp, context->Rsi, context->Rdi);
+ fprintf(log_file,
+ "R8= %016I64x R9= %016I64x R10= %016I64x R11=%016I64x\n",
+ context->R8, context->R9, context->R10, context->R11);
+ fprintf(log_file,
+ "R12=%016I64x R13=%016I64x R14=%016I64x R15=%016I64x\n",
+ context->R12, context->R13, context->R14, context->R15);
+
+ fprintf(log_file,
+ "cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x ss=%04x\n",
+ context->SegCs, context->SegDs, context->SegEs,
+ context->SegFs, context->SegGs, context->SegSs);
+#else
+#error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER
+#endif
+}
+
+/* Formats the value at address based on the specified basic type
+ * (char, int, long ...). */
+static void
+format_basic_type(char *buf, DWORD basic_type, DWORD64 length, void *address)
+{
+ switch(length)
+ {
+ case 1:
+ sprintf(buf, "0x%02x", (int)*(unsigned char *)address);
+ break;
+ case 2:
+ sprintf(buf, "0x%04x", (int)*(unsigned short *)address);
+ break;
+ case 4:
+ switch(basic_type)
+ {
+ case 2: /* btChar */
+ {
+ if (!IsBadStringPtr(*(PSTR*)address, 32))
+ sprintf(buf, "\"%.31s\"", *(const char **)address);
+ else
+ sprintf(buf, FORMAT_PTR, *(DWORD_PTR *)address);
+ }
+ case 6: /* btInt */
+ sprintf(buf, "%d", *(int *)address);
+ break;
+ case 8: /* btFloat */
+ sprintf(buf, "%f", *(float *)address);
+ break;
+ default:
+ sprintf(buf, FORMAT_PTR, *(DWORD_PTR *)address);
+ break;
+ }
+ break;
+ case 8:
+ if (basic_type == 8) /* btFloat */
+ sprintf(buf, "%lf", *(double *)address);
+ else
+ sprintf(buf, "0x%016I64X", *(unsigned __int64 *)address);
+ break;
+ default:
+ sprintf(buf, "[unhandled type 0x%08x of length " FORMAT_PTR "]",
+ basic_type, length);
+ break;
+ }
+}
+
+/* Formats the value at address based on the type (pointer, user defined,
+ * basic type). */
+static void
+format_value(char *value_str, DWORD64 mod_base, DWORD type, void *value_addr)
+{
+ DWORD tag = 0;
+ int ptr = 0;
+ HANDLE proc = GetCurrentProcess();
+
+ while (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMTAG, &tag))
+ {
+ /* SymTagPointerType */
+ if (tag == 14)
+ {
+ ptr++;
+ SymGetTypeInfo_(proc, mod_base, type, TI_GET_TYPE, &type);
+ continue;
+ }
+ break;
+ }
+
+ switch(tag)
+ {
+ case 11: /* SymTagUDT */
+ {
+ WCHAR *type_name_wbcs;
+ if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMNAME,
+ &type_name_wbcs))
+ {
+ char *type_name = convert_wbcs_to_utf8(type_name_wbcs);
+ LocalFree(type_name_wbcs);
+
+ if (ptr == 0)
+ sprintf(value_str, "(%s) " FORMAT_PTR,
+ type_name, (DWORD_PTR *)value_addr);
+ else if (ptr == 1)
+ sprintf(value_str, "(%s *) " FORMAT_PTR,
+ type_name, *(DWORD_PTR *)value_addr);
+ else
+ sprintf(value_str, "(%s **) " FORMAT_PTR,
+ type_name, *(DWORD_PTR *)value_addr);
+
+ free(type_name);
+ }
+ else
+ sprintf(value_str, "[no symbol tag]");
+ }
+ break;
+ case 16: /* SymTagBaseType */
+ {
+ DWORD bt;
+ ULONG64 length;
+ SymGetTypeInfo_(proc, mod_base, type, TI_GET_LENGTH, &length);
+
+ /* print a char * as a string */
+ if (ptr == 1 && length == 1)
+ {
+ sprintf(value_str, FORMAT_PTR " \"%s\"",
+ *(DWORD_PTR *)value_addr, *(const char **)value_addr);
+ }
+ else if (ptr >= 1)
+ {
+ sprintf(value_str, FORMAT_PTR, *(DWORD_PTR *)value_addr);
+ }
+ else if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_BASETYPE, &bt))
+ {
+ format_basic_type(value_str, bt, length, value_addr);
+ }
+ }
+ break;
+ case 12: /* SymTagEnum */
+ sprintf(value_str, "%d", *(DWORD_PTR *)value_addr);
+ break;
+ case 13: /* SymTagFunctionType */
+ sprintf(value_str, FORMAT_PTR, *(DWORD_PTR *)value_addr);
+ break;
+ default:
+ sprintf(value_str, "[unhandled tag: %d]", tag);
+ break;
+ }
+}
+
+/* Internal structure used to pass some data to the enumerate symbols
+ * callback */
+typedef struct symbols_baton_t {
+ STACKFRAME64 *stack_frame;
+ FILE *log_file;
+ int nr_of_frame;
+ BOOL log_params;
+} symbols_baton_t;
+
+/* Write the details of one parameter or local variable to the log file */
+static BOOL WINAPI
+write_var_values(PSYMBOL_INFO sym_info, ULONG sym_size, void *baton)
+{
+ static int last_nr_of_frame = 0;
+ DWORD_PTR var_data = 0; /* Will point to the variable's data in memory */
+ STACKFRAME64 *stack_frame = ((symbols_baton_t*)baton)->stack_frame;
+ FILE *log_file = ((symbols_baton_t*)baton)->log_file;
+ int nr_of_frame = ((symbols_baton_t*)baton)->nr_of_frame;
+ BOOL log_params = ((symbols_baton_t*)baton)->log_params;
+ char value_str[256] = "";
+
+ /* get the variable's data */
+ if (sym_info->Flags & SYMFLAG_REGREL)
+ {
+ var_data = (DWORD_PTR)stack_frame->AddrFrame.Offset;
+ var_data += (DWORD_PTR)sym_info->Address;
+ }
+ else
+ return FALSE;
+
+ if (log_params && sym_info->Flags & SYMFLAG_PARAMETER)
+ {
+ if (last_nr_of_frame == nr_of_frame)
+ fprintf(log_file, ", ", 2);
+ else
+ last_nr_of_frame = nr_of_frame;
+
+ format_value(value_str, sym_info->ModBase, sym_info->TypeIndex,
+ (void *)var_data);
+ fprintf(log_file, "%s=%s", sym_info->Name, value_str);
+ }
+ if (!log_params && sym_info->Flags & SYMFLAG_LOCAL)
+ {
+ format_value(value_str, sym_info->ModBase, sym_info->TypeIndex,
+ (void *)var_data);
+ fprintf(log_file, " %s = %s\n", sym_info->Name, value_str);
+ }
+
+ return TRUE;
+}
+
+/* Write the details of one function to the log file */
+static void
+write_function_detail(STACKFRAME64 stack_frame, int nr_of_frame, FILE *log_file)
+{
+ ULONG64 symbolBuffer[(sizeof(SYMBOL_INFO) +
+ MAX_SYM_NAME +
+ sizeof(ULONG64) - 1) /
+ sizeof(ULONG64)];
+ PSYMBOL_INFO pIHS = (PSYMBOL_INFO)symbolBuffer;
+ DWORD64 func_disp=0;
+
+ IMAGEHLP_STACK_FRAME ih_stack_frame;
+ IMAGEHLP_LINE64 ih_line;
+ DWORD line_disp=0;
+
+ HANDLE proc = GetCurrentProcess();
+
+ symbols_baton_t ensym;
+
+ nr_of_frame++; /* We need a 1 based index here */
+
+ /* log the function name */
+ pIHS->SizeOfStruct = sizeof(SYMBOL_INFO);
+ pIHS->MaxNameLen = MAX_SYM_NAME;
+ if (SymFromAddr_(proc, stack_frame.AddrPC.Offset, &func_disp, pIHS))
+ {
+ fprintf(log_file,
+ "#%d 0x%08I64x in %.200s(",
+ nr_of_frame, stack_frame.AddrPC.Offset, pIHS->Name);
+
+ /* restrict symbol enumeration to this frame only */
+ ih_stack_frame.InstructionOffset = stack_frame.AddrPC.Offset;
+ SymSetContext_(proc, &ih_stack_frame, 0);
+
+ ensym.log_file = log_file;
+ ensym.stack_frame = &stack_frame;
+ ensym.nr_of_frame = nr_of_frame;
+
+ /* log all function parameters */
+ ensym.log_params = TRUE;
+ SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym);
+
+ fprintf(log_file, ")");
+ }
+ else
+ {
+ fprintf(log_file,
+ "#%d 0x%08I64x in (unknown function)",
+ nr_of_frame, stack_frame.AddrPC.Offset);
+ }
+
+ /* find the source line for this function. */
+ ih_line.SizeOfStruct = sizeof(IMAGEHLP_LINE);
+ if (SymGetLineFromAddr64_(proc, stack_frame.AddrPC.Offset,
+ &line_disp, &ih_line) != 0)
+ {
+ fprintf(log_file,
+ " at %s:%d\n", ih_line.FileName, ih_line.LineNumber);
+ }
+ else
+ {
+ fprintf(log_file, "\n");
+ }
+
+ /* log all function local variables */
+ ensym.log_params = FALSE;
+ SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym);
+}
+
+/* Walk over the stack and log all relevant information to the log file */
+static void
+write_stacktrace(CONTEXT *context, FILE *log_file)
+{
+#if defined (_M_IX86) || defined(_M_X64) || defined(_M_IA64)
+ HANDLE proc = GetCurrentProcess();
+ STACKFRAME64 stack_frame;
+ DWORD machine;
+ CONTEXT ctx;
+ int skip = 0, i = 0;
+
+ /* The thread information - if not supplied. */
+ if (context == NULL)
+ {
+ /* If no context is supplied, skip 1 frame */
+ skip = 1;
+
+ ctx.ContextFlags = CONTEXT_FULL;
+ if (!GetThreadContext(GetCurrentThread(), &ctx))
+ return;
+ }
+ else
+ {
+ ctx = *context;
+ }
+
+ if (context == NULL)
+ return;
+
+ /* Write the stack trace */
+ ZeroMemory(&stack_frame, sizeof(STACKFRAME64));
+ stack_frame.AddrPC.Mode = AddrModeFlat;
+ stack_frame.AddrStack.Mode = AddrModeFlat;
+ stack_frame.AddrFrame.Mode = AddrModeFlat;
+
+#if defined(_M_IX86)
+ machine = IMAGE_FILE_MACHINE_I386;
+ stack_frame.AddrPC.Offset = context->Eip;
+ stack_frame.AddrStack.Offset = context->Esp;
+ stack_frame.AddrFrame.Offset = context->Ebp;
+#elif defined(_M_X64)
+ machine = IMAGE_FILE_MACHINE_AMD64;
+ stack_frame.AddrPC.Offset = context->Rip;
+ stack_frame.AddrStack.Offset = context->Rsp;
+ stack_frame.AddrFrame.Offset = context->Rbp;
+#elif defined(_M_IA64)
+ machine = IMAGE_FILE_MACHINE_IA64;
+ stack_frame.AddrPC.Offset = context->StIIP;
+ stack_frame.AddrStack.Offset = context->SP;
+ stack_frame.AddrBStore.Mode = AddrModeFlat;
+ stack_frame.AddrBStore.Offset = context->RsBSP;
+#else
+#error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER
+#endif
+
+ while (1)
+ {
+ if (! StackWalk64_(machine, proc, GetCurrentThread(),
+ &stack_frame, &ctx, NULL,
+ SymFunctionTableAccess64_, SymGetModuleBase64_, NULL))
+ {
+ break;
+ }
+
+ if (i >= skip)
+ {
+ /* Try to include symbolic information.
+ Also check that the address is not zero. Sometimes StackWalk
+ returns TRUE with a frame of zero. */
+ if (stack_frame.AddrPC.Offset != 0)
+ {
+ write_function_detail(stack_frame, i, log_file);
+ }
+ }
+ i++;
+ }
+#else
+#error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER
+#endif
+}
+
+/* Check if a debugger is attached to this process */
+static BOOL
+is_debugger_present()
+{
+ HANDLE kernel32_dll = LoadLibrary("kernel32.dll");
+ BOOL result;
+
+ ISDEBUGGERPRESENT IsDebuggerPresent_ =
+ (ISDEBUGGERPRESENT)GetProcAddress(kernel32_dll, "IsDebuggerPresent");
+
+ if (IsDebuggerPresent_ && IsDebuggerPresent_())
+ result = TRUE;
+ else
+ result = FALSE;
+
+ FreeLibrary(kernel32_dll);
+
+ return result;
+}
+
+/* Load the dbghelp.dll file, try to find a version that matches our
+ requirements. */
+static BOOL
+load_dbghelp_dll()
+{
+ dbghelp_dll = LoadLibrary(DBGHELP_DLL);
+ if (dbghelp_dll != INVALID_HANDLE_VALUE)
+ {
+ DWORD opts;
+
+ /* load the functions */
+ MiniDumpWriteDump_ =
+ (MINIDUMPWRITEDUMP)GetProcAddress(dbghelp_dll, "MiniDumpWriteDump");
+ SymInitialize_ =
+ (SYMINITIALIZE)GetProcAddress(dbghelp_dll, "SymInitialize");
+ SymSetOptions_ =
+ (SYMSETOPTIONS)GetProcAddress(dbghelp_dll, "SymSetOptions");
+ SymGetOptions_ =
+ (SYMGETOPTIONS)GetProcAddress(dbghelp_dll, "SymGetOptions");
+ SymCleanup_ =
+ (SYMCLEANUP)GetProcAddress(dbghelp_dll, "SymCleanup");
+ SymGetTypeInfo_ =
+ (SYMGETTYPEINFO)GetProcAddress(dbghelp_dll, "SymGetTypeInfo");
+ SymGetLineFromAddr64_ =
+ (SYMGETLINEFROMADDR64)GetProcAddress(dbghelp_dll,
+ "SymGetLineFromAddr64");
+ SymEnumSymbols_ =
+ (SYMENUMSYMBOLS)GetProcAddress(dbghelp_dll, "SymEnumSymbols");
+ SymSetContext_ =
+ (SYMSETCONTEXT)GetProcAddress(dbghelp_dll, "SymSetContext");
+ SymFromAddr_ = (SYMFROMADDR)GetProcAddress(dbghelp_dll, "SymFromAddr");
+ StackWalk64_ = (STACKWALK64)GetProcAddress(dbghelp_dll, "StackWalk64");
+ SymFunctionTableAccess64_ =
+ (SYMFUNCTIONTABLEACCESS64)GetProcAddress(dbghelp_dll,
+ "SymFunctionTableAccess64");
+ SymGetModuleBase64_ =
+ (SYMGETMODULEBASE64)GetProcAddress(dbghelp_dll, "SymGetModuleBase64");
+
+ if (! (MiniDumpWriteDump_ &&
+ SymInitialize_ && SymSetOptions_ && SymGetOptions_ &&
+ SymCleanup_ && SymGetTypeInfo_ && SymGetLineFromAddr64_ &&
+ SymEnumSymbols_ && SymSetContext_ && SymFromAddr_ &&
+ SymGetModuleBase64_ && StackWalk64_ &&
+ SymFunctionTableAccess64_))
+ goto cleanup;
+
+ /* initialize the symbol loading code */
+ opts = SymGetOptions_();
+
+ /* Set the 'load lines' option to retrieve line number information;
+ set the Deferred Loads option to map the debug info in memory only
+ when needed. */
+ SymSetOptions_(opts | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS);
+
+ /* Initialize the debughlp DLL with the default path and automatic
+ module enumeration (and loading of symbol tables) for this process.
+ */
+ SymInitialize_(GetCurrentProcess(), NULL, TRUE);
+
+ return TRUE;
+ }
+
+cleanup:
+ if (dbghelp_dll)
+ FreeLibrary(dbghelp_dll);
+
+ return FALSE;
+}
+
+/* Cleanup the dbghelp.dll library */
+static void
+cleanup_debughlp()
+{
+ SymCleanup_(GetCurrentProcess());
+
+ FreeLibrary(dbghelp_dll);
+}
+
+/* Create a filename based on a prefix, the timestamp and an extension.
+ check if the filename was already taken, retry 3 times. */
+BOOL
+get_temp_filename(char *filename, const char *prefix, const char *ext)
+{
+ char temp_dir[MAX_PATH - 64];
+ int i;
+
+ if (! GetTempPath(MAX_PATH - 64, temp_dir))
+ return FALSE;
+
+ for (i = 0;i < 3;i++)
+ {
+ HANDLE file;
+ time_t now;
+ char time_str[64];
+
+ time(&now);
+ strftime(time_str, 64, "%Y%m%d%H%M%S", localtime(&now));
+ sprintf(filename, "%s%s%s.%s", temp_dir, prefix, time_str, ext);
+
+ file = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (file != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(file);
+ return TRUE;
+ }
+ }
+
+ filename[0] = '\0';
+ return FALSE;
+}
+
+/* Unhandled exception callback set with SetUnhandledExceptionFilter() */
+LONG WINAPI
+svn__unhandled_exception_filter(PEXCEPTION_POINTERS ptrs)
+{
+ char dmp_filename[MAX_PATH];
+ char log_filename[MAX_PATH];
+ FILE *log_file;
+
+ /* Check if the crash handler was already loaded (crash while handling the
+ crash) */
+ if (dbghelp_dll != INVALID_HANDLE_VALUE)
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ /* don't log anything if we're running inside a debugger ... */
+ if (is_debugger_present())
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ /* ... or if we can't create the log files ... */
+ if (!get_temp_filename(dmp_filename, LOGFILE_PREFIX, "dmp") ||
+ !get_temp_filename(log_filename, LOGFILE_PREFIX, "log"))
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ /* If we can't load a recent version of the dbghelp.dll, pass on this
+ exception */
+ if (!load_dbghelp_dll())
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ /* open log file */
+ log_file = fopen(log_filename, "w+");
+
+ /* write information about the process */
+ fprintf(log_file, "\nProcess info:\n");
+ write_process_info(ptrs ? ptrs->ExceptionRecord : NULL,
+ ptrs ? ptrs->ContextRecord : NULL,
+ log_file);
+
+ /* write the stacktrace, if available */
+ fprintf(log_file, "\nStacktrace:\n");
+ write_stacktrace(ptrs ? ptrs->ContextRecord : NULL, log_file);
+
+ /* write the minidump file and use the callback to write the list of modules
+ to the log file */
+ fprintf(log_file, "\n\nLoaded modules:\n");
+ write_minidump_file(dmp_filename, ptrs,
+ write_module_info_callback, (void *)log_file);
+
+ fclose(log_file);
+
+ /* inform the user */
+ fprintf(stderr, "This application has halted due to an unexpected error.\n"
+ "A crash report and minidump file were saved to disk, you"
+ " can find them here:\n"
+ "%s\n%s\n"
+ "Please send the log file to %s to help us analyze\nand "
+ "solve this problem.\n\n"
+ "NOTE: The crash report and minidump files can contain some"
+ " sensitive information\n(filenames, partial file content, "
+ "usernames and passwords etc.)\n",
+ log_filename,
+ dmp_filename,
+ CRASHREPORT_EMAIL);
+
+ if (getenv("SVN_DBG_STACKTRACES_TO_STDERR") != NULL)
+ {
+ fprintf(stderr, "\nProcess info:\n");
+ write_process_info(ptrs ? ptrs->ExceptionRecord : NULL,
+ ptrs ? ptrs->ContextRecord : NULL,
+ stderr);
+ fprintf(stderr, "\nStacktrace:\n");
+ write_stacktrace(ptrs ? ptrs->ContextRecord : NULL, stderr);
+ }
+
+ fflush(stderr);
+ fflush(stdout);
+
+ cleanup_debughlp();
+
+ /* terminate the application */
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+#endif /* SVN_USE_WIN32_CRASHHANDLER */
+#endif /* WIN32 */
diff --git a/contrib/subversion/subversion/libsvn_subr/win32_crashrpt.h b/contrib/subversion/subversion/libsvn_subr/win32_crashrpt.h
new file mode 100644
index 0000000..77c25c1
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/win32_crashrpt.h
@@ -0,0 +1,35 @@
+/*
+ * win32_crashrpt.h : shares the win32 crashhandler functions in libsvn_subr.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_SUBR_WIN32_CRASHRPT_H
+#define SVN_LIBSVN_SUBR_WIN32_CRASHRPT_H
+
+#ifdef WIN32
+#ifdef SVN_USE_WIN32_CRASHHANDLER
+
+LONG WINAPI svn__unhandled_exception_filter(PEXCEPTION_POINTERS ptrs);
+
+#endif /* SVN_USE_WIN32_CRASHHANDLER */
+#endif /* WIN32 */
+
+#endif /* SVN_LIBSVN_SUBR_WIN32_CRASHRPT_H */
diff --git a/contrib/subversion/subversion/libsvn_subr/win32_crashrpt_dll.h b/contrib/subversion/subversion/libsvn_subr/win32_crashrpt_dll.h
new file mode 100644
index 0000000..18a4fc9
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/win32_crashrpt_dll.h
@@ -0,0 +1,93 @@
+/*
+ * win32_crashrpt_dll.h : private header file.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_SUBR_WIN32_CRASHRPT_DLL_H
+#define SVN_LIBSVN_SUBR_WIN32_CRASHRPT_DLL_H
+
+#ifdef WIN32
+#ifdef SVN_USE_WIN32_CRASHHANDLER
+
+/* public functions in dbghelp.dll */
+typedef BOOL (WINAPI * MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD ProcessId,
+ HANDLE hFile, MINIDUMP_TYPE DumpType,
+ CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
+ CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
+ CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
+typedef BOOL (WINAPI * SYMINITIALIZE)(HANDLE hProcess, PSTR UserSearchPath,
+ BOOL fInvadeProcess);
+typedef DWORD (WINAPI * SYMSETOPTIONS)(DWORD SymOptions);
+
+typedef DWORD (WINAPI * SYMGETOPTIONS)(VOID);
+
+typedef BOOL (WINAPI * SYMCLEANUP)(HANDLE hProcess);
+
+typedef BOOL (WINAPI * SYMGETTYPEINFO)(HANDLE hProcess, DWORD64 ModBase,
+ ULONG TypeId, IMAGEHLP_SYMBOL_TYPE_INFO GetType,
+ PVOID pInfo);
+
+typedef BOOL (WINAPI * SYMGETLINEFROMADDR64)(HANDLE hProcess, DWORD64 dwAddr,
+ PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line);
+
+typedef BOOL (WINAPI * SYMENUMSYMBOLS)(HANDLE hProcess, ULONG64 BaseOfDll, PCSTR Mask,
+ PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback,
+ PVOID UserContext);
+
+typedef BOOL (WINAPI * SYMSETCONTEXT)(HANDLE hProcess, PIMAGEHLP_STACK_FRAME StackFrame,
+ PIMAGEHLP_CONTEXT Context);
+
+typedef BOOL (WINAPI * SYMFROMADDR)(HANDLE hProcess, DWORD64 Address,
+ PDWORD64 Displacement, PSYMBOL_INFO Symbol);
+
+typedef BOOL (WINAPI * STACKWALK64)(DWORD MachineType, HANDLE hProcess, HANDLE hThread,
+ LPSTACKFRAME64 StackFrame, PVOID ContextRecord,
+ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
+ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
+ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
+ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress);
+
+typedef PVOID (WINAPI * SYMFUNCTIONTABLEACCESS64)(HANDLE hProcess, DWORD64 AddrBase);
+
+typedef DWORD64 (WINAPI * SYMGETMODULEBASE64)(HANDLE hProcess, DWORD64 dwAddr);
+
+/* public functions in kernel32.dll */
+typedef BOOL (WINAPI * ISDEBUGGERPRESENT)(VOID);
+
+/* function pointers */
+MINIDUMPWRITEDUMP MiniDumpWriteDump_;
+SYMINITIALIZE SymInitialize_;
+SYMSETOPTIONS SymSetOptions_;
+SYMGETOPTIONS SymGetOptions_;
+SYMCLEANUP SymCleanup_;
+SYMGETTYPEINFO SymGetTypeInfo_;
+SYMGETLINEFROMADDR64 SymGetLineFromAddr64_;
+SYMENUMSYMBOLS SymEnumSymbols_;
+SYMSETCONTEXT SymSetContext_;
+SYMFROMADDR SymFromAddr_;
+STACKWALK64 StackWalk64_;
+SYMFUNCTIONTABLEACCESS64 SymFunctionTableAccess64_;
+SYMGETMODULEBASE64 SymGetModuleBase64_;
+
+#endif /* SVN_USE_WIN32_CRASHHANDLER */
+#endif /* WIN32 */
+
+#endif /* SVN_LIBSVN_SUBR_WIN32_CRASHRPT_DLL_H */ \ No newline at end of file
diff --git a/contrib/subversion/subversion/libsvn_subr/win32_crypto.c b/contrib/subversion/subversion/libsvn_subr/win32_crypto.c
new file mode 100644
index 0000000..a7e3828
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/win32_crypto.c
@@ -0,0 +1,492 @@
+/*
+ * win32_crypto.c: win32 providers for SVN_AUTH_*
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* prevent "empty compilation unit" warning on e.g. UNIX */
+typedef int win32_crypto__dummy;
+
+/* ==================================================================== */
+
+#if defined(WIN32) && !defined(__MINGW32__)
+
+/*** Includes. ***/
+
+#include <apr_pools.h>
+#include <apr_base64.h>
+#include "svn_auth.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_utf.h"
+#include "svn_config.h"
+#include "svn_user.h"
+#include "svn_base64.h"
+
+#include "private/svn_auth_private.h"
+
+#include "svn_private_config.h"
+
+#include <wincrypt.h>
+
+
+/* The description string that's combined with unencrypted data by the
+ Windows CryptoAPI. Used during decryption to verify that the
+ encrypted data were valid. */
+static const WCHAR description[] = L"auth_svn.simple.wincrypt";
+
+
+/* Return a copy of ORIG, encrypted using the Windows CryptoAPI and
+ allocated from POOL. */
+const svn_string_t *
+encrypt_data(const svn_string_t *orig,
+ apr_pool_t *pool)
+{
+ DATA_BLOB blobin;
+ DATA_BLOB blobout;
+ const svn_string_t *crypted = NULL;
+
+ blobin.cbData = orig->len;
+ blobin.pbData = (BYTE *)orig->data;
+ if (CryptProtectData(&blobin, description, NULL, NULL, NULL,
+ CRYPTPROTECT_UI_FORBIDDEN, &blobout))
+ {
+ crypted = svn_string_ncreate((const char *)blobout.pbData,
+ blobout.cbData, pool);
+ LocalFree(blobout.pbData);
+ }
+ return crypted;
+}
+
+/* Return a copy of CRYPTED, decrypted using the Windows CryptoAPI and
+ allocated from POOL. */
+const svn_string_t *
+decrypt_data(const svn_string_t *crypted,
+ apr_pool_t *pool)
+{
+ DATA_BLOB blobin;
+ DATA_BLOB blobout;
+ LPWSTR descr;
+ const svn_string_t *orig = NULL;
+
+ blobin.cbData = crypted->len;
+ blobin.pbData = (BYTE *)crypted->data;
+ if (CryptUnprotectData(&blobin, &descr, NULL, NULL, NULL,
+ CRYPTPROTECT_UI_FORBIDDEN, &blobout))
+ {
+ if (0 == lstrcmpW(descr, description))
+ orig = svn_string_ncreate((const char *)blobout.pbData,
+ blobout.cbData, pool);
+ LocalFree(blobout.pbData);
+ LocalFree(descr);
+ }
+ return orig;
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* Windows simple provider, encrypts the password on Win2k and later. */
+/*-----------------------------------------------------------------------*/
+
+/* Implementation of svn_auth__password_set_t that encrypts
+ the incoming password using the Windows CryptoAPI. */
+static svn_error_t *
+windows_password_encrypter(svn_boolean_t *done,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ const char *in,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ const svn_string_t *coded;
+
+ coded = encrypt_data(svn_string_create(in, pool), pool);
+ if (coded)
+ {
+ coded = svn_base64_encode_string2(coded, FALSE, pool);
+ SVN_ERR(svn_auth__simple_password_set(done, creds, realmstring, username,
+ coded->data, parameters,
+ non_interactive, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implementation of svn_auth__password_get_t that decrypts
+ the incoming password using the Windows CryptoAPI and verifies its
+ validity. */
+static svn_error_t *
+windows_password_decrypter(svn_boolean_t *done,
+ const char **out,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ const svn_string_t *orig;
+ const char *in;
+
+ SVN_ERR(svn_auth__simple_password_get(done, &in, creds, realmstring, username,
+ parameters, non_interactive, pool));
+ if (!*done)
+ return SVN_NO_ERROR;
+
+ orig = svn_base64_decode_string(svn_string_create(in, pool), pool);
+ orig = decrypt_data(orig, pool);
+ if (orig)
+ {
+ *out = orig->data;
+ *done = TRUE;
+ }
+ else
+ {
+ *done = FALSE;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Get cached encrypted credentials from the simple provider's cache. */
+static svn_error_t *
+windows_simple_first_creds(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_get(credentials,
+ iter_baton,
+ provider_baton,
+ parameters,
+ realmstring,
+ windows_password_decrypter,
+ SVN_AUTH__WINCRYPT_PASSWORD_TYPE,
+ pool);
+}
+
+/* Save encrypted credentials to the simple provider's cache. */
+static svn_error_t *
+windows_simple_save_creds(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__simple_creds_cache_set(saved, credentials,
+ provider_baton,
+ parameters,
+ realmstring,
+ windows_password_encrypter,
+ SVN_AUTH__WINCRYPT_PASSWORD_TYPE,
+ pool);
+}
+
+static const svn_auth_provider_t windows_simple_provider = {
+ SVN_AUTH_CRED_SIMPLE,
+ windows_simple_first_creds,
+ NULL,
+ windows_simple_save_creds
+};
+
+
+/* Public API */
+void
+svn_auth_get_windows_simple_provider(svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+
+ po->vtable = &windows_simple_provider;
+ *provider = po;
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* Windows SSL server trust provider, validates ssl certificate using */
+/* CryptoApi. */
+/*-----------------------------------------------------------------------*/
+
+/* Implementation of svn_auth__password_set_t that encrypts
+ the incoming password using the Windows CryptoAPI. */
+static svn_error_t *
+windows_ssl_client_cert_pw_encrypter(svn_boolean_t *done,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ const char *in,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ const svn_string_t *coded;
+
+ coded = encrypt_data(svn_string_create(in, pool), pool);
+ if (coded)
+ {
+ coded = svn_base64_encode_string2(coded, FALSE, pool);
+ SVN_ERR(svn_auth__ssl_client_cert_pw_set(done, creds, realmstring,
+ username, coded->data,
+ parameters, non_interactive,
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implementation of svn_auth__password_get_t that decrypts
+ the incoming password using the Windows CryptoAPI and verifies its
+ validity. */
+static svn_error_t *
+windows_ssl_client_cert_pw_decrypter(svn_boolean_t *done,
+ const char **out,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ const svn_string_t *orig;
+ const char *in;
+
+ SVN_ERR(svn_auth__ssl_client_cert_pw_get(done, &in, creds, realmstring,
+ username, parameters,
+ non_interactive, pool));
+ if (!*done)
+ return SVN_NO_ERROR;
+
+ orig = svn_base64_decode_string(svn_string_create(in, pool), pool);
+ orig = decrypt_data(orig, pool);
+ if (orig)
+ {
+ *out = orig->data;
+ *done = TRUE;
+ }
+ else
+ {
+ *done = FALSE;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Get cached encrypted credentials from the simple provider's cache. */
+static svn_error_t *
+windows_ssl_client_cert_pw_first_creds(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__ssl_client_cert_pw_cache_get(
+ credentials, iter_baton, provider_baton, parameters, realmstring,
+ windows_ssl_client_cert_pw_decrypter,
+ SVN_AUTH__WINCRYPT_PASSWORD_TYPE, pool);
+}
+
+/* Save encrypted credentials to the simple provider's cache. */
+static svn_error_t *
+windows_ssl_client_cert_pw_save_creds(svn_boolean_t *saved,
+ void *credentials,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ return svn_auth__ssl_client_cert_pw_cache_set(
+ saved, credentials, provider_baton, parameters, realmstring,
+ windows_ssl_client_cert_pw_encrypter,
+ SVN_AUTH__WINCRYPT_PASSWORD_TYPE, pool);
+}
+
+static const svn_auth_provider_t windows_ssl_client_cert_pw_provider = {
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ windows_ssl_client_cert_pw_first_creds,
+ NULL,
+ windows_ssl_client_cert_pw_save_creds
+};
+
+
+/* Public API */
+void
+svn_auth_get_windows_ssl_client_cert_pw_provider
+ (svn_auth_provider_object_t **provider,
+ apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+
+ po->vtable = &windows_ssl_client_cert_pw_provider;
+ *provider = po;
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* Windows SSL server trust provider, validates ssl certificate using */
+/* CryptoApi. */
+/*-----------------------------------------------------------------------*/
+
+/* Helper to create CryptoAPI CERT_CONTEXT from base64 encoded BASE64_CERT.
+ * Returns NULL on error.
+ */
+static PCCERT_CONTEXT
+certcontext_from_base64(const char *base64_cert, apr_pool_t *pool)
+{
+ PCCERT_CONTEXT cert_context = NULL;
+ int cert_len;
+ BYTE *binary_cert;
+
+ /* Use apr-util as CryptStringToBinaryA is available only on XP+. */
+ binary_cert = apr_palloc(pool,
+ apr_base64_decode_len(base64_cert));
+ cert_len = apr_base64_decode((char*)binary_cert, base64_cert);
+
+ /* Parse the certificate into a context. */
+ cert_context = CertCreateCertificateContext
+ (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, binary_cert, cert_len);
+
+ return cert_context;
+}
+
+/* Helper for windows_ssl_server_trust_first_credentials for validating
+ * certificate using CryptoApi. Sets *OK_P to TRUE if base64 encoded ASCII_CERT
+ * certificate considered as valid.
+ */
+static svn_error_t *
+windows_validate_certificate(svn_boolean_t *ok_p,
+ const char *ascii_cert,
+ apr_pool_t *pool)
+{
+ PCCERT_CONTEXT cert_context = NULL;
+ CERT_CHAIN_PARA chain_para;
+ PCCERT_CHAIN_CONTEXT chain_context = NULL;
+
+ *ok_p = FALSE;
+
+ /* Parse the certificate into a context. */
+ cert_context = certcontext_from_base64(ascii_cert, pool);
+
+ if (cert_context)
+ {
+ /* Retrieve the certificate chain of the certificate
+ (a certificate without a valid root does not have a chain). */
+ memset(&chain_para, 0, sizeof(chain_para));
+ chain_para.cbSize = sizeof(chain_para);
+
+ if (CertGetCertificateChain(NULL, cert_context, NULL, NULL, &chain_para,
+ CERT_CHAIN_CACHE_END_CERT |
+ CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,
+ NULL, &chain_context))
+ {
+ CERT_CHAIN_POLICY_PARA policy_para;
+ CERT_CHAIN_POLICY_STATUS policy_status;
+
+ policy_para.cbSize = sizeof(policy_para);
+ policy_para.dwFlags = 0;
+ policy_para.pvExtraPolicyPara = NULL;
+
+ policy_status.cbSize = sizeof(policy_status);
+
+ if (CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL,
+ chain_context, &policy_para,
+ &policy_status))
+ {
+ if (policy_status.dwError == S_OK)
+ {
+ /* Windows thinks the certificate is valid. */
+ *ok_p = TRUE;
+ }
+ }
+
+ CertFreeCertificateChain(chain_context);
+ }
+ CertFreeCertificateContext(cert_context);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Retrieve ssl server CA failure overrides (if any) from CryptoApi. */
+static svn_error_t *
+windows_ssl_server_trust_first_credentials(void **credentials,
+ void **iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ apr_uint32_t *failures = svn_hash_gets(parameters,
+ SVN_AUTH_PARAM_SSL_SERVER_FAILURES);
+ const svn_auth_ssl_server_cert_info_t *cert_info =
+ svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO);
+
+ *credentials = NULL;
+ *iter_baton = NULL;
+
+ /* We can accept only unknown certificate authority. */
+ if (*failures & SVN_AUTH_SSL_UNKNOWNCA)
+ {
+ svn_boolean_t ok;
+
+ SVN_ERR(windows_validate_certificate(&ok, cert_info->ascii_cert, pool));
+
+ /* Windows thinks that certificate is ok. */
+ if (ok)
+ {
+ /* Clear failure flag. */
+ *failures &= ~SVN_AUTH_SSL_UNKNOWNCA;
+ }
+ }
+
+ /* If all failures are cleared now, we return the creds */
+ if (! *failures)
+ {
+ svn_auth_cred_ssl_server_trust_t *creds =
+ apr_pcalloc(pool, sizeof(*creds));
+ creds->may_save = FALSE; /* No need to save it. */
+ *credentials = creds;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static const svn_auth_provider_t windows_server_trust_provider = {
+ SVN_AUTH_CRED_SSL_SERVER_TRUST,
+ windows_ssl_server_trust_first_credentials,
+ NULL,
+ NULL,
+};
+
+/* Public API */
+void
+svn_auth_get_windows_ssl_server_trust_provider
+ (svn_auth_provider_object_t **provider, apr_pool_t *pool)
+{
+ svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
+
+ po->vtable = &windows_server_trust_provider;
+ *provider = po;
+}
+
+#endif /* WIN32 */
diff --git a/contrib/subversion/subversion/libsvn_subr/win32_xlate.c b/contrib/subversion/subversion/libsvn_subr/win32_xlate.c
new file mode 100644
index 0000000..efe9c05
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/win32_xlate.c
@@ -0,0 +1,238 @@
+/*
+ * win32_xlate.c : Windows xlate stuff.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* prevent "empty compilation unit" warning on e.g. UNIX */
+typedef int win32_xlate__dummy;
+
+#ifdef WIN32
+
+/* Define _WIN32_DCOM for CoInitializeEx(). */
+#define _WIN32_DCOM
+
+/* We must include windows.h ourselves or apr.h includes it for us with
+ many ignore options set. Including Winsock is required to resolve IPv6
+ compilation errors. APR_HAVE_IPV6 is only defined after including
+ apr.h, so we can't detect this case here. */
+
+/* winsock2.h includes windows.h */
+#include <winsock2.h>
+#include <Ws2tcpip.h>
+#include <mlang.h>
+
+#include <apr.h>
+#include <apr_errno.h>
+#include <apr_portable.h>
+
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_utf.h"
+#include "private/svn_atomic.h"
+
+#include "win32_xlate.h"
+
+static svn_atomic_t com_initialized = 0;
+
+/* Initializes COM and keeps COM available until process exit.
+ Implements svn_atomic__init_once init_func */
+static svn_error_t *
+initialize_com(void *baton, apr_pool_t* pool)
+{
+ /* Try to initialize for apartment-threaded object concurrency. */
+ HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+
+ if (hr == RPC_E_CHANGED_MODE)
+ {
+ /* COM already initalized for multi-threaded object concurrency. We are
+ neutral to object concurrency so try to initalize it in the same way
+ for us, to keep an handle open. */
+ hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ }
+
+ if (FAILED(hr))
+ return svn_error_create(APR_EGENERAL, NULL, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+typedef struct win32_xlate_t
+{
+ UINT from_page_id;
+ UINT to_page_id;
+} win32_xlate_t;
+
+static apr_status_t
+get_page_id_from_name(UINT *page_id_p, const char *page_name, apr_pool_t *pool)
+{
+ IMultiLanguage * mlang = NULL;
+ HRESULT hr;
+ MIMECSETINFO page_info;
+ WCHAR ucs2_page_name[128];
+ svn_error_t *err;
+
+ if (page_name == SVN_APR_DEFAULT_CHARSET)
+ {
+ *page_id_p = CP_ACP;
+ return APR_SUCCESS;
+ }
+ else if (page_name == SVN_APR_LOCALE_CHARSET)
+ {
+ *page_id_p = CP_THREAD_ACP; /* Valid on Windows 2000+ */
+ return APR_SUCCESS;
+ }
+ else if (!strcmp(page_name, "UTF-8"))
+ {
+ *page_id_p = CP_UTF8;
+ return APR_SUCCESS;
+ }
+
+ /* Use codepage identifier nnn if the codepage name is in the form
+ of "CPnnn".
+ We need this code since apr_os_locale_encoding() and svn_cmdline_init()
+ generates such codepage names even if they are not valid IANA charset
+ name. */
+ if ((page_name[0] == 'c' || page_name[0] == 'C')
+ && (page_name[1] == 'p' || page_name[1] == 'P'))
+ {
+ *page_id_p = atoi(page_name + 2);
+ return APR_SUCCESS;
+ }
+
+ err = svn_atomic__init_once(&com_initialized, initialize_com, NULL, pool);
+
+ if (err)
+ {
+ svn_error_clear(err);
+ return APR_EGENERAL;
+ }
+
+ hr = CoCreateInstance(&CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER,
+ &IID_IMultiLanguage, (void **) &mlang);
+
+ if (FAILED(hr))
+ return APR_EGENERAL;
+
+ /* Convert page name to wide string. */
+ MultiByteToWideChar(CP_UTF8, 0, page_name, -1, ucs2_page_name,
+ sizeof(ucs2_page_name) / sizeof(ucs2_page_name[0]));
+ memset(&page_info, 0, sizeof(page_info));
+ hr = mlang->lpVtbl->GetCharsetInfo(mlang, ucs2_page_name, &page_info);
+ if (FAILED(hr))
+ {
+ mlang->lpVtbl->Release(mlang);
+ return APR_EINVAL;
+ }
+
+ if (page_info.uiInternetEncoding)
+ *page_id_p = page_info.uiInternetEncoding;
+ else
+ *page_id_p = page_info.uiCodePage;
+
+ mlang->lpVtbl->Release(mlang);
+
+ return APR_SUCCESS;
+}
+
+apr_status_t
+svn_subr__win32_xlate_open(win32_xlate_t **xlate_p, const char *topage,
+ const char *frompage, apr_pool_t *pool)
+{
+ UINT from_page_id, to_page_id;
+ apr_status_t apr_err = APR_SUCCESS;
+ win32_xlate_t *xlate;
+
+ apr_err = get_page_id_from_name(&to_page_id, topage, pool);
+ if (apr_err == APR_SUCCESS)
+ apr_err = get_page_id_from_name(&from_page_id, frompage, pool);
+
+ if (apr_err == APR_SUCCESS)
+ {
+ xlate = apr_palloc(pool, sizeof(*xlate));
+ xlate->from_page_id = from_page_id;
+ xlate->to_page_id = to_page_id;
+
+ *xlate_p = xlate;
+ }
+
+ return apr_err;
+}
+
+apr_status_t
+svn_subr__win32_xlate_to_stringbuf(win32_xlate_t *handle,
+ const char *src_data,
+ apr_size_t src_length,
+ svn_stringbuf_t **dest,
+ apr_pool_t *pool)
+{
+ WCHAR * wide_str;
+ int retval, wide_size;
+
+ if (src_length == 0)
+ {
+ *dest = svn_stringbuf_create_empty(pool);
+ return APR_SUCCESS;
+ }
+
+ retval = MultiByteToWideChar(handle->from_page_id, 0, src_data, src_length,
+ NULL, 0);
+ if (retval == 0)
+ return apr_get_os_error();
+
+ wide_size = retval;
+
+ /* Allocate temporary buffer for small strings on stack instead of heap. */
+ if (wide_size <= MAX_PATH)
+ {
+ wide_str = alloca(wide_size * sizeof(WCHAR));
+ }
+ else
+ {
+ wide_str = apr_palloc(pool, wide_size * sizeof(WCHAR));
+ }
+
+ retval = MultiByteToWideChar(handle->from_page_id, 0, src_data, src_length,
+ wide_str, wide_size);
+
+ if (retval == 0)
+ return apr_get_os_error();
+
+ retval = WideCharToMultiByte(handle->to_page_id, 0, wide_str, wide_size,
+ NULL, 0, NULL, NULL);
+
+ if (retval == 0)
+ return apr_get_os_error();
+
+ /* Ensure that buffer is enough to hold result string and termination
+ character. */
+ *dest = svn_stringbuf_create_ensure(retval + 1, pool);
+ (*dest)->len = retval;
+
+ retval = WideCharToMultiByte(handle->to_page_id, 0, wide_str, wide_size,
+ (*dest)->data, (*dest)->len, NULL, NULL);
+ if (retval == 0)
+ return apr_get_os_error();
+
+ (*dest)->len = retval;
+ return APR_SUCCESS;
+}
+
+#endif /* WIN32 */
diff --git a/contrib/subversion/subversion/libsvn_subr/win32_xlate.h b/contrib/subversion/subversion/libsvn_subr/win32_xlate.h
new file mode 100644
index 0000000..82fc832
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/win32_xlate.h
@@ -0,0 +1,52 @@
+/*
+ * win32_xlate.h : Windows xlate stuff.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_SUBR_WIN32_XLATE_H
+#define SVN_LIBSVN_SUBR_WIN32_XLATE_H
+
+#ifdef WIN32
+
+/* Opaque translation buffer. */
+typedef struct win32_xlate_t win32_xlate_t;
+
+/* Set *XLATE_P to a handle node for converting from FROMPAGE to TOPAGE.
+ Returns APR_EINVAL or APR_ENOTIMPL, if a conversion isn't supported.
+ If fail for any other reason, return the error.
+
+ Allocate *RET in POOL. */
+apr_status_t svn_subr__win32_xlate_open(win32_xlate_t **xlate_p,
+ const char *topage,
+ const char *frompage,
+ apr_pool_t *pool);
+
+/* Convert SRC_LENGTH bytes of SRC_DATA in NODE->handle, store the result
+ in *DEST, which is allocated in POOL. */
+apr_status_t svn_subr__win32_xlate_to_stringbuf(win32_xlate_t *handle,
+ const char *src_data,
+ apr_size_t src_length,
+ svn_stringbuf_t **dest,
+ apr_pool_t *pool);
+
+#endif /* WIN32 */
+
+#endif /* SVN_LIBSVN_SUBR_WIN32_XLATE_H */
diff --git a/contrib/subversion/subversion/libsvn_subr/xml.c b/contrib/subversion/subversion/libsvn_subr/xml.c
new file mode 100644
index 0000000..a9d834a
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_subr/xml.c
@@ -0,0 +1,655 @@
+/*
+ * xml.c: xml helper code shared among the Subversion libraries.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+#include <assert.h>
+
+#include "svn_private_config.h" /* for SVN_HAVE_OLD_EXPAT */
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_xml.h"
+#include "svn_error.h"
+#include "svn_ctype.h"
+
+#include "private/svn_utf_private.h"
+
+#ifdef SVN_HAVE_OLD_EXPAT
+#include <xmlparse.h>
+#else
+#include <expat.h>
+#endif
+
+#ifdef XML_UNICODE
+#error Expat is unusable -- it has been compiled for wide characters
+#endif
+
+/* The private internals for a parser object. */
+struct svn_xml_parser_t
+{
+ /** the expat parser */
+ XML_Parser parser;
+
+ /** the SVN callbacks to call from the Expat callbacks */
+ svn_xml_start_elem start_handler;
+ svn_xml_end_elem end_handler;
+ svn_xml_char_data data_handler;
+
+ /** the user's baton for private data */
+ void *baton;
+
+ /** if non-@c NULL, an error happened while parsing */
+ svn_error_t *error;
+
+ /** where this object is allocated, so we can free it easily */
+ apr_pool_t *pool;
+
+};
+
+
+/*** XML character validation ***/
+
+svn_boolean_t
+svn_xml_is_xml_safe(const char *data, apr_size_t len)
+{
+ const char *end = data + len;
+ const char *p;
+
+ if (! svn_utf__is_valid(data, len))
+ return FALSE;
+
+ for (p = data; p < end; p++)
+ {
+ unsigned char c = *p;
+
+ if (svn_ctype_iscntrl(c))
+ {
+ if ((c != SVN_CTYPE_ASCII_TAB)
+ && (c != SVN_CTYPE_ASCII_LINEFEED)
+ && (c != SVN_CTYPE_ASCII_CARRIAGERETURN)
+ && (c != SVN_CTYPE_ASCII_DELETE))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+
+
+
+/*** XML escaping. ***/
+
+/* ### ...?
+ *
+ * If *OUTSTR is @c NULL, set *OUTSTR to a new stringbuf allocated
+ * in POOL, else append to the existing stringbuf there.
+ */
+static void
+xml_escape_cdata(svn_stringbuf_t **outstr,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ const char *end = data + len;
+ const char *p = data, *q;
+
+ if (*outstr == NULL)
+ *outstr = svn_stringbuf_create_empty(pool);
+
+ while (1)
+ {
+ /* Find a character which needs to be quoted and append bytes up
+ to that point. Strictly speaking, '>' only needs to be
+ quoted if it follows "]]", but it's easier to quote it all
+ the time.
+
+ So, why are we escaping '\r' here? Well, according to the
+ XML spec, '\r\n' gets converted to '\n' during XML parsing.
+ Also, any '\r' not followed by '\n' is converted to '\n'. By
+ golly, if we say we want to escape a '\r', we want to make
+ sure it remains a '\r'! */
+ q = p;
+ while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '\r')
+ q++;
+ svn_stringbuf_appendbytes(*outstr, p, q - p);
+
+ /* We may already be a winner. */
+ if (q == end)
+ break;
+
+ /* Append the entity reference for the character. */
+ if (*q == '&')
+ svn_stringbuf_appendcstr(*outstr, "&amp;");
+ else if (*q == '<')
+ svn_stringbuf_appendcstr(*outstr, "&lt;");
+ else if (*q == '>')
+ svn_stringbuf_appendcstr(*outstr, "&gt;");
+ else if (*q == '\r')
+ svn_stringbuf_appendcstr(*outstr, "&#13;");
+
+ p = q + 1;
+ }
+}
+
+/* Essentially the same as xml_escape_cdata, with the addition of
+ whitespace and quote characters. */
+static void
+xml_escape_attr(svn_stringbuf_t **outstr,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ const char *end = data + len;
+ const char *p = data, *q;
+
+ if (*outstr == NULL)
+ *outstr = svn_stringbuf_create_ensure(len, pool);
+
+ while (1)
+ {
+ /* Find a character which needs to be quoted and append bytes up
+ to that point. */
+ q = p;
+ while (q < end && *q != '&' && *q != '<' && *q != '>'
+ && *q != '"' && *q != '\'' && *q != '\r'
+ && *q != '\n' && *q != '\t')
+ q++;
+ svn_stringbuf_appendbytes(*outstr, p, q - p);
+
+ /* We may already be a winner. */
+ if (q == end)
+ break;
+
+ /* Append the entity reference for the character. */
+ if (*q == '&')
+ svn_stringbuf_appendcstr(*outstr, "&amp;");
+ else if (*q == '<')
+ svn_stringbuf_appendcstr(*outstr, "&lt;");
+ else if (*q == '>')
+ svn_stringbuf_appendcstr(*outstr, "&gt;");
+ else if (*q == '"')
+ svn_stringbuf_appendcstr(*outstr, "&quot;");
+ else if (*q == '\'')
+ svn_stringbuf_appendcstr(*outstr, "&apos;");
+ else if (*q == '\r')
+ svn_stringbuf_appendcstr(*outstr, "&#13;");
+ else if (*q == '\n')
+ svn_stringbuf_appendcstr(*outstr, "&#10;");
+ else if (*q == '\t')
+ svn_stringbuf_appendcstr(*outstr, "&#9;");
+
+ p = q + 1;
+ }
+}
+
+
+void
+svn_xml_escape_cdata_stringbuf(svn_stringbuf_t **outstr,
+ const svn_stringbuf_t *string,
+ apr_pool_t *pool)
+{
+ xml_escape_cdata(outstr, string->data, string->len, pool);
+}
+
+
+void
+svn_xml_escape_cdata_string(svn_stringbuf_t **outstr,
+ const svn_string_t *string,
+ apr_pool_t *pool)
+{
+ xml_escape_cdata(outstr, string->data, string->len, pool);
+}
+
+
+void
+svn_xml_escape_cdata_cstring(svn_stringbuf_t **outstr,
+ const char *string,
+ apr_pool_t *pool)
+{
+ xml_escape_cdata(outstr, string, (apr_size_t) strlen(string), pool);
+}
+
+
+void
+svn_xml_escape_attr_stringbuf(svn_stringbuf_t **outstr,
+ const svn_stringbuf_t *string,
+ apr_pool_t *pool)
+{
+ xml_escape_attr(outstr, string->data, string->len, pool);
+}
+
+
+void
+svn_xml_escape_attr_string(svn_stringbuf_t **outstr,
+ const svn_string_t *string,
+ apr_pool_t *pool)
+{
+ xml_escape_attr(outstr, string->data, string->len, pool);
+}
+
+
+void
+svn_xml_escape_attr_cstring(svn_stringbuf_t **outstr,
+ const char *string,
+ apr_pool_t *pool)
+{
+ xml_escape_attr(outstr, string, (apr_size_t) strlen(string), pool);
+}
+
+
+const char *
+svn_xml_fuzzy_escape(const char *string, apr_pool_t *pool)
+{
+ const char *end = string + strlen(string);
+ const char *p = string, *q;
+ svn_stringbuf_t *outstr;
+ char escaped_char[6]; /* ? \ u u u \0 */
+
+ for (q = p; q < end; q++)
+ {
+ if (svn_ctype_iscntrl(*q)
+ && ! ((*q == '\n') || (*q == '\r') || (*q == '\t')))
+ break;
+ }
+
+ /* Return original string if no unsafe characters found. */
+ if (q == end)
+ return string;
+
+ outstr = svn_stringbuf_create_empty(pool);
+ while (1)
+ {
+ q = p;
+
+ /* Traverse till either unsafe character or eos. */
+ while ((q < end)
+ && ((! svn_ctype_iscntrl(*q))
+ || (*q == '\n') || (*q == '\r') || (*q == '\t')))
+ q++;
+
+ /* copy chunk before marker */
+ svn_stringbuf_appendbytes(outstr, p, q - p);
+
+ if (q == end)
+ break;
+
+ /* Append an escaped version of the unsafe character.
+
+ ### This format was chosen for consistency with
+ ### svn_utf__cstring_from_utf8_fuzzy(). The two functions
+ ### should probably share code, even though they escape
+ ### different characters.
+ */
+ apr_snprintf(escaped_char, sizeof(escaped_char), "?\\%03u",
+ (unsigned char) *q);
+ svn_stringbuf_appendcstr(outstr, escaped_char);
+
+ p = q + 1;
+ }
+
+ return outstr->data;
+}
+
+
+/*** Map from the Expat callback types to the SVN XML types. ***/
+
+static void expat_start_handler(void *userData,
+ const XML_Char *name,
+ const XML_Char **atts)
+{
+ svn_xml_parser_t *svn_parser = userData;
+
+ (*svn_parser->start_handler)(svn_parser->baton, name, atts);
+}
+
+static void expat_end_handler(void *userData, const XML_Char *name)
+{
+ svn_xml_parser_t *svn_parser = userData;
+
+ (*svn_parser->end_handler)(svn_parser->baton, name);
+}
+
+static void expat_data_handler(void *userData, const XML_Char *s, int len)
+{
+ svn_xml_parser_t *svn_parser = userData;
+
+ (*svn_parser->data_handler)(svn_parser->baton, s, (apr_size_t)len);
+}
+
+
+/*** Making a parser. ***/
+
+svn_xml_parser_t *
+svn_xml_make_parser(void *baton,
+ svn_xml_start_elem start_handler,
+ svn_xml_end_elem end_handler,
+ svn_xml_char_data data_handler,
+ apr_pool_t *pool)
+{
+ svn_xml_parser_t *svn_parser;
+ apr_pool_t *subpool;
+
+ XML_Parser parser = XML_ParserCreate(NULL);
+
+ XML_SetElementHandler(parser,
+ start_handler ? expat_start_handler : NULL,
+ end_handler ? expat_end_handler : NULL);
+ XML_SetCharacterDataHandler(parser,
+ data_handler ? expat_data_handler : NULL);
+
+ /* ### we probably don't want this pool; or at least we should pass it
+ ### to the callbacks and clear it periodically. */
+ subpool = svn_pool_create(pool);
+
+ svn_parser = apr_pcalloc(subpool, sizeof(*svn_parser));
+
+ svn_parser->parser = parser;
+ svn_parser->start_handler = start_handler;
+ svn_parser->end_handler = end_handler;
+ svn_parser->data_handler = data_handler;
+ svn_parser->baton = baton;
+ svn_parser->pool = subpool;
+
+ /* store our parser info as the UserData in the Expat parser */
+ XML_SetUserData(parser, svn_parser);
+
+ return svn_parser;
+}
+
+
+/* Free a parser */
+void
+svn_xml_free_parser(svn_xml_parser_t *svn_parser)
+{
+ /* Free the expat parser */
+ XML_ParserFree(svn_parser->parser);
+
+ /* Free the subversion parser */
+ svn_pool_destroy(svn_parser->pool);
+}
+
+
+
+
+svn_error_t *
+svn_xml_parse(svn_xml_parser_t *svn_parser,
+ const char *buf,
+ apr_size_t len,
+ svn_boolean_t is_final)
+{
+ svn_error_t *err;
+ int success;
+
+ /* Parse some xml data */
+ success = XML_Parse(svn_parser->parser, buf, (int) len, is_final);
+
+ /* If expat choked internally, return its error. */
+ if (! success)
+ {
+ /* Line num is "int" in Expat v1, "long" in v2; hide the difference. */
+ long line = XML_GetCurrentLineNumber(svn_parser->parser);
+
+ err = svn_error_createf
+ (SVN_ERR_XML_MALFORMED, NULL,
+ _("Malformed XML: %s at line %ld"),
+ XML_ErrorString(XML_GetErrorCode(svn_parser->parser)), line);
+
+ /* Kill all parsers and return the expat error */
+ svn_xml_free_parser(svn_parser);
+ return err;
+ }
+
+ /* Did an error occur somewhere *inside* the expat callbacks? */
+ if (svn_parser->error)
+ {
+ err = svn_parser->error;
+ svn_xml_free_parser(svn_parser);
+ return err;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+void svn_xml_signal_bailout(svn_error_t *error,
+ svn_xml_parser_t *svn_parser)
+{
+ /* This will cause the current XML_Parse() call to finish quickly! */
+ XML_SetElementHandler(svn_parser->parser, NULL, NULL);
+ XML_SetCharacterDataHandler(svn_parser->parser, NULL);
+
+ /* Once outside of XML_Parse(), the existence of this field will
+ cause svn_delta_parse()'s main read-loop to return error. */
+ svn_parser->error = error;
+}
+
+
+
+
+
+
+
+
+/*** Attribute walking. ***/
+
+const char *
+svn_xml_get_attr_value(const char *name, const char *const *atts)
+{
+ while (atts && (*atts))
+ {
+ if (strcmp(atts[0], name) == 0)
+ return atts[1];
+ else
+ atts += 2; /* continue looping */
+ }
+
+ /* Else no such attribute name seen. */
+ return NULL;
+}
+
+
+
+/*** Printing XML ***/
+
+void
+svn_xml_make_header2(svn_stringbuf_t **str, const char *encoding,
+ apr_pool_t *pool)
+{
+
+ if (*str == NULL)
+ *str = svn_stringbuf_create_empty(pool);
+ svn_stringbuf_appendcstr(*str, "<?xml version=\"1.0\"");
+ if (encoding)
+ {
+ encoding = apr_psprintf(pool, " encoding=\"%s\"", encoding);
+ svn_stringbuf_appendcstr(*str, encoding);
+ }
+ svn_stringbuf_appendcstr(*str, "?>\n");
+}
+
+
+
+/*** Creating attribute hashes. ***/
+
+/* Combine an existing attribute list ATTS with a HASH that itself
+ represents an attribute list. Iff PRESERVE is true, then no value
+ already in HASH will be changed, else values from ATTS will
+ override previous values in HASH. */
+static void
+amalgamate(const char **atts,
+ apr_hash_t *ht,
+ svn_boolean_t preserve,
+ apr_pool_t *pool)
+{
+ const char *key;
+
+ if (atts)
+ for (key = *atts; key; key = *(++atts))
+ {
+ const char *val = *(++atts);
+ size_t keylen;
+ assert(key != NULL);
+ /* kff todo: should we also insist that val be non-null here?
+ Probably. */
+
+ keylen = strlen(key);
+ if (preserve && ((apr_hash_get(ht, key, keylen)) != NULL))
+ continue;
+ else
+ apr_hash_set(ht, apr_pstrndup(pool, key, keylen), keylen,
+ val ? apr_pstrdup(pool, val) : NULL);
+ }
+}
+
+
+apr_hash_t *
+svn_xml_ap_to_hash(va_list ap, apr_pool_t *pool)
+{
+ apr_hash_t *ht = apr_hash_make(pool);
+ const char *key;
+
+ while ((key = va_arg(ap, char *)) != NULL)
+ {
+ const char *val = va_arg(ap, const char *);
+ svn_hash_sets(ht, key, val);
+ }
+
+ return ht;
+}
+
+
+apr_hash_t *
+svn_xml_make_att_hash(const char **atts, apr_pool_t *pool)
+{
+ apr_hash_t *ht = apr_hash_make(pool);
+ amalgamate(atts, ht, 0, pool); /* third arg irrelevant in this case */
+ return ht;
+}
+
+
+void
+svn_xml_hash_atts_overlaying(const char **atts,
+ apr_hash_t *ht,
+ apr_pool_t *pool)
+{
+ amalgamate(atts, ht, 0, pool);
+}
+
+
+void
+svn_xml_hash_atts_preserving(const char **atts,
+ apr_hash_t *ht,
+ apr_pool_t *pool)
+{
+ amalgamate(atts, ht, 1, pool);
+}
+
+
+
+/*** Making XML tags. ***/
+
+
+void
+svn_xml_make_open_tag_hash(svn_stringbuf_t **str,
+ apr_pool_t *pool,
+ enum svn_xml_open_tag_style style,
+ const char *tagname,
+ apr_hash_t *attributes)
+{
+ apr_hash_index_t *hi;
+ apr_size_t est_size = strlen(tagname) + 4 + apr_hash_count(attributes) * 30;
+
+ if (*str == NULL)
+ *str = svn_stringbuf_create_ensure(est_size, pool);
+
+ svn_stringbuf_appendcstr(*str, "<");
+ svn_stringbuf_appendcstr(*str, tagname);
+
+ for (hi = apr_hash_first(pool, attributes); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ assert(val != NULL);
+
+ svn_stringbuf_appendcstr(*str, "\n ");
+ svn_stringbuf_appendcstr(*str, key);
+ svn_stringbuf_appendcstr(*str, "=\"");
+ svn_xml_escape_attr_cstring(str, val, pool);
+ svn_stringbuf_appendcstr(*str, "\"");
+ }
+
+ if (style == svn_xml_self_closing)
+ svn_stringbuf_appendcstr(*str, "/");
+ svn_stringbuf_appendcstr(*str, ">");
+ if (style != svn_xml_protect_pcdata)
+ svn_stringbuf_appendcstr(*str, "\n");
+}
+
+
+void
+svn_xml_make_open_tag_v(svn_stringbuf_t **str,
+ apr_pool_t *pool,
+ enum svn_xml_open_tag_style style,
+ const char *tagname,
+ va_list ap)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *ht = svn_xml_ap_to_hash(ap, subpool);
+
+ svn_xml_make_open_tag_hash(str, pool, style, tagname, ht);
+ svn_pool_destroy(subpool);
+}
+
+
+
+void
+svn_xml_make_open_tag(svn_stringbuf_t **str,
+ apr_pool_t *pool,
+ enum svn_xml_open_tag_style style,
+ const char *tagname,
+ ...)
+{
+ va_list ap;
+
+ va_start(ap, tagname);
+ svn_xml_make_open_tag_v(str, pool, style, tagname, ap);
+ va_end(ap);
+}
+
+
+void svn_xml_make_close_tag(svn_stringbuf_t **str,
+ apr_pool_t *pool,
+ const char *tagname)
+{
+ if (*str == NULL)
+ *str = svn_stringbuf_create_empty(pool);
+
+ svn_stringbuf_appendcstr(*str, "</");
+ svn_stringbuf_appendcstr(*str, tagname);
+ svn_stringbuf_appendcstr(*str, ">\n");
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/README b/contrib/subversion/subversion/libsvn_wc/README
new file mode 100644
index 0000000..b5fc529
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/README
@@ -0,0 +1,195 @@
+ Oh Most High and Fragrant Emacs, please be in -*- text -*- mode!
+
+##############################################################################
+### The vast majority of this file is completely out-of-date as a result ###
+### of the ongoing work known as WC-NG. Please consult that documentation ###
+### for a more relevant and complete reference. ###
+### (See the files in notes/wc-ng ) ###
+##############################################################################
+
+
+This is the library described in the section "The working copy
+management library" of svn-design.texi. It performs local operations
+in the working copy, tweaking administrative files and versioned data.
+It does not communicate directly with a repository; instead, other
+libraries that do talk to the repository call into this library to
+make queries and changes in the working copy.
+
+Note: This document attempts to describe (insofar as development is still
+a moving target) the current working copy layout. For historic layouts,
+consulting the versioned history of this file (yay version control!)
+
+
+The Problem We're Solving
+-------------------------
+
+The working copy is arranged as a directory tree, which, at checkout,
+mirrors a tree rooted at some node in the repository. Over time, the
+working copy accumulates uncommitted changes, some of which may affect
+its tree layout. By commit time, the working copy's layout could be
+arbitrarily different from the repository tree on which it was based.
+
+Furthermore, updates/commits do not always involve the entire tree, so
+it is possible for the working copy to go a very long time without
+being a perfect mirror of some tree in the repository.
+
+
+One Way We're Not Solving It
+----------------------------
+
+Updates and commits are about merging two trees that share a common
+ancestor, but have diverged since that ancestor. In real life, one of
+the trees comes from the working copy, the other from the repository.
+But when thinking about how to merge two such trees, we can ignore the
+question of which is the working copy and which is the repository,
+because the principles involved are symmetrical.
+
+Why do we say symmetrical?
+
+It's tempting to think of a change as being either "from" the working
+copy or "in" the repository. But the true source of a change is some
+committer -- each change represents some developer's intention toward
+a file or a tree, and a conflict is what happens when two intentions
+are incompatible (or their compatibility cannot be automatically
+determined).
+
+It doesn't matter in what order the intentions were discovered --
+which has already made it into the repository versus which exists only
+in someone's working copy. Incompatibility is incompatibility,
+independent of timing.
+
+In fact, a working copy can be viewed as a "branch" off the
+repository, and the changes committed in the repository *since* then
+represent another, divergent branch. Thus, every update or commit is
+a general branch-merge problem:
+
+ - An update is an attempt to merge the repository's branch into the
+ working copy's branch, and the attempt may fail wholly or
+ partially depending on the number of conflicts.
+
+ - A commit is an attempt to merge the working copy's branch into
+ the repository. The exact same algorithm is used as with
+ updates, the only difference being that a commit must succeed
+ completely or not at all. That last condition is merely a
+ usability decision: the repository tree is shared by many
+ people, so folding both sides of a conflict into it to aid
+ resolution would actually make it less usable, not more. On the
+ other hand, representing both sides of a conflict in a working
+ copy is often helpful to the person who owns that copy.
+
+So below we consider the general problem of how to merge two trees
+that have a common ancestor. The concrete tree layout discussed will
+be that of the working copy, because this library needs to know
+exactly how to massage a working copy from one state to another.
+
+
+Structure of the Working Copy
+-----------------------------
+
+Working copy meta-information is stored in a single .svn/ subdirectory, in
+the root of a given working copy. For the purposes of storage, directories
+pull in through the use of svn:externals are considered separate working
+copies.
+
+ .svn/wc.db /* SQLite database containing node metadata. */
+ pristine/ /* Sharded directory containing base files. */
+ tmp/ /* Local tmp area. */
+
+`wc.db':
+ A self-contained SQLite database containing all the metadata Subversion
+ needs to track for this working copy. The schema is described by
+ libsvn_wc/wc-metadata.sql.
+
+`pristine':
+ Each file in the working copy has a corresponding unmodified version in
+ the .svn/pristine subdirectory. This files are stored by the SHA-1
+ hash of their contents, sharded into 256 subdirectories based upon the
+ first two characters of the hex expansion of the hash. In this way,
+ multiple identical files can share the same pristine representation.
+
+ Pristines are used for sending diffs back to the server, etc.
+
+
+How the client applies an update delta
+--------------------------------------
+
+Updating is more than just bringing changes down from the repository;
+it's also folding those changes into the working copy. Getting the
+right changes is the easy part -- folding them in is hard.
+
+Before we examine how Subversion handles this, let's look at what CVS
+does:
+
+ 1. Unmodified portions of the working copy are simply brought
+ up-to-date. The server sends a forward diff, the client applies
+ it.
+
+ 2. Locally modified portions are "merged", where possible. That
+ is, the changes from the repository are incorporated into the
+ local changes in an intelligent way (if the diff application
+ succeeds, then no conflict, else go to 3...)
+
+ 3. Where merging is not possible, a conflict is flagged, and *both*
+ sides of the conflict are folded into the local file in such a
+ way that it's easy for the developer to figure out what
+ happened. (And the old locally-modified file is saved under a
+ temp name, just in case.)
+
+It would be nice for Subversion to do things this way too;
+unfortunately, that's not possible in every case.
+
+CVS has a wonderfully simplifying limitation: it doesn't version
+directories, so never has tree-structure conflicts. Given that only
+textual conflicts are possible, there is usually a natural way to
+express both sides of a conflict -- just include the opposing texts
+inside the file, delimited with conflict markers. (Or for binary
+files, make both revisions available under temporary names.)
+
+While Subversion can behave the same way for textual conflicts, the
+situation is more complex for trees. There is sometimes no way for a
+working copy to reflect both sides of a tree conflict without being
+more confusing than helpful. How does one put "conflict markers" into
+a directory, especially when what was a directory might now be a file,
+or vice-versa?
+
+Therefore, while Subversion does everything it can to fold conflicts
+intelligently (doing at least as well as CVS does), in extreme cases
+it is acceptable for the Subversion client to punt, saying in effect
+"Your working copy is too out of whack; please move it aside, check
+out a fresh one, redo your changes in the fresh copy, and commit from
+that." (This response may also apply to subtrees of the working copy,
+of course).
+
+Usually it offers more detail than that, too. In addition to the
+overall out-of-whackness message, it can say "Directory foo was
+renamed to bar, conflicting with your new file bar; file blah was
+deleted, conflicting with your local change to file blah, ..." and so
+on. The important thing is that these are informational only -- they
+tell the user what's wrong, but they don't try to fix it
+automatically.
+
+All this is purely a matter of *client-side* intelligence. Nothing in
+the repository logic or protocol affects the client's ability to fold
+conflicts. So as we get smarter, and/or as there is demand for more
+informative conflicting updates, the client's behavior can improve and
+punting can become a rare event. We should start out with a _simple_
+conflict-folding algorithm initially, though.
+
+
+Text and Property Components
+----------------------------
+
+A Subversion working copy keeps track of *two* forks per file, much
+like the way MacOS files have "data" forks and "resource" forks. Each
+file under revision control has its "text" and "properties" tracked
+with different timestamps and different conflict (reject) files. In
+this vein, each file's status-line has two columns which describe the
+file's state.
+
+Examples:
+
+ -- glub.c --> glub.c is completely up-to-date.
+ U- foo.c --> foo.c's textual component was updated.
+ -M bar.c --> bar.c's properties have been locally modified
+ UC baz.c --> baz.c has had both components patched, but a
+ local property change is creating a conflict.
diff --git a/contrib/subversion/subversion/libsvn_wc/adm_crawler.c b/contrib/subversion/subversion/libsvn_wc/adm_crawler.c
new file mode 100644
index 0000000..e5935a2
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/adm_crawler.c
@@ -0,0 +1,1239 @@
+/*
+ * adm_crawler.c: report local WC mods to an Editor.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_hash.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_wc.h"
+#include "svn_io.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+
+#include "private/svn_wc_private.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "translate.h"
+#include "workqueue.h"
+#include "conflicts.h"
+
+#include "svn_private_config.h"
+
+
+/* Helper for report_revisions_and_depths().
+
+ Perform an atomic restoration of the file LOCAL_ABSPATH; that is, copy
+ the file's text-base to the administrative tmp area, and then move
+ that file to LOCAL_ABSPATH with possible translations/expansions. If
+ USE_COMMIT_TIMES is set, then set working file's timestamp to
+ last-commit-time. Either way, set entry-timestamp to match that of
+ the working file when all is finished.
+
+ If MARK_RESOLVED_TEXT_CONFLICT is TRUE, mark as resolved any existing
+ text conflict on LOCAL_ABSPATH.
+
+ Not that a valid access baton with a write lock to the directory of
+ LOCAL_ABSPATH must be available in DB.*/
+static svn_error_t *
+restore_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t mark_resolved_text_conflict,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *work_item;
+
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item,
+ db, local_abspath,
+ NULL /* source_abspath */,
+ use_commit_times,
+ TRUE /* record_fileinfo */,
+ scratch_pool, scratch_pool));
+ /* ### we need an existing path for wq_add. not entirely WRI_ABSPATH yet */
+ SVN_ERR(svn_wc__db_wq_add(db,
+ svn_dirent_dirname(local_abspath, scratch_pool),
+ work_item, scratch_pool));
+
+ /* Run the work item immediately. */
+ SVN_ERR(svn_wc__wq_run(db, local_abspath,
+ NULL, NULL, /* ### nice to have cancel_func/baton */
+ scratch_pool));
+
+ /* Remove any text conflict */
+ if (mark_resolved_text_conflict)
+ SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_restore(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t use_commit_times,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_node_kind_t disk_kind;
+ const svn_checksum_t *checksum;
+
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
+
+ if (disk_kind != svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
+ _("The existing node '%s' can not be restored."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &checksum, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (status != svn_wc__db_status_normal
+ && !((status == svn_wc__db_status_added
+ || status == svn_wc__db_status_incomplete)
+ && (kind == svn_node_dir
+ || (kind == svn_node_file && checksum != NULL)
+ /* || (kind == svn_node_symlink && target)*/)))
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("The node '%s' can not be restored."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ if (kind == svn_node_file || kind == svn_node_symlink)
+ SVN_ERR(restore_file(wc_ctx->db, local_abspath, use_commit_times,
+ FALSE /*mark_resolved_text_conflict*/,
+ scratch_pool));
+ else
+ SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Try to restore LOCAL_ABSPATH of node type KIND and if successfull,
+ notify that the node is restored. Use DB for accessing the working copy.
+ If USE_COMMIT_TIMES is set, then set working file's timestamp to
+ last-commit-time.
+
+ This function does all temporary allocations in SCRATCH_POOL
+ */
+static svn_error_t *
+restore_node(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ svn_boolean_t use_commit_times,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ if (kind == svn_node_file || kind == svn_node_symlink)
+ {
+ /* Recreate file from text-base; mark any text conflict as resolved */
+ SVN_ERR(restore_file(db, local_abspath, use_commit_times,
+ TRUE /*mark_resolved_text_conflict*/,
+ scratch_pool));
+ }
+ else if (kind == svn_node_dir)
+ {
+ /* Recreating a directory is just a mkdir */
+ SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
+ }
+
+ /* ... report the restoration to the caller. */
+ if (notify_func != NULL)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_restore,
+ scratch_pool);
+ notify->kind = svn_node_file;
+ (*notify_func)(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* The recursive crawler that describes a mixed-revision working
+ copy to an RA layer. Used to initiate updates.
+
+ This is a depth-first recursive walk of the children of DIR_ABSPATH
+ (not including DIR_ABSPATH itself) using DB. Look at each node and
+ check if its revision is different than DIR_REV. If so, report this
+ fact to REPORTER. If a node has a different URL than expected, or
+ a different depth than its parent, report that to REPORTER.
+
+ Report DIR_ABSPATH to the reporter as REPORT_RELPATH.
+
+ Alternatively, if REPORT_EVERYTHING is set, then report all
+ children unconditionally.
+
+ DEPTH is actually the *requested* depth for the update-like
+ operation for which we are reporting working copy state. However,
+ certain requested depths affect the depth of the report crawl. For
+ example, if the requested depth is svn_depth_empty, there's no
+ point descending into subdirs, no matter what their depths. So:
+
+ If DEPTH is svn_depth_empty, don't report any files and don't
+ descend into any subdirs. If svn_depth_files, report files but
+ still don't descend into subdirs. If svn_depth_immediates, report
+ files, and report subdirs themselves but not their entries. If
+ svn_depth_infinity or svn_depth_unknown, report everything all the
+ way down. (That last sentence might sound counterintuitive, but
+ since you can't go deeper than the local ambient depth anyway,
+ requesting svn_depth_infinity really means "as deep as the various
+ parts of this working copy go". Of course, the information that
+ comes back from the server will be different for svn_depth_unknown
+ than for svn_depth_infinity.)
+
+ DIR_REPOS_RELPATH, DIR_REPOS_ROOT and DIR_DEPTH are the repository
+ relative path, the repository root and depth stored on the directory,
+ passed here to avoid another database query.
+
+ DEPTH_COMPATIBILITY_TRICK means the same thing here as it does
+ in svn_wc_crawl_revisions5().
+
+ If RESTORE_FILES is set, then unexpectedly missing working files
+ will be restored from text-base and NOTIFY_FUNC/NOTIFY_BATON
+ will be called to report the restoration. USE_COMMIT_TIMES is
+ passed to restore_file() helper. */
+static svn_error_t *
+report_revisions_and_depths(svn_wc__db_t *db,
+ const char *dir_abspath,
+ const char *report_relpath,
+ svn_revnum_t dir_rev,
+ const char *dir_repos_relpath,
+ const char *dir_repos_root,
+ svn_depth_t dir_depth,
+ const svn_ra_reporter3_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_depth_t depth,
+ svn_boolean_t honor_depth_exclude,
+ svn_boolean_t depth_compatibility_trick,
+ svn_boolean_t report_everything,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *base_children;
+ apr_hash_t *dirents;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ svn_error_t *err;
+
+
+ /* Get both the SVN Entries and the actual on-disk entries. Also
+ notice that we're picking up hidden entries too (read_children never
+ hides children). */
+ SVN_ERR(svn_wc__db_base_get_children_info(&base_children, db, dir_abspath,
+ scratch_pool, iterpool));
+
+ if (restore_files)
+ {
+ err = svn_io_get_dirents3(&dirents, dir_abspath, TRUE,
+ scratch_pool, scratch_pool);
+
+ if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ {
+ svn_error_clear(err);
+ /* There is no directory, and if we could create the directory
+ we would have already created it when walking the parent
+ directory */
+ restore_files = FALSE;
+ dirents = NULL;
+ }
+ else
+ SVN_ERR(err);
+ }
+ else
+ dirents = NULL;
+
+ /*** Do the real reporting and recursing. ***/
+
+ /* Looping over current directory's BASE children: */
+ for (hi = apr_hash_first(scratch_pool, base_children);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ const char *child = svn__apr_hash_index_key(hi);
+ const char *this_report_relpath;
+ const char *this_abspath;
+ svn_boolean_t this_switched = FALSE;
+ struct svn_wc__db_base_info_t *ths = svn__apr_hash_index_val(hi);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Clear the iteration subpool here because the loop has a bunch
+ of 'continue' jump statements. */
+ svn_pool_clear(iterpool);
+
+ /* Compute the paths and URLs we need. */
+ this_report_relpath = svn_relpath_join(report_relpath, child, iterpool);
+ this_abspath = svn_dirent_join(dir_abspath, child, iterpool);
+
+ /*** File Externals **/
+ if (ths->update_root)
+ {
+ /* File externals are ... special. We ignore them. */;
+ continue;
+ }
+
+ /* First check for exclusion */
+ if (ths->status == svn_wc__db_status_excluded)
+ {
+ if (honor_depth_exclude)
+ {
+ /* Report the excluded path, no matter whether report_everything
+ flag is set. Because the report_everything flag indicates
+ that the server will treat the wc as empty and thus push
+ full content of the files/subdirs. But we want to prevent the
+ server from pushing the full content of this_path at us. */
+
+ /* The server does not support link_path report on excluded
+ path. We explicitly prohibit this situation in
+ svn_wc_crop_tree(). */
+ SVN_ERR(reporter->set_path(report_baton,
+ this_report_relpath,
+ dir_rev,
+ svn_depth_exclude,
+ FALSE,
+ NULL,
+ iterpool));
+ }
+ else
+ {
+ /* We want to pull in the excluded target. So, report it as
+ deleted, and server will respond properly. */
+ if (! report_everything)
+ SVN_ERR(reporter->delete_path(report_baton,
+ this_report_relpath, iterpool));
+ }
+ continue;
+ }
+
+ /*** The Big Tests: ***/
+ if (ths->status == svn_wc__db_status_server_excluded
+ || ths->status == svn_wc__db_status_not_present)
+ {
+ /* If the entry is 'absent' or 'not-present', make sure the server
+ knows it's gone...
+ ...unless we're reporting everything, in which case we're
+ going to report it missing later anyway.
+
+ This instructs the server to send it back to us, if it is
+ now available (an addition after a not-present state), or if
+ it is now authorized (change in authz for the absent item). */
+ if (! report_everything)
+ SVN_ERR(reporter->delete_path(report_baton, this_report_relpath,
+ iterpool));
+ continue;
+ }
+
+ /* Is the entry NOT on the disk? We may be able to restore it. */
+ if (restore_files
+ && svn_hash_gets(dirents, child) == NULL)
+ {
+ svn_wc__db_status_t wrk_status;
+ svn_node_kind_t wrk_kind;
+ const svn_checksum_t *checksum;
+
+ SVN_ERR(svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ &checksum, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ db, this_abspath, iterpool, iterpool));
+
+ if ((wrk_status == svn_wc__db_status_normal
+ || wrk_status == svn_wc__db_status_added
+ || wrk_status == svn_wc__db_status_incomplete)
+ && (wrk_kind == svn_node_dir || checksum))
+ {
+ svn_node_kind_t dirent_kind;
+
+ /* It is possible on a case insensitive system that the
+ entry is not really missing, but just cased incorrectly.
+ In this case we can't overwrite it with the pristine
+ version */
+ SVN_ERR(svn_io_check_path(this_abspath, &dirent_kind, iterpool));
+
+ if (dirent_kind == svn_node_none)
+ {
+ SVN_ERR(restore_node(db, this_abspath, wrk_kind,
+ use_commit_times, notify_func,
+ notify_baton, iterpool));
+ }
+ }
+ }
+
+ /* And finally prepare for reporting */
+ if (!ths->repos_relpath)
+ {
+ ths->repos_relpath = svn_relpath_join(dir_repos_relpath, child,
+ iterpool);
+ }
+ else
+ {
+ const char *childname
+ = svn_relpath_skip_ancestor(dir_repos_relpath, ths->repos_relpath);
+
+ if (childname == NULL || strcmp(childname, child) != 0)
+ {
+ this_switched = TRUE;
+ }
+ }
+
+ /* Tweak THIS_DEPTH to a useful value. */
+ if (ths->depth == svn_depth_unknown)
+ ths->depth = svn_depth_infinity;
+
+ /*** Files ***/
+ if (ths->kind == svn_node_file
+ || ths->kind == svn_node_symlink)
+ {
+ if (report_everything)
+ {
+ /* Report the file unconditionally, one way or another. */
+ if (this_switched)
+ SVN_ERR(reporter->link_path(report_baton,
+ this_report_relpath,
+ svn_path_url_add_component2(
+ dir_repos_root,
+ ths->repos_relpath, iterpool),
+ ths->revnum,
+ ths->depth,
+ FALSE,
+ ths->lock ? ths->lock->token : NULL,
+ iterpool));
+ else
+ SVN_ERR(reporter->set_path(report_baton,
+ this_report_relpath,
+ ths->revnum,
+ ths->depth,
+ FALSE,
+ ths->lock ? ths->lock->token : NULL,
+ iterpool));
+ }
+
+ /* Possibly report a disjoint URL ... */
+ else if (this_switched)
+ SVN_ERR(reporter->link_path(report_baton,
+ this_report_relpath,
+ svn_path_url_add_component2(
+ dir_repos_root,
+ ths->repos_relpath, iterpool),
+ ths->revnum,
+ ths->depth,
+ FALSE,
+ ths->lock ? ths->lock->token : NULL,
+ iterpool));
+ /* ... or perhaps just a differing revision or lock token,
+ or the mere presence of the file in a depth-empty dir. */
+ else if (ths->revnum != dir_rev
+ || ths->lock
+ || dir_depth == svn_depth_empty)
+ SVN_ERR(reporter->set_path(report_baton,
+ this_report_relpath,
+ ths->revnum,
+ ths->depth,
+ FALSE,
+ ths->lock ? ths->lock->token : NULL,
+ iterpool));
+ } /* end file case */
+
+ /*** Directories (in recursive mode) ***/
+ else if (ths->kind == svn_node_dir
+ && (depth > svn_depth_files
+ || depth == svn_depth_unknown))
+ {
+ svn_boolean_t is_incomplete;
+ svn_boolean_t start_empty;
+ svn_depth_t report_depth = ths->depth;
+
+ is_incomplete = (ths->status == svn_wc__db_status_incomplete);
+ start_empty = is_incomplete;
+
+ if (!SVN_DEPTH_IS_RECURSIVE(depth))
+ report_depth = svn_depth_empty;
+
+ /* When a <= 1.6 working copy is upgraded without some of its
+ subdirectories we miss some information in the database. If we
+ report the revision as -1, the update editor will receive an
+ add_directory() while it still knows the directory.
+
+ This would raise strange tree conflicts and probably assertions
+ as it would a BASE vs BASE conflict */
+ if (is_incomplete && !SVN_IS_VALID_REVNUM(ths->revnum))
+ ths->revnum = dir_rev;
+
+ if (depth_compatibility_trick
+ && ths->depth <= svn_depth_files
+ && depth > ths->depth)
+ {
+ start_empty = TRUE;
+ }
+
+ if (report_everything)
+ {
+ /* Report the dir unconditionally, one way or another... */
+ if (this_switched)
+ SVN_ERR(reporter->link_path(report_baton,
+ this_report_relpath,
+ svn_path_url_add_component2(
+ dir_repos_root,
+ ths->repos_relpath, iterpool),
+ ths->revnum,
+ report_depth,
+ start_empty,
+ ths->lock ? ths->lock->token
+ : NULL,
+ iterpool));
+ else
+ SVN_ERR(reporter->set_path(report_baton,
+ this_report_relpath,
+ ths->revnum,
+ report_depth,
+ start_empty,
+ ths->lock ? ths->lock->token : NULL,
+ iterpool));
+ }
+ else if (this_switched)
+ {
+ /* ...or possibly report a disjoint URL ... */
+ SVN_ERR(reporter->link_path(report_baton,
+ this_report_relpath,
+ svn_path_url_add_component2(
+ dir_repos_root,
+ ths->repos_relpath, iterpool),
+ ths->revnum,
+ report_depth,
+ start_empty,
+ ths->lock ? ths->lock->token : NULL,
+ iterpool));
+ }
+ else if (ths->revnum != dir_rev
+ || ths->lock
+ || is_incomplete
+ || dir_depth == svn_depth_empty
+ || dir_depth == svn_depth_files
+ || (dir_depth == svn_depth_immediates
+ && ths->depth != svn_depth_empty)
+ || (ths->depth < svn_depth_infinity
+ && SVN_DEPTH_IS_RECURSIVE(depth)))
+ {
+ /* ... or perhaps just a differing revision, lock token,
+ incomplete subdir, the mere presence of the directory
+ in a depth-empty or depth-files dir, or if the parent
+ dir is at depth-immediates but the child is not at
+ depth-empty. Also describe shallow subdirs if we are
+ trying to set depth to infinity. */
+ SVN_ERR(reporter->set_path(report_baton,
+ this_report_relpath,
+ ths->revnum,
+ report_depth,
+ start_empty,
+ ths->lock ? ths->lock->token : NULL,
+ iterpool));
+ }
+
+ /* Finally, recurse if necessary and appropriate. */
+ if (SVN_DEPTH_IS_RECURSIVE(depth))
+ {
+ const char *repos_relpath = ths->repos_relpath;
+
+ if (repos_relpath == NULL)
+ {
+ repos_relpath = svn_relpath_join(dir_repos_relpath, child,
+ iterpool);
+ }
+
+ SVN_ERR(report_revisions_and_depths(db,
+ this_abspath,
+ this_report_relpath,
+ ths->revnum,
+ repos_relpath,
+ dir_repos_root,
+ ths->depth,
+ reporter, report_baton,
+ restore_files, depth,
+ honor_depth_exclude,
+ depth_compatibility_trick,
+ start_empty,
+ use_commit_times,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool));
+ }
+ } /* end directory case */
+ } /* end main entries loop */
+
+ /* We're done examining this dir's entries, so free everything. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/*------------------------------------------------------------------*/
+/*** Public Interfaces ***/
+
+
+svn_error_t *
+svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_ra_reporter3_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_depth_t depth,
+ svn_boolean_t honor_depth_exclude,
+ svn_boolean_t depth_compatibility_trick,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_error_t *fserr, *err;
+ svn_revnum_t target_rev = SVN_INVALID_REVNUM;
+ svn_boolean_t start_empty;
+ svn_wc__db_status_t status;
+ svn_node_kind_t target_kind;
+ const char *repos_relpath, *repos_root_url;
+ svn_depth_t target_depth;
+ svn_wc__db_lock_t *target_lock;
+ svn_node_kind_t disk_kind;
+ svn_depth_t report_depth;
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Get the base rev, which is the first revnum that entries will be
+ compared to, and some other WC info about the target. */
+ err = svn_wc__db_base_get_info(&status, &target_kind, &target_rev,
+ &repos_relpath, &repos_root_url,
+ NULL, NULL, NULL, NULL, &target_depth,
+ NULL, NULL, &target_lock,
+ NULL, NULL, NULL,
+ db, local_abspath, scratch_pool,
+ scratch_pool);
+
+ if (err
+ || (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_incomplete))
+ {
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ /* We don't know about this node, so all we have to do is tell
+ the reporter that we don't know this node.
+
+ But first we have to start the report by sending some basic
+ information for the root. */
+
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_infinity;
+
+ SVN_ERR(reporter->set_path(report_baton, "", 0, depth, FALSE,
+ NULL, scratch_pool));
+ SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
+
+ /* Finish the report, which causes the update editor to be
+ driven. */
+ SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+ }
+
+ if (target_depth == svn_depth_unknown)
+ target_depth = svn_depth_infinity;
+
+ start_empty = (status == svn_wc__db_status_incomplete);
+ if (depth_compatibility_trick
+ && target_depth <= svn_depth_immediates
+ && depth > target_depth)
+ {
+ start_empty = TRUE;
+ }
+
+ if (restore_files)
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
+ else
+ disk_kind = svn_node_unknown;
+
+ /* Determine if there is a missing node that should be restored */
+ if (restore_files
+ && disk_kind == svn_node_none)
+ {
+ svn_wc__db_status_t wrk_status;
+ svn_node_kind_t wrk_kind;
+ const svn_checksum_t *checksum;
+
+ err = svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, &checksum, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool);
+
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ wrk_status = svn_wc__db_status_not_present;
+ wrk_kind = svn_node_file;
+ }
+ else
+ SVN_ERR(err);
+
+ if ((wrk_status == svn_wc__db_status_normal
+ || wrk_status == svn_wc__db_status_added
+ || wrk_status == svn_wc__db_status_incomplete)
+ && (wrk_kind == svn_node_dir || checksum))
+ {
+ SVN_ERR(restore_node(wc_ctx->db, local_abspath,
+ wrk_kind, use_commit_times,
+ notify_func, notify_baton,
+ scratch_pool));
+ }
+ }
+
+ {
+ report_depth = target_depth;
+
+ if (honor_depth_exclude
+ && depth != svn_depth_unknown
+ && depth < target_depth)
+ report_depth = depth;
+
+ /* The first call to the reporter merely informs it that the
+ top-level directory being updated is at BASE_REV. Its PATH
+ argument is ignored. */
+ SVN_ERR(reporter->set_path(report_baton, "", target_rev, report_depth,
+ start_empty, NULL, scratch_pool));
+ }
+ if (target_kind == svn_node_dir)
+ {
+ if (depth != svn_depth_empty)
+ {
+ /* Recursively crawl ROOT_DIRECTORY and report differing
+ revisions. */
+ err = report_revisions_and_depths(wc_ctx->db,
+ local_abspath,
+ "",
+ target_rev,
+ repos_relpath,
+ repos_root_url,
+ report_depth,
+ reporter, report_baton,
+ restore_files, depth,
+ honor_depth_exclude,
+ depth_compatibility_trick,
+ start_empty,
+ use_commit_times,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool);
+ if (err)
+ goto abort_report;
+ }
+ }
+
+ else if (target_kind == svn_node_file || target_kind == svn_node_symlink)
+ {
+ const char *parent_abspath, *base;
+ svn_wc__db_status_t parent_status;
+ const char *parent_repos_relpath;
+
+ svn_dirent_split(&parent_abspath, &base, local_abspath,
+ scratch_pool);
+
+ /* We can assume a file is in the same repository as its parent
+ directory, so we only look at the relpath. */
+ err = svn_wc__db_base_get_info(&parent_status, NULL, NULL,
+ &parent_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, parent_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ goto abort_report;
+
+ if (strcmp(repos_relpath,
+ svn_relpath_join(parent_repos_relpath, base,
+ scratch_pool)) != 0)
+ {
+ /* This file is disjoint with respect to its parent
+ directory. Since we are looking at the actual target of
+ the report (not some file in a subdirectory of a target
+ directory), and that target is a file, we need to pass an
+ empty string to link_path. */
+ err = reporter->link_path(report_baton,
+ "",
+ svn_path_url_add_component2(
+ repos_root_url,
+ repos_relpath,
+ scratch_pool),
+ target_rev,
+ svn_depth_infinity,
+ FALSE,
+ target_lock ? target_lock->token : NULL,
+ scratch_pool);
+ if (err)
+ goto abort_report;
+ }
+ else if (target_lock)
+ {
+ /* If this entry is a file node, we just want to report that
+ node's revision. Since we are looking at the actual target
+ of the report (not some file in a subdirectory of a target
+ directory), and that target is a file, we need to pass an
+ empty string to set_path. */
+ err = reporter->set_path(report_baton, "", target_rev,
+ svn_depth_infinity,
+ FALSE,
+ target_lock ? target_lock->token : NULL,
+ scratch_pool);
+ if (err)
+ goto abort_report;
+ }
+ }
+
+ /* Finish the report, which causes the update editor to be driven. */
+ return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
+
+ abort_report:
+ /* Clean up the fs transaction. */
+ if ((fserr = reporter->abort_report(report_baton, scratch_pool)))
+ {
+ fserr = svn_error_quick_wrap(fserr, _("Error aborting report"));
+ svn_error_compose(err, fserr);
+ }
+ return svn_error_trace(err);
+}
+
+/*** Copying stream ***/
+
+/* A copying stream is a bit like the unix tee utility:
+ *
+ * It reads the SOURCE when asked for data and while returning it,
+ * also writes the same data to TARGET.
+ */
+struct copying_stream_baton
+{
+ /* Stream to read input from. */
+ svn_stream_t *source;
+
+ /* Stream to write all data read to. */
+ svn_stream_t *target;
+};
+
+
+/* */
+static svn_error_t *
+read_handler_copy(void *baton, char *buffer, apr_size_t *len)
+{
+ struct copying_stream_baton *btn = baton;
+
+ SVN_ERR(svn_stream_read(btn->source, buffer, len));
+
+ return svn_stream_write(btn->target, buffer, len);
+}
+
+/* */
+static svn_error_t *
+close_handler_copy(void *baton)
+{
+ struct copying_stream_baton *btn = baton;
+
+ SVN_ERR(svn_stream_close(btn->target));
+ return svn_stream_close(btn->source);
+}
+
+
+/* Return a stream - allocated in POOL - which reads its input
+ * from SOURCE and, while returning that to the caller, at the
+ * same time writes that to TARGET.
+ */
+static svn_stream_t *
+copying_stream(svn_stream_t *source,
+ svn_stream_t *target,
+ apr_pool_t *pool)
+{
+ struct copying_stream_baton *baton;
+ svn_stream_t *stream;
+
+ baton = apr_palloc(pool, sizeof (*baton));
+ baton->source = source;
+ baton->target = target;
+
+ stream = svn_stream_create(baton, pool);
+ svn_stream_set_read(stream, read_handler_copy);
+ svn_stream_set_close(stream, close_handler_copy);
+
+ return stream;
+}
+
+
+/* Set *STREAM to a stream from which the caller can read the pristine text
+ * of the working version of the file at LOCAL_ABSPATH. If the working
+ * version of LOCAL_ABSPATH has no pristine text because it is locally
+ * added, set *STREAM to an empty stream. If the working version of
+ * LOCAL_ABSPATH is not a file, return an error.
+ *
+ * Set *EXPECTED_MD5_CHECKSUM to the recorded MD5 checksum.
+ *
+ * Arrange for the actual checksum of the text to be calculated and written
+ * into *ACTUAL_MD5_CHECKSUM when the stream is read.
+ */
+static svn_error_t *
+read_and_checksum_pristine_text(svn_stream_t **stream,
+ const svn_checksum_t **expected_md5_checksum,
+ svn_checksum_t **actual_md5_checksum,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *base_stream;
+
+ SVN_ERR(svn_wc__get_pristine_contents(&base_stream, NULL, db, local_abspath,
+ result_pool, scratch_pool));
+ if (base_stream == NULL)
+ {
+ base_stream = svn_stream_empty(result_pool);
+ *expected_md5_checksum = NULL;
+ *actual_md5_checksum = NULL;
+ }
+ else
+ {
+ const svn_checksum_t *expected_md5;
+
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, &expected_md5,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ if (expected_md5 == NULL)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Pristine checksum for file '%s' is missing"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ if (expected_md5->kind != svn_checksum_md5)
+ SVN_ERR(svn_wc__db_pristine_get_md5(&expected_md5, db, local_abspath,
+ expected_md5,
+ result_pool, scratch_pool));
+ *expected_md5_checksum = expected_md5;
+
+ /* Arrange to set ACTUAL_MD5_CHECKSUM to the MD5 of what is *actually*
+ found when the base stream is read. */
+ base_stream = svn_stream_checksummed2(base_stream, actual_md5_checksum,
+ NULL, svn_checksum_md5, TRUE,
+ result_pool);
+ }
+
+ *stream = base_stream;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__internal_transmit_text_deltas(const char **tempfile,
+ const svn_checksum_t **new_text_base_md5_checksum,
+ const svn_checksum_t **new_text_base_sha1_checksum,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t fulltext,
+ const svn_delta_editor_t *editor,
+ void *file_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_txdelta_window_handler_t handler;
+ void *wh_baton;
+ const svn_checksum_t *expected_md5_checksum; /* recorded MD5 of BASE_S. */
+ svn_checksum_t *verify_checksum; /* calc'd MD5 of BASE_STREAM */
+ svn_checksum_t *local_md5_checksum; /* calc'd MD5 of LOCAL_STREAM */
+ svn_checksum_t *local_sha1_checksum; /* calc'd SHA1 of LOCAL_STREAM */
+ const char *new_pristine_tmp_abspath;
+ svn_error_t *err;
+ svn_stream_t *base_stream; /* delta source */
+ svn_stream_t *local_stream; /* delta target: LOCAL_ABSPATH transl. to NF */
+
+ /* Translated input */
+ SVN_ERR(svn_wc__internal_translated_stream(&local_stream, db,
+ local_abspath, local_abspath,
+ SVN_WC_TRANSLATE_TO_NF,
+ scratch_pool, scratch_pool));
+
+ /* If the caller wants a copy of the working file translated to
+ * repository-normal form, make the copy by tee-ing the stream and set
+ * *TEMPFILE to the path to it. This is only needed for the 1.6 API,
+ * 1.7 doesn't set TEMPFILE. Even when using the 1.6 API this file
+ * is not used by the functions that would have used it when using
+ * the 1.6 code. It's possible that 3rd party users (if there are any)
+ * might expect this file to be a text-base. */
+ if (tempfile)
+ {
+ svn_stream_t *tempstream;
+
+ /* It can't be the same location as in 1.6 because the admin directory
+ no longer exists. */
+ SVN_ERR(svn_stream_open_unique(&tempstream, tempfile,
+ NULL, svn_io_file_del_none,
+ result_pool, scratch_pool));
+
+ /* Wrap the translated stream with a new stream that writes the
+ translated contents into the new text base file as we read from it.
+ Note that the new text base file will be closed when the new stream
+ is closed. */
+ local_stream = copying_stream(local_stream, tempstream, scratch_pool);
+ }
+ if (new_text_base_sha1_checksum)
+ {
+ svn_stream_t *new_pristine_stream;
+
+ SVN_ERR(svn_wc__open_writable_base(&new_pristine_stream,
+ &new_pristine_tmp_abspath,
+ NULL, &local_sha1_checksum,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ local_stream = copying_stream(local_stream, new_pristine_stream,
+ scratch_pool);
+ }
+
+ /* If sending a full text is requested, or if there is no pristine text
+ * (e.g. the node is locally added), then set BASE_STREAM to an empty
+ * stream and leave EXPECTED_MD5_CHECKSUM and VERIFY_CHECKSUM as NULL.
+ *
+ * Otherwise, set BASE_STREAM to a stream providing the base (source) text
+ * for the delta, set EXPECTED_MD5_CHECKSUM to its stored MD5 checksum,
+ * and arrange for its VERIFY_CHECKSUM to be calculated later. */
+ if (! fulltext)
+ {
+ /* We will be computing a delta against the pristine contents */
+ /* We need the expected checksum to be an MD-5 checksum rather than a
+ * SHA-1 because we want to pass it to apply_textdelta(). */
+ SVN_ERR(read_and_checksum_pristine_text(&base_stream,
+ &expected_md5_checksum,
+ &verify_checksum,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ /* Send a fulltext. */
+ base_stream = svn_stream_empty(scratch_pool);
+ expected_md5_checksum = NULL;
+ verify_checksum = NULL;
+ }
+
+ /* Tell the editor that we're about to apply a textdelta to the
+ file baton; the editor returns to us a window consumer and baton. */
+ {
+ /* apply_textdelta() is working against a base with this checksum */
+ const char *base_digest_hex = NULL;
+
+ if (expected_md5_checksum)
+ /* ### Why '..._display()'? expected_md5_checksum should never be all-
+ * zero, but if it is, we would want to pass NULL not an all-zero
+ * digest to apply_textdelta(), wouldn't we? */
+ base_digest_hex = svn_checksum_to_cstring_display(expected_md5_checksum,
+ scratch_pool);
+
+ SVN_ERR(editor->apply_textdelta(file_baton, base_digest_hex, scratch_pool,
+ &handler, &wh_baton));
+ }
+
+ /* Run diff processing, throwing windows at the handler. */
+ err = svn_txdelta_run(base_stream, local_stream,
+ handler, wh_baton,
+ svn_checksum_md5, &local_md5_checksum,
+ NULL, NULL,
+ scratch_pool, scratch_pool);
+
+ /* Close the two streams to force writing the digest */
+ err = svn_error_compose_create(err, svn_stream_close(base_stream));
+ err = svn_error_compose_create(err, svn_stream_close(local_stream));
+
+ /* If we have an error, it may be caused by a corrupt text base,
+ so check the checksum. */
+ if (expected_md5_checksum && verify_checksum
+ && !svn_checksum_match(expected_md5_checksum, verify_checksum))
+ {
+ /* The entry checksum does not match the actual text
+ base checksum. Extreme badness. Of course,
+ theoretically we could just switch to
+ fulltext transmission here, and everything would
+ work fine; after all, we're going to replace the
+ text base with a new one in a moment anyway, and
+ we'd fix the checksum then. But it's better to
+ error out. People should know that their text
+ bases are getting corrupted, so they can
+ investigate. Other commands could be affected,
+ too, such as `svn diff'. */
+
+ if (tempfile)
+ err = svn_error_compose_create(
+ err,
+ svn_io_remove_file2(*tempfile, TRUE, scratch_pool));
+
+ err = svn_error_compose_create(
+ svn_checksum_mismatch_err(expected_md5_checksum, verify_checksum,
+ scratch_pool,
+ _("Checksum mismatch for text base of '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool)),
+ err);
+
+ return svn_error_create(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, NULL);
+ }
+
+ /* Now, handle that delta transmission error if any, so we can stop
+ thinking about it after this point. */
+ SVN_ERR_W(err, apr_psprintf(scratch_pool,
+ _("While preparing '%s' for commit"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool)));
+
+ if (new_text_base_md5_checksum)
+ *new_text_base_md5_checksum = svn_checksum_dup(local_md5_checksum,
+ result_pool);
+ if (new_text_base_sha1_checksum)
+ {
+ SVN_ERR(svn_wc__db_pristine_install(db, new_pristine_tmp_abspath,
+ local_sha1_checksum,
+ local_md5_checksum,
+ scratch_pool));
+ *new_text_base_sha1_checksum = svn_checksum_dup(local_sha1_checksum,
+ result_pool);
+ }
+
+ /* Close the file baton, and get outta here. */
+ return svn_error_trace(
+ editor->close_file(file_baton,
+ svn_checksum_to_cstring(local_md5_checksum,
+ scratch_pool),
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc_transmit_text_deltas3(const svn_checksum_t **new_text_base_md5_checksum,
+ const svn_checksum_t **new_text_base_sha1_checksum,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t fulltext,
+ const svn_delta_editor_t *editor,
+ void *file_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_wc__internal_transmit_text_deltas(NULL,
+ new_text_base_md5_checksum,
+ new_text_base_sha1_checksum,
+ wc_ctx->db, local_abspath,
+ fulltext, editor,
+ file_baton, result_pool,
+ scratch_pool);
+}
+
+svn_error_t *
+svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_delta_editor_t *editor,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+ apr_array_header_t *propmods;
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath,
+ FALSE /* allow_missing */,
+ FALSE /* show_deleted */,
+ FALSE /* show_hidden */,
+ iterpool));
+
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath, iterpool));
+
+ /* Get an array of local changes by comparing the hashes. */
+ SVN_ERR(svn_wc__internal_propdiff(&propmods, NULL, db, local_abspath,
+ scratch_pool, iterpool));
+
+ /* Apply each local change to the baton */
+ for (i = 0; i < propmods->nelts; i++)
+ {
+ const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t);
+
+ svn_pool_clear(iterpool);
+
+ if (kind == svn_node_file)
+ SVN_ERR(editor->change_file_prop(baton, p->name, p->value,
+ iterpool));
+ else
+ SVN_ERR(editor->change_dir_prop(baton, p->name, p->value,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_delta_editor_t *editor,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_wc__internal_transmit_prop_deltas(wc_ctx->db, local_abspath,
+ editor, baton, scratch_pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/adm_files.c b/contrib/subversion/subversion/libsvn_wc/adm_files.c
new file mode 100644
index 0000000..11ad277
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/adm_files.c
@@ -0,0 +1,584 @@
+/*
+ * adm_files.c: helper routines for handling files & dirs in the
+ * working copy administrative area (creating,
+ * deleting, opening, and closing). This is the only
+ * code that actually knows where administrative
+ * information is kept.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdarg.h>
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_strings.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "entries.h"
+#include "lock.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+/*** File names in the adm area. ***/
+
+/* The default name of the WC admin directory. This name is always
+ checked by svn_wc_is_adm_dir. */
+static const char default_adm_dir_name[] = ".svn";
+
+/* The name that is actually used for the WC admin directory. The
+ commonest case where this won't be the default is in Windows
+ ASP.NET development environments, which used to choke on ".svn". */
+static const char *adm_dir_name = default_adm_dir_name;
+
+
+svn_boolean_t
+svn_wc_is_adm_dir(const char *name, apr_pool_t *pool)
+{
+ return (0 == strcmp(name, adm_dir_name)
+ || 0 == strcmp(name, default_adm_dir_name));
+}
+
+
+const char *
+svn_wc_get_adm_dir(apr_pool_t *pool)
+{
+ return adm_dir_name;
+}
+
+
+svn_error_t *
+svn_wc_set_adm_dir(const char *name, apr_pool_t *pool)
+{
+ /* This is the canonical list of administrative directory names.
+
+ FIXME:
+ An identical list is used in
+ libsvn_subr/opt.c:svn_opt__args_to_target_array(),
+ but that function can't use this list, because that use would
+ create a circular dependency between libsvn_wc and libsvn_subr.
+ Make sure changes to the lists are always synchronized! */
+ static const char *valid_dir_names[] = {
+ default_adm_dir_name,
+ "_svn",
+ NULL
+ };
+
+ const char **dir_name;
+ for (dir_name = valid_dir_names; *dir_name; ++dir_name)
+ if (0 == strcmp(name, *dir_name))
+ {
+ /* Use the pointer to the statically allocated string
+ constant, to avoid potential pool lifetime issues. */
+ adm_dir_name = *dir_name;
+ return SVN_NO_ERROR;
+ }
+ return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
+ _("'%s' is not a valid administrative "
+ "directory name"),
+ svn_dirent_local_style(name, pool));
+}
+
+
+const char *
+svn_wc__adm_child(const char *path,
+ const char *child,
+ apr_pool_t *result_pool)
+{
+ return svn_dirent_join_many(result_pool,
+ path,
+ adm_dir_name,
+ child,
+ NULL);
+}
+
+
+svn_boolean_t
+svn_wc__adm_area_exists(const char *adm_abspath,
+ apr_pool_t *pool)
+{
+ const char *path = svn_wc__adm_child(adm_abspath, NULL, pool);
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ err = svn_io_check_path(path, &kind, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ /* Return early, since kind is undefined in this case. */
+ return FALSE;
+ }
+
+ return kind != svn_node_none;
+}
+
+
+
+/*** Making and using files in the adm area. ***/
+
+
+/* */
+static svn_error_t *
+make_adm_subdir(const char *path,
+ const char *subdir,
+ apr_pool_t *pool)
+{
+ const char *fullpath;
+
+ fullpath = svn_wc__adm_child(path, subdir, pool);
+
+ return svn_io_dir_make(fullpath, APR_OS_DEFAULT, pool);
+}
+
+
+
+/*** Syncing files in the adm area. ***/
+
+
+svn_error_t *
+svn_wc__text_base_path_to_read(const char **result_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const svn_checksum_t *checksum;
+
+ SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, NULL,
+ &checksum, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Sanity */
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Can only get the pristine contents of files; "
+ "'%s' is not a file"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ if (status == svn_wc__db_status_not_present)
+ /* We know that the delete of this node has been committed.
+ This should be the same as if called on an unknown path. */
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Cannot get the pristine contents of '%s' "
+ "because its delete is already committed"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ else if (status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_incomplete)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot get the pristine contents of '%s' "
+ "because it has an unexpected status"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ if (checksum == NULL)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Node '%s' has no pristine text"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ SVN_ERR(svn_wc__db_pristine_get_path(result_abspath, db, local_abspath,
+ checksum,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__get_pristine_contents(svn_stream_t **contents,
+ svn_filesize_t *size,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const svn_checksum_t *sha1_checksum;
+
+ if (size)
+ *size = SVN_INVALID_FILESIZE;
+
+ SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, NULL,
+ &sha1_checksum, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Sanity */
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Can only get the pristine contents of files; "
+ "'%s' is not a file"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ if (status == svn_wc__db_status_added && !sha1_checksum)
+ {
+ /* Simply added. The pristine base does not exist. */
+ *contents = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (status == svn_wc__db_status_not_present)
+ /* We know that the delete of this node has been committed.
+ This should be the same as if called on an unknown path. */
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Cannot get the pristine contents of '%s' "
+ "because its delete is already committed"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ else if (status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_incomplete)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot get the pristine contents of '%s' "
+ "because it has an unexpected status"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ if (sha1_checksum)
+ SVN_ERR(svn_wc__db_pristine_read(contents, size, db, local_abspath,
+ sha1_checksum,
+ result_pool, scratch_pool));
+ else
+ *contents = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Opening and closing files in the adm area. ***/
+
+svn_error_t *
+svn_wc__open_adm_stream(svn_stream_t **stream,
+ const char *dir_abspath,
+ const char *fname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
+
+ local_abspath = svn_wc__adm_child(dir_abspath, fname, scratch_pool);
+ return svn_error_trace(svn_stream_open_readonly(stream, local_abspath,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__open_writable_base(svn_stream_t **stream,
+ const char **temp_base_abspath,
+ svn_checksum_t **md5_checksum,
+ svn_checksum_t **sha1_checksum,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *temp_dir_abspath;
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ SVN_ERR(svn_wc__db_pristine_get_tempdir(&temp_dir_abspath, db, wri_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_unique(stream,
+ temp_base_abspath,
+ temp_dir_abspath,
+ svn_io_file_del_none,
+ result_pool, scratch_pool));
+ if (md5_checksum)
+ *stream = svn_stream_checksummed2(*stream, NULL, md5_checksum,
+ svn_checksum_md5, FALSE, result_pool);
+ if (sha1_checksum)
+ *stream = svn_stream_checksummed2(*stream, NULL, sha1_checksum,
+ svn_checksum_sha1, FALSE, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Checking for and creating administrative subdirs. ***/
+
+
+/* */
+static svn_error_t *
+init_adm_tmp_area(const char *path, apr_pool_t *pool)
+{
+ /* SVN_WC__ADM_TMP */
+ SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TMP, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set up a new adm area for PATH, with REPOS_* as the repos info, and
+ INITIAL_REV as the starting revision. The entries file starts out
+ marked as 'incomplete. The adm area starts out locked; remember to
+ unlock it when done. */
+static svn_error_t *
+init_adm(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t initial_rev,
+ svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ /* First, make an empty administrative area. */
+ SVN_ERR(svn_io_dir_make_hidden(svn_wc__adm_child(local_abspath, NULL, pool),
+ APR_OS_DEFAULT, pool));
+
+ /** Make subdirectories. ***/
+
+ /* SVN_WC__ADM_PRISTINE */
+ SVN_ERR(make_adm_subdir(local_abspath, SVN_WC__ADM_PRISTINE, pool));
+
+ /* ### want to add another directory? do a format bump to ensure that
+ ### all existing working copies get the new directories. or maybe
+ ### create-on-demand (more expensive) */
+
+ /** Init the tmp area. ***/
+ SVN_ERR(init_adm_tmp_area(local_abspath, pool));
+
+ /* Create the SDB. */
+ SVN_ERR(svn_wc__db_init(db, local_abspath,
+ repos_relpath, repos_root_url, repos_uuid,
+ initial_rev, depth,
+ pool));
+
+ /* Stamp ENTRIES and FORMAT files for old clients. */
+ SVN_ERR(svn_io_file_create(svn_wc__adm_child(local_abspath,
+ SVN_WC__ADM_ENTRIES,
+ pool),
+ SVN_WC__NON_ENTRIES_STRING,
+ pool));
+ SVN_ERR(svn_io_file_create(svn_wc__adm_child(local_abspath,
+ SVN_WC__ADM_FORMAT,
+ pool),
+ SVN_WC__NON_ENTRIES_STRING,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_ensure_adm(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *url,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ int format;
+ const char *original_repos_relpath;
+ const char *original_root_url;
+ svn_boolean_t is_op_root;
+ const char *repos_relpath = svn_uri_skip_ancestor(repos_root_url, url,
+ scratch_pool);
+ svn_wc__db_status_t status;
+ const char *db_repos_relpath, *db_repos_root_url, *db_repos_uuid;
+ svn_revnum_t db_revision;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(url != NULL);
+ SVN_ERR_ASSERT(repos_root_url != NULL);
+ SVN_ERR_ASSERT(repos_uuid != NULL);
+ SVN_ERR_ASSERT(repos_relpath != NULL);
+
+ SVN_ERR(svn_wc__internal_check_wc(&format, db, local_abspath, TRUE,
+ scratch_pool));
+
+ /* Early out: we know we're not dealing with an existing wc, so
+ just create one. */
+ if (format == 0)
+ return svn_error_trace(init_adm(db, local_abspath,
+ repos_relpath, repos_root_url, repos_uuid,
+ revision, depth, scratch_pool));
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL,
+ &db_revision, &db_repos_relpath,
+ &db_repos_root_url, &db_repos_uuid,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ &original_repos_relpath, &original_root_url,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, &is_op_root, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ /* When the directory exists and is scheduled for deletion or is not-present
+ * do not check the revision or the URL. The revision can be any
+ * arbitrary revision and the URL may differ if the add is
+ * being driven from a merge which will have a different URL. */
+ if (status != svn_wc__db_status_deleted
+ && status != svn_wc__db_status_not_present)
+ {
+ /* ### Should we match copyfrom_revision? */
+ if (db_revision != revision)
+ return
+ svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Revision %ld doesn't match existing "
+ "revision %ld in '%s'"),
+ revision, db_revision, local_abspath);
+
+ if (!db_repos_root_url)
+ {
+ if (status == svn_wc__db_status_added)
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
+ &db_repos_relpath,
+ &db_repos_root_url,
+ &db_repos_uuid,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__db_scan_base_repos(&db_repos_relpath,
+ &db_repos_root_url,
+ &db_repos_uuid,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ /* The caller gives us a URL which should match the entry. However,
+ some callers compensate for an old problem in entry->url and pass
+ the copyfrom_url instead. See ^/notes/api-errata/1.7/wc002.txt. As
+ a result, we allow the passed URL to match copyfrom_url if it
+ does not match the entry's primary URL. */
+ if (strcmp(db_repos_uuid, repos_uuid)
+ || strcmp(db_repos_root_url, repos_root_url)
+ || !svn_relpath_skip_ancestor(db_repos_relpath, repos_relpath))
+ {
+ if (!is_op_root /* copy_from was set on op-roots only */
+ || original_root_url == NULL
+ || strcmp(original_root_url, repos_root_url)
+ || strcmp(original_repos_relpath, repos_relpath))
+ return
+ svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("URL '%s' (uuid: '%s') doesn't match existing "
+ "URL '%s' (uuid: '%s') in '%s'"),
+ url,
+ db_repos_uuid,
+ svn_path_url_add_component2(db_repos_root_url,
+ db_repos_relpath,
+ scratch_pool),
+ repos_uuid,
+ local_abspath);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_ensure_adm4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *url,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__internal_ensure_adm(wc_ctx->db, local_abspath, url, repos_root_url,
+ repos_uuid, revision, depth, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__adm_destroy(svn_wc__db_t *db,
+ const char *dir_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_wcroot;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
+
+ SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, dir_abspath, scratch_pool));
+
+ /* Well, the coast is clear for blowing away the administrative
+ directory, which also removes remaining locks */
+
+ /* Now close the DB, and we can delete the working copy */
+ if (is_wcroot)
+ {
+ SVN_ERR(svn_wc__db_drop_root(db, dir_abspath, scratch_pool));
+ SVN_ERR(svn_io_remove_dir2(svn_wc__adm_child(dir_abspath, NULL,
+ scratch_pool),
+ FALSE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__adm_cleanup_tmp_area(svn_wc__db_t *db,
+ const char *adm_abspath,
+ apr_pool_t *scratch_pool)
+{
+ const char *tmp_path;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(adm_abspath));
+
+ SVN_ERR(svn_wc__write_check(db, adm_abspath, scratch_pool));
+
+ /* Get the path to the tmp area, and blow it away. */
+ tmp_path = svn_wc__adm_child(adm_abspath, SVN_WC__ADM_TMP, scratch_pool);
+
+ SVN_ERR(svn_io_remove_dir2(tmp_path, TRUE, NULL, NULL, scratch_pool));
+
+ /* Now, rebuild the tmp area. */
+ return svn_error_trace(init_adm_tmp_area(adm_abspath, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__get_tmpdir(const char **tmpdir_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(tmpdir_abspath,
+ wc_ctx->db, wri_abspath,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/adm_files.h b/contrib/subversion/subversion/libsvn_wc/adm_files.h
new file mode 100644
index 0000000..3712149
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/adm_files.h
@@ -0,0 +1,161 @@
+/*
+ * adm_files.h : handles locations inside the wc adm area
+ * (This should be the only code that actually knows
+ * *where* things are in .svn/. If you can't get to
+ * something via these interfaces, something's wrong.)
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_WC_ADM_FILES_H
+#define SVN_LIBSVN_WC_ADM_FILES_H
+
+#include <apr_pools.h>
+#include "svn_types.h"
+
+#include "props.h"
+#include "wc_db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* Return a path to CHILD in the administrative area of PATH. If CHILD is
+ NULL, then the path to the admin area is returned. The result is
+ allocated in RESULT_POOL. */
+const char *svn_wc__adm_child(const char *path,
+ const char *child,
+ apr_pool_t *result_pool);
+
+/* Return TRUE if the administrative area exists for this directory. */
+svn_boolean_t svn_wc__adm_area_exists(const char *adm_abspath,
+ apr_pool_t *pool);
+
+
+/* Set *CONTENTS to a readonly stream on the pristine text of the working
+ * version of the file LOCAL_ABSPATH in DB. If the file is locally copied
+ * or moved to this path, this means the pristine text of the copy source,
+ * even if the file replaces a previously existing base node at this path.
+ *
+ * Set *CONTENTS to NULL if there is no pristine text because the file is
+ * locally added (even if it replaces an existing base node). Return an
+ * error if there is no pristine text for any other reason.
+ *
+ * If SIZE is not NULL, set *SIZE to the length of the pristine stream in
+ * BYTES or to SVN_INVALID_FILESIZE if no pristine is available for this
+ * file.
+ *
+ * For more detail, see the description of svn_wc_get_pristine_contents2().
+ */
+svn_error_t *
+svn_wc__get_pristine_contents(svn_stream_t **contents,
+ svn_filesize_t *size,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *RESULT_ABSPATH to the absolute path to a readable file containing
+ the WC-1 "normal text-base" of LOCAL_ABSPATH in DB.
+
+ "Normal text-base" means the same as in svn_wc__text_base_path().
+ ### May want to check the callers' exact requirements and replace this
+ definition with something easier to comprehend.
+
+ What the callers want:
+ A path to a file that will remain available and unchanged as long as
+ the caller wants it - such as for the lifetime of RESULT_POOL.
+
+ What the current implementation provides:
+ A path to the file in the pristine store. This file will be removed or
+ replaced the next time this or another Subversion client updates the WC.
+
+ If the node LOCAL_ABSPATH has no such pristine text, return an error of
+ type SVN_ERR_WC_PATH_UNEXPECTED_STATUS.
+
+ Allocate *RESULT_PATH in RESULT_POOL. */
+svn_error_t *
+svn_wc__text_base_path_to_read(const char **result_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/*** Opening all kinds of adm files ***/
+
+/* Open `PATH/<adminstrative_subdir>/FNAME'. */
+svn_error_t *svn_wc__open_adm_stream(svn_stream_t **stream,
+ const char *dir_abspath,
+ const char *fname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Open a writable stream to a temporary (normal or revert) text base,
+ associated with the versioned file LOCAL_ABSPATH in DB. Set *STREAM to
+ the opened stream and *TEMP_BASE_ABSPATH to the path to the temporary
+ file. The temporary file will have an arbitrary unique name, in contrast
+ to the deterministic name that svn_wc__text_base_deterministic_tmp_path()
+ returns.
+
+ Arrange that, on stream closure, *MD5_CHECKSUM and *SHA1_CHECKSUM will be
+ set to the MD-5 and SHA-1 checksums respectively of that file.
+ MD5_CHECKSUM and/or SHA1_CHECKSUM may be NULL if not wanted.
+
+ Allocate the new stream, path and checksums in RESULT_POOL.
+ */
+svn_error_t *
+svn_wc__open_writable_base(svn_stream_t **stream,
+ const char **temp_base_abspath,
+ svn_checksum_t **md5_checksum,
+ svn_checksum_t **sha1_checksum,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Blow away the admistrative directory associated with DIR_ABSPATH.
+ For single-db this doesn't perform actual work unless the wcroot is passed.
+ */
+svn_error_t *svn_wc__adm_destroy(svn_wc__db_t *db,
+ const char *dir_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Cleanup the temporary storage area of the administrative
+ directory (assuming temp and admin areas exist). */
+svn_error_t *
+svn_wc__adm_cleanup_tmp_area(svn_wc__db_t *db,
+ const char *adm_abspath,
+ apr_pool_t *scratch_pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_ADM_FILES_H */
diff --git a/contrib/subversion/subversion/libsvn_wc/adm_ops.c b/contrib/subversion/subversion/libsvn_wc/adm_ops.c
new file mode 100644
index 0000000..1f391fc
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/adm_ops.c
@@ -0,0 +1,1400 @@
+/*
+ * adm_ops.c: routines for affecting working copy administrative
+ * information. NOTE: this code doesn't know where the adm
+ * info is actually stored. Instead, generic handles to
+ * adm data are requested via a reference to some PATH
+ * (PATH being a regular, non-administrative directory or
+ * file in the working copy).
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_time.h>
+#include <apr_errno.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_io.h"
+#include "svn_time.h"
+#include "svn_sorts.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "workqueue.h"
+
+#include "svn_private_config.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_dep_compat.h"
+
+
+struct svn_wc_committed_queue_t
+{
+ /* The pool in which ->queue is allocated. */
+ apr_pool_t *pool;
+ /* Mapping (const char *) local_abspath to (committed_queue_item_t *). */
+ apr_hash_t *queue;
+ /* Is any item in the queue marked as 'recursive'? */
+ svn_boolean_t have_recursive;
+};
+
+typedef struct committed_queue_item_t
+{
+ const char *local_abspath;
+ svn_boolean_t recurse;
+ svn_boolean_t no_unlock;
+ svn_boolean_t keep_changelist;
+
+ /* The pristine text checksum. */
+ const svn_checksum_t *sha1_checksum;
+
+ apr_hash_t *new_dav_cache;
+} committed_queue_item_t;
+
+
+apr_pool_t *
+svn_wc__get_committed_queue_pool(const struct svn_wc_committed_queue_t *queue)
+{
+ return queue->pool;
+}
+
+
+
+/*** Finishing updates and commits. ***/
+
+/* Queue work items that will finish a commit of the file or directory
+ * LOCAL_ABSPATH in DB:
+ * - queue the removal of any "revert-base" props and text files;
+ * - queue an update of the DB entry for this node
+ *
+ * ### The Pristine Store equivalent should be:
+ * - remember the old BASE_NODE and WORKING_NODE pristine text c'sums;
+ * - queue an update of the DB entry for this node (incl. updating the
+ * BASE_NODE c'sum and setting the WORKING_NODE c'sum to NULL);
+ * - queue deletion of the old pristine texts by the remembered checksums.
+ *
+ * CHECKSUM is the checksum of the new text base for LOCAL_ABSPATH, and must
+ * be provided if there is one, else NULL.
+ *
+ * STATUS, KIND, PROP_MODS and OLD_CHECKSUM are the current in-db values of
+ * the node LOCAL_ABSPATH.
+ */
+static svn_error_t *
+process_committed_leaf(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t via_recurse,
+ svn_wc__db_status_t status,
+ svn_node_kind_t kind,
+ svn_boolean_t prop_mods,
+ const svn_checksum_t *old_checksum,
+ svn_revnum_t new_revnum,
+ apr_time_t new_changed_date,
+ const char *new_changed_author,
+ apr_hash_t *new_dav_cache,
+ svn_boolean_t no_unlock,
+ svn_boolean_t keep_changelist,
+ const svn_checksum_t *checksum,
+ apr_pool_t *scratch_pool)
+{
+ svn_revnum_t new_changed_rev = new_revnum;
+ svn_skel_t *work_item = NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ {
+ const char *adm_abspath;
+
+ if (kind == svn_node_dir)
+ adm_abspath = local_abspath;
+ else
+ adm_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ SVN_ERR(svn_wc__write_check(db, adm_abspath, scratch_pool));
+ }
+
+ if (status == svn_wc__db_status_deleted)
+ {
+ return svn_error_trace(
+ svn_wc__db_base_remove(
+ db, local_abspath,
+ FALSE /* keep_as_working */,
+ FALSE /* queue_deletes */,
+ (! via_recurse)
+ ? new_revnum : SVN_INVALID_REVNUM,
+ NULL, NULL,
+ scratch_pool));
+ }
+ else if (status == svn_wc__db_status_not_present)
+ {
+ /* We are committing the leaf of a copy operation.
+ We leave the not-present marker to allow pulling in excluded
+ children of a copy.
+
+ The next update will remove the not-present marker. */
+
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR_ASSERT(status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_incomplete
+ || status == svn_wc__db_status_added);
+
+ if (kind != svn_node_dir)
+ {
+ /* If we sent a delta (meaning: post-copy modification),
+ then this file will appear in the queue and so we should have
+ its checksum already. */
+ if (checksum == NULL)
+ {
+ /* It was copied and not modified. We must have a text
+ base for it. And the node should have a checksum. */
+ SVN_ERR_ASSERT(old_checksum != NULL);
+
+ checksum = old_checksum;
+
+ /* Is the node completely unmodified and are we recursing? */
+ if (via_recurse && !prop_mods)
+ {
+ /* If a copied node itself is not modified, but the op_root of
+ the copy is committed we have to make sure that changed_rev,
+ changed_date and changed_author don't change or the working
+ copy used for committing will show different last modified
+ information then a clean checkout of exactly the same
+ revisions. (Issue #3676) */
+
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL,
+ NULL, &new_changed_rev,
+ &new_changed_date,
+ &new_changed_author, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ }
+
+ SVN_ERR(svn_wc__wq_build_file_commit(&work_item,
+ db, local_abspath,
+ prop_mods,
+ scratch_pool, scratch_pool));
+ }
+
+ /* The new text base will be found in the pristine store by its checksum. */
+ SVN_ERR(svn_wc__db_global_commit(db, local_abspath,
+ new_revnum, new_changed_rev,
+ new_changed_date, new_changed_author,
+ checksum,
+ NULL /* new_children */,
+ new_dav_cache,
+ keep_changelist,
+ no_unlock,
+ work_item,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__process_committed_internal(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t recurse,
+ svn_boolean_t top_of_recurse,
+ svn_revnum_t new_revnum,
+ apr_time_t new_date,
+ const char *rev_author,
+ apr_hash_t *new_dav_cache,
+ svn_boolean_t no_unlock,
+ svn_boolean_t keep_changelist,
+ const svn_checksum_t *sha1_checksum,
+ const svn_wc_committed_queue_t *queue,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const svn_checksum_t *old_checksum;
+ svn_boolean_t prop_mods;
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &old_checksum, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, &prop_mods, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* NOTE: be wary of making crazy semantic changes in this function, since
+ svn_wc_process_committed4() calls this. */
+
+ SVN_ERR(process_committed_leaf(db, local_abspath, !top_of_recurse,
+ status, kind, prop_mods, old_checksum,
+ new_revnum, new_date, rev_author,
+ new_dav_cache,
+ no_unlock, keep_changelist,
+ sha1_checksum,
+ scratch_pool));
+
+ /* Only check for recursion on nodes that have children */
+ if (kind != svn_node_file
+ || status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_server_excluded
+ /* Node deleted -> then no longer a directory */
+ || status == svn_wc__db_status_deleted)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ if (recurse)
+ {
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ /* Read PATH's entries; this is the absolute path. */
+ SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath,
+ scratch_pool, iterpool));
+
+ /* Recursively loop over all children. */
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+ const char *this_abspath;
+ const committed_queue_item_t *cqi;
+
+ svn_pool_clear(iterpool);
+
+ this_abspath = svn_dirent_join(local_abspath, name, iterpool);
+
+ sha1_checksum = NULL;
+ cqi = svn_hash_gets(queue->queue, this_abspath);
+
+ if (cqi != NULL)
+ sha1_checksum = cqi->sha1_checksum;
+
+ /* Recurse. Pass NULL for NEW_DAV_CACHE, because the
+ ones present in the current call are only applicable to
+ this one committed item. */
+ SVN_ERR(svn_wc__process_committed_internal(
+ db, this_abspath,
+ TRUE /* recurse */,
+ FALSE /* top_of_recurse */,
+ new_revnum, new_date,
+ rev_author,
+ NULL /* new_dav_cache */,
+ TRUE /* no_unlock */,
+ keep_changelist,
+ sha1_checksum,
+ queue,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+apr_hash_t *
+svn_wc__prop_array_to_hash(const apr_array_header_t *props,
+ apr_pool_t *result_pool)
+{
+ int i;
+ apr_hash_t *prophash;
+
+ if (props == NULL || props->nelts == 0)
+ return NULL;
+
+ prophash = apr_hash_make(result_pool);
+
+ for (i = 0; i < props->nelts; i++)
+ {
+ const svn_prop_t *prop = APR_ARRAY_IDX(props, i, const svn_prop_t *);
+ if (prop->value != NULL)
+ svn_hash_sets(prophash, prop->name, prop->value);
+ }
+
+ return prophash;
+}
+
+
+svn_wc_committed_queue_t *
+svn_wc_committed_queue_create(apr_pool_t *pool)
+{
+ svn_wc_committed_queue_t *q;
+
+ q = apr_palloc(pool, sizeof(*q));
+ q->pool = pool;
+ q->queue = apr_hash_make(pool);
+ q->have_recursive = FALSE;
+
+ return q;
+}
+
+
+svn_error_t *
+svn_wc_queue_committed3(svn_wc_committed_queue_t *queue,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t recurse,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ svn_boolean_t remove_changelist,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool)
+{
+ committed_queue_item_t *cqi;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ queue->have_recursive |= recurse;
+
+ /* Use the same pool as the one QUEUE was allocated in,
+ to prevent lifetime issues. Intermediate operations
+ should use SCRATCH_POOL. */
+
+ /* Add to the array with paths and options */
+ cqi = apr_palloc(queue->pool, sizeof(*cqi));
+ cqi->local_abspath = local_abspath;
+ cqi->recurse = recurse;
+ cqi->no_unlock = !remove_lock;
+ cqi->keep_changelist = !remove_changelist;
+ cqi->sha1_checksum = sha1_checksum;
+ cqi->new_dav_cache = svn_wc__prop_array_to_hash(wcprop_changes, queue->pool);
+
+ svn_hash_sets(queue->queue, local_abspath, cqi);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return TRUE if any item of QUEUE is a parent of ITEM and will be
+ processed recursively, return FALSE otherwise.
+
+ The algorithmic complexity of this search implementation is O(queue
+ length), but it's quite quick.
+*/
+static svn_boolean_t
+have_recursive_parent(apr_hash_t *queue,
+ const committed_queue_item_t *item,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ const char *local_abspath = item->local_abspath;
+
+ for (hi = apr_hash_first(scratch_pool, queue); hi; hi = apr_hash_next(hi))
+ {
+ const committed_queue_item_t *qi = svn__apr_hash_index_val(hi);
+
+ if (qi == item)
+ continue;
+
+ if (qi->recurse && svn_dirent_is_child(qi->local_abspath, local_abspath,
+ NULL))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+svn_error_t *
+svn_wc_process_committed_queue2(svn_wc_committed_queue_t *queue,
+ svn_wc_context_t *wc_ctx,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *sorted_queue;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_time_t new_date;
+ apr_hash_t *run_wqs = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+
+ if (rev_date)
+ SVN_ERR(svn_time_from_cstring(&new_date, rev_date, iterpool));
+ else
+ new_date = 0;
+
+ /* Process the queued items in order of their paths. (The requirement is
+ * probably just that a directory must be processed before its children.) */
+ sorted_queue = svn_sort__hash(queue->queue, svn_sort_compare_items_as_paths,
+ scratch_pool);
+ for (i = 0; i < sorted_queue->nelts; i++)
+ {
+ const svn_sort__item_t *sort_item
+ = &APR_ARRAY_IDX(sorted_queue, i, svn_sort__item_t);
+ const committed_queue_item_t *cqi = sort_item->value;
+ const char *wcroot_abspath;
+
+ svn_pool_clear(iterpool);
+
+ /* Skip this item if it is a child of a recursive item, because it has
+ been (or will be) accounted for when that recursive item was (or
+ will be) processed. */
+ if (queue->have_recursive && have_recursive_parent(queue->queue, cqi,
+ iterpool))
+ continue;
+
+ SVN_ERR(svn_wc__process_committed_internal(
+ wc_ctx->db, cqi->local_abspath,
+ cqi->recurse,
+ TRUE /* top_of_recurse */,
+ new_revnum, new_date, rev_author,
+ cqi->new_dav_cache,
+ cqi->no_unlock,
+ cqi->keep_changelist,
+ cqi->sha1_checksum, queue,
+ iterpool));
+
+ /* Don't run the wq now, but remember that we must call it for this
+ working copy */
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath,
+ wc_ctx->db, cqi->local_abspath,
+ iterpool, iterpool));
+
+ if (! svn_hash_gets(run_wqs, wcroot_abspath))
+ {
+ wcroot_abspath = apr_pstrdup(scratch_pool, wcroot_abspath);
+ svn_hash_sets(run_wqs, wcroot_abspath, wcroot_abspath);
+ }
+ }
+
+ /* Make sure nothing happens if this function is called again. */
+ apr_hash_clear(queue->queue);
+
+ /* Ok; everything is committed now. Now we can start calling callbacks */
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ for (hi = apr_hash_first(scratch_pool, run_wqs);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *wcroot_abspath = svn__apr_hash_index_key(hi);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc__wq_run(wc_ctx->db, wcroot_abspath,
+ cancel_func, cancel_baton,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Schedule the single node at LOCAL_ABSPATH, of kind KIND, for addition in
+ * its parent directory in the WC. It will have the regular properties
+ * provided in PROPS, or none if that is NULL.
+ *
+ * If the node is a file, set its on-disk executable and read-only bits to
+ * match its properties and lock state,
+ * ### only if it has an svn:executable or svn:needs-lock property.
+ * ### This is to match the previous behaviour of setting its props
+ * afterwards by calling svn_wc_prop_set4(), but is not very clean.
+ *
+ * Sync the on-disk executable and read-only bits accordingly.
+ */
+static svn_error_t *
+add_from_disk(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ if (kind == svn_node_file)
+ {
+ svn_skel_t *work_item = NULL;
+
+ if (props && (svn_prop_get_value(props, SVN_PROP_EXECUTABLE)
+ || svn_prop_get_value(props, SVN_PROP_NEEDS_LOCK)))
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_op_add_file(db, local_abspath, props, work_item,
+ scratch_pool));
+ if (work_item)
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_op_add_directory(db, local_abspath, props, NULL,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *REPOS_ROOT_URL and *REPOS_UUID to the repository of the parent of
+ LOCAL_ABSPATH. REPOS_ROOT_URL and/or REPOS_UUID may be NULL if not
+ wanted. Check that the parent of LOCAL_ABSPATH is a versioned directory
+ in a state in which a new child node can be scheduled for addition;
+ return an error if not. */
+static svn_error_t *
+check_can_add_to_parent(const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ svn_wc__db_status_t parent_status;
+ svn_node_kind_t parent_kind;
+ svn_error_t *err;
+
+ SVN_ERR(svn_wc__write_check(db, parent_abspath, scratch_pool));
+
+ err = svn_wc__db_read_info(&parent_status, &parent_kind, NULL,
+ NULL, repos_root_url, repos_uuid, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, parent_abspath, result_pool, scratch_pool);
+
+ if (err
+ || parent_status == svn_wc__db_status_not_present
+ || parent_status == svn_wc__db_status_excluded
+ || parent_status == svn_wc__db_status_server_excluded)
+ {
+ return
+ svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, err,
+ _("Can't find parent directory's node while"
+ " trying to add '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ else if (parent_status == svn_wc__db_status_deleted)
+ {
+ return
+ svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL,
+ _("Can't add '%s' to a parent directory"
+ " scheduled for deletion"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ else if (parent_kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Can't schedule an addition of '%s'"
+ " below a not-directory node"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ /* If we haven't found the repository info yet, find it now. */
+ if ((repos_root_url && ! *repos_root_url)
+ || (repos_uuid && ! *repos_uuid))
+ {
+ if (parent_status == svn_wc__db_status_added)
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
+ repos_root_url, repos_uuid, NULL,
+ NULL, NULL, NULL,
+ db, parent_abspath,
+ result_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__db_scan_base_repos(NULL,
+ repos_root_url, repos_uuid,
+ db, parent_abspath,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Check that the on-disk item at LOCAL_ABSPATH can be scheduled for
+ * addition to its WC parent directory.
+ *
+ * Set *KIND_P to the kind of node to be added, *DB_ROW_EXISTS_P to whether
+ * it is already a versioned path, and if so, *IS_WC_ROOT_P to whether it's
+ * a WC root.
+ *
+ * ### The checks here, and the outputs, are geared towards svn_wc_add4().
+ */
+static svn_error_t *
+check_can_add_node(svn_node_kind_t *kind_p,
+ svn_boolean_t *db_row_exists_p,
+ svn_boolean_t *is_wc_root_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *scratch_pool)
+{
+ const char *base_name = svn_dirent_basename(local_abspath, scratch_pool);
+ svn_boolean_t is_wc_root;
+ svn_node_kind_t kind;
+ svn_boolean_t is_special;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(!copyfrom_url || (svn_uri_is_canonical(copyfrom_url,
+ scratch_pool)
+ && SVN_IS_VALID_REVNUM(copyfrom_rev)));
+
+ /* Check that the proposed node has an acceptable name. */
+ if (svn_wc_is_adm_dir(base_name, scratch_pool))
+ return svn_error_createf
+ (SVN_ERR_ENTRY_FORBIDDEN, NULL,
+ _("Can't create an entry with a reserved name while trying to add '%s'"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+
+ SVN_ERR(svn_path_check_valid(local_abspath, scratch_pool));
+
+ /* Make sure something's there; set KIND and *KIND_P. */
+ SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special,
+ scratch_pool));
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' not found"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ if (kind == svn_node_unknown)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Unsupported node kind for path '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ if (kind_p)
+ *kind_p = kind;
+
+ /* Determine whether a DB row for this node EXISTS, and whether it
+ IS_WC_ROOT. If it exists, check that it is in an acceptable state for
+ adding the new node; if not, return an error. */
+ {
+ svn_wc__db_status_t status;
+ svn_boolean_t conflicted;
+ svn_boolean_t exists;
+ svn_error_t *err
+ = svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ &conflicted,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ exists = FALSE;
+ is_wc_root = FALSE;
+ }
+ else
+ {
+ is_wc_root = FALSE;
+ exists = TRUE;
+
+ /* Note that the node may be in conflict even if it does not
+ * exist on disk (certain tree conflict scenarios). */
+ if (conflicted)
+ return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
+ _("'%s' is an existing item in conflict; "
+ "please mark the conflict as resolved "
+ "before adding a new item here"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ switch (status)
+ {
+ case svn_wc__db_status_not_present:
+ break;
+ case svn_wc__db_status_deleted:
+ /* A working copy root should never have a WORKING_NODE */
+ SVN_ERR_ASSERT(!is_wc_root);
+ break;
+ case svn_wc__db_status_normal:
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wc_root, db, local_abspath,
+ scratch_pool));
+
+ if (is_wc_root && copyfrom_url)
+ {
+ /* Integrate a sub working copy in a parent working copy
+ (legacy behavior) */
+ break;
+ }
+ else if (is_wc_root && is_special)
+ {
+ /* Adding a symlink to a working copy root.
+ (special_tests.py 23: externals as symlink targets) */
+ break;
+ }
+ /* else: Fall through in default error */
+
+ default:
+ return svn_error_createf(
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("'%s' is already under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ } /* err */
+
+ if (db_row_exists_p)
+ *db_row_exists_p = exists;
+ if (is_wc_root_p)
+ *is_wc_root_p = is_wc_root;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Convert the nested pristine working copy rooted at LOCAL_ABSPATH into
+ * a copied subtree in the outer working copy.
+ *
+ * LOCAL_ABSPATH must be the root of a nested working copy that has no
+ * local modifications. The parent directory of LOCAL_ABSPATH must be a
+ * versioned directory in the outer WC, and must belong to the same
+ * repository as the nested WC. The nested WC will be integrated into the
+ * parent's WC, and will no longer be a separate WC. */
+static svn_error_t *
+integrate_nested_wc_as_copy(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ const char *moved_abspath;
+
+ /* Drop any references to the wc that is to be rewritten */
+ SVN_ERR(svn_wc__db_drop_root(db, local_abspath, scratch_pool));
+
+ /* Move the admin dir from the wc to a temporary location: MOVED_ABSPATH */
+ {
+ const char *tmpdir_abspath;
+ const char *moved_adm_abspath;
+ const char *adm_abspath;
+
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db,
+ svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_open_unique_file3(NULL, &moved_abspath, tmpdir_abspath,
+ svn_io_file_del_on_close,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_dir_make(moved_abspath, APR_OS_DEFAULT, scratch_pool));
+
+ adm_abspath = svn_wc__adm_child(local_abspath, "", scratch_pool);
+ moved_adm_abspath = svn_wc__adm_child(moved_abspath, "", scratch_pool);
+ SVN_ERR(svn_io_file_move(adm_abspath, moved_adm_abspath, scratch_pool));
+ }
+
+ /* Copy entries from temporary location into the main db */
+ SVN_ERR(svn_wc_copy3(wc_ctx, moved_abspath, local_abspath,
+ TRUE /* metadata_only */,
+ NULL, NULL, NULL, NULL, scratch_pool));
+
+ /* Cleanup the temporary admin dir */
+ SVN_ERR(svn_wc__db_drop_root(db, moved_abspath, scratch_pool));
+ SVN_ERR(svn_io_remove_dir2(moved_abspath, FALSE, NULL, NULL,
+ scratch_pool));
+
+ /* The subdir is now part of our parent working copy. Our caller assumes
+ that we return the new node locked, so obtain a lock if we didn't
+ receive the lock via our depth infinity lock */
+ {
+ svn_boolean_t owns_lock;
+
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, local_abspath,
+ FALSE, scratch_pool));
+ if (!owns_lock)
+ SVN_ERR(svn_wc__db_wclock_obtain(db, local_abspath, 0, FALSE,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_add4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_node_kind_t kind;
+ svn_boolean_t db_row_exists;
+ svn_boolean_t is_wc_root;
+ const char *repos_root_url;
+ const char *repos_uuid;
+
+ SVN_ERR(check_can_add_node(&kind, &db_row_exists, &is_wc_root,
+ db, local_abspath, copyfrom_url, copyfrom_rev,
+ scratch_pool));
+
+ /* Get REPOS_ROOT_URL and REPOS_UUID. Check that the
+ parent is a versioned directory in an acceptable state. */
+ SVN_ERR(check_can_add_to_parent(&repos_root_url, &repos_uuid,
+ db, local_abspath, scratch_pool,
+ scratch_pool));
+
+ /* If we're performing a repos-to-WC copy, check that the copyfrom
+ repository is the same as the parent dir's repository. */
+ if (copyfrom_url && !svn_uri__is_ancestor(repos_root_url, copyfrom_url))
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The URL '%s' has a different repository "
+ "root than its parent"), copyfrom_url);
+
+ /* Verify that we can actually integrate the inner working copy */
+ if (is_wc_root)
+ {
+ const char *repos_relpath, *inner_repos_root_url, *inner_repos_uuid;
+ const char *inner_url;
+
+ SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath,
+ &inner_repos_root_url,
+ &inner_repos_uuid,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (strcmp(inner_repos_uuid, repos_uuid)
+ || strcmp(repos_root_url, inner_repos_root_url))
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Can't schedule the working copy at '%s' "
+ "from repository '%s' with uuid '%s' "
+ "for addition under a working copy from "
+ "repository '%s' with uuid '%s'."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool),
+ inner_repos_root_url, inner_repos_uuid,
+ repos_root_url, repos_uuid);
+
+ inner_url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ scratch_pool);
+
+ if (strcmp(copyfrom_url, inner_url))
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Can't add '%s' with URL '%s', but with "
+ "the data from '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool),
+ copyfrom_url, inner_url);
+ }
+
+ if (!copyfrom_url) /* Case 2a: It's a simple add */
+ {
+ SVN_ERR(add_from_disk(db, local_abspath, kind, NULL,
+ scratch_pool));
+ if (kind == svn_node_dir && !db_row_exists)
+ {
+ /* If using the legacy 1.6 interface the parent lock may not
+ be recursive and add is expected to lock the new dir.
+
+ ### Perhaps the lock should be created in the same
+ transaction that adds the node? */
+ svn_boolean_t owns_lock;
+
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, local_abspath,
+ FALSE, scratch_pool));
+ if (!owns_lock)
+ SVN_ERR(svn_wc__db_wclock_obtain(db, local_abspath, 0, FALSE,
+ scratch_pool));
+ }
+ }
+ else if (!is_wc_root) /* Case 2b: It's a copy from the repository */
+ {
+ if (kind == svn_node_file)
+ {
+ /* This code should never be used, as it doesn't install proper
+ pristine and/or properties. But it was not an error in the old
+ version of this function.
+
+ ===> Use svn_wc_add_repos_file4() directly! */
+ svn_stream_t *content = svn_stream_empty(scratch_pool);
+
+ SVN_ERR(svn_wc_add_repos_file4(wc_ctx, local_abspath,
+ content, NULL, NULL, NULL,
+ copyfrom_url, copyfrom_rev,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ else
+ {
+ const char *repos_relpath =
+ svn_uri_skip_ancestor(repos_root_url, copyfrom_url, scratch_pool);
+
+ SVN_ERR(svn_wc__db_op_copy_dir(db, local_abspath,
+ apr_hash_make(scratch_pool),
+ copyfrom_rev, 0, NULL,
+ repos_relpath,
+ repos_root_url, repos_uuid,
+ copyfrom_rev,
+ NULL /* children */, FALSE, depth,
+ NULL /* conflicts */,
+ NULL /* work items */,
+ scratch_pool));
+ }
+ }
+ else /* Case 1: Integrating a separate WC into this one, in place */
+ {
+ SVN_ERR(integrate_nested_wc_as_copy(wc_ctx, local_abspath,
+ scratch_pool));
+ }
+
+ /* Report the addition to the caller. */
+ if (notify_func != NULL)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_add,
+ scratch_pool);
+ notify->kind = kind;
+ (*notify_func)(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_add_from_disk2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(check_can_add_node(&kind, NULL, NULL, wc_ctx->db, local_abspath,
+ NULL, SVN_INVALID_REVNUM, scratch_pool));
+ SVN_ERR(check_can_add_to_parent(NULL, NULL, wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Canonicalize and check the props */
+ if (props)
+ {
+ apr_hash_t *new_props;
+
+ SVN_ERR(svn_wc__canonicalize_props(
+ &new_props,
+ local_abspath, kind, props, FALSE /* skip_some_checks */,
+ scratch_pool, scratch_pool));
+ props = new_props;
+ }
+
+ /* Add to the DB and maybe update on-disk executable read-only bits */
+ SVN_ERR(add_from_disk(wc_ctx->db, local_abspath, kind, props,
+ scratch_pool));
+
+ /* Report the addition to the caller. */
+ if (notify_func != NULL)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_add,
+ scratch_pool);
+ notify->kind = kind;
+ notify->mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
+ (*notify_func)(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return a path where nothing exists on disk, within the admin directory
+ belonging to the WCROOT_ABSPATH directory. */
+static const char *
+nonexistent_path(const char *wcroot_abspath, apr_pool_t *scratch_pool)
+{
+ return svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_NONEXISTENT_PATH,
+ scratch_pool);
+}
+
+
+svn_error_t *
+svn_wc_get_pristine_copy_path(const char *path,
+ const char **pristine_path,
+ apr_pool_t *pool)
+{
+ svn_wc__db_t *db;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ SVN_ERR(svn_wc__db_open(&db, NULL, FALSE, TRUE, pool, pool));
+ /* DB is now open. This is seemingly a "light" function that a caller
+ may use repeatedly despite error return values. The rest of this
+ function should aggressively close DB, even in the error case. */
+
+ err = svn_wc__text_base_path_to_read(pristine_path, db, local_abspath,
+ pool, pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ {
+ /* The node doesn't exist, so return a non-existent path located
+ in WCROOT/.svn/ */
+ const char *wcroot_abspath;
+
+ svn_error_clear(err);
+
+ err = svn_wc__db_get_wcroot(&wcroot_abspath, db, local_abspath,
+ pool, pool);
+ if (err == NULL)
+ *pristine_path = nonexistent_path(wcroot_abspath, pool);
+ }
+
+ return svn_error_compose_create(err, svn_wc__db_close(db));
+}
+
+
+svn_error_t *
+svn_wc_get_pristine_contents2(svn_stream_t **contents,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__get_pristine_contents(contents, NULL,
+ wc_ctx->db,
+ local_abspath,
+ result_pool,
+ scratch_pool));
+}
+
+
+typedef struct get_pristine_lazyopen_baton_t
+{
+ svn_wc_context_t *wc_ctx;
+ const char *wri_abspath;
+ const svn_checksum_t *checksum;
+
+} get_pristine_lazyopen_baton_t;
+
+
+/* Implements svn_stream_lazyopen_func_t */
+static svn_error_t *
+get_pristine_lazyopen_func(svn_stream_t **stream,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ get_pristine_lazyopen_baton_t *b = baton;
+ const svn_checksum_t *sha1_checksum;
+
+ /* svn_wc__db_pristine_read() wants a SHA1, so if we have an MD5,
+ we'll use it to lookup the SHA1. */
+ if (b->checksum->kind == svn_checksum_sha1)
+ sha1_checksum = b->checksum;
+ else
+ SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, b->wc_ctx->db,
+ b->wri_abspath, b->checksum,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_pristine_read(stream, NULL, b->wc_ctx->db,
+ b->wri_abspath, sha1_checksum,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__get_pristine_contents_by_checksum(svn_stream_t **contents,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const svn_checksum_t *checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t present;
+
+ *contents = NULL;
+
+ SVN_ERR(svn_wc__db_pristine_check(&present, wc_ctx->db, wri_abspath,
+ checksum, scratch_pool));
+
+ if (present)
+ {
+ get_pristine_lazyopen_baton_t *gpl_baton;
+
+ gpl_baton = apr_pcalloc(result_pool, sizeof(*gpl_baton));
+ gpl_baton->wc_ctx = wc_ctx;
+ gpl_baton->wri_abspath = wri_abspath;
+ gpl_baton->checksum = checksum;
+
+ *contents = svn_stream_lazyopen_create(get_pristine_lazyopen_func,
+ gpl_baton, FALSE, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_wc_add_lock2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_lock_t *lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_lock_t db_lock;
+ svn_error_t *err;
+ const svn_string_t *needs_lock;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* ### Enable after fixing callers */
+ /*SVN_ERR(svn_wc__write_check(wc_ctx->db,
+ svn_dirent_dirname(local_abspath, scratch_pool),
+ scratch_pool));*/
+
+ db_lock.token = lock->token;
+ db_lock.owner = lock->owner;
+ db_lock.comment = lock->comment;
+ db_lock.date = lock->creation_date;
+ err = svn_wc__db_lock_add(wc_ctx->db, local_abspath, &db_lock, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ /* Remap the error. */
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ /* if svn:needs-lock is present, then make the file read-write. */
+ err = svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath,
+ SVN_PROP_NEEDS_LOCK, scratch_pool,
+ scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ {
+ /* The node has non wc representation (e.g. deleted), so
+ we don't want to touch the in-wc file */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ if (needs_lock)
+ SVN_ERR(svn_io_set_file_read_write(local_abspath, FALSE, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_remove_lock2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ const svn_string_t *needs_lock;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* ### Enable after fixing callers */
+ /*SVN_ERR(svn_wc__write_check(wc_ctx->db,
+ svn_dirent_dirname(local_abspath, scratch_pool),
+ scratch_pool));*/
+
+ err = svn_wc__db_lock_remove(wc_ctx->db, local_abspath, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ /* Remap the error. */
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ /* if svn:needs-lock is present, then make the file read-only. */
+ err = svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath,
+ SVN_PROP_NEEDS_LOCK, scratch_pool,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ return SVN_NO_ERROR; /* Node is shadowed and/or deleted,
+ so we shouldn't apply its lock */
+ }
+
+ if (needs_lock)
+ SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_set_changelist2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *new_changelist,
+ svn_depth_t depth,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* Assert that we aren't being asked to set an empty changelist. */
+ SVN_ERR_ASSERT(! (new_changelist && new_changelist[0] == '\0'));
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_op_set_changelist(wc_ctx->db, local_abspath,
+ new_changelist, changelist_filter,
+ depth, notify_func, notify_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+struct get_cl_fn_baton
+{
+ svn_wc__db_t *db;
+ apr_hash_t *clhash;
+ svn_changelist_receiver_t callback_func;
+ void *callback_baton;
+};
+
+
+static svn_error_t *
+get_node_changelist(const char *local_abspath,
+ svn_node_kind_t kind,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct get_cl_fn_baton *b = baton;
+ const char *changelist;
+
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, &changelist,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ b->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (svn_wc__internal_changelist_match(b->db, local_abspath, b->clhash,
+ scratch_pool))
+ SVN_ERR(b->callback_func(b->callback_baton, local_abspath,
+ changelist, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_get_changelists(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ const apr_array_header_t *changelist_filter,
+ svn_changelist_receiver_t callback_func,
+ void *callback_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct get_cl_fn_baton gnb;
+
+ gnb.db = wc_ctx->db;
+ gnb.clhash = NULL;
+ gnb.callback_func = callback_func;
+ gnb.callback_baton = callback_baton;
+
+ if (changelist_filter)
+ SVN_ERR(svn_hash_from_cstring_keys(&gnb.clhash, changelist_filter,
+ scratch_pool));
+
+ return svn_error_trace(
+ svn_wc__internal_walk_children(wc_ctx->db, local_abspath, FALSE,
+ changelist_filter, get_node_changelist,
+ &gnb, depth,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+}
+
+
+svn_boolean_t
+svn_wc__internal_changelist_match(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *clhash,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ const char *changelist;
+
+ if (clhash == NULL)
+ return TRUE;
+
+ err = svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, &changelist,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return FALSE;
+ }
+
+ return (changelist
+ && svn_hash_gets((apr_hash_t *)clhash, changelist) != NULL);
+}
+
+
+svn_boolean_t
+svn_wc__changelist_match(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const apr_hash_t *clhash,
+ apr_pool_t *scratch_pool)
+{
+ return svn_wc__internal_changelist_match(wc_ctx->db, local_abspath, clhash,
+ scratch_pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/ambient_depth_filter_editor.c b/contrib/subversion/subversion/libsvn_wc/ambient_depth_filter_editor.c
new file mode 100644
index 0000000..ff9a5c3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/ambient_depth_filter_editor.c
@@ -0,0 +1,715 @@
+/*
+ * ambient_depth_filter_editor.c -- provide a svn_delta_editor_t which wraps
+ * another editor and provides
+ * *ambient* depth-based filtering
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_delta.h"
+#include "svn_wc.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+
+#include "wc.h"
+
+/*
+ Notes on the general depth-filtering strategy.
+ ==============================================
+
+ When a depth-aware (>= 1.5) client pulls an update from a
+ non-depth-aware server, the server may send back too much data,
+ because it doesn't hear what the client tells it about the
+ "requested depth" of the update (the "foo" in "--depth=foo"), nor
+ about the "ambient depth" of each working copy directory.
+
+ For example, suppose a 1.5 client does this against a 1.4 server:
+
+ $ svn co --depth=empty -rSOME_OLD_REV http://url/repos/blah/ wc
+ $ cd wc
+ $ svn up
+
+ In the initial checkout, the requested depth is 'empty', so the
+ depth-filtering editor (see libsvn_delta/depth_filter_editor.c)
+ that wraps the main update editor transparently filters out all
+ the unwanted calls.
+
+ In the 'svn up', the requested depth is unspecified, meaning that
+ the ambient depth(s) of the working copy should be preserved.
+ Since there's only one directory, and its depth is 'empty',
+ clearly we should filter out or render no-ops all editor calls
+ after open_root(), except maybe for change_dir_prop() on the
+ top-level directory. (Note that the server will have stuff to
+ send down, because we checked out at an old revision in the first
+ place, to set up this scenario.)
+
+ The depth-filtering editor won't help us here. It only filters
+ based on the requested depth, it never looks in the working copy
+ to get ambient depths. So the update editor itself will have to
+ filter out the unwanted calls -- or better yet, it will have to
+ be wrapped in a filtering editor that does the job.
+
+ This is that filtering editor.
+
+ Most of the work is done at the moment of baton construction.
+ When a file or dir is opened, we create its baton with the
+ appropriate ambient depth, either taking the depth directly from
+ the corresponding working copy object (if available), or from its
+ parent baton. In the latter case, we don't just copy the parent
+ baton's depth, but rather use it to choose the correct depth for
+ this child. The usual depth demotion rules apply, with the
+ additional stipulation that as soon as we find a subtree is not
+ present at all, due to being omitted for depth reasons, we set the
+ ambiently_excluded flag in its baton, which signals that
+ all descendant batons should be ignored.
+ (In fact, we may just re-use the parent baton, since none of the
+ other fields will be used anyway.)
+
+ See issues #2842 and #2897 for more.
+*/
+
+
+/*** Batons, and the Toys That Create Them ***/
+
+struct edit_baton
+{
+ const svn_delta_editor_t *wrapped_editor;
+ void *wrapped_edit_baton;
+ svn_wc__db_t *db;
+ const char *anchor_abspath;
+ const char *target;
+};
+
+struct file_baton
+{
+ svn_boolean_t ambiently_excluded;
+ struct edit_baton *edit_baton;
+ void *wrapped_baton;
+};
+
+struct dir_baton
+{
+ svn_boolean_t ambiently_excluded;
+ svn_depth_t ambient_depth;
+ struct edit_baton *edit_baton;
+ const char *abspath;
+ void *wrapped_baton;
+};
+
+/* Fetch the STATUS, KIND and DEPTH of the base node at LOCAL_ABSPATH.
+ * If there is no such base node, report 'normal', 'unknown' and 'unknown'
+ * respectively.
+ *
+ * STATUS and/or DEPTH may be NULL if not wanted; KIND must not be NULL.
+ */
+static svn_error_t *
+ambient_read_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_depth_t *depth,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ SVN_ERR_ASSERT(kind != NULL);
+
+ err = svn_wc__db_base_get_info(status, kind, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, depth, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+
+ *kind = svn_node_unknown;
+ if (status)
+ *status = svn_wc__db_status_normal;
+ if (depth)
+ *depth = svn_depth_unknown;
+
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+make_dir_baton(struct dir_baton **d_p,
+ const char *path,
+ struct edit_baton *eb,
+ struct dir_baton *pb,
+ svn_boolean_t added,
+ apr_pool_t *pool)
+{
+ struct dir_baton *d;
+
+ SVN_ERR_ASSERT(path || (! pb));
+
+ if (pb && pb->ambiently_excluded)
+ {
+ /* Just re-use the parent baton, since the only field that
+ matters is ambiently_excluded. */
+ *d_p = pb;
+ return SVN_NO_ERROR;
+ }
+
+ /* Okay, no easy out, so allocate and initialize a dir baton. */
+ d = apr_pcalloc(pool, sizeof(*d));
+
+ if (path)
+ d->abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
+ else
+ d->abspath = apr_pstrdup(pool, eb->anchor_abspath);
+
+ /* The svn_depth_unknown means that: 1) pb is the anchor; 2) there
+ is an non-null target, for which we are preparing the baton.
+ This enables explicitly pull in the target. */
+ if (pb && pb->ambient_depth != svn_depth_unknown)
+ {
+ svn_boolean_t exclude;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_boolean_t exists = TRUE;
+
+ if (!added)
+ {
+ SVN_ERR(ambient_read_info(&status, &kind, NULL,
+ eb->db, d->abspath, pool));
+ }
+ else
+ {
+ status = svn_wc__db_status_not_present;
+ kind = svn_node_unknown;
+ }
+
+ exists = (kind != svn_node_unknown);
+
+ if (pb->ambient_depth == svn_depth_empty
+ || pb->ambient_depth == svn_depth_files)
+ {
+ /* This is not a depth upgrade, and the parent directory is
+ depth==empty or depth==files. So if the parent doesn't
+ already have an entry for the new dir, then the parent
+ doesn't want the new dir at all, thus we should initialize
+ it with ambiently_excluded=TRUE. */
+ exclude = !exists;
+ }
+ else
+ {
+ /* If the parent expect all children by default, only exclude
+ it whenever it is explicitly marked as exclude. */
+ exclude = exists && (status == svn_wc__db_status_excluded);
+ }
+ if (exclude)
+ {
+ d->ambiently_excluded = TRUE;
+ *d_p = d;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ d->edit_baton = eb;
+ /* We'll initialize this differently in add_directory and
+ open_directory. */
+ d->ambient_depth = svn_depth_unknown;
+
+ *d_p = d;
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+make_file_baton(struct file_baton **f_p,
+ struct dir_baton *pb,
+ const char *path,
+ svn_boolean_t added,
+ apr_pool_t *pool)
+{
+ struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
+ struct edit_baton *eb = pb->edit_baton;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const char *abspath;
+
+ SVN_ERR_ASSERT(path);
+
+ if (pb->ambiently_excluded)
+ {
+ f->ambiently_excluded = TRUE;
+ *f_p = f;
+ return SVN_NO_ERROR;
+ }
+
+ abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
+
+ if (!added)
+ {
+ SVN_ERR(ambient_read_info(&status, &kind, NULL,
+ eb->db, abspath, pool));
+ }
+ else
+ {
+ status = svn_wc__db_status_not_present;
+ kind = svn_node_unknown;
+ }
+
+ if (pb->ambient_depth == svn_depth_empty)
+ {
+ /* This is not a depth upgrade, and the parent directory is
+ depth==empty. So if the parent doesn't
+ already have an entry for the file, then the parent
+ doesn't want to hear about the file at all. */
+
+ if (status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || kind == svn_node_unknown)
+ {
+ f->ambiently_excluded = TRUE;
+ *f_p = f;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* If pb->ambient_depth == svn_depth_unknown we are pulling
+ in new nodes */
+ if (pb->ambient_depth != svn_depth_unknown
+ && status == svn_wc__db_status_excluded)
+ {
+ f->ambiently_excluded = TRUE;
+ *f_p = f;
+ return SVN_NO_ERROR;
+ }
+
+ f->edit_baton = pb->edit_baton;
+
+ *f_p = f;
+ return SVN_NO_ERROR;
+}
+
+
+/*** Editor Functions ***/
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ /* Nothing depth-y to filter here. */
+ return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
+ target_revision, pool);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *b;
+
+ SVN_ERR(make_dir_baton(&b, NULL, eb, NULL, FALSE, pool));
+ *root_baton = b;
+
+ if (b->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ if (! *eb->target)
+ {
+ /* For an update with a NULL target, this is equivalent to open_dir(): */
+ svn_node_kind_t kind;
+ svn_wc__db_status_t status;
+ svn_depth_t depth;
+
+ /* Read the depth from the entry. */
+ SVN_ERR(ambient_read_info(&status, &kind, &depth,
+ eb->db, eb->anchor_abspath,
+ pool));
+
+ if (kind != svn_node_unknown
+ && status != svn_wc__db_status_not_present
+ && status != svn_wc__db_status_excluded
+ && status != svn_wc__db_status_server_excluded)
+ {
+ b->ambient_depth = depth;
+ }
+ }
+
+ return eb->wrapped_editor->open_root(eb->wrapped_edit_baton, base_revision,
+ pool, &b->wrapped_baton);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t base_revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+
+ if (pb->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ if (pb->ambient_depth < svn_depth_immediates)
+ {
+ /* If the entry we want to delete doesn't exist, that's OK.
+ It's probably an old server that doesn't understand
+ depths. */
+ svn_node_kind_t kind;
+ svn_wc__db_status_t status;
+ const char *abspath;
+
+ abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
+
+ SVN_ERR(ambient_read_info(&status, &kind, NULL,
+ eb->db, abspath, pool));
+
+ if (kind == svn_node_unknown
+ || status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_server_excluded)
+ return SVN_NO_ERROR;
+ }
+
+ return eb->wrapped_editor->delete_entry(path, base_revision,
+ pb->wrapped_baton, pool);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *b = NULL;
+
+ SVN_ERR(make_dir_baton(&b, path, eb, pb, TRUE, pool));
+ *child_baton = b;
+
+ if (b->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ /* It's not excluded, so what should we treat the ambient depth as
+ being? */
+ if (strcmp(eb->target, path) == 0)
+ {
+ /* The target of the edit is being added, so make it
+ infinity. */
+ b->ambient_depth = svn_depth_infinity;
+ }
+ else if (pb->ambient_depth == svn_depth_immediates)
+ {
+ b->ambient_depth = svn_depth_empty;
+ }
+ else
+ {
+ /* There may be a requested depth < svn_depth_infinity, but
+ that's okay, libsvn_delta/depth_filter_editor.c will filter
+ further calls out for us anyway, and the update_editor will
+ do the right thing when it creates the directory. */
+ b->ambient_depth = svn_depth_infinity;
+ }
+
+ return eb->wrapped_editor->add_directory(path, pb->wrapped_baton,
+ copyfrom_path,
+ copyfrom_revision,
+ pool, &b->wrapped_baton);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *b;
+ const char *local_abspath;
+ svn_node_kind_t kind;
+ svn_wc__db_status_t status;
+ svn_depth_t depth;
+
+ SVN_ERR(make_dir_baton(&b, path, eb, pb, FALSE, pool));
+ *child_baton = b;
+
+ if (b->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_baton,
+ base_revision, pool,
+ &b->wrapped_baton));
+ /* Note that for the update editor, the open_directory above will
+ flush the logs of pb's directory, which might be important for
+ this svn_wc_entry call. */
+
+ local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
+
+ SVN_ERR(ambient_read_info(&status, &kind, &depth,
+ eb->db, local_abspath, pool));
+
+ if (kind != svn_node_unknown
+ && status != svn_wc__db_status_not_present
+ && status != svn_wc__db_status_excluded
+ && status != svn_wc__db_status_server_excluded)
+ {
+ b->ambient_depth = depth;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *b = NULL;
+
+ SVN_ERR(make_file_baton(&b, pb, path, TRUE, pool));
+ *child_baton = b;
+
+ if (b->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ return eb->wrapped_editor->add_file(path, pb->wrapped_baton,
+ copyfrom_path, copyfrom_revision,
+ pool, &b->wrapped_baton);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *b;
+
+ SVN_ERR(make_file_baton(&b, pb, path, FALSE, pool));
+ *child_baton = b;
+ if (b->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ return eb->wrapped_editor->open_file(path, pb->wrapped_baton,
+ base_revision, pool,
+ &b->wrapped_baton);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ /* For filtered files, we just consume the textdelta. */
+ if (fb->ambiently_excluded)
+ {
+ *handler = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ return eb->wrapped_editor->apply_textdelta(fb->wrapped_baton,
+ base_checksum, pool,
+ handler, handler_baton);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ if (fb->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ return eb->wrapped_editor->close_file(fb->wrapped_baton,
+ text_checksum, pool);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+absent_file(const char *path,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+
+ if (pb->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ return eb->wrapped_editor->absent_file(path, pb->wrapped_baton, pool);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ if (db->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ return eb->wrapped_editor->close_directory(db->wrapped_baton, pool);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+absent_directory(const char *path,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+
+ /* Don't report absent items in filtered directories. */
+ if (pb->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ return eb->wrapped_editor->absent_directory(path, pb->wrapped_baton, pool);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct edit_baton *eb = fb->edit_baton;
+
+ if (fb->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ return eb->wrapped_editor->change_file_prop(fb->wrapped_baton,
+ name, value, pool);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+
+ if (db->ambiently_excluded)
+ return SVN_NO_ERROR;
+
+ return eb->wrapped_editor->change_dir_prop(db->wrapped_baton,
+ name, value, pool);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+ return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
+}
+
+svn_error_t *
+svn_wc__ambient_depth_filter_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc__db_t *db,
+ const char *anchor_abspath,
+ const char *target,
+ const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_edit_baton,
+ apr_pool_t *result_pool)
+{
+ svn_delta_editor_t *depth_filter_editor;
+ struct edit_baton *eb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath));
+
+ depth_filter_editor = svn_delta_default_editor(result_pool);
+ depth_filter_editor->set_target_revision = set_target_revision;
+ depth_filter_editor->open_root = open_root;
+ depth_filter_editor->delete_entry = delete_entry;
+ depth_filter_editor->add_directory = add_directory;
+ depth_filter_editor->open_directory = open_directory;
+ depth_filter_editor->change_dir_prop = change_dir_prop;
+ depth_filter_editor->close_directory = close_directory;
+ depth_filter_editor->absent_directory = absent_directory;
+ depth_filter_editor->add_file = add_file;
+ depth_filter_editor->open_file = open_file;
+ depth_filter_editor->apply_textdelta = apply_textdelta;
+ depth_filter_editor->change_file_prop = change_file_prop;
+ depth_filter_editor->close_file = close_file;
+ depth_filter_editor->absent_file = absent_file;
+ depth_filter_editor->close_edit = close_edit;
+
+ eb = apr_pcalloc(result_pool, sizeof(*eb));
+ eb->wrapped_editor = wrapped_editor;
+ eb->wrapped_edit_baton = wrapped_edit_baton;
+ eb->db = db;
+ eb->anchor_abspath = anchor_abspath;
+ eb->target = target;
+
+ *editor = depth_filter_editor;
+ *edit_baton = eb;
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/cleanup.c b/contrib/subversion/subversion/libsvn_wc/cleanup.c
new file mode 100644
index 0000000..8ffb87e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/cleanup.c
@@ -0,0 +1,231 @@
+/*
+ * cleanup.c: handle cleaning up workqueue items
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+
+#include "svn_wc.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_io.h"
+#include "svn_dirent_uri.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "lock.h"
+#include "workqueue.h"
+
+#include "private/svn_wc_private.h"
+#include "svn_private_config.h"
+
+
+/*** Recursively do log things. ***/
+
+/* */
+static svn_error_t *
+can_be_cleaned(int *wc_format,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__internal_check_wc(wc_format, db,
+ local_abspath, FALSE, scratch_pool));
+
+ /* a "version" of 0 means a non-wc directory */
+ if (*wc_format == 0)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy directory"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ if (*wc_format < SVN_WC__WC_NG_VERSION)
+ return svn_error_create(SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Log format too old, please use "
+ "Subversion 1.6 or earlier"));
+
+ return SVN_NO_ERROR;
+}
+
+/* Do a modifed check for LOCAL_ABSPATH, and all working children, to force
+ timestamp repair. */
+static svn_error_t *
+repair_timestamps(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ svn_wc__db_status_t status;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_deleted
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_not_present)
+ return SVN_NO_ERROR;
+
+ if (kind == svn_node_file
+ || kind == svn_node_symlink)
+ {
+ svn_boolean_t modified;
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified,
+ db, local_abspath, FALSE,
+ scratch_pool));
+ }
+ else if (kind == svn_node_dir)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const apr_array_header_t *children;
+ int i;
+
+ SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
+ local_abspath,
+ scratch_pool,
+ iterpool));
+ for (i = 0; i < children->nelts; ++i)
+ {
+ const char *child_abspath;
+
+ svn_pool_clear(iterpool);
+
+ child_abspath = svn_dirent_join(local_abspath,
+ APR_ARRAY_IDX(children, i,
+ const char *),
+ iterpool);
+
+ SVN_ERR(repair_timestamps(db, child_abspath,
+ cancel_func, cancel_baton, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+cleanup_internal(svn_wc__db_t *db,
+ const char *dir_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ int wc_format;
+ svn_boolean_t is_wcroot;
+ const char *lock_abspath;
+
+ /* Can we even work with this directory? */
+ SVN_ERR(can_be_cleaned(&wc_format, db, dir_abspath, scratch_pool));
+
+ /* We cannot obtain a lock on a directory that's within a locked
+ subtree, so always run cleanup from the lock owner. */
+ SVN_ERR(svn_wc__db_wclock_find_root(&lock_abspath, db, dir_abspath,
+ scratch_pool, scratch_pool));
+ if (lock_abspath)
+ dir_abspath = lock_abspath;
+ SVN_ERR(svn_wc__db_wclock_obtain(db, dir_abspath, -1, TRUE, scratch_pool));
+
+ /* Run our changes before the subdirectories. We may not have to recurse
+ if we blow away a subdir. */
+ if (wc_format >= SVN_WC__HAS_WORK_QUEUE)
+ SVN_ERR(svn_wc__wq_run(db, dir_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, dir_abspath, scratch_pool));
+
+#ifdef SVN_DEBUG
+ SVN_ERR(svn_wc__db_verify(db, dir_abspath, scratch_pool));
+#endif
+
+ /* Perform these operations if we lock the entire working copy.
+ Note that we really need to check a wcroot value and not
+ svn_wc__check_wcroot() as that function, will just return true
+ once we start sharing databases with externals.
+ */
+ if (is_wcroot)
+ {
+ /* Cleanup the tmp area of the admin subdir, if running the log has not
+ removed it! The logs have been run, so anything left here has no hope
+ of being useful. */
+ SVN_ERR(svn_wc__adm_cleanup_tmp_area(db, dir_abspath, scratch_pool));
+
+ /* Remove unreferenced pristine texts */
+ SVN_ERR(svn_wc__db_pristine_cleanup(db, dir_abspath, scratch_pool));
+ }
+
+ SVN_ERR(repair_timestamps(db, dir_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* All done, toss the lock */
+ SVN_ERR(svn_wc__db_wclock_release(db, dir_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ### possibly eliminate the WC_CTX parameter? callers really shouldn't
+ ### be doing anything *but* running a cleanup, and we need a special
+ ### DB anyway. ... *shrug* ... consider later. */
+svn_error_t *
+svn_wc_cleanup3(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* We need a DB that allows a non-empty work queue (though it *will*
+ auto-upgrade). We'll handle everything manually. */
+ SVN_ERR(svn_wc__db_open(&db,
+ NULL /* ### config */, FALSE, FALSE,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(cleanup_internal(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* The DAV cache suffers from flakiness from time to time, and the
+ pre-1.7 prescribed workarounds aren't as user-friendly in WC-NG. */
+ SVN_ERR(svn_wc__db_base_clear_dav_cache_recursive(db, local_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_vacuum(db, local_abspath, scratch_pool));
+
+ /* We're done with this DB, so proactively close it. */
+ SVN_ERR(svn_wc__db_close(db));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/conflicts.c b/contrib/subversion/subversion/libsvn_wc/conflicts.c
new file mode 100644
index 0000000..7a49188
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/conflicts.c
@@ -0,0 +1,3141 @@
+/*
+ * conflicts.c: routines for managing conflict data.
+ * NOTE: this code doesn't know where the conflict is
+ * actually stored.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+#include <apr_hash.h>
+#include <apr_errno.h>
+
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_wc.h"
+#include "svn_io.h"
+#include "svn_diff.h"
+
+#include "wc.h"
+#include "wc_db.h"
+#include "conflicts.h"
+#include "workqueue.h"
+#include "props.h"
+
+#include "private/svn_wc_private.h"
+#include "private/svn_skel.h"
+#include "private/svn_string_private.h"
+
+#include "svn_private_config.h"
+
+/* --------------------------------------------------------------------
+ * Conflict skel management
+ */
+
+svn_skel_t *
+svn_wc__conflict_skel_create(apr_pool_t *result_pool)
+{
+ svn_skel_t *conflict_skel = svn_skel__make_empty_list(result_pool);
+
+ /* Add empty CONFLICTS list */
+ svn_skel__prepend(svn_skel__make_empty_list(result_pool), conflict_skel);
+
+ /* Add empty WHY list */
+ svn_skel__prepend(svn_skel__make_empty_list(result_pool), conflict_skel);
+
+ return conflict_skel;
+}
+
+svn_error_t *
+svn_wc__conflict_skel_is_complete(svn_boolean_t *complete,
+ const svn_skel_t *conflict_skel)
+{
+ *complete = FALSE;
+
+ if (svn_skel__list_length(conflict_skel) < 2)
+ return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
+ _("Not a conflict skel"));
+
+ if (svn_skel__list_length(conflict_skel->children) < 2)
+ return SVN_NO_ERROR; /* WHY is not set */
+
+ if (svn_skel__list_length(conflict_skel->children->next) == 0)
+ return SVN_NO_ERROR; /* No conflict set */
+
+ *complete = TRUE;
+ return SVN_NO_ERROR;
+}
+
+/* Serialize a svn_wc_conflict_version_t before the existing data in skel */
+static svn_error_t *
+conflict__prepend_location(svn_skel_t *skel,
+ const svn_wc_conflict_version_t *location,
+ svn_boolean_t allow_NULL,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *loc;
+ SVN_ERR_ASSERT(location || allow_NULL);
+
+ if (!location)
+ {
+ svn_skel__prepend(svn_skel__make_empty_list(result_pool), skel);
+ return SVN_NO_ERROR;
+ }
+
+ /* ("subversion" repos_root_url repos_uuid repos_relpath rev kind) */
+ loc = svn_skel__make_empty_list(result_pool);
+
+ svn_skel__prepend_str(svn_node_kind_to_word(location->node_kind),
+ loc, result_pool);
+
+ svn_skel__prepend_int(location->peg_rev, loc, result_pool);
+
+ svn_skel__prepend_str(apr_pstrdup(result_pool, location->path_in_repos), loc,
+ result_pool);
+
+ if (!location->repos_uuid) /* Can theoretically be NULL */
+ svn_skel__prepend(svn_skel__make_empty_list(result_pool), loc);
+ else
+ svn_skel__prepend_str(location->repos_uuid, loc, result_pool);
+
+ svn_skel__prepend_str(apr_pstrdup(result_pool, location->repos_url), loc,
+ result_pool);
+
+ svn_skel__prepend_str(SVN_WC__CONFLICT_SRC_SUBVERSION, loc, result_pool);
+
+ svn_skel__prepend(loc, skel);
+ return SVN_NO_ERROR;
+}
+
+/* Deserialize a svn_wc_conflict_version_t from the skel.
+ Set *LOCATION to NULL when the data is not a svn_wc_conflict_version_t. */
+static svn_error_t *
+conflict__read_location(svn_wc_conflict_version_t **location,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *repos_relpath;
+ svn_revnum_t revision;
+ apr_int64_t v;
+ svn_node_kind_t node_kind; /* note that 'none' is a legitimate value */
+ const char *kind_str;
+
+ const svn_skel_t *c = skel->children;
+
+ if (!svn_skel__matches_atom(c, SVN_WC__CONFLICT_SRC_SUBVERSION))
+ {
+ *location = NULL;
+ return SVN_NO_ERROR;
+ }
+ c = c->next;
+
+ repos_root_url = apr_pstrmemdup(result_pool, c->data, c->len);
+ c = c->next;
+
+ if (c->is_atom)
+ repos_uuid = apr_pstrmemdup(result_pool, c->data, c->len);
+ else
+ repos_uuid = NULL;
+ c = c->next;
+
+ repos_relpath = apr_pstrmemdup(result_pool, c->data, c->len);
+ c = c->next;
+
+ SVN_ERR(svn_skel__parse_int(&v, c, scratch_pool));
+ revision = (svn_revnum_t)v;
+ c = c->next;
+
+ kind_str = apr_pstrmemdup(scratch_pool, c->data, c->len);
+ node_kind = svn_node_kind_from_word(kind_str);
+
+ *location = svn_wc_conflict_version_create2(repos_root_url,
+ repos_uuid,
+ repos_relpath,
+ revision,
+ node_kind,
+ result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Get the operation part of CONFLICT_SKELL or NULL if no operation is set
+ at this time */
+static svn_error_t *
+conflict__get_operation(svn_skel_t **why,
+ const svn_skel_t *conflict_skel)
+{
+ SVN_ERR_ASSERT(conflict_skel
+ && conflict_skel->children
+ && conflict_skel->children->next
+ && !conflict_skel->children->next->is_atom);
+
+ *why = conflict_skel->children;
+
+ if (!(*why)->children)
+ *why = NULL; /* Operation is not set yet */
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__conflict_skel_set_op_update(svn_skel_t *conflict_skel,
+ const svn_wc_conflict_version_t *original,
+ const svn_wc_conflict_version_t *target,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *why;
+ svn_skel_t *origins;
+
+ SVN_ERR_ASSERT(conflict_skel
+ && conflict_skel->children
+ && conflict_skel->children->next
+ && !conflict_skel->children->next->is_atom);
+
+ SVN_ERR(conflict__get_operation(&why, conflict_skel));
+
+ SVN_ERR_ASSERT(why == NULL); /* No operation set */
+
+ why = conflict_skel->children;
+
+ origins = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(conflict__prepend_location(origins, target, TRUE,
+ result_pool, scratch_pool));
+ SVN_ERR(conflict__prepend_location(origins, original, TRUE,
+ result_pool, scratch_pool));
+
+ svn_skel__prepend(origins, why);
+ svn_skel__prepend_str(SVN_WC__CONFLICT_OP_UPDATE, why, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__conflict_skel_set_op_switch(svn_skel_t *conflict_skel,
+ const svn_wc_conflict_version_t *original,
+ const svn_wc_conflict_version_t *target,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *why;
+ svn_skel_t *origins;
+
+ SVN_ERR_ASSERT(conflict_skel
+ && conflict_skel->children
+ && conflict_skel->children->next
+ && !conflict_skel->children->next->is_atom);
+
+ SVN_ERR(conflict__get_operation(&why, conflict_skel));
+
+ SVN_ERR_ASSERT(why == NULL); /* No operation set */
+
+ why = conflict_skel->children;
+
+ origins = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(conflict__prepend_location(origins, target, TRUE,
+ result_pool, scratch_pool));
+ SVN_ERR(conflict__prepend_location(origins, original, TRUE,
+ result_pool, scratch_pool));
+
+ svn_skel__prepend(origins, why);
+ svn_skel__prepend_str(SVN_WC__CONFLICT_OP_SWITCH, why, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__conflict_skel_set_op_merge(svn_skel_t *conflict_skel,
+ const svn_wc_conflict_version_t *left,
+ const svn_wc_conflict_version_t *right,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *why;
+ svn_skel_t *origins;
+
+ SVN_ERR_ASSERT(conflict_skel
+ && conflict_skel->children
+ && conflict_skel->children->next
+ && !conflict_skel->children->next->is_atom);
+
+ SVN_ERR(conflict__get_operation(&why, conflict_skel));
+
+ SVN_ERR_ASSERT(why == NULL); /* No operation set */
+
+ why = conflict_skel->children;
+
+ origins = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(conflict__prepend_location(origins, right, TRUE,
+ result_pool, scratch_pool));
+
+ SVN_ERR(conflict__prepend_location(origins, left, TRUE,
+ result_pool, scratch_pool));
+
+ svn_skel__prepend(origins, why);
+ svn_skel__prepend_str(SVN_WC__CONFLICT_OP_MERGE, why, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Gets the conflict data of the specified type CONFLICT_TYPE from
+ CONFLICT_SKEL, or NULL if no such conflict is recorded */
+static svn_error_t *
+conflict__get_conflict(svn_skel_t **conflict,
+ const svn_skel_t *conflict_skel,
+ const char *conflict_type)
+{
+ svn_skel_t *c;
+
+ SVN_ERR_ASSERT(conflict_skel
+ && conflict_skel->children
+ && conflict_skel->children->next
+ && !conflict_skel->children->next->is_atom);
+
+ for(c = conflict_skel->children->next->children;
+ c;
+ c = c->next)
+ {
+ if (svn_skel__matches_atom(c->children, conflict_type))
+ {
+ *conflict = c;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ *conflict = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__conflict_skel_add_text_conflict(svn_skel_t *conflict_skel,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *mine_abspath,
+ const char *their_old_abspath,
+ const char *their_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *text_conflict;
+ svn_skel_t *markers;
+
+ SVN_ERR(conflict__get_conflict(&text_conflict, conflict_skel,
+ SVN_WC__CONFLICT_KIND_TEXT));
+
+ SVN_ERR_ASSERT(!text_conflict); /* ### Use proper error? */
+
+ /* Current skel format
+ ("text"
+ (OLD MINE OLD-THEIRS THEIRS)) */
+
+ text_conflict = svn_skel__make_empty_list(result_pool);
+ markers = svn_skel__make_empty_list(result_pool);
+
+if (their_abspath)
+ {
+ const char *their_relpath;
+
+ SVN_ERR(svn_wc__db_to_relpath(&their_relpath,
+ db, wri_abspath, their_abspath,
+ result_pool, scratch_pool));
+ svn_skel__prepend_str(their_relpath, markers, result_pool);
+ }
+ else
+ svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers);
+
+ if (mine_abspath)
+ {
+ const char *mine_relpath;
+
+ SVN_ERR(svn_wc__db_to_relpath(&mine_relpath,
+ db, wri_abspath, mine_abspath,
+ result_pool, scratch_pool));
+ svn_skel__prepend_str(mine_relpath, markers, result_pool);
+ }
+ else
+ svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers);
+
+ if (their_old_abspath)
+ {
+ const char *original_relpath;
+
+ SVN_ERR(svn_wc__db_to_relpath(&original_relpath,
+ db, wri_abspath, their_old_abspath,
+ result_pool, scratch_pool));
+ svn_skel__prepend_str(original_relpath, markers, result_pool);
+ }
+ else
+ svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers);
+
+ svn_skel__prepend(markers, text_conflict);
+ svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_TEXT, text_conflict,
+ result_pool);
+
+ /* And add it to the conflict skel */
+ svn_skel__prepend(text_conflict, conflict_skel->children->next);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__conflict_skel_add_prop_conflict(svn_skel_t *conflict_skel,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *marker_abspath,
+ const apr_hash_t *mine_props,
+ const apr_hash_t *their_old_props,
+ const apr_hash_t *their_props,
+ const apr_hash_t *conflicted_prop_names,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *prop_conflict;
+ svn_skel_t *props;
+ svn_skel_t *conflict_names;
+ svn_skel_t *markers;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel,
+ SVN_WC__CONFLICT_KIND_PROP));
+
+ SVN_ERR_ASSERT(!prop_conflict); /* ### Use proper error? */
+
+ /* This function currently implements:
+ ("prop"
+ ("marker_relpath")
+ prop-conflicted_prop_names
+ old-props
+ mine-props
+ their-props)
+ NULL lists are recorded as "" */
+ /* ### Seems that this may not match what we read out. Read-out of
+ * 'theirs-old' comes as NULL. */
+
+ prop_conflict = svn_skel__make_empty_list(result_pool);
+
+ if (their_props)
+ {
+ SVN_ERR(svn_skel__unparse_proplist(&props, their_props, result_pool));
+ svn_skel__prepend(props, prop_conflict);
+ }
+ else
+ svn_skel__prepend_str("", prop_conflict, result_pool); /* No their_props */
+
+ if (mine_props)
+ {
+ SVN_ERR(svn_skel__unparse_proplist(&props, mine_props, result_pool));
+ svn_skel__prepend(props, prop_conflict);
+ }
+ else
+ svn_skel__prepend_str("", prop_conflict, result_pool); /* No mine_props */
+
+ if (their_old_props)
+ {
+ SVN_ERR(svn_skel__unparse_proplist(&props, their_old_props,
+ result_pool));
+ svn_skel__prepend(props, prop_conflict);
+ }
+ else
+ svn_skel__prepend_str("", prop_conflict, result_pool); /* No old_props */
+
+ conflict_names = svn_skel__make_empty_list(result_pool);
+ for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)conflicted_prop_names);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_skel__prepend_str(apr_pstrdup(result_pool,
+ svn__apr_hash_index_key(hi)),
+ conflict_names,
+ result_pool);
+ }
+ svn_skel__prepend(conflict_names, prop_conflict);
+
+ markers = svn_skel__make_empty_list(result_pool);
+
+ if (marker_abspath)
+ {
+ const char *marker_relpath;
+ SVN_ERR(svn_wc__db_to_relpath(&marker_relpath, db, wri_abspath,
+ marker_abspath,
+ result_pool, scratch_pool));
+
+ svn_skel__prepend_str(marker_relpath, markers, result_pool);
+ }
+/*else // ### set via svn_wc__conflict_create_markers
+ svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers);*/
+
+ svn_skel__prepend(markers, prop_conflict);
+
+ svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_PROP, prop_conflict, result_pool);
+
+ /* And add it to the conflict skel */
+ svn_skel__prepend(prop_conflict, conflict_skel->children->next);
+
+ return SVN_NO_ERROR;
+}
+
+/* A map for svn_wc_conflict_reason_t values. */
+static const svn_token_map_t local_change_map[] =
+{
+ { "edited", svn_wc_conflict_reason_edited },
+ { "obstructed", svn_wc_conflict_reason_obstructed },
+ { "deleted", svn_wc_conflict_reason_deleted },
+ { "missing", svn_wc_conflict_reason_missing },
+ { "unversioned", svn_wc_conflict_reason_unversioned },
+ { "added", svn_wc_conflict_reason_added },
+ { "replaced", svn_wc_conflict_reason_replaced },
+ { "moved-away", svn_wc_conflict_reason_moved_away },
+ { "moved-here", svn_wc_conflict_reason_moved_here },
+ { NULL }
+};
+
+static const svn_token_map_t incoming_change_map[] =
+{
+ { "edited", svn_wc_conflict_action_edit },
+ { "added", svn_wc_conflict_action_add },
+ { "deleted", svn_wc_conflict_action_delete },
+ { "replaced", svn_wc_conflict_action_replace },
+ { NULL }
+};
+
+svn_error_t *
+svn_wc__conflict_skel_add_tree_conflict(svn_skel_t *conflict_skel,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ svn_wc_conflict_reason_t local_change,
+ svn_wc_conflict_action_t incoming_change,
+ const char *move_src_op_root_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *tree_conflict;
+ svn_skel_t *markers;
+
+ SVN_ERR(conflict__get_conflict(&tree_conflict, conflict_skel,
+ SVN_WC__CONFLICT_KIND_TREE));
+
+ SVN_ERR_ASSERT(!tree_conflict); /* ### Use proper error? */
+
+ SVN_ERR_ASSERT(local_change == svn_wc_conflict_reason_moved_away
+ || !move_src_op_root_abspath); /* ### Use proper error? */
+
+ tree_conflict = svn_skel__make_empty_list(result_pool);
+
+ if (local_change == svn_wc_conflict_reason_moved_away
+ && move_src_op_root_abspath)
+ {
+ const char *move_src_op_root_relpath;
+
+ SVN_ERR(svn_wc__db_to_relpath(&move_src_op_root_relpath,
+ db, wri_abspath,
+ move_src_op_root_abspath,
+ result_pool, scratch_pool));
+
+ svn_skel__prepend_str(move_src_op_root_relpath, tree_conflict,
+ result_pool);
+ }
+
+ svn_skel__prepend_str(
+ svn_token__to_word(incoming_change_map, incoming_change),
+ tree_conflict, result_pool);
+
+ svn_skel__prepend_str(
+ svn_token__to_word(local_change_map, local_change),
+ tree_conflict, result_pool);
+
+ /* Tree conflicts have no marker files */
+ markers = svn_skel__make_empty_list(result_pool);
+ svn_skel__prepend(markers, tree_conflict);
+
+ svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_TREE, tree_conflict,
+ result_pool);
+
+ /* And add it to the conflict skel */
+ svn_skel__prepend(tree_conflict, conflict_skel->children->next);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__conflict_skel_resolve(svn_boolean_t *completely_resolved,
+ svn_skel_t *conflict_skel,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ svn_boolean_t resolve_text,
+ const char *resolve_prop,
+ svn_boolean_t resolve_tree,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *op;
+ svn_skel_t **pconflict;
+ SVN_ERR(conflict__get_operation(&op, conflict_skel));
+
+ if (!op)
+ return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
+ _("Not a completed conflict skel"));
+
+ /* We are going to drop items from a linked list. Instead of keeping
+ a pointer to the item we want to drop we store a pointer to the
+ pointer of what we may drop, to allow setting it to the next item. */
+
+ pconflict = &(conflict_skel->children->next->children);
+ while (*pconflict)
+ {
+ svn_skel_t *c = (*pconflict)->children;
+
+ if (resolve_text
+ && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_TEXT))
+ {
+ /* Remove the text conflict from the linked list */
+ *pconflict = (*pconflict)->next;
+ continue;
+ }
+ else if (resolve_prop
+ && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_PROP))
+ {
+ svn_skel_t **ppropnames = &(c->next->next->children);
+
+ if (resolve_prop[0] == '\0')
+ *ppropnames = NULL; /* remove all conflicted property names */
+ else
+ while (*ppropnames)
+ {
+ if (svn_skel__matches_atom(*ppropnames, resolve_prop))
+ {
+ *ppropnames = (*ppropnames)->next;
+ break;
+ }
+ ppropnames = &((*ppropnames)->next);
+ }
+
+ /* If no conflicted property names left */
+ if (!c->next->next->children)
+ {
+ /* Remove the propery conflict skel from the linked list */
+ *pconflict = (*pconflict)->next;
+ continue;
+ }
+ }
+ else if (resolve_tree
+ && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_TREE))
+ {
+ /* Remove the tree conflict from the linked list */
+ *pconflict = (*pconflict)->next;
+ continue;
+ }
+
+ pconflict = &((*pconflict)->next);
+ }
+
+ if (completely_resolved)
+ {
+ /* Nice, we can just call the complete function */
+ svn_boolean_t complete_conflict;
+ SVN_ERR(svn_wc__conflict_skel_is_complete(&complete_conflict,
+ conflict_skel));
+
+ *completely_resolved = !complete_conflict;
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* A map for svn_wc_operation_t values. */
+static const svn_token_map_t operation_map[] =
+{
+ { "", svn_wc_operation_none },
+ { SVN_WC__CONFLICT_OP_UPDATE, svn_wc_operation_update },
+ { SVN_WC__CONFLICT_OP_SWITCH, svn_wc_operation_switch },
+ { SVN_WC__CONFLICT_OP_MERGE, svn_wc_operation_merge },
+ { NULL }
+};
+
+svn_error_t *
+svn_wc__conflict_read_info(svn_wc_operation_t *operation,
+ const apr_array_header_t **locations,
+ svn_boolean_t *text_conflicted,
+ svn_boolean_t *prop_conflicted,
+ svn_boolean_t *tree_conflicted,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *op;
+ const svn_skel_t *c;
+
+ SVN_ERR(conflict__get_operation(&op, conflict_skel));
+
+ if (!op)
+ return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
+ _("Not a completed conflict skel"));
+
+ c = op->children;
+ if (operation)
+ {
+ int value = svn_token__from_mem(operation_map, c->data, c->len);
+
+ if (value != SVN_TOKEN_UNKNOWN)
+ *operation = value;
+ else
+ *operation = svn_wc_operation_none;
+ }
+ c = c->next;
+
+ if (locations && c->children)
+ {
+ const svn_skel_t *loc_skel;
+ svn_wc_conflict_version_t *loc;
+ apr_array_header_t *locs = apr_array_make(result_pool, 2, sizeof(loc));
+
+ for (loc_skel = c->children; loc_skel; loc_skel = loc_skel->next)
+ {
+ SVN_ERR(conflict__read_location(&loc, loc_skel, result_pool,
+ scratch_pool));
+
+ APR_ARRAY_PUSH(locs, svn_wc_conflict_version_t *) = loc;
+ }
+
+ *locations = locs;
+ }
+ else if (locations)
+ *locations = NULL;
+
+ if (text_conflicted)
+ {
+ svn_skel_t *c_skel;
+ SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel,
+ SVN_WC__CONFLICT_KIND_TEXT));
+
+ *text_conflicted = (c_skel != NULL);
+ }
+
+ if (prop_conflicted)
+ {
+ svn_skel_t *c_skel;
+ SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel,
+ SVN_WC__CONFLICT_KIND_PROP));
+
+ *prop_conflicted = (c_skel != NULL);
+ }
+
+ if (tree_conflicted)
+ {
+ svn_skel_t *c_skel;
+ SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel,
+ SVN_WC__CONFLICT_KIND_TREE));
+
+ *tree_conflicted = (c_skel != NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__conflict_read_text_conflict(const char **mine_abspath,
+ const char **their_old_abspath,
+ const char **their_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *text_conflict;
+ const svn_skel_t *m;
+
+ SVN_ERR(conflict__get_conflict(&text_conflict, conflict_skel,
+ SVN_WC__CONFLICT_KIND_TEXT));
+
+ if (!text_conflict)
+ return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set"));
+
+ m = text_conflict->children->next->children;
+
+ if (their_old_abspath)
+ {
+ if (m->is_atom)
+ {
+ const char *original_relpath;
+
+ original_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len);
+ SVN_ERR(svn_wc__db_from_relpath(their_old_abspath,
+ db, wri_abspath, original_relpath,
+ result_pool, scratch_pool));
+ }
+ else
+ *their_old_abspath = NULL;
+ }
+ m = m->next;
+
+ if (mine_abspath)
+ {
+ if (m->is_atom)
+ {
+ const char *mine_relpath;
+
+ mine_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len);
+ SVN_ERR(svn_wc__db_from_relpath(mine_abspath,
+ db, wri_abspath, mine_relpath,
+ result_pool, scratch_pool));
+ }
+ else
+ *mine_abspath = NULL;
+ }
+ m = m->next;
+
+ if (their_abspath)
+ {
+ if (m->is_atom)
+ {
+ const char *their_relpath;
+
+ their_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len);
+ SVN_ERR(svn_wc__db_from_relpath(their_abspath,
+ db, wri_abspath, their_relpath,
+ result_pool, scratch_pool));
+ }
+ else
+ *their_abspath = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__conflict_read_prop_conflict(const char **marker_abspath,
+ apr_hash_t **mine_props,
+ apr_hash_t **their_old_props,
+ apr_hash_t **their_props,
+ apr_hash_t **conflicted_prop_names,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *prop_conflict;
+ const svn_skel_t *c;
+
+ SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel,
+ SVN_WC__CONFLICT_KIND_PROP));
+
+ if (!prop_conflict)
+ return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set"));
+
+ c = prop_conflict->children;
+
+ c = c->next; /* Skip "prop" */
+
+ /* Get marker file */
+ if (marker_abspath)
+ {
+ const char *marker_relpath;
+
+ if (c->children && c->children->is_atom)
+ {
+ marker_relpath = apr_pstrmemdup(result_pool, c->children->data,
+ c->children->len);
+
+ SVN_ERR(svn_wc__db_from_relpath(marker_abspath, db, wri_abspath,
+ marker_relpath,
+ result_pool, scratch_pool));
+ }
+ else
+ *marker_abspath = NULL;
+ }
+ c = c->next;
+
+ /* Get conflicted properties */
+ if (conflicted_prop_names)
+ {
+ const svn_skel_t *name;
+ *conflicted_prop_names = apr_hash_make(result_pool);
+
+ for (name = c->children; name; name = name->next)
+ {
+ svn_hash_sets(*conflicted_prop_names,
+ apr_pstrmemdup(result_pool, name->data, name->len),
+ "");
+ }
+ }
+ c = c->next;
+
+ /* Get original properties */
+ if (their_old_props)
+ {
+ if (c->is_atom)
+ *their_old_props = apr_hash_make(result_pool);
+ else
+ SVN_ERR(svn_skel__parse_proplist(their_old_props, c, result_pool));
+ }
+ c = c->next;
+
+ /* Get mine properties */
+ if (mine_props)
+ {
+ if (c->is_atom)
+ *mine_props = apr_hash_make(result_pool);
+ else
+ SVN_ERR(svn_skel__parse_proplist(mine_props, c, result_pool));
+ }
+ c = c->next;
+
+ /* Get their properties */
+ if (their_props)
+ {
+ if (c->is_atom)
+ *their_props = apr_hash_make(result_pool);
+ else
+ SVN_ERR(svn_skel__parse_proplist(their_props, c, result_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__conflict_read_tree_conflict(svn_wc_conflict_reason_t *local_change,
+ svn_wc_conflict_action_t *incoming_change,
+ const char **move_src_op_root_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *tree_conflict;
+ const svn_skel_t *c;
+ svn_boolean_t is_moved_away = FALSE;
+
+ SVN_ERR(conflict__get_conflict(&tree_conflict, conflict_skel,
+ SVN_WC__CONFLICT_KIND_TREE));
+
+ if (!tree_conflict)
+ return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set"));
+
+ c = tree_conflict->children;
+
+ c = c->next; /* Skip "tree" */
+
+ c = c->next; /* Skip markers */
+
+ {
+ int value = svn_token__from_mem(local_change_map, c->data, c->len);
+
+ if (local_change)
+ {
+ if (value != SVN_TOKEN_UNKNOWN)
+ *local_change = value;
+ else
+ *local_change = svn_wc_conflict_reason_edited;
+ }
+
+ is_moved_away = (value == svn_wc_conflict_reason_moved_away);
+ }
+ c = c->next;
+
+ if (incoming_change)
+ {
+ int value = svn_token__from_mem(incoming_change_map, c->data, c->len);
+
+ if (value != SVN_TOKEN_UNKNOWN)
+ *incoming_change = value;
+ else
+ *incoming_change = svn_wc_conflict_action_edit;
+ }
+
+ c = c->next;
+
+ if (move_src_op_root_abspath)
+ {
+ /* Only set for update and switch tree conflicts */
+ if (c && is_moved_away)
+ {
+ const char *move_src_op_root_relpath
+ = apr_pstrmemdup(scratch_pool, c->data, c->len);
+
+ SVN_ERR(svn_wc__db_from_relpath(move_src_op_root_abspath,
+ db, wri_abspath,
+ move_src_op_root_relpath,
+ result_pool, scratch_pool));
+ }
+ else
+ *move_src_op_root_abspath = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__conflict_read_markers(const apr_array_header_t **markers,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *conflict;
+ apr_array_header_t *list = NULL;
+
+ SVN_ERR_ASSERT(conflict_skel != NULL);
+
+ /* Walk the conflicts */
+ for (conflict = conflict_skel->children->next->children;
+ conflict;
+ conflict = conflict->next)
+ {
+ const svn_skel_t *marker;
+
+ /* Get the list of markers stored per conflict */
+ for (marker = conflict->children->next->children;
+ marker;
+ marker = marker->next)
+ {
+ /* Skip placeholders */
+ if (! marker->is_atom)
+ continue;
+
+ if (! list)
+ list = apr_array_make(result_pool, 4, sizeof(const char *));
+
+ SVN_ERR(svn_wc__db_from_relpath(
+ &APR_ARRAY_PUSH(list, const char*),
+ db, wri_abspath,
+ apr_pstrmemdup(scratch_pool, marker->data,
+ marker->len),
+ result_pool, scratch_pool));
+ }
+ }
+ *markers = list;
+
+ return SVN_NO_ERROR;
+}
+
+/* --------------------------------------------------------------------
+ */
+/* Helper for svn_wc__conflict_create_markers */
+static svn_skel_t *
+prop_conflict_skel_new(apr_pool_t *result_pool)
+{
+ svn_skel_t *operation = svn_skel__make_empty_list(result_pool);
+ svn_skel_t *result = svn_skel__make_empty_list(result_pool);
+
+ svn_skel__prepend(operation, result);
+ return result;
+}
+
+
+/* Helper for prop_conflict_skel_add */
+static void
+prepend_prop_value(const svn_string_t *value,
+ svn_skel_t *skel,
+ apr_pool_t *result_pool)
+{
+ svn_skel_t *value_skel = svn_skel__make_empty_list(result_pool);
+
+ if (value != NULL)
+ {
+ const void *dup = apr_pmemdup(result_pool, value->data, value->len);
+
+ svn_skel__prepend(svn_skel__mem_atom(dup, value->len, result_pool),
+ value_skel);
+ }
+
+ svn_skel__prepend(value_skel, skel);
+}
+
+
+/* Helper for svn_wc__conflict_create_markers */
+static svn_error_t *
+prop_conflict_skel_add(
+ svn_skel_t *skel,
+ const char *prop_name,
+ const svn_string_t *original_value,
+ const svn_string_t *mine_value,
+ const svn_string_t *incoming_value,
+ const svn_string_t *incoming_base_value,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *prop_skel = svn_skel__make_empty_list(result_pool);
+
+ /* ### check that OPERATION has been filled in. */
+
+ /* See notes/wc-ng/conflict-storage */
+ prepend_prop_value(incoming_base_value, prop_skel, result_pool);
+ prepend_prop_value(incoming_value, prop_skel, result_pool);
+ prepend_prop_value(mine_value, prop_skel, result_pool);
+ prepend_prop_value(original_value, prop_skel, result_pool);
+ svn_skel__prepend_str(apr_pstrdup(result_pool, prop_name), prop_skel,
+ result_pool);
+ svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_PROP, prop_skel, result_pool);
+
+ /* Now we append PROP_SKEL to the end of the provided conflict SKEL. */
+ svn_skel__append(skel, prop_skel);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__conflict_create_markers(svn_skel_t **work_items,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t prop_conflicted;
+ svn_wc_operation_t operation;
+ *work_items = NULL;
+
+ SVN_ERR(svn_wc__conflict_read_info(&operation, NULL,
+ NULL, &prop_conflicted, NULL,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ if (prop_conflicted)
+ {
+ const char *marker_abspath = NULL;
+ svn_node_kind_t kind;
+ const char *marker_dir;
+ const char *marker_name;
+ const char *marker_relpath;
+
+ /* Ok, currently we have to do a few things for property conflicts:
+ - Create a marker file
+ - Create a WQ item that sets the marker name
+ - Create a WQ item that fills the marker with the expected data
+
+ This can be simplified once we really store conflict_skel in wc.db */
+
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
+
+ if (kind == svn_node_dir)
+ {
+ marker_dir = local_abspath;
+ marker_name = SVN_WC__THIS_DIR_PREJ;
+ }
+ else
+ svn_dirent_split(&marker_dir, &marker_name, local_abspath,
+ scratch_pool);
+
+ SVN_ERR(svn_io_open_uniquely_named(NULL, &marker_abspath,
+ marker_dir,
+ marker_name,
+ SVN_WC__PROP_REJ_EXT,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_to_relpath(&marker_relpath, db, local_abspath,
+ marker_abspath, result_pool, result_pool));
+
+ /* And store the marker in the skel */
+ {
+ svn_skel_t *prop_conflict;
+ SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel,
+ SVN_WC__CONFLICT_KIND_PROP));
+
+ svn_skel__prepend_str(marker_relpath, prop_conflict->children->next,
+ result_pool);
+ }
+
+ /* Store the data in the WQ item in the same format used as 1.7.
+ Once we store the data in DB it is easier to just read it back
+ from the workqueue */
+ {
+ svn_skel_t *prop_data;
+ apr_hash_index_t *hi;
+ apr_hash_t *old_props;
+ apr_hash_t *mine_props;
+ apr_hash_t *their_original_props;
+ apr_hash_t *their_props;
+ apr_hash_t *conflicted_props;
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(NULL,
+ &mine_props,
+ &their_original_props,
+ &their_props,
+ &conflicted_props,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool,
+ scratch_pool));
+
+ if (operation == svn_wc_operation_merge)
+ SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ old_props = their_original_props;
+
+ prop_data = prop_conflict_skel_new(result_pool);
+
+ for (hi = apr_hash_first(scratch_pool, conflicted_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+
+ SVN_ERR(prop_conflict_skel_add(
+ prop_data, propname,
+ old_props
+ ? svn_hash_gets(old_props, propname)
+ : NULL,
+ mine_props
+ ? svn_hash_gets(mine_props, propname)
+ : NULL,
+ their_props
+ ? svn_hash_gets(their_props, propname)
+ : NULL,
+ their_original_props
+ ? svn_hash_gets(their_original_props, propname)
+ : NULL,
+ result_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__wq_build_prej_install(work_items,
+ db, local_abspath,
+ prop_data,
+ scratch_pool, scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper function for the three apply_* functions below, used when
+ * merging properties together.
+ *
+ * Given property PROPNAME on LOCAL_ABSPATH, and four possible property
+ * values, generate four tmpfiles and pass them to CONFLICT_FUNC callback.
+ * This gives the client an opportunity to interactively resolve the
+ * property conflict.
+ *
+ * BASE_VAL/WORKING_VAL represent the current state of the working
+ * copy, and INCOMING_OLD_VAL/INCOMING_NEW_VAL represents the incoming
+ * propchange. Any of these values might be NULL, indicating either
+ * non-existence or intent-to-delete.
+ *
+ * If the callback isn't available, or if it responds with
+ * 'choose_postpone', then set *CONFLICT_REMAINS to TRUE and return.
+ *
+ * If the callback responds with a choice of 'base', 'theirs', 'mine',
+ * or 'merged', then install the proper value into ACTUAL_PROPS and
+ * set *CONFLICT_REMAINS to FALSE.
+ */
+static svn_error_t *
+generate_propconflict(svn_boolean_t *conflict_remains,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_operation_t operation,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ const char *propname,
+ const svn_string_t *base_val,
+ const svn_string_t *working_val,
+ const svn_string_t *incoming_old_val,
+ const svn_string_t *incoming_new_val,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_conflict_result_t *result = NULL;
+ svn_wc_conflict_description2_t *cdesc;
+ const char *dirpath = svn_dirent_dirname(local_abspath, scratch_pool);
+ svn_node_kind_t kind;
+ const svn_string_t *new_value = NULL;
+
+ SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath,
+ FALSE /* allow_missing */,
+ FALSE /* show_deleted */,
+ FALSE /* show_hidden */,
+ scratch_pool));
+
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ cdesc = svn_wc_conflict_description_create_prop2(
+ local_abspath,
+ (kind == svn_node_dir) ? svn_node_dir : svn_node_file,
+ propname, scratch_pool);
+
+ cdesc->operation = operation;
+ cdesc->src_left_version = left_version;
+ cdesc->src_right_version = right_version;
+
+ /* Create a tmpfile for each of the string_t's we've got. */
+ if (working_val)
+ {
+ const char *file_name;
+
+ SVN_ERR(svn_io_write_unique(&file_name, dirpath, working_val->data,
+ working_val->len,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool));
+ cdesc->my_abspath = svn_dirent_join(dirpath, file_name, scratch_pool);
+ }
+
+ if (incoming_new_val)
+ {
+ const char *file_name;
+
+ SVN_ERR(svn_io_write_unique(&file_name, dirpath, incoming_new_val->data,
+ incoming_new_val->len,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool));
+ cdesc->their_abspath = svn_dirent_join(dirpath, file_name, scratch_pool);
+ }
+
+ if (!base_val && !incoming_old_val)
+ {
+ /* If base and old are both NULL, then that's fine, we just let
+ base_file stay NULL as-is. Both agents are attempting to add a
+ new property. */
+ }
+
+ else if ((base_val && !incoming_old_val)
+ || (!base_val && incoming_old_val))
+ {
+ /* If only one of base and old are defined, then we've got a
+ situation where one agent is attempting to add the property
+ for the first time, and the other agent is changing a
+ property it thinks already exists. In this case, we return
+ whichever older-value happens to be defined, so that the
+ conflict-callback can still attempt a 3-way merge. */
+
+ const svn_string_t *conflict_base_val = base_val ? base_val
+ : incoming_old_val;
+ const char *file_name;
+
+ SVN_ERR(svn_io_write_unique(&file_name, dirpath,
+ conflict_base_val->data,
+ conflict_base_val->len,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool));
+ cdesc->base_abspath = svn_dirent_join(dirpath, file_name, scratch_pool);
+ }
+
+ else /* base and old are both non-NULL */
+ {
+ const svn_string_t *conflict_base_val;
+ const char *file_name;
+
+ if (! svn_string_compare(base_val, incoming_old_val))
+ {
+ /* What happens if 'base' and 'old' don't match up? In an
+ ideal situation, they would. But if they don't, this is
+ a classic example of a patch 'hunk' failing to apply due
+ to a lack of context. For example: imagine that the user
+ is busy changing the property from a value of "cat" to
+ "dog", but the incoming propchange wants to change the
+ same property value from "red" to "green". Total context
+ mismatch.
+
+ HOWEVER: we can still pass one of the two base values as
+ 'base_file' to the callback anyway. It's still useful to
+ present the working and new values to the user to
+ compare. */
+
+ if (working_val && svn_string_compare(base_val, working_val))
+ conflict_base_val = incoming_old_val;
+ else
+ conflict_base_val = base_val;
+ }
+ else
+ {
+ conflict_base_val = base_val;
+ }
+
+ SVN_ERR(svn_io_write_unique(&file_name, dirpath, conflict_base_val->data,
+ conflict_base_val->len,
+ svn_io_file_del_on_pool_cleanup, scratch_pool));
+ cdesc->base_abspath = svn_dirent_join(dirpath, file_name, scratch_pool);
+
+ if (working_val && incoming_new_val)
+ {
+ svn_stream_t *mergestream;
+ svn_diff_t *diff;
+ svn_diff_file_options_t *options =
+ svn_diff_file_options_create(scratch_pool);
+
+ SVN_ERR(svn_stream_open_unique(&mergestream, &cdesc->merged_file,
+ NULL, svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_diff_mem_string_diff3(&diff, conflict_base_val,
+ working_val,
+ incoming_new_val, options, scratch_pool));
+ SVN_ERR(svn_diff_mem_string_output_merge2
+ (mergestream, diff, conflict_base_val, working_val,
+ incoming_new_val, NULL, NULL, NULL, NULL,
+ svn_diff_conflict_display_modified_latest, scratch_pool));
+ SVN_ERR(svn_stream_close(mergestream));
+ }
+ }
+
+ if (!incoming_old_val && incoming_new_val)
+ cdesc->action = svn_wc_conflict_action_add;
+ else if (incoming_old_val && !incoming_new_val)
+ cdesc->action = svn_wc_conflict_action_delete;
+ else
+ cdesc->action = svn_wc_conflict_action_edit;
+
+ if (base_val && !working_val)
+ cdesc->reason = svn_wc_conflict_reason_deleted;
+ else if (!base_val && working_val)
+ cdesc->reason = svn_wc_conflict_reason_obstructed;
+ else
+ cdesc->reason = svn_wc_conflict_reason_edited;
+
+ /* Invoke the interactive conflict callback. */
+ {
+ SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool,
+ scratch_pool));
+ }
+ if (result == NULL)
+ {
+ *conflict_remains = TRUE;
+ return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ NULL, _("Conflict callback violated API:"
+ " returned no results"));
+ }
+
+
+ switch (result->choice)
+ {
+ default:
+ case svn_wc_conflict_choose_postpone:
+ {
+ *conflict_remains = TRUE;
+ break;
+ }
+ case svn_wc_conflict_choose_mine_full:
+ {
+ /* No need to change actual_props; it already contains working_val */
+ *conflict_remains = FALSE;
+ new_value = working_val;
+ break;
+ }
+ /* I think _mine_full and _theirs_full are appropriate for prop
+ behavior as well as the text behavior. There should even be
+ analogous behaviors for _mine and _theirs when those are
+ ready, namely: fold in all non-conflicting prop changes, and
+ then choose _mine side or _theirs side for conflicting ones. */
+ case svn_wc_conflict_choose_theirs_full:
+ {
+ *conflict_remains = FALSE;
+ new_value = incoming_new_val;
+ break;
+ }
+ case svn_wc_conflict_choose_base:
+ {
+ *conflict_remains = FALSE;
+ new_value = base_val;
+ break;
+ }
+ case svn_wc_conflict_choose_merged:
+ {
+ svn_stringbuf_t *merged_stringbuf;
+
+ if (!cdesc->merged_file && !result->merged_file)
+ return svn_error_create
+ (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ NULL, _("Conflict callback violated API:"
+ " returned no merged file"));
+
+ SVN_ERR(svn_stringbuf_from_file2(&merged_stringbuf,
+ result->merged_file ?
+ result->merged_file :
+ cdesc->merged_file,
+ scratch_pool));
+ new_value = svn_stringbuf__morph_into_string(merged_stringbuf);
+ *conflict_remains = FALSE;
+ break;
+ }
+ }
+
+ if (!*conflict_remains)
+ {
+ apr_hash_t *props;
+
+ /* For now, just set the property values. This should really do some of the
+ more advanced things from svn_wc_prop_set() */
+
+ SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, scratch_pool,
+ scratch_pool));
+
+ svn_hash_sets(props, propname, new_value);
+
+ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, props,
+ FALSE, NULL, NULL,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Resolve the text conflict on DB/LOCAL_ABSPATH in the manner specified
+ * by CHOICE.
+ *
+ * Set *WORK_ITEMS to new work items that will make the on-disk changes
+ * needed to complete the resolution (but not to mark it as resolved).
+ * Set *IS_RESOLVED to true if the conflicts are resolved; otherwise
+ * (which is only if CHOICE is 'postpone') to false.
+ *
+ * LEFT_ABSPATH, RIGHT_ABSPATH, and DETRANSLATED_TARGET are the
+ * input files to the 3-way merge that will be performed if CHOICE is
+ * 'theirs-conflict' or 'mine-conflict'. LEFT_ABSPATH is also the file
+ * that will be used if CHOICE is 'base', and RIGHT_ABSPATH if CHOICE is
+ * 'theirs-full'. MERGED_ABSPATH will be used if CHOICE is 'merged'.
+ *
+ * DETRANSLATED_TARGET is the detranslated version of 'mine' (see
+ * detranslate_wc_file() above). MERGE_OPTIONS are passed to the
+ * diff3 implementation in case a 3-way merge has to be carried out.
+ */
+static svn_error_t *
+eval_text_conflict_func_result(svn_skel_t **work_items,
+ svn_boolean_t *is_resolved,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_conflict_choice_t choice,
+ const apr_array_header_t *merge_options,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *merged_abspath,
+ const char *detranslated_target,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *install_from_abspath = NULL;
+ svn_boolean_t remove_source = FALSE;
+
+ *work_items = NULL;
+
+ switch (choice)
+ {
+ /* If the callback wants to use one of the fulltexts
+ to resolve the conflict, so be it.*/
+ case svn_wc_conflict_choose_base:
+ {
+ install_from_abspath = left_abspath;
+ *is_resolved = TRUE;
+ break;
+ }
+ case svn_wc_conflict_choose_theirs_full:
+ {
+ install_from_abspath = right_abspath;
+ *is_resolved = TRUE;
+ break;
+ }
+ case svn_wc_conflict_choose_mine_full:
+ {
+ install_from_abspath = detranslated_target;
+ *is_resolved = TRUE;
+ break;
+ }
+ case svn_wc_conflict_choose_theirs_conflict:
+ case svn_wc_conflict_choose_mine_conflict:
+ {
+ const char *chosen_abspath;
+ const char *temp_dir;
+ svn_stream_t *chosen_stream;
+ svn_diff_t *diff;
+ svn_diff_conflict_display_style_t style;
+ svn_diff_file_options_t *diff3_options;
+
+ diff3_options = svn_diff_file_options_create(scratch_pool);
+
+ if (merge_options)
+ SVN_ERR(svn_diff_file_options_parse(diff3_options,
+ merge_options,
+ scratch_pool));
+
+ style = choice == svn_wc_conflict_choose_theirs_conflict
+ ? svn_diff_conflict_display_latest
+ : svn_diff_conflict_display_modified;
+
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_unique(&chosen_stream, &chosen_abspath,
+ temp_dir, svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_diff_file_diff3_2(&diff,
+ left_abspath,
+ detranslated_target, right_abspath,
+ diff3_options, scratch_pool));
+ SVN_ERR(svn_diff_file_output_merge2(chosen_stream, diff,
+ left_abspath,
+ detranslated_target,
+ right_abspath,
+ /* markers ignored */
+ NULL, NULL,
+ NULL, NULL,
+ style,
+ scratch_pool));
+ SVN_ERR(svn_stream_close(chosen_stream));
+
+ install_from_abspath = chosen_abspath;
+ remove_source = TRUE;
+ *is_resolved = TRUE;
+ break;
+ }
+
+ /* For the case of 3-way file merging, we don't
+ really distinguish between these return values;
+ if the callback claims to have "generally
+ resolved" the situation, we still interpret
+ that as "OK, we'll assume the merged version is
+ good to use". */
+ case svn_wc_conflict_choose_merged:
+ {
+ install_from_abspath = merged_abspath;
+ *is_resolved = TRUE;
+ break;
+ }
+ case svn_wc_conflict_choose_postpone:
+ default:
+ {
+ /* Assume conflict remains. */
+ *is_resolved = FALSE;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ SVN_ERR_ASSERT(install_from_abspath != NULL);
+
+ {
+ svn_skel_t *work_item;
+
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item,
+ db, local_abspath,
+ install_from_abspath,
+ FALSE /* use_commit_times */,
+ FALSE /* record_fileinfo */,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ if (remove_source)
+ {
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item,
+ db, local_abspath,
+ install_from_abspath,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Create a new file in the same directory as LOCAL_ABSPATH, with the
+ same basename as LOCAL_ABSPATH, with a ".edited" extension, and set
+ *WORK_ITEM to a new work item that will copy and translate from the file
+ SOURCE_ABSPATH to that new file. It will be translated from repository-
+ normal form to working-copy form according to the versioned properties
+ of LOCAL_ABSPATH that are current when the work item is executed.
+
+ DB should have a write lock for the directory containing SOURCE.
+
+ Allocate *WORK_ITEM in RESULT_POOL. */
+static svn_error_t *
+save_merge_result(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *source_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *edited_copy_abspath;
+ const char *dir_abspath;
+ const char *filename;
+
+ svn_dirent_split(&dir_abspath, &filename, local_abspath, scratch_pool);
+
+ /* ### Should use preserved-conflict-file-exts. */
+ /* Create the .edited file within this file's DIR_ABSPATH */
+ SVN_ERR(svn_io_open_uniquely_named(NULL,
+ &edited_copy_abspath,
+ dir_abspath,
+ filename,
+ ".edited",
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__wq_build_file_copy_translated(work_item,
+ db, local_abspath,
+ source_abspath,
+ edited_copy_abspath,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Call the conflict resolver callback for a text conflict, and resolve
+ * the conflict if it tells us to do so.
+ *
+ * Assume that there is a text conflict on the path DB/LOCAL_ABSPATH.
+ *
+ * Call CONFLICT_FUNC with CONFLICT_BATON to find out whether and how
+ * it wants to resolve the conflict. Pass it a conflict description
+ * containing OPERATION, LEFT/RIGHT_ABSPATH, LEFT/RIGHT_VERSION,
+ * RESULT_TARGET and DETRANSLATED_TARGET.
+ *
+ * If the callback returns a resolution other than 'postpone', then
+ * perform that requested resolution and prepare to mark the conflict
+ * as resolved.
+ *
+ * Return *WORK_ITEMS that will do the on-disk work required to complete
+ * the resolution (but not to mark the conflict as resolved), and set
+ * *WAS_RESOLVED to true, if it was resolved. Set *WORK_ITEMS to NULL
+ * and *WAS_RESOLVED to FALSE otherwise.
+ *
+ * RESULT_TARGET is the path to the merged file produced by the internal
+ * or external 3-way merge, which may contain conflict markers, in
+ * repository normal form. DETRANSLATED_TARGET is the 'mine' version of
+ * the file, also in RNF.
+ */
+static svn_error_t *
+resolve_text_conflict(svn_skel_t **work_items,
+ svn_boolean_t *was_resolved,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_array_header_t *merge_options,
+ svn_wc_operation_t operation,
+ const char *left_abspath,
+ const char *right_abspath,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ const char *result_target,
+ const char *detranslated_target,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_conflict_result_t *result;
+ svn_skel_t *work_item;
+ svn_wc_conflict_description2_t *cdesc;
+ apr_hash_t *props;
+
+ *work_items = NULL;
+ *was_resolved = FALSE;
+
+ /* Give the conflict resolution callback a chance to clean
+ up the conflicts before we mark the file 'conflicted' */
+
+ SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ cdesc = svn_wc_conflict_description_create_text2(local_abspath,
+ scratch_pool);
+ cdesc->is_binary = FALSE;
+ cdesc->mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
+ cdesc->base_abspath = left_abspath;
+ cdesc->their_abspath = right_abspath;
+ cdesc->my_abspath = detranslated_target;
+ cdesc->merged_file = result_target;
+ cdesc->operation = operation;
+ cdesc->src_left_version = left_version;
+ cdesc->src_right_version = right_version;
+
+ SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool,
+ scratch_pool));
+ if (result == NULL)
+ return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Conflict callback violated API:"
+ " returned no results"));
+
+ if (result->save_merged)
+ {
+ SVN_ERR(save_merge_result(work_items,
+ db, local_abspath,
+ /* Look for callback's own
+ merged-file first: */
+ result->merged_file
+ ? result->merged_file
+ : result_target,
+ result_pool, scratch_pool));
+ }
+
+ if (result->choice != svn_wc_conflict_choose_postpone)
+ {
+ SVN_ERR(eval_text_conflict_func_result(&work_item,
+ was_resolved,
+ db, local_abspath,
+ result->choice,
+ merge_options,
+ left_abspath,
+ right_abspath,
+ /* ### Sure this is an abspath? */
+ result->merged_file
+ ? result->merged_file
+ : result_target,
+ detranslated_target,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+ }
+ else
+ *was_resolved = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+setup_tree_conflict_desc(svn_wc_conflict_description2_t **desc,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_operation_t operation,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ svn_wc_conflict_reason_t local_change,
+ svn_wc_conflict_action_t incoming_change,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t tc_kind;
+
+ if (left_version)
+ tc_kind = left_version->node_kind;
+ else if (right_version)
+ tc_kind = right_version->node_kind;
+ else
+ tc_kind = svn_node_file; /* Avoid assertion */
+
+ *desc = svn_wc_conflict_description_create_tree2(local_abspath, tc_kind,
+ operation,
+ left_version, right_version,
+ result_pool);
+ (*desc)->reason = local_change;
+ (*desc)->action = incoming_change;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__conflict_invoke_resolver(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflict_skel,
+ const apr_array_header_t *merge_options,
+ svn_wc_conflict_resolver_func2_t resolver_func,
+ void *resolver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ svn_boolean_t tree_conflicted;
+ svn_wc_operation_t operation;
+ const apr_array_header_t *locations;
+ const svn_wc_conflict_version_t *left_version = NULL;
+ const svn_wc_conflict_version_t *right_version = NULL;
+
+ SVN_ERR(svn_wc__conflict_read_info(&operation, &locations,
+ &text_conflicted, &prop_conflicted,
+ &tree_conflicted,
+ db, local_abspath, conflict_skel,
+ scratch_pool, scratch_pool));
+
+ if (locations && locations->nelts > 0)
+ left_version = APR_ARRAY_IDX(locations, 0, const svn_wc_conflict_version_t *);
+
+ if (locations && locations->nelts > 1)
+ right_version = APR_ARRAY_IDX(locations, 1, const svn_wc_conflict_version_t *);
+
+ /* Quick and dirty compatibility wrapper. My guess would be that most resolvers
+ would want to look at all properties at the same time.
+
+ ### svn currently only invokes this from the merge code to collect the list of
+ ### conflicted paths. Eventually this code will be the base for 'svn resolve'
+ ### and at that time the test coverage will improve
+ */
+ if (prop_conflicted)
+ {
+ apr_hash_t *old_props;
+ apr_hash_t *mine_props;
+ apr_hash_t *their_props;
+ apr_hash_t *old_their_props;
+ apr_hash_t *conflicted;
+ apr_pool_t *iterpool;
+ apr_hash_index_t *hi;
+ svn_boolean_t mark_resolved = TRUE;
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(NULL,
+ &mine_props,
+ &old_their_props,
+ &their_props,
+ &conflicted,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ if (operation == svn_wc_operation_merge)
+ SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ old_props = old_their_props;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, conflicted);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ svn_boolean_t conflict_remains = TRUE;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(generate_propconflict(&conflict_remains,
+ db, local_abspath,
+ operation,
+ left_version,
+ right_version,
+ propname,
+ old_props
+ ? svn_hash_gets(old_props, propname)
+ : NULL,
+ mine_props
+ ? svn_hash_gets(mine_props, propname)
+ : NULL,
+ old_their_props
+ ? svn_hash_gets(old_their_props, propname)
+ : NULL,
+ their_props
+ ? svn_hash_gets(their_props, propname)
+ : NULL,
+ resolver_func, resolver_baton,
+ iterpool));
+
+ if (conflict_remains)
+ mark_resolved = FALSE;
+ }
+
+ if (mark_resolved)
+ {
+ SVN_ERR(svn_wc__mark_resolved_prop_conflicts(db, local_abspath,
+ scratch_pool));
+ }
+ }
+
+ if (text_conflicted)
+ {
+ const char *mine_abspath;
+ const char *their_original_abspath;
+ const char *their_abspath;
+ svn_skel_t *work_items;
+ svn_boolean_t was_resolved;
+
+ SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath,
+ &their_original_abspath,
+ &their_abspath,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(resolve_text_conflict(&work_items, &was_resolved,
+ db, local_abspath,
+ merge_options,
+ operation,
+ their_original_abspath, their_abspath,
+ left_version, right_version,
+ local_abspath, mine_abspath,
+ resolver_func, resolver_baton,
+ scratch_pool, scratch_pool));
+
+ if (was_resolved)
+ {
+ if (work_items)
+ {
+ SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_items,
+ scratch_pool));
+ SVN_ERR(svn_wc__wq_run(db, local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath,
+ scratch_pool));
+ }
+ }
+
+ if (tree_conflicted)
+ {
+ svn_wc_conflict_reason_t local_change;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_result_t *result;
+ svn_wc_conflict_description2_t *desc;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change,
+ &incoming_change,
+ NULL,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(setup_tree_conflict_desc(&desc,
+ db, local_abspath,
+ operation, left_version, right_version,
+ local_change, incoming_change,
+ scratch_pool, scratch_pool));
+
+ /* Tell the resolver func about this conflict. */
+ SVN_ERR(resolver_func(&result, desc, resolver_baton, scratch_pool,
+ scratch_pool));
+
+ /* Ignore the result. We cannot apply it here since this code runs
+ * during an update or merge operation. Tree conflicts are always
+ * postponed and resolved after the operation has completed. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read all property conflicts contained in CONFLICT_SKEL into
+ * individual conflict descriptions, and append those descriptions
+ * to the CONFLICTS array.
+ *
+ * If NOT create_tempfiles, always create a legacy property conflict
+ * descriptor.
+ *
+ * Use NODE_KIND, OPERATION and shallow copies of LEFT_VERSION and
+ * RIGHT_VERSION, rather than reading them from CONFLICT_SKEL.
+ *
+ * Allocate results in RESULT_POOL. SCRATCH_POOL is used for temporary
+ * allocations. */
+static svn_error_t *
+read_prop_conflicts(apr_array_header_t *conflicts,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_skel_t *conflict_skel,
+ svn_boolean_t create_tempfiles,
+ svn_node_kind_t node_kind,
+ svn_wc_operation_t operation,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *prop_reject_file;
+ apr_hash_t *my_props;
+ apr_hash_t *their_old_props;
+ apr_hash_t *their_props;
+ apr_hash_t *conflicted_props;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_file,
+ &my_props,
+ &their_old_props,
+ &their_props,
+ &conflicted_props,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ if ((! create_tempfiles) || apr_hash_count(conflicted_props) == 0)
+ {
+ /* Legacy prop conflict with only a .reject file. */
+ svn_wc_conflict_description2_t *desc;
+
+ desc = svn_wc_conflict_description_create_prop2(local_abspath,
+ node_kind,
+ "", result_pool);
+
+ /* ### This should be changed. The prej file should be stored
+ * ### separately from the other files. We need to rev the
+ * ### conflict description struct for this. */
+ desc->their_abspath = apr_pstrdup(result_pool, prop_reject_file);
+
+ desc->operation = operation;
+ desc->src_left_version = left_version;
+ desc->src_right_version = right_version;
+
+ APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t*) = desc;
+
+ return SVN_NO_ERROR;
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, conflicted_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ svn_string_t *old_value;
+ svn_string_t *my_value;
+ svn_string_t *their_value;
+ svn_wc_conflict_description2_t *desc;
+
+ svn_pool_clear(iterpool);
+
+ desc = svn_wc_conflict_description_create_prop2(local_abspath,
+ node_kind,
+ propname,
+ result_pool);
+
+ desc->operation = operation;
+ desc->src_left_version = left_version;
+ desc->src_right_version = right_version;
+
+ desc->property_name = apr_pstrdup(result_pool, propname);
+
+ my_value = svn_hash_gets(my_props, propname);
+ their_value = svn_hash_gets(their_props, propname);
+ old_value = svn_hash_gets(their_old_props, propname);
+
+ /* Compute the incoming side of the conflict ('action'). */
+ if (their_value == NULL)
+ desc->action = svn_wc_conflict_action_delete;
+ else if (old_value == NULL)
+ desc->action = svn_wc_conflict_action_add;
+ else
+ desc->action = svn_wc_conflict_action_edit;
+
+ /* Compute the local side of the conflict ('reason'). */
+ if (my_value == NULL)
+ desc->reason = svn_wc_conflict_reason_deleted;
+ else if (old_value == NULL)
+ desc->reason = svn_wc_conflict_reason_added;
+ else
+ desc->reason = svn_wc_conflict_reason_edited;
+
+ /* ### This should be changed. The prej file should be stored
+ * ### separately from the other files. We need to rev the
+ * ### conflict description struct for this. */
+ desc->their_abspath = apr_pstrdup(result_pool, prop_reject_file);
+
+ /* ### This should be changed. The conflict description for
+ * ### props should contain these values as svn_string_t,
+ * ### rather than in temporary files. We need to rev the
+ * ### conflict description struct for this. */
+ if (my_value)
+ {
+ svn_stream_t *s;
+ apr_size_t len;
+
+ SVN_ERR(svn_stream_open_unique(&s, &desc->my_abspath, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, iterpool));
+ len = my_value->len;
+ SVN_ERR(svn_stream_write(s, my_value->data, &len));
+ SVN_ERR(svn_stream_close(s));
+ }
+
+ if (their_value)
+ {
+ svn_stream_t *s;
+ apr_size_t len;
+
+ /* ### Currently, their_abspath is used for the prop reject file.
+ * ### Put their value into merged instead...
+ * ### We need to rev the conflict description struct to fix this. */
+ SVN_ERR(svn_stream_open_unique(&s, &desc->merged_file, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, iterpool));
+ len = their_value->len;
+ SVN_ERR(svn_stream_write(s, their_value->data, &len));
+ SVN_ERR(svn_stream_close(s));
+ }
+
+ if (old_value)
+ {
+ svn_stream_t *s;
+ apr_size_t len;
+
+ SVN_ERR(svn_stream_open_unique(&s, &desc->base_abspath, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, iterpool));
+ len = old_value->len;
+ SVN_ERR(svn_stream_write(s, old_value->data, &len));
+ SVN_ERR(svn_stream_close(s));
+ }
+
+ APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t*) = desc;
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__read_conflicts(const apr_array_header_t **conflicts,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t create_tempfiles,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *conflict_skel;
+ apr_array_header_t *cflcts;
+ svn_boolean_t prop_conflicted;
+ svn_boolean_t text_conflicted;
+ svn_boolean_t tree_conflicted;
+ svn_wc_operation_t operation;
+ const apr_array_header_t *locations;
+ const svn_wc_conflict_version_t *left_version = NULL;
+ const svn_wc_conflict_version_t *right_version = NULL;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflict_skel, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!conflict_skel)
+ {
+ /* Some callers expect not NULL */
+ *conflicts = apr_array_make(result_pool, 0,
+ sizeof(svn_wc_conflict_description2_t*));;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc__conflict_read_info(&operation, &locations, &text_conflicted,
+ &prop_conflicted, &tree_conflicted,
+ db, local_abspath, conflict_skel,
+ result_pool, scratch_pool));
+
+ cflcts = apr_array_make(result_pool, 4,
+ sizeof(svn_wc_conflict_description2_t*));
+
+ if (locations && locations->nelts > 0)
+ left_version = APR_ARRAY_IDX(locations, 0, const svn_wc_conflict_version_t *);
+ if (locations && locations->nelts > 1)
+ right_version = APR_ARRAY_IDX(locations, 1, const svn_wc_conflict_version_t *);
+
+ if (prop_conflicted)
+ {
+ svn_node_kind_t node_kind
+ = left_version ? left_version->node_kind : svn_node_unknown;
+
+ SVN_ERR(read_prop_conflicts(cflcts, db, local_abspath, conflict_skel,
+ create_tempfiles, node_kind,
+ operation, left_version, right_version,
+ result_pool, scratch_pool));
+ }
+
+ if (text_conflicted)
+ {
+ svn_wc_conflict_description2_t *desc;
+ desc = svn_wc_conflict_description_create_text2(local_abspath,
+ result_pool);
+
+ desc->operation = operation;
+ desc->src_left_version = left_version;
+ desc->src_right_version = right_version;
+
+ SVN_ERR(svn_wc__conflict_read_text_conflict(&desc->my_abspath,
+ &desc->base_abspath,
+ &desc->their_abspath,
+ db, local_abspath,
+ conflict_skel,
+ result_pool, scratch_pool));
+
+ desc->merged_file = apr_pstrdup(result_pool, local_abspath);
+
+ APR_ARRAY_PUSH(cflcts, svn_wc_conflict_description2_t*) = desc;
+ }
+
+ if (tree_conflicted)
+ {
+ svn_wc_conflict_reason_t local_change;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_description2_t *desc;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change,
+ &incoming_change,
+ NULL,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(setup_tree_conflict_desc(&desc,
+ db, local_abspath,
+ operation, left_version, right_version,
+ local_change, incoming_change,
+ result_pool, scratch_pool));
+
+ APR_ARRAY_PUSH(cflcts, const svn_wc_conflict_description2_t *) = desc;
+ }
+
+ *conflicts = cflcts;
+ return SVN_NO_ERROR;
+}
+
+
+/*** Resolving a conflict automatically ***/
+
+/* Prepare to delete an artifact file at ARTIFACT_FILE_ABSPATH in the
+ * working copy at DB/WRI_ABSPATH.
+ *
+ * Set *WORK_ITEMS to a new work item that, when run, will delete the
+ * artifact file; or to NULL if there is no file to delete.
+ *
+ * Set *FILE_FOUND to TRUE if the artifact file is found on disk and its
+ * node kind is 'file'; otherwise do not change *FILE_FOUND. FILE_FOUND
+ * may be NULL if not required.
+ */
+static svn_error_t *
+remove_artifact_file_if_exists(svn_skel_t **work_items,
+ svn_boolean_t *file_found,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *artifact_file_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *work_items = NULL;
+ if (artifact_file_abspath)
+ {
+ svn_node_kind_t node_kind;
+
+ SVN_ERR(svn_io_check_path(artifact_file_abspath, &node_kind,
+ scratch_pool));
+ if (node_kind == svn_node_file)
+ {
+ SVN_ERR(svn_wc__wq_build_file_remove(work_items,
+ db, wri_abspath,
+ artifact_file_abspath,
+ result_pool, scratch_pool));
+ if (file_found)
+ *file_found = TRUE;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Resolve the text conflict found in DB/LOCAL_ABSPATH according
+ * to CONFLICT_CHOICE.
+ *
+ * It is not an error if there is no text conflict. If a text conflict
+ * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE.
+ *
+ * Note: When there are no conflict markers to remove there is no existing
+ * text conflict; just a database containing old information, which we should
+ * remove to avoid checking all the time. Resolving a text conflict by
+ * removing all the marker files is a fully supported scenario since
+ * Subversion 1.0.
+ */
+static svn_error_t *
+resolve_text_conflict_on_node(svn_boolean_t *did_resolve,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_conflict_choice_t conflict_choice,
+ const char *merged_file,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *conflict_old = NULL;
+ const char *conflict_new = NULL;
+ const char *conflict_working = NULL;
+ const char *auto_resolve_src;
+ svn_skel_t *work_item;
+ svn_skel_t *work_items = NULL;
+ svn_skel_t *conflicts;
+ svn_wc_operation_t operation;
+ svn_boolean_t text_conflicted;
+
+ *did_resolve = FALSE;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (!conflicts)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, &text_conflicted,
+ NULL, NULL, db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+ if (!text_conflicted)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__conflict_read_text_conflict(&conflict_working,
+ &conflict_old,
+ &conflict_new,
+ db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+
+ /* Handle automatic conflict resolution before the temporary files are
+ * deleted, if necessary. */
+ switch (conflict_choice)
+ {
+ case svn_wc_conflict_choose_base:
+ auto_resolve_src = conflict_old;
+ break;
+ case svn_wc_conflict_choose_mine_full:
+ auto_resolve_src = conflict_working;
+ break;
+ case svn_wc_conflict_choose_theirs_full:
+ auto_resolve_src = conflict_new;
+ break;
+ case svn_wc_conflict_choose_merged:
+ auto_resolve_src = merged_file;
+ break;
+ case svn_wc_conflict_choose_theirs_conflict:
+ case svn_wc_conflict_choose_mine_conflict:
+ {
+ if (conflict_old && conflict_working && conflict_new)
+ {
+ const char *temp_dir;
+ svn_stream_t *tmp_stream = NULL;
+ svn_diff_t *diff;
+ svn_diff_conflict_display_style_t style =
+ conflict_choice == svn_wc_conflict_choose_theirs_conflict
+ ? svn_diff_conflict_display_latest
+ : svn_diff_conflict_display_modified;
+
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db,
+ local_abspath,
+ scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_stream_open_unique(&tmp_stream,
+ &auto_resolve_src,
+ temp_dir,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_diff_file_diff3_2(&diff,
+ conflict_old,
+ conflict_working,
+ conflict_new,
+ svn_diff_file_options_create(
+ scratch_pool),
+ scratch_pool));
+ SVN_ERR(svn_diff_file_output_merge2(tmp_stream, diff,
+ conflict_old,
+ conflict_working,
+ conflict_new,
+ /* markers ignored */
+ NULL, NULL, NULL, NULL,
+ style,
+ scratch_pool));
+ SVN_ERR(svn_stream_close(tmp_stream));
+ }
+ else
+ auto_resolve_src = NULL;
+ break;
+ }
+ default:
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Invalid 'conflict_result' argument"));
+ }
+
+ if (auto_resolve_src)
+ {
+ SVN_ERR(svn_wc__wq_build_file_copy_translated(
+ &work_item, db, local_abspath,
+ auto_resolve_src, local_abspath, scratch_pool, scratch_pool));
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ /* Legacy behavior: Only report text conflicts as resolved when at least
+ one conflict marker file exists.
+
+ If not the UI shows the conflict as already resolved
+ (and in this case we just remove the in-db conflict) */
+
+ SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve,
+ db, local_abspath, conflict_old,
+ scratch_pool, scratch_pool));
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve,
+ db, local_abspath, conflict_new,
+ scratch_pool, scratch_pool));
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve,
+ db, local_abspath, conflict_working,
+ scratch_pool, scratch_pool));
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath,
+ TRUE, FALSE, FALSE,
+ work_items, scratch_pool));
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Resolve the property conflicts found in DB/LOCAL_ABSPATH according
+ * to CONFLICT_CHOICE.
+ *
+ * It is not an error if there is no prop conflict. If a prop conflict
+ * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE.
+ *
+ * Note: When there are no conflict markers on-disk to remove there is
+ * no existing text conflict (unless we are still in the process of
+ * creating the text conflict and we didn't register a marker file yet).
+ * In this case the database contains old information, which we should
+ * remove to avoid checking the next time. Resolving a property conflict
+ * by just removing the marker file is a fully supported scenario since
+ * Subversion 1.0.
+ *
+ * ### TODO [JAF] The '*_full' and '*_conflict' choices should differ.
+ * In my opinion, 'mine_full'/'theirs_full' should select
+ * the entire set of properties from 'mine' or 'theirs' respectively,
+ * while 'mine_conflict'/'theirs_conflict' should select just the
+ * properties that are in conflict. Or, '_full' should select the
+ * entire property whereas '_conflict' should do a text merge within
+ * each property, selecting hunks. Or all three kinds of behaviour
+ * should be available (full set of props, full value of conflicting
+ * props, or conflicting text hunks).
+ * ### BH: If we make *_full select the full set of properties, we should
+ * check if we shouldn't make it also select the full text for files.
+ *
+ * ### TODO [JAF] All this complexity should not be down here in libsvn_wc
+ * but in a layer above.
+ *
+ * ### TODO [JAF] Options for 'base' should be like options for 'mine' and
+ * for 'theirs' -- choose full set of props, full value of conflicting
+ * props, or conflicting text hunks.
+ *
+ */
+static svn_error_t *
+resolve_prop_conflict_on_node(svn_boolean_t *did_resolve,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *conflicted_propname,
+ svn_wc_conflict_choice_t conflict_choice,
+ const char *merged_file,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *prop_reject_file;
+ apr_hash_t *mine_props;
+ apr_hash_t *their_old_props;
+ apr_hash_t *their_props;
+ apr_hash_t *conflicted_props;
+ apr_hash_t *old_props;
+ apr_hash_t *resolve_from = NULL;
+ svn_skel_t *work_items = NULL;
+ svn_skel_t *conflicts;
+ svn_wc_operation_t operation;
+ svn_boolean_t prop_conflicted;
+
+ *did_resolve = FALSE;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!conflicts)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, &prop_conflicted,
+ NULL, db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+ if (!prop_conflicted)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_file,
+ &mine_props, &their_old_props,
+ &their_props, &conflicted_props,
+ db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+
+ if (operation == svn_wc_operation_merge)
+ SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ old_props = their_old_props;
+
+ /* We currently handle *_conflict as *_full as this argument is currently
+ always applied for all conflicts on a node at the same time. Giving
+ an error would break some tests that assumed that this would just
+ resolve property conflicts to working.
+
+ An alternative way to handle these conflicts would be to just copy all
+ property state from mine/theirs on the _full option instead of just the
+ conflicted properties. In some ways this feels like a sensible option as
+ that would take both properties and text from mine/theirs, but when not
+ both properties and text are conflicted we would fail in doing so.
+ */
+ switch (conflict_choice)
+ {
+ case svn_wc_conflict_choose_base:
+ resolve_from = their_old_props ? their_old_props : old_props;
+ break;
+ case svn_wc_conflict_choose_mine_full:
+ case svn_wc_conflict_choose_mine_conflict:
+ resolve_from = mine_props;
+ break;
+ case svn_wc_conflict_choose_theirs_full:
+ case svn_wc_conflict_choose_theirs_conflict:
+ resolve_from = their_props;
+ break;
+ case svn_wc_conflict_choose_merged:
+ if (merged_file && conflicted_propname[0] != '\0')
+ {
+ apr_hash_t *actual_props;
+ svn_stream_t *stream;
+ svn_string_t *merged_propval;
+
+ SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ resolve_from = actual_props;
+
+ SVN_ERR(svn_stream_open_readonly(&stream, merged_file,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_string_from_stream(&merged_propval, stream,
+ scratch_pool, scratch_pool));
+ svn_hash_sets(resolve_from, conflicted_propname, merged_propval);
+ }
+ else
+ resolve_from = NULL;
+ break;
+ default:
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Invalid 'conflict_result' argument"));
+ }
+
+ if (conflicted_props && apr_hash_count(conflicted_props) && resolve_from)
+ {
+ apr_hash_index_t *hi;
+ apr_hash_t *actual_props;
+
+ SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, conflicted_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ svn_string_t *new_value = NULL;
+
+ new_value = svn_hash_gets(resolve_from, propname);
+
+ svn_hash_sets(actual_props, propname, new_value);
+ }
+ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, actual_props,
+ FALSE, NULL, NULL,
+ scratch_pool));
+ }
+
+ /* Legacy behavior: Only report property conflicts as resolved when the
+ property reject file exists
+
+ If not the UI shows the conflict as already resolved
+ (and in this case we just remove the in-db conflict) */
+
+ {
+ svn_skel_t *work_item;
+
+ SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve,
+ db, local_abspath, prop_reject_file,
+ scratch_pool, scratch_pool));
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, FALSE, TRUE, FALSE,
+ work_items, scratch_pool));
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Resolve the tree conflict found in DB/LOCAL_ABSPATH according to
+ * CONFLICT_CHOICE.
+ *
+ * It is not an error if there is no tree conflict. If a tree conflict
+ * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE.
+ *
+ * It is not an error if there is no tree conflict.
+ */
+static svn_error_t *
+resolve_tree_conflict_on_node(svn_boolean_t *did_resolve,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_conflict_choice_t conflict_choice,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_conflict_reason_t reason;
+ svn_wc_conflict_action_t action;
+ svn_skel_t *conflicts;
+ svn_wc_operation_t operation;
+ svn_boolean_t tree_conflicted;
+
+ *did_resolve = FALSE;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (!conflicts)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL,
+ &tree_conflicted, db, local_abspath,
+ conflicts, scratch_pool, scratch_pool));
+ if (!tree_conflicted)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL,
+ db, local_abspath,
+ conflicts,
+ scratch_pool, scratch_pool));
+
+ if (operation == svn_wc_operation_update
+ || operation == svn_wc_operation_switch)
+ {
+ if (reason == svn_wc_conflict_reason_deleted ||
+ reason == svn_wc_conflict_reason_replaced)
+ {
+ if (conflict_choice == svn_wc_conflict_choose_merged)
+ {
+ /* Break moves for any children moved out of this directory,
+ * and leave this directory deleted. */
+ SVN_ERR(svn_wc__db_resolve_break_moved_away_children(
+ db, local_abspath, notify_func, notify_baton,
+ scratch_pool));
+ *did_resolve = TRUE;
+ }
+ else if (conflict_choice == svn_wc_conflict_choose_mine_conflict)
+ {
+ /* Raised moved-away conflicts on any children moved out of
+ * this directory, and leave this directory deleted.
+ * The newly conflicted moved-away children will be updated
+ * if they are resolved with 'mine_conflict' as well. */
+ SVN_ERR(svn_wc__db_resolve_delete_raise_moved_away(
+ db, local_abspath, notify_func, notify_baton,
+ scratch_pool));
+ *did_resolve = TRUE;
+ }
+ else
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ NULL,
+ _("Tree conflict can only be resolved to "
+ "'working' or 'mine-conflict' state; "
+ "'%s' not resolved"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ else if (reason == svn_wc_conflict_reason_moved_away
+ && action == svn_wc_conflict_action_edit)
+ {
+ /* After updates, we can resolve local moved-away
+ * vs. any incoming change, either by updating the
+ * moved-away node (mine-conflict) or by breaking the
+ * move (theirs-conflict). */
+ if (conflict_choice == svn_wc_conflict_choose_mine_conflict)
+ {
+ SVN_ERR(svn_wc__db_update_moved_away_conflict_victim(
+ db, local_abspath,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ *did_resolve = TRUE;
+ }
+ else if (conflict_choice == svn_wc_conflict_choose_merged)
+ {
+ /* We must break the move if the user accepts the current
+ * working copy state instead of updating the move.
+ * Else the move would be left in an invalid state. */
+
+ /* ### This breaks the move but leaves the conflict
+ ### involving the move until
+ ### svn_wc__db_op_mark_resolved. */
+ SVN_ERR(svn_wc__db_resolve_break_moved_away(db, local_abspath,
+ notify_func,
+ notify_baton,
+ scratch_pool));
+ *did_resolve = TRUE;
+ }
+ else
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ NULL,
+ _("Tree conflict can only be resolved to "
+ "'working' or 'mine-conflict' state; "
+ "'%s' not resolved"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ }
+
+ if (! *did_resolve && conflict_choice != svn_wc_conflict_choose_merged)
+ {
+ /* For other tree conflicts, there is no way to pick
+ * theirs-full or mine-full, etc. Throw an error if the
+ * user expects us to be smarter than we really are. */
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ NULL,
+ _("Tree conflict can only be "
+ "resolved to 'working' state; "
+ "'%s' not resolved"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, FALSE, FALSE, TRUE,
+ NULL, scratch_pool));
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__mark_resolved_text_conflict(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t ignored_result;
+
+ return svn_error_trace(resolve_text_conflict_on_node(
+ &ignored_result,
+ db, local_abspath,
+ svn_wc_conflict_choose_merged, NULL,
+ NULL, NULL,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__mark_resolved_prop_conflicts(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t ignored_result;
+
+ return svn_error_trace(resolve_prop_conflict_on_node(
+ &ignored_result,
+ db, local_abspath, "",
+ svn_wc_conflict_choose_merged, NULL,
+ NULL, NULL,
+ scratch_pool));
+}
+
+
+/* Baton for conflict_status_walker */
+struct conflict_status_walker_baton
+{
+ svn_wc__db_t *db;
+ svn_boolean_t resolve_text;
+ const char *resolve_prop;
+ svn_boolean_t resolve_tree;
+ svn_wc_conflict_choice_t conflict_choice;
+ svn_wc_conflict_resolver_func2_t conflict_func;
+ void *conflict_baton;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+};
+
+/* Implements svn_wc_status4_t to walk all conflicts to resolve.
+ */
+static svn_error_t *
+conflict_status_walker(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct conflict_status_walker_baton *cswb = baton;
+ svn_wc__db_t *db = cswb->db;
+
+ const apr_array_header_t *conflicts;
+ apr_pool_t *iterpool;
+ int i;
+ svn_boolean_t resolved = FALSE;
+
+ if (!status->conflicted)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_wc__read_conflicts(&conflicts, db, local_abspath, TRUE,
+ scratch_pool, iterpool));
+
+ for (i = 0; i < conflicts->nelts; i++)
+ {
+ const svn_wc_conflict_description2_t *cd;
+ svn_boolean_t did_resolve;
+ svn_wc_conflict_choice_t my_choice = cswb->conflict_choice;
+ const char *merged_file = NULL;
+
+ cd = APR_ARRAY_IDX(conflicts, i, const svn_wc_conflict_description2_t *);
+
+ svn_pool_clear(iterpool);
+
+ if (my_choice == svn_wc_conflict_choose_unspecified)
+ {
+ svn_wc_conflict_result_t *result;
+
+ if (!cswb->conflict_func)
+ return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("No conflict-callback and no "
+ "pre-defined conflict-choice provided"));
+
+ SVN_ERR(cswb->conflict_func(&result, cd, cswb->conflict_baton,
+ iterpool, iterpool));
+
+ my_choice = result->choice;
+ merged_file = result->merged_file;
+ /* ### Bug: ignores result->save_merged */
+ }
+
+
+ if (my_choice == svn_wc_conflict_choose_postpone)
+ continue;
+
+ switch (cd->kind)
+ {
+ case svn_wc_conflict_kind_tree:
+ if (!cswb->resolve_tree)
+ break;
+ SVN_ERR(resolve_tree_conflict_on_node(&did_resolve,
+ db,
+ local_abspath,
+ my_choice,
+ cswb->notify_func,
+ cswb->notify_baton,
+ cswb->cancel_func,
+ cswb->cancel_baton,
+ iterpool));
+
+ resolved = TRUE;
+ break;
+
+ case svn_wc_conflict_kind_text:
+ if (!cswb->resolve_text)
+ break;
+
+ SVN_ERR(resolve_text_conflict_on_node(&did_resolve,
+ db,
+ local_abspath,
+ my_choice,
+ merged_file,
+ cswb->cancel_func,
+ cswb->cancel_baton,
+ iterpool));
+
+ if (did_resolve)
+ resolved = TRUE;
+ break;
+
+ case svn_wc_conflict_kind_property:
+ if (!cswb->resolve_prop)
+ break;
+
+ if (*cswb->resolve_prop != '\0' &&
+ strcmp(cswb->resolve_prop, cd->property_name) != 0)
+ {
+ break; /* This is not the property we want to resolve. */
+ }
+
+ SVN_ERR(resolve_prop_conflict_on_node(&did_resolve,
+ db,
+ local_abspath,
+ cd->property_name,
+ my_choice,
+ merged_file,
+ cswb->cancel_func,
+ cswb->cancel_baton,
+ iterpool));
+
+ if (did_resolve)
+ resolved = TRUE;
+ break;
+
+ default:
+ /* We can't resolve other conflict types */
+ break;
+ }
+ }
+
+ /* Notify */
+ if (cswb->notify_func && resolved)
+ cswb->notify_func(cswb->notify_baton,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_resolved,
+ iterpool),
+ iterpool);
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__resolve_conflicts(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t resolve_text,
+ const char *resolve_prop,
+ svn_boolean_t resolve_tree,
+ svn_wc_conflict_choice_t conflict_choice,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ svn_boolean_t conflicted;
+ struct conflict_status_walker_baton cswb;
+
+ /* ### the underlying code does NOT support resolving individual
+ ### properties. bail out if the caller tries it. */
+ if (resolve_prop != NULL && *resolve_prop != '\0')
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ U_("Resolving a single property is not (yet) "
+ "supported."));
+
+ /* ### Just a versioned check? */
+ /* Conflicted is set to allow invoking on actual only nodes */
+ SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, &conflicted,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* When the implementation still used the entry walker, depth
+ unknown was translated to infinity. */
+ if (kind != svn_node_dir)
+ depth = svn_depth_empty;
+ else if (depth == svn_depth_unknown)
+ depth = svn_depth_infinity;
+
+ cswb.db = wc_ctx->db;
+ cswb.resolve_text = resolve_text;
+ cswb.resolve_prop = resolve_prop;
+ cswb.resolve_tree = resolve_tree;
+ cswb.conflict_choice = conflict_choice;
+
+ cswb.conflict_func = conflict_func;
+ cswb.conflict_baton = conflict_baton;
+
+ cswb.cancel_func = cancel_func;
+ cswb.cancel_baton = cancel_baton;
+
+ cswb.notify_func = notify_func;
+ cswb.notify_baton = notify_baton;
+
+ if (notify_func)
+ notify_func(notify_baton,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_conflict_resolver_starting,
+ scratch_pool),
+ scratch_pool);
+
+ SVN_ERR(svn_wc_walk_status(wc_ctx,
+ local_abspath,
+ depth,
+ FALSE /* get_all */,
+ FALSE /* no_ignore */,
+ TRUE /* ignore_text_mods */,
+ NULL /* ignore_patterns */,
+ conflict_status_walker, &cswb,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ if (notify_func)
+ notify_func(notify_baton,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_conflict_resolver_done,
+ scratch_pool),
+ scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_resolved_conflict5(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t resolve_text,
+ const char *resolve_prop,
+ svn_boolean_t resolve_tree,
+ svn_wc_conflict_choice_t conflict_choice,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__resolve_conflicts(wc_ctx, local_abspath,
+ depth, resolve_text,
+ resolve_prop, resolve_tree,
+ conflict_choice,
+ NULL, NULL,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+}
+
+/* Constructor for the result-structure returned by conflict callbacks. */
+svn_wc_conflict_result_t *
+svn_wc_create_conflict_result(svn_wc_conflict_choice_t choice,
+ const char *merged_file,
+ apr_pool_t *pool)
+{
+ svn_wc_conflict_result_t *result = apr_pcalloc(pool, sizeof(*result));
+ result->choice = choice;
+ result->merged_file = merged_file;
+ result->save_merged = FALSE;
+
+ /* If we add more fields to svn_wc_conflict_result_t, add them here. */
+
+ return result;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/conflicts.h b/contrib/subversion/subversion/libsvn_wc/conflicts.h
new file mode 100644
index 0000000..d473065
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/conflicts.h
@@ -0,0 +1,443 @@
+/*
+ * conflicts.h: declarations related to conflicts
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_WC_CONFLICTS_H
+#define SVN_WC_CONFLICTS_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_wc.h"
+
+#include "wc_db.h"
+#include "private/svn_skel.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+#define SVN_WC__CONFLICT_OP_UPDATE "update"
+#define SVN_WC__CONFLICT_OP_SWITCH "switch"
+#define SVN_WC__CONFLICT_OP_MERGE "merge"
+#define SVN_WC__CONFLICT_OP_PATCH "patch"
+
+#define SVN_WC__CONFLICT_KIND_TEXT "text"
+#define SVN_WC__CONFLICT_KIND_PROP "prop"
+#define SVN_WC__CONFLICT_KIND_TREE "tree"
+#define SVN_WC__CONFLICT_KIND_REJECT "reject"
+#define SVN_WC__CONFLICT_KIND_OBSTRUCTED "obstructed"
+
+#define SVN_WC__CONFLICT_SRC_SUBVERSION "subversion"
+
+/* Return a new conflict skel, allocated in RESULT_POOL.
+
+ Typically creating a conflict starts with calling this function and then
+ collecting details via one or more calls to svn_wc__conflict_skel_add_*().
+
+ The caller can then (when necessary) add operation details via
+ svn_wc__conflict_skel_set_op_*() and store the resulting conflict together
+ with the result of its operation in the working copy database.
+*/
+svn_skel_t *
+svn_wc__conflict_skel_create(apr_pool_t *result_pool);
+
+/* Return a boolean in *COMPLETE indicating whether CONFLICT_SKEL contains
+ everything needed for installing in the working copy database.
+
+ This typically checks if CONFLICT_SKEL contains at least one conflict
+ and an operation.
+ */
+svn_error_t *
+svn_wc__conflict_skel_is_complete(svn_boolean_t *complete,
+ const svn_skel_t *conflict_skel);
+
+
+/* Set 'update' as the conflicting operation in CONFLICT_SKEL.
+ Allocate data stored in the skel in RESULT_POOL.
+
+ ORIGINAL and TARGET specify the BASE node before and after updating.
+
+ It is an error to set another operation to a conflict skel that
+ already has an operation.
+
+ Do temporary allocations in SCRATCH_POOL. The new skel data is
+ completely stored in RESULT-POOL. */
+svn_error_t *
+svn_wc__conflict_skel_set_op_update(svn_skel_t *conflict_skel,
+ const svn_wc_conflict_version_t *original,
+ const svn_wc_conflict_version_t *target,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set 'switch' as the conflicting operation in CONFLICT_SKEL.
+ Allocate data stored in the skel in RESULT_POOL.
+
+ ORIGINAL and TARGET specify the BASE node before and after switching.
+
+ It is an error to set another operation to a conflict skel that
+ already has an operation.
+
+ Do temporary allocations in SCRATCH_POOL. */
+svn_error_t *
+svn_wc__conflict_skel_set_op_switch(svn_skel_t *conflict_skel,
+ const svn_wc_conflict_version_t *original,
+ const svn_wc_conflict_version_t *target,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set 'merge' as conflicting operation in CONFLICT_SKEL.
+ Allocate data stored in the skel in RESULT_POOL.
+
+ LEFT and RIGHT paths are the merge-left and merge-right merge
+ sources of the merge.
+
+ It is an error to set another operation to a conflict skel that
+ already has an operation.
+
+ Do temporary allocations in SCRATCH_POOL. */
+svn_error_t *
+svn_wc__conflict_skel_set_op_merge(svn_skel_t *conflict_skel,
+ const svn_wc_conflict_version_t *left,
+ const svn_wc_conflict_version_t *right,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Add a text conflict to CONFLICT_SKEL.
+ Allocate data stored in the skel in RESULT_POOL.
+
+ The DB, WRI_ABSPATH pair specifies in which working copy the conflict
+ will be recorded. (Needed for making the paths relative).
+
+ MINE_ABSPATH, THEIR_OLD_ABSPATH and THEIR_ABSPATH specify the marker
+ files for this text conflict. Each of these values can be NULL to specify
+ that the node doesn't exist in this case.
+
+ ### It is expected that in a future version we will also want to store
+ ### the sha1 checksum of these files to allow reinstalling the conflict
+ ### markers from the pristine store.
+
+ It is an error to add another text conflict to a conflict skel that
+ already contains a text conflict.
+
+ Do temporary allocations in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__conflict_skel_add_text_conflict(svn_skel_t *conflict_skel,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *mine_abspath,
+ const char *their_old_abspath,
+ const char *their_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Add property conflict details to CONFLICT_SKEL.
+ Allocate data stored in the skel in RESULT_POOL.
+
+ The DB, WRI_ABSPATH pair specifies in which working copy the conflict
+ will be recorded. (Needed for making the paths relative).
+
+ The MARKER_ABSPATH is NULL when raising a conflict in v1.8+. See below.
+
+ The MINE_PROPS, THEIR_OLD_PROPS and THEIR_PROPS are hashes mapping a
+ const char * property name to a const svn_string_t* value.
+
+ The CONFLICTED_PROP_NAMES is a const char * property name value mapping
+ to "", recording which properties aren't resolved yet in the current
+ property values.
+ ### Needed for creating the marker file from this conflict data.
+ ### Would also allow per property marking as resolved.
+ ### Maybe useful for calling (legacy) conflict resolvers that expect one
+ ### property conflict per invocation.
+
+ When raising a property conflict in the course of upgrading an old WC,
+ MARKER_ABSPATH is the path to the file containing a human-readable
+ description of the conflict, MINE_PROPS and THEIR_OLD_PROPS and
+ THEIR_PROPS are all NULL, and CONFLICTED_PROP_NAMES is an empty hash.
+
+ It is an error to add another prop conflict to a conflict skel that
+ already contains a prop conflict. (A single call to this function can
+ record that multiple properties are in conflict.)
+
+ Do temporary allocations in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__conflict_skel_add_prop_conflict(svn_skel_t *conflict_skel,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *marker_abspath,
+ const apr_hash_t *mine_props,
+ const apr_hash_t *their_old_props,
+ const apr_hash_t *their_props,
+ const apr_hash_t *conflicted_prop_names,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Add a tree conflict to CONFLICT_SKEL.
+ Allocate data stored in the skel in RESULT_POOL.
+
+ LOCAL_CHANGE is the local tree change made to the node.
+ INCOMING_CHANGE is the incoming change made to the node.
+
+ MOVE_SRC_OP_ROOT_ABSPATH must be set when LOCAL_CHANGE is
+ svn_wc_conflict_reason_moved_away and NULL otherwise and the operation
+ is svn_wc_operation_update or svn_wc_operation_switch. It should be
+ set to the op-root of the move-away unless the move is inside a
+ delete in which case it should be set to the op-root of the delete
+ (the delete can be a replace). So given:
+ A/B/C moved away (1)
+ A deleted and replaced
+ A/B/C moved away (2)
+ A/B deleted
+ MOVE_SRC_OP_ROOT_ABSPATH should be A for a conflict associated
+ with (1), MOVE_SRC_OP_ROOT_ABSPATH should be A/B for a conflict
+ associated with (2).
+
+ It is an error to add another tree conflict to a conflict skel that
+ already contains a tree conflict. (It is not an error, at this level,
+ to add a tree conflict to an existing text or property conflict skel.)
+
+ Do temporary allocations in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__conflict_skel_add_tree_conflict(svn_skel_t *conflict_skel,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ svn_wc_conflict_reason_t local_change,
+ svn_wc_conflict_action_t incoming_change,
+ const char *move_src_op_root_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Allows resolving specific conflicts stored in CONFLICT_SKEL.
+
+ When RESOLVE_TEXT is TRUE and CONFLICT_SKEL contains a text conflict,
+ resolve/remove the text conflict in CONFLICT_SKEL.
+
+ When RESOLVE_PROP is "" and CONFLICT_SKEL contains a property conflict,
+ resolve/remove all property conflicts in CONFLICT_SKEL.
+
+ When RESOLVE_PROP is not NULL and not "", remove the property conflict on
+ the property RESOLVE_PROP in CONFLICT_SKEL. When RESOLVE_PROP was the last
+ property in CONFLICT_SKEL remove the property conflict info from
+ CONFLICT_SKEL.
+
+ When RESOLVE_TREE is TRUE and CONFLICT_SKEL contains a tree conflict,
+ resolve/remove the tree conflict in CONFLICT_SKEL.
+
+ If COMPLETELY_RESOLVED is not NULL, then set *COMPLETELY_RESOLVED to TRUE,
+ when no conflict registration is left in CONFLICT_SKEL after editting,
+ otherwise to FALSE.
+
+ Allocate data stored in the skel in RESULT_POOL.
+
+ This functions edits CONFLICT_SKEL. New skels might be created in
+ RESULT_POOL. Temporary allocations will use SCRATCH_POOL.
+ */
+/* ### db, wri_abspath is currently unused. Remove? */
+svn_error_t *
+svn_wc__conflict_skel_resolve(svn_boolean_t *completely_resolved,
+ svn_skel_t *conflict_skel,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ svn_boolean_t resolve_text,
+ const char *resolve_prop,
+ svn_boolean_t resolve_tree,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/*
+ * -----------------------------------------------------------
+ * Reading conflict skels. Maybe this can be made private later
+ * -----------------------------------------------------------
+ */
+
+/* Read common information from CONFLICT_SKEL to determine the operation
+ * and merge origins.
+ *
+ * Output arguments can be NULL if the value is not necessary.
+ *
+ * Set *LOCATIONS to an array of (svn_wc_conflict_version_t *). For
+ * conflicts written by current code, there are 2 elements: index [0] is
+ * the 'old' or 'left' side and [1] is the 'new' or 'right' side.
+ *
+ * For conflicts written by 1.6 or 1.7 there are 2 locations for a tree
+ * conflict, but none for a text or property conflict.
+ *
+ * TEXT_, PROP_ and TREE_CONFLICTED (when not NULL) will be set to TRUE
+ * when the conflict contains the specified kind of conflict, otherwise
+ * to false.
+ *
+ * Allocate the result in RESULT_POOL. Perform temporary allocations in
+ * SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__conflict_read_info(svn_wc_operation_t *operation,
+ const apr_array_header_t **locations,
+ svn_boolean_t *text_conflicted,
+ svn_boolean_t *prop_conflicted,
+ svn_boolean_t *tree_conflicted,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Reads back the original data stored by svn_wc__conflict_skel_add_text_conflict()
+ * in CONFLICT_SKEL for a node in DB, WRI_ABSPATH.
+ *
+ * Values as documented for svn_wc__conflict_skel_add_text_conflict().
+ *
+ * Output arguments can be NULL if the value is not necessary.
+ *
+ * Allocate the result in RESULT_POOL. Perform temporary allocations in
+ * SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__conflict_read_text_conflict(const char **mine_abspath,
+ const char **their_old_abspath,
+ const char **their_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Reads back the original data stored by svn_wc__conflict_skel_add_prop_conflict()
+ * in CONFLICT_SKEL for a node in DB, WRI_ABSPATH.
+ *
+ * Values as documented for svn_wc__conflict_skel_add_prop_conflict().
+ *
+ * Output arguments can be NULL if the value is not necessary
+ * Allocate the result in RESULT_POOL. Perform temporary allocations in
+ * SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__conflict_read_prop_conflict(const char **marker_abspath,
+ apr_hash_t **mine_props,
+ apr_hash_t **their_old_props,
+ apr_hash_t **their_props,
+ apr_hash_t **conflicted_prop_names,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Reads back the original data stored by svn_wc__conflict_skel_add_tree_conflict()
+ * in CONFLICT_SKEL for a node in DB, WRI_ABSPATH.
+ *
+ * Values as documented for svn_wc__conflict_skel_add_tree_conflict().
+ *
+ * Output arguments can be NULL if the value is not necessary
+ * Allocate the result in RESULT_POOL. Perform temporary allocations in
+ * SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__conflict_read_tree_conflict(svn_wc_conflict_reason_t *local_change,
+ svn_wc_conflict_action_t *incoming_change,
+ const char **move_src_op_root_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Reads in *MARKERS a list of const char * absolute paths of the marker files
+ referenced from CONFLICT_SKEL.
+ * Allocate the result in RESULT_POOL. Perform temporary allocations in
+ * SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__conflict_read_markers(const apr_array_header_t **markers,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Create the necessary marker files for the conflicts stored in
+ * CONFLICT_SKEL and return the work items to fill the markers from
+ * the work queue.
+ *
+ * Currently only used for property conflicts as text conflict markers
+ * are just in-wc files.
+ *
+ * Allocate the result in RESULT_POOL. Perform temporary allocations in
+ * SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__conflict_create_markers(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Call the interactive conflict resolver RESOLVER_FUNC with RESOLVER_BATON to
+ allow resolving the conflicts on LOCAL_ABSPATH.
+
+ Call RESOLVER_FUNC once for each property conflict, and again for any
+ text conflict, and again for any tree conflict on the node.
+
+ CONFLICT_SKEL contains the details of the conflicts on LOCAL_ABSPATH.
+
+ Resolver actions are directly applied to the in-db state of LOCAL_ABSPATH,
+ so the conflict and the state in CONFLICT_SKEL must already be installed in
+ wc.db. */
+svn_error_t *
+svn_wc__conflict_invoke_resolver(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflict_skel,
+ const apr_array_header_t *merge_options,
+ svn_wc_conflict_resolver_func2_t resolver_func,
+ void *resolver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Mark as resolved any text conflict on the node at DB/LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__mark_resolved_text_conflict(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Mark as resolved any prop conflicts on the node at DB/LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__mark_resolved_prop_conflicts(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_WC_CONFLICTS_H */
diff --git a/contrib/subversion/subversion/libsvn_wc/context.c b/contrib/subversion/subversion/libsvn_wc/context.c
new file mode 100644
index 0000000..4bf2369
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/context.c
@@ -0,0 +1,116 @@
+/*
+ * context.c: routines for managing a working copy context
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+
+#include "wc.h"
+#include "wc_db.h"
+
+#include "svn_private_config.h"
+
+
+
+/* APR cleanup function used to explicitly close any of our dependent
+ data structures before we disappear ourselves. */
+static apr_status_t
+close_ctx_apr(void *data)
+{
+ svn_wc_context_t *ctx = data;
+
+ if (ctx->close_db_on_destroy)
+ {
+ svn_error_t *err = svn_wc__db_close(ctx->db);
+ if (err)
+ {
+ int result = err->apr_err;
+ svn_error_clear(err);
+ return result;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+
+svn_error_t *
+svn_wc_context_create(svn_wc_context_t **wc_ctx,
+ const svn_config_t *config,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_context_t *ctx = apr_pcalloc(result_pool, sizeof(*ctx));
+
+ /* Create the state_pool, and open up a wc_db in it.
+ * Since config contains a private mutable member but C doesn't support
+ * we need to make it writable */
+ ctx->state_pool = result_pool;
+ SVN_ERR(svn_wc__db_open(&ctx->db, (svn_config_t *)config,
+ FALSE, TRUE, ctx->state_pool, scratch_pool));
+ ctx->close_db_on_destroy = TRUE;
+
+ apr_pool_cleanup_register(result_pool, ctx, close_ctx_apr,
+ apr_pool_cleanup_null);
+
+ *wc_ctx = ctx;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__context_create_with_db(svn_wc_context_t **wc_ctx,
+ svn_config_t *config,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool)
+{
+ svn_wc_context_t *ctx = apr_pcalloc(result_pool, sizeof(*ctx));
+
+ /* Create the state pool. We don't put the wc_db in it, because it's
+ already open in a separate pool somewhere. We also won't close the
+ wc_db when we destroy the context, since it's not ours to close. */
+ ctx->state_pool = result_pool;
+ ctx->db = db;
+ ctx->close_db_on_destroy = FALSE;
+
+ apr_pool_cleanup_register(result_pool, ctx, close_ctx_apr,
+ apr_pool_cleanup_null);
+
+ *wc_ctx = ctx;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_context_destroy(svn_wc_context_t *wc_ctx)
+{
+ /* We added a cleanup when creating; just run it now to close the context. */
+ apr_pool_cleanup_run(wc_ctx->state_pool, wc_ctx, close_ctx_apr);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/copy.c b/contrib/subversion/subversion/libsvn_wc/copy.c
new file mode 100644
index 0000000..1b82c2d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/copy.c
@@ -0,0 +1,1048 @@
+/*
+ * copy.c: wc 'copy' functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+
+#include "wc.h"
+#include "workqueue.h"
+#include "props.h"
+#include "conflicts.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+/*** Code. ***/
+
+/* Make a copy of the filesystem node (or tree if RECURSIVE) at
+ SRC_ABSPATH under a temporary name in the directory
+ TMPDIR_ABSPATH and return the absolute path of the copy in
+ *DST_ABSPATH. Return the node kind of SRC_ABSPATH in *KIND. If
+ SRC_ABSPATH doesn't exist then set *DST_ABSPATH to NULL to indicate
+ that no copy was made. */
+static svn_error_t *
+copy_to_tmpdir(svn_skel_t **work_item,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *dst_abspath,
+ const char *tmpdir_abspath,
+ svn_boolean_t file_copy,
+ svn_boolean_t unversioned,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_special;
+ svn_io_file_del_t delete_when;
+ const char *dst_tmp_abspath;
+ svn_node_kind_t dsk_kind;
+ if (!kind)
+ kind = &dsk_kind;
+
+ *work_item = NULL;
+
+ SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special,
+ scratch_pool));
+ if (*kind == svn_node_none)
+ {
+ return SVN_NO_ERROR;
+ }
+ else if (*kind == svn_node_unknown)
+ {
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Source '%s' is unexpected kind"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+ }
+ else if (*kind == svn_node_dir || is_special)
+ delete_when = svn_io_file_del_on_close;
+ else /* the default case: (*kind == svn_node_file) */
+ delete_when = svn_io_file_del_none;
+
+ /* ### Do we need a pool cleanup to remove the copy? We can't use
+ ### svn_io_file_del_on_pool_cleanup above because a) it won't
+ ### handle the directory case and b) we need to be able to remove
+ ### the cleanup before queueing the move work item. */
+
+ if (file_copy && !unversioned)
+ {
+ svn_boolean_t modified;
+ /* It's faster to look for mods on the source now, as
+ the timestamp might match, than to examine the
+ destination later as the destination timestamp will
+ never match. */
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified,
+ db, src_abspath,
+ FALSE, scratch_pool));
+ if (!modified)
+ {
+ /* Why create a temp copy if we can just reinstall from pristine? */
+ SVN_ERR(svn_wc__wq_build_file_install(work_item,
+ db, dst_abspath, NULL, FALSE,
+ TRUE,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Set DST_TMP_ABSPATH to a temporary unique path. If *KIND is file, leave
+ a file there and then overwrite it; otherwise leave no node on disk at
+ that path. In the latter case, something else might use that path
+ before we get around to using it a moment later, but never mind. */
+ SVN_ERR(svn_io_open_unique_file3(NULL, &dst_tmp_abspath, tmpdir_abspath,
+ delete_when, scratch_pool, scratch_pool));
+
+ if (*kind == svn_node_dir)
+ {
+ if (file_copy)
+ SVN_ERR(svn_io_copy_dir_recursively(
+ src_abspath,
+ tmpdir_abspath,
+ svn_dirent_basename(dst_tmp_abspath, scratch_pool),
+ TRUE, /* copy_perms */
+ cancel_func, cancel_baton,
+ scratch_pool));
+ else
+ SVN_ERR(svn_io_dir_make(dst_tmp_abspath, APR_OS_DEFAULT, scratch_pool));
+ }
+ else if (!is_special)
+ SVN_ERR(svn_io_copy_file(src_abspath, dst_tmp_abspath,
+ TRUE /* copy_perms */,
+ scratch_pool));
+ else
+ SVN_ERR(svn_io_copy_link(src_abspath, dst_tmp_abspath, scratch_pool));
+
+ if (file_copy)
+ {
+ /* Remove 'read-only' from the destination file; it's a local add now. */
+ SVN_ERR(svn_io_set_file_read_write(dst_tmp_abspath,
+ FALSE, scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__wq_build_file_move(work_item, db, dst_abspath,
+ dst_tmp_abspath, dst_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy the versioned file SRC_ABSPATH in DB to the path DST_ABSPATH in DB.
+ If METADATA_ONLY is true, copy only the versioned metadata,
+ otherwise copy both the versioned metadata and the filesystem node (even
+ if it is the wrong kind, and recursively if it is a dir).
+
+ If IS_MOVE is true, record move information in working copy meta
+ data in addition to copying the file.
+
+ If the versioned file has a text conflict, and the .mine file exists in
+ the filesystem, copy the .mine file to DST_ABSPATH. Otherwise, copy the
+ versioned file itself.
+
+ This also works for versioned symlinks that are stored in the db as
+ svn_node_file with svn:special set. */
+static svn_error_t *
+copy_versioned_file(svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *dst_abspath,
+ const char *dst_op_root_abspath,
+ const char *tmpdir_abspath,
+ svn_boolean_t metadata_only,
+ svn_boolean_t conflicted,
+ svn_boolean_t is_move,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *work_items = NULL;
+
+ /* In case we are copying from one WC to another (e.g. an external dir),
+ ensure the destination WC has a copy of the pristine text. */
+
+ /* Prepare a temp copy of the filesystem node. It is usually a file, but
+ copy recursively if it's a dir. */
+ if (!metadata_only)
+ {
+ const char *my_src_abspath = NULL;
+ svn_boolean_t handle_as_unversioned = FALSE;
+
+ /* By default, take the copy source as given. */
+ my_src_abspath = src_abspath;
+
+ if (conflicted)
+ {
+ svn_skel_t *conflict;
+ const char *conflict_working;
+ svn_error_t *err;
+
+ /* Is there a text conflict at the source path? */
+ SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath,
+ scratch_pool, scratch_pool));
+
+ err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL,
+ db, src_abspath, conflict,
+ scratch_pool,
+ scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_MISSING)
+ {
+ /* not text conflicted */
+ svn_error_clear(err);
+ conflict_working = NULL;
+ }
+ else
+ SVN_ERR(err);
+
+ if (conflict_working)
+ {
+ svn_node_kind_t working_kind;
+
+ /* Does the ".mine" file exist? */
+ SVN_ERR(svn_io_check_path(conflict_working, &working_kind,
+ scratch_pool));
+
+ if (working_kind == svn_node_file)
+ {
+ /* Don't perform unmodified/pristine optimization */
+ handle_as_unversioned = TRUE;
+ my_src_abspath = conflict_working;
+ }
+ }
+ }
+
+ SVN_ERR(copy_to_tmpdir(&work_items, NULL, db, my_src_abspath,
+ dst_abspath, tmpdir_abspath,
+ TRUE /* file_copy */,
+ handle_as_unversioned /* unversioned */,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+ }
+
+ /* Copy the (single) node's metadata, and move the new filesystem node
+ into place. */
+ SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
+ dst_op_root_abspath, is_move, work_items,
+ scratch_pool));
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
+ scratch_pool);
+ notify->kind = svn_node_file;
+
+ /* When we notify that we performed a copy, make sure we already did */
+ if (work_items != NULL)
+ SVN_ERR(svn_wc__wq_run(db, dst_abspath,
+ cancel_func, cancel_baton, scratch_pool));
+ (*notify_func)(notify_baton, notify, scratch_pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Copy the versioned dir SRC_ABSPATH in DB to the path DST_ABSPATH in DB,
+ recursively. If METADATA_ONLY is true, copy only the versioned metadata,
+ otherwise copy both the versioned metadata and the filesystem nodes (even
+ if they are the wrong kind, and including unversioned children).
+ If IS_MOVE is true, record move information in working copy meta
+ data in addition to copying the directory.
+
+ WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root)
+ */
+static svn_error_t *
+copy_versioned_dir(svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *dst_abspath,
+ const char *dst_op_root_abspath,
+ const char *tmpdir_abspath,
+ svn_boolean_t metadata_only,
+ svn_boolean_t is_move,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *work_items = NULL;
+ const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
+ apr_hash_t *versioned_children;
+ apr_hash_t *conflicted_children;
+ apr_hash_t *disk_children;
+ apr_hash_index_t *hi;
+ svn_node_kind_t disk_kind;
+ apr_pool_t *iterpool;
+
+ /* Prepare a temp copy of the single filesystem node (usually a dir). */
+ if (!metadata_only)
+ {
+ SVN_ERR(copy_to_tmpdir(&work_items, &disk_kind,
+ db, src_abspath, dst_abspath,
+ tmpdir_abspath,
+ FALSE /* file_copy */,
+ FALSE /* unversioned */,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+ }
+
+ /* Copy the (single) node's metadata, and move the new filesystem node
+ into place. */
+ SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath,
+ dst_op_root_abspath, is_move, work_items,
+ scratch_pool));
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(dst_abspath, svn_wc_notify_add,
+ scratch_pool);
+ notify->kind = svn_node_dir;
+
+ /* When we notify that we performed a copy, make sure we already did */
+ if (work_items != NULL)
+ SVN_ERR(svn_wc__wq_run(db, dir_abspath,
+ cancel_func, cancel_baton, scratch_pool));
+
+ (*notify_func)(notify_baton, notify, scratch_pool);
+ }
+
+ if (!metadata_only && disk_kind == svn_node_dir)
+ /* All filesystem children, versioned and unversioned. We're only
+ interested in their names, so we can pass TRUE as the only_check_type
+ param. */
+ SVN_ERR(svn_io_get_dirents3(&disk_children, src_abspath, TRUE,
+ scratch_pool, scratch_pool));
+ else
+ disk_children = NULL;
+
+ /* Copy all the versioned children */
+ iterpool = svn_pool_create(scratch_pool);
+ SVN_ERR(svn_wc__db_read_children_info(&versioned_children,
+ &conflicted_children,
+ db, src_abspath,
+ scratch_pool, iterpool));
+ for (hi = apr_hash_first(scratch_pool, versioned_children);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child_name, *child_src_abspath, *child_dst_abspath;
+ struct svn_wc__db_info_t *info;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ child_name = svn__apr_hash_index_key(hi);
+ info = svn__apr_hash_index_val(hi);
+ child_src_abspath = svn_dirent_join(src_abspath, child_name, iterpool);
+ child_dst_abspath = svn_dirent_join(dst_abspath, child_name, iterpool);
+
+ if (info->op_root)
+ SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db,
+ child_src_abspath,
+ child_dst_abspath,
+ is_move,
+ scratch_pool));
+
+ if (info->status == svn_wc__db_status_normal
+ || info->status == svn_wc__db_status_added)
+ {
+ /* We have more work to do than just changing the DB */
+ if (info->kind == svn_node_file)
+ {
+ /* We should skip this node if this child is a file external
+ (issues #3589, #4000) */
+ if (!info->file_external)
+ SVN_ERR(copy_versioned_file(db,
+ child_src_abspath,
+ child_dst_abspath,
+ dst_op_root_abspath,
+ tmpdir_abspath,
+ metadata_only, info->conflicted,
+ is_move,
+ cancel_func, cancel_baton,
+ NULL, NULL,
+ iterpool));
+ }
+ else if (info->kind == svn_node_dir)
+ SVN_ERR(copy_versioned_dir(db,
+ child_src_abspath, child_dst_abspath,
+ dst_op_root_abspath, tmpdir_abspath,
+ metadata_only, is_move,
+ cancel_func, cancel_baton, NULL, NULL,
+ iterpool));
+ else
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("cannot handle node kind for '%s'"),
+ svn_dirent_local_style(child_src_abspath,
+ scratch_pool));
+ }
+ else if (info->status == svn_wc__db_status_deleted
+ || info->status == svn_wc__db_status_not_present
+ || info->status == svn_wc__db_status_excluded)
+ {
+ /* This will be copied as some kind of deletion. Don't touch
+ any actual files */
+ SVN_ERR(svn_wc__db_op_copy(db, child_src_abspath,
+ child_dst_abspath, dst_op_root_abspath,
+ is_move, NULL, iterpool));
+
+ /* Don't recurse on children while all we do is creating not-present
+ children */
+ }
+ else if (info->status == svn_wc__db_status_incomplete)
+ {
+ /* Should go ahead and copy incomplete to incomplete? Try to
+ copy as much as possible, or give up early? */
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot handle status of '%s'"),
+ svn_dirent_local_style(child_src_abspath,
+ iterpool));
+ }
+ else
+ {
+ SVN_ERR_ASSERT(info->status == svn_wc__db_status_server_excluded);
+
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot copy '%s' excluded by server"),
+ svn_dirent_local_style(child_src_abspath,
+ iterpool));
+ }
+
+ if (disk_children
+ && (info->status == svn_wc__db_status_normal
+ || info->status == svn_wc__db_status_added))
+ {
+ /* Remove versioned child as it has been handled */
+ svn_hash_sets(disk_children, child_name, NULL);
+ }
+ }
+
+ /* Copy the remaining filesystem children, which are unversioned, skipping
+ any conflict-marker files. */
+ if (disk_children && apr_hash_count(disk_children))
+ {
+ apr_hash_t *marker_files;
+
+ SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db,
+ src_abspath, scratch_pool,
+ scratch_pool));
+
+ work_items = NULL;
+
+ for (hi = apr_hash_first(scratch_pool, disk_children); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const char *unver_src_abspath, *unver_dst_abspath;
+ svn_skel_t *work_item;
+
+ if (svn_wc_is_adm_dir(name, iterpool))
+ continue;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ svn_pool_clear(iterpool);
+ unver_src_abspath = svn_dirent_join(src_abspath, name, iterpool);
+ unver_dst_abspath = svn_dirent_join(dst_abspath, name, iterpool);
+
+ if (marker_files &&
+ svn_hash_gets(marker_files, unver_src_abspath))
+ continue;
+
+ SVN_ERR(copy_to_tmpdir(&work_item, NULL, db, unver_src_abspath,
+ unver_dst_abspath, tmpdir_abspath,
+ TRUE /* recursive */, TRUE /* unversioned */,
+ cancel_func, cancel_baton,
+ scratch_pool, iterpool));
+
+ if (work_item)
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+ SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_items, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The guts of svn_wc_copy3() and svn_wc_move().
+ * The additional parameter IS_MOVE indicates whether this is a copy or
+ * a move operation.
+ *
+ * If MOVE_DEGRADED_TO_COPY is not NULL and a move had to be degraded
+ * to a copy, then set *MOVE_DEGRADED_TO_COPY. */
+static svn_error_t *
+copy_or_move(svn_boolean_t *move_degraded_to_copy,
+ svn_wc_context_t *wc_ctx,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t metadata_only,
+ svn_boolean_t is_move,
+ svn_boolean_t allow_mixed_revisions,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_node_kind_t src_db_kind;
+ const char *dstdir_abspath;
+ svn_boolean_t conflicted;
+ const char *tmpdir_abspath;
+ const char *src_wcroot_abspath;
+ const char *dst_wcroot_abspath;
+ svn_boolean_t within_one_wc;
+ svn_wc__db_status_t src_status;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+ dstdir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
+
+ /* Ensure DSTDIR_ABSPATH belongs to the same repository as SRC_ABSPATH;
+ throw an error if not. */
+ {
+ svn_wc__db_status_t dstdir_status;
+ const char *src_repos_root_url, *dst_repos_root_url;
+ const char *src_repos_uuid, *dst_repos_uuid;
+ const char *src_repos_relpath;
+
+ err = svn_wc__db_read_info(&src_status, &src_db_kind, NULL,
+ &src_repos_relpath, &src_repos_root_url,
+ &src_repos_uuid, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, &conflicted, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ db, src_abspath, scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ /* Replicate old error code and text */
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+ }
+ else
+ SVN_ERR(err);
+
+ /* Do this now, as we know the right data is cached */
+ SVN_ERR(svn_wc__db_get_wcroot(&src_wcroot_abspath, db, src_abspath,
+ scratch_pool, scratch_pool));
+
+ switch (src_status)
+ {
+ case svn_wc__db_status_deleted:
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Deleted node '%s' can't be copied."),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+
+ case svn_wc__db_status_excluded:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_not_present:
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+ default:
+ break;
+ }
+
+ if (is_move && ! strcmp(src_abspath, src_wcroot_abspath))
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("'%s' is the root of a working copy and "
+ "cannot be moved"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+ }
+ if (is_move && src_repos_relpath && !src_repos_relpath[0])
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("'%s' represents the repository root "
+ "and cannot be moved"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+ }
+
+ err = svn_wc__db_read_info(&dstdir_status, NULL, NULL, NULL,
+ &dst_repos_root_url, &dst_repos_uuid, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, dstdir_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ /* An unversioned destination directory exists on disk. */
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(dstdir_abspath,
+ scratch_pool));
+ }
+ else
+ SVN_ERR(err);
+
+ /* Do this now, as we know the right data is cached */
+ SVN_ERR(svn_wc__db_get_wcroot(&dst_wcroot_abspath, db, dstdir_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!src_repos_root_url)
+ {
+ if (src_status == svn_wc__db_status_added)
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
+ &src_repos_root_url,
+ &src_repos_uuid, NULL, NULL, NULL,
+ NULL,
+ db, src_abspath,
+ scratch_pool, scratch_pool));
+ else
+ /* If not added, the node must have a base or we can't copy */
+ SVN_ERR(svn_wc__db_scan_base_repos(NULL, &src_repos_root_url,
+ &src_repos_uuid,
+ db, src_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ if (!dst_repos_root_url)
+ {
+ if (dstdir_status == svn_wc__db_status_added)
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
+ &dst_repos_root_url,
+ &dst_repos_uuid, NULL, NULL, NULL,
+ NULL,
+ db, dstdir_abspath,
+ scratch_pool, scratch_pool));
+ else
+ /* If not added, the node must have a base or we can't copy */
+ SVN_ERR(svn_wc__db_scan_base_repos(NULL, &dst_repos_root_url,
+ &dst_repos_uuid,
+ db, dstdir_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ if (strcmp(src_repos_root_url, dst_repos_root_url) != 0
+ || strcmp(src_repos_uuid, dst_repos_uuid) != 0)
+ return svn_error_createf(
+ SVN_ERR_WC_INVALID_SCHEDULE, NULL,
+ _("Cannot copy to '%s', as it is not from repository '%s'; "
+ "it is from '%s'"),
+ svn_dirent_local_style(dst_abspath, scratch_pool),
+ src_repos_root_url, dst_repos_root_url);
+
+ if (dstdir_status == svn_wc__db_status_deleted)
+ return svn_error_createf(
+ SVN_ERR_WC_INVALID_SCHEDULE, NULL,
+ _("Cannot copy to '%s' as it is scheduled for deletion"),
+ svn_dirent_local_style(dst_abspath, scratch_pool));
+ /* ### should report dstdir_abspath instead of dst_abspath? */
+ }
+
+ /* TODO(#2843): Rework the error report. */
+ /* Check if the copy target is missing or hidden and thus not exist on the
+ disk, before actually doing the file copy. */
+ {
+ svn_wc__db_status_t dst_status;
+
+ err = svn_wc__db_read_info(&dst_status, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ db, dst_abspath, scratch_pool, scratch_pool);
+
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ if (!err)
+ switch (dst_status)
+ {
+ case svn_wc__db_status_excluded:
+ return svn_error_createf(
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("'%s' is already under version control "
+ "but is excluded."),
+ svn_dirent_local_style(dst_abspath, scratch_pool));
+ case svn_wc__db_status_server_excluded:
+ return svn_error_createf(
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("'%s' is already under version control"),
+ svn_dirent_local_style(dst_abspath, scratch_pool));
+
+ case svn_wc__db_status_deleted:
+ case svn_wc__db_status_not_present:
+ break; /* OK to add */
+
+ default:
+ return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
+ _("There is already a versioned item '%s'"),
+ svn_dirent_local_style(dst_abspath,
+ scratch_pool));
+ }
+ }
+
+ /* Check that the target path is not obstructed, if required. */
+ if (!metadata_only)
+ {
+ svn_node_kind_t dst_kind;
+
+ /* (We need only to check the root of the copy, not every path inside
+ copy_versioned_file/_dir.) */
+ SVN_ERR(svn_io_check_path(dst_abspath, &dst_kind, scratch_pool));
+ if (dst_kind != svn_node_none)
+ return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
+ _("'%s' already exists and is in the way"),
+ svn_dirent_local_style(dst_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db,
+ dstdir_abspath,
+ scratch_pool, scratch_pool));
+
+ within_one_wc = (strcmp(src_wcroot_abspath, dst_wcroot_abspath) == 0);
+
+ if (is_move
+ && !within_one_wc)
+ {
+ if (move_degraded_to_copy)
+ *move_degraded_to_copy = TRUE;
+
+ is_move = FALSE;
+ }
+
+ if (!within_one_wc)
+ SVN_ERR(svn_wc__db_pristine_transfer(db, src_abspath, dst_wcroot_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ if (src_db_kind == svn_node_file
+ || src_db_kind == svn_node_symlink)
+ {
+ err = copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath,
+ tmpdir_abspath,
+ metadata_only, conflicted, is_move,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool);
+ }
+ else
+ {
+ if (is_move
+ && src_status == svn_wc__db_status_normal)
+ {
+ svn_revnum_t min_rev;
+ svn_revnum_t max_rev;
+
+ /* Verify that the move source is a single-revision subtree. */
+ SVN_ERR(svn_wc__db_min_max_revisions(&min_rev, &max_rev, db,
+ src_abspath, FALSE, scratch_pool));
+ if (SVN_IS_VALID_REVNUM(min_rev) && SVN_IS_VALID_REVNUM(max_rev) &&
+ min_rev != max_rev)
+ {
+ if (!allow_mixed_revisions)
+ return svn_error_createf(SVN_ERR_WC_MIXED_REVISIONS, NULL,
+ _("Cannot move mixed-revision "
+ "subtree '%s' [%ld:%ld]; "
+ "try updating it first"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool),
+ min_rev, max_rev);
+
+ is_move = FALSE;
+ if (move_degraded_to_copy)
+ *move_degraded_to_copy = TRUE;
+ }
+ }
+
+ err = copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath,
+ tmpdir_abspath, metadata_only, is_move,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool);
+ }
+
+ if (err && svn_error_find_cause(err, SVN_ERR_CANCELLED))
+ return svn_error_trace(err);
+
+ if (is_move)
+ err = svn_error_compose_create(err,
+ svn_wc__db_op_handle_move_back(NULL,
+ db, dst_abspath, src_abspath,
+ NULL /* work_items */,
+ scratch_pool));
+
+ /* Run the work queue with the remaining work */
+ SVN_ERR(svn_error_compose_create(
+ err,
+ svn_wc__wq_run(db, dst_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool)));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Public Interface */
+
+svn_error_t *
+svn_wc_copy3(svn_wc_context_t *wc_ctx,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t metadata_only,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* Verify that we have the required write lock. */
+ SVN_ERR(svn_wc__write_check(wc_ctx->db,
+ svn_dirent_dirname(dst_abspath, scratch_pool),
+ scratch_pool));
+
+ return svn_error_trace(copy_or_move(NULL, wc_ctx, src_abspath, dst_abspath,
+ metadata_only, FALSE /* is_move */,
+ TRUE /* allow_mixed_revisions */,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+}
+
+
+/* Remove the conflict markers of NODE_ABSPATH, that were left over after
+ copying NODE_ABSPATH from SRC_ABSPATH.
+
+ Only use this function when you know what you're doing. This function
+ explicitly ignores some case insensitivity issues!
+
+ */
+static svn_error_t *
+remove_node_conflict_markers(svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *node_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *conflict;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Do we have conflict markers that should be removed? */
+ if (conflict != NULL)
+ {
+ const apr_array_header_t *markers;
+ int i;
+ const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool);
+ const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath,
+ conflict,
+ scratch_pool, scratch_pool));
+
+ /* No iterpool: Maximum number of possible conflict markers is 4 */
+ for (i = 0; markers && (i < markers->nelts); i++)
+ {
+ const char *marker_abspath;
+ const char *child_relpath;
+ const char *child_abpath;
+
+ marker_abspath = APR_ARRAY_IDX(markers, i, const char *);
+
+ child_relpath = svn_dirent_is_child(src_dir, marker_abspath, NULL);
+
+ if (child_relpath)
+ {
+ child_abpath = svn_dirent_join(dst_dir, child_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_io_remove_file2(child_abpath, TRUE, scratch_pool));
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Remove all the conflict markers below SRC_DIR_ABSPATH, that were left over
+ after copying WC_DIR_ABSPATH from SRC_DIR_ABSPATH.
+
+ This function doesn't remove the conflict markers on WC_DIR_ABSPATH
+ itself!
+
+ Only use this function when you know what you're doing. This function
+ explicitly ignores some case insensitivity issues!
+ */
+static svn_error_t *
+remove_all_conflict_markers(svn_wc__db_t *db,
+ const char *src_dir_abspath,
+ const char *wc_dir_abspath,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *nodes;
+ apr_hash_t *conflicts; /* Unused */
+ apr_hash_index_t *hi;
+
+ /* Reuse a status helper to obtain all subdirs and conflicts in a single
+ db transaction. */
+ /* ### This uses a rifle to kill a fly. But at least it doesn't use heavy
+ artillery. */
+ SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db,
+ src_dir_abspath,
+ scratch_pool, iterpool));
+
+ for (hi = apr_hash_first(scratch_pool, nodes);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ struct svn_wc__db_info_t *info = svn__apr_hash_index_val(hi);
+
+ if (info->conflicted)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(remove_node_conflict_markers(
+ db,
+ svn_dirent_join(src_dir_abspath, name, iterpool),
+ svn_dirent_join(wc_dir_abspath, name, iterpool),
+ iterpool));
+ }
+ if (info->kind == svn_node_dir)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(remove_all_conflict_markers(
+ db,
+ svn_dirent_join(src_dir_abspath, name, iterpool),
+ svn_dirent_join(wc_dir_abspath, name, iterpool),
+ iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__move2(svn_wc_context_t *wc_ctx,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t metadata_only,
+ svn_boolean_t allow_mixed_revisions,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_boolean_t move_degraded_to_copy = FALSE;
+ svn_node_kind_t kind;
+ svn_boolean_t conflicted;
+
+ /* Verify that we have the required write locks. */
+ SVN_ERR(svn_wc__write_check(wc_ctx->db,
+ svn_dirent_dirname(src_abspath, scratch_pool),
+ scratch_pool));
+ SVN_ERR(svn_wc__write_check(wc_ctx->db,
+ svn_dirent_dirname(dst_abspath, scratch_pool),
+ scratch_pool));
+
+ SVN_ERR(copy_or_move(&move_degraded_to_copy,
+ wc_ctx, src_abspath, dst_abspath,
+ TRUE /* metadata_only */,
+ TRUE /* is_move */,
+ allow_mixed_revisions,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+
+ /* An interrupt at this point will leave the new copy marked as
+ moved-here but the source has not yet been deleted or marked as
+ moved-to. */
+
+ /* Should we be using a workqueue for this move? It's not clear.
+ What should happen if the copy above is interrupted? The user
+ may want to abort the move and a workqueue might interfere with
+ that.
+
+ BH: On Windows it is not unlikely to encounter an access denied on
+ this line. Installing the move in the workqueue via the copy_or_move
+ might make it hard to recover from that situation, while the DB
+ is still in a valid state. So be careful when switching this over
+ to the workqueue. */
+ if (!metadata_only)
+ SVN_ERR(svn_io_file_rename(src_abspath, dst_abspath, scratch_pool));
+
+ SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, src_abspath,
+ scratch_pool, scratch_pool));
+
+ if (kind == svn_node_dir)
+ SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath,
+ scratch_pool));
+
+ if (conflicted)
+ SVN_ERR(remove_node_conflict_markers(db, src_abspath, dst_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_op_delete(db, src_abspath,
+ move_degraded_to_copy ? NULL : dst_abspath,
+ TRUE /* delete_dir_externals */,
+ NULL /* conflict */, NULL /* work_items */,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/crop.c b/contrib/subversion/subversion/libsvn_wc/crop.c
new file mode 100644
index 0000000..2278476
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/crop.c
@@ -0,0 +1,361 @@
+/*
+ * crop.c: Cropping the WC
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+#include "svn_wc.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+
+#include "wc.h"
+#include "workqueue.h"
+
+#include "svn_private_config.h"
+
+/* Helper function that crops the children of the LOCAL_ABSPATH, under the
+ * constraint of NEW_DEPTH. The DIR_PATH itself will never be cropped. The
+ * whole subtree should have been locked.
+ *
+ * DIR_DEPTH is the current depth of LOCAL_ABSPATH as stored in DB.
+ *
+ * If NOTIFY_FUNC is not null, each file and ROOT of subtree will be reported
+ * upon remove.
+ */
+static svn_error_t *
+crop_children(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t dir_depth,
+ svn_depth_t new_depth,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool;
+ int i;
+
+ SVN_ERR_ASSERT(new_depth >= svn_depth_empty
+ && new_depth <= svn_depth_infinity);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ iterpool = svn_pool_create(pool);
+
+ if (dir_depth == svn_depth_unknown)
+ dir_depth = svn_depth_infinity;
+
+ /* Update the depth of target first, if needed. */
+ if (dir_depth > new_depth)
+ SVN_ERR(svn_wc__db_op_set_base_depth(db, local_abspath, new_depth,
+ iterpool));
+
+ /* Looping over current directory's SVN entries: */
+ SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath, pool,
+ iterpool));
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *child_name = APR_ARRAY_IDX(children, i, const char *);
+ const char *child_abspath;
+ svn_wc__db_status_t child_status;
+ svn_node_kind_t kind;
+ svn_depth_t child_depth;
+
+ svn_pool_clear(iterpool);
+
+ /* Get the next node */
+ child_abspath = svn_dirent_join(local_abspath, child_name, iterpool);
+
+ SVN_ERR(svn_wc__db_read_info(&child_status, &kind, NULL, NULL, NULL,
+ NULL,NULL, NULL, NULL, &child_depth,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ db, child_abspath, iterpool, iterpool));
+
+ if (child_status == svn_wc__db_status_server_excluded ||
+ child_status == svn_wc__db_status_excluded ||
+ child_status == svn_wc__db_status_not_present)
+ {
+ svn_depth_t remove_below = (kind == svn_node_dir)
+ ? svn_depth_immediates
+ : svn_depth_files;
+ if (new_depth < remove_below)
+ SVN_ERR(svn_wc__db_base_remove(db, child_abspath,
+ FALSE /* keep_as_working */,
+ FALSE /* queue_deletes */,
+ SVN_INVALID_REVNUM,
+ NULL, NULL, iterpool));
+
+ continue;
+ }
+ else if (kind == svn_node_file)
+ {
+ if (new_depth == svn_depth_empty)
+ SVN_ERR(svn_wc__db_op_remove_node(NULL,
+ db, child_abspath,
+ TRUE /* destroy */,
+ FALSE /* destroy_changes */,
+ SVN_INVALID_REVNUM,
+ svn_wc__db_status_not_present,
+ svn_node_none,
+ NULL, NULL,
+ cancel_func, cancel_baton,
+ iterpool));
+ else
+ continue;
+
+ }
+ else if (kind == svn_node_dir)
+ {
+ if (new_depth < svn_depth_immediates)
+ {
+ SVN_ERR(svn_wc__db_op_remove_node(NULL,
+ db, child_abspath,
+ TRUE /* destroy */,
+ FALSE /* destroy_changes */,
+ SVN_INVALID_REVNUM,
+ svn_wc__db_status_not_present,
+ svn_node_none,
+ NULL, NULL,
+ cancel_func, cancel_baton,
+ iterpool));
+ }
+ else
+ {
+ SVN_ERR(crop_children(db,
+ child_abspath,
+ child_depth,
+ svn_depth_empty,
+ notify_func,
+ notify_baton,
+ cancel_func,
+ cancel_baton,
+ iterpool));
+ continue;
+ }
+ }
+ else
+ {
+ return svn_error_createf
+ (SVN_ERR_NODE_UNKNOWN_KIND, NULL, _("Unknown node kind for '%s'"),
+ svn_dirent_local_style(child_abspath, iterpool));
+ }
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify;
+ notify = svn_wc_create_notify(child_abspath,
+ svn_wc_notify_delete,
+ iterpool);
+ (*notify_func)(notify_baton, notify, iterpool);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_exclude(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_root, is_switched;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_revnum_t revision;
+ const char *repos_relpath, *repos_root, *repos_uuid;
+
+ SVN_ERR(svn_wc__db_is_switched(&is_root, &is_switched, NULL,
+ wc_ctx->db, local_abspath, scratch_pool));
+
+ if (is_root)
+ {
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot exclude '%s': "
+ "it is a working copy root"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ if (is_switched)
+ {
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot exclude '%s': "
+ "it is a switched path"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, &repos_relpath,
+ &repos_root, &repos_uuid, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ switch (status)
+ {
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_excluded:
+ case svn_wc__db_status_not_present:
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ case svn_wc__db_status_added:
+ /* Would have to check parents if we want to allow this */
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot exclude '%s': it is to be added "
+ "to the repository. Try commit instead"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ case svn_wc__db_status_deleted:
+ /* Would have to check parents if we want to allow this */
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot exclude '%s': it is to be deleted "
+ "from the repository. Try commit instead"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ case svn_wc__db_status_normal:
+ case svn_wc__db_status_incomplete:
+ default:
+ break; /* Ok to exclude */
+ }
+
+ /* Remove all working copy data below local_abspath */
+ SVN_ERR(svn_wc__db_op_remove_node(NULL,
+ wc_ctx->db, local_abspath,
+ TRUE /* destroy */,
+ FALSE /* destroy_changes */,
+ revision,
+ svn_wc__db_status_excluded,
+ kind,
+ NULL, NULL,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify;
+ notify = svn_wc_create_notify(local_abspath,
+ svn_wc_notify_exclude,
+ scratch_pool);
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_crop_tree2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_depth_t dir_depth;
+
+ /* Only makes sense when the depth is restrictive. */
+ if (depth == svn_depth_infinity)
+ return SVN_NO_ERROR; /* Nothing to crop */
+ if (!(depth >= svn_depth_empty && depth < svn_depth_infinity))
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Can only crop a working copy with a restrictive depth"));
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, &dir_depth, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (kind != svn_node_dir)
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Can only crop directories"));
+
+ switch (status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_server_excluded:
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ case svn_wc__db_status_deleted:
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot crop '%s': it is going to be removed "
+ "from repository. Try commit instead"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ case svn_wc__db_status_added:
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot crop '%s': it is to be added "
+ "to the repository. Try commit instead"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ case svn_wc__db_status_excluded:
+ return SVN_NO_ERROR; /* Nothing to do */
+
+ case svn_wc__db_status_normal:
+ case svn_wc__db_status_incomplete:
+ break;
+
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ SVN_ERR(crop_children(db, local_abspath, dir_depth, depth,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton, scratch_pool));
+
+ return svn_error_trace(svn_wc__wq_run(db, local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/delete.c b/contrib/subversion/subversion/libsvn_wc/delete.c
new file mode 100644
index 0000000..37c8af0
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/delete.c
@@ -0,0 +1,508 @@
+/*
+ * delete.c: Handling of the in-wc side of the delete operation
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_wc.h"
+#include "svn_io.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "workqueue.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+/* Remove/erase PATH from the working copy. This involves deleting PATH
+ * from the physical filesystem. PATH is assumed to be an unversioned file
+ * or directory.
+ *
+ * If ignore_enoent is TRUE, ignore missing targets.
+ *
+ * If CANCEL_FUNC is non-null, invoke it with CANCEL_BATON at various
+ * points, return any error immediately.
+ */
+static svn_error_t *
+erase_unversioned_from_wc(const char *path,
+ svn_boolean_t ignore_enoent,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ /* Optimize the common case: try to delete the file */
+ err = svn_io_remove_file2(path, ignore_enoent, scratch_pool);
+ if (err)
+ {
+ /* Then maybe it was a directory? */
+ svn_error_clear(err);
+
+ err = svn_io_remove_dir2(path, ignore_enoent, cancel_func, cancel_baton,
+ scratch_pool);
+
+ if (err)
+ {
+ /* We're unlikely to end up here. But we need this fallback
+ to make sure we report the right error *and* try the
+ correct deletion at least once. */
+ svn_node_kind_t kind;
+
+ svn_error_clear(err);
+ SVN_ERR(svn_io_check_path(path, &kind, scratch_pool));
+ if (kind == svn_node_file)
+ SVN_ERR(svn_io_remove_file2(path, ignore_enoent, scratch_pool));
+ else if (kind == svn_node_dir)
+ SVN_ERR(svn_io_remove_dir2(path, ignore_enoent,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ else if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
+ _("'%s' does not exist"),
+ svn_dirent_local_style(path,
+ scratch_pool));
+ else
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Unsupported node kind for path '%s'"),
+ svn_dirent_local_style(path,
+ scratch_pool));
+
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_wc__delete and svn_wc__delete_many */
+static svn_error_t *
+create_delete_wq_items(svn_skel_t **work_items,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ svn_boolean_t conflicted,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *work_items = NULL;
+
+ /* Schedule the on-disk delete */
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_wc__wq_build_dir_remove(work_items, db, local_abspath,
+ local_abspath,
+ TRUE /* recursive */,
+ result_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__wq_build_file_remove(work_items, db, local_abspath,
+ local_abspath,
+ result_pool, scratch_pool));
+
+ /* Read conflicts, to allow deleting the markers after updating the DB */
+ if (conflicted)
+ {
+ svn_skel_t *conflict;
+ const apr_array_header_t *markers;
+ int i;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflict, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_read_markers(&markers, db, local_abspath,
+ conflict,
+ scratch_pool, scratch_pool));
+
+ /* Maximum number of markers is 4, so no iterpool */
+ for (i = 0; markers && i < markers->nelts; i++)
+ {
+ const char *marker_abspath;
+ svn_node_kind_t marker_kind;
+
+ marker_abspath = APR_ARRAY_IDX(markers, i, const char *);
+ SVN_ERR(svn_io_check_path(marker_abspath, &marker_kind,
+ scratch_pool));
+
+ if (marker_kind == svn_node_file)
+ {
+ svn_skel_t *work_item;
+
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db,
+ local_abspath,
+ marker_abspath,
+ result_pool,
+ scratch_pool));
+
+ *work_items = svn_wc__wq_merge(*work_items, work_item,
+ result_pool);
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__delete_many(svn_wc_context_t *wc_ctx,
+ const apr_array_header_t *targets,
+ svn_boolean_t keep_local,
+ svn_boolean_t delete_unversioned_target,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_error_t *err;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_skel_t *work_items = NULL;
+ apr_array_header_t *versioned_targets;
+ const char *local_abspath;
+ int i;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+ versioned_targets = apr_array_make(scratch_pool, targets->nelts,
+ sizeof(const char *));
+ for (i = 0; i < targets->nelts; i++)
+ {
+ svn_boolean_t conflicted = FALSE;
+ const char *repos_relpath;
+
+ svn_pool_clear(iterpool);
+
+ local_abspath = APR_ARRAY_IDX(targets, i, const char *);
+ err = svn_wc__db_read_info(&status, &kind, NULL, &repos_relpath, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, &conflicted,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath, iterpool, iterpool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ if (delete_unversioned_target && !keep_local)
+ SVN_ERR(erase_unversioned_from_wc(local_abspath, FALSE,
+ cancel_func, cancel_baton,
+ iterpool));
+ continue;
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ APR_ARRAY_PUSH(versioned_targets, const char *) = local_abspath;
+
+ switch (status)
+ {
+ /* svn_wc__db_status_server_excluded handled by
+ * svn_wc__db_op_delete_many */
+ case svn_wc__db_status_excluded:
+ case svn_wc__db_status_not_present:
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' cannot be deleted"),
+ svn_dirent_local_style(local_abspath,
+ iterpool));
+
+ /* Explicitly ignore other statii */
+ default:
+ break;
+ }
+
+ if (status == svn_wc__db_status_normal
+ && kind == svn_node_dir)
+ {
+ svn_boolean_t is_wcroot;
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath,
+ iterpool));
+
+ if (is_wcroot)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("'%s' is the root of a working copy and "
+ "cannot be deleted"),
+ svn_dirent_local_style(local_abspath,
+ iterpool));
+ }
+ if (repos_relpath && !repos_relpath[0])
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("'%s' represents the repository root "
+ "and cannot be deleted"),
+ svn_dirent_local_style(local_abspath,
+ iterpool));
+
+ /* Verify if we have a write lock on the parent of this node as we might
+ be changing the childlist of that directory. */
+ SVN_ERR(svn_wc__write_check(db, svn_dirent_dirname(local_abspath,
+ iterpool),
+ iterpool));
+
+ /* Prepare the on-disk delete */
+ if (!keep_local)
+ {
+ svn_skel_t *work_item;
+
+ SVN_ERR(create_delete_wq_items(&work_item, db, local_abspath, kind,
+ conflicted,
+ scratch_pool, iterpool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item,
+ scratch_pool);
+ }
+ }
+
+ if (versioned_targets->nelts == 0)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_op_delete_many(db, versioned_targets,
+ !keep_local /* delete_dir_externals */,
+ work_items,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool));
+
+ if (work_items != NULL)
+ {
+ /* Our only caller locked the wc, so for now assume it only passed
+ nodes from a single wc (asserted in svn_wc__db_op_delete_many) */
+ local_abspath = APR_ARRAY_IDX(versioned_targets, 0, const char *);
+
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_delete4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t keep_local,
+ svn_boolean_t delete_unversioned_target,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *pool = scratch_pool;
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_error_t *err;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_boolean_t conflicted;
+ svn_skel_t *work_items = NULL;
+ const char *repos_relpath;
+
+ err = svn_wc__db_read_info(&status, &kind, NULL, &repos_relpath, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, &conflicted,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath, pool, pool);
+
+ if (delete_unversioned_target &&
+ err != NULL && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+
+ if (!keep_local)
+ SVN_ERR(erase_unversioned_from_wc(local_abspath, FALSE,
+ cancel_func, cancel_baton,
+ pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ switch (status)
+ {
+ /* svn_wc__db_status_server_excluded handled by svn_wc__db_op_delete */
+ case svn_wc__db_status_excluded:
+ case svn_wc__db_status_not_present:
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' cannot be deleted"),
+ svn_dirent_local_style(local_abspath, pool));
+
+ /* Explicitly ignore other statii */
+ default:
+ break;
+ }
+
+ if (status == svn_wc__db_status_normal
+ && kind == svn_node_dir)
+ {
+ svn_boolean_t is_wcroot;
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, pool));
+
+ if (is_wcroot)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("'%s' is the root of a working copy and "
+ "cannot be deleted"),
+ svn_dirent_local_style(local_abspath, pool));
+ }
+ if (repos_relpath && !repos_relpath[0])
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("'%s' represents the repository root "
+ "and cannot be deleted"),
+ svn_dirent_local_style(local_abspath, pool));
+
+ /* Verify if we have a write lock on the parent of this node as we might
+ be changing the childlist of that directory. */
+ SVN_ERR(svn_wc__write_check(db, svn_dirent_dirname(local_abspath, pool),
+ pool));
+
+ /* Prepare the on-disk delete */
+ if (!keep_local)
+ {
+ SVN_ERR(create_delete_wq_items(&work_items, db, local_abspath, kind,
+ conflicted,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_op_delete(db, local_abspath,
+ NULL /*moved_to_abspath */,
+ !keep_local /* delete_dir_externals */,
+ NULL, work_items,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ pool));
+
+ if (work_items)
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_remove_from_revision_control(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t destroy_wf,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t left_something = FALSE;
+ svn_boolean_t is_root;
+ svn_error_t *err = NULL;
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool));
+
+ SVN_ERR(svn_wc__write_check(db, is_root ? local_abspath
+ : svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_op_remove_node(&left_something,
+ db, local_abspath,
+ destroy_wf /* destroy_wc */,
+ destroy_wf /* destroy_changes */,
+ SVN_INVALID_REVNUM,
+ svn_wc__db_status_not_present,
+ svn_node_none,
+ NULL, NULL,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__wq_run(db, local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ if (is_root)
+ {
+ /* Destroy the administrative area */
+ SVN_ERR(svn_wc__adm_destroy(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* And if we didn't leave something interesting, remove the directory */
+ if (!left_something && destroy_wf)
+ err = svn_io_dir_remove_nonrecursive(local_abspath, scratch_pool);
+ }
+
+ if (left_something || err)
+ return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, err, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_wc_status_func4_t for svn_wc_remove_from_revision_control2 */
+static svn_error_t *
+remove_from_revision_status_callback(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ /* For legacy reasons we only check the file contents for changes */
+ if (status->versioned
+ && status->kind == svn_node_file
+ && (status->text_status == svn_wc_status_modified
+ || status->text_status == svn_wc_status_conflicted))
+ {
+ return svn_error_createf(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL,
+ _("File '%s' has local modifications"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_remove_from_revision_control2(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t destroy_wf,
+ svn_boolean_t instant_error,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ if (instant_error)
+ {
+ SVN_ERR(svn_wc_walk_status(wc_ctx, local_abspath, svn_depth_infinity,
+ FALSE, FALSE, FALSE, NULL,
+ remove_from_revision_status_callback, NULL,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ return svn_error_trace(
+ svn_wc__internal_remove_from_revision_control(wc_ctx->db,
+ local_abspath,
+ destroy_wf,
+ cancel_func,
+ cancel_baton,
+ scratch_pool));
+}
+
diff --git a/contrib/subversion/subversion/libsvn_wc/deprecated.c b/contrib/subversion/subversion/libsvn_wc/deprecated.c
new file mode 100644
index 0000000..79cdb30
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/deprecated.c
@@ -0,0 +1,4582 @@
+/*
+ * deprecated.c: holding file for all deprecated APIs.
+ * "we can't lose 'em, but we can shun 'em!"
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* We define this here to remove any further warnings about the usage of
+ deprecated functions in this file. */
+#define SVN_DEPRECATED
+
+#include <apr_md5.h>
+
+#include "svn_wc.h"
+#include "svn_subst.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_hash.h"
+#include "svn_time.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+
+#include "private/svn_subr_private.h"
+#include "private/svn_wc_private.h"
+
+#include "wc.h"
+#include "entries.h"
+#include "lock.h"
+#include "props.h"
+#include "translate.h"
+#include "workqueue.h"
+
+#include "svn_private_config.h"
+
+/* baton for traversal_info_update */
+struct traversal_info_update_baton
+{
+ struct svn_wc_traversal_info_t *traversal;
+ svn_wc__db_t *db;
+};
+
+/* Helper for updating svn_wc_traversal_info_t structures
+ * Implements svn_wc_external_update_t */
+static svn_error_t *
+traversal_info_update(void *baton,
+ const char *local_abspath,
+ const svn_string_t *old_val,
+ const svn_string_t *new_val,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ const char *dup_path;
+ svn_wc_adm_access_t *adm_access;
+ struct traversal_info_update_baton *ub = baton;
+ apr_pool_t *dup_pool = ub->traversal->pool;
+ const char *dup_val = NULL;
+
+ /* We make the abspath relative by retrieving the access baton
+ for the specific directory */
+ adm_access = svn_wc__adm_retrieve_internal2(ub->db, local_abspath,
+ scratch_pool);
+
+ if (adm_access)
+ dup_path = apr_pstrdup(dup_pool, svn_wc_adm_access_path(adm_access));
+ else
+ dup_path = apr_pstrdup(dup_pool, local_abspath);
+
+ if (old_val)
+ {
+ dup_val = apr_pstrmemdup(dup_pool, old_val->data, old_val->len);
+
+ svn_hash_sets(ub->traversal->externals_old, dup_path, dup_val);
+ }
+
+ if (new_val)
+ {
+ /* In most cases the value is identical */
+ if (old_val != new_val)
+ dup_val = apr_pstrmemdup(dup_pool, new_val->data, new_val->len);
+
+ svn_hash_sets(ub->traversal->externals_new, dup_path, dup_val);
+ }
+
+ svn_hash_sets(ub->traversal->depths, dup_path, svn_depth_to_word(depth));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for functions that used to gather traversal_info */
+static svn_error_t *
+gather_traversal_info(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *path,
+ svn_depth_t depth,
+ struct svn_wc_traversal_info_t *traversal_info,
+ svn_boolean_t gather_as_old,
+ svn_boolean_t gather_as_new,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *externals;
+ apr_hash_t *ambient_depths;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_wc__externals_gather_definitions(&externals, &ambient_depths,
+ wc_ctx, local_abspath,
+ depth,
+ scratch_pool, scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *node_abspath = svn__apr_hash_index_key(hi);
+ const char *relpath;
+
+ relpath = svn_dirent_join(path,
+ svn_dirent_skip_ancestor(local_abspath,
+ node_abspath),
+ traversal_info->pool);
+
+ if (gather_as_old)
+ svn_hash_sets(traversal_info->externals_old, relpath,
+ svn__apr_hash_index_val(hi));
+
+ if (gather_as_new)
+ svn_hash_sets(traversal_info->externals_new, relpath,
+ svn__apr_hash_index_val(hi));
+
+ svn_hash_sets(traversal_info->depths, relpath,
+ svn_hash_gets(ambient_depths, node_abspath));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** From adm_crawler.c ***/
+
+svn_error_t *
+svn_wc_crawl_revisions4(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_ra_reporter3_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_depth_t depth,
+ svn_boolean_t honor_depth_exclude,
+ svn_boolean_t depth_compatibility_trick,
+ svn_boolean_t use_commit_times,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *wc_db = svn_wc__adm_get_db(adm_access);
+ const char *local_abspath;
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool));
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ SVN_ERR(svn_wc_crawl_revisions5(wc_ctx,
+ local_abspath,
+ reporter,
+ report_baton,
+ restore_files,
+ depth,
+ honor_depth_exclude,
+ depth_compatibility_trick,
+ use_commit_times,
+ NULL /* cancel_func */,
+ NULL /* cancel_baton */,
+ notify_func,
+ notify_baton,
+ pool));
+
+ if (traversal_info)
+ SVN_ERR(gather_traversal_info(wc_ctx, local_abspath, path, depth,
+ traversal_info, TRUE, FALSE, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+/*** Compatibility wrapper: turns an svn_ra_reporter2_t into an
+ svn_ra_reporter3_t.
+
+ This code looks like it duplicates code in libsvn_ra/ra_loader.c,
+ but it does not. That code makes an new thing look like an old
+ thing; this code makes an old thing look like a new thing. ***/
+
+struct wrap_3to2_report_baton {
+ const svn_ra_reporter2_t *reporter;
+ void *baton;
+};
+
+/* */
+static svn_error_t *wrap_3to2_set_path(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ struct wrap_3to2_report_baton *wrb = report_baton;
+
+ return wrb->reporter->set_path(wrb->baton, path, revision, start_empty,
+ lock_token, pool);
+}
+
+/* */
+static svn_error_t *wrap_3to2_delete_path(void *report_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct wrap_3to2_report_baton *wrb = report_baton;
+
+ return wrb->reporter->delete_path(wrb->baton, path, pool);
+}
+
+/* */
+static svn_error_t *wrap_3to2_link_path(void *report_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ struct wrap_3to2_report_baton *wrb = report_baton;
+
+ return wrb->reporter->link_path(wrb->baton, path, url, revision,
+ start_empty, lock_token, pool);
+}
+
+/* */
+static svn_error_t *wrap_3to2_finish_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ struct wrap_3to2_report_baton *wrb = report_baton;
+
+ return wrb->reporter->finish_report(wrb->baton, pool);
+}
+
+/* */
+static svn_error_t *wrap_3to2_abort_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ struct wrap_3to2_report_baton *wrb = report_baton;
+
+ return wrb->reporter->abort_report(wrb->baton, pool);
+}
+
+static const svn_ra_reporter3_t wrap_3to2_reporter = {
+ wrap_3to2_set_path,
+ wrap_3to2_delete_path,
+ wrap_3to2_link_path,
+ wrap_3to2_finish_report,
+ wrap_3to2_abort_report
+};
+
+svn_error_t *
+svn_wc_crawl_revisions3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_ra_reporter3_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_depth_t depth,
+ svn_boolean_t depth_compatibility_trick,
+ svn_boolean_t use_commit_times,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ return svn_wc_crawl_revisions4(path,
+ adm_access,
+ reporter, report_baton,
+ restore_files,
+ depth,
+ FALSE,
+ depth_compatibility_trick,
+ use_commit_times,
+ notify_func,
+ notify_baton,
+ traversal_info,
+ pool);
+}
+
+svn_error_t *
+svn_wc_crawl_revisions2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_ra_reporter2_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_boolean_t recurse,
+ svn_boolean_t use_commit_times,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ struct wrap_3to2_report_baton wrb;
+ wrb.reporter = reporter;
+ wrb.baton = report_baton;
+
+ return svn_wc_crawl_revisions3(path,
+ adm_access,
+ &wrap_3to2_reporter, &wrb,
+ restore_files,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ FALSE,
+ use_commit_times,
+ notify_func,
+ notify_baton,
+ traversal_info,
+ pool);
+}
+
+
+/* Baton for compat_call_notify_func below. */
+struct compat_notify_baton_t {
+ /* Wrapped func/baton. */
+ svn_wc_notify_func_t func;
+ void *baton;
+};
+
+
+/* Implements svn_wc_notify_func2_t. Call BATON->func (BATON is of type
+ svn_wc__compat_notify_baton_t), passing BATON->baton and the appropriate
+ arguments from NOTIFY. */
+static void
+compat_call_notify_func(void *baton,
+ const svn_wc_notify_t *n,
+ apr_pool_t *pool)
+{
+ struct compat_notify_baton_t *nb = baton;
+
+ if (nb->func)
+ (*nb->func)(nb->baton, n->path, n->action, n->kind, n->mime_type,
+ n->content_state, n->prop_state, n->revision);
+}
+
+
+/*** Compatibility wrapper: turns an svn_ra_reporter_t into an
+ svn_ra_reporter2_t.
+
+ This code looks like it duplicates code in libsvn_ra/ra_loader.c,
+ but it does not. That code makes an new thing look like an old
+ thing; this code makes an old thing look like a new thing. ***/
+
+struct wrap_2to1_report_baton {
+ const svn_ra_reporter_t *reporter;
+ void *baton;
+};
+
+/* */
+static svn_error_t *wrap_2to1_set_path(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ struct wrap_2to1_report_baton *wrb = report_baton;
+
+ return wrb->reporter->set_path(wrb->baton, path, revision, start_empty,
+ pool);
+}
+
+/* */
+static svn_error_t *wrap_2to1_delete_path(void *report_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct wrap_2to1_report_baton *wrb = report_baton;
+
+ return wrb->reporter->delete_path(wrb->baton, path, pool);
+}
+
+/* */
+static svn_error_t *wrap_2to1_link_path(void *report_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ struct wrap_2to1_report_baton *wrb = report_baton;
+
+ return wrb->reporter->link_path(wrb->baton, path, url, revision,
+ start_empty, pool);
+}
+
+/* */
+static svn_error_t *wrap_2to1_finish_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ struct wrap_2to1_report_baton *wrb = report_baton;
+
+ return wrb->reporter->finish_report(wrb->baton, pool);
+}
+
+/* */
+static svn_error_t *wrap_2to1_abort_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ struct wrap_2to1_report_baton *wrb = report_baton;
+
+ return wrb->reporter->abort_report(wrb->baton, pool);
+}
+
+static const svn_ra_reporter2_t wrap_2to1_reporter = {
+ wrap_2to1_set_path,
+ wrap_2to1_delete_path,
+ wrap_2to1_link_path,
+ wrap_2to1_finish_report,
+ wrap_2to1_abort_report
+};
+
+svn_error_t *
+svn_wc_crawl_revisions(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_ra_reporter_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_boolean_t recurse,
+ svn_boolean_t use_commit_times,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ struct wrap_2to1_report_baton wrb;
+ struct compat_notify_baton_t nb;
+
+ wrb.reporter = reporter;
+ wrb.baton = report_baton;
+
+ nb.func = notify_func;
+ nb.baton = notify_baton;
+
+ return svn_wc_crawl_revisions2(path, adm_access, &wrap_2to1_reporter, &wrb,
+ restore_files, recurse, use_commit_times,
+ compat_call_notify_func, &nb,
+ traversal_info,
+ pool);
+}
+
+svn_error_t *
+svn_wc_transmit_text_deltas2(const char **tempfile,
+ unsigned char digest[],
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t fulltext,
+ const svn_delta_editor_t *editor,
+ void *file_baton,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc_context_t *wc_ctx;
+ const svn_checksum_t *new_text_base_md5_checksum;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ SVN_ERR(svn_wc__internal_transmit_text_deltas(tempfile,
+ (digest
+ ? &new_text_base_md5_checksum
+ : NULL),
+ NULL, wc_ctx->db,
+ local_abspath, fulltext,
+ editor, file_baton,
+ pool, pool));
+
+ if (digest)
+ memcpy(digest, new_text_base_md5_checksum->digest, APR_MD5_DIGESTSIZE);
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_transmit_text_deltas(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t fulltext,
+ const svn_delta_editor_t *editor,
+ void *file_baton,
+ const char **tempfile,
+ apr_pool_t *pool)
+{
+ return svn_wc_transmit_text_deltas2(tempfile, NULL, path, adm_access,
+ fulltext, editor, file_baton, pool);
+}
+
+svn_error_t *
+svn_wc_transmit_prop_deltas(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_t *entry,
+ const svn_delta_editor_t *editor,
+ void *baton,
+ const char **tempfile,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc_context_t *wc_ctx;
+
+ if (tempfile)
+ *tempfile = NULL;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ SVN_ERR(svn_wc_transmit_prop_deltas2(wc_ctx, local_abspath, editor, baton,
+ pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+/*** From adm_files.c ***/
+svn_error_t *
+svn_wc_ensure_adm3(const char *path,
+ const char *uuid,
+ const char *url,
+ const char *repos,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc_context_t *wc_ctx;
+
+ if (uuid == NULL)
+ return svn_error_create(SVN_ERR_BAD_UUID, NULL, NULL);
+ if (repos == NULL)
+ return svn_error_create(SVN_ERR_BAD_URL, NULL, NULL);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc_context_create(&wc_ctx, NULL /* config */, pool, pool));
+
+ SVN_ERR(svn_wc_ensure_adm4(wc_ctx, local_abspath, url, repos, uuid, revision,
+ depth, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_ensure_adm2(const char *path,
+ const char *uuid,
+ const char *url,
+ const char *repos,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ return svn_wc_ensure_adm3(path, uuid, url, repos, revision,
+ svn_depth_infinity, pool);
+}
+
+
+svn_error_t *
+svn_wc_ensure_adm(const char *path,
+ const char *uuid,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ return svn_wc_ensure_adm2(path, uuid, url, NULL, revision, pool);
+}
+
+svn_error_t *
+svn_wc_create_tmp_file(apr_file_t **fp,
+ const char *path,
+ svn_boolean_t delete_on_close,
+ apr_pool_t *pool)
+{
+ return svn_wc_create_tmp_file2(fp, NULL, path,
+ delete_on_close
+ ? svn_io_file_del_on_close
+ : svn_io_file_del_none,
+ pool);
+}
+
+svn_error_t *
+svn_wc_create_tmp_file2(apr_file_t **fp,
+ const char **new_name,
+ const char *path,
+ svn_io_file_del_t delete_when,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+ const char *temp_dir;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(fp || new_name);
+
+ SVN_ERR(svn_wc_context_create(&wc_ctx, NULL /* config */, pool, pool));
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ err = svn_wc__get_tmpdir(&temp_dir, wc_ctx, local_abspath, pool, pool);
+ err = svn_error_compose_create(err, svn_wc_context_destroy(wc_ctx));
+ if (err)
+ return svn_error_trace(err);
+
+ SVN_ERR(svn_io_open_unique_file3(fp, new_name, temp_dir,
+ delete_when, pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** From adm_ops.c ***/
+svn_error_t *
+svn_wc_get_pristine_contents(svn_stream_t **contents,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+
+ SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, scratch_pool, scratch_pool));
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+
+ SVN_ERR(svn_wc_get_pristine_contents2(contents,
+ wc_ctx,
+ local_abspath,
+ result_pool,
+ scratch_pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+
+svn_error_t *
+svn_wc_queue_committed2(svn_wc_committed_queue_t *queue,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ svn_boolean_t remove_changelist,
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+ const svn_checksum_t *sha1_checksum = NULL;
+
+ SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, scratch_pool, scratch_pool));
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+
+ if (md5_checksum != NULL)
+ {
+ svn_error_t *err;
+ err = svn_wc__db_pristine_get_sha1(&sha1_checksum, wc_ctx->db,
+ local_abspath, md5_checksum,
+ svn_wc__get_committed_queue_pool(queue),
+ scratch_pool);
+
+ /* Don't fail on SHA1 not found */
+ if (err && err->apr_err == SVN_ERR_WC_DB_ERROR)
+ {
+ svn_error_clear(err);
+ sha1_checksum = NULL;
+ }
+ else
+ SVN_ERR(err);
+ }
+
+ SVN_ERR(svn_wc_queue_committed3(queue, wc_ctx, local_abspath, recurse,
+ wcprop_changes,
+ remove_lock, remove_changelist,
+ sha1_checksum, scratch_pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_queue_committed(svn_wc_committed_queue_t **queue,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ svn_boolean_t remove_changelist,
+ const unsigned char *digest,
+ apr_pool_t *pool)
+{
+ const svn_checksum_t *md5_checksum;
+
+ if (digest)
+ md5_checksum = svn_checksum__from_digest_md5(
+ digest, svn_wc__get_committed_queue_pool(*queue));
+ else
+ md5_checksum = NULL;
+
+ return svn_wc_queue_committed2(*queue, path, adm_access, recurse,
+ wcprop_changes, remove_lock,
+ remove_changelist, md5_checksum, pool);
+}
+
+svn_error_t *
+svn_wc_process_committed_queue(svn_wc_committed_queue_t *queue,
+ svn_wc_adm_access_t *adm_access,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+ SVN_ERR(svn_wc_process_committed_queue2(queue, wc_ctx, new_revnum,
+ rev_date, rev_author,
+ NULL, NULL, pool));
+ SVN_ERR(svn_wc_context_destroy(wc_ctx));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_process_committed4(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ svn_boolean_t remove_changelist,
+ const unsigned char *digest,
+ apr_pool_t *pool)
+{
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ const char *local_abspath;
+ const svn_checksum_t *md5_checksum;
+ const svn_checksum_t *sha1_checksum = NULL;
+ apr_time_t new_date;
+ apr_hash_t *wcprop_changes_hash;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ if (rev_date)
+ SVN_ERR(svn_time_from_cstring(&new_date, rev_date, pool));
+ else
+ new_date = 0;
+
+ if (digest)
+ md5_checksum = svn_checksum__from_digest_md5(digest, pool);
+ else
+ md5_checksum = NULL;
+
+ if (md5_checksum != NULL)
+ {
+ svn_error_t *err;
+ err = svn_wc__db_pristine_get_sha1(&sha1_checksum, db,
+ local_abspath, md5_checksum,
+ pool, pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_DB_ERROR)
+ {
+ svn_error_clear(err);
+ sha1_checksum = NULL;
+ }
+ else
+ SVN_ERR(err);
+ }
+
+ wcprop_changes_hash = svn_wc__prop_array_to_hash(wcprop_changes, pool);
+ SVN_ERR(svn_wc__process_committed_internal(db, local_abspath, recurse, TRUE,
+ new_revnum, new_date, rev_author,
+ wcprop_changes_hash,
+ !remove_lock, !remove_changelist,
+ sha1_checksum, NULL, pool));
+
+ /* Run the log file(s) we just created. */
+ return svn_error_trace(svn_wc__wq_run(db, local_abspath, NULL, NULL, pool));
+}
+
+
+svn_error_t *
+svn_wc_process_committed3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ const unsigned char *digest,
+ apr_pool_t *pool)
+{
+ return svn_wc_process_committed4(path, adm_access, recurse, new_revnum,
+ rev_date, rev_author, wcprop_changes,
+ remove_lock, FALSE, digest, pool);
+}
+
+svn_error_t *
+svn_wc_process_committed2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ const apr_array_header_t *wcprop_changes,
+ svn_boolean_t remove_lock,
+ apr_pool_t *pool)
+{
+ return svn_wc_process_committed3(path, adm_access, recurse, new_revnum,
+ rev_date, rev_author, wcprop_changes,
+ remove_lock, NULL, pool);
+}
+
+svn_error_t *
+svn_wc_process_committed(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t recurse,
+ svn_revnum_t new_revnum,
+ const char *rev_date,
+ const char *rev_author,
+ const apr_array_header_t *wcprop_changes,
+ apr_pool_t *pool)
+{
+ return svn_wc_process_committed2(path, adm_access, recurse, new_revnum,
+ rev_date, rev_author, wcprop_changes,
+ FALSE, pool);
+}
+
+svn_error_t *
+svn_wc_maybe_set_repos_root(svn_wc_adm_access_t *adm_access,
+ const char *path,
+ const char *repos,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_delete3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_boolean_t keep_local,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *wc_db = svn_wc__adm_get_db(adm_access);
+ svn_wc_adm_access_t *dir_access;
+ const char *local_abspath;
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool));
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ /* Open access batons for everything below path, because we used to open
+ these before. */
+ SVN_ERR(svn_wc_adm_probe_try3(&dir_access, adm_access, path,
+ TRUE, -1, cancel_func, cancel_baton, pool));
+
+ SVN_ERR(svn_wc_delete4(wc_ctx,
+ local_abspath,
+ keep_local,
+ TRUE,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_delete2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ return svn_wc_delete3(path, adm_access, cancel_func, cancel_baton,
+ notify_func, notify_baton, FALSE, pool);
+}
+
+svn_error_t *
+svn_wc_delete(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ struct compat_notify_baton_t nb;
+
+ nb.func = notify_func;
+ nb.baton = notify_baton;
+
+ return svn_wc_delete2(path, adm_access, cancel_func, cancel_baton,
+ compat_call_notify_func, &nb, pool);
+}
+
+svn_error_t *
+svn_wc_add_from_disk(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc_add_from_disk2(wc_ctx, local_abspath, NULL,
+ notify_func, notify_baton, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_add3(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ svn_depth_t depth,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *wc_db = svn_wc__adm_get_db(parent_access);
+ const char *local_abspath;
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool));
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ SVN_ERR(svn_wc_add4(wc_ctx, local_abspath,
+ depth, copyfrom_url,
+ copyfrom_rev,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton, pool));
+
+ /* Make sure the caller gets the new access baton in the set. */
+ if (svn_wc__adm_retrieve_internal2(wc_db, local_abspath, pool) == NULL)
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_wc__db_read_kind(&kind, wc_db, local_abspath,
+ FALSE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */, pool));
+ if (kind == svn_node_dir)
+ {
+ svn_wc_adm_access_t *adm_access;
+
+ /* Open the access baton in adm_access' pool to give it the same
+ lifetime */
+ SVN_ERR(svn_wc_adm_open3(&adm_access, parent_access, path, TRUE,
+ copyfrom_url ? -1 : 0,
+ cancel_func, cancel_baton,
+ svn_wc_adm_access_pool(parent_access)));
+ }
+ }
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+
+svn_error_t *
+svn_wc_add2(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ return svn_wc_add3(path, parent_access, svn_depth_infinity,
+ copyfrom_url, copyfrom_rev,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton, pool);
+}
+
+svn_error_t *
+svn_wc_add(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ struct compat_notify_baton_t nb;
+
+ nb.func = notify_func;
+ nb.baton = notify_baton;
+
+ return svn_wc_add2(path, parent_access, copyfrom_url, copyfrom_rev,
+ cancel_func, cancel_baton,
+ compat_call_notify_func, &nb, pool);
+}
+
+svn_error_t *
+svn_wc_revert3(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *wc_db = svn_wc__adm_get_db(parent_access);
+ const char *local_abspath;
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool));
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ SVN_ERR(svn_wc_revert4(wc_ctx,
+ local_abspath,
+ depth,
+ use_commit_times,
+ changelist_filter,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_revert2(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ svn_boolean_t recursive,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ return svn_wc_revert3(path, parent_access,
+ SVN_DEPTH_INFINITY_OR_EMPTY(recursive),
+ use_commit_times, NULL, cancel_func, cancel_baton,
+ notify_func, notify_baton, pool);
+}
+
+svn_error_t *
+svn_wc_revert(const char *path,
+ svn_wc_adm_access_t *parent_access,
+ svn_boolean_t recursive,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ struct compat_notify_baton_t nb;
+
+ nb.func = notify_func;
+ nb.baton = notify_baton;
+
+ return svn_wc_revert2(path, parent_access, recursive, use_commit_times,
+ cancel_func, cancel_baton,
+ compat_call_notify_func, &nb, pool);
+}
+
+svn_error_t *
+svn_wc_remove_from_revision_control(svn_wc_adm_access_t *adm_access,
+ const char *name,
+ svn_boolean_t destroy_wf,
+ svn_boolean_t instant_error,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *wc_db = svn_wc__adm_get_db(adm_access);
+ const char *local_abspath = svn_dirent_join(
+ svn_wc__adm_access_abspath(adm_access),
+ name,
+ pool);
+
+ /* name must be an entry in adm_access, fail if not */
+ SVN_ERR_ASSERT(strcmp(svn_dirent_basename(name, NULL), name) == 0);
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool));
+
+ SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx,
+ local_abspath,
+ destroy_wf,
+ instant_error,
+ cancel_func, cancel_baton,
+ pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_resolved_conflict4(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t resolve_text,
+ svn_boolean_t resolve_props,
+ svn_boolean_t resolve_tree,
+ svn_depth_t depth,
+ svn_wc_conflict_choice_t conflict_choice,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *wc_db = svn_wc__adm_get_db(adm_access);
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool));
+
+ SVN_ERR(svn_wc_resolved_conflict5(wc_ctx,
+ local_abspath,
+ depth,
+ resolve_text,
+ resolve_props ? "" : NULL,
+ resolve_tree,
+ conflict_choice,
+ cancel_func,
+ cancel_baton,
+ notify_func,
+ notify_baton,
+ pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+
+}
+
+svn_error_t *
+svn_wc_resolved_conflict(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t resolve_text,
+ svn_boolean_t resolve_props,
+ svn_boolean_t recurse,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ struct compat_notify_baton_t nb;
+
+ nb.func = notify_func;
+ nb.baton = notify_baton;
+
+ return svn_wc_resolved_conflict2(path, adm_access,
+ resolve_text, resolve_props, recurse,
+ compat_call_notify_func, &nb,
+ NULL, NULL, pool);
+
+}
+
+svn_error_t *
+svn_wc_resolved_conflict2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t resolve_text,
+ svn_boolean_t resolve_props,
+ svn_boolean_t recurse,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_wc_resolved_conflict3(path, adm_access, resolve_text,
+ resolve_props,
+ SVN_DEPTH_INFINITY_OR_EMPTY(recurse),
+ svn_wc_conflict_choose_merged,
+ notify_func, notify_baton, cancel_func,
+ cancel_baton, pool);
+}
+
+svn_error_t *
+svn_wc_resolved_conflict3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t resolve_text,
+ svn_boolean_t resolve_props,
+ svn_depth_t depth,
+ svn_wc_conflict_choice_t conflict_choice,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_wc_resolved_conflict4(path, adm_access, resolve_text,
+ resolve_props, FALSE, depth,
+ svn_wc_conflict_choose_merged,
+ notify_func, notify_baton, cancel_func,
+ cancel_baton, pool);
+}
+
+svn_error_t *
+svn_wc_add_lock(const char *path,
+ const svn_lock_t *lock,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc_context_t *wc_ctx;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ SVN_ERR(svn_wc_add_lock2(wc_ctx, local_abspath, lock, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_remove_lock(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc_context_t *wc_ctx;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ SVN_ERR(svn_wc_remove_lock2(wc_ctx, local_abspath, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+
+}
+
+svn_error_t *
+svn_wc_get_ancestry(char **url,
+ svn_revnum_t *rev,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ const svn_wc_entry_t *entry;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ SVN_ERR(svn_wc__get_entry(&entry, svn_wc__adm_get_db(adm_access),
+ local_abspath, FALSE,
+ svn_node_unknown,
+ pool, pool));
+
+ if (url)
+ *url = apr_pstrdup(pool, entry->url);
+
+ if (rev)
+ *rev = entry->revision;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_set_changelist(const char *path,
+ const char *changelist,
+ svn_wc_adm_access_t *adm_access,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc_context_t *wc_ctx;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ SVN_ERR(svn_wc_set_changelist2(wc_ctx, local_abspath, changelist,
+ svn_depth_empty, NULL,
+ cancel_func, cancel_baton, notify_func,
+ notify_baton, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+
+/*** From diff.c ***/
+/* Used to wrap svn_wc_diff_callbacks_t. */
+struct diff_callbacks_wrapper_baton {
+ const svn_wc_diff_callbacks_t *callbacks;
+ void *baton;
+};
+
+/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */
+static svn_error_t *
+wrap_3to1_file_changed(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton)
+{
+ struct diff_callbacks_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ if (tmpfile2 != NULL)
+ SVN_ERR(b->callbacks->file_changed(adm_access, contentstate, path,
+ tmpfile1, tmpfile2,
+ rev1, rev2, mimetype1, mimetype2,
+ b->baton));
+ if (propchanges->nelts > 0)
+ SVN_ERR(b->callbacks->props_changed(adm_access, propstate, path,
+ propchanges, originalprops,
+ b->baton));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */
+static svn_error_t *
+wrap_3to1_file_added(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton)
+{
+ struct diff_callbacks_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ SVN_ERR(b->callbacks->file_added(adm_access, contentstate, path,
+ tmpfile1, tmpfile2, rev1, rev2,
+ mimetype1, mimetype2, b->baton));
+ if (propchanges->nelts > 0)
+ SVN_ERR(b->callbacks->props_changed(adm_access, propstate, path,
+ propchanges, originalprops,
+ b->baton));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */
+static svn_error_t *
+wrap_3to1_file_deleted(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ const char *mimetype1,
+ const char *mimetype2,
+ apr_hash_t *originalprops,
+ void *diff_baton)
+{
+ struct diff_callbacks_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ SVN_ERR_ASSERT(originalprops);
+
+ return b->callbacks->file_deleted(adm_access, state, path,
+ tmpfile1, tmpfile2, mimetype1, mimetype2,
+ b->baton);
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */
+static svn_error_t *
+wrap_3to1_dir_added(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton)
+{
+ struct diff_callbacks_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ return b->callbacks->dir_added(adm_access, state, path, rev, b->baton);
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */
+static svn_error_t *
+wrap_3to1_dir_deleted(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ void *diff_baton)
+{
+ struct diff_callbacks_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ return b->callbacks->dir_deleted(adm_access, state, path, b->baton);
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */
+static svn_error_t *
+wrap_3to1_dir_props_changed(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton)
+{
+ struct diff_callbacks_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ return b->callbacks->props_changed(adm_access, state, path, propchanges,
+ originalprops, b->baton);
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t
+ and svn_wc_diff_callbacks2_t. */
+static svn_error_t *
+wrap_3to1or2_dir_opened(svn_wc_adm_access_t *adm_access,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton)
+{
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+ /* Do nothing. */
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t
+ and svn_wc_diff_callbacks2_t. */
+static svn_error_t *
+wrap_3to1or2_dir_closed(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *propstate,
+ svn_wc_notify_state_t *contentstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ void *diff_baton)
+{
+ if (contentstate)
+ *contentstate = svn_wc_notify_state_unknown;
+ if (propstate)
+ *propstate = svn_wc_notify_state_unknown;
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+ /* Do nothing. */
+ return SVN_NO_ERROR;
+}
+
+/* Used to wrap svn_diff_callbacks_t as an svn_wc_diff_callbacks3_t. */
+static struct svn_wc_diff_callbacks3_t diff_callbacks_wrapper = {
+ wrap_3to1_file_changed,
+ wrap_3to1_file_added,
+ wrap_3to1_file_deleted,
+ wrap_3to1_dir_added,
+ wrap_3to1_dir_deleted,
+ wrap_3to1_dir_props_changed,
+ wrap_3to1or2_dir_opened,
+ wrap_3to1or2_dir_closed
+};
+
+
+
+/* Used to wrap svn_wc_diff_callbacks2_t. */
+struct diff_callbacks2_wrapper_baton {
+ const svn_wc_diff_callbacks2_t *callbacks2;
+ void *baton;
+};
+
+/* An svn_wc_diff_callbacks3_t function for wrapping
+ * svn_wc_diff_callbacks2_t. */
+static svn_error_t *
+wrap_3to2_file_changed(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton)
+{
+ struct diff_callbacks2_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ return b->callbacks2->file_changed(adm_access, contentstate, propstate,
+ path, tmpfile1, tmpfile2,
+ rev1, rev2, mimetype1, mimetype2,
+ propchanges, originalprops, b->baton);
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping
+ * svn_wc_diff_callbacks2_t. */
+static svn_error_t *
+wrap_3to2_file_added(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton)
+{
+ struct diff_callbacks2_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ return b->callbacks2->file_added(adm_access, contentstate, propstate, path,
+ tmpfile1, tmpfile2, rev1, rev2,
+ mimetype1, mimetype2, propchanges,
+ originalprops, b->baton);
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping
+ * svn_wc_diff_callbacks2_t. */
+static svn_error_t *
+wrap_3to2_file_deleted(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ const char *mimetype1,
+ const char *mimetype2,
+ apr_hash_t *originalprops,
+ void *diff_baton)
+{
+ struct diff_callbacks2_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ return b->callbacks2->file_deleted(adm_access, state, path,
+ tmpfile1, tmpfile2, mimetype1, mimetype2,
+ originalprops, b->baton);
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping
+ * svn_wc_diff_callbacks2_t. */
+static svn_error_t *
+wrap_3to2_dir_added(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton)
+{
+ struct diff_callbacks2_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ return b->callbacks2->dir_added(adm_access, state, path, rev, b->baton);
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping
+ * svn_wc_diff_callbacks2_t. */
+static svn_error_t *
+wrap_3to2_dir_deleted(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ void *diff_baton)
+{
+ struct diff_callbacks2_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ return b->callbacks2->dir_deleted(adm_access, state, path, b->baton);
+}
+
+/* An svn_wc_diff_callbacks3_t function for wrapping
+ * svn_wc_diff_callbacks2_t. */
+static svn_error_t *
+wrap_3to2_dir_props_changed(svn_wc_adm_access_t *adm_access,
+ svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton)
+{
+ struct diff_callbacks2_wrapper_baton *b = diff_baton;
+
+ if (tree_conflicted)
+ *tree_conflicted = FALSE;
+
+ return b->callbacks2->dir_props_changed(adm_access, state, path, propchanges,
+ originalprops, b->baton);
+}
+
+/* Used to wrap svn_diff_callbacks2_t as an svn_wc_diff_callbacks3_t. */
+static struct svn_wc_diff_callbacks3_t diff_callbacks2_wrapper = {
+ wrap_3to2_file_changed,
+ wrap_3to2_file_added,
+ wrap_3to2_file_deleted,
+ wrap_3to2_dir_added,
+ wrap_3to2_dir_deleted,
+ wrap_3to2_dir_props_changed,
+ wrap_3to1or2_dir_opened,
+ wrap_3to1or2_dir_closed
+};
+
+
+
+/* Used to wrap svn_wc_diff_callbacks3_t. */
+struct diff_callbacks3_wrapper_baton {
+ const svn_wc_diff_callbacks3_t *callbacks3;
+ svn_wc__db_t *db;
+ void *baton;
+ const char *anchor;
+ const char *anchor_abspath;
+};
+
+static svn_error_t *
+wrap_4to3_file_opened(svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function for wrapping
+ * svn_wc_diff_callbacks3_t. */
+static svn_error_t *
+wrap_4to3_file_changed(svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_callbacks3_wrapper_baton *b = diff_baton;
+ svn_wc_adm_access_t *adm_access;
+ const char *dir = svn_relpath_dirname(path, scratch_pool);
+
+ adm_access = svn_wc__adm_retrieve_internal2(
+ b->db,
+ svn_dirent_join(b->anchor_abspath, dir, scratch_pool),
+ scratch_pool);
+
+ return b->callbacks3->file_changed(adm_access, contentstate, propstate,
+ tree_conflicted,
+ svn_dirent_join(b->anchor, path,
+ scratch_pool),
+ tmpfile1, tmpfile2,
+ rev1, rev2, mimetype1, mimetype2,
+ propchanges, originalprops, b->baton);
+}
+
+/* An svn_wc_diff_callbacks4_t function for wrapping
+ * svn_wc_diff_callbacks3_t. */
+static svn_error_t *
+wrap_4to3_file_added(svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *originalprops,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_callbacks3_wrapper_baton *b = diff_baton;
+ svn_wc_adm_access_t *adm_access;
+ const char *dir = svn_relpath_dirname(path, scratch_pool);
+
+ adm_access = svn_wc__adm_retrieve_internal2(
+ b->db,
+ svn_dirent_join(b->anchor_abspath, dir, scratch_pool),
+ scratch_pool);
+
+ return b->callbacks3->file_added(adm_access, contentstate, propstate,
+ tree_conflicted,
+ svn_dirent_join(b->anchor, path,
+ scratch_pool),
+ tmpfile1, tmpfile2,
+ rev1, rev2, mimetype1, mimetype2,
+ propchanges, originalprops, b->baton);
+}
+
+/* An svn_wc_diff_callbacks4_t function for wrapping
+ * svn_wc_diff_callbacks3_t. */
+static svn_error_t *
+wrap_4to3_file_deleted(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ const char *mimetype1,
+ const char *mimetype2,
+ apr_hash_t *originalprops,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_callbacks3_wrapper_baton *b = diff_baton;
+ svn_wc_adm_access_t *adm_access;
+ const char *dir = svn_relpath_dirname(path, scratch_pool);
+
+ adm_access = svn_wc__adm_retrieve_internal2(
+ b->db,
+ svn_dirent_join(b->anchor_abspath, dir, scratch_pool),
+ scratch_pool);
+
+ return b->callbacks3->file_deleted(adm_access, state, tree_conflicted,
+ svn_dirent_join(b->anchor, path,
+ scratch_pool),
+ tmpfile1, tmpfile2,
+ mimetype1, mimetype2, originalprops,
+ b->baton);
+}
+
+/* An svn_wc_diff_callbacks4_t function for wrapping
+ * svn_wc_diff_callbacks3_t. */
+static svn_error_t *
+wrap_4to3_dir_added(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *path,
+ svn_revnum_t rev,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_callbacks3_wrapper_baton *b = diff_baton;
+ svn_wc_adm_access_t *adm_access;
+
+ adm_access = svn_wc__adm_retrieve_internal2(
+ b->db,
+ svn_dirent_join(b->anchor_abspath, path, scratch_pool),
+ scratch_pool);
+
+ return b->callbacks3->dir_added(adm_access, state, tree_conflicted,
+ svn_dirent_join(b->anchor, path,
+ scratch_pool),
+ rev, b->baton);
+}
+
+/* An svn_wc_diff_callbacks4_t function for wrapping
+ * svn_wc_diff_callbacks3_t. */
+static svn_error_t *
+wrap_4to3_dir_deleted(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_callbacks3_wrapper_baton *b = diff_baton;
+ svn_wc_adm_access_t *adm_access;
+
+ adm_access = svn_wc__adm_retrieve_internal2(
+ b->db,
+ svn_dirent_join(b->anchor_abspath, path, scratch_pool),
+ scratch_pool);
+
+ return b->callbacks3->dir_deleted(adm_access, state, tree_conflicted,
+ svn_dirent_join(b->anchor, path,
+ scratch_pool),
+ b->baton);
+}
+
+/* An svn_wc_diff_callbacks4_t function for wrapping
+ * svn_wc_diff_callbacks3_t. */
+static svn_error_t *
+wrap_4to3_dir_props_changed(svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ svn_boolean_t dir_was_added,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_callbacks3_wrapper_baton *b = diff_baton;
+ svn_wc_adm_access_t *adm_access;
+
+ adm_access = svn_wc__adm_retrieve_internal2(
+ b->db,
+ svn_dirent_join(b->anchor_abspath, path, scratch_pool),
+ scratch_pool);
+
+ return b->callbacks3->dir_props_changed(adm_access, propstate,
+ tree_conflicted,
+ svn_dirent_join(b->anchor, path,
+ scratch_pool),
+ propchanges, original_props,
+ b->baton);
+}
+
+/* An svn_wc_diff_callbacks4_t function for wrapping
+ * svn_wc_diff_callbacks3_t. */
+static svn_error_t *
+wrap_4to3_dir_opened(svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *path,
+ svn_revnum_t rev,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_callbacks3_wrapper_baton *b = diff_baton;
+ svn_wc_adm_access_t *adm_access;
+
+ adm_access = svn_wc__adm_retrieve_internal2(
+ b->db,
+ svn_dirent_join(b->anchor_abspath, path, scratch_pool),
+ scratch_pool);
+ if (skip_children)
+ *skip_children = FALSE;
+
+ return b->callbacks3->dir_opened(adm_access, tree_conflicted,
+ svn_dirent_join(b->anchor, path,
+ scratch_pool),
+ rev, b->baton);
+}
+
+/* An svn_wc_diff_callbacks4_t function for wrapping
+ * svn_wc_diff_callbacks3_t. */
+static svn_error_t *
+wrap_4to3_dir_closed(svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *path,
+ svn_boolean_t dir_was_added,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_callbacks3_wrapper_baton *b = diff_baton;
+ svn_wc_adm_access_t *adm_access;
+
+ adm_access = svn_wc__adm_retrieve_internal2(
+ b->db,
+ svn_dirent_join(b->anchor_abspath, path, scratch_pool),
+ scratch_pool);
+
+ return b->callbacks3->dir_closed(adm_access, contentstate, propstate,
+ tree_conflicted,
+ svn_dirent_join(b->anchor, path,
+ scratch_pool),
+ b->baton);
+}
+
+
+/* Used to wrap svn_diff_callbacks3_t as an svn_wc_diff_callbacks4_t. */
+static struct svn_wc_diff_callbacks4_t diff_callbacks3_wrapper = {
+ wrap_4to3_file_opened,
+ wrap_4to3_file_changed,
+ wrap_4to3_file_added,
+ wrap_4to3_file_deleted,
+ wrap_4to3_dir_deleted,
+ wrap_4to3_dir_opened,
+ wrap_4to3_dir_added,
+ wrap_4to3_dir_props_changed,
+ wrap_4to3_dir_closed
+};
+
+
+svn_error_t *
+svn_wc_get_diff_editor6(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_boolean_t server_performs_filtering,
+ const apr_array_header_t *changelist_filter,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__get_diff_editor(editor, edit_baton,
+ wc_ctx,
+ anchor_abspath, target,
+ depth,
+ ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, use_text_base,
+ reverse_order, server_performs_filtering,
+ changelist_filter,
+ callbacks, callback_baton,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc_get_diff_editor5(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks3_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const apr_array_header_t *changelist_filter,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool)
+{
+ struct diff_callbacks3_wrapper_baton *b = apr_palloc(pool, sizeof(*b));
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *db = svn_wc__adm_get_db(anchor);
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool));
+
+ b->callbacks3 = callbacks;
+ b->baton = callback_baton;
+ b->db = db;
+ b->anchor = svn_wc_adm_access_path(anchor);
+ b->anchor_abspath = svn_wc__adm_access_abspath(anchor);
+
+ SVN_ERR(svn_wc_get_diff_editor6(editor,
+ edit_baton,
+ wc_ctx,
+ b->anchor_abspath,
+ target,
+ depth,
+ ignore_ancestry,
+ FALSE,
+ FALSE,
+ use_text_base,
+ reverse_order,
+ FALSE,
+ changelist_filter,
+ &diff_callbacks3_wrapper,
+ b,
+ cancel_func,
+ cancel_baton,
+ pool,
+ pool));
+
+ /* Can't destroy wc_ctx. It is used by the diff editor */
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_get_diff_editor4(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks2_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const apr_array_header_t *changelist_filter,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool)
+{
+ struct diff_callbacks2_wrapper_baton *b = apr_palloc(pool, sizeof(*b));
+ b->callbacks2 = callbacks;
+ b->baton = callback_baton;
+ return svn_wc_get_diff_editor5(anchor,
+ target,
+ &diff_callbacks2_wrapper,
+ b,
+ depth,
+ ignore_ancestry,
+ use_text_base,
+ reverse_order,
+ cancel_func,
+ cancel_baton,
+ changelist_filter,
+ editor,
+ edit_baton,
+ pool);
+}
+
+svn_error_t *
+svn_wc_get_diff_editor3(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks2_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool)
+{
+ return svn_wc_get_diff_editor4(anchor,
+ target,
+ callbacks,
+ callback_baton,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_ancestry,
+ use_text_base,
+ reverse_order,
+ cancel_func,
+ cancel_baton,
+ NULL,
+ editor,
+ edit_baton,
+ pool);
+}
+
+svn_error_t *
+svn_wc_get_diff_editor2(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool)
+{
+ struct diff_callbacks_wrapper_baton *b = apr_palloc(pool, sizeof(*b));
+ b->callbacks = callbacks;
+ b->baton = callback_baton;
+ return svn_wc_get_diff_editor5(anchor, target, &diff_callbacks_wrapper, b,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse),
+ ignore_ancestry, use_text_base,
+ reverse_order, cancel_func, cancel_baton,
+ NULL, editor, edit_baton, pool);
+}
+
+svn_error_t *
+svn_wc_get_diff_editor(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool)
+{
+ return svn_wc_get_diff_editor2(anchor, target, callbacks, callback_baton,
+ recurse, FALSE, use_text_base, reverse_order,
+ cancel_func, cancel_baton,
+ editor, edit_baton, pool);
+}
+
+svn_error_t *
+svn_wc_diff5(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks3_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelist_filter,
+ apr_pool_t *pool)
+{
+ struct diff_callbacks3_wrapper_baton *b = apr_palloc(pool, sizeof(*b));
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *db = svn_wc__adm_get_db(anchor);
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool));
+
+ b->callbacks3 = callbacks;
+ b->baton = callback_baton;
+ b->anchor = svn_wc_adm_access_path(anchor);
+ b->anchor_abspath = svn_wc__adm_access_abspath(anchor);
+
+ SVN_ERR(svn_wc_diff6(wc_ctx,
+ svn_dirent_join(b->anchor_abspath, target, pool),
+ &diff_callbacks3_wrapper,
+ b,
+ depth,
+ ignore_ancestry,
+ FALSE,
+ FALSE,
+ changelist_filter,
+ NULL, NULL,
+ pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_diff4(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks2_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelist_filter,
+ apr_pool_t *pool)
+{
+ struct diff_callbacks2_wrapper_baton *b = apr_palloc(pool, sizeof(*b));
+ b->callbacks2 = callbacks;
+ b->baton = callback_baton;
+
+ return svn_wc_diff5(anchor, target, &diff_callbacks2_wrapper, b,
+ depth, ignore_ancestry, changelist_filter, pool);
+}
+
+svn_error_t *
+svn_wc_diff3(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks2_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool)
+{
+ return svn_wc_diff4(anchor, target, callbacks, callback_baton,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse), ignore_ancestry,
+ NULL, pool);
+}
+
+svn_error_t *
+svn_wc_diff2(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool)
+{
+ struct diff_callbacks_wrapper_baton *b = apr_pcalloc(pool, sizeof(*b));
+ b->callbacks = callbacks;
+ b->baton = callback_baton;
+ return svn_wc_diff5(anchor, target, &diff_callbacks_wrapper, b,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse), ignore_ancestry,
+ NULL, pool);
+}
+
+svn_error_t *
+svn_wc_diff(svn_wc_adm_access_t *anchor,
+ const char *target,
+ const svn_wc_diff_callbacks_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t recurse,
+ apr_pool_t *pool)
+{
+ return svn_wc_diff2(anchor, target, callbacks, callback_baton,
+ recurse, FALSE, pool);
+}
+
+/*** From entries.c ***/
+svn_error_t *
+svn_wc_walk_entries2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_callbacks_t *walk_callbacks,
+ void *walk_baton,
+ svn_boolean_t show_hidden,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_entry_callbacks2_t walk_cb2 = { 0 };
+ walk_cb2.found_entry = walk_callbacks->found_entry;
+ walk_cb2.handle_error = svn_wc__walker_default_error_handler;
+ return svn_wc_walk_entries3(path, adm_access,
+ &walk_cb2, walk_baton, svn_depth_infinity,
+ show_hidden, cancel_func, cancel_baton, pool);
+}
+
+svn_error_t *
+svn_wc_walk_entries(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_callbacks_t *walk_callbacks,
+ void *walk_baton,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool)
+{
+ return svn_wc_walk_entries2(path, adm_access, walk_callbacks,
+ walk_baton, show_hidden, NULL, NULL,
+ pool);
+}
+
+svn_error_t *
+svn_wc_mark_missing_deleted(const char *path,
+ svn_wc_adm_access_t *parent,
+ apr_pool_t *pool)
+{
+ /* With a single DB a node will never be missing */
+ return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
+ _("Unexpectedly found '%s': "
+ "path is marked 'missing'"),
+ svn_dirent_local_style(path, pool));
+}
+
+
+/*** From props.c ***/
+svn_error_t *
+svn_wc_parse_externals_description2(apr_array_header_t **externals_p,
+ const char *parent_directory,
+ const char *desc,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *list;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_wc_parse_externals_description3(externals_p ? &list : NULL,
+ parent_directory, desc,
+ TRUE, subpool));
+
+ if (externals_p)
+ {
+ int i;
+
+ *externals_p = apr_array_make(pool, list->nelts,
+ sizeof(svn_wc_external_item_t *));
+ for (i = 0; i < list->nelts; i++)
+ {
+ svn_wc_external_item2_t *item2 = APR_ARRAY_IDX(list, i,
+ svn_wc_external_item2_t *);
+ svn_wc_external_item_t *item = apr_palloc(pool, sizeof (*item));
+
+ if (item2->target_dir)
+ item->target_dir = apr_pstrdup(pool, item2->target_dir);
+ if (item2->url)
+ item->url = apr_pstrdup(pool, item2->url);
+ item->revision = item2->revision;
+
+ APR_ARRAY_PUSH(*externals_p, svn_wc_external_item_t *) = item;
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_parse_externals_description(apr_hash_t **externals_p,
+ const char *parent_directory,
+ const char *desc,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *list;
+
+ SVN_ERR(svn_wc_parse_externals_description2(externals_p ? &list : NULL,
+ parent_directory, desc, pool));
+
+ /* Store all of the items into the hash if that was requested. */
+ if (externals_p)
+ {
+ int i;
+
+ *externals_p = apr_hash_make(pool);
+ for (i = 0; i < list->nelts; i++)
+ {
+ svn_wc_external_item_t *item;
+ item = APR_ARRAY_IDX(list, i, svn_wc_external_item_t *);
+
+ svn_hash_sets(*externals_p, item->target_dir, item);
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_prop_set3(const char *name,
+ const svn_string_t *value,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t skip_checks,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ err = svn_wc_prop_set4(wc_ctx, local_abspath,
+ name, value,
+ svn_depth_empty,
+ skip_checks, NULL /* changelist_filter */,
+ NULL, NULL /* cancellation */,
+ notify_func, notify_baton,
+ pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_INVALID_SCHEDULE)
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_prop_set2(const char *name,
+ const svn_string_t *value,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t skip_checks,
+ apr_pool_t *pool)
+{
+ return svn_wc_prop_set3(name, value, path, adm_access, skip_checks,
+ NULL, NULL, pool);
+}
+
+svn_error_t *
+svn_wc_prop_set(const char *name,
+ const svn_string_t *value,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ return svn_wc_prop_set2(name, value, path, adm_access, FALSE, pool);
+}
+
+svn_error_t *
+svn_wc_prop_list(apr_hash_t **props,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access), pool));
+
+ err = svn_wc_prop_list2(props, wc_ctx, local_abspath, pool, pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ *props = apr_hash_make(pool);
+ svn_error_clear(err);
+ err = NULL;
+ }
+
+ return svn_error_compose_create(err, svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_prop_get(const svn_string_t **value,
+ const char *name,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access), pool));
+
+ err = svn_wc_prop_get2(value, wc_ctx, local_abspath, name, pool, pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ *value = NULL;
+ svn_error_clear(err);
+ err = NULL;
+ }
+
+ return svn_error_compose_create(err, svn_wc_context_destroy(wc_ctx));
+}
+
+/* baton for conflict_func_1to2_wrapper */
+struct conflict_func_1to2_baton
+{
+ svn_wc_conflict_resolver_func_t inner_func;
+ void *inner_baton;
+};
+
+
+/* Implements svn_wc_conflict_resolver_func2_t */
+static svn_error_t *
+conflict_func_1to2_wrapper(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *conflict,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct conflict_func_1to2_baton *btn = baton;
+ svn_wc_conflict_description_t *cd = svn_wc__cd2_to_cd(conflict,
+ scratch_pool);
+
+ return svn_error_trace(btn->inner_func(result, cd, btn->inner_baton,
+ result_pool));
+}
+
+svn_error_t *
+svn_wc_merge_props2(svn_wc_notify_state_t *state,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_hash_t *baseprops,
+ const apr_array_header_t *propchanges,
+ svn_boolean_t base_merge,
+ svn_boolean_t dry_run,
+ svn_wc_conflict_resolver_func_t conflict_func,
+ void *conflict_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ svn_error_t *err;
+ svn_wc_context_t *wc_ctx;
+ struct conflict_func_1to2_baton conflict_wrapper;
+
+ if (base_merge && !dry_run)
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ U_("base_merge=TRUE is no longer supported; "
+ "see notes/api-errata/1.7/wc006.txt"));
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+
+ conflict_wrapper.inner_func = conflict_func;
+ conflict_wrapper.inner_baton = conflict_baton;
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL,
+ svn_wc__adm_get_db(adm_access),
+ scratch_pool));
+
+ err = svn_wc_merge_props3(state,
+ wc_ctx,
+ local_abspath,
+ NULL /* left_version */,
+ NULL /* right_version */,
+ baseprops,
+ propchanges,
+ dry_run,
+ conflict_func ? conflict_func_1to2_wrapper
+ : NULL,
+ &conflict_wrapper,
+ NULL, NULL,
+ scratch_pool);
+
+ if (err)
+ switch(err->apr_err)
+ {
+ case SVN_ERR_WC_PATH_NOT_FOUND:
+ case SVN_ERR_WC_PATH_UNEXPECTED_STATUS:
+ err->apr_err = SVN_ERR_UNVERSIONED_RESOURCE;
+ break;
+ }
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_wc_context_destroy(wc_ctx)));
+}
+
+svn_error_t *
+svn_wc_merge_props(svn_wc_notify_state_t *state,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_hash_t *baseprops,
+ const apr_array_header_t *propchanges,
+ svn_boolean_t base_merge,
+ svn_boolean_t dry_run,
+ apr_pool_t *pool)
+{
+ return svn_wc_merge_props2(state, path, adm_access, baseprops, propchanges,
+ base_merge, dry_run, NULL, NULL, pool);
+}
+
+
+svn_error_t *
+svn_wc_merge_prop_diffs(svn_wc_notify_state_t *state,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const apr_array_header_t *propchanges,
+ svn_boolean_t base_merge,
+ svn_boolean_t dry_run,
+ apr_pool_t *pool)
+{
+ /* NOTE: Here, we use implementation knowledge. The public
+ svn_wc_merge_props2 doesn't allow NULL as baseprops argument, but we know
+ that it works. */
+ return svn_wc_merge_props2(state, path, adm_access, NULL, propchanges,
+ base_merge, dry_run, NULL, NULL, pool);
+}
+
+svn_error_t *
+svn_wc_get_prop_diffs(apr_array_header_t **propchanges,
+ apr_hash_t **original_props,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access), pool));
+
+ SVN_ERR(svn_wc_get_prop_diffs2(propchanges, original_props, wc_ctx,
+ local_abspath, pool, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+
+svn_error_t *
+svn_wc_props_modified_p(svn_boolean_t *modified_p,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access), pool));
+
+ err = svn_wc_props_modified_p2(modified_p,
+ wc_ctx,
+ local_abspath,
+ pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ *modified_p = FALSE;
+ }
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+
+/*** From status.c ***/
+
+struct status4_wrapper_baton
+{
+ svn_wc_status_func3_t old_func;
+ void *old_baton;
+ const char *anchor_abspath;
+ const char *anchor_relpath;
+ svn_wc_context_t *wc_ctx;
+};
+
+/* */
+static svn_error_t *
+status4_wrapper_func(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct status4_wrapper_baton *swb = baton;
+ svn_wc_status2_t *dup;
+ const char *path = local_abspath;
+
+ SVN_ERR(svn_wc__status2_from_3(&dup, status, swb->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (swb->anchor_abspath != NULL)
+ {
+ path = svn_dirent_join(
+ swb->anchor_relpath,
+ svn_dirent_skip_ancestor(swb->anchor_abspath, local_abspath),
+ scratch_pool);
+ }
+
+ return (*swb->old_func)(swb->old_baton, path, dup, scratch_pool);
+}
+
+
+svn_error_t *
+svn_wc_get_status_editor5(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ void **set_locks_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t depth_as_sticky,
+ svn_boolean_t server_performs_filtering,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__get_status_editor(editor, edit_baton,
+ set_locks_baton,
+ edit_revision,
+ wc_ctx,
+ anchor_abspath,
+ target_basename,
+ depth,
+ get_all, no_ignore,
+ depth_as_sticky,
+ server_performs_filtering,
+ ignore_patterns,
+ status_func, status_baton,
+ cancel_func, cancel_baton,
+ result_pool,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc_get_status_editor4(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ void **set_locks_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func3_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ struct status4_wrapper_baton *swb = apr_palloc(pool, sizeof(*swb));
+ svn_wc__db_t *wc_db;
+ svn_wc_context_t *wc_ctx;
+ const char *anchor_abspath;
+
+ swb->old_func = status_func;
+ swb->old_baton = status_baton;
+
+ wc_db = svn_wc__adm_get_db(anchor);
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ wc_db, pool));
+
+ swb->wc_ctx = wc_ctx;
+
+ anchor_abspath = svn_wc__adm_access_abspath(anchor);
+
+ if (!svn_dirent_is_absolute(svn_wc_adm_access_path(anchor)))
+ {
+ swb->anchor_abspath = anchor_abspath;
+ swb->anchor_relpath = svn_wc_adm_access_path(anchor);
+ }
+ else
+ {
+ swb->anchor_abspath = NULL;
+ swb->anchor_relpath = NULL;
+ }
+
+ /* Before subversion 1.7 status always handled depth as sticky. 1.7 made
+ the output of svn status by default match the result of what would be
+ updated by a similar svn update. (Following the documentation) */
+
+ SVN_ERR(svn_wc_get_status_editor5(editor, edit_baton, set_locks_baton,
+ edit_revision, wc_ctx, anchor_abspath,
+ target, depth, get_all,
+ no_ignore,
+ (depth != svn_depth_unknown) /*as_sticky*/,
+ FALSE /* server_performs_filtering */,
+ ignore_patterns,
+ status4_wrapper_func, swb,
+ cancel_func, cancel_baton,
+ pool, pool));
+
+ if (traversal_info)
+ {
+ const char *local_path = svn_wc_adm_access_path(anchor);
+ const char *local_abspath = anchor_abspath;
+ if (*target)
+ {
+ local_path = svn_dirent_join(local_path, target, pool);
+ local_abspath = svn_dirent_join(local_abspath, target, pool);
+ }
+
+ SVN_ERR(gather_traversal_info(wc_ctx, local_abspath, local_path, depth,
+ traversal_info, TRUE, TRUE,
+ pool));
+ }
+
+ /* We can't destroy wc_ctx here, because the editor needs it while it's
+ driven. */
+ return SVN_NO_ERROR;
+}
+
+struct status_editor3_compat_baton
+{
+ svn_wc_status_func2_t old_func;
+ void *old_baton;
+};
+
+/* */
+static svn_error_t *
+status_editor3_compat_func(void *baton,
+ const char *path,
+ svn_wc_status2_t *status,
+ apr_pool_t *pool)
+{
+ struct status_editor3_compat_baton *secb = baton;
+
+ secb->old_func(secb->old_baton, path, status);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_get_status_editor3(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ void **set_locks_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func2_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ /* This baton must live beyond this function. Alloc on heap. */
+ struct status_editor3_compat_baton *secb = apr_palloc(pool, sizeof(*secb));
+
+ secb->old_func = status_func;
+ secb->old_baton = status_baton;
+
+ return svn_wc_get_status_editor4(editor, edit_baton, set_locks_baton,
+ edit_revision, anchor, target, depth,
+ get_all, no_ignore, ignore_patterns,
+ status_editor3_compat_func, secb,
+ cancel_func, cancel_baton, traversal_info,
+ pool);
+}
+
+svn_error_t *
+svn_wc_get_status_editor2(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ void **set_locks_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ apr_hash_t *config,
+ svn_boolean_t recurse,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func2_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *ignores;
+
+ SVN_ERR(svn_wc_get_default_ignores(&ignores, config, pool));
+ return svn_wc_get_status_editor3(editor,
+ edit_baton,
+ set_locks_baton,
+ edit_revision,
+ anchor,
+ target,
+ SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse),
+ get_all,
+ no_ignore,
+ ignores,
+ status_func,
+ status_baton,
+ cancel_func,
+ cancel_baton,
+ traversal_info,
+ pool);
+}
+
+
+/* Helpers for deprecated svn_wc_status_editor(), of type
+ svn_wc_status_func2_t. */
+struct old_status_func_cb_baton
+{
+ svn_wc_status_func_t original_func;
+ void *original_baton;
+};
+
+/* */
+static void old_status_func_cb(void *baton,
+ const char *path,
+ svn_wc_status2_t *status)
+{
+ struct old_status_func_cb_baton *b = baton;
+ svn_wc_status_t *stat = (svn_wc_status_t *) status;
+
+ b->original_func(b->original_baton, path, stat);
+}
+
+svn_error_t *
+svn_wc_get_status_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ apr_hash_t *config,
+ svn_boolean_t recurse,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ struct old_status_func_cb_baton *b = apr_pcalloc(pool, sizeof(*b));
+ apr_array_header_t *ignores;
+ b->original_func = status_func;
+ b->original_baton = status_baton;
+ SVN_ERR(svn_wc_get_default_ignores(&ignores, config, pool));
+ return svn_wc_get_status_editor3(editor, edit_baton, NULL, edit_revision,
+ anchor, target,
+ SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse),
+ get_all, no_ignore, ignores,
+ old_status_func_cb, b,
+ cancel_func, cancel_baton,
+ traversal_info, pool);
+}
+
+svn_error_t *
+svn_wc_status(svn_wc_status_t **status,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ svn_wc_status2_t *stat2;
+
+ SVN_ERR(svn_wc_status2(&stat2, path, adm_access, pool));
+ *status = (svn_wc_status_t *) stat2;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_wc_conflict_description_t *
+conflict_description_dup(const svn_wc_conflict_description_t *conflict,
+ apr_pool_t *pool)
+{
+ svn_wc_conflict_description_t *new_conflict;
+
+ new_conflict = apr_pcalloc(pool, sizeof(*new_conflict));
+
+ /* Shallow copy all members. */
+ *new_conflict = *conflict;
+
+ if (conflict->path)
+ new_conflict->path = apr_pstrdup(pool, conflict->path);
+ if (conflict->property_name)
+ new_conflict->property_name = apr_pstrdup(pool, conflict->property_name);
+ if (conflict->mime_type)
+ new_conflict->mime_type = apr_pstrdup(pool, conflict->mime_type);
+ /* NOTE: We cannot make a deep copy of adm_access. */
+ if (conflict->base_file)
+ new_conflict->base_file = apr_pstrdup(pool, conflict->base_file);
+ if (conflict->their_file)
+ new_conflict->their_file = apr_pstrdup(pool, conflict->their_file);
+ if (conflict->my_file)
+ new_conflict->my_file = apr_pstrdup(pool, conflict->my_file);
+ if (conflict->merged_file)
+ new_conflict->merged_file = apr_pstrdup(pool, conflict->merged_file);
+ if (conflict->src_left_version)
+ new_conflict->src_left_version =
+ svn_wc_conflict_version_dup(conflict->src_left_version, pool);
+ if (conflict->src_right_version)
+ new_conflict->src_right_version =
+ svn_wc_conflict_version_dup(conflict->src_right_version, pool);
+
+ return new_conflict;
+}
+
+
+svn_wc_status2_t *
+svn_wc_dup_status2(const svn_wc_status2_t *orig_stat,
+ apr_pool_t *pool)
+{
+ svn_wc_status2_t *new_stat = apr_palloc(pool, sizeof(*new_stat));
+
+ /* Shallow copy all members. */
+ *new_stat = *orig_stat;
+
+ /* Now go back and dup the deep items into this pool. */
+ if (orig_stat->entry)
+ new_stat->entry = svn_wc_entry_dup(orig_stat->entry, pool);
+
+ if (orig_stat->repos_lock)
+ new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool);
+
+ if (orig_stat->url)
+ new_stat->url = apr_pstrdup(pool, orig_stat->url);
+
+ if (orig_stat->ood_last_cmt_author)
+ new_stat->ood_last_cmt_author
+ = apr_pstrdup(pool, orig_stat->ood_last_cmt_author);
+
+ if (orig_stat->tree_conflict)
+ new_stat->tree_conflict
+ = conflict_description_dup(orig_stat->tree_conflict, pool);
+
+ /* Return the new hotness. */
+ return new_stat;
+}
+
+svn_wc_status_t *
+svn_wc_dup_status(const svn_wc_status_t *orig_stat,
+ apr_pool_t *pool)
+{
+ svn_wc_status_t *new_stat = apr_palloc(pool, sizeof(*new_stat));
+
+ /* Shallow copy all members. */
+ *new_stat = *orig_stat;
+
+ /* Now go back and dup the deep item into this pool. */
+ if (orig_stat->entry)
+ new_stat->entry = svn_wc_entry_dup(orig_stat->entry, pool);
+
+ /* Return the new hotness. */
+ return new_stat;
+}
+
+svn_error_t *
+svn_wc_get_ignores(apr_array_header_t **patterns,
+ apr_hash_t *config,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath,
+ svn_wc_adm_access_path(adm_access), pool));
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ SVN_ERR(svn_wc_get_ignores2(patterns, wc_ctx, local_abspath, config, pool,
+ pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_status2(svn_wc_status2_t **status,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc_context_t *wc_ctx;
+ svn_wc_status3_t *stat3;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ SVN_ERR(svn_wc_status3(&stat3, wc_ctx, local_abspath, pool, pool));
+ SVN_ERR(svn_wc__status2_from_3(status, stat3, wc_ctx, local_abspath,
+ pool, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+
+/*** From update_editor.c ***/
+
+svn_error_t *
+svn_wc_add_repos_file3(const char *dst_path,
+ svn_wc_adm_access_t *adm_access,
+ svn_stream_t *new_base_contents,
+ svn_stream_t *new_contents,
+ apr_hash_t *new_base_props,
+ apr_hash_t *new_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc_context_t *wc_ctx;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, dst_path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ SVN_ERR(svn_wc_add_repos_file4(wc_ctx,
+ local_abspath,
+ new_base_contents,
+ new_contents,
+ new_base_props,
+ new_props,
+ copyfrom_url,
+ copyfrom_rev,
+ cancel_func, cancel_baton,
+ pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_add_repos_file2(const char *dst_path,
+ svn_wc_adm_access_t *adm_access,
+ const char *new_text_base_path,
+ const char *new_text_path,
+ apr_hash_t *new_base_props,
+ apr_hash_t *new_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool)
+{
+ svn_stream_t *new_base_contents;
+ svn_stream_t *new_contents = NULL;
+
+ SVN_ERR(svn_stream_open_readonly(&new_base_contents, new_text_base_path,
+ pool, pool));
+
+ if (new_text_path)
+ {
+ /* NOTE: the specified path may *not* be under version control.
+ It is most likely sitting in .svn/tmp/. Thus, we cannot use the
+ typical WC functions to access "special", "keywords" or "EOL"
+ information. We need to look at the properties given to us. */
+
+ /* If the new file is special, then we can simply open the given
+ contents since it is already in normal form. */
+ if (svn_hash_gets(new_props, SVN_PROP_SPECIAL) != NULL)
+ {
+ SVN_ERR(svn_stream_open_readonly(&new_contents, new_text_path,
+ pool, pool));
+ }
+ else
+ {
+ /* The new text contents need to be detrans'd into normal form. */
+ svn_subst_eol_style_t eol_style;
+ const char *eol_str;
+ apr_hash_t *keywords = NULL;
+ svn_string_t *list;
+
+ list = svn_hash_gets(new_props, SVN_PROP_KEYWORDS);
+ if (list != NULL)
+ {
+ /* Since we are detranslating, all of the keyword values
+ can be "". */
+ SVN_ERR(svn_subst_build_keywords2(&keywords,
+ list->data,
+ "", "", 0, "",
+ pool));
+ if (apr_hash_count(keywords) == 0)
+ keywords = NULL;
+ }
+
+ svn_subst_eol_style_from_value(&eol_style, &eol_str,
+ svn_hash_gets(new_props,
+ SVN_PROP_EOL_STYLE));
+
+ if (svn_subst_translation_required(eol_style, eol_str, keywords,
+ FALSE, FALSE))
+ {
+ SVN_ERR(svn_subst_stream_detranslated(&new_contents,
+ new_text_path,
+ eol_style, eol_str,
+ FALSE,
+ keywords,
+ FALSE,
+ pool));
+ }
+ else
+ {
+ SVN_ERR(svn_stream_open_readonly(&new_contents, new_text_path,
+ pool, pool));
+ }
+ }
+ }
+
+ SVN_ERR(svn_wc_add_repos_file3(dst_path, adm_access,
+ new_base_contents, new_contents,
+ new_base_props, new_props,
+ copyfrom_url, copyfrom_rev,
+ NULL, NULL, NULL, NULL,
+ pool));
+
+ /* The API contract states that the text files will be removed upon
+ successful completion. add_repos_file3() does not remove the files
+ since it only has streams on them. Toss 'em now. */
+ svn_error_clear(svn_io_remove_file(new_text_base_path, pool));
+ if (new_text_path)
+ svn_error_clear(svn_io_remove_file(new_text_path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_add_repos_file(const char *dst_path,
+ svn_wc_adm_access_t *adm_access,
+ const char *new_text_path,
+ apr_hash_t *new_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool)
+{
+ return svn_wc_add_repos_file2(dst_path, adm_access,
+ new_text_path, NULL,
+ new_props, NULL,
+ copyfrom_url, copyfrom_rev,
+ pool);
+}
+
+svn_error_t *
+svn_wc_get_actual_target(const char *path,
+ const char **anchor,
+ const char **target,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+
+ SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool));
+ SVN_ERR(svn_wc_get_actual_target2(anchor, target, wc_ctx, path, pool, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+/* This function has no internal variant as its behavior on switched
+ non-directories is not what you would expect. But this happens to
+ be the legacy behavior of this function. */
+svn_error_t *
+svn_wc_is_wc_root2(svn_boolean_t *wc_root,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_root;
+ svn_boolean_t is_switched;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ err = svn_wc__db_is_switched(&is_root, &is_switched, &kind,
+ wc_ctx->db, local_abspath, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND &&
+ err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ return svn_error_trace(err);
+
+ return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND, err, err->message);
+ }
+
+ *wc_root = is_root || (kind == svn_node_dir && is_switched);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_is_wc_root(svn_boolean_t *wc_root,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ /* Subversion <= 1.6 said that '.' or a drive root is a WC root. */
+ if (svn_path_is_empty(path) || svn_dirent_is_root(path, strlen(path)))
+ {
+ *wc_root = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ err = svn_wc_is_wc_root2(wc_root, wc_ctx, local_abspath, pool);
+
+ if (err
+ && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY
+ || err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND))
+ {
+ /* Subversion <= 1.6 said that an unversioned path is a WC root. */
+ svn_error_clear(err);
+ *wc_root = TRUE;
+ }
+ else
+ SVN_ERR(err);
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+
+svn_error_t *
+svn_wc_get_update_editor4(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t server_performs_filtering,
+ svn_boolean_t clean_checkout,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__get_update_editor(editor, edit_baton,
+ target_revision,
+ wc_ctx,
+ anchor_abspath,
+ target_basename, NULL,
+ use_commit_times,
+ depth, depth_is_sticky,
+ allow_unver_obstructions,
+ adds_as_modification,
+ server_performs_filtering,
+ clean_checkout,
+ diff3_cmd,
+ preserved_exts,
+ fetch_dirents_func, fetch_dirents_baton,
+ conflict_func, conflict_baton,
+ external_func, external_baton,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc_get_update_editor3(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_conflict_resolver_func_t conflict_func,
+ void *conflict_baton,
+ svn_wc_get_file_t fetch_func,
+ void *fetch_baton,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *db = svn_wc__adm_get_db(anchor);
+ svn_wc_external_update_t external_func = NULL;
+ struct traversal_info_update_baton *eb = NULL;
+ struct conflict_func_1to2_baton *cfw = NULL;
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool));
+
+ if (traversal_info)
+ {
+ eb = apr_palloc(pool, sizeof(*eb));
+ eb->db = db;
+ eb->traversal = traversal_info;
+ external_func = traversal_info_update;
+ }
+
+ if (conflict_func)
+ {
+ cfw = apr_pcalloc(pool, sizeof(*cfw));
+ cfw->inner_func = conflict_func;
+ cfw->inner_baton = conflict_baton;
+ }
+
+ if (diff3_cmd)
+ SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool));
+
+ SVN_ERR(svn_wc_get_update_editor4(editor, edit_baton,
+ target_revision,
+ wc_ctx,
+ svn_wc__adm_access_abspath(anchor),
+ target,
+ use_commit_times,
+ depth, depth_is_sticky,
+ allow_unver_obstructions,
+ TRUE /* adds_as_modification */,
+ FALSE /* server_performs_filtering */,
+ FALSE /* clean_checkout */,
+ diff3_cmd,
+ preserved_exts,
+ NULL, NULL, /* fetch_dirents_func, baton */
+ conflict_func ? conflict_func_1to2_wrapper
+ : NULL,
+ cfw,
+ external_func, eb,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ pool, pool));
+
+ /* We can't destroy wc_ctx here, because the editor needs it while it's
+ driven. */
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_get_update_editor2(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t recurse,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const char *diff3_cmd,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ return svn_wc_get_update_editor3(target_revision, anchor, target,
+ use_commit_times,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE,
+ FALSE, notify_func, notify_baton,
+ cancel_func, cancel_baton, NULL, NULL,
+ NULL, NULL,
+ diff3_cmd, NULL, editor, edit_baton,
+ traversal_info, pool);
+}
+
+svn_error_t *
+svn_wc_get_update_editor(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t recurse,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const char *diff3_cmd,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ /* This baton must live beyond this function. Alloc on heap. */
+ struct compat_notify_baton_t *nb = apr_palloc(pool, sizeof(*nb));
+
+ nb->func = notify_func;
+ nb->baton = notify_baton;
+
+ return svn_wc_get_update_editor3(target_revision, anchor, target,
+ use_commit_times,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE,
+ FALSE, compat_call_notify_func, nb,
+ cancel_func, cancel_baton, NULL, NULL,
+ NULL, NULL,
+ diff3_cmd, NULL, editor, edit_baton,
+ traversal_info, pool);
+}
+
+
+svn_error_t *
+svn_wc_get_switch_editor4(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ const char *switch_url,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t server_performs_filtering,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__get_switch_editor(editor, edit_baton,
+ target_revision,
+ wc_ctx,
+ anchor_abspath, target_basename,
+ switch_url, NULL,
+ use_commit_times,
+ depth, depth_is_sticky,
+ allow_unver_obstructions,
+ server_performs_filtering,
+ diff3_cmd,
+ preserved_exts,
+ fetch_dirents_func, fetch_dirents_baton,
+ conflict_func, conflict_baton,
+ external_func, external_baton,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc_get_switch_editor3(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ const char *switch_url,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_conflict_resolver_func_t conflict_func,
+ void *conflict_baton,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *db = svn_wc__adm_get_db(anchor);
+ svn_wc_external_update_t external_func = NULL;
+ struct traversal_info_update_baton *eb = NULL;
+ struct conflict_func_1to2_baton *cfw = NULL;
+
+ SVN_ERR_ASSERT(switch_url && svn_uri_is_canonical(switch_url, pool));
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool));
+
+ if (traversal_info)
+ {
+ eb = apr_palloc(pool, sizeof(*eb));
+ eb->db = db;
+ eb->traversal = traversal_info;
+ external_func = traversal_info_update;
+ }
+
+ if (conflict_func)
+ {
+ cfw = apr_pcalloc(pool, sizeof(*cfw));
+ cfw->inner_func = conflict_func;
+ cfw->inner_baton = conflict_baton;
+ }
+
+ if (diff3_cmd)
+ SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool));
+
+ SVN_ERR(svn_wc_get_switch_editor4(editor, edit_baton,
+ target_revision,
+ wc_ctx,
+ svn_wc__adm_access_abspath(anchor),
+ target, switch_url,
+ use_commit_times,
+ depth, depth_is_sticky,
+ allow_unver_obstructions,
+ FALSE /* server_performs_filtering */,
+ diff3_cmd,
+ preserved_exts,
+ NULL, NULL, /* fetch_dirents_func, baton */
+ conflict_func ? conflict_func_1to2_wrapper
+ : NULL,
+ cfw,
+ external_func, eb,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ pool, pool));
+
+ /* We can't destroy wc_ctx here, because the editor needs it while it's
+ driven. */
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_get_switch_editor2(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ const char *switch_url,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t recurse,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const char *diff3_cmd,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(switch_url);
+
+ return svn_wc_get_switch_editor3(target_revision, anchor, target,
+ switch_url, use_commit_times,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE,
+ FALSE, notify_func, notify_baton,
+ cancel_func, cancel_baton,
+ NULL, NULL, diff3_cmd,
+ NULL, editor, edit_baton, traversal_info,
+ pool);
+}
+
+svn_error_t *
+svn_wc_get_switch_editor(svn_revnum_t *target_revision,
+ svn_wc_adm_access_t *anchor,
+ const char *target,
+ const char *switch_url,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t recurse,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ const char *diff3_cmd,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_traversal_info_t *traversal_info,
+ apr_pool_t *pool)
+{
+ /* This baton must live beyond this function. Alloc on heap. */
+ struct compat_notify_baton_t *nb = apr_palloc(pool, sizeof(*nb));
+
+ nb->func = notify_func;
+ nb->baton = notify_baton;
+
+ return svn_wc_get_switch_editor3(target_revision, anchor, target,
+ switch_url, use_commit_times,
+ SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE,
+ FALSE, compat_call_notify_func, nb,
+ cancel_func, cancel_baton,
+ NULL, NULL, diff3_cmd,
+ NULL, editor, edit_baton, traversal_info,
+ pool);
+}
+
+
+svn_error_t *
+svn_wc_external_item_create(const svn_wc_external_item2_t **item,
+ apr_pool_t *pool)
+{
+ *item = apr_pcalloc(pool, sizeof(svn_wc_external_item2_t));
+ return SVN_NO_ERROR;
+}
+
+svn_wc_external_item_t *
+svn_wc_external_item_dup(const svn_wc_external_item_t *item,
+ apr_pool_t *pool)
+{
+ svn_wc_external_item_t *new_item = apr_palloc(pool, sizeof(*new_item));
+
+ *new_item = *item;
+
+ if (new_item->target_dir)
+ new_item->target_dir = apr_pstrdup(pool, new_item->target_dir);
+
+ if (new_item->url)
+ new_item->url = apr_pstrdup(pool, new_item->url);
+
+ return new_item;
+}
+
+
+svn_wc_traversal_info_t *
+svn_wc_init_traversal_info(apr_pool_t *pool)
+{
+ svn_wc_traversal_info_t *ti = apr_palloc(pool, sizeof(*ti));
+
+ ti->pool = pool;
+ ti->externals_old = apr_hash_make(pool);
+ ti->externals_new = apr_hash_make(pool);
+ ti->depths = apr_hash_make(pool);
+
+ return ti;
+}
+
+
+void
+svn_wc_edited_externals(apr_hash_t **externals_old,
+ apr_hash_t **externals_new,
+ svn_wc_traversal_info_t *traversal_info)
+{
+ *externals_old = traversal_info->externals_old;
+ *externals_new = traversal_info->externals_new;
+}
+
+
+void
+svn_wc_traversed_depths(apr_hash_t **depths,
+ svn_wc_traversal_info_t *traversal_info)
+{
+ *depths = traversal_info->depths;
+}
+
+
+/*** From lock.c ***/
+
+/* To preserve API compatibility with Subversion 1.0.0 */
+svn_error_t *
+svn_wc_adm_open(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ svn_boolean_t tree_lock,
+ apr_pool_t *pool)
+{
+ return svn_wc_adm_open3(adm_access, associated, path, write_lock,
+ (tree_lock ? -1 : 0), NULL, NULL, pool);
+}
+
+svn_error_t *
+svn_wc_adm_open2(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ apr_pool_t *pool)
+{
+ return svn_wc_adm_open3(adm_access, associated, path, write_lock,
+ levels_to_lock, NULL, NULL, pool);
+}
+
+svn_error_t *
+svn_wc_adm_probe_open(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ svn_boolean_t tree_lock,
+ apr_pool_t *pool)
+{
+ return svn_wc_adm_probe_open3(adm_access, associated, path,
+ write_lock, (tree_lock ? -1 : 0),
+ NULL, NULL, pool);
+}
+
+
+svn_error_t *
+svn_wc_adm_probe_open2(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ apr_pool_t *pool)
+{
+ return svn_wc_adm_probe_open3(adm_access, associated, path, write_lock,
+ levels_to_lock, NULL, NULL, pool);
+}
+
+svn_error_t *
+svn_wc_adm_probe_try2(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ apr_pool_t *pool)
+{
+ return svn_wc_adm_probe_try3(adm_access, associated, path, write_lock,
+ levels_to_lock, NULL, NULL, pool);
+}
+
+svn_error_t *
+svn_wc_adm_probe_try(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ svn_boolean_t tree_lock,
+ apr_pool_t *pool)
+{
+ return svn_wc_adm_probe_try3(adm_access, associated, path, write_lock,
+ (tree_lock ? -1 : 0), NULL, NULL, pool);
+}
+
+svn_error_t *
+svn_wc_adm_close(svn_wc_adm_access_t *adm_access)
+{
+ /* This is the only pool we have access to. */
+ apr_pool_t *scratch_pool = svn_wc_adm_access_pool(adm_access);
+
+ return svn_wc_adm_close2(adm_access, scratch_pool);
+}
+
+svn_error_t *
+svn_wc_locked(svn_boolean_t *locked,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool));
+
+ SVN_ERR(svn_wc_locked2(NULL, locked, wc_ctx, local_abspath, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_check_wc(const char *path,
+ int *wc_format,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool));
+
+ SVN_ERR(svn_wc_check_wc2(wc_format, wc_ctx, local_abspath, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+
+/*** From translate.c ***/
+
+svn_error_t *
+svn_wc_translated_file(const char **xlated_p,
+ const char *vfile,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t force_repair,
+ apr_pool_t *pool)
+{
+ return svn_wc_translated_file2(xlated_p, vfile, vfile, adm_access,
+ SVN_WC_TRANSLATE_TO_NF
+ | (force_repair ?
+ SVN_WC_TRANSLATE_FORCE_EOL_REPAIR : 0),
+ pool);
+}
+
+svn_error_t *
+svn_wc_translated_stream(svn_stream_t **stream,
+ const char *path,
+ const char *versioned_file,
+ svn_wc_adm_access_t *adm_access,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ const char *versioned_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_dirent_get_absolute(&versioned_abspath, versioned_file, pool));
+
+ return svn_error_trace(
+ svn_wc__internal_translated_stream(stream, svn_wc__adm_get_db(adm_access),
+ local_abspath, versioned_abspath, flags,
+ pool, pool));
+}
+
+svn_error_t *
+svn_wc_translated_file2(const char **xlated_path,
+ const char *src,
+ const char *versioned_file,
+ svn_wc_adm_access_t *adm_access,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ const char *versioned_abspath;
+ const char *root;
+ const char *tmp_root;
+ const char *src_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&versioned_abspath, versioned_file, pool));
+ SVN_ERR(svn_dirent_get_absolute(&src_abspath, src, pool));
+
+ SVN_ERR(svn_wc__internal_translated_file(xlated_path, src_abspath,
+ svn_wc__adm_get_db(adm_access),
+ versioned_abspath,
+ flags, NULL, NULL, pool, pool));
+
+ if (strcmp(*xlated_path, src_abspath) == 0)
+ *xlated_path = src;
+ else if (! svn_dirent_is_absolute(versioned_file))
+ {
+ SVN_ERR(svn_io_temp_dir(&tmp_root, pool));
+ if (! svn_dirent_is_child(tmp_root, *xlated_path, pool))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&root, "", pool));
+
+ if (svn_dirent_is_child(root, *xlated_path, pool))
+ *xlated_path = svn_dirent_is_child(root, *xlated_path, pool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*** From relocate.c ***/
+svn_error_t *
+svn_wc_relocate3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const char *from,
+ const char *to,
+ svn_boolean_t recurse,
+ svn_wc_relocation_validator3_t validator,
+ void *validator_baton,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc_context_t *wc_ctx;
+
+ if (! recurse)
+ SVN_ERR(svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Non-recursive relocation not supported")));
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ SVN_ERR(svn_wc_relocate4(wc_ctx, local_abspath, from, to,
+ validator, validator_baton, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+/* Compatibility baton and wrapper. */
+struct compat2_baton {
+ svn_wc_relocation_validator2_t validator;
+ void *baton;
+};
+
+/* Compatibility baton and wrapper. */
+struct compat_baton {
+ svn_wc_relocation_validator_t validator;
+ void *baton;
+};
+
+/* This implements svn_wc_relocate_validator3_t. */
+static svn_error_t *
+compat2_validator(void *baton,
+ const char *uuid,
+ const char *url,
+ const char *root_url,
+ apr_pool_t *pool)
+{
+ struct compat2_baton *cb = baton;
+ /* The old callback type doesn't set root_url. */
+ return cb->validator(cb->baton, uuid,
+ (root_url ? root_url : url), (root_url != NULL),
+ pool);
+}
+
+/* This implements svn_wc_relocate_validator3_t. */
+static svn_error_t *
+compat_validator(void *baton,
+ const char *uuid,
+ const char *url,
+ const char *root_url,
+ apr_pool_t *pool)
+{
+ struct compat_baton *cb = baton;
+ /* The old callback type doesn't allow uuid to be NULL. */
+ if (uuid)
+ return cb->validator(cb->baton, uuid, url);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_relocate2(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const char *from,
+ const char *to,
+ svn_boolean_t recurse,
+ svn_wc_relocation_validator2_t validator,
+ void *validator_baton,
+ apr_pool_t *pool)
+{
+ struct compat2_baton cb;
+
+ cb.validator = validator;
+ cb.baton = validator_baton;
+
+ return svn_wc_relocate3(path, adm_access, from, to, recurse,
+ compat2_validator, &cb, pool);
+}
+
+svn_error_t *
+svn_wc_relocate(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const char *from,
+ const char *to,
+ svn_boolean_t recurse,
+ svn_wc_relocation_validator_t validator,
+ void *validator_baton,
+ apr_pool_t *pool)
+{
+ struct compat_baton cb;
+
+ cb.validator = validator;
+ cb.baton = validator_baton;
+
+ return svn_wc_relocate3(path, adm_access, from, to, recurse,
+ compat_validator, &cb, pool);
+}
+
+
+/*** From log.c ***/
+
+svn_error_t *
+svn_wc_cleanup2(const char *path,
+ const char *diff3_cmd,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool));
+
+ SVN_ERR(svn_wc_cleanup3(wc_ctx, local_abspath, cancel_func,
+ cancel_baton, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_cleanup(const char *path,
+ svn_wc_adm_access_t *optional_adm_access,
+ const char *diff3_cmd,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_wc_cleanup2(path, diff3_cmd, cancel_func, cancel_baton, pool);
+}
+
+/*** From questions.c ***/
+
+svn_error_t *
+svn_wc_has_binary_prop(svn_boolean_t *has_binary_prop,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ const char *local_abspath;
+ const svn_string_t *value;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ SVN_ERR(svn_wc__internal_propget(&value, db, local_abspath,
+ SVN_PROP_MIME_TYPE,
+ pool, pool));
+
+ if (value && (svn_mime_type_is_binary(value->data)))
+ *has_binary_prop = TRUE;
+ else
+ *has_binary_prop = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_conflicted_p2(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ svn_boolean_t *tree_conflicted_p,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc_context_t *wc_ctx;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */,
+ svn_wc__adm_get_db(adm_access),
+ pool));
+
+ err = svn_wc_conflicted_p3(text_conflicted_p, prop_conflicted_p,
+ tree_conflicted_p, wc_ctx, local_abspath, pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+
+ if (text_conflicted_p)
+ *text_conflicted_p = FALSE;
+ if (prop_conflicted_p)
+ *prop_conflicted_p = FALSE;
+ if (tree_conflicted_p)
+ *tree_conflicted_p = FALSE;
+ }
+ else if (err)
+ return err;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_conflicted_p(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ const char *dir_path,
+ const svn_wc_entry_t *entry,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ const char *path;
+
+ *text_conflicted_p = FALSE;
+ *prop_conflicted_p = FALSE;
+
+ if (entry->conflict_old)
+ {
+ path = svn_dirent_join(dir_path, entry->conflict_old, pool);
+ SVN_ERR(svn_io_check_path(path, &kind, pool));
+ *text_conflicted_p = (kind == svn_node_file);
+ }
+
+ if ((! *text_conflicted_p) && (entry->conflict_new))
+ {
+ path = svn_dirent_join(dir_path, entry->conflict_new, pool);
+ SVN_ERR(svn_io_check_path(path, &kind, pool));
+ *text_conflicted_p = (kind == svn_node_file);
+ }
+
+ if ((! *text_conflicted_p) && (entry->conflict_wrk))
+ {
+ path = svn_dirent_join(dir_path, entry->conflict_wrk, pool);
+ SVN_ERR(svn_io_check_path(path, &kind, pool));
+ *text_conflicted_p = (kind == svn_node_file);
+ }
+
+ if (entry->prejfile)
+ {
+ path = svn_dirent_join(dir_path, entry->prejfile, pool);
+ SVN_ERR(svn_io_check_path(path, &kind, pool));
+ *prop_conflicted_p = (kind == svn_node_file);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_text_modified_p(svn_boolean_t *modified_p,
+ const char *filename,
+ svn_boolean_t force_comparison,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, filename, pool));
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool));
+
+ SVN_ERR(svn_wc_text_modified_p2(modified_p, wc_ctx, local_abspath,
+ force_comparison, pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+
+/*** From copy.c ***/
+svn_error_t *
+svn_wc_copy2(const char *src,
+ svn_wc_adm_access_t *dst_parent,
+ const char *dst_basename,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *wc_db = svn_wc__adm_get_db(dst_parent);
+ const char *src_abspath;
+ const char *dst_abspath;
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool));
+ SVN_ERR(svn_dirent_get_absolute(&src_abspath, src, pool));
+
+ dst_abspath = svn_dirent_join(svn_wc__adm_access_abspath(dst_parent),
+ dst_basename, pool);
+
+ SVN_ERR(svn_wc_copy3(wc_ctx,
+ src_abspath,
+ dst_abspath,
+ FALSE /* metadata_only */,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_copy(const char *src_path,
+ svn_wc_adm_access_t *dst_parent,
+ const char *dst_basename,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func_t notify_func,
+ void *notify_baton,
+ apr_pool_t *pool)
+{
+ struct compat_notify_baton_t nb;
+
+ nb.func = notify_func;
+ nb.baton = notify_baton;
+
+ return svn_wc_copy2(src_path, dst_parent, dst_basename, cancel_func,
+ cancel_baton, compat_call_notify_func,
+ &nb, pool);
+}
+
+
+/*** From merge.c ***/
+
+svn_error_t *
+svn_wc_merge4(enum svn_wc_merge_outcome_t *merge_outcome,
+ svn_wc_context_t *wc_ctx,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ const apr_array_header_t *prop_diff,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc_merge5(merge_outcome,
+ NULL /* merge_props_outcome */,
+ wc_ctx,
+ left_abspath,
+ right_abspath,
+ target_abspath,
+ left_label,
+ right_label,
+ target_label,
+ left_version,
+ right_version,
+ dry_run,
+ diff3_cmd,
+ merge_options,
+ NULL /* original_props */,
+ prop_diff,
+ conflict_func, conflict_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc_merge3(enum svn_wc_merge_outcome_t *merge_outcome,
+ const char *left,
+ const char *right,
+ const char *merge_target,
+ svn_wc_adm_access_t *adm_access,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ const apr_array_header_t *prop_diff,
+ svn_wc_conflict_resolver_func_t conflict_func,
+ void *conflict_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ const char *left_abspath, *right_abspath, *target_abspath;
+ struct conflict_func_1to2_baton cfw;
+
+ SVN_ERR(svn_dirent_get_absolute(&left_abspath, left, pool));
+ SVN_ERR(svn_dirent_get_absolute(&right_abspath, right, pool));
+ SVN_ERR(svn_dirent_get_absolute(&target_abspath, merge_target, pool));
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, db, pool));
+
+ cfw.inner_func = conflict_func;
+ cfw.inner_baton = conflict_baton;
+
+ if (diff3_cmd)
+ SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool));
+
+ SVN_ERR(svn_wc_merge4(merge_outcome,
+ wc_ctx,
+ left_abspath,
+ right_abspath,
+ target_abspath,
+ left_label,
+ right_label,
+ target_label,
+ NULL,
+ NULL,
+ dry_run,
+ diff3_cmd,
+ merge_options,
+ prop_diff,
+ conflict_func ? conflict_func_1to2_wrapper : NULL,
+ &cfw,
+ NULL, NULL,
+ pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_merge2(enum svn_wc_merge_outcome_t *merge_outcome,
+ const char *left,
+ const char *right,
+ const char *merge_target,
+ svn_wc_adm_access_t *adm_access,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ apr_pool_t *pool)
+{
+ return svn_wc_merge3(merge_outcome,
+ left, right, merge_target, adm_access,
+ left_label, right_label, target_label,
+ dry_run, diff3_cmd, merge_options, NULL,
+ NULL, NULL, pool);
+}
+
+svn_error_t *
+svn_wc_merge(const char *left,
+ const char *right,
+ const char *merge_target,
+ svn_wc_adm_access_t *adm_access,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ svn_boolean_t dry_run,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ const char *diff3_cmd,
+ apr_pool_t *pool)
+{
+ return svn_wc_merge3(merge_outcome,
+ left, right, merge_target, adm_access,
+ left_label, right_label, target_label,
+ dry_run, diff3_cmd, NULL, NULL, NULL,
+ NULL, pool);
+}
+
+
+/*** From util.c ***/
+
+svn_wc_conflict_version_t *
+svn_wc_conflict_version_create(const char *repos_url,
+ const char *path_in_repos,
+ svn_revnum_t peg_rev,
+ svn_node_kind_t node_kind,
+ apr_pool_t *pool)
+{
+ return svn_wc_conflict_version_create2(repos_url, NULL, path_in_repos,
+ peg_rev, node_kind, pool);
+}
+
+svn_wc_conflict_description_t *
+svn_wc_conflict_description_create_text(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ svn_wc_conflict_description_t *conflict;
+
+ conflict = apr_pcalloc(pool, sizeof(*conflict));
+ conflict->path = path;
+ conflict->node_kind = svn_node_file;
+ conflict->kind = svn_wc_conflict_kind_text;
+ conflict->access = adm_access;
+ conflict->action = svn_wc_conflict_action_edit;
+ conflict->reason = svn_wc_conflict_reason_edited;
+ return conflict;
+}
+
+svn_wc_conflict_description_t *
+svn_wc_conflict_description_create_prop(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_node_kind_t node_kind,
+ const char *property_name,
+ apr_pool_t *pool)
+{
+ svn_wc_conflict_description_t *conflict;
+
+ conflict = apr_pcalloc(pool, sizeof(*conflict));
+ conflict->path = path;
+ conflict->node_kind = node_kind;
+ conflict->kind = svn_wc_conflict_kind_property;
+ conflict->access = adm_access;
+ conflict->property_name = property_name;
+ return conflict;
+}
+
+svn_wc_conflict_description_t *
+svn_wc_conflict_description_create_tree(
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_node_kind_t node_kind,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_version_t *src_left_version,
+ svn_wc_conflict_version_t *src_right_version,
+ apr_pool_t *pool)
+{
+ svn_wc_conflict_description_t *conflict;
+
+ conflict = apr_pcalloc(pool, sizeof(*conflict));
+ conflict->path = path;
+ conflict->node_kind = node_kind;
+ conflict->kind = svn_wc_conflict_kind_tree;
+ conflict->access = adm_access;
+ conflict->operation = operation;
+ conflict->src_left_version = src_left_version;
+ conflict->src_right_version = src_right_version;
+ return conflict;
+}
+
+
+/*** From revision_status.c ***/
+
+svn_error_t *
+svn_wc_revision_status(svn_wc_revision_status_t **result_p,
+ const char *wc_path,
+ const char *trail_url,
+ svn_boolean_t committed,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, wc_path, pool));
+ SVN_ERR(svn_wc_context_create(&wc_ctx, NULL /* config */, pool, pool));
+
+ SVN_ERR(svn_wc_revision_status2(result_p, wc_ctx, local_abspath, trail_url,
+ committed, cancel_func, cancel_baton, pool,
+ pool));
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+/*** From crop.c ***/
+svn_error_t *
+svn_wc_crop_tree(svn_wc_adm_access_t *anchor,
+ const char *target,
+ svn_depth_t depth,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_wc_context_t *wc_ctx;
+ svn_wc__db_t *db = svn_wc__adm_get_db(anchor);
+ const char *local_abspath;
+
+ local_abspath = svn_dirent_join(svn_wc__adm_access_abspath(anchor),
+ target, pool);
+
+ SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool));
+
+ if (depth == svn_depth_exclude)
+ {
+ SVN_ERR(svn_wc_exclude(wc_ctx,
+ local_abspath,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc_crop_tree2(wc_ctx,
+ local_abspath,
+ depth,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ pool));
+ }
+
+ return svn_error_trace(svn_wc_context_destroy(wc_ctx));
+}
+
+svn_error_t *
+svn_wc_move(svn_wc_context_t *wc_ctx,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t metadata_only,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__move2(wc_ctx, src_abspath, dst_abspath,
+ metadata_only,
+ TRUE, /* allow_mixed_revisions */
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc_read_kind(svn_node_kind_t *kind,
+ svn_wc_context_t *wc_ctx,
+ const char *abspath,
+ svn_boolean_t show_hidden,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc_read_kind2(kind,
+ wc_ctx, abspath,
+ TRUE /* show_deleted */,
+ show_hidden,
+ scratch_pool));
+
+ /*if (db_kind == svn_node_dir)
+ *kind = svn_node_dir;
+ else if (db_kind == svn_node_file || db_kind == svn_node_symlink)
+ *kind = svn_node_file;
+ else
+ *kind = svn_node_none;*/
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/diff.h b/contrib/subversion/subversion/libsvn_wc/diff.h
new file mode 100644
index 0000000..d16a9e5
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/diff.h
@@ -0,0 +1,144 @@
+/*
+ * lock.h: routines for diffing local files and directories.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_WC_DIFF_H
+#define SVN_LIBSVN_WC_DIFF_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_wc.h"
+
+#include "wc_db.h"
+#include "private/svn_diff_tree.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Reports the file LOCAL_ABSPATH as ADDED file with relpath RELPATH to
+ PROCESSOR with as parent baton PROCESSOR_PARENT_BATON.
+
+ The node is expected to have status svn_wc__db_status_normal, or
+ svn_wc__db_status_added. When DIFF_PRISTINE is TRUE, report the pristine
+ version of LOCAL_ABSPATH as ADDED. In this case an
+ svn_wc__db_status_deleted may shadow an added or deleted node.
+
+ If CHANGELIST_HASH is not NULL and LOCAL_ABSPATH's changelist is not
+ in the changelist, don't report the node.
+ */
+svn_error_t *
+svn_wc__diff_local_only_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ apr_hash_t *changelist_hash,
+ svn_boolean_t diff_pristine,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Reports the directory LOCAL_ABSPATH and everything below it (limited by
+ DEPTH) as added with relpath RELPATH to PROCESSOR with as parent baton
+ PROCESSOR_PARENT_BATON.
+
+ The node is expected to have status svn_wc__db_status_normal, or
+ svn_wc__db_status_added. When DIFF_PRISTINE is TRUE, report the pristine
+ version of LOCAL_ABSPATH as ADDED. In this case an
+ svn_wc__db_status_deleted may shadow an added or deleted node.
+
+ If CHANGELIST_HASH is not NULL and LOCAL_ABSPATH's changelist is not
+ in the changelist, don't report the node.
+ */
+svn_error_t *
+svn_wc__diff_local_only_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_depth_t depth,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ apr_hash_t *changelist_hash,
+ svn_boolean_t diff_pristine,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Reports the BASE-file LOCAL_ABSPATH as deleted to PROCESSOR with relpath
+ RELPATH, revision REVISION and parent baton PROCESSOR_PARENT_BATON.
+
+ If REVISION is invalid, the revision as stored in BASE is used.
+
+ The node is expected to have status svn_wc__db_status_normal in BASE. */
+svn_error_t *
+svn_wc__diff_base_only_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_revnum_t revision,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ apr_pool_t *scratch_pool);
+
+/* Reports the BASE-directory LOCAL_ABSPATH and everything below it (limited
+ by DEPTH) as deleted to PROCESSOR with relpath RELPATH and parent baton
+ PROCESSOR_PARENT_BATON.
+
+ If REVISION is invalid, the revision as stored in BASE is used.
+
+ The node is expected to have status svn_wc__db_status_normal in BASE. */
+svn_error_t *
+svn_wc__diff_base_only_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Diff the file PATH against the text base of its BASE layer. At this
+ * stage we are dealing with a file that does exist in the working copy.
+ */
+svn_error_t *
+svn_wc__diff_base_working_diff(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *changelist_hash,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_dir_baton,
+ svn_boolean_t diff_pristine,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_DIFF_H */
diff --git a/contrib/subversion/subversion/libsvn_wc/diff_editor.c b/contrib/subversion/subversion/libsvn_wc/diff_editor.c
new file mode 100644
index 0000000..839241f
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/diff_editor.c
@@ -0,0 +1,2747 @@
+/*
+ * diff_editor.c -- The diff editor for comparing the working copy against the
+ * repository.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/*
+ * This code uses an svn_delta_editor_t editor driven by
+ * svn_wc_crawl_revisions (like the update command) to retrieve the
+ * differences between the working copy and the requested repository
+ * version. Rather than updating the working copy, this new editor creates
+ * temporary files that contain the pristine repository versions. When the
+ * crawler closes the files the editor calls back to a client layer
+ * function to compare the working copy and the temporary file. There is
+ * only ever one temporary file in existence at any time.
+ *
+ * When the crawler closes a directory, the editor then calls back to the
+ * client layer to compare any remaining files that may have been modified
+ * locally. Added directories do not have corresponding temporary
+ * directories created, as they are not needed.
+ *
+ * The diff result from this editor is a combination of the restructuring
+ * operations from the repository with the local restructurings since checking
+ * out.
+ *
+ * ### TODO: Make sure that we properly support and report multi layered
+ * operations instead of only simple file replacements.
+ *
+ * ### TODO: Replacements where the node kind changes needs support. It
+ * mostly works when the change is in the repository, but not when it is
+ * in the working copy.
+ *
+ * ### TODO: Do we need to support copyfrom?
+ *
+ */
+
+#include <apr_hash.h>
+#include <apr_md5.h>
+
+#include <assert.h>
+
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_sorts.h"
+
+#include "private/svn_subr_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_diff_tree.h"
+#include "private/svn_editor.h"
+
+#include "wc.h"
+#include "props.h"
+#include "adm_files.h"
+#include "translate.h"
+#include "diff.h"
+
+#include "svn_private_config.h"
+
+/*-------------------------------------------------------------------------*/
+
+
+/* Overall crawler editor baton.
+ */
+struct edit_baton_t
+{
+ /* A wc db. */
+ svn_wc__db_t *db;
+
+ /* A diff tree processor, receiving the result of the diff. */
+ const svn_diff_tree_processor_t *processor;
+
+ /* A boolean indicating whether local additions should be reported before
+ remote deletes. The processor can transform adds in deletes and deletes
+ in adds, but it can't reorder the output. */
+ svn_boolean_t local_before_remote;
+
+ /* ANCHOR/TARGET represent the base of the hierarchy to be compared. */
+ const char *target;
+ const char *anchor_abspath;
+
+ /* Target revision */
+ svn_revnum_t revnum;
+
+ /* Was the root opened? */
+ svn_boolean_t root_opened;
+
+ /* How does this diff descend as seen from target? */
+ svn_depth_t depth;
+
+ /* Should this diff ignore node ancestry? */
+ svn_boolean_t ignore_ancestry;
+
+ /* Possibly diff repos against text-bases instead of working files. */
+ svn_boolean_t diff_pristine;
+
+ /* Hash whose keys are const char * changelist names. */
+ apr_hash_t *changelist_hash;
+
+ /* Cancel function/baton */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ apr_pool_t *pool;
+};
+
+/* Directory level baton.
+ */
+struct dir_baton_t
+{
+ /* Reference to parent directory baton (or NULL for the root) */
+ struct dir_baton_t *parent_baton;
+
+ /* The depth at which this directory should be diffed. */
+ svn_depth_t depth;
+
+ /* The name and path of this directory as if they would be/are in the
+ local working copy. */
+ const char *name;
+ const char *relpath;
+ const char *local_abspath;
+
+ /* TRUE if the file is added by the editor drive. */
+ svn_boolean_t added;
+ /* TRUE if the node exists only on the repository side (op_depth 0 or added) */
+ svn_boolean_t repos_only;
+ /* TRUE if the node is to be compared with an unrelated node*/
+ svn_boolean_t ignoring_ancestry;
+
+ /* Processor state */
+ void *pdb;
+ svn_boolean_t skip;
+ svn_boolean_t skip_children;
+
+ svn_diff_source_t *left_src;
+ svn_diff_source_t *right_src;
+
+ apr_hash_t *local_info;
+
+ /* A hash containing the basenames of the nodes reported deleted by the
+ repository (or NULL for no values). */
+ apr_hash_t *deletes;
+
+ /* Identifies those directory elements that get compared while running
+ the crawler. These elements should not be compared again when
+ recursively looking for local modifications.
+
+ This hash maps the basename of the node to an unimportant value.
+
+ If the directory's properties have been compared, an item with hash
+ key of "" will be present in the hash. */
+ apr_hash_t *compared;
+
+ /* The list of incoming BASE->repos propchanges. */
+ apr_array_header_t *propchanges;
+
+ /* Has a change on regular properties */
+ svn_boolean_t has_propchange;
+
+ /* The overall crawler editor baton. */
+ struct edit_baton_t *eb;
+
+ apr_pool_t *pool;
+ int users;
+};
+
+/* File level baton.
+ */
+struct file_baton_t
+{
+ struct dir_baton_t *parent_baton;
+
+ /* The name and path of this file as if they would be/are in the
+ parent directory, diff session and local working copy. */
+ const char *name;
+ const char *relpath;
+ const char *local_abspath;
+
+ /* Processor state */
+ void *pfb;
+ svn_boolean_t skip;
+
+ /* TRUE if the file is added by the editor drive. */
+ svn_boolean_t added;
+ /* TRUE if the node exists only on the repository side (op_depth 0 or added) */
+ svn_boolean_t repos_only;
+ /* TRUE if the node is to be compared with an unrelated node*/
+ svn_boolean_t ignoring_ancestry;
+
+ const svn_diff_source_t *left_src;
+ const svn_diff_source_t *right_src;
+
+ /* The list of incoming BASE->repos propchanges. */
+ apr_array_header_t *propchanges;
+
+ /* Has a change on regular properties */
+ svn_boolean_t has_propchange;
+
+ /* The current BASE checksum and props */
+ const svn_checksum_t *base_checksum;
+ apr_hash_t *base_props;
+
+ /* The resulting from apply_textdelta */
+ const char *temp_file_path;
+ unsigned char result_digest[APR_MD5_DIGESTSIZE];
+
+ /* The overall crawler editor baton. */
+ struct edit_baton_t *eb;
+
+ apr_pool_t *pool;
+};
+
+/* Create a new edit baton. TARGET_PATH/ANCHOR are working copy paths
+ * that describe the root of the comparison. CALLBACKS/CALLBACK_BATON
+ * define the callbacks to compare files. DEPTH defines if and how to
+ * descend into subdirectories; see public doc string for exactly how.
+ * IGNORE_ANCESTRY defines whether to utilize node ancestry when
+ * calculating diffs. USE_TEXT_BASE defines whether to compare
+ * against working files or text-bases. REVERSE_ORDER defines which
+ * direction to perform the diff.
+ *
+ * CHANGELIST_FILTER is a list of const char * changelist names, used to
+ * filter diff output responses to only those items in one of the
+ * specified changelists, empty (or NULL altogether) if no changelist
+ * filtering is requested.
+ */
+static svn_error_t *
+make_edit_baton(struct edit_baton_t **edit_baton,
+ svn_wc__db_t *db,
+ const char *anchor_abspath,
+ const char *target,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ apr_hash_t *changelist_hash = NULL;
+ struct edit_baton_t *eb;
+ const svn_diff_tree_processor_t *processor;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath));
+
+ if (changelist_filter && changelist_filter->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
+ pool));
+
+ SVN_ERR(svn_wc__wrap_diff_callbacks(&processor,
+ callbacks, callback_baton, TRUE,
+ pool, pool));
+
+ if (reverse_order)
+ processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool);
+
+ /* --show-copies-as-adds implies --notice-ancestry */
+ if (show_copies_as_adds)
+ ignore_ancestry = FALSE;
+
+ if (! show_copies_as_adds)
+ processor = svn_diff__tree_processor_copy_as_changed_create(processor,
+ pool);
+
+ eb = apr_pcalloc(pool, sizeof(*eb));
+ eb->db = db;
+ eb->anchor_abspath = apr_pstrdup(pool, anchor_abspath);
+ eb->target = apr_pstrdup(pool, target);
+ eb->processor = processor;
+ eb->depth = depth;
+ eb->ignore_ancestry = ignore_ancestry;
+ eb->local_before_remote = reverse_order;
+ eb->diff_pristine = use_text_base;
+ eb->changelist_hash = changelist_hash;
+ eb->cancel_func = cancel_func;
+ eb->cancel_baton = cancel_baton;
+ eb->pool = pool;
+
+ *edit_baton = eb;
+ return SVN_NO_ERROR;
+}
+
+/* Create a new directory baton. PATH is the directory path,
+ * including anchor_path. ADDED is set if this directory is being
+ * added rather than replaced. PARENT_BATON is the baton of the
+ * parent directory, it will be null if this is the root of the
+ * comparison hierarchy. The directory and its parent may or may not
+ * exist in the working copy. EDIT_BATON is the overall crawler
+ * editor baton.
+ */
+static struct dir_baton_t *
+make_dir_baton(const char *path,
+ struct dir_baton_t *parent_baton,
+ struct edit_baton_t *eb,
+ svn_boolean_t added,
+ svn_depth_t depth,
+ apr_pool_t *result_pool)
+{
+ apr_pool_t *dir_pool = svn_pool_create(parent_baton ? parent_baton->pool
+ : eb->pool);
+ struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
+
+ db->parent_baton = parent_baton;
+
+ /* Allocate 1 string for using as 3 strings */
+ db->local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
+ db->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, db->local_abspath);
+ db->name = svn_dirent_basename(db->relpath, NULL);
+
+ db->eb = eb;
+ db->added = added;
+ db->depth = depth;
+ db->pool = dir_pool;
+ db->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t));
+ db->compared = apr_hash_make(dir_pool);
+
+ if (parent_baton != NULL)
+ {
+ parent_baton->users++;
+ }
+
+ db->users = 1;
+
+ return db;
+}
+
+/* Create a new file baton. PATH is the file path, including
+ * anchor_path. ADDED is set if this file is being added rather than
+ * replaced. PARENT_BATON is the baton of the parent directory.
+ * The directory and its parent may or may not exist in the working copy.
+ */
+static struct file_baton_t *
+make_file_baton(const char *path,
+ svn_boolean_t added,
+ struct dir_baton_t *parent_baton,
+ apr_pool_t *result_pool)
+{
+ apr_pool_t *file_pool = svn_pool_create(result_pool);
+ struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
+ struct edit_baton_t *eb = parent_baton->eb;
+
+ fb->eb = eb;
+ fb->parent_baton = parent_baton;
+ fb->parent_baton->users++;
+
+ /* Allocate 1 string for using as 3 strings */
+ fb->local_abspath = svn_dirent_join(eb->anchor_abspath, path, file_pool);
+ fb->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, fb->local_abspath);
+ fb->name = svn_dirent_basename(fb->relpath, NULL);
+
+ fb->added = added;
+ fb->pool = file_pool;
+ fb->propchanges = apr_array_make(file_pool, 8, sizeof(svn_prop_t));
+
+ return fb;
+}
+
+/* Destroy DB when there are no more registered users */
+static svn_error_t *
+maybe_done(struct dir_baton_t *db)
+{
+ db->users--;
+
+ if (!db->users)
+ {
+ struct dir_baton_t *pb = db->parent_baton;
+
+ svn_pool_clear(db->pool);
+
+ if (pb != NULL)
+ SVN_ERR(maybe_done(pb));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Standard check to see if a node is represented in the local working copy */
+#define NOT_PRESENT(status) \
+ ((status) == svn_wc__db_status_not_present \
+ || (status) == svn_wc__db_status_excluded \
+ || (status) == svn_wc__db_status_server_excluded)
+
+svn_error_t *
+svn_wc__diff_base_working_diff(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *changelist_hash,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_dir_baton,
+ svn_boolean_t diff_pristine,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ void *file_baton = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_wc__db_status_t status;
+ svn_revnum_t db_revision;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+ svn_boolean_t files_same = FALSE;
+ svn_wc__db_status_t base_status;
+ const svn_checksum_t *working_checksum;
+ const svn_checksum_t *checksum;
+ svn_filesize_t recorded_size;
+ apr_time_t recorded_time;
+ const char *pristine_file;
+ const char *local_file;
+ svn_diff_source_t *left_src;
+ svn_diff_source_t *right_src;
+ apr_hash_t *base_props;
+ apr_hash_t *local_props;
+ apr_array_header_t *prop_changes;
+ const char *changelist;
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, &db_revision, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, &working_checksum, NULL,
+ NULL, NULL, NULL, NULL, NULL, &recorded_size,
+ &recorded_time, &changelist, NULL, NULL,
+ &had_props, &props_mod, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool));
+ checksum = working_checksum;
+
+ assert(status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_added
+ || (status == svn_wc__db_status_deleted && diff_pristine));
+
+ /* If the item is not a member of a specified changelist (and there are
+ some specified changelists), skip it. */
+ if (changelist_hash && !svn_hash_gets(changelist_hash, changelist))
+ return SVN_NO_ERROR;
+
+
+ if (status != svn_wc__db_status_normal)
+ {
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &db_revision,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, &checksum, NULL, NULL, &had_props,
+ NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ recorded_size = SVN_INVALID_FILESIZE;
+ recorded_time = 0;
+ props_mod = TRUE; /* Requires compare */
+ }
+ else if (diff_pristine)
+ files_same = TRUE;
+ else
+ {
+ const svn_io_dirent2_t *dirent;
+
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath,
+ FALSE /* verify truename */,
+ TRUE /* ingore_enoent */,
+ scratch_pool, scratch_pool));
+
+ if (dirent->kind == svn_node_file
+ && dirent->filesize == recorded_size
+ && dirent->mtime == recorded_time)
+ {
+ files_same = TRUE;
+ }
+ }
+
+ if (files_same && !props_mod)
+ return SVN_NO_ERROR; /* Cheap exit */
+
+ assert(checksum);
+
+ if (!SVN_IS_VALID_REVNUM(revision))
+ revision = db_revision;
+
+ left_src = svn_diff__source_create(revision, scratch_pool);
+ right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
+
+ SVN_ERR(processor->file_opened(&file_baton, &skip, relpath,
+ left_src,
+ right_src,
+ NULL /* copyfrom_src */,
+ processor_dir_baton,
+ processor,
+ scratch_pool, scratch_pool));
+
+ if (skip)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file,
+ db, local_abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ if (diff_pristine)
+ SVN_ERR(svn_wc__db_pristine_get_path(&local_file,
+ db, local_abspath,
+ working_checksum,
+ scratch_pool, scratch_pool));
+ else if (! (had_props || props_mod))
+ local_file = local_abspath;
+ else if (files_same)
+ local_file = pristine_file;
+ else
+ SVN_ERR(svn_wc__internal_translated_file(
+ &local_file, local_abspath,
+ db, local_abspath,
+ SVN_WC_TRANSLATE_TO_NF
+ | SVN_WC_TRANSLATE_USE_GLOBAL_TMP,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ if (! files_same)
+ SVN_ERR(svn_io_files_contents_same_p(&files_same, local_file,
+ pristine_file, scratch_pool));
+
+ if (had_props)
+ SVN_ERR(svn_wc__db_base_get_props(&base_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ base_props = apr_hash_make(scratch_pool);
+
+ if (status == svn_wc__db_status_normal && (diff_pristine || !props_mod))
+ local_props = base_props;
+ else if (diff_pristine)
+ SVN_ERR(svn_wc__db_read_pristine_props(&local_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__db_read_props(&local_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, local_props, base_props, scratch_pool));
+
+ if (prop_changes->nelts || !files_same)
+ {
+ SVN_ERR(processor->file_changed(relpath,
+ left_src,
+ right_src,
+ pristine_file,
+ local_file,
+ base_props,
+ local_props,
+ ! files_same,
+ prop_changes,
+ file_baton,
+ processor,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(processor->file_closed(relpath,
+ left_src,
+ right_src,
+ file_baton,
+ processor,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ensure_local_info(struct dir_baton_t *db,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *conflicts;
+
+ if (db->local_info)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_read_children_info(&db->local_info, &conflicts,
+ db->eb->db, db->local_abspath,
+ db->pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Called when the directory is closed to compare any elements that have
+ * not yet been compared. This identifies local, working copy only
+ * changes. At this stage we are dealing with files/directories that do
+ * exist in the working copy.
+ *
+ * DIR_BATON is the baton for the directory.
+ */
+static svn_error_t *
+walk_local_nodes_diff(struct edit_baton_t *eb,
+ const char *local_abspath,
+ const char *path,
+ svn_depth_t depth,
+ apr_hash_t *compared,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = eb->db;
+ svn_boolean_t in_anchor_not_target;
+ apr_pool_t *iterpool;
+ void *dir_baton = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_boolean_t skip_children = FALSE;
+ svn_revnum_t revision;
+ svn_boolean_t props_mod;
+ svn_diff_source_t *left_src;
+ svn_diff_source_t *right_src;
+
+ /* Everything we do below is useless if we are comparing to BASE. */
+ if (eb->diff_pristine)
+ return SVN_NO_ERROR;
+
+ /* Determine if this is the anchor directory if the anchor is different
+ to the target. When the target is a file, the anchor is the parent
+ directory and if this is that directory the non-target entries must be
+ skipped. */
+ in_anchor_not_target = ((*path == '\0') && (*eb->target != '\0'));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, &revision, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, &props_mod, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ left_src = svn_diff__source_create(revision, scratch_pool);
+ right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
+
+ if (compared)
+ {
+ dir_baton = parent_baton;
+ skip = TRUE;
+ }
+ else if (!in_anchor_not_target)
+ SVN_ERR(eb->processor->dir_opened(&dir_baton, &skip, &skip_children,
+ path,
+ left_src,
+ right_src,
+ NULL /* copyfrom_src */,
+ parent_baton,
+ eb->processor,
+ scratch_pool, scratch_pool));
+
+
+ if (!skip_children && depth != svn_depth_empty)
+ {
+ apr_hash_t *nodes;
+ apr_hash_t *conflicts;
+ apr_array_header_t *children;
+ svn_depth_t depth_below_here = depth;
+ svn_boolean_t diff_files;
+ svn_boolean_t diff_dirs;
+ int i;
+
+ if (depth_below_here == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ diff_files = (depth == svn_depth_unknown
+ || depth >= svn_depth_files);
+ diff_dirs = (depth == svn_depth_unknown
+ || depth >= svn_depth_immediates);
+
+ SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
+ db, local_abspath,
+ scratch_pool, iterpool));
+
+ children = svn_sort__hash(nodes, svn_sort_compare_items_lexically,
+ scratch_pool);
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(children, i,
+ svn_sort__item_t);
+ const char *name = item->key;
+ struct svn_wc__db_info_t *info = item->value;
+
+ const char *child_abspath;
+ const char *child_relpath;
+ svn_boolean_t repos_only;
+ svn_boolean_t local_only;
+ svn_node_kind_t base_kind;
+
+ if (eb->cancel_func)
+ SVN_ERR(eb->cancel_func(eb->cancel_baton));
+
+ /* In the anchor directory, if the anchor is not the target then all
+ entries other than the target should not be diff'd. Running diff
+ on one file in a directory should not diff other files in that
+ directory. */
+ if (in_anchor_not_target && strcmp(eb->target, name))
+ continue;
+
+ if (compared && svn_hash_gets(compared, name))
+ continue;
+
+ if (NOT_PRESENT(info->status))
+ continue;
+
+ assert(info->status == svn_wc__db_status_normal
+ || info->status == svn_wc__db_status_added
+ || info->status == svn_wc__db_status_deleted);
+
+ svn_pool_clear(iterpool);
+ child_abspath = svn_dirent_join(local_abspath, name, iterpool);
+ child_relpath = svn_relpath_join(path, name, iterpool);
+
+ repos_only = FALSE;
+ local_only = FALSE;
+
+ if (!info->have_base)
+ {
+ local_only = TRUE; /* Only report additions */
+ }
+ else if (info->status == svn_wc__db_status_normal)
+ {
+ /* Simple diff */
+ base_kind = info->kind;
+ }
+ else if (info->status == svn_wc__db_status_deleted
+ && (!eb->diff_pristine || !info->have_more_work))
+ {
+ svn_wc__db_status_t base_status;
+ repos_only = TRUE;
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, child_abspath,
+ iterpool, iterpool));
+
+ if (NOT_PRESENT(base_status))
+ continue;
+ }
+ else
+ {
+ /* working status is either added or deleted */
+ svn_wc__db_status_t base_status;
+
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, child_abspath,
+ iterpool, iterpool));
+
+ if (NOT_PRESENT(base_status))
+ local_only = TRUE;
+ else if (base_kind != info->kind || !eb->ignore_ancestry)
+ {
+ repos_only = TRUE;
+ local_only = TRUE;
+ }
+ }
+
+ if (eb->local_before_remote && local_only)
+ {
+ if (info->kind == svn_node_file && diff_files)
+ SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
+ child_relpath,
+ eb->processor, dir_baton,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func,
+ eb->cancel_baton,
+ iterpool));
+ else if (info->kind == svn_node_dir && diff_dirs)
+ SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
+ child_relpath,
+ depth_below_here,
+ eb->processor, dir_baton,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func,
+ eb->cancel_baton,
+ iterpool));
+ }
+
+ if (repos_only)
+ {
+ /* Report repository form deleted */
+ if (base_kind == svn_node_file && diff_files)
+ SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath,
+ child_relpath, eb->revnum,
+ eb->processor, dir_baton,
+ iterpool));
+ else if (base_kind == svn_node_dir && diff_dirs)
+ SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath,
+ child_relpath, eb->revnum,
+ depth_below_here,
+ eb->processor, dir_baton,
+ eb->cancel_func,
+ eb->cancel_baton,
+ iterpool));
+ }
+ else if (!local_only) /* Not local only nor remote only */
+ {
+ /* Diff base against actual */
+ if (info->kind == svn_node_file && diff_files)
+ {
+ if (info->status != svn_wc__db_status_normal
+ || !eb->diff_pristine)
+ {
+ SVN_ERR(svn_wc__diff_base_working_diff(
+ db, child_abspath,
+ child_relpath,
+ eb->revnum,
+ eb->changelist_hash,
+ eb->processor, dir_baton,
+ eb->diff_pristine,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+ }
+ }
+ else if (info->kind == svn_node_dir && diff_dirs)
+ SVN_ERR(walk_local_nodes_diff(eb, child_abspath,
+ child_relpath,
+ depth_below_here,
+ NULL /* compared */,
+ dir_baton,
+ scratch_pool));
+ }
+
+ if (!eb->local_before_remote && local_only)
+ {
+ if (info->kind == svn_node_file && diff_files)
+ SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
+ child_relpath,
+ eb->processor, dir_baton,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func,
+ eb->cancel_baton,
+ iterpool));
+ else if (info->kind == svn_node_dir && diff_dirs)
+ SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
+ child_relpath, depth_below_here,
+ eb->processor, dir_baton,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func,
+ eb->cancel_baton,
+ iterpool));
+ }
+ }
+ }
+
+ if (compared)
+ return SVN_NO_ERROR;
+
+ /* Check for local property mods on this directory, if we haven't
+ already reported them and we aren't changelist-filted.
+ ### it should be noted that we do not currently allow directories
+ ### to be part of changelists, so if a changelist is provided, the
+ ### changelist check will always fail. */
+ if (! skip
+ && ! eb->changelist_hash
+ && ! in_anchor_not_target
+ && props_mod)
+ {
+ apr_array_header_t *propchanges;
+ apr_hash_t *left_props;
+ apr_hash_t *right_props;
+
+ SVN_ERR(svn_wc__internal_propdiff(&propchanges, &left_props,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ right_props = svn_prop__patch(left_props, propchanges, scratch_pool);
+
+ SVN_ERR(eb->processor->dir_changed(path,
+ left_src,
+ right_src,
+ left_props,
+ right_props,
+ propchanges,
+ dir_baton,
+ eb->processor,
+ scratch_pool));
+ }
+ else if (! skip)
+ SVN_ERR(eb->processor->dir_closed(path,
+ left_src,
+ right_src,
+ dir_baton,
+ eb->processor,
+ scratch_pool));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__diff_local_only_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ apr_hash_t *changelist_hash,
+ svn_boolean_t diff_pristine,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_diff_source_t *right_src;
+ svn_diff_source_t *copyfrom_src = NULL;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const svn_checksum_t *checksum;
+ const char *original_repos_relpath;
+ svn_revnum_t original_revision;
+ const char *changelist;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+ apr_hash_t *pristine_props;
+ apr_hash_t *right_props = NULL;
+ const char *pristine_file;
+ const char *translated_file;
+ svn_revnum_t revision;
+ void *file_baton = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_boolean_t file_mod = TRUE;
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, &checksum, NULL,
+ &original_repos_relpath, NULL, NULL,
+ &original_revision, NULL, NULL, NULL,
+ &changelist, NULL, NULL, &had_props,
+ &props_mod, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ assert(kind == svn_node_file
+ && (status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_added
+ || (status == svn_wc__db_status_deleted && diff_pristine)));
+
+
+ if (changelist && changelist_hash
+ && !svn_hash_gets(changelist_hash, changelist))
+ return SVN_NO_ERROR;
+
+ if (status == svn_wc__db_status_deleted)
+ {
+ assert(diff_pristine);
+
+ SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL,
+ NULL, &checksum, NULL, &had_props,
+ &pristine_props,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ props_mod = FALSE;
+ }
+ else if (!had_props)
+ pristine_props = apr_hash_make(scratch_pool);
+ else
+ SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (original_repos_relpath)
+ {
+ copyfrom_src = svn_diff__source_create(original_revision, scratch_pool);
+ copyfrom_src->repos_relpath = original_repos_relpath;
+ }
+
+ if (props_mod || !SVN_IS_VALID_REVNUM(revision))
+ right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
+ else
+ {
+ if (diff_pristine)
+ file_mod = FALSE;
+ else
+ SVN_ERR(svn_wc__internal_file_modified_p(&file_mod, db, local_abspath,
+ FALSE, scratch_pool));
+
+ if (!file_mod)
+ right_src = svn_diff__source_create(revision, scratch_pool);
+ else
+ right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
+ }
+
+ SVN_ERR(processor->file_opened(&file_baton, &skip,
+ relpath,
+ NULL /* left_source */,
+ right_src,
+ copyfrom_src,
+ processor_parent_baton,
+ processor,
+ scratch_pool, scratch_pool));
+
+ if (skip)
+ return SVN_NO_ERROR;
+
+ if (props_mod && !diff_pristine)
+ SVN_ERR(svn_wc__db_read_props(&right_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ right_props = svn_prop_hash_dup(pristine_props, scratch_pool);
+
+ if (checksum)
+ SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, db, local_abspath,
+ checksum, scratch_pool, scratch_pool));
+ else
+ pristine_file = NULL;
+
+ if (diff_pristine)
+ {
+ translated_file = pristine_file; /* No translation needed */
+ }
+ else
+ {
+ SVN_ERR(svn_wc__internal_translated_file(
+ &translated_file, local_abspath, db, local_abspath,
+ SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(processor->file_added(relpath,
+ copyfrom_src,
+ right_src,
+ copyfrom_src
+ ? pristine_file
+ : NULL,
+ translated_file,
+ copyfrom_src
+ ? pristine_props
+ : NULL,
+ right_props,
+ file_baton,
+ processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__diff_local_only_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_depth_t depth,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ apr_hash_t *changelist_hash,
+ svn_boolean_t diff_pristine,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *children;
+ int i;
+ apr_pool_t *iterpool;
+ void *pdb = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_boolean_t skip_children = FALSE;
+ svn_diff_source_t *right_src = svn_diff__source_create(SVN_INVALID_REVNUM,
+ scratch_pool);
+ svn_depth_t depth_below_here = depth;
+ apr_hash_t *nodes;
+ apr_hash_t *conflicts;
+
+ /* Report the addition of the directory's contents. */
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(processor->dir_opened(&pdb, &skip, &skip_children,
+ relpath,
+ NULL,
+ right_src,
+ NULL /* copyfrom_src */,
+ processor_parent_baton,
+ processor,
+ scratch_pool, iterpool));
+
+ SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db, local_abspath,
+ scratch_pool, iterpool));
+
+ if (depth_below_here == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ children = svn_sort__hash(nodes, svn_sort_compare_items_lexically,
+ scratch_pool);
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, svn_sort__item_t);
+ const char *name = item->key;
+ struct svn_wc__db_info_t *info = item->value;
+ const char *child_abspath;
+ const char *child_relpath;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ child_abspath = svn_dirent_join(local_abspath, name, iterpool);
+
+ if (NOT_PRESENT(info->status))
+ {
+ continue;
+ }
+
+ /* If comparing against WORKING, skip entries that are
+ schedule-deleted - they don't really exist. */
+ if (!diff_pristine && info->status == svn_wc__db_status_deleted)
+ continue;
+
+ child_relpath = svn_relpath_join(relpath, name, iterpool);
+
+ switch (info->kind)
+ {
+ case svn_node_file:
+ case svn_node_symlink:
+ SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
+ child_relpath,
+ processor, pdb,
+ changelist_hash,
+ diff_pristine,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ break;
+
+ case svn_node_dir:
+ if (depth > svn_depth_files || depth == svn_depth_unknown)
+ {
+ SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
+ child_relpath, depth_below_here,
+ processor, pdb,
+ changelist_hash,
+ diff_pristine,
+ cancel_func, cancel_baton,
+ iterpool));
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (!skip)
+ {
+ apr_hash_t *right_props;
+ if (diff_pristine)
+ SVN_ERR(svn_wc__db_read_pristine_props(&right_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__get_actual_props(&right_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(processor->dir_added(relpath,
+ NULL /* copyfrom_src */,
+ right_src,
+ NULL,
+ right_props,
+ pdb,
+ processor,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Reports local changes. */
+static svn_error_t *
+handle_local_only(struct dir_baton_t *pb,
+ const char *name,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton_t *eb = pb->eb;
+ const struct svn_wc__db_info_t *info;
+ svn_boolean_t repos_delete = (pb->deletes
+ && svn_hash_gets(pb->deletes, name));
+
+ assert(!strchr(name, '/'));
+ assert(!pb->added || eb->ignore_ancestry);
+
+ if (pb->skip_children)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(ensure_local_info(pb, scratch_pool));
+
+ info = svn_hash_gets(pb->local_info, name);
+
+ if (info == NULL || NOT_PRESENT(info->status))
+ return SVN_NO_ERROR;
+
+ switch (info->status)
+ {
+ case svn_wc__db_status_incomplete:
+ return SVN_NO_ERROR; /* Not local only */
+
+ case svn_wc__db_status_normal:
+ if (!repos_delete)
+ return SVN_NO_ERROR; /* Local and remote */
+ svn_hash_sets(pb->deletes, name, NULL);
+ break;
+
+ case svn_wc__db_status_deleted:
+ if (!(eb->diff_pristine && repos_delete))
+ return SVN_NO_ERROR;
+ break;
+
+ case svn_wc__db_status_added:
+ default:
+ break;
+ }
+
+ if (info->kind == svn_node_dir)
+ {
+ svn_depth_t depth ;
+
+ if (pb->depth == svn_depth_infinity || pb->depth == svn_depth_unknown)
+ depth = pb->depth;
+ else
+ depth = svn_depth_empty;
+
+ SVN_ERR(svn_wc__diff_local_only_dir(
+ eb->db,
+ svn_dirent_join(pb->local_abspath, name, scratch_pool),
+ svn_relpath_join(pb->relpath, name, scratch_pool),
+ repos_delete ? svn_depth_infinity : depth,
+ eb->processor, pb->pdb,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+ }
+ else
+ SVN_ERR(svn_wc__diff_local_only_file(
+ eb->db,
+ svn_dirent_join(pb->local_abspath, name, scratch_pool),
+ svn_relpath_join(pb->relpath, name, scratch_pool),
+ eb->processor, pb->pdb,
+ eb->changelist_hash,
+ eb->diff_pristine,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Reports a file LOCAL_ABSPATH in BASE as deleted */
+svn_error_t *
+svn_wc__diff_base_only_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_revnum_t revision,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const svn_checksum_t *checksum;
+ apr_hash_t *props;
+ void *file_baton = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_diff_source_t *left_src;
+ const char *pristine_file;
+
+ SVN_ERR(svn_wc__db_base_get_info(&status, &kind,
+ SVN_IS_VALID_REVNUM(revision)
+ ? NULL : &revision,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &checksum, NULL, NULL, NULL, &props, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(status == svn_wc__db_status_normal
+ && kind == svn_node_file
+ && checksum);
+
+ left_src = svn_diff__source_create(revision, scratch_pool);
+
+ SVN_ERR(processor->file_opened(&file_baton, &skip,
+ relpath,
+ left_src,
+ NULL /* right_src */,
+ NULL /* copyfrom_source */,
+ processor_parent_baton,
+ processor,
+ scratch_pool, scratch_pool));
+
+ if (skip)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file,
+ db, local_abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(processor->file_deleted(relpath,
+ left_src,
+ pristine_file,
+ props,
+ file_baton,
+ processor,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__diff_base_only_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *relpath,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_diff_tree_processor_t *processor,
+ void *processor_parent_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ void *dir_baton = NULL;
+ svn_boolean_t skip = FALSE;
+ svn_boolean_t skip_children = FALSE;
+ svn_diff_source_t *left_src;
+ svn_revnum_t report_rev = revision;
+
+ if (!SVN_IS_VALID_REVNUM(report_rev))
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &report_rev, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ left_src = svn_diff__source_create(report_rev, scratch_pool);
+
+ SVN_ERR(processor->dir_opened(&dir_baton, &skip, &skip_children,
+ relpath,
+ left_src,
+ NULL /* right_src */,
+ NULL /* copyfrom_src */,
+ processor_parent_baton,
+ processor,
+ scratch_pool, scratch_pool));
+
+ if (!skip_children && (depth == svn_depth_unknown || depth > svn_depth_empty))
+ {
+ apr_hash_t *nodes;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *children;
+ int i;
+
+ SVN_ERR(svn_wc__db_base_get_children_info(&nodes, db, local_abspath,
+ scratch_pool, iterpool));
+
+ children = svn_sort__hash(nodes, svn_sort_compare_items_lexically,
+ scratch_pool);
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(children, i,
+ svn_sort__item_t);
+ const char *name = item->key;
+ struct svn_wc__db_base_info_t *info = item->value;
+ const char *child_abspath;
+ const char *child_relpath;
+
+ if (info->status != svn_wc__db_status_normal)
+ continue;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ svn_pool_clear(iterpool);
+
+ child_abspath = svn_dirent_join(local_abspath, name, iterpool);
+ child_relpath = svn_relpath_join(relpath, name, iterpool);
+
+ switch (info->kind)
+ {
+ case svn_node_file:
+ case svn_node_symlink:
+ SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath,
+ child_relpath,
+ revision,
+ processor, dir_baton,
+ iterpool));
+ break;
+ case svn_node_dir:
+ if (depth > svn_depth_files || depth == svn_depth_unknown)
+ {
+ svn_depth_t depth_below_here = depth;
+
+ if (depth_below_here == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath,
+ child_relpath,
+ revision,
+ depth_below_here,
+ processor, dir_baton,
+ cancel_func,
+ cancel_baton,
+ iterpool));
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ if (!skip)
+ {
+ apr_hash_t *props;
+ SVN_ERR(svn_wc__db_base_get_props(&props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(processor->dir_deleted(relpath,
+ left_src,
+ props,
+ dir_baton,
+ processor,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton_t *eb = edit_baton;
+ eb->revnum = target_revision;
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. The root of the comparison hierarchy */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **root_baton)
+{
+ struct edit_baton_t *eb = edit_baton;
+ struct dir_baton_t *db;
+
+ eb->root_opened = TRUE;
+ db = make_dir_baton("", NULL, eb, FALSE, eb->depth, dir_pool);
+ *root_baton = db;
+
+ if (eb->target[0] == '\0')
+ {
+ db->left_src = svn_diff__source_create(eb->revnum, db->pool);
+ db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool);
+
+ SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip,
+ &db->skip_children,
+ "",
+ db->left_src,
+ db->right_src,
+ NULL /* copyfrom_source */,
+ NULL /* parent_baton */,
+ eb->processor,
+ db->pool, db->pool));
+ }
+ else
+ db->skip = TRUE; /* Skip this, but not the children */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t base_revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton_t *pb = parent_baton;
+ const char *name = svn_dirent_basename(path, pb->pool);
+
+ if (!pb->deletes)
+ pb->deletes = apr_hash_make(pb->pool);
+
+ svn_hash_sets(pb->deletes, name, "");
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *dir_pool,
+ void **child_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ struct dir_baton_t *db;
+ svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates)
+ ? svn_depth_empty : pb->depth;
+
+ db = make_dir_baton(path, pb, pb->eb, TRUE, subdir_depth,
+ dir_pool);
+ *child_baton = db;
+
+ if (pb->repos_only || !eb->ignore_ancestry)
+ db->repos_only = TRUE;
+ else
+ {
+ struct svn_wc__db_info_t *info;
+ SVN_ERR(ensure_local_info(pb, dir_pool));
+
+ info = svn_hash_gets(pb->local_info, db->name);
+
+ if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status))
+ db->repos_only = TRUE;
+
+ if (!db->repos_only && info->status != svn_wc__db_status_added)
+ db->repos_only = TRUE;
+
+ if (!db->repos_only)
+ {
+ db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool);
+ db->ignoring_ancestry = TRUE;
+
+ svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), "");
+ }
+ }
+
+ db->left_src = svn_diff__source_create(eb->revnum, db->pool);
+
+ if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry)
+ SVN_ERR(handle_local_only(pb, db->name, dir_pool));
+
+ SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children,
+ db->relpath,
+ db->left_src,
+ db->right_src,
+ NULL /* copyfrom src */,
+ pb->pdb,
+ eb->processor,
+ db->pool, db->pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **child_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ struct dir_baton_t *db;
+ svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates)
+ ? svn_depth_empty : pb->depth;
+
+ /* Allocate path from the parent pool since the memory is used in the
+ parent's compared hash */
+ db = make_dir_baton(path, pb, pb->eb, FALSE, subdir_depth, dir_pool);
+ *child_baton = db;
+
+ if (pb->repos_only)
+ db->repos_only = TRUE;
+ else
+ {
+ struct svn_wc__db_info_t *info;
+ SVN_ERR(ensure_local_info(pb, dir_pool));
+
+ info = svn_hash_gets(pb->local_info, db->name);
+
+ if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status))
+ db->repos_only = TRUE;
+
+ if (!db->repos_only)
+ switch (info->status)
+ {
+ case svn_wc__db_status_normal:
+ break;
+ case svn_wc__db_status_deleted:
+ db->repos_only = TRUE;
+
+ if (!info->have_more_work)
+ svn_hash_sets(pb->compared,
+ apr_pstrdup(pb->pool, db->name), "");
+ break;
+ case svn_wc__db_status_added:
+ if (eb->ignore_ancestry)
+ db->ignoring_ancestry = TRUE;
+ else
+ db->repos_only = TRUE;
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ if (!db->repos_only)
+ {
+ db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool);
+ svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), "");
+ }
+ }
+
+ db->left_src = svn_diff__source_create(eb->revnum, db->pool);
+
+ if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry)
+ SVN_ERR(handle_local_only(pb, db->name, dir_pool));
+
+ SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children,
+ db->relpath,
+ db->left_src,
+ db->right_src,
+ NULL /* copyfrom src */,
+ pb->pdb,
+ eb->processor,
+ db->pool, db->pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. When a directory is closed, all the
+ * directory elements that have been added or replaced will already have been
+ * diff'd. However there may be other elements in the working copy
+ * that have not yet been considered. */
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton_t *db = dir_baton;
+ struct dir_baton_t *pb = db->parent_baton;
+ struct edit_baton_t *eb = db->eb;
+ apr_pool_t *scratch_pool = db->pool;
+ svn_boolean_t reported_closed = FALSE;
+
+ if (!db->skip_children && db->deletes && apr_hash_count(db->deletes))
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *children;
+ int i;
+ children = svn_sort__hash(db->deletes, svn_sort_compare_items_lexically,
+ scratch_pool);
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ svn_sort__item_t *item = &APR_ARRAY_IDX(children, i,
+ svn_sort__item_t);
+ const char *name = item->key;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(handle_local_only(db, name, iterpool));
+
+ svn_hash_sets(db->compared, name, "");
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ /* Report local modifications for this directory. Skip added
+ directories since they can only contain added elements, all of
+ which have already been diff'd. */
+ if (!db->repos_only && !db->skip_children)
+ {
+ SVN_ERR(walk_local_nodes_diff(eb,
+ db->local_abspath,
+ db->relpath,
+ db->depth,
+ db->compared,
+ db->pdb,
+ scratch_pool));
+ }
+
+ /* Report the property changes on the directory itself, if necessary. */
+ if (db->skip)
+ {
+ /* Diff processor requested no directory details */
+ }
+ else if (db->propchanges->nelts > 0 || db->repos_only)
+ {
+ apr_hash_t *repos_props;
+
+ if (db->added)
+ {
+ repos_props = apr_hash_make(scratch_pool);
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_base_get_props(&repos_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ /* Add received property changes and entry props */
+ if (db->propchanges->nelts)
+ repos_props = svn_prop__patch(repos_props, db->propchanges,
+ scratch_pool);
+
+ if (db->repos_only)
+ {
+ SVN_ERR(eb->processor->dir_deleted(db->relpath,
+ db->left_src,
+ repos_props,
+ db->pdb,
+ eb->processor,
+ scratch_pool));
+ reported_closed = TRUE;
+ }
+ else
+ {
+ apr_hash_t *local_props;
+ apr_array_header_t *prop_changes;
+
+ if (eb->diff_pristine)
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ &local_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__db_read_props(&local_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props,
+ scratch_pool));
+
+ /* ### as a good diff processor we should now only report changes
+ if there are non-entry changes, but for now we stick to
+ compatibility */
+
+ if (prop_changes->nelts)
+ {
+ SVN_ERR(eb->processor->dir_changed(db->relpath,
+ db->left_src,
+ db->right_src,
+ repos_props,
+ local_props,
+ prop_changes,
+ db->pdb,
+ eb->processor,
+ scratch_pool));
+ reported_closed = TRUE;
+ }
+ }
+ }
+
+ /* Mark this directory as compared in the parent directory's baton,
+ unless this is the root of the comparison. */
+ if (!reported_closed && !db->skip)
+ SVN_ERR(eb->processor->dir_closed(db->relpath,
+ db->left_src,
+ db->right_src,
+ db->pdb,
+ eb->processor,
+ scratch_pool));
+
+ if (pb && !eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry)
+ SVN_ERR(handle_local_only(pb, db->name, scratch_pool));
+
+ SVN_ERR(maybe_done(db)); /* destroys scratch_pool */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ struct file_baton_t *fb;
+
+ fb = make_file_baton(path, TRUE, pb, file_pool);
+ *file_baton = fb;
+
+ if (pb->skip_children)
+ {
+ fb->skip = TRUE;
+ return SVN_NO_ERROR;
+ }
+ else if (pb->repos_only || !eb->ignore_ancestry)
+ fb->repos_only = TRUE;
+ else
+ {
+ struct svn_wc__db_info_t *info;
+ SVN_ERR(ensure_local_info(pb, file_pool));
+
+ info = svn_hash_gets(pb->local_info, fb->name);
+
+ if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status))
+ fb->repos_only = TRUE;
+
+ if (!fb->repos_only && info->status != svn_wc__db_status_added)
+ fb->repos_only = TRUE;
+
+ if (!fb->repos_only)
+ {
+ /* Add this path to the parent directory's list of elements that
+ have been compared. */
+ fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool);
+ fb->ignoring_ancestry = TRUE;
+
+ svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), "");
+ }
+ }
+
+ fb->left_src = svn_diff__source_create(eb->revnum, fb->pool);
+
+ SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip,
+ fb->relpath,
+ fb->left_src,
+ fb->right_src,
+ NULL /* copyfrom src */,
+ pb->pdb,
+ eb->processor,
+ fb->pool, fb->pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ struct file_baton_t *fb;
+
+ fb = make_file_baton(path, FALSE, pb, file_pool);
+ *file_baton = fb;
+
+ if (pb->skip_children)
+ fb->skip = TRUE;
+ else if (pb->repos_only)
+ fb->repos_only = TRUE;
+ else
+ {
+ struct svn_wc__db_info_t *info;
+ SVN_ERR(ensure_local_info(pb, file_pool));
+
+ info = svn_hash_gets(pb->local_info, fb->name);
+
+ if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status))
+ fb->repos_only = TRUE;
+
+ if (!fb->repos_only)
+ switch (info->status)
+ {
+ case svn_wc__db_status_normal:
+ break;
+ case svn_wc__db_status_deleted:
+ fb->repos_only = TRUE;
+ if (!info->have_more_work)
+ svn_hash_sets(pb->compared,
+ apr_pstrdup(pb->pool, fb->name), "");
+ break;
+ case svn_wc__db_status_added:
+ if (eb->ignore_ancestry)
+ fb->ignoring_ancestry = TRUE;
+ else
+ fb->repos_only = TRUE;
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ if (!fb->repos_only)
+ {
+ /* Add this path to the parent directory's list of elements that
+ have been compared. */
+ fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool);
+ svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), "");
+ }
+ }
+
+ fb->left_src = svn_diff__source_create(eb->revnum, fb->pool);
+
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &fb->base_checksum, NULL,
+ NULL, NULL, &fb->base_props, NULL,
+ eb->db, fb->local_abspath,
+ fb->pool, fb->pool));
+
+ SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip,
+ fb->relpath,
+ fb->left_src,
+ fb->right_src,
+ NULL /* copyfrom src */,
+ pb->pdb,
+ eb->processor,
+ fb->pool, fb->pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum_hex,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton_t *fb = file_baton;
+ struct edit_baton_t *eb = fb->eb;
+ svn_stream_t *source;
+ svn_stream_t *temp_stream;
+ svn_checksum_t *repos_checksum = NULL;
+
+ if (fb->skip)
+ {
+ *handler = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (base_checksum_hex && fb->base_checksum)
+ {
+ const svn_checksum_t *base_md5;
+ SVN_ERR(svn_checksum_parse_hex(&repos_checksum, svn_checksum_md5,
+ base_checksum_hex, pool));
+
+ SVN_ERR(svn_wc__db_pristine_get_md5(&base_md5,
+ eb->db, eb->anchor_abspath,
+ fb->base_checksum,
+ pool, pool));
+
+ if (! svn_checksum_match(repos_checksum, base_md5))
+ {
+ /* ### I expect that there are some bad drivers out there
+ ### that used to give bad results. We could look in
+ ### working to see if the expected checksum matches and
+ ### then return the pristine of that... But that only moves
+ ### the problem */
+
+ /* If needed: compare checksum obtained via md5 of working.
+ And if they match set fb->base_checksum and fb->base_props */
+
+ return svn_checksum_mismatch_err(
+ base_md5,
+ repos_checksum,
+ pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(fb->local_abspath,
+ pool));
+ }
+
+ SVN_ERR(svn_wc__db_pristine_read(&source, NULL,
+ eb->db, fb->local_abspath,
+ fb->base_checksum,
+ pool, pool));
+ }
+ else if (fb->base_checksum)
+ {
+ SVN_ERR(svn_wc__db_pristine_read(&source, NULL,
+ eb->db, fb->local_abspath,
+ fb->base_checksum,
+ pool, pool));
+ }
+ else
+ source = svn_stream_empty(pool);
+
+ /* This is the file that will contain the pristine repository version. */
+ SVN_ERR(svn_stream_open_unique(&temp_stream, &fb->temp_file_path, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ fb->pool, fb->pool));
+
+ svn_txdelta_apply(source, temp_stream,
+ fb->result_digest,
+ fb->local_abspath /* error_info */,
+ fb->pool,
+ handler, handler_baton);
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. When the file is closed we have a temporary
+ * file containing a pristine version of the repository file. This can
+ * be compared against the working copy.
+ *
+ * Ignore TEXT_CHECKSUM.
+ */
+static svn_error_t *
+close_file(void *file_baton,
+ const char *expected_md5_digest,
+ apr_pool_t *pool)
+{
+ struct file_baton_t *fb = file_baton;
+ struct dir_baton_t *pb = fb->parent_baton;
+ struct edit_baton_t *eb = fb->eb;
+ apr_pool_t *scratch_pool = fb->pool;
+
+ /* The repository information; constructed from BASE + Changes */
+ const char *repos_file;
+ apr_hash_t *repos_props;
+
+ if (!fb->skip && expected_md5_digest != NULL)
+ {
+ svn_checksum_t *expected_checksum;
+ const svn_checksum_t *result_checksum;
+
+ if (fb->temp_file_path)
+ result_checksum = svn_checksum__from_digest_md5(fb->result_digest,
+ scratch_pool);
+ else
+ result_checksum = fb->base_checksum;
+
+ SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
+ expected_md5_digest, scratch_pool));
+
+ if (result_checksum->kind != svn_checksum_md5)
+ SVN_ERR(svn_wc__db_pristine_get_md5(&result_checksum,
+ eb->db, fb->local_abspath,
+ result_checksum,
+ scratch_pool, scratch_pool));
+
+ if (!svn_checksum_match(expected_checksum, result_checksum))
+ return svn_checksum_mismatch_err(
+ expected_checksum,
+ result_checksum,
+ pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(fb->local_abspath,
+ scratch_pool));
+ }
+
+ if (eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry)
+ SVN_ERR(handle_local_only(pb, fb->name, scratch_pool));
+
+ {
+ apr_hash_t *prop_base;
+
+ if (fb->added)
+ prop_base = apr_hash_make(scratch_pool);
+ else
+ prop_base = fb->base_props;
+
+ /* includes entry props */
+ repos_props = svn_prop__patch(prop_base, fb->propchanges, scratch_pool);
+
+ repos_file = fb->temp_file_path;
+ if (! repos_file)
+ {
+ assert(fb->base_checksum);
+ SVN_ERR(svn_wc__db_pristine_get_path(&repos_file,
+ eb->db, eb->anchor_abspath,
+ fb->base_checksum,
+ scratch_pool, scratch_pool));
+ }
+ }
+
+ if (fb->skip)
+ {
+ /* Diff processor requested skipping information */
+ }
+ else if (fb->repos_only)
+ {
+ SVN_ERR(eb->processor->file_deleted(fb->relpath,
+ fb->left_src,
+ fb->temp_file_path,
+ repos_props,
+ fb->pfb,
+ eb->processor,
+ scratch_pool));
+ }
+ else
+ {
+ /* Produce a diff of actual or pristine against repos */
+ apr_hash_t *local_props;
+ apr_array_header_t *prop_changes;
+ const char *localfile;
+
+ /* pb->local_info contains some information that might allow optimizing
+ this a bit */
+
+ if (eb->diff_pristine)
+ {
+ const svn_checksum_t *checksum;
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL,
+ NULL, &checksum, NULL, NULL,
+ &local_props,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+ assert(checksum);
+ SVN_ERR(svn_wc__db_pristine_get_path(&localfile,
+ eb->db, eb->anchor_abspath,
+ checksum,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_read_props(&local_props,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* a detranslated version of the working file */
+ SVN_ERR(svn_wc__internal_translated_file(
+ &localfile, fb->local_abspath, eb->db, fb->local_abspath,
+ SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props,
+ scratch_pool));
+
+
+ /* ### as a good diff processor we should now only report changes, and
+ report file_closed() in other cases */
+ SVN_ERR(eb->processor->file_changed(fb->relpath,
+ fb->left_src,
+ fb->right_src,
+ repos_file /* left file */,
+ localfile /* right file */,
+ repos_props /* left_props */,
+ local_props /* right props */,
+ TRUE /* ### file_modified */,
+ prop_changes,
+ fb->pfb,
+ eb->processor,
+ scratch_pool));
+ }
+
+ if (!eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry)
+ SVN_ERR(handle_local_only(pb, fb->name, scratch_pool));
+
+ svn_pool_destroy(fb->pool); /* destroys scratch_pool and fb */
+ SVN_ERR(maybe_done(pb));
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct file_baton_t *fb = file_baton;
+ svn_prop_t *propchange;
+ svn_prop_kind_t propkind;
+
+ propkind = svn_property_kind2(name);
+ if (propkind == svn_prop_wc_kind)
+ return SVN_NO_ERROR;
+ else if (propkind == svn_prop_regular_kind)
+ fb->has_propchange = TRUE;
+
+ propchange = apr_array_push(fb->propchanges);
+ propchange->name = apr_pstrdup(fb->pool, name);
+ propchange->value = value ? svn_string_dup(value, fb->pool) : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton_t *db = dir_baton;
+ svn_prop_t *propchange;
+ svn_prop_kind_t propkind;
+
+ propkind = svn_property_kind2(name);
+ if (propkind == svn_prop_wc_kind)
+ return SVN_NO_ERROR;
+ else if (propkind == svn_prop_regular_kind)
+ db->has_propchange = TRUE;
+
+ propchange = apr_array_push(db->propchanges);
+ propchange->name = apr_pstrdup(db->pool, name);
+ propchange->value = value ? svn_string_dup(value, db->pool) : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton_t *eb = edit_baton;
+
+ if (!eb->root_opened)
+ {
+ SVN_ERR(walk_local_nodes_diff(eb,
+ eb->anchor_abspath,
+ "",
+ eb->depth,
+ NULL /* compared */,
+ NULL /* No parent_baton */,
+ eb->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Public Interface */
+
+
+/* Create a diff editor and baton. */
+svn_error_t *
+svn_wc__get_diff_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ svn_boolean_t use_text_base,
+ svn_boolean_t reverse_order,
+ svn_boolean_t server_performs_filtering,
+ const apr_array_header_t *changelist_filter,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton_t *eb;
+ void *inner_baton;
+ svn_delta_editor_t *tree_editor;
+ const svn_delta_editor_t *inner_editor;
+ struct svn_wc__shim_fetch_baton_t *sfb;
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(result_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath));
+
+ /* --git implies --show-copies-as-adds */
+ if (use_git_diff_format)
+ show_copies_as_adds = TRUE;
+
+ SVN_ERR(make_edit_baton(&eb,
+ wc_ctx->db,
+ anchor_abspath, target,
+ callbacks, callback_baton,
+ depth, ignore_ancestry, show_copies_as_adds,
+ use_text_base, reverse_order, changelist_filter,
+ cancel_func, cancel_baton,
+ result_pool));
+
+ tree_editor = svn_delta_default_editor(eb->pool);
+
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->open_root = open_root;
+ tree_editor->delete_entry = delete_entry;
+ tree_editor->add_directory = add_directory;
+ tree_editor->open_directory = open_directory;
+ tree_editor->close_directory = close_directory;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->change_dir_prop = change_dir_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->close_edit = close_edit;
+
+ inner_editor = tree_editor;
+ inner_baton = eb;
+
+ if (!server_performs_filtering
+ && depth == svn_depth_unknown)
+ SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
+ &inner_baton,
+ wc_ctx->db,
+ anchor_abspath,
+ target,
+ inner_editor,
+ inner_baton,
+ result_pool));
+
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func,
+ cancel_baton,
+ inner_editor,
+ inner_baton,
+ editor,
+ edit_baton,
+ result_pool));
+
+ sfb = apr_palloc(result_pool, sizeof(*sfb));
+ sfb->db = wc_ctx->db;
+ sfb->base_abspath = eb->anchor_abspath;
+ sfb->fetch_base = TRUE;
+
+ shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
+ shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
+ shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
+ shim_callbacks->fetch_baton = sfb;
+
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ NULL, NULL, shim_callbacks,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Wrapping svn_wc_diff_callbacks4_t as svn_diff_tree_processor_t */
+
+/* baton for the svn_diff_tree_processor_t wrapper */
+typedef struct wc_diff_wrap_baton_t
+{
+ const svn_wc_diff_callbacks4_t *callbacks;
+ void *callback_baton;
+
+ svn_boolean_t walk_deleted_dirs;
+
+ apr_pool_t *result_pool;
+ const char *empty_file;
+
+} wc_diff_wrap_baton_t;
+
+static svn_error_t *
+wrap_ensure_empty_file(wc_diff_wrap_baton_t *wb,
+ apr_pool_t *scratch_pool)
+{
+ if (wb->empty_file)
+ return SVN_NO_ERROR;
+
+ /* Create a unique file in the tempdir */
+ SVN_ERR(svn_io_open_unique_file3(NULL, &wb->empty_file, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ wb->result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_dir_opened(void **new_dir_baton,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *parent_dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+
+ assert(left_source || right_source);
+ assert(!copyfrom_source || !right_source);
+
+ /* Maybe store state and tree_conflicted in baton? */
+ if (left_source != NULL)
+ {
+ /* Open for change or delete */
+ SVN_ERR(wb->callbacks->dir_opened(&tree_conflicted, skip, skip_children,
+ relpath,
+ right_source
+ ? right_source->revision
+ : (left_source
+ ? left_source->revision
+ : SVN_INVALID_REVNUM),
+ wb->callback_baton,
+ scratch_pool));
+
+ if (! right_source && !wb->walk_deleted_dirs)
+ *skip_children = TRUE;
+ }
+ else /* left_source == NULL -> Add */
+ {
+ svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+ SVN_ERR(wb->callbacks->dir_added(&state, &tree_conflicted,
+ skip, skip_children,
+ relpath,
+ right_source->revision,
+ copyfrom_source
+ ? copyfrom_source->repos_relpath
+ : NULL,
+ copyfrom_source
+ ? copyfrom_source->revision
+ : SVN_INVALID_REVNUM,
+ wb->callback_baton,
+ scratch_pool));
+ }
+
+ *new_dir_baton = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_dir_added(const char *relpath,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t state = svn_wc_notify_state_unknown;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
+ apr_hash_t *pristine_props = copyfrom_props;
+ apr_array_header_t *prop_changes = NULL;
+
+ if (right_props && apr_hash_count(right_props))
+ {
+ if (!pristine_props)
+ pristine_props = apr_hash_make(scratch_pool);
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, right_props, pristine_props,
+ scratch_pool));
+
+ SVN_ERR(wb->callbacks->dir_props_changed(&prop_state,
+ &tree_conflicted,
+ relpath,
+ TRUE /* dir_was_added */,
+ prop_changes, pristine_props,
+ wb->callback_baton,
+ scratch_pool));
+ }
+
+ SVN_ERR(wb->callbacks->dir_closed(&state, &prop_state,
+ &tree_conflicted,
+ relpath,
+ TRUE /* dir_was_added */,
+ wb->callback_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_dir_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ /*const*/ apr_hash_t *left_props,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+
+ SVN_ERR(wb->callbacks->dir_deleted(&state, &tree_conflicted,
+ relpath,
+ wb->callback_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_dir_closed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+
+ /* No previous implementations provided these arguments, so we
+ are not providing them either */
+ SVN_ERR(wb->callbacks->dir_closed(NULL, NULL, NULL,
+ relpath,
+ FALSE /* added */,
+ wb->callback_baton,
+ scratch_pool));
+
+return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_dir_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ const apr_array_header_t *prop_changes,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable;
+
+ assert(left_source && right_source);
+
+ SVN_ERR(wb->callbacks->dir_props_changed(&prop_state, &tree_conflicted,
+ relpath,
+ FALSE /* dir_was_added */,
+ prop_changes,
+ left_props,
+ wb->callback_baton,
+ scratch_pool));
+
+ /* And call dir_closed, etc */
+ SVN_ERR(wrap_dir_closed(relpath, left_source, right_source,
+ dir_baton, processor,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_file_opened(void **new_file_baton,
+ svn_boolean_t *skip,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *dir_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+
+ if (left_source) /* If ! added */
+ SVN_ERR(wb->callbacks->file_opened(&tree_conflicted, skip, relpath,
+ right_source
+ ? right_source->revision
+ : (left_source
+ ? left_source->revision
+ : SVN_INVALID_REVNUM),
+ wb->callback_baton, scratch_pool));
+
+ /* No old implementation used the output arguments for notify */
+
+ *new_file_baton = NULL;
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_file_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ const char *copyfrom_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable;
+ apr_array_header_t *prop_changes;
+
+ if (! copyfrom_props)
+ copyfrom_props = apr_hash_make(scratch_pool);
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, right_props, copyfrom_props,
+ scratch_pool));
+
+ if (! copyfrom_source)
+ SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool));
+
+ SVN_ERR(wb->callbacks->file_added(&state, &prop_state, &tree_conflicted,
+ relpath,
+ copyfrom_source
+ ? copyfrom_file
+ : wb->empty_file,
+ right_file,
+ 0,
+ right_source->revision,
+ copyfrom_props
+ ? svn_prop_get_value(copyfrom_props,
+ SVN_PROP_MIME_TYPE)
+ : NULL,
+ right_props
+ ? svn_prop_get_value(right_props,
+ SVN_PROP_MIME_TYPE)
+ : NULL,
+ copyfrom_source
+ ? copyfrom_source->repos_relpath
+ : NULL,
+ copyfrom_source
+ ? copyfrom_source->revision
+ : SVN_INVALID_REVNUM,
+ prop_changes, copyfrom_props,
+ wb->callback_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+wrap_file_deleted(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const char *left_file,
+ apr_hash_t *left_props,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+
+ SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool));
+
+ SVN_ERR(wb->callbacks->file_deleted(&state, &tree_conflicted,
+ relpath,
+ left_file, wb->empty_file,
+ left_props
+ ? svn_prop_get_value(left_props,
+ SVN_PROP_MIME_TYPE)
+ : NULL,
+ NULL,
+ left_props,
+ wb->callback_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* svn_diff_tree_processor_t function */
+static svn_error_t *
+wrap_file_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const char *left_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_boolean_t file_modified,
+ const apr_array_header_t *prop_changes,
+ void *file_baton,
+ const svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wb = processor->baton;
+ svn_boolean_t tree_conflicted = FALSE;
+ svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable;
+
+ SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool));
+
+ assert(left_source && right_source);
+
+ SVN_ERR(wb->callbacks->file_changed(&state, &prop_state, &tree_conflicted,
+ relpath,
+ file_modified ? left_file : NULL,
+ file_modified ? right_file : NULL,
+ left_source->revision,
+ right_source->revision,
+ left_props
+ ? svn_prop_get_value(left_props,
+ SVN_PROP_MIME_TYPE)
+ : NULL,
+ right_props
+ ? svn_prop_get_value(right_props,
+ SVN_PROP_MIME_TYPE)
+ : NULL,
+ prop_changes,
+ left_props,
+ wb->callback_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__wrap_diff_callbacks(const svn_diff_tree_processor_t **diff_processor,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_boolean_t walk_deleted_dirs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wc_diff_wrap_baton_t *wrap_baton;
+ svn_diff_tree_processor_t *processor;
+
+ wrap_baton = apr_pcalloc(result_pool, sizeof(*wrap_baton));
+
+ wrap_baton->result_pool = result_pool;
+ wrap_baton->callbacks = callbacks;
+ wrap_baton->callback_baton = callback_baton;
+ wrap_baton->empty_file = NULL;
+ wrap_baton->walk_deleted_dirs = walk_deleted_dirs;
+
+ processor = svn_diff__tree_processor_create(wrap_baton, result_pool);
+
+ processor->dir_opened = wrap_dir_opened;
+ processor->dir_added = wrap_dir_added;
+ processor->dir_deleted = wrap_dir_deleted;
+ processor->dir_changed = wrap_dir_changed;
+ processor->dir_closed = wrap_dir_closed;
+
+ processor->file_opened = wrap_file_opened;
+ processor->file_added = wrap_file_added;
+ processor->file_deleted = wrap_file_deleted;
+ processor->file_changed = wrap_file_changed;
+ /*processor->file_closed = wrap_file_closed*/; /* Not needed */
+
+ *diff_processor = processor;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/diff_local.c b/contrib/subversion/subversion/libsvn_wc/diff_local.c
new file mode 100644
index 0000000..ad87c76
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/diff_local.c
@@ -0,0 +1,541 @@
+/*
+ * diff_pristine.c -- A simple diff walker which compares local files against
+ * their pristine versions.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This is the simple working copy diff algorithm which is used when you
+ * just use 'svn diff PATH'. It shows what is modified in your working copy
+ * since a node was checked out or copied but doesn't show most kinds of
+ * restructuring operations.
+ *
+ * You can look at this as another form of the status walker.
+ */
+
+#include <apr_hash.h>
+
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+
+#include "private/svn_wc_private.h"
+#include "private/svn_diff_tree.h"
+
+#include "wc.h"
+#include "props.h"
+#include "translate.h"
+#include "diff.h"
+
+#include "svn_private_config.h"
+
+/*-------------------------------------------------------------------------*/
+
+/* Baton containing the state of a directory
+ reported open via a diff processor */
+struct node_state_t
+{
+ struct node_state_t *parent;
+
+ apr_pool_t *pool;
+
+ const char *local_abspath;
+ const char *relpath;
+ void *baton;
+
+ svn_diff_source_t *left_src;
+ svn_diff_source_t *right_src;
+ svn_diff_source_t *copy_src;
+
+ svn_boolean_t skip;
+ svn_boolean_t skip_children;
+
+ apr_hash_t *left_props;
+ apr_hash_t *right_props;
+ const apr_array_header_t *propchanges;
+};
+
+/* The diff baton */
+struct diff_baton
+{
+ /* A wc db. */
+ svn_wc__db_t *db;
+
+ /* Report editor paths relative from this directory */
+ const char *anchor_abspath;
+
+ struct node_state_t *cur;
+
+ const svn_diff_tree_processor_t *processor;
+
+ /* Should this diff ignore node ancestry? */
+ svn_boolean_t ignore_ancestry;
+
+ /* Should this diff not compare copied files with their source? */
+ svn_boolean_t show_copies_as_adds;
+
+ /* Hash whose keys are const char * changelist names. */
+ apr_hash_t *changelist_hash;
+
+ /* Cancel function/baton */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ apr_pool_t *pool;
+};
+
+/* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH
+ is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself,
+ but create it marked with skip+skip_children.
+ */
+static svn_error_t *
+ensure_state(struct diff_baton *eb,
+ const char *local_abspath,
+ svn_boolean_t recursive_skip,
+ apr_pool_t *scratch_pool)
+{
+ struct node_state_t *ns;
+ apr_pool_t *ns_pool;
+ if (!eb->cur)
+ {
+ if (!svn_dirent_is_ancestor(eb->anchor_abspath, local_abspath))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(ensure_state(eb,
+ svn_dirent_dirname(local_abspath,scratch_pool),
+ FALSE,
+ scratch_pool));
+ }
+ else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL))
+ SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath,scratch_pool),
+ FALSE,
+ scratch_pool));
+ else
+ return SVN_NO_ERROR;
+
+ if (eb->cur && eb->cur->skip_children)
+ return SVN_NO_ERROR;
+
+ ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool);
+ ns = apr_pcalloc(ns_pool, sizeof(*ns));
+
+ ns->pool = ns_pool;
+ ns->local_abspath = apr_pstrdup(ns_pool, local_abspath);
+ ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath);
+ ns->parent = eb->cur;
+ eb->cur = ns;
+
+ if (recursive_skip)
+ {
+ ns->skip = TRUE;
+ ns->skip_children = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ {
+ svn_revnum_t revision;
+ svn_error_t *err;
+
+ err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ revision = 0; /* Use original revision? */
+ }
+ ns->left_src = svn_diff__source_create(revision, ns->pool);
+ ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool);
+
+ SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip,
+ &ns->skip_children,
+ ns->relpath,
+ ns->left_src,
+ ns->right_src,
+ NULL /* copyfrom_source */,
+ ns->parent ? ns->parent->baton : NULL,
+ eb->processor,
+ ns->pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_wc_status_func3_t */
+static svn_error_t *
+diff_status_callback(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_baton *eb = baton;
+ svn_wc__db_t *db = eb->db;
+
+ switch (status->node_status)
+ {
+ case svn_wc_status_unversioned:
+ case svn_wc_status_ignored:
+ return SVN_NO_ERROR; /* No diff */
+
+ case svn_wc_status_conflicted:
+ if (status->text_status == svn_wc_status_none
+ && status->prop_status == svn_wc_status_none)
+ {
+ /* Node is an actual only node describing a tree conflict */
+ return SVN_NO_ERROR;
+ }
+ break;
+
+ default:
+ break; /* Go check other conditions */
+ }
+
+ /* Not text/prop modified, not copied. Easy out */
+ if (status->node_status == svn_wc_status_normal && !status->copied)
+ return SVN_NO_ERROR;
+
+ /* Mark all directories where we are no longer inside as closed */
+ while (eb->cur
+ && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath))
+ {
+ struct node_state_t *ns = eb->cur;
+
+ if (!ns->skip)
+ {
+ if (ns->propchanges)
+ SVN_ERR(eb->processor->dir_changed(ns->relpath,
+ ns->left_src,
+ ns->right_src,
+ ns->left_props,
+ ns->right_props,
+ ns->propchanges,
+ ns->baton,
+ eb->processor,
+ ns->pool));
+ else
+ SVN_ERR(eb->processor->dir_closed(ns->relpath,
+ ns->left_src,
+ ns->right_src,
+ ns->baton,
+ eb->processor,
+ ns->pool));
+ }
+ eb->cur = ns->parent;
+ svn_pool_clear(ns->pool);
+ }
+ SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool),
+ FALSE, scratch_pool));
+
+ if (eb->cur && eb->cur->skip_children)
+ return SVN_NO_ERROR;
+
+ if (eb->changelist_hash != NULL
+ && (!status->changelist
+ || ! svn_hash_gets(eb->changelist_hash, status->changelist)))
+ return SVN_NO_ERROR; /* Filtered via changelist */
+
+ /* This code does about the same thing as the inner body of
+ walk_local_nodes_diff() in diff_editor.c, except that
+ it is already filtered by the status walker, doesn't have to
+ account for remote changes (and many tiny other details) */
+
+ {
+ svn_boolean_t repos_only;
+ svn_boolean_t local_only;
+ svn_wc__db_status_t db_status;
+ svn_boolean_t have_base;
+ svn_node_kind_t base_kind;
+ svn_node_kind_t db_kind = status->kind;
+ svn_depth_t depth_below_here = svn_depth_unknown;
+
+ const char *child_abspath = local_abspath;
+ const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath,
+ local_abspath);
+
+
+ repos_only = FALSE;
+ local_only = FALSE;
+
+ /* ### optimize away this call using status info. Should
+ be possible in almost every case (except conflict, missing, obst.)*/
+ SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ &have_base, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (!have_base)
+ {
+ local_only = TRUE; /* Only report additions */
+ }
+ else if (db_status == svn_wc__db_status_normal)
+ {
+ /* Simple diff */
+ base_kind = db_kind;
+ }
+ else if (db_status == svn_wc__db_status_deleted)
+ {
+ svn_wc__db_status_t base_status;
+ repos_only = TRUE;
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (base_status != svn_wc__db_status_normal)
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* working status is either added or deleted */
+ svn_wc__db_status_t base_status;
+
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (base_status != svn_wc__db_status_normal)
+ local_only = TRUE;
+ else if (base_kind != db_kind || !eb->ignore_ancestry)
+ {
+ repos_only = TRUE;
+ local_only = TRUE;
+ }
+ }
+
+ if (repos_only)
+ {
+ /* Report repository form deleted */
+ if (base_kind == svn_node_file)
+ SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath,
+ child_relpath,
+ SVN_INVALID_REVNUM,
+ eb->processor,
+ eb->cur ? eb->cur->baton : NULL,
+ scratch_pool));
+ else if (base_kind == svn_node_dir)
+ SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath,
+ child_relpath,
+ SVN_INVALID_REVNUM,
+ depth_below_here,
+ eb->processor,
+ eb->cur ? eb->cur->baton : NULL,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+ }
+ else if (!local_only)
+ {
+ /* Diff base against actual */
+ if (db_kind == svn_node_file)
+ {
+ SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath,
+ child_relpath,
+ SVN_INVALID_REVNUM,
+ eb->changelist_hash,
+ eb->processor,
+ eb->cur
+ ? eb->cur->baton
+ : NULL,
+ FALSE,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+ }
+ else if (db_kind == svn_node_dir)
+ {
+ SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool));
+
+ if (status->prop_status != svn_wc_status_none
+ && status->prop_status != svn_wc_status_normal)
+ {
+ apr_array_header_t *propchanges;
+ SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props,
+ eb->db, local_abspath,
+ eb->cur->pool,
+ scratch_pool));
+ SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props,
+ eb->db, local_abspath,
+ eb->cur->pool,
+ scratch_pool));
+
+ SVN_ERR(svn_prop_diffs(&propchanges,
+ eb->cur->right_props,
+ eb->cur->left_props,
+ eb->cur->pool));
+
+ eb->cur->propchanges = propchanges;
+ }
+ }
+ }
+
+ if (local_only)
+ {
+ if (db_kind == svn_node_file)
+ SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath,
+ child_relpath,
+ eb->processor,
+ eb->cur ? eb->cur->baton : NULL,
+ eb->changelist_hash,
+ FALSE,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+ else if (db_kind == svn_node_dir)
+ SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath,
+ child_relpath, depth_below_here,
+ eb->processor,
+ eb->cur ? eb->cur->baton : NULL,
+ eb->changelist_hash,
+ FALSE,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+ }
+
+ if (db_kind == svn_node_dir && (local_only || repos_only))
+ SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Public Interface */
+svn_error_t *
+svn_wc_diff6(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_baton eb = { 0 };
+ svn_node_kind_t kind;
+ svn_boolean_t get_all;
+ const svn_diff_tree_processor_t *processor;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath,
+ FALSE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */,
+ scratch_pool));
+
+ if (kind == svn_node_dir)
+ eb.anchor_abspath = local_abspath;
+ else
+ eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__wrap_diff_callbacks(&processor,
+ callbacks, callback_baton, TRUE,
+ scratch_pool, scratch_pool));
+
+ if (use_git_diff_format)
+ show_copies_as_adds = TRUE;
+ if (show_copies_as_adds)
+ ignore_ancestry = FALSE;
+
+
+
+ /*
+ if (reverse_order)
+ processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool);
+ */
+
+ if (! show_copies_as_adds && !use_git_diff_format)
+ processor = svn_diff__tree_processor_copy_as_changed_create(processor,
+ scratch_pool);
+
+ eb.db = wc_ctx->db;
+ eb.processor = processor;
+ eb.ignore_ancestry = ignore_ancestry;
+ eb.show_copies_as_adds = show_copies_as_adds;
+ eb.pool = scratch_pool;
+
+ if (changelist_filter && changelist_filter->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&eb.changelist_hash, changelist_filter,
+ scratch_pool));
+
+ if (show_copies_as_adds || use_git_diff_format || !ignore_ancestry)
+ get_all = TRUE; /* We need unmodified descendants of copies */
+ else
+ get_all = FALSE;
+
+ /* Walk status handles files and directories */
+ SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth,
+ get_all,
+ TRUE /* no_ignore */,
+ FALSE /* ignore_text_mods */,
+ NULL /* ignore_patterns */,
+ diff_status_callback, &eb,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* Close the remaining open directories */
+ while (eb.cur)
+ {
+ struct node_state_t *ns = eb.cur;
+
+ if (!ns->skip)
+ {
+ if (ns->propchanges)
+ SVN_ERR(processor->dir_changed(ns->relpath,
+ ns->left_src,
+ ns->right_src,
+ ns->left_props,
+ ns->right_props,
+ ns->propchanges,
+ ns->baton,
+ processor,
+ ns->pool));
+ else
+ SVN_ERR(processor->dir_closed(ns->relpath,
+ ns->left_src,
+ ns->right_src,
+ ns->baton,
+ processor,
+ ns->pool));
+ }
+ eb.cur = ns->parent;
+ svn_pool_clear(ns->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/entries.c b/contrib/subversion/subversion/libsvn_wc/entries.c
new file mode 100644
index 0000000..f6a73bf
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/entries.c
@@ -0,0 +1,2738 @@
+/*
+ * entries.c : manipulating the administrative `entries' file.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <string.h>
+#include <assert.h>
+
+#include <apr_strings.h>
+
+#include "svn_error.h"
+#include "svn_types.h"
+#include "svn_time.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_ctype.h"
+#include "svn_string.h"
+#include "svn_hash.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "entries.h"
+#include "lock.h"
+#include "tree_conflicts.h"
+#include "wc_db.h"
+#include "wc-queries.h" /* for STMT_* */
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_sqlite.h"
+
+#define MAYBE_ALLOC(x,p) ((x) ? (x) : apr_pcalloc((p), sizeof(*(x))))
+
+
+/* Temporary structures which mirror the tables in wc-metadata.sql.
+ For detailed descriptions of each field, see that file. */
+typedef struct db_node_t {
+ apr_int64_t wc_id;
+ const char *local_relpath;
+ int op_depth;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ const char *parent_relpath;
+ svn_wc__db_status_t presence;
+ svn_revnum_t revision;
+ svn_node_kind_t kind;
+ svn_checksum_t *checksum;
+ svn_filesize_t recorded_size;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ svn_depth_t depth;
+ apr_time_t recorded_time;
+ apr_hash_t *properties;
+ svn_boolean_t file_external;
+ apr_array_header_t *inherited_props;
+} db_node_t;
+
+typedef struct db_actual_node_t {
+ apr_int64_t wc_id;
+ const char *local_relpath;
+ const char *parent_relpath;
+ apr_hash_t *properties;
+ const char *conflict_old;
+ const char *conflict_new;
+ const char *conflict_working;
+ const char *prop_reject;
+ const char *changelist;
+ /* ### enum for text_mod */
+ const char *tree_conflict_data;
+} db_actual_node_t;
+
+
+
+/*** reading and writing the entries file ***/
+
+
+/* */
+static svn_wc_entry_t *
+alloc_entry(apr_pool_t *pool)
+{
+ svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry));
+ entry->revision = SVN_INVALID_REVNUM;
+ entry->copyfrom_rev = SVN_INVALID_REVNUM;
+ entry->cmt_rev = SVN_INVALID_REVNUM;
+ entry->kind = svn_node_none;
+ entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
+ entry->depth = svn_depth_infinity;
+ entry->file_external_peg_rev.kind = svn_opt_revision_unspecified;
+ entry->file_external_rev.kind = svn_opt_revision_unspecified;
+ return entry;
+}
+
+
+/* Is the entry in a 'hidden' state in the sense of the 'show_hidden'
+ * switches on svn_wc_entries_read(), svn_wc_walk_entries*(), etc.? */
+svn_error_t *
+svn_wc__entry_is_hidden(svn_boolean_t *hidden, const svn_wc_entry_t *entry)
+{
+ /* In English, the condition is: "the entry is not present, and I haven't
+ scheduled something over the top of it." */
+ if (entry->deleted
+ || entry->absent
+ || entry->depth == svn_depth_exclude)
+ {
+ /* These kinds of nodes cannot be marked for deletion (which also
+ means no "replace" either). */
+ SVN_ERR_ASSERT(entry->schedule == svn_wc_schedule_add
+ || entry->schedule == svn_wc_schedule_normal);
+
+ /* Hidden if something hasn't been added over it.
+
+ ### is this even possible with absent or excluded nodes? */
+ *hidden = entry->schedule != svn_wc_schedule_add;
+ }
+ else
+ *hidden = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Hit the database to check the file external information for the given
+ entry. The entry will be modified in place. */
+static svn_error_t *
+check_file_external(svn_wc_entry_t *entry,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const char *repos_relpath;
+ svn_revnum_t peg_revision;
+ svn_revnum_t revision;
+ svn_error_t *err;
+
+ err = svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL,
+ &repos_relpath, &peg_revision, &revision,
+ db, local_abspath, wri_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ if (status == svn_wc__db_status_normal
+ && kind == svn_node_file)
+ {
+ entry->file_external_path = repos_relpath;
+ if (SVN_IS_VALID_REVNUM(peg_revision))
+ {
+ entry->file_external_peg_rev.kind = svn_opt_revision_number;
+ entry->file_external_peg_rev.value.number = peg_revision;
+ entry->file_external_rev = entry->file_external_peg_rev;
+ }
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ entry->file_external_rev.kind = svn_opt_revision_number;
+ entry->file_external_rev.value.number = revision;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Fill in the following fields of ENTRY:
+
+ REVISION
+ REPOS
+ UUID
+ CMT_REV
+ CMT_DATE
+ CMT_AUTHOR
+ DEPTH
+ DELETED
+
+ Return: KIND, REPOS_RELPATH, CHECKSUM
+*/
+static svn_error_t *
+get_info_for_deleted(svn_wc_entry_t *entry,
+ svn_node_kind_t *kind,
+ const char **repos_relpath,
+ const svn_checksum_t **checksum,
+ svn_wc__db_lock_t **lock,
+ svn_wc__db_t *db,
+ const char *entry_abspath,
+ const svn_wc_entry_t *parent_entry,
+ svn_boolean_t have_base,
+ svn_boolean_t have_more_work,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (have_base && !have_more_work)
+ {
+ /* This is the delete of a BASE node */
+ SVN_ERR(svn_wc__db_base_get_info(NULL, kind,
+ &entry->revision,
+ repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ &entry->cmt_rev,
+ &entry->cmt_date,
+ &entry->cmt_author,
+ &entry->depth,
+ checksum,
+ NULL,
+ lock,
+ &entry->has_props, NULL,
+ NULL,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+ }
+ else
+ {
+ const char *work_del_abspath;
+ const char *parent_repos_relpath;
+ const char *parent_abspath;
+
+ /* This is a deleted child of a copy/move-here,
+ so we need to scan up the WORKING tree to find the root of
+ the deletion. Then examine its parent to discover its
+ future location in the repository. */
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, kind,
+ &entry->cmt_rev,
+ &entry->cmt_date,
+ &entry->cmt_author,
+ &entry->depth,
+ checksum,
+ NULL,
+ &entry->has_props, NULL,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+ /* working_size and text_time unavailable */
+
+ SVN_ERR(svn_wc__db_scan_deletion(NULL,
+ NULL,
+ &work_del_abspath, NULL,
+ db, entry_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(work_del_abspath != NULL);
+ parent_abspath = svn_dirent_dirname(work_del_abspath, scratch_pool);
+
+ /* The parent directory of the delete root must be added, so we
+ can find the required information there */
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
+ &parent_repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ NULL, NULL, NULL, NULL,
+ db, parent_abspath,
+ result_pool, scratch_pool));
+
+ /* Now glue it all together */
+ *repos_relpath = svn_relpath_join(parent_repos_relpath,
+ svn_dirent_is_child(parent_abspath,
+ entry_abspath,
+ NULL),
+ result_pool);
+
+
+ /* Even though this is the delete of a WORKING node, there might still
+ be a BASE node somewhere below with an interesting revision */
+ if (have_base)
+ {
+ svn_wc__db_status_t status;
+ SVN_ERR(svn_wc__db_base_get_info(&status, NULL, &entry->revision,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, lock, NULL, NULL,
+ NULL,
+ db, entry_abspath,
+ result_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_not_present)
+ entry->deleted = TRUE;
+ }
+ }
+
+ /* Do some extra work for the child nodes. */
+ if (!SVN_IS_VALID_REVNUM(entry->revision) && parent_entry != NULL)
+ {
+ /* For child nodes without a revision, pick up the parent's
+ revision. */
+ entry->revision = parent_entry->revision;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*
+ * Encode tree conflict descriptions into a single string.
+ *
+ * Set *CONFLICT_DATA to a string, allocated in POOL, that encodes the tree
+ * conflicts in CONFLICTS in a form suitable for storage in a single string
+ * field in a WC entry. CONFLICTS is a hash of zero or more pointers to
+ * svn_wc_conflict_description2_t objects, index by their basenames. All of the
+ * conflict victim paths must be siblings.
+ *
+ * Do all allocations in POOL.
+ *
+ * @see svn_wc__read_tree_conflicts()
+ */
+static svn_error_t *
+write_tree_conflicts(const char **conflict_data,
+ apr_hash_t *conflicts,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, conflicts); hi; hi = apr_hash_next(hi))
+ {
+ svn_skel_t *c_skel;
+
+ SVN_ERR(svn_wc__serialize_conflict(&c_skel, svn__apr_hash_index_val(hi),
+ pool, pool));
+ svn_skel__prepend(c_skel, skel);
+ }
+
+ *conflict_data = svn_skel__unparse(skel, pool)->data;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Read one entry from wc_db. It will be allocated in RESULT_POOL and
+ returned in *NEW_ENTRY.
+
+ DIR_ABSPATH is the name of the directory to read this entry from, and
+ it will be named NAME (use "" for "this dir").
+
+ DB specifies the wc_db database, and WC_ID specifies which working copy
+ this information is being read from.
+
+ If this node is "this dir", then PARENT_ENTRY should be NULL. Otherwise,
+ it should refer to the entry for the child's parent directory.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+read_one_entry(const svn_wc_entry_t **new_entry,
+ svn_wc__db_t *db,
+ apr_int64_t wc_id,
+ const char *dir_abspath,
+ const char *name,
+ const svn_wc_entry_t *parent_entry,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ svn_wc__db_status_t status;
+ svn_wc__db_lock_t *lock;
+ const char *repos_relpath;
+ const svn_checksum_t *checksum;
+ svn_filesize_t translated_size;
+ svn_wc_entry_t *entry = alloc_entry(result_pool);
+ const char *entry_abspath;
+ const char *original_repos_relpath;
+ const char *original_root_url;
+ svn_boolean_t conflicted;
+ svn_boolean_t have_base;
+ svn_boolean_t have_more_work;
+
+ entry->name = name;
+
+ entry_abspath = svn_dirent_join(dir_abspath, entry->name, scratch_pool);
+
+ SVN_ERR(svn_wc__db_read_info(
+ &status,
+ &kind,
+ &entry->revision,
+ &repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ &entry->cmt_rev,
+ &entry->cmt_date,
+ &entry->cmt_author,
+ &entry->depth,
+ &checksum,
+ NULL,
+ &original_repos_relpath,
+ &original_root_url,
+ NULL,
+ &entry->copyfrom_rev,
+ &lock,
+ &translated_size,
+ &entry->text_time,
+ &entry->changelist,
+ &conflicted,
+ NULL /* op_root */,
+ &entry->has_props /* have_props */,
+ &entry->has_prop_mods /* props_mod */,
+ &have_base,
+ &have_more_work,
+ NULL /* have_work */,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+
+ if (entry->has_prop_mods)
+ entry->has_props = TRUE;
+
+ if (strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
+ {
+ /* get the tree conflict data. */
+ apr_hash_t *tree_conflicts = NULL;
+ const apr_array_header_t *conflict_victims;
+ int k;
+
+ SVN_ERR(svn_wc__db_read_conflict_victims(&conflict_victims, db,
+ dir_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ for (k = 0; k < conflict_victims->nelts; k++)
+ {
+ int j;
+ const apr_array_header_t *child_conflicts;
+ const char *child_name;
+ const char *child_abspath;
+
+ child_name = APR_ARRAY_IDX(conflict_victims, k, const char *);
+ child_abspath = svn_dirent_join(dir_abspath, child_name,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__read_conflicts(&child_conflicts,
+ db, child_abspath,
+ FALSE /* create tempfiles */,
+ scratch_pool, scratch_pool));
+
+ for (j = 0; j < child_conflicts->nelts; j++)
+ {
+ const svn_wc_conflict_description2_t *conflict =
+ APR_ARRAY_IDX(child_conflicts, j,
+ svn_wc_conflict_description2_t *);
+
+ if (conflict->kind == svn_wc_conflict_kind_tree)
+ {
+ if (!tree_conflicts)
+ tree_conflicts = apr_hash_make(scratch_pool);
+ svn_hash_sets(tree_conflicts, child_name, conflict);
+ }
+ }
+ }
+
+ if (tree_conflicts)
+ {
+ SVN_ERR(write_tree_conflicts(&entry->tree_conflict_data,
+ tree_conflicts, result_pool));
+ }
+ }
+
+ if (status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_incomplete)
+ {
+ /* Plain old BASE node. */
+ entry->schedule = svn_wc_schedule_normal;
+
+ /* Grab inherited repository information, if necessary. */
+ if (repos_relpath == NULL)
+ {
+ SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+ }
+
+ entry->incomplete = (status == svn_wc__db_status_incomplete);
+ }
+ else if (status == svn_wc__db_status_deleted)
+ {
+ svn_node_kind_t path_kind;
+
+ /* ### we don't have to worry about moves, so this is a delete. */
+ entry->schedule = svn_wc_schedule_delete;
+
+ /* If there are multiple working layers or no BASE layer, then
+ this is a WORKING delete or WORKING not-present. */
+ if (have_more_work || !have_base)
+ entry->copied = TRUE;
+ else if (have_base && !have_more_work)
+ entry->copied = FALSE;
+ else
+ {
+ const char *work_del_abspath;
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
+ &work_del_abspath, NULL,
+ db, entry_abspath,
+ scratch_pool, scratch_pool));
+
+ if (work_del_abspath)
+ entry->copied = TRUE;
+ }
+
+ /* If there is still a directory on-disk we keep it, if not it is
+ already deleted. Simple, isn't it?
+
+ Before single-db we had to keep the administative area alive until
+ after the commit really deletes it. Setting keep alive stopped the
+ commit processing from deleting the directory. We don't delete it
+ any more, so all we have to do is provide some 'sane' value.
+ */
+ SVN_ERR(svn_io_check_path(entry_abspath, &path_kind, scratch_pool));
+ entry->keep_local = (path_kind == svn_node_dir);
+ }
+ else if (status == svn_wc__db_status_added)
+ {
+ svn_wc__db_status_t work_status;
+ const char *op_root_abspath;
+ const char *scanned_original_relpath;
+ svn_revnum_t original_revision;
+
+ /* For child nodes, pick up the parent's revision. */
+ if (*entry->name != '\0')
+ {
+ assert(parent_entry != NULL);
+ assert(entry->revision == SVN_INVALID_REVNUM);
+
+ entry->revision = parent_entry->revision;
+ }
+
+ if (have_base)
+ {
+ svn_wc__db_status_t base_status;
+
+ /* ENTRY->REVISION is overloaded. When a node is schedule-add
+ or -replace, then REVISION refers to the BASE node's revision
+ that is being overwritten. We need to fetch it now. */
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL,
+ &entry->revision,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, entry_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ if (base_status == svn_wc__db_status_not_present)
+ {
+ /* The underlying node is DELETED in this revision. */
+ entry->deleted = TRUE;
+
+ /* This is an add since there isn't a node to replace. */
+ entry->schedule = svn_wc_schedule_add;
+ }
+ else
+ entry->schedule = svn_wc_schedule_replace;
+ }
+ else
+ {
+ /* There is NO 'not-present' BASE_NODE for this node.
+ Therefore, we are looking at some kind of add/copy
+ rather than a replace. */
+
+ /* ### if this looks like a plain old add, then rev=0. */
+ if (!SVN_IS_VALID_REVNUM(entry->copyfrom_rev)
+ && !SVN_IS_VALID_REVNUM(entry->cmt_rev))
+ entry->revision = 0;
+
+ entry->schedule = svn_wc_schedule_add;
+ }
+
+ /* If we don't have "real" data from the entry (obstruction),
+ then we cannot begin a scan for data. The original node may
+ have important data. Set up stuff to kill that idea off,
+ and finish up this entry. */
+ {
+ SVN_ERR(svn_wc__db_scan_addition(&work_status,
+ &op_root_abspath,
+ &repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ &scanned_original_relpath,
+ NULL, NULL, /* original_root|uuid */
+ &original_revision,
+ db,
+ entry_abspath,
+ result_pool, scratch_pool));
+
+ /* In wc.db we want to keep the valid revision of the not-present
+ BASE_REV, but when we used entries we set the revision to 0
+ when adding a new node over a not present base node. */
+ if (work_status == svn_wc__db_status_added
+ && entry->deleted)
+ entry->revision = 0;
+ }
+
+ if (!SVN_IS_VALID_REVNUM(entry->cmt_rev)
+ && scanned_original_relpath == NULL)
+ {
+ /* There is NOT a last-changed revision (last-changed date and
+ author may be unknown, but we can always check the rev).
+ The absence of a revision implies this node was added WITHOUT
+ any history. Avoid the COPIED checks in the else block. */
+ /* ### scan_addition may need to be updated to avoid returning
+ ### status_copied in this case. */
+ }
+ /* For backwards-compatiblity purposes we treat moves just like
+ * regular copies. */
+ else if (work_status == svn_wc__db_status_copied ||
+ work_status == svn_wc__db_status_moved_here)
+ {
+ entry->copied = TRUE;
+
+ /* If this is a child of a copied subtree, then it should be
+ schedule_normal. */
+ if (original_repos_relpath == NULL)
+ {
+ /* ### what if there is a BASE node under there? */
+ entry->schedule = svn_wc_schedule_normal;
+ }
+
+ /* Copied nodes need to mirror their copyfrom_rev, if they
+ don't have a revision of their own already. */
+ if (!SVN_IS_VALID_REVNUM(entry->revision)
+ || entry->revision == 0 /* added */)
+ entry->revision = original_revision;
+ }
+
+ /* Does this node have copyfrom_* information? */
+ if (scanned_original_relpath != NULL)
+ {
+ svn_boolean_t is_copied_child;
+ svn_boolean_t is_mixed_rev = FALSE;
+
+ SVN_ERR_ASSERT(work_status == svn_wc__db_status_copied ||
+ work_status == svn_wc__db_status_moved_here);
+
+ /* If this node inherits copyfrom information from an
+ ancestor node, then it must be a copied child. */
+ is_copied_child = (original_repos_relpath == NULL);
+
+ /* If this node has copyfrom information on it, then it may
+ be an actual copy-root, or it could be participating in
+ a mixed-revision copied tree. So if we don't already know
+ this is a copied child, then we need to look for this
+ mixed-revision situation. */
+ if (!is_copied_child)
+ {
+ const char *parent_abspath;
+ svn_error_t *err;
+ const char *parent_repos_relpath;
+ const char *parent_root_url;
+
+ /* When we insert entries into the database, we will
+ construct additional copyfrom records for mixed-revision
+ copies. The old entries would simply record the different
+ revision in the entry->revision field. That is not
+ available within wc-ng, so additional copies are made
+ (see the logic inside write_entry()). However, when
+ reading these back *out* of the database, the additional
+ copies look like new "Added" nodes rather than a simple
+ mixed-rev working copy.
+
+ That would be a behavior change if we did not compensate.
+ If there is copyfrom information for this node, then the
+ code below looks at the parent to detect if it *also* has
+ copyfrom information, and if the copyfrom_url would align
+ properly. If it *does*, then we omit storing copyfrom_url
+ and copyfrom_rev (ie. inherit the copyfrom info like a
+ normal child), and update entry->revision with the
+ copyfrom_rev in order to (re)create the mixed-rev copied
+ subtree that was originally presented for storage. */
+
+ /* Get the copyfrom information from our parent.
+
+ Note that the parent could be added/copied/moved-here.
+ There is no way for it to be deleted/moved-away and
+ have *this* node appear as copied. */
+ parent_abspath = svn_dirent_dirname(entry_abspath,
+ scratch_pool);
+ err = svn_wc__db_scan_addition(NULL,
+ &op_root_abspath,
+ NULL, NULL, NULL,
+ &parent_repos_relpath,
+ &parent_root_url,
+ NULL, NULL,
+ db, parent_abspath,
+ scratch_pool,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+ }
+ else if (parent_root_url != NULL
+ && strcmp(original_root_url, parent_root_url) == 0)
+ {
+ const char *relpath_to_entry = svn_dirent_is_child(
+ op_root_abspath, entry_abspath, NULL);
+ const char *entry_repos_relpath = svn_relpath_join(
+ parent_repos_relpath, relpath_to_entry, scratch_pool);
+
+ /* The copyfrom repos roots matched.
+
+ Now we look to see if the copyfrom path of the parent
+ would align with our own path. If so, then it means
+ this copyfrom was spontaneously created and inserted
+ for mixed-rev purposes and can be eliminated without
+ changing the semantics of a mixed-rev copied subtree.
+
+ See notes/api-errata/wc003.txt for some additional
+ detail, and potential issues. */
+ if (strcmp(entry_repos_relpath,
+ original_repos_relpath) == 0)
+ {
+ is_copied_child = TRUE;
+ is_mixed_rev = TRUE;
+ }
+ }
+ }
+
+ if (is_copied_child)
+ {
+ /* We won't be settig the copyfrom_url, yet need to
+ clear out the copyfrom_rev. Thus, this node becomes a
+ child of a copied subtree (rather than its own root). */
+ entry->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* Children in a copied subtree are schedule normal
+ since we don't plan to actually *do* anything with
+ them. Their operation is implied by ancestors. */
+ entry->schedule = svn_wc_schedule_normal;
+
+ /* And *finally* we turn this entry into the mixed
+ revision node that it was intended to be. This
+ node's revision is taken from the copyfrom record
+ that we spontaneously constructed. */
+ if (is_mixed_rev)
+ entry->revision = original_revision;
+ }
+ else if (original_repos_relpath != NULL)
+ {
+ entry->copyfrom_url =
+ svn_path_url_add_component2(original_root_url,
+ original_repos_relpath,
+ result_pool);
+ }
+ else
+ {
+ /* NOTE: if original_repos_relpath == NULL, then the
+ second call to scan_addition() will not have occurred.
+ Thus, this use of OP_ROOT_ABSPATH still contains the
+ original value where we fetched a value for
+ SCANNED_REPOS_RELPATH. */
+ const char *relpath_to_entry = svn_dirent_is_child(
+ op_root_abspath, entry_abspath, NULL);
+ const char *entry_repos_relpath = svn_relpath_join(
+ scanned_original_relpath, relpath_to_entry, scratch_pool);
+
+ entry->copyfrom_url =
+ svn_path_url_add_component2(original_root_url,
+ entry_repos_relpath,
+ result_pool);
+ }
+ }
+ }
+ else if (status == svn_wc__db_status_not_present)
+ {
+ /* ### buh. 'deleted' nodes are actually supposed to be
+ ### schedule "normal" since we aren't going to actually *do*
+ ### anything to this node at commit time. */
+ entry->schedule = svn_wc_schedule_normal;
+ entry->deleted = TRUE;
+ }
+ else if (status == svn_wc__db_status_server_excluded)
+ {
+ entry->absent = TRUE;
+ }
+ else if (status == svn_wc__db_status_excluded)
+ {
+ entry->schedule = svn_wc_schedule_normal;
+ entry->depth = svn_depth_exclude;
+ }
+ else
+ {
+ /* ### we should have handled all possible status values. */
+ SVN_ERR_MALFUNCTION();
+ }
+
+ /* ### higher levels want repos information about deleted nodes, even
+ ### tho they are not "part of" a repository any more. */
+ if (entry->schedule == svn_wc_schedule_delete)
+ {
+ SVN_ERR(get_info_for_deleted(entry,
+ &kind,
+ &repos_relpath,
+ &checksum,
+ &lock,
+ db, entry_abspath,
+ parent_entry,
+ have_base, have_more_work,
+ result_pool, scratch_pool));
+ }
+
+ /* ### default to the infinite depth if we don't know it. */
+ if (entry->depth == svn_depth_unknown)
+ entry->depth = svn_depth_infinity;
+
+ if (kind == svn_node_dir)
+ entry->kind = svn_node_dir;
+ else if (kind == svn_node_file)
+ entry->kind = svn_node_file;
+ else if (kind == svn_node_symlink)
+ entry->kind = svn_node_file; /* ### no symlink kind */
+ else
+ entry->kind = svn_node_unknown;
+
+ /* We should always have a REPOS_RELPATH, except for:
+ - deleted nodes
+ - certain obstructed nodes
+ - not-present nodes
+ - absent nodes
+ - excluded nodes
+
+ ### the last three should probably have an "implied" REPOS_RELPATH
+ */
+ SVN_ERR_ASSERT(repos_relpath != NULL
+ || entry->schedule == svn_wc_schedule_delete
+ || status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded);
+ if (repos_relpath)
+ entry->url = svn_path_url_add_component2(entry->repos,
+ repos_relpath,
+ result_pool);
+
+ if (checksum)
+ {
+ /* We got a SHA-1, get the corresponding MD-5. */
+ if (checksum->kind != svn_checksum_md5)
+ SVN_ERR(svn_wc__db_pristine_get_md5(&checksum, db,
+ entry_abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(checksum->kind == svn_checksum_md5);
+ entry->checksum = svn_checksum_to_cstring(checksum, result_pool);
+ }
+
+ if (conflicted)
+ {
+ svn_skel_t *conflict;
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ SVN_ERR(svn_wc__db_read_conflict(&conflict, db, entry_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, &text_conflicted,
+ &prop_conflicted, NULL,
+ db, dir_abspath, conflict,
+ scratch_pool, scratch_pool));
+
+ if (text_conflicted)
+ {
+ const char *my_abspath;
+ const char *their_old_abspath;
+ const char *their_abspath;
+ SVN_ERR(svn_wc__conflict_read_text_conflict(&my_abspath,
+ &their_old_abspath,
+ &their_abspath,
+ db, dir_abspath,
+ conflict, scratch_pool,
+ scratch_pool));
+
+ if (my_abspath)
+ entry->conflict_wrk = svn_dirent_basename(my_abspath, result_pool);
+
+ if (their_old_abspath)
+ entry->conflict_old = svn_dirent_basename(their_old_abspath,
+ result_pool);
+
+ if (their_abspath)
+ entry->conflict_new = svn_dirent_basename(their_abspath,
+ result_pool);
+ }
+
+ if (prop_conflicted)
+ {
+ const char *prej_abspath;
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath, NULL,
+ NULL, NULL, NULL,
+ db, dir_abspath,
+ conflict, scratch_pool,
+ scratch_pool));
+
+ if (prej_abspath)
+ entry->prejfile = svn_dirent_basename(prej_abspath, result_pool);
+ }
+ }
+
+ if (lock)
+ {
+ entry->lock_token = lock->token;
+ entry->lock_owner = lock->owner;
+ entry->lock_comment = lock->comment;
+ entry->lock_creation_date = lock->date;
+ }
+
+ /* Let's check for a file external. ugh. */
+ if (status == svn_wc__db_status_normal
+ && kind == svn_node_file)
+ SVN_ERR(check_file_external(entry, db, entry_abspath, dir_abspath,
+ result_pool, scratch_pool));
+
+ entry->working_size = translated_size;
+
+ *new_entry = entry;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read entries for PATH/LOCAL_ABSPATH from DB. The entries
+ will be allocated in RESULT_POOL, with temporary allocations in
+ SCRATCH_POOL. The entries are returned in RESULT_ENTRIES. */
+static svn_error_t *
+read_entries_new(apr_hash_t **result_entries,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *entries;
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+ const svn_wc_entry_t *parent_entry;
+ apr_int64_t wc_id = 1; /* ### hacky. should remove. */
+
+ entries = apr_hash_make(result_pool);
+
+ SVN_ERR(read_one_entry(&parent_entry, db, wc_id, local_abspath,
+ "" /* name */,
+ NULL /* parent_entry */,
+ result_pool, iterpool));
+ svn_hash_sets(entries, "", parent_entry);
+
+ /* Use result_pool so that the child names (used by reference, rather
+ than copied) appear in result_pool. */
+ SVN_ERR(svn_wc__db_read_children(&children, db,
+ local_abspath,
+ result_pool, iterpool));
+ for (i = children->nelts; i--; )
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+ const svn_wc_entry_t *entry;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(read_one_entry(&entry,
+ db, wc_id, local_abspath, name, parent_entry,
+ result_pool, iterpool));
+ svn_hash_sets(entries, entry->name, entry);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ *result_entries = entries;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Read a pair of entries from wc_db in the directory DIR_ABSPATH. Return
+ the directory's entry in *PARENT_ENTRY and NAME's entry in *ENTRY. The
+ two returned pointers will be the same if NAME=="" ("this dir").
+
+ The parent entry must exist.
+
+ The requested entry MAY exist. If it does not, then NULL will be returned.
+
+ The resulting entries are allocated in RESULT_POOL, and all temporary
+ allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+read_entry_pair(const svn_wc_entry_t **parent_entry,
+ const svn_wc_entry_t **entry,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_int64_t wc_id = 1; /* ### hacky. should remove. */
+
+ SVN_ERR(read_one_entry(parent_entry, db, wc_id, dir_abspath,
+ "" /* name */,
+ NULL /* parent_entry */,
+ result_pool, scratch_pool));
+
+ /* If we need the entry for "this dir", then return the parent_entry
+ in both outputs. Otherwise, read the child node. */
+ if (*name == '\0')
+ {
+ /* If the retrieved node is a FILE, then we have a problem. We asked
+ for a directory. This implies there is an obstructing, unversioned
+ directory where a FILE should be. We navigated from the obstructing
+ subdir up to the parent dir, then returned the FILE found there.
+
+ Let's return WC_MISSING cuz the caller thought we had a dir, but
+ that (versioned subdir) isn't there. */
+ if ((*parent_entry)->kind == svn_node_file)
+ {
+ *parent_entry = NULL;
+ return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
+ _("'%s' is not a versioned working copy"),
+ svn_dirent_local_style(dir_abspath,
+ scratch_pool));
+ }
+
+ *entry = *parent_entry;
+ }
+ else
+ {
+ const apr_array_header_t *children;
+ int i;
+
+ /* Default to not finding the child. */
+ *entry = NULL;
+
+ /* Determine whether the parent KNOWS about this child. If it does
+ not, then we should not attempt to look for it.
+
+ For example: the parent doesn't "know" about the child, but the
+ versioned directory *does* exist on disk. We don't want to look
+ into that subdir. */
+ SVN_ERR(svn_wc__db_read_children(&children, db, dir_abspath,
+ scratch_pool, scratch_pool));
+ for (i = children->nelts; i--; )
+ {
+ const char *child = APR_ARRAY_IDX(children, i, const char *);
+
+ if (strcmp(child, name) == 0)
+ {
+ svn_error_t *err;
+
+ err = read_one_entry(entry,
+ db, wc_id, dir_abspath, name, *parent_entry,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ /* No problem. Clear the error and leave the default value
+ of "missing". */
+ svn_error_clear(err);
+ }
+
+ /* Found it. No need to keep searching. */
+ break;
+ }
+ }
+ /* if the loop ends without finding a child, then we have the default
+ ENTRY value of NULL. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+read_entries(apr_hash_t **entries,
+ svn_wc__db_t *db,
+ const char *wcroot_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int wc_format;
+
+ SVN_ERR(svn_wc__db_temp_get_format(&wc_format, db, wcroot_abspath,
+ scratch_pool));
+
+ if (wc_format < SVN_WC__WC_NG_VERSION)
+ return svn_error_trace(svn_wc__read_entries_old(entries,
+ wcroot_abspath,
+ result_pool,
+ scratch_pool));
+
+ return svn_error_trace(read_entries_new(entries, db, wcroot_abspath,
+ result_pool, scratch_pool));
+}
+
+
+/* For a given LOCAL_ABSPATH, using DB, set *ADM_ABSPATH to the directory in
+ which the entry information is located, and *ENTRY_NAME to the entry name
+ to access that entry.
+
+ KIND is as in svn_wc__get_entry().
+
+ Return the results in RESULT_POOL and use SCRATCH_POOL for temporary
+ allocations. */
+static svn_error_t *
+get_entry_access_info(const char **adm_abspath,
+ const char **entry_name,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_adm_access_t *adm_access;
+ svn_boolean_t read_from_subdir = FALSE;
+
+ /* If the caller didn't know the node kind, then stat the path. Maybe
+ it is really there, and we can speed up the steps below. */
+ if (kind == svn_node_unknown)
+ {
+ svn_node_kind_t on_disk;
+
+ /* Do we already have an access baton for LOCAL_ABSPATH? */
+ adm_access = svn_wc__adm_retrieve_internal2(db, local_abspath,
+ scratch_pool);
+ if (adm_access)
+ {
+ /* Sweet. The node is a directory. */
+ on_disk = svn_node_dir;
+ }
+ else
+ {
+ svn_boolean_t special;
+
+ /* What's on disk? */
+ SVN_ERR(svn_io_check_special_path(local_abspath, &on_disk, &special,
+ scratch_pool));
+ }
+
+ if (on_disk != svn_node_dir)
+ {
+ /* If this is *anything* besides a directory (FILE, NONE, or
+ UNKNOWN), then we cannot treat it as a versioned directory
+ containing entries to read. Leave READ_FROM_SUBDIR as FALSE,
+ so that the parent will be examined.
+
+ For NONE and UNKNOWN, it may be that metadata exists for the
+ node, even though on-disk is unhelpful.
+
+ If NEED_PARENT_STUB is TRUE, and the entry is not a DIRECTORY,
+ then we'll error.
+
+ If NEED_PARENT_STUB if FALSE, and we successfully read a stub,
+ then this on-disk node is obstructing the read. */
+ }
+ else
+ {
+ /* We found a directory for this UNKNOWN node. Determine whether
+ we need to read inside it. */
+ read_from_subdir = TRUE;
+ }
+ }
+ else if (kind == svn_node_dir)
+ {
+ read_from_subdir = TRUE;
+ }
+
+ if (read_from_subdir)
+ {
+ /* KIND must be a DIR or UNKNOWN (and we found a subdir). We want
+ the "real" data, so treat LOCAL_ABSPATH as a versioned directory. */
+ *adm_abspath = apr_pstrdup(result_pool, local_abspath);
+ *entry_name = "";
+ }
+ else
+ {
+ /* FILE node needs to read the parent directory. Or a DIR node
+ needs to read from the parent to get at the stub entry. Or this
+ is an UNKNOWN node, and we need to examine the parent. */
+ svn_dirent_split(adm_abspath, entry_name, local_abspath, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__get_entry(const svn_wc_entry_t **entry,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t allow_unversioned,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *dir_abspath;
+ const char *entry_name;
+
+ SVN_ERR(get_entry_access_info(&dir_abspath, &entry_name, db, local_abspath,
+ kind, scratch_pool, scratch_pool));
+
+ {
+ const svn_wc_entry_t *parent_entry;
+ svn_error_t *err;
+
+ /* NOTE: if KIND is UNKNOWN and we decided to examine the *parent*
+ directory, then it is possible we moved out of the working copy.
+ If the on-disk node is a DIR, and we asked for a stub, then we
+ obviously can't provide that (parent has no info). If the on-disk
+ node is a FILE/NONE/UNKNOWN, then it is obstructing the real
+ LOCAL_ABSPATH (or it was never a versioned item). In all these
+ cases, the read_entries() will (properly) throw an error.
+
+ NOTE: if KIND is a DIR and we asked for the real data, but it is
+ obstructed on-disk by some other node kind (NONE, FILE, UNKNOWN),
+ then this will throw an error. */
+
+ err = read_entry_pair(&parent_entry, entry,
+ db, dir_abspath, entry_name,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_MISSING || kind != svn_node_unknown
+ || *entry_name != '\0')
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* The caller didn't know the node type, we saw a directory there,
+ we attempted to read IN that directory, and then wc_db reports
+ that it is NOT a working copy directory. It is possible that
+ one of two things has happened:
+
+ 1) a directory is obstructing a file in the parent
+ 2) the (versioned) directory's contents have been removed
+
+ Let's assume situation (1); if that is true, then we can just
+ return the newly-found data.
+
+ If we assumed (2), then a valid result still won't help us
+ since the caller asked for the actual contents, not the stub
+ (which is why we read *into* the directory). However, if we
+ assume (1) and get back a stub, then we have verified a
+ missing, versioned directory, and can return an error
+ describing that.
+
+ Redo the fetch, but "insist" we are trying to find a file.
+ This will read from the parent directory of the "file". */
+ err = svn_wc__get_entry(entry, db, local_abspath, allow_unversioned,
+ svn_node_file, result_pool, scratch_pool);
+ if (err == SVN_NO_ERROR)
+ return SVN_NO_ERROR;
+ if (err->apr_err != SVN_ERR_NODE_UNEXPECTED_KIND)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* We asked for a FILE, but the node found is a DIR. Thus, we
+ are looking at a stub. Originally, we tried to read into the
+ subdir because NEED_PARENT_STUB is FALSE. The stub we just
+ read is not going to work for the caller, so inform them of
+ the missing subdirectory. */
+ SVN_ERR_ASSERT(*entry != NULL && (*entry)->kind == svn_node_dir);
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Admin area of '%s' is missing"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ }
+
+ if (*entry == NULL)
+ {
+ if (allow_unversioned)
+ return SVN_NO_ERROR;
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ /* The caller had the wrong information. */
+ if ((kind == svn_node_file && (*entry)->kind != svn_node_file)
+ || (kind == svn_node_dir && (*entry)->kind != svn_node_dir))
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' is not of the right kind"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* TODO ### Rewrite doc string to mention ENTRIES_ALL; not ADM_ACCESS.
+
+ Prune the deleted entries from the cached entries in ADM_ACCESS, and
+ return that collection in *ENTRIES_PRUNED. SCRATCH_POOL is used for local,
+ short term, memory allocation, RESULT_POOL for permanent stuff. */
+static svn_error_t *
+prune_deleted(apr_hash_t **entries_pruned,
+ apr_hash_t *entries_all,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ if (!entries_all)
+ {
+ *entries_pruned = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* I think it will be common for there to be no deleted entries, so
+ it is worth checking for that case as we can optimise it. */
+ for (hi = apr_hash_first(scratch_pool, entries_all);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_boolean_t hidden;
+
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden,
+ svn__apr_hash_index_val(hi)));
+ if (hidden)
+ break;
+ }
+
+ if (! hi)
+ {
+ /* There are no deleted entries, so we can use the full hash */
+ *entries_pruned = entries_all;
+ return SVN_NO_ERROR;
+ }
+
+ /* Construct pruned hash without deleted entries */
+ *entries_pruned = apr_hash_make(result_pool);
+ for (hi = apr_hash_first(scratch_pool, entries_all);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key = svn__apr_hash_index_key(hi);
+ const svn_wc_entry_t *entry = svn__apr_hash_index_val(hi);
+ svn_boolean_t hidden;
+
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry));
+ if (!hidden)
+ svn_hash_sets(*entries_pruned, key, entry);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct entries_read_baton_t
+{
+ apr_hash_t **new_entries;
+ svn_wc__db_t *db;
+ const char *local_abspath;
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+entries_read_txn(void *baton, svn_sqlite__db_t *db, apr_pool_t *scratch_pool)
+{
+ struct entries_read_baton_t *erb = baton;
+
+ SVN_ERR(read_entries(erb->new_entries, erb->db, erb->local_abspath,
+ erb->result_pool, scratch_pool));
+
+ return NULL;
+}
+
+svn_error_t *
+svn_wc__entries_read_internal(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool)
+{
+ apr_hash_t *new_entries;
+
+ new_entries = svn_wc__adm_access_entries(adm_access);
+ if (! new_entries)
+ {
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ const char *local_abspath = svn_wc__adm_access_abspath(adm_access);
+ apr_pool_t *result_pool = svn_wc__adm_access_pool_internal(adm_access);
+ svn_sqlite__db_t *sdb;
+ struct entries_read_baton_t erb;
+
+ /* ### Use the borrow DB api to handle all calls in a single read
+ ### transaction. This api is used extensively in our test suite
+ ### via the entries-read application. */
+
+ SVN_ERR(svn_wc__db_temp_borrow_sdb(&sdb, db, local_abspath, pool));
+
+ erb.db = db;
+ erb.local_abspath = local_abspath;
+ erb.new_entries = &new_entries;
+ erb.result_pool = result_pool;
+
+ SVN_ERR(svn_sqlite__with_lock(sdb, entries_read_txn, &erb, pool));
+
+ svn_wc__adm_access_set_entries(adm_access, new_entries);
+ }
+
+ if (show_hidden)
+ *entries = new_entries;
+ else
+ SVN_ERR(prune_deleted(entries, new_entries,
+ svn_wc__adm_access_pool_internal(adm_access),
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_entries_read(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_wc__entries_read_internal(entries, adm_access,
+ show_hidden, pool));
+}
+
+/* No transaction required: called from write_entry which is itself
+ transaction-wrapped. */
+static svn_error_t *
+insert_node(svn_sqlite__db_t *sdb,
+ const db_node_t *node,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR_ASSERT(node->op_depth > 0 || node->repos_relpath);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnnnsnrisnnni",
+ node->wc_id,
+ node->local_relpath,
+ node->op_depth,
+ node->parent_relpath,
+ /* Setting depth for files? */
+ svn_depth_to_word(node->depth),
+ node->changed_rev,
+ node->changed_date,
+ node->changed_author,
+ node->recorded_time));
+
+ if (node->repos_relpath)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 5,
+ node->repos_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 6,
+ node->repos_relpath));
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, node->revision));
+ }
+
+ if (node->presence == svn_wc__db_status_normal)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "normal"));
+ else if (node->presence == svn_wc__db_status_not_present)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "not-present"));
+ else if (node->presence == svn_wc__db_status_base_deleted)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "base-deleted"));
+ else if (node->presence == svn_wc__db_status_incomplete)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "incomplete"));
+ else if (node->presence == svn_wc__db_status_excluded)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "excluded"));
+ else if (node->presence == svn_wc__db_status_server_excluded)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "server-excluded"));
+
+ if (node->kind == svn_node_none)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 10, "unknown"));
+ else
+ SVN_ERR(svn_sqlite__bind_text(stmt, 10,
+ svn_node_kind_to_word(node->kind)));
+
+ if (node->kind == svn_node_file)
+ {
+ if (!node->checksum
+ && node->op_depth == 0
+ && node->presence != svn_wc__db_status_not_present
+ && node->presence != svn_wc__db_status_excluded
+ && node->presence != svn_wc__db_status_server_excluded)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("The file '%s' has no checksum"),
+ svn_dirent_local_style(node->local_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, node->checksum,
+ scratch_pool));
+ }
+
+ if (node->properties) /* ### Never set, props done later */
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 15, node->properties,
+ scratch_pool));
+
+ if (node->recorded_size != SVN_INVALID_FILESIZE)
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 16, node->recorded_size));
+
+ if (node->file_external)
+ SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1));
+
+ if (node->inherited_props)
+ SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, node->inherited_props,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+insert_actual_node(svn_sqlite__db_t *sdb,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const db_actual_node_t *actual_node,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_skel_t *conflict_data = NULL;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL_NODE));
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 1, actual_node->wc_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 2, actual_node->local_relpath));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 3, actual_node->parent_relpath));
+
+ if (actual_node->properties)
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 4, actual_node->properties,
+ scratch_pool));
+
+ if (actual_node->changelist)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 5, actual_node->changelist));
+
+ SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw(
+ &conflict_data,
+ db, wri_abspath,
+ actual_node->local_relpath,
+ actual_node->conflict_old,
+ actual_node->conflict_working,
+ actual_node->conflict_new,
+ actual_node->prop_reject,
+ actual_node->tree_conflict_data,
+ actual_node->tree_conflict_data
+ ? strlen(actual_node->tree_conflict_data)
+ : 0,
+ scratch_pool, scratch_pool));
+
+ if (conflict_data)
+ {
+ svn_stringbuf_t *data = svn_skel__unparse(conflict_data, scratch_pool);
+
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 6, data->data, data->len));
+ }
+
+ /* Execute and reset the insert clause. */
+ return svn_error_trace(svn_sqlite__insert(NULL, stmt));
+}
+
+static svn_boolean_t
+is_switched(db_node_t *parent,
+ db_node_t *child,
+ apr_pool_t *scratch_pool)
+{
+ if (parent && child)
+ {
+ if (parent->repos_id != child->repos_id)
+ return TRUE;
+
+ if (parent->repos_relpath && child->repos_relpath)
+ {
+ const char *unswitched
+ = svn_relpath_join(parent->repos_relpath,
+ svn_relpath_basename(child->local_relpath,
+ scratch_pool),
+ scratch_pool);
+ if (strcmp(unswitched, child->repos_relpath))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+struct write_baton {
+ db_node_t *base;
+ db_node_t *work;
+ db_node_t *below_work;
+ apr_hash_t *tree_conflicts;
+};
+
+#define WRITE_ENTRY_ASSERT(expr) \
+ if (!(expr)) \
+ return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, \
+ _("Unable to upgrade '%s' at line %d"), \
+ svn_dirent_local_style( \
+ svn_dirent_join(root_abspath, \
+ local_relpath, \
+ scratch_pool), \
+ scratch_pool), __LINE__)
+
+/* Write the information for ENTRY to WC_DB. The WC_ID, REPOS_ID and
+ REPOS_ROOT will all be used for writing ENTRY.
+ ### transitioning from straight sql to using the wc_db APIs. For the
+ ### time being, we'll need both parameters. */
+static svn_error_t *
+write_entry(struct write_baton **entry_node,
+ const struct write_baton *parent_node,
+ svn_wc__db_t *db,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ apr_int64_t repos_id,
+ const svn_wc_entry_t *entry,
+ const svn_wc__text_base_info_t *text_base_info,
+ const char *local_relpath,
+ const char *tmp_entry_abspath,
+ const char *root_abspath,
+ const svn_wc_entry_t *this_dir,
+ svn_boolean_t create_locks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ db_node_t *base_node = NULL;
+ db_node_t *working_node = NULL, *below_working_node = NULL;
+ db_actual_node_t *actual_node = NULL;
+ const char *parent_relpath;
+ apr_hash_t *tree_conflicts;
+
+ if (*local_relpath == '\0')
+ parent_relpath = NULL;
+ else
+ parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool);
+
+ /* This is how it should work, it doesn't work like this yet because
+ we need proper op_depth to layer the working nodes.
+
+ Using "svn add", "svn rm", "svn cp" only files can be replaced
+ pre-wcng; directories can only be normal, deleted or added.
+ Files cannot be replaced within a deleted directory, so replaced
+ files can only exist in a normal directory, or a directory that
+ is added+copied. In a normal directory a replaced file needs a
+ base node and a working node, in an added+copied directory a
+ replaced file needs two working nodes at different op-depths.
+
+ With just the above operations the conversion for files and
+ directories is straightforward:
+
+ pre-wcng wcng
+ parent child parent child
+
+ normal normal base base
+ add+copied normal+copied work work
+ normal+copied normal+copied work work
+ normal delete base base+work
+ delete delete base+work base+work
+ add+copied delete work work
+ normal add base work
+ add add work work
+ add+copied add work work
+ normal add+copied base work
+ add add+copied work work
+ add+copied add+copied work work
+ normal replace base base+work
+ add+copied replace work work+work
+ normal replace+copied base base+work
+ add+copied replace+copied work work+work
+
+ However "svn merge" make this more complicated. The pre-wcng
+ "svn merge" is capable of replacing a directory, that is it can
+ mark the whole tree deleted, and then copy another tree on top.
+ The entries then represent the replacing tree overlayed on the
+ deleted tree.
+
+ original replace schedule in
+ tree tree combined tree
+
+ A A replace+copied
+ A/f delete+copied
+ A/g A/g replace+copied
+ A/h add+copied
+ A/B A/B replace+copied
+ A/B/f delete+copied
+ A/B/g A/B/g replace+copied
+ A/B/h add+copied
+ A/C delete+copied
+ A/C/f delete+copied
+ A/D add+copied
+ A/D/f add+copied
+
+ The original tree could be normal tree, or an add+copied tree.
+ Committing such a merge generally worked, but making further tree
+ modifications before commit sometimes failed.
+
+ The root of the replace is handled like the file replace:
+
+ pre-wcng wcng
+ parent child parent child
+
+ normal replace+copied base base+work
+ add+copied replace+copied work work+work
+
+ although obviously the node is a directory rather then a file.
+ There are then more conversion states where the parent is
+ replaced.
+
+ pre-wcng wcng
+ parent child parent child
+
+ replace+copied add [base|work]+work work
+ replace+copied add+copied [base|work]+work work
+ replace+copied delete+copied [base|work]+work [base|work]+work
+ delete+copied delete+copied [base|work]+work [base|work]+work
+ replace+copied replace+copied [base|work]+work [base|work]+work
+ */
+
+ WRITE_ENTRY_ASSERT(parent_node || entry->schedule == svn_wc_schedule_normal);
+
+ WRITE_ENTRY_ASSERT(!parent_node || parent_node->base
+ || parent_node->below_work || parent_node->work);
+
+ switch (entry->schedule)
+ {
+ case svn_wc_schedule_normal:
+ if (entry->copied ||
+ (entry->depth == svn_depth_exclude
+ && parent_node && !parent_node->base && parent_node->work))
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ else
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ break;
+
+ case svn_wc_schedule_add:
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ if (entry->deleted)
+ {
+ if (parent_node->base)
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ else
+ below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
+ }
+ break;
+
+ case svn_wc_schedule_delete:
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ if (parent_node->base)
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ if (parent_node->work)
+ below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
+ break;
+
+ case svn_wc_schedule_replace:
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ if (parent_node->base)
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ else
+ below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
+ break;
+ }
+
+ /* Something deleted in this revision means there should always be a
+ BASE node to indicate the not-present node. */
+ if (entry->deleted)
+ {
+ WRITE_ENTRY_ASSERT(base_node || below_working_node);
+ WRITE_ENTRY_ASSERT(!entry->incomplete);
+ if (base_node)
+ base_node->presence = svn_wc__db_status_not_present;
+ else
+ below_working_node->presence = svn_wc__db_status_not_present;
+ }
+ else if (entry->absent)
+ {
+ WRITE_ENTRY_ASSERT(base_node && !working_node && !below_working_node);
+ WRITE_ENTRY_ASSERT(!entry->incomplete);
+ base_node->presence = svn_wc__db_status_server_excluded;
+ }
+
+ if (entry->copied)
+ {
+ if (entry->copyfrom_url)
+ {
+ working_node->repos_id = repos_id;
+ working_node->repos_relpath = svn_uri_skip_ancestor(
+ this_dir->repos, entry->copyfrom_url,
+ result_pool);
+ working_node->revision = entry->copyfrom_rev;
+ working_node->op_depth
+ = svn_wc__db_op_depth_for_upgrade(local_relpath);
+ }
+ else if (parent_node->work && parent_node->work->repos_relpath)
+ {
+ working_node->repos_id = repos_id;
+ working_node->repos_relpath
+ = svn_relpath_join(parent_node->work->repos_relpath,
+ svn_relpath_basename(local_relpath, NULL),
+ result_pool);
+ working_node->revision = parent_node->work->revision;
+ working_node->op_depth = parent_node->work->op_depth;
+ }
+ else if (parent_node->below_work
+ && parent_node->below_work->repos_relpath)
+ {
+ working_node->repos_id = repos_id;
+ working_node->repos_relpath
+ = svn_relpath_join(parent_node->below_work->repos_relpath,
+ svn_relpath_basename(local_relpath, NULL),
+ result_pool);
+ working_node->revision = parent_node->below_work->revision;
+ working_node->op_depth = parent_node->below_work->op_depth;
+ }
+ else
+ return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
+ _("No copyfrom URL for '%s'"),
+ svn_dirent_local_style(local_relpath,
+ scratch_pool));
+ }
+
+ if (entry->conflict_old)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ if (parent_relpath && entry->conflict_old)
+ actual_node->conflict_old = svn_relpath_join(parent_relpath,
+ entry->conflict_old,
+ scratch_pool);
+ else
+ actual_node->conflict_old = entry->conflict_old;
+ if (parent_relpath && entry->conflict_new)
+ actual_node->conflict_new = svn_relpath_join(parent_relpath,
+ entry->conflict_new,
+ scratch_pool);
+ else
+ actual_node->conflict_new = entry->conflict_new;
+ if (parent_relpath && entry->conflict_wrk)
+ actual_node->conflict_working = svn_relpath_join(parent_relpath,
+ entry->conflict_wrk,
+ scratch_pool);
+ else
+ actual_node->conflict_working = entry->conflict_wrk;
+ }
+
+ if (entry->prejfile)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->prop_reject = svn_relpath_join((entry->kind == svn_node_dir
+ ? local_relpath
+ : parent_relpath),
+ entry->prejfile,
+ scratch_pool);
+ }
+
+ if (entry->changelist)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->changelist = entry->changelist;
+ }
+
+ /* ### set the text_mod value? */
+
+ if (entry_node && entry->tree_conflict_data)
+ {
+ /* Issues #3840/#3916: 1.6 stores multiple tree conflicts on the
+ parent node, 1.7 stores them directly on the conflited nodes.
+ So "((skel1) (skel2))" becomes "(skel1)" and "(skel2)" */
+ svn_skel_t *skel;
+
+ skel = svn_skel__parse(entry->tree_conflict_data,
+ strlen(entry->tree_conflict_data),
+ scratch_pool);
+ tree_conflicts = apr_hash_make(result_pool);
+ skel = skel->children;
+ while(skel)
+ {
+ svn_wc_conflict_description2_t *conflict;
+ svn_skel_t *new_skel;
+ const char *key;
+
+ /* *CONFLICT is allocated so it is safe to use a non-const pointer */
+ SVN_ERR(svn_wc__deserialize_conflict(
+ (const svn_wc_conflict_description2_t**)&conflict,
+ skel,
+ svn_dirent_join(root_abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ WRITE_ENTRY_ASSERT(conflict->kind == svn_wc_conflict_kind_tree);
+
+ /* Fix dubious data stored by old clients, local adds don't have
+ a repository URL. */
+ if (conflict->reason == svn_wc_conflict_reason_added)
+ conflict->src_left_version = NULL;
+
+ SVN_ERR(svn_wc__serialize_conflict(&new_skel, conflict,
+ scratch_pool, scratch_pool));
+
+ /* Store in hash to be retrieved when writing the child
+ row. */
+ key = svn_dirent_skip_ancestor(root_abspath, conflict->local_abspath);
+ svn_hash_sets(tree_conflicts, apr_pstrdup(result_pool, key),
+ svn_skel__unparse(new_skel, result_pool)->data);
+ skel = skel->next;
+ }
+ }
+ else
+ tree_conflicts = NULL;
+
+ if (parent_node && parent_node->tree_conflicts)
+ {
+ const char *tree_conflict_data =
+ svn_hash_gets(parent_node->tree_conflicts, local_relpath);
+ if (tree_conflict_data)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->tree_conflict_data = tree_conflict_data;
+ }
+
+ /* Reset hash so that we don't write the row again when writing
+ actual-only nodes */
+ svn_hash_sets(parent_node->tree_conflicts, local_relpath, NULL);
+ }
+
+ if (entry->file_external_path != NULL)
+ {
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ }
+
+
+ /* Insert the base node. */
+ if (base_node)
+ {
+ base_node->wc_id = wc_id;
+ base_node->local_relpath = local_relpath;
+ base_node->op_depth = 0;
+ base_node->parent_relpath = parent_relpath;
+ base_node->revision = entry->revision;
+ base_node->recorded_time = entry->text_time;
+ base_node->recorded_size = entry->working_size;
+
+ if (entry->depth != svn_depth_exclude)
+ base_node->depth = entry->depth;
+ else
+ {
+ base_node->presence = svn_wc__db_status_excluded;
+ base_node->depth = svn_depth_infinity;
+ }
+
+ if (entry->deleted)
+ {
+ WRITE_ENTRY_ASSERT(base_node->presence
+ == svn_wc__db_status_not_present);
+ /* ### should be svn_node_unknown, but let's store what we have. */
+ base_node->kind = entry->kind;
+ }
+ else if (entry->absent)
+ {
+ WRITE_ENTRY_ASSERT(base_node->presence
+ == svn_wc__db_status_server_excluded);
+ /* ### should be svn_node_unknown, but let's store what we have. */
+ base_node->kind = entry->kind;
+
+ /* Store the most likely revision in the node to avoid
+ base nodes without a valid revision. Of course
+ we remember that the data is still incomplete. */
+ if (!SVN_IS_VALID_REVNUM(base_node->revision) && parent_node->base)
+ base_node->revision = parent_node->base->revision;
+ }
+ else
+ {
+ base_node->kind = entry->kind;
+
+ if (base_node->presence != svn_wc__db_status_excluded)
+ {
+ /* All subdirs are initially incomplete, they stop being
+ incomplete when the entries file in the subdir is
+ upgraded and remain incomplete if that doesn't happen. */
+ if (entry->kind == svn_node_dir
+ && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
+ {
+ base_node->presence = svn_wc__db_status_incomplete;
+
+ /* Store the most likely revision in the node to avoid
+ base nodes without a valid revision. Of course
+ we remember that the data is still incomplete. */
+ if (parent_node->base)
+ base_node->revision = parent_node->base->revision;
+ }
+ else if (entry->incomplete)
+ {
+ /* ### nobody should have set the presence. */
+ WRITE_ENTRY_ASSERT(base_node->presence
+ == svn_wc__db_status_normal);
+ base_node->presence = svn_wc__db_status_incomplete;
+ }
+ }
+ }
+
+ if (entry->kind == svn_node_dir)
+ base_node->checksum = NULL;
+ else
+ {
+ if (text_base_info && text_base_info->revert_base.sha1_checksum)
+ base_node->checksum = text_base_info->revert_base.sha1_checksum;
+ else if (text_base_info && text_base_info->normal_base.sha1_checksum)
+ base_node->checksum = text_base_info->normal_base.sha1_checksum;
+ else
+ base_node->checksum = NULL;
+
+ /* The base MD5 checksum is available in the entry, unless there
+ * is a copied WORKING node. If possible, verify that the entry
+ * checksum matches the base file that we found. */
+ if (! (working_node && entry->copied))
+ {
+ svn_checksum_t *entry_md5_checksum, *found_md5_checksum;
+ SVN_ERR(svn_checksum_parse_hex(&entry_md5_checksum,
+ svn_checksum_md5,
+ entry->checksum, scratch_pool));
+ if (text_base_info && text_base_info->revert_base.md5_checksum)
+ found_md5_checksum = text_base_info->revert_base.md5_checksum;
+ else if (text_base_info
+ && text_base_info->normal_base.md5_checksum)
+ found_md5_checksum = text_base_info->normal_base.md5_checksum;
+ else
+ found_md5_checksum = NULL;
+ if (entry_md5_checksum && found_md5_checksum &&
+ !svn_checksum_match(entry_md5_checksum, found_md5_checksum))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Bad base MD5 checksum for '%s'; "
+ "expected: '%s'; found '%s'; "),
+ svn_dirent_local_style(
+ svn_dirent_join(root_abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool),
+ svn_checksum_to_cstring_display(
+ entry_md5_checksum, scratch_pool),
+ svn_checksum_to_cstring_display(
+ found_md5_checksum, scratch_pool));
+ else
+ {
+ /* ### Not sure what conditions this should cover. */
+ /* SVN_ERR_ASSERT(entry->deleted || ...); */
+ }
+ }
+ }
+
+ if (this_dir->repos)
+ {
+ base_node->repos_id = repos_id;
+
+ if (entry->url != NULL)
+ {
+ base_node->repos_relpath = svn_uri_skip_ancestor(
+ this_dir->repos, entry->url,
+ result_pool);
+ }
+ else
+ {
+ const char *relpath = svn_uri_skip_ancestor(this_dir->repos,
+ this_dir->url,
+ scratch_pool);
+ if (relpath == NULL || *relpath == '\0')
+ base_node->repos_relpath = entry->name;
+ else
+ base_node->repos_relpath =
+ svn_dirent_join(relpath, entry->name, result_pool);
+ }
+ }
+
+ /* TODO: These values should always be present, if they are missing
+ during an upgrade, set a flag, and then ask the user to talk to the
+ server.
+
+ Note: cmt_rev is the distinguishing value. The others may be 0 or
+ NULL if the corresponding revprop has been deleted. */
+ base_node->changed_rev = entry->cmt_rev;
+ base_node->changed_date = entry->cmt_date;
+ base_node->changed_author = entry->cmt_author;
+
+ if (entry->file_external_path)
+ base_node->file_external = TRUE;
+
+ /* Switched nodes get an empty iprops cache. */
+ if (parent_node
+ && is_switched(parent_node->base, base_node, scratch_pool))
+ base_node->inherited_props
+ = apr_array_make(scratch_pool, 0, sizeof(svn_prop_inherited_item_t*));
+
+ SVN_ERR(insert_node(sdb, base_node, scratch_pool));
+
+ /* We have to insert the lock after the base node, because the node
+ must exist to lookup various bits of repos related information for
+ the abs path. */
+ if (entry->lock_token && create_locks)
+ {
+ svn_wc__db_lock_t lock;
+
+ lock.token = entry->lock_token;
+ lock.owner = entry->lock_owner;
+ lock.comment = entry->lock_comment;
+ lock.date = entry->lock_creation_date;
+
+ SVN_ERR(svn_wc__db_lock_add(db, tmp_entry_abspath, &lock,
+ scratch_pool));
+ }
+ }
+
+ if (below_working_node)
+ {
+ db_node_t *work
+ = parent_node->below_work ? parent_node->below_work : parent_node->work;
+
+ below_working_node->wc_id = wc_id;
+ below_working_node->local_relpath = local_relpath;
+ below_working_node->op_depth = work->op_depth;
+ below_working_node->parent_relpath = parent_relpath;
+ below_working_node->presence = svn_wc__db_status_normal;
+ below_working_node->kind = entry->kind;
+ below_working_node->repos_id = work->repos_id;
+
+ /* This is just guessing. If the node below would have been switched
+ or if it was updated to a different version, the guess would
+ fail. But we don't have better information pre wc-ng :( */
+ if (work->repos_relpath)
+ below_working_node->repos_relpath
+ = svn_relpath_join(work->repos_relpath, entry->name,
+ result_pool);
+ else
+ below_working_node->repos_relpath = NULL;
+ below_working_node->revision = parent_node->work->revision;
+
+ /* The revert_base checksum isn't available in the entry structure,
+ so the caller provides it. */
+
+ /* text_base_info is NULL for files scheduled to be added. */
+ below_working_node->checksum = NULL;
+ if (text_base_info)
+ {
+ if (entry->schedule == svn_wc_schedule_delete)
+ below_working_node->checksum =
+ text_base_info->normal_base.sha1_checksum;
+ else
+ below_working_node->checksum =
+ text_base_info->revert_base.sha1_checksum;
+ }
+ below_working_node->recorded_size = 0;
+ below_working_node->changed_rev = SVN_INVALID_REVNUM;
+ below_working_node->changed_date = 0;
+ below_working_node->changed_author = NULL;
+ below_working_node->depth = svn_depth_infinity;
+ below_working_node->recorded_time = 0;
+ below_working_node->properties = NULL;
+
+ if (working_node
+ && entry->schedule == svn_wc_schedule_delete
+ && working_node->repos_relpath)
+ {
+ /* We are lucky, our guesses above are not necessary. The known
+ correct information is in working. But our op_depth design
+ expects more information here */
+ below_working_node->repos_relpath = working_node->repos_relpath;
+ below_working_node->repos_id = working_node->repos_id;
+ below_working_node->revision = working_node->revision;
+
+ /* Nice for 'svn status' */
+ below_working_node->changed_rev = entry->cmt_rev;
+ below_working_node->changed_date = entry->cmt_date;
+ below_working_node->changed_author = entry->cmt_author;
+
+ /* And now remove it from WORKING, because in wc-ng code
+ should read it from the lower layer */
+ working_node->repos_relpath = NULL;
+ working_node->repos_id = 0;
+ working_node->revision = SVN_INVALID_REVNUM;
+ }
+
+ SVN_ERR(insert_node(sdb, below_working_node, scratch_pool));
+ }
+
+ /* Insert the working node. */
+ if (working_node)
+ {
+ working_node->wc_id = wc_id;
+ working_node->local_relpath = local_relpath;
+ working_node->parent_relpath = parent_relpath;
+ working_node->changed_rev = SVN_INVALID_REVNUM;
+ working_node->recorded_time = entry->text_time;
+ working_node->recorded_size = entry->working_size;
+
+ if (entry->depth != svn_depth_exclude)
+ working_node->depth = entry->depth;
+ else
+ {
+ working_node->presence = svn_wc__db_status_excluded;
+ working_node->depth = svn_depth_infinity;
+ }
+
+ if (entry->kind == svn_node_dir)
+ working_node->checksum = NULL;
+ else
+ {
+ working_node->checksum = NULL;
+ /* text_base_info is NULL for files scheduled to be added. */
+ if (text_base_info)
+ working_node->checksum = text_base_info->normal_base.sha1_checksum;
+
+
+ /* If an MD5 checksum is present in the entry, we can verify that
+ * it matches the MD5 of the base file we found earlier. */
+#ifdef SVN_DEBUG
+ if (entry->checksum && text_base_info)
+ {
+ svn_checksum_t *md5_checksum;
+ SVN_ERR(svn_checksum_parse_hex(&md5_checksum, svn_checksum_md5,
+ entry->checksum, result_pool));
+ SVN_ERR_ASSERT(
+ md5_checksum && text_base_info->normal_base.md5_checksum);
+ SVN_ERR_ASSERT(svn_checksum_match(
+ md5_checksum, text_base_info->normal_base.md5_checksum));
+ }
+#endif
+ }
+
+ working_node->kind = entry->kind;
+ if (working_node->presence != svn_wc__db_status_excluded)
+ {
+ /* All subdirs start of incomplete, and stop being incomplete
+ when the entries file in the subdir is upgraded. */
+ if (entry->kind == svn_node_dir
+ && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
+ {
+ working_node->presence = svn_wc__db_status_incomplete;
+ working_node->kind = svn_node_dir;
+ }
+ else if (entry->schedule == svn_wc_schedule_delete)
+ {
+ working_node->presence = svn_wc__db_status_base_deleted;
+ working_node->kind = entry->kind;
+ }
+ else
+ {
+ /* presence == normal */
+ working_node->kind = entry->kind;
+
+ if (entry->incomplete)
+ {
+ /* We shouldn't be overwriting another status. */
+ WRITE_ENTRY_ASSERT(working_node->presence
+ == svn_wc__db_status_normal);
+ working_node->presence = svn_wc__db_status_incomplete;
+ }
+ }
+ }
+
+ /* These should generally be unset for added and deleted files,
+ and contain whatever information we have for copied files. Let's
+ just store whatever we have.
+
+ Note: cmt_rev is the distinguishing value. The others may be 0 or
+ NULL if the corresponding revprop has been deleted. */
+ if (working_node->presence != svn_wc__db_status_base_deleted)
+ {
+ working_node->changed_rev = entry->cmt_rev;
+ working_node->changed_date = entry->cmt_date;
+ working_node->changed_author = entry->cmt_author;
+ }
+
+ if (entry->schedule == svn_wc_schedule_delete
+ && parent_node->work
+ && parent_node->work->presence == svn_wc__db_status_base_deleted)
+ {
+ working_node->op_depth = parent_node->work->op_depth;
+ }
+ else if (!entry->copied)
+ {
+ working_node->op_depth
+ = svn_wc__db_op_depth_for_upgrade(local_relpath);
+ }
+
+ SVN_ERR(insert_node(sdb, working_node, scratch_pool));
+ }
+
+ /* Insert the actual node. */
+ if (actual_node)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+
+ actual_node->wc_id = wc_id;
+ actual_node->local_relpath = local_relpath;
+ actual_node->parent_relpath = parent_relpath;
+
+ SVN_ERR(insert_actual_node(sdb, db, tmp_entry_abspath,
+ actual_node, scratch_pool));
+ }
+
+ if (entry_node)
+ {
+ *entry_node = apr_palloc(result_pool, sizeof(**entry_node));
+ (*entry_node)->base = base_node;
+ (*entry_node)->work = working_node;
+ (*entry_node)->below_work = below_working_node;
+ (*entry_node)->tree_conflicts = tree_conflicts;
+ }
+
+ if (entry->file_external_path)
+ {
+ /* TODO: Maybe add a file external registration inside EXTERNALS here,
+ to allow removing file externals that aren't referenced from
+ svn:externals.
+
+ The svn:externals values are processed anyway after everything is
+ upgraded */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_actual_only_entries(apr_hash_t *tree_conflicts,
+ svn_sqlite__db_t *sdb,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_int64_t wc_id,
+ const char *parent_relpath,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, tree_conflicts);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ db_actual_node_t *actual_node = NULL;
+
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->wc_id = wc_id;
+ actual_node->local_relpath = svn__apr_hash_index_key(hi);
+ actual_node->parent_relpath = parent_relpath;
+ actual_node->tree_conflict_data = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(insert_actual_node(sdb, db, wri_abspath, actual_node,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__write_upgraded_entries(void **dir_baton,
+ void *parent_baton,
+ svn_wc__db_t *db,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t repos_id,
+ apr_int64_t wc_id,
+ const char *dir_abspath,
+ const char *new_root_abspath,
+ apr_hash_t *entries,
+ apr_hash_t *text_bases_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_wc_entry_t *this_dir;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const char *old_root_abspath, *dir_relpath;
+ struct write_baton *parent_node = parent_baton;
+ struct write_baton *dir_node;
+
+ /* Get a copy of the "this dir" entry for comparison purposes. */
+ this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+
+ /* If there is no "this dir" entry, something is wrong. */
+ if (! this_dir)
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("No default entry in directory '%s'"),
+ svn_dirent_local_style(dir_abspath,
+ iterpool));
+ old_root_abspath = svn_dirent_get_longest_ancestor(dir_abspath,
+ new_root_abspath,
+ scratch_pool);
+
+ SVN_ERR_ASSERT(old_root_abspath[0]);
+
+ dir_relpath = svn_dirent_skip_ancestor(old_root_abspath, dir_abspath);
+
+ /* Write out "this dir" */
+ SVN_ERR(write_entry(&dir_node, parent_node, db, sdb,
+ wc_id, repos_id, this_dir, NULL, dir_relpath,
+ svn_dirent_join(new_root_abspath, dir_relpath,
+ iterpool),
+ old_root_abspath,
+ this_dir, FALSE, result_pool, iterpool));
+
+ for (hi = apr_hash_first(scratch_pool, entries); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_wc_entry_t *this_entry = svn__apr_hash_index_val(hi);
+ const char *child_abspath, *child_relpath;
+ svn_wc__text_base_info_t *text_base_info
+ = svn_hash_gets(text_bases_info, name);
+
+ svn_pool_clear(iterpool);
+
+ /* Don't rewrite the "this dir" entry! */
+ if (strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0)
+ continue;
+
+ /* Write the entry. Pass TRUE for create locks, because we still
+ use this function for upgrading old working copies. */
+ child_abspath = svn_dirent_join(dir_abspath, name, iterpool);
+ child_relpath = svn_dirent_skip_ancestor(old_root_abspath, child_abspath);
+ SVN_ERR(write_entry(NULL, dir_node, db, sdb,
+ wc_id, repos_id,
+ this_entry, text_base_info, child_relpath,
+ svn_dirent_join(new_root_abspath, child_relpath,
+ iterpool),
+ old_root_abspath,
+ this_dir, TRUE, iterpool, iterpool));
+ }
+
+ if (dir_node->tree_conflicts)
+ SVN_ERR(write_actual_only_entries(dir_node->tree_conflicts, sdb, db,
+ new_root_abspath, wc_id, dir_relpath,
+ iterpool));
+
+ *dir_baton = dir_node;
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_wc_entry_t *
+svn_wc_entry_dup(const svn_wc_entry_t *entry, apr_pool_t *pool)
+{
+ svn_wc_entry_t *dupentry = apr_palloc(pool, sizeof(*dupentry));
+
+ /* Perform a trivial copy ... */
+ *dupentry = *entry;
+
+ /* ...and then re-copy stuff that needs to be duped into our pool. */
+ if (entry->name)
+ dupentry->name = apr_pstrdup(pool, entry->name);
+ if (entry->url)
+ dupentry->url = apr_pstrdup(pool, entry->url);
+ if (entry->repos)
+ dupentry->repos = apr_pstrdup(pool, entry->repos);
+ if (entry->uuid)
+ dupentry->uuid = apr_pstrdup(pool, entry->uuid);
+ if (entry->copyfrom_url)
+ dupentry->copyfrom_url = apr_pstrdup(pool, entry->copyfrom_url);
+ if (entry->conflict_old)
+ dupentry->conflict_old = apr_pstrdup(pool, entry->conflict_old);
+ if (entry->conflict_new)
+ dupentry->conflict_new = apr_pstrdup(pool, entry->conflict_new);
+ if (entry->conflict_wrk)
+ dupentry->conflict_wrk = apr_pstrdup(pool, entry->conflict_wrk);
+ if (entry->prejfile)
+ dupentry->prejfile = apr_pstrdup(pool, entry->prejfile);
+ if (entry->checksum)
+ dupentry->checksum = apr_pstrdup(pool, entry->checksum);
+ if (entry->cmt_author)
+ dupentry->cmt_author = apr_pstrdup(pool, entry->cmt_author);
+ if (entry->lock_token)
+ dupentry->lock_token = apr_pstrdup(pool, entry->lock_token);
+ if (entry->lock_owner)
+ dupentry->lock_owner = apr_pstrdup(pool, entry->lock_owner);
+ if (entry->lock_comment)
+ dupentry->lock_comment = apr_pstrdup(pool, entry->lock_comment);
+ if (entry->changelist)
+ dupentry->changelist = apr_pstrdup(pool, entry->changelist);
+
+ /* NOTE: we do not dup cachable_props or present_props since they
+ are deprecated. Use "" to indicate "nothing cachable or cached". */
+ dupentry->cachable_props = "";
+ dupentry->present_props = "";
+
+ if (entry->tree_conflict_data)
+ dupentry->tree_conflict_data = apr_pstrdup(pool,
+ entry->tree_conflict_data);
+ if (entry->file_external_path)
+ dupentry->file_external_path = apr_pstrdup(pool,
+ entry->file_external_path);
+ return dupentry;
+}
+
+
+/*** Generic Entry Walker */
+
+/* A recursive entry-walker, helper for svn_wc_walk_entries3().
+ *
+ * For this directory (DIRPATH, ADM_ACCESS), call the "found_entry" callback
+ * in WALK_CALLBACKS, passing WALK_BATON to it. Then, for each versioned
+ * entry in this directory, call the "found entry" callback and then recurse
+ * (if it is a directory and if DEPTH allows).
+ *
+ * If SHOW_HIDDEN is true, include entries that are in a 'deleted' or
+ * 'absent' state (and not scheduled for re-addition), else skip them.
+ *
+ * Call CANCEL_FUNC with CANCEL_BATON to allow cancellation.
+ */
+static svn_error_t *
+walker_helper(const char *dirpath,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_callbacks2_t *walk_callbacks,
+ void *walk_baton,
+ svn_depth_t depth,
+ svn_boolean_t show_hidden,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ svn_wc_entry_t *dot_entry;
+ svn_error_t *err;
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+
+ err = svn_wc__entries_read_internal(&entries, adm_access, show_hidden,
+ pool);
+
+ if (err)
+ SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool));
+
+ /* As promised, always return the '.' entry first. */
+ dot_entry = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+ if (! dot_entry)
+ return walk_callbacks->handle_error
+ (dirpath, svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("Directory '%s' has no THIS_DIR entry"),
+ svn_dirent_local_style(dirpath, pool)),
+ walk_baton, pool);
+
+ /* Call the "found entry" callback for this directory as a "this dir"
+ * entry. Note that if this directory has been reached by recursion, this
+ * is the second visit as it will already have been visited once as a
+ * child entry of its parent. */
+
+ err = walk_callbacks->found_entry(dirpath, dot_entry, walk_baton, subpool);
+
+
+ if(err)
+ SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool));
+
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* Loop over each of the other entries. */
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_wc_entry_t *current_entry = svn__apr_hash_index_val(hi);
+ const char *entrypath;
+ const char *entry_abspath;
+ svn_boolean_t hidden;
+
+ svn_pool_clear(subpool);
+
+ /* See if someone wants to cancel this operation. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Skip the "this dir" entry. */
+ if (strcmp(current_entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
+ continue;
+
+ entrypath = svn_dirent_join(dirpath, name, subpool);
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden, current_entry));
+ SVN_ERR(svn_dirent_get_absolute(&entry_abspath, entrypath, subpool));
+
+ /* Call the "found entry" callback for this entry. (For a directory,
+ * this is the first visit: as a child.) */
+ if (current_entry->kind == svn_node_file
+ || depth >= svn_depth_immediates)
+ {
+ err = walk_callbacks->found_entry(entrypath, current_entry,
+ walk_baton, subpool);
+
+ if (err)
+ SVN_ERR(walk_callbacks->handle_error(entrypath, err,
+ walk_baton, pool));
+ }
+
+ /* Recurse into this entry if appropriate. */
+ if (current_entry->kind == svn_node_dir
+ && !hidden
+ && depth >= svn_depth_immediates)
+ {
+ svn_wc_adm_access_t *entry_access;
+ svn_depth_t depth_below_here = depth;
+
+ if (depth == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ entry_access = svn_wc__adm_retrieve_internal2(db, entry_abspath,
+ subpool);
+
+ if (entry_access)
+ SVN_ERR(walker_helper(entrypath, entry_access,
+ walk_callbacks, walk_baton,
+ depth_below_here, show_hidden,
+ cancel_func, cancel_baton,
+ subpool));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__walker_default_error_handler(const char *path,
+ svn_error_t *err,
+ void *walk_baton,
+ apr_pool_t *pool)
+{
+ /* Note: don't trace this. We don't want to insert a false "stack frame"
+ onto an error generated elsewhere. */
+ return svn_error_trace(err);
+}
+
+
+/* The public API. */
+svn_error_t *
+svn_wc_walk_entries3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_callbacks2_t *walk_callbacks,
+ void *walk_baton,
+ svn_depth_t walk_depth,
+ svn_boolean_t show_hidden,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ svn_error_t *err;
+ svn_node_kind_t kind;
+ svn_depth_t depth;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ err = svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &depth, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ pool, pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ /* Remap into SVN_ERR_UNVERSIONED_RESOURCE. */
+ svn_error_clear(err);
+ return walk_callbacks->handle_error(
+ path, svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath, pool)),
+ walk_baton, pool);
+ }
+
+ if (kind == svn_node_file || depth == svn_depth_exclude)
+ {
+ const svn_wc_entry_t *entry;
+
+ /* ### we should stop passing out entry structures.
+ ###
+ ### we should not call handle_error for an error the *callback*
+ ### gave us. let it deal with the problem before returning. */
+
+ if (!show_hidden)
+ {
+ svn_boolean_t hidden;
+ SVN_ERR(svn_wc__db_node_hidden(&hidden, db, local_abspath, pool));
+
+ if (hidden)
+ {
+ /* The fool asked to walk a "hidden" node. Report the node as
+ unversioned.
+
+ ### this is incorrect behavior. see depth_test 36. the walk
+ ### API will be revamped to avoid entry structures. we should
+ ### be able to solve the problem with the new API. (since we
+ ### shouldn't return a hidden entry here) */
+ return walk_callbacks->handle_error(
+ path, svn_error_createf(
+ SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath, pool)),
+ walk_baton, pool);
+ }
+ }
+
+ SVN_ERR(svn_wc__get_entry(&entry, db, local_abspath, FALSE,
+ svn_node_file, pool, pool));
+
+ err = walk_callbacks->found_entry(path, entry, walk_baton, pool);
+ if (err)
+ return walk_callbacks->handle_error(path, err, walk_baton, pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ if (kind == svn_node_dir)
+ return walker_helper(path, adm_access, walk_callbacks, walk_baton,
+ walk_depth, show_hidden, cancel_func, cancel_baton,
+ pool);
+
+ return walk_callbacks->handle_error(
+ path, svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("'%s' has an unrecognized node kind"),
+ svn_dirent_local_style(local_abspath, pool)),
+ walk_baton, pool);
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/entries.h b/contrib/subversion/subversion/libsvn_wc/entries.h
new file mode 100644
index 0000000..87caa46
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/entries.h
@@ -0,0 +1,164 @@
+/*
+ * entries.h : manipulating entries
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_WC_ENTRIES_H
+#define SVN_LIBSVN_WC_ENTRIES_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+
+#include "wc_db.h"
+#include "private/svn_sqlite.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** Get an ENTRY for the given LOCAL_ABSPATH.
+ *
+ * This API does not require an access baton, just a wc_db handle (DB).
+ * The requested entry MUST be present and version-controlled when
+ * ALLOW_UNVERSIONED is FALSE; otherwise, SVN_ERR_WC_PATH_NOT_FOUND is
+ * returned. When ALLOW_UNVERSIONED is TRUE, and the node is not under
+ * version control, *ENTRY will be set to NULL (this is easier for callers
+ * to handle, than detecting the error and clearing it).
+ *
+ * If you know the entry is a FILE or DIR, then specify that in KIND. If you
+ * are unsure, then specify 'svn_node_unknown' for KIND. This value will be
+ * used to optimize the access to the entry, so it is best to know the kind.
+ * If you specify FILE/DIR, and the entry is *something else*, then
+ * SVN_ERR_NODE_UNEXPECTED_KIND will be returned.
+ *
+ * If KIND == UNKNOWN, and you request the parent stub, and the node turns
+ * out to NOT be a directory, then SVN_ERR_NODE_UNEXPECTED_KIND is returned.
+ *
+ * NOTE: if SVN_ERR_NODE_UNEXPECTED_KIND is returned, then the ENTRY *IS*
+ * valid and may be examined. For any other error, ENTRY *IS NOT* valid.
+ *
+ * NOTE: if an access baton is available, then it will be examined for
+ * cached entries (and this routine may even cache them for you). It is
+ * not required, however, to do any access baton management for this API.
+ *
+ * ENTRY will be allocated in RESULT_POOL, and all temporary allocations
+ * will be performed in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__get_entry(const svn_wc_entry_t **entry,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t allow_unversioned,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Is ENTRY in a 'hidden' state in the sense of the 'show_hidden'
+ * switches on svn_wc_entries_read(), svn_wc_walk_entries*(), etc.? */
+svn_error_t *
+svn_wc__entry_is_hidden(svn_boolean_t *hidden, const svn_wc_entry_t *entry);
+
+
+/* The checksums of one pre-1.7 text-base file. If the text-base file
+ * exists, both checksums are filled in, otherwise both fields are NULL. */
+typedef struct svn_wc__text_base_file_info_t
+{
+ svn_checksum_t *sha1_checksum;
+ svn_checksum_t *md5_checksum;
+} svn_wc__text_base_file_info_t;
+
+/* The text-base checksums of the normal base and/or the revert-base of one
+ * pre-1.7 versioned text file. */
+typedef struct svn_wc__text_base_info_t
+{
+ svn_wc__text_base_file_info_t normal_base;
+ svn_wc__text_base_file_info_t revert_base;
+} svn_wc__text_base_info_t;
+
+/* For internal use by upgrade.c to write entries in the wc-ng format.
+ Return in DIR_BATON the baton to be passed as PARENT_BATON when
+ upgrading child directories. Pass a NULL PARENT_BATON when upgrading
+ the root directory.
+
+ TEXT_BASES_INFO is a hash of information about all the text bases found
+ in this directory's admin area, keyed on (const char *) name of the
+ versioned file, with (svn_wc__text_base_info_t *) values. */
+svn_error_t *
+svn_wc__write_upgraded_entries(void **dir_baton,
+ void *parent_baton,
+ svn_wc__db_t *db,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t repos_id,
+ apr_int64_t wc_id,
+ const char *dir_abspath,
+ const char *new_root_abspath,
+ apr_hash_t *entries,
+ apr_hash_t *text_bases_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Parse a file external specification in the NULL terminated STR and
+ place the path in PATH_RESULT, the peg revision in PEG_REV_RESULT
+ and revision number in REV_RESULT. STR may be NULL, in which case
+ PATH_RESULT will be set to NULL and both PEG_REV_RESULT and
+ REV_RESULT set to svn_opt_revision_unspecified.
+
+ The format that is read is the same as a working-copy path with a
+ peg revision; see svn_opt_parse_path(). */
+svn_error_t *
+svn_wc__unserialize_file_external(const char **path_result,
+ svn_opt_revision_t *peg_rev_result,
+ svn_opt_revision_t *rev_result,
+ const char *str,
+ apr_pool_t *pool);
+
+/* Serialize into STR the file external path, peg revision number and
+ the operative revision number into a format that
+ unserialize_file_external() can parse. The format is
+ %{peg_rev}:%{rev}:%{path}
+ where a rev will either be HEAD or the string revision number. If
+ PATH is NULL then STR will be set to NULL. This method writes to a
+ string instead of a svn_stringbuf_t so that the string can be
+ protected by write_str(). */
+svn_error_t *
+svn_wc__serialize_file_external(const char **str,
+ const char *path,
+ const svn_opt_revision_t *peg_rev,
+ const svn_opt_revision_t *rev,
+ apr_pool_t *pool);
+
+/* Non-deprecated wrapper variant of svn_wc_entries_read used implement
+ legacy API functions. See svn_wc_entries_read for a detailed description.
+ */
+svn_error_t *
+svn_wc__entries_read_internal(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_ENTRIES_H */
diff --git a/contrib/subversion/subversion/libsvn_wc/externals.c b/contrib/subversion/subversion/libsvn_wc/externals.c
new file mode 100644
index 0000000..514148fe
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/externals.c
@@ -0,0 +1,1686 @@
+/*
+ * externals.c : routines dealing with (file) externals in the working copy
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_general.h>
+#include <apr_uri.h>
+
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_string.h"
+#include "svn_time.h"
+#include "svn_types.h"
+#include "svn_wc.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_subr_private.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "props.h"
+#include "translate.h"
+#include "workqueue.h"
+#include "conflicts.h"
+
+#include "svn_private_config.h"
+
+/** Externals **/
+
+/*
+ * Look for either
+ *
+ * -r N
+ * -rN
+ *
+ * in the LINE_PARTS array and update the revision field in ITEM with
+ * the revision if the revision is found. Set REV_IDX to the index in
+ * LINE_PARTS where the revision specification starts. Remove from
+ * LINE_PARTS the element(s) that specify the revision.
+ * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error
+ * string.
+ *
+ * If this function returns successfully, then LINE_PARTS will have
+ * only two elements in it.
+ */
+static svn_error_t *
+find_and_remove_externals_revision(int *rev_idx,
+ const char **line_parts,
+ int num_line_parts,
+ svn_wc_external_item2_t *item,
+ const char *parent_directory_display,
+ const char *line,
+ apr_pool_t *pool)
+{
+ int i;
+
+ for (i = 0; i < 2; ++i)
+ {
+ const char *token = line_parts[i];
+
+ if (token[0] == '-' && token[1] == 'r')
+ {
+ svn_opt_revision_t end_revision = { svn_opt_revision_unspecified };
+ const char *digits_ptr;
+ int shift_count;
+ int j;
+
+ *rev_idx = i;
+
+ if (token[2] == '\0')
+ {
+ /* There must be a total of four elements in the line if
+ -r N is used. */
+ if (num_line_parts != 4)
+ goto parse_error;
+
+ shift_count = 2;
+ digits_ptr = line_parts[i+1];
+ }
+ else
+ {
+ /* There must be a total of three elements in the line
+ if -rN is used. */
+ if (num_line_parts != 3)
+ goto parse_error;
+
+ shift_count = 1;
+ digits_ptr = token+2;
+ }
+
+ if (svn_opt_parse_revision(&item->revision,
+ &end_revision,
+ digits_ptr, pool) != 0)
+ goto parse_error;
+ /* We want a single revision, not a range. */
+ if (end_revision.kind != svn_opt_revision_unspecified)
+ goto parse_error;
+ /* Allow only numbers and dates, not keywords. */
+ if (item->revision.kind != svn_opt_revision_number
+ && item->revision.kind != svn_opt_revision_date)
+ goto parse_error;
+
+ /* Shift any line elements past the revision specification
+ down over the revision specification. */
+ for (j = i; j < num_line_parts-shift_count; ++j)
+ line_parts[j] = line_parts[j+shift_count];
+ line_parts[num_line_parts-shift_count] = NULL;
+
+ /* Found the revision, so leave the function immediately, do
+ * not continue looking for additional revisions. */
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* No revision was found, so there must be exactly two items in the
+ line array. */
+ if (num_line_parts == 2)
+ return SVN_NO_ERROR;
+
+ parse_error:
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Error parsing %s property on '%s': '%s'"),
+ SVN_PROP_EXTERNALS,
+ parent_directory_display,
+ line);
+}
+
+svn_error_t *
+svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
+ const char *parent_directory,
+ const char *desc,
+ svn_boolean_t canonicalize_url,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_array_header_t *externals = NULL;
+ apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool);
+ const char *parent_directory_display = svn_path_is_url(parent_directory) ?
+ parent_directory : svn_dirent_local_style(parent_directory, pool);
+
+ /* If an error occurs halfway through parsing, *externals_p should stay
+ * untouched. So, store the list in a local var first. */
+ if (externals_p)
+ externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *));
+
+ for (i = 0; i < lines->nelts; i++)
+ {
+ const char *line = APR_ARRAY_IDX(lines, i, const char *);
+ apr_status_t status;
+ char **line_parts;
+ int num_line_parts;
+ svn_wc_external_item2_t *item;
+ const char *token0;
+ const char *token1;
+ svn_boolean_t token0_is_url;
+ svn_boolean_t token1_is_url;
+
+ /* Index into line_parts where the revision specification
+ started. */
+ int rev_idx = -1;
+
+ if ((! line) || (line[0] == '#'))
+ continue;
+
+ /* else proceed */
+
+ status = apr_tokenize_to_argv(line, &line_parts, pool);
+ if (status)
+ return svn_error_wrap_apr(status,
+ _("Can't split line into components: '%s'"),
+ line);
+ /* Count the number of tokens. */
+ for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++)
+ ;
+
+ SVN_ERR(svn_wc_external_item2_create(&item, pool));
+ item->revision.kind = svn_opt_revision_unspecified;
+ item->peg_revision.kind = svn_opt_revision_unspecified;
+
+ /*
+ * There are six different formats of externals:
+ *
+ * 1) DIR URL
+ * 2) DIR -r N URL
+ * 3) DIR -rN URL
+ * 4) URL DIR
+ * 5) -r N URL DIR
+ * 6) -rN URL DIR
+ *
+ * The last three allow peg revisions in the URL.
+ *
+ * With relative URLs and no '-rN' or '-r N', there is no way to
+ * distinguish between 'DIR URL' and 'URL DIR' when URL is a
+ * relative URL like /svn/repos/trunk, so this case is taken as
+ * case 4).
+ */
+ if (num_line_parts < 2 || num_line_parts > 4)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Error parsing %s property on '%s': '%s'"),
+ SVN_PROP_EXTERNALS,
+ parent_directory_display,
+ line);
+
+ /* To make it easy to check for the forms, find and remove -r N
+ or -rN from the line item array. If it is found, rev_idx
+ contains the index into line_parts where '-r' was found and
+ set item->revision to the parsed revision. */
+ /* ### ugh. stupid cast. */
+ SVN_ERR(find_and_remove_externals_revision(&rev_idx,
+ (const char **)line_parts,
+ num_line_parts, item,
+ parent_directory_display,
+ line, pool));
+
+ token0 = line_parts[0];
+ token1 = line_parts[1];
+
+ token0_is_url = svn_path_is_url(token0);
+ token1_is_url = svn_path_is_url(token1);
+
+ if (token0_is_url && token1_is_url)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Invalid %s property on '%s': "
+ "cannot use two absolute URLs ('%s' and '%s') in an external; "
+ "one must be a path where an absolute or relative URL is "
+ "checked out to"),
+ SVN_PROP_EXTERNALS, parent_directory_display, token0, token1);
+
+ if (0 == rev_idx && token1_is_url)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Invalid %s property on '%s': "
+ "cannot use a URL '%s' as the target directory for an external "
+ "definition"),
+ SVN_PROP_EXTERNALS, parent_directory_display, token1);
+
+ if (1 == rev_idx && token0_is_url)
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Invalid %s property on '%s': "
+ "cannot use a URL '%s' as the target directory for an external "
+ "definition"),
+ SVN_PROP_EXTERNALS, parent_directory_display, token0);
+
+ /* The appearence of -r N or -rN forces the type of external.
+ If -r is at the beginning of the line or the first token is
+ an absolute URL or if the second token is not an absolute
+ URL, then the URL supports peg revisions. */
+ if (0 == rev_idx ||
+ (-1 == rev_idx && (token0_is_url || ! token1_is_url)))
+ {
+ /* The URL is passed to svn_opt_parse_path in
+ uncanonicalized form so that the scheme relative URL
+ //hostname/foo is not collapsed to a server root relative
+ URL /hostname/foo. */
+ SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url,
+ token0, pool));
+ item->target_dir = token1;
+ }
+ else
+ {
+ item->target_dir = token0;
+ item->url = token1;
+ item->peg_revision = item->revision;
+ }
+
+ SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision,
+ &item->revision, TRUE, FALSE,
+ pool));
+
+ item->target_dir = svn_dirent_internal_style(item->target_dir, pool);
+
+ if (item->target_dir[0] == '\0'
+ || svn_dirent_is_absolute(item->target_dir)
+ || svn_path_is_backpath_present(item->target_dir)
+ || !svn_dirent_skip_ancestor("dummy",
+ svn_dirent_join("dummy",
+ item->target_dir,
+ pool)))
+ return svn_error_createf
+ (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("Invalid %s property on '%s': "
+ "target '%s' is an absolute path or involves '..'"),
+ SVN_PROP_EXTERNALS,
+ parent_directory_display,
+ item->target_dir);
+
+ if (canonicalize_url)
+ {
+ /* Uh... this is stupid. But it's consistent with what our
+ code did before we split up the relpath/dirent/uri APIs.
+ Still, given this, it's no wonder that our own libraries
+ don't ask this function to canonicalize the results. */
+ if (svn_path_is_url(item->url))
+ item->url = svn_uri_canonicalize(item->url, pool);
+ else
+ item->url = svn_dirent_canonicalize(item->url, pool);
+ }
+
+ if (externals)
+ APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item;
+ }
+
+ if (externals_p)
+ *externals_p = externals;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets,
+ apr_array_header_t *externals,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ unsigned int len;
+ unsigned int len2;
+ const char *target;
+ apr_hash_t *targets = apr_hash_make(scratch_pool);
+ apr_hash_t *targets2 = NULL;
+ *duplicate_targets = NULL;
+
+ for (i = 0; i < externals->nelts; i++)
+ {
+ target = APR_ARRAY_IDX(externals, i,
+ svn_wc_external_item2_t*)->target_dir;
+ len = apr_hash_count(targets);
+ svn_hash_sets(targets, target, "");
+ if (len == apr_hash_count(targets))
+ {
+ /* Hashtable length is unchanged. This must be a duplicate. */
+
+ /* Collapse multiple duplicates of the same target by using a second
+ * hash layer. */
+ if (! targets2)
+ targets2 = apr_hash_make(scratch_pool);
+ len2 = apr_hash_count(targets2);
+ svn_hash_sets(targets2, target, "");
+ if (len2 < apr_hash_count(targets2))
+ {
+ /* The second hash list just got bigger, i.e. this target has
+ * not been counted as duplicate before. */
+ if (! *duplicate_targets)
+ {
+ *duplicate_targets = apr_array_make(
+ pool, 1, sizeof(svn_wc_external_item2_t*));
+ }
+ APR_ARRAY_PUSH((*duplicate_targets), const char *) = target;
+ }
+ /* Else, this same target has already been recorded as a duplicate,
+ * don't count it again. */
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+struct edit_baton
+{
+ apr_pool_t *pool;
+ svn_wc__db_t *db;
+
+ /* We explicitly use wri_abspath and local_abspath here, because we
+ might want to install file externals in an obstructing working copy */
+ const char *wri_abspath; /* The working defining the file external */
+ const char *local_abspath; /* The file external itself */
+ const char *name; /* The basename of the file external itself */
+
+ /* Information from the caller */
+ svn_boolean_t use_commit_times;
+ const apr_array_header_t *ext_patterns;
+ const char *diff3cmd;
+
+ const char *url;
+ const char *repos_root_url;
+ const char *repos_uuid;
+
+ const char *record_ancestor_abspath;
+ const char *recorded_repos_relpath;
+ svn_revnum_t recorded_peg_revision;
+ svn_revnum_t recorded_revision;
+
+ /* Introducing a new file external */
+ svn_boolean_t added;
+
+ svn_wc_conflict_resolver_func2_t conflict_func;
+ void *conflict_baton;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+
+ svn_revnum_t *target_revision;
+
+ /* What was there before the update */
+ svn_revnum_t original_revision;
+ const svn_checksum_t *original_checksum;
+
+ /* What we are installing now */
+ const char *new_pristine_abspath;
+ svn_checksum_t *new_sha1_checksum;
+ svn_checksum_t *new_md5_checksum;
+
+ /* List of incoming propchanges */
+ apr_array_header_t *propchanges;
+
+ /* Array of svn_prop_inherited_item_t * structures representing the
+ properties inherited by the base node at LOCAL_ABSPATH. */
+ apr_array_header_t *iprops;
+
+ /* The last change information */
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+
+ svn_boolean_t had_props;
+
+ svn_boolean_t file_closed;
+};
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ *eb->target_revision = target_revision;
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **root_baton)
+{
+ *root_baton = edit_baton;
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ struct edit_baton *eb = parent_baton;
+ if (strcmp(path, eb->name))
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("This editor can only update '%s'"),
+ svn_dirent_local_style(eb->local_abspath,
+ file_pool));
+
+ *file_baton = eb;
+ eb->original_revision = SVN_INVALID_REVNUM;
+ eb->added = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ struct edit_baton *eb = parent_baton;
+ svn_node_kind_t kind;
+ if (strcmp(path, eb->name))
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("This editor can only update '%s'"),
+ svn_dirent_local_style(eb->local_abspath,
+ file_pool));
+
+ *file_baton = eb;
+ SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
+ NULL, NULL, NULL, &eb->changed_rev,
+ &eb->changed_date, &eb->changed_author,
+ NULL, &eb->original_checksum, NULL, NULL,
+ &eb->had_props, NULL, NULL,
+ eb->db, eb->local_abspath,
+ eb->pool, file_pool));
+
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Node '%s' is no existing file external"),
+ svn_dirent_local_style(eb->local_abspath,
+ file_pool));
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum_digest,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct edit_baton *eb = file_baton;
+ svn_stream_t *src_stream;
+ svn_stream_t *dest_stream;
+
+ if (eb->original_checksum)
+ {
+ if (base_checksum_digest)
+ {
+ svn_checksum_t *expected_checksum;
+ const svn_checksum_t *original_md5;
+
+ SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
+ base_checksum_digest, pool));
+
+ if (eb->original_checksum->kind != svn_checksum_md5)
+ SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
+ eb->db, eb->wri_abspath,
+ eb->original_checksum,
+ pool, pool));
+ else
+ original_md5 = eb->original_checksum;
+
+ if (!svn_checksum_match(expected_checksum, original_md5))
+ return svn_error_trace(svn_checksum_mismatch_err(
+ expected_checksum,
+ original_md5,
+ pool,
+ _("Base checksum mismatch for '%s'"),
+ svn_dirent_local_style(eb->local_abspath,
+ pool)));
+ }
+
+ SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
+ eb->wri_abspath, eb->original_checksum,
+ pool, pool));
+ }
+ else
+ src_stream = svn_stream_empty(pool);
+
+ SVN_ERR(svn_wc__open_writable_base(&dest_stream, &eb->new_pristine_abspath,
+ &eb->new_md5_checksum,
+ &eb->new_sha1_checksum,
+ eb->db, eb->wri_abspath,
+ eb->pool, pool));
+
+ svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
+ handler, handler_baton);
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = file_baton;
+ svn_prop_t *propchange;
+
+ propchange = apr_array_push(eb->propchanges);
+ propchange->name = apr_pstrdup(eb->pool, name);
+ propchange->value = value ? svn_string_dup(value, eb->pool) : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+close_file(void *file_baton,
+ const char *expected_md5_digest,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = file_baton;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
+ svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
+ svn_boolean_t obstructed = FALSE;
+
+ eb->file_closed = TRUE; /* We bump the revision here */
+
+ /* Check the checksum, if provided */
+ if (expected_md5_digest)
+ {
+ svn_checksum_t *expected_md5_checksum;
+ const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
+
+ SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
+ expected_md5_digest, pool));
+
+ if (actual_md5_checksum == NULL)
+ {
+ actual_md5_checksum = eb->original_checksum;
+
+ if (actual_md5_checksum != NULL
+ && actual_md5_checksum->kind != svn_checksum_md5)
+ {
+ SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
+ eb->db, eb->wri_abspath,
+ actual_md5_checksum,
+ pool, pool));
+ }
+ }
+
+ if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
+ return svn_checksum_mismatch_err(
+ expected_md5_checksum,
+ actual_md5_checksum, pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(eb->local_abspath, pool));
+ }
+
+ /* First move the file in the pristine store; this hands over the cleanup
+ behavior to the pristine store. */
+ if (eb->new_sha1_checksum)
+ {
+ SVN_ERR(svn_wc__db_pristine_install(eb->db, eb->new_pristine_abspath,
+ eb->new_sha1_checksum,
+ eb->new_md5_checksum, pool));
+
+ eb->new_pristine_abspath = NULL;
+ }
+
+ /* Merge the changes */
+ {
+ svn_skel_t *all_work_items = NULL;
+ svn_skel_t *conflict_skel = NULL;
+ svn_skel_t *work_item;
+ apr_hash_t *base_props = NULL;
+ apr_hash_t *actual_props = NULL;
+ apr_hash_t *new_pristine_props = NULL;
+ apr_hash_t *new_actual_props = NULL;
+ apr_hash_t *new_dav_props = NULL;
+ const svn_checksum_t *new_checksum = NULL;
+ const svn_checksum_t *original_checksum = NULL;
+
+ svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
+ const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url,
+ eb->url, pool);
+
+ if (! added)
+ {
+ new_checksum = eb->original_checksum;
+
+ if (eb->had_props)
+ SVN_ERR(svn_wc__db_base_get_props(
+ &base_props, eb->db, eb->local_abspath, pool, pool));
+
+ SVN_ERR(svn_wc__db_read_props(
+ &actual_props, eb->db, eb->local_abspath, pool, pool));
+ }
+
+ if (!base_props)
+ base_props = apr_hash_make(pool);
+
+ if (!actual_props)
+ actual_props = apr_hash_make(pool);
+
+ if (eb->new_sha1_checksum)
+ new_checksum = eb->new_sha1_checksum;
+
+ /* Merge the properties */
+ {
+ apr_array_header_t *entry_prop_changes;
+ apr_array_header_t *dav_prop_changes;
+ apr_array_header_t *regular_prop_changes;
+ int i;
+
+ SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes,
+ &dav_prop_changes, &regular_prop_changes,
+ pool));
+
+ /* Read the entry-prop changes to update the last-changed info. */
+ for (i = 0; i < entry_prop_changes->nelts; i++)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i,
+ svn_prop_t);
+
+ if (! prop->value)
+ continue; /* authz or something */
+
+ if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
+ eb->changed_author = apr_pstrdup(pool, prop->value->data);
+ else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
+ {
+ apr_int64_t rev;
+ SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
+ eb->changed_rev = (svn_revnum_t)rev;
+ }
+ else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
+ SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data,
+ pool));
+ }
+
+ /* Store the DAV-prop (aka WC-prop) changes. (This treats a list
+ * of changes as a list of new props, but we only use this when
+ * adding a new file and it's equivalent in that case.) */
+ if (dav_prop_changes->nelts > 0)
+ new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool);
+
+ /* Merge the regular prop changes. */
+ if (regular_prop_changes->nelts > 0)
+ {
+ new_pristine_props = svn_prop__patch(base_props, regular_prop_changes,
+ pool);
+ SVN_ERR(svn_wc__merge_props(&conflict_skel,
+ &prop_state,
+ &new_actual_props,
+ eb->db, eb->local_abspath,
+ NULL /* server_baseprops*/,
+ base_props,
+ actual_props,
+ regular_prop_changes,
+ pool, pool));
+ }
+ else
+ {
+ new_pristine_props = base_props;
+ new_actual_props = actual_props;
+ }
+ }
+
+ /* Merge the text */
+ if (eb->new_sha1_checksum)
+ {
+ svn_node_kind_t disk_kind;
+ svn_boolean_t install_pristine = FALSE;
+ const char *install_from = NULL;
+
+ SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool));
+
+ if (disk_kind == svn_node_none)
+ {
+ /* Just install the file */
+ install_pristine = TRUE;
+ content_state = svn_wc_notify_state_changed;
+ }
+ else if (disk_kind != svn_node_file
+ || (eb->added && disk_kind == svn_node_file))
+ {
+ /* The node is obstructed; we just change the DB */
+ obstructed = TRUE;
+ content_state = svn_wc_notify_state_unchanged;
+ }
+ else
+ {
+ svn_boolean_t is_mod;
+ SVN_ERR(svn_wc__internal_file_modified_p(&is_mod,
+ eb->db, eb->local_abspath,
+ FALSE, pool));
+
+ if (!is_mod)
+ {
+ install_pristine = TRUE;
+ content_state = svn_wc_notify_state_changed;
+ }
+ else
+ {
+ svn_boolean_t found_text_conflict;
+
+ /* Ok, we have to do some work to merge a local change */
+ SVN_ERR(svn_wc__perform_file_merge(&work_item,
+ &conflict_skel,
+ &found_text_conflict,
+ eb->db,
+ eb->local_abspath,
+ eb->wri_abspath,
+ new_checksum,
+ original_checksum,
+ actual_props,
+ eb->ext_patterns,
+ eb->original_revision,
+ *eb->target_revision,
+ eb->propchanges,
+ eb->diff3cmd,
+ eb->cancel_func,
+ eb->cancel_baton,
+ pool, pool));
+
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ pool);
+
+ if (found_text_conflict)
+ content_state = svn_wc_notify_state_conflicted;
+ else
+ content_state = svn_wc_notify_state_merged;
+ }
+ }
+ if (install_pristine)
+ {
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db,
+ eb->local_abspath,
+ install_from,
+ eb->use_commit_times, TRUE,
+ pool, pool));
+
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
+ }
+ }
+ else
+ {
+ content_state = svn_wc_notify_state_unchanged;
+ /* ### Retranslate on magic property changes, etc. */
+ }
+
+ /* Generate a conflict description, if needed */
+ if (conflict_skel)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(
+ conflict_skel,
+ svn_wc_conflict_version_create2(
+ eb->repos_root_url,
+ eb->repos_uuid,
+ repos_relpath,
+ eb->original_revision,
+ svn_node_file,
+ pool),
+ svn_wc_conflict_version_create2(
+ eb->repos_root_url,
+ eb->repos_uuid,
+ repos_relpath,
+ *eb->target_revision,
+ svn_node_file,
+ pool),
+ pool, pool));
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ eb->db, eb->local_abspath,
+ conflict_skel,
+ pool, pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ pool);
+ }
+
+ /* Install the file in the DB */
+ SVN_ERR(svn_wc__db_external_add_file(
+ eb->db,
+ eb->local_abspath,
+ eb->wri_abspath,
+ repos_relpath,
+ eb->repos_root_url,
+ eb->repos_uuid,
+ *eb->target_revision,
+ new_pristine_props,
+ eb->iprops,
+ eb->changed_rev,
+ eb->changed_date,
+ eb->changed_author,
+ new_checksum,
+ new_dav_props,
+ eb->record_ancestor_abspath,
+ eb->recorded_repos_relpath,
+ eb->recorded_peg_revision,
+ eb->recorded_revision,
+ TRUE, new_actual_props,
+ FALSE /* keep_recorded_info */,
+ conflict_skel,
+ all_work_items,
+ pool));
+
+ /* close_edit may also update iprops for switched files, catching
+ those for which close_file is never called (e.g. an update of a
+ file external with no changes). So as a minor optimization we
+ clear the iprops so as not to set them again in close_edit. */
+ eb->iprops = NULL;
+
+ /* Run the work queue to complete the installation */
+ SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath,
+ eb->cancel_func, eb->cancel_baton, pool));
+ }
+
+ /* Notify */
+ if (eb->notify_func)
+ {
+ svn_wc_notify_action_t action;
+ svn_wc_notify_t *notify;
+
+ if (!eb->added)
+ action = obstructed ? svn_wc_notify_update_shadowed_update
+ : svn_wc_notify_update_update;
+ else
+ action = obstructed ? svn_wc_notify_update_shadowed_add
+ : svn_wc_notify_update_add;
+
+ notify = svn_wc_create_notify(eb->local_abspath, action, pool);
+ notify->kind = svn_node_file;
+
+ notify->revision = *eb->target_revision;
+ notify->prop_state = prop_state;
+ notify->content_state = content_state;
+
+ notify->old_revision = eb->original_revision;
+
+ eb->notify_func(eb->notify_baton, notify, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function for svn_wc__get_file_external_editor */
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ if (!eb->file_closed
+ || eb->iprops)
+ {
+ apr_hash_t *wcroot_iprops = NULL;
+
+ if (eb->iprops)
+ {
+ wcroot_iprops = apr_hash_make(pool);
+ svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
+ }
+
+ /* The node wasn't updated, so we just have to bump its revision */
+ SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
+ eb->local_abspath,
+ svn_depth_infinity,
+ NULL, NULL, NULL,
+ *eb->target_revision,
+ apr_hash_make(pool),
+ wcroot_iprops,
+ eb->notify_func,
+ eb->notify_baton,
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *url,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ apr_array_header_t *iprops,
+ svn_boolean_t use_commit_times,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ const char *record_ancestor_abspath,
+ const char *recorded_url,
+ const svn_opt_revision_t *recorded_peg_rev,
+ const svn_opt_revision_t *recorded_rev,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ apr_pool_t *edit_pool = result_pool;
+ struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
+
+ eb->pool = edit_pool;
+ eb->db = db;
+ eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
+ if (wri_abspath)
+ eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
+ else
+ eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
+ eb->name = svn_dirent_basename(eb->local_abspath, NULL);
+ eb->target_revision = target_revision;
+
+ eb->url = apr_pstrdup(edit_pool, url);
+ eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
+ eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
+
+ eb->iprops = iprops;
+
+ eb->use_commit_times = use_commit_times;
+ eb->ext_patterns = preserved_exts;
+ eb->diff3cmd = diff3_cmd;
+
+ eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
+ eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
+ edit_pool);
+
+ eb->changed_rev = SVN_INVALID_REVNUM;
+
+ if (recorded_peg_rev->kind == svn_opt_revision_number)
+ eb->recorded_peg_revision = recorded_peg_rev->value.number;
+ else
+ eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
+
+ if (recorded_rev->kind == svn_opt_revision_number)
+ eb->recorded_revision = recorded_rev->value.number;
+ else
+ eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
+
+ eb->conflict_func = conflict_func;
+ eb->conflict_baton = conflict_baton;
+ eb->cancel_func = cancel_func;
+ eb->cancel_baton = cancel_baton;
+ eb->notify_func = notify_func;
+ eb->notify_baton = notify_baton;
+
+ eb->propchanges = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
+
+ tree_editor->open_root = open_root;
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->close_edit = close_edit;
+
+ return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
+ tree_editor, eb,
+ editor, edit_baton,
+ result_pool);
+}
+
+svn_error_t *
+svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_ra_reporter3_t *reporter,
+ void *report_baton,
+ svn_boolean_t restore_files,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_error_t *err;
+ svn_node_kind_t kind;
+ svn_wc__db_lock_t *lock;
+ svn_revnum_t revision;
+ const char *repos_root_url;
+ const char *repos_relpath;
+ svn_boolean_t update_root;
+
+ err = svn_wc__db_base_get_info(NULL, &kind, &revision,
+ &repos_relpath, &repos_root_url, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, &lock,
+ NULL, NULL, &update_root,
+ db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err
+ || kind == svn_node_dir
+ || !update_root)
+ {
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ /* We don't know about this node, so all we have to do is tell
+ the reporter that we don't know this node.
+
+ But first we have to start the report by sending some basic
+ information for the root. */
+
+ SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
+ FALSE, NULL, scratch_pool));
+ SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
+
+ /* Finish the report, which causes the update editor to be
+ driven. */
+ SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ if (restore_files)
+ {
+ svn_node_kind_t disk_kind;
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
+
+ if (disk_kind == svn_node_none)
+ {
+ err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
+ scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ }
+ }
+ }
+
+ /* Report that we know the path */
+ SVN_ERR(reporter->set_path(report_baton, "", revision,
+ svn_depth_infinity, FALSE, NULL,
+ scratch_pool));
+
+ /* For compatibility with the normal update editor report we report
+ the target as switched.
+
+ ### We can probably report a parent url and unswitched later */
+ SVN_ERR(reporter->link_path(report_baton, "",
+ svn_path_url_add_component2(repos_root_url,
+ repos_relpath,
+ scratch_pool),
+ revision,
+ svn_depth_infinity,
+ FALSE /* start_empty*/,
+ lock ? lock->token : NULL,
+ scratch_pool));
+ }
+
+ return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__read_external_info(svn_node_kind_t *external_kind,
+ const char **defining_abspath,
+ const char **defining_url,
+ svn_revnum_t *defining_operational_revision,
+ svn_revnum_t *defining_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_root_url;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ err = svn_wc__db_external_read(&status, &kind, defining_abspath,
+ defining_url ? &repos_root_url : NULL, NULL,
+ defining_url, defining_operational_revision,
+ defining_revision,
+ wc_ctx->db, local_abspath, wri_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ if (external_kind)
+ *external_kind = svn_node_none;
+
+ if (defining_abspath)
+ *defining_abspath = NULL;
+
+ if (defining_url)
+ *defining_url = NULL;
+
+ if (defining_operational_revision)
+ *defining_operational_revision = SVN_INVALID_REVNUM;
+
+ if (defining_revision)
+ *defining_revision = SVN_INVALID_REVNUM;
+
+ return SVN_NO_ERROR;
+ }
+
+ if (external_kind)
+ {
+ if (status != svn_wc__db_status_normal)
+ *external_kind = svn_node_unknown;
+ else
+ switch(kind)
+ {
+ case svn_node_file:
+ case svn_node_symlink:
+ *external_kind = svn_node_file;
+ break;
+ case svn_node_dir:
+ *external_kind = svn_node_dir;
+ break;
+ default:
+ *external_kind = svn_node_none;
+ }
+ }
+
+ if (defining_url && *defining_url)
+ *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
+ * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
+ * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+is_external_rolled_out(svn_boolean_t *is_rolled_out,
+ svn_wc_context_t *wc_ctx,
+ svn_wc__committable_external_info_t *xinfo,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_relpath;
+ const char *repos_root_url;
+ svn_error_t *err;
+
+ *is_rolled_out = FALSE;
+
+ err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
+ &repos_root_url, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, xinfo->local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+ }
+
+ *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
+ strcmp(xinfo->repos_relpath, repos_relpath) == 0);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__committable_externals_below(apr_array_header_t **externals,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *orig_externals;
+ int i;
+ apr_pool_t *iterpool;
+
+ /* For svn_depth_files, this also fetches dirs. They are filtered later. */
+ SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
+ wc_ctx->db,
+ local_abspath,
+ depth != svn_depth_infinity,
+ result_pool, scratch_pool));
+
+ if (orig_externals == NULL)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < orig_externals->nelts; i++)
+ {
+ svn_boolean_t is_rolled_out;
+
+ svn_wc__committable_external_info_t *xinfo =
+ APR_ARRAY_IDX(orig_externals, i,
+ svn_wc__committable_external_info_t *);
+
+ /* Discard dirs for svn_depth_files (s.a.). */
+ if (depth == svn_depth_files
+ && xinfo->kind == svn_node_dir)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ /* Discard those externals that are not currently checked out. */
+ SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
+ iterpool));
+ if (! is_rolled_out)
+ continue;
+
+ if (*externals == NULL)
+ *externals = apr_array_make(
+ result_pool, 0,
+ sizeof(svn_wc__committable_external_info_t *));
+
+ APR_ARRAY_PUSH(*externals,
+ svn_wc__committable_external_info_t *) = xinfo;
+
+ if (depth != svn_depth_infinity)
+ continue;
+
+ /* Are there any nested externals? */
+ SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
+ xinfo->local_abspath,
+ svn_depth_infinity,
+ result_pool, iterpool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__externals_defined_below(apr_hash_t **externals,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_externals_defined_below(externals,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__external_register(svn_wc_context_t *wc_ctx,
+ const char *defining_abspath,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ const char *repos_relpath,
+ svn_revnum_t operational_revision,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(kind == svn_node_dir);
+ return svn_error_trace(
+ svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
+ defining_abspath,
+ repos_root_url,
+ repos_uuid,
+ defining_abspath,
+ repos_relpath,
+ operational_revision,
+ revision,
+ NULL,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__external_remove(svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_boolean_t declaration_only,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ wc_ctx->db, local_abspath, wri_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
+ NULL, scratch_pool));
+
+ if (declaration_only)
+ return SVN_NO_ERROR;
+
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
+ TRUE, TRUE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ else
+ {
+ SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
+ FALSE /* keep_as_working */,
+ TRUE /* queue_deletes */,
+ SVN_INVALID_REVNUM,
+ NULL, NULL, scratch_pool));
+ SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__externals_gather_definitions(apr_hash_t **externals,
+ apr_hash_t **depths,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (depth == svn_depth_infinity
+ || depth == svn_depth_unknown)
+ {
+ return svn_error_trace(
+ svn_wc__db_externals_gather_definitions(externals, depths,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+ }
+ else
+ {
+ const svn_string_t *value;
+ svn_error_t *err;
+ *externals = apr_hash_make(result_pool);
+
+ local_abspath = apr_pstrdup(result_pool, local_abspath);
+
+ err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
+ SVN_PROP_EXTERNALS, result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ value = NULL;
+ }
+
+ if (value)
+ svn_hash_sets(*externals, local_abspath, value->data);
+
+ if (value && depths)
+ {
+ svn_depth_t node_depth;
+ *depths = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &node_depth, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
+ }
+
+ return SVN_NO_ERROR;
+ }
+}
+
+svn_error_t *
+svn_wc__close_db(const char *external_abspath,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Return the scheme of @a uri in @a scheme allocated from @a pool.
+ If @a uri does not appear to be a valid URI, then @a scheme will
+ not be updated. */
+static svn_error_t *
+uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
+{
+ apr_size_t i;
+
+ for (i = 0; uri[i] && uri[i] != ':'; ++i)
+ if (uri[i] == '/')
+ goto error;
+
+ if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
+ {
+ *scheme = apr_pstrmemdup(pool, uri, i);
+ return SVN_NO_ERROR;
+ }
+
+error:
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("URL '%s' does not begin with a scheme"),
+ uri);
+}
+
+svn_error_t *
+svn_wc__resolve_relative_external_url(const char **resolved_url,
+ const svn_wc_external_item2_t *item,
+ const char *repos_root_url,
+ const char *parent_dir_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *url = item->url;
+ apr_uri_t parent_dir_uri;
+ apr_status_t status;
+
+ *resolved_url = item->url;
+
+ /* If the URL is already absolute, there is nothing to do. */
+ if (svn_path_is_url(url))
+ {
+ /* "http://server/path" */
+ *resolved_url = svn_uri_canonicalize(url, result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ if (url[0] == '/')
+ {
+ /* "/path", "//path", and "///path" */
+ int num_leading_slashes = 1;
+ if (url[1] == '/')
+ {
+ num_leading_slashes++;
+ if (url[2] == '/')
+ num_leading_slashes++;
+ }
+
+ /* "//schema-relative" and in some cases "///schema-relative".
+ This last format is supported on file:// schema relative. */
+ url = apr_pstrcat(scratch_pool,
+ apr_pstrndup(scratch_pool, url, num_leading_slashes),
+ svn_relpath_canonicalize(url + num_leading_slashes,
+ scratch_pool),
+ (char*)NULL);
+ }
+ else
+ {
+ /* "^/path" and "../path" */
+ url = svn_relpath_canonicalize(url, scratch_pool);
+ }
+
+ /* Parse the parent directory URL into its parts. */
+ status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
+ if (status)
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("Illegal parent directory URL '%s'"),
+ parent_dir_url);
+
+ /* If the parent directory URL is at the server root, then the URL
+ may have no / after the hostname so apr_uri_parse() will leave
+ the URL's path as NULL. */
+ if (! parent_dir_uri.path)
+ parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
+ parent_dir_uri.query = NULL;
+ parent_dir_uri.fragment = NULL;
+
+ /* Handle URLs relative to the current directory or to the
+ repository root. The backpaths may only remove path elements,
+ not the hostname. This allows an external to refer to another
+ repository in the same server relative to the location of this
+ repository, say using SVNParentPath. */
+ if ((0 == strncmp("../", url, 3)) ||
+ (0 == strncmp("^/", url, 2)))
+ {
+ apr_array_header_t *base_components;
+ apr_array_header_t *relative_components;
+ int i;
+
+ /* Decompose either the parent directory's URL path or the
+ repository root's URL path into components. */
+ if (0 == strncmp("../", url, 3))
+ {
+ base_components = svn_path_decompose(parent_dir_uri.path,
+ scratch_pool);
+ relative_components = svn_path_decompose(url, scratch_pool);
+ }
+ else
+ {
+ apr_uri_t repos_root_uri;
+
+ status = apr_uri_parse(scratch_pool, repos_root_url,
+ &repos_root_uri);
+ if (status)
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("Illegal repository root URL '%s'"),
+ repos_root_url);
+
+ /* If the repository root URL is at the server root, then
+ the URL may have no / after the hostname so
+ apr_uri_parse() will leave the URL's path as NULL. */
+ if (! repos_root_uri.path)
+ repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
+
+ base_components = svn_path_decompose(repos_root_uri.path,
+ scratch_pool);
+ relative_components = svn_path_decompose(url + 2, scratch_pool);
+ }
+
+ for (i = 0; i < relative_components->nelts; ++i)
+ {
+ const char *component = APR_ARRAY_IDX(relative_components,
+ i,
+ const char *);
+ if (0 == strcmp("..", component))
+ {
+ /* Constructing the final absolute URL together with
+ apr_uri_unparse() requires that the path be absolute,
+ so only pop a component if the component being popped
+ is not the component for the root directory. */
+ if (base_components->nelts > 1)
+ apr_array_pop(base_components);
+ }
+ else
+ APR_ARRAY_PUSH(base_components, const char *) = component;
+ }
+
+ parent_dir_uri.path = (char *)svn_path_compose(base_components,
+ scratch_pool);
+ *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
+ &parent_dir_uri, 0),
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* The remaining URLs are relative to either the scheme or server root
+ and can only refer to locations inside that scope, so backpaths are
+ not allowed. */
+ if (svn_path_is_backpath_present(url))
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("The external relative URL '%s' cannot have "
+ "backpaths, i.e. '..'"),
+ item->url);
+
+ /* Relative to the scheme: Build a new URL from the parts we know. */
+ if (0 == strncmp("//", url, 2))
+ {
+ const char *scheme;
+
+ SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
+ *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
+ ":", url, (char *)NULL),
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* Relative to the server root: Just replace the path portion of the
+ parent's URL. */
+ if (url[0] == '/')
+ {
+ parent_dir_uri.path = (char *)url;
+ *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
+ &parent_dir_uri, 0),
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("Unrecognized format for the relative external "
+ "URL '%s'"),
+ item->url);
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/info.c b/contrib/subversion/subversion/libsvn_wc/info.c
new file mode 100644
index 0000000..4a37e00
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/info.c
@@ -0,0 +1,580 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ */
+
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_wc.h"
+
+#include "wc.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+svn_wc_info_t *
+svn_wc_info_dup(const svn_wc_info_t *info,
+ apr_pool_t *pool)
+{
+ svn_wc_info_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info));
+
+ if (info->changelist)
+ new_info->changelist = apr_pstrdup(pool, info->changelist);
+ new_info->checksum = svn_checksum_dup(info->checksum, pool);
+ if (info->conflicts)
+ {
+ int i;
+
+ apr_array_header_t *new_conflicts
+ = apr_array_make(pool, info->conflicts->nelts, info->conflicts->elt_size);
+ for (i = 0; i < info->conflicts->nelts; i++)
+ {
+ APR_ARRAY_PUSH(new_conflicts, svn_wc_conflict_description2_t *)
+ = svn_wc__conflict_description2_dup(
+ APR_ARRAY_IDX(info->conflicts, i,
+ const svn_wc_conflict_description2_t *),
+ pool);
+ }
+ new_info->conflicts = new_conflicts;
+ }
+ if (info->copyfrom_url)
+ new_info->copyfrom_url = apr_pstrdup(pool, info->copyfrom_url);
+ if (info->wcroot_abspath)
+ new_info->wcroot_abspath = apr_pstrdup(pool, info->wcroot_abspath);
+ if (info->moved_from_abspath)
+ new_info->moved_from_abspath = apr_pstrdup(pool, info->moved_from_abspath);
+ if (info->moved_to_abspath)
+ new_info->moved_to_abspath = apr_pstrdup(pool, info->moved_to_abspath);
+
+ return new_info;
+}
+
+
+/* Set *INFO to a new struct, allocated in RESULT_POOL, built from the WC
+ metadata of LOCAL_ABSPATH. Pointer fields are copied by reference, not
+ dup'd. */
+static svn_error_t *
+build_info_for_node(svn_wc__info2_t **info,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__info2_t *tmpinfo;
+ const char *repos_relpath;
+ svn_wc__db_status_t status;
+ svn_node_kind_t db_kind;
+ const char *original_repos_relpath;
+ const char *original_repos_root_url;
+ const char *original_uuid;
+ svn_revnum_t original_revision;
+ svn_wc__db_lock_t *lock;
+ svn_boolean_t conflicted;
+ svn_boolean_t op_root;
+ svn_boolean_t have_base;
+ svn_boolean_t have_more_work;
+ svn_wc_info_t *wc_info;
+
+ tmpinfo = apr_pcalloc(result_pool, sizeof(*tmpinfo));
+ tmpinfo->kind = kind;
+
+ wc_info = apr_pcalloc(result_pool, sizeof(*wc_info));
+ tmpinfo->wc_info = wc_info;
+
+ wc_info->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ SVN_ERR(svn_wc__db_read_info(&status, &db_kind, &tmpinfo->rev,
+ &repos_relpath,
+ &tmpinfo->repos_root_URL, &tmpinfo->repos_UUID,
+ &tmpinfo->last_changed_rev,
+ &tmpinfo->last_changed_date,
+ &tmpinfo->last_changed_author,
+ &wc_info->depth, &wc_info->checksum, NULL,
+ &original_repos_relpath,
+ &original_repos_root_url, &original_uuid,
+ &original_revision, &lock,
+ &wc_info->recorded_size,
+ &wc_info->recorded_time,
+ &wc_info->changelist,
+ &conflicted, &op_root, NULL, NULL,
+ &have_base, &have_more_work, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (original_repos_root_url != NULL)
+ {
+ tmpinfo->repos_root_URL = original_repos_root_url;
+ tmpinfo->repos_UUID = original_uuid;
+ }
+
+ if (status == svn_wc__db_status_added)
+ {
+ /* ### We should also just be fetching the true BASE revision
+ ### here, which means copied items would also not have a
+ ### revision to display. But WC-1 wants to show the revision of
+ ### copy targets as the copyfrom-rev. *sigh* */
+
+ if (original_repos_relpath)
+ {
+ /* Root or child of copy */
+ tmpinfo->rev = original_revision;
+ repos_relpath = original_repos_relpath;
+
+ if (op_root)
+ {
+ svn_error_t *err;
+ wc_info->copyfrom_url =
+ svn_path_url_add_component2(tmpinfo->repos_root_URL,
+ original_repos_relpath,
+ result_pool);
+
+ wc_info->copyfrom_rev = original_revision;
+
+ err = svn_wc__db_scan_moved(&wc_info->moved_from_abspath,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+ wc_info->moved_from_abspath = NULL;
+ }
+ }
+ }
+ else if (op_root)
+ {
+ /* Local addition */
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath,
+ &tmpinfo->repos_root_URL,
+ &tmpinfo->repos_UUID,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (have_base)
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ /* Child of copy. ### Not WC-NG like */
+ SVN_ERR(svn_wc__internal_get_origin(NULL, &tmpinfo->rev,
+ &repos_relpath,
+ &tmpinfo->repos_root_URL,
+ &tmpinfo->repos_UUID, NULL,
+ db, local_abspath, TRUE,
+ result_pool, scratch_pool));
+ }
+
+ /* ### We should be able to avoid both these calls with the information
+ from read_info() in most cases */
+ if (! op_root)
+ wc_info->schedule = svn_wc_schedule_normal;
+ else if (! have_more_work && ! have_base)
+ wc_info->schedule = svn_wc_schedule_add;
+ else
+ {
+ svn_wc__db_status_t below_working;
+ svn_boolean_t have_work;
+
+ SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
+ &below_working,
+ db, local_abspath,
+ scratch_pool));
+
+ /* If the node is not present or deleted (read: not present
+ in working), then the node is not a replacement */
+ if (below_working != svn_wc__db_status_not_present
+ && below_working != svn_wc__db_status_deleted)
+ {
+ wc_info->schedule = svn_wc_schedule_replace;
+ }
+ else
+ wc_info->schedule = svn_wc_schedule_add;
+ }
+ SVN_ERR(svn_wc__db_read_url(&tmpinfo->URL, db, local_abspath,
+ result_pool, scratch_pool));
+ }
+ else if (status == svn_wc__db_status_deleted)
+ {
+ const char *work_del_abspath;
+
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL,
+ &tmpinfo->last_changed_rev,
+ &tmpinfo->last_changed_date,
+ &tmpinfo->last_changed_author,
+ &wc_info->depth,
+ &wc_info->checksum,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ /* And now fetch the url and revision of what will be deleted */
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, &wc_info->moved_to_abspath,
+ &work_del_abspath, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (work_del_abspath != NULL)
+ {
+ /* This is a deletion within a copied subtree. Get the copied-from
+ * revision. */
+ const char *added_abspath = svn_dirent_dirname(work_del_abspath,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath,
+ &tmpinfo->repos_root_URL,
+ &tmpinfo->repos_UUID,
+ NULL, NULL, NULL,
+ &tmpinfo->rev,
+ db, added_abspath,
+ result_pool, scratch_pool));
+
+ tmpinfo->URL = svn_path_url_add_component2(
+ tmpinfo->repos_root_URL,
+ svn_relpath_join(repos_relpath,
+ svn_dirent_skip_ancestor(added_abspath,
+ local_abspath),
+ scratch_pool),
+ result_pool);
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev,
+ &repos_relpath,
+ &tmpinfo->repos_root_URL,
+ &tmpinfo->repos_UUID, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL,
+ repos_relpath,
+ result_pool);
+ }
+
+ wc_info->schedule = svn_wc_schedule_delete;
+ }
+ else if (status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_server_excluded)
+ {
+ *info = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* Just a BASE node. We have all the info we need */
+ tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL,
+ repos_relpath,
+ result_pool);
+ wc_info->schedule = svn_wc_schedule_normal;
+ }
+
+ if (status == svn_wc__db_status_excluded)
+ tmpinfo->wc_info->depth = svn_depth_exclude;
+
+ /* A default */
+ tmpinfo->size = SVN_INVALID_FILESIZE;
+
+ SVN_ERR(svn_wc__db_get_wcroot(&tmpinfo->wc_info->wcroot_abspath, db,
+ local_abspath, result_pool, scratch_pool));
+
+ if (conflicted)
+ SVN_ERR(svn_wc__read_conflicts(&wc_info->conflicts, db,
+ local_abspath,
+ TRUE /* ### create tempfiles */,
+ result_pool, scratch_pool));
+ else
+ wc_info->conflicts = NULL;
+
+ /* lock stuff */
+ if (lock != NULL)
+ {
+ tmpinfo->lock = apr_pcalloc(result_pool, sizeof(*(tmpinfo->lock)));
+ tmpinfo->lock->token = lock->token;
+ tmpinfo->lock->owner = lock->owner;
+ tmpinfo->lock->comment = lock->comment;
+ tmpinfo->lock->creation_date = lock->date;
+ }
+
+ *info = tmpinfo;
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *INFO to a new struct with minimal content, to be
+ used in reporting info for unversioned tree conflict victims. */
+/* ### Some fields we could fill out based on the parent dir's entry
+ or by looking at an obstructing item. */
+static svn_error_t *
+build_info_for_unversioned(svn_wc__info2_t **info,
+ apr_pool_t *pool)
+{
+ svn_wc__info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo));
+ svn_wc_info_t *wc_info = apr_pcalloc(pool, sizeof (*wc_info));
+
+ tmpinfo->URL = NULL;
+ tmpinfo->repos_UUID = NULL;
+ tmpinfo->repos_root_URL = NULL;
+ tmpinfo->rev = SVN_INVALID_REVNUM;
+ tmpinfo->kind = svn_node_none;
+ tmpinfo->size = SVN_INVALID_FILESIZE;
+ tmpinfo->last_changed_rev = SVN_INVALID_REVNUM;
+ tmpinfo->last_changed_date = 0;
+ tmpinfo->last_changed_author = NULL;
+ tmpinfo->lock = NULL;
+
+ tmpinfo->wc_info = wc_info;
+
+ wc_info->copyfrom_rev = SVN_INVALID_REVNUM;
+ wc_info->depth = svn_depth_unknown;
+ wc_info->recorded_size = SVN_INVALID_FILESIZE;
+
+ *info = tmpinfo;
+ return SVN_NO_ERROR;
+}
+
+/* Callback and baton for crawl_entries() walk over entries files. */
+struct found_entry_baton
+{
+ svn_wc__info_receiver2_t receiver;
+ void *receiver_baton;
+ svn_wc__db_t *db;
+ svn_boolean_t actual_only;
+ svn_boolean_t first;
+ /* The set of tree conflicts that have been found but not (yet) visited by
+ * the tree walker. Map of abspath -> svn_wc_conflict_description2_t. */
+ apr_hash_t *tree_conflicts;
+ apr_pool_t *pool;
+};
+
+/* Call WALK_BATON->receiver with WALK_BATON->receiver_baton, passing to it
+ * info about the path LOCAL_ABSPATH.
+ * An svn_wc__node_found_func_t callback function. */
+static svn_error_t *
+info_found_node_callback(const char *local_abspath,
+ svn_node_kind_t kind,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct found_entry_baton *fe_baton = walk_baton;
+ svn_wc__info2_t *info;
+
+ SVN_ERR(build_info_for_node(&info, fe_baton->db, local_abspath,
+ kind, scratch_pool, scratch_pool));
+
+ if (info == NULL)
+ {
+ if (!fe_baton->first)
+ return SVN_NO_ERROR; /* not present or server excluded descendant */
+
+ /* If the info root is not found, that is an error */
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ fe_baton->first = FALSE;
+
+ SVN_ERR_ASSERT(info->wc_info != NULL);
+ SVN_ERR(fe_baton->receiver(fe_baton->receiver_baton, local_abspath,
+ info, scratch_pool));
+
+ /* If this node is a versioned directory, make a note of any tree conflicts
+ * on all immediate children. Some of these may be visited later in this
+ * walk, at which point they will be removed from the list, while any that
+ * are not visited will remain in the list. */
+ if (fe_baton->actual_only && kind == svn_node_dir)
+ {
+ const apr_array_header_t *victims;
+ int i;
+
+ SVN_ERR(svn_wc__db_read_conflict_victims(&victims,
+ fe_baton->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ for (i = 0; i < victims->nelts; i++)
+ {
+ const char *this_basename = APR_ARRAY_IDX(victims, i, const char *);
+
+ svn_hash_sets(fe_baton->tree_conflicts,
+ svn_dirent_join(local_abspath, this_basename,
+ fe_baton->pool),
+ "");
+ }
+ }
+
+ /* Delete this path which we are currently visiting from the list of tree
+ * conflicts. This relies on the walker visiting a directory before visiting
+ * its children. */
+ svn_hash_sets(fe_baton->tree_conflicts, local_abspath, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return TRUE iff the subtree at ROOT_ABSPATH, restricted to depth DEPTH,
+ * would include the path CHILD_ABSPATH of kind CHILD_KIND. */
+static svn_boolean_t
+depth_includes(const char *root_abspath,
+ svn_depth_t depth,
+ const char *child_abspath,
+ svn_node_kind_t child_kind,
+ apr_pool_t *scratch_pool)
+{
+ const char *parent_abspath = svn_dirent_dirname(child_abspath, scratch_pool);
+
+ return (depth == svn_depth_infinity
+ || ((depth == svn_depth_immediates
+ || (depth == svn_depth_files && child_kind == svn_node_file))
+ && strcmp(root_abspath, parent_abspath) == 0)
+ || strcmp(root_abspath, child_abspath) == 0);
+}
+
+
+svn_error_t *
+svn_wc__get_info(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t fetch_excluded,
+ svn_boolean_t fetch_actual_only,
+ const apr_array_header_t *changelist_filter,
+ svn_wc__info_receiver2_t receiver,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct found_entry_baton fe_baton;
+ svn_error_t *err;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ const char *repos_root_url = NULL;
+ const char *repos_uuid = NULL;
+
+ fe_baton.receiver = receiver;
+ fe_baton.receiver_baton = receiver_baton;
+ fe_baton.db = wc_ctx->db;
+ fe_baton.actual_only = fetch_actual_only;
+ fe_baton.first = TRUE;
+ fe_baton.tree_conflicts = apr_hash_make(scratch_pool);
+ fe_baton.pool = scratch_pool;
+
+ err = svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
+ fetch_excluded,
+ changelist_filter,
+ info_found_node_callback,
+ &fe_baton, depth,
+ cancel_func, cancel_baton,
+ iterpool);
+
+ /* If the target root node is not present, svn_wc__internal_walk_children()
+ returns a PATH_NOT_FOUND error and doesn't call the callback. If there
+ is a tree conflict on this node, that is not an error. */
+ if (fe_baton.first /* not visited by walk_children */
+ && fetch_actual_only
+ && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_boolean_t tree_conflicted;
+ svn_error_t *err2;
+
+ err2 = svn_wc__internal_conflicted_p(NULL, NULL, &tree_conflicted,
+ wc_ctx->db, local_abspath,
+ iterpool);
+
+ if ((err2 && err2->apr_err == SVN_ERR_WC_PATH_NOT_FOUND))
+ {
+ svn_error_clear(err2);
+ return svn_error_trace(err);
+ }
+ else if (err2 || !tree_conflicted)
+ return svn_error_compose_create(err, err2);
+
+ svn_error_clear(err);
+
+ svn_hash_sets(fe_baton.tree_conflicts, local_abspath, "");
+ }
+ else
+ SVN_ERR(err);
+
+ /* If there are any tree conflicts that we have found but have not reported,
+ * send a minimal info struct for each one now. */
+ for (hi = apr_hash_first(scratch_pool, fe_baton.tree_conflicts); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *this_abspath = svn__apr_hash_index_key(hi);
+ const svn_wc_conflict_description2_t *tree_conflict;
+ svn_wc__info2_t *info;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(build_info_for_unversioned(&info, iterpool));
+
+ if (!repos_root_url)
+ {
+ SVN_ERR(svn_wc__internal_get_repos_info(NULL, NULL,
+ &repos_root_url,
+ &repos_uuid,
+ wc_ctx->db,
+ svn_dirent_dirname(
+ local_abspath,
+ iterpool),
+ scratch_pool,
+ iterpool));
+ }
+
+ info->repos_root_URL = repos_root_url;
+ info->repos_UUID = repos_uuid;
+
+ SVN_ERR(svn_wc__read_conflicts(&info->wc_info->conflicts,
+ wc_ctx->db, this_abspath,
+ TRUE /* ### create tempfiles */,
+ iterpool, iterpool));
+
+ if (! info->wc_info->conflicts || ! info->wc_info->conflicts->nelts)
+ continue;
+
+ tree_conflict = APR_ARRAY_IDX(info->wc_info->conflicts, 0,
+ svn_wc_conflict_description2_t *);
+
+ if (!depth_includes(local_abspath, depth, tree_conflict->local_abspath,
+ tree_conflict->node_kind, iterpool))
+ continue;
+
+ SVN_ERR(receiver(receiver_baton, this_abspath, info, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/lock.c b/contrib/subversion/subversion/libsvn_wc/lock.c
new file mode 100644
index 0000000..36fbb0e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/lock.c
@@ -0,0 +1,1656 @@
+/*
+ * lock.c: routines for locking working copy subdirectories.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define SVN_DEPRECATED
+
+#include <apr_pools.h>
+#include <apr_time.h>
+
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_sorts.h"
+#include "svn_hash.h"
+#include "svn_types.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "lock.h"
+#include "props.h"
+#include "wc_db.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+
+struct svn_wc_adm_access_t
+{
+ /* PATH to directory which contains the administrative area */
+ const char *path;
+
+ /* And the absolute form of the path. */
+ const char *abspath;
+
+ /* Indicates that the baton has been closed. */
+ svn_boolean_t closed;
+
+ /* Handle to the administrative database. */
+ svn_wc__db_t *db;
+
+ /* Was the DB provided to us? If so, then we'll never close it. */
+ svn_boolean_t db_provided;
+
+ /* ENTRIES_HIDDEN is all cached entries including those in
+ state deleted or state absent. It may be NULL. */
+ apr_hash_t *entries_all;
+
+ /* POOL is used to allocate cached items, they need to persist for the
+ lifetime of this access baton */
+ apr_pool_t *pool;
+
+};
+
+
+/* This is a placeholder used in the set hash to represent missing
+ directories. Only its address is important, it contains no useful
+ data. */
+static const svn_wc_adm_access_t missing = { 0 };
+#define IS_MISSING(lock) ((lock) == &missing)
+
+/* ### hack for now. future functionality coming in a future revision. */
+#define svn_wc__db_is_closed(db) FALSE
+
+
+svn_error_t *
+svn_wc__internal_check_wc(int *wc_format,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t check_path,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ err = svn_wc__db_temp_get_format(wc_format, db, local_abspath, scratch_pool);
+ if (err)
+ {
+ svn_node_kind_t kind;
+
+ if (err->apr_err != SVN_ERR_WC_MISSING &&
+ err->apr_err != SVN_ERR_WC_UNSUPPORTED_FORMAT &&
+ err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* ### the stuff below seems to be redundant. get_format() probably
+ ### does all this.
+ ###
+ ### investigate all callers. DEFINITELY keep in mind the
+ ### svn_wc_check_wc() entrypoint.
+ */
+
+ /* If the format file does not exist or path not directory, then for
+ our purposes this is not a working copy, so return 0. */
+ *wc_format = 0;
+
+ /* Check path itself exists. */
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ {
+ return svn_error_createf(APR_ENOENT, NULL, _("'%s' does not exist"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ }
+
+ if (*wc_format >= SVN_WC__WC_NG_VERSION)
+ {
+ svn_wc__db_status_t db_status;
+ svn_node_kind_t db_kind;
+
+ if (check_path)
+ {
+ /* If a node is not a directory, it is not a working copy
+ directory. This allows creating new working copies as
+ a path below an existing working copy. */
+ svn_node_kind_t wc_kind;
+
+ SVN_ERR(svn_io_check_path(local_abspath, &wc_kind, scratch_pool));
+ if (wc_kind != svn_node_dir)
+ {
+ *wc_format = 0; /* Not a directory, so not a wc-directory */
+ return SVN_NO_ERROR;
+ }
+ }
+
+ err = svn_wc__db_read_info(&db_status, &db_kind, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *wc_format = 0;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ if (db_kind != svn_node_dir)
+ {
+ /* The WC thinks there must be a file, so this is not
+ a wc-directory */
+ *wc_format = 0;
+ return SVN_NO_ERROR;
+ }
+
+ switch (db_status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_excluded:
+ /* If there is a directory here, it is not related to the parent
+ working copy: Obstruction */
+ *wc_format = 0;
+ return SVN_NO_ERROR;
+ default:
+ break;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_check_wc2(int *wc_format,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ /* ### Should we pass TRUE for check_path to find obstructions and
+ missing directories? */
+ return svn_error_trace(
+ svn_wc__internal_check_wc(wc_format, wc_ctx->db, local_abspath, FALSE,
+ scratch_pool));
+}
+
+
+/* */
+static svn_error_t *
+add_to_shared(svn_wc_adm_access_t *lock, apr_pool_t *scratch_pool)
+{
+ /* ### sometimes we replace &missing with a now-valid lock. */
+ {
+ svn_wc_adm_access_t *prior = svn_wc__db_temp_get_access(lock->db,
+ lock->abspath,
+ scratch_pool);
+ if (IS_MISSING(prior))
+ SVN_ERR(svn_wc__db_temp_close_access(lock->db, lock->abspath,
+ prior, scratch_pool));
+ }
+
+ svn_wc__db_temp_set_access(lock->db, lock->abspath, lock,
+ scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_wc_adm_access_t *
+get_from_shared(const char *abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ /* We closed the DB when it became empty. ABSPATH is not present. */
+ if (db == NULL)
+ return NULL;
+ return svn_wc__db_temp_get_access(db, abspath, scratch_pool);
+}
+
+
+/* */
+static svn_error_t *
+close_single(svn_wc_adm_access_t *adm_access,
+ svn_boolean_t preserve_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t locked;
+
+ if (adm_access->closed)
+ return SVN_NO_ERROR;
+
+ /* Physically unlock if required */
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&locked, adm_access->db,
+ adm_access->abspath, TRUE,
+ scratch_pool));
+ if (locked)
+ {
+ if (!preserve_lock)
+ {
+ /* Remove the physical lock in the admin directory for
+ PATH. It is acceptable for the administrative area to
+ have disappeared, such as when the directory is removed
+ from the working copy. It is an error for the lock to
+ have disappeared if the administrative area still exists. */
+
+ svn_error_t *err = svn_wc__db_wclock_release(adm_access->db,
+ adm_access->abspath,
+ scratch_pool);
+ if (err)
+ {
+ if (svn_wc__adm_area_exists(adm_access->abspath, scratch_pool))
+ return err;
+ svn_error_clear(err);
+ }
+ }
+ }
+
+ /* Reset to prevent further use of the lock. */
+ adm_access->closed = TRUE;
+
+ /* Detach from set */
+ SVN_ERR(svn_wc__db_temp_close_access(adm_access->db, adm_access->abspath,
+ adm_access, scratch_pool));
+
+ /* Possibly close the underlying wc_db. */
+ if (!adm_access->db_provided)
+ {
+ apr_hash_t *opened = svn_wc__db_temp_get_all_access(adm_access->db,
+ scratch_pool);
+ if (apr_hash_count(opened) == 0)
+ {
+ SVN_ERR(svn_wc__db_close(adm_access->db));
+ adm_access->db = NULL;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Cleanup for a locked access baton.
+
+ This handles closing access batons when their pool gets destroyed.
+ The physical locks associated with such batons remain in the working
+ copy if they are protecting work items in the workqueue. */
+static apr_status_t
+pool_cleanup_locked(void *p)
+{
+ svn_wc_adm_access_t *lock = p;
+ apr_uint64_t id;
+ svn_skel_t *work_item;
+ svn_error_t *err;
+
+ if (lock->closed)
+ return APR_SUCCESS;
+
+ /* If the DB is closed, then we have a bunch of extra work to do. */
+ if (svn_wc__db_is_closed(lock->db))
+ {
+ apr_pool_t *scratch_pool;
+ svn_wc__db_t *db;
+
+ lock->closed = TRUE;
+
+ /* If there is no ADM area, then we definitely have no work items
+ or physical locks to worry about. Bail out. */
+ if (!svn_wc__adm_area_exists(lock->abspath, lock->pool))
+ return APR_SUCCESS;
+
+ /* Creating a subpool is safe within a pool cleanup, as long as
+ we're absolutely sure to destroy it before we exit this function.
+
+ We avoid using LOCK->POOL to keep the following functions from
+ hanging cleanups or subpools from it. (the cleanups *might* get
+ run, but the subpools will NOT be destroyed) */
+ scratch_pool = svn_pool_create(lock->pool);
+
+ err = svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE,
+ scratch_pool, scratch_pool);
+ if (!err)
+ {
+ err = svn_wc__db_wq_fetch_next(&id, &work_item, db, lock->abspath, 0,
+ scratch_pool, scratch_pool);
+ if (!err && work_item == NULL)
+ {
+ /* There is no remaining work, so we're good to remove any
+ potential "physical" lock. */
+ err = svn_wc__db_wclock_release(db, lock->abspath, scratch_pool);
+ }
+ }
+ svn_error_clear(err);
+
+ /* Closes the DB, too. */
+ svn_pool_destroy(scratch_pool);
+
+ return APR_SUCCESS;
+ }
+
+ /* ### should we create an API that just looks, but doesn't return? */
+ err = svn_wc__db_wq_fetch_next(&id, &work_item, lock->db, lock->abspath, 0,
+ lock->pool, lock->pool);
+
+ /* Close just this access baton. The pool cleanup will close the rest. */
+ if (!err)
+ err = close_single(lock,
+ work_item != NULL /* preserve_lock */,
+ lock->pool);
+
+ if (err)
+ {
+ apr_status_t apr_err = err->apr_err;
+ svn_error_clear(err);
+ return apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Cleanup for a readonly access baton. */
+static apr_status_t
+pool_cleanup_readonly(void *data)
+{
+ svn_wc_adm_access_t *lock = data;
+ svn_error_t *err;
+
+ if (lock->closed)
+ return APR_SUCCESS;
+
+ /* If the DB is closed, then we have nothing to do. There are no
+ "physical" locks to remove, and we don't care whether this baton
+ is registered with the DB. */
+ if (svn_wc__db_is_closed(lock->db))
+ return APR_SUCCESS;
+
+ /* Close this baton. No lock to preserve. Since this is part of the
+ pool cleanup, we don't need to close children -- the cleanup process
+ will close all children. */
+ err = close_single(lock, FALSE /* preserve_lock */, lock->pool);
+ if (err)
+ {
+ apr_status_t result = err->apr_err;
+ svn_error_clear(err);
+ return result;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* An APR pool cleanup handler. This is a child handler, it removes the
+ main pool handler. */
+static apr_status_t
+pool_cleanup_child(void *p)
+{
+ svn_wc_adm_access_t *lock = p;
+
+ apr_pool_cleanup_kill(lock->pool, lock, pool_cleanup_locked);
+ apr_pool_cleanup_kill(lock->pool, lock, pool_cleanup_readonly);
+
+ return APR_SUCCESS;
+}
+
+
+/* Allocate from POOL, initialise and return an access baton. TYPE and PATH
+ are used to initialise the baton. If STEAL_LOCK, steal the lock if path
+ is already locked */
+static svn_error_t *
+adm_access_alloc(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ svn_boolean_t write_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_wc_adm_access_t *lock = apr_palloc(result_pool, sizeof(*lock));
+
+ lock->closed = FALSE;
+ lock->entries_all = NULL;
+ lock->db = db;
+ lock->db_provided = db_provided;
+ lock->path = apr_pstrdup(result_pool, path);
+ lock->pool = result_pool;
+
+ SVN_ERR(svn_dirent_get_absolute(&lock->abspath, path, result_pool));
+
+ *adm_access = lock;
+
+ if (write_lock)
+ {
+ svn_boolean_t owns_lock;
+
+ /* If the db already owns a lock, we can't add an extra lock record */
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, path, FALSE,
+ scratch_pool));
+
+ /* If DB owns the lock, but when there is no access baton open for this
+ directory, old access baton based code is trying to access data that
+ was previously locked by new code. Just hand them the lock, or
+ important code paths like svn_wc_add3() will start failing */
+ if (!owns_lock
+ || svn_wc__adm_retrieve_internal2(db, lock->abspath, scratch_pool))
+ {
+ SVN_ERR(svn_wc__db_wclock_obtain(db, lock->abspath, 0, FALSE,
+ scratch_pool));
+ }
+ }
+
+ err = add_to_shared(lock, scratch_pool);
+
+ if (err)
+ return svn_error_compose_create(
+ err,
+ svn_wc__db_wclock_release(db, lock->abspath, scratch_pool));
+
+ /* ### does this utf8 thing really/still apply?? */
+ /* It's important that the cleanup handler is registered *after* at least
+ one UTF8 conversion has been done, since such a conversion may create
+ the apr_xlate_t object in the pool, and that object must be around
+ when the cleanup handler runs. If the apr_xlate_t cleanup handler
+ were to run *before* the access baton cleanup handler, then the access
+ baton's handler won't work. */
+
+ /* Register an appropriate cleanup handler, based on the whether this
+ access baton is locked or not. */
+ apr_pool_cleanup_register(lock->pool, lock,
+ write_lock
+ ? pool_cleanup_locked
+ : pool_cleanup_readonly,
+ pool_cleanup_child);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+probe(svn_wc__db_t *db,
+ const char **dir,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ int wc_format = 0;
+
+ SVN_ERR(svn_io_check_path(path, &kind, pool));
+ if (kind == svn_node_dir)
+ {
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__internal_check_wc(&wc_format, db, local_abspath,
+ FALSE, pool));
+ }
+
+ /* a "version" of 0 means a non-wc directory */
+ if (kind != svn_node_dir || wc_format == 0)
+ {
+ /* Passing a path ending in "." or ".." to svn_dirent_dirname() is
+ probably always a bad idea; certainly it is in this case.
+ Unfortunately, svn_dirent_dirname()'s current signature can't
+ return an error, so we have to insert the protection in this
+ caller, ideally the API needs a change. See issue #1617. */
+ const char *base_name = svn_dirent_basename(path, pool);
+ if ((strcmp(base_name, "..") == 0)
+ || (strcmp(base_name, ".") == 0))
+ {
+ return svn_error_createf
+ (SVN_ERR_WC_BAD_PATH, NULL,
+ _("Path '%s' ends in '%s', "
+ "which is unsupported for this operation"),
+ svn_dirent_local_style(path, pool), base_name);
+ }
+
+ *dir = svn_dirent_dirname(path, pool);
+ }
+ else
+ *dir = path;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+open_single(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_boolean_t write_lock,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ int wc_format = 0;
+ svn_error_t *err;
+ svn_wc_adm_access_t *lock;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+ err = svn_wc__internal_check_wc(&wc_format, db, local_abspath, FALSE,
+ scratch_pool);
+ if (wc_format == 0 || (err && APR_STATUS_IS_ENOENT(err->apr_err)))
+ {
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, err,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(path, scratch_pool));
+ }
+ SVN_ERR(err);
+
+ /* The format version must match exactly. Note that wc_db will perform
+ an auto-upgrade if allowed. If it does *not*, then it has decided a
+ manual upgrade is required and it should have raised an error. */
+ SVN_ERR_ASSERT(wc_format == SVN_WC__VERSION);
+
+ /* Need to create a new lock */
+ SVN_ERR(adm_access_alloc(&lock, path, db, db_provided, write_lock,
+ result_pool, scratch_pool));
+
+ /* ### recurse was here */
+ *adm_access = lock;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Retrieves the KIND of LOCAL_ABSPATH and whether its administrative data is
+ available in the working copy.
+
+ *AVAILABLE is set to TRUE when the node and its metadata are available,
+ otherwise to FALSE (due to obstruction, missing, absence, exclusion,
+ or a "not-present" child).
+
+ KIND can be NULL.
+
+ ### note: this function should go away when we move to a single
+ ### adminstrative area. */
+static svn_error_t *
+adm_available(svn_boolean_t *available,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ if (kind)
+ *kind = svn_node_unknown;
+
+ SVN_ERR(svn_wc__db_read_info(&status, kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ *available = !(status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_not_present);
+
+ return SVN_NO_ERROR;
+}
+/* This is essentially the guts of svn_wc_adm_open3.
+ *
+ * If the working copy is already locked, return SVN_ERR_WC_LOCKED; if
+ * it is not a versioned directory, return SVN_ERR_WC_NOT_WORKING_COPY.
+ */
+static svn_error_t *
+do_open(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ apr_array_header_t *rollback,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_adm_access_t *lock;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(open_single(&lock, path, write_lock, db, db_provided,
+ result_pool, iterpool));
+
+ /* Add self to the rollback list in case of error. */
+ APR_ARRAY_PUSH(rollback, svn_wc_adm_access_t *) = lock;
+
+ if (levels_to_lock != 0)
+ {
+ const apr_array_header_t *children;
+ const char *local_abspath = svn_wc__adm_access_abspath(lock);
+ int i;
+
+ /* Reduce levels_to_lock since we are about to recurse */
+ if (levels_to_lock > 0)
+ levels_to_lock--;
+
+ SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath,
+ scratch_pool, iterpool));
+
+ /* Open the tree */
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *node_abspath;
+ svn_node_kind_t kind;
+ svn_boolean_t available;
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ /* See if someone wants to cancel this operation. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ node_abspath = svn_dirent_join(local_abspath, name, iterpool);
+
+ SVN_ERR(adm_available(&available,
+ &kind,
+ db,
+ node_abspath,
+ scratch_pool));
+
+ if (kind != svn_node_dir)
+ continue;
+
+ if (available)
+ {
+ const char *node_path = svn_dirent_join(path, name, iterpool);
+ svn_wc_adm_access_t *node_access;
+
+ SVN_ERR(do_open(&node_access, node_path, db, db_provided,
+ rollback, write_lock, levels_to_lock,
+ cancel_func, cancel_baton,
+ lock->pool, iterpool));
+ /* node_access has been registered in DB, so we don't need
+ to do anything with it. */
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ *adm_access = lock;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+open_all(svn_wc_adm_access_t **adm_access,
+ const char *path,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *rollback;
+ svn_error_t *err;
+
+ rollback = apr_array_make(pool, 10, sizeof(svn_wc_adm_access_t *));
+
+ err = do_open(adm_access, path, db, db_provided, rollback,
+ write_lock, levels_to_lock,
+ cancel_func, cancel_baton, pool, pool);
+ if (err)
+ {
+ int i;
+
+ for (i = rollback->nelts; i--; )
+ {
+ svn_wc_adm_access_t *lock = APR_ARRAY_IDX(rollback, i,
+ svn_wc_adm_access_t *);
+ SVN_ERR_ASSERT(!IS_MISSING(lock));
+
+ svn_error_clear(close_single(lock, FALSE /* preserve_lock */, pool));
+ }
+ }
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_wc_adm_open3(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_wc__db_t *db;
+ svn_boolean_t db_provided;
+
+ /* Make sure that ASSOCIATED has a set of access batons, so that we can
+ glom a reference to self into it. */
+ if (associated)
+ {
+ const char *abspath;
+ svn_wc_adm_access_t *lock;
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool));
+ lock = get_from_shared(abspath, associated->db, pool);
+ if (lock && !IS_MISSING(lock))
+ /* Already locked. The reason we don't return the existing baton
+ here is that the user is supposed to know whether a directory is
+ locked: if it's not locked call svn_wc_adm_open, if it is locked
+ call svn_wc_adm_retrieve. */
+ return svn_error_createf(SVN_ERR_WC_LOCKED, NULL,
+ _("Working copy '%s' locked"),
+ svn_dirent_local_style(path, pool));
+ db = associated->db;
+ db_provided = associated->db_provided;
+ }
+ else
+ {
+ /* Any baton creation is going to need a shared structure for holding
+ data across the entire set. The caller isn't providing one, so we
+ do it here. */
+ /* ### we could optimize around levels_to_lock==0, but much of this
+ ### is going to be simplified soon anyways. */
+ SVN_ERR(svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE,
+ pool, pool));
+ db_provided = FALSE;
+ }
+
+ return svn_error_trace(open_all(adm_access, path, db, db_provided,
+ write_lock, levels_to_lock,
+ cancel_func, cancel_baton, pool));
+}
+
+
+svn_error_t *
+svn_wc_adm_probe_open3(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ const char *dir;
+
+ if (associated == NULL)
+ {
+ svn_wc__db_t *db;
+
+ /* Ugh. Too bad about having to open a DB. */
+ SVN_ERR(svn_wc__db_open(&db,
+ NULL /* ### config */, FALSE, TRUE, pool, pool));
+ err = probe(db, &dir, path, pool);
+ svn_error_clear(svn_wc__db_close(db));
+ SVN_ERR(err);
+ }
+ else
+ {
+ SVN_ERR(probe(associated->db, &dir, path, pool));
+ }
+
+ /* If we moved up a directory, then the path is not a directory, or it
+ is not under version control. In either case, the notion of
+ levels_to_lock does not apply to the provided path. Disable it so
+ that we don't end up trying to lock more than we need. */
+ if (dir != path)
+ levels_to_lock = 0;
+
+ err = svn_wc_adm_open3(adm_access, associated, dir, write_lock,
+ levels_to_lock, cancel_func, cancel_baton, pool);
+ if (err)
+ {
+ svn_error_t *err2;
+
+ /* If we got an error on the parent dir, that means we failed to
+ get an access baton for the child in the first place. And if
+ the reason we couldn't get the child access baton is that the
+ child is not a versioned directory, then return an error
+ about the child, not the parent. */
+ svn_node_kind_t child_kind;
+ if ((err2 = svn_io_check_path(path, &child_kind, pool)))
+ {
+ svn_error_compose(err, err2);
+ return err;
+ }
+
+ if ((dir != path)
+ && (child_kind == svn_node_dir)
+ && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY))
+ {
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(path, pool));
+ }
+
+ return err;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_wc_adm_access_t *
+svn_wc__adm_retrieve_internal2(svn_wc__db_t *db,
+ const char *abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_adm_access_t *adm_access = get_from_shared(abspath, db, scratch_pool);
+
+ /* If the entry is marked as "missing", then return nothing. */
+ if (IS_MISSING(adm_access))
+ adm_access = NULL;
+
+ return adm_access;
+}
+
+
+/* SVN_DEPRECATED */
+svn_error_t *
+svn_wc_adm_retrieve(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_node_kind_t kind = svn_node_unknown;
+ svn_node_kind_t wckind;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ if (strcmp(associated->path, path) == 0)
+ *adm_access = associated;
+ else
+ *adm_access = svn_wc__adm_retrieve_internal2(associated->db, local_abspath,
+ pool);
+
+ /* We found what we're looking for, so bail. */
+ if (*adm_access)
+ return SVN_NO_ERROR;
+
+ /* Most of the code expects access batons to exist, so returning an error
+ generally makes the calling code simpler as it doesn't need to check
+ for NULL batons. */
+ /* We are going to send a SVN_ERR_WC_NOT_LOCKED, but let's provide
+ a bit more information to our caller */
+
+ err = svn_io_check_path(path, &wckind, pool);
+
+ /* If we can't check the path, we can't make a good error message. */
+ if (err)
+ {
+ return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, err,
+ _("Unable to check path existence for '%s'"),
+ svn_dirent_local_style(path, pool));
+ }
+
+ if (associated)
+ {
+ err = svn_wc__db_read_kind(&kind, svn_wc__adm_get_db(associated),
+ local_abspath,
+ TRUE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */, pool);
+
+ if (err)
+ {
+ kind = svn_node_unknown;
+ svn_error_clear(err);
+ }
+ }
+
+ if (kind == svn_node_dir && wckind == svn_node_file)
+ {
+ err = svn_error_createf(
+ SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("Expected '%s' to be a directory but found a file"),
+ svn_dirent_local_style(path, pool));
+
+ return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
+ }
+
+ if (kind != svn_node_dir && kind != svn_node_unknown)
+ {
+ err = svn_error_createf(
+ SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("Can't retrieve an access baton for non-directory '%s'"),
+ svn_dirent_local_style(path, pool));
+
+ return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
+ }
+
+ if (kind == svn_node_unknown || wckind == svn_node_none)
+ {
+ err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Directory '%s' is missing"),
+ svn_dirent_local_style(path, pool));
+
+ return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message);
+ }
+
+ /* If all else fails, return our useless generic error. */
+ return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
+ _("Working copy '%s' is not locked"),
+ svn_dirent_local_style(path, pool));
+}
+
+
+/* SVN_DEPRECATED */
+svn_error_t *
+svn_wc_adm_probe_retrieve(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *dir;
+ const char *local_abspath;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(associated != NULL);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR(svn_wc__db_read_kind(&kind, associated->db, local_abspath,
+ TRUE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden*/,
+ pool));
+
+ if (kind == svn_node_dir)
+ dir = path;
+ else if (kind != svn_node_unknown)
+ dir = svn_dirent_dirname(path, pool);
+ else
+ /* Not a versioned item, probe it */
+ SVN_ERR(probe(associated->db, &dir, path, pool));
+
+ err = svn_wc_adm_retrieve(adm_access, associated, dir, pool);
+ if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED)
+ {
+ /* We'll receive a NOT LOCKED error for various reasons,
+ including the reason we'll actually want to test for:
+ The path is a versioned directory, but missing, in which case
+ we want its parent's adm_access (which holds minimal data
+ on the child) */
+ svn_error_clear(err);
+ SVN_ERR(probe(associated->db, &dir, path, pool));
+ SVN_ERR(svn_wc_adm_retrieve(adm_access, associated, dir, pool));
+ }
+ else
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* SVN_DEPRECATED */
+svn_error_t *
+svn_wc_adm_probe_try3(svn_wc_adm_access_t **adm_access,
+ svn_wc_adm_access_t *associated,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ err = svn_wc_adm_probe_retrieve(adm_access, associated, path, pool);
+
+ /* SVN_ERR_WC_NOT_LOCKED would mean there was no access baton for
+ path in associated, in which case we want to open an access
+ baton and add it to associated. */
+ if (err && (err->apr_err == SVN_ERR_WC_NOT_LOCKED))
+ {
+ svn_error_clear(err);
+ err = svn_wc_adm_probe_open3(adm_access, associated,
+ path, write_lock, levels_to_lock,
+ cancel_func, cancel_baton,
+ svn_wc_adm_access_pool(associated));
+
+ /* If the path is not a versioned directory, we just return a
+ null access baton with no error. Note that of the errors we
+ do report, the most important (and probably most likely) is
+ SVN_ERR_WC_LOCKED. That error would mean that someone else
+ has this area locked, and we definitely want to bail in that
+ case. */
+ if (err && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY))
+ {
+ svn_error_clear(err);
+ *adm_access = NULL;
+ err = NULL;
+ }
+ }
+
+ return err;
+}
+
+
+/* */
+static svn_error_t *
+child_is_disjoint(svn_boolean_t *disjoint,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_switched;
+
+ /* Check if the parent directory knows about this node */
+ SVN_ERR(svn_wc__db_is_switched(disjoint, &is_switched, NULL,
+ db, local_abspath, scratch_pool));
+
+ if (*disjoint)
+ return SVN_NO_ERROR;
+
+ if (is_switched)
+ *disjoint = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+open_anchor(svn_wc_adm_access_t **anchor_access,
+ svn_wc_adm_access_t **target_access,
+ const char **target,
+ svn_wc__db_t *db,
+ svn_boolean_t db_provided,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const char *base_name = svn_dirent_basename(path, pool);
+
+ /* Any baton creation is going to need a shared structure for holding
+ data across the entire set. The caller isn't providing one, so we
+ do it here. */
+ /* ### we could maybe skip the shared struct for levels_to_lock==0, but
+ ### given that we need DB for format detection, may as well keep this.
+ ### in any case, much of this is going to be simplified soon anyways. */
+ if (!db_provided)
+ SVN_ERR(svn_wc__db_open(&db, NULL, /* ### config. need! */ FALSE, TRUE,
+ pool, pool));
+
+ if (svn_path_is_empty(path)
+ || svn_dirent_is_root(path, strlen(path))
+ || ! strcmp(base_name, ".."))
+ {
+ SVN_ERR(open_all(anchor_access, path, db, db_provided,
+ write_lock, levels_to_lock,
+ cancel_func, cancel_baton, pool));
+ *target_access = *anchor_access;
+ *target = "";
+ }
+ else
+ {
+ svn_error_t *err;
+ svn_wc_adm_access_t *p_access = NULL;
+ svn_wc_adm_access_t *t_access = NULL;
+ const char *parent = svn_dirent_dirname(path, pool);
+ const char *local_abspath;
+ svn_error_t *p_access_err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ /* Try to open parent of PATH to setup P_ACCESS */
+ err = open_single(&p_access, parent, write_lock, db, db_provided,
+ pool, pool);
+ if (err)
+ {
+ const char *abspath = svn_dirent_dirname(local_abspath, pool);
+ svn_wc_adm_access_t *existing_adm = svn_wc__db_temp_get_access(db, abspath, pool);
+
+ if (IS_MISSING(existing_adm))
+ svn_wc__db_temp_clear_access(db, abspath, pool);
+ else
+ SVN_ERR_ASSERT(existing_adm == NULL);
+
+ if (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
+ {
+ svn_error_clear(err);
+ p_access = NULL;
+ }
+ else if (write_lock && (err->apr_err == SVN_ERR_WC_LOCKED
+ || APR_STATUS_IS_EACCES(err->apr_err)))
+ {
+ /* If P_ACCESS isn't to be returned then a read-only baton
+ will do for now, but keep the error in case we need it. */
+ svn_error_t *err2 = open_single(&p_access, parent, FALSE,
+ db, db_provided, pool, pool);
+ if (err2)
+ {
+ svn_error_clear(err2);
+ return err;
+ }
+ p_access_err = err;
+ }
+ else
+ return err;
+ }
+
+ /* Try to open PATH to setup T_ACCESS */
+ err = open_all(&t_access, path, db, db_provided, write_lock,
+ levels_to_lock, cancel_func, cancel_baton, pool);
+ if (err)
+ {
+ if (p_access == NULL)
+ {
+ /* Couldn't open the parent or the target. Bail out. */
+ svn_error_clear(p_access_err);
+ return svn_error_trace(err);
+ }
+
+ if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ {
+ if (p_access)
+ svn_error_clear(svn_wc_adm_close2(p_access, pool));
+ svn_error_clear(p_access_err);
+ return svn_error_trace(err);
+ }
+
+ /* This directory is not under version control. Ignore it. */
+ svn_error_clear(err);
+ t_access = NULL;
+ }
+
+ /* At this stage might have P_ACCESS, T_ACCESS or both */
+
+ /* Check for switched or disjoint P_ACCESS and T_ACCESS */
+ if (p_access && t_access)
+ {
+ svn_boolean_t disjoint;
+
+ err = child_is_disjoint(&disjoint, db, local_abspath, pool);
+ if (err)
+ {
+ svn_error_clear(p_access_err);
+ svn_error_clear(svn_wc_adm_close2(p_access, pool));
+ svn_error_clear(svn_wc_adm_close2(t_access, pool));
+ return svn_error_trace(err);
+ }
+
+ if (disjoint)
+ {
+ /* Switched or disjoint, so drop P_ACCESS. Don't close any
+ descendents, or we might blast the child. */
+ err = close_single(p_access, FALSE /* preserve_lock */, pool);
+ if (err)
+ {
+ svn_error_clear(p_access_err);
+ svn_error_clear(svn_wc_adm_close2(t_access, pool));
+ return svn_error_trace(err);
+ }
+ p_access = NULL;
+ }
+ }
+
+ /* We have a parent baton *and* we have an error related to opening
+ the baton. That means we have a readonly baton, but that isn't
+ going to work for us. (p_access would have been set to NULL if
+ a writable parent baton is not required) */
+ if (p_access && p_access_err)
+ {
+ if (t_access)
+ svn_error_clear(svn_wc_adm_close2(t_access, pool));
+ svn_error_clear(svn_wc_adm_close2(p_access, pool));
+ return svn_error_trace(p_access_err);
+ }
+ svn_error_clear(p_access_err);
+
+ if (! t_access)
+ {
+ svn_boolean_t available;
+ svn_node_kind_t kind;
+
+ err = adm_available(&available, &kind, db, local_abspath, pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ svn_error_clear(err);
+ else if (err)
+ {
+ svn_error_clear(svn_wc_adm_close2(p_access, pool));
+ return svn_error_trace(err);
+ }
+ }
+
+ *anchor_access = p_access ? p_access : t_access;
+ *target_access = t_access ? t_access : p_access;
+
+ if (! p_access)
+ *target = "";
+ else
+ *target = base_name;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_adm_open_anchor(svn_wc_adm_access_t **anchor_access,
+ svn_wc_adm_access_t **target_access,
+ const char **target,
+ const char *path,
+ svn_boolean_t write_lock,
+ int levels_to_lock,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(open_anchor(anchor_access, target_access, target,
+ NULL, FALSE, path, write_lock,
+ levels_to_lock, cancel_func,
+ cancel_baton, pool));
+}
+
+
+/* Does the work of closing the access baton ADM_ACCESS. Any physical
+ locks are removed from the working copy if PRESERVE_LOCK is FALSE, or
+ are left if PRESERVE_LOCK is TRUE. Any associated access batons that
+ are direct descendants will also be closed.
+ */
+static svn_error_t *
+do_close(svn_wc_adm_access_t *adm_access,
+ svn_boolean_t preserve_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_adm_access_t *look;
+
+ if (adm_access->closed)
+ return SVN_NO_ERROR;
+
+ /* If we are part of the shared set, then close descendant batons. */
+ look = get_from_shared(adm_access->abspath, adm_access->db, scratch_pool);
+ if (look != NULL)
+ {
+ apr_hash_t *opened;
+ apr_hash_index_t *hi;
+
+ /* Gather all the opened access batons from the DB. */
+ opened = svn_wc__db_temp_get_all_access(adm_access->db, scratch_pool);
+
+ /* Close any that are descendents of this baton. */
+ for (hi = apr_hash_first(scratch_pool, opened);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *abspath = svn__apr_hash_index_key(hi);
+ svn_wc_adm_access_t *child = svn__apr_hash_index_val(hi);
+ const char *path = child->path;
+
+ if (IS_MISSING(child))
+ {
+ /* We don't close the missing entry, but get rid of it from
+ the set. */
+ svn_wc__db_temp_clear_access(adm_access->db, abspath,
+ scratch_pool);
+ continue;
+ }
+
+ if (! svn_dirent_is_ancestor(adm_access->path, path)
+ || strcmp(adm_access->path, path) == 0)
+ continue;
+
+ SVN_ERR(close_single(child, preserve_lock, scratch_pool));
+ }
+ }
+
+ return svn_error_trace(close_single(adm_access, preserve_lock,
+ scratch_pool));
+}
+
+
+/* SVN_DEPRECATED */
+svn_error_t *
+svn_wc_adm_close2(svn_wc_adm_access_t *adm_access, apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(do_close(adm_access, FALSE, scratch_pool));
+}
+
+
+/* SVN_DEPRECATED */
+svn_boolean_t
+svn_wc_adm_locked(const svn_wc_adm_access_t *adm_access)
+{
+ svn_boolean_t locked;
+ apr_pool_t *subpool = svn_pool_create(adm_access->pool);
+ svn_error_t *err = svn_wc__db_wclock_owns_lock(&locked, adm_access->db,
+ adm_access->abspath, TRUE,
+ subpool);
+ svn_pool_destroy(subpool);
+
+ if (err)
+ {
+ svn_error_clear(err);
+ /* ### is this right? */
+ return FALSE;
+ }
+
+ return locked;
+}
+
+svn_error_t *
+svn_wc__write_check(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t locked;
+
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&locked, db, local_abspath, FALSE,
+ scratch_pool));
+ if (!locked)
+ return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
+ _("No write-lock in '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_locked2(svn_boolean_t *locked_here,
+ svn_boolean_t *locked,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (locked_here != NULL)
+ SVN_ERR(svn_wc__db_wclock_owns_lock(locked_here, wc_ctx->db, local_abspath,
+ FALSE, scratch_pool));
+ if (locked != NULL)
+ SVN_ERR(svn_wc__db_wclocked(locked, wc_ctx->db, local_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* SVN_DEPRECATED */
+const char *
+svn_wc_adm_access_path(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->path;
+}
+
+
+const char *
+svn_wc__adm_access_abspath(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->abspath;
+}
+
+
+/* SVN_DEPRECATED */
+apr_pool_t *
+svn_wc_adm_access_pool(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->pool;
+}
+
+apr_pool_t *
+svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->pool;
+}
+
+void
+svn_wc__adm_access_set_entries(svn_wc_adm_access_t *adm_access,
+ apr_hash_t *entries)
+{
+ adm_access->entries_all = entries;
+}
+
+
+apr_hash_t *
+svn_wc__adm_access_entries(svn_wc_adm_access_t *adm_access)
+{
+ /* Compile with -DSVN_DISABLE_ENTRY_CACHE to disable the in-memory
+ entry caching. As of 2010-03-18 (r924708) merge_tests 34 and 134
+ fail during "make check". */
+#ifdef SVN_DISABLE_ENTRY_CACHE
+ return NULL;
+#else
+ return adm_access->entries_all;
+#endif
+}
+
+
+svn_wc__db_t *
+svn_wc__adm_get_db(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->db;
+}
+
+svn_error_t *
+svn_wc__acquire_write_lock(const char **lock_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t lock_anchor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ svn_boolean_t is_wcroot;
+ svn_boolean_t is_switched;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+
+ err = svn_wc__db_is_switched(&is_wcroot, &is_switched, &kind,
+ db, local_abspath, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ kind = svn_node_none;
+ is_wcroot = FALSE;
+ is_switched = FALSE;
+ }
+
+ if (!lock_root_abspath && kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL,
+ _("Can't obtain lock on non-directory '%s'."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ if (lock_anchor && kind == svn_node_dir)
+ {
+ if (is_wcroot)
+ lock_anchor = FALSE;
+ }
+
+ if (lock_anchor)
+ {
+ const char *parent_abspath;
+ SVN_ERR_ASSERT(lock_root_abspath != NULL);
+
+ parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ if (kind == svn_node_dir)
+ {
+ if (! is_switched)
+ local_abspath = parent_abspath;
+ }
+ else if (kind != svn_node_none && kind != svn_node_unknown)
+ {
+ /* In the single-DB world we know parent exists */
+ local_abspath = parent_abspath;
+ }
+ else
+ {
+ /* Can't lock parents that don't exist */
+ svn_node_kind_t parent_kind;
+ err = svn_wc__db_read_kind(&parent_kind, db, parent_abspath,
+ TRUE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */,
+ scratch_pool);
+ if (err && SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
+ {
+ svn_error_clear(err);
+ parent_kind = svn_node_unknown;
+ }
+ else
+ SVN_ERR(err);
+
+ if (parent_kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ local_abspath = parent_abspath;
+ }
+ }
+ else if (kind != svn_node_dir)
+ {
+ local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ }
+
+ if (lock_root_abspath)
+ *lock_root_abspath = apr_pstrdup(result_pool, local_abspath);
+
+ SVN_ERR(svn_wc__db_wclock_obtain(wc_ctx->db, local_abspath,
+ -1 /* levels_to_lock (infinite) */,
+ FALSE /* steal_lock */,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__release_write_lock(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ apr_uint64_t id;
+ svn_skel_t *work_item;
+
+ SVN_ERR(svn_wc__db_wq_fetch_next(&id, &work_item, wc_ctx->db, local_abspath,
+ 0, scratch_pool, scratch_pool));
+ if (work_item)
+ {
+ /* Do not release locks (here or below) if there is work to do. */
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc__db_wclock_release(wc_ctx->db, local_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__call_with_write_lock(svn_wc__with_write_lock_func_t func,
+ void *baton,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t lock_anchor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err1, *err2;
+ const char *lock_root_abspath;
+
+ SVN_ERR(svn_wc__acquire_write_lock(&lock_root_abspath, wc_ctx, local_abspath,
+ lock_anchor, scratch_pool, scratch_pool));
+ err1 = svn_error_trace(func(baton, result_pool, scratch_pool));
+ err2 = svn_wc__release_write_lock(wc_ctx, lock_root_abspath, scratch_pool);
+ return svn_error_compose_create(err1, err2);
+}
+
+
+svn_error_t *
+svn_wc__acquire_write_lock_for_resolve(const char **lock_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t locked = FALSE;
+ const char *obtained_abspath;
+ const char *requested_abspath = local_abspath;
+
+ while (!locked)
+ {
+ const char *required_abspath;
+ const char *child;
+
+ SVN_ERR(svn_wc__acquire_write_lock(&obtained_abspath, wc_ctx,
+ requested_abspath, FALSE,
+ scratch_pool, scratch_pool));
+ locked = TRUE;
+
+ SVN_ERR(svn_wc__required_lock_for_resolve(&required_abspath,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* It's possible for the required lock path to be an ancestor
+ of, a descendent of, or equal to, the obtained lock path. If
+ it's an ancestor we have to try again, otherwise the obtained
+ lock will do. */
+ child = svn_dirent_skip_ancestor(required_abspath, obtained_abspath);
+ if (child && child[0])
+ {
+ SVN_ERR(svn_wc__release_write_lock(wc_ctx, obtained_abspath,
+ scratch_pool));
+ locked = FALSE;
+ requested_abspath = required_abspath;
+ }
+ else
+ {
+ /* required should be a descendent of, or equal to, obtained */
+ SVN_ERR_ASSERT(!strcmp(required_abspath, obtained_abspath)
+ || svn_dirent_skip_ancestor(obtained_abspath,
+ required_abspath));
+ }
+ }
+
+ *lock_root_abspath = apr_pstrdup(result_pool, obtained_abspath);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/lock.h b/contrib/subversion/subversion/libsvn_wc/lock.h
new file mode 100644
index 0000000..e015c7e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/lock.h
@@ -0,0 +1,91 @@
+/*
+ * lock.h: routines for locking working copy subdirectories.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_WC_LOCK_H
+#define SVN_LIBSVN_WC_LOCK_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_wc.h"
+
+#include "wc_db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** General utilities that may get moved upstairs at some point. */
+
+/* Store ENTRIES in the cache in ADM_ACCESS. ENTRIES may be NULL. */
+void svn_wc__adm_access_set_entries(svn_wc_adm_access_t *adm_access,
+ apr_hash_t *entries);
+
+/* Return the entries hash cached in ADM_ACCESS. The returned hash may
+ be NULL. */
+apr_hash_t *svn_wc__adm_access_entries(svn_wc_adm_access_t *adm_access);
+
+/* Same as svn_wc__adm_retrieve_internal, but takes a DB and an absolute
+ directory path. */
+svn_wc_adm_access_t *
+svn_wc__adm_retrieve_internal2(svn_wc__db_t *db,
+ const char *abspath,
+ apr_pool_t *scratch_pool);
+
+/* ### this is probably bunk. but I dunna want to trace backwards-compat
+ ### users of svn_wc_check_wc(). probably gonna be rewritten for wc-ng
+ ### in any case.
+
+ If CHECK_PATH is TRUE, a not-existing directory is not a working copy */
+svn_error_t *
+svn_wc__internal_check_wc(int *wc_format,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t check_path,
+ apr_pool_t *scratch_pool);
+
+/* Return the working copy database associated with this access baton. */
+svn_wc__db_t *
+svn_wc__adm_get_db(const svn_wc_adm_access_t *adm_access);
+
+
+/* Get a reference to the baton's internal ABSPATH. */
+const char *
+svn_wc__adm_access_abspath(const svn_wc_adm_access_t *adm_access);
+
+/* Return the pool used by access baton ADM_ACCESS.
+ * Note: This is a non-deprecated variant of svn_wc_adm_access_pool for
+ * libsvn_wc internal usage only.
+ */
+apr_pool_t *
+svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_LOCK_H */
diff --git a/contrib/subversion/subversion/libsvn_wc/merge.c b/contrib/subversion/subversion/libsvn_wc/merge.c
new file mode 100644
index 0000000..7cff3e4
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/merge.c
@@ -0,0 +1,1424 @@
+/*
+ * merge.c: merging changes into a working file
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_wc.h"
+#include "svn_diff.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "translate.h"
+#include "workqueue.h"
+
+#include "private/svn_skel.h"
+
+#include "svn_private_config.h"
+
+/* Contains some information on the merge target before merge, and some
+ information needed for the diff processing. */
+typedef struct merge_target_t
+{
+ svn_wc__db_t *db; /* The DB used to access target */
+ const char *local_abspath; /* The absolute path to target */
+ const char *wri_abspath; /* The working copy of target */
+
+ apr_hash_t *old_actual_props; /* The set of actual properties
+ before merging */
+ const apr_array_header_t *prop_diff; /* The property changes */
+
+ const char *diff3_cmd; /* The diff3 command and options */
+ const apr_array_header_t *merge_options;
+
+} merge_target_t;
+
+
+/* Return a pointer to the svn_prop_t structure from PROP_DIFF
+ belonging to PROP_NAME, if any. NULL otherwise.*/
+static const svn_prop_t *
+get_prop(const apr_array_header_t *prop_diff,
+ const char *prop_name)
+{
+ if (prop_diff)
+ {
+ int i;
+ for (i = 0; i < prop_diff->nelts; i++)
+ {
+ const svn_prop_t *elt = &APR_ARRAY_IDX(prop_diff, i,
+ svn_prop_t);
+
+ if (strcmp(elt->name, prop_name) == 0)
+ return elt;
+ }
+ }
+
+ return NULL;
+}
+
+
+/* Detranslate a working copy file MERGE_TARGET to achieve the effect of:
+
+ 1. Detranslate
+ 2. Install new props
+ 3. Retranslate
+ 4. Detranslate
+
+ in one pass, to get a file which can be compared with the left and right
+ files which are in repository normal form.
+
+ Property changes make this a little complex though. Changes in
+
+ - svn:mime-type
+ - svn:eol-style
+ - svn:keywords
+ - svn:special
+
+ may change the way a file is translated.
+
+ Effect for svn:mime-type:
+
+ If svn:mime-type is considered 'binary', we ignore svn:eol-style (but
+ still translate keywords).
+
+ I) both old and new mime-types are texty
+ -> just do the translation dance (as lined out below)
+ ### actually we do a shortcut with just one translation:
+ detranslate with the old keywords and ... eol-style
+ (the new re+detranslation is a no-op w.r.t. keywords [1])
+
+ II) the old one is texty, the new one is binary
+ -> detranslate with the old eol-style and keywords
+ (the new re+detranslation is a no-op [1])
+
+ III) the old one is binary, the new one texty
+ -> detranslate with the old keywords and new eol-style
+ (the old detranslation is a no-op w.r.t. eol, and
+ the new re+detranslation is a no-op w.r.t. keywords [1])
+
+ IV) the old and new ones are binary
+ -> detranslate with the old keywords
+ (the new re+detranslation is a no-op [1])
+
+ Effect for svn:eol-style
+
+ I) On add or change of svn:eol-style, use the new value
+
+ II) otherwise: use the old value (absent means 'no translation')
+
+ Effect for svn:keywords
+
+ Always use the old settings (re+detranslation are no-op [1]).
+
+ [1] Translation of keywords from repository normal form to WC form and
+ back is normally a no-op, but is not a no-op if text contains a kw
+ that is only enabled by the new props and is present in non-
+ contracted form (such as "$Rev: 1234 $"). If we want to catch this
+ case we should detranslate with both the old & the new keywords
+ together.
+
+ Effect for svn:special
+
+ Always use the old settings (re+detranslation are no-op).
+
+ Sets *DETRANSLATED_ABSPATH to the path to the detranslated file,
+ this may be the same as SOURCE_ABSPATH if FORCE_COPY is FALSE and no
+ translation is required.
+
+ If FORCE_COPY is FALSE and *DETRANSLATED_ABSPATH is a file distinct
+ from SOURCE_ABSPATH then the file will be deleted on RESULT_POOL
+ cleanup.
+
+ If FORCE_COPY is TRUE then *DETRANSLATED_ABSPATH will always be a
+ new file distinct from SOURCE_ABSPATH and it will be the callers
+ responsibility to delete the file.
+
+*/
+static svn_error_t *
+detranslate_wc_file(const char **detranslated_abspath,
+ const merge_target_t *mt,
+ svn_boolean_t force_copy,
+ const char *source_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t old_is_binary, new_is_binary;
+ svn_subst_eol_style_t style;
+ const char *eol;
+ apr_hash_t *keywords;
+ svn_boolean_t special;
+
+ {
+ const char *old_mime_value
+ = svn_prop_get_value(mt->old_actual_props, SVN_PROP_MIME_TYPE);
+ const svn_prop_t *prop = get_prop(mt->prop_diff, SVN_PROP_MIME_TYPE);
+ const char *new_mime_value
+ = prop ? (prop->value ? prop->value->data : NULL) : old_mime_value;
+
+ old_is_binary = old_mime_value && svn_mime_type_is_binary(old_mime_value);
+ new_is_binary = new_mime_value && svn_mime_type_is_binary(new_mime_value);;
+ }
+
+ /* See what translations we want to do */
+ if (old_is_binary && new_is_binary)
+ {
+ /* Case IV. Old and new props 'binary': detranslate keywords only */
+ SVN_ERR(svn_wc__get_translate_info(NULL, NULL, &keywords, NULL,
+ mt->db, mt->local_abspath,
+ mt->old_actual_props, TRUE,
+ scratch_pool, scratch_pool));
+ /* ### Why override 'special'? Elsewhere it has precedence. */
+ special = FALSE;
+ eol = NULL;
+ style = svn_subst_eol_style_none;
+ }
+ else if (!old_is_binary && new_is_binary)
+ {
+ /* Case II. Old props indicate texty, new props indicate binary:
+ detranslate keywords and old eol-style */
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special,
+ mt->db, mt->local_abspath,
+ mt->old_actual_props, TRUE,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ /* Case I & III. New props indicate texty, regardless of old props */
+
+ /* In case the file used to be special, detranslate specially */
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special,
+ mt->db, mt->local_abspath,
+ mt->old_actual_props, TRUE,
+ scratch_pool, scratch_pool));
+
+ if (special)
+ {
+ keywords = NULL;
+ eol = NULL;
+ style = svn_subst_eol_style_none;
+ }
+ else
+ {
+ const svn_prop_t *prop;
+
+ /* In case a new eol style was set, use that for detranslation */
+ if ((prop = get_prop(mt->prop_diff, SVN_PROP_EOL_STYLE)) && prop->value)
+ {
+ /* Value added or changed */
+ svn_subst_eol_style_from_value(&style, &eol, prop->value->data);
+ }
+ else if (!old_is_binary)
+ {
+ /* Already fetched */
+ }
+ else
+ {
+ eol = NULL;
+ style = svn_subst_eol_style_none;
+ }
+ }
+ }
+
+ /* Now, detranslate with the settings we created above */
+
+ if (force_copy || keywords || eol || special)
+ {
+ const char *temp_dir_abspath;
+ const char *detranslated;
+
+ /* Force a copy into the temporary wc area to avoid having
+ temporary files created below to appear in the actual wc. */
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db,
+ mt->wri_abspath,
+ scratch_pool, scratch_pool));
+
+ /* ### svn_subst_copy_and_translate4() also creates a tempfile
+ ### internally. Anyway to piggyback on that? */
+ SVN_ERR(svn_io_open_unique_file3(NULL, &detranslated, temp_dir_abspath,
+ (force_copy
+ ? svn_io_file_del_none
+ : svn_io_file_del_on_pool_cleanup),
+ result_pool, scratch_pool));
+
+ /* Always 'repair' EOLs here, so that we can apply a diff that
+ changes from inconsistent newlines and no 'svn:eol-style' to
+ consistent newlines and 'svn:eol-style' set. */
+
+ if (style == svn_subst_eol_style_native)
+ eol = SVN_SUBST_NATIVE_EOL_STR;
+ else if (style != svn_subst_eol_style_fixed
+ && style != svn_subst_eol_style_none)
+ return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
+
+ SVN_ERR(svn_subst_copy_and_translate4(source_abspath,
+ detranslated,
+ eol,
+ TRUE /* repair */,
+ keywords,
+ FALSE /* contract keywords */,
+ special,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ SVN_ERR(svn_dirent_get_absolute(detranslated_abspath, detranslated,
+ result_pool));
+ }
+ else
+ *detranslated_abspath = apr_pstrdup(result_pool, source_abspath);
+
+ return SVN_NO_ERROR;
+}
+
+/* Updates (by copying and translating) the eol style in
+ OLD_TARGET_ABSPATH returning the filename containing the
+ correct eol style in NEW_TARGET_ABSPATH, if an eol style
+ change is contained in PROP_DIFF. */
+static svn_error_t *
+maybe_update_target_eols(const char **new_target_abspath,
+ const apr_array_header_t *prop_diff,
+ const char *old_target_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE);
+
+ if (prop && prop->value)
+ {
+ const char *eol;
+ const char *tmp_new;
+
+ svn_subst_eol_style_from_value(NULL, &eol, prop->value->data);
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_new, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+
+ /* Always 'repair' EOLs here, so that we can apply a diff that
+ changes from inconsistent newlines and no 'svn:eol-style' to
+ consistent newlines and 'svn:eol-style' set. */
+ SVN_ERR(svn_subst_copy_and_translate4(old_target_abspath,
+ tmp_new,
+ eol,
+ TRUE /* repair */,
+ NULL /* keywords */,
+ FALSE /* expand */,
+ FALSE /* special */,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ *new_target_abspath = apr_pstrdup(result_pool, tmp_new);
+ }
+ else
+ *new_target_abspath = apr_pstrdup(result_pool, old_target_abspath);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *TARGET_MARKER, *LEFT_MARKER and *RIGHT_MARKER to strings suitable
+ for delimiting the alternative texts in a text conflict. Include in each
+ marker a string that may be given by TARGET_LABEL, LEFT_LABEL and
+ RIGHT_LABEL respectively or a default value where any of those are NULL.
+
+ Allocate the results in POOL or statically. */
+static void
+init_conflict_markers(const char **target_marker,
+ const char **left_marker,
+ const char **right_marker,
+ const char *target_label,
+ const char *left_label,
+ const char *right_label,
+ apr_pool_t *pool)
+{
+ /* Labels fall back to sensible defaults if not specified. */
+ if (target_label)
+ *target_marker = apr_psprintf(pool, "<<<<<<< %s", target_label);
+ else
+ *target_marker = "<<<<<<< .working";
+
+ if (left_label)
+ *left_marker = apr_psprintf(pool, "||||||| %s", left_label);
+ else
+ *left_marker = "||||||| .old";
+
+ if (right_label)
+ *right_marker = apr_psprintf(pool, ">>>>>>> %s", right_label);
+ else
+ *right_marker = ">>>>>>> .new";
+}
+
+/* Do a 3-way merge of the files at paths LEFT, DETRANSLATED_TARGET,
+ * and RIGHT, using diff options provided in MERGE_OPTIONS. Store the merge
+ * result in the file RESULT_F.
+ * If there are conflicts, set *CONTAINS_CONFLICTS to true, and use
+ * TARGET_LABEL, LEFT_LABEL, and RIGHT_LABEL as labels for conflict
+ * markers. Else, set *CONTAINS_CONFLICTS to false.
+ * Do all allocations in POOL. */
+static svn_error_t *
+do_text_merge(svn_boolean_t *contains_conflicts,
+ apr_file_t *result_f,
+ const apr_array_header_t *merge_options,
+ const char *detranslated_target,
+ const char *left,
+ const char *right,
+ const char *target_label,
+ const char *left_label,
+ const char *right_label,
+ apr_pool_t *pool)
+{
+ svn_diff_t *diff;
+ svn_stream_t *ostream;
+ const char *target_marker;
+ const char *left_marker;
+ const char *right_marker;
+ svn_diff_file_options_t *diff3_options;
+
+ diff3_options = svn_diff_file_options_create(pool);
+
+ if (merge_options)
+ SVN_ERR(svn_diff_file_options_parse(diff3_options,
+ merge_options, pool));
+
+
+ init_conflict_markers(&target_marker, &left_marker, &right_marker,
+ target_label, left_label, right_label, pool);
+
+ SVN_ERR(svn_diff_file_diff3_2(&diff, left, detranslated_target, right,
+ diff3_options, pool));
+
+ ostream = svn_stream_from_aprfile2(result_f, TRUE, pool);
+
+ SVN_ERR(svn_diff_file_output_merge2(ostream, diff,
+ left, detranslated_target, right,
+ left_marker,
+ target_marker,
+ right_marker,
+ "=======", /* separator */
+ svn_diff_conflict_display_modified_latest,
+ pool));
+ SVN_ERR(svn_stream_close(ostream));
+
+ *contains_conflicts = svn_diff_contains_conflicts(diff);
+
+ return SVN_NO_ERROR;
+}
+
+/* Same as do_text_merge() above, but use the external diff3
+ * command DIFF3_CMD to perform the merge. Pass MERGE_OPTIONS
+ * to the diff3 command. Do all allocations in POOL. */
+static svn_error_t *
+do_text_merge_external(svn_boolean_t *contains_conflicts,
+ apr_file_t *result_f,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ const char *detranslated_target,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_label,
+ const char *left_label,
+ const char *right_label,
+ apr_pool_t *scratch_pool)
+{
+ int exit_code;
+
+ SVN_ERR(svn_io_run_diff3_3(&exit_code, ".",
+ detranslated_target, left_abspath, right_abspath,
+ target_label, left_label, right_label,
+ result_f, diff3_cmd,
+ merge_options, scratch_pool));
+
+ *contains_conflicts = exit_code == 1;
+
+ return SVN_NO_ERROR;
+}
+
+/* Preserve the three pre-merge files.
+
+ Create three empty files, with unique names that each include the
+ basename of TARGET_ABSPATH and one of LEFT_LABEL, RIGHT_LABEL and
+ TARGET_LABEL, in the directory that contains TARGET_ABSPATH. Typical
+ names are "foo.c.r37" or "foo.c.2.mine". Set *LEFT_COPY, *RIGHT_COPY and
+ *TARGET_COPY to their absolute paths.
+
+ Set *WORK_ITEMS to a list of new work items that will write copies of
+ LEFT_ABSPATH, RIGHT_ABSPATH and TARGET_ABSPATH into the three files,
+ translated to working-copy form.
+
+ The translation to working-copy form will be done according to the
+ versioned properties of TARGET_ABSPATH that are current when the work
+ queue items are executed.
+
+ If target_abspath is not versioned use detranslated_target_abspath
+ as the target file.
+ ### NOT IMPLEMENTED -- 'detranslated_target_abspath' is not used.
+*/
+static svn_error_t *
+preserve_pre_merge_files(svn_skel_t **work_items,
+ const char **left_copy,
+ const char **right_copy,
+ const char **target_copy,
+ const merge_target_t *mt,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ const char *detranslated_target_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *tmp_left, *tmp_right, *detranslated_target_copy;
+ const char *dir_abspath, *target_name;
+ const char *wcroot_abspath, *temp_dir_abspath;
+ svn_skel_t *work_item, *last_items = NULL;
+
+ *work_items = NULL;
+
+ svn_dirent_split(&dir_abspath, &target_name, mt->local_abspath,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, mt->db, mt->wri_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db,
+ mt->wri_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Create three empty files in DIR_ABSPATH, naming them with unique names
+ that each include TARGET_NAME and one of {LEFT,RIGHT,TARGET}_LABEL,
+ and set *{LEFT,RIGHT,TARGET}_COPY to those names. */
+ SVN_ERR(svn_io_open_uniquely_named(
+ NULL, left_copy, dir_abspath, target_name, left_label,
+ svn_io_file_del_none, result_pool, scratch_pool));
+ SVN_ERR(svn_io_open_uniquely_named(
+ NULL, right_copy, dir_abspath, target_name, right_label,
+ svn_io_file_del_none, result_pool, scratch_pool));
+ SVN_ERR(svn_io_open_uniquely_named(
+ NULL, target_copy, dir_abspath, target_name, target_label,
+ svn_io_file_del_none, result_pool, scratch_pool));
+
+ /* We preserve all the files with keywords expanded and line
+ endings in local (working) form. */
+
+ /* The workingqueue requires its paths to be in the subtree
+ relative to the wcroot path they are executed in.
+
+ Make our LEFT and RIGHT files 'local' if they aren't... */
+ if (! svn_dirent_is_ancestor(wcroot_abspath, left_abspath))
+ {
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_left, temp_dir_abspath,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_copy_file(left_abspath, tmp_left, TRUE, scratch_pool));
+
+ /* And create a wq item to remove the file later */
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
+ tmp_left,
+ result_pool, scratch_pool));
+
+ last_items = svn_wc__wq_merge(last_items, work_item, result_pool);
+ }
+ else
+ tmp_left = left_abspath;
+
+ if (! svn_dirent_is_ancestor(wcroot_abspath, right_abspath))
+ {
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_right, temp_dir_abspath,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_copy_file(right_abspath, tmp_right, TRUE, scratch_pool));
+
+ /* And create a wq item to remove the file later */
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
+ tmp_right,
+ result_pool, scratch_pool));
+
+ last_items = svn_wc__wq_merge(last_items, work_item, result_pool);
+ }
+ else
+ tmp_right = right_abspath;
+
+ /* NOTE: Callers must ensure that the svn:eol-style and
+ svn:keywords property values are correct in the currently
+ installed props. With 'svn merge', it's no big deal. But
+ when 'svn up' calls this routine, it needs to make sure that
+ this routine is using the newest property values that may
+ have been received *during* the update. Since this routine
+ will be run from within a log-command, merge_file()
+ needs to make sure that a previous log-command to 'install
+ latest props' has already executed first. Ben and I just
+ checked, and that is indeed the order in which the log items
+ are written, so everything should be fine. Really. */
+
+ /* Create LEFT and RIGHT backup files, in expanded form.
+ We use TARGET_ABSPATH's current properties to do the translation. */
+ /* Derive the basenames of the 3 backup files. */
+ SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
+ mt->db, mt->local_abspath,
+ tmp_left, *left_copy,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
+ mt->db, mt->local_abspath,
+ tmp_right, *right_copy,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ /* Back up TARGET_ABSPATH through detranslation/retranslation:
+ the new translation properties may not match the current ones */
+ SVN_ERR(detranslate_wc_file(&detranslated_target_copy, mt, TRUE,
+ mt->local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item,
+ mt->db, mt->local_abspath,
+ detranslated_target_copy,
+ *target_copy,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ /* And maybe delete some tempfiles */
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath,
+ detranslated_target_copy,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ *work_items = svn_wc__wq_merge(*work_items, last_items, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Attempt a trivial merge of LEFT_ABSPATH and RIGHT_ABSPATH to
+ * the target file at TARGET_ABSPATH.
+ *
+ * These are the inherently trivial cases:
+ *
+ * left == right == target => no-op
+ * left != right, left == target => target := right
+ *
+ * This case is also treated as trivial:
+ *
+ * left != right, right == target => no-op
+ *
+ * ### Strictly, this case is a conflict, and the no-op outcome is only
+ * one of the possible resolutions.
+ *
+ * TODO: Raise a conflict at this level and implement the 'no-op'
+ * resolution of that conflict at a higher level, in preparation for
+ * being able to support stricter conflict detection.
+ *
+ * This case is inherently trivial but not currently handled here:
+ *
+ * left == right != target => no-op
+ *
+ * The files at LEFT_ABSPATH and RIGHT_ABSPATH are in repository normal
+ * form. The file at DETRANSLATED_TARGET_ABSPATH is a copy of the target,
+ * 'detranslated' to repository normal form, or may be the target file
+ * itself if no translation is necessary.
+ *
+ * When this function updates the target file, it translates to working copy
+ * form.
+ *
+ * On success, set *MERGE_OUTCOME to SVN_WC_MERGE_MERGED in case the
+ * target was changed, or to SVN_WC_MERGE_UNCHANGED if the target was not
+ * changed. Install work queue items allocated in RESULT_POOL in *WORK_ITEMS.
+ * On failure, set *MERGE_OUTCOME to SVN_WC_MERGE_NO_MERGE.
+ */
+static svn_error_t *
+merge_file_trivial(svn_skel_t **work_items,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_abspath,
+ const char *detranslated_target_abspath,
+ svn_boolean_t dry_run,
+ svn_wc__db_t *db,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *work_item;
+ svn_boolean_t same_left_right;
+ svn_boolean_t same_right_target;
+ svn_boolean_t same_left_target;
+ svn_node_kind_t kind;
+ svn_boolean_t is_special;
+
+ /* If the target is not a normal file, do not attempt a trivial merge. */
+ SVN_ERR(svn_io_check_special_path(target_abspath, &kind, &is_special,
+ scratch_pool));
+ if (kind != svn_node_file || is_special)
+ {
+ *merge_outcome = svn_wc_merge_no_merge;
+ return SVN_NO_ERROR;
+ }
+
+ /* Check the files */
+ SVN_ERR(svn_io_files_contents_three_same_p(&same_left_right,
+ &same_right_target,
+ &same_left_target,
+ left_abspath,
+ right_abspath,
+ detranslated_target_abspath,
+ scratch_pool));
+
+ /* If the LEFT side of the merge is equal to WORKING, then we can
+ * copy RIGHT directly. */
+ if (same_left_target)
+ {
+ /* If the left side equals the right side, there is no change to merge
+ * so we leave the target unchanged. */
+ if (same_left_right)
+ {
+ *merge_outcome = svn_wc_merge_unchanged;
+ }
+ else
+ {
+ *merge_outcome = svn_wc_merge_merged;
+ if (!dry_run)
+ {
+ const char *wcroot_abspath;
+ svn_boolean_t delete_src = FALSE;
+
+ /* The right_abspath might be outside our working copy. In that
+ case we should copy the file to a safe location before
+ installing to avoid breaking the workqueue.
+
+ This matches the behavior in preserve_pre_merge_files */
+
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath,
+ db, target_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!svn_dirent_is_child(wcroot_abspath, right_abspath, NULL))
+ {
+ svn_stream_t *tmp_src;
+ svn_stream_t *tmp_dst;
+
+ SVN_ERR(svn_stream_open_readonly(&tmp_src, right_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__open_writable_base(&tmp_dst, &right_abspath,
+ NULL, NULL,
+ db, target_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_stream_copy3(tmp_src, tmp_dst,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ delete_src = TRUE;
+ }
+
+ SVN_ERR(svn_wc__wq_build_file_install(
+ &work_item, db, target_abspath, right_abspath,
+ FALSE /* use_commit_times */,
+ FALSE /* record_fileinfo */,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item,
+ result_pool);
+
+ if (delete_src)
+ {
+ SVN_ERR(svn_wc__wq_build_file_remove(
+ &work_item, db, wcroot_abspath,
+ right_abspath,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item,
+ result_pool);
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* If the locally existing, changed file equals the incoming 'right'
+ * file, there is no conflict. For binary files, we historically
+ * conflicted them needlessly, while merge_text_file figured it out
+ * eventually and returned svn_wc_merge_unchanged for them, which
+ * is what we do here. */
+ if (same_right_target)
+ {
+ *merge_outcome = svn_wc_merge_unchanged;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ *merge_outcome = svn_wc_merge_no_merge;
+ return SVN_NO_ERROR;
+}
+
+
+/* Handle a non-trivial merge of 'text' files. (Assume that a trivial
+ * merge was not possible.)
+ *
+ * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME according to the
+ * result -- to install the merged file, or to indicate a conflict.
+ *
+ * On successful merge, leave the result in a temporary file and set
+ * *WORK_ITEMS to hold work items that will translate and install that
+ * file into its proper form and place (unless DRY_RUN) and delete the
+ * temporary file (in any case). Set *MERGE_OUTCOME to 'merged' or
+ * 'unchanged'.
+ *
+ * If a conflict occurs, set *MERGE_OUTCOME to 'conflicted', and (unless
+ * DRY_RUN) set *WORK_ITEMS and *CONFLICT_SKEL to record the conflict
+ * and copies of the pre-merge files. See preserve_pre_merge_files()
+ * for details.
+ *
+ * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL
+ * must either point to an existing conflict skel or be NULL.
+ */
+static svn_error_t*
+merge_text_file(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ const merge_target_t *mt,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ svn_boolean_t dry_run,
+ const char *detranslated_target_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *pool = scratch_pool; /* ### temporary rename */
+ svn_boolean_t contains_conflicts;
+ apr_file_t *result_f;
+ const char *result_target;
+ const char *base_name;
+ const char *temp_dir;
+ svn_skel_t *work_item;
+
+ *work_items = NULL;
+
+ base_name = svn_dirent_basename(mt->local_abspath, scratch_pool);
+
+ /* Open a second temporary file for writing; this is where diff3
+ will write the merged results. We want to use a tempfile
+ with a name that reflects the original, in case this
+ ultimately winds up in a conflict resolution editor. */
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, mt->db, mt->wri_abspath,
+ pool, pool));
+ SVN_ERR(svn_io_open_uniquely_named(&result_f, &result_target,
+ temp_dir, base_name, ".tmp",
+ svn_io_file_del_none, pool, pool));
+
+ /* Run the external or internal merge, as requested. */
+ if (mt->diff3_cmd)
+ SVN_ERR(do_text_merge_external(&contains_conflicts,
+ result_f,
+ mt->diff3_cmd,
+ mt->merge_options,
+ detranslated_target_abspath,
+ left_abspath,
+ right_abspath,
+ target_label,
+ left_label,
+ right_label,
+ pool));
+ else /* Use internal merge. */
+ SVN_ERR(do_text_merge(&contains_conflicts,
+ result_f,
+ mt->merge_options,
+ detranslated_target_abspath,
+ left_abspath,
+ right_abspath,
+ target_label,
+ left_label,
+ right_label,
+ pool));
+
+ SVN_ERR(svn_io_file_close(result_f, pool));
+
+ /* Determine the MERGE_OUTCOME, and record any conflict. */
+ if (contains_conflicts && ! dry_run)
+ {
+ *merge_outcome = svn_wc_merge_conflict;
+ if (*merge_outcome == svn_wc_merge_conflict)
+ {
+ const char *left_copy, *right_copy, *target_copy;
+
+ /* Preserve the three conflict files */
+ SVN_ERR(preserve_pre_merge_files(
+ &work_item,
+ &left_copy, &right_copy, &target_copy,
+ mt, left_abspath, right_abspath,
+ left_label, right_label, target_label,
+ detranslated_target_abspath,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ /* Track the conflict marker files in the metadata. */
+
+ if (!*conflict_skel)
+ *conflict_skel = svn_wc__conflict_skel_create(result_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel,
+ mt->db, mt->local_abspath,
+ target_copy,
+ left_copy,
+ right_copy,
+ result_pool,
+ scratch_pool));
+ }
+
+ if (*merge_outcome == svn_wc_merge_merged)
+ goto done;
+ }
+ else if (contains_conflicts && dry_run)
+ *merge_outcome = svn_wc_merge_conflict;
+ else
+ {
+ svn_boolean_t same, special;
+
+ /* If 'special', then use the detranslated form of the
+ target file. This is so we don't try to follow symlinks,
+ but the same treatment is probably also appropriate for
+ whatever special file types we may invent in the future. */
+ SVN_ERR(svn_wc__get_translate_info(NULL, NULL, NULL,
+ &special, mt->db, mt->local_abspath,
+ mt->old_actual_props, TRUE,
+ pool, pool));
+ SVN_ERR(svn_io_files_contents_same_p(&same, result_target,
+ (special ?
+ detranslated_target_abspath :
+ mt->local_abspath),
+ pool));
+
+ *merge_outcome = same ? svn_wc_merge_unchanged : svn_wc_merge_merged;
+ }
+
+ if (*merge_outcome != svn_wc_merge_unchanged && ! dry_run)
+ {
+ /* replace TARGET_ABSPATH with the new merged file, expanding. */
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item,
+ mt->db, mt->local_abspath,
+ result_target,
+ FALSE /* use_commit_times */,
+ FALSE /* record_fileinfo */,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+ }
+
+done:
+ /* Remove the tempfile after use */
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, mt->local_abspath,
+ result_target,
+ result_pool, scratch_pool));
+
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Handle a non-trivial merge of 'binary' files: don't actually merge, just
+ * flag a conflict. (Assume that a trivial merge was not possible.)
+ *
+ * Copy* the files at LEFT_ABSPATH and RIGHT_ABSPATH into the same directory
+ * as the target file, giving them unique names that start with the target
+ * file's name and end with LEFT_LABEL and RIGHT_LABEL respectively.
+ * If the merge target has been 'detranslated' to repository normal form,
+ * move the detranslated file similarly to a unique name ending with
+ * TARGET_LABEL.
+ *
+ * ### * Why do we copy the left and right temp files when we could (maybe
+ * not always?) move them?
+ *
+ * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL
+ * must either point to an existing conflict skel or be NULL.
+ *
+ * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME to indicate the
+ * conflict.
+ *
+ * ### Why do we not use preserve_pre_merge_files() in here? The
+ * behaviour would be slightly different, more consistent: the
+ * preserved 'left' and 'right' files would be translated to working
+ * copy form, which may make a difference when a binary file
+ * contains keyword expansions or when some versions of the file are
+ * not 'binary' even though we're merging in 'binary files' mode.
+ */
+static svn_error_t *
+merge_binary_file(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ const merge_target_t *mt,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ svn_boolean_t dry_run,
+ const char *detranslated_target_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *pool = scratch_pool; /* ### temporary rename */
+ /* ### when making the binary-file backups, should we be honoring
+ keywords and eol stuff? */
+ const char *left_copy, *right_copy;
+ const char *merge_dirpath, *merge_filename;
+ const char *conflict_wrk;
+
+ *work_items = NULL;
+
+ svn_dirent_split(&merge_dirpath, &merge_filename, mt->local_abspath, pool);
+
+ if (dry_run)
+ {
+ *merge_outcome = svn_wc_merge_conflict;
+ return SVN_NO_ERROR;
+ }
+
+ /* reserve names for backups of left and right fulltexts */
+ SVN_ERR(svn_io_open_uniquely_named(NULL,
+ &left_copy,
+ merge_dirpath,
+ merge_filename,
+ left_label,
+ svn_io_file_del_none,
+ pool, pool));
+
+ SVN_ERR(svn_io_open_uniquely_named(NULL,
+ &right_copy,
+ merge_dirpath,
+ merge_filename,
+ right_label,
+ svn_io_file_del_none,
+ pool, pool));
+
+ /* create the backup files */
+ SVN_ERR(svn_io_copy_file(left_abspath, left_copy, TRUE, pool));
+ SVN_ERR(svn_io_copy_file(right_abspath, right_copy, TRUE, pool));
+
+ /* Was the merge target detranslated? */
+ if (strcmp(mt->local_abspath, detranslated_target_abspath) != 0)
+ {
+ /* Create a .mine file too */
+ SVN_ERR(svn_io_open_uniquely_named(NULL,
+ &conflict_wrk,
+ merge_dirpath,
+ merge_filename,
+ target_label,
+ svn_io_file_del_none,
+ pool, pool));
+ SVN_ERR(svn_wc__wq_build_file_move(work_items, mt->db,
+ mt->local_abspath,
+ detranslated_target_abspath,
+ conflict_wrk,
+ pool, result_pool));
+ }
+ else
+ {
+ conflict_wrk = NULL;
+ }
+
+ /* Mark target_abspath's entry as "Conflicted", and start tracking
+ the backup files in the entry as well. */
+ if (!*conflict_skel)
+ *conflict_skel = svn_wc__conflict_skel_create(result_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel,
+ mt->db, mt->local_abspath,
+ conflict_wrk,
+ left_copy,
+ right_copy,
+ result_pool, scratch_pool));
+
+ *merge_outcome = svn_wc_merge_conflict; /* a conflict happened */
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_merge(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ svn_wc__db_t *db,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_abspath,
+ const char *wri_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ apr_hash_t *old_actual_props,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ const apr_array_header_t *prop_diff,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *detranslated_target_abspath;
+ svn_boolean_t is_binary = FALSE;
+ const svn_prop_t *mimeprop;
+ svn_skel_t *work_item;
+ merge_target_t mt;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
+
+ *work_items = NULL;
+
+ /* Fill the merge target baton */
+ mt.db = db;
+ mt.local_abspath = target_abspath;
+ mt.wri_abspath = wri_abspath;
+ mt.old_actual_props = old_actual_props;
+ mt.prop_diff = prop_diff;
+ mt.diff3_cmd = diff3_cmd;
+ mt.merge_options = merge_options;
+
+ /* Decide if the merge target is a text or binary file. */
+ if ((mimeprop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
+ && mimeprop->value)
+ is_binary = svn_mime_type_is_binary(mimeprop->value->data);
+ else
+ {
+ const char *value = svn_prop_get_value(mt.old_actual_props,
+ SVN_PROP_MIME_TYPE);
+
+ is_binary = value && svn_mime_type_is_binary(value);
+ }
+
+ SVN_ERR(detranslate_wc_file(&detranslated_target_abspath, &mt,
+ (! is_binary) && diff3_cmd != NULL,
+ target_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ /* We cannot depend on the left file to contain the same eols as the
+ right file. If the merge target has mods, this will mark the entire
+ file as conflicted, so we need to compensate. */
+ SVN_ERR(maybe_update_target_eols(&left_abspath, prop_diff, left_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(merge_file_trivial(work_items, merge_outcome,
+ left_abspath, right_abspath,
+ target_abspath, detranslated_target_abspath,
+ dry_run, db, cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+ if (*merge_outcome == svn_wc_merge_no_merge)
+ {
+ /* We have a non-trivial merge. If we classify it as a merge of
+ * 'binary' files we'll just raise a conflict, otherwise we'll do
+ * the actual merge of 'text' file contents. */
+ if (is_binary)
+ {
+ /* Raise a text conflict */
+ SVN_ERR(merge_binary_file(work_items,
+ conflict_skel,
+ merge_outcome,
+ &mt,
+ left_abspath,
+ right_abspath,
+ left_label,
+ right_label,
+ target_label,
+ dry_run,
+ detranslated_target_abspath,
+ result_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(merge_text_file(work_items,
+ conflict_skel,
+ merge_outcome,
+ &mt,
+ left_abspath,
+ right_abspath,
+ left_label,
+ right_label,
+ target_label,
+ dry_run,
+ detranslated_target_abspath,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+ }
+ }
+
+ /* Merging is complete. Regardless of text or binariness, we might
+ need to tweak the executable bit on the new working file, and
+ possibly make it read-only. */
+ if (! dry_run)
+ {
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db,
+ target_abspath,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_merge5(enum svn_wc_merge_outcome_t *merge_content_outcome,
+ enum svn_wc_notify_state_t *merge_props_outcome,
+ svn_wc_context_t *wc_ctx,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ apr_hash_t *original_props,
+ const apr_array_header_t *prop_diff,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *dir_abspath = svn_dirent_dirname(target_abspath, scratch_pool);
+ svn_skel_t *work_items;
+ svn_skel_t *conflict_skel = NULL;
+ apr_hash_t *pristine_props = NULL;
+ apr_hash_t *old_actual_props;
+ apr_hash_t *new_actual_props = NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
+
+ /* Before we do any work, make sure we hold a write lock. */
+ if (!dry_run)
+ SVN_ERR(svn_wc__write_check(wc_ctx->db, dir_abspath, scratch_pool));
+
+ /* Sanity check: the merge target must be a file under revision control */
+ {
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+ svn_boolean_t conflicted;
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, &had_props, &props_mod,
+ NULL, NULL, NULL,
+ wc_ctx->db, target_abspath,
+ scratch_pool, scratch_pool));
+
+ if (kind != svn_node_file || (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added))
+ {
+ *merge_content_outcome = svn_wc_merge_no_merge;
+ if (merge_props_outcome)
+ *merge_props_outcome = svn_wc_notify_state_unchanged;
+ return SVN_NO_ERROR;
+ }
+
+ if (conflicted)
+ {
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ svn_boolean_t tree_conflicted;
+
+ SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
+ &prop_conflicted,
+ &tree_conflicted,
+ wc_ctx->db, target_abspath,
+ scratch_pool));
+
+ /* We can't install two prop conflicts on a single node, so
+ avoid even checking that we have to merge it */
+ if (text_conflicted || prop_conflicted || tree_conflicted)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Can't merge into conflicted node '%s'"),
+ svn_dirent_local_style(target_abspath,
+ scratch_pool));
+ }
+ /* else: Conflict was resolved by removing markers */
+ }
+
+ if (merge_props_outcome && had_props)
+ {
+ SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props,
+ wc_ctx->db, target_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else if (merge_props_outcome)
+ pristine_props = apr_hash_make(scratch_pool);
+
+ if (props_mod)
+ {
+ SVN_ERR(svn_wc__db_read_props(&old_actual_props,
+ wc_ctx->db, target_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else if (pristine_props)
+ old_actual_props = pristine_props;
+ else
+ old_actual_props = apr_hash_make(scratch_pool);
+ }
+
+ /* Merge the properties, if requested. We merge the properties first
+ * because the properties can affect the text (EOL style, keywords). */
+ if (merge_props_outcome)
+ {
+ int i;
+
+ /* The PROPCHANGES may not have non-"normal" properties in it. If entry
+ or wc props were allowed, then the following code would install them
+ into the BASE and/or WORKING properties(!). */
+ for (i = prop_diff->nelts; i--; )
+ {
+ const svn_prop_t *change = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t);
+
+ if (!svn_wc_is_normal_prop(change->name))
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("The property '%s' may not be merged "
+ "into '%s'."),
+ change->name,
+ svn_dirent_local_style(target_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__merge_props(&conflict_skel,
+ merge_props_outcome,
+ &new_actual_props,
+ wc_ctx->db, target_abspath,
+ original_props, pristine_props, old_actual_props,
+ prop_diff,
+ scratch_pool, scratch_pool));
+ }
+
+ /* Merge the text. */
+ SVN_ERR(svn_wc__internal_merge(&work_items,
+ &conflict_skel,
+ merge_content_outcome,
+ wc_ctx->db,
+ left_abspath,
+ right_abspath,
+ target_abspath,
+ target_abspath,
+ left_label, right_label, target_label,
+ old_actual_props,
+ dry_run,
+ diff3_cmd,
+ merge_options,
+ prop_diff,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ /* If this isn't a dry run, then update the DB, run the work, and
+ * call the conflict resolver callback. */
+ if (!dry_run)
+ {
+ if (conflict_skel)
+ {
+ svn_skel_t *work_item;
+
+ SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
+ left_version,
+ right_version,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ wc_ctx->db, target_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ if (new_actual_props)
+ SVN_ERR(svn_wc__db_op_set_props(wc_ctx->db, target_abspath,
+ new_actual_props,
+ svn_wc__has_magic_property(prop_diff),
+ conflict_skel, work_items,
+ scratch_pool));
+ else if (conflict_skel)
+ SVN_ERR(svn_wc__db_op_mark_conflict(wc_ctx->db, target_abspath,
+ conflict_skel, work_items,
+ scratch_pool));
+ else if (work_items)
+ SVN_ERR(svn_wc__db_wq_add(wc_ctx->db, target_abspath, work_items,
+ scratch_pool));
+
+ if (work_items)
+ SVN_ERR(svn_wc__wq_run(wc_ctx->db, target_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ if (conflict_skel && conflict_func)
+ {
+ svn_boolean_t text_conflicted, prop_conflicted;
+
+ SVN_ERR(svn_wc__conflict_invoke_resolver(
+ wc_ctx->db, target_abspath,
+ conflict_skel, merge_options,
+ conflict_func, conflict_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* Reset *MERGE_CONTENT_OUTCOME etc. if a conflict was resolved. */
+ SVN_ERR(svn_wc__internal_conflicted_p(
+ &text_conflicted, &prop_conflicted, NULL,
+ wc_ctx->db, target_abspath, scratch_pool));
+ if (*merge_props_outcome == svn_wc_notify_state_conflicted
+ && ! prop_conflicted)
+ *merge_props_outcome = svn_wc_notify_state_merged;
+ if (*merge_content_outcome == svn_wc_merge_conflict
+ && ! text_conflicted)
+ *merge_content_outcome = svn_wc_merge_merged;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/node.c b/contrib/subversion/subversion/libsvn_wc/node.c
new file mode 100644
index 0000000..a1d6b02
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/node.c
@@ -0,0 +1,1418 @@
+/*
+ * node.c: routines for getting information about nodes in the working copy.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* A note about these functions:
+
+ We aren't really sure yet which bits of data libsvn_client needs about
+ nodes. In wc-1, we just grab the entry, and then use whatever we want
+ from it. Such a pattern is Bad.
+
+ This file is intended to hold functions which retrieve specific bits of
+ information about a node, and will hopefully give us a better idea about
+ what data libsvn_client needs, and how to best provide that data in 1.7
+ final. As such, these functions should only be called from outside
+ libsvn_wc; any internal callers are encouraged to use the appropriate
+ information fetching function, such as svn_wc__db_read_info().
+*/
+
+#include <apr_pools.h>
+#include <apr_time.h>
+
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_types.h"
+
+#include "wc.h"
+#include "props.h"
+#include "entries.h"
+#include "wc_db.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+/* Set *CHILDREN_ABSPATHS to a new array of the full paths formed by joining
+ * each name in REL_CHILDREN onto DIR_ABSPATH. If SHOW_HIDDEN is false then
+ * omit any paths that are reported as 'hidden' by svn_wc__db_node_hidden().
+ *
+ * Allocate the output array and its elements in RESULT_POOL. */
+static svn_error_t *
+filter_and_make_absolute(const apr_array_header_t **children_abspaths,
+ svn_wc_context_t *wc_ctx,
+ const char *dir_abspath,
+ const apr_array_header_t *rel_children,
+ svn_boolean_t show_hidden,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *children;
+ int i;
+
+ children = apr_array_make(result_pool, rel_children->nelts,
+ sizeof(const char *));
+ for (i = 0; i < rel_children->nelts; i++)
+ {
+ const char *child_abspath = svn_dirent_join(dir_abspath,
+ APR_ARRAY_IDX(rel_children,
+ i,
+ const char *),
+ result_pool);
+
+ /* Don't add hidden nodes to *CHILDREN if we don't want them. */
+ if (!show_hidden)
+ {
+ svn_boolean_t child_is_hidden;
+
+ SVN_ERR(svn_wc__db_node_hidden(&child_is_hidden, wc_ctx->db,
+ child_abspath, scratch_pool));
+ if (child_is_hidden)
+ continue;
+ }
+
+ APR_ARRAY_PUSH(children, const char *) = child_abspath;
+ }
+
+ *children_abspaths = children;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__node_get_children_of_working_node(const apr_array_header_t **children,
+ svn_wc_context_t *wc_ctx,
+ const char *dir_abspath,
+ svn_boolean_t show_hidden,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *rel_children;
+
+ SVN_ERR(svn_wc__db_read_children_of_working_node(&rel_children,
+ wc_ctx->db, dir_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(filter_and_make_absolute(children, wc_ctx, dir_abspath,
+ rel_children, show_hidden,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_children(const apr_array_header_t **children,
+ svn_wc_context_t *wc_ctx,
+ const char *dir_abspath,
+ svn_boolean_t show_hidden,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *rel_children;
+
+ SVN_ERR(svn_wc__db_read_children(&rel_children, wc_ctx->db, dir_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(filter_and_make_absolute(children, wc_ctx, dir_abspath,
+ rel_children, show_hidden,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__internal_get_repos_info(svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_boolean_t have_work;
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, repos_relpath,
+ repos_root_url, repos_uuid,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, &have_work,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ if ((repos_relpath ? *repos_relpath != NULL : TRUE)
+ && (repos_root_url ? *repos_root_url != NULL: TRUE)
+ && (repos_uuid ? *repos_uuid != NULL : TRUE))
+ return SVN_NO_ERROR; /* We got the requested information */
+
+ if (!have_work) /* not-present, (server-)excluded? */
+ {
+ return SVN_NO_ERROR; /* Can't fetch more */
+ }
+
+ if (status == svn_wc__db_status_deleted)
+ {
+ const char *base_del_abspath, *wrk_del_abspath;
+
+ SVN_ERR(svn_wc__db_scan_deletion(&base_del_abspath, NULL,
+ &wrk_del_abspath, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (base_del_abspath)
+ {
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, repos_relpath,
+ repos_root_url, repos_uuid, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, base_del_abspath,
+ result_pool, scratch_pool));
+
+ /* If we have a repos_relpath, it is of the op-root */
+ if (repos_relpath)
+ *repos_relpath = svn_relpath_join(*repos_relpath,
+ svn_dirent_skip_ancestor(base_del_abspath,
+ local_abspath),
+ result_pool);
+ /* We keep revision as SVN_INVALID_REVNUM */
+ }
+ else if (wrk_del_abspath)
+ {
+ const char *op_root_abspath = NULL;
+
+ SVN_ERR(svn_wc__db_scan_addition(NULL, repos_relpath
+ ? &op_root_abspath : NULL,
+ repos_relpath, repos_root_url,
+ repos_uuid, NULL, NULL, NULL, NULL,
+ db, svn_dirent_dirname(
+ wrk_del_abspath,
+ scratch_pool),
+ result_pool, scratch_pool));
+
+ /* If we have a repos_relpath, it is of the op-root */
+ if (repos_relpath)
+ *repos_relpath = svn_relpath_join(
+ *repos_relpath,
+ svn_dirent_skip_ancestor(op_root_abspath,
+ local_abspath),
+ result_pool);
+ }
+ }
+ else /* added, or WORKING incomplete */
+ {
+ const char *op_root_abspath = NULL;
+
+ /* We have an addition. scan_addition() will find the intended
+ repository location by scanning up the tree. */
+ SVN_ERR(svn_wc__db_scan_addition(NULL, repos_relpath
+ ? &op_root_abspath : NULL,
+ repos_relpath, repos_root_url,
+ repos_uuid, NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ }
+
+ SVN_ERR_ASSERT(repos_root_url == NULL || *repos_root_url != NULL);
+ SVN_ERR_ASSERT(repos_uuid == NULL || *repos_uuid != NULL);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_repos_info(svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__internal_get_repos_info(revision,
+ repos_relpath,
+ repos_root_url,
+ repos_uuid,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+/* Convert DB_KIND into the appropriate NODE_KIND value.
+ * If SHOW_HIDDEN is TRUE, report the node kind as found in the DB
+ * even if DB_STATUS indicates that the node is hidden.
+ * Else, return svn_node_none for such nodes.
+ *
+ * ### This is a bit ugly. We should consider promoting svn_kind_t
+ * ### to the de-facto node kind type instead of converting between them
+ * ### in non-backwards compat code.
+ * ### See also comments at the definition of svn_kind_t.
+ *
+ * ### In reality, the previous comment is out of date, as there is
+ * ### now only one enumeration for node kinds, and that is
+ * ### svn_node_kind_t (svn_kind_t was merged with that). But it's
+ * ### still ugly.
+ */
+static svn_error_t *
+convert_db_kind_to_node_kind(svn_node_kind_t *node_kind,
+ svn_node_kind_t db_kind,
+ svn_wc__db_status_t db_status,
+ svn_boolean_t show_hidden)
+{
+ *node_kind = db_kind;
+
+ /* Make sure hidden nodes return svn_node_none. */
+ if (! show_hidden)
+ switch (db_status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_excluded:
+ *node_kind = svn_node_none;
+
+ default:
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_read_kind2(svn_node_kind_t *kind,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t show_deleted,
+ svn_boolean_t show_hidden,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t db_kind;
+
+ SVN_ERR(svn_wc__db_read_kind(&db_kind,
+ wc_ctx->db, local_abspath,
+ TRUE,
+ show_deleted,
+ show_hidden,
+ scratch_pool));
+
+ if (db_kind == svn_node_dir)
+ *kind = svn_node_dir;
+ else if (db_kind == svn_node_file || db_kind == svn_node_symlink)
+ *kind = svn_node_file;
+ else
+ *kind = svn_node_none;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_depth(svn_depth_t *depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, depth, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath, scratch_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__node_get_changed_info(svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, changed_rev,
+ changed_date, changed_author, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath, result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__node_get_url(const char **url,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_read_url(url, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+/* A recursive node-walker, helper for svn_wc__internal_walk_children().
+ *
+ * Call WALK_CALLBACK with WALK_BATON on all children (recursively) of
+ * DIR_ABSPATH in DB, but not on DIR_ABSPATH itself. DIR_ABSPATH must be a
+ * versioned directory. If SHOW_HIDDEN is true, visit hidden nodes, else
+ * ignore them. Restrict the depth of the walk to DEPTH.
+ *
+ * ### Is it possible for a subdirectory to be hidden and known to be a
+ * directory? If so, and if show_hidden is true, this will try to
+ * recurse into it. */
+static svn_error_t *
+walker_helper(svn_wc__db_t *db,
+ const char *dir_abspath,
+ svn_boolean_t show_hidden,
+ const apr_hash_t *changelist_filter,
+ svn_wc__node_found_func_t walk_callback,
+ void *walk_baton,
+ svn_depth_t depth,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *rel_children_info;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_read_children_walker_info(&rel_children_info, db,
+ dir_abspath, scratch_pool,
+ scratch_pool));
+
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, rel_children_info);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child_name = svn__apr_hash_index_key(hi);
+ struct svn_wc__db_walker_info_t *wi = svn__apr_hash_index_val(hi);
+ svn_node_kind_t child_kind = wi->kind;
+ svn_wc__db_status_t child_status = wi->status;
+ const char *child_abspath;
+
+ svn_pool_clear(iterpool);
+
+ /* See if someone wants to cancel this operation. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ child_abspath = svn_dirent_join(dir_abspath, child_name, iterpool);
+
+ if (!show_hidden)
+ switch (child_status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_excluded:
+ continue;
+ default:
+ break;
+ }
+
+ /* Return the child, if appropriate. */
+ if ( (child_kind == svn_node_file
+ || depth >= svn_depth_immediates)
+ && svn_wc__internal_changelist_match(db, child_abspath,
+ changelist_filter,
+ scratch_pool) )
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(convert_db_kind_to_node_kind(&kind, child_kind,
+ child_status, show_hidden));
+ /* ### We might want to pass child_status as well because at least
+ * ### one callee is asking for it.
+ * ### But is it OK to use an svn_wc__db type in this API?
+ * ### Not yet, we need to get the node walker
+ * ### libsvn_wc-internal first. -hkw */
+ SVN_ERR(walk_callback(child_abspath, kind, walk_baton, iterpool));
+ }
+
+ /* Recurse into this directory, if appropriate. */
+ if (child_kind == svn_node_dir
+ && depth >= svn_depth_immediates)
+ {
+ svn_depth_t depth_below_here = depth;
+
+ if (depth == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ SVN_ERR(walker_helper(db, child_abspath, show_hidden,
+ changelist_filter,
+ walk_callback, walk_baton,
+ depth_below_here,
+ cancel_func, cancel_baton,
+ iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__internal_walk_children(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t show_hidden,
+ const apr_array_header_t *changelist_filter,
+ svn_wc__node_found_func_t walk_callback,
+ void *walk_baton,
+ svn_depth_t walk_depth,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t db_kind;
+ svn_node_kind_t kind;
+ svn_wc__db_status_t status;
+ apr_hash_t *changelist_hash = NULL;
+
+ SVN_ERR_ASSERT(walk_depth >= svn_depth_empty
+ && walk_depth <= svn_depth_infinity);
+
+ if (changelist_filter && changelist_filter->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
+ scratch_pool));
+
+ /* Check if the node exists before the first callback */
+ SVN_ERR(svn_wc__db_read_info(&status, &db_kind, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ SVN_ERR(convert_db_kind_to_node_kind(&kind, db_kind, status, show_hidden));
+
+ if (svn_wc__internal_changelist_match(db, local_abspath,
+ changelist_hash, scratch_pool))
+ SVN_ERR(walk_callback(local_abspath, kind, walk_baton, scratch_pool));
+
+ if (db_kind == svn_node_file
+ || status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_server_excluded)
+ return SVN_NO_ERROR;
+
+ if (db_kind == svn_node_dir)
+ {
+ return svn_error_trace(
+ walker_helper(db, local_abspath, show_hidden, changelist_hash,
+ walk_callback, walk_baton,
+ walk_depth, cancel_func, cancel_baton, scratch_pool));
+ }
+
+ return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("'%s' has an unrecognized node kind"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__node_is_status_deleted(svn_boolean_t *is_deleted,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ SVN_ERR(svn_wc__db_read_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ *is_deleted = (status == svn_wc__db_status_deleted);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_deleted_ancestor(const char **deleted_ancestor_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ *deleted_ancestor_abspath = NULL;
+
+ SVN_ERR(svn_wc__db_read_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_deleted)
+ SVN_ERR(svn_wc__db_scan_deletion(deleted_ancestor_abspath, NULL, NULL,
+ NULL, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_is_not_present(svn_boolean_t *is_not_present,
+ svn_boolean_t *is_excluded,
+ svn_boolean_t *is_server_excluded,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t base_only,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ if (base_only)
+ {
+ SVN_ERR(svn_wc__db_base_get_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_read_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ if (is_not_present)
+ *is_not_present = (status == svn_wc__db_status_not_present);
+
+ if (is_excluded)
+ *is_excluded = (status == svn_wc__db_status_excluded);
+
+ if (is_server_excluded)
+ *is_server_excluded = (status == svn_wc__db_status_server_excluded);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_is_added(svn_boolean_t *is_added,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ SVN_ERR(svn_wc__db_read_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+ *is_added = (status == svn_wc__db_status_added);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_has_working(svn_boolean_t *has_working,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+
+ SVN_ERR(svn_wc__db_read_info(&status,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, has_working,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__node_get_base(svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **lock_token,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t ignore_enoent,
+ svn_boolean_t show_hidden,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_wc__db_status_t status;
+ svn_wc__db_lock_t *lock;
+ svn_node_kind_t db_kind;
+
+ err = svn_wc__db_base_get_info(&status, &db_kind, revision, repos_relpath,
+ repos_root_url, repos_uuid, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ lock_token ? &lock : NULL,
+ NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool);
+
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ else if (err
+ || (!err && !show_hidden
+ && (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_incomplete)))
+ {
+ if (!ignore_enoent)
+ {
+ if (err)
+ return svn_error_trace(err);
+ else
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ svn_error_clear(err);
+
+ if (kind)
+ *kind = svn_node_unknown;
+ if (revision)
+ *revision = SVN_INVALID_REVNUM;
+ if (repos_relpath)
+ *repos_relpath = NULL;
+ if (repos_root_url)
+ *repos_root_url = NULL;
+ if (repos_uuid)
+ *repos_uuid = NULL;
+ if (lock_token)
+ *lock_token = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (kind)
+ *kind = db_kind;
+ if (lock_token)
+ *lock_token = lock ? lock->token : NULL;
+
+ SVN_ERR_ASSERT(!revision || SVN_IS_VALID_REVNUM(*revision));
+ SVN_ERR_ASSERT(!repos_relpath || *repos_relpath);
+ SVN_ERR_ASSERT(!repos_root_url || *repos_root_url);
+ SVN_ERR_ASSERT(!repos_uuid || *repos_uuid);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_pre_ng_status_data(svn_revnum_t *revision,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_boolean_t have_base, have_more_work, have_work;
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, NULL, NULL, NULL,
+ changed_rev, changed_date, changed_author,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &have_base, &have_more_work, &have_work,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (!have_work
+ || ((!changed_rev || SVN_IS_VALID_REVNUM(*changed_rev))
+ && (!revision || SVN_IS_VALID_REVNUM(*revision)))
+ || ((status != svn_wc__db_status_added)
+ && (status != svn_wc__db_status_deleted)))
+ {
+ return SVN_NO_ERROR; /* We got everything we need */
+ }
+
+ if (have_base && !have_more_work)
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, NULL, NULL, NULL,
+ changed_rev, changed_date, changed_author,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+ else if (status == svn_wc__db_status_deleted)
+ /* Check the information below a WORKING delete */
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, changed_rev,
+ changed_date, changed_author, NULL,
+ NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_node_get_schedule(svn_wc_schedule_t *schedule,
+ svn_boolean_t *copied,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_boolean_t op_root;
+ svn_boolean_t have_base;
+ svn_boolean_t have_work;
+ svn_boolean_t have_more_work;
+ const char *copyfrom_relpath;
+
+ if (schedule)
+ *schedule = svn_wc_schedule_normal;
+ if (copied)
+ *copied = FALSE;
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, &copyfrom_relpath,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &op_root, NULL, NULL,
+ &have_base, &have_more_work, &have_work,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ switch (status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_excluded:
+ /* We used status normal in the entries world. */
+ if (schedule)
+ *schedule = svn_wc_schedule_normal;
+ break;
+ case svn_wc__db_status_normal:
+ case svn_wc__db_status_incomplete:
+ break;
+
+ case svn_wc__db_status_deleted:
+ {
+ if (schedule)
+ *schedule = svn_wc_schedule_delete;
+
+ if (!copied)
+ break;
+
+ if (have_more_work || !have_base)
+ *copied = TRUE;
+ else
+ {
+ const char *work_del_abspath;
+
+ /* Find out details of our deletion. */
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
+ &work_del_abspath, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (work_del_abspath)
+ *copied = TRUE; /* Working deletion */
+ }
+ break;
+ }
+ case svn_wc__db_status_added:
+ {
+ if (!op_root)
+ {
+ if (copied)
+ *copied = TRUE;
+
+ if (schedule)
+ *schedule = svn_wc_schedule_normal;
+
+ break;
+ }
+
+ if (copied)
+ *copied = (copyfrom_relpath != NULL);
+
+ if (schedule)
+ *schedule = svn_wc_schedule_add;
+ else
+ break;
+
+ /* Check for replaced */
+ if (have_base || have_more_work)
+ {
+ svn_wc__db_status_t below_working;
+ SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
+ &below_working,
+ db, local_abspath,
+ scratch_pool));
+
+ /* If the node is not present or deleted (read: not present
+ in working), then the node is not a replacement */
+ if (below_working != svn_wc__db_status_not_present
+ && below_working != svn_wc__db_status_deleted)
+ {
+ *schedule = svn_wc_schedule_replace;
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_schedule(svn_wc_schedule_t *schedule,
+ svn_boolean_t *copied,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__internal_node_get_schedule(schedule,
+ copied,
+ wc_ctx->db,
+ local_abspath,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__node_clear_dav_cache_recursive(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_base_clear_dav_cache_recursive(
+ wc_ctx->db, local_abspath, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__node_get_lock_tokens_recursive(apr_hash_t **lock_tokens,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_base_get_lock_tokens_recursive(
+ lock_tokens, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__get_excluded_subtrees(apr_hash_t **server_excluded_subtrees,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_get_excluded_subtrees(server_excluded_subtrees,
+ wc_ctx->db,
+ local_abspath,
+ result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__internal_get_origin(svn_boolean_t *is_copy,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **copy_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t scan_deleted,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *original_repos_relpath;
+ const char *original_repos_root_url;
+ const char *original_repos_uuid;
+ svn_revnum_t original_revision;
+ svn_wc__db_status_t status;
+
+ const char *tmp_repos_relpath;
+
+ if (!repos_relpath)
+ repos_relpath = &tmp_repos_relpath;
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, repos_relpath,
+ repos_root_url, repos_uuid, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ &original_repos_relpath,
+ &original_repos_root_url,
+ &original_repos_uuid, &original_revision,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, is_copy,
+ db, local_abspath, result_pool, scratch_pool));
+
+ if (*repos_relpath)
+ {
+ return SVN_NO_ERROR; /* Returned BASE information */
+ }
+
+ if (status == svn_wc__db_status_deleted && !scan_deleted)
+ {
+ if (is_copy)
+ *is_copy = FALSE; /* Deletes are stored in working; default to FALSE */
+
+ return SVN_NO_ERROR; /* No info */
+ }
+
+ if (original_repos_relpath)
+ {
+ *repos_relpath = original_repos_relpath;
+ if (revision)
+ *revision = original_revision;
+ if (repos_root_url)
+ *repos_root_url = original_repos_root_url;
+ if (repos_uuid)
+ *repos_uuid = original_repos_uuid;
+
+ if (copy_root_abspath == NULL)
+ return SVN_NO_ERROR;
+ }
+
+ {
+ svn_boolean_t scan_working = FALSE;
+
+ if (status == svn_wc__db_status_added)
+ scan_working = TRUE;
+ else if (status == svn_wc__db_status_deleted)
+ {
+ svn_boolean_t have_base;
+ /* Is this a BASE or a WORKING delete? */
+ SVN_ERR(svn_wc__db_info_below_working(&have_base, &scan_working,
+ &status, db, local_abspath,
+ scratch_pool));
+ }
+
+ if (scan_working)
+ {
+ const char *op_root_abspath;
+
+ SVN_ERR(svn_wc__db_scan_addition(&status, &op_root_abspath, NULL,
+ NULL, NULL, &original_repos_relpath,
+ repos_root_url,
+ repos_uuid, revision,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_added)
+ {
+ if (is_copy)
+ *is_copy = FALSE;
+ return SVN_NO_ERROR; /* Local addition */
+ }
+
+ /* We don't know how the following error condition can be fulfilled
+ * but we have seen that happening in the wild. Better to create
+ * an error than a SEGFAULT. */
+ if (status == svn_wc__db_status_incomplete && !original_repos_relpath)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Incomplete copy information on path '%s'."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ *repos_relpath = svn_relpath_join(
+ original_repos_relpath,
+ svn_dirent_skip_ancestor(op_root_abspath,
+ local_abspath),
+ result_pool);
+ if (copy_root_abspath)
+ *copy_root_abspath = op_root_abspath;
+ }
+ else /* Deleted, excluded, not-present, server-excluded, ... */
+ {
+ if (is_copy)
+ *is_copy = FALSE;
+
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, repos_relpath,
+ repos_root_url, repos_uuid, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+}
+
+svn_error_t *
+svn_wc__node_get_origin(svn_boolean_t *is_copy,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **copy_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t scan_deleted,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__internal_get_origin(is_copy, revision,
+ repos_relpath, repos_root_url, repos_uuid,
+ copy_root_abspath,
+ wc_ctx->db, local_abspath, scan_deleted,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__node_get_commit_status(svn_boolean_t *added,
+ svn_boolean_t *deleted,
+ svn_boolean_t *is_replace_root,
+ svn_boolean_t *is_op_root,
+ svn_revnum_t *revision,
+ svn_revnum_t *original_revision,
+ const char **original_repos_relpath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_boolean_t have_base;
+ svn_boolean_t have_more_work;
+ svn_boolean_t op_root;
+
+ /* ### All of this should be handled inside a single read transaction */
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ original_repos_relpath, NULL, NULL,
+ original_revision, NULL, NULL, NULL,
+ NULL, NULL,
+ &op_root, NULL, NULL,
+ &have_base, &have_more_work, NULL,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (added)
+ *added = (status == svn_wc__db_status_added);
+ if (deleted)
+ *deleted = (status == svn_wc__db_status_deleted);
+ if (is_op_root)
+ *is_op_root = op_root;
+
+ if (is_replace_root)
+ {
+ if (status == svn_wc__db_status_added
+ && op_root
+ && (have_base || have_more_work))
+ SVN_ERR(svn_wc__db_node_check_replace(is_replace_root, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool));
+ else
+ *is_replace_root = FALSE;
+ }
+
+ /* Retrieve some information from BASE which is needed for replacing
+ and/or deleting BASE nodes. */
+ if (have_base
+ && !have_more_work
+ && op_root
+ && (revision && !SVN_IS_VALID_REVNUM(*revision)))
+ {
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__node_get_md5_from_sha1(const svn_checksum_t **md5_checksum,
+ svn_wc_context_t *wc_ctx,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_pristine_get_md5(md5_checksum,
+ wc_ctx->db,
+ wri_abspath,
+ sha1_checksum,
+ result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__get_not_present_descendants(const apr_array_header_t **descendants,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_get_not_present_descendants(descendants,
+ wc_ctx->db,
+ local_abspath,
+ result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__rename_wc(svn_wc_context_t *wc_ctx,
+ const char *from_abspath,
+ const char *dst_abspath,
+ apr_pool_t *scratch_pool)
+{
+ const char *wcroot_abspath;
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, wc_ctx->db, from_abspath,
+ scratch_pool, scratch_pool));
+
+ if (! strcmp(from_abspath, wcroot_abspath))
+ {
+ SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, wcroot_abspath, scratch_pool));
+
+ SVN_ERR(svn_io_file_rename(from_abspath, dst_abspath, scratch_pool));
+ }
+ else
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("'%s' is not the root of the working copy '%s'"),
+ svn_dirent_local_style(from_abspath, scratch_pool),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__check_for_obstructions(svn_wc_notify_state_t *obstruction_state,
+ svn_node_kind_t *kind,
+ svn_boolean_t *deleted,
+ svn_boolean_t *excluded,
+ svn_depth_t *parent_depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t no_wcroot_check,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t db_kind;
+ svn_node_kind_t disk_kind;
+ svn_error_t *err;
+
+ *obstruction_state = svn_wc_notify_state_inapplicable;
+ if (kind)
+ *kind = svn_node_none;
+ if (deleted)
+ *deleted = FALSE;
+ if (excluded)
+ *excluded = FALSE;
+ if (parent_depth)
+ *parent_depth = svn_depth_unknown;
+
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
+
+ err = svn_wc__db_read_info(&status, &db_kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+
+ if (disk_kind != svn_node_none)
+ {
+ /* Nothing in the DB, but something on disk */
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_wc__db_read_info(&status, &db_kind, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, parent_depth, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL,
+ wc_ctx->db, svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ /* No versioned parent; we can't add a node here */
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ if (db_kind != svn_node_dir
+ || (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added))
+ {
+ /* The parent doesn't allow nodes to be added below it */
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ }
+
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ /* Check for obstructing working copies */
+ if (!no_wcroot_check
+ && db_kind == svn_node_dir
+ && status == svn_wc__db_status_normal)
+ {
+ svn_boolean_t is_root;
+ SVN_ERR(svn_wc__db_is_wcroot(&is_root, wc_ctx->db, local_abspath,
+ scratch_pool));
+
+ if (is_root)
+ {
+ /* Callers should handle this as unversioned */
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (kind)
+ SVN_ERR(convert_db_kind_to_node_kind(kind, db_kind, status, FALSE));
+
+ switch (status)
+ {
+ case svn_wc__db_status_deleted:
+ if (deleted)
+ *deleted = TRUE;
+ /* Fall through to svn_wc__db_status_not_present */
+ case svn_wc__db_status_not_present:
+ if (disk_kind != svn_node_none)
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ break;
+
+ case svn_wc__db_status_excluded:
+ case svn_wc__db_status_server_excluded:
+ if (excluded)
+ *excluded = TRUE;
+ /* fall through */
+ case svn_wc__db_status_incomplete:
+ *obstruction_state = svn_wc_notify_state_missing;
+ break;
+
+ case svn_wc__db_status_added:
+ case svn_wc__db_status_normal:
+ if (disk_kind == svn_node_none)
+ *obstruction_state = svn_wc_notify_state_missing;
+ else
+ {
+ svn_node_kind_t expected_kind;
+
+ SVN_ERR(convert_db_kind_to_node_kind(&expected_kind, db_kind,
+ status, FALSE));
+
+ if (disk_kind != expected_kind)
+ *obstruction_state = svn_wc_notify_state_obstructed;
+ }
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__node_was_moved_away(const char **moved_to_abspath,
+ const char **op_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_deleted;
+
+ if (moved_to_abspath)
+ *moved_to_abspath = NULL;
+ if (op_root_abspath)
+ *op_root_abspath = NULL;
+
+ SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, wc_ctx, local_abspath,
+ scratch_pool));
+ if (is_deleted)
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, moved_to_abspath, NULL,
+ op_root_abspath, wc_ctx->db,
+ local_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__node_was_moved_here(const char **moved_from_abspath,
+ const char **delete_op_root_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ if (moved_from_abspath)
+ *moved_from_abspath = NULL;
+ if (delete_op_root_abspath)
+ *delete_op_root_abspath = NULL;
+
+ err = svn_wc__db_scan_moved(moved_from_abspath, NULL, NULL,
+ delete_op_root_abspath,
+ wc_ctx->db, local_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ /* Return error for not added nodes */
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ /* Path not moved here */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/old-and-busted.c b/contrib/subversion/subversion/libsvn_wc/old-and-busted.c
new file mode 100644
index 0000000..20f7c6c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/old-and-busted.c
@@ -0,0 +1,1340 @@
+/*
+ * old-and-busted.c: routines for reading pre-1.7 working copies.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_time.h"
+#include "svn_xml.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_ctype.h"
+#include "svn_pools.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "entries.h"
+#include "lock.h"
+
+#include "private/svn_wc_private.h"
+#include "svn_private_config.h"
+
+
+/* Within the (old) entries file, boolean values have a specific string
+ value (thus, TRUE), or they are missing (for FALSE). Below are the
+ values for each of the booleans stored. */
+#define ENTRIES_BOOL_COPIED "copied"
+#define ENTRIES_BOOL_DELETED "deleted"
+#define ENTRIES_BOOL_ABSENT "absent"
+#define ENTRIES_BOOL_INCOMPLETE "incomplete"
+#define ENTRIES_BOOL_KEEP_LOCAL "keep-local"
+
+/* Tag names used in our old XML entries file. */
+#define ENTRIES_TAG_ENTRY "entry"
+
+/* Attribute names used in our old XML entries file. */
+#define ENTRIES_ATTR_NAME "name"
+#define ENTRIES_ATTR_REPOS "repos"
+#define ENTRIES_ATTR_UUID "uuid"
+#define ENTRIES_ATTR_INCOMPLETE "incomplete"
+#define ENTRIES_ATTR_LOCK_TOKEN "lock-token"
+#define ENTRIES_ATTR_LOCK_OWNER "lock-owner"
+#define ENTRIES_ATTR_LOCK_COMMENT "lock-comment"
+#define ENTRIES_ATTR_LOCK_CREATION_DATE "lock-creation-date"
+#define ENTRIES_ATTR_DELETED "deleted"
+#define ENTRIES_ATTR_ABSENT "absent"
+#define ENTRIES_ATTR_CMT_REV "committed-rev"
+#define ENTRIES_ATTR_CMT_DATE "committed-date"
+#define ENTRIES_ATTR_CMT_AUTHOR "last-author"
+#define ENTRIES_ATTR_REVISION "revision"
+#define ENTRIES_ATTR_URL "url"
+#define ENTRIES_ATTR_KIND "kind"
+#define ENTRIES_ATTR_SCHEDULE "schedule"
+#define ENTRIES_ATTR_COPIED "copied"
+#define ENTRIES_ATTR_COPYFROM_URL "copyfrom-url"
+#define ENTRIES_ATTR_COPYFROM_REV "copyfrom-rev"
+#define ENTRIES_ATTR_CHECKSUM "checksum"
+#define ENTRIES_ATTR_WORKING_SIZE "working-size"
+#define ENTRIES_ATTR_TEXT_TIME "text-time"
+#define ENTRIES_ATTR_CONFLICT_OLD "conflict-old" /* saved old file */
+#define ENTRIES_ATTR_CONFLICT_NEW "conflict-new" /* saved new file */
+#define ENTRIES_ATTR_CONFLICT_WRK "conflict-wrk" /* saved wrk file */
+#define ENTRIES_ATTR_PREJFILE "prop-reject-file"
+
+/* Attribute values used in our old XML entries file. */
+#define ENTRIES_VALUE_FILE "file"
+#define ENTRIES_VALUE_DIR "dir"
+#define ENTRIES_VALUE_ADD "add"
+#define ENTRIES_VALUE_DELETE "delete"
+#define ENTRIES_VALUE_REPLACE "replace"
+
+
+/* */
+static svn_wc_entry_t *
+alloc_entry(apr_pool_t *pool)
+{
+ svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry));
+ entry->revision = SVN_INVALID_REVNUM;
+ entry->copyfrom_rev = SVN_INVALID_REVNUM;
+ entry->cmt_rev = SVN_INVALID_REVNUM;
+ entry->kind = svn_node_none;
+ entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
+ entry->depth = svn_depth_infinity;
+ entry->file_external_path = NULL;
+ entry->file_external_peg_rev.kind = svn_opt_revision_unspecified;
+ entry->file_external_rev.kind = svn_opt_revision_unspecified;
+ return entry;
+}
+
+
+
+/* Read an escaped byte on the form 'xHH' from [*BUF, END), placing
+ the byte in *RESULT. Advance *BUF to point after the escape
+ sequence. */
+static svn_error_t *
+read_escaped(char *result, char **buf, const char *end)
+{
+ apr_uint64_t val;
+ char digits[3];
+
+ if (end - *buf < 3 || **buf != 'x' || ! svn_ctype_isxdigit((*buf)[1])
+ || ! svn_ctype_isxdigit((*buf)[2]))
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid escape sequence"));
+ (*buf)++;
+ digits[0] = *((*buf)++);
+ digits[1] = *((*buf)++);
+ digits[2] = 0;
+ if ((val = apr_strtoi64(digits, NULL, 16)) == 0)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid escaped character"));
+ *result = (char) val;
+ return SVN_NO_ERROR;
+}
+
+/* Read a field, possibly with escaped bytes, from [*BUF, END),
+ stopping at the terminator. Place the read string in *RESULT, or set
+ *RESULT to NULL if it is the empty string. Allocate the returned string
+ in POOL. Advance *BUF to point after the terminator. */
+static svn_error_t *
+read_str(const char **result,
+ char **buf, const char *end,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *s = NULL;
+ const char *start;
+ if (*buf == end)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Unexpected end of entry"));
+ if (**buf == '\n')
+ {
+ *result = NULL;
+ (*buf)++;
+ return SVN_NO_ERROR;
+ }
+
+ start = *buf;
+ while (*buf != end && **buf != '\n')
+ {
+ if (**buf == '\\')
+ {
+ char c;
+ if (! s)
+ s = svn_stringbuf_ncreate(start, *buf - start, pool);
+ else
+ svn_stringbuf_appendbytes(s, start, *buf - start);
+ (*buf)++;
+ SVN_ERR(read_escaped(&c, buf, end));
+ svn_stringbuf_appendbyte(s, c);
+ start = *buf;
+ }
+ else
+ (*buf)++;
+ }
+
+ if (*buf == end)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Unexpected end of entry"));
+
+ if (s)
+ {
+ svn_stringbuf_appendbytes(s, start, *buf - start);
+ *result = s->data;
+ }
+ else
+ *result = apr_pstrndup(pool, start, *buf - start);
+ (*buf)++;
+ return SVN_NO_ERROR;
+}
+
+/* This is wrapper around read_str() (which see for details); it
+ simply asks svn_path_is_canonical() of the string it reads,
+ returning an error if the test fails.
+ ### It seems this is only called for entrynames now
+ */
+static svn_error_t *
+read_path(const char **result,
+ char **buf, const char *end,
+ apr_pool_t *pool)
+{
+ SVN_ERR(read_str(result, buf, end, pool));
+ if (*result && **result && !svn_relpath_is_canonical(*result))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Entry contains non-canonical path '%s'"),
+ *result);
+ return SVN_NO_ERROR;
+}
+
+/* This is read_path() for urls. This function does not do the is_canonical
+ test for entries from working copies older than version 10, as since that
+ version the canonicalization of urls has been changed. See issue #2475.
+ If the test is done and fails, read_url returs an error. */
+static svn_error_t *
+read_url(const char **result,
+ char **buf, const char *end,
+ int wc_format,
+ apr_pool_t *pool)
+{
+ SVN_ERR(read_str(result, buf, end, pool));
+
+ /* Always canonicalize the url, as we have stricter canonicalization rules
+ in 1.7+ then before */
+ if (*result && **result)
+ *result = svn_uri_canonicalize(*result, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Read a field from [*BUF, END), terminated by a newline character.
+ The field may not contain escape sequences. The field is not
+ copied and the buffer is modified in place, by replacing the
+ terminator with a NUL byte. Make *BUF point after the original
+ terminator. */
+static svn_error_t *
+read_val(const char **result,
+ char **buf, const char *end)
+{
+ const char *start = *buf;
+
+ if (*buf == end)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Unexpected end of entry"));
+ if (**buf == '\n')
+ {
+ (*buf)++;
+ *result = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ while (*buf != end && **buf != '\n')
+ (*buf)++;
+ if (*buf == end)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Unexpected end of entry"));
+ **buf = '\0';
+ *result = start;
+ (*buf)++;
+ return SVN_NO_ERROR;
+}
+
+/* Read a boolean field from [*BUF, END), placing the result in
+ *RESULT. If there is no boolean value (just a terminator), it
+ defaults to false. Else, the value must match FIELD_NAME, in which
+ case *RESULT will be set to true. Advance *BUF to point after the
+ terminator. */
+static svn_error_t *
+read_bool(svn_boolean_t *result, const char *field_name,
+ char **buf, const char *end)
+{
+ const char *val;
+ SVN_ERR(read_val(&val, buf, end));
+ if (val)
+ {
+ if (strcmp(val, field_name) != 0)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid value for field '%s'"),
+ field_name);
+ *result = TRUE;
+ }
+ else
+ *result = FALSE;
+ return SVN_NO_ERROR;
+}
+
+/* Read a revision number from [*BUF, END) stopping at the
+ terminator. Set *RESULT to the revision number, or
+ SVN_INVALID_REVNUM if there is none. Use POOL for temporary
+ allocations. Make *BUF point after the terminator. */
+static svn_error_t *
+read_revnum(svn_revnum_t *result,
+ char **buf,
+ const char *end,
+ apr_pool_t *pool)
+{
+ const char *val;
+
+ SVN_ERR(read_val(&val, buf, end));
+
+ if (val)
+ *result = SVN_STR_TO_REV(val);
+ else
+ *result = SVN_INVALID_REVNUM;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read a timestamp from [*BUF, END) stopping at the terminator.
+ Set *RESULT to the resulting timestamp, or 0 if there is none. Use
+ POOL for temporary allocations. Make *BUF point after the
+ terminator. */
+static svn_error_t *
+read_time(apr_time_t *result,
+ char **buf, const char *end,
+ apr_pool_t *pool)
+{
+ const char *val;
+
+ SVN_ERR(read_val(&val, buf, end));
+ if (val)
+ SVN_ERR(svn_time_from_cstring(result, val, pool));
+ else
+ *result = 0;
+
+ return SVN_NO_ERROR;
+}
+
+/**
+ * Parse the string at *STR as an revision and save the result in
+ * *OPT_REV. After returning successfully, *STR points at next
+ * character in *STR where further parsing can be done.
+ */
+static svn_error_t *
+string_to_opt_revision(svn_opt_revision_t *opt_rev,
+ const char **str,
+ apr_pool_t *pool)
+{
+ const char *s = *str;
+
+ SVN_ERR_ASSERT(opt_rev);
+
+ while (*s && *s != ':')
+ ++s;
+
+ /* Should not find a \0. */
+ if (!*s)
+ return svn_error_createf
+ (SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Found an unexpected \\0 in the file external '%s'"), *str);
+
+ if (0 == strncmp(*str, "HEAD:", 5))
+ {
+ opt_rev->kind = svn_opt_revision_head;
+ }
+ else
+ {
+ svn_revnum_t rev;
+ const char *endptr;
+
+ SVN_ERR(svn_revnum_parse(&rev, *str, &endptr));
+ SVN_ERR_ASSERT(endptr == s);
+ opt_rev->kind = svn_opt_revision_number;
+ opt_rev->value.number = rev;
+ }
+
+ *str = s + 1;
+
+ return SVN_NO_ERROR;
+}
+
+/**
+ * Given a revision, return a string for the revision, either "HEAD"
+ * or a string representation of the revision value. All other
+ * revision kinds return an error.
+ */
+static svn_error_t *
+opt_revision_to_string(const char **str,
+ const char *path,
+ const svn_opt_revision_t *rev,
+ apr_pool_t *pool)
+{
+ switch (rev->kind)
+ {
+ case svn_opt_revision_head:
+ *str = apr_pstrmemdup(pool, "HEAD", 4);
+ break;
+ case svn_opt_revision_number:
+ *str = apr_ltoa(pool, rev->value.number);
+ break;
+ default:
+ return svn_error_createf
+ (SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Illegal file external revision kind %d for path '%s'"),
+ rev->kind, path);
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__unserialize_file_external(const char **path_result,
+ svn_opt_revision_t *peg_rev_result,
+ svn_opt_revision_t *rev_result,
+ const char *str,
+ apr_pool_t *pool)
+{
+ if (str)
+ {
+ svn_opt_revision_t peg_rev;
+ svn_opt_revision_t op_rev;
+ const char *s = str;
+
+ SVN_ERR(string_to_opt_revision(&peg_rev, &s, pool));
+ SVN_ERR(string_to_opt_revision(&op_rev, &s, pool));
+
+ *path_result = apr_pstrdup(pool, s);
+ *peg_rev_result = peg_rev;
+ *rev_result = op_rev;
+ }
+ else
+ {
+ *path_result = NULL;
+ peg_rev_result->kind = svn_opt_revision_unspecified;
+ rev_result->kind = svn_opt_revision_unspecified;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__serialize_file_external(const char **str,
+ const char *path,
+ const svn_opt_revision_t *peg_rev,
+ const svn_opt_revision_t *rev,
+ apr_pool_t *pool)
+{
+ const char *s;
+
+ if (path)
+ {
+ const char *s1;
+ const char *s2;
+
+ SVN_ERR(opt_revision_to_string(&s1, path, peg_rev, pool));
+ SVN_ERR(opt_revision_to_string(&s2, path, rev, pool));
+
+ s = apr_pstrcat(pool, s1, ":", s2, ":", path, (char *)NULL);
+ }
+ else
+ s = NULL;
+
+ *str = s;
+
+ return SVN_NO_ERROR;
+}
+
+/* Allocate an entry from POOL and read it from [*BUF, END). The
+ buffer may be modified in place while parsing. Return the new
+ entry in *NEW_ENTRY. Advance *BUF to point at the end of the entry
+ record.
+ The entries file format should be provided in ENTRIES_FORMAT. */
+static svn_error_t *
+read_entry(svn_wc_entry_t **new_entry,
+ char **buf, const char *end,
+ int entries_format,
+ apr_pool_t *pool)
+{
+ svn_wc_entry_t *entry = alloc_entry(pool);
+ const char *name;
+
+#define MAYBE_DONE if (**buf == '\f') goto done
+
+ /* Find the name and set up the entry under that name. */
+ SVN_ERR(read_path(&name, buf, end, pool));
+ entry->name = name ? name : SVN_WC_ENTRY_THIS_DIR;
+
+ /* Set up kind. */
+ {
+ const char *kindstr;
+ SVN_ERR(read_val(&kindstr, buf, end));
+ if (kindstr)
+ {
+ if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0)
+ entry->kind = svn_node_file;
+ else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0)
+ entry->kind = svn_node_dir;
+ else
+ return svn_error_createf
+ (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("Entry '%s' has invalid node kind"),
+ (name ? name : SVN_WC_ENTRY_THIS_DIR));
+ }
+ else
+ entry->kind = svn_node_none;
+ }
+ MAYBE_DONE;
+
+ /* Attempt to set revision (resolve_to_defaults may do it later, too) */
+ SVN_ERR(read_revnum(&entry->revision, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Attempt to set up url path (again, see resolve_to_defaults). */
+ SVN_ERR(read_url(&entry->url, buf, end, entries_format, pool));
+ MAYBE_DONE;
+
+ /* Set up repository root. Make sure it is a prefix of url. */
+ SVN_ERR(read_url(&entry->repos, buf, end, entries_format, pool));
+ if (entry->repos && entry->url
+ && ! svn_uri__is_ancestor(entry->repos, entry->url))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Entry for '%s' has invalid repository "
+ "root"),
+ name ? name : SVN_WC_ENTRY_THIS_DIR);
+ MAYBE_DONE;
+
+ /* Look for a schedule attribute on this entry. */
+ {
+ const char *schedulestr;
+ SVN_ERR(read_val(&schedulestr, buf, end));
+ entry->schedule = svn_wc_schedule_normal;
+ if (schedulestr)
+ {
+ if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0)
+ entry->schedule = svn_wc_schedule_add;
+ else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0)
+ entry->schedule = svn_wc_schedule_delete;
+ else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0)
+ entry->schedule = svn_wc_schedule_replace;
+ else
+ return svn_error_createf(
+ SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
+ _("Entry '%s' has invalid 'schedule' value"),
+ name ? name : SVN_WC_ENTRY_THIS_DIR);
+ }
+ }
+ MAYBE_DONE;
+
+ /* Attempt to set up text timestamp. */
+ SVN_ERR(read_time(&entry->text_time, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Checksum. */
+ SVN_ERR(read_str(&entry->checksum, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Setup last-committed values. */
+ SVN_ERR(read_time(&entry->cmt_date, buf, end, pool));
+ MAYBE_DONE;
+
+ SVN_ERR(read_revnum(&entry->cmt_rev, buf, end, pool));
+ MAYBE_DONE;
+
+ SVN_ERR(read_str(&entry->cmt_author, buf, end, pool));
+ MAYBE_DONE;
+
+ /* has-props, has-prop-mods, cachable-props, present-props are all
+ deprecated. Read any values that may be in the 'entries' file, but
+ discard them, and just put default values into the entry. */
+ {
+ const char *unused_value;
+
+ /* has-props flag. */
+ SVN_ERR(read_val(&unused_value, buf, end));
+ entry->has_props = FALSE;
+ MAYBE_DONE;
+
+ /* has-prop-mods flag. */
+ SVN_ERR(read_val(&unused_value, buf, end));
+ entry->has_prop_mods = FALSE;
+ MAYBE_DONE;
+
+ /* Use the empty string for cachable_props, indicating that we no
+ longer attempt to cache any properties. An empty string for
+ present_props means that no cachable props are present. */
+
+ /* cachable-props string. */
+ SVN_ERR(read_val(&unused_value, buf, end));
+ entry->cachable_props = "";
+ MAYBE_DONE;
+
+ /* present-props string. */
+ SVN_ERR(read_val(&unused_value, buf, end));
+ entry->present_props = "";
+ MAYBE_DONE;
+ }
+
+ /* Is this entry in a state of mental torment (conflict)? */
+ {
+ SVN_ERR(read_path(&entry->prejfile, buf, end, pool));
+ MAYBE_DONE;
+ SVN_ERR(read_path(&entry->conflict_old, buf, end, pool));
+ MAYBE_DONE;
+ SVN_ERR(read_path(&entry->conflict_new, buf, end, pool));
+ MAYBE_DONE;
+ SVN_ERR(read_path(&entry->conflict_wrk, buf, end, pool));
+ MAYBE_DONE;
+ }
+
+ /* Is this entry copied? */
+ SVN_ERR(read_bool(&entry->copied, ENTRIES_BOOL_COPIED, buf, end));
+ MAYBE_DONE;
+
+ SVN_ERR(read_url(&entry->copyfrom_url, buf, end, entries_format, pool));
+ MAYBE_DONE;
+ SVN_ERR(read_revnum(&entry->copyfrom_rev, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Is this entry deleted? */
+ SVN_ERR(read_bool(&entry->deleted, ENTRIES_BOOL_DELETED, buf, end));
+ MAYBE_DONE;
+
+ /* Is this entry absent? */
+ SVN_ERR(read_bool(&entry->absent, ENTRIES_BOOL_ABSENT, buf, end));
+ MAYBE_DONE;
+
+ /* Is this entry incomplete? */
+ SVN_ERR(read_bool(&entry->incomplete, ENTRIES_BOOL_INCOMPLETE, buf, end));
+ MAYBE_DONE;
+
+ /* UUID. */
+ SVN_ERR(read_str(&entry->uuid, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Lock token. */
+ SVN_ERR(read_str(&entry->lock_token, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Lock owner. */
+ SVN_ERR(read_str(&entry->lock_owner, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Lock comment. */
+ SVN_ERR(read_str(&entry->lock_comment, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Lock creation date. */
+ SVN_ERR(read_time(&entry->lock_creation_date, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Changelist. */
+ SVN_ERR(read_str(&entry->changelist, buf, end, pool));
+ MAYBE_DONE;
+
+ /* Keep entry in working copy after deletion? */
+ SVN_ERR(read_bool(&entry->keep_local, ENTRIES_BOOL_KEEP_LOCAL, buf, end));
+ MAYBE_DONE;
+
+ /* Translated size */
+ {
+ const char *val;
+
+ /* read_val() returns NULL on an empty (e.g. default) entry line,
+ and entry has already been initialized accordingly already */
+ SVN_ERR(read_val(&val, buf, end));
+ if (val)
+ entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
+ }
+ MAYBE_DONE;
+
+ /* Depth. */
+ {
+ const char *result;
+ SVN_ERR(read_val(&result, buf, end));
+ if (result)
+ {
+ svn_boolean_t invalid;
+ svn_boolean_t is_this_dir;
+
+ entry->depth = svn_depth_from_word(result);
+
+ /* Verify the depth value:
+ THIS_DIR should not have an excluded value and SUB_DIR should only
+ have excluded value. Remember that infinity value is not stored and
+ should not show up here. Otherwise, something bad may have
+ happened. However, infinity value itself will always be okay. */
+ is_this_dir = !name;
+ /* '!=': XOR */
+ invalid = is_this_dir != (entry->depth != svn_depth_exclude);
+ if (entry->depth != svn_depth_infinity && invalid)
+ return svn_error_createf(
+ SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
+ _("Entry '%s' has invalid 'depth' value"),
+ name ? name : SVN_WC_ENTRY_THIS_DIR);
+ }
+ else
+ entry->depth = svn_depth_infinity;
+
+ }
+ MAYBE_DONE;
+
+ /* Tree conflict data. */
+ SVN_ERR(read_str(&entry->tree_conflict_data, buf, end, pool));
+ MAYBE_DONE;
+
+ /* File external URL and revision. */
+ {
+ const char *str;
+ SVN_ERR(read_str(&str, buf, end, pool));
+ SVN_ERR(svn_wc__unserialize_file_external(&entry->file_external_path,
+ &entry->file_external_peg_rev,
+ &entry->file_external_rev,
+ str,
+ pool));
+ }
+ MAYBE_DONE;
+
+ done:
+ *new_entry = entry;
+ return SVN_NO_ERROR;
+}
+
+
+/* If attribute ATTR_NAME appears in hash ATTS, set *ENTRY_FLAG to its
+ boolean value, else set *ENTRY_FLAG false. ENTRY_NAME is the name
+ of the WC-entry. */
+static svn_error_t *
+do_bool_attr(svn_boolean_t *entry_flag,
+ apr_hash_t *atts, const char *attr_name,
+ const char *entry_name)
+{
+ const char *str = svn_hash_gets(atts, attr_name);
+
+ *entry_flag = FALSE;
+ if (str)
+ {
+ if (strcmp(str, "true") == 0)
+ *entry_flag = TRUE;
+ else if (strcmp(str, "false") == 0 || strcmp(str, "") == 0)
+ *entry_flag = FALSE;
+ else
+ return svn_error_createf
+ (SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
+ _("Entry '%s' has invalid '%s' value"),
+ (entry_name ? entry_name : SVN_WC_ENTRY_THIS_DIR), attr_name);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static const char *
+extract_string(apr_hash_t *atts,
+ const char *att_name,
+ apr_pool_t *result_pool)
+{
+ const char *value = svn_hash_gets(atts, att_name);
+
+ if (value == NULL)
+ return NULL;
+
+ return apr_pstrdup(result_pool, value);
+}
+
+
+/* Like extract_string(), but normalizes empty strings to NULL. */
+static const char *
+extract_string_normalize(apr_hash_t *atts,
+ const char *att_name,
+ apr_pool_t *result_pool)
+{
+ const char *value = svn_hash_gets(atts, att_name);
+
+ if (value == NULL)
+ return NULL;
+
+ if (*value == '\0')
+ return NULL;
+
+ return apr_pstrdup(result_pool, value);
+}
+
+
+/* NOTE: this is used for upgrading old XML-based entries file. Be wary of
+ removing items.
+
+ ### many attributes are no longer used within the old-style log files.
+ ### These attrs need to be recognized for old entries, however. For these
+ ### cases, the code will parse the attribute, but not set *MODIFY_FLAGS
+ ### for that particular field. MODIFY_FLAGS is *only* used by the
+ ### log-based entry modification system, and will go way once we
+ ### completely move away from loggy.
+
+ Set *NEW_ENTRY to a new entry, taking attributes from ATTS, whose
+ keys and values are both char *. Allocate the entry and copy
+ attributes into POOL as needed. */
+static svn_error_t *
+atts_to_entry(svn_wc_entry_t **new_entry,
+ apr_hash_t *atts,
+ apr_pool_t *pool)
+{
+ svn_wc_entry_t *entry = alloc_entry(pool);
+ const char *name;
+
+ /* Find the name and set up the entry under that name. */
+ name = svn_hash_gets(atts, ENTRIES_ATTR_NAME);
+ entry->name = name ? apr_pstrdup(pool, name) : SVN_WC_ENTRY_THIS_DIR;
+
+ /* Attempt to set revision (resolve_to_defaults may do it later, too)
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *revision_str
+ = svn_hash_gets(atts, ENTRIES_ATTR_REVISION);
+
+ if (revision_str)
+ entry->revision = SVN_STR_TO_REV(revision_str);
+ else
+ entry->revision = SVN_INVALID_REVNUM;
+ }
+
+ /* Attempt to set up url path (again, see resolve_to_defaults).
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->url = extract_string(atts, ENTRIES_ATTR_URL, pool);
+
+ /* Set up repository root. Make sure it is a prefix of url.
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->repos = extract_string(atts, ENTRIES_ATTR_REPOS, pool);
+
+ if (entry->url && entry->repos
+ && !svn_uri__is_ancestor(entry->repos, entry->url))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Entry for '%s' has invalid repository "
+ "root"),
+ name ? name : SVN_WC_ENTRY_THIS_DIR);
+
+ /* Set up kind. */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *kindstr
+ = svn_hash_gets(atts, ENTRIES_ATTR_KIND);
+
+ entry->kind = svn_node_none;
+ if (kindstr)
+ {
+ if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0)
+ entry->kind = svn_node_file;
+ else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0)
+ entry->kind = svn_node_dir;
+ else
+ return svn_error_createf
+ (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("Entry '%s' has invalid node kind"),
+ (name ? name : SVN_WC_ENTRY_THIS_DIR));
+ }
+ }
+
+ /* Look for a schedule attribute on this entry. */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *schedulestr
+ = svn_hash_gets(atts, ENTRIES_ATTR_SCHEDULE);
+
+ entry->schedule = svn_wc_schedule_normal;
+ if (schedulestr)
+ {
+ if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0)
+ entry->schedule = svn_wc_schedule_add;
+ else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0)
+ entry->schedule = svn_wc_schedule_delete;
+ else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0)
+ entry->schedule = svn_wc_schedule_replace;
+ else if (strcmp(schedulestr, "") == 0)
+ entry->schedule = svn_wc_schedule_normal;
+ else
+ return svn_error_createf(
+ SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL,
+ _("Entry '%s' has invalid 'schedule' value"),
+ (name ? name : SVN_WC_ENTRY_THIS_DIR));
+ }
+ }
+
+ /* Is this entry in a state of mental torment (conflict)? */
+ entry->prejfile = extract_string_normalize(atts,
+ ENTRIES_ATTR_PREJFILE,
+ pool);
+ entry->conflict_old = extract_string_normalize(atts,
+ ENTRIES_ATTR_CONFLICT_OLD,
+ pool);
+ entry->conflict_new = extract_string_normalize(atts,
+ ENTRIES_ATTR_CONFLICT_NEW,
+ pool);
+ entry->conflict_wrk = extract_string_normalize(atts,
+ ENTRIES_ATTR_CONFLICT_WRK,
+ pool);
+
+ /* Is this entry copied? */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ SVN_ERR(do_bool_attr(&entry->copied, atts, ENTRIES_ATTR_COPIED, name));
+
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->copyfrom_url = extract_string(atts, ENTRIES_ATTR_COPYFROM_URL, pool);
+
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *revstr;
+
+ revstr = svn_hash_gets(atts, ENTRIES_ATTR_COPYFROM_REV);
+ if (revstr)
+ entry->copyfrom_rev = SVN_STR_TO_REV(revstr);
+ }
+
+ /* Is this entry deleted?
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ SVN_ERR(do_bool_attr(&entry->deleted, atts, ENTRIES_ATTR_DELETED, name));
+
+ /* Is this entry absent?
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ SVN_ERR(do_bool_attr(&entry->absent, atts, ENTRIES_ATTR_ABSENT, name));
+
+ /* Is this entry incomplete?
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ SVN_ERR(do_bool_attr(&entry->incomplete, atts, ENTRIES_ATTR_INCOMPLETE,
+ name));
+
+ /* Attempt to set up timestamps. */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *text_timestr;
+
+ text_timestr = svn_hash_gets(atts, ENTRIES_ATTR_TEXT_TIME);
+ if (text_timestr)
+ SVN_ERR(svn_time_from_cstring(&entry->text_time, text_timestr, pool));
+
+ /* Note: we do not persist prop_time, so there is no need to attempt
+ to parse a new prop_time value from the log. Certainly, on any
+ recent working copy, there will not be a log record to alter
+ the prop_time value. */
+ }
+
+ /* Checksum. */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->checksum = extract_string(atts, ENTRIES_ATTR_CHECKSUM, pool);
+
+ /* UUID.
+
+ ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->uuid = extract_string(atts, ENTRIES_ATTR_UUID, pool);
+
+ /* Setup last-committed values. */
+ {
+ const char *cmt_datestr, *cmt_revstr;
+
+ cmt_datestr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_DATE);
+ if (cmt_datestr)
+ {
+ SVN_ERR(svn_time_from_cstring(&entry->cmt_date, cmt_datestr, pool));
+ }
+ else
+ entry->cmt_date = 0;
+
+ cmt_revstr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_REV);
+ if (cmt_revstr)
+ {
+ entry->cmt_rev = SVN_STR_TO_REV(cmt_revstr);
+ }
+ else
+ entry->cmt_rev = SVN_INVALID_REVNUM;
+
+ entry->cmt_author = extract_string(atts, ENTRIES_ATTR_CMT_AUTHOR, pool);
+ }
+
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ entry->lock_token = extract_string(atts, ENTRIES_ATTR_LOCK_TOKEN, pool);
+ entry->lock_owner = extract_string(atts, ENTRIES_ATTR_LOCK_OWNER, pool);
+ entry->lock_comment = extract_string(atts, ENTRIES_ATTR_LOCK_COMMENT, pool);
+ {
+ const char *cdate_str =
+ svn_hash_gets(atts, ENTRIES_ATTR_LOCK_CREATION_DATE);
+ if (cdate_str)
+ {
+ SVN_ERR(svn_time_from_cstring(&entry->lock_creation_date,
+ cdate_str, pool));
+ }
+ }
+ /* ----- end of lock handling. */
+
+ /* Note: if there are attributes for the (deprecated) has_props,
+ has_prop_mods, cachable_props, or present_props, then we're just
+ going to ignore them. */
+
+ /* Translated size */
+ /* ### not used by loggy; no need to set MODIFY_FLAGS */
+ {
+ const char *val = svn_hash_gets(atts, ENTRIES_ATTR_WORKING_SIZE);
+ if (val)
+ {
+ /* Cast to off_t; it's safe: we put in an off_t to start with... */
+ entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0);
+ }
+ }
+
+ *new_entry = entry;
+ return SVN_NO_ERROR;
+}
+
+/* Used when reading an entries file in XML format. */
+struct entries_accumulator
+{
+ /* Keys are entry names, vals are (struct svn_wc_entry_t *)'s. */
+ apr_hash_t *entries;
+
+ /* The parser that's parsing it, for signal_expat_bailout(). */
+ svn_xml_parser_t *parser;
+
+ /* Don't leave home without one. */
+ apr_pool_t *pool;
+
+ /* Cleared before handling each entry. */
+ apr_pool_t *scratch_pool;
+};
+
+
+
+/* Called whenever we find an <open> tag of some kind. */
+static void
+handle_start_tag(void *userData, const char *tagname, const char **atts)
+{
+ struct entries_accumulator *accum = userData;
+ apr_hash_t *attributes;
+ svn_wc_entry_t *entry;
+ svn_error_t *err;
+
+ /* We only care about the `entry' tag; all other tags, such as `xml'
+ and `wc-entries', are ignored. */
+ if (strcmp(tagname, ENTRIES_TAG_ENTRY))
+ return;
+
+ svn_pool_clear(accum->scratch_pool);
+ /* Make an entry from the attributes. */
+ attributes = svn_xml_make_att_hash(atts, accum->scratch_pool);
+ err = atts_to_entry(&entry, attributes, accum->pool);
+ if (err)
+ {
+ svn_xml_signal_bailout(err, accum->parser);
+ return;
+ }
+
+ /* Find the name and set up the entry under that name. This
+ should *NOT* be NULL, since svn_wc__atts_to_entry() should
+ have made it into SVN_WC_ENTRY_THIS_DIR. */
+ svn_hash_sets(accum->entries, entry->name, entry);
+}
+
+/* Parse BUF of size SIZE as an entries file in XML format, storing the parsed
+ entries in ENTRIES. Use SCRATCH_POOL for temporary allocations and
+ RESULT_POOL for the returned entries. */
+static svn_error_t *
+parse_entries_xml(const char *dir_abspath,
+ apr_hash_t *entries,
+ const char *buf,
+ apr_size_t size,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_xml_parser_t *svn_parser;
+ struct entries_accumulator accum;
+
+ /* Set up userData for the XML parser. */
+ accum.entries = entries;
+ accum.pool = result_pool;
+ accum.scratch_pool = svn_pool_create(scratch_pool);
+
+ /* Create the XML parser */
+ svn_parser = svn_xml_make_parser(&accum,
+ handle_start_tag,
+ NULL,
+ NULL,
+ scratch_pool);
+
+ /* Store parser in its own userdata, so callbacks can call
+ svn_xml_signal_bailout() */
+ accum.parser = svn_parser;
+
+ /* Parse. */
+ SVN_ERR_W(svn_xml_parse(svn_parser, buf, size, TRUE),
+ apr_psprintf(scratch_pool,
+ _("XML parser failed in '%s'"),
+ svn_dirent_local_style(dir_abspath, scratch_pool)));
+
+ svn_pool_destroy(accum.scratch_pool);
+
+ /* Clean up the XML parser */
+ svn_xml_free_parser(svn_parser);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Use entry SRC to fill in blank portions of entry DST. SRC itself
+ may not have any blanks, of course.
+ Typically, SRC is a parent directory's own entry, and DST is some
+ child in that directory. */
+static void
+take_from_entry(const svn_wc_entry_t *src,
+ svn_wc_entry_t *dst,
+ apr_pool_t *pool)
+{
+ /* Inherits parent's revision if doesn't have a revision of one's
+ own, unless this is a subdirectory. */
+ if ((dst->revision == SVN_INVALID_REVNUM) && (dst->kind != svn_node_dir))
+ dst->revision = src->revision;
+
+ /* Inherits parent's url if doesn't have a url of one's own. */
+ if (! dst->url)
+ dst->url = svn_path_url_add_component2(src->url, dst->name, pool);
+
+ if (! dst->repos)
+ dst->repos = src->repos;
+
+ if ((! dst->uuid)
+ && (! ((dst->schedule == svn_wc_schedule_add)
+ || (dst->schedule == svn_wc_schedule_replace))))
+ {
+ dst->uuid = src->uuid;
+ }
+}
+
+/* Resolve any missing information in ENTRIES by deducing from the
+ directory's own entry (which must already be present in ENTRIES). */
+static svn_error_t *
+resolve_to_defaults(apr_hash_t *entries,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ svn_wc_entry_t *default_entry
+ = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+
+ /* First check the dir's own entry for consistency. */
+ if (! default_entry)
+ return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND,
+ NULL,
+ _("Missing default entry"));
+
+ if (default_entry->revision == SVN_INVALID_REVNUM)
+ return svn_error_create(SVN_ERR_ENTRY_MISSING_REVISION,
+ NULL,
+ _("Default entry has no revision number"));
+
+ if (! default_entry->url)
+ return svn_error_create(SVN_ERR_ENTRY_MISSING_URL,
+ NULL,
+ _("Default entry is missing URL"));
+
+
+ /* Then use it to fill in missing information in other entries. */
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ svn_wc_entry_t *this_entry = svn__apr_hash_index_val(hi);
+
+ if (this_entry == default_entry)
+ /* THIS_DIR already has all the information it can possibly
+ have. */
+ continue;
+
+ if (this_entry->kind == svn_node_dir)
+ /* Entries that are directories have everything but their
+ name, kind, and state stored in the THIS_DIR entry of the
+ directory itself. However, we are disallowing the perusing
+ of any entries outside of the current entries file. If a
+ caller wants more info about a directory, it should look in
+ the entries file in the directory. */
+ continue;
+
+ if (this_entry->kind == svn_node_file)
+ /* For file nodes that do not explicitly have their ancestry
+ stated, this can be derived from the default entry of the
+ directory in which those files reside. */
+ take_from_entry(default_entry, this_entry, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Read and parse an old-style 'entries' file in the administrative area
+ of PATH, filling in ENTRIES with the contents. The results will be
+ allocated in RESULT_POOL, and temporary allocations will be made in
+ SCRATCH_POOL. */
+svn_error_t *
+svn_wc__read_entries_old(apr_hash_t **entries,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *curp;
+ const char *endp;
+ svn_wc_entry_t *entry;
+ svn_stream_t *stream;
+ svn_string_t *buf;
+
+ *entries = apr_hash_make(result_pool);
+
+ /* Open the entries file. */
+ SVN_ERR(svn_wc__open_adm_stream(&stream, dir_abspath, SVN_WC__ADM_ENTRIES,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_string_from_stream(&buf, stream, scratch_pool, scratch_pool));
+
+ /* We own the returned data; it is modifiable, so cast away... */
+ curp = (char *)buf->data;
+ endp = buf->data + buf->len;
+
+ /* If the first byte of the file is not a digit, then it is probably in XML
+ format. */
+ if (curp != endp && !svn_ctype_isdigit(*curp))
+ SVN_ERR(parse_entries_xml(dir_abspath, *entries, buf->data, buf->len,
+ result_pool, scratch_pool));
+ else
+ {
+ int entryno, entries_format;
+ const char *val;
+
+ /* Read the format line from the entries file. In case we're in the
+ middle of upgrading a working copy, this line will contain the
+ original format pre-upgrade. */
+ SVN_ERR(read_val(&val, &curp, endp));
+ if (val)
+ entries_format = (int)apr_strtoi64(val, NULL, 0);
+ else
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid version line in entries file "
+ "of '%s'"),
+ svn_dirent_local_style(dir_abspath,
+ scratch_pool));
+ entryno = 1;
+
+ while (curp != endp)
+ {
+ svn_error_t *err = read_entry(&entry, &curp, endp,
+ entries_format, result_pool);
+ if (! err)
+ {
+ /* We allow extra fields at the end of the line, for
+ extensibility. */
+ curp = memchr(curp, '\f', endp - curp);
+ if (! curp)
+ err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Missing entry terminator"));
+ if (! err && (curp == endp || *(++curp) != '\n'))
+ err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid entry terminator"));
+ }
+ if (err)
+ return svn_error_createf(err->apr_err, err,
+ _("Error at entry %d in entries file for "
+ "'%s':"),
+ entryno,
+ svn_dirent_local_style(dir_abspath,
+ scratch_pool));
+
+ ++curp;
+ ++entryno;
+
+ svn_hash_sets(*entries, entry->name, entry);
+ }
+ }
+
+ /* Fill in any implied fields. */
+ return svn_error_trace(resolve_to_defaults(*entries, result_pool));
+}
+
+
+/* For non-directory PATHs full entry information is obtained by reading
+ * the entries for the parent directory of PATH and then extracting PATH's
+ * entry. If PATH is a directory then only abrieviated information is
+ * available in the parent directory, more complete information is
+ * available by reading the entries for PATH itself.
+ *
+ * Note: There is one bit of information about directories that is only
+ * available in the parent directory, that is the "deleted" state. If PATH
+ * is a versioned directory then the "deleted" state information will not
+ * be returned in ENTRY. This means some bits of the code (e.g. revert)
+ * need to obtain it by directly extracting the directory entry from the
+ * parent directory's entries. I wonder if this function should handle
+ * that?
+ */
+svn_error_t *
+svn_wc_entry(const svn_wc_entry_t **entry,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool)
+{
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ const char *local_abspath;
+ svn_wc_adm_access_t *dir_access;
+ const char *entry_name;
+ apr_hash_t *entries;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+
+ /* Does the provided path refer to a directory with an associated
+ access baton? */
+ dir_access = svn_wc__adm_retrieve_internal2(db, local_abspath, pool);
+ if (dir_access == NULL)
+ {
+ /* Damn. Okay. Assume the path is to a child, and let's look for
+ a baton associated with its parent. */
+
+ const char *dir_abspath;
+
+ svn_dirent_split(&dir_abspath, &entry_name, local_abspath, pool);
+
+ dir_access = svn_wc__adm_retrieve_internal2(db, dir_abspath, pool);
+ }
+ else
+ {
+ /* Woo! Got one. Look for "this dir" in the entries hash. */
+ entry_name = "";
+ }
+
+ if (dir_access == NULL)
+ {
+ /* Early exit. */
+ *entry = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Load an entries hash, and cache it into DIR_ACCESS. Go ahead and
+ fetch all entries here (optimization) since we know how to filter
+ out a "hidden" node. */
+ SVN_ERR(svn_wc__entries_read_internal(&entries, dir_access, TRUE, pool));
+ *entry = svn_hash_gets(entries, entry_name);
+
+ if (!show_hidden && *entry != NULL)
+ {
+ svn_boolean_t hidden;
+
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden, *entry));
+ if (hidden)
+ *entry = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/props.c b/contrib/subversion/subversion/libsvn_wc/props.c
new file mode 100644
index 0000000..a7b2339
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/props.c
@@ -0,0 +1,2344 @@
+/*
+ * props.c : routines dealing with properties in the working copy
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_tables.h>
+#include <apr_file_io.h>
+#include <apr_strings.h>
+#include <apr_general.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_props.h"
+#include "svn_io.h"
+#include "svn_hash.h"
+#include "svn_mergeinfo.h"
+#include "svn_wc.h"
+#include "svn_utf.h"
+#include "svn_diff.h"
+#include "svn_sorts.h"
+
+#include "private/svn_wc_private.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_skel.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+
+#include "wc.h"
+#include "props.h"
+#include "translate.h"
+#include "workqueue.h"
+#include "conflicts.h"
+
+#include "svn_private_config.h"
+
+/* Forward declaration. */
+static svn_error_t *
+prop_conflict_from_skel(const svn_string_t **conflict_desc,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Given a *SINGLE* property conflict in PROP_SKEL, generate a description
+ for it, and write it to STREAM, along with a trailing EOL sequence.
+
+ See prop_conflict_from_skel() for details on PROP_SKEL. */
+static svn_error_t *
+append_prop_conflict(svn_stream_t *stream,
+ const svn_skel_t *prop_skel,
+ apr_pool_t *pool)
+{
+ /* TODO: someday, perhaps prefix each conflict_description with a
+ timestamp or something? */
+ const svn_string_t *conflict_desc;
+
+ SVN_ERR(prop_conflict_from_skel(&conflict_desc, prop_skel, pool, pool));
+
+ return svn_stream_puts(stream, conflict_desc->data);
+}
+
+/*---------------------------------------------------------------------*/
+
+/*** Merging propchanges into the working copy ***/
+
+
+/* Parse FROM_PROP_VAL and TO_PROP_VAL into mergeinfo hashes, and
+ calculate the deltas between them. */
+static svn_error_t *
+diff_mergeinfo_props(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
+ const svn_string_t *from_prop_val,
+ const svn_string_t *to_prop_val, apr_pool_t *pool)
+{
+ if (svn_string_compare(from_prop_val, to_prop_val))
+ {
+ /* Don't bothering parsing identical mergeinfo. */
+ *deleted = apr_hash_make(pool);
+ *added = apr_hash_make(pool);
+ }
+ else
+ {
+ svn_mergeinfo_t from, to;
+ SVN_ERR(svn_mergeinfo_parse(&from, from_prop_val->data, pool));
+ SVN_ERR(svn_mergeinfo_parse(&to, to_prop_val->data, pool));
+ SVN_ERR(svn_mergeinfo_diff2(deleted, added, from, to,
+ TRUE, pool, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Parse the mergeinfo from PROP_VAL1 and PROP_VAL2, combine it, then
+ reconstitute it into *OUTPUT. Call when the WC's mergeinfo has
+ been modified to combine it with incoming mergeinfo from the
+ repos. */
+static svn_error_t *
+combine_mergeinfo_props(const svn_string_t **output,
+ const svn_string_t *prop_val1,
+ const svn_string_t *prop_val2,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_t mergeinfo1, mergeinfo2;
+ svn_string_t *mergeinfo_string;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo1, prop_val1->data, scratch_pool));
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo2, prop_val2->data, scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(mergeinfo1, mergeinfo2, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo1, result_pool));
+ *output = mergeinfo_string;
+ return SVN_NO_ERROR;
+}
+
+/* Perform a 3-way merge operation on mergeinfo. FROM_PROP_VAL is
+ the "base" property value, WORKING_PROP_VAL is the current value,
+ and TO_PROP_VAL is the new value. */
+static svn_error_t *
+combine_forked_mergeinfo_props(const svn_string_t **output,
+ const svn_string_t *from_prop_val,
+ const svn_string_t *working_prop_val,
+ const svn_string_t *to_prop_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added;
+ svn_string_t *mergeinfo_string;
+
+ /* ### OPTIMIZE: Use from_mergeinfo when diff'ing. */
+ SVN_ERR(diff_mergeinfo_props(&l_deleted, &l_added, from_prop_val,
+ working_prop_val, scratch_pool));
+ SVN_ERR(diff_mergeinfo_props(&r_deleted, &r_added, from_prop_val,
+ to_prop_val, scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(l_deleted, r_deleted,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(l_added, r_added,
+ scratch_pool, scratch_pool));
+
+ /* Apply the combined deltas to the base. */
+ SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data,
+ scratch_pool));
+ SVN_ERR(svn_mergeinfo_merge2(from_mergeinfo, l_added,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_mergeinfo_remove2(&from_mergeinfo, l_deleted, from_mergeinfo,
+ TRUE, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, from_mergeinfo,
+ result_pool));
+ *output = mergeinfo_string;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_merge_props3(svn_wc_notify_state_t *state,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const svn_wc_conflict_version_t *left_version,
+ const svn_wc_conflict_version_t *right_version,
+ apr_hash_t *baseprops,
+ const apr_array_header_t *propchanges,
+ svn_boolean_t dry_run,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ apr_hash_t *pristine_props = NULL;
+ apr_hash_t *actual_props;
+ apr_hash_t *new_actual_props;
+ svn_boolean_t had_props, props_mod;
+ svn_boolean_t have_base;
+ svn_boolean_t conflicted;
+ svn_skel_t *work_items = NULL;
+ svn_skel_t *conflict_skel = NULL;
+ svn_wc__db_t *db = wc_ctx->db;
+
+ /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops
+ may be NULL. */
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, &conflicted, NULL,
+ &had_props, &props_mod, &have_base, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Checks whether the node exists and returns the hidden flag */
+ if (status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else if (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added
+ && status != svn_wc__db_status_incomplete)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("The node '%s' does not have properties in this state."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else if (conflicted)
+ {
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ svn_boolean_t tree_conflicted;
+
+ SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
+ &prop_conflicted,
+ &tree_conflicted,
+ db, local_abspath,
+ scratch_pool));
+
+ /* We can't install two text/prop conflicts on a single node, so
+ avoid even checking that we have to merge it */
+ if (text_conflicted || prop_conflicted || tree_conflicted)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Can't merge into conflicted node '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ /* else: Conflict was resolved by removing markers */
+ }
+
+ /* The PROPCHANGES may not have non-"normal" properties in it. If entry
+ or wc props were allowed, then the following code would install them
+ into the BASE and/or WORKING properties(!). */
+ for (i = propchanges->nelts; i--; )
+ {
+ const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
+
+ if (!svn_wc_is_normal_prop(change->name))
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("The property '%s' may not be merged "
+ "into '%s'."),
+ change->name,
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ if (had_props)
+ SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (pristine_props == NULL)
+ pristine_props = apr_hash_make(scratch_pool);
+
+ if (props_mod)
+ SVN_ERR(svn_wc__get_actual_props(&actual_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ actual_props = pristine_props;
+
+ /* Note that while this routine does the "real" work, it's only
+ prepping tempfiles and writing log commands. */
+ SVN_ERR(svn_wc__merge_props(&conflict_skel, state,
+ &new_actual_props,
+ db, local_abspath,
+ baseprops /* server_baseprops */,
+ pristine_props,
+ actual_props,
+ propchanges,
+ scratch_pool, scratch_pool));
+
+ if (dry_run)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ {
+ const char *dir_abspath;
+
+ if (kind == svn_node_dir)
+ dir_abspath = local_abspath;
+ else
+ dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ /* Verify that we're holding this directory's write lock. */
+ SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
+ }
+
+ if (conflict_skel)
+ {
+ svn_skel_t *work_item;
+ SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
+ left_version,
+ right_version,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ /* After a (not-dry-run) merge, we ALWAYS have props to save. */
+ SVN_ERR_ASSERT(new_actual_props != NULL);
+
+ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, new_actual_props,
+ svn_wc__has_magic_property(propchanges),
+ conflict_skel,
+ work_items,
+ scratch_pool));
+
+ if (work_items != NULL)
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* If there is a conflict, try to resolve it. */
+ if (conflict_skel && conflict_func)
+ {
+ svn_boolean_t prop_conflicted;
+
+ SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, conflict_skel,
+ NULL /* merge_options */,
+ conflict_func, conflict_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* Reset *STATE if all prop conflicts were resolved. */
+ SVN_ERR(svn_wc__internal_conflicted_p(
+ NULL, &prop_conflicted, NULL,
+ wc_ctx->db, local_abspath, scratch_pool));
+ if (! prop_conflicted)
+ *state = svn_wc_notify_state_merged;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Generate a message to describe the property conflict among these four
+ values.
+
+ Note that this function (currently) interprets the property values as
+ strings, but they could actually be binary values. We'll keep the
+ types as svn_string_t in case we fix this in the future. */
+static svn_stringbuf_t *
+generate_conflict_message(const char *propname,
+ const svn_string_t *original,
+ const svn_string_t *mine,
+ const svn_string_t *incoming,
+ const svn_string_t *incoming_base,
+ apr_pool_t *result_pool)
+{
+ if (incoming_base == NULL)
+ {
+ /* Attempting to add the value INCOMING. */
+ SVN_ERR_ASSERT_NO_RETURN(incoming != NULL);
+
+ if (mine)
+ {
+ /* To have a conflict, these must be different. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(mine, incoming));
+
+ /* Note that we don't care whether MINE is locally-added or
+ edited, or just something different that is a copy of the
+ pristine ORIGINAL. */
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to add new property '%s'\n"
+ "but the property already exists.\n"),
+ propname);
+ }
+
+ /* To have a conflict, we must have an ORIGINAL which has been
+ locally-deleted. */
+ SVN_ERR_ASSERT_NO_RETURN(original != NULL);
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to add new property '%s'\n"
+ "but the property has been locally "
+ "deleted.\n"),
+ propname);
+ }
+
+ if (incoming == NULL)
+ {
+ /* Attempting to delete the value INCOMING_BASE. */
+ SVN_ERR_ASSERT_NO_RETURN(incoming_base != NULL);
+
+ /* Are we trying to delete a local addition? */
+ if (original == NULL && mine != NULL)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the property has been locally "
+ "added.\n"),
+ propname);
+
+ /* A conflict can only occur if we originally had the property;
+ otherwise, we would have merged the property-delete into the
+ non-existent property. */
+ SVN_ERR_ASSERT_NO_RETURN(original != NULL);
+
+ if (svn_string_compare(original, incoming_base))
+ {
+ if (mine)
+ /* We were trying to delete the correct property, but an edit
+ caused the conflict. */
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the property has been locally "
+ "modified.\n"),
+ propname);
+ }
+ else if (mine == NULL)
+ {
+ /* We were trying to delete the property, but we have locally
+ deleted the same property, but with a different value. */
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the property has been locally "
+ "deleted and had a different "
+ "value.\n"),
+ propname);
+ }
+
+ /* We were trying to delete INCOMING_BASE but our ORIGINAL is
+ something else entirely. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
+
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to delete property '%s'\n"
+ "but the local property value is "
+ "different.\n"),
+ propname);
+ }
+
+ /* Attempting to change the property from INCOMING_BASE to INCOMING. */
+
+ /* If we have a (current) property value, then it should be different
+ from the INCOMING_BASE; otherwise, the incoming change would have
+ been applied to it. */
+ SVN_ERR_ASSERT_NO_RETURN(!mine || !svn_string_compare(mine, incoming_base));
+
+ if (original && mine && svn_string_compare(original, mine))
+ {
+ /* We have an unchanged property, so the original values must
+ have been different. */
+ SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\n"
+ "but the local property value conflicts "
+ "with the incoming change.\n"),
+ propname);
+ }
+
+ if (original && mine)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\n"
+ "but the property has already been locally "
+ "changed to a different value.\n"),
+ propname);
+
+ if (original)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\nbut "
+ "the property has been locally deleted.\n"),
+ propname);
+
+ if (mine)
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\nbut the "
+ "property has been locally added with a "
+ "different value.\n"),
+ propname);
+
+ return svn_stringbuf_createf(result_pool,
+ _("Trying to change property '%s'\nbut "
+ "the property does not exist locally.\n"),
+ propname);
+}
+
+
+/* SKEL will be one of:
+
+ ()
+ (VALUE)
+
+ Return NULL for the former (the particular property value was not
+ present), and VALUE for the second. */
+static const svn_string_t *
+maybe_prop_value(const svn_skel_t *skel,
+ apr_pool_t *result_pool)
+{
+ if (skel->children == NULL)
+ return NULL;
+
+ return svn_string_ncreate(skel->children->data,
+ skel->children->len,
+ result_pool);
+}
+
+
+/* Parse a property conflict description from the provided SKEL.
+ The result includes a descriptive message (see generate_conflict_message)
+ and maybe a diff of property values containing conflict markers.
+ The result will be allocated in RESULT_POOL.
+
+ Note: SKEL is a single property conflict of the form:
+
+ ("prop" ([ORIGINAL]) ([MINE]) ([INCOMING]) ([INCOMING_BASE]))
+
+ See notes/wc-ng/conflict-storage for more information. */
+static svn_error_t *
+prop_conflict_from_skel(const svn_string_t **conflict_desc,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_string_t *original;
+ const svn_string_t *mine;
+ const svn_string_t *incoming;
+ const svn_string_t *incoming_base;
+ const char *propname;
+ svn_diff_t *diff;
+ svn_diff_file_options_t *diff_opts;
+ svn_stringbuf_t *buf;
+ svn_boolean_t original_is_binary;
+ svn_boolean_t mine_is_binary;
+ svn_boolean_t incoming_is_binary;
+
+ /* Navigate to the property name. */
+ skel = skel->children->next;
+
+ /* We need to copy these into SCRATCH_POOL in order to nul-terminate
+ the values. */
+ propname = apr_pstrmemdup(scratch_pool, skel->data, skel->len);
+ original = maybe_prop_value(skel->next, scratch_pool);
+ mine = maybe_prop_value(skel->next->next, scratch_pool);
+ incoming = maybe_prop_value(skel->next->next->next, scratch_pool);
+ incoming_base = maybe_prop_value(skel->next->next->next->next, scratch_pool);
+
+ buf = generate_conflict_message(propname, original, mine, incoming,
+ incoming_base, scratch_pool);
+
+ if (mine == NULL)
+ mine = svn_string_create_empty(scratch_pool);
+ if (incoming == NULL)
+ incoming = svn_string_create_empty(scratch_pool);
+
+ /* Pick a suitable base for the conflict diff.
+ * The incoming value is always a change,
+ * but the local value might not have changed. */
+ if (original == NULL)
+ {
+ if (incoming_base)
+ original = incoming_base;
+ else
+ original = svn_string_create_empty(scratch_pool);
+ }
+ else if (incoming_base && svn_string_compare(original, mine))
+ original = incoming_base;
+
+ /* If any of the property values involved in the diff is binary data,
+ * do not generate a diff. */
+ original_is_binary = svn_io_is_binary_data(original->data, original->len);
+ mine_is_binary = svn_io_is_binary_data(mine->data, mine->len);
+ incoming_is_binary = svn_io_is_binary_data(incoming->data, incoming->len);
+
+ if (!(original_is_binary || mine_is_binary || incoming_is_binary))
+ {
+ diff_opts = svn_diff_file_options_create(scratch_pool);
+ diff_opts->ignore_space = svn_diff_file_ignore_space_none;
+ diff_opts->ignore_eol_style = FALSE;
+ diff_opts->show_c_function = FALSE;
+ SVN_ERR(svn_diff_mem_string_diff3(&diff, original, mine, incoming,
+ diff_opts, scratch_pool));
+ if (svn_diff_contains_conflicts(diff))
+ {
+ svn_stream_t *stream;
+ svn_diff_conflict_display_style_t style;
+ const char *mine_marker = _("<<<<<<< (local property value)");
+ const char *incoming_marker = _(">>>>>>> (incoming property value)");
+ const char *separator = "=======";
+ svn_string_t *original_ascii =
+ svn_string_create(svn_utf_cstring_from_utf8_fuzzy(original->data,
+ scratch_pool),
+ scratch_pool);
+ svn_string_t *mine_ascii =
+ svn_string_create(svn_utf_cstring_from_utf8_fuzzy(mine->data,
+ scratch_pool),
+ scratch_pool);
+ svn_string_t *incoming_ascii =
+ svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming->data,
+ scratch_pool),
+ scratch_pool);
+
+ style = svn_diff_conflict_display_modified_latest;
+ stream = svn_stream_from_stringbuf(buf, scratch_pool);
+ SVN_ERR(svn_stream_skip(stream, buf->len));
+ SVN_ERR(svn_diff_mem_string_output_merge2(stream, diff,
+ original_ascii,
+ mine_ascii,
+ incoming_ascii,
+ NULL, mine_marker,
+ incoming_marker, separator,
+ style, scratch_pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ *conflict_desc = svn_string_create_from_buf(buf, result_pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* If we could not print a conflict diff just print full values . */
+ if (mine->len > 0)
+ {
+ svn_stringbuf_appendcstr(buf, _("Local property value:\n"));
+ if (mine_is_binary)
+ svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
+ "binary data\n"));
+ else
+ svn_stringbuf_appendbytes(buf, mine->data, mine->len);
+ svn_stringbuf_appendcstr(buf, "\n");
+ }
+
+ if (incoming->len > 0)
+ {
+ svn_stringbuf_appendcstr(buf, _("Incoming property value:\n"));
+ if (incoming_is_binary)
+ svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
+ "binary data\n"));
+ else
+ svn_stringbuf_appendbytes(buf, incoming->data, incoming->len);
+ svn_stringbuf_appendcstr(buf, "\n");
+ }
+
+ *conflict_desc = svn_string_create_from_buf(buf, result_pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Create a property conflict file at PREJFILE based on the property
+ conflicts in CONFLICT_SKEL. */
+svn_error_t *
+svn_wc__create_prejfile(const char **tmp_prejfile_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *tempdir_abspath;
+ svn_stream_t *stream;
+ const char *temp_abspath;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const svn_skel_t *scan;
+
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tempdir_abspath,
+ db, local_abspath,
+ iterpool, iterpool));
+
+ SVN_ERR(svn_stream_open_unique(&stream, &temp_abspath,
+ tempdir_abspath, svn_io_file_del_none,
+ scratch_pool, iterpool));
+
+ for (scan = conflict_skel->children->next; scan != NULL; scan = scan->next)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(append_prop_conflict(stream, scan, iterpool));
+ }
+
+ SVN_ERR(svn_stream_close(stream));
+
+ svn_pool_destroy(iterpool);
+
+ *tmp_prejfile_abspath = apr_pstrdup(result_pool, temp_abspath);
+ return SVN_NO_ERROR;
+}
+
+
+/* Set the value of *STATE to NEW_VALUE if STATE is not NULL
+ * and NEW_VALUE is a higer order value than *STATE's current value
+ * using this ordering (lower order first):
+ *
+ * - unknown, unchanged, inapplicable
+ * - changed
+ * - merged
+ * - missing
+ * - obstructed
+ * - conflicted
+ *
+ */
+static void
+set_prop_merge_state(svn_wc_notify_state_t *state,
+ svn_wc_notify_state_t new_value)
+{
+ static char ordering[] =
+ { svn_wc_notify_state_unknown,
+ svn_wc_notify_state_unchanged,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_changed,
+ svn_wc_notify_state_merged,
+ svn_wc_notify_state_obstructed,
+ svn_wc_notify_state_conflicted };
+ int state_pos = 0, i;
+
+ if (! state)
+ return;
+
+ /* Find *STATE in our ordering */
+ for (i = 0; i < sizeof(ordering); i++)
+ {
+ if (*state == ordering[i])
+ {
+ state_pos = i;
+ break;
+ }
+ }
+
+ /* Find NEW_VALUE in our ordering
+ * We don't need to look further than where we found *STATE though:
+ * If we find our value, it's order is too low.
+ * If we don't find it, we'll want to set it, no matter its order.
+ */
+
+ for (i = 0; i <= state_pos; i++)
+ {
+ if (new_value == ordering[i])
+ return;
+ }
+
+ *state = new_value;
+}
+
+/* Apply the addition of a property with name PROPNAME and value NEW_VAL to
+ * the existing property with value WORKING_VAL, that originally had value
+ * PRISTINE_VAL.
+ *
+ * Sets *RESULT_VAL to the resulting value.
+ * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
+ * Sets *DID_MERGE to true if the result is caused by a merge
+ */
+static svn_error_t *
+apply_single_prop_add(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const char *propname,
+ const svn_string_t *pristine_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+
+{
+ *conflict_remains = FALSE;
+
+ if (working_val)
+ {
+ /* the property already exists in actual_props... */
+
+ if (svn_string_compare(working_val, new_val))
+ /* The value we want is already there, so it's a merge. */
+ *did_merge = TRUE;
+
+ else
+ {
+ svn_boolean_t merged_prop = FALSE;
+
+ /* The WC difference doesn't match the new value.
+ We only merge mergeinfo; other props conflict */
+ if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
+ {
+ const svn_string_t *merged_val;
+ svn_error_t *err = combine_mergeinfo_props(&merged_val,
+ working_val,
+ new_val,
+ result_pool,
+ scratch_pool);
+
+ /* Issue #3896 'mergeinfo syntax errors should be treated
+ gracefully': If bogus mergeinfo is present we can't
+ merge intelligently, so raise a conflict instead. */
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ merged_prop = TRUE;
+ *result_val = merged_val;
+ *did_merge = TRUE;
+ }
+ }
+
+ if (!merged_prop)
+ *conflict_remains = TRUE;
+ }
+ }
+ else if (pristine_val)
+ *conflict_remains = TRUE;
+ else /* property doesn't yet exist in actual_props... */
+ /* so just set it */
+ *result_val = new_val;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Apply the deletion of a property to the existing
+ * property with value WORKING_VAL, that originally had value PRISTINE_VAL.
+ *
+ * Sets *RESULT_VAL to the resulting value.
+ * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
+ * Sets *DID_MERGE to true if the result is caused by a merge
+ */
+static svn_error_t *
+apply_single_prop_delete(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const svn_string_t *base_val,
+ const svn_string_t *old_val,
+ const svn_string_t *working_val)
+{
+ *conflict_remains = FALSE;
+
+ if (! base_val)
+ {
+ if (working_val
+ && !svn_string_compare(working_val, old_val))
+ {
+ /* We are trying to delete a locally-added prop. */
+ *conflict_remains = TRUE;
+ }
+ else
+ {
+ *result_val = NULL;
+ if (old_val)
+ /* This is a merge, merging a delete into non-existent
+ property or a local addition of same prop value. */
+ *did_merge = TRUE;
+ }
+ }
+
+ else if (svn_string_compare(base_val, old_val))
+ {
+ if (working_val)
+ {
+ if (svn_string_compare(working_val, old_val))
+ /* they have the same values, so it's an update */
+ *result_val = NULL;
+ else
+ *conflict_remains = TRUE;
+ }
+ else
+ /* The property is locally deleted from the same value, so it's
+ a merge */
+ *did_merge = TRUE;
+ }
+
+ else
+ *conflict_remains = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Merge a change to the mergeinfo property. Similar to
+ apply_single_prop_change(), except that the property name is always
+ SVN_PROP_MERGEINFO. */
+/* ### This function is extracted straight from the previous all-in-one
+ version of apply_single_prop_change() by removing the code paths that
+ were not followed for this property, but with no attempt to rationalize
+ the remainder. */
+static svn_error_t *
+apply_single_mergeinfo_prop_change(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const svn_string_t *base_val,
+ const svn_string_t *old_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if ((working_val && ! base_val)
+ || (! working_val && base_val)
+ || (working_val && base_val
+ && !svn_string_compare(working_val, base_val)))
+ {
+ /* Locally changed property */
+ if (working_val)
+ {
+ if (svn_string_compare(working_val, new_val))
+ /* The new value equals the changed value: a no-op merge */
+ *did_merge = TRUE;
+ else
+ {
+ /* We have base, WC, and new values. Discover
+ deltas between base <-> WC, and base <->
+ incoming. Combine those deltas, and apply
+ them to base to get the new value. */
+ SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
+ working_val,
+ new_val,
+ result_pool,
+ scratch_pool));
+ *result_val = new_val;
+ *did_merge = TRUE;
+ }
+ }
+ else
+ {
+ /* There is a base_val but no working_val */
+ *conflict_remains = TRUE;
+ }
+ }
+
+ else if (! working_val) /* means !working_val && !base_val due
+ to conditions above: no prop at all */
+ {
+ /* Discover any mergeinfo additions in the
+ incoming value relative to the base, and
+ "combine" those with the empty WC value. */
+ svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo;
+ svn_string_t *mergeinfo_string;
+
+ SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo,
+ &added_mergeinfo,
+ old_val, new_val, scratch_pool));
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string,
+ added_mergeinfo, result_pool));
+ *result_val = mergeinfo_string;
+ }
+
+ else /* means working && base && svn_string_compare(working, base) */
+ {
+ if (svn_string_compare(old_val, base_val))
+ *result_val = new_val;
+ else
+ {
+ /* We have base, WC, and new values. Discover
+ deltas between base <-> WC, and base <->
+ incoming. Combine those deltas, and apply
+ them to base to get the new value. */
+ SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
+ working_val,
+ new_val, result_pool,
+ scratch_pool));
+ *result_val = new_val;
+ *did_merge = TRUE;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Merge a change to a property, using the rule that if the working value
+ is equal to the new value then there is nothing we need to do. Else, if
+ the working value is the same as the old value then apply the change as a
+ simple update (replacement), otherwise invoke maybe_generate_propconflict().
+ The definition of the arguments and behaviour is the same as
+ apply_single_prop_change(). */
+static svn_error_t *
+apply_single_generic_prop_change(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const svn_string_t *old_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val)
+{
+ SVN_ERR_ASSERT(old_val != NULL);
+
+ /* If working_val is the same as new_val already then there is
+ * nothing to do */
+ if (working_val && new_val
+ && svn_string_compare(working_val, new_val))
+ {
+ /* All values identical is a trivial, non-notifiable merge */
+ if (! old_val || ! svn_string_compare(old_val, new_val))
+ *did_merge = TRUE;
+ }
+ /* If working_val is the same as old_val... */
+ else if (working_val && old_val
+ && svn_string_compare(working_val, old_val))
+ {
+ /* A trivial update: change it to new_val. */
+ *result_val = new_val;
+ }
+ else
+ {
+ /* Merge the change. */
+ *conflict_remains = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Change the property with name PROPNAME, setting *RESULT_VAL,
+ * *CONFLICT_REMAINS and *DID_MERGE according to the merge outcome.
+ *
+ * BASE_VAL contains the working copy base property value. (May be null.)
+ *
+ * OLD_VAL contains the value of the property the server
+ * thinks it's overwriting. (Not null.)
+ *
+ * NEW_VAL contains the value to be set. (Not null.)
+ *
+ * WORKING_VAL contains the working copy actual value. (May be null.)
+ */
+static svn_error_t *
+apply_single_prop_change(const svn_string_t **result_val,
+ svn_boolean_t *conflict_remains,
+ svn_boolean_t *did_merge,
+ const char *propname,
+ const svn_string_t *base_val,
+ const svn_string_t *old_val,
+ const svn_string_t *new_val,
+ const svn_string_t *working_val,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t merged_prop = FALSE;
+
+ *conflict_remains = FALSE;
+
+ /* Note: The purpose is to apply the change (old_val -> new_val) onto
+ (working_val). There is no need for base_val to be involved in the
+ process except as a bit of context to help the user understand and
+ resolve any conflict. */
+
+ /* Decide how to merge, based on whether we know anything special about
+ the property. */
+ if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
+ {
+ /* We know how to merge any mergeinfo property change...
+
+ ...But Issue #3896 'mergeinfo syntax errors should be treated
+ gracefully' might thwart us. If bogus mergeinfo is present we
+ can't merge intelligently, so let the standard method deal with
+ it instead. */
+ svn_error_t *err = apply_single_mergeinfo_prop_change(result_val,
+ conflict_remains,
+ did_merge,
+ base_val,
+ old_val,
+ new_val,
+ working_val,
+ result_pool,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ merged_prop = TRUE;
+ }
+ }
+
+ if (!merged_prop)
+ {
+ /* The standard method: perform a simple update automatically, but
+ pass any other kind of merge to maybe_generate_propconflict(). */
+
+ SVN_ERR(apply_single_generic_prop_change(result_val, conflict_remains,
+ did_merge,
+ old_val, new_val, working_val));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__merge_props(svn_skel_t **conflict_skel,
+ svn_wc_notify_state_t *state,
+ apr_hash_t **new_actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_hash_t *server_baseprops,
+ apr_hash_t *pristine_props,
+ apr_hash_t *actual_props,
+ const apr_array_header_t *propchanges,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ int i;
+ apr_hash_t *conflict_props = NULL;
+ apr_hash_t *their_props;
+
+ SVN_ERR_ASSERT(pristine_props != NULL);
+ SVN_ERR_ASSERT(actual_props != NULL);
+
+ *new_actual_props = apr_hash_copy(result_pool, actual_props);
+
+ if (!server_baseprops)
+ server_baseprops = pristine_props;
+
+ their_props = apr_hash_copy(scratch_pool, server_baseprops);
+
+ if (state)
+ {
+ /* Start out assuming no changes or conflicts. Don't bother to
+ examine propchanges->nelts yet; even if we knew there were
+ propchanges, we wouldn't yet know if they are "normal" props,
+ as opposed wc or entry props. */
+ *state = svn_wc_notify_state_unchanged;
+ }
+
+ /* Looping over the array of incoming propchanges we want to apply: */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < propchanges->nelts; i++)
+ {
+ const svn_prop_t *incoming_change
+ = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
+ const char *propname = incoming_change->name;
+ const svn_string_t *base_val /* Pristine in WC */
+ = svn_hash_gets(pristine_props, propname);
+ const svn_string_t *from_val /* Merge left */
+ = svn_hash_gets(server_baseprops, propname);
+ const svn_string_t *to_val /* Merge right */
+ = incoming_change->value;
+ const svn_string_t *working_val /* Mine */
+ = svn_hash_gets(actual_props, propname);
+ const svn_string_t *result_val;
+ svn_boolean_t conflict_remains;
+ svn_boolean_t did_merge = FALSE;
+
+ svn_pool_clear(iterpool);
+
+ to_val = to_val ? svn_string_dup(to_val, result_pool) : NULL;
+
+ svn_hash_sets(their_props, propname, to_val);
+
+
+ /* We already know that state is at least `changed', so mark
+ that, but remember that we may later upgrade to `merged' or
+ even `conflicted'. */
+ set_prop_merge_state(state, svn_wc_notify_state_changed);
+
+ result_val = working_val;
+
+ if (! from_val) /* adding a new property */
+ SVN_ERR(apply_single_prop_add(&result_val, &conflict_remains,
+ &did_merge, propname,
+ base_val, to_val, working_val,
+ result_pool, iterpool));
+
+ else if (! to_val) /* delete an existing property */
+ SVN_ERR(apply_single_prop_delete(&result_val, &conflict_remains,
+ &did_merge,
+ base_val, from_val, working_val));
+
+ else /* changing an existing property */
+ SVN_ERR(apply_single_prop_change(&result_val, &conflict_remains,
+ &did_merge, propname,
+ base_val, from_val, to_val, working_val,
+ result_pool, iterpool));
+
+ if (result_val != working_val)
+ svn_hash_sets(*new_actual_props, propname, result_val);
+ if (did_merge)
+ set_prop_merge_state(state, svn_wc_notify_state_merged);
+
+ /* merging logic complete, now we need to possibly log conflict
+ data to tmpfiles. */
+
+ if (conflict_remains)
+ {
+ set_prop_merge_state(state, svn_wc_notify_state_conflicted);
+
+ if (!conflict_props)
+ conflict_props = apr_hash_make(scratch_pool);
+
+ svn_hash_sets(conflict_props, propname, "");
+ }
+
+ } /* foreach propchange ... */
+ svn_pool_destroy(iterpool);
+
+ /* Finished applying all incoming propchanges to our hashes! */
+
+ if (conflict_props != NULL)
+ {
+ /* Ok, we got some conflict. Lets store all the property knowledge we
+ have for resolving later */
+
+ if (!*conflict_skel)
+ *conflict_skel = svn_wc__conflict_skel_create(result_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(*conflict_skel,
+ db, local_abspath,
+ NULL /* reject_path */,
+ actual_props,
+ server_baseprops,
+ their_props,
+ conflict_props,
+ result_pool,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set a single 'wcprop' NAME to VALUE for versioned object LOCAL_ABSPATH.
+ If VALUE is null, remove property NAME. */
+static svn_error_t *
+wcprop_set(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *prophash;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Note: this is not well-transacted. But... meh. This is merely a cache,
+ and if two processes are trying to modify this one entry at the same
+ time, then fine: we can let one be a winner, and one a loser. Of course,
+ if there are *other* state changes afoot, then the lack of a txn could
+ be a real issue, but we cannot solve that here. */
+
+ SVN_ERR(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (prophash == NULL)
+ prophash = apr_hash_make(scratch_pool);
+
+ svn_hash_sets(prophash, name, value);
+ return svn_error_trace(svn_wc__db_base_set_dav_cache(db, local_abspath,
+ prophash,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__get_actual_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* ### perform some state checking. for example, locally-deleted nodes
+ ### should not have any ACTUAL props. */
+
+ return svn_error_trace(svn_wc__db_read_props(props, db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc_prop_list2(apr_hash_t **props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__get_actual_props(props,
+ wc_ctx->db,
+ local_abspath,
+ result_pool,
+ scratch_pool));
+}
+
+struct propname_filter_baton_t {
+ svn_wc__proplist_receiver_t receiver_func;
+ void *receiver_baton;
+ const char *propname;
+};
+
+static svn_error_t *
+propname_filter_receiver(void *baton,
+ const char *local_abspath,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ struct propname_filter_baton_t *pfb = baton;
+ const svn_string_t *propval = svn_hash_gets(props, pfb->propname);
+
+ if (propval)
+ {
+ props = apr_hash_make(scratch_pool);
+ svn_hash_sets(props, pfb->propname, propval);
+
+ SVN_ERR(pfb->receiver_func(pfb->receiver_baton, local_abspath, props,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ svn_depth_t depth,
+ svn_boolean_t pristine,
+ const apr_array_header_t *changelists,
+ svn_wc__proplist_receiver_t receiver_func,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__proplist_receiver_t receiver = receiver_func;
+ void *baton = receiver_baton;
+ struct propname_filter_baton_t pfb;
+
+ pfb.receiver_func = receiver_func;
+ pfb.receiver_baton = receiver_baton;
+ pfb.propname = propname;
+
+ SVN_ERR_ASSERT(receiver_func);
+
+ if (propname)
+ {
+ baton = &pfb;
+ receiver = propname_filter_receiver;
+ }
+
+ switch (depth)
+ {
+ case svn_depth_empty:
+ {
+ apr_hash_t *props;
+ apr_hash_t *changelist_hash = NULL;
+
+ if (changelists && changelists->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
+ changelists, scratch_pool));
+
+ if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
+ changelist_hash, scratch_pool))
+ break;
+
+ if (pristine)
+ SVN_ERR(svn_wc__db_read_pristine_props(&props, wc_ctx->db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (props && apr_hash_count(props) > 0)
+ SVN_ERR(receiver(baton, local_abspath, props, scratch_pool));
+ }
+ break;
+ case svn_depth_files:
+ case svn_depth_immediates:
+ case svn_depth_infinity:
+ {
+ SVN_ERR(svn_wc__db_read_props_streamily(wc_ctx->db, local_abspath,
+ depth, pristine,
+ changelists, receiver, baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__prop_retrieve_recursive(apr_hash_t **values,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_prop_retrieve_recursive(values,
+ wc_ctx->db,
+ local_abspath,
+ propname,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc_get_pristine_props(apr_hash_t **props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Certain node stats do not have properties defined on them. Check the
+ state, and return NULL for these situations. */
+
+ err = svn_wc__db_read_pristine_props(props, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ /* Documented behavior is to set *PROPS to NULL */
+ *props = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_prop_get2(const svn_string_t **value,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (kind == svn_prop_entry_kind)
+ {
+ /* we don't do entry properties here */
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is an entry property"), name);
+ }
+
+ err = svn_wc__internal_propget(value, wc_ctx->db, local_abspath, name,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ /* Documented behavior is to set *VALUE to NULL */
+ *value = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_propget(const svn_string_t **value,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *prophash = NULL;
+ enum svn_prop_kind kind = svn_property_kind2(name);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(kind != svn_prop_entry_kind);
+
+ if (kind == svn_prop_wc_kind)
+ {
+ SVN_ERR_W(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
+ result_pool, scratch_pool),
+ _("Failed to load properties"));
+ }
+ else
+ {
+ /* regular prop */
+ SVN_ERR_W(svn_wc__get_actual_props(&prophash, db, local_abspath,
+ result_pool, scratch_pool),
+ _("Failed to load properties"));
+ }
+
+ if (prophash)
+ *value = svn_hash_gets(prophash, name);
+ else
+ *value = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The special Subversion properties are not valid for all node kinds.
+ Return an error if NAME is an invalid Subversion property for PATH which
+ is of kind NODE_KIND. NAME must be in the "svn:" name space.
+
+ Note that we only disallow the property if we're sure it's one that
+ already has a meaning for a different node kind. We don't disallow
+ setting an *unknown* svn: prop here, at this level; a higher level
+ should disallow that if desired.
+ */
+static svn_error_t *
+validate_prop_against_node_kind(const char *name,
+ const char *path,
+ svn_node_kind_t node_kind,
+ apr_pool_t *pool)
+{
+ const char *path_display
+ = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
+
+ switch (node_kind)
+ {
+ case svn_node_dir:
+ if (! svn_prop_is_known_svn_dir_prop(name)
+ && svn_prop_is_known_svn_file_prop(name))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Cannot set '%s' on a directory ('%s')"),
+ name, path_display);
+ break;
+ case svn_node_file:
+ if (! svn_prop_is_known_svn_file_prop(name)
+ && svn_prop_is_known_svn_dir_prop(name))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Cannot set '%s' on a file ('%s')"),
+ name,
+ path_display);
+ break;
+ default:
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' is not a file or directory"),
+ path_display);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct getter_baton {
+ const svn_string_t *mime_type;
+ const char *local_abspath;
+};
+
+
+/* Provide the MIME_TYPE and/or push the content to STREAM for the file
+ * referenced by (getter_baton *) BATON.
+ *
+ * Implements svn_wc_canonicalize_svn_prop_get_file_t. */
+static svn_error_t *
+get_file_for_validation(const svn_string_t **mime_type,
+ svn_stream_t *stream,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct getter_baton *gb = baton;
+
+ if (mime_type)
+ *mime_type = gb->mime_type;
+
+ if (stream)
+ {
+ svn_stream_t *read_stream;
+
+ /* Copy the text of GB->LOCAL_ABSPATH into STREAM. */
+ SVN_ERR(svn_stream_open_readonly(&read_stream, gb->local_abspath,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(read_stream, svn_stream_disown(stream, pool),
+ NULL, NULL, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Validate that a file has a 'non-binary' MIME type and contains
+ * self-consistent line endings. If not, then return an error.
+ *
+ * Call GETTER (which must not be NULL) with GETTER_BATON to get the
+ * file's MIME type and/or content. If the MIME type is non-null and
+ * is categorized as 'binary' then return an error and do not request
+ * the file content.
+ *
+ * Use PATH (a local path or a URL) only for error messages.
+ */
+static svn_error_t *
+validate_eol_prop_against_file(const char *path,
+ svn_wc_canonicalize_svn_prop_get_file_t getter,
+ void *getter_baton,
+ apr_pool_t *pool)
+{
+ svn_stream_t *translating_stream;
+ svn_error_t *err;
+ const svn_string_t *mime_type;
+ const char *path_display
+ = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
+
+ /* First just ask the "getter" for the MIME type. */
+ SVN_ERR(getter(&mime_type, NULL, getter_baton, pool));
+
+ /* See if this file has been determined to be binary. */
+ if (mime_type && svn_mime_type_is_binary(mime_type->data))
+ return svn_error_createf
+ (SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Can't set '%s': "
+ "file '%s' has binary mime type property"),
+ SVN_PROP_EOL_STYLE, path_display);
+
+ /* Now ask the getter for the contents of the file; this will do a
+ newline translation. All we really care about here is whether or
+ not the function fails on inconsistent line endings. The
+ function is "translating" to an empty stream. This is
+ sneeeeeeeeeeeaky. */
+ translating_stream = svn_subst_stream_translated(svn_stream_empty(pool),
+ "", FALSE, NULL, FALSE,
+ pool);
+
+ err = getter(NULL, translating_stream, getter_baton, pool);
+
+ err = svn_error_compose_create(err, svn_stream_close(translating_stream));
+
+ if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err,
+ _("File '%s' has inconsistent newlines"),
+ path_display);
+
+ return svn_error_trace(err);
+}
+
+static svn_error_t *
+do_propset(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *name,
+ const svn_string_t *value,
+ svn_boolean_t skip_checks,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *prophash;
+ svn_wc_notify_action_t notify_action;
+ svn_skel_t *work_item = NULL;
+ svn_boolean_t clear_recorded_info = FALSE;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR_W(svn_wc__db_read_props(&prophash, db, local_abspath,
+ scratch_pool, scratch_pool),
+ _("Failed to load current properties"));
+
+ /* Setting an inappropriate property is not allowed (unless
+ overridden by 'skip_checks', in some circumstances). Deleting an
+ inappropriate property is allowed, however, since older clients
+ allowed (and other clients possibly still allow) setting it in
+ the first place. */
+ if (value && svn_prop_is_svn_prop(name))
+ {
+ const svn_string_t *new_value;
+ struct getter_baton gb;
+
+ gb.mime_type = svn_hash_gets(prophash, SVN_PROP_MIME_TYPE);
+ gb.local_abspath = local_abspath;
+
+ SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value,
+ local_abspath, kind,
+ skip_checks,
+ get_file_for_validation, &gb,
+ scratch_pool));
+ value = new_value;
+ }
+
+ if (kind == svn_node_file
+ && (strcmp(name, SVN_PROP_EXECUTABLE) == 0
+ || strcmp(name, SVN_PROP_NEEDS_LOCK) == 0))
+ {
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ /* If we're changing this file's list of expanded keywords, then
+ * we'll need to invalidate its text timestamp, since keyword
+ * expansion affects the comparison of working file to text base.
+ *
+ * Here we retrieve the old list of expanded keywords; after the
+ * property is set, we'll grab the new list and see if it differs
+ * from the old one.
+ */
+ if (kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0)
+ {
+ svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_KEYWORDS);
+ apr_hash_t *old_keywords, *new_keywords;
+
+ if (old_value)
+ SVN_ERR(svn_wc__expand_keywords(&old_keywords,
+ db, local_abspath, NULL,
+ old_value->data, TRUE,
+ scratch_pool, scratch_pool));
+ else
+ old_keywords = apr_hash_make(scratch_pool);
+
+ if (value)
+ SVN_ERR(svn_wc__expand_keywords(&new_keywords,
+ db, local_abspath, NULL,
+ value->data, TRUE,
+ scratch_pool, scratch_pool));
+ else
+ new_keywords = apr_hash_make(scratch_pool);
+
+ if (svn_subst_keywords_differ2(old_keywords, new_keywords, FALSE,
+ scratch_pool))
+ {
+ /* If the keywords have changed, then the translation of the file
+ may be different. We should invalidate the RECORDED_SIZE
+ and RECORDED_TIME on this node.
+
+ Note that we don't immediately re-translate the file. But a
+ "has it changed?" check in the future will do a translation
+ from the pristine, and it will want to compare the (new)
+ resulting RECORDED_SIZE against the working copy file.
+
+ Also, when this file is (de)translated with the new keywords,
+ then it could be different, relative to the pristine. We want
+ to ensure the RECORDED_TIME is different, to indicate that
+ a full detranslate/compare is performed. */
+ clear_recorded_info = TRUE;
+ }
+ }
+ else if (kind == svn_node_file && strcmp(name, SVN_PROP_EOL_STYLE) == 0)
+ {
+ svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_EOL_STYLE);
+
+ if (((value == NULL) != (old_value == NULL))
+ || (value && ! svn_string_compare(value, old_value)))
+ {
+ clear_recorded_info = TRUE;
+ }
+ }
+
+ /* Find out what type of property change we are doing: add, modify, or
+ delete. */
+ if (svn_hash_gets(prophash, name) == NULL)
+ {
+ if (value == NULL)
+ /* Deleting a non-existent property. */
+ notify_action = svn_wc_notify_property_deleted_nonexistent;
+ else
+ /* Adding a property. */
+ notify_action = svn_wc_notify_property_added;
+ }
+ else
+ {
+ if (value == NULL)
+ /* Deleting the property. */
+ notify_action = svn_wc_notify_property_deleted;
+ else
+ /* Modifying property. */
+ notify_action = svn_wc_notify_property_modified;
+ }
+
+ /* Now we have all the properties in our hash. Simply merge the new
+ property into it. */
+ svn_hash_sets(prophash, name, value);
+
+ /* Drop it right into the db.. */
+ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, prophash,
+ clear_recorded_info, NULL, work_item,
+ scratch_pool));
+
+ /* Run our workqueue item for sync'ing flags with props. */
+ if (work_item)
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool));
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
+ notify_action,
+ scratch_pool);
+ notify->prop_name = name;
+ notify->kind = kind;
+
+ (*notify_func)(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* A baton for propset_walk_cb. */
+struct propset_walk_baton
+{
+ const char *propname; /* The name of the property to set. */
+ const svn_string_t *propval; /* The value to set. */
+ svn_wc__db_t *db; /* Database for the tree being walked. */
+ svn_boolean_t force; /* True iff force was passed. */
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+};
+
+/* An node-walk callback for svn_wc_prop_set4().
+ *
+ * For LOCAL_ABSPATH, set the property named wb->PROPNAME to the value
+ * wb->PROPVAL, where "wb" is the WALK_BATON of type "struct
+ * propset_walk_baton *".
+ */
+static svn_error_t *
+propset_walk_cb(const char *local_abspath,
+ svn_node_kind_t kind,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct propset_walk_baton *wb = walk_baton;
+ svn_error_t *err;
+
+ err = do_propset(wb->db, local_abspath, kind, wb->propname, wb->propval,
+ wb->force, wb->notify_func, wb->notify_baton, scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_ILLEGAL_TARGET
+ || err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_wc_prop_set4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *name,
+ const svn_string_t *value,
+ svn_depth_t depth,
+ svn_boolean_t skip_checks,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ enum svn_prop_kind prop_kind = svn_property_kind2(name);
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_wc__db_t *db = wc_ctx->db;
+
+ /* we don't do entry properties here */
+ if (prop_kind == svn_prop_entry_kind)
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is an entry property"), name);
+
+ /* Check to see if we're setting the dav cache. */
+ if (prop_kind == svn_prop_wc_kind)
+ {
+ SVN_ERR_ASSERT(depth == svn_depth_empty);
+ return svn_error_trace(wcprop_set(wc_ctx->db, local_abspath,
+ name, value, scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added
+ && status != svn_wc__db_status_incomplete)
+ {
+ return svn_error_createf(SVN_ERR_WC_INVALID_SCHEDULE, NULL,
+ _("Can't set properties on '%s':"
+ " invalid status for updating properties."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ /* We have to do this little DIR_ABSPATH dance for backwards compat.
+ But from 1.7 onwards, all locks are of infinite depth, and from 1.6
+ backward we never call this API with depth > empty, so we only need
+ to do the write check once per call, here (and not for every node in
+ the node walker).
+
+ ### Note that we could check for a write lock on local_abspath first
+ ### if we would want to. And then justy check for kind if that fails.
+ ### ... but we need kind for the "svn:" property checks anyway */
+ {
+ const char *dir_abspath;
+
+ if (kind == svn_node_dir)
+ dir_abspath = local_abspath;
+ else
+ dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ /* Verify that we're holding this directory's write lock. */
+ SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
+ }
+
+ if (depth == svn_depth_empty || kind != svn_node_dir)
+ {
+ apr_hash_t *changelist_hash = NULL;
+
+ if (changelist_filter && changelist_filter->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
+ scratch_pool));
+
+ if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
+ changelist_hash, scratch_pool))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(do_propset(wc_ctx->db, local_abspath,
+ kind == svn_node_dir
+ ? svn_node_dir
+ : svn_node_file,
+ name, value, skip_checks,
+ notify_func, notify_baton, scratch_pool));
+
+ }
+ else
+ {
+ struct propset_walk_baton wb;
+
+ wb.propname = name;
+ wb.propval = value;
+ wb.db = wc_ctx->db;
+ wb.force = skip_checks;
+ wb.notify_func = notify_func;
+ wb.notify_baton = notify_baton;
+
+ SVN_ERR(svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
+ FALSE, changelist_filter,
+ propset_walk_cb, &wb,
+ depth,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Check that NAME names a regular prop. Return an error if it names an
+ * entry prop or a WC prop. */
+static svn_error_t *
+ensure_prop_is_regular_kind(const char *name)
+{
+ enum svn_prop_kind prop_kind = svn_property_kind2(name);
+
+ /* we don't do entry properties here */
+ if (prop_kind == svn_prop_entry_kind)
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is an entry property"), name);
+
+ /* Check to see if we're setting the dav cache. */
+ if (prop_kind == svn_prop_wc_kind)
+ return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
+ _("Property '%s' is a WC property, not "
+ "a regular property"), name);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__canonicalize_props(apr_hash_t **prepared_props,
+ const char *local_abspath,
+ svn_node_kind_t node_kind,
+ const apr_hash_t *props,
+ svn_boolean_t skip_some_checks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_string_t *mime_type;
+ struct getter_baton gb;
+ apr_hash_index_t *hi;
+
+ /* While we allocate new parts of *PREPARED_PROPS in RESULT_POOL, we
+ don't promise to deep-copy the unchanged keys and values. */
+ *prepared_props = apr_hash_make(result_pool);
+
+ /* Before we can canonicalize svn:eol-style we need to know svn:mime-type,
+ * so process that first. */
+ mime_type = svn_hash_gets((apr_hash_t *)props, SVN_PROP_MIME_TYPE);
+ if (mime_type)
+ {
+ SVN_ERR(svn_wc_canonicalize_svn_prop(
+ &mime_type, SVN_PROP_MIME_TYPE, mime_type,
+ local_abspath, node_kind, skip_some_checks,
+ NULL, NULL, scratch_pool));
+ svn_hash_sets(*prepared_props, SVN_PROP_MIME_TYPE, mime_type);
+ }
+
+ /* Set up the context for canonicalizing the other properties. */
+ gb.mime_type = mime_type;
+ gb.local_abspath = local_abspath;
+
+ /* Check and canonicalize the other properties. */
+ for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)props); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_string_t *value = svn__apr_hash_index_val(hi);
+
+ if (strcmp(name, SVN_PROP_MIME_TYPE) == 0)
+ continue;
+
+ SVN_ERR(ensure_prop_is_regular_kind(name));
+ SVN_ERR(svn_wc_canonicalize_svn_prop(
+ &value, name, value,
+ local_abspath, node_kind, skip_some_checks,
+ get_file_for_validation, &gb, scratch_pool));
+ svn_hash_sets(*prepared_props, name, value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p,
+ const char *propname,
+ const svn_string_t *propval,
+ const char *path,
+ svn_node_kind_t kind,
+ svn_boolean_t skip_some_checks,
+ svn_wc_canonicalize_svn_prop_get_file_t getter,
+ void *getter_baton,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *new_value = NULL;
+
+ /* Keep this static, it may get stored (for read-only purposes) in a
+ hash that outlives this function. */
+ static const svn_string_t boolean_value =
+ {
+ SVN_PROP_BOOLEAN_TRUE,
+ sizeof(SVN_PROP_BOOLEAN_TRUE) - 1
+ };
+
+ SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool));
+
+ /* This code may place the new prop val in either NEW_VALUE or PROPVAL. */
+ if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0))
+ {
+ svn_subst_eol_style_t eol_style;
+ const char *ignored_eol;
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_strip_whitespace(new_value);
+ svn_subst_eol_style_from_value(&eol_style, &ignored_eol, new_value->data);
+ if (eol_style == svn_subst_eol_style_unknown)
+ return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
+ _("Unrecognized line ending style '%s' for '%s'"),
+ new_value->data,
+ svn_dirent_local_style(path, pool));
+ SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton,
+ pool));
+ }
+ else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0))
+ {
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_strip_whitespace(new_value);
+ SVN_ERR(svn_mime_type_validate(new_value->data, pool));
+ }
+ else if (strcmp(propname, SVN_PROP_IGNORE) == 0
+ || strcmp(propname, SVN_PROP_EXTERNALS) == 0
+ || strcmp(propname, SVN_PROP_INHERITABLE_IGNORES) == 0
+ || strcmp(propname, SVN_PROP_INHERITABLE_AUTO_PROPS) == 0)
+ {
+ /* Make sure that the last line ends in a newline */
+ if (propval->len == 0
+ || propval->data[propval->len - 1] != '\n')
+ {
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_appendbyte(new_value, '\n');
+ }
+
+ /* Make sure this is a valid externals property. Do not
+ allow 'skip_some_checks' to override, as there is no circumstance in
+ which this is proper (because there is no circumstance in
+ which Subversion can handle it). */
+ if (strcmp(propname, SVN_PROP_EXTERNALS) == 0)
+ {
+ /* We don't allow "." nor ".." as target directories in
+ an svn:externals line. As it happens, our parse code
+ checks for this, so all we have to is invoke it --
+ we're not interested in the parsed result, only in
+ whether or not the parsing errored. */
+ apr_array_header_t *externals = NULL;
+ apr_array_header_t *duplicate_targets = NULL;
+ SVN_ERR(svn_wc_parse_externals_description3(&externals, path,
+ propval->data, FALSE,
+ /*scratch_*/pool));
+ SVN_ERR(svn_wc__externals_find_target_dups(&duplicate_targets,
+ externals,
+ /*scratch_*/pool,
+ /*scratch_*/pool));
+ if (duplicate_targets && duplicate_targets->nelts > 0)
+ {
+ const char *more_str = "";
+ if (duplicate_targets->nelts > 1)
+ {
+ more_str = apr_psprintf(/*scratch_*/pool,
+ _(" (%d more duplicate targets found)"),
+ duplicate_targets->nelts - 1);
+ }
+ return svn_error_createf(
+ SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET, NULL,
+ _("Invalid %s property on '%s': "
+ "target '%s' appears more than once%s"),
+ SVN_PROP_EXTERNALS,
+ svn_dirent_local_style(path, pool),
+ APR_ARRAY_IDX(duplicate_targets, 0, const char*),
+ more_str);
+ }
+ }
+ }
+ else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0)
+ {
+ new_value = svn_stringbuf_create_from_string(propval, pool);
+ svn_stringbuf_strip_whitespace(new_value);
+ }
+ else if (svn_prop_is_boolean(propname))
+ {
+ /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
+ propval = &boolean_value;
+ }
+ else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
+ {
+ apr_hash_t *mergeinfo;
+ svn_string_t *new_value_str;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool));
+
+ /* Non-inheritable mergeinfo is only valid on directories. */
+ if (kind != svn_node_dir
+ && svn_mergeinfo__is_noninheritable(mergeinfo, pool))
+ return svn_error_createf(
+ SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
+ _("Cannot set non-inheritable mergeinfo on a non-directory ('%s')"),
+ svn_dirent_local_style(path, pool));
+
+ SVN_ERR(svn_mergeinfo_to_string(&new_value_str, mergeinfo, pool));
+ propval = new_value_str;
+ }
+
+ if (new_value)
+ *propval_p = svn_stringbuf__morph_into_string(new_value);
+ else
+ *propval_p = propval;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_boolean_t
+svn_wc_is_normal_prop(const char *name)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ return (kind == svn_prop_regular_kind);
+}
+
+
+svn_boolean_t
+svn_wc_is_wc_prop(const char *name)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ return (kind == svn_prop_wc_kind);
+}
+
+
+svn_boolean_t
+svn_wc_is_entry_prop(const char *name)
+{
+ enum svn_prop_kind kind = svn_property_kind2(name);
+ return (kind == svn_prop_entry_kind);
+}
+
+
+svn_error_t *
+svn_wc__props_modified(svn_boolean_t *modified_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, modified_p, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_props_modified_p2(svn_boolean_t *modified_p,
+ svn_wc_context_t* wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__props_modified(modified_p,
+ wc_ctx->db,
+ local_abspath,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__internal_propdiff(apr_array_header_t **propchanges,
+ apr_hash_t **original_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *baseprops;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* ### if pristines are not defined, then should this raise an error,
+ ### or use an empty set? */
+ SVN_ERR(svn_wc__db_read_pristine_props(&baseprops, db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (original_props != NULL)
+ *original_props = baseprops;
+
+ if (propchanges != NULL)
+ {
+ apr_hash_t *actual_props;
+
+ /* Some nodes do not have pristine props, so let's just use an empty
+ set here. Thus, any ACTUAL props are additions. */
+ if (baseprops == NULL)
+ baseprops = apr_hash_make(scratch_pool);
+
+ SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath,
+ result_pool, scratch_pool));
+ /* ### be wary. certain nodes don't have ACTUAL props either. we
+ ### may want to raise an error. or maybe that is a deletion of
+ ### any potential pristine props? */
+
+ SVN_ERR(svn_prop_diffs(propchanges, actual_props, baseprops,
+ result_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_get_prop_diffs2(apr_array_header_t **propchanges,
+ apr_hash_t **original_props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__internal_propdiff(propchanges,
+ original_props, wc_ctx->db, local_abspath,
+ result_pool, scratch_pool));
+}
+
+svn_boolean_t
+svn_wc__has_magic_property(const apr_array_header_t *properties)
+{
+ int i;
+
+ for (i = 0; i < properties->nelts; i++)
+ {
+ const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t);
+
+ if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0
+ || strcmp(property->name, SVN_PROP_KEYWORDS) == 0
+ || strcmp(property->name, SVN_PROP_EOL_STYLE) == 0
+ || strcmp(property->name, SVN_PROP_SPECIAL) == 0
+ || strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+svn_error_t *
+svn_wc__get_iprops(apr_array_header_t **inherited_props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__db_read_inherited_props(inherited_props, NULL,
+ wc_ctx->db, local_abspath,
+ propname,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths,
+ depth,
+ local_abspath,
+ wc_ctx->db,
+ result_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/props.h b/contrib/subversion/subversion/libsvn_wc/props.h
new file mode 100644
index 0000000..c648e3c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/props.h
@@ -0,0 +1,154 @@
+/*
+ * props.h : properties
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_WC_PROPS_H
+#define SVN_LIBSVN_WC_PROPS_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_props.h"
+
+#include "wc_db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Internal function for diffing props. See svn_wc_get_prop_diffs2(). */
+svn_error_t *
+svn_wc__internal_propdiff(apr_array_header_t **propchanges,
+ apr_hash_t **original_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Internal function for fetching a property. See svn_wc_prop_get2(). */
+svn_error_t *
+svn_wc__internal_propget(const svn_string_t **value,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Validate and canonicalize the PROPS like svn_wc_prop_set4() does;
+ * see that function for details of the SKIP_SOME_CHECKS option.
+ *
+ * The properties are checked against the node at LOCAL_ABSPATH (which
+ * need not be under version control) of kind KIND. This text of this
+ * node may be read (if it is a file) in order to validate the
+ * svn:eol-style property.
+ *
+ * Only regular props are accepted; WC props and entry props raise an error
+ * (unlike svn_wc_prop_set4() which accepts WC props).
+ *
+ * Set *PREPARED_PROPS to the resulting canonicalized properties,
+ * allocating any new data in RESULT_POOL but making shallow copies of
+ * keys and unchanged values from PROPS.
+ */
+svn_error_t *
+svn_wc__canonicalize_props(apr_hash_t **prepared_props,
+ const char *local_abspath,
+ svn_node_kind_t node_kind,
+ const apr_hash_t *props,
+ svn_boolean_t skip_some_checks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Given LOCAL_ABSPATH/DB and an array of PROPCHANGES based on
+ SERVER_BASEPROPS, calculate what changes should be applied to the working
+ copy.
+
+ We return the new property collections to the caller, so the caller
+ can combine the property update with other operations.
+
+ If SERVER_BASEPROPS is NULL then use the pristine props as PROPCHANGES
+ base.
+
+ Return the new set of actual properties in *NEW_ACTUAL_PROPS.
+
+ Append any conflicts of the actual props to *CONFLICT_SKEL. (First
+ allocate *CONFLICT_SKEL from RESULT_POOL if it is initially NULL.
+ CONFLICT_SKEL itself must not be NULL.)
+
+ If STATE is non-null, set *STATE to the state of the local properties
+ after the merge, one of:
+
+ svn_wc_notify_state_unchanged
+ svn_wc_notify_state_changed
+ svn_wc_notify_state_merged
+ svn_wc_notify_state_conflicted
+ */
+svn_error_t *
+svn_wc__merge_props(svn_skel_t **conflict_skel,
+ svn_wc_notify_state_t *state,
+ apr_hash_t **new_actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ /*const*/ apr_hash_t *server_baseprops,
+ /*const*/ apr_hash_t *pristine_props,
+ /*const*/ apr_hash_t *actual_props,
+ const apr_array_header_t *propchanges,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Given PROPERTIES is array of @c svn_prop_t structures. Returns TRUE if any
+ of the PROPERTIES are the known "magic" ones that might require
+ changing the working file. */
+svn_boolean_t svn_wc__has_magic_property(const apr_array_header_t *properties);
+
+/* Set *MODIFIED_P TRUE if the props for LOCAL_ABSPATH have been modified. */
+svn_error_t *
+svn_wc__props_modified(svn_boolean_t *modified_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc_prop_list2(). */
+svn_error_t *
+svn_wc__get_actual_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__create_prejfile(const char **tmp_prejfile_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_PROPS_H */
diff --git a/contrib/subversion/subversion/libsvn_wc/questions.c b/contrib/subversion/subversion/libsvn_wc/questions.c
new file mode 100644
index 0000000..c2a42b6
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/questions.c
@@ -0,0 +1,621 @@
+/*
+ * questions.c: routines for asking questions about working copies
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_file_info.h>
+#include <apr_time.h>
+
+#include "svn_pools.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_time.h"
+#include "svn_io.h"
+#include "svn_props.h"
+
+#include "wc.h"
+#include "conflicts.h"
+#include "translate.h"
+#include "wc_db.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+/*** svn_wc_text_modified_p ***/
+
+/* svn_wc_text_modified_p answers the question:
+
+ "Are the contents of F different than the contents of
+ .svn/text-base/F.svn-base or .svn/tmp/text-base/F.svn-base?"
+
+ In the first case, we're looking to see if a user has made local
+ modifications to a file since the last update or commit. In the
+ second, the file may not be versioned yet (it doesn't exist in
+ entries). Support for the latter case came about to facilitate
+ forced checkouts, updates, and switches, where an unversioned file
+ may obstruct a file about to be added.
+
+ Note: Assuming that F lives in a directory D at revision V, please
+ notice that we are *NOT* answering the question, "are the contents
+ of F different than revision V of F?" While F may be at a different
+ revision number than its parent directory, but we're only looking
+ for local edits on F, not for consistent directory revisions.
+
+ TODO: the logic of the routines on this page might change in the
+ future, as they bear some relation to the user interface. For
+ example, if a file is removed -- without telling subversion about
+ it -- how should subversion react? Should it copy the file back
+ out of text-base? Should it ask whether one meant to officially
+ mark it for removal?
+*/
+
+
+/* Set *MODIFIED_P to TRUE if (after translation) VERSIONED_FILE_ABSPATH
+ * (of VERSIONED_FILE_SIZE bytes) differs from PRISTINE_STREAM (of
+ * PRISTINE_SIZE bytes), else to FALSE if not.
+ *
+ * If EXACT_COMPARISON is FALSE, translate VERSIONED_FILE_ABSPATH's EOL
+ * style and keywords to repository-normal form according to its properties,
+ * and compare the result with PRISTINE_STREAM. If EXACT_COMPARISON is
+ * TRUE, translate PRISTINE_STREAM's EOL style and keywords to working-copy
+ * form according to VERSIONED_FILE_ABSPATH's properties, and compare the
+ * result with VERSIONED_FILE_ABSPATH.
+ *
+ * HAS_PROPS should be TRUE if the file had properties when it was not
+ * modified, otherwise FALSE.
+ *
+ * PROPS_MOD should be TRUE if the file's properties have been changed,
+ * otherwise FALSE.
+ *
+ * PRISTINE_STREAM will be closed before a successful return.
+ *
+ * DB is a wc_db; use SCRATCH_POOL for temporary allocation.
+ */
+static svn_error_t *
+compare_and_verify(svn_boolean_t *modified_p,
+ svn_wc__db_t *db,
+ const char *versioned_file_abspath,
+ svn_filesize_t versioned_file_size,
+ svn_stream_t *pristine_stream,
+ svn_filesize_t pristine_size,
+ svn_boolean_t has_props,
+ svn_boolean_t props_mod,
+ svn_boolean_t exact_comparison,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t same;
+ svn_subst_eol_style_t eol_style;
+ const char *eol_str;
+ apr_hash_t *keywords;
+ svn_boolean_t special = FALSE;
+ svn_boolean_t need_translation;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_file_abspath));
+
+ if (props_mod)
+ has_props = TRUE; /* Maybe it didn't have properties; but it has now */
+
+ if (has_props)
+ {
+ SVN_ERR(svn_wc__get_translate_info(&eol_style, &eol_str,
+ &keywords,
+ &special,
+ db, versioned_file_abspath, NULL,
+ !exact_comparison,
+ scratch_pool, scratch_pool));
+
+ need_translation = svn_subst_translation_required(eol_style, eol_str,
+ keywords, special,
+ TRUE);
+ }
+ else
+ need_translation = FALSE;
+
+ if (! need_translation
+ && (versioned_file_size != pristine_size))
+ {
+ *modified_p = TRUE;
+
+ /* ### Why did we open the pristine? */
+ return svn_error_trace(svn_stream_close(pristine_stream));
+ }
+
+ /* ### Other checks possible? */
+
+ if (need_translation)
+ {
+ /* Reading files is necessary. */
+ svn_stream_t *v_stream; /* versioned_file */
+
+ if (special)
+ {
+ SVN_ERR(svn_subst_read_specialfile(&v_stream, versioned_file_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_stream_open_readonly(&v_stream, versioned_file_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!exact_comparison && need_translation)
+ {
+ if (eol_style == svn_subst_eol_style_native)
+ eol_str = SVN_SUBST_NATIVE_EOL_STR;
+ else if (eol_style != svn_subst_eol_style_fixed
+ && eol_style != svn_subst_eol_style_none)
+ return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL,
+ svn_stream_close(v_stream), NULL);
+
+ /* Wrap file stream to detranslate into normal form,
+ * "repairing" the EOL style if it is inconsistent. */
+ v_stream = svn_subst_stream_translated(v_stream,
+ eol_str,
+ TRUE /* repair */,
+ keywords,
+ FALSE /* expand */,
+ scratch_pool);
+ }
+ else if (need_translation)
+ {
+ /* Wrap base stream to translate into working copy form, and
+ * arrange to throw an error if its EOL style is inconsistent. */
+ pristine_stream = svn_subst_stream_translated(pristine_stream,
+ eol_str, FALSE,
+ keywords, TRUE,
+ scratch_pool);
+ }
+ }
+
+ SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream,
+ scratch_pool));
+ }
+ else
+ {
+ /* Translation would be a no-op, so compare the original file. */
+ svn_stream_t *v_stream; /* versioned_file */
+
+ SVN_ERR(svn_stream_open_readonly(&v_stream, versioned_file_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream,
+ scratch_pool));
+ }
+
+ *modified_p = (! same);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_file_modified_p(svn_boolean_t *modified_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t exact_comparison,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *pristine_stream;
+ svn_filesize_t pristine_size;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const svn_checksum_t *checksum;
+ svn_filesize_t recorded_size;
+ apr_time_t recorded_mod_time;
+ svn_boolean_t has_props;
+ svn_boolean_t props_mod;
+ const svn_io_dirent2_t *dirent;
+
+ /* Read the relevant info */
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &checksum, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ &recorded_size, &recorded_mod_time,
+ NULL, NULL, NULL, &has_props, &props_mod,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* If we don't have a pristine or the node has a status that allows a
+ pristine, just say that the node is modified */
+ if (!checksum
+ || (kind != svn_node_file)
+ || ((status != svn_wc__db_status_normal)
+ && (status != svn_wc__db_status_added)))
+ {
+ *modified_p = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
+ scratch_pool, scratch_pool));
+
+ if (dirent->kind != svn_node_file)
+ {
+ /* There is no file on disk, so the text is missing, not modified. */
+ *modified_p = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ if (! exact_comparison)
+ {
+ /* We're allowed to use a heuristic to determine whether files may
+ have changed. The heuristic has these steps:
+
+ 1. Compare the working file's size
+ with the size cached in the entries file
+ 2. If they differ, do a full file compare
+ 3. Compare the working file's timestamp
+ with the timestamp cached in the entries file
+ 4. If they differ, do a full file compare
+ 5. Otherwise, return indicating an unchanged file.
+
+ There are 2 problematic situations which may occur:
+
+ 1. The cached working size is missing
+ --> In this case, we forget we ever tried to compare
+ and skip to the timestamp comparison. This is
+ because old working copies do not contain cached sizes
+
+ 2. The cached timestamp is missing
+ --> In this case, we forget we ever tried to compare
+ and skip to full file comparison. This is because
+ the timestamp will be removed when the library
+ updates a locally changed file. (ie, this only happens
+ when the file was locally modified.)
+
+ */
+
+ /* Compare the sizes, if applicable */
+ if (recorded_size != SVN_INVALID_FILESIZE
+ && dirent->filesize != recorded_size)
+ goto compare_them;
+
+ /* Compare the timestamps
+
+ Note: recorded_mod_time == 0 means not available,
+ which also means the timestamps won't be equal,
+ so there's no need to explicitly check the 'absent' value. */
+ if (recorded_mod_time != dirent->mtime)
+ goto compare_them;
+
+ *modified_p = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ compare_them:
+ SVN_ERR(svn_wc__db_pristine_read(&pristine_stream, &pristine_size,
+ db, local_abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ /* Check all bytes, and verify checksum if requested. */
+ {
+ svn_error_t *err;
+ err = compare_and_verify(modified_p, db,
+ local_abspath, dirent->filesize,
+ pristine_stream, pristine_size,
+ has_props, props_mod,
+ exact_comparison,
+ scratch_pool);
+
+ /* At this point we already opened the pristine file, so we know that
+ the access denied applies to the working copy path */
+ if (err && APR_STATUS_IS_EACCES(err->apr_err))
+ return svn_error_create(SVN_ERR_WC_PATH_ACCESS_DENIED, err, NULL);
+ else
+ SVN_ERR(err);
+ }
+
+ if (!*modified_p)
+ {
+ svn_boolean_t own_lock;
+
+ /* The timestamp is missing or "broken" so "repair" it if we can. */
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE,
+ scratch_pool));
+ if (own_lock)
+ SVN_ERR(svn_wc__db_global_record_fileinfo(db, local_abspath,
+ dirent->filesize,
+ dirent->mtime,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_text_modified_p2(svn_boolean_t *modified_p,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t unused,
+ apr_pool_t *scratch_pool)
+{
+ return svn_wc__internal_file_modified_p(modified_p, wc_ctx->db,
+ local_abspath, FALSE, scratch_pool);
+}
+
+
+
+static svn_error_t *
+internal_conflicted_p(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ svn_boolean_t *tree_conflicted_p,
+ svn_boolean_t *ignore_move_edit_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ svn_skel_t *conflicts;
+ svn_boolean_t resolved_text = FALSE;
+ svn_boolean_t resolved_props = FALSE;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!conflicts)
+ {
+ if (text_conflicted_p)
+ *text_conflicted_p = FALSE;
+ if (prop_conflicted_p)
+ *prop_conflicted_p = FALSE;
+ if (tree_conflicted_p)
+ *tree_conflicted_p = FALSE;
+ if (ignore_move_edit_p)
+ *ignore_move_edit_p = FALSE;
+
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, text_conflicted_p,
+ prop_conflicted_p, tree_conflicted_p,
+ db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+
+ if (text_conflicted_p && *text_conflicted_p)
+ {
+ const char *mine_abspath;
+ const char *their_old_abspath;
+ const char *their_abspath;
+ svn_boolean_t done = FALSE;
+
+ /* Look for any text conflict, exercising only as much effort as
+ necessary to obtain a definitive answer. This only applies to
+ files, but we don't have to explicitly check that entry is a
+ file, since these attributes would never be set on a directory
+ anyway. A conflict file entry notation only counts if the
+ conflict file still exists on disk. */
+
+ SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath,
+ &their_old_abspath,
+ &their_abspath,
+ db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+
+ if (mine_abspath)
+ {
+ SVN_ERR(svn_io_check_path(mine_abspath, &kind, scratch_pool));
+
+ *text_conflicted_p = (kind == svn_node_file);
+
+ if (*text_conflicted_p)
+ done = TRUE;
+ }
+
+ if (!done && their_abspath)
+ {
+ SVN_ERR(svn_io_check_path(their_abspath, &kind, scratch_pool));
+
+ *text_conflicted_p = (kind == svn_node_file);
+
+ if (*text_conflicted_p)
+ done = TRUE;
+ }
+
+ if (!done && their_old_abspath)
+ {
+ SVN_ERR(svn_io_check_path(their_old_abspath, &kind, scratch_pool));
+
+ *text_conflicted_p = (kind == svn_node_file);
+
+ if (*text_conflicted_p)
+ done = TRUE;
+ }
+
+ if (!done && (mine_abspath || their_abspath || their_old_abspath))
+ resolved_text = TRUE; /* Remove in-db conflict marker */
+ }
+
+ if (prop_conflicted_p && *prop_conflicted_p)
+ {
+ const char *prej_abspath;
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+
+ if (prej_abspath)
+ {
+ SVN_ERR(svn_io_check_path(prej_abspath, &kind, scratch_pool));
+
+ *prop_conflicted_p = (kind == svn_node_file);
+
+ if (! *prop_conflicted_p)
+ resolved_props = TRUE; /* Remove in-db conflict marker */
+ }
+ }
+
+ if (ignore_move_edit_p)
+ {
+ *ignore_move_edit_p = FALSE;
+ if (tree_conflicted_p && *tree_conflicted_p)
+ {
+ svn_wc_conflict_reason_t reason;
+ svn_wc_conflict_action_t action;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL,
+ db, local_abspath,
+ conflicts,
+ scratch_pool,
+ scratch_pool));
+
+ if (reason == svn_wc_conflict_reason_moved_away
+ && action == svn_wc_conflict_action_edit)
+ {
+ *tree_conflicted_p = FALSE;
+ *ignore_move_edit_p = TRUE;
+ }
+ }
+ }
+
+ if (resolved_text || resolved_props)
+ {
+ svn_boolean_t own_lock;
+
+ /* The marker files are missing, so "repair" wc.db if we can */
+ SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE,
+ scratch_pool));
+ if (own_lock)
+ SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath,
+ resolved_text,
+ resolved_props,
+ FALSE /* resolved_tree */,
+ NULL /* work_items */,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ svn_boolean_t *tree_conflicted_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(internal_conflicted_p(text_conflicted_p, prop_conflicted_p,
+ tree_conflicted_p, NULL,
+ db, local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p,
+ svn_boolean_t *conflict_ignored_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t tree_only,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
+ svn_boolean_t conflict_ignored;
+
+ if (!conflict_ignored_p)
+ conflict_ignored_p = &conflict_ignored;
+
+ SVN_ERR(internal_conflicted_p(tree_only ? NULL: &text_conflicted,
+ tree_only ? NULL: &prop_conflicted,
+ &tree_conflicted, conflict_ignored_p,
+ db, local_abspath, scratch_pool));
+ if (tree_only)
+ *conflicted_p = tree_conflicted;
+ else
+ *conflicted_p = text_conflicted || prop_conflicted || tree_conflicted;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_conflicted_p3(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ svn_boolean_t *tree_conflicted_p,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__internal_conflicted_p(text_conflicted_p,
+ prop_conflicted_p,
+ tree_conflicted_p,
+ wc_ctx->db,
+ local_abspath,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__min_max_revisions(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_boolean_t committed,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_min_max_revisions(min_revision,
+ max_revision,
+ wc_ctx->db,
+ local_abspath,
+ committed,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__has_switched_subtrees(svn_boolean_t *is_switched,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *trail_url,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_has_switched_subtrees(is_switched,
+ wc_ctx->db,
+ local_abspath,
+ trail_url,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__has_local_mods(svn_boolean_t *is_modified,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_has_local_mods(is_modified,
+ wc_ctx->db,
+ local_abspath,
+ cancel_func,
+ cancel_baton,
+ scratch_pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/relocate.c b/contrib/subversion/subversion/libsvn_wc/relocate.c
new file mode 100644
index 0000000..4a9df67
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/relocate.c
@@ -0,0 +1,170 @@
+/*
+ * relocate.c: do wc repos relocation
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include "svn_wc.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+
+#include "wc.h"
+#include "props.h"
+
+#include "svn_private_config.h"
+
+
+/* If the components of RELPATH exactly match (after being
+ URI-encoded) the final components of URL, return a copy of URL
+ minus those components allocated in RESULT_POOL; otherwise, return
+ NULL. */
+static const char *
+url_remove_final_relpath(const char *url,
+ const char *relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *result = apr_pstrdup(result_pool, url);
+ char *result_end;
+ const char *relpath_end;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_path_is_url(url));
+ SVN_ERR_ASSERT_NO_RETURN(svn_relpath_is_canonical(relpath));
+
+ if (relpath[0] == 0)
+ return result;
+
+ relpath = svn_path_uri_encode(relpath, scratch_pool);
+ result_end = result + strlen(result) - 1;
+ relpath_end = relpath + strlen(relpath) - 1;
+
+ while (relpath_end >= relpath)
+ {
+ if (*result_end != *relpath_end)
+ return NULL;
+
+ relpath_end--;
+ result_end--;
+ }
+
+ if (*result_end != '/')
+ return NULL;
+
+ *result_end = 0;
+
+ return result;
+}
+
+svn_error_t *
+svn_wc_relocate4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *from,
+ const char *to,
+ svn_wc_relocation_validator3_t validator,
+ void *validator_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ const char *repos_relpath;
+ const char *old_repos_root, *old_url;
+ const char *new_repos_root, *new_url;
+ size_t from_len;
+ size_t old_url_len;
+ const char *uuid;
+ svn_boolean_t is_wc_root;
+
+ SVN_ERR(svn_wc__is_wcroot(&is_wc_root, wc_ctx, local_abspath,
+ scratch_pool));
+ if (! is_wc_root)
+ {
+ const char *wcroot_abspath;
+ svn_error_t *err;
+
+ err = svn_wc__db_get_wcroot(&wcroot_abspath, wc_ctx->db,
+ local_abspath, scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return svn_error_createf(
+ SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
+ _("Cannot relocate '%s' as it is not the root of a working copy"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
+ _("Cannot relocate '%s' as it is not the root of a working copy; "
+ "try relocating '%s' instead"),
+ svn_dirent_local_style(local_abspath, scratch_pool),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool));
+ }
+ }
+
+ SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, &repos_relpath,
+ &old_repos_root, &uuid,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wc_ctx->db, local_abspath, scratch_pool,
+ scratch_pool));
+
+ if (kind != svn_node_dir)
+ return svn_error_create(SVN_ERR_CLIENT_INVALID_RELOCATION, NULL,
+ _("Cannot relocate a single file"));
+
+ old_url = svn_path_url_add_component2(old_repos_root, repos_relpath,
+ scratch_pool);
+ old_url_len = strlen(old_url);
+ from_len = strlen(from);
+ if ((from_len > old_url_len) || (strncmp(old_url, from, strlen(from)) != 0))
+ return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL,
+ _("Invalid source URL prefix: '%s' (does not "
+ "overlap target's URL '%s')"),
+ from, old_url);
+
+ if (old_url_len == from_len)
+ new_url = to;
+ else
+ new_url = apr_pstrcat(scratch_pool, to, old_url + from_len, (char *)NULL);
+ if (! svn_path_is_url(new_url))
+ return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL,
+ _("Invalid relocation destination: '%s' "
+ "(not a URL)"), new_url);
+
+ new_repos_root = url_remove_final_relpath(new_url, repos_relpath,
+ scratch_pool, scratch_pool);
+ if (!new_repos_root)
+ return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL,
+ _("Invalid relocation destination: '%s' "
+ "(does not point to target)" ), new_url);
+
+ SVN_ERR(validator(validator_baton, uuid, new_url, new_repos_root,
+ scratch_pool));
+
+ return svn_error_trace(svn_wc__db_global_relocate(wc_ctx->db, local_abspath,
+ new_repos_root,
+ scratch_pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/revert.c b/contrib/subversion/subversion/libsvn_wc/revert.c
new file mode 100644
index 0000000..5e190e8
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/revert.c
@@ -0,0 +1,886 @@
+/*
+ * revert.c: Handling of the in-wc side of the revert operation
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_io.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "workqueue.h"
+
+#include "svn_private_config.h"
+#include "private/svn_io_private.h"
+#include "private/svn_wc_private.h"
+
+/* Thoughts on Reversion.
+
+ What does is mean to revert a given PATH in a tree? We'll
+ consider things by their modifications.
+
+ Adds
+
+ - For files, svn_wc_remove_from_revision_control(), baby.
+
+ - Added directories may contain nothing but added children, and
+ reverting the addition of a directory necessarily means reverting
+ the addition of all the directory's children. Again,
+ svn_wc_remove_from_revision_control() should do the trick.
+
+ Deletes
+
+ - Restore properties to their unmodified state.
+
+ - For files, restore the pristine contents, and reset the schedule
+ to 'normal'.
+
+ - For directories, reset the schedule to 'normal'. All children
+ of a directory marked for deletion must also be marked for
+ deletion, but it's okay for those children to remain deleted even
+ if their parent directory is restored. That's what the
+ recursive flag is for.
+
+ Replaces
+
+ - Restore properties to their unmodified state.
+
+ - For files, restore the pristine contents, and reset the schedule
+ to 'normal'.
+
+ - For directories, reset the schedule to normal. A replaced
+ directory can have deleted children (left over from the initial
+ deletion), replaced children (children of the initial deletion
+ now re-added), and added children (new entries under the
+ replaced directory). Since this is technically an addition, it
+ necessitates recursion.
+
+ Modifications
+
+ - Restore properties and, for files, contents to their unmodified
+ state.
+
+*/
+
+
+/* Remove conflict file CONFLICT_ABSPATH, which may not exist, and set
+ * *NOTIFY_REQUIRED to TRUE if the file was present and removed. */
+static svn_error_t *
+remove_conflict_file(svn_boolean_t *notify_required,
+ const char *conflict_abspath,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ if (conflict_abspath)
+ {
+ svn_error_t *err = svn_io_remove_file2(conflict_abspath, FALSE,
+ scratch_pool);
+ if (err)
+ svn_error_clear(err);
+ else
+ *notify_required = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Sort copied children obtained from the revert list based on
+ * their paths in descending order (longest paths first). */
+static int
+compare_revert_list_copied_children(const void *a, const void *b)
+{
+ const svn_wc__db_revert_list_copied_child_info_t * const *ca = a;
+ const svn_wc__db_revert_list_copied_child_info_t * const *cb = b;
+ int i;
+
+ i = svn_path_compare_paths(ca[0]->abspath, cb[0]->abspath);
+
+ /* Reverse the result of svn_path_compare_paths() to achieve
+ * descending order. */
+ return -i;
+}
+
+
+/* Remove all reverted copied children from the directory at LOCAL_ABSPATH.
+ * If REMOVE_SELF is TRUE, try to remove LOCAL_ABSPATH itself (REMOVE_SELF
+ * should be set if LOCAL_ABSPATH is itself a reverted copy).
+ *
+ * If REMOVED_SELF is not NULL, indicate in *REMOVED_SELF whether
+ * LOCAL_ABSPATH itself was removed.
+ *
+ * All reverted copied file children are removed from disk. Reverted copied
+ * directories left empty as a result are also removed from disk.
+ */
+static svn_error_t *
+revert_restore_handle_copied_dirs(svn_boolean_t *removed_self,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t remove_self,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *copied_children;
+ svn_wc__db_revert_list_copied_child_info_t *child_info;
+ int i;
+ svn_node_kind_t on_disk;
+ apr_pool_t *iterpool;
+ svn_error_t *err;
+
+ if (removed_self)
+ *removed_self = FALSE;
+
+ SVN_ERR(svn_wc__db_revert_list_read_copied_children(&copied_children,
+ db, local_abspath,
+ scratch_pool,
+ scratch_pool));
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* Remove all copied file children. */
+ for (i = 0; i < copied_children->nelts; i++)
+ {
+ child_info = APR_ARRAY_IDX(
+ copied_children, i,
+ svn_wc__db_revert_list_copied_child_info_t *);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ if (child_info->kind != svn_node_file)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ /* Make sure what we delete from disk is really a file. */
+ SVN_ERR(svn_io_check_path(child_info->abspath, &on_disk, iterpool));
+ if (on_disk != svn_node_file)
+ continue;
+
+ SVN_ERR(svn_io_remove_file2(child_info->abspath, TRUE, iterpool));
+ }
+
+ /* Delete every empty child directory.
+ * We cannot delete children recursively since we want to keep any files
+ * that still exist on disk (e.g. unversioned files within the copied tree).
+ * So sort the children list such that longest paths come first and try to
+ * remove each child directory in order. */
+ qsort(copied_children->elts, copied_children->nelts,
+ sizeof(svn_wc__db_revert_list_copied_child_info_t *),
+ compare_revert_list_copied_children);
+ for (i = 0; i < copied_children->nelts; i++)
+ {
+ child_info = APR_ARRAY_IDX(
+ copied_children, i,
+ svn_wc__db_revert_list_copied_child_info_t *);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ if (child_info->kind != svn_node_dir)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ err = svn_io_dir_remove_nonrecursive(child_info->abspath, iterpool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err) ||
+ SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) ||
+ APR_STATUS_IS_ENOTEMPTY(err->apr_err))
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ }
+
+ if (remove_self)
+ {
+ /* Delete LOCAL_ABSPATH itself if no children are left. */
+ err = svn_io_dir_remove_nonrecursive(local_abspath, iterpool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else if (removed_self)
+ *removed_self = TRUE;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Make the working tree under LOCAL_ABSPATH to depth DEPTH match the
+ versioned tree. This function is called after svn_wc__db_op_revert
+ has done the database revert and created the revert list. Notifies
+ for all paths equal to or below LOCAL_ABSPATH that are reverted.
+
+ REVERT_ROOT is true for explicit revert targets and FALSE for targets
+ reached via recursion.
+ */
+static svn_error_t *
+revert_restore(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t revert_root,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_node_kind_t on_disk;
+ svn_boolean_t notify_required;
+ const apr_array_header_t *conflict_files;
+ svn_filesize_t recorded_size;
+ apr_time_t recorded_time;
+ apr_finfo_t finfo;
+#ifdef HAVE_SYMLINK
+ svn_boolean_t special;
+#endif
+ svn_boolean_t copied_here;
+ svn_node_kind_t reverted_kind;
+ svn_boolean_t is_wcroot;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool));
+ if (is_wcroot && !revert_root)
+ {
+ /* Issue #4162: Obstructing working copy. We can't access the working
+ copy data from the parent working copy for this node by just using
+ local_abspath */
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify = svn_wc_create_notify(
+ local_abspath,
+ svn_wc_notify_update_skip_obstruction,
+ scratch_pool);
+
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR; /* We don't revert obstructing working copies */
+ }
+
+ SVN_ERR(svn_wc__db_revert_list_read(&notify_required,
+ &conflict_files,
+ &copied_here, &reverted_kind,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ err = svn_wc__db_read_info(&status, &kind,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &recorded_size, &recorded_time, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+
+ if (!copied_here)
+ {
+ if (notify_func && notify_required)
+ notify_func(notify_baton,
+ svn_wc_create_notify(local_abspath,
+ svn_wc_notify_revert,
+ scratch_pool),
+ scratch_pool);
+
+ if (notify_func)
+ SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton,
+ db, local_abspath,
+ scratch_pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* ### Initialise to values which prevent the code below from
+ * ### trying to restore anything to disk.
+ * ### 'status' should be status_unknown but that doesn't exist. */
+ status = svn_wc__db_status_normal;
+ kind = svn_node_unknown;
+ recorded_size = SVN_INVALID_FILESIZE;
+ recorded_time = 0;
+ }
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ err = svn_io_stat(&finfo, local_abspath,
+ APR_FINFO_TYPE | APR_FINFO_LINK
+ | APR_FINFO_SIZE | APR_FINFO_MTIME
+ | SVN__APR_FINFO_EXECUTABLE
+ | SVN__APR_FINFO_READONLY,
+ scratch_pool);
+
+ if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ {
+ svn_error_clear(err);
+ on_disk = svn_node_none;
+#ifdef HAVE_SYMLINK
+ special = FALSE;
+#endif
+ }
+ else if (!err)
+ {
+ if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK)
+ on_disk = svn_node_file;
+ else if (finfo.filetype == APR_DIR)
+ on_disk = svn_node_dir;
+ else
+ on_disk = svn_node_unknown;
+
+#ifdef HAVE_SYMLINK
+ special = (finfo.filetype == APR_LNK);
+#endif
+ }
+ else
+ return svn_error_trace(err);
+
+ if (copied_here)
+ {
+ /* The revert target itself is the op-root of a copy. */
+ if (reverted_kind == svn_node_file && on_disk == svn_node_file)
+ {
+ SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool));
+ on_disk = svn_node_none;
+ }
+ else if (reverted_kind == svn_node_dir && on_disk == svn_node_dir)
+ {
+ svn_boolean_t removed;
+
+ SVN_ERR(revert_restore_handle_copied_dirs(&removed, db,
+ local_abspath, TRUE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ if (removed)
+ on_disk = svn_node_none;
+ }
+ }
+
+ /* If we expect a versioned item to be present then check that any
+ item on disk matches the versioned item, if it doesn't match then
+ fix it or delete it. */
+ if (on_disk != svn_node_none
+ && status != svn_wc__db_status_server_excluded
+ && status != svn_wc__db_status_deleted
+ && status != svn_wc__db_status_excluded
+ && status != svn_wc__db_status_not_present)
+ {
+ if (on_disk == svn_node_dir && kind != svn_node_dir)
+ {
+ SVN_ERR(svn_io_remove_dir2(local_abspath, FALSE,
+ cancel_func, cancel_baton, scratch_pool));
+ on_disk = svn_node_none;
+ }
+ else if (on_disk == svn_node_file && kind != svn_node_file)
+ {
+#ifdef HAVE_SYMLINK
+ /* Preserve symlinks pointing at directories. Changes on the
+ * directory node have been reverted. The symlink should remain. */
+ if (!(special && kind == svn_node_dir))
+#endif
+ {
+ SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool));
+ on_disk = svn_node_none;
+ }
+ }
+ else if (on_disk == svn_node_file)
+ {
+ svn_boolean_t modified;
+ apr_hash_t *props;
+#ifdef HAVE_SYMLINK
+ svn_string_t *special_prop;
+#endif
+
+ SVN_ERR(svn_wc__db_read_pristine_props(&props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+#ifdef HAVE_SYMLINK
+ special_prop = svn_hash_gets(props, SVN_PROP_SPECIAL);
+
+ if ((special_prop != NULL) != special)
+ {
+ /* File/symlink mismatch. */
+ SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool));
+ on_disk = svn_node_none;
+ }
+ else
+#endif
+ {
+ /* Issue #1663 asserts that we should compare a file in its
+ working copy format here, but before r1101473 we would only
+ do that if the file was already unequal to its recorded
+ information.
+
+ r1101473 removes the option of asking for a working format
+ compare but *also* check the recorded information first, as
+ that combination doesn't guarantee a stable behavior.
+ (See the revert_test.py: revert_reexpand_keyword)
+
+ But to have the same issue #1663 behavior for revert as we
+ had in <=1.6 we only have to check the recorded information
+ ourselves. And we already have everything we need, because
+ we called stat ourselves. */
+ if (recorded_size != SVN_INVALID_FILESIZE
+ && recorded_time != 0
+ && recorded_size == finfo.size
+ && recorded_time == finfo.mtime)
+ {
+ modified = FALSE;
+ }
+ else
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified,
+ db, local_abspath,
+ TRUE, scratch_pool));
+
+ if (modified)
+ {
+ SVN_ERR(svn_io_remove_file2(local_abspath, FALSE,
+ scratch_pool));
+ on_disk = svn_node_none;
+ }
+ else
+ {
+ if (status == svn_wc__db_status_normal)
+ {
+ svn_boolean_t read_only;
+ svn_string_t *needs_lock_prop;
+
+ SVN_ERR(svn_io__is_finfo_read_only(&read_only, &finfo,
+ scratch_pool));
+
+ needs_lock_prop = svn_hash_gets(props,
+ SVN_PROP_NEEDS_LOCK);
+ if (needs_lock_prop && !read_only)
+ {
+ SVN_ERR(svn_io_set_file_read_only(local_abspath,
+ FALSE,
+ scratch_pool));
+ notify_required = TRUE;
+ }
+ else if (!needs_lock_prop && read_only)
+ {
+ SVN_ERR(svn_io_set_file_read_write(local_abspath,
+ FALSE,
+ scratch_pool));
+ notify_required = TRUE;
+ }
+ }
+
+#if !defined(WIN32) && !defined(__OS2__)
+#ifdef HAVE_SYMLINK
+ if (!special)
+#endif
+ {
+ svn_boolean_t executable;
+ svn_string_t *executable_prop;
+
+ SVN_ERR(svn_io__is_finfo_executable(&executable, &finfo,
+ scratch_pool));
+ executable_prop = svn_hash_gets(props,
+ SVN_PROP_EXECUTABLE);
+ if (executable_prop && !executable)
+ {
+ SVN_ERR(svn_io_set_file_executable(local_abspath,
+ TRUE, FALSE,
+ scratch_pool));
+ notify_required = TRUE;
+ }
+ else if (!executable_prop && executable)
+ {
+ SVN_ERR(svn_io_set_file_executable(local_abspath,
+ FALSE, FALSE,
+ scratch_pool));
+ notify_required = TRUE;
+ }
+ }
+#endif
+ }
+ }
+ }
+ }
+
+ /* If we expect a versioned item to be present and there is nothing
+ on disk then recreate it. */
+ if (on_disk == svn_node_none
+ && status != svn_wc__db_status_server_excluded
+ && status != svn_wc__db_status_deleted
+ && status != svn_wc__db_status_excluded
+ && status != svn_wc__db_status_not_present)
+ {
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
+
+ if (kind == svn_node_file)
+ {
+ svn_skel_t *work_item;
+
+ /* ### Get the checksum from read_info above and pass in here? */
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, local_abspath,
+ NULL, use_commit_times, TRUE,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_item,
+ scratch_pool));
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ notify_required = TRUE;
+ }
+
+ if (conflict_files)
+ {
+ int i;
+ for (i = 0; i < conflict_files->nelts; i++)
+ {
+ SVN_ERR(remove_conflict_file(&notify_required,
+ APR_ARRAY_IDX(conflict_files, i,
+ const char *),
+ local_abspath, scratch_pool));
+ }
+ }
+
+ if (notify_func && notify_required)
+ notify_func(notify_baton,
+ svn_wc_create_notify(local_abspath, svn_wc_notify_revert,
+ scratch_pool),
+ scratch_pool);
+
+ if (depth == svn_depth_infinity && kind == svn_node_dir)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const apr_array_header_t *children;
+ int i;
+
+ SVN_ERR(revert_restore_handle_copied_dirs(NULL, db, local_abspath, FALSE,
+ cancel_func, cancel_baton,
+ iterpool));
+
+ SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
+ local_abspath,
+ scratch_pool,
+ iterpool));
+ for (i = 0; i < children->nelts; ++i)
+ {
+ const char *child_abspath;
+
+ svn_pool_clear(iterpool);
+
+ child_abspath = svn_dirent_join(local_abspath,
+ APR_ARRAY_IDX(children, i,
+ const char *),
+ iterpool);
+
+ SVN_ERR(revert_restore(db, child_abspath, depth,
+ use_commit_times, FALSE /* revert root */,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ if (notify_func)
+ SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton,
+ db, local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__revert_internal(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(depth == svn_depth_empty || depth == svn_depth_infinity);
+
+ /* We should have a write lock on the parent of local_abspath, except
+ when local_abspath is the working copy root. */
+ {
+ const char *dir_abspath;
+ svn_boolean_t is_wcroot;
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool));
+
+ if (! is_wcroot)
+ dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ else
+ dir_abspath = local_abspath;
+
+ SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
+ }
+
+ err = svn_wc__db_op_revert(db, local_abspath, depth,
+ scratch_pool, scratch_pool);
+
+ if (!err)
+ err = revert_restore(db, local_abspath, depth,
+ use_commit_times, TRUE /* revert root */,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool);
+
+ err = svn_error_compose_create(err,
+ svn_wc__db_revert_list_done(db,
+ local_abspath,
+ scratch_pool));
+
+ return err;
+}
+
+
+/* Revert files in LOCAL_ABSPATH to depth DEPTH that match
+ CHANGELIST_HASH and notify for all reverts. */
+static svn_error_t *
+revert_changelist(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ apr_hash_t *changelist_hash,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ const apr_array_header_t *children;
+ int i;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Revert this node (depth=empty) if it matches one of the changelists. */
+ if (svn_wc__internal_changelist_match(db, local_abspath, changelist_hash,
+ scratch_pool))
+ SVN_ERR(svn_wc__revert_internal(db, local_abspath,
+ svn_depth_empty, use_commit_times,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* We can handle both depth=files and depth=immediates by setting
+ depth=empty here. We don't need to distinguish files and
+ directories when making the recursive call because directories
+ can never match a changelist, so making the recursive call for
+ directories when asked for depth=files is a no-op. */
+ if (depth == svn_depth_files || depth == svn_depth_immediates)
+ depth = svn_depth_empty;
+
+ SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
+ local_abspath,
+ scratch_pool,
+ iterpool));
+ for (i = 0; i < children->nelts; ++i)
+ {
+ const char *child_abspath;
+
+ svn_pool_clear(iterpool);
+
+ child_abspath = svn_dirent_join(local_abspath,
+ APR_ARRAY_IDX(children, i,
+ const char *),
+ iterpool);
+
+ SVN_ERR(revert_changelist(db, child_abspath, depth,
+ use_commit_times, changelist_hash,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Does a partially recursive revert of LOCAL_ABSPATH to depth DEPTH
+ (which must be either svn_depth_files or svn_depth_immediates) by
+ doing a non-recursive revert on each permissible path. Notifies
+ all reverted paths.
+
+ ### This won't revert a copied dir with one level of children since
+ ### the non-recursive revert on the dir will fail. Not sure how a
+ ### partially recursive revert should handle actual-only nodes. */
+static svn_error_t *
+revert_partial(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ const apr_array_header_t *children;
+ int i;
+
+ SVN_ERR_ASSERT(depth == svn_depth_files || depth == svn_depth_immediates);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* Revert the root node itself (depth=empty), then move on to the
+ children. */
+ SVN_ERR(svn_wc__revert_internal(db, local_abspath, svn_depth_empty,
+ use_commit_times, cancel_func, cancel_baton,
+ notify_func, notify_baton, iterpool));
+
+ SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
+ local_abspath,
+ scratch_pool,
+ iterpool));
+ for (i = 0; i < children->nelts; ++i)
+ {
+ const char *child_abspath;
+
+ svn_pool_clear(iterpool);
+
+ child_abspath = svn_dirent_join(local_abspath,
+ APR_ARRAY_IDX(children, i, const char *),
+ iterpool);
+
+ /* For svn_depth_files: don't revert non-files. */
+ if (depth == svn_depth_files)
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_wc__db_read_kind(&kind, db, child_abspath,
+ FALSE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */,
+ iterpool));
+ if (kind != svn_node_file)
+ continue;
+ }
+
+ /* Revert just this node (depth=empty). */
+ SVN_ERR(svn_wc__revert_internal(db, child_abspath,
+ svn_depth_empty, use_commit_times,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_revert4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ const apr_array_header_t *changelist_filter,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ if (changelist_filter && changelist_filter->nelts)
+ {
+ apr_hash_t *changelist_hash;
+
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
+ scratch_pool));
+ return svn_error_trace(revert_changelist(wc_ctx->db, local_abspath,
+ depth, use_commit_times,
+ changelist_hash,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+ }
+
+ if (depth == svn_depth_empty || depth == svn_depth_infinity)
+ return svn_error_trace(svn_wc__revert_internal(wc_ctx->db, local_abspath,
+ depth, use_commit_times,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+
+ /* The user may expect svn_depth_files/svn_depth_immediates to work
+ on copied dirs with one level of children. It doesn't, the user
+ will get an error and will need to invoke an infinite revert. If
+ we identified those cases where svn_depth_infinity would not
+ revert too much we could invoke the recursive call above. */
+
+ if (depth == svn_depth_files || depth == svn_depth_immediates)
+ return svn_error_trace(revert_partial(wc_ctx->db, local_abspath,
+ depth, use_commit_times,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+
+ /* Bogus depth. Tell the caller. */
+ return svn_error_create(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, NULL);
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/revision_status.c b/contrib/subversion/subversion/libsvn_wc/revision_status.c
new file mode 100644
index 0000000..a4b9bea
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/revision_status.c
@@ -0,0 +1,67 @@
+/*
+ * revision_status.c: report the revision range and status of a working copy
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_wc.h"
+#include "svn_dirent_uri.h"
+#include "wc_db.h"
+#include "wc.h"
+#include "props.h"
+
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+svn_error_t *
+svn_wc_revision_status2(svn_wc_revision_status_t **result_p,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *trail_url,
+ svn_boolean_t committed,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_revision_status_t *result = apr_pcalloc(result_pool, sizeof(*result));
+
+ *result_p = result;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* set result as nil */
+ result->min_rev = SVN_INVALID_REVNUM;
+ result->max_rev = SVN_INVALID_REVNUM;
+ result->switched = FALSE;
+ result->modified = FALSE;
+ result->sparse_checkout = FALSE;
+
+ SVN_ERR(svn_wc__db_revision_status(&result->min_rev, &result->max_rev,
+ &result->sparse_checkout,
+ &result->modified,
+ &result->switched,
+ wc_ctx->db, local_abspath, trail_url,
+ committed, cancel_func, cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/status.c b/contrib/subversion/subversion/libsvn_wc/status.c
new file mode 100644
index 0000000..1440b2e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/status.c
@@ -0,0 +1,3047 @@
+/*
+ * status.c: construct a status structure from an entry structure
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <assert.h>
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_hash.h>
+
+#include "svn_pools.h"
+#include "svn_types.h"
+#include "svn_delta.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_io.h"
+#include "svn_config.h"
+#include "svn_time.h"
+#include "svn_hash.h"
+#include "svn_sorts.h"
+
+#include "svn_private_config.h"
+
+#include "wc.h"
+#include "props.h"
+#include "entries.h"
+#include "translate.h"
+#include "tree_conflicts.h"
+
+#include "private/svn_wc_private.h"
+#include "private/svn_fspath.h"
+#include "private/svn_editor.h"
+
+
+
+/*** Baton used for walking the local status */
+struct walk_status_baton
+{
+ /* The DB handle for managing the working copy state. */
+ svn_wc__db_t *db;
+
+ /*** External handling ***/
+ /* Target of the status */
+ const char *target_abspath;
+
+ /* Should we ignore text modifications? */
+ svn_boolean_t ignore_text_mods;
+
+ /* Externals info harvested during the status run. */
+ apr_hash_t *externals;
+
+ /*** Repository lock handling ***/
+ /* The repository root URL, if set. */
+ const char *repos_root;
+
+ /* Repository locks, if set. */
+ apr_hash_t *repos_locks;
+};
+
+/*** Editor batons ***/
+
+struct edit_baton
+{
+ /* For status, the "destination" of the edit. */
+ const char *anchor_abspath;
+ const char *target_abspath;
+ const char *target_basename;
+
+ /* The DB handle for managing the working copy state. */
+ svn_wc__db_t *db;
+ svn_wc_context_t *wc_ctx;
+
+ /* The overall depth of this edit (a dir baton may override this).
+ *
+ * If this is svn_depth_unknown, the depths found in the working
+ * copy will govern the edit; or if the edit depth indicates a
+ * descent deeper than the found depths are capable of, the found
+ * depths also govern, of course (there's no point descending into
+ * something that's not there).
+ */
+ svn_depth_t default_depth;
+
+ /* Do we want all statuses (instead of just the interesting ones) ? */
+ svn_boolean_t get_all;
+
+ /* Ignore the svn:ignores. */
+ svn_boolean_t no_ignore;
+
+ /* The comparison revision in the repository. This is a reference
+ because this editor returns this rev to the driver directly, as
+ well as in each statushash entry. */
+ svn_revnum_t *target_revision;
+
+ /* Status function/baton. */
+ svn_wc_status_func4_t status_func;
+ void *status_baton;
+
+ /* Cancellation function/baton. */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ /* The configured set of default ignores. */
+ const apr_array_header_t *ignores;
+
+ /* Status item for the path represented by the anchor of the edit. */
+ svn_wc_status3_t *anchor_status;
+
+ /* Was open_root() called for this edit drive? */
+ svn_boolean_t root_opened;
+
+ /* The local status baton */
+ struct walk_status_baton wb;
+};
+
+
+struct dir_baton
+{
+ /* The path to this directory. */
+ const char *local_abspath;
+
+ /* Basename of this directory. */
+ const char *name;
+
+ /* The global edit baton. */
+ struct edit_baton *edit_baton;
+
+ /* Baton for this directory's parent, or NULL if this is the root
+ directory. */
+ struct dir_baton *parent_baton;
+
+ /* The ambient requested depth below this point in the edit. This
+ can differ from the parent baton's depth (with the edit baton
+ considered the ultimate parent baton). For example, if the
+ parent baton has svn_depth_immediates, then here we should have
+ svn_depth_empty, because there would be no further recursion, not
+ even to file children. */
+ svn_depth_t depth;
+
+ /* Is this directory filtered out due to depth? (Note that if this
+ is TRUE, the depth field is undefined.) */
+ svn_boolean_t excluded;
+
+ /* 'svn status' shouldn't print status lines for things that are
+ added; we're only interest in asking if objects that the user
+ *already* has are up-to-date or not. Thus if this flag is set,
+ the next two will be ignored. :-) */
+ svn_boolean_t added;
+
+ /* Gets set iff there's a change to this directory's properties, to
+ guide us when syncing adm files later. */
+ svn_boolean_t prop_changed;
+
+ /* This means (in terms of 'svn status') that some child was deleted
+ or added to the directory */
+ svn_boolean_t text_changed;
+
+ /* Working copy status structures for children of this directory.
+ This hash maps const char * abspaths to svn_wc_status3_t *
+ status items. */
+ apr_hash_t *statii;
+
+ /* The pool in which this baton itself is allocated. */
+ apr_pool_t *pool;
+
+ /* The repository root relative path to this item in the repository. */
+ const char *repos_relpath;
+
+ /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
+ svn_node_kind_t ood_kind;
+ svn_revnum_t ood_changed_rev;
+ apr_time_t ood_changed_date;
+ const char *ood_changed_author;
+};
+
+
+struct file_baton
+{
+/* Absolute local path to this file */
+ const char *local_abspath;
+
+ /* The global edit baton. */
+ struct edit_baton *edit_baton;
+
+ /* Baton for this file's parent directory. */
+ struct dir_baton *dir_baton;
+
+ /* Pool specific to this file_baton. */
+ apr_pool_t *pool;
+
+ /* Basename of this file */
+ const char *name;
+
+ /* 'svn status' shouldn't print status lines for things that are
+ added; we're only interest in asking if objects that the user
+ *already* has are up-to-date or not. Thus if this flag is set,
+ the next two will be ignored. :-) */
+ svn_boolean_t added;
+
+ /* This gets set if the file underwent a text change, which guides
+ the code that syncs up the adm dir and working copy. */
+ svn_boolean_t text_changed;
+
+ /* This gets set if the file underwent a prop change, which guides
+ the code that syncs up the adm dir and working copy. */
+ svn_boolean_t prop_changed;
+
+ /* The repository root relative path to this item in the repository. */
+ const char *repos_relpath;
+
+ /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
+ svn_node_kind_t ood_kind;
+ svn_revnum_t ood_changed_rev;
+ apr_time_t ood_changed_date;
+
+ const char *ood_changed_author;
+};
+
+
+/** Code **/
+
+/* Fill in *INFO with the information it would contain if it were
+ obtained from svn_wc__db_read_children_info. */
+static svn_error_t *
+read_info(const struct svn_wc__db_info_t **info,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct svn_wc__db_info_t *mtb = apr_pcalloc(result_pool, sizeof(*mtb));
+ const svn_checksum_t *checksum;
+ const char *original_repos_relpath;
+
+ SVN_ERR(svn_wc__db_read_info(&mtb->status, &mtb->kind,
+ &mtb->revnum, &mtb->repos_relpath,
+ &mtb->repos_root_url, &mtb->repos_uuid,
+ &mtb->changed_rev, &mtb->changed_date,
+ &mtb->changed_author, &mtb->depth,
+ &checksum, NULL, &original_repos_relpath, NULL,
+ NULL, NULL, &mtb->lock, &mtb->recorded_size,
+ &mtb->recorded_time, &mtb->changelist,
+ &mtb->conflicted, &mtb->op_root,
+ &mtb->had_props, &mtb->props_mod,
+ &mtb->have_base, &mtb->have_more_work, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wclocked(&mtb->locked, db, local_abspath, scratch_pool));
+
+ /* Maybe we have to get some shadowed lock from BASE to make our test suite
+ happy... (It might be completely unrelated, but...) */
+ if (mtb->have_base
+ && (mtb->status == svn_wc__db_status_added
+ || mtb->status == svn_wc__db_status_deleted
+ || mtb->kind == svn_node_file))
+ {
+ svn_boolean_t update_root;
+ svn_wc__db_lock_t **lock_arg = NULL;
+
+ if (mtb->status == svn_wc__db_status_added
+ || mtb->status == svn_wc__db_status_deleted)
+ lock_arg = &mtb->lock;
+
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ lock_arg, NULL, NULL, &update_root,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ mtb->file_external = (update_root && mtb->kind == svn_node_file);
+
+ if (mtb->status == svn_wc__db_status_deleted)
+ {
+ const char *moved_to_abspath;
+ const char *moved_to_op_root_abspath;
+
+ /* NOTE: we can't use op-root-ness as a condition here since a base
+ * node can be the root of a move and still not be an explicit
+ * op-root (having a working node with op_depth == pathelements).
+ *
+ * Both these (almost identical) situations showcase this:
+ * svn mv a/b bb
+ * svn del a
+ * and
+ * svn mv a aa
+ * svn mv aa/b bb
+ * In both, 'bb' is moved from 'a/b', but 'a/b' has no op_depth>0
+ * node at all, as its parent 'a' is locally deleted. */
+
+ SVN_ERR(svn_wc__db_scan_deletion(NULL,
+ &moved_to_abspath,
+ NULL,
+ &moved_to_op_root_abspath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (moved_to_abspath != NULL
+ && moved_to_op_root_abspath != NULL
+ && strcmp(moved_to_abspath, moved_to_op_root_abspath) == 0)
+ {
+ mtb->moved_to_abspath = apr_pstrdup(result_pool,
+ moved_to_abspath);
+ }
+ /* ### ^^^ THIS SUCKS. For at least two reasons:
+ * 1) We scan the node deletion and that's technically not necessary.
+ * We'd be fine to know if this is an actual root of a move.
+ * 2) From the elaborately calculated results, we backwards-guess
+ * whether this is a root.
+ * It works ok, and this code only gets called when a node is an
+ * explicit target of a 'status'. But it would be better to do this
+ * differently.
+ * We could return moved-to via svn_wc__db_base_get_info() (called
+ * just above), but as moved-to is only intended to be returned for
+ * roots of a move, that doesn't fit too well. */
+ }
+ }
+
+ /* ### svn_wc__db_read_info() could easily return the moved-here flag. But
+ * for now... (The per-dir query for recursive status is far more optimal.)
+ * Note that this actually scans around to get the full path, for a bool.
+ * This bool then gets returned, later is evaluated, and if true leads to
+ * the same paths being scanned again. We'd want to obtain this bool here as
+ * cheaply as svn_wc__db_read_children_info() does. */
+ if (mtb->status == svn_wc__db_status_added)
+ {
+ svn_wc__db_status_t status;
+
+ SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ mtb->moved_here = (status == svn_wc__db_status_moved_here);
+ mtb->incomplete = (status == svn_wc__db_status_incomplete);
+ }
+
+ mtb->has_checksum = (checksum != NULL);
+ mtb->copied = (original_repos_relpath != NULL);
+
+#ifdef HAVE_SYMLINK
+ if (mtb->kind == svn_node_file
+ && (mtb->had_props || mtb->props_mod))
+ {
+ apr_hash_t *properties;
+
+ if (mtb->props_mod)
+ SVN_ERR(svn_wc__db_read_props(&properties, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__db_read_pristine_props(&properties, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ mtb->special = (NULL != svn_hash_gets(properties, SVN_PROP_SPECIAL));
+ }
+#endif
+ *info = mtb;
+
+ return SVN_NO_ERROR;
+}
+
+/* Return *REPOS_RELPATH and *REPOS_ROOT_URL for LOCAL_ABSPATH using
+ information in INFO if available, falling back on
+ PARENT_REPOS_RELPATH and PARENT_REPOS_ROOT_URL if available, and
+ finally falling back on querying DB. */
+static svn_error_t *
+get_repos_root_url_relpath(const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const struct svn_wc__db_info_t *info,
+ const char *parent_repos_relpath,
+ const char *parent_repos_root_url,
+ const char *parent_repos_uuid,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (info->repos_relpath && info->repos_root_url)
+ {
+ *repos_relpath = apr_pstrdup(result_pool, info->repos_relpath);
+ *repos_root_url = apr_pstrdup(result_pool, info->repos_root_url);
+ *repos_uuid = apr_pstrdup(result_pool, info->repos_uuid);
+ }
+ else if (parent_repos_relpath && parent_repos_root_url)
+ {
+ *repos_relpath = svn_relpath_join(parent_repos_relpath,
+ svn_dirent_basename(local_abspath,
+ NULL),
+ result_pool);
+ *repos_root_url = apr_pstrdup(result_pool, parent_repos_root_url);
+ *repos_uuid = apr_pstrdup(result_pool, parent_repos_uuid);
+ }
+ else if (info->status == svn_wc__db_status_added)
+ {
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
+ repos_relpath, repos_root_url,
+ repos_uuid, NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ }
+ else if (info->have_base)
+ {
+ SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, repos_root_url,
+ repos_uuid,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ }
+ else
+ {
+ *repos_relpath = NULL;
+ *repos_root_url = NULL;
+ *repos_uuid = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+internal_status(svn_wc_status3_t **status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in
+ RESULT_POOL and use SCRATCH_POOL for temporary allocations.
+
+ PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the repository root
+ and repository relative path of the parent of LOCAL_ABSPATH or NULL if
+ LOCAL_ABSPATH doesn't have a versioned parent directory.
+
+ DIRENT is the local representation of LOCAL_ABSPATH in the working copy or
+ NULL if the node does not exist on disk.
+
+ If GET_ALL is FALSE, and LOCAL_ABSPATH is not locally modified, then
+ *STATUS will be set to NULL. If GET_ALL is non-zero, then *STATUS will be
+ allocated and returned no matter what. If IGNORE_TEXT_MODS is TRUE then
+ don't check for text mods, assume there are none and set and *STATUS
+ returned to reflect that assumption.
+
+ The status struct's repos_lock field will be set to REPOS_LOCK.
+*/
+static svn_error_t *
+assemble_status(svn_wc_status3_t **status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *parent_repos_root_url,
+ const char *parent_repos_relpath,
+ const char *parent_repos_uuid,
+ const struct svn_wc__db_info_t *info,
+ const svn_io_dirent2_t *dirent,
+ svn_boolean_t get_all,
+ svn_boolean_t ignore_text_mods,
+ const svn_lock_t *repos_lock,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_status3_t *stat;
+ svn_boolean_t switched_p = FALSE;
+ svn_boolean_t copied = FALSE;
+ svn_boolean_t conflicted;
+ const char *moved_from_abspath = NULL;
+ svn_filesize_t filesize = (dirent && (dirent->kind == svn_node_file))
+ ? dirent->filesize
+ : SVN_INVALID_FILESIZE;
+
+ /* Defaults for two main variables. */
+ enum svn_wc_status_kind node_status = svn_wc_status_normal;
+ enum svn_wc_status_kind text_status = svn_wc_status_normal;
+ enum svn_wc_status_kind prop_status = svn_wc_status_none;
+
+
+ if (!info)
+ SVN_ERR(read_info(&info, local_abspath, db, result_pool, scratch_pool));
+
+ if (!info->repos_relpath || !parent_repos_relpath)
+ switched_p = FALSE;
+ else
+ {
+ /* A node is switched if it doesn't have the implied repos_relpath */
+ const char *name = svn_relpath_skip_ancestor(parent_repos_relpath,
+ info->repos_relpath);
+ switched_p = !name || (strcmp(name,
+ svn_dirent_basename(local_abspath, NULL))
+ != 0);
+ }
+
+ if (info->status == svn_wc__db_status_incomplete || info->incomplete)
+ {
+ /* Highest precedence. */
+ node_status = svn_wc_status_incomplete;
+ }
+ else if (info->status == svn_wc__db_status_deleted)
+ {
+ node_status = svn_wc_status_deleted;
+
+ if (!info->have_base || info->have_more_work || info->copied)
+ copied = TRUE;
+ else if (!info->have_more_work && info->have_base)
+ copied = FALSE;
+ else
+ {
+ const char *work_del_abspath;
+
+ /* Find out details of our deletion. */
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
+ &work_del_abspath, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (work_del_abspath)
+ copied = TRUE; /* Working deletion */
+ }
+ }
+ else
+ {
+ /* Examine whether our target is missing or obstructed. To detect
+ * obstructions, we have to look at the on-disk status in DIRENT. */
+ svn_node_kind_t expected_kind = (info->kind == svn_node_dir)
+ ? svn_node_dir
+ : svn_node_file;
+
+ if (!dirent || dirent->kind != expected_kind)
+ {
+ /* A present or added node should be on disk, so it is
+ reported missing or obstructed. */
+ if (!dirent || dirent->kind == svn_node_none)
+ node_status = svn_wc_status_missing;
+ else
+ node_status = svn_wc_status_obstructed;
+ }
+ }
+
+ /* Does the node have props? */
+ if (info->status != svn_wc__db_status_deleted)
+ {
+ if (info->props_mod)
+ prop_status = svn_wc_status_modified;
+ else if (info->had_props)
+ prop_status = svn_wc_status_normal;
+ }
+
+ /* If NODE_STATUS is still normal, after the above checks, then
+ we should proceed to refine the status.
+
+ If it was changed, then the subdir is incomplete or missing/obstructed.
+ */
+ if (info->kind != svn_node_dir
+ && node_status == svn_wc_status_normal)
+ {
+ svn_boolean_t text_modified_p = FALSE;
+
+ /* Implement predecence rules: */
+
+ /* 1. Set the two main variables to "discovered" values first (M, C).
+ Together, these two stati are of lowest precedence, and C has
+ precedence over M. */
+
+ /* If the entry is a file, check for textual modifications */
+ if ((info->kind == svn_node_file
+ || info->kind == svn_node_symlink)
+#ifdef HAVE_SYMLINK
+ && (info->special == (dirent && dirent->special))
+#endif /* HAVE_SYMLINK */
+ )
+ {
+ /* If the on-disk dirent exactly matches the expected state
+ skip all operations in svn_wc__internal_text_modified_p()
+ to avoid an extra filestat for every file, which can be
+ expensive on network drives as a filestat usually can't
+ be cached there */
+ if (!info->has_checksum)
+ text_modified_p = TRUE; /* Local addition -> Modified */
+ else if (ignore_text_mods
+ ||(dirent
+ && info->recorded_size != SVN_INVALID_FILESIZE
+ && info->recorded_time != 0
+ && info->recorded_size == dirent->filesize
+ && info->recorded_time == dirent->mtime))
+ text_modified_p = FALSE;
+ else
+ {
+ svn_error_t *err;
+ err = svn_wc__internal_file_modified_p(&text_modified_p,
+ db, local_abspath,
+ FALSE, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_ACCESS_DENIED)
+ return svn_error_trace(err);
+
+ /* An access denied is very common on Windows when another
+ application has the file open. Previously we ignored
+ this error in svn_wc__text_modified_internal_p, where it
+ should have really errored. */
+ svn_error_clear(err);
+ text_modified_p = TRUE;
+ }
+ }
+ }
+#ifdef HAVE_SYMLINK
+ else if (info->special != (dirent && dirent->special))
+ node_status = svn_wc_status_obstructed;
+#endif /* HAVE_SYMLINK */
+
+ if (text_modified_p)
+ text_status = svn_wc_status_modified;
+ }
+
+ conflicted = info->conflicted;
+ if (conflicted)
+ {
+ svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
+
+ /* ### Check if the conflict was resolved by removing the marker files.
+ ### This should really be moved to the users of this API */
+ SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, &prop_conflicted,
+ &tree_conflicted,
+ db, local_abspath, scratch_pool));
+
+ if (!text_conflicted && !prop_conflicted && !tree_conflicted)
+ conflicted = FALSE;
+ }
+
+ if (node_status == svn_wc_status_normal)
+ {
+ /* 2. Possibly overwrite the text_status variable with "scheduled"
+ states from the entry (A, D, R). As a group, these states are
+ of medium precedence. They also override any C or M that may
+ be in the prop_status field at this point, although they do not
+ override a C text status.*/
+ if (info->status == svn_wc__db_status_added)
+ {
+ copied = info->copied;
+ if (!info->op_root)
+ { /* Keep status normal */ }
+ else if (!info->have_base && !info->have_more_work)
+ {
+ /* Simple addition or copy, no replacement */
+ node_status = svn_wc_status_added;
+ }
+ else
+ {
+ svn_wc__db_status_t below_working;
+ svn_boolean_t have_base, have_work;
+
+ SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
+ &below_working,
+ db, local_abspath,
+ scratch_pool));
+
+ /* If the node is not present or deleted (read: not present
+ in working), then the node is not a replacement */
+ if (below_working != svn_wc__db_status_not_present
+ && below_working != svn_wc__db_status_deleted)
+ {
+ node_status = svn_wc_status_replaced;
+ }
+ else
+ node_status = svn_wc_status_added;
+ }
+
+ /* Get moved-from info (only for potential op-roots of a move). */
+ if (info->moved_here && info->op_root)
+ {
+ svn_error_t *err;
+ err = svn_wc__db_scan_moved(&moved_from_abspath, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ /* We are no longer moved... So most likely we are somehow
+ changing the db for things like resolving conflicts. */
+
+ moved_from_abspath = NULL;
+ }
+ }
+ }
+ }
+
+
+ if (node_status == svn_wc_status_normal)
+ node_status = text_status;
+
+ if (node_status == svn_wc_status_normal
+ && prop_status != svn_wc_status_none)
+ node_status = prop_status;
+
+ /* 5. Easy out: unless we're fetching -every- entry, don't bother
+ to allocate a struct for an uninteresting entry. */
+
+ if (! get_all)
+ if (((node_status == svn_wc_status_none)
+ || (node_status == svn_wc_status_normal))
+
+ && (! switched_p)
+ && (! info->locked )
+ && (! info->lock)
+ && (! repos_lock)
+ && (! info->changelist)
+ && (! conflicted))
+ {
+ *status = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* 6. Build and return a status structure. */
+
+ stat = apr_pcalloc(result_pool, sizeof(**status));
+
+ switch (info->kind)
+ {
+ case svn_node_dir:
+ stat->kind = svn_node_dir;
+ break;
+ case svn_node_file:
+ case svn_node_symlink:
+ stat->kind = svn_node_file;
+ break;
+ case svn_node_unknown:
+ default:
+ stat->kind = svn_node_unknown;
+ }
+ stat->depth = info->depth;
+ stat->filesize = filesize;
+ stat->node_status = node_status;
+ stat->text_status = text_status;
+ stat->prop_status = prop_status;
+ stat->repos_node_status = svn_wc_status_none; /* default */
+ stat->repos_text_status = svn_wc_status_none; /* default */
+ stat->repos_prop_status = svn_wc_status_none; /* default */
+ stat->switched = switched_p;
+ stat->copied = copied;
+ stat->repos_lock = repos_lock;
+ stat->revision = info->revnum;
+ stat->changed_rev = info->changed_rev;
+ if (info->changed_author)
+ stat->changed_author = apr_pstrdup(result_pool, info->changed_author);
+ stat->changed_date = info->changed_date;
+
+ stat->ood_kind = svn_node_none;
+ stat->ood_changed_rev = SVN_INVALID_REVNUM;
+ stat->ood_changed_date = 0;
+ stat->ood_changed_author = NULL;
+
+ SVN_ERR(get_repos_root_url_relpath(&stat->repos_relpath,
+ &stat->repos_root_url,
+ &stat->repos_uuid, info,
+ parent_repos_relpath,
+ parent_repos_root_url,
+ parent_repos_uuid,
+ db, local_abspath,
+ result_pool, scratch_pool));
+
+ if (info->lock)
+ {
+ svn_lock_t *lck = svn_lock_create(result_pool);
+ lck->path = stat->repos_relpath;
+ lck->token = info->lock->token;
+ lck->owner = info->lock->owner;
+ lck->comment = info->lock->comment;
+ lck->creation_date = info->lock->date;
+ stat->lock = lck;
+ }
+ else
+ stat->lock = NULL;
+
+ stat->locked = info->locked;
+ stat->conflicted = conflicted;
+ stat->versioned = TRUE;
+ if (info->changelist)
+ stat->changelist = apr_pstrdup(result_pool, info->changelist);
+
+ stat->moved_from_abspath = moved_from_abspath;
+ if (info->moved_to_abspath)
+ stat->moved_to_abspath = apr_pstrdup(result_pool, info->moved_to_abspath);
+
+ stat->file_external = info->file_external;
+
+ *status = stat;
+
+ return SVN_NO_ERROR;
+}
+
+/* Fill in *STATUS for the unversioned path LOCAL_ABSPATH, using data
+ available in DB. Allocate *STATUS in POOL. Use SCRATCH_POOL for
+ temporary allocations.
+
+ If IS_IGNORED is non-zero and this is a non-versioned entity, set
+ the node_status to svn_wc_status_none. Otherwise set the
+ node_status to svn_wc_status_unversioned.
+ */
+static svn_error_t *
+assemble_unversioned(svn_wc_status3_t **status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_io_dirent2_t *dirent,
+ svn_boolean_t tree_conflicted,
+ svn_boolean_t is_ignored,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_status3_t *stat;
+
+ /* return a fairly blank structure. */
+ stat = apr_pcalloc(result_pool, sizeof(*stat));
+
+ /*stat->versioned = FALSE;*/
+ stat->kind = svn_node_unknown; /* not versioned */
+ stat->depth = svn_depth_unknown;
+ stat->filesize = (dirent && dirent->kind == svn_node_file)
+ ? dirent->filesize
+ : SVN_INVALID_FILESIZE;
+ stat->node_status = svn_wc_status_none;
+ stat->text_status = svn_wc_status_none;
+ stat->prop_status = svn_wc_status_none;
+ stat->repos_node_status = svn_wc_status_none;
+ stat->repos_text_status = svn_wc_status_none;
+ stat->repos_prop_status = svn_wc_status_none;
+
+ /* If this path has no entry, but IS present on disk, it's
+ unversioned. If this file is being explicitly ignored (due
+ to matching an ignore-pattern), the node_status is set to
+ svn_wc_status_ignored. Otherwise the node_status is set to
+ svn_wc_status_unversioned. */
+ if (dirent && dirent->kind != svn_node_none)
+ {
+ if (is_ignored)
+ stat->node_status = svn_wc_status_ignored;
+ else
+ stat->node_status = svn_wc_status_unversioned;
+ }
+ else if (tree_conflicted)
+ {
+ /* If this path has no entry, is NOT present on disk, and IS a
+ tree conflict victim, report it as conflicted. */
+ stat->node_status = svn_wc_status_conflicted;
+ }
+
+ stat->revision = SVN_INVALID_REVNUM;
+ stat->changed_rev = SVN_INVALID_REVNUM;
+ stat->ood_changed_rev = SVN_INVALID_REVNUM;
+ stat->ood_kind = svn_node_none;
+
+ /* For the case of an incoming delete to a locally deleted path during
+ an update, we get a tree conflict. */
+ stat->conflicted = tree_conflicted;
+ stat->changelist = NULL;
+
+ *status = stat;
+ return SVN_NO_ERROR;
+}
+
+
+/* Given an ENTRY object representing PATH, build a status structure
+ and pass it off to the STATUS_FUNC/STATUS_BATON. All other
+ arguments are the same as those passed to assemble_status(). */
+static svn_error_t *
+send_status_structure(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ const char *parent_repos_root_url,
+ const char *parent_repos_relpath,
+ const char *parent_repos_uuid,
+ const struct svn_wc__db_info_t *info,
+ const svn_io_dirent2_t *dirent,
+ svn_boolean_t get_all,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_status3_t *statstruct;
+ const svn_lock_t *repos_lock = NULL;
+
+ /* Check for a repository lock. */
+ if (wb->repos_locks)
+ {
+ const char *repos_relpath, *repos_root_url, *repos_uuid;
+
+ SVN_ERR(get_repos_root_url_relpath(&repos_relpath, &repos_root_url,
+ &repos_uuid,
+ info, parent_repos_relpath,
+ parent_repos_root_url,
+ parent_repos_uuid,
+ wb->db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (repos_relpath)
+ {
+ /* repos_lock still uses the deprecated filesystem absolute path
+ format */
+ repos_lock = svn_hash_gets(wb->repos_locks,
+ svn_fspath__join("/", repos_relpath,
+ scratch_pool));
+ }
+ }
+
+ SVN_ERR(assemble_status(&statstruct, wb->db, local_abspath,
+ parent_repos_root_url, parent_repos_relpath,
+ parent_repos_uuid,
+ info, dirent, get_all, wb->ignore_text_mods,
+ repos_lock, scratch_pool, scratch_pool));
+
+ if (statstruct && status_func)
+ return svn_error_trace((*status_func)(status_baton, local_abspath,
+ statstruct, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Store in *PATTERNS a list of ignores collected from svn:ignore properties
+ on LOCAL_ABSPATH and svn:global-ignores on LOCAL_ABSPATH and its
+ repository ancestors (as cached in the working copy), including the default
+ ignores passed in as IGNORES.
+
+ Upon return, *PATTERNS will contain zero or more (const char *)
+ patterns from the value of the SVN_PROP_IGNORE property set on
+ the working directory path.
+
+ IGNORES is a list of patterns to include; typically this will
+ be the default ignores as, for example, specified in a config file.
+
+ DB, LOCAL_ABSPATH is used to access the working copy.
+
+ Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL.
+
+ None of the arguments may be NULL.
+*/
+static svn_error_t *
+collect_ignore_patterns(apr_array_header_t **patterns,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_array_header_t *ignores,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_hash_t *props;
+ apr_array_header_t *inherited_props;
+ svn_error_t *err;
+
+ /* ### assert we are passed a directory? */
+
+ *patterns = apr_array_make(result_pool, 1, sizeof(const char *));
+
+ /* Copy default ignores into the local PATTERNS array. */
+ for (i = 0; i < ignores->nelts; i++)
+ {
+ const char *ignore = APR_ARRAY_IDX(ignores, i, const char *);
+ APR_ARRAY_PUSH(*patterns, const char *) = apr_pstrdup(result_pool,
+ ignore);
+ }
+
+ err = svn_wc__db_read_inherited_props(&inherited_props, &props,
+ db, local_abspath,
+ SVN_PROP_INHERITABLE_IGNORES,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ if (props)
+ {
+ const svn_string_t *value;
+
+ value = svn_hash_gets(props, SVN_PROP_IGNORE);
+ if (value)
+ svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
+ result_pool);
+
+ value = svn_hash_gets(props, SVN_PROP_INHERITABLE_IGNORES);
+ if (value)
+ svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
+ result_pool);
+ }
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
+ inherited_props, i, svn_prop_inherited_item_t *);
+ const svn_string_t *value;
+
+ value = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES);
+
+ if (value)
+ svn_cstring_split_append(*patterns, value->data,
+ "\n\r", FALSE, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Compare LOCAL_ABSPATH with items in the EXTERNALS hash to see if
+ LOCAL_ABSPATH is the drop location for, or an intermediate directory
+ of the drop location for, an externals definition. Use SCRATCH_POOL
+ for scratchwork. */
+static svn_boolean_t
+is_external_path(apr_hash_t *externals,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ /* First try: does the path exist as a key in the hash? */
+ if (svn_hash_gets(externals, local_abspath))
+ return TRUE;
+
+ /* Failing that, we need to check if any external is a child of
+ LOCAL_ABSPATH. */
+ for (hi = apr_hash_first(scratch_pool, externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *external_abspath = svn__apr_hash_index_key(hi);
+
+ if (svn_dirent_is_child(local_abspath, external_abspath, NULL))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/* Assuming that LOCAL_ABSPATH is unversioned, send a status structure
+ for it through STATUS_FUNC/STATUS_BATON unless this path is being
+ ignored. This function should never be called on a versioned entry.
+
+ LOCAL_ABSPATH is the path to the unversioned file whose status is being
+ requested. PATH_KIND is the node kind of NAME as determined by the
+ caller. PATH_SPECIAL is the special status of the path, also determined
+ by the caller.
+ PATTERNS points to a list of filename patterns which are marked as ignored.
+ None of these parameter may be NULL.
+
+ If NO_IGNORE is TRUE, the item will be added regardless of
+ whether it is ignored; otherwise we will only add the item if it
+ does not match any of the patterns in PATTERN or INHERITED_IGNORES.
+
+ Allocate everything in POOL.
+*/
+static svn_error_t *
+send_unversioned_item(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ const svn_io_dirent2_t *dirent,
+ svn_boolean_t tree_conflicted,
+ const apr_array_header_t *patterns,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_ignored;
+ svn_boolean_t is_external;
+ svn_wc_status3_t *status;
+ const char *base_name = svn_dirent_basename(local_abspath, NULL);
+
+ is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool);
+ SVN_ERR(assemble_unversioned(&status,
+ wb->db, local_abspath,
+ dirent, tree_conflicted,
+ is_ignored,
+ scratch_pool, scratch_pool));
+
+ is_external = is_external_path(wb->externals, local_abspath, scratch_pool);
+ if (is_external)
+ status->node_status = svn_wc_status_external;
+
+ /* We can have a tree conflict on an unversioned path, i.e. an incoming
+ * delete on a locally deleted path during an update. Don't ever ignore
+ * those! */
+ if (status->conflicted)
+ is_ignored = FALSE;
+
+ /* If we aren't ignoring it, or if it's an externals path, pass this
+ entry to the status func. */
+ if (no_ignore
+ || !is_ignored
+ || is_external)
+ return svn_error_trace((*status_func)(status_baton, local_abspath,
+ status, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_dir_status(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ svn_boolean_t skip_this_dir,
+ const char *parent_repos_root_url,
+ const char *parent_repos_relpath,
+ const char *parent_repos_uuid,
+ const struct svn_wc__db_info_t *dir_info,
+ const svn_io_dirent2_t *dirent,
+ const apr_array_header_t *ignore_patterns,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Send out a status structure according to the information gathered on one
+ * child node. (Basically this function is the guts of the loop in
+ * get_dir_status() and of get_child_status().)
+ *
+ * Send a status structure of LOCAL_ABSPATH. PARENT_ABSPATH must be the
+ * dirname of LOCAL_ABSPATH.
+ *
+ * INFO should reflect the information on LOCAL_ABSPATH; LOCAL_ABSPATH must
+ * be an unversioned file or dir, or a versioned file. For versioned
+ * directories use get_dir_status() instead.
+ *
+ * INFO may be NULL for an unversioned node. If such node has a tree conflict,
+ * UNVERSIONED_TREE_CONFLICTED may be set to TRUE. If INFO is non-NULL,
+ * UNVERSIONED_TREE_CONFLICTED is ignored.
+ *
+ * DIRENT should reflect LOCAL_ABSPATH's dirent information.
+ *
+ * DIR_REPOS_* should reflect LOCAL_ABSPATH's parent URL, i.e. LOCAL_ABSPATH's
+ * URL treated with svn_uri_dirname(). ### TODO verify this (externals)
+ *
+ * If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in this
+ * call, then *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t*
+ * containing all ignore patterns, as returned by collect_ignore_patterns() on
+ * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed
+ * non-NULL, it is assumed it already holds those results.
+ * This speeds up repeated calls with the same PARENT_ABSPATH.
+ *
+ * *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other
+ * allocations are made in SCRATCH_POOL.
+ *
+ * The remaining parameters correspond to get_dir_status(). */
+static svn_error_t *
+one_child_status(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ const char *parent_abspath,
+ const struct svn_wc__db_info_t *info,
+ const svn_io_dirent2_t *dirent,
+ const char *dir_repos_root_url,
+ const char *dir_repos_relpath,
+ const char *dir_repos_uuid,
+ svn_boolean_t unversioned_tree_conflicted,
+ apr_array_header_t **collected_ignore_patterns,
+ const apr_array_header_t *ignore_patterns,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t conflicted = info ? info->conflicted
+ : unversioned_tree_conflicted;
+
+ if (info
+ && info->status != svn_wc__db_status_not_present
+ && info->status != svn_wc__db_status_excluded
+ && info->status != svn_wc__db_status_server_excluded
+ && !(info->kind == svn_node_unknown
+ && info->status == svn_wc__db_status_normal))
+ {
+ if (depth == svn_depth_files
+ && info->kind == svn_node_dir)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(send_status_structure(wb, local_abspath,
+ dir_repos_root_url,
+ dir_repos_relpath,
+ dir_repos_uuid,
+ info, dirent, get_all,
+ status_func, status_baton,
+ scratch_pool));
+
+ /* Descend in subdirectories. */
+ if (depth == svn_depth_infinity
+ && info->kind == svn_node_dir)
+ {
+ SVN_ERR(get_dir_status(wb, local_abspath, TRUE,
+ dir_repos_root_url, dir_repos_relpath,
+ dir_repos_uuid, info,
+ dirent, ignore_patterns,
+ svn_depth_infinity, get_all,
+ no_ignore,
+ status_func, status_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* If conflicted, fall right through to unversioned.
+ * With depth_files, show all conflicts, even if their report is only
+ * about directories. A tree conflict may actually report two different
+ * kinds, so it's not so easy to define what depth=files means. We could go
+ * look up the kinds in the conflict ... just show all. */
+ if (! conflicted)
+ {
+ /* Selected node, but not found */
+ if (dirent == NULL)
+ return SVN_NO_ERROR;
+
+ if (depth == svn_depth_files && dirent->kind == svn_node_dir)
+ return SVN_NO_ERROR;
+
+ if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
+ scratch_pool))
+ return SVN_NO_ERROR;
+ }
+
+ /* The node exists on disk but there is no versioned information about it,
+ * or it doesn't exist but is a tree conflicted path or should be
+ * reported not-present. */
+
+ /* Why pass ignore patterns on a tree conflicted node, even if it should
+ * always show up in clients' status reports anyway? Because the calling
+ * client decides whether to ignore, and thus this flag needs to be
+ * determined. For example, in 'svn status', plain unversioned nodes show
+ * as '? C', where ignored ones show as 'I C'. */
+
+ if (ignore_patterns && ! *collected_ignore_patterns)
+ SVN_ERR(collect_ignore_patterns(collected_ignore_patterns,
+ wb->db, parent_abspath, ignore_patterns,
+ result_pool, scratch_pool));
+
+ SVN_ERR(send_unversioned_item(wb,
+ local_abspath,
+ dirent,
+ conflicted,
+ *collected_ignore_patterns,
+ no_ignore,
+ status_func, status_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and
+ for all its child nodes (according to DEPTH) through STATUS_FUNC /
+ STATUS_BATON.
+
+ If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported.
+ All subdirs reached by recursion will be reported regardless of this
+ parameter's value.
+
+ PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's
+ URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid
+ retrieving them again. Otherwise they must be NULL.
+
+ DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving
+ it again. Otherwise it must be NULL.
+
+ DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported,
+ so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL.
+
+ Other arguments are the same as those passed to
+ svn_wc_get_status_editor5(). */
+static svn_error_t *
+get_dir_status(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ svn_boolean_t skip_this_dir,
+ const char *parent_repos_root_url,
+ const char *parent_repos_relpath,
+ const char *parent_repos_uuid,
+ const struct svn_wc__db_info_t *dir_info,
+ const svn_io_dirent2_t *dirent,
+ const apr_array_header_t *ignore_patterns,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *dir_repos_root_url;
+ const char *dir_repos_relpath;
+ const char *dir_repos_uuid;
+ apr_hash_t *dirents, *nodes, *conflicts, *all_children;
+ apr_array_header_t *sorted_children;
+ apr_array_header_t *collected_ignore_patterns = NULL;
+ apr_pool_t *iterpool;
+ svn_error_t *err;
+ int i;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_infinity;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ err = svn_io_get_dirents3(&dirents, local_abspath, FALSE, scratch_pool,
+ iterpool);
+ if (err
+ && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ {
+ svn_error_clear(err);
+ dirents = apr_hash_make(scratch_pool);
+ }
+ else
+ SVN_ERR(err);
+
+ if (!dir_info)
+ SVN_ERR(read_info(&dir_info, local_abspath, wb->db,
+ scratch_pool, iterpool));
+
+ SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
+ &dir_repos_uuid, dir_info,
+ parent_repos_relpath,
+ parent_repos_root_url, parent_repos_uuid,
+ wb->db, local_abspath,
+ scratch_pool, iterpool));
+
+ /* Create a hash containing all children. The source hashes
+ don't all map the same types, but only the keys of the result
+ hash are subsequently used. */
+ SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
+ wb->db, local_abspath,
+ scratch_pool, iterpool));
+
+ all_children = apr_hash_overlay(scratch_pool, nodes, dirents);
+ if (apr_hash_count(conflicts) > 0)
+ all_children = apr_hash_overlay(scratch_pool, conflicts, all_children);
+
+ /* Handle "this-dir" first. */
+ if (! skip_this_dir)
+ {
+ /* This code is not conditional on HAVE_SYMLINK as some systems that do
+ not allow creating symlinks (!HAVE_SYMLINK) can still encounter
+ symlinks (or in case of Windows also 'Junctions') created by other
+ methods.
+
+ Without this block a working copy in the root of a junction is
+ reported as an obstruction, because the junction itself is reported as
+ special.
+
+ Systems that have no symlink support at all, would always see
+ dirent->special as FALSE, so even there enabling this code shouldn't
+ produce problems.
+ */
+ if (dirent->special)
+ {
+ svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, iterpool);
+
+ /* We're being pointed to "this-dir" via a symlink.
+ * Get the real node kind and pretend the path is not a symlink.
+ * This prevents send_status_structure() from treating this-dir
+ * as a directory obstructed by a file. */
+ SVN_ERR(svn_io_check_resolved_path(local_abspath,
+ &this_dirent->kind, iterpool));
+ this_dirent->special = FALSE;
+ SVN_ERR(send_status_structure(wb, local_abspath,
+ parent_repos_root_url,
+ parent_repos_relpath,
+ parent_repos_uuid,
+ dir_info, this_dirent, get_all,
+ status_func, status_baton,
+ iterpool));
+ }
+ else
+ SVN_ERR(send_status_structure(wb, local_abspath,
+ parent_repos_root_url,
+ parent_repos_relpath,
+ parent_repos_uuid,
+ dir_info, dirent, get_all,
+ status_func, status_baton,
+ iterpool));
+ }
+
+ /* If the requested depth is empty, we only need status on this-dir. */
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* Walk all the children of this directory. */
+ sorted_children = svn_sort__hash(all_children,
+ svn_sort_compare_items_lexically,
+ scratch_pool);
+ for (i = 0; i < sorted_children->nelts; i++)
+ {
+ const void *key;
+ apr_ssize_t klen;
+ svn_sort__item_t item;
+ const char *child_abspath;
+ svn_io_dirent2_t *child_dirent;
+ const struct svn_wc__db_info_t *child_info;
+
+ svn_pool_clear(iterpool);
+
+ item = APR_ARRAY_IDX(sorted_children, i, svn_sort__item_t);
+ key = item.key;
+ klen = item.klen;
+
+ child_abspath = svn_dirent_join(local_abspath, key, iterpool);
+ child_dirent = apr_hash_get(dirents, key, klen);
+ child_info = apr_hash_get(nodes, key, klen);
+
+ SVN_ERR(one_child_status(wb,
+ child_abspath,
+ local_abspath,
+ child_info,
+ child_dirent,
+ dir_repos_root_url,
+ dir_repos_relpath,
+ dir_repos_uuid,
+ apr_hash_get(conflicts, key, klen) != NULL,
+ &collected_ignore_patterns,
+ ignore_patterns,
+ depth,
+ get_all,
+ no_ignore,
+ status_func,
+ status_baton,
+ cancel_func,
+ cancel_baton,
+ scratch_pool,
+ iterpool));
+ }
+
+ /* Destroy our subpools. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Send an svn_wc_status3_t * structure for the versioned file, or for the
+ * unversioned file or directory, LOCAL_ABSPATH, which is not ignored (an
+ * explicit target). Does not recurse.
+ *
+ * INFO should reflect LOCAL_ABSPATH's information, but should be NULL for
+ * unversioned nodes. An unversioned and tree-conflicted node however should
+ * pass a non-NULL INFO as returned by read_info() (INFO->CONFLICTED = TRUE).
+ *
+ * DIRENT should reflect LOCAL_ABSPATH.
+ *
+ * All allocations made in SCRATCH_POOL.
+ *
+ * The remaining parameters correspond to get_dir_status(). */
+static svn_error_t *
+get_child_status(const struct walk_status_baton *wb,
+ const char *local_abspath,
+ const struct svn_wc__db_info_t *info,
+ const svn_io_dirent2_t *dirent,
+ const apr_array_header_t *ignore_patterns,
+ svn_boolean_t get_all,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *dir_repos_root_url;
+ const char *dir_repos_relpath;
+ const char *dir_repos_uuid;
+ const struct svn_wc__db_info_t *dir_info;
+ apr_array_header_t *collected_ignore_patterns = NULL;
+ const char *parent_abspath = svn_dirent_dirname(local_abspath,
+ scratch_pool);
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ if (dirent->kind == svn_node_none)
+ dirent = NULL;
+
+ SVN_ERR(read_info(&dir_info, parent_abspath, wb->db,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
+ &dir_repos_uuid, dir_info,
+ NULL, NULL, NULL,
+ wb->db, parent_abspath,
+ scratch_pool, scratch_pool));
+
+ /* An unversioned node with a tree conflict will see an INFO != NULL here,
+ * in which case the FALSE passed for UNVERSIONED_TREE_CONFLICTED has no
+ * effect and INFO->CONFLICTED counts.
+ * ### Maybe svn_wc__db_read_children_info() and read_info() should be more
+ * ### alike? */
+ SVN_ERR(one_child_status(wb,
+ local_abspath,
+ parent_abspath,
+ info,
+ dirent,
+ dir_repos_root_url,
+ dir_repos_relpath,
+ dir_repos_uuid,
+ FALSE, /* unversioned_tree_conflicted */
+ &collected_ignore_patterns,
+ ignore_patterns,
+ svn_depth_empty,
+ get_all,
+ TRUE, /* no_ignore. This is an explicit target. */
+ status_func,
+ status_baton,
+ cancel_func,
+ cancel_baton,
+ scratch_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Helpers ***/
+
+/* A faux status callback function for stashing STATUS item in an hash
+ (which is the BATON), keyed on PATH. This implements the
+ svn_wc_status_func4_t interface. */
+static svn_error_t *
+hash_stash(void *baton,
+ const char *path,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *stat_hash = baton;
+ apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash);
+ assert(! svn_hash_gets(stat_hash, path));
+ svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path),
+ svn_wc_dup_status3(status, hash_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether
+ baton is a struct *dir_baton or struct *file_baton. If the value doesn't
+ yet exist, and the REPOS_NODE_STATUS indicates that this is an addition,
+ create a new status struct using the hash's pool.
+
+ If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out
+ of date (ood) information we want to set in BATON. This is necessary
+ because this function tweaks the status of out-of-date directories
+ (BATON == THIS_DIR_BATON) and out-of-date directories' parents
+ (BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON
+ contains the ood info we want to bubble up to ancestor directories so these
+ accurately reflect the fact they have an ood descendant.
+
+ Merge REPOS_NODE_STATUS, REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the
+ status structure's "network" fields.
+
+ Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it
+ is ignored:
+
+ If REPOS_NODE_STATUS is svn_wc_status_deleted then DELETED_REV is
+ optionally the revision path was deleted, in all other cases it must
+ be set to SVN_INVALID_REVNUM. If DELETED_REV is not
+ SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted,
+ then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON.
+ If DELETED_REV is SVN_INVALID_REVNUM and REPOS_NODE_STATUS is
+ svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's
+ ood_last_cmt_rev value - see comment below.
+
+ If a new struct was added, set the repos_lock to REPOS_LOCK. */
+static svn_error_t *
+tweak_statushash(void *baton,
+ void *this_dir_baton,
+ svn_boolean_t is_dir_baton,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ enum svn_wc_status_kind repos_node_status,
+ enum svn_wc_status_kind repos_text_status,
+ enum svn_wc_status_kind repos_prop_status,
+ svn_revnum_t deleted_rev,
+ const svn_lock_t *repos_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_status3_t *statstruct;
+ apr_pool_t *pool;
+ apr_hash_t *statushash;
+
+ if (is_dir_baton)
+ statushash = ((struct dir_baton *) baton)->statii;
+ else
+ statushash = ((struct file_baton *) baton)->dir_baton->statii;
+ pool = apr_hash_pool_get(statushash);
+
+ /* Is PATH already a hash-key? */
+ statstruct = svn_hash_gets(statushash, local_abspath);
+
+ /* If not, make it so. */
+ if (! statstruct)
+ {
+ /* If this item isn't being added, then we're most likely
+ dealing with a non-recursive (or at least partially
+ non-recursive) working copy. Due to bugs in how the client
+ reports the state of non-recursive working copies, the
+ repository can send back responses about paths that don't
+ even exist locally. Our best course here is just to ignore
+ those responses. After all, if the client had reported
+ correctly in the first, that path would either be mentioned
+ as an 'add' or not mentioned at all, depending on how we
+ eventually fix the bugs in non-recursivity. See issue
+ #2122 for details. */
+ if (repos_node_status != svn_wc_status_added)
+ return SVN_NO_ERROR;
+
+ /* Use the public API to get a statstruct, and put it into the hash. */
+ SVN_ERR(internal_status(&statstruct, db, local_abspath, pool,
+ scratch_pool));
+ statstruct->repos_lock = repos_lock;
+ svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct);
+ }
+
+ /* Merge a repos "delete" + "add" into a single "replace". */
+ if ((repos_node_status == svn_wc_status_added)
+ && (statstruct->repos_node_status == svn_wc_status_deleted))
+ repos_node_status = svn_wc_status_replaced;
+
+ /* Tweak the structure's repos fields. */
+ if (repos_node_status)
+ statstruct->repos_node_status = repos_node_status;
+ if (repos_text_status)
+ statstruct->repos_text_status = repos_text_status;
+ if (repos_prop_status)
+ statstruct->repos_prop_status = repos_prop_status;
+
+ /* Copy out-of-date info. */
+ if (is_dir_baton)
+ {
+ struct dir_baton *b = this_dir_baton;
+
+ if (!statstruct->repos_relpath && b->repos_relpath)
+ {
+ if (statstruct->repos_node_status == svn_wc_status_deleted)
+ {
+ /* When deleting PATH, BATON is for PATH's parent,
+ so we must construct PATH's real statstruct->url. */
+ statstruct->repos_relpath =
+ svn_relpath_join(b->repos_relpath,
+ svn_dirent_basename(local_abspath,
+ NULL),
+ pool);
+ }
+ else
+ statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
+
+ statstruct->repos_root_url =
+ b->edit_baton->anchor_status->repos_root_url;
+ statstruct->repos_uuid =
+ b->edit_baton->anchor_status->repos_uuid;
+ }
+
+ /* The last committed date, and author for deleted items
+ isn't available. */
+ if (statstruct->repos_node_status == svn_wc_status_deleted)
+ {
+ statstruct->ood_kind = statstruct->kind;
+
+ /* Pre 1.5 servers don't provide the revision a path was deleted.
+ So we punt and use the last committed revision of the path's
+ parent, which has some chance of being correct. At worse it
+ is a higher revision than the path was deleted, but this is
+ better than nothing... */
+ if (deleted_rev == SVN_INVALID_REVNUM)
+ statstruct->ood_changed_rev =
+ ((struct dir_baton *) baton)->ood_changed_rev;
+ else
+ statstruct->ood_changed_rev = deleted_rev;
+ }
+ else
+ {
+ statstruct->ood_kind = b->ood_kind;
+ statstruct->ood_changed_rev = b->ood_changed_rev;
+ statstruct->ood_changed_date = b->ood_changed_date;
+ if (b->ood_changed_author)
+ statstruct->ood_changed_author =
+ apr_pstrdup(pool, b->ood_changed_author);
+ }
+
+ }
+ else
+ {
+ struct file_baton *b = baton;
+ statstruct->ood_changed_rev = b->ood_changed_rev;
+ statstruct->ood_changed_date = b->ood_changed_date;
+ if (!statstruct->repos_relpath && b->repos_relpath)
+ {
+ statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
+ statstruct->repos_root_url =
+ b->edit_baton->anchor_status->repos_root_url;
+ statstruct->repos_uuid =
+ b->edit_baton->anchor_status->repos_uuid;
+ }
+ statstruct->ood_kind = b->ood_kind;
+ if (b->ood_changed_author)
+ statstruct->ood_changed_author =
+ apr_pstrdup(pool, b->ood_changed_author);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Returns the URL for DB */
+static const char *
+find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool)
+{
+ /* If we have no name, we're the root, return the anchor URL. */
+ if (! db->name)
+ return db->edit_baton->anchor_status->repos_relpath;
+ else
+ {
+ const char *repos_relpath;
+ struct dir_baton *pb = db->parent_baton;
+ const svn_wc_status3_t *status = svn_hash_gets(pb->statii,
+ db->local_abspath);
+ /* Note that status->repos_relpath could be NULL in the case of a missing
+ * directory, which means we need to recurse up another level to get
+ * a useful relpath. */
+ if (status && status->repos_relpath)
+ return status->repos_relpath;
+
+ repos_relpath = find_dir_repos_relpath(pb, pool);
+ return svn_relpath_join(repos_relpath, db->name, pool);
+ }
+}
+
+
+
+/* Create a new dir_baton for subdir PATH. */
+static svn_error_t *
+make_dir_baton(void **dir_baton,
+ const char *path,
+ struct edit_baton *edit_baton,
+ struct dir_baton *parent_baton,
+ apr_pool_t *result_pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *d;
+ const char *local_abspath;
+ const svn_wc_status3_t *status_in_parent;
+ apr_pool_t *dir_pool;
+
+ if (parent_baton)
+ dir_pool = svn_pool_create(parent_baton->pool);
+ else
+ dir_pool = svn_pool_create(result_pool);
+
+ d = apr_pcalloc(dir_pool, sizeof(*d));
+
+ SVN_ERR_ASSERT(path || (! pb));
+
+ /* Construct the absolute path of this directory. */
+ if (pb)
+ local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
+ else
+ local_abspath = eb->anchor_abspath;
+
+ /* Finish populating the baton members. */
+ d->pool = dir_pool;
+ d->local_abspath = local_abspath;
+ d->name = path ? svn_dirent_basename(path, dir_pool) : NULL;
+ d->edit_baton = edit_baton;
+ d->parent_baton = parent_baton;
+ d->statii = apr_hash_make(dir_pool);
+ d->ood_changed_rev = SVN_INVALID_REVNUM;
+ d->ood_changed_date = 0;
+ d->repos_relpath = find_dir_repos_relpath(d, dir_pool);
+ d->ood_kind = svn_node_dir;
+ d->ood_changed_author = NULL;
+
+ if (pb)
+ {
+ if (pb->excluded)
+ d->excluded = TRUE;
+ else if (pb->depth == svn_depth_immediates)
+ d->depth = svn_depth_empty;
+ else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty)
+ d->excluded = TRUE;
+ else if (pb->depth == svn_depth_unknown)
+ /* This is only tentative, it can be overridden from d's entry
+ later. */
+ d->depth = svn_depth_unknown;
+ else
+ d->depth = svn_depth_infinity;
+ }
+ else
+ {
+ d->depth = eb->default_depth;
+ }
+
+ /* Get the status for this path's children. Of course, we only want
+ to do this if the path is versioned as a directory. */
+ if (pb)
+ status_in_parent = svn_hash_gets(pb->statii, d->local_abspath);
+ else
+ status_in_parent = eb->anchor_status;
+
+ if (status_in_parent
+ && status_in_parent->versioned
+ && (status_in_parent->kind == svn_node_dir)
+ && (! d->excluded)
+ && (d->depth == svn_depth_unknown
+ || d->depth == svn_depth_infinity
+ || d->depth == svn_depth_files
+ || d->depth == svn_depth_immediates)
+ )
+ {
+ const svn_wc_status3_t *this_dir_status;
+ const apr_array_header_t *ignores = eb->ignores;
+
+ SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE,
+ status_in_parent->repos_root_url,
+ NULL /*parent_repos_relpath*/,
+ status_in_parent->repos_uuid,
+ NULL,
+ NULL /* dirent */, ignores,
+ d->depth == svn_depth_files
+ ? svn_depth_files
+ : svn_depth_immediates,
+ TRUE, TRUE,
+ hash_stash, d->statii,
+ eb->cancel_func, eb->cancel_baton,
+ dir_pool));
+
+ /* If we found a depth here, it should govern. */
+ this_dir_status = svn_hash_gets(d->statii, d->local_abspath);
+ if (this_dir_status && this_dir_status->versioned
+ && (d->depth == svn_depth_unknown
+ || d->depth > status_in_parent->depth))
+ {
+ d->depth = this_dir_status->depth;
+ }
+ }
+
+ *dir_baton = d;
+ return SVN_NO_ERROR;
+}
+
+
+/* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool.
+ NAME is just one component, not a path. */
+static struct file_baton *
+make_file_baton(struct dir_baton *parent_dir_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_dir_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
+
+ /* Finish populating the baton members. */
+ f->local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
+ f->name = svn_dirent_basename(f->local_abspath, NULL);
+ f->pool = pool;
+ f->dir_baton = pb;
+ f->edit_baton = eb;
+ f->ood_changed_rev = SVN_INVALID_REVNUM;
+ f->ood_changed_date = 0;
+ f->repos_relpath = svn_relpath_join(find_dir_repos_relpath(pb, pool),
+ f->name, pool);
+ f->ood_kind = svn_node_file;
+ f->ood_changed_author = NULL;
+ return f;
+}
+
+
+/**
+ * Return a boolean answer to the question "Is @a status something that
+ * should be reported?". @a no_ignore and @a get_all are the same as
+ * svn_wc_get_status_editor4().
+ */
+static svn_boolean_t
+is_sendable_status(const svn_wc_status3_t *status,
+ svn_boolean_t no_ignore,
+ svn_boolean_t get_all)
+{
+ /* If the repository status was touched at all, it's interesting. */
+ if (status->repos_node_status != svn_wc_status_none)
+ return TRUE;
+
+ /* If there is a lock in the repository, send it. */
+ if (status->repos_lock)
+ return TRUE;
+
+ if (status->conflicted)
+ return TRUE;
+
+ /* If the item is ignored, and we don't want ignores, skip it. */
+ if ((status->node_status == svn_wc_status_ignored) && (! no_ignore))
+ return FALSE;
+
+ /* If we want everything, we obviously want this single-item subset
+ of everything. */
+ if (get_all)
+ return TRUE;
+
+ /* If the item is unversioned, display it. */
+ if (status->node_status == svn_wc_status_unversioned)
+ return TRUE;
+
+ /* If the text, property or tree state is interesting, send it. */
+ if ((status->node_status != svn_wc_status_none
+ && (status->node_status != svn_wc_status_normal)))
+ return TRUE;
+
+ /* If it's switched, send it. */
+ if (status->switched)
+ return TRUE;
+
+ /* If there is a lock token, send it. */
+ if (status->versioned && status->lock)
+ return TRUE;
+
+ /* If the entry is associated with a changelist, send it. */
+ if (status->changelist)
+ return TRUE;
+
+ /* Otherwise, don't send it. */
+ return FALSE;
+}
+
+
+/* Baton for mark_status. */
+struct status_baton
+{
+ svn_wc_status_func4_t real_status_func; /* real status function */
+ void *real_status_baton; /* real status baton */
+};
+
+/* A status callback function which wraps the *real* status
+ function/baton. It simply sets the "repos_node_status" field of the
+ STATUS to svn_wc_status_deleted and passes it off to the real
+ status func/baton. Implements svn_wc_status_func4_t */
+static svn_error_t *
+mark_deleted(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct status_baton *sb = baton;
+ svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
+ new_status->repos_node_status = svn_wc_status_deleted;
+ return sb->real_status_func(sb->real_status_baton, local_abspath,
+ new_status, scratch_pool);
+}
+
+
+/* Handle a directory's STATII hash. EB is the edit baton. DIR_PATH
+ and DIR_ENTRY are the on-disk path and entry, respectively, for the
+ directory itself. Descend into subdirectories according to DEPTH.
+ Also, if DIR_WAS_DELETED is set, each status that is reported
+ through this function will have its repos_text_status field showing
+ a deletion. Use POOL for all allocations. */
+static svn_error_t *
+handle_statii(struct edit_baton *eb,
+ const char *dir_repos_root_url,
+ const char *dir_repos_relpath,
+ const char *dir_repos_uuid,
+ apr_hash_t *statii,
+ svn_boolean_t dir_was_deleted,
+ svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ const apr_array_header_t *ignores = eb->ignores;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_wc_status_func4_t status_func = eb->status_func;
+ void *status_baton = eb->status_baton;
+ struct status_baton sb;
+
+ if (dir_was_deleted)
+ {
+ sb.real_status_func = eb->status_func;
+ sb.real_status_baton = eb->status_baton;
+ status_func = mark_deleted;
+ status_baton = &sb;
+ }
+
+ /* Loop over all the statii still in our hash, handling each one. */
+ for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi))
+ {
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+ svn_wc_status3_t *status = svn__apr_hash_index_val(hi);
+
+ /* Clear the subpool. */
+ svn_pool_clear(iterpool);
+
+ /* Now, handle the status. We don't recurse for svn_depth_immediates
+ because we already have the subdirectories' statii. */
+ if (status->versioned && status->kind == svn_node_dir
+ && (depth == svn_depth_unknown
+ || depth == svn_depth_infinity))
+ {
+ SVN_ERR(get_dir_status(&eb->wb,
+ local_abspath, TRUE,
+ dir_repos_root_url, dir_repos_relpath,
+ dir_repos_uuid,
+ NULL,
+ NULL /* dirent */,
+ ignores, depth, eb->get_all, eb->no_ignore,
+ status_func, status_baton,
+ eb->cancel_func, eb->cancel_baton,
+ iterpool));
+ }
+ if (dir_was_deleted)
+ status->repos_node_status = svn_wc_status_deleted;
+ if (is_sendable_status(status, eb->no_ignore, eb->get_all))
+ SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, status,
+ iterpool));
+ }
+
+ /* Destroy the subpool. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/*----------------------------------------------------------------------*/
+
+/*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+ *(eb->target_revision) = target_revision;
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **dir_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ eb->root_opened = TRUE;
+ return make_dir_baton(dir_baton, NULL, eb, NULL, pool);
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = parent_baton;
+ struct edit_baton *eb = db->edit_baton;
+ const char *local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
+
+ /* Note: when something is deleted, it's okay to tweak the
+ statushash immediately. No need to wait until close_file or
+ close_dir, because there's no risk of having to honor the 'added'
+ flag. We already know this item exists in the working copy. */
+ SVN_ERR(tweak_statushash(db, db, TRUE, eb->db,
+ local_abspath,
+ svn_wc_status_deleted, 0, 0, revision, NULL, pool));
+
+ /* Mark the parent dir -- it lost an entry (unless that parent dir
+ is the root node and we're not supposed to report on the root
+ node). */
+ if (db->parent_baton && (! *eb->target_basename))
+ SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,eb->db,
+ db->local_abspath,
+ svn_wc_status_modified, svn_wc_status_modified,
+ 0, SVN_INVALID_REVNUM, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *new_db;
+
+ SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool));
+
+ /* Make this dir as added. */
+ new_db = *child_baton;
+ new_db->added = TRUE;
+
+ /* Mark the parent as changed; it gained an entry. */
+ pb->text_changed = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool);
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ if (svn_wc_is_normal_prop(name))
+ db->prop_changed = TRUE;
+
+ /* Note any changes to the repository. */
+ if (value != NULL)
+ {
+ if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
+ db->ood_changed_rev = SVN_STR_TO_REV(value->data);
+ else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
+ db->ood_changed_author = apr_pstrdup(db->pool, value->data);
+ else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
+ {
+ apr_time_t tm;
+ SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool));
+ db->ood_changed_date = tm;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct dir_baton *pb = db->parent_baton;
+ struct edit_baton *eb = db->edit_baton;
+ apr_pool_t *scratch_pool = db->pool;
+
+ /* If nothing has changed and directory has no out of
+ date descendants, return. */
+ if (db->added || db->prop_changed || db->text_changed
+ || db->ood_changed_rev != SVN_INVALID_REVNUM)
+ {
+ enum svn_wc_status_kind repos_node_status;
+ enum svn_wc_status_kind repos_text_status;
+ enum svn_wc_status_kind repos_prop_status;
+
+ /* If this is a new directory, add it to the statushash. */
+ if (db->added)
+ {
+ repos_node_status = svn_wc_status_added;
+ repos_text_status = svn_wc_status_none;
+ repos_prop_status = db->prop_changed ? svn_wc_status_added
+ : svn_wc_status_none;
+ }
+ else
+ {
+ repos_node_status = (db->text_changed || db->prop_changed)
+ ? svn_wc_status_modified
+ : svn_wc_status_none;
+ repos_text_status = db->text_changed ? svn_wc_status_modified
+ : svn_wc_status_none;
+ repos_prop_status = db->prop_changed ? svn_wc_status_modified
+ : svn_wc_status_none;
+ }
+
+ /* Maybe add this directory to its parent's status hash. Note
+ that tweak_statushash won't do anything if repos_text_status
+ is not svn_wc_status_added. */
+ if (pb)
+ {
+ /* ### When we add directory locking, we need to find a
+ ### directory lock here. */
+ SVN_ERR(tweak_statushash(pb, db, TRUE, eb->db, db->local_abspath,
+ repos_node_status, repos_text_status,
+ repos_prop_status, SVN_INVALID_REVNUM, NULL,
+ scratch_pool));
+ }
+ else
+ {
+ /* We're editing the root dir of the WC. As its repos
+ status info isn't otherwise set, set it directly to
+ trigger invocation of the status callback below. */
+ eb->anchor_status->repos_node_status = repos_node_status;
+ eb->anchor_status->repos_prop_status = repos_prop_status;
+ eb->anchor_status->repos_text_status = repos_text_status;
+
+ /* If the root dir is out of date set the ood info directly too. */
+ if (db->ood_changed_rev != eb->anchor_status->revision)
+ {
+ eb->anchor_status->ood_changed_rev = db->ood_changed_rev;
+ eb->anchor_status->ood_changed_date = db->ood_changed_date;
+ eb->anchor_status->ood_kind = db->ood_kind;
+ eb->anchor_status->ood_changed_author =
+ apr_pstrdup(pool, db->ood_changed_author);
+ }
+ }
+ }
+
+ /* Handle this directory's statuses, and then note in the parent
+ that this has been done. */
+ if (pb && ! db->excluded)
+ {
+ svn_boolean_t was_deleted = FALSE;
+ const svn_wc_status3_t *dir_status;
+
+ /* See if the directory was deleted or replaced. */
+ dir_status = svn_hash_gets(pb->statii, db->local_abspath);
+ if (dir_status &&
+ ((dir_status->repos_node_status == svn_wc_status_deleted)
+ || (dir_status->repos_node_status == svn_wc_status_replaced)))
+ was_deleted = TRUE;
+
+ /* Now do the status reporting. */
+ SVN_ERR(handle_statii(eb,
+ dir_status ? dir_status->repos_root_url : NULL,
+ dir_status ? dir_status->repos_relpath : NULL,
+ dir_status ? dir_status->repos_uuid : NULL,
+ db->statii, was_deleted, db->depth, scratch_pool));
+ if (dir_status && is_sendable_status(dir_status, eb->no_ignore,
+ eb->get_all))
+ SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
+ dir_status, scratch_pool));
+ svn_hash_sets(pb->statii, db->local_abspath, NULL);
+ }
+ else if (! pb)
+ {
+ /* If this is the top-most directory, and the operation had a
+ target, we should only report the target. */
+ if (*eb->target_basename)
+ {
+ const svn_wc_status3_t *tgt_status;
+
+ tgt_status = svn_hash_gets(db->statii, eb->target_abspath);
+ if (tgt_status)
+ {
+ if (tgt_status->versioned
+ && tgt_status->kind == svn_node_dir)
+ {
+ SVN_ERR(get_dir_status(&eb->wb,
+ eb->target_abspath, TRUE,
+ NULL, NULL, NULL, NULL,
+ NULL /* dirent */,
+ eb->ignores,
+ eb->default_depth,
+ eb->get_all, eb->no_ignore,
+ eb->status_func, eb->status_baton,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+ }
+ if (is_sendable_status(tgt_status, eb->no_ignore, eb->get_all))
+ SVN_ERR((eb->status_func)(eb->status_baton, eb->target_abspath,
+ tgt_status, scratch_pool));
+ }
+ }
+ else
+ {
+ /* Otherwise, we report on all our children and ourself.
+ Note that our directory couldn't have been deleted,
+ because it is the root of the edit drive. */
+ SVN_ERR(handle_statii(eb,
+ eb->anchor_status->repos_root_url,
+ eb->anchor_status->repos_relpath,
+ eb->anchor_status->repos_uuid,
+ db->statii, FALSE, eb->default_depth,
+ scratch_pool));
+ if (is_sendable_status(eb->anchor_status, eb->no_ignore,
+ eb->get_all))
+ SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
+ eb->anchor_status, scratch_pool));
+ eb->anchor_status = NULL;
+ }
+ }
+
+ svn_pool_clear(scratch_pool); /* Clear baton and its pool */
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct file_baton *new_fb = make_file_baton(pb, path, pool);
+
+ /* Mark parent dir as changed */
+ pb->text_changed = TRUE;
+
+ /* Make this file as added. */
+ new_fb->added = TRUE;
+
+ *file_baton = new_fb;
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct file_baton *new_fb = make_file_baton(pb, path, pool);
+
+ *file_baton = new_fb;
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+
+ /* Mark file as having textual mods. */
+ fb->text_changed = TRUE;
+
+ /* Send back a NULL window handler -- we don't need the actual diffs. */
+ *handler_baton = NULL;
+ *handler = svn_delta_noop_window_handler;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ if (svn_wc_is_normal_prop(name))
+ fb->prop_changed = TRUE;
+
+ /* Note any changes to the repository. */
+ if (value != NULL)
+ {
+ if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
+ fb->ood_changed_rev = SVN_STR_TO_REV(value->data);
+ else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
+ fb->ood_changed_author = apr_pstrdup(fb->dir_baton->pool,
+ value->data);
+ else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
+ {
+ apr_time_t tm;
+ SVN_ERR(svn_time_from_cstring(&tm, value->data,
+ fb->dir_baton->pool));
+ fb->ood_changed_date = tm;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum, /* ignored, as we receive no data */
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ enum svn_wc_status_kind repos_node_status;
+ enum svn_wc_status_kind repos_text_status;
+ enum svn_wc_status_kind repos_prop_status;
+ const svn_lock_t *repos_lock = NULL;
+
+ /* If nothing has changed, return. */
+ if (! (fb->added || fb->prop_changed || fb->text_changed))
+ return SVN_NO_ERROR;
+
+ /* If this is a new file, add it to the statushash. */
+ if (fb->added)
+ {
+ repos_node_status = svn_wc_status_added;
+ repos_text_status = fb->text_changed ? svn_wc_status_modified
+ : 0 /* don't tweak */;
+ repos_prop_status = fb->prop_changed ? svn_wc_status_modified
+ : 0 /* don't tweak */;
+
+ if (fb->edit_baton->wb.repos_locks)
+ {
+ const char *dir_repos_relpath = find_dir_repos_relpath(fb->dir_baton,
+ pool);
+
+ /* repos_lock still uses the deprecated filesystem absolute path
+ format */
+ const char *repos_relpath = svn_relpath_join(dir_repos_relpath,
+ fb->name, pool);
+
+ repos_lock = svn_hash_gets(fb->edit_baton->wb.repos_locks,
+ svn_fspath__join("/", repos_relpath,
+ pool));
+ }
+ }
+ else
+ {
+ repos_node_status = (fb->text_changed || fb->prop_changed)
+ ? svn_wc_status_modified
+ : 0 /* don't tweak */;
+ repos_text_status = fb->text_changed ? svn_wc_status_modified
+ : 0 /* don't tweak */;
+ repos_prop_status = fb->prop_changed ? svn_wc_status_modified
+ : 0 /* don't tweak */;
+ }
+
+ return tweak_statushash(fb, NULL, FALSE, fb->edit_baton->db,
+ fb->local_abspath, repos_node_status,
+ repos_text_status, repos_prop_status,
+ SVN_INVALID_REVNUM, repos_lock, pool);
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ /* If we get here and the root was not opened as part of the edit,
+ we need to transmit statuses for everything. Otherwise, we
+ should be done. */
+ if (eb->root_opened)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc_walk_status(eb->wc_ctx,
+ eb->target_abspath,
+ eb->default_depth,
+ eb->get_all,
+ eb->no_ignore,
+ FALSE,
+ eb->ignores,
+ eb->status_func,
+ eb->status_baton,
+ eb->cancel_func,
+ eb->cancel_baton,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Public API ***/
+
+svn_error_t *
+svn_wc__get_status_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ void **set_locks_baton,
+ svn_revnum_t *edit_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t depth_as_sticky,
+ svn_boolean_t server_performs_filtering,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb;
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(result_pool);
+ void *inner_baton;
+ struct svn_wc__shim_fetch_baton_t *sfb;
+ const svn_delta_editor_t *inner_editor;
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(result_pool);
+
+ /* Construct an edit baton. */
+ eb = apr_pcalloc(result_pool, sizeof(*eb));
+ eb->default_depth = depth;
+ eb->target_revision = edit_revision;
+ eb->db = wc_ctx->db;
+ eb->wc_ctx = wc_ctx;
+ eb->get_all = get_all;
+ eb->no_ignore = no_ignore;
+ eb->status_func = status_func;
+ eb->status_baton = status_baton;
+ eb->cancel_func = cancel_func;
+ eb->cancel_baton = cancel_baton;
+ eb->anchor_abspath = apr_pstrdup(result_pool, anchor_abspath);
+ eb->target_abspath = svn_dirent_join(anchor_abspath, target_basename,
+ result_pool);
+
+ eb->target_basename = apr_pstrdup(result_pool, target_basename);
+ eb->root_opened = FALSE;
+
+ eb->wb.db = wc_ctx->db;
+ eb->wb.target_abspath = eb->target_abspath;
+ eb->wb.ignore_text_mods = FALSE;
+ eb->wb.repos_locks = NULL;
+ eb->wb.repos_root = NULL;
+
+ SVN_ERR(svn_wc__db_externals_defined_below(&eb->wb.externals,
+ wc_ctx->db, eb->target_abspath,
+ result_pool, scratch_pool));
+
+ /* Use the caller-provided ignore patterns if provided; the build-time
+ configured defaults otherwise. */
+ if (ignore_patterns)
+ {
+ eb->ignores = ignore_patterns;
+ }
+ else
+ {
+ apr_array_header_t *ignores;
+
+ SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, result_pool));
+ eb->ignores = ignores;
+ }
+
+ /* The edit baton's status structure maps to PATH, and the editor
+ have to be aware of whether that is the anchor or the target. */
+ SVN_ERR(internal_status(&(eb->anchor_status), wc_ctx->db, anchor_abspath,
+ result_pool, scratch_pool));
+
+ /* Construct an editor. */
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->open_root = open_root;
+ tree_editor->delete_entry = delete_entry;
+ tree_editor->add_directory = add_directory;
+ tree_editor->open_directory = open_directory;
+ tree_editor->change_dir_prop = change_dir_prop;
+ tree_editor->close_directory = close_directory;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->close_edit = close_edit;
+
+ inner_editor = tree_editor;
+ inner_baton = eb;
+
+ if (!server_performs_filtering
+ && !depth_as_sticky)
+ SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
+ &inner_baton,
+ wc_ctx->db,
+ anchor_abspath,
+ target_basename,
+ inner_editor,
+ inner_baton,
+ result_pool));
+
+ /* Conjoin a cancellation editor with our status editor. */
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
+ inner_editor, inner_baton,
+ editor, edit_baton,
+ result_pool));
+
+ if (set_locks_baton)
+ *set_locks_baton = eb;
+
+ sfb = apr_palloc(result_pool, sizeof(*sfb));
+ sfb->db = wc_ctx->db;
+ sfb->base_abspath = eb->anchor_abspath;
+ sfb->fetch_base = FALSE;
+
+ shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
+ shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
+ shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
+ shim_callbacks->fetch_baton = sfb;
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ NULL, NULL, shim_callbacks,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Like svn_io_stat_dirent, but works case sensitive inside working
+ copies. Before 1.8 we handled this with a selection filter inside
+ a directory */
+static svn_error_t *
+stat_wc_dirent_case_sensitive(const svn_io_dirent2_t **dirent,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_wcroot;
+
+ /* The wcroot is "" inside the wc; handle it as not in the wc, as
+ the case of the root is indifferent to us. */
+
+ /* Note that for performance this is really just a few hashtable lookups,
+ as we just used local_abspath for a db call in both our callers */
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath,
+ scratch_pool));
+
+ return svn_error_trace(
+ svn_io_stat_dirent2(dirent, local_abspath,
+ ! is_wcroot /* verify_truename */,
+ TRUE /* ignore_enoent */,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__internal_walk_status(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_text_mods,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct walk_status_baton wb;
+ const svn_io_dirent2_t *dirent;
+ const struct svn_wc__db_info_t *info;
+ svn_error_t *err;
+
+ wb.db = db;
+ wb.target_abspath = local_abspath;
+ wb.ignore_text_mods = ignore_text_mods;
+ wb.repos_root = NULL;
+ wb.repos_locks = NULL;
+
+ /* Use the caller-provided ignore patterns if provided; the build-time
+ configured defaults otherwise. */
+ if (!ignore_patterns)
+ {
+ apr_array_header_t *ignores;
+
+ SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, scratch_pool));
+ ignore_patterns = ignores;
+ }
+
+ err = read_info(&info, local_abspath, db, scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ info = NULL;
+ }
+ else
+ return svn_error_trace(err);
+
+ wb.externals = apr_hash_make(scratch_pool);
+
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ if (info
+ && info->kind == svn_node_dir
+ && info->status != svn_wc__db_status_not_present
+ && info->status != svn_wc__db_status_excluded
+ && info->status != svn_wc__db_status_server_excluded)
+ {
+ SVN_ERR(get_dir_status(&wb,
+ local_abspath,
+ FALSE /* skip_root */,
+ NULL, NULL, NULL,
+ info,
+ dirent,
+ ignore_patterns,
+ depth,
+ get_all,
+ no_ignore,
+ status_func, status_baton,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ else
+ {
+ /* It may be a file or an unversioned item. And this is an explicit
+ * target, so no ignoring. An unversioned item (file or dir) shows a
+ * status like '?', and can yield a tree conflicted path. */
+ err = get_child_status(&wb,
+ local_abspath,
+ info,
+ dirent,
+ ignore_patterns,
+ get_all,
+ status_func, status_baton,
+ cancel_func, cancel_baton,
+ scratch_pool);
+
+ if (!info && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ /* The parent is also not versioned, but it is not nice to show
+ an error about a path a user didn't intend to touch. */
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ SVN_ERR(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_walk_status(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_text_mods,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ svn_wc__internal_walk_status(wc_ctx->db,
+ local_abspath,
+ depth,
+ get_all,
+ no_ignore,
+ ignore_text_mods,
+ ignore_patterns,
+ status_func,
+ status_baton,
+ cancel_func,
+ cancel_baton,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc_status_set_repos_locks(void *edit_baton,
+ apr_hash_t *locks,
+ const char *repos_root,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ eb->wb.repos_locks = locks;
+ eb->wb.repos_root = apr_pstrdup(pool, repos_root);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_get_default_ignores(apr_array_header_t **patterns,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ svn_config_t *cfg = config
+ ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+ const char *val;
+
+ /* Check the Subversion run-time configuration for global ignores.
+ If no configuration value exists, we fall back to our defaults. */
+ svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_GLOBAL_IGNORES,
+ SVN_CONFIG_DEFAULT_GLOBAL_IGNORES);
+ *patterns = apr_array_make(pool, 16, sizeof(const char *));
+
+ /* Split the patterns on whitespace, and stuff them into *PATTERNS. */
+ svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+internal_status(svn_wc_status3_t **status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_io_dirent2_t *dirent;
+ svn_node_kind_t node_kind;
+ const char *parent_repos_relpath;
+ const char *parent_repos_root_url;
+ const char *parent_repos_uuid;
+ svn_wc__db_status_t node_status;
+ svn_boolean_t conflicted;
+ svn_boolean_t is_root = FALSE;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ err = svn_wc__db_read_info(&node_status, &node_kind, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, &conflicted,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ node_kind = svn_node_unknown;
+ /* Ensure conflicted is always set, but don't hide tree conflicts
+ on 'hidden' nodes. */
+ conflicted = FALSE;
+
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
+ scratch_pool, scratch_pool));
+ }
+ else
+ SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (node_kind != svn_node_unknown
+ && (node_status == svn_wc__db_status_not_present
+ || node_status == svn_wc__db_status_server_excluded
+ || node_status == svn_wc__db_status_excluded))
+ {
+ node_kind = svn_node_unknown;
+ }
+
+ if (node_kind == svn_node_unknown)
+ return svn_error_trace(assemble_unversioned(status,
+ db, local_abspath,
+ dirent, conflicted,
+ FALSE /* is_ignored */,
+ result_pool, scratch_pool));
+
+ if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+ is_root = TRUE;
+ else
+ SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool));
+
+ if (!is_root)
+ {
+ svn_wc__db_status_t parent_status;
+ const char *parent_abspath = svn_dirent_dirname(local_abspath,
+ scratch_pool);
+
+ err = svn_wc__db_read_info(&parent_status, NULL, NULL,
+ &parent_repos_relpath, &parent_repos_root_url,
+ &parent_repos_uuid, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, parent_abspath,
+ result_pool, scratch_pool);
+
+ if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
+ || SVN_WC__ERR_IS_NOT_CURRENT_WC(err)))
+ {
+ svn_error_clear(err);
+ parent_repos_root_url = NULL;
+ parent_repos_relpath = NULL;
+ parent_repos_uuid = NULL;
+ }
+ else SVN_ERR(err);
+ }
+ else
+ {
+ parent_repos_root_url = NULL;
+ parent_repos_relpath = NULL;
+ parent_repos_uuid = NULL;
+ }
+
+ return svn_error_trace(assemble_status(status, db, local_abspath,
+ parent_repos_root_url,
+ parent_repos_relpath,
+ parent_repos_uuid,
+ NULL,
+ dirent,
+ TRUE /* get_all */,
+ FALSE,
+ NULL /* repos_lock */,
+ result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc_status3(svn_wc_status3_t **status,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ internal_status(status, wc_ctx->db, local_abspath, result_pool,
+ scratch_pool));
+}
+
+svn_wc_status3_t *
+svn_wc_dup_status3(const svn_wc_status3_t *orig_stat,
+ apr_pool_t *pool)
+{
+ svn_wc_status3_t *new_stat = apr_palloc(pool, sizeof(*new_stat));
+
+ /* Shallow copy all members. */
+ *new_stat = *orig_stat;
+
+ /* Now go back and dup the deep items into this pool. */
+ if (orig_stat->repos_lock)
+ new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool);
+
+ if (orig_stat->changed_author)
+ new_stat->changed_author = apr_pstrdup(pool, orig_stat->changed_author);
+
+ if (orig_stat->ood_changed_author)
+ new_stat->ood_changed_author
+ = apr_pstrdup(pool, orig_stat->ood_changed_author);
+
+ if (orig_stat->lock)
+ new_stat->lock = svn_lock_dup(orig_stat->lock, pool);
+
+ if (orig_stat->changelist)
+ new_stat->changelist
+ = apr_pstrdup(pool, orig_stat->changelist);
+
+ if (orig_stat->repos_root_url)
+ new_stat->repos_root_url
+ = apr_pstrdup(pool, orig_stat->repos_root_url);
+
+ if (orig_stat->repos_relpath)
+ new_stat->repos_relpath
+ = apr_pstrdup(pool, orig_stat->repos_relpath);
+
+ if (orig_stat->repos_uuid)
+ new_stat->repos_uuid
+ = apr_pstrdup(pool, orig_stat->repos_uuid);
+
+ if (orig_stat->moved_from_abspath)
+ new_stat->moved_from_abspath
+ = apr_pstrdup(pool, orig_stat->moved_from_abspath);
+
+ if (orig_stat->moved_to_abspath)
+ new_stat->moved_to_abspath
+ = apr_pstrdup(pool, orig_stat->moved_to_abspath);
+
+ /* Return the new hotness. */
+ return new_stat;
+}
+
+svn_error_t *
+svn_wc_get_ignores2(apr_array_header_t **patterns,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_hash_t *config,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *default_ignores;
+
+ SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, scratch_pool));
+ return svn_error_trace(collect_ignore_patterns(patterns, wc_ctx->db,
+ local_abspath,
+ default_ignores,
+ result_pool, scratch_pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/token-map.h b/contrib/subversion/subversion/libsvn_wc/token-map.h
new file mode 100644
index 0000000..9da12b8
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/token-map.h
@@ -0,0 +1,70 @@
+/**
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This header is parsed by transform-sql.py to allow SQLite
+ * statements to refer to string values by symbolic names.
+ */
+
+#ifndef SVN_WC_TOKEN_MAP_H
+#define SVN_WC_TOKEN_MAP_H
+
+#include "svn_types.h"
+#include "wc_db.h"
+#include "private/svn_token.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static const svn_token_map_t kind_map[] = {
+ { "file", svn_node_file }, /* MAP_FILE */
+ { "dir", svn_node_dir }, /* MAP_DIR */
+ { "symlink", svn_node_symlink }, /* MAP_SYMLINK */
+ { "unknown", svn_node_unknown }, /* MAP_UNKNOWN */
+ { NULL }
+};
+
+/* Note: we only decode presence values from the database. These are a
+ subset of all the status values. */
+static const svn_token_map_t presence_map[] = {
+ { "normal", svn_wc__db_status_normal }, /* MAP_NORMAL */
+ { "server-excluded", svn_wc__db_status_server_excluded }, /* MAP_SERVER_EXCLUDED */
+ { "excluded", svn_wc__db_status_excluded }, /* MAP_EXCLUDED */
+ { "not-present", svn_wc__db_status_not_present }, /* MAP_NOT_PRESENT */
+ { "incomplete", svn_wc__db_status_incomplete }, /* MAP_INCOMPLETE */
+ { "base-deleted", svn_wc__db_status_base_deleted }, /* MAP_BASE_DELETED */
+ { NULL }
+};
+
+/* The subset of svn_depth_t used in the database. */
+static const svn_token_map_t depth_map[] = {
+ { "unknown", svn_depth_unknown }, /* MAP_DEPTH_UNKNOWN */
+ { "empty", svn_depth_empty },
+ { "files", svn_depth_files },
+ { "immediates", svn_depth_immediates },
+ { "infinity", svn_depth_infinity }, /* MAP_DEPTH_INFINITY */
+ { NULL }
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/contrib/subversion/subversion/libsvn_wc/translate.c b/contrib/subversion/subversion/libsvn_wc/translate.c
new file mode 100644
index 0000000..9e0b265
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/translate.c
@@ -0,0 +1,452 @@
+/*
+ * translate.c : wc-specific eol/keyword substitution
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+#include <apr_strings.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_subst.h"
+#include "svn_io.h"
+#include "svn_props.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "translate.h"
+#include "props.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+/* */
+static svn_error_t *
+read_handler_unsupported(void *baton, char *buffer, apr_size_t *len)
+{
+ SVN_ERR_MALFUNCTION();
+}
+
+/* */
+static svn_error_t *
+write_handler_unsupported(void *baton, const char *buffer, apr_size_t *len)
+{
+ SVN_ERR_MALFUNCTION();
+}
+
+svn_error_t *
+svn_wc__internal_translated_stream(svn_stream_t **stream,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *versioned_abspath,
+ apr_uint32_t flags,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t special;
+ svn_boolean_t to_nf = flags & SVN_WC_TRANSLATE_TO_NF;
+ svn_subst_eol_style_t style;
+ const char *eol;
+ apr_hash_t *keywords;
+ svn_boolean_t repair_forced = flags & SVN_WC_TRANSLATE_FORCE_EOL_REPAIR;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_abspath));
+
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special,
+ db, versioned_abspath, NULL, FALSE,
+ scratch_pool, scratch_pool));
+
+ if (special)
+ {
+ if (to_nf)
+ return svn_subst_read_specialfile(stream, local_abspath, result_pool,
+ scratch_pool);
+
+ return svn_subst_create_specialfile(stream, local_abspath, result_pool,
+ scratch_pool);
+ }
+
+ if (to_nf)
+ SVN_ERR(svn_stream_open_readonly(stream, local_abspath, result_pool,
+ scratch_pool));
+ else
+ {
+ apr_file_t *file;
+
+ /* We don't want the "open-exclusively" feature of the normal
+ svn_stream_open_writable interface. Do this manually. */
+ SVN_ERR(svn_io_file_open(&file, local_abspath,
+ APR_CREATE | APR_WRITE | APR_BUFFERED,
+ APR_OS_DEFAULT, result_pool));
+ *stream = svn_stream_from_aprfile2(file, FALSE, result_pool);
+ }
+
+ if (svn_subst_translation_required(style, eol, keywords, special, TRUE))
+ {
+ if (to_nf)
+ {
+ if (style == svn_subst_eol_style_native)
+ eol = SVN_SUBST_NATIVE_EOL_STR;
+ else if (style == svn_subst_eol_style_fixed)
+ repair_forced = TRUE;
+ else if (style != svn_subst_eol_style_none)
+ return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
+
+ /* Wrap the stream to translate to normal form */
+ *stream = svn_subst_stream_translated(*stream,
+ eol,
+ repair_forced,
+ keywords,
+ FALSE /* expand */,
+ result_pool);
+
+ /* Enforce our contract. TO_NF streams are readonly */
+ svn_stream_set_write(*stream, write_handler_unsupported);
+ }
+ else
+ {
+ *stream = svn_subst_stream_translated(*stream, eol, TRUE,
+ keywords, TRUE, result_pool);
+
+ /* Enforce our contract. FROM_NF streams are write-only */
+ svn_stream_set_read(*stream, read_handler_unsupported);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__internal_translated_file(const char **xlated_abspath,
+ const char *src_abspath,
+ svn_wc__db_t *db,
+ const char *versioned_abspath,
+ apr_uint32_t flags,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_subst_eol_style_t style;
+ const char *eol;
+ apr_hash_t *keywords;
+ svn_boolean_t special;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_abspath));
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special,
+ db, versioned_abspath, NULL, FALSE,
+ scratch_pool, scratch_pool));
+
+ if (! svn_subst_translation_required(style, eol, keywords, special, TRUE)
+ && (! (flags & SVN_WC_TRANSLATE_FORCE_COPY)))
+ {
+ /* Translation would be a no-op, so return the original file. */
+ *xlated_abspath = src_abspath;
+ }
+ else /* some translation (or copying) is necessary */
+ {
+ const char *tmp_dir;
+ const char *tmp_vfile;
+ svn_boolean_t repair_forced
+ = (flags & SVN_WC_TRANSLATE_FORCE_EOL_REPAIR) != 0;
+ svn_boolean_t expand = (flags & SVN_WC_TRANSLATE_TO_NF) == 0;
+
+ if (flags & SVN_WC_TRANSLATE_USE_GLOBAL_TMP)
+ tmp_dir = NULL;
+ else
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmp_dir, db, versioned_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_vfile, tmp_dir,
+ (flags & SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP)
+ ? svn_io_file_del_none
+ : svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+
+ /* ### ugh. the repair behavior does NOT match the docstring. bleah.
+ ### all of these translation functions are crap and should go
+ ### away anyways. we'll just deprecate most of the functions and
+ ### properly document the survivors */
+
+ if (expand)
+ {
+ /* from normal form */
+
+ repair_forced = TRUE;
+ }
+ else
+ {
+ /* to normal form */
+
+ if (style == svn_subst_eol_style_native)
+ eol = SVN_SUBST_NATIVE_EOL_STR;
+ else if (style == svn_subst_eol_style_fixed)
+ repair_forced = TRUE;
+ else if (style != svn_subst_eol_style_none)
+ return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
+ }
+
+ SVN_ERR(svn_subst_copy_and_translate4(src_abspath, tmp_vfile,
+ eol, repair_forced,
+ keywords,
+ expand,
+ special,
+ cancel_func, cancel_baton,
+ result_pool));
+
+ *xlated_abspath = tmp_vfile;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_wc__eol_value_from_string(const char **value, const char *eol)
+{
+ if (eol == NULL)
+ *value = NULL;
+ else if (! strcmp("\n", eol))
+ *value = "LF";
+ else if (! strcmp("\r", eol))
+ *value = "CR";
+ else if (! strcmp("\r\n", eol))
+ *value = "CRLF";
+ else
+ *value = NULL;
+}
+
+svn_error_t *
+svn_wc__get_translate_info(svn_subst_eol_style_t *style,
+ const char **eol,
+ apr_hash_t **keywords,
+ svn_boolean_t *special,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_hash_t *props,
+ svn_boolean_t for_normalization,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *propval;
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (props == NULL)
+ SVN_ERR(svn_wc__get_actual_props(&props, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (eol)
+ {
+ propval = svn_prop_get_value(props, SVN_PROP_EOL_STYLE);
+
+ svn_subst_eol_style_from_value(style, eol, propval);
+ }
+
+ if (keywords)
+ {
+ propval = svn_prop_get_value(props, SVN_PROP_KEYWORDS);
+
+ if (!propval || *propval == '\0')
+ *keywords = NULL;
+ else
+ SVN_ERR(svn_wc__expand_keywords(keywords,
+ db, local_abspath, NULL,
+ propval, for_normalization,
+ result_pool, scratch_pool));
+ }
+ if (special)
+ {
+ propval = svn_prop_get_value(props, SVN_PROP_SPECIAL);
+
+ *special = (propval != NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__expand_keywords(apr_hash_t **keywords,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *keyword_list,
+ svn_boolean_t for_normalization,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ const char *url;
+ const char *repos_root_url;
+
+ if (! for_normalization)
+ {
+ const char *repos_relpath;
+
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, &repos_relpath,
+ &repos_root_url, NULL, &changed_rev,
+ &changed_date, &changed_author, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (repos_relpath)
+ url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ scratch_pool);
+ else
+ SVN_ERR(svn_wc__db_read_url(&url, db, local_abspath, scratch_pool,
+ scratch_pool));
+ }
+ else
+ {
+ url = "";
+ changed_rev = SVN_INVALID_REVNUM;
+ changed_date = 0;
+ changed_author = "";
+ repos_root_url = "";
+ }
+
+ SVN_ERR(svn_subst_build_keywords3(keywords, keyword_list,
+ apr_psprintf(scratch_pool, "%ld",
+ changed_rev),
+ url, repos_root_url,
+ changed_date, changed_author,
+ result_pool));
+
+ if (apr_hash_count(*keywords) == 0)
+ *keywords = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__sync_flags_with_props(svn_boolean_t *did_set,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_wc__db_lock_t *lock;
+ apr_hash_t *props = NULL;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+
+ if (did_set)
+ *did_set = FALSE;
+
+ /* ### We'll consolidate these info gathering statements in a future
+ commit. */
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, &lock, NULL, NULL, NULL, NULL, NULL,
+ &had_props, &props_mod, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* We actually only care about the following flags on files, so just
+ early-out for all other types.
+
+ Also bail if there is no in-wc representation of the file. */
+ if (kind != svn_node_file
+ || (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_added))
+ return SVN_NO_ERROR;
+
+ if (props_mod || had_props)
+ SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, scratch_pool,
+ scratch_pool));
+ else
+ props = NULL;
+
+ /* If we get this far, we're going to change *something*, so just set
+ the flag appropriately. */
+ if (did_set)
+ *did_set = TRUE;
+
+ /* Handle the read-write bit. */
+ if (status != svn_wc__db_status_normal
+ || props == NULL
+ || ! svn_hash_gets(props, SVN_PROP_NEEDS_LOCK)
+ || lock)
+ {
+ SVN_ERR(svn_io_set_file_read_write(local_abspath, FALSE, scratch_pool));
+ }
+ else
+ {
+ /* Special case: If we have an uncommitted svn:needs-lock, we don't
+ set the file read_only just yet. That happens upon commit. */
+ apr_hash_t *pristine_props;
+
+ if (! props_mod)
+ pristine_props = props;
+ else if (had_props)
+ SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath,
+ scratch_pool, scratch_pool));
+ else
+ pristine_props = NULL;
+
+ if (pristine_props
+ && svn_hash_gets(pristine_props, SVN_PROP_NEEDS_LOCK) )
+ /*&& props
+ && apr_hash_get(props, SVN_PROP_NEEDS_LOCK, APR_HASH_KEY_STRING) )*/
+ SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool));
+ }
+
+/* Windows doesn't care about the execute bit. */
+#ifndef WIN32
+
+ if (props == NULL
+ || ! svn_hash_gets(props, SVN_PROP_EXECUTABLE))
+ {
+ /* Turn off the execute bit */
+ SVN_ERR(svn_io_set_file_executable(local_abspath, FALSE, FALSE,
+ scratch_pool));
+ }
+ else
+ SVN_ERR(svn_io_set_file_executable(local_abspath, TRUE, FALSE,
+ scratch_pool));
+#endif
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/translate.h b/contrib/subversion/subversion/libsvn_wc/translate.h
new file mode 100644
index 0000000..c5203be
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/translate.h
@@ -0,0 +1,189 @@
+/*
+ * translate.h : eol and keyword translation
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_WC_TRANSLATE_H
+#define SVN_LIBSVN_WC_TRANSLATE_H
+
+#include <apr_pools.h>
+#include "svn_types.h"
+#include "svn_subst.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Newline and keyword translation properties */
+
+/* If EOL is not-NULL query the SVN_PROP_EOL_STYLE property on file
+ LOCAL_ABSPATH in DB. If STYLE is non-null, set *STYLE to LOCAL_ABSPATH's
+ eol style. Set *EOL to
+
+ - NULL for svn_subst_eol_style_none, or
+
+ - a null-terminated C string containing the native eol marker
+ for this platform, for svn_subst_eol_style_native, or
+
+ - a null-terminated C string containing the eol marker indicated
+ by the property value, for svn_subst_eol_style_fixed.
+
+ If STYLE is null on entry, ignore it. If *EOL is non-null on exit,
+ it is a static string not allocated in POOL.
+
+ If KEYWORDS is not NULL Expand keywords for the file at LOCAL_ABSPATH
+ in DB, by parsing a whitespace-delimited list of keywords. If any keywords
+ are found in the list, allocate *KEYWORDS from RESULT_POOL and populate it
+ with mappings from (const char *) keywords to their (svn_string_t *)
+ values (also allocated in RESULT_POOL).
+
+ If a keyword is in the list, but no corresponding value is
+ available, do not create a hash entry for it. If no keywords are
+ found in the list, or if there is no list, set *KEYWORDS to NULL.
+
+ If SPECIAL is not NULL determine if the svn:special flag is set on
+ LOCAL_ABSPATH in DB. If so, set SPECIAL to TRUE, if not, set it to FALSE.
+
+ If PROPS is not NULL, use PROPS instead of the properties on LOCAL_ABSPATH.
+
+ If WRI_ABSPATH is not NULL, retrieve the information for LOCAL_ABSPATH
+ from the working copy identified by WRI_ABSPATH. Falling back to file
+ external information if the file is not present as versioned node.
+
+ If FOR_NORMALIZATION is TRUE, just return a list of keywords instead of
+ calculating their intended values.
+
+ Use SCRATCH_POOL for temporary allocation, RESULT_POOL for allocating
+ *STYLE and *EOL.
+*/
+svn_error_t *
+svn_wc__get_translate_info(svn_subst_eol_style_t *style,
+ const char **eol,
+ apr_hash_t **keywords,
+ svn_boolean_t *special,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_hash_t *props,
+ svn_boolean_t for_normalization,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Reverse parser. Given a real EOL string ("\n", "\r", or "\r\n"),
+ return an encoded *VALUE ("LF", "CR", "CRLF") that one might see in
+ the property value. */
+void svn_wc__eol_value_from_string(const char **value,
+ const char *eol);
+
+/* Expand keywords for the file at LOCAL_ABSPATH in DB, by parsing a
+ whitespace-delimited list of keywords KEYWORD_LIST. If any keywords
+ are found in the list, allocate *KEYWORDS from RESULT_POOL and populate
+ it with mappings from (const char *) keywords to their (svn_string_t *)
+ values (also allocated in RESULT_POOL).
+
+ If a keyword is in the list, but no corresponding value is
+ available, do not create a hash entry for it. If no keywords are
+ found in the list, or if there is no list, set *KEYWORDS to NULL.
+ ### THIS LOOKS WRONG -- it creates a hash entry for every recognized kw
+ and expands each missing value as an empty string or "-1" or similar.
+
+ Use LOCAL_ABSPATH to expand keyword values.
+
+ If WRI_ABSPATH is not NULL, retrieve the information for LOCAL_ABSPATH
+ from the working copy identified by WRI_ABSPATH. Falling back to file
+ external information if the file is not present as versioned node.
+ ### THIS IS NOT IMPLEMENTED -- WRI_ABSPATH is ignored
+
+ If FOR_NORMALIZATION is TRUE, just return a list of keywords instead of
+ calculating their intended values.
+ ### This would be better done by a separate API, since in this case
+ only the KEYWORD_LIST input parameter is needed. (And there is no
+ need to print "-1" as the revision value.)
+
+ Use SCRATCH_POOL for any temporary allocations.
+*/
+svn_error_t *
+svn_wc__expand_keywords(apr_hash_t **keywords,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *keyword_list,
+ svn_boolean_t for_normalization,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Sync the write and execute bit for LOCAL_ABSPATH with what is currently
+ indicated by the properties in the database:
+
+ * If the SVN_PROP_NEEDS_LOCK property is present and there is no
+ lock token for the file in the working copy, set LOCAL_ABSPATH to
+ read-only.
+ * If the SVN_PROP_EXECUTABLE property is present at all, then set
+ LOCAL_ABSPATH executable.
+
+ If DID_SET is non-null, then liberally set *DID_SET to TRUE if we might
+ have change the permissions on LOCAL_ABSPATH. (A TRUE value in *DID_SET
+ does not guarantee that we changed the permissions, simply that more
+ investigation is warrented.)
+
+ This function looks at the current values of the above properties,
+ including any scheduled-but-not-yet-committed changes.
+
+ If LOCAL_ABSPATH is a directory, this function is a no-op.
+
+ Use SCRATCH_POOL for any temporary allocations.
+ */
+svn_error_t *
+svn_wc__sync_flags_with_props(svn_boolean_t *did_set,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc_translated_stream2(), which see. */
+svn_error_t *
+svn_wc__internal_translated_stream(svn_stream_t **stream,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *versioned_abspath,
+ apr_uint32_t flags,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc_translated_file2(), except the working copy database
+ * is used directly and the function assumes abspaths. */
+svn_error_t *
+svn_wc__internal_translated_file(const char **xlated_abspath,
+ const char *src_abspath,
+ svn_wc__db_t *db,
+ const char *versioned_abspath,
+ apr_uint32_t flags,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_TRANSLATE_H */
diff --git a/contrib/subversion/subversion/libsvn_wc/tree_conflicts.c b/contrib/subversion/subversion/libsvn_wc/tree_conflicts.c
new file mode 100644
index 0000000..4445c96
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/tree_conflicts.c
@@ -0,0 +1,513 @@
+/*
+ * tree_conflicts.c: Storage of tree conflict descriptions in the WC.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+
+#include "tree_conflicts.h"
+#include "conflicts.h"
+#include "wc.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_token.h"
+
+#include "svn_private_config.h"
+
+/* ### this should move to a more general location... */
+/* A map for svn_node_kind_t values. */
+/* FIXME: this mapping defines a different representation of
+ svn_node_unknown than the one defined in token-map.h */
+static const svn_token_map_t node_kind_map[] =
+{
+ { "none", svn_node_none },
+ { "file", svn_node_file },
+ { "dir", svn_node_dir },
+ { "", svn_node_unknown },
+ { NULL }
+};
+
+/* A map for svn_wc_operation_t values. */
+const svn_token_map_t svn_wc__operation_map[] =
+{
+ { "none", svn_wc_operation_none },
+ { "update", svn_wc_operation_update },
+ { "switch", svn_wc_operation_switch },
+ { "merge", svn_wc_operation_merge },
+ { NULL }
+};
+
+/* A map for svn_wc_conflict_action_t values. */
+const svn_token_map_t svn_wc__conflict_action_map[] =
+{
+ { "edited", svn_wc_conflict_action_edit },
+ { "deleted", svn_wc_conflict_action_delete },
+ { "added", svn_wc_conflict_action_add },
+ { "replaced", svn_wc_conflict_action_replace },
+ { NULL }
+};
+
+/* A map for svn_wc_conflict_reason_t values. */
+const svn_token_map_t svn_wc__conflict_reason_map[] =
+{
+ { "edited", svn_wc_conflict_reason_edited },
+ { "deleted", svn_wc_conflict_reason_deleted },
+ { "missing", svn_wc_conflict_reason_missing },
+ { "obstructed", svn_wc_conflict_reason_obstructed },
+ { "added", svn_wc_conflict_reason_added },
+ { "replaced", svn_wc_conflict_reason_replaced },
+ { "unversioned", svn_wc_conflict_reason_unversioned },
+ { "moved-away", svn_wc_conflict_reason_moved_away },
+ { "moved-here", svn_wc_conflict_reason_moved_here },
+ { NULL }
+};
+
+
+/* */
+static svn_boolean_t
+is_valid_version_info_skel(const svn_skel_t *skel)
+{
+ return (svn_skel__list_length(skel) == 5
+ && svn_skel__matches_atom(skel->children, "version")
+ && skel->children->next->is_atom
+ && skel->children->next->next->is_atom
+ && skel->children->next->next->next->is_atom
+ && skel->children->next->next->next->next->is_atom);
+}
+
+
+/* */
+static svn_boolean_t
+is_valid_conflict_skel(const svn_skel_t *skel)
+{
+ int i;
+
+ if (svn_skel__list_length(skel) != 8
+ || !svn_skel__matches_atom(skel->children, "conflict"))
+ return FALSE;
+
+ /* 5 atoms ... */
+ skel = skel->children->next;
+ for (i = 5; i--; skel = skel->next)
+ if (!skel->is_atom)
+ return FALSE;
+
+ /* ... and 2 version info skels. */
+ return (is_valid_version_info_skel(skel)
+ && is_valid_version_info_skel(skel->next));
+}
+
+
+/* Parse the enumeration value in VALUE into a plain
+ * 'int', using MAP to convert from strings to enumeration values.
+ * In MAP, a null .str field marks the end of the map.
+ */
+static svn_error_t *
+read_enum_field(int *result,
+ const svn_token_map_t *map,
+ const svn_skel_t *skel)
+{
+ int value = svn_token__from_mem(map, skel->data, skel->len);
+
+ if (value == SVN_TOKEN_UNKNOWN)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Unknown enumeration value in tree conflict "
+ "description"));
+
+ *result = value;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Parse the conflict info fields from SKEL into *VERSION_INFO. */
+static svn_error_t *
+read_node_version_info(const svn_wc_conflict_version_t **version_info,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int n;
+ const char *repos_root;
+ const char *repos_relpath;
+ svn_revnum_t peg_rev;
+ svn_node_kind_t kind;
+
+ if (!is_valid_version_info_skel(skel))
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid version info in tree conflict "
+ "description"));
+
+ repos_root = apr_pstrmemdup(scratch_pool,
+ skel->children->next->data,
+ skel->children->next->len);
+ if (*repos_root == '\0')
+ {
+ *version_info = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Apply the Subversion 1.7+ url canonicalization rules to a pre 1.7 url */
+ repos_root = svn_uri_canonicalize(repos_root, result_pool);
+
+ peg_rev = SVN_STR_TO_REV(apr_pstrmemdup(scratch_pool,
+ skel->children->next->next->data,
+ skel->children->next->next->len));
+
+ repos_relpath = apr_pstrmemdup(result_pool,
+ skel->children->next->next->next->data,
+ skel->children->next->next->next->len);
+
+ SVN_ERR(read_enum_field(&n, node_kind_map,
+ skel->children->next->next->next->next));
+ kind = (svn_node_kind_t)n;
+
+ *version_info = svn_wc_conflict_version_create2(repos_root,
+ NULL,
+ repos_relpath,
+ peg_rev,
+ kind,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__deserialize_conflict(const svn_wc_conflict_description2_t **conflict,
+ const svn_skel_t *skel,
+ const char *dir_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *victim_basename;
+ const char *victim_abspath;
+ svn_node_kind_t node_kind;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_action_t action;
+ svn_wc_conflict_reason_t reason;
+ const svn_wc_conflict_version_t *src_left_version;
+ const svn_wc_conflict_version_t *src_right_version;
+ int n;
+ svn_wc_conflict_description2_t *new_conflict;
+
+ if (!is_valid_conflict_skel(skel))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid conflict info '%s' in tree conflict "
+ "description"),
+ skel ? svn_skel__unparse(skel, scratch_pool)->data
+ : "(null)");
+
+ /* victim basename */
+ victim_basename = apr_pstrmemdup(scratch_pool,
+ skel->children->next->data,
+ skel->children->next->len);
+ if (victim_basename[0] == '\0')
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Empty 'victim' field in tree conflict "
+ "description"));
+
+ /* node_kind */
+ SVN_ERR(read_enum_field(&n, node_kind_map, skel->children->next->next));
+ node_kind = (svn_node_kind_t)n;
+ if (node_kind != svn_node_file && node_kind != svn_node_dir)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid 'node_kind' field in tree conflict description"));
+
+ /* operation */
+ SVN_ERR(read_enum_field(&n, svn_wc__operation_map,
+ skel->children->next->next->next));
+ operation = (svn_wc_operation_t)n;
+
+ SVN_ERR(svn_dirent_get_absolute(&victim_abspath,
+ svn_dirent_join(dir_path, victim_basename, scratch_pool),
+ scratch_pool));
+
+ /* action */
+ SVN_ERR(read_enum_field(&n, svn_wc__conflict_action_map,
+ skel->children->next->next->next->next));
+ action = n;
+
+ /* reason */
+ SVN_ERR(read_enum_field(&n, svn_wc__conflict_reason_map,
+ skel->children->next->next->next->next->next));
+ reason = n;
+
+ /* Let's just make it a bit easier on ourself here... */
+ skel = skel->children->next->next->next->next->next->next;
+
+ /* src_left_version */
+ SVN_ERR(read_node_version_info(&src_left_version, skel,
+ result_pool, scratch_pool));
+
+ /* src_right_version */
+ SVN_ERR(read_node_version_info(&src_right_version, skel->next,
+ result_pool, scratch_pool));
+
+ new_conflict = svn_wc_conflict_description_create_tree2(victim_abspath,
+ node_kind, operation, src_left_version, src_right_version,
+ result_pool);
+ new_conflict->action = action;
+ new_conflict->reason = reason;
+
+ *conflict = new_conflict;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Prepend to SKEL the string corresponding to enumeration value N, as found
+ * in MAP. */
+static void
+skel_prepend_enum(svn_skel_t *skel,
+ const svn_token_map_t *map,
+ int n,
+ apr_pool_t *result_pool)
+{
+ svn_skel__prepend(svn_skel__str_atom(svn_token__to_word(map, n),
+ result_pool), skel);
+}
+
+
+/* Prepend to PARENT_SKEL the several fields that represent VERSION_INFO, */
+static svn_error_t *
+prepend_version_info_skel(svn_skel_t *parent_skel,
+ const svn_wc_conflict_version_t *version_info,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(pool);
+
+ /* node_kind */
+ skel_prepend_enum(skel, node_kind_map, version_info->node_kind, pool);
+
+ /* path_in_repos */
+ svn_skel__prepend(svn_skel__str_atom(version_info->path_in_repos
+ ? version_info->path_in_repos
+ : "", pool), skel);
+
+ /* peg_rev */
+ svn_skel__prepend(svn_skel__str_atom(apr_psprintf(pool, "%ld",
+ version_info->peg_rev),
+ pool), skel);
+
+ /* repos_url */
+ svn_skel__prepend(svn_skel__str_atom(version_info->repos_url
+ ? version_info->repos_url
+ : "", pool), skel);
+
+ svn_skel__prepend(svn_skel__str_atom("version", pool), skel);
+
+ SVN_ERR_ASSERT(is_valid_version_info_skel(skel));
+
+ svn_skel__prepend(skel, parent_skel);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__serialize_conflict(svn_skel_t **skel,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* A conflict version struct with all fields null/invalid. */
+ static const svn_wc_conflict_version_t null_version = {
+ NULL, SVN_INVALID_REVNUM, NULL, svn_node_unknown };
+ svn_skel_t *c_skel = svn_skel__make_empty_list(result_pool);
+ const char *victim_basename;
+
+ /* src_right_version */
+ if (conflict->src_right_version)
+ SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_right_version,
+ result_pool));
+ else
+ SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool));
+
+ /* src_left_version */
+ if (conflict->src_left_version)
+ SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_left_version,
+ result_pool));
+ else
+ SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool));
+
+ /* reason */
+ skel_prepend_enum(c_skel, svn_wc__conflict_reason_map, conflict->reason,
+ result_pool);
+
+ /* action */
+ skel_prepend_enum(c_skel, svn_wc__conflict_action_map, conflict->action,
+ result_pool);
+
+ /* operation */
+ skel_prepend_enum(c_skel, svn_wc__operation_map, conflict->operation,
+ result_pool);
+
+ /* node_kind */
+ SVN_ERR_ASSERT(conflict->node_kind == svn_node_dir
+ || conflict->node_kind == svn_node_file);
+ skel_prepend_enum(c_skel, node_kind_map, conflict->node_kind, result_pool);
+
+ /* Victim path (escaping separator chars). */
+ victim_basename = svn_dirent_basename(conflict->local_abspath, result_pool);
+ SVN_ERR_ASSERT(victim_basename[0]);
+ svn_skel__prepend(svn_skel__str_atom(victim_basename, result_pool), c_skel);
+
+ svn_skel__prepend(svn_skel__str_atom("conflict", result_pool), c_skel);
+
+ SVN_ERR_ASSERT(is_valid_conflict_skel(c_skel));
+
+ *skel = c_skel;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__del_tree_conflict(svn_wc_context_t *wc_ctx,
+ const char *victim_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(victim_abspath));
+
+ SVN_ERR(svn_wc__db_op_mark_resolved(wc_ctx->db, victim_abspath,
+ FALSE, FALSE, TRUE, NULL,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+ }
+
+svn_error_t *
+svn_wc__add_tree_conflict(svn_wc_context_t *wc_ctx,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t existing_conflict;
+ svn_skel_t *conflict_skel;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(conflict != NULL);
+ SVN_ERR_ASSERT(conflict->operation == svn_wc_operation_merge
+ || (conflict->reason != svn_wc_conflict_reason_moved_away
+ && conflict->reason != svn_wc_conflict_reason_moved_here)
+ );
+
+ /* Re-adding an existing tree conflict victim is an error. */
+ err = svn_wc__internal_conflicted_p(NULL, NULL, &existing_conflict,
+ wc_ctx->db, conflict->local_abspath,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ }
+ else if (existing_conflict)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Attempt to add tree conflict that already "
+ "exists at '%s'"),
+ svn_dirent_local_style(conflict->local_abspath,
+ scratch_pool));
+ else if (!conflict)
+ return SVN_NO_ERROR;
+
+ conflict_skel = svn_wc__conflict_skel_create(scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(conflict_skel, wc_ctx->db,
+ conflict->local_abspath,
+ conflict->reason,
+ conflict->action,
+ NULL,
+ scratch_pool, scratch_pool));
+
+ switch(conflict->operation)
+ {
+ case svn_wc_operation_update:
+ default:
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_skel,
+ conflict->src_left_version,
+ conflict->src_right_version,
+ scratch_pool, scratch_pool));
+ break;
+ case svn_wc_operation_switch:
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict_skel,
+ conflict->src_left_version,
+ conflict->src_right_version,
+ scratch_pool, scratch_pool));
+ break;
+ case svn_wc_operation_merge:
+ SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
+ conflict->src_left_version,
+ conflict->src_right_version,
+ scratch_pool, scratch_pool));
+ break;
+ }
+
+ return svn_error_trace(
+ svn_wc__db_op_mark_conflict(wc_ctx->db, conflict->local_abspath,
+ conflict_skel, NULL, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__get_tree_conflict(const svn_wc_conflict_description2_t **tree_conflict,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *conflicts;
+ int i;
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__read_conflicts(&conflicts,
+ wc_ctx->db, local_abspath, FALSE,
+ scratch_pool, scratch_pool));
+
+ if (!conflicts || conflicts->nelts == 0)
+ {
+ *tree_conflict = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ for (i = 0; i < conflicts->nelts; i++)
+ {
+ const svn_wc_conflict_description2_t *desc;
+
+ desc = APR_ARRAY_IDX(conflicts, i, svn_wc_conflict_description2_t *);
+
+ if (desc->kind == svn_wc_conflict_kind_tree)
+ {
+ *tree_conflict = svn_wc__conflict_description2_dup(desc,
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ *tree_conflict = NULL;
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/libsvn_wc/tree_conflicts.h b/contrib/subversion/subversion/libsvn_wc/tree_conflicts.h
new file mode 100644
index 0000000..68cd9f6
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/tree_conflicts.h
@@ -0,0 +1,93 @@
+/*
+ * tree_conflicts.h: declarations related to tree conflicts
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_WC_TREE_CONFLICTS_H
+#define SVN_LIBSVN_WC_TREE_CONFLICTS_H
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#include "svn_string.h"
+#include "svn_wc.h"
+
+#include "private/svn_token.h"
+#include "private/svn_skel.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*
+ * See the notes/tree-conflicts/ directory for more information
+ * about tree conflicts in general.
+ *
+ * A given directory may contain potentially many tree conflicts.
+ * Each tree conflict is identified by the path of the file
+ * or directory (both a.k.a node) that it affects.
+ * We call this file or directory the "victim" of the tree conflict.
+ *
+ * For example, a file that is deleted by an update but locally
+ * modified by the user is a victim of a tree conflict.
+ *
+ * For now, tree conflict victims are always direct children of the
+ * directory in which the tree conflict is recorded.
+ * This may change once the way Subversion handles adm areas changes.
+ *
+ * If a directory has tree conflicts, the "tree-conflict-data" field
+ * in the entry for the directory contains one or more tree conflict
+ * descriptions stored using the "skel" format.
+ */
+
+
+svn_error_t *
+svn_wc__serialize_conflict(svn_skel_t **skel,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Parse a newly allocated svn_wc_conflict_description2_t object from the
+ * provided SKEL. Return the result in *CONFLICT, allocated in RESULT_POOL.
+ * DIR_PATH is the path to the WC directory whose conflicts are being read.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_wc__deserialize_conflict(const svn_wc_conflict_description2_t **conflict,
+ const svn_skel_t *skel,
+ const char *dir_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Token mapping tables. */
+extern const svn_token_map_t svn_wc__operation_map[];
+extern const svn_token_map_t svn_wc__conflict_action_map[];
+extern const svn_token_map_t svn_wc__conflict_reason_map[];
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_TREE_CONFLICTS_H */
diff --git a/contrib/subversion/subversion/libsvn_wc/update_editor.c b/contrib/subversion/subversion/libsvn_wc/update_editor.c
new file mode 100644
index 0000000..617ad47
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/update_editor.c
@@ -0,0 +1,5486 @@
+/*
+ * update_editor.c : main editor for checkouts and updates
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include <apr_md5.h>
+#include <apr_tables.h>
+#include <apr_strings.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_hash.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_private_config.h"
+#include "svn_time.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "translate.h"
+#include "workqueue.h"
+
+#include "private/svn_subr_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_editor.h"
+
+/* Checks whether a svn_wc__db_status_t indicates whether a node is
+ present in a working copy. Used by the editor implementation */
+#define IS_NODE_PRESENT(status) \
+ ((status) != svn_wc__db_status_server_excluded &&\
+ (status) != svn_wc__db_status_excluded && \
+ (status) != svn_wc__db_status_not_present)
+
+static svn_error_t *
+path_join_under_root(const char **result_path,
+ const char *base_path,
+ const char *add_path,
+ apr_pool_t *result_pool);
+
+
+/*
+ * This code handles "checkout" and "update" and "switch".
+ * A checkout is similar to an update that is only adding new items.
+ *
+ * The intended behaviour of "update" and "switch", focusing on the checks
+ * to be made before applying a change, is:
+ *
+ * For each incoming change:
+ * if target is already in conflict or obstructed:
+ * skip this change
+ * else
+ * if this action will cause a tree conflict:
+ * record the tree conflict
+ * skip this change
+ * else:
+ * make this change
+ *
+ * In more detail:
+ *
+ * For each incoming change:
+ *
+ * 1. if # Incoming change is inside an item already in conflict:
+ * a. tree/text/prop change to node beneath tree-conflicted dir
+ * then # Skip all changes in this conflicted subtree [*1]:
+ * do not update the Base nor the Working
+ * notify "skipped because already in conflict" just once
+ * for the whole conflicted subtree
+ *
+ * if # Incoming change affects an item already in conflict:
+ * b. tree/text/prop change to tree-conflicted dir/file, or
+ * c. tree change to a text/prop-conflicted file/dir, or
+ * d. text/prop change to a text/prop-conflicted file/dir [*2], or
+ * e. tree change to a dir tree containing any conflicts,
+ * then # Skip this change [*1]:
+ * do not update the Base nor the Working
+ * notify "skipped because already in conflict"
+ *
+ * 2. if # Incoming change affects an item that's "obstructed":
+ * a. on-disk node kind doesn't match recorded Working node kind
+ * (including an absence/presence mis-match),
+ * then # Skip this change [*1]:
+ * do not update the Base nor the Working
+ * notify "skipped because obstructed"
+ *
+ * 3. if # Incoming change raises a tree conflict:
+ * a. tree/text/prop change to node beneath sched-delete dir, or
+ * b. tree/text/prop change to sched-delete dir/file, or
+ * c. text/prop change to tree-scheduled dir/file,
+ * then # Skip this change:
+ * do not update the Base nor the Working [*3]
+ * notify "tree conflict"
+ *
+ * 4. Apply the change:
+ * update the Base
+ * update the Working, possibly raising text/prop conflicts
+ * notify
+ *
+ * Notes:
+ *
+ * "Tree change" here refers to an add or delete of the target node,
+ * including the add or delete part of a copy or move or rename.
+ *
+ * [*1] We should skip changes to an entire node, as the base revision number
+ * applies to the entire node. Not sure how this affects attempts to
+ * handle text and prop changes separately.
+ *
+ * [*2] Details of which combinations of property and text changes conflict
+ * are not specified here.
+ *
+ * [*3] For now, we skip the update, and require the user to:
+ * - Modify the WC to be compatible with the incoming change;
+ * - Mark the conflict as resolved;
+ * - Repeat the update.
+ * Ideally, it would be possible to resolve any conflict without
+ * repeating the update. To achieve this, we would have to store the
+ * necessary data at conflict detection time, and delay the update of
+ * the Base until the time of resolving.
+ */
+
+
+/*** batons ***/
+
+struct edit_baton
+{
+ /* For updates, the "destination" of the edit is ANCHOR_ABSPATH, the
+ directory containing TARGET_ABSPATH. If ANCHOR_ABSPATH itself is the
+ target, the values are identical.
+
+ TARGET_BASENAME is the name of TARGET_ABSPATH in ANCHOR_ABSPATH, or "" if
+ ANCHOR_ABSPATH is the target */
+ const char *target_basename;
+
+ /* Absolute variants of ANCHOR and TARGET */
+ const char *anchor_abspath;
+ const char *target_abspath;
+
+ /* The DB handle for managing the working copy state. */
+ svn_wc__db_t *db;
+
+ /* Array of file extension patterns to preserve as extensions in
+ generated conflict files. */
+ const apr_array_header_t *ext_patterns;
+
+ /* Hash mapping const char * absolute working copy paths to depth-first
+ ordered arrays of svn_prop_inherited_item_t * structures representing
+ the properties inherited by the base node at that working copy path.
+ May be NULL. */
+ apr_hash_t *wcroot_iprops;
+
+ /* The revision we're targeting...or something like that. This
+ starts off as a pointer to the revision to which we are updating,
+ or SVN_INVALID_REVNUM, but by the end of the edit, should be
+ pointing to the final revision. */
+ svn_revnum_t *target_revision;
+
+ /* The requested depth of this edit. */
+ svn_depth_t requested_depth;
+
+ /* Is the requested depth merely an operational limitation, or is
+ also the new sticky ambient depth of the update target? */
+ svn_boolean_t depth_is_sticky;
+
+ /* Need to know if the user wants us to overwrite the 'now' times on
+ edited/added files with the last-commit-time. */
+ svn_boolean_t use_commit_times;
+
+ /* Was the root actually opened (was this a non-empty edit)? */
+ svn_boolean_t root_opened;
+
+ /* Was the update-target deleted? This is a special situation. */
+ svn_boolean_t target_deleted;
+
+ /* Allow unversioned obstructions when adding a path. */
+ svn_boolean_t allow_unver_obstructions;
+
+ /* Handle local additions as modifications of new nodes */
+ svn_boolean_t adds_as_modification;
+
+ /* If set, we check out into an empty directory. This allows for a number
+ of conflict checks to be omitted. */
+ svn_boolean_t clean_checkout;
+
+ /* If this is a 'switch' operation, the new relpath of target_abspath,
+ else NULL. */
+ const char *switch_relpath;
+
+ /* The URL to the root of the repository. */
+ const char *repos_root;
+
+ /* The UUID of the repos, or NULL. */
+ const char *repos_uuid;
+
+ /* External diff3 to use for merges (can be null, in which case
+ internal merge code is used). */
+ const char *diff3_cmd;
+
+ /* Externals handler */
+ svn_wc_external_update_t external_func;
+ void *external_baton;
+
+ /* This editor sends back notifications as it edits. */
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+
+ /* This editor is normally wrapped in a cancellation editor anyway,
+ so it doesn't bother to check for cancellation itself. However,
+ it needs a cancel_func and cancel_baton available to pass to
+ long-running functions. */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ /* This editor will invoke a interactive conflict-resolution
+ callback, if available. */
+ svn_wc_conflict_resolver_func2_t conflict_func;
+ void *conflict_baton;
+
+ /* Subtrees that were skipped during the edit, and therefore shouldn't
+ have their revision/url info updated at the end. If a path is a
+ directory, its descendants will also be skipped. The keys are paths
+ relative to the working copy root and the values unspecified. */
+ apr_hash_t *skipped_trees;
+
+ /* A mapping from const char * repos_relpaths to the apr_hash_t * instances
+ returned from fetch_dirents_func for that repos_relpath. These
+ are used to avoid issue #3569 in specific update scenarios where a
+ restricted depth is used. */
+ apr_hash_t *dir_dirents;
+
+ /* Absolute path of the working copy root or NULL if not initialized yet */
+ const char *wcroot_abspath;
+
+ apr_pool_t *pool;
+};
+
+
+/* Record in the edit baton EB that LOCAL_ABSPATH's base version is not being
+ * updated.
+ *
+ * Add to EB->skipped_trees a copy (allocated in EB->pool) of the string
+ * LOCAL_ABSPATH.
+ */
+static svn_error_t *
+remember_skipped_tree(struct edit_baton *eb,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ svn_hash_sets(eb->skipped_trees,
+ apr_pstrdup(eb->pool,
+ svn_dirent_skip_ancestor(eb->wcroot_abspath,
+ local_abspath)),
+ (void *)1);
+
+ return SVN_NO_ERROR;
+}
+
+/* Per directory baton. Lives in its own subpool of the parent directory
+ or of the edit baton if there is no parent directory */
+struct dir_baton
+{
+ /* Basename of this directory. */
+ const char *name;
+
+ /* Absolute path of this directory */
+ const char *local_abspath;
+
+ /* The repository relative path this directory will correspond to. */
+ const char *new_relpath;
+
+ /* The revision of the directory before updating */
+ svn_revnum_t old_revision;
+
+ /* The repos_relpath before updating/switching */
+ const char *old_repos_relpath;
+
+ /* The global edit baton. */
+ struct edit_baton *edit_baton;
+
+ /* Baton for this directory's parent, or NULL if this is the root
+ directory. */
+ struct dir_baton *parent_baton;
+
+ /* Set if updates to this directory are skipped */
+ svn_boolean_t skip_this;
+
+ /* Set if there was a previous notification for this directory */
+ svn_boolean_t already_notified;
+
+ /* Set if this directory is being added during this editor drive. */
+ svn_boolean_t adding_dir;
+
+ /* Set on a node and its descendants are not present in the working copy
+ but should still be updated (not skipped). These nodes should all be
+ marked as deleted. */
+ svn_boolean_t shadowed;
+
+ /* Set on a node when the existing node is obstructed, and the edit operation
+ continues as semi-shadowed update */
+ svn_boolean_t edit_obstructed;
+
+ /* The (new) changed_* information, cached to avoid retrieving it later */
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+
+ /* If not NULL, contains a mapping of const char* basenames of children that
+ have been deleted to their svn_skel_t* tree conflicts.
+ We store this hash to allow replacements to continue under a just
+ installed tree conflict.
+
+ The add after the delete will then update the tree conflicts information
+ and reinstall it. */
+ apr_hash_t *deletion_conflicts;
+
+ /* A hash of file names (only the hash key matters) seen by add_file
+ and not yet added to the database by close_file. */
+ apr_hash_t *not_present_files;
+
+ /* Set if an unversioned dir of the same name already existed in
+ this directory. */
+ svn_boolean_t obstruction_found;
+
+ /* Set if a dir of the same name already exists and is
+ scheduled for addition without history. */
+ svn_boolean_t add_existed;
+
+ /* An array of svn_prop_t structures, representing all the property
+ changes to be applied to this directory. */
+ apr_array_header_t *propchanges;
+
+ /* A boolean indicating whether this node or one of its children has
+ received any 'real' changes. Used to avoid tree conflicts for simple
+ entryprop changes, like lock management */
+ svn_boolean_t edited;
+
+ /* The tree conflict to install once the node is really edited */
+ svn_skel_t *edit_conflict;
+
+ /* The bump information for this directory. */
+ struct bump_dir_info *bump_info;
+
+ /* The depth of the directory in the wc (or inferred if added). Not
+ used for filtering; we have a separate wrapping editor for that. */
+ svn_depth_t ambient_depth;
+
+ /* Was the directory marked as incomplete before the update?
+ (In other words, are we resuming an interrupted update?)
+
+ If WAS_INCOMPLETE is set to TRUE we expect to receive all child nodes
+ and properties for/of the directory. If WAS_INCOMPLETE is FALSE then
+ we only receive the changes in/for children and properties.*/
+ svn_boolean_t was_incomplete;
+
+ /* The pool in which this baton itself is allocated. */
+ apr_pool_t *pool;
+
+ /* how many nodes are referring to baton? */
+ int ref_count;
+
+};
+
+
+struct handler_baton
+{
+ svn_txdelta_window_handler_t apply_handler;
+ void *apply_baton;
+ apr_pool_t *pool;
+ struct file_baton *fb;
+
+ /* Where we are assembling the new file. */
+ const char *new_text_base_tmp_abspath;
+
+ /* The expected source checksum of the text source or NULL if no base
+ checksum is available (MD5 if the server provides a checksum, SHA1 if
+ the server doesn't) */
+ svn_checksum_t *expected_source_checksum;
+
+ /* Why two checksums?
+ The editor currently provides an md5 which we use to detect corruption
+ during transmission. We use the sha1 inside libsvn_wc both for pristine
+ handling and corruption detection. In the future, the editor will also
+ provide a sha1, so we may not have to calculate both, but for the time
+ being, that's the way it is. */
+
+ /* The calculated checksum of the text source or NULL if the acual
+ checksum is not being calculated. The checksum kind is identical to the
+ kind of expected_source_checksum. */
+ svn_checksum_t *actual_source_checksum;
+
+ /* The stream used to calculate the source checksums */
+ svn_stream_t *source_checksum_stream;
+
+ /* A calculated MD5 digest of NEW_TEXT_BASE_TMP_ABSPATH.
+ This is initialized to all zeroes when the baton is created, then
+ populated with the MD5 digest of the resultant fulltext after the
+ last window is handled by the handler returned from
+ apply_textdelta(). */
+ unsigned char new_text_base_md5_digest[APR_MD5_DIGESTSIZE];
+
+ /* A calculated SHA-1 of NEW_TEXT_BASE_TMP_ABSPATH, which we'll use for
+ eventually writing the pristine. */
+ svn_checksum_t * new_text_base_sha1_checksum;
+};
+
+
+/* Get an empty file in the temporary area for WRI_ABSPATH. The file will
+ not be set for automatic deletion, and the name will be returned in
+ TMP_FILENAME.
+
+ This implementation creates a new empty file with a unique name.
+
+ ### This is inefficient for callers that just want an empty file to read
+ ### from. There could be (and there used to be) a permanent, shared
+ ### empty file for this purpose.
+
+ ### This is inefficient for callers that just want to reserve a unique
+ ### file name to create later. A better way may not be readily available.
+ */
+static svn_error_t *
+get_empty_tmp_file(const char **tmp_filename,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *temp_dir_abspath;
+
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, db, wri_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_open_unique_file3(NULL, tmp_filename, temp_dir_abspath,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An APR pool cleanup handler. This runs the working queue for an
+ editor baton. */
+static apr_status_t
+cleanup_edit_baton(void *edit_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ svn_error_t *err;
+ apr_pool_t *pool = apr_pool_parent_get(eb->pool);
+
+ err = svn_wc__wq_run(eb->db, eb->wcroot_abspath,
+ NULL /* cancel_func */, NULL /* cancel_baton */,
+ pool);
+
+ if (err)
+ {
+ apr_status_t apr_err = err->apr_err;
+ svn_error_clear(err);
+ return apr_err;
+ }
+ return APR_SUCCESS;
+}
+
+/* Make a new dir baton in a subpool of PB->pool. PB is the parent baton.
+ If PATH and PB are NULL, this is the root directory of the edit; in this
+ case, make the new dir baton in a subpool of EB->pool.
+ ADDING should be TRUE if we are adding this directory. */
+static svn_error_t *
+make_dir_baton(struct dir_baton **d_p,
+ const char *path,
+ struct edit_baton *eb,
+ struct dir_baton *pb,
+ svn_boolean_t adding,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *dir_pool;
+ struct dir_baton *d;
+
+ if (pb != NULL)
+ dir_pool = svn_pool_create(pb->pool);
+ else
+ dir_pool = svn_pool_create(eb->pool);
+
+ SVN_ERR_ASSERT(path || (! pb));
+
+ /* Okay, no easy out, so allocate and initialize a dir baton. */
+ d = apr_pcalloc(dir_pool, sizeof(*d));
+
+ /* Construct the PATH and baseNAME of this directory. */
+ if (path)
+ {
+ d->name = svn_dirent_basename(path, dir_pool);
+ SVN_ERR(path_join_under_root(&d->local_abspath,
+ pb->local_abspath, d->name, dir_pool));
+ }
+ else
+ {
+ /* This is the root baton. */
+ d->name = NULL;
+ d->local_abspath = eb->anchor_abspath;
+ }
+
+ /* Figure out the new_relpath for this directory. */
+ if (eb->switch_relpath)
+ {
+ /* Handle switches... */
+
+ if (pb == NULL)
+ {
+ if (*eb->target_basename == '\0')
+ {
+ /* No parent baton and target_basename=="" means that we are
+ the target of the switch. Thus, our NEW_RELPATH will be
+ the SWITCH_RELPATH. */
+ d->new_relpath = eb->switch_relpath;
+ }
+ else
+ {
+ /* This node is NOT the target of the switch (one of our
+ children is the target); therefore, it must already exist.
+ Get its old REPOS_RELPATH, as it won't be changing. */
+ SVN_ERR(svn_wc__db_scan_base_repos(&d->new_relpath, NULL, NULL,
+ eb->db, d->local_abspath,
+ dir_pool, scratch_pool));
+ }
+ }
+ else
+ {
+ /* This directory is *not* the root (has a parent). If there is
+ no grandparent, then we may have anchored at the parent,
+ and self is the target. If we match the target, then set
+ NEW_RELPATH to the SWITCH_RELPATH.
+
+ Otherwise, we simply extend NEW_RELPATH from the parent. */
+ if (pb->parent_baton == NULL
+ && strcmp(eb->target_basename, d->name) == 0)
+ d->new_relpath = eb->switch_relpath;
+ else
+ d->new_relpath = svn_relpath_join(pb->new_relpath, d->name,
+ dir_pool);
+ }
+ }
+ else /* must be an update */
+ {
+ /* If we are adding the node, then simply extend the parent's
+ relpath for our own. */
+ if (adding)
+ {
+ SVN_ERR_ASSERT(pb != NULL);
+ d->new_relpath = svn_relpath_join(pb->new_relpath, d->name,
+ dir_pool);
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_scan_base_repos(&d->new_relpath, NULL, NULL,
+ eb->db, d->local_abspath,
+ dir_pool, scratch_pool));
+ SVN_ERR_ASSERT(d->new_relpath);
+ }
+ }
+
+ d->edit_baton = eb;
+ d->parent_baton = pb;
+ d->pool = dir_pool;
+ d->propchanges = apr_array_make(dir_pool, 1, sizeof(svn_prop_t));
+ d->obstruction_found = FALSE;
+ d->add_existed = FALSE;
+ d->ref_count = 1;
+ d->old_revision = SVN_INVALID_REVNUM;
+ d->adding_dir = adding;
+ d->changed_rev = SVN_INVALID_REVNUM;
+ d->not_present_files = apr_hash_make(dir_pool);
+
+ /* Copy some flags from the parent baton */
+ if (pb)
+ {
+ d->skip_this = pb->skip_this;
+ d->shadowed = pb->shadowed || pb->edit_obstructed;
+
+ /* the parent's bump info has one more referer */
+ pb->ref_count++;
+ }
+
+ /* The caller of this function needs to fill these in. */
+ d->ambient_depth = svn_depth_unknown;
+ d->was_incomplete = FALSE;
+
+ *d_p = d;
+ return SVN_NO_ERROR;
+}
+
+
+/* Forward declarations. */
+static svn_error_t *
+already_in_a_tree_conflict(svn_boolean_t *conflicted,
+ svn_boolean_t *ignored,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+static void
+do_notification(const struct edit_baton *eb,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ svn_wc_notify_action_t action,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_notify_t *notify;
+
+ if (eb->notify_func == NULL)
+ return;
+
+ notify = svn_wc_create_notify(local_abspath, action, scratch_pool);
+ notify->kind = kind;
+
+ (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
+}
+
+/* Decrement the directory's reference count. If it hits zero,
+ then this directory is "done". This means it is safe to clear its pool.
+
+ In addition, when the directory is "done", we recurse to possible cleanup
+ the parent directory.
+*/
+static svn_error_t *
+maybe_release_dir_info(struct dir_baton *db)
+{
+ db->ref_count--;
+
+ if (!db->ref_count)
+ {
+ struct dir_baton *pb = db->parent_baton;
+
+ svn_pool_destroy(db->pool);
+
+ if (pb)
+ SVN_ERR(maybe_release_dir_info(pb));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Per file baton. Lives in its own subpool below the pool of the parent
+ directory */
+struct file_baton
+{
+ /* Pool specific to this file_baton. */
+ apr_pool_t *pool;
+
+ /* Name of this file (its entry in the directory). */
+ const char *name;
+
+ /* Absolute path to this file */
+ const char *local_abspath;
+
+ /* The repository relative path this file will correspond to. */
+ const char *new_relpath;
+
+ /* The revision of the file before updating */
+ svn_revnum_t old_revision;
+
+ /* The repos_relpath before updating/switching */
+ const char *old_repos_relpath;
+
+ /* The global edit baton. */
+ struct edit_baton *edit_baton;
+
+ /* The parent directory of this file. */
+ struct dir_baton *dir_baton;
+
+ /* Set if updates to this directory are skipped */
+ svn_boolean_t skip_this;
+
+ /* Set if there was a previous notification */
+ svn_boolean_t already_notified;
+
+ /* Set if this file is new. */
+ svn_boolean_t adding_file;
+
+ /* Set if an unversioned file of the same name already existed in
+ this directory. */
+ svn_boolean_t obstruction_found;
+
+ /* Set if a file of the same name already exists and is
+ scheduled for addition without history. */
+ svn_boolean_t add_existed;
+
+ /* Set if this file is being added in the BASE layer, but is not-present
+ in the working copy (replaced, deleted, etc.). */
+ svn_boolean_t shadowed;
+
+ /* Set on a node when the existing node is obstructed, and the edit operation
+ continues as semi-shadowed update */
+ svn_boolean_t edit_obstructed;
+
+ /* The (new) changed_* information, cached to avoid retrieving it later */
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+
+ /* If there are file content changes, these are the checksums of the
+ resulting new text base, which is in the pristine store, else NULL. */
+ const svn_checksum_t *new_text_base_md5_checksum;
+ const svn_checksum_t *new_text_base_sha1_checksum;
+
+ /* The checksum of the file before the update */
+ const svn_checksum_t *original_checksum;
+
+ /* An array of svn_prop_t structures, representing all the property
+ changes to be applied to this file. Once a file baton is
+ initialized, this is never NULL, but it may have zero elements. */
+ apr_array_header_t *propchanges;
+
+ /* For existing files, whether there are local modifications. FALSE for added
+ files */
+ svn_boolean_t local_prop_mods;
+
+ /* Bump information for the directory this file lives in */
+ struct bump_dir_info *bump_info;
+
+ /* A boolean indicating whether this node or one of its children has
+ received any 'real' changes. Used to avoid tree conflicts for simple
+ entryprop changes, like lock management */
+ svn_boolean_t edited;
+
+ /* The tree conflict to install once the node is really edited */
+ svn_skel_t *edit_conflict;
+};
+
+
+/* Make a new file baton in a subpool of PB->pool. PB is the parent baton.
+ * PATH is relative to the root of the edit. ADDING tells whether this file
+ * is being added. */
+static svn_error_t *
+make_file_baton(struct file_baton **f_p,
+ struct dir_baton *pb,
+ const char *path,
+ svn_boolean_t adding,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = pb->edit_baton;
+ apr_pool_t *file_pool = svn_pool_create(pb->pool);
+ struct file_baton *f = apr_pcalloc(file_pool, sizeof(*f));
+
+ SVN_ERR_ASSERT(path);
+
+ /* Make the file's on-disk name. */
+ f->name = svn_dirent_basename(path, file_pool);
+ f->old_revision = SVN_INVALID_REVNUM;
+ SVN_ERR(path_join_under_root(&f->local_abspath,
+ pb->local_abspath, f->name, file_pool));
+
+ /* Figure out the new URL for this file. */
+ if (eb->switch_relpath)
+ {
+ /* Handle switches... */
+
+ /* This file has a parent directory. If there is
+ no grandparent, then we may have anchored at the parent,
+ and self is the target. If we match the target, then set
+ NEW_RELPATH to the SWITCH_RELPATH.
+
+ Otherwise, we simply extend NEW_RELPATH from the parent. */
+ if (pb->parent_baton == NULL
+ && strcmp(eb->target_basename, f->name) == 0)
+ f->new_relpath = eb->switch_relpath;
+ else
+ f->new_relpath = svn_relpath_join(pb->new_relpath, f->name,
+ file_pool);
+ }
+ else /* must be an update */
+ {
+ if (adding)
+ f->new_relpath = svn_relpath_join(pb->new_relpath, f->name, file_pool);
+ else
+ {
+ SVN_ERR(svn_wc__db_scan_base_repos(&f->new_relpath, NULL, NULL,
+ eb->db, f->local_abspath,
+ file_pool, scratch_pool));
+ SVN_ERR_ASSERT(f->new_relpath);
+ }
+ }
+
+ f->pool = file_pool;
+ f->edit_baton = pb->edit_baton;
+ f->propchanges = apr_array_make(file_pool, 1, sizeof(svn_prop_t));
+ f->bump_info = pb->bump_info;
+ f->adding_file = adding;
+ f->obstruction_found = FALSE;
+ f->add_existed = FALSE;
+ f->skip_this = pb->skip_this;
+ f->shadowed = pb->shadowed || pb->edit_obstructed;
+ f->dir_baton = pb;
+ f->changed_rev = SVN_INVALID_REVNUM;
+
+ /* the directory has one more referer now */
+ pb->ref_count++;
+
+ *f_p = f;
+ return SVN_NO_ERROR;
+}
+
+/* Complete a conflict skel by describing the update.
+ *
+ * LOCAL_KIND is the node kind of the tree conflict victim in the
+ * working copy.
+ *
+ * All temporary allocations are be made in SCRATCH_POOL, while allocations
+ * needed for the returned conflict struct are made in RESULT_POOL.
+ */
+static svn_error_t *
+complete_conflict(svn_skel_t *conflict,
+ const struct edit_baton *eb,
+ const char *local_abspath,
+ const char *old_repos_relpath,
+ svn_revnum_t old_revision,
+ const char *new_repos_relpath,
+ svn_node_kind_t local_kind,
+ svn_node_kind_t target_kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_conflict_version_t *original_version;
+ svn_wc_conflict_version_t *target_version;
+ svn_boolean_t is_complete;
+
+ if (!conflict)
+ return SVN_NO_ERROR; /* Not conflicted */
+
+ SVN_ERR(svn_wc__conflict_skel_is_complete(&is_complete, conflict));
+
+ if (is_complete)
+ return SVN_NO_ERROR; /* Already completed */
+
+ if (old_repos_relpath)
+ original_version = svn_wc_conflict_version_create2(eb->repos_root,
+ eb->repos_uuid,
+ old_repos_relpath,
+ old_revision,
+ local_kind,
+ result_pool);
+ else
+ original_version = NULL;
+
+ if (new_repos_relpath)
+ target_version = svn_wc_conflict_version_create2(eb->repos_root,
+ eb->repos_uuid,
+ new_repos_relpath,
+ *eb->target_revision,
+ target_kind,
+ result_pool);
+ else
+ target_version = NULL;
+
+ if (eb->switch_relpath)
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict,
+ original_version,
+ target_version,
+ result_pool, scratch_pool));
+ else
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict,
+ original_version,
+ target_version,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Called when a directory is really edited, to avoid marking a
+ tree conflict on a node for a no-change edit */
+static svn_error_t *
+mark_directory_edited(struct dir_baton *db, apr_pool_t *scratch_pool)
+{
+ if (db->edited)
+ return SVN_NO_ERROR;
+
+ if (db->parent_baton)
+ SVN_ERR(mark_directory_edited(db->parent_baton, scratch_pool));
+
+ db->edited = TRUE;
+
+ if (db->edit_conflict)
+ {
+ /* We have a (delayed) tree conflict to install */
+
+ SVN_ERR(complete_conflict(db->edit_conflict, db->edit_baton,
+ db->local_abspath,
+ db->old_repos_relpath, db->old_revision,
+ db->new_relpath,
+ svn_node_dir, svn_node_dir,
+ db->pool, scratch_pool));
+ SVN_ERR(svn_wc__db_op_mark_conflict(db->edit_baton->db,
+ db->local_abspath,
+ db->edit_conflict, NULL,
+ scratch_pool));
+
+ do_notification(db->edit_baton, db->local_abspath, svn_node_dir,
+ svn_wc_notify_tree_conflict, scratch_pool);
+ db->already_notified = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Called when a file is really edited, to avoid marking a
+ tree conflict on a node for a no-change edit */
+static svn_error_t *
+mark_file_edited(struct file_baton *fb, apr_pool_t *scratch_pool)
+{
+ if (fb->edited)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(mark_directory_edited(fb->dir_baton, scratch_pool));
+
+ fb->edited = TRUE;
+
+ if (fb->edit_conflict)
+ {
+ /* We have a (delayed) tree conflict to install */
+
+ SVN_ERR(complete_conflict(fb->edit_conflict, fb->edit_baton,
+ fb->local_abspath, fb->old_repos_relpath,
+ fb->old_revision, fb->new_relpath,
+ svn_node_file, svn_node_file,
+ fb->pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_op_mark_conflict(fb->edit_baton->db,
+ fb->local_abspath,
+ fb->edit_conflict, NULL,
+ scratch_pool));
+
+ do_notification(fb->edit_baton, fb->local_abspath, svn_node_file,
+ svn_wc_notify_tree_conflict, scratch_pool);
+ fb->already_notified = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Handle the next delta window of the file described by BATON. If it is
+ * the end (WINDOW == NULL), then check the checksum, store the text in the
+ * pristine store and write its details into BATON->fb->new_text_base_*. */
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct handler_baton *hb = baton;
+ struct file_baton *fb = hb->fb;
+ svn_wc__db_t *db = fb->edit_baton->db;
+ svn_error_t *err;
+
+ /* Apply this window. We may be done at that point. */
+ err = hb->apply_handler(window, hb->apply_baton);
+ if (window != NULL && !err)
+ return SVN_NO_ERROR;
+
+ if (hb->expected_source_checksum)
+ {
+ /* Close the stream to calculate HB->actual_source_md5_checksum. */
+ svn_error_t *err2 = svn_stream_close(hb->source_checksum_stream);
+
+ if (!err2)
+ {
+ SVN_ERR_ASSERT(hb->expected_source_checksum->kind ==
+ hb->actual_source_checksum->kind);
+
+ if (!svn_checksum_match(hb->expected_source_checksum,
+ hb->actual_source_checksum))
+ {
+ err = svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, err,
+ _("Checksum mismatch while updating '%s':\n"
+ " expected: %s\n"
+ " actual: %s\n"),
+ svn_dirent_local_style(fb->local_abspath, hb->pool),
+ svn_checksum_to_cstring(hb->expected_source_checksum,
+ hb->pool),
+ svn_checksum_to_cstring(hb->actual_source_checksum,
+ hb->pool));
+ }
+ }
+
+ err = svn_error_compose_create(err, err2);
+ }
+
+ if (err)
+ {
+ /* We failed to apply the delta; clean up the temporary file. */
+ svn_error_clear(svn_io_remove_file2(hb->new_text_base_tmp_abspath, TRUE,
+ hb->pool));
+ }
+ else
+ {
+ /* Tell the file baton about the new text base's checksums. */
+ fb->new_text_base_md5_checksum =
+ svn_checksum__from_digest_md5(hb->new_text_base_md5_digest, fb->pool);
+ fb->new_text_base_sha1_checksum =
+ svn_checksum_dup(hb->new_text_base_sha1_checksum, fb->pool);
+
+ /* Store the new pristine text in the pristine store now. Later, in a
+ single transaction we will update the BASE_NODE to include a
+ reference to this pristine text's checksum. */
+ SVN_ERR(svn_wc__db_pristine_install(db, hb->new_text_base_tmp_abspath,
+ fb->new_text_base_sha1_checksum,
+ fb->new_text_base_md5_checksum,
+ hb->pool));
+ }
+
+ svn_pool_destroy(hb->pool);
+
+ return err;
+}
+
+
+/* Find the last-change info within ENTRY_PROPS, and return then in the
+ CHANGED_* parameters. Each parameter will be initialized to its "none"
+ value, and will contain the relavent info if found.
+
+ CHANGED_AUTHOR will be allocated in RESULT_POOL. SCRATCH_POOL will be
+ used for some temporary allocations.
+*/
+static svn_error_t *
+accumulate_last_change(svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ const apr_array_header_t *entry_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ *changed_rev = SVN_INVALID_REVNUM;
+ *changed_date = 0;
+ *changed_author = NULL;
+
+ for (i = 0; i < entry_props->nelts; ++i)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(entry_props, i, svn_prop_t);
+
+ /* A prop value of NULL means the information was not
+ available. We don't remove this field from the entries
+ file; we have convention just leave it empty. So let's
+ just skip those entry props that have no values. */
+ if (! prop->value)
+ continue;
+
+ if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
+ *changed_author = apr_pstrdup(result_pool, prop->value->data);
+ else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
+ {
+ apr_int64_t rev;
+ SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
+ *changed_rev = (svn_revnum_t)rev;
+ }
+ else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
+ SVN_ERR(svn_time_from_cstring(changed_date, prop->value->data,
+ scratch_pool));
+
+ /* Starting with Subversion 1.7 we ignore the SVN_PROP_ENTRY_UUID
+ property here. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Join ADD_PATH to BASE_PATH. If ADD_PATH is absolute, or if any ".."
+ * component of it resolves to a path above BASE_PATH, then return
+ * SVN_ERR_WC_OBSTRUCTED_UPDATE.
+ *
+ * This is to prevent the situation where the repository contains,
+ * say, "..\nastyfile". Although that's perfectly legal on some
+ * systems, when checked out onto Win32 it would cause "nastyfile" to
+ * be created in the parent of the current edit directory.
+ *
+ * (http://cve.mitre.org/cgi-bin/cvename.cgi?name=2007-3846)
+ */
+static svn_error_t *
+path_join_under_root(const char **result_path,
+ const char *base_path,
+ const char *add_path,
+ apr_pool_t *pool)
+{
+ svn_boolean_t under_root;
+
+ SVN_ERR(svn_dirent_is_under_root(&under_root,
+ result_path, base_path, add_path, pool));
+
+ if (! under_root)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Path '%s' is not in the working copy"),
+ svn_dirent_local_style(svn_dirent_join(base_path, add_path, pool),
+ pool));
+ }
+
+ /* This catches issue #3288 */
+ if (strcmp(add_path, svn_dirent_basename(*result_path, NULL)) != 0)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("'%s' is not valid as filename in directory '%s'"),
+ svn_dirent_local_style(add_path, pool),
+ svn_dirent_local_style(base_path, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+
+ *(eb->target_revision) = target_revision;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+check_tree_conflict(svn_skel_t **pconflict,
+ struct edit_baton *eb,
+ const char *local_abspath,
+ svn_wc__db_status_t working_status,
+ svn_boolean_t exists_in_repos,
+ svn_node_kind_t expected_kind,
+ svn_wc_conflict_action_t action,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision, /* This is ignored in co */
+ apr_pool_t *pool,
+ void **dir_baton)
+{
+ struct edit_baton *eb = edit_baton;
+ struct dir_baton *db;
+ svn_boolean_t already_conflicted, conflict_ignored;
+ svn_error_t *err;
+ svn_wc__db_status_t status;
+ svn_wc__db_status_t base_status;
+ svn_node_kind_t kind;
+ svn_boolean_t have_work;
+
+ /* Note that something interesting is actually happening in this
+ edit run. */
+ eb->root_opened = TRUE;
+
+ SVN_ERR(make_dir_baton(&db, NULL, eb, NULL, FALSE, pool));
+ *dir_baton = db;
+
+ err = already_in_a_tree_conflict(&already_conflicted, &conflict_ignored,
+ eb->db, db->local_abspath, pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ already_conflicted = conflict_ignored = FALSE;
+ }
+ else if (already_conflicted)
+ {
+ /* Record a skip of both the anchor and target in the skipped tree
+ as the anchor itself might not be updated */
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+ SVN_ERR(remember_skipped_tree(eb, eb->target_abspath, pool));
+
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ /* Notify that we skipped the target, while we actually skipped
+ the anchor */
+ do_notification(eb, eb->target_abspath, svn_node_unknown,
+ svn_wc_notify_skip_conflicted, pool);
+
+ return SVN_NO_ERROR;
+ }
+
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, &db->old_revision,
+ &db->old_repos_relpath, NULL, NULL,
+ &db->changed_rev, &db->changed_date,
+ &db->changed_author, &db->ambient_depth,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, &have_work,
+ eb->db, db->local_abspath,
+ db->pool, pool));
+
+ if (conflict_ignored)
+ {
+ db->shadowed = TRUE;
+ }
+ else if (have_work)
+ {
+ const char *move_src_root_abspath;
+
+ SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, &move_src_root_abspath,
+ NULL, eb->db, db->local_abspath,
+ pool, pool));
+ if (move_src_root_abspath || *eb->target_basename == '\0')
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL,
+ &db->old_revision,
+ &db->old_repos_relpath, NULL, NULL,
+ &db->changed_rev, &db->changed_date,
+ &db->changed_author,
+ &db->ambient_depth,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ eb->db, db->local_abspath,
+ db->pool, pool));
+
+ if (move_src_root_abspath)
+ {
+ /* This is an update anchored inside a move. We need to
+ raise a move-edit tree-conflict on the move root to
+ update the move destination. */
+ svn_skel_t *tree_conflict = svn_wc__conflict_skel_create(pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ tree_conflict, eb->db, move_src_root_abspath,
+ svn_wc_conflict_reason_moved_away,
+ svn_wc_conflict_action_edit,
+ move_src_root_abspath, pool, pool));
+
+ if (strcmp(db->local_abspath, move_src_root_abspath))
+ {
+ /* We are raising the tree-conflict on some parent of
+ the edit root, we won't be handling that path again
+ so raise the conflict now. */
+ SVN_ERR(complete_conflict(tree_conflict, eb,
+ move_src_root_abspath,
+ db->old_repos_relpath,
+ db->old_revision, db->new_relpath,
+ svn_node_dir, svn_node_dir,
+ pool, pool));
+ SVN_ERR(svn_wc__db_op_mark_conflict(eb->db,
+ move_src_root_abspath,
+ tree_conflict,
+ NULL, pool));
+ do_notification(eb, move_src_root_abspath, svn_node_dir,
+ svn_wc_notify_tree_conflict, pool);
+ }
+ else
+ db->edit_conflict = tree_conflict;
+ }
+
+ db->shadowed = TRUE; /* Needed for the close_directory() on the root, to
+ make sure it doesn't use the ACTUAL tree */
+ }
+ else
+ base_status = status;
+
+ if (*eb->target_basename == '\0')
+ {
+ /* For an update with a NULL target, this is equivalent to open_dir(): */
+
+ db->was_incomplete = (base_status == svn_wc__db_status_incomplete);
+
+ /* ### TODO: Add some tree conflict and obstruction detection, etc. like
+ open_directory() does.
+ (or find a way to reuse that code here)
+
+ ### BH 2013: I don't think we need all of the detection here, as the
+ user explicitly asked to update this node. So we don't
+ have to tell that it is a local replacement/delete.
+ */
+
+ SVN_ERR(svn_wc__db_temp_op_start_directory_update(eb->db,
+ db->local_abspath,
+ db->new_relpath,
+ *eb->target_revision,
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ===================================================================== */
+/* Checking for local modifications. */
+
+/* A baton for use with modcheck_found_entry(). */
+typedef struct modcheck_baton_t {
+ svn_wc__db_t *db; /* wc_db to access nodes */
+ svn_boolean_t found_mod; /* whether a modification has been found */
+ svn_boolean_t found_not_delete; /* Found a not-delete modification */
+} modcheck_baton_t;
+
+/* An implementation of svn_wc_status_func4_t. */
+static svn_error_t *
+modcheck_callback(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ modcheck_baton_t *mb = baton;
+
+ switch (status->node_status)
+ {
+ case svn_wc_status_normal:
+ case svn_wc_status_incomplete:
+ case svn_wc_status_ignored:
+ case svn_wc_status_none:
+ case svn_wc_status_unversioned:
+ case svn_wc_status_external:
+ break;
+
+ case svn_wc_status_deleted:
+ mb->found_mod = TRUE;
+ break;
+
+ case svn_wc_status_missing:
+ case svn_wc_status_obstructed:
+ mb->found_mod = TRUE;
+ mb->found_not_delete = TRUE;
+ /* Exit from the status walker: We know what we want to know */
+ return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
+
+ default:
+ case svn_wc_status_added:
+ case svn_wc_status_replaced:
+ case svn_wc_status_modified:
+ mb->found_mod = TRUE;
+ mb->found_not_delete = TRUE;
+ /* Exit from the status walker: We know what we want to know */
+ return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *MODIFIED to true iff there are any local modifications within the
+ * tree rooted at LOCAL_ABSPATH, using DB. If *MODIFIED
+ * is set to true and all the local modifications were deletes then set
+ * *ALL_EDITS_ARE_DELETES to true, set it to false otherwise. LOCAL_ABSPATH
+ * may be a file or a directory. */
+svn_error_t *
+svn_wc__node_has_local_mods(svn_boolean_t *modified,
+ svn_boolean_t *all_edits_are_deletes,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ modcheck_baton_t modcheck_baton = { NULL, FALSE, FALSE };
+ svn_error_t *err;
+
+ modcheck_baton.db = db;
+
+ /* Walk the WC tree for status with depth infinity, looking for any local
+ * modifications. If it's a "sparse" directory, that's OK: there can be
+ * no local mods in the pieces that aren't present in the WC. */
+
+ err = svn_wc__internal_walk_status(db, local_abspath,
+ svn_depth_infinity,
+ FALSE, FALSE, FALSE, NULL,
+ modcheck_callback, &modcheck_baton,
+ cancel_func, cancel_baton,
+ scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+
+ *modified = modcheck_baton.found_mod;
+ *all_edits_are_deletes = (modcheck_baton.found_mod
+ && !modcheck_baton.found_not_delete);
+
+ return SVN_NO_ERROR;
+}
+
+/* Indicates an unset svn_wc_conflict_reason_t. */
+#define SVN_WC_CONFLICT_REASON_NONE (svn_wc_conflict_reason_t)(-1)
+
+/* Check whether the incoming change ACTION on FULL_PATH would conflict with
+ * LOCAL_ABSPATH's scheduled change. If so, then raise a tree conflict with
+ * LOCAL_ABSPATH as the victim.
+ *
+ * The edit baton EB gives information including whether the operation is
+ * an update or a switch.
+ *
+ * WORKING_STATUS is the current node status of LOCAL_ABSPATH
+ * and EXISTS_IN_REPOS specifies whether a BASE_NODE representation for exists
+ * for this node. In that case the on disk type is compared to EXPECTED_KIND.
+ *
+ * If a tree conflict reason was found for the incoming action, the resulting
+ * tree conflict info is returned in *PCONFLICT. PCONFLICT must be non-NULL,
+ * while *PCONFLICT is always overwritten.
+ *
+ * The tree conflict is allocated in RESULT_POOL. Temporary allocations use
+ * SCRATCH_POOL.
+ */
+static svn_error_t *
+check_tree_conflict(svn_skel_t **pconflict,
+ struct edit_baton *eb,
+ const char *local_abspath,
+ svn_wc__db_status_t working_status,
+ svn_boolean_t exists_in_repos,
+ svn_node_kind_t expected_kind,
+ svn_wc_conflict_action_t action,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_conflict_reason_t reason = SVN_WC_CONFLICT_REASON_NONE;
+ svn_boolean_t modified = FALSE;
+ svn_boolean_t all_mods_are_deletes = FALSE;
+ const char *move_src_op_root_abspath = NULL;
+
+ *pconflict = NULL;
+
+ /* Find out if there are any local changes to this node that may
+ * be the "reason" of a tree-conflict with the incoming "action". */
+ switch (working_status)
+ {
+ case svn_wc__db_status_added:
+ case svn_wc__db_status_moved_here:
+ case svn_wc__db_status_copied:
+ if (!exists_in_repos)
+ {
+ /* The node is locally added, and it did not exist before. This
+ * is an 'update', so the local add can only conflict with an
+ * incoming 'add'. In fact, if we receive anything else than an
+ * svn_wc_conflict_action_add (which includes 'added',
+ * 'copied-here' and 'moved-here') during update on a node that
+ * did not exist before, then something is very wrong.
+ * Note that if there was no action on the node, this code
+ * would not have been called in the first place. */
+ SVN_ERR_ASSERT(action == svn_wc_conflict_action_add);
+
+ /* Scan the addition in case our caller didn't. */
+ if (working_status == svn_wc__db_status_added)
+ SVN_ERR(svn_wc__db_scan_addition(&working_status, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (working_status == svn_wc__db_status_moved_here)
+ reason = svn_wc_conflict_reason_moved_here;
+ else
+ reason = svn_wc_conflict_reason_added;
+ }
+ else
+ {
+ /* The node is locally replaced but could also be moved-away. */
+ SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, NULL,
+ &move_src_op_root_abspath,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (move_src_op_root_abspath)
+ reason = svn_wc_conflict_reason_moved_away;
+ else
+ reason = svn_wc_conflict_reason_replaced;
+ }
+ break;
+
+
+ case svn_wc__db_status_deleted:
+ {
+ SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, NULL,
+ &move_src_op_root_abspath,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+ if (move_src_op_root_abspath)
+ reason = svn_wc_conflict_reason_moved_away;
+ else
+ reason = svn_wc_conflict_reason_deleted;
+ }
+ break;
+
+ case svn_wc__db_status_incomplete:
+ /* We used svn_wc__db_read_info(), so 'incomplete' means
+ * - there is no node in the WORKING tree
+ * - a BASE node is known to exist
+ * So the node exists and is essentially 'normal'. We still need to
+ * check prop and text mods, and those checks will retrieve the
+ * missing information (hopefully). */
+ case svn_wc__db_status_normal:
+ if (action == svn_wc_conflict_action_edit)
+ {
+ /* An edit onto a local edit or onto *no* local changes is no
+ * tree-conflict. (It's possibly a text- or prop-conflict,
+ * but we don't handle those here.)
+ *
+ * Except when there is a local obstruction
+ */
+ if (exists_in_repos)
+ {
+ svn_node_kind_t disk_kind;
+
+ SVN_ERR(svn_io_check_path(local_abspath, &disk_kind,
+ scratch_pool));
+
+ if (disk_kind != expected_kind && disk_kind != svn_node_none)
+ {
+ reason = svn_wc_conflict_reason_obstructed;
+ break;
+ }
+
+ }
+ return SVN_NO_ERROR;
+ }
+
+ /* Replace is handled as delete and then specifically in
+ add_directory() and add_file(), so we only expect deletes here */
+ SVN_ERR_ASSERT(action == svn_wc_conflict_action_delete);
+
+ /* Check if the update wants to delete or replace a locally
+ * modified node. */
+
+
+ /* Do a deep tree detection of local changes. The update editor will
+ * not visit the subdirectories of a directory that it wants to delete.
+ * Therefore, we need to start a separate crawl here. */
+
+ SVN_ERR(svn_wc__node_has_local_mods(&modified, &all_mods_are_deletes,
+ eb->db, local_abspath,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+
+ if (modified)
+ {
+ if (all_mods_are_deletes)
+ reason = svn_wc_conflict_reason_deleted;
+ else
+ reason = svn_wc_conflict_reason_edited;
+ }
+ break;
+
+ case svn_wc__db_status_server_excluded:
+ /* Not allowed to view the node. Not allowed to report tree
+ * conflicts. */
+ case svn_wc__db_status_excluded:
+ /* Locally marked as excluded. No conflicts wanted. */
+ case svn_wc__db_status_not_present:
+ /* A committed delete (but parent not updated). The delete is
+ committed, so no conflict possible during update. */
+ return SVN_NO_ERROR;
+
+ case svn_wc__db_status_base_deleted:
+ /* An internal status. Should never show up here. */
+ SVN_ERR_MALFUNCTION();
+ break;
+
+ }
+
+ if (reason == SVN_WC_CONFLICT_REASON_NONE)
+ /* No conflict with the current action. */
+ return SVN_NO_ERROR;
+
+
+ /* Sanity checks. Note that if there was no action on the node, this function
+ * would not have been called in the first place.*/
+ if (reason == svn_wc_conflict_reason_edited
+ || reason == svn_wc_conflict_reason_obstructed
+ || reason == svn_wc_conflict_reason_deleted
+ || reason == svn_wc_conflict_reason_moved_away
+ || reason == svn_wc_conflict_reason_replaced)
+ {
+ /* When the node existed before (it was locally deleted, replaced or
+ * edited), then 'update' cannot add it "again". So it can only send
+ * _action_edit, _delete or _replace. */
+ if (action != svn_wc_conflict_action_edit
+ && action != svn_wc_conflict_action_delete
+ && action != svn_wc_conflict_action_replace)
+ return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
+ _("Unexpected attempt to add a node at path '%s'"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else if (reason == svn_wc_conflict_reason_added ||
+ reason == svn_wc_conflict_reason_moved_here)
+ {
+ /* When the node did not exist before (it was locally added),
+ * then 'update' cannot want to modify it in any way.
+ * It can only send _action_add. */
+ if (action != svn_wc_conflict_action_add)
+ return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
+ _("Unexpected attempt to edit, delete, or replace "
+ "a node at path '%s'"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+
+ }
+
+
+ /* A conflict was detected. Create a conflict skel to record it. */
+ *pconflict = svn_wc__conflict_skel_create(result_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(*pconflict,
+ eb->db, local_abspath,
+ reason,
+ action,
+ move_src_op_root_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* If LOCAL_ABSPATH is inside a conflicted tree and the conflict is
+ * not a moved-away-edit conflict, set *CONFLICTED to TRUE. Otherwise
+ * set *CONFLICTED to FALSE.
+ */
+static svn_error_t *
+already_in_a_tree_conflict(svn_boolean_t *conflicted,
+ svn_boolean_t *ignored,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ const char *ancestor_abspath = local_abspath;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ *conflicted = *ignored = FALSE;
+
+ while (TRUE)
+ {
+ svn_boolean_t is_wc_root;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc__conflicted_for_update_p(conflicted, ignored, db,
+ ancestor_abspath, TRUE,
+ scratch_pool));
+ if (*conflicted || *ignored)
+ break;
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wc_root, db, ancestor_abspath,
+ iterpool));
+ if (is_wc_root)
+ break;
+
+ ancestor_abspath = svn_dirent_dirname(ancestor_abspath, scratch_pool);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Temporary helper until the new conflict handling is in place */
+static svn_error_t *
+node_already_conflicted(svn_boolean_t *conflicted,
+ svn_boolean_t *conflict_ignored,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__conflicted_for_update_p(conflicted, conflict_ignored, db,
+ local_abspath, FALSE,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ const char *base = svn_relpath_basename(path, NULL);
+ const char *local_abspath;
+ const char *repos_relpath;
+ svn_node_kind_t kind, base_kind;
+ svn_revnum_t old_revision;
+ svn_boolean_t conflicted;
+ svn_boolean_t have_work;
+ svn_skel_t *tree_conflict = NULL;
+ svn_wc__db_status_t status;
+ svn_wc__db_status_t base_status;
+ apr_pool_t *scratch_pool;
+ svn_boolean_t deleting_target;
+ svn_boolean_t deleting_switched;
+ svn_boolean_t keep_as_working = FALSE;
+ svn_boolean_t queue_deletes = TRUE;
+
+ if (pb->skip_this)
+ return SVN_NO_ERROR;
+
+ scratch_pool = svn_pool_create(pb->pool);
+
+ SVN_ERR(mark_directory_edited(pb, scratch_pool));
+
+ SVN_ERR(path_join_under_root(&local_abspath, pb->local_abspath, base,
+ scratch_pool));
+
+ deleting_target = (strcmp(local_abspath, eb->target_abspath) == 0);
+
+ /* Detect obstructing working copies */
+ {
+ svn_boolean_t is_root;
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, local_abspath,
+ scratch_pool));
+
+ if (is_root)
+ {
+ /* Just skip this node; a future update will handle it */
+ SVN_ERR(remember_skipped_tree(eb, local_abspath, pool));
+ do_notification(eb, local_abspath, svn_node_unknown,
+ svn_wc_notify_update_skip_obstruction, scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, &old_revision, &repos_relpath,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, NULL, NULL,
+ NULL, NULL, &have_work,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!have_work)
+ {
+ base_status = status;
+ base_kind = kind;
+ }
+ else
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, &old_revision,
+ &repos_relpath,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (pb->old_repos_relpath && repos_relpath)
+ {
+ const char *expected_name;
+
+ expected_name = svn_relpath_skip_ancestor(pb->old_repos_relpath,
+ repos_relpath);
+
+ deleting_switched = (!expected_name || strcmp(expected_name, base) != 0);
+ }
+ else
+ deleting_switched = FALSE;
+
+ /* Is this path a conflict victim? */
+ if (pb->shadowed)
+ conflicted = FALSE; /* Conflict applies to WORKING */
+ else if (conflicted)
+ SVN_ERR(node_already_conflicted(&conflicted, NULL,
+ eb->db, local_abspath, scratch_pool));
+ if (conflicted)
+ {
+ SVN_ERR(remember_skipped_tree(eb, local_abspath, scratch_pool));
+
+ do_notification(eb, local_abspath, svn_node_unknown,
+ svn_wc_notify_skip_conflicted,
+ scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+
+
+ /* Receive the remote removal of excluded/server-excluded/not present node.
+ Do not notify, but perform the change even when the node is shadowed */
+ if (base_status == svn_wc__db_status_not_present
+ || base_status == svn_wc__db_status_excluded
+ || base_status == svn_wc__db_status_server_excluded)
+ {
+ SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath,
+ FALSE /* keep_as_working */,
+ FALSE /* queue_deletes */,
+ SVN_INVALID_REVNUM /* not_present_rev */,
+ NULL, NULL,
+ scratch_pool));
+
+ if (deleting_target)
+ eb->target_deleted = TRUE;
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Is this path the victim of a newly-discovered tree conflict? If so,
+ * remember it and notify the client. Then (if it was existing and
+ * modified), re-schedule the node to be added back again, as a (modified)
+ * copy of the previous base version. */
+
+ /* Check for conflicts only when we haven't already recorded
+ * a tree-conflict on a parent node. */
+ if (!pb->shadowed && !pb->edit_obstructed)
+ {
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb, local_abspath,
+ status, TRUE,
+ (kind == svn_node_dir)
+ ? svn_node_dir
+ : svn_node_file,
+ svn_wc_conflict_action_delete,
+ pb->pool, scratch_pool));
+ }
+ else
+ queue_deletes = FALSE; /* There is no in-wc representation */
+
+ if (tree_conflict != NULL)
+ {
+ svn_wc_conflict_reason_t reason;
+ /* When we raise a tree conflict on a node, we don't want to mark the
+ * node as skipped, to allow a replacement to continue doing at least
+ * a bit of its work (possibly adding a not present node, for the
+ * next update) */
+ if (!pb->deletion_conflicts)
+ pb->deletion_conflicts = apr_hash_make(pb->pool);
+
+ svn_hash_sets(pb->deletion_conflicts, apr_pstrdup(pb->pool, base),
+ tree_conflict);
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
+ eb->db, local_abspath,
+ tree_conflict,
+ scratch_pool, scratch_pool));
+
+ if (reason == svn_wc_conflict_reason_edited
+ || reason == svn_wc_conflict_reason_obstructed)
+ {
+ /* The item exists locally and has some sort of local mod.
+ * It no longer exists in the repository at its target URL@REV.
+ *
+ * To prepare the "accept mine" resolution for the tree conflict,
+ * we must schedule the existing content for re-addition as a copy
+ * of what it was, but with its local modifications preserved. */
+ keep_as_working = TRUE;
+
+ /* Fall through to remove the BASE_NODEs properly, with potentially
+ keeping a not-present marker */
+ }
+ else if (reason == svn_wc_conflict_reason_deleted
+ || reason == svn_wc_conflict_reason_moved_away
+ || reason == svn_wc_conflict_reason_replaced)
+ {
+ /* The item does not exist locally because it was already shadowed.
+ * We must complete the deletion, leaving the tree conflict info
+ * as the only difference from a normal deletion. */
+
+ /* Fall through to the normal "delete" code path. */
+ }
+ else
+ SVN_ERR_MALFUNCTION(); /* other reasons are not expected here */
+ }
+
+ SVN_ERR(complete_conflict(tree_conflict, eb, local_abspath, repos_relpath,
+ old_revision, NULL,
+ (kind == svn_node_dir)
+ ? svn_node_dir
+ : svn_node_file,
+ svn_node_none,
+ pb->pool, scratch_pool));
+
+ /* Issue a wq operation to delete the BASE_NODE data and to delete actual
+ nodes based on that from disk, but leave any WORKING_NODEs on disk.
+
+ Local modifications are already turned into copies at this point.
+
+ If the thing being deleted is the *target* of this update, then
+ we need to recreate a 'deleted' entry, so that the parent can give
+ accurate reports about itself in the future. */
+ if (! deleting_target && ! deleting_switched)
+ {
+ /* Delete, and do not leave a not-present node. */
+ SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath,
+ keep_as_working, queue_deletes,
+ SVN_INVALID_REVNUM /* not_present_rev */,
+ tree_conflict, NULL,
+ scratch_pool));
+ }
+ else
+ {
+ /* Delete, leaving a not-present node. */
+ SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath,
+ keep_as_working, queue_deletes,
+ *eb->target_revision,
+ tree_conflict, NULL,
+ scratch_pool));
+ if (deleting_target)
+ eb->target_deleted = TRUE;
+ else
+ {
+ /* Don't remove the not-present marker at the final bump */
+ SVN_ERR(remember_skipped_tree(eb, local_abspath, pool));
+ }
+ }
+
+ SVN_ERR(svn_wc__wq_run(eb->db, pb->local_abspath,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+
+ /* Notify. */
+ if (tree_conflict)
+ do_notification(eb, local_abspath, svn_node_unknown,
+ svn_wc_notify_tree_conflict, scratch_pool);
+ else
+ {
+ svn_wc_notify_action_t action = svn_wc_notify_update_delete;
+ svn_node_kind_t node_kind;
+
+ if (pb->shadowed || pb->edit_obstructed)
+ action = svn_wc_notify_update_shadowed_delete;
+
+ if (kind == svn_node_dir)
+ node_kind = svn_node_dir;
+ else
+ node_kind = svn_node_file;
+
+ do_notification(eb, local_abspath, node_kind, action, scratch_pool);
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct dir_baton *db;
+ svn_node_kind_t kind;
+ svn_wc__db_status_t status;
+ svn_node_kind_t wc_kind;
+ svn_boolean_t conflicted;
+ svn_boolean_t conflict_ignored = FALSE;
+ svn_boolean_t versioned_locally_and_present;
+ svn_skel_t *tree_conflict = NULL;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(! (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev)));
+
+ SVN_ERR(make_dir_baton(&db, path, eb, pb, TRUE, pool));
+ *child_baton = db;
+
+ if (db->skip_this)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(mark_directory_edited(db, pool));
+
+ if (strcmp(eb->target_abspath, db->local_abspath) == 0)
+ {
+ /* The target of the edit is being added, give it the requested
+ depth of the edit (but convert svn_depth_unknown to
+ svn_depth_infinity). */
+ db->ambient_depth = (eb->requested_depth == svn_depth_unknown)
+ ? svn_depth_infinity : eb->requested_depth;
+ }
+ else if (eb->requested_depth == svn_depth_immediates
+ || (eb->requested_depth == svn_depth_unknown
+ && pb->ambient_depth == svn_depth_immediates))
+ {
+ db->ambient_depth = svn_depth_empty;
+ }
+ else
+ {
+ db->ambient_depth = svn_depth_infinity;
+ }
+
+ /* It may not be named the same as the administrative directory. */
+ if (svn_wc_is_adm_dir(db->name, pool))
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Failed to add directory '%s': object of the same name as the "
+ "administrative directory"),
+ svn_dirent_local_style(db->local_abspath, pool));
+
+ SVN_ERR(svn_io_check_path(db->local_abspath, &kind, db->pool));
+
+ err = svn_wc__db_read_info(&status, &wc_kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, NULL, NULL, NULL, NULL, NULL,
+ eb->db, db->local_abspath, db->pool, db->pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ wc_kind = svn_node_unknown;
+ status = svn_wc__db_status_normal;
+ conflicted = FALSE;
+
+ versioned_locally_and_present = FALSE;
+ }
+ else if (wc_kind == svn_node_dir
+ && status == svn_wc__db_status_normal)
+ {
+ /* !! We found the root of a separate working copy obstructing the wc !!
+
+ If the directory would be part of our own working copy then
+ we wouldn't have been called as an add_directory().
+
+ The only thing we can do is add a not-present node, to allow
+ a future update to bring in the new files when the problem is
+ resolved. Note that svn_wc__db_base_add_not_present_node()
+ explicitly adds the node into the parent's node database. */
+
+ SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db, db->local_abspath,
+ db->new_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *eb->target_revision,
+ svn_node_file,
+ NULL, NULL,
+ pool));
+
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ do_notification(eb, db->local_abspath, svn_node_dir,
+ svn_wc_notify_update_skip_obstruction, pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (status == svn_wc__db_status_normal
+ && (wc_kind == svn_node_file
+ || wc_kind == svn_node_symlink))
+ {
+ /* We found a file external occupating the place we need in BASE.
+
+ We can't add a not-present node in this case as that would overwrite
+ the file external. Luckily the file external itself stops us from
+ forgetting a child of this parent directory like an obstructing
+ working copy would.
+
+ The reason we get here is that the adm crawler doesn't report
+ file externals.
+ */
+
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ do_notification(eb, db->local_abspath, svn_node_file,
+ svn_wc_notify_update_skip_obstruction, pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (wc_kind == svn_node_unknown)
+ versioned_locally_and_present = FALSE; /* Tree conflict ACTUAL-only node */
+ else
+ versioned_locally_and_present = IS_NODE_PRESENT(status);
+
+ /* Is this path a conflict victim? */
+ if (conflicted)
+ {
+ if (pb->deletion_conflicts)
+ tree_conflict = svn_hash_gets(pb->deletion_conflicts, db->name);
+
+ if (tree_conflict)
+ {
+ svn_wc_conflict_reason_t reason;
+ /* So this deletion wasn't just a deletion, it is actually a
+ replacement. Let's install a better tree conflict. */
+
+ /* ### Should store the conflict in DB to allow reinstalling
+ ### with theoretically more data in close_directory() */
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
+ eb->db,
+ db->local_abspath,
+ tree_conflict,
+ db->pool, db->pool));
+
+ tree_conflict = svn_wc__conflict_skel_create(db->pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ tree_conflict,
+ eb->db, db->local_abspath,
+ reason, svn_wc_conflict_action_replace,
+ NULL,
+ db->pool, db->pool));
+
+ /* And now stop checking for conflicts here and just perform
+ a shadowed update */
+ db->edit_conflict = tree_conflict; /* Cache for close_directory */
+ tree_conflict = NULL; /* No direct notification */
+ db->shadowed = TRUE; /* Just continue */
+ conflicted = FALSE; /* No skip */
+ }
+ else
+ SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
+ eb->db, db->local_abspath, pool));
+ }
+
+ /* Now the "usual" behaviour if already conflicted. Skip it. */
+ if (conflicted)
+ {
+ /* Record this conflict so that its descendants are skipped silently. */
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ /* We skip this node, but once the update completes the parent node will
+ be updated to the new revision. So a future recursive update of the
+ parent will not bring in this new node as the revision of the parent
+ describes to the repository that all children are available.
+
+ To resolve this problem, we add a not-present node to allow bringing
+ the node in once this conflict is resolved.
+
+ Note that we can safely assume that no present base node exists,
+ because then we would not have received an add_directory.
+ */
+ SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db, db->local_abspath,
+ db->new_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *eb->target_revision,
+ svn_node_dir,
+ NULL, NULL,
+ pool));
+
+ /* ### TODO: Also print victim_path in the skip msg. */
+ do_notification(eb, db->local_abspath, svn_node_dir,
+ svn_wc_notify_skip_conflicted, pool);
+ return SVN_NO_ERROR;
+ }
+ else if (conflict_ignored)
+ {
+ db->shadowed = TRUE;
+ }
+
+ if (db->shadowed)
+ {
+ /* Nothing to check; does not and will not exist in working copy */
+ }
+ else if (versioned_locally_and_present)
+ {
+ /* What to do with a versioned or schedule-add dir:
+
+ A dir already added without history is OK. Set add_existed
+ so that user notification is delayed until after any prop
+ conflicts have been found.
+
+ An existing versioned dir is an error. In the future we may
+ relax this restriction and simply update such dirs.
+
+ A dir added with history is a tree conflict. */
+
+ svn_boolean_t local_is_non_dir;
+ svn_wc__db_status_t add_status = svn_wc__db_status_normal;
+
+ /* Is the local add a copy? */
+ if (status == svn_wc__db_status_added)
+ SVN_ERR(svn_wc__db_scan_addition(&add_status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ eb->db, db->local_abspath,
+ pool, pool));
+
+
+ /* Is there *something* that is not a dir? */
+ local_is_non_dir = (wc_kind != svn_node_dir
+ && status != svn_wc__db_status_deleted);
+
+ /* Do tree conflict checking if
+ * - if there is a local copy.
+ * - if this is a switch operation
+ * - the node kinds mismatch
+ *
+ * During switch, local adds at the same path as incoming adds get
+ * "lost" in that switching back to the original will no longer have the
+ * local add. So switch always alerts the user with a tree conflict. */
+ if (!eb->adds_as_modification
+ || local_is_non_dir
+ || add_status != svn_wc__db_status_added)
+ {
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb,
+ db->local_abspath,
+ status, FALSE, svn_node_none,
+ svn_wc_conflict_action_add,
+ pool, pool));
+ }
+
+ if (tree_conflict == NULL)
+ db->add_existed = TRUE; /* Take over WORKING */
+ else
+ db->shadowed = TRUE; /* Only update BASE */
+ }
+ else if (kind != svn_node_none)
+ {
+ /* There's an unversioned node at this path. */
+ db->obstruction_found = TRUE;
+
+ /* Unversioned, obstructing dirs are handled by prop merge/conflict,
+ * if unversioned obstructions are allowed. */
+ if (! (kind == svn_node_dir && eb->allow_unver_obstructions))
+ {
+ /* Bring in the node as deleted */ /* ### Obstructed Conflict */
+ db->shadowed = TRUE;
+
+ /* Mark a conflict */
+ tree_conflict = svn_wc__conflict_skel_create(db->pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ tree_conflict,
+ eb->db, db->local_abspath,
+ svn_wc_conflict_reason_unversioned,
+ svn_wc_conflict_action_add, NULL,
+ db->pool, pool));
+ db->edit_conflict = tree_conflict;
+ }
+ }
+
+ if (tree_conflict)
+ SVN_ERR(complete_conflict(tree_conflict, eb, db->local_abspath,
+ db->old_repos_relpath, db->old_revision,
+ db->new_relpath,
+ wc_kind,
+ svn_node_dir,
+ db->pool, pool));
+
+ SVN_ERR(svn_wc__db_base_add_incomplete_directory(
+ eb->db, db->local_abspath,
+ db->new_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *eb->target_revision,
+ db->ambient_depth,
+ (db->shadowed && db->obstruction_found),
+ (! db->shadowed
+ && status == svn_wc__db_status_added),
+ tree_conflict, NULL,
+ pool));
+
+ /* Make sure there is a real directory at LOCAL_ABSPATH, unless we are just
+ updating the DB */
+ if (!db->shadowed)
+ SVN_ERR(svn_wc__ensure_directory(db->local_abspath, pool));
+
+ if (tree_conflict != NULL)
+ {
+ db->already_notified = TRUE;
+ do_notification(eb, db->local_abspath, svn_node_dir,
+ svn_wc_notify_tree_conflict, pool);
+ }
+
+
+ /* If this add was obstructed by dir scheduled for addition without
+ history let close_directory() handle the notification because there
+ might be properties to deal with. If PATH was added inside a locally
+ deleted tree, then suppress notification, a tree conflict was already
+ issued. */
+ if (eb->notify_func && !db->already_notified && !db->add_existed)
+ {
+ svn_wc_notify_action_t action;
+
+ if (db->shadowed)
+ action = svn_wc_notify_update_shadowed_add;
+ else if (db->obstruction_found || db->add_existed)
+ action = svn_wc_notify_exists;
+ else
+ action = svn_wc_notify_update_add;
+
+ db->already_notified = TRUE;
+
+ do_notification(eb, db->local_abspath, svn_node_dir, action, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *db, *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ svn_boolean_t have_work;
+ svn_boolean_t conflicted;
+ svn_boolean_t conflict_ignored = FALSE;
+ svn_skel_t *tree_conflict = NULL;
+ svn_wc__db_status_t status, base_status;
+ svn_node_kind_t wc_kind;
+
+ SVN_ERR(make_dir_baton(&db, path, eb, pb, FALSE, pool));
+ *child_baton = db;
+
+ if (db->skip_this)
+ return SVN_NO_ERROR;
+
+ /* Detect obstructing working copies */
+ {
+ svn_boolean_t is_root;
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, db->local_abspath,
+ pool));
+
+ if (is_root)
+ {
+ /* Just skip this node; a future update will handle it */
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ do_notification(eb, db->local_abspath, svn_node_dir,
+ svn_wc_notify_update_skip_obstruction, pool);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* We should have a write lock on every directory touched. */
+ SVN_ERR(svn_wc__write_check(eb->db, db->local_abspath, pool));
+
+ SVN_ERR(svn_wc__db_read_info(&status, &wc_kind, &db->old_revision,
+ &db->old_repos_relpath, NULL, NULL,
+ &db->changed_rev, &db->changed_date,
+ &db->changed_author, &db->ambient_depth,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, NULL, NULL,
+ NULL, NULL, &have_work,
+ eb->db, db->local_abspath,
+ db->pool, pool));
+
+ if (!have_work)
+ base_status = status;
+ else
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &db->old_revision,
+ &db->old_repos_relpath, NULL, NULL,
+ &db->changed_rev, &db->changed_date,
+ &db->changed_author, &db->ambient_depth,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ eb->db, db->local_abspath,
+ db->pool, pool));
+
+ db->was_incomplete = (base_status == svn_wc__db_status_incomplete);
+
+ /* Is this path a conflict victim? */
+ if (db->shadowed)
+ conflicted = FALSE; /* Conflict applies to WORKING */
+ else if (conflicted)
+ SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
+ eb->db, db->local_abspath, pool));
+ if (conflicted)
+ {
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
+
+ db->skip_this = TRUE;
+ db->already_notified = TRUE;
+
+ do_notification(eb, db->local_abspath, svn_node_unknown,
+ svn_wc_notify_skip_conflicted, pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (conflict_ignored)
+ {
+ db->shadowed = TRUE;
+ }
+
+ /* Is this path a fresh tree conflict victim? If so, skip the tree
+ with one notification. */
+
+ /* Check for conflicts only when we haven't already recorded
+ * a tree-conflict on a parent node. */
+ if (!db->shadowed)
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb, db->local_abspath,
+ status, TRUE, svn_node_dir,
+ svn_wc_conflict_action_edit,
+ db->pool, pool));
+
+ /* Remember the roots of any locally deleted trees. */
+ if (tree_conflict != NULL)
+ {
+ svn_wc_conflict_reason_t reason;
+ db->edit_conflict = tree_conflict;
+ /* Other modifications wouldn't be a tree conflict */
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
+ eb->db, db->local_abspath,
+ tree_conflict,
+ db->pool, db->pool));
+ SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_deleted
+ || reason == svn_wc_conflict_reason_moved_away
+ || reason == svn_wc_conflict_reason_replaced
+ || reason == svn_wc_conflict_reason_obstructed);
+
+ /* Continue updating BASE */
+ if (reason == svn_wc_conflict_reason_obstructed)
+ db->edit_obstructed = TRUE;
+ else
+ db->shadowed = TRUE;
+ }
+
+ /* Mark directory as being at target_revision and URL, but incomplete. */
+ SVN_ERR(svn_wc__db_temp_op_start_directory_update(eb->db, db->local_abspath,
+ db->new_relpath,
+ *eb->target_revision,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ svn_prop_t *propchange;
+ struct dir_baton *db = dir_baton;
+
+ if (db->skip_this)
+ return SVN_NO_ERROR;
+
+ propchange = apr_array_push(db->propchanges);
+ propchange->name = apr_pstrdup(db->pool, name);
+ propchange->value = value ? svn_string_dup(value, db->pool) : NULL;
+
+ if (!db->edited && svn_property_kind2(name) == svn_prop_regular_kind)
+ SVN_ERR(mark_directory_edited(db, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* If any of the svn_prop_t objects in PROPCHANGES represents a change
+ to the SVN_PROP_EXTERNALS property, return that change, else return
+ null. If PROPCHANGES contains more than one such change, return
+ the first. */
+static const svn_prop_t *
+externals_prop_changed(const apr_array_header_t *propchanges)
+{
+ int i;
+
+ for (i = 0; i < propchanges->nelts; i++)
+ {
+ const svn_prop_t *p = &(APR_ARRAY_IDX(propchanges, i, svn_prop_t));
+ if (strcmp(p->name, SVN_PROP_EXTERNALS) == 0)
+ return p;
+ }
+
+ return NULL;
+}
+
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ struct edit_baton *eb = db->edit_baton;
+ svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
+ apr_array_header_t *entry_prop_changes;
+ apr_array_header_t *dav_prop_changes;
+ apr_array_header_t *regular_prop_changes;
+ apr_hash_t *base_props;
+ apr_hash_t *actual_props;
+ apr_hash_t *new_base_props = NULL;
+ apr_hash_t *new_actual_props = NULL;
+ svn_revnum_t new_changed_rev = SVN_INVALID_REVNUM;
+ apr_time_t new_changed_date = 0;
+ const char *new_changed_author = NULL;
+ apr_pool_t *scratch_pool = db->pool;
+ svn_skel_t *all_work_items = NULL;
+ svn_skel_t *conflict_skel = NULL;
+
+ /* Skip if we're in a conflicted tree. */
+ if (db->skip_this)
+ {
+ /* Allow the parent to complete its update. */
+ SVN_ERR(maybe_release_dir_info(db));
+
+ return SVN_NO_ERROR;
+ }
+
+ if (db->edited)
+ conflict_skel = db->edit_conflict;
+
+ SVN_ERR(svn_categorize_props(db->propchanges, &entry_prop_changes,
+ &dav_prop_changes, &regular_prop_changes, pool));
+
+ /* Fetch the existing properties. */
+ if ((!db->adding_dir || db->add_existed)
+ && !db->shadowed)
+ {
+ SVN_ERR(svn_wc__get_actual_props(&actual_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ actual_props = apr_hash_make(pool);
+
+ if (db->add_existed)
+ {
+ /* This node already exists. Grab the current pristine properties. */
+ SVN_ERR(svn_wc__db_read_pristine_props(&base_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else if (!db->adding_dir)
+ {
+ /* Get the BASE properties for proper merging. */
+ SVN_ERR(svn_wc__db_base_get_props(&base_props,
+ eb->db, db->local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ base_props = apr_hash_make(pool);
+
+ /* An incomplete directory might have props which were supposed to be
+ deleted but weren't. Because the server sent us all the props we're
+ supposed to have, any previous base props not in this list must be
+ deleted (issue #1672). */
+ if (db->was_incomplete)
+ {
+ int i;
+ apr_hash_t *props_to_delete;
+ apr_hash_index_t *hi;
+
+ /* In a copy of the BASE props, remove every property that we see an
+ incoming change for. The remaining unmentioned properties are those
+ which need to be deleted. */
+ props_to_delete = apr_hash_copy(pool, base_props);
+ for (i = 0; i < regular_prop_changes->nelts; i++)
+ {
+ const svn_prop_t *prop;
+ prop = &APR_ARRAY_IDX(regular_prop_changes, i, svn_prop_t);
+ svn_hash_sets(props_to_delete, prop->name, NULL);
+ }
+
+ /* Add these props to the incoming propchanges (in
+ * regular_prop_changes). */
+ for (hi = apr_hash_first(pool, props_to_delete);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ svn_prop_t *prop = apr_array_push(regular_prop_changes);
+
+ /* Record a deletion for PROPNAME. */
+ prop->name = propname;
+ prop->value = NULL;
+ }
+ }
+
+ /* If this directory has property changes stored up, now is the time
+ to deal with them. */
+ if (regular_prop_changes->nelts)
+ {
+ /* If recording traversal info, then see if the
+ SVN_PROP_EXTERNALS property on this directory changed,
+ and record before and after for the change. */
+ if (eb->external_func)
+ {
+ const svn_prop_t *change
+ = externals_prop_changed(regular_prop_changes);
+
+ if (change)
+ {
+ const svn_string_t *new_val_s = change->value;
+ const svn_string_t *old_val_s;
+
+ old_val_s = svn_hash_gets(base_props, SVN_PROP_EXTERNALS);
+
+ if ((new_val_s == NULL) && (old_val_s == NULL))
+ ; /* No value before, no value after... so do nothing. */
+ else if (new_val_s && old_val_s
+ && (svn_string_compare(old_val_s, new_val_s)))
+ ; /* Value did not change... so do nothing. */
+ else if (old_val_s || new_val_s)
+ /* something changed, record the change */
+ {
+ SVN_ERR((eb->external_func)(
+ eb->external_baton,
+ db->local_abspath,
+ old_val_s,
+ new_val_s,
+ db->ambient_depth,
+ db->pool));
+ }
+ }
+ }
+
+ if (db->shadowed)
+ {
+ /* We don't have a relevant actual row, but we need actual properties
+ to allow property merging without conflicts. */
+ if (db->adding_dir)
+ actual_props = apr_hash_make(scratch_pool);
+ else
+ actual_props = base_props;
+ }
+
+ /* Merge pending properties. */
+ new_base_props = svn_prop__patch(base_props, regular_prop_changes,
+ db->pool);
+ SVN_ERR_W(svn_wc__merge_props(&conflict_skel,
+ &prop_state,
+ &new_actual_props,
+ eb->db,
+ db->local_abspath,
+ NULL /* use baseprops */,
+ base_props,
+ actual_props,
+ regular_prop_changes,
+ db->pool,
+ scratch_pool),
+ _("Couldn't do property merge"));
+ /* After a (not-dry-run) merge, we ALWAYS have props to save. */
+ SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL);
+ }
+
+ SVN_ERR(accumulate_last_change(&new_changed_rev, &new_changed_date,
+ &new_changed_author, entry_prop_changes,
+ scratch_pool, scratch_pool));
+
+ /* Check if we should add some not-present markers before marking the
+ directory complete (Issue #3569) */
+ {
+ apr_hash_t *new_children = svn_hash_gets(eb->dir_dirents, db->new_relpath);
+
+ if (new_children != NULL)
+ {
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, new_children);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child_name;
+ const char *child_abspath;
+ const char *child_relpath;
+ const svn_dirent_t *dirent;
+ svn_wc__db_status_t status;
+ svn_node_kind_t child_kind;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ child_name = svn__apr_hash_index_key(hi);
+ child_abspath = svn_dirent_join(db->local_abspath, child_name,
+ iterpool);
+
+ dirent = svn__apr_hash_index_val(hi);
+ child_kind = (dirent->kind == svn_node_dir)
+ ? svn_node_dir
+ : svn_node_file;
+
+ if (db->ambient_depth < svn_depth_immediates
+ && child_kind == svn_node_dir)
+ continue; /* We don't need the subdirs */
+
+ /* ### We just check if there is some node in BASE at this path */
+ err = svn_wc__db_base_get_info(&status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ eb->db, child_abspath,
+ iterpool, iterpool);
+
+ if (!err)
+ {
+ svn_boolean_t is_wcroot;
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, eb->db, child_abspath,
+ iterpool));
+
+ if (!is_wcroot)
+ continue; /* Everything ok... Nothing to do here */
+ /* Fall through to allow recovering later */
+ }
+ else if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ child_relpath = svn_relpath_join(db->new_relpath, child_name,
+ iterpool);
+
+ SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db,
+ child_abspath,
+ child_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *eb->target_revision,
+ child_kind,
+ NULL, NULL,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+ }
+
+ if (apr_hash_count(db->not_present_files))
+ {
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* This should call some new function (which could also be used
+ for new_children above) to add all the names in single
+ transaction, but I can't even trigger it. I've tried
+ ra_local, ra_svn, ra_neon, ra_serf and they all call
+ close_file before close_dir. */
+ for (hi = apr_hash_first(scratch_pool, db->not_present_files);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child = svn__apr_hash_index_key(hi);
+ const char *child_abspath, *child_relpath;
+
+ svn_pool_clear(iterpool);
+
+ child_abspath = svn_dirent_join(db->local_abspath, child, iterpool);
+ child_relpath = svn_dirent_join(db->new_relpath, child, iterpool);
+
+ SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db,
+ child_abspath,
+ child_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *eb->target_revision,
+ svn_node_file,
+ NULL, NULL,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ /* If this directory is merely an anchor for a targeted child, then we
+ should not be updating the node at all. */
+ if (db->parent_baton == NULL
+ && *eb->target_basename != '\0')
+ {
+ /* And we should not have received any changes! */
+ SVN_ERR_ASSERT(db->propchanges->nelts == 0);
+ /* ... which also implies NEW_CHANGED_* are not set,
+ and NEW_BASE_PROPS == NULL. */
+ }
+ else
+ {
+ apr_hash_t *props;
+ apr_array_header_t *iprops = NULL;
+
+ /* ### we know a base node already exists. it was created in
+ ### open_directory or add_directory. let's just preserve the
+ ### existing DEPTH value, and possibly CHANGED_*. */
+ /* If we received any changed_* values, then use them. */
+ if (SVN_IS_VALID_REVNUM(new_changed_rev))
+ db->changed_rev = new_changed_rev;
+ if (new_changed_date != 0)
+ db->changed_date = new_changed_date;
+ if (new_changed_author != NULL)
+ db->changed_author = new_changed_author;
+
+ /* If no depth is set yet, set to infinity. */
+ if (db->ambient_depth == svn_depth_unknown)
+ db->ambient_depth = svn_depth_infinity;
+
+ if (eb->depth_is_sticky
+ && db->ambient_depth != eb->requested_depth)
+ {
+ /* After a depth upgrade the entry must reflect the new depth.
+ Upgrading to infinity changes the depth of *all* directories,
+ upgrading to something else only changes the target. */
+
+ if (eb->requested_depth == svn_depth_infinity
+ || (strcmp(db->local_abspath, eb->target_abspath) == 0
+ && eb->requested_depth > db->ambient_depth))
+ {
+ db->ambient_depth = eb->requested_depth;
+ }
+ }
+
+ /* Do we have new properties to install? Or shall we simply retain
+ the prior set of properties? If we're installing new properties,
+ then we also want to write them to an old-style props file. */
+ props = new_base_props;
+ if (props == NULL)
+ props = base_props;
+
+ if (conflict_skel)
+ {
+ svn_skel_t *work_item;
+
+ SVN_ERR(complete_conflict(conflict_skel,
+ db->edit_baton,
+ db->local_abspath,
+ db->old_repos_relpath,
+ db->old_revision,
+ db->new_relpath,
+ svn_node_dir, svn_node_dir,
+ db->pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ eb->db, db->local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+
+ /* Any inherited props to be set set for this base node? */
+ if (eb->wcroot_iprops)
+ {
+ iprops = svn_hash_gets(eb->wcroot_iprops, db->local_abspath);
+
+ /* close_edit may also update iprops for switched nodes, catching
+ those for which close_directory is never called (e.g. a switch
+ with no changes). So as a minor optimization we remove any
+ iprops from the hash so as not to set them again in
+ close_edit. */
+ if (iprops)
+ svn_hash_sets(eb->wcroot_iprops, db->local_abspath, NULL);
+ }
+
+ /* Update the BASE data for the directory and mark the directory
+ complete */
+ SVN_ERR(svn_wc__db_base_add_directory(
+ eb->db, db->local_abspath,
+ eb->wcroot_abspath,
+ db->new_relpath,
+ eb->repos_root, eb->repos_uuid,
+ *eb->target_revision,
+ props,
+ db->changed_rev, db->changed_date, db->changed_author,
+ NULL /* children */,
+ db->ambient_depth,
+ (dav_prop_changes->nelts > 0)
+ ? svn_prop_array_to_hash(dav_prop_changes, pool)
+ : NULL,
+ conflict_skel,
+ (! db->shadowed) && new_base_props != NULL,
+ new_actual_props,
+ iprops, all_work_items,
+ scratch_pool));
+ }
+
+ /* Process all of the queued work items for this directory. */
+ SVN_ERR(svn_wc__wq_run(eb->db, db->local_abspath,
+ eb->cancel_func, eb->cancel_baton,
+ scratch_pool));
+
+ if (conflict_skel && eb->conflict_func)
+ SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, db->local_abspath,
+ conflict_skel,
+ NULL /* merge_options */,
+ eb->conflict_func,
+ eb->conflict_baton,
+ eb->cancel_func,
+ eb->conflict_baton,
+ scratch_pool));
+
+ /* Notify of any prop changes on this directory -- but do nothing if
+ it's an added or skipped directory, because notification has already
+ happened in that case - unless the add was obstructed by a dir
+ scheduled for addition without history, in which case we handle
+ notification here). */
+ if (!db->already_notified && eb->notify_func && db->edited)
+ {
+ svn_wc_notify_t *notify;
+ svn_wc_notify_action_t action;
+
+ if (db->shadowed || db->edit_obstructed)
+ action = svn_wc_notify_update_shadowed_update;
+ else if (db->obstruction_found || db->add_existed)
+ action = svn_wc_notify_exists;
+ else
+ action = svn_wc_notify_update_update;
+
+ notify = svn_wc_create_notify(db->local_abspath, action, pool);
+ notify->kind = svn_node_dir;
+ notify->prop_state = prop_state;
+ notify->revision = *eb->target_revision;
+ notify->old_revision = db->old_revision;
+
+ eb->notify_func(eb->notify_baton, notify, scratch_pool);
+ }
+
+ /* We're done with this directory, so remove one reference from the
+ bump information. */
+ SVN_ERR(maybe_release_dir_info(db));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Common code for 'absent_file' and 'absent_directory'. */
+static svn_error_t *
+absent_node(const char *path,
+ svn_node_kind_t absent_kind,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ const char *name = svn_dirent_basename(path, NULL);
+ const char *local_abspath;
+ svn_error_t *err;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+
+ if (pb->skip_this)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(mark_directory_edited(pb, scratch_pool));
+
+ local_abspath = svn_dirent_join(pb->local_abspath, name, scratch_pool);
+
+ /* If an item by this name is scheduled for addition that's a
+ genuine tree-conflict. */
+ err = svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ eb->db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ status = svn_wc__db_status_not_present;
+ kind = svn_node_unknown;
+ }
+
+ if (status == svn_wc__db_status_normal
+ && kind == svn_node_dir)
+ {
+ /* We found an obstructing working copy!
+
+ We can do two things now:
+ 1) notify the user, record a skip, etc.
+ 2) Just record the absent node in BASE in the parent
+ working copy.
+
+ As option 2 happens to be exactly what we do anyway, lets do that.
+ */
+ }
+ else if (status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded)
+ {
+ /* The BASE node is not actually there, so we can safely turn it into
+ an absent node */
+ }
+ else
+ {
+ /* We have a local addition. If this would be a BASE node it would have
+ been deleted before we get here. (Which might have turned it into
+ a copy).
+
+ ### This should be recorded as a tree conflict and the update
+ ### can just continue, as we can just record the absent status
+ ### in BASE.
+ */
+ SVN_ERR_ASSERT(status != svn_wc__db_status_normal);
+
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Failed to mark '%s' absent: item of the same name is already "
+ "scheduled for addition"),
+ svn_dirent_local_style(local_abspath, pool));
+ }
+
+ {
+ const char *repos_relpath;
+ repos_relpath = svn_relpath_join(pb->new_relpath, name, scratch_pool);
+
+ /* Insert an excluded node below the parent node to note that this child
+ is absent. (This puts it in the parent db if the child is obstructed) */
+ SVN_ERR(svn_wc__db_base_add_excluded_node(eb->db, local_abspath,
+ repos_relpath, eb->repos_root,
+ eb->repos_uuid,
+ *(eb->target_revision),
+ absent_kind,
+ svn_wc__db_status_server_excluded,
+ NULL, NULL,
+ scratch_pool));
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+absent_file(const char *path,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ return absent_node(path, svn_node_file, parent_baton, pool);
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+absent_directory(const char *path,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ return absent_node(path, svn_node_dir, parent_baton, pool);
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb;
+ svn_node_kind_t kind = svn_node_none;
+ svn_node_kind_t wc_kind = svn_node_unknown;
+ svn_wc__db_status_t status = svn_wc__db_status_normal;
+ apr_pool_t *scratch_pool;
+ svn_boolean_t conflicted = FALSE;
+ svn_boolean_t conflict_ignored = FALSE;
+ svn_boolean_t versioned_locally_and_present = FALSE;
+ svn_skel_t *tree_conflict = NULL;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(! (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev)));
+
+ SVN_ERR(make_file_baton(&fb, pb, path, TRUE, pool));
+ *file_baton = fb;
+
+ if (fb->skip_this)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(mark_file_edited(fb, pool));
+
+ /* The file_pool can stick around for a *long* time, so we want to
+ use a subpool for any temporary allocations. */
+ scratch_pool = svn_pool_create(pool);
+
+
+ /* It may not be named the same as the administrative directory. */
+ if (svn_wc_is_adm_dir(fb->name, pool))
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Failed to add file '%s': object of the same name as the "
+ "administrative directory"),
+ svn_dirent_local_style(fb->local_abspath, pool));
+
+ if (!eb->clean_checkout)
+ {
+ SVN_ERR(svn_io_check_path(fb->local_abspath, &kind, scratch_pool));
+
+ err = svn_wc__db_read_info(&status, &wc_kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, NULL, NULL, NULL, NULL, NULL,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool);
+ }
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ wc_kind = svn_node_unknown;
+ conflicted = FALSE;
+
+ versioned_locally_and_present = FALSE;
+ }
+ else if (wc_kind == svn_node_dir
+ && status == svn_wc__db_status_normal)
+ {
+ /* !! We found the root of a separate working copy obstructing the wc !!
+
+ If the directory would be part of our own working copy then
+ we wouldn't have been called as an add_file().
+
+ The only thing we can do is add a not-present node, to allow
+ a future update to bring in the new files when the problem is
+ resolved. */
+ svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name),
+ (void *)1);
+
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
+ fb->skip_this = TRUE;
+ fb->already_notified = TRUE;
+
+ do_notification(eb, fb->local_abspath, svn_node_file,
+ svn_wc_notify_update_skip_obstruction, scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (status == svn_wc__db_status_normal
+ && (wc_kind == svn_node_file
+ || wc_kind == svn_node_symlink))
+ {
+ /* We found a file external occupating the place we need in BASE.
+
+ We can't add a not-present node in this case as that would overwrite
+ the file external. Luckily the file external itself stops us from
+ forgetting a child of this parent directory like an obstructing
+ working copy would.
+
+ The reason we get here is that the adm crawler doesn't report
+ file externals.
+ */
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
+ fb->skip_this = TRUE;
+ fb->already_notified = TRUE;
+
+ do_notification(eb, fb->local_abspath, svn_node_file,
+ svn_wc_notify_update_skip_obstruction, scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (wc_kind == svn_node_unknown)
+ versioned_locally_and_present = FALSE; /* Tree conflict ACTUAL-only node */
+ else
+ versioned_locally_and_present = IS_NODE_PRESENT(status);
+
+
+ /* Is this path a conflict victim? */
+ if (fb->shadowed)
+ conflicted = FALSE; /* Conflict applies to WORKING */
+ else if (conflicted)
+ {
+ if (pb->deletion_conflicts)
+ tree_conflict = svn_hash_gets(pb->deletion_conflicts, fb->name);
+
+ if (tree_conflict)
+ {
+ svn_wc_conflict_reason_t reason;
+ /* So this deletion wasn't just a deletion, it is actually a
+ replacement. Let's install a better tree conflict. */
+
+ /* ### Should store the conflict in DB to allow reinstalling
+ ### with theoretically more data in close_directory() */
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
+ eb->db,
+ fb->local_abspath,
+ tree_conflict,
+ fb->pool, fb->pool));
+
+ tree_conflict = svn_wc__conflict_skel_create(fb->pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ tree_conflict,
+ eb->db, fb->local_abspath,
+ reason, svn_wc_conflict_action_replace,
+ NULL,
+ fb->pool, fb->pool));
+
+ /* And now stop checking for conflicts here and just perform
+ a shadowed update */
+ fb->edit_conflict = tree_conflict; /* Cache for close_file */
+ tree_conflict = NULL; /* No direct notification */
+ fb->shadowed = TRUE; /* Just continue */
+ conflicted = FALSE; /* No skip */
+ }
+ else
+ SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
+ eb->db, fb->local_abspath, pool));
+ }
+
+ /* Now the usual conflict handling: skip. */
+ if (conflicted)
+ {
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
+
+ fb->skip_this = TRUE;
+ fb->already_notified = TRUE;
+
+ /* We skip this node, but once the update completes the parent node will
+ be updated to the new revision. So a future recursive update of the
+ parent will not bring in this new node as the revision of the parent
+ describes to the repository that all children are available.
+
+ To resolve this problem, we add a not-present node to allow bringing
+ the node in once this conflict is resolved.
+
+ Note that we can safely assume that no present base node exists,
+ because then we would not have received an add_file.
+ */
+ svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name),
+ (void *)1);
+
+ do_notification(eb, fb->local_abspath, svn_node_unknown,
+ svn_wc_notify_skip_conflicted, scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (conflict_ignored)
+ {
+ fb->shadowed = TRUE;
+ }
+
+ if (fb->shadowed)
+ {
+ /* Nothing to check; does not and will not exist in working copy */
+ }
+ else if (versioned_locally_and_present)
+ {
+ /* What to do with a versioned or schedule-add file:
+
+ If the UUID doesn't match the parent's, or the URL isn't a child of
+ the parent dir's URL, it's an error.
+
+ Set add_existed so that user notification is delayed until after any
+ text or prop conflicts have been found.
+
+ Whether the incoming add is a symlink or a file will only be known in
+ close_file(), when the props are known. So with a locally added file
+ or symlink, let close_file() check for a tree conflict.
+
+ We will never see missing files here, because these would be
+ re-added during the crawler phase. */
+ svn_boolean_t local_is_file;
+
+ /* Is the local node a copy or move */
+ if (status == svn_wc__db_status_added)
+ SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Is there something that is a file? */
+ local_is_file = (wc_kind == svn_node_file
+ || wc_kind == svn_node_symlink);
+
+ /* Do tree conflict checking if
+ * - if there is a local copy.
+ * - if this is a switch operation
+ * - the node kinds mismatch
+ *
+ * During switch, local adds at the same path as incoming adds get
+ * "lost" in that switching back to the original will no longer have the
+ * local add. So switch always alerts the user with a tree conflict. */
+ if (!eb->adds_as_modification
+ || !local_is_file
+ || status != svn_wc__db_status_added)
+ {
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb,
+ fb->local_abspath,
+ status, FALSE, svn_node_none,
+ svn_wc_conflict_action_add,
+ scratch_pool, scratch_pool));
+ }
+
+ if (tree_conflict == NULL)
+ fb->add_existed = TRUE; /* Take over WORKING */
+ else
+ fb->shadowed = TRUE; /* Only update BASE */
+
+ }
+ else if (kind != svn_node_none)
+ {
+ /* There's an unversioned node at this path. */
+ fb->obstruction_found = TRUE;
+
+ /* Unversioned, obstructing files are handled by text merge/conflict,
+ * if unversioned obstructions are allowed. */
+ if (! (kind == svn_node_file && eb->allow_unver_obstructions))
+ {
+ /* Bring in the node as deleted */ /* ### Obstructed Conflict */
+ fb->shadowed = TRUE;
+
+ /* Mark a conflict */
+ tree_conflict = svn_wc__conflict_skel_create(fb->pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ tree_conflict,
+ eb->db, fb->local_abspath,
+ svn_wc_conflict_reason_unversioned,
+ svn_wc_conflict_action_add,
+ NULL,
+ fb->pool, scratch_pool));
+ }
+ }
+
+ /* When this is not the update target add a not-present BASE node now,
+ to allow marking the parent directory complete in its close_edit() call.
+ This resolves issues when that occurs before the close_file(). */
+ if (pb->parent_baton
+ || *eb->target_basename == '\0'
+ || (strcmp(fb->local_abspath, eb->target_abspath) != 0))
+ {
+ svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name),
+ (void *)1);
+ }
+
+ if (tree_conflict != NULL)
+ {
+ SVN_ERR(complete_conflict(tree_conflict,
+ fb->edit_baton,
+ fb->local_abspath,
+ fb->old_repos_relpath,
+ fb->old_revision,
+ fb->new_relpath,
+ wc_kind,
+ svn_node_file,
+ fb->pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_op_mark_conflict(eb->db,
+ fb->local_abspath,
+ tree_conflict, NULL,
+ scratch_pool));
+
+ fb->already_notified = TRUE;
+ do_notification(eb, fb->local_abspath, svn_node_file,
+ svn_wc_notify_tree_conflict, scratch_pool);
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct edit_baton *eb = pb->edit_baton;
+ struct file_baton *fb;
+ svn_boolean_t conflicted;
+ svn_boolean_t conflict_ignored = FALSE;
+ svn_boolean_t have_work;
+ svn_wc__db_status_t status;
+ svn_node_kind_t wc_kind;
+ svn_skel_t *tree_conflict = NULL;
+
+ /* the file_pool can stick around for a *long* time, so we want to use
+ a subpool for any temporary allocations. */
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(make_file_baton(&fb, pb, path, FALSE, pool));
+ *file_baton = fb;
+
+ if (fb->skip_this)
+ return SVN_NO_ERROR;
+
+ /* Detect obstructing working copies */
+ {
+ svn_boolean_t is_root;
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, fb->local_abspath,
+ pool));
+
+ if (is_root)
+ {
+ /* Just skip this node; a future update will handle it */
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
+ fb->skip_this = TRUE;
+ fb->already_notified = TRUE;
+
+ do_notification(eb, fb->local_abspath, svn_node_file,
+ svn_wc_notify_update_skip_obstruction, pool);
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Sanity check. */
+
+ /* If replacing, make sure the .svn entry already exists. */
+ SVN_ERR(svn_wc__db_read_info(&status, &wc_kind, &fb->old_revision,
+ &fb->old_repos_relpath, NULL, NULL,
+ &fb->changed_rev, &fb->changed_date,
+ &fb->changed_author, NULL,
+ &fb->original_checksum, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ &conflicted, NULL, NULL, &fb->local_prop_mods,
+ NULL, NULL, &have_work,
+ eb->db, fb->local_abspath,
+ fb->pool, scratch_pool));
+
+ if (have_work)
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &fb->old_revision,
+ &fb->old_repos_relpath, NULL, NULL,
+ &fb->changed_rev, &fb->changed_date,
+ &fb->changed_author, NULL,
+ &fb->original_checksum, NULL, NULL,
+ NULL, NULL, NULL,
+ eb->db, fb->local_abspath,
+ fb->pool, scratch_pool));
+
+ /* Is this path a conflict victim? */
+ if (fb->shadowed)
+ conflicted = FALSE; /* Conflict applies to WORKING */
+ else if (conflicted)
+ SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
+ eb->db, fb->local_abspath, pool));
+ if (conflicted)
+ {
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
+
+ fb->skip_this = TRUE;
+ fb->already_notified = TRUE;
+
+ do_notification(eb, fb->local_abspath, svn_node_unknown,
+ svn_wc_notify_skip_conflicted, scratch_pool);
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+ }
+ else if (conflict_ignored)
+ {
+ fb->shadowed = TRUE;
+ }
+
+ /* Check for conflicts only when we haven't already recorded
+ * a tree-conflict on a parent node. */
+ if (!fb->shadowed)
+ SVN_ERR(check_tree_conflict(&tree_conflict, eb, fb->local_abspath,
+ status, TRUE, svn_node_file,
+ svn_wc_conflict_action_edit,
+ fb->pool, scratch_pool));
+
+ /* Is this path the victim of a newly-discovered tree conflict? */
+ if (tree_conflict != NULL)
+ {
+ svn_wc_conflict_reason_t reason;
+ fb->edit_conflict = tree_conflict;
+ /* Other modifications wouldn't be a tree conflict */
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
+ eb->db, fb->local_abspath,
+ tree_conflict,
+ scratch_pool, scratch_pool));
+ SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_deleted
+ || reason == svn_wc_conflict_reason_moved_away
+ || reason == svn_wc_conflict_reason_replaced
+ || reason == svn_wc_conflict_reason_obstructed);
+
+ /* Continue updating BASE */
+ if (reason == svn_wc_conflict_reason_obstructed)
+ fb->edit_obstructed = TRUE;
+ else
+ fb->shadowed = TRUE;
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_stream_lazyopen_func_t. */
+static svn_error_t *
+lazy_open_source(svn_stream_t **stream,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct file_baton *fb = baton;
+
+ SVN_ERR(svn_wc__db_pristine_read(stream, NULL, fb->edit_baton->db,
+ fb->local_abspath,
+ fb->original_checksum,
+ result_pool, scratch_pool));
+
+
+ return SVN_NO_ERROR;
+}
+
+struct lazy_target_baton {
+ struct file_baton *fb;
+ struct handler_baton *hb;
+ struct edit_baton *eb;
+};
+
+/* Implements svn_stream_lazyopen_func_t. */
+static svn_error_t *
+lazy_open_target(svn_stream_t **stream,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct lazy_target_baton *tb = baton;
+
+ SVN_ERR(svn_wc__open_writable_base(stream, &tb->hb->new_text_base_tmp_abspath,
+ NULL, &tb->hb->new_text_base_sha1_checksum,
+ tb->fb->edit_baton->db,
+ tb->eb->wcroot_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *expected_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+ apr_pool_t *handler_pool = svn_pool_create(fb->pool);
+ struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
+ struct edit_baton *eb = fb->edit_baton;
+ const svn_checksum_t *recorded_base_checksum;
+ svn_checksum_t *expected_base_checksum;
+ svn_stream_t *source;
+ struct lazy_target_baton *tb;
+ svn_stream_t *target;
+
+ if (fb->skip_this)
+ {
+ *handler = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(mark_file_edited(fb, pool));
+
+ /* Parse checksum or sets expected_base_checksum to NULL */
+ SVN_ERR(svn_checksum_parse_hex(&expected_base_checksum, svn_checksum_md5,
+ expected_checksum, pool));
+
+ /* Before applying incoming svndiff data to text base, make sure
+ text base hasn't been corrupted, and that its checksum
+ matches the expected base checksum. */
+
+ /* The incoming delta is targeted against EXPECTED_BASE_CHECKSUM. Find and
+ check our RECORDED_BASE_CHECKSUM. (In WC-1, we could not do this test
+ for replaced nodes because we didn't store the checksum of the "revert
+ base". In WC-NG, we do and we can.) */
+ recorded_base_checksum = fb->original_checksum;
+
+ /* If we have a checksum that we want to compare to a MD5 checksum,
+ ensure that it is a MD5 checksum */
+ if (recorded_base_checksum
+ && expected_base_checksum
+ && recorded_base_checksum->kind != svn_checksum_md5)
+ SVN_ERR(svn_wc__db_pristine_get_md5(&recorded_base_checksum,
+ eb->db, eb->wcroot_abspath,
+ recorded_base_checksum, pool, pool));
+
+
+ if (!svn_checksum_match(expected_base_checksum, recorded_base_checksum))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
+ _("Checksum mismatch for '%s':\n"
+ " expected: %s\n"
+ " recorded: %s\n"),
+ svn_dirent_local_style(fb->local_abspath, pool),
+ svn_checksum_to_cstring_display(expected_base_checksum,
+ pool),
+ svn_checksum_to_cstring_display(recorded_base_checksum,
+ pool));
+
+ /* Open the text base for reading, unless this is an added file. */
+
+ /*
+ kff todo: what we really need to do here is:
+
+ 1. See if there's a file or dir by this name already here.
+ 2. See if it's under revision control.
+ 3. If both are true, open text-base.
+ 4. If only 1 is true, bail, because we can't go destroying user's
+ files (or as an alternative to bailing, move it to some tmp
+ name and somehow tell the user, but communicating with the
+ user without erroring is a whole callback system we haven't
+ finished inventing yet.)
+ */
+
+ if (! fb->adding_file)
+ {
+ SVN_ERR_ASSERT(!fb->original_checksum
+ || fb->original_checksum->kind == svn_checksum_sha1);
+
+ source = svn_stream_lazyopen_create(lazy_open_source, fb, FALSE,
+ handler_pool);
+ }
+ else
+ {
+ source = svn_stream_empty(handler_pool);
+ }
+
+ /* If we don't have a recorded checksum, use the ra provided checksum */
+ if (!recorded_base_checksum)
+ recorded_base_checksum = expected_base_checksum;
+
+ /* Checksum the text base while applying deltas */
+ if (recorded_base_checksum)
+ {
+ hb->expected_source_checksum = svn_checksum_dup(recorded_base_checksum,
+ handler_pool);
+
+ /* Wrap stream and store reference to allow calculating the
+ checksum. */
+ source = svn_stream_checksummed2(source,
+ &hb->actual_source_checksum,
+ NULL, recorded_base_checksum->kind,
+ TRUE, handler_pool);
+ hb->source_checksum_stream = source;
+ }
+
+ tb = apr_palloc(handler_pool, sizeof(struct lazy_target_baton));
+ tb->hb = hb;
+ tb->fb = fb;
+ tb->eb = eb;
+ target = svn_stream_lazyopen_create(lazy_open_target, tb, TRUE, handler_pool);
+
+ /* Prepare to apply the delta. */
+ svn_txdelta_apply(source, target,
+ hb->new_text_base_md5_digest,
+ hb->new_text_base_tmp_abspath /* error_info */,
+ handler_pool,
+ &hb->apply_handler, &hb->apply_baton);
+
+ hb->pool = handler_pool;
+ hb->fb = fb;
+
+ /* We're all set. */
+ *handler_baton = hb;
+ *handler = window_handler;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct file_baton *fb = file_baton;
+ svn_prop_t *propchange;
+
+ if (fb->skip_this)
+ return SVN_NO_ERROR;
+
+ /* Push a new propchange to the file baton's array of propchanges */
+ propchange = apr_array_push(fb->propchanges);
+ propchange->name = apr_pstrdup(fb->pool, name);
+ propchange->value = value ? svn_string_dup(value, fb->pool) : NULL;
+
+ if (!fb->edited && svn_property_kind2(name) == svn_prop_regular_kind)
+ SVN_ERR(mark_file_edited(fb, scratch_pool));
+
+ if (! fb->shadowed
+ && strcmp(name, SVN_PROP_SPECIAL) == 0)
+ {
+ struct edit_baton *eb = fb->edit_baton;
+ svn_boolean_t modified = FALSE;
+ svn_boolean_t becomes_symlink;
+ svn_boolean_t was_symlink;
+
+ /* Let's see if we have a change as in some scenarios servers report
+ non-changes of properties. */
+ becomes_symlink = (value != NULL);
+
+ if (fb->adding_file)
+ was_symlink = becomes_symlink; /* No change */
+ else
+ {
+ apr_hash_t *props;
+
+ /* We read the server-props, not the ACTUAL props here as we just
+ want to see if this is really an incoming prop change. */
+ SVN_ERR(svn_wc__db_base_get_props(&props, eb->db,
+ fb->local_abspath,
+ scratch_pool, scratch_pool));
+
+ was_symlink = ((props
+ && svn_hash_gets(props, SVN_PROP_SPECIAL) != NULL)
+ ? svn_tristate_true
+ : svn_tristate_false);
+ }
+
+ if (was_symlink != becomes_symlink)
+ {
+ /* If the local node was not modified, we continue as usual, if
+ modified we want a tree conflict just like how we would handle
+ it when receiving a delete + add (aka "replace") */
+ if (fb->local_prop_mods)
+ modified = TRUE;
+ else
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified, eb->db,
+ fb->local_abspath,
+ FALSE, scratch_pool));
+ }
+
+ if (modified)
+ {
+ if (!fb->edit_conflict)
+ fb->edit_conflict = svn_wc__conflict_skel_create(fb->pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ fb->edit_conflict,
+ eb->db, fb->local_abspath,
+ svn_wc_conflict_reason_edited,
+ svn_wc_conflict_action_replace,
+ NULL,
+ fb->pool, scratch_pool));
+
+ SVN_ERR(complete_conflict(fb->edit_conflict, fb->edit_baton,
+ fb->local_abspath, fb->old_repos_relpath,
+ fb->old_revision, fb->new_relpath,
+ svn_node_file, svn_node_file,
+ fb->pool, scratch_pool));
+
+ /* Create a copy of the existing (pre update) BASE node in WORKING,
+ mark a tree conflict and handle the rest of the update as
+ shadowed */
+ SVN_ERR(svn_wc__db_op_make_copy(eb->db, fb->local_abspath,
+ fb->edit_conflict, NULL,
+ scratch_pool));
+
+ do_notification(eb, fb->local_abspath, svn_node_file,
+ svn_wc_notify_tree_conflict, scratch_pool);
+
+ /* Ok, we introduced a replacement, so we can now handle the rest
+ as a normal shadowed update */
+ fb->shadowed = TRUE;
+ fb->add_existed = FALSE;
+ fb->already_notified = TRUE;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Perform the actual merge of file changes between an original file,
+ identified by ORIGINAL_CHECKSUM (an empty file if NULL) to a new file
+ identified by NEW_CHECKSUM.
+
+ Merge the result into LOCAL_ABSPATH, which is part of the working copy
+ identified by WRI_ABSPATH. Use OLD_REVISION and TARGET_REVISION for naming
+ the intermediate files.
+
+ The rest of the arguments are passed to svn_wc__internal_merge().
+ */
+svn_error_t *
+svn_wc__perform_file_merge(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ svn_boolean_t *found_conflict,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const svn_checksum_t *new_checksum,
+ const svn_checksum_t *original_checksum,
+ apr_hash_t *old_actual_props,
+ const apr_array_header_t *ext_patterns,
+ svn_revnum_t old_revision,
+ svn_revnum_t target_revision,
+ const apr_array_header_t *propchanges,
+ const char *diff3_cmd,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Actual file exists and has local mods:
+ Now we need to let loose svn_wc__internal_merge() to merge
+ the textual changes into the working file. */
+ const char *oldrev_str, *newrev_str, *mine_str;
+ const char *merge_left;
+ svn_boolean_t delete_left = FALSE;
+ const char *path_ext = "";
+ const char *new_text_base_tmp_abspath;
+ enum svn_wc_merge_outcome_t merge_outcome = svn_wc_merge_unchanged;
+ svn_skel_t *work_item;
+
+ *work_items = NULL;
+
+ SVN_ERR(svn_wc__db_pristine_get_path(&new_text_base_tmp_abspath,
+ db, wri_abspath, new_checksum,
+ scratch_pool, scratch_pool));
+
+ /* If we have any file extensions we're supposed to
+ preserve in generated conflict file names, then find
+ this path's extension. But then, if it isn't one of
+ the ones we want to keep in conflict filenames,
+ pretend it doesn't have an extension at all. */
+ if (ext_patterns && ext_patterns->nelts)
+ {
+ svn_path_splitext(NULL, &path_ext, local_abspath, scratch_pool);
+ if (! (*path_ext && svn_cstring_match_glob_list(path_ext, ext_patterns)))
+ path_ext = "";
+ }
+
+ /* old_revision can be invalid when the conflict is against a
+ local addition */
+ if (!SVN_IS_VALID_REVNUM(old_revision))
+ old_revision = 0;
+
+ oldrev_str = apr_psprintf(scratch_pool, ".r%ld%s%s",
+ old_revision,
+ *path_ext ? "." : "",
+ *path_ext ? path_ext : "");
+
+ newrev_str = apr_psprintf(scratch_pool, ".r%ld%s%s",
+ target_revision,
+ *path_ext ? "." : "",
+ *path_ext ? path_ext : "");
+ mine_str = apr_psprintf(scratch_pool, ".mine%s%s",
+ *path_ext ? "." : "",
+ *path_ext ? path_ext : "");
+
+ if (! original_checksum)
+ {
+ SVN_ERR(get_empty_tmp_file(&merge_left, db, wri_abspath,
+ result_pool, scratch_pool));
+ delete_left = TRUE;
+ }
+ else
+ SVN_ERR(svn_wc__db_pristine_get_path(&merge_left, db, wri_abspath,
+ original_checksum,
+ result_pool, scratch_pool));
+
+ /* Merge the changes from the old textbase to the new
+ textbase into the file we're updating.
+ Remember that this function wants full paths! */
+ SVN_ERR(svn_wc__internal_merge(&work_item,
+ conflict_skel,
+ &merge_outcome,
+ db,
+ merge_left,
+ new_text_base_tmp_abspath,
+ local_abspath,
+ wri_abspath,
+ oldrev_str, newrev_str, mine_str,
+ old_actual_props,
+ FALSE /* dry_run */,
+ diff3_cmd, NULL, propchanges,
+ cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+ *found_conflict = (merge_outcome == svn_wc_merge_conflict);
+
+ /* If we created a temporary left merge file, get rid of it. */
+ if (delete_left)
+ {
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, wri_abspath,
+ merge_left,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* This is the small planet. It has the complex responsibility of
+ * "integrating" a new revision of a file into a working copy.
+ *
+ * Given a file_baton FB for a file either already under version control, or
+ * prepared (see below) to join version control, fully install a
+ * new revision of the file.
+ *
+ * ### transitional: installation of the working file will be handled
+ * ### by the *INSTALL_PRISTINE flag.
+ *
+ * By "install", we mean: create a new text-base and prop-base, merge
+ * any textual and property changes into the working file, and finally
+ * update all metadata so that the working copy believes it has a new
+ * working revision of the file. All of this work includes being
+ * sensitive to eol translation, keyword substitution, and performing
+ * all actions accumulated the parent directory's work queue.
+ *
+ * Set *CONTENT_STATE to the state of the contents after the
+ * installation.
+ *
+ * Return values are allocated in RESULT_POOL and temporary allocations
+ * are performed in SCRATCH_POOL.
+ */
+static svn_error_t *
+merge_file(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ svn_boolean_t *install_pristine,
+ const char **install_from,
+ svn_wc_notify_state_t *content_state,
+ struct file_baton *fb,
+ apr_hash_t *actual_props,
+ apr_time_t last_changed_date,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb = fb->edit_baton;
+ struct dir_baton *pb = fb->dir_baton;
+ svn_boolean_t is_locally_modified;
+ svn_boolean_t found_text_conflict = FALSE;
+
+ SVN_ERR_ASSERT(! fb->shadowed
+ && ! fb->obstruction_found
+ && ! fb->edit_obstructed);
+
+ /*
+ When this function is called on file F, we assume the following
+ things are true:
+
+ - The new pristine text of F is present in the pristine store
+ iff FB->NEW_TEXT_BASE_SHA1_CHECKSUM is not NULL.
+
+ - The WC metadata still reflects the old version of F.
+ (We can still access the old pristine base text of F.)
+
+ The goal is to update the local working copy of F to reflect
+ the changes received from the repository, preserving any local
+ modifications.
+ */
+
+ *work_items = NULL;
+ *install_pristine = FALSE;
+ *install_from = NULL;
+
+ /* Start by splitting the file path, getting an access baton for the parent,
+ and an entry for the file if any. */
+
+ /* Has the user made local mods to the working file?
+ Note that this compares to the current pristine file, which is
+ different from fb->old_text_base_path if we have a replaced-with-history
+ file. However, in the case we had an obstruction, we check against the
+ new text base.
+ */
+ if (fb->adding_file && !fb->add_existed)
+ {
+ is_locally_modified = FALSE; /* There is no file: Don't check */
+ }
+ else
+ {
+ /* The working file is not an obstruction.
+ So: is the file modified, relative to its ORIGINAL pristine?
+
+ This function sets is_locally_modified to FALSE for
+ files that do not exist and for directories. */
+
+ SVN_ERR(svn_wc__internal_file_modified_p(&is_locally_modified,
+ eb->db, fb->local_abspath,
+ FALSE /* exact_comparison */,
+ scratch_pool));
+ }
+
+ /* For 'textual' merging, we use the following system:
+
+ When a file is modified and we have a new BASE:
+ - For text files
+ * svn_wc_merge uses diff3
+ * possibly makes backups and marks files as conflicted.
+
+ - For binary files
+ * svn_wc_merge makes backups and marks files as conflicted.
+
+ If a file is not modified and we have a new BASE:
+ * Install from pristine.
+
+ If we have property changes related to magic properties or if the
+ svn:keywords property is set:
+ * Retranslate from the working file.
+ */
+ if (! is_locally_modified
+ && fb->new_text_base_sha1_checksum)
+ {
+ /* If there are no local mods, who cares whether it's a text
+ or binary file! Just write a log command to overwrite
+ any working file with the new text-base. If newline
+ conversion or keyword substitution is activated, this
+ will happen as well during the copy.
+ For replaced files, though, we want to merge in the changes
+ even if the file is not modified compared to the (non-revert)
+ text-base. */
+
+ *install_pristine = TRUE;
+ }
+ else if (fb->new_text_base_sha1_checksum)
+ {
+ /* Actual file exists and has local mods:
+ Now we need to let loose svn_wc__merge_internal() to merge
+ the textual changes into the working file. */
+ SVN_ERR(svn_wc__perform_file_merge(work_items,
+ conflict_skel,
+ &found_text_conflict,
+ eb->db,
+ fb->local_abspath,
+ pb->local_abspath,
+ fb->new_text_base_sha1_checksum,
+ fb->add_existed
+ ? NULL
+ : fb->original_checksum,
+ actual_props,
+ eb->ext_patterns,
+ fb->old_revision,
+ *eb->target_revision,
+ fb->propchanges,
+ eb->diff3_cmd,
+ eb->cancel_func, eb->cancel_baton,
+ result_pool, scratch_pool));
+ } /* end: working file exists and has mods */
+ else
+ {
+ /* There is no new text base, but let's see if the working file needs
+ to be updated for any other reason. */
+
+ apr_hash_t *keywords;
+
+ /* Determine if any of the propchanges are the "magic" ones that
+ might require changing the working file. */
+ svn_boolean_t magic_props_changed;
+
+ magic_props_changed = svn_wc__has_magic_property(fb->propchanges);
+
+ SVN_ERR(svn_wc__get_translate_info(NULL, NULL,
+ &keywords,
+ NULL,
+ eb->db, fb->local_abspath,
+ actual_props, TRUE,
+ scratch_pool, scratch_pool));
+ if (magic_props_changed || keywords)
+ {
+ /* Special edge-case: it's possible that this file installation
+ only involves propchanges, but that some of those props still
+ require a retranslation of the working file.
+
+ OR that the file doesn't involve propchanges which by themselves
+ require retranslation, but receiving a change bumps the revision
+ number which requires re-expansion of keywords... */
+
+ if (is_locally_modified)
+ {
+ const char *tmptext;
+
+ /* Copy and DEtranslate the working file to a temp text-base.
+ Note that detranslation is done according to the old props. */
+ SVN_ERR(svn_wc__internal_translated_file(
+ &tmptext, fb->local_abspath, eb->db, fb->local_abspath,
+ SVN_WC_TRANSLATE_TO_NF
+ | SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP,
+ eb->cancel_func, eb->cancel_baton,
+ result_pool, scratch_pool));
+
+ /* We always want to reinstall the working file if the magic
+ properties have changed, or there are any keywords present.
+ Note that TMPTEXT might actually refer to the working file
+ itself (the above function skips a detranslate when not
+ required). This is acceptable, as we will (re)translate
+ according to the new properties into a temporary file (from
+ the working file), and then rename the temp into place. Magic!
+ */
+ *install_pristine = TRUE;
+ *install_from = tmptext;
+ }
+ else
+ {
+ /* Use our existing 'copy' from the pristine store instead
+ of making a new copy. This way we can use the standard code
+ to update the recorded size and modification time.
+ (Issue #3842) */
+ *install_pristine = TRUE;
+ }
+ }
+ }
+
+ /* Set the returned content state. */
+
+ if (found_text_conflict)
+ *content_state = svn_wc_notify_state_conflicted;
+ else if (fb->new_text_base_sha1_checksum)
+ {
+ if (is_locally_modified)
+ *content_state = svn_wc_notify_state_merged;
+ else
+ *content_state = svn_wc_notify_state_changed;
+ }
+ else
+ *content_state = svn_wc_notify_state_unchanged;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+/* Mostly a wrapper around merge_file. */
+static svn_error_t *
+close_file(void *file_baton,
+ const char *expected_md5_digest,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct dir_baton *pdb = fb->dir_baton;
+ struct edit_baton *eb = fb->edit_baton;
+ svn_wc_notify_state_t content_state, prop_state;
+ svn_wc_notify_lock_state_t lock_state;
+ svn_checksum_t *expected_md5_checksum = NULL;
+ apr_hash_t *new_base_props = NULL;
+ apr_hash_t *new_actual_props = NULL;
+ apr_array_header_t *entry_prop_changes;
+ apr_array_header_t *dav_prop_changes;
+ apr_array_header_t *regular_prop_changes;
+ apr_hash_t *current_base_props = NULL;
+ apr_hash_t *current_actual_props = NULL;
+ apr_hash_t *local_actual_props = NULL;
+ svn_skel_t *all_work_items = NULL;
+ svn_skel_t *conflict_skel = NULL;
+ svn_skel_t *work_item;
+ apr_pool_t *scratch_pool = fb->pool; /* Destroyed at function exit */
+ svn_boolean_t keep_recorded_info = FALSE;
+ const svn_checksum_t *new_checksum;
+ apr_array_header_t *iprops = NULL;
+
+ if (fb->skip_this)
+ {
+ svn_pool_destroy(fb->pool);
+ SVN_ERR(maybe_release_dir_info(pdb));
+ return SVN_NO_ERROR;
+ }
+
+ if (fb->edited)
+ conflict_skel = fb->edit_conflict;
+
+ if (expected_md5_digest)
+ SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
+ expected_md5_digest, scratch_pool));
+
+ if (fb->new_text_base_md5_checksum && expected_md5_checksum
+ && !svn_checksum_match(expected_md5_checksum,
+ fb->new_text_base_md5_checksum))
+ return svn_error_trace(
+ svn_checksum_mismatch_err(expected_md5_checksum,
+ fb->new_text_base_md5_checksum,
+ scratch_pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(
+ fb->local_abspath, pool)));
+
+ /* Gather the changes for each kind of property. */
+ SVN_ERR(svn_categorize_props(fb->propchanges, &entry_prop_changes,
+ &dav_prop_changes, &regular_prop_changes,
+ scratch_pool));
+
+ /* Extract the changed_* and lock state information. */
+ {
+ svn_revnum_t new_changed_rev;
+ apr_time_t new_changed_date;
+ const char *new_changed_author;
+
+ SVN_ERR(accumulate_last_change(&new_changed_rev,
+ &new_changed_date,
+ &new_changed_author,
+ entry_prop_changes,
+ scratch_pool, scratch_pool));
+
+ if (SVN_IS_VALID_REVNUM(new_changed_rev))
+ fb->changed_rev = new_changed_rev;
+ if (new_changed_date != 0)
+ fb->changed_date = new_changed_date;
+ if (new_changed_author != NULL)
+ fb->changed_author = new_changed_author;
+ }
+
+ /* Determine whether the file has become unlocked. */
+ {
+ int i;
+
+ lock_state = svn_wc_notify_lock_state_unchanged;
+
+ for (i = 0; i < entry_prop_changes->nelts; ++i)
+ {
+ const svn_prop_t *prop
+ = &APR_ARRAY_IDX(entry_prop_changes, i, svn_prop_t);
+
+ /* If we see a change to the LOCK_TOKEN entry prop, then the only
+ possible change is its REMOVAL. Thus, the lock has been removed,
+ and we should likewise remove our cached copy of it. */
+ if (! strcmp(prop->name, SVN_PROP_ENTRY_LOCK_TOKEN))
+ {
+ /* If we lose the lock, but not because we are switching to
+ another url, remove the state lock from the wc */
+ if (! eb->switch_relpath
+ || strcmp(fb->new_relpath, fb->old_repos_relpath) == 0)
+ {
+ SVN_ERR_ASSERT(prop->value == NULL);
+ SVN_ERR(svn_wc__db_lock_remove(eb->db, fb->local_abspath,
+ scratch_pool));
+
+ lock_state = svn_wc_notify_lock_state_unlocked;
+ }
+ break;
+ }
+ }
+ }
+
+ /* Install all kinds of properties. It is important to do this before
+ any file content merging, since that process might expand keywords, in
+ which case we want the new entryprops to be in place. */
+
+ /* Write log commands to merge REGULAR_PROPS into the existing
+ properties of FB->LOCAL_ABSPATH. Update *PROP_STATE to reflect
+ the result of the regular prop merge.
+
+ BASE_PROPS and WORKING_PROPS are hashes of the base and
+ working props of the file; if NULL they are read from the wc. */
+
+ /* ### some of this feels like voodoo... */
+
+ if ((!fb->adding_file || fb->add_existed)
+ && !fb->shadowed)
+ SVN_ERR(svn_wc__get_actual_props(&local_actual_props,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+ if (local_actual_props == NULL)
+ local_actual_props = apr_hash_make(scratch_pool);
+
+ if (fb->add_existed)
+ {
+ /* This node already exists. Grab the current pristine properties. */
+ SVN_ERR(svn_wc__db_read_pristine_props(&current_base_props,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+ current_actual_props = local_actual_props;
+ }
+ else if (!fb->adding_file)
+ {
+ /* Get the BASE properties for proper merging. */
+ SVN_ERR(svn_wc__db_base_get_props(&current_base_props,
+ eb->db, fb->local_abspath,
+ scratch_pool, scratch_pool));
+ current_actual_props = local_actual_props;
+ }
+
+ /* Note: even if the node existed before, it may not have
+ pristine props (e.g a local-add) */
+ if (current_base_props == NULL)
+ current_base_props = apr_hash_make(scratch_pool);
+
+ /* And new nodes need an empty set of ACTUAL props. */
+ if (current_actual_props == NULL)
+ current_actual_props = apr_hash_make(scratch_pool);
+
+ prop_state = svn_wc_notify_state_unknown;
+
+ if (! fb->shadowed)
+ {
+ svn_boolean_t install_pristine;
+ const char *install_from = NULL;
+
+ /* Merge the 'regular' props into the existing working proplist. */
+ /* This will merge the old and new props into a new prop db, and
+ write <cp> commands to the logfile to install the merged
+ props. */
+ new_base_props = svn_prop__patch(current_base_props, regular_prop_changes,
+ scratch_pool);
+ SVN_ERR(svn_wc__merge_props(&conflict_skel,
+ &prop_state,
+ &new_actual_props,
+ eb->db,
+ fb->local_abspath,
+ NULL /* server_baseprops (update, not merge) */,
+ current_base_props,
+ current_actual_props,
+ regular_prop_changes, /* propchanges */
+ scratch_pool,
+ scratch_pool));
+ /* We will ALWAYS have properties to save (after a not-dry-run merge). */
+ SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL);
+
+ /* Merge the text. This will queue some additional work. */
+ if (!fb->obstruction_found && !fb->edit_obstructed)
+ {
+ svn_error_t *err;
+ err = merge_file(&work_item, &conflict_skel,
+ &install_pristine, &install_from,
+ &content_state, fb, current_actual_props,
+ fb->changed_date, scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_ACCESS_DENIED)
+ {
+ if (eb->notify_func)
+ {
+ svn_wc_notify_t *notify =svn_wc_create_notify(
+ fb->local_abspath,
+ svn_wc_notify_update_skip_access_denied,
+ scratch_pool);
+
+ notify->kind = svn_node_file;
+ notify->err = err;
+
+ eb->notify_func(eb->notify_baton, notify, scratch_pool);
+ }
+ svn_error_clear(err);
+
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath,
+ scratch_pool));
+ fb->skip_this = TRUE;
+
+ svn_pool_destroy(fb->pool);
+ SVN_ERR(maybe_release_dir_info(pdb));
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+ else
+ {
+ install_pristine = FALSE;
+ if (fb->new_text_base_sha1_checksum)
+ content_state = svn_wc_notify_state_changed;
+ else
+ content_state = svn_wc_notify_state_unchanged;
+ }
+
+ if (install_pristine)
+ {
+ svn_boolean_t record_fileinfo;
+
+ /* If we are installing from the pristine contents, then go ahead and
+ record the fileinfo. That will be the "proper" values. Installing
+ from some random file means the fileinfo does NOT correspond to
+ the pristine (in which case, the fileinfo will be cleared for
+ safety's sake). */
+ record_fileinfo = (install_from == NULL);
+
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item,
+ eb->db,
+ fb->local_abspath,
+ install_from,
+ eb->use_commit_times,
+ record_fileinfo,
+ scratch_pool, scratch_pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+ else if (lock_state == svn_wc_notify_lock_state_unlocked
+ && !fb->obstruction_found)
+ {
+ /* If a lock was removed and we didn't update the text contents, we
+ might need to set the file read-only.
+
+ Note: this will also update the executable flag, but ... meh. */
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, eb->db,
+ fb->local_abspath,
+ scratch_pool, scratch_pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+
+ if (! install_pristine
+ && (content_state == svn_wc_notify_state_unchanged))
+ {
+ /* It is safe to keep the current recorded timestamp and size */
+ keep_recorded_info = TRUE;
+ }
+
+ /* Clean up any temporary files. */
+
+ /* Remove the INSTALL_FROM file, as long as it doesn't refer to the
+ working file. */
+ if (install_from != NULL
+ && strcmp(install_from, fb->local_abspath) != 0)
+ {
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, eb->db,
+ fb->local_abspath, install_from,
+ scratch_pool, scratch_pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+ }
+ else
+ {
+ /* Adding or updating a BASE node under a locally added node. */
+ apr_hash_t *fake_actual_props;
+
+ if (fb->adding_file)
+ fake_actual_props = apr_hash_make(scratch_pool);
+ else
+ fake_actual_props = current_base_props;
+
+ /* Store the incoming props (sent as propchanges) in new_base_props
+ and create a set of new actual props to use for notifications */
+ new_base_props = svn_prop__patch(current_base_props, regular_prop_changes,
+ scratch_pool);
+ SVN_ERR(svn_wc__merge_props(&conflict_skel,
+ &prop_state,
+ &new_actual_props,
+ eb->db,
+ fb->local_abspath,
+ NULL /* server_baseprops (not merging) */,
+ current_base_props /* pristine_props */,
+ fake_actual_props /* actual_props */,
+ regular_prop_changes, /* propchanges */
+ scratch_pool,
+ scratch_pool));
+
+ if (fb->new_text_base_sha1_checksum)
+ content_state = svn_wc_notify_state_changed;
+ else
+ content_state = svn_wc_notify_state_unchanged;
+ }
+
+ /* Insert/replace the BASE node with all of the new metadata. */
+
+ /* Set the 'checksum' column of the file's BASE_NODE row to
+ * NEW_TEXT_BASE_SHA1_CHECKSUM. The pristine text identified by that
+ * checksum is already in the pristine store. */
+ new_checksum = fb->new_text_base_sha1_checksum;
+
+ /* If we don't have a NEW checksum, then the base must not have changed.
+ Just carry over the old checksum. */
+ if (new_checksum == NULL)
+ new_checksum = fb->original_checksum;
+
+ if (conflict_skel)
+ {
+ SVN_ERR(complete_conflict(conflict_skel,
+ fb->edit_baton,
+ fb->local_abspath,
+ fb->old_repos_relpath,
+ fb->old_revision,
+ fb->new_relpath,
+ svn_node_file, svn_node_file,
+ fb->pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ eb->db, fb->local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ scratch_pool);
+ }
+
+ /* Any inherited props to be set set for this base node? */
+ if (eb->wcroot_iprops)
+ {
+ iprops = svn_hash_gets(eb->wcroot_iprops, fb->local_abspath);
+
+ /* close_edit may also update iprops for switched nodes, catching
+ those for which close_directory is never called (e.g. a switch
+ with no changes). So as a minor optimization we remove any
+ iprops from the hash so as not to set them again in
+ close_edit. */
+ if (iprops)
+ svn_hash_sets(eb->wcroot_iprops, fb->local_abspath, NULL);
+ }
+
+ SVN_ERR(svn_wc__db_base_add_file(eb->db, fb->local_abspath,
+ eb->wcroot_abspath,
+ fb->new_relpath,
+ eb->repos_root, eb->repos_uuid,
+ *eb->target_revision,
+ new_base_props,
+ fb->changed_rev,
+ fb->changed_date,
+ fb->changed_author,
+ new_checksum,
+ (dav_prop_changes->nelts > 0)
+ ? svn_prop_array_to_hash(
+ dav_prop_changes,
+ scratch_pool)
+ : NULL,
+ (fb->add_existed && fb->adding_file),
+ (! fb->shadowed) && new_base_props,
+ new_actual_props,
+ iprops,
+ keep_recorded_info,
+ (fb->shadowed && fb->obstruction_found),
+ conflict_skel,
+ all_work_items,
+ scratch_pool));
+
+ if (conflict_skel && eb->conflict_func)
+ SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, fb->local_abspath,
+ conflict_skel,
+ NULL /* merge_options */,
+ eb->conflict_func,
+ eb->conflict_baton,
+ eb->cancel_func,
+ eb->cancel_baton,
+ scratch_pool));
+
+ /* Deal with the WORKING tree, based on updates to the BASE tree. */
+
+ svn_hash_sets(fb->dir_baton->not_present_files, fb->name, NULL);
+
+ /* Send a notification to the callback function. (Skip notifications
+ about files which were already notified for another reason.) */
+ if (eb->notify_func && !fb->already_notified
+ && (fb->edited || lock_state == svn_wc_notify_lock_state_unlocked))
+ {
+ svn_wc_notify_t *notify;
+ svn_wc_notify_action_t action = svn_wc_notify_update_update;
+
+ if (fb->edited)
+ {
+ if (fb->shadowed || fb->edit_obstructed)
+ action = fb->adding_file
+ ? svn_wc_notify_update_shadowed_add
+ : svn_wc_notify_update_shadowed_update;
+ else if (fb->obstruction_found || fb->add_existed)
+ {
+ if (content_state != svn_wc_notify_state_conflicted)
+ action = svn_wc_notify_exists;
+ }
+ else if (fb->adding_file)
+ {
+ action = svn_wc_notify_update_add;
+ }
+ }
+ else
+ {
+ SVN_ERR_ASSERT(lock_state == svn_wc_notify_lock_state_unlocked);
+ action = svn_wc_notify_update_broken_lock;
+ }
+
+ /* If the file was moved-away, notify for the moved-away node.
+ * The original location only had its BASE info changed and
+ * we don't usually notify about such changes. */
+ notify = svn_wc_create_notify(fb->local_abspath, action, scratch_pool);
+ notify->kind = svn_node_file;
+ notify->content_state = content_state;
+ notify->prop_state = prop_state;
+ notify->lock_state = lock_state;
+ notify->revision = *eb->target_revision;
+ notify->old_revision = fb->old_revision;
+
+ /* Fetch the mimetype from the actual properties */
+ notify->mime_type = svn_prop_get_value(new_actual_props,
+ SVN_PROP_MIME_TYPE);
+
+ eb->notify_func(eb->notify_baton, notify, scratch_pool);
+ }
+
+ svn_pool_destroy(fb->pool); /* Destroy scratch_pool */
+
+ /* We have one less referrer to the directory */
+ SVN_ERR(maybe_release_dir_info(pdb));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ struct edit_baton *eb = edit_baton;
+ apr_pool_t *scratch_pool = eb->pool;
+
+ /* The editor didn't even open the root; we have to take care of
+ some cleanup stuffs. */
+ if (! eb->root_opened
+ && *eb->target_basename == '\0')
+ {
+ /* We need to "un-incomplete" the root directory. */
+ SVN_ERR(svn_wc__db_temp_op_end_directory_update(eb->db,
+ eb->anchor_abspath,
+ scratch_pool));
+ }
+
+ /* By definition, anybody "driving" this editor for update or switch
+ purposes at a *minimum* must have called set_target_revision() at
+ the outset, and close_edit() at the end -- even if it turned out
+ that no changes ever had to be made, and open_root() was never
+ called. That's fine. But regardless, when the edit is over,
+ this editor needs to make sure that *all* paths have had their
+ revisions bumped to the new target revision. */
+
+ /* Make sure our update target now has the new working revision.
+ Also, if this was an 'svn switch', then rewrite the target's
+ url. All of this tweaking might happen recursively! Note
+ that if eb->target is NULL, that's okay (albeit "sneaky",
+ some might say). */
+
+ /* Extra check: if the update did nothing but make its target
+ 'deleted', then do *not* run cleanup on the target, as it
+ will only remove the deleted entry! */
+ if (! eb->target_deleted)
+ {
+ SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
+ eb->target_abspath,
+ eb->requested_depth,
+ eb->switch_relpath,
+ eb->repos_root,
+ eb->repos_uuid,
+ *(eb->target_revision),
+ eb->skipped_trees,
+ eb->wcroot_iprops,
+ eb->notify_func,
+ eb->notify_baton,
+ eb->pool));
+
+ if (*eb->target_basename != '\0')
+ {
+ svn_wc__db_status_t status;
+ svn_error_t *err;
+
+ /* Note: we are fetching information about the *target*, not anchor.
+ There is no guarantee that the target has a BASE node.
+ For example:
+
+ The node was not present in BASE, but locally-added, and the
+ update did not create a new BASE node "under" the local-add.
+
+ If there is no BASE node for the target, then we certainly don't
+ have to worry about removing it. */
+ err = svn_wc__db_base_get_info(&status, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ eb->db, eb->target_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ }
+ else if (status == svn_wc__db_status_excluded)
+ {
+ /* There is a small chance that the explicit target of an update/
+ switch is gone in the repository, in that specific case the
+ node hasn't been re-added to the BASE tree by this update.
+
+ If so, we should get rid of this excluded node now. */
+
+ SVN_ERR(svn_wc__db_base_remove(eb->db, eb->target_abspath,
+ FALSE /* keep_as_working */,
+ FALSE /* queue_deletes */,
+ SVN_INVALID_REVNUM,
+ NULL, NULL, scratch_pool));
+ }
+ }
+ }
+
+ /* The edit is over: run the wq with proper cancel support,
+ but first kill the handler that would run it on the pool
+ cleanup at the end of this function. */
+ apr_pool_cleanup_kill(eb->pool, eb, cleanup_edit_baton);
+
+ SVN_ERR(svn_wc__wq_run(eb->db, eb->wcroot_abspath,
+ eb->cancel_func, eb->cancel_baton,
+ eb->pool));
+
+ /* The edit is over, free its pool.
+ ### No, this is wrong. Who says this editor/baton won't be used
+ again? But the change is not merely to remove this call. We
+ should also make eb->pool not be a subpool (see make_editor),
+ and change callers of svn_client_{checkout,update,switch} to do
+ better pool management. ### */
+
+ svn_pool_destroy(eb->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Returning editors. ***/
+
+/* Helper for the three public editor-supplying functions. */
+static svn_error_t *
+make_editor(svn_revnum_t *target_revision,
+ svn_wc__db_t *db,
+ const char *anchor_abspath,
+ const char *target_basename,
+ apr_hash_t *wcroot_iprops,
+ svn_boolean_t use_commit_times,
+ const char *switch_url,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t server_performs_filtering,
+ svn_boolean_t clean_checkout,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct edit_baton *eb;
+ void *inner_baton;
+ apr_pool_t *edit_pool = svn_pool_create(result_pool);
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
+ const svn_delta_editor_t *inner_editor;
+ const char *repos_root, *repos_uuid;
+ struct svn_wc__shim_fetch_baton_t *sfb;
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(edit_pool);
+
+ /* An unknown depth can't be sticky. */
+ if (depth == svn_depth_unknown)
+ depth_is_sticky = FALSE;
+
+ /* Get the anchor's repository root and uuid. The anchor must already exist
+ in BASE. */
+ SVN_ERR(svn_wc__db_scan_base_repos(NULL, &repos_root, &repos_uuid,
+ db, anchor_abspath,
+ result_pool, scratch_pool));
+
+ /* With WC-NG we need a valid repository root */
+ SVN_ERR_ASSERT(repos_root != NULL && repos_uuid != NULL);
+
+ /* Disallow a switch operation to change the repository root of the target,
+ if that is known. */
+ if (switch_url && !svn_uri__is_ancestor(repos_root, switch_url))
+ return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL,
+ _("'%s'\nis not the same repository as\n'%s'"),
+ switch_url, repos_root);
+
+ /* Construct an edit baton. */
+ eb = apr_pcalloc(edit_pool, sizeof(*eb));
+ eb->pool = edit_pool;
+ eb->use_commit_times = use_commit_times;
+ eb->target_revision = target_revision;
+ eb->repos_root = repos_root;
+ eb->repos_uuid = repos_uuid;
+ eb->db = db;
+ eb->target_basename = target_basename;
+ eb->anchor_abspath = anchor_abspath;
+ eb->wcroot_iprops = wcroot_iprops;
+
+ SVN_ERR(svn_wc__db_get_wcroot(&eb->wcroot_abspath, db, anchor_abspath,
+ edit_pool, scratch_pool));
+
+ if (switch_url)
+ eb->switch_relpath =
+ svn_uri_skip_ancestor(repos_root, switch_url, scratch_pool);
+ else
+ eb->switch_relpath = NULL;
+
+ if (svn_path_is_empty(target_basename))
+ eb->target_abspath = eb->anchor_abspath;
+ else
+ eb->target_abspath = svn_dirent_join(eb->anchor_abspath, target_basename,
+ edit_pool);
+
+ eb->requested_depth = depth;
+ eb->depth_is_sticky = depth_is_sticky;
+ eb->notify_func = notify_func;
+ eb->notify_baton = notify_baton;
+ eb->external_func = external_func;
+ eb->external_baton = external_baton;
+ eb->diff3_cmd = diff3_cmd;
+ eb->cancel_func = cancel_func;
+ eb->cancel_baton = cancel_baton;
+ eb->conflict_func = conflict_func;
+ eb->conflict_baton = conflict_baton;
+ eb->allow_unver_obstructions = allow_unver_obstructions;
+ eb->adds_as_modification = adds_as_modification;
+ eb->clean_checkout = clean_checkout;
+ eb->skipped_trees = apr_hash_make(edit_pool);
+ eb->dir_dirents = apr_hash_make(edit_pool);
+ eb->ext_patterns = preserved_exts;
+
+ apr_pool_cleanup_register(edit_pool, eb, cleanup_edit_baton,
+ apr_pool_cleanup_null);
+
+ /* Construct an editor. */
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->open_root = open_root;
+ tree_editor->delete_entry = delete_entry;
+ tree_editor->add_directory = add_directory;
+ tree_editor->open_directory = open_directory;
+ tree_editor->change_dir_prop = change_dir_prop;
+ tree_editor->close_directory = close_directory;
+ tree_editor->absent_directory = absent_directory;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->absent_file = absent_file;
+ tree_editor->close_edit = close_edit;
+
+ /* Fiddle with the type system. */
+ inner_editor = tree_editor;
+ inner_baton = eb;
+
+ if (!depth_is_sticky
+ && depth != svn_depth_unknown
+ && svn_depth_empty <= depth && depth < svn_depth_infinity
+ && fetch_dirents_func)
+ {
+ /* We are asked to perform an update at a depth less than the ambient
+ depth. In this case the update won't describe additions that would
+ have been reported if we updated at the ambient depth. */
+ svn_error_t *err;
+ svn_node_kind_t dir_kind;
+ svn_wc__db_status_t dir_status;
+ const char *dir_repos_relpath;
+ svn_depth_t dir_depth;
+
+ /* we have to do this on the target of the update, not the anchor */
+ err = svn_wc__db_base_get_info(&dir_status, &dir_kind, NULL,
+ &dir_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, &dir_depth, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, eb->target_abspath,
+ scratch_pool, scratch_pool);
+
+ if (!err
+ && dir_kind == svn_node_dir
+ && dir_status == svn_wc__db_status_normal)
+ {
+ if (dir_depth > depth)
+ {
+ apr_hash_t *dirents;
+
+ /* If we switch, we should look at the new relpath */
+ if (eb->switch_relpath)
+ dir_repos_relpath = eb->switch_relpath;
+
+ SVN_ERR(fetch_dirents_func(fetch_dirents_baton, &dirents,
+ repos_root, dir_repos_relpath,
+ edit_pool, scratch_pool));
+
+ if (dirents != NULL && apr_hash_count(dirents))
+ svn_hash_sets(eb->dir_dirents,
+ apr_pstrdup(edit_pool, dir_repos_relpath),
+ dirents);
+ }
+
+ if (depth == svn_depth_immediates)
+ {
+ /* Worst case scenario of issue #3569 fix: We have to do the
+ same for all existing subdirs, but then we check for
+ svn_depth_empty. */
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+ SVN_ERR(svn_wc__db_base_get_children(&children, db,
+ eb->target_abspath,
+ scratch_pool,
+ iterpool));
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *child_abspath;
+ const char *child_name;
+
+ svn_pool_clear(iterpool);
+
+ child_name = APR_ARRAY_IDX(children, i, const char *);
+
+ child_abspath = svn_dirent_join(eb->target_abspath,
+ child_name, iterpool);
+
+ SVN_ERR(svn_wc__db_base_get_info(&dir_status, &dir_kind,
+ NULL, &dir_repos_relpath,
+ NULL, NULL, NULL, NULL,
+ NULL, &dir_depth, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL,
+ db, child_abspath,
+ iterpool, iterpool));
+
+ if (dir_kind == svn_node_dir
+ && dir_status == svn_wc__db_status_normal
+ && dir_depth > svn_depth_empty)
+ {
+ apr_hash_t *dirents;
+
+ /* If we switch, we should look at the new relpath */
+ if (eb->switch_relpath)
+ dir_repos_relpath = svn_relpath_join(
+ eb->switch_relpath,
+ child_name, iterpool);
+
+ SVN_ERR(fetch_dirents_func(fetch_dirents_baton, &dirents,
+ repos_root, dir_repos_relpath,
+ edit_pool, iterpool));
+
+ if (dirents != NULL && apr_hash_count(dirents))
+ svn_hash_sets(eb->dir_dirents,
+ apr_pstrdup(edit_pool,
+ dir_repos_relpath),
+ dirents);
+ }
+ }
+ }
+ }
+ else if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+ }
+
+ /* We need to limit the scope of our operation to the ambient depths
+ present in the working copy already, but only if the requested
+ depth is not sticky. If a depth was explicitly requested,
+ libsvn_delta/depth_filter_editor.c will ensure that we never see
+ editor calls that extend beyond the scope of the requested depth.
+ But even what we do so might extend beyond the scope of our
+ ambient depth. So we use another filtering editor to avoid
+ modifying the ambient working copy depth when not asked to do so.
+ (This can also be skipped if the server understands depth.) */
+ if (!server_performs_filtering
+ && !depth_is_sticky)
+ SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
+ &inner_baton,
+ db,
+ anchor_abspath,
+ target_basename,
+ inner_editor,
+ inner_baton,
+ result_pool));
+
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func,
+ cancel_baton,
+ inner_editor,
+ inner_baton,
+ editor,
+ edit_baton,
+ result_pool));
+
+ sfb = apr_palloc(result_pool, sizeof(*sfb));
+ sfb->db = db;
+ sfb->base_abspath = eb->anchor_abspath;
+ sfb->fetch_base = TRUE;
+
+ shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
+ shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
+ shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
+ shim_callbacks->fetch_baton = sfb;
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ NULL, NULL, shim_callbacks,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__get_update_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ apr_hash_t *wcroot_iprops,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t adds_as_modification,
+ svn_boolean_t server_performs_filtering,
+ svn_boolean_t clean_checkout,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return make_editor(target_revision, wc_ctx->db, anchor_abspath,
+ target_basename, wcroot_iprops, use_commit_times,
+ NULL, depth, depth_is_sticky, allow_unver_obstructions,
+ adds_as_modification, server_performs_filtering,
+ clean_checkout,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton,
+ fetch_dirents_func, fetch_dirents_baton,
+ conflict_func, conflict_baton,
+ external_func, external_baton,
+ diff3_cmd, preserved_exts, editor, edit_baton,
+ result_pool, scratch_pool);
+}
+
+svn_error_t *
+svn_wc__get_switch_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t *target_revision,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor_abspath,
+ const char *target_basename,
+ const char *switch_url,
+ apr_hash_t *wcroot_iprops,
+ svn_boolean_t use_commit_times,
+ svn_depth_t depth,
+ svn_boolean_t depth_is_sticky,
+ svn_boolean_t allow_unver_obstructions,
+ svn_boolean_t server_performs_filtering,
+ const char *diff3_cmd,
+ const apr_array_header_t *preserved_exts,
+ svn_wc_dirents_func_t fetch_dirents_func,
+ void *fetch_dirents_baton,
+ svn_wc_conflict_resolver_func2_t conflict_func,
+ void *conflict_baton,
+ svn_wc_external_update_t external_func,
+ void *external_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(switch_url && svn_uri_is_canonical(switch_url, scratch_pool));
+
+ return make_editor(target_revision, wc_ctx->db, anchor_abspath,
+ target_basename, wcroot_iprops, use_commit_times,
+ switch_url,
+ depth, depth_is_sticky, allow_unver_obstructions,
+ FALSE /* adds_as_modification */,
+ server_performs_filtering,
+ FALSE /* clean_checkout */,
+ notify_func, notify_baton,
+ cancel_func, cancel_baton,
+ fetch_dirents_func, fetch_dirents_baton,
+ conflict_func, conflict_baton,
+ external_func, external_baton,
+ diff3_cmd, preserved_exts,
+ editor, edit_baton,
+ result_pool, scratch_pool);
+}
+
+
+
+/* ### Note that this function is completely different from the rest of the
+ update editor in what it updates. The update editor changes only BASE
+ and ACTUAL and this function just changes WORKING and ACTUAL.
+
+ In the entries world this function shared a lot of code with the
+ update editor but in the wonderful new WC-NG world it will probably
+ do more and more by itself and would be more logically grouped with
+ the add/copy functionality in adm_ops.c and copy.c. */
+svn_error_t *
+svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_stream_t *new_base_contents,
+ svn_stream_t *new_contents,
+ apr_hash_t *new_base_props,
+ apr_hash_t *new_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = wc_ctx->db;
+ const char *dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const char *tmp_text_base_abspath;
+ svn_checksum_t *new_text_base_md5_checksum;
+ svn_checksum_t *new_text_base_sha1_checksum;
+ const char *source_abspath = NULL;
+ svn_skel_t *all_work_items = NULL;
+ svn_skel_t *work_item;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *original_repos_relpath;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ svn_error_t *err;
+ apr_pool_t *pool = scratch_pool;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(new_base_contents != NULL);
+ SVN_ERR_ASSERT(new_base_props != NULL);
+
+ /* We should have a write lock on this file's parent directory. */
+ SVN_ERR(svn_wc__write_check(db, dir_abspath, pool));
+
+ err = svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool);
+
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ else if(err)
+ svn_error_clear(err);
+ else
+ switch (status)
+ {
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_deleted:
+ break;
+ default:
+ return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
+ _("Node '%s' exists."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, &repos_root_url,
+ &repos_uuid, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ db, dir_abspath, scratch_pool, scratch_pool));
+
+ switch (status)
+ {
+ case svn_wc__db_status_normal:
+ case svn_wc__db_status_added:
+ break;
+ case svn_wc__db_status_deleted:
+ return
+ svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL,
+ _("Can't add '%s' to a parent directory"
+ " scheduled for deletion"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ default:
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, err,
+ _("Can't find parent directory's node while"
+ " trying to add '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ if (kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("Can't schedule an addition of '%s'"
+ " below a not-directory node"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ /* Fabricate the anticipated new URL of the target and check the
+ copyfrom URL to be in the same repository. */
+ if (copyfrom_url != NULL)
+ {
+ /* Find the repository_root via the parent directory, which
+ is always versioned before this function is called */
+
+ if (!repos_root_url)
+ {
+ /* The parent is an addition, scan upwards to find the right info */
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
+ &repos_root_url, &repos_uuid,
+ NULL, NULL, NULL, NULL,
+ wc_ctx->db, dir_abspath,
+ scratch_pool, scratch_pool));
+ }
+ SVN_ERR_ASSERT(repos_root_url);
+
+ original_repos_relpath =
+ svn_uri_skip_ancestor(repos_root_url, copyfrom_url, scratch_pool);
+
+ if (!original_repos_relpath)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Copyfrom-url '%s' has different repository"
+ " root than '%s'"),
+ copyfrom_url, repos_root_url);
+ }
+ else
+ {
+ original_repos_relpath = NULL;
+ copyfrom_rev = SVN_INVALID_REVNUM; /* Just to be sure. */
+ }
+
+ /* Set CHANGED_* to reflect the entry props in NEW_BASE_PROPS, and
+ filter NEW_BASE_PROPS so it contains only regular props. */
+ {
+ apr_array_header_t *regular_props;
+ apr_array_header_t *entry_props;
+
+ SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(new_base_props, pool),
+ &entry_props, NULL, &regular_props,
+ pool));
+
+ /* Put regular props back into a hash table. */
+ new_base_props = svn_prop_array_to_hash(regular_props, pool);
+
+ /* Get the change_* info from the entry props. */
+ SVN_ERR(accumulate_last_change(&changed_rev,
+ &changed_date,
+ &changed_author,
+ entry_props, pool, pool));
+ }
+
+ /* Copy NEW_BASE_CONTENTS into a temporary file so our log can refer to
+ it, and set TMP_TEXT_BASE_ABSPATH to its path. Compute its
+ NEW_TEXT_BASE_MD5_CHECKSUM and NEW_TEXT_BASE_SHA1_CHECKSUM as we copy. */
+ {
+ svn_stream_t *tmp_base_contents;
+
+ SVN_ERR(svn_wc__open_writable_base(&tmp_base_contents,
+ &tmp_text_base_abspath,
+ &new_text_base_md5_checksum,
+ &new_text_base_sha1_checksum,
+ wc_ctx->db, local_abspath,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(new_base_contents, tmp_base_contents,
+ cancel_func, cancel_baton, pool));
+ }
+
+ /* If the caller gave us a new working file, copy it to a safe (temporary)
+ location and set SOURCE_ABSPATH to that path. We'll then translate/copy
+ that into place after the node's state has been created. */
+ if (new_contents)
+ {
+ const char *temp_dir_abspath;
+ svn_stream_t *tmp_contents;
+
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, db,
+ local_abspath, pool, pool));
+ SVN_ERR(svn_stream_open_unique(&tmp_contents, &source_abspath,
+ temp_dir_abspath, svn_io_file_del_none,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(new_contents, tmp_contents,
+ cancel_func, cancel_baton, pool));
+ }
+
+ /* Install new text base for copied files. Added files do NOT have a
+ text base. */
+ if (copyfrom_url != NULL)
+ {
+ SVN_ERR(svn_wc__db_pristine_install(db, tmp_text_base_abspath,
+ new_text_base_sha1_checksum,
+ new_text_base_md5_checksum, pool));
+ }
+ else
+ {
+ /* ### There's something wrong around here. Sometimes (merge from a
+ foreign repository, at least) we are called with copyfrom_url =
+ NULL and an empty new_base_contents (and an empty set of
+ new_base_props). Why an empty "new base"?
+
+ That happens in merge_tests.py 54,87,88,89,143.
+
+ In that case, having been given this supposed "new base" file, we
+ copy it and calculate its checksum but do not install it. Why?
+ That must be wrong.
+
+ To crudely work around one issue with this, that we shouldn't
+ record a checksum in the database if we haven't installed the
+ corresponding pristine text, for now we'll just set the checksum
+ to NULL.
+
+ The proper solution is probably more like: the caller should pass
+ NULL for the missing information, and this function should learn to
+ handle that. */
+
+ new_text_base_sha1_checksum = NULL;
+ new_text_base_md5_checksum = NULL;
+ }
+
+ /* For added files without NEW_CONTENTS, then generate the working file
+ from the provided "pristine" contents. */
+ if (new_contents == NULL && copyfrom_url == NULL)
+ source_abspath = tmp_text_base_abspath;
+
+ {
+ svn_boolean_t record_fileinfo;
+
+ /* If new contents were provided, then we do NOT want to record the
+ file information. We assume the new contents do not match the
+ "proper" values for RECORDED_SIZE and RECORDED_TIME. */
+ record_fileinfo = (new_contents == NULL);
+
+ /* Install the working copy file (with appropriate translation) from
+ the appropriate source. SOURCE_ABSPATH will be NULL, indicating an
+ installation from the pristine (available for copied/moved files),
+ or it will specify a temporary file where we placed a "pristine"
+ (for an added file) or a detranslated local-mods file. */
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item,
+ db, local_abspath,
+ source_abspath,
+ FALSE /* use_commit_times */,
+ record_fileinfo,
+ pool, pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
+
+ /* If we installed from somewhere besides the official pristine, then
+ it is a temporary file, which needs to be removed. */
+ if (source_abspath != NULL)
+ {
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, local_abspath,
+ source_abspath,
+ pool, pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
+ }
+ }
+
+ /* ### ideally, we would have a single DB operation, and queue the work
+ ### items on that. for now, we'll queue them with the second call. */
+
+ SVN_ERR(svn_wc__db_op_copy_file(db, local_abspath,
+ new_base_props,
+ changed_rev,
+ changed_date,
+ changed_author,
+ original_repos_relpath,
+ original_repos_relpath ? repos_root_url
+ : NULL,
+ original_repos_relpath ? repos_uuid : NULL,
+ copyfrom_rev,
+ new_text_base_sha1_checksum,
+ TRUE,
+ new_props,
+ FALSE /* is_move */,
+ NULL /* conflict */,
+ all_work_items,
+ pool));
+
+ return svn_error_trace(svn_wc__wq_run(db, dir_abspath,
+ cancel_func, cancel_baton,
+ pool));
+}
+
+svn_error_t *
+svn_wc__complete_directory_add(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_hash_t *new_original_props,
+ const char *copyfrom_url,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const char *original_repos_relpath;
+ const char *original_root_url;
+ const char *original_uuid;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+
+ svn_revnum_t original_revision;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+
+ SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ &original_repos_relpath, &original_root_url,
+ &original_uuid, &original_revision, NULL, NULL,
+ NULL, NULL, NULL, NULL, &had_props, &props_mod,
+ NULL, NULL, NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (status != svn_wc__db_status_added
+ || kind != svn_node_dir
+ || had_props
+ || props_mod
+ || !original_repos_relpath)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("'%s' is not an unmodified copied directory"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ if (original_revision != copyfrom_rev
+ || strcmp(copyfrom_url,
+ svn_path_url_add_component2(original_root_url,
+ original_repos_relpath,
+ scratch_pool)))
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND, NULL,
+ _("Copyfrom '%s' doesn't match original location of '%s'"),
+ copyfrom_url,
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+
+ {
+ apr_array_header_t *regular_props;
+ apr_array_header_t *entry_props;
+
+ SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(new_original_props,
+ scratch_pool),
+ &entry_props, NULL, &regular_props,
+ scratch_pool));
+
+ /* Put regular props back into a hash table. */
+ new_original_props = svn_prop_array_to_hash(regular_props, scratch_pool);
+
+ /* Get the change_* info from the entry props. */
+ SVN_ERR(accumulate_last_change(&changed_rev,
+ &changed_date,
+ &changed_author,
+ entry_props, scratch_pool, scratch_pool));
+ }
+
+ return svn_error_trace(
+ svn_wc__db_op_copy_dir(wc_ctx->db, local_abspath,
+ new_original_props,
+ changed_rev, changed_date, changed_author,
+ original_repos_relpath, original_root_url,
+ original_uuid, original_revision,
+ NULL /* children */,
+ FALSE /* is_move */,
+ svn_depth_infinity,
+ NULL /* conflict */,
+ NULL /* work_items */,
+ scratch_pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/upgrade.c b/contrib/subversion/subversion/libsvn_wc/upgrade.c
new file mode 100644
index 0000000..983892c
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/upgrade.c
@@ -0,0 +1,2376 @@
+/*
+ * upgrade.c: routines for upgrading a working copy
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "entries.h"
+#include "wc_db.h"
+#include "tree_conflicts.h"
+#include "wc-queries.h" /* for STMT_* */
+#include "workqueue.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_token.h"
+
+/* WC-1.0 administrative area extensions */
+#define SVN_WC__BASE_EXT ".svn-base" /* for text and prop bases */
+#define SVN_WC__WORK_EXT ".svn-work" /* for working propfiles */
+#define SVN_WC__REVERT_EXT ".svn-revert" /* for reverting a replaced
+ file */
+
+/* Old locations for storing "wcprops" (aka "dav cache"). */
+#define WCPROPS_SUBDIR_FOR_FILES "wcprops"
+#define WCPROPS_FNAME_FOR_DIR "dir-wcprops"
+#define WCPROPS_ALL_DATA "all-wcprops"
+
+/* Old property locations. */
+#define PROPS_SUBDIR "props"
+#define PROP_BASE_SUBDIR "prop-base"
+#define PROP_BASE_FOR_DIR "dir-prop-base"
+#define PROP_REVERT_FOR_DIR "dir-prop-revert"
+#define PROP_WORKING_FOR_DIR "dir-props"
+
+/* Old textbase location. */
+#define TEXT_BASE_SUBDIR "text-base"
+
+#define TEMP_DIR "tmp"
+
+/* Old data files that we no longer need/use. */
+#define ADM_README "README.txt"
+#define ADM_EMPTY_FILE "empty-file"
+#define ADM_LOG "log"
+#define ADM_LOCK "lock"
+
+/* New pristine location */
+#define PRISTINE_STORAGE_RELPATH "pristine"
+#define PRISTINE_STORAGE_EXT ".svn-base"
+/* Number of characters in a pristine file basename, in WC format <= 28. */
+#define PRISTINE_BASENAME_OLD_LEN 40
+#define SDB_FILE "wc.db"
+
+
+/* Read the properties from the file at PROPFILE_ABSPATH, returning them
+ as a hash in *PROPS. If the propfile is NOT present, then NULL will
+ be returned in *PROPS. */
+static svn_error_t *
+read_propfile(apr_hash_t **props,
+ const char *propfile_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_stream_t *stream;
+ apr_finfo_t finfo;
+
+ err = svn_io_stat(&finfo, propfile_abspath, APR_FINFO_SIZE, scratch_pool);
+
+ if (err
+ && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ {
+ svn_error_clear(err);
+
+ /* The propfile was not there. Signal with a NULL. */
+ *props = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ /* A 0-bytes file signals an empty property list.
+ (mostly used for revert-props) */
+ if (finfo.size == 0)
+ {
+ *props = apr_hash_make(result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_stream_open_readonly(&stream, propfile_abspath,
+ scratch_pool, scratch_pool));
+
+ /* ### does this function need to be smarter? will we see zero-length
+ ### files? see props.c::load_props(). there may be more work here.
+ ### need a historic analysis of 1.x property storage. what will we
+ ### actually run into? */
+
+ /* ### loggy_write_properties() and immediate_install_props() write
+ ### zero-length files for "no props", so we should be a bit smarter
+ ### in here. */
+
+ /* ### should we be forgiving in here? I say "no". if we can't be sure,
+ ### then we could effectively corrupt the local working copy. */
+
+ *props = apr_hash_make(result_pool);
+ SVN_ERR(svn_hash_read2(*props, stream, SVN_HASH_TERMINATOR, result_pool));
+
+ return svn_error_trace(svn_stream_close(stream));
+}
+
+
+/* Read one proplist (allocated from RESULT_POOL) from STREAM, and place it
+ into ALL_WCPROPS at NAME. */
+static svn_error_t *
+read_one_proplist(apr_hash_t *all_wcprops,
+ const char *name,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *proplist;
+
+ proplist = apr_hash_make(result_pool);
+ SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, result_pool));
+ svn_hash_sets(all_wcprops, name, proplist);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Read the wcprops from all the files in the admin area of DIR_ABSPATH,
+ returning them in *ALL_WCPROPS. Results are allocated in RESULT_POOL,
+ and temporary allocations are performed in SCRATCH_POOL. */
+static svn_error_t *
+read_many_wcprops(apr_hash_t **all_wcprops,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *propfile_abspath;
+ apr_hash_t *wcprops;
+ apr_hash_t *dirents;
+ const char *props_dir_abspath;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+
+ *all_wcprops = apr_hash_make(result_pool);
+
+ /* First, look at dir-wcprops. */
+ propfile_abspath = svn_wc__adm_child(dir_abspath, WCPROPS_FNAME_FOR_DIR,
+ scratch_pool);
+ SVN_ERR(read_propfile(&wcprops, propfile_abspath, result_pool, iterpool));
+ if (wcprops != NULL)
+ svn_hash_sets(*all_wcprops, SVN_WC_ENTRY_THIS_DIR, wcprops);
+
+ props_dir_abspath = svn_wc__adm_child(dir_abspath, WCPROPS_SUBDIR_FOR_FILES,
+ scratch_pool);
+
+ /* Now walk the wcprops directory. */
+ SVN_ERR(svn_io_get_dirents3(&dirents, props_dir_abspath, TRUE,
+ scratch_pool, scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, dirents);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+
+ svn_pool_clear(iterpool);
+
+ propfile_abspath = svn_dirent_join(props_dir_abspath, name, iterpool);
+
+ SVN_ERR(read_propfile(&wcprops, propfile_abspath,
+ result_pool, iterpool));
+ SVN_ERR_ASSERT(wcprops != NULL);
+ svn_hash_sets(*all_wcprops, apr_pstrdup(result_pool, name), wcprops);
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* For wcprops stored in a single file in this working copy, read that
+ file and return it in *ALL_WCPROPS, allocated in RESULT_POOL. Use
+ SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+read_wcprops(apr_hash_t **all_wcprops,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *stream;
+ svn_error_t *err;
+
+ *all_wcprops = apr_hash_make(result_pool);
+
+ err = svn_wc__open_adm_stream(&stream, dir_abspath,
+ WCPROPS_ALL_DATA,
+ scratch_pool, scratch_pool);
+
+ /* A non-existent file means there are no props. */
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* Read the proplist for THIS_DIR. */
+ SVN_ERR(read_one_proplist(*all_wcprops, SVN_WC_ENTRY_THIS_DIR, stream,
+ result_pool, scratch_pool));
+
+ /* And now, the children. */
+ while (1729)
+ {
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool));
+ if (eof)
+ {
+ if (line->len > 0)
+ return svn_error_createf
+ (SVN_ERR_WC_CORRUPT, NULL,
+ _("Missing end of line in wcprops file for '%s'"),
+ svn_dirent_local_style(dir_abspath, scratch_pool));
+ break;
+ }
+ SVN_ERR(read_one_proplist(*all_wcprops, line->data, stream,
+ result_pool, scratch_pool));
+ }
+
+ return svn_error_trace(svn_stream_close(stream));
+}
+
+/* Return in CHILDREN, the list of all 1.6 versioned subdirectories
+ which also exist on disk as directories.
+
+ If DELETE_DIR is not NULL set *DELETE_DIR to TRUE if the directory
+ should be deleted after migrating to WC-NG, otherwise to FALSE.
+
+ If SKIP_MISSING is TRUE, don't add missing or obstructed subdirectories
+ to the list of children.
+ */
+static svn_error_t *
+get_versioned_subdirs(apr_array_header_t **children,
+ svn_boolean_t *delete_dir,
+ const char *dir_abspath,
+ svn_boolean_t skip_missing,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ svn_wc_entry_t *this_dir = NULL;
+
+ *children = apr_array_make(result_pool, 10, sizeof(const char *));
+
+ SVN_ERR(svn_wc__read_entries_old(&entries, dir_abspath,
+ scratch_pool, iterpool));
+ for (hi = apr_hash_first(scratch_pool, entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_wc_entry_t *entry = svn__apr_hash_index_val(hi);
+ const char *child_abspath;
+ svn_boolean_t hidden;
+
+ /* skip "this dir" */
+ if (*name == '\0')
+ {
+ this_dir = svn__apr_hash_index_val(hi);
+ continue;
+ }
+ else if (entry->kind != svn_node_dir)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ /* If a directory is 'hidden' skip it as subdir */
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry));
+ if (hidden)
+ continue;
+
+ child_abspath = svn_dirent_join(dir_abspath, name, scratch_pool);
+
+ if (skip_missing)
+ {
+ svn_node_kind_t kind;
+ SVN_ERR(svn_io_check_path(child_abspath, &kind, scratch_pool));
+
+ if (kind != svn_node_dir)
+ continue;
+ }
+
+ APR_ARRAY_PUSH(*children, const char *) = apr_pstrdup(result_pool,
+ child_abspath);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ if (delete_dir != NULL)
+ {
+ *delete_dir = (this_dir != NULL)
+ && (this_dir->schedule == svn_wc_schedule_delete)
+ && ! this_dir->keep_local;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return in CHILDREN the names of all versioned *files* in SDB that
+ are children of PARENT_RELPATH. These files' existence on disk is
+ not tested.
+
+ This set of children is intended for property upgrades.
+ Subdirectory's properties exist in the subdirs.
+
+ Note that this uses just the SDB to locate children, which means
+ that the children must have been upgraded to wc-ng format. */
+static svn_error_t *
+get_versioned_files(const apr_array_header_t **children,
+ const char *parent_relpath,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ apr_array_header_t *child_names;
+ svn_boolean_t have_row;
+
+ /* ### just select 'file' children. do we need 'symlink' in the future? */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ALL_FILES));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, parent_relpath));
+
+ /* ### 10 is based on Subversion's average of 8.5 files per versioned
+ ### directory in its repository. maybe use a different value? or
+ ### count rows first? */
+ child_names = apr_array_make(result_pool, 10, sizeof(const char *));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *local_relpath = svn_sqlite__column_text(stmt, 0,
+ result_pool);
+
+ APR_ARRAY_PUSH(child_names, const char *)
+ = svn_relpath_basename(local_relpath, result_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ *children = child_names;
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+/* Return the path of the old-school administrative lock file
+ associated with LOCAL_DIR_ABSPATH, allocated from RESULT_POOL. */
+static const char *
+build_lockfile_path(const char *local_dir_abspath,
+ apr_pool_t *result_pool)
+{
+ return svn_dirent_join_many(result_pool,
+ local_dir_abspath,
+ svn_wc_get_adm_dir(result_pool),
+ ADM_LOCK,
+ NULL);
+}
+
+
+/* Create a physical lock file in the admin directory for ABSPATH. */
+static svn_error_t *
+create_physical_lock(const char *abspath, apr_pool_t *scratch_pool)
+{
+ const char *lock_abspath = build_lockfile_path(abspath, scratch_pool);
+ svn_error_t *err;
+ apr_file_t *file;
+
+ err = svn_io_file_open(&file, lock_abspath,
+ APR_WRITE | APR_CREATE | APR_EXCL,
+ APR_OS_DEFAULT,
+ scratch_pool);
+
+ if (err && APR_STATUS_IS_EEXIST(err->apr_err))
+ {
+ /* Congratulations, we just stole a physical lock from somebody */
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+}
+
+
+/* Wipe out all the obsolete files/dirs from the administrative area. */
+static void
+wipe_obsolete_files(const char *wcroot_abspath, apr_pool_t *scratch_pool)
+{
+ /* Zap unused files. */
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ SVN_WC__ADM_FORMAT,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ SVN_WC__ADM_ENTRIES,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ ADM_EMPTY_FILE,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ ADM_README,
+ scratch_pool),
+ TRUE, scratch_pool));
+
+ /* For formats <= SVN_WC__WCPROPS_MANY_FILES_VERSION, we toss the wcprops
+ for the directory itself, and then all the wcprops for the files. */
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ WCPROPS_FNAME_FOR_DIR,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_dir2(
+ svn_wc__adm_child(wcroot_abspath,
+ WCPROPS_SUBDIR_FOR_FILES,
+ scratch_pool),
+ FALSE, NULL, NULL, scratch_pool));
+
+ /* And for later formats, they are aggregated into one file. */
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ WCPROPS_ALL_DATA,
+ scratch_pool),
+ TRUE, scratch_pool));
+
+ /* Remove the old text-base directory and the old text-base files. */
+ svn_error_clear(svn_io_remove_dir2(
+ svn_wc__adm_child(wcroot_abspath,
+ TEXT_BASE_SUBDIR,
+ scratch_pool),
+ FALSE, NULL, NULL, scratch_pool));
+
+ /* Remove the old properties files... whole directories at a time. */
+ svn_error_clear(svn_io_remove_dir2(
+ svn_wc__adm_child(wcroot_abspath,
+ PROPS_SUBDIR,
+ scratch_pool),
+ FALSE, NULL, NULL, scratch_pool));
+ svn_error_clear(svn_io_remove_dir2(
+ svn_wc__adm_child(wcroot_abspath,
+ PROP_BASE_SUBDIR,
+ scratch_pool),
+ FALSE, NULL, NULL, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ PROP_WORKING_FOR_DIR,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ PROP_BASE_FOR_DIR,
+ scratch_pool),
+ TRUE, scratch_pool));
+ svn_error_clear(svn_io_remove_file2(
+ svn_wc__adm_child(wcroot_abspath,
+ PROP_REVERT_FOR_DIR,
+ scratch_pool),
+ TRUE, scratch_pool));
+
+#if 0
+ /* ### this checks for a write-lock, and we are not (always) taking out
+ ### a write lock in all callers. */
+ SVN_ERR(svn_wc__adm_cleanup_tmp_area(db, wcroot_abspath, iterpool));
+#endif
+
+ /* Remove the old-style lock file LAST. */
+ svn_error_clear(svn_io_remove_file2(
+ build_lockfile_path(wcroot_abspath, scratch_pool),
+ TRUE, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__wipe_postupgrade(const char *dir_abspath,
+ svn_boolean_t whole_admin,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *subdirs;
+ svn_error_t *err;
+ svn_boolean_t delete_dir;
+ int i;
+
+ if (cancel_func)
+ SVN_ERR((*cancel_func)(cancel_baton));
+
+ err = get_versioned_subdirs(&subdirs, &delete_dir, dir_abspath, TRUE,
+ scratch_pool, iterpool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* An unversioned dir is obstructing a versioned dir */
+ svn_error_clear(err);
+ err = NULL;
+ }
+ svn_pool_destroy(iterpool);
+ return svn_error_trace(err);
+ }
+ for (i = 0; i < subdirs->nelts; ++i)
+ {
+ const char *child_abspath = APR_ARRAY_IDX(subdirs, i, const char *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_wc__wipe_postupgrade(child_abspath, TRUE,
+ cancel_func, cancel_baton, iterpool));
+ }
+
+ /* ### Should we really be ignoring errors here? */
+ if (whole_admin)
+ svn_error_clear(svn_io_remove_dir2(svn_wc__adm_child(dir_abspath, "",
+ iterpool),
+ TRUE, NULL, NULL, iterpool));
+ else
+ wipe_obsolete_files(dir_abspath, scratch_pool);
+
+ if (delete_dir)
+ {
+ /* If this was a WC-NG single database copy, this directory wouldn't
+ be here (unless it was deleted with --keep-local)
+
+ If the directory is empty, we can just delete it; if not we
+ keep it.
+ */
+ svn_error_clear(svn_io_dir_remove_nonrecursive(dir_abspath, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Ensure that ENTRY has its REPOS and UUID fields set. These will be
+ used to establish the REPOSITORY row in the new database, and then
+ used within the upgraded entries as they are written into the database.
+
+ If one or both are not available, then it attempts to retrieve this
+ information from REPOS_CACHE. And if that fails from REPOS_INFO_FUNC,
+ passing REPOS_INFO_BATON.
+ Returns a user understandable error using LOCAL_ABSPATH if the
+ information cannot be obtained. */
+static svn_error_t *
+ensure_repos_info(svn_wc_entry_t *entry,
+ const char *local_abspath,
+ svn_wc_upgrade_get_repos_info_t repos_info_func,
+ void *repos_info_baton,
+ apr_hash_t *repos_cache,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Easy exit. */
+ if (entry->repos != NULL && entry->uuid != NULL)
+ return SVN_NO_ERROR;
+
+ if ((entry->repos == NULL || entry->uuid == NULL)
+ && entry->url)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, repos_cache);
+ hi; hi = apr_hash_next(hi))
+ {
+ if (svn_uri__is_ancestor(svn__apr_hash_index_key(hi), entry->url))
+ {
+ if (!entry->repos)
+ entry->repos = svn__apr_hash_index_key(hi);
+
+ if (!entry->uuid)
+ entry->uuid = svn__apr_hash_index_val(hi);
+
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+
+ if (entry->repos == NULL && repos_info_func == NULL)
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Working copy '%s' can't be upgraded because the repository root is "
+ "not available and can't be retrieved"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+
+ if (entry->uuid == NULL && repos_info_func == NULL)
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Working copy '%s' can't be upgraded because the repository uuid is "
+ "not available and can't be retrieved"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+
+ if (entry->url == NULL)
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Working copy '%s' can't be upgraded because it doesn't have a url"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+
+ return svn_error_trace((*repos_info_func)(&entry->repos, &entry->uuid,
+ repos_info_baton,
+ entry->url,
+ result_pool, scratch_pool));
+}
+
+
+/*
+ * Read tree conflict descriptions from @a conflict_data. Set @a *conflicts
+ * to a hash of pointers to svn_wc_conflict_description2_t objects indexed by
+ * svn_wc_conflict_description2_t.local_abspath, all newly allocated in @a
+ * pool. @a dir_path is the path to the working copy directory whose conflicts
+ * are being read. The conflicts read are the tree conflicts on the immediate
+ * child nodes of @a dir_path. Do all allocations in @a pool.
+ *
+ * Note: There were some concerns about this function:
+ *
+ * ### this is BAD. the CONFLICTS structure should not be dependent upon
+ * ### DIR_PATH. each conflict should be labeled with an entry name, not
+ * ### a whole path. (and a path which happens to vary based upon invocation
+ * ### of the user client and these APIs)
+ *
+ * those assumptions were baked into former versions of the data model, so
+ * they have to stick around here. But they have been removed from the
+ * New Way. */
+static svn_error_t *
+read_tree_conflicts(apr_hash_t **conflicts,
+ const char *conflict_data,
+ const char *dir_path,
+ apr_pool_t *pool)
+{
+ const svn_skel_t *skel;
+ apr_pool_t *iterpool;
+
+ *conflicts = apr_hash_make(pool);
+
+ if (conflict_data == NULL)
+ return SVN_NO_ERROR;
+
+ skel = svn_skel__parse(conflict_data, strlen(conflict_data), pool);
+ if (skel == NULL)
+ return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
+ _("Error parsing tree conflict skel"));
+
+ iterpool = svn_pool_create(pool);
+ for (skel = skel->children; skel != NULL; skel = skel->next)
+ {
+ const svn_wc_conflict_description2_t *conflict;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_wc__deserialize_conflict(&conflict, skel, dir_path,
+ pool, iterpool));
+ if (conflict != NULL)
+ svn_hash_sets(*conflicts,
+ svn_dirent_basename(conflict->local_abspath, pool),
+ conflict);
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+migrate_single_tree_conflict_data(svn_sqlite__db_t *sdb,
+ const char *tree_conflict_data,
+ apr_int64_t wc_id,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *conflicts;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(read_tree_conflicts(&conflicts, tree_conflict_data, local_relpath,
+ scratch_pool));
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, conflicts);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const svn_wc_conflict_description2_t *conflict =
+ svn__apr_hash_index_val(hi);
+ const char *conflict_relpath;
+ const char *conflict_data;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_skel_t *skel;
+
+ svn_pool_clear(iterpool);
+
+ conflict_relpath = svn_dirent_join(local_relpath,
+ svn_dirent_basename(
+ conflict->local_abspath, iterpool),
+ iterpool);
+
+ SVN_ERR(svn_wc__serialize_conflict(&skel, conflict, iterpool, iterpool));
+ conflict_data = svn_skel__unparse(skel, iterpool)->data;
+
+ /* See if we need to update or insert an ACTUAL node. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, conflict_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (have_row)
+ {
+ /* There is an existing ACTUAL row, so just update it. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPDATE_ACTUAL_CONFLICT_DATA));
+ }
+ else
+ {
+ /* We need to insert an ACTUAL row with the tree conflict data. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_INSERT_ACTUAL_CONFLICT_DATA));
+ }
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wc_id, conflict_relpath,
+ conflict_data));
+ if (!have_row)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 4, local_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+migrate_tree_conflict_data(svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Iterate over each node which has a set of tree conflicts, then insert
+ all of them into the new schema. */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT));
+
+ /* Get all the existing tree conflict data. */
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ apr_int64_t wc_id;
+ const char *local_relpath;
+ const char *tree_conflict_data;
+
+ svn_pool_clear(iterpool);
+
+ wc_id = svn_sqlite__column_int64(stmt, 0);
+ local_relpath = svn_sqlite__column_text(stmt, 1, iterpool);
+ tree_conflict_data = svn_sqlite__column_text(stmt, 2, iterpool);
+
+ SVN_ERR(migrate_single_tree_conflict_data(sdb, tree_conflict_data,
+ wc_id, local_relpath,
+ iterpool));
+
+ /* We don't need to do anything but step over the previously
+ prepared statement. */
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Erase all the old tree conflict data. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_21_ERASE_OLD_CONFLICTS));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+struct bump_baton {
+ const char *wcroot_abspath;
+};
+
+/* Migrate the properties for one node (LOCAL_ABSPATH). */
+static svn_error_t *
+migrate_node_props(const char *dir_abspath,
+ const char *new_wcroot_abspath,
+ const char *name,
+ svn_sqlite__db_t *sdb,
+ int original_format,
+ apr_int64_t wc_id,
+ apr_pool_t *scratch_pool)
+{
+ const char *base_abspath; /* old name. nowadays: "pristine" */
+ const char *revert_abspath; /* old name. nowadays: "BASE" */
+ const char *working_abspath; /* old name. nowadays: "ACTUAL" */
+ apr_hash_t *base_props;
+ apr_hash_t *revert_props;
+ apr_hash_t *working_props;
+ const char *old_wcroot_abspath
+ = svn_dirent_get_longest_ancestor(dir_abspath, new_wcroot_abspath,
+ scratch_pool);
+ const char *dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath,
+ dir_abspath);
+
+ if (*name == '\0')
+ {
+ base_abspath = svn_wc__adm_child(dir_abspath,
+ PROP_BASE_FOR_DIR, scratch_pool);
+ revert_abspath = svn_wc__adm_child(dir_abspath,
+ PROP_REVERT_FOR_DIR, scratch_pool);
+ working_abspath = svn_wc__adm_child(dir_abspath,
+ PROP_WORKING_FOR_DIR, scratch_pool);
+ }
+ else
+ {
+ const char *basedir_abspath;
+ const char *propsdir_abspath;
+
+ propsdir_abspath = svn_wc__adm_child(dir_abspath, PROPS_SUBDIR,
+ scratch_pool);
+ basedir_abspath = svn_wc__adm_child(dir_abspath, PROP_BASE_SUBDIR,
+ scratch_pool);
+
+ base_abspath = svn_dirent_join(basedir_abspath,
+ apr_pstrcat(scratch_pool,
+ name,
+ SVN_WC__BASE_EXT,
+ (char *)NULL),
+ scratch_pool);
+
+ revert_abspath = svn_dirent_join(basedir_abspath,
+ apr_pstrcat(scratch_pool,
+ name,
+ SVN_WC__REVERT_EXT,
+ (char *)NULL),
+ scratch_pool);
+
+ working_abspath = svn_dirent_join(propsdir_abspath,
+ apr_pstrcat(scratch_pool,
+ name,
+ SVN_WC__WORK_EXT,
+ (char *)NULL),
+ scratch_pool);
+ }
+
+ SVN_ERR(read_propfile(&base_props, base_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(read_propfile(&revert_props, revert_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(read_propfile(&working_props, working_abspath,
+ scratch_pool, scratch_pool));
+
+ return svn_error_trace(svn_wc__db_upgrade_apply_props(
+ sdb, new_wcroot_abspath,
+ svn_relpath_join(dir_relpath, name, scratch_pool),
+ base_props, revert_props, working_props,
+ original_format, wc_id,
+ scratch_pool));
+}
+
+
+/* */
+static svn_error_t *
+migrate_props(const char *dir_abspath,
+ const char *new_wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ int original_format,
+ apr_int64_t wc_id,
+ apr_pool_t *scratch_pool)
+{
+ /* General logic here: iterate over all the immediate children of the root
+ (since we aren't yet in a centralized system), and for any properties that
+ exist, map them as follows:
+
+ if (revert props exist):
+ revert -> BASE
+ base -> WORKING
+ working -> ACTUAL
+ else if (prop pristine is working [as defined in props.c] ):
+ base -> WORKING
+ working -> ACTUAL
+ else:
+ base -> BASE
+ working -> ACTUAL
+
+ ### the middle "test" should simply look for a WORKING_NODE row
+
+ Note that it is legal for "working" props to be missing. That implies
+ no local changes to the properties.
+ */
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const char *old_wcroot_abspath
+ = svn_dirent_get_longest_ancestor(dir_abspath, new_wcroot_abspath,
+ scratch_pool);
+ const char *dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath,
+ dir_abspath);
+ int i;
+
+ /* Migrate the props for "this dir". */
+ SVN_ERR(migrate_node_props(dir_abspath, new_wcroot_abspath, "", sdb,
+ original_format, wc_id, iterpool));
+
+ /* Iterate over all the files in this SDB. */
+ SVN_ERR(get_versioned_files(&children, dir_relpath, sdb, wc_id, scratch_pool,
+ iterpool));
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(migrate_node_props(dir_abspath, new_wcroot_abspath,
+ name, sdb, original_format, wc_id, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* If STR ends with SUFFIX and is longer than SUFFIX, return the part of
+ * STR that comes before SUFFIX; else return NULL. */
+static char *
+remove_suffix(const char *str, const char *suffix, apr_pool_t *result_pool)
+{
+ size_t str_len = strlen(str);
+ size_t suffix_len = strlen(suffix);
+
+ if (str_len > suffix_len
+ && strcmp(str + str_len - suffix_len, suffix) == 0)
+ {
+ return apr_pstrmemdup(result_pool, str, str_len - suffix_len);
+ }
+
+ return NULL;
+}
+
+/* Copy all the text-base files from the administrative area of WC directory
+ DIR_ABSPATH into the pristine store of SDB which is located in directory
+ NEW_WCROOT_ABSPATH.
+
+ Set *TEXT_BASES_INFO to a new hash, allocated in RESULT_POOL, that maps
+ (const char *) name of the versioned file to (svn_wc__text_base_info_t *)
+ information about the pristine text. */
+static svn_error_t *
+migrate_text_bases(apr_hash_t **text_bases_info,
+ const char *dir_abspath,
+ const char *new_wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *dirents;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ const char *text_base_dir = svn_wc__adm_child(dir_abspath,
+ TEXT_BASE_SUBDIR,
+ scratch_pool);
+
+ *text_bases_info = apr_hash_make(result_pool);
+
+ /* Iterate over the text-base files */
+ SVN_ERR(svn_io_get_dirents3(&dirents, text_base_dir, TRUE,
+ scratch_pool, scratch_pool));
+ for (hi = apr_hash_first(scratch_pool, dirents); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *text_base_basename = svn__apr_hash_index_key(hi);
+ svn_checksum_t *md5_checksum;
+ svn_checksum_t *sha1_checksum;
+
+ svn_pool_clear(iterpool);
+
+ /* Calculate its checksums and copy it to the pristine store */
+ {
+ const char *pristine_path;
+ const char *text_base_path;
+ const char *temp_path;
+ svn_sqlite__stmt_t *stmt;
+ apr_finfo_t finfo;
+ svn_stream_t *read_stream;
+ svn_stream_t *result_stream;
+
+ text_base_path = svn_dirent_join(text_base_dir, text_base_basename,
+ iterpool);
+
+ /* Create a copy and calculate a checksum in one step */
+ SVN_ERR(svn_stream_open_unique(&result_stream, &temp_path,
+ new_wcroot_abspath,
+ svn_io_file_del_none,
+ iterpool, iterpool));
+
+ SVN_ERR(svn_stream_open_readonly(&read_stream, text_base_path,
+ iterpool, iterpool));
+
+ read_stream = svn_stream_checksummed2(read_stream, &md5_checksum,
+ NULL, svn_checksum_md5,
+ TRUE, iterpool);
+
+ read_stream = svn_stream_checksummed2(read_stream, &sha1_checksum,
+ NULL, svn_checksum_sha1,
+ TRUE, iterpool);
+
+ /* This calculates the hash, creates a copy and closes the stream */
+ SVN_ERR(svn_stream_copy3(read_stream, result_stream,
+ NULL, NULL, iterpool));
+
+ SVN_ERR(svn_io_stat(&finfo, text_base_path, APR_FINFO_SIZE, iterpool));
+
+ /* Insert a row into the pristine table. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_INSERT_OR_IGNORE_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, iterpool));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, iterpool));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 3, finfo.size));
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ SVN_ERR(svn_wc__db_pristine_get_future_path(&pristine_path,
+ new_wcroot_abspath,
+ sha1_checksum,
+ iterpool, iterpool));
+
+ /* Ensure any sharding directories exist. */
+ SVN_ERR(svn_wc__ensure_directory(svn_dirent_dirname(pristine_path,
+ iterpool),
+ iterpool));
+
+ /* Now move the file into the pristine store, overwriting
+ existing files with the same checksum. */
+ SVN_ERR(svn_io_file_move(temp_path, pristine_path, iterpool));
+ }
+
+ /* Add the checksums for this text-base to *TEXT_BASES_INFO. */
+ {
+ const char *versioned_file_name;
+ svn_boolean_t is_revert_base;
+ svn_wc__text_base_info_t *info;
+ svn_wc__text_base_file_info_t *file_info;
+
+ /* Determine the versioned file name and whether this is a normal base
+ * or a revert base. */
+ versioned_file_name = remove_suffix(text_base_basename,
+ SVN_WC__REVERT_EXT, result_pool);
+ if (versioned_file_name)
+ {
+ is_revert_base = TRUE;
+ }
+ else
+ {
+ versioned_file_name = remove_suffix(text_base_basename,
+ SVN_WC__BASE_EXT, result_pool);
+ is_revert_base = FALSE;
+ }
+
+ if (! versioned_file_name)
+ {
+ /* Some file that doesn't end with .svn-base or .svn-revert.
+ No idea why that would be in our administrative area, but
+ we shouldn't segfault on this case.
+
+ Note that we already copied this file in the pristine store,
+ but the next cleanup will take care of that.
+ */
+ continue;
+ }
+
+ /* Create a new info struct for this versioned file, or fill in the
+ * existing one if this is the second text-base we've found for it. */
+ info = svn_hash_gets(*text_bases_info, versioned_file_name);
+ if (info == NULL)
+ info = apr_pcalloc(result_pool, sizeof (*info));
+ file_info = (is_revert_base ? &info->revert_base : &info->normal_base);
+
+ file_info->sha1_checksum = svn_checksum_dup(sha1_checksum, result_pool);
+ file_info->md5_checksum = svn_checksum_dup(md5_checksum, result_pool);
+ svn_hash_sets(*text_bases_info, versioned_file_name, info);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_20(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_NODES));
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_20));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_21(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_21));
+ SVN_ERR(migrate_tree_conflict_data(sdb, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_22(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_22));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_23(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ const char *wcroot_abspath = ((struct bump_baton *)baton)->wcroot_abspath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_23_HAS_WORKING_NODES));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The working copy at '%s' is format 22 with "
+ "WORKING nodes; use a format 22 client to "
+ "diff/revert before using this client"),
+ wcroot_abspath);
+
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_23));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_24(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_24));
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_NODES_TRIGGERS));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_25(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_25));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_26(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_26));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_27(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ const char *wcroot_abspath = ((struct bump_baton *)baton)->wcroot_abspath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The working copy at '%s' is format 26 with "
+ "conflicts; use a format 26 client to resolve "
+ "before using this client"),
+ wcroot_abspath);
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_27));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_28(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_28));
+ return SVN_NO_ERROR;
+}
+
+/* If FINFO indicates that ABSPATH names a file, rename it to
+ * '<ABSPATH>.svn-base'.
+ *
+ * Ignore any file whose name is not the expected length, in order to make
+ * life easier for any developer who runs this code twice or has some
+ * non-standard files in the pristine directory.
+ *
+ * A callback for bump_to_29(), implementing #svn_io_walk_func_t. */
+static svn_error_t *
+rename_pristine_file(void *baton,
+ const char *abspath,
+ const apr_finfo_t *finfo,
+ apr_pool_t *pool)
+{
+ if (finfo->filetype == APR_REG
+ && (strlen(svn_dirent_basename(abspath, pool))
+ == PRISTINE_BASENAME_OLD_LEN))
+ {
+ const char *new_abspath
+ = apr_pstrcat(pool, abspath, PRISTINE_STORAGE_EXT, (char *)NULL);
+
+ SVN_ERR(svn_io_file_rename(abspath, new_abspath, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+upgrade_externals(struct bump_baton *bb,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_sqlite__stmt_t *stmt_add;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_SELECT_EXTERNAL_PROPERTIES));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt_add, sdb,
+ STMT_INSERT_EXTERNAL));
+
+ /* ### For this intermediate upgrade we just assume WC_ID = 1.
+ ### Before this bump we lost track of externals all the time,
+ ### so lets keep this easy. */
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", (apr_int64_t)1, ""));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ apr_hash_t *props;
+ const char *externals;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__column_properties(&props, stmt, 0,
+ iterpool, iterpool));
+
+ externals = svn_prop_get_value(props, SVN_PROP_EXTERNALS);
+
+ if (externals)
+ {
+ apr_array_header_t *ext;
+ const char *local_relpath;
+ const char *local_abspath;
+ int i;
+
+ local_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ local_abspath = svn_dirent_join(bb->wcroot_abspath, local_relpath,
+ iterpool);
+
+ SVN_ERR(svn_wc_parse_externals_description3(&ext, local_abspath,
+ externals, FALSE,
+ iterpool));
+
+ for (i = 0; i < ext->nelts; i++)
+ {
+ const svn_wc_external_item2_t *item;
+ const char *item_relpath;
+
+ item = APR_ARRAY_IDX(ext, i, const svn_wc_external_item2_t *);
+ item_relpath = svn_relpath_join(local_relpath, item->target_dir,
+ iterpool);
+
+ /* Insert dummy externals definitions: Insert an unknown
+ external, to make sure it will be cleaned up when it is not
+ updated on the next update. */
+ SVN_ERR(svn_sqlite__bindf(stmt_add, "isssssis",
+ (apr_int64_t)1, /* wc_id */
+ item_relpath,
+ svn_relpath_dirname(item_relpath,
+ iterpool),
+ "normal",
+ "unknown",
+ local_relpath,
+ (apr_int64_t)1, /* repos_id */
+ "" /* repos_relpath */));
+ SVN_ERR(svn_sqlite__insert(NULL, stmt_add));
+ }
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+static svn_error_t *
+bump_to_29(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ struct bump_baton *bb = baton;
+ const char *wcroot_abspath = bb->wcroot_abspath;
+ const char *pristine_dir_abspath;
+
+ /* Rename all pristine files, adding a ".svn-base" suffix. */
+ pristine_dir_abspath = svn_dirent_join_many(scratch_pool, wcroot_abspath,
+ svn_wc_get_adm_dir(scratch_pool),
+ PRISTINE_STORAGE_RELPATH, NULL);
+ SVN_ERR(svn_io_dir_walk2(pristine_dir_abspath, APR_FINFO_MIN,
+ rename_pristine_file, NULL, scratch_pool));
+
+ /* Externals */
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_EXTERNALS));
+
+ SVN_ERR(upgrade_externals(bb, sdb, scratch_pool));
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_29));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__upgrade_conflict_skel_from_raw(svn_skel_t **conflicts,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_relpath,
+ const char *conflict_old,
+ const char *conflict_wrk,
+ const char *conflict_new,
+ const char *prej_file,
+ const char *tree_conflict_data,
+ apr_size_t tree_conflict_len,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *conflict_data = NULL;
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, db, wri_abspath,
+ scratch_pool, scratch_pool));
+
+ if (conflict_old || conflict_new || conflict_wrk)
+ {
+ const char *old_abspath = NULL;
+ const char *new_abspath = NULL;
+ const char *wrk_abspath = NULL;
+
+ conflict_data = svn_wc__conflict_skel_create(result_pool);
+
+ if (conflict_old)
+ old_abspath = svn_dirent_join(wcroot_abspath, conflict_old,
+ scratch_pool);
+
+ if (conflict_new)
+ new_abspath = svn_dirent_join(wcroot_abspath, conflict_new,
+ scratch_pool);
+
+ if (conflict_wrk)
+ wrk_abspath = svn_dirent_join(wcroot_abspath, conflict_wrk,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_text_conflict(conflict_data,
+ db, wri_abspath,
+ wrk_abspath,
+ old_abspath,
+ new_abspath,
+ scratch_pool,
+ scratch_pool));
+ }
+
+ if (prej_file)
+ {
+ const char *prej_abspath;
+
+ if (!conflict_data)
+ conflict_data = svn_wc__conflict_skel_create(result_pool);
+
+ prej_abspath = svn_dirent_join(wcroot_abspath, prej_file, scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflict_data,
+ db, wri_abspath,
+ prej_abspath,
+ NULL, NULL, NULL,
+ apr_hash_make(scratch_pool),
+ scratch_pool,
+ scratch_pool));
+ }
+
+ if (tree_conflict_data)
+ {
+ svn_skel_t *tc_skel;
+ const svn_wc_conflict_description2_t *tc;
+ const char *local_abspath;
+
+ if (!conflict_data)
+ conflict_data = svn_wc__conflict_skel_create(scratch_pool);
+
+ tc_skel = svn_skel__parse(tree_conflict_data, tree_conflict_len,
+ scratch_pool);
+
+ local_abspath = svn_dirent_join(wcroot_abspath, local_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__deserialize_conflict(&tc, tc_skel,
+ svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(conflict_data,
+ db, wri_abspath,
+ tc->reason,
+ tc->action,
+ NULL,
+ scratch_pool,
+ scratch_pool));
+
+ switch (tc->operation)
+ {
+ case svn_wc_operation_update:
+ default:
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_data,
+ tc->src_left_version,
+ tc->src_right_version,
+ scratch_pool,
+ scratch_pool));
+ break;
+ case svn_wc_operation_switch:
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict_data,
+ tc->src_left_version,
+ tc->src_right_version,
+ scratch_pool,
+ scratch_pool));
+ break;
+ case svn_wc_operation_merge:
+ SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_data,
+ tc->src_left_version,
+ tc->src_right_version,
+ scratch_pool,
+ scratch_pool));
+ break;
+ }
+ }
+ else if (conflict_data)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_data, NULL, NULL,
+ scratch_pool,
+ scratch_pool));
+ }
+
+ *conflicts = conflict_data;
+ return SVN_NO_ERROR;
+}
+
+/* Helper function to upgrade a single conflict from bump_to_30 */
+static svn_error_t *
+bump_30_upgrade_one_conflict(svn_wc__db_t *wc_db,
+ const char *wcroot_abspath,
+ svn_sqlite__stmt_t *stmt,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt_store;
+ svn_stringbuf_t *skel_data;
+ svn_skel_t *conflict_data;
+ apr_int64_t wc_id = svn_sqlite__column_int64(stmt, 0);
+ const char *local_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ const char *conflict_old = svn_sqlite__column_text(stmt, 2, NULL);
+ const char *conflict_wrk = svn_sqlite__column_text(stmt, 3, NULL);
+ const char *conflict_new = svn_sqlite__column_text(stmt, 4, NULL);
+ const char *prop_reject = svn_sqlite__column_text(stmt, 5, NULL);
+ apr_size_t tree_conflict_size;
+ const char *tree_conflict_data = svn_sqlite__column_blob(stmt, 6,
+ &tree_conflict_size, NULL);
+
+ SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw(&conflict_data,
+ wc_db, wcroot_abspath,
+ local_relpath,
+ conflict_old,
+ conflict_wrk,
+ conflict_new,
+ prop_reject,
+ tree_conflict_data,
+ tree_conflict_size,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(conflict_data != NULL);
+
+ skel_data = svn_skel__unparse(conflict_data, scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt_store, sdb,
+ STMT_UPGRADE_30_SET_CONFLICT));
+ SVN_ERR(svn_sqlite__bindf(stmt_store, "isb", wc_id, local_relpath,
+ skel_data->data, skel_data->len));
+ SVN_ERR(svn_sqlite__step_done(stmt_store));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_30(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+ struct bump_baton *bb = baton;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_t *db; /* Read only temp db */
+
+ SVN_ERR(svn_wc__db_open(&db, NULL, TRUE /* open_without_upgrade */, FALSE,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ svn_error_t *err;
+ svn_pool_clear(iterpool);
+
+ err = bump_30_upgrade_one_conflict(db, bb->wcroot_abspath, stmt, sdb,
+ iterpool);
+
+ if (err)
+ {
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_30));
+ SVN_ERR(svn_wc__db_close(db));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+bump_to_31(void *baton,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt, *stmt_mark_switch_roots;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *empty_iprops = apr_array_make(
+ scratch_pool, 0, sizeof(svn_prop_inherited_item_t *));
+ svn_boolean_t iprops_column_exists = FALSE;
+ svn_error_t *err;
+
+ /* Add the inherited_props column to NODES if it does not yet exist.
+ *
+ * When using a format >= 31 client to upgrade from old formats which
+ * did not yet have a NODES table, the inherited_props column has
+ * already been created as part of the NODES table. Attemping to add
+ * the inherited_props column will raise an error in this case, so check
+ * if the column exists first.
+ *
+ * Checking for the existence of a column before ALTER TABLE is not
+ * possible within SQLite. We need to run a separate query and evaluate
+ * its result in C first.
+ */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_PRAGMA_TABLE_INFO_NODES));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *column_name = svn_sqlite__column_text(stmt, 1, NULL);
+
+ if (strcmp(column_name, "inherited_props") == 0)
+ {
+ iprops_column_exists = TRUE;
+ break;
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!iprops_column_exists)
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_31_ALTER_TABLE));
+
+ /* Run additional statements to finalize the upgrade to format 31. */
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_31_FINALIZE));
+
+ /* Set inherited_props to an empty array for the roots of all
+ switched subtrees in the WC. This allows subsequent updates
+ to recognize these roots as needing an iprops cache. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_31_SELECT_WCROOT_NODES));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ err = svn_sqlite__get_statement(&stmt_mark_switch_roots, sdb,
+ STMT_UPDATE_IPROP);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ while (have_row)
+ {
+ const char *switched_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ apr_int64_t wc_id = svn_sqlite__column_int64(stmt, 0);
+
+ err = svn_sqlite__bindf(stmt_mark_switch_roots, "is", wc_id,
+ switched_relpath);
+ if (!err)
+ err = svn_sqlite__bind_iprops(stmt_mark_switch_roots, 3,
+ empty_iprops, iterpool);
+ if (!err)
+ err = svn_sqlite__step_done(stmt_mark_switch_roots);
+ if (!err)
+ err = svn_sqlite__step(&have_row, stmt);
+
+ if (err)
+ return svn_error_compose_create(
+ err,
+ svn_error_compose_create(
+ /* Reset in either order is OK. */
+ svn_sqlite__reset(stmt),
+ svn_sqlite__reset(stmt_mark_switch_roots)));
+ }
+
+ err = svn_sqlite__reset(stmt_mark_switch_roots);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+struct upgrade_data_t {
+ svn_sqlite__db_t *sdb;
+ const char *root_abspath;
+ apr_int64_t repos_id;
+ apr_int64_t wc_id;
+};
+
+/* Upgrade the working copy directory represented by DB/DIR_ABSPATH
+ from OLD_FORMAT to the wc-ng format (SVN_WC__WC_NG_VERSION)'.
+
+ Pass REPOS_INFO_FUNC, REPOS_INFO_BATON and REPOS_CACHE to
+ ensure_repos_info. Add the found repository root and UUID to
+ REPOS_CACHE if it doesn't have a cached entry for this
+ repository.
+
+ *DATA refers to the single root db.
+
+ Uses SCRATCH_POOL for all temporary allocation. */
+static svn_error_t *
+upgrade_to_wcng(void **dir_baton,
+ void *parent_baton,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ int old_format,
+ apr_int64_t wc_id,
+ svn_wc_upgrade_get_repos_info_t repos_info_func,
+ void *repos_info_baton,
+ apr_hash_t *repos_cache,
+ const struct upgrade_data_t *data,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *logfile_path = svn_wc__adm_child(dir_abspath, ADM_LOG,
+ scratch_pool);
+ svn_node_kind_t logfile_on_disk_kind;
+ apr_hash_t *entries;
+ svn_wc_entry_t *this_dir;
+ const char *old_wcroot_abspath, *dir_relpath;
+ apr_hash_t *text_bases_info;
+ svn_error_t *err;
+
+ /* Don't try to mess with the WC if there are old log files left. */
+
+ /* Is the (first) log file present? */
+ SVN_ERR(svn_io_check_path(logfile_path, &logfile_on_disk_kind,
+ scratch_pool));
+ if (logfile_on_disk_kind == svn_node_file)
+ return svn_error_create(SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Cannot upgrade with existing logs; run a "
+ "cleanup operation on this working copy using "
+ "a client version which is compatible with this "
+ "working copy's format (such as the version "
+ "you are upgrading from), then retry the "
+ "upgrade with the current version"));
+
+ /* Lock this working copy directory, or steal an existing lock. Do this
+ BEFORE we read the entries. We don't want another process to modify the
+ entries after we've read them into memory. */
+ SVN_ERR(create_physical_lock(dir_abspath, scratch_pool));
+
+ /* What's going on here?
+ *
+ * We're attempting to upgrade an older working copy to the new wc-ng format.
+ * The semantics and storage mechanisms between the two are vastly different,
+ * so it's going to be a bit painful. Here's a plan for the operation:
+ *
+ * 1) Read the old 'entries' using the old-format reader.
+ *
+ * 2) Create the new DB if it hasn't already been created.
+ *
+ * 3) Use our compatibility code for writing entries to fill out the (new)
+ * DB state. Use the remembered checksums, since an entry has only the
+ * MD5 not the SHA1 checksum, and in the case of a revert-base doesn't
+ * even have that.
+ *
+ * 4) Convert wcprop to the wc-ng format
+ *
+ * 5) Migrate regular properties to the WC-NG DB.
+ */
+
+ /***** ENTRIES - READ *****/
+ SVN_ERR(svn_wc__read_entries_old(&entries, dir_abspath,
+ scratch_pool, scratch_pool));
+
+ this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+ SVN_ERR(ensure_repos_info(this_dir, dir_abspath,
+ repos_info_func, repos_info_baton,
+ repos_cache,
+ scratch_pool, scratch_pool));
+
+ /* Cache repos UUID pairs for when a subdir doesn't have this information */
+ if (!svn_hash_gets(repos_cache, this_dir->repos))
+ {
+ apr_pool_t *hash_pool = apr_hash_pool_get(repos_cache);
+
+ svn_hash_sets(repos_cache,
+ apr_pstrdup(hash_pool, this_dir->repos),
+ apr_pstrdup(hash_pool, this_dir->uuid));
+ }
+
+ old_wcroot_abspath = svn_dirent_get_longest_ancestor(dir_abspath,
+ data->root_abspath,
+ scratch_pool);
+ dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath, dir_abspath);
+
+ /***** TEXT BASES *****/
+ SVN_ERR(migrate_text_bases(&text_bases_info, dir_abspath, data->root_abspath,
+ data->sdb, scratch_pool, scratch_pool));
+
+ /***** ENTRIES - WRITE *****/
+ err = svn_wc__write_upgraded_entries(dir_baton, parent_baton, db, data->sdb,
+ data->repos_id, data->wc_id,
+ dir_abspath, data->root_abspath,
+ entries, text_bases_info,
+ result_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_CORRUPT)
+ return svn_error_quick_wrap(err,
+ _("This working copy is corrupt and "
+ "cannot be upgraded. Please check out "
+ "a new working copy."));
+ else
+ SVN_ERR(err);
+
+ /***** WC PROPS *****/
+ /* If we don't know precisely where the wcprops are, ignore them. */
+ if (old_format != SVN_WC__WCPROPS_LOST)
+ {
+ apr_hash_t *all_wcprops;
+
+ if (old_format <= SVN_WC__WCPROPS_MANY_FILES_VERSION)
+ SVN_ERR(read_many_wcprops(&all_wcprops, dir_abspath,
+ scratch_pool, scratch_pool));
+ else
+ SVN_ERR(read_wcprops(&all_wcprops, dir_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_upgrade_apply_dav_cache(data->sdb, dir_relpath,
+ all_wcprops, scratch_pool));
+ }
+
+ /* Upgrade all the properties (including "this dir").
+
+ Note: this must come AFTER the entries have been migrated into the
+ database. The upgrade process needs the children in BASE_NODE and
+ WORKING_NODE, and to examine the resultant WORKING state. */
+ SVN_ERR(migrate_props(dir_abspath, data->root_abspath, data->sdb, old_format,
+ wc_id, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_wc__version_string_from_format(int wc_format)
+{
+ switch (wc_format)
+ {
+ case 4: return "<=1.3";
+ case 8: return "1.4";
+ case 9: return "1.5";
+ case 10: return "1.6";
+ case SVN_WC__WC_NG_VERSION: return "1.7";
+ }
+ return _("(unreleased development version)");
+}
+
+svn_error_t *
+svn_wc__upgrade_sdb(int *result_format,
+ const char *wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ int start_format,
+ apr_pool_t *scratch_pool)
+{
+ struct bump_baton bb;
+
+ bb.wcroot_abspath = wcroot_abspath;
+
+ if (start_format < SVN_WC__WC_NG_VERSION /* 12 */)
+ return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL,
+ _("Working copy '%s' is too old (format %d, "
+ "created by Subversion %s)"),
+ svn_dirent_local_style(wcroot_abspath,
+ scratch_pool),
+ start_format,
+ svn_wc__version_string_from_format(start_format));
+
+ /* Early WCNG formats no longer supported. */
+ if (start_format < 19)
+ return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL,
+ _("Working copy '%s' is an old development "
+ "version (format %d); to upgrade it, "
+ "use a format 18 client, then "
+ "use 'tools/dev/wc-ng/bump-to-19.py', then "
+ "use the current client"),
+ svn_dirent_local_style(wcroot_abspath,
+ scratch_pool),
+ start_format);
+
+ /* ### need lock-out. only one upgrade at a time. note that other code
+ ### cannot use this un-upgraded database until we finish the upgrade. */
+
+ /* Note: none of these have "break" statements; the fall-through is
+ intentional. */
+ switch (start_format)
+ {
+ case 19:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_20, &bb,
+ scratch_pool));
+ *result_format = 20;
+ /* FALLTHROUGH */
+
+ case 20:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_21, &bb,
+ scratch_pool));
+ *result_format = 21;
+ /* FALLTHROUGH */
+
+ case 21:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_22, &bb,
+ scratch_pool));
+ *result_format = 22;
+ /* FALLTHROUGH */
+
+ case 22:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_23, &bb,
+ scratch_pool));
+ *result_format = 23;
+ /* FALLTHROUGH */
+
+ case 23:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_24, &bb,
+ scratch_pool));
+ *result_format = 24;
+ /* FALLTHROUGH */
+
+ case 24:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_25, &bb,
+ scratch_pool));
+ *result_format = 25;
+ /* FALLTHROUGH */
+
+ case 25:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_26, &bb,
+ scratch_pool));
+ *result_format = 26;
+ /* FALLTHROUGH */
+
+ case 26:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_27, &bb,
+ scratch_pool));
+ *result_format = 27;
+ /* FALLTHROUGH */
+
+ case 27:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_28, &bb,
+ scratch_pool));
+ *result_format = 28;
+ /* FALLTHROUGH */
+
+ case 28:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_29, &bb,
+ scratch_pool));
+ *result_format = 29;
+ /* FALLTHROUGH */
+
+ case 29:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_30, &bb,
+ scratch_pool));
+ *result_format = 30;
+
+ case 30:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_31, &bb,
+ scratch_pool));
+ *result_format = 31;
+ /* FALLTHROUGH */
+ /* ### future bumps go here. */
+#if 0
+ case XXX-1:
+ /* Revamp the recording of tree conflicts. */
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_XXX, &bb,
+ scratch_pool));
+ *result_format = XXX;
+ /* FALLTHROUGH */
+#endif
+ case SVN_WC__VERSION:
+ /* already upgraded */
+ *result_format = SVN_WC__VERSION;
+ }
+
+#ifdef SVN_DEBUG
+ if (*result_format != start_format)
+ {
+ int schema_version;
+ SVN_ERR(svn_sqlite__read_schema_version(&schema_version, sdb, scratch_pool));
+
+ /* If this assertion fails the schema isn't updated correctly */
+ SVN_ERR_ASSERT(schema_version == *result_format);
+ }
+#endif
+
+ /* Zap anything that might be remaining or escaped our notice. */
+ wipe_obsolete_files(wcroot_abspath, scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+upgrade_working_copy(void *parent_baton,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ svn_wc_upgrade_get_repos_info_t repos_info_func,
+ void *repos_info_baton,
+ apr_hash_t *repos_cache,
+ const struct upgrade_data_t *data,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ void *dir_baton;
+ int old_format;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *subdirs;
+ svn_error_t *err;
+ int i;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(svn_wc__db_temp_get_format(&old_format, db, dir_abspath,
+ iterpool));
+
+ if (old_format >= SVN_WC__WC_NG_VERSION)
+ {
+ if (notify_func)
+ notify_func(notify_baton,
+ svn_wc_create_notify(dir_abspath, svn_wc_notify_skip,
+ iterpool),
+ iterpool);
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+
+ err = get_versioned_subdirs(&subdirs, NULL, dir_abspath, FALSE,
+ scratch_pool, iterpool);
+ if (err)
+ {
+ if (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
+ {
+ /* An unversioned dir is obstructing a versioned dir */
+ svn_error_clear(err);
+ err = NULL;
+ if (notify_func)
+ notify_func(notify_baton,
+ svn_wc_create_notify(dir_abspath, svn_wc_notify_skip,
+ iterpool),
+ iterpool);
+ }
+ svn_pool_destroy(iterpool);
+ return err;
+ }
+
+
+ SVN_ERR(upgrade_to_wcng(&dir_baton, parent_baton, db, dir_abspath,
+ old_format, data->wc_id,
+ repos_info_func, repos_info_baton,
+ repos_cache, data, scratch_pool, iterpool));
+
+ if (notify_func)
+ notify_func(notify_baton,
+ svn_wc_create_notify(dir_abspath, svn_wc_notify_upgraded_path,
+ iterpool),
+ iterpool);
+
+ for (i = 0; i < subdirs->nelts; ++i)
+ {
+ const char *child_abspath = APR_ARRAY_IDX(subdirs, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(upgrade_working_copy(dir_baton, db, child_abspath,
+ repos_info_func, repos_info_baton,
+ repos_cache, data,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return a verbose error if LOCAL_ABSPATH is a not a pre-1.7 working
+ copy root */
+static svn_error_t *
+is_old_wcroot(const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *entries;
+ const char *parent_abspath, *name;
+ svn_wc_entry_t *entry;
+ svn_error_t *err = svn_wc__read_entries_old(&entries, local_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_INVALID_OP_ON_CWD, err,
+ _("Can't upgrade '%s' as it is not a working copy"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+ return SVN_NO_ERROR;
+
+ svn_dirent_split(&parent_abspath, &name, local_abspath, scratch_pool);
+
+ err = svn_wc__read_entries_old(&entries, parent_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ entry = svn_hash_gets(entries, name);
+ if (!entry
+ || entry->absent
+ || (entry->deleted && entry->schedule != svn_wc_schedule_add)
+ || entry->depth == svn_depth_exclude)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ while (!svn_dirent_is_root(parent_abspath, strlen(parent_abspath)))
+ {
+ svn_dirent_split(&parent_abspath, &name, parent_abspath, scratch_pool);
+ err = svn_wc__read_entries_old(&entries, parent_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ parent_abspath = svn_dirent_join(parent_abspath, name, scratch_pool);
+ break;
+ }
+ entry = svn_hash_gets(entries, name);
+ if (!entry
+ || entry->absent
+ || (entry->deleted && entry->schedule != svn_wc_schedule_add)
+ || entry->depth == svn_depth_exclude)
+ {
+ parent_abspath = svn_dirent_join(parent_abspath, name, scratch_pool);
+ break;
+ }
+ }
+
+ return svn_error_createf(
+ SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
+ _("Can't upgrade '%s' as it is not a working copy root,"
+ " the root is '%s'"),
+ svn_dirent_local_style(local_abspath, scratch_pool),
+ svn_dirent_local_style(parent_abspath, scratch_pool));
+}
+
+/* Data for upgrade_working_copy_txn(). */
+typedef struct upgrade_working_copy_baton_t
+{
+ svn_wc__db_t *db;
+ const char *dir_abspath;
+ svn_wc_upgrade_get_repos_info_t repos_info_func;
+ void *repos_info_baton;
+ apr_hash_t *repos_cache;
+ const struct upgrade_data_t *data;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+ apr_pool_t *result_pool;
+} upgrade_working_copy_baton_t;
+
+
+/* Helper for svn_wc_upgrade. Implements svn_sqlite__transaction_callback_t */
+static svn_error_t *
+upgrade_working_copy_txn(void *baton,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ upgrade_working_copy_baton_t *b = baton;
+
+ /* Upgrade the pre-wcng into a wcng in a temporary location. */
+ return(upgrade_working_copy(NULL, b->db, b->dir_abspath,
+ b->repos_info_func, b->repos_info_baton,
+ b->repos_cache, b->data,
+ b->cancel_func, b->cancel_baton,
+ b->notify_func, b->notify_baton,
+ b->result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc_upgrade(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_wc_upgrade_get_repos_info_t repos_info_func,
+ void *repos_info_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db;
+ struct upgrade_data_t data = { NULL };
+ svn_skel_t *work_item, *work_items = NULL;
+ const char *pristine_from, *pristine_to, *db_from, *db_to;
+ apr_hash_t *repos_cache = apr_hash_make(scratch_pool);
+ svn_wc_entry_t *this_dir;
+ apr_hash_t *entries;
+ const char *root_adm_abspath;
+ upgrade_working_copy_baton_t cb_baton;
+ svn_error_t *err;
+ int result_format;
+
+ /* Try upgrading a wc-ng-style working copy. */
+ SVN_ERR(svn_wc__db_open(&db, NULL /* ### config */, TRUE, FALSE,
+ scratch_pool, scratch_pool));
+
+
+ err = svn_wc__db_bump_format(&result_format, local_abspath, db,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED)
+ {
+ return svn_error_trace(
+ svn_error_compose_create(
+ err,
+ svn_wc__db_close(db)));
+ }
+
+ svn_error_clear(err);
+ /* Pre 1.7: Fall through */
+ }
+ else
+ {
+ /* Auto-upgrade worked! */
+ SVN_ERR(svn_wc__db_close(db));
+
+ SVN_ERR_ASSERT(result_format == SVN_WC__VERSION);
+
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(is_old_wcroot(local_abspath, scratch_pool));
+
+ /* Given a pre-wcng root some/wc we create a temporary wcng in
+ some/wc/.svn/tmp/wcng/wc.db and copy the metadata from one to the
+ other, then the temporary wc.db file gets moved into the original
+ root. Until the wc.db file is moved the original working copy
+ remains a pre-wcng and 'cleanup' with an old client will remove
+ the partial upgrade. Moving the wc.db file creates a wcng, and
+ 'cleanup' with a new client will complete any outstanding
+ upgrade. */
+
+ SVN_ERR(svn_wc__read_entries_old(&entries, local_abspath,
+ scratch_pool, scratch_pool));
+
+ this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+ SVN_ERR(ensure_repos_info(this_dir, local_abspath, repos_info_func,
+ repos_info_baton, repos_cache,
+ scratch_pool, scratch_pool));
+
+ /* Cache repos UUID pairs for when a subdir doesn't have this information */
+ if (!svn_hash_gets(repos_cache, this_dir->repos))
+ svn_hash_sets(repos_cache,
+ apr_pstrdup(scratch_pool, this_dir->repos),
+ apr_pstrdup(scratch_pool, this_dir->uuid));
+
+ /* Create the new DB in the temporary root wc/.svn/tmp/wcng/.svn */
+ data.root_abspath = svn_dirent_join(svn_wc__adm_child(local_abspath, "tmp",
+ scratch_pool),
+ "wcng", scratch_pool);
+ root_adm_abspath = svn_wc__adm_child(data.root_abspath, "",
+ scratch_pool);
+ SVN_ERR(svn_io_remove_dir2(root_adm_abspath, TRUE, NULL, NULL,
+ scratch_pool));
+ SVN_ERR(svn_wc__ensure_directory(root_adm_abspath, scratch_pool));
+
+ /* Create an empty sqlite database for this directory and store it in DB. */
+ SVN_ERR(svn_wc__db_upgrade_begin(&data.sdb,
+ &data.repos_id, &data.wc_id,
+ db, data.root_abspath,
+ this_dir->repos, this_dir->uuid,
+ scratch_pool));
+
+ /* Migrate the entries over to the new database.
+ ### We need to think about atomicity here.
+
+ entries_write_new() writes in current format rather than
+ f12. Thus, this function bumps a working copy all the way to
+ current. */
+ SVN_ERR(svn_wc__db_wclock_obtain(db, data.root_abspath, 0, FALSE,
+ scratch_pool));
+
+ cb_baton.db = db;
+ cb_baton.dir_abspath = local_abspath;
+ cb_baton.repos_info_func = repos_info_func;
+ cb_baton.repos_info_baton = repos_info_baton;
+ cb_baton.repos_cache = repos_cache;
+ cb_baton.data = &data;
+ cb_baton.cancel_func = cancel_func;
+ cb_baton.cancel_baton = cancel_baton;
+ cb_baton.notify_func = notify_func;
+ cb_baton.notify_baton = notify_baton;
+ cb_baton.result_pool = scratch_pool;
+
+ SVN_ERR(svn_sqlite__with_lock(data.sdb,
+ upgrade_working_copy_txn,
+ &cb_baton,
+ scratch_pool));
+
+ /* A workqueue item to move the pristine dir into place */
+ pristine_from = svn_wc__adm_child(data.root_abspath, PRISTINE_STORAGE_RELPATH,
+ scratch_pool);
+ pristine_to = svn_wc__adm_child(local_abspath, PRISTINE_STORAGE_RELPATH,
+ scratch_pool);
+ SVN_ERR(svn_wc__ensure_directory(pristine_from, scratch_pool));
+ SVN_ERR(svn_wc__wq_build_file_move(&work_item, db, local_abspath,
+ pristine_from, pristine_to,
+ scratch_pool, scratch_pool));
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ /* A workqueue item to remove pre-wcng metadata */
+ SVN_ERR(svn_wc__wq_build_postupgrade(&work_item, scratch_pool));
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ SVN_ERR(svn_wc__db_wq_add(db, data.root_abspath, work_items, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wclock_release(db, data.root_abspath, scratch_pool));
+ SVN_ERR(svn_wc__db_close(db));
+
+ /* Renaming the db file is what makes the pre-wcng into a wcng */
+ db_from = svn_wc__adm_child(data.root_abspath, SDB_FILE, scratch_pool);
+ db_to = svn_wc__adm_child(local_abspath, SDB_FILE, scratch_pool);
+ SVN_ERR(svn_io_file_rename(db_from, db_to, scratch_pool));
+
+ /* Now we have a working wcng, tidy up the droppings */
+ SVN_ERR(svn_wc__db_open(&db, NULL /* ### config */, FALSE, FALSE,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+ SVN_ERR(svn_wc__db_close(db));
+
+ /* Should we have the workqueue remove this empty dir? */
+ SVN_ERR(svn_io_remove_dir2(data.root_abspath, FALSE, NULL, NULL,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__upgrade_add_external_info(svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *def_local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t def_peg_revision,
+ svn_revnum_t def_revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t db_kind;
+ switch (kind)
+ {
+ case svn_node_dir:
+ db_kind = svn_node_dir;
+ break;
+
+ case svn_node_file:
+ db_kind = svn_node_file;
+ break;
+
+ case svn_node_unknown:
+ db_kind = svn_node_unknown;
+ break;
+
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+
+ SVN_ERR(svn_wc__db_upgrade_insert_external(wc_ctx->db, local_abspath,
+ db_kind,
+ svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ def_local_abspath, repos_relpath,
+ repos_root_url, repos_uuid,
+ def_peg_revision, def_revision,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/util.c b/contrib/subversion/subversion/libsvn_wc/util.c
new file mode 100644
index 0000000..a527eda
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/util.c
@@ -0,0 +1,636 @@
+/*
+ * util.c: general routines defying categorization; eventually I
+ * suspect they'll end up in libsvn_subr, but don't want to
+ * pollute that right now. Note that nothing in here is
+ * specific to working copies.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+
+#include "svn_io.h"
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_version.h"
+
+#include "wc.h" /* just for prototypes of things in this .c file */
+#include "entries.h"
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+
+svn_error_t *
+svn_wc__ensure_directory(const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_io_check_path(path, &kind, pool));
+
+ if (kind != svn_node_none && kind != svn_node_dir)
+ {
+ /* If got an error other than dir non-existence, then we can't
+ ensure this directory's existence, so just return the error.
+ Might happen if there's a file in the way, for example. */
+ return svn_error_createf(APR_ENOTDIR, NULL,
+ _("'%s' is not a directory"),
+ svn_dirent_local_style(path, pool));
+ }
+ else if (kind == svn_node_none)
+ {
+ /* The dir doesn't exist, and it's our job to change that. */
+ SVN_ERR(svn_io_make_dir_recursively(path, pool));
+ }
+ else /* No problem, the dir already existed, so just leave. */
+ SVN_ERR_ASSERT(kind == svn_node_dir);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the library version number. */
+const svn_version_t *
+svn_wc_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+svn_wc_notify_t *
+svn_wc_create_notify(const char *path,
+ svn_wc_notify_action_t action,
+ apr_pool_t *pool)
+{
+ svn_wc_notify_t *ret = apr_pcalloc(pool, sizeof(*ret));
+ ret->path = path;
+ ret->action = action;
+ ret->kind = svn_node_unknown;
+ ret->content_state = ret->prop_state = svn_wc_notify_state_unknown;
+ ret->lock_state = svn_wc_notify_lock_state_unknown;
+ ret->revision = SVN_INVALID_REVNUM;
+ ret->old_revision = SVN_INVALID_REVNUM;
+
+ return ret;
+}
+
+svn_wc_notify_t *
+svn_wc_create_notify_url(const char *url,
+ svn_wc_notify_action_t action,
+ apr_pool_t *pool)
+{
+ svn_wc_notify_t *ret = svn_wc_create_notify(".", action, pool);
+ ret->url = url;
+
+ return ret;
+}
+
+/* Pool cleanup function to clear an svn_error_t *. */
+static apr_status_t err_cleanup(void *data)
+{
+ svn_error_clear(data);
+
+ return APR_SUCCESS;
+}
+
+svn_wc_notify_t *
+svn_wc_dup_notify(const svn_wc_notify_t *notify,
+ apr_pool_t *pool)
+{
+ svn_wc_notify_t *ret = apr_palloc(pool, sizeof(*ret));
+
+ *ret = *notify;
+
+ if (ret->path)
+ ret->path = apr_pstrdup(pool, ret->path);
+ if (ret->mime_type)
+ ret->mime_type = apr_pstrdup(pool, ret->mime_type);
+ if (ret->lock)
+ ret->lock = svn_lock_dup(ret->lock, pool);
+ if (ret->err)
+ {
+ ret->err = svn_error_dup(ret->err);
+ apr_pool_cleanup_register(pool, ret->err, err_cleanup,
+ apr_pool_cleanup_null);
+ }
+ if (ret->changelist_name)
+ ret->changelist_name = apr_pstrdup(pool, ret->changelist_name);
+ if (ret->merge_range)
+ ret->merge_range = svn_merge_range_dup(ret->merge_range, pool);
+ if (ret->url)
+ ret->url = apr_pstrdup(pool, ret->url);
+ if (ret->path_prefix)
+ ret->path_prefix = apr_pstrdup(pool, ret->path_prefix);
+ if (ret->prop_name)
+ ret->prop_name = apr_pstrdup(pool, ret->prop_name);
+ if (ret->rev_props)
+ ret->rev_props = svn_prop_hash_dup(ret->rev_props, pool);
+
+ return ret;
+}
+
+svn_error_t *
+svn_wc_external_item2_create(svn_wc_external_item2_t **item,
+ apr_pool_t *pool)
+{
+ *item = apr_pcalloc(pool, sizeof(svn_wc_external_item2_t));
+ return SVN_NO_ERROR;
+}
+
+
+svn_wc_external_item2_t *
+svn_wc_external_item2_dup(const svn_wc_external_item2_t *item,
+ apr_pool_t *pool)
+{
+ svn_wc_external_item2_t *new_item = apr_palloc(pool, sizeof(*new_item));
+
+ *new_item = *item;
+
+ if (new_item->target_dir)
+ new_item->target_dir = apr_pstrdup(pool, new_item->target_dir);
+
+ if (new_item->url)
+ new_item->url = apr_pstrdup(pool, new_item->url);
+
+ return new_item;
+}
+
+
+svn_boolean_t
+svn_wc_match_ignore_list(const char *str, const apr_array_header_t *list,
+ apr_pool_t *pool)
+{
+ /* For now, we simply forward to svn_cstring_match_glob_list. In the
+ future, if we support more complex ignore patterns, we would iterate
+ over 'list' ourselves, and decide for each pattern how to handle
+ it. */
+
+ return svn_cstring_match_glob_list(str, list);
+}
+
+svn_wc_conflict_description2_t *
+svn_wc_conflict_description_create_text2(const char *local_abspath,
+ apr_pool_t *result_pool)
+{
+ svn_wc_conflict_description2_t *conflict;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath));
+
+ conflict = apr_pcalloc(result_pool, sizeof(*conflict));
+ conflict->local_abspath = apr_pstrdup(result_pool, local_abspath);
+ conflict->node_kind = svn_node_file;
+ conflict->kind = svn_wc_conflict_kind_text;
+ conflict->action = svn_wc_conflict_action_edit;
+ conflict->reason = svn_wc_conflict_reason_edited;
+ return conflict;
+}
+
+svn_wc_conflict_description2_t *
+svn_wc_conflict_description_create_prop2(const char *local_abspath,
+ svn_node_kind_t node_kind,
+ const char *property_name,
+ apr_pool_t *result_pool)
+{
+ svn_wc_conflict_description2_t *conflict;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath));
+
+ conflict = apr_pcalloc(result_pool, sizeof(*conflict));
+ conflict->local_abspath = apr_pstrdup(result_pool, local_abspath);
+ conflict->node_kind = node_kind;
+ conflict->kind = svn_wc_conflict_kind_property;
+ conflict->property_name = apr_pstrdup(result_pool, property_name);
+ return conflict;
+}
+
+svn_wc_conflict_description2_t *
+svn_wc_conflict_description_create_tree2(
+ const char *local_abspath,
+ svn_node_kind_t node_kind,
+ svn_wc_operation_t operation,
+ const svn_wc_conflict_version_t *src_left_version,
+ const svn_wc_conflict_version_t *src_right_version,
+ apr_pool_t *result_pool)
+{
+ svn_wc_conflict_description2_t *conflict;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath));
+
+ conflict = apr_pcalloc(result_pool, sizeof(*conflict));
+ conflict->local_abspath = apr_pstrdup(result_pool, local_abspath);
+ conflict->node_kind = node_kind;
+ conflict->kind = svn_wc_conflict_kind_tree;
+ conflict->operation = operation;
+ conflict->src_left_version = svn_wc_conflict_version_dup(src_left_version,
+ result_pool);
+ conflict->src_right_version = svn_wc_conflict_version_dup(src_right_version,
+ result_pool);
+ return conflict;
+}
+
+
+svn_wc_conflict_description2_t *
+svn_wc__conflict_description2_dup(const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool)
+{
+ svn_wc_conflict_description2_t *new_conflict;
+
+ new_conflict = apr_pcalloc(pool, sizeof(*new_conflict));
+
+ /* Shallow copy all members. */
+ *new_conflict = *conflict;
+
+ if (conflict->local_abspath)
+ new_conflict->local_abspath = apr_pstrdup(pool, conflict->local_abspath);
+ if (conflict->property_name)
+ new_conflict->property_name = apr_pstrdup(pool, conflict->property_name);
+ if (conflict->mime_type)
+ new_conflict->mime_type = apr_pstrdup(pool, conflict->mime_type);
+ if (conflict->base_abspath)
+ new_conflict->base_abspath = apr_pstrdup(pool, conflict->base_abspath);
+ if (conflict->their_abspath)
+ new_conflict->their_abspath = apr_pstrdup(pool, conflict->their_abspath);
+ if (conflict->my_abspath)
+ new_conflict->my_abspath = apr_pstrdup(pool, conflict->my_abspath);
+ if (conflict->merged_file)
+ new_conflict->merged_file = apr_pstrdup(pool, conflict->merged_file);
+ if (conflict->src_left_version)
+ new_conflict->src_left_version =
+ svn_wc_conflict_version_dup(conflict->src_left_version, pool);
+ if (conflict->src_right_version)
+ new_conflict->src_right_version =
+ svn_wc_conflict_version_dup(conflict->src_right_version, pool);
+
+ return new_conflict;
+}
+
+svn_wc_conflict_version_t *
+svn_wc_conflict_version_create2(const char *repos_url,
+ const char *repos_uuid,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool)
+{
+ svn_wc_conflict_version_t *version;
+
+ version = apr_pcalloc(result_pool, sizeof(*version));
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_uri_is_canonical(repos_url, result_pool)
+ && svn_relpath_is_canonical(repos_relpath)
+ && SVN_IS_VALID_REVNUM(revision)
+ /* ### repos_uuid can be NULL :( */);
+
+ version->repos_url = repos_url;
+ version->peg_rev = revision;
+ version->path_in_repos = repos_relpath;
+ version->node_kind = kind;
+ version->repos_uuid = repos_uuid;
+
+ return version;
+}
+
+
+svn_wc_conflict_version_t *
+svn_wc_conflict_version_dup(const svn_wc_conflict_version_t *version,
+ apr_pool_t *result_pool)
+{
+
+ svn_wc_conflict_version_t *new_version;
+
+ if (version == NULL)
+ return NULL;
+
+ new_version = apr_pcalloc(result_pool, sizeof(*new_version));
+
+ /* Shallow copy all members. */
+ *new_version = *version;
+
+ if (version->repos_url)
+ new_version->repos_url = apr_pstrdup(result_pool, version->repos_url);
+
+ if (version->path_in_repos)
+ new_version->path_in_repos = apr_pstrdup(result_pool,
+ version->path_in_repos);
+
+ if (version->repos_uuid)
+ new_version->repos_uuid = apr_pstrdup(result_pool, version->repos_uuid);
+
+ return new_version;
+}
+
+
+svn_wc_conflict_description_t *
+svn_wc__cd2_to_cd(const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *result_pool)
+{
+ svn_wc_conflict_description_t *new_conflict;
+
+ if (conflict == NULL)
+ return NULL;
+
+ new_conflict = apr_pcalloc(result_pool, sizeof(*new_conflict));
+
+ new_conflict->path = apr_pstrdup(result_pool, conflict->local_abspath);
+ new_conflict->node_kind = conflict->node_kind;
+ new_conflict->kind = conflict->kind;
+ new_conflict->action = conflict->action;
+ new_conflict->reason = conflict->reason;
+ if (conflict->src_left_version)
+ new_conflict->src_left_version =
+ svn_wc_conflict_version_dup(conflict->src_left_version, result_pool);
+ if (conflict->src_right_version)
+ new_conflict->src_right_version =
+ svn_wc_conflict_version_dup(conflict->src_right_version, result_pool);
+
+ switch (conflict->kind)
+ {
+
+ case svn_wc_conflict_kind_property:
+ new_conflict->property_name = apr_pstrdup(result_pool,
+ conflict->property_name);
+ /* Falling through. */
+
+ case svn_wc_conflict_kind_text:
+ new_conflict->is_binary = conflict->is_binary;
+ if (conflict->mime_type)
+ new_conflict->mime_type = apr_pstrdup(result_pool,
+ conflict->mime_type);
+ if (conflict->base_abspath)
+ new_conflict->base_file = apr_pstrdup(result_pool,
+ conflict->base_abspath);
+ if (conflict->their_abspath)
+ new_conflict->their_file = apr_pstrdup(result_pool,
+ conflict->their_abspath);
+ if (conflict->my_abspath)
+ new_conflict->my_file = apr_pstrdup(result_pool,
+ conflict->my_abspath);
+ if (conflict->merged_file)
+ new_conflict->merged_file = apr_pstrdup(result_pool,
+ conflict->merged_file);
+ break;
+
+ case svn_wc_conflict_kind_tree:
+ new_conflict->operation = conflict->operation;
+ break;
+ }
+
+ /* A NULL access baton is allowable by the API. */
+ new_conflict->access = NULL;
+
+ return new_conflict;
+}
+
+
+svn_error_t *
+svn_wc__status2_from_3(svn_wc_status2_t **status,
+ const svn_wc_status3_t *old_status,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_wc_entry_t *entry = NULL;
+
+ if (old_status == NULL)
+ {
+ *status = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ *status = apr_pcalloc(result_pool, sizeof(**status));
+
+ if (old_status->versioned)
+ {
+ svn_error_t *err;
+ err= svn_wc__get_entry(&entry, wc_ctx->db, local_abspath, FALSE,
+ svn_node_unknown, result_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND)
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+ }
+
+ (*status)->entry = entry;
+ (*status)->copied = old_status->copied;
+ (*status)->repos_lock = svn_lock_dup(old_status->repos_lock, result_pool);
+
+ if (old_status->repos_relpath)
+ (*status)->url = svn_path_url_add_component2(old_status->repos_root_url,
+ old_status->repos_relpath,
+ result_pool);
+ (*status)->ood_last_cmt_rev = old_status->ood_changed_rev;
+ (*status)->ood_last_cmt_date = old_status->ood_changed_date;
+ (*status)->ood_kind = old_status->ood_kind;
+ (*status)->ood_last_cmt_author = old_status->ood_changed_author;
+
+ if (old_status->conflicted)
+ {
+ const svn_wc_conflict_description2_t *tree_conflict;
+ SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ (*status)->tree_conflict = svn_wc__cd2_to_cd(tree_conflict, result_pool);
+ }
+
+ (*status)->switched = old_status->switched;
+
+ (*status)->text_status = old_status->node_status;
+ (*status)->prop_status = old_status->prop_status;
+
+ (*status)->repos_text_status = old_status->repos_node_status;
+ (*status)->repos_prop_status = old_status->repos_prop_status;
+
+ /* Some values might be inherited from properties */
+ if (old_status->node_status == svn_wc_status_modified
+ || old_status->node_status == svn_wc_status_conflicted)
+ (*status)->text_status = old_status->text_status;
+
+ /* (Currently a no-op, but just make sure it is ok) */
+ if (old_status->repos_node_status == svn_wc_status_modified
+ || old_status->repos_node_status == svn_wc_status_conflicted)
+ (*status)->repos_text_status = old_status->repos_text_status;
+
+ if (old_status->node_status == svn_wc_status_added)
+ (*status)->prop_status = svn_wc_status_none; /* No separate info */
+
+ /* Find pristine_text_status value */
+ switch (old_status->text_status)
+ {
+ case svn_wc_status_none:
+ case svn_wc_status_normal:
+ case svn_wc_status_modified:
+ (*status)->pristine_text_status = old_status->text_status;
+ break;
+ case svn_wc_status_conflicted:
+ default:
+ /* ### Fetch compare data, or fall back to the documented
+ not retrieved behavior? */
+ (*status)->pristine_text_status = svn_wc_status_none;
+ break;
+ }
+
+ /* Find pristine_prop_status value */
+ switch (old_status->prop_status)
+ {
+ case svn_wc_status_none:
+ case svn_wc_status_normal:
+ case svn_wc_status_modified:
+ if (old_status->node_status != svn_wc_status_added
+ && old_status->node_status != svn_wc_status_deleted
+ && old_status->node_status != svn_wc_status_replaced)
+ {
+ (*status)->pristine_prop_status = old_status->prop_status;
+ }
+ else
+ (*status)->pristine_prop_status = svn_wc_status_none;
+ break;
+ case svn_wc_status_conflicted:
+ default:
+ /* ### Fetch compare data, or fall back to the documented
+ not retrieved behavior? */
+ (*status)->pristine_prop_status = svn_wc_status_none;
+ break;
+ }
+
+ if (old_status->versioned
+ && old_status->conflicted
+ && old_status->node_status != svn_wc_status_obstructed
+ && (old_status->kind == svn_node_file
+ || old_status->node_status != svn_wc_status_missing))
+ {
+ svn_boolean_t text_conflict_p, prop_conflict_p;
+
+ /* The entry says there was a conflict, but the user might have
+ marked it as resolved by deleting the artifact files, so check
+ for that. */
+ SVN_ERR(svn_wc__internal_conflicted_p(&text_conflict_p,
+ &prop_conflict_p,
+ NULL,
+ wc_ctx->db, local_abspath,
+ scratch_pool));
+
+ if (text_conflict_p)
+ (*status)->text_status = svn_wc_status_conflicted;
+
+ if (prop_conflict_p)
+ (*status)->prop_status = svn_wc_status_conflicted;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct svn_wc__shim_fetch_baton_t *sfb = baton;
+ const char *local_abspath = svn_dirent_join(sfb->base_abspath, path,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__db_read_kind(kind, sfb->db, local_abspath,
+ FALSE /* allow_missing */,
+ TRUE /* show_deleted */,
+ FALSE /* show_hidden */,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct svn_wc__shim_fetch_baton_t *sfb = baton;
+ const char *local_abspath = svn_dirent_join(sfb->base_abspath, path,
+ scratch_pool);
+ svn_error_t *err;
+
+ if (sfb->fetch_base)
+ err = svn_wc__db_base_get_props(props, sfb->db, local_abspath, result_pool,
+ scratch_pool);
+ else
+ err = svn_wc__db_read_props(props, sfb->db, local_abspath,
+ result_pool, scratch_pool);
+
+ /* If the path doesn't exist, just return an empty set of props. */
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *props = apr_hash_make(result_pool);
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct svn_wc__shim_fetch_baton_t *sfb = baton;
+ const svn_checksum_t *checksum;
+ svn_error_t *err;
+ const char *local_abspath = svn_dirent_join(sfb->base_abspath, path,
+ scratch_pool);
+
+ err = svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, &checksum,
+ NULL, NULL, NULL, NULL, NULL,
+ sfb->db, local_abspath,
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ if (checksum == NULL)
+ {
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc__db_pristine_get_path(filename, sfb->db, local_abspath,
+ checksum, scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/wc-checks.h b/contrib/subversion/subversion/libsvn_wc/wc-checks.h
new file mode 100644
index 0000000..470460e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc-checks.h
@@ -0,0 +1,55 @@
+/* This file is automatically generated from wc-checks.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_wc/token-map.h.
+ * Do not edit this file -- edit the source and rerun gen-make.py */
+
+#define STMT_VERIFICATION_TRIGGERS 0
+#define STMT_0_INFO {"STMT_VERIFICATION_TRIGGERS", NULL}
+#define STMT_0 \
+ "CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'Updates to REPOSITORY are not allowed.'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_01 BEFORE INSERT ON nodes " \
+ "WHEN NOT ((new.local_relpath = '' AND new.parent_relpath IS NULL) " \
+ " OR (relpath_depth(new.local_relpath) " \
+ " = relpath_depth(new.parent_relpath) + 1)) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 01 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_02 BEFORE INSERT ON nodes " \
+ "WHEN NOT new.op_depth <= relpath_depth(new.local_relpath) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 02 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_03 BEFORE INSERT ON nodes " \
+ "WHEN NOT ( " \
+ " (new.op_depth = relpath_depth(new.local_relpath)) " \
+ " OR " \
+ " (EXISTS (SELECT 1 FROM nodes " \
+ " WHERE wc_id = new.wc_id AND op_depth = new.op_depth " \
+ " AND local_relpath = new.parent_relpath)) " \
+ " ) " \
+ " AND NOT (new.file_external IS NOT NULL AND new.op_depth = 0) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 03 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_04 BEFORE INSERT ON actual_node " \
+ "WHEN NOT (new.local_relpath = '' " \
+ " OR EXISTS (SELECT 1 FROM nodes " \
+ " WHERE wc_id = new.wc_id " \
+ " AND local_relpath = new.parent_relpath)) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 04 failed'); " \
+ "END; " \
+ ""
+
+#define WC_CHECKS_SQL_DECLARE_STATEMENTS(varname) \
+ static const char * const varname[] = { \
+ STMT_0, \
+ NULL \
+ }
+
+#define WC_CHECKS_SQL_DECLARE_STATEMENT_INFO(varname) \
+ static const char * const varname[][2] = { \
+ STMT_0_INFO, \
+ {NULL, NULL} \
+ }
diff --git a/contrib/subversion/subversion/libsvn_wc/wc-checks.sql b/contrib/subversion/subversion/libsvn_wc/wc-checks.sql
new file mode 100644
index 0000000..a677270
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc-checks.sql
@@ -0,0 +1,77 @@
+/* wc-checks.sql -- trigger-based checks for the wc-metadata database.
+ * This is intended for use with SQLite 3
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+-- STMT_VERIFICATION_TRIGGERS
+
+/* ------------------------------------------------------------------------- */
+
+CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository
+BEGIN
+ SELECT RAISE(FAIL, 'Updates to REPOSITORY are not allowed.');
+END;
+
+/* ------------------------------------------------------------------------- */
+
+/* Verify: on every NODES row: parent_relpath is parent of local_relpath */
+CREATE TEMPORARY TRIGGER validation_01 BEFORE INSERT ON nodes
+WHEN NOT ((new.local_relpath = '' AND new.parent_relpath IS NULL)
+ OR (relpath_depth(new.local_relpath)
+ = relpath_depth(new.parent_relpath) + 1))
+BEGIN
+ SELECT RAISE(FAIL, 'WC DB validity check 01 failed');
+END;
+
+/* Verify: on every NODES row: its op-depth <= its own depth */
+CREATE TEMPORARY TRIGGER validation_02 BEFORE INSERT ON nodes
+WHEN NOT new.op_depth <= relpath_depth(new.local_relpath)
+BEGIN
+ SELECT RAISE(FAIL, 'WC DB validity check 02 failed');
+END;
+
+/* Verify: on every NODES row: it is an op-root or it has a parent with the
+ sames op-depth. (Except when the node is a file external) */
+CREATE TEMPORARY TRIGGER validation_03 BEFORE INSERT ON nodes
+WHEN NOT (
+ (new.op_depth = relpath_depth(new.local_relpath))
+ OR
+ (EXISTS (SELECT 1 FROM nodes
+ WHERE wc_id = new.wc_id AND op_depth = new.op_depth
+ AND local_relpath = new.parent_relpath))
+ )
+ AND NOT (new.file_external IS NOT NULL AND new.op_depth = 0)
+BEGIN
+ SELECT RAISE(FAIL, 'WC DB validity check 03 failed');
+END;
+
+/* Verify: on every ACTUAL row (except root): a NODES row exists at its
+ * parent path. */
+CREATE TEMPORARY TRIGGER validation_04 BEFORE INSERT ON actual_node
+WHEN NOT (new.local_relpath = ''
+ OR EXISTS (SELECT 1 FROM nodes
+ WHERE wc_id = new.wc_id
+ AND local_relpath = new.parent_relpath))
+BEGIN
+ SELECT RAISE(FAIL, 'WC DB validity check 04 failed');
+END;
+
diff --git a/contrib/subversion/subversion/libsvn_wc/wc-metadata.h b/contrib/subversion/subversion/libsvn_wc/wc-metadata.h
new file mode 100644
index 0000000..a0d6965
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc-metadata.h
@@ -0,0 +1,516 @@
+/* This file is automatically generated from wc-metadata.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_wc/token-map.h.
+ * Do not edit this file -- edit the source and rerun gen-make.py */
+
+#define STMT_CREATE_SCHEMA 0
+#define STMT_0_INFO {"STMT_CREATE_SCHEMA", NULL}
+#define STMT_0 \
+ "CREATE TABLE REPOSITORY ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " root TEXT UNIQUE NOT NULL, " \
+ " uuid TEXT NOT NULL " \
+ " ); " \
+ "CREATE INDEX I_UUID ON REPOSITORY (uuid); " \
+ "CREATE INDEX I_ROOT ON REPOSITORY (root); " \
+ "CREATE TABLE WCROOT ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " local_abspath TEXT UNIQUE " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (local_abspath); " \
+ "CREATE TABLE PRISTINE ( " \
+ " checksum TEXT NOT NULL PRIMARY KEY, " \
+ " compression INTEGER, " \
+ " size INTEGER NOT NULL, " \
+ " refcount INTEGER NOT NULL, " \
+ " md5_checksum TEXT NOT NULL " \
+ " ); " \
+ "CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \
+ "CREATE TABLE ACTUAL_NODE ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT, " \
+ " tree_conflict_data TEXT, " \
+ " conflict_data BLOB, " \
+ " older_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " left_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " right_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "CREATE TABLE LOCK ( " \
+ " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \
+ " repos_relpath TEXT NOT NULL, " \
+ " lock_token TEXT NOT NULL, " \
+ " lock_owner TEXT, " \
+ " lock_comment TEXT, " \
+ " lock_date INTEGER, " \
+ " PRIMARY KEY (repos_id, repos_relpath) " \
+ " ); " \
+ "CREATE TABLE WORK_QUEUE ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " work BLOB NOT NULL " \
+ " ); " \
+ "CREATE TABLE WC_LOCK ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_dir_relpath TEXT NOT NULL, " \
+ " locked_levels INTEGER NOT NULL DEFAULT -1, " \
+ " PRIMARY KEY (wc_id, local_dir_relpath) " \
+ " ); " \
+ "PRAGMA user_version = " \
+ APR_STRINGIFY(SVN_WC__VERSION) \
+ "; " \
+ ""
+
+#define STMT_CREATE_NODES 1
+#define STMT_1_INFO {"STMT_CREATE_NODES", NULL}
+#define STMT_1 \
+ "CREATE TABLE NODES ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " op_depth INTEGER NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " repos_id INTEGER REFERENCES REPOSITORY (id), " \
+ " repos_path TEXT, " \
+ " revision INTEGER, " \
+ " presence TEXT NOT NULL, " \
+ " moved_here INTEGER, " \
+ " moved_to TEXT, " \
+ " kind TEXT NOT NULL, " \
+ " properties BLOB, " \
+ " depth TEXT, " \
+ " checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " symlink_target TEXT, " \
+ " changed_revision INTEGER, " \
+ " changed_date INTEGER, " \
+ " changed_author TEXT, " \
+ " translated_size INTEGER, " \
+ " last_mod_time INTEGER, " \
+ " dav_cache BLOB, " \
+ " file_external INTEGER, " \
+ " inherited_props BLOB, " \
+ " PRIMARY KEY (wc_id, local_relpath, op_depth) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth); " \
+ "CREATE VIEW NODES_CURRENT AS " \
+ " SELECT * FROM nodes AS n " \
+ " WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2 " \
+ " WHERE n2.wc_id = n.wc_id " \
+ " AND n2.local_relpath = n.local_relpath); " \
+ "CREATE VIEW NODES_BASE AS " \
+ " SELECT * FROM nodes " \
+ " WHERE op_depth = 0; " \
+ ""
+
+#define STMT_CREATE_NODES_TRIGGERS 2
+#define STMT_2_INFO {"STMT_CREATE_NODES_TRIGGERS", NULL}
+#define STMT_2 \
+ "CREATE TRIGGER nodes_insert_trigger " \
+ "AFTER INSERT ON nodes " \
+ "WHEN NEW.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_delete_trigger " \
+ "AFTER DELETE ON nodes " \
+ "WHEN OLD.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_update_checksum_trigger " \
+ "AFTER UPDATE OF checksum ON nodes " \
+ "WHEN NEW.checksum IS NOT OLD.checksum " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ ""
+
+#define STMT_CREATE_EXTERNALS 3
+#define STMT_3_INFO {"STMT_CREATE_EXTERNALS", NULL}
+#define STMT_3 \
+ "CREATE TABLE EXTERNALS ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT NOT NULL, " \
+ " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \
+ " presence TEXT NOT NULL, " \
+ " kind TEXT NOT NULL, " \
+ " def_local_relpath TEXT NOT NULL, " \
+ " def_repos_relpath TEXT NOT NULL, " \
+ " def_operational_revision TEXT, " \
+ " def_revision TEXT, " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ "); " \
+ "CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, " \
+ " def_local_relpath, " \
+ " local_relpath); " \
+ ""
+
+#define STMT_UPGRADE_TO_20 4
+#define STMT_4_INFO {"STMT_UPGRADE_TO_20", NULL}
+#define STMT_4 \
+ "UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = BASE_NODE.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum); " \
+ "UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = WORKING_NODE.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine " \
+ " WHERE md5_checksum = WORKING_NODE.checksum); " \
+ "INSERT INTO NODES ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, " \
+ " repos_id, repos_path, revision, " \
+ " presence, depth, moved_here, moved_to, kind, " \
+ " changed_revision, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external ) " \
+ "SELECT wc_id, local_relpath, 0 , parent_relpath, " \
+ " repos_id, repos_relpath, revnum, " \
+ " presence, depth, NULL , NULL , kind, " \
+ " changed_rev, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external " \
+ "FROM BASE_NODE; " \
+ "INSERT INTO NODES ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, " \
+ " repos_id, repos_path, revision, " \
+ " presence, depth, moved_here, moved_to, kind, " \
+ " changed_revision, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external ) " \
+ "SELECT wc_id, local_relpath, 2 , parent_relpath, " \
+ " copyfrom_repos_id, copyfrom_repos_path, copyfrom_revnum, " \
+ " presence, depth, NULL , NULL , kind, " \
+ " changed_rev, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " NULL , symlink_target, NULL " \
+ "FROM WORKING_NODE; " \
+ "DROP TABLE BASE_NODE; " \
+ "DROP TABLE WORKING_NODE; " \
+ "PRAGMA user_version = 20; " \
+ ""
+
+#define STMT_UPGRADE_TO_21 5
+#define STMT_5_INFO {"STMT_UPGRADE_TO_21", NULL}
+#define STMT_5 \
+ "PRAGMA user_version = 21; " \
+ ""
+
+#define STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT 6
+#define STMT_6_INFO {"STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT", NULL}
+#define STMT_6 \
+ "SELECT wc_id, local_relpath, tree_conflict_data " \
+ "FROM actual_node " \
+ "WHERE tree_conflict_data IS NOT NULL " \
+ ""
+
+#define STMT_UPGRADE_21_ERASE_OLD_CONFLICTS 7
+#define STMT_7_INFO {"STMT_UPGRADE_21_ERASE_OLD_CONFLICTS", NULL}
+#define STMT_7 \
+ "UPDATE actual_node SET tree_conflict_data = NULL " \
+ ""
+
+#define STMT_UPGRADE_TO_22 8
+#define STMT_8_INFO {"STMT_UPGRADE_TO_22", NULL}
+#define STMT_8 \
+ "UPDATE actual_node SET tree_conflict_data = conflict_data; " \
+ "UPDATE actual_node SET conflict_data = NULL; " \
+ "PRAGMA user_version = 22; " \
+ ""
+
+#define STMT_UPGRADE_TO_23 9
+#define STMT_9_INFO {"STMT_UPGRADE_TO_23", NULL}
+#define STMT_9 \
+ "PRAGMA user_version = 23; " \
+ ""
+
+#define STMT_UPGRADE_23_HAS_WORKING_NODES 10
+#define STMT_10_INFO {"STMT_UPGRADE_23_HAS_WORKING_NODES", NULL}
+#define STMT_10 \
+ "SELECT 1 FROM nodes WHERE op_depth > 0 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_UPGRADE_TO_24 11
+#define STMT_11_INFO {"STMT_UPGRADE_TO_24", NULL}
+#define STMT_11 \
+ "UPDATE pristine SET refcount = " \
+ " (SELECT COUNT(*) FROM nodes " \
+ " WHERE checksum = pristine.checksum ); " \
+ "PRAGMA user_version = 24; " \
+ ""
+
+#define STMT_UPGRADE_TO_25 12
+#define STMT_12_INFO {"STMT_UPGRADE_TO_25", NULL}
+#define STMT_12 \
+ "DROP VIEW IF EXISTS NODES_CURRENT; " \
+ "CREATE VIEW NODES_CURRENT AS " \
+ " SELECT * FROM nodes " \
+ " JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes " \
+ " GROUP BY wc_id, local_relpath) AS filter " \
+ " ON nodes.wc_id = filter.wc_id " \
+ " AND nodes.local_relpath = filter.local_relpath " \
+ " AND nodes.op_depth = filter.op_depth; " \
+ "PRAGMA user_version = 25; " \
+ ""
+
+#define STMT_UPGRADE_TO_26 13
+#define STMT_13_INFO {"STMT_UPGRADE_TO_26", NULL}
+#define STMT_13 \
+ "DROP VIEW IF EXISTS NODES_BASE; " \
+ "CREATE VIEW NODES_BASE AS " \
+ " SELECT * FROM nodes " \
+ " WHERE op_depth = 0; " \
+ "PRAGMA user_version = 26; " \
+ ""
+
+#define STMT_UPGRADE_TO_27 14
+#define STMT_14_INFO {"STMT_UPGRADE_TO_27", NULL}
+#define STMT_14 \
+ "PRAGMA user_version = 27; " \
+ ""
+
+#define STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS 15
+#define STMT_15_INFO {"STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS", NULL}
+#define STMT_15 \
+ "SELECT 1 FROM actual_node " \
+ "WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) " \
+ " AND (conflict_new IS NULL) AND (conflict_working IS NULL) " \
+ " AND (tree_conflict_data IS NULL)) " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_UPGRADE_TO_28 16
+#define STMT_16_INFO {"STMT_UPGRADE_TO_28", NULL}
+#define STMT_16 \
+ "UPDATE NODES SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = nodes.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum); " \
+ "PRAGMA user_version = 28; " \
+ ""
+
+#define STMT_UPGRADE_TO_29 17
+#define STMT_17_INFO {"STMT_UPGRADE_TO_29", NULL}
+#define STMT_17 \
+ "DROP TRIGGER IF EXISTS nodes_update_checksum_trigger; " \
+ "DROP TRIGGER IF EXISTS nodes_insert_trigger; " \
+ "DROP TRIGGER IF EXISTS nodes_delete_trigger; " \
+ "CREATE TRIGGER nodes_update_checksum_trigger " \
+ "AFTER UPDATE OF checksum ON nodes " \
+ "WHEN NEW.checksum IS NOT OLD.checksum " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_insert_trigger " \
+ "AFTER INSERT ON nodes " \
+ "WHEN NEW.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_delete_trigger " \
+ "AFTER DELETE ON nodes " \
+ "WHEN OLD.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "PRAGMA user_version = 29; " \
+ ""
+
+#define STMT_UPGRADE_TO_30 18
+#define STMT_18_INFO {"STMT_UPGRADE_TO_30", NULL}
+#define STMT_18 \
+ "CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED " \
+ "ON NODES (wc_id, moved_to, op_depth); " \
+ "CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \
+ "UPDATE nodes SET presence = \"server-excluded\" WHERE presence = \"absent\"; " \
+ "UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL; " \
+ ""
+
+#define STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE 19
+#define STMT_19_INFO {"STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE", NULL}
+#define STMT_19 \
+ "SELECT wc_id, local_relpath, " \
+ " conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data " \
+ "FROM actual_node " \
+ "WHERE conflict_old IS NOT NULL " \
+ " OR conflict_working IS NOT NULL " \
+ " OR conflict_new IS NOT NULL " \
+ " OR prop_reject IS NOT NULL " \
+ " OR tree_conflict_data IS NOT NULL " \
+ "ORDER by wc_id, local_relpath " \
+ ""
+
+#define STMT_UPGRADE_30_SET_CONFLICT 20
+#define STMT_20_INFO {"STMT_UPGRADE_30_SET_CONFLICT", NULL}
+#define STMT_20 \
+ "UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL, " \
+ " conflict_working = NULL, conflict_new = NULL, prop_reject = NULL, " \
+ " tree_conflict_data = NULL " \
+ "WHERE wc_id = ?1 and local_relpath = ?2 " \
+ ""
+
+#define STMT_UPGRADE_TO_31_ALTER_TABLE 21
+#define STMT_21_INFO {"STMT_UPGRADE_TO_31_ALTER_TABLE", NULL}
+#define STMT_21 \
+ "ALTER TABLE NODES ADD COLUMN inherited_props BLOB; " \
+ ""
+
+#define STMT_UPGRADE_TO_31_FINALIZE 22
+#define STMT_22_INFO {"STMT_UPGRADE_TO_31_FINALIZE", NULL}
+#define STMT_22 \
+ "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \
+ "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \
+ "DROP INDEX I_NODES_PARENT; " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "DROP INDEX I_ACTUAL_PARENT; " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "PRAGMA user_version = 31; " \
+ ""
+
+#define STMT_UPGRADE_31_SELECT_WCROOT_NODES 23
+#define STMT_23_INFO {"STMT_UPGRADE_31_SELECT_WCROOT_NODES", NULL}
+#define STMT_23 \
+ "SELECT l.wc_id, l.local_relpath FROM nodes as l " \
+ "LEFT OUTER JOIN nodes as r " \
+ "ON l.wc_id = r.wc_id " \
+ " AND r.local_relpath = l.parent_relpath " \
+ " AND r.op_depth = 0 " \
+ "WHERE l.op_depth = 0 " \
+ " AND l.repos_path != '' " \
+ " AND ((l.repos_id IS NOT r.repos_id) " \
+ " OR (l.repos_path IS NOT (CASE WHEN (r.local_relpath) = '' THEN (CASE WHEN (r.repos_path) = '' THEN (l.local_relpath) WHEN (l.local_relpath) = '' THEN (r.repos_path) ELSE (r.repos_path) || '/' || (l.local_relpath) END) WHEN (r.repos_path) = '' THEN (CASE WHEN (r.local_relpath) = '' THEN (l.local_relpath) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN '' WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+2) END END) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN (r.repos_path) WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN (r.repos_path) || SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1) END END))) " \
+ ""
+
+#define STMT_UPGRADE_TO_32 24
+#define STMT_24_INFO {"STMT_UPGRADE_TO_32", NULL}
+#define STMT_24 \
+ "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \
+ "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \
+ "CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); " \
+ "DROP INDEX I_NODES_PARENT; " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "DROP INDEX I_ACTUAL_PARENT; " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "-- format: YYY " \
+ ""
+
+#define WC_METADATA_SQL_99 \
+ "CREATE TABLE ACTUAL_NODE_BACKUP ( " \
+ " wc_id INTEGER NOT NULL, " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT " \
+ " ); " \
+ "INSERT INTO ACTUAL_NODE_BACKUP SELECT " \
+ " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \
+ " conflict_new, conflict_working, prop_reject, changelist, text_mod " \
+ "FROM ACTUAL_NODE; " \
+ "DROP TABLE ACTUAL_NODE; " \
+ "CREATE TABLE ACTUAL_NODE ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT, " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "INSERT INTO ACTUAL_NODE SELECT " \
+ " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \
+ " conflict_new, conflict_working, prop_reject, changelist, text_mod " \
+ "FROM ACTUAL_NODE_BACKUP; " \
+ "DROP TABLE ACTUAL_NODE_BACKUP; " \
+ ""
+
+#define WC_METADATA_SQL_DECLARE_STATEMENTS(varname) \
+ static const char * const varname[] = { \
+ STMT_0, \
+ STMT_1, \
+ STMT_2, \
+ STMT_3, \
+ STMT_4, \
+ STMT_5, \
+ STMT_6, \
+ STMT_7, \
+ STMT_8, \
+ STMT_9, \
+ STMT_10, \
+ STMT_11, \
+ STMT_12, \
+ STMT_13, \
+ STMT_14, \
+ STMT_15, \
+ STMT_16, \
+ STMT_17, \
+ STMT_18, \
+ STMT_19, \
+ STMT_20, \
+ STMT_21, \
+ STMT_22, \
+ STMT_23, \
+ STMT_24, \
+ NULL \
+ }
+
+#define WC_METADATA_SQL_DECLARE_STATEMENT_INFO(varname) \
+ static const char * const varname[][2] = { \
+ STMT_0_INFO, \
+ STMT_1_INFO, \
+ STMT_2_INFO, \
+ STMT_3_INFO, \
+ STMT_4_INFO, \
+ STMT_5_INFO, \
+ STMT_6_INFO, \
+ STMT_7_INFO, \
+ STMT_8_INFO, \
+ STMT_9_INFO, \
+ STMT_10_INFO, \
+ STMT_11_INFO, \
+ STMT_12_INFO, \
+ STMT_13_INFO, \
+ STMT_14_INFO, \
+ STMT_15_INFO, \
+ STMT_16_INFO, \
+ STMT_17_INFO, \
+ STMT_18_INFO, \
+ STMT_19_INFO, \
+ STMT_20_INFO, \
+ STMT_21_INFO, \
+ STMT_22_INFO, \
+ STMT_23_INFO, \
+ STMT_24_INFO, \
+ {NULL, NULL} \
+ }
diff --git a/contrib/subversion/subversion/libsvn_wc/wc-metadata.sql b/contrib/subversion/subversion/libsvn_wc/wc-metadata.sql
new file mode 100644
index 0000000..d2a6161
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc-metadata.sql
@@ -0,0 +1,951 @@
+/* wc-metadata.sql -- schema used in the wc-metadata SQLite database
+ * This is intended for use with SQLite 3
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/*
+ * the KIND column in these tables has one of the following values
+ * (documented in the corresponding C type #svn_kind_t):
+ * "file"
+ * "dir"
+ * "symlink"
+ * "unknown"
+ *
+ * the PRESENCE column in these tables has one of the following values
+ * (see also the C type #svn_wc__db_status_t):
+ * "normal"
+ * "server-excluded" -- server has declared it excluded (ie. authz failure)
+ * "excluded" -- administratively excluded (ie. sparse WC)
+ * "not-present" -- node not present at this REV
+ * "incomplete" -- state hasn't been filled in
+ * "base-deleted" -- node represents a delete of a BASE node
+ */
+
+/* One big list of statements to create our (current) schema. */
+-- STMT_CREATE_SCHEMA
+
+/* ------------------------------------------------------------------------- */
+
+CREATE TABLE REPOSITORY (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+
+ /* The root URL of the repository. This value is URI-encoded. */
+ root TEXT UNIQUE NOT NULL,
+
+ /* the UUID of the repository */
+ uuid TEXT NOT NULL
+ );
+
+/* Note: a repository (identified by its UUID) may appear at multiple URLs.
+ For example, http://example.com/repos/ and https://example.com/repos/. */
+CREATE INDEX I_UUID ON REPOSITORY (uuid);
+CREATE INDEX I_ROOT ON REPOSITORY (root);
+
+
+/* ------------------------------------------------------------------------- */
+
+CREATE TABLE WCROOT (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+
+ /* absolute path in the local filesystem. NULL if storing metadata in
+ the wcroot itself. */
+ local_abspath TEXT UNIQUE
+ );
+
+CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (local_abspath);
+
+
+/* ------------------------------------------------------------------------- */
+
+/* The PRISTINE table keeps track of pristine texts. Each row describes a
+ single pristine text. The text itself is stored in a file whose name is
+ derived from the 'checksum' column. Each pristine text is referenced by
+ any number of rows in the NODES and ACTUAL_NODE tables.
+
+ In future, the pristine text file may be compressed.
+ */
+CREATE TABLE PRISTINE (
+ /* The SHA-1 checksum of the pristine text. This is a unique key. The
+ SHA-1 checksum of a pristine text is assumed to be unique among all
+ pristine texts referenced from this database. */
+ checksum TEXT NOT NULL PRIMARY KEY,
+
+ /* Enumerated values specifying type of compression. The only value
+ supported so far is NULL, meaning that no compression has been applied
+ and the pristine text is stored verbatim in the file. */
+ compression INTEGER,
+
+ /* The size in bytes of the file in which the pristine text is stored.
+ Used to verify the pristine file is "proper". */
+ size INTEGER NOT NULL,
+
+ /* The number of rows in the NODES table that have a 'checksum' column
+ value that refers to this row. (References in other places, such as
+ in the ACTUAL_NODE table, are not counted.) */
+ refcount INTEGER NOT NULL,
+
+ /* Alternative MD5 checksum used for communicating with older
+ repositories. Not strictly guaranteed to be unique among table rows. */
+ md5_checksum TEXT NOT NULL
+ );
+
+CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum);
+
+/* ------------------------------------------------------------------------- */
+
+/* The ACTUAL_NODE table describes text changes and property changes
+ on each node in the WC, relative to the NODES table row for the
+ same path. (A NODES row must exist if this node exists, but an
+ ACTUAL_NODE row can exist on its own if it is just recording info
+ on a non-present node - a tree conflict or a changelist, for
+ example.)
+
+ The ACTUAL_NODE table row for a given path exists if the node at that
+ path is known to have text or property changes relative to its
+ NODES row. ("Is known" because a text change on disk may not yet
+ have been discovered and recorded here.)
+
+ The ACTUAL_NODE table row for a given path may also exist in other cases,
+ including if the "changelist" or any of the conflict columns have a
+ non-null value.
+ */
+CREATE TABLE ACTUAL_NODE (
+ /* specifies the location of this node in the local filesystem */
+ wc_id INTEGER NOT NULL REFERENCES WCROOT (id),
+ local_relpath TEXT NOT NULL,
+
+ /* parent's local_relpath for aggregating children of a given parent.
+ this will be "" if the parent is the wcroot. NULL if this is the
+ wcroot node. */
+ parent_relpath TEXT,
+
+ /* serialized skel of this node's properties. NULL implies no change to
+ the properties, relative to WORKING/BASE as appropriate. */
+ properties BLOB,
+
+ /* relpaths of the conflict files. */
+ /* ### These columns will eventually be merged into conflict_data below. */
+ conflict_old TEXT,
+ conflict_new TEXT,
+ conflict_working TEXT,
+ prop_reject TEXT,
+
+ /* if not NULL, this node is part of a changelist. */
+ changelist TEXT,
+
+ /* ### need to determine values. "unknown" (no info), "admin" (they
+ ### used something like 'svn edit'), "noticed" (saw a mod while
+ ### scanning the filesystem). */
+ text_mod TEXT,
+
+ /* if a directory, serialized data for all of tree conflicts therein.
+ ### This column will eventually be merged into the conflict_data column,
+ ### but within the ACTUAL node of the tree conflict victim itself, rather
+ ### than the node of the tree conflict victim's parent directory. */
+ tree_conflict_data TEXT,
+
+ /* A skel containing the conflict details. */
+ conflict_data BLOB,
+
+ /* Three columns containing the checksums of older, left and right conflict
+ texts. Stored in a column to allow storing them in the pristine store */
+ /* stsp: This is meant for text conflicts, right? What about property
+ conflicts? Why do we need these in a column to refer to the
+ pristine store? Can't we just parse the checksums from
+ conflict_data as well?
+ rhuijben: Because that won't allow triggers to handle refcounts.
+ We would have to scan all conflict skels before cleaning up the
+ a single file from the pristine stor */
+ older_checksum TEXT REFERENCES PRISTINE (checksum),
+ left_checksum TEXT REFERENCES PRISTINE (checksum),
+ right_checksum TEXT REFERENCES PRISTINE (checksum),
+
+ PRIMARY KEY (wc_id, local_relpath)
+ );
+
+CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath,
+ local_relpath);
+
+
+/* ------------------------------------------------------------------------- */
+
+/* This table is a cache of information about repository locks. */
+CREATE TABLE LOCK (
+ /* what repository location is locked */
+ repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id),
+ repos_relpath TEXT NOT NULL,
+
+ /* Information about the lock. Note: these values are just caches from
+ the server, and are not authoritative. */
+ lock_token TEXT NOT NULL,
+ /* ### make the following fields NOT NULL ? */
+ lock_owner TEXT,
+ lock_comment TEXT,
+ lock_date INTEGER, /* an APR date/time (usec since 1970) */
+
+ PRIMARY KEY (repos_id, repos_relpath)
+ );
+
+
+/* ------------------------------------------------------------------------- */
+
+CREATE TABLE WORK_QUEUE (
+ /* Work items are identified by this value. */
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+
+ /* A serialized skel specifying the work item. */
+ work BLOB NOT NULL
+ );
+
+
+/* ------------------------------------------------------------------------- */
+
+CREATE TABLE WC_LOCK (
+ /* specifies the location of this node in the local filesystem */
+ wc_id INTEGER NOT NULL REFERENCES WCROOT (id),
+ local_dir_relpath TEXT NOT NULL,
+
+ locked_levels INTEGER NOT NULL DEFAULT -1,
+
+ PRIMARY KEY (wc_id, local_dir_relpath)
+ );
+
+
+PRAGMA user_version =
+-- define: SVN_WC__VERSION
+;
+
+
+/* ------------------------------------------------------------------------- */
+
+/* The NODES table describes the way WORKING nodes are layered on top of
+ BASE nodes and on top of other WORKING nodes, due to nested tree structure
+ changes. The layers are modelled using the "op_depth" column.
+
+ An 'operation depth' refers to the number of directory levels down from
+ the WC root at which a tree-change operation (delete, add?, copy, move)
+ was performed. A row's 'op_depth' does NOT refer to the depth of its own
+ 'local_relpath', but rather to the depth of the nearest tree change that
+ affects that node.
+
+ The row with op_depth=0 for any given local relpath represents the "base"
+ node that is created and updated by checkout, update, switch and commit
+ post-processing. The row with the highest op_depth for a particular
+ local_relpath represents the working version. Any rows with intermediate
+ op_depth values are not normally visible to the user but may become
+ visible after reverting local changes.
+
+ This table contains full node descriptions for nodes in either the BASE
+ or WORKING trees as described in notes/wc-ng/design. Fields relate
+ both to BASE and WORKING trees, unless documented otherwise.
+
+ For illustration, with a scenario like this:
+
+ # (0)
+ svn rm foo
+ svn cp ^/moo foo # (1)
+ svn rm foo/bar
+ touch foo/bar
+ svn add foo/bar # (2)
+
+ , these are the NODES table rows for the path foo/bar:
+
+ (0) "BASE" ---> NODES (op_depth == 0)
+ (1) NODES (op_depth == 1)
+ (2) NODES (op_depth == 2)
+
+ 0 is the original data for foo/bar before 'svn rm foo' (if it existed).
+ 1 is the data for foo/bar copied in from ^/moo/bar.
+ 2 is the to-be-committed data for foo/bar, created by 'svn add foo/bar'.
+
+ An 'svn revert foo/bar' would remove the NODES of (2).
+
+ */
+-- STMT_CREATE_NODES
+CREATE TABLE NODES (
+ /* Working copy location related fields */
+
+ wc_id INTEGER NOT NULL REFERENCES WCROOT (id),
+ local_relpath TEXT NOT NULL,
+
+ /* Contains the depth (= number of path segments) of the operation
+ modifying the working copy tree structure. All nodes below the root
+ of the operation (aka operation root, aka oproot) affected by the
+ operation will be assigned the same op_depth.
+
+ op_depth == 0 designates the initial checkout; the BASE tree.
+
+ */
+ op_depth INTEGER NOT NULL,
+
+ /* parent's local_relpath for aggregating children of a given parent.
+ this will be "" if the parent is the wcroot. Since a wcroot will
+ never have a WORKING node the parent_relpath will never be null,
+ except when op_depth == 0 and the node is a wcroot. */
+ parent_relpath TEXT,
+
+
+ /* Repository location fields */
+
+ /* When op_depth == 0, these fields refer to the repository location of the
+ BASE node, the location of the initial checkout.
+
+ When op_depth != 0, they indicate where this node was copied/moved from.
+ In this case, the fields are set for the root of the operation and for all
+ children. */
+ repos_id INTEGER REFERENCES REPOSITORY (id),
+ repos_path TEXT,
+ revision INTEGER,
+
+
+ /* WC state fields */
+
+ /* The tree state of the node.
+
+ In case 'op_depth' is equal to 0, this node is part of the 'BASE'
+ tree. The 'BASE' represents pristine nodes that are in the
+ repository; it is obtained and modified by commands such as
+ checkout/update/switch.
+
+ In case 'op_depth' is greater than 0, this node is part of a
+ layer of working nodes. The 'WORKING' tree is obtained and
+ modified by commands like delete/copy/revert.
+
+ The 'BASE' and 'WORKING' trees use the same literal values for
+ the 'presence' but the meaning of each value can vary depending
+ on the tree.
+
+ normal: in the 'BASE' tree this is an ordinary node for which we
+ have full information. In the 'WORKING' tree it's an added or
+ copied node for which we have full information.
+
+ not-present: in the 'BASE' tree this is a node that is implied to
+ exist by the parent node, but is not present in the working
+ copy. Typically obtained by delete/commit, or by update to
+ revision in which the node does not exist. In the 'WORKING'
+ tree this is a copy of a 'not-present' node from the 'BASE'
+ tree, and it will be deleted on commit. Such a node cannot be
+ copied directly, but can be copied as a descendant.
+
+ incomplete: in the 'BASE' tree this is an ordinary node for which
+ we do not have full information. Only the name is guaranteed;
+ we may not have all its children, we may not have its checksum,
+ etc. In the 'WORKING' tree this is a copied node for which we
+ do not have the full information. This state is generally
+ obtained when an operation was interrupted.
+
+ base-deleted: not valid in 'BASE' tree. In the 'WORKING' tree
+ this represents a node that is deleted from the tree below the
+ current 'op_depth'. This state is badly named, it should be
+ something like 'deleted'.
+
+ server-excluded: in the 'BASE' tree this is a node that is excluded by
+ authz. The name of the node is known from the parent, but no
+ other information is available. Not valid in the 'WORKING'
+ tree as there is no way to commit such a node.
+
+ excluded: in the 'BASE' tree this node is administratively
+ excluded by the user (sparse WC). In the 'WORKING' tree this
+ is a copy of an excluded node from the 'BASE' tree. Such a
+ node cannot be copied directly but can be copied as a
+ descendant. */
+
+ presence TEXT NOT NULL,
+
+ /* ### JF: For an old-style move, "copyfrom" info stores its source, but a
+ new WC-NG "move" is intended to be a "true rename" so its copyfrom
+ revision is implicit, being in effect (new head - 1) at commit time.
+ For a (new) move, we need to store or deduce the copyfrom local-relpath;
+ perhaps add a column called "moved_from". */
+
+ /* Boolean value, specifying if this node was moved here (rather than just
+ copied). This is set on all the nodes in the moved tree. The source of
+ the move is implied by a different node with a moved_to column pointing
+ at the root node of the moved tree. */
+ moved_here INTEGER,
+
+ /* If the underlying node was moved away (rather than just deleted), this
+ specifies the local_relpath of where the node was moved to.
+ This is set only on the root of a move, and is NULL for all children.
+
+ The op-depth of the moved-to node is not recorded. A moved_to path
+ always points at a node within the highest op-depth layer at the
+ destination. This invariant must be maintained by operations which
+ change existing move information. */
+ moved_to TEXT,
+
+
+ /* Content fields */
+
+ /* the kind of the new node. may be "unknown" if the node is not present. */
+ kind TEXT NOT NULL,
+
+ /* serialized skel of this node's properties (when presence is 'normal' or
+ 'incomplete'); an empty skel or NULL indicates no properties. NULL if
+ we have no information about the properties (any other presence).
+ TODO: Choose & require a single representation for 'no properties'.
+ */
+ properties BLOB,
+
+ /* NULL depth means "default" (typically svn_depth_infinity) */
+ /* ### depth on WORKING? seems this is a BASE-only concept. how do
+ ### you do "files" on an added-directory? can't really ignore
+ ### the subdirs! */
+ /* ### maybe a WC-to-WC copy can retain a depth? */
+ depth TEXT,
+
+ /* The SHA-1 checksum of the pristine text, if this node is a file and was
+ moved here or copied here, else NULL. */
+ checksum TEXT REFERENCES PRISTINE (checksum),
+
+ /* for kind==symlink, this specifies the target. */
+ symlink_target TEXT,
+
+
+ /* Last-Change fields */
+
+ /* If this node was moved here or copied here, then the following fields may
+ have information about their source node. changed_rev must be not-null
+ if this node has presence=="normal". changed_date and changed_author may
+ be null if the corresponding revprops are missing.
+
+ For an added or not-present node, these are null. */
+ changed_revision INTEGER,
+ changed_date INTEGER, /* an APR date/time (usec since 1970) */
+ changed_author TEXT,
+
+
+ /* Various cache fields */
+
+ /* The size in bytes of the working file when it had no local text
+ modifications. This means the size of the text when translated from
+ repository-normal format to working copy format with EOL style
+ translated and keywords expanded according to the properties in the
+ "properties" column of this row.
+
+ NULL if this node is not a file or if the size has not (yet) been
+ computed. */
+ translated_size INTEGER,
+
+ /* The mod-time of the working file when it was last determined to be
+ logically unmodified relative to its base, taking account of keywords
+ and EOL style. This value is used in the change detection heuristic
+ used by the status command.
+
+ NULL if this node is not a file or if this info has not yet been
+ determined.
+ */
+ last_mod_time INTEGER, /* an APR date/time (usec since 1970) */
+
+ /* serialized skel of this node's dav-cache. could be NULL if the
+ node does not have any dav-cache. */
+ dav_cache BLOB,
+
+ /* Is there a file external in this location. NULL if there
+ is no file external, otherwise '1' */
+ /* ### Originally we had a wc-1.0 like skel in this place, so we
+ ### check for NULL.
+ ### In Subversion 1.7 we defined this column as TEXT, but Sqlite
+ ### only uses this information for deciding how to optimize
+ ### anyway. */
+ file_external INTEGER,
+
+ /* serialized skel of this node's inherited properties. NULL if this
+ is not the BASE of a WC root node. */
+ inherited_props BLOB,
+
+ PRIMARY KEY (wc_id, local_relpath, op_depth)
+
+ );
+
+CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath,
+ local_relpath, op_depth);
+/* I_NODES_MOVED is introduced in format 30 */
+CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth);
+
+/* Many queries have to filter the nodes table to pick only that version
+ of each node with the highest (most "current") op_depth. This view
+ does the heavy lifting for such queries.
+
+ Note that this view includes a row for each and every path that is known
+ in the WC, including, for example, paths that were children of a base- or
+ lower-op-depth directory that has been replaced by something else in the
+ current view.
+ */
+CREATE VIEW NODES_CURRENT AS
+ SELECT * FROM nodes AS n
+ WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2
+ WHERE n2.wc_id = n.wc_id
+ AND n2.local_relpath = n.local_relpath);
+
+/* Many queries have to filter the nodes table to pick only that version
+ of each node with the BASE ("as checked out") op_depth. This view
+ does the heavy lifting for such queries. */
+CREATE VIEW NODES_BASE AS
+ SELECT * FROM nodes
+ WHERE op_depth = 0;
+
+-- STMT_CREATE_NODES_TRIGGERS
+
+CREATE TRIGGER nodes_insert_trigger
+AFTER INSERT ON nodes
+WHEN NEW.checksum IS NOT NULL
+BEGIN
+ UPDATE pristine SET refcount = refcount + 1
+ WHERE checksum = NEW.checksum;
+END;
+
+CREATE TRIGGER nodes_delete_trigger
+AFTER DELETE ON nodes
+WHEN OLD.checksum IS NOT NULL
+BEGIN
+ UPDATE pristine SET refcount = refcount - 1
+ WHERE checksum = OLD.checksum;
+END;
+
+CREATE TRIGGER nodes_update_checksum_trigger
+AFTER UPDATE OF checksum ON nodes
+WHEN NEW.checksum IS NOT OLD.checksum
+ /* AND (NEW.checksum IS NOT NULL OR OLD.checksum IS NOT NULL) */
+BEGIN
+ UPDATE pristine SET refcount = refcount + 1
+ WHERE checksum = NEW.checksum;
+ UPDATE pristine SET refcount = refcount - 1
+ WHERE checksum = OLD.checksum;
+END;
+
+-- STMT_CREATE_EXTERNALS
+
+CREATE TABLE EXTERNALS (
+ /* Working copy location related fields (like NODES)*/
+
+ wc_id INTEGER NOT NULL REFERENCES WCROOT (id),
+ local_relpath TEXT NOT NULL,
+
+ /* The working copy root can't be recorded as an external in itself
+ so this will never be NULL. ### ATM only inserted, never queried */
+ parent_relpath TEXT NOT NULL,
+
+ /* Repository location fields */
+ repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id),
+
+ /* Either MAP_NORMAL or MAP_EXCLUDED */
+ presence TEXT NOT NULL,
+
+ /* the kind of the external. */
+ kind TEXT NOT NULL,
+
+ /* The local relpath of the directory NODE defining this external
+ (Defaults to the parent directory of the file external after upgrade) */
+ def_local_relpath TEXT NOT NULL,
+
+ /* The url of the external as used in the definition */
+ def_repos_relpath TEXT NOT NULL,
+
+ /* The operational (peg) and node revision if this is a revision fixed
+ external; otherwise NULL. (Usually these will both have the same value) */
+ def_operational_revision TEXT,
+ def_revision TEXT,
+
+ PRIMARY KEY (wc_id, local_relpath)
+);
+
+CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id,
+ def_local_relpath,
+ local_relpath);
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 20 introduces NODES and removes BASE_NODE and WORKING_NODE */
+
+-- STMT_UPGRADE_TO_20
+
+UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine
+ WHERE md5_checksum = BASE_NODE.checksum)
+WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum);
+
+UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine
+ WHERE md5_checksum = WORKING_NODE.checksum)
+WHERE EXISTS (SELECT 1 FROM pristine
+ WHERE md5_checksum = WORKING_NODE.checksum);
+
+INSERT INTO NODES (
+ wc_id, local_relpath, op_depth, parent_relpath,
+ repos_id, repos_path, revision,
+ presence, depth, moved_here, moved_to, kind,
+ changed_revision, changed_date, changed_author,
+ checksum, properties, translated_size, last_mod_time,
+ dav_cache, symlink_target, file_external )
+SELECT wc_id, local_relpath, 0 /*op_depth*/, parent_relpath,
+ repos_id, repos_relpath, revnum,
+ presence, depth, NULL /*moved_here*/, NULL /*moved_to*/, kind,
+ changed_rev, changed_date, changed_author,
+ checksum, properties, translated_size, last_mod_time,
+ dav_cache, symlink_target, file_external
+FROM BASE_NODE;
+INSERT INTO NODES (
+ wc_id, local_relpath, op_depth, parent_relpath,
+ repos_id, repos_path, revision,
+ presence, depth, moved_here, moved_to, kind,
+ changed_revision, changed_date, changed_author,
+ checksum, properties, translated_size, last_mod_time,
+ dav_cache, symlink_target, file_external )
+SELECT wc_id, local_relpath, 2 /*op_depth*/, parent_relpath,
+ copyfrom_repos_id, copyfrom_repos_path, copyfrom_revnum,
+ presence, depth, NULL /*moved_here*/, NULL /*moved_to*/, kind,
+ changed_rev, changed_date, changed_author,
+ checksum, properties, translated_size, last_mod_time,
+ NULL /*dav_cache*/, symlink_target, NULL /*file_external*/
+FROM WORKING_NODE;
+
+DROP TABLE BASE_NODE;
+DROP TABLE WORKING_NODE;
+
+PRAGMA user_version = 20;
+
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 21 involves no schema changes, it moves the tree conflict victim
+ information to victime nodes, rather than parents. */
+
+-- STMT_UPGRADE_TO_21
+PRAGMA user_version = 21;
+
+/* For format 21 bump code */
+-- STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT
+SELECT wc_id, local_relpath, tree_conflict_data
+FROM actual_node
+WHERE tree_conflict_data IS NOT NULL
+
+/* For format 21 bump code */
+-- STMT_UPGRADE_21_ERASE_OLD_CONFLICTS
+UPDATE actual_node SET tree_conflict_data = NULL
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 22 simply moves the tree conflict information from the conflict_data
+ column to the tree_conflict_data column. */
+
+-- STMT_UPGRADE_TO_22
+UPDATE actual_node SET tree_conflict_data = conflict_data;
+UPDATE actual_node SET conflict_data = NULL;
+
+PRAGMA user_version = 22;
+
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 23 involves no schema changes, it introduces multi-layer
+ op-depth processing for NODES. */
+
+-- STMT_UPGRADE_TO_23
+PRAGMA user_version = 23;
+
+-- STMT_UPGRADE_23_HAS_WORKING_NODES
+SELECT 1 FROM nodes WHERE op_depth > 0
+LIMIT 1
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 24 involves no schema changes; it starts using the pristine
+ table's refcount column correctly. */
+
+-- STMT_UPGRADE_TO_24
+UPDATE pristine SET refcount =
+ (SELECT COUNT(*) FROM nodes
+ WHERE checksum = pristine.checksum /*OR checksum = pristine.md5_checksum*/);
+
+PRAGMA user_version = 24;
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 25 introduces the NODES_CURRENT view. */
+
+-- STMT_UPGRADE_TO_25
+DROP VIEW IF EXISTS NODES_CURRENT;
+CREATE VIEW NODES_CURRENT AS
+ SELECT * FROM nodes
+ JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes
+ GROUP BY wc_id, local_relpath) AS filter
+ ON nodes.wc_id = filter.wc_id
+ AND nodes.local_relpath = filter.local_relpath
+ AND nodes.op_depth = filter.op_depth;
+
+PRAGMA user_version = 25;
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 26 introduces the NODES_BASE view. */
+
+-- STMT_UPGRADE_TO_26
+DROP VIEW IF EXISTS NODES_BASE;
+CREATE VIEW NODES_BASE AS
+ SELECT * FROM nodes
+ WHERE op_depth = 0;
+
+PRAGMA user_version = 26;
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 27 involves no schema changes, it introduces stores
+ conflict files as relpaths rather than names in ACTUAL_NODE. */
+
+-- STMT_UPGRADE_TO_27
+PRAGMA user_version = 27;
+
+/* For format 27 bump code */
+-- STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS
+SELECT 1 FROM actual_node
+WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL)
+ AND (conflict_new IS NULL) AND (conflict_working IS NULL)
+ AND (tree_conflict_data IS NULL))
+LIMIT 1
+
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 28 involves no schema changes, it only converts MD5 pristine
+ references to SHA1. */
+
+-- STMT_UPGRADE_TO_28
+
+UPDATE NODES SET checksum = (SELECT checksum FROM pristine
+ WHERE md5_checksum = nodes.checksum)
+WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum);
+
+PRAGMA user_version = 28;
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 29 introduces the EXTERNALS table (See STMT_CREATE_TRIGGERS) and
+ optimizes a few trigger definitions. ... */
+
+-- STMT_UPGRADE_TO_29
+
+DROP TRIGGER IF EXISTS nodes_update_checksum_trigger;
+DROP TRIGGER IF EXISTS nodes_insert_trigger;
+DROP TRIGGER IF EXISTS nodes_delete_trigger;
+
+CREATE TRIGGER nodes_update_checksum_trigger
+AFTER UPDATE OF checksum ON nodes
+WHEN NEW.checksum IS NOT OLD.checksum
+ /* AND (NEW.checksum IS NOT NULL OR OLD.checksum IS NOT NULL) */
+BEGIN
+ UPDATE pristine SET refcount = refcount + 1
+ WHERE checksum = NEW.checksum;
+ UPDATE pristine SET refcount = refcount - 1
+ WHERE checksum = OLD.checksum;
+END;
+
+CREATE TRIGGER nodes_insert_trigger
+AFTER INSERT ON nodes
+WHEN NEW.checksum IS NOT NULL
+BEGIN
+ UPDATE pristine SET refcount = refcount + 1
+ WHERE checksum = NEW.checksum;
+END;
+
+CREATE TRIGGER nodes_delete_trigger
+AFTER DELETE ON nodes
+WHEN OLD.checksum IS NOT NULL
+BEGIN
+ UPDATE pristine SET refcount = refcount - 1
+ WHERE checksum = OLD.checksum;
+END;
+
+PRAGMA user_version = 29;
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 30 creates a new NODES index for move information, and a new
+ PRISTINE index for the md5_checksum column. It also activates use of
+ skel-based conflict storage -- see notes/wc-ng/conflict-storage-2.0.
+ It also renames the "absent" presence to "server-excluded". */
+-- STMT_UPGRADE_TO_30
+CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED
+ON NODES (wc_id, moved_to, op_depth);
+
+CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum);
+
+UPDATE nodes SET presence = "server-excluded" WHERE presence = "absent";
+
+/* Just to be sure clear out file external skels from pre 1.7.0 development
+ working copies that were never updated by 1.7.0+ style clients */
+UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL;
+
+-- STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE
+SELECT wc_id, local_relpath,
+ conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data
+FROM actual_node
+WHERE conflict_old IS NOT NULL
+ OR conflict_working IS NOT NULL
+ OR conflict_new IS NOT NULL
+ OR prop_reject IS NOT NULL
+ OR tree_conflict_data IS NOT NULL
+ORDER by wc_id, local_relpath
+
+-- STMT_UPGRADE_30_SET_CONFLICT
+UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL,
+ conflict_working = NULL, conflict_new = NULL, prop_reject = NULL,
+ tree_conflict_data = NULL
+WHERE wc_id = ?1 and local_relpath = ?2
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 31 adds the inherited_props column to the NODES table. C code then
+ initializes the update/switch roots to make sure future updates fetch the
+ inherited properties */
+-- STMT_UPGRADE_TO_31_ALTER_TABLE
+ALTER TABLE NODES ADD COLUMN inherited_props BLOB;
+-- STMT_UPGRADE_TO_31_FINALIZE
+DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST;
+DROP INDEX IF EXISTS I_EXTERNALS_PARENT;
+
+DROP INDEX I_NODES_PARENT;
+CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath,
+ local_relpath, op_depth);
+
+DROP INDEX I_ACTUAL_PARENT;
+CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath,
+ local_relpath);
+
+PRAGMA user_version = 31;
+
+-- STMT_UPGRADE_31_SELECT_WCROOT_NODES
+/* Select all base nodes which are the root of a WC, including
+ switched subtrees, but excluding those which map to the root
+ of the repos.
+
+ ### IPROPS: Is this query horribly inefficient? Quite likely,
+ ### but it only runs during an upgrade, so do we care? */
+SELECT l.wc_id, l.local_relpath FROM nodes as l
+LEFT OUTER JOIN nodes as r
+ON l.wc_id = r.wc_id
+ AND r.local_relpath = l.parent_relpath
+ AND r.op_depth = 0
+WHERE l.op_depth = 0
+ AND l.repos_path != ''
+ AND ((l.repos_id IS NOT r.repos_id)
+ OR (l.repos_path IS NOT RELPATH_SKIP_JOIN(r.local_relpath, r.repos_path, l.local_relpath)))
+
+
+/* ------------------------------------------------------------------------- */
+/* Format 32 .... */
+-- STMT_UPGRADE_TO_32
+
+/* Drop old index. ### Remove this part from the upgrade to 31 once bumped */
+DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST;
+DROP INDEX IF EXISTS I_EXTERNALS_PARENT;
+CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath);
+
+DROP INDEX I_NODES_PARENT;
+CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath,
+ local_relpath, op_depth);
+
+DROP INDEX I_ACTUAL_PARENT;
+CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath,
+ local_relpath);
+
+/* ------------------------------------------------------------------------- */
+
+/* Format YYY introduces new handling for conflict information. */
+-- format: YYY
+
+
+/* ------------------------------------------------------------------------- */
+
+/* Format 99 drops all columns not needed due to previous format upgrades.
+ Before we release 1.7, these statements will be pulled into a format bump
+ and all the tables will be cleaned up. We don't know what that format
+ number will be, however, so we're just marking it as 99 for now. */
+-- format: 99
+
+/* TODO: Un-confuse *_revision column names in the EXTERNALS table to
+ "-r<operative> foo@<peg>", as suggested by the patch attached to
+ http://svn.haxx.se/dev/archive-2011-09/0478.shtml */
+/* TODO: Remove column parent_relpath from EXTERNALS. We're not using it and
+ never will. It's not interesting like in the NODES table: the external's
+ parent path may be *anything*: unversioned, "behind" a another WC... */
+
+/* Now "drop" the tree_conflict_data column from actual_node. */
+CREATE TABLE ACTUAL_NODE_BACKUP (
+ wc_id INTEGER NOT NULL,
+ local_relpath TEXT NOT NULL,
+ parent_relpath TEXT,
+ properties BLOB,
+ conflict_old TEXT,
+ conflict_new TEXT,
+ conflict_working TEXT,
+ prop_reject TEXT,
+ changelist TEXT,
+ text_mod TEXT
+ );
+
+INSERT INTO ACTUAL_NODE_BACKUP SELECT
+ wc_id, local_relpath, parent_relpath, properties, conflict_old,
+ conflict_new, conflict_working, prop_reject, changelist, text_mod
+FROM ACTUAL_NODE;
+
+DROP TABLE ACTUAL_NODE;
+
+CREATE TABLE ACTUAL_NODE (
+ wc_id INTEGER NOT NULL REFERENCES WCROOT (id),
+ local_relpath TEXT NOT NULL,
+ parent_relpath TEXT,
+ properties BLOB,
+ conflict_old TEXT,
+ conflict_new TEXT,
+ conflict_working TEXT,
+ prop_reject TEXT,
+ changelist TEXT,
+ text_mod TEXT,
+
+ PRIMARY KEY (wc_id, local_relpath)
+ );
+
+CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath,
+ local_relpath);
+
+INSERT INTO ACTUAL_NODE SELECT
+ wc_id, local_relpath, parent_relpath, properties, conflict_old,
+ conflict_new, conflict_working, prop_reject, changelist, text_mod
+FROM ACTUAL_NODE_BACKUP;
+
+DROP TABLE ACTUAL_NODE_BACKUP;
+
+/* Note: Other differences between the schemas of an upgraded and a
+ * fresh WC.
+ *
+ * While format 22 was current, "NOT NULL" was added to the
+ * columns PRISTINE.size and PRISTINE.md5_checksum. The format was not
+ * bumped because it is a forward- and backward-compatible change.
+ *
+ * While format 23 was current, "REFERENCES PRISTINE" was added to the
+ * columns ACTUAL_NODE.older_checksum, ACTUAL_NODE.left_checksum,
+ * ACTUAL_NODE.right_checksum, NODES.checksum.
+ *
+ * The "NODES_BASE" view was originally implemented with a more complex (but
+ * functionally equivalent) statement using a 'JOIN'. WCs that were created
+ * at or upgraded to format 26 before it was changed will still have the old
+ * version.
+ */
+
diff --git a/contrib/subversion/subversion/libsvn_wc/wc-queries.h b/contrib/subversion/subversion/libsvn_wc/wc-queries.h
new file mode 100644
index 0000000..19c709e
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc-queries.h
@@ -0,0 +1,3100 @@
+/* This file is automatically generated from wc-queries.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_wc/token-map.h.
+ * Do not edit this file -- edit the source and rerun gen-make.py */
+
+#define STMT_SELECT_NODE_INFO 0
+#define STMT_0_INFO {"STMT_SELECT_NODE_INFO", NULL}
+#define STMT_0 \
+ "SELECT op_depth, repos_id, repos_path, presence, kind, revision, checksum, " \
+ " translated_size, changed_revision, changed_date, changed_author, depth, " \
+ " symlink_target, last_mod_time, properties, moved_here, inherited_props, " \
+ " moved_to " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ "ORDER BY op_depth DESC " \
+ ""
+
+#define STMT_SELECT_NODE_INFO_WITH_LOCK 1
+#define STMT_1_INFO {"STMT_SELECT_NODE_INFO_WITH_LOCK", NULL}
+#define STMT_1 \
+ "SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, " \
+ " checksum, translated_size, changed_revision, changed_date, changed_author, " \
+ " depth, symlink_target, last_mod_time, properties, moved_here, " \
+ " inherited_props, " \
+ " lock_token, lock_owner, lock_comment, lock_date " \
+ "FROM nodes " \
+ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \
+ " AND nodes.repos_path = lock.repos_relpath " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ "ORDER BY op_depth DESC " \
+ ""
+
+#define STMT_SELECT_BASE_NODE 2
+#define STMT_2_INFO {"STMT_SELECT_BASE_NODE", NULL}
+#define STMT_2 \
+ "SELECT repos_id, repos_path, presence, kind, revision, checksum, " \
+ " translated_size, changed_revision, changed_date, changed_author, depth, " \
+ " symlink_target, last_mod_time, properties, file_external " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_SELECT_BASE_NODE_WITH_LOCK 3
+#define STMT_3_INFO {"STMT_SELECT_BASE_NODE_WITH_LOCK", NULL}
+#define STMT_3 \
+ "SELECT nodes.repos_id, nodes.repos_path, presence, kind, revision, " \
+ " checksum, translated_size, changed_revision, changed_date, changed_author, " \
+ " depth, symlink_target, last_mod_time, properties, file_external, " \
+ " lock_token, lock_owner, lock_comment, lock_date " \
+ "FROM nodes " \
+ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \
+ " AND nodes.repos_path = lock.repos_relpath " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_SELECT_BASE_CHILDREN_INFO 4
+#define STMT_4_INFO {"STMT_SELECT_BASE_CHILDREN_INFO", NULL}
+#define STMT_4 \
+ "SELECT local_relpath, nodes.repos_id, nodes.repos_path, presence, kind, " \
+ " revision, depth, file_external, " \
+ " lock_token, lock_owner, lock_comment, lock_date " \
+ "FROM nodes " \
+ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \
+ " AND nodes.repos_path = lock.repos_relpath " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_SELECT_WORKING_NODE 5
+#define STMT_5_INFO {"STMT_SELECT_WORKING_NODE", NULL}
+#define STMT_5 \
+ "SELECT op_depth, presence, kind, checksum, translated_size, " \
+ " changed_revision, changed_date, changed_author, depth, symlink_target, " \
+ " repos_id, repos_path, revision, " \
+ " moved_here, moved_to, last_mod_time, properties " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0 " \
+ "ORDER BY op_depth DESC " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_DEPTH_NODE 6
+#define STMT_6_INFO {"STMT_SELECT_DEPTH_NODE", NULL}
+#define STMT_6 \
+ "SELECT repos_id, repos_path, presence, kind, revision, checksum, " \
+ " translated_size, changed_revision, changed_date, changed_author, depth, " \
+ " symlink_target, last_mod_time, properties " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_LOWEST_WORKING_NODE 7
+#define STMT_7_INFO {"STMT_SELECT_LOWEST_WORKING_NODE", NULL}
+#define STMT_7 \
+ "SELECT op_depth, presence, kind, moved_to " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 " \
+ "ORDER BY op_depth " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_HIGHEST_WORKING_NODE 8
+#define STMT_8_INFO {"STMT_SELECT_HIGHEST_WORKING_NODE", NULL}
+#define STMT_8 \
+ "SELECT op_depth " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth < ?3 " \
+ "ORDER BY op_depth DESC " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_ACTUAL_NODE 9
+#define STMT_9_INFO {"STMT_SELECT_ACTUAL_NODE", NULL}
+#define STMT_9 \
+ "SELECT changelist, properties, conflict_data " \
+ "FROM actual_node " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_NODE_CHILDREN_INFO 10
+#define STMT_10_INFO {"STMT_SELECT_NODE_CHILDREN_INFO", NULL}
+#define STMT_10 \
+ "SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, " \
+ " checksum, translated_size, changed_revision, changed_date, changed_author, " \
+ " depth, symlink_target, last_mod_time, properties, lock_token, lock_owner, " \
+ " lock_comment, lock_date, local_relpath, moved_here, moved_to, file_external " \
+ "FROM nodes " \
+ "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \
+ " AND nodes.repos_path = lock.repos_relpath AND op_depth = 0 " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_NODE_CHILDREN_WALKER_INFO 11
+#define STMT_11_INFO {"STMT_SELECT_NODE_CHILDREN_WALKER_INFO", NULL}
+#define STMT_11 \
+ "SELECT local_relpath, op_depth, presence, kind " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_ACTUAL_CHILDREN_INFO 12
+#define STMT_12_INFO {"STMT_SELECT_ACTUAL_CHILDREN_INFO", NULL}
+#define STMT_12 \
+ "SELECT local_relpath, changelist, properties, conflict_data " \
+ "FROM actual_node " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_REPOSITORY_BY_ID 13
+#define STMT_13_INFO {"STMT_SELECT_REPOSITORY_BY_ID", NULL}
+#define STMT_13 \
+ "SELECT root, uuid FROM repository WHERE id = ?1 " \
+ ""
+
+#define STMT_SELECT_WCROOT_NULL 14
+#define STMT_14_INFO {"STMT_SELECT_WCROOT_NULL", NULL}
+#define STMT_14 \
+ "SELECT id FROM wcroot WHERE local_abspath IS NULL " \
+ ""
+
+#define STMT_SELECT_REPOSITORY 15
+#define STMT_15_INFO {"STMT_SELECT_REPOSITORY", NULL}
+#define STMT_15 \
+ "SELECT id FROM repository WHERE root = ?1 " \
+ ""
+
+#define STMT_INSERT_REPOSITORY 16
+#define STMT_16_INFO {"STMT_INSERT_REPOSITORY", NULL}
+#define STMT_16 \
+ "INSERT INTO repository (root, uuid) VALUES (?1, ?2) " \
+ ""
+
+#define STMT_INSERT_NODE 17
+#define STMT_17_INFO {"STMT_INSERT_NODE", NULL}
+#define STMT_17 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \
+ " revision, presence, depth, kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external, moved_to, moved_here, " \
+ " inherited_props) " \
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, " \
+ " ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23) " \
+ ""
+
+#define STMT_SELECT_BASE_PRESENT 18
+#define STMT_18_INFO {"STMT_SELECT_BASE_PRESENT", NULL}
+#define STMT_18 \
+ "SELECT local_relpath, kind FROM nodes n " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND presence in ('normal', 'incomplete') " \
+ " AND NOT EXISTS(SELECT 1 FROM NODES w " \
+ " WHERE w.wc_id = ?1 AND w.local_relpath = n.local_relpath " \
+ " AND op_depth > 0) " \
+ "ORDER BY local_relpath DESC " \
+ ""
+
+#define STMT_SELECT_WORKING_PRESENT 19
+#define STMT_19_INFO {"STMT_SELECT_WORKING_PRESENT", NULL}
+#define STMT_19 \
+ "SELECT local_relpath, kind, checksum, translated_size, last_mod_time " \
+ "FROM nodes n " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND presence in ('normal', 'incomplete') " \
+ " AND op_depth = (SELECT MAX(op_depth) " \
+ " FROM NODES w " \
+ " WHERE w.wc_id = ?1 " \
+ " AND w.local_relpath = n.local_relpath) " \
+ "ORDER BY local_relpath DESC " \
+ ""
+
+#define STMT_DELETE_NODE_RECURSIVE 20
+#define STMT_20_INFO {"STMT_DELETE_NODE_RECURSIVE", NULL}
+#define STMT_20 \
+ "DELETE FROM NODES " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_DELETE_NODE 21
+#define STMT_21_INFO {"STMT_DELETE_NODE", NULL}
+#define STMT_21 \
+ "DELETE " \
+ "FROM NODES " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE 22
+#define STMT_22_INFO {"STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE", NULL}
+#define STMT_22 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND EXISTS(SELECT 1 FROM NODES b " \
+ " WHERE b.wc_id = ?1 " \
+ " AND b.local_relpath = actual_node.local_relpath " \
+ " AND op_depth = 0) " \
+ " AND NOT EXISTS(SELECT 1 FROM NODES w " \
+ " WHERE w.wc_id = ?1 " \
+ " AND w.local_relpath = actual_node.local_relpath " \
+ " AND op_depth > 0 " \
+ " AND presence in ('normal', 'incomplete', 'not-present')) " \
+ ""
+
+#define STMT_DELETE_WORKING_BASE_DELETE 23
+#define STMT_23_INFO {"STMT_DELETE_WORKING_BASE_DELETE", NULL}
+#define STMT_23 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND presence = 'base-deleted' " \
+ " AND op_depth > 0 " \
+ " AND op_depth = (SELECT MIN(op_depth) FROM nodes n " \
+ " WHERE n.wc_id = ?1 " \
+ " AND n.local_relpath = nodes.local_relpath " \
+ " AND op_depth > 0) " \
+ ""
+
+#define STMT_DELETE_WORKING_RECURSIVE 24
+#define STMT_24_INFO {"STMT_DELETE_WORKING_RECURSIVE", NULL}
+#define STMT_24 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth > 0 " \
+ ""
+
+#define STMT_DELETE_BASE_RECURSIVE 25
+#define STMT_25_INFO {"STMT_DELETE_BASE_RECURSIVE", NULL}
+#define STMT_25 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ ""
+
+#define STMT_DELETE_WORKING_OP_DEPTH 26
+#define STMT_26_INFO {"STMT_DELETE_WORKING_OP_DEPTH", NULL}
+#define STMT_26 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_DELETE_WORKING_OP_DEPTH_ABOVE 27
+#define STMT_27_INFO {"STMT_DELETE_WORKING_OP_DEPTH_ABOVE", NULL}
+#define STMT_27 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth > ?3 " \
+ ""
+
+#define STMT_SELECT_LOCAL_RELPATH_OP_DEPTH 28
+#define STMT_28_INFO {"STMT_SELECT_LOCAL_RELPATH_OP_DEPTH", NULL}
+#define STMT_28 \
+ "SELECT local_relpath " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_CHILDREN_OP_DEPTH 29
+#define STMT_29_INFO {"STMT_SELECT_CHILDREN_OP_DEPTH", NULL}
+#define STMT_29 \
+ "SELECT local_relpath, kind " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = ?3 " \
+ "ORDER BY local_relpath DESC " \
+ ""
+
+#define STMT_COPY_NODE_MOVE 30
+#define STMT_30_INFO {"STMT_COPY_NODE_MOVE", NULL}
+#define STMT_30 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \
+ " revision, presence, depth, kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, translated_size, last_mod_time, " \
+ " symlink_target, moved_here, moved_to ) " \
+ "SELECT " \
+ " wc_id, ?4 , ?5 , ?6 , " \
+ " repos_id, " \
+ " repos_path, revision, presence, depth, kind, changed_revision, " \
+ " changed_date, changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target, 1, " \
+ " (SELECT dst.moved_to FROM nodes AS dst " \
+ " WHERE dst.wc_id = ?1 " \
+ " AND dst.local_relpath = ?4 " \
+ " AND dst.op_depth = ?5) " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_OP_DEPTH_CHILDREN 31
+#define STMT_31_INFO {"STMT_SELECT_OP_DEPTH_CHILDREN", NULL}
+#define STMT_31 \
+ "SELECT local_relpath, kind FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND parent_relpath = ?2 " \
+ " AND op_depth = ?3 " \
+ " AND presence != 'base-deleted' " \
+ " AND file_external is NULL " \
+ ""
+
+#define STMT_SELECT_GE_OP_DEPTH_CHILDREN 32
+#define STMT_32_INFO {"STMT_SELECT_GE_OP_DEPTH_CHILDREN", NULL}
+#define STMT_32 \
+ "SELECT 1 FROM nodes " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ " AND (op_depth > ?3 OR (op_depth = ?3 AND presence != 'base-deleted')) " \
+ "UNION ALL " \
+ "SELECT 1 FROM ACTUAL_NODE a " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ " AND NOT EXISTS (SELECT 1 FROM nodes n " \
+ " WHERE wc_id = ?1 AND n.local_relpath = a.local_relpath) " \
+ ""
+
+#define STMT_DELETE_SHADOWED_RECURSIVE 33
+#define STMT_33_INFO {"STMT_DELETE_SHADOWED_RECURSIVE", NULL}
+#define STMT_33 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND (op_depth < ?3 " \
+ " OR (op_depth = ?3 AND presence = 'base-deleted')) " \
+ ""
+
+#define STMT_CLEAR_MOVED_TO_FROM_DEST 34
+#define STMT_34_INFO {"STMT_CLEAR_MOVED_TO_FROM_DEST", NULL}
+#define STMT_34 \
+ "UPDATE NODES SET moved_to = NULL " \
+ "WHERE wc_id = ?1 " \
+ " AND moved_to = ?2 " \
+ ""
+
+#define STMT_SELECT_NOT_PRESENT_DESCENDANTS 35
+#define STMT_35_INFO {"STMT_SELECT_NOT_PRESENT_DESCENDANTS", NULL}
+#define STMT_35 \
+ "SELECT local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 AND op_depth = ?3 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND presence = 'not-present' " \
+ ""
+
+#define STMT_COMMIT_DESCENDANTS_TO_BASE 36
+#define STMT_36_INFO {"STMT_COMMIT_DESCENDANTS_TO_BASE", NULL}
+#define STMT_36 \
+ "UPDATE NODES SET op_depth = 0, " \
+ " repos_id = ?4, " \
+ " repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), " \
+ " revision = ?6, " \
+ " dav_cache = NULL, " \
+ " moved_here = NULL, " \
+ " presence = CASE presence " \
+ " WHEN 'normal' THEN 'normal' " \
+ " WHEN 'excluded' THEN 'excluded' " \
+ " ELSE 'not-present' " \
+ " END " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_NODE_CHILDREN 37
+#define STMT_37_INFO {"STMT_SELECT_NODE_CHILDREN", NULL}
+#define STMT_37 \
+ "SELECT local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_WORKING_CHILDREN 38
+#define STMT_38_INFO {"STMT_SELECT_WORKING_CHILDREN", NULL}
+#define STMT_38 \
+ "SELECT local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ " AND (op_depth > (SELECT MAX(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2) " \
+ " OR " \
+ " (op_depth = (SELECT MAX(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2) " \
+ " AND presence != 'base-deleted')) " \
+ ""
+
+#define STMT_SELECT_NODE_PROPS 39
+#define STMT_39_INFO {"STMT_SELECT_NODE_PROPS", NULL}
+#define STMT_39 \
+ "SELECT properties, presence FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ "ORDER BY op_depth DESC " \
+ ""
+
+#define STMT_SELECT_ACTUAL_PROPS 40
+#define STMT_40_INFO {"STMT_SELECT_ACTUAL_PROPS", NULL}
+#define STMT_40 \
+ "SELECT properties FROM actual_node " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_UPDATE_ACTUAL_PROPS 41
+#define STMT_41_INFO {"STMT_UPDATE_ACTUAL_PROPS", NULL}
+#define STMT_41 \
+ "UPDATE actual_node SET properties = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_INSERT_ACTUAL_PROPS 42
+#define STMT_42_INFO {"STMT_INSERT_ACTUAL_PROPS", NULL}
+#define STMT_42 \
+ "INSERT INTO actual_node (wc_id, local_relpath, parent_relpath, properties) " \
+ "VALUES (?1, ?2, ?3, ?4) " \
+ ""
+
+#define STMT_INSERT_LOCK 43
+#define STMT_43_INFO {"STMT_INSERT_LOCK", NULL}
+#define STMT_43 \
+ "INSERT OR REPLACE INTO lock " \
+ "(repos_id, repos_relpath, lock_token, lock_owner, lock_comment, " \
+ " lock_date) " \
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6) " \
+ ""
+
+#define STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE 44
+#define STMT_44_INFO {"STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE", NULL}
+#define STMT_44 \
+ "SELECT nodes.repos_id, nodes.repos_path, lock_token " \
+ "FROM nodes " \
+ "LEFT JOIN lock ON nodes.repos_id = lock.repos_id " \
+ " AND nodes.repos_path = lock.repos_relpath " \
+ "WHERE wc_id = ?1 AND op_depth = 0 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_INSERT_WCROOT 45
+#define STMT_45_INFO {"STMT_INSERT_WCROOT", NULL}
+#define STMT_45 \
+ "INSERT INTO wcroot (local_abspath) " \
+ "VALUES (?1) " \
+ ""
+
+#define STMT_UPDATE_BASE_NODE_DAV_CACHE 46
+#define STMT_46_INFO {"STMT_UPDATE_BASE_NODE_DAV_CACHE", NULL}
+#define STMT_46 \
+ "UPDATE nodes SET dav_cache = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_SELECT_BASE_DAV_CACHE 47
+#define STMT_47_INFO {"STMT_SELECT_BASE_DAV_CACHE", NULL}
+#define STMT_47 \
+ "SELECT dav_cache FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_SELECT_DELETION_INFO 48
+#define STMT_48_INFO {"STMT_SELECT_DELETION_INFO", NULL}
+#define STMT_48 \
+ "SELECT (SELECT b.presence FROM nodes AS b " \
+ " WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), " \
+ " work.presence, work.op_depth " \
+ "FROM nodes_current AS work " \
+ "WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_DELETION_INFO_SCAN 49
+#define STMT_49_INFO {"STMT_SELECT_DELETION_INFO_SCAN", NULL}
+#define STMT_49 \
+ "SELECT (SELECT b.presence FROM nodes AS b " \
+ " WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), " \
+ " work.presence, work.op_depth, moved.moved_to " \
+ "FROM nodes_current AS work " \
+ "LEFT OUTER JOIN nodes AS moved " \
+ " ON moved.wc_id = work.wc_id " \
+ " AND moved.local_relpath = work.local_relpath " \
+ " AND moved.moved_to IS NOT NULL " \
+ "WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_OP_DEPTH_MOVED_TO 50
+#define STMT_50_INFO {"STMT_SELECT_OP_DEPTH_MOVED_TO", NULL}
+#define STMT_50 \
+ "SELECT op_depth, moved_to, repos_path, revision " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND op_depth <= (SELECT MIN(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) " \
+ "ORDER BY op_depth DESC " \
+ ""
+
+#define STMT_SELECT_MOVED_TO 51
+#define STMT_51_INFO {"STMT_SELECT_MOVED_TO", NULL}
+#define STMT_51 \
+ "SELECT moved_to " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_MOVED_HERE 52
+#define STMT_52_INFO {"STMT_SELECT_MOVED_HERE", NULL}
+#define STMT_52 \
+ "SELECT moved_here, presence, repos_path, revision " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth >= ?3 " \
+ "ORDER BY op_depth " \
+ ""
+
+#define STMT_SELECT_MOVED_BACK 53
+#define STMT_53_INFO {"STMT_SELECT_MOVED_BACK", NULL}
+#define STMT_53 \
+ "SELECT u.local_relpath, " \
+ " u.presence, u.repos_id, u.repos_path, u.revision, " \
+ " l.presence, l.repos_id, l.repos_path, l.revision, " \
+ " u.moved_here, u.moved_to " \
+ "FROM nodes u " \
+ "LEFT OUTER JOIN nodes l ON l.wc_id = ?1 " \
+ " AND l.local_relpath = u.local_relpath " \
+ " AND l.op_depth = ?3 " \
+ "WHERE u.wc_id = ?1 " \
+ " AND u.local_relpath = ?2 " \
+ " AND u.op_depth = ?4 " \
+ "UNION ALL " \
+ "SELECT u.local_relpath, " \
+ " u.presence, u.repos_id, u.repos_path, u.revision, " \
+ " l.presence, l.repos_id, l.repos_path, l.revision, " \
+ " u.moved_here, NULL " \
+ "FROM nodes u " \
+ "LEFT OUTER JOIN nodes l ON l.wc_id=?1 " \
+ " AND l.local_relpath=u.local_relpath " \
+ " AND l.op_depth=?3 " \
+ "WHERE u.wc_id = ?1 " \
+ " AND (((u.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((u.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND u.op_depth = ?4 " \
+ ""
+
+#define STMT_DELETE_MOVED_BACK 54
+#define STMT_54_INFO {"STMT_DELETE_MOVED_BACK", NULL}
+#define STMT_54 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_DELETE_LOCK 55
+#define STMT_55_INFO {"STMT_DELETE_LOCK", NULL}
+#define STMT_55 \
+ "DELETE FROM lock " \
+ "WHERE repos_id = ?1 AND repos_relpath = ?2 " \
+ ""
+
+#define STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE 56
+#define STMT_56_INFO {"STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE", NULL}
+#define STMT_56 \
+ "UPDATE nodes SET dav_cache = NULL " \
+ "WHERE dav_cache IS NOT NULL AND wc_id = ?1 AND op_depth = 0 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ ""
+
+#define STMT_RECURSIVE_UPDATE_NODE_REPO 57
+#define STMT_57_INFO {"STMT_RECURSIVE_UPDATE_NODE_REPO", NULL}
+#define STMT_57 \
+ "UPDATE nodes SET repos_id = ?4, dav_cache = NULL " \
+ "WHERE (wc_id = ?1 AND local_relpath = ?2 AND repos_id = ?3) " \
+ " OR (wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND repos_id = ?3) " \
+ ""
+
+#define STMT_UPDATE_LOCK_REPOS_ID 58
+#define STMT_58_INFO {"STMT_UPDATE_LOCK_REPOS_ID", NULL}
+#define STMT_58 \
+ "UPDATE lock SET repos_id = ?2 " \
+ "WHERE repos_id = ?1 " \
+ ""
+
+#define STMT_UPDATE_NODE_FILEINFO 59
+#define STMT_59_INFO {"STMT_UPDATE_NODE_FILEINFO", NULL}
+#define STMT_59 \
+ "UPDATE nodes SET translated_size = ?3, last_mod_time = ?4 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND op_depth = (SELECT MAX(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2) " \
+ ""
+
+#define STMT_INSERT_ACTUAL_CONFLICT 60
+#define STMT_60_INFO {"STMT_INSERT_ACTUAL_CONFLICT", NULL}
+#define STMT_60 \
+ "INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) " \
+ "VALUES (?1, ?2, ?3, ?4) " \
+ ""
+
+#define STMT_UPDATE_ACTUAL_CONFLICT 61
+#define STMT_61_INFO {"STMT_UPDATE_ACTUAL_CONFLICT", NULL}
+#define STMT_61 \
+ "UPDATE actual_node SET conflict_data = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_UPDATE_ACTUAL_CHANGELISTS 62
+#define STMT_62_INFO {"STMT_UPDATE_ACTUAL_CHANGELISTS", NULL}
+#define STMT_62 \
+ "UPDATE actual_node SET changelist = ?3 " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND local_relpath = (SELECT local_relpath FROM targets_list AS t " \
+ " WHERE wc_id = ?1 " \
+ " AND t.local_relpath = actual_node.local_relpath " \
+ " AND kind = 'file') " \
+ ""
+
+#define STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST 63
+#define STMT_63_INFO {"STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST", NULL}
+#define STMT_63 \
+ "UPDATE actual_node SET changelist = NULL " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_MARK_SKIPPED_CHANGELIST_DIRS 64
+#define STMT_64_INFO {"STMT_MARK_SKIPPED_CHANGELIST_DIRS", NULL}
+#define STMT_64 \
+ "INSERT INTO changelist_list (wc_id, local_relpath, notify, changelist) " \
+ "SELECT wc_id, local_relpath, 7, ?3 " \
+ "FROM targets_list " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND kind = 'dir' " \
+ ""
+
+#define STMT_RESET_ACTUAL_WITH_CHANGELIST 65
+#define STMT_65_INFO {"STMT_RESET_ACTUAL_WITH_CHANGELIST", NULL}
+#define STMT_65 \
+ "REPLACE INTO actual_node ( " \
+ " wc_id, local_relpath, parent_relpath, changelist) " \
+ "VALUES (?1, ?2, ?3, ?4) " \
+ ""
+
+#define STMT_CREATE_CHANGELIST_LIST 66
+#define STMT_66_INFO {"STMT_CREATE_CHANGELIST_LIST", NULL}
+#define STMT_66 \
+ "DROP TABLE IF EXISTS changelist_list; " \
+ "CREATE TEMPORARY TABLE changelist_list ( " \
+ " wc_id INTEGER NOT NULL, " \
+ " local_relpath TEXT NOT NULL, " \
+ " notify INTEGER NOT NULL, " \
+ " changelist TEXT NOT NULL, " \
+ " PRIMARY KEY (wc_id, local_relpath, notify DESC) " \
+ ") " \
+ ""
+
+#define STMT_CREATE_CHANGELIST_TRIGGER 67
+#define STMT_67_INFO {"STMT_CREATE_CHANGELIST_TRIGGER", NULL}
+#define STMT_67 \
+ "DROP TRIGGER IF EXISTS trigger_changelist_list_change; " \
+ "CREATE TEMPORARY TRIGGER trigger_changelist_list_change " \
+ "BEFORE UPDATE ON actual_node " \
+ "WHEN old.changelist IS NOT new.changelist " \
+ "BEGIN " \
+ " INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \
+ " SELECT old.wc_id, old.local_relpath, 27, old.changelist " \
+ " WHERE old.changelist is NOT NULL; " \
+ " INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \
+ " SELECT new.wc_id, new.local_relpath, 26, new.changelist " \
+ " WHERE new.changelist IS NOT NULL; " \
+ "END " \
+ ""
+
+#define STMT_FINALIZE_CHANGELIST 68
+#define STMT_68_INFO {"STMT_FINALIZE_CHANGELIST", NULL}
+#define STMT_68 \
+ "DROP TRIGGER trigger_changelist_list_change; " \
+ "DROP TABLE changelist_list; " \
+ "DROP TABLE targets_list " \
+ ""
+
+#define STMT_SELECT_CHANGELIST_LIST 69
+#define STMT_69_INFO {"STMT_SELECT_CHANGELIST_LIST", NULL}
+#define STMT_69 \
+ "SELECT wc_id, local_relpath, notify, changelist " \
+ "FROM changelist_list " \
+ "ORDER BY wc_id, local_relpath ASC, notify DESC " \
+ ""
+
+#define STMT_CREATE_TARGETS_LIST 70
+#define STMT_70_INFO {"STMT_CREATE_TARGETS_LIST", NULL}
+#define STMT_70 \
+ "DROP TABLE IF EXISTS targets_list; " \
+ "CREATE TEMPORARY TABLE targets_list ( " \
+ " wc_id INTEGER NOT NULL, " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " kind TEXT NOT NULL, " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ " ); " \
+ ""
+
+#define STMT_DROP_TARGETS_LIST 71
+#define STMT_71_INFO {"STMT_DROP_TARGETS_LIST", NULL}
+#define STMT_71 \
+ "DROP TABLE targets_list " \
+ ""
+
+#define STMT_INSERT_TARGET 72
+#define STMT_72_INFO {"STMT_INSERT_TARGET", NULL}
+#define STMT_72 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT wc_id, local_relpath, parent_relpath, kind " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 " \
+ " AND local_relpath = ?2 " \
+ ""
+
+#define STMT_INSERT_TARGET_DEPTH_FILES 73
+#define STMT_73_INFO {"STMT_INSERT_TARGET_DEPTH_FILES", NULL}
+#define STMT_73 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT wc_id, local_relpath, parent_relpath, kind " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 " \
+ " AND parent_relpath = ?2 " \
+ " AND kind = 'file' " \
+ ""
+
+#define STMT_INSERT_TARGET_DEPTH_IMMEDIATES 74
+#define STMT_74_INFO {"STMT_INSERT_TARGET_DEPTH_IMMEDIATES", NULL}
+#define STMT_74 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT wc_id, local_relpath, parent_relpath, kind " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 " \
+ " AND parent_relpath = ?2 " \
+ ""
+
+#define STMT_INSERT_TARGET_DEPTH_INFINITY 75
+#define STMT_75_INFO {"STMT_INSERT_TARGET_DEPTH_INFINITY", NULL}
+#define STMT_75 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT wc_id, local_relpath, parent_relpath, kind " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_INSERT_TARGET_WITH_CHANGELIST 76
+#define STMT_76_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST", NULL}
+#define STMT_76 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \
+ " FROM actual_node AS A JOIN nodes_current AS N " \
+ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \
+ " WHERE N.wc_id = ?1 " \
+ " AND N.local_relpath = ?2 " \
+ " AND A.changelist = ?3 " \
+ ""
+
+#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES 77
+#define STMT_77_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES", NULL}
+#define STMT_77 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \
+ " FROM actual_node AS A JOIN nodes_current AS N " \
+ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \
+ " WHERE N.wc_id = ?1 " \
+ " AND N.parent_relpath = ?2 " \
+ " AND kind = 'file' " \
+ " AND A.changelist = ?3 " \
+ ""
+
+#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES 78
+#define STMT_78_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES", NULL}
+#define STMT_78 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \
+ " FROM actual_node AS A JOIN nodes_current AS N " \
+ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \
+ " WHERE N.wc_id = ?1 " \
+ " AND N.parent_relpath = ?2 " \
+ " AND A.changelist = ?3 " \
+ ""
+
+#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY 79
+#define STMT_79_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY", NULL}
+#define STMT_79 \
+ "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \
+ "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \
+ " FROM actual_node AS A JOIN nodes_current AS N " \
+ " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \
+ " WHERE N.wc_id = ?1 " \
+ " AND (((N.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((N.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND A.changelist = ?3 " \
+ ""
+
+#define STMT_INSERT_ACTUAL_EMPTIES 80
+#define STMT_80_INFO {"STMT_INSERT_ACTUAL_EMPTIES", NULL}
+#define STMT_80 \
+ "INSERT OR IGNORE INTO actual_node ( " \
+ " wc_id, local_relpath, parent_relpath) " \
+ "SELECT wc_id, local_relpath, parent_relpath " \
+ "FROM targets_list " \
+ ""
+
+#define STMT_DELETE_ACTUAL_EMPTY 81
+#define STMT_81_INFO {"STMT_DELETE_ACTUAL_EMPTY", NULL}
+#define STMT_81 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND properties IS NULL " \
+ " AND conflict_data IS NULL " \
+ " AND changelist IS NULL " \
+ " AND text_mod IS NULL " \
+ " AND older_checksum IS NULL " \
+ " AND right_checksum IS NULL " \
+ " AND left_checksum IS NULL " \
+ ""
+
+#define STMT_DELETE_ACTUAL_EMPTIES 82
+#define STMT_82_INFO {"STMT_DELETE_ACTUAL_EMPTIES", NULL}
+#define STMT_82 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND properties IS NULL " \
+ " AND conflict_data IS NULL " \
+ " AND changelist IS NULL " \
+ " AND text_mod IS NULL " \
+ " AND older_checksum IS NULL " \
+ " AND right_checksum IS NULL " \
+ " AND left_checksum IS NULL " \
+ ""
+
+#define STMT_DELETE_BASE_NODE 83
+#define STMT_83_INFO {"STMT_DELETE_BASE_NODE", NULL}
+#define STMT_83 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_DELETE_WORKING_NODE 84
+#define STMT_84_INFO {"STMT_DELETE_WORKING_NODE", NULL}
+#define STMT_84 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND op_depth = (SELECT MAX(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0) " \
+ ""
+
+#define STMT_DELETE_LOWEST_WORKING_NODE 85
+#define STMT_85_INFO {"STMT_DELETE_LOWEST_WORKING_NODE", NULL}
+#define STMT_85 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND op_depth = (SELECT MIN(op_depth) FROM nodes " \
+ " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) " \
+ " AND presence = 'base-deleted' " \
+ ""
+
+#define STMT_DELETE_ALL_LAYERS 86
+#define STMT_86_INFO {"STMT_DELETE_ALL_LAYERS", NULL}
+#define STMT_86 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE 87
+#define STMT_87_INFO {"STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE", NULL}
+#define STMT_87 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth >= ?3 " \
+ ""
+
+#define STMT_DELETE_ACTUAL_NODE 88
+#define STMT_88_INFO {"STMT_DELETE_ACTUAL_NODE", NULL}
+#define STMT_88 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_DELETE_ACTUAL_NODE_RECURSIVE 89
+#define STMT_89_INFO {"STMT_DELETE_ACTUAL_NODE_RECURSIVE", NULL}
+#define STMT_89 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ ""
+
+#define STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST 90
+#define STMT_90_INFO {"STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST", NULL}
+#define STMT_90 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 " \
+ " AND local_relpath = ?2 " \
+ " AND (changelist IS NULL " \
+ " OR NOT EXISTS (SELECT 1 FROM nodes_current c " \
+ " WHERE c.wc_id = ?1 AND c.local_relpath = ?2 " \
+ " AND c.kind = 'file')) " \
+ ""
+
+#define STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE 91
+#define STMT_91_INFO {"STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE", NULL}
+#define STMT_91 \
+ "DELETE FROM actual_node " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND (changelist IS NULL " \
+ " OR NOT EXISTS (SELECT 1 FROM nodes_current c " \
+ " WHERE c.wc_id = ?1 " \
+ " AND c.local_relpath = actual_node.local_relpath " \
+ " AND c.kind = 'file')) " \
+ ""
+
+#define STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST 92
+#define STMT_92_INFO {"STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST", NULL}
+#define STMT_92 \
+ "UPDATE actual_node " \
+ "SET properties = NULL, " \
+ " text_mod = NULL, " \
+ " conflict_data = NULL, " \
+ " tree_conflict_data = NULL, " \
+ " older_checksum = NULL, " \
+ " left_checksum = NULL, " \
+ " right_checksum = NULL " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE 93
+#define STMT_93_INFO {"STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE", NULL}
+#define STMT_93 \
+ "UPDATE actual_node " \
+ "SET properties = NULL, " \
+ " text_mod = NULL, " \
+ " conflict_data = NULL, " \
+ " tree_conflict_data = NULL, " \
+ " older_checksum = NULL, " \
+ " left_checksum = NULL, " \
+ " right_checksum = NULL " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ ""
+
+#define STMT_UPDATE_NODE_BASE_DEPTH 94
+#define STMT_94_INFO {"STMT_UPDATE_NODE_BASE_DEPTH", NULL}
+#define STMT_94 \
+ "UPDATE nodes SET depth = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ " AND kind='dir' " \
+ ""
+
+#define STMT_UPDATE_NODE_BASE_PRESENCE 95
+#define STMT_95_INFO {"STMT_UPDATE_NODE_BASE_PRESENCE", NULL}
+#define STMT_95 \
+ "UPDATE nodes SET presence = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH 96
+#define STMT_96_INFO {"STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH", NULL}
+#define STMT_96 \
+ "UPDATE nodes SET presence = ?3, revision = ?4, repos_path = ?5 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_LOOK_FOR_WORK 97
+#define STMT_97_INFO {"STMT_LOOK_FOR_WORK", NULL}
+#define STMT_97 \
+ "SELECT id FROM work_queue LIMIT 1 " \
+ ""
+
+#define STMT_INSERT_WORK_ITEM 98
+#define STMT_98_INFO {"STMT_INSERT_WORK_ITEM", NULL}
+#define STMT_98 \
+ "INSERT INTO work_queue (work) VALUES (?1) " \
+ ""
+
+#define STMT_SELECT_WORK_ITEM 99
+#define STMT_99_INFO {"STMT_SELECT_WORK_ITEM", NULL}
+#define STMT_99 \
+ "SELECT id, work FROM work_queue ORDER BY id LIMIT 1 " \
+ ""
+
+#define STMT_DELETE_WORK_ITEM 100
+#define STMT_100_INFO {"STMT_DELETE_WORK_ITEM", NULL}
+#define STMT_100 \
+ "DELETE FROM work_queue WHERE id = ?1 " \
+ ""
+
+#define STMT_INSERT_OR_IGNORE_PRISTINE 101
+#define STMT_101_INFO {"STMT_INSERT_OR_IGNORE_PRISTINE", NULL}
+#define STMT_101 \
+ "INSERT OR IGNORE INTO pristine (checksum, md5_checksum, size, refcount) " \
+ "VALUES (?1, ?2, ?3, 0) " \
+ ""
+
+#define STMT_INSERT_PRISTINE 102
+#define STMT_102_INFO {"STMT_INSERT_PRISTINE", NULL}
+#define STMT_102 \
+ "INSERT INTO pristine (checksum, md5_checksum, size, refcount) " \
+ "VALUES (?1, ?2, ?3, 0) " \
+ ""
+
+#define STMT_SELECT_PRISTINE 103
+#define STMT_103_INFO {"STMT_SELECT_PRISTINE", NULL}
+#define STMT_103 \
+ "SELECT md5_checksum " \
+ "FROM pristine " \
+ "WHERE checksum = ?1 " \
+ ""
+
+#define STMT_SELECT_PRISTINE_SIZE 104
+#define STMT_104_INFO {"STMT_SELECT_PRISTINE_SIZE", NULL}
+#define STMT_104 \
+ "SELECT size " \
+ "FROM pristine " \
+ "WHERE checksum = ?1 LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_PRISTINE_BY_MD5 105
+#define STMT_105_INFO {"STMT_SELECT_PRISTINE_BY_MD5", NULL}
+#define STMT_105 \
+ "SELECT checksum " \
+ "FROM pristine " \
+ "WHERE md5_checksum = ?1 " \
+ ""
+
+#define STMT_SELECT_UNREFERENCED_PRISTINES 106
+#define STMT_106_INFO {"STMT_SELECT_UNREFERENCED_PRISTINES", NULL}
+#define STMT_106 \
+ "SELECT checksum " \
+ "FROM pristine " \
+ "WHERE refcount = 0 " \
+ ""
+
+#define STMT_DELETE_PRISTINE_IF_UNREFERENCED 107
+#define STMT_107_INFO {"STMT_DELETE_PRISTINE_IF_UNREFERENCED", NULL}
+#define STMT_107 \
+ "DELETE FROM pristine " \
+ "WHERE checksum = ?1 AND refcount = 0 " \
+ ""
+
+#define STMT_SELECT_COPY_PRISTINES 108
+#define STMT_108_INFO {"STMT_SELECT_COPY_PRISTINES", NULL}
+#define STMT_108 \
+ "SELECT n.checksum, md5_checksum, size " \
+ "FROM nodes_current n " \
+ "LEFT JOIN pristine p ON n.checksum = p.checksum " \
+ "WHERE wc_id = ?1 " \
+ " AND n.local_relpath = ?2 " \
+ " AND n.checksum IS NOT NULL " \
+ "UNION ALL " \
+ "SELECT n.checksum, md5_checksum, size " \
+ "FROM nodes n " \
+ "LEFT JOIN pristine p ON n.checksum = p.checksum " \
+ "WHERE wc_id = ?1 " \
+ " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth >= " \
+ " (SELECT MAX(op_depth) FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2) " \
+ " AND n.checksum IS NOT NULL " \
+ ""
+
+#define STMT_VACUUM 109
+#define STMT_109_INFO {"STMT_VACUUM", NULL}
+#define STMT_109 \
+ "VACUUM " \
+ ""
+
+#define STMT_SELECT_CONFLICT_VICTIMS 110
+#define STMT_110_INFO {"STMT_SELECT_CONFLICT_VICTIMS", NULL}
+#define STMT_110 \
+ "SELECT local_relpath, conflict_data " \
+ "FROM actual_node " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 AND " \
+ " NOT (conflict_data IS NULL) " \
+ ""
+
+#define STMT_INSERT_WC_LOCK 111
+#define STMT_111_INFO {"STMT_INSERT_WC_LOCK", NULL}
+#define STMT_111 \
+ "INSERT INTO wc_lock (wc_id, local_dir_relpath, locked_levels) " \
+ "VALUES (?1, ?2, ?3) " \
+ ""
+
+#define STMT_SELECT_WC_LOCK 112
+#define STMT_112_INFO {"STMT_SELECT_WC_LOCK", NULL}
+#define STMT_112 \
+ "SELECT locked_levels FROM wc_lock " \
+ "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_ANCESTOR_WCLOCKS 113
+#define STMT_113_INFO {"STMT_SELECT_ANCESTOR_WCLOCKS", NULL}
+#define STMT_113 \
+ "SELECT local_dir_relpath, locked_levels FROM wc_lock " \
+ "WHERE wc_id = ?1 " \
+ " AND ((local_dir_relpath >= ?3 AND local_dir_relpath <= ?2) " \
+ " OR local_dir_relpath = '') " \
+ ""
+
+#define STMT_DELETE_WC_LOCK 114
+#define STMT_114_INFO {"STMT_DELETE_WC_LOCK", NULL}
+#define STMT_114 \
+ "DELETE FROM wc_lock " \
+ "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \
+ ""
+
+#define STMT_FIND_WC_LOCK 115
+#define STMT_115_INFO {"STMT_FIND_WC_LOCK", NULL}
+#define STMT_115 \
+ "SELECT local_dir_relpath FROM wc_lock " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_dir_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_dir_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_DELETE_WC_LOCK_ORPHAN 116
+#define STMT_116_INFO {"STMT_DELETE_WC_LOCK_ORPHAN", NULL}
+#define STMT_116 \
+ "DELETE FROM wc_lock " \
+ "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \
+ "AND NOT EXISTS (SELECT 1 FROM nodes " \
+ " WHERE nodes.wc_id = ?1 " \
+ " AND nodes.local_relpath = wc_lock.local_dir_relpath) " \
+ ""
+
+#define STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE 117
+#define STMT_117_INFO {"STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE", NULL}
+#define STMT_117 \
+ "DELETE FROM wc_lock " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_dir_relpath = ?2 " \
+ " OR (((local_dir_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_dir_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND NOT EXISTS (SELECT 1 FROM nodes " \
+ " WHERE nodes.wc_id = ?1 " \
+ " AND nodes.local_relpath = wc_lock.local_dir_relpath) " \
+ ""
+
+#define STMT_APPLY_CHANGES_TO_BASE_NODE 118
+#define STMT_118_INFO {"STMT_APPLY_CHANGES_TO_BASE_NODE", NULL}
+#define STMT_118 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \
+ " revision, presence, depth, kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, dav_cache, symlink_target, " \
+ " inherited_props, file_external ) " \
+ "VALUES (?1, ?2, 0, " \
+ " ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, " \
+ " (SELECT file_external FROM nodes " \
+ " WHERE wc_id = ?1 " \
+ " AND local_relpath = ?2 " \
+ " AND op_depth = 0)) " \
+ ""
+
+#define STMT_INSTALL_WORKING_NODE_FOR_DELETE 119
+#define STMT_119_INFO {"STMT_INSTALL_WORKING_NODE_FOR_DELETE", NULL}
+#define STMT_119 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, " \
+ " parent_relpath, presence, kind) " \
+ "VALUES(?1, ?2, ?3, ?4, 'base-deleted', ?5) " \
+ ""
+
+#define STMT_DELETE_NO_LOWER_LAYER 120
+#define STMT_120_INFO {"STMT_DELETE_NO_LOWER_LAYER", NULL}
+#define STMT_120 \
+ "DELETE FROM nodes " \
+ " WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ " AND NOT EXISTS (SELECT 1 FROM nodes n " \
+ " WHERE n.wc_id = ?1 " \
+ " AND n.local_relpath = nodes.local_relpath " \
+ " AND n.op_depth = ?4 " \
+ " AND n.presence IN ('normal', 'incomplete')) " \
+ ""
+
+#define STMT_REPLACE_WITH_BASE_DELETED 121
+#define STMT_121_INFO {"STMT_REPLACE_WITH_BASE_DELETED", NULL}
+#define STMT_121 \
+ "INSERT OR REPLACE INTO nodes (wc_id, local_relpath, op_depth, parent_relpath, " \
+ " kind, moved_to, presence) " \
+ "SELECT wc_id, local_relpath, op_depth, parent_relpath, " \
+ " kind, moved_to, 'base-deleted' " \
+ " FROM nodes " \
+ " WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_INSERT_DELETE_FROM_NODE_RECURSIVE 122
+#define STMT_122_INFO {"STMT_INSERT_DELETE_FROM_NODE_RECURSIVE", NULL}
+#define STMT_122 \
+ "INSERT INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, presence, kind) " \
+ "SELECT wc_id, local_relpath, ?4 , parent_relpath, 'base-deleted', " \
+ " kind " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ " AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'server-excluded') " \
+ " AND file_external IS NULL " \
+ ""
+
+#define STMT_INSERT_WORKING_NODE_FROM_BASE_COPY 123
+#define STMT_123_INFO {"STMT_INSERT_WORKING_NODE_FROM_BASE_COPY", NULL}
+#define STMT_123 \
+ "INSERT INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \
+ " revision, presence, depth, kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, translated_size, last_mod_time, " \
+ " symlink_target ) " \
+ "SELECT wc_id, local_relpath, ?3 , parent_relpath, repos_id, " \
+ " repos_path, revision, presence, depth, kind, changed_revision, " \
+ " changed_date, changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_INSERT_DELETE_FROM_BASE 124
+#define STMT_124_INFO {"STMT_INSERT_DELETE_FROM_BASE", NULL}
+#define STMT_124 \
+ "INSERT INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, presence, kind) " \
+ "SELECT wc_id, local_relpath, ?3 , parent_relpath, " \
+ " 'base-deleted', kind " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE 125
+#define STMT_125_INFO {"STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE", NULL}
+#define STMT_125 \
+ "UPDATE nodes SET op_depth = ?3 + 1 " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_UPDATE_OP_DEPTH_RECURSIVE 126
+#define STMT_126_INFO {"STMT_UPDATE_OP_DEPTH_RECURSIVE", NULL}
+#define STMT_126 \
+ "UPDATE nodes SET op_depth = ?4, moved_here = NULL " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_DOES_NODE_EXIST 127
+#define STMT_127_INFO {"STMT_DOES_NODE_EXIST", NULL}
+#define STMT_127 \
+ "SELECT 1 FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_HAS_SERVER_EXCLUDED_DESCENDANTS 128
+#define STMT_128_INFO {"STMT_HAS_SERVER_EXCLUDED_DESCENDANTS", NULL}
+#define STMT_128 \
+ "SELECT local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 AND presence = 'server-excluded' " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_ALL_EXCLUDED_DESCENDANTS 129
+#define STMT_129_INFO {"STMT_SELECT_ALL_EXCLUDED_DESCENDANTS", NULL}
+#define STMT_129 \
+ "SELECT local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND (presence = 'server-excluded' OR presence = 'excluded') " \
+ ""
+
+#define STMT_INSERT_WORKING_NODE_COPY_FROM 130
+#define STMT_130_INFO {"STMT_INSERT_WORKING_NODE_COPY_FROM", NULL}
+#define STMT_130 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, " \
+ " repos_path, revision, presence, depth, moved_here, kind, changed_revision, " \
+ " changed_date, changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target, moved_to ) " \
+ "SELECT wc_id, ?3 , ?4 , ?5 , " \
+ " repos_id, repos_path, revision, ?6 , depth, " \
+ " ?7, kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target, " \
+ " (SELECT dst.moved_to FROM nodes AS dst " \
+ " WHERE dst.wc_id = ?1 " \
+ " AND dst.local_relpath = ?3 " \
+ " AND dst.op_depth = ?4) " \
+ "FROM nodes_current " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH 131
+#define STMT_131_INFO {"STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH", NULL}
+#define STMT_131 \
+ "INSERT OR REPLACE INTO nodes ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, repos_id, " \
+ " repos_path, revision, presence, depth, moved_here, kind, changed_revision, " \
+ " changed_date, changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target, moved_to ) " \
+ "SELECT wc_id, ?3 , ?4 , ?5 , " \
+ " repos_id, repos_path, revision, ?6 , depth, " \
+ " ?8 , kind, changed_revision, changed_date, " \
+ " changed_author, checksum, properties, translated_size, " \
+ " last_mod_time, symlink_target, " \
+ " (SELECT dst.moved_to FROM nodes AS dst " \
+ " WHERE dst.wc_id = ?1 " \
+ " AND dst.local_relpath = ?3 " \
+ " AND dst.op_depth = ?4) " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?7 " \
+ ""
+
+#define STMT_UPDATE_BASE_REVISION 132
+#define STMT_132_INFO {"STMT_UPDATE_BASE_REVISION", NULL}
+#define STMT_132 \
+ "UPDATE nodes SET revision = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_UPDATE_BASE_REPOS 133
+#define STMT_133_INFO {"STMT_UPDATE_BASE_REPOS", NULL}
+#define STMT_133 \
+ "UPDATE nodes SET repos_id = ?3, repos_path = ?4 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \
+ ""
+
+#define STMT_ACTUAL_HAS_CHILDREN 134
+#define STMT_134_INFO {"STMT_ACTUAL_HAS_CHILDREN", NULL}
+#define STMT_134 \
+ "SELECT 1 FROM actual_node " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_INSERT_EXTERNAL 135
+#define STMT_135_INFO {"STMT_INSERT_EXTERNAL", NULL}
+#define STMT_135 \
+ "INSERT OR REPLACE INTO externals ( " \
+ " wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath, " \
+ " repos_id, def_repos_relpath, def_operational_revision, def_revision) " \
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) " \
+ ""
+
+#define STMT_SELECT_EXTERNAL_INFO 136
+#define STMT_136_INFO {"STMT_SELECT_EXTERNAL_INFO", NULL}
+#define STMT_136 \
+ "SELECT presence, kind, def_local_relpath, repos_id, " \
+ " def_repos_relpath, def_operational_revision, def_revision " \
+ "FROM externals WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_DELETE_FILE_EXTERNALS 137
+#define STMT_137_INFO {"STMT_DELETE_FILE_EXTERNALS", NULL}
+#define STMT_137 \
+ "DELETE FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND file_external IS NOT NULL " \
+ ""
+
+#define STMT_DELETE_FILE_EXTERNAL_REGISTATIONS 138
+#define STMT_138_INFO {"STMT_DELETE_FILE_EXTERNAL_REGISTATIONS", NULL}
+#define STMT_138 \
+ "DELETE FROM externals " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND kind != 'dir' " \
+ ""
+
+#define STMT_DELETE_EXTERNAL_REGISTATIONS 139
+#define STMT_139_INFO {"STMT_DELETE_EXTERNAL_REGISTATIONS", NULL}
+#define STMT_139 \
+ "DELETE FROM externals " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW 140
+#define STMT_140_INFO {"STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW", NULL}
+#define STMT_140 \
+ "SELECT local_relpath, kind, def_repos_relpath, " \
+ " (SELECT root FROM repository AS r WHERE r.id = e.repos_id) " \
+ "FROM externals e " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND def_revision IS NULL " \
+ " AND repos_id = (SELECT repos_id " \
+ " FROM nodes AS n " \
+ " WHERE n.wc_id = ?1 " \
+ " AND n.local_relpath = '' " \
+ " AND n.op_depth = 0) " \
+ " AND ((kind='dir') " \
+ " OR EXISTS (SELECT 1 FROM nodes " \
+ " WHERE nodes.wc_id = e.wc_id " \
+ " AND nodes.local_relpath = e.parent_relpath)) " \
+ ""
+
+#define STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW 141
+#define STMT_141_INFO {"STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW", NULL}
+#define STMT_141 \
+ "SELECT local_relpath, kind, def_repos_relpath, " \
+ " (SELECT root FROM repository AS r WHERE r.id = e.repos_id) " \
+ "FROM externals e " \
+ "WHERE wc_id = ?1 " \
+ " AND (((e.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((e.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND parent_relpath = ?2 " \
+ " AND def_revision IS NULL " \
+ " AND repos_id = (SELECT repos_id " \
+ " FROM nodes AS n " \
+ " WHERE n.wc_id = ?1 " \
+ " AND n.local_relpath = '' " \
+ " AND n.op_depth = 0) " \
+ " AND ((kind='dir') " \
+ " OR EXISTS (SELECT 1 FROM nodes " \
+ " WHERE nodes.wc_id = e.wc_id " \
+ " AND nodes.local_relpath = e.parent_relpath)) " \
+ ""
+
+#define STMT_SELECT_EXTERNALS_DEFINED 142
+#define STMT_142_INFO {"STMT_SELECT_EXTERNALS_DEFINED", NULL}
+#define STMT_142 \
+ "SELECT local_relpath, def_local_relpath " \
+ "FROM externals " \
+ "WHERE (wc_id = ?1 AND def_local_relpath = ?2) " \
+ " OR (wc_id = ?1 AND (((def_local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((def_local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ ""
+
+#define STMT_DELETE_EXTERNAL 143
+#define STMT_143_INFO {"STMT_DELETE_EXTERNAL", NULL}
+#define STMT_143 \
+ "DELETE FROM externals " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_SELECT_EXTERNAL_PROPERTIES 144
+#define STMT_144_INFO {"STMT_SELECT_EXTERNAL_PROPERTIES", NULL}
+#define STMT_144 \
+ "SELECT IFNULL((SELECT properties FROM actual_node a " \
+ " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \
+ " properties), " \
+ " local_relpath, depth " \
+ "FROM nodes_current n " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ " AND kind = 'dir' AND presence IN ('normal', 'incomplete') " \
+ "UNION ALL " \
+ "SELECT IFNULL((SELECT properties FROM actual_node a " \
+ " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \
+ " properties), " \
+ " local_relpath, depth " \
+ "FROM nodes_current n " \
+ "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND kind = 'dir' AND presence IN ('normal', 'incomplete') " \
+ ""
+
+#define STMT_SELECT_CURRENT_PROPS_RECURSIVE 145
+#define STMT_145_INFO {"STMT_SELECT_CURRENT_PROPS_RECURSIVE", NULL}
+#define STMT_145 \
+ "SELECT IFNULL((SELECT properties FROM actual_node a " \
+ " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \
+ " properties), " \
+ " local_relpath " \
+ "FROM nodes_current n " \
+ "WHERE (wc_id = ?1 AND local_relpath = ?2) " \
+ " OR (wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ ""
+
+#define STMT_PRAGMA_LOCKING_MODE 146
+#define STMT_146_INFO {"STMT_PRAGMA_LOCKING_MODE", NULL}
+#define STMT_146 \
+ "PRAGMA locking_mode = exclusive " \
+ ""
+
+#define STMT_INSERT_ACTUAL_NODE 147
+#define STMT_147_INFO {"STMT_INSERT_ACTUAL_NODE", NULL}
+#define STMT_147 \
+ "INSERT OR REPLACE INTO actual_node ( " \
+ " wc_id, local_relpath, parent_relpath, properties, changelist, conflict_data) " \
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6) " \
+ ""
+
+#define STMT_UPDATE_ACTUAL_CONFLICT_DATA 148
+#define STMT_148_INFO {"STMT_UPDATE_ACTUAL_CONFLICT_DATA", NULL}
+#define STMT_148 \
+ "UPDATE actual_node SET conflict_data = ?3 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 " \
+ ""
+
+#define STMT_INSERT_ACTUAL_CONFLICT_DATA 149
+#define STMT_149_INFO {"STMT_INSERT_ACTUAL_CONFLICT_DATA", NULL}
+#define STMT_149 \
+ "INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) " \
+ "VALUES (?1, ?2, ?3, ?4) " \
+ ""
+
+#define STMT_SELECT_ALL_FILES 150
+#define STMT_150_INFO {"STMT_SELECT_ALL_FILES", NULL}
+#define STMT_150 \
+ "SELECT local_relpath FROM nodes_current " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 AND kind = 'file' " \
+ ""
+
+#define STMT_UPDATE_NODE_PROPS 151
+#define STMT_151_INFO {"STMT_UPDATE_NODE_PROPS", NULL}
+#define STMT_151 \
+ "UPDATE nodes SET properties = ?4 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_PRAGMA_TABLE_INFO_NODES 152
+#define STMT_152_INFO {"STMT_PRAGMA_TABLE_INFO_NODES", NULL}
+#define STMT_152 \
+ "PRAGMA table_info(\"NODES\") " \
+ ""
+
+#define STMT_CREATE_TARGET_PROP_CACHE 153
+#define STMT_153_INFO {"STMT_CREATE_TARGET_PROP_CACHE", NULL}
+#define STMT_153 \
+ "DROP TABLE IF EXISTS target_prop_cache; " \
+ "CREATE TEMPORARY TABLE target_prop_cache ( " \
+ " local_relpath TEXT NOT NULL PRIMARY KEY, " \
+ " kind TEXT NOT NULL, " \
+ " properties BLOB " \
+ "); " \
+ ""
+
+#define STMT_CACHE_TARGET_PROPS 154
+#define STMT_154_INFO {"STMT_CACHE_TARGET_PROPS", NULL}
+#define STMT_154 \
+ "INSERT INTO target_prop_cache(local_relpath, kind, properties) " \
+ " SELECT n.local_relpath, n.kind, " \
+ " IFNULL((SELECT properties FROM actual_node AS a " \
+ " WHERE a.wc_id = n.wc_id " \
+ " AND a.local_relpath = n.local_relpath), " \
+ " n.properties) " \
+ " FROM targets_list AS t " \
+ " JOIN nodes AS n " \
+ " ON n.wc_id = ?1 " \
+ " AND n.local_relpath = t.local_relpath " \
+ " AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 " \
+ " WHERE n3.wc_id = ?1 " \
+ " AND n3.local_relpath = t.local_relpath) " \
+ " WHERE t.wc_id = ?1 " \
+ " AND (presence='normal' OR presence='incomplete') " \
+ " ORDER BY t.local_relpath " \
+ ""
+
+#define STMT_CACHE_TARGET_PRISTINE_PROPS 155
+#define STMT_155_INFO {"STMT_CACHE_TARGET_PRISTINE_PROPS", NULL}
+#define STMT_155 \
+ "INSERT INTO target_prop_cache(local_relpath, kind, properties) " \
+ " SELECT n.local_relpath, n.kind, " \
+ " CASE n.presence " \
+ " WHEN 'base-deleted' " \
+ " THEN (SELECT properties FROM nodes AS p " \
+ " WHERE p.wc_id = n.wc_id " \
+ " AND p.local_relpath = n.local_relpath " \
+ " AND p.op_depth < n.op_depth " \
+ " ORDER BY p.op_depth DESC ) " \
+ " ELSE properties END " \
+ " FROM targets_list AS t " \
+ " JOIN nodes AS n " \
+ " ON n.wc_id = ?1 " \
+ " AND n.local_relpath = t.local_relpath " \
+ " AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 " \
+ " WHERE n3.wc_id = ?1 " \
+ " AND n3.local_relpath = t.local_relpath) " \
+ " WHERE t.wc_id = ?1 " \
+ " AND (presence = 'normal' " \
+ " OR presence = 'incomplete' " \
+ " OR presence = 'base-deleted') " \
+ " ORDER BY t.local_relpath " \
+ ""
+
+#define STMT_SELECT_ALL_TARGET_PROP_CACHE 156
+#define STMT_156_INFO {"STMT_SELECT_ALL_TARGET_PROP_CACHE", NULL}
+#define STMT_156 \
+ "SELECT local_relpath, properties FROM target_prop_cache " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_DROP_TARGET_PROP_CACHE 157
+#define STMT_157_INFO {"STMT_DROP_TARGET_PROP_CACHE", NULL}
+#define STMT_157 \
+ "DROP TABLE target_prop_cache; " \
+ ""
+
+#define STMT_CREATE_REVERT_LIST 158
+#define STMT_158_INFO {"STMT_CREATE_REVERT_LIST", NULL}
+#define STMT_158 \
+ "DROP TABLE IF EXISTS revert_list; " \
+ "CREATE TEMPORARY TABLE revert_list ( " \
+ " local_relpath TEXT NOT NULL, " \
+ " actual INTEGER NOT NULL, " \
+ " conflict_data BLOB, " \
+ " notify INTEGER, " \
+ " op_depth INTEGER, " \
+ " repos_id INTEGER, " \
+ " kind TEXT, " \
+ " PRIMARY KEY (local_relpath, actual) " \
+ " ); " \
+ "DROP TRIGGER IF EXISTS trigger_revert_list_nodes; " \
+ "CREATE TEMPORARY TRIGGER trigger_revert_list_nodes " \
+ "BEFORE DELETE ON nodes " \
+ "BEGIN " \
+ " INSERT OR REPLACE INTO revert_list(local_relpath, actual, op_depth, " \
+ " repos_id, kind) " \
+ " SELECT OLD.local_relpath, 0, OLD.op_depth, OLD.repos_id, OLD.kind; " \
+ "END; " \
+ "DROP TRIGGER IF EXISTS trigger_revert_list_actual_delete; " \
+ "CREATE TEMPORARY TRIGGER trigger_revert_list_actual_delete " \
+ "BEFORE DELETE ON actual_node " \
+ "BEGIN " \
+ " INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, " \
+ " notify) " \
+ " SELECT OLD.local_relpath, 1, OLD.conflict_data, " \
+ " CASE " \
+ " WHEN OLD.properties IS NOT NULL " \
+ " THEN 1 " \
+ " WHEN NOT EXISTS(SELECT 1 FROM NODES n " \
+ " WHERE n.wc_id = OLD.wc_id " \
+ " AND n.local_relpath = OLD.local_relpath) " \
+ " THEN 1 " \
+ " ELSE NULL " \
+ " END; " \
+ "END; " \
+ "DROP TRIGGER IF EXISTS trigger_revert_list_actual_update; " \
+ "CREATE TEMPORARY TRIGGER trigger_revert_list_actual_update " \
+ "BEFORE UPDATE ON actual_node " \
+ "BEGIN " \
+ " INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, " \
+ " notify) " \
+ " SELECT OLD.local_relpath, 1, OLD.conflict_data, " \
+ " CASE " \
+ " WHEN OLD.properties IS NOT NULL " \
+ " THEN 1 " \
+ " WHEN NOT EXISTS(SELECT 1 FROM NODES n " \
+ " WHERE n.wc_id = OLD.wc_id " \
+ " AND n.local_relpath = OLD.local_relpath) " \
+ " THEN 1 " \
+ " ELSE NULL " \
+ " END; " \
+ "END " \
+ ""
+
+#define STMT_DROP_REVERT_LIST_TRIGGERS 159
+#define STMT_159_INFO {"STMT_DROP_REVERT_LIST_TRIGGERS", NULL}
+#define STMT_159 \
+ "DROP TRIGGER trigger_revert_list_nodes; " \
+ "DROP TRIGGER trigger_revert_list_actual_delete; " \
+ "DROP TRIGGER trigger_revert_list_actual_update " \
+ ""
+
+#define STMT_SELECT_REVERT_LIST 160
+#define STMT_160_INFO {"STMT_SELECT_REVERT_LIST", NULL}
+#define STMT_160 \
+ "SELECT actual, notify, kind, op_depth, repos_id, conflict_data " \
+ "FROM revert_list " \
+ "WHERE local_relpath = ?1 " \
+ "ORDER BY actual DESC " \
+ ""
+
+#define STMT_SELECT_REVERT_LIST_COPIED_CHILDREN 161
+#define STMT_161_INFO {"STMT_SELECT_REVERT_LIST_COPIED_CHILDREN", NULL}
+#define STMT_161 \
+ "SELECT local_relpath, kind " \
+ "FROM revert_list " \
+ "WHERE (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END)) " \
+ " AND op_depth >= ?2 " \
+ " AND repos_id IS NOT NULL " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_DELETE_REVERT_LIST 162
+#define STMT_162_INFO {"STMT_DELETE_REVERT_LIST", NULL}
+#define STMT_162 \
+ "DELETE FROM revert_list WHERE local_relpath = ?1 " \
+ ""
+
+#define STMT_SELECT_REVERT_LIST_RECURSIVE 163
+#define STMT_163_INFO {"STMT_SELECT_REVERT_LIST_RECURSIVE", NULL}
+#define STMT_163 \
+ "SELECT DISTINCT local_relpath " \
+ "FROM revert_list " \
+ "WHERE (local_relpath = ?1 " \
+ " OR (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END))) " \
+ " AND (notify OR actual = 0) " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_DELETE_REVERT_LIST_RECURSIVE 164
+#define STMT_164_INFO {"STMT_DELETE_REVERT_LIST_RECURSIVE", NULL}
+#define STMT_164 \
+ "DELETE FROM revert_list " \
+ "WHERE (local_relpath = ?1 " \
+ " OR (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END))) " \
+ ""
+
+#define STMT_DROP_REVERT_LIST 165
+#define STMT_165_INFO {"STMT_DROP_REVERT_LIST", NULL}
+#define STMT_165 \
+ "DROP TABLE IF EXISTS revert_list " \
+ ""
+
+#define STMT_CREATE_DELETE_LIST 166
+#define STMT_166_INFO {"STMT_CREATE_DELETE_LIST", NULL}
+#define STMT_166 \
+ "DROP TABLE IF EXISTS delete_list; " \
+ "CREATE TEMPORARY TABLE delete_list ( " \
+ " local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE " \
+ " ) " \
+ ""
+
+#define STMT_INSERT_DELETE_LIST 167
+#define STMT_167_INFO {"STMT_INSERT_DELETE_LIST", NULL}
+#define STMT_167 \
+ "INSERT INTO delete_list(local_relpath) " \
+ "SELECT local_relpath FROM nodes AS n " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth >= ?3 " \
+ " AND op_depth = (SELECT MAX(s.op_depth) FROM nodes AS s " \
+ " WHERE s.wc_id = ?1 " \
+ " AND s.local_relpath = n.local_relpath) " \
+ " AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'server-excluded') " \
+ " AND file_external IS NULL " \
+ ""
+
+#define STMT_SELECT_DELETE_LIST 168
+#define STMT_168_INFO {"STMT_SELECT_DELETE_LIST", NULL}
+#define STMT_168 \
+ "SELECT local_relpath FROM delete_list " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_FINALIZE_DELETE 169
+#define STMT_169_INFO {"STMT_FINALIZE_DELETE", NULL}
+#define STMT_169 \
+ "DROP TABLE IF EXISTS delete_list " \
+ ""
+
+#define STMT_CREATE_UPDATE_MOVE_LIST 170
+#define STMT_170_INFO {"STMT_CREATE_UPDATE_MOVE_LIST", NULL}
+#define STMT_170 \
+ "DROP TABLE IF EXISTS update_move_list; " \
+ "CREATE TEMPORARY TABLE update_move_list ( " \
+ " local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE, " \
+ " action INTEGER NOT NULL, " \
+ " kind INTEGER NOT NULL, " \
+ " content_state INTEGER NOT NULL, " \
+ " prop_state INTEGER NOT NULL " \
+ " ) " \
+ ""
+
+#define STMT_INSERT_UPDATE_MOVE_LIST 171
+#define STMT_171_INFO {"STMT_INSERT_UPDATE_MOVE_LIST", NULL}
+#define STMT_171 \
+ "INSERT INTO update_move_list(local_relpath, action, kind, content_state, " \
+ " prop_state) " \
+ "VALUES (?1, ?2, ?3, ?4, ?5) " \
+ ""
+
+#define STMT_SELECT_UPDATE_MOVE_LIST 172
+#define STMT_172_INFO {"STMT_SELECT_UPDATE_MOVE_LIST", NULL}
+#define STMT_172 \
+ "SELECT local_relpath, action, kind, content_state, prop_state " \
+ "FROM update_move_list " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_FINALIZE_UPDATE_MOVE 173
+#define STMT_173_INFO {"STMT_FINALIZE_UPDATE_MOVE", NULL}
+#define STMT_173 \
+ "DROP TABLE IF EXISTS update_move_list " \
+ ""
+
+#define STMT_SELECT_MIN_MAX_REVISIONS 174
+#define STMT_174_INFO {"STMT_SELECT_MIN_MAX_REVISIONS", NULL}
+#define STMT_174 \
+ "SELECT MIN(revision), MAX(revision), " \
+ " MIN(changed_revision), MAX(changed_revision) FROM nodes " \
+ " WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND presence IN ('normal', 'incomplete') " \
+ " AND file_external IS NULL " \
+ " AND op_depth = 0 " \
+ ""
+
+#define STMT_HAS_SPARSE_NODES 175
+#define STMT_175_INFO {"STMT_HAS_SPARSE_NODES", NULL}
+#define STMT_175 \
+ "SELECT 1 FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = 0 " \
+ " AND (presence IN ('server-excluded', 'excluded') " \
+ " OR depth NOT IN ('infinity', 'unknown')) " \
+ " AND file_external IS NULL " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SUBTREE_HAS_TREE_MODIFICATIONS 176
+#define STMT_176_INFO {"STMT_SUBTREE_HAS_TREE_MODIFICATIONS", NULL}
+#define STMT_176 \
+ "SELECT 1 FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth > 0 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SUBTREE_HAS_PROP_MODIFICATIONS 177
+#define STMT_177_INFO {"STMT_SUBTREE_HAS_PROP_MODIFICATIONS", NULL}
+#define STMT_177 \
+ "SELECT 1 FROM actual_node " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND properties IS NOT NULL " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_HAS_SWITCHED 178
+#define STMT_178_INFO {"STMT_HAS_SWITCHED", NULL}
+#define STMT_178 \
+ "SELECT 1 " \
+ "FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND file_external IS NULL " \
+ " AND presence IN ('normal', 'incomplete') " \
+ " AND repos_path IS NOT (CASE WHEN (?2) = '' THEN (CASE WHEN (?3) = '' THEN (local_relpath) WHEN (local_relpath) = '' THEN (?3) ELSE (?3) || '/' || (local_relpath) END) WHEN (?3) = '' THEN (CASE WHEN (?2) = '' THEN (local_relpath) WHEN SUBSTR((local_relpath), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(local_relpath) THEN '' WHEN SUBSTR((local_relpath), LENGTH(?2)+1, 1) = '/' THEN SUBSTR((local_relpath), LENGTH(?2)+2) END END) WHEN SUBSTR((local_relpath), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(local_relpath) THEN (?3) WHEN SUBSTR((local_relpath), LENGTH(?2)+1, 1) = '/' THEN (?3) || SUBSTR((local_relpath), LENGTH(?2)+1) END END) " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_SELECT_BASE_FILES_RECURSIVE 179
+#define STMT_179_INFO {"STMT_SELECT_BASE_FILES_RECURSIVE", NULL}
+#define STMT_179 \
+ "SELECT local_relpath, translated_size, last_mod_time FROM nodes AS n " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = 0 " \
+ " AND kind='file' " \
+ " AND presence='normal' " \
+ " AND file_external IS NULL " \
+ ""
+
+#define STMT_SELECT_MOVED_FROM_RELPATH 180
+#define STMT_180_INFO {"STMT_SELECT_MOVED_FROM_RELPATH", NULL}
+#define STMT_180 \
+ "SELECT local_relpath, op_depth FROM nodes " \
+ "WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth > 0 " \
+ ""
+
+#define STMT_UPDATE_MOVED_TO_RELPATH 181
+#define STMT_181_INFO {"STMT_UPDATE_MOVED_TO_RELPATH", NULL}
+#define STMT_181 \
+ "UPDATE nodes SET moved_to = ?4 " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_CLEAR_MOVED_TO_RELPATH 182
+#define STMT_182_INFO {"STMT_CLEAR_MOVED_TO_RELPATH", NULL}
+#define STMT_182 \
+ "UPDATE nodes SET moved_to = NULL " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \
+ ""
+
+#define STMT_CLEAR_MOVED_HERE_RECURSIVE 183
+#define STMT_183_INFO {"STMT_CLEAR_MOVED_HERE_RECURSIVE", NULL}
+#define STMT_183 \
+ "UPDATE nodes SET moved_here = NULL " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_SELECT_MOVED_HERE_CHILDREN 184
+#define STMT_184_INFO {"STMT_SELECT_MOVED_HERE_CHILDREN", NULL}
+#define STMT_184 \
+ "SELECT moved_to, local_relpath FROM nodes " \
+ "WHERE wc_id = ?1 AND op_depth > 0 " \
+ " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_SELECT_MOVED_FOR_DELETE 185
+#define STMT_185_INFO {"STMT_SELECT_MOVED_FOR_DELETE", NULL}
+#define STMT_185 \
+ "SELECT local_relpath, moved_to, op_depth FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND moved_to IS NOT NULL " \
+ " AND op_depth >= (SELECT MAX(op_depth) FROM nodes o " \
+ " WHERE o.wc_id = ?1 " \
+ " AND o.local_relpath = ?2) " \
+ ""
+
+#define STMT_UPDATE_MOVED_TO_DESCENDANTS 186
+#define STMT_186_INFO {"STMT_UPDATE_MOVED_TO_DESCENDANTS", NULL}
+#define STMT_186 \
+ "UPDATE nodes SET moved_to = (CASE WHEN (?2) = '' THEN (CASE WHEN (?3) = '' THEN (moved_to) WHEN (moved_to) = '' THEN (?3) ELSE (?3) || '/' || (moved_to) END) WHEN (?3) = '' THEN (CASE WHEN (?2) = '' THEN (moved_to) WHEN SUBSTR((moved_to), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(moved_to) THEN '' WHEN SUBSTR((moved_to), LENGTH(?2)+1, 1) = '/' THEN SUBSTR((moved_to), LENGTH(?2)+2) END END) WHEN SUBSTR((moved_to), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(moved_to) THEN (?3) WHEN SUBSTR((moved_to), LENGTH(?2)+1, 1) = '/' THEN (?3) || SUBSTR((moved_to), LENGTH(?2)+1) END END) " \
+ " WHERE wc_id = ?1 " \
+ " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_CLEAR_MOVED_TO_DESCENDANTS 187
+#define STMT_187_INFO {"STMT_CLEAR_MOVED_TO_DESCENDANTS", NULL}
+#define STMT_187 \
+ "UPDATE nodes SET moved_to = NULL " \
+ " WHERE wc_id = ?1 " \
+ " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_SELECT_MOVED_PAIR2 188
+#define STMT_188_INFO {"STMT_SELECT_MOVED_PAIR2", NULL}
+#define STMT_188 \
+ "SELECT local_relpath, moved_to, op_depth FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND moved_to IS NOT NULL " \
+ " AND NOT (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth >= (SELECT MAX(op_depth) FROM nodes o " \
+ " WHERE o.wc_id = ?1 " \
+ " AND o.local_relpath = ?2) " \
+ ""
+
+#define STMT_SELECT_MOVED_PAIR3 189
+#define STMT_189_INFO {"STMT_SELECT_MOVED_PAIR3", NULL}
+#define STMT_189 \
+ "SELECT local_relpath, moved_to, op_depth, kind FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth > ?3 " \
+ " AND moved_to IS NOT NULL " \
+ ""
+
+#define STMT_SELECT_MOVED_OUTSIDE 190
+#define STMT_190_INFO {"STMT_SELECT_MOVED_OUTSIDE", NULL}
+#define STMT_190 \
+ "SELECT local_relpath, moved_to FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth >= ?3 " \
+ " AND moved_to IS NOT NULL " \
+ " AND NOT (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ ""
+
+#define STMT_SELECT_OP_DEPTH_MOVED_PAIR 191
+#define STMT_191_INFO {"STMT_SELECT_OP_DEPTH_MOVED_PAIR", NULL}
+#define STMT_191 \
+ "SELECT n.local_relpath, n.moved_to, " \
+ " (SELECT o.repos_path FROM nodes AS o " \
+ " WHERE o.wc_id = n.wc_id " \
+ " AND o.local_relpath = n.local_relpath " \
+ " AND o.op_depth < ?3 ORDER BY o.op_depth DESC LIMIT 1) " \
+ "FROM nodes AS n " \
+ "WHERE n.wc_id = ?1 " \
+ " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND n.op_depth = ?3 " \
+ " AND n.moved_to IS NOT NULL " \
+ ""
+
+#define STMT_SELECT_MOVED_DESCENDANTS 192
+#define STMT_192_INFO {"STMT_SELECT_MOVED_DESCENDANTS", NULL}
+#define STMT_192 \
+ "SELECT n.local_relpath, h.moved_to " \
+ "FROM nodes n, nodes h " \
+ "WHERE n.wc_id = ?1 " \
+ " AND h.wc_id = ?1 " \
+ " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND h.local_relpath = n.local_relpath " \
+ " AND n.op_depth = ?3 " \
+ " AND h.op_depth = (SELECT MIN(o.op_depth) " \
+ " FROM nodes o " \
+ " WHERE o.wc_id = ?1 " \
+ " AND o.local_relpath = n.local_relpath " \
+ " AND o.op_depth > ?3) " \
+ " AND h.moved_to IS NOT NULL " \
+ ""
+
+#define STMT_COMMIT_UPDATE_ORIGIN 193
+#define STMT_193_INFO {"STMT_COMMIT_UPDATE_ORIGIN", NULL}
+#define STMT_193 \
+ "UPDATE nodes SET repos_id = ?4, " \
+ " repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), " \
+ " revision = ?6 " \
+ "WHERE wc_id = ?1 " \
+ " AND (local_relpath = ?2 " \
+ " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \
+ " AND op_depth = ?3 " \
+ ""
+
+#define STMT_HAS_LAYER_BETWEEN 194
+#define STMT_194_INFO {"STMT_HAS_LAYER_BETWEEN", NULL}
+#define STMT_194 \
+ "SELECT 1 FROM NODES " \
+ "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 AND op_depth < ?4 " \
+ ""
+
+#define STMT_SELECT_REPOS_PATH_REVISION 195
+#define STMT_195_INFO {"STMT_SELECT_REPOS_PATH_REVISION", NULL}
+#define STMT_195 \
+ "SELECT local_relpath, repos_path, revision FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ "ORDER BY local_relpath " \
+ ""
+
+#define STMT_SELECT_HAS_NON_FILE_CHILDREN 196
+#define STMT_196_INFO {"STMT_SELECT_HAS_NON_FILE_CHILDREN", NULL}
+#define STMT_196 \
+ "SELECT 1 FROM nodes " \
+ "WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 AND kind != 'file' " \
+ ""
+
+#define STMT_SELECT_HAS_GRANDCHILDREN 197
+#define STMT_197_INFO {"STMT_SELECT_HAS_GRANDCHILDREN", NULL}
+#define STMT_197 \
+ "SELECT 1 FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((parent_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((parent_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND file_external IS NULL " \
+ ""
+
+#define STMT_SELECT_ALL_NODES 198
+#define STMT_198_INFO {"STMT_SELECT_ALL_NODES", NULL}
+#define STMT_198 \
+ "SELECT op_depth, local_relpath, parent_relpath, file_external FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ ""
+
+#define STMT_SELECT_IPROPS 199
+#define STMT_199_INFO {"STMT_SELECT_IPROPS", NULL}
+#define STMT_199 \
+ "SELECT inherited_props FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND local_relpath = ?2 " \
+ " AND op_depth = 0 " \
+ ""
+
+#define STMT_UPDATE_IPROP 200
+#define STMT_200_INFO {"STMT_UPDATE_IPROP", NULL}
+#define STMT_200 \
+ "UPDATE nodes " \
+ "SET inherited_props = ?3 " \
+ "WHERE (wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0) " \
+ ""
+
+#define STMT_SELECT_IPROPS_NODE 201
+#define STMT_201_INFO {"STMT_SELECT_IPROPS_NODE", NULL}
+#define STMT_201 \
+ "SELECT local_relpath, repos_path FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND local_relpath = ?2 " \
+ " AND op_depth = 0 " \
+ " AND (inherited_props not null) " \
+ ""
+
+#define STMT_SELECT_IPROPS_RECURSIVE 202
+#define STMT_202_INFO {"STMT_SELECT_IPROPS_RECURSIVE", NULL}
+#define STMT_202 \
+ "SELECT local_relpath, repos_path FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \
+ " AND op_depth = 0 " \
+ " AND (inherited_props not null) " \
+ ""
+
+#define STMT_SELECT_IPROPS_CHILDREN 203
+#define STMT_203_INFO {"STMT_SELECT_IPROPS_CHILDREN", NULL}
+#define STMT_203 \
+ "SELECT local_relpath, repos_path FROM nodes " \
+ "WHERE wc_id = ?1 " \
+ " AND parent_relpath = ?2 " \
+ " AND op_depth = 0 " \
+ " AND (inherited_props not null) " \
+ ""
+
+#define STMT_CREATE_SCHEMA 204
+#define STMT_204_INFO {"STMT_CREATE_SCHEMA", NULL}
+#define STMT_204 \
+ "CREATE TABLE REPOSITORY ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " root TEXT UNIQUE NOT NULL, " \
+ " uuid TEXT NOT NULL " \
+ " ); " \
+ "CREATE INDEX I_UUID ON REPOSITORY (uuid); " \
+ "CREATE INDEX I_ROOT ON REPOSITORY (root); " \
+ "CREATE TABLE WCROOT ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " local_abspath TEXT UNIQUE " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (local_abspath); " \
+ "CREATE TABLE PRISTINE ( " \
+ " checksum TEXT NOT NULL PRIMARY KEY, " \
+ " compression INTEGER, " \
+ " size INTEGER NOT NULL, " \
+ " refcount INTEGER NOT NULL, " \
+ " md5_checksum TEXT NOT NULL " \
+ " ); " \
+ "CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \
+ "CREATE TABLE ACTUAL_NODE ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT, " \
+ " tree_conflict_data TEXT, " \
+ " conflict_data BLOB, " \
+ " older_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " left_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " right_checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "CREATE TABLE LOCK ( " \
+ " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \
+ " repos_relpath TEXT NOT NULL, " \
+ " lock_token TEXT NOT NULL, " \
+ " lock_owner TEXT, " \
+ " lock_comment TEXT, " \
+ " lock_date INTEGER, " \
+ " PRIMARY KEY (repos_id, repos_relpath) " \
+ " ); " \
+ "CREATE TABLE WORK_QUEUE ( " \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT, " \
+ " work BLOB NOT NULL " \
+ " ); " \
+ "CREATE TABLE WC_LOCK ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_dir_relpath TEXT NOT NULL, " \
+ " locked_levels INTEGER NOT NULL DEFAULT -1, " \
+ " PRIMARY KEY (wc_id, local_dir_relpath) " \
+ " ); " \
+ "PRAGMA user_version = " \
+ APR_STRINGIFY(SVN_WC__VERSION) \
+ "; " \
+ ""
+
+#define STMT_CREATE_NODES 205
+#define STMT_205_INFO {"STMT_CREATE_NODES", NULL}
+#define STMT_205 \
+ "CREATE TABLE NODES ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " op_depth INTEGER NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " repos_id INTEGER REFERENCES REPOSITORY (id), " \
+ " repos_path TEXT, " \
+ " revision INTEGER, " \
+ " presence TEXT NOT NULL, " \
+ " moved_here INTEGER, " \
+ " moved_to TEXT, " \
+ " kind TEXT NOT NULL, " \
+ " properties BLOB, " \
+ " depth TEXT, " \
+ " checksum TEXT REFERENCES PRISTINE (checksum), " \
+ " symlink_target TEXT, " \
+ " changed_revision INTEGER, " \
+ " changed_date INTEGER, " \
+ " changed_author TEXT, " \
+ " translated_size INTEGER, " \
+ " last_mod_time INTEGER, " \
+ " dav_cache BLOB, " \
+ " file_external INTEGER, " \
+ " inherited_props BLOB, " \
+ " PRIMARY KEY (wc_id, local_relpath, op_depth) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth); " \
+ "CREATE VIEW NODES_CURRENT AS " \
+ " SELECT * FROM nodes AS n " \
+ " WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2 " \
+ " WHERE n2.wc_id = n.wc_id " \
+ " AND n2.local_relpath = n.local_relpath); " \
+ "CREATE VIEW NODES_BASE AS " \
+ " SELECT * FROM nodes " \
+ " WHERE op_depth = 0; " \
+ ""
+
+#define STMT_CREATE_NODES_TRIGGERS 206
+#define STMT_206_INFO {"STMT_CREATE_NODES_TRIGGERS", NULL}
+#define STMT_206 \
+ "CREATE TRIGGER nodes_insert_trigger " \
+ "AFTER INSERT ON nodes " \
+ "WHEN NEW.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_delete_trigger " \
+ "AFTER DELETE ON nodes " \
+ "WHEN OLD.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_update_checksum_trigger " \
+ "AFTER UPDATE OF checksum ON nodes " \
+ "WHEN NEW.checksum IS NOT OLD.checksum " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ ""
+
+#define STMT_CREATE_EXTERNALS 207
+#define STMT_207_INFO {"STMT_CREATE_EXTERNALS", NULL}
+#define STMT_207 \
+ "CREATE TABLE EXTERNALS ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT NOT NULL, " \
+ " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \
+ " presence TEXT NOT NULL, " \
+ " kind TEXT NOT NULL, " \
+ " def_local_relpath TEXT NOT NULL, " \
+ " def_repos_relpath TEXT NOT NULL, " \
+ " def_operational_revision TEXT, " \
+ " def_revision TEXT, " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ "); " \
+ "CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, " \
+ " def_local_relpath, " \
+ " local_relpath); " \
+ ""
+
+#define STMT_UPGRADE_TO_20 208
+#define STMT_208_INFO {"STMT_UPGRADE_TO_20", NULL}
+#define STMT_208 \
+ "UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = BASE_NODE.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum); " \
+ "UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = WORKING_NODE.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine " \
+ " WHERE md5_checksum = WORKING_NODE.checksum); " \
+ "INSERT INTO NODES ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, " \
+ " repos_id, repos_path, revision, " \
+ " presence, depth, moved_here, moved_to, kind, " \
+ " changed_revision, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external ) " \
+ "SELECT wc_id, local_relpath, 0 , parent_relpath, " \
+ " repos_id, repos_relpath, revnum, " \
+ " presence, depth, NULL , NULL , kind, " \
+ " changed_rev, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external " \
+ "FROM BASE_NODE; " \
+ "INSERT INTO NODES ( " \
+ " wc_id, local_relpath, op_depth, parent_relpath, " \
+ " repos_id, repos_path, revision, " \
+ " presence, depth, moved_here, moved_to, kind, " \
+ " changed_revision, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " dav_cache, symlink_target, file_external ) " \
+ "SELECT wc_id, local_relpath, 2 , parent_relpath, " \
+ " copyfrom_repos_id, copyfrom_repos_path, copyfrom_revnum, " \
+ " presence, depth, NULL , NULL , kind, " \
+ " changed_rev, changed_date, changed_author, " \
+ " checksum, properties, translated_size, last_mod_time, " \
+ " NULL , symlink_target, NULL " \
+ "FROM WORKING_NODE; " \
+ "DROP TABLE BASE_NODE; " \
+ "DROP TABLE WORKING_NODE; " \
+ "PRAGMA user_version = 20; " \
+ ""
+
+#define STMT_UPGRADE_TO_21 209
+#define STMT_209_INFO {"STMT_UPGRADE_TO_21", NULL}
+#define STMT_209 \
+ "PRAGMA user_version = 21; " \
+ ""
+
+#define STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT 210
+#define STMT_210_INFO {"STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT", NULL}
+#define STMT_210 \
+ "SELECT wc_id, local_relpath, tree_conflict_data " \
+ "FROM actual_node " \
+ "WHERE tree_conflict_data IS NOT NULL " \
+ ""
+
+#define STMT_UPGRADE_21_ERASE_OLD_CONFLICTS 211
+#define STMT_211_INFO {"STMT_UPGRADE_21_ERASE_OLD_CONFLICTS", NULL}
+#define STMT_211 \
+ "UPDATE actual_node SET tree_conflict_data = NULL " \
+ ""
+
+#define STMT_UPGRADE_TO_22 212
+#define STMT_212_INFO {"STMT_UPGRADE_TO_22", NULL}
+#define STMT_212 \
+ "UPDATE actual_node SET tree_conflict_data = conflict_data; " \
+ "UPDATE actual_node SET conflict_data = NULL; " \
+ "PRAGMA user_version = 22; " \
+ ""
+
+#define STMT_UPGRADE_TO_23 213
+#define STMT_213_INFO {"STMT_UPGRADE_TO_23", NULL}
+#define STMT_213 \
+ "PRAGMA user_version = 23; " \
+ ""
+
+#define STMT_UPGRADE_23_HAS_WORKING_NODES 214
+#define STMT_214_INFO {"STMT_UPGRADE_23_HAS_WORKING_NODES", NULL}
+#define STMT_214 \
+ "SELECT 1 FROM nodes WHERE op_depth > 0 " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_UPGRADE_TO_24 215
+#define STMT_215_INFO {"STMT_UPGRADE_TO_24", NULL}
+#define STMT_215 \
+ "UPDATE pristine SET refcount = " \
+ " (SELECT COUNT(*) FROM nodes " \
+ " WHERE checksum = pristine.checksum ); " \
+ "PRAGMA user_version = 24; " \
+ ""
+
+#define STMT_UPGRADE_TO_25 216
+#define STMT_216_INFO {"STMT_UPGRADE_TO_25", NULL}
+#define STMT_216 \
+ "DROP VIEW IF EXISTS NODES_CURRENT; " \
+ "CREATE VIEW NODES_CURRENT AS " \
+ " SELECT * FROM nodes " \
+ " JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes " \
+ " GROUP BY wc_id, local_relpath) AS filter " \
+ " ON nodes.wc_id = filter.wc_id " \
+ " AND nodes.local_relpath = filter.local_relpath " \
+ " AND nodes.op_depth = filter.op_depth; " \
+ "PRAGMA user_version = 25; " \
+ ""
+
+#define STMT_UPGRADE_TO_26 217
+#define STMT_217_INFO {"STMT_UPGRADE_TO_26", NULL}
+#define STMT_217 \
+ "DROP VIEW IF EXISTS NODES_BASE; " \
+ "CREATE VIEW NODES_BASE AS " \
+ " SELECT * FROM nodes " \
+ " WHERE op_depth = 0; " \
+ "PRAGMA user_version = 26; " \
+ ""
+
+#define STMT_UPGRADE_TO_27 218
+#define STMT_218_INFO {"STMT_UPGRADE_TO_27", NULL}
+#define STMT_218 \
+ "PRAGMA user_version = 27; " \
+ ""
+
+#define STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS 219
+#define STMT_219_INFO {"STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS", NULL}
+#define STMT_219 \
+ "SELECT 1 FROM actual_node " \
+ "WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) " \
+ " AND (conflict_new IS NULL) AND (conflict_working IS NULL) " \
+ " AND (tree_conflict_data IS NULL)) " \
+ "LIMIT 1 " \
+ ""
+
+#define STMT_UPGRADE_TO_28 220
+#define STMT_220_INFO {"STMT_UPGRADE_TO_28", NULL}
+#define STMT_220 \
+ "UPDATE NODES SET checksum = (SELECT checksum FROM pristine " \
+ " WHERE md5_checksum = nodes.checksum) " \
+ "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum); " \
+ "PRAGMA user_version = 28; " \
+ ""
+
+#define STMT_UPGRADE_TO_29 221
+#define STMT_221_INFO {"STMT_UPGRADE_TO_29", NULL}
+#define STMT_221 \
+ "DROP TRIGGER IF EXISTS nodes_update_checksum_trigger; " \
+ "DROP TRIGGER IF EXISTS nodes_insert_trigger; " \
+ "DROP TRIGGER IF EXISTS nodes_delete_trigger; " \
+ "CREATE TRIGGER nodes_update_checksum_trigger " \
+ "AFTER UPDATE OF checksum ON nodes " \
+ "WHEN NEW.checksum IS NOT OLD.checksum " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_insert_trigger " \
+ "AFTER INSERT ON nodes " \
+ "WHEN NEW.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount + 1 " \
+ " WHERE checksum = NEW.checksum; " \
+ "END; " \
+ "CREATE TRIGGER nodes_delete_trigger " \
+ "AFTER DELETE ON nodes " \
+ "WHEN OLD.checksum IS NOT NULL " \
+ "BEGIN " \
+ " UPDATE pristine SET refcount = refcount - 1 " \
+ " WHERE checksum = OLD.checksum; " \
+ "END; " \
+ "PRAGMA user_version = 29; " \
+ ""
+
+#define STMT_UPGRADE_TO_30 222
+#define STMT_222_INFO {"STMT_UPGRADE_TO_30", NULL}
+#define STMT_222 \
+ "CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED " \
+ "ON NODES (wc_id, moved_to, op_depth); " \
+ "CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \
+ "UPDATE nodes SET presence = \"server-excluded\" WHERE presence = \"absent\"; " \
+ "UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL; " \
+ ""
+
+#define STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE 223
+#define STMT_223_INFO {"STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE", NULL}
+#define STMT_223 \
+ "SELECT wc_id, local_relpath, " \
+ " conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data " \
+ "FROM actual_node " \
+ "WHERE conflict_old IS NOT NULL " \
+ " OR conflict_working IS NOT NULL " \
+ " OR conflict_new IS NOT NULL " \
+ " OR prop_reject IS NOT NULL " \
+ " OR tree_conflict_data IS NOT NULL " \
+ "ORDER by wc_id, local_relpath " \
+ ""
+
+#define STMT_UPGRADE_30_SET_CONFLICT 224
+#define STMT_224_INFO {"STMT_UPGRADE_30_SET_CONFLICT", NULL}
+#define STMT_224 \
+ "UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL, " \
+ " conflict_working = NULL, conflict_new = NULL, prop_reject = NULL, " \
+ " tree_conflict_data = NULL " \
+ "WHERE wc_id = ?1 and local_relpath = ?2 " \
+ ""
+
+#define STMT_UPGRADE_TO_31_ALTER_TABLE 225
+#define STMT_225_INFO {"STMT_UPGRADE_TO_31_ALTER_TABLE", NULL}
+#define STMT_225 \
+ "ALTER TABLE NODES ADD COLUMN inherited_props BLOB; " \
+ ""
+
+#define STMT_UPGRADE_TO_31_FINALIZE 226
+#define STMT_226_INFO {"STMT_UPGRADE_TO_31_FINALIZE", NULL}
+#define STMT_226 \
+ "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \
+ "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \
+ "DROP INDEX I_NODES_PARENT; " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "DROP INDEX I_ACTUAL_PARENT; " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "PRAGMA user_version = 31; " \
+ ""
+
+#define STMT_UPGRADE_31_SELECT_WCROOT_NODES 227
+#define STMT_227_INFO {"STMT_UPGRADE_31_SELECT_WCROOT_NODES", NULL}
+#define STMT_227 \
+ "SELECT l.wc_id, l.local_relpath FROM nodes as l " \
+ "LEFT OUTER JOIN nodes as r " \
+ "ON l.wc_id = r.wc_id " \
+ " AND r.local_relpath = l.parent_relpath " \
+ " AND r.op_depth = 0 " \
+ "WHERE l.op_depth = 0 " \
+ " AND l.repos_path != '' " \
+ " AND ((l.repos_id IS NOT r.repos_id) " \
+ " OR (l.repos_path IS NOT (CASE WHEN (r.local_relpath) = '' THEN (CASE WHEN (r.repos_path) = '' THEN (l.local_relpath) WHEN (l.local_relpath) = '' THEN (r.repos_path) ELSE (r.repos_path) || '/' || (l.local_relpath) END) WHEN (r.repos_path) = '' THEN (CASE WHEN (r.local_relpath) = '' THEN (l.local_relpath) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN '' WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+2) END END) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN (r.repos_path) WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN (r.repos_path) || SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1) END END))) " \
+ ""
+
+#define STMT_UPGRADE_TO_32 228
+#define STMT_228_INFO {"STMT_UPGRADE_TO_32", NULL}
+#define STMT_228 \
+ "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \
+ "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \
+ "CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); " \
+ "DROP INDEX I_NODES_PARENT; " \
+ "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \
+ " local_relpath, op_depth); " \
+ "DROP INDEX I_ACTUAL_PARENT; " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "-- format: YYY " \
+ ""
+
+#define WC_QUERIES_SQL_99 \
+ "CREATE TABLE ACTUAL_NODE_BACKUP ( " \
+ " wc_id INTEGER NOT NULL, " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT " \
+ " ); " \
+ "INSERT INTO ACTUAL_NODE_BACKUP SELECT " \
+ " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \
+ " conflict_new, conflict_working, prop_reject, changelist, text_mod " \
+ "FROM ACTUAL_NODE; " \
+ "DROP TABLE ACTUAL_NODE; " \
+ "CREATE TABLE ACTUAL_NODE ( " \
+ " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \
+ " local_relpath TEXT NOT NULL, " \
+ " parent_relpath TEXT, " \
+ " properties BLOB, " \
+ " conflict_old TEXT, " \
+ " conflict_new TEXT, " \
+ " conflict_working TEXT, " \
+ " prop_reject TEXT, " \
+ " changelist TEXT, " \
+ " text_mod TEXT, " \
+ " PRIMARY KEY (wc_id, local_relpath) " \
+ " ); " \
+ "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \
+ " local_relpath); " \
+ "INSERT INTO ACTUAL_NODE SELECT " \
+ " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \
+ " conflict_new, conflict_working, prop_reject, changelist, text_mod " \
+ "FROM ACTUAL_NODE_BACKUP; " \
+ "DROP TABLE ACTUAL_NODE_BACKUP; " \
+ ""
+
+#define STMT_VERIFICATION_TRIGGERS 229
+#define STMT_229_INFO {"STMT_VERIFICATION_TRIGGERS", NULL}
+#define STMT_229 \
+ "CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'Updates to REPOSITORY are not allowed.'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_01 BEFORE INSERT ON nodes " \
+ "WHEN NOT ((new.local_relpath = '' AND new.parent_relpath IS NULL) " \
+ " OR (relpath_depth(new.local_relpath) " \
+ " = relpath_depth(new.parent_relpath) + 1)) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 01 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_02 BEFORE INSERT ON nodes " \
+ "WHEN NOT new.op_depth <= relpath_depth(new.local_relpath) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 02 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_03 BEFORE INSERT ON nodes " \
+ "WHEN NOT ( " \
+ " (new.op_depth = relpath_depth(new.local_relpath)) " \
+ " OR " \
+ " (EXISTS (SELECT 1 FROM nodes " \
+ " WHERE wc_id = new.wc_id AND op_depth = new.op_depth " \
+ " AND local_relpath = new.parent_relpath)) " \
+ " ) " \
+ " AND NOT (new.file_external IS NOT NULL AND new.op_depth = 0) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 03 failed'); " \
+ "END; " \
+ "CREATE TEMPORARY TRIGGER validation_04 BEFORE INSERT ON actual_node " \
+ "WHEN NOT (new.local_relpath = '' " \
+ " OR EXISTS (SELECT 1 FROM nodes " \
+ " WHERE wc_id = new.wc_id " \
+ " AND local_relpath = new.parent_relpath)) " \
+ "BEGIN " \
+ " SELECT RAISE(FAIL, 'WC DB validity check 04 failed'); " \
+ "END; " \
+ ""
+
+#define WC_QUERIES_SQL_DECLARE_STATEMENTS(varname) \
+ static const char * const varname[] = { \
+ STMT_0, \
+ STMT_1, \
+ STMT_2, \
+ STMT_3, \
+ STMT_4, \
+ STMT_5, \
+ STMT_6, \
+ STMT_7, \
+ STMT_8, \
+ STMT_9, \
+ STMT_10, \
+ STMT_11, \
+ STMT_12, \
+ STMT_13, \
+ STMT_14, \
+ STMT_15, \
+ STMT_16, \
+ STMT_17, \
+ STMT_18, \
+ STMT_19, \
+ STMT_20, \
+ STMT_21, \
+ STMT_22, \
+ STMT_23, \
+ STMT_24, \
+ STMT_25, \
+ STMT_26, \
+ STMT_27, \
+ STMT_28, \
+ STMT_29, \
+ STMT_30, \
+ STMT_31, \
+ STMT_32, \
+ STMT_33, \
+ STMT_34, \
+ STMT_35, \
+ STMT_36, \
+ STMT_37, \
+ STMT_38, \
+ STMT_39, \
+ STMT_40, \
+ STMT_41, \
+ STMT_42, \
+ STMT_43, \
+ STMT_44, \
+ STMT_45, \
+ STMT_46, \
+ STMT_47, \
+ STMT_48, \
+ STMT_49, \
+ STMT_50, \
+ STMT_51, \
+ STMT_52, \
+ STMT_53, \
+ STMT_54, \
+ STMT_55, \
+ STMT_56, \
+ STMT_57, \
+ STMT_58, \
+ STMT_59, \
+ STMT_60, \
+ STMT_61, \
+ STMT_62, \
+ STMT_63, \
+ STMT_64, \
+ STMT_65, \
+ STMT_66, \
+ STMT_67, \
+ STMT_68, \
+ STMT_69, \
+ STMT_70, \
+ STMT_71, \
+ STMT_72, \
+ STMT_73, \
+ STMT_74, \
+ STMT_75, \
+ STMT_76, \
+ STMT_77, \
+ STMT_78, \
+ STMT_79, \
+ STMT_80, \
+ STMT_81, \
+ STMT_82, \
+ STMT_83, \
+ STMT_84, \
+ STMT_85, \
+ STMT_86, \
+ STMT_87, \
+ STMT_88, \
+ STMT_89, \
+ STMT_90, \
+ STMT_91, \
+ STMT_92, \
+ STMT_93, \
+ STMT_94, \
+ STMT_95, \
+ STMT_96, \
+ STMT_97, \
+ STMT_98, \
+ STMT_99, \
+ STMT_100, \
+ STMT_101, \
+ STMT_102, \
+ STMT_103, \
+ STMT_104, \
+ STMT_105, \
+ STMT_106, \
+ STMT_107, \
+ STMT_108, \
+ STMT_109, \
+ STMT_110, \
+ STMT_111, \
+ STMT_112, \
+ STMT_113, \
+ STMT_114, \
+ STMT_115, \
+ STMT_116, \
+ STMT_117, \
+ STMT_118, \
+ STMT_119, \
+ STMT_120, \
+ STMT_121, \
+ STMT_122, \
+ STMT_123, \
+ STMT_124, \
+ STMT_125, \
+ STMT_126, \
+ STMT_127, \
+ STMT_128, \
+ STMT_129, \
+ STMT_130, \
+ STMT_131, \
+ STMT_132, \
+ STMT_133, \
+ STMT_134, \
+ STMT_135, \
+ STMT_136, \
+ STMT_137, \
+ STMT_138, \
+ STMT_139, \
+ STMT_140, \
+ STMT_141, \
+ STMT_142, \
+ STMT_143, \
+ STMT_144, \
+ STMT_145, \
+ STMT_146, \
+ STMT_147, \
+ STMT_148, \
+ STMT_149, \
+ STMT_150, \
+ STMT_151, \
+ STMT_152, \
+ STMT_153, \
+ STMT_154, \
+ STMT_155, \
+ STMT_156, \
+ STMT_157, \
+ STMT_158, \
+ STMT_159, \
+ STMT_160, \
+ STMT_161, \
+ STMT_162, \
+ STMT_163, \
+ STMT_164, \
+ STMT_165, \
+ STMT_166, \
+ STMT_167, \
+ STMT_168, \
+ STMT_169, \
+ STMT_170, \
+ STMT_171, \
+ STMT_172, \
+ STMT_173, \
+ STMT_174, \
+ STMT_175, \
+ STMT_176, \
+ STMT_177, \
+ STMT_178, \
+ STMT_179, \
+ STMT_180, \
+ STMT_181, \
+ STMT_182, \
+ STMT_183, \
+ STMT_184, \
+ STMT_185, \
+ STMT_186, \
+ STMT_187, \
+ STMT_188, \
+ STMT_189, \
+ STMT_190, \
+ STMT_191, \
+ STMT_192, \
+ STMT_193, \
+ STMT_194, \
+ STMT_195, \
+ STMT_196, \
+ STMT_197, \
+ STMT_198, \
+ STMT_199, \
+ STMT_200, \
+ STMT_201, \
+ STMT_202, \
+ STMT_203, \
+ STMT_204, \
+ STMT_205, \
+ STMT_206, \
+ STMT_207, \
+ STMT_208, \
+ STMT_209, \
+ STMT_210, \
+ STMT_211, \
+ STMT_212, \
+ STMT_213, \
+ STMT_214, \
+ STMT_215, \
+ STMT_216, \
+ STMT_217, \
+ STMT_218, \
+ STMT_219, \
+ STMT_220, \
+ STMT_221, \
+ STMT_222, \
+ STMT_223, \
+ STMT_224, \
+ STMT_225, \
+ STMT_226, \
+ STMT_227, \
+ STMT_228, \
+ STMT_229, \
+ NULL \
+ }
+
+#define WC_QUERIES_SQL_DECLARE_STATEMENT_INFO(varname) \
+ static const char * const varname[][2] = { \
+ STMT_0_INFO, \
+ STMT_1_INFO, \
+ STMT_2_INFO, \
+ STMT_3_INFO, \
+ STMT_4_INFO, \
+ STMT_5_INFO, \
+ STMT_6_INFO, \
+ STMT_7_INFO, \
+ STMT_8_INFO, \
+ STMT_9_INFO, \
+ STMT_10_INFO, \
+ STMT_11_INFO, \
+ STMT_12_INFO, \
+ STMT_13_INFO, \
+ STMT_14_INFO, \
+ STMT_15_INFO, \
+ STMT_16_INFO, \
+ STMT_17_INFO, \
+ STMT_18_INFO, \
+ STMT_19_INFO, \
+ STMT_20_INFO, \
+ STMT_21_INFO, \
+ STMT_22_INFO, \
+ STMT_23_INFO, \
+ STMT_24_INFO, \
+ STMT_25_INFO, \
+ STMT_26_INFO, \
+ STMT_27_INFO, \
+ STMT_28_INFO, \
+ STMT_29_INFO, \
+ STMT_30_INFO, \
+ STMT_31_INFO, \
+ STMT_32_INFO, \
+ STMT_33_INFO, \
+ STMT_34_INFO, \
+ STMT_35_INFO, \
+ STMT_36_INFO, \
+ STMT_37_INFO, \
+ STMT_38_INFO, \
+ STMT_39_INFO, \
+ STMT_40_INFO, \
+ STMT_41_INFO, \
+ STMT_42_INFO, \
+ STMT_43_INFO, \
+ STMT_44_INFO, \
+ STMT_45_INFO, \
+ STMT_46_INFO, \
+ STMT_47_INFO, \
+ STMT_48_INFO, \
+ STMT_49_INFO, \
+ STMT_50_INFO, \
+ STMT_51_INFO, \
+ STMT_52_INFO, \
+ STMT_53_INFO, \
+ STMT_54_INFO, \
+ STMT_55_INFO, \
+ STMT_56_INFO, \
+ STMT_57_INFO, \
+ STMT_58_INFO, \
+ STMT_59_INFO, \
+ STMT_60_INFO, \
+ STMT_61_INFO, \
+ STMT_62_INFO, \
+ STMT_63_INFO, \
+ STMT_64_INFO, \
+ STMT_65_INFO, \
+ STMT_66_INFO, \
+ STMT_67_INFO, \
+ STMT_68_INFO, \
+ STMT_69_INFO, \
+ STMT_70_INFO, \
+ STMT_71_INFO, \
+ STMT_72_INFO, \
+ STMT_73_INFO, \
+ STMT_74_INFO, \
+ STMT_75_INFO, \
+ STMT_76_INFO, \
+ STMT_77_INFO, \
+ STMT_78_INFO, \
+ STMT_79_INFO, \
+ STMT_80_INFO, \
+ STMT_81_INFO, \
+ STMT_82_INFO, \
+ STMT_83_INFO, \
+ STMT_84_INFO, \
+ STMT_85_INFO, \
+ STMT_86_INFO, \
+ STMT_87_INFO, \
+ STMT_88_INFO, \
+ STMT_89_INFO, \
+ STMT_90_INFO, \
+ STMT_91_INFO, \
+ STMT_92_INFO, \
+ STMT_93_INFO, \
+ STMT_94_INFO, \
+ STMT_95_INFO, \
+ STMT_96_INFO, \
+ STMT_97_INFO, \
+ STMT_98_INFO, \
+ STMT_99_INFO, \
+ STMT_100_INFO, \
+ STMT_101_INFO, \
+ STMT_102_INFO, \
+ STMT_103_INFO, \
+ STMT_104_INFO, \
+ STMT_105_INFO, \
+ STMT_106_INFO, \
+ STMT_107_INFO, \
+ STMT_108_INFO, \
+ STMT_109_INFO, \
+ STMT_110_INFO, \
+ STMT_111_INFO, \
+ STMT_112_INFO, \
+ STMT_113_INFO, \
+ STMT_114_INFO, \
+ STMT_115_INFO, \
+ STMT_116_INFO, \
+ STMT_117_INFO, \
+ STMT_118_INFO, \
+ STMT_119_INFO, \
+ STMT_120_INFO, \
+ STMT_121_INFO, \
+ STMT_122_INFO, \
+ STMT_123_INFO, \
+ STMT_124_INFO, \
+ STMT_125_INFO, \
+ STMT_126_INFO, \
+ STMT_127_INFO, \
+ STMT_128_INFO, \
+ STMT_129_INFO, \
+ STMT_130_INFO, \
+ STMT_131_INFO, \
+ STMT_132_INFO, \
+ STMT_133_INFO, \
+ STMT_134_INFO, \
+ STMT_135_INFO, \
+ STMT_136_INFO, \
+ STMT_137_INFO, \
+ STMT_138_INFO, \
+ STMT_139_INFO, \
+ STMT_140_INFO, \
+ STMT_141_INFO, \
+ STMT_142_INFO, \
+ STMT_143_INFO, \
+ STMT_144_INFO, \
+ STMT_145_INFO, \
+ STMT_146_INFO, \
+ STMT_147_INFO, \
+ STMT_148_INFO, \
+ STMT_149_INFO, \
+ STMT_150_INFO, \
+ STMT_151_INFO, \
+ STMT_152_INFO, \
+ STMT_153_INFO, \
+ STMT_154_INFO, \
+ STMT_155_INFO, \
+ STMT_156_INFO, \
+ STMT_157_INFO, \
+ STMT_158_INFO, \
+ STMT_159_INFO, \
+ STMT_160_INFO, \
+ STMT_161_INFO, \
+ STMT_162_INFO, \
+ STMT_163_INFO, \
+ STMT_164_INFO, \
+ STMT_165_INFO, \
+ STMT_166_INFO, \
+ STMT_167_INFO, \
+ STMT_168_INFO, \
+ STMT_169_INFO, \
+ STMT_170_INFO, \
+ STMT_171_INFO, \
+ STMT_172_INFO, \
+ STMT_173_INFO, \
+ STMT_174_INFO, \
+ STMT_175_INFO, \
+ STMT_176_INFO, \
+ STMT_177_INFO, \
+ STMT_178_INFO, \
+ STMT_179_INFO, \
+ STMT_180_INFO, \
+ STMT_181_INFO, \
+ STMT_182_INFO, \
+ STMT_183_INFO, \
+ STMT_184_INFO, \
+ STMT_185_INFO, \
+ STMT_186_INFO, \
+ STMT_187_INFO, \
+ STMT_188_INFO, \
+ STMT_189_INFO, \
+ STMT_190_INFO, \
+ STMT_191_INFO, \
+ STMT_192_INFO, \
+ STMT_193_INFO, \
+ STMT_194_INFO, \
+ STMT_195_INFO, \
+ STMT_196_INFO, \
+ STMT_197_INFO, \
+ STMT_198_INFO, \
+ STMT_199_INFO, \
+ STMT_200_INFO, \
+ STMT_201_INFO, \
+ STMT_202_INFO, \
+ STMT_203_INFO, \
+ STMT_204_INFO, \
+ STMT_205_INFO, \
+ STMT_206_INFO, \
+ STMT_207_INFO, \
+ STMT_208_INFO, \
+ STMT_209_INFO, \
+ STMT_210_INFO, \
+ STMT_211_INFO, \
+ STMT_212_INFO, \
+ STMT_213_INFO, \
+ STMT_214_INFO, \
+ STMT_215_INFO, \
+ STMT_216_INFO, \
+ STMT_217_INFO, \
+ STMT_218_INFO, \
+ STMT_219_INFO, \
+ STMT_220_INFO, \
+ STMT_221_INFO, \
+ STMT_222_INFO, \
+ STMT_223_INFO, \
+ STMT_224_INFO, \
+ STMT_225_INFO, \
+ STMT_226_INFO, \
+ STMT_227_INFO, \
+ STMT_228_INFO, \
+ STMT_229_INFO, \
+ {NULL, NULL} \
+ }
diff --git a/contrib/subversion/subversion/libsvn_wc/wc-queries.sql b/contrib/subversion/subversion/libsvn_wc/wc-queries.sql
new file mode 100644
index 0000000..0ffe6f0
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc-queries.sql
@@ -0,0 +1,1693 @@
+/* wc-queries.sql -- queries used to interact with the wc-metadata
+ * SQLite database
+ * This is intended for use with SQLite 3
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ------------------------------------------------------------------------- */
+
+/* these are used in wc_db.c */
+
+-- STMT_SELECT_NODE_INFO
+SELECT op_depth, repos_id, repos_path, presence, kind, revision, checksum,
+ translated_size, changed_revision, changed_date, changed_author, depth,
+ symlink_target, last_mod_time, properties, moved_here, inherited_props,
+ moved_to
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+ORDER BY op_depth DESC
+
+-- STMT_SELECT_NODE_INFO_WITH_LOCK
+SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision,
+ checksum, translated_size, changed_revision, changed_date, changed_author,
+ depth, symlink_target, last_mod_time, properties, moved_here,
+ inherited_props,
+ /* All the columns until now must match those returned by
+ STMT_SELECT_NODE_INFO. The implementation of svn_wc__db_read_info()
+ assumes that these columns are followed by the lock information) */
+ lock_token, lock_owner, lock_comment, lock_date
+FROM nodes
+LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id
+ AND nodes.repos_path = lock.repos_relpath
+WHERE wc_id = ?1 AND local_relpath = ?2
+ORDER BY op_depth DESC
+
+-- STMT_SELECT_BASE_NODE
+SELECT repos_id, repos_path, presence, kind, revision, checksum,
+ translated_size, changed_revision, changed_date, changed_author, depth,
+ symlink_target, last_mod_time, properties, file_external
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_SELECT_BASE_NODE_WITH_LOCK
+SELECT nodes.repos_id, nodes.repos_path, presence, kind, revision,
+ checksum, translated_size, changed_revision, changed_date, changed_author,
+ depth, symlink_target, last_mod_time, properties, file_external,
+ /* All the columns until now must match those returned by
+ STMT_SELECT_BASE_NODE. The implementation of svn_wc__db_base_get_info()
+ assumes that these columns are followed by the lock information) */
+ lock_token, lock_owner, lock_comment, lock_date
+FROM nodes
+LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id
+ AND nodes.repos_path = lock.repos_relpath
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_SELECT_BASE_CHILDREN_INFO
+SELECT local_relpath, nodes.repos_id, nodes.repos_path, presence, kind,
+ revision, depth, file_external,
+ lock_token, lock_owner, lock_comment, lock_date
+FROM nodes
+LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id
+ AND nodes.repos_path = lock.repos_relpath
+WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0
+
+-- STMT_SELECT_WORKING_NODE
+SELECT op_depth, presence, kind, checksum, translated_size,
+ changed_revision, changed_date, changed_author, depth, symlink_target,
+ repos_id, repos_path, revision,
+ moved_here, moved_to, last_mod_time, properties
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0
+ORDER BY op_depth DESC
+LIMIT 1
+
+-- STMT_SELECT_DEPTH_NODE
+SELECT repos_id, repos_path, presence, kind, revision, checksum,
+ translated_size, changed_revision, changed_date, changed_author, depth,
+ symlink_target, last_mod_time, properties
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_SELECT_LOWEST_WORKING_NODE
+SELECT op_depth, presence, kind, moved_to
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3
+ORDER BY op_depth
+LIMIT 1
+
+-- STMT_SELECT_HIGHEST_WORKING_NODE
+SELECT op_depth
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth < ?3
+ORDER BY op_depth DESC
+LIMIT 1
+
+-- STMT_SELECT_ACTUAL_NODE
+SELECT changelist, properties, conflict_data
+FROM actual_node
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_SELECT_NODE_CHILDREN_INFO
+/* Getting rows in an advantageous order using
+ ORDER BY local_relpath, op_depth DESC
+ turns out to be slower than getting rows in a random order and making the
+ C code handle it. */
+SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision,
+ checksum, translated_size, changed_revision, changed_date, changed_author,
+ depth, symlink_target, last_mod_time, properties, lock_token, lock_owner,
+ lock_comment, lock_date, local_relpath, moved_here, moved_to, file_external
+FROM nodes
+LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id
+ AND nodes.repos_path = lock.repos_relpath AND op_depth = 0
+WHERE wc_id = ?1 AND parent_relpath = ?2
+
+-- STMT_SELECT_NODE_CHILDREN_WALKER_INFO
+SELECT local_relpath, op_depth, presence, kind
+FROM nodes_current
+WHERE wc_id = ?1 AND parent_relpath = ?2
+
+-- STMT_SELECT_ACTUAL_CHILDREN_INFO
+SELECT local_relpath, changelist, properties, conflict_data
+FROM actual_node
+WHERE wc_id = ?1 AND parent_relpath = ?2
+
+-- STMT_SELECT_REPOSITORY_BY_ID
+SELECT root, uuid FROM repository WHERE id = ?1
+
+-- STMT_SELECT_WCROOT_NULL
+SELECT id FROM wcroot WHERE local_abspath IS NULL
+
+-- STMT_SELECT_REPOSITORY
+SELECT id FROM repository WHERE root = ?1
+
+-- STMT_INSERT_REPOSITORY
+INSERT INTO repository (root, uuid) VALUES (?1, ?2)
+
+-- STMT_INSERT_NODE
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path,
+ revision, presence, depth, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, translated_size, last_mod_time,
+ dav_cache, symlink_target, file_external, moved_to, moved_here,
+ inherited_props)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14,
+ ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23)
+
+-- STMT_SELECT_BASE_PRESENT
+SELECT local_relpath, kind FROM nodes n
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ AND presence in (MAP_NORMAL, MAP_INCOMPLETE)
+ AND NOT EXISTS(SELECT 1 FROM NODES w
+ WHERE w.wc_id = ?1 AND w.local_relpath = n.local_relpath
+ AND op_depth > 0)
+ORDER BY local_relpath DESC
+
+-- STMT_SELECT_WORKING_PRESENT
+SELECT local_relpath, kind, checksum, translated_size, last_mod_time
+FROM nodes n
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND presence in (MAP_NORMAL, MAP_INCOMPLETE)
+ AND op_depth = (SELECT MAX(op_depth)
+ FROM NODES w
+ WHERE w.wc_id = ?1
+ AND w.local_relpath = n.local_relpath)
+ORDER BY local_relpath DESC
+
+-- STMT_DELETE_NODE_RECURSIVE
+DELETE FROM NODES
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+
+-- STMT_DELETE_NODE
+DELETE
+FROM NODES
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE
+/* The ACTUAL_NODE applies to BASE, unless there is in at least one op_depth
+ a WORKING node that could have a conflict */
+DELETE FROM actual_node
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND EXISTS(SELECT 1 FROM NODES b
+ WHERE b.wc_id = ?1
+ AND b.local_relpath = actual_node.local_relpath
+ AND op_depth = 0)
+ AND NOT EXISTS(SELECT 1 FROM NODES w
+ WHERE w.wc_id = ?1
+ AND w.local_relpath = actual_node.local_relpath
+ AND op_depth > 0
+ AND presence in (MAP_NORMAL, MAP_INCOMPLETE, MAP_NOT_PRESENT))
+
+-- STMT_DELETE_WORKING_BASE_DELETE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND presence = MAP_BASE_DELETED
+ AND op_depth > 0
+ AND op_depth = (SELECT MIN(op_depth) FROM nodes n
+ WHERE n.wc_id = ?1
+ AND n.local_relpath = nodes.local_relpath
+ AND op_depth > 0)
+
+-- STMT_DELETE_WORKING_RECURSIVE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth > 0
+
+-- STMT_DELETE_BASE_RECURSIVE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+
+-- STMT_DELETE_WORKING_OP_DEPTH
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+-- STMT_DELETE_WORKING_OP_DEPTH_ABOVE
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth > ?3
+
+-- STMT_SELECT_LOCAL_RELPATH_OP_DEPTH
+SELECT local_relpath
+FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+-- STMT_SELECT_CHILDREN_OP_DEPTH
+SELECT local_relpath, kind
+FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = ?3
+ORDER BY local_relpath DESC
+
+-- STMT_COPY_NODE_MOVE
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path,
+ revision, presence, depth, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, translated_size, last_mod_time,
+ symlink_target, moved_here, moved_to )
+SELECT
+ wc_id, ?4 /*local_relpath */, ?5 /*op_depth*/, ?6 /* parent_relpath */,
+ repos_id,
+ repos_path, revision, presence, depth, kind, changed_revision,
+ changed_date, changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target, 1,
+ (SELECT dst.moved_to FROM nodes AS dst
+ WHERE dst.wc_id = ?1
+ AND dst.local_relpath = ?4
+ AND dst.op_depth = ?5)
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_SELECT_OP_DEPTH_CHILDREN
+SELECT local_relpath, kind FROM nodes
+WHERE wc_id = ?1
+ AND parent_relpath = ?2
+ AND op_depth = ?3
+ AND presence != MAP_BASE_DELETED
+ AND file_external is NULL
+
+/* Used by non-recursive revert to detect higher level children, and
+ actual-only rows that would be left orphans, if the revert
+ proceeded. */
+-- STMT_SELECT_GE_OP_DEPTH_CHILDREN
+SELECT 1 FROM nodes
+WHERE wc_id = ?1 AND parent_relpath = ?2
+ AND (op_depth > ?3 OR (op_depth = ?3 AND presence != MAP_BASE_DELETED))
+UNION ALL
+SELECT 1 FROM ACTUAL_NODE a
+WHERE wc_id = ?1 AND parent_relpath = ?2
+ AND NOT EXISTS (SELECT 1 FROM nodes n
+ WHERE wc_id = ?1 AND n.local_relpath = a.local_relpath)
+
+/* Delete the nodes shadowed by local_relpath. Not valid for the wc-root */
+-- STMT_DELETE_SHADOWED_RECURSIVE
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND (op_depth < ?3
+ OR (op_depth = ?3 AND presence = MAP_BASE_DELETED))
+
+-- STMT_CLEAR_MOVED_TO_FROM_DEST
+UPDATE NODES SET moved_to = NULL
+WHERE wc_id = ?1
+ AND moved_to = ?2
+
+/* Get not-present descendants of a copied node. Not valid for the wc-root */
+-- STMT_SELECT_NOT_PRESENT_DESCENDANTS
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1 AND op_depth = ?3
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND presence = MAP_NOT_PRESENT
+
+-- STMT_COMMIT_DESCENDANTS_TO_BASE
+UPDATE NODES SET op_depth = 0,
+ repos_id = ?4,
+ repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1),
+ revision = ?6,
+ dav_cache = NULL,
+ moved_here = NULL,
+ presence = CASE presence
+ WHEN MAP_NORMAL THEN MAP_NORMAL
+ WHEN MAP_EXCLUDED THEN MAP_EXCLUDED
+ ELSE MAP_NOT_PRESENT
+ END
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = ?3
+
+-- STMT_SELECT_NODE_CHILDREN
+/* Return all paths that are children of the directory (?1, ?2) in any
+ op-depth, including children of any underlying, replaced directories. */
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1 AND parent_relpath = ?2
+
+-- STMT_SELECT_WORKING_CHILDREN
+/* Return all paths that are children of the working version of the
+ directory (?1, ?2). A given path is not included just because it is a
+ child of an underlying (replaced) directory, it has to be in the
+ working version of the directory. */
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1 AND parent_relpath = ?2
+ AND (op_depth > (SELECT MAX(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2)
+ OR
+ (op_depth = (SELECT MAX(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2)
+ AND presence != MAP_BASE_DELETED))
+
+-- STMT_SELECT_NODE_PROPS
+SELECT properties, presence FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+ORDER BY op_depth DESC
+
+-- STMT_SELECT_ACTUAL_PROPS
+SELECT properties FROM actual_node
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_UPDATE_ACTUAL_PROPS
+UPDATE actual_node SET properties = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_INSERT_ACTUAL_PROPS
+INSERT INTO actual_node (wc_id, local_relpath, parent_relpath, properties)
+VALUES (?1, ?2, ?3, ?4)
+
+-- STMT_INSERT_LOCK
+INSERT OR REPLACE INTO lock
+(repos_id, repos_relpath, lock_token, lock_owner, lock_comment,
+ lock_date)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6)
+
+/* Not valid for the working copy root */
+-- STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE
+SELECT nodes.repos_id, nodes.repos_path, lock_token
+FROM nodes
+LEFT JOIN lock ON nodes.repos_id = lock.repos_id
+ AND nodes.repos_path = lock.repos_relpath
+WHERE wc_id = ?1 AND op_depth = 0
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+
+-- STMT_INSERT_WCROOT
+INSERT INTO wcroot (local_abspath)
+VALUES (?1)
+
+-- STMT_UPDATE_BASE_NODE_DAV_CACHE
+UPDATE nodes SET dav_cache = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_SELECT_BASE_DAV_CACHE
+SELECT dav_cache FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_SELECT_DELETION_INFO
+SELECT (SELECT b.presence FROM nodes AS b
+ WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0),
+ work.presence, work.op_depth
+FROM nodes_current AS work
+WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0
+LIMIT 1
+
+-- STMT_SELECT_DELETION_INFO_SCAN
+/* ### FIXME. moved.moved_to IS NOT NULL works when there is
+ only one move but we need something else when there are several. */
+SELECT (SELECT b.presence FROM nodes AS b
+ WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0),
+ work.presence, work.op_depth, moved.moved_to
+FROM nodes_current AS work
+LEFT OUTER JOIN nodes AS moved
+ ON moved.wc_id = work.wc_id
+ AND moved.local_relpath = work.local_relpath
+ AND moved.moved_to IS NOT NULL
+WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0
+LIMIT 1
+
+-- STMT_SELECT_OP_DEPTH_MOVED_TO
+SELECT op_depth, moved_to, repos_path, revision
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND op_depth <= (SELECT MIN(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3)
+ORDER BY op_depth DESC
+
+-- STMT_SELECT_MOVED_TO
+SELECT moved_to
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_SELECT_MOVED_HERE
+SELECT moved_here, presence, repos_path, revision
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth >= ?3
+ORDER BY op_depth
+
+-- STMT_SELECT_MOVED_BACK
+SELECT u.local_relpath,
+ u.presence, u.repos_id, u.repos_path, u.revision,
+ l.presence, l.repos_id, l.repos_path, l.revision,
+ u.moved_here, u.moved_to
+FROM nodes u
+LEFT OUTER JOIN nodes l ON l.wc_id = ?1
+ AND l.local_relpath = u.local_relpath
+ AND l.op_depth = ?3
+WHERE u.wc_id = ?1
+ AND u.local_relpath = ?2
+ AND u.op_depth = ?4
+UNION ALL
+SELECT u.local_relpath,
+ u.presence, u.repos_id, u.repos_path, u.revision,
+ l.presence, l.repos_id, l.repos_path, l.revision,
+ u.moved_here, NULL
+FROM nodes u
+LEFT OUTER JOIN nodes l ON l.wc_id=?1
+ AND l.local_relpath=u.local_relpath
+ AND l.op_depth=?3
+WHERE u.wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(u.local_relpath, ?2)
+ AND u.op_depth = ?4
+
+-- STMT_DELETE_MOVED_BACK
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+-- STMT_DELETE_LOCK
+DELETE FROM lock
+WHERE repos_id = ?1 AND repos_relpath = ?2
+
+-- STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE
+UPDATE nodes SET dav_cache = NULL
+WHERE dav_cache IS NOT NULL AND wc_id = ?1 AND op_depth = 0
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+
+-- STMT_RECURSIVE_UPDATE_NODE_REPO
+UPDATE nodes SET repos_id = ?4, dav_cache = NULL
+/* ### The Sqlite optimizer needs help here ###
+ * WHERE wc_id = ?1
+ * AND repos_id = ?3
+ * AND (local_relpath = ?2
+ * OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))*/
+WHERE (wc_id = ?1 AND local_relpath = ?2 AND repos_id = ?3)
+ OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND repos_id = ?3)
+
+
+-- STMT_UPDATE_LOCK_REPOS_ID
+UPDATE lock SET repos_id = ?2
+WHERE repos_id = ?1
+
+-- STMT_UPDATE_NODE_FILEINFO
+UPDATE nodes SET translated_size = ?3, last_mod_time = ?4
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND op_depth = (SELECT MAX(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2)
+
+-- STMT_INSERT_ACTUAL_CONFLICT
+INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath)
+VALUES (?1, ?2, ?3, ?4)
+
+-- STMT_UPDATE_ACTUAL_CONFLICT
+UPDATE actual_node SET conflict_data = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_UPDATE_ACTUAL_CHANGELISTS
+UPDATE actual_node SET changelist = ?3
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND local_relpath = (SELECT local_relpath FROM targets_list AS t
+ WHERE wc_id = ?1
+ AND t.local_relpath = actual_node.local_relpath
+ AND kind = MAP_FILE)
+
+-- STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST
+UPDATE actual_node SET changelist = NULL
+ WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_MARK_SKIPPED_CHANGELIST_DIRS
+/* 7 corresponds to svn_wc_notify_skip */
+INSERT INTO changelist_list (wc_id, local_relpath, notify, changelist)
+SELECT wc_id, local_relpath, 7, ?3
+FROM targets_list
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND kind = MAP_DIR
+
+-- STMT_RESET_ACTUAL_WITH_CHANGELIST
+REPLACE INTO actual_node (
+ wc_id, local_relpath, parent_relpath, changelist)
+VALUES (?1, ?2, ?3, ?4)
+
+-- STMT_CREATE_CHANGELIST_LIST
+DROP TABLE IF EXISTS changelist_list;
+CREATE TEMPORARY TABLE changelist_list (
+ wc_id INTEGER NOT NULL,
+ local_relpath TEXT NOT NULL,
+ notify INTEGER NOT NULL,
+ changelist TEXT NOT NULL,
+ /* Order NOTIFY descending to make us show clears (27) before adds (26) */
+ PRIMARY KEY (wc_id, local_relpath, notify DESC)
+)
+
+/* Create notify items for when a node is removed from a changelist and
+ when a node is added to a changelist. Make sure nothing is notified
+ if there were no changes.
+*/
+-- STMT_CREATE_CHANGELIST_TRIGGER
+DROP TRIGGER IF EXISTS trigger_changelist_list_change;
+CREATE TEMPORARY TRIGGER trigger_changelist_list_change
+BEFORE UPDATE ON actual_node
+WHEN old.changelist IS NOT new.changelist
+BEGIN
+ /* 27 corresponds to svn_wc_notify_changelist_clear */
+ INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist)
+ SELECT old.wc_id, old.local_relpath, 27, old.changelist
+ WHERE old.changelist is NOT NULL;
+
+ /* 26 corresponds to svn_wc_notify_changelist_set */
+ INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist)
+ SELECT new.wc_id, new.local_relpath, 26, new.changelist
+ WHERE new.changelist IS NOT NULL;
+END
+
+-- STMT_FINALIZE_CHANGELIST
+DROP TRIGGER trigger_changelist_list_change;
+DROP TABLE changelist_list;
+DROP TABLE targets_list
+
+-- STMT_SELECT_CHANGELIST_LIST
+SELECT wc_id, local_relpath, notify, changelist
+FROM changelist_list
+ORDER BY wc_id, local_relpath ASC, notify DESC
+
+-- STMT_CREATE_TARGETS_LIST
+DROP TABLE IF EXISTS targets_list;
+CREATE TEMPORARY TABLE targets_list (
+ wc_id INTEGER NOT NULL,
+ local_relpath TEXT NOT NULL,
+ parent_relpath TEXT,
+ kind TEXT NOT NULL,
+ PRIMARY KEY (wc_id, local_relpath)
+ );
+/* need more indicies? */
+
+-- STMT_DROP_TARGETS_LIST
+DROP TABLE targets_list
+
+-- STMT_INSERT_TARGET
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT wc_id, local_relpath, parent_relpath, kind
+FROM nodes_current
+WHERE wc_id = ?1
+ AND local_relpath = ?2
+
+-- STMT_INSERT_TARGET_DEPTH_FILES
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT wc_id, local_relpath, parent_relpath, kind
+FROM nodes_current
+WHERE wc_id = ?1
+ AND parent_relpath = ?2
+ AND kind = MAP_FILE
+
+-- STMT_INSERT_TARGET_DEPTH_IMMEDIATES
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT wc_id, local_relpath, parent_relpath, kind
+FROM nodes_current
+WHERE wc_id = ?1
+ AND parent_relpath = ?2
+
+-- STMT_INSERT_TARGET_DEPTH_INFINITY
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT wc_id, local_relpath, parent_relpath, kind
+FROM nodes_current
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+
+-- STMT_INSERT_TARGET_WITH_CHANGELIST
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind
+ FROM actual_node AS A JOIN nodes_current AS N
+ ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath
+ WHERE N.wc_id = ?1
+ AND N.local_relpath = ?2
+ AND A.changelist = ?3
+
+-- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind
+ FROM actual_node AS A JOIN nodes_current AS N
+ ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath
+ WHERE N.wc_id = ?1
+ AND N.parent_relpath = ?2
+ AND kind = MAP_FILE
+ AND A.changelist = ?3
+
+-- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind
+ FROM actual_node AS A JOIN nodes_current AS N
+ ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath
+ WHERE N.wc_id = ?1
+ AND N.parent_relpath = ?2
+ AND A.changelist = ?3
+
+-- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY
+INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind)
+SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind
+ FROM actual_node AS A JOIN nodes_current AS N
+ ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath
+ WHERE N.wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(N.local_relpath, ?2)
+ AND A.changelist = ?3
+
+/* Only used by commented dump_targets() in wc_db.c */
+/*-- STMT_SELECT_TARGETS
+SELECT local_relpath, parent_relpath from targets_list*/
+
+-- STMT_INSERT_ACTUAL_EMPTIES
+INSERT OR IGNORE INTO actual_node (
+ wc_id, local_relpath, parent_relpath)
+SELECT wc_id, local_relpath, parent_relpath
+FROM targets_list
+
+-- STMT_DELETE_ACTUAL_EMPTY
+DELETE FROM actual_node
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND properties IS NULL
+ AND conflict_data IS NULL
+ AND changelist IS NULL
+ AND text_mod IS NULL
+ AND older_checksum IS NULL
+ AND right_checksum IS NULL
+ AND left_checksum IS NULL
+
+-- STMT_DELETE_ACTUAL_EMPTIES
+DELETE FROM actual_node
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND properties IS NULL
+ AND conflict_data IS NULL
+ AND changelist IS NULL
+ AND text_mod IS NULL
+ AND older_checksum IS NULL
+ AND right_checksum IS NULL
+ AND left_checksum IS NULL
+
+-- STMT_DELETE_BASE_NODE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_DELETE_WORKING_NODE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND op_depth = (SELECT MAX(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0)
+
+-- STMT_DELETE_LOWEST_WORKING_NODE
+DELETE FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND op_depth = (SELECT MIN(op_depth) FROM nodes
+ WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3)
+ AND presence = MAP_BASE_DELETED
+
+-- STMT_DELETE_ALL_LAYERS
+DELETE FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth >= ?3
+
+-- STMT_DELETE_ACTUAL_NODE
+DELETE FROM actual_node
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+/* Will not delete recursive when run on the wcroot */
+-- STMT_DELETE_ACTUAL_NODE_RECURSIVE
+DELETE FROM actual_node
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+
+-- STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST
+DELETE FROM actual_node
+WHERE wc_id = ?1
+ AND local_relpath = ?2
+ AND (changelist IS NULL
+ OR NOT EXISTS (SELECT 1 FROM nodes_current c
+ WHERE c.wc_id = ?1 AND c.local_relpath = ?2
+ AND c.kind = MAP_FILE))
+
+-- STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE
+DELETE FROM actual_node
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND (changelist IS NULL
+ OR NOT EXISTS (SELECT 1 FROM nodes_current c
+ WHERE c.wc_id = ?1
+ AND c.local_relpath = actual_node.local_relpath
+ AND c.kind = MAP_FILE))
+
+-- STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST
+UPDATE actual_node
+SET properties = NULL,
+ text_mod = NULL,
+ conflict_data = NULL,
+ tree_conflict_data = NULL,
+ older_checksum = NULL,
+ left_checksum = NULL,
+ right_checksum = NULL
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE
+UPDATE actual_node
+SET properties = NULL,
+ text_mod = NULL,
+ conflict_data = NULL,
+ tree_conflict_data = NULL,
+ older_checksum = NULL,
+ left_checksum = NULL,
+ right_checksum = NULL
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+
+-- STMT_UPDATE_NODE_BASE_DEPTH
+UPDATE nodes SET depth = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+ AND kind=MAP_DIR
+
+-- STMT_UPDATE_NODE_BASE_PRESENCE
+UPDATE nodes SET presence = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH
+UPDATE nodes SET presence = ?3, revision = ?4, repos_path = ?5
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_LOOK_FOR_WORK
+SELECT id FROM work_queue LIMIT 1
+
+-- STMT_INSERT_WORK_ITEM
+INSERT INTO work_queue (work) VALUES (?1)
+
+-- STMT_SELECT_WORK_ITEM
+SELECT id, work FROM work_queue ORDER BY id LIMIT 1
+
+-- STMT_DELETE_WORK_ITEM
+DELETE FROM work_queue WHERE id = ?1
+
+-- STMT_INSERT_OR_IGNORE_PRISTINE
+INSERT OR IGNORE INTO pristine (checksum, md5_checksum, size, refcount)
+VALUES (?1, ?2, ?3, 0)
+
+-- STMT_INSERT_PRISTINE
+INSERT INTO pristine (checksum, md5_checksum, size, refcount)
+VALUES (?1, ?2, ?3, 0)
+
+-- STMT_SELECT_PRISTINE
+SELECT md5_checksum
+FROM pristine
+WHERE checksum = ?1
+
+-- STMT_SELECT_PRISTINE_SIZE
+SELECT size
+FROM pristine
+WHERE checksum = ?1 LIMIT 1
+
+-- STMT_SELECT_PRISTINE_BY_MD5
+SELECT checksum
+FROM pristine
+WHERE md5_checksum = ?1
+
+-- STMT_SELECT_UNREFERENCED_PRISTINES
+SELECT checksum
+FROM pristine
+WHERE refcount = 0
+
+-- STMT_DELETE_PRISTINE_IF_UNREFERENCED
+DELETE FROM pristine
+WHERE checksum = ?1 AND refcount = 0
+
+-- STMT_SELECT_COPY_PRISTINES
+/* For the root itself */
+SELECT n.checksum, md5_checksum, size
+FROM nodes_current n
+LEFT JOIN pristine p ON n.checksum = p.checksum
+WHERE wc_id = ?1
+ AND n.local_relpath = ?2
+ AND n.checksum IS NOT NULL
+UNION ALL
+/* And all descendants */
+SELECT n.checksum, md5_checksum, size
+FROM nodes n
+LEFT JOIN pristine p ON n.checksum = p.checksum
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2)
+ AND op_depth >=
+ (SELECT MAX(op_depth) FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2)
+ AND n.checksum IS NOT NULL
+
+-- STMT_VACUUM
+VACUUM
+
+-- STMT_SELECT_CONFLICT_VICTIMS
+SELECT local_relpath, conflict_data
+FROM actual_node
+WHERE wc_id = ?1 AND parent_relpath = ?2 AND
+ NOT (conflict_data IS NULL)
+
+-- STMT_INSERT_WC_LOCK
+INSERT INTO wc_lock (wc_id, local_dir_relpath, locked_levels)
+VALUES (?1, ?2, ?3)
+
+-- STMT_SELECT_WC_LOCK
+SELECT locked_levels FROM wc_lock
+WHERE wc_id = ?1 AND local_dir_relpath = ?2
+
+-- STMT_SELECT_ANCESTOR_WCLOCKS
+SELECT local_dir_relpath, locked_levels FROM wc_lock
+WHERE wc_id = ?1
+ AND ((local_dir_relpath >= ?3 AND local_dir_relpath <= ?2)
+ OR local_dir_relpath = '')
+
+-- STMT_DELETE_WC_LOCK
+DELETE FROM wc_lock
+WHERE wc_id = ?1 AND local_dir_relpath = ?2
+
+-- STMT_FIND_WC_LOCK
+SELECT local_dir_relpath FROM wc_lock
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_dir_relpath, ?2)
+
+-- STMT_DELETE_WC_LOCK_ORPHAN
+DELETE FROM wc_lock
+WHERE wc_id = ?1 AND local_dir_relpath = ?2
+AND NOT EXISTS (SELECT 1 FROM nodes
+ WHERE nodes.wc_id = ?1
+ AND nodes.local_relpath = wc_lock.local_dir_relpath)
+
+-- STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE
+DELETE FROM wc_lock
+WHERE wc_id = ?1
+ AND (local_dir_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_dir_relpath, ?2))
+ AND NOT EXISTS (SELECT 1 FROM nodes
+ WHERE nodes.wc_id = ?1
+ AND nodes.local_relpath = wc_lock.local_dir_relpath)
+
+-- STMT_APPLY_CHANGES_TO_BASE_NODE
+/* translated_size and last_mod_time are not mentioned here because they will
+ be tweaked after the working-file is installed. When we replace an existing
+ BASE node (read: bump), preserve its file_external status. */
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path,
+ revision, presence, depth, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, dav_cache, symlink_target,
+ inherited_props, file_external )
+VALUES (?1, ?2, 0,
+ ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17,
+ (SELECT file_external FROM nodes
+ WHERE wc_id = ?1
+ AND local_relpath = ?2
+ AND op_depth = 0))
+
+-- STMT_INSTALL_WORKING_NODE_FOR_DELETE
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth,
+ parent_relpath, presence, kind)
+VALUES(?1, ?2, ?3, ?4, MAP_BASE_DELETED, ?5)
+
+-- STMT_DELETE_NO_LOWER_LAYER
+DELETE FROM nodes
+ WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+ AND NOT EXISTS (SELECT 1 FROM nodes n
+ WHERE n.wc_id = ?1
+ AND n.local_relpath = nodes.local_relpath
+ AND n.op_depth = ?4
+ AND n.presence IN (MAP_NORMAL, MAP_INCOMPLETE))
+
+-- STMT_REPLACE_WITH_BASE_DELETED
+INSERT OR REPLACE INTO nodes (wc_id, local_relpath, op_depth, parent_relpath,
+ kind, moved_to, presence)
+SELECT wc_id, local_relpath, op_depth, parent_relpath,
+ kind, moved_to, MAP_BASE_DELETED
+ FROM nodes
+ WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+/* If this query is updated, STMT_INSERT_DELETE_LIST should too. */
+-- STMT_INSERT_DELETE_FROM_NODE_RECURSIVE
+INSERT INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, presence, kind)
+SELECT wc_id, local_relpath, ?4 /*op_depth*/, parent_relpath, MAP_BASE_DELETED,
+ kind
+FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+ AND presence NOT IN (MAP_BASE_DELETED, MAP_NOT_PRESENT, MAP_EXCLUDED, MAP_SERVER_EXCLUDED)
+ AND file_external IS NULL
+
+-- STMT_INSERT_WORKING_NODE_FROM_BASE_COPY
+INSERT INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path,
+ revision, presence, depth, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, translated_size, last_mod_time,
+ symlink_target )
+SELECT wc_id, local_relpath, ?3 /*op_depth*/, parent_relpath, repos_id,
+ repos_path, revision, presence, depth, kind, changed_revision,
+ changed_date, changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_INSERT_DELETE_FROM_BASE
+INSERT INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, presence, kind)
+SELECT wc_id, local_relpath, ?3 /*op_depth*/, parent_relpath,
+ MAP_BASE_DELETED, kind
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+/* Not valid on the wc-root */
+-- STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE
+UPDATE nodes SET op_depth = ?3 + 1
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = ?3
+
+-- STMT_UPDATE_OP_DEPTH_RECURSIVE
+UPDATE nodes SET op_depth = ?4, moved_here = NULL
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+-- STMT_DOES_NODE_EXIST
+SELECT 1 FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2
+LIMIT 1
+
+-- STMT_HAS_SERVER_EXCLUDED_DESCENDANTS
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0 AND presence = MAP_SERVER_EXCLUDED
+LIMIT 1
+
+/* Select all excluded nodes. Not valid on the WC-root */
+-- STMT_SELECT_ALL_EXCLUDED_DESCENDANTS
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ AND (presence = MAP_SERVER_EXCLUDED OR presence = MAP_EXCLUDED)
+
+/* Creates a copy from one top level NODE to a different location */
+-- STMT_INSERT_WORKING_NODE_COPY_FROM
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id,
+ repos_path, revision, presence, depth, moved_here, kind, changed_revision,
+ changed_date, changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target, moved_to )
+SELECT wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/,
+ repos_id, repos_path, revision, ?6 /*presence*/, depth,
+ ?7/*moved_here*/, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target,
+ (SELECT dst.moved_to FROM nodes AS dst
+ WHERE dst.wc_id = ?1
+ AND dst.local_relpath = ?3
+ AND dst.op_depth = ?4)
+FROM nodes_current
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH
+INSERT OR REPLACE INTO nodes (
+ wc_id, local_relpath, op_depth, parent_relpath, repos_id,
+ repos_path, revision, presence, depth, moved_here, kind, changed_revision,
+ changed_date, changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target, moved_to )
+SELECT wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/,
+ repos_id, repos_path, revision, ?6 /*presence*/, depth,
+ ?8 /*moved_here*/, kind, changed_revision, changed_date,
+ changed_author, checksum, properties, translated_size,
+ last_mod_time, symlink_target,
+ (SELECT dst.moved_to FROM nodes AS dst
+ WHERE dst.wc_id = ?1
+ AND dst.local_relpath = ?3
+ AND dst.op_depth = ?4)
+FROM nodes
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?7
+
+-- STMT_UPDATE_BASE_REVISION
+UPDATE nodes SET revision = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_UPDATE_BASE_REPOS
+UPDATE nodes SET repos_id = ?3, repos_path = ?4
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+
+-- STMT_ACTUAL_HAS_CHILDREN
+SELECT 1 FROM actual_node
+WHERE wc_id = ?1 AND parent_relpath = ?2
+LIMIT 1
+
+-- STMT_INSERT_EXTERNAL
+INSERT OR REPLACE INTO externals (
+ wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath,
+ repos_id, def_repos_relpath, def_operational_revision, def_revision)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
+
+-- STMT_SELECT_EXTERNAL_INFO
+SELECT presence, kind, def_local_relpath, repos_id,
+ def_repos_relpath, def_operational_revision, def_revision
+FROM externals WHERE wc_id = ?1 AND local_relpath = ?2
+LIMIT 1
+
+-- STMT_DELETE_FILE_EXTERNALS
+DELETE FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ AND file_external IS NOT NULL
+
+-- STMT_DELETE_FILE_EXTERNAL_REGISTATIONS
+DELETE FROM externals
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND kind != MAP_DIR
+
+-- STMT_DELETE_EXTERNAL_REGISTATIONS
+DELETE FROM externals
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+
+/* Select all committable externals, i.e. only unpegged ones on the same
+ * repository as the target path ?2, that are defined by WC ?1 to
+ * live below the target path. It does not matter which ancestor has the
+ * svn:externals definition, only the local path at which the external is
+ * supposed to be checked out is queried.
+ * Arguments:
+ * ?1: wc_id.
+ * ?2: the target path, local relpath inside ?1.
+ *
+ * ### NOTE: This statement deliberately removes file externals that live
+ * inside an unversioned dir, because commit still breaks on those.
+ * Once that's been fixed, the conditions below "--->8---" become obsolete. */
+-- STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW
+SELECT local_relpath, kind, def_repos_relpath,
+ (SELECT root FROM repository AS r WHERE r.id = e.repos_id)
+FROM externals e
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND def_revision IS NULL
+ AND repos_id = (SELECT repos_id
+ FROM nodes AS n
+ WHERE n.wc_id = ?1
+ AND n.local_relpath = ''
+ AND n.op_depth = 0)
+ AND ((kind='dir')
+ OR EXISTS (SELECT 1 FROM nodes
+ WHERE nodes.wc_id = e.wc_id
+ AND nodes.local_relpath = e.parent_relpath))
+
+-- STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW
+SELECT local_relpath, kind, def_repos_relpath,
+ (SELECT root FROM repository AS r WHERE r.id = e.repos_id)
+FROM externals e
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(e.local_relpath, ?2)
+ AND parent_relpath = ?2
+ AND def_revision IS NULL
+ AND repos_id = (SELECT repos_id
+ FROM nodes AS n
+ WHERE n.wc_id = ?1
+ AND n.local_relpath = ''
+ AND n.op_depth = 0)
+ AND ((kind='dir')
+ OR EXISTS (SELECT 1 FROM nodes
+ WHERE nodes.wc_id = e.wc_id
+ AND nodes.local_relpath = e.parent_relpath))
+
+-- STMT_SELECT_EXTERNALS_DEFINED
+SELECT local_relpath, def_local_relpath
+FROM externals
+/* ### The Sqlite optimizer needs help here ###
+ * WHERE wc_id = ?1
+ * AND (def_local_relpath = ?2
+ * OR IS_STRICT_DESCENDANT_OF(def_local_relpath, ?2)) */
+WHERE (wc_id = ?1 AND def_local_relpath = ?2)
+ OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(def_local_relpath, ?2))
+
+-- STMT_DELETE_EXTERNAL
+DELETE FROM externals
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_SELECT_EXTERNAL_PROPERTIES
+/* ### It would be nice if Sqlite would handle
+ * SELECT IFNULL((SELECT properties FROM actual_node a
+ * WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath),
+ * properties),
+ * local_relpath, depth
+ * FROM nodes_current n
+ * WHERE wc_id = ?1
+ * AND (local_relpath = ?2
+ * OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ * AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE)
+ * ### But it would take a double table scan execution plan for it.
+ * ### Maybe there is something else going on? */
+SELECT IFNULL((SELECT properties FROM actual_node a
+ WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath),
+ properties),
+ local_relpath, depth
+FROM nodes_current n
+WHERE wc_id = ?1 AND local_relpath = ?2
+ AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE)
+UNION ALL
+SELECT IFNULL((SELECT properties FROM actual_node a
+ WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath),
+ properties),
+ local_relpath, depth
+FROM nodes_current n
+WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE)
+
+-- STMT_SELECT_CURRENT_PROPS_RECURSIVE
+/* ### Ugly OR to make sqlite use the proper optimizations */
+SELECT IFNULL((SELECT properties FROM actual_node a
+ WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath),
+ properties),
+ local_relpath
+FROM nodes_current n
+WHERE (wc_id = ?1 AND local_relpath = ?2)
+ OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+
+-- STMT_PRAGMA_LOCKING_MODE
+PRAGMA locking_mode = exclusive
+
+/* ------------------------------------------------------------------------- */
+
+/* these are used in entries.c */
+
+-- STMT_INSERT_ACTUAL_NODE
+INSERT OR REPLACE INTO actual_node (
+ wc_id, local_relpath, parent_relpath, properties, changelist, conflict_data)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6)
+
+/* ------------------------------------------------------------------------- */
+
+/* these are used in upgrade.c */
+
+-- STMT_UPDATE_ACTUAL_CONFLICT_DATA
+UPDATE actual_node SET conflict_data = ?3
+WHERE wc_id = ?1 AND local_relpath = ?2
+
+-- STMT_INSERT_ACTUAL_CONFLICT_DATA
+INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath)
+VALUES (?1, ?2, ?3, ?4)
+
+-- STMT_SELECT_ALL_FILES
+SELECT local_relpath FROM nodes_current
+WHERE wc_id = ?1 AND parent_relpath = ?2 AND kind = MAP_FILE
+
+-- STMT_UPDATE_NODE_PROPS
+UPDATE nodes SET properties = ?4
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_PRAGMA_TABLE_INFO_NODES
+PRAGMA table_info("NODES")
+
+/* --------------------------------------------------------------------------
+ * Complex queries for callback walks, caching results in a temporary table.
+ *
+ * These target table are then used for joins against NODES, or for reporting
+ */
+
+-- STMT_CREATE_TARGET_PROP_CACHE
+DROP TABLE IF EXISTS target_prop_cache;
+CREATE TEMPORARY TABLE target_prop_cache (
+ local_relpath TEXT NOT NULL PRIMARY KEY,
+ kind TEXT NOT NULL,
+ properties BLOB
+);
+/* ### Need index?
+CREATE UNIQUE INDEX temp__node_props_cache_unique
+ ON temp__node_props_cache (local_relpath) */
+
+-- STMT_CACHE_TARGET_PROPS
+INSERT INTO target_prop_cache(local_relpath, kind, properties)
+ SELECT n.local_relpath, n.kind,
+ IFNULL((SELECT properties FROM actual_node AS a
+ WHERE a.wc_id = n.wc_id
+ AND a.local_relpath = n.local_relpath),
+ n.properties)
+ FROM targets_list AS t
+ JOIN nodes AS n
+ ON n.wc_id = ?1
+ AND n.local_relpath = t.local_relpath
+ AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3
+ WHERE n3.wc_id = ?1
+ AND n3.local_relpath = t.local_relpath)
+ WHERE t.wc_id = ?1
+ AND (presence=MAP_NORMAL OR presence=MAP_INCOMPLETE)
+ ORDER BY t.local_relpath
+
+-- STMT_CACHE_TARGET_PRISTINE_PROPS
+INSERT INTO target_prop_cache(local_relpath, kind, properties)
+ SELECT n.local_relpath, n.kind,
+ CASE n.presence
+ WHEN MAP_BASE_DELETED
+ THEN (SELECT properties FROM nodes AS p
+ WHERE p.wc_id = n.wc_id
+ AND p.local_relpath = n.local_relpath
+ AND p.op_depth < n.op_depth
+ ORDER BY p.op_depth DESC /* LIMIT 1 */)
+ ELSE properties END
+ FROM targets_list AS t
+ JOIN nodes AS n
+ ON n.wc_id = ?1
+ AND n.local_relpath = t.local_relpath
+ AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3
+ WHERE n3.wc_id = ?1
+ AND n3.local_relpath = t.local_relpath)
+ WHERE t.wc_id = ?1
+ AND (presence = MAP_NORMAL
+ OR presence = MAP_INCOMPLETE
+ OR presence = MAP_BASE_DELETED)
+ ORDER BY t.local_relpath
+
+-- STMT_SELECT_ALL_TARGET_PROP_CACHE
+SELECT local_relpath, properties FROM target_prop_cache
+ORDER BY local_relpath
+
+-- STMT_DROP_TARGET_PROP_CACHE
+DROP TABLE target_prop_cache;
+
+-- STMT_CREATE_REVERT_LIST
+DROP TABLE IF EXISTS revert_list;
+CREATE TEMPORARY TABLE revert_list (
+ /* need wc_id if/when revert spans multiple working copies */
+ local_relpath TEXT NOT NULL,
+ actual INTEGER NOT NULL, /* 1 if an actual row, 0 if a nodes row */
+ conflict_data BLOB,
+ notify INTEGER, /* 1 if an actual row had props or tree conflict */
+ op_depth INTEGER,
+ repos_id INTEGER,
+ kind TEXT,
+ PRIMARY KEY (local_relpath, actual)
+ );
+DROP TRIGGER IF EXISTS trigger_revert_list_nodes;
+CREATE TEMPORARY TRIGGER trigger_revert_list_nodes
+BEFORE DELETE ON nodes
+BEGIN
+ INSERT OR REPLACE INTO revert_list(local_relpath, actual, op_depth,
+ repos_id, kind)
+ SELECT OLD.local_relpath, 0, OLD.op_depth, OLD.repos_id, OLD.kind;
+END;
+DROP TRIGGER IF EXISTS trigger_revert_list_actual_delete;
+CREATE TEMPORARY TRIGGER trigger_revert_list_actual_delete
+BEFORE DELETE ON actual_node
+BEGIN
+ INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data,
+ notify)
+ SELECT OLD.local_relpath, 1, OLD.conflict_data,
+ CASE
+ WHEN OLD.properties IS NOT NULL
+ THEN 1
+ WHEN NOT EXISTS(SELECT 1 FROM NODES n
+ WHERE n.wc_id = OLD.wc_id
+ AND n.local_relpath = OLD.local_relpath)
+ THEN 1
+ ELSE NULL
+ END;
+END;
+DROP TRIGGER IF EXISTS trigger_revert_list_actual_update;
+CREATE TEMPORARY TRIGGER trigger_revert_list_actual_update
+BEFORE UPDATE ON actual_node
+BEGIN
+ INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data,
+ notify)
+ SELECT OLD.local_relpath, 1, OLD.conflict_data,
+ CASE
+ WHEN OLD.properties IS NOT NULL
+ THEN 1
+ WHEN NOT EXISTS(SELECT 1 FROM NODES n
+ WHERE n.wc_id = OLD.wc_id
+ AND n.local_relpath = OLD.local_relpath)
+ THEN 1
+ ELSE NULL
+ END;
+END
+
+-- STMT_DROP_REVERT_LIST_TRIGGERS
+DROP TRIGGER trigger_revert_list_nodes;
+DROP TRIGGER trigger_revert_list_actual_delete;
+DROP TRIGGER trigger_revert_list_actual_update
+
+-- STMT_SELECT_REVERT_LIST
+SELECT actual, notify, kind, op_depth, repos_id, conflict_data
+FROM revert_list
+WHERE local_relpath = ?1
+ORDER BY actual DESC
+
+-- STMT_SELECT_REVERT_LIST_COPIED_CHILDREN
+SELECT local_relpath, kind
+FROM revert_list
+WHERE IS_STRICT_DESCENDANT_OF(local_relpath, ?1)
+ AND op_depth >= ?2
+ AND repos_id IS NOT NULL
+ORDER BY local_relpath
+
+-- STMT_DELETE_REVERT_LIST
+DELETE FROM revert_list WHERE local_relpath = ?1
+
+-- STMT_SELECT_REVERT_LIST_RECURSIVE
+SELECT DISTINCT local_relpath
+FROM revert_list
+WHERE (local_relpath = ?1
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?1))
+ AND (notify OR actual = 0)
+ORDER BY local_relpath
+
+-- STMT_DELETE_REVERT_LIST_RECURSIVE
+DELETE FROM revert_list
+WHERE (local_relpath = ?1
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?1))
+
+-- STMT_DROP_REVERT_LIST
+DROP TABLE IF EXISTS revert_list
+
+-- STMT_CREATE_DELETE_LIST
+DROP TABLE IF EXISTS delete_list;
+CREATE TEMPORARY TABLE delete_list (
+/* ### we should put the wc_id in here in case a delete spans multiple
+ ### working copies. queries, etc will need to be adjusted. */
+ local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE
+ )
+
+/* This matches the selection in STMT_INSERT_DELETE_FROM_NODE_RECURSIVE.
+ A subquery is used instead of nodes_current to avoid a table scan */
+-- STMT_INSERT_DELETE_LIST
+INSERT INTO delete_list(local_relpath)
+SELECT local_relpath FROM nodes AS n
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth >= ?3
+ AND op_depth = (SELECT MAX(s.op_depth) FROM nodes AS s
+ WHERE s.wc_id = ?1
+ AND s.local_relpath = n.local_relpath)
+ AND presence NOT IN (MAP_BASE_DELETED, MAP_NOT_PRESENT, MAP_EXCLUDED, MAP_SERVER_EXCLUDED)
+ AND file_external IS NULL
+
+-- STMT_SELECT_DELETE_LIST
+SELECT local_relpath FROM delete_list
+ORDER BY local_relpath
+
+-- STMT_FINALIZE_DELETE
+DROP TABLE IF EXISTS delete_list
+
+-- STMT_CREATE_UPDATE_MOVE_LIST
+DROP TABLE IF EXISTS update_move_list;
+CREATE TEMPORARY TABLE update_move_list (
+/* ### we should put the wc_id in here in case a move update spans multiple
+ ### working copies. queries, etc will need to be adjusted. */
+ local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE,
+ action INTEGER NOT NULL,
+ kind INTEGER NOT NULL,
+ content_state INTEGER NOT NULL,
+ prop_state INTEGER NOT NULL
+ )
+
+-- STMT_INSERT_UPDATE_MOVE_LIST
+INSERT INTO update_move_list(local_relpath, action, kind, content_state,
+ prop_state)
+VALUES (?1, ?2, ?3, ?4, ?5)
+
+-- STMT_SELECT_UPDATE_MOVE_LIST
+SELECT local_relpath, action, kind, content_state, prop_state
+FROM update_move_list
+ORDER BY local_relpath
+
+-- STMT_FINALIZE_UPDATE_MOVE
+DROP TABLE IF EXISTS update_move_list
+
+/* ------------------------------------------------------------------------- */
+
+/* Queries for revision status. */
+
+-- STMT_SELECT_MIN_MAX_REVISIONS
+SELECT MIN(revision), MAX(revision),
+ MIN(changed_revision), MAX(changed_revision) FROM nodes
+ WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND presence IN (MAP_NORMAL, MAP_INCOMPLETE)
+ AND file_external IS NULL
+ AND op_depth = 0
+
+-- STMT_HAS_SPARSE_NODES
+SELECT 1 FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = 0
+ AND (presence IN (MAP_SERVER_EXCLUDED, MAP_EXCLUDED)
+ OR depth NOT IN (MAP_DEPTH_INFINITY, MAP_DEPTH_UNKNOWN))
+ AND file_external IS NULL
+LIMIT 1
+
+-- STMT_SUBTREE_HAS_TREE_MODIFICATIONS
+SELECT 1 FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth > 0
+LIMIT 1
+
+-- STMT_SUBTREE_HAS_PROP_MODIFICATIONS
+SELECT 1 FROM actual_node
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND properties IS NOT NULL
+LIMIT 1
+
+-- STMT_HAS_SWITCHED
+SELECT 1
+FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ AND file_external IS NULL
+ AND presence IN (MAP_NORMAL, MAP_INCOMPLETE)
+ AND repos_path IS NOT RELPATH_SKIP_JOIN(?2, ?3, local_relpath)
+LIMIT 1
+
+-- STMT_SELECT_BASE_FILES_RECURSIVE
+SELECT local_relpath, translated_size, last_mod_time FROM nodes AS n
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = 0
+ AND kind=MAP_FILE
+ AND presence=MAP_NORMAL
+ AND file_external IS NULL
+
+/* ### FIXME: op-depth? What about multiple moves? */
+-- STMT_SELECT_MOVED_FROM_RELPATH
+SELECT local_relpath, op_depth FROM nodes
+WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth > 0
+
+-- STMT_UPDATE_MOVED_TO_RELPATH
+UPDATE nodes SET moved_to = ?4
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_CLEAR_MOVED_TO_RELPATH
+UPDATE nodes SET moved_to = NULL
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
+
+-- STMT_CLEAR_MOVED_HERE_RECURSIVE
+UPDATE nodes SET moved_here = NULL
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+/* This statement returns pairs of move-roots below the path ?2 in WC_ID ?1.
+ * Each row returns a moved-here path (always a child of ?2) in the first
+ * column, and its matching moved-away (deleted) path in the second column. */
+-- STMT_SELECT_MOVED_HERE_CHILDREN
+SELECT moved_to, local_relpath FROM nodes
+WHERE wc_id = ?1 AND op_depth > 0
+ AND IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+
+-- STMT_SELECT_MOVED_FOR_DELETE
+SELECT local_relpath, moved_to, op_depth FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND moved_to IS NOT NULL
+ AND op_depth >= (SELECT MAX(op_depth) FROM nodes o
+ WHERE o.wc_id = ?1
+ AND o.local_relpath = ?2)
+
+-- STMT_UPDATE_MOVED_TO_DESCENDANTS
+UPDATE nodes SET moved_to = RELPATH_SKIP_JOIN(?2, ?3, moved_to)
+ WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+
+-- STMT_CLEAR_MOVED_TO_DESCENDANTS
+UPDATE nodes SET moved_to = NULL
+ WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+
+
+/* This statement returns pairs of move-roots below the path ?2 in WC_ID ?1,
+ * where the source of the move is within the subtree rooted at path ?2, and
+ * the destination of the move is outside the subtree rooted at path ?2. */
+-- STMT_SELECT_MOVED_PAIR2
+SELECT local_relpath, moved_to, op_depth FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND moved_to IS NOT NULL
+ AND NOT IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+ AND op_depth >= (SELECT MAX(op_depth) FROM nodes o
+ WHERE o.wc_id = ?1
+ AND o.local_relpath = ?2)
+
+-- STMT_SELECT_MOVED_PAIR3
+SELECT local_relpath, moved_to, op_depth, kind FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth > ?3
+ AND moved_to IS NOT NULL
+
+-- STMT_SELECT_MOVED_OUTSIDE
+SELECT local_relpath, moved_to FROM nodes
+WHERE wc_id = ?1
+ AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth >= ?3
+ AND moved_to IS NOT NULL
+ AND NOT IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+
+-- STMT_SELECT_OP_DEPTH_MOVED_PAIR
+SELECT n.local_relpath, n.moved_to,
+ (SELECT o.repos_path FROM nodes AS o
+ WHERE o.wc_id = n.wc_id
+ AND o.local_relpath = n.local_relpath
+ AND o.op_depth < ?3 ORDER BY o.op_depth DESC LIMIT 1)
+FROM nodes AS n
+WHERE n.wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2)
+ AND n.op_depth = ?3
+ AND n.moved_to IS NOT NULL
+
+-- STMT_SELECT_MOVED_DESCENDANTS
+SELECT n.local_relpath, h.moved_to
+FROM nodes n, nodes h
+WHERE n.wc_id = ?1
+ AND h.wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2)
+ AND h.local_relpath = n.local_relpath
+ AND n.op_depth = ?3
+ AND h.op_depth = (SELECT MIN(o.op_depth)
+ FROM nodes o
+ WHERE o.wc_id = ?1
+ AND o.local_relpath = n.local_relpath
+ AND o.op_depth > ?3)
+ AND h.moved_to IS NOT NULL
+
+-- STMT_COMMIT_UPDATE_ORIGIN
+/* Note that the only reason this SUBSTR() trick is valid is that you
+ can move neither the working copy nor the repository root.
+
+ SUBSTR(local_relpath, LENGTH(?2)+1) includes the '/' of the path */
+UPDATE nodes SET repos_id = ?4,
+ repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1),
+ revision = ?6
+WHERE wc_id = ?1
+ AND (local_relpath = ?2
+ OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+ AND op_depth = ?3
+
+-- STMT_HAS_LAYER_BETWEEN
+SELECT 1 FROM NODES
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 AND op_depth < ?4
+
+-- STMT_SELECT_REPOS_PATH_REVISION
+SELECT local_relpath, repos_path, revision FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ORDER BY local_relpath
+
+-- STMT_SELECT_HAS_NON_FILE_CHILDREN
+SELECT 1 FROM nodes
+WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 AND kind != MAP_FILE
+
+-- STMT_SELECT_HAS_GRANDCHILDREN
+SELECT 1 FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(parent_relpath, ?2)
+ AND op_depth = 0
+ AND file_external IS NULL
+
+/* ------------------------------------------------------------------------- */
+
+/* Queries for verification. */
+
+-- STMT_SELECT_ALL_NODES
+SELECT op_depth, local_relpath, parent_relpath, file_external FROM nodes
+WHERE wc_id = ?1
+
+/* ------------------------------------------------------------------------- */
+
+/* Queries for cached inherited properties. */
+
+/* Select the inherited properties of a single base node. */
+-- STMT_SELECT_IPROPS
+SELECT inherited_props FROM nodes
+WHERE wc_id = ?1
+ AND local_relpath = ?2
+ AND op_depth = 0
+
+/* Update the inherited properties of a single base node. */
+-- STMT_UPDATE_IPROP
+UPDATE nodes
+SET inherited_props = ?3
+WHERE (wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0)
+
+/* Select a single path if its base node has cached inherited properties. */
+-- STMT_SELECT_IPROPS_NODE
+SELECT local_relpath, repos_path FROM nodes
+WHERE wc_id = ?1
+ AND local_relpath = ?2
+ AND op_depth = 0
+ AND (inherited_props not null)
+
+/* Select all paths whose base nodes are below a given path, which
+ have cached inherited properties. */
+-- STMT_SELECT_IPROPS_RECURSIVE
+SELECT local_relpath, repos_path FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ AND (inherited_props not null)
+
+-- STMT_SELECT_IPROPS_CHILDREN
+SELECT local_relpath, repos_path FROM nodes
+WHERE wc_id = ?1
+ AND parent_relpath = ?2
+ AND op_depth = 0
+ AND (inherited_props not null)
+
+/* ------------------------------------------------------------------------- */
+
+/* Grab all the statements related to the schema. */
+
+-- include: wc-metadata
+-- include: wc-checks
diff --git a/contrib/subversion/subversion/libsvn_wc/wc.h b/contrib/subversion/subversion/libsvn_wc/wc.h
new file mode 100644
index 0000000..9438e2b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc.h
@@ -0,0 +1,808 @@
+/*
+ * wc.h : shared stuff internal to the svn_wc library.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVN_LIBSVN_WC_H
+#define SVN_LIBSVN_WC_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_wc.h"
+
+#include "private/svn_sqlite.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_skel.h"
+
+#include "wc_db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define SVN_WC__PROP_REJ_EXT ".prej"
+
+/* We can handle this format or anything lower, and we (should) error
+ * on anything higher.
+ *
+ * There is no format version 0; we started with 1.
+ *
+ * The bump to 2 introduced the ".svn-work" extension. For example,
+ * ".svn/props/foo" became ".svn/props/foo.svn-work".
+ *
+ * The bump to 3 introduced the entry attribute
+ * old-and-busted.c::ENTRIES_ATTR_ABSENT.
+ *
+ * The bump to 4 renamed the magic "svn:this_dir" entry name to "".
+ *
+ * == 1.0.x shipped with format 4
+ * == 1.1.x shipped with format 4
+ * == 1.2.x shipped with format 4
+ * == 1.3.x shipped with format 4
+ *
+ * The bump to 5 added support for replacing files with history (the
+ * "revert base"). This was introduced in 1.4.0, but buggy until 1.4.6.
+ *
+ * The bump to 6 introduced caching of property modification state and
+ * certain properties in the entries file.
+ *
+ * The bump to 7 changed the entries file format from XML to a custom
+ * text-based format.
+ *
+ * The bump to 8 placed wcprops in one file per directory (named
+ * upgrade.c::WCPROPS_ALL_DATA)
+ *
+ * == 1.4.x shipped with format 8
+ *
+ * The bump to 9 added changelists, keep-local, and sticky depth (for
+ * selective/sparse checkouts) to each entry.
+ *
+ * == 1.5.x shipped with format 9
+ *
+ * The bump to 10 added tree-conflicts, file externals and a different
+ * canonicalization of urls.
+ *
+ * == 1.6.x shipped with format 10
+ *
+ * The bump to 11 cleared the has_props, has_prop_mods, cachable_props,
+ * and present_props values in the entries file. Older clients expect
+ * proper values for these fields.
+ *
+ * The bump to 12 switched from 'entries' to the SQLite database 'wc.db'.
+ *
+ * The bump to 13 added the WORK_QUEUE table into 'wc.db', moved the
+ * wcprops into the 'dav_cache' column in BASE_NODE, and stopped using
+ * the 'incomplete_children' column of BASE_NODE.
+ *
+ * The bump to 14 added the WCLOCKS table (and migrated locks from the
+ * filesystem into wc.db), and some columns to ACTUAL_NODE for future
+ * use.
+ *
+ * The bump to 15 switched from depth='exclude' on directories to using
+ * presence='exclude' within the BASE_NODE and WORKING_NODE tables.
+ * This change also enabled exclude support on files and symlinks.
+ *
+ * The bump to 16 added 'locked_levels' to WC_LOCK, setting any existing
+ * locks to a level of 0. The 'md5_checksum' column was added to PRISTINE
+ * for future use.
+ *
+ * The bump to 17 added a '.svn/pristine' dir and moved the text bases into
+ * the Pristine Store (the PRISTINE table and '.svn/pristine' dir), and
+ * removed the '/.svn/text-base' dir.
+ *
+ * The bump to 18 moved the properties from separate files in the props and
+ * prop-base directory (and .svn for the dir itself) into the wc.db file,
+ * and then removed the props and prop-base dir.
+ *
+ * The bump to 19 introduced the 'single DB' per working copy. All metadata
+ * is held in a single '.svn/wc.db' in the root directory of the working
+ * copy. Bumped in r991236.
+ *
+ * The bump to 20 introduced NODES and drops BASE_NODE and WORKING_NODE,
+ * op_depth is always 0 or 2. Bumped in r1005388.
+ *
+ * The bump to 21 moved tree conflict storage from the parent to the
+ * conflicted node. Bumped in r1034436.
+ *
+ * The bump to 22 moved tree conflict storage from conflict_data column
+ * to the tree_conflict_data column. Bumped in r1040255.
+ *
+ * The bump to 23 introduced multi-layer op_depth processing for NODES.
+ * Bumped in r1044384.
+ *
+ * The bump to 24 started using the 'refcount' column of the PRISTINE table
+ * correctly, instead of always setting it to '1'. Bumped in r1058523.
+ *
+ * The bump to 25 introduced the NODES_CURRENT view. Bumped in r1071283.
+ *
+ * The bump to 26 introduced the NODES_BASE view. Bumped in r1076617.
+ *
+ * The bump to 27 stored conflict files as relpaths rather than basenames.
+ * Bumped in r1089593.
+ *
+ * The bump to 28 converted any remaining references to MD5 checksums
+ * to SHA1 checksums. Bumped in r1095214.
+ *
+ * The bump to 29 renamed the pristine files from '<SHA1>' to '<SHA1>.svn-base'
+ * and introduced the EXTERNALS store. Bumped in r1129286.
+ *
+ * == 1.7.x shipped with format 29
+ *
+ * The bump to 30 switched the conflict storage to a skel inside conflict_data.
+ * Also clears some known invalid state. Bumped in r1387742.
+ *
+ * The bump to 31 added the inherited_props column in the NODES table.
+ * Bumped in r1395109.
+ *
+ * Please document any further format changes here.
+ */
+
+#define SVN_WC__VERSION 31
+
+
+/* Formats <= this have no concept of "revert text-base/props". */
+#define SVN_WC__NO_REVERT_FILES 4
+
+/* A version <= this has wcprops stored in one file per entry. */
+#define SVN_WC__WCPROPS_MANY_FILES_VERSION 7
+
+/* A version < this can have urls that aren't canonical according to the new
+ rules. See issue #2475. */
+#define SVN_WC__CHANGED_CANONICAL_URLS 10
+
+/* The format number written to wc-ng working copies so that old clients
+ can recognize them as "newer Subversion"'s working copies. */
+#define SVN_WC__NON_ENTRIES 12
+#define SVN_WC__NON_ENTRIES_STRING "12\n"
+
+/* A version < this uses the old 'entries' file mechanism. */
+#define SVN_WC__WC_NG_VERSION 12
+
+/* In this version, the wcprops are "lost" between files and wc.db. We want
+ to ignore them in upgrades. */
+#define SVN_WC__WCPROPS_LOST 12
+
+/* A version < this has no work queue (see workqueue.h). */
+#define SVN_WC__HAS_WORK_QUEUE 13
+
+/* Return a string indicating the released version (or versions) of
+ * Subversion that used WC format number WC_FORMAT, or some other
+ * suitable string if no released version used WC_FORMAT.
+ *
+ * ### It's not ideal to encode this sort of knowledge in this low-level
+ * library. On the other hand, it doesn't need to be updated often and
+ * should be easily found when it does need to be updated. */
+const char *
+svn_wc__version_string_from_format(int wc_format);
+
+/* Return true iff error E indicates an "is not a working copy" type
+ of error, either because something wasn't a working copy at all, or
+ because it's a working copy from a previous version (in need of
+ upgrade). */
+#define SVN_WC__ERR_IS_NOT_CURRENT_WC(e) \
+ ((e->apr_err == SVN_ERR_WC_NOT_WORKING_COPY) || \
+ (e->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED))
+
+
+
+/*** Context handling ***/
+struct svn_wc_context_t
+{
+ /* The wc_db handle for this working copy. */
+ svn_wc__db_t *db;
+
+ /* Close the DB when we destroy this context?
+ (This is used inside backward compat wrappers, and should only be
+ modified by the proper create() functions. */
+ svn_boolean_t close_db_on_destroy;
+
+ /* The state pool for this context. */
+ apr_pool_t *state_pool;
+};
+
+/**
+ * Just like svn_wc_context_create(), only use the provided DB to construct
+ * the context.
+ *
+ * Even though DB is not allocated from the same pool at *WC_CTX, it is
+ * expected to remain open throughout the life of *WC_CTX.
+ */
+svn_error_t *
+svn_wc__context_create_with_db(svn_wc_context_t **wc_ctx,
+ svn_config_t *config,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool);
+
+
+/*** Committed Queue ***/
+
+/**
+ * Return the pool associated with QUEUE. (This so we can keep some
+ * deprecated functions that need to peek inside the QUEUE struct in
+ * deprecated.c).
+ */
+apr_pool_t *
+svn_wc__get_committed_queue_pool(const struct svn_wc_committed_queue_t *queue);
+
+
+/** Internal helper for svn_wc_process_committed_queue2().
+ *
+ * ### If @a queue is NULL, then ...?
+ * ### else:
+ * Bump an item from @a queue (the one associated with @a
+ * local_abspath) to @a new_revnum after a commit succeeds, recursing
+ * if @a recurse is set.
+ *
+ * @a new_date is the (server-side) date of the new revision, or 0.
+ *
+ * @a rev_author is the (server-side) author of the new
+ * revision; it may be @c NULL.
+ *
+ * @a new_dav_cache is a hash of dav property changes to be made to
+ * the @a local_abspath.
+ * ### [JAF] Is it? See svn_wc_queue_committed3(). It ends up being
+ * ### assigned as a whole to wc.db:BASE_NODE:dav_cache.
+ *
+ * If @a no_unlock is set, don't release any user locks on @a
+ * local_abspath; otherwise release them as part of this processing.
+ *
+ * If @a keep_changelist is set, don't remove any changeset assignments
+ * from @a local_abspath; otherwise, clear it of such assignments.
+ *
+ * If @a sha1_checksum is non-NULL, use it to identify the node's pristine
+ * text.
+ *
+ * Set TOP_OF_RECURSE to TRUE to show that this the top of a possibly
+ * recursive commit operation.
+ */
+svn_error_t *
+svn_wc__process_committed_internal(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t recurse,
+ svn_boolean_t top_of_recurse,
+ svn_revnum_t new_revnum,
+ apr_time_t new_date,
+ const char *rev_author,
+ apr_hash_t *new_dav_cache,
+ svn_boolean_t no_unlock,
+ svn_boolean_t keep_changelist,
+ const svn_checksum_t *sha1_checksum,
+ const svn_wc_committed_queue_t *queue,
+ apr_pool_t *scratch_pool);
+
+
+/*** Update traversals. ***/
+
+struct svn_wc_traversal_info_t
+{
+ /* The pool in which this structure and everything inside it is
+ allocated. */
+ apr_pool_t *pool;
+
+ /* The before and after values of the SVN_PROP_EXTERNALS property,
+ * for each directory on which that property changed. These have
+ * the same layout as those returned by svn_wc_edited_externals().
+ *
+ * The hashes, their keys, and their values are allocated in the
+ * above pool.
+ */
+ apr_hash_t *externals_old;
+ apr_hash_t *externals_new;
+
+ /* The ambient depths of the working copy directories. The keys are
+ working copy paths (as for svn_wc_edited_externals()), the values
+ are the result of svn_depth_to_word(depth_of_each_dir). */
+ apr_hash_t *depths;
+};
+
+
+/*** Names and file/dir operations in the administrative area. ***/
+
+/** The files within the administrative subdir. **/
+#define SVN_WC__ADM_FORMAT "format"
+#define SVN_WC__ADM_ENTRIES "entries"
+#define SVN_WC__ADM_TMP "tmp"
+#define SVN_WC__ADM_PRISTINE "pristine"
+#define SVN_WC__ADM_NONEXISTENT_PATH "nonexistent-path"
+
+/* The basename of the ".prej" file, if a directory ever has property
+ conflicts. This .prej file will appear *within* the conflicted
+ directory. */
+#define SVN_WC__THIS_DIR_PREJ "dir_conflicts"
+
+
+/* A few declarations for stuff in util.c.
+ * If this section gets big, move it all out into a new util.h file. */
+
+/* Ensure that DIR exists. */
+svn_error_t *svn_wc__ensure_directory(const char *path, apr_pool_t *pool);
+
+
+/* Return a hash keyed by 'const char *' property names and with
+ 'svn_string_t *' values built from PROPS (which is an array of
+ pointers to svn_prop_t's) or to NULL if PROPS is NULL or empty.
+ PROPS items which lack a value will be ignored. If PROPS contains
+ multiple properties with the same name, each successive such item
+ reached in a walk from the beginning to the end of the array will
+ overwrite the previous in the returned hash.
+
+ NOTE: While the returned hash will be allocated in RESULT_POOL, the
+ items it holds will share storage with those in PROPS.
+
+ ### This is rather the reverse of svn_prop_hash_to_array(), except
+ ### that function's arrays contains svn_prop_t's, whereas this
+ ### one's contains *pointers* to svn_prop_t's. So much for
+ ### consistency. */
+apr_hash_t *
+svn_wc__prop_array_to_hash(const apr_array_header_t *props,
+ apr_pool_t *result_pool);
+
+
+/* Set *MODIFIED_P to non-zero if LOCAL_ABSPATH's text is modified with
+ * regard to the base revision, else set *MODIFIED_P to zero.
+ *
+ * If EXACT_COMPARISON is FALSE, translate LOCAL_ABSPATH's EOL
+ * style and keywords to repository-normal form according to its properties,
+ * and compare the result with the text base.
+ * Usually, EXACT_COMPARISON should be FALSE.
+ *
+ * If LOCAL_ABSPATH does not exist, consider it unmodified. If it exists
+ * but is not under revision control (not even scheduled for
+ * addition), return the error SVN_WC_PATH_NOT_FOUND.
+ *
+ * If the text is unmodified and a write-lock is held this function
+ * will ensure that the last-known-unmodified timestamp and
+ * filesize of the file as recorded in DB matches the corresponding
+ * attributes of the actual file. (This is often referred to as
+ * "timestamp repair", and serves to help future unforced is-modified
+ * checks return quickly if the file remains untouched.)
+ */
+svn_error_t *
+svn_wc__internal_file_modified_p(svn_boolean_t *modified_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t exact_comparison,
+ apr_pool_t *scratch_pool);
+
+
+/* Prepare to merge a file content change into the working copy.
+
+ This does not merge properties; see svn_wc__merge_props() for that.
+ This does not necessarily change the file TARGET_ABSPATH on disk; it
+ may instead return work items that will replace the file on disk when
+ they are run. ### Can we be more consistent about this?
+
+ Merge the difference between LEFT_ABSPATH and RIGHT_ABSPATH into
+ TARGET_ABSPATH.
+
+ Set *WORK_ITEMS to the appropriate work queue operations.
+
+ If there are any conflicts, append a conflict description to
+ *CONFLICT_SKEL. (First allocate *CONFLICT_SKEL from RESULT_POOL if
+ it is initially NULL. CONFLICT_SKEL itself must not be NULL.)
+ Also, unless it is considered to be a 'binary' file, mark any
+ conflicts in the text of the file TARGET_ABSPATH using LEFT_LABEL,
+ RIGHT_LABEL and TARGET_LABEL.
+
+ Set *MERGE_OUTCOME to indicate the result.
+
+ When DRY_RUN is true, no actual changes are made to the working copy.
+
+ If DIFF3_CMD is specified, the given external diff3 tool will
+ be used instead of our built in diff3 routines.
+
+ When MERGE_OPTIONS are specified, they are used by the internal
+ diff3 routines, or passed to the external diff3 tool.
+
+ WRI_ABSPATH describes in which working copy information should be
+ retrieved. (Interesting for merging file externals).
+
+ OLD_ACTUAL_PROPS is the set of actual properties before merging; used for
+ detranslating the file before merging. This is necessary because, in
+ the case of updating, the update can have sent new properties, so we
+ cannot simply fetch and use the current actual properties.
+
+ ### Is OLD_ACTUAL_PROPS still necessary, now that we first prepare the
+ content change and property change and then apply them both to
+ the WC together?
+
+ Property changes sent by the update are provided in PROP_DIFF.
+
+ For a complete description, see svn_wc_merge5() for which this is
+ the (loggy) implementation.
+
+ *WORK_ITEMS will be allocated in RESULT_POOL. All temporary allocations
+ will be performed in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__internal_merge(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ enum svn_wc_merge_outcome_t *merge_outcome,
+ svn_wc__db_t *db,
+ const char *left_abspath,
+ const char *right_abspath,
+ const char *target_abspath,
+ const char *wri_abspath,
+ const char *left_label,
+ const char *right_label,
+ const char *target_label,
+ apr_hash_t *old_actual_props,
+ svn_boolean_t dry_run,
+ const char *diff3_cmd,
+ const apr_array_header_t *merge_options,
+ const apr_array_header_t *prop_diff,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* A default error handler for svn_wc_walk_entries3(). Returns ERR in
+ all cases. */
+svn_error_t *
+svn_wc__walker_default_error_handler(const char *path,
+ svn_error_t *err,
+ void *walk_baton,
+ apr_pool_t *pool);
+
+/* Set *EDITOR and *EDIT_BATON to an ambient-depth-based filtering
+ * editor that wraps WRAPPED_EDITOR and WRAPPED_BATON. This is only
+ * required for operations where the requested depth is @c
+ * svn_depth_unknown and the server's editor driver doesn't understand
+ * depth. It is safe for *EDITOR and *EDIT_BATON to start as
+ * WRAPPED_EDITOR and WRAPPED_BATON.
+ *
+ * ANCHOR, TARGET, and DB are as in svn_wc_get_update_editor3.
+ *
+ * @a requested_depth must be one of the following depth values:
+ * @c svn_depth_infinity, @c svn_depth_empty, @c svn_depth_files,
+ * @c svn_depth_immediates, or @c svn_depth_unknown.
+ *
+ * Allocations are done in POOL.
+ */
+svn_error_t *
+svn_wc__ambient_depth_filter_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_wc__db_t *db,
+ const char *anchor_abspath,
+ const char *target,
+ const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_edit_baton,
+ apr_pool_t *result_pool);
+
+
+/* Similar to svn_wc_conflicted_p3(), but with a wc_db parameter in place of
+ * a wc_context. */
+svn_error_t *
+svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p,
+ svn_boolean_t *prop_conflicted_p,
+ svn_boolean_t *tree_conflicted_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Similar to svn_wc__internal_conflicted_p(), but ignores
+ * moved-away-edit tree conflicts. If CONFLICT_IGNORED_P is not NULL
+ * then sets *CONFLICT_IGNORED_P TRUE if a tree-conflict is ignored
+ * and FALSE otherwise. Also ignores text and property conflicts if
+ * TREE_ONLY is TRUE */
+svn_error_t *
+svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p,
+ svn_boolean_t *conflict_ignored_p,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t tree_only,
+ apr_pool_t *scratch_pool);
+
+
+/* Internal version of svn_wc_transmit_text_deltas3(). */
+svn_error_t *
+svn_wc__internal_transmit_text_deltas(const char **tempfile,
+ const svn_checksum_t **new_text_base_md5_checksum,
+ const svn_checksum_t **new_text_base_sha1_checksum,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t fulltext,
+ const svn_delta_editor_t *editor,
+ void *file_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc_transmit_prop_deltas2(). */
+svn_error_t *
+svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_delta_editor_t *editor,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+/* Library-internal version of svn_wc_ensure_adm4(). */
+svn_error_t *
+svn_wc__internal_ensure_adm(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *url,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool);
+
+
+/* Library-internal version of svn_wc__changelist_match(). */
+svn_boolean_t
+svn_wc__internal_changelist_match(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *clhash,
+ apr_pool_t *scratch_pool);
+
+/* Library-internal version of svn_wc_walk_status(), which see. */
+svn_error_t *
+svn_wc__internal_walk_status(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t get_all,
+ svn_boolean_t no_ignore,
+ svn_boolean_t ignore_text_mods,
+ const apr_array_header_t *ignore_patterns,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/** A callback invoked by the generic node-walker function. */
+typedef svn_error_t *(*svn_wc__node_found_func_t)(const char *local_abspath,
+ svn_node_kind_t kind,
+ void *walk_baton,
+ apr_pool_t *scratch_pool);
+
+/* Call @a walk_callback with @a walk_baton for @a local_abspath and all
+ nodes underneath it, restricted by @a walk_depth, and possibly
+ @a changelists.
+
+ If @a show_hidden is true, include hidden nodes, else ignore them.
+ If CHANGELISTS is non-NULL and non-empty, filter thereon. */
+svn_error_t *
+svn_wc__internal_walk_children(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t show_hidden,
+ const apr_array_header_t *changelists,
+ svn_wc__node_found_func_t walk_callback,
+ void *walk_baton,
+ svn_depth_t walk_depth,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Library-internal version of svn_wc_remove_from_revision_control2,
+ which see.*/
+svn_error_t *
+svn_wc__internal_remove_from_revision_control(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t destroy_wf,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Library-internal version of svn_wc__node_get_schedule(). */
+svn_error_t *
+svn_wc__internal_node_get_schedule(svn_wc_schedule_t *schedule,
+ svn_boolean_t *copied,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc__node_get_origin() */
+svn_error_t *
+svn_wc__internal_get_origin(svn_boolean_t *is_copy,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **copy_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t scan_deleted,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc__node_get_repos_info() */
+svn_error_t *
+svn_wc__internal_get_repos_info(svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Upgrade the wc sqlite database given in SDB for the wc located at
+ WCROOT_ABSPATH. It's current/starting format is given by START_FORMAT.
+ After the upgrade is complete (to as far as the automatic upgrade will
+ perform), the resulting format is RESULT_FORMAT. All allocations are
+ performed in SCRATCH_POOL. */
+svn_error_t *
+svn_wc__upgrade_sdb(int *result_format,
+ const char *wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ int start_format,
+ apr_pool_t *scratch_pool);
+
+/* Create a conflict skel from the old separated data */
+svn_error_t *
+svn_wc__upgrade_conflict_skel_from_raw(svn_skel_t **conflicts,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_relpath,
+ const char *conflict_old,
+ const char *conflict_wrk,
+ const char *conflict_new,
+ const char *prej_file,
+ const char *tree_conflict_data,
+ apr_size_t tree_conflict_len,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__wipe_postupgrade(const char *dir_abspath,
+ svn_boolean_t whole_admin,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Ensure LOCAL_ABSPATH is still locked in DB. Returns the error
+ * SVN_ERR_WC_NOT_LOCKED if this is not the case.
+ */
+svn_error_t *
+svn_wc__write_check(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Read into CONFLICTS svn_wc_conflict_description2_t* structs
+ * for all conflicts that have LOCAL_ABSPATH as victim.
+ *
+ * Victim must be versioned or be part of a tree conflict.
+ *
+ * If CREATE_TEMPFILES is TRUE, create temporary files for property conflicts.
+ *
+ * Allocate *CONFLICTS in RESULT_POOL and do temporary allocations in
+ * SCRATCH_POOL
+ */
+svn_error_t *
+svn_wc__read_conflicts(const apr_array_header_t **conflicts,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t create_tempfiles,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Perform the actual merge of file changes between an original file,
+ identified by ORIGINAL_CHECKSUM (an empty file if NULL) to a new file
+ identified by NEW_CHECKSUM in the working copy identified by WRI_ABSPATH.
+
+ Merge the result into LOCAL_ABSPATH, which is part of the working copy
+ identified by WRI_ABSPATH. Use OLD_REVISION and TARGET_REVISION for naming
+ the intermediate files.
+
+ Set *FOUND_TEXT_CONFLICT to TRUE when the merge encountered a conflict,
+ otherwise to FALSE.
+
+ The rest of the arguments are passed to svn_wc__internal_merge.
+ */
+svn_error_t *
+svn_wc__perform_file_merge(svn_skel_t **work_items,
+ svn_skel_t **conflict_skel,
+ svn_boolean_t *found_conflict,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const svn_checksum_t *new_checksum,
+ const svn_checksum_t *original_checksum,
+ apr_hash_t *old_actual_props,
+ const apr_array_header_t *ext_patterns,
+ svn_revnum_t old_revision,
+ svn_revnum_t target_revision,
+ const apr_array_header_t *propchanges,
+ const char *diff3_cmd,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Couple of random helpers for the Ev2 shims.
+ ### These will eventually be obsoleted and removed. */
+struct svn_wc__shim_fetch_baton_t
+{
+ svn_wc__db_t *db;
+ const char *base_abspath;
+ svn_boolean_t fetch_base;
+};
+
+/* Using a BATON of struct shim_fetch_baton, return KIND for PATH. */
+svn_error_t *
+svn_wc__fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool);
+
+/* Using a BATON of struct shim_fetch_baton, return PROPS for PATH. */
+svn_error_t *
+svn_wc__fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Using a BATON of struct shim_fetch_baton, return a delta base for PATH. */
+svn_error_t *
+svn_wc__fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Find duplicate targets in *EXTERNALS, a list of svn_wc_external_item2_t*
+ * elements, and store each target string in *DUPLICATE_TARGETS as const
+ * char * elements. *DUPLICATE_TARGETS will be NULL if no duplicates were
+ * found. */
+svn_error_t *
+svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets,
+ apr_array_header_t *externals,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool);
+
+/* Revert tree LOCAL_ABSPATH to depth DEPTH and notify for all
+ reverts. */
+svn_error_t *
+svn_wc__revert_internal(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__node_has_local_mods(svn_boolean_t *modified,
+ svn_boolean_t *all_edits_are_deletes,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_WC_H */
diff --git a/contrib/subversion/subversion/libsvn_wc/wc_db.c b/contrib/subversion/subversion/libsvn_wc/wc_db.c
new file mode 100644
index 0000000..7e1e877
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc_db.c
@@ -0,0 +1,15050 @@
+/*
+ * wc_db.c : manipulating the administrative database
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include <assert.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_sorts.h"
+#include "svn_wc.h"
+#include "svn_checksum.h"
+#include "svn_pools.h"
+
+#include "wc.h"
+#include "wc_db.h"
+#include "adm_files.h"
+#include "wc-queries.h"
+#include "entries.h"
+#include "lock.h"
+#include "conflicts.h"
+#include "wc_db_private.h"
+#include "workqueue.h"
+#include "token-map.h"
+
+#include "svn_private_config.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_skel.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_token.h"
+
+
+#define NOT_IMPLEMENTED() SVN__NOT_IMPLEMENTED()
+
+
+/*
+ * Some filename constants.
+ */
+#define SDB_FILE "wc.db"
+
+#define WCROOT_TEMPDIR_RELPATH "tmp"
+
+
+/*
+ * PARAMETER ASSERTIONS
+ *
+ * Every (semi-)public entrypoint in this file has a set of assertions on
+ * the parameters passed into the function. Since this is a brand new API,
+ * we want to make sure that everybody calls it properly. The original WC
+ * code had years to catch stray bugs, but we do not have that luxury in
+ * the wc-nb rewrite. Any extra assurances that we can find will be
+ * welcome. The asserts will ensure we have no doubt about the values
+ * passed into the function.
+ *
+ * Some parameters are *not* specifically asserted. Typically, these are
+ * params that will be used immediately, so something like a NULL value
+ * will be obvious.
+ *
+ * ### near 1.7 release, it would be a Good Thing to review the assertions
+ * ### and decide if any can be removed or switched to assert() in order
+ * ### to remove their runtime cost in the production release.
+ *
+ *
+ * DATABASE OPERATIONS
+ *
+ * Each function should leave the database in a consistent state. If it
+ * does *not*, then the implication is some other function needs to be
+ * called to restore consistency. Subtle requirements like that are hard
+ * to maintain over a long period of time, so this API will not allow it.
+ *
+ *
+ * STANDARD VARIABLE NAMES
+ *
+ * db working copy database (this module)
+ * sdb SQLite database (not to be confused with 'db')
+ * wc_id a WCROOT id associated with a node
+ */
+
+#define INVALID_REPOS_ID ((apr_int64_t) -1)
+#define UNKNOWN_WC_ID ((apr_int64_t) -1)
+#define FORMAT_FROM_SDB (-1)
+
+/* Check if column number I, a property-skel column, contains a non-empty
+ set of properties. The empty set of properties is stored as "()", so we
+ have properties if the size of the column is larger than 2. */
+#define SQLITE_PROPERTIES_AVAILABLE(stmt, i) \
+ (svn_sqlite__column_bytes(stmt, i) > 2)
+
+int
+svn_wc__db_op_depth_for_upgrade(const char *local_relpath)
+{
+ return relpath_depth(local_relpath);
+}
+
+
+/* Representation of a new base row for the NODES table */
+typedef struct insert_base_baton_t {
+ /* common to all insertions into BASE */
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ svn_revnum_t revision;
+
+ /* Only used when repos_id == INVALID_REPOS_ID */
+ const char *repos_root_url;
+ const char *repos_uuid;
+
+ /* common to all "normal" presence insertions */
+ const apr_hash_t *props;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ const apr_hash_t *dav_cache;
+
+ /* for inserting directories */
+ const apr_array_header_t *children;
+ svn_depth_t depth;
+
+ /* for inserting files */
+ const svn_checksum_t *checksum;
+
+ /* for inserting symlinks */
+ const char *target;
+
+ svn_boolean_t file_external;
+
+ /* may need to insert/update ACTUAL to record a conflict */
+ const svn_skel_t *conflict;
+
+ /* may need to insert/update ACTUAL to record new properties */
+ svn_boolean_t update_actual_props;
+ const apr_hash_t *new_actual_props;
+
+ /* A depth-first ordered array of svn_prop_inherited_item_t *
+ structures representing the properties inherited by the base
+ node. */
+ apr_array_header_t *iprops;
+
+ /* maybe we should copy information from a previous record? */
+ svn_boolean_t keep_recorded_info;
+
+ /* insert a base-deleted working node as well as a base node */
+ svn_boolean_t insert_base_deleted;
+
+ /* delete the current working nodes above BASE */
+ svn_boolean_t delete_working;
+
+ /* may have work items to queue in this transaction */
+ const svn_skel_t *work_items;
+
+} insert_base_baton_t;
+
+
+/* Representation of a new working row for the NODES table */
+typedef struct insert_working_baton_t {
+ /* common to all insertions into WORKING (including NODE_DATA) */
+ svn_wc__db_status_t presence;
+ svn_node_kind_t kind;
+ int op_depth;
+
+ /* common to all "normal" presence insertions */
+ const apr_hash_t *props;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ apr_int64_t original_repos_id;
+ const char *original_repos_relpath;
+ svn_revnum_t original_revnum;
+ svn_boolean_t moved_here;
+
+ /* for inserting directories */
+ const apr_array_header_t *children;
+ svn_depth_t depth;
+
+ /* for inserting (copied/moved-here) files */
+ const svn_checksum_t *checksum;
+
+ /* for inserting symlinks */
+ const char *target;
+
+ svn_boolean_t update_actual_props;
+ const apr_hash_t *new_actual_props;
+
+ /* may have work items to queue in this transaction */
+ const svn_skel_t *work_items;
+
+ /* may have conflict to install in this transaction */
+ const svn_skel_t *conflict;
+
+ /* If the value is > 0 and < op_depth, also insert a not-present
+ at op-depth NOT_PRESENT_OP_DEPTH, based on this same information */
+ int not_present_op_depth;
+
+} insert_working_baton_t;
+
+/* Representation of a new row for the EXTERNALS table */
+typedef struct insert_external_baton_t {
+ /* common to all insertions into EXTERNALS */
+ svn_node_kind_t kind;
+ svn_wc__db_status_t presence;
+
+ /* The repository of the external */
+ apr_int64_t repos_id;
+ /* for file and symlink externals */
+ const char *repos_relpath;
+ svn_revnum_t revision;
+
+ /* Only used when repos_id == INVALID_REPOS_ID */
+ const char *repos_root_url;
+ const char *repos_uuid;
+
+ /* for file and symlink externals */
+ const apr_hash_t *props;
+ apr_array_header_t *iprops;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ const apr_hash_t *dav_cache;
+
+ /* for inserting files */
+ const svn_checksum_t *checksum;
+
+ /* for inserting symlinks */
+ const char *target;
+
+ const char *record_ancestor_relpath;
+ const char *recorded_repos_relpath;
+ svn_revnum_t recorded_peg_revision;
+ svn_revnum_t recorded_revision;
+
+ /* may need to insert/update ACTUAL to record a conflict */
+ const svn_skel_t *conflict;
+
+ /* may need to insert/update ACTUAL to record new properties */
+ svn_boolean_t update_actual_props;
+ const apr_hash_t *new_actual_props;
+
+ /* maybe we should copy information from a previous record? */
+ svn_boolean_t keep_recorded_info;
+
+ /* may have work items to queue in this transaction */
+ const svn_skel_t *work_items;
+
+} insert_external_baton_t;
+
+
+/* Forward declarations */
+static svn_error_t *
+add_work_items(svn_sqlite__db_t *sdb,
+ const svn_skel_t *skel,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+set_actual_props(apr_int64_t wc_id,
+ const char *local_relpath,
+ apr_hash_t *props,
+ svn_sqlite__db_t *db,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+insert_incomplete_children(svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ const char *local_relpath,
+ apr_int64_t repos_id,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ int op_depth,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+db_read_pristine_props(apr_hash_t **props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_boolean_t deleted_ok,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+read_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ svn_wc__db_lock_t **lock,
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_time,
+ const char **changelist,
+ svn_boolean_t *conflicted,
+ svn_boolean_t *op_root,
+ svn_boolean_t *had_props,
+ svn_boolean_t *props_mod,
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+scan_addition(svn_wc__db_status_t *status,
+ const char **op_root_relpath,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ const char **moved_from_relpath,
+ const char **moved_from_op_root_relpath,
+ int *moved_from_op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+convert_to_working_status(svn_wc__db_status_t *working_status,
+ svn_wc__db_status_t status);
+
+static svn_error_t *
+wclock_owns_lock(svn_boolean_t *own_lock,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_boolean_t exact,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+db_is_switched(svn_boolean_t *is_switched,
+ svn_node_kind_t *kind,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool);
+
+
+/* Return the absolute path, in local path style, of LOCAL_RELPATH
+ in WCROOT. */
+static const char *
+path_for_error_message(const svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool)
+{
+ const char *local_abspath
+ = svn_dirent_join(wcroot->abspath, local_relpath, result_pool);
+
+ return svn_dirent_local_style(local_abspath, result_pool);
+}
+
+
+/* Return a file size from column SLOT of the SQLITE statement STMT, or
+ SVN_INVALID_FILESIZE if the column value is NULL. */
+static svn_filesize_t
+get_recorded_size(svn_sqlite__stmt_t *stmt, int slot)
+{
+ if (svn_sqlite__column_is_null(stmt, slot))
+ return SVN_INVALID_FILESIZE;
+ return svn_sqlite__column_int64(stmt, slot);
+}
+
+
+/* Return a lock info structure constructed from the given columns of the
+ SQLITE statement STMT, or return NULL if the token column value is null. */
+static svn_wc__db_lock_t *
+lock_from_columns(svn_sqlite__stmt_t *stmt,
+ int col_token,
+ int col_owner,
+ int col_comment,
+ int col_date,
+ apr_pool_t *result_pool)
+{
+ svn_wc__db_lock_t *lock;
+
+ if (svn_sqlite__column_is_null(stmt, col_token))
+ {
+ lock = NULL;
+ }
+ else
+ {
+ lock = apr_pcalloc(result_pool, sizeof(svn_wc__db_lock_t));
+ lock->token = svn_sqlite__column_text(stmt, col_token, result_pool);
+ lock->owner = svn_sqlite__column_text(stmt, col_owner, result_pool);
+ lock->comment = svn_sqlite__column_text(stmt, col_comment, result_pool);
+ lock->date = svn_sqlite__column_int64(stmt, col_date);
+ }
+ return lock;
+}
+
+
+svn_error_t *
+svn_wc__db_fetch_repos_info(const char **repos_root_url,
+ const char **repos_uuid,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t repos_id,
+ apr_pool_t *result_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ if (!repos_root_url && !repos_uuid)
+ return SVN_NO_ERROR;
+
+ if (repos_id == INVALID_REPOS_ID)
+ {
+ if (repos_root_url)
+ *repos_root_url = NULL;
+ if (repos_uuid)
+ *repos_uuid = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_SELECT_REPOSITORY_BY_ID));
+ SVN_ERR(svn_sqlite__bindf(stmt, "i", repos_id));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, svn_sqlite__reset(stmt),
+ _("No REPOSITORY table entry for id '%ld'"),
+ (long int)repos_id);
+
+ if (repos_root_url)
+ *repos_root_url = svn_sqlite__column_text(stmt, 0, result_pool);
+ if (repos_uuid)
+ *repos_uuid = svn_sqlite__column_text(stmt, 1, result_pool);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+/* Set *REPOS_ID, *REVISION and *REPOS_RELPATH from the given columns of the
+ SQLITE statement STMT, or to NULL/SVN_INVALID_REVNUM if the respective
+ column value is null. Any of the output parameters may be NULL if not
+ required. */
+static void
+repos_location_from_columns(apr_int64_t *repos_id,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ svn_sqlite__stmt_t *stmt,
+ int col_repos_id,
+ int col_revision,
+ int col_repos_relpath,
+ apr_pool_t *result_pool)
+{
+ if (repos_id)
+ {
+ /* Fetch repository information via REPOS_ID. */
+ if (svn_sqlite__column_is_null(stmt, col_repos_id))
+ *repos_id = INVALID_REPOS_ID;
+ else
+ *repos_id = svn_sqlite__column_int64(stmt, col_repos_id);
+ }
+ if (revision)
+ {
+ *revision = svn_sqlite__column_revnum(stmt, col_revision);
+ }
+ if (repos_relpath)
+ {
+ *repos_relpath = svn_sqlite__column_text(stmt, col_repos_relpath,
+ result_pool);
+ }
+}
+
+
+/* Get the statement given by STMT_IDX, and bind the appropriate wc_id and
+ local_relpath based upon LOCAL_ABSPATH. Store it in *STMT, and use
+ SCRATCH_POOL for temporary allocations.
+
+ Note: WC_ID and LOCAL_RELPATH must be arguments 1 and 2 in the statement. */
+static svn_error_t *
+get_statement_for_path(svn_sqlite__stmt_t **stmt,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ int stmt_idx,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(stmt, wcroot->sdb, stmt_idx));
+ SVN_ERR(svn_sqlite__bindf(*stmt, "is", wcroot->wc_id, local_relpath));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* For a given REPOS_ROOT_URL/REPOS_UUID pair, return the existing REPOS_ID
+ value. If one does not exist, then create a new one. */
+static svn_error_t *
+create_repos_id(apr_int64_t *repos_id,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *get_stmt;
+ svn_sqlite__stmt_t *insert_stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&get_stmt, sdb, STMT_SELECT_REPOSITORY));
+ SVN_ERR(svn_sqlite__bindf(get_stmt, "s", repos_root_url));
+ SVN_ERR(svn_sqlite__step(&have_row, get_stmt));
+
+ if (have_row)
+ {
+ *repos_id = svn_sqlite__column_int64(get_stmt, 0);
+ return svn_error_trace(svn_sqlite__reset(get_stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(get_stmt));
+
+ /* NOTE: strictly speaking, there is a race condition between the
+ above query and the insertion below. We're simply going to ignore
+ that, as it means two processes are *modifying* the working copy
+ at the same time, *and* new repositores are becoming visible.
+ This is rare enough, let alone the miniscule chance of hitting
+ this race condition. Further, simply failing out will leave the
+ database in a consistent state, and the user can just re-run the
+ failed operation. */
+
+ SVN_ERR(svn_sqlite__get_statement(&insert_stmt, sdb,
+ STMT_INSERT_REPOSITORY));
+ SVN_ERR(svn_sqlite__bindf(insert_stmt, "ss", repos_root_url, repos_uuid));
+ return svn_error_trace(svn_sqlite__insert(repos_id, insert_stmt));
+}
+
+
+/* Initialize the baton with appropriate "blank" values. This allows the
+ insertion function to leave certain columns null. */
+static void
+blank_ibb(insert_base_baton_t *pibb)
+{
+ memset(pibb, 0, sizeof(*pibb));
+ pibb->revision = SVN_INVALID_REVNUM;
+ pibb->changed_rev = SVN_INVALID_REVNUM;
+ pibb->depth = svn_depth_infinity;
+ pibb->repos_id = INVALID_REPOS_ID;
+}
+
+
+svn_error_t *
+svn_wc__db_extend_parent_delete(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_node_kind_t kind,
+ int op_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t have_row;
+ svn_sqlite__stmt_t *stmt;
+ int parent_op_depth;
+ const char *parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool);
+
+ SVN_ERR_ASSERT(local_relpath[0]);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, parent_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ parent_op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ {
+ int existing_op_depth;
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ existing_op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!have_row || parent_op_depth < existing_op_depth)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSTALL_WORKING_NODE_FOR_DELETE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdst", wcroot->wc_id,
+ local_relpath, parent_op_depth,
+ parent_relpath, kind_map, kind));
+ SVN_ERR(svn_sqlite__update(NULL, stmt));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This is the reverse of svn_wc__db_extend_parent_delete.
+
+ When removing a node if the parent has a higher working node then
+ the parent node and this node are both deleted or replaced and any
+ delete over this node must be removed.
+ */
+svn_error_t *
+svn_wc__db_retract_parent_delete(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Insert the base row represented by (insert_base_baton_t *) BATON. */
+static svn_error_t *
+insert_base_node(const insert_base_baton_t *pibb,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ apr_int64_t repos_id = pibb->repos_id;
+ svn_sqlite__stmt_t *stmt;
+ svn_filesize_t recorded_size = SVN_INVALID_FILESIZE;
+ apr_int64_t recorded_time;
+
+ /* The directory at the WCROOT has a NULL parent_relpath. Otherwise,
+ bind the appropriate parent_relpath. */
+ const char *parent_relpath =
+ (*local_relpath == '\0') ? NULL
+ : svn_relpath_dirname(local_relpath, scratch_pool);
+
+ if (pibb->repos_id == INVALID_REPOS_ID)
+ SVN_ERR(create_repos_id(&repos_id, pibb->repos_root_url, pibb->repos_uuid,
+ wcroot->sdb, scratch_pool));
+
+ SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID);
+ SVN_ERR_ASSERT(pibb->repos_relpath != NULL);
+
+ if (pibb->keep_recorded_info)
+ {
+ svn_boolean_t have_row;
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ /* Preserve size and modification time if caller asked us to. */
+ recorded_size = get_recorded_size(stmt, 6);
+ recorded_time = svn_sqlite__column_int64(stmt, 12);
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsisr"
+ "tstr" /* 8 - 11 */
+ "isnnnnns", /* 12 - 19 */
+ wcroot->wc_id, /* 1 */
+ local_relpath, /* 2 */
+ 0, /* op_depth is 0 for base */
+ parent_relpath, /* 4 */
+ repos_id,
+ pibb->repos_relpath,
+ pibb->revision,
+ presence_map, pibb->status, /* 8 */
+ (pibb->kind == svn_node_dir) ? /* 9 */
+ svn_token__to_word(depth_map, pibb->depth) : NULL,
+ kind_map, pibb->kind, /* 10 */
+ pibb->changed_rev, /* 11 */
+ pibb->changed_date, /* 12 */
+ pibb->changed_author, /* 13 */
+ (pibb->kind == svn_node_symlink) ?
+ pibb->target : NULL)); /* 19 */
+ if (pibb->kind == svn_node_file)
+ {
+ if (!pibb->checksum
+ && pibb->status != svn_wc__db_status_not_present
+ && pibb->status != svn_wc__db_status_excluded
+ && pibb->status != svn_wc__db_status_server_excluded)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, svn_sqlite__reset(stmt),
+ _("The file '%s' has no checksum."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, pibb->checksum,
+ scratch_pool));
+
+ if (recorded_size != SVN_INVALID_FILESIZE)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 16, recorded_size));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 17, recorded_time));
+ }
+ }
+
+ /* Set properties. Must be null if presence not normal or incomplete. */
+ assert(pibb->status == svn_wc__db_status_normal
+ || pibb->status == svn_wc__db_status_incomplete
+ || pibb->props == NULL);
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 15, pibb->props,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, pibb->iprops,
+ scratch_pool));
+
+ if (pibb->dav_cache)
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 18, pibb->dav_cache,
+ scratch_pool));
+
+ if (pibb->file_external)
+ SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ if (pibb->update_actual_props)
+ {
+ /* Cast away const, to allow calling property helpers */
+ apr_hash_t *base_props = (apr_hash_t *)pibb->props;
+ apr_hash_t *new_actual_props = (apr_hash_t *)pibb->new_actual_props;
+
+ if (base_props != NULL
+ && new_actual_props != NULL
+ && (apr_hash_count(base_props) == apr_hash_count(new_actual_props)))
+ {
+ apr_array_header_t *diffs;
+
+ SVN_ERR(svn_prop_diffs(&diffs, new_actual_props, base_props,
+ scratch_pool));
+
+ if (diffs->nelts == 0)
+ new_actual_props = NULL;
+ }
+
+ SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, new_actual_props,
+ wcroot->sdb, scratch_pool));
+ }
+
+ if (pibb->kind == svn_node_dir && pibb->children)
+ SVN_ERR(insert_incomplete_children(wcroot->sdb, wcroot->wc_id,
+ local_relpath,
+ repos_id,
+ pibb->repos_relpath,
+ pibb->revision,
+ pibb->children,
+ 0 /* BASE */,
+ scratch_pool));
+
+ /* When this is not the root node, check shadowing behavior */
+ if (*local_relpath)
+ {
+ if (parent_relpath
+ && ((pibb->status == svn_wc__db_status_normal)
+ || (pibb->status == svn_wc__db_status_incomplete))
+ && ! pibb->file_external)
+ {
+ SVN_ERR(svn_wc__db_extend_parent_delete(wcroot, local_relpath,
+ pibb->kind, 0,
+ scratch_pool));
+ }
+ else if (pibb->status == svn_wc__db_status_not_present
+ || pibb->status == svn_wc__db_status_server_excluded
+ || pibb->status == svn_wc__db_status_excluded)
+ {
+ SVN_ERR(svn_wc__db_retract_parent_delete(wcroot, local_relpath, 0,
+ scratch_pool));
+ }
+ }
+
+ if (pibb->delete_working)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ if (pibb->insert_base_deleted)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_DELETE_FROM_BASE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wcroot->wc_id, local_relpath,
+ relpath_depth(local_relpath)));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, pibb->work_items, scratch_pool));
+ if (pibb->conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ pibb->conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Initialize the baton with appropriate "blank" values. This allows the
+ insertion function to leave certain columns null. */
+static void
+blank_iwb(insert_working_baton_t *piwb)
+{
+ memset(piwb, 0, sizeof(*piwb));
+ piwb->changed_rev = SVN_INVALID_REVNUM;
+ piwb->depth = svn_depth_infinity;
+
+ /* ORIGINAL_REPOS_ID and ORIGINAL_REVNUM could use some kind of "nil"
+ value, but... meh. We'll avoid them if ORIGINAL_REPOS_RELPATH==NULL. */
+}
+
+
+/* Insert a row in NODES for each (const char *) child name in CHILDREN,
+ whose parent directory is LOCAL_RELPATH, at op_depth=OP_DEPTH. Set each
+ child's presence to 'incomplete', kind to 'unknown', repos_id to REPOS_ID,
+ repos_path by appending the child name to REPOS_PATH, and revision to
+ REVISION (which should match the parent's revision).
+
+ If REPOS_ID is INVALID_REPOS_ID, set each child's repos_id to null. */
+static svn_error_t *
+insert_incomplete_children(svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ const char *local_relpath,
+ apr_int64_t repos_id,
+ const char *repos_path,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ int op_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *moved_to_relpaths = apr_hash_make(scratch_pool);
+
+ SVN_ERR_ASSERT(repos_path != NULL || op_depth > 0);
+ SVN_ERR_ASSERT((repos_id != INVALID_REPOS_ID)
+ == (repos_path != NULL));
+
+ /* If we're inserting WORKING nodes, we might be replacing existing
+ * nodes which were moved-away. We need to retain the moved-to relpath of
+ * such nodes in order not to lose move information during replace. */
+ if (op_depth > 0)
+ {
+ for (i = children->nelts; i--; )
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+ svn_boolean_t have_row;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id,
+ svn_relpath_join(local_relpath, name,
+ iterpool)));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row && !svn_sqlite__column_is_null(stmt, 14))
+ svn_hash_sets(moved_to_relpaths, name,
+ svn_sqlite__column_text(stmt, 14, scratch_pool));
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
+
+ for (i = children->nelts; i--; )
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnrsnsnnnnnnnnnnsn",
+ wc_id,
+ svn_relpath_join(local_relpath, name,
+ iterpool),
+ op_depth,
+ local_relpath,
+ revision,
+ "incomplete", /* 8, presence */
+ "unknown", /* 10, kind */
+ /* 21, moved_to */
+ svn_hash_gets(moved_to_relpaths, name)));
+ if (repos_id != INVALID_REPOS_ID)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 5, repos_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 6,
+ svn_relpath_join(repos_path, name,
+ iterpool)));
+ }
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Insert the working row represented by (insert_working_baton_t *) BATON. */
+static svn_error_t *
+insert_working_node(const insert_working_baton_t *piwb,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const char *parent_relpath;
+ const char *moved_to_relpath = NULL;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(piwb->op_depth > 0);
+
+ /* We cannot insert a WORKING_NODE row at the wcroot. */
+ SVN_ERR_ASSERT(*local_relpath != '\0');
+ parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool);
+
+ /* Preserve existing moved-to information for this relpath,
+ * which might exist in case we're replacing an existing base-deleted
+ * node. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_MOVED_TO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ piwb->op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ moved_to_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnntstrisn"
+ "nnnn" /* properties translated_size last_mod_time dav_cache */
+ "sns", /* symlink_target, file_external, moved_to */
+ wcroot->wc_id, local_relpath,
+ piwb->op_depth,
+ parent_relpath,
+ presence_map, piwb->presence,
+ (piwb->kind == svn_node_dir)
+ ? svn_token__to_word(depth_map, piwb->depth) : NULL,
+ kind_map, piwb->kind,
+ piwb->changed_rev,
+ piwb->changed_date,
+ piwb->changed_author,
+ /* Note: incomplete nodes may have a NULL target. */
+ (piwb->kind == svn_node_symlink)
+ ? piwb->target : NULL,
+ moved_to_relpath));
+
+ if (piwb->moved_here)
+ {
+ SVN_ERR(svn_sqlite__bind_int(stmt, 8, TRUE));
+ }
+
+ if (piwb->kind == svn_node_file)
+ {
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, piwb->checksum,
+ scratch_pool));
+ }
+
+ if (piwb->original_repos_relpath != NULL)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 5, piwb->original_repos_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 6, piwb->original_repos_relpath));
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, piwb->original_revnum));
+ }
+
+ /* Set properties. Must be null if presence not normal or incomplete. */
+ assert(piwb->presence == svn_wc__db_status_normal
+ || piwb->presence == svn_wc__db_status_incomplete
+ || piwb->props == NULL);
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 15, piwb->props, scratch_pool));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ /* Insert incomplete children, if specified.
+ The children are part of the same op and so have the same op_depth.
+ (The only time we'd want a different depth is during a recursive
+ simple add, but we never insert children here during a simple add.) */
+ if (piwb->kind == svn_node_dir && piwb->children)
+ SVN_ERR(insert_incomplete_children(wcroot->sdb, wcroot->wc_id,
+ local_relpath,
+ INVALID_REPOS_ID /* inherit repos_id */,
+ NULL /* inherit repos_path */,
+ piwb->original_revnum,
+ piwb->children,
+ piwb->op_depth,
+ scratch_pool));
+
+ if (piwb->update_actual_props)
+ {
+ /* Cast away const, to allow calling property helpers */
+ apr_hash_t *base_props = (apr_hash_t *)piwb->props;
+ apr_hash_t *new_actual_props = (apr_hash_t *)piwb->new_actual_props;
+
+ if (base_props != NULL
+ && new_actual_props != NULL
+ && (apr_hash_count(base_props) == apr_hash_count(new_actual_props)))
+ {
+ apr_array_header_t *diffs;
+
+ SVN_ERR(svn_prop_diffs(&diffs, new_actual_props, base_props,
+ scratch_pool));
+
+ if (diffs->nelts == 0)
+ new_actual_props = NULL;
+ }
+
+ SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, new_actual_props,
+ wcroot->sdb, scratch_pool));
+ }
+
+ if (piwb->kind == svn_node_dir)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_EMPTY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ if (piwb->not_present_op_depth > 0
+ && piwb->not_present_op_depth < piwb->op_depth)
+ {
+ /* And also insert a not-present node to tell the commit processing that
+ a child of the parent node was not copied. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_NODE));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtnt",
+ wcroot->wc_id, local_relpath,
+ piwb->not_present_op_depth, parent_relpath,
+ piwb->original_repos_id,
+ piwb->original_repos_relpath,
+ piwb->original_revnum,
+ presence_map, svn_wc__db_status_not_present,
+ /* NULL */
+ kind_map, piwb->kind));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, piwb->work_items, scratch_pool));
+ if (piwb->conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ piwb->conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Each name is allocated in RESULT_POOL and stored into CHILDREN as a key
+ pointed to the same name. */
+static svn_error_t *
+add_children_to_hash(apr_hash_t *children,
+ int stmt_idx,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ const char *parent_relpath,
+ apr_pool_t *result_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, stmt_idx));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, parent_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *name = svn_relpath_basename(child_relpath, result_pool);
+
+ svn_hash_sets(children, name, name);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ return svn_sqlite__reset(stmt);
+}
+
+
+/* Set *CHILDREN to a new array of the (const char *) basenames of the
+ immediate children, whatever their status, of the working node at
+ LOCAL_RELPATH. */
+static svn_error_t *
+gather_children2(const apr_array_header_t **children,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *names_hash = apr_hash_make(scratch_pool);
+ apr_array_header_t *names_array;
+
+ /* All of the names get allocated in RESULT_POOL. It
+ appears to be faster to use the hash to remove duplicates than to
+ use DISTINCT in the SQL query. */
+ SVN_ERR(add_children_to_hash(names_hash, STMT_SELECT_WORKING_CHILDREN,
+ wcroot->sdb, wcroot->wc_id,
+ local_relpath, result_pool));
+
+ SVN_ERR(svn_hash_keys(&names_array, names_hash, result_pool));
+ *children = names_array;
+ return SVN_NO_ERROR;
+}
+
+/* Return in *CHILDREN all of the children of the directory LOCAL_RELPATH,
+ of any status, in all op-depths in the NODES table. */
+static svn_error_t *
+gather_children(const apr_array_header_t **children,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *names_hash = apr_hash_make(scratch_pool);
+ apr_array_header_t *names_array;
+
+ /* All of the names get allocated in RESULT_POOL. It
+ appears to be faster to use the hash to remove duplicates than to
+ use DISTINCT in the SQL query. */
+ SVN_ERR(add_children_to_hash(names_hash, STMT_SELECT_NODE_CHILDREN,
+ wcroot->sdb, wcroot->wc_id,
+ local_relpath, result_pool));
+
+ SVN_ERR(svn_hash_keys(&names_array, names_hash, result_pool));
+ *children = names_array;
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *CHILDREN to a new array of (const char *) names of the children of
+ the repository directory corresponding to WCROOT:LOCAL_RELPATH:OP_DEPTH -
+ that is, only the children that are at the same op-depth as their parent. */
+static svn_error_t *
+gather_repo_children(const apr_array_header_t **children,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *result
+ = apr_array_make(result_pool, 0, sizeof(const char *));
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ /* Allocate the name in RESULT_POOL so we won't have to copy it. */
+ APR_ARRAY_PUSH(result, const char *)
+ = svn_relpath_basename(child_relpath, result_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *children = result;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_get_children_op_depth(apr_hash_t **children,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *children = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ svn_node_kind_t *child_kind = apr_palloc(result_pool, sizeof(svn_node_kind_t));
+
+ *child_kind = svn_sqlite__column_token(stmt, 1, kind_map);
+ svn_hash_sets(*children,
+ svn_relpath_basename(child_relpath, result_pool),
+ child_kind);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return TRUE if CHILD_ABSPATH is an immediate child of PARENT_ABSPATH.
+ * Else, return FALSE. */
+static svn_boolean_t
+is_immediate_child_path(const char *parent_abspath, const char *child_abspath)
+{
+ const char *local_relpath = svn_dirent_skip_ancestor(parent_abspath,
+ child_abspath);
+
+ /* To be an immediate child local_relpath should have one (not empty)
+ component */
+ return local_relpath && *local_relpath && !strchr(local_relpath, '/');
+}
+
+
+/* Remove the access baton for LOCAL_ABSPATH from ACCESS_CACHE. */
+static void
+remove_from_access_cache(apr_hash_t *access_cache,
+ const char *local_abspath)
+{
+ svn_wc_adm_access_t *adm_access;
+
+ adm_access = svn_hash_gets(access_cache, local_abspath);
+ if (adm_access)
+ svn_wc__adm_access_set_entries(adm_access, NULL);
+}
+
+
+/* Flush the access baton for LOCAL_ABSPATH, and any of its children up to
+ * the specified DEPTH, from the access baton cache in WCROOT.
+ * Also flush the access baton for the parent of LOCAL_ABSPATH.I
+ *
+ * This function must be called when the access baton cache goes stale,
+ * i.e. data about LOCAL_ABSPATH will need to be read again from disk.
+ *
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+flush_entries(svn_wc__db_wcroot_t *wcroot,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ const char *parent_abspath;
+
+ if (apr_hash_count(wcroot->access_cache) == 0)
+ return SVN_NO_ERROR;
+
+ remove_from_access_cache(wcroot->access_cache, local_abspath);
+
+ if (depth > svn_depth_empty)
+ {
+ apr_hash_index_t *hi;
+
+ /* Flush access batons of children within the specified depth. */
+ for (hi = apr_hash_first(scratch_pool, wcroot->access_cache);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *item_abspath = svn__apr_hash_index_key(hi);
+
+ if ((depth == svn_depth_files || depth == svn_depth_immediates) &&
+ is_immediate_child_path(local_abspath, item_abspath))
+ {
+ remove_from_access_cache(wcroot->access_cache, item_abspath);
+ }
+ else if (depth == svn_depth_infinity &&
+ svn_dirent_is_ancestor(local_abspath, item_abspath))
+ {
+ remove_from_access_cache(wcroot->access_cache, item_abspath);
+ }
+ }
+ }
+
+ /* We're going to be overly aggressive here and just flush the parent
+ without doing much checking. This may hurt performance for
+ legacy API consumers, but that's not our problem. :) */
+ parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ remove_from_access_cache(wcroot->access_cache, parent_abspath);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Add a single WORK_ITEM into the given SDB's WORK_QUEUE table. This does
+ not perform its work within a transaction, assuming the caller will
+ manage that. */
+static svn_error_t *
+add_single_work_item(svn_sqlite__db_t *sdb,
+ const svn_skel_t *work_item,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *serialized;
+ svn_sqlite__stmt_t *stmt;
+
+ serialized = svn_skel__unparse(work_item, scratch_pool);
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_WORK_ITEM));
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 1, serialized->data, serialized->len));
+ return svn_error_trace(svn_sqlite__insert(NULL, stmt));
+}
+
+
+/* Add work item(s) to the given SDB. Also see add_single_work_item(). This
+ SKEL is usually passed to the various wc_db operation functions. It may
+ be NULL, indicating no additional work items are needed, it may be a
+ single work item, or it may be a list of work items. */
+static svn_error_t *
+add_work_items(svn_sqlite__db_t *sdb,
+ const svn_skel_t *skel,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+
+ /* Maybe there are no work items to insert. */
+ if (skel == NULL)
+ return SVN_NO_ERROR;
+
+ /* Should have a list. */
+ SVN_ERR_ASSERT(!skel->is_atom);
+
+ /* Is the list a single work item? Or a list of work items? */
+ if (SVN_WC__SINGLE_WORK_ITEM(skel))
+ return svn_error_trace(add_single_work_item(sdb, skel, scratch_pool));
+
+ /* SKEL is a list-of-lists, aka list of work items. */
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (skel = skel->children; skel; skel = skel->next)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(add_single_work_item(sdb, skel, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Determine whether the node exists for a given WCROOT and LOCAL_RELPATH. */
+static svn_error_t *
+does_node_exist(svn_boolean_t *exists,
+ const svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DOES_NODE_EXIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(exists, stmt));
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+/* Helper for create_db(). Initializes our wc.db schema.
+ */
+static svn_error_t *
+init_db(/* output values */
+ apr_int64_t *repos_id,
+ apr_int64_t *wc_id,
+ /* input values */
+ svn_sqlite__db_t *db,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ const char *root_node_repos_relpath,
+ svn_revnum_t root_node_revision,
+ svn_depth_t root_node_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ /* Create the database's schema. */
+ SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_SCHEMA));
+ SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_NODES));
+ SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_NODES_TRIGGERS));
+ SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_EXTERNALS));
+
+ /* Insert the repository. */
+ SVN_ERR(create_repos_id(repos_id, repos_root_url, repos_uuid,
+ db, scratch_pool));
+
+ /* Insert the wcroot. */
+ /* ### Right now, this just assumes wc metadata is being stored locally. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_WCROOT));
+ SVN_ERR(svn_sqlite__insert(wc_id, stmt));
+
+ if (root_node_repos_relpath)
+ {
+ svn_wc__db_status_t status = svn_wc__db_status_normal;
+
+ if (root_node_revision > 0)
+ status = svn_wc__db_status_incomplete; /* Will be filled by update */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtst",
+ *wc_id, /* 1 */
+ "", /* 2 */
+ 0, /* op_depth is 0 for base */
+ NULL, /* 4 */
+ *repos_id,
+ root_node_repos_relpath,
+ root_node_revision,
+ presence_map, status, /* 8 */
+ svn_token__to_word(depth_map,
+ root_node_depth),
+ kind_map, svn_node_dir /* 10 */));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Create an sqlite database at DIR_ABSPATH/SDB_FNAME and insert
+ records for REPOS_ID (using REPOS_ROOT_URL and REPOS_UUID) into
+ REPOSITORY and for WC_ID into WCROOT. Return the DB connection
+ in *SDB.
+
+ If ROOT_NODE_REPOS_RELPATH is not NULL, insert a BASE node at
+ the working copy root with repository relpath ROOT_NODE_REPOS_RELPATH,
+ revision ROOT_NODE_REVISION and depth ROOT_NODE_DEPTH.
+ */
+static svn_error_t *
+create_db(svn_sqlite__db_t **sdb,
+ apr_int64_t *repos_id,
+ apr_int64_t *wc_id,
+ const char *dir_abspath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ const char *sdb_fname,
+ const char *root_node_repos_relpath,
+ svn_revnum_t root_node_revision,
+ svn_depth_t root_node_depth,
+ svn_boolean_t exclusive,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_util_open_db(sdb, dir_abspath, sdb_fname,
+ svn_sqlite__mode_rwcreate, exclusive,
+ NULL /* my_statements */,
+ result_pool, scratch_pool));
+
+ SVN_SQLITE__WITH_LOCK(init_db(repos_id, wc_id,
+ *sdb, repos_root_url, repos_uuid,
+ root_node_repos_relpath, root_node_revision,
+ root_node_depth, scratch_pool),
+ *sdb);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_init(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t initial_rev,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__db_t *sdb;
+ apr_int64_t repos_id;
+ apr_int64_t wc_id;
+ svn_wc__db_wcroot_t *wcroot;
+ svn_boolean_t sqlite_exclusive = FALSE;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(repos_relpath != NULL);
+ SVN_ERR_ASSERT(depth == svn_depth_empty
+ || depth == svn_depth_files
+ || depth == svn_depth_immediates
+ || depth == svn_depth_infinity);
+
+ /* ### REPOS_ROOT_URL and REPOS_UUID may be NULL. ... more doc: tbd */
+
+ SVN_ERR(svn_config_get_bool((svn_config_t *)db->config, &sqlite_exclusive,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
+ FALSE));
+
+ /* Create the SDB and insert the basic rows. */
+ SVN_ERR(create_db(&sdb, &repos_id, &wc_id, local_abspath, repos_root_url,
+ repos_uuid, SDB_FILE,
+ repos_relpath, initial_rev, depth, sqlite_exclusive,
+ db->state_pool, scratch_pool));
+
+ /* Create the WCROOT for this directory. */
+ SVN_ERR(svn_wc__db_pdh_create_wcroot(&wcroot,
+ apr_pstrdup(db->state_pool, local_abspath),
+ sdb, wc_id, FORMAT_FROM_SDB,
+ FALSE /* auto-upgrade */,
+ FALSE /* enforce_empty_wq */,
+ db->state_pool, scratch_pool));
+
+ /* The WCROOT is complete. Stash it into DB. */
+ svn_hash_sets(db->dir_data, wcroot->abspath, wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_to_relpath(const char **local_relpath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &relpath, db,
+ wri_abspath, result_pool, scratch_pool));
+
+ /* This function is indirectly called from the upgrade code, so we
+ can't verify the wcroot here. Just check that it is not NULL */
+ CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool);
+
+ if (svn_dirent_is_ancestor(wcroot->abspath, local_abspath))
+ {
+ *local_relpath = apr_pstrdup(result_pool,
+ svn_dirent_skip_ancestor(wcroot->abspath,
+ local_abspath));
+ }
+ else
+ /* Probably moving from $TMP. Should we allow this? */
+ *local_relpath = apr_pstrdup(result_pool, local_abspath);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_from_relpath(const char **local_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *unused_relpath;
+#if 0
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(local_relpath));
+#endif
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &unused_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+
+ /* This function is indirectly called from the upgrade code, so we
+ can't verify the wcroot here. Just check that it is not NULL */
+ CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool);
+
+
+ *local_abspath = svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ result_pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_get_wcroot(const char **wcroot_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *unused_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &unused_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+
+ /* Can't use VERIFY_USABLE_WCROOT, as this should be usable to detect
+ where call upgrade */
+ CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool);
+
+ *wcroot_abspath = apr_pstrdup(result_pool, wcroot->abspath);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_add_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const apr_array_header_t *children,
+ svn_depth_t depth,
+ apr_hash_t *dav_cache,
+ const svn_skel_t *conflict,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_base_baton_t ibb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(repos_relpath != NULL);
+ SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool));
+ SVN_ERR_ASSERT(repos_uuid != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev));
+#if 0
+ SVN_ERR_ASSERT(children != NULL);
+#endif
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ blank_ibb(&ibb);
+
+ /* Calculate repos_id in insert_base_node() to avoid extra transaction */
+ ibb.repos_root_url = repos_root_url;
+ ibb.repos_uuid = repos_uuid;
+
+ ibb.status = svn_wc__db_status_normal;
+ ibb.kind = svn_node_dir;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = revision;
+
+ ibb.iprops = new_iprops;
+ ibb.props = props;
+ ibb.changed_rev = changed_rev;
+ ibb.changed_date = changed_date;
+ ibb.changed_author = changed_author;
+
+ ibb.children = children;
+ ibb.depth = depth;
+
+ ibb.dav_cache = dav_cache;
+ ibb.conflict = conflict;
+ ibb.work_items = work_items;
+
+ if (update_actual_props)
+ {
+ ibb.update_actual_props = TRUE;
+ ibb.new_actual_props = new_actual_props;
+ }
+
+ /* Insert the directory and all its children transactionally.
+
+ Note: old children can stick around, even if they are no longer present
+ in this directory's revision. */
+ SVN_WC__DB_WITH_TXN(
+ insert_base_node(&ibb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_base_add_incomplete_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t insert_base_deleted,
+ svn_boolean_t delete_working,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ struct insert_base_baton_t ibb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ SVN_ERR_ASSERT(repos_relpath && repos_root_url && repos_uuid);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_ibb(&ibb);
+
+ /* Calculate repos_id in insert_base_node() to avoid extra transaction */
+ ibb.repos_root_url = repos_root_url;
+ ibb.repos_uuid = repos_uuid;
+
+ ibb.status = svn_wc__db_status_incomplete;
+ ibb.kind = svn_node_dir;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = revision;
+ ibb.depth = depth;
+ ibb.insert_base_deleted = insert_base_deleted;
+ ibb.delete_working = delete_working;
+
+ ibb.conflict = conflict;
+ ibb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_base_node(&ibb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const svn_checksum_t *checksum,
+ apr_hash_t *dav_cache,
+ svn_boolean_t delete_working,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ svn_boolean_t keep_recorded_info,
+ svn_boolean_t insert_base_deleted,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_base_baton_t ibb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(repos_relpath != NULL);
+ SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool));
+ SVN_ERR_ASSERT(repos_uuid != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev));
+ SVN_ERR_ASSERT(checksum != NULL);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ blank_ibb(&ibb);
+
+ /* Calculate repos_id in insert_base_node() to avoid extra transaction */
+ ibb.repos_root_url = repos_root_url;
+ ibb.repos_uuid = repos_uuid;
+
+ ibb.status = svn_wc__db_status_normal;
+ ibb.kind = svn_node_file;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = revision;
+
+ ibb.props = props;
+ ibb.changed_rev = changed_rev;
+ ibb.changed_date = changed_date;
+ ibb.changed_author = changed_author;
+
+ ibb.checksum = checksum;
+
+ ibb.dav_cache = dav_cache;
+ ibb.iprops = new_iprops;
+
+ if (update_actual_props)
+ {
+ ibb.update_actual_props = TRUE;
+ ibb.new_actual_props = new_actual_props;
+ }
+
+ ibb.keep_recorded_info = keep_recorded_info;
+ ibb.insert_base_deleted = insert_base_deleted;
+ ibb.delete_working = delete_working;
+
+ ibb.conflict = conflict;
+ ibb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_base_node(&ibb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ /* If this used to be a directory we should remove children so pass
+ * depth infinity. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *target,
+ apr_hash_t *dav_cache,
+ svn_boolean_t delete_working,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ svn_boolean_t keep_recorded_info,
+ svn_boolean_t insert_base_deleted,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_base_baton_t ibb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(repos_relpath != NULL);
+ SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool));
+ SVN_ERR_ASSERT(repos_uuid != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ SVN_ERR_ASSERT(props != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev));
+ SVN_ERR_ASSERT(target != NULL);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+ blank_ibb(&ibb);
+
+ /* Calculate repos_id in insert_base_node() to avoid extra transaction */
+ ibb.repos_root_url = repos_root_url;
+ ibb.repos_uuid = repos_uuid;
+
+ ibb.status = svn_wc__db_status_normal;
+ ibb.kind = svn_node_symlink;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = revision;
+
+ ibb.props = props;
+ ibb.changed_rev = changed_rev;
+ ibb.changed_date = changed_date;
+ ibb.changed_author = changed_author;
+
+ ibb.target = target;
+
+ ibb.dav_cache = dav_cache;
+ ibb.iprops = new_iprops;
+
+ if (update_actual_props)
+ {
+ ibb.update_actual_props = TRUE;
+ ibb.new_actual_props = new_actual_props;
+ }
+
+ ibb.keep_recorded_info = keep_recorded_info;
+ ibb.insert_base_deleted = insert_base_deleted;
+ ibb.delete_working = delete_working;
+
+ ibb.conflict = conflict;
+ ibb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_base_node(&ibb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ /* If this used to be a directory we should remove children so pass
+ * depth infinity. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+add_excluded_or_not_present_node(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ svn_wc__db_status_t status,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_base_baton_t ibb;
+ const char *dir_abspath, *name;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(repos_relpath != NULL);
+ SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool));
+ SVN_ERR_ASSERT(repos_uuid != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+ SVN_ERR_ASSERT(status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_not_present);
+
+ /* These absent presence nodes are only useful below a parent node that is
+ present. To avoid problems with working copies obstructing the child
+ we calculate the wcroot and local_relpath of the parent and then add
+ our own relpath. */
+
+ svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ local_relpath = svn_relpath_join(local_relpath, name, scratch_pool);
+
+ blank_ibb(&ibb);
+
+ /* Calculate repos_id in insert_base_node() to avoid extra transaction */
+ ibb.repos_root_url = repos_root_url;
+ ibb.repos_uuid = repos_uuid;
+
+ ibb.status = status;
+ ibb.kind = kind;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = revision;
+
+ /* Depending upon KIND, any of these might get used. */
+ ibb.children = NULL;
+ ibb.depth = svn_depth_unknown;
+ ibb.checksum = NULL;
+ ibb.target = NULL;
+
+ ibb.conflict = conflict;
+ ibb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_base_node(&ibb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ /* If this used to be a directory we should remove children so pass
+ * depth infinity. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_add_excluded_node(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ svn_wc__db_status_t status,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded);
+
+ return add_excluded_or_not_present_node(
+ db, local_abspath, repos_relpath, repos_root_url, repos_uuid, revision,
+ kind, status, conflict, work_items, scratch_pool);
+}
+
+
+svn_error_t *
+svn_wc__db_base_add_not_present_node(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ return add_excluded_or_not_present_node(
+ db, local_abspath, repos_relpath, repos_root_url, repos_uuid, revision,
+ kind, svn_wc__db_status_not_present, conflict, work_items, scratch_pool);
+}
+
+/* Recursively clear moved-here information at the copy-half of the move
+ * which moved the node at SRC_RELPATH away. This transforms the move into
+ * a simple copy. */
+static svn_error_t *
+clear_moved_here(const char *src_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ const char *dst_relpath;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_MOVED_TO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ src_relpath, relpath_depth(src_relpath)));
+ SVN_ERR(svn_sqlite__step_row(stmt));
+ dst_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_MOVED_HERE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ dst_relpath, relpath_depth(dst_relpath)));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_base_remove().
+ */
+static svn_error_t *
+db_base_remove(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db, /* For checking conflicts */
+ svn_boolean_t keep_as_working,
+ svn_boolean_t queue_deletes,
+ svn_revnum_t not_present_revision,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_wc__db_status_t status;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ svn_node_kind_t kind;
+ svn_boolean_t keep_working;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(&status, &kind, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_normal
+ && keep_as_working)
+ {
+ SVN_ERR(svn_wc__db_op_make_copy(db,
+ svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ NULL, NULL,
+ scratch_pool));
+ keep_working = TRUE;
+ }
+ else
+ {
+ /* Check if there is already a working node */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&keep_working, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ /* Step 1: Create workqueue operations to remove files and dirs in the
+ local-wc */
+ if (!keep_working
+ && queue_deletes
+ && (status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_incomplete))
+ {
+ svn_skel_t *work_item;
+ const char *local_abspath;
+
+ local_abspath = svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool);
+ if (kind == svn_node_dir)
+ {
+ apr_pool_t *iterpool;
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_PRESENT));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *node_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 1,
+ kind_map);
+ const char *node_abspath;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ node_abspath = svn_dirent_join(wcroot->abspath, node_relpath,
+ iterpool);
+
+ if (node_kind == svn_node_dir)
+ err = svn_wc__wq_build_dir_remove(&work_item,
+ db, wcroot->abspath,
+ node_abspath, FALSE,
+ iterpool, iterpool);
+ else
+ err = svn_wc__wq_build_file_remove(&work_item,
+ db,
+ wcroot->abspath,
+ node_abspath,
+ iterpool, iterpool);
+
+ if (!err)
+ err = add_work_items(wcroot->sdb, work_item, iterpool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_wc__wq_build_dir_remove(&work_item,
+ db, wcroot->abspath,
+ local_abspath, FALSE,
+ scratch_pool, iterpool));
+ svn_pool_destroy(iterpool);
+ }
+ else
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item,
+ db, wcroot->abspath,
+ local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_item, scratch_pool));
+ }
+
+ /* Step 2: Delete ACTUAL nodes */
+ if (! keep_working)
+ {
+ /* There won't be a record in NODE left for this node, so we want
+ to remove *all* ACTUAL nodes, including ACTUAL ONLY. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ else if (! keep_as_working)
+ {
+ /* Delete only the ACTUAL nodes that apply to a delete of a BASE node */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ /* Else: Everything has been turned into a copy, so we want to keep all
+ ACTUAL_NODE records */
+
+ /* Step 3: Delete WORKING nodes */
+ if (conflict)
+ {
+ apr_pool_t *iterpool;
+
+ /*
+ * When deleting a conflicted node, moves of any moved-outside children
+ * of the node must be broken. Else, the destination will still be marked
+ * moved-here after the move source disappears from the working copy.
+ *
+ * ### FIXME: It would be nicer to have the conflict resolver
+ * break the move instead. It might also be a good idea to
+ * flag a tree conflict on each moved-away child. But doing so
+ * might introduce actual-only nodes without direct parents,
+ * and we're not yet sure if other existing code is prepared
+ * to handle such nodes. To be revisited post-1.8.
+ */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_OUTSIDE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath,
+ relpath_depth(local_relpath)));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *child_relpath;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+ child_relpath = svn_sqlite__column_text(stmt, 0, iterpool);
+ err = clear_moved_here(child_relpath, wcroot, iterpool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+ if (keep_working)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WORKING_BASE_DELETE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WORKING_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Step 4: Delete the BASE node descendants */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_BASE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* Step 5: handle the BASE node itself */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_wc__db_retract_parent_delete(wcroot, local_relpath, 0,
+ scratch_pool));
+
+ /* Step 6: Delete actual node if we don't keep working */
+ if (! keep_working)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ if (SVN_IS_VALID_REVNUM(not_present_revision))
+ {
+ struct insert_base_baton_t ibb;
+ blank_ibb(&ibb);
+
+ ibb.repos_id = repos_id;
+ ibb.status = svn_wc__db_status_not_present;
+ ibb.kind = kind;
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = not_present_revision;
+
+ /* Depending upon KIND, any of these might get used. */
+ ibb.children = NULL;
+ ibb.depth = svn_depth_unknown;
+ ibb.checksum = NULL;
+ ibb.target = NULL;
+
+ SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool));
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+ if (conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t keep_as_working,
+ svn_boolean_t queue_deletes,
+ svn_revnum_t not_present_revision,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(db_base_remove(wcroot, local_relpath,
+ db, keep_as_working, queue_deletes,
+ not_present_revision,
+ conflict, work_items, scratch_pool),
+ wcroot);
+
+ /* If this used to be a directory we should remove children so pass
+ * depth infinity. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_info_internal(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_wc__db_lock_t **lock,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_boolean_t *update_root,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ lock ? STMT_SELECT_BASE_NODE_WITH_LOCK
+ : STMT_SELECT_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ svn_wc__db_status_t node_status = svn_sqlite__column_token(stmt, 2,
+ presence_map);
+ svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 3, kind_map);
+
+ if (kind)
+ {
+ *kind = node_kind;
+ }
+ if (status)
+ {
+ *status = node_status;
+ }
+ repos_location_from_columns(repos_id, revision, repos_relpath,
+ stmt, 0, 4, 1, result_pool);
+ SVN_ERR_ASSERT(!repos_id || *repos_id != INVALID_REPOS_ID);
+ SVN_ERR_ASSERT(!repos_relpath || *repos_relpath);
+ if (lock)
+ {
+ *lock = lock_from_columns(stmt, 15, 16, 17, 18, result_pool);
+ }
+ if (changed_rev)
+ {
+ *changed_rev = svn_sqlite__column_revnum(stmt, 7);
+ }
+ if (changed_date)
+ {
+ *changed_date = svn_sqlite__column_int64(stmt, 8);
+ }
+ if (changed_author)
+ {
+ /* Result may be NULL. */
+ *changed_author = svn_sqlite__column_text(stmt, 9, result_pool);
+ }
+ if (depth)
+ {
+ if (node_kind != svn_node_dir)
+ {
+ *depth = svn_depth_unknown;
+ }
+ else
+ {
+ *depth = svn_sqlite__column_token_null(stmt, 10, depth_map,
+ svn_depth_unknown);
+ }
+ }
+ if (checksum)
+ {
+ if (node_kind != svn_node_file)
+ {
+ *checksum = NULL;
+ }
+ else
+ {
+ err = svn_sqlite__column_checksum(checksum, stmt, 5,
+ result_pool);
+ if (err != NULL)
+ err = svn_error_createf(
+ err->apr_err, err,
+ _("The node '%s' has a corrupt checksum value."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+ }
+ if (target)
+ {
+ if (node_kind != svn_node_symlink)
+ *target = NULL;
+ else
+ *target = svn_sqlite__column_text(stmt, 11, result_pool);
+ }
+ if (had_props)
+ {
+ *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 13);
+ }
+ if (props)
+ {
+ if (node_status == svn_wc__db_status_normal
+ || node_status == svn_wc__db_status_incomplete)
+ {
+ SVN_ERR(svn_sqlite__column_properties(props, stmt, 13,
+ result_pool, scratch_pool));
+ if (*props == NULL)
+ *props = apr_hash_make(result_pool);
+ }
+ else
+ {
+ assert(svn_sqlite__column_is_null(stmt, 13));
+ *props = NULL;
+ }
+ }
+ if (update_root)
+ {
+ /* It's an update root iff it's a file external. */
+ *update_root = svn_sqlite__column_boolean(stmt, 14);
+ }
+ }
+ else
+ {
+ err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ /* Note: given the composition, no need to wrap for tracing. */
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_wc__db_lock_t **lock,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_boolean_t *update_root,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ apr_int64_t repos_id;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(status, kind, revision,
+ repos_relpath, &repos_id,
+ changed_rev, changed_date,
+ changed_author, depth,
+ checksum, target, lock,
+ had_props, props, update_root,
+ wcroot, local_relpath,
+ result_pool, scratch_pool));
+ SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID);
+ SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid,
+ wcroot->sdb, repos_id, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_base_get_children_info(apr_hash_t **nodes,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *nodes = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_CHILDREN_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ struct svn_wc__db_base_info_t *info;
+ svn_error_t *err;
+ apr_int64_t repos_id;
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *name = svn_relpath_basename(child_relpath, result_pool);
+
+ info = apr_pcalloc(result_pool, sizeof(*info));
+
+ repos_id = svn_sqlite__column_int64(stmt, 1);
+ info->repos_relpath = svn_sqlite__column_text(stmt, 2, result_pool);
+ info->status = svn_sqlite__column_token(stmt, 3, presence_map);
+ info->kind = svn_sqlite__column_token(stmt, 4, kind_map);
+ info->revnum = svn_sqlite__column_revnum(stmt, 5);
+
+ info->depth = svn_sqlite__column_token_null(stmt, 6, depth_map,
+ svn_depth_unknown);
+
+ info->update_root = svn_sqlite__column_boolean(stmt, 7);
+
+ info->lock = lock_from_columns(stmt, 8, 9, 10, 11, result_pool);
+
+ err = svn_wc__db_fetch_repos_info(&info->repos_root_url, NULL,
+ wcroot->sdb, repos_id, result_pool);
+
+ if (err)
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_sqlite__reset(stmt)));
+
+
+ svn_hash_sets(*nodes, name, info);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t presence;
+
+ SVN_ERR(svn_wc__db_base_get_info(&presence, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, props, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ if (presence != svn_wc__db_status_normal
+ && presence != svn_wc__db_status_incomplete)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("The node '%s' has a BASE status that"
+ " has no properties."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return gather_repo_children(children, wcroot, local_relpath, 0,
+ result_pool, scratch_pool);
+}
+
+
+svn_error_t *
+svn_wc__db_base_set_dav_cache(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows;
+
+ SVN_ERR(get_statement_for_path(&stmt, db, local_abspath,
+ STMT_UPDATE_BASE_NODE_DAV_CACHE,
+ scratch_pool));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool));
+
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows != 1)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_dav_cache(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(get_statement_for_path(&stmt, db, local_abspath,
+ STMT_SELECT_BASE_DAV_CACHE, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__column_properties(props, stmt, 0, result_pool,
+ scratch_pool));
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+svn_error_t *
+svn_wc__db_base_clear_dav_cache_recursive(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_depth_get_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_DEPTH_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wcroot->wc_id, local_relpath, op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ svn_wc__db_status_t node_status = svn_sqlite__column_token(stmt, 2,
+ presence_map);
+ svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 3, kind_map);
+
+ if (kind)
+ {
+ *kind = node_kind;
+ }
+ if (status)
+ {
+ *status = node_status;
+
+ if (op_depth > 0)
+ SVN_ERR(convert_to_working_status(status, *status));
+ }
+ repos_location_from_columns(repos_id, revision, repos_relpath,
+ stmt, 0, 4, 1, result_pool);
+
+ if (changed_rev)
+ {
+ *changed_rev = svn_sqlite__column_revnum(stmt, 7);
+ }
+ if (changed_date)
+ {
+ *changed_date = svn_sqlite__column_int64(stmt, 8);
+ }
+ if (changed_author)
+ {
+ /* Result may be NULL. */
+ *changed_author = svn_sqlite__column_text(stmt, 9, result_pool);
+ }
+ if (depth)
+ {
+ if (node_kind != svn_node_dir)
+ {
+ *depth = svn_depth_unknown;
+ }
+ else
+ {
+ *depth = svn_sqlite__column_token_null(stmt, 10, depth_map,
+ svn_depth_unknown);
+ }
+ }
+ if (checksum)
+ {
+ if (node_kind != svn_node_file)
+ {
+ *checksum = NULL;
+ }
+ else
+ {
+ err = svn_sqlite__column_checksum(checksum, stmt, 5,
+ result_pool);
+ if (err != NULL)
+ err = svn_error_createf(
+ err->apr_err, err,
+ _("The node '%s' has a corrupt checksum value."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+ }
+ if (target)
+ {
+ if (node_kind != svn_node_symlink)
+ *target = NULL;
+ else
+ *target = svn_sqlite__column_text(stmt, 11, result_pool);
+ }
+ if (had_props)
+ {
+ *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 13);
+ }
+ if (props)
+ {
+ if (node_status == svn_wc__db_status_normal
+ || node_status == svn_wc__db_status_incomplete)
+ {
+ SVN_ERR(svn_sqlite__column_properties(props, stmt, 13,
+ result_pool, scratch_pool));
+ if (*props == NULL)
+ *props = apr_hash_make(result_pool);
+ }
+ else
+ {
+ assert(svn_sqlite__column_is_null(stmt, 13));
+ *props = NULL;
+ }
+ }
+ }
+ else
+ {
+ err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ /* Note: given the composition, no need to wrap for tracing. */
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+}
+
+
+/* Baton for passing args to with_triggers(). */
+struct with_triggers_baton_t {
+ int create_trigger;
+ int drop_trigger;
+ svn_wc__db_txn_callback_t cb_func;
+ void *cb_baton;
+};
+
+/* Helper for creating SQLite triggers, running the main transaction
+ callback, and then dropping the triggers. It guarantees that the
+ triggers will not survive the transaction. This could be used for
+ any general prefix/postscript statements where the postscript
+ *must* be executed if the transaction completes.
+
+ Implements svn_wc__db_txn_callback_t. */
+static svn_error_t *
+with_triggers(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ struct with_triggers_baton_t *b = baton;
+ svn_error_t *err1;
+ svn_error_t *err2;
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, b->create_trigger));
+
+ err1 = b->cb_func(b->cb_baton, wcroot, local_relpath, scratch_pool);
+
+ err2 = svn_sqlite__exec_statements(wcroot->sdb, b->drop_trigger);
+
+ return svn_error_trace(svn_error_compose_create(err1, err2));
+}
+
+
+/* Prototype for the "work callback" used by with_finalization(). */
+typedef svn_error_t * (*work_callback_t)(
+ void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/* Utility function to provide several features, with a guaranteed
+ finalization (ie. to drop temporary tables).
+
+ 1) for WCROOT and LOCAL_RELPATH, run TXN_CB(TXN_BATON) within a
+ sqlite transaction
+ 2) if (1) is successful and a NOTIFY_FUNC is provided, then run
+ the "work" step: WORK_CB(WORK_BATON).
+ 3) execute FINALIZE_STMT_IDX no matter what errors may be thrown
+ from the above two steps.
+
+ CANCEL_FUNC, CANCEL_BATON, NOTIFY_FUNC and NOTIFY_BATON are their
+ typical values. These are passed to the work callback, which typically
+ provides notification about the work done by TXN_CB. */
+static svn_error_t *
+with_finalization(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_txn_callback_t txn_cb,
+ void *txn_baton,
+ work_callback_t work_cb,
+ void *work_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ int finalize_stmt_idx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err1;
+ svn_error_t *err2;
+
+ err1 = svn_wc__db_with_txn(wcroot, local_relpath, txn_cb, txn_baton,
+ scratch_pool);
+
+ if (err1 == NULL && notify_func != NULL)
+ {
+ err2 = work_cb(work_baton, wcroot,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool);
+ err1 = svn_error_compose_create(err1, err2);
+ }
+
+ err2 = svn_sqlite__exec_statements(wcroot->sdb, finalize_stmt_idx);
+
+ return svn_error_trace(svn_error_compose_create(err1, err2));
+}
+
+
+/* Initialize the baton with appropriate "blank" values. This allows the
+ insertion function to leave certain columns null. */
+static void
+blank_ieb(insert_external_baton_t *ieb)
+{
+ memset(ieb, 0, sizeof(*ieb));
+ ieb->revision = SVN_INVALID_REVNUM;
+ ieb->changed_rev = SVN_INVALID_REVNUM;
+ ieb->repos_id = INVALID_REPOS_ID;
+
+ ieb->recorded_peg_revision = SVN_INVALID_REVNUM;
+ ieb->recorded_revision = SVN_INVALID_REVNUM;
+}
+
+/* Insert the externals row represented by (insert_external_baton_t *) BATON.
+ *
+ * Implements svn_wc__db_txn_callback_t. */
+static svn_error_t *
+insert_external_node(const insert_external_baton_t *ieb,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_error_t *err;
+ svn_boolean_t update_root;
+ apr_int64_t repos_id;
+ svn_sqlite__stmt_t *stmt;
+
+ if (ieb->repos_id != INVALID_REPOS_ID)
+ repos_id = ieb->repos_id;
+ else
+ SVN_ERR(create_repos_id(&repos_id, ieb->repos_root_url, ieb->repos_uuid,
+ wcroot->sdb, scratch_pool));
+
+ /* And there must be no existing BASE node or it must be a file external */
+ err = svn_wc__db_base_get_info_internal(&status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, &update_root,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ }
+ else if (status == svn_wc__db_status_normal && !update_root)
+ return svn_error_create(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, NULL);
+
+ if (ieb->kind == svn_node_file
+ || ieb->kind == svn_node_symlink)
+ {
+ struct insert_base_baton_t ibb;
+
+ blank_ibb(&ibb);
+
+ ibb.status = svn_wc__db_status_normal;
+ ibb.kind = ieb->kind;
+
+ ibb.repos_id = repos_id;
+ ibb.repos_relpath = ieb->repos_relpath;
+ ibb.revision = ieb->revision;
+
+ ibb.props = ieb->props;
+ ibb.iprops = ieb->iprops;
+ ibb.changed_rev = ieb->changed_rev;
+ ibb.changed_date = ieb->changed_date;
+ ibb.changed_author = ieb->changed_author;
+
+ ibb.dav_cache = ieb->dav_cache;
+
+ ibb.checksum = ieb->checksum;
+ ibb.target = ieb->target;
+
+ ibb.conflict = ieb->conflict;
+
+ ibb.update_actual_props = ieb->update_actual_props;
+ ibb.new_actual_props = ieb->new_actual_props;
+
+ ibb.keep_recorded_info = ieb->keep_recorded_info;
+
+ ibb.work_items = ieb->work_items;
+
+ ibb.file_external = TRUE;
+
+ SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool));
+ }
+ else
+ SVN_ERR(add_work_items(wcroot->sdb, ieb->work_items, scratch_pool));
+
+ /* The externals table only support presence normal and excluded */
+ SVN_ERR_ASSERT(ieb->presence == svn_wc__db_status_normal
+ || ieb->presence == svn_wc__db_status_excluded);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_EXTERNAL));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "issttsis",
+ wcroot->wc_id,
+ local_relpath,
+ svn_relpath_dirname(local_relpath,
+ scratch_pool),
+ presence_map, ieb->presence,
+ kind_map, ieb->kind,
+ ieb->record_ancestor_relpath,
+ repos_id,
+ ieb->recorded_repos_relpath));
+
+ if (SVN_IS_VALID_REVNUM(ieb->recorded_peg_revision))
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 9, ieb->recorded_peg_revision));
+
+ if (SVN_IS_VALID_REVNUM(ieb->recorded_revision))
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 10, ieb->recorded_revision));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_external_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+
+ const apr_hash_t *props,
+ apr_array_header_t *iprops,
+
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+
+ const svn_checksum_t *checksum,
+
+ const apr_hash_t *dav_cache,
+
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+
+ svn_boolean_t keep_recorded_info,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_external_baton_t ieb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (! wri_abspath)
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath,
+ record_ancestor_abspath));
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ blank_ieb(&ieb);
+
+ ieb.kind = svn_node_file;
+ ieb.presence = svn_wc__db_status_normal;
+
+ ieb.repos_root_url = repos_root_url;
+ ieb.repos_uuid = repos_uuid;
+
+ ieb.repos_relpath = repos_relpath;
+ ieb.revision = revision;
+
+ ieb.props = props;
+ ieb.iprops = iprops;
+
+ ieb.changed_rev = changed_rev;
+ ieb.changed_date = changed_date;
+ ieb.changed_author = changed_author;
+
+ ieb.checksum = checksum;
+
+ ieb.dav_cache = dav_cache;
+
+ ieb.record_ancestor_relpath = svn_dirent_skip_ancestor(
+ wcroot->abspath,
+ record_ancestor_abspath);
+ ieb.recorded_repos_relpath = recorded_repos_relpath;
+ ieb.recorded_peg_revision = recorded_peg_revision;
+ ieb.recorded_revision = recorded_revision;
+
+ ieb.update_actual_props = update_actual_props;
+ ieb.new_actual_props = new_actual_props;
+
+ ieb.keep_recorded_info = keep_recorded_info;
+
+ ieb.conflict = conflict;
+ ieb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_external_node(&ieb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_external_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *target,
+ const apr_hash_t *dav_cache,
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ svn_boolean_t keep_recorded_info,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_external_baton_t ieb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (! wri_abspath)
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath,
+ record_ancestor_abspath));
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ blank_ieb(&ieb);
+
+ ieb.kind = svn_node_symlink;
+ ieb.presence = svn_wc__db_status_normal;
+
+ ieb.repos_root_url = repos_root_url;
+ ieb.repos_uuid = repos_uuid;
+
+ ieb.repos_relpath = repos_relpath;
+ ieb.revision = revision;
+
+ ieb.props = props;
+
+ ieb.changed_rev = changed_rev;
+ ieb.changed_date = changed_date;
+ ieb.changed_author = changed_author;
+
+ ieb.target = target;
+
+ ieb.dav_cache = dav_cache;
+
+ ieb.record_ancestor_relpath = svn_dirent_skip_ancestor(
+ wcroot->abspath,
+ record_ancestor_abspath);
+ ieb.recorded_repos_relpath = recorded_repos_relpath;
+ ieb.recorded_peg_revision = recorded_peg_revision;
+ ieb.recorded_revision = recorded_revision;
+
+ ieb.update_actual_props = update_actual_props;
+ ieb.new_actual_props = new_actual_props;
+
+ ieb.keep_recorded_info = keep_recorded_info;
+
+ ieb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_external_node(&ieb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_external_add_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_external_baton_t ieb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (! wri_abspath)
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath,
+ record_ancestor_abspath));
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ blank_ieb(&ieb);
+
+ ieb.kind = svn_node_dir;
+ ieb.presence = svn_wc__db_status_normal;
+
+ ieb.repos_root_url = repos_root_url;
+ ieb.repos_uuid = repos_uuid;
+
+ ieb.record_ancestor_relpath = svn_dirent_skip_ancestor(
+ wcroot->abspath,
+ record_ancestor_abspath);
+ ieb.recorded_repos_relpath = recorded_repos_relpath;
+ ieb.recorded_peg_revision = recorded_peg_revision;
+ ieb.recorded_revision = recorded_revision;
+
+ ieb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_external_node(&ieb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_external_remove(). */
+static svn_error_t *
+db_external_remove(const svn_skel_t *work_items,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_EXTERNAL));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+
+ /* ### What about actual? */
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_external_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (! wri_abspath)
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ SVN_WC__DB_WITH_TXN(db_external_remove(work_items, wcroot, local_relpath,
+ scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_external_read(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ const char **definining_abspath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **recorded_repos_relpath,
+ svn_revnum_t *recorded_peg_revision,
+ svn_revnum_t *recorded_revision,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_info;
+ svn_error_t *err = NULL;
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (! wri_abspath)
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_EXTERNAL_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_info, stmt));
+
+ if (have_info)
+ {
+ if (status)
+ *status = svn_sqlite__column_token(stmt, 0, presence_map);
+
+ if (kind)
+ *kind = svn_sqlite__column_token(stmt, 1, kind_map);
+
+ if (definining_abspath)
+ {
+ const char *record_relpath = svn_sqlite__column_text(stmt, 2, NULL);
+
+ *definining_abspath = svn_dirent_join(wcroot->abspath,
+ record_relpath, result_pool);
+ }
+
+ if (repos_root_url || repos_uuid)
+ {
+ apr_int64_t repos_id;
+
+ repos_id = svn_sqlite__column_int64(stmt, 3);
+
+ err = svn_error_compose_create(
+ err,
+ svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid,
+ wcroot->sdb, repos_id,
+ result_pool));
+ }
+
+ if (recorded_repos_relpath)
+ *recorded_repos_relpath = svn_sqlite__column_text(stmt, 4,
+ result_pool);
+
+ if (recorded_peg_revision)
+ *recorded_peg_revision = svn_sqlite__column_revnum(stmt, 5);
+
+ if (recorded_revision)
+ *recorded_revision = svn_sqlite__column_revnum(stmt, 6);
+ }
+ else
+ {
+ err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' is not an external."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ return svn_error_trace(
+ svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+}
+
+svn_error_t *
+svn_wc__db_committable_externals_below(apr_array_header_t **externals,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t immediates_only,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ svn_sqlite__stmt_t *stmt;
+ const char *local_relpath;
+ svn_boolean_t have_row;
+ svn_wc__committable_external_info_t *info;
+ svn_node_kind_t db_kind;
+ apr_array_header_t *result = NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ immediates_only
+ ? STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW
+ : STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ result = apr_array_make(result_pool, 0,
+ sizeof(svn_wc__committable_external_info_t *));
+
+ while (have_row)
+ {
+ info = apr_palloc(result_pool, sizeof(*info));
+
+ local_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ info->local_abspath = svn_dirent_join(wcroot->abspath, local_relpath,
+ result_pool);
+
+ db_kind = svn_sqlite__column_token(stmt, 1, kind_map);
+ SVN_ERR_ASSERT(db_kind == svn_node_file || db_kind == svn_node_dir);
+ info->kind = db_kind;
+
+ info->repos_relpath = svn_sqlite__column_text(stmt, 2, result_pool);
+ info->repos_root_url = svn_sqlite__column_text(stmt, 3, result_pool);
+
+ APR_ARRAY_PUSH(result, svn_wc__committable_external_info_t *) = info;
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ *externals = result;
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_wc__db_externals_defined_below(apr_hash_t **externals,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ svn_sqlite__stmt_t *stmt;
+ const char *local_relpath;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_EXTERNALS_DEFINED));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ *externals = apr_hash_make(result_pool);
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *def_local_relpath;
+
+ local_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ def_local_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+
+ svn_hash_sets(*externals,
+ svn_dirent_join(wcroot->abspath, local_relpath,
+ result_pool),
+ svn_dirent_join(wcroot->abspath, def_local_relpath,
+ result_pool));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_wc__db_externals_gather_definitions(apr_hash_t **externals,
+ apr_hash_t **depths,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ svn_sqlite__stmt_t *stmt;
+ const char *local_relpath;
+ svn_boolean_t have_row;
+ svn_error_t *err = NULL;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, iterpool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *externals = apr_hash_make(result_pool);
+ if (depths != NULL)
+ *depths = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_EXTERNAL_PROPERTIES));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ apr_hash_t *node_props;
+ const char *external_value;
+
+ svn_pool_clear(iterpool);
+ err = svn_sqlite__column_properties(&node_props, stmt, 0, iterpool,
+ iterpool);
+
+ if (err)
+ break;
+
+ external_value = svn_prop_get_value(node_props, SVN_PROP_EXTERNALS);
+
+ if (external_value)
+ {
+ const char *node_abspath;
+ const char *node_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+
+ node_abspath = svn_dirent_join(wcroot->abspath, node_relpath,
+ result_pool);
+
+ svn_hash_sets(*externals, node_abspath,
+ apr_pstrdup(result_pool, external_value));
+
+ if (depths)
+ {
+ svn_depth_t depth
+ = svn_sqlite__column_token_null(stmt, 2, depth_map,
+ svn_depth_unknown);
+
+ svn_hash_sets(*depths, node_abspath,
+ /* Use static string */
+ svn_token__to_word(depth_map, depth));
+ }
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return svn_error_trace(svn_error_compose_create(err,
+ svn_sqlite__reset(stmt)));
+}
+
+/* Copy the ACTUAL data for SRC_RELPATH and tweak it to refer to DST_RELPATH.
+ The new ACTUAL data won't have any conflicts. */
+static svn_error_t *
+copy_actual(svn_wc__db_wcroot_t *src_wcroot,
+ const char *src_relpath,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const char *dst_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", src_wcroot->wc_id, src_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ apr_size_t props_size;
+ const char *changelist;
+ const char *properties;
+
+ /* Skipping conflict data... */
+ changelist = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ /* No need to parse the properties when simply copying. */
+ properties = svn_sqlite__column_blob(stmt, 1, &props_size, scratch_pool);
+
+ if (changelist || properties)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb,
+ STMT_INSERT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "issbs",
+ dst_wcroot->wc_id, dst_relpath,
+ svn_relpath_dirname(dst_relpath, scratch_pool),
+ properties, props_size, changelist));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_wc__db_op_copy to handle copying from one db to
+ another */
+static svn_error_t *
+cross_db_copy(svn_wc__db_wcroot_t *src_wcroot,
+ const char *src_relpath,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const char *dst_relpath,
+ svn_wc__db_status_t dst_status,
+ int dst_op_depth,
+ int dst_np_op_depth,
+ svn_node_kind_t kind,
+ const apr_array_header_t *children,
+ apr_int64_t copyfrom_id,
+ const char *copyfrom_relpath,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *scratch_pool)
+{
+ insert_working_baton_t iwb;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ const svn_checksum_t *checksum;
+ apr_hash_t *props;
+ svn_depth_t depth;
+
+ SVN_ERR_ASSERT(kind == svn_node_file
+ || kind == svn_node_dir
+ );
+
+ SVN_ERR(read_info(NULL, NULL, NULL, NULL, NULL,
+ &changed_rev, &changed_date, &changed_author, &depth,
+ &checksum, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ src_wcroot, src_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(db_read_pristine_props(&props, src_wcroot, src_relpath, FALSE,
+ scratch_pool, scratch_pool));
+
+ blank_iwb(&iwb);
+ iwb.presence = dst_status;
+ iwb.kind = kind;
+
+ iwb.props = props;
+ iwb.changed_rev = changed_rev;
+ iwb.changed_date = changed_date;
+ iwb.changed_author = changed_author;
+ iwb.original_repos_id = copyfrom_id;
+ iwb.original_repos_relpath = copyfrom_relpath;
+ iwb.original_revnum = copyfrom_rev;
+ iwb.moved_here = FALSE;
+
+ iwb.op_depth = dst_op_depth;
+
+ iwb.checksum = checksum;
+ iwb.children = children;
+ iwb.depth = depth;
+
+ iwb.not_present_op_depth = dst_np_op_depth;
+
+ SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath, scratch_pool));
+
+ SVN_ERR(copy_actual(src_wcroot, src_relpath,
+ dst_wcroot, dst_relpath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for scan_deletion_txn. Extracts the moved-to information, if
+ any, from STMT. Sets *SCAN to FALSE if moved-to was available. */
+static svn_error_t *
+get_moved_to(const char **moved_to_relpath_p,
+ const char **moved_to_op_root_relpath_p,
+ svn_boolean_t *scan,
+ svn_sqlite__stmt_t *stmt,
+ const char *current_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *moved_to_relpath = svn_sqlite__column_text(stmt, 3, NULL);
+
+ if (moved_to_relpath)
+ {
+ const char *moved_to_op_root_relpath = moved_to_relpath;
+
+ if (strcmp(current_relpath, local_relpath))
+ {
+ /* LOCAL_RELPATH is a child inside the move op-root. */
+ const char *moved_child_relpath;
+
+ /* The CURRENT_RELPATH is the op_root of the delete-half of
+ * the move. LOCAL_RELPATH is a child that was moved along.
+ * Compute the child's new location within the move target. */
+ moved_child_relpath = svn_relpath_skip_ancestor(current_relpath,
+ local_relpath);
+ SVN_ERR_ASSERT(moved_child_relpath &&
+ strlen(moved_child_relpath) > 0);
+ moved_to_relpath = svn_relpath_join(moved_to_op_root_relpath,
+ moved_child_relpath,
+ result_pool);
+ }
+
+ if (moved_to_op_root_relpath && moved_to_op_root_relpath_p)
+ *moved_to_op_root_relpath_p
+ = apr_pstrdup(result_pool, moved_to_op_root_relpath);
+
+ if (moved_to_relpath && moved_to_relpath_p)
+ *moved_to_relpath_p
+ = apr_pstrdup(result_pool, moved_to_relpath);
+
+ *scan = FALSE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_scan_deletion().
+ */
+static svn_error_t *
+scan_deletion_txn(const char **base_del_relpath,
+ const char **moved_to_relpath,
+ const char **work_del_relpath,
+ const char **moved_to_op_root_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *current_relpath = local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_status_t work_presence;
+ svn_boolean_t have_row, scan, have_base;
+ int op_depth;
+
+ /* Initialize all the OUT parameters. */
+ if (base_del_relpath != NULL)
+ *base_del_relpath = NULL;
+ if (moved_to_relpath != NULL)
+ *moved_to_relpath = NULL;
+ if (work_del_relpath != NULL)
+ *work_del_relpath = NULL;
+ if (moved_to_op_root_relpath != NULL)
+ *moved_to_op_root_relpath = NULL;
+
+ /* If looking for moved-to info then we need to scan every path
+ until we find it. If not looking for moved-to we only need to
+ check op-roots and parents of op-roots. */
+ scan = (moved_to_op_root_relpath || moved_to_relpath);
+
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ scan ? STMT_SELECT_DELETION_INFO_SCAN
+ : STMT_SELECT_DELETION_INFO));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, current_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ work_presence = svn_sqlite__column_token(stmt, 1, presence_map);
+ have_base = !svn_sqlite__column_is_null(stmt, 0);
+ if (work_presence != svn_wc__db_status_not_present
+ && work_presence != svn_wc__db_status_base_deleted)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ svn_sqlite__reset(stmt),
+ _("Expected node '%s' to be deleted."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ op_depth = svn_sqlite__column_int(stmt, 2);
+
+ /* Special case: LOCAL_RELPATH not-present within a WORKING tree, we
+ treat this as an op-root. At commit time we need to explicitly
+ delete such nodes otherwise they will be present in the
+ repository copy. */
+ if (work_presence == svn_wc__db_status_not_present
+ && work_del_relpath && !*work_del_relpath)
+ {
+ *work_del_relpath = apr_pstrdup(result_pool, current_relpath);
+
+ if (!scan && !base_del_relpath)
+ {
+ /* We have all we need, exit early */
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+ }
+
+
+ while (TRUE)
+ {
+ svn_error_t *err;
+ const char *parent_relpath;
+ int current_depth = relpath_depth(current_relpath);
+
+ /* Step CURRENT_RELPATH to op-root */
+
+ while (TRUE)
+ {
+ if (scan)
+ {
+ err = get_moved_to(moved_to_relpath, moved_to_op_root_relpath,
+ &scan, stmt, current_relpath,
+ wcroot, local_relpath,
+ result_pool, scratch_pool);
+ if (err || (!scan
+ && !base_del_relpath
+ && !work_del_relpath))
+ {
+ /* We have all we need (or an error occurred) */
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return svn_error_trace(err);
+ }
+ }
+
+ if (current_depth <= op_depth)
+ break;
+
+ current_relpath = svn_relpath_dirname(current_relpath, scratch_pool);
+ --current_depth;
+
+ if (scan || current_depth == op_depth)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ current_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR_ASSERT(have_row);
+ have_base = !svn_sqlite__column_is_null(stmt, 0);
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Now CURRENT_RELPATH is an op-root, have a look at the parent. */
+
+ SVN_ERR_ASSERT(current_relpath[0] != '\0'); /* Catch invalid data */
+ parent_relpath = svn_relpath_dirname(current_relpath, scratch_pool);
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ {
+ /* No row means no WORKING node which mean we just fell off
+ the WORKING tree, so CURRENT_RELPATH is the op-root
+ closest to the wc root. */
+ if (have_base && base_del_relpath)
+ *base_del_relpath = apr_pstrdup(result_pool, current_relpath);
+ break;
+ }
+
+ /* Still in the WORKING tree so the first time we get here
+ CURRENT_RELPATH is a delete op-root in the WORKING tree. */
+ if (work_del_relpath && !*work_del_relpath)
+ {
+ *work_del_relpath = apr_pstrdup(result_pool, current_relpath);
+
+ if (!scan && !base_del_relpath)
+ break; /* We have all we need */
+ }
+
+ current_relpath = parent_relpath;
+ op_depth = svn_sqlite__column_int(stmt, 2);
+ have_base = !svn_sqlite__column_is_null(stmt, 0);
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_scan_deletion(const char **base_del_abspath,
+ const char **moved_to_abspath,
+ const char **work_del_abspath,
+ const char **moved_to_op_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *base_del_relpath, *moved_to_relpath, *work_del_relpath;
+ const char *moved_to_op_root_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ scan_deletion_txn(&base_del_relpath, &moved_to_relpath,
+ &work_del_relpath, &moved_to_op_root_relpath,
+ wcroot, local_relpath, result_pool, scratch_pool),
+ wcroot);
+
+ if (base_del_abspath)
+ {
+ *base_del_abspath = (base_del_relpath
+ ? svn_dirent_join(wcroot->abspath,
+ base_del_relpath, result_pool)
+ : NULL);
+ }
+ if (moved_to_abspath)
+ {
+ *moved_to_abspath = (moved_to_relpath
+ ? svn_dirent_join(wcroot->abspath,
+ moved_to_relpath, result_pool)
+ : NULL);
+ }
+ if (work_del_abspath)
+ {
+ *work_del_abspath = (work_del_relpath
+ ? svn_dirent_join(wcroot->abspath,
+ work_del_relpath, result_pool)
+ : NULL);
+ }
+ if (moved_to_op_root_abspath)
+ {
+ *moved_to_op_root_abspath = (moved_to_op_root_relpath
+ ? svn_dirent_join(wcroot->abspath,
+ moved_to_op_root_relpath,
+ result_pool)
+ : NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *COPYFROM_ID, *COPYFROM_RELPATH, *COPYFROM_REV to the values
+ appropriate for the copy. Also return *STATUS, *KIND and *HAVE_WORK, *OP_ROOT
+ since they are available. This is a helper for
+ svn_wc__db_op_copy. */
+static svn_error_t *
+get_info_for_copy(apr_int64_t *copyfrom_id,
+ const char **copyfrom_relpath,
+ svn_revnum_t *copyfrom_rev,
+ svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_boolean_t *op_root,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_relpath;
+ svn_revnum_t revision;
+ svn_wc__db_status_t node_status;
+ apr_int64_t repos_id;
+ svn_boolean_t is_op_root;
+
+ SVN_ERR(read_info(&node_status, kind, &revision, &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL, copyfrom_relpath,
+ copyfrom_id, copyfrom_rev, NULL, NULL, NULL, NULL,
+ NULL, &is_op_root, NULL, NULL,
+ NULL /* have_base */,
+ NULL /* have_more_work */,
+ NULL /* have_work */,
+ wcroot, local_relpath, result_pool, scratch_pool));
+
+ if (op_root)
+ *op_root = is_op_root;
+
+ if (node_status == svn_wc__db_status_excluded)
+ {
+ /* The parent cannot be excluded, so look at the parent and then
+ adjust the relpath */
+ const char *parent_relpath, *base_name;
+
+ svn_dirent_split(&parent_relpath, &base_name, local_relpath,
+ scratch_pool);
+ SVN_ERR(get_info_for_copy(copyfrom_id, copyfrom_relpath, copyfrom_rev,
+ NULL, NULL, NULL,
+ wcroot, parent_relpath,
+ scratch_pool, scratch_pool));
+ if (*copyfrom_relpath)
+ *copyfrom_relpath = svn_relpath_join(*copyfrom_relpath, base_name,
+ result_pool);
+ }
+ else if (node_status == svn_wc__db_status_added)
+ {
+ SVN_ERR(scan_addition(&node_status, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+ }
+ else if (node_status == svn_wc__db_status_deleted && is_op_root)
+ {
+ const char *base_del_relpath, *work_del_relpath;
+
+ SVN_ERR(scan_deletion_txn(&base_del_relpath, NULL,
+ &work_del_relpath,
+ NULL, wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+ if (work_del_relpath)
+ {
+ const char *op_root_relpath;
+ const char *parent_del_relpath = svn_relpath_dirname(work_del_relpath,
+ scratch_pool);
+
+ /* Similar to, but not the same as, the _scan_addition and
+ _join above. Can we use get_copyfrom here? */
+ SVN_ERR(scan_addition(NULL, &op_root_relpath,
+ NULL, NULL, /* repos_* */
+ copyfrom_relpath, copyfrom_id, copyfrom_rev,
+ NULL, NULL, NULL, wcroot, parent_del_relpath,
+ scratch_pool, scratch_pool));
+ *copyfrom_relpath
+ = svn_relpath_join(*copyfrom_relpath,
+ svn_relpath_skip_ancestor(op_root_relpath,
+ local_relpath),
+ result_pool);
+ }
+ else if (base_del_relpath)
+ {
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, copyfrom_rev,
+ copyfrom_relpath,
+ copyfrom_id, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ result_pool,
+ scratch_pool));
+ }
+ else
+ SVN_ERR_MALFUNCTION();
+ }
+ else if (node_status == svn_wc__db_status_deleted)
+ {
+ /* Keep original_* from read_info() to allow seeing the difference
+ between base-deleted and not present */
+ }
+ else
+ {
+ *copyfrom_relpath = repos_relpath;
+ *copyfrom_rev = revision;
+ *copyfrom_id = repos_id;
+ }
+
+ if (status)
+ *status = node_status;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *OP_DEPTH to the highest op depth of WCROOT:LOCAL_RELPATH. */
+static svn_error_t *
+op_depth_of(int *op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR_ASSERT(have_row);
+ *op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Determine at which OP_DEPTH a copy of COPYFROM_REPOS_ID, COPYFROM_RELPATH at
+ revision COPYFROM_REVISION should be inserted as LOCAL_RELPATH. Do this
+ by checking if this would be a direct child of a copy of its parent
+ directory. If it is then set *OP_DEPTH to the op_depth of its parent.
+
+ If the node is not a direct copy at the same revision of the parent
+ *NP_OP_DEPTH will be set to the op_depth of the parent when a not-present
+ node should be inserted at this op_depth. This will be the case when the
+ parent already defined an incomplete child with the same name. Otherwise
+ *NP_OP_DEPTH will be set to -1.
+
+ If the parent node is not the parent of the to be copied node, then
+ *OP_DEPTH will be set to the proper op_depth for a new operation root.
+
+ Set *PARENT_OP_DEPTH to the op_depth of the parent.
+
+ */
+static svn_error_t *
+op_depth_for_copy(int *op_depth,
+ int *np_op_depth,
+ int *parent_op_depth,
+ apr_int64_t copyfrom_repos_id,
+ const char *copyfrom_relpath,
+ svn_revnum_t copyfrom_revision,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const char *parent_relpath, *name;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int incomplete_op_depth = -1;
+ int min_op_depth = 1; /* Never touch BASE */
+
+ *op_depth = relpath_depth(local_relpath);
+ *np_op_depth = -1;
+
+ svn_relpath_split(&parent_relpath, &name, local_relpath, scratch_pool);
+ *parent_op_depth = relpath_depth(parent_relpath);
+
+ if (!copyfrom_relpath)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ svn_wc__db_status_t status = svn_sqlite__column_token(stmt, 1,
+ presence_map);
+
+ min_op_depth = svn_sqlite__column_int(stmt, 0);
+ if (status == svn_wc__db_status_incomplete)
+ incomplete_op_depth = min_op_depth;
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ svn_wc__db_status_t presence = svn_sqlite__column_token(stmt, 1,
+ presence_map);
+
+ *parent_op_depth = svn_sqlite__column_int(stmt, 0);
+ if (*parent_op_depth < min_op_depth)
+ {
+ /* We want to create a copy; not overwrite the lower layers */
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+
+ /* You can only add children below a node that exists.
+ In WORKING that must be status added, which is represented
+ as presence normal */
+ SVN_ERR_ASSERT(presence == svn_wc__db_status_normal);
+
+ if ((incomplete_op_depth < 0)
+ || (incomplete_op_depth == *parent_op_depth))
+ {
+ apr_int64_t parent_copyfrom_repos_id
+ = svn_sqlite__column_int64(stmt, 10);
+ const char *parent_copyfrom_relpath
+ = svn_sqlite__column_text(stmt, 11, NULL);
+ svn_revnum_t parent_copyfrom_revision
+ = svn_sqlite__column_revnum(stmt, 12);
+
+ if (parent_copyfrom_repos_id == copyfrom_repos_id)
+ {
+ if (copyfrom_revision == parent_copyfrom_revision
+ && !strcmp(copyfrom_relpath,
+ svn_relpath_join(parent_copyfrom_relpath, name,
+ scratch_pool)))
+ *op_depth = *parent_op_depth;
+ else if (incomplete_op_depth > 0)
+ *np_op_depth = incomplete_op_depth;
+ }
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Like svn_wc__db_op_copy(), but with WCROOT+LOCAL_RELPATH
+ * instead of DB+LOCAL_ABSPATH. A non-zero MOVE_OP_DEPTH implies that the
+ * copy operation is part of a move, and indicates the op-depth of the
+ * move destination op-root. */
+static svn_error_t *
+db_op_copy(svn_wc__db_wcroot_t *src_wcroot,
+ const char *src_relpath,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const char *dst_relpath,
+ const svn_skel_t *work_items,
+ int move_op_depth,
+ apr_pool_t *scratch_pool)
+{
+ const char *copyfrom_relpath;
+ svn_revnum_t copyfrom_rev;
+ svn_wc__db_status_t status;
+ svn_wc__db_status_t dst_presence;
+ svn_boolean_t op_root;
+ apr_int64_t copyfrom_id;
+ int dst_op_depth;
+ int dst_np_op_depth;
+ int dst_parent_op_depth;
+ svn_node_kind_t kind;
+ const apr_array_header_t *children;
+
+ SVN_ERR(get_info_for_copy(&copyfrom_id, &copyfrom_relpath, &copyfrom_rev,
+ &status, &kind, &op_root, src_wcroot,
+ src_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(op_depth_for_copy(&dst_op_depth, &dst_np_op_depth,
+ &dst_parent_op_depth,
+ copyfrom_id, copyfrom_relpath, copyfrom_rev,
+ dst_wcroot, dst_relpath, scratch_pool));
+
+ SVN_ERR_ASSERT(kind == svn_node_file || kind == svn_node_dir);
+
+ /* ### New status, not finished, see notes/wc-ng/copying */
+ switch (status)
+ {
+ case svn_wc__db_status_normal:
+ case svn_wc__db_status_added:
+ case svn_wc__db_status_moved_here:
+ case svn_wc__db_status_copied:
+ dst_presence = svn_wc__db_status_normal;
+ break;
+ case svn_wc__db_status_deleted:
+ if (op_root)
+ {
+ /* If the lower layer is already shadowcopied we can skip adding
+ a not present node. */
+ svn_error_t *err;
+ svn_wc__db_status_t dst_status;
+
+ err = read_info(&dst_status, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ dst_wcroot, dst_relpath, scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else if (dst_status == svn_wc__db_status_deleted)
+ {
+ /* Node is already deleted; skip the NODES work, but do
+ install wq items if requested */
+ SVN_ERR(add_work_items(dst_wcroot->sdb, work_items,
+ scratch_pool));
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ {
+ /* This node is either a not-present node (which should be copied), or
+ a base-delete of some lower layer (which shouldn't).
+ Subversion <= 1.7 always added a not-present node here, which is
+ safe (as it postpones the hard work until commit time and then we
+ ask the repository), but it breaks some move scenarios.
+ */
+
+ if (! copyfrom_relpath)
+ {
+ SVN_ERR(add_work_items(dst_wcroot->sdb, work_items,
+ scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ /* Fall through. Install not present node */
+ }
+ case svn_wc__db_status_not_present:
+ case svn_wc__db_status_excluded:
+ /* These presence values should not create a new op depth */
+ if (dst_np_op_depth > 0)
+ {
+ dst_op_depth = dst_np_op_depth;
+ dst_np_op_depth = -1;
+ }
+ if (status == svn_wc__db_status_excluded)
+ dst_presence = svn_wc__db_status_excluded;
+ else
+ dst_presence = svn_wc__db_status_not_present;
+ break;
+ case svn_wc__db_status_server_excluded:
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot copy '%s' excluded by server"),
+ path_for_error_message(src_wcroot,
+ src_relpath,
+ scratch_pool));
+ default:
+ /* Perhaps we should allow incomplete to incomplete? We can't
+ avoid incomplete working nodes as one step in copying a
+ directory is to add incomplete children. */
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot handle status of '%s'"),
+ path_for_error_message(src_wcroot,
+ src_relpath,
+ scratch_pool));
+ }
+
+ if (kind == svn_node_dir)
+ {
+ int src_op_depth;
+
+ SVN_ERR(op_depth_of(&src_op_depth, src_wcroot, src_relpath));
+ SVN_ERR(gather_repo_children(&children, src_wcroot, src_relpath,
+ src_op_depth, scratch_pool, scratch_pool));
+ }
+ else
+ children = NULL;
+
+ if (src_wcroot == dst_wcroot)
+ {
+ svn_sqlite__stmt_t *stmt;
+ const char *dst_parent_relpath = svn_relpath_dirname(dst_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb,
+ STMT_INSERT_WORKING_NODE_COPY_FROM));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "issdst",
+ src_wcroot->wc_id, src_relpath,
+ dst_relpath,
+ dst_op_depth,
+ dst_parent_relpath,
+ presence_map, dst_presence));
+
+ if (move_op_depth > 0)
+ {
+ if (relpath_depth(dst_relpath) == move_op_depth)
+ {
+ /* We're moving the root of the move operation.
+ *
+ * When an added node or the op-root of a copy is moved,
+ * there is no 'moved-from' corresponding to the moved-here
+ * node. So the net effect is the same as copy+delete.
+ * Perform a normal copy operation in these cases. */
+ if (!(status == svn_wc__db_status_added ||
+ (status == svn_wc__db_status_copied && op_root)))
+ SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1));
+ }
+ else
+ {
+ svn_sqlite__stmt_t *info_stmt;
+ svn_boolean_t have_row;
+
+ /* We're moving a child along with the root of the move.
+ *
+ * Set moved-here depending on dst_parent, propagating the
+ * above decision to moved-along children at the same op_depth.
+ * We can't use scan_addition() to detect moved-here because
+ * the delete-half of the move might not yet exist. */
+ SVN_ERR(svn_sqlite__get_statement(&info_stmt, dst_wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(info_stmt, "is", dst_wcroot->wc_id,
+ dst_parent_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, info_stmt));
+ SVN_ERR_ASSERT(have_row);
+ if (svn_sqlite__column_boolean(info_stmt, 15) &&
+ dst_op_depth == dst_parent_op_depth)
+ {
+ SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1));
+ SVN_ERR(svn_sqlite__reset(info_stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__reset(info_stmt));
+
+ /* If the child has been moved into the tree we're moving,
+ * keep its moved-here bit set. */
+ SVN_ERR(svn_sqlite__get_statement(&info_stmt,
+ dst_wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(info_stmt, "is",
+ dst_wcroot->wc_id, src_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, info_stmt));
+ SVN_ERR_ASSERT(have_row);
+ if (svn_sqlite__column_boolean(info_stmt, 15))
+ SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1));
+ SVN_ERR(svn_sqlite__reset(info_stmt));
+ }
+ }
+ }
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* ### Copying changelist is OK for a move but what about a copy? */
+ SVN_ERR(copy_actual(src_wcroot, src_relpath,
+ dst_wcroot, dst_relpath, scratch_pool));
+
+ if (dst_np_op_depth > 0)
+ {
+ /* We introduce a not-present node at the parent's op_depth to
+ properly start a new op-depth at our own op_depth. This marks
+ us as an op_root for commit and allows reverting just this
+ operation */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb,
+ STMT_INSERT_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtnt",
+ src_wcroot->wc_id, dst_relpath,
+ dst_np_op_depth, dst_parent_relpath,
+ copyfrom_id, copyfrom_relpath,
+ copyfrom_rev,
+ presence_map,
+ svn_wc__db_status_not_present,
+ /* NULL */
+ kind_map, kind));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ /* Insert incomplete children, if relevant.
+ The children are part of the same op and so have the same op_depth.
+ (The only time we'd want a different depth is during a recursive
+ simple add, but we never insert children here during a simple add.) */
+ if (kind == svn_node_dir
+ && dst_presence == svn_wc__db_status_normal)
+ SVN_ERR(insert_incomplete_children(
+ dst_wcroot->sdb,
+ dst_wcroot->wc_id,
+ dst_relpath,
+ copyfrom_id,
+ copyfrom_relpath,
+ copyfrom_rev,
+ children,
+ dst_op_depth,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(cross_db_copy(src_wcroot, src_relpath, dst_wcroot,
+ dst_relpath, dst_presence, dst_op_depth,
+ dst_np_op_depth, kind,
+ children, copyfrom_id, copyfrom_relpath,
+ copyfrom_rev, scratch_pool));
+ }
+
+ SVN_ERR(add_work_items(dst_wcroot->sdb, work_items, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for passing args to op_copy_txn(). */
+struct op_copy_baton
+{
+ svn_wc__db_wcroot_t *src_wcroot;
+ const char *src_relpath;
+
+ svn_wc__db_wcroot_t *dst_wcroot;
+ const char *dst_relpath;
+
+ const svn_skel_t *work_items;
+
+ svn_boolean_t is_move;
+ const char *dst_op_root_relpath;
+};
+
+/* Helper for svn_wc__db_op_copy().
+ *
+ * Implements svn_sqlite__transaction_callback_t. */
+static svn_error_t *
+op_copy_txn(void * baton,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ struct op_copy_baton *ocb = baton;
+ int move_op_depth;
+
+ if (sdb != ocb->dst_wcroot->sdb)
+ {
+ /* Source and destination databases differ; so also start a lock
+ in the destination database, by calling ourself in a lock. */
+
+ return svn_error_trace(
+ svn_sqlite__with_lock(ocb->dst_wcroot->sdb,
+ op_copy_txn, ocb, scratch_pool));
+ }
+
+ /* From this point we can assume a lock in the src and dst databases */
+
+ if (ocb->is_move)
+ move_op_depth = relpath_depth(ocb->dst_op_root_relpath);
+ else
+ move_op_depth = 0;
+
+ SVN_ERR(db_op_copy(ocb->src_wcroot, ocb->src_relpath,
+ ocb->dst_wcroot, ocb->dst_relpath,
+ ocb->work_items, move_op_depth, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_copy(svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *dst_abspath,
+ const char *dst_op_root_abspath,
+ svn_boolean_t is_move,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ struct op_copy_baton ocb = {0};
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_op_root_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.src_wcroot,
+ &ocb.src_relpath, db,
+ src_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(ocb.src_wcroot);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.dst_wcroot,
+ &ocb.dst_relpath,
+ db, dst_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(ocb.dst_wcroot);
+
+ ocb.work_items = work_items;
+ ocb.is_move = is_move;
+ ocb.dst_op_root_relpath = svn_dirent_skip_ancestor(ocb.dst_wcroot->abspath,
+ dst_op_root_abspath);
+
+ /* Call with the sdb in src_wcroot. It might call itself again to
+ also obtain a lock in dst_wcroot */
+ SVN_ERR(svn_sqlite__with_lock(ocb.src_wcroot->sdb, op_copy_txn, &ocb,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* The txn body of svn_wc__db_op_handle_move_back */
+static svn_error_t *
+handle_move_back(svn_boolean_t *moved_back,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const char *moved_from_relpath,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_status_t status;
+ svn_boolean_t op_root;
+ svn_boolean_t have_more_work;
+ int from_op_depth = 0;
+ svn_boolean_t have_row;
+ svn_boolean_t different = FALSE;
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+
+ SVN_ERR(svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &op_root, NULL, NULL, NULL,
+ &have_more_work, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (status != svn_wc__db_status_added || !op_root)
+ return SVN_NO_ERROR;
+
+ /* We have two cases here: BASE-move-back and WORKING-move-back */
+ if (have_more_work)
+ SVN_ERR(op_depth_of(&from_op_depth, wcroot,
+ svn_relpath_dirname(local_relpath, scratch_pool)));
+ else
+ from_op_depth = 0;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_BACK));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id,
+ local_relpath,
+ from_op_depth,
+ relpath_depth(local_relpath)));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ SVN_ERR_ASSERT(have_row); /* We checked that the node is an op-root */
+
+ {
+ svn_boolean_t moved_here = svn_sqlite__column_boolean(stmt, 9);
+ const char *moved_to = svn_sqlite__column_text(stmt, 10, NULL);
+
+ if (!moved_here
+ || !moved_to
+ || strcmp(moved_to, moved_from_relpath))
+ {
+ different = TRUE;
+ have_row = FALSE;
+ }
+ }
+
+ while (have_row)
+ {
+ svn_wc__db_status_t upper_status;
+ svn_wc__db_status_t lower_status;
+
+ upper_status = svn_sqlite__column_token(stmt, 1, presence_map);
+
+ if (svn_sqlite__column_is_null(stmt, 5))
+ {
+ /* No lower layer replaced. */
+ if (upper_status != svn_wc__db_status_not_present)
+ {
+ different = TRUE;
+ break;
+ }
+ continue;
+ }
+
+ lower_status = svn_sqlite__column_token(stmt, 5, presence_map);
+
+ if (upper_status != lower_status)
+ {
+ different = TRUE;
+ break;
+ }
+
+ if (upper_status == svn_wc__db_status_not_present
+ || upper_status == svn_wc__db_status_excluded)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ continue; /* Nothing to check */
+ }
+ else if (upper_status != svn_wc__db_status_normal)
+ {
+ /* Not a normal move. Mixed revision move? */
+ different = TRUE;
+ break;
+ }
+
+ {
+ const char *upper_repos_relpath;
+ const char *lower_repos_relpath;
+
+ upper_repos_relpath = svn_sqlite__column_text(stmt, 3, NULL);
+ lower_repos_relpath = svn_sqlite__column_text(stmt, 7, NULL);
+
+ if (! upper_repos_relpath
+ || strcmp(upper_repos_relpath, lower_repos_relpath))
+ {
+ different = TRUE;
+ break;
+ }
+ }
+
+ {
+ svn_revnum_t upper_rev;
+ svn_revnum_t lower_rev;
+
+ upper_rev = svn_sqlite__column_revnum(stmt, 4);
+ lower_rev = svn_sqlite__column_revnum(stmt, 8);
+
+ if (upper_rev != lower_rev)
+ {
+ different = TRUE;
+ break;
+ }
+ }
+
+ {
+ apr_int64_t upper_repos_id;
+ apr_int64_t lower_repos_id;
+
+ upper_repos_id = svn_sqlite__column_int64(stmt, 2);
+ lower_repos_id = svn_sqlite__column_int64(stmt, 6);
+
+ if (upper_repos_id != lower_repos_id)
+ {
+ different = TRUE;
+ break;
+ }
+ }
+
+ /* Check moved_here? */
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (! different)
+ {
+ /* Ok, we can now safely remove this complete move, because we
+ determined that it 100% matches the layer below it. */
+
+ /* ### We could copy the recorded timestamps from the higher to the
+ lower layer in an attempt to improve status performance, but
+ generally these values should be the same anyway as it was
+ a no-op move. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_MOVED_BACK));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath,
+ relpath_depth(local_relpath)));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (moved_back)
+ *moved_back = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_handle_move_back(svn_boolean_t *moved_back,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *moved_from_abspath,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *moved_from_relpath;
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (moved_back)
+ *moved_back = FALSE;
+
+ moved_from_relpath = svn_dirent_skip_ancestor(wcroot->abspath,
+ moved_from_abspath);
+
+ if (! local_relpath[0]
+ || !moved_from_relpath)
+ {
+ /* WC-Roots can't be moved */
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ SVN_WC__DB_WITH_TXN(handle_move_back(moved_back, wcroot, local_relpath,
+ moved_from_relpath, work_items,
+ scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The recursive implementation of svn_wc__db_op_copy_shadowed_layer.
+ *
+ * A non-zero MOVE_OP_DEPTH implies that the copy operation is part of
+ * a move, and indicates the op-depth of the move destination op-root. */
+static svn_error_t *
+db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot,
+ const char *src_relpath,
+ int src_op_depth,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const char *dst_relpath,
+ int dst_op_depth,
+ int del_op_depth,
+ apr_int64_t repos_id,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ int move_op_depth,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_revnum_t node_revision;
+ const char *node_repos_relpath;
+ apr_int64_t node_repos_id;
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_status_t dst_presence;
+ int i;
+
+ {
+ svn_error_t *err;
+ err = svn_wc__db_depth_get_info(&status, &kind, &node_revision,
+ &node_repos_relpath, &node_repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ src_wcroot, src_relpath, src_op_depth,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ return SVN_NO_ERROR; /* There is no shadowed node at src_op_depth */
+ }
+ }
+
+ if (src_op_depth == 0)
+ {
+ /* If the node is switched or has a different revision then its parent
+ we shouldn't copy it. (We can't as we would have to insert it at
+ an unshadowed depth) */
+ if (status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_server_excluded
+ || node_revision != revision
+ || node_repos_id != repos_id
+ || strcmp(node_repos_relpath, repos_relpath))
+ {
+ /* Add a not-present node in the destination wcroot */
+ struct insert_working_baton_t iwb;
+ const char *repos_root_url;
+ const char *repos_uuid;
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid,
+ src_wcroot->sdb, node_repos_id,
+ scratch_pool));
+
+ SVN_ERR(create_repos_id(&node_repos_id, repos_root_url, repos_uuid,
+ dst_wcroot->sdb, scratch_pool));
+
+ blank_iwb(&iwb);
+
+ iwb.op_depth = dst_op_depth;
+ if (status != svn_wc__db_status_excluded)
+ iwb.presence = svn_wc__db_status_not_present;
+ else
+ iwb.presence = svn_wc__db_status_excluded;
+
+ iwb.kind = kind;
+
+ iwb.original_repos_id = node_repos_id;
+ iwb.original_revnum = node_revision;
+ iwb.original_repos_relpath = node_repos_relpath;
+
+ SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+ }
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ switch (status)
+ {
+ case svn_wc__db_status_normal:
+ case svn_wc__db_status_added:
+ case svn_wc__db_status_moved_here:
+ case svn_wc__db_status_copied:
+ dst_presence = svn_wc__db_status_normal;
+ break;
+ case svn_wc__db_status_deleted:
+ case svn_wc__db_status_not_present:
+ dst_presence = svn_wc__db_status_not_present;
+ break;
+ case svn_wc__db_status_excluded:
+ dst_presence = svn_wc__db_status_excluded;
+ break;
+ case svn_wc__db_status_server_excluded:
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot copy '%s' excluded by server"),
+ path_for_error_message(src_wcroot,
+ src_relpath,
+ scratch_pool));
+ default:
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot handle status of '%s'"),
+ path_for_error_message(src_wcroot,
+ src_relpath,
+ scratch_pool));
+ }
+
+ if (dst_presence == svn_wc__db_status_normal
+ && src_wcroot == dst_wcroot) /* ### Remove limitation */
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb,
+ STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "issdstd",
+ src_wcroot->wc_id, src_relpath,
+ dst_relpath,
+ dst_op_depth,
+ svn_relpath_dirname(dst_relpath, iterpool),
+ presence_map, dst_presence,
+ src_op_depth));
+
+ /* moved_here */
+ if (dst_op_depth == move_op_depth)
+ SVN_ERR(svn_sqlite__bind_int(stmt, 8, TRUE));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ {
+ /* And mark it deleted to allow proper shadowing */
+ struct insert_working_baton_t iwb;
+
+ blank_iwb(&iwb);
+
+ iwb.op_depth = del_op_depth;
+ iwb.presence = svn_wc__db_status_base_deleted;
+
+ iwb.kind = kind;
+
+ SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath,
+ scratch_pool));
+ }
+ }
+ else
+ {
+ struct insert_working_baton_t iwb;
+ if (dst_presence == svn_wc__db_status_normal) /* Fallback for multi-db */
+ dst_presence = svn_wc__db_status_not_present;
+
+ /* And mark it deleted to allow proper shadowing */
+
+ blank_iwb(&iwb);
+
+ iwb.op_depth = dst_op_depth;
+ iwb.presence = dst_presence;
+ iwb.kind = kind;
+
+ SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath,
+ scratch_pool));
+ }
+
+ SVN_ERR(gather_repo_children(&children, src_wcroot, src_relpath,
+ src_op_depth, scratch_pool, iterpool));
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+ const char *child_src_relpath;
+ const char *child_dst_relpath;
+ const char *child_repos_relpath = NULL;
+
+ svn_pool_clear(iterpool);
+ child_src_relpath = svn_relpath_join(src_relpath, name, iterpool);
+ child_dst_relpath = svn_relpath_join(dst_relpath, name, iterpool);
+
+ if (repos_relpath)
+ child_repos_relpath = svn_relpath_join(repos_relpath, name, iterpool);
+
+ SVN_ERR(db_op_copy_shadowed_layer(
+ src_wcroot, child_src_relpath, src_op_depth,
+ dst_wcroot, child_dst_relpath, dst_op_depth,
+ del_op_depth,
+ repos_id, child_repos_relpath, revision,
+ move_op_depth, scratch_pool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_wc__db_op_copy_shadowed_layer().
+ *
+ * Implements svn_sqlite__transaction_callback_t. */
+static svn_error_t *
+op_copy_shadowed_layer_txn(void *baton,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ struct op_copy_baton *ocb = baton;
+ const char *src_parent_relpath;
+ const char *dst_parent_relpath;
+ int src_op_depth;
+ int dst_op_depth;
+ int del_op_depth;
+ const char *repos_relpath = NULL;
+ apr_int64_t repos_id = INVALID_REPOS_ID;
+ svn_revnum_t revision = SVN_INVALID_REVNUM;
+
+ if (sdb != ocb->dst_wcroot->sdb)
+ {
+ /* Source and destination databases differ; so also start a lock
+ in the destination database, by calling ourself in a lock. */
+
+ return svn_error_trace(
+ svn_sqlite__with_lock(ocb->dst_wcroot->sdb,
+ op_copy_shadowed_layer_txn,
+ ocb, scratch_pool));
+ }
+
+ /* From this point we can assume a lock in the src and dst databases */
+
+
+ /* src_relpath and dst_relpath can't be wcroot as we need their parents */
+ SVN_ERR_ASSERT(*ocb->src_relpath && *ocb->dst_relpath);
+
+ src_parent_relpath = svn_relpath_dirname(ocb->src_relpath, scratch_pool);
+ dst_parent_relpath = svn_relpath_dirname(ocb->dst_relpath, scratch_pool);
+
+ /* src_parent must be status normal or added; get its op-depth */
+ SVN_ERR(op_depth_of(&src_op_depth, ocb->src_wcroot, src_parent_relpath));
+
+ /* dst_parent must be status added; get its op-depth */
+ SVN_ERR(op_depth_of(&dst_op_depth, ocb->dst_wcroot, dst_parent_relpath));
+
+ del_op_depth = relpath_depth(ocb->dst_relpath);
+
+ /* Get some information from the parent */
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, NULL, &revision, &repos_relpath,
+ &repos_id, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ ocb->src_wcroot,
+ src_parent_relpath, src_op_depth,
+ scratch_pool, scratch_pool));
+
+ if (repos_relpath == NULL)
+ {
+ /* The node is a local addition and has no shadowed information */
+ return SVN_NO_ERROR;
+ }
+
+ /* And calculate the child repos relpath */
+ repos_relpath = svn_relpath_join(repos_relpath,
+ svn_relpath_basename(ocb->src_relpath,
+ NULL),
+ scratch_pool);
+
+ SVN_ERR(db_op_copy_shadowed_layer(
+ ocb->src_wcroot, ocb->src_relpath, src_op_depth,
+ ocb->dst_wcroot, ocb->dst_relpath, dst_op_depth,
+ del_op_depth,
+ repos_id, repos_relpath, revision,
+ (ocb->is_move ? dst_op_depth : 0),
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_copy_shadowed_layer(svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t is_move,
+ apr_pool_t *scratch_pool)
+{
+ struct op_copy_baton ocb = {0};
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.src_wcroot,
+ &ocb.src_relpath, db,
+ src_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(ocb.src_wcroot);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.dst_wcroot,
+ &ocb.dst_relpath,
+ db, dst_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(ocb.dst_wcroot);
+
+ ocb.is_move = is_move;
+ ocb.dst_op_root_relpath = NULL; /* not used by op_copy_shadowed_layer_txn */
+
+ ocb.work_items = NULL;
+
+ /* Call with the sdb in src_wcroot. It might call itself again to
+ also obtain a lock in dst_wcroot */
+ SVN_ERR(svn_sqlite__with_lock(ocb.src_wcroot->sdb,
+ op_copy_shadowed_layer_txn,
+ &ocb, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* If there are any server-excluded base nodes then the copy must fail
+ as it's not possible to commit such a copy.
+ Return an error if there are any server-excluded nodes. */
+static svn_error_t *
+catch_copy_of_server_excluded(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *server_excluded_relpath;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_HAS_SERVER_EXCLUDED_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ server_excluded_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_AUTHZ_UNREADABLE, NULL,
+ _("Cannot copy '%s' excluded by server"),
+ path_for_error_message(wcroot,
+ server_excluded_relpath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_copy_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const apr_array_header_t *children,
+ svn_boolean_t is_move,
+ svn_depth_t depth,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_working_baton_t iwb;
+ int parent_op_depth;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(props != NULL);
+ /* ### any assertions for CHANGED_* ? */
+ /* ### any assertions for ORIGINAL_* ? */
+#if 0
+ SVN_ERR_ASSERT(children != NULL);
+#endif
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_dir;
+
+ iwb.props = props;
+ iwb.changed_rev = changed_rev;
+ iwb.changed_date = changed_date;
+ iwb.changed_author = changed_author;
+
+ if (original_root_url != NULL)
+ {
+ SVN_ERR(create_repos_id(&iwb.original_repos_id,
+ original_root_url, original_uuid,
+ wcroot->sdb, scratch_pool));
+ iwb.original_repos_relpath = original_repos_relpath;
+ iwb.original_revnum = original_revision;
+ }
+
+ /* ### Should we do this inside the transaction? */
+ SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth,
+ &parent_op_depth, iwb.original_repos_id,
+ original_repos_relpath, original_revision,
+ wcroot, local_relpath, scratch_pool));
+
+ iwb.children = children;
+ iwb.depth = depth;
+ iwb.moved_here = is_move && (parent_op_depth == 0 ||
+ iwb.op_depth == parent_op_depth);
+
+ iwb.work_items = work_items;
+ iwb.conflict = conflict;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_copy_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const svn_checksum_t *checksum,
+ svn_boolean_t update_actual_props,
+ const apr_hash_t *new_actual_props,
+ svn_boolean_t is_move,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_working_baton_t iwb;
+ int parent_op_depth;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(props != NULL);
+ /* ### any assertions for CHANGED_* ? */
+ SVN_ERR_ASSERT((! original_repos_relpath && ! original_root_url
+ && ! original_uuid && ! checksum
+ && original_revision == SVN_INVALID_REVNUM)
+ || (original_repos_relpath && original_root_url
+ && original_uuid && checksum
+ && original_revision != SVN_INVALID_REVNUM));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_file;
+
+ iwb.props = props;
+ iwb.changed_rev = changed_rev;
+ iwb.changed_date = changed_date;
+ iwb.changed_author = changed_author;
+
+ if (original_root_url != NULL)
+ {
+ SVN_ERR(create_repos_id(&iwb.original_repos_id,
+ original_root_url, original_uuid,
+ wcroot->sdb, scratch_pool));
+ iwb.original_repos_relpath = original_repos_relpath;
+ iwb.original_revnum = original_revision;
+ }
+
+ /* ### Should we do this inside the transaction? */
+ SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth,
+ &parent_op_depth, iwb.original_repos_id,
+ original_repos_relpath, original_revision,
+ wcroot, local_relpath, scratch_pool));
+
+ iwb.checksum = checksum;
+ iwb.moved_here = is_move && (parent_op_depth == 0 ||
+ iwb.op_depth == parent_op_depth);
+
+ if (update_actual_props)
+ {
+ iwb.update_actual_props = update_actual_props;
+ iwb.new_actual_props = new_actual_props;
+ }
+
+ iwb.work_items = work_items;
+ iwb.conflict = conflict;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_copy_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const char *target,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_working_baton_t iwb;
+ int parent_op_depth;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(props != NULL);
+ /* ### any assertions for CHANGED_* ? */
+ /* ### any assertions for ORIGINAL_* ? */
+ SVN_ERR_ASSERT(target != NULL);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_symlink;
+
+ iwb.props = props;
+ iwb.changed_rev = changed_rev;
+ iwb.changed_date = changed_date;
+ iwb.changed_author = changed_author;
+ iwb.moved_here = FALSE;
+
+ if (original_root_url != NULL)
+ {
+ SVN_ERR(create_repos_id(&iwb.original_repos_id,
+ original_root_url, original_uuid,
+ wcroot->sdb, scratch_pool));
+ iwb.original_repos_relpath = original_repos_relpath;
+ iwb.original_revnum = original_revision;
+ }
+
+ /* ### Should we do this inside the transaction? */
+ SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth,
+ &parent_op_depth, iwb.original_repos_id,
+ original_repos_relpath, original_revision,
+ wcroot, local_relpath, scratch_pool));
+
+ iwb.target = target;
+
+ iwb.work_items = work_items;
+ iwb.conflict = conflict;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_add_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *dir_abspath;
+ const char *name;
+ insert_working_baton_t iwb;
+
+ /* Resolve wcroot via parent directory to avoid obstruction handling */
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ local_relpath = svn_relpath_join(local_relpath, name, scratch_pool);
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_dir;
+ iwb.op_depth = relpath_depth(local_relpath);
+ if (props && apr_hash_count((apr_hash_t *)props))
+ {
+ iwb.update_actual_props = TRUE;
+ iwb.new_actual_props = props;
+ }
+
+ iwb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ /* Use depth infinity to make sure we have no invalid cached information
+ * about children of this dir. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_working_baton_t iwb;
+ const char *dir_abspath;
+ const char *name;
+
+ /* Resolve wcroot via parent directory to avoid obstruction handling */
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ local_relpath = svn_relpath_join(local_relpath, name, scratch_pool);
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_file;
+ iwb.op_depth = relpath_depth(local_relpath);
+ if (props && apr_hash_count((apr_hash_t *)props))
+ {
+ iwb.update_actual_props = TRUE;
+ iwb.new_actual_props = props;
+ }
+
+ iwb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *target,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ insert_working_baton_t iwb;
+ const char *dir_abspath;
+ const char *name;
+
+ /* Resolve wcroot via parent directory to avoid obstruction handling */
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(target != NULL);
+
+ svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ dir_abspath, scratch_pool, scratch_pool));
+
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ blank_iwb(&iwb);
+
+ local_relpath = svn_relpath_join(local_relpath, name, scratch_pool);
+ iwb.presence = svn_wc__db_status_normal;
+ iwb.kind = svn_node_symlink;
+ iwb.op_depth = relpath_depth(local_relpath);
+ if (props && apr_hash_count((apr_hash_t *)props))
+ {
+ iwb.update_actual_props = TRUE;
+ iwb.new_actual_props = props;
+ }
+
+ iwb.target = target;
+
+ iwb.work_items = work_items;
+
+ SVN_WC__DB_WITH_TXN(
+ insert_working_node(&iwb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Record RECORDED_SIZE and RECORDED_TIME into top layer in NODES */
+static svn_error_t *
+db_record_fileinfo(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_int64_t recorded_size,
+ apr_int64_t recorded_time,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_NODE_FILEINFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isii", wcroot->wc_id, local_relpath,
+ recorded_size, recorded_time));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ SVN_ERR_ASSERT(affected_rows == 1);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_global_record_fileinfo(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_filesize_t recorded_size,
+ apr_time_t recorded_time,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(db_record_fileinfo(wcroot, local_relpath,
+ recorded_size, recorded_time, scratch_pool));
+
+ /* We *totally* monkeyed the entries. Toss 'em. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set the ACTUAL_NODE properties column for (WC_ID, LOCAL_RELPATH) to
+ * PROPS.
+ *
+ * Note: PROPS=NULL means the actual props are the same as the pristine
+ * props; to indicate no properties when the pristine has some props,
+ * PROPS must be an empty hash. */
+static svn_error_t *
+set_actual_props(apr_int64_t wc_id,
+ const char *local_relpath,
+ apr_hash_t *props,
+ svn_sqlite__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_UPDATE_ACTUAL_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows == 1 || !props)
+ return SVN_NO_ERROR; /* We are done */
+
+ /* We have to insert a row in ACTUAL */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_ACTUAL_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+ if (*local_relpath != '\0')
+ SVN_ERR(svn_sqlite__bind_text(stmt, 3,
+ svn_relpath_dirname(local_relpath,
+ scratch_pool)));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 4, props, scratch_pool));
+ return svn_error_trace(svn_sqlite__step_done(stmt));
+}
+
+
+/* The body of svn_wc__db_op_set_props().
+
+ Set the 'properties' column in the 'ACTUAL_NODE' table to BATON->props.
+ Create an entry in the ACTUAL table for the node if it does not yet
+ have one.
+ To specify no properties, BATON->props must be an empty hash, not NULL.
+ BATON is of type 'struct set_props_baton_t'.
+*/
+static svn_error_t *
+set_props_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_hash_t *props,
+ svn_boolean_t clear_recorded_info,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *pristine_props;
+
+ /* Check if the props are modified. If no changes, then wipe out the
+ ACTUAL props. PRISTINE_PROPS==NULL means that any
+ ACTUAL props are okay as provided, so go ahead and set them. */
+ SVN_ERR(db_read_pristine_props(&pristine_props, wcroot, local_relpath, FALSE,
+ scratch_pool, scratch_pool));
+ if (props && pristine_props)
+ {
+ apr_array_header_t *prop_diffs;
+
+ SVN_ERR(svn_prop_diffs(&prop_diffs, props, pristine_props,
+ scratch_pool));
+ if (prop_diffs->nelts == 0)
+ props = NULL;
+ }
+
+ SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath,
+ props, wcroot->sdb, scratch_pool));
+
+ if (clear_recorded_info)
+ {
+ SVN_ERR(db_record_fileinfo(wcroot, local_relpath,
+ SVN_INVALID_FILESIZE, 0,
+ scratch_pool));
+ }
+
+ /* And finally. */
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+ if (conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_set_props(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_hash_t *props,
+ svn_boolean_t clear_recorded_info,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(set_props_txn(wcroot, local_relpath, props,
+ clear_recorded_info, conflict, work_items,
+ scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_modified(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ NOT_IMPLEMENTED();
+}
+
+/* */
+static svn_error_t *
+populate_targets_tree(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_depth_t depth,
+ const apr_array_header_t *changelist_filter,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows = 0;
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_TARGETS_LIST));
+
+ if (changelist_filter && changelist_filter->nelts > 0)
+ {
+ /* Iterate over the changelists, adding the nodes which match.
+ Common case: we only have one changelist, so this only
+ happens once. */
+ int i;
+ int stmt_idx;
+
+ switch (depth)
+ {
+ case svn_depth_empty:
+ stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST;
+ break;
+
+ case svn_depth_files:
+ stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES;
+ break;
+
+ case svn_depth_immediates:
+ stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES;
+ break;
+
+ case svn_depth_infinity:
+ stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY;
+ break;
+
+ default:
+ /* We don't know how to handle unknown or exclude. */
+ SVN_ERR_MALFUNCTION();
+ break;
+ }
+
+ for (i = 0; i < changelist_filter->nelts; i++)
+ {
+ int sub_affected;
+ const char *changelist = APR_ARRAY_IDX(changelist_filter, i,
+ const char *);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_TARGET_WITH_CHANGELIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id,
+ local_relpath, changelist));
+ SVN_ERR(svn_sqlite__update(&sub_affected, stmt));
+
+ /* If the root is matched by the changelist, we don't have to match
+ the children. As that tells us the root is a file */
+ if (!sub_affected && depth > svn_depth_empty)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id,
+ local_relpath, changelist));
+ SVN_ERR(svn_sqlite__update(&sub_affected, stmt));
+ }
+
+ affected_rows += sub_affected;
+ }
+ }
+ else /* No changelist filtering */
+ {
+ int stmt_idx;
+ int sub_affected;
+
+ switch (depth)
+ {
+ case svn_depth_empty:
+ stmt_idx = STMT_INSERT_TARGET;
+ break;
+
+ case svn_depth_files:
+ stmt_idx = STMT_INSERT_TARGET_DEPTH_FILES;
+ break;
+
+ case svn_depth_immediates:
+ stmt_idx = STMT_INSERT_TARGET_DEPTH_IMMEDIATES;
+ break;
+
+ case svn_depth_infinity:
+ stmt_idx = STMT_INSERT_TARGET_DEPTH_INFINITY;
+ break;
+
+ default:
+ /* We don't know how to handle unknown or exclude. */
+ SVN_ERR_MALFUNCTION();
+ break;
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_TARGET));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&sub_affected, stmt));
+ affected_rows += sub_affected;
+
+ if (depth > svn_depth_empty)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&sub_affected, stmt));
+ affected_rows += sub_affected;
+ }
+ }
+
+ /* Does the target exist? */
+ if (affected_rows == 0)
+ {
+ svn_boolean_t exists;
+ SVN_ERR(does_node_exist(&exists, wcroot, local_relpath));
+
+ if (!exists)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+#if 0
+static svn_error_t *
+dump_targets(svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_TARGETS));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *target = svn_sqlite__column_text(stmt, 0, NULL);
+ SVN_DBG(("Target: '%s'\n", target));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+#endif
+
+
+struct set_changelist_baton_t
+{
+ const char *new_changelist;
+ const apr_array_header_t *changelist_filter;
+ svn_depth_t depth;
+};
+
+
+/* The main part of svn_wc__db_op_set_changelist().
+ *
+ * Implements svn_wc__db_txn_callback_t. */
+static svn_error_t *
+set_changelist_txn(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ struct set_changelist_baton_t *scb = baton;
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(populate_targets_tree(wcroot, local_relpath, scb->depth,
+ scb->changelist_filter, scratch_pool));
+
+ /* Ensure we have actual nodes for our targets. */
+ if (scb->new_changelist)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_ACTUAL_EMPTIES));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Now create our notification table. */
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_CHANGELIST_LIST));
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_CHANGELIST_TRIGGER));
+
+ /* Update our changelists. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_ACTUAL_CHANGELISTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath,
+ scb->new_changelist));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (scb->new_changelist)
+ {
+ /* We have to notify that we skipped directories, so do that now. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_MARK_SKIPPED_CHANGELIST_DIRS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath,
+ scb->new_changelist));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* We may have left empty ACTUAL nodes, so remove them. This is only a
+ potential problem if we removed changelists. */
+ if (!scb->new_changelist)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_EMPTIES));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Send notifications for svn_wc__db_op_set_changelist().
+ *
+ * Implements work_callback_t. */
+static svn_error_t *
+do_changelist_notify(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_CHANGELIST_LIST));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ /* ### wc_id is column 0. use it one day... */
+ const char *notify_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ svn_wc_notify_action_t action = svn_sqlite__column_int(stmt, 2);
+ svn_wc_notify_t *notify;
+ const char *notify_abspath;
+
+ svn_pool_clear(iterpool);
+
+ if (cancel_func)
+ {
+ svn_error_t *err = cancel_func(cancel_baton);
+
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+ }
+
+ notify_abspath = svn_dirent_join(wcroot->abspath, notify_relpath,
+ iterpool);
+ notify = svn_wc_create_notify(notify_abspath, action, iterpool);
+ notify->changelist_name = svn_sqlite__column_text(stmt, 3, NULL);
+ notify_func(notify_baton, notify, iterpool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+svn_error_t *
+svn_wc__db_op_set_changelist(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *new_changelist,
+ const apr_array_header_t *changelist_filter,
+ svn_depth_t depth,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ struct set_changelist_baton_t scb;
+
+ scb.new_changelist = new_changelist;
+ scb.changelist_filter = changelist_filter;
+ scb.depth = depth;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* Flush the entries before we do the work. Even if no work is performed,
+ the flush isn't a problem. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool));
+
+ /* Perform the set-changelist operation (transactionally), perform any
+ notifications necessary, and then clean out our temporary tables. */
+ return svn_error_trace(with_finalization(wcroot, local_relpath,
+ set_changelist_txn, &scb,
+ do_changelist_notify, NULL,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ STMT_FINALIZE_CHANGELIST,
+ scratch_pool));
+}
+
+/* Implementation of svn_wc__db_op_mark_conflict() */
+svn_error_t *
+svn_wc__db_mark_conflict_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t got_row;
+ svn_boolean_t is_complete;
+
+ SVN_ERR(svn_wc__conflict_skel_is_complete(&is_complete, conflict_skel));
+ SVN_ERR_ASSERT(is_complete);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (got_row)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_ACTUAL_CONFLICT));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_ACTUAL_CONFLICT));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ if (*local_relpath != '\0')
+ SVN_ERR(svn_sqlite__bind_text(stmt, 4,
+ svn_relpath_dirname(local_relpath,
+ scratch_pool)));
+ }
+
+ {
+ svn_stringbuf_t *sb = svn_skel__unparse(conflict_skel, scratch_pool);
+
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 3, sb->data, sb->len));
+ }
+
+ SVN_ERR(svn_sqlite__update(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_mark_conflict(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflict_skel,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict_skel, scratch_pool));
+
+ /* ### Should be handled in the same transaction as setting the conflict */
+ if (work_items)
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+
+}
+
+/* The body of svn_wc__db_op_mark_resolved().
+ */
+static svn_error_t *
+db_op_mark_resolved(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ svn_boolean_t resolved_text,
+ svn_boolean_t resolved_props,
+ svn_boolean_t resolved_tree,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int total_affected_rows = 0;
+ svn_boolean_t resolved_all;
+ apr_size_t conflict_len;
+ const void *conflict_data;
+ svn_skel_t *conflicts;
+
+ /* Check if we have a conflict in ACTUAL */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (! have_row)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (have_row)
+ return SVN_NO_ERROR;
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ conflict_data = svn_sqlite__column_blob(stmt, 2, &conflict_len,
+ scratch_pool);
+ conflicts = svn_skel__parse(conflict_data, conflict_len, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_wc__conflict_skel_resolve(&resolved_all, conflicts,
+ db, wcroot->abspath,
+ resolved_text,
+ resolved_props ? "" : NULL,
+ resolved_tree,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_ACTUAL_CONFLICT));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ if (! resolved_all)
+ {
+ svn_stringbuf_t *sb = svn_skel__unparse(conflicts, scratch_pool);
+
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 3, sb->data, sb->len));
+ }
+
+ SVN_ERR(svn_sqlite__update(&total_affected_rows, stmt));
+
+ /* Now, remove the actual node if it doesn't have any more useful
+ information. We only need to do this if we've remove data ourselves. */
+ if (total_affected_rows > 0)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_EMPTY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_mark_resolved(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t resolved_text,
+ svn_boolean_t resolved_props,
+ svn_boolean_t resolved_tree,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ db_op_mark_resolved(wcroot, local_relpath, db,
+ resolved_text, resolved_props, resolved_tree,
+ work_items, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Clear moved-to information at the delete-half of the move which
+ * moved LOCAL_RELPATH here. This transforms the move into a simple delete. */
+static svn_error_t *
+clear_moved_to(const char *local_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *moved_from_relpath;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_FROM_RELPATH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+
+ moved_from_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_MOVED_TO_RELPATH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ moved_from_relpath,
+ relpath_depth(moved_from_relpath)));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* One of the two alternative bodies of svn_wc__db_op_revert().
+ *
+ * Implements svn_wc__db_txn_callback_t. */
+static svn_error_t *
+op_revert_txn(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_t *db = baton;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int op_depth;
+ svn_boolean_t moved_here;
+ int affected_rows;
+ const char *moved_to;
+
+ /* ### Similar structure to op_revert_recursive_txn, should they be
+ combined? */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* There was no NODE row, so attempt to delete an ACTUAL_NODE row. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+ if (affected_rows)
+ {
+ /* Can't do non-recursive actual-only revert if actual-only
+ children exist. Raise an error to cancel the transaction. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_ACTUAL_HAS_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL,
+ _("Can't revert '%s' without"
+ " reverting children"),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ moved_here = svn_sqlite__column_boolean(stmt, 15);
+ moved_to = svn_sqlite__column_text(stmt, 17, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (moved_to)
+ {
+ SVN_ERR(svn_wc__db_resolve_break_moved_away_internal(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+ else
+ {
+ svn_skel_t *conflict;
+
+ SVN_ERR(svn_wc__db_read_conflict_internal(&conflict, wcroot,
+ local_relpath,
+ scratch_pool, scratch_pool));
+ if (conflict)
+ {
+ svn_wc_operation_t operation;
+ svn_boolean_t tree_conflicted;
+
+ SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL,
+ &tree_conflicted,
+ db, wcroot->abspath,
+ conflict,
+ scratch_pool, scratch_pool));
+ if (tree_conflicted
+ && (operation == svn_wc_operation_update
+ || operation == svn_wc_operation_switch))
+ {
+ svn_wc_conflict_reason_t reason;
+ svn_wc_conflict_action_t action;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action,
+ NULL,
+ db, wcroot->abspath,
+ conflict,
+ scratch_pool,
+ scratch_pool));
+
+ if (reason == svn_wc_conflict_reason_deleted)
+ SVN_ERR(svn_wc__db_resolve_delete_raise_moved_away(
+ db, svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool),
+ NULL, NULL /* ### How do we notify this? */,
+ scratch_pool));
+ }
+ }
+ }
+
+ if (op_depth > 0 && op_depth == relpath_depth(local_relpath))
+ {
+ /* Can't do non-recursive revert if children exist */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_GE_OP_DEPTH_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath, op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL,
+ _("Can't revert '%s' without"
+ " reverting children"),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+
+ /* Rewrite the op-depth of all deleted children making the
+ direct children into roots of deletes. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* ### This removes the lock, but what about the access baton? */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WC_LOCK_ORPHAN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* If this node was moved-here, clear moved-to at the move source. */
+ if (moved_here)
+ SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+ if (!affected_rows)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* One of the two alternative bodies of svn_wc__db_op_revert().
+ *
+ * Implements svn_wc__db_txn_callback_t. */
+static svn_error_t *
+op_revert_recursive_txn(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int op_depth;
+ int select_op_depth;
+ svn_boolean_t moved_here;
+ int affected_rows;
+ apr_pool_t *iterpool;
+
+ /* ### Similar structure to op_revert_txn, should they be
+ combined? */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows)
+ return SVN_NO_ERROR; /* actual-only revert */
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ moved_here = svn_sqlite__column_boolean(stmt, 15);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (op_depth > 0 && op_depth != relpath_depth(local_relpath))
+ return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL,
+ _("Can't revert '%s' without"
+ " reverting parent"),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+
+ /* Remove moved-here from move destinations outside the tree. */
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb, STMT_SELECT_MOVED_OUTSIDE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *move_src_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ svn_error_t *err;
+
+ err = svn_wc__db_resolve_break_moved_away_internal(wcroot,
+ move_src_relpath,
+ scratch_pool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Don't delete BASE nodes */
+ select_op_depth = op_depth ? op_depth : 1;
+
+ /* Reverting any non wc-root node */
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath, select_op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* ### This removes the locks, but what about the access batons? */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_HERE_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *moved_here_child_relpath;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ moved_here_child_relpath = svn_sqlite__column_text(stmt, 0, iterpool);
+ err = clear_moved_to(moved_here_child_relpath, wcroot, iterpool);
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ svn_pool_destroy(iterpool);
+
+ /* Clear potential moved-to pointing at the target node itself. */
+ if (op_depth > 0 && op_depth == relpath_depth(local_relpath)
+ && moved_here)
+ SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_revert(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ struct with_triggers_baton_t wtb = { STMT_CREATE_REVERT_LIST,
+ STMT_DROP_REVERT_LIST_TRIGGERS,
+ NULL, NULL};
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ switch (depth)
+ {
+ case svn_depth_empty:
+ wtb.cb_func = op_revert_txn;
+ wtb.cb_baton = db;
+ break;
+ case svn_depth_infinity:
+ wtb.cb_func = op_revert_recursive_txn;
+ break;
+ default:
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Unsupported depth for revert of '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(with_triggers(&wtb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_revert_list_read().
+ */
+static svn_error_t *
+revert_list_read(svn_boolean_t *reverted,
+ const apr_array_header_t **marker_paths,
+ svn_boolean_t *copied_here,
+ svn_node_kind_t *kind,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *reverted = FALSE;
+ *marker_paths = NULL;
+ *copied_here = FALSE;
+ *kind = svn_node_unknown;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_REVERT_LIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ svn_boolean_t is_actual = svn_sqlite__column_boolean(stmt, 0);
+ svn_boolean_t another_row = FALSE;
+
+ if (is_actual)
+ {
+ apr_size_t conflict_len;
+ const void *conflict_data;
+
+ conflict_data = svn_sqlite__column_blob(stmt, 5, &conflict_len,
+ scratch_pool);
+ if (conflict_data)
+ {
+ svn_skel_t *conflicts = svn_skel__parse(conflict_data,
+ conflict_len,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_read_markers(marker_paths,
+ db, wcroot->abspath,
+ conflicts,
+ result_pool,
+ scratch_pool));
+ }
+
+ if (!svn_sqlite__column_is_null(stmt, 1)) /* notify */
+ *reverted = TRUE;
+
+ SVN_ERR(svn_sqlite__step(&another_row, stmt));
+ }
+
+ if (!is_actual || another_row)
+ {
+ *reverted = TRUE;
+ if (!svn_sqlite__column_is_null(stmt, 4)) /* repos_id */
+ {
+ int op_depth = svn_sqlite__column_int(stmt, 3);
+ *copied_here = (op_depth == relpath_depth(local_relpath));
+ }
+ *kind = svn_sqlite__column_token(stmt, 2, kind_map);
+ }
+
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (have_row)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_REVERT_LIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_revert_list_read(svn_boolean_t *reverted,
+ const apr_array_header_t **marker_files,
+ svn_boolean_t *copied_here,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ revert_list_read(reverted, marker_files, copied_here, kind,
+ wcroot, local_relpath, db,
+ result_pool, scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_revert_list_read_copied_children().
+ */
+static svn_error_t *
+revert_list_read_copied_children(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const apr_array_header_t **children_p,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_array_header_t *children;
+
+ children =
+ apr_array_make(result_pool, 0,
+ sizeof(svn_wc__db_revert_list_copied_child_info_t *));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_REVERT_LIST_COPIED_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "sd",
+ local_relpath, relpath_depth(local_relpath)));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_wc__db_revert_list_copied_child_info_t *child_info;
+ const char *child_relpath;
+
+ child_info = apr_palloc(result_pool, sizeof(*child_info));
+
+ child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ child_info->abspath = svn_dirent_join(wcroot->abspath, child_relpath,
+ result_pool);
+ child_info->kind = svn_sqlite__column_token(stmt, 1, kind_map);
+ APR_ARRAY_PUSH(
+ children,
+ svn_wc__db_revert_list_copied_child_info_t *) = child_info;
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *children_p = children;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_revert_list_read_copied_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ revert_list_read_copied_children(wcroot, local_relpath, children,
+ result_pool, scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_revert_list_notify(svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, iterpool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_REVERT_LIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_trace(svn_sqlite__reset(stmt)); /* optimise for no row */
+ while (have_row)
+ {
+ const char *notify_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ svn_pool_clear(iterpool);
+
+ notify_func(notify_baton,
+ svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+ notify_relpath,
+ iterpool),
+ svn_wc_notify_revert,
+ iterpool),
+ iterpool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_REVERT_LIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_revert_list_done(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_DROP_REVERT_LIST));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_op_remove_node().
+ */
+static svn_error_t *
+remove_node_txn(svn_boolean_t *left_changes,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ svn_boolean_t destroy_wc,
+ svn_boolean_t destroy_changes,
+ svn_revnum_t not_present_rev,
+ svn_wc__db_status_t not_present_status,
+ svn_node_kind_t not_present_kind,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+
+ /* Note that unlike many similar functions it is a valid scenario for this
+ function to be called on a wcroot! */
+
+ /* db set when destroying wc */
+ SVN_ERR_ASSERT(!destroy_wc || db != NULL);
+
+ if (left_changes)
+ *left_changes = FALSE;
+
+ /* Need info for not_present node? */
+ if (SVN_IS_VALID_REVNUM(not_present_rev))
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (destroy_wc
+ && (!destroy_changes || *local_relpath == '\0'))
+ {
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+ svn_error_t *err = NULL;
+
+ /* Install WQ items for deleting the unmodified files and all dirs */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_PRESENT));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ while (have_row)
+ {
+ const char *child_relpath;
+ const char *child_abspath;
+ svn_node_kind_t child_kind;
+ svn_boolean_t have_checksum;
+ svn_filesize_t recorded_size;
+ apr_int64_t recorded_time;
+ const svn_io_dirent2_t *dirent;
+ svn_boolean_t modified_p = TRUE;
+ svn_skel_t *work_item = NULL;
+
+ svn_pool_clear(iterpool);
+
+ child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ child_kind = svn_sqlite__column_token(stmt, 1, kind_map);
+
+ child_abspath = svn_dirent_join(wcroot->abspath, child_relpath,
+ iterpool);
+
+ if (child_kind == svn_node_file)
+ {
+ have_checksum = !svn_sqlite__column_is_null(stmt, 2);
+ recorded_size = get_recorded_size(stmt, 3);
+ recorded_time = svn_sqlite__column_int64(stmt, 4);
+ }
+
+ if (cancel_func)
+ err = cancel_func(cancel_baton);
+
+ if (err)
+ break;
+
+ err = svn_io_stat_dirent2(&dirent, child_abspath, FALSE, TRUE,
+ iterpool, iterpool);
+
+ if (err)
+ break;
+
+ if (destroy_changes
+ || dirent->kind != svn_node_file
+ || child_kind != svn_node_file)
+ {
+ /* Not interested in keeping changes */
+ modified_p = FALSE;
+ }
+ else if (child_kind == svn_node_file
+ && dirent->kind == svn_node_file
+ && dirent->filesize == recorded_size
+ && dirent->mtime == recorded_time)
+ {
+ modified_p = FALSE; /* File matches recorded state */
+ }
+ else if (have_checksum)
+ err = svn_wc__internal_file_modified_p(&modified_p,
+ db, child_abspath,
+ FALSE, iterpool);
+
+ if (err)
+ break;
+
+ if (modified_p)
+ {
+ if (left_changes)
+ *left_changes = TRUE;
+ }
+ else if (child_kind == svn_node_dir)
+ {
+ err = svn_wc__wq_build_dir_remove(&work_item,
+ db, wcroot->abspath,
+ child_abspath, FALSE,
+ iterpool, iterpool);
+ }
+ else /* svn_node_file || svn_node_symlink */
+ {
+ err = svn_wc__wq_build_file_remove(&work_item,
+ db, wcroot->abspath,
+ child_abspath,
+ iterpool, iterpool);
+ }
+
+ if (err)
+ break;
+
+ if (work_item)
+ {
+ err = add_work_items(wcroot->sdb, work_item, iterpool);
+ if (err)
+ break;
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+ }
+
+ if (destroy_wc && *local_relpath != '\0')
+ {
+ /* Create work item for destroying the root */
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ SVN_ERR(read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_added
+ || status == svn_wc__db_status_incomplete)
+ {
+ svn_skel_t *work_item = NULL;
+ const char *local_abspath = svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool);
+
+ if (kind == svn_node_dir)
+ {
+ SVN_ERR(svn_wc__wq_build_dir_remove(&work_item,
+ db, wcroot->abspath,
+ local_abspath,
+ destroy_changes
+ /* recursive */,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ svn_boolean_t modified_p = FALSE;
+
+ if (!destroy_changes)
+ {
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified_p,
+ db, local_abspath,
+ FALSE,
+ scratch_pool));
+ }
+
+ if (!modified_p)
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item,
+ db, wcroot->abspath,
+ local_abspath,
+ scratch_pool,
+ scratch_pool));
+ else
+ {
+ if (left_changes)
+ *left_changes = TRUE;
+ }
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_item, scratch_pool));
+ }
+ }
+
+ /* Remove all nodes below local_relpath */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_NODE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* Delete the root NODE when this is not the working copy root */
+ if (local_relpath[0] != '\0')
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE_RECURSIVE));
+
+ /* Delete all actual nodes at or below local_relpath */
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* Should we leave a not-present node? */
+ if (SVN_IS_VALID_REVNUM(not_present_rev))
+ {
+ insert_base_baton_t ibb;
+ blank_ibb(&ibb);
+
+ ibb.repos_id = repos_id;
+
+ SVN_ERR_ASSERT(not_present_status == svn_wc__db_status_not_present
+ || not_present_status == svn_wc__db_status_excluded);
+
+ ibb.status = not_present_status;
+ ibb.kind = not_present_kind;
+
+ ibb.repos_relpath = repos_relpath;
+ ibb.revision = not_present_rev;
+
+ SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool));
+ }
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+ if (conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_remove_node(svn_boolean_t *left_changes,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t destroy_wc,
+ svn_boolean_t destroy_changes,
+ svn_revnum_t not_present_revision,
+ svn_wc__db_status_t not_present_status,
+ svn_node_kind_t not_present_kind,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(remove_node_txn(left_changes,
+ wcroot, local_relpath, db,
+ destroy_wc, destroy_changes,
+ not_present_revision, not_present_status,
+ not_present_kind, conflict, work_items,
+ cancel_func, cancel_baton, scratch_pool),
+ wcroot);
+
+ /* Flush everything below this node in all ways */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_op_set_base_depth().
+ */
+static svn_error_t *
+db_op_set_base_depth(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows;
+
+ /* Flush any entries before we start monkeying the database. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_NODE_BASE_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath,
+ svn_token__to_word(depth_map, depth)));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows == 0)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ "The node '%s' is not a committed directory",
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_set_base_depth(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(depth >= svn_depth_empty && depth <= svn_depth_infinity);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* ### We set depth on working and base to match entry behavior.
+ Maybe these should be separated later? */
+ SVN_WC__DB_WITH_TXN(db_op_set_base_depth(wcroot, local_relpath, depth,
+ scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+info_below_working(svn_boolean_t *have_base,
+ svn_boolean_t *have_work,
+ svn_wc__db_status_t *status,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int below_op_depth, /* < 0 is ignored */
+ apr_pool_t *scratch_pool);
+
+
+/* Convert STATUS, the raw status obtained from the presence map, to
+ the status appropriate for a working (op_depth > 0) node and return
+ it in *WORKING_STATUS. */
+static svn_error_t *
+convert_to_working_status(svn_wc__db_status_t *working_status,
+ svn_wc__db_status_t status)
+{
+ svn_wc__db_status_t work_status = status;
+
+ SVN_ERR_ASSERT(work_status == svn_wc__db_status_normal
+ || work_status == svn_wc__db_status_not_present
+ || work_status == svn_wc__db_status_base_deleted
+ || work_status == svn_wc__db_status_incomplete
+ || work_status == svn_wc__db_status_excluded);
+
+ if (work_status == svn_wc__db_status_excluded)
+ {
+ *working_status = svn_wc__db_status_excluded;
+ }
+ else if (work_status == svn_wc__db_status_not_present
+ || work_status == svn_wc__db_status_base_deleted)
+ {
+ /* The caller should scan upwards to detect whether this
+ deletion has occurred because this node has been moved
+ away, or it is a regular deletion. Also note that the
+ deletion could be of the BASE tree, or a child of
+ something that has been copied/moved here. */
+
+ *working_status = svn_wc__db_status_deleted;
+ }
+ else /* normal or incomplete */
+ {
+ /* The caller should scan upwards to detect whether this
+ addition has occurred because of a simple addition,
+ a copy, or is the destination of a move. */
+ *working_status = svn_wc__db_status_added;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return the status of the node, if any, below the "working" node (or
+ below BELOW_OP_DEPTH if >= 0).
+ Set *HAVE_BASE or *HAVE_WORK to indicate if a base node or lower
+ working node is present, and *STATUS to the status of the first
+ layer below the selected node. */
+static svn_error_t *
+info_below_working(svn_boolean_t *have_base,
+ svn_boolean_t *have_work,
+ svn_wc__db_status_t *status,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int below_op_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *have_base = *have_work = FALSE;
+ *status = svn_wc__db_status_normal;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (below_op_depth >= 0)
+ {
+ while (have_row &&
+ (svn_sqlite__column_int(stmt, 0) > below_op_depth))
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ }
+ if (have_row)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ *status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ while (have_row)
+ {
+ int op_depth = svn_sqlite__column_int(stmt, 0);
+
+ if (op_depth > 0)
+ *have_work = TRUE;
+ else
+ *have_base = TRUE;
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (*have_work)
+ SVN_ERR(convert_to_working_status(status, *status));
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper function for op_delete_txn */
+static svn_error_t *
+delete_update_movedto(svn_wc__db_wcroot_t *wcroot,
+ const char *child_moved_from_relpath,
+ int op_depth,
+ const char *new_moved_to_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_MOVED_TO_RELPATH));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isds",
+ wcroot->wc_id,
+ child_moved_from_relpath,
+ op_depth,
+ new_moved_to_relpath));
+ SVN_ERR(svn_sqlite__update(&affected, stmt));
+ assert(affected == 1);
+
+ return SVN_NO_ERROR;
+}
+
+
+struct op_delete_baton_t {
+ const char *moved_to_relpath; /* NULL if delete is not part of a move */
+ svn_skel_t *conflict;
+ svn_skel_t *work_items;
+ svn_boolean_t delete_dir_externals;
+ svn_boolean_t notify;
+};
+
+/* This structure is used while rewriting move information for nodes.
+ *
+ * The most simple case of rewriting move information happens when
+ * a moved-away subtree is moved again: mv A B; mv B C
+ * The second move requires rewriting moved-to info at or within A.
+ *
+ * Another example is a move of a subtree which had nodes moved into it:
+ * mv A B/F; mv B G
+ * This requires rewriting such that A/F is marked has having moved to G/F.
+ *
+ * Another case is where a node becomes a nested moved node.
+ * A nested move happens when a subtree child is moved before or after
+ * the subtree itself is moved. For example:
+ * mv A/F A/G; mv A B
+ * In this case, the move A/F -> A/G is rewritten to B/F -> B/G.
+ * Note that the following sequence results in the same DB state:
+ * mv A B; mv B/F B/G
+ * We do not care about the order the moves were performed in.
+ * For details, see http://wiki.apache.org/subversion/MultiLayerMoves
+ */
+struct moved_node_t {
+ /* The source of the move. */
+ const char *local_relpath;
+
+ /* The move destination. */
+ const char *moved_to_relpath;
+
+ /* The op-depth of the deleted node at the source of the move. */
+ int op_depth;
+};
+
+static svn_error_t *
+delete_node(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ struct op_delete_baton_t *b = baton;
+ svn_wc__db_status_t status;
+ svn_boolean_t have_row, op_root;
+ svn_boolean_t add_work = FALSE;
+ svn_sqlite__stmt_t *stmt;
+ int select_depth; /* Depth of what is to be deleted */
+ svn_boolean_t refetch_depth = FALSE;
+ svn_node_kind_t kind;
+ apr_array_header_t *moved_nodes = NULL;
+ int delete_depth = relpath_depth(local_relpath);
+
+ SVN_ERR(read_info(&status,
+ &kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &op_root, NULL, NULL,
+ NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_deleted
+ || status == svn_wc__db_status_not_present)
+ return SVN_NO_ERROR;
+
+ /* Don't copy BASE directories with server excluded nodes */
+ if (status == svn_wc__db_status_normal && kind == svn_node_dir)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_HAS_SERVER_EXCLUDED_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ const char *absent_path = svn_sqlite__column_text(stmt, 0,
+ scratch_pool);
+
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ svn_sqlite__reset(stmt),
+ _("Cannot delete '%s' as '%s' is excluded by server"),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool),
+ path_for_error_message(wcroot, absent_path,
+ scratch_pool));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+ else if (status == svn_wc__db_status_server_excluded)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot delete '%s' as it is excluded by server"),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+ else if (status == svn_wc__db_status_excluded)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Cannot delete '%s' as it is excluded"),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ if (b->moved_to_relpath)
+ {
+ const char *moved_from_relpath = NULL;
+ struct moved_node_t *moved_node;
+ int move_op_depth;
+
+ moved_nodes = apr_array_make(scratch_pool, 1,
+ sizeof(struct moved_node_t *));
+
+ /* The node is being moved-away.
+ * Figure out if the node was moved-here before, or whether this
+ * is the first time the node is moved. */
+ if (status == svn_wc__db_status_added)
+ SVN_ERR(scan_addition(&status, NULL, NULL, NULL, NULL, NULL, NULL,
+ &moved_from_relpath,
+ NULL,
+ &move_op_depth,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (op_root && moved_from_relpath)
+ {
+ const char *part = svn_relpath_skip_ancestor(local_relpath,
+ moved_from_relpath);
+
+ /* Existing move-root is moved to another location */
+ moved_node = apr_palloc(scratch_pool, sizeof(struct moved_node_t));
+ if (!part)
+ moved_node->local_relpath = moved_from_relpath;
+ else
+ moved_node->local_relpath = svn_relpath_join(b->moved_to_relpath,
+ part, scratch_pool);
+ moved_node->op_depth = move_op_depth;
+ moved_node->moved_to_relpath = b->moved_to_relpath;
+
+ APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node;
+ }
+ else if (!op_root && (status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_copied
+ || status == svn_wc__db_status_moved_here))
+ {
+ /* The node is becoming a move-root for the first time,
+ * possibly because of a nested move operation. */
+ moved_node = apr_palloc(scratch_pool, sizeof(struct moved_node_t));
+ moved_node->local_relpath = local_relpath;
+ moved_node->op_depth = delete_depth;
+ moved_node->moved_to_relpath = b->moved_to_relpath;
+
+ APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node;
+ }
+ /* Else: We can't track history of local additions and/or of things we are
+ about to delete. */
+
+ /* And update all moved_to values still pointing to this location */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_MOVED_TO_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id,
+ local_relpath,
+ b->moved_to_relpath));
+ SVN_ERR(svn_sqlite__update(NULL, stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_MOVED_TO_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__update(NULL, stmt));
+ }
+
+ /* Find children that were moved out of the subtree rooted at this node.
+ * We'll need to update their op-depth columns because their deletion
+ * is now implied by the deletion of their parent (i.e. this node). */
+ {
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_FOR_DELETE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ struct moved_node_t *mn;
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *mv_to_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ int child_op_depth = svn_sqlite__column_int(stmt, 2);
+ svn_boolean_t fixup = FALSE;
+
+ if (!b->moved_to_relpath
+ && ! svn_relpath_skip_ancestor(local_relpath, mv_to_relpath))
+ {
+ /* Update the op-depth of an moved node below this tree */
+ fixup = TRUE;
+ child_op_depth = delete_depth;
+ }
+ else if (b->moved_to_relpath
+ && delete_depth == child_op_depth)
+ {
+ /* Update the op-depth of a tree shadowed by this tree */
+ fixup = TRUE;
+ child_op_depth = delete_depth;
+ }
+ else if (b->moved_to_relpath
+ && child_op_depth >= delete_depth
+ && !svn_relpath_skip_ancestor(local_relpath, mv_to_relpath))
+ {
+ /* Update the move destination of something that is now moved
+ away further */
+
+ child_relpath = svn_relpath_skip_ancestor(local_relpath, child_relpath);
+
+ if (child_relpath)
+ {
+ child_relpath = svn_relpath_join(b->moved_to_relpath, child_relpath, scratch_pool);
+
+ if (child_op_depth > delete_depth
+ && svn_relpath_skip_ancestor(local_relpath, child_relpath))
+ child_op_depth = delete_depth;
+ else
+ child_op_depth = relpath_depth(child_relpath);
+
+ fixup = TRUE;
+ }
+ }
+
+ if (fixup)
+ {
+ mn = apr_pcalloc(scratch_pool, sizeof(struct moved_node_t));
+
+ mn->local_relpath = apr_pstrdup(scratch_pool, child_relpath);
+ mn->moved_to_relpath = apr_pstrdup(scratch_pool, mv_to_relpath);
+ mn->op_depth = child_op_depth;
+
+ if (!moved_nodes)
+ moved_nodes = apr_array_make(scratch_pool, 1,
+ sizeof(struct moved_node_t *));
+ APR_ARRAY_PUSH(moved_nodes, struct moved_node_t *) = mn;
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ if (op_root)
+ {
+ svn_boolean_t below_base;
+ svn_boolean_t below_work;
+ svn_wc__db_status_t below_status;
+
+ /* Use STMT_SELECT_NODE_INFO directly instead of read_info plus
+ info_below_working */
+ SVN_ERR(info_below_working(&below_base, &below_work, &below_status,
+ wcroot, local_relpath, -1, scratch_pool));
+ if ((below_base || below_work)
+ && below_status != svn_wc__db_status_not_present
+ && below_status != svn_wc__db_status_deleted)
+ {
+ add_work = TRUE;
+ refetch_depth = TRUE;
+ }
+
+ select_depth = relpath_depth(local_relpath);
+ }
+ else
+ {
+ add_work = TRUE;
+ if (status != svn_wc__db_status_normal)
+ SVN_ERR(op_depth_of(&select_depth, wcroot, local_relpath));
+ else
+ select_depth = 0; /* Deleting BASE node */
+ }
+
+ /* ### Put actual-only nodes into the list? */
+ if (b->notify)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_DELETE_LIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wcroot->wc_id, local_relpath, select_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wcroot->wc_id, local_relpath, delete_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (refetch_depth)
+ SVN_ERR(op_depth_of(&select_depth, wcroot, local_relpath));
+
+ /* Delete ACTUAL_NODE rows, but leave those that have changelist
+ and a NODES row. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (add_work)
+ {
+ /* Delete the node at LOCAL_RELPATH, and possibly mark it as moved. */
+
+ /* Delete the node and possible descendants. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_DELETE_FROM_NODE_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd",
+ wcroot->wc_id, local_relpath,
+ select_depth, delete_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ if (moved_nodes)
+ {
+ int i;
+
+ for (i = 0; i < moved_nodes->nelts; ++i)
+ {
+ const struct moved_node_t *moved_node
+ = APR_ARRAY_IDX(moved_nodes, i, void *);
+
+ SVN_ERR(delete_update_movedto(wcroot,
+ moved_node->local_relpath,
+ moved_node->op_depth,
+ moved_node->moved_to_relpath,
+ scratch_pool));
+ }
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_FILE_EXTERNALS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ b->delete_dir_externals
+ ? STMT_DELETE_EXTERNAL_REGISTATIONS
+ : STMT_DELETE_FILE_EXTERNAL_REGISTATIONS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ SVN_ERR(add_work_items(wcroot->sdb, b->work_items, scratch_pool));
+ if (b->conflict)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ b->conflict, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+op_delete_txn(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_DELETE_LIST));
+ SVN_ERR(delete_node(baton, wcroot, local_relpath, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+struct op_delete_many_baton_t {
+ apr_array_header_t *rel_targets;
+ svn_boolean_t delete_dir_externals;
+ const svn_skel_t *work_items;
+} op_delete_many_baton_t;
+
+static svn_error_t *
+op_delete_many_txn(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ struct op_delete_many_baton_t *odmb = baton;
+ struct op_delete_baton_t odb;
+ int i;
+ apr_pool_t *iterpool;
+
+ odb.moved_to_relpath = NULL;
+ odb.conflict = NULL;
+ odb.work_items = NULL;
+ odb.delete_dir_externals = odmb->delete_dir_externals;
+ odb.notify = TRUE;
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_DELETE_LIST));
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < odmb->rel_targets->nelts; i++)
+ {
+ const char *target_relpath = APR_ARRAY_IDX(odmb->rel_targets, i,
+ const char *);
+
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(delete_node(&odb, wcroot, target_relpath, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(add_work_items(wcroot->sdb, odmb->work_items, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+do_delete_notify(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_DELETE_LIST));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *notify_relpath;
+ const char *notify_abspath;
+
+ svn_pool_clear(iterpool);
+
+ notify_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ notify_abspath = svn_dirent_join(wcroot->abspath,
+ notify_relpath,
+ iterpool);
+
+ notify_func(notify_baton,
+ svn_wc_create_notify(notify_abspath,
+ svn_wc_notify_delete,
+ iterpool),
+ iterpool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* We only allow cancellation after notification for all deleted nodes
+ * has happened. The nodes are already deleted so we should notify for
+ * all of them. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_delete(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *moved_to_abspath,
+ svn_boolean_t delete_dir_externals,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ svn_wc__db_wcroot_t *moved_to_wcroot;
+ const char *local_relpath;
+ const char *moved_to_relpath;
+ struct op_delete_baton_t odb;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (moved_to_abspath)
+ {
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&moved_to_wcroot,
+ &moved_to_relpath,
+ db, moved_to_abspath,
+ scratch_pool,
+ scratch_pool));
+ VERIFY_USABLE_WCROOT(moved_to_wcroot);
+
+ if (strcmp(wcroot->abspath, moved_to_wcroot->abspath) != 0)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot move '%s' to '%s' because they "
+ "are not in the same working copy"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool),
+ svn_dirent_local_style(moved_to_abspath,
+ scratch_pool));
+ }
+ else
+ moved_to_relpath = NULL;
+
+ odb.moved_to_relpath = moved_to_relpath;
+ odb.conflict = conflict;
+ odb.work_items = work_items;
+ odb.delete_dir_externals = delete_dir_externals;
+
+ if (notify_func)
+ {
+ /* Perform the deletion operation (transactionally), perform any
+ notifications necessary, and then clean out our temporary tables. */
+ odb.notify = TRUE;
+ SVN_ERR(with_finalization(wcroot, local_relpath,
+ op_delete_txn, &odb,
+ do_delete_notify, NULL,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ STMT_FINALIZE_DELETE,
+ scratch_pool));
+ }
+ else
+ {
+ /* Avoid the trigger work */
+ odb.notify = FALSE;
+ SVN_WC__DB_WITH_TXN(
+ delete_node(&odb, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ }
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_delete_many(svn_wc__db_t *db,
+ apr_array_header_t *targets,
+ svn_boolean_t delete_dir_externals,
+ const svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ struct op_delete_many_baton_t odmb;
+ int i;
+ apr_pool_t *iterpool;
+
+ odmb.rel_targets = apr_array_make(scratch_pool, targets->nelts,
+ sizeof(const char *));
+ odmb.work_items = work_items;
+ odmb.delete_dir_externals = delete_dir_externals;
+ iterpool = svn_pool_create(scratch_pool);
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db,
+ APR_ARRAY_IDX(targets, 0,
+ const char *),
+ scratch_pool, iterpool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *local_abspath = APR_ARRAY_IDX(targets, i, const char*);
+ svn_wc__db_wcroot_t *target_wcroot;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&target_wcroot,
+ &local_relpath, db,
+ APR_ARRAY_IDX(targets, i,
+ const char *),
+ scratch_pool, iterpool));
+ VERIFY_USABLE_WCROOT(target_wcroot);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* Assert that all targets are within the same working copy. */
+ SVN_ERR_ASSERT(wcroot->wc_id == target_wcroot->wc_id);
+
+ APR_ARRAY_PUSH(odmb.rel_targets, const char *) = local_relpath;
+ SVN_ERR(flush_entries(target_wcroot, local_abspath, svn_depth_infinity,
+ iterpool));
+
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Perform the deletion operation (transactionally), perform any
+ notifications necessary, and then clean out our temporary tables. */
+ return svn_error_trace(with_finalization(wcroot, wcroot->abspath,
+ op_delete_many_txn, &odmb,
+ do_delete_notify, NULL,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ STMT_FINALIZE_DELETE,
+ scratch_pool));
+}
+
+
+/* Like svn_wc__db_read_info(), but taking WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH, and outputting repos ids instead of URL+UUID. */
+static svn_error_t *
+read_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ svn_wc__db_lock_t **lock,
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_time,
+ const char **changelist,
+ svn_boolean_t *conflicted,
+ svn_boolean_t *op_root,
+ svn_boolean_t *had_props,
+ svn_boolean_t *props_mod,
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt_info;
+ svn_sqlite__stmt_t *stmt_act;
+ svn_boolean_t have_info;
+ svn_boolean_t have_act;
+ svn_error_t *err = NULL;
+
+ /* Obtain the most likely to exist record first, to make sure we don't
+ have to obtain the SQLite read-lock multiple times */
+ SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb,
+ lock ? STMT_SELECT_NODE_INFO_WITH_LOCK
+ : STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_info, stmt_info));
+
+ if (changelist || conflicted || props_mod)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt_act, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt_act, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_act, stmt_act));
+ }
+ else
+ {
+ have_act = FALSE;
+ stmt_act = NULL;
+ }
+
+ if (have_info)
+ {
+ int op_depth;
+ svn_node_kind_t node_kind;
+
+ op_depth = svn_sqlite__column_int(stmt_info, 0);
+ node_kind = svn_sqlite__column_token(stmt_info, 4, kind_map);
+
+ if (status)
+ {
+ *status = svn_sqlite__column_token(stmt_info, 3, presence_map);
+
+ if (op_depth != 0) /* WORKING */
+ err = svn_error_compose_create(err,
+ convert_to_working_status(status,
+ *status));
+ }
+ if (kind)
+ {
+ *kind = node_kind;
+ }
+ if (op_depth != 0)
+ {
+ if (repos_id)
+ *repos_id = INVALID_REPOS_ID;
+ if (revision)
+ *revision = SVN_INVALID_REVNUM;
+ if (repos_relpath)
+ /* Our path is implied by our parent somewhere up the tree.
+ With the NULL value and status, the caller will know to
+ search up the tree for the base of our path. */
+ *repos_relpath = NULL;
+ }
+ else
+ {
+ /* Fetch repository information. If we have a
+ WORKING_NODE (and have been added), then the repository
+ we're being added to will be dependent upon a parent. The
+ caller can scan upwards to locate the repository. */
+ repos_location_from_columns(repos_id, revision, repos_relpath,
+ stmt_info, 1, 5, 2, result_pool);
+ }
+ if (changed_rev)
+ {
+ *changed_rev = svn_sqlite__column_revnum(stmt_info, 8);
+ }
+ if (changed_date)
+ {
+ *changed_date = svn_sqlite__column_int64(stmt_info, 9);
+ }
+ if (changed_author)
+ {
+ *changed_author = svn_sqlite__column_text(stmt_info, 10,
+ result_pool);
+ }
+ if (recorded_time)
+ {
+ *recorded_time = svn_sqlite__column_int64(stmt_info, 13);
+ }
+ if (depth)
+ {
+ if (node_kind != svn_node_dir)
+ {
+ *depth = svn_depth_unknown;
+ }
+ else
+ {
+ *depth = svn_sqlite__column_token_null(stmt_info, 11, depth_map,
+ svn_depth_unknown);
+ }
+ }
+ if (checksum)
+ {
+ if (node_kind != svn_node_file)
+ {
+ *checksum = NULL;
+ }
+ else
+ {
+
+ err = svn_error_compose_create(
+ err, svn_sqlite__column_checksum(checksum, stmt_info, 6,
+ result_pool));
+ }
+ }
+ if (recorded_size)
+ {
+ *recorded_size = get_recorded_size(stmt_info, 7);
+ }
+ if (target)
+ {
+ if (node_kind != svn_node_symlink)
+ *target = NULL;
+ else
+ *target = svn_sqlite__column_text(stmt_info, 12, result_pool);
+ }
+ if (changelist)
+ {
+ if (have_act)
+ *changelist = svn_sqlite__column_text(stmt_act, 0, result_pool);
+ else
+ *changelist = NULL;
+ }
+ if (op_depth == 0)
+ {
+ if (original_repos_id)
+ *original_repos_id = INVALID_REPOS_ID;
+ if (original_revision)
+ *original_revision = SVN_INVALID_REVNUM;
+ if (original_repos_relpath)
+ *original_repos_relpath = NULL;
+ }
+ else
+ {
+ repos_location_from_columns(original_repos_id,
+ original_revision,
+ original_repos_relpath,
+ stmt_info, 1, 5, 2, result_pool);
+ }
+ if (props_mod)
+ {
+ *props_mod = have_act && !svn_sqlite__column_is_null(stmt_act, 1);
+ }
+ if (had_props)
+ {
+ *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt_info, 14);
+ }
+ if (conflicted)
+ {
+ if (have_act)
+ {
+ *conflicted =
+ !svn_sqlite__column_is_null(stmt_act, 2); /* conflict_data */
+ }
+ else
+ *conflicted = FALSE;
+ }
+
+ if (lock)
+ {
+ if (op_depth != 0)
+ *lock = NULL;
+ else
+ *lock = lock_from_columns(stmt_info, 17, 18, 19, 20, result_pool);
+ }
+
+ if (have_work)
+ *have_work = (op_depth != 0);
+
+ if (op_root)
+ {
+ *op_root = ((op_depth > 0)
+ && (op_depth == relpath_depth(local_relpath)));
+ }
+
+ if (have_base || have_more_work)
+ {
+ if (have_more_work)
+ *have_more_work = FALSE;
+
+ while (!err && op_depth != 0)
+ {
+ err = svn_sqlite__step(&have_info, stmt_info);
+
+ if (err || !have_info)
+ break;
+
+ op_depth = svn_sqlite__column_int(stmt_info, 0);
+
+ if (have_more_work)
+ {
+ if (op_depth > 0)
+ *have_more_work = TRUE;
+
+ if (!have_base)
+ break;
+ }
+ }
+
+ if (have_base)
+ *have_base = (op_depth == 0);
+ }
+ }
+ else if (have_act)
+ {
+ /* A row in ACTUAL_NODE should never exist without a corresponding
+ node in BASE_NODE and/or WORKING_NODE unless it flags a tree conflict. */
+ if (svn_sqlite__column_is_null(stmt_act, 2)) /* conflict_data */
+ err = svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Corrupt data for '%s'"),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ /* ### What should we return? Should we have a separate
+ function for reading actual-only nodes? */
+
+ /* As a safety measure, until we decide if we want to use
+ read_info for actual-only nodes, make sure the caller asked
+ for the conflict status. */
+ SVN_ERR_ASSERT(conflicted);
+
+ if (status)
+ *status = svn_wc__db_status_normal; /* What! No it's not! */
+ if (kind)
+ *kind = svn_node_unknown;
+ if (revision)
+ *revision = SVN_INVALID_REVNUM;
+ if (repos_relpath)
+ *repos_relpath = NULL;
+ if (repos_id)
+ *repos_id = INVALID_REPOS_ID;
+ if (changed_rev)
+ *changed_rev = SVN_INVALID_REVNUM;
+ if (changed_date)
+ *changed_date = 0;
+ if (depth)
+ *depth = svn_depth_unknown;
+ if (checksum)
+ *checksum = NULL;
+ if (target)
+ *target = NULL;
+ if (original_repos_relpath)
+ *original_repos_relpath = NULL;
+ if (original_repos_id)
+ *original_repos_id = INVALID_REPOS_ID;
+ if (original_revision)
+ *original_revision = SVN_INVALID_REVNUM;
+ if (lock)
+ *lock = NULL;
+ if (recorded_size)
+ *recorded_size = 0;
+ if (recorded_time)
+ *recorded_time = 0;
+ if (changelist)
+ *changelist = svn_sqlite__column_text(stmt_act, 0, result_pool);
+ if (op_root)
+ *op_root = FALSE;
+ if (had_props)
+ *had_props = FALSE;
+ if (props_mod)
+ *props_mod = FALSE;
+ if (conflicted)
+ *conflicted = TRUE;
+ if (have_base)
+ *have_base = FALSE;
+ if (have_more_work)
+ *have_more_work = FALSE;
+ if (have_work)
+ *have_work = FALSE;
+ }
+ else
+ {
+ err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ if (stmt_act != NULL)
+ err = svn_error_compose_create(err, svn_sqlite__reset(stmt_act));
+
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ err = svn_error_quick_wrap(err,
+ apr_psprintf(scratch_pool,
+ "Error reading node '%s'",
+ local_relpath));
+
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt_info)));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_info_internal(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ svn_wc__db_lock_t **lock,
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_time,
+ const char **changelist,
+ svn_boolean_t *conflicted,
+ svn_boolean_t *op_root,
+ svn_boolean_t *had_props,
+ svn_boolean_t *props_mod,
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(
+ read_info(status, kind, revision, repos_relpath, repos_id,
+ changed_rev, changed_date, changed_author,
+ depth, checksum, target, original_repos_relpath,
+ original_repos_id, original_revision, lock,
+ recorded_size, recorded_time, changelist, conflicted,
+ op_root, had_props, props_mod,
+ have_base, have_more_work, have_work,
+ wcroot, local_relpath, result_pool, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__db_read_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ const char **original_repos_relpath,
+ const char **original_root_url,
+ const char **original_uuid,
+ svn_revnum_t *original_revision,
+ svn_wc__db_lock_t **lock,
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_time,
+ const char **changelist,
+ svn_boolean_t *conflicted,
+ svn_boolean_t *op_root,
+ svn_boolean_t *have_props,
+ svn_boolean_t *props_mod,
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ apr_int64_t repos_id, original_repos_id;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(read_info(status, kind, revision, repos_relpath, &repos_id,
+ changed_rev, changed_date, changed_author,
+ depth, checksum, target, original_repos_relpath,
+ &original_repos_id, original_revision, lock,
+ recorded_size, recorded_time, changelist, conflicted,
+ op_root, have_props, props_mod,
+ have_base, have_more_work, have_work,
+ wcroot, local_relpath, result_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid,
+ wcroot->sdb, repos_id, result_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(original_root_url, original_uuid,
+ wcroot->sdb, original_repos_id,
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+is_wclocked(svn_boolean_t *locked,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *dir_relpath,
+ apr_pool_t *scratch_pool);
+
+/* What we really want to store about a node. This relies on the
+ offset of svn_wc__db_info_t being zero. */
+struct read_children_info_item_t
+{
+ struct svn_wc__db_info_t info;
+ int op_depth;
+ int nr_layers;
+};
+
+static svn_error_t *
+read_children_info(svn_wc__db_wcroot_t *wcroot,
+ const char *dir_relpath,
+ apr_hash_t *conflicts,
+ apr_hash_t *nodes,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *repos_root_url = NULL;
+ const char *repos_uuid = NULL;
+ apr_int64_t last_repos_id = INVALID_REPOS_ID;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_CHILDREN_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ /* CHILD item points to what we have about the node. We only provide
+ CHILD->item to our caller. */
+ struct read_children_info_item_t *child_item;
+ const char *child_relpath = svn_sqlite__column_text(stmt, 19, NULL);
+ const char *name = svn_relpath_basename(child_relpath, NULL);
+ svn_error_t *err;
+ int op_depth;
+ svn_boolean_t new_child;
+
+ child_item = svn_hash_gets(nodes, name);
+ if (child_item)
+ new_child = FALSE;
+ else
+ {
+ child_item = apr_pcalloc(result_pool, sizeof(*child_item));
+ new_child = TRUE;
+ }
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+
+ /* Do we have new or better information? */
+ if (new_child || op_depth > child_item->op_depth)
+ {
+ struct svn_wc__db_info_t *child = &child_item->info;
+ child_item->op_depth = op_depth;
+
+ child->kind = svn_sqlite__column_token(stmt, 4, kind_map);
+
+ child->status = svn_sqlite__column_token(stmt, 3, presence_map);
+ if (op_depth != 0)
+ {
+ if (child->status == svn_wc__db_status_incomplete)
+ child->incomplete = TRUE;
+ err = convert_to_working_status(&child->status, child->status);
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+ }
+
+ if (op_depth != 0)
+ child->revnum = SVN_INVALID_REVNUM;
+ else
+ child->revnum = svn_sqlite__column_revnum(stmt, 5);
+
+ if (op_depth != 0)
+ child->repos_relpath = NULL;
+ else
+ child->repos_relpath = svn_sqlite__column_text(stmt, 2,
+ result_pool);
+
+ if (op_depth != 0 || svn_sqlite__column_is_null(stmt, 1))
+ {
+ child->repos_root_url = NULL;
+ child->repos_uuid = NULL;
+ }
+ else
+ {
+ const char *last_repos_root_url = NULL;
+
+ apr_int64_t repos_id = svn_sqlite__column_int64(stmt, 1);
+ if (!repos_root_url ||
+ (last_repos_id != INVALID_REPOS_ID &&
+ repos_id != last_repos_id))
+ {
+ last_repos_root_url = repos_root_url;
+ err = svn_wc__db_fetch_repos_info(&repos_root_url,
+ &repos_uuid,
+ wcroot->sdb, repos_id,
+ result_pool);
+ if (err)
+ SVN_ERR(svn_error_compose_create(err,
+ svn_sqlite__reset(stmt)));
+ }
+
+ if (last_repos_id == INVALID_REPOS_ID)
+ last_repos_id = repos_id;
+
+ /* Assume working copy is all one repos_id so that a
+ single cached value is sufficient. */
+ if (repos_id != last_repos_id)
+ {
+ err= svn_error_createf(
+ SVN_ERR_WC_DB_ERROR, NULL,
+ _("The node '%s' comes from unexpected repository "
+ "'%s', expected '%s'; if this node is a file "
+ "external using the correct URL in the external "
+ "definition can fix the problem, see issue #4087"),
+ child_relpath, repos_root_url, last_repos_root_url);
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+ }
+ child->repos_root_url = repos_root_url;
+ child->repos_uuid = repos_uuid;
+ }
+
+ child->changed_rev = svn_sqlite__column_revnum(stmt, 8);
+
+ child->changed_date = svn_sqlite__column_int64(stmt, 9);
+
+ child->changed_author = svn_sqlite__column_text(stmt, 10,
+ result_pool);
+
+ if (child->kind != svn_node_dir)
+ child->depth = svn_depth_unknown;
+ else
+ {
+ child->depth = svn_sqlite__column_token_null(stmt, 11, depth_map,
+ svn_depth_unknown);
+ if (new_child)
+ SVN_ERR(is_wclocked(&child->locked, wcroot, child_relpath,
+ scratch_pool));
+ }
+
+ child->recorded_time = svn_sqlite__column_int64(stmt, 13);
+ child->recorded_size = get_recorded_size(stmt, 7);
+ child->has_checksum = !svn_sqlite__column_is_null(stmt, 6);
+ child->copied = op_depth > 0 && !svn_sqlite__column_is_null(stmt, 2);
+ child->had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 14);
+#ifdef HAVE_SYMLINK
+ if (child->had_props)
+ {
+ apr_hash_t *properties;
+ err = svn_sqlite__column_properties(&properties, stmt, 14,
+ scratch_pool, scratch_pool);
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ child->special = (child->had_props
+ && svn_hash_gets(properties, SVN_PROP_SPECIAL));
+ }
+#endif
+ if (op_depth == 0)
+ child->op_root = FALSE;
+ else
+ child->op_root = (op_depth == relpath_depth(child_relpath));
+
+ svn_hash_sets(nodes, apr_pstrdup(result_pool, name), child);
+ }
+
+ if (op_depth == 0)
+ {
+ child_item->info.have_base = TRUE;
+
+ /* Get the lock info, available only at op_depth 0. */
+ child_item->info.lock = lock_from_columns(stmt, 15, 16, 17, 18,
+ result_pool);
+
+ /* FILE_EXTERNAL flag only on op_depth 0. */
+ child_item->info.file_external = svn_sqlite__column_boolean(stmt,
+ 22);
+ }
+ else
+ {
+ const char *moved_to_relpath;
+
+ child_item->nr_layers++;
+ child_item->info.have_more_work = (child_item->nr_layers > 1);
+
+ /* Moved-to can only exist at op_depth > 0. */
+ /* ### Should we really do this for every layer where op_depth > 0
+ in undefined order? */
+ moved_to_relpath = svn_sqlite__column_text(stmt, 21, NULL);
+ if (moved_to_relpath)
+ child_item->info.moved_to_abspath =
+ svn_dirent_join(wcroot->abspath, moved_to_relpath, result_pool);
+
+ /* Moved-here can only exist at op_depth > 0. */
+ /* ### Should we really do this for every layer where op_depth > 0
+ in undefined order? */
+ child_item->info.moved_here = svn_sqlite__column_boolean(stmt, 20);
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_CHILDREN_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ struct read_children_info_item_t *child_item;
+ struct svn_wc__db_info_t *child;
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *name = svn_relpath_basename(child_relpath, NULL);
+
+ child_item = svn_hash_gets(nodes, name);
+ if (!child_item)
+ {
+ child_item = apr_pcalloc(result_pool, sizeof(*child_item));
+ child_item->info.status = svn_wc__db_status_not_present;
+ }
+
+ child = &child_item->info;
+
+ child->changelist = svn_sqlite__column_text(stmt, 1, result_pool);
+
+ child->props_mod = !svn_sqlite__column_is_null(stmt, 2);
+#ifdef HAVE_SYMLINK
+ if (child->props_mod)
+ {
+ svn_error_t *err;
+ apr_hash_t *properties;
+
+ err = svn_sqlite__column_properties(&properties, stmt, 2,
+ scratch_pool, scratch_pool);
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+ child->special = (NULL != svn_hash_gets(properties,
+ SVN_PROP_SPECIAL));
+ }
+#endif
+
+ child->conflicted = !svn_sqlite__column_is_null(stmt, 3); /* conflict */
+
+ if (child->conflicted)
+ svn_hash_sets(conflicts, apr_pstrdup(result_pool, name), "");
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_children_info(apr_hash_t **nodes,
+ apr_hash_t **conflicts,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *dir_relpath;
+
+ *conflicts = apr_hash_make(result_pool);
+ *nodes = apr_hash_make(result_pool);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &dir_relpath, db,
+ dir_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ read_children_info(wcroot, dir_relpath, *conflicts, *nodes,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_pristine_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth, /* dirs only */
+ const svn_checksum_t **checksum, /* files only */
+ const char **target, /* symlinks only */
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_error_t *err = NULL;
+ int op_depth;
+ svn_wc__db_status_t raw_status;
+ svn_node_kind_t node_kind;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* Obtain the most likely to exist record first, to make sure we don't
+ have to obtain the SQLite read-lock multiple times */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ raw_status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ if (op_depth > 0 && raw_status == svn_wc__db_status_base_deleted)
+ {
+ SVN_ERR(svn_sqlite__step_row(stmt));
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ raw_status = svn_sqlite__column_token(stmt, 3, presence_map);
+ }
+
+ node_kind = svn_sqlite__column_token(stmt, 4, kind_map);
+
+ if (status)
+ {
+ if (op_depth > 0)
+ {
+ err = svn_error_compose_create(err,
+ convert_to_working_status(
+ status,
+ raw_status));
+ }
+ else
+ *status = raw_status;
+ }
+ if (kind)
+ {
+ *kind = node_kind;
+ }
+ if (changed_rev)
+ {
+ *changed_rev = svn_sqlite__column_revnum(stmt, 8);
+ }
+ if (changed_date)
+ {
+ *changed_date = svn_sqlite__column_int64(stmt, 9);
+ }
+ if (changed_author)
+ {
+ *changed_author = svn_sqlite__column_text(stmt, 10,
+ result_pool);
+ }
+ if (depth)
+ {
+ if (node_kind != svn_node_dir)
+ {
+ *depth = svn_depth_unknown;
+ }
+ else
+ {
+ *depth = svn_sqlite__column_token_null(stmt, 11, depth_map,
+ svn_depth_unknown);
+ }
+ }
+ if (checksum)
+ {
+ if (node_kind != svn_node_file)
+ {
+ *checksum = NULL;
+ }
+ else
+ {
+ svn_error_t *err2;
+ err2 = svn_sqlite__column_checksum(checksum, stmt, 6, result_pool);
+
+ if (err2 != NULL)
+ {
+ if (err)
+ err = svn_error_compose_create(
+ err,
+ svn_error_createf(
+ err->apr_err, err2,
+ _("The node '%s' has a corrupt checksum value."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool)));
+ else
+ err = err2;
+ }
+ }
+ }
+ if (target)
+ {
+ if (node_kind != svn_node_symlink)
+ *target = NULL;
+ else
+ *target = svn_sqlite__column_text(stmt, 12, result_pool);
+ }
+ if (had_props)
+ {
+ *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 14);
+ }
+ if (props)
+ {
+ if (raw_status == svn_wc__db_status_normal
+ || raw_status == svn_wc__db_status_incomplete)
+ {
+ SVN_ERR(svn_sqlite__column_properties(props, stmt, 14,
+ result_pool, scratch_pool));
+ if (*props == NULL)
+ *props = apr_hash_make(result_pool);
+ }
+ else
+ {
+ assert(svn_sqlite__column_is_null(stmt, 14));
+ *props = NULL;
+ }
+ }
+
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_sqlite__reset(stmt)));
+}
+
+svn_error_t *
+svn_wc__db_read_children_walker_info(apr_hash_t **nodes,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *dir_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &dir_relpath, db,
+ dir_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_CHILDREN_WALKER_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ *nodes = apr_hash_make(result_pool);
+ while (have_row)
+ {
+ struct svn_wc__db_walker_info_t *child;
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *name = svn_relpath_basename(child_relpath, NULL);
+ int op_depth = svn_sqlite__column_int(stmt, 1);
+ svn_error_t *err;
+
+ child = apr_palloc(result_pool, sizeof(*child));
+ child->status = svn_sqlite__column_token(stmt, 2, presence_map);
+ if (op_depth > 0)
+ {
+ err = convert_to_working_status(&child->status, child->status);
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+ }
+ child->kind = svn_sqlite__column_token(stmt, 3, kind_map);
+ svn_hash_sets(*nodes, apr_pstrdup(result_pool, name), child);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_node_install_info(const char **wcroot_abspath,
+ const svn_checksum_t **sha1_checksum,
+ apr_hash_t **pristine_props,
+ apr_time_t *changed_date,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_error_t *err = NULL;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ if (!wri_abspath)
+ wri_abspath = local_abspath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (local_abspath != wri_abspath
+ && strcmp(local_abspath, wri_abspath))
+ {
+ if (!svn_dirent_is_ancestor(wcroot->abspath, local_abspath))
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' is not in working copy '%s'"),
+ svn_dirent_local_style(local_abspath, scratch_pool),
+ svn_dirent_local_style(wcroot->abspath, scratch_pool));
+
+ local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath);
+ }
+
+ if (wcroot_abspath != NULL)
+ *wcroot_abspath = apr_pstrdup(result_pool, wcroot->abspath);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ if (!err && sha1_checksum)
+ err = svn_sqlite__column_checksum(sha1_checksum, stmt, 6, result_pool);
+
+ if (!err && pristine_props)
+ {
+ err = svn_sqlite__column_properties(pristine_props, stmt, 14,
+ result_pool, scratch_pool);
+ /* Null means no props (assuming presence normal or incomplete). */
+ if (*pristine_props == NULL)
+ *pristine_props = apr_hash_make(result_pool);
+ }
+
+ if (changed_date)
+ *changed_date = svn_sqlite__column_int64(stmt, 9);
+ }
+ else
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' is not installable"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* The body of svn_wc__db_read_url().
+ */
+static svn_error_t *
+read_url_txn(const char **url,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ apr_int64_t repos_id;
+ svn_boolean_t have_base;
+
+ SVN_ERR(read_info(&status, NULL, NULL, &repos_relpath, &repos_id, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &have_base, NULL, NULL,
+ wcroot, local_relpath, scratch_pool, scratch_pool));
+
+ if (repos_relpath == NULL)
+ {
+ if (status == svn_wc__db_status_added)
+ {
+ SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+ }
+ else if (status == svn_wc__db_status_deleted)
+ {
+ const char *base_del_relpath;
+ const char *work_del_relpath;
+
+ SVN_ERR(scan_deletion_txn(&base_del_relpath, NULL,
+ &work_del_relpath,
+ NULL, wcroot,
+ local_relpath,
+ scratch_pool,
+ scratch_pool));
+
+ if (base_del_relpath)
+ {
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath,
+ &repos_id,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot,
+ base_del_relpath,
+ scratch_pool,
+ scratch_pool));
+
+ repos_relpath = svn_relpath_join(
+ repos_relpath,
+ svn_dirent_skip_ancestor(base_del_relpath,
+ local_relpath),
+ scratch_pool);
+ }
+ else
+ {
+ /* The parent of the WORKING delete, must be an addition */
+ const char *work_relpath = NULL;
+
+ /* work_del_relpath should not be NULL. However, we have
+ * observed instances where that assumption was not met.
+ * Bail out in that case instead of crashing with a segfault.
+ */
+ SVN_ERR_ASSERT(work_del_relpath != NULL);
+ work_relpath = svn_relpath_dirname(work_del_relpath,
+ scratch_pool);
+
+ SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, work_relpath,
+ scratch_pool, scratch_pool));
+
+ repos_relpath = svn_relpath_join(
+ repos_relpath,
+ svn_dirent_skip_ancestor(work_relpath,
+ local_relpath),
+ scratch_pool);
+ }
+ }
+ else if (status == svn_wc__db_status_excluded)
+ {
+ const char *parent_relpath;
+ const char *name;
+ const char *url2;
+
+ /* Set 'url' to the *full URL* of the parent WC dir,
+ * and 'name' to the *single path component* that is the
+ * basename of this WC directory, so that joining them will result
+ * in the correct full URL. */
+ svn_relpath_split(&parent_relpath, &name, local_relpath,
+ scratch_pool);
+ SVN_ERR(read_url_txn(&url2, wcroot, parent_relpath,
+ scratch_pool, scratch_pool));
+
+ *url = svn_path_url_add_component2(url2, name, result_pool);
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* All working statee are explicitly handled and all base statee
+ have a repos_relpath */
+ SVN_ERR_MALFUNCTION();
+ }
+ }
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, NULL, wcroot->sdb,
+ repos_id, scratch_pool));
+
+ SVN_ERR_ASSERT(repos_root_url != NULL && repos_relpath != NULL);
+ *url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_url(const char **url,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(read_url_txn(url, wcroot, local_relpath,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and
+ a hash table mapping <tt>char *</tt> names onto svn_string_t *
+ values for any properties of immediate or recursive child nodes of
+ LOCAL_ABSPATH, the actual query being determined by STMT_IDX.
+ If FILES_ONLY is true, only report properties for file child nodes.
+ Check for cancellation between calls of RECEIVER_FUNC.
+*/
+typedef struct cache_props_baton_t
+{
+ svn_depth_t depth;
+ svn_boolean_t pristine;
+ const apr_array_header_t *changelists;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+} cache_props_baton_t;
+
+
+static svn_error_t *
+cache_props_recursive(void *cb_baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ cache_props_baton_t *baton = cb_baton;
+ svn_sqlite__stmt_t *stmt;
+ int stmt_idx;
+
+ SVN_ERR(populate_targets_tree(wcroot, local_relpath, baton->depth,
+ baton->changelists, scratch_pool));
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_TARGET_PROP_CACHE));
+
+ if (baton->pristine)
+ stmt_idx = STMT_CACHE_TARGET_PRISTINE_PROPS;
+ else
+ stmt_idx = STMT_CACHE_TARGET_PROPS;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 1, wcroot->wc_id));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_props_streamily(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t pristine,
+ const apr_array_header_t *changelists,
+ svn_wc__proplist_receiver_t receiver_func,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ cache_props_baton_t baton;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+ svn_error_t *err = NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(receiver_func);
+ SVN_ERR_ASSERT((depth == svn_depth_files) ||
+ (depth == svn_depth_immediates) ||
+ (depth == svn_depth_infinity));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ baton.depth = depth;
+ baton.pristine = pristine;
+ baton.changelists = changelists;
+ baton.cancel_func = cancel_func;
+ baton.cancel_baton = cancel_baton;
+
+ SVN_ERR(with_finalization(wcroot, local_relpath,
+ cache_props_recursive, &baton,
+ NULL, NULL,
+ cancel_func, cancel_baton,
+ NULL, NULL,
+ STMT_DROP_TARGETS_LIST,
+ scratch_pool));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ALL_TARGET_PROP_CACHE));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (!err && have_row)
+ {
+ apr_hash_t *props;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__column_properties(&props, stmt, 1, iterpool,
+ iterpool));
+
+ /* see if someone wants to cancel this operation. */
+ if (cancel_func)
+ err = cancel_func(cancel_baton);
+
+ if (!err && props && apr_hash_count(props) != 0)
+ {
+ const char *child_relpath;
+ const char *child_abspath;
+
+ child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ child_abspath = svn_dirent_join(wcroot->abspath,
+ child_relpath, iterpool);
+
+ err = receiver_func(receiver_baton, child_abspath, props, iterpool);
+ }
+
+ err = svn_error_compose_create(err, svn_sqlite__step(&have_row, stmt));
+ }
+
+ err = svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_error_compose_create(
+ err,
+ svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_DROP_TARGET_PROP_CACHE)));
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for svn_wc__db_read_props().
+ */
+static svn_error_t *
+db_read_props(apr_hash_t **props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_error_t *err = NULL;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row && !svn_sqlite__column_is_null(stmt, 0))
+ {
+ err = svn_sqlite__column_properties(props, stmt, 0,
+ result_pool, scratch_pool);
+ }
+ else
+ have_row = FALSE;
+
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ if (have_row)
+ return SVN_NO_ERROR;
+
+ /* No local changes. Return the pristine props for this node. */
+ SVN_ERR(db_read_pristine_props(props, wcroot, local_relpath, FALSE,
+ result_pool, scratch_pool));
+ if (*props == NULL)
+ {
+ /* Pristine properties are not defined for this node.
+ ### we need to determine whether this node is in a state that
+ ### allows for ACTUAL properties (ie. not deleted). for now,
+ ### just say all nodes, no matter the state, have at least an
+ ### empty set of props. */
+ *props = apr_hash_make(result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(db_read_props(props, wcroot, local_relpath,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+db_read_pristine_props(apr_hash_t **props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_boolean_t deleted_ok,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_wc__db_status_t presence;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_NODE_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+
+ /* Examine the presence: */
+ presence = svn_sqlite__column_token(stmt, 1, presence_map);
+
+ /* For "base-deleted", it is obvious the pristine props are located
+ below the current node. Fetch the NODE from the next record. */
+ if (presence == svn_wc__db_status_base_deleted && deleted_ok)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ SVN_ERR_ASSERT(have_row);
+
+ presence = svn_sqlite__column_token(stmt, 1, presence_map);
+ }
+
+ /* normal or copied: Fetch properties (during update we want
+ properties for incomplete as well) */
+ if (presence == svn_wc__db_status_normal
+ || presence == svn_wc__db_status_incomplete)
+ {
+ svn_error_t *err;
+
+ err = svn_sqlite__column_properties(props, stmt, 0, result_pool,
+ scratch_pool);
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ if (!*props)
+ *props = apr_hash_make(result_pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' has a status that"
+ " has no properties."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__db_read_pristine_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(db_read_pristine_props(props, wcroot, local_relpath, TRUE,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_prop_retrieve_recursive(apr_hash_t **values,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_CURRENT_PROPS_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ *values = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ apr_hash_t *node_props;
+ svn_string_t *value;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__column_properties(&node_props, stmt, 0,
+ iterpool, iterpool));
+
+ value = (node_props
+ ? svn_hash_gets(node_props, propname)
+ : NULL);
+
+ if (value)
+ {
+ svn_hash_sets(*values,
+ svn_dirent_join(wcroot->abspath,
+ svn_sqlite__column_text(stmt, 1, NULL),
+ result_pool),
+ svn_string_dup(value, result_pool));
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+/* The body of svn_wc__db_read_cached_iprops(). */
+static svn_error_t *
+db_read_cached_iprops(apr_array_header_t **iprops,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_IPROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_sqlite__column_iprops(iprops, stmt, 0,
+ result_pool, scratch_pool));
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_cached_iprops(apr_array_header_t **iprops,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* Don't use with_txn yet, as we perform just a single transaction */
+ SVN_ERR(db_read_cached_iprops(iprops, wcroot, local_relpath,
+ result_pool, scratch_pool));
+
+ if (!*iprops)
+ {
+ *iprops = apr_array_make(result_pool, 0,
+ sizeof(svn_prop_inherited_item_t *));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Remove all prop name value pairs from PROP_HASH where the property
+ name is not PROPNAME. */
+static void
+filter_unwanted_props(apr_hash_t *prop_hash,
+ const char * propname,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *ipropname = svn__apr_hash_index_key(hi);
+
+ if (strcmp(ipropname, propname) != 0)
+ svn_hash_sets(prop_hash, ipropname, NULL);
+ }
+ return;
+}
+
+/* Get the changed properties as stored in the ACTUAL table */
+static svn_error_t *
+db_get_changed_props(apr_hash_t **actual_props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row && !svn_sqlite__column_is_null(stmt, 0))
+ SVN_ERR(svn_sqlite__column_properties(actual_props, stmt, 0,
+ result_pool, scratch_pool));
+ else
+ *actual_props = NULL; /* Cached when we read that record */
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+/* The body of svn_wc__db_read_inherited_props(). */
+static svn_error_t *
+db_read_inherited_props(apr_array_header_t **inherited_props,
+ apr_hash_t **actual_props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_array_header_t *cached_iprops = NULL;
+ apr_array_header_t *iprops;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_sqlite__stmt_t *stmt;
+ const char *relpath;
+ const char *expected_parent_repos_relpath = NULL;
+ const char *parent_relpath;
+
+ iprops = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+ *inherited_props = iprops;
+
+ if (actual_props)
+ *actual_props = NULL;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+
+ relpath = local_relpath;
+
+ /* Walk up to the root of the WC looking for inherited properties. When we
+ reach the WC root also check for cached inherited properties. */
+ for (relpath = local_relpath; relpath; relpath = parent_relpath)
+ {
+ svn_boolean_t have_row;
+ int op_depth;
+ svn_wc__db_status_t status;
+ apr_hash_t *node_props;
+
+ parent_relpath = relpath[0] ? svn_relpath_dirname(relpath, scratch_pool)
+ : NULL;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_NOT_FOUND, svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, relpath,
+ scratch_pool));
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+
+ status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ if (status != svn_wc__db_status_normal
+ && status != svn_wc__db_status_incomplete)
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_UNEXPECTED_STATUS, svn_sqlite__reset(stmt),
+ _("The node '%s' has a status that has no properties."),
+ path_for_error_message(wcroot, relpath,
+ scratch_pool));
+
+ if (op_depth > 0)
+ {
+ /* WORKING node. Nothing to check */
+ }
+ else if (expected_parent_repos_relpath)
+ {
+ const char *repos_relpath = svn_sqlite__column_text(stmt, 2, NULL);
+
+ if (strcmp(expected_parent_repos_relpath, repos_relpath) != 0)
+ {
+ /* The child of this node has a different parent than this node
+ (It is "switched"), so we can stop here. Note that switched
+ with the same parent is not interesting for us here. */
+ SVN_ERR(svn_sqlite__reset(stmt));
+ break;
+ }
+
+ expected_parent_repos_relpath =
+ svn_relpath_dirname(expected_parent_repos_relpath, scratch_pool);
+ }
+ else
+ {
+ const char *repos_relpath = svn_sqlite__column_text(stmt, 2, NULL);
+
+ expected_parent_repos_relpath =
+ svn_relpath_dirname(repos_relpath, scratch_pool);
+ }
+
+ if (op_depth == 0
+ && !svn_sqlite__column_is_null(stmt, 16))
+ {
+ /* The node contains a cache. No reason to look further */
+ SVN_ERR(svn_sqlite__column_iprops(&cached_iprops, stmt, 16,
+ result_pool, iterpool));
+
+ parent_relpath = NULL; /* Stop after this */
+ }
+
+ SVN_ERR(svn_sqlite__column_properties(&node_props, stmt, 14,
+ iterpool, iterpool));
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* If PARENT_ABSPATH is a parent of LOCAL_ABSPATH, then LOCAL_ABSPATH
+ can inherit properties from it. */
+ if (relpath != local_relpath)
+ {
+ apr_hash_t *changed_props;
+
+ SVN_ERR(db_get_changed_props(&changed_props, wcroot, relpath,
+ result_pool, iterpool));
+
+ if (changed_props)
+ node_props = changed_props;
+ else if (node_props)
+ node_props = svn_prop_hash_dup(node_props, result_pool);
+
+ if (node_props && apr_hash_count(node_props))
+ {
+ /* If we only want PROPNAME filter out any other properties. */
+ if (propname)
+ filter_unwanted_props(node_props, propname, iterpool);
+
+ if (apr_hash_count(node_props))
+ {
+ svn_prop_inherited_item_t *iprop_elt =
+ apr_pcalloc(result_pool,
+ sizeof(svn_prop_inherited_item_t));
+ iprop_elt->path_or_url = svn_dirent_join(wcroot->abspath,
+ relpath,
+ result_pool);
+
+ iprop_elt->prop_hash = node_props;
+ /* Build the output array in depth-first order. */
+ svn_sort__array_insert(&iprop_elt, iprops, 0);
+ }
+ }
+ }
+ else if (actual_props)
+ {
+ apr_hash_t *changed_props;
+
+ SVN_ERR(db_get_changed_props(&changed_props, wcroot, relpath,
+ result_pool, iterpool));
+
+ if (changed_props)
+ *actual_props = changed_props;
+ else if (node_props)
+ *actual_props = svn_prop_hash_dup(node_props, result_pool);
+ }
+ }
+
+ if (cached_iprops)
+ {
+ for (i = cached_iprops->nelts - 1; i >= 0; i--)
+ {
+ svn_prop_inherited_item_t *cached_iprop =
+ APR_ARRAY_IDX(cached_iprops, i, svn_prop_inherited_item_t *);
+
+ /* An empty property hash in the iprops cache means there are no
+ inherited properties. */
+ if (apr_hash_count(cached_iprop->prop_hash) == 0)
+ continue;
+
+ if (propname)
+ filter_unwanted_props(cached_iprop->prop_hash, propname,
+ scratch_pool);
+
+ /* If we didn't filter everything then keep this iprop. */
+ if (apr_hash_count(cached_iprop->prop_hash))
+ svn_sort__array_insert(&cached_iprop, iprops, 0);
+ }
+ }
+
+ if (actual_props && !*actual_props)
+ *actual_props = apr_hash_make(result_pool);
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_inherited_props(apr_array_header_t **iprops,
+ apr_hash_t **actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(db_read_inherited_props(iprops, actual_props,
+ wcroot, local_relpath, propname,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_get_children_with_cached_iprops().
+ */
+static svn_error_t *
+get_children_with_cached_iprops(apr_hash_t **iprop_paths,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *iprop_paths = apr_hash_make(result_pool);
+
+ /* First check if LOCAL_RELPATH itself has iprops */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_IPROPS_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ const char *relpath_with_cache = svn_sqlite__column_text(stmt, 0,
+ NULL);
+ const char *abspath_with_cache = svn_dirent_join(wcroot->abspath,
+ relpath_with_cache,
+ result_pool);
+ svn_hash_sets(*iprop_paths, abspath_with_cache,
+ svn_sqlite__column_text(stmt, 1, result_pool));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* Now fetch information for children or all descendants */
+ if (depth == svn_depth_files
+ || depth == svn_depth_immediates)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_IPROPS_CHILDREN));
+ }
+ else /* Default to svn_depth_infinity. */
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_IPROPS_RECURSIVE));
+ }
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *relpath_with_cache = svn_sqlite__column_text(stmt, 0,
+ NULL);
+ const char *abspath_with_cache = svn_dirent_join(wcroot->abspath,
+ relpath_with_cache,
+ result_pool);
+ svn_hash_sets(*iprop_paths, abspath_with_cache,
+ svn_sqlite__column_text(stmt, 1, result_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* For depth files we should filter non files */
+ if (depth == svn_depth_files)
+ {
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, *iprop_paths);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child_abspath = svn__apr_hash_index_key(hi);
+ const char *child_relpath;
+ svn_node_kind_t child_kind;
+
+ svn_pool_clear(iterpool);
+
+ child_relpath = svn_dirent_is_child(local_relpath, child_abspath,
+ NULL);
+
+ if (! child_relpath)
+ {
+ continue; /* local_relpath itself */
+ }
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &child_kind, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, child_relpath,
+ scratch_pool,
+ scratch_pool));
+
+ /* Filter if not a file */
+ if (child_kind != svn_node_file)
+ {
+ svn_hash_sets(*iprop_paths, child_abspath, NULL);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_get_children_with_cached_iprops(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool,
+ scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ get_children_with_cached_iprops(iprop_paths, wcroot, local_relpath,
+ depth, result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_children_of_working_node(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return gather_children2(children, wcroot, local_relpath,
+ result_pool, scratch_pool);
+}
+
+/* Helper for svn_wc__db_node_check_replace().
+ */
+static svn_error_t *
+check_replace_txn(svn_boolean_t *is_replace_root_p,
+ svn_boolean_t *base_replace_p,
+ svn_boolean_t *is_replace_p,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_boolean_t is_replace = FALSE;
+ int replaced_op_depth;
+ svn_wc__db_status_t replaced_status;
+
+ /* Our caller initialized the output values to FALSE */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ {
+ svn_wc__db_status_t status;
+
+ status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ if (status != svn_wc__db_status_normal)
+ return svn_error_trace(svn_sqlite__reset(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ return svn_error_trace(svn_sqlite__reset(stmt));
+
+ replaced_status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ /* If the layer below the add describes a not present or a deleted node,
+ this is not a replacement. Deleted can only occur if an ancestor is
+ the delete root. */
+ if (replaced_status != svn_wc__db_status_not_present
+ && replaced_status != svn_wc__db_status_excluded
+ && replaced_status != svn_wc__db_status_server_excluded
+ && replaced_status != svn_wc__db_status_base_deleted)
+ {
+ is_replace = TRUE;
+ if (is_replace_p)
+ *is_replace_p = TRUE;
+ }
+
+ replaced_op_depth = svn_sqlite__column_int(stmt, 0);
+
+ if (base_replace_p)
+ {
+ int op_depth = svn_sqlite__column_int(stmt, 0);
+
+ while (op_depth != 0 && have_row)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ }
+
+ if (have_row && op_depth == 0)
+ {
+ svn_wc__db_status_t base_status;
+
+ base_status = svn_sqlite__column_token(stmt, 3, presence_map);
+
+ *base_replace_p = (base_status != svn_wc__db_status_not_present);
+ }
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (!is_replace_root_p || !is_replace)
+ return SVN_NO_ERROR;
+
+ if (replaced_status != svn_wc__db_status_base_deleted)
+ {
+ int parent_op_depth;
+
+ /* Check the current op-depth of the parent to see if we are a replacement
+ root */
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ svn_relpath_dirname(local_relpath,
+ scratch_pool)));
+
+ SVN_ERR(svn_sqlite__step_row(stmt)); /* Parent must exist as 'normal' */
+
+ parent_op_depth = svn_sqlite__column_int(stmt, 0);
+
+ if (parent_op_depth >= replaced_op_depth)
+ {
+ /* Did we replace inside our directory? */
+
+ *is_replace_root_p = (parent_op_depth == replaced_op_depth);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ parent_op_depth = svn_sqlite__column_int(stmt, 0);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (!have_row)
+ *is_replace_root_p = TRUE; /* Parent is no replacement */
+ else if (parent_op_depth < replaced_op_depth)
+ *is_replace_root_p = TRUE; /* Parent replaces a lower layer */
+ /*else // No replacement root */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_node_check_replace(svn_boolean_t *is_replace_root,
+ svn_boolean_t *base_replace,
+ svn_boolean_t *is_replace,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (is_replace_root)
+ *is_replace_root = FALSE;
+ if (base_replace)
+ *base_replace = FALSE;
+ if (is_replace)
+ *is_replace = FALSE;
+
+ if (local_relpath[0] == '\0')
+ return SVN_NO_ERROR; /* Working copy root can't be replaced */
+
+ SVN_WC__DB_WITH_TXN(
+ check_replace_txn(is_replace_root, base_replace, is_replace,
+ wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_read_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return gather_children(children, wcroot, local_relpath,
+ result_pool, scratch_pool);
+}
+
+
+/* */
+static svn_error_t *
+relocate_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_boolean_t have_base_node,
+ apr_int64_t old_repos_id,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ apr_int64_t new_repos_id;
+
+ /* This function affects all the children of the given local_relpath,
+ but the way that it does this is through the repos inheritance mechanism.
+ So, we only need to rewrite the repos_id of the given local_relpath,
+ as well as any children with a non-null repos_id, as well as various
+ repos_id fields in the locks and working_node tables.
+ */
+
+ /* Get the repos_id for the new repository. */
+ SVN_ERR(create_repos_id(&new_repos_id, repos_root_url, repos_uuid,
+ wcroot->sdb, scratch_pool));
+
+ /* Set the (base and working) repos_ids and clear the dav_caches */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_RECURSIVE_UPDATE_NODE_REPO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isii", wcroot->wc_id, local_relpath,
+ old_repos_id, new_repos_id));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (have_base_node)
+ {
+ /* Update any locks for the root or its children. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_LOCK_REPOS_ID));
+ SVN_ERR(svn_sqlite__bindf(stmt, "ii", old_repos_id, new_repos_id));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_global_relocate(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ const char *repos_root_url,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *local_dir_relpath;
+ svn_wc__db_status_t status;
+ const char *repos_uuid;
+ svn_boolean_t have_base_node;
+ apr_int64_t old_repos_id;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath));
+ /* ### assert that we were passed a directory? */
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_dir_relpath,
+ db, local_dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ local_relpath = local_dir_relpath;
+
+ SVN_ERR(read_info(&status,
+ NULL, NULL, NULL, &old_repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL,
+ &have_base_node, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_excluded)
+ {
+ /* The parent cannot be excluded, so look at the parent and then
+ adjust the relpath */
+ const char *parent_relpath = svn_relpath_dirname(local_dir_relpath,
+ scratch_pool);
+ SVN_ERR(read_info(&status,
+ NULL, NULL, NULL, &old_repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wcroot, parent_relpath,
+ scratch_pool, scratch_pool));
+ local_dir_relpath = parent_relpath;
+ }
+
+ if (old_repos_id == INVALID_REPOS_ID)
+ {
+ /* Do we need to support relocating something that is
+ added/deleted/excluded without relocating the parent? If not
+ then perhaps relpath, root_url and uuid should be passed down
+ to the children so that they don't have to scan? */
+
+ if (status == svn_wc__db_status_deleted)
+ {
+ const char *work_del_relpath;
+
+ SVN_ERR(scan_deletion_txn(NULL, NULL,
+ &work_del_relpath, NULL,
+ wcroot, local_dir_relpath,
+ scratch_pool,
+ scratch_pool));
+ if (work_del_relpath)
+ {
+ /* Deleted within a copy/move */
+
+ /* The parent of the delete is added. */
+ status = svn_wc__db_status_added;
+ local_dir_relpath = svn_relpath_dirname(work_del_relpath,
+ scratch_pool);
+ }
+ }
+
+ if (status == svn_wc__db_status_added)
+ {
+ SVN_ERR(scan_addition(NULL, NULL, NULL, &old_repos_id,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_dir_relpath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, NULL,
+ &old_repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_dir_relpath,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(NULL, &repos_uuid, wcroot->sdb,
+ old_repos_id, scratch_pool));
+ SVN_ERR_ASSERT(repos_uuid);
+
+ SVN_WC__DB_WITH_TXN(
+ relocate_txn(wcroot, local_relpath, repos_root_url, repos_uuid,
+ have_base_node, old_repos_id, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *REPOS_ID and *REPOS_RELPATH to the BASE repository location of
+ (WCROOT, LOCAL_RELPATH), directly if its BASE row exists or implied from
+ its parent's BASE row if not. In the latter case, error if the parent
+ BASE row does not exist. */
+static svn_error_t *
+determine_repos_info(apr_int64_t *repos_id,
+ const char **repos_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *repos_parent_relpath;
+ const char *local_parent_relpath, *name;
+
+ /* ### is it faster to fetch fewer columns? */
+
+ /* Prefer the current node's repository information. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 0));
+ SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 1));
+
+ *repos_id = svn_sqlite__column_int64(stmt, 0);
+ *repos_relpath = svn_sqlite__column_text(stmt, 1, result_pool);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* This was a child node within this wcroot. We want to look at the
+ BASE node of the directory. */
+ svn_relpath_split(&local_parent_relpath, &name, local_relpath, scratch_pool);
+
+ /* The REPOS_ID will be the same (### until we support mixed-repos) */
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_parent_relpath, repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_parent_relpath,
+ scratch_pool, scratch_pool));
+
+ *repos_relpath = svn_relpath_join(repos_parent_relpath, name, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_wc__db_global_commit()
+
+ Makes local_relpath and all its descendants at the same op-depth represent
+ the copy origin repos_id:repos_relpath@revision.
+
+ This code is only valid to fix-up a move from an old location, to a new
+ location during a commit.
+
+ Assumptions:
+ * local_relpath is not the working copy root (can't be moved)
+ * repos_relpath is not the repository root (can't be moved)
+ */
+static svn_error_t *
+moved_descendant_commit(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_int64_t repos_id,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *children;
+ apr_pool_t *iterpool;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_hash_index_t *hi;
+
+ SVN_ERR_ASSERT(*local_relpath != '\0'
+ && *repos_relpath != '\0');
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath,
+ op_depth));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (! have_row)
+ return svn_error_trace(svn_sqlite__reset(stmt));
+
+ children = apr_hash_make(scratch_pool);
+
+ /* First, obtain all moved children */
+ /* To keep error handling simple, first cache them in a hashtable */
+ while (have_row)
+ {
+ const char *src_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ const char *to_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool);
+
+ svn_hash_sets(children, src_relpath, to_relpath);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Then update them */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_COMMIT_UPDATE_ORIGIN));
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, children); hi; hi = apr_hash_next(hi))
+ {
+ const char *src_relpath = svn__apr_hash_index_key(hi);
+ const char *to_relpath = svn__apr_hash_index_val(hi);
+ const char *new_repos_relpath;
+ int to_op_depth = relpath_depth(to_relpath);
+ int affected;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR_ASSERT(to_op_depth > 0);
+
+ new_repos_relpath = svn_relpath_join(
+ repos_relpath,
+ svn_relpath_skip_ancestor(local_relpath,
+ src_relpath),
+ iterpool);
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdisr", wcroot->wc_id,
+ to_relpath,
+ to_op_depth,
+ repos_id,
+ new_repos_relpath,
+ revision));
+ SVN_ERR(svn_sqlite__update(&affected, stmt));
+
+#ifdef SVN_DEBUG
+ /* Enable in release code?
+ Broken moves are not fatal yet, but this assertion would break
+ committing them */
+ SVN_ERR_ASSERT(affected >= 1); /* If this fails there is no move dest */
+#endif
+
+ SVN_ERR(moved_descendant_commit(wcroot, to_relpath, to_op_depth,
+ repos_id, new_repos_relpath, revision,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_wc__db_global_commit()
+
+ Moves all nodes below LOCAL_RELPATH from op-depth OP_DEPTH to op-depth 0
+ (BASE), setting their presence to 'not-present' if their presence wasn't
+ 'normal'.
+
+ Makes all nodes below LOCAL_RELPATH represent the descendants of repository
+ location repos_id:repos_relpath@revision.
+
+ Assumptions:
+ * local_relpath is not the working copy root (can't be replaced)
+ * repos_relpath is not the repository root (can't be replaced)
+ */
+static svn_error_t *
+descendant_commit(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_int64_t repos_id,
+ const char *repos_relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR_ASSERT(*local_relpath != '\0'
+ && *repos_relpath != '\0');
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_COMMIT_DESCENDANTS_TO_BASE));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdisr", wcroot->wc_id,
+ local_relpath,
+ op_depth,
+ repos_id,
+ repos_relpath,
+ revision));
+
+ SVN_ERR(svn_sqlite__update(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_global_commit().
+ */
+static svn_error_t *
+commit_node(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_revnum_t new_revision,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const svn_checksum_t *new_checksum,
+ const apr_array_header_t *new_children,
+ apr_hash_t *new_dav_cache,
+ svn_boolean_t keep_changelist,
+ svn_boolean_t no_unlock,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt_info;
+ svn_sqlite__stmt_t *stmt_act;
+ svn_boolean_t have_act;
+ svn_string_t prop_blob = { 0 };
+ svn_string_t inherited_prop_blob = { 0 };
+ const char *changelist = NULL;
+ const char *parent_relpath;
+ svn_wc__db_status_t new_presence;
+ svn_node_kind_t new_kind;
+ const char *new_depth_str = NULL;
+ svn_sqlite__stmt_t *stmt;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ int op_depth;
+ svn_wc__db_status_t old_presence;
+
+ /* If we are adding a file or directory, then we need to get
+ repository information from the parent node since "this node" does
+ not have a BASE).
+
+ For existing nodes, we should retain the (potentially-switched)
+ repository information. */
+ SVN_ERR(determine_repos_info(&repos_id, &repos_relpath,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ /* ### is it better to select only the data needed? */
+ SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_row(stmt_info));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt_act, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt_act, "is",
+ wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_act, stmt_act));
+
+ /* There should be something to commit! */
+
+ op_depth = svn_sqlite__column_int(stmt_info, 0);
+
+ /* Figure out the new node's kind. It will be whatever is in WORKING_NODE,
+ or there will be a BASE_NODE that has it. */
+ new_kind = svn_sqlite__column_token(stmt_info, 4, kind_map);
+
+ /* What will the new depth be? */
+ if (new_kind == svn_node_dir)
+ new_depth_str = svn_sqlite__column_text(stmt_info, 11, scratch_pool);
+
+ /* Check that the repository information is not being changed. */
+ if (op_depth == 0)
+ {
+ SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt_info, 1));
+ SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt_info, 2));
+
+ /* A commit cannot change these values. */
+ SVN_ERR_ASSERT(repos_id == svn_sqlite__column_int64(stmt_info, 1));
+ SVN_ERR_ASSERT(strcmp(repos_relpath,
+ svn_sqlite__column_text(stmt_info, 2, NULL)) == 0);
+ }
+
+ /* Find the appropriate new properties -- ACTUAL overrides any properties
+ in WORKING that arrived as part of a copy/move.
+
+ Note: we'll keep them as a big blob of data, rather than
+ deserialize/serialize them. */
+ if (have_act)
+ prop_blob.data = svn_sqlite__column_blob(stmt_act, 1, &prop_blob.len,
+ scratch_pool);
+ if (prop_blob.data == NULL)
+ prop_blob.data = svn_sqlite__column_blob(stmt_info, 14, &prop_blob.len,
+ scratch_pool);
+
+ inherited_prop_blob.data = svn_sqlite__column_blob(stmt_info, 16,
+ &inherited_prop_blob.len,
+ scratch_pool);
+
+ if (keep_changelist && have_act)
+ changelist = svn_sqlite__column_text(stmt_act, 0, scratch_pool);
+
+ old_presence = svn_sqlite__column_token(stmt_info, 3, presence_map);
+
+ /* ### other stuff? */
+
+ SVN_ERR(svn_sqlite__reset(stmt_info));
+ SVN_ERR(svn_sqlite__reset(stmt_act));
+
+ if (op_depth > 0)
+ {
+ int affected_rows;
+
+ /* This removes all layers of this node and at the same time determines
+ if we need to remove shadowed layers below our descendants. */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ALL_LAYERS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows > 1)
+ {
+ /* We commit a shadowing operation
+
+ 1) Remove all shadowed nodes
+ 2) And remove all nodes that have a base-deleted as lowest layer,
+ because 1) removed that layer */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_SHADOWED_RECURSIVE));
+
+ SVN_ERR(svn_sqlite__bindf(stmt,
+ "isd",
+ wcroot->wc_id,
+ local_relpath,
+ op_depth));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Note that while these two calls look so similar that they might
+ be integrated, they really affect a different op-depth and
+ completely different nodes (via a different recursion pattern). */
+
+ /* Collapse descendants of the current op_depth in layer 0 */
+ SVN_ERR(descendant_commit(wcroot, local_relpath, op_depth,
+ repos_id, repos_relpath, new_revision,
+ scratch_pool));
+
+ /* And make the recorded local moves represent moves of the node we just
+ committed. */
+ SVN_ERR(moved_descendant_commit(wcroot, local_relpath, 0,
+ repos_id, repos_relpath, new_revision,
+ scratch_pool));
+
+ /* This node is no longer modified, so no node was moved here */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_MOVED_TO_FROM_DEST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Update or add the BASE_NODE row with all the new information. */
+
+ if (*local_relpath == '\0')
+ parent_relpath = NULL;
+ else
+ parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool);
+
+ /* Preserve any incomplete status */
+ new_presence = (old_presence == svn_wc__db_status_incomplete
+ ? svn_wc__db_status_incomplete
+ : svn_wc__db_status_normal);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_APPLY_CHANGES_TO_BASE_NODE));
+ /* symlink_target not yet used */
+ SVN_ERR(svn_sqlite__bindf(stmt, "issisrtstrisnbn",
+ wcroot->wc_id, local_relpath,
+ parent_relpath,
+ repos_id,
+ repos_relpath,
+ new_revision,
+ presence_map, new_presence,
+ new_depth_str,
+ kind_map, new_kind,
+ changed_rev,
+ changed_date,
+ changed_author,
+ prop_blob.data, prop_blob.len));
+
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 13, new_checksum,
+ scratch_pool));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 15, new_dav_cache,
+ scratch_pool));
+ if (inherited_prop_blob.data != NULL)
+ {
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 17, inherited_prop_blob.data,
+ inherited_prop_blob.len));
+ }
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ if (have_act)
+ {
+ if (keep_changelist && changelist != NULL)
+ {
+ /* The user told us to keep the changelist. Replace the row in
+ ACTUAL_NODE with the basic keys and the changelist. */
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ STMT_RESET_ACTUAL_WITH_CHANGELIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isss",
+ wcroot->wc_id, local_relpath,
+ svn_relpath_dirname(local_relpath,
+ scratch_pool),
+ changelist));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ else
+ {
+ /* Toss the ACTUAL_NODE row. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ }
+
+ if (new_kind == svn_node_dir)
+ {
+ /* When committing a directory, we should have its new children. */
+ /* ### one day. just not today. */
+#if 0
+ SVN_ERR_ASSERT(new_children != NULL);
+#endif
+
+ /* ### process the children */
+ }
+
+ if (!no_unlock)
+ {
+ svn_sqlite__stmt_t *lock_stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&lock_stmt, wcroot->sdb,
+ STMT_DELETE_LOCK));
+ SVN_ERR(svn_sqlite__bindf(lock_stmt, "is", repos_id, repos_relpath));
+ SVN_ERR(svn_sqlite__step_done(lock_stmt));
+ }
+
+ /* Install any work items into the queue, as part of this transaction. */
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_global_commit(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_revnum_t new_revision,
+ svn_revnum_t changed_revision,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const svn_checksum_t *new_checksum,
+ const apr_array_header_t *new_children,
+ apr_hash_t *new_dav_cache,
+ svn_boolean_t keep_changelist,
+ svn_boolean_t no_unlock,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_revision));
+ SVN_ERR_ASSERT(new_checksum == NULL || new_children == NULL);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ commit_node(wcroot, local_relpath,
+ new_revision, changed_revision, changed_date, changed_author,
+ new_checksum, new_children, new_dav_cache, keep_changelist,
+ no_unlock, work_items, scratch_pool),
+ wcroot);
+
+ /* We *totally* monkeyed the entries. Toss 'em. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_global_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t new_kind,
+ const char *new_repos_relpath,
+ svn_revnum_t new_revision,
+ const apr_hash_t *new_props,
+ svn_revnum_t new_changed_rev,
+ apr_time_t new_changed_date,
+ const char *new_changed_author,
+ const apr_array_header_t *new_children,
+ const svn_checksum_t *new_checksum,
+ const char *new_target,
+ const apr_hash_t *new_dav_cache,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ NOT_IMPLEMENTED();
+
+#if 0
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ /* ### allow NULL for NEW_REPOS_RELPATH to indicate "no change"? */
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(new_repos_relpath));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_revision));
+ SVN_ERR_ASSERT(new_props != NULL);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_changed_rev));
+ SVN_ERR_ASSERT((new_children != NULL
+ && new_checksum == NULL
+ && new_target == NULL)
+ || (new_children == NULL
+ && new_checksum != NULL
+ && new_target == NULL)
+ || (new_children == NULL
+ && new_checksum == NULL
+ && new_target != NULL));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ update_node(wcroot, local_relpath,
+ new_repos_relpath, new_revision, new_props,
+ new_changed_rev, new_changed_date, new_changed_author,
+ new_children, new_checksum, new_target,
+ conflict, work_items, scratch_pool),
+ wcroot);
+
+ /* We *totally* monkeyed the entries. Toss 'em. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+#endif
+}
+
+/* Sets a base nodes revision, repository relative path, and/or inherited
+ propertis. If LOCAL_ABSPATH's rev (REV) is valid, set its revision. If
+ SET_REPOS_RELPATH is TRUE set its repository relative path to REPOS_RELPATH
+ (and make sure its REPOS_ID is still valid). If IPROPS is not NULL set its
+ inherited properties to IPROPS, if IPROPS is NULL then clear any the iprops
+ cache for the base node.
+ */
+static svn_error_t *
+db_op_set_rev_repos_relpath_iprops(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_array_header_t *iprops,
+ svn_revnum_t rev,
+ svn_boolean_t set_repos_relpath,
+ const char *repos_relpath,
+ apr_int64_t repos_id,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(flush_entries(wcroot,
+ svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool),
+ svn_depth_empty, scratch_pool));
+
+
+ if (SVN_IS_VALID_REVNUM(rev))
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_BASE_REVISION));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isr", wcroot->wc_id, local_relpath,
+ rev));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ if (set_repos_relpath)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_BASE_REPOS));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isis", wcroot->wc_id, local_relpath,
+ repos_id, repos_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Set or clear iprops. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_IPROP));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__bind_iprops(stmt, 3, iprops, scratch_pool));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* The main body of bump_revisions_post_update().
+ *
+ * Tweak the information for LOCAL_RELPATH in WCROOT. If NEW_REPOS_RELPATH is
+ * non-NULL update the entry to the new url specified by NEW_REPOS_RELPATH,
+ * NEW_REPOS_ID. If NEW_REV is valid, make this the node's working revision.
+ *
+ * If WCROOT_IPROPS is not NULL it is a hash mapping const char * absolute
+ * working copy paths to depth-first ordered arrays of
+ * svn_prop_inherited_item_t * structures. If the absolute path equivalent
+ * of LOCAL_RELPATH exists in WCROOT_IPROPS, then set the hashed value as the
+ * node's inherited properties.
+ *
+ * Unless S_ROOT is TRUE the tweaks might cause the node for LOCAL_ABSPATH to
+ * be removed from the WC; if IS_ROOT is TRUE this will not happen.
+ */
+static svn_error_t *
+bump_node_revision(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_int64_t new_repos_id,
+ const char *new_repos_relpath,
+ svn_revnum_t new_rev,
+ svn_depth_t depth,
+ apr_hash_t *exclude_relpaths,
+ apr_hash_t *wcroot_iprops,
+ svn_boolean_t is_root,
+ svn_boolean_t skip_when_dir,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ const apr_array_header_t *children;
+ int i;
+ svn_wc__db_status_t status;
+ svn_node_kind_t db_kind;
+ svn_revnum_t revision;
+ const char *repos_relpath;
+ apr_int64_t repos_id;
+ svn_boolean_t set_repos_relpath = FALSE;
+ svn_boolean_t update_root;
+ svn_depth_t depth_below_here = depth;
+ apr_array_header_t *iprops = NULL;
+
+ /* Skip an excluded path and its descendants. */
+ if (svn_hash_gets(exclude_relpaths, local_relpath))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(&status, &db_kind, &revision,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, &update_root,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ /* Skip file externals */
+ if (update_root
+ && db_kind == svn_node_file
+ && !is_root)
+ return SVN_NO_ERROR;
+
+ if (skip_when_dir && db_kind == svn_node_dir)
+ return SVN_NO_ERROR;
+
+ /* If the node is still marked 'not-present', then the server did not
+ re-add it. So it's really gone in this revision, thus we remove the node.
+
+ If the node is still marked 'server-excluded' and yet is not the same
+ revision as new_rev, then the server did not re-add it, nor
+ re-server-exclude it, so we can remove the node. */
+ if (!is_root
+ && (status == svn_wc__db_status_not_present
+ || (status == svn_wc__db_status_server_excluded &&
+ revision != new_rev)))
+ {
+ return svn_error_trace(db_base_remove(wcroot, local_relpath,
+ db, FALSE, FALSE,
+ SVN_INVALID_REVNUM,
+ NULL, NULL, scratch_pool));
+ }
+
+ if (new_repos_relpath != NULL && strcmp(repos_relpath, new_repos_relpath))
+ set_repos_relpath = TRUE;
+
+ if (wcroot_iprops)
+ iprops = svn_hash_gets(wcroot_iprops,
+ svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool));
+
+ if (iprops
+ || set_repos_relpath
+ || (SVN_IS_VALID_REVNUM(new_rev) && new_rev != revision))
+ {
+ SVN_ERR(db_op_set_rev_repos_relpath_iprops(wcroot, local_relpath,
+ iprops, new_rev,
+ set_repos_relpath,
+ new_repos_relpath,
+ new_repos_id,
+ scratch_pool));
+ }
+
+ /* Early out */
+ if (depth <= svn_depth_empty
+ || db_kind != svn_node_dir
+ || status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_not_present)
+ return SVN_NO_ERROR;
+
+ /* And now recurse over the children */
+
+ depth_below_here = depth;
+
+ if (depth == svn_depth_immediates || depth == svn_depth_files)
+ depth_below_here = svn_depth_empty;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(gather_repo_children(&children, wcroot, local_relpath, 0,
+ scratch_pool, iterpool));
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *child_basename = APR_ARRAY_IDX(children, i, const char *);
+ const char *child_local_relpath;
+ const char *child_repos_relpath = NULL;
+
+ svn_pool_clear(iterpool);
+
+ /* Derive the new URL for the current (child) entry */
+ if (new_repos_relpath)
+ child_repos_relpath = svn_relpath_join(new_repos_relpath,
+ child_basename, iterpool);
+
+ child_local_relpath = svn_relpath_join(local_relpath, child_basename,
+ iterpool);
+
+ SVN_ERR(bump_node_revision(wcroot, child_local_relpath, new_repos_id,
+ child_repos_relpath, new_rev,
+ depth_below_here,
+ exclude_relpaths, wcroot_iprops,
+ FALSE /* is_root */,
+ (depth < svn_depth_immediates), db,
+ iterpool));
+ }
+
+ /* Cleanup */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_wc__db_op_bump_revisions_post_update().
+ */
+static svn_error_t *
+bump_revisions_post_update(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ svn_depth_t depth,
+ const char *new_repos_relpath,
+ const char *new_repos_root_url,
+ const char *new_repos_uuid,
+ svn_revnum_t new_revision,
+ apr_hash_t *exclude_relpaths,
+ apr_hash_t *wcroot_iprops,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_error_t *err;
+ apr_int64_t new_repos_id = INVALID_REPOS_ID;
+
+ err = svn_wc__db_base_get_info_internal(&status, &kind, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ switch (status)
+ {
+ case svn_wc__db_status_excluded:
+ case svn_wc__db_status_server_excluded:
+ case svn_wc__db_status_not_present:
+ return SVN_NO_ERROR;
+
+ /* Explicitly ignore other statii */
+ default:
+ break;
+ }
+
+ if (new_repos_root_url != NULL)
+ SVN_ERR(create_repos_id(&new_repos_id, new_repos_root_url,
+ new_repos_uuid,
+ wcroot->sdb, scratch_pool));
+
+ SVN_ERR(bump_node_revision(wcroot, local_relpath, new_repos_id,
+ new_repos_relpath, new_revision,
+ depth, exclude_relpaths,
+ wcroot_iprops,
+ TRUE /* is_root */, FALSE, db,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_bump_moved_away(wcroot, local_relpath, depth, db,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM, notify_func,
+ notify_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_bump_revisions_post_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ const char *new_repos_relpath,
+ const char *new_repos_root_url,
+ const char *new_repos_uuid,
+ svn_revnum_t new_revision,
+ apr_hash_t *exclude_relpaths,
+ apr_hash_t *wcroot_iprops,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (svn_hash_gets(exclude_relpaths, local_relpath))
+ return SVN_NO_ERROR;
+
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_infinity;
+
+ SVN_WC__DB_WITH_TXN(
+ bump_revisions_post_update(wcroot, local_relpath, db,
+ depth, new_repos_relpath, new_repos_root_url,
+ new_repos_uuid, new_revision,
+ exclude_relpaths, wcroot_iprops,
+ notify_func, notify_baton, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_lock_add().
+ */
+static svn_error_t *
+lock_add_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const svn_wc__db_lock_t *lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ const char *repos_relpath;
+ apr_int64_t repos_id;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_LOCK));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss",
+ repos_id, repos_relpath, lock->token));
+
+ if (lock->owner != NULL)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 4, lock->owner));
+
+ if (lock->comment != NULL)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 5, lock->comment));
+
+ if (lock->date != 0)
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 6, lock->date));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_lock_add(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_wc__db_lock_t *lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(lock != NULL);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ lock_add_txn(wcroot, local_relpath, lock, scratch_pool),
+ wcroot);
+
+ /* There may be some entries, and the lock info is now out of date. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_lock_remove().
+ */
+static svn_error_t *
+lock_remove_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_relpath;
+ apr_int64_t repos_id;
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_LOCK));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", repos_id, repos_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_lock_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ lock_remove_txn(wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ /* There may be some entries, and the lock info is now out of date. */
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_scan_base_repos(const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ apr_int64_t repos_id;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb,
+ repos_id, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* A helper for scan_addition().
+ * Compute moved-from information for the node at LOCAL_RELPATH which
+ * has been determined as having been moved-here.
+ * If MOVED_FROM_RELPATH is not NULL, set *MOVED_FROM_RELPATH to the
+ * path of the move-source node in *MOVED_FROM_RELPATH.
+ * If DELETE_OP_ROOT_RELPATH is not NULL, set *DELETE_OP_ROOT_RELPATH
+ * to the path of the op-root of the delete-half of the move.
+ * If moved-from information cannot be derived, set both *MOVED_FROM_RELPATH
+ * and *DELETE_OP_ROOT_RELPATH to NULL, and return a "copied" status.
+ * COPY_OPT_ROOT_RELPATH is the relpath of the op-root of the copied-half
+ * of the move. */
+static svn_error_t *
+get_moved_from_info(const char **moved_from_relpath,
+ const char **moved_from_op_root_relpath,
+ const char *moved_to_op_root_relpath,
+ int *op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ /* Run a query to get the moved-from path from the DB. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_FROM_RELPATH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, moved_to_op_root_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ /* The move was only recorded at the copy-half, possibly because
+ * the move operation was interrupted mid-way between the copy
+ * and the delete. Treat this node as a normal copy. */
+ if (moved_from_relpath)
+ *moved_from_relpath = NULL;
+ if (moved_from_op_root_relpath)
+ *moved_from_op_root_relpath = NULL;
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+
+ if (op_depth)
+ *op_depth = svn_sqlite__column_int(stmt, 1);
+
+ if (moved_from_relpath || moved_from_op_root_relpath)
+ {
+ const char *db_delete_op_root_relpath;
+
+ /* The moved-from path from the DB is the relpath of
+ * the op_root of the delete-half of the move. */
+ db_delete_op_root_relpath = svn_sqlite__column_text(stmt, 0,
+ result_pool);
+ if (moved_from_op_root_relpath)
+ *moved_from_op_root_relpath = db_delete_op_root_relpath;
+
+ if (moved_from_relpath)
+ {
+ if (strcmp(moved_to_op_root_relpath, local_relpath) == 0)
+ {
+ /* LOCAL_RELPATH is the op_root of the copied-half of the
+ * move, so the correct MOVED_FROM_ABSPATH is the op-root
+ * of the delete-half. */
+ *moved_from_relpath = db_delete_op_root_relpath;
+ }
+ else
+ {
+ const char *child_relpath;
+
+ /* LOCAL_RELPATH is a child that was copied along with the
+ * op_root of the copied-half of the move. Construct the
+ * corresponding path beneath the op_root of the delete-half. */
+
+ /* Grab the child path relative to the op_root of the move
+ * destination. */
+ child_relpath = svn_relpath_skip_ancestor(
+ moved_to_op_root_relpath, local_relpath);
+
+ SVN_ERR_ASSERT(child_relpath && strlen(child_relpath) > 0);
+
+ /* This join is valid because LOCAL_RELPATH has not been moved
+ * within the copied-half of the move yet -- else, it would
+ * be its own op_root. */
+ *moved_from_relpath = svn_relpath_join(db_delete_op_root_relpath,
+ child_relpath,
+ result_pool);
+ }
+ }
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of scan_addition().
+ */
+static svn_error_t *
+scan_addition_txn(svn_wc__db_status_t *status,
+ const char **op_root_relpath_p,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ const char **moved_from_relpath,
+ const char **moved_from_op_root_relpath,
+ int *moved_from_op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *op_root_relpath;
+ const char *build_relpath = "";
+
+ /* Initialize most of the OUT parameters. Generally, we'll only be filling
+ in a subset of these, so it is easier to init all up front. Note that
+ the STATUS parameter will be initialized once we read the status of
+ the specified node. */
+ if (op_root_relpath_p)
+ *op_root_relpath_p = NULL;
+ if (original_repos_relpath)
+ *original_repos_relpath = NULL;
+ if (original_repos_id)
+ *original_repos_id = INVALID_REPOS_ID;
+ if (original_revision)
+ *original_revision = SVN_INVALID_REVNUM;
+ if (moved_from_relpath)
+ *moved_from_relpath = NULL;
+ if (moved_from_op_root_relpath)
+ *moved_from_op_root_relpath = NULL;
+ if (moved_from_op_depth)
+ *moved_from_op_depth = 0;
+
+ {
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_wc__db_status_t presence;
+ int op_depth;
+ const char *repos_prefix_path = "";
+ int i;
+
+ /* ### is it faster to fetch fewer columns? */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ /* Reset statement before returning */
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* ### maybe we should return a usage error instead? */
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ presence = svn_sqlite__column_token(stmt, 1, presence_map);
+
+ /* The starting node should exist normally. */
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ if (op_depth == 0 || (presence != svn_wc__db_status_normal
+ && presence != svn_wc__db_status_incomplete))
+ /* reset the statement as part of the error generation process */
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ svn_sqlite__reset(stmt),
+ _("Expected node '%s' to be added."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+
+ if (original_revision)
+ *original_revision = svn_sqlite__column_revnum(stmt, 12);
+
+ /* Provide the default status; we'll override as appropriate. */
+ if (status)
+ {
+ if (presence == svn_wc__db_status_normal)
+ *status = svn_wc__db_status_added;
+ else
+ *status = svn_wc__db_status_incomplete;
+ }
+
+
+ /* Calculate the op root local path components */
+ op_root_relpath = local_relpath;
+
+ for (i = relpath_depth(local_relpath); i > op_depth; --i)
+ {
+ /* Calculate the path of the operation root */
+ repos_prefix_path =
+ svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL),
+ repos_prefix_path,
+ scratch_pool);
+ op_root_relpath = svn_relpath_dirname(op_root_relpath, scratch_pool);
+ }
+
+ if (op_root_relpath_p)
+ *op_root_relpath_p = apr_pstrdup(result_pool, op_root_relpath);
+
+ /* ### This if-statement is quite redundant.
+ * ### We're checking all these values again within the body anyway.
+ * ### The body should be broken up appropriately and move into the
+ * ### outer scope. */
+ if (original_repos_relpath
+ || original_repos_id
+ || (original_revision
+ && *original_revision == SVN_INVALID_REVNUM)
+ || status
+ || moved_from_relpath || moved_from_op_root_relpath)
+ {
+ if (local_relpath != op_root_relpath)
+ /* requery to get the add/copy root */
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id, op_root_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ /* Reset statement before returning */
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* ### maybe we should return a usage error instead? */
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ op_root_relpath,
+ scratch_pool));
+ }
+
+ if (original_revision
+ && *original_revision == SVN_INVALID_REVNUM)
+ *original_revision = svn_sqlite__column_revnum(stmt, 12);
+ }
+
+ if (original_repos_relpath)
+ *original_repos_relpath = svn_sqlite__column_text(stmt, 11,
+ result_pool);
+
+ if (!svn_sqlite__column_is_null(stmt, 10)
+ && (status
+ || original_repos_id
+ || moved_from_relpath || moved_from_op_root_relpath))
+ /* If column 10 (original_repos_id) is NULL,
+ this is a plain add, not a copy or a move */
+ {
+ svn_boolean_t moved_here;
+ if (original_repos_id)
+ *original_repos_id = svn_sqlite__column_int64(stmt, 10);
+
+ moved_here = svn_sqlite__column_boolean(stmt, 13 /* moved_here */);
+ if (status)
+ *status = moved_here ? svn_wc__db_status_moved_here
+ : svn_wc__db_status_copied;
+
+ if (moved_here
+ && (moved_from_relpath || moved_from_op_root_relpath))
+ {
+ svn_error_t *err;
+
+ err = get_moved_from_info(moved_from_relpath,
+ moved_from_op_root_relpath,
+ op_root_relpath,
+ moved_from_op_depth,
+ wcroot, local_relpath,
+ result_pool,
+ scratch_pool);
+
+ if (err)
+ return svn_error_compose_create(
+ err, svn_sqlite__reset(stmt));
+ }
+ }
+ }
+
+
+ /* ### This loop here is to skip up to the first node which is a BASE node,
+ because base_get_info() doesn't accommodate the scenario that
+ we're looking at here; we found the true op_root, which may be inside
+ further changed trees. */
+ if (repos_relpath || repos_id)
+ {
+ const char *base_relpath;
+
+ while (TRUE)
+ {
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Pointing at op_depth, look at the parent */
+ repos_prefix_path =
+ svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL),
+ repos_prefix_path,
+ scratch_pool);
+ op_root_relpath = svn_relpath_dirname(op_root_relpath, scratch_pool);
+
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, op_root_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (! have_row)
+ break;
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+
+ /* Skip to op_depth */
+ for (i = relpath_depth(op_root_relpath); i > op_depth; i--)
+ {
+ /* Calculate the path of the operation root */
+ repos_prefix_path =
+ svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL),
+ repos_prefix_path,
+ scratch_pool);
+ op_root_relpath =
+ svn_relpath_dirname(op_root_relpath, scratch_pool);
+ }
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ build_relpath = repos_prefix_path;
+
+ /* If we're here, then we have an added/copied/moved (start) node, and
+ CURRENT_ABSPATH now points to a BASE node. Figure out the repository
+ information for the current node, and use that to compute the start
+ node's repository information. */
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &base_relpath, repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, op_root_relpath,
+ scratch_pool, scratch_pool));
+
+ if (repos_relpath)
+ *repos_relpath = svn_relpath_join(base_relpath, build_relpath,
+ result_pool);
+ }
+ else
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+ /* Postconditions */
+#ifdef SVN_DEBUG
+ if (status)
+ {
+ SVN_ERR_ASSERT(*status == svn_wc__db_status_added
+ || *status == svn_wc__db_status_copied
+ || *status == svn_wc__db_status_incomplete
+ || *status == svn_wc__db_status_moved_here);
+ if (*status == svn_wc__db_status_added)
+ {
+ SVN_ERR_ASSERT(!original_repos_relpath
+ || *original_repos_relpath == NULL);
+ SVN_ERR_ASSERT(!original_revision
+ || *original_revision == SVN_INVALID_REVNUM);
+ SVN_ERR_ASSERT(!original_repos_id
+ || *original_repos_id == INVALID_REPOS_ID);
+ }
+ /* An upgrade with a missing directory can leave INCOMPLETE working
+ op-roots. See upgrade_tests.py 29: upgrade with missing replaced dir
+ */
+ else if (*status != svn_wc__db_status_incomplete)
+ {
+ SVN_ERR_ASSERT(!original_repos_relpath
+ || *original_repos_relpath != NULL);
+ SVN_ERR_ASSERT(!original_revision
+ || *original_revision != SVN_INVALID_REVNUM);
+ SVN_ERR_ASSERT(!original_repos_id
+ || *original_repos_id != INVALID_REPOS_ID);
+ }
+ }
+ SVN_ERR_ASSERT(!op_root_relpath_p || *op_root_relpath_p != NULL);
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Like svn_wc__db_scan_addition(), but with WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH.
+
+ The output value of *ORIGINAL_REPOS_ID will be INVALID_REPOS_ID if there
+ is no 'copy-from' repository. */
+static svn_error_t *
+scan_addition(svn_wc__db_status_t *status,
+ const char **op_root_relpath,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ const char **moved_from_relpath,
+ const char **moved_from_op_root_relpath,
+ int *moved_from_op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_WC__DB_WITH_TXN(
+ scan_addition_txn(status, op_root_relpath, repos_relpath, repos_id,
+ original_repos_relpath, original_repos_id,
+ original_revision, moved_from_relpath,
+ moved_from_op_root_relpath, moved_from_op_depth,
+ wcroot, local_relpath, result_pool, scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_scan_addition(svn_wc__db_status_t *status,
+ const char **op_root_abspath,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **original_repos_relpath,
+ const char **original_root_url,
+ const char **original_uuid,
+ svn_revnum_t *original_revision,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *op_root_relpath = NULL;
+ apr_int64_t repos_id = INVALID_REPOS_ID;
+ apr_int64_t original_repos_id = INVALID_REPOS_ID;
+ apr_int64_t *repos_id_p
+ = (repos_root_url || repos_uuid) ? &repos_id : NULL;
+ apr_int64_t *original_repos_id_p
+ = (original_root_url || original_uuid) ? &original_repos_id : NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(scan_addition(status,
+ op_root_abspath
+ ? &op_root_relpath
+ : NULL,
+ repos_relpath, repos_id_p,
+ original_repos_relpath, original_repos_id_p,
+ original_revision,
+ NULL, NULL, NULL,
+ wcroot, local_relpath, result_pool, scratch_pool));
+
+ if (op_root_abspath)
+ *op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath,
+ result_pool);
+ /* REPOS_ID must be valid if requested; ORIGINAL_REPOS_ID need not be. */
+ SVN_ERR_ASSERT(repos_id_p == NULL || repos_id != INVALID_REPOS_ID);
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb,
+ repos_id, result_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(original_root_url, original_uuid,
+ wcroot->sdb, original_repos_id,
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_scan_moved(const char **moved_from_abspath,
+ const char **op_root_abspath,
+ const char **op_root_moved_from_abspath,
+ const char **moved_from_delete_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_wc__db_status_t status;
+ const char *op_root_relpath = NULL;
+ const char *moved_from_relpath = NULL;
+ const char *moved_from_op_root_relpath = NULL;
+ int moved_from_op_depth = -1;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(scan_addition(&status,
+ op_root_abspath
+ ? &op_root_relpath
+ : NULL,
+ NULL, NULL,
+ NULL, NULL, NULL,
+ moved_from_abspath
+ ? &moved_from_relpath
+ : NULL,
+ (op_root_moved_from_abspath
+ || moved_from_delete_abspath)
+ ? &moved_from_op_root_relpath
+ : NULL,
+ moved_from_delete_abspath
+ ? &moved_from_op_depth
+ : NULL,
+ wcroot, local_relpath, scratch_pool, scratch_pool));
+
+ if (status != svn_wc__db_status_moved_here || !moved_from_relpath)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Path '%s' was not moved here"),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+
+ if (op_root_abspath)
+ *op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath,
+ result_pool);
+
+ if (moved_from_abspath)
+ *moved_from_abspath = svn_dirent_join(wcroot->abspath, moved_from_relpath,
+ result_pool);
+
+ if (op_root_moved_from_abspath)
+ *op_root_moved_from_abspath = svn_dirent_join(wcroot->abspath,
+ moved_from_op_root_relpath,
+ result_pool);
+
+ /* The deleted node is either where we moved from, or one of its ancestors */
+ if (moved_from_delete_abspath)
+ {
+ const char *tmp = moved_from_op_root_relpath;
+
+ SVN_ERR_ASSERT(moved_from_op_depth >= 0);
+
+ while (relpath_depth(tmp) > moved_from_op_depth)
+ tmp = svn_relpath_dirname(tmp, scratch_pool);
+
+ *moved_from_delete_abspath = svn_dirent_join(wcroot->abspath, tmp,
+ scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* ###
+ */
+static svn_error_t *
+follow_moved_to(apr_array_header_t **moved_tos,
+ int op_depth,
+ const char *repos_path,
+ svn_revnum_t revision,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int working_op_depth;
+ const char *ancestor_relpath, *node_moved_to = NULL;
+ int i;
+
+ SVN_ERR_ASSERT((!op_depth && !repos_path) || (op_depth && repos_path));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_MOVED_TO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ working_op_depth = svn_sqlite__column_int(stmt, 0);
+ node_moved_to = svn_sqlite__column_text(stmt, 1, result_pool);
+ if (!repos_path)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row || svn_sqlite__column_revnum(stmt, 0))
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND,
+ svn_sqlite__reset(stmt),
+ _("The base node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ repos_path = svn_sqlite__column_text(stmt, 2, scratch_pool);
+ revision = svn_sqlite__column_revnum(stmt, 3);
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (node_moved_to)
+ {
+ svn_boolean_t have_row2;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_HERE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, node_moved_to,
+ relpath_depth(node_moved_to)));
+ SVN_ERR(svn_sqlite__step(&have_row2, stmt));
+ if (!have_row2 || !svn_sqlite__column_int(stmt, 0)
+ || revision != svn_sqlite__column_revnum(stmt, 3)
+ || strcmp(repos_path, svn_sqlite__column_text(stmt, 2, NULL)))
+ node_moved_to = NULL;
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ if (node_moved_to)
+ {
+ struct svn_wc__db_moved_to_t *moved_to;
+
+ moved_to = apr_palloc(result_pool, sizeof(*moved_to));
+ moved_to->op_depth = working_op_depth;
+ moved_to->local_relpath = node_moved_to;
+ APR_ARRAY_PUSH(*moved_tos, struct svn_wc__db_moved_to_t *) = moved_to;
+ }
+
+ /* A working row with moved_to, or no working row, and we are done. */
+ if (node_moved_to || !have_row)
+ return SVN_NO_ERROR;
+
+ /* Need to handle being moved via an ancestor. */
+ ancestor_relpath = local_relpath;
+ for (i = relpath_depth(local_relpath); i > working_op_depth; --i)
+ {
+ const char *ancestor_moved_to;
+
+ ancestor_relpath = svn_relpath_dirname(ancestor_relpath, scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_TO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, ancestor_relpath,
+ working_op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR_ASSERT(have_row);
+ ancestor_moved_to = svn_sqlite__column_text(stmt, 0, scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (ancestor_moved_to)
+ {
+ node_moved_to
+ = svn_relpath_join(ancestor_moved_to,
+ svn_relpath_skip_ancestor(ancestor_relpath,
+ local_relpath),
+ result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_HERE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, node_moved_to,
+ relpath_depth(ancestor_moved_to)));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ ancestor_moved_to = NULL;
+ else if (!svn_sqlite__column_int(stmt, 0))
+ {
+ svn_wc__db_status_t presence
+ = svn_sqlite__column_token(stmt, 1, presence_map);
+ if (presence != svn_wc__db_status_not_present)
+ ancestor_moved_to = NULL;
+ else
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row && !svn_sqlite__column_int(stmt, 0))
+ ancestor_moved_to = NULL;
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!ancestor_moved_to)
+ break;
+ /* verify repos_path points back? */
+ }
+ if (ancestor_moved_to)
+ {
+ struct svn_wc__db_moved_to_t *moved_to;
+
+ moved_to = apr_palloc(result_pool, sizeof(*moved_to));
+ moved_to->op_depth = working_op_depth;
+ moved_to->local_relpath = node_moved_to;
+ APR_ARRAY_PUSH(*moved_tos, struct svn_wc__db_moved_to_t *) = moved_to;
+
+ SVN_ERR(follow_moved_to(moved_tos, relpath_depth(ancestor_moved_to),
+ repos_path, revision, wcroot, node_moved_to,
+ result_pool, scratch_pool));
+ break;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_follow_moved_to(apr_array_header_t **moved_tos,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *moved_tos = apr_array_make(result_pool, 0,
+ sizeof(struct svn_wc__db_moved_to_t *));
+
+ /* ### Wrap in a transaction */
+ SVN_ERR(follow_moved_to(moved_tos, 0, NULL, SVN_INVALID_REVNUM,
+ wcroot, local_relpath,
+ result_pool, scratch_pool));
+
+ /* ### Convert moved_to to abspath */
+
+ return SVN_NO_ERROR;
+}
+
+/* Extract the moved-to information for LOCAL_RELPATH at OP-DEPTH by
+ examining the lowest working node above OP_DEPTH. The output paths
+ are NULL if there is no move, otherwise:
+
+ *MOVE_DST_RELPATH: the moved-to destination of LOCAL_RELPATH.
+
+ *MOVE_DST_OP_ROOT_RELPATH: the moved-to destination of the root of
+ the move of LOCAL_RELPATH. This may be equal to *MOVE_DST_RELPATH
+ if LOCAL_RELPATH is the root of the move.
+
+ *MOVE_SRC_ROOT_RELPATH: the root of the move source. For moves
+ inside a delete this will be different from *MOVE_SRC_OP_ROOT_RELPATH.
+
+ *MOVE_SRC_OP_ROOT_RELPATH: the root of the source layer that
+ contains the move. For moves inside deletes this is the root of
+ the delete, for other moves this is the root of the move.
+
+ Given a path A/B/C with A/B moved to X then for A/B/C
+
+ MOVE_DST_RELPATH is X/C
+ MOVE_DST_OP_ROOT_RELPATH is X
+ MOVE_SRC_ROOT_RELPATH is A/B
+ MOVE_SRC_OP_ROOT_RELPATH is A/B
+
+ If A is then deleted the MOVE_DST_RELPATH, MOVE_DST_OP_ROOT_RELPATH
+ and MOVE_SRC_ROOT_RELPATH remain the same but MOVE_SRC_OP_ROOT_RELPATH
+ changes to A.
+
+ ### Think about combining with scan_deletion? Also with
+ ### scan_addition to get moved-to for replaces? Do we need to
+ ### return the op-root of the move source, i.e. A/B in the example
+ ### above? */
+svn_error_t *
+svn_wc__db_op_depth_moved_to(const char **move_dst_relpath,
+ const char **move_dst_op_root_relpath,
+ const char **move_src_root_relpath,
+ const char **move_src_op_root_relpath,
+ int op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int delete_op_depth;
+ const char *relpath = local_relpath;
+
+ *move_dst_relpath = *move_dst_op_root_relpath = NULL;
+ *move_src_root_relpath = *move_src_op_root_relpath = NULL;
+
+ do
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, relpath, op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ delete_op_depth = svn_sqlite__column_int(stmt, 0);
+ *move_dst_op_root_relpath = svn_sqlite__column_text(stmt, 3,
+ result_pool);
+ if (*move_dst_op_root_relpath)
+ *move_src_root_relpath = apr_pstrdup(result_pool, relpath);
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!*move_dst_op_root_relpath)
+ relpath = svn_relpath_dirname(relpath, scratch_pool);
+ }
+ while (!*move_dst_op_root_relpath
+ && have_row && delete_op_depth <= relpath_depth(relpath));
+
+ if (*move_dst_op_root_relpath)
+ {
+ *move_dst_relpath
+ = svn_relpath_join(*move_dst_op_root_relpath,
+ svn_relpath_skip_ancestor(relpath, local_relpath),
+ result_pool);
+ while (delete_op_depth < relpath_depth(relpath))
+ relpath = svn_relpath_dirname(relpath, scratch_pool);
+ *move_src_op_root_relpath = apr_pstrdup(result_pool, relpath);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Public (within libsvn_wc) absolute path version of
+ svn_wc__db_op_depth_moved_to with the op-depth hard-coded to
+ BASE. */
+svn_error_t *
+svn_wc__db_base_moved_to(const char **move_dst_abspath,
+ const char **move_dst_op_root_abspath,
+ const char **move_src_root_abspath,
+ const char **move_src_op_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *move_dst_relpath, *move_dst_op_root_relpath;
+ const char *move_src_root_relpath, *move_src_op_root_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(svn_wc__db_op_depth_moved_to(&move_dst_relpath,
+ &move_dst_op_root_relpath,
+ &move_src_root_relpath,
+ &move_src_op_root_relpath,
+ 0 /* BASE op-depth */,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool),
+ wcroot);
+
+ if (move_dst_abspath)
+ *move_dst_abspath
+ = move_dst_relpath
+ ? svn_dirent_join(wcroot->abspath, move_dst_relpath, result_pool)
+ : NULL;
+
+ if (move_dst_op_root_abspath)
+ *move_dst_op_root_abspath
+ = move_dst_op_root_relpath
+ ? svn_dirent_join(wcroot->abspath, move_dst_op_root_relpath, result_pool)
+ : NULL;
+
+ if (move_src_root_abspath)
+ *move_src_root_abspath
+ = move_src_root_relpath
+ ? svn_dirent_join(wcroot->abspath, move_src_root_relpath, result_pool)
+ : NULL;
+
+ if (move_src_op_root_abspath)
+ *move_src_op_root_abspath
+ = move_src_op_root_relpath
+ ? svn_dirent_join(wcroot->abspath, move_src_op_root_relpath, result_pool)
+ : NULL;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_upgrade_begin(svn_sqlite__db_t **sdb,
+ apr_int64_t *repos_id,
+ apr_int64_t *wc_id,
+ svn_wc__db_t *wc_db,
+ const char *dir_abspath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+
+ /* Upgrade is inherently exclusive so specify exclusive locking. */
+ SVN_ERR(create_db(sdb, repos_id, wc_id, dir_abspath,
+ repos_root_url, repos_uuid,
+ SDB_FILE,
+ NULL, SVN_INVALID_REVNUM, svn_depth_unknown,
+ TRUE /* exclusive */,
+ wc_db->state_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_pdh_create_wcroot(&wcroot,
+ apr_pstrdup(wc_db->state_pool,
+ dir_abspath),
+ *sdb, *wc_id, FORMAT_FROM_SDB,
+ FALSE /* auto-upgrade */,
+ FALSE /* enforce_empty_wq */,
+ wc_db->state_pool, scratch_pool));
+
+ /* The WCROOT is complete. Stash it into DB. */
+ svn_hash_sets(wc_db->dir_data, wcroot->abspath, wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_upgrade_apply_dav_cache(svn_sqlite__db_t *sdb,
+ const char *dir_relpath,
+ apr_hash_t *cache_values,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_int64_t wc_id;
+ apr_hash_index_t *hi;
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_wc__db_util_fetch_wc_id(&wc_id, sdb, iterpool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPDATE_BASE_NODE_DAV_CACHE));
+
+ /* Iterate over all the wcprops, writing each one to the wc_db. */
+ for (hi = apr_hash_first(scratch_pool, cache_values);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ apr_hash_t *props = svn__apr_hash_index_val(hi);
+ const char *local_relpath;
+
+ svn_pool_clear(iterpool);
+
+ local_relpath = svn_relpath_join(dir_relpath, name, iterpool);
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, iterpool));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb,
+ const char *dir_abspath,
+ const char *local_relpath,
+ apr_hash_t *base_props,
+ apr_hash_t *revert_props,
+ apr_hash_t *working_props,
+ int original_format,
+ apr_int64_t wc_id,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int top_op_depth = -1;
+ int below_op_depth = -1;
+ svn_wc__db_status_t top_presence;
+ svn_wc__db_status_t below_presence;
+ int affected_rows;
+
+ /* ### working_props: use set_props_txn.
+ ### if working_props == NULL, then skip. what if they equal the
+ ### pristine props? we should probably do the compare here.
+ ###
+ ### base props go into WORKING_NODE if avail, otherwise BASE.
+ ###
+ ### revert only goes into BASE. (and WORKING better be there!)
+
+ Prior to 1.4.0 (ORIGINAL_FORMAT < 8), REVERT_PROPS did not exist. If a
+ file was deleted, then a copy (potentially with props) was disallowed
+ and could not replace the deletion. An addition *could* be performed,
+ but that would never bring its own props.
+
+ 1.4.0 through 1.4.5 created the concept of REVERT_PROPS, but had a
+ bug in svn_wc_add_repos_file2() whereby a copy-with-props did NOT
+ construct a REVERT_PROPS if the target had no props. Thus, reverting
+ the delete/copy would see no REVERT_PROPS to restore, leaving the
+ props from the copy source intact, and appearing as if they are (now)
+ the base props for the previously-deleted file. (wc corruption)
+
+ 1.4.6 ensured that an empty REVERT_PROPS would be established at all
+ times. See issue 2530, and r861670 as starting points.
+
+ We will use ORIGINAL_FORMAT and SVN_WC__NO_REVERT_FILES to determine
+ the handling of our inputs, relative to the state of this node.
+ */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ top_op_depth = svn_sqlite__column_int(stmt, 0);
+ top_presence = svn_sqlite__column_token(stmt, 3, presence_map);
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ below_op_depth = svn_sqlite__column_int(stmt, 0);
+ below_presence = svn_sqlite__column_token(stmt, 3, presence_map);
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Detect the buggy scenario described above. We cannot upgrade this
+ working copy if we have no idea where BASE_PROPS should go. */
+ if (original_format > SVN_WC__NO_REVERT_FILES
+ && revert_props == NULL
+ && top_op_depth != -1
+ && top_presence == svn_wc__db_status_normal
+ && below_op_depth != -1
+ && below_presence != svn_wc__db_status_not_present)
+ {
+ /* There should be REVERT_PROPS, so it appears that we just ran into
+ the described bug. Sigh. */
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("The properties of '%s' are in an "
+ "indeterminate state and cannot be "
+ "upgraded. See issue #2530."),
+ svn_dirent_local_style(
+ svn_dirent_join(dir_abspath, local_relpath,
+ scratch_pool), scratch_pool));
+ }
+
+ /* Need at least one row, or two rows if there are revert props */
+ if (top_op_depth == -1
+ || (below_op_depth == -1 && revert_props))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Insufficient NODES rows for '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_join(dir_abspath, local_relpath,
+ scratch_pool), scratch_pool));
+
+ /* one row, base props only: upper row gets base props
+ two rows, base props only: lower row gets base props
+ two rows, revert props only: lower row gets revert props
+ two rows, base and revert props: upper row gets base, lower gets revert */
+
+
+ if (revert_props || below_op_depth == -1)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPDATE_NODE_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wc_id, local_relpath, top_op_depth));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 4, base_props, scratch_pool));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ SVN_ERR_ASSERT(affected_rows == 1);
+ }
+
+ if (below_op_depth != -1)
+ {
+ apr_hash_t *props = revert_props ? revert_props : base_props;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPDATE_NODE_PROPS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wc_id, local_relpath, below_op_depth));
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 4, props, scratch_pool));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ SVN_ERR_ASSERT(affected_rows == 1);
+ }
+
+ /* If there are WORKING_PROPS, then they always go into ACTUAL_NODE. */
+ if (working_props != NULL
+ && base_props != NULL)
+ {
+ apr_array_header_t *diffs;
+
+ SVN_ERR(svn_prop_diffs(&diffs, working_props, base_props, scratch_pool));
+
+ if (diffs->nelts == 0)
+ working_props = NULL; /* No differences */
+ }
+
+ if (working_props != NULL)
+ {
+ SVN_ERR(set_actual_props(wc_id, local_relpath, working_props,
+ sdb, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_upgrade_insert_external(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *parent_abspath,
+ const char *def_local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t def_peg_revision,
+ svn_revnum_t def_revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *def_local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_int64_t repos_id;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ /* We know only of DEF_LOCAL_ABSPATH that it definitely belongs to "this"
+ * WC, i.e. where the svn:externals prop is set. The external target path
+ * itself may be "hidden behind" other working copies. */
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &def_local_relpath,
+ db, def_local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_REPOSITORY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", repos_root_url));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ repos_id = svn_sqlite__column_int64(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (!have_row)
+ {
+ /* Need to set up a new repository row. */
+ SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid,
+ wcroot->sdb, scratch_pool));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_EXTERNAL));
+
+ /* wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath,
+ * repos_id, def_repos_relpath, def_operational_revision, def_revision */
+ SVN_ERR(svn_sqlite__bindf(stmt, "issstsis",
+ wcroot->wc_id,
+ svn_dirent_skip_ancestor(wcroot->abspath,
+ local_abspath),
+ svn_dirent_skip_ancestor(wcroot->abspath,
+ parent_abspath),
+ "normal",
+ kind_map, kind,
+ def_local_relpath,
+ repos_id,
+ repos_relpath));
+
+ if (SVN_IS_VALID_REVNUM(def_peg_revision))
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 9, def_peg_revision));
+
+ if (SVN_IS_VALID_REVNUM(def_revision))
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 10, def_revision));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_upgrade_get_repos_id(apr_int64_t *repos_id,
+ svn_sqlite__db_t *sdb,
+ const char *repos_root_url,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_REPOSITORY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", repos_root_url));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_DB_ERROR, svn_sqlite__reset(stmt),
+ _("Repository '%s' not found in the database"),
+ repos_root_url);
+
+ *repos_id = svn_sqlite__column_int64(stmt, 0);
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+svn_error_t *
+svn_wc__db_wq_add(svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *work_item,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ /* Quick exit, if there are no work items to queue up. */
+ if (work_item == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* Add the work item(s) to the WORK_QUEUE. */
+ return svn_error_trace(add_work_items(wcroot->sdb, work_item,
+ scratch_pool));
+}
+
+/* The body of svn_wc__db_wq_fetch_next().
+ */
+static svn_error_t *
+wq_fetch_next(apr_uint64_t *id,
+ svn_skel_t **work_item,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_uint64_t completed_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ if (completed_id != 0)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WORK_ITEM));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 1, completed_id));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORK_ITEM));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ *id = 0;
+ *work_item = NULL;
+ }
+ else
+ {
+ apr_size_t len;
+ const void *val;
+
+ *id = svn_sqlite__column_int64(stmt, 0);
+
+ val = svn_sqlite__column_blob(stmt, 1, &len, result_pool);
+
+ *work_item = svn_skel__parse(val, len, result_pool);
+ }
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_wc__db_wq_fetch_next(apr_uint64_t *id,
+ svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_uint64_t completed_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(id != NULL);
+ SVN_ERR_ASSERT(work_item != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ wq_fetch_next(id, work_item,
+ wcroot, local_relpath, completed_id,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+/* Records timestamp and date for one or more files in wcroot */
+static svn_error_t *
+wq_record(svn_wc__db_wcroot_t *wcroot,
+ apr_hash_t *record_map,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, record_map); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+ const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi);
+ const char *local_relpath = svn_dirent_skip_ancestor(wcroot->abspath,
+ local_abspath);
+
+ svn_pool_clear(iterpool);
+
+ if (! local_relpath)
+ continue;
+
+ SVN_ERR(db_record_fileinfo(wcroot, local_relpath,
+ dirent->filesize, dirent->mtime,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_wq_record_and_fetch_next(apr_uint64_t *id,
+ svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_uint64_t completed_id,
+ apr_hash_t *record_map,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(id != NULL);
+ SVN_ERR_ASSERT(work_item != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ svn_error_compose_create(
+ wq_fetch_next(id, work_item,
+ wcroot, local_relpath, completed_id,
+ result_pool, scratch_pool),
+ wq_record(wcroot, record_map, scratch_pool)),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* ### temporary API. remove before release. */
+svn_error_t *
+svn_wc__db_temp_get_format(int *format,
+ svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath));
+ /* ### assert that we were passed a directory? */
+
+ err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_dir_abspath, scratch_pool, scratch_pool);
+
+ /* If we hit an error examining this directory, then declare this
+ directory to not be a working copy. */
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* Remap the returned error. */
+ *format = 0;
+ return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(local_dir_abspath,
+ scratch_pool));
+ }
+
+ SVN_ERR_ASSERT(wcroot != NULL);
+ SVN_ERR_ASSERT(wcroot->format >= 1);
+
+ *format = wcroot->format;
+
+ return SVN_NO_ERROR;
+}
+
+/* ### temporary API. remove before release. */
+svn_wc_adm_access_t *
+svn_wc__db_temp_get_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath));
+
+ /* ### we really need to assert that we were passed a directory. sometimes
+ ### adm_retrieve_internal is asked about a file, and then it asks us
+ ### for an access baton for it. we should definitely return NULL, but
+ ### ideally: the caller would never ask us about a non-directory. */
+
+ err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_dir_abspath, scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+
+ if (!wcroot)
+ return NULL;
+
+ return svn_hash_gets(wcroot->access_cache, local_dir_abspath);
+}
+
+
+/* ### temporary API. remove before release. */
+void
+svn_wc__db_temp_set_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath));
+ /* ### assert that we were passed a directory? */
+
+ err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_dir_abspath, scratch_pool, scratch_pool);
+ if (err)
+ {
+ /* We don't even have a wcroot, so just bail. */
+ svn_error_clear(err);
+ return;
+ }
+
+ /* Better not override something already there. */
+ SVN_ERR_ASSERT_NO_RETURN(
+ svn_hash_gets(wcroot->access_cache, local_dir_abspath) == NULL
+ );
+ svn_hash_sets(wcroot->access_cache, local_dir_abspath, adm_access);
+}
+
+
+/* ### temporary API. remove before release. */
+svn_error_t *
+svn_wc__db_temp_close_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath));
+ /* ### assert that we were passed a directory? */
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_dir_abspath, scratch_pool, scratch_pool));
+ svn_hash_sets(wcroot->access_cache, local_dir_abspath, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ### temporary API. remove before release. */
+void
+svn_wc__db_temp_clear_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ svn_wc__db_wcroot_t *wcroot;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath));
+ /* ### assert that we were passed a directory? */
+
+ err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_dir_abspath, scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return;
+ }
+
+ svn_hash_sets(wcroot->access_cache, local_dir_abspath, NULL);
+}
+
+
+apr_hash_t *
+svn_wc__db_temp_get_all_access(svn_wc__db_t *db,
+ apr_pool_t *result_pool)
+{
+ apr_hash_t *result = apr_hash_make(result_pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(result_pool, db->dir_data);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
+
+ /* This is highly redundant, 'cause the same WCROOT will appear many
+ times in dir_data. */
+ result = apr_hash_overlay(result_pool, result, wcroot->access_cache);
+ }
+
+ return result;
+}
+
+
+svn_error_t *
+svn_wc__db_temp_borrow_sdb(svn_sqlite__db_t **sdb,
+ svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *sdb = wcroot->sdb;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_conflict_victims(const apr_array_header_t **victims,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_array_header_t *new_victims;
+
+ /* The parent should be a working copy directory. */
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* ### This will be much easier once we have all conflicts in one
+ field of actual*/
+
+ /* Look for text, tree and property conflicts in ACTUAL */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_CONFLICT_VICTIMS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ new_victims = apr_array_make(result_pool, 0, sizeof(const char *));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ APR_ARRAY_PUSH(new_victims, const char *) =
+ svn_relpath_basename(child_relpath, result_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *victims = new_victims;
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_get_conflict_marker_files().
+ */
+static svn_error_t *
+get_conflict_marker_files(apr_hash_t **marker_files_p,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_hash_t *marker_files = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row && !svn_sqlite__column_is_null(stmt, 2))
+ {
+ apr_size_t len;
+ const void *data = svn_sqlite__column_blob(stmt, 2, &len, NULL);
+ svn_skel_t *conflicts;
+ const apr_array_header_t *markers;
+ int i;
+
+ conflicts = svn_skel__parse(data, len, scratch_pool);
+
+ /* ### ADD markers to *marker_files */
+ SVN_ERR(svn_wc__conflict_read_markers(&markers, db, wcroot->abspath,
+ conflicts,
+ result_pool, scratch_pool));
+
+ for (i = 0; markers && (i < markers->nelts); i++)
+ {
+ const char *marker_abspath = APR_ARRAY_IDX(markers, i, const char*);
+
+ svn_hash_sets(marker_files, marker_abspath, "");
+ }
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_CONFLICT_VICTIMS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ apr_size_t len;
+ const void *data = svn_sqlite__column_blob(stmt, 1, &len, NULL);
+
+ const apr_array_header_t *markers;
+ int i;
+
+ if (data)
+ {
+ svn_skel_t *conflicts;
+ conflicts = svn_skel__parse(data, len, scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_read_markers(&markers, db, wcroot->abspath,
+ conflicts,
+ result_pool, scratch_pool));
+
+ for (i = 0; markers && (i < markers->nelts); i++)
+ {
+ const char *marker_abspath = APR_ARRAY_IDX(markers, i, const char*);
+
+ svn_hash_sets(marker_files, marker_abspath, "");
+ }
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ if (apr_hash_count(marker_files))
+ *marker_files_p = marker_files;
+ else
+ *marker_files_p = NULL;
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_wc__db_get_conflict_marker_files(apr_hash_t **marker_files,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ /* The parent should be a working copy directory. */
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ get_conflict_marker_files(marker_files, wcroot, local_relpath, db,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_read_conflict(svn_skel_t **conflict,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ /* The parent should be a working copy directory. */
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return svn_error_trace(svn_wc__db_read_conflict_internal(conflict, wcroot,
+ local_relpath,
+ result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__db_read_conflict_internal(svn_skel_t **conflict,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ /* Check if we have a conflict in ACTUAL */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (! have_row)
+ {
+ /* Do this while stmt is still open to avoid closing the sqlite
+ transaction and then reopening. */
+ svn_sqlite__stmt_t *stmt_node;
+ svn_error_t *err;
+
+ err = svn_sqlite__get_statement(&stmt_node, wcroot->sdb,
+ STMT_SELECT_NODE_INFO);
+
+ if (err)
+ stmt_node = NULL;
+ else
+ err = svn_sqlite__bindf(stmt_node, "is", wcroot->wc_id,
+ local_relpath);
+
+ if (!err)
+ err = svn_sqlite__step(&have_row, stmt_node);
+
+ if (stmt_node)
+ err = svn_error_compose_create(err,
+ svn_sqlite__reset(stmt_node));
+
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ if (have_row)
+ {
+ *conflict = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ {
+ apr_size_t cfl_len;
+ const void *cfl_data;
+
+ /* svn_skel__parse doesn't copy data, so store in result_pool */
+ cfl_data = svn_sqlite__column_blob(stmt, 2, &cfl_len, result_pool);
+
+ if (cfl_data)
+ *conflict = svn_skel__parse(cfl_data, cfl_len, result_pool);
+ else
+ *conflict = NULL;
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+ }
+}
+
+
+svn_error_t *
+svn_wc__db_read_kind(svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t allow_missing,
+ svn_boolean_t show_deleted,
+ svn_boolean_t show_hidden,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt_info;
+ svn_boolean_t have_info;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_info, stmt_info));
+
+ if (!have_info)
+ {
+ if (allow_missing)
+ {
+ *kind = svn_node_unknown;
+ SVN_ERR(svn_sqlite__reset(stmt_info));
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__reset(stmt_info));
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+ }
+
+ if (!(show_deleted && show_hidden))
+ {
+ int op_depth = svn_sqlite__column_int(stmt_info, 0);
+ svn_boolean_t report_none = FALSE;
+ svn_wc__db_status_t status = svn_sqlite__column_token(stmt_info, 3,
+ presence_map);
+
+ if (op_depth > 0)
+ SVN_ERR(convert_to_working_status(&status, status));
+
+ switch (status)
+ {
+ case svn_wc__db_status_not_present:
+ if (! (show_hidden && show_deleted))
+ report_none = TRUE;
+ break;
+ case svn_wc__db_status_excluded:
+ case svn_wc__db_status_server_excluded:
+ if (! show_hidden)
+ report_none = TRUE;
+ break;
+ case svn_wc__db_status_deleted:
+ if (! show_deleted)
+ report_none = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ if (report_none)
+ {
+ *kind = svn_node_none;
+ return svn_error_trace(svn_sqlite__reset(stmt_info));
+ }
+ }
+
+ *kind = svn_sqlite__column_token(stmt_info, 4, kind_map);
+
+ return svn_error_trace(svn_sqlite__reset(stmt_info));
+}
+
+
+svn_error_t *
+svn_wc__db_node_hidden(svn_boolean_t *hidden,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_wc__db_status_t status;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(read_info(&status, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ *hidden = (status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_excluded);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_is_wcroot(svn_boolean_t *is_wcroot,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (*local_relpath != '\0')
+ {
+ *is_wcroot = FALSE; /* Node is a file, or has a parent directory within
+ the same wcroot */
+ return SVN_NO_ERROR;
+ }
+
+ *is_wcroot = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+/* Find a node's kind and whether it is switched, putting the outputs in
+ * *IS_SWITCHED and *KIND. Either of the outputs may be NULL if not wanted.
+ */
+static svn_error_t *
+db_is_switched(svn_boolean_t *is_switched,
+ svn_node_kind_t *kind,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ const char *name;
+ const char *parent_local_relpath;
+ apr_int64_t parent_repos_id;
+ const char *parent_repos_relpath;
+
+ SVN_ERR_ASSERT(*local_relpath != '\0'); /* Handled in wrapper */
+
+ SVN_ERR(read_info(&status, kind, NULL, &repos_relpath, &repos_id, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath, scratch_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded
+ || status == svn_wc__db_status_not_present)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+ else if (! repos_relpath)
+ {
+ /* Node is shadowed; easy out */
+ if (is_switched)
+ *is_switched = FALSE;
+
+ return SVN_NO_ERROR;
+ }
+
+ if (! is_switched)
+ return SVN_NO_ERROR;
+
+ svn_relpath_split(&parent_local_relpath, &name, local_relpath, scratch_pool);
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &parent_repos_relpath,
+ &parent_repos_id, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ wcroot, parent_local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (repos_id != parent_repos_id)
+ *is_switched = TRUE;
+ else
+ {
+ const char *expected_relpath;
+
+ expected_relpath = svn_relpath_join(parent_repos_relpath, name,
+ scratch_pool);
+
+ *is_switched = (strcmp(expected_relpath, repos_relpath) != 0);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_is_switched(svn_boolean_t *is_wcroot,
+ svn_boolean_t *is_switched,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (is_switched)
+ *is_switched = FALSE;
+
+ if (*local_relpath == '\0')
+ {
+ /* Easy out */
+ if (is_wcroot)
+ *is_wcroot = TRUE;
+
+ if (kind)
+ *kind = svn_node_dir;
+ return SVN_NO_ERROR;
+ }
+
+ if (is_wcroot)
+ *is_wcroot = FALSE;
+
+ if (! is_switched && ! kind)
+ return SVN_NO_ERROR;
+
+ SVN_WC__DB_WITH_TXN(
+ db_is_switched(is_switched, kind, wcroot, local_relpath, scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_temp_wcroot_tempdir(const char **temp_dir_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(temp_dir_abspath != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *temp_dir_abspath = svn_dirent_join_many(result_pool,
+ wcroot->abspath,
+ svn_wc_get_adm_dir(scratch_pool),
+ WCROOT_TEMPDIR_RELPATH,
+ NULL);
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for wclock_obtain_cb() to steal an existing lock */
+static svn_error_t *
+wclock_steal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_WC_LOCK));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_wclock_obtain().
+ */
+static svn_error_t *
+wclock_obtain_cb(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int levels_to_lock,
+ svn_boolean_t steal_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_error_t *err;
+ const char *lock_relpath;
+ int max_depth;
+ int lock_depth;
+ svn_boolean_t got_row;
+
+ svn_wc__db_wclock_t lock;
+
+ /* Upgrade locks the root before the node exists. Apart from that
+ the root node always exists so we will just skip the check.
+
+ ### Perhaps the lock for upgrade should be created when the db is
+ created? 1.6 used to lock .svn on creation. */
+ if (local_relpath[0])
+ {
+ svn_boolean_t exists;
+
+ SVN_ERR(does_node_exist(&exists, wcroot, local_relpath));
+ if (!exists)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ /* Check if there are nodes locked below the new lock root */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_FIND_WC_LOCK));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ lock_depth = relpath_depth(local_relpath);
+ max_depth = lock_depth + levels_to_lock;
+
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+
+ while (got_row)
+ {
+ svn_boolean_t own_lock;
+
+ lock_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+
+ /* If we are not locking with depth infinity, check if this lock
+ voids our lock request */
+ if (levels_to_lock >= 0
+ && relpath_depth(lock_relpath) > max_depth)
+ {
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ continue;
+ }
+
+ /* Check if we are the lock owner, because we should be able to
+ extend our lock. */
+ err = wclock_owns_lock(&own_lock, wcroot, lock_relpath,
+ TRUE, scratch_pool);
+
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+
+ if (!own_lock && !steal_lock)
+ {
+ SVN_ERR(svn_sqlite__reset(stmt));
+ err = svn_error_createf(SVN_ERR_WC_LOCKED, NULL,
+ _("'%s' is already locked."),
+ path_for_error_message(wcroot,
+ lock_relpath,
+ scratch_pool));
+ return svn_error_createf(SVN_ERR_WC_LOCKED, err,
+ _("Working copy '%s' locked."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+ else if (!own_lock)
+ {
+ err = wclock_steal(wcroot, lock_relpath, scratch_pool);
+
+ if (err)
+ SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+ }
+
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (steal_lock)
+ SVN_ERR(wclock_steal(wcroot, local_relpath, scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_WC_LOCK));
+ lock_relpath = local_relpath;
+
+ while (TRUE)
+ {
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, lock_relpath));
+
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+
+ if (got_row)
+ {
+ int levels = svn_sqlite__column_int(stmt, 0);
+ if (levels >= 0)
+ levels += relpath_depth(lock_relpath);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (levels == -1 || levels >= lock_depth)
+ {
+
+ err = svn_error_createf(
+ SVN_ERR_WC_LOCKED, NULL,
+ _("'%s' is already locked."),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath,
+ lock_relpath,
+ scratch_pool),
+ scratch_pool));
+ return svn_error_createf(
+ SVN_ERR_WC_LOCKED, err,
+ _("Working copy '%s' locked."),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+ }
+
+ break; /* There can't be interesting locks on higher nodes */
+ }
+ else
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (!*lock_relpath)
+ break;
+
+ lock_relpath = svn_relpath_dirname(lock_relpath, scratch_pool);
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_WC_LOCK));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ levels_to_lock));
+ err = svn_sqlite__insert(NULL, stmt);
+ if (err)
+ return svn_error_createf(SVN_ERR_WC_LOCKED, err,
+ _("Working copy '%s' locked"),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+
+ /* And finally store that we obtained the lock */
+ lock.local_relpath = apr_pstrdup(wcroot->owned_locks->pool, local_relpath);
+ lock.levels = levels_to_lock;
+ APR_ARRAY_PUSH(wcroot->owned_locks, svn_wc__db_wclock_t) = lock;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_wclock_obtain(svn_wc__db_t *db,
+ const char *local_abspath,
+ int levels_to_lock,
+ svn_boolean_t steal_lock,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(levels_to_lock >= -1);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ if (!steal_lock)
+ {
+ int i;
+ int depth = relpath_depth(local_relpath);
+
+ for (i = 0; i < wcroot->owned_locks->nelts; i++)
+ {
+ svn_wc__db_wclock_t* lock = &APR_ARRAY_IDX(wcroot->owned_locks,
+ i, svn_wc__db_wclock_t);
+
+ if (svn_relpath_skip_ancestor(lock->local_relpath, local_relpath)
+ && (lock->levels == -1
+ || (lock->levels + relpath_depth(lock->local_relpath))
+ >= depth))
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_LOCKED, NULL,
+ _("'%s' is already locked via '%s'."),
+ svn_dirent_local_style(local_abspath, scratch_pool),
+ path_for_error_message(wcroot, lock->local_relpath,
+ scratch_pool));
+ }
+ }
+ }
+
+ SVN_WC__DB_WITH_TXN(
+ wclock_obtain_cb(wcroot, local_relpath, levels_to_lock, steal_lock,
+ scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_wclock_find_root() and svn_wc__db_wclocked(). */
+static svn_error_t *
+find_wclock(const char **lock_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *dir_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int dir_depth = relpath_depth(dir_relpath);
+ const char *first_relpath;
+
+ /* Check for locks on all directories that might be ancestors.
+ As our new apis only use recursive locks the number of locks stored
+ in the DB will be very low */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ANCESTOR_WCLOCKS));
+
+ /* Get the top level relpath to reduce the worst case number of results
+ to the number of directories below this node plus two.
+ (1: the node itself and 2: the wcroot). */
+ first_relpath = strchr(dir_relpath, '/');
+
+ if (first_relpath != NULL)
+ first_relpath = apr_pstrndup(scratch_pool, dir_relpath,
+ first_relpath - dir_relpath);
+ else
+ first_relpath = dir_relpath;
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss",
+ wcroot->wc_id,
+ dir_relpath,
+ first_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ if (svn_relpath_skip_ancestor(relpath, dir_relpath))
+ {
+ int locked_levels = svn_sqlite__column_int(stmt, 1);
+ int row_depth = relpath_depth(relpath);
+
+ if (locked_levels == -1
+ || locked_levels + row_depth >= dir_depth)
+ {
+ *lock_relpath = apr_pstrdup(result_pool, relpath);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+ }
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ *lock_relpath = NULL;
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+static svn_error_t *
+is_wclocked(svn_boolean_t *locked,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *dir_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const char *lock_relpath;
+
+ SVN_ERR(find_wclock(&lock_relpath, wcroot, dir_relpath,
+ scratch_pool, scratch_pool));
+ *locked = (lock_relpath != NULL);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t*
+svn_wc__db_wclock_find_root(const char **lock_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *lock_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ find_wclock(&lock_relpath, wcroot, local_relpath,
+ scratch_pool, scratch_pool),
+ wcroot);
+
+ if (!lock_relpath)
+ *lock_abspath = NULL;
+ else
+ SVN_ERR(svn_wc__db_from_relpath(lock_abspath, db, wcroot->abspath,
+ lock_relpath, result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_wclocked(svn_boolean_t *locked,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ is_wclocked(locked, wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_wclock_release(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ int i;
+ apr_array_header_t *owned_locks;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* First check and remove the owns-lock information as failure in
+ removing the db record implies that we have to steal the lock later. */
+ owned_locks = wcroot->owned_locks;
+ for (i = 0; i < owned_locks->nelts; i++)
+ {
+ svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i,
+ svn_wc__db_wclock_t);
+
+ if (strcmp(lock->local_relpath, local_relpath) == 0)
+ break;
+ }
+
+ if (i >= owned_locks->nelts)
+ return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL,
+ _("Working copy not locked at '%s'."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ if (i < owned_locks->nelts)
+ {
+ owned_locks->nelts--;
+
+ /* Move the last item in the array to the deleted place */
+ if (owned_locks->nelts > 0)
+ APR_ARRAY_IDX(owned_locks, i, svn_wc__db_wclock_t) =
+ APR_ARRAY_IDX(owned_locks, owned_locks->nelts, svn_wc__db_wclock_t);
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_WC_LOCK));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Like svn_wc__db_wclock_owns_lock() but taking WCROOT+LOCAL_RELPATH instead
+ of DB+LOCAL_ABSPATH. */
+static svn_error_t *
+wclock_owns_lock(svn_boolean_t *own_lock,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_boolean_t exact,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *owned_locks;
+ int lock_level;
+ int i;
+
+ *own_lock = FALSE;
+ owned_locks = wcroot->owned_locks;
+ lock_level = relpath_depth(local_relpath);
+
+ if (exact)
+ {
+ for (i = 0; i < owned_locks->nelts; i++)
+ {
+ svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i,
+ svn_wc__db_wclock_t);
+
+ if (strcmp(lock->local_relpath, local_relpath) == 0)
+ {
+ *own_lock = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+ else
+ {
+ for (i = 0; i < owned_locks->nelts; i++)
+ {
+ svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i,
+ svn_wc__db_wclock_t);
+
+ if (svn_relpath_skip_ancestor(lock->local_relpath, local_relpath)
+ && (lock->levels == -1
+ || ((relpath_depth(lock->local_relpath) + lock->levels)
+ >= lock_level)))
+ {
+ *own_lock = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_wclock_owns_lock(svn_boolean_t *own_lock,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t exact,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+
+ if (!wcroot)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("The node '%s' was not found."),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(wclock_owns_lock(own_lock, wcroot, local_relpath, exact,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_temp_op_end_directory_update().
+ */
+static svn_error_t *
+end_directory_update(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_wc__db_status_t base_status;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(&base_status, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (base_status == svn_wc__db_status_normal)
+ return SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(base_status == svn_wc__db_status_incomplete);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_NODE_BASE_PRESENCE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "ist", wcroot->wc_id, local_relpath,
+ presence_map, svn_wc__db_status_normal));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_temp_op_end_directory_update(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_dir_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ end_directory_update(wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_dir_abspath, svn_depth_empty,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_temp_op_start_directory_update().
+ */
+static svn_error_t *
+start_directory_update_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const char *new_repos_relpath,
+ svn_revnum_t new_rev,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ /* Note: In the majority of calls, the repos_relpath is unchanged. */
+ /* ### TODO: Maybe check if we can make repos_relpath NULL. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "istrs",
+ wcroot->wc_id,
+ local_relpath,
+ presence_map, svn_wc__db_status_incomplete,
+ new_rev,
+ new_repos_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+
+}
+
+svn_error_t *
+svn_wc__db_temp_op_start_directory_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *new_repos_relpath,
+ svn_revnum_t new_rev,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_rev));
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(new_repos_relpath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ start_directory_update_txn(wcroot, local_relpath,
+ new_repos_relpath, new_rev, scratch_pool),
+ wcroot);
+
+ SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* The body of svn_wc__db_temp_op_make_copy(). This is
+ used by the update editor when deleting a base node tree would be a
+ tree-conflict because there are changes to subtrees. This function
+ inserts a copy of the base node tree below any existing working
+ subtrees. Given a tree:
+
+ 0 1 2 3
+ / normal -
+ A normal -
+ A/B normal - normal
+ A/B/C normal - base-del normal
+ A/F normal - normal
+ A/F/G normal - normal
+ A/F/H normal - base-deleted normal
+ A/F/E normal - not-present
+ A/X normal -
+ A/X/Y incomplete -
+
+ This function adds layers to A and some of its descendants in an attempt
+ to make the working copy look like as if it were a copy of the BASE nodes.
+
+ 0 1 2 3
+ / normal -
+ A normal norm
+ A/B normal norm norm
+ A/B/C normal norm base-del normal
+ A/F normal norm norm
+ A/F/G normal norm norm
+ A/F/H normal norm not-pres
+ A/F/E normal norm base-del
+ A/X normal norm
+ A/X/Y incomplete incomplete
+ */
+static svn_error_t *
+make_copy_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ const svn_skel_t *conflicts,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_boolean_t add_working_base_deleted = FALSE;
+ svn_boolean_t remove_working = FALSE;
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, 0));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ svn_wc__db_status_t working_status;
+ int working_op_depth;
+
+ working_status = svn_sqlite__column_token(stmt, 1, presence_map);
+ working_op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR_ASSERT(working_status == svn_wc__db_status_normal
+ || working_status == svn_wc__db_status_base_deleted
+ || working_status == svn_wc__db_status_not_present
+ || working_status == svn_wc__db_status_incomplete);
+
+ /* Only change nodes in the layers where we are creating the copy.
+ Deletes in higher layers will just apply to the copy */
+ if (working_op_depth <= op_depth)
+ {
+ add_working_base_deleted = TRUE;
+
+ if (working_status == svn_wc__db_status_base_deleted)
+ remove_working = TRUE;
+ }
+ }
+ else
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (remove_working)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_DELETE_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ if (add_working_base_deleted)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_DELETE_FROM_BASE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_WORKING_NODE_FROM_BASE_COPY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Get the BASE children, as WORKING children don't need modifications */
+ SVN_ERR(gather_repo_children(&children, wcroot, local_relpath,
+ 0, scratch_pool, iterpool));
+
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+ const char *copy_relpath;
+
+ svn_pool_clear(iterpool);
+
+ copy_relpath = svn_relpath_join(local_relpath, name, iterpool);
+
+ SVN_ERR(make_copy_txn(wcroot, copy_relpath, op_depth, NULL, NULL,
+ iterpool));
+ }
+
+ SVN_ERR(flush_entries(wcroot, svn_dirent_join(wcroot->abspath, local_relpath,
+ iterpool),
+ svn_depth_empty, iterpool));
+
+ if (conflicts)
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflicts, iterpool));
+
+ SVN_ERR(add_work_items(wcroot->sdb, work_items, iterpool));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_op_make_copy(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflicts,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* The update editor is supposed to call this function when there is
+ no working node for LOCAL_ABSPATH. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Modification of '%s' already exists"),
+ path_for_error_message(wcroot,
+ local_relpath,
+ scratch_pool));
+
+ /* We don't allow copies to contain server-excluded nodes;
+ the update editor is going to have to bail out. */
+ SVN_ERR(catch_copy_of_server_excluded(wcroot, local_relpath, scratch_pool));
+
+ SVN_WC__DB_WITH_TXN(
+ make_copy_txn(wcroot, local_relpath,
+ relpath_depth(local_relpath), conflicts, work_items,
+ scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_info_below_working(svn_boolean_t *have_base,
+ svn_boolean_t *have_work,
+ svn_wc__db_status_t *status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ SVN_ERR(info_below_working(have_base, have_work, status,
+ wcroot, local_relpath, -1, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_get_not_present_descendants(const apr_array_header_t **descendants,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_NOT_PRESENT_DESCENDANTS));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd",
+ wcroot->wc_id,
+ local_relpath,
+ relpath_depth(local_relpath)));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ {
+ apr_array_header_t *paths;
+
+ paths = apr_array_make(result_pool, 4, sizeof(const char*));
+ while (have_row)
+ {
+ const char *found_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ APR_ARRAY_PUSH(paths, const char *)
+ = apr_pstrdup(result_pool, svn_relpath_skip_ancestor(
+ local_relpath, found_relpath));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ *descendants = paths;
+ }
+ else
+ *descendants = apr_array_make(result_pool, 0, sizeof(const char*));
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+/* Like svn_wc__db_min_max_revisions(),
+ * but accepts a WCROOT/LOCAL_RELPATH pair. */
+static svn_error_t *
+get_min_max_revisions(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_boolean_t committed,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_revnum_t min_rev, max_rev;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MIN_MAX_REVISIONS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step_row(stmt));
+
+ if (committed)
+ {
+ min_rev = svn_sqlite__column_revnum(stmt, 2);
+ max_rev = svn_sqlite__column_revnum(stmt, 3);
+ }
+ else
+ {
+ min_rev = svn_sqlite__column_revnum(stmt, 0);
+ max_rev = svn_sqlite__column_revnum(stmt, 1);
+ }
+
+ /* The statement returns exactly one row. */
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (min_revision)
+ *min_revision = min_rev;
+ if (max_revision)
+ *max_revision = max_rev;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_min_max_revisions(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t committed,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return svn_error_trace(get_min_max_revisions(min_revision, max_revision,
+ wcroot, local_relpath,
+ committed, scratch_pool));
+}
+
+
+/* Set *IS_SPARSE_CHECKOUT TRUE if LOCAL_RELPATH or any of the nodes
+ * within LOCAL_RELPATH is sparse, FALSE otherwise. */
+static svn_error_t *
+is_sparse_checkout_internal(svn_boolean_t *is_sparse_checkout,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_HAS_SPARSE_NODES));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id,
+ local_relpath));
+ /* If this query returns a row, the working copy is sparse. */
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ *is_sparse_checkout = have_row;
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Like svn_wc__db_has_switched_subtrees(),
+ * but accepts a WCROOT/LOCAL_RELPATH pair. */
+static svn_error_t *
+has_switched_subtrees(svn_boolean_t *is_switched,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const char *trail_url,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+
+ /* Optional argument handling for caller */
+ if (!is_switched)
+ return SVN_NO_ERROR;
+
+ *is_switched = FALSE;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ /* First do the cheap check where we only need info on the origin itself */
+ if (trail_url != NULL)
+ {
+ const char *repos_root_url;
+ const char *url;
+ apr_size_t len1, len2;
+
+ /* If the trailing part of the URL of the working copy directory
+ does not match the given trailing URL then the whole working
+ copy is switched. */
+
+ SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, NULL, wcroot->sdb,
+ repos_id, scratch_pool));
+ url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ scratch_pool);
+
+ len1 = strlen(trail_url);
+ len2 = strlen(url);
+ if ((len1 > len2) || strcmp(url + len2 - len1, trail_url))
+ {
+ *is_switched = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_HAS_SWITCHED));
+ SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, repos_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ *is_switched = TRUE;
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_has_switched_subtrees(svn_boolean_t *is_switched,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *trail_url,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return svn_error_trace(has_switched_subtrees(is_switched, wcroot,
+ local_relpath, trail_url,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__db_get_excluded_subtrees(apr_hash_t **excluded_subtrees,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ALL_EXCLUDED_DESCENDANTS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (have_row)
+ *excluded_subtrees = apr_hash_make(result_pool);
+ else
+ *excluded_subtrees = NULL;
+
+ while (have_row)
+ {
+ const char *abs_path =
+ svn_dirent_join(wcroot->abspath,
+ svn_sqlite__column_text(stmt, 0, NULL),
+ result_pool);
+ svn_hash_sets(*excluded_subtrees, abs_path, abs_path);
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ return SVN_NO_ERROR;
+}
+
+/* Like svn_wc__db_has_local_mods(),
+ * but accepts a WCROOT/LOCAL_RELPATH pair.
+ * ### This needs a DB as well as a WCROOT/RELPATH pair... */
+static svn_error_t *
+has_local_mods(svn_boolean_t *is_modified,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ /* Check for additions or deletions. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SUBTREE_HAS_TREE_MODIFICATIONS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ /* If this query returns a row, the working copy is modified. */
+ SVN_ERR(svn_sqlite__step(is_modified, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ if (! *is_modified)
+ {
+ /* Check for property modifications. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SUBTREE_HAS_PROP_MODIFICATIONS));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ /* If this query returns a row, the working copy is modified. */
+ SVN_ERR(svn_sqlite__step(is_modified, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+ }
+
+ if (! *is_modified)
+ {
+ apr_pool_t *iterpool = NULL;
+ svn_boolean_t have_row;
+
+ /* Check for text modifications. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_FILES_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *node_abspath;
+ svn_filesize_t recorded_size;
+ apr_time_t recorded_time;
+ svn_boolean_t skip_check = FALSE;
+ svn_error_t *err;
+
+ if (cancel_func)
+ {
+ err = cancel_func(cancel_baton);
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+ }
+
+ svn_pool_clear(iterpool);
+
+ node_abspath = svn_dirent_join(wcroot->abspath,
+ svn_sqlite__column_text(stmt, 0,
+ iterpool),
+ iterpool);
+
+ recorded_size = get_recorded_size(stmt, 1);
+ recorded_time = svn_sqlite__column_int64(stmt, 2);
+
+ if (recorded_size != SVN_INVALID_FILESIZE
+ && recorded_time != 0)
+ {
+ const svn_io_dirent2_t *dirent;
+
+ err = svn_io_stat_dirent2(&dirent, node_abspath, FALSE, TRUE,
+ iterpool, iterpool);
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+
+ if (dirent->kind != svn_node_file)
+ {
+ *is_modified = TRUE; /* Missing or obstruction */
+ break;
+ }
+ else if (dirent->filesize == recorded_size
+ && dirent->mtime == recorded_time)
+ {
+ /* The file is not modified */
+ skip_check = TRUE;
+ }
+ }
+
+ if (! skip_check)
+ {
+ err = svn_wc__internal_file_modified_p(is_modified,
+ db, node_abspath,
+ FALSE, iterpool);
+
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+
+ if (*is_modified)
+ break;
+ }
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ if (iterpool)
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_has_local_mods(svn_boolean_t *is_modified,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ return svn_error_trace(has_local_mods(is_modified, wcroot, local_relpath,
+ db, cancel_func, cancel_baton,
+ scratch_pool));
+}
+
+
+/* The body of svn_wc__db_revision_status().
+ */
+static svn_error_t *
+revision_status_txn(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_boolean_t *is_sparse_checkout,
+ svn_boolean_t *is_modified,
+ svn_boolean_t *is_switched,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ const char *trail_url,
+ svn_boolean_t committed,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_boolean_t exists;
+
+ SVN_ERR(does_node_exist(&exists, wcroot, local_relpath));
+
+ if (!exists)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("The node '%s' was not found."),
+ path_for_error_message(wcroot, local_relpath,
+ scratch_pool));
+ }
+
+ /* Determine mixed-revisionness. */
+ SVN_ERR(get_min_max_revisions(min_revision, max_revision, wcroot,
+ local_relpath, committed, scratch_pool));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Determine sparseness. */
+ SVN_ERR(is_sparse_checkout_internal(is_sparse_checkout, wcroot,
+ local_relpath, scratch_pool));
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Check for switched nodes. */
+ {
+ err = has_switched_subtrees(is_switched, wcroot, local_relpath,
+ trail_url, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err); /* No Base node, but no fatal error */
+ *is_switched = FALSE;
+ }
+ }
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Check for local mods. */
+ SVN_ERR(has_local_mods(is_modified, wcroot, local_relpath, db,
+ cancel_func, cancel_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_revision_status(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_boolean_t *is_sparse_checkout,
+ svn_boolean_t *is_modified,
+ svn_boolean_t *is_switched,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *trail_url,
+ svn_boolean_t committed,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ revision_status_txn(min_revision, max_revision,
+ is_sparse_checkout, is_modified, is_switched,
+ wcroot, local_relpath, db,
+ trail_url, committed, cancel_func, cancel_baton,
+ scratch_pool),
+ wcroot);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_lock_tokens_recursive(apr_hash_t **lock_tokens,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_int64_t last_repos_id = INVALID_REPOS_ID;
+ const char *last_repos_root_url = NULL;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *lock_tokens = apr_hash_make(result_pool);
+
+ /* Fetch all the lock tokens in and under LOCAL_RELPATH. */
+ SVN_ERR(svn_sqlite__get_statement(
+ &stmt, wcroot->sdb,
+ STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ apr_int64_t child_repos_id = svn_sqlite__column_int64(stmt, 0);
+ const char *child_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ const char *lock_token = svn_sqlite__column_text(stmt, 2, result_pool);
+
+ if (child_repos_id != last_repos_id)
+ {
+ svn_error_t *err = svn_wc__db_fetch_repos_info(&last_repos_root_url,
+ NULL, wcroot->sdb,
+ child_repos_id,
+ scratch_pool);
+
+ if (err)
+ {
+ return svn_error_trace(
+ svn_error_compose_create(err,
+ svn_sqlite__reset(stmt)));
+ }
+
+ last_repos_id = child_repos_id;
+ }
+
+ SVN_ERR_ASSERT(last_repos_root_url != NULL);
+ svn_hash_sets(*lock_tokens,
+ svn_path_url_add_component2(last_repos_root_url,
+ child_relpath, result_pool),
+ lock_token);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ return svn_sqlite__reset(stmt);
+}
+
+
+/* If EXPRESSION is false, cause the caller to return an SVN_ERR_WC_CORRUPT
+ * error, showing EXPRESSION and the caller's LOCAL_RELPATH in the message. */
+#define VERIFY(expression) \
+ do { \
+ if (! (expression)) \
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, \
+ _("database inconsistency at local_relpath='%s' verifying " \
+ "expression '%s'"), local_relpath, #expression); \
+ } while (0)
+
+
+/* Verify consistency of the metadata concerning WCROOT. This is intended
+ * for use only during testing and debugging, so is not intended to be
+ * blazingly fast.
+ *
+ * This code is a complement to any verification that we can do in SQLite
+ * triggers. See, for example, 'wc-checks.sql'.
+ *
+ * Some more verification steps we might want to add are:
+ *
+ * * on every ACTUAL row (except root): a NODES row exists at its parent path
+ * * the op-depth root must always exist and every intermediate too
+ */
+static svn_error_t *
+verify_wcroot(svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_ALL_NODES));
+ SVN_ERR(svn_sqlite__bindf(stmt, "i", wcroot->wc_id));
+ while (TRUE)
+ {
+ svn_boolean_t have_row;
+ const char *local_relpath, *parent_relpath;
+ int op_depth;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ break;
+
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ local_relpath = svn_sqlite__column_text(stmt, 1, iterpool);
+ parent_relpath = svn_sqlite__column_text(stmt, 2, iterpool);
+
+ /* Verify parent_relpath is the parent path of local_relpath */
+ VERIFY((parent_relpath == NULL)
+ ? (local_relpath[0] == '\0')
+ : (strcmp(svn_relpath_dirname(local_relpath, iterpool),
+ parent_relpath) == 0));
+
+ /* Verify op_depth <= the tree depth of local_relpath */
+ VERIFY(op_depth <= relpath_depth(local_relpath));
+
+ /* Verify parent_relpath refers to a row that exists */
+ /* TODO: Verify there is a suitable parent row - e.g. has op_depth <=
+ * the child's and a suitable presence */
+ if (parent_relpath && svn_sqlite__column_is_null(stmt, 3))
+ {
+ svn_sqlite__stmt_t *stmt2;
+ svn_boolean_t have_a_parent_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt2, wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(stmt2, "is", wcroot->wc_id,
+ parent_relpath));
+ SVN_ERR(svn_sqlite__step(&have_a_parent_row, stmt2));
+ VERIFY(have_a_parent_row);
+ SVN_ERR(svn_sqlite__reset(stmt2));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+svn_error_t *
+svn_wc__db_verify(svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, wri_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(verify_wcroot(wcroot, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_bump_format(int *result_format,
+ const char *wcroot_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__db_t *sdb;
+ svn_error_t *err;
+ int format;
+
+ /* Do not scan upwards for a working copy root here to prevent accidental
+ * upgrades of any working copies the WCROOT might be nested in.
+ * Just try to open a DB at the specified path instead. */
+ err = svn_wc__db_util_open_db(&sdb, wcroot_abspath, SDB_FILE,
+ svn_sqlite__mode_readwrite,
+ TRUE, /* exclusive */
+ NULL, /* my statements */
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ svn_error_t *err2;
+ apr_hash_t *entries;
+
+ /* Could not open an sdb. Check for an entries file instead. */
+ err2 = svn_wc__read_entries_old(&entries, wcroot_abspath,
+ scratch_pool, scratch_pool);
+ if (err2 || apr_hash_count(entries) == 0)
+ return svn_error_createf(SVN_ERR_WC_INVALID_OP_ON_CWD,
+ svn_error_compose_create(err, err2),
+ _("Can't upgrade '%s' as it is not a working copy root"),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool));
+
+ /* An entries file was found. This is a pre-wc-ng working copy
+ * so suggest an upgrade. */
+ return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, err,
+ _("Working copy '%s' is too old and must be upgraded to "
+ "at least format %d, as created by Subversion %s"),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool),
+ SVN_WC__WC_NG_VERSION,
+ svn_wc__version_string_from_format(SVN_WC__WC_NG_VERSION));
+ }
+
+ SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool));
+ err = svn_wc__upgrade_sdb(result_format, wcroot_abspath,
+ sdb, format, scratch_pool);
+
+ /* Make sure we return a different error than expected for upgrades from
+ entries */
+ if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
+ err = svn_error_create(SVN_ERR_WC_UNSUPPORTED_FORMAT, err,
+ _("Working copy upgrade failed"));
+
+ err = svn_error_compose_create(err, svn_sqlite__close(sdb));
+
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_wc__db_vacuum(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_VACUUM));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/wc_db.h b/contrib/subversion/subversion/libsvn_wc/wc_db.h
new file mode 100644
index 0000000..154262d
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc_db.h
@@ -0,0 +1,3413 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_wc_db.h
+ * @brief The Subversion Working Copy Library - Metadata/Base-Text Support
+ *
+ * Requires:
+ * - A working copy
+ *
+ * Provides:
+ * - Ability to manipulate working copy's administrative files.
+ *
+ * Used By:
+ * - The main working copy library
+ */
+
+#ifndef SVN_WC_DB_H
+#define SVN_WC_DB_H
+
+#include "svn_wc.h"
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_config.h"
+#include "svn_io.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* INTERFACE CONVENTIONS
+
+ "OUT" PARAMETERS
+
+ There are numerous functions within this API which take a (large) number
+ of "out" parameters. These are listed individually, rather than combined
+ into a struct, so that a caller can be fine-grained about the which
+ pieces of information are being requested. In many cases, only a subset
+ is required, so the implementation can perform various optimizations
+ to fulfill the limited request for information.
+
+
+ POOLS
+
+ wc_db uses the dual-pool paradigm for all of its functions. Any OUT
+ parameter will be allocated within the result pool, and all temporary
+ allocations will be performed within the scratch pool.
+
+ The pool that DB is allocated within (the "state" pool) is only used
+ for a few, limited allocations to track each of the working copy roots
+ that the DB is asked to operate upon. The memory usage on this pool
+ is O(# wcroots), which should normally be one or a few. Custom clients
+ which hold open structures over a significant period of time should
+ pay particular attention to the number of roots touched, and the
+ resulting impact on memory consumption (which should still be minimal).
+
+
+ PARAMETER CONVENTIONS
+
+ * Parameter Order
+ - any output arguments
+ - DB
+ - LOCAL_ABSPATH
+ - any other input arguments
+ - RESULT_POOL
+ - SCRATCH_POOL
+
+ * DB
+ This parameter is the primary context for all operations on the
+ metadata for working copies. This parameter is passed to almost every
+ function, and maintains information and state about every working
+ copy "touched" by any of the APIs in this interface.
+
+ * *_ABSPATH
+ All *_ABSPATH parameters in this API are absolute paths in the local
+ filesystem, represented in Subversion internal canonical form.
+
+ * LOCAL_ABSPATH
+ This parameter specifies a particular *versioned* node in the local
+ filesystem. From this node, a working copy root is implied, and will
+ be used for the given API operation.
+
+ * LOCAL_DIR_ABSPATH
+ This parameter is similar to LOCAL_ABSPATH, but the semantics of the
+ parameter and operation require the node to be a directory within
+ the working copy.
+
+ * WRI_ABSPATH
+ This is a "Working copy Root Indicator" path. This refers to a location
+ in the local filesystem that is anywhere inside a working copy. The given
+ operation will be performed within the context of the root of that
+ working copy. This does not necessarily need to refer to a specific
+ versioned node or the root of a working copy (although it can) -- any
+ location, existing or not, is sufficient, as long as it is inside a
+ working copy.
+ ### TODO: Define behaviour for switches and externals.
+ ### Preference has been stated that WRI_ABSPATH should imply the root
+ ### of the parent WC of all switches and externals, but that may
+ ### not play out well, especially with multiple repositories involved.
+*/
+
+/* Context data structure for interacting with the administrative data. */
+typedef struct svn_wc__db_t svn_wc__db_t;
+
+
+/* Enumerated values describing the state of a node. */
+typedef enum svn_wc__db_status_t {
+ /* The node is present and has no known modifications applied to it. */
+ svn_wc__db_status_normal,
+
+ /* The node has been added (potentially obscuring a delete or move of
+ the BASE node; see HAVE_BASE param [### What param? This is an enum
+ not a function.] ). The text will be marked as
+ modified, and if properties exist, they will be marked as modified.
+
+ In many cases svn_wc__db_status_added means any of added, moved-here
+ or copied-here. See individual functions for clarification and
+ svn_wc__db_scan_addition() to get more details. */
+ svn_wc__db_status_added,
+
+ /* This node has been added with history, based on the move source.
+ Text and property modifications are based on whether changes have
+ been made against their pristine versions. */
+ svn_wc__db_status_moved_here,
+
+ /* This node has been added with history, based on the copy source.
+ Text and property modifications are based on whether changes have
+ been made against their pristine versions. */
+ svn_wc__db_status_copied,
+
+ /* This node has been deleted. No text or property modifications
+ will be present. */
+ svn_wc__db_status_deleted,
+
+ /* This node was named by the server, but no information was provided. */
+ svn_wc__db_status_server_excluded,
+
+ /* This node has been administratively excluded. */
+ svn_wc__db_status_excluded,
+
+ /* This node is not present in this revision. This typically happens
+ when a node is deleted and committed without updating its parent.
+ The parent revision indicates it should be present, but this node's
+ revision states otherwise. */
+ svn_wc__db_status_not_present,
+
+ /* This node is known, but its information is incomplete. Generally,
+ it should be treated similar to the other missing status values
+ until some (later) process updates the node with its data.
+
+ When the incomplete status applies to a directory, the list of
+ children and the list of its base properties as recorded in the
+ working copy do not match their working copy versions.
+ The update editor can complete a directory by using a different
+ update algorithm. */
+ svn_wc__db_status_incomplete,
+
+ /* The BASE node has been marked as deleted. Only used as an internal
+ status in wc_db.c and entries.c. */
+ svn_wc__db_status_base_deleted
+
+} svn_wc__db_status_t;
+
+/* Lock information. We write/read it all as one, so let's use a struct
+ for convenience. */
+typedef struct svn_wc__db_lock_t {
+ /* The lock token */
+ const char *token;
+
+ /* The owner of the lock, possibly NULL */
+ const char *owner;
+
+ /* A comment about the lock, possibly NULL */
+ const char *comment;
+
+ /* The date the lock was created */
+ apr_time_t date;
+} svn_wc__db_lock_t;
+
+
+/* ### NOTE: I have not provided docstrings for most of this file at this
+ ### point in time. The shape and extent of this API is still in massive
+ ### flux. I'm iterating in public, but do not want to doc until it feels
+ ### like it is "Right".
+*/
+
+/* ### where/how to handle: text_time, locks, working_size */
+
+
+/*
+ @defgroup svn_wc__db_admin General administrative functions
+ @{
+*/
+
+/* Open a working copy administrative database context.
+
+ This context is (initially) not associated with any particular working
+ copy directory or working copy root (wcroot). As operations are performed,
+ this context will load the appropriate wcroot information.
+
+ The context is returned in DB.
+
+ CONFIG should hold the various configuration options that may apply to
+ the administrative operation. It should live at least as long as the
+ RESULT_POOL parameter.
+
+ When OPEN_WITHOUT_UPGRADE is TRUE, then the working copy databases will
+ be opened even when an old database format is found/detected during
+ the operation of a wc_db API). If open_without_upgrade is FALSE and an
+ upgrade is required, then SVN_ERR_WC_UPGRADE_REQUIRED will be returned
+ from that API.
+ Passing TRUE will allow a bare minimum of APIs to function (most notably,
+ the temp_get_format() function will always return a value) since most of
+ these APIs expect a current-format database to be present.
+
+ If ENFORCE_EMPTY_WQ is TRUE, then any databases with stale work items in
+ their work queue will raise an error when they are opened. The operation
+ will raise SVN_ERR_WC_CLEANUP_REQUIRED. Passing FALSE for this routine
+ means that the work queue is being processed (via 'svn cleanup') and all
+ operations should be allowed.
+
+ The DB will be closed when RESULT_POOL is cleared. It may also be closed
+ manually using svn_wc__db_close(). In particular, this will close any
+ SQLite databases that have been opened and cached.
+
+ The context is allocated in RESULT_POOL. This pool is *retained* and used
+ for future allocations within the DB. Be forewarned about unbounded
+ memory growth if this DB is used across an unbounded number of wcroots
+ and versioned directories.
+
+ Temporary allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_open(svn_wc__db_t **db,
+ svn_config_t *config,
+ svn_boolean_t open_without_upgrade,
+ svn_boolean_t enforce_empty_wq,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Close DB. */
+svn_error_t *
+svn_wc__db_close(svn_wc__db_t *db);
+
+
+/* Initialize the SDB for LOCAL_ABSPATH, which should be a working copy path.
+
+ A REPOSITORY row will be constructed for the repository identified by
+ REPOS_ROOT_URL and REPOS_UUID. Neither of these may be NULL.
+
+ A BASE_NODE row will be created for the directory at REPOS_RELPATH at
+ revision INITIAL_REV.
+ If INITIAL_REV is greater than zero, then the node will be marked as
+ "incomplete" because we don't know its children. Contrary, if the
+ INITIAL_REV is zero, then this directory should represent the root and
+ we know it has no children, so the node is complete.
+
+ ### Is there any benefit to marking it 'complete' if rev==0? Seems like
+ ### an unnecessary special case.
+
+ DEPTH is the initial depth of the working copy; it must be a definite
+ depth, not svn_depth_unknown.
+
+ Use SCRATCH_POOL for temporary allocations.
+*/
+svn_error_t *
+svn_wc__db_init(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t initial_rev,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool);
+
+
+/* Compute the LOCAL_RELPATH for the given LOCAL_ABSPATH, relative
+ from wri_abspath.
+
+ The LOCAL_RELPATH is a relative path to the working copy's root. That
+ root will be located by this function, and the path will be relative to
+ that location. If LOCAL_ABSPATH is the wcroot directory, then "" will
+ be returned.
+
+ The LOCAL_RELPATH should ONLY be used for persisting paths to disk.
+ Those paths should not be abspaths, otherwise the working copy cannot
+ be moved. The working copy library should not make these paths visible
+ in its API (which should all be abspaths), and it should not be using
+ relpaths for other processing.
+
+ LOCAL_RELPATH will be allocated in RESULT_POOL. All other (temporary)
+ allocations will be made in SCRATCH_POOL.
+
+ This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE
+ option.
+*/
+svn_error_t *
+svn_wc__db_to_relpath(const char **local_relpath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Compute the LOCAL_ABSPATH for a LOCAL_RELPATH located within the working
+ copy identified by WRI_ABSPATH.
+
+ This is the reverse of svn_wc__db_to_relpath. It should be used for
+ returning a persisted relpath back into an abspath.
+
+ LOCAL_ABSPATH will be allocated in RESULT_POOL. All other (temporary)
+ allocations will be made in SCRATCH_POOL.
+
+ This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE
+ option.
+ */
+svn_error_t *
+svn_wc__db_from_relpath(const char **local_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Compute the working copy root WCROOT_ABSPATH for WRI_ABSPATH using DB.
+
+ This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE
+ option.
+ */
+svn_error_t *
+svn_wc__db_get_wcroot(const char **wcroot_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* @} */
+
+/* Different kinds of trees
+
+ The design doc mentions three different kinds of trees, BASE, WORKING and
+ ACTUAL: http://svn.apache.org/repos/asf/subversion/trunk/notes/wc-ng-design
+ We have different APIs to handle each tree, enumerated below, along with
+ a blurb to explain what that tree represents.
+*/
+
+/* @defgroup svn_wc__db_base BASE tree management
+
+ BASE is what we get from the server. It is the *absolute* pristine copy.
+ You need to use checkout, update, switch, or commit to alter your view of
+ the repository.
+
+ In the BASE tree, each node corresponds to a particular node-rev in the
+ repository. It can be a mixed-revision tree. Each node holds either a
+ copy of the node-rev as it exists in the repository (if presence =
+ 'normal'), or a place-holder (if presence = 'server-excluded' or 'excluded' or
+ 'not-present').
+
+ @{
+*/
+
+/* Add or replace a directory in the BASE tree.
+
+ The directory is located at LOCAL_ABSPATH on the local filesystem, and
+ corresponds to <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID> in the
+ repository, at revision REVISION.
+
+ The directory properties are given by the PROPS hash (which is
+ const char *name => const svn_string_t *).
+
+ The last-change information is given by <CHANGED_REV, CHANGED_DATE,
+ CHANGED_AUTHOR>.
+
+ The directory's children are listed in CHILDREN, as an array of
+ const char *. The child nodes do NOT have to exist when this API
+ is called. For each child node which does not exists, an "incomplete"
+ node will be added. These child nodes will be added regardless of
+ the DEPTH value. The caller must sort out which must be recorded,
+ and which must be omitted.
+
+ This subsystem does not use DEPTH, but it can be recorded here in
+ the BASE tree for higher-level code to use.
+
+ If DAV_CACHE is not NULL, sets LOCAL_ABSPATH's dav cache to the specified
+ data.
+
+ If CONFLICT is not NULL, then it describes a conflict for this node. The
+ node will be record as conflicted (in ACTUAL).
+
+ If UPDATE_ACTUAL_PROPS is TRUE, set the properties store NEW_ACTUAL_PROPS
+ as the new set of properties in ACTUAL. If NEW_ACTUAL_PROPS is NULL or
+ when the value of NEW_ACTUAL_PROPS matches NEW_PROPS, store NULL in
+ ACTUAL, to mark the properties unmodified.
+
+ If NEW_IPROPS is not NULL, then it is a depth-first ordered array of
+ svn_prop_inherited_item_t * structures that is set as the base node's
+ inherited_properties.
+
+ Any work items that are necessary as part of this node construction may
+ be passed in WORK_ITEMS.
+
+ All temporary allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_add_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const apr_array_header_t *children,
+ svn_depth_t depth,
+ apr_hash_t *dav_cache,
+ const svn_skel_t *conflict,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Add a new directory in BASE, whether WORKING nodes exist or not. Mark it
+ as incomplete and with revision REVISION. If REPOS_RELPATH is not NULL,
+ apply REPOS_RELPATH, REPOS_ROOT_URL and REPOS_UUID.
+ Perform all temporary allocations in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__db_base_add_incomplete_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t insert_base_deleted,
+ svn_boolean_t delete_working,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Add or replace a file in the BASE tree.
+
+ The file is located at LOCAL_ABSPATH on the local filesystem, and
+ corresponds to <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID> in the
+ repository, at revision REVISION.
+
+ The file properties are given by the PROPS hash (which is
+ const char *name => const svn_string_t *).
+
+ The last-change information is given by <CHANGED_REV, CHANGED_DATE,
+ CHANGED_AUTHOR>.
+
+ The checksum of the file contents is given in CHECKSUM. An entry in
+ the pristine text base is NOT required when this API is called.
+
+ If DAV_CACHE is not NULL, sets LOCAL_ABSPATH's dav cache to the specified
+ data.
+
+ If CONFLICT is not NULL, then it describes a conflict for this node. The
+ node will be record as conflicted (in ACTUAL).
+
+ If UPDATE_ACTUAL_PROPS is TRUE, set the properties store NEW_ACTUAL_PROPS
+ as the new set of properties in ACTUAL. If NEW_ACTUAL_PROPS is NULL or
+ when the value of NEW_ACTUAL_PROPS matches NEW_PROPS, store NULL in
+ ACTUAL, to mark the properties unmodified.
+
+ Any work items that are necessary as part of this node construction may
+ be passed in WORK_ITEMS.
+
+ Unless KEEP_RECORDED_INFO is set to TRUE, recorded size and timestamp values
+ will be cleared.
+
+ All temporary allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const svn_checksum_t *checksum,
+ apr_hash_t *dav_cache,
+ svn_boolean_t delete_working,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ svn_boolean_t keep_recorded_info,
+ svn_boolean_t insert_base_deleted,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Add or replace a symlink in the BASE tree.
+
+ The symlink is located at LOCAL_ABSPATH on the local filesystem, and
+ corresponds to <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID> in the
+ repository, at revision REVISION.
+
+ The symlink's properties are given by the PROPS hash (which is
+ const char *name => const svn_string_t *).
+
+ The last-change information is given by <CHANGED_REV, CHANGED_DATE,
+ CHANGED_AUTHOR>.
+
+ The target of the symlink is specified by TARGET.
+
+ If DAV_CACHE is not NULL, sets LOCAL_ABSPATH's dav cache to the specified
+ data.
+
+ If CONFLICT is not NULL, then it describes a conflict for this node. The
+ node will be record as conflicted (in ACTUAL).
+
+ If UPDATE_ACTUAL_PROPS is TRUE, set the properties store NEW_ACTUAL_PROPS
+ as the new set of properties in ACTUAL. If NEW_ACTUAL_PROPS is NULL or
+ when the value of NEW_ACTUAL_PROPS matches NEW_PROPS, store NULL in
+ ACTUAL, to mark the properties unmodified.
+
+ Any work items that are necessary as part of this node construction may
+ be passed in WORK_ITEMS.
+
+ All temporary allocations will be made in SCRATCH_POOL.
+*/
+/* ### KFF: This is an interesting question, because currently
+ ### symlinks are versioned as regular files with the svn:special
+ ### property; then the file's text contents indicate that it is a
+ ### symlink and where that symlink points. That's for portability:
+ ### you can check 'em out onto a platform that doesn't support
+ ### symlinks, and even modify the link and check it back in. It's
+ ### a great solution; but then the question for wc-ng is:
+ ###
+ ### Suppose you check out a symlink on platform X and platform Y.
+ ### X supports symlinks; Y does not. Should the wc-ng storage for
+ ### those two be the same? I mean, on platform Y, the file is just
+ ### going to look and behave like a regular file. It would be sort
+ ### of odd for the wc-ng storage for that file to be of a different
+ ### type from all the other files. (On the other hand, maybe it's
+ ### weird today that the wc-1 storage for a working symlink is to
+ ### be like a regular file (i.e., regular text-base and whatnot).
+ ###
+ ### I'm still feeling my way around this problem; just pointing out
+ ### the issues.
+
+ ### gjs: symlinks are stored in the database as first-class objects,
+ ### rather than in the filesystem as "special" regular files. thus,
+ ### all portability concerns are moot. higher-levels can figure out
+ ### how to represent the link in ACTUAL. higher-levels can also
+ ### deal with translating to/from the svn:special property and
+ ### the plain-text file contents.
+ ### dlr: What about hard links? At minimum, mention in doc string.
+*/
+svn_error_t *
+svn_wc__db_base_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *target,
+ apr_hash_t *dav_cache,
+ svn_boolean_t delete_working,
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
+ svn_boolean_t keep_recorded_info,
+ svn_boolean_t insert_base_deleted,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Create a node in the BASE tree that is present in name only.
+
+ The new node will be located at LOCAL_ABSPATH, and correspond to the
+ repository node described by <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID>
+ at revision REVISION.
+
+ The node's kind is described by KIND, and the reason for its absence
+ is specified by STATUS. Only these values are allowed for STATUS:
+
+ svn_wc__db_status_server_excluded
+ svn_wc__db_status_excluded
+
+ If CONFLICT is not NULL, then it describes a conflict for this node. The
+ node will be record as conflicted (in ACTUAL).
+
+ Any work items that are necessary as part of this node construction may
+ be passed in WORK_ITEMS.
+
+ All temporary allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_add_excluded_node(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ svn_wc__db_status_t status,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Create a node in the BASE tree that is present in name only.
+
+ The new node will be located at LOCAL_ABSPATH, and correspond to the
+ repository node described by <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID>
+ at revision REVISION.
+
+ The node's kind is described by KIND, and the reason for its absence
+ is 'svn_wc__db_status_not_present'.
+
+ If CONFLICT is not NULL, then it describes a conflict for this node. The
+ node will be record as conflicted (in ACTUAL).
+
+ Any work items that are necessary as part of this node construction may
+ be passed in WORK_ITEMS.
+
+ All temporary allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_add_not_present_node(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+ svn_node_kind_t kind,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Remove a node and all its descendants from the BASE tree. This handles
+ the deletion of a tree from the update editor and some file external
+ scenarios.
+
+ The node to remove is indicated by LOCAL_ABSPATH from the local
+ filesystem.
+
+ This operation *installs* workqueue operations to update the local
+ filesystem after the database operation.
+
+ To maintain a consistent database this function will also remove
+ any working node that marks LOCAL_ABSPATH as base-deleted. If this
+ results in there being no working node for LOCAL_ABSPATH then any
+ actual node will be removed if the actual node does not mark a
+ conflict.
+
+ If KEEP_AS_WORKING is TRUE, then the base tree is copied to higher
+ layers as a copy of itself before deleting the BASE nodes.
+
+ If KEEP_AS_WORKING is FALSE, and QUEUE_DELETES is TRUE, also queue
+ workqueue items to delete all in-wc representations that aren't
+ shadowed by higher layers.
+ (With KEEP_AS_WORKING TRUE, this is a no-op, as everything is
+ automatically shadowed by the created copy)
+
+ If NOT_PRESENT_REVISION specifies a valid revision a not-present
+ node is installed in BASE node with kind NOT_PRESENT_KIND after
+ deleting.
+
+ If CONFLICT and/or WORK_ITEMS are passed they are installed as part
+ of the operation, after the work items inserted by the operation
+ itself.
+*/
+svn_error_t *
+svn_wc__db_base_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t keep_as_working,
+ svn_boolean_t queue_deletes,
+ svn_revnum_t not_present_revision,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Retrieve information about a node in the BASE tree.
+
+ For the BASE node implied by LOCAL_ABSPATH from the local filesystem,
+ return information in the provided OUT parameters. Each OUT parameter
+ may be NULL, indicating that specific item is not requested.
+
+ If there is no information about this node, then SVN_ERR_WC_PATH_NOT_FOUND
+ will be returned.
+
+ The OUT parameters, and their "not available" values are:
+ STATUS n/a (always available)
+ KIND n/a (always available)
+ REVISION SVN_INVALID_REVNUM
+ REPOS_RELPATH NULL (caller should scan up)
+ REPOS_ROOT_URL NULL (caller should scan up)
+ REPOS_UUID NULL (caller should scan up)
+ CHANGED_REV SVN_INVALID_REVNUM
+ CHANGED_DATE 0
+ CHANGED_AUTHOR NULL
+ DEPTH svn_depth_unknown
+ CHECKSUM NULL
+ TARGET NULL
+ LOCK NULL
+
+ HAD_PROPS FALSE
+ PROPS NULL
+
+ UPDATE_ROOT FALSE
+
+ If the STATUS is normal, the REPOS_* values will be non-NULL.
+
+ If DEPTH is requested, and the node is NOT a directory, then the
+ value will be set to svn_depth_unknown. If LOCAL_ABSPATH is a link,
+ it's up to the caller to resolve depth for the link's target.
+
+ If CHECKSUM is requested, and the node is NOT a file, then it will
+ be set to NULL.
+
+ If TARGET is requested, and the node is NOT a symlink, then it will
+ be set to NULL.
+
+ *PROPS maps "const char *" names to "const svn_string_t *" values. If
+ the base node is capable of having properties but has none, set
+ *PROPS to an empty hash. If its status is such that it cannot have
+ properties, set *PROPS to NULL.
+
+ If UPDATE_ROOT is requested, set it to TRUE if the node should only
+ be updated when it is the root of an update (e.g. file externals).
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_get_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_wc__db_lock_t **lock,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_boolean_t *update_root,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Structure returned by svn_wc__db_base_get_children_info. Only has the
+ fields needed by the adm crawler. */
+struct svn_wc__db_base_info_t {
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_revnum_t revnum;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ svn_depth_t depth;
+ svn_boolean_t update_root;
+ svn_wc__db_lock_t *lock;
+};
+
+/* Return in *NODES a hash mapping name->struct svn_wc__db_base_info_t for
+ the children of DIR_ABSPATH at op_depth 0.
+ */
+svn_error_t *
+svn_wc__db_base_get_children_info(apr_hash_t **nodes,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *PROPS to the properties of the node LOCAL_ABSPATH in the BASE tree.
+
+ *PROPS maps "const char *" names to "const svn_string_t *" values.
+ If the node has no properties, set *PROPS to an empty hash.
+ *PROPS will never be set to NULL.
+ If the node is not present in the BASE tree (with presence 'normal'
+ or 'incomplete'), return an error.
+ Allocate *PROPS and its keys and values in RESULT_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_get_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Return a list of the BASE tree node's children's names.
+
+ For the node indicated by LOCAL_ABSPATH, this function will return
+ the names of all of its children in the array CHILDREN. The array
+ elements are const char * values.
+
+ If the node is not a directory, then SVN_ERR_WC_NOT_WORKING_COPY will
+ be returned.
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_base_get_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set the dav cache for LOCAL_ABSPATH to PROPS. Use SCRATCH_POOL for
+ temporary allocations. */
+svn_error_t *
+svn_wc__db_base_set_dav_cache(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ apr_pool_t *scratch_pool);
+
+
+/* Retrieve the dav cache for LOCAL_ABSPATH into *PROPS, allocated in
+ RESULT_POOL. Use SCRATCH_POOL for temporary allocations. Return
+ SVN_ERR_WC_PATH_NOT_FOUND if no dav cache can be located for
+ LOCAL_ABSPATH in DB. */
+svn_error_t *
+svn_wc__db_base_get_dav_cache(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Recursively clear the dav cache for LOCAL_ABSPATH. Use
+ SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_wc__db_base_clear_dav_cache_recursive(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Set LOCK_TOKENS to a hash mapping const char * full URLs to const char *
+ * lock tokens for every base node at or under LOCAL_ABSPATH in DB which has
+ * such a lock token set on it.
+ * Allocate the hash and all items therein from RESULT_POOL. */
+svn_error_t *
+svn_wc__db_base_get_lock_tokens_recursive(apr_hash_t **lock_tokens,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* ### anything else needed for maintaining the BASE tree? */
+
+
+/* @} */
+
+/* @defgroup svn_wc__db_pristine Pristine ("text base") management
+ @{
+*/
+
+/* Set *PRISTINE_ABSPATH to the path to the pristine text file
+ identified by SHA1_CHECKSUM. Error if it does not exist.
+
+ ### This is temporary - callers should not be looking at the file
+ directly.
+
+ Allocate the path in RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pristine_get_path(const char **pristine_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *PRISTINE_ABSPATH to the path under WCROOT_ABSPATH that will be
+ used by the pristine text identified by SHA1_CHECKSUM. The file
+ need not exist.
+ */
+svn_error_t *
+svn_wc__db_pristine_get_future_path(const char **pristine_abspath,
+ const char *wcroot_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* If requested set *CONTENTS to a readable stream that will yield the pristine
+ text identified by SHA1_CHECKSUM (must be a SHA-1 checksum) within the WC
+ identified by WRI_ABSPATH in DB.
+
+ If requested set *SIZE to the size of the pristine stream in bytes,
+
+ Even if the pristine text is removed from the store while it is being
+ read, the stream will remain valid and readable until it is closed.
+
+ Allocate the stream in RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pristine_read(svn_stream_t **contents,
+ svn_filesize_t *size,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *TEMP_DIR_ABSPATH to a directory in which the caller should create
+ a uniquely named file for later installation as a pristine text file.
+
+ The directory is guaranteed to be one that svn_wc__db_pristine_install()
+ can use: specifically, one from which it can atomically move the file.
+
+ Allocate *TEMP_DIR_ABSPATH in RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pristine_get_tempdir(const char **temp_dir_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Install the file TEMPFILE_ABSPATH (which is sitting in a directory given by
+ svn_wc__db_pristine_get_tempdir()) into the pristine data store, to be
+ identified by the SHA-1 checksum of its contents, SHA1_CHECKSUM, and whose
+ MD-5 checksum is MD5_CHECKSUM. */
+svn_error_t *
+svn_wc__db_pristine_install(svn_wc__db_t *db,
+ const char *tempfile_abspath,
+ const svn_checksum_t *sha1_checksum,
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *MD5_CHECKSUM to the MD-5 checksum of a pristine text
+ identified by its SHA-1 checksum SHA1_CHECKSUM. Return an error
+ if the pristine text does not exist or its MD5 checksum is not found.
+
+ Allocate *MD5_CHECKSUM in RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pristine_get_md5(const svn_checksum_t **md5_checksum,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *SHA1_CHECKSUM to the SHA-1 checksum of a pristine text
+ identified by its MD-5 checksum MD5_CHECKSUM. Return an error
+ if the pristine text does not exist or its SHA-1 checksum is not found.
+
+ Note: The MD-5 checksum is not strictly guaranteed to be unique in the
+ database table, although duplicates are expected to be extremely rare.
+ ### TODO: The behaviour is currently unspecified if the MD-5 checksum is
+ not unique. Need to see whether this function is going to stay in use,
+ and, if so, address this somehow.
+
+ Allocate *SHA1_CHECKSUM in RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pristine_get_sha1(const svn_checksum_t **sha1_checksum,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* If necessary transfers the PRISTINE files of the tree rooted at
+ SRC_LOCAL_ABSPATH to the working copy identified by DST_WRI_ABSPATH. */
+svn_error_t *
+svn_wc__db_pristine_transfer(svn_wc__db_t *db,
+ const char *src_local_abspath,
+ const char *dst_wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Remove the pristine text with SHA-1 checksum SHA1_CHECKSUM from the
+ * pristine store, iff it is not referenced by any of the (other) WC DB
+ * tables. */
+svn_error_t *
+svn_wc__db_pristine_remove(svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool);
+
+
+/* Remove all unreferenced pristines in the WC of WRI_ABSPATH in DB. */
+svn_error_t *
+svn_wc__db_pristine_cleanup(svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *PRESENT to true if the pristine store for WRI_ABSPATH in DB contains
+ a pristine text with SHA-1 checksum SHA1_CHECKSUM, and to false otherwise.
+*/
+svn_error_t *
+svn_wc__db_pristine_check(svn_boolean_t *present,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool);
+
+/* @defgroup svn_wc__db_external External management
+ @{ */
+
+/* Adds (or overwrites) a file external LOCAL_ABSPATH to the working copy
+ identified by WRI_ABSPATH.
+
+ It updates both EXTERNALS and NODES in one atomic step.
+ */
+svn_error_t *
+svn_wc__db_external_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+
+ const apr_hash_t *props,
+ apr_array_header_t *iprops,
+
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+
+ const svn_checksum_t *checksum,
+
+ const apr_hash_t *dav_cache,
+
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+
+ svn_boolean_t keep_recorded_info,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Adds (or overwrites) a symlink external LOCAL_ABSPATH to the working copy
+ identified by WRI_ABSPATH.
+ */
+svn_error_t *
+svn_wc__db_external_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t revision,
+
+ const apr_hash_t *props,
+
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+
+ const char *target,
+
+ const apr_hash_t *dav_cache,
+
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+
+ svn_boolean_t update_actual_props,
+ apr_hash_t *new_actual_props,
+
+ svn_boolean_t keep_recorded_info,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Adds (or overwrites) a directory external LOCAL_ABSPATH to the working copy
+ identified by WRI_ABSPATH.
+
+ Directory externals are stored in their own working copy, so one should use
+ the normal svn_wc__db functions to access the normal working copy
+ information.
+ */
+svn_error_t *
+svn_wc__db_external_add_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+
+ const char *repos_root_url,
+ const char *repos_uuid,
+
+ const char *record_ancestor_abspath,
+ const char *recorded_repos_relpath,
+ svn_revnum_t recorded_peg_revision,
+ svn_revnum_t recorded_revision,
+
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Remove a registered external LOCAL_ABSPATH from the working copy identified
+ by WRI_ABSPATH.
+ */
+svn_error_t *
+svn_wc__db_external_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Reads information on the external LOCAL_ABSPATH as stored in the working
+ copy identified with WRI_ABSPATH (If NULL the parent directory of
+ LOCAL_ABSPATH is taken as WRI_ABSPATH).
+
+ Return SVN_ERR_WC_PATH_NOT_FOUND if LOCAL_ABSPATH is not an external in
+ this working copy.
+
+ When STATUS is requested it has one of these values
+ svn_wc__db_status_normal The external is available
+ svn_wc__db_status_excluded The external is user excluded
+
+ When KIND is requested then the value will be set to the kind of external.
+
+ If DEFINING_ABSPATH is requested, then the value will be set to the
+ absolute path of the directory which originally defined the external.
+ (The path with the svn:externals property)
+
+ If REPOS_ROOT_URL is requested, then the value will be set to the
+ repository root of the external.
+
+ If REPOS_UUID is requested, then the value will be set to the
+ repository uuid of the external.
+
+ If RECORDED_REPOS_RELPATH is requested, then the value will be set to the
+ original repository relative path inside REPOS_ROOT_URL of the external.
+
+ If RECORDED_PEG_REVISION is requested, then the value will be set to the
+ original recorded operational (peg) revision of the external.
+
+ If RECORDED_REVISION is requested, then the value will be set to the
+ original recorded revision of the external.
+
+ Allocate the result in RESULT_POOL and perform temporary allocations in
+ SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__db_external_read(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ const char **defining_abspath,
+
+ const char **repos_root_url,
+ const char **repos_uuid,
+
+ const char **recorded_repos_relpath,
+ svn_revnum_t *recorded_peg_revision,
+ svn_revnum_t *recorded_revision,
+
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Return in *EXTERNALS a list of svn_wc__committable_external_info_t *
+ * containing info on externals defined to be checked out below LOCAL_ABSPATH,
+ * returning only those externals that are not fixed to a specific revision.
+ *
+ * If IMMEDIATES_ONLY is TRUE, only those externals defined to be checked out
+ * as immediate children of LOCAL_ABSPATH are returned (this is useful for
+ * treating user requested depth < infinity).
+ *
+ * If there are no externals to be returned, set *EXTERNALS to NULL. Otherwise
+ * set *EXTERNALS to an APR array newly cleated in RESULT_POOL.
+ *
+ * NOTE: This only returns the externals known by the immediate WC root for
+ * LOCAL_ABSPATH; i.e.:
+ * - If there is a further parent WC "above" the immediate WC root, and if
+ * that parent WC defines externals to live somewhere within this WC, these
+ * externals will appear to be foreign/unversioned and won't be picked up.
+ * - Likewise, only the topmost level of externals nestings (externals
+ * defined within a checked out external dir) is picked up by this function.
+ * (For recursion, see svn_wc__committable_externals_below().)
+ *
+ * ###TODO: Add a WRI_ABSPATH (wc root indicator) separate from LOCAL_ABSPATH,
+ * to allow searching any wc-root for externals under LOCAL_ABSPATH, not only
+ * LOCAL_ABSPATH's most immediate wc-root. */
+svn_error_t *
+svn_wc__db_committable_externals_below(apr_array_header_t **externals,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t immediates_only,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gets a mapping from const char * local abspaths of externals to the const
+ char * local abspath of where they are defined for all externals defined
+ at or below LOCAL_ABSPATH.
+
+ ### Returns NULL in *EXTERNALS until we bumped to format 29.
+
+ Allocate the result in RESULT_POOL and perform temporary allocations in
+ SCRATCH_POOL. */
+svn_error_t *
+svn_wc__db_externals_defined_below(apr_hash_t **externals,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gather all svn:externals property values from the actual properties on
+ directories below LOCAL_ABSPATH as a mapping of const char *local_abspath
+ to const char * property values.
+
+ If DEPTHS is not NULL, set *depths to an apr_hash_t* mapping the same
+ local_abspaths to the const char * ambient depth of the node.
+
+ Allocate the result in RESULT_POOL and perform temporary allocations in
+ SCRATCH_POOL. */
+svn_error_t *
+svn_wc__db_externals_gather_definitions(apr_hash_t **externals,
+ apr_hash_t **depths,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* @} */
+
+/* @defgroup svn_wc__db_op Operations on WORKING tree
+ @{
+*/
+
+/* Copy the node at SRC_ABSPATH (in NODES and ACTUAL_NODE tables) to
+ * DST_ABSPATH, both in DB but not necessarily in the same WC. The parent
+ * of DST_ABSPATH must be a versioned directory.
+ *
+ * This copy is NOT recursive. It simply establishes this one node, plus
+ * incomplete nodes for the children.
+ *
+ * If IS_MOVE is TRUE, mark this copy operation as the copy-half of
+ * a move. The delete-half of the move needs to be created separately
+ * with svn_wc__db_op_delete().
+ *
+ * Add WORK_ITEMS to the work queue. */
+svn_error_t *
+svn_wc__db_op_copy(svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *dst_abspath,
+ const char *dst_op_root_abspath,
+ svn_boolean_t is_move,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Checks if LOCAL_ABSPATH represents a move back to its original location,
+ * and if it is reverts the move while keeping local changes after it has been
+ * moved from MOVED_FROM_ABSPATH.
+ *
+ * If MOVED_BACK is not NULL, set *MOVED_BACK to TRUE when a move was reverted,
+ * otherwise to FALSE.
+ */
+svn_error_t *
+svn_wc__db_op_handle_move_back(svn_boolean_t *moved_back,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *moved_from_abspath,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Copy the leaves of the op_depth layer directly shadowed by the operation
+ * of SRC_ABSPATH (so SRC_ABSPATH must be an op_root) to dst_abspaths
+ * parents layer.
+ *
+ * This operation is recursive. It copies all the descendants at the lower
+ * layer and adds base-deleted nodes on dst_abspath layer to mark these nodes
+ * properly deleted.
+ *
+ * Usually this operation is directly followed by a call to svn_wc__db_op_copy
+ * which performs the real copy from src_abspath to dst_abspath.
+ */
+svn_error_t *
+svn_wc__db_op_copy_shadowed_layer(svn_wc__db_t *db,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t is_move,
+ apr_pool_t *scratch_pool);
+
+
+/* Record a copy at LOCAL_ABSPATH from a repository directory.
+
+ This copy is NOT recursive. It simply establishes this one node.
+ CHILDREN must be provided, and incomplete nodes will be constructed
+ for them.
+
+ ### arguments docco. */
+svn_error_t *
+svn_wc__db_op_copy_dir(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const apr_array_header_t *children,
+ svn_boolean_t is_move,
+ svn_depth_t depth,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Record a copy at LOCAL_ABSPATH from a repository file.
+
+ ### arguments docco. */
+svn_error_t *
+svn_wc__db_op_copy_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const svn_checksum_t *checksum,
+ svn_boolean_t update_actual_props,
+ const apr_hash_t *new_actual_props,
+ svn_boolean_t is_move,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+svn_error_t *
+svn_wc__db_op_copy_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ svn_revnum_t changed_rev,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const char *original_repos_relpath,
+ const char *original_root_url,
+ const char *original_uuid,
+ svn_revnum_t original_revision,
+ const char *target,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* ### do we need svn_wc__db_op_copy_server_excluded() ?? */
+
+
+/* ### add a new versioned directory. a list of children is NOT passed
+ ### since they are added in future, distinct calls to db_op_add_*.
+ PROPS gives the properties; empty or NULL means none. */
+/* ### do we need a CONFLICTS param? */
+svn_error_t *
+svn_wc__db_op_add_directory(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Add a file.
+ PROPS gives the properties; empty or NULL means none.
+ ### this file has no "pristine"
+ ### contents, so a checksum [reference] is not required. */
+/* ### do we need a CONFLICTS param? */
+svn_error_t *
+svn_wc__db_op_add_file(svn_wc__db_t *db,
+ const char *local_abspath,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Add a symlink.
+ PROPS gives the properties; empty or NULL means none. */
+/* ### do we need a CONFLICTS param? */
+svn_error_t *
+svn_wc__db_op_add_symlink(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *target,
+ const apr_hash_t *props,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Set the properties of the node LOCAL_ABSPATH in the ACTUAL tree to
+ PROPS.
+
+ PROPS maps "const char *" names to "const svn_string_t *" values.
+ To specify no properties, PROPS must be an empty hash, not NULL.
+ If the node is not present, return an error.
+
+ If PROPS is NULL, set the properties to be the same as the pristine
+ properties.
+
+ If CONFLICT is not NULL, it is used to register a conflict on this
+ node at the same time the properties are changed.
+
+ WORK_ITEMS are inserted into the work queue, as additional things that
+ need to be completed before the working copy is stable.
+
+
+ If CLEAR_RECORDED_INFO is true, the recorded information for the node
+ is cleared. (commonly used when updating svn:* magic properties).
+
+ NOTE: This will overwrite ALL working properties the node currently
+ has. There is no db_op_set_prop() function. Callers must read all the
+ properties, change one, and write all the properties.
+ ### ugh. this has poor transaction semantics...
+
+
+ NOTE: This will create an entry in the ACTUAL table for the node if it
+ does not yet have one.
+*/
+svn_error_t *
+svn_wc__db_op_set_props(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_hash_t *props,
+ svn_boolean_t clear_recorded_info,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Mark LOCAL_ABSPATH, and all children, for deletion.
+ *
+ * This function removes the file externals (and if DELETE_DIR_EXTERNALS is
+ * TRUE also the directory externals) registered below LOCAL_ABSPATH.
+ * (DELETE_DIR_EXTERNALS should be true if also removing unversioned nodes)
+ *
+ * If MOVED_TO_ABSPATH is not NULL, mark the deletion of LOCAL_ABSPATH
+ * as the delete-half of a move from LOCAL_ABSPATH to MOVED_TO_ABSPATH.
+ *
+ * If NOTIFY_FUNC is not NULL, then it will be called (with NOTIFY_BATON)
+ * for each node deleted. While this processing occurs, if CANCEL_FUNC is
+ * not NULL, then it will be called (with CANCEL_BATON) to detect cancellation
+ * during the processing.
+ *
+ * Note: the notification (and cancellation) occur outside of a SQLite
+ * transaction.
+ */
+svn_error_t *
+svn_wc__db_op_delete(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *moved_to_abspath,
+ svn_boolean_t delete_dir_externals,
+ svn_skel_t *conflict,
+ svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Mark all LOCAL_ABSPATH in the TARGETS array, and all of their children,
+ * for deletion.
+ *
+ * This function is more efficient than svn_wc__db_op_delete() because
+ * only one sqlite transaction is used for all targets.
+ * It currently lacks support for moves (though this could be changed,
+ * at which point svn_wc__db_op_delete() becomes redundant).
+ *
+ * This function removes the file externals (and if DELETE_DIR_EXTERNALS is
+ * TRUE also the directory externals) registered below the targets.
+ * (DELETE_DIR_EXTERNALS should be true if also removing unversioned nodes)
+ *
+ * If NOTIFY_FUNC is not NULL, then it will be called (with NOTIFY_BATON)
+ * for each node deleted. While this processing occurs, if CANCEL_FUNC is
+ * not NULL, then it will be called (with CANCEL_BATON) to detect cancellation
+ * during the processing.
+ *
+ * Note: the notification (and cancellation) occur outside of a SQLite
+ * transaction.
+ */
+svn_error_t *
+svn_wc__db_op_delete_many(svn_wc__db_t *db,
+ apr_array_header_t *targets,
+ svn_boolean_t delete_dir_externals,
+ const svn_skel_t *conflict,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* ### mark PATH as (possibly) modified. "svn edit" ... right API here? */
+svn_error_t *
+svn_wc__db_op_modified(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* ### use NULL to remove from a changelist.
+
+ ### NOTE: only depth=svn_depth_empty is supported right now.
+ */
+svn_error_t *
+svn_wc__db_op_set_changelist(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *new_changelist,
+ const apr_array_header_t *changelist_filter,
+ svn_depth_t depth,
+ /* ### flip to CANCEL, then NOTIFY. precedent. */
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Record CONFLICT on LOCAL_ABSPATH, potentially replacing other conflicts
+ recorded on LOCAL_ABSPATH.
+
+ Users should in most cases pass CONFLICT to another WC_DB call instead of
+ calling svn_wc__db_op_mark_conflict() directly outside a transaction, to
+ allow recording atomically with the operation involved.
+
+ Any work items that are necessary as part of marking this node conflicted
+ can be passed in WORK_ITEMS.
+ */
+svn_error_t *
+svn_wc__db_op_mark_conflict(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* ### caller maintains ACTUAL, and how the resolution occurred. we're just
+ ### recording state.
+ ###
+ ### I'm not sure that these three values are the best way to do this,
+ ### but they're handy for now. */
+svn_error_t *
+svn_wc__db_op_mark_resolved(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t resolved_text,
+ svn_boolean_t resolved_props,
+ svn_boolean_t resolved_tree,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Revert all local changes which are being maintained in the database,
+ * including conflict storage, properties and text modification status.
+ *
+ * Returns SVN_ERR_WC_INVALID_OPERATION_DEPTH if the revert is not
+ * possible, e.g. copy/delete but not a root, or a copy root with
+ * children.
+ *
+ * At present only depth=empty and depth=infinity are supported.
+ *
+ * This function populates the revert list that can be queried to
+ * determine what was reverted.
+ */
+svn_error_t *
+svn_wc__db_op_revert(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Query the revert list for LOCAL_ABSPATH and set *REVERTED if the
+ * path was reverted. Set *MARKER_FILES to a const char *list of
+ * marker files if any were recorded on LOCAL_ABSPATH.
+ *
+ * Set *COPIED_HERE if the reverted node was copied here and is the
+ * operation root of the copy.
+ * Set *KIND to the node kind of the reverted node.
+ *
+ * Removes the row for LOCAL_ABSPATH from the revert list.
+ */
+svn_error_t *
+svn_wc__db_revert_list_read(svn_boolean_t *reverted,
+ const apr_array_header_t **marker_files,
+ svn_boolean_t *copied_here,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* The type of elements in the array returned by
+ * svn_wc__db_revert_list_read_copied_children(). */
+typedef struct svn_wc__db_revert_list_copied_child_info_t {
+ const char *abspath;
+ svn_node_kind_t kind;
+} svn_wc__db_revert_list_copied_child_info_t ;
+
+/* Return in *CHILDREN a list of reverted copied nodes at or within
+ * LOCAL_ABSPATH (which is a reverted file or a reverted directory).
+ * Allocate *COPIED_CHILDREN and its elements in RESULT_POOL.
+ * The elements are of type svn_wc__db_revert_list_copied_child_info_t. */
+svn_error_t *
+svn_wc__db_revert_list_read_copied_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Make revert notifications for all paths in the revert list that are
+ * equal to LOCAL_ABSPATH or below LOCAL_ABSPATH.
+ *
+ * Removes all the corresponding rows from the revert list.
+ *
+ * ### Pass in cancel_func?
+ */
+svn_error_t *
+svn_wc__db_revert_list_notify(svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Clean up after svn_wc__db_op_revert by removing the revert list.
+ */
+svn_error_t *
+svn_wc__db_revert_list_done(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* ### status */
+
+
+/* @} */
+
+/* @defgroup svn_wc__db_read Read operations on the BASE/WORKING tree
+ @{
+
+ These functions query information about nodes in ACTUAL, and returns
+ the requested information from the appropriate ACTUAL, WORKING, or
+ BASE tree.
+
+ For example, asking for the checksum of the pristine version will
+ return the one recorded in WORKING, or if no WORKING node exists, then
+ the checksum comes from BASE.
+*/
+
+/* Retrieve information about a node.
+
+ For the node implied by LOCAL_ABSPATH from the local filesystem, return
+ information in the provided OUT parameters. Each OUT parameter may be
+ NULL, indicating that specific item is not requested.
+
+ The information returned comes from the BASE tree, as possibly modified
+ by the WORKING and ACTUAL trees.
+
+ If there is no information about the node, then SVN_ERR_WC_PATH_NOT_FOUND
+ will be returned.
+
+ The OUT parameters, and their "not available" values are:
+ STATUS n/a (always available)
+ KIND svn_node_unknown (For ACTUAL only nodes)
+ REVISION SVN_INVALID_REVNUM
+ REPOS_RELPATH NULL
+ REPOS_ROOT_URL NULL
+ REPOS_UUID NULL
+ CHANGED_REV SVN_INVALID_REVNUM
+ CHANGED_DATE 0
+ CHANGED_AUTHOR NULL
+ DEPTH svn_depth_unknown
+ CHECKSUM NULL
+ TARGET NULL
+
+ ORIGINAL_REPOS_RELPATH NULL
+ ORIGINAL_ROOT_URL NULL
+ ORIGINAL_UUID NULL
+ ORIGINAL_REVISION SVN_INVALID_REVNUM
+
+ LOCK NULL
+
+ RECORDED_SIZE SVN_INVALID_FILESIZE
+ RECORDED_TIME 0
+
+ CHANGELIST NULL
+ CONFLICTED FALSE
+
+ OP_ROOT FALSE
+ HAD_PROPS FALSE
+ PROPS_MOD FALSE
+
+ HAVE_BASE FALSE
+ HAVE_MORE_WORK FALSE
+ HAVE_WORK FALSE
+
+ When STATUS is requested, then it will be one of these values:
+
+ svn_wc__db_status_normal
+ A plain BASE node, with no local changes.
+
+ svn_wc__db_status_added
+ A node has been added/copied/moved to here. See HAVE_BASE to see
+ if this change overwrites a BASE node. Use scan_addition() to resolve
+ whether this has been added, copied, or moved, and the details of the
+ operation (this function only looks at LOCAL_ABSPATH, but resolving
+ the details requires scanning one or more ancestor nodes).
+
+ svn_wc__db_status_deleted
+ This node has been deleted or moved away. It may be a delete/move of
+ a BASE node, or a child node of a subtree that was copied/moved to
+ an ancestor location. Call scan_deletion() to determine the full
+ details of the operations upon this node.
+
+ svn_wc__db_status_server_excluded
+ The node is versioned/known by the server, but the server has
+ decided not to provide further information about the node. This
+ is a BASE node (since changes are not allowed to this node).
+
+ svn_wc__db_status_excluded
+ The node has been excluded from the working copy tree. This may
+ be an exclusion from the BASE tree, or an exclusion in the
+ WORKING tree for a child node of a copied/moved parent.
+
+ svn_wc__db_status_not_present
+ This is a node from the BASE tree, has been marked as "not-present"
+ within this mixed-revision working copy. This node is at a revision
+ that is not in the tree, contrary to its inclusion in the parent
+ node's revision.
+
+ svn_wc__db_status_incomplete
+ The BASE is incomplete due to an interrupted operation. An
+ incomplete WORKING node will be svn_wc__db_status_added.
+
+ If REVISION is requested, it will be set to the revision of the
+ unmodified (BASE) node, or to SVN_INVALID_REVNUM if any structural
+ changes have been made to that node (that is, if the node has a row in
+ the WORKING table).
+
+ If DEPTH is requested, and the node is NOT a directory, then
+ the value will be set to svn_depth_unknown.
+
+ If CHECKSUM is requested, and the node is NOT a file, then it will
+ be set to NULL.
+
+ If TARGET is requested, and the node is NOT a symlink, then it will
+ be set to NULL.
+
+ If TRANSLATED_SIZE is requested, and the node is NOT a file, then
+ it will be set to SVN_INVALID_FILESIZE.
+
+ If HAVE_WORK is TRUE, the returned information is from the highest WORKING
+ layer. In that case HAVE_MORE_WORK and HAVE_BASE provide information about
+ what other layers exist for this node.
+
+ If HAVE_WORK is FALSE and HAVE_BASE is TRUE then the information is from
+ the BASE tree.
+
+ If HAVE_WORK and HAVE_BASE are both FALSE and when retrieving CONFLICTED,
+ then the node doesn't exist at all.
+
+ If OP_ROOT is requested and the node has a WORKING layer, OP_ROOT will be
+ set to true if this node is the op_root for this layer.
+
+ If HAD_PROPS is requested and the node has pristine props, the value will
+ be set to TRUE.
+
+ If PROPS_MOD is requested and the node has property modification the value
+ will be set to TRUE.
+
+ ### add information about the need to scan upwards to get a complete
+ ### picture of the state of this node.
+
+ ### add some documentation about OUT parameter values based on STATUS ??
+
+ ### the TEXT_MOD may become an enumerated value at some point to
+ ### indicate different states of knowledge about text modifications.
+ ### for example, an "svn edit" command in the future might set a
+ ### flag indicating administratively-defined modification. and/or we
+ ### might have a status indicating that we saw it was modified while
+ ### performing a filesystem traversal.
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+*/
+/* ### old docco. needs to be incorporated as appropriate. there is
+ ### some pending, potential changes to the definition of this API,
+ ### so not worrying about it just yet.
+
+ ### if the node has not been committed (after adding):
+ ### revision will be SVN_INVALID_REVNUM
+ ### repos_* will be NULL
+ ### changed_rev will be SVN_INVALID_REVNUM
+ ### changed_date will be 0
+ ### changed_author will be NULL
+ ### status will be svn_wc__db_status_added
+ ### text_mod will be TRUE
+ ### prop_mod will be TRUE if any props have been set
+ ### base_shadowed will be FALSE
+
+ ### if the node is not a copy, or a move destination:
+ ### original_repos_path will be NULL
+ ### original_root_url will be NULL
+ ### original_uuid will be NULL
+ ### original_revision will be SVN_INVALID_REVNUM
+
+ ### note that @a base_shadowed can be derived. if the status specifies
+ ### an add/copy/move *and* there is a corresponding node in BASE, then
+ ### the BASE has been deleted to open the way for this node.
+*/
+svn_error_t *
+svn_wc__db_read_info(svn_wc__db_status_t *status, /* ### derived */
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth, /* dirs only */
+ const svn_checksum_t **checksum, /* files only */
+ const char **target, /* symlinks only */
+
+ /* ### the following fields if copied/moved (history) */
+ const char **original_repos_relpath,
+ const char **original_root_url,
+ const char **original_uuid,
+ svn_revnum_t *original_revision,
+
+ /* For BASE nodes */
+ svn_wc__db_lock_t **lock,
+
+ /* Recorded for files present in the working copy */
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_time,
+
+ /* From ACTUAL */
+ const char **changelist,
+ svn_boolean_t *conflicted,
+
+ /* ### the followed are derived fields */
+ svn_boolean_t *op_root,
+
+ svn_boolean_t *had_props,
+ svn_boolean_t *props_mod,
+
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Structure returned by svn_wc__db_read_children_info. Only has the
+ fields needed by status. */
+struct svn_wc__db_info_t {
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_revnum_t revnum;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ svn_revnum_t changed_rev;
+ const char *changed_author;
+ apr_time_t changed_date;
+ svn_depth_t depth;
+
+ svn_filesize_t recorded_size;
+ apr_time_t recorded_time;
+
+ const char *changelist;
+ svn_boolean_t conflicted;
+#ifdef HAVE_SYMLINK
+ svn_boolean_t special;
+#endif
+ svn_boolean_t op_root;
+
+ svn_boolean_t has_checksum;
+ svn_boolean_t copied;
+ svn_boolean_t had_props;
+ svn_boolean_t props_mod;
+
+ svn_boolean_t have_base;
+ svn_boolean_t have_more_work;
+
+ svn_boolean_t locked; /* WC directory lock */
+ svn_wc__db_lock_t *lock; /* Repository file lock */
+ svn_boolean_t incomplete; /* TRUE if a working node is incomplete */
+
+ const char *moved_to_abspath; /* Only on op-roots. See svn_wc_status3_t. */
+ svn_boolean_t moved_here; /* Only on op-roots. */
+
+ svn_boolean_t file_external;
+};
+
+/* Return in *NODES a hash mapping name->struct svn_wc__db_info_t for
+ the children of DIR_ABSPATH, and in *CONFLICTS a hash of names in
+ conflict.
+
+ The results include any path that was a child of a deleted directory that
+ existed at LOCAL_ABSPATH, even if that directory is now scheduled to be
+ replaced by the working node at LOCAL_ABSPATH.
+ */
+svn_error_t *
+svn_wc__db_read_children_info(apr_hash_t **nodes,
+ apr_hash_t **conflicts,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Structure returned by svn_wc__db_read_walker_info. Only has the
+ fields needed by svn_wc__internal_walk_children(). */
+struct svn_wc__db_walker_info_t {
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+};
+
+/* When a node is deleted in WORKING, some of its information is no longer
+ available. But in some cases it might still be relevant to obtain this
+ information even when the information isn't stored in the BASE tree.
+
+ This function allows access to that specific information.
+
+ When a node is not deleted, this node returns the same information
+ as svn_wc__db_read_info().
+
+ All output arguments are optional and behave in the same way as when
+ calling svn_wc__db_read_info().
+
+ (All other information (like original_*) can be obtained via other apis).
+
+ *PROPS maps "const char *" names to "const svn_string_t *" values. If
+ the pristine node is capable of having properties but has none, set
+ *PROPS to an empty hash. If its status is such that it cannot have
+ properties, set *PROPS to NULL.
+ */
+svn_error_t *
+svn_wc__db_read_pristine_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth, /* dirs only */
+ const svn_checksum_t **checksum, /* files only */
+ const char **target, /* symlinks only */
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gets the information required to install a pristine file to the working copy
+
+ Set WCROOT_ABSPATH to the working copy root, SHA1_CHECKSUM to the
+ checksum of the node (a valid reference into the pristine store)
+ and PRISTINE_PROPS to the node's pristine properties (to use for
+ installing the file).
+
+ If WRI_ABSPATH is not NULL, check for information in the working copy
+ identified by WRI_ABSPATH.
+ */
+svn_error_t *
+svn_wc__db_read_node_install_info(const char **wcroot_abspath,
+ const svn_checksum_t **sha1_checksum,
+ apr_hash_t **pristine_props,
+ apr_time_t *changed_date,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Return in *NODES a hash mapping name->struct svn_wc__db_walker_info_t for
+ the children of DIR_ABSPATH. "name" is the child's name relative to
+ DIR_ABSPATH, not an absolute path. */
+svn_error_t *
+svn_wc__db_read_children_walker_info(apr_hash_t **nodes,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Set *URL to the corresponding url for LOCAL_ABSPATH.
+ * If the node is added, return the url it will have in the repository.
+ */
+svn_error_t *
+svn_wc__db_read_url(const char **url,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *PROPS to the properties of the node LOCAL_ABSPATH in the ACTUAL
+ tree (looking through to the WORKING or BASE tree as required).
+
+ ### *PROPS will be set to NULL in the following situations:
+ ### ... tbd
+
+ PROPS maps "const char *" names to "const svn_string_t *" values.
+ If the node has no properties, set *PROPS to an empty hash.
+ If the node is not present, return an error.
+ Allocate *PROPS and its keys and values in RESULT_POOL.
+*/
+svn_error_t *
+svn_wc__db_read_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and
+ * a hash table mapping <tt>char *</tt> names onto svn_string_t *
+ * values for any properties of child nodes of LOCAL_ABSPATH (up to DEPTH).
+ *
+ * If PRISTINE is FALSE, read the properties from the WORKING layer (highest
+ * op_depth); if PRISTINE is FALSE, local modifications will be visible.
+ */
+svn_error_t *
+svn_wc__db_read_props_streamily(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t pristine,
+ const apr_array_header_t *changelists,
+ svn_wc__proplist_receiver_t receiver_func,
+ void *receiver_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *PROPS to the properties of the node LOCAL_ABSPATH in the WORKING
+ tree (looking through to the BASE tree as required).
+
+ ### *PROPS will set set to NULL in the following situations:
+ ### ... tbd. see props.c:svn_wc__get_pristine_props()
+
+ *PROPS maps "const char *" names to "const svn_string_t *" values.
+ If the node has no properties, set *PROPS to an empty hash.
+ If the node is not present, return an error.
+ Allocate *PROPS and its keys and values in RESULT_POOL.
+*/
+svn_error_t *
+svn_wc__db_read_pristine_props(apr_hash_t **props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
+ * Set @a *iprops to a depth-first ordered array of
+ * #svn_prop_inherited_item_t * structures representing the properties
+ * inherited by @a local_abspath from the ACTUAL tree above
+ * @a local_abspath (looking through to the WORKING or BASE tree as
+ * required), up to and including the root of the working copy and
+ * any cached inherited properties inherited by the root.
+ *
+ * The #svn_prop_inherited_item_t->path_or_url members of the
+ * #svn_prop_inherited_item_t * structures in @a *iprops are
+ * paths relative to the repository root URL for cached inherited
+ * properties and absolute working copy paths otherwise.
+ *
+ * If ACTUAL_PROPS is not NULL, then set *ACTUAL_PROPS to the actual
+ * properties stored on LOCAL_ABSPATH.
+ *
+ * Allocate @a *iprops in @a result_pool. Use @a scratch_pool
+ * for temporary allocations.
+ */
+svn_error_t *
+svn_wc__db_read_inherited_props(apr_array_header_t **iprops,
+ apr_hash_t **actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Read a BASE node's inherited property information.
+
+ Set *IPROPS to to a depth-first ordered array of
+ svn_prop_inherited_item_t * structures representing the cached
+ inherited properties for the BASE node at LOCAL_ABSPATH.
+
+ If no cached properties are found, then set *IPROPS to NULL.
+ If LOCAL_ABSPATH represents the root of the repository, then set
+ *IPROPS to an empty array.
+
+ Allocate *IPROPS in RESULT_POOL, use SCRATCH_POOL for temporary
+ allocations. */
+svn_error_t *
+svn_wc__db_read_cached_iprops(apr_array_header_t **iprops,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Find BASE nodes with cached inherited properties.
+
+ Set *IPROPS_PATHS to a hash mapping const char * absolute working copy
+ paths to the repos_relpath of the path for each path in the working copy
+ at or below LOCAL_ABSPATH, limited by DEPTH, that has cached inherited
+ properties for the BASE node of the path.
+
+ Allocate *IPROP_PATHS in RESULT_POOL.
+ Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_wc__db_get_children_with_cached_iprops(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Obtain a mapping of const char * local_abspaths to const svn_string_t*
+ * property values in *VALUES, of all PROPNAME properties on LOCAL_ABSPATH
+ * and its descendants.
+ *
+ * Allocate the result in RESULT_POOL, and perform temporary allocations in
+ * SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__db_prop_retrieve_recursive(apr_hash_t **values,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *CHILDREN to a new array of the (const char *) basenames of the
+ immediate children of the working node at LOCAL_ABSPATH in DB.
+
+ Return every path that refers to a child of the working node at
+ LOCAL_ABSPATH. Do not include a path just because it was a child of a
+ deleted directory that existed at LOCAL_ABSPATH if that directory is now
+ scheduled to be replaced by the working node at LOCAL_ABSPATH.
+
+ Allocate *CHILDREN in RESULT_POOL and do temporary allocations in
+ SCRATCH_POOL.
+
+ ### return some basic info for each child? e.g. kind.
+ ### maybe the data in _read_get_info should be a structure, and this
+ ### can return a struct for each one.
+ ### however: _read_get_info can say "not interested", which isn't the
+ ### case with a struct. thus, a struct requires fetching and/or
+ ### computing all info.
+*/
+svn_error_t *
+svn_wc__db_read_children_of_working_node(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc__db_read_children_of_working_node(), except also include any
+ path that was a child of a deleted directory that existed at
+ LOCAL_ABSPATH, even if that directory is now scheduled to be replaced by
+ the working node at LOCAL_ABSPATH.
+*/
+svn_error_t *
+svn_wc__db_read_children(const apr_array_header_t **children,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Read into *VICTIMS the basenames of the immediate children of
+ LOCAL_ABSPATH in DB that are conflicted.
+
+ In case of tree conflicts a victim doesn't have to be in the
+ working copy.
+
+ Allocate *VICTIMS in RESULT_POOL and do temporary allocations in
+ SCRATCH_POOL */
+/* ### This function will probably be removed. */
+svn_error_t *
+svn_wc__db_read_conflict_victims(const apr_array_header_t **victims,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Read into *MARKER_FILES the absolute paths of the marker files
+ of conflicts stored on LOCAL_ABSPATH and its immediate children in DB.
+ The on-disk files may have been deleted by the user.
+
+ Allocate *MARKER_FILES in RESULT_POOL and do temporary allocations
+ in SCRATCH_POOL */
+svn_error_t *
+svn_wc__db_get_conflict_marker_files(apr_hash_t **markers,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Read the conflict information recorded on LOCAL_ABSPATH in *CONFLICT,
+ an editable conflict skel.
+
+ If the node exists, but does not have a conflict set *CONFLICT to NULL,
+ otherwise return a SVN_ERR_WC_PATH_NOT_FOUND error.
+
+ Allocate *CONFLICTS in RESULT_POOL and do temporary allocations in
+ SCRATCH_POOL */
+svn_error_t *
+svn_wc__db_read_conflict(svn_skel_t **conflict,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Return the kind of the node in DB at LOCAL_ABSPATH. The WORKING tree will
+ be examined first, then the BASE tree. If the node is not present in either
+ tree and ALLOW_MISSING is TRUE, then svn_node_unknown is returned.
+ If the node is missing and ALLOW_MISSING is FALSE, then it will return
+ SVN_ERR_WC_PATH_NOT_FOUND.
+
+ The SHOW_HIDDEN and SHOW_DELETED flags report certain states as kind none.
+
+ When nodes have certain statee they are only reported when:
+ svn_wc__db_status_not_present when show_hidden && show_deleted
+
+ svn_wc__db_status_excluded when show_hidden
+ svn_wc__db_status_server_excluded when show_hidden
+
+ svn_wc__db_status_deleted when show_deleted
+
+ In other cases these nodes are reported with *KIND as svn_node_none.
+ (See also svn_wc_read_kind2()'s documentation)
+
+ Uses SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_wc__db_read_kind(svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t allow_missing,
+ svn_boolean_t show_deleted,
+ svn_boolean_t show_hidden,
+ apr_pool_t *scratch_pool);
+
+
+/* An analog to svn_wc__entry_is_hidden(). Set *HIDDEN to TRUE if
+ LOCAL_ABSPATH in DB "is not present, and I haven't scheduled something
+ over the top of it." */
+svn_error_t *
+svn_wc__db_node_hidden(svn_boolean_t *hidden,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Checks if a node replaces a node in a different layer. Also check if it
+ replaces a BASE (op_depth 0) node or just a node in a higher layer (a copy).
+ Finally check if this is the root of the replacement, or if the replacement
+ is initiated by the parent node.
+
+ IS_REPLACE_ROOT (if not NULL) is set to TRUE if the node is the root of a
+ replacement; otherwise to FALSE.
+
+ BASE_REPLACE (if not NULL) is set to TRUE if the node directly or indirectly
+ replaces a node in the BASE tree; otherwise to FALSE.
+
+ IS_REPLACE (if not NULL) is set to TRUE if the node directly replaces a node
+ in a lower layer; otherwise to FALSE.
+ */
+svn_error_t *
+svn_wc__db_node_check_replace(svn_boolean_t *is_replace_root,
+ svn_boolean_t *base_replace,
+ svn_boolean_t *is_replace,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* ### changelists. return an array, or an iterator interface? how big
+ ### are these things? are we okay with an in-memory array? examine other
+ ### changelist usage -- we may already assume the list fits in memory.
+*/
+
+/* The DB-private version of svn_wc__is_wcroot(), which see.
+ */
+svn_error_t *
+svn_wc__db_is_wcroot(svn_boolean_t *is_wcroot,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Check whether a node is a working copy root and/or switched.
+
+ If LOCAL_ABSPATH is the root of a working copy, set *IS_WC_ROOT to TRUE,
+ otherwise to FALSE.
+
+ If LOCAL_ABSPATH is switched against its parent in the same working copy
+ set *IS_SWITCHED to TRUE, otherwise to FALSE.
+
+ If KIND is not null, set *KIND to the node type of LOCAL_ABSPATH.
+
+ Any of the output arguments can be null to specify that the result is not
+ interesting to the caller.
+
+ Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_wc__db_is_switched(svn_boolean_t *is_wcroot,
+ svn_boolean_t *is_switched,
+ svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* @} */
+
+
+/* @defgroup svn_wc__db_global Operations that alter multiple trees
+ @{
+*/
+
+/* Associate LOCAL_DIR_ABSPATH, and all its children with the repository at
+ at REPOS_ROOT_URL. The relative path to the repos root will not change,
+ just the repository root. The repos uuid will also remain the same.
+ This also updates any locks which may exist for the node, as well as any
+ copyfrom repository information. Finally, the DAV cache (aka
+ "wcprops") will be reset for affected entries.
+
+ Use SCRATCH_POOL for any temporary allocations.
+
+ ### local_dir_abspath "should be" the wcroot or a switch root. all URLs
+ ### under this directory (depth=infinity) will be rewritten.
+
+ ### This API had a depth parameter, which was removed, should it be
+ ### resurrected? What's the purpose if we claim relocate is infinitely
+ ### recursive?
+
+ ### Assuming the future ability to copy across repositories, should we
+ ### refrain from resetting the copyfrom information in this operation?
+*/
+svn_error_t *
+svn_wc__db_global_relocate(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ const char *repos_root_url,
+ apr_pool_t *scratch_pool);
+
+
+/* ### docco
+
+ ### collapse the WORKING and ACTUAL tree changes down into BASE, called
+ for each committed node.
+
+ NEW_REVISION must be the revision number of the revision created by
+ the commit. It will become the BASE node's 'revnum' and 'changed_rev'
+ values in the BASE_NODE table.
+
+ CHANGED_REVISION is the new 'last changed' revision. If the node is
+ modified its value is equivalent to NEW_REVISION, but in case of a
+ descendant of a copy/move it can be an older revision.
+
+ CHANGED_DATE is the (server-side) date of CHANGED_REVISION. It may be 0 if
+ the revprop is missing on the revision.
+
+ CHANGED_AUTHOR is the (server-side) author of CHANGED_REVISION. It may be
+ NULL if the revprop is missing on the revision.
+
+ One or both of NEW_CHECKSUM and NEW_CHILDREN should be NULL. For new:
+ files: NEW_CHILDREN should be NULL
+ dirs: NEW_CHECKSUM should be NULL
+ symlinks: both should be NULL
+
+ WORK_ITEMS will be place into the work queue.
+*/
+svn_error_t *
+svn_wc__db_global_commit(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_revnum_t new_revision,
+ svn_revnum_t changed_revision,
+ apr_time_t changed_date,
+ const char *changed_author,
+ const svn_checksum_t *new_checksum,
+ const apr_array_header_t *new_children,
+ apr_hash_t *new_dav_cache,
+ svn_boolean_t keep_changelist,
+ svn_boolean_t no_unlock,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* ### docco
+
+ Perform an "update" operation at this node. It will create/modify a BASE
+ node, and possibly update the ACTUAL tree's node (e.g put the node into
+ a conflicted state).
+
+ ### there may be cases where we need to tweak an existing WORKING node
+
+ ### this operations on a single node, but may affect children
+
+ ### the repository cannot be changed with this function, but a "switch"
+ ### (aka changing repos_relpath) is possible
+
+ ### one of NEW_CHILDREN, NEW_CHECKSUM, or NEW_TARGET must be provided.
+ ### the other two values must be NULL.
+ ### should this be broken out into an update_(directory|file|symlink) ?
+
+ ### how does this differ from base_add_*? just the CONFLICT param.
+ ### the WORK_ITEMS param is new here, but the base_add_* functions
+ ### should probably grow that. should we instead just (re)use base_add
+ ### rather than grow a new function?
+
+ ### this does not allow a change of depth
+
+ ### we do not update a file's TRANSLATED_SIZE here. at some future point,
+ ### when the file is installed, then a TRANSLATED_SIZE will be set.
+*/
+svn_error_t *
+svn_wc__db_global_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t new_kind,
+ const char *new_repos_relpath,
+ svn_revnum_t new_revision,
+ const apr_hash_t *new_props,
+ svn_revnum_t new_changed_rev,
+ apr_time_t new_changed_date,
+ const char *new_changed_author,
+ const apr_array_header_t *new_children,
+ const svn_checksum_t *new_checksum,
+ const char *new_target,
+ const apr_hash_t *new_dav_cache,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+
+/* Modify the entry of working copy LOCAL_ABSPATH, presumably after an update
+ of depth DEPTH completes. If LOCAL_ABSPATH doesn't exist, this routine
+ does nothing.
+
+ Set the node's repository relpath, repository root, repository uuid and
+ revision to NEW_REPOS_RELPATH, NEW_REPOS_ROOT and NEW_REPOS_UUID. If
+ NEW_REPOS_RELPATH is null, the repository location is untouched; if
+ NEW_REVISION in invalid, the working revision field is untouched.
+ The modifications are mutually exclusive. If NEW_REPOS_ROOT is non-NULL,
+ set the repository root of the entry to NEW_REPOS_ROOT.
+
+ If LOCAL_ABSPATH is a directory, then, walk entries below LOCAL_ABSPATH
+ according to DEPTH thusly:
+
+ If DEPTH is svn_depth_infinity, perform the following actions on
+ every entry below PATH; if svn_depth_immediates, svn_depth_files,
+ or svn_depth_empty, perform them only on LOCAL_ABSPATH.
+
+ If NEW_REVISION is valid, then tweak every entry to have this new
+ working revision (excluding files that are scheduled for addition
+ or replacement). Likewise, if BASE_URL is non-null, then rewrite
+ all urls to be "telescoping" children of the base_url.
+
+ EXCLUDE_RELPATHS is a hash containing const char *local_relpath. Nodes
+ for pathnames contained in EXCLUDE_RELPATHS are not touched by this
+ function. These pathnames should be paths relative to the wcroot.
+
+ If WCROOT_IPROPS is not NULL it is a hash mapping const char * absolute
+ working copy paths to depth-first ordered arrays of
+ svn_prop_inherited_item_t * structures. If LOCAL_ABSPATH exists in
+ WCROOT_IPROPS, then set the hashed value as the node's inherited
+ properties.
+*/
+svn_error_t *
+svn_wc__db_op_bump_revisions_post_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ const char *new_repos_relpath,
+ const char *new_repos_root_url,
+ const char *new_repos_uuid,
+ svn_revnum_t new_revision,
+ apr_hash_t *exclude_relpaths,
+ apr_hash_t *wcroot_iprops,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Record the RECORDED_SIZE and RECORDED_TIME for a versioned node.
+
+ This function will record the information within the WORKING node,
+ if present, or within the BASE tree. If neither node is present, then
+ SVN_ERR_WC_PATH_NOT_FOUND will be returned.
+
+ RECORDED_SIZE may be SVN_INVALID_FILESIZE, which will be recorded
+ as such, implying "unknown size".
+
+ RECORDED_TIME may be 0, which will be recorded as such, implying
+ "unknown last mod time".
+*/
+svn_error_t *
+svn_wc__db_global_record_fileinfo(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_filesize_t recorded_size,
+ apr_time_t recorded_time,
+ apr_pool_t *scratch_pool);
+
+
+/* ### post-commit handling.
+ ### maybe multiple phases?
+ ### 1) mark a changelist as being-committed
+ ### 2) collect ACTUAL content, store for future use as TEXTBASE
+ ### 3) caller performs commit
+ ### 4) post-commit, integrate changelist into BASE
+*/
+
+
+/* @} */
+
+
+/* @defgroup svn_wc__db_lock Function to manage the LOCKS table.
+ @{
+*/
+
+/* Add or replace LOCK for LOCAL_ABSPATH to DB. */
+svn_error_t *
+svn_wc__db_lock_add(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_wc__db_lock_t *lock,
+ apr_pool_t *scratch_pool);
+
+
+/* Remove any lock for LOCAL_ABSPATH in DB. */
+svn_error_t *
+svn_wc__db_lock_remove(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* @} */
+
+
+/* @defgroup svn_wc__db_scan Functions to scan up a tree for further data.
+ @{
+*/
+
+/* Read a BASE node's repository information.
+
+ For the BASE node implied by LOCAL_ABSPATH, its location in the repository
+ returned in *REPOS_ROOT_URL and *REPOS_UUID will be returned in
+ *REPOS_RELPATH. Any of the OUT parameters may be NULL, indicating no
+ interest in that piece of information.
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+
+ ### Either delete this function and use _base_get_info instead, or
+ ### add a 'revision' output to make a complete repository node location
+ ### and rename to not say 'scan', because it doesn't.
+*/
+svn_error_t *
+svn_wc__db_scan_base_repos(const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Scan upwards for information about a known addition to the WORKING tree.
+
+ IFF a node's status as returned by svn_wc__db_read_info() is
+ svn_wc__db_status_added (NOT obstructed_add!), then this function
+ returns a refined status in *STATUS, which is one of:
+
+ svn_wc__db_status_added -- this NODE is a simple add without history.
+ OP_ROOT_ABSPATH will be set to the topmost node in the added subtree
+ (implying its parent will be an unshadowed BASE node). The REPOS_*
+ values will be implied by that ancestor BASE node and this node's
+ position in the added subtree. ORIGINAL_* will be set to their
+ NULL values (and SVN_INVALID_REVNUM for ORIGINAL_REVISION).
+
+ svn_wc__db_status_copied -- this NODE is the root or child of a copy.
+ The root of the copy will be stored in OP_ROOT_ABSPATH. Note that
+ the parent of the operation root could be another WORKING node (from
+ an add, copy, or move). The REPOS_* values will be implied by the
+ ancestor unshadowed BASE node. ORIGINAL_* will indicate the source
+ of the copy.
+
+ svn_wc__db_status_incomplete -- this NODE is copied but incomplete.
+
+ svn_wc__db_status_moved_here -- this NODE arrived as a result of a move.
+ The root of the moved nodes will be stored in OP_ROOT_ABSPATH.
+ Similar to the copied state, its parent may be a WORKING node or a
+ BASE node. And again, the REPOS_* values are implied by this node's
+ position in the subtree under the ancestor unshadowed BASE node.
+ ORIGINAL_* will indicate the source of the move.
+
+ All OUT parameters may be NULL to indicate a lack of interest in
+ that piece of information.
+
+ STATUS, OP_ROOT_ABSPATH, and REPOS_* will always be assigned a value
+ if that information is requested (and assuming a successful return).
+
+ ORIGINAL_REPOS_RELPATH will refer to the *root* of the operation. It
+ does *not* correspond to the node given by LOCAL_ABSPATH. The caller
+ can use the suffix on LOCAL_ABSPATH (relative to OP_ROOT_ABSPATH) in
+ order to compute the source node which corresponds to LOCAL_ABSPATH.
+
+ If the node given by LOCAL_ABSPATH does not have changes recorded in
+ the WORKING tree, then SVN_ERR_WC_PATH_NOT_FOUND is returned. If it
+ doesn't have an "added" status, then SVN_ERR_WC_PATH_UNEXPECTED_STATUS
+ will be returned.
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_scan_addition(svn_wc__db_status_t *status,
+ const char **op_root_abspath,
+ const char **repos_relpath,
+ const char **repos_root_url,
+ const char **repos_uuid,
+ const char **original_repos_relpath,
+ const char **original_root_url,
+ const char **original_uuid,
+ svn_revnum_t *original_revision,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Scan the working copy for move information of the node LOCAL_ABSPATH.
+ * If LOCAL_ABSPATH return a SVN_ERR_WC_PATH_UNEXPECTED_STATUS error.
+ *
+ * If not NULL *MOVED_FROM_ABSPATH will be set to the previous location
+ * of LOCAL_ABSPATH, before it or an ancestror was moved.
+ *
+ * If not NULL *OP_ROOT_ABSPATH will be set to the new location of the
+ * path that was actually moved
+ *
+ * If not NULL *OP_ROOT_MOVED_FROM_ABSPATH will be set to the old location
+ * of the path that was actually moved.
+ *
+ * If not NULL *MOVED_FROM_DELETE_ABSPATH will be set to the ancestor of the
+ * moved from location that deletes the original location
+ *
+ * Given a working copy
+ * A/B/C
+ * svn mv A/B D
+ * svn rm A
+ *
+ * You can call this function on D and D/C. When called on D/C all output
+ * MOVED_FROM_ABSPATH will be A/B/C
+ * OP_ROOT_ABSPATH will be D
+ * OP_ROOT_MOVED_FROM_ABSPATH will be A/B
+ * MOVED_FROM_DELETE_ABSPATH will be A
+ */
+svn_error_t *
+svn_wc__db_scan_moved(const char **moved_from_abspath,
+ const char **op_root_abspath,
+ const char **op_root_moved_from_abspath,
+ const char **moved_from_delete_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Scan upwards for additional information about a deleted node.
+
+ When a deleted node is discovered in the WORKING tree, the situation
+ may be quite complex. This function will provide the information to
+ resolve the circumstances of the deletion.
+
+ For discussion purposes, we will start with the most complex example
+ and then demonstrate simplified examples. Consider node B/W/D/N has been
+ found as deleted. B is an unmodified directory (thus, only in BASE). W is
+ "replacement" content that exists in WORKING, shadowing a similar B/W
+ directory in BASE. D is a deleted subtree in the WORKING tree, and N is
+ the deleted node.
+
+ In this example, BASE_DEL_ABSPATH will bet set to B/W. That is the root of
+ the BASE tree (implicitly) deleted by the replacement. WORK_DEL_ABSPATH
+ will be set to the subtree deleted within the replacement; in this case,
+ B/W/D. No move-away took place, so MOVED_TO_ABSPATH is set to NULL.
+
+ In another scenario, B/W was moved-away before W was put into the WORKING
+ tree through an add/copy/move-here. MOVED_TO_ABSPATH will indicate where
+ B/W was moved to. Note that further operations may have been performed
+ post-move, but that is not known or reported by this function.
+
+ If BASE does not have a B/W, then the WORKING B/W is not a replacement,
+ but a simple add/copy/move-here. BASE_DEL_ABSPATH will be set to NULL.
+
+ If B/W/D does not exist in the WORKING tree (we're only talking about a
+ deletion of nodes of the BASE tree), then deleting B/W/D would have marked
+ the subtree for deletion. BASE_DEL_ABSPATH will refer to B/W/D,
+ MOVED_TO_ABSPATH will be NULL, and WORK_DEL_ABSPATH will be NULL.
+
+ If the BASE node B/W/D was moved instead of deleted, then MOVED_TO_ABSPATH
+ would indicate the target location (and other OUT values as above).
+
+ When the user deletes B/W/D from the WORKING tree, there are a few
+ additional considerations. If B/W is a simple addition (not a copy or
+ a move-here), then the deletion will simply remove the nodes from WORKING
+ and possibly leave behind "base-delete" markers in the WORKING tree.
+ If the source is a copy/moved-here, then the nodes are replaced with
+ deletion markers.
+
+ If the user moves-away B/W/D from the WORKING tree, then behavior is
+ again dependent upon the origination of B/W. For a plain add, the nodes
+ simply move to the destination; this means that B/W/D ceases to be a
+ node and so cannot be scanned. For a copy, a deletion is made at B/W/D,
+ and a new copy (of a subtree of the original source) is made at the
+ destination. For a move-here, a deletion is made, and a copy is made at
+ the destination (we do not track multiple moves; the source is moved to
+ B/W, then B/W/D is deleted; then a copy is made at the destination;
+ however, note the double-move could have been performed by moving the
+ subtree first, then moving the source to B/W).
+
+ There are three further considerations when resolving a deleted node:
+
+ If the BASE B/W/D was deleted explicitly *and* B/W is a replacement,
+ then the explicit deletion is subsumed by the implicit deletion that
+ occurred with the B/W replacement. Thus, BASE_DEL_ABSPATH will point
+ to B/W as the root of the BASE deletion. IOW, we can detect the
+ explicit move-away, but not an explicit deletion.
+
+ If B/W/D/N refers to a node present in the BASE tree, and B/W was
+ replaced by a shallow subtree, then it is possible for N to be
+ reported as deleted (from BASE) yet no deletions occurred in the
+ WORKING tree above N. Thus, WORK_DEL_ABSPATH will be set to NULL.
+
+
+ Summary of OUT parameters:
+
+ BASE_DEL_ABSPATH will specify the nearest ancestor of the explicit or
+ implicit deletion (if any) that applies to the BASE tree.
+
+ WORK_DEL_ABSPATH will specify the root of a deleted subtree within
+ the WORKING tree (note there is no concept of layered delete operations
+ in WORKING, so there is only one deletion root in the ancestry).
+
+ MOVED_TO_ABSPATH will specify the path where this node was moved to
+ if the node has moved-away.
+
+ If the node was moved-away, MOVED_TO_OP_ROOT_ABSPATH will specify the
+ target path of the root of the move operation. If LOCAL_ABSPATH itself
+ is the source path of the root of the move operation, then
+ MOVED_TO_OP_ROOT_ABSPATH equals MOVED_TO_ABSPATH.
+
+ All OUT parameters may be set to NULL to indicate a lack of interest in
+ that piece of information.
+
+ If the node given by LOCAL_ABSPATH does not exist, then
+ SVN_ERR_WC_PATH_NOT_FOUND is returned. If it doesn't have a "deleted"
+ status, then SVN_ERR_WC_PATH_UNEXPECTED_STATUS will be returned.
+
+ All returned data will be allocated in RESULT_POOL. All temporary
+ allocations will be made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_wc__db_scan_deletion(const char **base_del_abspath,
+ const char **moved_to_abspath,
+ const char **work_del_abspath,
+ const char **moved_to_op_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* @} */
+
+
+/* @defgroup svn_wc__db_upgrade Functions for upgrading a working copy.
+ @{
+*/
+
+/* Create a new wc.db file for LOCAL_DIR_ABSPATH, which is going to be a
+ working copy for the repository REPOS_ROOT_URL with uuid REPOS_UUID.
+ Return the raw sqlite handle, repository id and working copy id
+ and store the database in WC_DB.
+
+ Perform temporary allocations in SCRATCH_POOL. */
+svn_error_t *
+svn_wc__db_upgrade_begin(svn_sqlite__db_t **sdb,
+ apr_int64_t *repos_id,
+ apr_int64_t *wc_id,
+ svn_wc__db_t *wc_db,
+ const char *local_dir_abspath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ apr_pool_t *scratch_pool);
+
+
+svn_error_t *
+svn_wc__db_upgrade_apply_dav_cache(svn_sqlite__db_t *sdb,
+ const char *dir_relpath,
+ apr_hash_t *cache_values,
+ apr_pool_t *scratch_pool);
+
+
+/* ### need much more docco
+
+ ### this function should be called within a sqlite transaction. it makes
+ ### assumptions around this fact.
+
+ Apply the various sets of properties to the database nodes based on
+ their existence/presence, the current state of the node, and the original
+ format of the working copy which provided these property sets.
+*/
+svn_error_t *
+svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb,
+ const char *dir_abspath,
+ const char *local_relpath,
+ apr_hash_t *base_props,
+ apr_hash_t *revert_props,
+ apr_hash_t *working_props,
+ int original_format,
+ apr_int64_t wc_id,
+ apr_pool_t *scratch_pool);
+
+/* Simply insert (or replace) one row in the EXTERNALS table. */
+svn_error_t *
+svn_wc__db_upgrade_insert_external(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_node_kind_t kind,
+ const char *parent_abspath,
+ const char *def_local_abspath,
+ const char *repos_relpath,
+ const char *repos_root_url,
+ const char *repos_uuid,
+ svn_revnum_t def_peg_revision,
+ svn_revnum_t def_revision,
+ apr_pool_t *scratch_pool);
+
+/* Get the repository identifier corresponding to REPOS_ROOT_URL from the
+ database in SDB. The value is returned in *REPOS_ID. All allocations
+ are allocated in SCRATCH_POOL.
+
+ NOTE: the row in REPOSITORY must exist. If not, then SVN_ERR_WC_DB_ERROR
+ is returned.
+
+ ### unclear on whether/how this interface will stay/evolve. */
+svn_error_t *
+svn_wc__db_upgrade_get_repos_id(apr_int64_t *repos_id,
+ svn_sqlite__db_t *sdb,
+ const char *repos_root_url,
+ apr_pool_t *scratch_pool);
+
+/* Upgrade the metadata concerning the WC at WCROOT_ABSPATH, in DB,
+ * to the SVN_WC__VERSION format.
+ *
+ * This function is used for upgrading wc-ng working copies to a newer
+ * wc-ng format. If a pre-1.7 working copy is found, this function
+ * returns SVN_ERR_WC_UPGRADE_REQUIRED.
+ *
+ * Upgrading subdirectories of a working copy is not supported.
+ * If WCROOT_ABSPATH is not a working copy root SVN_ERR_WC_INVALID_OP_ON_CWD
+ * is returned.
+ */
+svn_error_t *
+svn_wc__db_bump_format(int *result_format,
+ const char *wcroot_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool);
+
+/* @} */
+
+
+/* @defgroup svn_wc__db_wq Work queue manipulation. see workqueue.h
+ @{
+*/
+
+/* In the WCROOT associated with DB and WRI_ABSPATH, add WORK_ITEM to the
+ wcroot's work queue. Use SCRATCH_POOL for all temporary allocations. */
+svn_error_t *
+svn_wc__db_wq_add(svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *work_item,
+ apr_pool_t *scratch_pool);
+
+
+/* In the WCROOT associated with DB and WRI_ABSPATH, fetch a work item that
+ needs to be completed. Its identifier is returned in ID, and the data in
+ WORK_ITEM.
+
+ Items are returned in the same order they were queued. This allows for
+ (say) queueing work on a parent node to be handled before that of its
+ children.
+
+ If there are no work items to be completed, then ID will be set to zero,
+ and WORK_ITEM to NULL.
+
+ If COMPLETED_ID is not 0, the wq item COMPLETED_ID will be marked as
+ completed before returning the next item.
+
+ RESULT_POOL will be used to allocate WORK_ITEM, and SCRATCH_POOL
+ will be used for all temporary allocations. */
+svn_error_t *
+svn_wc__db_wq_fetch_next(apr_uint64_t *id,
+ svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_uint64_t completed_id,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Special variant of svn_wc__db_wq_fetch_next(), which in the same transaction
+ also records timestamps and sizes for one or more nodes */
+svn_error_t *
+svn_wc__db_wq_record_and_fetch_next(apr_uint64_t *id,
+ svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_uint64_t completed_id,
+ apr_hash_t *record_map,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* @} */
+
+
+/* Note: LEVELS_TO_LOCK is here strictly for backward compat. The access
+ batons still have the notion of 'levels to lock' and we need to ensure
+ that they still function correctly, even in the new world. 'levels to
+ lock' should not be exposed through the wc-ng APIs at all: users either
+ get to lock the entire tree (rooted at some subdir, of course), or none.
+
+ An infinite depth lock is obtained with LEVELS_TO_LOCK set to -1, but until
+ we move to a single DB only depth 0 is supported.
+*/
+svn_error_t *
+svn_wc__db_wclock_obtain(svn_wc__db_t *db,
+ const char *local_abspath,
+ int levels_to_lock,
+ svn_boolean_t steal_lock,
+ apr_pool_t *scratch_pool);
+
+/* Set LOCK_ABSPATH to the path of the the directory that owns the
+ lock on LOCAL_ABSPATH, or NULL, if LOCAL_ABSPATH is not locked. */
+svn_error_t*
+svn_wc__db_wclock_find_root(const char **lock_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Check if somebody has a wclock on LOCAL_ABSPATH */
+svn_error_t *
+svn_wc__db_wclocked(svn_boolean_t *locked,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Release the previously obtained lock on LOCAL_ABSPATH */
+svn_error_t *
+svn_wc__db_wclock_release(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Checks whether DB currently owns a lock to operate on LOCAL_ABSPATH.
+ If EXACT is TRUE only lock roots are checked. */
+svn_error_t *
+svn_wc__db_wclock_owns_lock(svn_boolean_t *own_lock,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t exact,
+ apr_pool_t *scratch_pool);
+
+
+
+/* @defgroup svn_wc__db_temp Various temporary functions during transition
+
+ ### These functions SHOULD be completely removed before 1.7
+
+ @{
+*/
+
+/* Removes all references to LOCAL_ABSPATH from DB, while optionally leaving
+ a not present node.
+
+ This operation always recursively removes all nodes at and below
+ LOCAL_ABSPATH from NODES and ACTUAL.
+
+ If NOT_PRESENT_REVISION specifies a valid revision, leave a not_present
+ BASE node at local_abspath of the specified status and kind.
+ (Requires an existing BASE node before removing)
+
+ If DESTROY_WC is TRUE, this operation *installs* workqueue operations to
+ update the local filesystem after the database operation. If DESTROY_CHANGES
+ is FALSE, modified and unversioned files are left after running this
+ operation (and the WQ). If DESTROY_CHANGES and DESTROY_WC are TRUE,
+ LOCAL_ABSPATH and everything below it will be removed by the WQ.
+
+
+ Note: Unlike many similar functions it is a valid scenario for this
+ function to be called on a wcroot! In this case it will just leave the root
+ record in BASE
+ */
+svn_error_t *
+svn_wc__db_op_remove_node(svn_boolean_t *left_changes,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t destroy_wc,
+ svn_boolean_t destroy_changes,
+ svn_revnum_t not_present_revision,
+ svn_wc__db_status_t not_present_status,
+ svn_node_kind_t not_present_kind,
+ const svn_skel_t *conflict,
+ const svn_skel_t *work_items,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Sets the depth of LOCAL_ABSPATH in its working copy to DEPTH using DB.
+
+ Returns SVN_ERR_WC_PATH_NOT_FOUND if LOCAL_ABSPATH is not a BASE directory
+ */
+svn_error_t *
+svn_wc__db_op_set_base_depth(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool);
+
+/* ### temp function. return the FORMAT for the directory LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__db_temp_get_format(int *format,
+ svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool);
+
+/* ### temp functions to manage/store access batons within the DB. */
+svn_wc_adm_access_t *
+svn_wc__db_temp_get_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool);
+void
+svn_wc__db_temp_set_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *scratch_pool);
+svn_error_t *
+svn_wc__db_temp_close_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *scratch_pool);
+void
+svn_wc__db_temp_clear_access(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool);
+
+/* ### shallow hash: abspath -> svn_wc_adm_access_t * */
+apr_hash_t *
+svn_wc__db_temp_get_all_access(svn_wc__db_t *db,
+ apr_pool_t *result_pool);
+
+/* ### temp function to open the sqlite database to the appropriate location,
+ ### then borrow it for a bit.
+ ### The *only* reason for this function is because entries.c still
+ ### manually hacks the sqlite database.
+
+ ### No matter how tempted you may be DO NOT USE THIS FUNCTION!
+ ### (if you do, gstein will hunt you down and burn your knee caps off
+ ### in the middle of the night)
+ ### "Bet on it." --gstein
+*/
+svn_error_t *
+svn_wc__db_temp_borrow_sdb(svn_sqlite__db_t **sdb,
+ svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* Return a directory in *TEMP_DIR_ABSPATH that is suitable for temporary
+ files which may need to be moved (atomically and same-device) into the
+ working copy indicated by WRI_ABSPATH. */
+svn_error_t *
+svn_wc__db_temp_wcroot_tempdir(const char **temp_dir_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Update the BASE_NODE of directory LOCAL_ABSPATH to be NEW_REPOS_RELPATH
+ at revision NEW_REV with status incomplete. */
+svn_error_t *
+svn_wc__db_temp_op_start_directory_update(svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *new_repos_relpath,
+ svn_revnum_t new_rev,
+ apr_pool_t *scratch_pool);
+
+/* Marks a directory update started with
+ svn_wc__db_temp_op_start_directory_update as completed, by removing
+ the incomplete status */
+svn_error_t *
+svn_wc__db_temp_op_end_directory_update(svn_wc__db_t *db,
+ const char *local_dir_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* Copy the base tree at LOCAL_ABSPATH into the working tree as copy,
+ leaving any subtree additions and copies as-is. This allows the
+ base node tree to be removed. */
+svn_error_t *
+svn_wc__db_op_make_copy(svn_wc__db_t *db,
+ const char *local_abspath,
+ const svn_skel_t *conflicts,
+ const svn_skel_t *work_items,
+ apr_pool_t *scratch_pool);
+
+/* Close the wc root LOCAL_ABSPATH and remove any per-directory
+ handles associated with it. */
+svn_error_t *
+svn_wc__db_drop_root(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* Return the OP_DEPTH for LOCAL_RELPATH. */
+int
+svn_wc__db_op_depth_for_upgrade(const char *local_relpath);
+
+/* Set *HAVE_WORK TRUE if there is a working layer below the top layer and
+ *HAVE_BASE if there is a base layer. Set *STATUS to the status of the
+ highest layer below WORKING */
+svn_error_t *
+svn_wc__db_info_below_working(svn_boolean_t *have_base,
+ svn_boolean_t *have_work,
+ svn_wc__db_status_t *status,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* Gets an array of const char *local_relpaths of descendants of LOCAL_ABSPATH,
+ * which itself must be the op root of an addition, copy or move.
+ * The descendants returned are at the same op_depth, but are to be deleted
+ * by the commit processing because they are not present in the local copy.
+ */
+svn_error_t *
+svn_wc__db_get_not_present_descendants(const apr_array_header_t **descendants,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gather revision status information about a working copy using DB.
+ *
+ * Set *MIN_REVISION and *MAX_REVISION to the lowest and highest revision
+ * numbers found within LOCAL_ABSPATH.
+ * Only nodes with op_depth zero and presence 'normal' or 'incomplete'
+ * are considered, so that added, deleted or excluded nodes do not affect
+ * the result. If COMMITTED is TRUE, set *MIN_REVISION and *MAX_REVISION
+ * to the lowest and highest committed (i.e. "last changed") revision numbers,
+ * respectively.
+ *
+ * Indicate in *IS_SPARSE_CHECKOUT whether any of the nodes within
+ * LOCAL_ABSPATH is sparse.
+ * Indicate in *IS_MODIFIED whether the working copy has local modifications.
+ *
+ * Indicate in *IS_SWITCHED whether any node beneath LOCAL_ABSPATH
+ * is switched. If TRAIL_URL is non-NULL, use it to determine if LOCAL_ABSPATH
+ * itself is switched. It should be any trailing portion of LOCAL_ABSPATH's
+ * expected URL, long enough to include any parts that the caller considers
+ * might be changed by a switch. If it does not match the end of WC_PATH's
+ * actual URL, then report a "switched" status.
+ *
+ * See also the functions below which provide a subset of this functionality.
+ */
+svn_error_t *
+svn_wc__db_revision_status(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_boolean_t *is_sparse_checkout,
+ svn_boolean_t *is_modified,
+ svn_boolean_t *is_switched,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *trail_url,
+ svn_boolean_t committed,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* Set *MIN_REVISION and *MAX_REVISION to the lowest and highest revision
+ * numbers found within LOCAL_ABSPATH in the working copy using DB.
+ * Only nodes with op_depth zero and presence 'normal' or 'incomplete'
+ * are considered, so that added, deleted or excluded nodes do not affect
+ * the result. If COMMITTED is TRUE, set *MIN_REVISION and *MAX_REVISION
+ * to the lowest and highest committed (i.e. "last changed") revision numbers,
+ * respectively. Use SCRATCH_POOL for temporary allocations.
+ *
+ * Either of MIN_REVISION and MAX_REVISION may be passed as NULL if
+ * the caller doesn't care about that return value.
+ *
+ * This function provides a subset of the functionality of
+ * svn_wc__db_revision_status() and is more efficient if the caller
+ * doesn't need all information returned by svn_wc__db_revision_status(). */
+svn_error_t *
+svn_wc__db_min_max_revisions(svn_revnum_t *min_revision,
+ svn_revnum_t *max_revision,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t committed,
+ apr_pool_t *scratch_pool);
+
+/* Indicate in *IS_SWITCHED whether any node beneath LOCAL_ABSPATH
+ * is switched, using DB. Use SCRATCH_POOL for temporary allocations.
+ *
+ * If TRAIL_URL is non-NULL, use it to determine if LOCAL_ABSPATH itself
+ * is switched. It should be any trailing portion of LOCAL_ABSPATH's
+ * expected URL, long enough to include any parts that the caller considers
+ * might be changed by a switch. If it does not match the end of WC_PATH's
+ * actual URL, then report a "switched" status.
+ *
+ * This function provides a subset of the functionality of
+ * svn_wc__db_revision_status() and is more efficient if the caller
+ * doesn't need all information returned by svn_wc__db_revision_status(). */
+svn_error_t *
+svn_wc__db_has_switched_subtrees(svn_boolean_t *is_switched,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *trail_url,
+ apr_pool_t *scratch_pool);
+
+/* Set @a *excluded_subtrees to a hash mapping <tt>const char *</tt>
+ * local absolute paths to <tt>const char *</tt> local absolute paths for
+ * every path under @a local_abspath in @a db which are excluded by
+ * the server (e.g. due to authz), or user. If no such paths are found then
+ * @a *server_excluded_subtrees is set to @c NULL.
+ * Allocate the hash and all items therein from @a result_pool.
+ */
+svn_error_t *
+svn_wc__db_get_excluded_subtrees(apr_hash_t **server_excluded_subtrees,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Indicate in *IS_MODIFIED whether the working copy has local modifications,
+ * using DB. Use SCRATCH_POOL for temporary allocations.
+ *
+ * This function provides a subset of the functionality of
+ * svn_wc__db_revision_status() and is more efficient if the caller
+ * doesn't need all information returned by svn_wc__db_revision_status(). */
+svn_error_t *
+svn_wc__db_has_local_mods(svn_boolean_t *is_modified,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Verify the consistency of metadata concerning the WC that contains
+ * WRI_ABSPATH, in DB. Return an error if any problem is found. */
+svn_error_t *
+svn_wc__db_verify(svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *scratch_pool);
+
+
+/* Possibly need two structures, one with relpaths and with abspaths?
+ * Only exposed for testing at present. */
+struct svn_wc__db_moved_to_t {
+ const char *local_relpath; /* moved-to destination */
+ int op_depth; /* op-root of source */
+};
+
+/* Set *FINAL_ABSPATH to an array of svn_wc__db_moved_to_t for
+ * LOCAL_ABSPATH after following any and all nested moves.
+ * Only exposed for testing at present. */
+svn_error_t *
+svn_wc__db_follow_moved_to(apr_array_header_t **moved_tos,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Update a moved-away tree conflict victim at VICTIM_ABSPATH with changes
+ * brought in by the update operation which flagged the tree conflict. */
+svn_error_t *
+svn_wc__db_update_moved_away_conflict_victim(svn_wc__db_t *db,
+ const char *victim_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+/* LOCAL_ABSPATH is moved to MOVE_DST_ABSPATH. MOVE_SRC_ROOT_ABSPATH
+ * is the root of the move to MOVE_DST_OP_ROOT_ABSPATH.
+ * MOVE_SRC_OP_ROOT_ABSPATH is the op-root of the move; it's the same
+ * as MOVE_SRC_ROOT_ABSPATH except for moves inside deletes when it is
+ * the op-root of the delete. */
+svn_error_t *
+svn_wc__db_base_moved_to(const char **move_dst_abspath,
+ const char **move_dst_op_root_abspath,
+ const char **move_src_root_abspath,
+ const char **move_src_op_root_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Recover space from the database file for LOCAL_ABSPATH by running
+ * the "vacuum" command. */
+svn_error_t *
+svn_wc__db_vacuum(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+/* This raises move-edit tree-conflicts on any moves inside the
+ delete-edit conflict on LOCAL_ABSPATH. This is experimental: see
+ comment in resolve_conflict_on_node about combining with another
+ function. */
+svn_error_t *
+svn_wc__db_resolve_delete_raise_moved_away(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc__db_resolve_delete_raise_moved_away this should be
+ combined. */
+svn_error_t *
+svn_wc__db_resolve_break_moved_away(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/* Break moves for all moved-away children of LOCAL_ABSPATH, within
+ * a single transaction.
+ *
+ * ### Like svn_wc__db_resolve_delete_raise_moved_away this should be
+ * combined. */
+svn_error_t *
+svn_wc__db_resolve_break_moved_away_children(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+/* Set *REQUIRED_ABSPATH to the path that should be locked to ensure
+ * that the lock covers all paths affected by resolving the conflicts
+ * in the tree LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__required_lock_for_resolve(const char **required_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+/* @} */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_WC_DB_H */
diff --git a/contrib/subversion/subversion/libsvn_wc/wc_db_pristine.c b/contrib/subversion/subversion/libsvn_wc/wc_db_pristine.c
new file mode 100644
index 0000000..d9dc8f3
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc_db_pristine.c
@@ -0,0 +1,925 @@
+/*
+ * wc_db_pristine.c : Pristine ("text base") management
+ *
+ * See the spec in 'notes/wc-ng/pristine-store'.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+
+#include "wc.h"
+#include "wc_db.h"
+#include "wc-queries.h"
+#include "wc_db_private.h"
+
+#define PRISTINE_STORAGE_EXT ".svn-base"
+#define PRISTINE_STORAGE_RELPATH "pristine"
+#define PRISTINE_TEMPDIR_RELPATH "tmp"
+
+
+
+/* Returns in PRISTINE_ABSPATH a new string allocated from RESULT_POOL,
+ holding the local absolute path to the file location that is dedicated
+ to hold CHECKSUM's pristine file, relating to the pristine store
+ configured for the working copy indicated by PDH. The returned path
+ does not necessarily currently exist.
+
+ Any other allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+get_pristine_fname(const char **pristine_abspath,
+ const char *wcroot_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *base_dir_abspath;
+ const char *hexdigest = svn_checksum_to_cstring(sha1_checksum, scratch_pool);
+ char subdir[3];
+
+ /* ### code is in transition. make sure we have the proper data. */
+ SVN_ERR_ASSERT(pristine_abspath != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wcroot_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+
+ base_dir_abspath = svn_dirent_join_many(scratch_pool,
+ wcroot_abspath,
+ svn_wc_get_adm_dir(scratch_pool),
+ PRISTINE_STORAGE_RELPATH,
+ NULL);
+
+ /* We should have a valid checksum and (thus) a valid digest. */
+ SVN_ERR_ASSERT(hexdigest != NULL);
+
+ /* Get the first two characters of the digest, for the subdir. */
+ subdir[0] = hexdigest[0];
+ subdir[1] = hexdigest[1];
+ subdir[2] = '\0';
+
+ hexdigest = apr_pstrcat(scratch_pool, hexdigest, PRISTINE_STORAGE_EXT,
+ (char *)NULL);
+
+ /* The file is located at DIR/.svn/pristine/XX/XXYYZZ...svn-base */
+ *pristine_abspath = svn_dirent_join_many(result_pool,
+ base_dir_abspath,
+ subdir,
+ hexdigest,
+ NULL);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_pristine_get_path(const char **pristine_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_boolean_t present;
+
+ SVN_ERR_ASSERT(pristine_abspath != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ /* ### Transitional: accept MD-5 and look up the SHA-1. Return an error
+ * if the pristine text is not in the store. */
+ if (sha1_checksum->kind != svn_checksum_sha1)
+ SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, db, wri_abspath,
+ sha1_checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, wri_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_wc__db_pristine_check(&present, db, wri_abspath, sha1_checksum,
+ scratch_pool));
+ if (! present)
+ return svn_error_createf(SVN_ERR_WC_DB_ERROR, NULL,
+ _("The pristine text with checksum '%s' was "
+ "not found"),
+ svn_checksum_to_cstring_display(sha1_checksum,
+ scratch_pool));
+
+ SVN_ERR(get_pristine_fname(pristine_abspath, wcroot->abspath,
+ sha1_checksum,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_pristine_get_future_path(const char **pristine_abspath,
+ const char *wcroot_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(get_pristine_fname(pristine_abspath, wcroot_abspath,
+ sha1_checksum,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Set *CONTENTS to a readable stream from which the pristine text
+ * identified by SHA1_CHECKSUM and PRISTINE_ABSPATH can be read from the
+ * pristine store of WCROOT. If SIZE is not null, set *SIZE to the size
+ * in bytes of that text. If that text is not in the pristine store,
+ * return an error.
+ *
+ * Even if the pristine text is removed from the store while it is being
+ * read, the stream will remain valid and readable until it is closed.
+ *
+ * Allocate the stream in RESULT_POOL.
+ *
+ * This function expects to be executed inside a SQLite txn.
+ *
+ * Implements 'notes/wc-ng/pristine-store' section A-3(d).
+ */
+static svn_error_t *
+pristine_read_txn(svn_stream_t **contents,
+ svn_filesize_t *size,
+ svn_wc__db_wcroot_t *wcroot,
+ const svn_checksum_t *sha1_checksum,
+ const char *pristine_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ /* Check that this pristine text is present in the store. (The presence
+ * of the file is not sufficient.) */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_PRISTINE_SIZE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (size)
+ *size = svn_sqlite__column_int64(stmt, 0);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (! have_row)
+ {
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Pristine text '%s' not present"),
+ svn_checksum_to_cstring_display(
+ sha1_checksum, scratch_pool));
+ }
+
+ /* Open the file as a readable stream. It will remain readable even when
+ * deleted from disk; APR guarantees that on Windows as well as Unix. */
+ if (contents)
+ SVN_ERR(svn_stream_open_readonly(contents, pristine_abspath,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_pristine_read(svn_stream_t **contents,
+ svn_filesize_t *size,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *pristine_abspath;
+
+ SVN_ERR_ASSERT(contents != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ /* Some 1.6-to-1.7 wc upgrades created rows without checksums and
+ updating such a row passes NULL here. */
+ if (!sha1_checksum)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Can't read '%s' from pristine store "
+ "because no checksum supplied"),
+ svn_dirent_local_style(wri_abspath, scratch_pool));
+
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath,
+ sha1_checksum,
+ scratch_pool, scratch_pool));
+ SVN_WC__DB_WITH_TXN(
+ pristine_read_txn(contents, size,
+ wcroot, sha1_checksum, pristine_abspath,
+ result_pool, scratch_pool),
+ wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return the absolute path to the temporary directory for pristine text
+ files within WCROOT. */
+static char *
+pristine_get_tempdir(svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_dirent_join_many(result_pool, wcroot->abspath,
+ svn_wc_get_adm_dir(scratch_pool),
+ PRISTINE_TEMPDIR_RELPATH, (char *)NULL);
+}
+
+svn_error_t *
+svn_wc__db_pristine_get_tempdir(const char **temp_dir_abspath,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(temp_dir_abspath != NULL);
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ *temp_dir_abspath = pristine_get_tempdir(wcroot, result_pool, scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Install the pristine text described by BATON into the pristine store of
+ * SDB. If it is already stored then just delete the new file
+ * BATON->tempfile_abspath.
+ *
+ * This function expects to be executed inside a SQLite txn that has already
+ * acquired a 'RESERVED' lock.
+ *
+ * Implements 'notes/wc-ng/pristine-store' section A-3(a).
+ */
+static svn_error_t *
+pristine_install_txn(svn_sqlite__db_t *sdb,
+ /* The path to the source file that is to be moved into place. */
+ const char *tempfile_abspath,
+ /* The target path for the file (within the pristine store). */
+ const char *pristine_abspath,
+ /* The pristine text's SHA-1 checksum. */
+ const svn_checksum_t *sha1_checksum,
+ /* The pristine text's MD-5 checksum. */
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *scratch_pool)
+{
+ apr_finfo_t finfo;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_error_t *err;
+
+ /* If this pristine text is already present in the store, just keep it:
+ * delete the new one and return. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ {
+#ifdef SVN_DEBUG
+ /* Consistency checks. Verify both files exist and match.
+ * ### We could check much more. */
+ {
+ apr_finfo_t finfo1, finfo2;
+ SVN_ERR(svn_io_stat(&finfo1, tempfile_abspath, APR_FINFO_SIZE,
+ scratch_pool));
+ SVN_ERR(svn_io_stat(&finfo2, pristine_abspath, APR_FINFO_SIZE,
+ scratch_pool));
+ if (finfo1.size != finfo2.size)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
+ _("New pristine text '%s' has different size: %ld versus %ld"),
+ svn_checksum_to_cstring_display(sha1_checksum, scratch_pool),
+ (long int)finfo1.size, (long int)finfo2.size);
+ }
+ }
+#endif
+
+ /* Remove the temp file: it's already there */
+ SVN_ERR(svn_io_remove_file2(tempfile_abspath,
+ FALSE /* ignore_enoent */, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ /* Move the file to its target location. (If it is already there, it is
+ * an orphan file and it doesn't matter if we overwrite it.) */
+ err = svn_io_file_rename(tempfile_abspath, pristine_abspath,
+ scratch_pool);
+
+ /* Maybe the directory doesn't exist yet? */
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_t *err2;
+
+ err2 = svn_io_dir_make(svn_dirent_dirname(pristine_abspath,
+ scratch_pool),
+ APR_OS_DEFAULT, scratch_pool);
+
+ if (err2)
+ /* Creating directory didn't work: Return all errors */
+ return svn_error_trace(svn_error_compose_create(err, err2));
+ else
+ /* We could create a directory: retry install */
+ svn_error_clear(err);
+
+ SVN_ERR(svn_io_file_rename(tempfile_abspath, pristine_abspath,
+ scratch_pool));
+ }
+ else
+ SVN_ERR(err);
+
+ SVN_ERR(svn_io_stat(&finfo, pristine_abspath, APR_FINFO_SIZE,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_INSERT_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 3, finfo.size));
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_pristine_install(svn_wc__db_t *db,
+ const char *tempfile_abspath,
+ const svn_checksum_t *sha1_checksum,
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *wri_abspath;
+ const char *pristine_abspath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(tempfile_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+ SVN_ERR_ASSERT(md5_checksum != NULL);
+ SVN_ERR_ASSERT(md5_checksum->kind == svn_checksum_md5);
+
+ /* ### this logic assumes that TEMPFILE_ABSPATH follows this pattern:
+ ### WCROOT_ABSPATH/COMPONENT/COMPONENT/TEMPFNAME
+ ### if we change this (see PRISTINE_TEMPDIR_RELPATH), then this
+ ### logic should change. */
+ wri_abspath = svn_dirent_dirname(
+ svn_dirent_dirname(
+ svn_dirent_dirname(tempfile_abspath, scratch_pool),
+ scratch_pool),
+ scratch_pool);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath,
+ sha1_checksum,
+ scratch_pool, scratch_pool));
+
+ /* Ensure the SQL txn has at least a 'RESERVED' lock before we start looking
+ * at the disk, to ensure no concurrent pristine install/delete txn. */
+ SVN_SQLITE__WITH_IMMEDIATE_TXN(
+ pristine_install_txn(wcroot->sdb,
+ tempfile_abspath, pristine_abspath,
+ sha1_checksum, md5_checksum,
+ scratch_pool),
+ wcroot->sdb);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_pristine_get_md5(const svn_checksum_t **md5_checksum,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_DB_ERROR, svn_sqlite__reset(stmt),
+ _("The pristine text with checksum '%s' was "
+ "not found"),
+ svn_checksum_to_cstring_display(sha1_checksum,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__column_checksum(md5_checksum, stmt, 0, result_pool));
+ SVN_ERR_ASSERT((*md5_checksum)->kind == svn_checksum_md5);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+svn_error_t *
+svn_wc__db_pristine_get_sha1(const svn_checksum_t **sha1_checksum,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *md5_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ SVN_ERR_ASSERT(md5_checksum->kind == svn_checksum_md5);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_PRISTINE_BY_MD5));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, md5_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_DB_ERROR, svn_sqlite__reset(stmt),
+ _("The pristine text with MD5 checksum '%s' was "
+ "not found"),
+ svn_checksum_to_cstring_display(md5_checksum,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__column_checksum(sha1_checksum, stmt, 0, result_pool));
+ SVN_ERR_ASSERT((*sha1_checksum)->kind == svn_checksum_sha1);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+/* Handle the moving of a pristine from SRC_WCROOT to DST_WCROOT. The existing
+ pristine in SRC_WCROOT is described by CHECKSUM, MD5_CHECKSUM and SIZE */
+static svn_error_t *
+maybe_transfer_one_pristine(svn_wc__db_wcroot_t *src_wcroot,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const svn_checksum_t *checksum,
+ const svn_checksum_t *md5_checksum,
+ apr_int64_t size,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *pristine_abspath;
+ svn_sqlite__stmt_t *stmt;
+ svn_stream_t *src_stream;
+ svn_stream_t *dst_stream;
+ const char *tmp_abspath;
+ const char *src_abspath;
+ int affected_rows;
+ svn_error_t *err;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb,
+ STMT_INSERT_OR_IGNORE_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 3, size));
+
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ if (affected_rows == 0)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_stream_open_unique(&dst_stream, &tmp_abspath,
+ pristine_get_tempdir(dst_wcroot,
+ scratch_pool,
+ scratch_pool),
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(get_pristine_fname(&src_abspath, src_wcroot->abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_stream_open_readonly(&src_stream, src_abspath,
+ scratch_pool, scratch_pool));
+
+ /* ### Should we verify the SHA1 or MD5 here, or is that too expensive? */
+ SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ SVN_ERR(get_pristine_fname(&pristine_abspath, dst_wcroot->abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ /* Move the file to its target location. (If it is already there, it is
+ * an orphan file and it doesn't matter if we overwrite it.) */
+ err = svn_io_file_rename(tmp_abspath, pristine_abspath, scratch_pool);
+
+ /* Maybe the directory doesn't exist yet? */
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_t *err2;
+
+ err2 = svn_io_dir_make(svn_dirent_dirname(pristine_abspath,
+ scratch_pool),
+ APR_OS_DEFAULT, scratch_pool);
+
+ if (err2)
+ /* Creating directory didn't work: Return all errors */
+ return svn_error_trace(svn_error_compose_create(err, err2));
+ else
+ /* We could create a directory: retry install */
+ svn_error_clear(err);
+
+ SVN_ERR(svn_io_file_rename(tmp_abspath, pristine_abspath, scratch_pool));
+ }
+ else
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* Transaction implementation of svn_wc__db_pristine_transfer().
+ We have a lock on DST_WCROOT.
+ */
+static svn_error_t *
+pristine_transfer_txn(svn_wc__db_wcroot_t *src_wcroot,
+ svn_wc__db_wcroot_t *dst_wcroot,
+ const char *src_relpath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t got_row;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb,
+ STMT_SELECT_COPY_PRISTINES));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", src_wcroot->wc_id, src_relpath));
+
+ /* This obtains an sqlite read lock on src_wcroot */
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+
+ while (got_row)
+ {
+ const svn_checksum_t *checksum;
+ const svn_checksum_t *md5_checksum;
+ apr_int64_t size;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_sqlite__column_checksum(&checksum, stmt, 0, iterpool));
+ SVN_ERR(svn_sqlite__column_checksum(&md5_checksum, stmt, 1, iterpool));
+ size = svn_sqlite__column_int64(stmt, 2);
+
+ err = maybe_transfer_one_pristine(src_wcroot, dst_wcroot,
+ checksum, md5_checksum, size,
+ cancel_func, cancel_baton,
+ iterpool);
+
+ if (err)
+ return svn_error_trace(svn_error_compose_create(
+ err,
+ svn_sqlite__reset(stmt)));
+
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_pristine_transfer(svn_wc__db_t *db,
+ const char *src_local_abspath,
+ const char *dst_wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *src_wcroot, *dst_wcroot;
+ const char *src_relpath, *dst_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&src_wcroot, &src_relpath,
+ db, src_local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(src_wcroot);
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&dst_wcroot, &dst_relpath,
+ db, dst_wri_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(dst_wcroot);
+
+ if (src_wcroot == dst_wcroot
+ || src_wcroot->sdb == dst_wcroot->sdb)
+ {
+ return SVN_NO_ERROR; /* Nothing to transfer */
+ }
+
+ SVN_WC__DB_WITH_TXN(
+ pristine_transfer_txn(src_wcroot, dst_wcroot, src_relpath,
+ cancel_func, cancel_baton, scratch_pool),
+ dst_wcroot);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+/* Remove the file at FILE_ABSPATH in such a way that we could re-create a
+ * new file of the same name at any time thereafter.
+ *
+ * On Windows, the file will not disappear immediately from the directory if
+ * it is still being read so the best thing to do is first rename it to a
+ * unique name. */
+static svn_error_t *
+remove_file(const char *file_abspath,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *scratch_pool)
+{
+#ifdef WIN32
+ svn_error_t *err;
+ const char *temp_abspath;
+ const char *temp_dir_abspath
+ = pristine_get_tempdir(wcroot, scratch_pool, scratch_pool);
+
+ /* To rename the file to a unique name in the temp dir, first create a
+ * uniquely named file in the temp dir and then overwrite it. */
+ SVN_ERR(svn_io_open_unique_file3(NULL, &temp_abspath, temp_dir_abspath,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ err = svn_io_file_rename(file_abspath, temp_abspath, scratch_pool);
+ if (err && ignore_enoent && APR_STATUS_IS_ENOENT(err->apr_err))
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+ file_abspath = temp_abspath;
+#endif
+
+ SVN_ERR(svn_io_remove_file2(file_abspath, ignore_enoent, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* If the pristine text referenced by SHA1_CHECKSUM in WCROOT/SDB, whose path
+ * within the pristine store is PRISTINE_ABSPATH, has a reference count of
+ * zero, delete it (both the database row and the disk file).
+ *
+ * This function expects to be executed inside a SQLite txn that has already
+ * acquired a 'RESERVED' lock.
+ */
+static svn_error_t *
+pristine_remove_if_unreferenced_txn(svn_sqlite__db_t *sdb,
+ svn_wc__db_wcroot_t *wcroot,
+ const svn_checksum_t *sha1_checksum,
+ const char *pristine_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ int affected_rows;
+
+ /* Remove the DB row, if refcount is 0. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_DELETE_PRISTINE_IF_UNREFERENCED));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+ /* If we removed the DB row, then remove the file. */
+ if (affected_rows > 0)
+ {
+ /* If the file is not present, something has gone wrong, but at this
+ * point it no longer matters. In a debug build, raise an error, but
+ * in a release build, it is more helpful to ignore it and continue. */
+#ifdef SVN_DEBUG
+ svn_boolean_t ignore_enoent = FALSE;
+#else
+ svn_boolean_t ignore_enoent = TRUE;
+#endif
+
+ SVN_ERR(remove_file(pristine_abspath, wcroot, ignore_enoent,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* If the pristine text referenced by SHA1_CHECKSUM in WCROOT has a
+ * reference count of zero, delete it (both the database row and the disk
+ * file).
+ *
+ * Implements 'notes/wc-ng/pristine-store' section A-3(b). */
+static svn_error_t *
+pristine_remove_if_unreferenced(svn_wc__db_wcroot_t *wcroot,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool)
+{
+ const char *pristine_abspath;
+
+ SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath,
+ sha1_checksum, scratch_pool, scratch_pool));
+
+ /* Ensure the SQL txn has at least a 'RESERVED' lock before we start looking
+ * at the disk, to ensure no concurrent pristine install/delete txn. */
+ SVN_SQLITE__WITH_IMMEDIATE_TXN(
+ pristine_remove_if_unreferenced_txn(
+ wcroot->sdb, wcroot, sha1_checksum, pristine_abspath, scratch_pool),
+ wcroot->sdb);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_pristine_remove(svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+ /* ### Transitional: accept MD-5 and look up the SHA-1. Return an error
+ * if the pristine text is not in the store. */
+ if (sha1_checksum->kind != svn_checksum_sha1)
+ SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, db, wri_abspath,
+ sha1_checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* If the work queue is not empty, don't delete any pristine text because
+ * the work queue may contain a reference to it. */
+ {
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_LOOK_FOR_WORK));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (have_row)
+ return SVN_NO_ERROR;
+ }
+
+ /* If not referenced, remove the PRISTINE table row and the file. */
+ SVN_ERR(pristine_remove_if_unreferenced(wcroot, sha1_checksum, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+pristine_cleanup_wcroot(svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_error_t *err = NULL;
+
+ /* Find each unreferenced pristine in the DB and remove it. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_UNREFERENCED_PRISTINES));
+ while (! err)
+ {
+ svn_boolean_t have_row;
+ const svn_checksum_t *sha1_checksum;
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (! have_row)
+ break;
+
+ SVN_ERR(svn_sqlite__column_checksum(&sha1_checksum, stmt, 0,
+ scratch_pool));
+ err = pristine_remove_if_unreferenced(wcroot, sha1_checksum,
+ scratch_pool);
+ }
+
+ return svn_error_trace(
+ svn_error_compose_create(err, svn_sqlite__reset(stmt)));
+}
+
+svn_error_t *
+svn_wc__db_pristine_cleanup(svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(pristine_cleanup_wcroot(wcroot, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_pristine_check(svn_boolean_t *present,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(sha1_checksum != NULL);
+
+ if (sha1_checksum->kind != svn_checksum_sha1)
+ {
+ *present = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ wri_abspath, scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ /* A filestat is much cheaper than a sqlite transaction especially on NFS,
+ so first check if there is a pristine file and then if we are allowed
+ to use it. */
+ {
+ const char *pristine_abspath;
+ svn_node_kind_t kind_on_disk;
+
+ SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath,
+ sha1_checksum, scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_check_path(pristine_abspath, &kind_on_disk, scratch_pool));
+ if (kind_on_disk != svn_node_file)
+ {
+ *present = FALSE;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Check that there is an entry in the PRISTINE table. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_PRISTINE));
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *present = have_row;
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/wc_db_private.h b/contrib/subversion/subversion/libsvn_wc/wc_db_private.h
new file mode 100644
index 0000000..0679b32
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc_db_private.h
@@ -0,0 +1,458 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ */
+
+/* This file is not for general consumption; it should only be used by
+ wc_db.c. */
+#ifndef SVN_WC__I_AM_WC_DB
+#error "You should not be using these data structures directly"
+#endif /* SVN_WC__I_AM_WC_DB */
+
+#ifndef WC_DB_PRIVATE_H
+#define WC_DB_PRIVATE_H
+
+#include "wc_db.h"
+
+
+struct svn_wc__db_t {
+ /* We need the config whenever we run into a new WC directory, in order
+ to figure out where we should look for the corresponding datastore. */
+ svn_config_t *config;
+
+ /* Should we fail with SVN_ERR_WC_UPGRADE_REQUIRED when it is
+ opened, and found to be not-current? */
+ svn_boolean_t verify_format;
+
+ /* Should we ensure the WORK_QUEUE is empty when a WCROOT is opened? */
+ svn_boolean_t enforce_empty_wq;
+
+ /* Should we open Sqlite databases EXCLUSIVE */
+ svn_boolean_t exclusive;
+
+ /* Map a given working copy directory to its relevant data.
+ const char *local_abspath -> svn_wc__db_wcroot_t *wcroot */
+ apr_hash_t *dir_data;
+
+ /* A few members to assist with caching of kind values for paths. See
+ get_path_kind() for use. */
+ struct
+ {
+ svn_stringbuf_t *abspath;
+ svn_node_kind_t kind;
+ } parse_cache;
+
+ /* As we grow the state of this DB, allocate that state here. */
+ apr_pool_t *state_pool;
+};
+
+
+/* Hold information about an owned lock */
+typedef struct svn_wc__db_wclock_t
+{
+ /* Relative path of the lock root */
+ const char *local_relpath;
+
+ /* Number of levels locked (0 for infinity) */
+ int levels;
+} svn_wc__db_wclock_t;
+
+
+/** Hold information about a WCROOT.
+ *
+ * This structure is referenced by all per-directory handles underneath it.
+ */
+typedef struct svn_wc__db_wcroot_t {
+ /* Location of this wcroot in the filesystem. */
+ const char *abspath;
+
+ /* The SQLite database containing the metadata for everything in
+ this wcroot. */
+ svn_sqlite__db_t *sdb;
+
+ /* The WCROOT.id for this directory (and all its children). */
+ apr_int64_t wc_id;
+
+ /* The format of this wcroot's metadata storage (see wc.h). If the
+ format has not (yet) been determined, this will be UNKNOWN_FORMAT. */
+ int format;
+
+ /* Array of svn_wc__db_wclock_t structures (not pointers!).
+ Typically just one or two locks maximum. */
+ apr_array_header_t *owned_locks;
+
+ /* Map a working copy directory to a cached adm_access baton.
+ const char *local_abspath -> svn_wc_adm_access_t *adm_access */
+ apr_hash_t *access_cache;
+
+} svn_wc__db_wcroot_t;
+
+
+/* */
+svn_error_t *
+svn_wc__db_close_many_wcroots(apr_hash_t *roots,
+ apr_pool_t *state_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Construct a new svn_wc__db_wcroot_t. The WCROOT_ABSPATH and SDB parameters
+ must have lifetime of at least RESULT_POOL. */
+svn_error_t *
+svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot,
+ const char *wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ int format,
+ svn_boolean_t verify_format,
+ svn_boolean_t enforce_empty_wq,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* For a given LOCAL_ABSPATH, figure out what sqlite database (WCROOT) to
+ use and the RELPATH within that wcroot.
+
+ *LOCAL_RELPATH will be allocated within RESULT_POOL. Temporary allocations
+ will be made in SCRATCH_POOL.
+
+ *WCROOT will be allocated within DB->STATE_POOL.
+
+ Certain internal structures will be allocated in DB->STATE_POOL.
+*/
+svn_error_t *
+svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot,
+ const char **local_relpath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Assert that the given WCROOT is usable.
+ NOTE: the expression is multiply-evaluated!! */
+#define VERIFY_USABLE_WCROOT(wcroot) SVN_ERR_ASSERT( \
+ (wcroot) != NULL && (wcroot)->format == SVN_WC__VERSION)
+
+/* Check if the WCROOT is usable for light db operations such as path
+ calculations */
+#define CHECK_MINIMAL_WCROOT(wcroot, abspath, scratch_pool) \
+ do \
+ { \
+ if (wcroot == NULL) \
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, \
+ _("The node '%s' is not in a working copy."), \
+ svn_dirent_local_style(wri_abspath, \
+ scratch_pool)); \
+ } \
+ while (0)
+
+/* Calculates the depth of the relpath below "" */
+APR_INLINE static int
+relpath_depth(const char *relpath)
+{
+ int n = 1;
+ if (*relpath == '\0')
+ return 0;
+
+ do
+ {
+ if (*relpath == '/')
+ n++;
+ }
+ while (*(++relpath));
+
+ return n;
+}
+
+
+/* */
+svn_error_t *
+svn_wc__db_util_fetch_wc_id(apr_int64_t *wc_id,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool);
+
+/* Open a connection in *SDB to the WC database found in the WC metadata
+ * directory inside DIR_ABSPATH, having the filename SDB_FNAME.
+ *
+ * SMODE is passed to svn_sqlite__open().
+ *
+ * Register MY_STATEMENTS, or if that is null, the default set of WC DB
+ * statements, as the set of statements to be prepared now and executed
+ * later. MY_STATEMENTS (the strings and the array itself) is not duplicated
+ * internally, and should have a lifetime at least as long as RESULT_POOL.
+ * See svn_sqlite__open() for details. */
+svn_error_t *
+svn_wc__db_util_open_db(svn_sqlite__db_t **sdb,
+ const char *dir_abspath,
+ const char *sdb_fname,
+ svn_sqlite__mode_t smode,
+ svn_boolean_t exclusive,
+ const char *const *my_statements,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc__db_read_info(), but taking WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH, and outputting repos ids instead of URL+UUID. */
+svn_error_t *
+svn_wc__db_read_info_internal(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ const char **original_repos_relpath,
+ apr_int64_t *original_repos_id,
+ svn_revnum_t *original_revision,
+ svn_wc__db_lock_t **lock,
+ svn_filesize_t *recorded_size,
+ apr_time_t *recorded_mod_time,
+ const char **changelist,
+ svn_boolean_t *conflicted,
+ svn_boolean_t *op_root,
+ svn_boolean_t *had_props,
+ svn_boolean_t *props_mod,
+ svn_boolean_t *have_base,
+ svn_boolean_t *have_more_work,
+ svn_boolean_t *have_work,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH and outputting REPOS_ID instead of URL+UUID. */
+svn_error_t *
+svn_wc__db_base_get_info_internal(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_wc__db_lock_t **lock,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_boolean_t *update_root,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Similar to svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH
+ * instead of DB+LOCAL_ABSPATH, an explicit op-depth of the node to get
+ * information about, and outputting REPOS_ID instead of URL+UUID, and
+ * without the LOCK or UPDATE_ROOT outputs.
+ *
+ * OR
+ *
+ * Similar to svn_wc__db_base_get_info_internal(), but taking an explicit
+ * op-depth OP_DEPTH of the node to get information about, and without the
+ * LOCK or UPDATE_ROOT outputs.
+ *
+ * ### [JAF] TODO: Harmonize svn_wc__db_base_get_info[_internal] with
+ * svn_wc__db_depth_get_info -- common API, common implementation.
+ */
+svn_error_t *
+svn_wc__db_depth_get_info(svn_wc__db_status_t *status,
+ svn_node_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_boolean_t *had_props,
+ apr_hash_t **props,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Look up REPOS_ID in SDB and set *REPOS_ROOT_URL and/or *REPOS_UUID to
+ its root URL and UUID respectively. If REPOS_ID is INVALID_REPOS_ID,
+ use NULL for both URL and UUID. Either or both output parameters may be
+ NULL if not wanted. */
+svn_error_t *
+svn_wc__db_fetch_repos_info(const char **repos_root_url,
+ const char **repos_uuid,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t repos_id,
+ apr_pool_t *result_pool);
+
+/* Like svn_wc__db_read_conflict(), but with WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH, and outputting relpaths instead of abspaths. */
+svn_error_t *
+svn_wc__db_read_conflict_internal(svn_skel_t **conflict,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc__db_op_mark_conflict(), but with WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__db_mark_conflict_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ const svn_skel_t *conflict_skel,
+ apr_pool_t *scratch_pool);
+
+
+/* Transaction handling */
+
+/* A callback which supplies WCROOTs and LOCAL_RELPATHs. */
+typedef svn_error_t *(*svn_wc__db_txn_callback_t)(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool);
+
+
+/* Run CB_FUNC in a SQLite transaction with CB_BATON, using WCROOT and
+ LOCAL_RELPATH. If callbacks require additional information, they may
+ provide it using CB_BATON. */
+svn_error_t *
+svn_wc__db_with_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_txn_callback_t cb_func,
+ void *cb_baton,
+ apr_pool_t *scratch_pool);
+
+/* Evaluate the expression EXPR within a transaction.
+ *
+ * Begin a transaction in WCROOT's DB; evaluate the expression EXPR, which would
+ * typically be a function call that does some work in DB; finally commit
+ * the transaction if EXPR evaluated to SVN_NO_ERROR, otherwise roll back
+ * the transaction.
+ */
+#define SVN_WC__DB_WITH_TXN(expr, wcroot) \
+ SVN_SQLITE__WITH_LOCK(expr, (wcroot)->sdb)
+
+
+/* Return CHILDREN mapping const char * names to svn_node_kind_t * for the
+ children of LOCAL_RELPATH at OP_DEPTH. */
+svn_error_t *
+svn_wc__db_get_children_op_depth(apr_hash_t **children,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Extend any delete of the parent of LOCAL_RELPATH to LOCAL_RELPATH.
+
+ ### What about KIND and OP_DEPTH? KIND ought to be redundant; I'm
+ discussing on dev@ whether we can let that be null for presence
+ == base-deleted. OP_DEPTH is the op-depth of what, and why?
+ It is used to select the lowest working node higher than OP_DEPTH,
+ so, in terms of the API, OP_DEPTH means ...?
+
+ Given a wc:
+
+ 0 1 2 3 4
+ normal
+ A normal
+ A/B normal normal
+ A/B/C not-pres normal
+ A/B/C/D normal
+
+ That is checkout, delete A/B, copy a replacement A/B, delete copied
+ child A/B/C, add replacement A/B/C, add A/B/C/D.
+
+ Now an update that adds base nodes for A/B/C, A/B/C/D and A/B/C/D/E
+ must extend the A/B deletion:
+
+ 0 1 2 3 4
+ normal
+ A normal
+ A/B normal normal
+ A/B/C normal not-pres normal
+ A/B/C/D normal base-del normal
+ A/B/C/D/E normal base-del
+
+ When adding a node if the parent has a higher working node then the
+ parent node is deleted (or replaced) and the delete must be extended
+ to cover new node.
+
+ In the example above A/B/C/D and A/B/C/D/E are the nodes that get
+ the extended delete, A/B/C is already deleted.
+ */
+svn_error_t *
+svn_wc__db_extend_parent_delete(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_node_kind_t kind,
+ int op_depth,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__db_retract_parent_delete(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__db_op_depth_moved_to(const char **move_dst_relpath,
+ const char **move_dst_op_root_relpath,
+ const char **move_src_root_relpath,
+ const char **move_src_op_root_relpath,
+ int op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Do a post-drive revision bump for the moved-away destination for
+ any move sources under LOCAL_RELPATH. This is called from within
+ the revision bump transaction after the tree at LOCAL_RELPATH has
+ been bumped. */
+svn_error_t *
+svn_wc__db_bump_moved_away(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_depth_t depth,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__db_resolve_break_moved_away_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool);
+
+svn_error_t *
+svn_wc__db_update_move_list_notify(svn_wc__db_wcroot_t *wcroot,
+ svn_revnum_t old_revision,
+ svn_revnum_t new_revision,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
+#endif /* WC_DB_PRIVATE_H */
diff --git a/contrib/subversion/subversion/libsvn_wc/wc_db_update_move.c b/contrib/subversion/subversion/libsvn_wc/wc_db_update_move.c
new file mode 100644
index 0000000..fa5afe4
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc_db_update_move.c
@@ -0,0 +1,2631 @@
+/*
+ * wc_db_update_move.c : updating moves during tree-conflict resolution
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* This file implements an editor and an edit driver which are used
+ * to resolve an "incoming edit, local move-away" tree conflict resulting
+ * from an update (or switch).
+ *
+ * Our goal is to be able to resolve this conflict such that the end
+ * result is just the same as if the user had run the update *before*
+ * the local move.
+ *
+ * When an update (or switch) produces incoming changes for a locally
+ * moved-away subtree, it updates the base nodes of the moved-away tree
+ * and flags a tree-conflict on the moved-away root node.
+ * This editor transfers these changes from the moved-away part of the
+ * working copy to the corresponding moved-here part of the working copy.
+ *
+ * Both the driver and receiver components of the editor are implemented
+ * in this file.
+ *
+ * The driver sees two NODES trees: the move source tree and the move
+ * destination tree. When the move is initially made these trees are
+ * equivalent, the destination is a copy of the source. The source is
+ * a single-op-depth, single-revision, deleted layer [1] and the
+ * destination has an equivalent single-op-depth, single-revision
+ * layer. The destination may have additional higher op-depths
+ * representing adds, deletes, moves within the move destination. [2]
+ *
+ * After the intial move an update has modified the NODES in the move
+ * source and may have introduced a tree-conflict since the source and
+ * destination trees are no longer equivalent. The source is a
+ * different revision and may have text, property and tree changes
+ * compared to the destination. The driver will compare the two NODES
+ * trees and drive an editor to change the destination tree so that it
+ * once again matches the source tree. Changes made to the
+ * destination NODES tree to achieve this match will be merged into
+ * the working files/directories.
+ *
+ * The whole drive occurs as one single wc.db transaction. At the end
+ * of the transaction the destination NODES table should have a layer
+ * that is equivalent to the source NODES layer, there should be
+ * workqueue items to make any required changes to working
+ * files/directories in the move destination, and there should be
+ * tree-conflicts in the move destination where it was not possible to
+ * update the working files/directories.
+ *
+ * [1] The move source tree is single-revision because we currently do
+ * not allow a mixed-rev move, and therefore it is single op-depth
+ * regardless whether it is a base layer or a nested move.
+ *
+ * [2] The source tree also may have additional higher op-depths,
+ * representing a replacement, but this editor only reads from the
+ * single-op-depth layer of it, and makes no changes of any kind
+ * within the source tree.
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include <assert.h>
+
+#include "svn_checksum.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_editor.h"
+
+#include "wc.h"
+#include "props.h"
+#include "wc_db_private.h"
+#include "wc-queries.h"
+#include "conflicts.h"
+#include "workqueue.h"
+#include "token-map.h"
+
+/*
+ * Receiver code.
+ *
+ * The receiver is an editor that, when driven with a certain change, will
+ * merge the edits into the working/actual state of the move destination
+ * at MOVE_ROOT_DST_RELPATH (in struct tc_editor_baton), perhaps raising
+ * conflicts if necessary.
+ *
+ * The receiver should not need to refer directly to the move source, as
+ * the driver should provide all relevant information about the change to
+ * be made at the move destination.
+ */
+
+struct tc_editor_baton {
+ svn_wc__db_t *db;
+ svn_wc__db_wcroot_t *wcroot;
+ const char *move_root_dst_relpath;
+
+ /* The most recent conflict raised during this drive. We rely on the
+ non-Ev2, depth-first, drive for this to make sense. */
+ const char *conflict_root_relpath;
+
+ svn_wc_operation_t operation;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+ apr_pool_t *result_pool; /* For things that live as long as the baton. */
+};
+
+/*
+ * Notifications are delayed until the entire update-move transaction
+ * completes. These functions provide the necessary support by storing
+ * notification information in a temporary db table (the "update_move_list")
+ * and spooling notifications out of that table after the transaction.
+ */
+
+/* Add an entry to the notification list. */
+static svn_error_t *
+update_move_list_add(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc_notify_action_t action,
+ svn_node_kind_t kind,
+ svn_wc_notify_state_t content_state,
+ svn_wc_notify_state_t prop_state)
+
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_INSERT_UPDATE_MOVE_LIST));
+ SVN_ERR(svn_sqlite__bindf(stmt, "sdddd", local_relpath,
+ action, kind, content_state, prop_state));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Send all notifications stored in the notification list, and then
+ * remove the temporary database table. */
+svn_error_t *
+svn_wc__db_update_move_list_notify(svn_wc__db_wcroot_t *wcroot,
+ svn_revnum_t old_revision,
+ svn_revnum_t new_revision,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ if (notify_func)
+ {
+ apr_pool_t *iterpool;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_UPDATE_MOVE_LIST));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *local_relpath;
+ svn_wc_notify_action_t action;
+ svn_wc_notify_t *notify;
+
+ svn_pool_clear(iterpool);
+
+ local_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ action = svn_sqlite__column_int(stmt, 1);
+ notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ iterpool),
+ action, iterpool);
+ notify->kind = svn_sqlite__column_int(stmt, 2);
+ notify->content_state = svn_sqlite__column_int(stmt, 3);
+ notify->prop_state = svn_sqlite__column_int(stmt, 4);
+ notify->old_revision = old_revision;
+ notify->revision = new_revision;
+ notify_func(notify_baton, notify, scratch_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_FINALIZE_UPDATE_MOVE));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Mark a tree-conflict on LOCAL_RELPATH if such a tree-conflict does
+ not already exist. */
+static svn_error_t *
+mark_tree_conflict(const char *local_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_wc__db_t *db,
+ const svn_wc_conflict_version_t *old_version,
+ const svn_wc_conflict_version_t *new_version,
+ const char *move_root_dst_relpath,
+ svn_wc_operation_t operation,
+ svn_node_kind_t old_kind,
+ svn_node_kind_t new_kind,
+ const char *old_repos_relpath,
+ svn_wc_conflict_reason_t reason,
+ svn_wc_conflict_action_t action,
+ const char *move_src_op_root_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_skel_t *conflict;
+ svn_wc_conflict_version_t *conflict_old_version, *conflict_new_version;
+ const char *move_src_op_root_abspath
+ = move_src_op_root_relpath
+ ? svn_dirent_join(wcroot->abspath,
+ move_src_op_root_relpath, scratch_pool)
+ : NULL;
+ const char *old_repos_relpath_part
+ = old_repos_relpath
+ ? svn_relpath_skip_ancestor(old_version->path_in_repos,
+ old_repos_relpath)
+ : NULL;
+ const char *new_repos_relpath
+ = old_repos_relpath_part
+ ? svn_relpath_join(new_version->path_in_repos, old_repos_relpath_part,
+ scratch_pool)
+ : NULL;
+
+ if (!new_repos_relpath)
+ new_repos_relpath
+ = svn_relpath_join(new_version->path_in_repos,
+ svn_relpath_skip_ancestor(move_root_dst_relpath,
+ local_relpath),
+ scratch_pool);
+
+ err = svn_wc__db_read_conflict_internal(&conflict, wcroot, local_relpath,
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ else if (err)
+ {
+ svn_error_clear(err);
+ conflict = NULL;
+ }
+
+ if (conflict)
+ {
+ svn_wc_operation_t conflict_operation;
+ svn_boolean_t tree_conflicted;
+
+ SVN_ERR(svn_wc__conflict_read_info(&conflict_operation, NULL, NULL, NULL,
+ &tree_conflicted,
+ db, wcroot->abspath, conflict,
+ scratch_pool, scratch_pool));
+
+ if (conflict_operation != svn_wc_operation_update
+ && conflict_operation != svn_wc_operation_switch)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' already in conflict"),
+ svn_dirent_local_style(local_relpath,
+ scratch_pool));
+
+ if (tree_conflicted)
+ {
+ svn_wc_conflict_reason_t existing_reason;
+ svn_wc_conflict_action_t existing_action;
+ const char *existing_abspath;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&existing_reason,
+ &existing_action,
+ &existing_abspath,
+ db, wcroot->abspath,
+ conflict,
+ scratch_pool,
+ scratch_pool));
+ if (reason != existing_reason
+ || action != existing_action
+ || (reason == svn_wc_conflict_reason_moved_away
+ && strcmp(move_src_op_root_relpath,
+ svn_dirent_skip_ancestor(wcroot->abspath,
+ existing_abspath))))
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' already in conflict"),
+ svn_dirent_local_style(local_relpath,
+ scratch_pool));
+
+ /* Already a suitable tree-conflict. */
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ conflict = svn_wc__conflict_skel_create(scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
+ conflict, db,
+ svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool),
+ reason,
+ action,
+ move_src_op_root_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ if (reason != svn_wc_conflict_reason_unversioned
+ && old_repos_relpath != NULL /* no local additions */)
+ {
+ conflict_old_version = svn_wc_conflict_version_create2(
+ old_version->repos_url, old_version->repos_uuid,
+ old_repos_relpath, old_version->peg_rev,
+ old_kind, scratch_pool);
+ }
+ else
+ conflict_old_version = NULL;
+
+ conflict_new_version = svn_wc_conflict_version_create2(
+ new_version->repos_url, new_version->repos_uuid,
+ new_repos_relpath, new_version->peg_rev,
+ new_kind, scratch_pool);
+
+ if (operation == svn_wc_operation_update)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(
+ conflict, conflict_old_version, conflict_new_version,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ assert(operation == svn_wc_operation_switch);
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(
+ conflict, conflict_old_version, conflict_new_version,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict, scratch_pool));
+
+ SVN_ERR(update_move_list_add(wcroot, local_relpath,
+ svn_wc_notify_tree_conflict,
+ new_kind,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ return SVN_NO_ERROR;
+}
+
+/* If LOCAL_RELPATH is a child of the most recently raised
+ tree-conflict or is shadowed then set *IS_CONFLICTED to TRUE and
+ raise a tree-conflict on the root of the obstruction if such a
+ tree-conflict does not already exist. KIND is the kind of the
+ incoming LOCAL_RELPATH. This relies on the non-Ev2, depth-first,
+ drive. */
+static svn_error_t *
+check_tree_conflict(svn_boolean_t *is_conflicted,
+ struct tc_editor_baton *b,
+ const char *local_relpath,
+ svn_node_kind_t old_kind,
+ svn_node_kind_t new_kind,
+ const char *old_repos_relpath,
+ svn_wc_conflict_action_t action,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int dst_op_depth = relpath_depth(b->move_root_dst_relpath);
+ int op_depth;
+ const char *conflict_root_relpath = local_relpath;
+ const char *move_dst_relpath, *dummy1;
+ const char *dummy2, *move_src_op_root_relpath;
+
+ if (b->conflict_root_relpath)
+ {
+ if (svn_relpath_skip_ancestor(b->conflict_root_relpath, local_relpath))
+ {
+ *is_conflicted = TRUE;
+ return SVN_NO_ERROR;
+ }
+ b->conflict_root_relpath = NULL;
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_SELECT_LOWEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, local_relpath,
+ dst_op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (!have_row)
+ {
+ *is_conflicted = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ *is_conflicted = TRUE;
+
+ while (relpath_depth(conflict_root_relpath) > op_depth)
+ {
+ conflict_root_relpath = svn_relpath_dirname(conflict_root_relpath,
+ scratch_pool);
+ old_kind = new_kind = svn_node_dir;
+ if (old_repos_relpath)
+ old_repos_relpath = svn_relpath_dirname(old_repos_relpath,
+ scratch_pool);
+ action = svn_wc_conflict_action_edit;
+ }
+
+ SVN_ERR(svn_wc__db_op_depth_moved_to(&move_dst_relpath,
+ &dummy1,
+ &dummy2,
+ &move_src_op_root_relpath,
+ dst_op_depth,
+ b->wcroot, conflict_root_relpath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(mark_tree_conflict(conflict_root_relpath,
+ b->wcroot, b->db, b->old_version, b->new_version,
+ b->move_root_dst_relpath, b->operation,
+ old_kind, new_kind,
+ old_repos_relpath,
+ (move_dst_relpath
+ ? svn_wc_conflict_reason_moved_away
+ : svn_wc_conflict_reason_deleted),
+ action, move_src_op_root_relpath,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, conflict_root_relpath);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_add_directory(void *baton,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ int op_depth = relpath_depth(b->move_root_dst_relpath);
+ const char *move_dst_repos_relpath;
+ svn_node_kind_t move_dst_kind;
+ svn_boolean_t is_conflicted;
+ const char *abspath;
+ svn_node_kind_t old_kind;
+ svn_skel_t *work_item;
+ svn_wc_notify_action_t action = svn_wc_notify_update_add;
+ svn_error_t *err;
+
+ /* Update NODES, only the bits not covered by the later call to
+ replace_moved_layer. */
+ SVN_ERR(svn_wc__db_extend_parent_delete(b->wcroot, relpath, svn_node_dir,
+ op_depth, scratch_pool));
+
+ err = svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ b->wcroot, relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ old_kind = svn_node_none;
+ move_dst_repos_relpath = NULL;
+ }
+ else
+ {
+ SVN_ERR(err);
+ old_kind = move_dst_kind;
+ }
+
+ /* Check for NODES tree-conflict. */
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath,
+ old_kind, svn_node_dir,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_add,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ /* Check for unversioned tree-conflict */
+ abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool);
+ SVN_ERR(svn_io_check_path(abspath, &old_kind, scratch_pool));
+
+ switch (old_kind)
+ {
+ case svn_node_file:
+ default:
+ SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version,
+ b->new_version, b->move_root_dst_relpath,
+ b->operation, old_kind, svn_node_dir,
+ move_dst_repos_relpath,
+ svn_wc_conflict_reason_unversioned,
+ svn_wc_conflict_action_add, NULL,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath);
+ action = svn_wc_notify_tree_conflict;
+ is_conflicted = TRUE;
+ break;
+
+ case svn_node_none:
+ SVN_ERR(svn_wc__wq_build_dir_install(&work_item, b->db, abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ scratch_pool));
+ /* Fall through */
+ case svn_node_dir:
+ break;
+ }
+
+ if (!is_conflicted)
+ SVN_ERR(update_move_list_add(b->wcroot, relpath,
+ action,
+ svn_node_dir,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_add_file(void *baton,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ int op_depth = relpath_depth(b->move_root_dst_relpath);
+ const char *move_dst_repos_relpath;
+ svn_node_kind_t move_dst_kind;
+ svn_node_kind_t old_kind;
+ svn_boolean_t is_conflicted;
+ const char *abspath;
+ svn_skel_t *work_item;
+ svn_error_t *err;
+
+ /* Update NODES, only the bits not covered by the later call to
+ replace_moved_layer. */
+ SVN_ERR(svn_wc__db_extend_parent_delete(b->wcroot, relpath, svn_node_file,
+ op_depth, scratch_pool));
+
+ err = svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ b->wcroot, relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ old_kind = svn_node_none;
+ move_dst_repos_relpath = NULL;
+ }
+ else
+ {
+ SVN_ERR(err);
+ old_kind = move_dst_kind;
+ }
+
+ /* Check for NODES tree-conflict. */
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath,
+ old_kind, svn_node_file, move_dst_repos_relpath,
+ svn_wc_conflict_action_add,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ /* Check for unversioned tree-conflict */
+ abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool);
+ SVN_ERR(svn_io_check_path(abspath, &old_kind, scratch_pool));
+
+ if (old_kind != svn_node_none)
+ {
+ SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version,
+ b->new_version, b->move_root_dst_relpath,
+ b->operation, old_kind, svn_node_file,
+ move_dst_repos_relpath,
+ svn_wc_conflict_reason_unversioned,
+ svn_wc_conflict_action_add, NULL,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath);
+ return SVN_NO_ERROR;
+ }
+
+ /* Update working file. */
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, b->db,
+ svn_dirent_join(b->wcroot->abspath,
+ relpath,
+ scratch_pool),
+ NULL,
+ FALSE /* FIXME: use_commit_times? */,
+ TRUE /* record_file_info */,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ scratch_pool));
+
+ SVN_ERR(update_move_list_add(b->wcroot, relpath,
+ svn_wc_notify_update_add,
+ svn_node_file,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_add_symlink(void *baton,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_add_absent(void *baton,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+/* All the info we need about one version of a working node. */
+typedef struct working_node_version_t
+{
+ svn_wc_conflict_version_t *location_and_kind;
+ apr_hash_t *props;
+ const svn_checksum_t *checksum; /* for files only */
+} working_node_version_t;
+
+/* Return *WORK_ITEMS to create a conflict on LOCAL_ABSPATH. */
+static svn_error_t *
+create_conflict_markers(svn_skel_t **work_items,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ const char *repos_relpath,
+ svn_skel_t *conflict_skel,
+ svn_wc_operation_t operation,
+ const working_node_version_t *old_version,
+ const working_node_version_t *new_version,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_conflict_version_t *original_version;
+ svn_wc_conflict_version_t *conflicted_version;
+ const char *part;
+
+ original_version = svn_wc_conflict_version_dup(
+ old_version->location_and_kind, scratch_pool);
+ original_version->node_kind = kind;
+ conflicted_version = svn_wc_conflict_version_dup(
+ new_version->location_and_kind, scratch_pool);
+ conflicted_version->node_kind = kind;
+
+ part = svn_relpath_skip_ancestor(original_version->path_in_repos,
+ repos_relpath);
+ conflicted_version->path_in_repos
+ = svn_relpath_join(conflicted_version->path_in_repos, part, scratch_pool);
+ original_version->path_in_repos = repos_relpath;
+
+ if (operation == svn_wc_operation_update)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(
+ conflict_skel, original_version,
+ conflicted_version,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(
+ conflict_skel, original_version,
+ conflicted_version,
+ scratch_pool, scratch_pool));
+ }
+
+ /* According to this func's doc string, it is "Currently only used for
+ * property conflicts as text conflict markers are just in-wc files." */
+ SVN_ERR(svn_wc__conflict_create_markers(work_items, db,
+ local_abspath,
+ conflict_skel,
+ result_pool,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+update_working_props(svn_wc_notify_state_t *prop_state,
+ svn_skel_t **conflict_skel,
+ apr_array_header_t **propchanges,
+ apr_hash_t **actual_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const struct working_node_version_t *old_version,
+ const struct working_node_version_t *new_version,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *new_actual_props;
+ apr_array_header_t *new_propchanges;
+
+ /*
+ * Run a 3-way prop merge to update the props, using the pre-update
+ * props as the merge base, the post-update props as the
+ * merge-left version, and the current props of the
+ * moved-here working file as the merge-right version.
+ */
+ SVN_ERR(svn_wc__db_read_props(actual_props,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_prop_diffs(propchanges, new_version->props, old_version->props,
+ result_pool));
+ SVN_ERR(svn_wc__merge_props(conflict_skel, prop_state,
+ &new_actual_props,
+ db, local_abspath,
+ old_version->props, old_version->props,
+ *actual_props, *propchanges,
+ result_pool, scratch_pool));
+
+ /* Setting properties in ACTUAL_NODE with svn_wc__db_op_set_props
+ relies on NODES row having been updated first which we don't do
+ at present. So this extra property diff has the same effect.
+
+ ### Perhaps we should update NODES first (but after
+ ### svn_wc__db_read_props above)? */
+ SVN_ERR(svn_prop_diffs(&new_propchanges, new_actual_props, new_version->props,
+ scratch_pool));
+ if (!new_propchanges->nelts)
+ new_actual_props = NULL;
+
+ /* Install the new actual props. Don't set the conflict_skel yet, because
+ we might need to add a text conflict to it as well. */
+ SVN_ERR(svn_wc__db_op_set_props(db, local_abspath,
+ new_actual_props,
+ svn_wc__has_magic_property(*propchanges),
+ NULL/*conflict_skel*/, NULL/*work_items*/,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_alter_directory(void *baton,
+ const char *dst_relpath,
+ svn_revnum_t expected_move_dst_revision,
+ const apr_array_header_t *children,
+ apr_hash_t *new_props,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ const char *move_dst_repos_relpath;
+ svn_revnum_t move_dst_revision;
+ svn_node_kind_t move_dst_kind;
+ working_node_version_t old_version, new_version;
+ svn_wc__db_status_t status;
+ svn_boolean_t is_conflicted;
+
+ SVN_ERR_ASSERT(expected_move_dst_revision == b->old_version->peg_rev);
+
+ SVN_ERR(svn_wc__db_depth_get_info(&status, &move_dst_kind, &move_dst_revision,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, &old_version.checksum, NULL,
+ NULL, &old_version.props,
+ b->wcroot, dst_relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool));
+
+ /* If the node would be recorded as svn_wc__db_status_base_deleted it
+ wouldn't have a repos_relpath */
+ /* ### Can svn_wc__db_depth_get_info() do this for us without this hint? */
+ if (status == svn_wc__db_status_deleted && move_dst_repos_relpath)
+ status = svn_wc__db_status_not_present;
+
+ /* There might be not-present nodes of a different revision as the same
+ depth as a copy. This is commonly caused by copying/moving mixed revision
+ directories */
+ SVN_ERR_ASSERT(move_dst_revision == expected_move_dst_revision
+ || status == svn_wc__db_status_not_present);
+ SVN_ERR_ASSERT(move_dst_kind == svn_node_dir);
+
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, dst_relpath,
+ move_dst_kind,
+ svn_node_dir,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_edit,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ old_version.location_and_kind = b->old_version;
+ new_version.location_and_kind = b->new_version;
+
+ new_version.checksum = NULL; /* not a file */
+ new_version.props = new_props ? new_props : old_version.props;
+
+ if (new_props)
+ {
+ const char *dst_abspath = svn_dirent_join(b->wcroot->abspath,
+ dst_relpath,
+ scratch_pool);
+ svn_wc_notify_state_t prop_state;
+ svn_skel_t *conflict_skel = NULL;
+ apr_hash_t *actual_props;
+ apr_array_header_t *propchanges;
+
+ SVN_ERR(update_working_props(&prop_state, &conflict_skel,
+ &propchanges, &actual_props,
+ b->db, dst_abspath,
+ &old_version, &new_version,
+ scratch_pool, scratch_pool));
+
+ if (conflict_skel)
+ {
+ svn_skel_t *work_items;
+
+ SVN_ERR(create_conflict_markers(&work_items, dst_abspath,
+ b->db, move_dst_repos_relpath,
+ conflict_skel, b->operation,
+ &old_version, &new_version,
+ svn_node_dir,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_mark_conflict_internal(b->wcroot, dst_relpath,
+ conflict_skel,
+ scratch_pool));
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_items,
+ scratch_pool));
+ }
+
+ SVN_ERR(update_move_list_add(b->wcroot, dst_relpath,
+ svn_wc_notify_update_update,
+ svn_node_dir,
+ svn_wc_notify_state_inapplicable,
+ prop_state));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Merge the difference between OLD_VERSION and NEW_VERSION into
+ * the working file at LOCAL_RELPATH.
+ *
+ * The term 'old' refers to the pre-update state, which is the state of
+ * (some layer of) LOCAL_RELPATH while this function runs; and 'new'
+ * refers to the post-update state, as found at the (base layer of) the
+ * move source path while this function runs.
+ *
+ * LOCAL_RELPATH is a file in the working copy at WCROOT in DB, and
+ * REPOS_RELPATH is the repository path it would be committed to.
+ *
+ * Use NOTIFY_FUNC and NOTIFY_BATON for notifications.
+ * Set *WORK_ITEMS to any required work items, allocated in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+update_working_file(const char *local_relpath,
+ const char *repos_relpath,
+ svn_wc_operation_t operation,
+ const working_node_version_t *old_version,
+ const working_node_version_t *new_version,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath = svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool);
+ const char *old_pristine_abspath;
+ const char *new_pristine_abspath;
+ svn_skel_t *conflict_skel = NULL;
+ apr_hash_t *actual_props;
+ apr_array_header_t *propchanges;
+ enum svn_wc_merge_outcome_t merge_outcome;
+ svn_wc_notify_state_t prop_state, content_state;
+ svn_skel_t *work_item, *work_items = NULL;
+
+ SVN_ERR(update_working_props(&prop_state, &conflict_skel, &propchanges,
+ &actual_props, db, local_abspath,
+ old_version, new_version,
+ scratch_pool, scratch_pool));
+
+ if (!svn_checksum_match(new_version->checksum, old_version->checksum))
+ {
+ svn_boolean_t is_locally_modified;
+
+ SVN_ERR(svn_wc__internal_file_modified_p(&is_locally_modified,
+ db, local_abspath,
+ FALSE /* exact_comparison */,
+ scratch_pool));
+ if (!is_locally_modified)
+ {
+ SVN_ERR(svn_wc__wq_build_file_install(&work_item, db,
+ local_abspath,
+ NULL,
+ FALSE /* FIXME: use_commit_times? */,
+ TRUE /* record_file_info */,
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ content_state = svn_wc_notify_state_changed;
+ }
+ else
+ {
+ /*
+ * Run a 3-way merge to update the file, using the pre-update
+ * pristine text as the merge base, the post-update pristine
+ * text as the merge-left version, and the current content of the
+ * moved-here working file as the merge-right version.
+ */
+ SVN_ERR(svn_wc__db_pristine_get_path(&old_pristine_abspath,
+ db, wcroot->abspath,
+ old_version->checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_pristine_get_path(&new_pristine_abspath,
+ db, wcroot->abspath,
+ new_version->checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel,
+ &merge_outcome, db,
+ old_pristine_abspath,
+ new_pristine_abspath,
+ local_abspath,
+ local_abspath,
+ NULL, NULL, NULL, /* diff labels */
+ actual_props,
+ FALSE, /* dry-run */
+ NULL, /* diff3-cmd */
+ NULL, /* merge options */
+ propchanges,
+ NULL, NULL, /* cancel_func + baton */
+ scratch_pool, scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+ if (merge_outcome == svn_wc_merge_conflict)
+ content_state = svn_wc_notify_state_conflicted;
+ else
+ content_state = svn_wc_notify_state_merged;
+ }
+ }
+ else
+ content_state = svn_wc_notify_state_unchanged;
+
+ /* If there are any conflicts to be stored, convert them into work items
+ * too. */
+ if (conflict_skel)
+ {
+ SVN_ERR(create_conflict_markers(&work_item, local_abspath, db,
+ repos_relpath, conflict_skel,
+ operation, old_version, new_version,
+ svn_node_file,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath,
+ conflict_skel,
+ scratch_pool));
+
+ work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+ }
+
+ SVN_ERR(svn_wc__db_wq_add(db, wcroot->abspath, work_items, scratch_pool));
+
+ SVN_ERR(update_move_list_add(wcroot, local_relpath,
+ svn_wc_notify_update_update,
+ svn_node_file,
+ content_state,
+ prop_state));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Edit the file found at the move destination, which is initially at
+ * the old state. Merge the changes into the "working"/"actual" file.
+ */
+static svn_error_t *
+tc_editor_alter_file(void *baton,
+ const char *dst_relpath,
+ svn_revnum_t expected_move_dst_revision,
+ apr_hash_t *new_props,
+ const svn_checksum_t *new_checksum,
+ svn_stream_t *new_contents,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ const char *move_dst_repos_relpath;
+ svn_revnum_t move_dst_revision;
+ svn_node_kind_t move_dst_kind;
+ working_node_version_t old_version, new_version;
+ svn_boolean_t is_conflicted;
+ svn_wc__db_status_t status;
+
+ SVN_ERR(svn_wc__db_depth_get_info(&status, &move_dst_kind, &move_dst_revision,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, &old_version.checksum, NULL,
+ NULL, &old_version.props,
+ b->wcroot, dst_relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool));
+
+ /* If the node would be recorded as svn_wc__db_status_base_deleted it
+ wouldn't have a repos_relpath */
+ /* ### Can svn_wc__db_depth_get_info() do this for us without this hint? */
+ if (status == svn_wc__db_status_deleted && move_dst_repos_relpath)
+ status = svn_wc__db_status_not_present;
+
+ SVN_ERR_ASSERT(move_dst_revision == expected_move_dst_revision
+ || status == svn_wc__db_status_not_present);
+ SVN_ERR_ASSERT(move_dst_kind == svn_node_file);
+
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, dst_relpath,
+ move_dst_kind,
+ svn_node_file,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_edit,
+ scratch_pool));
+ if (is_conflicted)
+ return SVN_NO_ERROR;
+
+ old_version.location_and_kind = b->old_version;
+ new_version.location_and_kind = b->new_version;
+
+ /* If new checksum is null that means no change; similarly props. */
+ new_version.checksum = new_checksum ? new_checksum : old_version.checksum;
+ new_version.props = new_props ? new_props : old_version.props;
+
+ /* Update file and prop contents if the update has changed them. */
+ if (!svn_checksum_match(new_checksum, old_version.checksum) || new_props)
+ {
+ SVN_ERR(update_working_file(dst_relpath, move_dst_repos_relpath,
+ b->operation, &old_version, &new_version,
+ b->wcroot, b->db,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_alter_symlink(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_delete(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ svn_sqlite__stmt_t *stmt;
+ int op_depth = relpath_depth(b->move_root_dst_relpath);
+ const char *move_dst_repos_relpath;
+ svn_node_kind_t move_dst_kind;
+ svn_boolean_t is_conflicted;
+ svn_boolean_t must_delete_working_nodes = FALSE;
+ const char *local_abspath = svn_dirent_join(b->wcroot->abspath, relpath,
+ scratch_pool);
+ const char *parent_relpath = svn_relpath_dirname(relpath, scratch_pool);
+ int op_depth_below;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL,
+ &move_dst_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ b->wcroot, relpath,
+ relpath_depth(b->move_root_dst_relpath),
+ scratch_pool, scratch_pool));
+
+ /* Check before retracting delete to catch delete-delete
+ conflicts. This catches conflicts on the node itself; deleted
+ children are caught as local modifications below.*/
+ SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath,
+ move_dst_kind,
+ svn_node_unknown,
+ move_dst_repos_relpath,
+ svn_wc_conflict_action_delete,
+ scratch_pool));
+
+ if (!is_conflicted)
+ {
+ svn_boolean_t is_modified, is_all_deletes;
+
+ SVN_ERR(svn_wc__node_has_local_mods(&is_modified, &is_all_deletes, b->db,
+ local_abspath,
+ NULL, NULL, scratch_pool));
+ if (is_modified)
+ {
+ svn_wc_conflict_reason_t reason;
+
+ if (!is_all_deletes)
+ {
+ /* No conflict means no NODES rows at the relpath op-depth
+ so it's easy to convert the modified tree into a copy. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_UPDATE_OP_DEPTH_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd", b->wcroot->wc_id, relpath,
+ op_depth, relpath_depth(relpath)));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ reason = svn_wc_conflict_reason_edited;
+ }
+ else
+ {
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_DELETE_WORKING_OP_DEPTH_ABOVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ reason = svn_wc_conflict_reason_deleted;
+ must_delete_working_nodes = TRUE;
+ }
+ is_conflicted = TRUE;
+ SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version,
+ b->new_version, b->move_root_dst_relpath,
+ b->operation,
+ move_dst_kind,
+ svn_node_none,
+ move_dst_repos_relpath, reason,
+ svn_wc_conflict_action_delete, NULL,
+ scratch_pool));
+ b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath);
+ }
+ }
+
+ if (!is_conflicted || must_delete_working_nodes)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_skel_t *work_item;
+ svn_node_kind_t del_kind;
+ const char *del_abspath;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_SELECT_CHILDREN_OP_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ del_kind = svn_sqlite__column_token(stmt, 1, kind_map);
+ del_abspath = svn_dirent_join(b->wcroot->abspath,
+ svn_sqlite__column_text(stmt, 0, NULL),
+ iterpool);
+ if (del_kind == svn_node_dir)
+ err = svn_wc__wq_build_dir_remove(&work_item, b->db,
+ b->wcroot->abspath, del_abspath,
+ FALSE /* recursive */,
+ iterpool, iterpool);
+ else
+ err = svn_wc__wq_build_file_remove(&work_item, b->db,
+ b->wcroot->abspath, del_abspath,
+ iterpool, iterpool);
+ if (!err)
+ err = svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ iterpool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, &del_kind, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ b->wcroot, relpath, op_depth,
+ iterpool, iterpool));
+ if (del_kind == svn_node_dir)
+ SVN_ERR(svn_wc__wq_build_dir_remove(&work_item, b->db,
+ b->wcroot->abspath, local_abspath,
+ FALSE /* recursive */,
+ iterpool, iterpool));
+ else
+ SVN_ERR(svn_wc__wq_build_file_remove(&work_item, b->db,
+ b->wcroot->abspath, local_abspath,
+ iterpool, iterpool));
+ SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item,
+ iterpool));
+
+ if (!is_conflicted)
+ SVN_ERR(update_move_list_add(b->wcroot, relpath,
+ svn_wc_notify_update_delete,
+ del_kind,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ svn_pool_destroy(iterpool);
+ }
+
+ /* Deleting the ROWS is valid so long as we update the parent before
+ committing the transaction. The removed rows could have been
+ replacing a lower layer in which case we need to add base-deleted
+ rows. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_SELECT_HIGHEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, parent_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ op_depth_below = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (have_row)
+ {
+ /* Remove non-shadowing nodes. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_DELETE_NO_LOWER_LAYER));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd", b->wcroot->wc_id, relpath,
+ op_depth, op_depth_below));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* Convert remaining shadowing nodes to presence='base-deleted'. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_REPLACE_WITH_BASE_DELETED));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb,
+ STMT_DELETE_WORKING_OP_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+ }
+
+ /* Retract any base-delete. */
+ SVN_ERR(svn_wc__db_retract_parent_delete(b->wcroot, relpath, op_depth,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_copy(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_move(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_rotate(void *baton,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_complete(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_abort(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/* The editor callback table implementing the receiver. */
+static const svn_editor_cb_many_t editor_ops = {
+ tc_editor_add_directory,
+ tc_editor_add_file,
+ tc_editor_add_symlink,
+ tc_editor_add_absent,
+ tc_editor_alter_directory,
+ tc_editor_alter_file,
+ tc_editor_alter_symlink,
+ tc_editor_delete,
+ tc_editor_copy,
+ tc_editor_move,
+ tc_editor_rotate,
+ tc_editor_complete,
+ tc_editor_abort
+};
+
+
+/*
+ * Driver code.
+ *
+ * The scenario is that a subtree has been locally moved, and then the base
+ * layer on the source side of the move has received an update to a new
+ * state. The destination subtree has not yet been updated, and still
+ * matches the pre-update state of the source subtree.
+ *
+ * The edit driver drives the receiver with the difference between the
+ * pre-update state (as found now at the move-destination) and the
+ * post-update state (found now at the move-source).
+ *
+ * We currently assume that both the pre-update and post-update states are
+ * single-revision.
+ */
+
+/* Set *OPERATION, *LOCAL_CHANGE, *INCOMING_CHANGE, *OLD_VERSION, *NEW_VERSION
+ * to reflect the tree conflict on the victim SRC_ABSPATH in DB.
+ *
+ * If SRC_ABSPATH is not a tree-conflict victim, return an error.
+ */
+static svn_error_t *
+get_tc_info(svn_wc_operation_t *operation,
+ svn_wc_conflict_reason_t *local_change,
+ svn_wc_conflict_action_t *incoming_change,
+ const char **move_src_op_root_abspath,
+ svn_wc_conflict_version_t **old_version,
+ svn_wc_conflict_version_t **new_version,
+ svn_wc__db_t *db,
+ const char *src_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *locations;
+ svn_boolean_t tree_conflicted;
+ svn_skel_t *conflict_skel;
+
+ /* Check for tree conflict on src. */
+ SVN_ERR(svn_wc__db_read_conflict(&conflict_skel, db,
+ src_abspath,
+ scratch_pool, scratch_pool));
+ if (!conflict_skel)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' is not in conflict"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_read_info(operation, &locations,
+ NULL, NULL, &tree_conflicted,
+ db, src_abspath,
+ conflict_skel, result_pool,
+ scratch_pool));
+ if ((*operation != svn_wc_operation_update
+ && *operation != svn_wc_operation_switch)
+ || !tree_conflicted)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' is not a tree-conflict victim"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+ if (locations)
+ {
+ SVN_ERR_ASSERT(locations->nelts >= 2);
+ *old_version = APR_ARRAY_IDX(locations, 0,
+ svn_wc_conflict_version_t *);
+ *new_version = APR_ARRAY_IDX(locations, 1,
+ svn_wc_conflict_version_t *);
+ }
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(local_change,
+ incoming_change,
+ move_src_op_root_abspath,
+ db, src_abspath,
+ conflict_skel, scratch_pool,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Return *PROPS, *CHECKSUM, *CHILDREN and *KIND for LOCAL_RELPATH at
+ OP_DEPTH provided the row exists. Return *KIND of svn_node_none if
+ the row does not exist. *CHILDREN is a sorted array of basenames of
+ type 'const char *', rather than a hash, to allow the driver to
+ process children in a defined order. */
+static svn_error_t *
+get_info(apr_hash_t **props,
+ const svn_checksum_t **checksum,
+ apr_array_header_t **children,
+ svn_node_kind_t *kind,
+ const char *local_relpath,
+ int op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *hash_children;
+ apr_array_header_t *sorted_children;
+ svn_error_t *err;
+ int i;
+
+ err = svn_wc__db_depth_get_info(NULL, kind, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, checksum, NULL, NULL, props,
+ wcroot, local_relpath, op_depth,
+ result_pool, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *kind = svn_node_none;
+ }
+ else
+ SVN_ERR(err);
+
+
+ SVN_ERR(svn_wc__db_get_children_op_depth(&hash_children, wcroot,
+ local_relpath, op_depth,
+ scratch_pool, scratch_pool));
+
+ sorted_children = svn_sort__hash(hash_children,
+ svn_sort_compare_items_lexically,
+ scratch_pool);
+
+ *children = apr_array_make(result_pool, sorted_children->nelts,
+ sizeof(const char *));
+ for (i = 0; i < sorted_children->nelts; ++i)
+ APR_ARRAY_PUSH(*children, const char *)
+ = apr_pstrdup(result_pool, APR_ARRAY_IDX(sorted_children, i,
+ svn_sort__item_t).key);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE if SRC_CHILDREN and DST_CHILDREN represent the same
+ children, FALSE otherwise. SRC_CHILDREN and DST_CHILDREN are
+ sorted arrays of basenames of type 'const char *'. */
+static svn_boolean_t
+children_match(apr_array_header_t *src_children,
+ apr_array_header_t *dst_children) { int i;
+
+ if (src_children->nelts != dst_children->nelts)
+ return FALSE;
+
+ for(i = 0; i < src_children->nelts; ++i)
+ {
+ const char *src_child =
+ APR_ARRAY_IDX(src_children, i, const char *);
+ const char *dst_child =
+ APR_ARRAY_IDX(dst_children, i, const char *);
+
+ if (strcmp(src_child, dst_child))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Return TRUE if SRC_PROPS and DST_PROPS contain the same properties,
+ FALSE otherwise. SRC_PROPS and DST_PROPS are standard property
+ hashes. */
+static svn_error_t *
+props_match(svn_boolean_t *match,
+ apr_hash_t *src_props,
+ apr_hash_t *dst_props,
+ apr_pool_t *scratch_pool)
+{
+ if (!src_props && !dst_props)
+ *match = TRUE;
+ else if (!src_props || ! dst_props)
+ *match = FALSE;
+ else
+ {
+ apr_array_header_t *propdiffs;
+
+ SVN_ERR(svn_prop_diffs(&propdiffs, src_props, dst_props, scratch_pool));
+ *match = propdiffs->nelts ? FALSE : TRUE;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* ### Drive TC_EDITOR so as to ...
+ */
+static svn_error_t *
+update_moved_away_node(svn_editor_t *tc_editor,
+ const char *src_relpath,
+ const char *dst_relpath,
+ int src_op_depth,
+ const char *move_root_dst_relpath,
+ svn_revnum_t move_root_dst_revision,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t src_kind, dst_kind;
+ const svn_checksum_t *src_checksum, *dst_checksum;
+ apr_hash_t *src_props, *dst_props;
+ apr_array_header_t *src_children, *dst_children;
+ int dst_op_depth = relpath_depth(move_root_dst_relpath);
+
+ SVN_ERR(get_info(&src_props, &src_checksum, &src_children, &src_kind,
+ src_relpath, src_op_depth,
+ wcroot, scratch_pool, scratch_pool));
+
+ SVN_ERR(get_info(&dst_props, &dst_checksum, &dst_children, &dst_kind,
+ dst_relpath, dst_op_depth,
+ wcroot, scratch_pool, scratch_pool));
+
+ if (src_kind == svn_node_none
+ || (dst_kind != svn_node_none && src_kind != dst_kind))
+ {
+ SVN_ERR(svn_editor_delete(tc_editor, dst_relpath,
+ move_root_dst_revision));
+ }
+
+ if (src_kind != svn_node_none && src_kind != dst_kind)
+ {
+ if (src_kind == svn_node_file || src_kind == svn_node_symlink)
+ {
+ svn_stream_t *contents;
+
+ SVN_ERR(svn_wc__db_pristine_read(&contents, NULL, db,
+ wcroot->abspath, src_checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_editor_add_file(tc_editor, dst_relpath,
+ src_checksum, contents, src_props,
+ move_root_dst_revision));
+ }
+ else if (src_kind == svn_node_dir)
+ {
+ SVN_ERR(svn_editor_add_directory(tc_editor, dst_relpath,
+ src_children, src_props,
+ move_root_dst_revision));
+ }
+ }
+ else if (src_kind != svn_node_none)
+ {
+ svn_boolean_t match;
+ apr_hash_t *props;
+
+ SVN_ERR(props_match(&match, src_props, dst_props, scratch_pool));
+ props = match ? NULL: src_props;
+
+
+ if (src_kind == svn_node_file || src_kind == svn_node_symlink)
+ {
+ svn_stream_t *contents;
+
+ if (svn_checksum_match(src_checksum, dst_checksum))
+ src_checksum = NULL;
+
+ if (src_checksum)
+ SVN_ERR(svn_wc__db_pristine_read(&contents, NULL, db,
+ wcroot->abspath, src_checksum,
+ scratch_pool, scratch_pool));
+ else
+ contents = NULL;
+
+ if (props || src_checksum)
+ SVN_ERR(svn_editor_alter_file(tc_editor, dst_relpath,
+ move_root_dst_revision,
+ props, src_checksum, contents));
+ }
+ else if (src_kind == svn_node_dir)
+ {
+ apr_array_header_t *children
+ = children_match(src_children, dst_children) ? NULL : src_children;
+
+ if (props || children)
+ SVN_ERR(svn_editor_alter_directory(tc_editor, dst_relpath,
+ move_root_dst_revision,
+ children, props));
+ }
+ }
+
+ if (src_kind == svn_node_dir)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i = 0, j = 0;
+
+ while (i < src_children->nelts || j < dst_children->nelts)
+ {
+ const char *child_name;
+ const char *src_child_relpath, *dst_child_relpath;
+ svn_boolean_t src_only = FALSE, dst_only = FALSE;
+
+ svn_pool_clear(iterpool);
+ if (i >= src_children->nelts)
+ {
+ dst_only = TRUE;
+ child_name = APR_ARRAY_IDX(dst_children, j, const char *);
+ }
+ else if (j >= dst_children->nelts)
+ {
+ src_only = TRUE;
+ child_name = APR_ARRAY_IDX(src_children, i, const char *);
+ }
+ else
+ {
+ const char *src_name = APR_ARRAY_IDX(src_children, i,
+ const char *);
+ const char *dst_name = APR_ARRAY_IDX(dst_children, j,
+ const char *);
+ int cmp = strcmp(src_name, dst_name);
+
+ if (cmp > 0)
+ dst_only = TRUE;
+ else if (cmp < 0)
+ src_only = TRUE;
+
+ child_name = dst_only ? dst_name : src_name;
+ }
+
+ src_child_relpath = svn_relpath_join(src_relpath, child_name,
+ iterpool);
+ dst_child_relpath = svn_relpath_join(dst_relpath, child_name,
+ iterpool);
+
+ SVN_ERR(update_moved_away_node(tc_editor, src_child_relpath,
+ dst_child_relpath, src_op_depth,
+ move_root_dst_relpath,
+ move_root_dst_revision,
+ db, wcroot, scratch_pool));
+
+ if (!dst_only)
+ ++i;
+ if (!src_only)
+ ++j;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Update the single op-depth layer in the move destination subtree
+ rooted at DST_RELPATH to make it match the move source subtree
+ rooted at SRC_RELPATH. */
+static svn_error_t *
+replace_moved_layer(const char *src_relpath,
+ const char *dst_relpath,
+ int src_op_depth,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int dst_op_depth = relpath_depth(dst_relpath);
+
+ /* Replace entire subtree at one op-depth. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_LOCAL_RELPATH_OP_DEPTH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ src_relpath, src_op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_error_t *err;
+ svn_sqlite__stmt_t *stmt2;
+ const char *src_cp_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *dst_cp_relpath
+ = svn_relpath_join(dst_relpath,
+ svn_relpath_skip_ancestor(src_relpath,
+ src_cp_relpath),
+ scratch_pool);
+
+ err = svn_sqlite__get_statement(&stmt2, wcroot->sdb,
+ STMT_COPY_NODE_MOVE);
+ if (!err)
+ err = svn_sqlite__bindf(stmt2, "isdsds", wcroot->wc_id,
+ src_cp_relpath, src_op_depth,
+ dst_cp_relpath, dst_op_depth,
+ svn_relpath_dirname(dst_cp_relpath,
+ scratch_pool));
+ if (!err)
+ err = svn_sqlite__step_done(stmt2);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Transfer changes from the move source to the move destination.
+ *
+ * Drive the editor TC_EDITOR with the difference between DST_RELPATH
+ * (at its own op-depth) and SRC_RELPATH (at op-depth zero).
+ *
+ * Then update the single op-depth layer in the move destination subtree
+ * rooted at DST_RELPATH to make it match the move source subtree
+ * rooted at SRC_RELPATH.
+ *
+ * ### And the other params?
+ */
+static svn_error_t *
+drive_tree_conflict_editor(svn_editor_t *tc_editor,
+ const char *src_relpath,
+ const char *dst_relpath,
+ int src_op_depth,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_reason_t local_change,
+ svn_wc_conflict_action_t incoming_change,
+ svn_wc_conflict_version_t *old_version,
+ svn_wc_conflict_version_t *new_version,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ /*
+ * Refuse to auto-resolve unsupported tree conflicts.
+ */
+ /* ### Only handle conflicts created by update/switch operations for now. */
+ if (operation != svn_wc_operation_update &&
+ operation != svn_wc_operation_switch)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot auto-resolve tree-conflict on '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath,
+ src_relpath, scratch_pool),
+ scratch_pool));
+
+ /* We walk the move source (i.e. the post-update tree), comparing each node
+ * with the equivalent node at the move destination and applying the update
+ * to nodes at the move destination. */
+ SVN_ERR(update_moved_away_node(tc_editor, src_relpath, dst_relpath,
+ src_op_depth,
+ dst_relpath, old_version->peg_rev,
+ db, wcroot, scratch_pool));
+
+ SVN_ERR(replace_moved_layer(src_relpath, dst_relpath, src_op_depth,
+ wcroot, scratch_pool));
+
+ SVN_ERR(svn_editor_complete(tc_editor));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+suitable_for_move(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_revnum_t revision;
+ const char *repos_relpath;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ revision = svn_sqlite__column_revnum(stmt, 4);
+ repos_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool);
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!have_row)
+ return SVN_NO_ERROR; /* Return an error? */
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_REPOS_PATH_REVISION));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_revnum_t node_revision = svn_sqlite__column_revnum(stmt, 2);
+ const char *relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+ svn_pool_clear(iterpool);
+
+ relpath = svn_relpath_skip_ancestor(local_relpath, relpath);
+ relpath = svn_relpath_join(repos_relpath, relpath, iterpool);
+
+ if (revision != node_revision)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ svn_sqlite__reset(stmt),
+ _("Cannot apply update because move source "
+ "%s' is a mixed-revision working copy"),
+ svn_dirent_local_style(svn_dirent_join(
+ wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ if (strcmp(relpath, svn_sqlite__column_text(stmt, 1, NULL)))
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
+ svn_sqlite__reset(stmt),
+ _("Cannot apply update because move source "
+ "'%s' is a switched subtree"),
+ svn_dirent_local_style(svn_dirent_join(
+ wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_update_moved_away_conflict_victim(), which see.
+ */
+static svn_error_t *
+update_moved_away_conflict_victim(svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *victim_relpath,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_reason_t local_change,
+ svn_wc_conflict_action_t incoming_change,
+ const char *move_src_op_root_relpath,
+ svn_wc_conflict_version_t *old_version,
+ svn_wc_conflict_version_t *new_version,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor_t *tc_editor;
+ struct tc_editor_baton *tc_editor_baton;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *dummy1, *dummy2, *dummy3;
+ int src_op_depth;
+ const char *move_root_dst_abspath;
+
+ /* ### assumes wc write lock already held */
+
+ /* Construct editor baton. */
+ tc_editor_baton = apr_pcalloc(scratch_pool, sizeof(*tc_editor_baton));
+ SVN_ERR(svn_wc__db_op_depth_moved_to(
+ &dummy1, &tc_editor_baton->move_root_dst_relpath, &dummy2, &dummy3,
+ relpath_depth(move_src_op_root_relpath) - 1,
+ wcroot, victim_relpath, scratch_pool, scratch_pool));
+ if (tc_editor_baton->move_root_dst_relpath == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("The node '%s' has not been moved away"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath, victim_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ move_root_dst_abspath
+ = svn_dirent_join(wcroot->abspath, tc_editor_baton->move_root_dst_relpath,
+ scratch_pool);
+ SVN_ERR(svn_wc__write_check(db, move_root_dst_abspath, scratch_pool));
+
+ tc_editor_baton->operation = operation;
+ tc_editor_baton->old_version= old_version;
+ tc_editor_baton->new_version= new_version;
+ tc_editor_baton->db = db;
+ tc_editor_baton->wcroot = wcroot;
+ tc_editor_baton->result_pool = scratch_pool;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_HIGHEST_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ move_src_op_root_relpath,
+ relpath_depth(move_src_op_root_relpath)));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ src_op_depth = svn_sqlite__column_int(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' is not deleted"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath, victim_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ if (src_op_depth == 0)
+ SVN_ERR(suitable_for_move(wcroot, victim_relpath, scratch_pool));
+
+ /* Create a new, and empty, list for notification information. */
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+ /* Create the editor... */
+ SVN_ERR(svn_editor_create(&tc_editor, tc_editor_baton,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_editor_setcb_many(tc_editor, &editor_ops, scratch_pool));
+
+ /* ... and drive it. */
+ SVN_ERR(drive_tree_conflict_editor(tc_editor,
+ victim_relpath,
+ tc_editor_baton->move_root_dst_relpath,
+ src_op_depth,
+ operation,
+ local_change, incoming_change,
+ tc_editor_baton->old_version,
+ tc_editor_baton->new_version,
+ db, wcroot,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_update_moved_away_conflict_victim(svn_wc__db_t *db,
+ const char *victim_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_reason_t local_change;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+ const char *move_src_op_root_abspath, *move_src_op_root_relpath;
+
+ /* ### Check for mixed-rev src or dst? */
+
+ SVN_ERR(get_tc_info(&operation, &local_change, &incoming_change,
+ &move_src_op_root_abspath,
+ &old_version, &new_version,
+ db, victim_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__write_check(db, move_src_op_root_abspath, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, victim_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ move_src_op_root_relpath
+ = svn_dirent_skip_ancestor(wcroot->abspath, move_src_op_root_abspath);
+
+ SVN_WC__DB_WITH_TXN(
+ update_moved_away_conflict_victim(
+ db, wcroot, local_relpath,
+ operation, local_change, incoming_change,
+ move_src_op_root_relpath,
+ old_version, new_version,
+ cancel_func, cancel_baton,
+ scratch_pool),
+ wcroot);
+
+ /* Send all queued up notifications. */
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot,
+ old_version->peg_rev,
+ new_version->peg_rev,
+ notify_func, notify_baton,
+ scratch_pool));
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ svn_wc_notify_update_completed,
+ scratch_pool);
+ notify->kind = svn_node_none;
+ notify->content_state = svn_wc_notify_state_inapplicable;
+ notify->prop_state = svn_wc_notify_state_inapplicable;
+ notify->revision = new_version->peg_rev;
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *CAN_BUMP to TRUE if DEPTH is sufficient to cover the entire
+ BASE tree at LOCAL_RELPATH, to FALSE otherwise. */
+static svn_error_t *
+depth_sufficient_to_bump(svn_boolean_t *can_bump,
+ const char *local_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ switch (depth)
+ {
+ case svn_depth_infinity:
+ *can_bump = TRUE;
+ return SVN_NO_ERROR;
+
+ case svn_depth_empty:
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id,
+ local_relpath, 0));
+ break;
+
+ case svn_depth_files:
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_HAS_NON_FILE_CHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ break;
+
+ case svn_depth_immediates:
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_HAS_GRANDCHILDREN));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id,
+ local_relpath));
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *can_bump = !have_row;
+ return SVN_NO_ERROR;
+}
+
+/* Mark a move-edit conflict on MOVE_SRC_ROOT_RELPATH. */
+static svn_error_t *
+bump_mark_tree_conflict(svn_wc__db_wcroot_t *wcroot,
+ const char *move_src_root_relpath,
+ const char *move_src_op_root_relpath,
+ const char *move_dst_op_root_relpath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ apr_int64_t repos_id;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *old_repos_relpath;
+ const char *new_repos_relpath;
+ svn_revnum_t old_rev;
+ svn_revnum_t new_rev;
+ svn_node_kind_t old_kind;
+ svn_node_kind_t new_kind;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+
+ /* Read new (post-update) information from the new move source BASE node. */
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &new_kind, &new_rev,
+ &new_repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, move_src_op_root_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid,
+ wcroot->sdb, repos_id, scratch_pool));
+
+ /* Read old (pre-update) information from the move destination node. */
+ SVN_ERR(svn_wc__db_depth_get_info(NULL, &old_kind, &old_rev,
+ &old_repos_relpath, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ wcroot, move_dst_op_root_relpath,
+ relpath_depth(move_dst_op_root_relpath),
+ scratch_pool, scratch_pool));
+
+ old_version = svn_wc_conflict_version_create2(
+ repos_root_url, repos_uuid, old_repos_relpath, old_rev,
+ old_kind, scratch_pool);
+ new_version = svn_wc_conflict_version_create2(
+ repos_root_url, repos_uuid, new_repos_relpath, new_rev,
+ new_kind, scratch_pool);
+
+ SVN_ERR(mark_tree_conflict(move_src_root_relpath,
+ wcroot, db, old_version, new_version,
+ move_dst_op_root_relpath,
+ svn_wc_operation_update,
+ old_kind, new_kind,
+ old_repos_relpath,
+ svn_wc_conflict_reason_moved_away,
+ svn_wc_conflict_action_edit,
+ move_src_op_root_relpath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Bump LOCAL_RELPATH, and all the children of LOCAL_RELPATH, that are
+ moved-to at op-depth greater than OP_DEPTH. SRC_DONE is a hash
+ with keys that are 'const char *' relpaths that have already been
+ bumped. Any bumped paths are added to SRC_DONE. */
+static svn_error_t *
+bump_moved_away(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ int op_depth,
+ apr_hash_t *src_done,
+ svn_depth_t depth,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_PAIR3));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while(have_row)
+ {
+ svn_sqlite__stmt_t *stmt2;
+ const char *src_relpath, *dst_relpath;
+ int src_op_depth = svn_sqlite__column_int(stmt, 2);
+ svn_error_t *err;
+ svn_skel_t *conflict;
+ svn_depth_t src_depth = depth;
+
+ svn_pool_clear(iterpool);
+
+ src_relpath = svn_sqlite__column_text(stmt, 0, iterpool);
+ dst_relpath = svn_sqlite__column_text(stmt, 1, iterpool);
+
+ if (depth != svn_depth_infinity)
+ {
+ svn_boolean_t skip_this_src = FALSE;
+ svn_node_kind_t src_kind;
+
+ if (strcmp(src_relpath, local_relpath))
+ {
+ switch (depth)
+ {
+ case svn_depth_empty:
+ skip_this_src = TRUE;
+ break;
+ case svn_depth_files:
+ src_kind = svn_sqlite__column_token(stmt, 3, kind_map);
+ if (src_kind != svn_node_file)
+ {
+ skip_this_src = TRUE;
+ break;
+ }
+ /* Fallthrough */
+ case svn_depth_immediates:
+ if (strcmp(svn_relpath_dirname(src_relpath, scratch_pool),
+ local_relpath))
+ skip_this_src = TRUE;
+ src_depth = svn_depth_empty;
+ break;
+ default:
+ SVN_ERR_MALFUNCTION();
+ }
+ }
+
+ if (skip_this_src)
+ {
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ continue;
+ }
+ }
+
+ err = svn_sqlite__get_statement(&stmt2, wcroot->sdb,
+ STMT_HAS_LAYER_BETWEEN);
+ if (!err)
+ err = svn_sqlite__bindf(stmt2, "isdd", wcroot->wc_id, local_relpath,
+ op_depth, src_op_depth);
+ if (!err)
+ err = svn_sqlite__step(&have_row, stmt2);
+ if (!err)
+ err = svn_sqlite__reset(stmt2);
+ if (!err && !have_row)
+ {
+ svn_boolean_t can_bump;
+ const char *src_root_relpath = src_relpath;
+
+ if (op_depth == 0)
+ err = depth_sufficient_to_bump(&can_bump, src_relpath, wcroot,
+ src_depth, scratch_pool);
+ else
+ /* Having chosen to bump an entire BASE tree move we
+ always have sufficient depth to bump subtree moves. */
+ can_bump = TRUE;
+
+ if (!err)
+ {
+ if (!can_bump)
+ {
+ err = bump_mark_tree_conflict(wcroot, src_relpath,
+ src_root_relpath, dst_relpath,
+ db, scratch_pool);
+ if (err)
+ return svn_error_compose_create(err,
+ svn_sqlite__reset(stmt));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ continue;
+ }
+
+ while (relpath_depth(src_root_relpath) > src_op_depth)
+ src_root_relpath = svn_relpath_dirname(src_root_relpath,
+ iterpool);
+
+ if (!svn_hash_gets(src_done, src_relpath))
+ {
+ svn_hash_sets(src_done,
+ apr_pstrdup(result_pool, src_relpath), "");
+ err = svn_wc__db_read_conflict_internal(&conflict, wcroot,
+ src_root_relpath,
+ iterpool, iterpool);
+ /* ### TODO: check this is the right sort of tree-conflict? */
+ if (!err && !conflict)
+ {
+ /* ### TODO: verify moved_here? */
+ err = replace_moved_layer(src_relpath, dst_relpath,
+ op_depth, wcroot, iterpool);
+
+ if (!err)
+ err = bump_moved_away(wcroot, dst_relpath,
+ relpath_depth(dst_relpath),
+ src_done, depth, db,
+ result_pool, iterpool);
+ }
+ }
+ }
+ }
+
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_bump_moved_away(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_depth_t depth,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+ const char *dummy1, *move_dst_op_root_relpath;
+ const char *move_src_root_relpath, *move_src_op_root_relpath;
+ apr_hash_t *src_done;
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+
+ SVN_ERR(svn_wc__db_op_depth_moved_to(&dummy1, &move_dst_op_root_relpath,
+ &move_src_root_relpath,
+ &move_src_op_root_relpath, 0,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+
+ if (move_src_root_relpath)
+ {
+ if (strcmp(move_src_root_relpath, local_relpath))
+ {
+ SVN_ERR(bump_mark_tree_conflict(wcroot, move_src_root_relpath,
+ move_src_op_root_relpath,
+ move_dst_op_root_relpath,
+ db, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ }
+
+ src_done = apr_hash_make(scratch_pool);
+ SVN_ERR(bump_moved_away(wcroot, local_relpath, 0, src_done, depth, db,
+ scratch_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+resolve_delete_raise_moved_away(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_t *db,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_action_t action,
+ svn_wc_conflict_version_t *old_version,
+ svn_wc_conflict_version_t *new_version,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int op_depth = relpath_depth(local_relpath);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_OP_DEPTH_MOVED_PAIR));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath,
+ op_depth));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while(have_row)
+ {
+ const char *moved_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+ const char *move_root_dst_relpath = svn_sqlite__column_text(stmt, 1,
+ NULL);
+ const char *moved_dst_repos_relpath = svn_sqlite__column_text(stmt, 2,
+ NULL);
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(mark_tree_conflict(moved_relpath,
+ wcroot, db, old_version, new_version,
+ move_root_dst_relpath, operation,
+ svn_node_dir /* ### ? */,
+ svn_node_dir /* ### ? */,
+ moved_dst_repos_relpath,
+ svn_wc_conflict_reason_moved_away,
+ action, local_relpath,
+ iterpool));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_delete_raise_moved_away(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_reason_t reason;
+ svn_wc_conflict_action_t action;
+ svn_wc_conflict_version_t *old_version, *new_version;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_ERR(get_tc_info(&operation, &reason, &action, NULL,
+ &old_version, &new_version,
+ db, local_abspath, scratch_pool, scratch_pool));
+
+ SVN_WC__DB_WITH_TXN(
+ resolve_delete_raise_moved_away(wcroot, local_relpath,
+ db, operation, action,
+ old_version, new_version,
+ scratch_pool),
+ wcroot);
+
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot,
+ old_version->peg_rev,
+ (new_version
+ ? new_version->peg_rev
+ : SVN_INVALID_REVNUM),
+ notify_func, notify_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+break_move(svn_wc__db_wcroot_t *wcroot,
+ const char *src_relpath,
+ int src_op_depth,
+ const char *dst_relpath,
+ int dst_op_depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_CLEAR_MOVED_TO_RELPATH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, src_relpath,
+ src_op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ /* This statement clears moved_here. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_OP_DEPTH_RECURSIVE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id,
+ dst_relpath, dst_op_depth, dst_op_depth));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_break_moved_away_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const char *dummy1, *move_dst_op_root_relpath;
+ const char *dummy2, *move_src_op_root_relpath;
+
+ SVN_ERR(svn_wc__db_op_depth_moved_to(&dummy1, &move_dst_op_root_relpath,
+ &dummy2,
+ &move_src_op_root_relpath,
+ relpath_depth(local_relpath) - 1,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(break_move(wcroot, local_relpath,
+ relpath_depth(move_src_op_root_relpath),
+ move_dst_op_root_relpath,
+ relpath_depth(move_dst_op_root_relpath),
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+break_moved_away_children_internal(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+ STMT_CREATE_UPDATE_MOVE_LIST));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_PAIR2));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (have_row)
+ {
+ const char *src_relpath = svn_sqlite__column_text(stmt, 0, iterpool);
+ const char *dst_relpath = svn_sqlite__column_text(stmt, 1, iterpool);
+ int src_op_depth = svn_sqlite__column_int(stmt, 2);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(break_move(wcroot, src_relpath, src_op_depth, dst_relpath,
+ relpath_depth(dst_relpath), iterpool));
+ SVN_ERR(update_move_list_add(wcroot, src_relpath,
+ svn_wc_notify_move_broken,
+ svn_node_unknown,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_break_moved_away(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ svn_wc__db_resolve_break_moved_away_internal(wcroot, local_relpath,
+ scratch_pool),
+ wcroot);
+
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+ local_relpath,
+ scratch_pool),
+ svn_wc_notify_move_broken,
+ scratch_pool);
+ notify->kind = svn_node_unknown;
+ notify->content_state = svn_wc_notify_state_inapplicable;
+ notify->prop_state = svn_wc_notify_state_inapplicable;
+ notify->revision = SVN_INVALID_REVNUM;
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_resolve_break_moved_away_children(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ break_moved_away_children_internal(wcroot, local_relpath, scratch_pool),
+ wcroot);
+
+ SVN_ERR(svn_wc__db_update_move_list_notify(wcroot,
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM,
+ notify_func, notify_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+required_lock_for_resolve(const char **required_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *required_relpath = local_relpath;
+
+ /* This simply looks for all moves out of the LOCAL_RELPATH tree. We
+ could attempt to limit it to only those moves that are going to
+ be resolved but that would require second guessing the resolver.
+ This simple algorithm is sufficient although it may give a
+ strictly larger/deeper lock than necessary. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_MOVED_OUTSIDE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, 0));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *move_dst_relpath = svn_sqlite__column_text(stmt, 1,
+ NULL);
+
+ *required_relpath
+ = svn_relpath_get_longest_ancestor(*required_relpath,
+ move_dst_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ *required_relpath = apr_pstrdup(result_pool, *required_relpath);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__required_lock_for_resolve(const char **required_abspath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ const char *required_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ SVN_WC__DB_WITH_TXN(
+ required_lock_for_resolve(&required_relpath, wcroot, local_relpath,
+ scratch_pool, scratch_pool),
+ wcroot);
+
+ *required_abspath = svn_dirent_join(wcroot->abspath, required_relpath,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/wc_db_util.c b/contrib/subversion/subversion/libsvn_wc/wc_db_util.c
new file mode 100644
index 0000000..39dd034
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc_db_util.c
@@ -0,0 +1,228 @@
+/*
+ * wc_db_util.c : Various util functions for wc_db(_pdh)
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* About this file:
+ This file is meant to be a stash of fairly low-level functions used by both
+ wc_db.c and wc_db_pdh.c. In breaking stuff out of the monolithic wc_db.c,
+ I have discovered that some utility functions are used by bits in both
+ files. Rather than shoehorn those functions into one file or the other, or
+ create circular dependencies between the files, I felt a third file, with
+ a well-defined scope, would be sensible. History will judge its effect.
+
+ The goal of it file is simple: just execute SQLite statements. That is,
+ functions in this file should have no knowledge of pdh's or db's, and
+ should just operate on the raw sdb object. If a function requires more
+ information than that, it shouldn't be in here. -hkw
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include "svn_dirent_uri.h"
+#include "private/svn_sqlite.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "wc_db_private.h"
+#include "wc-queries.h"
+
+#include "svn_private_config.h"
+
+WC_QUERIES_SQL_DECLARE_STATEMENTS(statements);
+
+
+
+/* */
+svn_error_t *
+svn_wc__db_util_fetch_wc_id(apr_int64_t *wc_id,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ /* ### cheat. we know there is just one WORKING_COPY row, and it has a
+ ### NULL value for local_abspath. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_WCROOT_NULL));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, svn_sqlite__reset(stmt),
+ _("Missing a row in WCROOT."));
+
+ SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 0));
+ *wc_id = svn_sqlite__column_int64(stmt, 0);
+
+ return svn_error_trace(svn_sqlite__reset(stmt));
+}
+
+
+
+
+/* An SQLite application defined function that allows SQL queries to
+ use "relpath_depth(local_relpath)". */
+static svn_error_t *
+relpath_depth_sqlite(svn_sqlite__context_t *sctx,
+ int argc,
+ svn_sqlite__value_t *values[],
+ apr_pool_t *scratch_pool)
+{
+ const char *path = NULL;
+ apr_int64_t depth;
+
+ if (argc == 1 && svn_sqlite__value_type(values[0]) == SVN_SQLITE__TEXT)
+ path = svn_sqlite__value_text(values[0]);
+ if (!path)
+ {
+ svn_sqlite__result_null(sctx);
+ return SVN_NO_ERROR;
+ }
+
+ depth = *path ? 1 : 0;
+ while (*path)
+ {
+ if (*path == '/')
+ ++depth;
+ ++path;
+ }
+ svn_sqlite__result_int64(sctx, depth);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_util_open_db(svn_sqlite__db_t **sdb,
+ const char *dir_abspath,
+ const char *sdb_fname,
+ svn_sqlite__mode_t smode,
+ svn_boolean_t exclusive,
+ const char *const *my_statements,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *sdb_abspath = svn_wc__adm_child(dir_abspath, sdb_fname,
+ scratch_pool);
+
+ if (smode != svn_sqlite__mode_rwcreate)
+ {
+ svn_node_kind_t kind;
+
+ /* A file stat is much cheaper then a failed database open handled
+ by SQLite. */
+ SVN_ERR(svn_io_check_path(sdb_abspath, &kind, scratch_pool));
+
+ if (kind != svn_node_file)
+ return svn_error_createf(APR_ENOENT, NULL,
+ _("Working copy database '%s' not found"),
+ svn_dirent_local_style(sdb_abspath,
+ scratch_pool));
+ }
+#ifndef WIN32
+ else
+ {
+ apr_file_t *f;
+
+ /* A standard SQLite build creates a DB with mode 644 ^ !umask
+ which means the file doesn't have group/world write access
+ even when umask allows it. By ensuring the file exists before
+ SQLite gets involved we give it the permissions allowed by
+ umask. */
+ SVN_ERR(svn_io_file_open(&f, sdb_abspath,
+ (APR_READ | APR_WRITE | APR_CREATE),
+ APR_OS_DEFAULT, scratch_pool));
+ SVN_ERR(svn_io_file_close(f, scratch_pool));
+ }
+#endif
+
+ SVN_ERR(svn_sqlite__open(sdb, sdb_abspath, smode,
+ my_statements ? my_statements : statements,
+ 0, NULL, result_pool, scratch_pool));
+
+ if (exclusive)
+ SVN_ERR(svn_sqlite__exec_statements(*sdb, STMT_PRAGMA_LOCKING_MODE));
+
+ SVN_ERR(svn_sqlite__create_scalar_function(*sdb, "relpath_depth", 1,
+ relpath_depth_sqlite, NULL));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Some helpful transaction helpers.
+
+ Instead of directly using SQLite transactions, these wrappers
+ relieve the consumer from the need to wrap the wcroot and
+ local_relpath, which are almost always used within the transaction.
+
+ This also means if we later want to implement some wc_db-specific txn
+ handling, we have a convenient place to do it.
+ */
+
+/* A callback which supplies WCROOTs and LOCAL_RELPATHs. */
+typedef svn_error_t *(*db_txn_callback_t)(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *scratch_pool);
+
+/* Baton for use with run_txn() and with_db_txn(). */
+struct txn_baton_t
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ db_txn_callback_t cb_func;
+ void *cb_baton;
+};
+
+
+/* Unwrap the sqlite transaction into a wc_db txn.
+ Implements svn_sqlite__transaction_callback_t. */
+static svn_error_t *
+run_txn(void *baton, svn_sqlite__db_t *db, apr_pool_t *scratch_pool)
+{
+ struct txn_baton_t *tb = baton;
+
+ return svn_error_trace(
+ tb->cb_func(tb->cb_baton, tb->wcroot, tb->local_relpath, scratch_pool));
+}
+
+
+/* Run CB_FUNC in a SQLite transaction with CB_BATON, using WCROOT and
+ LOCAL_RELPATH. If callbacks require additional information, they may
+ provide it using CB_BATON. */
+svn_error_t *
+svn_wc__db_with_txn(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ svn_wc__db_txn_callback_t cb_func,
+ void *cb_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct txn_baton_t tb;
+
+ tb.wcroot = wcroot;
+ tb.local_relpath = local_relpath;
+ tb.cb_func = cb_func;
+ tb.cb_baton = cb_baton;
+
+ return svn_error_trace(
+ svn_sqlite__with_lock(wcroot->sdb, run_txn, &tb, scratch_pool));
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/wc_db_wcroot.c b/contrib/subversion/subversion/libsvn_wc/wc_db_wcroot.c
new file mode 100644
index 0000000..1091f1b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wc_db_wcroot.c
@@ -0,0 +1,900 @@
+/*
+ * wc_db_wcroot.c : supporting datastructures for the administrative database
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include <assert.h>
+
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_version.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "wc_db_private.h"
+#include "wc-queries.h"
+
+#include "svn_private_config.h"
+
+/* ### Same values as wc_db.c */
+#define SDB_FILE "wc.db"
+#define UNKNOWN_WC_ID ((apr_int64_t) -1)
+#define FORMAT_FROM_SDB (-1)
+
+
+
+/* Get the format version from a wc-1 directory. If it is not a working copy
+ directory, then it sets VERSION to zero and returns no error. */
+static svn_error_t *
+get_old_version(int *version,
+ const char *abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ const char *format_file_path;
+ svn_node_kind_t kind;
+
+ /* Try reading the format number from the entries file. */
+ format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_ENTRIES,
+ scratch_pool);
+
+ /* Since trying to open a non-existent file is quite expensive, try a
+ quick stat call first. In wc-ng w/cs, this will be an early exit. */
+ SVN_ERR(svn_io_check_path(format_file_path, &kind, scratch_pool));
+ if (kind == svn_node_none)
+ {
+ *version = 0;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_io_read_version_file(version, format_file_path, scratch_pool);
+ if (err == NULL)
+ return SVN_NO_ERROR;
+ if (err->apr_err != SVN_ERR_BAD_VERSION_FILE_FORMAT
+ && !APR_STATUS_IS_ENOENT(err->apr_err)
+ && !APR_STATUS_IS_ENOTDIR(err->apr_err))
+ return svn_error_createf(SVN_ERR_WC_MISSING, err, _("'%s' does not exist"),
+ svn_dirent_local_style(abspath, scratch_pool));
+ svn_error_clear(err);
+
+ /* This must be a really old working copy! Fall back to reading the
+ format file.
+
+ Note that the format file might not exist in newer working copies
+ (format 7 and higher), but in that case, the entries file should
+ have contained the format number. */
+ format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_FORMAT,
+ scratch_pool);
+ err = svn_io_read_version_file(version, format_file_path, scratch_pool);
+ if (err == NULL)
+ return SVN_NO_ERROR;
+
+ /* Whatever error may have occurred... we can just ignore. This is not
+ a working copy directory. Signal the caller. */
+ svn_error_clear(err);
+
+ *version = 0;
+ return SVN_NO_ERROR;
+}
+
+
+/* A helper function to parse_local_abspath() which returns the on-disk KIND
+ of LOCAL_ABSPATH, using DB and SCRATCH_POOL as needed.
+
+ This function may do strange things, but at long as it comes up with the
+ Right Answer, we should be happy. */
+static svn_error_t *
+get_path_kind(svn_node_kind_t *kind,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t special;
+ svn_node_kind_t node_kind;
+
+ /* This implements a *really* simple LRU cache, where "simple" is defined
+ as "only one element". In other words, we remember the most recently
+ queried path, and nothing else. This gives >80% cache hits. */
+
+ if (db->parse_cache.abspath
+ && strcmp(db->parse_cache.abspath->data, local_abspath) == 0)
+ {
+ /* Cache hit! */
+ *kind = db->parse_cache.kind;
+ return SVN_NO_ERROR;
+ }
+
+ if (!db->parse_cache.abspath)
+ {
+ db->parse_cache.abspath = svn_stringbuf_create(local_abspath,
+ db->state_pool);
+ }
+ else
+ {
+ svn_stringbuf_set(db->parse_cache.abspath, local_abspath);
+ }
+
+ SVN_ERR(svn_io_check_special_path(local_abspath, &node_kind,
+ &special, scratch_pool));
+
+ db->parse_cache.kind = (special ? svn_node_symlink : node_kind);
+ *kind = db->parse_cache.kind;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return an error if the work queue in SDB is non-empty. */
+static svn_error_t *
+verify_no_work(svn_sqlite__db_t *sdb)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_LOOK_FOR_WORK));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (have_row)
+ return svn_error_create(SVN_ERR_WC_CLEANUP_REQUIRED, NULL,
+ NULL /* nothing to add. */);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static apr_status_t
+close_wcroot(void *data)
+{
+ svn_wc__db_wcroot_t *wcroot = data;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT_NO_RETURN(wcroot->sdb != NULL);
+
+ err = svn_sqlite__close(wcroot->sdb);
+ wcroot->sdb = NULL;
+ if (err)
+ {
+ apr_status_t result = err->apr_err;
+ svn_error_clear(err);
+ return result;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+svn_error_t *
+svn_wc__db_open(svn_wc__db_t **db,
+ svn_config_t *config,
+ svn_boolean_t open_without_upgrade,
+ svn_boolean_t enforce_empty_wq,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *db = apr_pcalloc(result_pool, sizeof(**db));
+ (*db)->config = config;
+ (*db)->verify_format = !open_without_upgrade;
+ (*db)->enforce_empty_wq = enforce_empty_wq;
+ (*db)->dir_data = apr_hash_make(result_pool);
+
+ (*db)->state_pool = result_pool;
+
+ /* Don't need to initialize (*db)->parse_cache, due to the calloc above */
+ if (config)
+ {
+ svn_error_t *err;
+ svn_boolean_t sqlite_exclusive = FALSE;
+
+ err = svn_config_get_bool(config, &sqlite_exclusive,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
+ FALSE);
+ if (err)
+ {
+ svn_error_clear(err);
+ }
+ else
+ (*db)->exclusive = sqlite_exclusive;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_close(svn_wc__db_t *db)
+{
+ apr_pool_t *scratch_pool = db->state_pool;
+ apr_hash_t *roots = apr_hash_make(scratch_pool);
+ apr_hash_index_t *hi;
+
+ /* Collect all the unique WCROOT structures, and empty out DIR_DATA. */
+ for (hi = apr_hash_first(scratch_pool, db->dir_data);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
+ const char *local_abspath = svn__apr_hash_index_key(hi);
+
+ if (wcroot->sdb)
+ svn_hash_sets(roots, wcroot->abspath, wcroot);
+
+ svn_hash_sets(db->dir_data, local_abspath, NULL);
+ }
+
+ /* Run the cleanup for each WCROOT. */
+ return svn_error_trace(svn_wc__db_close_many_wcroots(roots, db->state_pool,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot,
+ const char *wcroot_abspath,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ int format,
+ svn_boolean_t verify_format,
+ svn_boolean_t enforce_empty_wq,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (sdb != NULL)
+ SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool));
+
+ /* If we construct a wcroot, then we better have a format. */
+ SVN_ERR_ASSERT(format >= 1);
+
+ /* If this working copy is PRE-1.0, then simply bail out. */
+ if (format < 4)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("Working copy format of '%s' is too old (%d); "
+ "please check out your working copy again"),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool), format);
+ }
+
+ /* If this working copy is from a future version, then bail out. */
+ if (format > SVN_WC__VERSION)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
+ _("This client is too old to work with the working copy at\n"
+ "'%s' (format %d).\n"
+ "You need to get a newer Subversion client. For more details, see\n"
+ " http://subversion.apache.org/faq.html#working-copy-format-change\n"
+ ),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool),
+ format);
+ }
+
+ /* Verify that no work items exists. If they do, then our integrity is
+ suspect and, thus, we cannot use this database. */
+ if (format >= SVN_WC__HAS_WORK_QUEUE
+ && (enforce_empty_wq || (format < SVN_WC__VERSION && verify_format)))
+ {
+ svn_error_t *err = verify_no_work(sdb);
+ if (err)
+ {
+ /* Special message for attempts to upgrade a 1.7-dev wc with
+ outstanding workqueue items. */
+ if (err->apr_err == SVN_ERR_WC_CLEANUP_REQUIRED
+ && format < SVN_WC__VERSION && verify_format)
+ err = svn_error_quick_wrap(err, _("Cleanup with an older 1.7 "
+ "client before upgrading with "
+ "this client"));
+ return svn_error_trace(err);
+ }
+ }
+
+ /* Auto-upgrade the SDB if possible. */
+ if (format < SVN_WC__VERSION && verify_format)
+ {
+ return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL,
+ _("The working copy at '%s'\nis too old "
+ "(format %d) to work with client version "
+ "'%s' (expects format %d). You need to "
+ "upgrade the working copy first.\n"),
+ svn_dirent_local_style(wcroot_abspath,
+ scratch_pool),
+ format, SVN_VERSION, SVN_WC__VERSION);
+ }
+
+ *wcroot = apr_palloc(result_pool, sizeof(**wcroot));
+
+ (*wcroot)->abspath = wcroot_abspath;
+ (*wcroot)->sdb = sdb;
+ (*wcroot)->wc_id = wc_id;
+ (*wcroot)->format = format;
+ /* 8 concurrent locks is probably more than a typical wc_ng based svn client
+ uses. */
+ (*wcroot)->owned_locks = apr_array_make(result_pool, 8,
+ sizeof(svn_wc__db_wclock_t));
+ (*wcroot)->access_cache = apr_hash_make(result_pool);
+
+ /* SDB will be NULL for pre-NG working copies. We only need to run a
+ cleanup when the SDB is present. */
+ if (sdb != NULL)
+ apr_pool_cleanup_register(result_pool, *wcroot, close_wcroot,
+ apr_pool_cleanup_null);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_close_many_wcroots(apr_hash_t *roots,
+ apr_pool_t *state_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, roots); hi; hi = apr_hash_next(hi))
+ {
+ svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
+ apr_status_t result;
+
+ result = apr_pool_cleanup_run(state_pool, wcroot, close_wcroot);
+ if (result != APR_SUCCESS)
+ return svn_error_wrap_apr(result, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* POOL may be NULL if the lifetime of LOCAL_ABSPATH is sufficient. */
+static const char *
+compute_relpath(const svn_wc__db_wcroot_t *wcroot,
+ const char *local_abspath,
+ apr_pool_t *result_pool)
+{
+ const char *relpath = svn_dirent_is_child(wcroot->abspath, local_abspath,
+ result_pool);
+ if (relpath == NULL)
+ return "";
+ return relpath;
+}
+
+
+/* Return in *LINK_TARGET_ABSPATH the absolute path the symlink at
+ * LOCAL_ABSPATH is pointing to. Perform all allocations in POOL. */
+static svn_error_t *
+read_link_target(const char **link_target_abspath,
+ const char *local_abspath,
+ apr_pool_t *pool)
+{
+ svn_string_t *link_target;
+ const char *canon_link_target;
+
+ SVN_ERR(svn_io_read_link(&link_target, local_abspath, pool));
+ if (link_target->len == 0)
+ return svn_error_createf(SVN_ERR_WC_NOT_SYMLINK, NULL,
+ _("The symlink at '%s' points nowhere"),
+ svn_dirent_local_style(local_abspath, pool));
+
+ canon_link_target = svn_dirent_canonicalize(link_target->data, pool);
+
+ /* Treat relative symlinks as relative to LOCAL_ABSPATH's parent. */
+ if (!svn_dirent_is_absolute(canon_link_target))
+ canon_link_target = svn_dirent_join(svn_dirent_dirname(local_abspath,
+ pool),
+ canon_link_target, pool);
+
+ /* Collapse any .. in the symlink part of the path. */
+ if (svn_path_is_backpath_present(canon_link_target))
+ SVN_ERR(svn_dirent_get_absolute(link_target_abspath, canon_link_target,
+ pool));
+ else
+ *link_target_abspath = canon_link_target;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot,
+ const char **local_relpath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_dir_abspath;
+ const char *original_abspath = local_abspath;
+ svn_node_kind_t kind;
+ const char *build_relpath;
+ svn_wc__db_wcroot_t *probe_wcroot;
+ svn_wc__db_wcroot_t *found_wcroot = NULL;
+ const char *scan_abspath;
+ svn_sqlite__db_t *sdb = NULL;
+ svn_boolean_t moved_upwards = FALSE;
+ svn_boolean_t always_check = FALSE;
+ int wc_format = 0;
+ const char *adm_relpath;
+
+ /* ### we need more logic for finding the database (if it is located
+ ### outside of the wcroot) and then managing all of that within DB.
+ ### for now: play quick & dirty. */
+
+ probe_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ if (probe_wcroot != NULL)
+ {
+ *wcroot = probe_wcroot;
+
+ /* We got lucky. Just return the thing BEFORE performing any I/O. */
+ /* ### validate SMODE against how we opened wcroot->sdb? and against
+ ### DB->mode? (will we record per-dir mode?) */
+
+ /* ### for most callers, we could pass NULL for result_pool. */
+ *local_relpath = compute_relpath(probe_wcroot, local_abspath,
+ result_pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* ### at some point in the future, we may need to find a way to get
+ ### rid of this stat() call. it is going to happen for EVERY call
+ ### into wc_db which references a file. calls for directories could
+ ### get an early-exit in the hash lookup just above. */
+ SVN_ERR(get_path_kind(&kind, db, local_abspath, scratch_pool));
+ if (kind != svn_node_dir)
+ {
+ /* If the node specified by the path is NOT present, then it cannot
+ possibly be a directory containing ".svn/wc.db".
+
+ If it is a file, then it cannot contain ".svn/wc.db".
+
+ For both of these cases, strip the basename off of the path and
+ move up one level. Keep record of what we strip, though, since
+ we'll need it later to construct local_relpath. */
+ svn_dirent_split(&local_dir_abspath, &build_relpath, local_abspath,
+ scratch_pool);
+
+ /* Is this directory in our hash? */
+ probe_wcroot = svn_hash_gets(db->dir_data, local_dir_abspath);
+ if (probe_wcroot != NULL)
+ {
+ const char *dir_relpath;
+
+ *wcroot = probe_wcroot;
+
+ /* Stashed directory's local_relpath + basename. */
+ dir_relpath = compute_relpath(probe_wcroot, local_dir_abspath,
+ NULL);
+ *local_relpath = svn_relpath_join(dir_relpath,
+ build_relpath,
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* If the requested path is not on the disk, then we don't know how
+ many ancestors need to be scanned until we start hitting content
+ on the disk. Set ALWAYS_CHECK to keep looking for .svn/entries
+ rather than bailing out after the first check. */
+ if (kind == svn_node_none)
+ always_check = TRUE;
+
+ /* Start the scanning at LOCAL_DIR_ABSPATH. */
+ local_abspath = local_dir_abspath;
+ }
+ else
+ {
+ /* Start the local_relpath empty. If *this* directory contains the
+ wc.db, then relpath will be the empty string. */
+ build_relpath = "";
+
+ /* Remember the dir containing LOCAL_ABSPATH (they're the same). */
+ local_dir_abspath = local_abspath;
+ }
+
+ /* LOCAL_ABSPATH refers to a directory at this point. At this point,
+ we've determined that an associated WCROOT is NOT in the DB's hash
+ table for this directory. Let's find an existing one in the ancestors,
+ or create one when we find the actual wcroot. */
+
+ /* Assume that LOCAL_ABSPATH is a directory, and look for the SQLite
+ database in the right place. If we find it... great! If not, then
+ peel off some components, and try again. */
+
+ adm_relpath = svn_wc_get_adm_dir(scratch_pool);
+ while (TRUE)
+ {
+ svn_error_t *err;
+ svn_node_kind_t adm_subdir_kind;
+
+ const char *adm_subdir = svn_dirent_join(local_abspath, adm_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_io_check_path(adm_subdir, &adm_subdir_kind, scratch_pool));
+
+ if (adm_subdir_kind == svn_node_dir)
+ {
+ /* We always open the database in read/write mode. If the database
+ isn't writable in the filesystem, SQLite will internally open
+ it as read-only, and we'll get an error if we try to do a write
+ operation.
+
+ We could decide what to do on a per-operation basis, but since
+ we're caching database handles, it make sense to be as permissive
+ as the filesystem allows. */
+ err = svn_wc__db_util_open_db(&sdb, local_abspath, SDB_FILE,
+ svn_sqlite__mode_readwrite,
+ db->exclusive, NULL,
+ db->state_pool, scratch_pool);
+ if (err == NULL)
+ {
+#ifdef SVN_DEBUG
+ /* Install self-verification trigger statements. */
+ err = svn_sqlite__exec_statements(sdb,
+ STMT_VERIFICATION_TRIGGERS);
+ if (err && err->apr_err == SVN_ERR_SQLITE_ERROR)
+ {
+ /* Verification triggers can fail to install on old 1.7-dev
+ * formats which didn't have a NODES table yet. Ignore sqlite
+ * errors so such working copies can be upgraded. */
+ svn_error_clear(err);
+ }
+ else
+ SVN_ERR(err);
+#endif
+ break;
+ }
+ if (err->apr_err != SVN_ERR_SQLITE_ERROR
+ && !APR_STATUS_IS_ENOENT(err->apr_err))
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* If we have not moved upwards, then check for a wc-1 working copy.
+ Since wc-1 has a .svn in every directory, and we didn't find one
+ in the original directory, then we aren't looking at a wc-1.
+
+ If the original path is not present, then we have to check on every
+ iteration. The content may be the immediate parent, or possibly
+ five ancetors higher. We don't test for directory presence (just
+ for the presence of subdirs/files), so we don't know when we can
+ stop checking ... so just check always. */
+ if (!moved_upwards || always_check)
+ {
+ SVN_ERR(get_old_version(&wc_format, local_abspath,
+ scratch_pool));
+ if (wc_format != 0)
+ break;
+ }
+ }
+
+ /* We couldn't open the SDB within the specified directory, so
+ move up one more directory. */
+ if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+ {
+ /* Hit the root without finding a wcroot. */
+
+ /* The wcroot could be a symlink to a directory.
+ * (Issue #2557, #3987). If so, try again, this time scanning
+ * for a db within the directory the symlink points to,
+ * rather than within the symlink's parent directory. */
+ if (kind == svn_node_symlink)
+ {
+ svn_node_kind_t resolved_kind;
+
+ local_abspath = original_abspath;
+
+ SVN_ERR(svn_io_check_resolved_path(local_abspath,
+ &resolved_kind,
+ scratch_pool));
+ if (resolved_kind == svn_node_dir)
+ {
+ /* Is this directory recorded in our hash? */
+ found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ if (found_wcroot)
+ break;
+
+ SVN_ERR(read_link_target(&local_abspath, local_abspath,
+ scratch_pool));
+try_symlink_as_dir:
+ kind = svn_node_dir;
+ moved_upwards = FALSE;
+ local_dir_abspath = local_abspath;
+ build_relpath = "";
+
+ continue;
+ }
+ }
+
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy"),
+ svn_dirent_local_style(original_abspath,
+ scratch_pool));
+ }
+
+ local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ moved_upwards = TRUE;
+
+ /* Is the parent directory recorded in our hash? */
+ found_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ if (found_wcroot != NULL)
+ break;
+ }
+
+ if (found_wcroot != NULL)
+ {
+ /* We found a hash table entry for an ancestor, so we stopped scanning
+ since all subdirectories use the same WCROOT. */
+ *wcroot = found_wcroot;
+ }
+ else if (wc_format == 0)
+ {
+ /* We finally found the database. Construct a wcroot_t for it. */
+
+ apr_int64_t wc_id;
+ svn_error_t *err;
+
+ err = svn_wc__db_util_fetch_wc_id(&wc_id, sdb, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_CORRUPT)
+ return svn_error_quick_wrap(
+ err, apr_psprintf(scratch_pool,
+ _("Missing a row in WCROOT for '%s'."),
+ svn_dirent_local_style(original_abspath,
+ scratch_pool)));
+ return svn_error_trace(err);
+ }
+
+ /* WCROOT.local_abspath may be NULL when the database is stored
+ inside the wcroot, but we know the abspath is this directory
+ (ie. where we found it). */
+
+ err = svn_wc__db_pdh_create_wcroot(wcroot,
+ apr_pstrdup(db->state_pool, local_abspath),
+ sdb, wc_id, FORMAT_FROM_SDB,
+ db->verify_format, db->enforce_empty_wq,
+ db->state_pool, scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_WC_UNSUPPORTED_FORMAT ||
+ err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) &&
+ kind == svn_node_symlink)
+ {
+ /* We found an unsupported WC after traversing upwards from a
+ * symlink. Fall through to code below to check if the symlink
+ * points at a supported WC. */
+ svn_error_clear(err);
+ *wcroot = NULL;
+ }
+ else
+ SVN_ERR(err);
+ }
+ else
+ {
+ /* We found something that looks like a wc-1 working copy directory.
+ However, if the format version is 12 and the .svn/entries file
+ is only 3 bytes long, then it's a breadcrumb in a wc-ng working
+ copy that's missing an .svn/wc.db, or its .svn/wc.db is corrupt. */
+ if (wc_format == SVN_WC__WC_NG_VERSION /* 12 */)
+ {
+ apr_finfo_t info;
+
+ /* Check attributes of .svn/entries */
+ const char *admin_abspath = svn_wc__adm_child(
+ local_abspath, SVN_WC__ADM_ENTRIES, scratch_pool);
+ svn_error_t *err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
+ scratch_pool);
+
+ /* If the former does not succeed, something is seriously wrong. */
+ if (err)
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT, err,
+ _("The working copy at '%s' is corrupt."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ svn_error_clear(err);
+
+ if (3 == info.size)
+ {
+ /* Check existence of .svn/wc.db */
+ admin_abspath = svn_wc__adm_child(local_abspath, SDB_FILE,
+ scratch_pool);
+ err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE,
+ scratch_pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT, NULL,
+ _("The working copy database at '%s' is missing."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ else
+ /* We should never have reached this point in the code
+ if .svn/wc.db exists; therefore it's best to assume
+ it's corrupt. */
+ return svn_error_createf(
+ SVN_ERR_WC_CORRUPT, err,
+ _("The working copy database at '%s' is corrupt."),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ }
+ }
+
+ SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot,
+ apr_pstrdup(db->state_pool, local_abspath),
+ NULL, UNKNOWN_WC_ID, wc_format,
+ db->verify_format, db->enforce_empty_wq,
+ db->state_pool, scratch_pool));
+ }
+
+ if (*wcroot)
+ {
+ const char *dir_relpath;
+
+ /* The subdirectory's relpath is easily computed relative to the
+ wcroot that we just found. */
+ dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL);
+
+ /* And the result local_relpath may include a filename. */
+ *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool);
+ }
+
+ if (kind == svn_node_symlink)
+ {
+ svn_boolean_t retry_if_dir = FALSE;
+ svn_wc__db_status_t status;
+ svn_boolean_t conflicted;
+ svn_error_t *err;
+
+ /* Check if the symlink is versioned or obstructs a versioned node
+ * in this DB -- in that case, use this wcroot. Else, if the symlink
+ * points to a directory, try to find a wcroot in that directory
+ * instead. */
+
+ if (*wcroot)
+ {
+ err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &conflicted,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, *wcroot, *local_relpath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ retry_if_dir = TRUE; /* The symlink is unversioned. */
+ }
+ else
+ {
+ /* The symlink is versioned, or obstructs a versioned node.
+ * Ignore non-conflicted not-present/excluded nodes.
+ * This allows the symlink to redirect the wcroot query to a
+ * directory, regardless of 'invisible' nodes in this WC. */
+ retry_if_dir = ((status == svn_wc__db_status_not_present ||
+ status == svn_wc__db_status_excluded ||
+ status == svn_wc__db_status_server_excluded)
+ && !conflicted);
+ }
+ }
+ else
+ retry_if_dir = TRUE;
+
+ if (retry_if_dir)
+ {
+ svn_node_kind_t resolved_kind;
+
+ SVN_ERR(svn_io_check_resolved_path(original_abspath,
+ &resolved_kind,
+ scratch_pool));
+ if (resolved_kind == svn_node_dir)
+ {
+ SVN_ERR(read_link_target(&local_abspath, original_abspath,
+ scratch_pool));
+ /* This handle was opened in this function but is not going
+ to be used further so close it. */
+ if (sdb)
+ SVN_ERR(svn_sqlite__close(sdb));
+ goto try_symlink_as_dir;
+ }
+ }
+ }
+
+ /* We've found the appropriate WCROOT for the requested path. Stash
+ it into that path's directory. */
+ svn_hash_sets(db->dir_data,
+ apr_pstrdup(db->state_pool, local_dir_abspath),
+ *wcroot);
+
+ /* Did we traverse up to parent directories? */
+ if (!moved_upwards)
+ {
+ /* We did NOT move to a parent of the original requested directory.
+ We've constructed and filled in a WCROOT for the request, so we
+ are done. */
+ return SVN_NO_ERROR;
+ }
+
+ /* The WCROOT that we just found/built was for the LOCAL_ABSPATH originally
+ passed into this function. We stepped *at least* one directory above that.
+ We should now associate the WROOT for each parent directory that does
+ not (yet) have one. */
+
+ scan_abspath = local_dir_abspath;
+
+ do
+ {
+ const char *parent_dir = svn_dirent_dirname(scan_abspath, scratch_pool);
+ svn_wc__db_wcroot_t *parent_wcroot;
+
+ parent_wcroot = svn_hash_gets(db->dir_data, parent_dir);
+ if (parent_wcroot == NULL)
+ {
+ svn_hash_sets(db->dir_data, apr_pstrdup(db->state_pool, parent_dir),
+ *wcroot);
+ }
+
+ /* Move up a directory, stopping when we reach the directory where
+ we found/built the WCROOT. */
+ scan_abspath = parent_dir;
+ }
+ while (strcmp(scan_abspath, local_abspath) != 0);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_drop_root(svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *root_wcroot = svn_hash_gets(db->dir_data, local_abspath);
+ apr_hash_index_t *hi;
+ apr_status_t result;
+
+ if (!root_wcroot)
+ return SVN_NO_ERROR;
+
+ if (strcmp(root_wcroot->abspath, local_abspath) != 0)
+ return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+ _("'%s' is not a working copy root"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, db->dir_data);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi);
+
+ if (wcroot == root_wcroot)
+ svn_hash_sets(db->dir_data, svn__apr_hash_index_key(hi), NULL);
+ }
+
+ result = apr_pool_cleanup_run(db->state_pool, root_wcroot, close_wcroot);
+ if (result != APR_SUCCESS)
+ return svn_error_wrap_apr(result, NULL);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/wcroot_anchor.c b/contrib/subversion/subversion/libsvn_wc/wcroot_anchor.c
new file mode 100644
index 0000000..913a61b
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/wcroot_anchor.c
@@ -0,0 +1,227 @@
+/*
+ * wcroot_anchor.c : wcroot and anchor functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_private_config.h"
+
+#include "wc.h"
+
+#include "private/svn_wc_private.h"
+
+/* ABOUT ANCHOR AND TARGET, AND svn_wc_get_actual_target2()
+
+ THE GOAL
+
+ Note the following actions, where X is the thing we wish to update,
+ P is a directory whose repository URL is the parent of
+ X's repository URL, N is directory whose repository URL is *not*
+ the parent directory of X (including the case where N is not a
+ versioned resource at all):
+
+ 1. `svn up .' from inside X.
+ 2. `svn up ...P/X' from anywhere.
+ 3. `svn up ...N/X' from anywhere.
+
+ For the purposes of the discussion, in the '...N/X' situation, X is
+ said to be a "working copy (WC) root" directory.
+
+ Now consider the four cases for X's type (file/dir) in the working
+ copy vs. the repository:
+
+ A. dir in working copy, dir in repos.
+ B. dir in working copy, file in repos.
+ C. file in working copy, dir in repos.
+ D. file in working copy, file in repos.
+
+ Here are the results we expect for each combination of the above:
+
+ 1A. Successfully update X.
+ 1B. Error (you don't want to remove your current working
+ directory out from underneath the application).
+ 1C. N/A (you can't be "inside X" if X is a file).
+ 1D. N/A (you can't be "inside X" if X is a file).
+
+ 2A. Successfully update X.
+ 2B. Successfully update X.
+ 2C. Successfully update X.
+ 2D. Successfully update X.
+
+ 3A. Successfully update X.
+ 3B. Error (you can't create a versioned file X inside a
+ non-versioned directory).
+ 3C. N/A (you can't have a versioned file X in directory that is
+ not its repository parent).
+ 3D. N/A (you can't have a versioned file X in directory that is
+ not its repository parent).
+
+ To summarize, case 2 always succeeds, and cases 1 and 3 always fail
+ (or can't occur) *except* when the target is a dir that remains a
+ dir after the update.
+
+ ACCOMPLISHING THE GOAL
+
+ Updates are accomplished by driving an editor, and an editor is
+ "rooted" on a directory. So, in order to update a file, we need to
+ break off the basename of the file, rooting the editor in that
+ file's parent directory, and then updating only that file, not the
+ other stuff in its parent directory.
+
+ Secondly, we look at the case where we wish to update a directory.
+ This is typically trivial. However, one problematic case, exists
+ when we wish to update a directory that has been removed from the
+ repository and replaced with a file of the same name. If we root
+ our edit at the initial directory, there is no editor mechanism for
+ deleting that directory and replacing it with a file (this would be
+ like having an editor now anchored on a file, which is disallowed).
+
+ All that remains is to have a function with the knowledge required
+ to properly decide where to root our editor, and what to act upon
+ with that now-rooted editor. Given a path to be updated, this
+ function should conditionally split that path into an "anchor" and
+ a "target", where the "anchor" is the directory at which the update
+ editor is rooted (meaning, editor->open_root() is called with
+ this directory in mind), and the "target" is the actual intended
+ subject of the update.
+
+ svn_wc_get_actual_target2() is that function.
+
+ So, what are the conditions?
+
+ Case I: Any time X is '.' (implying it is a directory), we won't
+ lop off a basename. So we'll root our editor at X, and update all
+ of X.
+
+ Cases II & III: Any time we are trying to update some path ...N/X,
+ we again will not lop off a basename. We can't root an editor at
+ ...N with X as a target, either because ...N isn't a versioned
+ resource at all (Case II) or because X is X is not a child of ...N
+ in the repository (Case III). We root at X, and update X.
+
+ Cases IV-???: We lop off a basename when we are updating a
+ path ...P/X, rooting our editor at ...P and updating X, or when X
+ is missing from disk.
+
+ These conditions apply whether X is a file or directory.
+
+ ---
+
+ As it turns out, commits need to have a similar check in place,
+ too, specifically for the case where a single directory is being
+ committed (we have to anchor at that directory's parent in case the
+ directory itself needs to be modified).
+*/
+
+
+svn_error_t *
+svn_wc_check_root(svn_boolean_t *is_wcroot,
+ svn_boolean_t *is_switched,
+ svn_node_kind_t *kind,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ return svn_error_trace(svn_wc__db_is_switched(is_wcroot,is_switched, kind,
+ wc_ctx->db, local_abspath,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__is_wcroot(svn_boolean_t *is_wcroot,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_is_wcroot(is_wcroot,
+ wc_ctx->db,
+ local_abspath,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__get_wcroot(const char **wcroot_abspath,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_wc__db_get_wcroot(wcroot_abspath, wc_ctx->db,
+ local_abspath, result_pool, scratch_pool);
+}
+
+
+svn_error_t *
+svn_wc_get_actual_target2(const char **anchor,
+ const char **target,
+ svn_wc_context_t *wc_ctx,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_wc_root, is_switched;
+ svn_node_kind_t kind;
+ const char *local_abspath;
+ svn_error_t *err;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+
+ err = svn_wc__db_is_switched(&is_wc_root, &is_switched, &kind,
+ wc_ctx->db, local_abspath,
+ scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND &&
+ err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ is_wc_root = FALSE;
+ is_switched = FALSE;
+ }
+
+ /* If PATH is not a WC root, or if it is a file, lop off a basename. */
+ if (!(is_wc_root || is_switched) || (kind != svn_node_dir))
+ {
+ svn_dirent_split(anchor, target, path, result_pool);
+ }
+ else
+ {
+ *anchor = apr_pstrdup(result_pool, path);
+ *target = "";
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/workqueue.c b/contrib/subversion/subversion/libsvn_wc/workqueue.c
new file mode 100644
index 0000000..ddbac15
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/workqueue.c
@@ -0,0 +1,1666 @@
+/*
+ * workqueue.c : manipulating work queue items
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_subst.h"
+#include "svn_hash.h"
+#include "svn_io.h"
+
+#include "wc.h"
+#include "wc_db.h"
+#include "workqueue.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "translate.h"
+
+#include "svn_private_config.h"
+#include "private/svn_skel.h"
+
+
+/* Workqueue operation names. */
+#define OP_FILE_COMMIT "file-commit"
+#define OP_FILE_INSTALL "file-install"
+#define OP_FILE_REMOVE "file-remove"
+#define OP_FILE_MOVE "file-move"
+#define OP_FILE_COPY_TRANSLATED "file-translate"
+#define OP_SYNC_FILE_FLAGS "sync-file-flags"
+#define OP_PREJ_INSTALL "prej-install"
+#define OP_DIRECTORY_REMOVE "dir-remove"
+#define OP_DIRECTORY_INSTALL "dir-install"
+
+#define OP_POSTUPGRADE "postupgrade"
+
+/* Legacy items */
+#define OP_BASE_REMOVE "base-remove"
+#define OP_RECORD_FILEINFO "record-fileinfo"
+#define OP_TMP_SET_TEXT_CONFLICT_MARKERS "tmp-set-text-conflict-markers"
+#define OP_TMP_SET_PROPERTY_CONFLICT_MARKER "tmp-set-property-conflict-marker"
+
+/* For work queue debugging. Generates output about its operation. */
+/* #define SVN_DEBUG_WORK_QUEUE */
+
+typedef struct work_item_baton_t work_item_baton_t;
+
+struct work_item_dispatch {
+ const char *name;
+ svn_error_t *(*func)(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+};
+
+/* Forward definition */
+static svn_error_t *
+get_and_record_fileinfo(work_item_baton_t *wqb,
+ const char *local_abspath,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *scratch_pool);
+
+/* ------------------------------------------------------------------------ */
+/* OP_REMOVE_BASE */
+
+/* Removes a BASE_NODE and all it's data, leaving any adds and copies as is.
+ Do this as a depth first traversal to make sure than any parent still exists
+ on error conditions.
+ */
+
+/* Process the OP_REMOVE_BASE work item WORK_ITEM.
+ * See svn_wc__wq_build_remove_base() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_base_remove(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ svn_revnum_t not_present_rev = SVN_INVALID_REVNUM;
+ apr_int64_t val;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool));
+
+ if (arg1->next->next)
+ {
+ not_present_rev = (svn_revnum_t)val;
+
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next->next, scratch_pool));
+ }
+ else
+ {
+ svn_boolean_t keep_not_present;
+
+ SVN_ERR_ASSERT(SVN_WC__VERSION <= 28); /* Case unused in later versions*/
+
+ keep_not_present = (val != 0);
+
+ if (keep_not_present)
+ {
+ SVN_ERR(svn_wc__db_base_get_info(NULL, NULL,
+ &not_present_rev, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ }
+
+ SVN_ERR(svn_wc__db_base_remove(db, local_abspath,
+ FALSE /* keep_as_working */,
+ TRUE /* queue_deletes */,
+ not_present_rev,
+ NULL, NULL, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_FILE_COMMIT */
+
+
+/* FILE_ABSPATH is the new text base of the newly-committed versioned file,
+ * in repository-normal form (aka "detranslated" form). Adjust the working
+ * file accordingly.
+ *
+ * If eol and/or keyword translation would cause the working file to
+ * change, then overwrite the working file with a translated copy of
+ * the new text base (but only if the translated copy differs from the
+ * current working file -- if they are the same, do nothing, to avoid
+ * clobbering timestamps unnecessarily).
+ *
+ * Set the working file's executability according to its svn:executable
+ * property.
+ *
+ * Set the working file's read-only attribute according to its properties
+ * and lock status (see svn_wc__maybe_set_read_only()).
+ *
+ * If the working file was re-translated or had its executability or
+ * read-only state changed,
+ * then set OVERWROTE_WORKING to TRUE. If the working file isn't
+ * touched at all, then set to FALSE.
+ *
+ * Use SCRATCH_POOL for any temporary allocation.
+ */
+static svn_error_t *
+install_committed_file(svn_boolean_t *overwrote_working,
+ svn_wc__db_t *db,
+ const char *file_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t same;
+ const char *tmp_wfile;
+ svn_boolean_t special;
+
+ /* start off assuming that the working file isn't touched. */
+ *overwrote_working = FALSE;
+
+ /* In the commit, newlines and keywords may have been
+ * canonicalized and/or contracted... Or they may not have
+ * been. It's kind of hard to know. Here's how we find out:
+ *
+ * 1. Make a translated tmp copy of the committed text base,
+ * translated according to the versioned file's properties.
+ * Or, if no committed text base exists (the commit must have
+ * been a propchange only), make a translated tmp copy of the
+ * working file.
+ * 2. Compare the translated tmpfile to the working file.
+ * 3. If different, copy the tmpfile over working file.
+ *
+ * This means we only rewrite the working file if we absolutely
+ * have to, which is good because it avoids changing the file's
+ * timestamp unless necessary, so editors aren't tempted to
+ * reread the file if they don't really need to.
+ */
+
+ /* Copy and translate the new base-to-be file (if found, else the working
+ * file) from repository-normal form to working form, writing a new
+ * temporary file if any translation was actually done. Set TMP_WFILE to
+ * the translated file's path, which may be the source file's path if no
+ * translation was done. Set SAME to indicate whether the new working
+ * text is the same as the old working text (or TRUE if it's a special
+ * file). */
+ {
+ const char *tmp = file_abspath;
+
+ /* Copy and translate, if necessary. The output file will be deleted at
+ * scratch_pool cleanup.
+ * ### That's not quite safe: we might rename the file and then maybe
+ * its path will get re-used for another temp file before pool clean-up.
+ * Instead, we should take responsibility for deleting it. */
+ SVN_ERR(svn_wc__internal_translated_file(&tmp_wfile, tmp, db,
+ file_abspath,
+ SVN_WC_TRANSLATE_FROM_NF,
+ cancel_func, cancel_baton,
+ scratch_pool, scratch_pool));
+
+ /* If the translation is a no-op, the text base and the working copy
+ * file contain the same content, because we use the same props here
+ * as were used to detranslate from working file to text base.
+ *
+ * In that case: don't replace the working file, but make sure
+ * it has the right executable and read_write attributes set.
+ */
+
+ SVN_ERR(svn_wc__get_translate_info(NULL, NULL,
+ NULL,
+ &special,
+ db, file_abspath, NULL, FALSE,
+ scratch_pool, scratch_pool));
+ /* Translated file returns the exact pointer if not translated. */
+ if (! special && tmp != tmp_wfile)
+ SVN_ERR(svn_io_files_contents_same_p(&same, tmp_wfile,
+ file_abspath, scratch_pool));
+ else
+ same = TRUE;
+ }
+
+ if (! same)
+ {
+ SVN_ERR(svn_io_file_rename(tmp_wfile, file_abspath, scratch_pool));
+ *overwrote_working = TRUE;
+ }
+
+ /* ### should be using OP_SYNC_FILE_FLAGS, or an internal version of
+ ### that here. do we need to set *OVERWROTE_WORKING? */
+
+ /* ### Re: OVERWROTE_WORKING, the following function is rather liberal
+ ### with setting that flag, so we should probably decide if we really
+ ### care about it when syncing flags. */
+ SVN_ERR(svn_wc__sync_flags_with_props(overwrote_working, db, file_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+process_commit_file_install(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t overwrote_working;
+
+ /* Install the new file, which may involve expanding keywords.
+ A copy of this file should have been dropped into our `tmp/text-base'
+ directory during the commit process. Part of this process
+ involves recording the textual timestamp for this entry. We'd like
+ to just use the timestamp of the working file, but it is possible
+ that at some point during the commit, the real working file might
+ have changed again.
+ */
+
+ SVN_ERR(install_committed_file(&overwrote_working, db,
+ local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* We will compute and modify the size and timestamp */
+ if (overwrote_working)
+ {
+ apr_finfo_t finfo;
+
+ SVN_ERR(svn_io_stat(&finfo, local_abspath,
+ APR_FINFO_MIN | APR_FINFO_LINK, scratch_pool));
+ SVN_ERR(svn_wc__db_global_record_fileinfo(db, local_abspath,
+ finfo.size, finfo.mtime,
+ scratch_pool));
+ }
+ else
+ {
+ svn_boolean_t modified;
+
+ /* The working copy file hasn't been overwritten. We just
+ removed the recorded size and modification time from the nodes
+ record by calling svn_wc__db_global_commit().
+
+ Now we have some file in our working copy that might be what
+ we just committed, but we are not certain at this point.
+
+ We still have a write lock here, so we check if the file is
+ what we expect it to be and if it is the right file we update
+ the recorded information. (If it isn't we keep the null data).
+
+ Instead of reimplementing all this here, we just call a function
+ that already does implement this when it notices that we have the
+ right kind of lock (and we ignore the result)
+ */
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified,
+ db, local_abspath, FALSE,
+ scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+run_file_commit(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ /* We don't both parsing the other two values in the skel. */
+
+ return svn_error_trace(
+ process_commit_file_install(db, local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__wq_build_file_commit(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t props_mod,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ svn_skel__prepend_str(OP_FILE_COMMIT, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+/* OP_POSTUPGRADE */
+
+static svn_error_t *
+run_postupgrade(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *entries_path;
+ const char *format_path;
+ const char *wcroot_abspath;
+ const char *adm_path;
+ const char *temp_path;
+ svn_error_t *err;
+
+ err = svn_wc__wipe_postupgrade(wri_abspath, FALSE,
+ cancel_func, cancel_baton, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
+ /* No entry, this can happen when the wq item is rerun. */
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, db, wri_abspath,
+ scratch_pool, scratch_pool));
+
+ adm_path = svn_wc__adm_child(wcroot_abspath, NULL, scratch_pool);
+ entries_path = svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_ENTRIES,
+ scratch_pool);
+ format_path = svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_FORMAT,
+ scratch_pool);
+
+ /* Write the 'format' and 'entries' files.
+
+ ### The order may matter for some sufficiently old clients.. but
+ ### this code only runs during upgrade after the files had been
+ ### removed earlier during the upgrade. */
+ SVN_ERR(svn_io_write_unique(&temp_path, adm_path, SVN_WC__NON_ENTRIES_STRING,
+ sizeof(SVN_WC__NON_ENTRIES_STRING) - 1,
+ svn_io_file_del_none, scratch_pool));
+ SVN_ERR(svn_io_file_rename(temp_path, format_path, scratch_pool));
+
+ SVN_ERR(svn_io_write_unique(&temp_path, adm_path, SVN_WC__NON_ENTRIES_STRING,
+ sizeof(SVN_WC__NON_ENTRIES_STRING) - 1,
+ svn_io_file_del_none, scratch_pool));
+ SVN_ERR(svn_io_file_rename(temp_path, entries_path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__wq_build_postupgrade(svn_skel_t **work_item,
+ apr_pool_t *result_pool)
+{
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ svn_skel__prepend_str(OP_POSTUPGRADE, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_FILE_INSTALL */
+
+/* Process the OP_FILE_INSTALL work item WORK_ITEM.
+ * See svn_wc__wq_build_file_install() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_file_install(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const svn_skel_t *arg4 = arg1->next->next->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ svn_boolean_t use_commit_times;
+ svn_boolean_t record_fileinfo;
+ svn_boolean_t special;
+ svn_stream_t *src_stream;
+ svn_subst_eol_style_t style;
+ const char *eol;
+ apr_hash_t *keywords;
+ const char *temp_dir_abspath;
+ svn_stream_t *dst_stream;
+ const char *dst_abspath;
+ apr_int64_t val;
+ const char *wcroot_abspath;
+ const char *source_abspath;
+ const svn_checksum_t *checksum;
+ apr_hash_t *props;
+ apr_time_t changed_date;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool));
+ use_commit_times = (val != 0);
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next->next, scratch_pool));
+ record_fileinfo = (val != 0);
+
+ SVN_ERR(svn_wc__db_read_node_install_info(&wcroot_abspath,
+ &checksum, &props,
+ &changed_date,
+ db, local_abspath, wri_abspath,
+ scratch_pool, scratch_pool));
+
+ if (arg4 != NULL)
+ {
+ /* Use the provided path for the source. */
+ local_relpath = apr_pstrmemdup(scratch_pool, arg4->data, arg4->len);
+ SVN_ERR(svn_wc__db_from_relpath(&source_abspath, db, wri_abspath,
+ local_relpath,
+ scratch_pool, scratch_pool));
+ }
+ else if (! checksum)
+ {
+ /* This error replaces a previous assertion. Reporting an error from here
+ leaves the workingqueue operation in place, so the working copy is
+ still broken!
+
+ But when we report this error the user at least knows what node has
+ this specific problem, so maybe we can find out why users see this
+ error */
+ return svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
+ _("Can't install '%s' from pristine store, "
+ "because no checksum is recorded for this "
+ "file"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_pristine_get_future_path(&source_abspath,
+ wcroot_abspath,
+ checksum,
+ scratch_pool, scratch_pool));
+ }
+
+ SVN_ERR(svn_stream_open_readonly(&src_stream, source_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Fetch all the translation bits. */
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special, db, local_abspath,
+ props, FALSE,
+ scratch_pool, scratch_pool));
+ if (special)
+ {
+ /* When this stream is closed, the resulting special file will
+ atomically be created/moved into place at LOCAL_ABSPATH. */
+ SVN_ERR(svn_subst_create_specialfile(&dst_stream, local_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Copy the "repository normal" form of the special file into the
+ special stream. */
+ SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* No need to set exec or read-only flags on special files. */
+
+ /* ### Shouldn't this record a timestamp and size, etc.? */
+ return SVN_NO_ERROR;
+ }
+
+ if (svn_subst_translation_required(style, eol, keywords,
+ FALSE /* special */,
+ TRUE /* force_eol_check */))
+ {
+ /* Wrap it in a translating (expanding) stream. */
+ src_stream = svn_subst_stream_translated(src_stream, eol,
+ TRUE /* repair */,
+ keywords,
+ TRUE /* expand */,
+ scratch_pool);
+ }
+
+ /* Where is the Right Place to put a temp file in this working copy? */
+ SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath,
+ db, wcroot_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Translate to a temporary file. We don't want the user seeing a partial
+ file, nor let them muck with it while we translate. We may also need to
+ get its TRANSLATED_SIZE before the user can monkey it. */
+ SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_abspath,
+ temp_dir_abspath,
+ svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+
+ /* Copy from the source to the dest, translating as we go. This will also
+ close both streams. */
+ SVN_ERR(svn_stream_copy3(src_stream, dst_stream,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+ /* All done. Move the file into place. */
+
+ {
+ svn_error_t *err;
+
+ err = svn_io_file_rename(dst_abspath, local_abspath, scratch_pool);
+
+ /* With a single db we might want to install files in a missing directory.
+ Simply trying this scenario on error won't do any harm and at least
+ one user reported this problem on IRC. */
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_t *err2;
+
+ err2 = svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath,
+ scratch_pool),
+ scratch_pool);
+
+ if (err2)
+ /* Creating directory didn't work: Return all errors */
+ return svn_error_trace(svn_error_compose_create(err, err2));
+ else
+ /* We could create a directory: retry install */
+ svn_error_clear(err);
+
+ SVN_ERR(svn_io_file_rename(dst_abspath, local_abspath, scratch_pool));
+ }
+ else
+ SVN_ERR(err);
+ }
+
+ /* Tweak the on-disk file according to its properties. */
+#ifndef WIN32
+ if (props && svn_hash_gets(props, SVN_PROP_EXECUTABLE))
+ SVN_ERR(svn_io_set_file_executable(local_abspath, TRUE, FALSE,
+ scratch_pool));
+#endif
+
+ /* Note that this explicitly checks the pristine properties, to make sure
+ that when the lock is locally set (=modification) it is not read only */
+ if (props && svn_hash_gets(props, SVN_PROP_NEEDS_LOCK))
+ {
+ svn_wc__db_status_t status;
+ svn_wc__db_lock_t *lock;
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, &lock, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (!lock && status != svn_wc__db_status_added)
+ SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool));
+ }
+
+ if (use_commit_times)
+ {
+ if (changed_date)
+ SVN_ERR(svn_io_set_file_affected_time(changed_date,
+ local_abspath,
+ scratch_pool));
+ }
+
+ /* ### this should happen before we rename the file into place. */
+ if (record_fileinfo)
+ {
+ SVN_ERR(get_and_record_fileinfo(wqb, local_abspath,
+ FALSE /* ignore_enoent */,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__wq_build_file_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *source_abspath,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t record_fileinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ const char *wri_abspath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ /* Use the directory of the file to install as wri_abspath to avoid
+ filestats on just obtaining the wc-root */
+ wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ /* If a SOURCE_ABSPATH was provided, then put it into the skel. If this
+ value is not provided, then the file's pristine contents will be used. */
+ if (source_abspath != NULL)
+ {
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath,
+ source_abspath,
+ result_pool, scratch_pool));
+
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ }
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ svn_skel__prepend_int(record_fileinfo, *work_item, result_pool);
+ svn_skel__prepend_int(use_commit_times, *work_item, result_pool);
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ svn_skel__prepend_str(OP_FILE_INSTALL, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_FILE_REMOVE */
+
+/* Process the OP_FILE_REMOVE work item WORK_ITEM.
+ * See svn_wc__wq_build_file_remove() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_file_remove(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ /* Remove the path, no worrying if it isn't there. */
+ return svn_error_trace(svn_io_remove_file2(local_abspath, TRUE,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__wq_build_file_remove(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ svn_skel__prepend_str(OP_FILE_REMOVE, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_DIRECTORY_REMOVE */
+
+/* Process the OP_FILE_REMOVE work item WORK_ITEM.
+ * See svn_wc__wq_build_file_remove() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_dir_remove(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ svn_boolean_t recursive;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ recursive = FALSE;
+ if (arg1->next)
+ {
+ apr_int64_t val;
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool));
+
+ recursive = (val != 0);
+ }
+
+ /* Remove the path, no worrying if it isn't there. */
+ if (recursive)
+ return svn_error_trace(
+ svn_io_remove_dir2(local_abspath, TRUE,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ else
+ {
+ svn_error_t *err;
+
+ err = svn_io_dir_remove_nonrecursive(local_abspath, scratch_pool);
+
+ if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)
+ || APR_STATUS_IS_ENOTEMPTY(err->apr_err)))
+ {
+ svn_error_clear(err);
+ err = NULL;
+ }
+
+ return svn_error_trace(err);
+ }
+}
+
+svn_error_t *
+svn_wc__wq_build_dir_remove(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_boolean_t recursive,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ if (recursive)
+ svn_skel__prepend_int(TRUE, *work_item, result_pool);
+
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ svn_skel__prepend_str(OP_DIRECTORY_REMOVE, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_FILE_MOVE */
+
+/* Process the OP_FILE_MOVE work item WORK_ITEM.
+ * See svn_wc__wq_build_file_move() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_file_move(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *src_abspath, *dst_abspath;
+ const char *local_relpath;
+ svn_error_t *err;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&src_abspath, db, wri_abspath, local_relpath,
+ scratch_pool, scratch_pool));
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->next->data,
+ arg1->next->len);
+ SVN_ERR(svn_wc__db_from_relpath(&dst_abspath, db, wri_abspath, local_relpath,
+ scratch_pool, scratch_pool));
+
+ /* Use svn_io_file_move() instead of svn_io_file_rename() to allow cross
+ device copies. We should not fail in the workqueue. */
+
+ err = svn_io_file_move(src_abspath, dst_abspath, scratch_pool);
+
+ /* If the source is not found, we assume the wq op is already handled */
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__wq_build_file_move(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *src_abspath,
+ const char *dst_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+ /* File must exist */
+ SVN_ERR(svn_io_check_path(src_abspath, &kind, result_pool));
+
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' not found"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, dst_abspath,
+ result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, src_abspath,
+ result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ svn_skel__prepend_str(OP_FILE_MOVE, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_FILE_COPY_TRANSLATED */
+
+/* Process the OP_FILE_COPY_TRANSLATED work item WORK_ITEM.
+ * See run_file_copy_translated() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_file_copy_translated(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_abspath, *src_abspath, *dst_abspath;
+ const char *local_relpath;
+ svn_subst_eol_style_t style;
+ const char *eol;
+ apr_hash_t *keywords;
+ svn_boolean_t special;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->next->data,
+ arg1->next->len);
+ SVN_ERR(svn_wc__db_from_relpath(&src_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->next->next->data,
+ arg1->next->next->len);
+ SVN_ERR(svn_wc__db_from_relpath(&dst_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__get_translate_info(&style, &eol,
+ &keywords,
+ &special,
+ db, local_abspath, NULL, FALSE,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_subst_copy_and_translate4(src_abspath, dst_abspath,
+ eol, TRUE /* repair */,
+ keywords, TRUE /* expand */,
+ special,
+ cancel_func, cancel_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__wq_build_file_copy_translated(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *src_abspath,
+ const char *dst_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ const char *local_relpath;
+
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+ /* File must exist */
+ SVN_ERR(svn_io_check_path(src_abspath, &kind, result_pool));
+
+ if (kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' not found"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, dst_abspath,
+ result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, src_abspath,
+ result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath,
+ local_abspath, result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ svn_skel__prepend_str(OP_FILE_COPY_TRANSLATED, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_DIRECTORY_INSTALL */
+
+static svn_error_t *
+run_dir_install(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__ensure_directory(local_abspath, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__wq_build_dir_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *result_pool)
+{
+ const char *local_relpath;
+
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath,
+ local_abspath, result_pool, scratch_pool));
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+
+ svn_skel__prepend_str(OP_DIRECTORY_INSTALL, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_SYNC_FILE_FLAGS */
+
+/* Process the OP_SYNC_FILE_FLAGS work item WORK_ITEM.
+ * See svn_wc__wq_build_sync_file_flags() which generates this work item.
+ * Implements (struct work_item_dispatch).func. */
+static svn_error_t *
+run_sync_file_flags(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ return svn_error_trace(svn_wc__sync_flags_with_props(NULL, db,
+ local_abspath, scratch_pool));
+}
+
+
+svn_error_t *
+svn_wc__wq_build_sync_file_flags(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ svn_skel__prepend_str(OP_SYNC_FILE_FLAGS, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_PREJ_INSTALL */
+
+static svn_error_t *
+run_prej_install(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ svn_skel_t *conflicts;
+ const svn_skel_t *prop_conflict_skel;
+ const char *tmp_prejfile_abspath;
+ const char *prejfile_abspath;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(&prejfile_abspath,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath, conflicts,
+ scratch_pool, scratch_pool));
+
+ if (arg1->next != NULL)
+ prop_conflict_skel = arg1->next;
+ else
+ SVN_ERR_MALFUNCTION(); /* ### wc_db can't provide it ... yet. */
+
+ /* Construct a property reject file in the temporary area. */
+ SVN_ERR(svn_wc__create_prejfile(&tmp_prejfile_abspath,
+ db, local_abspath,
+ prop_conflict_skel,
+ scratch_pool, scratch_pool));
+
+ /* ... and atomically move it into place. */
+ SVN_ERR(svn_io_file_rename(tmp_prejfile_abspath,
+ prejfile_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__wq_build_prej_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_relpath;
+ *work_item = svn_skel__make_empty_list(result_pool);
+
+ /* ### gotta have this, today */
+ SVN_ERR_ASSERT(conflict_skel != NULL);
+
+ SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath,
+ local_abspath, result_pool, scratch_pool));
+
+ if (conflict_skel != NULL)
+ svn_skel__prepend(conflict_skel, *work_item);
+ svn_skel__prepend_str(local_relpath, *work_item, result_pool);
+ svn_skel__prepend_str(OP_PREJ_INSTALL, *work_item, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_RECORD_FILEINFO */
+
+
+static svn_error_t *
+run_record_fileinfo(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg1 = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ apr_time_t set_time = 0;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len);
+
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ if (arg1->next)
+ {
+ apr_int64_t val;
+
+ SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool));
+ set_time = (apr_time_t)val;
+ }
+
+ if (set_time != 0)
+ {
+ svn_node_kind_t kind;
+ svn_boolean_t is_special;
+
+ /* Do not set the timestamp on special files. */
+ SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special,
+ scratch_pool));
+
+ /* Don't set affected time when local_abspath does not exist or is
+ a special file */
+ if (kind == svn_node_file && !is_special)
+ SVN_ERR(svn_io_set_file_affected_time(set_time, local_abspath,
+ scratch_pool));
+
+ /* Note that we can't use the value we get here for recording as the
+ filesystem might have a different timestamp granularity */
+ }
+
+
+ return svn_error_trace(get_and_record_fileinfo(wqb, local_abspath,
+ TRUE /* ignore_enoent */,
+ scratch_pool));
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_TMP_SET_TEXT_CONFLICT_MARKERS */
+
+
+static svn_error_t *
+run_set_text_conflict_markers(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ const char *old_abspath = NULL;
+ const char *new_abspath = NULL;
+ const char *wrk_abspath = NULL;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg->data, arg->len);
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+ arg = arg->next;
+ local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len)
+ : NULL;
+
+ if (local_relpath)
+ {
+ SVN_ERR(svn_wc__db_from_relpath(&old_abspath, db, wri_abspath,
+ local_relpath,
+ scratch_pool, scratch_pool));
+ }
+
+ arg = arg->next;
+ local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len)
+ : NULL;
+ if (local_relpath)
+ {
+ SVN_ERR(svn_wc__db_from_relpath(&new_abspath, db, wri_abspath,
+ local_relpath,
+ scratch_pool, scratch_pool));
+ }
+
+ arg = arg->next;
+ local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len)
+ : NULL;
+
+ if (local_relpath)
+ {
+ SVN_ERR(svn_wc__db_from_relpath(&wrk_abspath, db, wri_abspath,
+ local_relpath,
+ scratch_pool, scratch_pool));
+ }
+
+ /* Upgrade scenario: We have a workqueue item that describes how to install a
+ non skel conflict. Fetch all the information we can to create a new style
+ conflict. */
+ /* ### Before format 30 this is/was a common code path as we didn't install
+ ### the conflict directly in the db. It just calls the wc_db code
+ ### to set the right fields. */
+
+ {
+ /* Check if we should combine with a property conflict... */
+ svn_skel_t *conflicts;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (! conflicts)
+ {
+ /* No conflict exists, create a basic skel */
+ conflicts = svn_wc__conflict_skel_create(scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflicts, NULL, NULL,
+ scratch_pool,
+ scratch_pool));
+ }
+
+ /* Add the text conflict to the existing onflict */
+ SVN_ERR(svn_wc__conflict_skel_add_text_conflict(conflicts, db,
+ local_abspath,
+ wrk_abspath,
+ old_abspath,
+ new_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_op_mark_conflict(db, local_abspath, conflicts,
+ NULL, scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* OP_TMP_SET_PROPERTY_CONFLICT_MARKER */
+
+static svn_error_t *
+run_set_property_conflict_marker(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const svn_skel_t *work_item,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const svn_skel_t *arg = work_item->children->next;
+ const char *local_relpath;
+ const char *local_abspath;
+ const char *prej_abspath = NULL;
+
+ local_relpath = apr_pstrmemdup(scratch_pool, arg->data, arg->len);
+
+ SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
+ local_relpath, scratch_pool, scratch_pool));
+
+
+ arg = arg->next;
+ local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len)
+ : NULL;
+
+ if (local_relpath)
+ SVN_ERR(svn_wc__db_from_relpath(&prej_abspath, db, wri_abspath,
+ local_relpath,
+ scratch_pool, scratch_pool));
+
+ {
+ /* Check if we should combine with a text conflict... */
+ svn_skel_t *conflicts;
+ apr_hash_t *prop_names;
+
+ SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (! conflicts)
+ {
+ /* No conflict exists, create a basic skel */
+ conflicts = svn_wc__conflict_skel_create(scratch_pool);
+
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflicts, NULL, NULL,
+ scratch_pool,
+ scratch_pool));
+ }
+
+ prop_names = apr_hash_make(scratch_pool);
+ SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflicts, db,
+ local_abspath,
+ prej_abspath,
+ NULL, NULL, NULL,
+ prop_names,
+ scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__db_op_mark_conflict(db, local_abspath, conflicts,
+ NULL, scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static const struct work_item_dispatch dispatch_table[] = {
+ { OP_FILE_COMMIT, run_file_commit },
+ { OP_FILE_INSTALL, run_file_install },
+ { OP_FILE_REMOVE, run_file_remove },
+ { OP_FILE_MOVE, run_file_move },
+ { OP_FILE_COPY_TRANSLATED, run_file_copy_translated },
+ { OP_SYNC_FILE_FLAGS, run_sync_file_flags },
+ { OP_PREJ_INSTALL, run_prej_install },
+ { OP_DIRECTORY_REMOVE, run_dir_remove },
+ { OP_DIRECTORY_INSTALL, run_dir_install },
+
+ /* Upgrade steps */
+ { OP_POSTUPGRADE, run_postupgrade },
+
+ /* Legacy workqueue items. No longer created */
+ { OP_BASE_REMOVE, run_base_remove },
+ { OP_RECORD_FILEINFO, run_record_fileinfo },
+ { OP_TMP_SET_TEXT_CONFLICT_MARKERS, run_set_text_conflict_markers },
+ { OP_TMP_SET_PROPERTY_CONFLICT_MARKER, run_set_property_conflict_marker },
+
+ /* Sentinel. */
+ { NULL }
+};
+
+struct work_item_baton_t
+{
+ apr_pool_t *result_pool; /* Pool to allocate result in */
+
+ svn_boolean_t used; /* needs reset */
+
+ apr_hash_t *record_map; /* const char * -> svn_io_dirent2_t map */
+};
+
+
+static svn_error_t *
+dispatch_work_item(work_item_baton_t *wqb,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const svn_skel_t *work_item,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ const struct work_item_dispatch *scan;
+
+ /* Scan the dispatch table for a function to handle this work item. */
+ for (scan = &dispatch_table[0]; scan->name != NULL; ++scan)
+ {
+ if (svn_skel__matches_atom(work_item->children, scan->name))
+ {
+
+#ifdef SVN_DEBUG_WORK_QUEUE
+ SVN_DBG(("dispatch: operation='%s'\n", scan->name));
+#endif
+ SVN_ERR((*scan->func)(wqb, db, work_item, wri_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+
+#ifdef SVN_RUN_WORK_QUEUE_TWICE
+#ifdef SVN_DEBUG_WORK_QUEUE
+ SVN_DBG(("dispatch: operation='%s'\n", scan->name));
+#endif
+ /* Being able to run every workqueue item twice is one
+ requirement for workqueues to be restartable. */
+ SVN_ERR((*scan->func)(db, work_item, wri_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
+#endif
+
+ break;
+ }
+ }
+
+ if (scan->name == NULL)
+ {
+ /* We should know about ALL possible work items here. If we do not,
+ then something is wrong. Most likely, some kind of format/code
+ skew. There is nothing more we can do. Erasing or ignoring this
+ work item could leave the WC in an even more broken state.
+
+ Contrary to issue #1581, we cannot simply remove work items and
+ continue, so bail out with an error. */
+ return svn_error_createf(SVN_ERR_WC_BAD_ADM_LOG, NULL,
+ _("Unrecognized work item in the queue"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__wq_run(svn_wc__db_t *db,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_uint64_t last_id = 0;
+ work_item_baton_t wib = { 0 };
+ wib.result_pool = svn_pool_create(scratch_pool);
+
+#ifdef SVN_DEBUG_WORK_QUEUE
+ SVN_DBG(("wq_run: wri='%s'\n", wri_abspath));
+ {
+ static int count = 0;
+ const char *count_env_var = getenv("SVN_DEBUG_WORK_QUEUE");
+
+ if (count_env_var && ++count == atoi(count_env_var))
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, "fake cancel");
+ }
+#endif
+
+ while (TRUE)
+ {
+ apr_uint64_t id;
+ svn_skel_t *work_item;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ if (! wib.used)
+ {
+ /* Make sure to do this *early* in the loop iteration. There may
+ be a LAST_ID that needs to be marked as completed, *before* we
+ start worrying about anything else. */
+ SVN_ERR(svn_wc__db_wq_fetch_next(&id, &work_item, db, wri_abspath,
+ last_id, iterpool, iterpool));
+ }
+ else
+ {
+ /* Make sure to do this *early* in the loop iteration. There may
+ be a LAST_ID that needs to be marked as completed, *before* we
+ start worrying about anything else. */
+ SVN_ERR(svn_wc__db_wq_record_and_fetch_next(&id, &work_item,
+ db, wri_abspath,
+ last_id, wib.record_map,
+ iterpool,
+ wib.result_pool));
+
+ svn_pool_clear(wib.result_pool);
+ wib.record_map = NULL;
+ wib.used = FALSE;
+ }
+
+ /* Stop work queue processing, if requested. A future 'svn cleanup'
+ should be able to continue the processing. Note that we may
+ have WORK_ITEM, but we'll just skip its processing for now. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* If we have a WORK_ITEM, then process the sucker. Otherwise,
+ we're done. */
+ if (work_item == NULL)
+ break;
+
+ err = dispatch_work_item(&wib, db, wri_abspath, work_item,
+ cancel_func, cancel_baton, iterpool);
+ if (err)
+ {
+ const char *skel = svn_skel__unparse(work_item, scratch_pool)->data;
+
+ return svn_error_createf(SVN_ERR_WC_BAD_ADM_LOG, err,
+ _("Failed to run the WC DB work queue "
+ "associated with '%s', work item %d %s"),
+ svn_dirent_local_style(wri_abspath,
+ scratch_pool),
+ (int)id, skel);
+ }
+
+ /* The work item finished without error. Mark it completed
+ in the next loop. */
+ last_id = id;
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_skel_t *
+svn_wc__wq_merge(svn_skel_t *work_item1,
+ svn_skel_t *work_item2,
+ apr_pool_t *result_pool)
+{
+ /* If either argument is NULL, then just return the other. */
+ if (work_item1 == NULL)
+ return work_item2;
+ if (work_item2 == NULL)
+ return work_item1;
+
+ /* We have two items. Figure out how to join them. */
+ if (SVN_WC__SINGLE_WORK_ITEM(work_item1))
+ {
+ if (SVN_WC__SINGLE_WORK_ITEM(work_item2))
+ {
+ /* Both are singular work items. Construct a list, then put
+ both work items into it (in the proper order). */
+
+ svn_skel_t *result = svn_skel__make_empty_list(result_pool);
+
+ svn_skel__prepend(work_item2, result);
+ svn_skel__prepend(work_item1, result);
+ return result;
+ }
+
+ /* WORK_ITEM2 is a list of work items. We can simply shove WORK_ITEM1
+ in the front to keep the ordering. */
+ svn_skel__prepend(work_item1, work_item2);
+ return work_item2;
+ }
+ /* WORK_ITEM1 is a list of work items. */
+
+ if (SVN_WC__SINGLE_WORK_ITEM(work_item2))
+ {
+ /* Put WORK_ITEM2 onto the end of the WORK_ITEM1 list. */
+ svn_skel__append(work_item1, work_item2);
+ return work_item1;
+ }
+
+ /* We have two lists of work items. We need to chain all of the work
+ items into one big list. We will leave behind the WORK_ITEM2 skel,
+ as we only want its children. */
+ svn_skel__append(work_item1, work_item2->children);
+ return work_item1;
+}
+
+
+static svn_error_t *
+get_and_record_fileinfo(work_item_baton_t *wqb,
+ const char *local_abspath,
+ svn_boolean_t ignore_enoent,
+ apr_pool_t *scratch_pool)
+{
+ const svn_io_dirent2_t *dirent;
+
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, ignore_enoent,
+ wqb->result_pool, scratch_pool));
+
+ if (dirent->kind != svn_node_file)
+ return SVN_NO_ERROR;
+
+ wqb->used = TRUE;
+
+ if (! wqb->record_map)
+ wqb->record_map = apr_hash_make(wqb->result_pool);
+
+ svn_hash_sets(wqb->record_map, apr_pstrdup(wqb->result_pool, local_abspath),
+ dirent);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/libsvn_wc/workqueue.h b/contrib/subversion/subversion/libsvn_wc/workqueue.h
new file mode 100644
index 0000000..0617a60
--- /dev/null
+++ b/contrib/subversion/subversion/libsvn_wc/workqueue.h
@@ -0,0 +1,235 @@
+/*
+ * workqueue.h : manipulating work queue items
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ *
+ * Greg says:
+ *
+ * I think the current items are misdirected
+ * work items should NOT touch the DB
+ * the work items should be inserted into WORK_QUEUE by wc_db,
+ * meaning: workqueue.[ch] should return work items for passing to the wc_db API,
+ * which installs them during a transaction with the other work,
+ * and those items should *only* make the on-disk state match what is in the database
+ * before you rejoined the chan, I was discussing with Bert that I might rejigger the postcommit work,
+ * in order to do the prop file handling as work items,
+ * and pass those to db_global_commit for insertion as part of its transaction
+ * so that once we switch to in-db props, those work items just get deleted,
+ * (where they're simple things like: move this file to there, or delete that file)
+ * i.e. workqueue should be seriously dumb
+ * */
+
+#ifndef SVN_WC_WORKQUEUE_H
+#define SVN_WC_WORKQUEUE_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_wc.h"
+
+#include "wc_db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Returns TRUE if WI refers to a single work item. Returns FALSE if
+ WI is a list of work items. WI must not be NULL.
+
+ A work item looks like: (OP_CODE arg1 arg2 ...)
+
+ If we see OP_CODE (an atom) as WI's first child, then this is a
+ single work item. Otherwise, it is a list of work items. */
+#define SVN_WC__SINGLE_WORK_ITEM(wi) ((wi)->children->is_atom)
+
+
+/* Combine WORK_ITEM1 and WORK_ITEM2 into a single, resulting work item.
+
+ Each of the WORK_ITEM parameters may have one of three values:
+
+ NULL no work item
+ (OPCODE arg1 arg2 ...) single work item
+ ((OPCODE ...) (OPCODE ...)) multiple work items
+
+ These will be combined as appropriate, and returned in one of the
+ above three styles.
+
+ The resulting list will be ordered: WORK_ITEM1 first, then WORK_ITEM2 */
+svn_skel_t *
+svn_wc__wq_merge(svn_skel_t *work_item1,
+ svn_skel_t *work_item2,
+ apr_pool_t *result_pool);
+
+
+/* For the WCROOT identified by the DB and WRI_ABSPATH pair, run any
+ work items that may be present in its workqueue. */
+svn_error_t *
+svn_wc__wq_run(svn_wc__db_t *db,
+ const char *wri_abspath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *WORK_ITEM to a new work item that will install the working
+ copy file at LOCAL_ABSPATH. If USE_COMMIT_TIMES is TRUE, then the newly
+ installed file will use the nodes CHANGE_DATE for the file timestamp.
+ If RECORD_FILEINFO is TRUE, then the resulting RECORDED_TIME and
+ RECORDED_SIZE will be recorded in the database.
+
+ If SOURCE_ABSPATH is NULL, then the pristine contents will be installed
+ (with appropriate translation). If SOURCE_ABSPATH is not NULL, then it
+ specifies a source file for the translation. The file must exist for as
+ long as *WORK_ITEM exists (and is queued). Typically, it will be a
+ temporary file, and an OP_FILE_REMOVE will be queued to later remove it.
+*/
+svn_error_t *
+svn_wc__wq_build_file_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *source_abspath,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t record_fileinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *WORK_ITEM to a new work item that will remove a single
+ file LOCAL_ABSPATH from the working copy identified by the pair DB,
+ WRI_ABSPATH. */
+svn_error_t *
+svn_wc__wq_build_file_remove(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *WORK_ITEM to a new work item that will remove a single
+ directory or if RECURSIVE is TRUE a directory with all its
+ descendants. */
+svn_error_t *
+svn_wc__wq_build_dir_remove(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *local_abspath,
+ svn_boolean_t recursive,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *WORK_ITEM to a new work item that describes a move of
+ a file or directory from SRC_ABSPATH to DST_ABSPATH, ready for
+ storing in the working copy managing DST_ABSPATH.
+
+ Perform temporary allocations in SCRATCH_POOL and *WORK_ITEM in
+ RESULT_POOL.
+*/
+svn_error_t *
+svn_wc__wq_build_file_move(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const char *src_abspath,
+ const char *dst_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *WORK_ITEM to a new work item that describes a copy from
+ SRC_ABSPATH to DST_ABSPATH, while translating the stream using
+ the information from LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__wq_build_file_copy_translated(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *src_abspath,
+ const char *dst_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *WORK_ITEM to a new work item that will synchronize the
+ target node's readonly and executable flags with the values defined
+ by its properties and lock status. */
+svn_error_t *
+svn_wc__wq_build_sync_file_flags(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set *WORK_ITEM to a new work item that will install a property reject
+ file for LOCAL_ABSPATH into the working copy. The property conflicts will
+ be taken from CONFLICT_SKEL.
+
+ ### Caution: Links CONFLICT_SKEL into the *WORK_ITEM, which involves
+ modifying *CONFLICT_SKEL.
+
+ ### TODO: Make CONFLICT_SKEL 'const' and dup it into RESULT_POOL.
+
+ ### TODO: If CONFLICT_SKEL is NULL, take property conflicts from wc_db
+ for the given DB/LOCAL_ABSPATH.
+ */
+svn_error_t *
+svn_wc__wq_build_prej_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_skel_t *conflict_skel,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Handle the final post-commit step of retranslating and recording the
+ working copy state of a committed file.
+
+ If PROP_MODS is false, assume that properties are not changed.
+
+ (Property modifications are read when svn_wc__wq_build_file_commit
+ is called and processed when the working queue is being evaluated)
+
+ Allocate *work_item in RESULT_POOL. Perform temporary allocations
+ in SCRATCH_POOL.
+ */
+svn_error_t *
+svn_wc__wq_build_file_commit(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t prop_mods,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Set *WORK_ITEM to a new work item that will install the working
+ copy directory at LOCAL_ABSPATH. */
+svn_error_t *
+svn_wc__wq_build_dir_install(svn_skel_t **work_item,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *result_pool);
+
+svn_error_t *
+svn_wc__wq_build_postupgrade(svn_skel_t **work_item,
+ apr_pool_t *scratch_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_WC_WORKQUEUE_H */
diff --git a/contrib/subversion/subversion/svn/add-cmd.c b/contrib/subversion/subversion/svn/add-cmd.c
new file mode 100644
index 0000000..44f73c7
--- /dev/null
+++ b/contrib/subversion/subversion/svn/add-cmd.c
@@ -0,0 +1,113 @@
+/*
+ * add-cmd.c -- Subversion add/unadd commands
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+#define APR_WANT_STDIO
+#include <apr_want.h>
+
+#include "svn_path.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__add(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ int i;
+ apr_pool_t *iterpool;
+ apr_array_header_t *errors = apr_array_make(pool, 0, sizeof(apr_status_t));
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_infinity;
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ iterpool = svn_pool_create(pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+ SVN_ERR(svn_cl__try
+ (svn_client_add5(target,
+ opt_state->depth,
+ opt_state->force, opt_state->no_ignore,
+ opt_state->no_autoprops, opt_state->parents,
+ ctx, iterpool),
+ errors, opt_state->quiet,
+ SVN_ERR_ENTRY_EXISTS,
+ SVN_ERR_WC_PATH_NOT_FOUND,
+ SVN_NO_ERROR));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ if (errors->nelts > 0)
+ {
+ svn_error_t *err;
+
+ err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL);
+ for (i = 0; i < errors->nelts; i++)
+ {
+ apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t);
+ if (status == SVN_ERR_WC_PATH_NOT_FOUND)
+ err = svn_error_quick_wrap(err,
+ _("Could not add all targets because "
+ "some targets don't exist"));
+ else if (status == SVN_ERR_ENTRY_EXISTS)
+ err = svn_error_quick_wrap(err,
+ _("Could not add all targets because "
+ "some targets are already versioned"));
+ }
+
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/blame-cmd.c b/contrib/subversion/subversion/svn/blame-cmd.c
new file mode 100644
index 0000000..174a199
--- /dev/null
+++ b/contrib/subversion/subversion/svn/blame-cmd.c
@@ -0,0 +1,419 @@
+/*
+ * blame-cmd.c -- Display blame information
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_cmdline.h"
+#include "svn_xml.h"
+#include "svn_time.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+typedef struct blame_baton_t
+{
+ svn_cl__opt_state_t *opt_state;
+ svn_stream_t *out;
+ svn_stringbuf_t *sbuf;
+} blame_baton_t;
+
+
+/*** Code. ***/
+
+/* This implements the svn_client_blame_receiver3_t interface, printing
+ XML to stdout. */
+static svn_error_t *
+blame_receiver_xml(void *baton,
+ svn_revnum_t start_revnum,
+ svn_revnum_t end_revnum,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ apr_hash_t *rev_props,
+ svn_revnum_t merged_revision,
+ apr_hash_t *merged_rev_props,
+ const char *merged_path,
+ const char *line,
+ svn_boolean_t local_change,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state =
+ ((blame_baton_t *) baton)->opt_state;
+ svn_stringbuf_t *sb = ((blame_baton_t *) baton)->sbuf;
+
+ /* "<entry ...>" */
+ /* line_no is 0-based, but the rest of the world is probably Pascal
+ programmers, so we make them happy and output 1-based line numbers. */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
+ "line-number",
+ apr_psprintf(pool, "%" APR_INT64_T_FMT,
+ line_no + 1),
+ NULL);
+
+ if (SVN_IS_VALID_REVNUM(revision))
+ svn_cl__print_xml_commit(&sb, revision,
+ svn_prop_get_value(rev_props,
+ SVN_PROP_REVISION_AUTHOR),
+ svn_prop_get_value(rev_props,
+ SVN_PROP_REVISION_DATE),
+ pool);
+
+ if (opt_state->use_merge_history && SVN_IS_VALID_REVNUM(merged_revision))
+ {
+ /* "<merged>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "merged",
+ "path", merged_path, NULL);
+
+ svn_cl__print_xml_commit(&sb, merged_revision,
+ svn_prop_get_value(merged_rev_props,
+ SVN_PROP_REVISION_AUTHOR),
+ svn_prop_get_value(merged_rev_props,
+ SVN_PROP_REVISION_DATE),
+ pool);
+
+ /* "</merged>" */
+ svn_xml_make_close_tag(&sb, pool, "merged");
+
+ }
+
+ /* "</entry>" */
+ svn_xml_make_close_tag(&sb, pool, "entry");
+
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ svn_stringbuf_setempty(sb);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+print_line_info(svn_stream_t *out,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ const char *path,
+ svn_boolean_t verbose,
+ svn_revnum_t end_revnum,
+ apr_pool_t *pool)
+{
+ const char *time_utf8;
+ const char *time_stdout;
+ const char *rev_str;
+ int rev_maxlength;
+
+ /* The standard column width for the revision number is 6 characters.
+ If the revision number can potentially be larger (i.e. if the end_revnum
+ is larger than 1000000), we increase the column width as needed. */
+ rev_maxlength = 6;
+ while (end_revnum >= 1000000)
+ {
+ rev_maxlength++;
+ end_revnum = end_revnum / 10;
+ }
+ rev_str = SVN_IS_VALID_REVNUM(revision)
+ ? apr_psprintf(pool, "%*ld", rev_maxlength, revision)
+ : apr_psprintf(pool, "%*s", rev_maxlength, "-");
+
+ if (verbose)
+ {
+ if (date)
+ {
+ SVN_ERR(svn_cl__time_cstring_to_human_cstring(&time_utf8,
+ date, pool));
+ SVN_ERR(svn_cmdline_cstring_from_utf8(&time_stdout, time_utf8,
+ pool));
+ }
+ else
+ {
+ /* ### This is a 44 characters long string. It assumes the current
+ format of svn_time_to_human_cstring and also 3 letter
+ abbreviations for the month and weekday names. Else, the
+ line contents will be misaligned. */
+ time_stdout = " -";
+ }
+
+ SVN_ERR(svn_stream_printf(out, pool, "%s %10s %s ", rev_str,
+ author ? author : " -",
+ time_stdout));
+
+ if (path)
+ SVN_ERR(svn_stream_printf(out, pool, "%-14s ", path));
+ }
+ else
+ {
+ return svn_stream_printf(out, pool, "%s %10.10s ", rev_str,
+ author ? author : " -");
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements the svn_client_blame_receiver3_t interface. */
+static svn_error_t *
+blame_receiver(void *baton,
+ svn_revnum_t start_revnum,
+ svn_revnum_t end_revnum,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ apr_hash_t *rev_props,
+ svn_revnum_t merged_revision,
+ apr_hash_t *merged_rev_props,
+ const char *merged_path,
+ const char *line,
+ svn_boolean_t local_change,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state =
+ ((blame_baton_t *) baton)->opt_state;
+ svn_stream_t *out = ((blame_baton_t *)baton)->out;
+ svn_boolean_t use_merged = FALSE;
+
+ if (opt_state->use_merge_history)
+ {
+ /* Choose which revision to use. If they aren't equal, prefer the
+ earliest revision. Since we do a forward blame, we want to the first
+ revision which put the line in its current state, so we use the
+ earliest revision. If we ever switch to a backward blame algorithm,
+ we may need to adjust this. */
+ if (merged_revision < revision)
+ {
+ SVN_ERR(svn_stream_puts(out, "G "));
+ use_merged = TRUE;
+ }
+ else
+ SVN_ERR(svn_stream_puts(out, " "));
+ }
+
+ if (use_merged)
+ SVN_ERR(print_line_info(out, merged_revision,
+ svn_prop_get_value(merged_rev_props,
+ SVN_PROP_REVISION_AUTHOR),
+ svn_prop_get_value(merged_rev_props,
+ SVN_PROP_REVISION_DATE),
+ merged_path, opt_state->verbose, end_revnum,
+ pool));
+ else
+ SVN_ERR(print_line_info(out, revision,
+ svn_prop_get_value(rev_props,
+ SVN_PROP_REVISION_AUTHOR),
+ svn_prop_get_value(rev_props,
+ SVN_PROP_REVISION_DATE),
+ NULL, opt_state->verbose, end_revnum,
+ pool));
+
+ return svn_stream_printf(out, pool, "%s%s", line, APR_EOL_STR);
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__blame(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_pool_t *subpool;
+ apr_array_header_t *targets;
+ blame_baton_t bl;
+ int i;
+ svn_boolean_t end_revision_unspecified = FALSE;
+ svn_diff_file_options_t *diff_options = svn_diff_file_options_create(pool);
+ svn_boolean_t seen_nonexistent_target = FALSE;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Blame needs a file on which to operate. */
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ {
+ if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ /* In the case that -rX was specified, we actually want to set the
+ range to be -r1:X. */
+
+ opt_state->end_revision = opt_state->start_revision;
+ opt_state->start_revision.kind = svn_opt_revision_number;
+ opt_state->start_revision.value.number = 1;
+ }
+ else
+ end_revision_unspecified = TRUE;
+ }
+
+ if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
+ {
+ opt_state->start_revision.kind = svn_opt_revision_number;
+ opt_state->start_revision.value.number = 1;
+ }
+
+ /* The final conclusion from issue #2431 is that blame info
+ is client output (unlike 'svn cat' which plainly cats the file),
+ so the EOL style should be the platform local one.
+ */
+ if (! opt_state->xml)
+ SVN_ERR(svn_stream_for_stdout(&bl.out, pool));
+ else
+ bl.sbuf = svn_stringbuf_create_empty(pool);
+
+ bl.opt_state = opt_state;
+
+ subpool = svn_pool_create(pool);
+
+ if (opt_state->extensions)
+ {
+ apr_array_header_t *opts;
+ opts = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
+ SVN_ERR(svn_diff_file_options_parse(diff_options, opts, pool));
+ }
+
+ if (opt_state->xml)
+ {
+ if (opt_state->verbose)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'verbose' option invalid in XML mode"));
+
+ /* If output is not incremental, output the XML header and wrap
+ everything in a top-level element. This makes the output in
+ its entirety a well-formed XML document. */
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_header("blame", pool));
+ }
+ else
+ {
+ if (opt_state->incremental)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'incremental' option only valid in XML "
+ "mode"));
+ }
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ svn_error_t *err;
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+ svn_client_blame_receiver3_t receiver;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Check for a peg revision. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ subpool));
+
+ if (end_revision_unspecified)
+ {
+ if (peg_revision.kind != svn_opt_revision_unspecified)
+ opt_state->end_revision = peg_revision;
+ else if (svn_path_is_url(target))
+ opt_state->end_revision.kind = svn_opt_revision_head;
+ else
+ opt_state->end_revision.kind = svn_opt_revision_working;
+ }
+
+ if (opt_state->xml)
+ {
+ /* "<target ...>" */
+ /* We don't output this tag immediately, which avoids creating
+ a target element if this path is skipped. */
+ const char *outpath = truepath;
+ if (! svn_path_is_url(target))
+ outpath = svn_dirent_local_style(truepath, subpool);
+ svn_xml_make_open_tag(&bl.sbuf, pool, svn_xml_normal, "target",
+ "path", outpath, NULL);
+
+ receiver = blame_receiver_xml;
+ }
+ else
+ receiver = blame_receiver;
+
+ err = svn_client_blame5(truepath,
+ &peg_revision,
+ &opt_state->start_revision,
+ &opt_state->end_revision,
+ diff_options,
+ opt_state->force,
+ opt_state->use_merge_history,
+ receiver,
+ &bl,
+ ctx,
+ subpool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_CLIENT_IS_BINARY_FILE)
+ {
+ svn_error_clear(err);
+ SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
+ _("Skipping binary file "
+ "(use --force to treat as text): "
+ "'%s'\n"),
+ target));
+ }
+ else if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
+ err->apr_err == SVN_ERR_ENTRY_NOT_FOUND ||
+ err->apr_err == SVN_ERR_FS_NOT_FILE ||
+ err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ err = NULL;
+ seen_nonexistent_target = TRUE;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+ else if (opt_state->xml)
+ {
+ /* "</target>" */
+ svn_xml_make_close_tag(&(bl.sbuf), pool, "target");
+ SVN_ERR(svn_cl__error_checked_fputs(bl.sbuf->data, stdout));
+ }
+
+ if (opt_state->xml)
+ svn_stringbuf_setempty(bl.sbuf);
+ }
+ svn_pool_destroy(subpool);
+ if (opt_state->xml && ! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_footer("blame", pool));
+
+ if (seen_nonexistent_target)
+ return svn_error_create(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Could not perform blame on all targets because some "
+ "targets don't exist"));
+ else
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/cat-cmd.c b/contrib/subversion/subversion/svn/cat-cmd.c
new file mode 100644
index 0000000..551420e
--- /dev/null
+++ b/contrib/subversion/subversion/svn/cat-cmd.c
@@ -0,0 +1,118 @@
+/*
+ * cat-cmd.c -- Print the content of a file or URL.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_opt.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__cat(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ int i;
+ svn_stream_t *out;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_array_header_t *errors = apr_array_make(pool, 0, sizeof(apr_status_t));
+ svn_error_t *err;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Cat cannot operate on an implicit '.' so a filename is required */
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ SVN_ERR(svn_stream_for_stdout(&out, pool));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Get peg revisions. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ subpool));
+
+ SVN_ERR(svn_cl__try(svn_client_cat2(out, truepath, &peg_revision,
+ &(opt_state->start_revision),
+ ctx, subpool),
+ errors, opt_state->quiet,
+ SVN_ERR_UNVERSIONED_RESOURCE,
+ SVN_ERR_ENTRY_NOT_FOUND,
+ SVN_ERR_CLIENT_IS_DIRECTORY,
+ SVN_ERR_FS_NOT_FOUND,
+ SVN_NO_ERROR));
+ }
+ svn_pool_destroy(subpool);
+
+ if (errors->nelts > 0)
+ {
+ err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL);
+
+ for (i = 0; i < errors->nelts; i++)
+ {
+ apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t);
+
+ if (status == SVN_ERR_ENTRY_NOT_FOUND ||
+ status == SVN_ERR_FS_NOT_FOUND)
+ err = svn_error_quick_wrap(err,
+ _("Could not cat all targets because "
+ "some targets don't exist"));
+ else if (status == SVN_ERR_UNVERSIONED_RESOURCE)
+ err = svn_error_quick_wrap(err,
+ _("Could not cat all targets because "
+ "some targets are not versioned"));
+ else if (status == SVN_ERR_CLIENT_IS_DIRECTORY)
+ err = svn_error_quick_wrap(err,
+ _("Could not cat all targets because "
+ "some targets are directories"));
+ }
+
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/changelist-cmd.c b/contrib/subversion/subversion/svn/changelist-cmd.c
new file mode 100644
index 0000000..46347b6
--- /dev/null
+++ b/contrib/subversion/subversion/svn/changelist-cmd.c
@@ -0,0 +1,149 @@
+/*
+ * changelist-cmd.c -- Associate (or deassociate) a wc path with a changelist.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__changelist(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const char *changelist_name = NULL;
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ svn_depth_t depth = opt_state->depth;
+ apr_array_header_t *errors = apr_array_make(pool, 0, sizeof(apr_status_t));
+
+ /* If we're not removing changelists, then our first argument should
+ be the name of a changelist. */
+
+ if (! opt_state->remove)
+ {
+ apr_array_header_t *args;
+ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
+ changelist_name = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(svn_utf_cstring_to_utf8(&changelist_name,
+ changelist_name, pool));
+ }
+
+ /* Parse the remaining arguments as paths. */
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Changelist has no implicit dot-target `.', so don't you put that
+ code here! */
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ if (opt_state->quiet)
+ /* FIXME: This is required because svn_client_create_context()
+ always initializes ctx->notify_func2 to a wrapper function
+ which calls ctx->notify_func() if it isn't NULL. In other
+ words, typically, ctx->notify_func2 is never NULL. This isn't
+ usually a problem, but the changelist logic generates
+ svn_error_t's as part of its notification.
+
+ So, svn_wc_set_changelist() checks its notify_func (our
+ ctx->notify_func2) for NULL-ness, and seeing non-NULL-ness,
+ generates a notificaton object and svn_error_t to describe some
+ problem. It passes that off to its notify_func (our
+ ctx->notify_func2) which drops the notification on the floor
+ (because it wraps a NULL ctx->notify_func). But svn_error_t's
+ dropped on the floor cause SEGFAULTs at pool cleanup time --
+ they need instead to be cleared.
+
+ SOOOooo... we set our ctx->notify_func2 to NULL so the WC code
+ doesn't even generate the errors. */
+ ctx->notify_func2 = NULL;
+
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_empty;
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ if (changelist_name)
+ {
+ SVN_ERR(svn_cl__try(
+ svn_client_add_to_changelist(targets, changelist_name,
+ depth, opt_state->changelists,
+ ctx, pool),
+ errors, opt_state->quiet,
+ SVN_ERR_UNVERSIONED_RESOURCE,
+ SVN_ERR_WC_PATH_NOT_FOUND,
+ SVN_NO_ERROR));
+ }
+ else
+ {
+ SVN_ERR(svn_cl__try(
+ svn_client_remove_from_changelists(targets, depth,
+ opt_state->changelists,
+ ctx, pool),
+ errors, opt_state->quiet,
+ SVN_ERR_UNVERSIONED_RESOURCE,
+ SVN_ERR_WC_PATH_NOT_FOUND,
+ SVN_NO_ERROR));
+ }
+
+ if (errors->nelts > 0)
+ {
+ int i;
+ svn_error_t *err;
+
+ err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL);
+ for (i = 0; i < errors->nelts; i++)
+ {
+ apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t);
+
+ if (status == SVN_ERR_WC_PATH_NOT_FOUND)
+ err = svn_error_quick_wrap(err,
+ _("Could not set changelists on "
+ "all targets because some targets "
+ "don't exist"));
+ else if (status == SVN_ERR_UNVERSIONED_RESOURCE)
+ err = svn_error_quick_wrap(err,
+ _("Could not set changelists on "
+ "all targets because some targets "
+ "are not versioned"));
+ }
+
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/checkout-cmd.c b/contrib/subversion/subversion/svn/checkout-cmd.c
new file mode 100644
index 0000000..6c192a0
--- /dev/null
+++ b/contrib/subversion/subversion/svn/checkout-cmd.c
@@ -0,0 +1,173 @@
+/*
+ * checkout-cmd.c -- Subversion checkout command
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/*
+ This is what it does
+
+ - case 1: one URL
+ $ svn co http://host/repos/module
+ checkout into ./module/
+
+ - case 2: one URL and explicit path
+ $ svn co http://host/repos/module path
+ checkout into ./path/
+
+ - case 3: multiple URLs
+ $ svn co http://host1/repos1/module1 http://host2/repos2/module2
+ checkout into ./module1/ and ./module2/
+
+ - case 4: multiple URLs and explicit path
+ $ svn co http://host1/repos1/module1 http://host2/repos2/module2 path
+ checkout into ./path/module1/ and ./path/module2/
+
+ Is this the same as CVS? Does it matter if it is not?
+*/
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__checkout(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_pool_t *subpool;
+ apr_array_header_t *targets;
+ const char *last_target, *local_dir;
+ int i;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
+
+ /* Determine LOCAL_DIR (case 1: URL basename; 2,4: specified; 3: "")
+ * and leave TARGETS holding just the source URLs. */
+ last_target = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *);
+ if (svn_path_is_url(last_target))
+ {
+ if (targets->nelts == 1)
+ {
+ svn_opt_revision_t pegrev;
+
+ /* Use the URL basename, discarding any peg revision. */
+ SVN_ERR(svn_opt_parse_path(&pegrev, &local_dir, last_target, pool));
+ local_dir = svn_uri_basename(local_dir, pool);
+ }
+ else
+ {
+ local_dir = "";
+ }
+ }
+ else
+ {
+ if (targets->nelts == 1)
+ /* What? They gave us one target, and it wasn't a URL. */
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, NULL);
+
+ apr_array_pop(targets);
+ local_dir = last_target;
+ }
+
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cl__notifier_mark_checkout(ctx->notify_baton2));
+
+ subpool = svn_pool_create(pool);
+ for (i = 0; i < targets->nelts; ++i)
+ {
+ const char *repos_url = APR_ARRAY_IDX(targets, i, const char *);
+ const char *target_dir;
+ const char *true_url;
+ svn_opt_revision_t revision = opt_state->start_revision;
+ svn_opt_revision_t peg_revision;
+
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Validate the REPOS_URL */
+ if (! svn_path_is_url(repos_url))
+ return svn_error_createf
+ (SVN_ERR_BAD_URL, NULL,
+ _("'%s' does not appear to be a URL"), repos_url);
+
+ /* Get a possible peg revision. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &true_url, repos_url,
+ subpool));
+
+ /* Use sub-directory of destination if checking-out multiple URLs */
+ if (targets->nelts == 1)
+ {
+ target_dir = local_dir;
+ }
+ else
+ {
+ target_dir = svn_dirent_join(local_dir,
+ svn_uri_basename(true_url, subpool),
+ subpool);
+ }
+
+ /* Checkout doesn't accept an unspecified revision, so default to
+ the peg revision, or to HEAD if there wasn't a peg. */
+ if (revision.kind == svn_opt_revision_unspecified)
+ {
+ if (peg_revision.kind != svn_opt_revision_unspecified)
+ revision = peg_revision;
+ else
+ revision.kind = svn_opt_revision_head;
+ }
+
+ SVN_ERR(svn_client_checkout3
+ (NULL, true_url, target_dir,
+ &peg_revision,
+ &revision,
+ opt_state->depth,
+ opt_state->ignore_externals,
+ opt_state->force,
+ ctx, subpool));
+ }
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/cl-conflicts.c b/contrib/subversion/subversion/svn/cl-conflicts.c
new file mode 100644
index 0000000..440c9d7
--- /dev/null
+++ b/contrib/subversion/subversion/svn/cl-conflicts.c
@@ -0,0 +1,454 @@
+/*
+ * conflicts.c: Tree conflicts.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "cl-conflicts.h"
+#include "svn_hash.h"
+#include "svn_xml.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "private/svn_token.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/* A map for svn_wc_conflict_action_t values to XML strings */
+static const svn_token_map_t map_conflict_action_xml[] =
+{
+ { "edit", svn_wc_conflict_action_edit },
+ { "delete", svn_wc_conflict_action_delete },
+ { "add", svn_wc_conflict_action_add },
+ { "replace", svn_wc_conflict_action_replace },
+ { NULL, 0 }
+};
+
+/* A map for svn_wc_conflict_reason_t values to XML strings */
+static const svn_token_map_t map_conflict_reason_xml[] =
+{
+ { "edit", svn_wc_conflict_reason_edited },
+ { "delete", svn_wc_conflict_reason_deleted },
+ { "missing", svn_wc_conflict_reason_missing },
+ { "obstruction", svn_wc_conflict_reason_obstructed },
+ { "add", svn_wc_conflict_reason_added },
+ { "replace", svn_wc_conflict_reason_replaced },
+ { "unversioned", svn_wc_conflict_reason_unversioned },
+ { "moved-away", svn_wc_conflict_reason_moved_away },
+ { "moved-here", svn_wc_conflict_reason_moved_here },
+ { NULL, 0 }
+};
+
+static const svn_token_map_t map_conflict_kind_xml[] =
+{
+ { "text", svn_wc_conflict_kind_text },
+ { "property", svn_wc_conflict_kind_property },
+ { "tree", svn_wc_conflict_kind_tree },
+ { NULL, 0 }
+};
+
+/* Return a localised string representation of the local part of a conflict;
+ NULL for non-localised odd cases. */
+static const char *
+local_reason_str(svn_node_kind_t kind, svn_wc_conflict_reason_t reason)
+{
+ switch (kind)
+ {
+ case svn_node_file:
+ switch (reason)
+ {
+ case svn_wc_conflict_reason_edited:
+ return _("local file edit");
+ case svn_wc_conflict_reason_obstructed:
+ return _("local file obstruction");
+ case svn_wc_conflict_reason_deleted:
+ return _("local file delete");
+ case svn_wc_conflict_reason_missing:
+ return _("local file missing");
+ case svn_wc_conflict_reason_unversioned:
+ return _("local file unversioned");
+ case svn_wc_conflict_reason_added:
+ return _("local file add");
+ case svn_wc_conflict_reason_replaced:
+ return _("local file replace");
+ case svn_wc_conflict_reason_moved_away:
+ return _("local file moved away");
+ case svn_wc_conflict_reason_moved_here:
+ return _("local file moved here");
+ }
+ break;
+ case svn_node_dir:
+ switch (reason)
+ {
+ case svn_wc_conflict_reason_edited:
+ return _("local dir edit");
+ case svn_wc_conflict_reason_obstructed:
+ return _("local dir obstruction");
+ case svn_wc_conflict_reason_deleted:
+ return _("local dir delete");
+ case svn_wc_conflict_reason_missing:
+ return _("local dir missing");
+ case svn_wc_conflict_reason_unversioned:
+ return _("local dir unversioned");
+ case svn_wc_conflict_reason_added:
+ return _("local dir add");
+ case svn_wc_conflict_reason_replaced:
+ return _("local dir replace");
+ case svn_wc_conflict_reason_moved_away:
+ return _("local dir moved away");
+ case svn_wc_conflict_reason_moved_here:
+ return _("local dir moved here");
+ }
+ break;
+ case svn_node_symlink:
+ case svn_node_none:
+ case svn_node_unknown:
+ break;
+ }
+ return NULL;
+}
+
+/* Return a localised string representation of the incoming part of a
+ conflict; NULL for non-localised odd cases. */
+static const char *
+incoming_action_str(svn_node_kind_t kind, svn_wc_conflict_action_t action)
+{
+ switch (kind)
+ {
+ case svn_node_file:
+ switch (action)
+ {
+ case svn_wc_conflict_action_edit:
+ return _("incoming file edit");
+ case svn_wc_conflict_action_add:
+ return _("incoming file add");
+ case svn_wc_conflict_action_delete:
+ return _("incoming file delete");
+ case svn_wc_conflict_action_replace:
+ return _("incoming file replace");
+ }
+ break;
+ case svn_node_dir:
+ switch (action)
+ {
+ case svn_wc_conflict_action_edit:
+ return _("incoming dir edit");
+ case svn_wc_conflict_action_add:
+ return _("incoming dir add");
+ case svn_wc_conflict_action_delete:
+ return _("incoming dir delete");
+ case svn_wc_conflict_action_replace:
+ return _("incoming dir replace");
+ }
+ break;
+ case svn_node_symlink:
+ case svn_node_none:
+ case svn_node_unknown:
+ break;
+ }
+ return NULL;
+}
+
+/* Return a localised string representation of the operation part of a
+ conflict. */
+static const char *
+operation_str(svn_wc_operation_t operation)
+{
+ switch (operation)
+ {
+ case svn_wc_operation_update: return _("upon update");
+ case svn_wc_operation_switch: return _("upon switch");
+ case svn_wc_operation_merge: return _("upon merge");
+ case svn_wc_operation_none: return _("upon none");
+ }
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ return NULL;
+}
+
+svn_error_t *
+svn_cl__get_human_readable_prop_conflict_description(
+ const char **desc,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool)
+{
+ const char *reason_str, *action_str;
+
+ /* We provide separately translatable strings for the values that we
+ * know about, and a fall-back in case any other values occur. */
+ switch (conflict->reason)
+ {
+ case svn_wc_conflict_reason_edited:
+ reason_str = _("local edit");
+ break;
+ case svn_wc_conflict_reason_added:
+ reason_str = _("local add");
+ break;
+ case svn_wc_conflict_reason_deleted:
+ reason_str = _("local delete");
+ break;
+ case svn_wc_conflict_reason_obstructed:
+ reason_str = _("local obstruction");
+ break;
+ default:
+ reason_str = apr_psprintf(pool, _("local %s"),
+ svn_token__to_word(map_conflict_reason_xml,
+ conflict->reason));
+ break;
+ }
+ switch (conflict->action)
+ {
+ case svn_wc_conflict_action_edit:
+ action_str = _("incoming edit");
+ break;
+ case svn_wc_conflict_action_add:
+ action_str = _("incoming add");
+ break;
+ case svn_wc_conflict_action_delete:
+ action_str = _("incoming delete");
+ break;
+ default:
+ action_str = apr_psprintf(pool, _("incoming %s"),
+ svn_token__to_word(map_conflict_action_xml,
+ conflict->action));
+ break;
+ }
+ SVN_ERR_ASSERT(reason_str && action_str);
+ *desc = apr_psprintf(pool, _("%s, %s %s"),
+ reason_str, action_str,
+ operation_str(conflict->operation));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__get_human_readable_tree_conflict_description(
+ const char **desc,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool)
+{
+ const char *action, *reason, *operation;
+ svn_node_kind_t incoming_kind;
+
+ /* Determine the node kind of the incoming change. */
+ incoming_kind = svn_node_unknown;
+ if (conflict->action == svn_wc_conflict_action_edit ||
+ conflict->action == svn_wc_conflict_action_delete)
+ {
+ /* Change is acting on 'src_left' version of the node. */
+ if (conflict->src_left_version)
+ incoming_kind = conflict->src_left_version->node_kind;
+ }
+ else if (conflict->action == svn_wc_conflict_action_add ||
+ conflict->action == svn_wc_conflict_action_replace)
+ {
+ /* Change is acting on 'src_right' version of the node.
+ *
+ * ### For 'replace', the node kind is ambiguous. However, src_left
+ * ### is NULL for replace, so we must use src_right. */
+ if (conflict->src_right_version)
+ incoming_kind = conflict->src_right_version->node_kind;
+ }
+
+ reason = local_reason_str(conflict->node_kind, conflict->reason);
+ action = incoming_action_str(incoming_kind, conflict->action);
+ operation = operation_str(conflict->operation);
+ SVN_ERR_ASSERT(operation);
+
+ if (action && reason)
+ {
+ *desc = apr_psprintf(pool, _("%s, %s %s"),
+ reason, action, operation);
+ }
+ else
+ {
+ /* A catch-all message for very rare or nominally impossible cases.
+ It will not be pretty, but is closer to an internal error than
+ an ordinary user-facing string. */
+ *desc = apr_psprintf(pool, _("local: %s %s incoming: %s %s %s"),
+ svn_node_kind_to_word(conflict->node_kind),
+ svn_token__to_word(map_conflict_reason_xml,
+ conflict->reason),
+ svn_node_kind_to_word(incoming_kind),
+ svn_token__to_word(map_conflict_action_xml,
+ conflict->action),
+ operation);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for svn_cl__append_tree_conflict_info_xml().
+ * Appends the attributes of the given VERSION to ATT_HASH.
+ * SIDE is the content of the version tag's side="..." attribute,
+ * currently one of "source-left" or "source-right".*/
+static svn_error_t *
+add_conflict_version_xml(svn_stringbuf_t **pstr,
+ const char *side,
+ const svn_wc_conflict_version_t *version,
+ apr_pool_t *pool)
+{
+ apr_hash_t *att_hash = apr_hash_make(pool);
+
+
+ svn_hash_sets(att_hash, "side", side);
+
+ if (version->repos_url)
+ svn_hash_sets(att_hash, "repos-url", version->repos_url);
+
+ if (version->path_in_repos)
+ svn_hash_sets(att_hash, "path-in-repos", version->path_in_repos);
+
+ if (SVN_IS_VALID_REVNUM(version->peg_rev))
+ svn_hash_sets(att_hash, "revision", apr_ltoa(pool, version->peg_rev));
+
+ if (version->node_kind != svn_node_unknown)
+ svn_hash_sets(att_hash, "kind",
+ svn_cl__node_kind_str_xml(version->node_kind));
+
+ svn_xml_make_open_tag_hash(pstr, pool, svn_xml_self_closing,
+ "version", att_hash);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+append_tree_conflict_info_xml(svn_stringbuf_t *str,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool)
+{
+ apr_hash_t *att_hash = apr_hash_make(pool);
+ const char *tmp;
+
+ svn_hash_sets(att_hash, "victim",
+ svn_dirent_basename(conflict->local_abspath, pool));
+
+ svn_hash_sets(att_hash, "kind",
+ svn_cl__node_kind_str_xml(conflict->node_kind));
+
+ svn_hash_sets(att_hash, "operation",
+ svn_cl__operation_str_xml(conflict->operation, pool));
+
+ tmp = svn_token__to_word(map_conflict_action_xml, conflict->action);
+ svn_hash_sets(att_hash, "action", tmp);
+
+ tmp = svn_token__to_word(map_conflict_reason_xml, conflict->reason);
+ svn_hash_sets(att_hash, "reason", tmp);
+
+ /* Open the tree-conflict tag. */
+ svn_xml_make_open_tag_hash(&str, pool, svn_xml_normal,
+ "tree-conflict", att_hash);
+
+ /* Add child tags for OLDER_VERSION and THEIR_VERSION. */
+
+ if (conflict->src_left_version)
+ SVN_ERR(add_conflict_version_xml(&str,
+ "source-left",
+ conflict->src_left_version,
+ pool));
+
+ if (conflict->src_right_version)
+ SVN_ERR(add_conflict_version_xml(&str,
+ "source-right",
+ conflict->src_right_version,
+ pool));
+
+ svn_xml_make_close_tag(&str, pool, "tree-conflict");
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__append_conflict_info_xml(svn_stringbuf_t *str,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *att_hash;
+ const char *kind;
+ if (conflict->kind == svn_wc_conflict_kind_tree)
+ {
+ /* Uses other element type */
+ return svn_error_trace(
+ append_tree_conflict_info_xml(str, conflict, scratch_pool));
+ }
+
+ att_hash = apr_hash_make(scratch_pool);
+
+ svn_hash_sets(att_hash, "operation",
+ svn_cl__operation_str_xml(conflict->operation, scratch_pool));
+
+
+ kind = svn_token__to_word(map_conflict_kind_xml, conflict->kind);
+ svn_hash_sets(att_hash, "type", kind);
+
+ svn_hash_sets(att_hash, "operation",
+ svn_cl__operation_str_xml(conflict->operation, scratch_pool));
+
+
+ /* "<conflict>" */
+ svn_xml_make_open_tag_hash(&str, scratch_pool,
+ svn_xml_normal, "conflict", att_hash);
+
+ if (conflict->src_left_version)
+ SVN_ERR(add_conflict_version_xml(&str,
+ "source-left",
+ conflict->src_left_version,
+ scratch_pool));
+
+ if (conflict->src_right_version)
+ SVN_ERR(add_conflict_version_xml(&str,
+ "source-right",
+ conflict->src_right_version,
+ scratch_pool));
+
+ switch (conflict->kind)
+ {
+ case svn_wc_conflict_kind_text:
+ /* "<prev-base-file> xx </prev-base-file>" */
+ svn_cl__xml_tagged_cdata(&str, scratch_pool, "prev-base-file",
+ conflict->base_abspath);
+
+ /* "<prev-wc-file> xx </prev-wc-file>" */
+ svn_cl__xml_tagged_cdata(&str, scratch_pool, "prev-wc-file",
+ conflict->my_abspath);
+
+ /* "<cur-base-file> xx </cur-base-file>" */
+ svn_cl__xml_tagged_cdata(&str, scratch_pool, "cur-base-file",
+ conflict->their_abspath);
+
+ break;
+
+ case svn_wc_conflict_kind_property:
+ /* "<prop-file> xx </prop-file>" */
+ svn_cl__xml_tagged_cdata(&str, scratch_pool, "prop-file",
+ conflict->their_abspath);
+ break;
+
+ default:
+ case svn_wc_conflict_kind_tree:
+ SVN_ERR_MALFUNCTION(); /* Handled separately */
+ break;
+ }
+
+ /* "</conflict>" */
+ svn_xml_make_close_tag(&str, scratch_pool, "conflict");
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/cl-conflicts.h b/contrib/subversion/subversion/svn/cl-conflicts.h
new file mode 100644
index 0000000..07591a0
--- /dev/null
+++ b/contrib/subversion/subversion/svn/cl-conflicts.h
@@ -0,0 +1,80 @@
+/*
+ * conflicts.h: Conflicts handling
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#ifndef SVN_CONFLICTS_H
+#define SVN_CONFLICTS_H
+
+/*** Includes. ***/
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_wc.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/**
+ * Return in @a desc a possibly localized human readable
+ * description of a property conflict described by @a conflict.
+ *
+ * Allocate the result in @a pool.
+ */
+svn_error_t *
+svn_cl__get_human_readable_prop_conflict_description(
+ const char **desc,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool);
+
+/**
+ * Return in @a desc a possibly localized human readable
+ * description of a tree conflict described by @a conflict.
+ *
+ * Allocate the result in @a pool.
+ */
+svn_error_t *
+svn_cl__get_human_readable_tree_conflict_description(
+ const char **desc,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool);
+
+/**
+ * Append to @a str an XML representation of the conflict data
+ * for @a conflict, in a format suitable for 'svn info --xml'.
+ */
+svn_error_t *
+svn_cl__append_conflict_info_xml(
+ svn_stringbuf_t *str,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CONFLICTS_H */
diff --git a/contrib/subversion/subversion/svn/cl.h b/contrib/subversion/subversion/svn/cl.h
new file mode 100644
index 0000000..f7ebee6
--- /dev/null
+++ b/contrib/subversion/subversion/svn/cl.h
@@ -0,0 +1,852 @@
+/*
+ * cl.h: shared stuff in the command line program
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#ifndef SVN_CL_H
+#define SVN_CL_H
+
+/*** Includes. ***/
+#include <apr_tables.h>
+#include <apr_getopt.h>
+
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_opt.h"
+#include "svn_auth.h"
+#include "svn_cmdline.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Option processing ***/
+
+/* --accept actions */
+typedef enum svn_cl__accept_t
+{
+ /* invalid accept action */
+ svn_cl__accept_invalid = -2,
+
+ /* unspecified accept action */
+ svn_cl__accept_unspecified = -1,
+
+ /* Leave conflicts alone, for later resolution. */
+ svn_cl__accept_postpone,
+
+ /* Resolve the conflict with the pre-conflict base file. */
+ svn_cl__accept_base,
+
+ /* Resolve the conflict with the current working file. */
+ svn_cl__accept_working,
+
+ /* Resolve the conflicted hunks by choosing the corresponding text
+ from the pre-conflict working copy file. */
+ svn_cl__accept_mine_conflict,
+
+ /* Resolve the conflicted hunks by choosing the corresponding text
+ from the post-conflict base copy file. */
+ svn_cl__accept_theirs_conflict,
+
+ /* Resolve the conflict by taking the entire pre-conflict working
+ copy file. */
+ svn_cl__accept_mine_full,
+
+ /* Resolve the conflict by taking the entire post-conflict base file. */
+ svn_cl__accept_theirs_full,
+
+ /* Launch user's editor and resolve conflict with edited file. */
+ svn_cl__accept_edit,
+
+ /* Launch user's resolver and resolve conflict with edited file. */
+ svn_cl__accept_launch
+
+} svn_cl__accept_t;
+
+/* --accept action user input words */
+#define SVN_CL__ACCEPT_POSTPONE "postpone"
+#define SVN_CL__ACCEPT_BASE "base"
+#define SVN_CL__ACCEPT_WORKING "working"
+#define SVN_CL__ACCEPT_MINE_CONFLICT "mine-conflict"
+#define SVN_CL__ACCEPT_THEIRS_CONFLICT "theirs-conflict"
+#define SVN_CL__ACCEPT_MINE_FULL "mine-full"
+#define SVN_CL__ACCEPT_THEIRS_FULL "theirs-full"
+#define SVN_CL__ACCEPT_EDIT "edit"
+#define SVN_CL__ACCEPT_LAUNCH "launch"
+
+/* Return the svn_cl__accept_t value corresponding to WORD, using exact
+ * case-sensitive string comparison. Return svn_cl__accept_invalid if WORD
+ * is empty or is not one of the known values. */
+svn_cl__accept_t
+svn_cl__accept_from_word(const char *word);
+
+
+/*** Mergeinfo flavors. ***/
+
+/* --show-revs values */
+typedef enum svn_cl__show_revs_t {
+ svn_cl__show_revs_invalid = -1,
+ svn_cl__show_revs_merged,
+ svn_cl__show_revs_eligible
+} svn_cl__show_revs_t;
+
+/* --show-revs user input words */
+#define SVN_CL__SHOW_REVS_MERGED "merged"
+#define SVN_CL__SHOW_REVS_ELIGIBLE "eligible"
+
+/* Return svn_cl__show_revs_t value corresponding to word. */
+svn_cl__show_revs_t
+svn_cl__show_revs_from_word(const char *word);
+
+
+/*** Command dispatch. ***/
+
+/* Hold results of option processing that are shared by multiple
+ commands. */
+typedef struct svn_cl__opt_state_t
+{
+ /* An array of svn_opt_revision_range_t *'s representing revisions
+ ranges indicated on the command-line via the -r and -c options.
+ For each range in the list, if only one revision was provided
+ (-rN), its 'end' member remains 'svn_opt_revision_unspecified'.
+ This array always has at least one element, even if that is a
+ null range in which both ends are 'svn_opt_revision_unspecified'. */
+ apr_array_header_t *revision_ranges;
+
+ /* These are simply a copy of the range start and end values present
+ in the first item of the revision_ranges list. */
+ svn_opt_revision_t start_revision;
+ svn_opt_revision_t end_revision;
+
+ /* Flag which is only set if the '-c' option was used. */
+ svn_boolean_t used_change_arg;
+
+ /* Flag which is only set if the '-r' option was used. */
+ svn_boolean_t used_revision_arg;
+
+ /* Max number of log messages to get back from svn_client_log2. */
+ int limit;
+
+ /* After option processing is done, reflects the switch actually
+ given on the command line, or svn_depth_unknown if none. */
+ svn_depth_t depth;
+
+ /* Was --no-unlock specified? */
+ svn_boolean_t no_unlock;
+
+ const char *message; /* log message */
+ svn_boolean_t force; /* be more forceful, as in "svn rm -f ..." */
+ svn_boolean_t force_log; /* force validity of a suspect log msg file */
+ svn_boolean_t incremental; /* yield output suitable for concatenation */
+ svn_boolean_t quiet; /* sssh...avoid unnecessary output */
+ svn_boolean_t non_interactive; /* do no interactive prompting */
+ svn_boolean_t version; /* print version information */
+ svn_boolean_t verbose; /* be verbose */
+ svn_boolean_t update; /* contact the server for the full story */
+ svn_boolean_t strict; /* do strictly what was requested */
+ svn_stringbuf_t *filedata; /* contents of file used as option data */
+ const char *encoding; /* the locale/encoding of the data*/
+ svn_boolean_t help; /* print usage message */
+ const char *auth_username; /* auth username */ /* UTF-8! */
+ const char *auth_password; /* auth password */ /* UTF-8! */
+ const char *extensions; /* subprocess extension args */ /* UTF-8! */
+ apr_array_header_t *targets; /* target list from file */ /* UTF-8! */
+ svn_boolean_t xml; /* output in xml, e.g., "svn log --xml" */
+ svn_boolean_t no_ignore; /* disregard default ignores & svn:ignore's */
+ svn_boolean_t no_auth_cache; /* do not cache authentication information */
+ struct
+ {
+ const char *diff_cmd; /* the external diff command to use */
+ svn_boolean_t internal_diff; /* override diff_cmd in config file */
+ svn_boolean_t no_diff_added; /* do not show diffs for deleted files */
+ svn_boolean_t no_diff_deleted; /* do not show diffs for deleted files */
+ svn_boolean_t show_copies_as_adds; /* do not diff copies with their source */
+ svn_boolean_t notice_ancestry; /* notice ancestry for diff-y operations */
+ svn_boolean_t summarize; /* create a summary of a diff */
+ svn_boolean_t use_git_diff_format; /* Use git's extended diff format */
+ svn_boolean_t ignore_properties; /* ignore properties */
+ svn_boolean_t properties_only; /* Show properties only */
+ svn_boolean_t patch_compatible; /* Output compatible with GNU patch */
+ } diff;
+ svn_boolean_t ignore_ancestry; /* ignore ancestry for merge-y operations */
+ svn_boolean_t ignore_externals;/* ignore externals definitions */
+ svn_boolean_t stop_on_copy; /* don't cross copies during processing */
+ svn_boolean_t dry_run; /* try operation but make no changes */
+ svn_boolean_t revprop; /* operate on a revision property */
+ const char *merge_cmd; /* the external merge command to use */
+ const char *editor_cmd; /* the external editor command to use */
+ svn_boolean_t record_only; /* whether to record mergeinfo */
+ const char *old_target; /* diff target */
+ const char *new_target; /* diff target */
+ svn_boolean_t relocate; /* rewrite urls (svn switch) */
+ const char *config_dir; /* over-riding configuration directory */
+ apr_array_header_t *config_options; /* over-riding configuration options */
+ svn_boolean_t autoprops; /* enable automatic properties */
+ svn_boolean_t no_autoprops; /* disable automatic properties */
+ const char *native_eol; /* override system standard eol marker */
+ svn_boolean_t remove; /* deassociate a changelist */
+ apr_array_header_t *changelists; /* changelist filters */
+ const char *changelist; /* operate on this changelist
+ THIS IS TEMPORARY (LAST OF CHANGELISTS) */
+ svn_boolean_t keep_changelists;/* don't remove changelists after commit */
+ svn_boolean_t keep_local; /* delete path only from repository */
+ svn_boolean_t all_revprops; /* retrieve all revprops */
+ svn_boolean_t no_revprops; /* retrieve no revprops */
+ apr_hash_t *revprop_table; /* table of revision properties to get/set */
+ svn_boolean_t parents; /* create intermediate directories */
+ svn_boolean_t use_merge_history; /* use/display extra merge information */
+ svn_cl__accept_t accept_which; /* how to handle conflicts */
+ svn_cl__show_revs_t show_revs; /* mergeinfo flavor */
+ svn_depth_t set_depth; /* new sticky ambient depth value */
+ svn_boolean_t reintegrate; /* use "reintegrate" merge-source heuristic */
+ svn_boolean_t trust_server_cert; /* trust server SSL certs that would
+ otherwise be rejected as "untrusted" */
+ int strip; /* number of leading path components to strip */
+ svn_boolean_t ignore_keywords; /* do not expand keywords */
+ svn_boolean_t reverse_diff; /* reverse a diff (e.g. when patching) */
+ svn_boolean_t ignore_whitespace; /* don't account for whitespace when
+ patching */
+ svn_boolean_t show_diff; /* produce diff output (maps to --diff) */
+ svn_boolean_t allow_mixed_rev; /* Allow operation on mixed-revision WC */
+ svn_boolean_t include_externals; /* Recurses (in)to file & dir externals */
+ svn_boolean_t show_inherited_props; /* get inherited properties */
+ apr_array_header_t* search_patterns; /* pattern arguments for --search */
+} svn_cl__opt_state_t;
+
+
+typedef struct svn_cl__cmd_baton_t
+{
+ svn_cl__opt_state_t *opt_state;
+ svn_client_ctx_t *ctx;
+} svn_cl__cmd_baton_t;
+
+
+/* Declare all the command procedures */
+svn_opt_subcommand_t
+ svn_cl__add,
+ svn_cl__blame,
+ svn_cl__cat,
+ svn_cl__changelist,
+ svn_cl__checkout,
+ svn_cl__cleanup,
+ svn_cl__commit,
+ svn_cl__copy,
+ svn_cl__delete,
+ svn_cl__diff,
+ svn_cl__export,
+ svn_cl__help,
+ svn_cl__import,
+ svn_cl__info,
+ svn_cl__lock,
+ svn_cl__log,
+ svn_cl__list,
+ svn_cl__merge,
+ svn_cl__mergeinfo,
+ svn_cl__mkdir,
+ svn_cl__move,
+ svn_cl__patch,
+ svn_cl__propdel,
+ svn_cl__propedit,
+ svn_cl__propget,
+ svn_cl__proplist,
+ svn_cl__propset,
+ svn_cl__relocate,
+ svn_cl__revert,
+ svn_cl__resolve,
+ svn_cl__resolved,
+ svn_cl__status,
+ svn_cl__switch,
+ svn_cl__unlock,
+ svn_cl__update,
+ svn_cl__upgrade;
+
+
+/* See definition in svn.c for documentation. */
+extern const svn_opt_subcommand_desc2_t svn_cl__cmd_table[];
+
+/* See definition in svn.c for documentation. */
+extern const int svn_cl__global_options[];
+
+/* See definition in svn.c for documentation. */
+extern const apr_getopt_option_t svn_cl__options[];
+
+
+/* A helper for the many subcommands that wish to merely warn when
+ * invoked on an unversioned, nonexistent, or otherwise innocuously
+ * errorful resource. Meant to be wrapped with SVN_ERR().
+ *
+ * If ERR is null, return SVN_NO_ERROR.
+ *
+ * Else if ERR->apr_err is one of the error codes supplied in varargs,
+ * then handle ERR as a warning (unless QUIET is true), clear ERR, and
+ * return SVN_NO_ERROR, and push the value of ERR->apr_err into the
+ * ERRORS_SEEN array, if ERRORS_SEEN is not NULL.
+ *
+ * Else return ERR.
+ *
+ * Typically, error codes like SVN_ERR_UNVERSIONED_RESOURCE,
+ * SVN_ERR_ENTRY_NOT_FOUND, etc, are supplied in varargs. Don't
+ * forget to terminate the argument list with SVN_NO_ERROR.
+ */
+svn_error_t *
+svn_cl__try(svn_error_t *err,
+ apr_array_header_t *errors_seen,
+ svn_boolean_t quiet,
+ ...);
+
+
+/* Our cancellation callback. */
+svn_error_t *
+svn_cl__check_cancel(void *baton);
+
+
+
+/* Various conflict-resolution callbacks. */
+
+/* Opaque baton type for svn_cl__conflict_func_interactive(). */
+typedef struct svn_cl__interactive_conflict_baton_t
+ svn_cl__interactive_conflict_baton_t;
+
+/* Conflict stats for operations such as update and merge. */
+typedef struct svn_cl__conflict_stats_t svn_cl__conflict_stats_t;
+
+/* Return a new, initialized, conflict stats structure, allocated in
+ * POOL. */
+svn_cl__conflict_stats_t *
+svn_cl__conflict_stats_create(apr_pool_t *pool);
+
+/* Update CONFLICT_STATS to reflect that a conflict on PATH_LOCAL of kind
+ * CONFLICT_KIND is resolved. (There is no support for updating the
+ * 'skipped paths' stats, since skips cannot be 'resolved'.) */
+void
+svn_cl__conflict_stats_resolved(svn_cl__conflict_stats_t *conflict_stats,
+ const char *path_local,
+ svn_wc_conflict_kind_t conflict_kind);
+
+
+/* Create and return an baton for use with svn_cl__conflict_func_interactive
+ * in *B, allocated from RESULT_POOL, and initialised with the values
+ * ACCEPT_WHICH, CONFIG, EDITOR_CMD, CANCEL_FUNC and CANCEL_BATON. */
+svn_error_t *
+svn_cl__get_conflict_func_interactive_baton(
+ svn_cl__interactive_conflict_baton_t **b,
+ svn_cl__accept_t accept_which,
+ apr_hash_t *config,
+ const char *editor_cmd,
+ svn_cl__conflict_stats_t *conflict_stats,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool);
+
+/* A callback capable of doing interactive conflict resolution.
+
+ The BATON must come from svn_cl__get_conflict_func_interactive_baton().
+ Resolves based on the --accept option if one was given to that function,
+ otherwise prompts the user to choose one of the three fulltexts, edit
+ the merged file on the spot, or just skip the conflict (to be resolved
+ later), among other options.
+
+ Implements svn_wc_conflict_resolver_func2_t.
+ */
+svn_error_t *
+svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *desc,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/*** Command-line output functions -- printing to the user. ***/
+
+/* Print out commit information found in COMMIT_INFO to the console.
+ * POOL is used for temporay allocations.
+ * COMMIT_INFO should not be NULL.
+ *
+ * This function implements svn_commit_callback2_t.
+ */
+svn_error_t *
+svn_cl__print_commit_info(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/* Convert the date in DATA to a human-readable UTF-8-encoded string
+ * *HUMAN_CSTRING, or set the latter to "(invalid date)" if DATA is not
+ * a valid date. DATA should be as expected by svn_time_from_cstring().
+ *
+ * Do all allocations in POOL.
+ */
+svn_error_t *
+svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
+ const char *data,
+ apr_pool_t *pool);
+
+
+/* Print STATUS for PATH to stdout for human consumption. Prints in
+ abbreviated format by default, or DETAILED format if flag is set.
+
+ When SUPPRESS_EXTERNALS_PLACEHOLDERS is set, avoid printing
+ externals placeholder lines ("X lines").
+
+ When DETAILED is set, use SHOW_LAST_COMMITTED to toggle display of
+ the last-committed-revision and last-committed-author.
+
+ If SKIP_UNRECOGNIZED is TRUE, this function will not print out
+ unversioned items found in the working copy.
+
+ When DETAILED is set, and REPOS_LOCKS is set, treat missing repository locks
+ as broken WC locks.
+
+ Increment *TEXT_CONFLICTS, *PROP_CONFLICTS, or *TREE_CONFLICTS if
+ a conflict was encountered.
+
+ Use CWD_ABSPATH -- the absolute path of the current working
+ directory -- to shorten PATH into something relative to that
+ directory as necessary.
+*/
+svn_error_t *
+svn_cl__print_status(const char *cwd_abspath,
+ const char *path,
+ const svn_client_status_t *status,
+ svn_boolean_t suppress_externals_placeholders,
+ svn_boolean_t detailed,
+ svn_boolean_t show_last_committed,
+ svn_boolean_t skip_unrecognized,
+ svn_boolean_t repos_locks,
+ unsigned int *text_conflicts,
+ unsigned int *prop_conflicts,
+ unsigned int *tree_conflicts,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/* Print STATUS for PATH in XML to stdout. Use POOL for temporary
+ allocations.
+
+ Use CWD_ABSPATH -- the absolute path of the current working
+ directory -- to shorten PATH into something relative to that
+ directory as necessary.
+ */
+svn_error_t *
+svn_cl__print_status_xml(const char *cwd_abspath,
+ const char *path,
+ const svn_client_status_t *status,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Output a commit xml element to *OUTSTR. If *OUTSTR is NULL, allocate it
+ first from POOL, otherwise append to it. If AUTHOR or DATE is
+ NULL, it will be omitted. */
+void
+svn_cl__print_xml_commit(svn_stringbuf_t **outstr,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ apr_pool_t *pool);
+
+/* Output an XML "<lock>" element describing LOCK to *OUTSTR. If *OUTSTR is
+ NULL, allocate it first from POOL, otherwise append to it. */
+void
+svn_cl__print_xml_lock(svn_stringbuf_t **outstr,
+ const svn_lock_t *lock,
+ apr_pool_t *pool);
+
+/* Do the following things that are commonly required before accessing revision
+ properties. Ensure that REVISION is specified explicitly and is not
+ relative to a working-copy item. Ensure that exactly one target is
+ specified in TARGETS. Set *URL to the URL of the target. Return an
+ appropriate error if any of those checks or operations fail. Use CTX for
+ accessing the working copy
+ */
+svn_error_t *
+svn_cl__revprop_prepare(const svn_opt_revision_t *revision,
+ const apr_array_header_t *targets,
+ const char **URL,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Search for a merge tool command in environment variables,
+ and use it to perform the merge of the four given files.
+ WC_PATH is the path of the file that is in conflict, relative
+ to the merge target.
+ Use POOL for all allocations.
+
+ CONFIG is a hash of svn_config_t * items keyed on a configuration
+ category (SVN_CONFIG_CATEGORY_CONFIG et al), and may be NULL.
+
+ Upon success, set *REMAINS_IN_CONFLICT to indicate whether the
+ merge result contains conflict markers.
+ */
+svn_error_t *
+svn_cl__merge_file_externally(const char *base_path,
+ const char *their_path,
+ const char *my_path,
+ const char *merged_path,
+ const char *wc_path,
+ apr_hash_t *config,
+ svn_boolean_t *remains_in_conflict,
+ apr_pool_t *pool);
+
+/* Like svn_cl__merge_file_externally, but using a built-in merge tool
+ * with help from an external editor specified by EDITOR_CMD. */
+svn_error_t *
+svn_cl__merge_file(const char *base_path,
+ const char *their_path,
+ const char *my_path,
+ const char *merged_path,
+ const char *wc_path,
+ const char *path_prefix,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ svn_boolean_t *remains_in_conflict,
+ apr_pool_t *scratch_pool);
+
+
+/*** Notification functions to display results on the terminal. */
+
+/* Set *NOTIFY_FUNC_P and *NOTIFY_BATON_P to a notifier/baton for all
+ * operations, allocated in POOL.
+ */
+svn_error_t *
+svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
+ void **notify_baton_p,
+ svn_cl__conflict_stats_t *conflict_stats,
+ apr_pool_t *pool);
+
+/* Make the notifier for use with BATON print the appropriate summary
+ * line at the end of the output.
+ */
+svn_error_t *
+svn_cl__notifier_mark_checkout(void *baton);
+
+/* Make the notifier for use with BATON print the appropriate summary
+ * line at the end of the output.
+ */
+svn_error_t *
+svn_cl__notifier_mark_export(void *baton);
+
+/* Make the notifier for use with BATON print the appropriate notifications
+ * for a wc to repository copy
+ */
+svn_error_t *
+svn_cl__notifier_mark_wc_to_repos_copy(void *baton);
+
+/* Baton for use with svn_cl__check_externals_failed_notify_wrapper(). */
+struct svn_cl__check_externals_failed_notify_baton
+{
+ svn_wc_notify_func2_t wrapped_func; /* The "real" notify_func2. */
+ void *wrapped_baton; /* The "real" notify_func2 baton. */
+ svn_boolean_t had_externals_error; /* Did something fail in an external? */
+};
+
+/* Notification function wrapper (implements `svn_wc_notify_func2_t').
+ Use with an svn_cl__check_externals_failed_notify_baton BATON. */
+void
+svn_cl__check_externals_failed_notify_wrapper(void *baton,
+ const svn_wc_notify_t *n,
+ apr_pool_t *pool);
+
+/* Print the conflict stats accumulated in BATON, which is the
+ * notifier baton from svn_cl__get_notifier().
+ * Return any error encountered during printing.
+ */
+svn_error_t *
+svn_cl__notifier_print_conflict_stats(void *baton, apr_pool_t *scratch_pool);
+
+
+ /*** Log message callback stuffs. ***/
+
+/* Allocate in POOL a baton for use with svn_cl__get_log_message().
+
+ OPT_STATE is the set of command-line options given.
+
+ BASE_DIR is a directory in which to create temporary files if an
+ external editor is used to edit the log message. If BASE_DIR is
+ NULL, the current working directory (`.') will be used, and
+ therefore the user must have the proper permissions on that
+ directory. ### todo: What *should* happen in the NULL case is that
+ we ask APR to tell us where a suitable tmp directory is (like, /tmp
+ on Unix and C:\Windows\Temp on Win32 or something), and use it.
+ But APR doesn't yet have that capability.
+
+ CONFIG is a client configuration hash of svn_config_t * items keyed
+ on config categories, and may be NULL.
+
+ NOTE: While the baton itself will be allocated from POOL, the items
+ add to it are added by reference, not duped into POOL!*/
+svn_error_t *
+svn_cl__make_log_msg_baton(void **baton,
+ svn_cl__opt_state_t *opt_state,
+ const char *base_dir,
+ apr_hash_t *config,
+ apr_pool_t *pool);
+
+/* A function of type svn_client_get_commit_log3_t. */
+svn_error_t *
+svn_cl__get_log_message(const char **log_msg,
+ const char **tmp_file,
+ const apr_array_header_t *commit_items,
+ void *baton,
+ apr_pool_t *pool);
+
+/* Handle the cleanup of a log message, using the data in the
+ LOG_MSG_BATON, in the face of COMMIT_ERR. This may mean removing a
+ temporary file left by an external editor, or it may be a complete
+ no-op. COMMIT_ERR may be NULL to indicate to indicate that the
+ function should act as though no commit error occurred. Use POOL
+ for temporary allocations.
+
+ All error returns from this function are guaranteed to at least
+ include COMMIT_ERR, and perhaps additional errors attached to the
+ end of COMMIT_ERR's chain. */
+svn_error_t *
+svn_cl__cleanup_log_msg(void *log_msg_baton,
+ svn_error_t *commit_err,
+ apr_pool_t *pool);
+
+/* Add a message about --force if appropriate */
+svn_error_t *
+svn_cl__may_need_force(svn_error_t *err);
+
+/* Write the STRING to the stdio STREAM, returning an error if it fails.
+
+ This function is equal to svn_cmdline_fputs() minus the utf8->local
+ encoding translation. */
+svn_error_t *
+svn_cl__error_checked_fputs(const char *string, FILE* stream);
+
+/* If STRING is non-null, append it, wrapped in a simple XML CDATA element
+ named TAGNAME, to the string SB. Use POOL for temporary allocations. */
+void
+svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
+ apr_pool_t *pool,
+ const char *tagname,
+ const char *string);
+
+/* Print the XML prolog and document root element start-tag to stdout, using
+ TAGNAME as the root element name. Use POOL for temporary allocations. */
+svn_error_t *
+svn_cl__xml_print_header(const char *tagname, apr_pool_t *pool);
+
+/* Print the XML document root element end-tag to stdout, using TAGNAME as the
+ root element name. Use POOL for temporary allocations. */
+svn_error_t *
+svn_cl__xml_print_footer(const char *tagname, apr_pool_t *pool);
+
+
+/* For use in XML output, return a non-localised string representation
+ * of KIND, being "none" or "dir" or "file" or, in any other case,
+ * the empty string. */
+const char *
+svn_cl__node_kind_str_xml(svn_node_kind_t kind);
+
+/* Return a (possibly localised) string representation of KIND, being "none" or
+ "dir" or "file" or, in any other case, the empty string. */
+const char *
+svn_cl__node_kind_str_human_readable(svn_node_kind_t kind);
+
+
+/** Provides an XML name for a given OPERATION.
+ * Note: POOL is currently not used.
+ */
+const char *
+svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool);
+
+/** Return a possibly localized human readable string for
+ * a given OPERATION.
+ * Note: POOL is currently not used.
+ */
+const char *
+svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
+ apr_pool_t *pool);
+
+
+/* What use is a property name intended for.
+ Used by svn_cl__check_svn_prop_name to customize error messages. */
+typedef enum svn_cl__prop_use_e
+ {
+ svn_cl__prop_use_set, /* setting the property */
+ svn_cl__prop_use_edit, /* editing the property */
+ svn_cl__prop_use_use /* using the property name */
+ }
+svn_cl__prop_use_t;
+
+/* If PROPNAME looks like but is not identical to one of the svn:
+ * poperties, raise an error and suggest a better spelling. Names that
+ * raise errors look like this:
+ *
+ * - start with svn: but do not exactly match a known property; or,
+ * - start with a 3-letter prefix that differs in only one letter
+ * from "svn:", and the rest exactly matches a known propery.
+ *
+ * If REVPROP is TRUE, only check revision property names; otherwise
+ * only check node property names.
+ *
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_cl__check_svn_prop_name(const char *propname,
+ svn_boolean_t revprop,
+ svn_cl__prop_use_t prop_use,
+ apr_pool_t *scratch_pool);
+
+/* If PROPNAME is one of the svn: properties with a boolean value, and
+ * PROPVAL looks like an attempt to turn the property off (i.e., it's
+ * "off", "no", "false", or ""), then print a warning to the user that
+ * setting the property to this value might not do what they expect.
+ * Perform temporary allocations in POOL.
+ */
+void
+svn_cl__check_boolean_prop_val(const char *propname,
+ const char *propval,
+ apr_pool_t *pool);
+
+/* De-streamifying wrapper around svn_client_get_changelists(), which
+ is called for each target in TARGETS to populate *PATHS (a list of
+ paths assigned to one of the CHANGELISTS.
+ If all targets are to be included, may set *PATHS to TARGETS without
+ reallocating. */
+svn_error_t *
+svn_cl__changelist_paths(apr_array_header_t **paths,
+ const apr_array_header_t *changelists,
+ const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_client_args_to_target_array() but, if the only error is that some
+ * arguments are reserved file names, then print warning messages for those
+ * targets, store the rest of the targets in TARGETS_P and return success. */
+svn_error_t *
+svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t keep_dest_origpath_on_truepath_collision,
+ apr_pool_t *pool);
+
+/* Return a string showing NODE's kind, URL and revision, to the extent that
+ * that information is available in NODE. If NODE itself is NULL, this prints
+ * just a 'none' node kind.
+ * WC_REPOS_ROOT_URL should reflect the target working copy's repository
+ * root URL. If NODE is from that same URL, the printed URL is abbreviated
+ * to caret notation (^/). WC_REPOS_ROOT_URL may be NULL, in which case
+ * this function tries to print the conflicted node's complete URL. */
+const char *
+svn_cl__node_description(const svn_wc_conflict_version_t *node,
+ const char *wc_repos_root_URL,
+ apr_pool_t *pool);
+
+/* Return, in @a *true_targets_p, a shallow copy of @a targets with any
+ * empty peg revision specifier snipped off the end of each element. If any
+ * target has a non-empty peg revision specifier, throw an error. The user
+ * may have specified a peg revision where it doesn't make sense to do so,
+ * or may have forgotten to escape an '@' character in a filename.
+ *
+ * This function is useful for subcommands for which peg revisions
+ * do not make any sense. Such subcommands still need to allow an empty
+ * peg revision to be specified on the command line so that users of
+ * the command line client can consistently escape '@' characters
+ * in filenames by appending an '@' character, regardless of the
+ * subcommand being used.
+ *
+ * It is safe to pass the address of @a targets as @a true_targets_p.
+ *
+ * Do all allocations in @a pool. */
+svn_error_t *
+svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
+ const apr_array_header_t *targets,
+ apr_pool_t *pool);
+
+/* Return an error if TARGETS contains a mixture of URLs and paths; otherwise
+ * return SVN_NO_ERROR. */
+svn_error_t *
+svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets);
+
+/* Return an error if TARGETS contains a URL; otherwise return SVN_NO_ERROR. */
+svn_error_t *
+svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets);
+
+/* Return an error if TARGET is a URL; otherwise return SVN_NO_ERROR. */
+svn_error_t *
+svn_cl__check_target_is_local_path(const char *target);
+
+/* Return a copy of PATH, converted to the local path style, skipping
+ * PARENT_PATH if it is non-null and is a parent of or equal to PATH.
+ *
+ * This function assumes PARENT_PATH and PATH are both absolute "dirents"
+ * or both relative "dirents". */
+const char *
+svn_cl__local_style_skip_ancestor(const char *parent_path,
+ const char *path,
+ apr_pool_t *pool);
+
+/* Check that PATH_OR_URL1@REVISION1 is related to PATH_OR_URL2@REVISION2.
+ * Raise an error if not.
+ *
+ * ### Ideally we would also check that they are on different lines of
+ * history. That is easy in common cases, but to give a correct answer in
+ * general we need to know the operative revision(s) as well. For example,
+ * when one location is the branch point from which the other branch was
+ * copied.
+ */
+svn_error_t *
+svn_cl__check_related_source_and_target(const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* If the user is setting a mime-type to mark one of the TARGETS as binary,
+ * as determined by property name PROPNAME and value PROPVAL, then check
+ * whether Subversion's own binary-file detection recognizes the target as
+ * a binary file. If Subversion doesn't consider the target to be a binary
+ * file, assume the user is making an error and print a warning to inform
+ * the user that some operations might fail on the file in the future. */
+svn_error_t *
+svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
+ const char *propname,
+ const svn_string_t *propval,
+ apr_pool_t *scratch_pool);
+
+/* A wrapper around the deprecated svn_client_merge_reintegrate. */
+svn_error_t *
+svn_cl__deprecated_merge_reintegrate(const char *source_path_or_url,
+ const svn_opt_revision_t *src_peg_revision,
+ const char *target_wcpath,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CL_H */
diff --git a/contrib/subversion/subversion/svn/cleanup-cmd.c b/contrib/subversion/subversion/svn/cleanup-cmd.c
new file mode 100644
index 0000000..64fa400
--- /dev/null
+++ b/contrib/subversion/subversion/svn/cleanup-cmd.c
@@ -0,0 +1,104 @@
+/*
+ * cleanup-cmd.c -- Subversion cleanup command
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__cleanup(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ apr_pool_t *subpool;
+ int i;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ subpool = svn_pool_create(pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_error_t *err;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+ err = svn_client_cleanup(target, ctx, subpool);
+ if (err && err->apr_err == SVN_ERR_WC_LOCKED)
+ {
+ const char *target_abspath;
+ svn_error_t *err2 = svn_dirent_get_absolute(&target_abspath,
+ target, subpool);
+ if (err2)
+ {
+ err = svn_error_compose_create(err, err2);
+ }
+ else
+ {
+ const char *wcroot_abspath;
+
+ err2 = svn_client_get_wc_root(&wcroot_abspath, target_abspath,
+ ctx, subpool, subpool);
+ if (err2)
+ err = svn_error_compose_create(err, err2);
+ else
+ err = svn_error_createf(SVN_ERR_WC_LOCKED, err,
+ _("Working copy locked; try running "
+ "'svn cleanup' on the root of the "
+ "working copy ('%s') instead."),
+ svn_dirent_local_style(wcroot_abspath,
+ subpool));
+ }
+ }
+ SVN_ERR(err);
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/client_errors.h b/contrib/subversion/subversion/svn/client_errors.h
new file mode 100644
index 0000000..19f0bdf
--- /dev/null
+++ b/contrib/subversion/subversion/svn/client_errors.h
@@ -0,0 +1,97 @@
+/*
+ * client_errors.h: error codes this command line client features
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#ifndef SVN_CLIENT_ERRORS_H
+#define SVN_CLIENT_ERRORS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * This error defining system is copied from and explained in
+ * ../../include/svn_error_codes.h
+ */
+
+/* Process this file if we're building an error array, or if we have
+ not defined the enumerated constants yet. */
+#if defined(SVN_ERROR_BUILD_ARRAY) || !defined(SVN_CMDLINE_ERROR_ENUM_DEFINED)
+
+#if defined(SVN_ERROR_BUILD_ARRAY)
+
+#error "Need to update err_defn for r1464679 and un-typo 'CDMLINE'"
+
+#define SVN_ERROR_START \
+ static const err_defn error_table[] = { \
+ { SVN_ERR_CDMLINE__WARNING, "Warning" },
+#define SVN_ERRDEF(n, s) { n, s },
+#define SVN_ERROR_END { 0, NULL } };
+
+#elif !defined(SVN_CMDLINE_ERROR_ENUM_DEFINED)
+
+#define SVN_ERROR_START \
+ typedef enum svn_client_errno_t { \
+ SVN_ERR_CDMLINE__WARNING = SVN_ERR_LAST + 1,
+#define SVN_ERRDEF(n, s) n,
+#define SVN_ERROR_END SVN_ERR_CMDLINE__ERR_LAST } svn_client_errno_t;
+
+#define SVN_CMDLINE_ERROR_ENUM_DEFINED
+
+#endif
+
+/* Define custom command line client error numbers */
+
+SVN_ERROR_START
+
+ /* BEGIN Client errors */
+
+SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_WRITE,
+ "Failed writing to temporary file.")
+
+ SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_STAT,
+ "Failed getting info about temporary file.")
+
+ SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_OPEN,
+ "Failed opening temporary file.")
+
+ /* END Client errors */
+
+
+SVN_ERROR_END
+
+#undef SVN_ERROR_START
+#undef SVN_ERRDEF
+#undef SVN_ERROR_END
+
+#endif /* SVN_ERROR_BUILD_ARRAY || !SVN_CMDLINE_ERROR_ENUM_DEFINED */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CLIENT_ERRORS_H */
diff --git a/contrib/subversion/subversion/svn/commit-cmd.c b/contrib/subversion/subversion/svn/commit-cmd.c
new file mode 100644
index 0000000..2d04c69
--- /dev/null
+++ b/contrib/subversion/subversion/svn/commit-cmd.c
@@ -0,0 +1,186 @@
+/*
+ * commit-cmd.c -- Check changes into the repository.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <apr_general.h>
+
+#include "svn_hash.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_path.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_config.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+/* Wrapper notify_func2 function and baton for warning about
+ reduced-depth commits of copied directories. */
+struct copy_warning_notify_baton
+{
+ svn_wc_notify_func2_t wrapped_func;
+ void *wrapped_baton;
+ svn_depth_t depth;
+ svn_boolean_t warned;
+};
+
+static void
+copy_warning_notify_func(void *baton,
+ const svn_wc_notify_t *notify,
+ apr_pool_t *pool)
+{
+ struct copy_warning_notify_baton *b = baton;
+
+ /* Call the wrapped notification system (if any). */
+ if (b->wrapped_func)
+ b->wrapped_func(b->wrapped_baton, notify, pool);
+
+ /* If we're being notified about a copy of a directory when our
+ commit depth is less-than-infinite, and we've not already warned
+ about this situation, then warn about it (and remember that we
+ now have.) */
+ if ((! b->warned)
+ && (b->depth < svn_depth_infinity)
+ && (notify->kind == svn_node_dir)
+ && ((notify->action == svn_wc_notify_commit_copied) ||
+ (notify->action == svn_wc_notify_commit_copied_replaced)))
+ {
+ svn_error_t *err;
+ err = svn_cmdline_printf(pool,
+ _("svn: The depth of this commit is '%s', "
+ "but copies are always performed "
+ "recursively in the repository.\n"),
+ svn_depth_to_word(b->depth));
+ /* ### FIXME: Try to return this error showhow? */
+ svn_error_clear(err);
+
+ /* We'll only warn once. */
+ b->warned = TRUE;
+ }
+}
+
+
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__commit(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ apr_array_header_t *condensed_targets;
+ const char *base_dir;
+ svn_config_t *cfg;
+ svn_boolean_t no_unlock = FALSE;
+ struct copy_warning_notify_baton cwnb;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ SVN_ERR_W(svn_cl__check_targets_are_local_paths(targets),
+ _("Commit targets must be local paths"));
+
+ /* Add "." if user passed 0 arguments. */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ /* Condense the targets (like commit does)... */
+ SVN_ERR(svn_dirent_condense_targets(&base_dir, &condensed_targets, targets,
+ TRUE, pool, pool));
+
+ if ((! condensed_targets) || (! condensed_targets->nelts))
+ {
+ const char *parent_dir, *base_name;
+
+ SVN_ERR(svn_wc_get_actual_target2(&parent_dir, &base_name, ctx->wc_ctx,
+ base_dir, pool, pool));
+ if (*base_name)
+ base_dir = apr_pstrdup(pool, parent_dir);
+ }
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_infinity;
+
+ cfg = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG);
+ if (cfg)
+ SVN_ERR(svn_config_get_bool(cfg, &no_unlock,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_NO_UNLOCK, FALSE));
+
+ /* We're creating a new log message baton because we can use our base_dir
+ to store the temp file, instead of the current working directory. The
+ client might not have write access to their working directory, but they
+ better have write access to the directory they're committing. */
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3),
+ opt_state, base_dir,
+ ctx->config, pool));
+
+ /* Copies are done server-side, and cheaply, which means they're
+ effectively always done with infinite depth. This is a potential
+ cause of confusion for users trying to commit copied subtrees in
+ part by restricting the commit's depth. See issues #3699 and #3752. */
+ if (opt_state->depth < svn_depth_infinity)
+ {
+ cwnb.wrapped_func = ctx->notify_func2;
+ cwnb.wrapped_baton = ctx->notify_baton2;
+ cwnb.depth = opt_state->depth;
+ cwnb.warned = FALSE;
+ ctx->notify_func2 = copy_warning_notify_func;
+ ctx->notify_baton2 = &cwnb;
+ }
+
+ /* Commit. */
+ err = svn_client_commit6(targets,
+ opt_state->depth,
+ no_unlock,
+ opt_state->keep_changelists,
+ TRUE /* commit_as_operations */,
+ opt_state->include_externals, /* file externals */
+ opt_state->include_externals, /* dir externals */
+ opt_state->changelists,
+ opt_state->revprop_table,
+ (opt_state->quiet
+ ? NULL : svn_cl__print_commit_info),
+ NULL,
+ ctx,
+ pool);
+ SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/conflict-callbacks.c b/contrib/subversion/subversion/svn/conflict-callbacks.c
new file mode 100644
index 0000000..096a189
--- /dev/null
+++ b/contrib/subversion/subversion/svn/conflict-callbacks.c
@@ -0,0 +1,1369 @@
+/*
+ * conflict-callbacks.c: conflict resolution callbacks specific to the
+ * commandline client.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_xlate.h> /* for APR_LOCALE_CHARSET */
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+#include "svn_utf.h"
+
+#include "cl.h"
+#include "cl-conflicts.h"
+
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+#define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0])))
+
+
+
+struct svn_cl__interactive_conflict_baton_t {
+ svn_cl__accept_t accept_which;
+ apr_hash_t *config;
+ const char *editor_cmd;
+ svn_boolean_t external_failed;
+ svn_cmdline_prompt_baton_t *pb;
+ const char *path_prefix;
+ svn_boolean_t quit;
+ svn_cl__conflict_stats_t *conflict_stats;
+};
+
+svn_error_t *
+svn_cl__get_conflict_func_interactive_baton(
+ svn_cl__interactive_conflict_baton_t **b,
+ svn_cl__accept_t accept_which,
+ apr_hash_t *config,
+ const char *editor_cmd,
+ svn_cl__conflict_stats_t *conflict_stats,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool)
+{
+ svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb));
+ pb->cancel_func = cancel_func;
+ pb->cancel_baton = cancel_baton;
+
+ *b = apr_palloc(result_pool, sizeof(**b));
+ (*b)->accept_which = accept_which;
+ (*b)->config = config;
+ (*b)->editor_cmd = editor_cmd;
+ (*b)->external_failed = FALSE;
+ (*b)->pb = pb;
+ SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool));
+ (*b)->quit = FALSE;
+ (*b)->conflict_stats = conflict_stats;
+
+ return SVN_NO_ERROR;
+}
+
+svn_cl__accept_t
+svn_cl__accept_from_word(const char *word)
+{
+ /* Shorthand options are consistent with svn_cl__conflict_handler(). */
+ if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0
+ || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0)
+ return svn_cl__accept_postpone;
+ if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0)
+ /* ### shorthand? */
+ return svn_cl__accept_base;
+ if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0)
+ /* ### shorthand? */
+ return svn_cl__accept_working;
+ if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0
+ || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0)
+ return svn_cl__accept_mine_conflict;
+ if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0
+ || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0)
+ return svn_cl__accept_theirs_conflict;
+ if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0
+ || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0)
+ return svn_cl__accept_mine_full;
+ if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0
+ || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0)
+ return svn_cl__accept_theirs_full;
+ if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0
+ || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0)
+ return svn_cl__accept_edit;
+ if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0
+ || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0)
+ return svn_cl__accept_launch;
+ /* word is an invalid action. */
+ return svn_cl__accept_invalid;
+}
+
+
+/* Print on stdout a diff that shows incoming conflicting changes
+ * corresponding to the conflict described in DESC. */
+static svn_error_t *
+show_diff(const svn_wc_conflict_description2_t *desc,
+ const char *path_prefix,
+ apr_pool_t *pool)
+{
+ const char *path1, *path2;
+ const char *label1, *label2;
+ svn_diff_t *diff;
+ svn_stream_t *output;
+ svn_diff_file_options_t *options;
+
+ if (desc->merged_file)
+ {
+ /* For conflicts recorded by the 'merge' operation, show a diff between
+ * 'mine' (the working version of the file as it appeared before the
+ * 'merge' operation was run) and 'merged' (the version of the file
+ * as it appears after the merge operation).
+ *
+ * For conflicts recorded by the 'update' and 'switch' operations,
+ * show a diff beween 'theirs' (the new pristine version of the
+ * file) and 'merged' (the version of the file as it appears with
+ * local changes merged with the new pristine version).
+ *
+ * This way, the diff is always minimal and clearly identifies changes
+ * brought into the working copy by the update/switch/merge operation. */
+ if (desc->operation == svn_wc_operation_merge)
+ {
+ path1 = desc->my_abspath;
+ label1 = _("MINE");
+ }
+ else
+ {
+ path1 = desc->their_abspath;
+ label1 = _("THEIRS");
+ }
+ path2 = desc->merged_file;
+ label2 = _("MERGED");
+ }
+ else
+ {
+ /* There's no merged file, but we can show the
+ difference between mine and theirs. */
+ path1 = desc->their_abspath;
+ label1 = _("THEIRS");
+ path2 = desc->my_abspath;
+ label2 = _("MINE");
+ }
+
+ label1 = apr_psprintf(pool, "%s\t- %s",
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, path1, pool), label1);
+ label2 = apr_psprintf(pool, "%s\t- %s",
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, path2, pool), label2);
+
+ options = svn_diff_file_options_create(pool);
+ options->ignore_eol_style = TRUE;
+ SVN_ERR(svn_stream_for_stdout(&output, pool));
+ SVN_ERR(svn_diff_file_diff_2(&diff, path1, path2,
+ options, pool));
+ return svn_diff_file_output_unified3(output, diff,
+ path1, path2,
+ label1, label2,
+ APR_LOCALE_CHARSET,
+ NULL, FALSE,
+ pool);
+}
+
+
+/* Print on stdout just the conflict hunks of a diff among the 'base', 'their'
+ * and 'my' files of DESC. */
+static svn_error_t *
+show_conflicts(const svn_wc_conflict_description2_t *desc,
+ apr_pool_t *pool)
+{
+ svn_diff_t *diff;
+ svn_stream_t *output;
+ svn_diff_file_options_t *options;
+
+ options = svn_diff_file_options_create(pool);
+ options->ignore_eol_style = TRUE;
+ SVN_ERR(svn_stream_for_stdout(&output, pool));
+ SVN_ERR(svn_diff_file_diff3_2(&diff,
+ desc->base_abspath,
+ desc->my_abspath,
+ desc->their_abspath,
+ options, pool));
+ /* ### Consider putting the markers/labels from
+ ### svn_wc__merge_internal in the conflict description. */
+ return svn_diff_file_output_merge2(output, diff,
+ desc->base_abspath,
+ desc->my_abspath,
+ desc->their_abspath,
+ _("||||||| ORIGINAL"),
+ _("<<<<<<< MINE (select with 'mc')"),
+ _(">>>>>>> THEIRS (select with 'tc')"),
+ "=======",
+ svn_diff_conflict_display_only_conflicts,
+ pool);
+}
+
+/* Perform a 3-way merge of the conflicting values of a property,
+ * and write the result to the OUTPUT stream.
+ *
+ * If MERGED_ABSPATH is non-NULL, use it as 'my' version instead of
+ * DESC->MY_ABSPATH.
+ *
+ * Assume the values are printable UTF-8 text.
+ */
+static svn_error_t *
+merge_prop_conflict(svn_stream_t *output,
+ const svn_wc_conflict_description2_t *desc,
+ const char *merged_abspath,
+ apr_pool_t *pool)
+{
+ const char *base_abspath = desc->base_abspath;
+ const char *my_abspath = desc->my_abspath;
+ const char *their_abspath = desc->their_abspath;
+ svn_diff_file_options_t *options = svn_diff_file_options_create(pool);
+ svn_diff_t *diff;
+
+ /* If any of the property values is missing, use an empty file instead
+ * for the purpose of showing a diff. */
+ if (! base_abspath || ! my_abspath || ! their_abspath)
+ {
+ const char *empty_file;
+
+ SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file,
+ NULL, svn_io_file_del_on_pool_cleanup,
+ pool, pool));
+ if (! base_abspath)
+ base_abspath = empty_file;
+ if (! my_abspath)
+ my_abspath = empty_file;
+ if (! their_abspath)
+ their_abspath = empty_file;
+ }
+
+ options->ignore_eol_style = TRUE;
+ SVN_ERR(svn_diff_file_diff3_2(&diff,
+ base_abspath,
+ merged_abspath ? merged_abspath : my_abspath,
+ their_abspath,
+ options, pool));
+ SVN_ERR(svn_diff_file_output_merge2(output, diff,
+ base_abspath,
+ merged_abspath ? merged_abspath
+ : my_abspath,
+ their_abspath,
+ _("||||||| ORIGINAL"),
+ _("<<<<<<< MINE"),
+ _(">>>>>>> THEIRS"),
+ "=======",
+ svn_diff_conflict_display_modified_original_latest,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Display the conflicting values of a property as a 3-way diff.
+ *
+ * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of
+ * DESC->MY_ABSPATH.
+ *
+ * Assume the values are printable UTF-8 text.
+ */
+static svn_error_t *
+show_prop_conflict(const svn_wc_conflict_description2_t *desc,
+ const char *merged_abspath,
+ apr_pool_t *pool)
+{
+ svn_stream_t *output;
+
+ SVN_ERR(svn_stream_for_stdout(&output, pool));
+ SVN_ERR(merge_prop_conflict(output, desc, merged_abspath, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Run an external editor, passing it the MERGED_FILE, or, if the
+ * 'merged' file is null, return an error. The tool to use is determined by
+ * B->editor_cmd, B->config and environment variables; see
+ * svn_cl__edit_file_externally() for details.
+ *
+ * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not
+ * configured or cannot run, do not touch *PERFORMED_EDIT, report the error
+ * on stderr, and return SVN_NO_ERROR; if any other error is encountered,
+ * return that error. */
+static svn_error_t *
+open_editor(svn_boolean_t *performed_edit,
+ const char *merged_file,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ if (merged_file)
+ {
+ err = svn_cmdline__edit_file_externally(merged_file, b->editor_cmd,
+ b->config, pool);
+ if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
+ {
+ svn_error_t *root_err = svn_error_root_cause(err);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ root_err->message ? root_err->message :
+ _("No editor found.")));
+ svn_error_clear(err);
+ }
+ else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
+ {
+ svn_error_t *root_err = svn_error_root_cause(err);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ root_err->message ? root_err->message :
+ _("Error running editor.")));
+ svn_error_clear(err);
+ }
+ else if (err)
+ return svn_error_trace(err);
+ else
+ *performed_edit = TRUE;
+ }
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool,
+ _("Invalid option; there's no "
+ "merged version to edit.\n\n")));
+
+ return SVN_NO_ERROR;
+}
+
+/* Run an external editor, passing it the 'merged' property in DESC.
+ * The tool to use is determined by B->editor_cmd, B->config and
+ * environment variables; see svn_cl__edit_file_externally() for details. */
+static svn_error_t *
+edit_prop_conflict(const char **merged_file_path,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *file;
+ const char *file_path;
+ svn_boolean_t performed_edit = FALSE;
+ svn_stream_t *merged_prop;
+
+ SVN_ERR(svn_io_open_unique_file3(&file, &file_path, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+ merged_prop = svn_stream_from_aprfile2(file, TRUE /* disown */,
+ scratch_pool);
+ SVN_ERR(merge_prop_conflict(merged_prop, desc, NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(merged_prop));
+ SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool));
+ SVN_ERR(open_editor(&performed_edit, file_path, b, scratch_pool));
+ *merged_file_path = (performed_edit ? file_path : NULL);
+
+ return SVN_NO_ERROR;
+}
+
+/* Run an external merge tool, passing it the 'base', 'their', 'my' and
+ * 'merged' files in DESC. The tool to use is determined by B->config and
+ * environment variables; see svn_cl__merge_file_externally() for details.
+ *
+ * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not
+ * configured or cannot run, do not touch *PERFORMED_EDIT, report the error
+ * on stderr, and return SVN_NO_ERROR; if any other error is encountered,
+ * return that error. */
+static svn_error_t *
+launch_resolver(svn_boolean_t *performed_edit,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ err = svn_cl__merge_file_externally(desc->base_abspath, desc->their_abspath,
+ desc->my_abspath, desc->merged_file,
+ desc->local_abspath, b->config, NULL,
+ pool);
+ if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ err->message ? err->message :
+ _("No merge tool found, "
+ "try '(m) merge' instead.\n")));
+ svn_error_clear(err);
+ }
+ else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ err->message ? err->message :
+ _("Error running merge tool, "
+ "try '(m) merge' instead.")));
+ svn_error_clear(err);
+ }
+ else if (err)
+ return svn_error_trace(err);
+ else if (performed_edit)
+ *performed_edit = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Maximum line length for the prompt string. */
+#define MAX_PROMPT_WIDTH 70
+
+/* Description of a resolver option */
+typedef struct resolver_option_t
+{
+ const char *code; /* one or two characters */
+ const char *short_desc; /* label in prompt (localized) */
+ const char *long_desc; /* longer description (localized) */
+ svn_wc_conflict_choice_t choice; /* or -1 if not a simple choice */
+} resolver_option_t;
+
+/* Resolver options for a text conflict */
+/* (opt->code == "" causes a blank line break in help_string()) */
+static const resolver_option_t text_conflict_options[] =
+{
+ /* Translators: keep long_desc below 70 characters (wrap with a left
+ margin of 9 spaces if needed); don't translate the words within square
+ brackets. */
+ { "e", N_("edit file"), N_("change merged file in an editor"
+ " [edit]"),
+ -1 },
+ { "df", N_("show diff"), N_("show all changes made to merged file"),
+ -1 },
+ { "r", N_("resolved"), N_("accept merged version of file"),
+ svn_wc_conflict_choose_merged },
+ { "", "", "", svn_wc_conflict_choose_unspecified },
+ { "dc", N_("display conflict"), N_("show all conflicts "
+ "(ignoring merged version)"), -1 },
+ { "mc", N_("my side of conflict"), N_("accept my version for all conflicts "
+ "(same) [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "tc", N_("their side of conflict"), N_("accept their version for all "
+ "conflicts (same)"
+ " [theirs-conflict]"),
+ svn_wc_conflict_choose_theirs_conflict },
+ { "", "", "", svn_wc_conflict_choose_unspecified },
+ { "mf", N_("my version"), N_("accept my version of entire file (even "
+ "non-conflicts) [mine-full]"),
+ svn_wc_conflict_choose_mine_full },
+ { "tf", N_("their version"), N_("accept their version of entire file "
+ "(same) [theirs-full]"),
+ svn_wc_conflict_choose_theirs_full },
+ { "", "", "", svn_wc_conflict_choose_unspecified },
+ { "p", N_("postpone"), N_("mark the conflict to be resolved later"
+ " [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "m", N_("merge"), N_("use internal merge tool to resolve "
+ "conflict"), -1 },
+ { "l", N_("launch tool"), N_("launch external tool to resolve "
+ "conflict [launch]"), -1 },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "s", N_("show all options"), N_("show this list (also 'h', '?')"), -1 },
+ { NULL }
+};
+
+/* Resolver options for a property conflict */
+static const resolver_option_t prop_conflict_options[] =
+{
+ { "p", N_("postpone"), N_("mark the conflict to be resolved later"
+ " [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "mf", N_("my version"), N_("accept my version of entire file (even "
+ "non-conflicts) [mine-full]"),
+ svn_wc_conflict_choose_mine_full },
+ { "tf", N_("their version"), N_("accept their version of entire file "
+ "(same) [theirs-full]"),
+ svn_wc_conflict_choose_theirs_full },
+ { "dc", N_("display conflict"), N_("show conflicts in this property"), -1 },
+ { "e", N_("edit property"), N_("change merged property value in an editor"
+ " [edit]"), -1 },
+ { "r", N_("resolved"), N_("accept edited version of property"),
+ svn_wc_conflict_choose_merged },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+/* Resolver options for an obstructued addition */
+static const resolver_option_t obstructed_add_options[] =
+{
+ { "p", N_("postpone"), N_("mark the conflict to be resolved later"
+ " [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "mf", N_("my version"), N_("accept pre-existing item (ignore "
+ "upstream addition) [mine-full]"),
+ svn_wc_conflict_choose_mine_full },
+ { "tf", N_("their version"), N_("accept incoming item (overwrite "
+ "pre-existing item) [theirs-full]"),
+ svn_wc_conflict_choose_theirs_full },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+/* Resolver options for a tree conflict */
+static const resolver_option_t tree_conflict_options[] =
+{
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "r", N_("resolved"), N_("accept current working copy state"),
+ svn_wc_conflict_choose_merged },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+static const resolver_option_t tree_conflict_options_update_moved_away[] =
+{
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "mc", N_("my side of conflict"), N_("apply update to the move destination"
+ " [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("resolved"), N_("mark resolved "
+ "(the move will become a copy)"),
+ svn_wc_conflict_choose_merged },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+static const resolver_option_t tree_conflict_options_update_deleted[] =
+{
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "mc", N_("my side of conflict"), N_("keep any moves affected "
+ "by this deletion [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("resolved"), N_("mark resolved (any affected moves will "
+ "become copies)"),
+ svn_wc_conflict_choose_merged },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+static const resolver_option_t tree_conflict_options_update_replaced[] =
+{
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "mc", N_("my side of conflict"), N_("keep any moves affected by this "
+ "replacement [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("resolved"), N_("mark resolved (any affected moves will "
+ "become copies)"),
+ svn_wc_conflict_choose_merged },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+
+/* Return a pointer to the option description in OPTIONS matching the
+ * one- or two-character OPTION_CODE. Return NULL if not found. */
+static const resolver_option_t *
+find_option(const resolver_option_t *options,
+ const char *option_code)
+{
+ const resolver_option_t *opt;
+
+ for (opt = options; opt->code; opt++)
+ {
+ /* Ignore code "" (blank lines) which is not a valid answer. */
+ if (opt->code[0] && strcmp(opt->code, option_code) == 0)
+ return opt;
+ }
+ return NULL;
+}
+
+/* Return a prompt string listing the options OPTIONS. If OPTION_CODES is
+ * non-null, select only the options whose codes are mentioned in it. */
+static const char *
+prompt_string(const resolver_option_t *options,
+ const char *const *option_codes,
+ apr_pool_t *pool)
+{
+ const char *result = _("Select:");
+ int left_margin = svn_utf_cstring_utf8_width(result);
+ const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, "");
+ int this_line_len = left_margin;
+ svn_boolean_t first = TRUE;
+
+ while (1)
+ {
+ const resolver_option_t *opt;
+ const char *s;
+ int slen;
+
+ if (option_codes)
+ {
+ if (! *option_codes)
+ break;
+ opt = find_option(options, *option_codes++);
+ }
+ else
+ {
+ opt = options++;
+ if (! opt->code)
+ break;
+ }
+
+ if (! first)
+ result = apr_pstrcat(pool, result, ",", (char *)NULL);
+ s = apr_psprintf(pool, _(" (%s) %s"),
+ opt->code, _(opt->short_desc));
+ slen = svn_utf_cstring_utf8_width(s);
+ /* Break the line if adding the next option would make it too long */
+ if (this_line_len + slen > MAX_PROMPT_WIDTH)
+ {
+ result = apr_pstrcat(pool, result, line_sep, (char *)NULL);
+ this_line_len = left_margin;
+ }
+ result = apr_pstrcat(pool, result, s, (char *)NULL);
+ this_line_len += slen;
+ first = FALSE;
+ }
+ return apr_pstrcat(pool, result, ": ", (char *)NULL);
+}
+
+/* Return a help string listing the OPTIONS. */
+static const char *
+help_string(const resolver_option_t *options,
+ apr_pool_t *pool)
+{
+ const char *result = "";
+ const resolver_option_t *opt;
+
+ for (opt = options; opt->code; opt++)
+ {
+ /* Append a line describing OPT, or a blank line if its code is "". */
+ if (opt->code[0])
+ {
+ const char *s = apr_psprintf(pool, " (%s)", opt->code);
+
+ result = apr_psprintf(pool, "%s%-6s - %s\n",
+ result, s, _(opt->long_desc));
+ }
+ else
+ {
+ result = apr_pstrcat(pool, result, "\n", (char *)NULL);
+ }
+ }
+ result = apr_pstrcat(pool, result,
+ _("Words in square brackets are the corresponding "
+ "--accept option arguments.\n"),
+ (char *)NULL);
+ return result;
+}
+
+/* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed
+ * in OPTIONS_TO_SHOW if that is non-null. Set *OPT to point to the chosen
+ * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to
+ * NULL if the answer was not one of them.
+ *
+ * If the answer is the (globally recognized) 'help' option, then display
+ * the help (on stderr) and return with *OPT == NULL.
+ */
+static svn_error_t *
+prompt_user(const resolver_option_t **opt,
+ const resolver_option_t *conflict_options,
+ const char *const *options_to_show,
+ void *prompt_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *prompt
+ = prompt_string(conflict_options, options_to_show, scratch_pool);
+ const char *answer;
+
+ SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool));
+ if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
+ help_string(conflict_options,
+ scratch_pool)));
+ *opt = NULL;
+ }
+ else
+ {
+ *opt = find_option(conflict_options, answer);
+ if (! *opt)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Unrecognized option.\n\n")));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Ask the user what to do about the text conflict described by DESC.
+ * Return the answer in RESULT. B is the conflict baton for this
+ * conflict resolution session.
+ * SCRATCH_POOL is used for temporary allocations. */
+static svn_error_t *
+handle_text_conflict(svn_wc_conflict_result_t *result,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_boolean_t diff_allowed = FALSE;
+ /* Have they done something that might have affected the merged
+ file (so that we need to save a .edited copy)? */
+ svn_boolean_t performed_edit = FALSE;
+ /* Have they done *something* (edit, look at diff, etc) to
+ give them a rational basis for choosing (r)esolved? */
+ svn_boolean_t knows_something = FALSE;
+
+ SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Conflict discovered in file '%s'.\n"),
+ svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath,
+ scratch_pool)));
+
+ /* Diffing can happen between base and merged, to show conflict
+ markers to the user (this is the typical 3-way merge
+ scenario), or if no base is available, we can show a diff
+ between mine and theirs. */
+ if ((desc->merged_file && desc->base_abspath)
+ || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))
+ diff_allowed = TRUE;
+
+ while (TRUE)
+ {
+ const char *options[ARRAY_LEN(text_conflict_options)];
+ const char **next_option = options;
+ const resolver_option_t *opt;
+
+ svn_pool_clear(iterpool);
+
+ *next_option++ = "p";
+ if (diff_allowed)
+ {
+ *next_option++ = "df";
+ *next_option++ = "e";
+ *next_option++ = "m";
+
+ if (knows_something)
+ *next_option++ = "r";
+
+ if (! desc->is_binary)
+ {
+ *next_option++ = "mc";
+ *next_option++ = "tc";
+ }
+ }
+ else
+ {
+ if (knows_something)
+ *next_option++ = "r";
+ *next_option++ = "mf";
+ *next_option++ = "tf";
+ }
+ *next_option++ = "s";
+ *next_option++ = NULL;
+
+ SVN_ERR(prompt_user(&opt, text_conflict_options, options, b->pb,
+ iterpool));
+ if (! opt)
+ continue;
+
+ if (strcmp(opt->code, "q") == 0)
+ {
+ result->choice = opt->choice;
+ b->accept_which = svn_cl__accept_postpone;
+ b->quit = TRUE;
+ break;
+ }
+ else if (strcmp(opt->code, "s") == 0)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
+ help_string(text_conflict_options,
+ iterpool)));
+ }
+ else if (strcmp(opt->code, "dc") == 0)
+ {
+ if (desc->is_binary)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; cannot "
+ "display conflicts for a "
+ "binary file.\n\n")));
+ continue;
+ }
+ else if (! (desc->my_abspath && desc->base_abspath &&
+ desc->their_abspath))
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; original "
+ "files not available.\n\n")));
+ continue;
+ }
+ SVN_ERR(show_conflicts(desc, iterpool));
+ knows_something = TRUE;
+ }
+ else if (strcmp(opt->code, "df") == 0)
+ {
+ if (! diff_allowed)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; there's no "
+ "merged version to diff.\n\n")));
+ continue;
+ }
+
+ SVN_ERR(show_diff(desc, b->path_prefix, iterpool));
+ knows_something = TRUE;
+ }
+ else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0)
+ {
+ SVN_ERR(open_editor(&performed_edit, desc->merged_file, b, iterpool));
+ if (performed_edit)
+ knows_something = TRUE;
+ }
+ else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 ||
+ strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0)
+ {
+ if (desc->kind != svn_wc_conflict_kind_text)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; can only "
+ "resolve text conflicts with "
+ "the internal merge tool."
+ "\n\n")));
+ continue;
+ }
+
+ if (desc->base_abspath && desc->their_abspath &&
+ desc->my_abspath && desc->merged_file)
+ {
+ svn_boolean_t remains_in_conflict;
+
+ SVN_ERR(svn_cl__merge_file(desc->base_abspath,
+ desc->their_abspath,
+ desc->my_abspath,
+ desc->merged_file,
+ desc->local_abspath,
+ b->path_prefix,
+ b->editor_cmd,
+ b->config,
+ &remains_in_conflict,
+ iterpool));
+ knows_something = !remains_in_conflict;
+ }
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option.\n\n")));
+ }
+ else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0)
+ {
+ /* ### This check should be earlier as it's nasty to offer an option
+ * and then when the user chooses it say 'Invalid option'. */
+ /* ### 'merged_file' shouldn't be necessary *before* we launch the
+ * resolver: it should be the *result* of doing so. */
+ if (desc->base_abspath && desc->their_abspath &&
+ desc->my_abspath && desc->merged_file)
+ {
+ SVN_ERR(launch_resolver(&performed_edit, desc, b, iterpool));
+ if (performed_edit)
+ knows_something = TRUE;
+ }
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option.\n\n")));
+ }
+ else if (opt->choice != -1)
+ {
+ if ((opt->choice == svn_wc_conflict_choose_mine_conflict
+ || opt->choice == svn_wc_conflict_choose_theirs_conflict)
+ && desc->is_binary)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; cannot choose "
+ "based on conflicts in a "
+ "binary file.\n\n")));
+ continue;
+ }
+
+ /* We only allow the user accept the merged version of
+ the file if they've edited it, or at least looked at
+ the diff. */
+ if (result->choice == svn_wc_conflict_choose_merged
+ && ! knows_something)
+ {
+ SVN_ERR(svn_cmdline_fprintf(
+ stderr, iterpool,
+ _("Invalid option; use diff/edit/merge/launch "
+ "before choosing 'resolved'.\n\n")));
+ continue;
+ }
+
+ result->choice = opt->choice;
+ if (performed_edit)
+ result->save_merged = TRUE;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Ask the user what to do about the property conflict described by DESC.
+ * Return the answer in RESULT. B is the conflict baton for this
+ * conflict resolution session.
+ * SCRATCH_POOL is used for temporary allocations. */
+static svn_error_t *
+handle_prop_conflict(svn_wc_conflict_result_t *result,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ const char *message;
+ const char *merged_file_path = NULL;
+ svn_boolean_t resolved_allowed = FALSE;
+
+ /* ### Work around a historical bug in the provider: the path to the
+ * conflict description file was put in the 'theirs' field, and
+ * 'theirs' was put in the 'merged' field. */
+ ((svn_wc_conflict_description2_t *)desc)->their_abspath = desc->merged_file;
+ ((svn_wc_conflict_description2_t *)desc)->merged_file = NULL;
+
+ SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Conflict for property '%s' discovered"
+ " on '%s'.\n"),
+ desc->property_name,
+ svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath,
+ scratch_pool)));
+
+ SVN_ERR(svn_cl__get_human_readable_prop_conflict_description(&message, desc,
+ scratch_pool));
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", message));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (TRUE)
+ {
+ const resolver_option_t *opt;
+ const char *options[ARRAY_LEN(prop_conflict_options)];
+ const char **next_option = options;
+
+ *next_option++ = "p";
+ *next_option++ = "mf";
+ *next_option++ = "tf";
+ *next_option++ = "dc";
+ *next_option++ = "e";
+ if (resolved_allowed)
+ *next_option++ = "r";
+ *next_option++ = "q";
+ *next_option++ = "h";
+ *next_option++ = NULL;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb,
+ iterpool));
+ if (! opt)
+ continue;
+
+ if (strcmp(opt->code, "q") == 0)
+ {
+ result->choice = opt->choice;
+ b->accept_which = svn_cl__accept_postpone;
+ b->quit = TRUE;
+ break;
+ }
+ else if (strcmp(opt->code, "dc") == 0)
+ {
+ SVN_ERR(show_prop_conflict(desc, merged_file_path, scratch_pool));
+ }
+ else if (strcmp(opt->code, "e") == 0)
+ {
+ SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b,
+ result_pool, scratch_pool));
+ resolved_allowed = (merged_file_path != NULL);
+ }
+ else if (strcmp(opt->code, "r") == 0)
+ {
+ if (! resolved_allowed)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; please edit the property "
+ "first.\n\n")));
+ continue;
+ }
+
+ result->merged_file = merged_file_path;
+ result->choice = svn_wc_conflict_choose_merged;
+ break;
+ }
+ else if (opt->choice != -1)
+ {
+ result->choice = opt->choice;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Ask the user what to do about the tree conflict described by DESC.
+ * Return the answer in RESULT. B is the conflict baton for this
+ * conflict resolution session.
+ * SCRATCH_POOL is used for temporary allocations. */
+static svn_error_t *
+handle_tree_conflict(svn_wc_conflict_result_t *result,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *scratch_pool)
+{
+ const char *readable_desc;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
+ &readable_desc, desc, scratch_pool));
+ SVN_ERR(svn_cmdline_fprintf(
+ stderr, scratch_pool,
+ _("Tree conflict on '%s'\n > %s\n"),
+ svn_cl__local_style_skip_ancestor(b->path_prefix,
+ desc->local_abspath,
+ scratch_pool),
+ readable_desc));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (1)
+ {
+ const resolver_option_t *opt;
+ const resolver_option_t *tc_opts;
+
+ svn_pool_clear(iterpool);
+
+ if (desc->operation == svn_wc_operation_update ||
+ desc->operation == svn_wc_operation_switch)
+ {
+ if (desc->reason == svn_wc_conflict_reason_moved_away)
+ tc_opts = tree_conflict_options_update_moved_away;
+ else if (desc->reason == svn_wc_conflict_reason_deleted)
+ tc_opts = tree_conflict_options_update_deleted;
+ else if (desc->reason == svn_wc_conflict_reason_replaced)
+ tc_opts = tree_conflict_options_update_replaced;
+ else
+ tc_opts = tree_conflict_options;
+ }
+ else
+ tc_opts = tree_conflict_options;
+
+ SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool));
+ if (! opt)
+ continue;
+
+ if (strcmp(opt->code, "q") == 0)
+ {
+ result->choice = opt->choice;
+ b->accept_which = svn_cl__accept_postpone;
+ b->quit = TRUE;
+ break;
+ }
+ else if (opt->choice != -1)
+ {
+ result->choice = opt->choice;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Ask the user what to do about the obstructed add described by DESC.
+ * Return the answer in RESULT. B is the conflict baton for this
+ * conflict resolution session.
+ * SCRATCH_POOL is used for temporary allocations. */
+static svn_error_t *
+handle_obstructed_add(svn_wc_conflict_result_t *result,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_cmdline_fprintf(
+ stderr, scratch_pool,
+ _("Conflict discovered when trying to add '%s'.\n"
+ "An object of the same name already exists.\n"),
+ svn_cl__local_style_skip_ancestor(b->path_prefix,
+ desc->local_abspath,
+ scratch_pool)));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (1)
+ {
+ const resolver_option_t *opt;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(prompt_user(&opt, obstructed_add_options, NULL, b->pb,
+ iterpool));
+ if (! opt)
+ continue;
+
+ if (strcmp(opt->code, "q") == 0)
+ {
+ result->choice = opt->choice;
+ b->accept_which = svn_cl__accept_postpone;
+ b->quit = TRUE;
+ break;
+ }
+ else if (opt->choice != -1)
+ {
+ result->choice = opt->choice;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_cl__conflict_func_interactive(). */
+static svn_error_t *
+conflict_func_interactive(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *desc,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__interactive_conflict_baton_t *b = baton;
+ svn_error_t *err;
+
+ /* Start out assuming we're going to postpone the conflict. */
+ *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
+ NULL, result_pool);
+
+ switch (b->accept_which)
+ {
+ case svn_cl__accept_invalid:
+ case svn_cl__accept_unspecified:
+ /* No (or no valid) --accept option, fall through to prompting. */
+ break;
+ case svn_cl__accept_postpone:
+ (*result)->choice = svn_wc_conflict_choose_postpone;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_base:
+ (*result)->choice = svn_wc_conflict_choose_base;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_working:
+ /* If the caller didn't merge the property values, then I guess
+ * 'choose working' means 'choose mine'... */
+ if (! desc->merged_file)
+ (*result)->merged_file = desc->my_abspath;
+ (*result)->choice = svn_wc_conflict_choose_merged;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_mine_conflict:
+ (*result)->choice = svn_wc_conflict_choose_mine_conflict;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_theirs_conflict:
+ (*result)->choice = svn_wc_conflict_choose_theirs_conflict;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_mine_full:
+ (*result)->choice = svn_wc_conflict_choose_mine_full;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_theirs_full:
+ (*result)->choice = svn_wc_conflict_choose_theirs_full;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_edit:
+ if (desc->merged_file)
+ {
+ if (b->external_failed)
+ {
+ (*result)->choice = svn_wc_conflict_choose_postpone;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_cmdline__edit_file_externally(desc->merged_file,
+ b->editor_cmd, b->config,
+ scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ err->message ? err->message :
+ _("No editor found;"
+ " leaving all conflicts.")));
+ svn_error_clear(err);
+ b->external_failed = TRUE;
+ }
+ else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ err->message ? err->message :
+ _("Error running editor;"
+ " leaving all conflicts.")));
+ svn_error_clear(err);
+ b->external_failed = TRUE;
+ }
+ else if (err)
+ return svn_error_trace(err);
+ (*result)->choice = svn_wc_conflict_choose_merged;
+ return SVN_NO_ERROR;
+ }
+ /* else, fall through to prompting. */
+ break;
+ case svn_cl__accept_launch:
+ if (desc->base_abspath && desc->their_abspath
+ && desc->my_abspath && desc->merged_file)
+ {
+ svn_boolean_t remains_in_conflict;
+
+ if (b->external_failed)
+ {
+ (*result)->choice = svn_wc_conflict_choose_postpone;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_cl__merge_file_externally(desc->base_abspath,
+ desc->their_abspath,
+ desc->my_abspath,
+ desc->merged_file,
+ desc->local_abspath,
+ b->config,
+ &remains_in_conflict,
+ scratch_pool);
+ if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ err->message ? err->message :
+ _("No merge tool found;"
+ " leaving all conflicts.")));
+ b->external_failed = TRUE;
+ return svn_error_trace(err);
+ }
+ else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ err->message ? err->message :
+ _("Error running merge tool;"
+ " leaving all conflicts.")));
+ b->external_failed = TRUE;
+ return svn_error_trace(err);
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ if (remains_in_conflict)
+ (*result)->choice = svn_wc_conflict_choose_postpone;
+ else
+ (*result)->choice = svn_wc_conflict_choose_merged;
+ return SVN_NO_ERROR;
+ }
+ /* else, fall through to prompting. */
+ break;
+ }
+
+ /* We're in interactive mode and either the user gave no --accept
+ option or the option did not apply; let's prompt. */
+
+ /* Handle the most common cases, which is either:
+
+ Conflicting edits on a file's text, or
+ Conflicting edits on a property.
+ */
+ if (((desc->kind == svn_wc_conflict_kind_text)
+ && (desc->action == svn_wc_conflict_action_edit)
+ && (desc->reason == svn_wc_conflict_reason_edited)))
+ SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool));
+ else if (desc->kind == svn_wc_conflict_kind_property)
+ SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool));
+
+ /*
+ Dealing with obstruction of additions can be tricky. The
+ obstructing item could be unversioned, versioned, or even
+ schedule-add. Here's a matrix of how the caller should behave,
+ based on results we return.
+
+ Unversioned Versioned Schedule-Add
+
+ choose_mine skip addition, skip addition skip addition
+ add existing item
+
+ choose_theirs destroy file, schedule-delete, revert add,
+ add new item. add new item. rm file,
+ add new item
+
+ postpone [ bail out ]
+
+ */
+ else if ((desc->action == svn_wc_conflict_action_add)
+ && (desc->reason == svn_wc_conflict_reason_obstructed))
+ SVN_ERR(handle_obstructed_add(*result, desc, b, scratch_pool));
+
+ else if (desc->kind == svn_wc_conflict_kind_tree)
+ SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool));
+
+ else /* other types of conflicts -- do nothing about them. */
+ {
+ (*result)->choice = svn_wc_conflict_choose_postpone;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *desc,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__interactive_conflict_baton_t *b = baton;
+
+ SVN_ERR(conflict_func_interactive(result, desc, baton,
+ result_pool, scratch_pool));
+
+ /* If we are resolving a conflict, adjust the summary of conflicts. */
+ if ((*result)->choice != svn_wc_conflict_choose_postpone)
+ {
+ const char *local_path
+ = svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath, scratch_pool);
+
+ svn_cl__conflict_stats_resolved(b->conflict_stats, local_path,
+ desc->kind);
+ }
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/copy-cmd.c b/contrib/subversion/subversion/svn/copy-cmd.c
new file mode 100644
index 0000000..e6fbd4b
--- /dev/null
+++ b/contrib/subversion/subversion/svn/copy-cmd.c
@@ -0,0 +1,184 @@
+/*
+ * copy-cmd.c -- Subversion copy command
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__copy(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets, *sources;
+ const char *src_path, *dst_path;
+ svn_boolean_t srcs_are_urls, dst_is_url;
+ svn_error_t *err;
+ int i;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+ if (targets->nelts < 2)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ /* Get the src list and associated peg revs */
+ sources = apr_array_make(pool, targets->nelts - 1,
+ sizeof(svn_client_copy_source_t *));
+ for (i = 0; i < (targets->nelts - 1); i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_client_copy_source_t *source = apr_palloc(pool, sizeof(*source));
+ const char *src;
+ svn_opt_revision_t *peg_revision = apr_palloc(pool,
+ sizeof(*peg_revision));
+
+ err = svn_opt_parse_path(peg_revision, &src, target, pool);
+
+ if (err)
+ {
+ /* Issue #3606: 'svn cp .@HEAD target' gives
+ svn: '@HEAD' is just a peg revision. Maybe try '@HEAD@' instead?
+
+ This is caused by a first round of canonicalization in
+ svn_cl__args_to_target_array_print_reserved(). Undo that in an
+ attempt to fix this issue without revving many apis.
+ */
+ if (*target == '@' && err->apr_err == SVN_ERR_BAD_FILENAME)
+ {
+ svn_error_t *err2;
+
+ err2 = svn_opt_parse_path(peg_revision, &src,
+ apr_pstrcat(pool, ".", target,
+ (const char *)NULL), pool);
+
+ if (err2)
+ {
+ /* Fix attempt failed; return original error */
+ svn_error_clear(err2);
+ }
+ else
+ {
+ /* Error resolved. Use path */
+ svn_error_clear(err);
+ err = NULL;
+ }
+ }
+
+ if (err)
+ return svn_error_trace(err);
+ }
+
+ source->path = src;
+ source->revision = &(opt_state->start_revision);
+ source->peg_revision = peg_revision;
+
+ APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = source;
+ }
+
+ /* Get DST_PATH (the target path or URL) and check that no peg revision is
+ * specified for it. */
+ {
+ const char *tgt = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *);
+ svn_opt_revision_t peg;
+
+ SVN_ERR(svn_opt_parse_path(&peg, &dst_path, tgt, pool));
+ if (peg.kind != svn_opt_revision_unspecified)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s': a peg revision is not allowed here"),
+ tgt);
+ }
+
+ /* Figure out which type of notification to use.
+ (There is no need to check that the src paths are homogeneous;
+ svn_client_copy6() through its subroutine try_copy() will return an
+ error if they are not.) */
+ src_path = APR_ARRAY_IDX(targets, 0, const char *);
+ srcs_are_urls = svn_path_is_url(src_path);
+ dst_is_url = svn_path_is_url(dst_path);
+
+ if ((! srcs_are_urls) && (! dst_is_url))
+ {
+ /* WC->WC */
+ }
+ else if ((! srcs_are_urls) && (dst_is_url))
+ {
+ /* WC->URL : Use notification. */
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cl__notifier_mark_wc_to_repos_copy(ctx->notify_baton2));
+ }
+ else if ((srcs_are_urls) && (! dst_is_url))
+ {
+ /* URL->WC : Use checkout-style notification. */
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cl__notifier_mark_checkout(ctx->notify_baton2));
+ }
+ else
+ {
+ /* URL -> URL, meaning that no notification is needed. */
+ ctx->notify_func2 = NULL;
+ }
+
+ if (! dst_is_url)
+ {
+ ctx->log_msg_func3 = NULL;
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
+ return svn_error_create
+ (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
+ }
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
+ NULL, ctx->config, pool));
+
+ err = svn_client_copy6(sources, dst_path, TRUE,
+ opt_state->parents, opt_state->ignore_externals,
+ opt_state->revprop_table,
+ (opt_state->quiet ? NULL : svn_cl__print_commit_info),
+ NULL,
+ ctx, pool);
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool));
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/delete-cmd.c b/contrib/subversion/subversion/svn/delete-cmd.c
new file mode 100644
index 0000000..e73813b
--- /dev/null
+++ b/contrib/subversion/subversion/svn/delete-cmd.c
@@ -0,0 +1,95 @@
+/*
+ * delete-cmd.c -- Delete/undelete commands
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__delete(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ svn_error_t *err;
+ svn_boolean_t is_url;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ SVN_ERR(svn_cl__assert_homogeneous_target_type(targets));
+ is_url = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
+
+ if (! is_url)
+ {
+ ctx->log_msg_func3 = NULL;
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
+ {
+ return svn_error_create
+ (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
+ }
+ }
+ else
+ {
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
+ NULL, ctx->config, pool));
+ }
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ err = svn_client_delete4(targets, opt_state->force, opt_state->keep_local,
+ opt_state->revprop_table,
+ (opt_state->quiet
+ ? NULL : svn_cl__print_commit_info),
+ NULL, ctx, pool);
+ if (err)
+ err = svn_cl__may_need_force(err);
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool));
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/deprecated.c b/contrib/subversion/subversion/svn/deprecated.c
new file mode 100644
index 0000000..6115573
--- /dev/null
+++ b/contrib/subversion/subversion/svn/deprecated.c
@@ -0,0 +1,41 @@
+/*
+ * deprecated.c: Wrappers to call deprecated functions.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define SVN_DEPRECATED
+#include "cl.h"
+#include "svn_client.h"
+
+svn_error_t *
+svn_cl__deprecated_merge_reintegrate(const char *source_path_or_url,
+ const svn_opt_revision_t *src_peg_revision,
+ const char *target_wcpath,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_client_merge_reintegrate(source_path_or_url, src_peg_revision,
+ target_wcpath, dry_run, merge_options,
+ ctx, pool));
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/diff-cmd.c b/contrib/subversion/subversion/svn/diff-cmd.c
new file mode 100644
index 0000000..2cbd202
--- /dev/null
+++ b/contrib/subversion/subversion/svn/diff-cmd.c
@@ -0,0 +1,476 @@
+/*
+ * diff-cmd.c -- Display context diff of a file
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_types.h"
+#include "svn_cmdline.h"
+#include "svn_xml.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* Convert KIND into a single character for display to the user. */
+static char
+kind_to_char(svn_client_diff_summarize_kind_t kind)
+{
+ switch (kind)
+ {
+ case svn_client_diff_summarize_kind_modified:
+ return 'M';
+
+ case svn_client_diff_summarize_kind_added:
+ return 'A';
+
+ case svn_client_diff_summarize_kind_deleted:
+ return 'D';
+
+ default:
+ return ' ';
+ }
+}
+
+/* Convert KIND into a word describing the kind to the user. */
+static const char *
+kind_to_word(svn_client_diff_summarize_kind_t kind)
+{
+ switch (kind)
+ {
+ case svn_client_diff_summarize_kind_modified: return "modified";
+ case svn_client_diff_summarize_kind_added: return "added";
+ case svn_client_diff_summarize_kind_deleted: return "deleted";
+ default: return "none";
+ }
+}
+
+/* Baton for summarize_xml and summarize_regular */
+struct summarize_baton_t
+{
+ const char *anchor;
+};
+
+/* Print summary information about a given change as XML, implements the
+ * svn_client_diff_summarize_func_t interface. The @a baton is a 'char *'
+ * representing the either the path to the working copy root or the url
+ * the path the working copy root corresponds to. */
+static svn_error_t *
+summarize_xml(const svn_client_diff_summarize_t *summary,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct summarize_baton_t *b = baton;
+ /* Full path to the object being diffed. This is created by taking the
+ * baton, and appending the target's relative path. */
+ const char *path = b->anchor;
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ /* Tack on the target path, so we can differentiate between different parts
+ * of the output when we're given multiple targets. */
+ if (svn_path_is_url(path))
+ {
+ path = svn_path_url_add_component2(path, summary->path, pool);
+ }
+ else
+ {
+ path = svn_dirent_join(path, summary->path, pool);
+
+ /* Convert non-urls to local style, so that things like ""
+ show up as "." */
+ path = svn_dirent_local_style(path, pool);
+ }
+
+ svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
+ "kind", svn_cl__node_kind_str_xml(summary->node_kind),
+ "item", kind_to_word(summary->summarize_kind),
+ "props", summary->prop_changed ? "modified" : "none",
+ NULL);
+
+ svn_xml_escape_cdata_cstring(&sb, path, pool);
+ svn_xml_make_close_tag(&sb, pool, "path");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+/* Print summary information about a given change, implements the
+ * svn_client_diff_summarize_func_t interface. */
+static svn_error_t *
+summarize_regular(const svn_client_diff_summarize_t *summary,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct summarize_baton_t *b = baton;
+ const char *path = b->anchor;
+
+ /* Tack on the target path, so we can differentiate between different parts
+ * of the output when we're given multiple targets. */
+ if (svn_path_is_url(path))
+ {
+ path = svn_path_url_add_component2(path, summary->path, pool);
+ }
+ else
+ {
+ path = svn_dirent_join(path, summary->path, pool);
+
+ /* Convert non-urls to local style, so that things like ""
+ show up as "." */
+ path = svn_dirent_local_style(path, pool);
+ }
+
+ /* Note: This output format tries to look like the output of 'svn status',
+ * thus the blank spaces where information that is not relevant to
+ * a diff summary would go. */
+
+ SVN_ERR(svn_cmdline_printf(pool,
+ "%c%c %s\n",
+ kind_to_char(summary->summarize_kind),
+ summary->prop_changed ? 'M' : ' ',
+ path));
+
+ return svn_cmdline_fflush(stdout);
+}
+
+/* An svn_opt_subcommand_t to handle the 'diff' command.
+ This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__diff(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *options;
+ apr_array_header_t *targets;
+ svn_stream_t *outstream;
+ svn_stream_t *errstream;
+ const char *old_target, *new_target;
+ apr_pool_t *iterpool;
+ svn_boolean_t pegged_diff = FALSE;
+ svn_boolean_t show_copies_as_adds =
+ opt_state->diff.patch_compatible || opt_state->diff.show_copies_as_adds;
+ svn_boolean_t ignore_properties =
+ opt_state->diff.patch_compatible || opt_state->diff.ignore_properties;
+ int i;
+ struct summarize_baton_t summarize_baton;
+ const svn_client_diff_summarize_func_t summarize_func =
+ (opt_state->xml ? summarize_xml : summarize_regular);
+
+ if (opt_state->extensions)
+ options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
+ else
+ options = NULL;
+
+ /* Get streams representing stdout and stderr, which is where
+ we'll have the external 'diff' program print to. */
+ SVN_ERR(svn_stream_for_stdout(&outstream, pool));
+ SVN_ERR(svn_stream_for_stderr(&errstream, pool));
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb;
+
+ /* Check that the --summarize is passed as well. */
+ if (!opt_state->diff.summarize)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'--xml' option only valid with "
+ "'--summarize' option"));
+
+ SVN_ERR(svn_cl__xml_print_header("diff", pool));
+
+ sb = svn_stringbuf_create_empty(pool);
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", NULL);
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (! opt_state->old_target && ! opt_state->new_target
+ && (targets->nelts == 2)
+ && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))
+ || svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *)))
+ && opt_state->start_revision.kind == svn_opt_revision_unspecified
+ && opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ {
+ /* A 2-target diff where one or both targets are URLs. These are
+ * shorthands for some 'svn diff --old X --new Y' invocations. */
+
+ SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target,
+ APR_ARRAY_IDX(targets, 0, const char *),
+ pool));
+ SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target,
+ APR_ARRAY_IDX(targets, 1, const char *),
+ pool));
+ targets->nelts = 0;
+
+ /* Set default start/end revisions based on target types, in the same
+ * manner as done for the corresponding '--old X --new Y' cases,
+ * (note that we have an explicit --new target) */
+ if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
+ opt_state->start_revision.kind = svn_path_is_url(old_target)
+ ? svn_opt_revision_head : svn_opt_revision_working;
+
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ opt_state->end_revision.kind = svn_path_is_url(new_target)
+ ? svn_opt_revision_head : svn_opt_revision_working;
+ }
+ else if (opt_state->old_target)
+ {
+ apr_array_header_t *tmp, *tmp2;
+ svn_opt_revision_t old_rev, new_rev;
+
+ /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
+ [PATH...]' case matches. */
+
+ tmp = apr_array_make(pool, 2, sizeof(const char *));
+ APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target);
+ APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target
+ ? opt_state->new_target
+ : opt_state->old_target);
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
+ ctx, FALSE, pool));
+
+ /* Check if either or both targets were skipped (e.g. because they
+ * were .svn directories). */
+ if (tmp2->nelts < 2)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
+
+ SVN_ERR(svn_opt_parse_path(&old_rev, &old_target,
+ APR_ARRAY_IDX(tmp2, 0, const char *),
+ pool));
+ if (old_rev.kind != svn_opt_revision_unspecified)
+ opt_state->start_revision = old_rev;
+ SVN_ERR(svn_opt_parse_path(&new_rev, &new_target,
+ APR_ARRAY_IDX(tmp2, 1, const char *),
+ pool));
+ if (new_rev.kind != svn_opt_revision_unspecified)
+ opt_state->end_revision = new_rev;
+
+ /* For URLs, default to HEAD. For WC paths, default to WORKING if
+ * new target is explicit; if new target is implicitly the same as
+ * old target, then default the old to BASE and new to WORKING. */
+ if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
+ opt_state->start_revision.kind = svn_path_is_url(old_target)
+ ? svn_opt_revision_head
+ : (opt_state->new_target
+ ? svn_opt_revision_working : svn_opt_revision_base);
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ opt_state->end_revision.kind = svn_path_is_url(new_target)
+ ? svn_opt_revision_head : svn_opt_revision_working;
+ }
+ else if (opt_state->new_target)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'--new' option only valid with "
+ "'--old' option"));
+ }
+ else
+ {
+ svn_boolean_t working_copy_present;
+
+ /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
+
+ /* Here each target is a pegged object. Find out the starting
+ and ending paths for each target. */
+
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ old_target = "";
+ new_target = "";
+
+ SVN_ERR_W(svn_cl__assert_homogeneous_target_type(targets),
+ _("'svn diff [-r N[:M]] [TARGET[@REV]...]' does not support mixed "
+ "target types. Try using the --old and --new options or one of "
+ "the shorthand invocations listed in 'svn help diff'."));
+
+ working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0,
+ const char *));
+
+ if (opt_state->start_revision.kind == svn_opt_revision_unspecified
+ && working_copy_present)
+ opt_state->start_revision.kind = svn_opt_revision_base;
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ opt_state->end_revision.kind = working_copy_present
+ ? svn_opt_revision_working : svn_opt_revision_head;
+
+ /* Determine if we need to do pegged diffs. */
+ if ((opt_state->start_revision.kind != svn_opt_revision_base
+ && opt_state->start_revision.kind != svn_opt_revision_working)
+ || (opt_state->end_revision.kind != svn_opt_revision_base
+ && opt_state->end_revision.kind != svn_opt_revision_working))
+ pegged_diff = TRUE;
+
+ }
+
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < targets->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(targets, i, const char *);
+ const char *target1, *target2;
+
+ svn_pool_clear(iterpool);
+ if (! pegged_diff)
+ {
+ /* We can't be tacking URLs onto base paths! */
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' not relative to base URLs"),
+ path);
+
+ if (svn_path_is_url(old_target))
+ target1 = svn_path_url_add_component2(
+ old_target,
+ svn_relpath_canonicalize(path, iterpool),
+ iterpool);
+ else
+ target1 = svn_dirent_join(old_target, path, iterpool);
+
+ if (svn_path_is_url(new_target))
+ target2 = svn_path_url_add_component2(
+ new_target,
+ svn_relpath_canonicalize(path, iterpool),
+ iterpool);
+ else
+ target2 = svn_dirent_join(new_target, path, iterpool);
+
+ if (opt_state->diff.summarize)
+ {
+ summarize_baton.anchor = target1;
+
+ SVN_ERR(svn_client_diff_summarize2(
+ target1,
+ &opt_state->start_revision,
+ target2,
+ &opt_state->end_revision,
+ opt_state->depth,
+ ! opt_state->diff.notice_ancestry,
+ opt_state->changelists,
+ summarize_func, &summarize_baton,
+ ctx, iterpool));
+ }
+ else
+ SVN_ERR(svn_client_diff6(
+ options,
+ target1,
+ &(opt_state->start_revision),
+ target2,
+ &(opt_state->end_revision),
+ NULL,
+ opt_state->depth,
+ ! opt_state->diff.notice_ancestry,
+ opt_state->diff.no_diff_added,
+ opt_state->diff.no_diff_deleted,
+ show_copies_as_adds,
+ opt_state->force,
+ ignore_properties,
+ opt_state->diff.properties_only,
+ opt_state->diff.use_git_diff_format,
+ svn_cmdline_output_encoding(pool),
+ outstream,
+ errstream,
+ opt_state->changelists,
+ ctx, iterpool));
+ }
+ else
+ {
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+
+ /* First check for a peg revision. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path,
+ iterpool));
+
+ /* Set the default peg revision if one was not specified. */
+ if (peg_revision.kind == svn_opt_revision_unspecified)
+ peg_revision.kind = svn_path_is_url(path)
+ ? svn_opt_revision_head : svn_opt_revision_working;
+
+ if (opt_state->diff.summarize)
+ {
+ summarize_baton.anchor = truepath;
+ SVN_ERR(svn_client_diff_summarize_peg2(
+ truepath,
+ &peg_revision,
+ &opt_state->start_revision,
+ &opt_state->end_revision,
+ opt_state->depth,
+ ! opt_state->diff.notice_ancestry,
+ opt_state->changelists,
+ summarize_func, &summarize_baton,
+ ctx, iterpool));
+ }
+ else
+ SVN_ERR(svn_client_diff_peg6(
+ options,
+ truepath,
+ &peg_revision,
+ &opt_state->start_revision,
+ &opt_state->end_revision,
+ NULL,
+ opt_state->depth,
+ ! opt_state->diff.notice_ancestry,
+ opt_state->diff.no_diff_added,
+ opt_state->diff.no_diff_deleted,
+ show_copies_as_adds,
+ opt_state->force,
+ ignore_properties,
+ opt_state->diff.properties_only,
+ opt_state->diff.use_git_diff_format,
+ svn_cmdline_output_encoding(pool),
+ outstream,
+ errstream,
+ opt_state->changelists,
+ ctx, iterpool));
+ }
+ }
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+ svn_xml_make_close_tag(&sb, pool, "paths");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ SVN_ERR(svn_cl__xml_print_footer("diff", pool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/export-cmd.c b/contrib/subversion/subversion/svn/export-cmd.c
new file mode 100644
index 0000000..75b6723
--- /dev/null
+++ b/contrib/subversion/subversion/svn/export-cmd.c
@@ -0,0 +1,128 @@
+/*
+ * export-cmd.c -- Subversion export command
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_opt_private.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__export(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *from, *to;
+ apr_array_header_t *targets;
+ svn_error_t *err;
+ svn_opt_revision_t peg_revision;
+ const char *truefrom;
+ struct svn_cl__check_externals_failed_notify_baton nwb;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* We want exactly 1 or 2 targets for this subcommand. */
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+ /* The first target is the `from' path. */
+ from = APR_ARRAY_IDX(targets, 0, const char *);
+
+ /* Get the peg revision if present. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truefrom, from, pool));
+
+ /* If only one target was given, split off the basename to use as
+ the `to' path. Else, a `to' path was supplied. */
+ if (targets->nelts == 1)
+ {
+ if (svn_path_is_url(truefrom))
+ to = svn_uri_basename(truefrom, pool);
+ else
+ to = svn_dirent_basename(truefrom, pool);
+ }
+ else
+ {
+ to = APR_ARRAY_IDX(targets, 1, const char *);
+
+ if (strcmp("", to) != 0)
+ /* svn_cl__eat_peg_revisions() but only on one target */
+ SVN_ERR(svn_opt__split_arg_at_peg_revision(&to, NULL, to, pool));
+ }
+
+ SVN_ERR(svn_cl__check_target_is_local_path(to));
+
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cl__notifier_mark_export(ctx->notify_baton2));
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_infinity;
+
+ nwb.wrapped_func = ctx->notify_func2;
+ nwb.wrapped_baton = ctx->notify_baton2;
+ nwb.had_externals_error = FALSE;
+ ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper;
+ ctx->notify_baton2 = &nwb;
+
+ /* Do the export. */
+ err = svn_client_export5(NULL, truefrom, to, &peg_revision,
+ &(opt_state->start_revision),
+ opt_state->force, opt_state->ignore_externals,
+ opt_state->ignore_keywords, opt_state->depth,
+ opt_state->native_eol, ctx, pool);
+ if (err && err->apr_err == SVN_ERR_WC_OBSTRUCTED_UPDATE && !opt_state->force)
+ SVN_ERR_W(err,
+ _("Destination directory exists; please remove "
+ "the directory or use --force to overwrite"));
+
+ if (nwb.had_externals_error)
+ {
+ svn_error_t *externals_err;
+
+ externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
+ NULL,
+ _("Failure occurred processing one or "
+ "more externals definitions"));
+ return svn_error_compose_create(externals_err, err);
+ }
+
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/svn/file-merge.c b/contrib/subversion/subversion/svn/file-merge.c
new file mode 100644
index 0000000..43ebba9
--- /dev/null
+++ b/contrib/subversion/subversion/svn/file-merge.c
@@ -0,0 +1,959 @@
+/*
+ * file-merge.c: internal file merge tool
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* This is an interactive file merge tool with an interface similar to
+ * the interactive mode of the UNIX sdiff ("side-by-side diff") utility.
+ * The merge tool is driven by Subversion's diff code and user input. */
+
+#include "svn_cmdline.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_io.h"
+#include "svn_utf.h"
+#include "svn_xml.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_utf_private.h"
+#include "private/svn_cmdline_private.h"
+#include "private/svn_dep_compat.h"
+
+#if APR_HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <fcntl.h>
+#include <stdlib.h>
+
+/* Baton for functions in this file which implement svn_diff_output_fns_t. */
+struct file_merge_baton {
+ /* The files being merged. */
+ apr_file_t *original_file;
+ apr_file_t *modified_file;
+ apr_file_t *latest_file;
+
+ /* Counters to keep track of the current line in each file. */
+ svn_linenum_t current_line_original;
+ svn_linenum_t current_line_modified;
+ svn_linenum_t current_line_latest;
+
+ /* The merge result is written to this file. */
+ apr_file_t *merged_file;
+
+ /* Whether the merged file remains in conflict after the merge. */
+ svn_boolean_t remains_in_conflict;
+
+ /* External editor command for editing chunks. */
+ const char *editor_cmd;
+
+ /* The client configuration hash. */
+ apr_hash_t *config;
+
+ /* Wether the merge should be aborted. */
+ svn_boolean_t abort_merge;
+
+ /* Pool for temporary allocations. */
+ apr_pool_t *scratch_pool;
+} file_merge_baton;
+
+/* Copy LEN lines from SOURCE_FILE to the MERGED_FILE, starting at
+ * line START. The CURRENT_LINE is the current line in the source file.
+ * The new current line is returned in *NEW_CURRENT_LINE. */
+static svn_error_t *
+copy_to_merged_file(svn_linenum_t *new_current_line,
+ apr_file_t *merged_file,
+ apr_file_t *source_file,
+ apr_off_t start,
+ apr_off_t len,
+ svn_linenum_t current_line,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ svn_stringbuf_t *line;
+ apr_size_t lines_read;
+ apr_size_t lines_copied;
+ svn_boolean_t eof;
+ svn_linenum_t orig_current_line = current_line;
+
+ lines_read = 0;
+ iterpool = svn_pool_create(scratch_pool);
+ while (current_line < start)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_io_file_readline(source_file, &line, NULL, &eof,
+ APR_SIZE_MAX, iterpool, iterpool));
+ if (eof)
+ break;
+
+ current_line++;
+ lines_read++;
+ }
+
+ lines_copied = 0;
+ while (lines_copied < len)
+ {
+ apr_size_t bytes_written;
+ const char *eol_str;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_io_file_readline(source_file, &line, &eol_str, &eof,
+ APR_SIZE_MAX, iterpool, iterpool));
+ if (eol_str)
+ svn_stringbuf_appendcstr(line, eol_str);
+ SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len,
+ &bytes_written, iterpool));
+ if (bytes_written != line->len)
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
+ _("Could not write data to merged file"));
+ if (eof)
+ break;
+ lines_copied++;
+ }
+ svn_pool_destroy(iterpool);
+
+ *new_current_line = orig_current_line + lines_read + lines_copied;
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy common data to the merged file. */
+static svn_error_t *
+file_merge_output_common(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length)
+{
+ struct file_merge_baton *b = output_baton;
+
+ if (b->abort_merge)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(copy_to_merged_file(&b->current_line_original,
+ b->merged_file,
+ b->original_file,
+ original_start,
+ original_length,
+ b->current_line_original,
+ b->scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Original/latest match up, but modified differs.
+ * Copy modified data to the merged file. */
+static svn_error_t *
+file_merge_output_diff_modified(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length)
+{
+ struct file_merge_baton *b = output_baton;
+
+ if (b->abort_merge)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(copy_to_merged_file(&b->current_line_modified,
+ b->merged_file,
+ b->modified_file,
+ modified_start,
+ modified_length,
+ b->current_line_modified,
+ b->scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Original/modified match up, but latest differs.
+ * Copy latest data to the merged file. */
+static svn_error_t *
+file_merge_output_diff_latest(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length)
+{
+ struct file_merge_baton *b = output_baton;
+
+ if (b->abort_merge)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(copy_to_merged_file(&b->current_line_latest,
+ b->merged_file,
+ b->latest_file,
+ latest_start,
+ latest_length,
+ b->current_line_latest,
+ b->scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Modified/latest match up, but original differs.
+ * Copy latest data to the merged file. */
+static svn_error_t *
+file_merge_output_diff_common(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length)
+{
+ struct file_merge_baton *b = output_baton;
+
+ if (b->abort_merge)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(copy_to_merged_file(&b->current_line_latest,
+ b->merged_file,
+ b->latest_file,
+ latest_start,
+ latest_length,
+ b->current_line_latest,
+ b->scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Return LEN lines within the diff chunk staring at line START
+ * in a *LINES array of svn_stringbuf_t* elements.
+ * Store the resulting current in in *NEW_CURRENT_LINE. */
+static svn_error_t *
+read_diff_chunk(apr_array_header_t **lines,
+ svn_linenum_t *new_current_line,
+ apr_file_t *file,
+ svn_linenum_t current_line,
+ apr_off_t start,
+ apr_off_t len,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *line;
+ const char *eol_str;
+ svn_boolean_t eof;
+ apr_pool_t *iterpool;
+
+ *lines = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
+
+ /* Skip lines before start of range. */
+ iterpool = svn_pool_create(scratch_pool);
+ while (current_line < start)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_io_file_readline(file, &line, NULL, &eof, APR_SIZE_MAX,
+ iterpool, iterpool));
+ if (eof)
+ return SVN_NO_ERROR;
+ current_line++;
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Now read the lines. */
+ do
+ {
+ SVN_ERR(svn_io_file_readline(file, &line, &eol_str, &eof, APR_SIZE_MAX,
+ result_pool, scratch_pool));
+ if (eol_str)
+ svn_stringbuf_appendcstr(line, eol_str);
+ APR_ARRAY_PUSH(*lines, svn_stringbuf_t *) = line;
+ if (eof)
+ break;
+ current_line++;
+ }
+ while ((*lines)->nelts < len);
+
+ *new_current_line = current_line;
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the terminal width in number of columns. */
+static int
+get_term_width(void)
+{
+ char *columns_env;
+#ifdef TIOCGWINSZ
+ int fd;
+
+ fd = open("/dev/tty", O_RDONLY, 0);
+ if (fd != -1)
+ {
+ struct winsize ws;
+ int error;
+
+ error = ioctl(fd, TIOCGWINSZ, &ws);
+ close(fd);
+ if (error != -1)
+ {
+ if (ws.ws_col < 80)
+ return 80;
+ return ws.ws_col;
+ }
+ }
+#elif defined WIN32
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
+ {
+ if (csbi.dwSize.X < 80)
+ return 80;
+ return csbi.dwSize.X;
+ }
+#endif
+
+ columns_env = getenv("COLUMNS");
+ if (columns_env)
+ {
+ svn_error_t *err;
+ int cols;
+
+ err = svn_cstring_atoi(&cols, columns_env);
+ if (err)
+ {
+ svn_error_clear(err);
+ return 80;
+ }
+
+ if (cols < 80)
+ return 80;
+ return cols;
+ }
+ else
+ return 80;
+}
+
+#define LINE_DISPLAY_WIDTH ((get_term_width() / 2) - 2)
+
+/* Prepare LINE for display, pruning or extending it to an appropriate
+ * display width, and stripping the EOL marker, if any.
+ * This function assumes that the data in LINE is encoded in UTF-8. */
+static const char *
+prepare_line_for_display(const char *line, apr_pool_t *pool)
+{
+ svn_stringbuf_t *buf = svn_stringbuf_create(line, pool);
+ size_t width;
+ size_t line_width = LINE_DISPLAY_WIDTH;
+ apr_pool_t *iterpool;
+
+ /* Trim EOL. */
+ if (buf->len >= 2 &&
+ buf->data[buf->len - 2] == '\r' &&
+ buf->data[buf->len - 1] == '\n')
+ svn_stringbuf_chop(buf, 2);
+ else if (buf->len >= 1 &&
+ (buf->data[buf->len - 1] == '\n' ||
+ buf->data[buf->len - 1] == '\r'))
+ svn_stringbuf_chop(buf, 1);
+
+ /* Determine the on-screen width of the line. */
+ width = svn_utf_cstring_utf8_width(buf->data);
+ if (width == -1)
+ {
+ /* Determining the width failed. Try to get rid of unprintable
+ * characters in the line buffer. */
+ buf = svn_stringbuf_create(svn_xml_fuzzy_escape(buf->data, pool), pool);
+ width = svn_utf_cstring_utf8_width(buf->data);
+ if (width == -1)
+ width = buf->len; /* fallback: buffer length */
+ }
+
+ /* Trim further in case line is still too long, or add padding in case
+ * it is too short. */
+ iterpool = svn_pool_create(pool);
+ while (width > line_width)
+ {
+ const char *last_valid;
+
+ svn_pool_clear(iterpool);
+
+ svn_stringbuf_chop(buf, 1);
+
+ /* Be careful not to invalidate the UTF-8 string by trimming
+ * just part of a character. */
+ last_valid = svn_utf__last_valid(buf->data, buf->len);
+ if (last_valid < buf->data + buf->len)
+ svn_stringbuf_chop(buf, (buf->data + buf->len) - last_valid);
+
+ width = svn_utf_cstring_utf8_width(buf->data);
+ if (width == -1)
+ width = buf->len; /* fallback: buffer length */
+ }
+ svn_pool_destroy(iterpool);
+
+ while (width == 0 || width < line_width)
+ {
+ svn_stringbuf_appendbyte(buf, ' ');
+ width++;
+ }
+
+ SVN_ERR_ASSERT_NO_RETURN(width == line_width);
+ return buf->data;
+}
+
+/* Merge CHUNK1 and CHUNK2 into a new chunk with conflict markers. */
+static apr_array_header_t *
+merge_chunks_with_conflict_markers(apr_array_header_t *chunk1,
+ apr_array_header_t *chunk2,
+ apr_pool_t *result_pool)
+{
+ apr_array_header_t *merged_chunk;
+ int i;
+
+ merged_chunk = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
+ /* ### would be nice to show filenames next to conflict markers */
+ APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
+ svn_stringbuf_create("<<<<<<<\n", result_pool);
+ for (i = 0; i < chunk1->nelts; i++)
+ {
+ APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
+ APR_ARRAY_IDX(chunk1, i, svn_stringbuf_t*);
+ }
+ APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
+ svn_stringbuf_create("=======\n", result_pool);
+ for (i = 0; i < chunk2->nelts; i++)
+ {
+ APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
+ APR_ARRAY_IDX(chunk2, i, svn_stringbuf_t*);
+ }
+ APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
+ svn_stringbuf_create(">>>>>>>\n", result_pool);
+
+ return merged_chunk;
+}
+
+/* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */
+static svn_error_t *
+edit_chunk(apr_array_header_t **merged_chunk,
+ apr_array_header_t *chunk,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *temp_file;
+ const char *temp_file_name;
+ int i;
+ apr_off_t pos;
+ svn_boolean_t eof;
+ svn_error_t *err;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_io_open_unique_file3(&temp_file, &temp_file_name, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < chunk->nelts; i++)
+ {
+ svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *);
+ apr_size_t bytes_written;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_io_file_write_full(temp_file, line->data, line->len,
+ &bytes_written, iterpool));
+ if (line->len != bytes_written)
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
+ _("Could not write data to temporary file"));
+ }
+ SVN_ERR(svn_io_file_flush_to_disk(temp_file, scratch_pool));
+
+ err = svn_cmdline__edit_file_externally(temp_file_name, editor_cmd,
+ config, scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
+ {
+ svn_error_t *root_err = svn_error_root_cause(err);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ root_err->message ? root_err->message :
+ _("No editor found.")));
+ svn_error_clear(err);
+ *merged_chunk = NULL;
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+ else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
+ {
+ svn_error_t *root_err = svn_error_root_cause(err);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ root_err->message ? root_err->message :
+ _("Error running editor.")));
+ svn_error_clear(err);
+ *merged_chunk = NULL;
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *));
+ pos = 0;
+ SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool));
+ do
+ {
+ svn_stringbuf_t *line;
+ const char *eol_str;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof,
+ APR_SIZE_MAX, result_pool, iterpool));
+ if (eol_str)
+ svn_stringbuf_appendcstr(line, eol_str);
+
+ APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line;
+ }
+ while (!eof);
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_io_file_close(temp_file, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Create a separator string of the appropriate length. */
+static const char *
+get_sep_string(apr_pool_t *result_pool)
+{
+ int line_width = LINE_DISPLAY_WIDTH;
+ int i;
+ svn_stringbuf_t *buf;
+
+ buf = svn_stringbuf_create_empty(result_pool);
+ for (i = 0; i < line_width; i++)
+ svn_stringbuf_appendbyte(buf, '-');
+ svn_stringbuf_appendbyte(buf, '+');
+ for (i = 0; i < line_width; i++)
+ svn_stringbuf_appendbyte(buf, '-');
+ svn_stringbuf_appendbyte(buf, '\n');
+
+ return buf->data;
+}
+
+/* Merge chunks CHUNK1 and CHUNK2.
+ * Each lines array contains elements of type svn_stringbuf_t*.
+ * Return the result in *MERGED_CHUNK, or set *MERGED_CHUNK to NULL in
+ * case the user chooses to postpone resolution of this chunk.
+ * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */
+static svn_error_t *
+merge_chunks(apr_array_header_t **merged_chunk,
+ svn_boolean_t *abort_merge,
+ apr_array_header_t *chunk1,
+ apr_array_header_t *chunk2,
+ svn_linenum_t current_line1,
+ svn_linenum_t current_line2,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *prompt;
+ int i;
+ int max_chunk_lines;
+ apr_pool_t *iterpool;
+
+ max_chunk_lines = chunk1->nelts > chunk2->nelts ? chunk1->nelts
+ : chunk2->nelts;
+ *abort_merge = FALSE;
+
+ /*
+ * Prepare the selection prompt.
+ */
+
+ prompt = svn_stringbuf_create(
+ apr_psprintf(scratch_pool, "%s\n%s|%s\n%s",
+ _("Conflicting section found during merge:"),
+ prepare_line_for_display(
+ apr_psprintf(scratch_pool,
+ _("(1) their version (at line %lu)"),
+ current_line1),
+ scratch_pool),
+ prepare_line_for_display(
+ apr_psprintf(scratch_pool,
+ _("(2) your version (at line %lu)"),
+ current_line2),
+ scratch_pool),
+ get_sep_string(scratch_pool)),
+ scratch_pool);
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < max_chunk_lines; i++)
+ {
+ const char *line1;
+ const char *line2;
+ const char *prompt_line;
+
+ svn_pool_clear(iterpool);
+
+ if (i < chunk1->nelts)
+ {
+ svn_stringbuf_t *line_utf8;
+
+ SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
+ APR_ARRAY_IDX(chunk1, i,
+ svn_stringbuf_t*),
+ iterpool));
+ line1 = prepare_line_for_display(line_utf8->data, iterpool);
+ }
+ else
+ line1 = prepare_line_for_display("", iterpool);
+
+ if (i < chunk2->nelts)
+ {
+ svn_stringbuf_t *line_utf8;
+
+ SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
+ APR_ARRAY_IDX(chunk2, i,
+ svn_stringbuf_t*),
+ iterpool));
+ line2 = prepare_line_for_display(line_utf8->data, iterpool);
+ }
+ else
+ line2 = prepare_line_for_display("", iterpool);
+
+ prompt_line = apr_psprintf(iterpool, "%s|%s\n", line1, line2);
+
+ svn_stringbuf_appendcstr(prompt, prompt_line);
+ }
+
+ svn_stringbuf_appendcstr(prompt, get_sep_string(scratch_pool));
+ svn_stringbuf_appendcstr(
+ prompt,
+ _("Select: (1) use their version, (2) use your version,\n"
+ " (e1) edit their version and use the result,\n"
+ " (e2) edit your version and use the result,\n"
+ " (eb) edit both versions and use the result,\n"
+ " (p) postpone this conflicting section leaving conflict markers,\n"
+ " (a) abort file merge and return to main menu: "));
+
+ /* Now let's see what the user wants to do with this conflict. */
+ while (TRUE)
+ {
+ const char *answer;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt->data, NULL, iterpool));
+ if (strcmp(answer, "1") == 0)
+ {
+ *merged_chunk = chunk1;
+ break;
+ }
+ else if (strcmp(answer, "2") == 0)
+ {
+ *merged_chunk = chunk2;
+ break;
+ }
+ else if (strcmp(answer, "p") == 0)
+ {
+ *merged_chunk = NULL;
+ break;
+ }
+ else if (strcmp(answer, "e1") == 0)
+ {
+ SVN_ERR(edit_chunk(merged_chunk, chunk1, editor_cmd, config,
+ result_pool, iterpool));
+ if (*merged_chunk)
+ break;
+ }
+ else if (strcmp(answer, "e2") == 0)
+ {
+ SVN_ERR(edit_chunk(merged_chunk, chunk2, editor_cmd, config,
+ result_pool, iterpool));
+ if (*merged_chunk)
+ break;
+ }
+ else if (strcmp(answer, "eb") == 0)
+ {
+ apr_array_header_t *conflict_chunk;
+
+ conflict_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
+ scratch_pool);
+ SVN_ERR(edit_chunk(merged_chunk, conflict_chunk, editor_cmd, config,
+ result_pool, iterpool));
+ if (*merged_chunk)
+ break;
+ }
+ else if (strcmp(answer, "a") == 0)
+ {
+ *abort_merge = TRUE;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Perform a merge of chunks from FILE1 and FILE2, specified by START1/LEN1
+ * and START2/LEN2, respectively. Append the result to MERGED_FILE.
+ * The current line numbers for FILE1 and FILE2 are passed in *CURRENT_LINE1
+ * and *CURRENT_LINE2, and will be updated to new values upon return.
+ * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */
+static svn_error_t *
+merge_file_chunks(svn_boolean_t *remains_in_conflict,
+ svn_boolean_t *abort_merge,
+ apr_file_t *merged_file,
+ apr_file_t *file1,
+ apr_file_t *file2,
+ apr_off_t start1,
+ apr_off_t len1,
+ apr_off_t start2,
+ apr_off_t len2,
+ svn_linenum_t *current_line1,
+ svn_linenum_t *current_line2,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *chunk1;
+ apr_array_header_t *chunk2;
+ apr_array_header_t *merged_chunk;
+ apr_pool_t *iterpool;
+ int i;
+
+ SVN_ERR(read_diff_chunk(&chunk1, current_line1, file1, *current_line1,
+ start1, len1, scratch_pool, scratch_pool));
+ SVN_ERR(read_diff_chunk(&chunk2, current_line2, file2, *current_line2,
+ start2, len2, scratch_pool, scratch_pool));
+
+ SVN_ERR(merge_chunks(&merged_chunk, abort_merge, chunk1, chunk2,
+ *current_line1, *current_line2,
+ editor_cmd, config,
+ scratch_pool, scratch_pool));
+
+ if (*abort_merge)
+ return SVN_NO_ERROR;
+
+ /* If the user chose 'postpone' put conflict markers and left/right
+ * versions into the merged file. */
+ if (merged_chunk == NULL)
+ {
+ *remains_in_conflict = TRUE;
+ merged_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
+ scratch_pool);
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < merged_chunk->nelts; i++)
+ {
+ apr_size_t bytes_written;
+ svn_stringbuf_t *line = APR_ARRAY_IDX(merged_chunk, i,
+ svn_stringbuf_t *);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len,
+ &bytes_written, iterpool));
+ if (line->len != bytes_written)
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
+ _("Could not write data to merged file"));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Original, modified, and latest all differ from one another.
+ * This is a conflict and we'll need to ask the user to merge it. */
+static svn_error_t *
+file_merge_output_conflict(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length,
+ svn_diff_t *resolved_diff)
+{
+ struct file_merge_baton *b = output_baton;
+
+ if (b->abort_merge)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(merge_file_chunks(&b->remains_in_conflict,
+ &b->abort_merge,
+ b->merged_file,
+ b->modified_file,
+ b->latest_file,
+ modified_start,
+ modified_length,
+ latest_start,
+ latest_length,
+ &b->current_line_modified,
+ &b->current_line_latest,
+ b->editor_cmd,
+ b->config,
+ b->scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Our collection of diff output functions that get driven during the merge. */
+static svn_diff_output_fns_t file_merge_diff_output_fns = {
+ file_merge_output_common,
+ file_merge_output_diff_modified,
+ file_merge_output_diff_latest,
+ file_merge_output_diff_common,
+ file_merge_output_conflict
+};
+
+svn_error_t *
+svn_cl__merge_file(const char *base_path,
+ const char *their_path,
+ const char *my_path,
+ const char *merged_path,
+ const char *wc_path,
+ const char *path_prefix,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ svn_boolean_t *remains_in_conflict,
+ apr_pool_t *scratch_pool)
+{
+ svn_diff_t *diff;
+ svn_diff_file_options_t *diff_options;
+ apr_file_t *original_file;
+ apr_file_t *modified_file;
+ apr_file_t *latest_file;
+ apr_file_t *merged_file;
+ const char *merged_file_name;
+ struct file_merge_baton fmb;
+ svn_boolean_t executable;
+ const char *merged_path_local_style;
+ const char *merged_rel_path;
+ const char *wc_path_local_style;
+ const char *wc_rel_path = svn_dirent_skip_ancestor(path_prefix, wc_path);
+
+ /* PATH_PREFIX may not be an ancestor of WC_PATH, just use the
+ full WC_PATH in that case. */
+ if (wc_rel_path)
+ wc_path_local_style = svn_dirent_local_style(wc_rel_path, scratch_pool);
+ else
+ wc_path_local_style = svn_dirent_local_style(wc_path, scratch_pool);
+
+ SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merging '%s'.\n"),
+ wc_path_local_style));
+
+ SVN_ERR(svn_io_file_open(&original_file, base_path,
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, scratch_pool));
+ SVN_ERR(svn_io_file_open(&modified_file, their_path,
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, scratch_pool));
+ SVN_ERR(svn_io_file_open(&latest_file, my_path,
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, scratch_pool));
+ SVN_ERR(svn_io_open_unique_file3(&merged_file, &merged_file_name,
+ NULL, svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+
+ diff_options = svn_diff_file_options_create(scratch_pool);
+ SVN_ERR(svn_diff_file_diff3_2(&diff, base_path, their_path, my_path,
+ diff_options, scratch_pool));
+
+ fmb.original_file = original_file;
+ fmb.modified_file = modified_file;
+ fmb.latest_file = latest_file;
+ fmb.current_line_original = 0;
+ fmb.current_line_modified = 0;
+ fmb.current_line_latest = 0;
+ fmb.merged_file = merged_file;
+ fmb.remains_in_conflict = FALSE;
+ fmb.editor_cmd = editor_cmd;
+ fmb.config = config;
+ fmb.abort_merge = FALSE;
+ fmb.scratch_pool = scratch_pool;
+
+ SVN_ERR(svn_diff_output(diff, &fmb, &file_merge_diff_output_fns));
+
+ SVN_ERR(svn_io_file_close(original_file, scratch_pool));
+ SVN_ERR(svn_io_file_close(modified_file, scratch_pool));
+ SVN_ERR(svn_io_file_close(latest_file, scratch_pool));
+ SVN_ERR(svn_io_file_close(merged_file, scratch_pool));
+
+ /* Start out assuming that conflicts remain. */
+ if (remains_in_conflict)
+ *remains_in_conflict = TRUE;
+
+ if (fmb.abort_merge)
+ {
+ SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
+ SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merge of '%s' aborted.\n"),
+ wc_path_local_style));
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool));
+
+ merged_rel_path = svn_dirent_skip_ancestor(path_prefix, merged_path);
+ if (merged_rel_path)
+ merged_path_local_style = svn_dirent_local_style(merged_rel_path,
+ scratch_pool);
+ else
+ merged_path_local_style = svn_dirent_local_style(merged_path,
+ scratch_pool);
+
+ SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE,
+ scratch_pool),
+ apr_psprintf(scratch_pool,
+ _("Could not write merged result to '%s', saved "
+ "instead at '%s'.\n'%s' remains in conflict.\n"),
+ merged_path_local_style,
+ svn_dirent_local_style(merged_file_name,
+ scratch_pool),
+ wc_path_local_style));
+ SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE,
+ scratch_pool));
+ SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
+
+ /* The merge was not aborted and we could install the merged result. The
+ * file remains in conflict unless all conflicting sections were resolved. */
+ if (remains_in_conflict)
+ *remains_in_conflict = fmb.remains_in_conflict;
+
+ if (fmb.remains_in_conflict)
+ SVN_ERR(svn_cmdline_printf(
+ scratch_pool,
+ _("Merge of '%s' completed (remains in conflict).\n"),
+ wc_path_local_style));
+ else
+ SVN_ERR(svn_cmdline_printf(
+ scratch_pool, _("Merge of '%s' completed.\n"),
+ wc_path_local_style));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/help-cmd.c b/contrib/subversion/subversion/svn/help-cmd.c
new file mode 100644
index 0000000..93fecc3
--- /dev/null
+++ b/contrib/subversion/subversion/svn/help-cmd.c
@@ -0,0 +1,153 @@
+/*
+ * help-cmd.c -- Provide help
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_hash.h"
+#include "svn_string.h"
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_version.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__help(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = NULL;
+ svn_stringbuf_t *version_footer = NULL;
+
+ /* xgettext: the %s is for SVN_VER_NUMBER. */
+ char help_header_template[] =
+ N_("usage: svn <subcommand> [options] [args]\n"
+ "Subversion command-line client, version %s.\n"
+ "Type 'svn help <subcommand>' for help on a specific subcommand.\n"
+ "Type 'svn --version' to see the program version and RA modules\n"
+ " or 'svn --version --quiet' to see just the version number.\n"
+ "\n"
+ "Most subcommands take file and/or directory arguments, recursing\n"
+ "on the directories. If no arguments are supplied to such a\n"
+ "command, it recurses on the current directory (inclusive) by default.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ char help_footer[] =
+ N_("Subversion is a tool for version control.\n"
+ "For additional information, see http://subversion.apache.org/\n");
+
+ char *help_header =
+ apr_psprintf(pool, _(help_header_template), SVN_VER_NUMBER);
+
+ const char *ra_desc_start
+ = _("The following repository access (RA) modules are available:\n\n");
+
+ if (baton)
+ {
+ svn_cl__cmd_baton_t *const cmd_baton = baton;
+#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
+ /* Windows never actually stores plaintext passwords, it
+ encrypts the contents using CryptoAPI. ...
+
+ ... If CryptoAPI is available ... but it should be on all
+ versions of Windows that are even remotely interesting two
+ days before the scheduled end of the world, when this comment
+ is being written. */
+# ifndef WIN32
+ svn_boolean_t store_auth_creds =
+ SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS;
+ svn_boolean_t store_passwords =
+ SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS;
+ svn_boolean_t store_plaintext_passwords = FALSE;
+ svn_config_t *cfg;
+
+ if (cmd_baton->ctx->config)
+ {
+ cfg = svn_hash_gets(cmd_baton->ctx->config,
+ SVN_CONFIG_CATEGORY_CONFIG);
+ if (cfg)
+ {
+ SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
+ store_auth_creds));
+ SVN_ERR(svn_config_get_bool(cfg, &store_passwords,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_STORE_PASSWORDS,
+ store_passwords));
+ }
+ cfg = svn_hash_gets(cmd_baton->ctx->config,
+ SVN_CONFIG_CATEGORY_SERVERS);
+ if (cfg)
+ {
+ const char *value;
+ SVN_ERR(svn_config_get_yes_no_ask
+ (cfg, &value,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_STORE_PLAINTEXT_PASSWORDS,
+ SVN_CONFIG_DEFAULT_OPTION_STORE_PLAINTEXT_PASSWORDS));
+ if (0 == svn_cstring_casecmp(value, SVN_CONFIG_TRUE))
+ store_plaintext_passwords = TRUE;
+ }
+ }
+
+ if (store_plaintext_passwords && store_auth_creds && store_passwords)
+ {
+ version_footer = svn_stringbuf_create(
+ _("WARNING: Plaintext password storage is enabled!\n\n"),
+ pool);
+ svn_stringbuf_appendcstr(version_footer, ra_desc_start);
+ }
+# endif /* !WIN32 */
+#endif /* !SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE */
+
+ opt_state = cmd_baton->opt_state;
+ }
+
+ if (!version_footer)
+ version_footer = svn_stringbuf_create(ra_desc_start, pool);
+ SVN_ERR(svn_ra_print_modules(version_footer, pool));
+
+ return svn_opt_print_help4(os,
+ "svn", /* ### erm, derive somehow? */
+ opt_state ? opt_state->version : FALSE,
+ opt_state ? opt_state->quiet : FALSE,
+ opt_state ? opt_state->verbose : FALSE,
+ version_footer->data,
+ help_header, /* already gettext()'d */
+ svn_cl__cmd_table,
+ svn_cl__options,
+ svn_cl__global_options,
+ _(help_footer),
+ pool);
+}
diff --git a/contrib/subversion/subversion/svn/import-cmd.c b/contrib/subversion/subversion/svn/import-cmd.c
new file mode 100644
index 0000000..6fe5af6
--- /dev/null
+++ b/contrib/subversion/subversion/svn/import-cmd.c
@@ -0,0 +1,132 @@
+/*
+ * import-cmd.c -- Import a file or tree into the repository.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__import(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ const char *path;
+ const char *url;
+
+ /* Import takes two arguments, for example
+ *
+ * $ svn import projects/test file:///home/jrandom/repos/trunk
+ * ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * (source) (repository)
+ *
+ * or
+ *
+ * $ svn import file:///home/jrandom/repos/some/subdir
+ *
+ * What is the nicest behavior for import, from the user's point of
+ * view? This is a subtle question. Seemingly intuitive answers
+ * can lead to weird situations, such never being able to create
+ * non-directories in the top-level of the repository.
+ *
+ * If 'source' is a file then the basename of 'url' is used as the
+ * filename in the repository. If 'source' is a directory then the
+ * import happens directly in the repository target dir, creating
+ * however many new entries are necessary. If some part of 'url'
+ * does not exist in the repository then parent directories are created
+ * as necessary.
+ *
+ * In the case where no 'source' is given '.' (the current directory)
+ * is implied.
+ *
+ * ### kff todo: review above behaviors.
+ */
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (targets->nelts < 1)
+ return svn_error_create
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Repository URL required when importing"));
+ else if (targets->nelts > 2)
+ return svn_error_create
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments to import command"));
+ else if (targets->nelts == 1)
+ {
+ url = APR_ARRAY_IDX(targets, 0, const char *);
+ path = "";
+ }
+ else
+ {
+ path = APR_ARRAY_IDX(targets, 0, const char *);
+ url = APR_ARRAY_IDX(targets, 1, const char *);
+ }
+
+ SVN_ERR(svn_cl__check_target_is_local_path(path));
+
+ if (! svn_path_is_url(url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid URL '%s'"), url);
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_infinity;
+
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
+ NULL, ctx->config, pool));
+
+ SVN_ERR(svn_cl__cleanup_log_msg
+ (ctx->log_msg_baton3,
+ svn_client_import5(path,
+ url,
+ opt_state->depth,
+ opt_state->no_ignore,
+ opt_state->no_autoprops,
+ opt_state->force,
+ opt_state->revprop_table,
+ NULL, NULL, /* filter callback / baton */
+ (opt_state->quiet
+ ? NULL : svn_cl__print_commit_info),
+ NULL,
+ ctx,
+ pool), pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/info-cmd.c b/contrib/subversion/subversion/svn/info-cmd.c
new file mode 100644
index 0000000..56833f6
--- /dev/null
+++ b/contrib/subversion/subversion/svn/info-cmd.c
@@ -0,0 +1,683 @@
+/*
+ * info-cmd.c -- Display information about a resource
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_string.h"
+#include "svn_cmdline.h"
+#include "svn_wc.h"
+#include "svn_pools.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_time.h"
+#include "svn_xml.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "cl-conflicts.h"
+
+
+/*** Code. ***/
+
+static svn_error_t *
+svn_cl__info_print_time(apr_time_t atime,
+ const char *desc,
+ apr_pool_t *pool)
+{
+ const char *time_utf8;
+
+ time_utf8 = svn_time_to_human_cstring(atime, pool);
+ return svn_cmdline_printf(pool, "%s: %s\n", desc, time_utf8);
+}
+
+
+/* Return string representation of SCHEDULE */
+static const char *
+schedule_str(svn_wc_schedule_t schedule)
+{
+ switch (schedule)
+ {
+ case svn_wc_schedule_normal:
+ return "normal";
+ case svn_wc_schedule_add:
+ return "add";
+ case svn_wc_schedule_delete:
+ return "delete";
+ case svn_wc_schedule_replace:
+ return "replace";
+ default:
+ return "none";
+ }
+}
+
+
+/* A callback of type svn_client_info_receiver2_t.
+ Prints svn info in xml mode to standard out */
+static svn_error_t *
+print_info_xml(void *baton,
+ const char *target,
+ const svn_client_info2_t *info,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+ const char *rev_str;
+ const char *path_prefix = baton;
+
+ if (SVN_IS_VALID_REVNUM(info->rev))
+ rev_str = apr_psprintf(pool, "%ld", info->rev);
+ else
+ rev_str = apr_pstrdup(pool, _("Resource is not under version control."));
+
+ /* "<entry ...>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
+ "path", svn_cl__local_style_skip_ancestor(
+ path_prefix, target, pool),
+ "kind", svn_cl__node_kind_str_xml(info->kind),
+ "revision", rev_str,
+ NULL);
+
+ /* "<url> xx </url>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL);
+
+ if (info->repos_root_URL && info->URL)
+ {
+ /* "<relative-url> xx </relative-url>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "relative-url",
+ apr_pstrcat(pool, "^/",
+ svn_path_uri_encode(
+ svn_uri_skip_ancestor(
+ info->repos_root_URL,
+ info->URL, pool),
+ pool),
+ NULL));
+ }
+
+ if (info->repos_root_URL || info->repos_UUID)
+ {
+ /* "<repository>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repository", NULL);
+
+ /* "<root> xx </root>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "root", info->repos_root_URL);
+
+ /* "<uuid> xx </uuid>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "uuid", info->repos_UUID);
+
+ /* "</repository>" */
+ svn_xml_make_close_tag(&sb, pool, "repository");
+ }
+
+ if (info->wc_info)
+ {
+ /* "<wc-info>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "wc-info", NULL);
+
+ /* "<wcroot-abspath> xx </wcroot-abspath>" */
+ if (info->wc_info->wcroot_abspath)
+ svn_cl__xml_tagged_cdata(&sb, pool, "wcroot-abspath",
+ info->wc_info->wcroot_abspath);
+
+ /* "<schedule> xx </schedule>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "schedule",
+ schedule_str(info->wc_info->schedule));
+
+ /* "<depth> xx </depth>" */
+ {
+ svn_depth_t depth = info->wc_info->depth;
+
+ /* In the entries world info just passed depth infinity for files */
+ if (depth == svn_depth_unknown && info->kind == svn_node_file)
+ depth = svn_depth_infinity;
+
+ svn_cl__xml_tagged_cdata(&sb, pool, "depth", svn_depth_to_word(depth));
+ }
+
+ /* "<copy-from-url> xx </copy-from-url>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-url",
+ info->wc_info->copyfrom_url);
+
+ /* "<copy-from-rev> xx </copy-from-rev>" */
+ if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
+ svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-rev",
+ apr_psprintf(pool, "%ld",
+ info->wc_info->copyfrom_rev));
+
+ /* "<text-updated> xx </text-updated>" */
+ if (info->wc_info->recorded_time)
+ svn_cl__xml_tagged_cdata(&sb, pool, "text-updated",
+ svn_time_to_cstring(
+ info->wc_info->recorded_time,
+ pool));
+
+ /* "<checksum> xx </checksum>" */
+ /* ### Print the checksum kind. */
+ svn_cl__xml_tagged_cdata(&sb, pool, "checksum",
+ svn_checksum_to_cstring(info->wc_info->checksum,
+ pool));
+
+ if (info->wc_info->changelist)
+ /* "<changelist> xx </changelist>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "changelist",
+ info->wc_info->changelist);
+
+ if (info->wc_info->moved_from_abspath)
+ {
+ const char *relpath;
+
+ relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
+ info->wc_info->moved_from_abspath);
+
+ /* <moved-from> xx </moved-from> */
+ if (relpath && relpath[0] != '\0')
+ svn_cl__xml_tagged_cdata(&sb, pool, "moved-from", relpath);
+ else
+ svn_cl__xml_tagged_cdata(&sb, pool, "moved-from",
+ info->wc_info->moved_from_abspath);
+ }
+
+ if (info->wc_info->moved_to_abspath)
+ {
+ const char *relpath;
+
+ relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
+ info->wc_info->moved_to_abspath);
+ /* <moved-to> xx </moved-to> */
+ if (relpath && relpath[0] != '\0')
+ svn_cl__xml_tagged_cdata(&sb, pool, "moved-to", relpath);
+ else
+ svn_cl__xml_tagged_cdata(&sb, pool, "moved-to",
+ info->wc_info->moved_to_abspath);
+ }
+
+ /* "</wc-info>" */
+ svn_xml_make_close_tag(&sb, pool, "wc-info");
+ }
+
+ if (info->last_changed_author
+ || SVN_IS_VALID_REVNUM(info->last_changed_rev)
+ || info->last_changed_date)
+ {
+ svn_cl__print_xml_commit(&sb, info->last_changed_rev,
+ info->last_changed_author,
+ svn_time_to_cstring(info->last_changed_date,
+ pool),
+ pool);
+ }
+
+ if (info->wc_info && info->wc_info->conflicts)
+ {
+ int i;
+
+ for (i = 0; i < info->wc_info->conflicts->nelts; i++)
+ {
+ const svn_wc_conflict_description2_t *conflict =
+ APR_ARRAY_IDX(info->wc_info->conflicts, i,
+ const svn_wc_conflict_description2_t *);
+
+ SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, pool));
+ }
+ }
+
+ if (info->lock)
+ svn_cl__print_xml_lock(&sb, info->lock, pool);
+
+ /* "</entry>" */
+ svn_xml_make_close_tag(&sb, pool, "entry");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* A callback of type svn_client_info_receiver2_t. */
+static svn_error_t *
+print_info(void *baton,
+ const char *target,
+ const svn_client_info2_t *info,
+ apr_pool_t *pool)
+{
+ const char *path_prefix = baton;
+
+ SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, target, pool)));
+
+ /* ### remove this someday: it's only here for cmdline output
+ compatibility with svn 1.1 and older. */
+ if (info->kind != svn_node_dir)
+ SVN_ERR(svn_cmdline_printf(pool, _("Name: %s\n"),
+ svn_dirent_basename(target, pool)));
+
+ if (info->wc_info && info->wc_info->wcroot_abspath)
+ SVN_ERR(svn_cmdline_printf(pool, _("Working Copy Root Path: %s\n"),
+ svn_dirent_local_style(
+ info->wc_info->wcroot_abspath,
+ pool)));
+
+ if (info->URL)
+ SVN_ERR(svn_cmdline_printf(pool, _("URL: %s\n"), info->URL));
+
+ if (info->URL && info->repos_root_URL)
+ SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: ^/%s\n"),
+ svn_path_uri_encode(
+ svn_uri_skip_ancestor(info->repos_root_URL,
+ info->URL, pool),
+ pool)));
+
+ if (info->repos_root_URL)
+ SVN_ERR(svn_cmdline_printf(pool, _("Repository Root: %s\n"),
+ info->repos_root_URL));
+
+ if (info->repos_UUID)
+ SVN_ERR(svn_cmdline_printf(pool, _("Repository UUID: %s\n"),
+ info->repos_UUID));
+
+ if (SVN_IS_VALID_REVNUM(info->rev))
+ SVN_ERR(svn_cmdline_printf(pool, _("Revision: %ld\n"), info->rev));
+
+ switch (info->kind)
+ {
+ case svn_node_file:
+ SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: file\n")));
+ break;
+
+ case svn_node_dir:
+ SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: directory\n")));
+ break;
+
+ case svn_node_none:
+ SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: none\n")));
+ break;
+
+ case svn_node_unknown:
+ default:
+ SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: unknown\n")));
+ break;
+ }
+
+ if (info->wc_info)
+ {
+ switch (info->wc_info->schedule)
+ {
+ case svn_wc_schedule_normal:
+ SVN_ERR(svn_cmdline_printf(pool, _("Schedule: normal\n")));
+ break;
+
+ case svn_wc_schedule_add:
+ SVN_ERR(svn_cmdline_printf(pool, _("Schedule: add\n")));
+ break;
+
+ case svn_wc_schedule_delete:
+ SVN_ERR(svn_cmdline_printf(pool, _("Schedule: delete\n")));
+ break;
+
+ case svn_wc_schedule_replace:
+ SVN_ERR(svn_cmdline_printf(pool, _("Schedule: replace\n")));
+ break;
+
+ default:
+ break;
+ }
+
+ switch (info->wc_info->depth)
+ {
+ case svn_depth_unknown:
+ /* Unknown depth is the norm for remote directories anyway
+ (although infinity would be equally appropriate). Let's
+ not bother to print it. */
+ break;
+
+ case svn_depth_empty:
+ SVN_ERR(svn_cmdline_printf(pool, _("Depth: empty\n")));
+ break;
+
+ case svn_depth_files:
+ SVN_ERR(svn_cmdline_printf(pool, _("Depth: files\n")));
+ break;
+
+ case svn_depth_immediates:
+ SVN_ERR(svn_cmdline_printf(pool, _("Depth: immediates\n")));
+ break;
+
+ case svn_depth_exclude:
+ SVN_ERR(svn_cmdline_printf(pool, _("Depth: exclude\n")));
+ break;
+
+ case svn_depth_infinity:
+ /* Infinity is the default depth for working copy
+ directories. Let's not print it, it's not special enough
+ to be worth mentioning. */
+ break;
+
+ default:
+ /* Other depths should never happen here. */
+ SVN_ERR(svn_cmdline_printf(pool, _("Depth: INVALID\n")));
+ }
+
+ if (info->wc_info->copyfrom_url)
+ SVN_ERR(svn_cmdline_printf(pool, _("Copied From URL: %s\n"),
+ info->wc_info->copyfrom_url));
+
+ if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
+ SVN_ERR(svn_cmdline_printf(pool, _("Copied From Rev: %ld\n"),
+ info->wc_info->copyfrom_rev));
+ if (info->wc_info->moved_from_abspath)
+ {
+ const char *relpath;
+
+ relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
+ info->wc_info->moved_from_abspath);
+ if (relpath && relpath[0] != '\0')
+ SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"), relpath));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"),
+ info->wc_info->moved_from_abspath));
+ }
+
+ if (info->wc_info->moved_to_abspath)
+ {
+ const char *relpath;
+
+ relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
+ info->wc_info->moved_to_abspath);
+ if (relpath && relpath[0] != '\0')
+ SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"), relpath));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"),
+ info->wc_info->moved_to_abspath));
+ }
+ }
+
+ if (info->last_changed_author)
+ SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Author: %s\n"),
+ info->last_changed_author));
+
+ if (SVN_IS_VALID_REVNUM(info->last_changed_rev))
+ SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Rev: %ld\n"),
+ info->last_changed_rev));
+
+ if (info->last_changed_date)
+ SVN_ERR(svn_cl__info_print_time(info->last_changed_date,
+ _("Last Changed Date"), pool));
+
+ if (info->wc_info)
+ {
+ if (info->wc_info->recorded_time)
+ SVN_ERR(svn_cl__info_print_time(info->wc_info->recorded_time,
+ _("Text Last Updated"), pool));
+
+ if (info->wc_info->checksum)
+ SVN_ERR(svn_cmdline_printf(pool, _("Checksum: %s\n"),
+ svn_checksum_to_cstring(
+ info->wc_info->checksum, pool)));
+
+ if (info->wc_info->conflicts)
+ {
+ svn_boolean_t printed_prop_conflict_file = FALSE;
+ int i;
+
+ for (i = 0; i < info->wc_info->conflicts->nelts; i++)
+ {
+ const svn_wc_conflict_description2_t *conflict =
+ APR_ARRAY_IDX(info->wc_info->conflicts, i,
+ const svn_wc_conflict_description2_t *);
+ const char *desc;
+
+ switch (conflict->kind)
+ {
+ case svn_wc_conflict_kind_text:
+ if (conflict->base_abspath)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Conflict Previous Base File: %s\n"),
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, conflict->base_abspath,
+ pool)));
+
+ if (conflict->my_abspath)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Conflict Previous Working File: %s\n"),
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, conflict->my_abspath,
+ pool)));
+
+ if (conflict->their_abspath)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Conflict Current Base File: %s\n"),
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, conflict->their_abspath,
+ pool)));
+ break;
+
+ case svn_wc_conflict_kind_property:
+ if (! printed_prop_conflict_file)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Conflict Properties File: %s\n"),
+ svn_dirent_local_style(conflict->their_abspath,
+ pool)));
+ printed_prop_conflict_file = TRUE;
+ break;
+
+ case svn_wc_conflict_kind_tree:
+ SVN_ERR(
+ svn_cl__get_human_readable_tree_conflict_description(
+ &desc, conflict, pool));
+
+ SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
+ _("Tree conflict"), desc));
+ break;
+ }
+ }
+
+ /* We only store one left and right version for all conflicts, which is
+ referenced from all conflicts.
+ Print it after the conflicts to match the 1.6/1.7 output where it is
+ only available for tree conflicts */
+ {
+ const char *src_left_version;
+ const char *src_right_version;
+ const svn_wc_conflict_description2_t *conflict =
+ APR_ARRAY_IDX(info->wc_info->conflicts, 0,
+ const svn_wc_conflict_description2_t *);
+
+ src_left_version =
+ svn_cl__node_description(conflict->src_left_version,
+ info->repos_root_URL, pool);
+
+ src_right_version =
+ svn_cl__node_description(conflict->src_right_version,
+ info->repos_root_URL, pool);
+
+ if (src_left_version)
+ SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n",
+ _("Source left"), /* (1) */
+ src_left_version));
+ /* (1): Sneaking in a space in "Source left" so that
+ * it is the same length as "Source right" while it still
+ * starts in the same column. That's just a tiny tweak in
+ * the English `svn'. */
+
+ if (src_right_version)
+ SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n",
+ _("Source right"),
+ src_right_version));
+ }
+ }
+ }
+
+ if (info->lock)
+ {
+ if (info->lock->token)
+ SVN_ERR(svn_cmdline_printf(pool, _("Lock Token: %s\n"),
+ info->lock->token));
+
+ if (info->lock->owner)
+ SVN_ERR(svn_cmdline_printf(pool, _("Lock Owner: %s\n"),
+ info->lock->owner));
+
+ if (info->lock->creation_date)
+ SVN_ERR(svn_cl__info_print_time(info->lock->creation_date,
+ _("Lock Created"), pool));
+
+ if (info->lock->expiration_date)
+ SVN_ERR(svn_cl__info_print_time(info->lock->expiration_date,
+ _("Lock Expires"), pool));
+
+ if (info->lock->comment)
+ {
+ int comment_lines;
+ /* NOTE: The stdio will handle newline translation. */
+ comment_lines = svn_cstring_count_newlines(info->lock->comment) + 1;
+ SVN_ERR(svn_cmdline_printf(pool,
+ Q_("Lock Comment (%i line):\n%s\n",
+ "Lock Comment (%i lines):\n%s\n",
+ comment_lines),
+ comment_lines,
+ info->lock->comment));
+ }
+ }
+
+ if (info->wc_info && info->wc_info->changelist)
+ SVN_ERR(svn_cmdline_printf(pool, _("Changelist: %s\n"),
+ info->wc_info->changelist));
+
+ /* Print extra newline separator. */
+ return svn_cmdline_printf(pool, "\n");
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__info(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets = NULL;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ int i;
+ svn_error_t *err;
+ svn_boolean_t seen_nonexistent_target = FALSE;
+ svn_opt_revision_t peg_revision;
+ svn_client_info_receiver2_t receiver;
+ const char *path_prefix;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments. */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ if (opt_state->xml)
+ {
+ receiver = print_info_xml;
+
+ /* If output is not incremental, output the XML header and wrap
+ everything in a top-level element. This makes the output in
+ its entirety a well-formed XML document. */
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_header("info", pool));
+ }
+ else
+ {
+ receiver = print_info;
+
+ if (opt_state->incremental)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'incremental' option only valid in XML "
+ "mode"));
+ }
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *truepath;
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Get peg revisions. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool));
+
+ /* If no peg-rev was attached to a URL target, then assume HEAD. */
+ if (svn_path_is_url(truepath))
+ {
+ if (peg_revision.kind == svn_opt_revision_unspecified)
+ peg_revision.kind = svn_opt_revision_head;
+ }
+ else
+ {
+ SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
+ }
+
+ err = svn_client_info3(truepath,
+ &peg_revision, &(opt_state->start_revision),
+ opt_state->depth, TRUE, TRUE,
+ opt_state->changelists,
+ receiver, (void *) path_prefix,
+ ctx, subpool);
+
+ if (err)
+ {
+ /* If one of the targets is a non-existent URL or wc-entry,
+ don't bail out. Just warn and move on to the next target. */
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
+ err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(svn_cmdline_fprintf(stderr, subpool, "\n"));
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+
+ svn_error_clear(err);
+ err = NULL;
+ seen_nonexistent_target = TRUE;
+ }
+ }
+ svn_pool_destroy(subpool);
+
+ if (opt_state->xml && (! opt_state->incremental))
+ SVN_ERR(svn_cl__xml_print_footer("info", pool));
+
+ if (seen_nonexistent_target)
+ return svn_error_create(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Could not display info for all targets because some "
+ "targets don't exist"));
+ else
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/list-cmd.c b/contrib/subversion/subversion/svn/list-cmd.c
new file mode 100644
index 0000000..efe4279
--- /dev/null
+++ b/contrib/subversion/subversion/svn/list-cmd.c
@@ -0,0 +1,424 @@
+/*
+ * list-cmd.c -- list a URL
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_cmdline.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_time.h"
+#include "svn_xml.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_opt.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+/* Baton used when printing directory entries. */
+struct print_baton {
+ svn_boolean_t verbose;
+ svn_client_ctx_t *ctx;
+
+ /* To keep track of last seen external information. */
+ const char *last_external_parent_url;
+ const char *last_external_target;
+ svn_boolean_t in_external;
+};
+
+/* This implements the svn_client_list_func2_t API, printing a single
+ directory entry in text format. */
+static svn_error_t *
+print_dirent(void *baton,
+ const char *path,
+ const svn_dirent_t *dirent,
+ const svn_lock_t *lock,
+ const char *abs_path,
+ const char *external_parent_url,
+ const char *external_target,
+ apr_pool_t *scratch_pool)
+{
+ struct print_baton *pb = baton;
+ const char *entryname;
+ static const char *time_format_long = NULL;
+ static const char *time_format_short = NULL;
+
+ SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
+ (external_parent_url && external_target));
+
+ if (time_format_long == NULL)
+ time_format_long = _("%b %d %H:%M");
+ if (time_format_short == NULL)
+ time_format_short = _("%b %d %Y");
+
+ if (pb->ctx->cancel_func)
+ SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
+
+ if (strcmp(path, "") == 0)
+ {
+ if (dirent->kind == svn_node_file)
+ entryname = svn_dirent_basename(abs_path, scratch_pool);
+ else if (pb->verbose)
+ entryname = ".";
+ else
+ /* Don't bother to list if no useful information will be shown. */
+ return SVN_NO_ERROR;
+ }
+ else
+ entryname = path;
+
+ if (external_parent_url && external_target)
+ {
+ if ((pb->last_external_parent_url == NULL
+ && pb->last_external_target == NULL)
+ || (strcmp(pb->last_external_parent_url, external_parent_url) != 0
+ || strcmp(pb->last_external_target, external_target) != 0))
+ {
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _("Listing external '%s'"
+ " defined on '%s':\n"),
+ external_target,
+ external_parent_url));
+
+ pb->last_external_parent_url = external_parent_url;
+ pb->last_external_target = external_target;
+ }
+ }
+
+ if (pb->verbose)
+ {
+ apr_time_t now = apr_time_now();
+ apr_time_exp_t exp_time;
+ apr_status_t apr_err;
+ apr_size_t size;
+ char timestr[20];
+ const char *sizestr, *utf8_timestr;
+
+ /* svn_time_to_human_cstring gives us something *way* too long
+ to use for this, so we have to roll our own. We include
+ the year if the entry's time is not within half a year. */
+ apr_time_exp_lt(&exp_time, dirent->time);
+ if (apr_time_sec(now - dirent->time) < (365 * 86400 / 2)
+ && apr_time_sec(dirent->time - now) < (365 * 86400 / 2))
+ {
+ apr_err = apr_strftime(timestr, &size, sizeof(timestr),
+ time_format_long, &exp_time);
+ }
+ else
+ {
+ apr_err = apr_strftime(timestr, &size, sizeof(timestr),
+ time_format_short, &exp_time);
+ }
+
+ /* if that failed, just zero out the string and print nothing */
+ if (apr_err)
+ timestr[0] = '\0';
+
+ /* we need it in UTF-8. */
+ SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, scratch_pool));
+
+ sizestr = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT,
+ dirent->size);
+
+ return svn_cmdline_printf
+ (scratch_pool, "%7ld %-8.8s %c %10s %12s %s%s\n",
+ dirent->created_rev,
+ dirent->last_author ? dirent->last_author : " ? ",
+ lock ? 'O' : ' ',
+ (dirent->kind == svn_node_file) ? sizestr : "",
+ utf8_timestr,
+ entryname,
+ (dirent->kind == svn_node_dir) ? "/" : "");
+ }
+ else
+ {
+ return svn_cmdline_printf(scratch_pool, "%s%s\n", entryname,
+ (dirent->kind == svn_node_dir)
+ ? "/" : "");
+ }
+}
+
+
+/* This implements the svn_client_list_func2_t API, printing a single dirent
+ in XML format. */
+static svn_error_t *
+print_dirent_xml(void *baton,
+ const char *path,
+ const svn_dirent_t *dirent,
+ const svn_lock_t *lock,
+ const char *abs_path,
+ const char *external_parent_url,
+ const char *external_target,
+ apr_pool_t *scratch_pool)
+{
+ struct print_baton *pb = baton;
+ const char *entryname;
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(scratch_pool);
+
+ SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
+ (external_parent_url && external_target));
+
+ if (strcmp(path, "") == 0)
+ {
+ if (dirent->kind == svn_node_file)
+ entryname = svn_dirent_basename(abs_path, scratch_pool);
+ else
+ /* Don't bother to list if no useful information will be shown. */
+ return SVN_NO_ERROR;
+ }
+ else
+ entryname = path;
+
+ if (pb->ctx->cancel_func)
+ SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
+
+ if (external_parent_url && external_target)
+ {
+ if ((pb->last_external_parent_url == NULL
+ && pb->last_external_target == NULL)
+ || (strcmp(pb->last_external_parent_url, external_parent_url) != 0
+ || strcmp(pb->last_external_target, external_target) != 0))
+ {
+ if (pb->in_external)
+ {
+ /* The external item being listed is different from the previous
+ one, so close the tag. */
+ svn_xml_make_close_tag(&sb, scratch_pool, "external");
+ pb->in_external = FALSE;
+ }
+
+ svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "external",
+ "parent_url", external_parent_url,
+ "target", external_target,
+ NULL);
+
+ pb->last_external_parent_url = external_parent_url;
+ pb->last_external_target = external_target;
+ pb->in_external = TRUE;
+ }
+ }
+
+ svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "entry",
+ "kind", svn_cl__node_kind_str_xml(dirent->kind),
+ NULL);
+
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "name", entryname);
+
+ if (dirent->kind == svn_node_file)
+ {
+ svn_cl__xml_tagged_cdata
+ (&sb, scratch_pool, "size",
+ apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, dirent->size));
+ }
+
+ svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "commit",
+ "revision",
+ apr_psprintf(scratch_pool, "%ld", dirent->created_rev),
+ NULL);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "author", dirent->last_author);
+ if (dirent->time)
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "date",
+ svn_time_to_cstring(dirent->time, scratch_pool));
+ svn_xml_make_close_tag(&sb, scratch_pool, "commit");
+
+ if (lock)
+ {
+ svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "lock", NULL);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "token", lock->token);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "owner", lock->owner);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "comment", lock->comment);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "created",
+ svn_time_to_cstring(lock->creation_date,
+ scratch_pool));
+ if (lock->expiration_date != 0)
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "expires",
+ svn_time_to_cstring
+ (lock->expiration_date, scratch_pool));
+ svn_xml_make_close_tag(&sb, scratch_pool, "lock");
+ }
+
+ svn_xml_make_close_tag(&sb, scratch_pool, "entry");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__list(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ int i;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_uint32_t dirent_fields;
+ struct print_baton pb;
+ svn_boolean_t seen_nonexistent_target = FALSE;
+ svn_error_t *err;
+ svn_error_t *externals_err = SVN_NO_ERROR;
+ struct svn_cl__check_externals_failed_notify_baton nwb;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ if (opt_state->xml)
+ {
+ /* The XML output contains all the information, so "--verbose"
+ does not apply. */
+ if (opt_state->verbose)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'verbose' option invalid in XML mode"));
+
+ /* If output is not incremental, output the XML header and wrap
+ everything in a top-level element. This makes the output in
+ its entirety a well-formed XML document. */
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_header("lists", pool));
+ }
+ else
+ {
+ if (opt_state->incremental)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'incremental' option only valid in XML "
+ "mode"));
+ }
+
+ if (opt_state->verbose || opt_state->xml)
+ dirent_fields = SVN_DIRENT_ALL;
+ else
+ dirent_fields = SVN_DIRENT_KIND; /* the only thing we actually need... */
+
+ pb.ctx = ctx;
+ pb.verbose = opt_state->verbose;
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_immediates;
+
+ if (opt_state->include_externals)
+ {
+ nwb.wrapped_func = ctx->notify_func2;
+ nwb.wrapped_baton = ctx->notify_baton2;
+ nwb.had_externals_error = FALSE;
+ ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper;
+ ctx->notify_baton2 = &nwb;
+ }
+
+ /* For each target, try to list it. */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+
+ /* Initialize the following variables for
+ every list target. */
+ pb.last_external_parent_url = NULL;
+ pb.last_external_target = NULL;
+ pb.in_external = FALSE;
+
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Get peg revisions. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ subpool));
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "list",
+ "path", truepath[0] == '\0' ? "." : truepath,
+ NULL);
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+
+ err = svn_client_list3(truepath, &peg_revision,
+ &(opt_state->start_revision),
+ opt_state->depth,
+ dirent_fields,
+ (opt_state->xml || opt_state->verbose),
+ opt_state->include_externals,
+ opt_state->xml ? print_dirent_xml : print_dirent,
+ &pb, ctx, subpool);
+
+ if (err)
+ {
+ /* If one of the targets is a non-existent URL or wc-entry,
+ don't bail out. Just warn and move on to the next target. */
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
+ err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ svn_handle_warning2(stderr, err, "svn: ");
+ else
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ err = NULL;
+ seen_nonexistent_target = TRUE;
+ }
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ if (pb.in_external)
+ {
+ /* close the final external item's tag */
+ svn_xml_make_close_tag(&sb, pool, "external");
+ pb.in_external = FALSE;
+ }
+
+ svn_xml_make_close_tag(&sb, pool, "list");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ if (opt_state->include_externals && nwb.had_externals_error)
+ {
+ externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
+ NULL,
+ _("Failure occurred processing one or "
+ "more externals definitions"));
+ }
+
+ if (opt_state->xml && ! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_footer("lists", pool));
+
+ if (seen_nonexistent_target)
+ err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Could not list all targets because some targets don't exist"));
+
+ return svn_error_compose_create(externals_err, err);
+}
diff --git a/contrib/subversion/subversion/svn/lock-cmd.c b/contrib/subversion/subversion/svn/lock-cmd.c
new file mode 100644
index 0000000..c2795da
--- /dev/null
+++ b/contrib/subversion/subversion/svn/lock-cmd.c
@@ -0,0 +1,110 @@
+/*
+ * lock-cmd.c -- LOck a working copy path in the repository.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_subst.h"
+#include "svn_path.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_cmdline.h"
+#include "cl.h"
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* Get a lock comment, allocate it in POOL and store it in *COMMENT. */
+static svn_error_t *
+get_comment(const char **comment, svn_client_ctx_t *ctx,
+ svn_cl__opt_state_t *opt_state, apr_pool_t *pool)
+{
+ svn_string_t *comment_string;
+
+ if (opt_state->filedata)
+ {
+ /* Get it from the -F argument. */
+ if (strlen(opt_state->filedata->data) < opt_state->filedata->len)
+ {
+ /* A message containing a zero byte can't be represented as a C
+ string. */
+ return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL,
+ _("Lock comment contains a zero byte"));
+ }
+ comment_string = svn_string_create(opt_state->filedata->data, pool);
+
+ }
+ else if (opt_state->message)
+ {
+ /* Get if from the -m option. */
+ comment_string = svn_string_create(opt_state->message, pool);
+ }
+ else
+ {
+ *comment = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Translate to UTF8/LF. */
+ SVN_ERR(svn_subst_translate_string2(&comment_string, NULL, NULL,
+ comment_string, opt_state->encoding,
+ FALSE, pool, pool));
+ *comment = comment_string->data;
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__lock(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ const char *comment;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* We only support locking files, so '.' is not valid. */
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ SVN_ERR(svn_cl__assert_homogeneous_target_type(targets));
+
+ /* Get comment. */
+ SVN_ERR(get_comment(&comment, ctx, opt_state, pool));
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ return svn_client_lock(targets, comment, opt_state->force, ctx, pool);
+}
diff --git a/contrib/subversion/subversion/svn/log-cmd.c b/contrib/subversion/subversion/svn/log-cmd.c
new file mode 100644
index 0000000..af57cf4
--- /dev/null
+++ b/contrib/subversion/subversion/svn/log-cmd.c
@@ -0,0 +1,875 @@
+/*
+ * log-cmd.c -- Display log messages
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_fnmatch.h>
+
+#include "svn_client.h"
+#include "svn_compat.h"
+#include "svn_dirent_uri.h"
+#include "svn_string.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_sorts.h"
+#include "svn_xml.h"
+#include "svn_time.h"
+#include "svn_cmdline.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "private/svn_cmdline_private.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
+struct log_receiver_baton
+{
+ /* Client context. */
+ svn_client_ctx_t *ctx;
+
+ /* The target of the log operation. */
+ const char *target_path_or_url;
+ svn_opt_revision_t target_peg_revision;
+
+ /* Don't print log message body nor its line count. */
+ svn_boolean_t omit_log_message;
+
+ /* Whether to show diffs in the log. (maps to --diff) */
+ svn_boolean_t show_diff;
+
+ /* Depth applied to diff output. */
+ svn_depth_t depth;
+
+ /* Diff arguments received from command line. */
+ const char *diff_extensions;
+
+ /* Stack which keeps track of merge revision nesting, using svn_revnum_t's */
+ apr_array_header_t *merge_stack;
+
+ /* Log message search patterns. Log entries will only be shown if the author,
+ * the log message, or a changed path matches one of these patterns. */
+ apr_array_header_t *search_patterns;
+
+ /* Pool for persistent allocations. */
+ apr_pool_t *pool;
+};
+
+
+/* The separator between log messages. */
+#define SEP_STRING \
+ "------------------------------------------------------------------------\n"
+
+
+/* Display a diff of the subtree TARGET_PATH_OR_URL@TARGET_PEG_REVISION as
+ * it changed in the revision that LOG_ENTRY describes.
+ *
+ * Restrict the diff to depth DEPTH. Pass DIFF_EXTENSIONS along to the diff
+ * subroutine.
+ *
+ * Write the diff to OUTSTREAM and write any stderr output to ERRSTREAM.
+ * ### How is exit code handled? 0 and 1 -> SVN_NO_ERROR, else an svn error?
+ * ### Should we get rid of ERRSTREAM and use svn_error_t instead?
+ */
+static svn_error_t *
+display_diff(const svn_log_entry_t *log_entry,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_peg_revision,
+ svn_depth_t depth,
+ const char *diff_extensions,
+ svn_stream_t *outstream,
+ svn_stream_t *errstream,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *diff_options;
+ svn_opt_revision_t start_revision;
+ svn_opt_revision_t end_revision;
+
+ /* Fall back to "" to get options initialized either way. */
+ if (diff_extensions)
+ diff_options = svn_cstring_split(diff_extensions, " \t\n\r",
+ TRUE, pool);
+ else
+ diff_options = NULL;
+
+ start_revision.kind = svn_opt_revision_number;
+ start_revision.value.number = log_entry->revision - 1;
+ end_revision.kind = svn_opt_revision_number;
+ end_revision.value.number = log_entry->revision;
+
+ SVN_ERR(svn_stream_puts(outstream, "\n"));
+ SVN_ERR(svn_client_diff_peg6(diff_options,
+ target_path_or_url,
+ target_peg_revision,
+ &start_revision, &end_revision,
+ NULL,
+ depth,
+ FALSE /* ignore ancestry */,
+ FALSE /* no diff added */,
+ TRUE /* no diff deleted */,
+ FALSE /* show copies as adds */,
+ FALSE /* ignore content type */,
+ FALSE /* ignore prop diff */,
+ FALSE /* properties only */,
+ FALSE /* use git diff format */,
+ svn_cmdline_output_encoding(pool),
+ outstream,
+ errstream,
+ NULL,
+ ctx, pool));
+ SVN_ERR(svn_stream_puts(outstream, _("\n")));
+ return SVN_NO_ERROR;
+}
+
+
+/* Return TRUE if SEARCH_PATTERN matches the AUTHOR, DATE, LOG_MESSAGE,
+ * or a path in the set of keys of the CHANGED_PATHS hash. Else, return FALSE.
+ * Any of AUTHOR, DATE, LOG_MESSAGE, and CHANGED_PATHS may be NULL. */
+static svn_boolean_t
+match_search_pattern(const char *search_pattern,
+ const char *author,
+ const char *date,
+ const char *log_message,
+ apr_hash_t *changed_paths,
+ apr_pool_t *pool)
+{
+ /* Match any substring containing the pattern, like UNIX 'grep' does. */
+ const char *pattern = apr_psprintf(pool, "*%s*", search_pattern);
+ int flags = 0;
+
+ /* Does the author match the search pattern? */
+ if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS)
+ return TRUE;
+
+ /* Does the date the search pattern? */
+ if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS)
+ return TRUE;
+
+ /* Does the log message the search pattern? */
+ if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS)
+ return TRUE;
+
+ if (changed_paths)
+ {
+ apr_hash_index_t *hi;
+
+ /* Does a changed path match the search pattern? */
+ for (hi = apr_hash_first(pool, changed_paths);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_log_changed_path2_t *log_item;
+
+ if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS)
+ return TRUE;
+
+ /* Match copy-from paths, too. */
+ log_item = svn__apr_hash_index_val(hi);
+ if (log_item->copyfrom_path
+ && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)
+ && apr_fnmatch(pattern,
+ log_item->copyfrom_path, flags) == APR_SUCCESS)
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* Match all search patterns in SEARCH_PATTERNS against AUTHOR, DATE, MESSAGE,
+ * and CHANGED_PATHS. Return TRUE if any pattern matches, else FALSE.
+ * SCRACH_POOL is used for temporary allocations. */
+static svn_boolean_t
+match_search_patterns(apr_array_header_t *search_patterns,
+ const char *author,
+ const char *date,
+ const char *message,
+ apr_hash_t *changed_paths,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_boolean_t match = FALSE;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < search_patterns->nelts; i++)
+ {
+ apr_array_header_t *pattern_group;
+ int j;
+
+ pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *);
+
+ /* All patterns within the group must match. */
+ for (j = 0; j < pattern_group->nelts; j++)
+ {
+ const char *pattern;
+
+ svn_pool_clear(iterpool);
+
+ pattern = APR_ARRAY_IDX(pattern_group, j, const char *);
+ match = match_search_pattern(pattern, author, date, message,
+ changed_paths, iterpool);
+ if (!match)
+ break;
+ }
+
+ match = (match && j == pattern_group->nelts);
+ if (match)
+ break;
+ }
+ svn_pool_destroy(iterpool);
+
+ return match;
+}
+
+/* Implement `svn_log_entry_receiver_t', printing the logs in
+ * a human-readable and machine-parseable format.
+ *
+ * BATON is of type `struct log_receiver_baton'.
+ *
+ * First, print a header line. Then if CHANGED_PATHS is non-null,
+ * print all affected paths in a list headed "Changed paths:\n",
+ * immediately following the header line. Then print a newline
+ * followed by the message body, unless BATON->omit_log_message is true.
+ *
+ * Here are some examples of the output:
+ *
+ * $ svn log -r1847:1846
+ * ------------------------------------------------------------------------
+ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
+ *
+ * Fix for Issue #694.
+ *
+ * * subversion/libsvn_repos/delta.c
+ * (delta_files): Rework the logic in this function to only call
+ * send_text_deltas if there are deltas to send, and within that case,
+ * only use a real delta stream if the caller wants real text deltas.
+ *
+ * ------------------------------------------------------------------------
+ * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line
+ *
+ * imagine an example log message here
+ * ------------------------------------------------------------------------
+ *
+ * Or:
+ *
+ * $ svn log -r1847:1846 -v
+ * ------------------------------------------------------------------------
+ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
+ * Changed paths:
+ * M /trunk/subversion/libsvn_repos/delta.c
+ *
+ * Fix for Issue #694.
+ *
+ * * subversion/libsvn_repos/delta.c
+ * (delta_files): Rework the logic in this function to only call
+ * send_text_deltas if there are deltas to send, and within that case,
+ * only use a real delta stream if the caller wants real text deltas.
+ *
+ * ------------------------------------------------------------------------
+ * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line
+ * Changed paths:
+ * M /trunk/notes/fs_dumprestore.txt
+ * M /trunk/subversion/libsvn_repos/dump.c
+ *
+ * imagine an example log message here
+ * ------------------------------------------------------------------------
+ *
+ * Or:
+ *
+ * $ svn log -r1847:1846 -q
+ * ------------------------------------------------------------------------
+ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26
+ * ------------------------------------------------------------------------
+ * rev 1846: whoever | Wed 1 May 2002 15:23:41
+ * ------------------------------------------------------------------------
+ *
+ * Or:
+ *
+ * $ svn log -r1847:1846 -qv
+ * ------------------------------------------------------------------------
+ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26
+ * Changed paths:
+ * M /trunk/subversion/libsvn_repos/delta.c
+ * ------------------------------------------------------------------------
+ * rev 1846: whoever | Wed 1 May 2002 15:23:41
+ * Changed paths:
+ * M /trunk/notes/fs_dumprestore.txt
+ * M /trunk/subversion/libsvn_repos/dump.c
+ * ------------------------------------------------------------------------
+ *
+ */
+static svn_error_t *
+log_entry_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct log_receiver_baton *lb = baton;
+ const char *author;
+ const char *date;
+ const char *message;
+
+ if (lb->ctx->cancel_func)
+ SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
+
+ svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+
+ if (log_entry->revision == 0 && message == NULL)
+ return SVN_NO_ERROR;
+
+ if (! SVN_IS_VALID_REVNUM(log_entry->revision))
+ {
+ apr_array_pop(lb->merge_stack);
+ return SVN_NO_ERROR;
+ }
+
+ /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
+ for more on the fallback fuzzy conversions below. */
+
+ if (author == NULL)
+ author = _("(no author)");
+
+ if (date && date[0])
+ /* Convert date to a format for humans. */
+ SVN_ERR(svn_cl__time_cstring_to_human_cstring(&date, date, pool));
+ else
+ date = _("(no date)");
+
+ if (! lb->omit_log_message && message == NULL)
+ message = "";
+
+ if (lb->search_patterns &&
+ ! match_search_patterns(lb->search_patterns, author, date, message,
+ log_entry->changed_paths2, pool))
+ {
+ if (log_entry->has_children)
+ APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
+
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_cmdline_printf(pool,
+ SEP_STRING "r%ld | %s | %s",
+ log_entry->revision, author, date));
+
+ if (message != NULL)
+ {
+ /* Number of lines in the msg. */
+ int lines = svn_cstring_count_newlines(message) + 1;
+
+ SVN_ERR(svn_cmdline_printf(pool,
+ Q_(" | %d line", " | %d lines", lines),
+ lines));
+ }
+
+ SVN_ERR(svn_cmdline_printf(pool, "\n"));
+
+ if (log_entry->changed_paths2)
+ {
+ apr_array_header_t *sorted_paths;
+ int i;
+
+ /* Get an array of sorted hash keys. */
+ sorted_paths = svn_sort__hash(log_entry->changed_paths2,
+ svn_sort_compare_items_as_paths, pool);
+
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Changed paths:\n")));
+ for (i = 0; i < sorted_paths->nelts; i++)
+ {
+ svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
+ svn_sort__item_t));
+ const char *path = item->key;
+ svn_log_changed_path2_t *log_item = item->value;
+ const char *copy_data = "";
+
+ if (lb->ctx->cancel_func)
+ SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
+
+ if (log_item->copyfrom_path
+ && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
+ {
+ copy_data
+ = apr_psprintf(pool,
+ _(" (from %s:%ld)"),
+ log_item->copyfrom_path,
+ log_item->copyfrom_rev);
+ }
+ SVN_ERR(svn_cmdline_printf(pool, " %c %s%s\n",
+ log_item->action, path,
+ copy_data));
+ }
+ }
+
+ if (lb->merge_stack->nelts > 0)
+ {
+ int i;
+
+ /* Print the result of merge line */
+ if (log_entry->subtractive_merge)
+ SVN_ERR(svn_cmdline_printf(pool, _("Reverse merged via:")));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, _("Merged via:")));
+ for (i = 0; i < lb->merge_stack->nelts; i++)
+ {
+ svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t);
+
+ SVN_ERR(svn_cmdline_printf(pool, " r%ld%c", rev,
+ i == lb->merge_stack->nelts - 1 ?
+ '\n' : ','));
+ }
+ }
+
+ if (message != NULL)
+ {
+ /* A blank line always precedes the log message. */
+ SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
+ }
+
+ SVN_ERR(svn_cmdline_fflush(stdout));
+ SVN_ERR(svn_cmdline_fflush(stderr));
+
+ /* Print a diff if requested. */
+ if (lb->show_diff)
+ {
+ svn_stream_t *outstream;
+ svn_stream_t *errstream;
+
+ SVN_ERR(svn_stream_for_stdout(&outstream, pool));
+ SVN_ERR(svn_stream_for_stderr(&errstream, pool));
+
+ SVN_ERR(display_diff(log_entry,
+ lb->target_path_or_url, &lb->target_peg_revision,
+ lb->depth, lb->diff_extensions,
+ outstream, errstream,
+ lb->ctx, pool));
+
+ SVN_ERR(svn_stream_close(outstream));
+ SVN_ERR(svn_stream_close(errstream));
+ }
+
+ if (log_entry->has_children)
+ APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_log_entry_receiver_t', printing the logs in XML.
+ *
+ * BATON is of type `struct log_receiver_baton'.
+ *
+ * Here is an example of the output; note that the "<log>" and
+ * "</log>" tags are not emitted by this function:
+ *
+ * $ svn log --xml -r 1648:1649
+ * <log>
+ * <logentry
+ * revision="1648">
+ * <author>david</author>
+ * <date>2002-04-06T16:34:51.428043Z</date>
+ * <msg> * packages/rpm/subversion.spec : Now requires apache 2.0.36.
+ * </msg>
+ * </logentry>
+ * <logentry
+ * revision="1649">
+ * <author>cmpilato</author>
+ * <date>2002-04-06T17:01:28.185136Z</date>
+ * <msg>Fix error handling when the $EDITOR is needed but unavailable. Ah
+ * ... now that&apos;s *much* nicer.
+ *
+ * * subversion/clients/cmdline/util.c
+ * (svn_cl__edit_externally): Clean up the &quot;no external editor&quot;
+ * error message.
+ * (svn_cl__get_log_message): Wrap &quot;no external editor&quot;
+ * errors with helpful hints about the -m and -F options.
+ *
+ * * subversion/libsvn_client/commit.c
+ * (svn_client_commit): Actually capture and propagate &quot;no external
+ * editor&quot; errors.</msg>
+ * </logentry>
+ * </log>
+ *
+ */
+static svn_error_t *
+log_entry_receiver_xml(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct log_receiver_baton *lb = baton;
+ /* Collate whole log message into sb before printing. */
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+ char *revstr;
+ const char *author;
+ const char *date;
+ const char *message;
+
+ if (lb->ctx->cancel_func)
+ SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
+
+ svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+
+ if (log_entry->revision == 0 && message == NULL)
+ return SVN_NO_ERROR;
+
+ if (! SVN_IS_VALID_REVNUM(log_entry->revision))
+ {
+ svn_xml_make_close_tag(&sb, pool, "logentry");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ apr_array_pop(lb->merge_stack);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Match search pattern before XML-escaping. */
+ if (lb->search_patterns &&
+ ! match_search_patterns(lb->search_patterns, author, date, message,
+ log_entry->changed_paths2, pool))
+ {
+ if (log_entry->has_children)
+ APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
+
+ return SVN_NO_ERROR;
+ }
+
+ if (author)
+ author = svn_xml_fuzzy_escape(author, pool);
+ if (date)
+ date = svn_xml_fuzzy_escape(date, pool);
+ if (message)
+ message = svn_xml_fuzzy_escape(message, pool);
+
+ revstr = apr_psprintf(pool, "%ld", log_entry->revision);
+ /* <logentry revision="xxx"> */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry",
+ "revision", revstr, NULL);
+
+ /* <author>xxx</author> */
+ svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
+
+ /* Print the full, uncut, date. This is machine output. */
+ /* According to the docs for svn_log_entry_receiver_t, either
+ NULL or the empty string represents no date. Avoid outputting an
+ empty date element. */
+ if (date && date[0] == '\0')
+ date = NULL;
+ /* <date>xxx</date> */
+ svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
+
+ if (log_entry->changed_paths2)
+ {
+ apr_array_header_t *sorted_paths;
+ int i;
+
+ /* <paths> */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths",
+ NULL);
+
+ /* Get an array of sorted hash keys. */
+ sorted_paths = svn_sort__hash(log_entry->changed_paths2,
+ svn_sort_compare_items_as_paths, pool);
+
+ for (i = 0; i < sorted_paths->nelts; i++)
+ {
+ svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
+ svn_sort__item_t));
+ const char *path = item->key;
+ svn_log_changed_path2_t *log_item = item->value;
+ char action[2];
+
+ action[0] = log_item->action;
+ action[1] = '\0';
+ if (log_item->copyfrom_path
+ && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
+ {
+ /* <path action="X" copyfrom-path="xxx" copyfrom-rev="xxx"> */
+ revstr = apr_psprintf(pool, "%ld",
+ log_item->copyfrom_rev);
+ svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
+ "action", action,
+ "copyfrom-path", log_item->copyfrom_path,
+ "copyfrom-rev", revstr,
+ "kind", svn_cl__node_kind_str_xml(
+ log_item->node_kind),
+ "text-mods", svn_tristate__to_word(
+ log_item->text_modified),
+ "prop-mods", svn_tristate__to_word(
+ log_item->props_modified),
+ NULL);
+ }
+ else
+ {
+ /* <path action="X"> */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
+ "action", action,
+ "kind", svn_cl__node_kind_str_xml(
+ log_item->node_kind),
+ "text-mods", svn_tristate__to_word(
+ log_item->text_modified),
+ "prop-mods", svn_tristate__to_word(
+ log_item->props_modified),
+ NULL);
+ }
+ /* xxx</path> */
+ svn_xml_escape_cdata_cstring(&sb, path, pool);
+ svn_xml_make_close_tag(&sb, pool, "path");
+ }
+
+ /* </paths> */
+ svn_xml_make_close_tag(&sb, pool, "paths");
+ }
+
+ if (message != NULL)
+ {
+ /* <msg>xxx</msg> */
+ svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
+ }
+
+ svn_compat_log_revprops_clear(log_entry->revprops);
+ if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
+ {
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL);
+ SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, log_entry->revprops,
+ FALSE, /* name_only */
+ FALSE, pool));
+ svn_xml_make_close_tag(&sb, pool, "revprops");
+ }
+
+ if (log_entry->has_children)
+ APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
+ else
+ svn_xml_make_close_tag(&sb, pool, "logentry");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__log(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ struct log_receiver_baton lb;
+ const char *target;
+ int i;
+ apr_array_header_t *revprops;
+
+ if (!opt_state->xml)
+ {
+ if (opt_state->all_revprops)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'with-all-revprops' option only valid in"
+ " XML mode"));
+ if (opt_state->no_revprops)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'with-no-revprops' option only valid in"
+ " XML mode"));
+ if (opt_state->revprop_table != NULL)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'with-revprop' option only valid in"
+ " XML mode"));
+ }
+ else
+ {
+ if (opt_state->show_diff)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'diff' option is not supported in "
+ "XML mode"));
+ }
+
+ if (opt_state->quiet && opt_state->show_diff)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'quiet' and 'diff' options are "
+ "mutually exclusive"));
+ if (opt_state->diff.diff_cmd && (! opt_state->show_diff))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'diff-cmd' option requires 'diff' "
+ "option"));
+ if (opt_state->diff.internal_diff && (! opt_state->show_diff))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'internal-diff' option requires "
+ "'diff' option"));
+ if (opt_state->extensions && (! opt_state->show_diff))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'extensions' option requires 'diff' "
+ "option"));
+
+ if (opt_state->depth != svn_depth_unknown && (! opt_state->show_diff))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'depth' option requires 'diff' option"));
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ /* Determine if they really want a two-revision range. */
+ if (opt_state->used_change_arg)
+ {
+ if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1)
+ {
+ return svn_error_create
+ (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("-c and -r are mutually exclusive"));
+ }
+ for (i = 0; i < opt_state->revision_ranges->nelts; i++)
+ {
+ svn_opt_revision_range_t *range;
+ range = APR_ARRAY_IDX(opt_state->revision_ranges, i,
+ svn_opt_revision_range_t *);
+ if (range->start.value.number < range->end.value.number)
+ range->start.value.number++;
+ else
+ range->end.value.number++;
+ }
+ }
+
+ /* Parse the first target into path-or-url and peg revision. */
+ target = APR_ARRAY_IDX(targets, 0, const char *);
+ SVN_ERR(svn_opt_parse_path(&lb.target_peg_revision, &lb.target_path_or_url,
+ target, pool));
+ if (lb.target_peg_revision.kind == svn_opt_revision_unspecified)
+ lb.target_peg_revision.kind = (svn_path_is_url(target)
+ ? svn_opt_revision_head
+ : svn_opt_revision_working);
+ APR_ARRAY_IDX(targets, 0, const char *) = lb.target_path_or_url;
+
+ if (svn_path_is_url(target))
+ {
+ for (i = 1; i < targets->nelts; i++)
+ {
+ target = APR_ARRAY_IDX(targets, i, const char *);
+
+ if (svn_path_is_url(target) || target[0] == '/')
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Only relative paths can be specified"
+ " after a URL for 'svn log', "
+ "but '%s' is not a relative path"),
+ target);
+ }
+ }
+
+ lb.ctx = ctx;
+ lb.omit_log_message = opt_state->quiet;
+ lb.show_diff = opt_state->show_diff;
+ lb.depth = opt_state->depth == svn_depth_unknown ? svn_depth_infinity
+ : opt_state->depth;
+ lb.diff_extensions = opt_state->extensions;
+ lb.merge_stack = apr_array_make(pool, 0, sizeof(svn_revnum_t));
+ lb.search_patterns = opt_state->search_patterns;
+ lb.pool = pool;
+
+ if (opt_state->xml)
+ {
+ /* If output is not incremental, output the XML header and wrap
+ everything in a top-level element. This makes the output in
+ its entirety a well-formed XML document. */
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_header("log", pool));
+
+ if (opt_state->all_revprops)
+ revprops = NULL;
+ else if(opt_state->no_revprops)
+ {
+ revprops = apr_array_make(pool, 0, sizeof(char *));
+ }
+ else if (opt_state->revprop_table != NULL)
+ {
+ apr_hash_index_t *hi;
+ revprops = apr_array_make(pool,
+ apr_hash_count(opt_state->revprop_table),
+ sizeof(char *));
+ for (hi = apr_hash_first(pool, opt_state->revprop_table);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ const char *property = svn__apr_hash_index_key(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+
+ if (value && value->data[0] != '\0')
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("cannot assign with 'with-revprop'"
+ " option (drop the '=')"));
+ APR_ARRAY_PUSH(revprops, const char *) = property;
+ }
+ }
+ else
+ {
+ revprops = apr_array_make(pool, 3, sizeof(char *));
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+ if (!opt_state->quiet)
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+ }
+ SVN_ERR(svn_client_log5(targets,
+ &lb.target_peg_revision,
+ opt_state->revision_ranges,
+ opt_state->limit,
+ opt_state->verbose,
+ opt_state->stop_on_copy,
+ opt_state->use_merge_history,
+ revprops,
+ log_entry_receiver_xml,
+ &lb,
+ ctx,
+ pool));
+
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_footer("log", pool));
+ }
+ else /* default output format */
+ {
+ revprops = apr_array_make(pool, 3, sizeof(char *));
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+ if (!opt_state->quiet)
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+ SVN_ERR(svn_client_log5(targets,
+ &lb.target_peg_revision,
+ opt_state->revision_ranges,
+ opt_state->limit,
+ opt_state->verbose,
+ opt_state->stop_on_copy,
+ opt_state->use_merge_history,
+ revprops,
+ log_entry_receiver,
+ &lb,
+ ctx,
+ pool));
+
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cmdline_printf(pool, SEP_STRING));
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/merge-cmd.c b/contrib/subversion/subversion/svn/merge-cmd.c
new file mode 100644
index 0000000..c14f769
--- /dev/null
+++ b/contrib/subversion/subversion/svn/merge-cmd.c
@@ -0,0 +1,467 @@
+/*
+ * merge-cmd.c -- Merging changes into a working copy.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_types.h"
+#include "cl.h"
+#include "private/svn_client_private.h"
+
+#include "svn_private_config.h"
+
+/* A handy constant */
+static const svn_opt_revision_t unspecified_revision
+ = { svn_opt_revision_unspecified, { 0 } };
+
+
+/*** Code. ***/
+
+/* Throw an error if PATH_OR_URL is a path and REVISION isn't a repository
+ * revision. */
+static svn_error_t *
+ensure_wc_path_has_repo_revision(const char *path_or_url,
+ const svn_opt_revision_t *revision,
+ apr_pool_t *scratch_pool)
+{
+ if (revision->kind != svn_opt_revision_number
+ && revision->kind != svn_opt_revision_date
+ && revision->kind != svn_opt_revision_head
+ && ! svn_path_is_url(path_or_url))
+ return svn_error_createf(
+ SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Invalid merge source '%s'; a working copy path can only be "
+ "used with a repository revision (a number, a date, or head)"),
+ svn_dirent_local_style(path_or_url, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Run a merge.
+ *
+ * (No docs yet, as this code was just hoisted out of svn_cl__merge().)
+ *
+ * Having FIRST_RANGE_START/_END params is ugly -- we should be able to use
+ * PEG_REVISION1/2 and/or RANGES_TO_MERGE instead, maybe adjusting the caller.
+ */
+static svn_error_t *
+run_merge(svn_boolean_t two_sources_specified,
+ const char *sourcepath1,
+ svn_opt_revision_t peg_revision1,
+ const char *sourcepath2,
+ const char *targetpath,
+ apr_array_header_t *ranges_to_merge,
+ svn_opt_revision_t first_range_start,
+ svn_opt_revision_t first_range_end,
+ svn_cl__opt_state_t *opt_state,
+ apr_array_header_t *options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *merge_err;
+
+ if (opt_state->reintegrate)
+ {
+ merge_err = svn_cl__deprecated_merge_reintegrate(
+ sourcepath1, &peg_revision1, targetpath,
+ opt_state->dry_run, options, ctx, scratch_pool);
+ }
+ else if (! two_sources_specified)
+ {
+ /* If we don't have at least one valid revision range, pick a
+ good one that spans the entire set of revisions on our
+ source. */
+ if ((first_range_start.kind == svn_opt_revision_unspecified)
+ && (first_range_end.kind == svn_opt_revision_unspecified))
+ {
+ ranges_to_merge = NULL;
+
+ /* This must be a 'sync' merge so check branch relationship. */
+ if (opt_state->verbose)
+ SVN_ERR(svn_cmdline_printf(
+ scratch_pool, _("--- Checking branch relationship\n")));
+ SVN_ERR_W(svn_cl__check_related_source_and_target(
+ sourcepath1, &peg_revision1,
+ targetpath, &unspecified_revision, ctx, scratch_pool),
+ _("Source and target must be different but related branches"));
+ }
+
+ if (opt_state->verbose)
+ SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
+ merge_err = svn_client_merge_peg5(sourcepath1,
+ ranges_to_merge,
+ &peg_revision1,
+ targetpath,
+ opt_state->depth,
+ opt_state->ignore_ancestry,
+ opt_state->ignore_ancestry,
+ opt_state->force, /* force_delete */
+ opt_state->record_only,
+ opt_state->dry_run,
+ opt_state->allow_mixed_rev,
+ options,
+ ctx,
+ scratch_pool);
+ }
+ else
+ {
+ if (svn_path_is_url(sourcepath1) != svn_path_is_url(sourcepath2))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Merge sources must both be "
+ "either paths or URLs"));
+
+ if (opt_state->verbose)
+ SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
+ merge_err = svn_client_merge5(sourcepath1,
+ &first_range_start,
+ sourcepath2,
+ &first_range_end,
+ targetpath,
+ opt_state->depth,
+ opt_state->ignore_ancestry,
+ opt_state->ignore_ancestry,
+ opt_state->force, /* force_delete */
+ opt_state->record_only,
+ opt_state->dry_run,
+ opt_state->allow_mixed_rev,
+ options,
+ ctx,
+ scratch_pool);
+ }
+
+ return merge_err;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__merge(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ const char *sourcepath1 = NULL, *sourcepath2 = NULL, *targetpath = "";
+ svn_boolean_t two_sources_specified = TRUE;
+ svn_error_t *merge_err;
+ svn_opt_revision_t first_range_start, first_range_end, peg_revision1,
+ peg_revision2;
+ apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges;
+ svn_boolean_t has_explicit_target = FALSE;
+
+ /* Merge doesn't support specifying a revision or revision range
+ when using --reintegrate. */
+ if (opt_state->reintegrate
+ && opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("-r and -c can't be used with --reintegrate"));
+ }
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* For now, we require at least one source. That may change in
+ future versions of Subversion, for example if we have support for
+ negated mergeinfo. See this IRC conversation:
+
+ <bhuvan> kfogel: yeah, i think you are correct; we should
+ specify the source url
+
+ <kfogel> bhuvan: I'll change the help output and propose for
+ backport. Thanks.
+
+ <bhuvan> kfogel: np; while we are at it, 'svn merge' simply
+ returns nothing; i think we should say: """svn: Not
+ enough arguments provided; try 'svn help' for more
+ info"""
+
+ <kfogel> good idea
+
+ <kfogel> (in the future, 'svn merge' might actually do
+ something, but that's all the more reason to make
+ sure it errors now)
+
+ <cmpilato> actually, i'm pretty sure 'svn merge' does something
+
+ <cmpilato> it says "please merge any unmerged changes from
+ myself to myself."
+
+ <cmpilato> :-)
+
+ <kfogel> har har
+
+ <cmpilato> kfogel: i was serious.
+
+ <kfogel> cmpilato: urrr, uh. Is that meaningful? Is there
+ ever a reason for a user to run it?
+
+ <cmpilato> kfogel: not while we don't have support for negated
+ mergeinfo.
+
+ <kfogel> cmpilato: do you concur that until it does something
+ useful it should error?
+
+ <cmpilato> kfogel: yup.
+
+ <kfogel> cool
+ */
+ if (targets->nelts < 1)
+ {
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
+ _("Merge source required"));
+ }
+ else /* Parse at least one, and possible two, sources. */
+ {
+ SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1,
+ APR_ARRAY_IDX(targets, 0, const char *),
+ pool));
+ if (targets->nelts >= 2)
+ SVN_ERR(svn_opt_parse_path(&peg_revision2, &sourcepath2,
+ APR_ARRAY_IDX(targets, 1, const char *),
+ pool));
+ }
+
+ /* We could have one or two sources. Deliberately written to stay
+ correct even if we someday permit implied merge source. */
+ if (targets->nelts <= 1)
+ {
+ two_sources_specified = FALSE;
+ }
+ else if (targets->nelts == 2)
+ {
+ if (svn_path_is_url(sourcepath1) && !svn_path_is_url(sourcepath2))
+ two_sources_specified = FALSE;
+ }
+
+ if (opt_state->revision_ranges->nelts > 0)
+ {
+ first_range_start = APR_ARRAY_IDX(opt_state->revision_ranges, 0,
+ svn_opt_revision_range_t *)->start;
+ first_range_end = APR_ARRAY_IDX(opt_state->revision_ranges, 0,
+ svn_opt_revision_range_t *)->end;
+ }
+ else
+ {
+ first_range_start.kind = first_range_end.kind =
+ svn_opt_revision_unspecified;
+ }
+
+ /* If revision_ranges has at least one real range at this point, then
+ we know the user must have used the '-r' and/or '-c' switch(es).
+ This means we're *not* doing two distinct sources. */
+ if (first_range_start.kind != svn_opt_revision_unspecified)
+ {
+ /* A revision *range* is required. */
+ if (first_range_end.kind == svn_opt_revision_unspecified)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
+ _("Second revision required"));
+
+ two_sources_specified = FALSE;
+ }
+
+ if (! two_sources_specified) /* TODO: Switch order of if */
+ {
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+
+ /* Set the default value for unspecified paths and peg revision. */
+ /* targets->nelts is 1 ("svn merge SOURCE") or 2 ("svn merge
+ SOURCE WCPATH") here. */
+ sourcepath2 = sourcepath1;
+
+ if (peg_revision1.kind == svn_opt_revision_unspecified)
+ peg_revision1.kind = svn_path_is_url(sourcepath1)
+ ? svn_opt_revision_head : svn_opt_revision_working;
+
+ if (targets->nelts == 2)
+ {
+ targetpath = APR_ARRAY_IDX(targets, 1, const char *);
+ has_explicit_target = TRUE;
+ if (svn_path_is_url(targetpath))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Cannot specify a revision range "
+ "with two URLs"));
+ }
+ }
+ else /* using @rev syntax */
+ {
+ if (targets->nelts < 2)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
+ if (targets->nelts > 3)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+
+ first_range_start = peg_revision1;
+ first_range_end = peg_revision2;
+
+ /* Catch 'svn merge wc_path1 wc_path2 [target]' without explicit
+ revisions--since it ignores local modifications it may not do what
+ the user expects. That is, it doesn't read from the WC itself, it
+ reads from the WC's URL. Forcing the user to specify a repository
+ revision should avoid any confusion. */
+ SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath1, &first_range_start,
+ pool));
+ SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath2, &first_range_end,
+ pool));
+
+ /* Default peg revisions to each URL's youngest revision. */
+ if (first_range_start.kind == svn_opt_revision_unspecified)
+ first_range_start.kind = svn_opt_revision_head;
+ if (first_range_end.kind == svn_opt_revision_unspecified)
+ first_range_end.kind = svn_opt_revision_head;
+
+ /* Decide where to apply the delta (defaulting to "."). */
+ if (targets->nelts == 3)
+ {
+ targetpath = APR_ARRAY_IDX(targets, 2, const char *);
+ has_explicit_target = TRUE;
+ }
+ }
+
+ /* If no targetpath was specified, see if we can infer it from the
+ sourcepaths. */
+ if (! has_explicit_target
+ && sourcepath1 && sourcepath2
+ && strcmp(targetpath, "") == 0)
+ {
+ /* If the sourcepath is a URL, it can only refer to a target in
+ the current working directory or which is the current working
+ directory. However, if the sourcepath is a local path, it can
+ refer to a target somewhere deeper in the directory structure. */
+ if (svn_path_is_url(sourcepath1))
+ {
+ const char *sp1_basename = svn_uri_basename(sourcepath1, pool);
+ const char *sp2_basename = svn_uri_basename(sourcepath2, pool);
+
+ if (strcmp(sp1_basename, sp2_basename) == 0)
+ {
+ const char *target_url;
+ const char *target_base;
+
+ SVN_ERR(svn_client_url_from_path2(&target_url, targetpath, ctx,
+ pool, pool));
+ target_base = svn_uri_basename(target_url, pool);
+
+ /* If the basename of the source is the same as the basename of
+ the cwd assume the cwd is the target. */
+ if (strcmp(sp1_basename, target_base) != 0)
+ {
+ svn_node_kind_t kind;
+
+ /* If the basename of the source differs from the basename
+ of the target. We still might assume the cwd is the
+ target, but first check if there is a file in the cwd
+ with the same name as the source basename. If there is,
+ then assume that file is the target. */
+ SVN_ERR(svn_io_check_path(sp1_basename, &kind, pool));
+ if (kind == svn_node_file)
+ {
+ targetpath = sp1_basename;
+ }
+ }
+ }
+ }
+ else if (strcmp(sourcepath1, sourcepath2) == 0)
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_io_check_path(sourcepath1, &kind, pool));
+ if (kind == svn_node_file)
+ {
+ targetpath = sourcepath1;
+ }
+ }
+ }
+
+ if (opt_state->extensions)
+ options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
+ else
+ options = NULL;
+
+ /* More input validation. */
+ if (opt_state->reintegrate)
+ {
+ if (opt_state->ignore_ancestry)
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--reintegrate cannot be used with "
+ "--ignore-ancestry"));
+
+ if (opt_state->record_only)
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--reintegrate cannot be used with "
+ "--record-only"));
+
+ if (opt_state->depth != svn_depth_unknown)
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--depth cannot be used with "
+ "--reintegrate"));
+
+ if (opt_state->force)
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--force cannot be used with "
+ "--reintegrate"));
+
+ if (two_sources_specified)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--reintegrate can only be used with "
+ "a single merge source"));
+ if (opt_state->allow_mixed_rev)
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--allow-mixed-revisions cannot be used "
+ "with --reintegrate"));
+ }
+
+ merge_err = run_merge(two_sources_specified,
+ sourcepath1, peg_revision1,
+ sourcepath2,
+ targetpath,
+ ranges_to_merge, first_range_start, first_range_end,
+ opt_state, options, ctx, pool);
+ if (merge_err && merge_err->apr_err
+ == SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING)
+ {
+ return svn_error_quick_wrap(
+ merge_err,
+ _("Merge tracking not possible, use --ignore-ancestry or\n"
+ "fix invalid mergeinfo in target with 'svn propset'"));
+ }
+
+ if (!opt_state->quiet)
+ {
+ svn_error_t *err = svn_cl__notifier_print_conflict_stats(
+ ctx->notify_baton2, pool);
+
+ merge_err = svn_error_compose_create(merge_err, err);
+ }
+
+ return svn_cl__may_need_force(merge_err);
+}
diff --git a/contrib/subversion/subversion/svn/mergeinfo-cmd.c b/contrib/subversion/subversion/svn/mergeinfo-cmd.c
new file mode 100644
index 0000000..a78c42a
--- /dev/null
+++ b/contrib/subversion/subversion/svn/mergeinfo-cmd.c
@@ -0,0 +1,349 @@
+/*
+ * mergeinfo-cmd.c -- Query merge-relative info.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_cmdline.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_types.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* Implements the svn_log_entry_receiver_t interface. */
+static svn_error_t *
+print_log_rev(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ if (log_entry->non_inheritable)
+ SVN_ERR(svn_cmdline_printf(pool, "r%ld*\n", log_entry->revision));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, "r%ld\n", log_entry->revision));
+
+ return SVN_NO_ERROR;
+}
+
+/* Draw a diagram (by printing text to the console) summarizing the state
+ * of merging between two branches, given the merge description
+ * indicated by YCA, BASE, RIGHT, TARGET, REINTEGRATE_LIKE. */
+static svn_error_t *
+mergeinfo_diagram(const char *yca_url,
+ const char *base_url,
+ const char *right_url,
+ const char *target_url,
+ svn_revnum_t yca_rev,
+ svn_revnum_t base_rev,
+ svn_revnum_t right_rev,
+ svn_revnum_t target_rev,
+ const char *repos_root_url,
+ svn_boolean_t target_is_wc,
+ svn_boolean_t reintegrate_like,
+ apr_pool_t *pool)
+{
+ /* The graph occupies 4 rows of text, and the annotations occupy
+ * another 2 rows above and 2 rows below. The graph is constructed
+ * from left to right in discrete sections ("columns"), each of which
+ * can have a different width (measured in characters). Each element in
+ * the array is either a text string of the appropriate width, or can
+ * be NULL to draw a blank cell. */
+#define ROWS 8
+#define COLS 4
+ const char *g[ROWS][COLS] = {{0}};
+ int col_width[COLS];
+ int row, col;
+
+ /* The YCA (that is, the branching point). And an ellipsis, because we
+ * don't show information about earlier merges */
+ g[0][0] = apr_psprintf(pool, " %-8ld ", yca_rev);
+ g[1][0] = " | ";
+ if (strcmp(yca_url, right_url) == 0)
+ {
+ g[2][0] = "-------| |--";
+ g[3][0] = " \\ ";
+ g[4][0] = " \\ ";
+ g[5][0] = " --| |--";
+ }
+ else if (strcmp(yca_url, target_url) == 0)
+ {
+ g[2][0] = " --| |--";
+ g[3][0] = " / ";
+ g[4][0] = " / ";
+ g[5][0] = "-------| |--";
+ }
+ else
+ {
+ g[2][0] = " --| |--";
+ g[3][0] = "... / ";
+ g[4][0] = " \\ ";
+ g[5][0] = " --| |--";
+ }
+
+ /* The last full merge */
+ if ((base_rev > yca_rev) && reintegrate_like)
+ {
+ g[2][2] = "---------";
+ g[3][2] = " / ";
+ g[4][2] = " / ";
+ g[5][2] = "---------";
+ g[6][2] = "| ";
+ g[7][2] = apr_psprintf(pool, "%-8ld ", base_rev);
+ }
+ else if (base_rev > yca_rev)
+ {
+ g[0][2] = apr_psprintf(pool, "%-8ld ", base_rev);
+ g[1][2] = "| ";
+ g[2][2] = "---------";
+ g[3][2] = " \\ ";
+ g[4][2] = " \\ ";
+ g[5][2] = "---------";
+ }
+ else
+ {
+ g[2][2] = "---------";
+ g[3][2] = " ";
+ g[4][2] = " ";
+ g[5][2] = "---------";
+ }
+
+ /* The tips of the branches */
+ {
+ g[0][3] = apr_psprintf(pool, "%-8ld", right_rev);
+ g[1][3] = "| ";
+ g[2][3] = "- ";
+ g[3][3] = " ";
+ g[4][3] = " ";
+ g[5][3] = "- ";
+ g[6][3] = "| ";
+ g[7][3] = target_is_wc ? "WC "
+ : apr_psprintf(pool, "%-8ld", target_rev);
+ }
+
+ /* Find the width of each column, so we know how to print blank cells */
+ for (col = 0; col < COLS; col++)
+ {
+ col_width[col] = 0;
+ for (row = 0; row < ROWS; row++)
+ {
+ if (g[row][col] && ((int)strlen(g[row][col]) > col_width[col]))
+ col_width[col] = (int)strlen(g[row][col]);
+ }
+ }
+
+ /* Column headings */
+ SVN_ERR(svn_cmdline_printf(pool,
+ " %s\n"
+ " | %s\n"
+ " | | %s\n"
+ " | | | %s\n"
+ "\n",
+ _("youngest common ancestor"), _("last full merge"),
+ _("tip of branch"), _("repository path")));
+
+ /* Print the diagram, row by row */
+ for (row = 0; row < ROWS; row++)
+ {
+ SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
+ for (col = 0; col < COLS; col++)
+ {
+ if (g[row][col])
+ {
+ SVN_ERR(svn_cmdline_fputs(g[row][col], stdout, pool));
+ }
+ else
+ {
+ /* Print <column-width> spaces */
+ SVN_ERR(svn_cmdline_printf(pool, "%*s", col_width[col], ""));
+ }
+ }
+ if (row == 2)
+ SVN_ERR(svn_cmdline_printf(pool, " %s",
+ svn_uri_skip_ancestor(repos_root_url, right_url, pool)));
+ if (row == 5)
+ SVN_ERR(svn_cmdline_printf(pool, " %s",
+ svn_uri_skip_ancestor(repos_root_url, target_url, pool)));
+ SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Display a summary of the state of merging between the two branches
+ * SOURCE_PATH_OR_URL@SOURCE_REVISION and
+ * TARGET_PATH_OR_URL@TARGET_REVISION. */
+static svn_error_t *
+mergeinfo_summary(
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *yca_url, *base_url, *right_url, *target_url;
+ svn_revnum_t yca_rev, base_rev, right_rev, target_rev;
+ const char *repos_root_url;
+ svn_boolean_t target_is_wc, is_reintegration;
+
+ target_is_wc = (! svn_path_is_url(target_path_or_url))
+ && (target_revision->kind == svn_opt_revision_unspecified
+ || target_revision->kind == svn_opt_revision_working);
+ SVN_ERR(svn_client_get_merging_summary(
+ &is_reintegration,
+ &yca_url, &yca_rev,
+ &base_url, &base_rev,
+ &right_url, &right_rev,
+ &target_url, &target_rev,
+ &repos_root_url,
+ source_path_or_url, source_revision,
+ target_path_or_url, target_revision,
+ ctx, pool, pool));
+
+ SVN_ERR(mergeinfo_diagram(yca_url, base_url, right_url, target_url,
+ yca_rev, base_rev, right_rev, target_rev,
+ repos_root_url, target_is_wc, is_reintegration,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__mergeinfo(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ const char *source, *target;
+ svn_opt_revision_t src_peg_revision, tgt_peg_revision;
+ svn_opt_revision_t *src_start_revision, *src_end_revision;
+ /* Default to depth empty. */
+ svn_depth_t depth = (opt_state->depth == svn_depth_unknown)
+ ? svn_depth_empty : opt_state->depth;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Parse the arguments: SOURCE[@REV] optionally followed by TARGET[@REV]. */
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Not enough arguments given"));
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+ SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source,
+ APR_ARRAY_IDX(targets, 0, const char *), pool));
+ if (targets->nelts == 2)
+ {
+ SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target,
+ APR_ARRAY_IDX(targets, 1, const char *),
+ pool));
+ }
+ else
+ {
+ target = "";
+ tgt_peg_revision.kind = svn_opt_revision_unspecified;
+ }
+
+ /* If no peg-rev was attached to the source URL, assume HEAD. */
+ /* ### But what if SOURCE is a WC path not a URL -- shouldn't we then use
+ * BASE (but not WORKING: that would be inconsistent with 'svn merge')? */
+ if (src_peg_revision.kind == svn_opt_revision_unspecified)
+ src_peg_revision.kind = svn_opt_revision_head;
+
+ /* If no peg-rev was attached to a URL target, then assume HEAD; if
+ no peg-rev was attached to a non-URL target, then assume BASE. */
+ /* ### But we would like to be able to examine a working copy with an
+ uncommitted merge in it, so change this to use WORKING not BASE? */
+ if (tgt_peg_revision.kind == svn_opt_revision_unspecified)
+ {
+ if (svn_path_is_url(target))
+ tgt_peg_revision.kind = svn_opt_revision_head;
+ else
+ tgt_peg_revision.kind = svn_opt_revision_base;
+ }
+
+ SVN_ERR_W(svn_cl__check_related_source_and_target(source, &src_peg_revision,
+ target, &tgt_peg_revision,
+ ctx, pool),
+ _("Source and target must be different but related branches"));
+
+ src_start_revision = &(opt_state->start_revision);
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ src_end_revision = src_start_revision;
+ else
+ src_end_revision = &(opt_state->end_revision);
+
+ /* Do the real work, depending on the requested data flavor. */
+ if (opt_state->show_revs == svn_cl__show_revs_merged)
+ {
+ SVN_ERR(svn_client_mergeinfo_log2(TRUE, target, &tgt_peg_revision,
+ source, &src_peg_revision,
+ src_start_revision,
+ src_end_revision,
+ print_log_rev, NULL,
+ TRUE, depth, NULL, ctx,
+ pool));
+ }
+ else if (opt_state->show_revs == svn_cl__show_revs_eligible)
+ {
+ SVN_ERR(svn_client_mergeinfo_log2(FALSE, target, &tgt_peg_revision,
+ source, &src_peg_revision,
+ src_start_revision,
+ src_end_revision,
+ print_log_rev, NULL,
+ TRUE, depth, NULL, ctx,
+ pool));
+ }
+ else
+ {
+ if ((opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ || (opt_state->end_revision.kind != svn_opt_revision_unspecified))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--revision (-r) option valid only with "
+ "--show-revs option"));
+ if (opt_state->depth != svn_depth_unknown)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Depth specification options valid only "
+ "with --show-revs option"));
+
+ SVN_ERR(mergeinfo_summary(source, &src_peg_revision,
+ target, &tgt_peg_revision,
+ ctx, pool));
+ }
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/mkdir-cmd.c b/contrib/subversion/subversion/svn/mkdir-cmd.c
new file mode 100644
index 0000000..64cb4f9
--- /dev/null
+++ b/contrib/subversion/subversion/svn/mkdir-cmd.c
@@ -0,0 +1,104 @@
+/*
+ * mkdir-cmd.c -- Subversion mkdir command
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__mkdir(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ svn_error_t *err;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ SVN_ERR(svn_cl__assert_homogeneous_target_type(targets));
+
+ if (! svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)))
+ {
+ ctx->log_msg_func3 = NULL;
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
+ {
+ return svn_error_create
+ (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
+ }
+ }
+ else
+ {
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
+ NULL, ctx->config, pool));
+ }
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ err = svn_client_mkdir4(targets, opt_state->parents,
+ opt_state->revprop_table,
+ (opt_state->quiet ? NULL : svn_cl__print_commit_info),
+ NULL, ctx, pool);
+
+ if (ctx->log_msg_func3)
+ err = svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool);
+
+ if (err)
+ {
+ if (err->apr_err == APR_EEXIST)
+ return svn_error_quick_wrap
+ (err, _("Try 'svn add' or 'svn add --non-recursive' instead?"));
+ else if (!(opt_state->parents) &&
+ (APR_STATUS_IS_ENOENT(err->apr_err) || /* in wc */
+ err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
+ err->apr_err == SVN_ERR_FS_NOT_FOUND /* all ra layers */))
+ return svn_error_quick_wrap
+ (err, _("Try 'svn mkdir --parents' instead?"));
+ else
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/move-cmd.c b/contrib/subversion/subversion/svn/move-cmd.c
new file mode 100644
index 0000000..bb71043
--- /dev/null
+++ b/contrib/subversion/subversion/svn/move-cmd.c
@@ -0,0 +1,105 @@
+/*
+ * move-cmd.c -- Subversion move command
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__move(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ const char *dst_path;
+ svn_error_t *err;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, TRUE, pool));
+
+ if (targets->nelts < 2)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ if (opt_state->start_revision.kind != svn_opt_revision_unspecified
+ && opt_state->start_revision.kind != svn_opt_revision_head)
+ {
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot specify revisions (except HEAD) with move operations"));
+ }
+
+ dst_path = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *);
+ apr_array_pop(targets);
+
+ if (! svn_path_is_url(dst_path))
+ {
+ ctx->log_msg_func3 = NULL;
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
+ return svn_error_create
+ (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
+ }
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
+ NULL, ctx->config, pool));
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ err = svn_client_move7(targets, dst_path,
+ TRUE /* move_as_child */,
+ opt_state->parents /* make_parents */,
+ opt_state->allow_mixed_rev /* allow_mixed_revisions*/,
+ FALSE /* metadata_only */,
+ opt_state->revprop_table,
+ (opt_state->quiet ? NULL : svn_cl__print_commit_info),
+ NULL, ctx, pool);
+
+ if (err)
+ err = svn_cl__may_need_force(err);
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool));
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/notify.c b/contrib/subversion/subversion/svn/notify.c
new file mode 100644
index 0000000..6498fb1
--- /dev/null
+++ b/contrib/subversion/subversion/svn/notify.c
@@ -0,0 +1,1222 @@
+/*
+ * notify.c: feedback handlers for cmdline client.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_sorts.h"
+#include "svn_hash.h"
+#include "cl.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_dep_compat.h"
+
+#include "svn_private_config.h"
+
+
+/* Baton for notify and friends. */
+struct notify_baton
+{
+ svn_boolean_t received_some_change;
+ svn_boolean_t is_checkout;
+ svn_boolean_t is_export;
+ svn_boolean_t is_wc_to_repos_copy;
+ svn_boolean_t sent_first_txdelta;
+ svn_boolean_t in_external;
+ svn_boolean_t had_print_error; /* Used to not keep printing error messages
+ when we've already had one print error. */
+
+ svn_cl__conflict_stats_t *conflict_stats;
+
+ /* The cwd, for use in decomposing absolute paths. */
+ const char *path_prefix;
+};
+
+/* Conflict stats for operations such as update and merge. */
+struct svn_cl__conflict_stats_t
+{
+ apr_pool_t *stats_pool;
+ apr_hash_t *text_conflicts, *prop_conflicts, *tree_conflicts;
+ int text_conflicts_resolved, prop_conflicts_resolved, tree_conflicts_resolved;
+ int skipped_paths;
+};
+
+svn_cl__conflict_stats_t *
+svn_cl__conflict_stats_create(apr_pool_t *pool)
+{
+ svn_cl__conflict_stats_t *conflict_stats
+ = apr_palloc(pool, sizeof(*conflict_stats));
+
+ conflict_stats->stats_pool = pool;
+ conflict_stats->text_conflicts = apr_hash_make(pool);
+ conflict_stats->prop_conflicts = apr_hash_make(pool);
+ conflict_stats->tree_conflicts = apr_hash_make(pool);
+ conflict_stats->text_conflicts_resolved = 0;
+ conflict_stats->prop_conflicts_resolved = 0;
+ conflict_stats->tree_conflicts_resolved = 0;
+ conflict_stats->skipped_paths = 0;
+ return conflict_stats;
+}
+
+/* Add the PATH (as a key, with a meaningless value) into the HASH in NB. */
+static void
+store_path(struct notify_baton *nb, apr_hash_t *hash, const char *path)
+{
+ svn_hash_sets(hash, apr_pstrdup(nb->conflict_stats->stats_pool, path), "");
+}
+
+void
+svn_cl__conflict_stats_resolved(svn_cl__conflict_stats_t *conflict_stats,
+ const char *path_local,
+ svn_wc_conflict_kind_t conflict_kind)
+{
+ switch (conflict_kind)
+ {
+ case svn_wc_conflict_kind_text:
+ if (svn_hash_gets(conflict_stats->text_conflicts, path_local))
+ {
+ svn_hash_sets(conflict_stats->text_conflicts, path_local, NULL);
+ conflict_stats->text_conflicts_resolved++;
+ }
+ break;
+ case svn_wc_conflict_kind_property:
+ if (svn_hash_gets(conflict_stats->prop_conflicts, path_local))
+ {
+ svn_hash_sets(conflict_stats->prop_conflicts, path_local, NULL);
+ conflict_stats->prop_conflicts_resolved++;
+ }
+ break;
+ case svn_wc_conflict_kind_tree:
+ if (svn_hash_gets(conflict_stats->tree_conflicts, path_local))
+ {
+ svn_hash_sets(conflict_stats->tree_conflicts, path_local, NULL);
+ conflict_stats->tree_conflicts_resolved++;
+ }
+ break;
+ }
+}
+
+static const char *
+remaining_str(apr_pool_t *pool, int n_remaining)
+{
+ return apr_psprintf(pool, Q_("%d remaining",
+ "%d remaining",
+ n_remaining),
+ n_remaining);
+}
+
+static const char *
+resolved_str(apr_pool_t *pool, int n_resolved)
+{
+ return apr_psprintf(pool, Q_("and %d already resolved",
+ "and %d already resolved",
+ n_resolved),
+ n_resolved);
+}
+
+svn_error_t *
+svn_cl__notifier_print_conflict_stats(void *baton, apr_pool_t *scratch_pool)
+{
+ struct notify_baton *nb = baton;
+ int n_text = apr_hash_count(nb->conflict_stats->text_conflicts);
+ int n_prop = apr_hash_count(nb->conflict_stats->prop_conflicts);
+ int n_tree = apr_hash_count(nb->conflict_stats->tree_conflicts);
+ int n_text_r = nb->conflict_stats->text_conflicts_resolved;
+ int n_prop_r = nb->conflict_stats->prop_conflicts_resolved;
+ int n_tree_r = nb->conflict_stats->tree_conflicts_resolved;
+
+ if (n_text > 0 || n_text_r > 0
+ || n_prop > 0 || n_prop_r > 0
+ || n_tree > 0 || n_tree_r > 0
+ || nb->conflict_stats->skipped_paths > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _("Summary of conflicts:\n")));
+
+ if (n_text_r == 0 && n_prop_r == 0 && n_tree_r == 0)
+ {
+ if (n_text > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Text conflicts: %d\n"),
+ n_text));
+ if (n_prop > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Property conflicts: %d\n"),
+ n_prop));
+ if (n_tree > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Tree conflicts: %d\n"),
+ n_tree));
+ }
+ else
+ {
+ if (n_text > 0 || n_text_r > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Text conflicts: %s (%s)\n"),
+ remaining_str(scratch_pool, n_text),
+ resolved_str(scratch_pool, n_text_r)));
+ if (n_prop > 0 || n_prop_r > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Property conflicts: %s (%s)\n"),
+ remaining_str(scratch_pool, n_prop),
+ resolved_str(scratch_pool, n_prop_r)));
+ if (n_tree > 0 || n_tree_r > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Tree conflicts: %s (%s)\n"),
+ remaining_str(scratch_pool, n_tree),
+ resolved_str(scratch_pool, n_tree_r)));
+ }
+ if (nb->conflict_stats->skipped_paths > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Skipped paths: %d\n"),
+ nb->conflict_stats->skipped_paths));
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_wc_notify_func2_t'.
+ * NOTE: This function can't fail, so we just ignore any print errors. */
+static void
+notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
+{
+ struct notify_baton *nb = baton;
+ char statchar_buf[5] = " ";
+ const char *path_local;
+ svn_error_t *err;
+
+ if (n->url)
+ path_local = n->url;
+ else
+ {
+ if (n->path_prefix)
+ path_local = svn_cl__local_style_skip_ancestor(n->path_prefix, n->path,
+ pool);
+ else /* skip nb->path_prefix, if it's non-null */
+ path_local = svn_cl__local_style_skip_ancestor(nb->path_prefix, n->path,
+ pool);
+ }
+
+ switch (n->action)
+ {
+ case svn_wc_notify_skip:
+ nb->conflict_stats->skipped_paths++;
+ if (n->content_state == svn_wc_notify_state_missing)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped missing target: '%s'\n"),
+ path_local)))
+ goto print_error;
+ }
+ else if (n->content_state == svn_wc_notify_state_source_missing)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped target: '%s' -- copy-source is missing\n"),
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped '%s'\n"), path_local)))
+ goto print_error;
+ }
+ break;
+ case svn_wc_notify_update_skip_obstruction:
+ nb->conflict_stats->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- An obstructing working copy was found\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_skip_working_only:
+ nb->conflict_stats->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Has no versioned parent\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_skip_access_denied:
+ nb->conflict_stats->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Access denied\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_skip_conflicted:
+ nb->conflict_stats->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Node remains in conflict\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_delete:
+ case svn_wc_notify_exclude:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "D %s\n", path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_broken_lock:
+ if ((err = svn_cmdline_printf(pool, "B %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_external_removed:
+ nb->received_some_change = TRUE;
+ if (n->err && n->err->message)
+ {
+ if ((err = svn_cmdline_printf(pool, "Removed external '%s': %s\n",
+ path_local, n->err->message)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "Removed external '%s'\n",
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_left_local_modifications:
+ if ((err = svn_cmdline_printf(pool, "Left local modifications as '%s'\n",
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_replace:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "R %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_add:
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
+ if ((err = svn_cmdline_printf(pool, "C %s\n", path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "A %s\n", path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_exists:
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
+ statchar_buf[0] = 'C';
+ }
+ else
+ statchar_buf[0] = 'E';
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_merged)
+ statchar_buf[1] = 'G';
+
+ if ((err = svn_cmdline_printf(pool, "%s %s\n", statchar_buf, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_restore:
+ if ((err = svn_cmdline_printf(pool, _("Restored '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revert:
+ if ((err = svn_cmdline_printf(pool, _("Reverted '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_revert:
+ if (( err = svn_cmdline_printf(pool, _("Failed to revert '%s' -- "
+ "try updating instead.\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_resolved:
+ if ((err = svn_cmdline_printf(pool,
+ _("Resolved conflicted state of '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_add:
+ /* We *should* only get the MIME_TYPE if PATH is a file. If we
+ do get it, and the mime-type is not textual, note that this
+ is a binary addition. */
+ if (n->mime_type && (svn_mime_type_is_binary(n->mime_type)))
+ {
+ if ((err = svn_cmdline_printf(pool, "A (bin) %s\n",
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "A %s\n",
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_delete:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "D %s\n",
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_patch:
+ {
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
+ statchar_buf[0] = 'C';
+ }
+ else if (n->kind == svn_node_file)
+ {
+ if (n->content_state == svn_wc_notify_state_merged)
+ statchar_buf[0] = 'G';
+ else if (n->content_state == svn_wc_notify_state_changed)
+ statchar_buf[0] = 'U';
+ }
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_changed)
+ statchar_buf[1] = 'U';
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
+ {
+ if ((err = svn_cmdline_printf(pool, "%s %s\n",
+ statchar_buf, path_local)))
+ goto print_error;
+ }
+ }
+ break;
+
+ case svn_wc_notify_patch_applied_hunk:
+ nb->received_some_change = TRUE;
+ if (n->hunk_original_start != n->hunk_matched_line)
+ {
+ apr_uint64_t off;
+ const char *s;
+ const char *minus;
+
+ if (n->hunk_matched_line > n->hunk_original_start)
+ {
+ /* If we are patching from the start of an empty file,
+ it is nicer to show offset 0 */
+ if (n->hunk_original_start == 0 && n->hunk_matched_line == 1)
+ off = 0; /* No offset, just adding */
+ else
+ off = n->hunk_matched_line - n->hunk_original_start;
+
+ minus = "";
+ }
+ else
+ {
+ off = n->hunk_original_start - n->hunk_matched_line;
+ minus = "-";
+ }
+
+ /* ### We're creating the localized strings without
+ * ### APR_INT64_T_FMT since it isn't translator-friendly */
+ if (n->hunk_fuzz)
+ {
+
+ if (n->prop_name)
+ {
+ s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with offset %s");
+
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT
+ " and fuzz %lu (%s)\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->hunk_fuzz,
+ n->prop_name);
+ }
+ else
+ {
+ s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with offset %s");
+
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT
+ " and fuzz %lu\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->hunk_fuzz);
+ }
+
+ if (err)
+ goto print_error;
+ }
+ else
+ {
+
+ if (n->prop_name)
+ {
+ s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with offset %s");
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT" (%s)\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->prop_name);
+ }
+ else
+ {
+ s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with offset %s");
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT"\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off);
+ }
+
+ if (err)
+ goto print_error;
+ }
+ }
+ else if (n->hunk_fuzz)
+ {
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with fuzz %lu (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->hunk_fuzz,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with fuzz %lu\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->hunk_fuzz);
+ if (err)
+ goto print_error;
+
+ }
+ break;
+
+ case svn_wc_notify_patch_rejected_hunk:
+ nb->received_some_change = TRUE;
+
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> rejected hunk "
+ "## -%lu,%lu +%lu,%lu ## (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> rejected hunk "
+ "@@ -%lu,%lu +%lu,%lu @@\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_patch_hunk_already_applied:
+ nb->received_some_change = TRUE;
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> hunk "
+ "## -%lu,%lu +%lu,%lu ## "
+ "already applied (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> hunk "
+ "@@ -%lu,%lu +%lu,%lu @@ "
+ "already applied\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_update:
+ case svn_wc_notify_merge_record_info:
+ {
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
+ statchar_buf[0] = 'C';
+ }
+ else if (n->kind == svn_node_file)
+ {
+ if (n->content_state == svn_wc_notify_state_merged)
+ statchar_buf[0] = 'G';
+ else if (n->content_state == svn_wc_notify_state_changed)
+ statchar_buf[0] = 'U';
+ }
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_merged)
+ statchar_buf[1] = 'G';
+ else if (n->prop_state == svn_wc_notify_state_changed)
+ statchar_buf[1] = 'U';
+
+ if (n->lock_state == svn_wc_notify_lock_state_unlocked)
+ statchar_buf[2] = 'B';
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
+ nb->received_some_change = TRUE;
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' '
+ || statchar_buf[2] != ' ')
+ {
+ if ((err = svn_cmdline_printf(pool, "%s %s\n",
+ statchar_buf, path_local)))
+ goto print_error;
+ }
+ }
+ break;
+
+ case svn_wc_notify_update_external:
+ /* Remember that we're now "inside" an externals definition. */
+ nb->in_external = TRUE;
+
+ /* Currently this is used for checkouts and switches too. If we
+ want different output, we'll have to add new actions. */
+ if ((err = svn_cmdline_printf(pool,
+ _("\nFetching external item into '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_external:
+ /* If we are currently inside the handling of an externals
+ definition, then we can simply present n->err as a warning
+ and feel confident that after this, we aren't handling that
+ externals definition any longer. */
+ if (nb->in_external)
+ {
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ nb->in_external = FALSE;
+ if ((err = svn_cmdline_printf(pool, "\n")))
+ goto print_error;
+ }
+ /* Otherwise, we'll just print two warnings. Why? Because
+ svn_handle_warning2() only shows the single "best message",
+ but we have two pretty important ones: that the external at
+ '/some/path' didn't pan out, and then the more specific
+ reason why (from n->err). */
+ else
+ {
+ svn_error_t *warn_err =
+ svn_error_createf(SVN_ERR_BASE, NULL,
+ _("Error handling externals definition for '%s':"),
+ path_local);
+ svn_handle_warning2(stderr, warn_err, "svn: ");
+ svn_error_clear(warn_err);
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ }
+ break;
+
+ case svn_wc_notify_update_started:
+ if (! (nb->in_external ||
+ nb->is_checkout ||
+ nb->is_export))
+ {
+ if ((err = svn_cmdline_printf(pool, _("Updating '%s':\n"),
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_update_completed:
+ {
+ if (SVN_IS_VALID_REVNUM(n->revision))
+ {
+ if (nb->is_export)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Exported external at revision %ld.\n")
+ : _("Exported revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else if (nb->is_checkout)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Checked out external at revision %ld.\n")
+ : _("Checked out revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else
+ {
+ if (nb->received_some_change)
+ {
+ nb->received_some_change = FALSE;
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Updated external to revision %ld.\n")
+ : _("Updated to revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External at revision %ld.\n")
+ : _("At revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ }
+ }
+ else /* no revision */
+ {
+ if (nb->is_export)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External export complete.\n")
+ : _("Export complete.\n"))))
+ goto print_error;
+ }
+ else if (nb->is_checkout)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External checkout complete.\n")
+ : _("Checkout complete.\n"))))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External update complete.\n")
+ : _("Update complete.\n"))))
+ goto print_error;
+ }
+ }
+ }
+
+ if (nb->in_external)
+ {
+ nb->in_external = FALSE;
+ if ((err = svn_cmdline_printf(pool, "\n")))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_status_external:
+ if ((err = svn_cmdline_printf
+ (pool, _("\nPerforming status on external item at '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_status_completed:
+ if (SVN_IS_VALID_REVNUM(n->revision))
+ if ((err = svn_cmdline_printf(pool,
+ _("Status against revision: %6ld\n"),
+ n->revision)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_modified:
+ /* xgettext: Align the %s's on this and the following 4 messages */
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Sending copy of %s\n")
+ : _("Sending %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_added:
+ case svn_wc_notify_commit_copied:
+ if (n->mime_type && svn_mime_type_is_binary(n->mime_type))
+ {
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Adding copy of (bin) %s\n")
+ : _("Adding (bin) %s\n"),
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Adding copy of %s\n")
+ : _("Adding %s\n"),
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_commit_deleted:
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Deleting copy of %s\n")
+ : _("Deleting %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_replaced:
+ case svn_wc_notify_commit_copied_replaced:
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Replacing copy of %s\n")
+ : _("Replacing %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_postfix_txdelta:
+ if (! nb->sent_first_txdelta)
+ {
+ nb->sent_first_txdelta = TRUE;
+ if ((err = svn_cmdline_printf(pool,
+ _("Transmitting file data "))))
+ goto print_error;
+ }
+
+ if ((err = svn_cmdline_printf(pool, ".")))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_locked:
+ if ((err = svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
+ path_local, n->lock->owner)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_unlocked:
+ if ((err = svn_cmdline_printf(pool, _("'%s' unlocked.\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_lock:
+ case svn_wc_notify_failed_unlock:
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ break;
+
+ case svn_wc_notify_changelist_set:
+ if ((err = svn_cmdline_printf(pool, "A [%s] %s\n",
+ n->changelist_name, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_changelist_clear:
+ case svn_wc_notify_changelist_moved:
+ if ((err = svn_cmdline_printf(pool,
+ "D [%s] %s\n",
+ n->changelist_name, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_begin:
+ if (n->merge_range == NULL)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging differences between "
+ "repository URLs into '%s':\n"),
+ path_local);
+ else if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(pool, _("--- Merging r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging r%ld through r%ld into "
+ "'%s':\n"),
+ n->merge_range->start + 1,
+ n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging r%ld through r%ld "
+ "into '%s':\n"),
+ n->merge_range->start,
+ n->merge_range->end + 1, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_record_info_begin:
+ if (!n->merge_range)
+ {
+ err = svn_cmdline_printf(pool,
+ _("--- Recording mergeinfo for merge "
+ "between repository URLs into '%s':\n"),
+ path_local);
+ }
+ else
+ {
+ if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for merge of r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for reverse merge of r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for merge of r%ld through r%ld into '%s':\n"),
+ n->merge_range->start + 1, n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for reverse merge of r%ld through r%ld into '%s':\n"),
+ n->merge_range->start, n->merge_range->end + 1, path_local);
+ }
+
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_elide_info:
+ if ((err = svn_cmdline_printf(pool,
+ _("--- Eliding mergeinfo from '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_foreign_merge_begin:
+ if (n->merge_range == NULL)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging differences between "
+ "foreign repository URLs into '%s':\n"),
+ path_local);
+ else if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging (from foreign repository) "
+ "r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging (from foreign "
+ "repository) r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging (from foreign repository) "
+ "r%ld through r%ld into '%s':\n"),
+ n->merge_range->start + 1,
+ n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging (from foreign "
+ "repository) r%ld through r%ld into "
+ "'%s':\n"),
+ n->merge_range->start,
+ n->merge_range->end + 1, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_tree_conflict:
+ store_path(nb, nb->conflict_stats->tree_conflicts, path_local);
+ if ((err = svn_cmdline_printf(pool, " C %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_add:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " A %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_update:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " U %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_delete:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " D %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_modified:
+ case svn_wc_notify_property_added:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' set on '%s'\n"),
+ n->prop_name, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_deleted:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' deleted from '%s'.\n"),
+ n->prop_name, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_deleted_nonexistent:
+ err = svn_cmdline_printf(pool,
+ _("Attempting to delete nonexistent "
+ "property '%s' on '%s'\n"), n->prop_name,
+ path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revprop_set:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' set on repository revision %ld\n"),
+ n->prop_name, n->revision);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revprop_deleted:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' deleted from repository revision %ld\n"),
+ n->prop_name, n->revision);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_upgraded_path:
+ err = svn_cmdline_printf(pool, _("Upgraded '%s'\n"), path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_url_redirect:
+ err = svn_cmdline_printf(pool, _("Redirecting to URL '%s':\n"),
+ n->url);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_path_nonexistent:
+ err = svn_cmdline_printf(pool, "%s\n",
+ apr_psprintf(pool, _("'%s' is not under version control"),
+ path_local));
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_conflict_resolver_starting:
+ /* Once all operations invoke the interactive conflict resolution after
+ * they've completed, we can run svn_cl__notifier_print_conflict_stats()
+ * here. */
+ break;
+
+ case svn_wc_notify_conflict_resolver_done:
+ break;
+
+ case svn_wc_notify_foreign_copy_begin:
+ if (n->merge_range == NULL)
+ {
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Copying from foreign repository URL '%s':\n"),
+ n->url);
+ if (err)
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_move_broken:
+ err = svn_cmdline_printf(pool,
+ _("Breaking move with source path '%s'\n"),
+ path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ default:
+ break;
+ }
+
+ if ((err = svn_cmdline_fflush(stdout)))
+ goto print_error;
+
+ return;
+
+ print_error:
+ /* If we had no errors before, print this error to stderr. Else, don't print
+ anything. The user already knows there were some output errors,
+ so there is no point in flooding her with an error per notification. */
+ if (!nb->had_print_error)
+ {
+ nb->had_print_error = TRUE;
+ /* Issue #3014:
+ * Don't print anything on broken pipes. The pipe was likely
+ * closed by the process at the other end. We expect that
+ * process to perform error reporting as necessary.
+ *
+ * ### This assumes that there is only one error in a chain for
+ * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
+ if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
+ svn_handle_error2(err, stderr, FALSE, "svn: ");
+ }
+ svn_error_clear(err);
+}
+
+
+svn_error_t *
+svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
+ void **notify_baton_p,
+ svn_cl__conflict_stats_t *conflict_stats,
+ apr_pool_t *pool)
+{
+ struct notify_baton *nb = apr_pcalloc(pool, sizeof(*nb));
+
+ nb->received_some_change = FALSE;
+ nb->sent_first_txdelta = FALSE;
+ nb->is_checkout = FALSE;
+ nb->is_export = FALSE;
+ nb->is_wc_to_repos_copy = FALSE;
+ nb->in_external = FALSE;
+ nb->had_print_error = FALSE;
+ nb->conflict_stats = conflict_stats;
+ SVN_ERR(svn_dirent_get_absolute(&nb->path_prefix, "", pool));
+
+ *notify_func_p = notify;
+ *notify_baton_p = nb;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__notifier_mark_checkout(void *baton)
+{
+ struct notify_baton *nb = baton;
+
+ nb->is_checkout = TRUE;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__notifier_mark_export(void *baton)
+{
+ struct notify_baton *nb = baton;
+
+ nb->is_export = TRUE;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__notifier_mark_wc_to_repos_copy(void *baton)
+{
+ struct notify_baton *nb = baton;
+
+ nb->is_wc_to_repos_copy = TRUE;
+ return SVN_NO_ERROR;
+}
+
+void
+svn_cl__check_externals_failed_notify_wrapper(void *baton,
+ const svn_wc_notify_t *n,
+ apr_pool_t *pool)
+{
+ struct svn_cl__check_externals_failed_notify_baton *nwb = baton;
+
+ if (n->action == svn_wc_notify_failed_external)
+ nwb->had_externals_error = TRUE;
+
+ if (nwb->wrapped_func)
+ nwb->wrapped_func(nwb->wrapped_baton, n, pool);
+}
diff --git a/contrib/subversion/subversion/svn/patch-cmd.c b/contrib/subversion/subversion/svn/patch-cmd.c
new file mode 100644
index 0000000..83707c6
--- /dev/null
+++ b/contrib/subversion/subversion/svn/patch-cmd.c
@@ -0,0 +1,98 @@
+/*
+ * patch-cmd.c -- Apply changes to a working copy.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_types.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__patch(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state;
+ svn_client_ctx_t *ctx;
+ apr_array_header_t *targets;
+ const char *abs_patch_path;
+ const char *patch_path;
+ const char *abs_target_path;
+ const char *target_path;
+
+ opt_state = ((svn_cl__cmd_baton_t *)baton)->opt_state;
+ ctx = ((svn_cl__cmd_baton_t *)baton)->ctx;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+ patch_path = APR_ARRAY_IDX(targets, 0, const char *);
+
+ SVN_ERR(svn_cl__check_target_is_local_path(patch_path));
+
+ SVN_ERR(svn_dirent_get_absolute(&abs_patch_path, patch_path, pool));
+
+ if (targets->nelts == 1)
+ target_path = ""; /* "" is the canonical form of "." */
+ else
+ {
+ target_path = APR_ARRAY_IDX(targets, 1, const char *);
+
+ SVN_ERR(svn_cl__check_target_is_local_path(target_path));
+ }
+ SVN_ERR(svn_dirent_get_absolute(&abs_target_path, target_path, pool));
+
+ SVN_ERR(svn_client_patch(abs_patch_path, abs_target_path,
+ opt_state->dry_run, opt_state->strip,
+ opt_state->reverse_diff,
+ opt_state->ignore_whitespace,
+ TRUE, NULL, NULL, ctx, pool));
+
+
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cl__notifier_print_conflict_stats(ctx->notify_baton2, pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/propdel-cmd.c b/contrib/subversion/subversion/svn/propdel-cmd.c
new file mode 100644
index 0000000..28c9597
--- /dev/null
+++ b/contrib/subversion/subversion/svn/propdel-cmd.c
@@ -0,0 +1,103 @@
+/*
+ * propdel-cmd.c -- Remove property from files/dirs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_path.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__propdel(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *pname, *pname_utf8;
+ apr_array_header_t *args, *targets;
+
+ /* Get the property's name (and a UTF-8 version of that name). */
+ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
+ pname = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool));
+ /* No need to check svn_prop_name_is_valid for *deleting*
+ properties, and it may even be useful to allow, in case invalid
+ properties sneaked through somehow. */
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+
+ /* Add "." if user passed 0 file arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ if (opt_state->revprop) /* operate on a revprop */
+ {
+ svn_revnum_t rev;
+ const char *URL;
+
+ SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
+ &URL, ctx, pool));
+
+ /* Let libsvn_client do the real work. */
+ SVN_ERR(svn_client_revprop_set2(pname_utf8, NULL, NULL,
+ URL, &(opt_state->start_revision),
+ &rev, FALSE, ctx, pool));
+ }
+ else if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("Cannot specify revision for deleting versioned property '%s'"),
+ pname);
+ }
+ else /* operate on a normal, versioned property (not a revprop) */
+ {
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ /* For each target, remove the property PNAME. */
+ SVN_ERR(svn_client_propset_local(pname_utf8, NULL, targets,
+ opt_state->depth, FALSE,
+ opt_state->changelists, ctx, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/propedit-cmd.c b/contrib/subversion/subversion/svn/propedit-cmd.c
new file mode 100644
index 0000000..520fe6c
--- /dev/null
+++ b/contrib/subversion/subversion/svn/propedit-cmd.c
@@ -0,0 +1,356 @@
+/*
+ * propedit-cmd.c -- Edit properties of files/dirs using $EDITOR
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_wc.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_props.h"
+#include "cl.h"
+
+#include "private/svn_cmdline_private.h"
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+struct commit_info_baton
+{
+ const char *pname_utf8;
+ const char *target_local;
+};
+
+static svn_error_t *
+commit_info_handler(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct commit_info_baton *cib = baton;
+
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Set new value for property '%s' on '%s'\n"),
+ cib->pname_utf8, cib->target_local));
+ SVN_ERR(svn_cl__print_commit_info(commit_info, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__propedit(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *pname, *pname_utf8;
+ apr_array_header_t *args, *targets;
+
+ /* Validate the input and get the property's name (and a UTF-8
+ version of that name). */
+ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
+ pname = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool));
+ if (! svn_prop_name_is_valid(pname_utf8))
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid Subversion property name"),
+ pname_utf8);
+ if (!opt_state->force)
+ SVN_ERR(svn_cl__check_svn_prop_name(pname_utf8, opt_state->revprop,
+ svn_cl__prop_use_edit, pool));
+
+ if (opt_state->encoding && !svn_prop_needs_translation(pname_utf8))
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("--encoding option applies only to textual"
+ " Subversion-controlled properties"));
+
+ /* Suck up all the remaining arguments into a targets array */
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* We do our own notifications */
+ ctx->notify_func2 = NULL;
+
+ if (opt_state->revprop) /* operate on a revprop */
+ {
+ svn_revnum_t rev;
+ const char *URL;
+ svn_string_t *propval;
+ svn_string_t original_propval;
+ const char *temp_dir;
+
+ /* Implicit "." is okay for revision properties; it just helps
+ us find the right repository. */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
+ &URL, ctx, pool));
+
+ /* Fetch the current property. */
+ SVN_ERR(svn_client_revprop_get(pname_utf8, &propval,
+ URL, &(opt_state->start_revision),
+ &rev, ctx, pool));
+
+ if (! propval)
+ {
+ propval = svn_string_create_empty(pool);
+ /* This is how we signify to svn_client_revprop_set2() that
+ we want it to check that the original value hasn't
+ changed, but that that original value was non-existent: */
+ original_propval.data = NULL; /* and .len is ignored */
+ }
+ else
+ {
+ original_propval = *propval;
+ }
+
+ /* Run the editor on a temporary file which contains the
+ original property value... */
+ SVN_ERR(svn_io_temp_dir(&temp_dir, pool));
+ SVN_ERR(svn_cmdline__edit_string_externally(
+ &propval, NULL,
+ opt_state->editor_cmd, temp_dir,
+ propval, "svn-prop",
+ ctx->config,
+ svn_prop_needs_translation(pname_utf8),
+ opt_state->encoding, pool));
+
+ /* ...and re-set the property's value accordingly. */
+ if (propval)
+ {
+ SVN_ERR(svn_client_revprop_set2(pname_utf8,
+ propval, &original_propval,
+ URL, &(opt_state->start_revision),
+ &rev, opt_state->force, ctx, pool));
+
+ SVN_ERR
+ (svn_cmdline_printf
+ (pool,
+ _("Set new value for property '%s' on revision %ld\n"),
+ pname_utf8, rev));
+ }
+ else
+ {
+ SVN_ERR(svn_cmdline_printf
+ (pool, _("No changes to property '%s' on revision %ld\n"),
+ pname_utf8, rev));
+ }
+ }
+ else if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ return svn_error_createf
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Cannot specify revision for editing versioned property '%s'"),
+ pname_utf8);
+ }
+ else /* operate on a normal, versioned property (not a revprop) */
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ struct commit_info_baton cib;
+ int i;
+
+ /* The customary implicit dot rule has been prone to user error
+ * here. For example, Jon Trowbridge <trow@gnu.og> did
+ *
+ * $ svn propedit HACKING
+ *
+ * and then when he closed his editor, he was surprised to see
+ *
+ * Set new value for property 'HACKING' on ''
+ *
+ * ...meaning that the property named 'HACKING' had been set on
+ * the current working directory, with the value taken from the
+ * editor. So we don't do the implicit dot thing anymore; an
+ * explicit target is always required when editing a versioned
+ * property.
+ */
+ if (targets->nelts == 0)
+ {
+ return svn_error_create
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Explicit target argument required"));
+ }
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ cib.pname_utf8 = pname_utf8;
+
+ /* For each target, edit the property PNAME. */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ apr_hash_t *props;
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_string_t *propval, *edited_propval;
+ const char *base_dir = target;
+ const char *target_local;
+ const char *abspath_or_url;
+ svn_node_kind_t kind;
+ svn_opt_revision_t peg_revision;
+ svn_revnum_t base_rev = SVN_INVALID_REVNUM;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ if (!svn_path_is_url(target))
+ SVN_ERR(svn_dirent_get_absolute(&abspath_or_url, target, subpool));
+ else
+ abspath_or_url = target;
+
+ /* Propedits can only happen on HEAD or the working copy, so
+ the peg revision can be as unspecified. */
+ peg_revision.kind = svn_opt_revision_unspecified;
+
+ /* Fetch the current property. */
+ SVN_ERR(svn_client_propget5(&props, NULL, pname_utf8, abspath_or_url,
+ &peg_revision,
+ &(opt_state->start_revision),
+ &base_rev, svn_depth_empty,
+ NULL, ctx, subpool, subpool));
+
+ /* Get the property value. */
+ propval = svn_hash_gets(props, abspath_or_url);
+ if (! propval)
+ propval = svn_string_create_empty(subpool);
+
+ if (svn_path_is_url(target))
+ {
+ /* For URLs, put the temporary file in the current directory. */
+ base_dir = ".";
+ }
+ else
+ {
+ if (opt_state->message || opt_state->filedata ||
+ opt_state->revprop_table)
+ {
+ return svn_error_create
+ (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
+ }
+
+ /* Split the path if it is a file path. */
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath_or_url,
+ FALSE, FALSE, subpool));
+
+ if (kind == svn_node_none)
+ return svn_error_createf(
+ SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("'%s' does not appear to be a working copy path"), target);
+ if (kind == svn_node_file)
+ base_dir = svn_dirent_dirname(target, subpool);
+ }
+
+ /* Run the editor on a temporary file which contains the
+ original property value... */
+ SVN_ERR(svn_cmdline__edit_string_externally(&edited_propval, NULL,
+ opt_state->editor_cmd,
+ base_dir,
+ propval,
+ "svn-prop",
+ ctx->config,
+ svn_prop_needs_translation
+ (pname_utf8),
+ opt_state->encoding,
+ subpool));
+
+ target_local = svn_path_is_url(target) ? target
+ : svn_dirent_local_style(target, subpool);
+ cib.target_local = target_local;
+
+ /* ...and re-set the property's value accordingly. */
+ if (edited_propval && !svn_string_compare(propval, edited_propval))
+ {
+ svn_error_t *err = SVN_NO_ERROR;
+
+ svn_cl__check_boolean_prop_val(pname_utf8, edited_propval->data,
+ subpool);
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3),
+ opt_state, NULL, ctx->config,
+ subpool));
+ if (svn_path_is_url(target))
+ {
+ err = svn_client_propset_remote(pname_utf8, edited_propval,
+ target, opt_state->force,
+ base_rev,
+ opt_state->revprop_table,
+ commit_info_handler, &cib,
+ ctx, subpool);
+ }
+ else
+ {
+ apr_array_header_t *targs = apr_array_make(subpool, 1,
+ sizeof(const char *));
+
+ APR_ARRAY_PUSH(targs, const char *) = target;
+
+ SVN_ERR(svn_cl__propset_print_binary_mime_type_warning(
+ targs, pname_utf8, propval, subpool));
+
+ err = svn_client_propset_local(pname_utf8, edited_propval,
+ targs, svn_depth_empty,
+ opt_state->force, NULL,
+ ctx, subpool);
+ }
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3,
+ err, pool));
+ else if (err)
+ return svn_error_trace(err);
+
+ /* Print a message if we successfully committed or if it
+ was just a wc propset (but not if the user aborted a URL
+ propedit). */
+ if (!svn_path_is_url(target))
+ SVN_ERR(svn_cmdline_printf(
+ subpool, _("Set new value for property '%s' on '%s'\n"),
+ pname_utf8, target_local));
+ }
+ else
+ {
+ SVN_ERR
+ (svn_cmdline_printf
+ (subpool, _("No changes to property '%s' on '%s'\n"),
+ pname_utf8, target_local));
+ }
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/propget-cmd.c b/contrib/subversion/subversion/svn/propget-cmd.c
new file mode 100644
index 0000000..e291911
--- /dev/null
+++ b/contrib/subversion/subversion/svn/propget-cmd.c
@@ -0,0 +1,493 @@
+/*
+ * propget-cmd.c -- Print properties and values of files/dirs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_sorts.h"
+#include "svn_subst.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_xml.h"
+#include "cl.h"
+
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+static svn_error_t *
+stream_write(svn_stream_t *out,
+ const char *data,
+ apr_size_t len)
+{
+ apr_size_t write_len = len;
+
+ /* We're gonna bail on an incomplete write here only because we know
+ that this stream is really stdout, which should never be blocking
+ on us. */
+ SVN_ERR(svn_stream_write(out, data, &write_len));
+ if (write_len != len)
+ return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
+ _("Error writing to stream"));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+print_properties_xml(const char *pname,
+ apr_hash_t *props,
+ apr_array_header_t *inherited_props,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *sorted_props;
+ int i;
+ apr_pool_t *iterpool = NULL;
+ svn_stringbuf_t *sb;
+
+ if (inherited_props && inherited_props->nelts)
+ {
+ iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ const char *name_local;
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+ svn_string_t *propval = svn__apr_hash_index_val(
+ apr_hash_first(pool, iprop->prop_hash));
+
+ sb = NULL;
+ svn_pool_clear(iterpool);
+
+ if (svn_path_is_url(iprop->path_or_url))
+ name_local = iprop->path_or_url;
+ else
+ name_local = svn_dirent_local_style(iprop->path_or_url, iterpool);
+
+ svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target",
+ "path", name_local, NULL);
+
+ svn_cmdline__print_xml_prop(&sb, pname, propval, TRUE, iterpool);
+ svn_xml_make_close_tag(&sb, iterpool, "target");
+
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+ }
+
+ if (iterpool == NULL)
+ iterpool = svn_pool_create(iterpool);
+
+ sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool);
+ for (i = 0; i < sorted_props->nelts; i++)
+ {
+ svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
+ const char *filename = item.key;
+ svn_string_t *propval = item.value;
+
+ sb = NULL;
+ svn_pool_clear(iterpool);
+
+ svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target",
+ "path", filename, NULL);
+ svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, iterpool);
+ svn_xml_make_close_tag(&sb, iterpool, "target");
+
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+
+ if (iterpool)
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Print the property PNAME_UTF with the value PROPVAL set on ABSPATH_OR_URL
+ to the stream OUT.
+
+ If INHERITED_PROPERTY is true then the property described is inherited,
+ otherwise it is explicit.
+
+ WC_PATH_PREFIX is the absolute path of the current working directory (and
+ is ignored if ABSPATH_OR_URL is a URL).
+
+ All other arguments are as per print_properties. */
+static svn_error_t *
+print_single_prop(svn_string_t *propval,
+ const char *target_abspath_or_url,
+ const char *abspath_or_URL,
+ const char *wc_path_prefix,
+ svn_stream_t *out,
+ const char *pname_utf8,
+ svn_boolean_t print_filenames,
+ svn_boolean_t omit_newline,
+ svn_boolean_t like_proplist,
+ svn_boolean_t inherited_property,
+ apr_pool_t *scratch_pool)
+{
+ if (print_filenames)
+ {
+ const char *header;
+
+ /* Print the file name. */
+
+ if (! svn_path_is_url(abspath_or_URL))
+ abspath_or_URL = svn_cl__local_style_skip_ancestor(wc_path_prefix,
+ abspath_or_URL,
+ scratch_pool);
+
+ /* In verbose mode, print exactly same as "proplist" does;
+ * otherwise, print a brief header. */
+ if (inherited_property)
+ {
+ if (like_proplist)
+ {
+ if (! svn_path_is_url(target_abspath_or_url))
+ target_abspath_or_url =
+ svn_cl__local_style_skip_ancestor(wc_path_prefix,
+ target_abspath_or_url,
+ scratch_pool);
+ header = apr_psprintf(
+ scratch_pool,
+ _("Inherited properties on '%s',\nfrom '%s':\n"),
+ target_abspath_or_url, abspath_or_URL);
+ }
+ else
+ {
+ header = apr_psprintf(scratch_pool, "%s - ", abspath_or_URL);
+ }
+ }
+ else
+ header = apr_psprintf(scratch_pool, like_proplist
+ ? _("Properties on '%s':\n")
+ : "%s - ", abspath_or_URL);
+ SVN_ERR(svn_cmdline_cstring_from_utf8(&header, header, scratch_pool));
+ SVN_ERR(svn_subst_translate_cstring2(header, &header,
+ APR_EOL_STR, /* 'native' eol */
+ FALSE, /* no repair */
+ NULL, /* no keywords */
+ FALSE, /* no expansion */
+ scratch_pool));
+ SVN_ERR(stream_write(out, header, strlen(header)));
+ }
+
+ if (like_proplist)
+ {
+ /* Print the property name and value just as "proplist -v" does */
+ apr_hash_t *hash = apr_hash_make(scratch_pool);
+
+ svn_hash_sets(hash, pname_utf8, propval);
+ SVN_ERR(svn_cmdline__print_prop_hash(out, hash, FALSE, scratch_pool));
+ }
+ else
+ {
+ /* If this is a special Subversion property, it is stored as
+ UTF8, so convert to the native format. */
+ if (svn_prop_needs_translation(pname_utf8))
+ SVN_ERR(svn_subst_detranslate_string(&propval, propval,
+ TRUE, scratch_pool));
+
+ SVN_ERR(stream_write(out, propval->data, propval->len));
+
+ if (! omit_newline)
+ SVN_ERR(stream_write(out, APR_EOL_STR,
+ strlen(APR_EOL_STR)));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Print the properties in PROPS and/or *INHERITED_PROPS to the stream OUT.
+ PROPS is a hash mapping (const char *) path to (svn_string_t) property
+ value. INHERITED_PROPS is a depth-first ordered array of
+ svn_prop_inherited_item_t * structures.
+
+ TARGET_ABSPATH_OR_URL is the path which inherits INHERITED_PROPS.
+
+ PROPS may be an empty hash, but is never null. INHERITED_PROPS may be
+ null.
+
+ If IS_URL is true, all paths in PROPS are URLs, else all paths are local
+ paths.
+
+ PNAME_UTF8 is the property name of all the properties.
+
+ If PRINT_FILENAMES is true, print the item's path before each property.
+
+ If OMIT_NEWLINE is true, don't add a newline at the end of each property.
+
+ If LIKE_PROPLIST is true, print everything in a more verbose format
+ like "svn proplist -v" does. */
+static svn_error_t *
+print_properties(svn_stream_t *out,
+ const char *target_abspath_or_url,
+ const char *pname_utf8,
+ apr_hash_t *props,
+ apr_array_header_t *inherited_props,
+ svn_boolean_t print_filenames,
+ svn_boolean_t omit_newline,
+ svn_boolean_t like_proplist,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *sorted_props;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ const char *path_prefix;
+
+ SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool));
+
+ if (inherited_props)
+ {
+ svn_pool_clear(iterpool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+ svn_string_t *propval = svn__apr_hash_index_val(apr_hash_first(pool,
+ iprop->prop_hash));
+ SVN_ERR(print_single_prop(propval, target_abspath_or_url,
+ iprop->path_or_url,
+ path_prefix, out, pname_utf8,
+ print_filenames, omit_newline,
+ like_proplist, TRUE, iterpool));
+ }
+ }
+
+ sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool);
+ for (i = 0; i < sorted_props->nelts; i++)
+ {
+ svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
+ const char *filename = item.key;
+ svn_string_t *propval = item.value;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(print_single_prop(propval, target_abspath_or_url, filename,
+ path_prefix, out, pname_utf8, print_filenames,
+ omit_newline, like_proplist, FALSE,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__propget(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *pname, *pname_utf8;
+ apr_array_header_t *args, *targets;
+ svn_stream_t *out;
+
+ if (opt_state->verbose && (opt_state->revprop || opt_state->strict
+ || opt_state->xml))
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--verbose cannot be used with --revprop or "
+ "--strict or --xml"));
+
+ /* PNAME is first argument (and PNAME_UTF8 will be a UTF-8 version
+ thereof) */
+ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
+ pname = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool));
+ if (! svn_prop_name_is_valid(pname_utf8))
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid Subversion property name"),
+ pname_utf8);
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 file arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ /* Open a stream to stdout. */
+ SVN_ERR(svn_stream_for_stdout(&out, pool));
+
+ if (opt_state->revprop) /* operate on a revprop */
+ {
+ svn_revnum_t rev;
+ const char *URL;
+ svn_string_t *propval;
+
+ if (opt_state->show_inherited_props)
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--show-inherited-props can't be used with --revprop"));
+
+ SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
+ &URL, ctx, pool));
+
+ /* Let libsvn_client do the real work. */
+ SVN_ERR(svn_client_revprop_get(pname_utf8, &propval,
+ URL, &(opt_state->start_revision),
+ &rev, ctx, pool));
+
+ if (propval != NULL)
+ {
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb = NULL;
+ char *revstr = apr_psprintf(pool, "%ld", rev);
+
+ SVN_ERR(svn_cl__xml_print_header("properties", pool));
+
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal,
+ "revprops",
+ "rev", revstr, NULL);
+
+ svn_cmdline__print_xml_prop(&sb, pname_utf8, propval, FALSE,
+ pool);
+
+ svn_xml_make_close_tag(&sb, pool, "revprops");
+
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ SVN_ERR(svn_cl__xml_print_footer("properties", pool));
+ }
+ else
+ {
+ svn_string_t *printable_val = propval;
+
+ /* If this is a special Subversion property, it is stored as
+ UTF8 and LF, so convert to the native locale and eol-style. */
+
+ if (svn_prop_needs_translation(pname_utf8))
+ SVN_ERR(svn_subst_detranslate_string(&printable_val, propval,
+ TRUE, pool));
+
+ SVN_ERR(stream_write(out, printable_val->data,
+ printable_val->len));
+ if (! opt_state->strict)
+ SVN_ERR(stream_write(out, APR_EOL_STR, strlen(APR_EOL_STR)));
+ }
+ }
+ }
+ else /* operate on a normal, versioned property (not a revprop) */
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ int i;
+
+ if (opt_state->xml)
+ SVN_ERR(svn_cl__xml_print_header("properties", subpool));
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ /* Strict mode only makes sense for a single target. So make
+ sure we have only a single target, and that we're not being
+ asked to recurse on that target. */
+ if (opt_state->strict
+ && ((targets->nelts > 1) || (opt_state->depth != svn_depth_empty)))
+ return svn_error_create
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Strict output of property values only available for single-"
+ "target, non-recursive propget operations"));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ apr_hash_t *props;
+ svn_boolean_t print_filenames;
+ svn_boolean_t omit_newline;
+ svn_boolean_t like_proplist;
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+ apr_array_header_t *inherited_props;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Check for a peg revision. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ subpool));
+
+ if (!svn_path_is_url(truepath))
+ SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
+
+ SVN_ERR(svn_client_propget5(
+ &props,
+ opt_state->show_inherited_props ? &inherited_props : NULL,
+ pname_utf8, truepath,
+ &peg_revision,
+ &(opt_state->start_revision),
+ NULL, opt_state->depth,
+ opt_state->changelists, ctx, subpool,
+ subpool));
+
+ /* Any time there is more than one thing to print, or where
+ the path associated with a printed thing is not obvious,
+ we'll print filenames. That is, unless we've been told
+ not to do so with the --strict option. */
+ print_filenames = ((opt_state->depth > svn_depth_empty
+ || targets->nelts > 1
+ || apr_hash_count(props) > 1
+ || opt_state->verbose
+ || opt_state->show_inherited_props)
+ && (! opt_state->strict));
+ omit_newline = opt_state->strict;
+ like_proplist = opt_state->verbose && !opt_state->strict;
+
+ if (opt_state->xml)
+ SVN_ERR(print_properties_xml(
+ pname_utf8, props,
+ opt_state->show_inherited_props ? inherited_props : NULL,
+ subpool));
+ else
+ SVN_ERR(print_properties(
+ out, truepath, pname_utf8,
+ props,
+ opt_state->show_inherited_props ? inherited_props : NULL,
+ print_filenames,
+ omit_newline, like_proplist, subpool));
+ }
+
+ if (opt_state->xml)
+ SVN_ERR(svn_cl__xml_print_footer("properties", subpool));
+
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/proplist-cmd.c b/contrib/subversion/subversion/svn/proplist-cmd.c
new file mode 100644
index 0000000..fe23a67
--- /dev/null
+++ b/contrib/subversion/subversion/svn/proplist-cmd.c
@@ -0,0 +1,336 @@
+/*
+ * proplist-cmd.c -- List properties of files/dirs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_xml.h"
+#include "svn_props.h"
+#include "cl.h"
+
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+typedef struct proplist_baton_t
+{
+ svn_cl__opt_state_t *opt_state;
+ svn_boolean_t is_url;
+} proplist_baton_t;
+
+
+/*** Code. ***/
+
+/* This implements the svn_proplist_receiver2_t interface, printing XML to
+ stdout. */
+static svn_error_t *
+proplist_receiver_xml(void *baton,
+ const char *path,
+ apr_hash_t *prop_hash,
+ apr_array_header_t *inherited_props,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((proplist_baton_t *)baton)->opt_state;
+ svn_boolean_t is_url = ((proplist_baton_t *)baton)->is_url;
+ svn_stringbuf_t *sb;
+ const char *name_local;
+
+ if (inherited_props && inherited_props->nelts)
+ {
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ sb = NULL;
+
+ if (svn_path_is_url(iprop->path_or_url))
+ name_local = iprop->path_or_url;
+ else
+ name_local = svn_dirent_local_style(iprop->path_or_url, iterpool);
+
+ svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target",
+ "path", name_local, NULL);
+ SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, iprop->prop_hash,
+ (! opt_state->verbose),
+ TRUE, iterpool));
+ svn_xml_make_close_tag(&sb, iterpool, "target");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ if (! is_url)
+ name_local = svn_dirent_local_style(path, pool);
+ else
+ name_local = path;
+
+ sb = NULL;
+
+
+ if (prop_hash)
+ {
+ /* "<target ...>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
+ "path", name_local, NULL);
+
+ SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, prop_hash,
+ (! opt_state->verbose),
+ FALSE, pool));
+
+ /* "</target>" */
+ svn_xml_make_close_tag(&sb, pool, "target");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the svn_proplist_receiver2_t interface. */
+static svn_error_t *
+proplist_receiver(void *baton,
+ const char *path,
+ apr_hash_t *prop_hash,
+ apr_array_header_t *inherited_props,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((proplist_baton_t *)baton)->opt_state;
+ svn_boolean_t is_url = ((proplist_baton_t *)baton)->is_url;
+ const char *name_local;
+
+ if (! is_url)
+ name_local = svn_dirent_local_style(path, pool);
+ else
+ name_local = path;
+
+ if (inherited_props)
+ {
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+
+ if (!opt_state->quiet)
+ {
+ if (svn_path_is_url(iprop->path_or_url))
+ SVN_ERR(svn_cmdline_printf(
+ iterpool, _("Inherited properties on '%s',\nfrom '%s':\n"),
+ name_local, iprop->path_or_url));
+ else
+ SVN_ERR(svn_cmdline_printf(
+ iterpool, _("Inherited properties on '%s',\nfrom '%s':\n"),
+ name_local, svn_dirent_local_style(iprop->path_or_url,
+ iterpool)));
+ }
+
+ SVN_ERR(svn_cmdline__print_prop_hash(NULL, iprop->prop_hash,
+ (! opt_state->verbose),
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ if (prop_hash && apr_hash_count(prop_hash))
+ {
+ if (!opt_state->quiet)
+ SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"),
+ name_local));
+ SVN_ERR(svn_cmdline__print_prop_hash(NULL, prop_hash,
+ (! opt_state->verbose), pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__proplist(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ apr_array_header_t *errors = apr_array_make(scratch_pool, 0,
+ sizeof(apr_status_t));
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* Add "." if user passed 0 file arguments */
+ svn_opt_push_implicit_dot_target(targets, scratch_pool);
+
+ if (opt_state->revprop) /* operate on revprops */
+ {
+ svn_revnum_t rev;
+ const char *URL;
+ apr_hash_t *proplist;
+
+ if (opt_state->show_inherited_props)
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--show-inherited-props can't be used with --revprop"));
+
+ SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
+ &URL, ctx, scratch_pool));
+
+ /* Let libsvn_client do the real work. */
+ SVN_ERR(svn_client_revprop_list(&proplist,
+ URL, &(opt_state->start_revision),
+ &rev, ctx, scratch_pool));
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb = NULL;
+ char *revstr = apr_psprintf(scratch_pool, "%ld", rev);
+
+ SVN_ERR(svn_cl__xml_print_header("properties", scratch_pool));
+
+ svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal,
+ "revprops",
+ "rev", revstr, NULL);
+ SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, proplist,
+ (! opt_state->verbose),
+ FALSE, scratch_pool));
+ svn_xml_make_close_tag(&sb, scratch_pool, "revprops");
+
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ SVN_ERR(svn_cl__xml_print_footer("properties", scratch_pool));
+ }
+ else
+ {
+ SVN_ERR
+ (svn_cmdline_printf(scratch_pool,
+ _("Unversioned properties on revision %ld:\n"),
+ rev));
+
+ SVN_ERR(svn_cmdline__print_prop_hash(NULL, proplist,
+ (! opt_state->verbose),
+ scratch_pool));
+ }
+ }
+ else /* operate on normal, versioned properties (not revprops) */
+ {
+ int i;
+ apr_pool_t *iterpool;
+ svn_proplist_receiver2_t pl_receiver;
+
+ if (opt_state->xml)
+ {
+ SVN_ERR(svn_cl__xml_print_header("properties", scratch_pool));
+ pl_receiver = proplist_receiver_xml;
+ }
+ else
+ {
+ pl_receiver = proplist_receiver;
+ }
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ proplist_baton_t pl_baton;
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ pl_baton.is_url = svn_path_is_url(target);
+ pl_baton.opt_state = opt_state;
+
+ /* Check for a peg revision. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ iterpool));
+
+ SVN_ERR(svn_cl__try(
+ svn_client_proplist4(truepath, &peg_revision,
+ &(opt_state->start_revision),
+ opt_state->depth,
+ opt_state->changelists,
+ opt_state->show_inherited_props,
+ pl_receiver, &pl_baton,
+ ctx, iterpool),
+ errors, opt_state->quiet,
+ SVN_ERR_UNVERSIONED_RESOURCE,
+ SVN_ERR_ENTRY_NOT_FOUND,
+ SVN_NO_ERROR));
+ }
+ svn_pool_destroy(iterpool);
+
+ if (opt_state->xml)
+ SVN_ERR(svn_cl__xml_print_footer("properties", scratch_pool));
+
+ /* Error out *after* we closed the XML element */
+ if (errors->nelts > 0)
+ {
+ svn_error_t *err;
+
+ err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL);
+ for (i = 0; i < errors->nelts; i++)
+ {
+ apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t);
+
+ if (status == SVN_ERR_ENTRY_NOT_FOUND)
+ err = svn_error_quick_wrap(err,
+ _("Could not display properties "
+ "of all targets because some "
+ "targets don't exist"));
+ else if (status == SVN_ERR_UNVERSIONED_RESOURCE)
+ err = svn_error_quick_wrap(err,
+ _("Could not display properties "
+ "of all targets because some "
+ "targets are not versioned"));
+ }
+
+ return svn_error_trace(err);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/props.c b/contrib/subversion/subversion/svn/props.c
new file mode 100644
index 0000000..2a41ac8
--- /dev/null
+++ b/contrib/subversion/subversion/svn/props.c
@@ -0,0 +1,356 @@
+/*
+ * props.c: Utility functions for property handling
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <stdlib.h>
+
+#include <apr_hash.h>
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_sorts.h"
+#include "svn_subst.h"
+#include "svn_props.h"
+#include "svn_string.h"
+#include "svn_opt.h"
+#include "svn_xml.h"
+#include "svn_base64.h"
+#include "cl.h"
+
+#include "private/svn_string_private.h"
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+
+svn_error_t *
+svn_cl__revprop_prepare(const svn_opt_revision_t *revision,
+ const apr_array_header_t *targets,
+ const char **URL,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *target;
+
+ if (revision->kind != svn_opt_revision_number
+ && revision->kind != svn_opt_revision_date
+ && revision->kind != svn_opt_revision_head)
+ return svn_error_create
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Must specify the revision as a number, a date or 'HEAD' "
+ "when operating on a revision property"));
+
+ /* There must be exactly one target at this point. If it was optional and
+ unspecified by the user, the caller has already added the implicit '.'. */
+ if (targets->nelts != 1)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Wrong number of targets specified"));
+
+ /* (The docs say the target must be either a URL or implicit '.', but
+ explicit WC targets are also accepted.) */
+ target = APR_ARRAY_IDX(targets, 0, const char *);
+ SVN_ERR(svn_client_url_from_path2(URL, target, ctx, pool, pool));
+ if (*URL == NULL)
+ return svn_error_create
+ (SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("Either a URL or versioned item is required"));
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_cl__check_boolean_prop_val(const char *propname, const char *propval,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *propbuf;
+
+ if (!svn_prop_is_boolean(propname))
+ return;
+
+ propbuf = svn_stringbuf_create(propval, pool);
+ svn_stringbuf_strip_whitespace(propbuf);
+
+ if (propbuf->data[0] == '\0'
+ || svn_cstring_casecmp(propbuf->data, "0") == 0
+ || svn_cstring_casecmp(propbuf->data, "no") == 0
+ || svn_cstring_casecmp(propbuf->data, "off") == 0
+ || svn_cstring_casecmp(propbuf->data, "false") == 0)
+ {
+ svn_error_t *err = svn_error_createf
+ (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
+ _("To turn off the %s property, use 'svn propdel';\n"
+ "setting the property to '%s' will not turn it off."),
+ propname, propval);
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ }
+}
+
+
+/* Context for sorting property names */
+struct simprop_context_t
+{
+ svn_string_t name; /* The name of the property we're comparing with */
+ svn_membuf_t buffer; /* Buffer for similarity testing */
+};
+
+struct simprop_t
+{
+ const char *propname; /* The original svn: property name */
+ svn_string_t name; /* The property name without the svn: prefix */
+ unsigned int score; /* The similarity score */
+ apr_size_t diff; /* Number of chars different from context.name */
+ struct simprop_context_t *context; /* Sorting context for qsort() */
+};
+
+/* Similarity test between two property names */
+static APR_INLINE unsigned int
+simprop_key_diff(const svn_string_t *key, const svn_string_t *ctx,
+ svn_membuf_t *buffer, apr_size_t *diff)
+{
+ apr_size_t lcs;
+ const unsigned int score = svn_string__similarity(key, ctx, buffer, &lcs);
+ if (key->len > ctx->len)
+ *diff = key->len - lcs;
+ else
+ *diff = ctx->len - lcs;
+ return score;
+}
+
+/* Key comparator for qsort for simprop_t */
+static int
+simprop_compare(const void *pkeya, const void *pkeyb)
+{
+ struct simprop_t *const keya = *(struct simprop_t *const *)pkeya;
+ struct simprop_t *const keyb = *(struct simprop_t *const *)pkeyb;
+ struct simprop_context_t *const context = keya->context;
+
+ if (keya->score == -1)
+ keya->score = simprop_key_diff(&keya->name, &context->name,
+ &context->buffer, &keya->diff);
+ if (keyb->score == -1)
+ keyb->score = simprop_key_diff(&keyb->name, &context->name,
+ &context->buffer, &keyb->diff);
+
+ return (keya->score < keyb->score ? 1
+ : (keya->score > keyb->score ? -1
+ : (keya->diff > keyb->diff ? 1
+ : (keya->diff < keyb->diff ? -1 : 0))));
+}
+
+
+static const char*
+force_prop_option_message(svn_cl__prop_use_t prop_use, const char *prop_name,
+ apr_pool_t *scratch_pool)
+{
+ switch (prop_use)
+ {
+ case svn_cl__prop_use_set:
+ return apr_psprintf(
+ scratch_pool,
+ _("(To set the '%s' property, re-run with '--force'.)"),
+ prop_name);
+ case svn_cl__prop_use_edit:
+ return apr_psprintf(
+ scratch_pool,
+ _("(To edit the '%s' property, re-run with '--force'.)"),
+ prop_name);
+ case svn_cl__prop_use_use:
+ default:
+ return apr_psprintf(
+ scratch_pool,
+ _("(To use the '%s' property, re-run with '--force'.)"),
+ prop_name);
+ }
+}
+
+static const char*
+wrong_prop_error_message(svn_cl__prop_use_t prop_use, const char *prop_name,
+ apr_pool_t *scratch_pool)
+{
+ switch (prop_use)
+ {
+ case svn_cl__prop_use_set:
+ return apr_psprintf(
+ scratch_pool,
+ _("'%s' is not a valid %s property name;"
+ " re-run with '--force' to set it"),
+ prop_name, SVN_PROP_PREFIX);
+ case svn_cl__prop_use_edit:
+ return apr_psprintf(
+ scratch_pool,
+ _("'%s' is not a valid %s property name;"
+ " re-run with '--force' to edit it"),
+ prop_name, SVN_PROP_PREFIX);
+ case svn_cl__prop_use_use:
+ default:
+ return apr_psprintf(
+ scratch_pool,
+ _("'%s' is not a valid %s property name;"
+ " re-run with '--force' to use it"),
+ prop_name, SVN_PROP_PREFIX);
+ }
+}
+
+svn_error_t *
+svn_cl__check_svn_prop_name(const char *propname,
+ svn_boolean_t revprop,
+ svn_cl__prop_use_t prop_use,
+ apr_pool_t *scratch_pool)
+{
+ static const char *const nodeprops[] =
+ {
+ SVN_PROP_NODE_ALL_PROPS
+ };
+ static const apr_size_t nodeprops_len = sizeof(nodeprops)/sizeof(*nodeprops);
+
+ static const char *const revprops[] =
+ {
+ SVN_PROP_REVISION_ALL_PROPS
+ };
+ static const apr_size_t revprops_len = sizeof(revprops)/sizeof(*revprops);
+
+ const char *const *const proplist = (revprop ? revprops : nodeprops);
+ const apr_size_t numprops = (revprop ? revprops_len : nodeprops_len);
+
+ struct simprop_t **propkeys;
+ struct simprop_t *propbuf;
+ apr_size_t i;
+
+ struct simprop_context_t context;
+ svn_string_t prefix;
+
+ context.name.data = propname;
+ context.name.len = strlen(propname);
+ prefix.data = SVN_PROP_PREFIX;
+ prefix.len = strlen(SVN_PROP_PREFIX);
+
+ svn_membuf__create(&context.buffer, 0, scratch_pool);
+
+ /* First, check if the name is even close to being in the svn: namespace.
+ It must contain a colon in the right place, and we only allow
+ one-char typos or a single transposition. */
+ if (context.name.len < prefix.len
+ || context.name.data[prefix.len - 1] != prefix.data[prefix.len - 1])
+ return SVN_NO_ERROR; /* Wrong prefix, ignore */
+ else
+ {
+ apr_size_t lcs;
+ const apr_size_t name_len = context.name.len;
+ context.name.len = prefix.len; /* Only check up to the prefix length */
+ svn_string__similarity(&context.name, &prefix, &context.buffer, &lcs);
+ context.name.len = name_len; /* Restore the original propname length */
+ if (lcs < prefix.len - 1)
+ return SVN_NO_ERROR; /* Wrong prefix, ignore */
+
+ /* If the prefix is slightly different, the rest must be
+ identical in order to trigger the error. */
+ if (lcs == prefix.len - 1)
+ {
+ for (i = 0; i < numprops; ++i)
+ {
+ if (0 == strcmp(proplist[i] + prefix.len, propname + prefix.len))
+ return svn_error_createf(
+ SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid %s property name;"
+ " did you mean '%s'?\n%s"),
+ propname, SVN_PROP_PREFIX, proplist[i],
+ force_prop_option_message(prop_use, propname, scratch_pool));
+ }
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Now find the closest match from amongst the set of reserved
+ node or revision property names. Skip the prefix while matching,
+ we already know that it's the same and looking at it would only
+ skew the results. */
+ propkeys = apr_palloc(scratch_pool,
+ numprops * sizeof(struct simprop_t*));
+ propbuf = apr_palloc(scratch_pool,
+ numprops * sizeof(struct simprop_t));
+ context.name.data += prefix.len;
+ context.name.len -= prefix.len;
+ for (i = 0; i < numprops; ++i)
+ {
+ propkeys[i] = &propbuf[i];
+ propbuf[i].propname = proplist[i];
+ propbuf[i].name.data = proplist[i] + prefix.len;
+ propbuf[i].name.len = strlen(propbuf[i].name.data);
+ propbuf[i].score = (unsigned int)-1;
+ propbuf[i].context = &context;
+ }
+
+ qsort(propkeys, numprops, sizeof(*propkeys), simprop_compare);
+
+ if (0 == propkeys[0]->diff)
+ return SVN_NO_ERROR; /* We found an exact match. */
+
+ /* See if we can suggest a sane alternative spelling */
+ for (i = 0; i < numprops; ++i)
+ if (propkeys[i]->score < 666) /* 2/3 similarity required */
+ break;
+
+ switch (i)
+ {
+ case 0:
+ /* The best alternative isn't good enough */
+ return svn_error_create(
+ SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ wrong_prop_error_message(prop_use, propname, scratch_pool));
+
+ case 1:
+ /* There is only one good candidate */
+ return svn_error_createf(
+ SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid %s property name; did you mean '%s'?\n%s"),
+ propname, SVN_PROP_PREFIX, propkeys[0]->propname,
+ force_prop_option_message(prop_use, propname, scratch_pool));
+
+ case 2:
+ /* Suggest a list of the most likely candidates */
+ return svn_error_createf(
+ SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid %s property name\n"
+ "Did you mean '%s' or '%s'?\n%s"),
+ propname, SVN_PROP_PREFIX,
+ propkeys[0]->propname, propkeys[1]->propname,
+ force_prop_option_message(prop_use, propname, scratch_pool));
+
+ default:
+ /* Never suggest more than three candidates */
+ return svn_error_createf(
+ SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid %s property name\n"
+ "Did you mean '%s', '%s' or '%s'?\n%s"),
+ propname, SVN_PROP_PREFIX,
+ propkeys[0]->propname, propkeys[1]->propname, propkeys[2]->propname,
+ force_prop_option_message(prop_use, propname, scratch_pool));
+ }
+}
diff --git a/contrib/subversion/subversion/svn/propset-cmd.c b/contrib/subversion/subversion/svn/propset-cmd.c
new file mode 100644
index 0000000..07b9bbd
--- /dev/null
+++ b/contrib/subversion/subversion/svn/propset-cmd.c
@@ -0,0 +1,191 @@
+/*
+ * propset-cmd.c -- Set property values on files/dirs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_subst.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__propset(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *pname, *pname_utf8;
+ svn_string_t *propval = NULL;
+ svn_boolean_t propval_came_from_cmdline;
+ apr_array_header_t *args, *targets;
+
+ /* PNAME and PROPVAL expected as first 2 arguments if filedata was
+ NULL, else PNAME alone will precede the targets. Get a UTF-8
+ version of the name, too. */
+ SVN_ERR(svn_opt_parse_num_args(&args, os,
+ opt_state->filedata ? 1 : 2, scratch_pool));
+ pname = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, scratch_pool));
+ if (! svn_prop_name_is_valid(pname_utf8))
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid Subversion property name"),
+ pname_utf8);
+ if (!opt_state->force)
+ SVN_ERR(svn_cl__check_svn_prop_name(pname_utf8, opt_state->revprop,
+ svn_cl__prop_use_set, scratch_pool));
+
+ /* Get the PROPVAL from either an external file, or from the command
+ line. */
+ if (opt_state->filedata)
+ {
+ propval = svn_string_create_from_buf(opt_state->filedata, scratch_pool);
+ propval_came_from_cmdline = FALSE;
+ }
+ else
+ {
+ propval = svn_string_create(APR_ARRAY_IDX(args, 1, const char *),
+ scratch_pool);
+ propval_came_from_cmdline = TRUE;
+ }
+
+ /* We only want special Subversion property values to be in UTF-8
+ and LF line endings. All other propvals are taken literally. */
+ if (svn_prop_needs_translation(pname_utf8))
+ SVN_ERR(svn_subst_translate_string2(&propval, NULL, NULL, propval,
+ opt_state->encoding, FALSE,
+ scratch_pool, scratch_pool));
+ else if (opt_state->encoding)
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("--encoding option applies only to textual"
+ " Subversion-controlled properties"));
+
+ /* Suck up all the remaining arguments into a targets array */
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* Implicit "." is okay for revision properties; it just helps
+ us find the right repository. */
+ if (opt_state->revprop)
+ svn_opt_push_implicit_dot_target(targets, scratch_pool);
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ if (opt_state->revprop) /* operate on a revprop */
+ {
+ svn_revnum_t rev;
+ const char *URL;
+
+ SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
+ &URL, ctx, scratch_pool));
+
+ /* Let libsvn_client do the real work. */
+ SVN_ERR(svn_client_revprop_set2(pname_utf8, propval, NULL,
+ URL, &(opt_state->start_revision),
+ &rev, opt_state->force, ctx,
+ scratch_pool));
+ }
+ else if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ return svn_error_createf
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Cannot specify revision for setting versioned property '%s'"),
+ pname);
+ }
+ else /* operate on a normal, versioned property (not a revprop) */
+ {
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ /* The customary implicit dot rule has been prone to user error
+ * here. People would do intuitive things like
+ *
+ * $ svn propset svn:executable script
+ *
+ * and then be surprised to get an error like:
+ *
+ * svn: Illegal target for the requested operation
+ * svn: Cannot set svn:executable on a directory ()
+ *
+ * So we don't do the implicit dot thing anymore. A * target
+ * must always be explicitly provided when setting a versioned
+ * property. See
+ *
+ * http://subversion.tigris.org/issues/show_bug.cgi?id=924
+ *
+ * for more details.
+ */
+
+ if (targets->nelts == 0)
+ {
+ if (propval_came_from_cmdline)
+ {
+ return svn_error_createf
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Explicit target required ('%s' interpreted as prop value)"),
+ propval->data);
+ }
+ else
+ {
+ return svn_error_create
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Explicit target argument required"));
+ }
+ }
+
+ SVN_ERR(svn_cl__propset_print_binary_mime_type_warning(targets,
+ pname_utf8,
+ propval,
+ scratch_pool));
+
+ SVN_ERR(svn_client_propset_local(pname_utf8, propval, targets,
+ opt_state->depth, opt_state->force,
+ opt_state->changelists, ctx,
+ scratch_pool));
+
+ if (! opt_state->quiet)
+ svn_cl__check_boolean_prop_val(pname_utf8, propval->data, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/relocate-cmd.c b/contrib/subversion/subversion/svn/relocate-cmd.c
new file mode 100644
index 0000000..fe50f66
--- /dev/null
+++ b/contrib/subversion/subversion/svn/relocate-cmd.c
@@ -0,0 +1,120 @@
+/*
+ * relocate-cmd.c -- Update working tree administrative data to
+ * account for repository change-of-address.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__relocate(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ svn_boolean_t ignore_externals = opt_state->ignore_externals;
+ apr_array_header_t *targets;
+ const char *from, *to, *path;
+
+ /* We've got two different syntaxes to support:
+
+ 1. relocate FROM-PREFIX TO-PREFIX [PATH ...]
+ 2. relocate TO-URL [PATH]
+ */
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ /* If we have a single target, we're in form #2. If we have two
+ targets and the first is a URL and the second is not, we're also
+ in form #2. */
+ if ((targets->nelts == 1) ||
+ ((targets->nelts == 2)
+ && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)))
+ && (! svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *)))))
+
+ {
+ to = APR_ARRAY_IDX(targets, 0, const char *);
+ path = (targets->nelts == 2) ? APR_ARRAY_IDX(targets, 1, const char *)
+ : "";
+
+ SVN_ERR(svn_client_url_from_path2(&from, path, ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_client_relocate2(path, from, to, ignore_externals,
+ ctx, scratch_pool));
+ }
+ /* ... Everything else is form #1. */
+ else
+ {
+ from = APR_ARRAY_IDX(targets, 0, const char *);
+ to = APR_ARRAY_IDX(targets, 1, const char *);
+
+ if (targets->nelts == 2)
+ {
+ SVN_ERR(svn_client_relocate2("", from, to, ignore_externals,
+ ctx, scratch_pool));
+ }
+ else
+ {
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+ int i;
+
+ /* Target working copy root dir must be local. */
+ for (i = 2; i < targets->nelts; i++)
+ {
+ path = APR_ARRAY_IDX(targets, i, const char *);
+ SVN_ERR(svn_cl__check_target_is_local_path(path));
+ }
+
+ for (i = 2; i < targets->nelts; i++)
+ {
+ svn_pool_clear(subpool);
+ path = APR_ARRAY_IDX(targets, i, const char *);
+ SVN_ERR(svn_client_relocate2(path, from, to, ignore_externals,
+ ctx, subpool));
+ }
+ svn_pool_destroy(subpool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/resolve-cmd.c b/contrib/subversion/subversion/svn/resolve-cmd.c
new file mode 100644
index 0000000..ce4818e
--- /dev/null
+++ b/contrib/subversion/subversion/svn/resolve-cmd.c
@@ -0,0 +1,131 @@
+/*
+ * resolve-cmd.c -- Subversion resolve subcommand
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+#include "svn_path.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__resolve(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ svn_wc_conflict_choice_t conflict_choice;
+ svn_error_t *err;
+ apr_array_header_t *targets;
+ int i;
+ apr_pool_t *iterpool;
+ svn_boolean_t had_error = FALSE;
+
+ switch (opt_state->accept_which)
+ {
+ case svn_cl__accept_working:
+ conflict_choice = svn_wc_conflict_choose_merged;
+ break;
+ case svn_cl__accept_base:
+ conflict_choice = svn_wc_conflict_choose_base;
+ break;
+ case svn_cl__accept_theirs_conflict:
+ conflict_choice = svn_wc_conflict_choose_theirs_conflict;
+ break;
+ case svn_cl__accept_mine_conflict:
+ conflict_choice = svn_wc_conflict_choose_mine_conflict;
+ break;
+ case svn_cl__accept_theirs_full:
+ conflict_choice = svn_wc_conflict_choose_theirs_full;
+ break;
+ case svn_cl__accept_mine_full:
+ conflict_choice = svn_wc_conflict_choose_mine_full;
+ break;
+ case svn_cl__accept_unspecified:
+ if (opt_state->non_interactive)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("missing --accept option"));
+ conflict_choice = svn_wc_conflict_choose_unspecified;
+ break;
+ default:
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("invalid 'accept' ARG"));
+ }
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+ if (! targets->nelts)
+ svn_opt_push_implicit_dot_target(targets, scratch_pool);
+
+ if (opt_state->depth == svn_depth_unknown)
+ {
+ if (opt_state->accept_which == svn_cl__accept_unspecified)
+ opt_state->depth = svn_depth_infinity;
+ else
+ opt_state->depth = svn_depth_empty;
+ }
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+ err = svn_client_resolve(target,
+ opt_state->depth, conflict_choice,
+ ctx,
+ iterpool);
+ if (err)
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ had_error = TRUE;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ if (had_error)
+ return svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, NULL,
+ _("Failure occurred resolving one or more "
+ "conflicts"));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/resolved-cmd.c b/contrib/subversion/subversion/svn/resolved-cmd.c
new file mode 100644
index 0000000..51e2da17
--- /dev/null
+++ b/contrib/subversion/subversion/svn/resolved-cmd.c
@@ -0,0 +1,88 @@
+/*
+ * resolved-cmd.c -- Subversion resolved subcommand
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+#include "svn_path.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__resolved(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ svn_error_t *err;
+ apr_array_header_t *targets;
+ apr_pool_t *iterpool;
+ int i;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+ err = svn_client_resolve(target,
+ opt_state->depth,
+ svn_wc_conflict_choose_merged,
+ ctx,
+ iterpool);
+ if (err)
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/revert-cmd.c b/contrib/subversion/subversion/svn/revert-cmd.c
new file mode 100644
index 0000000..d17aeb6
--- /dev/null
+++ b/contrib/subversion/subversion/svn/revert-cmd.c
@@ -0,0 +1,81 @@
+/*
+ * revert-cmd.c -- Subversion revert command
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_path.h"
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__revert(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets = NULL;
+ svn_error_t *err;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* Revert has no implicit dot-target `.', so don't you put that code here! */
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ /* Revert is especially conservative, by default it is as
+ nonrecursive as possible. */
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ err = svn_client_revert2(targets, opt_state->depth,
+ opt_state->changelists, ctx, scratch_pool);
+ if (err
+ && (err->apr_err == SVN_ERR_WC_INVALID_OPERATION_DEPTH)
+ && (! SVN_DEPTH_IS_RECURSIVE(opt_state->depth)))
+ {
+ err = svn_error_quick_wrap
+ (err, _("Try 'svn revert --depth infinity' instead?"));
+ }
+
+ return svn_error_trace(err);
+}
diff --git a/contrib/subversion/subversion/svn/schema/blame.rnc b/contrib/subversion/subversion/svn/schema/blame.rnc
new file mode 100644
index 0000000..b6a1e41
--- /dev/null
+++ b/contrib/subversion/subversion/svn/schema/blame.rnc
@@ -0,0 +1,42 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn blame"
+
+
+include "common.rnc"
+
+start = blame
+
+blame = element blame { target* }
+
+## Information for one blamed file.
+target = element target { attlist.target, entry* }
+attlist.target &= attribute path { target.type }
+
+## Information for one line of a blamed file.
+## NOTE: The order of entries in a target element is insignificant.
+entry = element entry { attlist.entry, commit?, merged? }
+attlist.entry &=
+ ## Line number.
+ attribute line-number { xsd:integer { minInclusive = "1" } }
+
+## The merged commit
+merged = element merged { attlist.merged, commit }
+attlist.merged &= attribute path { string }
diff --git a/contrib/subversion/subversion/svn/schema/common.rnc b/contrib/subversion/subversion/svn/schema/common.rnc
new file mode 100644
index 0000000..95729e3
--- /dev/null
+++ b/contrib/subversion/subversion/svn/schema/common.rnc
@@ -0,0 +1,77 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# Common declarations
+
+# Data types.
+
+## A revision number.
+revnum.type = xsd:nonNegativeInteger
+
+## A user name.
+username.type = string
+
+## A path or URL.
+target.type = string | xsd:anyURI
+
+## An UUID.
+uuid.type = string
+
+## An MD5 checksum.
+md5sum.type = xsd:hexBinary { length = "16" }
+
+# Common elements
+
+## Commit info.
+commit = element commit { attlist.commit, author?, date? }
+attlist.commit &= attribute revision { revnum.type }
+
+author = element author { username.type }
+
+date = element date { xsd:dateTime }
+
+## Lock info stored in repository or working copy.
+lock =
+ element lock {
+ \token, owner, comment?, created, expires?
+ }
+
+## Lock token.
+\token = element token { xsd:anyURI }
+
+## Lock owner.
+owner = element owner { username.type }
+
+## Lock comment.
+comment = element comment { text }
+
+## Creation date.
+created = element created { xsd:dateTime }
+
+## Expiration date.
+expires = element expires { xsd:dateTime }
+
+## Node and revision properties.
+property = element property { attlist.property, text }
+attlist.property &=
+ ## The property name
+ attribute name { string },
+ ## The encoding of the element content. If not present, the value
+ ## is the raw content of the element.
+ attribute encoding { "base64" }?
diff --git a/contrib/subversion/subversion/svn/schema/diff.rnc b/contrib/subversion/subversion/svn/schema/diff.rnc
new file mode 100644
index 0000000..ab89b81
--- /dev/null
+++ b/contrib/subversion/subversion/svn/schema/diff.rnc
@@ -0,0 +1,39 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn diff --summarize --xml"
+
+include "common.rnc"
+
+start = diff
+
+diff = element diff { paths }
+
+paths = element paths { path* }
+
+## A path entry
+path = element path { attlist.path, text }
+attlist.path &=
+ ## The props of the entry.
+ attribute props { "none" | "modified" },
+ ## The kind of the entry.
+ attribute kind { "dir" | "file" },
+ ## The action performed against this path. This terminology
+ ## was chosen for consistency with 'svn status'.
+ attribute item { "none" | "added" | "modified" | "deleted" }
diff --git a/contrib/subversion/subversion/svn/schema/info.rnc b/contrib/subversion/subversion/svn/schema/info.rnc
new file mode 100644
index 0000000..3dc43f6
--- /dev/null
+++ b/contrib/subversion/subversion/svn/schema/info.rnc
@@ -0,0 +1,134 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn info"
+
+include "common.rnc"
+
+start = info
+
+info = element info { entry* }
+
+entry =
+ element entry {
+ attlist.entry, url?, relative-url?, repository?, wc-info?,
+ commit?, conflict?, lock?, tree-conflict?
+ }
+attlist.entry &=
+ ## Local path.
+ attribute path { string },
+ ## Path type.
+ attribute kind { "file" | "dir" },
+ ## Revision number of path/URL.
+ attribute revision { revnum.type }
+
+## URL of this item in the repository.
+url = element url { xsd:anyURI }
+
+## Repository relative URL (^/...) of this item in the repository.
+relative-url = element relative-url { string }
+
+## Information of this item's repository.
+repository = element repository { root?, uuid? }
+
+## URL of the repository.
+root = element root { xsd:anyURI }
+
+## UUID of the repository.
+uuid = element uuid { uuid.type }
+
+## Info in the working copy entry.
+wc-info =
+ element wc-info {
+ wcroot-abspath?,
+ schedule?,
+ changelist?,
+ copy-from-url?,
+ copy-from-rev?,
+ depth?,
+ text-updated?,
+ prop-updated?,
+ checksum?,
+ moved-from?,
+ moved-to?
+ }
+
+wcroot-abspath = element wcroot-abspath { string }
+
+schedule =
+ element schedule { "normal" | "add" | "delete" | "replace" | "none" }
+
+## The name of the changelist that the path may be a member of.
+changelist = element changelist { string }
+
+copy-from-url = element copy-from-url { xsd:anyURI }
+
+copy-from-rev = element copy-from-rev { revnum.type }
+
+# Date when text was last updated.
+text-updated = element text-updated { xsd:dateTime }
+
+# Date when properties were last updated.
+prop-updated = element prop-updated { xsd:dateTime }
+
+checksum = element checksum { md5sum.type }
+
+moved-from = element moved-from { string }
+
+moved-to = element moved-to { string }
+
+conflict =
+ element conflict {
+ prev-base-file,
+ prev-wc-file?,
+ cur-base-file,
+ prop-file?
+ }
+
+## Previous base file.
+prev-base-file = element prev-base-file { string }
+
+## Previous WC file.
+prev-wc-file = element prev-wc-file { string }
+
+## Current base file.
+cur-base-file = element cur-base-file { string }
+
+## Current properties file.
+prop-file = element prop-file { string }
+
+## Depth of this directory, always "infinity" for non-directories
+depth = element depth { "infinity" | "immediates" | "files" | "empty" }
+
+tree-conflict =
+ element tree-conflict { attlist.tree-conflict }
+
+attlist.tree-conflict &=
+ ## Local path to the original victim.
+ attribute victim { string },
+ ## Path type.
+ attribute kind { "file" | "dir" },
+ ## Operation causing the tree conflict.
+ attribute operation { "update" | "merge" | "switch" },
+ ## Operation's action on the victim.
+ attribute action { "edit" | "add" | "delete" | "replace" },
+ ## Local reason for the conflict.
+ attribute reason { "edit" | "obstruction" | "delete" | "add" |
+ "missing" | "unversioned" | "replace" |
+ "moved-away" | "moved-here" }
diff --git a/contrib/subversion/subversion/svn/schema/list.rnc b/contrib/subversion/subversion/svn/schema/list.rnc
new file mode 100644
index 0000000..13d5897
--- /dev/null
+++ b/contrib/subversion/subversion/svn/schema/list.rnc
@@ -0,0 +1,45 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn list"
+
+include "common.rnc"
+
+start = lists
+
+lists = element lists { \list+ }
+
+## A target to the list command.
+\list = element list { attlist.list, entry* }
+attlist.list &=
+ ## Local path or repository URL.
+ attribute path { target.type }
+
+## A directory entry.
+entry = element entry { attlist.entry, name, size?, commit, lock? }
+attlist.entry &=
+ ## The kind of the entry.
+ attribute kind { "dir" | "file" }
+
+## Name of the file or directory.
+name = element name { string }
+
+## File size in bytes.
+size = element size { xsd:nonNegativeInteger }
+
diff --git a/contrib/subversion/subversion/svn/schema/log.rnc b/contrib/subversion/subversion/svn/schema/log.rnc
new file mode 100644
index 0000000..14a8b7e
--- /dev/null
+++ b/contrib/subversion/subversion/svn/schema/log.rnc
@@ -0,0 +1,55 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn log"
+
+include "common.rnc"
+
+start = log
+
+log = element log { logentry* }
+
+logentry =
+ element logentry { attlist.logentry, author?, date?, paths?, msg?, revprops?, logentry* }
+attlist.logentry &=
+ attribute revision { revnum.type }
+
+## Changed paths information.
+paths = element paths { path+ }
+
+## Path within repository.
+path = element path { attlist.path, text }
+attlist.path &=
+ ## "action code": A)dd, D)elete, R)eplace or M)odify
+ attribute action { "A" | "D" | "R" | "M" },
+ ## kind is "" when repository was < 1.6 when committing
+ attribute kind { "file" | "dir" | "" },
+ attribute text-mods { "true" | "false" }?,
+ attribute prop-mods { "true" | "false" }?,
+ (
+ ## The copyfrom path within repository.
+ attribute copyfrom-path { text },
+ ## Copyfrom revision number.
+ attribute copyfrom-rev { revnum.type })?
+
+## Log message.
+msg = element msg { text }
+
+## Revision properties.
+revprops = element revprops { property+ }
diff --git a/contrib/subversion/subversion/svn/schema/props.rnc b/contrib/subversion/subversion/svn/schema/props.rnc
new file mode 100644
index 0000000..260c93e
--- /dev/null
+++ b/contrib/subversion/subversion/svn/schema/props.rnc
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn proplist"
+
+include "common.rnc"
+
+start = properties
+
+properties = element properties { target* | revprops }
+
+target = element target { attlist.target, property* }
+attlist.target &=
+ ## The target path.
+ attribute path { string }
+
+revprops = element revprops { attlist.revprops, property*}
+attlist.revprops &=
+ ## The revision
+ attribute rev { revnum.type }
diff --git a/contrib/subversion/subversion/svn/schema/status.rnc b/contrib/subversion/subversion/svn/schema/status.rnc
new file mode 100644
index 0000000..73d0ca0
--- /dev/null
+++ b/contrib/subversion/subversion/svn/schema/status.rnc
@@ -0,0 +1,92 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn status"
+
+# The DTD compatibility annotations namespace, used for adding default
+# attribute values.
+namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0"
+
+include "common.rnc"
+
+start = status
+
+status = element status { (target | changelist)* }
+
+target = element target { attlist.target, entry*, against? }
+attlist.target &=
+ ## The target path.
+ attribute path { string }
+
+changelist = element changelist { attlist.changelist, entry*, against? }
+attlist.changelist &=
+ ## The changelist name.
+ attribute name { string }
+
+## Status information for a path under the target.
+entry = element entry { attlist.entry, wc-status, repos-status? }
+attlist.entry &=
+ ## Path inside the target.
+ attribute path { text }
+
+## Status of the entry in the working copy.
+wc-status = element wc-status { attlist.wc-status, commit?, lock? }
+
+attlist.wc-status &=
+ ## Item/text status.
+ attribute item {
+ "added" | "conflicted" | "deleted" | "external" | "ignored" |
+ "incomplete" | "merged" | "missing" | "modified" | "none" |
+ "normal" | "obstructed" | "replaced" | "unversioned"
+ },
+ ## Properties status.
+ attribute props { "conflicted" | "modified" | "normal" | "none" },
+ ## Base revision number.
+ attribute revision { revnum.type }?,
+ ## WC directory locked.
+ [ a:defaultValue = "false" ]
+ attribute wc-locked { "true" | "false" }?,
+ ## Add with history.
+ [ a:defaultValue = "false" ]
+ attribute copied { "true" | "false" }?,
+ # Item switched relative to its parent.
+ [ a:defaultValue = "false" ]
+ attribute switched { "true" | "false" }?,
+ ## Tree-conflict status of the item.
+ [ a:defaultValue = "false" ]
+ attribute tree-conflicted { "true" | "false" }?,
+ ## If root of a move-here, the local path to the move source.
+ attribute moved-from { text }?,
+ ## If root of a move-away, the local path to the move destination.
+ attribute moved-to { text }?
+
+## Status in repository (if --update was specified).
+repos-status = element repos-status { attlist.repos-status, lock? }
+attlist.repos-status &=
+ ## Text/item status in the repository.
+ attribute item {
+ "added" | "deleted" | "modified" | "replaced" | "none"
+ },
+ ## Properties status in repository.
+ attribute props { "modified" | "none" }
+
+against = element against { attlist.against, empty }
+attlist.against &=
+ ## Revision number at which the repository information was obtained.
+ attribute revision { revnum.type }
diff --git a/contrib/subversion/subversion/svn/status-cmd.c b/contrib/subversion/subversion/svn/status-cmd.c
new file mode 100644
index 0000000..0a73dac
--- /dev/null
+++ b/contrib/subversion/subversion/svn/status-cmd.c
@@ -0,0 +1,416 @@
+/*
+ * status-cmd.c -- Display status information in current directory
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_hash.h"
+#include "svn_string.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_xml.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_cmdline.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+/*** Code. ***/
+
+struct status_baton
+{
+ /* These fields all correspond to the ones in the
+ svn_cl__print_status() interface. */
+ const char *cwd_abspath;
+ svn_boolean_t suppress_externals_placeholders;
+ svn_boolean_t detailed;
+ svn_boolean_t show_last_committed;
+ svn_boolean_t skip_unrecognized;
+ svn_boolean_t repos_locks;
+
+ apr_hash_t *cached_changelists;
+ apr_pool_t *cl_pool; /* where cached changelists are allocated */
+
+ svn_boolean_t had_print_error; /* To avoid printing lots of errors if we get
+ errors while printing to stdout */
+ svn_boolean_t xml_mode;
+
+ /* Conflict stats. */
+ unsigned int text_conflicts;
+ unsigned int prop_conflicts;
+ unsigned int tree_conflicts;
+
+ svn_client_ctx_t *ctx;
+};
+
+
+struct status_cache
+{
+ const char *path;
+ svn_client_status_t *status;
+};
+
+/* Print conflict stats accumulated in status baton SB.
+ * Do temporary allocations in POOL. */
+static svn_error_t *
+print_conflict_stats(struct status_baton *sb, apr_pool_t *pool)
+{
+ if (sb->text_conflicts > 0 || sb->prop_conflicts > 0 ||
+ sb->tree_conflicts > 0)
+ SVN_ERR(svn_cmdline_printf(pool, "%s", _("Summary of conflicts:\n")));
+
+ if (sb->text_conflicts > 0)
+ SVN_ERR(svn_cmdline_printf
+ (pool, _(" Text conflicts: %u\n"), sb->text_conflicts));
+
+ if (sb->prop_conflicts > 0)
+ SVN_ERR(svn_cmdline_printf
+ (pool, _(" Property conflicts: %u\n"), sb->prop_conflicts));
+
+ if (sb->tree_conflicts > 0)
+ SVN_ERR(svn_cmdline_printf
+ (pool, _(" Tree conflicts: %u\n"), sb->tree_conflicts));
+
+ return SVN_NO_ERROR;
+}
+
+/* Prints XML target element with path attribute TARGET, using POOL for
+ temporary allocations. */
+static svn_error_t *
+print_start_target_xml(const char *target, apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
+ "path", target, NULL);
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* Finish a target element by optionally printing an against element if
+ * REPOS_REV is a valid revision number, and then printing an target end tag.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+print_finish_target_xml(svn_revnum_t repos_rev,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ if (SVN_IS_VALID_REVNUM(repos_rev))
+ {
+ const char *repos_rev_str;
+ repos_rev_str = apr_psprintf(pool, "%ld", repos_rev);
+ svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "against",
+ "revision", repos_rev_str, NULL);
+ }
+
+ svn_xml_make_close_tag(&sb, pool, "target");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* Function which *actually* causes a status structure to be output to
+ the user. Called by both print_status() and svn_cl__status(). */
+static svn_error_t *
+print_status_normal_or_xml(void *baton,
+ const char *path,
+ const svn_client_status_t *status,
+ apr_pool_t *pool)
+{
+ struct status_baton *sb = baton;
+
+ if (sb->xml_mode)
+ return svn_cl__print_status_xml(sb->cwd_abspath, path, status,
+ sb->ctx, pool);
+ else
+ return svn_cl__print_status(sb->cwd_abspath, path, status,
+ sb->suppress_externals_placeholders,
+ sb->detailed,
+ sb->show_last_committed,
+ sb->skip_unrecognized,
+ sb->repos_locks,
+ &sb->text_conflicts,
+ &sb->prop_conflicts,
+ &sb->tree_conflicts,
+ sb->ctx,
+ pool);
+}
+
+
+/* A status callback function for printing STATUS for PATH. */
+static svn_error_t *
+print_status(void *baton,
+ const char *path,
+ const svn_client_status_t *status,
+ apr_pool_t *pool)
+{
+ struct status_baton *sb = baton;
+ const char *local_abspath = status->local_abspath;
+
+ /* ### The revision information with associates are based on what
+ * ### _read_info() returns. The svn_wc_status_func4_t callback is
+ * ### suppposed to handle the gathering of additional information from the
+ * ### WORKING nodes on its own. Until we've agreed on how the CLI should
+ * ### handle the revision information, we use this appproach to stay compat
+ * ### with our testsuite. */
+ if (status->versioned
+ && !SVN_IS_VALID_REVNUM(status->revision)
+ && !status->copied
+ && (status->node_status == svn_wc_status_deleted
+ || status->node_status == svn_wc_status_replaced))
+ {
+ svn_client_status_t *twks = svn_client_status_dup(status, sb->cl_pool);
+
+ /* Copied is FALSE, so either we have a local addition, or we have
+ a delete that directly shadows a BASE node */
+
+ switch(status->node_status)
+ {
+ case svn_wc_status_replaced:
+ /* Just retrieve the revision below the replacement.
+ The other fields are filled by a copy.
+ (With ! copied, we know we have a BASE node)
+
+ ### Is this really what we want to provide? */
+ SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision,
+ NULL, NULL, NULL,
+ sb->ctx->wc_ctx,
+ local_abspath,
+ sb->cl_pool, pool));
+ break;
+ case svn_wc_status_deleted:
+ /* Retrieve some data from the original version below the delete */
+ SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision,
+ &twks->changed_rev,
+ &twks->changed_date,
+ &twks->changed_author,
+ sb->ctx->wc_ctx,
+ local_abspath,
+ sb->cl_pool, pool));
+ break;
+
+ default:
+ /* This space intentionally left blank. */
+ break;
+ }
+
+ status = twks;
+ }
+
+ /* If the path is part of a changelist, then we don't print
+ the item, but instead dup & cache the status structure for later. */
+ if (status->changelist)
+ {
+ /* The hash maps a changelist name to an array of status_cache
+ structures. */
+ apr_array_header_t *path_array;
+ const char *cl_key = apr_pstrdup(sb->cl_pool, status->changelist);
+ struct status_cache *scache = apr_pcalloc(sb->cl_pool, sizeof(*scache));
+ scache->path = apr_pstrdup(sb->cl_pool, path);
+ scache->status = svn_client_status_dup(status, sb->cl_pool);
+
+ path_array =
+ svn_hash_gets(sb->cached_changelists, cl_key);
+ if (path_array == NULL)
+ {
+ path_array = apr_array_make(sb->cl_pool, 1,
+ sizeof(struct status_cache *));
+ svn_hash_sets(sb->cached_changelists, cl_key, path_array);
+ }
+
+ APR_ARRAY_PUSH(path_array, struct status_cache *) = scache;
+ return SVN_NO_ERROR;
+ }
+
+ return print_status_normal_or_xml(baton, path, status, pool);
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__status(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ apr_pool_t *iterpool;
+ apr_hash_t *master_cl_hash = apr_hash_make(scratch_pool);
+ int i;
+ svn_opt_revision_t rev;
+ struct status_baton sb;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, scratch_pool);
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ /* We want our -u statuses to be against HEAD. */
+ rev.kind = svn_opt_revision_head;
+
+ sb.had_print_error = FALSE;
+
+ if (opt_state->xml)
+ {
+ /* If output is not incremental, output the XML header and wrap
+ everything in a top-level element. This makes the output in
+ its entirety a well-formed XML document. */
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_header("status", scratch_pool));
+ }
+ else
+ {
+ if (opt_state->incremental)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'incremental' option only valid in XML "
+ "mode"));
+ }
+
+ SVN_ERR(svn_dirent_get_absolute(&(sb.cwd_abspath), "", scratch_pool));
+ sb.suppress_externals_placeholders = (opt_state->quiet
+ && (! opt_state->verbose));
+ sb.detailed = (opt_state->verbose || opt_state->update);
+ sb.show_last_committed = opt_state->verbose;
+ sb.skip_unrecognized = opt_state->quiet;
+ sb.repos_locks = opt_state->update;
+ sb.xml_mode = opt_state->xml;
+ sb.cached_changelists = master_cl_hash;
+ sb.cl_pool = scratch_pool;
+ sb.text_conflicts = 0;
+ sb.prop_conflicts = 0;
+ sb.tree_conflicts = 0;
+ sb.ctx = ctx;
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_revnum_t repos_rev = SVN_INVALID_REVNUM;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ if (opt_state->xml)
+ SVN_ERR(print_start_target_xml(svn_dirent_local_style(target, iterpool),
+ iterpool));
+
+ /* Retrieve a hash of status structures with the information
+ requested by the user. */
+ SVN_ERR(svn_cl__try(svn_client_status5(&repos_rev, ctx, target, &rev,
+ opt_state->depth,
+ opt_state->verbose,
+ opt_state->update,
+ opt_state->no_ignore,
+ opt_state->ignore_externals,
+ FALSE /* depth_as_sticky */,
+ opt_state->changelists,
+ print_status, &sb,
+ iterpool),
+ NULL, opt_state->quiet,
+ /* not versioned: */
+ SVN_ERR_WC_NOT_WORKING_COPY,
+ SVN_ERR_WC_PATH_NOT_FOUND));
+
+ if (opt_state->xml)
+ SVN_ERR(print_finish_target_xml(repos_rev, iterpool));
+ }
+
+ /* If any paths were cached because they were associatied with
+ changelists, we can now display them as grouped changelists. */
+ if (apr_hash_count(master_cl_hash) > 0)
+ {
+ apr_hash_index_t *hi;
+ svn_stringbuf_t *buf;
+
+ if (opt_state->xml)
+ buf = svn_stringbuf_create_empty(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, master_cl_hash); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *changelist_name = svn__apr_hash_index_key(hi);
+ apr_array_header_t *path_array = svn__apr_hash_index_val(hi);
+ int j;
+
+ /* ### TODO: For non-XML output, we shouldn't print the
+ ### leading \n on the first changelist if there were no
+ ### non-changelist entries. */
+ if (opt_state->xml)
+ {
+ svn_stringbuf_setempty(buf);
+ svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
+ "changelist", "name", changelist_name,
+ NULL);
+ SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout));
+ }
+ else
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _("\n--- Changelist '%s':\n"),
+ changelist_name));
+
+ for (j = 0; j < path_array->nelts; j++)
+ {
+ struct status_cache *scache =
+ APR_ARRAY_IDX(path_array, j, struct status_cache *);
+ SVN_ERR(print_status_normal_or_xml(&sb, scache->path,
+ scache->status, scratch_pool));
+ }
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_setempty(buf);
+ svn_xml_make_close_tag(&buf, scratch_pool, "changelist");
+ SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout));
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ if (opt_state->xml && (! opt_state->incremental))
+ SVN_ERR(svn_cl__xml_print_footer("status", scratch_pool));
+
+ if (! opt_state->quiet && ! opt_state->xml)
+ SVN_ERR(print_conflict_stats(&sb, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/status.c b/contrib/subversion/subversion/svn/status.c
new file mode 100644
index 0000000..3679bff
--- /dev/null
+++ b/contrib/subversion/subversion/svn/status.c
@@ -0,0 +1,607 @@
+/*
+ * status.c: the command-line's portion of the "svn status" command
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_wc.h"
+#include "svn_dirent_uri.h"
+#include "svn_xml.h"
+#include "svn_time.h"
+#include "cl.h"
+#include "svn_private_config.h"
+#include "cl-conflicts.h"
+#include "private/svn_wc_private.h"
+
+/* Return the single character representation of STATUS */
+static char
+generate_status_code(enum svn_wc_status_kind status)
+{
+ switch (status)
+ {
+ case svn_wc_status_none: return ' ';
+ case svn_wc_status_normal: return ' ';
+ case svn_wc_status_added: return 'A';
+ case svn_wc_status_missing: return '!';
+ case svn_wc_status_incomplete: return '!';
+ case svn_wc_status_deleted: return 'D';
+ case svn_wc_status_replaced: return 'R';
+ case svn_wc_status_modified: return 'M';
+ case svn_wc_status_conflicted: return 'C';
+ case svn_wc_status_obstructed: return '~';
+ case svn_wc_status_ignored: return 'I';
+ case svn_wc_status_external: return 'X';
+ case svn_wc_status_unversioned: return '?';
+ default: return '?';
+ }
+}
+
+/* Return the combined STATUS as shown in 'svn status' based
+ on the node status and text status */
+static enum svn_wc_status_kind
+combined_status(const svn_client_status_t *status)
+{
+ enum svn_wc_status_kind new_status = status->node_status;
+
+ switch (status->node_status)
+ {
+ case svn_wc_status_conflicted:
+ if (!status->versioned && status->conflicted)
+ {
+ /* Report unversioned tree conflict victims as missing: '!' */
+ new_status = svn_wc_status_missing;
+ break;
+ }
+ /* fall through */
+ case svn_wc_status_modified:
+ /* This value might be the property status */
+ new_status = status->text_status;
+ break;
+ default:
+ break;
+ }
+
+ return new_status;
+}
+
+/* Return the combined repository STATUS as shown in 'svn status' based
+ on the repository node status and repository text status */
+static enum svn_wc_status_kind
+combined_repos_status(const svn_client_status_t *status)
+{
+ if (status->repos_node_status == svn_wc_status_modified)
+ return status->repos_text_status;
+
+ return status->repos_node_status;
+}
+
+/* Return the single character representation of the switched column
+ status. */
+static char
+generate_switch_column_code(const svn_client_status_t *status)
+{
+ if (status->switched)
+ return 'S';
+ else if (status->file_external)
+ return 'X';
+ else
+ return ' ';
+}
+
+/* Return the detailed string representation of STATUS */
+static const char *
+generate_status_desc(enum svn_wc_status_kind status)
+{
+ switch (status)
+ {
+ case svn_wc_status_none: return "none";
+ case svn_wc_status_normal: return "normal";
+ case svn_wc_status_added: return "added";
+ case svn_wc_status_missing: return "missing";
+ case svn_wc_status_incomplete: return "incomplete";
+ case svn_wc_status_deleted: return "deleted";
+ case svn_wc_status_replaced: return "replaced";
+ case svn_wc_status_modified: return "modified";
+ case svn_wc_status_conflicted: return "conflicted";
+ case svn_wc_status_obstructed: return "obstructed";
+ case svn_wc_status_ignored: return "ignored";
+ case svn_wc_status_external: return "external";
+ case svn_wc_status_unversioned: return "unversioned";
+ default:
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ }
+}
+
+/* Make a relative path containing '..' elements as needed.
+ RELATIVE_TO_PATH must be the path to a directory (not a file!) and
+ TARGET_PATH must be the path to any file or directory. Both
+ RELATIVE_TO_PATH and TARGET_PATH must be based on the same parent path,
+ i.e. they can either both be absolute or they can both be relative to the
+ same parent directory. Both paths are expected to be canonical.
+
+ If above conditions are met, a relative path that leads to TARGET_ABSPATH
+ from RELATIVE_TO_PATH is returned, but there is no error checking involved.
+
+ The returned path is allocated from RESULT_POOL, all other allocations are
+ made in SCRATCH_POOL. */
+static const char *
+make_relpath(const char *relative_to_path,
+ const char *target_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *la;
+ const char *parent_dir_els = "";
+
+ /* An example:
+ * relative_to_path = /a/b/c
+ * target_path = /a/x/y/z
+ * result = ../../x/y/z
+ *
+ * Another example (Windows specific):
+ * relative_to_path = F:/wc
+ * target_path = C:/wc
+ * result = C:/wc
+ */
+
+ /* Skip the common ancestor of both paths, here '/a'. */
+ la = svn_dirent_get_longest_ancestor(relative_to_path, target_path,
+ scratch_pool);
+ if (*la == '\0')
+ {
+ /* Nothing in common: E.g. C:/ vs F:/ on Windows */
+ return apr_pstrdup(result_pool, target_path);
+ }
+ relative_to_path = svn_dirent_skip_ancestor(la, relative_to_path);
+ target_path = svn_dirent_skip_ancestor(la, target_path);
+
+ /* In above example, we'd now have:
+ * relative_to_path = b/c
+ * target_path = x/y/z */
+
+ /* Count the elements of relative_to_path and prepend as many '..' elements
+ * to target_path. */
+ while (*relative_to_path)
+ {
+ svn_dirent_split(&relative_to_path, NULL, relative_to_path,
+ scratch_pool);
+ parent_dir_els = svn_dirent_join(parent_dir_els, "..", scratch_pool);
+ }
+
+ return svn_dirent_join(parent_dir_els, target_path, result_pool);
+}
+
+
+/* Print STATUS and PATH in a format determined by DETAILED and
+ SHOW_LAST_COMMITTED. */
+static svn_error_t *
+print_status(const char *cwd_abspath, const char *path,
+ svn_boolean_t detailed,
+ svn_boolean_t show_last_committed,
+ svn_boolean_t repos_locks,
+ const svn_client_status_t *status,
+ unsigned int *text_conflicts,
+ unsigned int *prop_conflicts,
+ unsigned int *tree_conflicts,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ enum svn_wc_status_kind node_status = status->node_status;
+ enum svn_wc_status_kind prop_status = status->prop_status;
+ char tree_status_code = ' ';
+ const char *tree_desc_line = "";
+ const char *moved_from_line = "";
+ const char *moved_to_line = "";
+
+ path = make_relpath(cwd_abspath, path, pool, pool);
+
+ /* For historic reasons svn ignores the property status for added nodes, even
+ if these nodes were copied and have local property changes.
+
+ Note that it doesn't do this on replacements, or children of copies.
+
+ ### Our test suite would catch more errors if we reported property
+ changes on copies. */
+ if (node_status == svn_wc_status_added)
+ prop_status = svn_wc_status_none;
+
+ /* To indicate this node is the victim of a tree conflict, we show
+ 'C' in the tree-conflict column, overriding any other status.
+ We also print a separate line describing the nature of the tree
+ conflict. */
+ if (status->conflicted)
+ {
+ const char *desc;
+ const char *local_abspath = status->local_abspath;
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ svn_boolean_t tree_conflicted;
+
+ if (status->versioned)
+ {
+ svn_error_t *err;
+
+ err = svn_wc_conflicted_p3(&text_conflicted,
+ &prop_conflicted,
+ &tree_conflicted, ctx->wc_ctx,
+ local_abspath, pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
+ {
+ svn_error_clear(err);
+ text_conflicted = FALSE;
+ prop_conflicted = FALSE;
+ tree_conflicted = FALSE;
+ }
+ else
+ SVN_ERR(err);
+ }
+ else
+ {
+ text_conflicted = FALSE;
+ prop_conflicted = FALSE;
+ tree_conflicted = TRUE;
+ }
+
+ if (tree_conflicted)
+ {
+ const svn_wc_conflict_description2_t *tree_conflict;
+ SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, ctx->wc_ctx,
+ local_abspath, pool, pool));
+ SVN_ERR_ASSERT(tree_conflict != NULL);
+
+ tree_status_code = 'C';
+ SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
+ &desc, tree_conflict, pool));
+ tree_desc_line = apr_psprintf(pool, "\n > %s", desc);
+ (*tree_conflicts)++;
+ }
+ else if (text_conflicted)
+ (*text_conflicts)++;
+ else if (prop_conflicted)
+ (*prop_conflicts)++;
+ }
+
+ /* Note that moved-from and moved-to information is only available in STATUS
+ * for (op-)roots of a move. Those are exactly the nodes we want to show
+ * move info for in 'svn status'. See also comments in svn_wc_status3_t. */
+ if (status->moved_from_abspath && status->moved_to_abspath &&
+ strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0)
+ {
+ const char *relpath;
+
+ relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
+ pool, pool);
+ relpath = svn_dirent_local_style(relpath, pool);
+ moved_from_line = apr_pstrcat(pool, "\n > ",
+ apr_psprintf(pool,
+ _("swapped places with %s"),
+ relpath),
+ (char *)NULL);
+ }
+ else if (status->moved_from_abspath || status->moved_to_abspath)
+ {
+ const char *relpath;
+
+ if (status->moved_from_abspath)
+ {
+ relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
+ pool, pool);
+ relpath = svn_dirent_local_style(relpath, pool);
+ moved_from_line = apr_pstrcat(pool, "\n > ",
+ apr_psprintf(pool, _("moved from %s"),
+ relpath),
+ (char *)NULL);
+ }
+
+ if (status->moved_to_abspath)
+ {
+ relpath = make_relpath(cwd_abspath, status->moved_to_abspath,
+ pool, pool);
+ relpath = svn_dirent_local_style(relpath, pool);
+ moved_to_line = apr_pstrcat(pool, "\n > ",
+ apr_psprintf(pool, _("moved to %s"),
+ relpath),
+ (char *)NULL);
+ }
+ }
+
+ if (detailed)
+ {
+ char ood_status, lock_status;
+ const char *working_rev;
+
+ if (! status->versioned)
+ working_rev = "";
+ else if (status->copied
+ || ! SVN_IS_VALID_REVNUM(status->revision))
+ working_rev = "-";
+ else
+ working_rev = apr_psprintf(pool, "%ld", status->revision);
+
+ if (status->repos_node_status != svn_wc_status_none)
+ ood_status = '*';
+ else
+ ood_status = ' ';
+
+ if (repos_locks)
+ {
+ if (status->repos_lock)
+ {
+ if (status->lock)
+ {
+ if (strcmp(status->repos_lock->token, status->lock->token)
+ == 0)
+ lock_status = 'K';
+ else
+ lock_status = 'T';
+ }
+ else
+ lock_status = 'O';
+ }
+ else if (status->lock)
+ lock_status = 'B';
+ else
+ lock_status = ' ';
+ }
+ else
+ lock_status = (status->lock) ? 'K' : ' ';
+
+ if (show_last_committed)
+ {
+ const char *commit_rev;
+ const char *commit_author;
+
+ if (SVN_IS_VALID_REVNUM(status->changed_rev))
+ commit_rev = apr_psprintf(pool, "%ld", status->changed_rev);
+ else if (status->versioned)
+ commit_rev = " ? ";
+ else
+ commit_rev = "";
+
+ if (status->changed_author)
+ commit_author = status->changed_author;
+ else if (status->versioned)
+ commit_author = " ? ";
+ else
+ commit_author = "";
+
+ SVN_ERR
+ (svn_cmdline_printf(pool,
+ "%c%c%c%c%c%c%c %c %8s %8s %-12s %s%s%s%s\n",
+ generate_status_code(combined_status(status)),
+ generate_status_code(prop_status),
+ status->wc_is_locked ? 'L' : ' ',
+ status->copied ? '+' : ' ',
+ generate_switch_column_code(status),
+ lock_status,
+ tree_status_code,
+ ood_status,
+ working_rev,
+ commit_rev,
+ commit_author,
+ path,
+ moved_to_line,
+ moved_from_line,
+ tree_desc_line));
+ }
+ else
+ SVN_ERR(
+ svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s %s%s%s%s\n",
+ generate_status_code(combined_status(status)),
+ generate_status_code(prop_status),
+ status->wc_is_locked ? 'L' : ' ',
+ status->copied ? '+' : ' ',
+ generate_switch_column_code(status),
+ lock_status,
+ tree_status_code,
+ ood_status,
+ working_rev,
+ path,
+ moved_to_line,
+ moved_from_line,
+ tree_desc_line));
+ }
+ else
+ SVN_ERR(
+ svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s%s%s\n",
+ generate_status_code(combined_status(status)),
+ generate_status_code(prop_status),
+ status->wc_is_locked ? 'L' : ' ',
+ status->copied ? '+' : ' ',
+ generate_switch_column_code(status),
+ ((status->lock)
+ ? 'K' : ' '),
+ tree_status_code,
+ path,
+ moved_to_line,
+ moved_from_line,
+ tree_desc_line));
+
+ return svn_cmdline_fflush(stdout);
+}
+
+
+svn_error_t *
+svn_cl__print_status_xml(const char *cwd_abspath,
+ const char *path,
+ const svn_client_status_t *status,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+ apr_hash_t *att_hash;
+ const char *local_abspath = status->local_abspath;
+ svn_boolean_t tree_conflicted = FALSE;
+
+ if (status->node_status == svn_wc_status_none
+ && status->repos_node_status == svn_wc_status_none)
+ return SVN_NO_ERROR;
+
+ if (status->conflicted)
+ SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
+ ctx->wc_ctx, local_abspath, pool));
+
+ path = make_relpath(cwd_abspath, path, pool, pool);
+
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
+ "path", svn_dirent_local_style(path, pool), NULL);
+
+ att_hash = apr_hash_make(pool);
+ svn_hash_sets(att_hash, "item",
+ generate_status_desc(combined_status(status)));
+
+ svn_hash_sets(att_hash, "props",
+ generate_status_desc(
+ (status->node_status != svn_wc_status_deleted)
+ ? status->prop_status
+ : svn_wc_status_none));
+ if (status->wc_is_locked)
+ svn_hash_sets(att_hash, "wc-locked", "true");
+ if (status->copied)
+ svn_hash_sets(att_hash, "copied", "true");
+ if (status->switched)
+ svn_hash_sets(att_hash, "switched", "true");
+ if (status->file_external)
+ svn_hash_sets(att_hash, "file-external", "true");
+ if (status->versioned && ! status->copied)
+ svn_hash_sets(att_hash, "revision",
+ apr_psprintf(pool, "%ld", status->revision));
+ if (tree_conflicted)
+ svn_hash_sets(att_hash, "tree-conflicted", "true");
+ if (status->moved_from_abspath || status->moved_to_abspath)
+ {
+ const char *relpath;
+
+ if (status->moved_from_abspath)
+ {
+ relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
+ pool, pool);
+ relpath = svn_dirent_local_style(relpath, pool);
+ svn_hash_sets(att_hash, "moved-from", relpath);
+ }
+ if (status->moved_to_abspath)
+ {
+ relpath = make_relpath(cwd_abspath, status->moved_to_abspath,
+ pool, pool);
+ relpath = svn_dirent_local_style(relpath, pool);
+ svn_hash_sets(att_hash, "moved-to", relpath);
+ }
+ }
+ svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status",
+ att_hash);
+
+ if (SVN_IS_VALID_REVNUM(status->changed_rev))
+ {
+ svn_cl__print_xml_commit(&sb, status->changed_rev,
+ status->changed_author,
+ svn_time_to_cstring(status->changed_date,
+ pool),
+ pool);
+ }
+
+ if (status->lock)
+ svn_cl__print_xml_lock(&sb, status->lock, pool);
+
+ svn_xml_make_close_tag(&sb, pool, "wc-status");
+
+ if (status->repos_node_status != svn_wc_status_none
+ || status->repos_lock)
+ {
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status",
+ "item",
+ generate_status_desc(combined_repos_status(status)),
+ "props",
+ generate_status_desc(status->repos_prop_status),
+ NULL);
+ if (status->repos_lock)
+ svn_cl__print_xml_lock(&sb, status->repos_lock, pool);
+
+ svn_xml_make_close_tag(&sb, pool, "repos-status");
+ }
+
+ svn_xml_make_close_tag(&sb, pool, "entry");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+/* Called by status-cmd.c */
+svn_error_t *
+svn_cl__print_status(const char *cwd_abspath,
+ const char *path,
+ const svn_client_status_t *status,
+ svn_boolean_t suppress_externals_placeholders,
+ svn_boolean_t detailed,
+ svn_boolean_t show_last_committed,
+ svn_boolean_t skip_unrecognized,
+ svn_boolean_t repos_locks,
+ unsigned int *text_conflicts,
+ unsigned int *prop_conflicts,
+ unsigned int *tree_conflicts,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ if (! status
+ || (skip_unrecognized
+ && !(status->versioned
+ || status->conflicted
+ || status->node_status == svn_wc_status_external))
+ || (status->node_status == svn_wc_status_none
+ && status->repos_node_status == svn_wc_status_none))
+ return SVN_NO_ERROR;
+
+ /* If we're trying not to print boring "X /path/to/external"
+ lines..." */
+ if (suppress_externals_placeholders)
+ {
+ /* ... skip regular externals unmodified in the repository. */
+ if ((status->node_status == svn_wc_status_external)
+ && (status->repos_node_status == svn_wc_status_none)
+ && (! status->conflicted))
+ return SVN_NO_ERROR;
+
+ /* ... skip file externals that aren't modified locally or
+ remotely, changelisted, or locked (in either sense of the
+ word). */
+ if ((status->file_external)
+ && (status->repos_node_status == svn_wc_status_none)
+ && ((status->node_status == svn_wc_status_normal)
+ || (status->node_status == svn_wc_status_none))
+ && ((status->prop_status == svn_wc_status_normal)
+ || (status->prop_status == svn_wc_status_none))
+ && (! status->changelist)
+ && (! status->lock)
+ && (! status->wc_is_locked)
+ && (! status->conflicted))
+ return SVN_NO_ERROR;
+ }
+
+ return print_status(cwd_abspath, svn_dirent_local_style(path, pool),
+ detailed, show_last_committed, repos_locks, status,
+ text_conflicts, prop_conflicts, tree_conflicts,
+ ctx, pool);
+}
diff --git a/contrib/subversion/subversion/svn/svn.1 b/contrib/subversion/subversion/svn/svn.1
new file mode 100644
index 0000000..f4ad251
--- /dev/null
+++ b/contrib/subversion/subversion/svn/svn.1
@@ -0,0 +1,47 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svn 1
+.SH NAME
+svn \- Subversion command line client tool
+.SH SYNOPSIS
+.TP
+\fBsvn\fP \fIcommand\fP [\fIoptions\fP] [\fIargs\fP]
+.SH OVERVIEW
+Subversion is a version control system, which allows you to keep old
+versions of files and directories (usually source code), keep a log of
+who, when, and why changes occurred, etc., like CVS, RCS or SCCS.
+\fBSubversion\fP keeps a single copy of the master sources. This copy
+is called the source ``repository''; it contains all the information
+to permit extracting previous versions of those files at any time.
+
+For more information about the Subversion project, visit
+http://subversion.apache.org.
+
+Documentation for Subversion and its tools, including detailed usage
+explanations of the \fBsvn\fP, \fBsvnadmin\fP, \fBsvnserve\fP and
+\fBsvnlook\fP programs, historical background, philosophical
+approaches and reasonings, etc., can be found at
+http://svnbook.red-bean.com/.
+
+Run `svn help' to access the built-in tool documentation.
diff --git a/contrib/subversion/subversion/svn/svn.c b/contrib/subversion/subversion/svn/svn.c
new file mode 100644
index 0000000..cbcec87
--- /dev/null
+++ b/contrib/subversion/subversion/svn/svn.c
@@ -0,0 +1,2961 @@
+/*
+ * svn.c: Subversion command line client main file.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <assert.h>
+
+#include <apr_strings.h>
+#include <apr_tables.h>
+#include <apr_general.h>
+#include <apr_signal.h>
+
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_config.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_delta.h"
+#include "svn_diff.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_opt.h"
+#include "svn_utf.h"
+#include "svn_auth.h"
+#include "svn_hash.h"
+#include "svn_version.h"
+#include "cl.h"
+
+#include "private/svn_opt_private.h"
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Option Processing ***/
+
+/* Add an identifier here for long options that don't have a short
+ option. Options that have both long and short options should just
+ use the short option letter as identifier. */
+typedef enum svn_cl__longopt_t {
+ opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID,
+ opt_auth_username,
+ opt_autoprops,
+ opt_changelist,
+ opt_config_dir,
+ opt_config_options,
+ /* diff options */
+ opt_diff_cmd,
+ opt_internal_diff,
+ opt_no_diff_added,
+ opt_no_diff_deleted,
+ opt_show_copies_as_adds,
+ opt_notice_ancestry,
+ opt_summarize,
+ opt_use_git_diff_format,
+ opt_ignore_properties,
+ opt_properties_only,
+ opt_patch_compatible,
+ /* end of diff options */
+ opt_dry_run,
+ opt_editor_cmd,
+ opt_encoding,
+ opt_force_log,
+ opt_force,
+ opt_keep_changelists,
+ opt_ignore_ancestry,
+ opt_ignore_externals,
+ opt_incremental,
+ opt_merge_cmd,
+ opt_native_eol,
+ opt_new_cmd,
+ opt_no_auth_cache,
+ opt_no_autoprops,
+ opt_no_ignore,
+ opt_no_unlock,
+ opt_non_interactive,
+ opt_force_interactive,
+ opt_old_cmd,
+ opt_record_only,
+ opt_relocate,
+ opt_remove,
+ opt_revprop,
+ opt_stop_on_copy,
+ opt_strict,
+ opt_targets,
+ opt_depth,
+ opt_set_depth,
+ opt_version,
+ opt_xml,
+ opt_keep_local,
+ opt_with_revprop,
+ opt_with_all_revprops,
+ opt_with_no_revprops,
+ opt_parents,
+ opt_accept,
+ opt_show_revs,
+ opt_reintegrate,
+ opt_trust_server_cert,
+ opt_strip,
+ opt_ignore_keywords,
+ opt_reverse_diff,
+ opt_ignore_whitespace,
+ opt_diff,
+ opt_allow_mixed_revisions,
+ opt_include_externals,
+ opt_show_inherited_props,
+ opt_search,
+ opt_search_and
+} svn_cl__longopt_t;
+
+
+/* Option codes and descriptions for the command line client.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ */
+const apr_getopt_option_t svn_cl__options[] =
+{
+ {"force", opt_force, 0, N_("force operation to run")},
+ {"force-log", opt_force_log, 0,
+ N_("force validity of log message source")},
+ {"help", 'h', 0, N_("show help on a subcommand")},
+ {NULL, '?', 0, N_("show help on a subcommand")},
+ {"message", 'm', 1, N_("specify log message ARG")},
+ {"quiet", 'q', 0, N_("print nothing, or only summary information")},
+ {"recursive", 'R', 0, N_("descend recursively, same as --depth=infinity")},
+ {"non-recursive", 'N', 0, N_("obsolete; try --depth=files or --depth=immediates")},
+ {"change", 'c', 1,
+ N_("the change made by revision ARG (like -r ARG-1:ARG)\n"
+ " "
+ "If ARG is negative this is like -r ARG:ARG-1\n"
+ " "
+ "If ARG is of the form ARG1-ARG2 then this is like\n"
+ " "
+ "ARG1:ARG2, where ARG1 is inclusive")},
+ {"revision", 'r', 1,
+ N_("ARG (some commands also take ARG1:ARG2 range)\n"
+ " "
+ "A revision argument can be one of:\n"
+ " "
+ " NUMBER revision number\n"
+ " "
+ " '{' DATE '}' revision at start of the date\n"
+ " "
+ " 'HEAD' latest in repository\n"
+ " "
+ " 'BASE' base rev of item's working copy\n"
+ " "
+ " 'COMMITTED' last commit at or before BASE\n"
+ " "
+ " 'PREV' revision just before COMMITTED")},
+ {"file", 'F', 1, N_("read log message from file ARG")},
+ {"incremental", opt_incremental, 0,
+ N_("give output suitable for concatenation")},
+ {"encoding", opt_encoding, 1,
+ N_("treat value as being in charset encoding ARG")},
+ {"version", opt_version, 0, N_("show program version information")},
+ {"verbose", 'v', 0, N_("print extra information")},
+ {"show-updates", 'u', 0, N_("display update information")},
+ {"username", opt_auth_username, 1, N_("specify a username ARG")},
+ {"password", opt_auth_password, 1, N_("specify a password ARG")},
+ {"extensions", 'x', 1,
+ N_("Specify differencing options for external diff or\n"
+ " "
+ "internal diff or blame. Default: '-u'. Options are\n"
+ " "
+ "separated by spaces. Internal diff and blame take:\n"
+ " "
+ " -u, --unified: Show 3 lines of unified context\n"
+ " "
+ " -b, --ignore-space-change: Ignore changes in\n"
+ " "
+ " amount of white space\n"
+ " "
+ " -w, --ignore-all-space: Ignore all white space\n"
+ " "
+ " --ignore-eol-style: Ignore changes in EOL style\n"
+ " "
+ " -p, --show-c-function: Show C function name")},
+ {"targets", opt_targets, 1,
+ N_("pass contents of file ARG as additional args")},
+ {"depth", opt_depth, 1,
+ N_("limit operation by depth ARG ('empty', 'files',\n"
+ " "
+ "'immediates', or 'infinity')")},
+ {"set-depth", opt_set_depth, 1,
+ N_("set new working copy depth to ARG ('exclude',\n"
+ " "
+ "'empty', 'files', 'immediates', or 'infinity')")},
+ {"xml", opt_xml, 0, N_("output in XML")},
+ {"strict", opt_strict, 0, N_("use strict semantics")},
+ {"stop-on-copy", opt_stop_on_copy, 0,
+ N_("do not cross copies while traversing history")},
+ {"no-ignore", opt_no_ignore, 0,
+ N_("disregard default and svn:ignore and\n"
+ " "
+ "svn:global-ignores property ignores")},
+ {"no-auth-cache", opt_no_auth_cache, 0,
+ N_("do not cache authentication tokens")},
+ {"trust-server-cert", opt_trust_server_cert, 0,
+ N_("accept SSL server certificates from unknown\n"
+ " "
+ "certificate authorities without prompting (but only\n"
+ " "
+ "with '--non-interactive')") },
+ {"non-interactive", opt_non_interactive, 0,
+ N_("do no interactive prompting (default is to prompt\n"
+ " "
+ "only if standard input is a terminal device)")},
+ {"force-interactive", opt_force_interactive, 0,
+ N_("do interactive prompting even if standard input\n"
+ " "
+ "is not a terminal device")},
+ {"dry-run", opt_dry_run, 0,
+ N_("try operation but make no changes")},
+ {"ignore-ancestry", opt_ignore_ancestry, 0,
+ N_("disable merge tracking; diff nodes as if related")},
+ {"ignore-externals", opt_ignore_externals, 0,
+ N_("ignore externals definitions")},
+ {"diff3-cmd", opt_merge_cmd, 1, N_("use ARG as merge command")},
+ {"editor-cmd", opt_editor_cmd, 1, N_("use ARG as external editor")},
+ {"record-only", opt_record_only, 0,
+ N_("merge only mergeinfo differences")},
+ {"old", opt_old_cmd, 1, N_("use ARG as the older target")},
+ {"new", opt_new_cmd, 1, N_("use ARG as the newer target")},
+ {"revprop", opt_revprop, 0,
+ N_("operate on a revision property (use with -r)")},
+ {"relocate", opt_relocate, 0, N_("relocate via URL-rewriting")},
+ {"config-dir", opt_config_dir, 1,
+ N_("read user configuration files from directory ARG")},
+ {"config-option", opt_config_options, 1,
+ N_("set user configuration option in the format:\n"
+ " "
+ " FILE:SECTION:OPTION=[VALUE]\n"
+ " "
+ "For example:\n"
+ " "
+ " servers:global:http-library=serf")},
+ {"auto-props", opt_autoprops, 0, N_("enable automatic properties")},
+ {"no-auto-props", opt_no_autoprops, 0, N_("disable automatic properties")},
+ {"native-eol", opt_native_eol, 1,
+ N_("use a different EOL marker than the standard\n"
+ " "
+ "system marker for files with the svn:eol-style\n"
+ " "
+ "property set to 'native'.\n"
+ " "
+ "ARG may be one of 'LF', 'CR', 'CRLF'")},
+ {"limit", 'l', 1, N_("maximum number of log entries")},
+ {"no-unlock", opt_no_unlock, 0, N_("don't unlock the targets")},
+ {"remove", opt_remove, 0, N_("remove changelist association")},
+ {"changelist", opt_changelist, 1,
+ N_("operate only on members of changelist ARG")},
+ {"keep-changelists", opt_keep_changelists, 0,
+ N_("don't delete changelists after commit")},
+ {"keep-local", opt_keep_local, 0, N_("keep path in working copy")},
+ {"with-all-revprops", opt_with_all_revprops, 0,
+ N_("retrieve all revision properties")},
+ {"with-no-revprops", opt_with_no_revprops, 0,
+ N_("retrieve no revision properties")},
+ {"with-revprop", opt_with_revprop, 1,
+ N_("set revision property ARG in new revision\n"
+ " "
+ "using the name[=value] format")},
+ {"parents", opt_parents, 0, N_("make intermediate directories")},
+ {"use-merge-history", 'g', 0,
+ N_("use/display additional information from merge\n"
+ " "
+ "history")},
+ {"accept", opt_accept, 1,
+ N_("specify automatic conflict resolution action\n"
+ " "
+ "('postpone', 'working', 'base', 'mine-conflict',\n"
+ " "
+ "'theirs-conflict', 'mine-full', 'theirs-full',\n"
+ " "
+ "'edit', 'launch')\n"
+ " "
+ "(shorthand: 'p', 'mc', 'tc', 'mf', 'tf', 'e', 'l')"
+ )},
+ {"show-revs", opt_show_revs, 1,
+ N_("specify which collection of revisions to display\n"
+ " "
+ "('merged', 'eligible')")},
+ {"reintegrate", opt_reintegrate, 0,
+ N_("deprecated")},
+ {"strip", opt_strip, 1,
+ N_("number of leading path components to strip from\n"
+ " "
+ "paths parsed from the patch file. --strip 0\n"
+ " "
+ "is the default and leaves paths unmodified.\n"
+ " "
+ "--strip 1 would change the path\n"
+ " "
+ "'doc/fudge/crunchy.html' to 'fudge/crunchy.html'.\n"
+ " "
+ "--strip 2 would leave just 'crunchy.html'\n"
+ " "
+ "The expected component separator is '/' on all\n"
+ " "
+ "platforms. A leading '/' counts as one component.")},
+ {"ignore-keywords", opt_ignore_keywords, 0,
+ N_("don't expand keywords")},
+ {"reverse-diff", opt_reverse_diff, 0,
+ N_("apply the unidiff in reverse")},
+ {"ignore-whitespace", opt_ignore_whitespace, 0,
+ N_("ignore whitespace during pattern matching")},
+ {"diff", opt_diff, 0, N_("produce diff output")}, /* maps to show_diff */
+ /* diff options */
+ {"diff-cmd", opt_diff_cmd, 1, N_("use ARG as diff command")},
+ {"internal-diff", opt_internal_diff, 0,
+ N_("override diff-cmd specified in config file")},
+ {"no-diff-added", opt_no_diff_added, 0,
+ N_("do not print differences for added files")},
+ {"no-diff-deleted", opt_no_diff_deleted, 0,
+ N_("do not print differences for deleted files")},
+ {"show-copies-as-adds", opt_show_copies_as_adds, 0,
+ N_("don't diff copied or moved files with their source")},
+ {"notice-ancestry", opt_notice_ancestry, 0,
+ N_("diff unrelated nodes as delete and add")},
+ {"summarize", opt_summarize, 0, N_("show a summary of the results")},
+ {"git", opt_use_git_diff_format, 0,
+ N_("use git's extended diff format")},
+ {"ignore-properties", opt_ignore_properties, 0,
+ N_("ignore properties during the operation")},
+ {"properties-only", opt_properties_only, 0,
+ N_("show only properties during the operation")},
+ {"patch-compatible", opt_patch_compatible, 0,
+ N_("generate diff suitable for generic third-party\n"
+ " "
+ "patch tools; currently the same as\n"
+ " "
+ "--show-copies-as-adds --ignore-properties"
+ )},
+ /* end of diff options */
+ {"allow-mixed-revisions", opt_allow_mixed_revisions, 0,
+ N_("Allow operation on mixed-revision working copy.\n"
+ " "
+ "Use of this option is not recommended!\n"
+ " "
+ "Please run 'svn update' instead.")},
+ {"include-externals", opt_include_externals, 0,
+ N_("Also commit file and dir externals reached by\n"
+ " "
+ "recursion. This does not include externals with a\n"
+ " "
+ "fixed revision. (See the svn:externals property)")},
+ {"show-inherited-props", opt_show_inherited_props, 0,
+ N_("retrieve target's inherited properties")},
+ {"search", opt_search, 1,
+ N_("use ARG as search pattern (glob syntax)")},
+ {"search-and", opt_search_and, 1,
+ N_("combine ARG with the previous search pattern")},
+
+ /* Long-opt Aliases
+ *
+ * These have NULL desriptions, but an option code that matches some
+ * other option (whose description should probably mention its aliases).
+ */
+
+ {"cl", opt_changelist, 1, NULL},
+
+ {0, 0, 0, 0},
+};
+
+
+
+/*** Command dispatch. ***/
+
+/* Our array of available subcommands.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ *
+ * In most of the help text "PATH" is used where a working copy path is
+ * required, "URL" where a repository URL is required and "TARGET" when
+ * either a path or a url can be used. Hmm, should this be part of the
+ * help text?
+ */
+
+/* Options that apply to all commands. (While not every command may
+ currently require authentication or be interactive, allowing every
+ command to take these arguments allows scripts to just pass them
+ willy-nilly to every invocation of 'svn') . */
+const int svn_cl__global_options[] =
+{ opt_auth_username, opt_auth_password, opt_no_auth_cache, opt_non_interactive,
+ opt_force_interactive, opt_trust_server_cert, opt_config_dir,
+ opt_config_options, 0
+};
+
+/* Options for giving a log message. (Some of these also have other uses.)
+ */
+#define SVN_CL__LOG_MSG_OPTIONS 'm', 'F', \
+ opt_force_log, \
+ opt_editor_cmd, \
+ opt_encoding, \
+ opt_with_revprop
+
+const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
+{
+ { "add", svn_cl__add, {0}, N_
+ ("Put files and directories under version control, scheduling\n"
+ "them for addition to repository. They will be added in next commit.\n"
+ "usage: add PATH...\n"),
+ {opt_targets, 'N', opt_depth, 'q', opt_force, opt_no_ignore, opt_autoprops,
+ opt_no_autoprops, opt_parents },
+ {{opt_parents, N_("add intermediate parents")}} },
+
+ { "blame", svn_cl__blame, {"praise", "annotate", "ann"}, N_
+ ("Output the content of specified files or\n"
+ "URLs with revision and author information in-line.\n"
+ "usage: blame TARGET[@REV]...\n"
+ "\n"
+ " If specified, REV determines in which revision the target is first\n"
+ " looked up.\n"),
+ {'r', 'v', 'g', opt_incremental, opt_xml, 'x', opt_force} },
+
+ { "cat", svn_cl__cat, {0}, N_
+ ("Output the content of specified files or URLs.\n"
+ "usage: cat TARGET[@REV]...\n"
+ "\n"
+ " If specified, REV determines in which revision the target is first\n"
+ " looked up.\n"),
+ {'r'} },
+
+ { "changelist", svn_cl__changelist, {"cl"}, N_
+ ("Associate (or dissociate) changelist CLNAME with the named files.\n"
+ "usage: 1. changelist CLNAME PATH...\n"
+ " 2. changelist --remove PATH...\n"),
+ { 'q', 'R', opt_depth, opt_remove, opt_targets, opt_changelist} },
+
+ { "checkout", svn_cl__checkout, {"co"}, N_
+ ("Check out a working copy from a repository.\n"
+ "usage: checkout URL[@REV]... [PATH]\n"
+ "\n"
+ " If specified, REV determines in which revision the URL is first\n"
+ " looked up.\n"
+ "\n"
+ " If PATH is omitted, the basename of the URL will be used as\n"
+ " the destination. If multiple URLs are given each will be checked\n"
+ " out into a sub-directory of PATH, with the name of the sub-directory\n"
+ " being the basename of the URL.\n"
+ "\n"
+ " If --force is used, unversioned obstructing paths in the working\n"
+ " copy destination do not automatically cause the check out to fail.\n"
+ " If the obstructing path is the same type (file or directory) as the\n"
+ " corresponding path in the repository it becomes versioned but its\n"
+ " contents are left 'as-is' in the working copy. This means that an\n"
+ " obstructing directory's unversioned children may also obstruct and\n"
+ " become versioned. For files, any content differences between the\n"
+ " obstruction and the repository are treated like a local modification\n"
+ " to the working copy. All properties from the repository are applied\n"
+ " to the obstructing path.\n"
+ "\n"
+ " See also 'svn help update' for a list of possible characters\n"
+ " reporting the action taken.\n"),
+ {'r', 'q', 'N', opt_depth, opt_force, opt_ignore_externals} },
+
+ { "cleanup", svn_cl__cleanup, {0}, N_
+ ("Recursively clean up the working copy, removing locks, resuming\n"
+ "unfinished operations, etc.\n"
+ "usage: cleanup [WCPATH...]\n"),
+ {opt_merge_cmd} },
+
+ { "commit", svn_cl__commit, {"ci"},
+ N_("Send changes from your working copy to the repository.\n"
+ "usage: commit [PATH...]\n"
+ "\n"
+ " A log message must be provided, but it can be empty. If it is not\n"
+ " given by a --message or --file option, an editor will be started.\n"
+ " If any targets are (or contain) locked items, those will be\n"
+ " unlocked after a successful commit.\n"),
+ {'q', 'N', opt_depth, opt_targets, opt_no_unlock, SVN_CL__LOG_MSG_OPTIONS,
+ opt_changelist, opt_keep_changelists, opt_include_externals} },
+
+ { "copy", svn_cl__copy, {"cp"}, N_
+ ("Copy files and directories in a working copy or repository.\n"
+ "usage: copy SRC[@REV]... DST\n"
+ "\n"
+ " SRC and DST can each be either a working copy (WC) path or URL:\n"
+ " WC -> WC: copy and schedule for addition (with history)\n"
+ " WC -> URL: immediately commit a copy of WC to URL\n"
+ " URL -> WC: check out URL into WC, schedule for addition\n"
+ " URL -> URL: complete server-side copy; used to branch and tag\n"
+ " All the SRCs must be of the same type. When copying multiple sources,\n"
+ " they will be added as children of DST, which must be a directory.\n"
+ "\n"
+ " WARNING: For compatibility with previous versions of Subversion,\n"
+ " copies performed using two working copy paths (WC -> WC) will not\n"
+ " contact the repository. As such, they may not, by default, be able\n"
+ " to propagate merge tracking information from the source of the copy\n"
+ " to the destination.\n"),
+ {'r', 'q', opt_ignore_externals, opt_parents, SVN_CL__LOG_MSG_OPTIONS} },
+
+ { "delete", svn_cl__delete, {"del", "remove", "rm"}, N_
+ ("Remove files and directories from version control.\n"
+ "usage: 1. delete PATH...\n"
+ " 2. delete URL...\n"
+ "\n"
+ " 1. Each item specified by a PATH is scheduled for deletion upon\n"
+ " the next commit. Files, and directories that have not been\n"
+ " committed, are immediately removed from the working copy\n"
+ " unless the --keep-local option is given.\n"
+ " PATHs that are, or contain, unversioned or modified items will\n"
+ " not be removed unless the --force or --keep-local option is given.\n"
+ "\n"
+ " 2. Each item specified by a URL is deleted from the repository\n"
+ " via an immediate commit.\n"),
+ {opt_force, 'q', opt_targets, SVN_CL__LOG_MSG_OPTIONS, opt_keep_local} },
+
+ { "diff", svn_cl__diff, {"di"}, N_
+ ("Display local changes or differences between two revisions or paths.\n"
+ "usage: 1. diff\n"
+ " 2. diff [-c M | -r N[:M]] [TARGET[@REV]...]\n"
+ " 3. diff [-r N[:M]] --old=OLD-TGT[@OLDREV] [--new=NEW-TGT[@NEWREV]] \\\n"
+ " [PATH...]\n"
+ " 4. diff OLD-URL[@OLDREV] NEW-URL[@NEWREV]\n"
+ " 5. diff OLD-URL[@OLDREV] NEW-PATH[@NEWREV]\n"
+ " 6. diff OLD-PATH[@OLDREV] NEW-URL[@NEWREV]\n"
+ "\n"
+ " 1. Use just 'svn diff' to display local modifications in a working copy.\n"
+ "\n"
+ " 2. Display the changes made to TARGETs as they are seen in REV between\n"
+ " two revisions. TARGETs may be all working copy paths or all URLs.\n"
+ " If TARGETs are working copy paths, N defaults to BASE and M to the\n"
+ " working copy; if URLs, N must be specified and M defaults to HEAD.\n"
+ " The '-c M' option is equivalent to '-r N:M' where N = M-1.\n"
+ " Using '-c -M' does the reverse: '-r M:N' where N = M-1.\n"
+ "\n"
+ " 3. Display the differences between OLD-TGT as it was seen in OLDREV and\n"
+ " NEW-TGT as it was seen in NEWREV. PATHs, if given, are relative to\n"
+ " OLD-TGT and NEW-TGT and restrict the output to differences for those\n"
+ " paths. OLD-TGT and NEW-TGT may be working copy paths or URL[@REV].\n"
+ " NEW-TGT defaults to OLD-TGT if not specified. -r N makes OLDREV default\n"
+ " to N, -r N:M makes OLDREV default to N and NEWREV default to M.\n"
+ " If OLDREV or NEWREV are not specified, they default to WORKING for\n"
+ " working copy targets and to HEAD for URL targets.\n"
+ "\n"
+ " Either or both OLD-TGT and NEW-TGT may also be paths to unversioned\n"
+ " targets. Revisions cannot be specified for unversioned targets.\n"
+ " Both targets must be of the same node kind (file or directory).\n"
+ " Diffing unversioned targets against URL targets is not supported.\n"
+ "\n"
+ " 4. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-URL[@NEWREV]'\n"
+ " 5. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-PATH[@NEWREV]'\n"
+ " 6. Shorthand for 'svn diff --old=OLD-PATH[@OLDREV] --new=NEW-URL[@NEWREV]'\n"),
+ {'r', 'c', opt_old_cmd, opt_new_cmd, 'N', opt_depth, opt_diff_cmd,
+ opt_internal_diff, 'x', opt_no_diff_added, opt_no_diff_deleted,
+ opt_ignore_properties, opt_properties_only,
+ opt_show_copies_as_adds, opt_notice_ancestry, opt_summarize, opt_changelist,
+ opt_force, opt_xml, opt_use_git_diff_format, opt_patch_compatible} },
+ { "export", svn_cl__export, {0}, N_
+ ("Create an unversioned copy of a tree.\n"
+ "usage: 1. export [-r REV] URL[@PEGREV] [PATH]\n"
+ " 2. export [-r REV] PATH1[@PEGREV] [PATH2]\n"
+ "\n"
+ " 1. Exports a clean directory tree from the repository specified by\n"
+ " URL, at revision REV if it is given, otherwise at HEAD, into\n"
+ " PATH. If PATH is omitted, the last component of the URL is used\n"
+ " for the local directory name.\n"
+ "\n"
+ " 2. Exports a clean directory tree from the working copy specified by\n"
+ " PATH1, at revision REV if it is given, otherwise at WORKING, into\n"
+ " PATH2. If PATH2 is omitted, the last component of the PATH1 is used\n"
+ " for the local directory name. If REV is not specified, all local\n"
+ " changes will be preserved. Files not under version control will\n"
+ " not be copied.\n"
+ "\n"
+ " If specified, PEGREV determines in which revision the target is first\n"
+ " looked up.\n"),
+ {'r', 'q', 'N', opt_depth, opt_force, opt_native_eol, opt_ignore_externals,
+ opt_ignore_keywords} },
+
+ { "help", svn_cl__help, {"?", "h"}, N_
+ ("Describe the usage of this program or its subcommands.\n"
+ "usage: help [SUBCOMMAND...]\n"),
+ {0} },
+ /* This command is also invoked if we see option "--help", "-h" or "-?". */
+
+ { "import", svn_cl__import, {0}, N_
+ ("Commit an unversioned file or tree into the repository.\n"
+ "usage: import [PATH] URL\n"
+ "\n"
+ " Recursively commit a copy of PATH to URL.\n"
+ " If PATH is omitted '.' is assumed.\n"
+ " Parent directories are created as necessary in the repository.\n"
+ " If PATH is a directory, the contents of the directory are added\n"
+ " directly under URL.\n"
+ " Unversionable items such as device files and pipes are ignored\n"
+ " if --force is specified.\n"),
+ {'q', 'N', opt_depth, opt_autoprops, opt_force, opt_no_autoprops,
+ SVN_CL__LOG_MSG_OPTIONS, opt_no_ignore} },
+
+ { "info", svn_cl__info, {0}, N_
+ ("Display information about a local or remote item.\n"
+ "usage: info [TARGET[@REV]...]\n"
+ "\n"
+ " Print information about each TARGET (default: '.').\n"
+ " TARGET may be either a working-copy path or URL. If specified, REV\n"
+ " determines in which revision the target is first looked up.\n"),
+ {'r', 'R', opt_depth, opt_targets, opt_incremental, opt_xml, opt_changelist}
+ },
+
+ { "list", svn_cl__list, {"ls"}, N_
+ ("List directory entries in the repository.\n"
+ "usage: list [TARGET[@REV]...]\n"
+ "\n"
+ " List each TARGET file and the contents of each TARGET directory as\n"
+ " they exist in the repository. If TARGET is a working copy path, the\n"
+ " corresponding repository URL will be used. If specified, REV determines\n"
+ " in which revision the target is first looked up.\n"
+ "\n"
+ " The default TARGET is '.', meaning the repository URL of the current\n"
+ " working directory.\n"
+ "\n"
+ " With --verbose, the following fields will be shown for each item:\n"
+ "\n"
+ " Revision number of the last commit\n"
+ " Author of the last commit\n"
+ " If locked, the letter 'O'. (Use 'svn info URL' to see details)\n"
+ " Size (in bytes)\n"
+ " Date and time of the last commit\n"),
+ {'r', 'v', 'R', opt_depth, opt_incremental, opt_xml,
+ opt_include_externals },
+ {{opt_include_externals, N_("include externals definitions")}} },
+
+ { "lock", svn_cl__lock, {0}, N_
+ ("Lock working copy paths or URLs in the repository, so that\n"
+ "no other user can commit changes to them.\n"
+ "usage: lock TARGET...\n"
+ "\n"
+ " Use --force to steal the lock from another user or working copy.\n"),
+ { opt_targets, 'm', 'F', opt_force_log, opt_encoding, opt_force },
+ {{'F', N_("read lock comment from file ARG")},
+ {'m', N_("specify lock comment ARG")},
+ {opt_force_log, N_("force validity of lock comment source")}} },
+
+ { "log", svn_cl__log, {0}, N_
+ ("Show the log messages for a set of revision(s) and/or path(s).\n"
+ "usage: 1. log [PATH][@REV]\n"
+ " 2. log URL[@REV] [PATH...]\n"
+ "\n"
+ " 1. Print the log messages for the URL corresponding to PATH\n"
+ " (default: '.'). If specified, REV is the revision in which the\n"
+ " URL is first looked up, and the default revision range is REV:1.\n"
+ " If REV is not specified, the default revision range is BASE:1,\n"
+ " since the URL might not exist in the HEAD revision.\n"
+ "\n"
+ " 2. Print the log messages for the PATHs (default: '.') under URL.\n"
+ " If specified, REV is the revision in which the URL is first\n"
+ " looked up, and the default revision range is REV:1; otherwise,\n"
+ " the URL is looked up in HEAD, and the default revision range is\n"
+ " HEAD:1.\n"
+ "\n"
+ " Multiple '-c' or '-r' options may be specified (but not a\n"
+ " combination of '-c' and '-r' options), and mixing of forward and\n"
+ " reverse ranges is allowed.\n"
+ "\n"
+ " With -v, also print all affected paths with each log message.\n"
+ " With -q, don't print the log message body itself (note that this is\n"
+ " compatible with -v).\n"
+ "\n"
+ " Each log message is printed just once, even if more than one of the\n"
+ " affected paths for that revision were explicitly requested. Logs\n"
+ " follow copy history by default. Use --stop-on-copy to disable this\n"
+ " behavior, which can be useful for determining branchpoints.\n"
+ "\n"
+ " The --depth option is only valid in combination with the --diff option\n"
+ " and limits the scope of the displayed diff to the specified depth.\n"
+ "\n"
+ " If the --search option is used, log messages are displayed only if the\n"
+ " provided search pattern matches any of the author, date, log message\n"
+ " text (unless --quiet is used), or, if the --verbose option is also\n"
+ " provided, a changed path.\n"
+ " The search pattern may include \"glob syntax\" wildcards:\n"
+ " ? matches any single character\n"
+ " * matches a sequence of arbitrary characters\n"
+ " [abc] matches any of the characters listed inside the brackets\n"
+ " If multiple --search options are provided, a log message is shown if\n"
+ " it matches any of the provided search patterns. If the --search-and\n"
+ " option is used, that option's argument is combined with the pattern\n"
+ " from the previous --search or --search-and option, and a log message\n"
+ " is shown only if it matches the combined search pattern.\n"
+ " If --limit is used in combination with --search, --limit restricts the\n"
+ " number of log messages searched, rather than restricting the output\n"
+ " to a particular number of matching log messages.\n"
+ "\n"
+ " Examples:\n"
+ "\n"
+ " Show the latest 5 log messages for the current working copy\n"
+ " directory and display paths changed in each commit:\n"
+ " svn log -l 5 -v\n"
+ "\n"
+ " Show the log for bar.c as of revision 42:\n"
+ " svn log bar.c@42\n"
+ "\n"
+ " Show log messages and diffs for each commit to foo.c:\n"
+ " svn log --diff http://www.example.com/repo/project/foo.c\n"
+ " (Because the above command uses a full URL it does not require\n"
+ " a working copy.)\n"
+ "\n"
+ " Show log messages for the children foo.c and bar.c of the directory\n"
+ " '/trunk' as it appeared in revision 50, using the ^/ URL shortcut:\n"
+ " svn log ^/trunk@50 foo.c bar.c\n"
+ "\n"
+ " Show the log messages for any incoming changes to foo.c during the\n"
+ " next 'svn update':\n"
+ " svn log -r BASE:HEAD foo.c\n"
+ "\n"
+ " Show the log message for the revision in which /branches/foo\n"
+ " was created:\n"
+ " svn log --stop-on-copy --limit 1 -r0:HEAD ^/branches/foo\n"),
+ {'r', 'q', 'v', 'g', 'c', opt_targets, opt_stop_on_copy, opt_incremental,
+ opt_xml, 'l', opt_with_all_revprops, opt_with_no_revprops, opt_with_revprop,
+ opt_depth, opt_diff, opt_diff_cmd, opt_internal_diff, 'x', opt_search,
+ opt_search_and, },
+ {{opt_with_revprop, N_("retrieve revision property ARG")},
+ {'c', N_("the change made in revision ARG")}} },
+
+ { "merge", svn_cl__merge, {0}, N_
+ ( /* For this large section, let's keep it unindented for easier
+ * viewing/editing. It has been vim-treated with a textwidth=75 and 'gw'
+ * (with quotes and newlines removed). */
+"Merge changes into a working copy.\n"
+"usage: 1. merge SOURCE[@REV] [TARGET_WCPATH]\n"
+" (the 'automatic' merge)\n"
+" 2. merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n"
+" (the 'cherry-pick' merge)\n"
+" 3. merge SOURCE1[@REV1] SOURCE2[@REV2] [TARGET_WCPATH]\n"
+" (the '2-URL' merge)\n"
+"\n"
+" 1. This form, with one source path and no revision range, is called\n"
+" an 'automatic' merge:\n"
+"\n"
+" svn merge SOURCE[@REV] [TARGET_WCPATH]\n"
+"\n"
+" The automatic merge is used for the 'sync' and 'reintegrate' merges\n"
+" in the 'feature branch' pattern described below. It finds all the\n"
+" changes on the source branch that have not already been merged to the\n"
+" target branch, and merges them into the working copy. Merge tracking\n"
+" is used to know which changes have already been merged.\n"
+"\n"
+" SOURCE specifies the branch from where the changes will be pulled, and\n"
+" TARGET_WCPATH specifies a working copy of the target branch to which\n"
+" the changes will be applied. Normally SOURCE and TARGET_WCPATH should\n"
+" each correspond to the root of a branch. (If you want to merge only a\n"
+" subtree, then the subtree path must be included in both SOURCE and\n"
+" TARGET_WCPATH; this is discouraged, to avoid subtree mergeinfo.)\n"
+"\n"
+" SOURCE is usually a URL. The optional '@REV' specifies both the peg\n"
+" revision of the URL and the latest revision that will be considered\n"
+" for merging; if REV is not specified, the HEAD revision is assumed. If\n"
+" SOURCE is a working copy path, the corresponding URL of the path is\n"
+" used, and the default value of 'REV' is the base revision (usually the\n"
+" revision last updated to).\n"
+"\n"
+" TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n"
+" assumed. There are some special cases:\n"
+"\n"
+" - If SOURCE is a URL:\n"
+"\n"
+" - If the basename of the URL and the basename of '.' are the\n"
+" same, then the differences are applied to '.'. Otherwise,\n"
+" if a file with the same basename as that of the URL is found\n"
+" within '.', then the differences are applied to that file.\n"
+" In all other cases, the target defaults to '.'.\n"
+"\n"
+" - If SOURCE is a working copy path:\n"
+"\n"
+" - If the source is a file, then differences are applied to that\n"
+" file (useful for reverse-merging earlier changes). Otherwise,\n"
+" if the source is a directory, then the target defaults to '.'.\n"
+"\n"
+" In normal usage the working copy should be up to date, at a single\n"
+" revision, with no local modifications and no switched subtrees.\n"
+"\n"
+" - The 'Feature Branch' Merging Pattern -\n"
+"\n"
+" In this commonly used work flow, known also as the 'development\n"
+" branch' pattern, a developer creates a branch and commits a series of\n"
+" changes that implement a new feature. The developer periodically\n"
+" merges all the latest changes from the parent branch so as to keep the\n"
+" development branch up to date with those changes. When the feature is\n"
+" complete, the developer performs a merge from the feature branch to\n"
+" the parent branch to re-integrate the changes.\n"
+"\n"
+" parent --+----------o------o-o-------------o--\n"
+" \\ \\ \\ /\n"
+" \\ merge merge merge\n"
+" \\ \\ \\ /\n"
+" feature +--o-o-------o----o-o----o-------\n"
+"\n"
+" A merge from the parent branch to the feature branch is called a\n"
+" 'sync' or 'catch-up' merge, and a merge from the feature branch to the\n"
+" parent branch is called a 'reintegrate' merge.\n"
+"\n"
+" - Sync Merge Example -\n"
+" ............\n"
+" . .\n"
+" trunk --+------------L--------------R------\n"
+" \\ \\\n"
+" \\ |\n"
+" \\ v\n"
+" feature +------------------------o-----\n"
+" r100 r200\n"
+"\n"
+" Subversion will locate all the changes on 'trunk' that have not yet\n"
+" been merged into the 'feature' branch. In this case that is a single\n"
+" range, r100:200. In the diagram above, L marks the left side (trunk@100)\n"
+" and R marks the right side (trunk@200) of the merge source. The\n"
+" difference between L and R will be applied to the target working copy\n"
+" path. In this case, the working copy is a clean checkout of the entire\n"
+" 'feature' branch.\n"
+"\n"
+" To perform this sync merge, have a clean working copy of the feature\n"
+" branch and run the following command in its top-level directory:\n"
+"\n"
+" svn merge ^/trunk\n"
+"\n"
+" Note that the merge is now only in your local working copy and still\n"
+" needs to be committed to the repository so that it can be seen by\n"
+" others. You can review the changes and you may have to resolve\n"
+" conflicts before you commit the merge.\n"
+"\n"
+" - Reintegrate Merge Example -\n"
+"\n"
+" The feature branch was last synced with trunk up to revision X. So the\n"
+" difference between trunk@X and feature@HEAD contains the complete set\n"
+" of changes that implement the feature, and no other changes. These\n"
+" changes are applied to trunk.\n"
+"\n"
+" rW rX\n"
+" trunk ------+--------------------L------------------o\n"
+" \\ . ^\n"
+" \\ ............. /\n"
+" \\ . /\n"
+" feature +--------------------------------R\n"
+"\n"
+" In the diagram above, L marks the left side (trunk@X) and R marks the\n"
+" right side (feature@HEAD) of the merge. The difference between the\n"
+" left and right side is merged into trunk, the target.\n"
+"\n"
+" To perform the merge, have a clean working copy of trunk and run the\n"
+" following command in its top-level directory:\n"
+"\n"
+" svn merge ^/feature\n"
+"\n"
+" To prevent unnecessary merge conflicts, a reintegrate merge requires\n"
+" that TARGET_WCPATH is not a mixed-revision working copy, has no local\n"
+" modifications, and has no switched subtrees.\n"
+"\n"
+" A reintegrate merge also requires that the source branch is coherently\n"
+" synced with the target -- in the above example, this means that all\n"
+" revisions between the branch point W and the last merged revision X\n"
+" are merged to the feature branch, so that there are no unmerged\n"
+" revisions in-between.\n"
+"\n"
+"\n"
+" 2. This form is called a 'cherry-pick' merge:\n"
+"\n"
+" svn merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n"
+"\n"
+" A cherry-pick merge is used to merge specific revisions (or revision\n"
+" ranges) from one branch to another. By default, this uses merge\n"
+" tracking to automatically skip any revisions that have already been\n"
+" merged to the target; you can use the --ignore-ancestry option to\n"
+" disable such skipping.\n"
+"\n"
+" SOURCE is usually a URL. The optional '@REV' specifies only the peg\n"
+" revision of the URL and does not affect the merge range; if REV is not\n"
+" specified, the HEAD revision is assumed. If SOURCE is a working copy\n"
+" path, the corresponding URL of the path is used, and the default value\n"
+" of 'REV' is the base revision (usually the revision last updated to).\n"
+"\n"
+" TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n"
+" assumed. The special cases noted above in the 'automatic' merge form\n"
+" also apply here.\n"
+"\n"
+" The revision ranges to be merged are specified by the '-r' and/or '-c'\n"
+" options. '-r N:M' refers to the difference in the history of the\n"
+" source branch between revisions N and M. You can use '-c M' to merge\n"
+" single revisions: '-c M' is equivalent to '-r <M-1>:M'. Each such\n"
+" difference is applied to TARGET_WCPATH.\n"
+"\n"
+" If the mergeinfo in TARGET_WCPATH indicates that revisions within the\n"
+" range were already merged, changes made in those revisions are not\n"
+" merged again. If needed, the range is broken into multiple sub-ranges,\n"
+" and each sub-range is merged separately.\n"
+"\n"
+" A 'reverse range' can be used to undo changes. For example, when\n"
+" source and target refer to the same branch, a previously committed\n"
+" revision can be 'undone'. In a reverse range, N is greater than M in\n"
+" '-r N:M', or the '-c' option is used with a negative number: '-c -M'\n"
+" is equivalent to '-r M:<M-1>'. Undoing changes like this is also known\n"
+" as performing a 'reverse merge'.\n"
+"\n"
+" Multiple '-c' and/or '-r' options may be specified and mixing of\n"
+" forward and reverse ranges is allowed.\n"
+"\n"
+" - Cherry-pick Merge Example -\n"
+"\n"
+" A bug has been fixed on trunk in revision 50. This fix needs to\n"
+" be merged from trunk onto the release branch.\n"
+"\n"
+" 1.x-release +-----------------------o-----\n"
+" / ^\n"
+" / |\n"
+" / |\n"
+" trunk ------+--------------------------LR-----\n"
+" r50\n"
+"\n"
+" In the above diagram, L marks the left side (trunk@49) and R marks the\n"
+" right side (trunk@50) of the merge. The difference between the left\n"
+" and right side is applied to the target working copy path.\n"
+"\n"
+" Note that the difference between revision 49 and 50 is exactly those\n"
+" changes that were committed in revision 50, not including changes\n"
+" committed in revision 49.\n"
+"\n"
+" To perform the merge, have a clean working copy of the release branch\n"
+" and run the following command in its top-level directory; remember\n"
+" that the default target is '.':\n"
+"\n"
+" svn merge -c50 ^/trunk\n"
+"\n"
+" You can also cherry-pick several revisions and/or revision ranges:\n"
+"\n"
+" svn merge -c50,54,60 -r65:68 ^/trunk\n"
+"\n"
+"\n"
+" 3. This form is called a '2-URL merge':\n"
+"\n"
+" svn merge SOURCE1[@REV1] SOURCE2[@REV2] [TARGET_WCPATH]\n"
+"\n"
+" You should use this merge variant only if the other variants do not\n"
+" apply to your situation, as this variant can be quite complex to\n"
+" master.\n"
+"\n"
+" Two source URLs are specified, identifying two trees on the same\n"
+" branch or on different branches. The trees are compared and the\n"
+" difference from SOURCE1@REV1 to SOURCE2@REV2 is applied to the\n"
+" working copy of the target branch at TARGET_WCPATH. The target\n"
+" branch may be the same as one or both sources, or different again.\n"
+" The three branches involved can be completely unrelated.\n"
+"\n"
+" TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n"
+" assumed. The special cases noted above in the 'automatic' merge form\n"
+" also apply here.\n"
+"\n"
+" SOURCE1 and/or SOURCE2 can also be specified as a working copy path,\n"
+" in which case the merge source URL is derived from the working copy.\n"
+"\n"
+" - 2-URL Merge Example -\n"
+"\n"
+" Two features have been developed on separate branches called 'foo' and\n"
+" 'bar'. It has since become clear that 'bar' should be combined with\n"
+" the 'foo' branch for further development before reintegration.\n"
+"\n"
+" Although both feature branches originate from trunk, they are not\n"
+" directly related -- one is not a direct copy of the other. A 2-URL\n"
+" merge is necessary.\n"
+"\n"
+" The 'bar' branch has been synced with trunk up to revision 500.\n"
+" (If this revision number is not known, it can be located using the\n"
+" 'svn log' and/or 'svn mergeinfo' commands.)\n"
+" The difference between trunk@500 and bar@HEAD contains the complete\n"
+" set of changes related to feature 'bar', and no other changes. These\n"
+" changes are applied to the 'foo' branch.\n"
+"\n"
+" foo +-----------------------------------o\n"
+" / ^\n"
+" / /\n"
+" / r500 /\n"
+" trunk ------+------+-----------------L---------> /\n"
+" \\ . /\n"
+" \\ ............ /\n"
+" \\ . /\n"
+" bar +-----------------------------------R\n"
+"\n"
+" In the diagram above, L marks the left side (trunk@500) and R marks\n"
+" the right side (bar@HEAD) of the merge. The difference between the\n"
+" left and right side is applied to the target working copy path, in\n"
+" this case a working copy of the 'foo' branch.\n"
+"\n"
+" To perform the merge, have a clean working copy of the 'foo' branch\n"
+" and run the following command in its top-level directory:\n"
+"\n"
+" svn merge ^/trunk@500 ^/bar\n"
+"\n"
+" The exact changes applied by a 2-URL merge can be previewed with svn's\n"
+" diff command, which is a good idea to verify if you do not have the\n"
+" luxury of a clean working copy to merge to. In this case:\n"
+"\n"
+" svn diff ^/trunk@500 ^/bar@HEAD\n"
+"\n"
+"\n"
+" The following applies to all types of merges:\n"
+"\n"
+" To prevent unnecessary merge conflicts, svn merge requires that\n"
+" TARGET_WCPATH is not a mixed-revision working copy. Running 'svn update'\n"
+" before starting a merge ensures that all items in the working copy are\n"
+" based on the same revision.\n"
+"\n"
+" If possible, you should have no local modifications in the merge's target\n"
+" working copy prior to the merge, to keep things simpler. It will be\n"
+" easier to revert the merge and to understand the branch's history.\n"
+"\n"
+" Switched sub-paths should also be avoided during merging, as they may\n"
+" cause incomplete merges and create subtree mergeinfo.\n"
+"\n"
+" For each merged item a line will be printed with characters reporting the\n"
+" action taken. These characters have the following meaning:\n"
+"\n"
+" A Added\n"
+" D Deleted\n"
+" U Updated\n"
+" C Conflict\n"
+" G Merged\n"
+" E Existed\n"
+" R Replaced\n"
+"\n"
+" Characters in the first column report about the item itself.\n"
+" Characters in the second column report about properties of the item.\n"
+" A 'C' in the third column indicates a tree conflict, while a 'C' in\n"
+" the first and second columns indicate textual conflicts in files\n"
+" and in property values, respectively.\n"
+"\n"
+" - Merge Tracking -\n"
+"\n"
+" Subversion uses the svn:mergeinfo property to track merge history. This\n"
+" property is considered at the start of a merge to determine what to merge\n"
+" and it is updated at the conclusion of the merge to describe the merge\n"
+" that took place. Mergeinfo is used only if the two sources are on the\n"
+" same line of history -- if the first source is an ancestor of the second,\n"
+" or vice-versa (i.e. if one has originally been created by copying the\n"
+" other). This is verified and enforced when using sync merges and\n"
+" reintegrate merges.\n"
+"\n"
+" The --ignore-ancestry option prevents merge tracking and thus ignores\n"
+" mergeinfo, neither considering it nor recording it.\n"
+"\n"
+" - Merging from foreign repositories -\n"
+"\n"
+" Subversion does support merging from foreign repositories.\n"
+" While all merge source URLs must point to the same repository, the merge\n"
+" target working copy may come from a different repository than the source.\n"
+" However, there are some caveats. Most notably, copies made in the\n"
+" merge source will be transformed into plain additions in the merge\n"
+" target. Also, merge-tracking is not supported for merges from foreign\n"
+" repositories.\n"),
+ {'r', 'c', 'N', opt_depth, 'q', opt_force, opt_dry_run, opt_merge_cmd,
+ opt_record_only, 'x', opt_ignore_ancestry, opt_accept, opt_reintegrate,
+ opt_allow_mixed_revisions, 'v'} },
+
+ { "mergeinfo", svn_cl__mergeinfo, {0}, N_
+ ("Display merge-related information.\n"
+ "usage: 1. mergeinfo SOURCE[@REV] [TARGET[@REV]]\n"
+ " 2. mergeinfo --show-revs=WHICH SOURCE[@REV] [TARGET[@REV]]\n"
+ "\n"
+ " 1. Summarize the history of merging between SOURCE and TARGET. The graph\n"
+ " shows, from left to right:\n"
+ " the youngest common ancestor of the branches;\n"
+ " the latest full merge in either direction, and thus the common base\n"
+ " that will be used for the next automatic merge;\n"
+ " the repository path and revision number of the tip of each branch.\n"
+ "\n"
+ " 2. Print the revision numbers on SOURCE that have been merged to TARGET\n"
+ " (with --show-revs=merged), or that have not been merged to TARGET\n"
+ " (with --show-revs=eligible). Print only revisions in which there was\n"
+ " at least one change in SOURCE.\n"
+ "\n"
+ " If --revision (-r) is provided, filter the displayed information to\n"
+ " show only that which is associated with the revisions within the\n"
+ " specified range. Revision numbers, dates, and the 'HEAD' keyword are\n"
+ " valid range values.\n"
+ "\n"
+ " SOURCE and TARGET are the source and target branch URLs, respectively.\n"
+ " (If a WC path is given, the corresponding base URL is used.) The default\n"
+ " TARGET is the current working directory ('.'). REV specifies the revision\n"
+ " to be considered the tip of the branch; the default for SOURCE is HEAD,\n"
+ " and the default for TARGET is HEAD for a URL or BASE for a WC path.\n"
+ "\n"
+ " The depth can be 'empty' or 'infinity'; the default is 'empty'.\n"),
+ {'r', 'R', opt_depth, opt_show_revs} },
+
+ { "mkdir", svn_cl__mkdir, {0}, N_
+ ("Create a new directory under version control.\n"
+ "usage: 1. mkdir PATH...\n"
+ " 2. mkdir URL...\n"
+ "\n"
+ " Create version controlled directories.\n"
+ "\n"
+ " 1. Each directory specified by a working copy PATH is created locally\n"
+ " and scheduled for addition upon the next commit.\n"
+ "\n"
+ " 2. Each directory specified by a URL is created in the repository via\n"
+ " an immediate commit.\n"
+ "\n"
+ " In both cases, all the intermediate directories must already exist,\n"
+ " unless the --parents option is given.\n"),
+ {'q', opt_parents, SVN_CL__LOG_MSG_OPTIONS} },
+
+ { "move", svn_cl__move, {"mv", "rename", "ren"}, N_
+ ("Move (rename) an item in a working copy or repository.\n"
+ "usage: move SRC... DST\n"
+ "\n"
+ " SRC and DST can both be working copy (WC) paths or URLs:\n"
+ " WC -> WC: move an item in a working copy, as a local change to\n"
+ " be committed later (with or without further changes)\n"
+ " URL -> URL: move an item in the repository directly, immediately\n"
+ " creating a new revision in the repository\n"
+ " All the SRCs must be of the same type. When moving multiple sources,\n"
+ " they will be added as children of DST, which must be a directory.\n"
+ "\n"
+ " SRC and DST of WC -> WC moves must be committed in the same revision.\n"
+ " Furthermore, WC -> WC moves will refuse to move a mixed-revision subtree.\n"
+ " To avoid unnecessary conflicts, it is recommended to run 'svn update'\n"
+ " to update the subtree to a single revision before moving it.\n"
+ " The --allow-mixed-revisions option is provided for backward compatibility.\n"
+ "\n"
+ " The --revision option has no use and is deprecated.\n"),
+ {'r', 'q', opt_force, opt_parents, opt_allow_mixed_revisions,
+ SVN_CL__LOG_MSG_OPTIONS} },
+
+ { "patch", svn_cl__patch, {0}, N_
+ ("Apply a patch to a working copy.\n"
+ "usage: patch PATCHFILE [WCPATH]\n"
+ "\n"
+ " Apply a unidiff patch in PATCHFILE to the working copy WCPATH.\n"
+ " If WCPATH is omitted, '.' is assumed.\n"
+ "\n"
+ " A unidiff patch suitable for application to a working copy can be\n"
+ " produced with the 'svn diff' command or third-party diffing tools.\n"
+ " Any non-unidiff content of PATCHFILE is ignored, except for Subversion\n"
+ " property diffs as produced by 'svn diff'.\n"
+ "\n"
+ " Changes listed in the patch will either be applied or rejected.\n"
+ " If a change does not match at its exact line offset, it may be applied\n"
+ " earlier or later in the file if a match is found elsewhere for the\n"
+ " surrounding lines of context provided by the patch.\n"
+ " A change may also be applied with fuzz, which means that one\n"
+ " or more lines of context are ignored when matching the change.\n"
+ " If no matching context can be found for a change, the change conflicts\n"
+ " and will be written to a reject file with the extension .svnpatch.rej.\n"
+ "\n"
+ " For each patched file a line will be printed with characters reporting\n"
+ " the action taken. These characters have the following meaning:\n"
+ "\n"
+ " A Added\n"
+ " D Deleted\n"
+ " U Updated\n"
+ " C Conflict\n"
+ " G Merged (with local uncommitted changes)\n"
+ "\n"
+ " Changes applied with an offset or fuzz are reported on lines starting\n"
+ " with the '>' symbol. You should review such changes carefully.\n"
+ "\n"
+ " If the patch removes all content from a file, that file is scheduled\n"
+ " for deletion. If the patch creates a new file, that file is scheduled\n"
+ " for addition. Use 'svn revert' to undo deletions and additions you\n"
+ " do not agree with.\n"
+ "\n"
+ " Hint: If the patch file was created with Subversion, it will contain\n"
+ " the number of a revision N the patch will cleanly apply to\n"
+ " (look for lines like '--- foo/bar.txt (revision N)').\n"
+ " To avoid rejects, first update to the revision N using\n"
+ " 'svn update -r N', apply the patch, and then update back to the\n"
+ " HEAD revision. This way, conflicts can be resolved interactively.\n"
+ ),
+ {'q', opt_dry_run, opt_strip, opt_reverse_diff,
+ opt_ignore_whitespace} },
+
+ { "propdel", svn_cl__propdel, {"pdel", "pd"}, N_
+ ("Remove a property from files, dirs, or revisions.\n"
+ "usage: 1. propdel PROPNAME [PATH...]\n"
+ " 2. propdel PROPNAME --revprop -r REV [TARGET]\n"
+ "\n"
+ " 1. Removes versioned props in working copy.\n"
+ " 2. Removes unversioned remote prop on repos revision.\n"
+ " TARGET only determines which repository to access.\n"),
+ {'q', 'R', opt_depth, 'r', opt_revprop, opt_changelist} },
+
+ { "propedit", svn_cl__propedit, {"pedit", "pe"}, N_
+ ("Edit a property with an external editor.\n"
+ "usage: 1. propedit PROPNAME TARGET...\n"
+ " 2. propedit PROPNAME --revprop -r REV [TARGET]\n"
+ "\n"
+ " 1. Edits versioned prop in working copy or repository.\n"
+ " 2. Edits unversioned remote prop on repos revision.\n"
+ " TARGET only determines which repository to access.\n"
+ "\n"
+ " See 'svn help propset' for more on setting properties.\n"),
+ {'r', opt_revprop, SVN_CL__LOG_MSG_OPTIONS, opt_force} },
+
+ { "propget", svn_cl__propget, {"pget", "pg"}, N_
+ ("Print the value of a property on files, dirs, or revisions.\n"
+ "usage: 1. propget PROPNAME [TARGET[@REV]...]\n"
+ " 2. propget PROPNAME --revprop -r REV [TARGET]\n"
+ "\n"
+ " 1. Prints versioned props. If specified, REV determines in which\n"
+ " revision the target is first looked up.\n"
+ " 2. Prints unversioned remote prop on repos revision.\n"
+ " TARGET only determines which repository to access.\n"
+ "\n"
+ " With --verbose, the target path and the property name are printed on\n"
+ " separate lines before each value, like 'svn proplist --verbose'.\n"
+ " Otherwise, if there is more than one TARGET or a depth other than\n"
+ " 'empty', the target path is printed on the same line before each value.\n"
+ "\n"
+ " By default, an extra newline is printed after the property value so that\n"
+ " the output looks pretty. With a single TARGET and depth 'empty', you can\n"
+ " use the --strict option to disable this (useful when redirecting a binary\n"
+ " property value to a file, for example).\n"),
+ {'v', 'R', opt_depth, 'r', opt_revprop, opt_strict, opt_xml,
+ opt_changelist, opt_show_inherited_props },
+ {{'v', N_("print path, name and value on separate lines")},
+ {opt_strict, N_("don't print an extra newline")}} },
+
+ { "proplist", svn_cl__proplist, {"plist", "pl"}, N_
+ ("List all properties on files, dirs, or revisions.\n"
+ "usage: 1. proplist [TARGET[@REV]...]\n"
+ " 2. proplist --revprop -r REV [TARGET]\n"
+ "\n"
+ " 1. Lists versioned props. If specified, REV determines in which\n"
+ " revision the target is first looked up.\n"
+ " 2. Lists unversioned remote props on repos revision.\n"
+ " TARGET only determines which repository to access.\n"
+ "\n"
+ " With --verbose, the property values are printed as well, like 'svn propget\n"
+ " --verbose'. With --quiet, the paths are not printed.\n"),
+ {'v', 'R', opt_depth, 'r', 'q', opt_revprop, opt_xml, opt_changelist,
+ opt_show_inherited_props },
+ {{'v', N_("print path, name and value on separate lines")},
+ {'q', N_("don't print the path")}} },
+
+ { "propset", svn_cl__propset, {"pset", "ps"}, N_
+ ("Set the value of a property on files, dirs, or revisions.\n"
+ "usage: 1. propset PROPNAME PROPVAL PATH...\n"
+ " 2. propset PROPNAME --revprop -r REV PROPVAL [TARGET]\n"
+ "\n"
+ " 1. Changes a versioned file or directory property in a working copy.\n"
+ " 2. Changes an unversioned property on a repository revision.\n"
+ " (TARGET only determines which repository to access.)\n"
+ "\n"
+ " The value may be provided with the --file option instead of PROPVAL.\n"
+ "\n"
+ " Property names starting with 'svn:' are reserved. Subversion recognizes\n"
+ " the following special versioned properties on a file:\n"
+ " svn:keywords - Keywords to be expanded. Valid keywords are:\n"
+ " URL, HeadURL - The URL for the head version of the file.\n"
+ " Author, LastChangedBy - The last person to modify the file.\n"
+ " Date, LastChangedDate - The date/time the file was last modified.\n"
+ " Rev, Revision, - The last revision the file changed.\n"
+ " LastChangedRevision\n"
+ " Id - A compressed summary of the previous four.\n"
+ " Header - Similar to Id but includes the full URL.\n"
+ "\n"
+ " Custom keywords can be defined with a format string separated from\n"
+ " the keyword name with '='. Valid format substitutions are:\n"
+ " %a - The author of the revision given by %r.\n"
+ " %b - The basename of the URL of the file.\n"
+ " %d - Short format of the date of the revision given by %r.\n"
+ " %D - Long format of the date of the revision given by %r.\n"
+ " %P - The file's path, relative to the repository root.\n"
+ " %r - The number of the revision which last changed the file.\n"
+ " %R - The URL to the root of the repository.\n"
+ " %u - The URL of the file.\n"
+ " %_ - A space (keyword definitions cannot contain a literal space).\n"
+ " %% - A literal '%'.\n"
+ " %H - Equivalent to %P%_%r%_%d%_%a.\n"
+ " %I - Equivalent to %b%_%r%_%d%_%a.\n"
+ " Example custom keyword definition: MyKeyword=%r%_%a%_%P\n"
+ " Once a custom keyword has been defined for a file, it can be used\n"
+ " within the file like any other keyword: $MyKeyword$\n"
+ "\n"
+ " svn:executable - If present, make the file executable. Use\n"
+ " 'svn propdel svn:executable PATH...' to clear.\n"
+ " svn:eol-style - One of 'native', 'LF', 'CR', 'CRLF'.\n"
+ " svn:mime-type - The mimetype of the file. Used to determine\n"
+ " whether to merge the file, and how to serve it from Apache.\n"
+ " A mimetype beginning with 'text/' (or an absent mimetype) is\n"
+ " treated as text. Anything else is treated as binary.\n"
+ " svn:needs-lock - If present, indicates that the file should be locked\n"
+ " before it is modified. Makes the working copy file read-only\n"
+ " when it is not locked. Use 'svn propdel svn:needs-lock PATH...'\n"
+ " to clear.\n"
+ "\n"
+ " Subversion recognizes the following special versioned properties on a\n"
+ " directory:\n"
+ " svn:ignore - A list of file glob patterns to ignore, one per line.\n"
+ " svn:global-ignores - Like svn:ignore, but inheritable.\n"
+ " svn:externals - A list of module specifiers, one per line, in the\n"
+ " following format similar to the syntax of 'svn checkout':\n"
+ " [-r REV] URL[@PEG] LOCALPATH\n"
+ " Example:\n"
+ " http://example.com/repos/zig foo/bar\n"
+ " The LOCALPATH is relative to the directory having this property.\n"
+ " To pin the external to a known revision, specify the optional REV:\n"
+ " -r25 http://example.com/repos/zig foo/bar\n"
+ " To unambiguously identify an element at a path which may have been\n"
+ " subsequently deleted or renamed, specify the optional PEG revision:\n"
+ " -r25 http://example.com/repos/zig@42 foo/bar\n"
+ " The URL may be a full URL or a relative URL starting with one of:\n"
+ " ../ to the parent directory of the extracted external\n"
+ " ^/ to the repository root\n"
+ " / to the server root\n"
+ " // to the URL scheme\n"
+ " Use of the following format is discouraged but is supported for\n"
+ " interoperability with Subversion 1.4 and earlier clients:\n"
+ " LOCALPATH [-r PEG] URL\n"
+ " The ambiguous format 'relative_path relative_path' is taken as\n"
+ " 'relative_url relative_path' with peg revision support.\n"
+ " Lines starting with a '#' character are ignored.\n"),
+ {'F', opt_encoding, 'q', 'r', opt_targets, 'R', opt_depth, opt_revprop,
+ opt_force, opt_changelist },
+ {{'F', N_("read property value from file ARG")}} },
+
+ { "relocate", svn_cl__relocate, {0}, N_
+ ("Relocate the working copy to point to a different repository root URL.\n"
+ "usage: 1. relocate FROM-PREFIX TO-PREFIX [PATH...]\n"
+ " 2. relocate TO-URL [PATH]\n"
+ "\n"
+ " Rewrite working copy URL metadata to reflect a syntactic change only.\n"
+ " This is used when a repository's root URL changes (such as a scheme\n"
+ " or hostname change) but your working copy still reflects the same\n"
+ " directory within the same repository.\n"
+ "\n"
+ " 1. FROM-PREFIX and TO-PREFIX are initial substrings of the working\n"
+ " copy's current and new URLs, respectively. (You may specify the\n"
+ " complete old and new URLs if you wish.) Use 'svn info' to determine\n"
+ " the current working copy URL.\n"
+ "\n"
+ " 2. TO-URL is the (complete) new repository URL to use for PATH.\n"
+ "\n"
+ " Examples:\n"
+ " svn relocate http:// svn:// project1 project2\n"
+ " svn relocate http://www.example.com/repo/project \\\n"
+ " svn://svn.example.com/repo/project\n"),
+ {opt_ignore_externals} },
+
+ { "resolve", svn_cl__resolve, {0}, N_
+ ("Resolve conflicts on working copy files or directories.\n"
+ "usage: resolve [PATH...]\n"
+ "\n"
+ " By default, perform interactive conflict resolution on PATH.\n"
+ " In this mode, the command is recursive by default (depth 'infinity').\n"
+ "\n"
+ " The --accept=ARG option prevents interactive prompting and forces\n"
+ " conflicts on PATH to be resolved in the manner specified by ARG.\n"
+ " In this mode, the command is not recursive by default (depth 'empty').\n"),
+ {opt_targets, 'R', opt_depth, 'q', opt_accept},
+ {{opt_accept, N_("specify automatic conflict resolution source\n"
+ " "
+ "('base', 'working', 'mine-conflict',\n"
+ " "
+ "'theirs-conflict', 'mine-full', 'theirs-full')")}} },
+
+ { "resolved", svn_cl__resolved, {0}, N_
+ ("Remove 'conflicted' state on working copy files or directories.\n"
+ "usage: resolved PATH...\n"
+ "\n"
+ " Note: this subcommand does not semantically resolve conflicts or\n"
+ " remove conflict markers; it merely removes the conflict-related\n"
+ " artifact files and allows PATH to be committed again. It has been\n"
+ " deprecated in favor of running 'svn resolve --accept working'.\n"),
+ {opt_targets, 'R', opt_depth, 'q'} },
+
+ { "revert", svn_cl__revert, {0}, N_
+ ("Restore pristine working copy state (undo local changes).\n"
+ "usage: revert PATH...\n"
+ "\n"
+ " Revert changes in the working copy at or within PATH, and remove\n"
+ " conflict markers as well, if any.\n"
+ "\n"
+ " This subcommand does not revert already committed changes.\n"
+ " For information about undoing already committed changes, search\n"
+ " the output of 'svn help merge' for 'undo'.\n"),
+ {opt_targets, 'R', opt_depth, 'q', opt_changelist} },
+
+ { "status", svn_cl__status, {"stat", "st"}, N_
+ ("Print the status of working copy files and directories.\n"
+ "usage: status [PATH...]\n"
+ "\n"
+ " With no args, print only locally modified items (no network access).\n"
+ " With -q, print only summary information about locally modified items.\n"
+ " With -u, add working revision and server out-of-date information.\n"
+ " With -v, print full revision information on every item.\n"
+ "\n"
+ " The first seven columns in the output are each one character wide:\n"
+ " First column: Says if item was added, deleted, or otherwise changed\n"
+ " ' ' no modifications\n"
+ " 'A' Added\n"
+ " 'C' Conflicted\n"
+ " 'D' Deleted\n"
+ " 'I' Ignored\n"
+ " 'M' Modified\n"
+ " 'R' Replaced\n"
+ " 'X' an unversioned directory created by an externals definition\n"
+ " '?' item is not under version control\n"
+ " '!' item is missing (removed by non-svn command) or incomplete\n"
+ " '~' versioned item obstructed by some item of a different kind\n"
+ " Second column: Modifications of a file's or directory's properties\n"
+ " ' ' no modifications\n"
+ " 'C' Conflicted\n"
+ " 'M' Modified\n"
+ " Third column: Whether the working copy directory is locked\n"
+ " ' ' not locked\n"
+ " 'L' locked\n"
+ " Fourth column: Scheduled commit will contain addition-with-history\n"
+ " ' ' no history scheduled with commit\n"
+ " '+' history scheduled with commit\n"
+ " Fifth column: Whether the item is switched or a file external\n"
+ " ' ' normal\n"
+ " 'S' the item has a Switched URL relative to the parent\n"
+ " 'X' a versioned file created by an eXternals definition\n"
+ " Sixth column: Repository lock token\n"
+ " (without -u)\n"
+ " ' ' no lock token\n"
+ " 'K' lock token present\n"
+ " (with -u)\n"
+ " ' ' not locked in repository, no lock token\n"
+ " 'K' locked in repository, lock toKen present\n"
+ " 'O' locked in repository, lock token in some Other working copy\n"
+ " 'T' locked in repository, lock token present but sTolen\n"
+ " 'B' not locked in repository, lock token present but Broken\n"
+ " Seventh column: Whether the item is the victim of a tree conflict\n"
+ " ' ' normal\n"
+ " 'C' tree-Conflicted\n"
+ " If the item is a tree conflict victim, an additional line is printed\n"
+ " after the item's status line, explaining the nature of the conflict.\n"
+ "\n"
+ " The out-of-date information appears in the ninth column (with -u):\n"
+ " '*' a newer revision exists on the server\n"
+ " ' ' the working copy is up to date\n"
+ "\n"
+ " Remaining fields are variable width and delimited by spaces:\n"
+ " The working revision (with -u or -v; '-' if the item is copied)\n"
+ " The last committed revision and last committed author (with -v)\n"
+ " The working copy path is always the final field, so it can\n"
+ " include spaces.\n"
+ "\n"
+ " The presence of a question mark ('?') where a working revision, last\n"
+ " committed revision, or last committed author was expected indicates\n"
+ " that the information is unknown or irrelevant given the state of the\n"
+ " item (for example, when the item is the result of a copy operation).\n"
+ " The question mark serves as a visual placeholder to facilitate parsing.\n"
+ "\n"
+ " Example output:\n"
+ " svn status wc\n"
+ " M wc/bar.c\n"
+ " A + wc/qax.c\n"
+ "\n"
+ " svn status -u wc\n"
+ " M 965 wc/bar.c\n"
+ " * 965 wc/foo.c\n"
+ " A + - wc/qax.c\n"
+ " Status against revision: 981\n"
+ "\n"
+ " svn status --show-updates --verbose wc\n"
+ " M 965 938 kfogel wc/bar.c\n"
+ " * 965 922 sussman wc/foo.c\n"
+ " A + - 687 joe wc/qax.c\n"
+ " 965 687 joe wc/zig.c\n"
+ " Status against revision: 981\n"
+ "\n"
+ " svn status\n"
+ " M wc/bar.c\n"
+ " ! C wc/qaz.c\n"
+ " > local missing, incoming edit upon update\n"
+ " D wc/qax.c\n"),
+ { 'u', 'v', 'N', opt_depth, 'q', opt_no_ignore, opt_incremental, opt_xml,
+ opt_ignore_externals, opt_changelist},
+ {{'q', N_("don't print unversioned items")}} },
+
+ { "switch", svn_cl__switch, {"sw"}, N_
+ ("Update the working copy to a different URL within the same repository.\n"
+ "usage: 1. switch URL[@PEGREV] [PATH]\n"
+ " 2. switch --relocate FROM-PREFIX TO-PREFIX [PATH...]\n"
+ "\n"
+ " 1. Update the working copy to mirror a new URL within the repository.\n"
+ " This behavior is similar to 'svn update', and is the way to\n"
+ " move a working copy to a branch or tag within the same repository.\n"
+ " If specified, PEGREV determines in which revision the target is first\n"
+ " looked up.\n"
+ "\n"
+ " If --force is used, unversioned obstructing paths in the working\n"
+ " copy do not automatically cause a failure if the switch attempts to\n"
+ " add the same path. If the obstructing path is the same type (file\n"
+ " or directory) as the corresponding path in the repository it becomes\n"
+ " versioned but its contents are left 'as-is' in the working copy.\n"
+ " This means that an obstructing directory's unversioned children may\n"
+ " also obstruct and become versioned. For files, any content differences\n"
+ " between the obstruction and the repository are treated like a local\n"
+ " modification to the working copy. All properties from the repository\n"
+ " are applied to the obstructing path.\n"
+ "\n"
+ " Use the --set-depth option to set a new working copy depth on the\n"
+ " targets of this operation.\n"
+ "\n"
+ " By default, Subversion will refuse to switch a working copy path to\n"
+ " a new URL with which it shares no common version control ancestry.\n"
+ " Use the '--ignore-ancestry' option to override this sanity check.\n"
+ "\n"
+ " 2. The '--relocate' option is deprecated. This syntax is equivalent to\n"
+ " 'svn relocate FROM-PREFIX TO-PREFIX [PATH]'.\n"
+ "\n"
+ " See also 'svn help update' for a list of possible characters\n"
+ " reporting the action taken.\n"
+ "\n"
+ " Examples:\n"
+ " svn switch ^/branches/1.x-release\n"
+ " svn switch --relocate http:// svn://\n"
+ " svn switch --relocate http://www.example.com/repo/project \\\n"
+ " svn://svn.example.com/repo/project\n"),
+ { 'r', 'N', opt_depth, opt_set_depth, 'q', opt_merge_cmd, opt_relocate,
+ opt_ignore_externals, opt_ignore_ancestry, opt_force, opt_accept},
+ {{opt_ignore_ancestry,
+ N_("allow switching to a node with no common ancestor")}}
+ },
+
+ { "unlock", svn_cl__unlock, {0}, N_
+ ("Unlock working copy paths or URLs.\n"
+ "usage: unlock TARGET...\n"
+ "\n"
+ " Use --force to break the lock.\n"),
+ { opt_targets, opt_force } },
+
+ { "update", svn_cl__update, {"up"}, N_
+ ("Bring changes from the repository into the working copy.\n"
+ "usage: update [PATH...]\n"
+ "\n"
+ " If no revision is given, bring working copy up-to-date with HEAD rev.\n"
+ " Else synchronize working copy to revision given by -r.\n"
+ "\n"
+ " For each updated item a line will be printed with characters reporting\n"
+ " the action taken. These characters have the following meaning:\n"
+ "\n"
+ " A Added\n"
+ " D Deleted\n"
+ " U Updated\n"
+ " C Conflict\n"
+ " G Merged\n"
+ " E Existed\n"
+ " R Replaced\n"
+ "\n"
+ " Characters in the first column report about the item itself.\n"
+ " Characters in the second column report about properties of the item.\n"
+ " A 'B' in the third column signifies that the lock for the file has\n"
+ " been broken or stolen.\n"
+ " A 'C' in the fourth column indicates a tree conflict, while a 'C' in\n"
+ " the first and second columns indicate textual conflicts in files\n"
+ " and in property values, respectively.\n"
+ "\n"
+ " If --force is used, unversioned obstructing paths in the working\n"
+ " copy do not automatically cause a failure if the update attempts to\n"
+ " add the same path. If the obstructing path is the same type (file\n"
+ " or directory) as the corresponding path in the repository it becomes\n"
+ " versioned but its contents are left 'as-is' in the working copy.\n"
+ " This means that an obstructing directory's unversioned children may\n"
+ " also obstruct and become versioned. For files, any content differences\n"
+ " between the obstruction and the repository are treated like a local\n"
+ " modification to the working copy. All properties from the repository\n"
+ " are applied to the obstructing path. Obstructing paths are reported\n"
+ " in the first column with code 'E'.\n"
+ "\n"
+ " If the specified update target is missing from the working copy but its\n"
+ " immediate parent directory is present, checkout the target into its\n"
+ " parent directory at the specified depth. If --parents is specified,\n"
+ " create any missing parent directories of the target by checking them\n"
+ " out, too, at depth=empty.\n"
+ "\n"
+ " Use the --set-depth option to set a new working copy depth on the\n"
+ " targets of this operation.\n"),
+ {'r', 'N', opt_depth, opt_set_depth, 'q', opt_merge_cmd, opt_force,
+ opt_ignore_externals, opt_changelist, opt_editor_cmd, opt_accept,
+ opt_parents} },
+
+ { "upgrade", svn_cl__upgrade, {0}, N_
+ ("Upgrade the metadata storage format for a working copy.\n"
+ "usage: upgrade [WCPATH...]\n"
+ "\n"
+ " Local modifications are preserved.\n"),
+ { 'q' } },
+
+ { NULL, NULL, {0}, NULL, {0} }
+};
+
+
+/* Version compatibility check */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_client", svn_client_version },
+ { "svn_wc", svn_wc_version },
+ { "svn_ra", svn_ra_version },
+ { "svn_delta", svn_delta_version },
+ { "svn_diff", svn_diff_version },
+ { NULL, NULL }
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+
+/* A flag to see if we've been cancelled by the client or not. */
+static volatile sig_atomic_t cancelled = FALSE;
+
+/* A signal handler to support cancellation. */
+static void
+signal_handler(int signum)
+{
+ apr_signal(signum, SIG_IGN);
+ cancelled = TRUE;
+}
+
+/* Our cancellation callback. */
+svn_error_t *
+svn_cl__check_cancel(void *baton)
+{
+ if (cancelled)
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
+ else
+ return SVN_NO_ERROR;
+}
+
+/* Add a --search argument to OPT_STATE.
+ * These options start a new search pattern group. */
+static void
+add_search_pattern_group(svn_cl__opt_state_t *opt_state,
+ const char *pattern,
+ apr_pool_t *result_pool)
+{
+ apr_array_header_t *group = NULL;
+
+ if (opt_state->search_patterns == NULL)
+ opt_state->search_patterns = apr_array_make(result_pool, 1,
+ sizeof(apr_array_header_t *));
+
+ group = apr_array_make(result_pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(group, const char *) = pattern;
+ APR_ARRAY_PUSH(opt_state->search_patterns, apr_array_header_t *) = group;
+}
+
+/* Add a --search-and argument to OPT_STATE.
+ * These patterns are added to an existing pattern group, if any. */
+static void
+add_search_pattern_to_latest_group(svn_cl__opt_state_t *opt_state,
+ const char *pattern,
+ apr_pool_t *result_pool)
+{
+ apr_array_header_t *group;
+
+ if (opt_state->search_patterns == NULL)
+ {
+ add_search_pattern_group(opt_state, pattern, result_pool);
+ return;
+ }
+
+ group = APR_ARRAY_IDX(opt_state->search_patterns,
+ opt_state->search_patterns->nelts - 1,
+ apr_array_header_t *);
+ APR_ARRAY_PUSH(group, const char *) = pattern;
+}
+
+
+/*** Main. ***/
+
+/* Report and clear the error ERR, and return EXIT_FAILURE. Suppress the
+ * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR. */
+#define EXIT_ERROR(err) \
+ svn_cmdline_handle_exit_error(err, NULL, "svn: ")
+
+/* A redefinition of the public SVN_INT_ERR macro, that suppresses the
+ * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR. */
+#undef SVN_INT_ERR
+#define SVN_INT_ERR(expr) \
+ do { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ return EXIT_ERROR(svn_err__temp); \
+ } while (0)
+
+static int
+sub_main(int argc, const char *argv[], apr_pool_t *pool)
+{
+ svn_error_t *err;
+ int opt_id;
+ apr_getopt_t *os;
+ svn_cl__opt_state_t opt_state = { 0, { 0 } };
+ svn_client_ctx_t *ctx;
+ apr_array_header_t *received_opts;
+ int i;
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+ const char *dash_m_arg = NULL, *dash_F_arg = NULL;
+ svn_cl__cmd_baton_t command_baton;
+ svn_auth_baton_t *ab;
+ svn_config_t *cfg_config;
+ svn_boolean_t descend = TRUE;
+ svn_boolean_t interactive_conflicts = FALSE;
+ svn_boolean_t force_interactive = FALSE;
+ svn_cl__conflict_stats_t *conflict_stats
+ = svn_cl__conflict_stats_create(pool);
+ svn_boolean_t use_notifier = TRUE;
+ svn_boolean_t reading_file_from_stdin = FALSE;
+ apr_hash_t *changelists;
+ apr_hash_t *cfg_hash;
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ /* Check library versions */
+ SVN_INT_ERR(check_lib_versions());
+
+#if defined(WIN32) || defined(__CYGWIN__)
+ /* Set the working copy administrative directory name. */
+ if (getenv("SVN_ASP_DOT_NET_HACK"))
+ {
+ SVN_INT_ERR(svn_wc_set_adm_dir("_svn", pool));
+ }
+#endif
+
+ /* Initialize the RA library. */
+ SVN_INT_ERR(svn_ra_initialize(pool));
+
+ /* Init our changelists hash. */
+ changelists = apr_hash_make(pool);
+
+ /* Begin processing arguments. */
+ opt_state.start_revision.kind = svn_opt_revision_unspecified;
+ opt_state.end_revision.kind = svn_opt_revision_unspecified;
+ opt_state.revision_ranges =
+ apr_array_make(pool, 0, sizeof(svn_opt_revision_range_t *));
+ opt_state.depth = svn_depth_unknown;
+ opt_state.set_depth = svn_depth_unknown;
+ opt_state.accept_which = svn_cl__accept_unspecified;
+ opt_state.show_revs = svn_cl__show_revs_invalid;
+
+ /* No args? Show usage. */
+ if (argc <= 1)
+ {
+ SVN_INT_ERR(svn_cl__help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+
+ /* Else, parse options. */
+ SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
+
+ os->interleave = 1;
+ while (1)
+ {
+ const char *opt_arg;
+ const char *utf8_opt_arg;
+
+ /* Parse the next option. */
+ apr_status_t apr_err = apr_getopt_long(os, svn_cl__options, &opt_id,
+ &opt_arg);
+ if (APR_STATUS_IS_EOF(apr_err))
+ break;
+ else if (apr_err)
+ {
+ SVN_INT_ERR(svn_cl__help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+
+ /* Stash the option code in an array before parsing it. */
+ APR_ARRAY_PUSH(received_opts, int) = opt_id;
+
+ switch (opt_id) {
+ case 'l':
+ {
+ err = svn_cstring_atoi(&opt_state.limit, opt_arg);
+ if (err)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err,
+ _("Non-numeric limit argument given"));
+ return EXIT_ERROR(err);
+ }
+ if (opt_state.limit <= 0)
+ {
+ err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Argument to --limit must be positive"));
+ return EXIT_ERROR(err);
+ }
+ }
+ break;
+ case 'm':
+ /* Note that there's no way here to detect if the log message
+ contains a zero byte -- if it does, then opt_arg will just
+ be shorter than the user intended. Oh well. */
+ opt_state.message = apr_pstrdup(pool, opt_arg);
+ dash_m_arg = opt_arg;
+ break;
+ case 'c':
+ {
+ apr_array_header_t *change_revs =
+ svn_cstring_split(opt_arg, ", \n\r\t\v", TRUE, pool);
+
+ if (opt_state.old_target)
+ {
+ err = svn_error_create
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Can't specify -c with --old"));
+ return EXIT_ERROR(err);
+ }
+
+ for (i = 0; i < change_revs->nelts; i++)
+ {
+ char *end;
+ svn_revnum_t changeno, changeno_end;
+ const char *change_str =
+ APR_ARRAY_IDX(change_revs, i, const char *);
+ const char *s = change_str;
+ svn_boolean_t is_negative;
+
+ /* Check for a leading minus to allow "-c -r42".
+ * The is_negative flag is used to handle "-c -42" and "-c -r42".
+ * The "-c r-42" case is handled by strtol() returning a
+ * negative number. */
+ is_negative = (*s == '-');
+ if (is_negative)
+ s++;
+
+ /* Allow any number of 'r's to prefix a revision number. */
+ while (*s == 'r')
+ s++;
+ changeno = changeno_end = strtol(s, &end, 10);
+ if (end != s && *end == '-')
+ {
+ if (changeno < 0 || is_negative)
+ {
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Negative number in range (%s)"
+ " not supported with -c"),
+ change_str);
+ return EXIT_ERROR(err);
+ }
+ s = end + 1;
+ while (*s == 'r')
+ s++;
+ changeno_end = strtol(s, &end, 10);
+ }
+ if (end == change_str || *end != '\0')
+ {
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Non-numeric change argument (%s) "
+ "given to -c"), change_str);
+ return EXIT_ERROR(err);
+ }
+
+ if (changeno == 0)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("There is no change 0"));
+ return EXIT_ERROR(err);
+ }
+
+ if (is_negative)
+ changeno = -changeno;
+
+ /* Figure out the range:
+ -c N -> -r N-1:N
+ -c -N -> -r N:N-1
+ -c M-N -> -r M-1:N for M < N
+ -c M-N -> -r M:N-1 for M > N
+ -c -M-N -> error (too confusing/no valid use case)
+ */
+ if (changeno > 0)
+ {
+ if (changeno <= changeno_end)
+ changeno--;
+ else
+ changeno_end--;
+ }
+ else
+ {
+ changeno = -changeno;
+ changeno_end = changeno - 1;
+ }
+
+ opt_state.used_change_arg = TRUE;
+ APR_ARRAY_PUSH(opt_state.revision_ranges,
+ svn_opt_revision_range_t *)
+ = svn_opt__revision_range_from_revnums(changeno, changeno_end,
+ pool);
+ }
+ }
+ break;
+ case 'r':
+ opt_state.used_revision_arg = TRUE;
+ if (svn_opt_parse_revision_to_range(opt_state.revision_ranges,
+ opt_arg, pool) != 0)
+ {
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ err = svn_error_createf
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error in revision argument '%s'"),
+ utf8_opt_arg);
+ return EXIT_ERROR(err);
+ }
+ break;
+ case 'v':
+ opt_state.verbose = TRUE;
+ break;
+ case 'u':
+ opt_state.update = TRUE;
+ break;
+ case 'h':
+ case '?':
+ opt_state.help = TRUE;
+ break;
+ case 'q':
+ opt_state.quiet = TRUE;
+ break;
+ case opt_incremental:
+ opt_state.incremental = TRUE;
+ break;
+ case 'F':
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_stringbuf_from_file2(&(opt_state.filedata),
+ utf8_opt_arg, pool));
+ reading_file_from_stdin = (strcmp(utf8_opt_arg, "-") == 0);
+ dash_F_arg = opt_arg;
+ break;
+ case opt_targets:
+ {
+ svn_stringbuf_t *buffer, *buffer_utf8;
+
+ /* We need to convert to UTF-8 now, even before we divide
+ the targets into an array, because otherwise we wouldn't
+ know what delimiter to use for svn_cstring_split(). */
+
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_stringbuf_from_file2(&buffer, utf8_opt_arg, pool));
+ SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
+ opt_state.targets = svn_cstring_split(buffer_utf8->data, "\n\r",
+ TRUE, pool);
+ }
+ break;
+ case opt_force:
+ opt_state.force = TRUE;
+ break;
+ case opt_force_log:
+ opt_state.force_log = TRUE;
+ break;
+ case opt_dry_run:
+ opt_state.dry_run = TRUE;
+ break;
+ case opt_revprop:
+ opt_state.revprop = TRUE;
+ break;
+ case 'R':
+ opt_state.depth = svn_depth_infinity;
+ break;
+ case 'N':
+ descend = FALSE;
+ break;
+ case opt_depth:
+ err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
+ if (err)
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
+ _("Error converting depth "
+ "from locale to UTF-8")));
+ opt_state.depth = svn_depth_from_word(utf8_opt_arg);
+ if (opt_state.depth == svn_depth_unknown
+ || opt_state.depth == svn_depth_exclude)
+ {
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a valid depth; try "
+ "'empty', 'files', 'immediates', "
+ "or 'infinity'"),
+ utf8_opt_arg));
+ }
+ break;
+ case opt_set_depth:
+ err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
+ if (err)
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
+ _("Error converting depth "
+ "from locale to UTF-8")));
+ opt_state.set_depth = svn_depth_from_word(utf8_opt_arg);
+ /* svn_depth_exclude is okay for --set-depth. */
+ if (opt_state.set_depth == svn_depth_unknown)
+ {
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a valid depth; try "
+ "'exclude', 'empty', 'files', "
+ "'immediates', or 'infinity'"),
+ utf8_opt_arg));
+ }
+ break;
+ case opt_version:
+ opt_state.version = TRUE;
+ break;
+ case opt_auth_username:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_username,
+ opt_arg, pool));
+ break;
+ case opt_auth_password:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_password,
+ opt_arg, pool));
+ break;
+ case opt_encoding:
+ opt_state.encoding = apr_pstrdup(pool, opt_arg);
+ break;
+ case opt_xml:
+ opt_state.xml = TRUE;
+ break;
+ case opt_stop_on_copy:
+ opt_state.stop_on_copy = TRUE;
+ break;
+ case opt_strict:
+ opt_state.strict = TRUE;
+ break;
+ case opt_no_ignore:
+ opt_state.no_ignore = TRUE;
+ break;
+ case opt_no_auth_cache:
+ opt_state.no_auth_cache = TRUE;
+ break;
+ case opt_non_interactive:
+ opt_state.non_interactive = TRUE;
+ break;
+ case opt_force_interactive:
+ force_interactive = TRUE;
+ break;
+ case opt_trust_server_cert:
+ opt_state.trust_server_cert = TRUE;
+ break;
+ case opt_no_diff_added:
+ opt_state.diff.no_diff_added = TRUE;
+ break;
+ case opt_no_diff_deleted:
+ opt_state.diff.no_diff_deleted = TRUE;
+ break;
+ case opt_ignore_properties:
+ opt_state.diff.ignore_properties = TRUE;
+ break;
+ case opt_show_copies_as_adds:
+ opt_state.diff.show_copies_as_adds = TRUE;
+ break;
+ case opt_notice_ancestry:
+ opt_state.diff.notice_ancestry = TRUE;
+ break;
+ case opt_ignore_ancestry:
+ opt_state.ignore_ancestry = TRUE;
+ break;
+ case opt_ignore_externals:
+ opt_state.ignore_externals = TRUE;
+ break;
+ case opt_relocate:
+ opt_state.relocate = TRUE;
+ break;
+ case 'x':
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.extensions,
+ opt_arg, pool));
+ break;
+ case opt_diff_cmd:
+ opt_state.diff.diff_cmd = apr_pstrdup(pool, opt_arg);
+ break;
+ case opt_merge_cmd:
+ opt_state.merge_cmd = apr_pstrdup(pool, opt_arg);
+ break;
+ case opt_record_only:
+ opt_state.record_only = TRUE;
+ break;
+ case opt_editor_cmd:
+ opt_state.editor_cmd = apr_pstrdup(pool, opt_arg);
+ break;
+ case opt_old_cmd:
+ if (opt_state.used_change_arg)
+ {
+ err = svn_error_create
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Can't specify -c with --old"));
+ return EXIT_ERROR(err);
+ }
+ opt_state.old_target = apr_pstrdup(pool, opt_arg);
+ break;
+ case opt_new_cmd:
+ opt_state.new_target = apr_pstrdup(pool, opt_arg);
+ break;
+ case opt_config_dir:
+ {
+ const char *path_utf8;
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool));
+ opt_state.config_dir = svn_dirent_internal_style(path_utf8, pool);
+ }
+ break;
+ case opt_config_options:
+ if (!opt_state.config_options)
+ opt_state.config_options =
+ apr_array_make(pool, 1,
+ sizeof(svn_cmdline__config_argument_t*));
+
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_cmdline__parse_config_option(opt_state.config_options,
+ opt_arg, pool));
+ break;
+ case opt_autoprops:
+ opt_state.autoprops = TRUE;
+ break;
+ case opt_no_autoprops:
+ opt_state.no_autoprops = TRUE;
+ break;
+ case opt_native_eol:
+ if ( !strcmp("LF", opt_arg) || !strcmp("CR", opt_arg) ||
+ !strcmp("CRLF", opt_arg))
+ opt_state.native_eol = apr_pstrdup(pool, opt_arg);
+ else
+ {
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ err = svn_error_createf
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error in native-eol argument '%s'"),
+ utf8_opt_arg);
+ return EXIT_ERROR(err);
+ }
+ break;
+ case opt_no_unlock:
+ opt_state.no_unlock = TRUE;
+ break;
+ case opt_summarize:
+ opt_state.diff.summarize = TRUE;
+ break;
+ case opt_remove:
+ opt_state.remove = TRUE;
+ break;
+ case opt_changelist:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ opt_state.changelist = utf8_opt_arg;
+ if (opt_state.changelist[0] == '\0')
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Changelist names must not be empty"));
+ return EXIT_ERROR(err);
+ }
+ svn_hash_sets(changelists, opt_state.changelist, (void *)1);
+ break;
+ case opt_keep_changelists:
+ opt_state.keep_changelists = TRUE;
+ break;
+ case opt_keep_local:
+ opt_state.keep_local = TRUE;
+ break;
+ case opt_with_all_revprops:
+ /* If --with-all-revprops is specified along with one or more
+ * --with-revprops options, --with-all-revprops takes precedence. */
+ opt_state.all_revprops = TRUE;
+ break;
+ case opt_with_no_revprops:
+ opt_state.no_revprops = TRUE;
+ break;
+ case opt_with_revprop:
+ SVN_INT_ERR(svn_opt_parse_revprop(&opt_state.revprop_table,
+ opt_arg, pool));
+ break;
+ case opt_parents:
+ opt_state.parents = TRUE;
+ break;
+ case 'g':
+ opt_state.use_merge_history = TRUE;
+ break;
+ case opt_accept:
+ opt_state.accept_which = svn_cl__accept_from_word(opt_arg);
+ if (opt_state.accept_which == svn_cl__accept_invalid)
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a valid --accept value"),
+ opt_arg));
+ break;
+ case opt_show_revs:
+ opt_state.show_revs = svn_cl__show_revs_from_word(opt_arg);
+ if (opt_state.show_revs == svn_cl__show_revs_invalid)
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a valid --show-revs value"),
+ opt_arg));
+ break;
+ case opt_reintegrate:
+ opt_state.reintegrate = TRUE;
+ break;
+ case opt_strip:
+ {
+ err = svn_cstring_atoi(&opt_state.strip, opt_arg);
+ if (err)
+ {
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
+ _("Invalid strip count '%s'"), opt_arg);
+ return EXIT_ERROR(err);
+ }
+ if (opt_state.strip < 0)
+ {
+ err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Argument to --strip must be positive"));
+ return EXIT_ERROR(err);
+ }
+ }
+ break;
+ case opt_ignore_keywords:
+ opt_state.ignore_keywords = TRUE;
+ break;
+ case opt_reverse_diff:
+ opt_state.reverse_diff = TRUE;
+ break;
+ case opt_ignore_whitespace:
+ opt_state.ignore_whitespace = TRUE;
+ break;
+ case opt_diff:
+ opt_state.show_diff = TRUE;
+ break;
+ case opt_internal_diff:
+ opt_state.diff.internal_diff = TRUE;
+ break;
+ case opt_patch_compatible:
+ opt_state.diff.patch_compatible = TRUE;
+ break;
+ case opt_use_git_diff_format:
+ opt_state.diff.use_git_diff_format = TRUE;
+ break;
+ case opt_allow_mixed_revisions:
+ opt_state.allow_mixed_rev = TRUE;
+ break;
+ case opt_include_externals:
+ opt_state.include_externals = TRUE;
+ break;
+ case opt_show_inherited_props:
+ opt_state.show_inherited_props = TRUE;
+ break;
+ case opt_properties_only:
+ opt_state.diff.properties_only = TRUE;
+ break;
+ case opt_search:
+ add_search_pattern_group(&opt_state, opt_arg, pool);
+ break;
+ case opt_search_and:
+ add_search_pattern_to_latest_group(&opt_state, opt_arg, pool);
+ default:
+ /* Hmmm. Perhaps this would be a good place to squirrel away
+ opts that commands like svn diff might need. Hmmm indeed. */
+ break;
+ }
+ }
+
+ /* The --non-interactive and --force-interactive options are mutually
+ * exclusive. */
+ if (opt_state.non_interactive && force_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--non-interactive and --force-interactive "
+ "are mutually exclusive"));
+ return EXIT_ERROR(err);
+ }
+ else
+ opt_state.non_interactive = !svn_cmdline__be_interactive(
+ opt_state.non_interactive,
+ force_interactive);
+
+ /* Turn our hash of changelists into an array of unique ones. */
+ SVN_INT_ERR(svn_hash_keys(&(opt_state.changelists), changelists, pool));
+
+ /* ### This really belongs in libsvn_client. The trouble is,
+ there's no one place there to run it from, no
+ svn_client_init(). We'd have to add it to all the public
+ functions that a client might call. It's unmaintainable to do
+ initialization from within libsvn_client itself, but it seems
+ burdensome to demand that all clients call svn_client_init()
+ before calling any other libsvn_client function... On the other
+ hand, the alternative is effectively to demand that they call
+ svn_config_ensure() instead, so maybe we should have a generic
+ init function anyway. Thoughts? */
+ SVN_INT_ERR(svn_config_ensure(opt_state.config_dir, pool));
+
+ /* If the user asked for help, then the rest of the arguments are
+ the names of subcommands to get help on (if any), or else they're
+ just typos/mistakes. Whatever the case, the subcommand to
+ actually run is svn_cl__help(). */
+ if (opt_state.help)
+ subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table, "help");
+
+ /* If we're not running the `help' subcommand, then look for a
+ subcommand in the first argument. */
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_state.version)
+ {
+ /* Use the "help" subcommand to handle the "--version" option. */
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+ { "--version", svn_cl__help, {0}, "",
+ {opt_version, /* must accept its own option */
+ 'q', /* brief output */
+ 'v', /* verbose output */
+ opt_config_dir /* all commands accept this */
+ } };
+
+ subcommand = &pseudo_cmd;
+ }
+ else
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Subcommand argument required\n")));
+ svn_error_clear(svn_cl__help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ const char *first_arg = os->argv[os->ind++];
+ subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table,
+ first_arg);
+ if (subcommand == NULL)
+ {
+ const char *first_arg_utf8;
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
+ first_arg, pool));
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Unknown subcommand: '%s'\n"),
+ first_arg_utf8));
+ svn_error_clear(svn_cl__help(NULL, NULL, pool));
+
+ /* Be kind to people who try 'svn undo'. */
+ if (strcmp(first_arg_utf8, "undo") == 0)
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Undo is done using either the "
+ "'svn revert' or the 'svn merge' "
+ "command.\n")));
+ }
+
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ /* Check that the subcommand wasn't passed any inappropriate options. */
+ for (i = 0; i < received_opts->nelts; i++)
+ {
+ opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ /* All commands implicitly accept --help, so just skip over this
+ when we see it. Note that we don't want to include this option
+ in their "accepted options" list because it would be awfully
+ redundant to display it in every commands' help text. */
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id,
+ svn_cl__global_options))
+ {
+ const char *optstr;
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, svn_cl__options,
+ subcommand, pool);
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ svn_error_clear(svn_cl__help(NULL, NULL, pool));
+ else
+ svn_error_clear
+ (svn_cmdline_fprintf
+ (stderr, pool, _("Subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svn help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name));
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* Only merge and log support multiple revisions/revision ranges. */
+ if (subcommand->cmd_func != svn_cl__merge
+ && subcommand->cmd_func != svn_cl__log)
+ {
+ if (opt_state.revision_ranges->nelts > 1)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Multiple revision arguments "
+ "encountered; can't specify -c twice, "
+ "or both -c and -r"));
+ return EXIT_ERROR(err);
+ }
+ }
+
+ /* Disallow simultaneous use of both --depth and --set-depth. */
+ if ((opt_state.depth != svn_depth_unknown)
+ && (opt_state.set_depth != svn_depth_unknown))
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--depth and --set-depth are mutually "
+ "exclusive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* Disallow simultaneous use of both --with-all-revprops and
+ --with-no-revprops. */
+ if (opt_state.all_revprops && opt_state.no_revprops)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--with-all-revprops and --with-no-revprops "
+ "are mutually exclusive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* Disallow simultaneous use of both --with-revprop and
+ --with-no-revprops. */
+ if (opt_state.revprop_table && opt_state.no_revprops)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--with-revprop and --with-no-revprops "
+ "are mutually exclusive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* Disallow simultaneous use of both -m and -F, when they are
+ both used to pass a commit message or lock comment. ('propset'
+ takes the property value, not a commit message, from -F.)
+ */
+ if (opt_state.filedata && opt_state.message
+ && subcommand->cmd_func != svn_cl__propset)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--message (-m) and --file (-F) "
+ "are mutually exclusive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* --trust-server-cert can only be used with --non-interactive */
+ if (opt_state.trust_server_cert && !opt_state.non_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--trust-server-cert requires "
+ "--non-interactive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* Disallow simultaneous use of both --diff-cmd and
+ --internal-diff. */
+ if (opt_state.diff.diff_cmd && opt_state.diff.internal_diff)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--diff-cmd and --internal-diff "
+ "are mutually exclusive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* Ensure that 'revision_ranges' has at least one item, and make
+ 'start_revision' and 'end_revision' match that item. */
+ if (opt_state.revision_ranges->nelts == 0)
+ {
+ svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
+ range->start.kind = svn_opt_revision_unspecified;
+ range->end.kind = svn_opt_revision_unspecified;
+ APR_ARRAY_PUSH(opt_state.revision_ranges,
+ svn_opt_revision_range_t *) = range;
+ }
+ opt_state.start_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
+ svn_opt_revision_range_t *)->start;
+ opt_state.end_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
+ svn_opt_revision_range_t *)->end;
+
+ err = svn_config_get_config(&cfg_hash, opt_state.config_dir, pool);
+ if (err)
+ {
+ /* Fallback to default config if the config directory isn't readable
+ or is not a directory. */
+ if (APR_STATUS_IS_EACCES(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ cfg_hash = NULL;
+ }
+ else
+ return EXIT_ERROR(err);
+ }
+
+ /* Relocation is infinite-depth only. */
+ if (opt_state.relocate)
+ {
+ if (opt_state.depth != svn_depth_unknown)
+ {
+ err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--relocate and --depth are mutually "
+ "exclusive"));
+ return EXIT_ERROR(err);
+ }
+ if (! descend)
+ {
+ err = svn_error_create(
+ SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--relocate and --non-recursive (-N) are mutually "
+ "exclusive"));
+ return EXIT_ERROR(err);
+ }
+ }
+
+ /* Only a few commands can accept a revision range; the rest can take at
+ most one revision number. */
+ if (subcommand->cmd_func != svn_cl__blame
+ && subcommand->cmd_func != svn_cl__diff
+ && subcommand->cmd_func != svn_cl__log
+ && subcommand->cmd_func != svn_cl__mergeinfo
+ && subcommand->cmd_func != svn_cl__merge)
+ {
+ if (opt_state.end_revision.kind != svn_opt_revision_unspecified)
+ {
+ err = svn_error_create(SVN_ERR_CLIENT_REVISION_RANGE, NULL, NULL);
+ return EXIT_ERROR(err);
+ }
+ }
+
+ /* -N has a different meaning depending on the command */
+ if (!descend)
+ {
+ if (subcommand->cmd_func == svn_cl__status)
+ {
+ opt_state.depth = svn_depth_immediates;
+ }
+ else if (subcommand->cmd_func == svn_cl__revert
+ || subcommand->cmd_func == svn_cl__add
+ || subcommand->cmd_func == svn_cl__commit)
+ {
+ /* In pre-1.5 Subversion, some commands treated -N like
+ --depth=empty, so force that mapping here. Anyway, with
+ revert it makes sense to be especially conservative,
+ since revert can lose data. */
+ opt_state.depth = svn_depth_empty;
+ }
+ else
+ {
+ opt_state.depth = svn_depth_files;
+ }
+ }
+
+ cfg_config = svn_hash_gets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG);
+
+ /* Update the options in the config */
+ if (opt_state.config_options)
+ {
+ svn_error_clear(
+ svn_cmdline__apply_config_options(cfg_hash,
+ opt_state.config_options,
+ "svn: ", "--config-option"));
+ }
+
+#if !defined(SVN_CL_NO_EXCLUSIVE_LOCK)
+ {
+ const char *exclusive_clients_option;
+ apr_array_header_t *exclusive_clients;
+
+ svn_config_get(cfg_config, &exclusive_clients_option,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE_CLIENTS,
+ NULL);
+ exclusive_clients = svn_cstring_split(exclusive_clients_option,
+ " ,", TRUE, pool);
+ for (i = 0; i < exclusive_clients->nelts; ++i)
+ {
+ const char *exclusive_client = APR_ARRAY_IDX(exclusive_clients, i,
+ const char *);
+
+ /* This blocks other clients from accessing the wc.db so it must
+ be explicitly enabled.*/
+ if (!strcmp(exclusive_client, "svn"))
+ svn_config_set(cfg_config,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
+ "true");
+ }
+ }
+#endif
+
+ /* Create a client context object. */
+ command_baton.opt_state = &opt_state;
+ SVN_INT_ERR(svn_client_create_context2(&ctx, cfg_hash, pool));
+ command_baton.ctx = ctx;
+
+ /* If we're running a command that could result in a commit, verify
+ that any log message we were given on the command line makes
+ sense (unless we've also been instructed not to care). This may
+ access the working copy so do it after setting the locking mode. */
+ if ((! opt_state.force_log)
+ && (subcommand->cmd_func == svn_cl__commit
+ || subcommand->cmd_func == svn_cl__copy
+ || subcommand->cmd_func == svn_cl__delete
+ || subcommand->cmd_func == svn_cl__import
+ || subcommand->cmd_func == svn_cl__mkdir
+ || subcommand->cmd_func == svn_cl__move
+ || subcommand->cmd_func == svn_cl__lock
+ || subcommand->cmd_func == svn_cl__propedit))
+ {
+ /* If the -F argument is a file that's under revision control,
+ that's probably not what the user intended. */
+ if (dash_F_arg)
+ {
+ svn_node_kind_t kind;
+ const char *local_abspath;
+ const char *fname_utf8 = svn_dirent_internal_style(dash_F_arg, pool);
+
+ err = svn_dirent_get_absolute(&local_abspath, fname_utf8, pool);
+
+ if (!err)
+ {
+ err = svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE,
+ FALSE, pool);
+
+ if (!err && kind != svn_node_none && kind != svn_node_unknown)
+ {
+ if (subcommand->cmd_func != svn_cl__lock)
+ {
+ err = svn_error_create(
+ SVN_ERR_CL_LOG_MESSAGE_IS_VERSIONED_FILE, NULL,
+ _("Log message file is a versioned file; "
+ "use '--force-log' to override"));
+ }
+ else
+ {
+ err = svn_error_create(
+ SVN_ERR_CL_LOG_MESSAGE_IS_VERSIONED_FILE, NULL,
+ _("Lock comment file is a versioned file; "
+ "use '--force-log' to override"));
+ }
+ return EXIT_ERROR(err);
+ }
+ }
+ svn_error_clear(err);
+ }
+
+ /* If the -m argument is a file at all, that's probably not what
+ the user intended. */
+ if (dash_m_arg)
+ {
+ apr_finfo_t finfo;
+ if (apr_stat(&finfo, dash_m_arg,
+ APR_FINFO_MIN, pool) == APR_SUCCESS)
+ {
+ if (subcommand->cmd_func != svn_cl__lock)
+ {
+ err = svn_error_create
+ (SVN_ERR_CL_LOG_MESSAGE_IS_PATHNAME, NULL,
+ _("The log message is a pathname "
+ "(was -F intended?); use '--force-log' to override"));
+ }
+ else
+ {
+ err = svn_error_create
+ (SVN_ERR_CL_LOG_MESSAGE_IS_PATHNAME, NULL,
+ _("The lock comment is a pathname "
+ "(was -F intended?); use '--force-log' to override"));
+ }
+ return EXIT_ERROR(err);
+ }
+ }
+ }
+
+ /* XXX: Only diff_cmd for now, overlay rest later and stop passing
+ opt_state altogether? */
+ if (opt_state.diff.diff_cmd)
+ svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF_CMD, opt_state.diff.diff_cmd);
+ if (opt_state.merge_cmd)
+ svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF3_CMD, opt_state.merge_cmd);
+ if (opt_state.diff.internal_diff)
+ svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF_CMD, NULL);
+
+ /* Check for mutually exclusive args --auto-props and --no-auto-props */
+ if (opt_state.autoprops && opt_state.no_autoprops)
+ {
+ err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--auto-props and --no-auto-props are "
+ "mutually exclusive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* Update auto-props-enable option, and populate the MIME types map,
+ for add/import commands */
+ if (subcommand->cmd_func == svn_cl__add
+ || subcommand->cmd_func == svn_cl__import)
+ {
+ const char *mimetypes_file;
+ svn_config_get(cfg_config, &mimetypes_file,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_MIMETYPES_FILE, FALSE);
+ if (mimetypes_file && *mimetypes_file)
+ {
+ SVN_INT_ERR(svn_io_parse_mimetypes_file(&(ctx->mimetypes_map),
+ mimetypes_file, pool));
+ }
+
+ if (opt_state.autoprops)
+ {
+ svn_config_set_bool(cfg_config, SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS, TRUE);
+ }
+ if (opt_state.no_autoprops)
+ {
+ svn_config_set_bool(cfg_config, SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS, FALSE);
+ }
+ }
+
+ /* Update the 'keep-locks' runtime option */
+ if (opt_state.no_unlock)
+ svn_config_set_bool(cfg_config, SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_NO_UNLOCK, TRUE);
+
+ /* Set the log message callback function. Note that individual
+ subcommands will populate the ctx->log_msg_baton3. */
+ ctx->log_msg_func3 = svn_cl__get_log_message;
+
+ /* Set up the notifier.
+
+ In general, we use it any time we aren't in --quiet mode. 'svn
+ status' is unique, though, in that we don't want it in --quiet mode
+ unless we're also in --verbose mode. When in --xml mode,
+ though, we never want it. */
+ if (opt_state.quiet)
+ use_notifier = FALSE;
+ if ((subcommand->cmd_func == svn_cl__status) && opt_state.verbose)
+ use_notifier = TRUE;
+ if (opt_state.xml)
+ use_notifier = FALSE;
+ if (use_notifier)
+ {
+ SVN_INT_ERR(svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2,
+ conflict_stats, pool));
+ }
+
+ /* Set up our cancellation support. */
+ ctx->cancel_func = svn_cl__check_cancel;
+ apr_signal(SIGINT, signal_handler);
+#ifdef SIGBREAK
+ /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
+ apr_signal(SIGBREAK, signal_handler);
+#endif
+#ifdef SIGHUP
+ apr_signal(SIGHUP, signal_handler);
+#endif
+#ifdef SIGTERM
+ apr_signal(SIGTERM, signal_handler);
+#endif
+
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it, otherwise
+ * working with large files when compiled against an APR that doesn't have
+ * large file support will crash the program, which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /* Set up Authentication stuff. */
+ SVN_INT_ERR(svn_cmdline_create_auth_baton(&ab,
+ opt_state.non_interactive,
+ opt_state.auth_username,
+ opt_state.auth_password,
+ opt_state.config_dir,
+ opt_state.no_auth_cache,
+ opt_state.trust_server_cert,
+ cfg_config,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ pool));
+
+ ctx->auth_baton = ab;
+
+ if (opt_state.non_interactive)
+ {
+ if (opt_state.accept_which == svn_cl__accept_edit)
+ return EXIT_ERROR(
+ svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--accept=%s incompatible with"
+ " --non-interactive"),
+ SVN_CL__ACCEPT_EDIT));
+
+ if (opt_state.accept_which == svn_cl__accept_launch)
+ return EXIT_ERROR(
+ svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--accept=%s incompatible with"
+ " --non-interactive"),
+ SVN_CL__ACCEPT_LAUNCH));
+
+ /* The default action when we're non-interactive is to postpone
+ * conflict resolution. */
+ if (opt_state.accept_which == svn_cl__accept_unspecified)
+ opt_state.accept_which = svn_cl__accept_postpone;
+ }
+
+ /* Check whether interactive conflict resolution is disabled by
+ * the configuration file. If no --accept option was specified
+ * we postpone all conflicts in this case. */
+ SVN_INT_ERR(svn_config_get_bool(cfg_config, &interactive_conflicts,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_INTERACTIVE_CONFLICTS,
+ TRUE));
+ if (!interactive_conflicts)
+ {
+ /* Make 'svn resolve' non-interactive. */
+ if (subcommand->cmd_func == svn_cl__resolve)
+ opt_state.non_interactive = TRUE;
+
+ /* We're not resolving conflicts interactively. If no --accept option
+ * was provided the default behaviour is to postpone all conflicts. */
+ if (opt_state.accept_which == svn_cl__accept_unspecified)
+ opt_state.accept_which = svn_cl__accept_postpone;
+ }
+
+ /* Install the default conflict handler. */
+ {
+ svn_cl__interactive_conflict_baton_t *b;
+
+ ctx->conflict_func = NULL;
+ ctx->conflict_baton = NULL;
+
+ ctx->conflict_func2 = svn_cl__conflict_func_interactive;
+ SVN_INT_ERR(svn_cl__get_conflict_func_interactive_baton(
+ &b,
+ opt_state.accept_which,
+ ctx->config, opt_state.editor_cmd, conflict_stats,
+ ctx->cancel_func, ctx->cancel_baton, pool));
+ ctx->conflict_baton2 = b;
+ }
+
+ /* And now we finally run the subcommand. */
+ err = (*subcommand->cmd_func)(os, &command_baton, pool);
+ if (err)
+ {
+ /* For argument-related problems, suggest using the 'help'
+ subcommand. */
+ if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
+ || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
+ {
+ err = svn_error_quick_wrap(
+ err, apr_psprintf(pool,
+ _("Try 'svn help %s' for more information"),
+ subcommand->name));
+ }
+ if (err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Please see the 'svn upgrade' command"));
+ }
+
+ if (err->apr_err == SVN_ERR_AUTHN_FAILED && opt_state.non_interactive)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Authentication failed and interactive"
+ " prompting is disabled; see the"
+ " --force-interactive option"));
+ if (reading_file_from_stdin)
+ err = svn_error_quick_wrap(err,
+ _("Reading file from standard input "
+ "because of -F option; this can "
+ "interfere with interactive "
+ "prompting"));
+ }
+
+ /* Tell the user about 'svn cleanup' if any error on the stack
+ was about locked working copies. */
+ if (svn_error_find_cause(err, SVN_ERR_WC_LOCKED))
+ {
+ err = svn_error_quick_wrap(
+ err, _("Run 'svn cleanup' to remove locks "
+ "(type 'svn help cleanup' for details)"));
+ }
+
+ if (err->apr_err == SVN_ERR_SQLITE_BUSY)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Another process is blocking the "
+ "working copy database, or the "
+ "underlying filesystem does not "
+ "support file locking; if the working "
+ "copy is on a network filesystem, make "
+ "sure file locking has been enabled "
+ "on the file server"));
+ }
+
+ if (svn_error_find_cause(err, SVN_ERR_RA_CANNOT_CREATE_TUNNEL) &&
+ (opt_state.auth_username || opt_state.auth_password))
+ {
+ err = svn_error_quick_wrap(
+ err, _("When using svn+ssh:// URLs, keep in mind that the "
+ "--username and --password options are ignored "
+ "because authentication is performed by SSH, not "
+ "Subversion"));
+ }
+
+ return EXIT_ERROR(err);
+ }
+ else
+ {
+ /* Ensure that stdout is flushed, so the user will see any write errors.
+ This makes sure that output is not silently lost. */
+ SVN_INT_ERR(svn_cmdline_fflush(stdout));
+
+ return EXIT_SUCCESS;
+ }
+}
+
+int
+main(int argc, const char *argv[])
+{
+ apr_pool_t *pool;
+ int exit_code;
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svn", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ exit_code = sub_main(argc, argv, pool);
+
+ svn_pool_destroy(pool);
+ return exit_code;
+}
diff --git a/contrib/subversion/subversion/svn/switch-cmd.c b/contrib/subversion/subversion/svn/switch-cmd.c
new file mode 100644
index 0000000..aaef2b5
--- /dev/null
+++ b/contrib/subversion/subversion/svn/switch-cmd.c
@@ -0,0 +1,199 @@
+/*
+ * switch-cmd.c -- Bring work tree in sync with a different URL
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+/*** Code. ***/
+
+static svn_error_t *
+rewrite_urls(const apr_array_header_t *targets,
+ svn_boolean_t ignore_externals,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool;
+ const char *from;
+ const char *to;
+
+ if (targets->nelts < 2)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ from = APR_ARRAY_IDX(targets, 0, const char *);
+ to = APR_ARRAY_IDX(targets, 1, const char *);
+
+ /* "--relocate http https" and "--relocate http://foo svn://bar" are OK,
+ but things like "--relocate http://foo svn" are not */
+ if (svn_path_is_url(from) != svn_path_is_url(to))
+ return svn_error_createf
+ (SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("'%s' to '%s' is not a valid relocation"), from, to);
+
+ subpool = svn_pool_create(pool);
+
+ if (targets->nelts == 2)
+ {
+ SVN_ERR(svn_client_relocate2("", from, to, ignore_externals,
+ ctx, pool));
+ }
+ else
+ {
+ int i;
+
+ for (i = 2; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_client_relocate2(target, from, to,
+ ignore_externals, ctx, subpool));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__switch(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_error_t *externals_err = SVN_NO_ERROR;
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ const char *target, *switch_url;
+ svn_opt_revision_t peg_revision;
+ svn_depth_t depth;
+ svn_boolean_t depth_is_sticky;
+ struct svn_cl__check_externals_failed_notify_baton nwb;
+
+ /* This command should discover (or derive) exactly two cmdline
+ arguments: a local path to update ("target"), and a new url to
+ switch to ("switch_url"). */
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* handle only-rewrite case specially */
+ if (opt_state->relocate)
+ return rewrite_urls(targets, opt_state->ignore_externals,
+ ctx, scratch_pool);
+
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+ /* Get the required SWITCH_URL and its optional PEG_REVISION, and the
+ * optional TARGET argument. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &switch_url,
+ APR_ARRAY_IDX(targets, 0, const char *),
+ scratch_pool));
+ if (targets->nelts == 1)
+ target = "";
+ else
+ target = APR_ARRAY_IDX(targets, 1, const char *);
+
+ /* Validate the switch_url */
+ if (! svn_path_is_url(switch_url))
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ _("'%s' does not appear to be a URL"), switch_url);
+
+ SVN_ERR(svn_cl__check_target_is_local_path(target));
+
+ /* Deal with depthstuffs. */
+ if (opt_state->set_depth != svn_depth_unknown)
+ {
+ depth = opt_state->set_depth;
+ depth_is_sticky = TRUE;
+ }
+ else
+ {
+ depth = opt_state->depth;
+ depth_is_sticky = FALSE;
+ }
+
+ nwb.wrapped_func = ctx->notify_func2;
+ nwb.wrapped_baton = ctx->notify_baton2;
+ nwb.had_externals_error = FALSE;
+ ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper;
+ ctx->notify_baton2 = &nwb;
+
+ /* Do the 'switch' update. */
+ err = svn_client_switch3(NULL, target, switch_url, &peg_revision,
+ &(opt_state->start_revision), depth,
+ depth_is_sticky, opt_state->ignore_externals,
+ opt_state->force, opt_state->ignore_ancestry,
+ ctx, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES)
+ return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, err,
+ _("Path '%s' does not share common version "
+ "control ancestry with the requested switch "
+ "location. Use --ignore-ancestry to "
+ "disable this check."),
+ svn_dirent_local_style(target,
+ scratch_pool));
+ if (err->apr_err == SVN_ERR_RA_UUID_MISMATCH
+ || err->apr_err == SVN_ERR_WC_INVALID_SWITCH)
+ return svn_error_quick_wrap(
+ err,
+ _("'svn switch' does not support switching a working copy to "
+ "a different repository"));
+ return err;
+ }
+
+ if (nwb.had_externals_error)
+ externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
+ NULL,
+ _("Failure occurred processing one or "
+ "more externals definitions"));
+
+ if (! opt_state->quiet)
+ {
+ err = svn_cl__notifier_print_conflict_stats(nwb.wrapped_baton, scratch_pool);
+ if (err)
+ return svn_error_compose_create(externals_err, err);
+ }
+
+ return svn_error_compose_create(externals_err, err);
+}
diff --git a/contrib/subversion/subversion/svn/unlock-cmd.c b/contrib/subversion/subversion/svn/unlock-cmd.c
new file mode 100644
index 0000000..0f94d2a
--- /dev/null
+++ b/contrib/subversion/subversion/svn/unlock-cmd.c
@@ -0,0 +1,68 @@
+/*
+ * unlock-cmd.c -- Unlock a working copy path.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_cmdline.h"
+#include "cl.h"
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__unlock(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* We don't support unlock on directories, so "." is not relevant. */
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ SVN_ERR(svn_cl__assert_homogeneous_target_type(targets));
+
+ return svn_error_trace(
+ svn_client_unlock(targets, opt_state->force, ctx, scratch_pool));
+}
diff --git a/contrib/subversion/subversion/svn/update-cmd.c b/contrib/subversion/subversion/svn/update-cmd.c
new file mode 100644
index 0000000..77c28f9
--- /dev/null
+++ b/contrib/subversion/subversion/svn/update-cmd.c
@@ -0,0 +1,196 @@
+/*
+ * update-cmd.c -- Bring work tree in sync with repository
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_path.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* Print an update summary when there's more than one target to report
+ about. Each (const char *) path in TARGETS is an absolute or relative
+ dirent, and each (svn_revnum_t) entry in RESULT_REVS is the corresponding
+ updated revision, or SVN_INVALID_REVNUM if not a valid target. */
+static svn_error_t *
+print_update_summary(apr_array_header_t *targets,
+ apr_array_header_t *result_revs,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ const char *path_prefix;
+ apr_pool_t *iterpool;
+ svn_boolean_t printed_header = FALSE;
+
+ if (targets->nelts < 2)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", scratch_pool));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(targets, i, const char *);
+ svn_revnum_t rev = SVN_INVALID_REVNUM;
+
+ svn_pool_clear(iterpool);
+
+ /* PATH shouldn't be a URL. */
+ SVN_ERR_ASSERT(! svn_path_is_url(path));
+
+ /* Grab the result revision from the corresponding slot in our
+ RESULT_REVS array. */
+ if (i < result_revs->nelts)
+ rev = APR_ARRAY_IDX(result_revs, i, svn_revnum_t);
+
+ /* No result rev? We must have skipped this path. At any rate,
+ nothing to report here. */
+ if (! SVN_IS_VALID_REVNUM(rev))
+ continue;
+
+ /* Convert to an absolute path if it's not already. */
+ if (! svn_dirent_is_absolute(path))
+ SVN_ERR(svn_dirent_get_absolute(&path, path, iterpool));
+
+ /* Print an update summary for this target, removing the current
+ working directory prefix from PATH (if PATH is at or under
+ $CWD), and converting the path to local style for display. */
+ if (! printed_header)
+ {
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _("Summary of updates:\n")));
+ printed_header = TRUE;
+ }
+
+ SVN_ERR(svn_cmdline_printf(iterpool, _(" Updated '%s' to r%ld.\n"),
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, path, iterpool),
+ rev));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__update(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ svn_depth_t depth;
+ svn_boolean_t depth_is_sticky;
+ struct svn_cl__check_externals_failed_notify_baton nwb;
+ apr_array_header_t *result_revs;
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_error_t *externals_err = SVN_NO_ERROR;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, scratch_pool);
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ /* If using changelists, convert targets into a set of paths that
+ match the specified changelist(s). */
+ if (opt_state->changelists)
+ {
+ svn_depth_t cl_depth = opt_state->depth;
+ if (cl_depth == svn_depth_unknown)
+ cl_depth = svn_depth_infinity;
+ SVN_ERR(svn_cl__changelist_paths(&targets,
+ opt_state->changelists, targets,
+ cl_depth, ctx, scratch_pool,
+ scratch_pool));
+ }
+
+ /* Deal with depthstuffs. */
+ if (opt_state->set_depth != svn_depth_unknown)
+ {
+ depth = opt_state->set_depth;
+ depth_is_sticky = TRUE;
+ }
+ else
+ {
+ depth = opt_state->depth;
+ depth_is_sticky = FALSE;
+ }
+
+ nwb.wrapped_func = ctx->notify_func2;
+ nwb.wrapped_baton = ctx->notify_baton2;
+ nwb.had_externals_error = FALSE;
+ ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper;
+ ctx->notify_baton2 = &nwb;
+
+ SVN_ERR(svn_client_update4(&result_revs, targets,
+ &(opt_state->start_revision),
+ depth, depth_is_sticky,
+ opt_state->ignore_externals,
+ opt_state->force, TRUE /* adds_as_modification */,
+ opt_state->parents,
+ ctx, scratch_pool));
+
+ if (nwb.had_externals_error)
+ externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
+ NULL,
+ _("Failure occurred processing one or "
+ "more externals definitions"));
+
+ if (! opt_state->quiet)
+ {
+ err = print_update_summary(targets, result_revs, scratch_pool);
+ if (err)
+ return svn_error_compose_create(externals_err, err);
+
+ /* ### Layering problem: This call assumes that the baton we're
+ * passing is the one that was originally provided by
+ * svn_cl__get_notifier(), but that isn't promised. */
+ err = svn_cl__notifier_print_conflict_stats(nwb.wrapped_baton,
+ scratch_pool);
+ if (err)
+ return svn_error_compose_create(externals_err, err);
+ }
+
+ return svn_error_compose_create(externals_err, err);
+}
diff --git a/contrib/subversion/subversion/svn/upgrade-cmd.c b/contrib/subversion/subversion/svn/upgrade-cmd.c
new file mode 100644
index 0000000..e2df143
--- /dev/null
+++ b/contrib/subversion/subversion/svn/upgrade-cmd.c
@@ -0,0 +1,78 @@
+/*
+ * upgrade-cmd.c -- Upgrade a working copy.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "cl.h"
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__upgrade(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ apr_pool_t *iterpool;
+ int i;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, scratch_pool);
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+ SVN_ERR(svn_client_upgrade(target, ctx, scratch_pool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svn/util.c b/contrib/subversion/subversion/svn/util.c
new file mode 100644
index 0000000..5d386f8
--- /dev/null
+++ b/contrib/subversion/subversion/svn/util.c
@@ -0,0 +1,1109 @@
+/*
+ * util.c: Subversion command line client utility functions. Any
+ * functions that need to be shared across subcommands should be put
+ * in here.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <apr_env.h>
+#include <apr_errno.h>
+#include <apr_file_info.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+#include <apr_general.h>
+#include <apr_lib.h>
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_ctype.h"
+#include "svn_client.h"
+#include "svn_cmdline.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_io.h"
+#include "svn_utf.h"
+#include "svn_subst.h"
+#include "svn_config.h"
+#include "svn_wc.h"
+#include "svn_xml.h"
+#include "svn_time.h"
+#include "svn_props.h"
+#include "svn_private_config.h"
+#include "cl.h"
+
+#include "private/svn_token.h"
+#include "private/svn_opt_private.h"
+#include "private/svn_client_private.h"
+#include "private/svn_cmdline_private.h"
+#include "private/svn_string_private.h"
+
+
+
+
+svn_error_t *
+svn_cl__print_commit_info(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ if (SVN_IS_VALID_REVNUM(commit_info->revision))
+ SVN_ERR(svn_cmdline_printf(pool, _("\nCommitted revision %ld%s.\n"),
+ commit_info->revision,
+ commit_info->revision == 42 &&
+ getenv("SVN_I_LOVE_PANGALACTIC_GARGLE_BLASTERS")
+ ? _(" (the answer to life, the universe, "
+ "and everything)")
+ : ""));
+
+ /* Writing to stdout, as there maybe systems that consider the
+ * presence of stderr as an indication of commit failure.
+ * OTOH, this is only of informational nature to the user as
+ * the commit has succeeded. */
+ if (commit_info->post_commit_err)
+ SVN_ERR(svn_cmdline_printf(pool, _("\nWarning: %s\n"),
+ commit_info->post_commit_err));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_cl__merge_file_externally(const char *base_path,
+ const char *their_path,
+ const char *my_path,
+ const char *merged_path,
+ const char *wc_path,
+ apr_hash_t *config,
+ svn_boolean_t *remains_in_conflict,
+ apr_pool_t *pool)
+{
+ char *merge_tool;
+ /* Error if there is no editor specified */
+ if (apr_env_get(&merge_tool, "SVN_MERGE", pool) != APR_SUCCESS)
+ {
+ struct svn_config_t *cfg;
+ merge_tool = NULL;
+ cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
+ /* apr_env_get wants char **, this wants const char ** */
+ svn_config_get(cfg, (const char **)&merge_tool,
+ SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_MERGE_TOOL_CMD, NULL);
+ }
+
+ if (merge_tool)
+ {
+ const char *c;
+
+ for (c = merge_tool; *c; c++)
+ if (!svn_ctype_isspace(*c))
+ break;
+
+ if (! *c)
+ return svn_error_create
+ (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
+ _("The SVN_MERGE environment variable is empty or "
+ "consists solely of whitespace. Expected a shell command.\n"));
+ }
+ else
+ return svn_error_create
+ (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
+ _("The environment variable SVN_MERGE and the merge-tool-cmd run-time "
+ "configuration option were not set.\n"));
+
+ {
+ const char *arguments[7] = { 0 };
+ char *cwd;
+ int exitcode;
+
+ apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool);
+ if (status != 0)
+ return svn_error_wrap_apr(status, NULL);
+
+ arguments[0] = merge_tool;
+ arguments[1] = base_path;
+ arguments[2] = their_path;
+ arguments[3] = my_path;
+ arguments[4] = merged_path;
+ arguments[5] = wc_path;
+ arguments[6] = NULL;
+
+ SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), merge_tool,
+ arguments, &exitcode, NULL, TRUE, NULL, NULL, NULL,
+ pool));
+ /* Exit code 0 means the merge was successful.
+ * Exit code 1 means the file was left in conflict but it
+ * is OK to continue with the merge.
+ * Any other exit code means there was a real problem. */
+ if (exitcode != 0 && exitcode != 1)
+ return svn_error_createf
+ (SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("The external merge tool exited with exit code %d"), exitcode);
+ else if (remains_in_conflict)
+ *remains_in_conflict = exitcode == 1;
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* A svn_client_ctx_t's log_msg_baton3, for use with
+ svn_cl__make_log_msg_baton(). */
+struct log_msg_baton
+{
+ const char *editor_cmd; /* editor specified via --editor-cmd, else NULL */
+ const char *message; /* the message. */
+ const char *message_encoding; /* the locale/encoding of the message. */
+ const char *base_dir; /* the base directory for an external edit. UTF-8! */
+ const char *tmpfile_left; /* the tmpfile left by an external edit. UTF-8! */
+ svn_boolean_t non_interactive; /* if true, don't pop up an editor */
+ apr_hash_t *config; /* client configuration hash */
+ svn_boolean_t keep_locks; /* Keep repository locks? */
+ apr_pool_t *pool; /* a pool. */
+};
+
+
+svn_error_t *
+svn_cl__make_log_msg_baton(void **baton,
+ svn_cl__opt_state_t *opt_state,
+ const char *base_dir /* UTF-8! */,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ struct log_msg_baton *lmb = apr_palloc(pool, sizeof(*lmb));
+
+ if (opt_state->filedata)
+ {
+ if (strlen(opt_state->filedata->data) < opt_state->filedata->len)
+ {
+ /* The data contains a zero byte, and therefore can't be
+ represented as a C string. Punt now; it's probably not
+ a deliberate encoding, and even if it is, we still
+ can't handle it. */
+ return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL,
+ _("Log message contains a zero byte"));
+ }
+ lmb->message = opt_state->filedata->data;
+ }
+ else
+ {
+ lmb->message = opt_state->message;
+ }
+
+ lmb->editor_cmd = opt_state->editor_cmd;
+ if (opt_state->encoding)
+ {
+ lmb->message_encoding = opt_state->encoding;
+ }
+ else if (config)
+ {
+ svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
+ svn_config_get(cfg, &(lmb->message_encoding),
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_LOG_ENCODING,
+ NULL);
+ }
+
+ lmb->base_dir = base_dir ? base_dir : "";
+ lmb->tmpfile_left = NULL;
+ lmb->config = config;
+ lmb->keep_locks = opt_state->no_unlock;
+ lmb->non_interactive = opt_state->non_interactive;
+ lmb->pool = pool;
+ *baton = lmb;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_cl__cleanup_log_msg(void *log_msg_baton,
+ svn_error_t *commit_err,
+ apr_pool_t *pool)
+{
+ struct log_msg_baton *lmb = log_msg_baton;
+ svn_error_t *err;
+
+ /* If there was no tmpfile left, or there is no log message baton,
+ return COMMIT_ERR. */
+ if ((! lmb) || (! lmb->tmpfile_left))
+ return commit_err;
+
+ /* If there was no commit error, cleanup the tmpfile and return. */
+ if (! commit_err)
+ return svn_io_remove_file2(lmb->tmpfile_left, FALSE, lmb->pool);
+
+ /* There was a commit error; there is a tmpfile. Leave the tmpfile
+ around, and add message about its presence to the commit error
+ chain. Then return COMMIT_ERR. If the conversion from UTF-8 to
+ native encoding fails, we have to compose that error with the
+ commit error chain, too. */
+
+ err = svn_error_createf(commit_err->apr_err, NULL,
+ _(" '%s'"),
+ svn_dirent_local_style(lmb->tmpfile_left, pool));
+ svn_error_compose(commit_err,
+ svn_error_create(commit_err->apr_err, err,
+ _("Your commit message was left in "
+ "a temporary file:")));
+ return commit_err;
+}
+
+
+/* Remove line-starting PREFIX and everything after it from BUFFER.
+ If NEW_LEN is non-NULL, return the new length of BUFFER in
+ *NEW_LEN. */
+static void
+truncate_buffer_at_prefix(apr_size_t *new_len,
+ char *buffer,
+ const char *prefix)
+{
+ char *substring = buffer;
+
+ assert(buffer && prefix);
+
+ /* Initialize *NEW_LEN. */
+ if (new_len)
+ *new_len = strlen(buffer);
+
+ while (1)
+ {
+ /* Find PREFIX in BUFFER. */
+ substring = strstr(substring, prefix);
+ if (! substring)
+ return;
+
+ /* We found PREFIX. Is it really a PREFIX? Well, if it's the first
+ thing in the file, or if the character before it is a
+ line-terminator character, it sure is. */
+ if ((substring == buffer)
+ || (*(substring - 1) == '\r')
+ || (*(substring - 1) == '\n'))
+ {
+ *substring = '\0';
+ if (new_len)
+ *new_len = substring - buffer;
+ }
+ else if (substring)
+ {
+ /* Well, it wasn't really a prefix, so just advance by 1
+ character and continue. */
+ substring++;
+ }
+ }
+
+ /* NOTREACHED */
+}
+
+
+#define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--")
+
+svn_error_t *
+svn_cl__get_log_message(const char **log_msg,
+ const char **tmp_file,
+ const apr_array_header_t *commit_items,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *default_msg = NULL;
+ struct log_msg_baton *lmb = baton;
+ svn_stringbuf_t *message = NULL;
+
+ /* Set default message. */
+ default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
+ svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
+ svn_stringbuf_appendcstr(default_msg, APR_EOL_STR APR_EOL_STR);
+
+ *tmp_file = NULL;
+ if (lmb->message)
+ {
+ svn_stringbuf_t *log_msg_buf = svn_stringbuf_create(lmb->message, pool);
+ svn_string_t *log_msg_str = apr_pcalloc(pool, sizeof(*log_msg_str));
+
+ /* Trim incoming messages of the EOF marker text and the junk
+ that follows it. */
+ truncate_buffer_at_prefix(&(log_msg_buf->len), log_msg_buf->data,
+ EDITOR_EOF_PREFIX);
+
+ /* Make a string from a stringbuf, sharing the data allocation. */
+ log_msg_str->data = log_msg_buf->data;
+ log_msg_str->len = log_msg_buf->len;
+ SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, FALSE, FALSE,
+ log_msg_str, lmb->message_encoding,
+ FALSE, pool, pool),
+ _("Error normalizing log message to internal format"));
+
+ *log_msg = log_msg_str->data;
+ return SVN_NO_ERROR;
+ }
+
+ if (! commit_items->nelts)
+ {
+ *log_msg = "";
+ return SVN_NO_ERROR;
+ }
+
+ while (! message)
+ {
+ /* We still don't have a valid commit message. Use $EDITOR to
+ get one. Note that svn_cl__edit_string_externally will still
+ return a UTF-8'ized log message. */
+ int i;
+ svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_string_t *msg_string = svn_string_create_empty(pool);
+
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item
+ = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+ const char *path = item->path;
+ char text_mod = '_', prop_mod = ' ', unlock = ' ';
+
+ if (! path)
+ path = item->url;
+ else if (! *path)
+ path = ".";
+
+ if (! svn_path_is_url(path) && lmb->base_dir)
+ path = svn_dirent_is_child(lmb->base_dir, path, pool);
+
+ /* If still no path, then just use current directory. */
+ if (! path)
+ path = ".";
+
+ if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
+ text_mod = 'R';
+ else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
+ text_mod = 'A';
+ else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ text_mod = 'D';
+ else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
+ text_mod = 'M';
+
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
+ prop_mod = 'M';
+
+ if (! lmb->keep_locks
+ && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
+ unlock = 'U';
+
+ svn_stringbuf_appendbyte(tmp_message, text_mod);
+ svn_stringbuf_appendbyte(tmp_message, prop_mod);
+ svn_stringbuf_appendbyte(tmp_message, unlock);
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
+ /* History included via copy/move. */
+ svn_stringbuf_appendcstr(tmp_message, "+ ");
+ else
+ svn_stringbuf_appendcstr(tmp_message, " ");
+ svn_stringbuf_appendcstr(tmp_message, path);
+ svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
+ }
+
+ msg_string->data = tmp_message->data;
+ msg_string->len = tmp_message->len;
+
+ /* Use the external edit to get a log message. */
+ if (! lmb->non_interactive)
+ {
+ err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
+ lmb->editor_cmd, lmb->base_dir,
+ msg_string, "svn-commit",
+ lmb->config, TRUE,
+ lmb->message_encoding,
+ pool);
+ }
+ else /* non_interactive flag says we can't pop up an editor, so error */
+ {
+ return svn_error_create
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Cannot invoke editor to get log message "
+ "when non-interactive"));
+ }
+
+ /* Dup the tmpfile path into its baton's pool. */
+ *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
+ lmb->tmpfile_left);
+
+ /* If the edit returned an error, handle it. */
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
+ err = svn_error_quick_wrap
+ (err, _("Could not use external editor to fetch log message; "
+ "consider setting the $SVN_EDITOR environment variable "
+ "or using the --message (-m) or --file (-F) options"));
+ return svn_error_trace(err);
+ }
+
+ if (msg_string)
+ message = svn_stringbuf_create_from_string(msg_string, pool);
+
+ /* Strip the prefix from the buffer. */
+ if (message)
+ truncate_buffer_at_prefix(&message->len, message->data,
+ EDITOR_EOF_PREFIX);
+
+ if (message)
+ {
+ /* We did get message, now check if it is anything more than just
+ white space as we will consider white space only as empty */
+ apr_size_t len;
+
+ for (len = 0; len < message->len; len++)
+ {
+ /* FIXME: should really use an UTF-8 whitespace test
+ rather than svn_ctype_isspace, which is ASCII only */
+ if (! svn_ctype_isspace(message->data[len]))
+ break;
+ }
+ if (len == message->len)
+ message = NULL;
+ }
+
+ if (! message)
+ {
+ const char *reply;
+ SVN_ERR(svn_cmdline_prompt_user2
+ (&reply,
+ _("\nLog message unchanged or not specified\n"
+ "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
+ if (reply)
+ {
+ int letter = apr_tolower(reply[0]);
+
+ /* If the user chooses to abort, we cleanup the
+ temporary file and exit the loop with a NULL
+ message. */
+ if ('a' == letter)
+ {
+ SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
+ *tmp_file = lmb->tmpfile_left = NULL;
+ break;
+ }
+
+ /* If the user chooses to continue, we make an empty
+ message, which will cause us to exit the loop. We
+ also cleanup the temporary file. */
+ if ('c' == letter)
+ {
+ SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
+ *tmp_file = lmb->tmpfile_left = NULL;
+ message = svn_stringbuf_create_empty(pool);
+ }
+
+ /* If the user chooses anything else, the loop will
+ continue on the NULL message. */
+ }
+ }
+ }
+
+ *log_msg = message ? message->data : NULL;
+ return SVN_NO_ERROR;
+}
+
+
+/* ### The way our error wrapping currently works, the error returned
+ * from here will look as though it originates in this source file,
+ * instead of in the caller's source file. This can be a bit
+ * misleading, until one starts debugging. Ideally, there'd be a way
+ * to wrap an error while preserving its FILE/LINE info.
+ */
+svn_error_t *
+svn_cl__may_need_force(svn_error_t *err)
+{
+ if (err
+ && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE ||
+ err->apr_err == SVN_ERR_CLIENT_MODIFIED))
+ {
+ /* Should this svn_error_compose a new error number? Probably not,
+ the error hasn't changed. */
+ err = svn_error_quick_wrap
+ (err, _("Use --force to override this restriction (local modifications "
+ "may be lost)"));
+ }
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_cl__error_checked_fputs(const char *string, FILE* stream)
+{
+ /* On POSIX systems, errno will be set on an error in fputs, but this might
+ not be the case on other platforms. We reset errno and only
+ use it if it was set by the below fputs call. Else, we just return
+ a generic error. */
+ errno = 0;
+
+ if (fputs(string, stream) == EOF)
+ {
+ if (errno)
+ return svn_error_wrap_apr(errno, _("Write error"));
+ else
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_cl__try(svn_error_t *err,
+ apr_array_header_t *errors_seen,
+ svn_boolean_t quiet,
+ ...)
+{
+ if (err)
+ {
+ apr_status_t apr_err;
+ va_list ap;
+
+ va_start(ap, quiet);
+ while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS)
+ {
+ if (errors_seen)
+ {
+ int i;
+ svn_boolean_t add = TRUE;
+
+ /* Don't report duplicate error codes. */
+ for (i = 0; i < errors_seen->nelts; i++)
+ {
+ if (APR_ARRAY_IDX(errors_seen, i,
+ apr_status_t) == err->apr_err)
+ {
+ add = FALSE;
+ break;
+ }
+ }
+ if (add)
+ APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err;
+ }
+ if (err->apr_err == apr_err)
+ {
+ if (! quiet)
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ }
+ va_end(ap);
+ }
+
+ return svn_error_trace(err);
+}
+
+
+void
+svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
+ apr_pool_t *pool,
+ const char *tagname,
+ const char *string)
+{
+ if (string)
+ {
+ svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
+ tagname, NULL);
+ svn_xml_escape_cdata_cstring(sb, string, pool);
+ svn_xml_make_close_tag(sb, pool, tagname);
+ }
+}
+
+
+void
+svn_cl__print_xml_commit(svn_stringbuf_t **sb,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ apr_pool_t *pool)
+{
+ /* "<commit ...>" */
+ svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
+ "revision",
+ apr_psprintf(pool, "%ld", revision), NULL);
+
+ /* "<author>xx</author>" */
+ if (author)
+ svn_cl__xml_tagged_cdata(sb, pool, "author", author);
+
+ /* "<date>xx</date>" */
+ if (date)
+ svn_cl__xml_tagged_cdata(sb, pool, "date", date);
+
+ /* "</commit>" */
+ svn_xml_make_close_tag(sb, pool, "commit");
+}
+
+
+void
+svn_cl__print_xml_lock(svn_stringbuf_t **sb,
+ const svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ /* "<lock>" */
+ svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", NULL);
+
+ /* "<token>xx</token>" */
+ svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token);
+
+ /* "<owner>xx</owner>" */
+ svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner);
+
+ /* "<comment>xx</comment>" */
+ svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment);
+
+ /* "<created>xx</created>" */
+ svn_cl__xml_tagged_cdata(sb, pool, "created",
+ svn_time_to_cstring(lock->creation_date, pool));
+
+ /* "<expires>xx</expires>" */
+ if (lock->expiration_date != 0)
+ svn_cl__xml_tagged_cdata(sb, pool, "expires",
+ svn_time_to_cstring(lock->expiration_date, pool));
+
+ /* "</lock>" */
+ svn_xml_make_close_tag(sb, pool, "lock");
+}
+
+
+svn_error_t *
+svn_cl__xml_print_header(const char *tagname,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ /* <?xml version="1.0" encoding="UTF-8"?> */
+ svn_xml_make_header2(&sb, "UTF-8", pool);
+
+ /* "<TAGNAME>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, NULL);
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+svn_error_t *
+svn_cl__xml_print_footer(const char *tagname,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ /* "</TAGNAME>" */
+ svn_xml_make_close_tag(&sb, pool, tagname);
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* A map for svn_node_kind_t values to XML strings */
+static const svn_token_map_t map_node_kind_xml[] =
+{
+ { "none", svn_node_none },
+ { "file", svn_node_file },
+ { "dir", svn_node_dir },
+ { "", svn_node_unknown },
+ { NULL, 0 }
+};
+
+/* A map for svn_node_kind_t values to human-readable strings */
+static const svn_token_map_t map_node_kind_human[] =
+{
+ { N_("none"), svn_node_none },
+ { N_("file"), svn_node_file },
+ { N_("dir"), svn_node_dir },
+ { "", svn_node_unknown },
+ { NULL, 0 }
+};
+
+const char *
+svn_cl__node_kind_str_xml(svn_node_kind_t kind)
+{
+ return svn_token__to_word(map_node_kind_xml, kind);
+}
+
+const char *
+svn_cl__node_kind_str_human_readable(svn_node_kind_t kind)
+{
+ return _(svn_token__to_word(map_node_kind_human, kind));
+}
+
+
+/* A map for svn_wc_operation_t values to XML strings */
+static const svn_token_map_t map_wc_operation_xml[] =
+{
+ { "none", svn_wc_operation_none },
+ { "update", svn_wc_operation_update },
+ { "switch", svn_wc_operation_switch },
+ { "merge", svn_wc_operation_merge },
+ { NULL, 0 }
+};
+
+/* A map for svn_wc_operation_t values to human-readable strings */
+static const svn_token_map_t map_wc_operation_human[] =
+{
+ { N_("none"), svn_wc_operation_none },
+ { N_("update"), svn_wc_operation_update },
+ { N_("switch"), svn_wc_operation_switch },
+ { N_("merge"), svn_wc_operation_merge },
+ { NULL, 0 }
+};
+
+const char *
+svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool)
+{
+ return svn_token__to_word(map_wc_operation_xml, operation);
+}
+
+const char *
+svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
+ apr_pool_t *pool)
+{
+ return _(svn_token__to_word(map_wc_operation_human, operation));
+}
+
+
+svn_error_t *
+svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t keep_last_origpath_on_truepath_collision,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_client_args_to_target_array2(targets,
+ os,
+ known_targets,
+ ctx,
+ keep_last_origpath_on_truepath_collision,
+ pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
+ {
+ svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
+ svn_error_clear(err);
+ }
+ else
+ return svn_error_trace(err);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for svn_cl__get_changelist(); implements
+ svn_changelist_receiver_t. */
+static svn_error_t *
+changelist_receiver(void *baton,
+ const char *path,
+ const char *changelist,
+ apr_pool_t *pool)
+{
+ /* No need to check CHANGELIST; our caller only asked about one of them. */
+ apr_array_header_t *paths = baton;
+ APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_cl__changelist_paths(apr_array_header_t **paths,
+ const apr_array_header_t *changelists,
+ const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *found;
+ apr_hash_t *paths_hash;
+ apr_pool_t *iterpool;
+ int i;
+
+ if (! (changelists && changelists->nelts))
+ {
+ *paths = (apr_array_header_t *)targets;
+ return SVN_NO_ERROR;
+ }
+
+ found = apr_array_make(scratch_pool, 8, sizeof(const char *));
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_client_get_changelists(target, changelists, depth,
+ changelist_receiver, found,
+ ctx, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool));
+ return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool));
+}
+
+svn_cl__show_revs_t
+svn_cl__show_revs_from_word(const char *word)
+{
+ if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
+ return svn_cl__show_revs_merged;
+ if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
+ return svn_cl__show_revs_eligible;
+ /* word is an invalid flavor. */
+ return svn_cl__show_revs_invalid;
+}
+
+
+svn_error_t *
+svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
+ const char *data,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_time_t when;
+
+ err = svn_time_from_cstring(&when, data, pool);
+ if (err && err->apr_err == SVN_ERR_BAD_DATE)
+ {
+ svn_error_clear(err);
+
+ *human_cstring = _("(invalid date)");
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ *human_cstring = svn_time_to_human_cstring(when, pool);
+
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_cl__node_description(const svn_wc_conflict_version_t *node,
+ const char *wc_repos_root_URL,
+ apr_pool_t *pool)
+{
+ const char *root_str = "^";
+ const char *path_str = "...";
+
+ if (!node)
+ /* Printing "(none)" the harder way to ensure conformity (mostly with
+ * translations). */
+ return apr_psprintf(pool, "(%s)",
+ svn_cl__node_kind_str_human_readable(svn_node_none));
+
+ /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL.
+ * Otherwise show the complete URL, and if we can't, show dots. */
+
+ if (node->repos_url &&
+ (wc_repos_root_URL == NULL ||
+ strcmp(node->repos_url, wc_repos_root_URL) != 0))
+ root_str = node->repos_url;
+
+ if (node->path_in_repos)
+ path_str = node->path_in_repos;
+
+ return apr_psprintf(pool, "(%s) %s@%ld",
+ svn_cl__node_kind_str_human_readable(node->node_kind),
+ svn_path_url_add_component2(root_str, path_str, pool),
+ node->peg_rev);
+}
+
+svn_error_t *
+svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
+ const apr_array_header_t *targets,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_array_header_t *true_targets;
+
+ true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *true_target, *peg;
+
+ SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
+ target, pool));
+ if (peg[0] && peg[1])
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s': a peg revision is not allowed here"),
+ target);
+ APR_ARRAY_PUSH(true_targets, const char *) = true_target;
+ }
+
+ SVN_ERR_ASSERT(true_targets_p);
+ *true_targets_p = true_targets;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
+{
+ svn_error_t *err;
+
+ err = svn_client__assert_homogeneous_target_type(targets);
+ if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
+ return err;
+}
+
+svn_error_t *
+svn_cl__check_target_is_local_path(const char *target)
+{
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a local path"), target);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets)
+{
+ int i;
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+
+ SVN_ERR(svn_cl__check_target_is_local_path(target));
+ }
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_cl__local_style_skip_ancestor(const char *parent_path,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *relpath = NULL;
+
+ if (parent_path)
+ relpath = svn_dirent_skip_ancestor(parent_path, path);
+
+ return svn_dirent_local_style(relpath ? relpath : path, pool);
+}
+
+/* Return a string of the form "PATH_OR_URL@REVISION". */
+static const char *
+path_for_display(const char *path_or_url,
+ const svn_opt_revision_t *revision,
+ apr_pool_t *pool)
+{
+ const char *rev_str = svn_opt__revision_to_string(revision, pool);
+
+ if (! svn_path_is_url(path_or_url))
+ path_or_url = svn_dirent_local_style(path_or_url, pool);
+ return apr_psprintf(pool, "%s@%s", path_or_url, rev_str);
+}
+
+svn_error_t *
+svn_cl__check_related_source_and_target(const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *ancestor_url;
+ svn_revnum_t ancestor_rev;
+
+ SVN_ERR(svn_client__youngest_common_ancestor(
+ &ancestor_url, &ancestor_rev,
+ path_or_url1, revision1, path_or_url2, revision2,
+ ctx, pool, pool));
+
+ if (ancestor_url == NULL)
+ {
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Source and target have no common ancestor: "
+ "'%s' and '%s'"),
+ path_for_display(path_or_url1, revision1, pool),
+ path_for_display(path_or_url2, revision2, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
+ const char *propname,
+ const svn_string_t *propval,
+ apr_pool_t *scratch_pool)
+{
+ if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *detected_mimetype;
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *local_abspath;
+ const svn_string_t *canon_propval;
+ svn_node_kind_t node_kind;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
+ SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
+ if (node_kind != svn_node_file)
+ continue;
+
+ SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
+ propname, propval,
+ local_abspath,
+ svn_node_file,
+ FALSE, NULL, NULL,
+ iterpool));
+
+ if (svn_mime_type_is_binary(canon_propval->data))
+ {
+ SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
+ local_abspath, NULL,
+ iterpool));
+ if (detected_mimetype == NULL ||
+ !svn_mime_type_is_binary(detected_mimetype))
+ svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
+ _("svn: warning: '%s' is a binary mime-type but file '%s' "
+ "looks like text; diff, merge, blame, and other "
+ "operations will stop working on this file\n"),
+ canon_propval->data,
+ svn_dirent_local_style(local_abspath, iterpool)));
+
+ }
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/svn_private_config.h.in b/contrib/subversion/subversion/svn_private_config.h.in
new file mode 100644
index 0000000..84d157b
--- /dev/null
+++ b/contrib/subversion/subversion/svn_private_config.h.in
@@ -0,0 +1,257 @@
+/* subversion/svn_private_config.h.in. Generated from configure.ac by autoheader. */
+
+/* The fs type to use by default */
+#undef DEFAULT_FS_TYPE
+
+/* The http library to use by default */
+#undef DEFAULT_HTTP_LIBRARY
+
+/* Define to 1 if Ev2 implementations should be used. */
+#undef ENABLE_EV2_IMPL
+
+/* Define to 1 if translation of program messages to the user's native
+ language is requested. */
+#undef ENABLE_NLS
+
+/* Define to 1 if you have the `bind_textdomain_codeset' function. */
+#undef HAVE_BIND_TEXTDOMAIN_CODESET
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */
+#undef HAVE_DOPRNT
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the `iconv' library (-liconv). */
+#undef HAVE_LIBICONV
+
+/* Define to 1 if you have the `socket' library (-lsocket). */
+#undef HAVE_LIBSOCKET
+
+/* Define to 1 if you have the <magic.h> header file. */
+#undef HAVE_MAGIC_H
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the `rb_errinfo' function. */
+#undef HAVE_RB_ERRINFO
+
+/* Define to 1 if you have the `readlink' function. */
+#undef HAVE_READLINK
+
+/* Define to 1 if you have the <serf.h> header file. */
+#undef HAVE_SERF_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the `symlink' function. */
+#undef HAVE_SYMLINK
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <sys/utsname.h> header file. */
+#undef HAVE_SYS_UTSNAME_H
+
+/* Define to 1 if you have the `tcgetattr' function. */
+#undef HAVE_TCGETATTR
+
+/* Define to 1 if you have the `tcsetattr' function. */
+#undef HAVE_TCSETATTR
+
+/* Defined if we have a usable termios library. */
+#undef HAVE_TERMIOS_H
+
+/* Define to 1 if you have the `uname' function. */
+#undef HAVE_UNAME
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to 1 if you have the `vprintf' function. */
+#undef HAVE_VPRINTF
+
+/* Define to 1 if you have the <zlib.h> header file. */
+#undef HAVE_ZLIB_H
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#undef LT_OBJDIR
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Define to the Python/C API format character suitable for apr_int64_t */
+#undef SVN_APR_INT64_T_PYCFMT
+
+/* Define if circular linkage is not possible on this platform. */
+#undef SVN_AVOID_CIRCULAR_LINKAGE_AT_ALL_COSTS_HACK
+
+/* Defined to be the path to the installed binaries */
+#undef SVN_BINDIR
+
+/* Defined to the config.guess name of the build system */
+#undef SVN_BUILD_HOST
+
+/* Defined to the config.guess name of the build target */
+#undef SVN_BUILD_TARGET
+
+/* The path of a default editor for the client. */
+#undef SVN_CLIENT_EDITOR
+
+/* Defined if the full version matching rules are disabled */
+#undef SVN_DISABLE_FULL_VERSION_MATCH
+
+/* Defined if plaintext password/passphrase storage is disabled */
+#undef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
+
+/* The desired major version for the Berkeley DB */
+#undef SVN_FS_WANT_DB_MAJOR
+
+/* The desired minor version for the Berkeley DB */
+#undef SVN_FS_WANT_DB_MINOR
+
+/* The desired patch version for the Berkeley DB */
+#undef SVN_FS_WANT_DB_PATCH
+
+/* Define if compiler provides atomic builtins */
+#undef SVN_HAS_ATOMIC_BUILTINS
+
+/* Is GNOME Keyring support enabled? */
+#undef SVN_HAVE_GNOME_KEYRING
+
+/* Is GPG Agent support enabled? */
+#undef SVN_HAVE_GPG_AGENT
+
+/* Is Mac OS KeyChain support enabled? */
+#undef SVN_HAVE_KEYCHAIN_SERVICES
+
+/* Defined if KWallet support is enabled */
+#undef SVN_HAVE_KWALLET
+
+/* Defined if libmagic support is enabled */
+#undef SVN_HAVE_LIBMAGIC
+
+/* Is Mach-O low-level _dyld API available? */
+#undef SVN_HAVE_MACHO_ITERATE
+
+/* Is Mac OS property list API available? */
+#undef SVN_HAVE_MACOS_PLIST
+
+/* Defined if apr_memcache (standalone or in apr-util) is present */
+#undef SVN_HAVE_MEMCACHE
+
+/* Defined if Expat 1.0 or 1.1 was found */
+#undef SVN_HAVE_OLD_EXPAT
+
+/* Defined if Cyrus SASL v2 is present on the system */
+#undef SVN_HAVE_SASL
+
+/* Defined if support for Serf is enabled */
+#undef SVN_HAVE_SERF
+
+/* Defined if libsvn_client should link against libsvn_ra_local */
+#undef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
+
+/* Defined if libsvn_client should link against libsvn_ra_serf */
+#undef SVN_LIBSVN_CLIENT_LINKS_RA_SERF
+
+/* Defined if libsvn_client should link against libsvn_ra_svn */
+#undef SVN_LIBSVN_CLIENT_LINKS_RA_SVN
+
+/* Defined if libsvn_fs should link against libsvn_fs_base */
+#undef SVN_LIBSVN_FS_LINKS_FS_BASE
+
+/* Defined if libsvn_fs should link against libsvn_fs_fs */
+#undef SVN_LIBSVN_FS_LINKS_FS_FS
+
+/* Defined to be the path to the installed locale dirs */
+#undef SVN_LOCALE_DIR
+
+/* Defined to be the null device for the system */
+#undef SVN_NULL_DEVICE_NAME
+
+/* Defined to be the path separator used on your local filesystem */
+#undef SVN_PATH_LOCAL_SEPARATOR
+
+/* Subversion library major verson */
+#undef SVN_SOVERSION
+
+/* Defined if svn should use the amalgamated version of sqlite */
+#undef SVN_SQLITE_INLINE
+
+/* Defined if svn should try to load DSOs */
+#undef SVN_USE_DSO
+
+/* Define to empty if `const' does not conform to ANSI C. */
+#undef const
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+#undef size_t
+
+#ifdef SVN_WANT_BDB
+#define APU_WANT_DB
+@SVN_DB_HEADER@
+#endif
+
+
+
+/* Indicate to translators that string X should be translated. Do not look
+ up the translation at run time; just expand to X. This macro is suitable
+ for use where a constant string is required at compile time. */
+#define N_(x) x
+/* Indicate to translators that we have decided the string X should not be
+ translated. Expand to X. */
+#define U_(x) x
+#ifdef ENABLE_NLS
+#include <locale.h>
+#include <libintl.h>
+/* Indicate to translators that string X should be translated. At run time,
+ look up and return the translation of X. */
+#define _(x) dgettext(PACKAGE_NAME, x)
+/* Indicate to translators that strings X1 and X2 are singular and plural
+ forms of the same message, and should be translated. At run time, return
+ an appropriate translation depending on the number N. */
+#define Q_(x1, x2, n) dngettext(PACKAGE_NAME, x1, x2, n)
+#else
+#define _(x) (x)
+#define Q_(x1, x2, n) (((n) == 1) ? x1 : x2)
+#define gettext(x) (x)
+#define dgettext(domain, x) (x)
+#endif
+
diff --git a/contrib/subversion/subversion/svn_private_config.hw b/contrib/subversion/subversion/svn_private_config.hw
new file mode 100644
index 0000000..61517f9
--- /dev/null
+++ b/contrib/subversion/subversion/svn_private_config.hw
@@ -0,0 +1,110 @@
+/*
+ * svn_private_config.hw : Template for svn_private_config.h on Win32.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+
+#ifndef SVN_PRIVATE_CONFIG_HW
+#define SVN_PRIVATE_CONFIG_HW
+
+
+/* Define to a Windows-specific equivalent of config.guess output */
+#define SVN_BUILD_HOST "x86-microsoft-windows"
+
+#if defined(_M_X64)
+#define SVN_BUILD_TARGET "x64-microsoft-windows"
+#elif defined(_M_IA64)
+#define SVN_BUILD_TARGET "ia64-microsoft-windows"
+#elif defined( _M_IX86)
+#define SVN_BUILD_TARGET "x86-microsoft-windows"
+#else
+#error Unsupported build target.
+#endif
+
+/* The minimal version of Berkeley DB we want */
+#define SVN_FS_WANT_DB_MAJOR 4
+#define SVN_FS_WANT_DB_MINOR 0
+#define SVN_FS_WANT_DB_PATCH 14
+
+
+/* Path separator for local filesystem */
+#define SVN_PATH_LOCAL_SEPARATOR '\\'
+
+/* Name of system's null device */
+#define SVN_NULL_DEVICE_NAME "nul"
+
+/* Link fs fs library into the fs library */
+#define SVN_LIBSVN_FS_LINKS_FS_FS
+
+/* Link local repos access library to client */
+#define SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
+
+/* Link pipe repos access library to client */
+#define SVN_LIBSVN_CLIENT_LINKS_RA_SVN
+
+/* Defined to be the path to the installed binaries */
+#define SVN_BINDIR "/usr/local/bin"
+
+
+
+/* The default FS back-end type */
+#define DEFAULT_FS_TYPE "fsfs"
+
+/* The default HTTP library to use */
+#define DEFAULT_HTTP_LIBRARY "serf"
+
+/* Define to the Python/C API format character suitable for apr_int64_t */
+#if defined(_WIN64)
+#define SVN_APR_INT64_T_PYCFMT "l"
+#elif defined(_WIN32)
+#define SVN_APR_INT64_T_PYCFMT "L"
+#endif
+
+/* Setup gettext macros */
+#define N_(x) x
+#define U_(x) x
+#define PACKAGE_NAME "subversion"
+
+#ifdef ENABLE_NLS
+#define SVN_LOCALE_RELATIVE_PATH "../share/locale"
+#include <locale.h>
+#include <libintl.h>
+#define _(x) dgettext(PACKAGE_NAME, x)
+#define Q_(x1, x2, n) dngettext(PACKAGE_NAME, x1, x2, n)
+#define HAVE_BIND_TEXTDOMAIN_CODESET
+#else
+#define _(x) (x)
+#define Q_(x1, x2, n) (((n) == 1) ? x1 : x2)
+#define gettext(x) (x)
+#define dgettext(domain, x) (x)
+#endif
+
+#endif /* SVN_PRIVATE_CONFIG_HW */
+
+/* Inclusion of Berkeley DB header */
+#ifdef SVN_WANT_BDB
+#define APU_WANT_DB
+#include <apu_want.h>
+#endif
diff --git a/contrib/subversion/subversion/svnadmin/svnadmin.1 b/contrib/subversion/subversion/svnadmin/svnadmin.1
new file mode 100644
index 0000000..c613bb1
--- /dev/null
+++ b/contrib/subversion/subversion/svnadmin/svnadmin.1
@@ -0,0 +1,47 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svnadmin 1
+.SH NAME
+svnadmin \- Subversion repository administration tool
+.SH SYNOPSIS
+.TP
+\fBsvnadmin\fP \fIcommand\fP \fI/path/to/repos\fP [\fIoptions\fP] [\fIargs\fP]
+.SH OVERVIEW
+Subversion is a version control system, which allows you to keep old
+versions of files and directories (usually source code), keep a log of
+who, when, and why changes occurred, etc., like CVS, RCS or SCCS.
+\fBSubversion\fP keeps a single copy of the master sources. This copy
+is called the source ``repository''; it contains all the information
+to permit extracting previous versions of those files at any time.
+
+For more information about the Subversion project, visit
+http://subversion.apache.org.
+
+Documentation for Subversion and its tools, including detailed usage
+explanations of the \fBsvn\fP, \fBsvnadmin\fP, \fBsvnserve\fP and
+\fBsvnlook\fP programs, historical background, philosophical
+approaches and reasonings, etc., can be found at
+http://svnbook.red-bean.com/.
+
+Run `svnadmin help' to access the built-in tool documentation.
diff --git a/contrib/subversion/subversion/svnadmin/svnadmin.c b/contrib/subversion/subversion/svnadmin/svnadmin.c
new file mode 100644
index 0000000..65ff971
--- /dev/null
+++ b/contrib/subversion/subversion/svnadmin/svnadmin.c
@@ -0,0 +1,2380 @@
+/*
+ * svnadmin.c: Subversion server administration tool main file.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_file_io.h>
+#include <apr_signal.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_cmdline.h"
+#include "svn_error.h"
+#include "svn_opt.h"
+#include "svn_utf.h"
+#include "svn_subst.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_config.h"
+#include "svn_repos.h"
+#include "svn_cache_config.h"
+#include "svn_version.h"
+#include "svn_props.h"
+#include "svn_time.h"
+#include "svn_user.h"
+#include "svn_xml.h"
+
+#include "private/svn_opt_private.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* A flag to see if we've been cancelled by the client or not. */
+static volatile sig_atomic_t cancelled = FALSE;
+
+/* A signal handler to support cancellation. */
+static void
+signal_handler(int signum)
+{
+ apr_signal(signum, SIG_IGN);
+ cancelled = TRUE;
+}
+
+
+/* A helper to set up the cancellation signal handlers. */
+static void
+setup_cancellation_signals(void (*handler)(int signum))
+{
+ apr_signal(SIGINT, handler);
+#ifdef SIGBREAK
+ /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
+ apr_signal(SIGBREAK, handler);
+#endif
+#ifdef SIGHUP
+ apr_signal(SIGHUP, handler);
+#endif
+#ifdef SIGTERM
+ apr_signal(SIGTERM, handler);
+#endif
+}
+
+
+/* Our cancellation callback. */
+static svn_error_t *
+check_cancel(void *baton)
+{
+ if (cancelled)
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+/* Custom filesystem warning function. */
+static void
+warning_func(void *baton,
+ svn_error_t *err)
+{
+ if (! err)
+ return;
+ svn_handle_error2(err, stderr, FALSE, "svnadmin: ");
+}
+
+
+/* Helper to open a repository and set a warning func (so we don't
+ * SEGFAULT when libsvn_fs's default handler gets run). */
+static svn_error_t *
+open_repos(svn_repos_t **repos,
+ const char *path,
+ apr_pool_t *pool)
+{
+ /* construct FS configuration parameters: enable caches for r/o data */
+ apr_hash_t *fs_config = apr_hash_make(pool);
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1");
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1");
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2");
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
+ svn_uuid_generate(pool));
+
+ /* now, open the requested repository */
+ SVN_ERR(svn_repos_open2(repos, path, fs_config, pool));
+ svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL);
+ return SVN_NO_ERROR;
+}
+
+
+/* Version compatibility check */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_repos", svn_repos_version },
+ { "svn_fs", svn_fs_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+
+
+/** Subcommands. **/
+
+static svn_opt_subcommand_t
+ subcommand_crashtest,
+ subcommand_create,
+ subcommand_deltify,
+ subcommand_dump,
+ subcommand_freeze,
+ subcommand_help,
+ subcommand_hotcopy,
+ subcommand_load,
+ subcommand_list_dblogs,
+ subcommand_list_unused_dblogs,
+ subcommand_lock,
+ subcommand_lslocks,
+ subcommand_lstxns,
+ subcommand_pack,
+ subcommand_recover,
+ subcommand_rmlocks,
+ subcommand_rmtxns,
+ subcommand_setlog,
+ subcommand_setrevprop,
+ subcommand_setuuid,
+ subcommand_unlock,
+ subcommand_upgrade,
+ subcommand_verify;
+
+enum svnadmin__cmdline_options_t
+ {
+ svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID,
+ svnadmin__incremental,
+ svnadmin__deltas,
+ svnadmin__ignore_uuid,
+ svnadmin__force_uuid,
+ svnadmin__fs_type,
+ svnadmin__parent_dir,
+ svnadmin__bdb_txn_nosync,
+ svnadmin__bdb_log_keep,
+ svnadmin__config_dir,
+ svnadmin__bypass_hooks,
+ svnadmin__bypass_prop_validation,
+ svnadmin__use_pre_commit_hook,
+ svnadmin__use_post_commit_hook,
+ svnadmin__use_pre_revprop_change_hook,
+ svnadmin__use_post_revprop_change_hook,
+ svnadmin__clean_logs,
+ svnadmin__wait,
+ svnadmin__pre_1_4_compatible,
+ svnadmin__pre_1_5_compatible,
+ svnadmin__pre_1_6_compatible,
+ svnadmin__compatible_version
+ };
+
+/* Option codes and descriptions.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ */
+static const apr_getopt_option_t options_table[] =
+ {
+ {"help", 'h', 0,
+ N_("show help on a subcommand")},
+
+ {NULL, '?', 0,
+ N_("show help on a subcommand")},
+
+ {"version", svnadmin__version, 0,
+ N_("show program version information")},
+
+ {"revision", 'r', 1,
+ N_("specify revision number ARG (or X:Y range)")},
+
+ {"transaction", 't', 1,
+ N_("specify transaction name ARG")},
+
+ {"incremental", svnadmin__incremental, 0,
+ N_("dump or hotcopy incrementally")},
+
+ {"deltas", svnadmin__deltas, 0,
+ N_("use deltas in dump output")},
+
+ {"bypass-hooks", svnadmin__bypass_hooks, 0,
+ N_("bypass the repository hook system")},
+
+ {"bypass-prop-validation", svnadmin__bypass_prop_validation, 0,
+ N_("bypass property validation logic")},
+
+ {"quiet", 'q', 0,
+ N_("no progress (only errors) to stderr")},
+
+ {"ignore-uuid", svnadmin__ignore_uuid, 0,
+ N_("ignore any repos UUID found in the stream")},
+
+ {"force-uuid", svnadmin__force_uuid, 0,
+ N_("set repos UUID to that found in stream, if any")},
+
+ {"fs-type", svnadmin__fs_type, 1,
+ N_("type of repository: 'fsfs' (default) or 'bdb'")},
+
+ {"parent-dir", svnadmin__parent_dir, 1,
+ N_("load at specified directory in repository")},
+
+ {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0,
+ N_("disable fsync at transaction commit [Berkeley DB]")},
+
+ {"bdb-log-keep", svnadmin__bdb_log_keep, 0,
+ N_("disable automatic log file removal [Berkeley DB]")},
+
+ {"config-dir", svnadmin__config_dir, 1,
+ N_("read user configuration files from directory ARG")},
+
+ {"clean-logs", svnadmin__clean_logs, 0,
+ N_("remove redundant Berkeley DB log files\n"
+ " from source repository [Berkeley DB]")},
+
+ {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0,
+ N_("call pre-commit hook before committing revisions")},
+
+ {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0,
+ N_("call post-commit hook after committing revisions")},
+
+ {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook, 0,
+ N_("call hook before changing revision property")},
+
+ {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook, 0,
+ N_("call hook after changing revision property")},
+
+ {"wait", svnadmin__wait, 0,
+ N_("wait instead of exit if the repository is in\n"
+ " use by another process")},
+
+ {"pre-1.4-compatible", svnadmin__pre_1_4_compatible, 0,
+ N_("deprecated; see --compatible-version")},
+
+ {"pre-1.5-compatible", svnadmin__pre_1_5_compatible, 0,
+ N_("deprecated; see --compatible-version")},
+
+ {"pre-1.6-compatible", svnadmin__pre_1_6_compatible, 0,
+ N_("deprecated; see --compatible-version")},
+
+ {"memory-cache-size", 'M', 1,
+ N_("size of the extra in-memory cache in MB used to\n"
+ " minimize redundant operations. Default: 16.\n"
+ " [used for FSFS repositories only]")},
+
+ {"compatible-version", svnadmin__compatible_version, 1,
+ N_("use repository format compatible with Subversion\n"
+ " version ARG (\"1.5.5\", \"1.7\", etc.)")},
+
+ {"file", 'F', 1, N_("read repository paths from file ARG")},
+
+ {NULL}
+ };
+
+
+/* Array of available subcommands.
+ * The entire list must be terminated with an entry of nulls.
+ */
+static const svn_opt_subcommand_desc2_t cmd_table[] =
+{
+ {"crashtest", subcommand_crashtest, {0}, N_
+ ("usage: svnadmin crashtest REPOS_PATH\n\n"
+ "Open the repository at REPOS_PATH, then abort, thus simulating\n"
+ "a process that crashes while holding an open repository handle.\n"),
+ {0} },
+
+ {"create", subcommand_create, {0}, N_
+ ("usage: svnadmin create REPOS_PATH\n\n"
+ "Create a new, empty repository at REPOS_PATH.\n"),
+ {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep,
+ svnadmin__config_dir, svnadmin__fs_type, svnadmin__compatible_version,
+ svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible,
+ svnadmin__pre_1_6_compatible
+ } },
+
+ {"deltify", subcommand_deltify, {0}, N_
+ ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n"
+ "Run over the requested revision range, performing predecessor delti-\n"
+ "fication on the paths changed in those revisions. Deltification in\n"
+ "essence compresses the repository by only storing the differences or\n"
+ "delta from the preceding revision. If no revisions are specified,\n"
+ "this will simply deltify the HEAD revision.\n"),
+ {'r', 'q', 'M'} },
+
+ {"dump", subcommand_dump, {0}, N_
+ ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER] [--incremental]]\n\n"
+ "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
+ "portable format, sending feedback to stderr. Dump revisions\n"
+ "LOWER rev through UPPER rev. If no revisions are given, dump all\n"
+ "revision trees. If only LOWER is given, dump that one revision tree.\n"
+ "If --incremental is passed, the first revision dumped will describe\n"
+ "only the paths changed in that revision; otherwise it will describe\n"
+ "every path present in the repository as of that revision. (In either\n"
+ "case, the second and subsequent revisions, if any, describe only paths\n"
+ "changed in those revisions.)\n"),
+ {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M'} },
+
+ {"freeze", subcommand_freeze, {0}, N_
+ ("usage: 1. svnadmin freeze REPOS_PATH PROGRAM [ARG...]\n"
+ " 2. svnadmin freeze -F FILE PROGRAM [ARG...]\n\n"
+ "1. Run PROGRAM passing ARGS while holding a write-lock on REPOS_PATH.\n"
+ "\n"
+ "2. Like 1 except all repositories listed in FILE are locked. The file\n"
+ " format is repository paths separated by newlines. Repositories are\n"
+ " locked in the same order as they are listed in the file.\n"),
+ {'F'} },
+
+ {"help", subcommand_help, {"?", "h"}, N_
+ ("usage: svnadmin help [SUBCOMMAND...]\n\n"
+ "Describe the usage of this program or its subcommands.\n"),
+ {0} },
+
+ {"hotcopy", subcommand_hotcopy, {0}, N_
+ ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
+ "Make a hot copy of a repository.\n"
+ "If --incremental is passed, data which already exists at the destination\n"
+ "is not copied again. Incremental mode is implemented for FSFS repositories.\n"),
+ {svnadmin__clean_logs, svnadmin__incremental} },
+
+ {"list-dblogs", subcommand_list_dblogs, {0}, N_
+ ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
+ "List all Berkeley DB log files.\n\n"
+ "WARNING: Modifying or deleting logfiles which are still in use\n"
+ "will cause your repository to be corrupted.\n"),
+ {0} },
+
+ {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_
+ ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
+ "List unused Berkeley DB log files.\n\n"),
+ {0} },
+
+ {"load", subcommand_load, {0}, N_
+ ("usage: svnadmin load REPOS_PATH\n\n"
+ "Read a 'dumpfile'-formatted stream from stdin, committing\n"
+ "new revisions into the repository's filesystem. If the repository\n"
+ "was previously empty, its UUID will, by default, be changed to the\n"
+ "one specified in the stream. Progress feedback is sent to stdout.\n"
+ "If --revision is specified, limit the loaded revisions to only those\n"
+ "in the dump stream whose revision numbers match the specified range.\n"),
+ {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid,
+ svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
+ svnadmin__parent_dir, svnadmin__bypass_prop_validation, 'M'} },
+
+ {"lock", subcommand_lock, {0}, N_
+ ("usage: svnadmin lock REPOS_PATH PATH USERNAME COMMENT-FILE [TOKEN]\n\n"
+ "Lock PATH by USERNAME setting comments from COMMENT-FILE.\n"
+ "If provided, use TOKEN as lock token. Use --bypass-hooks to avoid\n"
+ "triggering the pre-lock and post-lock hook scripts.\n"),
+ {svnadmin__bypass_hooks} },
+
+ {"lslocks", subcommand_lslocks, {0}, N_
+ ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
+ "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
+ "if not provided, is the root of the repository).\n"),
+ {0} },
+
+ {"lstxns", subcommand_lstxns, {0}, N_
+ ("usage: svnadmin lstxns REPOS_PATH\n\n"
+ "Print the names of all uncommitted transactions.\n"),
+ {0} },
+
+ {"pack", subcommand_pack, {0}, N_
+ ("usage: svnadmin pack REPOS_PATH\n\n"
+ "Possibly compact the repository into a more efficient storage model.\n"
+ "This may not apply to all repositories, in which case, exit.\n"),
+ {'q'} },
+
+ {"recover", subcommand_recover, {0}, N_
+ ("usage: svnadmin recover REPOS_PATH\n\n"
+ "Run the recovery procedure on a repository. Do this if you've\n"
+ "been getting errors indicating that recovery ought to be run.\n"
+ "Berkeley DB recovery requires exclusive access and will\n"
+ "exit if the repository is in use by another process.\n"),
+ {svnadmin__wait} },
+
+ {"rmlocks", subcommand_rmlocks, {0}, N_
+ ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
+ "Unconditionally remove lock from each LOCKED_PATH.\n"),
+ {0} },
+
+ {"rmtxns", subcommand_rmtxns, {0}, N_
+ ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
+ "Delete the named transaction(s).\n"),
+ {'q'} },
+
+ {"setlog", subcommand_setlog, {0}, N_
+ ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
+ "Set the log-message on revision REVISION to the contents of FILE. Use\n"
+ "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
+ "(for example, if you do not want an email notification sent\n"
+ "from your post-revprop-change hook, or because the modification of\n"
+ "revision properties has not been enabled in the pre-revprop-change\n"
+ "hook).\n\n"
+ "NOTE: Revision properties are not versioned, so this command will\n"
+ "overwrite the previous log message.\n"),
+ {'r', svnadmin__bypass_hooks} },
+
+ {"setrevprop", subcommand_setrevprop, {0}, N_
+ ("usage: svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n\n"
+ "Set the property NAME on revision REVISION to the contents of FILE. Use\n"
+ "--use-pre-revprop-change-hook/--use-post-revprop-change-hook to trigger\n"
+ "the revision property-related hooks (for example, if you want an email\n"
+ "notification sent from your post-revprop-change hook).\n\n"
+ "NOTE: Revision properties are not versioned, so this command will\n"
+ "overwrite the previous value of the property.\n"),
+ {'r', svnadmin__use_pre_revprop_change_hook,
+ svnadmin__use_post_revprop_change_hook} },
+
+ {"setuuid", subcommand_setuuid, {0}, N_
+ ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
+ "Reset the repository UUID for the repository located at REPOS_PATH. If\n"
+ "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
+ "generate a brand new UUID for the repository.\n"),
+ {0} },
+
+ {"unlock", subcommand_unlock, {0}, N_
+ ("usage: svnadmin unlock REPOS_PATH LOCKED_PATH USERNAME TOKEN\n\n"
+ "Unlock LOCKED_PATH (as USERNAME) after verifying that the token\n"
+ "associated with the lock matches TOKEN. Use --bypass-hooks to avoid\n"
+ "triggering the pre-unlock and post-unlock hook scripts.\n"),
+ {svnadmin__bypass_hooks} },
+
+ {"upgrade", subcommand_upgrade, {0}, N_
+ ("usage: svnadmin upgrade REPOS_PATH\n\n"
+ "Upgrade the repository located at REPOS_PATH to the latest supported\n"
+ "schema version.\n\n"
+ "This functionality is provided as a convenience for repository\n"
+ "administrators who wish to make use of new Subversion functionality\n"
+ "without having to undertake a potentially costly full repository dump\n"
+ "and load operation. As such, the upgrade performs only the minimum\n"
+ "amount of work needed to accomplish this while still maintaining the\n"
+ "integrity of the repository. It does not guarantee the most optimized\n"
+ "repository state as a dump and subsequent load would.\n"),
+ {0} },
+
+ {"verify", subcommand_verify, {0}, N_
+ ("usage: svnadmin verify REPOS_PATH\n\n"
+ "Verify the data stored in the repository.\n"),
+ {'t', 'r', 'q', 'M'} },
+
+ { NULL, NULL, {0}, NULL, {0} }
+};
+
+
+/* Baton for passing option/argument state to a subcommand function. */
+struct svnadmin_opt_state
+{
+ const char *repository_path;
+ const char *fs_type; /* --fs-type */
+ svn_boolean_t pre_1_4_compatible; /* --pre-1.4-compatible */
+ svn_boolean_t pre_1_5_compatible; /* --pre-1.5-compatible */
+ svn_boolean_t pre_1_6_compatible; /* --pre-1.6-compatible */
+ svn_version_t *compatible_version; /* --compatible-version */
+ svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */
+ const char *txn_id; /* -t TXN */
+ svn_boolean_t help; /* --help or -? */
+ svn_boolean_t version; /* --version */
+ svn_boolean_t incremental; /* --incremental */
+ svn_boolean_t use_deltas; /* --deltas */
+ svn_boolean_t use_pre_commit_hook; /* --use-pre-commit-hook */
+ svn_boolean_t use_post_commit_hook; /* --use-post-commit-hook */
+ svn_boolean_t use_pre_revprop_change_hook; /* --use-pre-revprop-change-hook */
+ svn_boolean_t use_post_revprop_change_hook; /* --use-post-revprop-change-hook */
+ svn_boolean_t quiet; /* --quiet */
+ svn_boolean_t bdb_txn_nosync; /* --bdb-txn-nosync */
+ svn_boolean_t bdb_log_keep; /* --bdb-log-keep */
+ svn_boolean_t clean_logs; /* --clean-logs */
+ svn_boolean_t bypass_hooks; /* --bypass-hooks */
+ svn_boolean_t wait; /* --wait */
+ svn_boolean_t bypass_prop_validation; /* --bypass-prop-validation */
+ enum svn_repos_load_uuid uuid_action; /* --ignore-uuid,
+ --force-uuid */
+ apr_uint64_t memory_cache_size; /* --memory-cache-size M */
+ const char *parent_dir;
+ svn_stringbuf_t *filedata; /* --file */
+
+ const char *config_dir; /* Overriding Configuration Directory */
+};
+
+
+/* Set *REVNUM to the revision specified by REVISION (or to
+ SVN_INVALID_REVNUM if that has the type 'unspecified'),
+ possibly making use of the YOUNGEST revision number in REPOS. */
+static svn_error_t *
+get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
+ svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
+{
+ if (revision->kind == svn_opt_revision_number)
+ *revnum = revision->value.number;
+ else if (revision->kind == svn_opt_revision_head)
+ *revnum = youngest;
+ else if (revision->kind == svn_opt_revision_date)
+ SVN_ERR(svn_repos_dated_revision(revnum, repos, revision->value.date,
+ pool));
+ else if (revision->kind == svn_opt_revision_unspecified)
+ *revnum = SVN_INVALID_REVNUM;
+ else
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid revision specifier"));
+
+ if (*revnum > youngest)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Revisions must not be greater than the youngest revision (%ld)"),
+ youngest);
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *PATH to an internal-style, UTF8-encoded, local dirent path
+ allocated from POOL and parsed from raw command-line argument ARG. */
+static svn_error_t *
+target_arg_to_dirent(const char **dirent,
+ const char *arg,
+ apr_pool_t *pool)
+{
+ const char *path;
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&path, arg, pool));
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ "Path '%s' is not a local path", path);
+ *dirent = svn_dirent_internal_style(path, pool);
+ return SVN_NO_ERROR;
+}
+
+/* Parse the remaining command-line arguments from OS, returning them
+ in a new array *ARGS (allocated from POOL) and optionally verifying
+ that we got the expected number thereof. If MIN_EXPECTED is not
+ negative, return an error if the function would return fewer than
+ MIN_EXPECTED arguments. If MAX_EXPECTED is not negative, return an
+ error if the function would return more than MAX_EXPECTED
+ arguments.
+
+ As a special case, when MIN_EXPECTED and MAX_EXPECTED are both 0,
+ allow ARGS to be NULL. */
+static svn_error_t *
+parse_args(apr_array_header_t **args,
+ apr_getopt_t *os,
+ int min_expected,
+ int max_expected,
+ apr_pool_t *pool)
+{
+ int num_args = os ? (os->argc - os->ind) : 0;
+
+ if (min_expected || max_expected)
+ SVN_ERR_ASSERT(args);
+
+ if ((min_expected >= 0) && (num_args < min_expected))
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
+ "Not enough arguments");
+ if ((max_expected >= 0) && (num_args > max_expected))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
+ "Too many arguments");
+ if (args)
+ {
+ *args = apr_array_make(pool, num_args, sizeof(const char *));
+
+ if (num_args)
+ while (os->ind < os->argc)
+ APR_ARRAY_PUSH(*args, const char *) =
+ apr_pstrdup(pool, os->argv[os->ind++]);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ SVN_ERR_MALFUNCTION();
+
+ /* merely silence a compiler warning (this will never be executed) */
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ apr_hash_t *fs_config = apr_hash_make(pool);
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
+ (opt_state->bdb_txn_nosync ? "1" :"0"));
+
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
+ (opt_state->bdb_log_keep ? "0" :"1"));
+
+ if (opt_state->fs_type)
+ {
+ /* With 1.8 we are announcing that BDB is deprecated. No support
+ * has been removed and it will continue to work until some future
+ * date. The purpose here is to discourage people from creating
+ * new BDB repositories which they will need to dump/load into
+ * FSFS or some new FS type in the future. */
+ if (0 == strcmp(opt_state->fs_type, SVN_FS_TYPE_BDB))
+ {
+ SVN_ERR(svn_cmdline_fprintf(
+ stderr, pool,
+ _("%swarning:"
+ " The \"%s\" repository back-end is deprecated,"
+ " consider using \"%s\" instead.\n"),
+ "svnadmin: ", SVN_FS_TYPE_BDB, SVN_FS_TYPE_FSFS));
+ fflush(stderr);
+ }
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, opt_state->fs_type);
+ }
+
+ /* Prior to 1.8, we had explicit options to specify compatibility
+ with a handful of prior Subversion releases. */
+ if (opt_state->pre_1_4_compatible)
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
+ if (opt_state->pre_1_5_compatible)
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
+ if (opt_state->pre_1_6_compatible)
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
+
+ /* In 1.8, we figured out that we didn't have to keep extending this
+ madness indefinitely. */
+ if (opt_state->compatible_version)
+ {
+ if (! svn_version__at_least(opt_state->compatible_version, 1, 4, 0))
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
+ if (! svn_version__at_least(opt_state->compatible_version, 1, 5, 0))
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
+ if (! svn_version__at_least(opt_state->compatible_version, 1, 6, 0))
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
+ if (! svn_version__at_least(opt_state->compatible_version, 1, 8, 0))
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1");
+ }
+
+ SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
+ NULL, NULL, NULL, fs_config, pool));
+ svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
+ svn_revnum_t youngest, revision;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
+
+ /* Find the revision numbers at which to start and end. */
+ SVN_ERR(get_revnum(&start, &opt_state->start_revision,
+ youngest, repos, pool));
+ SVN_ERR(get_revnum(&end, &opt_state->end_revision,
+ youngest, repos, pool));
+
+ /* Fill in implied revisions if necessary. */
+ if (start == SVN_INVALID_REVNUM)
+ start = youngest;
+ if (end == SVN_INVALID_REVNUM)
+ end = start;
+
+ if (start > end)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("First revision cannot be higher than second"));
+
+ /* Loop over the requested revision range, performing the
+ predecessor deltification on paths changed in each. */
+ for (revision = start; revision <= end; revision++)
+ {
+ svn_pool_clear(subpool);
+ SVN_ERR(check_cancel(NULL));
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
+ revision));
+ SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
+ }
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+static void
+cmdline_stream_printf(svn_stream_t *stream,
+ apr_pool_t *pool,
+ const char *fmt,
+ ...)
+ __attribute__((format(printf, 3, 4)));
+
+static void
+cmdline_stream_printf(svn_stream_t *stream,
+ apr_pool_t *pool,
+ const char *fmt,
+ ...)
+{
+ const char *message;
+ va_list ap;
+ svn_error_t *err;
+ const char *out;
+
+ va_start(ap, fmt);
+ message = apr_pvsprintf(pool, fmt, ap);
+ va_end(ap);
+
+ err = svn_cmdline_cstring_from_utf8(&out, message, pool);
+
+ if (err)
+ {
+ svn_error_clear(err);
+ out = svn_cmdline_cstring_from_utf8_fuzzy(message, pool);
+ }
+
+ svn_error_clear(svn_stream_puts(stream, out));
+}
+
+
+/* Implementation of svn_repos_notify_func_t to wrap the output to a
+ response stream for svn_repos_dump_fs2() and svn_repos_verify_fs() */
+static void
+repos_notify_handler(void *baton,
+ const svn_repos_notify_t *notify,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *feedback_stream = baton;
+
+ switch (notify->action)
+ {
+ case svn_repos_notify_warning:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ "WARNING 0x%04x: %s\n", notify->warning,
+ notify->warning_str);
+ return;
+
+ case svn_repos_notify_dump_rev_end:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("* Dumped revision %ld.\n"),
+ notify->revision);
+ return;
+
+ case svn_repos_notify_verify_rev_end:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("* Verified revision %ld.\n"),
+ notify->revision);
+ return;
+
+ case svn_repos_notify_verify_rev_structure:
+ if (notify->revision == SVN_INVALID_REVNUM)
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("* Verifying repository metadata ...\n"));
+ else
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("* Verifying metadata at revision %ld ...\n"),
+ notify->revision);
+ return;
+
+ case svn_repos_notify_pack_shard_start:
+ {
+ const char *shardstr = apr_psprintf(scratch_pool,
+ "%" APR_INT64_T_FMT,
+ notify->shard);
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("Packing revisions in shard %s..."),
+ shardstr);
+ }
+ return;
+
+ case svn_repos_notify_pack_shard_end:
+ cmdline_stream_printf(feedback_stream, scratch_pool, _("done.\n"));
+ return;
+
+ case svn_repos_notify_pack_shard_start_revprop:
+ {
+ const char *shardstr = apr_psprintf(scratch_pool,
+ "%" APR_INT64_T_FMT,
+ notify->shard);
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("Packing revprops in shard %s..."),
+ shardstr);
+ }
+ return;
+
+ case svn_repos_notify_pack_shard_end_revprop:
+ cmdline_stream_printf(feedback_stream, scratch_pool, _("done.\n"));
+ return;
+
+ case svn_repos_notify_load_txn_committed:
+ if (notify->old_revision == SVN_INVALID_REVNUM)
+ {
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("\n------- Committed revision %ld >>>\n\n"),
+ notify->new_revision);
+ }
+ else
+ {
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("\n------- Committed new rev %ld"
+ " (loaded from original rev %ld"
+ ") >>>\n\n"), notify->new_revision,
+ notify->old_revision);
+ }
+ return;
+
+ case svn_repos_notify_load_node_start:
+ {
+ switch (notify->node_action)
+ {
+ case svn_node_action_change:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _(" * editing path : %s ..."),
+ notify->path);
+ break;
+
+ case svn_node_action_delete:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _(" * deleting path : %s ..."),
+ notify->path);
+ break;
+
+ case svn_node_action_add:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _(" * adding path : %s ..."),
+ notify->path);
+ break;
+
+ case svn_node_action_replace:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _(" * replacing path : %s ..."),
+ notify->path);
+ break;
+
+ }
+ }
+ return;
+
+ case svn_repos_notify_load_node_done:
+ cmdline_stream_printf(feedback_stream, scratch_pool, _(" done.\n"));
+ return;
+
+ case svn_repos_notify_load_copied_node:
+ cmdline_stream_printf(feedback_stream, scratch_pool, "COPIED...");
+ return;
+
+ case svn_repos_notify_load_txn_start:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("<<< Started new transaction, based on "
+ "original revision %ld\n"),
+ notify->old_revision);
+ return;
+
+ case svn_repos_notify_load_skipped_rev:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("<<< Skipped original revision %ld\n"),
+ notify->old_revision);
+ return;
+
+ case svn_repos_notify_load_normalized_mergeinfo:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _(" removing '\\r' from %s ..."),
+ SVN_PROP_MERGEINFO);
+ return;
+
+ case svn_repos_notify_mutex_acquired:
+ /* Enable cancellation signal handlers. */
+ setup_cancellation_signals(signal_handler);
+ return;
+
+ case svn_repos_notify_recover_start:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("Repository lock acquired.\n"
+ "Please wait; recovering the"
+ " repository may take some time...\n"));
+ return;
+
+ case svn_repos_notify_upgrade_start:
+ cmdline_stream_printf(feedback_stream, scratch_pool,
+ _("Repository lock acquired.\n"
+ "Please wait; upgrading the"
+ " repository may take some time...\n"));
+ return;
+
+ default:
+ return;
+ }
+}
+
+
+/* Baton for recode_write(). */
+struct recode_write_baton
+{
+ apr_pool_t *pool;
+ FILE *out;
+};
+
+/* This implements the 'svn_write_fn_t' interface.
+
+ Write DATA to ((struct recode_write_baton *) BATON)->out, in the
+ console encoding, using svn_cmdline_fprintf(). DATA is a
+ UTF8-encoded C string, therefore ignore LEN.
+
+ ### This recoding mechanism might want to be abstracted into
+ ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
+static svn_error_t *recode_write(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct recode_write_baton *rwb = baton;
+ svn_pool_clear(rwb->pool);
+ return svn_cmdline_fputs(data, rwb->out, rwb->pool);
+}
+
+/* Create a stream, to write to STD_STREAM, that uses recode_write()
+ to perform UTF-8 to console encoding translation. */
+static svn_stream_t *
+recode_stream_create(FILE *std_stream, apr_pool_t *pool)
+{
+ struct recode_write_baton *std_stream_rwb =
+ apr_palloc(pool, sizeof(struct recode_write_baton));
+
+ svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
+ std_stream_rwb->pool = svn_pool_create(pool);
+ std_stream_rwb->out = std_stream;
+ svn_stream_set_write(rw_stream, recode_write);
+ return rw_stream;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_stream_t *stdout_stream;
+ svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
+ svn_revnum_t youngest;
+ svn_stream_t *progress_stream = NULL;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
+
+ /* Find the revision numbers at which to start and end. */
+ SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
+ youngest, repos, pool));
+ SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
+ youngest, repos, pool));
+
+ /* Fill in implied revisions if necessary. */
+ if (lower == SVN_INVALID_REVNUM)
+ {
+ lower = 0;
+ upper = youngest;
+ }
+ else if (upper == SVN_INVALID_REVNUM)
+ {
+ upper = lower;
+ }
+
+ if (lower > upper)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("First revision cannot be higher than second"));
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ /* Progress feedback goes to STDERR, unless they asked to suppress it. */
+ if (! opt_state->quiet)
+ progress_stream = recode_stream_create(stderr, pool);
+
+ SVN_ERR(svn_repos_dump_fs3(repos, stdout_stream, lower, upper,
+ opt_state->incremental, opt_state->use_deltas,
+ !opt_state->quiet ? repos_notify_handler : NULL,
+ progress_stream, check_cancel, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+struct freeze_baton_t {
+ const char *command;
+ const char **args;
+ int status;
+};
+
+/* Implements svn_repos_freeze_func_t */
+static svn_error_t *
+freeze_body(void *baton,
+ apr_pool_t *pool)
+{
+ struct freeze_baton_t *b = baton;
+ apr_status_t apr_err;
+ apr_file_t *infile, *outfile, *errfile;
+
+ apr_err = apr_file_open_stdin(&infile, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stdin");
+ apr_err = apr_file_open_stdout(&outfile, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stdout");
+ apr_err = apr_file_open_stderr(&errfile, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stderr");
+
+ SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
+ NULL, TRUE,
+ infile, outfile, errfile, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *paths;
+ apr_array_header_t *args;
+ int i;
+ struct freeze_baton_t b;
+
+ SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
+
+ if (!args->nelts)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
+ _("No program provided"));
+
+ if (!opt_state->filedata)
+ {
+ /* One repository on the command line. */
+ paths = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
+ }
+ else
+ {
+ /* All repositories in filedata. */
+ paths = svn_cstring_split(opt_state->filedata->data, "\n", FALSE, pool);
+ }
+
+ b.command = APR_ARRAY_IDX(args, 0, const char *);
+ b.args = apr_palloc(pool, sizeof(char *) * args->nelts + 1);
+ for (i = 0; i < args->nelts; ++i)
+ b.args[i] = APR_ARRAY_IDX(args, i, const char *);
+ b.args[args->nelts] = NULL;
+
+ SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
+
+ /* Make any non-zero status visible to the user. */
+ if (b.status)
+ exit(b.status);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ const char *header =
+ _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
+ "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
+ "Type 'svnadmin --version' to see the program version and FS modules.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ const char *fs_desc_start
+ = _("The following repository back-end (FS) modules are available:\n\n");
+
+ svn_stringbuf_t *version_footer;
+
+ version_footer = svn_stringbuf_create(fs_desc_start, pool);
+ SVN_ERR(svn_fs_print_modules(version_footer, pool));
+
+ SVN_ERR(svn_opt_print_help4(os, "svnadmin",
+ opt_state ? opt_state->version : FALSE,
+ opt_state ? opt_state->quiet : FALSE,
+ /*###opt_state ? opt_state->verbose :*/ FALSE,
+ version_footer->data,
+ header, cmd_table, options_table, NULL, NULL,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *REVNUM to the revision number of a numeric REV, or to
+ SVN_INVALID_REVNUM if REV is unspecified. */
+static svn_error_t *
+optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
+{
+ if (opt_rev->kind == svn_opt_revision_number)
+ {
+ *revnum = opt_rev->value.number;
+ if (! SVN_IS_VALID_REVNUM(*revnum))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid revision number (%ld) specified"),
+ *revnum);
+ }
+ else if (opt_rev->kind == svn_opt_revision_unspecified)
+ {
+ *revnum = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Non-numeric revision specified"));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ svn_error_t *err;
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
+ svn_stream_t *stdin_stream, *stdout_stream = NULL;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ /* Find the revision numbers at which to start and end. We only
+ support a limited set of revision kinds: number and unspecified. */
+ SVN_ERR(optrev_to_revnum(&lower, &opt_state->start_revision));
+ SVN_ERR(optrev_to_revnum(&upper, &opt_state->end_revision));
+
+ /* Fill in implied revisions if necessary. */
+ if ((upper == SVN_INVALID_REVNUM) && (lower != SVN_INVALID_REVNUM))
+ {
+ upper = lower;
+ }
+ else if ((upper != SVN_INVALID_REVNUM) && (lower == SVN_INVALID_REVNUM))
+ {
+ lower = upper;
+ }
+
+ /* Ensure correct range ordering. */
+ if (lower > upper)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("First revision cannot be higher than second"));
+ }
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+
+ /* Read the stream from STDIN. Users can redirect a file. */
+ SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool));
+
+ /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
+ if (! opt_state->quiet)
+ stdout_stream = recode_stream_create(stdout, pool);
+
+ err = svn_repos_load_fs4(repos, stdin_stream, lower, upper,
+ opt_state->uuid_action, opt_state->parent_dir,
+ opt_state->use_pre_commit_hook,
+ opt_state->use_post_commit_hook,
+ !opt_state->bypass_prop_validation,
+ opt_state->quiet ? NULL : repos_notify_handler,
+ stdout_stream, check_cancel, NULL, pool);
+ if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
+ return svn_error_quick_wrap(err,
+ _("Invalid property value found in "
+ "dumpstream; consider repairing the source "
+ "or using --bypass-prop-validation while "
+ "loading."));
+ return err;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ apr_array_header_t *txns;
+ int i;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
+
+ /* Loop, printing revisions. */
+ for (i = 0; i < txns->nelts; i++)
+ {
+ SVN_ERR(svn_cmdline_printf(pool, "%s\n",
+ APR_ARRAY_IDX(txns, i, const char *)));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ svn_revnum_t youngest_rev;
+ svn_repos_t *repos;
+ svn_error_t *err;
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_stream_t *stdout_stream;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ /* Restore default signal handlers until after we have acquired the
+ * exclusive lock so that the user interrupt before we actually
+ * touch the repository. */
+ setup_cancellation_signals(SIG_DFL);
+
+ err = svn_repos_recover4(opt_state->repository_path, TRUE,
+ repos_notify_handler, stdout_stream,
+ check_cancel, NULL, pool);
+ if (err)
+ {
+ if (! APR_STATUS_IS_EAGAIN(err->apr_err))
+ return err;
+ svn_error_clear(err);
+ if (! opt_state->wait)
+ return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
+ _("Failed to get exclusive repository "
+ "access; perhaps another process\n"
+ "such as httpd, svnserve or svn "
+ "has it open?"));
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Waiting on repository lock; perhaps"
+ " another process has it open?\n")));
+ SVN_ERR(svn_cmdline_fflush(stdout));
+ SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
+ repos_notify_handler, stdout_stream,
+ check_cancel, NULL, pool));
+ }
+
+ SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
+
+ /* Since db transactions may have been replayed, it's nice to tell
+ people what the latest revision is. It also proves that the
+ recovery actually worked. */
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
+ SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
+ youngest_rev));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
+ apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *logfiles;
+ int i;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(svn_repos_db_logfiles(&logfiles,
+ opt_state->repository_path,
+ only_unused,
+ pool));
+
+ /* Loop, printing log files. We append the log paths to the
+ repository path, making sure to return everything to the native
+ style before printing. */
+ for (i = 0; i < logfiles->nelts; i++)
+ {
+ const char *log_utf8;
+ log_utf8 = svn_dirent_join(opt_state->repository_path,
+ APR_ARRAY_IDX(logfiles, i, const char *),
+ pool);
+ log_utf8 = svn_dirent_local_style(log_utf8, pool);
+ SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ SVN_ERR(list_dblogs(os, baton, FALSE, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(list_dblogs(os, baton, TRUE, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_fs_txn_t *txn;
+ apr_array_header_t *args;
+ int i;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+
+ /* All the rest of the arguments are transaction names. */
+ for (i = 0; i < args->nelts; i++)
+ {
+ const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
+ const char *txn_name_utf8;
+ svn_error_t *err;
+
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, subpool));
+
+ /* Try to open the txn. If that succeeds, try to abort it. */
+ err = svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool);
+ if (! err)
+ err = svn_fs_abort_txn(txn, subpool);
+
+ /* If either the open or the abort of the txn fails because that
+ transaction is dead, just try to purge the thing. Else,
+ there was either an error worth reporting, or not error at
+ all. */
+ if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
+ {
+ svn_error_clear(err);
+ err = svn_fs_purge_txn(fs, txn_name_utf8, subpool);
+ }
+
+ /* If we had a real from the txn open, abort, or purge, we clear
+ that error and just report to the user that we had an issue
+ with this particular txn. */
+ if (err)
+ {
+ svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
+ svn_error_clear(err);
+ }
+ else if (! opt_state->quiet)
+ {
+ SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
+ txn_name));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* A helper for the 'setrevprop' and 'setlog' commands. Expects
+ OPT_STATE->use_pre_revprop_change_hook and
+ OPT_STATE->use_post_revprop_change_hook to be set appropriately. */
+static svn_error_t *
+set_revprop(const char *prop_name, const char *filename,
+ struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+ svn_string_t *prop_value = svn_string_create_empty(pool);
+ svn_stringbuf_t *file_contents;
+
+ SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
+
+ prop_value->data = file_contents->data;
+ prop_value->len = file_contents->len;
+
+ SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
+ NULL, FALSE, pool, pool));
+
+ /* Open the filesystem */
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+
+ /* If we are bypassing the hooks system, we just hit the filesystem
+ directly. */
+ SVN_ERR(svn_repos_fs_change_rev_prop4(
+ repos, opt_state->start_revision.value.number,
+ NULL, prop_name, NULL, prop_value,
+ opt_state->use_pre_revprop_change_hook,
+ opt_state->use_post_revprop_change_hook,
+ NULL, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *args;
+ const char *prop_name, *filename;
+
+ /* Expect two more arguments: NAME FILE */
+ SVN_ERR(parse_args(&args, os, 2, 2, pool));
+ prop_name = APR_ARRAY_IDX(args, 0, const char *);
+ filename = APR_ARRAY_IDX(args, 1, const char *);
+ SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
+
+ if (opt_state->start_revision.kind != svn_opt_revision_number)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Missing revision"));
+ else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Only one revision allowed"));
+
+ return set_revprop(prop_name, filename, opt_state, pool);
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *args;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ const char *uuid = NULL;
+
+ /* Expect zero or one more arguments: [UUID] */
+ SVN_ERR(parse_args(&args, os, 0, 1, pool));
+ if (args->nelts == 1)
+ uuid = APR_ARRAY_IDX(args, 0, const char *);
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ return svn_fs_set_uuid(fs, uuid, pool);
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *args;
+ const char *filename;
+
+ /* Expect one more argument: FILE */
+ SVN_ERR(parse_args(&args, os, 1, 1, pool));
+ filename = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
+
+ if (opt_state->start_revision.kind != svn_opt_revision_number)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Missing revision"));
+ else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Only one revision allowed"));
+
+ /* set_revprop() responds only to pre-/post-revprop-change opts. */
+ if (!opt_state->bypass_hooks)
+ {
+ opt_state->use_pre_revprop_change_hook = TRUE;
+ opt_state->use_post_revprop_change_hook = TRUE;
+ }
+
+ return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
+}
+
+
+/* This implements 'svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_stream_t *progress_stream = NULL;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+
+ /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
+ if (! opt_state->quiet)
+ progress_stream = recode_stream_create(stdout, pool);
+
+ return svn_error_trace(
+ svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
+ progress_stream, check_cancel, NULL, pool));
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_revnum_t youngest, lower, upper;
+ svn_stream_t *progress_stream = NULL;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ if (opt_state->txn_id
+ && (opt_state->start_revision.kind != svn_opt_revision_unspecified
+ || opt_state->end_revision.kind != svn_opt_revision_unspecified))
+ {
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--revision (-r) and --transaction (-t) "
+ "are mutually exclusive"));
+ }
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
+
+ /* Usage 2. */
+ if (opt_state->txn_id)
+ {
+ svn_fs_txn_t *txn;
+ svn_fs_root_t *root;
+
+ SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
+ SVN_ERR(svn_fs_txn_root(&root, txn, pool));
+ SVN_ERR(svn_fs_verify_root(root, pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ /* Usage 1. */
+ ;
+
+ /* Find the revision numbers at which to start and end. */
+ SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
+ youngest, repos, pool));
+ SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
+ youngest, repos, pool));
+
+ if (upper == SVN_INVALID_REVNUM)
+ {
+ upper = lower;
+ }
+
+ if (! opt_state->quiet)
+ progress_stream = recode_stream_create(stderr, pool);
+
+ return svn_repos_verify_fs2(repos, lower, upper,
+ !opt_state->quiet
+ ? repos_notify_handler : NULL,
+ progress_stream, check_cancel, NULL, pool);
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+svn_error_t *
+subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *targets;
+ const char *new_repos_path;
+
+ /* Expect one more argument: NEW_REPOS_PATH */
+ SVN_ERR(parse_args(&targets, os, 1, 1, pool));
+ new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
+ SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
+
+ return svn_repos_hotcopy2(opt_state->repository_path, new_repos_path,
+ opt_state->clean_logs, opt_state->incremental,
+ check_cancel, NULL, pool);
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_fs_access_t *access;
+ apr_array_header_t *args;
+ const char *username;
+ const char *lock_path;
+ const char *comment_file_name;
+ svn_stringbuf_t *file_contents;
+ const char *lock_path_utf8;
+ svn_lock_t *lock;
+ const char *lock_token = NULL;
+
+ /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
+ SVN_ERR(parse_args(&args, os, 3, 4, pool));
+ lock_path = APR_ARRAY_IDX(args, 0, const char *);
+ username = APR_ARRAY_IDX(args, 1, const char *);
+ comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
+
+ /* Expect one more optional argument: TOKEN */
+ if (args->nelts == 4)
+ lock_token = APR_ARRAY_IDX(args, 3, const char *);
+
+ SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+
+ /* Create an access context describing the user. */
+ SVN_ERR(svn_fs_create_access(&access, username, pool));
+
+ /* Attach the access context to the filesystem. */
+ SVN_ERR(svn_fs_set_access(fs, access));
+
+ SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
+
+ if (opt_state->bypass_hooks)
+ SVN_ERR(svn_fs_lock(&lock, fs, lock_path_utf8,
+ lock_token,
+ file_contents->data, /* comment */
+ 0, /* is_dav_comment */
+ 0, /* no expiration time. */
+ SVN_INVALID_REVNUM,
+ FALSE, pool));
+ else
+ SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path_utf8,
+ lock_token,
+ file_contents->data, /* comment */
+ 0, /* is_dav_comment */
+ 0, /* no expiration time. */
+ SVN_INVALID_REVNUM,
+ FALSE, pool));
+
+ SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
+ lock_path, username));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *targets;
+ svn_repos_t *repos;
+ const char *fs_path = "/";
+ apr_hash_t *locks;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_opt__args_to_target_array(&targets, os,
+ apr_array_make(pool, 0,
+ sizeof(const char *)),
+ pool));
+ if (targets->nelts > 1)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
+ _("Too many arguments given"));
+ if (targets->nelts)
+ fs_path = APR_ARRAY_IDX(targets, 0, const char *);
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+
+ /* Fetch all locks on or below the root directory. */
+ SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
+ NULL, NULL, pool));
+
+ for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
+ {
+ const char *cr_date, *exp_date = "";
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_lock_t *lock = svn__apr_hash_index_val(hi);
+ int comment_lines = 0;
+
+ cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
+
+ if (lock->expiration_date)
+ exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
+
+ if (lock->comment)
+ comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
+
+ SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), path));
+ SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
+ SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
+ SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
+ SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
+ SVN_ERR(svn_cmdline_printf(pool,
+ Q_("Comment (%i line):\n%s\n\n",
+ "Comment (%i lines):\n%s\n\n",
+ comment_lines),
+ comment_lines,
+ lock->comment ? lock->comment : ""));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+static svn_error_t *
+subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_fs_access_t *access;
+ svn_error_t *err;
+ apr_array_header_t *args;
+ int i;
+ const char *username;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+
+ /* svn_fs_unlock() demands that some username be associated with the
+ filesystem, so just use the UID of the person running 'svnadmin'.*/
+ username = svn_user_get_name(pool);
+ if (! username)
+ username = "administrator";
+
+ /* Create an access context describing the current user. */
+ SVN_ERR(svn_fs_create_access(&access, username, pool));
+
+ /* Attach the access context to the filesystem. */
+ SVN_ERR(svn_fs_set_access(fs, access));
+
+ /* Parse out any options. */
+ SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
+
+ /* Our usage requires at least one FS path. */
+ if (args->nelts == 0)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
+ _("No paths to unlock provided"));
+
+ /* All the rest of the arguments are paths from which to remove locks. */
+ for (i = 0; i < args->nelts; i++)
+ {
+ const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
+ const char *lock_path_utf8;
+ svn_lock_t *lock;
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, subpool));
+
+ /* Fetch the path's svn_lock_t. */
+ err = svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool);
+ if (err)
+ goto move_on;
+ if (! lock)
+ {
+ SVN_ERR(svn_cmdline_printf(subpool,
+ _("Path '%s' isn't locked.\n"),
+ lock_path));
+ continue;
+ }
+
+ /* Now forcibly destroy the lock. */
+ err = svn_fs_unlock(fs, lock_path_utf8,
+ lock->token, 1 /* force */, subpool);
+ if (err)
+ goto move_on;
+
+ SVN_ERR(svn_cmdline_printf(subpool,
+ _("Removed lock on '%s'.\n"), lock->path));
+
+ move_on:
+ if (err)
+ {
+ /* Print the error, but move on to the next lock. */
+ svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
+ svn_error_clear(err);
+ }
+
+ svn_pool_clear(subpool);
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_fs_access_t *access;
+ apr_array_header_t *args;
+ const char *username;
+ const char *lock_path;
+ const char *lock_path_utf8;
+ const char *lock_token = NULL;
+
+ /* Expect three more arguments: PATH USERNAME TOKEN */
+ SVN_ERR(parse_args(&args, os, 3, 3, pool));
+ lock_path = APR_ARRAY_IDX(args, 0, const char *);
+ username = APR_ARRAY_IDX(args, 1, const char *);
+ lock_token = APR_ARRAY_IDX(args, 2, const char *);
+
+ /* Open the repos/FS, and associate an access context containing
+ USERNAME. */
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ SVN_ERR(svn_fs_create_access(&access, username, pool));
+ SVN_ERR(svn_fs_set_access(fs, access));
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
+ if (opt_state->bypass_hooks)
+ SVN_ERR(svn_fs_unlock(fs, lock_path_utf8, lock_token,
+ FALSE, pool));
+ else
+ SVN_ERR(svn_repos_fs_unlock(repos, lock_path_utf8, lock_token,
+ FALSE, pool));
+
+ SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
+ lock_path, username));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ svn_error_t *err;
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_stream_t *stdout_stream;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ /* Restore default signal handlers. */
+ setup_cancellation_signals(SIG_DFL);
+
+ err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
+ repos_notify_handler, stdout_stream, pool);
+ if (err)
+ {
+ if (APR_STATUS_IS_EAGAIN(err->apr_err))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ if (! opt_state->wait)
+ return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
+ _("Failed to get exclusive repository "
+ "access; perhaps another process\n"
+ "such as httpd, svnserve or svn "
+ "has it open?"));
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Waiting on repository lock; perhaps"
+ " another process has it open?\n")));
+ SVN_ERR(svn_cmdline_fflush(stdout));
+ SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
+ repos_notify_handler, stdout_stream,
+ pool));
+ }
+ else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
+ {
+ return svn_error_quick_wrap(err,
+ _("Upgrade of this repository's underlying versioned "
+ "filesystem is not supported; consider "
+ "dumping and loading the data elsewhere"));
+ }
+ else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
+ {
+ return svn_error_quick_wrap(err,
+ _("Upgrade of this repository is not supported; consider "
+ "dumping and loading the data elsewhere"));
+ }
+ }
+ SVN_ERR(err);
+
+ SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
+ return SVN_NO_ERROR;
+}
+
+
+
+/** Main. **/
+
+/* Report and clear the error ERR, and return EXIT_FAILURE. */
+#define EXIT_ERROR(err) \
+ svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ")
+
+/* A redefinition of the public SVN_INT_ERR macro, that suppresses the
+ * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR, amd with the
+ * program name 'svnadmin' instead of 'svn'. */
+#undef SVN_INT_ERR
+#define SVN_INT_ERR(expr) \
+ do { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ return EXIT_ERROR(svn_err__temp); \
+ } while (0)
+
+static int
+sub_main(int argc, const char *argv[], apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_status_t apr_err;
+
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+ struct svnadmin_opt_state opt_state = { 0 };
+ apr_getopt_t *os;
+ int opt_id;
+ apr_array_header_t *received_opts;
+ int i;
+ svn_boolean_t dash_F_arg = FALSE;
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ /* Check library versions */
+ SVN_INT_ERR(check_lib_versions());
+
+ /* Initialize the FS library. */
+ SVN_INT_ERR(svn_fs_initialize(pool));
+
+ if (argc <= 1)
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+
+ /* Initialize opt_state. */
+ opt_state.start_revision.kind = svn_opt_revision_unspecified;
+ opt_state.end_revision.kind = svn_opt_revision_unspecified;
+ opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
+
+ /* Parse options. */
+ SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
+
+ os->interleave = 1;
+
+ while (1)
+ {
+ const char *opt_arg;
+ const char *utf8_opt_arg;
+
+ /* Parse the next option. */
+ apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
+ if (APR_STATUS_IS_EOF(apr_err))
+ break;
+ else if (apr_err)
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+
+ /* Stash the option code in an array before parsing it. */
+ APR_ARRAY_PUSH(received_opts, int) = opt_id;
+
+ switch (opt_id) {
+ case 'r':
+ {
+ if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Multiple revision arguments encountered; "
+ "try '-r N:M' instead of '-r N -r M'"));
+ return EXIT_ERROR(err);
+ }
+ if (svn_opt_parse_revision(&(opt_state.start_revision),
+ &(opt_state.end_revision),
+ opt_arg, pool) != 0)
+ {
+ err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg,
+ pool);
+
+ if (! err)
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error in revision argument '%s'"),
+ utf8_opt_arg);
+ return EXIT_ERROR(err);
+ }
+ }
+ break;
+ case 't':
+ opt_state.txn_id = opt_arg;
+ break;
+
+ case 'q':
+ opt_state.quiet = TRUE;
+ break;
+ case 'h':
+ case '?':
+ opt_state.help = TRUE;
+ break;
+ case 'M':
+ opt_state.memory_cache_size
+ = 0x100000 * apr_strtoi64(opt_arg, NULL, 0);
+ break;
+ case 'F':
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_stringbuf_from_file2(&(opt_state.filedata),
+ utf8_opt_arg, pool));
+ dash_F_arg = TRUE;
+ case svnadmin__version:
+ opt_state.version = TRUE;
+ break;
+ case svnadmin__incremental:
+ opt_state.incremental = TRUE;
+ break;
+ case svnadmin__deltas:
+ opt_state.use_deltas = TRUE;
+ break;
+ case svnadmin__ignore_uuid:
+ opt_state.uuid_action = svn_repos_load_uuid_ignore;
+ break;
+ case svnadmin__force_uuid:
+ opt_state.uuid_action = svn_repos_load_uuid_force;
+ break;
+ case svnadmin__pre_1_4_compatible:
+ opt_state.pre_1_4_compatible = TRUE;
+ break;
+ case svnadmin__pre_1_5_compatible:
+ opt_state.pre_1_5_compatible = TRUE;
+ break;
+ case svnadmin__pre_1_6_compatible:
+ opt_state.pre_1_6_compatible = TRUE;
+ break;
+ case svnadmin__compatible_version:
+ {
+ svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
+ SVN_VER_PATCH, NULL };
+ svn_version_t *compatible_version;
+
+ /* Parse the version string which carries our target
+ compatibility. */
+ SVN_INT_ERR(svn_version__parse_version_string(&compatible_version,
+ opt_arg, pool));
+
+ /* We can't create repository with a version older than 1.0.0. */
+ if (! svn_version__at_least(compatible_version, 1, 0, 0))
+ {
+ err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot create pre-1.0-compatible "
+ "repositories"));
+ return EXIT_ERROR(err);
+ }
+
+ /* We can't create repository with a version newer than what
+ the running version of Subversion supports. */
+ if (! svn_version__at_least(&latest,
+ compatible_version->major,
+ compatible_version->minor,
+ compatible_version->patch))
+ {
+ err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot guarantee compatibility "
+ "beyond the current running version "
+ "(%s)"),
+ SVN_VER_NUM );
+ return EXIT_ERROR(err);
+ }
+
+ opt_state.compatible_version = compatible_version;
+ }
+ break;
+ case svnadmin__fs_type:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
+ break;
+ case svnadmin__parent_dir:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
+ pool));
+ opt_state.parent_dir
+ = svn_dirent_internal_style(opt_state.parent_dir, pool);
+ break;
+ case svnadmin__use_pre_commit_hook:
+ opt_state.use_pre_commit_hook = TRUE;
+ break;
+ case svnadmin__use_post_commit_hook:
+ opt_state.use_post_commit_hook = TRUE;
+ break;
+ case svnadmin__use_pre_revprop_change_hook:
+ opt_state.use_pre_revprop_change_hook = TRUE;
+ break;
+ case svnadmin__use_post_revprop_change_hook:
+ opt_state.use_post_revprop_change_hook = TRUE;
+ break;
+ case svnadmin__bdb_txn_nosync:
+ opt_state.bdb_txn_nosync = TRUE;
+ break;
+ case svnadmin__bdb_log_keep:
+ opt_state.bdb_log_keep = TRUE;
+ break;
+ case svnadmin__bypass_hooks:
+ opt_state.bypass_hooks = TRUE;
+ break;
+ case svnadmin__bypass_prop_validation:
+ opt_state.bypass_prop_validation = TRUE;
+ break;
+ case svnadmin__clean_logs:
+ opt_state.clean_logs = TRUE;
+ break;
+ case svnadmin__config_dir:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ opt_state.config_dir =
+ apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
+ break;
+ case svnadmin__wait:
+ opt_state.wait = TRUE;
+ break;
+ default:
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+ } /* close `switch' */
+ } /* close `while' */
+
+ /* If the user asked for help, then the rest of the arguments are
+ the names of subcommands to get help on (if any), or else they're
+ just typos/mistakes. Whatever the case, the subcommand to
+ actually run is subcommand_help(). */
+ if (opt_state.help)
+ subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
+
+ /* If we're not running the `help' subcommand, then look for a
+ subcommand in the first argument. */
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_state.version)
+ {
+ /* Use the "help" subcommand to handle the "--version" option. */
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+ { "--version", subcommand_help, {0}, "",
+ {svnadmin__version, /* must accept its own option */
+ 'q', /* --quiet */
+ } };
+
+ subcommand = &pseudo_cmd;
+ }
+ else
+ {
+ svn_error_clear(svn_cmdline_fprintf(stderr, pool,
+ _("subcommand argument required\n")));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ const char *first_arg = os->argv[os->ind++];
+ subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
+ if (subcommand == NULL)
+ {
+ const char *first_arg_utf8;
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
+ first_arg, pool));
+ svn_error_clear(
+ svn_cmdline_fprintf(stderr, pool,
+ _("Unknown subcommand: '%s'\n"),
+ first_arg_utf8));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ /* Every subcommand except `help' and `freeze' with '-F' require a
+ second argument -- the repository path. Parse it out here and
+ store it in opt_state. */
+ if (!(subcommand->cmd_func == subcommand_help
+ || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
+ {
+ const char *repos_path = NULL;
+
+ if (os->ind >= os->argc)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Repository argument required"));
+ return EXIT_ERROR(err);
+ }
+
+ if ((err = svn_utf_cstring_to_utf8(&repos_path,
+ os->argv[os->ind++], pool)))
+ {
+ return EXIT_ERROR(err);
+ }
+
+ if (svn_path_is_url(repos_path))
+ {
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is a URL when it should be a "
+ "local path"), repos_path);
+ return EXIT_ERROR(err);
+ }
+
+ opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
+ }
+
+ /* Check that the subcommand wasn't passed any inappropriate options. */
+ for (i = 0; i < received_opts->nelts; i++)
+ {
+ opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ /* All commands implicitly accept --help, so just skip over this
+ when we see it. Note that we don't want to include this option
+ in their "accepted options" list because it would be awfully
+ redundant to display it in every commands' help text. */
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
+ {
+ const char *optstr;
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
+ pool);
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ else
+ svn_error_clear(svn_cmdline_fprintf(stderr, pool
+ , _("Subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svnadmin help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name));
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* Set up our cancellation support. */
+ setup_cancellation_signals(signal_handler);
+
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it, otherwise
+ * working with large files when compiled against an APR that doesn't have
+ * large file support will crash the program, which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /* Configure FSFS caches for maximum efficiency with svnadmin.
+ * Also, apply the respective command line parameters, if given. */
+ {
+ svn_cache_config_t settings = *svn_cache_config_get();
+
+ settings.cache_size = opt_state.memory_cache_size;
+ settings.single_threaded = TRUE;
+
+ svn_cache_config_set(&settings);
+ }
+
+ /* Run the subcommand. */
+ err = (*subcommand->cmd_func)(os, &opt_state, pool);
+ if (err)
+ {
+ /* For argument-related problems, suggest using the 'help'
+ subcommand. */
+ if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
+ || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Try 'svnadmin help' for more info"));
+ }
+ return EXIT_ERROR(err);
+ }
+ else
+ {
+ /* Ensure that everything is written to stdout, so the user will
+ see any print errors. */
+ err = svn_cmdline_fflush(stdout);
+ if (err)
+ {
+ return EXIT_ERROR(err);
+ }
+ return EXIT_SUCCESS;
+ }
+}
+
+int
+main(int argc, const char *argv[])
+{
+ apr_pool_t *pool;
+ int exit_code;
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ exit_code = sub_main(argc, argv, pool);
+
+ svn_pool_destroy(pool);
+ return exit_code;
+}
diff --git a/contrib/subversion/subversion/svndumpfilter/svndumpfilter.1 b/contrib/subversion/subversion/svndumpfilter/svndumpfilter.1
new file mode 100644
index 0000000..c8c00fe
--- /dev/null
+++ b/contrib/subversion/subversion/svndumpfilter/svndumpfilter.1
@@ -0,0 +1,47 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svndumpfilter 1
+.SH NAME
+svndumpfilter \- Filter a subversion repository 'dumpfile'.
+.SH SYNOPSIS
+.TP
+\fBsvndumpfilter\fP \fIcommand\fP [\fIoptions\fP & \fIargs\fP]
+.SH OVERVIEW
+Subversion is a version control system, which allows you to keep old
+versions of files and directories (usually source code), keep a log of
+who, when, and why changes occurred, etc., like CVS, RCS or SCCS.
+\fBSubversion\fP keeps a single copy of the master sources. This copy
+is called the source ``repository''; it contains all the information
+to permit extracting previous versions of those files at any time.
+
+For more information about the Subversion project, visit
+http://subversion.apache.org.
+
+Documentation for Subversion and its tools, including detailed usage
+explanations of the \fBsvn\fP, \fBsvnadmin\fP, \fBsvnserve\fP and
+\fBsvnlook\fP programs, historical background, philosophical
+approaches and reasonings, etc., can be found at
+http://svnbook.red-bean.com/.
+
+Run `svndumpfilter help' to access the built-in tool documentation.
diff --git a/contrib/subversion/subversion/svndumpfilter/svndumpfilter.c b/contrib/subversion/subversion/svndumpfilter/svndumpfilter.c
new file mode 100644
index 0000000..dd4e4ab
--- /dev/null
+++ b/contrib/subversion/subversion/svndumpfilter/svndumpfilter.c
@@ -0,0 +1,1658 @@
+/*
+ * svndumpfilter.c: Subversion dump stream filtering tool main file.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <stdlib.h>
+
+#include <apr_file_io.h>
+
+#include "svn_private_config.h"
+#include "svn_cmdline.h"
+#include "svn_error.h"
+#include "svn_string.h"
+#include "svn_opt.h"
+#include "svn_utf.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_repos.h"
+#include "svn_fs.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "svn_version.h"
+
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_cmdline_private.h"
+
+#ifdef _WIN32
+typedef apr_status_t (__stdcall *open_fn_t)(apr_file_t **, apr_pool_t *);
+#else
+typedef apr_status_t (*open_fn_t)(apr_file_t **, apr_pool_t *);
+#endif
+
+/*** Code. ***/
+
+/* Helper to open stdio streams */
+
+/* NOTE: we used to call svn_stream_from_stdio(), which wraps a stream
+ around a standard stdio.h FILE pointer. The problem is that these
+ pointers operate through C Run Time (CRT) on Win32, which does all
+ sorts of translation on them: LF's become CRLF's, and ctrl-Z's
+ embedded in Word documents are interpreted as premature EOF's.
+
+ So instead, we use apr_file_open_std*, which bypass the CRT and
+ directly wrap the OS's file-handles, which don't know or care about
+ translation. Thus dump/load works correctly on Win32.
+*/
+static svn_error_t *
+create_stdio_stream(svn_stream_t **stream,
+ open_fn_t open_fn,
+ apr_pool_t *pool)
+{
+ apr_file_t *stdio_file;
+ apr_status_t apr_err = open_fn(&stdio_file, pool);
+
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't open stdio file"));
+
+ *stream = svn_stream_from_aprfile2(stdio_file, TRUE, pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Writes a property in dumpfile format to given stringbuf. */
+static void
+write_prop_to_stringbuf(svn_stringbuf_t *strbuf,
+ const char *name,
+ const svn_string_t *value)
+{
+ int bytes_used;
+ size_t namelen;
+ char buf[SVN_KEYLINE_MAXLEN];
+
+ /* Output name length, then name. */
+ namelen = strlen(name);
+ svn_stringbuf_appendbytes(strbuf, "K ", 2);
+
+ bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
+ svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
+ svn_stringbuf_appendbyte(strbuf, '\n');
+
+ svn_stringbuf_appendbytes(strbuf, name, namelen);
+ svn_stringbuf_appendbyte(strbuf, '\n');
+
+ /* Output value length, then value. */
+ svn_stringbuf_appendbytes(strbuf, "V ", 2);
+
+ bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, value->len);
+ svn_stringbuf_appendbytes(strbuf, buf, bytes_used);
+ svn_stringbuf_appendbyte(strbuf, '\n');
+
+ svn_stringbuf_appendbytes(strbuf, value->data, value->len);
+ svn_stringbuf_appendbyte(strbuf, '\n');
+}
+
+
+/* Writes a property deletion in dumpfile format to given stringbuf. */
+static void
+write_propdel_to_stringbuf(svn_stringbuf_t **strbuf,
+ const char *name)
+{
+ int bytes_used;
+ size_t namelen;
+ char buf[SVN_KEYLINE_MAXLEN];
+
+ /* Output name length, then name. */
+ namelen = strlen(name);
+ svn_stringbuf_appendbytes(*strbuf, "D ", 2);
+
+ bytes_used = apr_snprintf(buf, sizeof(buf), "%" APR_SIZE_T_FMT, namelen);
+ svn_stringbuf_appendbytes(*strbuf, buf, bytes_used);
+ svn_stringbuf_appendbyte(*strbuf, '\n');
+
+ svn_stringbuf_appendbytes(*strbuf, name, namelen);
+ svn_stringbuf_appendbyte(*strbuf, '\n');
+}
+
+
+/* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
+ * Return TRUE if any prefix is a prefix of PATH (matching whole path
+ * components); FALSE otherwise.
+ * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
+static svn_boolean_t
+ary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
+{
+ int i;
+ size_t path_len = strlen(path);
+
+ for (i = 0; i < pfxlist->nelts; i++)
+ {
+ const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
+ size_t pfx_len = strlen(pfx);
+
+ if (path_len < pfx_len)
+ continue;
+ if (strncmp(path, pfx, pfx_len) == 0
+ && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/* Check whether we need to skip this PATH based on its presence in
+ the PREFIXES list, and the DO_EXCLUDE option.
+ PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
+static APR_INLINE svn_boolean_t
+skip_path(const char *path, const apr_array_header_t *prefixes,
+ svn_boolean_t do_exclude, svn_boolean_t glob)
+{
+ const svn_boolean_t matches =
+ (glob
+ ? svn_cstring_match_glob_list(path, prefixes)
+ : ary_prefix_match(prefixes, path));
+
+ /* NXOR */
+ return (matches ? do_exclude : !do_exclude);
+}
+
+
+
+/* Note: the input stream parser calls us with events.
+ Output of the filtered dump occurs for the most part streamily with the
+ event callbacks, to avoid caching large quantities of data in memory.
+ The exceptions this are:
+ - All revision data (headers and props) must be cached until a non-skipped
+ node within the revision is found, or the revision is closed.
+ - Node headers and props must be cached until all props have been received
+ (to allow the Prop-content-length to be found). This is signalled either
+ by the node text arriving, or the node being closed.
+ The writing_begun members of the associated object batons track the state.
+ output_revision() and output_node() are called to cause this flushing of
+ cached data to occur.
+*/
+
+
+/* Filtering batons */
+
+struct revmap_t
+{
+ svn_revnum_t rev; /* Last non-dropped revision to which this maps. */
+ svn_boolean_t was_dropped; /* Was this revision dropped? */
+};
+
+struct parse_baton_t
+{
+ /* Command-line options values. */
+ svn_boolean_t do_exclude;
+ svn_boolean_t quiet;
+ svn_boolean_t glob;
+ svn_boolean_t drop_empty_revs;
+ svn_boolean_t drop_all_empty_revs;
+ svn_boolean_t do_renumber_revs;
+ svn_boolean_t preserve_revprops;
+ svn_boolean_t skip_missing_merge_sources;
+ svn_boolean_t allow_deltas;
+ apr_array_header_t *prefixes;
+
+ /* Input and output streams. */
+ svn_stream_t *in_stream;
+ svn_stream_t *out_stream;
+
+ /* State for the filtering process. */
+ apr_int32_t rev_drop_count;
+ apr_hash_t *dropped_nodes;
+ apr_hash_t *renumber_history; /* svn_revnum_t -> struct revmap_t */
+ svn_revnum_t last_live_revision;
+ /* The oldest original revision, greater than r0, in the input
+ stream which was not filtered. */
+ svn_revnum_t oldest_original_rev;
+};
+
+struct revision_baton_t
+{
+ /* Reference to the global parse baton. */
+ struct parse_baton_t *pb;
+
+ /* Does this revision have node or prop changes? */
+ svn_boolean_t has_nodes;
+ svn_boolean_t has_props;
+
+ /* Did we drop any nodes? */
+ svn_boolean_t had_dropped_nodes;
+
+ /* Written to output stream? */
+ svn_boolean_t writing_begun;
+
+ /* The original and new (re-mapped) revision numbers. */
+ svn_revnum_t rev_orig;
+ svn_revnum_t rev_actual;
+
+ /* Pointers to dumpfile data. */
+ svn_stringbuf_t *header;
+ apr_hash_t *props;
+};
+
+struct node_baton_t
+{
+ /* Reference to the current revision baton. */
+ struct revision_baton_t *rb;
+
+ /* Are we skipping this node? */
+ svn_boolean_t do_skip;
+
+ /* Have we been instructed to change or remove props on, or change
+ the text of, this node? */
+ svn_boolean_t has_props;
+ svn_boolean_t has_text;
+
+ /* Written to output stream? */
+ svn_boolean_t writing_begun;
+
+ /* The text content length according to the dumpfile headers, because we
+ need the length before we have the actual text. */
+ svn_filesize_t tcl;
+
+ /* Pointers to dumpfile data. */
+ svn_stringbuf_t *header;
+ svn_stringbuf_t *props;
+
+ /* Expect deltas? */
+ svn_boolean_t has_prop_delta;
+ svn_boolean_t has_text_delta;
+
+ /* We might need the node path in a parse error message. */
+ char *node_path;
+};
+
+
+
+/* Filtering vtable members */
+
+/* File-format stamp. */
+static svn_error_t *
+magic_header_record(int version, void *parse_baton, apr_pool_t *pool)
+{
+ struct parse_baton_t *pb = parse_baton;
+
+ if (version >= SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS)
+ pb->allow_deltas = TRUE;
+
+ SVN_ERR(svn_stream_printf(pb->out_stream, pool,
+ SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
+ version));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* New revision: set up revision_baton, decide if we skip it. */
+static svn_error_t *
+new_revision_record(void **revision_baton,
+ apr_hash_t *headers,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ struct revision_baton_t *rb;
+ apr_hash_index_t *hi;
+ const char *rev_orig;
+ svn_stream_t *header_stream;
+
+ *revision_baton = apr_palloc(pool, sizeof(struct revision_baton_t));
+ rb = *revision_baton;
+ rb->pb = parse_baton;
+ rb->has_nodes = FALSE;
+ rb->has_props = FALSE;
+ rb->had_dropped_nodes = FALSE;
+ rb->writing_begun = FALSE;
+ rb->header = svn_stringbuf_create_empty(pool);
+ rb->props = apr_hash_make(pool);
+
+ header_stream = svn_stream_from_stringbuf(rb->header, pool);
+
+ rev_orig = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
+ rb->rev_orig = SVN_STR_TO_REV(rev_orig);
+
+ if (rb->pb->do_renumber_revs)
+ rb->rev_actual = rb->rev_orig - rb->pb->rev_drop_count;
+ else
+ rb->rev_actual = rb->rev_orig;
+
+ SVN_ERR(svn_stream_printf(header_stream, pool,
+ SVN_REPOS_DUMPFILE_REVISION_NUMBER ": %ld\n",
+ rb->rev_actual));
+
+ for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
+ {
+ const char *key = svn__apr_hash_index_key(hi);
+ const char *val = svn__apr_hash_index_val(hi);
+
+ if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
+ || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
+ || (!strcmp(key, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
+ continue;
+
+ /* passthru: put header into header stringbuf. */
+
+ SVN_ERR(svn_stream_printf(header_stream, pool, "%s: %s\n",
+ key, val));
+ }
+
+ SVN_ERR(svn_stream_close(header_stream));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Output revision to dumpstream
+ This may be called by new_node_record(), iff rb->has_nodes has been set
+ to TRUE, or by close_revision() otherwise. This must only be called
+ if rb->writing_begun is FALSE. */
+static svn_error_t *
+output_revision(struct revision_baton_t *rb)
+{
+ int bytes_used;
+ char buf[SVN_KEYLINE_MAXLEN];
+ apr_hash_index_t *hi;
+ svn_boolean_t write_out_rev = FALSE;
+ apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
+ svn_stringbuf_t *props = svn_stringbuf_create_empty(hash_pool);
+ apr_pool_t *subpool = svn_pool_create(hash_pool);
+
+ rb->writing_begun = TRUE;
+
+ /* If this revision has no nodes left because the ones it had were
+ dropped, and we are not dropping empty revisions, and we were not
+ told to preserve revision props, then we want to fixup the
+ revision props to only contain:
+ - the date
+ - a log message that reports that this revision is just stuffing. */
+ if ((! rb->pb->preserve_revprops)
+ && (! rb->has_nodes)
+ && rb->had_dropped_nodes
+ && (! rb->pb->drop_empty_revs)
+ && (! rb->pb->drop_all_empty_revs))
+ {
+ apr_hash_t *old_props = rb->props;
+ rb->has_props = TRUE;
+ rb->props = apr_hash_make(hash_pool);
+ svn_hash_sets(rb->props, SVN_PROP_REVISION_DATE,
+ svn_hash_gets(old_props, SVN_PROP_REVISION_DATE));
+ svn_hash_sets(rb->props, SVN_PROP_REVISION_LOG,
+ svn_string_create(_("This is an empty revision for "
+ "padding."), hash_pool));
+ }
+
+ /* Now, "rasterize" the props to a string, and append the property
+ information to the header string. */
+ if (rb->has_props)
+ {
+ for (hi = apr_hash_first(subpool, rb->props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *pname = svn__apr_hash_index_key(hi);
+ const svn_string_t *pval = svn__apr_hash_index_val(hi);
+
+ write_prop_to_stringbuf(props, pname, pval);
+ }
+ svn_stringbuf_appendcstr(props, "PROPS-END\n");
+ svn_stringbuf_appendcstr(rb->header,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
+ bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT,
+ props->len);
+ svn_stringbuf_appendbytes(rb->header, buf, bytes_used);
+ svn_stringbuf_appendbyte(rb->header, '\n');
+ }
+
+ svn_stringbuf_appendcstr(rb->header, SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
+ bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT, props->len);
+ svn_stringbuf_appendbytes(rb->header, buf, bytes_used);
+ svn_stringbuf_appendbyte(rb->header, '\n');
+
+ /* put an end to headers */
+ svn_stringbuf_appendbyte(rb->header, '\n');
+
+ /* put an end to revision */
+ svn_stringbuf_appendbyte(props, '\n');
+
+ /* write out the revision */
+ /* Revision is written out in the following cases:
+ 1. If the revision has nodes or
+ it is revision 0 (Special case: To preserve the props on r0).
+ 2. --drop-empty-revs has been supplied,
+ but revision has not all nodes dropped.
+ 3. If no --drop-empty-revs or --drop-all-empty-revs have been supplied,
+ write out the revision which has no nodes to begin with.
+ */
+ if (rb->has_nodes || (rb->rev_orig == 0))
+ write_out_rev = TRUE;
+ else if (rb->pb->drop_empty_revs)
+ write_out_rev = ! rb->had_dropped_nodes;
+ else if (! rb->pb->drop_all_empty_revs)
+ write_out_rev = TRUE;
+
+ if (write_out_rev)
+ {
+ /* This revision is a keeper. */
+ SVN_ERR(svn_stream_write(rb->pb->out_stream,
+ rb->header->data, &(rb->header->len)));
+ SVN_ERR(svn_stream_write(rb->pb->out_stream,
+ props->data, &(props->len)));
+
+ /* Stash the oldest original rev not dropped. */
+ if (rb->rev_orig > 0
+ && !SVN_IS_VALID_REVNUM(rb->pb->oldest_original_rev))
+ rb->pb->oldest_original_rev = rb->rev_orig;
+
+ if (rb->pb->do_renumber_revs)
+ {
+ svn_revnum_t *rr_key;
+ struct revmap_t *rr_val;
+ apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
+ rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
+ rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
+ *rr_key = rb->rev_orig;
+ rr_val->rev = rb->rev_actual;
+ rr_val->was_dropped = FALSE;
+ apr_hash_set(rb->pb->renumber_history, rr_key,
+ sizeof(*rr_key), rr_val);
+ rb->pb->last_live_revision = rb->rev_actual;
+ }
+
+ if (! rb->pb->quiet)
+ SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
+ _("Revision %ld committed as %ld.\n"),
+ rb->rev_orig, rb->rev_actual));
+ }
+ else
+ {
+ /* We're dropping this revision. */
+ rb->pb->rev_drop_count++;
+ if (rb->pb->do_renumber_revs)
+ {
+ svn_revnum_t *rr_key;
+ struct revmap_t *rr_val;
+ apr_pool_t *rr_pool = apr_hash_pool_get(rb->pb->renumber_history);
+ rr_key = apr_palloc(rr_pool, sizeof(*rr_key));
+ rr_val = apr_palloc(rr_pool, sizeof(*rr_val));
+ *rr_key = rb->rev_orig;
+ rr_val->rev = rb->pb->last_live_revision;
+ rr_val->was_dropped = TRUE;
+ apr_hash_set(rb->pb->renumber_history, rr_key,
+ sizeof(*rr_key), rr_val);
+ }
+
+ if (! rb->pb->quiet)
+ SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
+ _("Revision %ld skipped.\n"),
+ rb->rev_orig));
+ }
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* UUID record here: dump it, as we do not filter them. */
+static svn_error_t *
+uuid_record(const char *uuid, void *parse_baton, apr_pool_t *pool)
+{
+ struct parse_baton_t *pb = parse_baton;
+ SVN_ERR(svn_stream_printf(pb->out_stream, pool,
+ SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
+ return SVN_NO_ERROR;
+}
+
+
+/* New node here. Set up node_baton by copying headers. */
+static svn_error_t *
+new_node_record(void **node_baton,
+ apr_hash_t *headers,
+ void *rev_baton,
+ apr_pool_t *pool)
+{
+ struct parse_baton_t *pb;
+ struct node_baton_t *nb;
+ char *node_path, *copyfrom_path;
+ apr_hash_index_t *hi;
+ const char *tcl;
+
+ *node_baton = apr_palloc(pool, sizeof(struct node_baton_t));
+ nb = *node_baton;
+ nb->rb = rev_baton;
+ pb = nb->rb->pb;
+
+ node_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH);
+ copyfrom_path = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH);
+
+ /* Ensure that paths start with a leading '/'. */
+ if (node_path[0] != '/')
+ node_path = apr_pstrcat(pool, "/", node_path, (char *)NULL);
+ if (copyfrom_path && copyfrom_path[0] != '/')
+ copyfrom_path = apr_pstrcat(pool, "/", copyfrom_path, (char *)NULL);
+
+ nb->do_skip = skip_path(node_path, pb->prefixes,
+ pb->do_exclude, pb->glob);
+
+ /* If we're skipping the node, take note of path, discarding the
+ rest. */
+ if (nb->do_skip)
+ {
+ svn_hash_sets(pb->dropped_nodes,
+ apr_pstrdup(apr_hash_pool_get(pb->dropped_nodes),
+ node_path),
+ (void *)1);
+ nb->rb->had_dropped_nodes = TRUE;
+ }
+ else
+ {
+ tcl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
+
+ /* Test if this node was copied from dropped source. */
+ if (copyfrom_path &&
+ skip_path(copyfrom_path, pb->prefixes, pb->do_exclude, pb->glob))
+ {
+ /* This node was copied from a dropped source.
+ We have a problem, since we did not want to drop this node too.
+
+ However, there is one special case we'll handle. If the node is
+ a file, and this was a copy-and-modify operation, then the
+ dumpfile should contain the new contents of the file. In this
+ scenario, we'll just do an add without history using the new
+ contents. */
+ const char *kind;
+ kind = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND);
+
+ /* If there is a Text-content-length header, and the kind is
+ "file", we just fallback to an add without history. */
+ if (tcl && (strcmp(kind, "file") == 0))
+ {
+ svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH,
+ NULL);
+ svn_hash_sets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV,
+ NULL);
+ copyfrom_path = NULL;
+ }
+ /* Else, this is either a directory or a file whose contents we
+ don't have readily available. */
+ else
+ {
+ return svn_error_createf
+ (SVN_ERR_INCOMPLETE_DATA, 0,
+ _("Invalid copy source path '%s'"), copyfrom_path);
+ }
+ }
+
+ nb->has_props = FALSE;
+ nb->has_text = FALSE;
+ nb->has_prop_delta = FALSE;
+ nb->has_text_delta = FALSE;
+ nb->writing_begun = FALSE;
+ nb->tcl = tcl ? svn__atoui64(tcl) : 0;
+ nb->header = svn_stringbuf_create_empty(pool);
+ nb->props = svn_stringbuf_create_empty(pool);
+ nb->node_path = apr_pstrdup(pool, node_path);
+
+ /* Now we know for sure that we have a node that will not be
+ skipped, flush the revision if it has not already been done. */
+ nb->rb->has_nodes = TRUE;
+ if (! nb->rb->writing_begun)
+ SVN_ERR(output_revision(nb->rb));
+
+ for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
+ {
+ const char *key = svn__apr_hash_index_key(hi);
+ const char *val = svn__apr_hash_index_val(hi);
+
+ if ((!strcmp(key, SVN_REPOS_DUMPFILE_PROP_DELTA))
+ && (!strcmp(val, "true")))
+ nb->has_prop_delta = TRUE;
+
+ if ((!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_DELTA))
+ && (!strcmp(val, "true")))
+ nb->has_text_delta = TRUE;
+
+ if ((!strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH))
+ || (!strcmp(key, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH))
+ || (!strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH)))
+ continue;
+
+ /* Rewrite Node-Copyfrom-Rev if we are renumbering revisions.
+ The number points to some revision in the past. We keep track
+ of revision renumbering in an apr_hash, which maps original
+ revisions to new ones. Dropped revision are mapped to -1.
+ This should never happen here.
+ */
+ if (pb->do_renumber_revs
+ && (!strcmp(key, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
+ {
+ svn_revnum_t cf_orig_rev;
+ struct revmap_t *cf_renum_val;
+
+ cf_orig_rev = SVN_STR_TO_REV(val);
+ cf_renum_val = apr_hash_get(pb->renumber_history,
+ &cf_orig_rev,
+ sizeof(svn_revnum_t));
+ if (! (cf_renum_val && SVN_IS_VALID_REVNUM(cf_renum_val->rev)))
+ return svn_error_createf
+ (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("No valid copyfrom revision in filtered stream"));
+ SVN_ERR(svn_stream_printf
+ (nb->rb->pb->out_stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV ": %ld\n",
+ cf_renum_val->rev));
+ continue;
+ }
+
+ /* passthru: put header straight to output */
+
+ SVN_ERR(svn_stream_printf(nb->rb->pb->out_stream,
+ pool, "%s: %s\n",
+ key, val));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Output node header and props to dumpstream
+ This will be called by set_fulltext() after setting nb->has_text to TRUE,
+ if the node has any text, or by close_node() otherwise. This must only
+ be called if nb->writing_begun is FALSE. */
+static svn_error_t *
+output_node(struct node_baton_t *nb)
+{
+ int bytes_used;
+ char buf[SVN_KEYLINE_MAXLEN];
+
+ nb->writing_begun = TRUE;
+
+ /* when there are no props nb->props->len would be zero and won't mess up
+ Content-Length. */
+ if (nb->has_props)
+ svn_stringbuf_appendcstr(nb->props, "PROPS-END\n");
+
+ /* 1. recalculate & check text-md5 if present. Passed through right now. */
+
+ /* 2. recalculate and add content-lengths */
+
+ if (nb->has_props)
+ {
+ svn_stringbuf_appendcstr(nb->header,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
+ bytes_used = apr_snprintf(buf, sizeof(buf), ": %" APR_SIZE_T_FMT,
+ nb->props->len);
+ svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
+ svn_stringbuf_appendbyte(nb->header, '\n');
+ }
+ if (nb->has_text)
+ {
+ svn_stringbuf_appendcstr(nb->header,
+ SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
+ bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT,
+ nb->tcl);
+ svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
+ svn_stringbuf_appendbyte(nb->header, '\n');
+ }
+ svn_stringbuf_appendcstr(nb->header, SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
+ bytes_used = apr_snprintf(buf, sizeof(buf), ": %" SVN_FILESIZE_T_FMT,
+ (svn_filesize_t) (nb->props->len + nb->tcl));
+ svn_stringbuf_appendbytes(nb->header, buf, bytes_used);
+ svn_stringbuf_appendbyte(nb->header, '\n');
+
+ /* put an end to headers */
+ svn_stringbuf_appendbyte(nb->header, '\n');
+
+ /* 3. output all the stuff */
+
+ SVN_ERR(svn_stream_write(nb->rb->pb->out_stream,
+ nb->header->data , &(nb->header->len)));
+ SVN_ERR(svn_stream_write(nb->rb->pb->out_stream,
+ nb->props->data , &(nb->props->len)));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Examine the mergeinfo in INITIAL_VAL, omitting missing merge
+ sources or renumbering revisions in rangelists as appropriate, and
+ return the (possibly new) mergeinfo in *FINAL_VAL (allocated from
+ POOL). */
+static svn_error_t *
+adjust_mergeinfo(svn_string_t **final_val, const svn_string_t *initial_val,
+ struct revision_baton_t *rb, apr_pool_t *pool)
+{
+ apr_hash_t *mergeinfo;
+ apr_hash_t *final_mergeinfo = apr_hash_make(pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
+
+ /* Issue #3020: If we are skipping missing merge sources, then also
+ filter mergeinfo ranges as old or older than the oldest revision in the
+ dump stream. Those older than the oldest obviously refer to history
+ outside of the dump stream. The oldest rev itself is present in the
+ dump, but cannot be a valid merge source revision since it is the
+ start of all history. E.g. if we dump -r100:400 then dumpfilter the
+ result with --skip-missing-merge-sources, any mergeinfo with revision
+ 100 implies a change of -r99:100, but r99 is part of the history we
+ want filtered. This is analogous to how r1 is always meaningless as
+ a merge source revision.
+
+ If the oldest rev is r0 then there is nothing to filter. */
+ if (rb->pb->skip_missing_merge_sources && rb->pb->oldest_original_rev > 0)
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &mergeinfo, mergeinfo,
+ rb->pb->oldest_original_rev, 0,
+ FALSE, subpool, subpool));
+
+ for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ const char *merge_source = svn__apr_hash_index_key(hi);
+ svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi);
+ struct parse_baton_t *pb = rb->pb;
+
+ /* Determine whether the merge_source is a part of the prefix. */
+ if (skip_path(merge_source, pb->prefixes, pb->do_exclude, pb->glob))
+ {
+ if (pb->skip_missing_merge_sources)
+ continue;
+ else
+ return svn_error_createf(SVN_ERR_INCOMPLETE_DATA, 0,
+ _("Missing merge source path '%s'; try "
+ "with --skip-missing-merge-sources"),
+ merge_source);
+ }
+
+ /* Possibly renumber revisions in merge source's rangelist. */
+ if (pb->do_renumber_revs)
+ {
+ int i;
+
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ struct revmap_t *revmap_start;
+ struct revmap_t *revmap_end;
+ svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
+ svn_merge_range_t *);
+
+ revmap_start = apr_hash_get(pb->renumber_history,
+ &range->start, sizeof(svn_revnum_t));
+ if (! (revmap_start && SVN_IS_VALID_REVNUM(revmap_start->rev)))
+ return svn_error_createf
+ (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("No valid revision range 'start' in filtered stream"));
+
+ revmap_end = apr_hash_get(pb->renumber_history,
+ &range->end, sizeof(svn_revnum_t));
+ if (! (revmap_end && SVN_IS_VALID_REVNUM(revmap_end->rev)))
+ return svn_error_createf
+ (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("No valid revision range 'end' in filtered stream"));
+
+ range->start = revmap_start->rev;
+ range->end = revmap_end->rev;
+ }
+ }
+ svn_hash_sets(final_mergeinfo, merge_source, rangelist);
+ }
+
+ SVN_ERR(svn_mergeinfo_sort(final_mergeinfo, subpool));
+ SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+set_revision_property(void *revision_baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ struct revision_baton_t *rb = revision_baton;
+ apr_pool_t *hash_pool = apr_hash_pool_get(rb->props);
+
+ rb->has_props = TRUE;
+ svn_hash_sets(rb->props,
+ apr_pstrdup(hash_pool, name),
+ svn_string_dup(value, hash_pool));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+set_node_property(void *node_baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ struct node_baton_t *nb = node_baton;
+ struct revision_baton_t *rb = nb->rb;
+
+ if (nb->do_skip)
+ return SVN_NO_ERROR;
+
+ if (! (nb->has_props || nb->has_prop_delta))
+ return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+ _("Delta property block detected, but deltas "
+ "are not enabled for node '%s' in original "
+ "revision %ld"),
+ nb->node_path, rb->rev_orig);
+
+ if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
+ {
+ svn_string_t *filtered_mergeinfo; /* Avoid compiler warning. */
+ apr_pool_t *pool = apr_hash_pool_get(rb->props);
+ SVN_ERR(adjust_mergeinfo(&filtered_mergeinfo, value, rb, pool));
+ value = filtered_mergeinfo;
+ }
+
+ nb->has_props = TRUE;
+ write_prop_to_stringbuf(nb->props, name, value);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+delete_node_property(void *node_baton, const char *name)
+{
+ struct node_baton_t *nb = node_baton;
+ struct revision_baton_t *rb = nb->rb;
+
+ if (nb->do_skip)
+ return SVN_NO_ERROR;
+
+ if (!nb->has_prop_delta)
+ return svn_error_createf(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
+ _("Delta property block detected, but deltas "
+ "are not enabled for node '%s' in original "
+ "revision %ld"),
+ nb->node_path, rb->rev_orig);
+
+ nb->has_props = TRUE;
+ write_propdel_to_stringbuf(&(nb->props), name);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+remove_node_props(void *node_baton)
+{
+ struct node_baton_t *nb = node_baton;
+
+ /* In this case, not actually indicating that the node *has* props,
+ rather that we know about all the props that it has, since it now
+ has none. */
+ nb->has_props = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+set_fulltext(svn_stream_t **stream, void *node_baton)
+{
+ struct node_baton_t *nb = node_baton;
+
+ if (!nb->do_skip)
+ {
+ nb->has_text = TRUE;
+ if (! nb->writing_begun)
+ SVN_ERR(output_node(nb));
+ *stream = nb->rb->pb->out_stream;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Finalize node */
+static svn_error_t *
+close_node(void *node_baton)
+{
+ struct node_baton_t *nb = node_baton;
+ apr_size_t len = 2;
+
+ /* Get out of here if we can. */
+ if (nb->do_skip)
+ return SVN_NO_ERROR;
+
+ /* If the node was not flushed already to output its text, do it now. */
+ if (! nb->writing_begun)
+ SVN_ERR(output_node(nb));
+
+ /* put an end to node. */
+ SVN_ERR(svn_stream_write(nb->rb->pb->out_stream, "\n\n", &len));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Finalize revision */
+static svn_error_t *
+close_revision(void *revision_baton)
+{
+ struct revision_baton_t *rb = revision_baton;
+
+ /* If no node has yet flushed the revision, do it now. */
+ if (! rb->writing_begun)
+ return output_revision(rb);
+ else
+ return SVN_NO_ERROR;
+}
+
+
+/* Filtering vtable */
+svn_repos_parse_fns3_t filtering_vtable =
+ {
+ magic_header_record,
+ uuid_record,
+ new_revision_record,
+ new_node_record,
+ set_revision_property,
+ set_node_property,
+ delete_node_property,
+ remove_node_props,
+ set_fulltext,
+ NULL,
+ close_node,
+ close_revision
+ };
+
+
+
+/** Subcommands. **/
+
+static svn_opt_subcommand_t
+ subcommand_help,
+ subcommand_exclude,
+ subcommand_include;
+
+enum
+ {
+ svndumpfilter__drop_empty_revs = SVN_OPT_FIRST_LONGOPT_ID,
+ svndumpfilter__drop_all_empty_revs,
+ svndumpfilter__renumber_revs,
+ svndumpfilter__preserve_revprops,
+ svndumpfilter__skip_missing_merge_sources,
+ svndumpfilter__targets,
+ svndumpfilter__quiet,
+ svndumpfilter__glob,
+ svndumpfilter__version
+ };
+
+/* Option codes and descriptions.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ */
+static const apr_getopt_option_t options_table[] =
+ {
+ {"help", 'h', 0,
+ N_("show help on a subcommand")},
+
+ {NULL, '?', 0,
+ N_("show help on a subcommand")},
+
+ {"version", svndumpfilter__version, 0,
+ N_("show program version information") },
+ {"quiet", svndumpfilter__quiet, 0,
+ N_("Do not display filtering statistics.") },
+ {"pattern", svndumpfilter__glob, 0,
+ N_("Treat the path prefixes as file glob patterns.") },
+ {"drop-empty-revs", svndumpfilter__drop_empty_revs, 0,
+ N_("Remove revisions emptied by filtering.")},
+ {"drop-all-empty-revs", svndumpfilter__drop_all_empty_revs, 0,
+ N_("Remove all empty revisions found in dumpstream\n"
+ " except revision 0.")},
+ {"renumber-revs", svndumpfilter__renumber_revs, 0,
+ N_("Renumber revisions left after filtering.") },
+ {"skip-missing-merge-sources",
+ svndumpfilter__skip_missing_merge_sources, 0,
+ N_("Skip missing merge sources.") },
+ {"preserve-revprops", svndumpfilter__preserve_revprops, 0,
+ N_("Don't filter revision properties.") },
+ {"targets", svndumpfilter__targets, 1,
+ N_("Read additional prefixes, one per line, from\n"
+ " file ARG.")},
+ {NULL}
+ };
+
+
+/* Array of available subcommands.
+ * The entire list must be terminated with an entry of nulls.
+ */
+static const svn_opt_subcommand_desc2_t cmd_table[] =
+ {
+ {"exclude", subcommand_exclude, {0},
+ N_("Filter out nodes with given prefixes from dumpstream.\n"
+ "usage: svndumpfilter exclude PATH_PREFIX...\n"),
+ {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
+ svndumpfilter__renumber_revs,
+ svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
+ svndumpfilter__preserve_revprops, svndumpfilter__quiet,
+ svndumpfilter__glob} },
+
+ {"include", subcommand_include, {0},
+ N_("Filter out nodes without given prefixes from dumpstream.\n"
+ "usage: svndumpfilter include PATH_PREFIX...\n"),
+ {svndumpfilter__drop_empty_revs, svndumpfilter__drop_all_empty_revs,
+ svndumpfilter__renumber_revs,
+ svndumpfilter__skip_missing_merge_sources, svndumpfilter__targets,
+ svndumpfilter__preserve_revprops, svndumpfilter__quiet,
+ svndumpfilter__glob} },
+
+ {"help", subcommand_help, {"?", "h"},
+ N_("Describe the usage of this program or its subcommands.\n"
+ "usage: svndumpfilter help [SUBCOMMAND...]\n"),
+ {0} },
+
+ { NULL, NULL, {0}, NULL, {0} }
+ };
+
+
+/* Baton for passing option/argument state to a subcommand function. */
+struct svndumpfilter_opt_state
+{
+ svn_opt_revision_t start_revision; /* -r X[:Y] is */
+ svn_opt_revision_t end_revision; /* not implemented. */
+ svn_boolean_t quiet; /* --quiet */
+ svn_boolean_t glob; /* --pattern */
+ svn_boolean_t version; /* --version */
+ svn_boolean_t drop_empty_revs; /* --drop-empty-revs */
+ svn_boolean_t drop_all_empty_revs; /* --drop-all-empty-revs */
+ svn_boolean_t help; /* --help or -? */
+ svn_boolean_t renumber_revs; /* --renumber-revs */
+ svn_boolean_t preserve_revprops; /* --preserve-revprops */
+ svn_boolean_t skip_missing_merge_sources;
+ /* --skip-missing-merge-sources */
+ const char *targets_file; /* --targets-file */
+ apr_array_header_t *prefixes; /* mainargs. */
+};
+
+
+static svn_error_t *
+parse_baton_initialize(struct parse_baton_t **pb,
+ struct svndumpfilter_opt_state *opt_state,
+ svn_boolean_t do_exclude,
+ apr_pool_t *pool)
+{
+ struct parse_baton_t *baton = apr_palloc(pool, sizeof(*baton));
+
+ /* Read the stream from STDIN. Users can redirect a file. */
+ SVN_ERR(create_stdio_stream(&(baton->in_stream),
+ apr_file_open_stdin, pool));
+
+ /* Have the parser dump results to STDOUT. Users can redirect a file. */
+ SVN_ERR(create_stdio_stream(&(baton->out_stream),
+ apr_file_open_stdout, pool));
+
+ baton->do_exclude = do_exclude;
+
+ /* Ignore --renumber-revs if there can't possibly be
+ anything to renumber. */
+ baton->do_renumber_revs =
+ (opt_state->renumber_revs && (opt_state->drop_empty_revs
+ || opt_state->drop_all_empty_revs));
+
+ baton->drop_empty_revs = opt_state->drop_empty_revs;
+ baton->drop_all_empty_revs = opt_state->drop_all_empty_revs;
+ baton->preserve_revprops = opt_state->preserve_revprops;
+ baton->quiet = opt_state->quiet;
+ baton->glob = opt_state->glob;
+ baton->prefixes = opt_state->prefixes;
+ baton->skip_missing_merge_sources = opt_state->skip_missing_merge_sources;
+ baton->rev_drop_count = 0; /* used to shift revnums while filtering */
+ baton->dropped_nodes = apr_hash_make(pool);
+ baton->renumber_history = apr_hash_make(pool);
+ baton->last_live_revision = SVN_INVALID_REVNUM;
+ baton->oldest_original_rev = SVN_INVALID_REVNUM;
+ baton->allow_deltas = FALSE;
+
+ *pb = baton;
+ return SVN_NO_ERROR;
+}
+
+/* This implements `help` subcommand. */
+static svn_error_t *
+subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svndumpfilter_opt_state *opt_state = baton;
+ const char *header =
+ _("general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]\n"
+ "Type 'svndumpfilter help <subcommand>' for help on a "
+ "specific subcommand.\n"
+ "Type 'svndumpfilter --version' to see the program version.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ SVN_ERR(svn_opt_print_help4(os, "svndumpfilter",
+ opt_state ? opt_state->version : FALSE,
+ opt_state ? opt_state->quiet : FALSE,
+ /*###opt_state ? opt_state->verbose :*/ FALSE,
+ NULL, header, cmd_table, options_table,
+ NULL, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Version compatibility check */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_repos", svn_repos_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+
+/* Do the real work of filtering. */
+static svn_error_t *
+do_filter(apr_getopt_t *os,
+ void *baton,
+ svn_boolean_t do_exclude,
+ apr_pool_t *pool)
+{
+ struct svndumpfilter_opt_state *opt_state = baton;
+ struct parse_baton_t *pb;
+ apr_hash_index_t *hi;
+ apr_array_header_t *keys;
+ int i, num_keys;
+
+ if (! opt_state->quiet)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ if (opt_state->glob)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
+ do_exclude
+ ? (opt_state->drop_empty_revs
+ || opt_state->drop_all_empty_revs)
+ ? _("Excluding (and dropping empty "
+ "revisions for) prefix patterns:\n")
+ : _("Excluding prefix patterns:\n")
+ : (opt_state->drop_empty_revs
+ || opt_state->drop_all_empty_revs)
+ ? _("Including (and dropping empty "
+ "revisions for) prefix patterns:\n")
+ : _("Including prefix patterns:\n")));
+ }
+ else
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
+ do_exclude
+ ? (opt_state->drop_empty_revs
+ || opt_state->drop_all_empty_revs)
+ ? _("Excluding (and dropping empty "
+ "revisions for) prefixes:\n")
+ : _("Excluding prefixes:\n")
+ : (opt_state->drop_empty_revs
+ || opt_state->drop_all_empty_revs)
+ ? _("Including (and dropping empty "
+ "revisions for) prefixes:\n")
+ : _("Including prefixes:\n")));
+ }
+
+ for (i = 0; i < opt_state->prefixes->nelts; i++)
+ {
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cmdline_fprintf
+ (stderr, subpool, " '%s'\n",
+ APR_ARRAY_IDX(opt_state->prefixes, i, const char *)));
+ }
+
+ SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
+ svn_pool_destroy(subpool);
+ }
+
+ SVN_ERR(parse_baton_initialize(&pb, opt_state, do_exclude, pool));
+ SVN_ERR(svn_repos_parse_dumpstream3(pb->in_stream, &filtering_vtable, pb,
+ TRUE, NULL, NULL, pool));
+
+ /* The rest of this is just reporting. If we aren't reporting, get
+ outta here. */
+ if (opt_state->quiet)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_cmdline_fputs("\n", stderr, pool));
+
+ if (pb->rev_drop_count)
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool,
+ Q_("Dropped %d revision.\n\n",
+ "Dropped %d revisions.\n\n",
+ pb->rev_drop_count),
+ pb->rev_drop_count));
+
+ if (pb->do_renumber_revs)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ SVN_ERR(svn_cmdline_fputs(_("Revisions renumbered as follows:\n"),
+ stderr, subpool));
+
+ /* Get the keys of the hash, sort them, then print the hash keys
+ and values, sorted by keys. */
+ num_keys = apr_hash_count(pb->renumber_history);
+ keys = apr_array_make(pool, num_keys + 1, sizeof(svn_revnum_t));
+ for (hi = apr_hash_first(pool, pb->renumber_history);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const svn_revnum_t *revnum = svn__apr_hash_index_key(hi);
+
+ APR_ARRAY_PUSH(keys, svn_revnum_t) = *revnum;
+ }
+ qsort(keys->elts, keys->nelts,
+ keys->elt_size, svn_sort_compare_revisions);
+ for (i = 0; i < keys->nelts; i++)
+ {
+ svn_revnum_t this_key;
+ struct revmap_t *this_val;
+
+ svn_pool_clear(subpool);
+ this_key = APR_ARRAY_IDX(keys, i, svn_revnum_t);
+ this_val = apr_hash_get(pb->renumber_history, &this_key,
+ sizeof(this_key));
+ if (this_val->was_dropped)
+ SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
+ _(" %ld => (dropped)\n"),
+ this_key));
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
+ " %ld => %ld\n",
+ this_key, this_val->rev));
+ }
+ SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
+ svn_pool_destroy(subpool);
+ }
+
+ if ((num_keys = apr_hash_count(pb->dropped_nodes)))
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
+ Q_("Dropped %d node:\n",
+ "Dropped %d nodes:\n",
+ num_keys),
+ num_keys));
+
+ /* Get the keys of the hash, sort them, then print the hash keys
+ and values, sorted by keys. */
+ keys = apr_array_make(pool, num_keys + 1, sizeof(const char *));
+ for (hi = apr_hash_first(pool, pb->dropped_nodes);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+
+ APR_ARRAY_PUSH(keys, const char *) = path;
+ }
+ qsort(keys->elts, keys->nelts, keys->elt_size, svn_sort_compare_paths);
+ for (i = 0; i < keys->nelts; i++)
+ {
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cmdline_fprintf
+ (stderr, subpool, " '%s'\n",
+ (const char *)APR_ARRAY_IDX(keys, i, const char *)));
+ }
+ SVN_ERR(svn_cmdline_fputs("\n", stderr, subpool));
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements `exclude' subcommand. */
+static svn_error_t *
+subcommand_exclude(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ return do_filter(os, baton, TRUE, pool);
+}
+
+
+/* This implements `include` subcommand. */
+static svn_error_t *
+subcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ return do_filter(os, baton, FALSE, pool);
+}
+
+
+
+/** Main. **/
+
+int
+main(int argc, const char *argv[])
+{
+ svn_error_t *err;
+ apr_status_t apr_err;
+ apr_pool_t *pool;
+
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+ struct svndumpfilter_opt_state opt_state;
+ apr_getopt_t *os;
+ int opt_id;
+ apr_array_header_t *received_opts;
+ int i;
+
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svndumpfilter", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ /* Check library versions */
+ err = check_lib_versions();
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ /* Initialize the FS library. */
+ err = svn_fs_initialize(pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
+
+ if (argc <= 1)
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ /* Initialize opt_state. */
+ memset(&opt_state, 0, sizeof(opt_state));
+ opt_state.start_revision.kind = svn_opt_revision_unspecified;
+ opt_state.end_revision.kind = svn_opt_revision_unspecified;
+
+ /* Parse options. */
+ err = svn_cmdline__getopt_init(&os, argc, argv, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
+
+ os->interleave = 1;
+ while (1)
+ {
+ const char *opt_arg;
+
+ /* Parse the next option. */
+ apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
+ if (APR_STATUS_IS_EOF(apr_err))
+ break;
+ else if (apr_err)
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ /* Stash the option code in an array before parsing it. */
+ APR_ARRAY_PUSH(received_opts, int) = opt_id;
+
+ switch (opt_id)
+ {
+ case 'h':
+ case '?':
+ opt_state.help = TRUE;
+ break;
+ case svndumpfilter__version:
+ opt_state.version = TRUE;
+ break;
+ case svndumpfilter__quiet:
+ opt_state.quiet = TRUE;
+ break;
+ case svndumpfilter__glob:
+ opt_state.glob = TRUE;
+ break;
+ case svndumpfilter__drop_empty_revs:
+ opt_state.drop_empty_revs = TRUE;
+ break;
+ case svndumpfilter__drop_all_empty_revs:
+ opt_state.drop_all_empty_revs = TRUE;
+ break;
+ case svndumpfilter__renumber_revs:
+ opt_state.renumber_revs = TRUE;
+ break;
+ case svndumpfilter__preserve_revprops:
+ opt_state.preserve_revprops = TRUE;
+ break;
+ case svndumpfilter__skip_missing_merge_sources:
+ opt_state.skip_missing_merge_sources = TRUE;
+ break;
+ case svndumpfilter__targets:
+ opt_state.targets_file = opt_arg;
+ break;
+ default:
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ } /* close `switch' */
+ } /* close `while' */
+
+ /* Disallow simultaneous use of both --drop-empty-revs and
+ --drop-all-empty-revs. */
+ if (opt_state.drop_empty_revs && opt_state.drop_all_empty_revs)
+ {
+ err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--drop-empty-revs cannot be used with "
+ "--drop-all-empty-revs"));
+ return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
+ }
+
+ /* If the user asked for help, then the rest of the arguments are
+ the names of subcommands to get help on (if any), or else they're
+ just typos/mistakes. Whatever the case, the subcommand to
+ actually run is subcommand_help(). */
+ if (opt_state.help)
+ subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
+
+ /* If we're not running the `help' subcommand, then look for a
+ subcommand in the first argument. */
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_state.version)
+ {
+ /* Use the "help" subcommand to handle the "--version" option. */
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+ { "--version", subcommand_help, {0}, "",
+ {svndumpfilter__version, /* must accept its own option */
+ svndumpfilter__quiet,
+ } };
+
+ subcommand = &pseudo_cmd;
+ }
+ else
+ {
+ svn_error_clear(svn_cmdline_fprintf
+ (stderr, pool,
+ _("Subcommand argument required\n")));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ const char *first_arg = os->argv[os->ind++];
+ subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
+ if (subcommand == NULL)
+ {
+ const char* first_arg_utf8;
+ if ((err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
+ pool)))
+ return svn_cmdline_handle_exit_error(err, pool,
+ "svndumpfilter: ");
+
+ svn_error_clear(
+ svn_cmdline_fprintf(stderr, pool,
+ _("Unknown subcommand: '%s'\n"),
+ first_arg_utf8));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ /* If there's a second argument, it's probably [one of] prefixes.
+ Every subcommand except `help' requires at least one, so we parse
+ them out here and store in opt_state. */
+
+ if (subcommand->cmd_func != subcommand_help)
+ {
+
+ opt_state.prefixes = apr_array_make(pool, os->argc - os->ind,
+ sizeof(const char *));
+ for (i = os->ind ; i< os->argc; i++)
+ {
+ const char *prefix;
+
+ /* Ensure that each prefix is UTF8-encoded, in internal
+ style, and absolute. */
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&prefix, os->argv[i], pool));
+ prefix = svn_relpath__internal_style(prefix, pool);
+ if (prefix[0] != '/')
+ prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL);
+ APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
+ }
+
+ if (opt_state.targets_file)
+ {
+ svn_stringbuf_t *buffer, *buffer_utf8;
+ const char *utf8_targets_file;
+ apr_array_header_t *targets = apr_array_make(pool, 0,
+ sizeof(const char *));
+
+ /* We need to convert to UTF-8 now, even before we divide
+ the targets into an array, because otherwise we wouldn't
+ know what delimiter to use for svn_cstring_split(). */
+
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_targets_file,
+ opt_state.targets_file, pool));
+
+ SVN_INT_ERR(svn_stringbuf_from_file2(&buffer, utf8_targets_file,
+ pool));
+ SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
+
+ targets = apr_array_append(pool,
+ svn_cstring_split(buffer_utf8->data, "\n\r",
+ TRUE, pool),
+ targets);
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *prefix = APR_ARRAY_IDX(targets, i, const char *);
+ if (prefix[0] != '/')
+ prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL);
+ APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
+ }
+ }
+
+ if (apr_is_empty_array(opt_state.prefixes))
+ {
+ svn_error_clear(svn_cmdline_fprintf
+ (stderr, pool,
+ _("\nError: no prefixes supplied.\n")));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+
+
+ /* Check that the subcommand wasn't passed any inappropriate options. */
+ for (i = 0; i < received_opts->nelts; i++)
+ {
+ opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ /* All commands implicitly accept --help, so just skip over this
+ when we see it. Note that we don't want to include this option
+ in their "accepted options" list because it would be awfully
+ redundant to display it in every commands' help text. */
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
+ {
+ const char *optstr;
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
+ pool);
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ else
+ svn_error_clear(svn_cmdline_fprintf
+ (stderr, pool,
+ _("Subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svndumpfilter help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* Run the subcommand. */
+ err = (*subcommand->cmd_func)(os, &opt_state, pool);
+ if (err)
+ {
+ /* For argument-related problems, suggest using the 'help'
+ subcommand. */
+ if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
+ || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Try 'svndumpfilter help' for more "
+ "info"));
+ }
+ return svn_cmdline_handle_exit_error(err, pool, "svndumpfilter: ");
+ }
+ else
+ {
+ svn_pool_destroy(pool);
+
+ /* Flush stdout, making sure the user will see any print errors. */
+ SVN_INT_ERR(svn_cmdline_fflush(stdout));
+ return EXIT_SUCCESS;
+ }
+}
diff --git a/contrib/subversion/subversion/svnlook/svnlook.1 b/contrib/subversion/subversion/svnlook/svnlook.1
new file mode 100644
index 0000000..22ded4c
--- /dev/null
+++ b/contrib/subversion/subversion/svnlook/svnlook.1
@@ -0,0 +1,47 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svnlook 1
+.SH NAME
+svnlook \- Subversion repository examination tool
+.SH SYNOPSIS
+.TP
+\fBsvnlook\fP \fIcommand\fP \fI/path/to/repos\fP [\fIoptions\fP] [\fIargs\fP]
+.SH OVERVIEW
+Subversion is a version control system, which allows you to keep old
+versions of files and directories (usually source code), keep a log of
+who, when, and why changes occurred, etc., like CVS, RCS or SCCS.
+\fBSubversion\fP keeps a single copy of the master sources. This copy
+is called the source ``repository''; it contains all the information
+to permit extracting previous versions of those files at any time.
+
+For more information about the Subversion project, visit
+http://subversion.apache.org.
+
+Documentation for Subversion and its tools, including detailed usage
+explanations of the \fBsvn\fP, \fBsvnadmin\fP, \fBsvnserve\fP and
+\fBsvnlook\fP programs, historical background, philosophical
+approaches and reasonings, etc., can be found at
+http://svnbook.red-bean.com/.
+
+Run `svnlook help' to access the built-in tool documentation.
diff --git a/contrib/subversion/subversion/svnlook/svnlook.c b/contrib/subversion/subversion/svnlook/svnlook.c
new file mode 100644
index 0000000..e619450
--- /dev/null
+++ b/contrib/subversion/subversion/svnlook/svnlook.c
@@ -0,0 +1,2830 @@
+/*
+ * svnlook.c: Subversion server inspection tool main file.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <apr_general.h>
+#include <apr_pools.h>
+#include <apr_time.h>
+#include <apr_file_io.h>
+#include <apr_signal.h>
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_repos.h"
+#include "svn_fs.h"
+#include "svn_time.h"
+#include "svn_utf.h"
+#include "svn_subst.h"
+#include "svn_sorts.h"
+#include "svn_opt.h"
+#include "svn_props.h"
+#include "svn_diff.h"
+#include "svn_version.h"
+#include "svn_xml.h"
+
+#include "private/svn_diff_private.h"
+#include "private/svn_cmdline_private.h"
+#include "private/svn_fspath.h"
+
+#include "svn_private_config.h"
+
+
+/*** Some convenience macros and types. ***/
+
+
+/* Option handling. */
+
+static svn_opt_subcommand_t
+ subcommand_author,
+ subcommand_cat,
+ subcommand_changed,
+ subcommand_date,
+ subcommand_diff,
+ subcommand_dirschanged,
+ subcommand_filesize,
+ subcommand_help,
+ subcommand_history,
+ subcommand_info,
+ subcommand_lock,
+ subcommand_log,
+ subcommand_pget,
+ subcommand_plist,
+ subcommand_tree,
+ subcommand_uuid,
+ subcommand_youngest;
+
+/* Option codes and descriptions. */
+enum
+ {
+ svnlook__version = SVN_OPT_FIRST_LONGOPT_ID,
+ svnlook__show_ids,
+ svnlook__no_diff_deleted,
+ svnlook__no_diff_added,
+ svnlook__diff_copy_from,
+ svnlook__revprop_opt,
+ svnlook__full_paths,
+ svnlook__copy_info,
+ svnlook__xml_opt,
+ svnlook__ignore_properties,
+ svnlook__properties_only,
+ svnlook__diff_cmd,
+ svnlook__show_inherited_props
+ };
+
+/*
+ * The entire list must be terminated with an entry of nulls.
+ */
+static const apr_getopt_option_t options_table[] =
+{
+ {NULL, '?', 0,
+ N_("show help on a subcommand")},
+
+ {"copy-info", svnlook__copy_info, 0,
+ N_("show details for copies")},
+
+ {"diff-copy-from", svnlook__diff_copy_from, 0,
+ N_("print differences against the copy source")},
+
+ {"full-paths", svnlook__full_paths, 0,
+ N_("show full paths instead of indenting them")},
+
+ {"help", 'h', 0,
+ N_("show help on a subcommand")},
+
+ {"limit", 'l', 1,
+ N_("maximum number of history entries")},
+
+ {"no-diff-added", svnlook__no_diff_added, 0,
+ N_("do not print differences for added files")},
+
+ {"no-diff-deleted", svnlook__no_diff_deleted, 0,
+ N_("do not print differences for deleted files")},
+
+ {"diff-cmd", svnlook__diff_cmd, 1,
+ N_("use ARG as diff command")},
+
+ {"ignore-properties", svnlook__ignore_properties, 0,
+ N_("ignore properties during the operation")},
+
+ {"properties-only", svnlook__properties_only, 0,
+ N_("show only properties during the operation")},
+
+ {"non-recursive", 'N', 0,
+ N_("operate on single directory only")},
+
+ {"revision", 'r', 1,
+ N_("specify revision number ARG")},
+
+ {"revprop", svnlook__revprop_opt, 0,
+ N_("operate on a revision property (use with -r or -t)")},
+
+ {"show-ids", svnlook__show_ids, 0,
+ N_("show node revision ids for each path")},
+
+ {"show-inherited-props", svnlook__show_inherited_props, 0,
+ N_("show path's inherited properties")},
+
+ {"transaction", 't', 1,
+ N_("specify transaction name ARG")},
+
+ {"verbose", 'v', 0,
+ N_("be verbose")},
+
+ {"version", svnlook__version, 0,
+ N_("show program version information")},
+
+ {"xml", svnlook__xml_opt, 0,
+ N_("output in XML")},
+
+ {"extensions", 'x', 1,
+ N_("Specify differencing options for external diff or\n"
+ " "
+ "internal diff. Default: '-u'. Options are\n"
+ " "
+ "separated by spaces. Internal diff takes:\n"
+ " "
+ " -u, --unified: Show 3 lines of unified context\n"
+ " "
+ " -b, --ignore-space-change: Ignore changes in\n"
+ " "
+ " amount of white space\n"
+ " "
+ " -w, --ignore-all-space: Ignore all white space\n"
+ " "
+ " --ignore-eol-style: Ignore changes in EOL style\n"
+ " "
+ " -p, --show-c-function: Show C function name")},
+
+ {"quiet", 'q', 0,
+ N_("no progress (only errors) to stderr")},
+
+ {0, 0, 0, 0}
+};
+
+
+/* Array of available subcommands.
+ * The entire list must be terminated with an entry of nulls.
+ */
+static const svn_opt_subcommand_desc2_t cmd_table[] =
+{
+ {"author", subcommand_author, {0},
+ N_("usage: svnlook author REPOS_PATH\n\n"
+ "Print the author.\n"),
+ {'r', 't'} },
+
+ {"cat", subcommand_cat, {0},
+ N_("usage: svnlook cat REPOS_PATH FILE_PATH\n\n"
+ "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"),
+ {'r', 't'} },
+
+ {"changed", subcommand_changed, {0},
+ N_("usage: svnlook changed REPOS_PATH\n\n"
+ "Print the paths that were changed.\n"),
+ {'r', 't', svnlook__copy_info} },
+
+ {"date", subcommand_date, {0},
+ N_("usage: svnlook date REPOS_PATH\n\n"
+ "Print the datestamp.\n"),
+ {'r', 't'} },
+
+ {"diff", subcommand_diff, {0},
+ N_("usage: svnlook diff REPOS_PATH\n\n"
+ "Print GNU-style diffs of changed files and properties.\n"),
+ {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added,
+ svnlook__diff_copy_from, svnlook__diff_cmd, 'x',
+ svnlook__ignore_properties, svnlook__properties_only} },
+
+ {"dirs-changed", subcommand_dirschanged, {0},
+ N_("usage: svnlook dirs-changed REPOS_PATH\n\n"
+ "Print the directories that were themselves changed (property edits)\n"
+ "or whose file children were changed.\n"),
+ {'r', 't'} },
+
+ {"filesize", subcommand_filesize, {0},
+ N_("usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n\n"
+ "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n"
+ "it is represented in the repository.\n"),
+ {'r', 't'} },
+
+ {"help", subcommand_help, {"?", "h"},
+ N_("usage: svnlook help [SUBCOMMAND...]\n\n"
+ "Describe the usage of this program or its subcommands.\n"),
+ {0} },
+
+ {"history", subcommand_history, {0},
+ N_("usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n\n"
+ "Print information about the history of a path in the repository (or\n"
+ "the root directory if no path is supplied).\n"),
+ {'r', svnlook__show_ids, 'l'} },
+
+ {"info", subcommand_info, {0},
+ N_("usage: svnlook info REPOS_PATH\n\n"
+ "Print the author, datestamp, log message size, and log message.\n"),
+ {'r', 't'} },
+
+ {"lock", subcommand_lock, {0},
+ N_("usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n\n"
+ "If a lock exists on a path in the repository, describe it.\n"),
+ {0} },
+
+ {"log", subcommand_log, {0},
+ N_("usage: svnlook log REPOS_PATH\n\n"
+ "Print the log message.\n"),
+ {'r', 't'} },
+
+ {"propget", subcommand_pget, {"pget", "pg"},
+ N_("usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n"
+ " "
+ /* The line above is actually needed, so do NOT delete it! */
+ " 2. svnlook propget --revprop REPOS_PATH PROPNAME\n\n"
+ "Print the raw value of a property on a path in the repository.\n"
+ "With --revprop, print the raw value of a revision property.\n"),
+ {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} },
+
+ {"proplist", subcommand_plist, {"plist", "pl"},
+ N_("usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n"
+ " "
+ /* The line above is actually needed, so do NOT delete it! */
+ " 2. svnlook proplist --revprop REPOS_PATH\n\n"
+ "List the properties of a path in the repository, or\n"
+ "with the --revprop option, revision properties.\n"
+ "With -v, show the property values too.\n"),
+ {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt,
+ svnlook__show_inherited_props} },
+
+ {"tree", subcommand_tree, {0},
+ N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n"
+ "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n"
+ "of the tree otherwise), optionally showing node revision ids.\n"),
+ {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths} },
+
+ {"uuid", subcommand_uuid, {0},
+ N_("usage: svnlook uuid REPOS_PATH\n\n"
+ "Print the repository's UUID.\n"),
+ {0} },
+
+ {"youngest", subcommand_youngest, {0},
+ N_("usage: svnlook youngest REPOS_PATH\n\n"
+ "Print the youngest revision number.\n"),
+ {0} },
+
+ { NULL, NULL, {0}, NULL, {0} }
+};
+
+
+/* Baton for passing option/argument state to a subcommand function. */
+struct svnlook_opt_state
+{
+ const char *repos_path; /* 'arg0' is always the path to the repository. */
+ const char *arg1; /* Usually an fs path, a propname, or NULL. */
+ const char *arg2; /* Usually an fs path or NULL. */
+ svn_revnum_t rev;
+ const char *txn;
+ svn_boolean_t version; /* --version */
+ svn_boolean_t show_ids; /* --show-ids */
+ apr_size_t limit; /* --limit */
+ svn_boolean_t help; /* --help */
+ svn_boolean_t no_diff_deleted; /* --no-diff-deleted */
+ svn_boolean_t no_diff_added; /* --no-diff-added */
+ svn_boolean_t diff_copy_from; /* --diff-copy-from */
+ svn_boolean_t verbose; /* --verbose */
+ svn_boolean_t revprop; /* --revprop */
+ svn_boolean_t full_paths; /* --full-paths */
+ svn_boolean_t copy_info; /* --copy-info */
+ svn_boolean_t non_recursive; /* --non-recursive */
+ svn_boolean_t xml; /* --xml */
+ const char *extensions; /* diff extension args (UTF-8!) */
+ svn_boolean_t quiet; /* --quiet */
+ svn_boolean_t ignore_properties; /* --ignore_properties */
+ svn_boolean_t properties_only; /* --properties-only */
+ const char *diff_cmd; /* --diff-cmd */
+ svn_boolean_t show_inherited_props; /* --show-inherited-props */
+};
+
+
+typedef struct svnlook_ctxt_t
+{
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_boolean_t is_revision;
+ svn_boolean_t show_ids;
+ apr_size_t limit;
+ svn_boolean_t no_diff_deleted;
+ svn_boolean_t no_diff_added;
+ svn_boolean_t diff_copy_from;
+ svn_boolean_t full_paths;
+ svn_boolean_t copy_info;
+ svn_revnum_t rev_id;
+ svn_fs_txn_t *txn;
+ const char *txn_name /* UTF-8! */;
+ const apr_array_header_t *diff_options;
+ svn_boolean_t ignore_properties;
+ svn_boolean_t properties_only;
+ const char *diff_cmd;
+
+} svnlook_ctxt_t;
+
+/* A flag to see if we've been cancelled by the client or not. */
+static volatile sig_atomic_t cancelled = FALSE;
+
+
+/*** Helper functions. ***/
+
+/* A signal handler to support cancellation. */
+static void
+signal_handler(int signum)
+{
+ apr_signal(signum, SIG_IGN);
+ cancelled = TRUE;
+}
+
+/* Our cancellation callback. */
+static svn_error_t *
+check_cancel(void *baton)
+{
+ if (cancelled)
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+/* Version compatibility check */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_repos", svn_repos_version },
+ { "svn_fs", svn_fs_version },
+ { "svn_delta", svn_delta_version },
+ { "svn_diff", svn_diff_version },
+ { NULL, NULL }
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+
+/* Get revision or transaction property PROP_NAME for the revision or
+ transaction specified in C, allocating in in POOL and placing it in
+ *PROP_VALUE. */
+static svn_error_t *
+get_property(svn_string_t **prop_value,
+ svnlook_ctxt_t *c,
+ const char *prop_name,
+ apr_pool_t *pool)
+{
+ svn_string_t *raw_value;
+
+ /* Fetch transaction property... */
+ if (! c->is_revision)
+ SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool));
+
+ /* ...or revision property -- it's your call. */
+ else
+ SVN_ERR(svn_fs_revision_prop(&raw_value, c->fs, c->rev_id,
+ prop_name, pool));
+
+ *prop_value = raw_value;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+get_root(svn_fs_root_t **root,
+ svnlook_ctxt_t *c,
+ apr_pool_t *pool)
+{
+ /* Open up the appropriate root (revision or transaction). */
+ if (c->is_revision)
+ {
+ /* If we didn't get a valid revision number, we'll look at the
+ youngest revision. */
+ if (! SVN_IS_VALID_REVNUM(c->rev_id))
+ SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool));
+
+ SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool));
+ }
+ else
+ {
+ SVN_ERR(svn_fs_txn_root(root, c->txn, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Tree Routines ***/
+
+/* Generate a generic delta tree. */
+static svn_error_t *
+generate_delta_tree(svn_repos_node_t **tree,
+ svn_repos_t *repos,
+ svn_fs_root_t *root,
+ svn_revnum_t base_rev,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *base_root;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ apr_pool_t *edit_pool = svn_pool_create(pool);
+ svn_fs_t *fs = svn_repos_fs(repos);
+
+ /* Get the base root. */
+ SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool));
+
+ /* Request our editor. */
+ SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
+ base_root, root, pool, edit_pool));
+
+ /* Drive our editor. */
+ SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE,
+ editor, edit_baton, NULL, NULL, edit_pool));
+
+ /* Return the tree we just built. */
+ *tree = svn_repos_node_from_baton(edit_baton);
+ svn_pool_destroy(edit_pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Tree Printing Routines ***/
+
+/* Recursively print only directory nodes that either a) have property
+ mods, or b) contains files that have changed, or c) has added or deleted
+ children. NODE is the root node of the tree delta, so every node in it
+ is either changed or is a directory with a changed node somewhere in the
+ subtree below it.
+ */
+static svn_error_t *
+print_dirs_changed_tree(svn_repos_node_t *node,
+ const char *path /* UTF-8! */,
+ apr_pool_t *pool)
+{
+ svn_repos_node_t *tmp_node;
+ svn_boolean_t print_me = FALSE;
+ const char *full_path;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(check_cancel(NULL));
+
+ if (! node)
+ return SVN_NO_ERROR;
+
+ /* Not a directory? We're not interested. */
+ if (node->kind != svn_node_dir)
+ return SVN_NO_ERROR;
+
+ /* Got prop mods? Excellent. */
+ if (node->prop_mod)
+ print_me = TRUE;
+
+ /* Fly through the list of children, checking for modified files. */
+ tmp_node = node->child;
+ while (tmp_node && (! print_me))
+ {
+ if ((tmp_node->kind == svn_node_file)
+ || (tmp_node->action == 'A')
+ || (tmp_node->action == 'D'))
+ {
+ print_me = TRUE;
+ }
+ tmp_node = tmp_node->sibling;
+ }
+
+ /* Print the node if it qualifies. */
+ if (print_me)
+ {
+ SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path));
+ }
+
+ /* Return here if the node has no children. */
+ tmp_node = node->child;
+ if (! tmp_node)
+ return SVN_NO_ERROR;
+
+ /* Recursively handle the node's children. */
+ iterpool = svn_pool_create(pool);
+ while (tmp_node)
+ {
+ svn_pool_clear(iterpool);
+ full_path = svn_dirent_join(path, tmp_node->name, iterpool);
+ SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool));
+ tmp_node = tmp_node->sibling;
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Recursively print all nodes in the tree that have been modified
+ (do not include directories affected only by "bubble-up"). */
+static svn_error_t *
+print_changed_tree(svn_repos_node_t *node,
+ const char *path /* UTF-8! */,
+ svn_boolean_t copy_info,
+ apr_pool_t *pool)
+{
+ const char *full_path;
+ char status[4] = "_ ";
+ svn_boolean_t print_me = TRUE;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(check_cancel(NULL));
+
+ if (! node)
+ return SVN_NO_ERROR;
+
+ /* Print the node. */
+ if (node->action == 'A')
+ {
+ status[0] = 'A';
+ if (copy_info && node->copyfrom_path)
+ status[2] = '+';
+ }
+ else if (node->action == 'D')
+ status[0] = 'D';
+ else if (node->action == 'R')
+ {
+ if ((! node->text_mod) && (! node->prop_mod))
+ print_me = FALSE;
+ if (node->text_mod)
+ status[0] = 'U';
+ if (node->prop_mod)
+ status[1] = 'U';
+ }
+ else
+ print_me = FALSE;
+
+ /* Print this node unless told to skip it. */
+ if (print_me)
+ {
+ SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n",
+ status,
+ path,
+ node->kind == svn_node_dir ? "/" : ""));
+ if (copy_info && node->copyfrom_path)
+ /* Remove the leading slash from the copyfrom path for consistency
+ with the rest of the output. */
+ SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n",
+ (node->copyfrom_path[0] == '/'
+ ? node->copyfrom_path + 1
+ : node->copyfrom_path),
+ (node->kind == svn_node_dir ? "/" : ""),
+ node->copyfrom_rev));
+ }
+
+ /* Return here if the node has no children. */
+ node = node->child;
+ if (! node)
+ return SVN_NO_ERROR;
+
+ /* Recursively handle the node's children. */
+ iterpool = svn_pool_create(pool);
+ while (node)
+ {
+ svn_pool_clear(iterpool);
+ full_path = svn_dirent_join(path, node->name, iterpool);
+ SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool));
+ node = node->sibling;
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+dump_contents(svn_stream_t *stream,
+ svn_fs_root_t *root,
+ const char *path /* UTF-8! */,
+ apr_pool_t *pool)
+{
+ if (root == NULL)
+ SVN_ERR(svn_stream_close(stream)); /* leave an empty file */
+ else
+ {
+ svn_stream_t *contents;
+
+ /* Grab the contents and copy them into the given stream. */
+ SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
+ SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing
+ PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL,
+ the temporary file for its path/root will be an empty one.
+ Otherwise, its temporary file will contain the contents of that
+ path/root in the repository.
+
+ An exception to this is when either path/root has an svn:mime-type
+ property set on it which indicates that the file contains
+ non-textual data -- in this case, the *IS_BINARY flag is set and no
+ temporary files are created.
+
+ Use POOL for all that allocation goodness. */
+static svn_error_t *
+prepare_tmpfiles(const char **tmpfile1,
+ const char **tmpfile2,
+ svn_boolean_t *is_binary,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ const char *tmpdir,
+ apr_pool_t *pool)
+{
+ svn_string_t *mimetype;
+ svn_stream_t *stream;
+
+ /* Init the return values. */
+ *tmpfile1 = NULL;
+ *tmpfile2 = NULL;
+ *is_binary = FALSE;
+
+ assert(path1 && path2);
+
+ /* Check for binary mimetypes. If either file has a binary
+ mimetype, get outta here. */
+ if (root1)
+ {
+ SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1,
+ SVN_PROP_MIME_TYPE, pool));
+ if (mimetype && svn_mime_type_is_binary(mimetype->data))
+ {
+ *is_binary = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+ if (root2)
+ {
+ SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2,
+ SVN_PROP_MIME_TYPE, pool));
+ if (mimetype && svn_mime_type_is_binary(mimetype->data))
+ {
+ *is_binary = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Now, prepare the two temporary files, each of which will either
+ be empty, or will have real contents. */
+ SVN_ERR(svn_stream_open_unique(&stream, tmpfile1,
+ tmpdir,
+ svn_io_file_del_none,
+ pool, pool));
+ SVN_ERR(dump_contents(stream, root1, path1, pool));
+
+ SVN_ERR(svn_stream_open_unique(&stream, tmpfile2,
+ tmpdir,
+ svn_io_file_del_none,
+ pool, pool));
+ SVN_ERR(dump_contents(stream, root2, path2, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Generate a diff label for PATH in ROOT, allocating in POOL.
+ ROOT may be NULL, in which case revision 0 is used. */
+static svn_error_t *
+generate_label(const char **label,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_string_t *date;
+ const char *datestr;
+ const char *name = NULL;
+ svn_revnum_t rev = SVN_INVALID_REVNUM;
+
+ if (root)
+ {
+ svn_fs_t *fs = svn_fs_root_fs(root);
+ if (svn_fs_is_revision_root(root))
+ {
+ rev = svn_fs_revision_root_revision(root);
+ SVN_ERR(svn_fs_revision_prop(&date, fs, rev,
+ SVN_PROP_REVISION_DATE, pool));
+ }
+ else
+ {
+ svn_fs_txn_t *txn;
+ name = svn_fs_txn_root_name(root, pool);
+ SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool));
+ SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool));
+ }
+ }
+ else
+ {
+ rev = 0;
+ date = NULL;
+ }
+
+ if (date)
+ datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11);
+ else
+ datestr = " ";
+
+ if (name)
+ *label = apr_psprintf(pool, "%s\t%s (txn %s)",
+ path, datestr, name);
+ else
+ *label = apr_psprintf(pool, "%s\t%s (rev %ld)",
+ path, datestr, rev);
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper function to display differences in properties of a file */
+static svn_error_t *
+display_prop_diffs(svn_stream_t *outstream,
+ const char *encoding,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ const char *path,
+ apr_pool_t *pool)
+{
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
+ _("%sProperty changes on: %s%s"),
+ APR_EOL_STR,
+ path,
+ APR_EOL_STR));
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
+ SVN_DIFF__UNDER_STRING APR_EOL_STR));
+
+ SVN_ERR(check_cancel(NULL));
+
+ SVN_ERR(svn_diff__display_prop_diffs(
+ outstream, encoding, propchanges, original_props,
+ FALSE /* pretty_print_mergeinfo */, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Recursively print all nodes in the tree that have been modified
+ (do not include directories affected only by "bubble-up"). */
+static svn_error_t *
+print_diff_tree(svn_stream_t *out_stream,
+ const char *encoding,
+ svn_fs_root_t *root,
+ svn_fs_root_t *base_root,
+ svn_repos_node_t *node,
+ const char *path /* UTF-8! */,
+ const char *base_path /* UTF-8! */,
+ const svnlook_ctxt_t *c,
+ const char *tmpdir,
+ apr_pool_t *pool)
+{
+ const char *orig_path = NULL, *new_path = NULL;
+ svn_boolean_t do_diff = FALSE;
+ svn_boolean_t orig_empty = FALSE;
+ svn_boolean_t is_copy = FALSE;
+ svn_boolean_t binary = FALSE;
+ svn_boolean_t diff_header_printed = FALSE;
+ apr_pool_t *subpool;
+ svn_stringbuf_t *header;
+
+ SVN_ERR(check_cancel(NULL));
+
+ if (! node)
+ return SVN_NO_ERROR;
+
+ header = svn_stringbuf_create_empty(pool);
+
+ /* Print copyfrom history for the top node of a copied tree. */
+ if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev))
+ && (node->copyfrom_path != NULL))
+ {
+ /* This is ... a copy. */
+ is_copy = TRUE;
+
+ /* Propagate the new base. Copyfrom paths usually start with a
+ slash; we remove it for consistency with the target path.
+ ### Yes, it would be *much* better for something in the path
+ library to be taking care of this! */
+ if (node->copyfrom_path[0] == '/')
+ base_path = apr_pstrdup(pool, node->copyfrom_path + 1);
+ else
+ base_path = apr_pstrdup(pool, node->copyfrom_path);
+
+ svn_stringbuf_appendcstr
+ (header,
+ apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"),
+ path, node->copyfrom_rev, base_path));
+
+ SVN_ERR(svn_fs_revision_root(&base_root,
+ svn_fs_root_fs(base_root),
+ node->copyfrom_rev, pool));
+ }
+
+ /*** First, we'll just print file content diffs. ***/
+ if (node->kind == svn_node_file)
+ {
+ /* Here's the generalized way we do our diffs:
+
+ - First, we'll check for svn:mime-type properties on the old
+ and new files. If either has such a property, and it
+ represents a binary type, we won't actually be doing a real
+ diff.
+
+ - Second, dump the contents of the new version of the file
+ into the temporary directory.
+
+ - Then, dump the contents of the old version of the file into
+ the temporary directory.
+
+ - Next, we run 'diff', passing the repository paths as the
+ labels.
+
+ - Finally, we delete the temporary files. */
+ if (node->action == 'R' && node->text_mod)
+ {
+ do_diff = TRUE;
+ SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
+ base_root, base_path, root, path,
+ tmpdir, pool));
+ }
+ else if (c->diff_copy_from && node->action == 'A' && is_copy)
+ {
+ if (node->text_mod)
+ {
+ do_diff = TRUE;
+ SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
+ base_root, base_path, root, path,
+ tmpdir, pool));
+ }
+ }
+ else if (! c->no_diff_added && node->action == 'A')
+ {
+ do_diff = TRUE;
+ orig_empty = TRUE;
+ SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
+ NULL, base_path, root, path,
+ tmpdir, pool));
+ }
+ else if (! c->no_diff_deleted && node->action == 'D')
+ {
+ do_diff = TRUE;
+ SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
+ base_root, base_path, NULL, path,
+ tmpdir, pool));
+ }
+
+ /* The header for the copy case has already been created, and we don't
+ want a header here for files with only property modifications. */
+ if (header->len == 0
+ && (node->action != 'R' || node->text_mod))
+ {
+ svn_stringbuf_appendcstr
+ (header, apr_psprintf(pool, "%s: %s\n",
+ ((node->action == 'A') ? _("Added") :
+ ((node->action == 'D') ? _("Deleted") :
+ ((node->action == 'R') ? _("Modified")
+ : _("Index")))),
+ path));
+ }
+ }
+
+ if (do_diff && (! c->properties_only))
+ {
+ svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n");
+
+ if (binary)
+ {
+ svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n"));
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "%s", header->data));
+ }
+ else
+ {
+ if (c->diff_cmd)
+ {
+ apr_file_t *outfile;
+ apr_file_t *errfile;
+ const char *outfilename;
+ const char *errfilename;
+ svn_stream_t *stream;
+ svn_stream_t *err_stream;
+ const char **diff_cmd_argv;
+ int diff_cmd_argc;
+ int exitcode;
+ const char *orig_label;
+ const char *new_label;
+
+ diff_cmd_argv = NULL;
+ diff_cmd_argc = c->diff_options->nelts;
+ if (diff_cmd_argc)
+ {
+ int i;
+ diff_cmd_argv = apr_palloc(pool,
+ diff_cmd_argc * sizeof(char *));
+ for (i = 0; i < diff_cmd_argc; i++)
+ SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i],
+ APR_ARRAY_IDX(c->diff_options, i, const char *),
+ pool));
+ }
+
+ /* Print diff header. */
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "%s", header->data));
+
+ if (orig_empty)
+ SVN_ERR(generate_label(&orig_label, NULL, path, pool));
+ else
+ SVN_ERR(generate_label(&orig_label, base_root,
+ base_path, pool));
+ SVN_ERR(generate_label(&new_label, root, path, pool));
+
+ /* We deal in streams, but svn_io_run_diff2() deals in file
+ handles, unfortunately, so we need to make these temporary
+ files, and then copy the contents to our stream. */
+ SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
+ svn_io_file_del_on_pool_cleanup, pool, pool));
+ SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
+ svn_io_file_del_on_pool_cleanup, pool, pool));
+
+ SVN_ERR(svn_io_run_diff2(".",
+ diff_cmd_argv,
+ diff_cmd_argc,
+ orig_label, new_label,
+ orig_path, new_path,
+ &exitcode, outfile, errfile,
+ c->diff_cmd, pool));
+
+ SVN_ERR(svn_io_file_close(outfile, pool));
+ SVN_ERR(svn_io_file_close(errfile, pool));
+
+ /* Now, open and copy our files to our output streams. */
+ SVN_ERR(svn_stream_for_stderr(&err_stream, pool));
+ SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(stream,
+ svn_stream_disown(out_stream, pool),
+ NULL, NULL, pool));
+ SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(stream,
+ svn_stream_disown(err_stream, pool),
+ NULL, NULL, pool));
+
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "\n"));
+ diff_header_printed = TRUE;
+ }
+ else
+ {
+ svn_diff_t *diff;
+ svn_diff_file_options_t *opts = svn_diff_file_options_create(pool);
+
+ if (c->diff_options)
+ SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool));
+
+ SVN_ERR(svn_diff_file_diff_2(&diff, orig_path,
+ new_path, opts, pool));
+
+ if (svn_diff_contains_diffs(diff))
+ {
+ const char *orig_label, *new_label;
+
+ /* Print diff header. */
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "%s", header->data));
+
+ if (orig_empty)
+ SVN_ERR(generate_label(&orig_label, NULL, path, pool));
+ else
+ SVN_ERR(generate_label(&orig_label, base_root,
+ base_path, pool));
+ SVN_ERR(generate_label(&new_label, root, path, pool));
+ SVN_ERR(svn_diff_file_output_unified3
+ (out_stream, diff, orig_path, new_path,
+ orig_label, new_label,
+ svn_cmdline_output_encoding(pool), NULL,
+ opts->show_c_function, pool));
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "\n"));
+ diff_header_printed = TRUE;
+ }
+ else if (! node->prop_mod &&
+ ((! c->no_diff_added && node->action == 'A') ||
+ (! c->no_diff_deleted && node->action == 'D')))
+ {
+ /* There was an empty file added or deleted in this revision.
+ * We can't print a diff, but we can at least print
+ * a diff header since we know what happened to this file. */
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "%s", header->data));
+ }
+ }
+ }
+ }
+
+ /* Make sure we delete any temporary files. */
+ if (orig_path)
+ SVN_ERR(svn_io_remove_file2(orig_path, FALSE, pool));
+ if (new_path)
+ SVN_ERR(svn_io_remove_file2(new_path, FALSE, pool));
+
+ /*** Now handle property diffs ***/
+ if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties))
+ {
+ apr_hash_t *local_proptable;
+ apr_hash_t *base_proptable;
+ apr_array_header_t *propchanges, *props;
+
+ SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool));
+ if (c->diff_copy_from && node->action == 'A' && is_copy)
+ SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
+ base_path, pool));
+ else if (node->action == 'A')
+ base_proptable = apr_hash_make(pool);
+ else /* node->action == 'R' */
+ SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
+ base_path, pool));
+ SVN_ERR(svn_prop_diffs(&propchanges, local_proptable,
+ base_proptable, pool));
+ SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool));
+ if (props->nelts > 0)
+ {
+ /* We print a diff header for the case when we only have property
+ * mods. */
+ if (! diff_header_printed)
+ {
+ const char *orig_label, *new_label;
+
+ SVN_ERR(generate_label(&orig_label, base_root, base_path,
+ pool));
+ SVN_ERR(generate_label(&new_label, root, path, pool));
+
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "Index: %s\n", path));
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ SVN_DIFF__EQUAL_STRING "\n"));
+ /* --- <label1>
+ * +++ <label2> */
+ SVN_ERR(svn_diff__unidiff_write_header(
+ out_stream, encoding, orig_label, new_label, pool));
+ }
+ SVN_ERR(display_prop_diffs(out_stream, encoding,
+ props, base_proptable, path, pool));
+ }
+ }
+
+ /* Return here if the node has no children. */
+ node = node->child;
+ if (! node)
+ return SVN_NO_ERROR;
+
+ /* Recursively handle the node's children. */
+ subpool = svn_pool_create(pool);
+ SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
+ svn_dirent_join(path, node->name, subpool),
+ svn_dirent_join(base_path, node->name, subpool),
+ c, tmpdir, subpool));
+ while (node->sibling)
+ {
+ svn_pool_clear(subpool);
+ node = node->sibling;
+ SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
+ svn_dirent_join(path, node->name, subpool),
+ svn_dirent_join(base_path, node->name, subpool),
+ c, tmpdir, subpool));
+ }
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print a repository directory, maybe recursively, possibly showing
+ the node revision ids, and optionally using full paths.
+
+ ROOT is the revision or transaction root used to build that tree.
+ PATH and ID are the current path and node revision id being
+ printed, and INDENTATION the number of spaces to prepent to that
+ path's printed output. ID may be NULL if SHOW_IDS is FALSE (in
+ which case, ids won't be printed at all). If RECURSE is TRUE,
+ then print the tree recursively; otherwise, we'll stop after the
+ first level (and use INDENTATION to keep track of how deep we are).
+
+ Use POOL for all allocations. */
+static svn_error_t *
+print_tree(svn_fs_root_t *root,
+ const char *path /* UTF-8! */,
+ const svn_fs_id_t *id,
+ svn_boolean_t is_dir,
+ int indentation,
+ svn_boolean_t show_ids,
+ svn_boolean_t full_paths,
+ svn_boolean_t recurse,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool;
+ apr_hash_t *entries;
+ const char* name;
+
+ SVN_ERR(check_cancel(NULL));
+
+ /* Print the indentation. */
+ if (!full_paths)
+ {
+ int i;
+ for (i = 0; i < indentation; i++)
+ SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
+ }
+
+ /* ### The path format is inconsistent.. needs fix */
+ if (full_paths)
+ name = path;
+ else if (*path == '/')
+ name = svn_fspath__basename(path, pool);
+ else
+ name = svn_relpath_basename(path, NULL);
+
+ if (svn_path_is_empty(name))
+ name = "/"; /* basename of '/' is "" */
+
+ /* Print the node. */
+ SVN_ERR(svn_cmdline_printf(pool, "%s%s",
+ name,
+ is_dir && strcmp(name, "/") ? "/" : ""));
+
+ if (show_ids)
+ {
+ svn_string_t *unparsed_id = NULL;
+ if (id)
+ unparsed_id = svn_fs_unparse_id(id, pool);
+ SVN_ERR(svn_cmdline_printf(pool, " <%s>",
+ unparsed_id
+ ? unparsed_id->data
+ : _("unknown")));
+ }
+ SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
+
+ /* Return here if PATH is not a directory. */
+ if (! is_dir)
+ return SVN_NO_ERROR;
+
+ /* Recursively handle the node's children. */
+ if (recurse || (indentation == 0))
+ {
+ apr_array_header_t *sorted_entries;
+ int i;
+
+ SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool));
+ subpool = svn_pool_create(pool);
+ sorted_entries = svn_sort__hash(entries,
+ svn_sort_compare_items_lexically, pool);
+ for (i = 0; i < sorted_entries->nelts; i++)
+ {
+ svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i,
+ svn_sort__item_t);
+ svn_fs_dirent_t *entry = item.value;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(print_tree(root,
+ (*path == '/')
+ ? svn_fspath__join(path, entry->name, pool)
+ : svn_relpath_join(path, entry->name, pool),
+ entry->id, (entry->kind == svn_node_dir),
+ indentation + 1, show_ids, full_paths,
+ recurse, subpool));
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *BASE_REV to the revision on which the target root specified in
+ C is based, or to SVN_INVALID_REVNUM when C represents "revision
+ 0" (because that revision isn't based on another revision). */
+static svn_error_t *
+get_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ if (c->is_revision)
+ {
+ *base_rev = c->rev_id - 1;
+ }
+ else
+ {
+ *base_rev = svn_fs_txn_base_revision(c->txn);
+
+ if (! SVN_IS_VALID_REVNUM(*base_rev))
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("Transaction '%s' is not based on a revision; how odd"),
+ c->txn_name);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Subcommand handlers. ***/
+
+/* Print the revision's log message to stdout, followed by a newline. */
+static svn_error_t *
+do_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool)
+{
+ svn_string_t *prop_value;
+ const char *prop_value_eol, *prop_value_native;
+ svn_stream_t *stream;
+ svn_error_t *err;
+ apr_size_t len;
+
+ SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool));
+ if (! (prop_value && prop_value->data))
+ {
+ SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : ""));
+ return SVN_NO_ERROR;
+ }
+
+ /* We immitate what svn_cmdline_printf does here, since we need the byte
+ size of what we are going to print. */
+
+ SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol,
+ APR_EOL_STR, TRUE,
+ NULL, FALSE, pool));
+
+ err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol,
+ pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol,
+ pool);
+ }
+
+ len = strlen(prop_value_native);
+
+ if (print_size)
+ SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len));
+
+ /* Use a stream to bypass all stdio translations. */
+ SVN_ERR(svn_cmdline_fflush(stdout));
+ SVN_ERR(svn_stream_for_stdout(&stream, pool));
+ SVN_ERR(svn_stream_write(stream, prop_value_native, &len));
+ SVN_ERR(svn_stream_close(stream));
+
+ SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print the timestamp of the commit (in the revision case) or the
+ empty string (in the transaction case) to stdout, followed by a
+ newline. */
+static svn_error_t *
+do_date(svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ svn_string_t *prop_value;
+
+ SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool));
+ if (prop_value && prop_value->data)
+ {
+ /* Convert the date for humans. */
+ apr_time_t aprtime;
+ const char *time_utf8;
+
+ SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool));
+
+ time_utf8 = svn_time_to_human_cstring(aprtime, pool);
+
+ SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8));
+ }
+
+ SVN_ERR(svn_cmdline_printf(pool, "\n"));
+ return SVN_NO_ERROR;
+}
+
+
+/* Print the author of the commit to stdout, followed by a newline. */
+static svn_error_t *
+do_author(svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ svn_string_t *prop_value;
+
+ SVN_ERR(get_property(&prop_value, c,
+ SVN_PROP_REVISION_AUTHOR, pool));
+ if (prop_value && prop_value->data)
+ SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data));
+
+ SVN_ERR(svn_cmdline_printf(pool, "\n"));
+ return SVN_NO_ERROR;
+}
+
+
+/* Print a list of all directories in which files, or directory
+ properties, have been modified. */
+static svn_error_t *
+do_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_revnum_t base_rev_id;
+ svn_repos_node_t *tree;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(get_base_rev(&base_rev_id, c, pool));
+ if (base_rev_id == SVN_INVALID_REVNUM)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
+ if (tree)
+ SVN_ERR(print_dirs_changed_tree(tree, "", pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *KIND to PATH's kind, if PATH exists.
+ *
+ * If PATH does not exist, then error; the text of the error depends
+ * on whether PATH looks like a URL or not.
+ */
+static svn_error_t *
+verify_path(svn_node_kind_t *kind,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs_check_path(kind, root, path, pool));
+
+ if (*kind == svn_node_none)
+ {
+ if (svn_path_is_url(path)) /* check for a common mistake. */
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL,
+ _("'%s' is a URL, probably should be a path"), path);
+ else
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print the size (in bytes) of a file. */
+static svn_error_t *
+do_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_node_kind_t kind;
+ svn_filesize_t length;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(verify_path(&kind, root, path, pool));
+
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
+
+ /* Else. */
+
+ SVN_ERR(svn_fs_file_length(&length, root, path, pool));
+ return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length);
+}
+
+/* Print the contents of the file at PATH in the repository.
+ Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
+ SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */
+static svn_error_t *
+do_cat(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_node_kind_t kind;
+ svn_stream_t *fstream, *stdout_stream;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(verify_path(&kind, root, path, pool));
+
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
+
+ /* Else. */
+
+ SVN_ERR(svn_fs_file_contents(&fstream, root, path, pool));
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool),
+ check_cancel, NULL, pool);
+}
+
+
+/* Print a list of all paths modified in a format compatible with `svn
+ update'. */
+static svn_error_t *
+do_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_revnum_t base_rev_id;
+ svn_repos_node_t *tree;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(get_base_rev(&base_rev_id, c, pool));
+ if (base_rev_id == SVN_INVALID_REVNUM)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
+ if (tree)
+ SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print some diff-y stuff in a TBD way. :-) */
+static svn_error_t *
+do_diff(svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ svn_fs_root_t *root, *base_root;
+ svn_revnum_t base_rev_id;
+ svn_repos_node_t *tree;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(get_base_rev(&base_rev_id, c, pool));
+ if (base_rev_id == SVN_INVALID_REVNUM)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
+ if (tree)
+ {
+ const char *tmpdir;
+ svn_stream_t *out_stream;
+ const char *encoding = svn_cmdline_output_encoding(pool);
+
+ SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool));
+ SVN_ERR(svn_io_temp_dir(&tmpdir, pool));
+
+ /* This fflush() might seem odd, but it was added to deal
+ with this bug report:
+
+ http://subversion.tigris.org/servlets/ReadMsg?\
+ list=dev&msgNo=140782
+
+ From: "Steve Hay" <SteveHay{_AT_}planit.com>
+ To: <dev@subversion.tigris.org>
+ Subject: svnlook diff output in wrong order when redirected
+ Date: Fri, 4 Jul 2008 16:34:15 +0100
+ Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\
+ ukmail02.planit.group>
+
+ Adding the fflush() fixed the bug (not everyone could
+ reproduce it, but those who could confirmed the fix).
+ Later in the thread, Daniel Shahaf speculated as to
+ why the fix works:
+
+ "Because svn_cmdline_printf() uses the standard
+ 'FILE *stdout' to write to stdout, while
+ svn_stream_for_stdout() uses (through
+ apr_file_open_stdout()) Windows API's to get a
+ handle for stdout?" */
+ SVN_ERR(svn_cmdline_fflush(stdout));
+ SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
+
+ SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree,
+ "", "", c, tmpdir, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Callback baton for print_history() (and do_history()). */
+struct print_history_baton
+{
+ svn_fs_t *fs;
+ svn_boolean_t show_ids; /* whether to show node IDs */
+ apr_size_t limit; /* max number of history items */
+ apr_size_t count; /* number of history items processed */
+};
+
+/* Implements svn_repos_history_func_t interface. Print the history
+ that's reported through this callback, possibly finding and
+ displaying node-rev-ids. */
+static svn_error_t *
+print_history(void *baton,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ struct print_history_baton *phb = baton;
+
+ SVN_ERR(check_cancel(NULL));
+
+ if (phb->show_ids)
+ {
+ const svn_fs_id_t *node_id;
+ svn_fs_root_t *rev_root;
+ svn_string_t *id_string;
+
+ SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool));
+ SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool));
+ id_string = svn_fs_unparse_id(node_id, pool);
+ SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n",
+ revision, path, id_string->data));
+ }
+ else
+ {
+ SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path));
+ }
+
+ if (phb->limit > 0)
+ {
+ phb->count++;
+ if (phb->count >= phb->limit)
+ /* Not L10N'd, since this error is supressed by the caller. */
+ return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL,
+ _("History item limit reached"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print a tabular display of history location points for PATH in
+ revision C->rev_id. Optionally, SHOW_IDS. Use POOL for
+ allocations. */
+static svn_error_t *
+do_history(svnlook_ctxt_t *c,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct print_history_baton args;
+
+ if (c->show_ids)
+ {
+ SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n"
+ "-------- ---------\n")));
+ }
+ else
+ {
+ SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n"
+ "-------- ----\n")));
+ }
+
+ /* Call our history crawler. We want the whole lifetime of the path
+ (prior to the user-supplied revision, of course), across all
+ copies. */
+ args.fs = c->fs;
+ args.show_ids = c->show_ids;
+ args.limit = c->limit;
+ args.count = 0;
+ SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args,
+ NULL, NULL, 0, c->rev_id, TRUE, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Print the value of property PROPNAME on PATH in the repository.
+
+ If VERBOSE, print their values too. If SHOW_INHERITED_PROPS, print
+ PATH's inherited props too.
+
+ Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If
+ SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND
+ if there is no such property on PATH. If SHOW_INHERITED_PROPS is TRUE,
+ then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such
+ property on PATH nor inherited by path.
+
+ If PATH is NULL, operate on a revision property. */
+static svn_error_t *
+do_pget(svnlook_ctxt_t *c,
+ const char *propname,
+ const char *path,
+ svn_boolean_t verbose,
+ svn_boolean_t show_inherited_props,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_string_t *prop;
+ svn_node_kind_t kind;
+ svn_stream_t *stdout_stream;
+ apr_size_t len;
+ apr_array_header_t *inherited_props = NULL;
+
+ SVN_ERR(get_root(&root, c, pool));
+ if (path != NULL)
+ {
+ path = svn_fspath__canonicalize(path, pool);
+ SVN_ERR(verify_path(&kind, root, path, pool));
+ SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool));
+
+ if (show_inherited_props)
+ {
+ SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
+ path, propname, NULL,
+ NULL, pool, pool));
+ }
+ }
+ else /* --revprop */
+ {
+ SVN_ERR(get_property(&prop, c, propname, pool));
+ }
+
+ /* Did we find nothing? */
+ if (prop == NULL
+ && (!show_inherited_props || inherited_props->nelts == 0))
+ {
+ const char *err_msg;
+ if (path == NULL)
+ {
+ /* We're operating on a revprop (e.g. c->is_revision). */
+ err_msg = apr_psprintf(pool,
+ _("Property '%s' not found on revision %ld"),
+ propname, c->rev_id);
+ }
+ else
+ {
+ if (SVN_IS_VALID_REVNUM(c->rev_id))
+ {
+ if (show_inherited_props)
+ err_msg = apr_psprintf(pool,
+ _("Property '%s' not found on path '%s' "
+ "or inherited from a parent "
+ "in revision %ld"),
+ propname, path, c->rev_id);
+ else
+ err_msg = apr_psprintf(pool,
+ _("Property '%s' not found on path '%s' "
+ "in revision %ld"),
+ propname, path, c->rev_id);
+ }
+ else
+ {
+ if (show_inherited_props)
+ err_msg = apr_psprintf(pool,
+ _("Property '%s' not found on path '%s' "
+ "or inherited from a parent "
+ "in transaction %s"),
+ propname, path, c->txn_name);
+ else
+ err_msg = apr_psprintf(pool,
+ _("Property '%s' not found on path '%s' "
+ "in transaction %s"),
+ propname, path, c->txn_name);
+ }
+ }
+ return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg);
+ }
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ if (verbose || show_inherited_props)
+ {
+ if (inherited_props)
+ {
+ int i;
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt =
+ APR_ARRAY_IDX(inherited_props, i,
+ svn_prop_inherited_item_t *);
+
+ if (verbose)
+ {
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ _("Inherited properties on '%s',\nfrom '%s':\n"),
+ path, svn_fspath__canonicalize(elt->path_or_url,
+ pool)));
+ SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream,
+ elt->prop_hash,
+ !verbose, pool));
+ }
+ else
+ {
+ svn_string_t *propval =
+ svn__apr_hash_index_val(apr_hash_first(pool,
+ elt->prop_hash));
+
+ SVN_ERR(svn_stream_printf(
+ stdout_stream, pool, "%s - ",
+ svn_fspath__canonicalize(elt->path_or_url, pool)));
+ len = propval->len;
+ SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len));
+ /* If we have more than one property to write, then add a newline*/
+ if (inherited_props->nelts > 1 || prop)
+ {
+ len = strlen(APR_EOL_STR);
+ SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len));
+ }
+ }
+ }
+ }
+
+ if (prop)
+ {
+ if (verbose)
+ {
+ apr_hash_t *hash = apr_hash_make(pool);
+
+ svn_hash_sets(hash, propname, prop);
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ _("Properties on '%s':\n"), path));
+ SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash,
+ FALSE, pool));
+ }
+ else
+ {
+ SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path));
+ len = prop->len;
+ SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
+ }
+ }
+ }
+ else /* Raw single prop output, i.e. non-verbose output with no
+ inherited props. */
+ {
+ /* Unlike the command line client, we don't translate the property
+ value or print a trailing newline here. We just output the raw
+ bytes of whatever's in the repository, as svnlook is more likely
+ to be used for automated inspections. */
+ len = prop->len;
+ SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print the property names of all properties on PATH in the repository.
+
+ If VERBOSE, print their values too. If XML, print as XML rather than as
+ plain text. If SHOW_INHERITED_PROPS, print PATH's inherited props too.
+
+ Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist.
+
+ If PATH is NULL, operate on a revision properties. */
+static svn_error_t *
+do_plist(svnlook_ctxt_t *c,
+ const char *path,
+ svn_boolean_t verbose,
+ svn_boolean_t xml,
+ svn_boolean_t show_inherited_props,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ apr_hash_t *props;
+ apr_hash_index_t *hi;
+ svn_node_kind_t kind;
+ svn_stringbuf_t *sb = NULL;
+ svn_boolean_t revprop = FALSE;
+ apr_array_header_t *inherited_props = NULL;
+
+ if (path != NULL)
+ {
+ /* PATH might be the root of the repsository and we accept both
+ "" and "/". But to avoid the somewhat cryptic output like this:
+
+ >svnlook pl repos-path ""
+ Properties on '':
+ svn:auto-props
+ svn:global-ignores
+
+ We canonicalize PATH so that is has a leading slash. */
+ path = svn_fspath__canonicalize(path, pool);
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(verify_path(&kind, root, path, pool));
+ SVN_ERR(svn_fs_node_proplist(&props, root, path, pool));
+
+ if (show_inherited_props)
+ SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
+ path, NULL, NULL, NULL,
+ pool, pool));
+ }
+ else if (c->is_revision)
+ {
+ SVN_ERR(svn_fs_revision_proplist(&props, c->fs, c->rev_id, pool));
+ revprop = TRUE;
+ }
+ else
+ {
+ SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool));
+ revprop = TRUE;
+ }
+
+ if (xml)
+ {
+ /* <?xml version="1.0" encoding="UTF-8"?> */
+ svn_xml_make_header2(&sb, "UTF-8", pool);
+
+ /* "<properties>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties", NULL);
+ }
+
+ if (inherited_props)
+ {
+ int i;
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ /* Canonicalize the inherited parent paths for consistency
+ with PATH. */
+ if (xml)
+ {
+ svn_xml_make_open_tag(
+ &sb, pool, svn_xml_normal, "target", "path",
+ svn_fspath__canonicalize(elt->path_or_url, pool),
+ NULL);
+ SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, elt->prop_hash,
+ !verbose, TRUE,
+ pool));
+ svn_xml_make_close_tag(&sb, pool, "target");
+ }
+ else
+ {
+ SVN_ERR(svn_cmdline_printf(
+ pool, _("Inherited properties on '%s',\nfrom '%s':\n"),
+ path, svn_fspath__canonicalize(elt->path_or_url, pool)));
+ SVN_ERR(svn_cmdline__print_prop_hash(NULL, elt->prop_hash,
+ !verbose, pool));
+ }
+ }
+ }
+
+ if (xml)
+ {
+ if (revprop)
+ {
+ /* "<revprops ...>" */
+ if (c->is_revision)
+ {
+ char *revstr = apr_psprintf(pool, "%ld", c->rev_id);
+
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
+ "rev", revstr, NULL);
+ }
+ else
+ {
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
+ "txn", c->txn_name, NULL);
+ }
+ }
+ else
+ {
+ /* "<target ...>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
+ "path", path, NULL);
+ }
+ }
+
+ if (!xml && path /* Not a --revprop */)
+ SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), path));
+
+ for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
+ {
+ const char *pname = svn__apr_hash_index_key(hi);
+ svn_string_t *propval = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(check_cancel(NULL));
+
+ /* Since we're already adding a trailing newline (and possible a
+ colon and some spaces) anyway, just mimic the output of the
+ command line client proplist. Compare to 'svnlook propget',
+ which sends the raw bytes to stdout, untranslated. */
+ /* We leave printf calls here, since we don't always know the encoding
+ of the prop value. */
+ if (svn_prop_needs_translation(pname))
+ SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool));
+
+ if (verbose)
+ {
+ if (xml)
+ svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool);
+ else
+ {
+ const char *pname_stdout;
+ const char *indented_newval;
+
+ SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname,
+ pool));
+ printf(" %s\n", pname_stdout);
+ /* Add an extra newline to the value before indenting, so that
+ every line of output has the indentation whether the value
+ already ended in a newline or not. */
+ indented_newval =
+ svn_cmdline__indent_string(apr_psprintf(pool, "%s\n",
+ propval->data),
+ " ", pool);
+ printf("%s", indented_newval);
+ }
+ }
+ else if (xml)
+ svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "property",
+ "name", pname, NULL);
+ else
+ printf(" %s\n", pname);
+ }
+ if (xml)
+ {
+ errno = 0;
+ if (revprop)
+ {
+ /* "</revprops>" */
+ svn_xml_make_close_tag(&sb, pool, "revprops");
+ }
+ else
+ {
+ /* "</target>" */
+ svn_xml_make_close_tag(&sb, pool, "target");
+ }
+
+ /* "</properties>" */
+ svn_xml_make_close_tag(&sb, pool, "properties");
+
+ if (fputs(sb->data, stdout) == EOF)
+ {
+ if (errno)
+ return svn_error_wrap_apr(errno, _("Write error"));
+ else
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+do_tree(svnlook_ctxt_t *c,
+ const char *path,
+ svn_boolean_t show_ids,
+ svn_boolean_t full_paths,
+ svn_boolean_t recurse,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ const svn_fs_id_t *id;
+ svn_boolean_t is_dir;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(svn_fs_node_id(&id, root, path, pool));
+ SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool));
+ SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths,
+ recurse, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Custom filesystem warning function. */
+static void
+warning_func(void *baton,
+ svn_error_t *err)
+{
+ if (! err)
+ return;
+ svn_handle_error2(err, stderr, FALSE, "svnlook: ");
+}
+
+
+/* Return an error if the number of arguments (excluding the repository
+ * argument) is not NUM_ARGS. NUM_ARGS must be 0 or 1. The arguments
+ * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */
+static svn_error_t *
+check_number_of_args(struct svnlook_opt_state *opt_state,
+ int num_args)
+{
+ if ((num_args == 0 && opt_state->arg1 != NULL)
+ || (num_args == 1 && opt_state->arg2 != NULL))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+ if ((num_args == 1 && opt_state->arg1 == NULL))
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Missing repository path argument"));
+ return SVN_NO_ERROR;
+}
+
+
+/* Factory function for the context baton. */
+static svn_error_t *
+get_ctxt_baton(svnlook_ctxt_t **baton_p,
+ struct svnlook_opt_state *opt_state,
+ apr_pool_t *pool)
+{
+ svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton));
+
+ SVN_ERR(svn_repos_open2(&(baton->repos), opt_state->repos_path, NULL,
+ pool));
+ baton->fs = svn_repos_fs(baton->repos);
+ svn_fs_set_warning_func(baton->fs, warning_func, NULL);
+ baton->show_ids = opt_state->show_ids;
+ baton->limit = opt_state->limit;
+ baton->no_diff_deleted = opt_state->no_diff_deleted;
+ baton->no_diff_added = opt_state->no_diff_added;
+ baton->diff_copy_from = opt_state->diff_copy_from;
+ baton->full_paths = opt_state->full_paths;
+ baton->copy_info = opt_state->copy_info;
+ baton->is_revision = opt_state->txn == NULL;
+ baton->rev_id = opt_state->rev;
+ baton->txn_name = apr_pstrdup(pool, opt_state->txn);
+ baton->diff_options = svn_cstring_split(opt_state->extensions
+ ? opt_state->extensions : "",
+ " \t\n\r", TRUE, pool);
+ baton->ignore_properties = opt_state->ignore_properties;
+ baton->properties_only = opt_state->properties_only;
+ baton->diff_cmd = opt_state->diff_cmd;
+
+ if (baton->txn_name)
+ SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs,
+ baton->txn_name, pool));
+ else if (baton->rev_id == SVN_INVALID_REVNUM)
+ SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool));
+
+ *baton_p = baton;
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Subcommands. ***/
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_author(c, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 1));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_cat(c, opt_state->arg1, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_changed(c, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_date(c, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_diff(c, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_dirs_changed(c, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 1));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_filesize(c, opt_state->arg1, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ const char *header =
+ _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
+ "Note: any subcommand which takes the '--revision' and '--transaction'\n"
+ " options will, if invoked without one of those options, act on\n"
+ " the repository's youngest revision.\n"
+ "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n"
+ "Type 'svnlook --version' to see the program version and FS modules.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ const char *fs_desc_start
+ = _("The following repository back-end (FS) modules are available:\n\n");
+
+ svn_stringbuf_t *version_footer;
+
+ version_footer = svn_stringbuf_create(fs_desc_start, pool);
+ SVN_ERR(svn_fs_print_modules(version_footer, pool));
+
+ SVN_ERR(svn_opt_print_help4(os, "svnlook",
+ opt_state ? opt_state->version : FALSE,
+ opt_state ? opt_state->quiet : FALSE,
+ opt_state ? opt_state->verbose : FALSE,
+ version_footer->data,
+ header, cmd_table, options_table, NULL,
+ NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+ const char *path = (opt_state->arg1 ? opt_state->arg1 : "/");
+
+ if (opt_state->arg2 != NULL)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_history(c, path, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+ svn_lock_t *lock;
+
+ SVN_ERR(check_number_of_args(opt_state, 1));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+
+ SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool));
+
+ if (lock)
+ {
+ const char *cr_date, *exp_date = "";
+ int comment_lines = 0;
+
+ cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
+
+ if (lock->expiration_date)
+ exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
+
+ if (lock->comment)
+ comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
+
+ SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
+ SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
+ SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
+ SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
+ SVN_ERR(svn_cmdline_printf(pool,
+ Q_("Comment (%i line):\n%s\n",
+ "Comment (%i lines):\n%s\n",
+ comment_lines),
+ comment_lines,
+ lock->comment ? lock->comment : ""));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_author(c, pool));
+ SVN_ERR(do_date(c, pool));
+ SVN_ERR(do_log(c, TRUE, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_log(c, FALSE, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ if (opt_state->arg1 == NULL)
+ {
+ return svn_error_createf
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ opt_state->revprop ? _("Missing propname argument") :
+ _("Missing propname and repository path arguments"));
+ }
+ else if (!opt_state->revprop && opt_state->arg2 == NULL)
+ {
+ return svn_error_create
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Missing propname or repository path argument"));
+ }
+ if ((opt_state->revprop && opt_state->arg2 != NULL)
+ || os->ind < os->argc)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_pget(c, opt_state->arg1,
+ opt_state->revprop ? NULL : opt_state->arg2,
+ opt_state->verbose, opt_state->show_inherited_props,
+ pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1,
+ opt_state->verbose, opt_state->xml,
+ opt_state->show_inherited_props, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ if (opt_state->arg2 != NULL)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "",
+ opt_state->show_ids, opt_state->full_paths,
+ ! opt_state->non_recursive, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(svn_cmdline_printf(pool, "%ld\n", c->rev_id));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+ const char *uuid;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool));
+ SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid));
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Main. ***/
+
+int
+main(int argc, const char *argv[])
+{
+ svn_error_t *err;
+ apr_status_t apr_err;
+ apr_pool_t *pool;
+
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+ struct svnlook_opt_state opt_state;
+ apr_getopt_t *os;
+ int opt_id;
+ apr_array_header_t *received_opts;
+ int i;
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ /* Check library versions */
+ err = check_lib_versions();
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+
+ /* Initialize the FS library. */
+ err = svn_fs_initialize(pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+
+ if (argc <= 1)
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ /* Initialize opt_state. */
+ memset(&opt_state, 0, sizeof(opt_state));
+ opt_state.rev = SVN_INVALID_REVNUM;
+
+ /* Parse options. */
+ err = svn_cmdline__getopt_init(&os, argc, argv, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+
+ os->interleave = 1;
+ while (1)
+ {
+ const char *opt_arg;
+
+ /* Parse the next option. */
+ apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
+ if (APR_STATUS_IS_EOF(apr_err))
+ break;
+ else if (apr_err)
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ /* Stash the option code in an array before parsing it. */
+ APR_ARRAY_PUSH(received_opts, int) = opt_id;
+
+ switch (opt_id)
+ {
+ case 'r':
+ {
+ char *digits_end = NULL;
+ opt_state.rev = strtol(opt_arg, &digits_end, 10);
+ if ((! SVN_IS_VALID_REVNUM(opt_state.rev))
+ || (! digits_end)
+ || *digits_end)
+ SVN_INT_ERR(svn_error_create
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid revision number supplied")));
+ }
+ break;
+
+ case 't':
+ opt_state.txn = opt_arg;
+ break;
+
+ case 'N':
+ opt_state.non_recursive = TRUE;
+ break;
+
+ case 'v':
+ opt_state.verbose = TRUE;
+ break;
+
+ case 'h':
+ case '?':
+ opt_state.help = TRUE;
+ break;
+
+ case 'q':
+ opt_state.quiet = TRUE;
+ break;
+
+ case svnlook__revprop_opt:
+ opt_state.revprop = TRUE;
+ break;
+
+ case svnlook__xml_opt:
+ opt_state.xml = TRUE;
+ break;
+
+ case svnlook__version:
+ opt_state.version = TRUE;
+ break;
+
+ case svnlook__show_ids:
+ opt_state.show_ids = TRUE;
+ break;
+
+ case 'l':
+ {
+ char *end;
+ opt_state.limit = strtol(opt_arg, &end, 10);
+ if (end == opt_arg || *end != '\0')
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Non-numeric limit argument given"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+ }
+ if (opt_state.limit <= 0)
+ {
+ err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Argument to --limit must be positive"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+ }
+ }
+ break;
+
+ case svnlook__no_diff_deleted:
+ opt_state.no_diff_deleted = TRUE;
+ break;
+
+ case svnlook__no_diff_added:
+ opt_state.no_diff_added = TRUE;
+ break;
+
+ case svnlook__diff_copy_from:
+ opt_state.diff_copy_from = TRUE;
+ break;
+
+ case svnlook__full_paths:
+ opt_state.full_paths = TRUE;
+ break;
+
+ case svnlook__copy_info:
+ opt_state.copy_info = TRUE;
+ break;
+
+ case 'x':
+ opt_state.extensions = opt_arg;
+ break;
+
+ case svnlook__ignore_properties:
+ opt_state.ignore_properties = TRUE;
+ break;
+
+ case svnlook__properties_only:
+ opt_state.properties_only = TRUE;
+ break;
+
+ case svnlook__diff_cmd:
+ opt_state.diff_cmd = opt_arg;
+ break;
+
+ case svnlook__show_inherited_props:
+ opt_state.show_inherited_props = TRUE;
+ break;
+
+ default:
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+
+ }
+ }
+
+ /* The --transaction and --revision options may not co-exist. */
+ if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn)
+ SVN_INT_ERR(svn_error_create
+ (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("The '--transaction' (-t) and '--revision' (-r) arguments "
+ "cannot co-exist")));
+
+ /* The --show-inherited-props and --revprop options may not co-exist. */
+ if (opt_state.show_inherited_props && opt_state.revprop)
+ SVN_INT_ERR(svn_error_create
+ (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("Cannot use the '--show-inherited-props' option with the "
+ "'--revprop' option")));
+
+ /* If the user asked for help, then the rest of the arguments are
+ the names of subcommands to get help on (if any), or else they're
+ just typos/mistakes. Whatever the case, the subcommand to
+ actually run is subcommand_help(). */
+ if (opt_state.help)
+ subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
+
+ /* If we're not running the `help' subcommand, then look for a
+ subcommand in the first argument. */
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_state.version)
+ {
+ /* Use the "help" subcommand to handle the "--version" option. */
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+ { "--version", subcommand_help, {0}, "",
+ {svnlook__version, /* must accept its own option */
+ 'q', 'v',
+ } };
+
+ subcommand = &pseudo_cmd;
+ }
+ else
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Subcommand argument required\n")));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ const char *first_arg = os->argv[os->ind++];
+ subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
+ if (subcommand == NULL)
+ {
+ const char *first_arg_utf8;
+ err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
+ pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+ svn_error_clear(
+ svn_cmdline_fprintf(stderr, pool,
+ _("Unknown subcommand: '%s'\n"),
+ first_arg_utf8));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+
+ /* Be kind to people who try 'svnlook verify'. */
+ if (strcmp(first_arg_utf8, "verify") == 0)
+ {
+ svn_error_clear(
+ svn_cmdline_fprintf(stderr, pool,
+ _("Try 'svnadmin verify' instead.\n")));
+ }
+
+
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ /* If there's a second argument, it's the repository. There may be
+ more arguments following the repository; usually the next one is
+ a path within the repository, or it's a propname and the one
+ after that is the path. Since we don't know, we just call them
+ arg1 and arg2, meaning the first and second arguments following
+ the repository. */
+ if (subcommand->cmd_func != subcommand_help)
+ {
+ const char *repos_path = NULL;
+ const char *arg1 = NULL, *arg2 = NULL;
+
+ /* Get the repository. */
+ if (os->ind < os->argc)
+ {
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&repos_path,
+ os->argv[os->ind++],
+ pool));
+ repos_path = svn_dirent_internal_style(repos_path, pool);
+ }
+
+ if (repos_path == NULL)
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Repository argument required\n")));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ else if (svn_path_is_url(repos_path))
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("'%s' is a URL when it should be a path\n"),
+ repos_path));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ opt_state.repos_path = repos_path;
+
+ /* Get next arg (arg1), if any. */
+ if (os->ind < os->argc)
+ {
+ SVN_INT_ERR(svn_utf_cstring_to_utf8
+ (&arg1, os->argv[os->ind++], pool));
+ arg1 = svn_dirent_internal_style(arg1, pool);
+ }
+ opt_state.arg1 = arg1;
+
+ /* Get next arg (arg2), if any. */
+ if (os->ind < os->argc)
+ {
+ SVN_INT_ERR(svn_utf_cstring_to_utf8
+ (&arg2, os->argv[os->ind++], pool));
+ arg2 = svn_dirent_internal_style(arg2, pool);
+ }
+ opt_state.arg2 = arg2;
+ }
+
+ /* Check that the subcommand wasn't passed any inappropriate options. */
+ for (i = 0; i < received_opts->nelts; i++)
+ {
+ opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ /* All commands implicitly accept --help, so just skip over this
+ when we see it. Note that we don't want to include this option
+ in their "accepted options" list because it would be awfully
+ redundant to display it in every commands' help text. */
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
+ {
+ const char *optstr;
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
+ pool);
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ else
+ svn_error_clear
+ (svn_cmdline_fprintf
+ (stderr, pool,
+ _("Subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svnlook help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* Set up our cancellation support. */
+ apr_signal(SIGINT, signal_handler);
+#ifdef SIGBREAK
+ /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
+ apr_signal(SIGBREAK, signal_handler);
+#endif
+#ifdef SIGHUP
+ apr_signal(SIGHUP, signal_handler);
+#endif
+#ifdef SIGTERM
+ apr_signal(SIGTERM, signal_handler);
+#endif
+
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it, otherwise
+ * working with large files when compiled against an APR that doesn't have
+ * large file support will crash the program, which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /* Run the subcommand. */
+ err = (*subcommand->cmd_func)(os, &opt_state, pool);
+ if (err)
+ {
+ /* For argument-related problems, suggest using the 'help'
+ subcommand. */
+ if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
+ || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Try 'svnlook help' for more info"));
+ }
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+ }
+ else
+ {
+ svn_pool_destroy(pool);
+ /* Ensure everything is printed on stdout, so the user sees any
+ print errors. */
+ SVN_INT_ERR(svn_cmdline_fflush(stdout));
+ return EXIT_SUCCESS;
+ }
+}
diff --git a/contrib/subversion/subversion/svnmucc/svnmucc.1 b/contrib/subversion/subversion/svnmucc/svnmucc.1
new file mode 100644
index 0000000..2b9ae67
--- /dev/null
+++ b/contrib/subversion/subversion/svnmucc/svnmucc.1
@@ -0,0 +1,47 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svnmucc 1
+.SH NAME
+svnmucc \- Multiple URL Command Client for Subversion
+.SH SYNOPSIS
+.TP
+\fBsvnmucc\fP [\fIoptions\fP]
+.SH OVERVIEW
+Subversion is a version control system, which allows you to keep old
+versions of files and directories (usually source code), keep a log of
+who, when, and why changes occurred, etc., like CVS, RCS or SCCS.
+\fBSubversion\fP keeps a single copy of the master sources. This copy
+is called the source ``repository''; it contains all the information
+to permit extracting previous versions of those files at any time.
+
+For more information about the Subversion project, visit
+http://subversion.apache.org.
+
+Documentation for Subversion and its tools, including detailed usage
+explanations of the \fBsvn\fP, \fBsvnadmin\fP, \fBsvnserve\fP and
+\fBsvnlook\fP programs, historical background, philosophical
+approaches and reasonings, etc., can be found at
+http://svnbook.red-bean.com/.
+
+Run `svnmucc --help' to access the built-in tool documentation.
diff --git a/contrib/subversion/subversion/svnmucc/svnmucc.c b/contrib/subversion/subversion/svnmucc/svnmucc.c
new file mode 100644
index 0000000..076a9ee
--- /dev/null
+++ b/contrib/subversion/subversion/svnmucc/svnmucc.c
@@ -0,0 +1,1460 @@
+/*
+ * svnmucc.c: Subversion Multiple URL Client
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ */
+
+/* Multiple URL Command Client
+
+ Combine a list of mv, cp and rm commands on URLs into a single commit.
+
+ How it works: the command line arguments are parsed into an array of
+ action structures. The action structures are interpreted to build a
+ tree of operation structures. The tree of operation structures is
+ used to drive an RA commit editor to produce a single commit.
+
+ To build this client, type 'make svnmucc' from the root of your
+ Subversion source directory.
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include <apr_lib.h>
+
+#include "svn_hash.h"
+#include "svn_client.h"
+#include "svn_cmdline.h"
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_ra.h"
+#include "svn_string.h"
+#include "svn_subst.h"
+#include "svn_utf.h"
+#include "svn_version.h"
+
+#include "private/svn_cmdline_private.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_string_private.h"
+
+#include "svn_private_config.h"
+
+static void handle_error(svn_error_t *err, apr_pool_t *pool)
+{
+ if (err)
+ svn_handle_error2(err, stderr, FALSE, "svnmucc: ");
+ svn_error_clear(err);
+ if (pool)
+ svn_pool_destroy(pool);
+ exit(EXIT_FAILURE);
+}
+
+static apr_pool_t *
+init(const char *application)
+{
+ svn_error_t *err;
+ const svn_version_checklist_t checklist[] = {
+ {"svn_client", svn_client_version},
+ {"svn_subr", svn_subr_version},
+ {"svn_ra", svn_ra_version},
+ {NULL, NULL}
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ if (svn_cmdline_init(application, stderr))
+ exit(EXIT_FAILURE);
+
+ err = svn_ver_check_list(&my_version, checklist);
+ if (err)
+ handle_error(err, NULL);
+
+ return apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+}
+
+static svn_error_t *
+open_tmp_file(apr_file_t **fp,
+ void *callback_baton,
+ apr_pool_t *pool)
+{
+ /* Open a unique file; use APR_DELONCLOSE. */
+ return svn_io_open_unique_file3(fp, NULL, NULL, svn_io_file_del_on_close,
+ pool, pool);
+}
+
+static svn_error_t *
+create_ra_callbacks(svn_ra_callbacks2_t **callbacks,
+ const char *username,
+ const char *password,
+ const char *config_dir,
+ svn_config_t *cfg_config,
+ svn_boolean_t non_interactive,
+ svn_boolean_t trust_server_cert,
+ svn_boolean_t no_auth_cache,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_ra_create_callbacks(callbacks, pool));
+
+ SVN_ERR(svn_cmdline_create_auth_baton(&(*callbacks)->auth_baton,
+ non_interactive,
+ username, password, config_dir,
+ no_auth_cache,
+ trust_server_cert,
+ cfg_config, NULL, NULL, pool));
+
+ (*callbacks)->open_tmp_file = open_tmp_file;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+static svn_error_t *
+commit_callback(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_cmdline_printf(pool, "r%ld committed by %s at %s\n",
+ commit_info->revision,
+ (commit_info->author
+ ? commit_info->author : "(no author)"),
+ commit_info->date));
+ return SVN_NO_ERROR;
+}
+
+typedef enum action_code_t {
+ ACTION_MV,
+ ACTION_MKDIR,
+ ACTION_CP,
+ ACTION_PROPSET,
+ ACTION_PROPSETF,
+ ACTION_PROPDEL,
+ ACTION_PUT,
+ ACTION_RM
+} action_code_t;
+
+struct operation {
+ enum {
+ OP_OPEN,
+ OP_DELETE,
+ OP_ADD,
+ OP_REPLACE,
+ OP_PROPSET /* only for files for which no other operation is
+ occuring; directories are OP_OPEN with non-empty
+ props */
+ } operation;
+ svn_node_kind_t kind; /* to copy, mkdir, put or set revprops */
+ svn_revnum_t rev; /* to copy, valid for add and replace */
+ const char *url; /* to copy, valid for add and replace */
+ const char *src_file; /* for put, the source file for contents */
+ apr_hash_t *children; /* const char *path -> struct operation * */
+ apr_hash_t *prop_mods; /* const char *prop_name ->
+ const svn_string_t *prop_value */
+ apr_array_header_t *prop_dels; /* const char *prop_name deletions */
+ void *baton; /* as returned by the commit editor */
+};
+
+
+/* An iterator (for use via apr_table_do) which sets node properties.
+ REC is a pointer to a struct driver_state. */
+static svn_error_t *
+change_props(const svn_delta_editor_t *editor,
+ void *baton,
+ struct operation *child,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ if (child->prop_dels)
+ {
+ int i;
+ for (i = 0; i < child->prop_dels->nelts; i++)
+ {
+ const char *prop_name;
+
+ svn_pool_clear(iterpool);
+ prop_name = APR_ARRAY_IDX(child->prop_dels, i, const char *);
+ if (child->kind == svn_node_dir)
+ SVN_ERR(editor->change_dir_prop(baton, prop_name,
+ NULL, iterpool));
+ else
+ SVN_ERR(editor->change_file_prop(baton, prop_name,
+ NULL, iterpool));
+ }
+ }
+ if (apr_hash_count(child->prop_mods))
+ {
+ apr_hash_index_t *hi;
+ for (hi = apr_hash_first(pool, child->prop_mods);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ const svn_string_t *val = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+ if (child->kind == svn_node_dir)
+ SVN_ERR(editor->change_dir_prop(baton, propname, val, iterpool));
+ else
+ SVN_ERR(editor->change_file_prop(baton, propname, val, iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Drive EDITOR to affect the change represented by OPERATION. HEAD
+ is the last-known youngest revision in the repository. */
+static svn_error_t *
+drive(struct operation *operation,
+ svn_revnum_t head,
+ const svn_delta_editor_t *editor,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, operation->children);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *key = svn__apr_hash_index_key(hi);
+ struct operation *child = svn__apr_hash_index_val(hi);
+ void *file_baton = NULL;
+
+ svn_pool_clear(subpool);
+
+ /* Deletes and replacements are simple -- delete something. */
+ if (child->operation == OP_DELETE || child->operation == OP_REPLACE)
+ {
+ SVN_ERR(editor->delete_entry(key, head, operation->baton, subpool));
+ }
+ /* Opens could be for directories or files. */
+ if (child->operation == OP_OPEN || child->operation == OP_PROPSET)
+ {
+ if (child->kind == svn_node_dir)
+ {
+ SVN_ERR(editor->open_directory(key, operation->baton, head,
+ subpool, &child->baton));
+ }
+ else
+ {
+ SVN_ERR(editor->open_file(key, operation->baton, head,
+ subpool, &file_baton));
+ }
+ }
+ /* Adds and replacements could also be for directories or files. */
+ if (child->operation == OP_ADD || child->operation == OP_REPLACE)
+ {
+ if (child->kind == svn_node_dir)
+ {
+ SVN_ERR(editor->add_directory(key, operation->baton,
+ child->url, child->rev,
+ subpool, &child->baton));
+ }
+ else
+ {
+ SVN_ERR(editor->add_file(key, operation->baton, child->url,
+ child->rev, subpool, &file_baton));
+ }
+ }
+ /* If there's a source file and an open file baton, we get to
+ change textual contents. */
+ if ((child->src_file) && (file_baton))
+ {
+ svn_txdelta_window_handler_t handler;
+ void *handler_baton;
+ svn_stream_t *contents;
+
+ SVN_ERR(editor->apply_textdelta(file_baton, NULL, subpool,
+ &handler, &handler_baton));
+ if (strcmp(child->src_file, "-") != 0)
+ {
+ SVN_ERR(svn_stream_open_readonly(&contents, child->src_file,
+ pool, pool));
+ }
+ else
+ {
+ SVN_ERR(svn_stream_for_stdin(&contents, pool));
+ }
+ SVN_ERR(svn_txdelta_send_stream(contents, handler,
+ handler_baton, NULL, pool));
+ }
+ /* If we opened a file, we need to apply outstanding propmods,
+ then close it. */
+ if (file_baton)
+ {
+ if (child->kind == svn_node_file)
+ {
+ SVN_ERR(change_props(editor, file_baton, child, subpool));
+ }
+ SVN_ERR(editor->close_file(file_baton, NULL, subpool));
+ }
+ /* If we opened, added, or replaced a directory, we need to
+ recurse, apply outstanding propmods, and then close it. */
+ if ((child->kind == svn_node_dir)
+ && child->operation != OP_DELETE)
+ {
+ SVN_ERR(change_props(editor, child->baton, child, subpool));
+
+ SVN_ERR(drive(child, head, editor, subpool));
+
+ SVN_ERR(editor->close_directory(child->baton, subpool));
+ }
+ }
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Find the operation associated with PATH, which is a single-path
+ component representing a child of the path represented by
+ OPERATION. If no such child operation exists, create a new one of
+ type OP_OPEN. */
+static struct operation *
+get_operation(const char *path,
+ struct operation *operation,
+ apr_pool_t *pool)
+{
+ struct operation *child = svn_hash_gets(operation->children, path);
+ if (! child)
+ {
+ child = apr_pcalloc(pool, sizeof(*child));
+ child->children = apr_hash_make(pool);
+ child->operation = OP_OPEN;
+ child->rev = SVN_INVALID_REVNUM;
+ child->kind = svn_node_dir;
+ child->prop_mods = apr_hash_make(pool);
+ child->prop_dels = apr_array_make(pool, 1, sizeof(const char *));
+ svn_hash_sets(operation->children, path, child);
+ }
+ return child;
+}
+
+/* Return the portion of URL that is relative to ANCHOR (URI-decoded). */
+static const char *
+subtract_anchor(const char *anchor, const char *url, apr_pool_t *pool)
+{
+ return svn_uri_skip_ancestor(anchor, url, pool);
+}
+
+/* Add PATH to the operations tree rooted at OPERATION, creating any
+ intermediate nodes that are required. Here's what's expected for
+ each action type:
+
+ ACTION URL REV SRC-FILE PROPNAME
+ ------------ ----- ------- -------- --------
+ ACTION_MKDIR NULL invalid NULL NULL
+ ACTION_CP valid valid NULL NULL
+ ACTION_PUT NULL invalid valid NULL
+ ACTION_RM NULL invalid NULL NULL
+ ACTION_PROPSET valid invalid NULL valid
+ ACTION_PROPDEL valid invalid NULL valid
+
+ Node type information is obtained for any copy source (to determine
+ whether to create a file or directory) and for any deleted path (to
+ ensure it exists since svn_delta_editor_t->delete_entry doesn't
+ return an error on non-existent nodes). */
+static svn_error_t *
+build(action_code_t action,
+ const char *path,
+ const char *url,
+ svn_revnum_t rev,
+ const char *prop_name,
+ const svn_string_t *prop_value,
+ const char *src_file,
+ svn_revnum_t head,
+ const char *anchor,
+ svn_ra_session_t *session,
+ struct operation *operation,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *path_bits = svn_path_decompose(path, pool);
+ const char *path_so_far = "";
+ const char *copy_src = NULL;
+ svn_revnum_t copy_rev = SVN_INVALID_REVNUM;
+ int i;
+
+ /* Look for any previous operations we've recognized for PATH. If
+ any of PATH's ancestors have not yet been traversed, we'll be
+ creating OP_OPEN operations for them as we walk down PATH's path
+ components. */
+ for (i = 0; i < path_bits->nelts; ++i)
+ {
+ const char *path_bit = APR_ARRAY_IDX(path_bits, i, const char *);
+ path_so_far = svn_relpath_join(path_so_far, path_bit, pool);
+ operation = get_operation(path_so_far, operation, pool);
+
+ /* If we cross a replace- or add-with-history, remember the
+ source of those things in case we need to lookup the node kind
+ of one of their children. And if this isn't such a copy,
+ but we've already seen one in of our parent paths, we just need
+ to extend that copy source path by our current path
+ component. */
+ if (operation->url
+ && SVN_IS_VALID_REVNUM(operation->rev)
+ && (operation->operation == OP_REPLACE
+ || operation->operation == OP_ADD))
+ {
+ copy_src = subtract_anchor(anchor, operation->url, pool);
+ copy_rev = operation->rev;
+ }
+ else if (copy_src)
+ {
+ copy_src = svn_relpath_join(copy_src, path_bit, pool);
+ }
+ }
+
+ /* Handle property changes. */
+ if (prop_name)
+ {
+ if (operation->operation == OP_DELETE)
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ "cannot set properties on a location being"
+ " deleted ('%s')", path);
+ /* If we're not adding this thing ourselves, check for existence. */
+ if (! ((operation->operation == OP_ADD) ||
+ (operation->operation == OP_REPLACE)))
+ {
+ SVN_ERR(svn_ra_check_path(session,
+ copy_src ? copy_src : path,
+ copy_src ? copy_rev : head,
+ &operation->kind, pool));
+ if (operation->kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ "propset: '%s' not found", path);
+ else if ((operation->kind == svn_node_file)
+ && (operation->operation == OP_OPEN))
+ operation->operation = OP_PROPSET;
+ }
+ if (! prop_value)
+ APR_ARRAY_PUSH(operation->prop_dels, const char *) = prop_name;
+ else
+ svn_hash_sets(operation->prop_mods, prop_name, prop_value);
+ if (!operation->rev)
+ operation->rev = rev;
+ return SVN_NO_ERROR;
+ }
+
+ /* We won't fuss about multiple operations on the same path in the
+ following cases:
+
+ - the prior operation was, in fact, a no-op (open)
+ - the prior operation was a propset placeholder
+ - the prior operation was a deletion
+
+ Note: while the operation structure certainly supports the
+ ability to do a copy of a file followed by a put of new contents
+ for the file, we don't let that happen (yet).
+ */
+ if (operation->operation != OP_OPEN
+ && operation->operation != OP_PROPSET
+ && operation->operation != OP_DELETE)
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ "unsupported multiple operations on '%s'", path);
+
+ /* For deletions, we validate that there's actually something to
+ delete. If this is a deletion of the child of a copied
+ directory, we need to remember to look in the copy source tree to
+ verify that this thing actually exists. */
+ if (action == ACTION_RM)
+ {
+ operation->operation = OP_DELETE;
+ SVN_ERR(svn_ra_check_path(session,
+ copy_src ? copy_src : path,
+ copy_src ? copy_rev : head,
+ &operation->kind, pool));
+ if (operation->kind == svn_node_none)
+ {
+ if (copy_src && strcmp(path, copy_src))
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ "'%s' (from '%s:%ld') not found",
+ path, copy_src, copy_rev);
+ else
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' not found",
+ path);
+ }
+ }
+ /* Handle copy operations (which can be adds or replacements). */
+ else if (action == ACTION_CP)
+ {
+ if (rev > head)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ "Copy source revision cannot be younger "
+ "than base revision");
+ operation->operation =
+ operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD;
+ if (operation->operation == OP_ADD)
+ {
+ /* There is a bug in the current version of mod_dav_svn
+ which incorrectly replaces existing directories.
+ Therefore we need to check if the target exists
+ and raise an error here. */
+ SVN_ERR(svn_ra_check_path(session,
+ copy_src ? copy_src : path,
+ copy_src ? copy_rev : head,
+ &operation->kind, pool));
+ if (operation->kind != svn_node_none)
+ {
+ if (copy_src && strcmp(path, copy_src))
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ "'%s' (from '%s:%ld') already exists",
+ path, copy_src, copy_rev);
+ else
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ "'%s' already exists", path);
+ }
+ }
+ SVN_ERR(svn_ra_check_path(session, subtract_anchor(anchor, url, pool),
+ rev, &operation->kind, pool));
+ if (operation->kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ "'%s' not found",
+ subtract_anchor(anchor, url, pool));
+ operation->url = url;
+ operation->rev = rev;
+ }
+ /* Handle mkdir operations (which can be adds or replacements). */
+ else if (action == ACTION_MKDIR)
+ {
+ operation->operation =
+ operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD;
+ operation->kind = svn_node_dir;
+ }
+ /* Handle put operations (which can be adds, replacements, or opens). */
+ else if (action == ACTION_PUT)
+ {
+ if (operation->operation == OP_DELETE)
+ {
+ operation->operation = OP_REPLACE;
+ }
+ else
+ {
+ SVN_ERR(svn_ra_check_path(session,
+ copy_src ? copy_src : path,
+ copy_src ? copy_rev : head,
+ &operation->kind, pool));
+ if (operation->kind == svn_node_file)
+ operation->operation = OP_OPEN;
+ else if (operation->kind == svn_node_none)
+ operation->operation = OP_ADD;
+ else
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ "'%s' is not a file", path);
+ }
+ operation->kind = svn_node_file;
+ operation->src_file = src_file;
+ }
+ else
+ {
+ /* We shouldn't get here. */
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct action {
+ action_code_t action;
+
+ /* revision (copy-from-rev of path[0] for cp; base-rev for put) */
+ svn_revnum_t rev;
+
+ /* action path[0] path[1]
+ * ------ ------- -------
+ * mv source target
+ * mkdir target (null)
+ * cp source target
+ * put target source
+ * rm target (null)
+ * propset target (null)
+ */
+ const char *path[2];
+
+ /* property name/value */
+ const char *prop_name;
+ const svn_string_t *prop_value;
+};
+
+struct fetch_baton
+{
+ svn_ra_session_t *session;
+ svn_revnum_t head;
+};
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct fetch_baton *fb = baton;
+ svn_stream_t *fstream;
+ svn_error_t *err;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = fb->head;
+
+ SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+
+ err = svn_ra_get_file(fb->session, path, base_revision, fstream, NULL, NULL,
+ scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ SVN_ERR(svn_stream_close(fstream));
+
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ SVN_ERR(svn_stream_close(fstream));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct fetch_baton *fb = baton;
+ svn_node_kind_t node_kind;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = fb->head;
+
+ SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, &node_kind,
+ scratch_pool));
+
+ if (node_kind == svn_node_file)
+ {
+ SVN_ERR(svn_ra_get_file(fb->session, path, base_revision, NULL, NULL,
+ props, result_pool));
+ }
+ else if (node_kind == svn_node_dir)
+ {
+ apr_array_header_t *tmp_props;
+
+ SVN_ERR(svn_ra_get_dir2(fb->session, NULL, NULL, props, path,
+ base_revision, 0 /* Dirent fields */,
+ result_pool));
+ tmp_props = svn_prop_hash_to_array(*props, result_pool);
+ SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
+ result_pool));
+ *props = svn_prop_array_to_hash(tmp_props, result_pool);
+ }
+ else
+ {
+ *props = apr_hash_make(result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct fetch_baton *fb = baton;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = fb->head;
+
+ SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, kind,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_delta_shim_callbacks_t *
+get_shim_callbacks(svn_ra_session_t *session,
+ svn_revnum_t head,
+ apr_pool_t *result_pool)
+{
+ svn_delta_shim_callbacks_t *callbacks =
+ svn_delta_shim_callbacks_default(result_pool);
+ struct fetch_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
+
+ fb->session = session;
+ fb->head = head;
+
+ callbacks->fetch_props_func = fetch_props_func;
+ callbacks->fetch_kind_func = fetch_kind_func;
+ callbacks->fetch_base_func = fetch_base_func;
+ callbacks->fetch_baton = fb;
+
+ return callbacks;
+}
+
+static svn_error_t *
+execute(const apr_array_header_t *actions,
+ const char *anchor,
+ apr_hash_t *revprops,
+ const char *username,
+ const char *password,
+ const char *config_dir,
+ const apr_array_header_t *config_options,
+ svn_boolean_t non_interactive,
+ svn_boolean_t trust_server_cert,
+ svn_boolean_t no_auth_cache,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *session;
+ svn_ra_session_t *aux_session;
+ const char *repos_root;
+ svn_revnum_t head;
+ const svn_delta_editor_t *editor;
+ svn_ra_callbacks2_t *ra_callbacks;
+ void *editor_baton;
+ struct operation root;
+ svn_error_t *err;
+ apr_hash_t *config;
+ svn_config_t *cfg_config;
+ int i;
+
+ SVN_ERR(svn_config_get_config(&config, config_dir, pool));
+ SVN_ERR(svn_cmdline__apply_config_options(config, config_options,
+ "svnmucc: ", "--config-option"));
+ cfg_config = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
+
+ if (! svn_hash_gets(revprops, SVN_PROP_REVISION_LOG))
+ {
+ svn_string_t *msg = svn_string_create("", pool);
+
+ /* If we can do so, try to pop up $EDITOR to fetch a log message. */
+ if (non_interactive)
+ {
+ return svn_error_create
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Cannot invoke editor to get log message "
+ "when non-interactive"));
+ }
+ else
+ {
+ SVN_ERR(svn_cmdline__edit_string_externally(
+ &msg, NULL, NULL, "", msg, "svnmucc-commit", config,
+ TRUE, NULL, apr_hash_pool_get(revprops)));
+ }
+
+ svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, msg);
+ }
+
+ SVN_ERR(create_ra_callbacks(&ra_callbacks, username, password, config_dir,
+ cfg_config, non_interactive, trust_server_cert,
+ no_auth_cache, pool));
+ SVN_ERR(svn_ra_open4(&session, NULL, anchor, NULL, ra_callbacks,
+ NULL, config, pool));
+ /* Open, then reparent to avoid AUTHZ errors when opening the reposroot */
+ SVN_ERR(svn_ra_open4(&aux_session, NULL, anchor, NULL, ra_callbacks,
+ NULL, config, pool));
+ SVN_ERR(svn_ra_get_repos_root2(aux_session, &repos_root, pool));
+ SVN_ERR(svn_ra_reparent(aux_session, repos_root, pool));
+ SVN_ERR(svn_ra_get_latest_revnum(session, &head, pool));
+
+ /* Reparent to ANCHOR's dir, if ANCHOR is not a directory. */
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_ra_check_path(aux_session,
+ svn_uri_skip_ancestor(repos_root, anchor, pool),
+ head, &kind, pool));
+ if (kind != svn_node_dir)
+ {
+ anchor = svn_uri_dirname(anchor, pool);
+ SVN_ERR(svn_ra_reparent(session, anchor, pool));
+ }
+ }
+
+ if (SVN_IS_VALID_REVNUM(base_revision))
+ {
+ if (base_revision > head)
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ "No such revision %ld (youngest is %ld)",
+ base_revision, head);
+ head = base_revision;
+ }
+
+ memset(&root, 0, sizeof(root));
+ root.children = apr_hash_make(pool);
+ root.operation = OP_OPEN;
+ root.kind = svn_node_dir; /* For setting properties */
+ root.prop_mods = apr_hash_make(pool);
+ root.prop_dels = apr_array_make(pool, 1, sizeof(const char *));
+
+ for (i = 0; i < actions->nelts; ++i)
+ {
+ struct action *action = APR_ARRAY_IDX(actions, i, struct action *);
+ const char *path1, *path2;
+ switch (action->action)
+ {
+ case ACTION_MV:
+ path1 = subtract_anchor(anchor, action->path[0], pool);
+ path2 = subtract_anchor(anchor, action->path[1], pool);
+ SVN_ERR(build(ACTION_RM, path1, NULL,
+ SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
+ session, &root, pool));
+ SVN_ERR(build(ACTION_CP, path2, action->path[0],
+ head, NULL, NULL, NULL, head, anchor,
+ session, &root, pool));
+ break;
+ case ACTION_CP:
+ path2 = subtract_anchor(anchor, action->path[1], pool);
+ if (action->rev == SVN_INVALID_REVNUM)
+ action->rev = head;
+ SVN_ERR(build(ACTION_CP, path2, action->path[0],
+ action->rev, NULL, NULL, NULL, head, anchor,
+ session, &root, pool));
+ break;
+ case ACTION_RM:
+ path1 = subtract_anchor(anchor, action->path[0], pool);
+ SVN_ERR(build(ACTION_RM, path1, NULL,
+ SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
+ session, &root, pool));
+ break;
+ case ACTION_MKDIR:
+ path1 = subtract_anchor(anchor, action->path[0], pool);
+ SVN_ERR(build(ACTION_MKDIR, path1, action->path[0],
+ SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
+ session, &root, pool));
+ break;
+ case ACTION_PUT:
+ path1 = subtract_anchor(anchor, action->path[0], pool);
+ SVN_ERR(build(ACTION_PUT, path1, action->path[0],
+ SVN_INVALID_REVNUM, NULL, NULL, action->path[1],
+ head, anchor, session, &root, pool));
+ break;
+ case ACTION_PROPSET:
+ case ACTION_PROPDEL:
+ path1 = subtract_anchor(anchor, action->path[0], pool);
+ SVN_ERR(build(action->action, path1, action->path[0],
+ SVN_INVALID_REVNUM,
+ action->prop_name, action->prop_value,
+ NULL, head, anchor, session, &root, pool));
+ break;
+ case ACTION_PROPSETF:
+ default:
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ }
+ }
+
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(session,
+ get_shim_callbacks(aux_session, head, pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(session, &editor, &editor_baton, revprops,
+ commit_callback, NULL, NULL, FALSE, pool));
+
+ SVN_ERR(editor->open_root(editor_baton, head, pool, &root.baton));
+ err = change_props(editor, root.baton, &root, pool);
+ if (!err)
+ err = drive(&root, head, editor, pool);
+ if (!err)
+ err = editor->close_directory(root.baton, pool);
+ if (!err)
+ err = editor->close_edit(editor_baton, pool);
+
+ if (err)
+ err = svn_error_compose_create(err,
+ editor->abort_edit(editor_baton, pool));
+
+ return err;
+}
+
+static svn_error_t *
+read_propvalue_file(const svn_string_t **value_p,
+ const char *filename,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *value;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(svn_stringbuf_from_file2(&value, filename, scratch_pool));
+ *value_p = svn_string_create_from_buf(value, pool);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Perform the typical suite of manipulations for user-provided URLs
+ on URL, returning the result (allocated from POOL): IRI-to-URI
+ conversion, auto-escaping, and canonicalization. */
+static const char *
+sanitize_url(const char *url,
+ apr_pool_t *pool)
+{
+ url = svn_path_uri_from_iri(url, pool);
+ url = svn_path_uri_autoescape(url, pool);
+ return svn_uri_canonicalize(url, pool);
+}
+
+static void
+usage(apr_pool_t *pool, int exit_val)
+{
+ FILE *stream = exit_val == EXIT_SUCCESS ? stdout : stderr;
+ svn_error_clear(svn_cmdline_fputs(
+ _("Subversion multiple URL command client\n"
+ "usage: svnmucc ACTION...\n"
+ "\n"
+ " Perform one or more Subversion repository URL-based ACTIONs, committing\n"
+ " the result as a (single) new revision.\n"
+ "\n"
+ "Actions:\n"
+ " cp REV SRC-URL DST-URL : copy SRC-URL@REV to DST-URL\n"
+ " mkdir URL : create new directory URL\n"
+ " mv SRC-URL DST-URL : move SRC-URL to DST-URL\n"
+ " rm URL : delete URL\n"
+ " put SRC-FILE URL : add or modify file URL with contents copied from\n"
+ " SRC-FILE (use \"-\" to read from standard input)\n"
+ " propset NAME VALUE URL : set property NAME on URL to VALUE\n"
+ " propsetf NAME FILE URL : set property NAME on URL to value read from FILE\n"
+ " propdel NAME URL : delete property NAME from URL\n"
+ "\n"
+ "Valid options:\n"
+ " -h, -? [--help] : display this text\n"
+ " -m [--message] ARG : use ARG as a log message\n"
+ " -F [--file] ARG : read log message from file ARG\n"
+ " -u [--username] ARG : commit the changes as username ARG\n"
+ " -p [--password] ARG : use ARG as the password\n"
+ " -U [--root-url] ARG : interpret all action URLs relative to ARG\n"
+ " -r [--revision] ARG : use revision ARG as baseline for changes\n"
+ " --with-revprop ARG : set revision property in the following format:\n"
+ " NAME[=VALUE]\n"
+ " --non-interactive : do no interactive prompting (default is to\n"
+ " prompt only if standard input is a terminal)\n"
+ " --force-interactive : do interactive prompting even if standard\n"
+ " input is not a terminal\n"
+ " --trust-server-cert : accept SSL server certificates from unknown\n"
+ " certificate authorities without prompting (but\n"
+ " only with '--non-interactive')\n"
+ " -X [--extra-args] ARG : append arguments from file ARG (one per line;\n"
+ " use \"-\" to read from standard input)\n"
+ " --config-dir ARG : use ARG to override the config directory\n"
+ " --config-option ARG : use ARG to override a configuration option\n"
+ " --no-auth-cache : do not cache authentication tokens\n"
+ " --version : print version information\n"),
+ stream, pool));
+ svn_pool_destroy(pool);
+ exit(exit_val);
+}
+
+static void
+insufficient(apr_pool_t *pool)
+{
+ handle_error(svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "insufficient arguments"),
+ pool);
+}
+
+static svn_error_t *
+display_version(apr_getopt_t *os, apr_pool_t *pool)
+{
+ const char *ra_desc_start
+ = "The following repository access (RA) modules are available:\n\n";
+ svn_stringbuf_t *version_footer;
+
+ version_footer = svn_stringbuf_create(ra_desc_start, pool);
+ SVN_ERR(svn_ra_print_modules(version_footer, pool));
+
+ SVN_ERR(svn_opt_print_help4(os, "svnmucc", TRUE, FALSE, FALSE,
+ version_footer->data,
+ NULL, NULL, NULL, NULL, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Return an error about the mutual exclusivity of the -m, -F, and
+ --with-revprop=svn:log command-line options. */
+static svn_error_t *
+mutually_exclusive_logs_error(void)
+{
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--message (-m), --file (-F), and "
+ "--with-revprop=svn:log are mutually "
+ "exclusive"));
+}
+
+/* Ensure that the REVPROPS hash contains a command-line-provided log
+ message, if any, and that there was but one source of such a thing
+ provided on that command-line. */
+static svn_error_t *
+sanitize_log_sources(apr_hash_t *revprops,
+ const char *message,
+ svn_stringbuf_t *filedata)
+{
+ apr_pool_t *hash_pool = apr_hash_pool_get(revprops);
+
+ /* If we already have a log message in the revprop hash, then just
+ make sure the user didn't try to also use -m or -F. Otherwise,
+ we need to consult -m or -F to find a log message, if any. */
+ if (svn_hash_gets(revprops, SVN_PROP_REVISION_LOG))
+ {
+ if (filedata || message)
+ return mutually_exclusive_logs_error();
+ }
+ else if (filedata)
+ {
+ if (message)
+ return mutually_exclusive_logs_error();
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&message, filedata->data, hash_pool));
+ svn_hash_sets(revprops, SVN_PROP_REVISION_LOG,
+ svn_stringbuf__morph_into_string(filedata));
+ }
+ else if (message)
+ {
+ svn_hash_sets(revprops, SVN_PROP_REVISION_LOG,
+ svn_string_create(message, hash_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+int
+main(int argc, const char **argv)
+{
+ apr_pool_t *pool = init("svnmucc");
+ apr_array_header_t *actions = apr_array_make(pool, 1,
+ sizeof(struct action *));
+ const char *anchor = NULL;
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_getopt_t *opts;
+ enum {
+ config_dir_opt = SVN_OPT_FIRST_LONGOPT_ID,
+ config_inline_opt,
+ no_auth_cache_opt,
+ version_opt,
+ with_revprop_opt,
+ non_interactive_opt,
+ force_interactive_opt,
+ trust_server_cert_opt
+ };
+ static const apr_getopt_option_t options[] = {
+ {"message", 'm', 1, ""},
+ {"file", 'F', 1, ""},
+ {"username", 'u', 1, ""},
+ {"password", 'p', 1, ""},
+ {"root-url", 'U', 1, ""},
+ {"revision", 'r', 1, ""},
+ {"with-revprop", with_revprop_opt, 1, ""},
+ {"extra-args", 'X', 1, ""},
+ {"help", 'h', 0, ""},
+ {NULL, '?', 0, ""},
+ {"non-interactive", non_interactive_opt, 0, ""},
+ {"force-interactive", force_interactive_opt, 0, ""},
+ {"trust-server-cert", trust_server_cert_opt, 0, ""},
+ {"config-dir", config_dir_opt, 1, ""},
+ {"config-option", config_inline_opt, 1, ""},
+ {"no-auth-cache", no_auth_cache_opt, 0, ""},
+ {"version", version_opt, 0, ""},
+ {NULL, 0, 0, NULL}
+ };
+ const char *message = NULL;
+ svn_stringbuf_t *filedata = NULL;
+ const char *username = NULL, *password = NULL;
+ const char *root_url = NULL, *extra_args_file = NULL;
+ const char *config_dir = NULL;
+ apr_array_header_t *config_options;
+ svn_boolean_t non_interactive = FALSE;
+ svn_boolean_t force_interactive = FALSE;
+ svn_boolean_t trust_server_cert = FALSE;
+ svn_boolean_t no_auth_cache = FALSE;
+ svn_revnum_t base_revision = SVN_INVALID_REVNUM;
+ apr_array_header_t *action_args;
+ apr_hash_t *revprops = apr_hash_make(pool);
+ int i;
+
+ config_options = apr_array_make(pool, 0,
+ sizeof(svn_cmdline__config_argument_t*));
+
+ apr_getopt_init(&opts, pool, argc, argv);
+ opts->interleave = 1;
+ while (1)
+ {
+ int opt;
+ const char *arg;
+ const char *opt_arg;
+
+ apr_status_t status = apr_getopt_long(opts, options, &opt, &arg);
+ if (APR_STATUS_IS_EOF(status))
+ break;
+ if (status != APR_SUCCESS)
+ handle_error(svn_error_wrap_apr(status, "getopt failure"), pool);
+ switch(opt)
+ {
+ case 'm':
+ err = svn_utf_cstring_to_utf8(&message, arg, pool);
+ if (err)
+ handle_error(err, pool);
+ break;
+ case 'F':
+ {
+ const char *arg_utf8;
+ err = svn_utf_cstring_to_utf8(&arg_utf8, arg, pool);
+ if (! err)
+ err = svn_stringbuf_from_file2(&filedata, arg, pool);
+ if (err)
+ handle_error(err, pool);
+ }
+ break;
+ case 'u':
+ username = apr_pstrdup(pool, arg);
+ break;
+ case 'p':
+ password = apr_pstrdup(pool, arg);
+ break;
+ case 'U':
+ err = svn_utf_cstring_to_utf8(&root_url, arg, pool);
+ if (err)
+ handle_error(err, pool);
+ if (! svn_path_is_url(root_url))
+ handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "'%s' is not a URL\n", root_url),
+ pool);
+ root_url = sanitize_url(root_url, pool);
+ break;
+ case 'r':
+ {
+ char *digits_end = NULL;
+ base_revision = strtol(arg, &digits_end, 10);
+ if ((! SVN_IS_VALID_REVNUM(base_revision))
+ || (! digits_end)
+ || *digits_end)
+ handle_error(svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL, "Invalid revision number"),
+ pool);
+ }
+ break;
+ case with_revprop_opt:
+ err = svn_opt_parse_revprop(&revprops, arg, pool);
+ if (err != SVN_NO_ERROR)
+ handle_error(err, pool);
+ break;
+ case 'X':
+ extra_args_file = apr_pstrdup(pool, arg);
+ break;
+ case non_interactive_opt:
+ non_interactive = TRUE;
+ break;
+ case force_interactive_opt:
+ force_interactive = TRUE;
+ break;
+ case trust_server_cert_opt:
+ trust_server_cert = TRUE;
+ break;
+ case config_dir_opt:
+ err = svn_utf_cstring_to_utf8(&config_dir, arg, pool);
+ if (err)
+ handle_error(err, pool);
+ break;
+ case config_inline_opt:
+ err = svn_utf_cstring_to_utf8(&opt_arg, arg, pool);
+ if (err)
+ handle_error(err, pool);
+
+ err = svn_cmdline__parse_config_option(config_options, opt_arg,
+ pool);
+ if (err)
+ handle_error(err, pool);
+ break;
+ case no_auth_cache_opt:
+ no_auth_cache = TRUE;
+ break;
+ case version_opt:
+ SVN_INT_ERR(display_version(opts, pool));
+ exit(EXIT_SUCCESS);
+ break;
+ case 'h':
+ case '?':
+ usage(pool, EXIT_SUCCESS);
+ break;
+ }
+ }
+
+ if (non_interactive && force_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--non-interactive and --force-interactive "
+ "are mutually exclusive"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnmucc: ");
+ }
+ else
+ non_interactive = !svn_cmdline__be_interactive(non_interactive,
+ force_interactive);
+
+ if (trust_server_cert && !non_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--trust-server-cert requires "
+ "--non-interactive"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnmucc: ");
+ }
+
+ /* Make sure we have a log message to use. */
+ err = sanitize_log_sources(revprops, message, filedata);
+ if (err)
+ handle_error(err, pool);
+
+ /* Copy the rest of our command-line arguments to an array,
+ UTF-8-ing them along the way. */
+ action_args = apr_array_make(pool, opts->argc, sizeof(const char *));
+ while (opts->ind < opts->argc)
+ {
+ const char *arg = opts->argv[opts->ind++];
+ if ((err = svn_utf_cstring_to_utf8(&(APR_ARRAY_PUSH(action_args,
+ const char *)),
+ arg, pool)))
+ handle_error(err, pool);
+ }
+
+ /* If there are extra arguments in a supplementary file, tack those
+ on, too (again, in UTF8 form). */
+ if (extra_args_file)
+ {
+ const char *extra_args_file_utf8;
+ svn_stringbuf_t *contents, *contents_utf8;
+
+ err = svn_utf_cstring_to_utf8(&extra_args_file_utf8,
+ extra_args_file, pool);
+ if (! err)
+ err = svn_stringbuf_from_file2(&contents, extra_args_file_utf8, pool);
+ if (! err)
+ err = svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool);
+ if (err)
+ handle_error(err, pool);
+ svn_cstring_split_append(action_args, contents_utf8->data, "\n\r",
+ FALSE, pool);
+ }
+
+ /* Now, we iterate over the combined set of arguments -- our actions. */
+ for (i = 0; i < action_args->nelts; )
+ {
+ int j, num_url_args;
+ const char *action_string = APR_ARRAY_IDX(action_args, i, const char *);
+ struct action *action = apr_pcalloc(pool, sizeof(*action));
+
+ /* First, parse the action. */
+ if (! strcmp(action_string, "mv"))
+ action->action = ACTION_MV;
+ else if (! strcmp(action_string, "cp"))
+ action->action = ACTION_CP;
+ else if (! strcmp(action_string, "mkdir"))
+ action->action = ACTION_MKDIR;
+ else if (! strcmp(action_string, "rm"))
+ action->action = ACTION_RM;
+ else if (! strcmp(action_string, "put"))
+ action->action = ACTION_PUT;
+ else if (! strcmp(action_string, "propset"))
+ action->action = ACTION_PROPSET;
+ else if (! strcmp(action_string, "propsetf"))
+ action->action = ACTION_PROPSETF;
+ else if (! strcmp(action_string, "propdel"))
+ action->action = ACTION_PROPDEL;
+ else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h")
+ || ! strcmp(action_string, "help"))
+ usage(pool, EXIT_SUCCESS);
+ else
+ handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "'%s' is not an action\n",
+ action_string), pool);
+ if (++i == action_args->nelts)
+ insufficient(pool);
+
+ /* For copies, there should be a revision number next. */
+ if (action->action == ACTION_CP)
+ {
+ const char *rev_str = APR_ARRAY_IDX(action_args, i, const char *);
+ if (strcmp(rev_str, "head") == 0)
+ action->rev = SVN_INVALID_REVNUM;
+ else if (strcmp(rev_str, "HEAD") == 0)
+ action->rev = SVN_INVALID_REVNUM;
+ else
+ {
+ char *end;
+
+ while (*rev_str == 'r')
+ ++rev_str;
+
+ action->rev = strtol(rev_str, &end, 0);
+ if (*end)
+ handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "'%s' is not a revision\n",
+ rev_str), pool);
+ }
+ if (++i == action_args->nelts)
+ insufficient(pool);
+ }
+ else
+ {
+ action->rev = SVN_INVALID_REVNUM;
+ }
+
+ /* For puts, there should be a local file next. */
+ if (action->action == ACTION_PUT)
+ {
+ action->path[1] =
+ svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i,
+ const char *), pool);
+ if (++i == action_args->nelts)
+ insufficient(pool);
+ }
+
+ /* For propset, propsetf, and propdel, a property name (and
+ maybe a property value or file which contains one) comes next. */
+ if ((action->action == ACTION_PROPSET)
+ || (action->action == ACTION_PROPSETF)
+ || (action->action == ACTION_PROPDEL))
+ {
+ action->prop_name = APR_ARRAY_IDX(action_args, i, const char *);
+ if (++i == action_args->nelts)
+ insufficient(pool);
+
+ if (action->action == ACTION_PROPDEL)
+ {
+ action->prop_value = NULL;
+ }
+ else if (action->action == ACTION_PROPSET)
+ {
+ action->prop_value =
+ svn_string_create(APR_ARRAY_IDX(action_args, i,
+ const char *), pool);
+ if (++i == action_args->nelts)
+ insufficient(pool);
+ }
+ else
+ {
+ const char *propval_file =
+ svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i,
+ const char *), pool);
+
+ if (++i == action_args->nelts)
+ insufficient(pool);
+
+ err = read_propvalue_file(&(action->prop_value),
+ propval_file, pool);
+ if (err)
+ handle_error(err, pool);
+
+ action->action = ACTION_PROPSET;
+ }
+
+ if (action->prop_value
+ && svn_prop_needs_translation(action->prop_name))
+ {
+ svn_string_t *translated_value;
+ err = svn_subst_translate_string2(&translated_value, NULL,
+ NULL, action->prop_value, NULL,
+ FALSE, pool, pool);
+ if (err)
+ handle_error(
+ svn_error_quick_wrap(err,
+ "Error normalizing property value"),
+ pool);
+ action->prop_value = translated_value;
+ }
+ }
+
+ /* How many URLs does this action expect? */
+ if (action->action == ACTION_RM
+ || action->action == ACTION_MKDIR
+ || action->action == ACTION_PUT
+ || action->action == ACTION_PROPSET
+ || action->action == ACTION_PROPSETF /* shouldn't see this one */
+ || action->action == ACTION_PROPDEL)
+ num_url_args = 1;
+ else
+ num_url_args = 2;
+
+ /* Parse the required number of URLs. */
+ for (j = 0; j < num_url_args; ++j)
+ {
+ const char *url = APR_ARRAY_IDX(action_args, i, const char *);
+
+ /* If there's a ROOT_URL, we expect URL to be a path
+ relative to ROOT_URL (and we build a full url from the
+ combination of the two). Otherwise, it should be a full
+ url. */
+ if (! svn_path_is_url(url))
+ {
+ if (! root_url)
+ handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "'%s' is not a URL, and "
+ "--root-url (-U) not provided\n",
+ url), pool);
+ /* ### These relpaths are already URI-encoded. */
+ url = apr_pstrcat(pool, root_url, "/",
+ svn_relpath_canonicalize(url, pool),
+ (char *)NULL);
+ }
+ url = sanitize_url(url, pool);
+ action->path[j] = url;
+
+ /* The first URL arguments to 'cp', 'pd', 'ps' could be the anchor,
+ but the other URLs should be children of the anchor. */
+ if (! (action->action == ACTION_CP && j == 0)
+ && action->action != ACTION_PROPDEL
+ && action->action != ACTION_PROPSET
+ && action->action != ACTION_PROPSETF)
+ url = svn_uri_dirname(url, pool);
+ if (! anchor)
+ anchor = url;
+ else
+ anchor = svn_uri_get_longest_ancestor(anchor, url, pool);
+
+ if ((++i == action_args->nelts) && (j + 1 < num_url_args))
+ insufficient(pool);
+ }
+ APR_ARRAY_PUSH(actions, struct action *) = action;
+ }
+
+ if (! actions->nelts)
+ usage(pool, EXIT_FAILURE);
+
+ if ((err = execute(actions, anchor, revprops, username, password,
+ config_dir, config_options, non_interactive,
+ trust_server_cert, no_auth_cache, base_revision, pool)))
+ {
+ if (err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
+ err = svn_error_quick_wrap(err,
+ _("Authentication failed and interactive"
+ " prompting is disabled; see the"
+ " --force-interactive option"));
+ handle_error(err, pool);
+ }
+
+ /* Ensure that stdout is flushed, so the user will see all results. */
+ svn_error_clear(svn_cmdline_fflush(stdout));
+
+ svn_pool_destroy(pool);
+ return EXIT_SUCCESS;
+}
diff --git a/contrib/subversion/subversion/svnrdump/dump_editor.c b/contrib/subversion/subversion/svnrdump/dump_editor.c
new file mode 100644
index 0000000..faf029f
--- /dev/null
+++ b/contrib/subversion/subversion/svnrdump/dump_editor.c
@@ -0,0 +1,1280 @@
+/*
+ * dump_editor.c: The svn_delta_editor_t editor used by svnrdump to
+ * dump revisions.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_repos.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_subst.h"
+#include "svn_dirent_uri.h"
+
+#include "private/svn_subr_private.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_editor.h"
+
+#include "svnrdump.h"
+#include <assert.h>
+
+#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
+
+#if 0
+#define LDR_DBG(x) SVN_DBG(x)
+#else
+#define LDR_DBG(x) while(0)
+#endif
+
+/* A directory baton used by all directory-related callback functions
+ * in the dump editor. */
+struct dir_baton
+{
+ struct dump_edit_baton *eb;
+ struct dir_baton *parent_dir_baton;
+
+ /* Pool for per-directory allocations */
+ apr_pool_t *pool;
+
+ /* is this directory a new addition to this revision? */
+ svn_boolean_t added;
+
+ /* has this directory been written to the output stream? */
+ svn_boolean_t written_out;
+
+ /* the path to this directory */
+ const char *repos_relpath; /* a relpath */
+
+ /* Copyfrom info for the node, if any. */
+ const char *copyfrom_path; /* a relpath */
+ svn_revnum_t copyfrom_rev;
+
+ /* Properties which were modified during change_dir_prop. */
+ apr_hash_t *props;
+
+ /* Properties which were deleted during change_dir_prop. */
+ apr_hash_t *deleted_props;
+
+ /* Hash of paths that need to be deleted, though some -might- be
+ replaced. Maps const char * paths to this dir_baton. Note that
+ they're full paths, because that's what the editor driver gives
+ us, although they're all really within this directory. */
+ apr_hash_t *deleted_entries;
+
+ /* Flags to trigger dumping props and record termination newlines. */
+ svn_boolean_t dump_props;
+ svn_boolean_t dump_newlines;
+};
+
+/* A file baton used by all file-related callback functions in the dump
+ * editor */
+struct file_baton
+{
+ struct dump_edit_baton *eb;
+ struct dir_baton *parent_dir_baton;
+
+ /* Pool for per-file allocations */
+ apr_pool_t *pool;
+
+ /* the path to this file */
+ const char *repos_relpath; /* a relpath */
+
+ /* Properties which were modified during change_file_prop. */
+ apr_hash_t *props;
+
+ /* Properties which were deleted during change_file_prop. */
+ apr_hash_t *deleted_props;
+
+ /* The checksum of the file the delta is being applied to */
+ const char *base_checksum;
+
+ /* Copy state and source information (if any). */
+ svn_boolean_t is_copy;
+ const char *copyfrom_path;
+ svn_revnum_t copyfrom_rev;
+
+ /* The action associate with this node. */
+ enum svn_node_action action;
+
+ /* Flags to trigger dumping props and text. */
+ svn_boolean_t dump_text;
+ svn_boolean_t dump_props;
+};
+
+/* A handler baton to be used in window_handler(). */
+struct handler_baton
+{
+ svn_txdelta_window_handler_t apply_handler;
+ void *apply_baton;
+};
+
+/* The baton used by the dump editor. */
+struct dump_edit_baton {
+ /* The output stream we write the dumpfile to */
+ svn_stream_t *stream;
+
+ /* A backdoor ra session to fetch additional information during the edit. */
+ svn_ra_session_t *ra_session;
+
+ /* The repository relpath of the anchor of the editor when driven
+ via the RA update mechanism; NULL otherwise. (When the editor is
+ driven via the RA "replay" mechanism instead, the editor is
+ always anchored at the repository, we don't need to prepend an
+ anchor path to the dumped node paths, and open_root() doesn't
+ need to manufacture directory additions.) */
+ const char *update_anchor_relpath;
+
+ /* Pool for per-revision allocations */
+ apr_pool_t *pool;
+
+ /* Temporary file used for textdelta application along with its
+ absolute path; these two variables should be allocated in the
+ per-edit-session pool */
+ const char *delta_abspath;
+ apr_file_t *delta_file;
+
+ /* The revision we're currently dumping. */
+ svn_revnum_t current_revision;
+
+ /* The kind (file or directory) and baton of the item whose block of
+ dump stream data has not been fully completed; NULL if there's no
+ such item. */
+ svn_node_kind_t pending_kind;
+ void *pending_baton;
+};
+
+/* Make a directory baton to represent the directory at PATH (relative
+ * to the EDIT_BATON).
+ *
+ * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this
+ * directory should be compared for changes. If the copyfrom
+ * information is valid, the directory will be compared against its
+ * copy source.
+ *
+ * PB is the directory baton of this directory's parent, or NULL if
+ * this is the top-level directory of the edit. ADDED indicates if
+ * this directory is newly added in this revision. Perform all
+ * allocations in POOL. */
+static struct dir_baton *
+make_dir_baton(const char *path,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ void *edit_baton,
+ struct dir_baton *pb,
+ svn_boolean_t added,
+ apr_pool_t *pool)
+{
+ struct dump_edit_baton *eb = edit_baton;
+ struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
+ const char *repos_relpath;
+
+ /* Construct the full path of this node. */
+ if (pb)
+ repos_relpath = svn_relpath_canonicalize(path, pool);
+ else
+ repos_relpath = "";
+
+ /* Strip leading slash from copyfrom_path so that the path is
+ canonical and svn_relpath_join can be used */
+ if (copyfrom_path)
+ copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool);
+
+ new_db->eb = eb;
+ new_db->parent_dir_baton = pb;
+ new_db->pool = pool;
+ new_db->repos_relpath = repos_relpath;
+ new_db->copyfrom_path = copyfrom_path
+ ? svn_relpath_canonicalize(copyfrom_path, pool)
+ : NULL;
+ new_db->copyfrom_rev = copyfrom_rev;
+ new_db->added = added;
+ new_db->written_out = FALSE;
+ new_db->props = apr_hash_make(pool);
+ new_db->deleted_props = apr_hash_make(pool);
+ new_db->deleted_entries = apr_hash_make(pool);
+
+ return new_db;
+}
+
+/* Make a file baton to represent the directory at PATH (relative to
+ * PB->eb). PB is the directory baton of this directory's parent, or
+ * NULL if this is the top-level directory of the edit. Perform all
+ * allocations in POOL. */
+static struct file_baton *
+make_file_baton(const char *path,
+ struct dir_baton *pb,
+ apr_pool_t *pool)
+{
+ struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
+
+ new_fb->eb = pb->eb;
+ new_fb->parent_dir_baton = pb;
+ new_fb->pool = pool;
+ new_fb->repos_relpath = svn_relpath_canonicalize(path, pool);
+ new_fb->props = apr_hash_make(pool);
+ new_fb->deleted_props = apr_hash_make(pool);
+ new_fb->is_copy = FALSE;
+ new_fb->copyfrom_path = NULL;
+ new_fb->copyfrom_rev = SVN_INVALID_REVNUM;
+ new_fb->action = svn_node_action_change;
+
+ return new_fb;
+}
+
+/* Return in *HEADER and *CONTENT the headers and content for PROPS. */
+static svn_error_t *
+get_props_content(svn_stringbuf_t **header,
+ svn_stringbuf_t **content,
+ apr_hash_t *props,
+ apr_hash_t *deleted_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *content_stream;
+ apr_hash_t *normal_props;
+ const char *buf;
+
+ *content = svn_stringbuf_create_empty(result_pool);
+ *header = svn_stringbuf_create_empty(result_pool);
+
+ content_stream = svn_stream_from_stringbuf(*content, scratch_pool);
+
+ SVN_ERR(svn_rdump__normalize_props(&normal_props, props, scratch_pool));
+ SVN_ERR(svn_hash_write_incremental(normal_props, deleted_props,
+ content_stream, "PROPS-END",
+ scratch_pool));
+ SVN_ERR(svn_stream_close(content_stream));
+
+ /* Prop-delta: true */
+ *header = svn_stringbuf_createf(result_pool, SVN_REPOS_DUMPFILE_PROP_DELTA
+ ": true\n");
+
+ /* Prop-content-length: 193 */
+ buf = apr_psprintf(scratch_pool, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n", (*content)->len);
+ svn_stringbuf_appendcstr(*header, buf);
+
+ return SVN_NO_ERROR;
+}
+
+/* Extract and dump properties stored in PROPS and property deletions
+ * stored in DELETED_PROPS. If TRIGGER_VAR is not NULL, it is set to
+ * FALSE.
+ *
+ * If PROPSTRING is non-NULL, set *PROPSTRING to a string containing
+ * the content block of the property changes; otherwise, dump that to
+ * the stream, too.
+ */
+static svn_error_t *
+do_dump_props(svn_stringbuf_t **propstring,
+ svn_stream_t *stream,
+ apr_hash_t *props,
+ apr_hash_t *deleted_props,
+ svn_boolean_t *trigger_var,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *header;
+ svn_stringbuf_t *content;
+ apr_size_t len;
+
+ if (trigger_var && !*trigger_var)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(get_props_content(&header, &content, props, deleted_props,
+ result_pool, scratch_pool));
+ len = header->len;
+ SVN_ERR(svn_stream_write(stream, header->data, &len));
+
+ if (propstring)
+ {
+ *propstring = content;
+ }
+ else
+ {
+ /* Content-length: 14 */
+ SVN_ERR(svn_stream_printf(stream, scratch_pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n\n",
+ content->len));
+
+ len = content->len;
+ SVN_ERR(svn_stream_write(stream, content->data, &len));
+
+ /* No text is going to be dumped. Write a couple of newlines and
+ wait for the next node/ revision. */
+ SVN_ERR(svn_stream_puts(stream, "\n\n"));
+
+ /* Cleanup so that data is never dumped twice. */
+ apr_hash_clear(props);
+ apr_hash_clear(deleted_props);
+ if (trigger_var)
+ *trigger_var = FALSE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+do_dump_newlines(struct dump_edit_baton *eb,
+ svn_boolean_t *trigger_var,
+ apr_pool_t *pool)
+{
+ if (trigger_var && *trigger_var)
+ {
+ SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
+ *trigger_var = FALSE;
+ }
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Write out a node record for PATH of type KIND under EB->FS_ROOT.
+ * ACTION describes what is happening to the node (see enum
+ * svn_node_action). Write record to writable EB->STREAM, using
+ * EB->BUFFER to write in chunks.
+ *
+ * If the node was itself copied, IS_COPY is TRUE and the
+ * path/revision of the copy source are in COPYFROM_PATH/COPYFROM_REV.
+ * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this
+ * node is part of a copied subtree.
+ */
+static svn_error_t *
+dump_node(struct dump_edit_baton *eb,
+ const char *repos_relpath,
+ struct dir_baton *db,
+ struct file_baton *fb,
+ enum svn_node_action action,
+ svn_boolean_t is_copy,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool)
+{
+ const char *node_relpath = repos_relpath;
+
+ assert(svn_relpath_is_canonical(repos_relpath));
+ assert(!copyfrom_path || svn_relpath_is_canonical(copyfrom_path));
+ assert(! (db && fb));
+
+ /* Add the edit root relpath prefix if necessary. */
+ if (eb->update_anchor_relpath)
+ node_relpath = svn_relpath_join(eb->update_anchor_relpath,
+ node_relpath, pool);
+
+ /* Node-path: ... */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
+ node_relpath));
+
+ /* Node-kind: "file" | "dir" */
+ if (fb)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
+ else if (db)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
+
+
+ /* Write the appropriate Node-action header */
+ switch (action)
+ {
+ case svn_node_action_change:
+ /* We are here after a change_file_prop or change_dir_prop. They
+ set up whatever dump_props they needed to- nothing to
+ do here but print node action information.
+
+ Node-action: change. */
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n"));
+ break;
+
+ case svn_node_action_replace:
+ if (is_copy)
+ {
+ /* Delete the original, and then re-add the replacement as a
+ copy using recursive calls into this function. */
+ SVN_ERR(dump_node(eb, repos_relpath, db, fb, svn_node_action_delete,
+ FALSE, NULL, SVN_INVALID_REVNUM, pool));
+ SVN_ERR(dump_node(eb, repos_relpath, db, fb, svn_node_action_add,
+ is_copy, copyfrom_path, copyfrom_rev, pool));
+ }
+ else
+ {
+ /* Node-action: replace */
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION
+ ": replace\n"));
+
+ /* Wait for a change_*_prop to be called before dumping
+ anything */
+ if (fb)
+ fb->dump_props = TRUE;
+ else if (db)
+ db->dump_props = TRUE;
+ }
+ break;
+
+ case svn_node_action_delete:
+ /* Node-action: delete */
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n"));
+
+ /* We can leave this routine quietly now. Nothing more to do-
+ print a couple of newlines because we're not dumping props or
+ text. */
+ SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
+
+ break;
+
+ case svn_node_action_add:
+ /* Node-action: add */
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
+
+ if (is_copy)
+ {
+ /* Node-copyfrom-rev / Node-copyfrom-path */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
+ ": %ld\n"
+ SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
+ ": %s\n",
+ copyfrom_rev, copyfrom_path));
+
+ /* Ugly hack: If a directory was copied from a previous
+ revision, nothing like close_file() will be called to write two
+ blank lines. If change_dir_prop() is called, props are dumped
+ (along with the necessary PROPS-END\n\n and we're good. So
+ set DUMP_NEWLINES here to print the newlines unless
+ change_dir_prop() is called next otherwise the `svnadmin load`
+ parser will fail. */
+ if (db)
+ db->dump_newlines = TRUE;
+ }
+ else
+ {
+ /* fb->dump_props (for files) is handled in close_file()
+ which is called immediately.
+
+ However, directories are not closed until all the work
+ inside them has been done; db->dump_props (for directories)
+ is handled (via dump_pending()) in all the functions that
+ can possibly be called after add_directory():
+
+ - add_directory()
+ - open_directory()
+ - delete_entry()
+ - close_directory()
+ - add_file()
+ - open_file()
+
+ change_dir_prop() is a special case. */
+ if (fb)
+ fb->dump_props = TRUE;
+ else if (db)
+ db->dump_props = TRUE;
+ }
+
+ break;
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dump_mkdir(struct dump_edit_baton *eb,
+ const char *repos_relpath,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *prop_header, *prop_content;
+ apr_size_t len;
+ const char *buf;
+
+ /* Node-path: ... */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
+ repos_relpath));
+
+ /* Node-kind: dir */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
+
+ /* Node-action: add */
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
+
+ /* Dump the (empty) property block. */
+ SVN_ERR(get_props_content(&prop_header, &prop_content,
+ apr_hash_make(pool), apr_hash_make(pool),
+ pool, pool));
+ len = prop_header->len;
+ SVN_ERR(svn_stream_write(eb->stream, prop_header->data, &len));
+ len = prop_content->len;
+ buf = apr_psprintf(pool, SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n", len);
+ SVN_ERR(svn_stream_puts(eb->stream, buf));
+ SVN_ERR(svn_stream_puts(eb->stream, "\n"));
+ SVN_ERR(svn_stream_write(eb->stream, prop_content->data, &len));
+
+ /* Newlines to tie it all off. */
+ SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
+
+ return SVN_NO_ERROR;
+}
+
+/* Dump pending items from the specified node, to allow starting the dump
+ of a child node */
+static svn_error_t *
+dump_pending(struct dump_edit_baton *eb,
+ apr_pool_t *scratch_pool)
+{
+ if (! eb->pending_baton)
+ return SVN_NO_ERROR;
+
+ if (eb->pending_kind == svn_node_dir)
+ {
+ struct dir_baton *db = eb->pending_baton;
+
+ /* Some pending properties to dump? */
+ SVN_ERR(do_dump_props(NULL, eb->stream, db->props, db->deleted_props,
+ &(db->dump_props), db->pool, scratch_pool));
+
+ /* Some pending newlines to dump? */
+ SVN_ERR(do_dump_newlines(eb, &(db->dump_newlines), scratch_pool));
+ }
+ else if (eb->pending_kind == svn_node_file)
+ {
+ struct file_baton *fb = eb->pending_baton;
+
+ /* Some pending properties to dump? */
+ SVN_ERR(do_dump_props(NULL, eb->stream, fb->props, fb->deleted_props,
+ &(fb->dump_props), fb->pool, scratch_pool));
+ }
+ else
+ abort();
+
+ /* Anything that was pending is pending no longer. */
+ eb->pending_baton = NULL;
+ eb->pending_kind = svn_node_none;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Editor Function Implementations ***/
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ struct dump_edit_baton *eb = edit_baton;
+ struct dir_baton *new_db = NULL;
+
+ /* Clear the per-revision pool after each revision */
+ svn_pool_clear(eb->pool);
+
+ LDR_DBG(("open_root %p\n", *root_baton));
+
+ if (eb->update_anchor_relpath)
+ {
+ int i;
+ const char *parent_path = eb->update_anchor_relpath;
+ apr_array_header_t *dirs_to_add =
+ apr_array_make(pool, 4, sizeof(const char *));
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ while (! svn_path_is_empty(parent_path))
+ {
+ APR_ARRAY_PUSH(dirs_to_add, const char *) = parent_path;
+ parent_path = svn_relpath_dirname(parent_path, pool);
+ }
+
+ for (i = dirs_to_add->nelts; i; --i)
+ {
+ const char *dir_to_add =
+ APR_ARRAY_IDX(dirs_to_add, i - 1, const char *);
+
+ svn_pool_clear(iterpool);
+
+ /* For parents of the source directory, we just manufacture
+ the adds ourselves. */
+ if (i > 1)
+ {
+ SVN_ERR(dump_mkdir(eb, dir_to_add, iterpool));
+ }
+ else
+ {
+ /* ... but for the source directory itself, we'll defer
+ to letting the typical plumbing handle this task. */
+ new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
+ edit_baton, NULL, TRUE, pool);
+ SVN_ERR(dump_node(eb, new_db->repos_relpath, new_db,
+ NULL, svn_node_action_add, FALSE,
+ NULL, SVN_INVALID_REVNUM, pool));
+
+ /* Remember that we've started but not yet finished
+ handling this directory. */
+ new_db->written_out = TRUE;
+ eb->pending_baton = new_db;
+ eb->pending_kind = svn_node_dir;
+ }
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ if (! new_db)
+ {
+ new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
+ edit_baton, NULL, FALSE, pool);
+ }
+
+ *root_baton = new_db;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *pb = parent_baton;
+
+ LDR_DBG(("delete_entry %s\n", path));
+
+ SVN_ERR(dump_pending(pb->eb, pool));
+
+ /* We don't dump this deletion immediate. Rather, we add this path
+ to the deleted_entries of the parent directory baton. That way,
+ we can tell (later) an addition from a replacement. All the real
+ deletions get handled in close_directory(). */
+ svn_hash_sets(pb->deleted_entries, apr_pstrdup(pb->eb->pool, path), pb);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ void *val;
+ struct dir_baton *new_db;
+ svn_boolean_t is_copy;
+
+ LDR_DBG(("add_directory %s\n", path));
+
+ SVN_ERR(dump_pending(pb->eb, pool));
+
+ new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb,
+ pb, TRUE, pb->eb->pool);
+
+ /* This might be a replacement -- is the path already deleted? */
+ val = svn_hash_gets(pb->deleted_entries, path);
+
+ /* Detect an add-with-history */
+ is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
+
+ /* Dump the node */
+ SVN_ERR(dump_node(pb->eb, new_db->repos_relpath, new_db, NULL,
+ val ? svn_node_action_replace : svn_node_action_add,
+ is_copy,
+ is_copy ? new_db->copyfrom_path : NULL,
+ is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
+ pool));
+
+ if (val)
+ /* Delete the path, it's now been dumped */
+ svn_hash_sets(pb->deleted_entries, path, NULL);
+
+ /* Remember that we've started, but not yet finished handling this
+ directory. */
+ new_db->written_out = TRUE;
+ pb->eb->pending_baton = new_db;
+ pb->eb->pending_kind = svn_node_dir;
+
+ *child_baton = new_db;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct dir_baton *new_db;
+ const char *copyfrom_path = NULL;
+ svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+
+ LDR_DBG(("open_directory %s\n", path));
+
+ SVN_ERR(dump_pending(pb->eb, pool));
+
+ /* If the parent directory has explicit comparison path and rev,
+ record the same for this one. */
+ if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
+ {
+ copyfrom_path = svn_relpath_join(pb->copyfrom_path,
+ svn_relpath_basename(path, NULL),
+ pb->eb->pool);
+ copyfrom_rev = pb->copyfrom_rev;
+ }
+
+ new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb,
+ FALSE, pb->eb->pool);
+
+ *child_baton = new_db;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = dir_baton;
+ apr_hash_index_t *hi;
+ svn_boolean_t this_pending;
+
+ LDR_DBG(("close_directory %p\n", dir_baton));
+
+ /* Remember if this directory is the one currently pending. */
+ this_pending = (db->eb->pending_baton == db);
+
+ SVN_ERR(dump_pending(db->eb, pool));
+
+ /* If this directory was pending, then dump_pending() should have
+ taken care of all the props and such. Of course, the only way
+ that would be the case is if this directory was added/replaced.
+
+ Otherwise, if stuff for this directory has already been written
+ out (at some point in the past, prior to our handling other
+ nodes), we might need to generate a second "change" record just
+ to carry the information we've since learned about the
+ directory. */
+ if ((! this_pending) && (db->dump_props))
+ {
+ SVN_ERR(dump_node(db->eb, db->repos_relpath, db, NULL,
+ svn_node_action_change, FALSE,
+ NULL, SVN_INVALID_REVNUM, pool));
+ db->eb->pending_baton = db;
+ db->eb->pending_kind = svn_node_dir;
+ SVN_ERR(dump_pending(db->eb, pool));
+ }
+
+ /* Dump the deleted directory entries */
+ for (hi = apr_hash_first(pool, db->deleted_entries); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+
+ SVN_ERR(dump_node(db->eb, path, NULL, NULL, svn_node_action_delete,
+ FALSE, NULL, SVN_INVALID_REVNUM, pool));
+ }
+
+ /* ### should be unnecessary */
+ apr_hash_clear(db->deleted_entries);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct file_baton *fb;
+ void *val;
+
+ LDR_DBG(("add_file %s\n", path));
+
+ SVN_ERR(dump_pending(pb->eb, pool));
+
+ /* Make the file baton. */
+ fb = make_file_baton(path, pb, pool);
+
+ /* This might be a replacement -- is the path already deleted? */
+ val = svn_hash_gets(pb->deleted_entries, path);
+
+ /* Detect add-with-history. */
+ if (ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev))
+ {
+ fb->copyfrom_path = svn_relpath_canonicalize(copyfrom_path, fb->pool);
+ fb->copyfrom_rev = copyfrom_rev;
+ fb->is_copy = TRUE;
+ }
+ fb->action = val ? svn_node_action_replace : svn_node_action_add;
+
+ /* Delete the path, it's now been dumped. */
+ if (val)
+ svn_hash_sets(pb->deleted_entries, path, NULL);
+
+ *file_baton = fb;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t ancestor_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ struct dir_baton *pb = parent_baton;
+ struct file_baton *fb;
+
+ LDR_DBG(("open_file %s\n", path));
+
+ SVN_ERR(dump_pending(pb->eb, pool));
+
+ /* Make the file baton. */
+ fb = make_file_baton(path, pb, pool);
+
+ /* If the parent directory has explicit copyfrom path and rev,
+ record the same for this one. */
+ if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
+ {
+ fb->copyfrom_path = svn_relpath_join(pb->copyfrom_path,
+ svn_relpath_basename(path, NULL),
+ pb->eb->pool);
+ fb->copyfrom_rev = pb->copyfrom_rev;
+ }
+
+ *file_baton = fb;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_dir_prop(void *parent_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct dir_baton *db = parent_baton;
+ svn_boolean_t this_pending;
+
+ LDR_DBG(("change_dir_prop %p\n", parent_baton));
+
+ /* This directory is not pending, but something else is, so handle
+ the "something else". */
+ this_pending = (db->eb->pending_baton == db);
+ if (! this_pending)
+ SVN_ERR(dump_pending(db->eb, pool));
+
+ if (svn_property_kind2(name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+
+ if (value)
+ svn_hash_sets(db->props,
+ apr_pstrdup(db->pool, name),
+ svn_string_dup(value, db->pool));
+ else
+ svn_hash_sets(db->deleted_props, apr_pstrdup(db->pool, name), "");
+
+ /* Make sure we eventually output the props, and disable printing
+ a couple of extra newlines */
+ db->dump_newlines = FALSE;
+ db->dump_props = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+
+ LDR_DBG(("change_file_prop %p\n", file_baton));
+
+ if (svn_property_kind2(name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+
+ if (value)
+ svn_hash_sets(fb->props,
+ apr_pstrdup(fb->pool, name),
+ svn_string_dup(value, fb->pool));
+ else
+ svn_hash_sets(fb->deleted_props, apr_pstrdup(fb->pool, name), "");
+
+ /* Dump the property headers and wait; close_file might need
+ to write text headers too depending on whether
+ apply_textdelta is called */
+ fb->dump_props = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct handler_baton *hb = baton;
+ static svn_error_t *err;
+
+ err = hb->apply_handler(window, hb->apply_baton);
+ if (window != NULL && !err)
+ return SVN_NO_ERROR;
+
+ if (err)
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton, const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton *fb = file_baton;
+ struct dump_edit_baton *eb = fb->eb;
+ struct handler_baton *hb;
+ svn_stream_t *delta_filestream;
+
+ LDR_DBG(("apply_textdelta %p\n", file_baton));
+
+ /* This is custom handler_baton, allocated from a separate pool. */
+ hb = apr_pcalloc(eb->pool, sizeof(*hb));
+
+ /* Use a temporary file to measure the Text-content-length */
+ delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
+
+ /* Prepare to write the delta to the delta_filestream */
+ svn_txdelta_to_svndiff3(&(hb->apply_handler), &(hb->apply_baton),
+ delta_filestream, 0,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+
+ /* Record that there's text to be dumped, and its base checksum. */
+ fb->dump_text = TRUE;
+ fb->base_checksum = apr_pstrdup(eb->pool, base_checksum);
+
+ /* The actual writing takes place when this function has
+ finished. Set handler and handler_baton now so for
+ window_handler() */
+ *handler = window_handler;
+ *handler_baton = hb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ struct file_baton *fb = file_baton;
+ struct dump_edit_baton *eb = fb->eb;
+ apr_finfo_t *info = apr_pcalloc(pool, sizeof(apr_finfo_t));
+ svn_stringbuf_t *propstring;
+
+ LDR_DBG(("close_file %p\n", file_baton));
+
+ SVN_ERR(dump_pending(eb, pool));
+
+ /* Dump the node. */
+ SVN_ERR(dump_node(eb, fb->repos_relpath, NULL, fb,
+ fb->action, fb->is_copy, fb->copyfrom_path,
+ fb->copyfrom_rev, pool));
+
+ /* Some pending properties to dump? We'll dump just the headers for
+ now, then dump the actual propchange content only after dumping
+ the text headers too (if present). */
+ SVN_ERR(do_dump_props(&propstring, eb->stream, fb->props, fb->deleted_props,
+ &(fb->dump_props), pool, pool));
+
+ /* Dump the text headers */
+ if (fb->dump_text)
+ {
+ apr_status_t err;
+
+ /* Text-delta: true */
+ SVN_ERR(svn_stream_puts(eb->stream,
+ SVN_REPOS_DUMPFILE_TEXT_DELTA
+ ": true\n"));
+
+ err = apr_file_info_get(info, APR_FINFO_SIZE, eb->delta_file);
+ if (err)
+ SVN_ERR(svn_error_wrap_apr(err, NULL));
+
+ if (fb->base_checksum)
+ /* Text-delta-base-md5: */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
+ ": %s\n",
+ fb->base_checksum));
+
+ /* Text-content-length: 39 */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
+ ": %lu\n",
+ (unsigned long)info->size));
+
+ /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
+ ": %s\n",
+ text_checksum));
+ }
+
+ /* Content-length: 1549 */
+ /* If both text and props are absent, skip this header */
+ if (fb->dump_props)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %ld\n\n",
+ (unsigned long)info->size + propstring->len));
+ else if (fb->dump_text)
+ SVN_ERR(svn_stream_printf(eb->stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %ld\n\n",
+ (unsigned long)info->size));
+
+ /* Dump the props now */
+ if (fb->dump_props)
+ {
+ SVN_ERR(svn_stream_write(eb->stream, propstring->data,
+ &(propstring->len)));
+
+ /* Cleanup */
+ fb->dump_props = FALSE;
+ apr_hash_clear(fb->props);
+ apr_hash_clear(fb->deleted_props);
+ }
+
+ /* Dump the text */
+ if (fb->dump_text)
+ {
+ /* Seek to the beginning of the delta file, map it to a stream,
+ and copy the stream to eb->stream. Then close the stream and
+ truncate the file so we can reuse it for the next textdelta
+ application. Note that the file isn't created, opened or
+ closed here */
+ svn_stream_t *delta_filestream;
+ apr_off_t offset = 0;
+
+ SVN_ERR(svn_io_file_seek(eb->delta_file, APR_SET, &offset, pool));
+ delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
+ SVN_ERR(svn_stream_copy3(delta_filestream, eb->stream, NULL, NULL, pool));
+
+ /* Cleanup */
+ SVN_ERR(svn_stream_close(delta_filestream));
+ SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool));
+ }
+
+ /* Write a couple of blank lines for matching output with `svnadmin
+ dump` */
+ SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_edit(void *edit_baton, apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct dump_edit_baton *eb = baton;
+ svn_stream_t *fstream;
+ svn_error_t *err;
+
+ if (path[0] == '/')
+ path += 1;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->current_revision - 1;
+
+ SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+
+ err = svn_ra_get_file(eb->ra_session, path, base_revision,
+ fstream, NULL, NULL, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ SVN_ERR(svn_stream_close(fstream));
+
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ SVN_ERR(svn_stream_close(fstream));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct dump_edit_baton *eb = baton;
+ svn_node_kind_t node_kind;
+
+ if (path[0] == '/')
+ path += 1;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->current_revision - 1;
+
+ SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind,
+ scratch_pool));
+
+ if (node_kind == svn_node_file)
+ {
+ SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision,
+ NULL, NULL, props, result_pool));
+ }
+ else if (node_kind == svn_node_dir)
+ {
+ apr_array_header_t *tmp_props;
+
+ SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path,
+ base_revision, 0 /* Dirent fields */,
+ result_pool));
+ tmp_props = svn_prop_hash_to_array(*props, result_pool);
+ SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
+ result_pool));
+ *props = svn_prop_array_to_hash(tmp_props, result_pool);
+ }
+ else
+ {
+ *props = apr_hash_make(result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct dump_edit_baton *eb = baton;
+
+ if (path[0] == '/')
+ path += 1;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = eb->current_revision - 1;
+
+ SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_rdump__get_dump_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_ra_session_t *ra_session,
+ const char *update_anchor_relpath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ struct dump_edit_baton *eb;
+ svn_delta_editor_t *de;
+ svn_delta_shim_callbacks_t *shim_callbacks =
+ svn_delta_shim_callbacks_default(pool);
+
+ eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton));
+ eb->stream = stream;
+ eb->ra_session = ra_session;
+ eb->update_anchor_relpath = update_anchor_relpath;
+ eb->current_revision = revision;
+ eb->pending_kind = svn_node_none;
+
+ /* Create a special per-revision pool */
+ eb->pool = svn_pool_create(pool);
+
+ /* Open a unique temporary file for all textdelta applications in
+ this edit session. The file is automatically closed and cleaned
+ up when the edit session is done. */
+ SVN_ERR(svn_io_open_unique_file3(&(eb->delta_file), &(eb->delta_abspath),
+ NULL, svn_io_file_del_on_close, pool, pool));
+
+ de = svn_delta_default_editor(pool);
+ de->open_root = open_root;
+ de->delete_entry = delete_entry;
+ de->add_directory = add_directory;
+ de->open_directory = open_directory;
+ de->close_directory = close_directory;
+ de->change_dir_prop = change_dir_prop;
+ de->change_file_prop = change_file_prop;
+ de->apply_textdelta = apply_textdelta;
+ de->add_file = add_file;
+ de->open_file = open_file;
+ de->close_file = close_file;
+ de->close_edit = close_edit;
+
+ /* Set the edit_baton and editor. */
+ *edit_baton = eb;
+ *editor = de;
+
+ /* Wrap this editor in a cancellation editor. */
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
+ de, eb, editor, edit_baton, pool));
+
+ shim_callbacks->fetch_base_func = fetch_base_func;
+ shim_callbacks->fetch_props_func = fetch_props_func;
+ shim_callbacks->fetch_kind_func = fetch_kind_func;
+ shim_callbacks->fetch_baton = eb;
+
+ SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
+ NULL, NULL, shim_callbacks, pool, pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svnrdump/load_editor.c b/contrib/subversion/subversion/svnrdump/load_editor.c
new file mode 100644
index 0000000..1b053f2
--- /dev/null
+++ b/contrib/subversion/subversion/svnrdump/load_editor.c
@@ -0,0 +1,1211 @@
+/*
+ * load_editor.c: The svn_delta_editor_t editor used by svnrdump to
+ * load revisions.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_delta.h"
+#include "svn_repos.h"
+#include "svn_props.h"
+#include "svn_path.h"
+#include "svn_ra.h"
+#include "svn_subst.h"
+#include "svn_io.h"
+#include "svn_private_config.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_fspath.h"
+
+#include "svnrdump.h"
+
+#define SVNRDUMP_PROP_LOCK SVN_PROP_PREFIX "rdump-lock"
+
+#if 0
+#define LDR_DBG(x) SVN_DBG(x)
+#else
+#define LDR_DBG(x) while(0)
+#endif
+
+
+
+/**
+ * General baton used by the parser functions.
+ */
+struct parse_baton
+{
+ /* Commit editor and baton used to transfer loaded revisions to
+ the target repository. */
+ const svn_delta_editor_t *commit_editor;
+ void *commit_edit_baton;
+
+ /* RA session(s) for committing to the target repository. */
+ svn_ra_session_t *session;
+ svn_ra_session_t *aux_session;
+
+ /* To bleep, or not to bleep? (What kind of question is that?) */
+ svn_boolean_t quiet;
+
+ /* UUID found in the dumpstream, if any; NULL otherwise. */
+ const char *uuid;
+
+ /* Root URL of the target repository. */
+ const char *root_url;
+
+ /* The "parent directory" of the target repository in which to load.
+ (This is essentially the difference between ROOT_URL and
+ SESSION's url, and roughly equivalent to the 'svnadmin load
+ --parent-dir' option.) */
+ const char *parent_dir;
+
+ /* A mapping of svn_revnum_t * dump stream revisions to their
+ corresponding svn_revnum_t * target repository revisions. */
+ /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903
+ ### for discussion about improving the memory costs of this mapping. */
+ apr_hash_t *rev_map;
+
+ /* The most recent (youngest) revision from the dump stream mapped in
+ REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */
+ svn_revnum_t last_rev_mapped;
+
+ /* The oldest revision loaded from the dump stream, or
+ SVN_INVALID_REVNUM if none have been loaded. */
+ svn_revnum_t oldest_dumpstream_rev;
+};
+
+/**
+ * Use to wrap the dir_context_t in commit.c so we can keep track of
+ * depth, relpath and parent for open_directory and close_directory.
+ */
+struct directory_baton
+{
+ void *baton;
+ const char *relpath;
+ int depth;
+ struct directory_baton *parent;
+};
+
+/**
+ * Baton used to represent a node; to be used by the parser
+ * functions. Contains a link to the revision baton.
+ */
+struct node_baton
+{
+ const char *path;
+ svn_node_kind_t kind;
+ enum svn_node_action action;
+
+ svn_revnum_t copyfrom_rev;
+ const char *copyfrom_path;
+
+ void *file_baton;
+ const char *base_checksum;
+
+ struct revision_baton *rb;
+};
+
+/**
+ * Baton used to represet a revision; used by the parser
+ * functions. Contains a link to the parser baton.
+ */
+struct revision_baton
+{
+ svn_revnum_t rev;
+ apr_hash_t *revprop_table;
+ apr_int32_t rev_offset;
+
+ const svn_string_t *datestamp;
+ const svn_string_t *author;
+
+ struct parse_baton *pb;
+ struct directory_baton *db;
+ apr_pool_t *pool;
+};
+
+
+
+/* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
+ anything added to the hash is allocated in the hash's pool. */
+static void
+set_revision_mapping(apr_hash_t *rev_map,
+ svn_revnum_t from_rev,
+ svn_revnum_t to_rev)
+{
+ svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
+ sizeof(svn_revnum_t) * 2);
+ mapped_revs[0] = from_rev;
+ mapped_revs[1] = to_rev;
+ apr_hash_set(rev_map, mapped_revs,
+ sizeof(svn_revnum_t), mapped_revs + 1);
+}
+
+/* Return the revision to which FROM_REV maps in REV_MAP, or
+ SVN_INVALID_REVNUM if no such mapping exists. */
+static svn_revnum_t
+get_revision_mapping(apr_hash_t *rev_map,
+ svn_revnum_t from_rev)
+{
+ svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
+ sizeof(from_rev));
+ return to_rev ? *to_rev : SVN_INVALID_REVNUM;
+}
+
+
+/* Prepend the mergeinfo source paths in MERGEINFO_ORIG with
+ PARENT_DIR, and return it in *MERGEINFO_VAL. */
+/* ### FIXME: Consider somehow sharing code with
+ ### libsvn_repos/load-fs-vtable.c:prefix_mergeinfo_paths() */
+static svn_error_t *
+prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
+ const svn_string_t *mergeinfo_orig,
+ const char *parent_dir,
+ apr_pool_t *pool)
+{
+ apr_hash_t *prefixed_mergeinfo, *mergeinfo;
+ apr_hash_index_t *hi;
+ void *rangelist;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
+ prefixed_mergeinfo = apr_hash_make(pool);
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ const char *path, *merge_source;
+
+ apr_hash_this(hi, &key, NULL, &rangelist);
+ merge_source = svn_relpath_canonicalize(key, pool);
+
+ /* The svn:mergeinfo property syntax demands a repos abspath */
+ path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
+ merge_source, pool),
+ pool);
+ svn_hash_sets(prefixed_mergeinfo, path, rangelist);
+ }
+ return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
+}
+
+
+/* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
+ as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
+ (allocated from POOL). */
+/* ### FIXME: Consider somehow sharing code with
+ ### libsvn_repos/load-fs-vtable.c:renumber_mergeinfo_revs() */
+static svn_error_t *
+renumber_mergeinfo_revs(svn_string_t **final_val,
+ const svn_string_t *initial_val,
+ struct revision_baton *rb,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
+ svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
+
+ /* Issue #3020
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
+ Remove mergeinfo older than the oldest revision in the dump stream
+ and adjust its revisions by the difference between the head rev of
+ the target repository and the current dump stream rev. */
+ if (rb->pb->oldest_dumpstream_rev > 1)
+ {
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &predates_stream_mergeinfo, mergeinfo,
+ rb->pb->oldest_dumpstream_rev - 1, 0,
+ TRUE, subpool, subpool));
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &mergeinfo, mergeinfo,
+ rb->pb->oldest_dumpstream_rev - 1, 0,
+ FALSE, subpool, subpool));
+ SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
+ &predates_stream_mergeinfo,
+ predates_stream_mergeinfo,
+ -rb->rev_offset, subpool, subpool));
+ }
+ else
+ {
+ predates_stream_mergeinfo = NULL;
+ }
+
+ for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ svn_rangelist_t *rangelist;
+ struct parse_baton *pb = rb->pb;
+ int i;
+ const void *path;
+ apr_ssize_t pathlen;
+ void *val;
+
+ apr_hash_this(hi, &path, &pathlen, &val);
+ rangelist = val;
+
+ /* Possibly renumber revisions in merge source's rangelist. */
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_revnum_t rev_from_map;
+ svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
+ svn_merge_range_t *);
+ rev_from_map = get_revision_mapping(pb->rev_map, range->start);
+ if (SVN_IS_VALID_REVNUM(rev_from_map))
+ {
+ range->start = rev_from_map;
+ }
+ else if (range->start == pb->oldest_dumpstream_rev - 1)
+ {
+ /* Since the start revision of svn_merge_range_t are not
+ inclusive there is one possible valid start revision that
+ won't be found in the PB->REV_MAP mapping of load stream
+ revsions to loaded revisions: The revision immediately
+ preceeding the oldest revision from the load stream.
+ This is a valid revision for mergeinfo, but not a valid
+ copy from revision (which PB->REV_MAP also maps for) so it
+ will never be in the mapping.
+
+ If that is what we have here, then find the mapping for the
+ oldest rev from the load stream and subtract 1 to get the
+ renumbered, non-inclusive, start revision. */
+ rev_from_map = get_revision_mapping(pb->rev_map,
+ pb->oldest_dumpstream_rev);
+ if (SVN_IS_VALID_REVNUM(rev_from_map))
+ range->start = rev_from_map - 1;
+ }
+ else
+ {
+ /* If we can't remap the start revision then don't even bother
+ trying to remap the end revision. It's possible we might
+ actually succeed at the latter, which can result in invalid
+ mergeinfo with a start rev > end rev. If that gets into the
+ repository then a world of bustage breaks loose anytime that
+ bogus mergeinfo is parsed. See
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
+ */
+ continue;
+ }
+
+ rev_from_map = get_revision_mapping(pb->rev_map, range->end);
+ if (SVN_IS_VALID_REVNUM(rev_from_map))
+ range->end = rev_from_map;
+ }
+ apr_hash_set(final_mergeinfo, path, pathlen, rangelist);
+ }
+
+ if (predates_stream_mergeinfo)
+ {
+ SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
+ subpool, subpool));
+ }
+
+ SVN_ERR(svn_mergeinfo_sort(final_mergeinfo, subpool));
+
+ /* Mergeinfo revision sources for r0 and r1 are invalid; you can't merge r0
+ or r1. However, svndumpfilter can be abused to produce r1 merge source
+ revs. So if we encounter any, then strip them out, no need to put them
+ into the load target. */
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(&final_mergeinfo,
+ final_mergeinfo,
+ 1, 0, FALSE,
+ subpool, subpool));
+
+ SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+commit_callback(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct revision_baton *rb = baton;
+ struct parse_baton *pb = rb->pb;
+
+ /* ### Don't print directly; generate a notification. */
+ if (! pb->quiet)
+ SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n",
+ commit_info->revision));
+
+ /* Add the mapping of the dumpstream revision to the committed revision. */
+ set_revision_mapping(pb->rev_map, rb->rev, commit_info->revision);
+
+ /* If the incoming dump stream has non-contiguous revisions (e.g. from
+ using svndumpfilter --drop-empty-revs without --renumber-revs) then
+ we must account for the missing gaps in PB->REV_MAP. Otherwise we
+ might not be able to map all mergeinfo source revisions to the correct
+ revisions in the target repos. */
+ if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
+ && (rb->rev != pb->last_rev_mapped + 1))
+ {
+ svn_revnum_t i;
+
+ for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
+ {
+ set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
+ }
+ }
+
+ /* Update our "last revision mapped". */
+ pb->last_rev_mapped = rb->rev;
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements `svn_ra__lock_retry_func_t'. */
+static svn_error_t *
+lock_retry_func(void *baton,
+ const svn_string_t *reposlocktoken,
+ apr_pool_t *pool)
+{
+ return svn_cmdline_printf(pool,
+ _("Failed to get lock on destination "
+ "repos, currently held by '%s'\n"),
+ reposlocktoken->data);
+}
+
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct revision_baton *rb = baton;
+ svn_stream_t *fstream;
+ svn_error_t *err;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = rb->rev - 1;
+
+ SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+
+ err = svn_ra_get_file(rb->pb->aux_session, path, base_revision,
+ fstream, NULL, NULL, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ SVN_ERR(svn_stream_close(fstream));
+
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ SVN_ERR(svn_stream_close(fstream));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct revision_baton *rb = baton;
+ svn_node_kind_t node_kind;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = rb->rev - 1;
+
+ SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
+ &node_kind, scratch_pool));
+
+ if (node_kind == svn_node_file)
+ {
+ SVN_ERR(svn_ra_get_file(rb->pb->aux_session, path, base_revision,
+ NULL, NULL, props, result_pool));
+ }
+ else if (node_kind == svn_node_dir)
+ {
+ apr_array_header_t *tmp_props;
+
+ SVN_ERR(svn_ra_get_dir2(rb->pb->aux_session, NULL, NULL, props, path,
+ base_revision, 0 /* Dirent fields */,
+ result_pool));
+ tmp_props = svn_prop_hash_to_array(*props, result_pool);
+ SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
+ result_pool));
+ *props = svn_prop_array_to_hash(tmp_props, result_pool);
+ }
+ else
+ {
+ *props = apr_hash_make(result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct revision_baton *rb = baton;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = rb->rev - 1;
+
+ SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
+ kind, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_delta_shim_callbacks_t *
+get_shim_callbacks(struct revision_baton *rb,
+ apr_pool_t *pool)
+{
+ svn_delta_shim_callbacks_t *callbacks =
+ svn_delta_shim_callbacks_default(pool);
+
+ callbacks->fetch_props_func = fetch_props_func;
+ callbacks->fetch_kind_func = fetch_kind_func;
+ callbacks->fetch_base_func = fetch_base_func;
+ callbacks->fetch_baton = rb;
+
+ return callbacks;
+}
+
+/* Acquire a lock (of sorts) on the repository associated with the
+ * given RA SESSION. This lock is just a revprop change attempt in a
+ * time-delay loop. This function is duplicated by svnsync in
+ * svnsync/svnsync.c
+ *
+ * ### TODO: Make this function more generic and
+ * expose it through a header for use by other Subversion
+ * applications to avoid duplication.
+ */
+static svn_error_t *
+get_lock(const svn_string_t **lock_string_p,
+ svn_ra_session_t *session,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_boolean_t be_atomic;
+
+ SVN_ERR(svn_ra_has_capability(session, &be_atomic,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ pool));
+ if (! be_atomic)
+ {
+ /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */
+ svn_error_t *err =
+ svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Target server does not support atomic revision "
+ "property edits; consider upgrading it to 1.7."));
+ svn_handle_warning2(stderr, err, "svnrdump: ");
+ svn_error_clear(err);
+ }
+
+ return svn_ra__get_operational_lock(lock_string_p, NULL, session,
+ SVNRDUMP_PROP_LOCK, FALSE,
+ 10 /* retries */, lock_retry_func, NULL,
+ cancel_func, cancel_baton, pool);
+}
+
+static svn_error_t *
+new_revision_record(void **revision_baton,
+ apr_hash_t *headers,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ struct revision_baton *rb;
+ struct parse_baton *pb;
+ apr_hash_index_t *hi;
+ svn_revnum_t head_rev;
+
+ rb = apr_pcalloc(pool, sizeof(*rb));
+ pb = parse_baton;
+ rb->pool = svn_pool_create(pool);
+ rb->pb = pb;
+
+ for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
+ {
+ const char *hname = svn__apr_hash_index_key(hi);
+ const char *hval = svn__apr_hash_index_val(hi);
+
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_REVISION_NUMBER) == 0)
+ rb->rev = atoi(hval);
+ }
+
+ SVN_ERR(svn_ra_get_latest_revnum(pb->session, &head_rev, pool));
+
+ /* FIXME: This is a lame fallback loading multiple segments of dump in
+ several separate operations. It is highly susceptible to race conditions.
+ Calculate the revision 'offset' for finding copyfrom sources.
+ It might be positive or negative. */
+ rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
+
+ /* Stash the oldest (non-zero) dumpstream revision seen. */
+ if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev)))
+ pb->oldest_dumpstream_rev = rb->rev;
+
+ /* Set the commit_editor/ commit_edit_baton to NULL and wait for
+ them to be created in new_node_record */
+ rb->pb->commit_editor = NULL;
+ rb->pb->commit_edit_baton = NULL;
+ rb->revprop_table = apr_hash_make(rb->pool);
+
+ *revision_baton = rb;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+magic_header_record(int version,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+uuid_record(const char *uuid,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ struct parse_baton *pb;
+ pb = parse_baton;
+ pb->uuid = apr_pstrdup(pool, uuid);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+new_node_record(void **node_baton,
+ apr_hash_t *headers,
+ void *revision_baton,
+ apr_pool_t *pool)
+{
+ struct revision_baton *rb = revision_baton;
+ const struct svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
+ void *commit_edit_baton = rb->pb->commit_edit_baton;
+ struct node_baton *nb;
+ struct directory_baton *child_db;
+ apr_hash_index_t *hi;
+ void *child_baton;
+ char *relpath_compose;
+ const char *nb_dirname;
+
+ nb = apr_pcalloc(rb->pool, sizeof(*nb));
+ nb->rb = rb;
+
+ nb->copyfrom_path = NULL;
+ nb->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* If the creation of commit_editor is pending, create it now and
+ open_root on it; also create a top-level directory baton. */
+
+ if (!commit_editor)
+ {
+ /* The revprop_table should have been filled in with important
+ information like svn:log in set_revision_property. We can now
+ use it all this information to create our commit_editor. But
+ first, clear revprops that we aren't allowed to set with the
+ commit_editor. We'll set them separately using the RA API
+ after closing the editor (see close_revision). */
+
+ svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL);
+ svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL);
+
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->pb->session,
+ get_shim_callbacks(rb, rb->pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
+ &commit_edit_baton, rb->revprop_table,
+ commit_callback, revision_baton,
+ NULL, FALSE, rb->pool));
+
+ rb->pb->commit_editor = commit_editor;
+ rb->pb->commit_edit_baton = commit_edit_baton;
+
+ SVN_ERR(commit_editor->open_root(commit_edit_baton,
+ rb->rev - rb->rev_offset - 1,
+ rb->pool, &child_baton));
+
+ LDR_DBG(("Opened root %p\n", child_baton));
+
+ /* child_db corresponds to the root directory baton here */
+ child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
+ child_db->baton = child_baton;
+ child_db->depth = 0;
+ child_db->relpath = "";
+ child_db->parent = NULL;
+ rb->db = child_db;
+ }
+
+ for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi))
+ {
+ const char *hname = svn__apr_hash_index_key(hi);
+ const char *hval = svn__apr_hash_index_val(hi);
+
+ /* Parse the different kinds of headers we can encounter and
+ stuff them into the node_baton for writing later */
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0)
+ nb->path = apr_pstrdup(rb->pool, hval);
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0)
+ nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir;
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0)
+ {
+ if (strcmp(hval, "add") == 0)
+ nb->action = svn_node_action_add;
+ if (strcmp(hval, "change") == 0)
+ nb->action = svn_node_action_change;
+ if (strcmp(hval, "delete") == 0)
+ nb->action = svn_node_action_delete;
+ if (strcmp(hval, "replace") == 0)
+ nb->action = svn_node_action_replace;
+ }
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0)
+ nb->base_checksum = apr_pstrdup(rb->pool, hval);
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0)
+ nb->copyfrom_rev = atoi(hval);
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0)
+ nb->copyfrom_path = apr_pstrdup(rb->pool, hval);
+ }
+
+ nb_dirname = svn_relpath_dirname(nb->path, pool);
+ if (svn_path_compare_paths(nb_dirname,
+ rb->db->relpath) != 0)
+ {
+ char *ancestor_path;
+ apr_size_t residual_close_count;
+ apr_array_header_t *residual_open_path;
+ int i;
+ apr_size_t n;
+
+ /* Before attempting to handle the action, call open_directory
+ for all the path components and set the directory baton
+ accordingly */
+ ancestor_path =
+ svn_relpath_get_longest_ancestor(nb_dirname,
+ rb->db->relpath, pool);
+ residual_close_count =
+ svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path,
+ rb->db->relpath));
+ residual_open_path =
+ svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path,
+ nb_dirname), pool);
+
+ /* First close all as many directories as there are after
+ skip_ancestor, and then open fresh directories */
+ for (n = 0; n < residual_close_count; n ++)
+ {
+ /* Don't worry about destroying the actual rb->db object,
+ since the pool we're using has the lifetime of one
+ revision anyway */
+ LDR_DBG(("Closing dir %p\n", rb->db->baton));
+ SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
+ rb->db = rb->db->parent;
+ }
+
+ for (i = 0; i < residual_open_path->nelts; i ++)
+ {
+ relpath_compose =
+ svn_relpath_join(rb->db->relpath,
+ APR_ARRAY_IDX(residual_open_path, i, const char *),
+ rb->pool);
+ SVN_ERR(commit_editor->open_directory(relpath_compose,
+ rb->db->baton,
+ rb->rev - rb->rev_offset - 1,
+ rb->pool, &child_baton));
+ LDR_DBG(("Opened dir %p\n", child_baton));
+ child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
+ child_db->baton = child_baton;
+ child_db->depth = rb->db->depth + 1;
+ child_db->relpath = relpath_compose;
+ child_db->parent = rb->db;
+ rb->db = child_db;
+ }
+ }
+
+ /* Fix up the copyfrom information in light of mapped revisions and
+ non-root load targets, and convert copyfrom path into a full
+ URL. */
+ if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
+ {
+ svn_revnum_t copyfrom_rev;
+
+ /* Try to find the copyfrom revision in the revision map;
+ failing that, fall back to the revision offset approach. */
+ copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
+ if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
+ copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
+
+ if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("Relative source revision %ld is not"
+ " available in current repository"),
+ copyfrom_rev);
+
+ nb->copyfrom_rev = copyfrom_rev;
+
+ if (rb->pb->parent_dir)
+ nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir,
+ nb->copyfrom_path, rb->pool);
+ nb->copyfrom_path = svn_path_url_add_component2(rb->pb->root_url,
+ nb->copyfrom_path,
+ rb->pool);
+ }
+
+
+ switch (nb->action)
+ {
+ case svn_node_action_delete:
+ case svn_node_action_replace:
+ LDR_DBG(("Deleting entry %s in %p\n", nb->path, rb->db->baton));
+ SVN_ERR(commit_editor->delete_entry(nb->path, rb->rev - rb->rev_offset,
+ rb->db->baton, rb->pool));
+ if (nb->action == svn_node_action_delete)
+ break;
+ else
+ /* FALL THROUGH */;
+ case svn_node_action_add:
+ switch (nb->kind)
+ {
+ case svn_node_file:
+ SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton,
+ nb->copyfrom_path,
+ nb->copyfrom_rev,
+ rb->pool, &(nb->file_baton)));
+ LDR_DBG(("Added file %s to dir %p as %p\n",
+ nb->path, rb->db->baton, nb->file_baton));
+ break;
+ case svn_node_dir:
+ SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton,
+ nb->copyfrom_path,
+ nb->copyfrom_rev,
+ rb->pool, &child_baton));
+ LDR_DBG(("Added dir %s to dir %p as %p\n",
+ nb->path, rb->db->baton, child_baton));
+ child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
+ child_db->baton = child_baton;
+ child_db->depth = rb->db->depth + 1;
+ child_db->relpath = apr_pstrdup(rb->pool, nb->path);
+ child_db->parent = rb->db;
+ rb->db = child_db;
+ break;
+ default:
+ break;
+ }
+ break;
+ case svn_node_action_change:
+ switch (nb->kind)
+ {
+ case svn_node_file:
+ SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton,
+ SVN_INVALID_REVNUM, rb->pool,
+ &(nb->file_baton)));
+ break;
+ default:
+ SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton,
+ rb->rev - rb->rev_offset - 1,
+ rb->pool, &child_baton));
+ child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
+ child_db->baton = child_baton;
+ child_db->depth = rb->db->depth + 1;
+ child_db->relpath = apr_pstrdup(rb->pool, nb->path);
+ child_db->parent = rb->db;
+ rb->db = child_db;
+ break;
+ }
+ break;
+ }
+
+ *node_baton = nb;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+set_revision_property(void *baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ struct revision_baton *rb = baton;
+
+ SVN_ERR(svn_rdump__normalize_prop(name, &value, rb->pool));
+
+ SVN_ERR(svn_repos__validate_prop(name, value, rb->pool));
+
+ if (rb->rev > 0)
+ {
+ svn_hash_sets(rb->revprop_table,
+ apr_pstrdup(rb->pool, name),
+ svn_string_dup(value, rb->pool));
+ }
+ else if (rb->rev_offset == -1)
+ {
+ /* Special case: set revision 0 properties directly (which is
+ safe because the commit_editor hasn't been created yet), but
+ only when loading into an 'empty' filesystem. */
+ SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0,
+ name, NULL, value, rb->pool));
+ }
+
+ /* Remember any datestamp/ author that passes through (see comment
+ in close_revision). */
+ if (!strcmp(name, SVN_PROP_REVISION_DATE))
+ rb->datestamp = svn_string_dup(value, rb->pool);
+ if (!strcmp(name, SVN_PROP_REVISION_AUTHOR))
+ rb->author = svn_string_dup(value, rb->pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+set_node_property(void *baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ struct node_baton *nb = baton;
+ const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
+ apr_pool_t *pool = nb->rb->pool;
+
+ if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
+ {
+ svn_string_t *renumbered_mergeinfo;
+ svn_string_t prop_val;
+
+ /* Tolerate mergeinfo with "\r\n" line endings because some
+ dumpstream sources might contain as much. If so normalize
+ the line endings to '\n' and make a notification to
+ PARSE_BATON->FEEDBACK_STREAM that we have made this
+ correction. */
+ if (strstr(value->data, "\r"))
+ {
+ const char *prop_eol_normalized;
+
+ SVN_ERR(svn_subst_translate_cstring2(value->data,
+ &prop_eol_normalized,
+ "\n", /* translate to LF */
+ FALSE, /* no repair */
+ NULL, /* no keywords */
+ FALSE, /* no expansion */
+ pool));
+ prop_val.data = prop_eol_normalized;
+ prop_val.len = strlen(prop_eol_normalized);
+ value = &prop_val;
+
+ /* ### TODO: notify? */
+ }
+
+ /* Renumber mergeinfo as appropriate. */
+ SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo, value,
+ nb->rb, pool));
+ value = renumbered_mergeinfo;
+
+ if (nb->rb->pb->parent_dir)
+ {
+ /* Prefix the merge source paths with PB->parent_dir. */
+ /* ASSUMPTION: All source paths are included in the dump stream. */
+ svn_string_t *mergeinfo_val;
+ SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val, value,
+ nb->rb->pb->parent_dir, pool));
+ value = mergeinfo_val;
+ }
+ }
+
+ SVN_ERR(svn_rdump__normalize_prop(name, &value, pool));
+
+ SVN_ERR(svn_repos__validate_prop(name, value, pool));
+
+ switch (nb->kind)
+ {
+ case svn_node_file:
+ LDR_DBG(("Applying properties on %p\n", nb->file_baton));
+ SVN_ERR(commit_editor->change_file_prop(nb->file_baton, name,
+ value, pool));
+ break;
+ case svn_node_dir:
+ LDR_DBG(("Applying properties on %p\n", nb->rb->db->baton));
+ SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton, name,
+ value, pool));
+ break;
+ default:
+ break;
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_node_property(void *baton,
+ const char *name)
+{
+ struct node_baton *nb = baton;
+ const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
+ apr_pool_t *pool = nb->rb->pool;
+
+ SVN_ERR(svn_repos__validate_prop(name, NULL, pool));
+
+ if (nb->kind == svn_node_file)
+ SVN_ERR(commit_editor->change_file_prop(nb->file_baton, name,
+ NULL, pool));
+ else
+ SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton, name,
+ NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+remove_node_props(void *baton)
+{
+ struct node_baton *nb = baton;
+ apr_pool_t *pool = nb->rb->pool;
+ apr_hash_index_t *hi;
+ apr_hash_t *props;
+
+ if ((nb->action == svn_node_action_add
+ || nb->action == svn_node_action_replace)
+ && ! SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
+ /* Add-without-history; no "old" properties to worry about. */
+ return SVN_NO_ERROR;
+
+ if (nb->kind == svn_node_file)
+ {
+ SVN_ERR(svn_ra_get_file(nb->rb->pb->aux_session, nb->path,
+ SVN_INVALID_REVNUM, NULL, NULL, &props, pool));
+ }
+ else /* nb->kind == svn_node_dir */
+ {
+ SVN_ERR(svn_ra_get_dir2(nb->rb->pb->aux_session, NULL, NULL, &props,
+ nb->path, SVN_INVALID_REVNUM, 0, pool));
+ }
+
+ for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_prop_kind_t kind = svn_property_kind2(name);
+
+ if (kind == svn_prop_regular_kind)
+ SVN_ERR(set_node_property(nb, name, NULL));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+set_fulltext(svn_stream_t **stream,
+ void *node_baton)
+{
+ struct node_baton *nb = node_baton;
+ const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
+ svn_txdelta_window_handler_t handler;
+ void *handler_baton;
+ apr_pool_t *pool = nb->rb->pool;
+
+ LDR_DBG(("Setting fulltext for %p\n", nb->file_baton));
+ SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
+ pool, &handler, &handler_baton));
+ *stream = svn_txdelta_target_push(handler, handler_baton,
+ svn_stream_empty(pool), pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ void *node_baton)
+{
+ struct node_baton *nb = node_baton;
+ const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
+ apr_pool_t *pool = nb->rb->pool;
+
+ LDR_DBG(("Applying textdelta to %p\n", nb->file_baton));
+ SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
+ pool, handler, handler_baton));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_node(void *baton)
+{
+ struct node_baton *nb = baton;
+ const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
+
+ /* Pass a file node closure through to the editor *unless* we
+ deleted the file (which doesn't require us to open it). */
+ if ((nb->kind == svn_node_file) && (nb->file_baton))
+ {
+ LDR_DBG(("Closing file %p\n", nb->file_baton));
+ SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool));
+ }
+
+ /* The svn_node_dir case is handled in close_revision */
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_revision(void *baton)
+{
+ struct revision_baton *rb = baton;
+ const svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
+ void *commit_edit_baton = rb->pb->commit_edit_baton;
+ svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
+
+ /* Fake revision 0 */
+ if (rb->rev == 0)
+ {
+ /* ### Don't print directly; generate a notification. */
+ if (! rb->pb->quiet)
+ SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n"));
+ }
+ else if (commit_editor)
+ {
+ /* Close all pending open directories, and then close the edit
+ session itself */
+ while (rb->db && rb->db->parent)
+ {
+ LDR_DBG(("Closing dir %p\n", rb->db->baton));
+ SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
+ rb->db = rb->db->parent;
+ }
+ /* root dir's baton */
+ LDR_DBG(("Closing edit on %p\n", commit_edit_baton));
+ SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
+ SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
+ }
+ else
+ {
+ void *child_baton;
+
+ /* Legitimate revision with no node information */
+ SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
+ &commit_edit_baton, rb->revprop_table,
+ commit_callback, baton,
+ NULL, FALSE, rb->pool));
+
+ SVN_ERR(commit_editor->open_root(commit_edit_baton,
+ rb->rev - rb->rev_offset - 1,
+ rb->pool, &child_baton));
+
+ LDR_DBG(("Opened root %p\n", child_baton));
+ LDR_DBG(("Closing edit on %p\n", commit_edit_baton));
+ SVN_ERR(commit_editor->close_directory(child_baton, rb->pool));
+ SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
+ }
+
+ /* svn_fs_commit_txn() rewrites the datestamp and author properties;
+ we'll rewrite them again by hand after closing the commit_editor.
+ The only time we don't do this is for revision 0 when loaded into
+ a non-empty repository. */
+ if (rb->rev > 0)
+ {
+ committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev);
+ }
+ else if (rb->rev_offset == -1)
+ {
+ committed_rev = 0;
+ }
+
+ if (SVN_IS_VALID_REVNUM(committed_rev))
+ {
+ SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_DATE,
+ rb->datestamp, rb->pool));
+ SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
+ SVN_PROP_REVISION_DATE,
+ NULL, rb->datestamp, rb->pool));
+ SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_AUTHOR,
+ rb->author, rb->pool));
+ SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
+ SVN_PROP_REVISION_AUTHOR,
+ NULL, rb->author, rb->pool));
+ }
+
+ svn_pool_destroy(rb->pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_rdump__load_dumpstream(svn_stream_t *stream,
+ svn_ra_session_t *session,
+ svn_ra_session_t *aux_session,
+ svn_boolean_t quiet,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_repos_parse_fns3_t *parser;
+ struct parse_baton *parse_baton;
+ const svn_string_t *lock_string;
+ svn_boolean_t be_atomic;
+ svn_error_t *err;
+ const char *session_url, *root_url, *parent_dir;
+
+ SVN_ERR(svn_ra_has_capability(session, &be_atomic,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ pool));
+ SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool));
+ SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool));
+ SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
+ SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir,
+ session_url, pool));
+
+ parser = apr_pcalloc(pool, sizeof(*parser));
+ parser->magic_header_record = magic_header_record;
+ parser->uuid_record = uuid_record;
+ parser->new_revision_record = new_revision_record;
+ parser->new_node_record = new_node_record;
+ parser->set_revision_property = set_revision_property;
+ parser->set_node_property = set_node_property;
+ parser->delete_node_property = delete_node_property;
+ parser->remove_node_props = remove_node_props;
+ parser->set_fulltext = set_fulltext;
+ parser->apply_textdelta = apply_textdelta;
+ parser->close_node = close_node;
+ parser->close_revision = close_revision;
+
+ parse_baton = apr_pcalloc(pool, sizeof(*parse_baton));
+ parse_baton->session = session;
+ parse_baton->aux_session = aux_session;
+ parse_baton->quiet = quiet;
+ parse_baton->root_url = root_url;
+ parse_baton->parent_dir = parent_dir;
+ parse_baton->rev_map = apr_hash_make(pool);
+ parse_baton->last_rev_mapped = SVN_INVALID_REVNUM;
+ parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
+
+ err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE,
+ cancel_func, cancel_baton, pool);
+
+ /* If all goes well, or if we're cancelled cleanly, don't leave a
+ stray lock behind. */
+ if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED)))
+ err = svn_error_compose_create(
+ svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK,
+ lock_string, pool),
+ err);
+ return err;
+}
diff --git a/contrib/subversion/subversion/svnrdump/svnrdump.1 b/contrib/subversion/subversion/svnrdump/svnrdump.1
new file mode 100644
index 0000000..99f5210
--- /dev/null
+++ b/contrib/subversion/subversion/svnrdump/svnrdump.1
@@ -0,0 +1,47 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svnrdump 1
+.SH NAME
+svnrdump \- Subversion remote repository dumper and loader
+.SH SYNOPSIS
+.TP
+\fBsvnrdump\fP \fIcommand\fP \fIurl\fP [\fIoptions\fP] [\fIargs\fP]
+.SH OVERVIEW
+Subversion is a version control system, which allows you to keep old
+versions of files and directories (usually source code), keep a log of
+who, when, and why changes occurred, etc., like CVS, RCS or SCCS.
+\fBSubversion\fP keeps a single copy of the master sources. This copy
+is called the source ``repository''; it contains all the information
+to permit extracting previous versions of those files at any time.
+
+For more information about the Subversion project, visit
+http://subversion.apache.org.
+
+Documentation for Subversion and its tools, including detailed usage
+explanations of the \fBsvn\fP, \fBsvnadmin\fP, \fBsvnserve\fP and
+\fBsvnlook\fP programs, historical background, philosophical
+approaches and reasonings, etc., can be found at
+http://svnbook.red-bean.com/.
+
+Run `svnrdump help' to access the built-in tool documentation.
diff --git a/contrib/subversion/subversion/svnrdump/svnrdump.c b/contrib/subversion/subversion/svnrdump/svnrdump.c
new file mode 100644
index 0000000..6bf409c
--- /dev/null
+++ b/contrib/subversion/subversion/svnrdump/svnrdump.c
@@ -0,0 +1,1185 @@
+/*
+ * svnrdump.c: Produce a dumpfile of a local or remote repository
+ * without touching the filesystem, but for temporary files.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_signal.h>
+#include <apr_uri.h>
+
+#include "svn_pools.h"
+#include "svn_cmdline.h"
+#include "svn_client.h"
+#include "svn_hash.h"
+#include "svn_ra.h"
+#include "svn_repos.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_private_config.h"
+#include "svn_string.h"
+#include "svn_props.h"
+
+#include "svnrdump.h"
+
+#include "private/svn_cmdline_private.h"
+#include "private/svn_ra_private.h"
+
+
+
+/*** Cancellation ***/
+
+/* A flag to see if we've been cancelled by the client or not. */
+static volatile sig_atomic_t cancelled = FALSE;
+
+/* A signal handler to support cancellation. */
+static void
+signal_handler(int signum)
+{
+ apr_signal(signum, SIG_IGN);
+ cancelled = TRUE;
+}
+
+/* Our cancellation callback. */
+static svn_error_t *
+check_cancel(void *baton)
+{
+ if (cancelled)
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+
+
+static svn_opt_subcommand_t dump_cmd, load_cmd;
+
+enum svn_svnrdump__longopt_t
+ {
+ opt_config_dir = SVN_OPT_FIRST_LONGOPT_ID,
+ opt_config_option,
+ opt_auth_username,
+ opt_auth_password,
+ opt_auth_nocache,
+ opt_non_interactive,
+ opt_force_interactive,
+ opt_incremental,
+ opt_trust_server_cert,
+ opt_version
+ };
+
+#define SVN_SVNRDUMP__BASE_OPTIONS opt_config_dir, \
+ opt_config_option, \
+ opt_auth_username, \
+ opt_auth_password, \
+ opt_auth_nocache, \
+ opt_trust_server_cert, \
+ opt_non_interactive, \
+ opt_force_interactive
+
+static const svn_opt_subcommand_desc2_t svnrdump__cmd_table[] =
+{
+ { "dump", dump_cmd, { 0 },
+ N_("usage: svnrdump dump URL [-r LOWER[:UPPER]]\n\n"
+ "Dump revisions LOWER to UPPER of repository at remote URL to stdout\n"
+ "in a 'dumpfile' portable format. If only LOWER is given, dump that\n"
+ "one revision.\n"),
+ { 'r', 'q', opt_incremental, SVN_SVNRDUMP__BASE_OPTIONS } },
+ { "load", load_cmd, { 0 },
+ N_("usage: svnrdump load URL\n\n"
+ "Load a 'dumpfile' given on stdin to a repository at remote URL.\n"),
+ { 'q', SVN_SVNRDUMP__BASE_OPTIONS } },
+ { "help", 0, { "?", "h" },
+ N_("usage: svnrdump help [SUBCOMMAND...]\n\n"
+ "Describe the usage of this program or its subcommands.\n"),
+ { 0 } },
+ { NULL, NULL, { 0 }, NULL, { 0 } }
+};
+
+static const apr_getopt_option_t svnrdump__options[] =
+ {
+ {"revision", 'r', 1,
+ N_("specify revision number ARG (or X:Y range)")},
+ {"quiet", 'q', 0,
+ N_("no progress (only errors) to stderr")},
+ {"incremental", opt_incremental, 0,
+ N_("dump incrementally")},
+ {"config-dir", opt_config_dir, 1,
+ N_("read user configuration files from directory ARG")},
+ {"username", opt_auth_username, 1,
+ N_("specify a username ARG")},
+ {"password", opt_auth_password, 1,
+ N_("specify a password ARG")},
+ {"non-interactive", opt_non_interactive, 0,
+ N_("do no interactive prompting (default is to prompt\n"
+ " "
+ "only if standard input is a terminal device)")},
+ {"force-interactive", opt_force_interactive, 0,
+ N_("do interactive prompting even if standard input\n"
+ " "
+ "is not a terminal device")},
+ {"no-auth-cache", opt_auth_nocache, 0,
+ N_("do not cache authentication tokens")},
+ {"help", 'h', 0,
+ N_("display this help")},
+ {"version", opt_version, 0,
+ N_("show program version information")},
+ {"config-option", opt_config_option, 1,
+ N_("set user configuration option in the format:\n"
+ " "
+ " FILE:SECTION:OPTION=[VALUE]\n"
+ " "
+ "For example:\n"
+ " "
+ " servers:global:http-library=serf")},
+ {"trust-server-cert", opt_trust_server_cert, 0,
+ N_("accept SSL server certificates from unknown\n"
+ " "
+ "certificate authorities without prompting (but only\n"
+ " "
+ "with '--non-interactive')") },
+ {0, 0, 0, 0}
+ };
+
+/* Baton for the RA replay session. */
+struct replay_baton {
+ /* A backdoor ra session for fetching information. */
+ svn_ra_session_t *extra_ra_session;
+
+ /* The output stream */
+ svn_stream_t *stdout_stream;
+
+ /* Whether to be quiet. */
+ svn_boolean_t quiet;
+};
+
+/* Option set */
+typedef struct opt_baton_t {
+ svn_client_ctx_t *ctx;
+ svn_ra_session_t *session;
+ const char *url;
+ svn_boolean_t help;
+ svn_boolean_t version;
+ svn_opt_revision_t start_revision;
+ svn_opt_revision_t end_revision;
+ svn_boolean_t quiet;
+ svn_boolean_t incremental;
+} opt_baton_t;
+
+/* Print dumpstream-formatted information about REVISION.
+ * Implements the `svn_ra_replay_revstart_callback_t' interface.
+ */
+static svn_error_t *
+replay_revstart(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ struct replay_baton *rb = replay_baton;
+ apr_hash_t *normal_props;
+ svn_stringbuf_t *propstring;
+ svn_stream_t *stdout_stream;
+ svn_stream_t *revprop_stream;
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ /* Revision-number: 19 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_REVISION_NUMBER
+ ": %ld\n", revision));
+ SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
+ propstring = svn_stringbuf_create_ensure(0, pool);
+ revprop_stream = svn_stream_from_stringbuf(propstring, pool);
+ SVN_ERR(svn_hash_write2(normal_props, revprop_stream, "PROPS-END", pool));
+ SVN_ERR(svn_stream_close(revprop_stream));
+
+ /* Prop-content-length: 13 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n", propstring->len));
+
+ /* Content-length: 29 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n\n", propstring->len));
+
+ /* Property data. */
+ SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
+ &(propstring->len)));
+
+ SVN_ERR(svn_stream_puts(stdout_stream, "\n"));
+ SVN_ERR(svn_stream_close(stdout_stream));
+
+ SVN_ERR(svn_rdump__get_dump_editor(editor, edit_baton, revision,
+ rb->stdout_stream, rb->extra_ra_session,
+ NULL, check_cancel, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Print progress information about the dump of REVISION.
+ Implements the `svn_ra_replay_revfinish_callback_t' interface. */
+static svn_error_t *
+replay_revend(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ /* No resources left to free. */
+ struct replay_baton *rb = replay_baton;
+
+ SVN_ERR(editor->close_edit(edit_baton, pool));
+
+ if (! rb->quiet)
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
+ revision));
+ return SVN_NO_ERROR;
+}
+
+#ifdef USE_EV2_IMPL
+/* Print dumpstream-formatted information about REVISION.
+ * Implements the `svn_ra_replay_revstart_callback_t' interface.
+ */
+static svn_error_t *
+replay_revstart_v2(svn_revnum_t revision,
+ void *replay_baton,
+ svn_editor_t **editor,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ struct replay_baton *rb = replay_baton;
+ apr_hash_t *normal_props;
+ svn_stringbuf_t *propstring;
+ svn_stream_t *stdout_stream;
+ svn_stream_t *revprop_stream;
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ /* Revision-number: 19 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_REVISION_NUMBER
+ ": %ld\n", revision));
+ SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
+ propstring = svn_stringbuf_create_ensure(0, pool);
+ revprop_stream = svn_stream_from_stringbuf(propstring, pool);
+ SVN_ERR(svn_hash_write2(normal_props, revprop_stream, "PROPS-END", pool));
+ SVN_ERR(svn_stream_close(revprop_stream));
+
+ /* Prop-content-length: 13 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n", propstring->len));
+
+ /* Content-length: 29 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n\n", propstring->len));
+
+ /* Property data. */
+ SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
+ &(propstring->len)));
+
+ SVN_ERR(svn_stream_puts(stdout_stream, "\n"));
+ SVN_ERR(svn_stream_close(stdout_stream));
+
+ SVN_ERR(svn_rdump__get_dump_editor_v2(editor, revision,
+ rb->stdout_stream,
+ rb->extra_ra_session,
+ NULL, check_cancel, NULL, pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Print progress information about the dump of REVISION.
+ Implements the `svn_ra_replay_revfinish_callback_t' interface. */
+static svn_error_t *
+replay_revend_v2(svn_revnum_t revision,
+ void *replay_baton,
+ svn_editor_t *editor,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ /* No resources left to free. */
+ struct replay_baton *rb = replay_baton;
+
+ SVN_ERR(svn_editor_complete(editor));
+
+ if (! rb->quiet)
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
+ revision));
+ return SVN_NO_ERROR;
+}
+#endif
+
+/* Initialize the RA layer, and set *CTX to a new client context baton
+ * allocated from POOL. Use CONFIG_DIR and pass USERNAME, PASSWORD,
+ * CONFIG_DIR and NO_AUTH_CACHE to initialize the authorization baton.
+ * CONFIG_OPTIONS (if not NULL) is a list of configuration overrides.
+ * REPOS_URL is used to fiddle with server-specific configuration
+ * options.
+ */
+static svn_error_t *
+init_client_context(svn_client_ctx_t **ctx_p,
+ svn_boolean_t non_interactive,
+ const char *username,
+ const char *password,
+ const char *config_dir,
+ const char *repos_url,
+ svn_boolean_t no_auth_cache,
+ svn_boolean_t trust_server_cert,
+ apr_array_header_t *config_options,
+ apr_pool_t *pool)
+{
+ svn_client_ctx_t *ctx = NULL;
+ svn_config_t *cfg_config, *cfg_servers;
+
+ SVN_ERR(svn_ra_initialize(pool));
+
+ SVN_ERR(svn_config_ensure(config_dir, pool));
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+
+ SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool));
+
+ if (config_options)
+ SVN_ERR(svn_cmdline__apply_config_options(ctx->config, config_options,
+ "svnrdump: ", "--config-option"));
+
+ cfg_config = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG);
+
+ /* ### FIXME: This is a hack to work around the fact that our dump
+ ### editor simply can't handle the way ra_serf violates the
+ ### editor v1 drive ordering requirements.
+ ###
+ ### We'll override both the global value and server-specific one
+ ### for the 'http-bulk-updates' and 'http-max-connections'
+ ### options in order to get ra_serf to try a bulk-update if the
+ ### server will allow it, or at least try to limit all its
+ ### auxiliary GETs/PROPFINDs to happening (well-ordered) on a
+ ### single server connection.
+ ###
+ ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
+ */
+ cfg_servers = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_SERVERS);
+ svn_config_set_bool(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
+ svn_config_set_int64(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
+ if (cfg_servers)
+ {
+ apr_status_t status;
+ apr_uri_t parsed_url;
+
+ status = apr_uri_parse(pool, repos_url, &parsed_url);
+ if (! status)
+ {
+ const char *server_group;
+
+ server_group = svn_config_find_group(cfg_servers, parsed_url.hostname,
+ SVN_CONFIG_SECTION_GROUPS, pool);
+ if (server_group)
+ {
+ svn_config_set_bool(cfg_servers, server_group,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
+ svn_config_set_int64(cfg_servers, server_group,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
+ }
+ }
+ }
+
+ /* Set up our cancellation support. */
+ ctx->cancel_func = check_cancel;
+
+ /* Default authentication providers for non-interactive use */
+ SVN_ERR(svn_cmdline_create_auth_baton(&(ctx->auth_baton), non_interactive,
+ username, password, config_dir,
+ no_auth_cache, trust_server_cert,
+ cfg_config, ctx->cancel_func,
+ ctx->cancel_baton, pool));
+ *ctx_p = ctx;
+ return SVN_NO_ERROR;
+}
+
+/* Print a revision record header for REVISION to STDOUT_STREAM. Use
+ * SESSION to contact the repository for revision properties and
+ * such.
+ */
+static svn_error_t *
+dump_revision_header(svn_ra_session_t *session,
+ svn_stream_t *stdout_stream,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ apr_hash_t *prophash;
+ svn_stringbuf_t *propstring;
+ svn_stream_t *propstream;
+
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_REVISION_NUMBER
+ ": %ld\n", revision));
+
+ prophash = apr_hash_make(pool);
+ propstring = svn_stringbuf_create_empty(pool);
+ SVN_ERR(svn_ra_rev_proplist(session, revision, &prophash, pool));
+
+ propstream = svn_stream_from_stringbuf(propstring, pool);
+ SVN_ERR(svn_hash_write2(prophash, propstream, "PROPS-END", pool));
+ SVN_ERR(svn_stream_close(propstream));
+
+ /* Property-content-length: 14; Content-length: 14 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n",
+ propstring->len));
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n\n",
+ propstring->len));
+ /* The properties */
+ SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
+ &(propstring->len)));
+ SVN_ERR(svn_stream_puts(stdout_stream, "\n"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dump_initial_full_revision(svn_ra_session_t *session,
+ svn_ra_session_t *extra_ra_session,
+ svn_stream_t *stdout_stream,
+ svn_revnum_t revision,
+ svn_boolean_t quiet,
+ apr_pool_t *pool)
+{
+ const svn_ra_reporter3_t *reporter;
+ void *report_baton;
+ const svn_delta_editor_t *dump_editor;
+ void *dump_baton;
+ const char *session_url, *source_relpath;
+
+ /* Determine whether we're dumping the repository root URL or some
+ child thereof. If we're dumping a subtree of the repository
+ rather than the root, we have to jump through some hoops to make
+ our update-driven dump generation work the way a replay-driven
+ one would.
+
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=4101
+ */
+ SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
+ SVN_ERR(svn_ra_get_path_relative_to_root(session, &source_relpath,
+ session_url, pool));
+
+ /* Start with a revision record header. */
+ SVN_ERR(dump_revision_header(session, stdout_stream, revision, pool));
+
+ /* Then, we'll drive the dump editor with what would look like a
+ full checkout of the repository as it looked in START_REVISION.
+ We do this by manufacturing a basic 'report' to the update
+ reporter, telling it that we have nothing to start with. The
+ delta between nothing and everything-at-REV is, effectively, a
+ full dump of REV. */
+ SVN_ERR(svn_rdump__get_dump_editor(&dump_editor, &dump_baton, revision,
+ stdout_stream, extra_ra_session,
+ source_relpath, check_cancel, NULL, pool));
+ SVN_ERR(svn_ra_do_update3(session, &reporter, &report_baton, revision,
+ "", svn_depth_infinity, FALSE, FALSE,
+ dump_editor, dump_baton, pool, pool));
+ SVN_ERR(reporter->set_path(report_baton, "", revision,
+ svn_depth_infinity, TRUE, NULL, pool));
+ SVN_ERR(reporter->finish_report(report_baton, pool));
+
+ /* All finished with START_REVISION! */
+ if (! quiet)
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
+ revision));
+
+ return SVN_NO_ERROR;
+}
+
+/* Replay revisions START_REVISION thru END_REVISION (inclusive) of
+ * the repository URL at which SESSION is rooted, using callbacks
+ * which generate Subversion repository dumpstreams describing the
+ * changes made in those revisions. If QUIET is set, don't generate
+ * progress messages.
+ */
+static svn_error_t *
+replay_revisions(svn_ra_session_t *session,
+ svn_ra_session_t *extra_ra_session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_boolean_t quiet,
+ svn_boolean_t incremental,
+ apr_pool_t *pool)
+{
+ struct replay_baton *replay_baton;
+ const char *uuid;
+ svn_stream_t *stdout_stream;
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ replay_baton = apr_pcalloc(pool, sizeof(*replay_baton));
+ replay_baton->stdout_stream = stdout_stream;
+ replay_baton->extra_ra_session = extra_ra_session;
+ replay_baton->quiet = quiet;
+
+ /* Write the magic header and UUID */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
+ SVN_REPOS_DUMPFILE_FORMAT_VERSION));
+ SVN_ERR(svn_ra_get_uuid2(session, &uuid, pool));
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
+
+ /* Fake revision 0 if necessary */
+ if (start_revision == 0)
+ {
+ SVN_ERR(dump_revision_header(session, stdout_stream,
+ start_revision, pool));
+
+ /* Revision 0 has no tree changes, so we're done. */
+ if (! quiet)
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
+ start_revision));
+ start_revision++;
+
+ /* If our first revision is 0, we can treat this as an
+ incremental dump. */
+ incremental = TRUE;
+ }
+
+ /* If what remains to be dumped is not going to be dumped
+ incrementally, then dump the first revision in full. */
+ if (!incremental)
+ {
+ SVN_ERR(dump_initial_full_revision(session, extra_ra_session,
+ stdout_stream, start_revision,
+ quiet, pool));
+ start_revision++;
+ }
+
+ /* If there are still revisions left to be dumped, do so. */
+ if (start_revision <= end_revision)
+ {
+#ifndef USE_EV2_IMPL
+ SVN_ERR(svn_ra_replay_range(session, start_revision, end_revision,
+ 0, TRUE, replay_revstart, replay_revend,
+ replay_baton, pool));
+#else
+ SVN_ERR(svn_ra__replay_range_ev2(session, start_revision, end_revision,
+ 0, TRUE, replay_revstart_v2,
+ replay_revend_v2, replay_baton,
+ NULL, NULL, NULL, NULL, pool));
+#endif
+ }
+
+ SVN_ERR(svn_stream_close(stdout_stream));
+ return SVN_NO_ERROR;
+}
+
+/* Read a dumpstream from stdin, and use it to feed a loader capable
+ * of transmitting that information to the repository located at URL
+ * (to which SESSION has been opened). AUX_SESSION is a second RA
+ * session opened to the same URL for performing auxiliary out-of-band
+ * operations.
+ */
+static svn_error_t *
+load_revisions(svn_ra_session_t *session,
+ svn_ra_session_t *aux_session,
+ const char *url,
+ svn_boolean_t quiet,
+ apr_pool_t *pool)
+{
+ apr_file_t *stdin_file;
+ svn_stream_t *stdin_stream;
+
+ apr_file_open_stdin(&stdin_file, pool);
+ stdin_stream = svn_stream_from_aprfile2(stdin_file, FALSE, pool);
+
+ SVN_ERR(svn_rdump__load_dumpstream(stdin_stream, session, aux_session,
+ quiet, check_cancel, NULL, pool));
+
+ SVN_ERR(svn_stream_close(stdin_stream));
+
+ return SVN_NO_ERROR;
+}
+
+/* Return a program name for this program, the basename of the path
+ * represented by PROGNAME if not NULL; use "svnrdump" otherwise.
+ */
+static const char *
+ensure_appname(const char *progname,
+ apr_pool_t *pool)
+{
+ if (!progname)
+ return "svnrdump";
+
+ return svn_dirent_basename(svn_dirent_internal_style(progname, pool), NULL);
+}
+
+/* Print a simple usage string. */
+static svn_error_t *
+usage(const char *progname,
+ apr_pool_t *pool)
+{
+ return svn_cmdline_fprintf(stderr, pool,
+ _("Type '%s help' for usage.\n"),
+ ensure_appname(progname, pool));
+}
+
+/* Print information about the version of this program and dependent
+ * modules.
+ */
+static svn_error_t *
+version(const char *progname,
+ svn_boolean_t quiet,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *version_footer =
+ svn_stringbuf_create(_("The following repository access (RA) modules "
+ "are available:\n\n"),
+ pool);
+
+ SVN_ERR(svn_ra_print_modules(version_footer, pool));
+ return svn_opt_print_help4(NULL, ensure_appname(progname, pool),
+ TRUE, quiet, FALSE, version_footer->data,
+ NULL, NULL, NULL, NULL, NULL, pool);
+}
+
+
+/* A statement macro, similar to @c SVN_ERR, but returns an integer.
+ * Evaluate @a expr. If it yields an error, handle that error and
+ * return @c EXIT_FAILURE.
+ */
+#define SVNRDUMP_ERR(expr) \
+ do \
+ { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ { \
+ svn_handle_error2(svn_err__temp, stderr, FALSE, "svnrdump: "); \
+ svn_error_clear(svn_err__temp); \
+ return EXIT_FAILURE; \
+ } \
+ } \
+ while (0)
+
+/* Handle the "dump" subcommand. Implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+dump_cmd(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ opt_baton_t *opt_baton = baton;
+ svn_ra_session_t *extra_ra_session;
+ const char *repos_root;
+
+ SVN_ERR(svn_client_open_ra_session2(&extra_ra_session,
+ opt_baton->url, NULL,
+ opt_baton->ctx, pool, pool));
+ SVN_ERR(svn_ra_get_repos_root2(extra_ra_session, &repos_root, pool));
+ SVN_ERR(svn_ra_reparent(extra_ra_session, repos_root, pool));
+
+ return replay_revisions(opt_baton->session, extra_ra_session,
+ opt_baton->start_revision.value.number,
+ opt_baton->end_revision.value.number,
+ opt_baton->quiet, opt_baton->incremental, pool);
+}
+
+/* Handle the "load" subcommand. Implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+load_cmd(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ opt_baton_t *opt_baton = baton;
+ svn_ra_session_t *aux_session;
+
+ SVN_ERR(svn_client_open_ra_session2(&aux_session, opt_baton->url, NULL,
+ opt_baton->ctx, pool, pool));
+ return load_revisions(opt_baton->session, aux_session, opt_baton->url,
+ opt_baton->quiet, pool);
+}
+
+/* Handle the "help" subcommand. Implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+help_cmd(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const char *header =
+ _("general usage: svnrdump SUBCOMMAND URL [-r LOWER[:UPPER]]\n"
+ "Type 'svnrdump help <subcommand>' for help on a specific subcommand.\n"
+ "Type 'svnrdump --version' to see the program version and RA modules.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ return svn_opt_print_help4(os, "svnrdump", FALSE, FALSE, FALSE, NULL,
+ header, svnrdump__cmd_table, svnrdump__options,
+ NULL, NULL, pool);
+}
+
+/* Examine the OPT_BATON's 'start_revision' and 'end_revision'
+ * members, making sure that they make sense (in general, and as
+ * applied to a repository whose current youngest revision is
+ * LATEST_REVISION).
+ */
+static svn_error_t *
+validate_and_resolve_revisions(opt_baton_t *opt_baton,
+ svn_revnum_t latest_revision,
+ apr_pool_t *pool)
+{
+ svn_revnum_t provided_start_rev = SVN_INVALID_REVNUM;
+
+ /* Ensure that the start revision is something we can handle. We
+ want a number >= 0. If unspecified, make it a number (r0) --
+ anything else is bogus. */
+ if (opt_baton->start_revision.kind == svn_opt_revision_number)
+ {
+ provided_start_rev = opt_baton->start_revision.value.number;
+ }
+ else if (opt_baton->start_revision.kind == svn_opt_revision_head)
+ {
+ opt_baton->start_revision.kind = svn_opt_revision_number;
+ opt_baton->start_revision.value.number = latest_revision;
+ }
+ else if (opt_baton->start_revision.kind == svn_opt_revision_unspecified)
+ {
+ opt_baton->start_revision.kind = svn_opt_revision_number;
+ opt_baton->start_revision.value.number = 0;
+ }
+
+ if (opt_baton->start_revision.kind != svn_opt_revision_number)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Unsupported revision specifier used; use "
+ "only integer values or 'HEAD'"));
+ }
+
+ if ((opt_baton->start_revision.value.number < 0) ||
+ (opt_baton->start_revision.value.number > latest_revision))
+ {
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Revision '%ld' does not exist"),
+ opt_baton->start_revision.value.number);
+ }
+
+ /* Ensure that the end revision is something we can handle. We want
+ a number <= the youngest, and > the start revision. If
+ unspecified, make it a number (start_revision + 1 if that was
+ specified, the youngest revision in the repository otherwise) --
+ anything else is bogus. */
+ if (opt_baton->end_revision.kind == svn_opt_revision_unspecified)
+ {
+ opt_baton->end_revision.kind = svn_opt_revision_number;
+ if (SVN_IS_VALID_REVNUM(provided_start_rev))
+ opt_baton->end_revision.value.number = provided_start_rev;
+ else
+ opt_baton->end_revision.value.number = latest_revision;
+ }
+ else if (opt_baton->end_revision.kind == svn_opt_revision_head)
+ {
+ opt_baton->end_revision.kind = svn_opt_revision_number;
+ opt_baton->end_revision.value.number = latest_revision;
+ }
+
+ if (opt_baton->end_revision.kind != svn_opt_revision_number)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Unsupported revision specifier used; use "
+ "only integer values or 'HEAD'"));
+ }
+
+ if ((opt_baton->end_revision.value.number < 0) ||
+ (opt_baton->end_revision.value.number > latest_revision))
+ {
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Revision '%ld' does not exist"),
+ opt_baton->end_revision.value.number);
+ }
+
+ /* Finally, make sure that the end revision is younger than the
+ start revision. We don't do "backwards" 'round here. */
+ if (opt_baton->end_revision.value.number <
+ opt_baton->start_revision.value.number)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("LOWER revision cannot be greater than "
+ "UPPER revision; consider reversing your "
+ "revision range"));
+ }
+ return SVN_NO_ERROR;
+}
+
+int
+main(int argc, const char **argv)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+ opt_baton_t *opt_baton;
+ svn_revnum_t latest_revision = SVN_INVALID_REVNUM;
+ apr_pool_t *pool = NULL;
+ const char *config_dir = NULL;
+ const char *username = NULL;
+ const char *password = NULL;
+ svn_boolean_t no_auth_cache = FALSE;
+ svn_boolean_t trust_server_cert = FALSE;
+ svn_boolean_t non_interactive = FALSE;
+ svn_boolean_t force_interactive = FALSE;
+ apr_array_header_t *config_options = NULL;
+ apr_getopt_t *os;
+ const char *first_arg;
+ apr_array_header_t *received_opts;
+ int i;
+
+ if (svn_cmdline_init ("svnrdump", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ opt_baton = apr_pcalloc(pool, sizeof(*opt_baton));
+ opt_baton->start_revision.kind = svn_opt_revision_unspecified;
+ opt_baton->end_revision.kind = svn_opt_revision_unspecified;
+ opt_baton->url = NULL;
+
+ SVNRDUMP_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
+
+ os->interleave = TRUE; /* Options and arguments can be interleaved */
+
+ /* Set up our cancellation support. */
+ apr_signal(SIGINT, signal_handler);
+#ifdef SIGBREAK
+ /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
+ apr_signal(SIGBREAK, signal_handler);
+#endif
+#ifdef SIGHUP
+ apr_signal(SIGHUP, signal_handler);
+#endif
+#ifdef SIGTERM
+ apr_signal(SIGTERM, signal_handler);
+#endif
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it, otherwise
+ * working with large files when compiled against an APR that doesn't have
+ * large file support will crash the program, which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ while (1)
+ {
+ int opt;
+ const char *opt_arg;
+ apr_status_t status = apr_getopt_long(os, svnrdump__options, &opt,
+ &opt_arg);
+
+ if (APR_STATUS_IS_EOF(status))
+ break;
+ if (status != APR_SUCCESS)
+ {
+ SVNRDUMP_ERR(usage(argv[0], pool));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Stash the option code in an array before parsing it. */
+ APR_ARRAY_PUSH(received_opts, int) = opt;
+
+ switch(opt)
+ {
+ case 'r':
+ {
+ /* Make sure we've not seen -r already. */
+ if (opt_baton->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Multiple revision arguments "
+ "encountered; try '-r N:M' instead "
+ "of '-r N -r M'"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
+ }
+ /* Parse the -r argument. */
+ if (svn_opt_parse_revision(&(opt_baton->start_revision),
+ &(opt_baton->end_revision),
+ opt_arg, pool) != 0)
+ {
+ const char *utf8_opt_arg;
+ err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
+ if (! err)
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error in revision "
+ "argument '%s'"), utf8_opt_arg);
+ return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
+ }
+ }
+ break;
+ case 'q':
+ opt_baton->quiet = TRUE;
+ break;
+ case opt_config_dir:
+ config_dir = opt_arg;
+ break;
+ case opt_version:
+ opt_baton->version = TRUE;
+ break;
+ case 'h':
+ opt_baton->help = TRUE;
+ break;
+ case opt_auth_username:
+ SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&username, opt_arg, pool));
+ break;
+ case opt_auth_password:
+ SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&password, opt_arg, pool));
+ break;
+ case opt_auth_nocache:
+ no_auth_cache = TRUE;
+ break;
+ case opt_non_interactive:
+ non_interactive = TRUE;
+ break;
+ case opt_force_interactive:
+ force_interactive = TRUE;
+ break;
+ case opt_incremental:
+ opt_baton->incremental = TRUE;
+ break;
+ case opt_trust_server_cert:
+ trust_server_cert = TRUE;
+ break;
+ case opt_config_option:
+ if (!config_options)
+ config_options =
+ apr_array_make(pool, 1,
+ sizeof(svn_cmdline__config_argument_t*));
+
+ SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
+ SVNRDUMP_ERR(svn_cmdline__parse_config_option(config_options,
+ opt_arg, pool));
+ }
+ }
+
+ /* The --non-interactive and --force-interactive options are mutually
+ * exclusive. */
+ if (non_interactive && force_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--non-interactive and --force-interactive "
+ "are mutually exclusive"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
+ }
+
+ if (opt_baton->help)
+ {
+ subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
+ "help");
+ }
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_baton->version)
+ {
+ /* Use the "help" subcommand to handle the "--version" option. */
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+ { "--version", help_cmd, {0}, "",
+ {opt_version, /* must accept its own option */
+ 'q', /* --quiet */
+ } };
+ subcommand = &pseudo_cmd;
+ }
+
+ else
+ {
+ SVNRDUMP_ERR(help_cmd(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ exit(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ first_arg = os->argv[os->ind++];
+ subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
+ first_arg);
+
+ if (subcommand == NULL)
+ {
+ const char *first_arg_utf8;
+ err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
+ svn_error_clear(
+ svn_cmdline_fprintf(stderr, pool,
+ _("Unknown subcommand: '%s'\n"),
+ first_arg_utf8));
+ SVNRDUMP_ERR(help_cmd(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
+ /* Check that the subcommand wasn't passed any inappropriate options. */
+ for (i = 0; i < received_opts->nelts; i++)
+ {
+ int opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ /* All commands implicitly accept --help, so just skip over this
+ when we see it. Note that we don't want to include this option
+ in their "accepted options" list because it would be awfully
+ redundant to display it in every commands' help text. */
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
+ {
+ const char *optstr;
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, svnrdump__options,
+ subcommand, pool);
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ SVN_INT_ERR(help_cmd(NULL, NULL, pool));
+ else
+ svn_error_clear(svn_cmdline_fprintf(
+ stderr, pool,
+ _("Subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svnrdump help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (subcommand && strcmp(subcommand->name, "--version") == 0)
+ {
+ SVNRDUMP_ERR(version(argv[0], opt_baton->quiet, pool));
+ svn_pool_destroy(pool);
+ exit(EXIT_SUCCESS);
+ }
+
+ if (subcommand && strcmp(subcommand->name, "help") == 0)
+ {
+ SVNRDUMP_ERR(help_cmd(os, opt_baton, pool));
+ svn_pool_destroy(pool);
+ exit(EXIT_SUCCESS);
+ }
+
+ /* --trust-server-cert can only be used with --non-interactive */
+ if (trust_server_cert && !non_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--trust-server-cert requires "
+ "--non-interactive"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
+ }
+
+ /* Expect one more non-option argument: the repository URL. */
+ if (os->ind != os->argc - 1)
+ {
+ SVNRDUMP_ERR(usage(argv[0], pool));
+ svn_pool_destroy(pool);
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ const char *repos_url;
+
+ SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&repos_url,
+ os->argv[os->ind], pool));
+ if (! svn_path_is_url(repos_url))
+ {
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
+ "Target '%s' is not a URL",
+ repos_url);
+ SVNRDUMP_ERR(err);
+ svn_pool_destroy(pool);
+ exit(EXIT_FAILURE);
+ }
+ opt_baton->url = svn_uri_canonicalize(repos_url, pool);
+ }
+
+ if (strcmp(subcommand->name, "load") == 0)
+ {
+ /*
+ * By default (no --*-interactive options given), the 'load' subcommand
+ * is interactive unless username and password were provided on the
+ * command line. This allows prompting for auth creds to work without
+ * requiring users to remember to use --force-interactive.
+ * See issue #3913, "svnrdump load is not working in interactive mode".
+ */
+ if (!non_interactive && !force_interactive)
+ force_interactive = (username == NULL || password == NULL);
+ }
+
+ non_interactive = !svn_cmdline__be_interactive(non_interactive,
+ force_interactive);
+
+ SVNRDUMP_ERR(init_client_context(&(opt_baton->ctx),
+ non_interactive,
+ username,
+ password,
+ config_dir,
+ opt_baton->url,
+ no_auth_cache,
+ trust_server_cert,
+ config_options,
+ pool));
+
+ err = svn_client_open_ra_session2(&(opt_baton->session),
+ opt_baton->url, NULL,
+ opt_baton->ctx, pool, pool);
+
+ /* Have sane opt_baton->start_revision and end_revision defaults if
+ unspecified. */
+ if (!err)
+ err = svn_ra_get_latest_revnum(opt_baton->session, &latest_revision, pool);
+
+ /* Make sure any provided revisions make sense. */
+ if (!err)
+ err = validate_and_resolve_revisions(opt_baton, latest_revision, pool);
+
+ /* Dispatch the subcommand */
+ if (!err)
+ err = (*subcommand->cmd_func)(os, opt_baton, pool);
+
+ if (err && err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Authentication failed and interactive"
+ " prompting is disabled; see the"
+ " --force-interactive option"));
+ }
+
+ SVNRDUMP_ERR(err);
+
+ svn_pool_destroy(pool);
+
+ return EXIT_SUCCESS;
+}
diff --git a/contrib/subversion/subversion/svnrdump/svnrdump.h b/contrib/subversion/subversion/svnrdump/svnrdump.h
new file mode 100644
index 0000000..2a81014
--- /dev/null
+++ b/contrib/subversion/subversion/svnrdump/svnrdump.h
@@ -0,0 +1,129 @@
+/*
+ * svnrdump.h: Internal header file for svnrdump.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#ifndef SVNRDUMP_H
+#define SVNRDUMP_H
+
+/*** Includes. ***/
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_hash.h"
+#include "svn_delta.h"
+#include "svn_ra.h"
+
+#include "private/svn_editor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/**
+ * Get a dump editor @a editor along with a @a edit_baton allocated in
+ * @a pool. The editor will write output to @a stream.
+ *
+ * @a update_anchor_relpath is the repository relative path of the
+ * anchor of the update-style drive which will happen on @a *editor;
+ * if a replay-style drive will instead be used, it should be passed
+ * as @c NULL.
+ *
+ * Use @a cancel_func and @a cancel_baton to check for user
+ * cancellation of the operation (for timely-but-safe termination).
+ */
+svn_error_t *
+svn_rdump__get_dump_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_ra_session_t *ra_session,
+ const char *update_anchor_relpath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+/* Same as above, only returns an Ev2 editor. */
+svn_error_t *
+svn_rdump__get_dump_editor_v2(svn_editor_t **editor,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_ra_session_t *ra_session,
+ const char *edit_root_relpath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *result_pool);
+
+
+/**
+ * Load the dumpstream carried in @a stream to the location described
+ * by @a session. Use @a aux_session (which is opened to the same URL
+ * as @a session) for any secondary, out-of-band RA communications
+ * required. If @a quiet is set, suppress notifications. Use @a pool
+ * for all memory allocations. Use @a cancel_func and @a cancel_baton
+ * to check for user cancellation of the operation (for
+ * timely-but-safe termination).
+ */
+svn_error_t *
+svn_rdump__load_dumpstream(svn_stream_t *stream,
+ svn_ra_session_t *session,
+ svn_ra_session_t *aux_session,
+ svn_boolean_t quiet,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool);
+
+
+/* Normalize the line ending style of the values of properties in PROPS
+ * that "need translation" (according to svn_prop_needs_translation(),
+ * currently all svn:* props) so that they contain only LF (\n) line endings.
+ *
+ * Put the normalized props into NORMAL_PROPS, allocated in RESULT_POOL.
+ *
+ * Note: this function does not do a deep copy; it is expected that PROPS has
+ * a longer lifetime than NORMAL_PROPS.
+ */
+svn_error_t *
+svn_rdump__normalize_props(apr_hash_t **normal_props,
+ apr_hash_t *props,
+ apr_pool_t *result_pool);
+
+/* Normalize the line ending style of a single property that "needs
+ * translation" (according to svn_prop_needs_translation(),
+ * currently all svn:* props) so that they contain only LF (\n) line endings.
+ * "\r" characters found mid-line are replaced with "\n".
+ * "\r\n" sequences are replaced with "\n"
+ *
+ * NAME is used to check that VALUE should be normalized, and if this is the
+ * case, VALUE is then normalized, allocated from RESULT_POOL
+ */
+svn_error_t *
+svn_rdump__normalize_prop(const char *name,
+ const svn_string_t **value,
+ apr_pool_t *result_pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVNRDUMP_H */
diff --git a/contrib/subversion/subversion/svnrdump/util.c b/contrib/subversion/subversion/svnrdump/util.c
new file mode 100644
index 0000000..91cefb3
--- /dev/null
+++ b/contrib/subversion/subversion/svnrdump/util.c
@@ -0,0 +1,73 @@
+/*
+ * util.c: A few utility functions.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_string.h"
+#include "svn_props.h"
+#include "svn_subst.h"
+
+#include "svnrdump.h"
+
+
+svn_error_t *
+svn_rdump__normalize_prop(const char *name,
+ const svn_string_t **value,
+ apr_pool_t *result_pool)
+{
+ if (svn_prop_needs_translation(name))
+ {
+ const char *cstring;
+
+ SVN_ERR(svn_subst_translate_cstring2((*value)->data, &cstring,
+ "\n", TRUE,
+ NULL, FALSE,
+ result_pool));
+
+ *value = svn_string_create(cstring, result_pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_rdump__normalize_props(apr_hash_t **normal_props,
+ apr_hash_t *props,
+ apr_pool_t *result_pool)
+{
+ apr_hash_index_t *hi;
+
+ *normal_props = apr_hash_make(result_pool);
+
+ for (hi = apr_hash_first(result_pool, props); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *key = svn__apr_hash_index_key(hi);
+ const svn_string_t *value = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(svn_rdump__normalize_prop(key, &value,
+ result_pool));
+
+ svn_hash_sets(*normal_props, key, value);
+ }
+ return SVN_NO_ERROR;
+}
diff --git a/contrib/subversion/subversion/svnserve/cyrus_auth.c b/contrib/subversion/subversion/svnserve/cyrus_auth.c
new file mode 100644
index 0000000..2d75047
--- /dev/null
+++ b/contrib/subversion/subversion/svnserve/cyrus_auth.c
@@ -0,0 +1,377 @@
+/*
+ * sasl_auth.c : Functions for SASL-based authentication
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_private_config.h"
+#ifdef SVN_HAVE_SASL
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_ra_svn.h"
+#include "svn_base64.h"
+
+#include "private/svn_atomic.h"
+#include "private/ra_svn_sasl.h"
+#include "private/svn_ra_svn_private.h"
+
+#include "server.h"
+
+/* SASL calls this function before doing anything with a username, which gives
+ us an opportunity to do some sanity-checking. If the username contains
+ an '@', SASL interprets the part following the '@' as the name of the
+ authentication realm, and worst of all, this realm overrides the one that
+ we pass to sasl_server_new(). If we didn't check this, a user that could
+ successfully authenticate in one realm would be able to authenticate
+ in any other realm, simply by appending '@realm' to his username.
+
+ Note that the value returned in *OUT does not need to be
+ '\0'-terminated; we just need to set *OUT_LEN correctly.
+*/
+static int canonicalize_username(sasl_conn_t *conn,
+ void *context, /* not used */
+ const char *in, /* the username */
+ unsigned inlen, /* its length */
+ unsigned flags, /* not used */
+ const char *user_realm,
+ char *out, /* the output buffer */
+ unsigned out_max, unsigned *out_len)
+{
+ size_t realm_len = strlen(user_realm);
+ char *pos;
+
+ *out_len = inlen;
+
+ /* If the username contains an '@', the part after the '@' is the realm
+ that the user wants to authenticate in. */
+ pos = memchr(in, '@', inlen);
+ if (pos)
+ {
+ /* The only valid realm is user_realm (i.e. the repository's realm).
+ If the user gave us another realm, complain. */
+ if (strncmp(pos+1, user_realm, inlen-(pos-in+1)) != 0)
+ return SASL_BADPROT;
+ }
+ else
+ *out_len += realm_len + 1;
+
+ /* First, check that the output buffer is large enough. */
+ if (*out_len > out_max)
+ return SASL_BADPROT;
+
+ /* Copy the username part. */
+ strncpy(out, in, inlen);
+
+ /* If necessary, copy the realm part. */
+ if (!pos)
+ {
+ out[inlen] = '@';
+ strncpy(&out[inlen+1], user_realm, realm_len);
+ }
+
+ return SASL_OK;
+}
+
+static sasl_callback_t callbacks[] =
+{
+ { SASL_CB_CANON_USER, (int (*)(void))canonicalize_username, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+
+static svn_error_t *initialize(void *baton, apr_pool_t *pool)
+{
+ int result;
+ SVN_ERR(svn_ra_svn__sasl_common_init(pool));
+
+ /* The second parameter tells SASL to look for a configuration file
+ named subversion.conf. */
+ result = sasl_server_init(callbacks, SVN_RA_SVN_SASL_NAME);
+ if (result != SASL_OK)
+ {
+ svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ sasl_errstring(result, NULL, NULL));
+ return svn_error_quick_wrap(err,
+ _("Could not initialize the SASL library"));
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *cyrus_init(apr_pool_t *pool)
+{
+ SVN_ERR(svn_atomic__init_once(&svn_ra_svn__sasl_status,
+ initialize, NULL, pool));
+ return SVN_NO_ERROR;
+}
+
+/* Tell the client the authentication failed. This is only used during
+ the authentication exchange (i.e. inside try_auth()). */
+static svn_error_t *
+fail_auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
+{
+ const char *msg = sasl_errdetail(sasl_ctx);
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", msg));
+ return svn_ra_svn__flush(conn, pool);
+}
+
+/* Like svn_ra_svn_write_cmd_failure, but also clears the given error
+ and sets it to SVN_NO_ERROR. */
+static svn_error_t *
+write_failure(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_error_t **err_p)
+{
+ svn_error_t *write_err = svn_ra_svn__write_cmd_failure(conn, pool, *err_p);
+ svn_error_clear(*err_p);
+ *err_p = SVN_NO_ERROR;
+ return write_err;
+}
+
+/* Used if we run into a SASL error outside try_auth(). */
+static svn_error_t *
+fail_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, sasl_conn_t *sasl_ctx)
+{
+ svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ sasl_errdetail(sasl_ctx));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn__flush(conn, pool);
+}
+
+static svn_error_t *try_auth(svn_ra_svn_conn_t *conn,
+ sasl_conn_t *sasl_ctx,
+ apr_pool_t *pool,
+ server_baton_t *b,
+ svn_boolean_t *success)
+{
+ const char *out, *mech;
+ const svn_string_t *arg = NULL, *in;
+ unsigned int outlen;
+ int result;
+ svn_boolean_t use_base64;
+
+ *success = FALSE;
+
+ /* Read the client's chosen mech and the initial token. */
+ SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?s)", &mech, &in));
+
+ if (strcmp(mech, "EXTERNAL") == 0 && !in)
+ in = svn_string_create(b->tunnel_user, pool);
+ else if (in)
+ in = svn_base64_decode_string(in, pool);
+
+ /* For CRAM-MD5, we don't base64-encode stuff. */
+ use_base64 = (strcmp(mech, "CRAM-MD5") != 0);
+
+ result = sasl_server_start(sasl_ctx, mech,
+ in ? in->data : NULL,
+ in ? in->len : 0, &out, &outlen);
+
+ if (result != SASL_OK && result != SASL_CONTINUE)
+ return fail_auth(conn, pool, sasl_ctx);
+
+ while (result == SASL_CONTINUE)
+ {
+ svn_ra_svn_item_t *item;
+
+ arg = svn_string_ncreate(out, outlen, pool);
+ /* Encode what we send to the client. */
+ if (use_base64)
+ arg = svn_base64_encode_string2(arg, TRUE, pool);
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(s)", "step", arg));
+
+ /* Read and decode the client response. */
+ SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
+ if (item->kind != SVN_RA_SVN_STRING)
+ return SVN_NO_ERROR;
+
+ in = item->u.string;
+ if (use_base64)
+ in = svn_base64_decode_string(in, pool);
+ result = sasl_server_step(sasl_ctx, in->data, in->len, &out, &outlen);
+ }
+
+ if (result != SASL_OK)
+ return fail_auth(conn, pool, sasl_ctx);
+
+ /* Send our last response, if necessary. */
+ if (outlen)
+ arg = svn_base64_encode_string2(svn_string_ncreate(out, outlen, pool), TRUE,
+ pool);
+ else
+ arg = NULL;
+
+ *success = TRUE;
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(?s)", "success", arg));
+
+ return SVN_NO_ERROR;
+}
+
+static apr_status_t sasl_dispose_cb(void *data)
+{
+ sasl_conn_t *sasl_ctx = (sasl_conn_t*) data;
+ sasl_dispose(&sasl_ctx);
+ return APR_SUCCESS;
+}
+
+svn_error_t *cyrus_auth_request(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ server_baton_t *b,
+ enum access_type required,
+ svn_boolean_t needs_username)
+{
+ sasl_conn_t *sasl_ctx;
+ apr_pool_t *subpool;
+ apr_status_t apr_err;
+ const char *localaddrport = NULL, *remoteaddrport = NULL;
+ const char *mechlist, *val;
+ char hostname[APRMAXHOSTLEN + 1];
+ sasl_security_properties_t secprops;
+ svn_boolean_t success, no_anonymous;
+ int mech_count, result = SASL_OK;
+
+ SVN_ERR(svn_ra_svn__get_addresses(&localaddrport, &remoteaddrport,
+ conn, pool));
+ apr_err = apr_gethostname(hostname, sizeof(hostname), pool);
+ if (apr_err)
+ {
+ svn_error_t *err = svn_error_wrap_apr(apr_err, _("Can't get hostname"));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn__flush(conn, pool);
+ }
+
+ /* Create a SASL context. SASL_SUCCESS_DATA tells SASL that the protocol
+ supports sending data along with the final "success" message. */
+ result = sasl_server_new(SVN_RA_SVN_SASL_NAME,
+ hostname, b->realm,
+ localaddrport, remoteaddrport,
+ NULL, SASL_SUCCESS_DATA,
+ &sasl_ctx);
+ if (result != SASL_OK)
+ {
+ svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ sasl_errstring(result, NULL, NULL));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn__flush(conn, pool);
+ }
+
+ /* Make sure the context is always destroyed. */
+ apr_pool_cleanup_register(b->pool, sasl_ctx, sasl_dispose_cb,
+ apr_pool_cleanup_null);
+
+ /* Initialize security properties. */
+ svn_ra_svn__default_secprops(&secprops);
+
+ /* Don't allow ANONYMOUS if a username is required. */
+ no_anonymous = needs_username || get_access(b, UNAUTHENTICATED) < required;
+ if (no_anonymous)
+ secprops.security_flags |= SASL_SEC_NOANONYMOUS;
+
+ svn_config_get(b->cfg, &val,
+ SVN_CONFIG_SECTION_SASL,
+ SVN_CONFIG_OPTION_MIN_SSF,
+ "0");
+ SVN_ERR(svn_cstring_atoui(&secprops.min_ssf, val));
+
+ svn_config_get(b->cfg, &val,
+ SVN_CONFIG_SECTION_SASL,
+ SVN_CONFIG_OPTION_MAX_SSF,
+ "256");
+ SVN_ERR(svn_cstring_atoui(&secprops.max_ssf, val));
+
+ /* Set security properties. */
+ result = sasl_setprop(sasl_ctx, SASL_SEC_PROPS, &secprops);
+ if (result != SASL_OK)
+ return fail_cmd(conn, pool, sasl_ctx);
+
+ /* SASL needs to know if we are externally authenticated. */
+ if (b->tunnel_user)
+ result = sasl_setprop(sasl_ctx, SASL_AUTH_EXTERNAL, b->tunnel_user);
+ if (result != SASL_OK)
+ return fail_cmd(conn, pool, sasl_ctx);
+
+ /* Get the list of mechanisms. */
+ result = sasl_listmech(sasl_ctx, NULL, NULL, " ", NULL,
+ &mechlist, NULL, &mech_count);
+
+ if (result != SASL_OK)
+ return fail_cmd(conn, pool, sasl_ctx);
+
+ if (mech_count == 0)
+ {
+ svn_error_t *err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Could not obtain the list"
+ " of SASL mechanisms"));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn__flush(conn, pool);
+ }
+
+ /* Send the list of mechanisms and the realm to the client. */
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(w)c",
+ mechlist, b->realm));
+
+ /* The main authentication loop. */
+ subpool = svn_pool_create(pool);
+ do
+ {
+ svn_pool_clear(subpool);
+ SVN_ERR(try_auth(conn, sasl_ctx, subpool, b, &success));
+ }
+ while (!success);
+ svn_pool_destroy(subpool);
+
+ SVN_ERR(svn_ra_svn__enable_sasl_encryption(conn, sasl_ctx, pool));
+
+ if (no_anonymous)
+ {
+ char *p;
+ const void *user;
+
+ /* Get the authenticated username. */
+ result = sasl_getprop(sasl_ctx, SASL_USERNAME, &user);
+
+ if (result != SASL_OK)
+ return fail_cmd(conn, pool, sasl_ctx);
+
+ if ((p = strchr(user, '@')) != NULL)
+ {
+ /* Drop the realm part. */
+ b->user = apr_pstrndup(b->pool, user, p - (const char *)user);
+ }
+ else
+ {
+ svn_error_t *err;
+ err = svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Couldn't obtain the authenticated"
+ " username"));
+ SVN_ERR(write_failure(conn, pool, &err));
+ return svn_ra_svn__flush(conn, pool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#endif /* SVN_HAVE_SASL */
diff --git a/contrib/subversion/subversion/svnserve/log-escape.c b/contrib/subversion/subversion/svnserve/log-escape.c
new file mode 100644
index 0000000..79f5312
--- /dev/null
+++ b/contrib/subversion/subversion/svnserve/log-escape.c
@@ -0,0 +1,143 @@
+/*
+ * log-escape.c : Functions for escaping log items
+ * copied from Apache httpd
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr.h>
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include "server.h"
+#include "svn_ctype.h"
+
+/* copied from httpd-2.2.4/server/util.c */
+/* c2x takes an unsigned, and expects the caller has guaranteed that
+ * 0 <= what < 256... which usually means that you have to cast to
+ * unsigned char first, because (unsigned)(char)(x) first goes through
+ * signed extension to an int before the unsigned cast.
+ *
+ * The reason for this assumption is to assist gcc code generation --
+ * the unsigned char -> unsigned extension is already done earlier in
+ * both uses of this code, so there's no need to waste time doing it
+ * again.
+ */
+static const char c2x_table[] = "0123456789abcdef";
+
+/* copied from httpd-2.2.4/server/util.c */
+static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix,
+ unsigned char *where)
+{
+#if APR_CHARSET_EBCDIC
+ what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what);
+#endif /*APR_CHARSET_EBCDIC*/
+ *where++ = prefix;
+ *where++ = c2x_table[what >> 4];
+ *where++ = c2x_table[what & 0xf];
+ return where;
+}
+
+/* copied from httpd-2.2.4/server/util.c */
+apr_size_t escape_errorlog_item(char *dest, const char *source,
+ apr_size_t buflen)
+{
+ unsigned char *d, *ep;
+ const unsigned char *s;
+
+ if (!source || !buflen) { /* be safe */
+ return 0;
+ }
+
+ d = (unsigned char *)dest;
+ s = (const unsigned char *)source;
+ ep = d + buflen - 1;
+
+ for (; d < ep && *s; ++s) {
+
+ /* httpd-2.2.4/server/util.c has this:
+ if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) {
+ which does this same check with a fast lookup table. Well,
+ mostly the same; we don't escape quotes, as that does.
+ */
+ if (*s && ( !svn_ctype_isprint(*s)
+ || *s == '\\'
+ || svn_ctype_iscntrl(*s))) {
+ *d++ = '\\';
+ if (d >= ep) {
+ --d;
+ break;
+ }
+
+ switch(*s) {
+ case '\b':
+ *d++ = 'b';
+ break;
+ case '\n':
+ *d++ = 'n';
+ break;
+ case '\r':
+ *d++ = 'r';
+ break;
+ case '\t':
+ *d++ = 't';
+ break;
+ case '\v':
+ *d++ = 'v';
+ break;
+ case '\\':
+ *d++ = *s;
+ break;
+ case '"': /* no need for this in error log */
+ d[-1] = *s;
+ break;
+ default:
+ if (d >= ep - 2) {
+ ep = --d; /* break the for loop as well */
+ break;
+ }
+ c2x(*s, 'x', d);
+ d += 3;
+ }
+ }
+ else {
+ *d++ = *s;
+ }
+ }
+ *d = '\0';
+
+ return (d - (unsigned char *)dest);
+}
diff --git a/contrib/subversion/subversion/svnserve/serve.c b/contrib/subversion/subversion/svnserve/serve.c
new file mode 100644
index 0000000..9e49bdd
--- /dev/null
+++ b/contrib/subversion/subversion/svnserve/serve.c
@@ -0,0 +1,3678 @@
+/*
+ * serve.c : Functions for serving the Subversion protocol
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+
+#include <limits.h> /* for UINT_MAX */
+#include <stdarg.h>
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include "svn_compat.h"
+#include "svn_private_config.h" /* For SVN_PATH_LOCAL_SEPARATOR */
+#include "svn_hash.h"
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */
+#include "svn_ra_svn.h"
+#include "svn_repos.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_time.h"
+#include "svn_config.h"
+#include "svn_props.h"
+#include "svn_mergeinfo.h"
+#include "svn_user.h"
+
+#include "private/svn_log.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_ra_svn_private.h"
+#include "private/svn_fspath.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* For getpid() */
+#endif
+
+#include "server.h"
+
+typedef struct commit_callback_baton_t {
+ apr_pool_t *pool;
+ svn_revnum_t *new_rev;
+ const char **date;
+ const char **author;
+ const char **post_commit_err;
+} commit_callback_baton_t;
+
+typedef struct report_driver_baton_t {
+ server_baton_t *sb;
+ const char *repos_url; /* Decoded repository URL. */
+ void *report_baton;
+ svn_error_t *err;
+ /* so update() can distinguish checkout from update in logging */
+ int entry_counter;
+ svn_boolean_t only_empty_entries;
+ /* for diff() logging */
+ svn_revnum_t *from_rev;
+} report_driver_baton_t;
+
+typedef struct log_baton_t {
+ const char *fs_path;
+ svn_ra_svn_conn_t *conn;
+ int stack_depth;
+} log_baton_t;
+
+typedef struct file_revs_baton_t {
+ svn_ra_svn_conn_t *conn;
+ apr_pool_t *pool; /* Pool provided in the handler call. */
+} file_revs_baton_t;
+
+typedef struct fs_warning_baton_t {
+ server_baton_t *server;
+ svn_ra_svn_conn_t *conn;
+ apr_pool_t *pool;
+} fs_warning_baton_t;
+
+typedef struct authz_baton_t {
+ server_baton_t *server;
+ svn_ra_svn_conn_t *conn;
+} authz_baton_t;
+
+/* Write LEN bytes of ERRSTR to LOG_FILE with svn_io_file_write(). */
+static svn_error_t *
+log_write(apr_file_t *log_file, const char *errstr, apr_size_t len,
+ apr_pool_t *pool)
+{
+ return svn_io_file_write(log_file, errstr, &len, pool);
+}
+
+void
+log_error(svn_error_t *err, apr_file_t *log_file, const char *remote_host,
+ const char *user, const char *repos, apr_pool_t *pool)
+{
+ const char *timestr, *continuation;
+ char errbuf[256];
+ /* 8192 from MAX_STRING_LEN in from httpd-2.2.4/include/httpd.h */
+ char errstr[8192];
+
+ if (err == SVN_NO_ERROR)
+ return;
+
+ if (log_file == NULL)
+ return;
+
+ timestr = svn_time_to_cstring(apr_time_now(), pool);
+ remote_host = (remote_host ? remote_host : "-");
+ user = (user ? user : "-");
+ repos = (repos ? repos : "-");
+
+ continuation = "";
+ while (err != NULL)
+ {
+ const char *message = svn_err_best_message(err, errbuf, sizeof(errbuf));
+ /* based on httpd-2.2.4/server/log.c:log_error_core */
+ apr_size_t len = apr_snprintf(errstr, sizeof(errstr),
+ "%" APR_PID_T_FMT
+ " %s %s %s %s ERR%s %s %ld %d ",
+ getpid(), timestr, remote_host, user,
+ repos, continuation,
+ err->file ? err->file : "-", err->line,
+ err->apr_err);
+
+ len += escape_errorlog_item(errstr + len, message,
+ sizeof(errstr) - len);
+ /* Truncate for the terminator (as apr_snprintf does) */
+ if (len > sizeof(errstr) - sizeof(APR_EOL_STR)) {
+ len = sizeof(errstr) - sizeof(APR_EOL_STR);
+ }
+ strcpy(errstr + len, APR_EOL_STR);
+ len += strlen(APR_EOL_STR);
+ svn_error_clear(log_write(log_file, errstr, len, pool));
+
+ continuation = "-";
+ err = err->child;
+ }
+}
+
+/* Call log_error with log_file, remote_host, user, and repos
+ arguments from SERVER and CONN. */
+static void
+log_server_error(svn_error_t *err, server_baton_t *server,
+ svn_ra_svn_conn_t *conn, apr_pool_t *pool)
+{
+ log_error(err, server->log_file, svn_ra_svn_conn_remote_host(conn),
+ server->user, server->repos_name, pool);
+}
+
+/* svn_error_create() a new error, log_server_error() it, and
+ return it. */
+static svn_error_t *
+error_create_and_log(apr_status_t apr_err, svn_error_t *child,
+ const char *message, server_baton_t *server,
+ svn_ra_svn_conn_t *conn, apr_pool_t *pool)
+{
+ svn_error_t *err = svn_error_create(apr_err, child, message);
+ log_server_error(err, server, conn, pool);
+ return err;
+}
+
+/* Log a failure ERR, transmit ERR back to the client (as part of a
+ "failure" notification), consume ERR, and flush the connection. */
+static svn_error_t *
+log_fail_and_flush(svn_error_t *err, server_baton_t *server,
+ svn_ra_svn_conn_t *conn, apr_pool_t *pool)
+{
+ svn_error_t *io_err;
+
+ log_server_error(err, server, conn, pool);
+ io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
+ svn_error_clear(err);
+ SVN_ERR(io_err);
+ return svn_ra_svn__flush(conn, pool);
+}
+
+/* Log a client command. */
+static svn_error_t *log_command(server_baton_t *b,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char *fmt, ...)
+{
+ const char *remote_host, *timestr, *log, *line;
+ va_list ap;
+ apr_size_t nbytes;
+
+ if (b->log_file == NULL)
+ return SVN_NO_ERROR;
+
+ remote_host = svn_ra_svn_conn_remote_host(conn);
+ timestr = svn_time_to_cstring(apr_time_now(), pool);
+
+ va_start(ap, fmt);
+ log = apr_pvsprintf(pool, fmt, ap);
+ va_end(ap);
+
+ line = apr_psprintf(pool, "%" APR_PID_T_FMT
+ " %s %s %s %s %s" APR_EOL_STR,
+ getpid(), timestr,
+ (remote_host ? remote_host : "-"),
+ (b->user ? b->user : "-"), b->repos_name, log);
+ nbytes = strlen(line);
+
+ return log_write(b->log_file, line, nbytes, pool);
+}
+
+/* Log an authz failure */
+static svn_error_t *
+log_authz_denied(const char *path,
+ svn_repos_authz_access_t required,
+ server_baton_t *b,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ const char *timestr, *remote_host, *line;
+
+ if (b->log_file == NULL)
+ return SVN_NO_ERROR;
+
+ if (!b->user)
+ return SVN_NO_ERROR;
+
+ timestr = svn_time_to_cstring(apr_time_now(), pool);
+ remote_host = svn_ra_svn_conn_remote_host(conn);
+
+ line = apr_psprintf(pool, "%" APR_PID_T_FMT
+ " %s %s %s %s Authorization Failed %s%s %s" APR_EOL_STR,
+ getpid(), timestr,
+ (remote_host ? remote_host : "-"),
+ (b->user ? b->user : "-"),
+ b->repos_name,
+ (required & svn_authz_recursive ? "recursive " : ""),
+ (required & svn_authz_write ? "write" : "read"),
+ (path && path[0] ? path : "/"));
+
+ return log_write(b->log_file, line, strlen(line), pool);
+}
+
+
+svn_error_t *load_pwdb_config(server_baton_t *server,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ const char *pwdb_path;
+ svn_error_t *err;
+
+ svn_config_get(server->cfg, &pwdb_path, SVN_CONFIG_SECTION_GENERAL,
+ SVN_CONFIG_OPTION_PASSWORD_DB, NULL);
+
+ server->pwdb = NULL;
+ if (pwdb_path)
+ {
+ pwdb_path = svn_dirent_internal_style(pwdb_path, pool);
+ pwdb_path = svn_dirent_join(server->base, pwdb_path, pool);
+
+ err = svn_config_read3(&server->pwdb, pwdb_path, TRUE,
+ FALSE, FALSE, pool);
+ if (err)
+ {
+ log_server_error(err, server, conn, pool);
+
+ /* Because it may be possible to read the pwdb file with some
+ access methods and not others, ignore errors reading the pwdb
+ file and just don't present password authentication as an
+ option. Also, some authentications (e.g. --tunnel) can
+ proceed without it anyway.
+
+ ### Not entirely sure why SVN_ERR_BAD_FILENAME is checked
+ ### for here. That seems to have been introduced in r856914,
+ ### and only in r870942 was the APR_EACCES check introduced. */
+ if (err->apr_err != SVN_ERR_BAD_FILENAME
+ && ! APR_STATUS_IS_EACCES(err->apr_err))
+ {
+ /* Now that we've logged the error, clear it and return a
+ * nice, generic error to the user:
+ * http://subversion.tigris.org/issues/show_bug.cgi?id=2271 */
+ svn_error_clear(err);
+ return svn_error_create(SVN_ERR_AUTHN_FAILED, NULL, NULL);
+ }
+ else
+ /* Ignore SVN_ERR_BAD_FILENAME and APR_EACCES and proceed. */
+ svn_error_clear(err);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Canonicalize *ACCESS_FILE based on the type of argument. Results are
+ * placed in *ACCESS_FILE. SERVER baton is used to convert relative paths to
+ * absolute paths rooted at the server root. REPOS_ROOT is used to calculate
+ * an absolute URL for repos-relative URLs. */
+static svn_error_t *
+canonicalize_access_file(const char **access_file, server_baton_t *server,
+ const char *repos_root, apr_pool_t *pool)
+{
+ if (svn_path_is_url(*access_file))
+ {
+ *access_file = svn_uri_canonicalize(*access_file, pool);
+ }
+ else if (svn_path_is_repos_relative_url(*access_file))
+ {
+ const char *repos_root_url;
+
+ SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_root_url, repos_root,
+ pool));
+ SVN_ERR(svn_path_resolve_repos_relative_url(access_file, *access_file,
+ repos_root_url, pool));
+ *access_file = svn_uri_canonicalize(*access_file, pool);
+ }
+ else
+ {
+ *access_file = svn_dirent_internal_style(*access_file, pool);
+ *access_file = svn_dirent_join(server->base, *access_file, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *load_authz_config(server_baton_t *server,
+ svn_ra_svn_conn_t *conn,
+ const char *repos_root,
+ apr_pool_t *pool)
+{
+ const char *authzdb_path;
+ const char *groupsdb_path;
+ svn_error_t *err;
+
+ /* Read authz configuration. */
+ svn_config_get(server->cfg, &authzdb_path, SVN_CONFIG_SECTION_GENERAL,
+ SVN_CONFIG_OPTION_AUTHZ_DB, NULL);
+
+ svn_config_get(server->cfg, &groupsdb_path, SVN_CONFIG_SECTION_GENERAL,
+ SVN_CONFIG_OPTION_GROUPS_DB, NULL);
+
+ if (authzdb_path)
+ {
+ const char *case_force_val;
+
+ /* Canonicalize and add the base onto the authzdb_path (if needed). */
+ err = canonicalize_access_file(&authzdb_path, server,
+ repos_root, pool);
+
+ /* Same for the groupsdb_path if it is present. */
+ if (groupsdb_path && !err)
+ err = canonicalize_access_file(&groupsdb_path, server,
+ repos_root, pool);
+
+ if (!err)
+ err = svn_repos_authz_read2(&server->authzdb, authzdb_path,
+ groupsdb_path, TRUE, pool);
+
+ if (err)
+ {
+ log_server_error(err, server, conn, pool);
+ svn_error_clear(err);
+ return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL, NULL);
+ }
+
+ /* Are we going to be case-normalizing usernames when we consult
+ * this authz file? */
+ svn_config_get(server->cfg, &case_force_val, SVN_CONFIG_SECTION_GENERAL,
+ SVN_CONFIG_OPTION_FORCE_USERNAME_CASE, NULL);
+ if (case_force_val)
+ {
+ if (strcmp(case_force_val, "upper") == 0)
+ server->username_case = CASE_FORCE_UPPER;
+ else if (strcmp(case_force_val, "lower") == 0)
+ server->username_case = CASE_FORCE_LOWER;
+ else
+ server->username_case = CASE_ASIS;
+ }
+ }
+ else
+ {
+ server->authzdb = NULL;
+ server->username_case = CASE_ASIS;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *FS_PATH to the portion of URL that is the path within the
+ repository, if URL is inside REPOS_URL (if URL is not inside
+ REPOS_URL, then error, with the effect on *FS_PATH undefined).
+
+ If the resultant fs path would be the empty string (i.e., URL and
+ REPOS_URL are the same), then set *FS_PATH to "/".
+
+ Assume that REPOS_URL and URL are already URI-decoded. */
+static svn_error_t *get_fs_path(const char *repos_url, const char *url,
+ const char **fs_path)
+{
+ apr_size_t len;
+
+ len = strlen(repos_url);
+ if (strncmp(url, repos_url, len) != 0)
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ "'%s' is not the same repository as '%s'",
+ url, repos_url);
+ *fs_path = url + len;
+ if (! **fs_path)
+ *fs_path = "/";
+
+ return SVN_NO_ERROR;
+}
+
+/* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */
+
+/* Convert TEXT to upper case if TO_UPPERCASE is TRUE, else
+ converts it to lower case. */
+static void convert_case(char *text, svn_boolean_t to_uppercase)
+{
+ char *c = text;
+ while (*c)
+ {
+ *c = (char)(to_uppercase ? apr_toupper(*c) : apr_tolower(*c));
+ ++c;
+ }
+}
+
+/* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to
+ the user described in BATON according to the authz rules in BATON.
+ Use POOL for temporary allocations only. If no authz rules are
+ present in BATON, grant access by default. */
+static svn_error_t *authz_check_access(svn_boolean_t *allowed,
+ const char *path,
+ svn_repos_authz_access_t required,
+ server_baton_t *b,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ /* If authz cannot be performed, grant access. This is NOT the same
+ as the default policy when authz is performed on a path with no
+ rules. In the latter case, the default is to deny access, and is
+ set by svn_repos_authz_check_access. */
+ if (!b->authzdb)
+ {
+ *allowed = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* If the authz request is for the empty path (ie. ""), replace it
+ with the root path. This happens because of stripping done at
+ various levels in svnserve that remove the leading / on an
+ absolute path. Passing such a malformed path to the authz
+ routines throws them into an infinite loop and makes them miss
+ ACLs. */
+ if (path)
+ path = svn_fspath__canonicalize(path, pool);
+
+ /* If we have a username, and we've not yet used it + any username
+ case normalization that might be requested to determine "the
+ username we used for authz purposes", do so now. */
+ if (b->user && (! b->authz_user))
+ {
+ char *authz_user = apr_pstrdup(b->pool, b->user);
+ if (b->username_case == CASE_FORCE_UPPER)
+ convert_case(authz_user, TRUE);
+ else if (b->username_case == CASE_FORCE_LOWER)
+ convert_case(authz_user, FALSE);
+ b->authz_user = authz_user;
+ }
+
+ SVN_ERR(svn_repos_authz_check_access(b->authzdb, b->authz_repos_name,
+ path, b->authz_user, required,
+ allowed, pool));
+ if (!*allowed)
+ SVN_ERR(log_authz_denied(path, required, b, conn, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *ALLOWED to TRUE if PATH is readable by the user described in
+ * BATON. Use POOL for temporary allocations only. ROOT is not used.
+ * Implements the svn_repos_authz_func_t interface.
+ */
+static svn_error_t *authz_check_access_cb(svn_boolean_t *allowed,
+ svn_fs_root_t *root,
+ const char *path,
+ void *baton,
+ apr_pool_t *pool)
+{
+ authz_baton_t *sb = baton;
+
+ return authz_check_access(allowed, path, svn_authz_read,
+ sb->server, sb->conn, pool);
+}
+
+/* If authz is enabled in the specified BATON, return a read authorization
+ function. Otherwise, return NULL. */
+static svn_repos_authz_func_t authz_check_access_cb_func(server_baton_t *baton)
+{
+ if (baton->authzdb)
+ return authz_check_access_cb;
+ return NULL;
+}
+
+/* Set *ALLOWED to TRUE if the REQUIRED access to PATH is granted,
+ * according to the state in BATON. Use POOL for temporary
+ * allocations only. ROOT is not used. Implements the
+ * svn_repos_authz_callback_t interface.
+ */
+static svn_error_t *authz_commit_cb(svn_repos_authz_access_t required,
+ svn_boolean_t *allowed,
+ svn_fs_root_t *root,
+ const char *path,
+ void *baton,
+ apr_pool_t *pool)
+{
+ authz_baton_t *sb = baton;
+
+ return authz_check_access(allowed, path, required,
+ sb->server, sb->conn, pool);
+}
+
+
+enum access_type get_access(server_baton_t *b, enum authn_type auth)
+{
+ const char *var = (auth == AUTHENTICATED) ? SVN_CONFIG_OPTION_AUTH_ACCESS :
+ SVN_CONFIG_OPTION_ANON_ACCESS;
+ const char *val, *def = (auth == AUTHENTICATED) ? "write" : "read";
+ enum access_type result;
+
+ svn_config_get(b->cfg, &val, SVN_CONFIG_SECTION_GENERAL, var, def);
+ result = (strcmp(val, "write") == 0 ? WRITE_ACCESS :
+ strcmp(val, "read") == 0 ? READ_ACCESS : NO_ACCESS);
+ return (result == WRITE_ACCESS && b->read_only) ? READ_ACCESS : result;
+}
+
+static enum access_type current_access(server_baton_t *b)
+{
+ return get_access(b, (b->user) ? AUTHENTICATED : UNAUTHENTICATED);
+}
+
+/* Send authentication mechs for ACCESS_TYPE to the client. If NEEDS_USERNAME
+ is true, don't send anonymous mech even if that would give the desired
+ access. */
+static svn_error_t *send_mechs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ server_baton_t *b, enum access_type required,
+ svn_boolean_t needs_username)
+{
+ if (!needs_username && get_access(b, UNAUTHENTICATED) >= required)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, "ANONYMOUS"));
+ if (b->tunnel_user && get_access(b, AUTHENTICATED) >= required)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, "EXTERNAL"));
+ if (b->pwdb && get_access(b, AUTHENTICATED) >= required)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, "CRAM-MD5"));
+ return SVN_NO_ERROR;
+}
+
+/* Context for cleanup handler. */
+struct cleanup_fs_access_baton
+{
+ svn_fs_t *fs;
+ apr_pool_t *pool;
+};
+
+/* Pool cleanup handler. Make sure fs's access_t points to NULL when
+ the command pool is destroyed. */
+static apr_status_t cleanup_fs_access(void *data)
+{
+ svn_error_t *serr;
+ struct cleanup_fs_access_baton *baton = data;
+
+ serr = svn_fs_set_access(baton->fs, NULL);
+ if (serr)
+ {
+ apr_status_t apr_err = serr->apr_err;
+ svn_error_clear(serr);
+ return apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Create an svn_fs_access_t in POOL for USER and associate it with
+ B's filesystem. Also, register a cleanup handler with POOL which
+ de-associates the svn_fs_access_t from B's filesystem. */
+static svn_error_t *
+create_fs_access(server_baton_t *b, apr_pool_t *pool)
+{
+ svn_fs_access_t *fs_access;
+ struct cleanup_fs_access_baton *cleanup_baton;
+
+ if (!b->user)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_create_access(&fs_access, b->user, pool));
+ SVN_ERR(svn_fs_set_access(b->fs, fs_access));
+
+ cleanup_baton = apr_pcalloc(pool, sizeof(*cleanup_baton));
+ cleanup_baton->pool = pool;
+ cleanup_baton->fs = b->fs;
+ apr_pool_cleanup_register(pool, cleanup_baton, cleanup_fs_access,
+ apr_pool_cleanup_null);
+
+ return SVN_NO_ERROR;
+}
+
+/* Authenticate, once the client has chosen a mechanism and possibly
+ * sent an initial mechanism token. On success, set *success to true
+ * and b->user to the authenticated username (or NULL for anonymous).
+ * On authentication failure, report failure to the client and set
+ * *success to FALSE. On communications failure, return an error.
+ * If NEEDS_USERNAME is TRUE, don't allow anonymous authentication. */
+static svn_error_t *auth(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char *mech, const char *mecharg,
+ server_baton_t *b, enum access_type required,
+ svn_boolean_t needs_username,
+ svn_boolean_t *success)
+{
+ const char *user;
+ *success = FALSE;
+
+ if (get_access(b, AUTHENTICATED) >= required
+ && b->tunnel_user && strcmp(mech, "EXTERNAL") == 0)
+ {
+ if (*mecharg && strcmp(mecharg, b->tunnel_user) != 0)
+ return svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure",
+ "Requested username does not match");
+ b->user = b->tunnel_user;
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success"));
+ *success = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ if (get_access(b, UNAUTHENTICATED) >= required
+ && strcmp(mech, "ANONYMOUS") == 0 && ! needs_username)
+ {
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w()", "success"));
+ *success = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ if (get_access(b, AUTHENTICATED) >= required
+ && b->pwdb && strcmp(mech, "CRAM-MD5") == 0)
+ {
+ SVN_ERR(svn_ra_svn_cram_server(conn, pool, b->pwdb, &user, success));
+ b->user = apr_pstrdup(b->pool, user);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure",
+ "Must authenticate with listed mechanism");
+}
+
+/* Perform an authentication request using the built-in SASL implementation. */
+static svn_error_t *
+internal_auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ server_baton_t *b, enum access_type required,
+ svn_boolean_t needs_username)
+{
+ svn_boolean_t success;
+ const char *mech, *mecharg;
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
+ SVN_ERR(send_mechs(conn, pool, b, required, needs_username));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)c)", b->realm));
+ do
+ {
+ SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &mech, &mecharg));
+ if (!*mech)
+ break;
+ SVN_ERR(auth(conn, pool, mech, mecharg, b, required, needs_username,
+ &success));
+ }
+ while (!success);
+ return SVN_NO_ERROR;
+}
+
+/* Perform an authentication request in order to get an access level of
+ * REQUIRED or higher. Since the client may escape the authentication
+ * exchange, the caller should check current_access(b) to see if
+ * authentication succeeded. */
+static svn_error_t *auth_request(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ server_baton_t *b, enum access_type required,
+ svn_boolean_t needs_username)
+{
+#ifdef SVN_HAVE_SASL
+ if (b->use_sasl)
+ return cyrus_auth_request(conn, pool, b, required, needs_username);
+#endif
+
+ return internal_auth_request(conn, pool, b, required, needs_username);
+}
+
+/* Send a trivial auth notification on CONN which lists no mechanisms,
+ * indicating that authentication is unnecessary. Usually called in
+ * response to invocation of a svnserve command.
+ */
+static svn_error_t *trivial_auth_request(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool, server_baton_t *b)
+{
+ return svn_ra_svn__write_cmd_response(conn, pool, "()c", "");
+}
+
+/* Ensure that the client has the REQUIRED access by checking the
+ * access directives (both blanket and per-directory) in BATON. If
+ * PATH is NULL, then only the blanket access configuration will
+ * impact the result.
+ *
+ * If NEEDS_USERNAME is TRUE, then a lookup is only successful if the
+ * user described in BATON is authenticated and, well, has a username
+ * assigned to him.
+ *
+ * Use POOL for temporary allocations only.
+ */
+static svn_boolean_t lookup_access(apr_pool_t *pool,
+ server_baton_t *baton,
+ svn_ra_svn_conn_t *conn,
+ svn_repos_authz_access_t required,
+ const char *path,
+ svn_boolean_t needs_username)
+{
+ enum access_type req = (required & svn_authz_write) ?
+ WRITE_ACCESS : READ_ACCESS;
+ svn_boolean_t authorized;
+ svn_error_t *err;
+
+ /* Get authz's opinion on the access. */
+ err = authz_check_access(&authorized, path, required, baton, conn, pool);
+
+ /* If an error made lookup fail, deny access. */
+ if (err)
+ {
+ log_server_error(err, baton, conn, pool);
+ svn_error_clear(err);
+ return FALSE;
+ }
+
+ /* If the required access is blanket-granted AND granted by authz
+ AND we already have a username if one is required, then the
+ lookup has succeeded. */
+ if (current_access(baton) >= req
+ && authorized
+ && (! needs_username || baton->user))
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Check that the client has the REQUIRED access by consulting the
+ * authentication and authorization states stored in BATON. If the
+ * client does not have the required access credentials, attempt to
+ * authenticate the client to get that access, using CONN for
+ * communication.
+ *
+ * This function is supposed to be called to handle the authentication
+ * half of a standard svn protocol reply. If an error is returned, it
+ * probably means that the server can terminate the client connection
+ * with an apologetic error, as it implies an authentication failure.
+ *
+ * PATH and NEEDS_USERNAME are passed along to lookup_access, their
+ * behaviour is documented there.
+ */
+static svn_error_t *must_have_access(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ server_baton_t *b,
+ svn_repos_authz_access_t required,
+ const char *path,
+ svn_boolean_t needs_username)
+{
+ enum access_type req = (required & svn_authz_write) ?
+ WRITE_ACCESS : READ_ACCESS;
+
+ /* See whether the user already has the required access. If so,
+ nothing needs to be done. Create the FS access and send a
+ trivial auth request. */
+ if (lookup_access(pool, b, conn, required, path, needs_username))
+ {
+ SVN_ERR(create_fs_access(b, pool));
+ return trivial_auth_request(conn, pool, b);
+ }
+
+ /* If the required blanket access can be obtained by authenticating,
+ try that. Unfortunately, we can't tell until after
+ authentication whether authz will work or not. We force
+ requiring a username because we need one to be able to check
+ authz configuration again with a different user credentials than
+ the first time round. */
+ if (b->user == NULL
+ && get_access(b, AUTHENTICATED) >= req
+ && (b->tunnel_user || b->pwdb || b->use_sasl))
+ SVN_ERR(auth_request(conn, pool, b, req, TRUE));
+
+ /* Now that an authentication has been done get the new take of
+ authz on the request. */
+ if (! lookup_access(pool, b, conn, required, path, needs_username))
+ return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
+ error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
+ NULL, NULL, b, conn, pool),
+ NULL);
+
+ /* Else, access is granted, and there is much rejoicing. */
+ SVN_ERR(create_fs_access(b, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* --- REPORTER COMMAND SET --- */
+
+/* To allow for pipelining, reporter commands have no reponses. If we
+ * get an error, we ignore all subsequent reporter commands and return
+ * the error finish_report, to be handled by the calling command.
+ */
+
+static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ report_driver_baton_t *b = baton;
+ const char *path, *lock_token, *depth_word;
+ svn_revnum_t rev;
+ /* Default to infinity, for old clients that don't send depth. */
+ svn_depth_t depth = svn_depth_infinity;
+ svn_boolean_t start_empty;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crb?(?c)?w",
+ &path, &rev, &start_empty, &lock_token,
+ &depth_word));
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ path = svn_relpath_canonicalize(path, pool);
+ if (b->from_rev && strcmp(path, "") == 0)
+ *b->from_rev = rev;
+ if (!b->err)
+ b->err = svn_repos_set_path3(b->report_baton, path, rev, depth,
+ start_empty, lock_token, pool);
+ b->entry_counter++;
+ if (!start_empty)
+ b->only_empty_entries = FALSE;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *delete_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ report_driver_baton_t *b = baton;
+ const char *path;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path));
+ path = svn_relpath_canonicalize(path, pool);
+ if (!b->err)
+ b->err = svn_repos_delete_path(b->report_baton, path, pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ report_driver_baton_t *b = baton;
+ const char *path, *url, *lock_token, *fs_path, *depth_word;
+ svn_revnum_t rev;
+ svn_boolean_t start_empty;
+ /* Default to infinity, for old clients that don't send depth. */
+ svn_depth_t depth = svn_depth_infinity;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "ccrb?(?c)?w",
+ &path, &url, &rev, &start_empty,
+ &lock_token, &depth_word));
+
+ /* ### WHAT?! The link path is an absolute URL?! Didn't see that
+ coming... -- cmpilato */
+ path = svn_relpath_canonicalize(path, pool);
+ url = svn_uri_canonicalize(url, pool);
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ if (!b->err)
+ b->err = get_fs_path(svn_path_uri_decode(b->repos_url, pool),
+ svn_path_uri_decode(url, pool),
+ &fs_path);
+ if (!b->err)
+ b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev,
+ depth, start_empty, lock_token, pool);
+ b->entry_counter++;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *finish_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ report_driver_baton_t *b = baton;
+
+ /* No arguments to parse. */
+ SVN_ERR(trivial_auth_request(conn, pool, b->sb));
+ if (!b->err)
+ b->err = svn_repos_finish_report(b->report_baton, pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *abort_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ report_driver_baton_t *b = baton;
+
+ /* No arguments to parse. */
+ svn_error_clear(svn_repos_abort_report(b->report_baton, pool));
+ return SVN_NO_ERROR;
+}
+
+static const svn_ra_svn_cmd_entry_t report_commands[] = {
+ { "set-path", set_path },
+ { "delete-path", delete_path },
+ { "link-path", link_path },
+ { "finish-report", finish_report, TRUE },
+ { "abort-report", abort_report, TRUE },
+ { NULL }
+};
+
+/* Accept a report from the client, drive the network editor with the
+ * result, and then write an empty command response. If there is a
+ * non-protocol failure, accept_report will abort the edit and return
+ * a command error to be reported by handle_commands().
+ *
+ * If only_empty_entry is not NULL and the report contains only one
+ * item, and that item is empty, set *only_empty_entry to TRUE, else
+ * set it to FALSE.
+ *
+ * If from_rev is not NULL, set *from_rev to the revision number from
+ * the set-path on ""; if somehow set-path "" never happens, set
+ * *from_rev to SVN_INVALID_REVNUM.
+ */
+static svn_error_t *accept_report(svn_boolean_t *only_empty_entry,
+ svn_revnum_t *from_rev,
+ svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ server_baton_t *b, svn_revnum_t rev,
+ const char *target, const char *tgt_path,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry)
+{
+ const svn_delta_editor_t *editor;
+ void *edit_baton, *report_baton;
+ report_driver_baton_t rb;
+ svn_error_t *err;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ /* Make an svn_repos report baton. Tell it to drive the network editor
+ * when the report is complete. */
+ svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
+ SVN_CMD_ERR(svn_repos_begin_report3(&report_baton, rev, b->repos,
+ b->fs_path->data, target, tgt_path,
+ text_deltas, depth, ignore_ancestry,
+ send_copyfrom_args,
+ editor, edit_baton,
+ authz_check_access_cb_func(b),
+ &ab, svn_ra_svn_zero_copy_limit(conn),
+ pool));
+
+ rb.sb = b;
+ rb.repos_url = svn_path_uri_decode(b->repos_url, pool);
+ rb.report_baton = report_baton;
+ rb.err = NULL;
+ rb.entry_counter = 0;
+ rb.only_empty_entries = TRUE;
+ rb.from_rev = from_rev;
+ if (from_rev)
+ *from_rev = SVN_INVALID_REVNUM;
+ err = svn_ra_svn__handle_commands2(conn, pool, report_commands, &rb, TRUE);
+ if (err)
+ {
+ /* Network or protocol error while handling commands. */
+ svn_error_clear(rb.err);
+ return err;
+ }
+ else if (rb.err)
+ {
+ /* Some failure during the reporting or editing operations. */
+ SVN_CMD_ERR(rb.err);
+ }
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+
+ if (only_empty_entry)
+ *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries;
+
+ return SVN_NO_ERROR;
+}
+
+/* --- MAIN COMMAND SET --- */
+
+/* Write out a list of property diffs. PROPDIFFS is an array of svn_prop_t
+ * values. */
+static svn_error_t *write_prop_diffs(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const apr_array_header_t *propdiffs)
+{
+ int i;
+
+ for (i = 0; i < propdiffs->nelts; ++i)
+ {
+ const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "c(?s)",
+ prop->name, prop->value));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Write out a lock to the client. */
+static svn_error_t *write_lock(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_lock_t *lock)
+{
+ const char *cdate, *edate;
+
+ cdate = svn_time_to_cstring(lock->creation_date, pool);
+ edate = lock->expiration_date
+ ? svn_time_to_cstring(lock->expiration_date, pool) : NULL;
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "ccc(?c)c(?c)", lock->path,
+ lock->token, lock->owner, lock->comment,
+ cdate, edate));
+
+ return SVN_NO_ERROR;
+}
+
+/* ### This really belongs in libsvn_repos. */
+/* Get the explicit properties and/or inherited properties for a PATH in
+ ROOT, with hardcoded committed-info values. */
+static svn_error_t *
+get_props(apr_hash_t **props,
+ apr_array_header_t **iprops,
+ authz_baton_t *b,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ /* Get the explicit properties. */
+ if (props)
+ {
+ svn_string_t *str;
+ svn_revnum_t crev;
+ const char *cdate, *cauthor, *uuid;
+
+ SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
+
+ /* Hardcode the values for the committed revision, date, and author. */
+ SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
+ path, pool));
+ str = svn_string_create(apr_psprintf(pool, "%ld", crev),
+ pool);
+ svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, str);
+ str = (cdate) ? svn_string_create(cdate, pool) : NULL;
+ svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, str);
+ str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
+ svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, str);
+
+ /* Hardcode the values for the UUID. */
+ SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
+ str = (uuid) ? svn_string_create(uuid, pool) : NULL;
+ svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, str);
+ }
+
+ /* Get any inherited properties the user is authorized to. */
+ if (iprops)
+ {
+ SVN_ERR(svn_repos_fs_get_inherited_props(
+ iprops, root, path, NULL,
+ authz_check_access_cb_func(b->server),
+ b, pool, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set BATON->FS_PATH for the repository URL found in PARAMS. */
+static svn_error_t *reparent(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *url;
+ const char *fs_path;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &url));
+ url = svn_uri_canonicalize(url, pool);
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
+ svn_path_uri_decode(url, pool),
+ &fs_path));
+ SVN_ERR(log_command(b, conn, pool, "%s", svn_log__reparent(fs_path, pool)));
+ svn_stringbuf_set(b->fs_path, fs_path);
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+
+ SVN_ERR(log_command(b, conn, pool, "get-latest-rev"));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ apr_time_t tm;
+ const char *timestr;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &timestr));
+ SVN_ERR(log_command(b, conn, pool, "get-dated-rev %s", timestr));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool));
+ SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", rev));
+ return SVN_NO_ERROR;
+}
+
+/* Common logic for change_rev_prop() and change_rev_prop2(). */
+static svn_error_t *do_change_rev_prop(svn_ra_svn_conn_t *conn,
+ server_baton_t *b,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__change_rev_prop(rev, name, pool)));
+ SVN_CMD_ERR(svn_repos_fs_change_rev_prop4(b->repos, rev, b->user,
+ name, old_value_p, value,
+ TRUE, TRUE,
+ authz_check_access_cb_func(b), &ab,
+ pool));
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *change_rev_prop2(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *name;
+ svn_string_t *value;
+ const svn_string_t *const *old_value_p;
+ svn_string_t *old_value;
+ svn_boolean_t dont_care;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc(?s)(b?s)",
+ &rev, &name, &value,
+ &dont_care, &old_value));
+
+ /* Argument parsing. */
+ if (dont_care)
+ old_value_p = NULL;
+ else
+ old_value_p = (const svn_string_t *const *)&old_value;
+
+ /* Input validation. */
+ if (dont_care && old_value)
+ {
+ svn_error_t *err;
+ err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "'previous-value' and 'dont-care' cannot both be "
+ "set in 'change-rev-prop2' request");
+ return log_fail_and_flush(err, b, conn, pool);
+ }
+
+ /* Do it. */
+ SVN_ERR(do_change_rev_prop(conn, b, rev, name, old_value_p, value, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *name;
+ svn_string_t *value;
+
+ /* Because the revprop value was at one time mandatory, the usual
+ optional element pattern "(?s)" isn't used. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc?s", &rev, &name, &value));
+
+ SVN_ERR(do_change_rev_prop(conn, b, rev, name, NULL, value, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ apr_hash_t *props;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "r", &rev));
+ SVN_ERR(log_command(b, conn, pool, "%s", svn_log__rev_proplist(rev, pool)));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
+ authz_check_access_cb_func(b), &ab,
+ pool));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
+ SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *name;
+ svn_string_t *value;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rc", &rev, &name));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__rev_prop(rev, name, pool)));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name,
+ authz_check_access_cb_func(b), &ab,
+ pool));
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "(?s)", value));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *commit_done(const svn_commit_info_t *commit_info,
+ void *baton, apr_pool_t *pool)
+{
+ commit_callback_baton_t *ccb = baton;
+
+ *ccb->new_rev = commit_info->revision;
+ *ccb->date = commit_info->date
+ ? apr_pstrdup(ccb->pool, commit_info->date): NULL;
+ *ccb->author = commit_info->author
+ ? apr_pstrdup(ccb->pool, commit_info->author) : NULL;
+ *ccb->post_commit_err = commit_info->post_commit_err
+ ? apr_pstrdup(ccb->pool, commit_info->post_commit_err) : NULL;
+ return SVN_NO_ERROR;
+}
+
+/* Add the LOCK_TOKENS (if any) to the filesystem access context,
+ * checking path authorizations using the state in SB as we go.
+ * LOCK_TOKENS is an array of svn_ra_svn_item_t structs. Return a
+ * client error if LOCK_TOKENS is not a list of lists. If a lock
+ * violates the authz configuration, return SVN_ERR_RA_NOT_AUTHORIZED
+ * to the client. Use POOL for temporary allocations only.
+ */
+static svn_error_t *add_lock_tokens(svn_ra_svn_conn_t *conn,
+ const apr_array_header_t *lock_tokens,
+ server_baton_t *sb,
+ apr_pool_t *pool)
+{
+ int i;
+ svn_fs_access_t *fs_access;
+
+ SVN_ERR(svn_fs_get_access(&fs_access, sb->fs));
+
+ /* If there is no access context, nowhere to add the tokens. */
+ if (! fs_access)
+ return SVN_NO_ERROR;
+
+ for (i = 0; i < lock_tokens->nelts; ++i)
+ {
+ const char *path, *token, *full_path;
+ svn_ra_svn_item_t *path_item, *token_item;
+ svn_ra_svn_item_t *item = &APR_ARRAY_IDX(lock_tokens, i,
+ svn_ra_svn_item_t);
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Lock tokens aren't a list of lists");
+
+ path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
+ if (path_item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Lock path isn't a string");
+
+ token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
+ if (token_item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Lock token isn't a string");
+
+ path = path_item->u.string->data;
+ full_path = svn_fspath__join(sb->fs_path->data,
+ svn_relpath_canonicalize(path, pool),
+ pool);
+
+ if (! lookup_access(pool, sb, conn, svn_authz_write,
+ full_path, TRUE))
+ return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
+ sb, conn, pool);
+
+ token = token_item->u.string->data;
+ SVN_ERR(svn_fs_access_add_lock_token2(fs_access, path, token));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Unlock the paths with lock tokens in LOCK_TOKENS, ignoring any errors.
+ LOCK_TOKENS contains svn_ra_svn_item_t elements, assumed to be lists. */
+static svn_error_t *unlock_paths(const apr_array_header_t *lock_tokens,
+ server_baton_t *sb,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < lock_tokens->nelts; ++i)
+ {
+ svn_ra_svn_item_t *item, *path_item, *token_item;
+ const char *path, *token, *full_path;
+ svn_error_t *err;
+ svn_pool_clear(iterpool);
+
+ item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
+ path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
+ token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
+
+ path = path_item->u.string->data;
+ token = token_item->u.string->data;
+
+ full_path = svn_fspath__join(sb->fs_path->data,
+ svn_relpath_canonicalize(path, iterpool),
+ iterpool);
+
+ /* The lock may have become defunct after the commit, so ignore such
+ errors. */
+ err = svn_repos_fs_unlock(sb->repos, full_path, token,
+ FALSE, iterpool);
+ log_server_error(err, sb, conn, iterpool);
+ svn_error_clear(err);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *log_msg = NULL,
+ *date = NULL,
+ *author = NULL,
+ *post_commit_err = NULL;
+ apr_array_header_t *lock_tokens;
+ svn_boolean_t keep_locks;
+ apr_array_header_t *revprop_list = NULL;
+ apr_hash_t *revprop_table;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ svn_boolean_t aborted;
+ commit_callback_baton_t ccb;
+ svn_revnum_t new_rev;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ if (params->nelts == 1)
+ {
+ /* Clients before 1.2 don't send lock-tokens, keep-locks,
+ and rev-props fields. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &log_msg));
+ lock_tokens = NULL;
+ keep_locks = TRUE;
+ revprop_list = NULL;
+ }
+ else
+ {
+ /* Clients before 1.5 don't send the rev-props field. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "clb?l", &log_msg,
+ &lock_tokens, &keep_locks,
+ &revprop_list));
+ }
+
+ /* The handling for locks is a little problematic, because the
+ protocol won't let us send several auth requests once one has
+ succeeded. So we request write access and a username before
+ adding tokens (if we have any), and subsequently fail if a lock
+ violates authz. */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
+ NULL,
+ (lock_tokens && lock_tokens->nelts)));
+
+ /* Authorize the lock tokens and give them to the FS if we got
+ any. */
+ if (lock_tokens && lock_tokens->nelts)
+ SVN_CMD_ERR(add_lock_tokens(conn, lock_tokens, b, pool));
+
+ if (revprop_list)
+ SVN_ERR(svn_ra_svn__parse_proplist(revprop_list, pool, &revprop_table));
+ else
+ {
+ revprop_table = apr_hash_make(pool);
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
+ svn_string_create(log_msg, pool));
+ }
+
+ /* Get author from the baton, making sure clients can't circumvent
+ the authentication via the revision props. */
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
+ b->user ? svn_string_create(b->user, pool) : NULL);
+
+ ccb.pool = pool;
+ ccb.new_rev = &new_rev;
+ ccb.date = &date;
+ ccb.author = &author;
+ ccb.post_commit_err = &post_commit_err;
+ /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
+ SVN_CMD_ERR(svn_repos_get_commit_editor5
+ (&editor, &edit_baton, b->repos, NULL,
+ svn_path_uri_decode(b->repos_url, pool),
+ b->fs_path->data, revprop_table,
+ commit_done, &ccb,
+ authz_commit_cb, &ab, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+ SVN_ERR(svn_ra_svn_drive_editor2(conn, pool, editor, edit_baton,
+ &aborted, FALSE));
+ if (!aborted)
+ {
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__commit(new_rev, pool)));
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ /* In tunnel mode, deltify before answering the client, because
+ answering may cause the client to terminate the connection
+ and thus kill the server. But otherwise, deltify after
+ answering the client, to avoid user-visible delay. */
+
+ if (b->tunnel)
+ SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
+
+ /* Unlock the paths. */
+ if (! keep_locks && lock_tokens && lock_tokens->nelts)
+ SVN_ERR(unlock_paths(lock_tokens, b, conn, pool));
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "r(?c)(?c)(?c)",
+ new_rev, date, author, post_commit_err));
+
+ if (! b->tunnel)
+ SVN_ERR(svn_fs_deltify_revision(b->fs, new_rev, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path, *full_path, *hex_digest;
+ svn_revnum_t rev;
+ svn_fs_root_t *root;
+ svn_stream_t *contents;
+ apr_hash_t *props = NULL;
+ apr_array_header_t *inherited_props;
+ svn_string_t write_str;
+ char buf[4096];
+ apr_size_t len;
+ svn_boolean_t want_props, want_contents;
+ apr_uint64_t wants_inherited_props;
+ svn_checksum_t *checksum;
+ svn_error_t *err, *write_err;
+ int i;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ /* Parse arguments. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?B", &path, &rev,
+ &want_props, &want_contents,
+ &wants_inherited_props));
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Check authorizations */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
+ full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_file(full_path, rev,
+ want_contents, want_props, pool)));
+
+ /* Fetch the properties and a stream for the contents. */
+ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
+ SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root,
+ full_path, TRUE, pool));
+ hex_digest = svn_checksum_to_cstring_display(checksum, pool);
+ if (want_props || wants_inherited_props)
+ SVN_CMD_ERR(get_props(&props, &inherited_props, &ab, root, full_path,
+ pool));
+ if (want_contents)
+ SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
+
+ /* Send successful command response with revision and props. */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((?c)r(!", "success",
+ hex_digest, rev));
+ SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
+
+ if (wants_inherited_props)
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
+ iprop->path_or_url));
+ SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
+ iprop->path_or_url));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ /* Now send the file's contents. */
+ if (want_contents)
+ {
+ err = SVN_NO_ERROR;
+ while (1)
+ {
+ len = sizeof(buf);
+ err = svn_stream_read(contents, buf, &len);
+ if (err)
+ break;
+ if (len > 0)
+ {
+ write_str.data = buf;
+ write_str.len = len;
+ SVN_ERR(svn_ra_svn__write_string(conn, pool, &write_str));
+ }
+ if (len < sizeof(buf))
+ {
+ err = svn_stream_close(contents);
+ break;
+ }
+ }
+ write_err = svn_ra_svn__write_cstring(conn, pool, "");
+ if (write_err)
+ {
+ svn_error_clear(err);
+ return write_err;
+ }
+ SVN_CMD_ERR(err);
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path, *full_path;
+ svn_revnum_t rev;
+ apr_hash_t *entries, *props = NULL;
+ apr_array_header_t *inherited_props;
+ apr_hash_index_t *hi;
+ svn_fs_root_t *root;
+ apr_pool_t *subpool;
+ svn_boolean_t want_props, want_contents;
+ apr_uint64_t wants_inherited_props;
+ apr_uint64_t dirent_fields;
+ apr_array_header_t *dirent_fields_list = NULL;
+ svn_ra_svn_item_t *elt;
+ int i;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)bb?l?B", &path, &rev,
+ &want_props, &want_contents,
+ &dirent_fields_list,
+ &wants_inherited_props));
+
+ if (! dirent_fields_list)
+ {
+ dirent_fields = SVN_DIRENT_ALL;
+ }
+ else
+ {
+ dirent_fields = 0;
+
+ for (i = 0; i < dirent_fields_list->nelts; ++i)
+ {
+ elt = &APR_ARRAY_IDX(dirent_fields_list, i, svn_ra_svn_item_t);
+
+ if (elt->kind != SVN_RA_SVN_WORD)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Dirent field not a string");
+
+ if (strcmp(SVN_RA_SVN_DIRENT_KIND, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_KIND;
+ else if (strcmp(SVN_RA_SVN_DIRENT_SIZE, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_SIZE;
+ else if (strcmp(SVN_RA_SVN_DIRENT_HAS_PROPS, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_HAS_PROPS;
+ else if (strcmp(SVN_RA_SVN_DIRENT_CREATED_REV, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_CREATED_REV;
+ else if (strcmp(SVN_RA_SVN_DIRENT_TIME, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_TIME;
+ else if (strcmp(SVN_RA_SVN_DIRENT_LAST_AUTHOR, elt->u.word) == 0)
+ dirent_fields |= SVN_DIRENT_LAST_AUTHOR;
+ }
+ }
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Check authorizations */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
+ full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_dir(full_path, rev,
+ want_contents, want_props,
+ dirent_fields, pool)));
+
+ /* Fetch the root of the appropriate revision. */
+ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
+
+ /* Fetch the directory's explicit and/or inherited properties
+ if requested. */
+ if (want_props || wants_inherited_props)
+ SVN_CMD_ERR(get_props(&props, &inherited_props, &ab, root, full_path,
+ pool));
+
+ /* Begin response ... */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(r(!", "success", rev));
+ SVN_ERR(svn_ra_svn__write_proplist(conn, pool, props));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(!"));
+
+ /* Fetch the directory entries if requested and send them immediately. */
+ if (want_contents)
+ {
+ /* Use epoch for a placeholder for a missing date. */
+ const char *missing_date = svn_time_to_cstring(0, pool);
+
+ SVN_CMD_ERR(svn_fs_dir_entries(&entries, root, full_path, pool));
+
+ /* Transform the hash table's FS entries into dirents. This probably
+ * belongs in libsvn_repos. */
+ subpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_fs_dirent_t *fsent = svn__apr_hash_index_val(hi);
+ const char *file_path;
+
+ /* The fields in the entry tuple. */
+ svn_node_kind_t entry_kind = svn_node_none;
+ svn_filesize_t entry_size = 0;
+ svn_boolean_t has_props = FALSE;
+ /* If 'created rev' was not requested, send 0. We can't use
+ * SVN_INVALID_REVNUM as the tuple field is not optional.
+ * See the email thread on dev@, 2012-03-28, subject
+ * "buildbot failure in ASF Buildbot on svn-slik-w2k3-x64-ra",
+ * <http://svn.haxx.se/dev/archive-2012-03/0655.shtml>. */
+ svn_revnum_t created_rev = 0;
+ const char *cdate = NULL;
+ const char *last_author = NULL;
+
+ svn_pool_clear(subpool);
+
+ file_path = svn_fspath__join(full_path, name, subpool);
+ if (! lookup_access(subpool, b, conn, svn_authz_read,
+ file_path, FALSE))
+ continue;
+
+ if (dirent_fields & SVN_DIRENT_KIND)
+ entry_kind = fsent->kind;
+
+ if (dirent_fields & SVN_DIRENT_SIZE)
+ if (entry_kind != svn_node_dir)
+ SVN_CMD_ERR(svn_fs_file_length(&entry_size, root, file_path,
+ subpool));
+
+ if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ apr_hash_t *file_props;
+
+ /* has_props */
+ SVN_CMD_ERR(svn_fs_node_proplist(&file_props, root, file_path,
+ subpool));
+ has_props = (apr_hash_count(file_props) > 0);
+ }
+
+ if ((dirent_fields & SVN_DIRENT_LAST_AUTHOR)
+ || (dirent_fields & SVN_DIRENT_TIME)
+ || (dirent_fields & SVN_DIRENT_CREATED_REV))
+ {
+ /* created_rev, last_author, time */
+ SVN_CMD_ERR(svn_repos_get_committed_info(&created_rev,
+ &cdate,
+ &last_author,
+ root,
+ file_path,
+ subpool));
+ }
+
+ /* The client does not properly handle a missing CDATE. For
+ interoperability purposes, we must fill in some junk.
+
+ See libsvn_ra_svn/client.c:ra_svn_get_dir() */
+ if (cdate == NULL)
+ cdate = missing_date;
+
+ /* Send the entry. */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "cwnbr(?c)(?c)", name,
+ svn_node_kind_to_word(entry_kind),
+ (apr_uint64_t) entry_size,
+ has_props, created_rev,
+ cdate, last_author));
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ if (wants_inherited_props)
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)(?!"));
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
+ iprop->path_or_url));
+ SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
+ iprop->path_or_url));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ /* Finish response. */
+ return svn_ra_svn__write_tuple(conn, pool, "!))");
+}
+
+static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *target, *full_path, *depth_word;
+ svn_boolean_t recurse;
+ apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
+ apr_uint64_t ignore_ancestry; /* Optional; default FALSE */
+ /* Default to unknown. Old clients won't send depth, but we'll
+ handle that by converting recurse if necessary. */
+ svn_depth_t depth = svn_depth_unknown;
+ svn_boolean_t is_checkout;
+
+ /* Parse the arguments. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cb?wB?B", &rev, &target,
+ &recurse, &depth_word,
+ &send_copyfrom_args, &ignore_ancestry));
+ target = svn_relpath_canonicalize(target, pool);
+
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ else
+ depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
+
+ full_path = svn_fspath__join(b->fs_path->data, target, pool);
+ /* Check authorization and authenticate the user if necessary. */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(accept_report(&is_checkout, NULL,
+ conn, pool, b, rev, target, NULL, TRUE,
+ depth,
+ (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
+ (ignore_ancestry == TRUE) /* ignore_ancestry */));
+ if (is_checkout)
+ {
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__checkout(full_path, rev,
+ depth, pool)));
+ }
+ else
+ {
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__update(full_path, rev, depth,
+ send_copyfrom_args, pool)));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *target, *depth_word;
+ const char *switch_url, *switch_path;
+ svn_boolean_t recurse;
+ /* Default to unknown. Old clients won't send depth, but we'll
+ handle that by converting recurse if necessary. */
+ svn_depth_t depth = svn_depth_unknown;
+ apr_uint64_t send_copyfrom_args; /* Optional; default FALSE */
+ apr_uint64_t ignore_ancestry; /* Optional; default TRUE */
+
+ /* Parse the arguments. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbc?w?BB", &rev, &target,
+ &recurse, &switch_url, &depth_word,
+ &send_copyfrom_args, &ignore_ancestry));
+ target = svn_relpath_canonicalize(target, pool);
+ switch_url = svn_uri_canonicalize(switch_url, pool);
+
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ else
+ depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
+ svn_path_uri_decode(switch_url, pool),
+ &switch_path));
+
+ {
+ const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__switch(full_path, switch_path, rev,
+ depth, pool)));
+ }
+
+ return accept_report(NULL, NULL,
+ conn, pool, b, rev, target, switch_path, TRUE,
+ depth,
+ (send_copyfrom_args == TRUE) /* send_copyfrom_args */,
+ (ignore_ancestry != FALSE) /* ignore_ancestry */);
+}
+
+static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *target, *depth_word;
+ svn_boolean_t recurse;
+ /* Default to unknown. Old clients won't send depth, but we'll
+ handle that by converting recurse if necessary. */
+ svn_depth_t depth = svn_depth_unknown;
+
+ /* Parse the arguments. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "cb?(?r)?w",
+ &target, &recurse, &rev, &depth_word));
+ target = svn_relpath_canonicalize(target, pool);
+
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ else
+ depth = SVN_DEPTH_INFINITY_OR_EMPTY(recurse);
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ {
+ const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__status(full_path, rev, depth, pool)));
+ }
+
+ return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE,
+ depth, FALSE, FALSE);
+}
+
+static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *target, *versus_url, *versus_path, *depth_word;
+ svn_boolean_t recurse, ignore_ancestry;
+ svn_boolean_t text_deltas;
+ /* Default to unknown. Old clients won't send depth, but we'll
+ handle that by converting recurse if necessary. */
+ svn_depth_t depth = svn_depth_unknown;
+
+ /* Parse the arguments. */
+ if (params->nelts == 5)
+ {
+ /* Clients before 1.4 don't send the text_deltas boolean or depth. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbc", &rev, &target,
+ &recurse, &ignore_ancestry, &versus_url));
+ text_deltas = TRUE;
+ depth_word = NULL;
+ }
+ else
+ {
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?r)cbbcb?w",
+ &rev, &target, &recurse,
+ &ignore_ancestry, &versus_url,
+ &text_deltas, &depth_word));
+ }
+ target = svn_relpath_canonicalize(target, pool);
+ versus_url = svn_uri_canonicalize(versus_url, pool);
+
+ if (depth_word)
+ depth = svn_depth_from_word(depth_word);
+ else
+ depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+ SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool),
+ svn_path_uri_decode(versus_url, pool),
+ &versus_path));
+
+ {
+ const char *full_path = svn_fspath__join(b->fs_path->data, target, pool);
+ svn_revnum_t from_rev;
+ SVN_ERR(accept_report(NULL, &from_rev,
+ conn, pool, b, rev, target, versus_path,
+ text_deltas, depth, FALSE, ignore_ancestry));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__diff(full_path, from_rev, versus_path,
+ rev, depth, ignore_ancestry,
+ pool)));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Regardless of whether a client's capabilities indicate an
+ understanding of this command (by way of SVN_RA_SVN_CAP_MERGEINFO),
+ we provide a response.
+
+ ASSUMPTION: When performing a 'merge' with two URLs at different
+ revisions, the client will call this command more than once. */
+static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ apr_array_header_t *paths, *canonical_paths;
+ svn_mergeinfo_catalog_t mergeinfo;
+ int i;
+ apr_hash_index_t *hi;
+ const char *inherit_word;
+ svn_mergeinfo_inheritance_t inherit;
+ svn_boolean_t include_descendants;
+ apr_pool_t *iterpool;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)wb", &paths, &rev,
+ &inherit_word, &include_descendants));
+ inherit = svn_inheritance_from_word(inherit_word);
+
+ /* Canonicalize the paths which mergeinfo has been requested for. */
+ canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
+ for (i = 0; i < paths->nelts; i++)
+ {
+ svn_ra_svn_item_t *item = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
+ const char *full_path;
+
+ if (item->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Path is not a string"));
+ full_path = svn_relpath_canonicalize(item->u.string->data, pool);
+ full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
+ APR_ARRAY_PUSH(canonical_paths, const char *) = full_path;
+ }
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_mergeinfo(canonical_paths, inherit,
+ include_descendants,
+ pool)));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos,
+ canonical_paths, rev,
+ inherit,
+ include_descendants,
+ authz_check_access_cb_func(b), &ab,
+ pool));
+ SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(&mergeinfo, mergeinfo,
+ b->fs_path->data, pool));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ const char *key = svn__apr_hash_index_key(hi);
+ svn_mergeinfo_t value = svn__apr_hash_index_val(hi);
+ svn_string_t *mergeinfo_string;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, value, iterpool));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "cs", key,
+ mergeinfo_string));
+ }
+ svn_pool_destroy(iterpool);
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ return SVN_NO_ERROR;
+}
+
+/* Send a log entry to the client. */
+static svn_error_t *log_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ log_baton_t *b = baton;
+ svn_ra_svn_conn_t *conn = b->conn;
+ apr_hash_index_t *h;
+ svn_boolean_t invalid_revnum = FALSE;
+ char action[2];
+ const char *author, *date, *message;
+ apr_uint64_t revprop_count;
+
+ if (log_entry->revision == SVN_INVALID_REVNUM)
+ {
+ /* If the stack depth is zero, we've seen the last revision, so don't
+ send it, just return. */
+ if (b->stack_depth == 0)
+ return SVN_NO_ERROR;
+
+ /* Because the svn protocol won't let us send an invalid revnum, we have
+ to fudge here and send an additional flag. */
+ log_entry->revision = 0;
+ invalid_revnum = TRUE;
+ b->stack_depth--;
+ }
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "(!"));
+ if (log_entry->changed_paths2)
+ {
+ for (h = apr_hash_first(pool, log_entry->changed_paths2); h;
+ h = apr_hash_next(h))
+ {
+ const char *path = svn__apr_hash_index_key(h);
+ svn_log_changed_path2_t *change = svn__apr_hash_index_val(h);
+
+ action[0] = change->action;
+ action[1] = '\0';
+ SVN_ERR(svn_ra_svn__write_tuple(
+ conn, pool, "cw(?cr)(cbb)",
+ path,
+ action,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ svn_node_kind_to_word(change->node_kind),
+ /* text_modified and props_modified are never unknown */
+ change->text_modified == svn_tristate_true,
+ change->props_modified == svn_tristate_true));
+ }
+ }
+ svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+ svn_compat_log_revprops_clear(log_entry->revprops);
+ if (log_entry->revprops)
+ revprop_count = apr_hash_count(log_entry->revprops);
+ else
+ revprop_count = 0;
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)r(?c)(?c)(?c)bbn(!",
+ log_entry->revision,
+ author, date, message,
+ log_entry->has_children,
+ invalid_revnum, revprop_count));
+ SVN_ERR(svn_ra_svn__write_proplist(conn, pool, log_entry->revprops));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)b",
+ log_entry->subtractive_merge));
+
+ if (log_entry->has_children)
+ b->stack_depth++;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ svn_error_t *err, *write_err;
+ server_baton_t *b = baton;
+ svn_revnum_t start_rev, end_rev;
+ const char *full_path;
+ svn_boolean_t send_changed_paths, strict_node, include_merged_revisions;
+ apr_array_header_t *paths, *full_paths, *revprop_items, *revprops;
+ char *revprop_word;
+ svn_ra_svn_item_t *elt;
+ int i;
+ apr_uint64_t limit, include_merged_revs_param;
+ log_baton_t lb;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "l(?r)(?r)bb?n?Bwl", &paths,
+ &start_rev, &end_rev, &send_changed_paths,
+ &strict_node, &limit,
+ &include_merged_revs_param,
+ &revprop_word, &revprop_items));
+
+ if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
+ include_merged_revisions = FALSE;
+ else
+ include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
+
+ if (revprop_word == NULL)
+ /* pre-1.5 client */
+ revprops = svn_compat_log_revprops_in(pool);
+ else if (strcmp(revprop_word, "all-revprops") == 0)
+ revprops = NULL;
+ else if (strcmp(revprop_word, "revprops") == 0)
+ {
+ SVN_ERR_ASSERT(revprop_items);
+
+ revprops = apr_array_make(pool, revprop_items->nelts,
+ sizeof(char *));
+ for (i = 0; i < revprop_items->nelts; i++)
+ {
+ elt = &APR_ARRAY_IDX(revprop_items, i, svn_ra_svn_item_t);
+ if (elt->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Log revprop entry not a string"));
+ APR_ARRAY_PUSH(revprops, const char *) = elt->u.string->data;
+ }
+ }
+ else
+ return svn_error_createf(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Unknown revprop word '%s' in log command"),
+ revprop_word);
+
+ /* If we got an unspecified number then the user didn't send us anything,
+ so we assume no limit. If it's larger than INT_MAX then someone is
+ messing with us, since we know the svn client libraries will never send
+ us anything that big, so play it safe and default to no limit. */
+ if (limit == SVN_RA_SVN_UNSPECIFIED_NUMBER || limit > INT_MAX)
+ limit = 0;
+
+ full_paths = apr_array_make(pool, paths->nelts, sizeof(const char *));
+ for (i = 0; i < paths->nelts; i++)
+ {
+ elt = &APR_ARRAY_IDX(paths, i, svn_ra_svn_item_t);
+ if (elt->kind != SVN_RA_SVN_STRING)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Log path entry not a string"));
+ full_path = svn_relpath_canonicalize(elt->u.string->data, pool),
+ full_path = svn_fspath__join(b->fs_path->data, full_path, pool);
+ APR_ARRAY_PUSH(full_paths, const char *) = full_path;
+ }
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__log(full_paths, start_rev, end_rev,
+ (int) limit, send_changed_paths,
+ strict_node, include_merged_revisions,
+ revprops, pool)));
+
+ /* Get logs. (Can't report errors back to the client at this point.) */
+ lb.fs_path = b->fs_path->data;
+ lb.conn = conn;
+ lb.stack_depth = 0;
+ err = svn_repos_get_logs4(b->repos, full_paths, start_rev, end_rev,
+ (int) limit, send_changed_paths, strict_node,
+ include_merged_revisions, revprops,
+ authz_check_access_cb_func(b), &ab, log_receiver,
+ &lb, pool);
+
+ write_err = svn_ra_svn__write_word(conn, pool, "done");
+ if (write_err)
+ {
+ svn_error_clear(err);
+ return write_err;
+ }
+ SVN_CMD_ERR(err);
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *path, *full_path;
+ svn_fs_root_t *root;
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Check authorizations */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
+ full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(log_command(b, conn, pool, "check-path %s@%d",
+ svn_path_uri_encode(full_path, pool), rev));
+
+ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
+ SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "w",
+ svn_node_kind_to_word(kind)));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *stat_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_revnum_t rev;
+ const char *path, *full_path, *cdate;
+ svn_fs_root_t *root;
+ svn_dirent_t *dirent;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)", &path, &rev));
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Check authorizations */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
+ full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(log_command(b, conn, pool, "stat %s@%d",
+ svn_path_uri_encode(full_path, pool), rev));
+
+ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
+ SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool));
+
+ /* Need to return the equivalent of "(?l)", since that's what the
+ client is reading. */
+
+ if (dirent == NULL)
+ {
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "()"));
+ return SVN_NO_ERROR;
+ }
+
+ cdate = (dirent->time == (time_t) -1) ? NULL
+ : svn_time_to_cstring(dirent->time, pool);
+
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "((wnbr(?c)(?c)))",
+ svn_node_kind_to_word(dirent->kind),
+ (apr_uint64_t) dirent->size,
+ dirent->has_props, dirent->created_rev,
+ cdate, dirent->last_author));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ svn_error_t *err, *write_err;
+ server_baton_t *b = baton;
+ svn_revnum_t revision;
+ apr_array_header_t *location_revisions, *loc_revs_proto;
+ svn_ra_svn_item_t *elt;
+ int i;
+ const char *relative_path;
+ svn_revnum_t peg_revision;
+ apr_hash_t *fs_locations;
+ const char *abs_path;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ /* Parse the arguments. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crl", &relative_path,
+ &peg_revision,
+ &loc_revs_proto));
+ relative_path = svn_relpath_canonicalize(relative_path, pool);
+
+ abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
+
+ location_revisions = apr_array_make(pool, loc_revs_proto->nelts,
+ sizeof(svn_revnum_t));
+ for (i = 0; i < loc_revs_proto->nelts; i++)
+ {
+ elt = &APR_ARRAY_IDX(loc_revs_proto, i, svn_ra_svn_item_t);
+ if (elt->kind != SVN_RA_SVN_NUMBER)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Get-locations location revisions entry "
+ "not a revision number");
+ revision = (svn_revnum_t)(elt->u.number);
+ APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision;
+ }
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_locations(abs_path, peg_revision,
+ location_revisions, pool)));
+
+ /* All the parameters are fine - let's perform the query against the
+ * repository. */
+
+ /* We store both err and write_err here, so the client will get
+ * the "done" even if there was an error in fetching the results. */
+
+ err = svn_repos_trace_node_locations(b->fs, &fs_locations, abs_path,
+ peg_revision, location_revisions,
+ authz_check_access_cb_func(b), &ab,
+ pool);
+
+ /* Now, write the results to the connection. */
+ if (!err)
+ {
+ if (fs_locations)
+ {
+ apr_hash_index_t *iter;
+
+ for (iter = apr_hash_first(pool, fs_locations); iter;
+ iter = apr_hash_next(iter))
+ {
+ const svn_revnum_t *iter_key = svn__apr_hash_index_key(iter);
+ const char *iter_value = svn__apr_hash_index_val(iter);
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "rc",
+ *iter_key, iter_value));
+ }
+ }
+ }
+
+ write_err = svn_ra_svn__write_word(conn, pool, "done");
+ if (write_err)
+ {
+ svn_error_clear(err);
+ return write_err;
+ }
+ SVN_CMD_ERR(err);
+
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *gls_receiver(svn_location_segment_t *segment,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_svn_conn_t *conn = baton;
+ return svn_ra_svn__write_tuple(conn, pool, "rr(?c)",
+ segment->range_start,
+ segment->range_end,
+ segment->path);
+}
+
+static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_array_header_t *params,
+ void *baton)
+{
+ svn_error_t *err, *write_err;
+ server_baton_t *b = baton;
+ svn_revnum_t peg_revision, start_rev, end_rev;
+ const char *relative_path;
+ const char *abs_path;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ /* Parse the arguments. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)(?r)",
+ &relative_path, &peg_revision,
+ &start_rev, &end_rev));
+ relative_path = svn_relpath_canonicalize(relative_path, pool);
+
+ abs_path = svn_fspath__join(b->fs_path->data, relative_path, pool);
+
+ if (SVN_IS_VALID_REVNUM(start_rev)
+ && SVN_IS_VALID_REVNUM(end_rev)
+ && (end_rev > start_rev))
+ {
+ err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "Get-location-segments end revision must not be "
+ "younger than start revision");
+ return log_fail_and_flush(err, b, conn, pool);
+ }
+
+ if (SVN_IS_VALID_REVNUM(peg_revision)
+ && SVN_IS_VALID_REVNUM(start_rev)
+ && (start_rev > peg_revision))
+ {
+ err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "Get-location-segments start revision must not "
+ "be younger than peg revision");
+ return log_fail_and_flush(err, b, conn, pool);
+ }
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_ERR(log_command(baton, conn, pool, "%s",
+ svn_log__get_location_segments(abs_path, peg_revision,
+ start_rev, end_rev,
+ pool)));
+
+ /* All the parameters are fine - let's perform the query against the
+ * repository. */
+
+ /* We store both err and write_err here, so the client will get
+ * the "done" even if there was an error in fetching the results. */
+
+ err = svn_repos_node_location_segments(b->repos, abs_path,
+ peg_revision, start_rev, end_rev,
+ gls_receiver, (void *)conn,
+ authz_check_access_cb_func(b), &ab,
+ pool);
+ write_err = svn_ra_svn__write_word(conn, pool, "done");
+ if (write_err)
+ {
+ svn_error_clear(err);
+ return write_err;
+ }
+ SVN_CMD_ERR(err);
+
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_write_fn_t. Write LEN bytes starting at DATA to the
+ client as a string. */
+static svn_error_t *svndiff_handler(void *baton, const char *data,
+ apr_size_t *len)
+{
+ file_revs_baton_t *b = baton;
+ svn_string_t str;
+
+ str.data = data;
+ str.len = *len;
+ return svn_ra_svn__write_string(b->conn, b->pool, &str);
+}
+
+/* This implements svn_close_fn_t. Mark the end of the data by writing an
+ empty string to the client. */
+static svn_error_t *svndiff_close_handler(void *baton)
+{
+ file_revs_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__write_cstring(b->conn, b->pool, ""));
+ return SVN_NO_ERROR;
+}
+
+/* This implements the svn_repos_file_rev_handler_t interface. */
+static svn_error_t *file_rev_handler(void *baton, const char *path,
+ svn_revnum_t rev, apr_hash_t *rev_props,
+ svn_boolean_t merged_revision,
+ svn_txdelta_window_handler_t *d_handler,
+ void **d_baton,
+ apr_array_header_t *prop_diffs,
+ apr_pool_t *pool)
+{
+ file_revs_baton_t *frb = baton;
+ svn_stream_t *stream;
+
+ SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "cr(!",
+ path, rev));
+ SVN_ERR(svn_ra_svn__write_proplist(frb->conn, pool, rev_props));
+ SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)(!"));
+ SVN_ERR(write_prop_diffs(frb->conn, pool, prop_diffs));
+ SVN_ERR(svn_ra_svn__write_tuple(frb->conn, pool, "!)b", merged_revision));
+
+ /* Store the pool for the delta stream. */
+ frb->pool = pool;
+
+ /* Prepare for the delta or just write an empty string. */
+ if (d_handler)
+ {
+ stream = svn_stream_create(baton, pool);
+ svn_stream_set_write(stream, svndiff_handler);
+ svn_stream_set_close(stream, svndiff_close_handler);
+
+ /* If the connection does not support SVNDIFF1 or if we don't want to use
+ * compression, use the non-compressing "version 0" implementation */
+ if ( svn_ra_svn_compression_level(frb->conn) > 0
+ && svn_ra_svn_has_capability(frb->conn, SVN_RA_SVN_CAP_SVNDIFF1))
+ svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 1,
+ svn_ra_svn_compression_level(frb->conn), pool);
+ else
+ svn_txdelta_to_svndiff3(d_handler, d_baton, stream, 0,
+ svn_ra_svn_compression_level(frb->conn), pool);
+ }
+ else
+ SVN_ERR(svn_ra_svn__write_cstring(frb->conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_error_t *err, *write_err;
+ file_revs_baton_t frb;
+ svn_revnum_t start_rev, end_rev;
+ const char *path;
+ const char *full_path;
+ apr_uint64_t include_merged_revs_param;
+ svn_boolean_t include_merged_revisions;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ /* Parse arguments. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?r)(?r)?B",
+ &path, &start_rev, &end_rev,
+ &include_merged_revs_param));
+ path = svn_relpath_canonicalize(path, pool);
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ full_path = svn_fspath__join(b->fs_path->data, path, pool);
+
+ if (include_merged_revs_param == SVN_RA_SVN_UNSPECIFIED_NUMBER)
+ include_merged_revisions = FALSE;
+ else
+ include_merged_revisions = (svn_boolean_t) include_merged_revs_param;
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_file_revs(full_path, start_rev, end_rev,
+ include_merged_revisions,
+ pool)));
+
+ frb.conn = conn;
+ frb.pool = NULL;
+
+ err = svn_repos_get_file_revs2(b->repos, full_path, start_rev, end_rev,
+ include_merged_revisions,
+ authz_check_access_cb_func(b), &ab,
+ file_rev_handler, &frb, pool);
+ write_err = svn_ra_svn__write_word(conn, pool, "done");
+ if (write_err)
+ {
+ svn_error_clear(err);
+ return write_err;
+ }
+ SVN_CMD_ERR(err);
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path;
+ const char *comment;
+ const char *full_path;
+ svn_boolean_t steal_lock;
+ svn_revnum_t current_rev;
+ svn_lock_t *l;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b(?r)", &path, &comment,
+ &steal_lock, &current_rev));
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
+ full_path, TRUE));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__lock_one_path(full_path, steal_lock, pool)));
+
+ SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0,
+ 0, /* No expiration time. */
+ current_rev, steal_lock, pool));
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(!", "success"));
+ SVN_ERR(write_lock(conn, pool, l));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!)"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ apr_array_header_t *path_revs;
+ const char *comment;
+ svn_boolean_t steal_lock;
+ int i;
+ apr_pool_t *subpool;
+ const char *path;
+ const char *full_path;
+ svn_revnum_t current_rev;
+ apr_array_header_t *log_paths;
+ svn_lock_t *l;
+ svn_error_t *err = SVN_NO_ERROR, *write_err;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
+ &path_revs));
+
+ subpool = svn_pool_create(pool);
+
+ /* Because we can only send a single auth reply per request, we send
+ a reply before parsing the lock commands. This means an authz
+ access denial will abort the processing of the locks and return
+ an error. */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
+
+ /* Loop through the lock requests. */
+ log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path));
+ for (i = 0; i < path_revs->nelts; ++i)
+ {
+ svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
+ svn_ra_svn_item_t);
+
+ svn_pool_clear(subpool);
+
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Lock requests should be list of lists");
+
+ SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "c(?r)", &path,
+ &current_rev));
+
+ /* Allocate the full_path out of pool so it will survive for use
+ * by operational logging, after this loop. */
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, subpool),
+ pool);
+ APR_ARRAY_PUSH(log_paths, const char *) = full_path;
+
+ if (! lookup_access(pool, b, conn, svn_authz_write, full_path, TRUE))
+ {
+ err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
+ b, conn, pool);
+ break;
+ }
+
+ err = svn_repos_fs_lock(&l, b->repos, full_path,
+ NULL, comment, FALSE,
+ 0, /* No expiration time. */
+ current_rev,
+ steal_lock, subpool);
+
+ if (err)
+ {
+ if (SVN_ERR_IS_LOCK_ERROR(err))
+ {
+ write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
+ svn_error_clear(err);
+ err = NULL;
+ SVN_ERR(write_err);
+ }
+ else
+ break;
+ }
+ else
+ {
+ SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w!", "success"));
+ SVN_ERR(write_lock(conn, subpool, l));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "!"));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__lock(log_paths, steal_lock, pool)));
+
+ /* NOTE: err might contain a fatal locking error from the loop above. */
+ write_err = svn_ra_svn__write_word(conn, pool, "done");
+ if (!write_err)
+ SVN_CMD_ERR(err);
+ svn_error_clear(err);
+ SVN_ERR(write_err);
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path, *token, *full_path;
+ svn_boolean_t break_lock;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c(?c)b", &path, &token,
+ &break_lock));
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ /* Username required unless break_lock was specified. */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write,
+ full_path, ! break_lock));
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__unlock_one_path(full_path, break_lock, pool)));
+
+ SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
+ pool));
+
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ svn_boolean_t break_lock;
+ apr_array_header_t *unlock_tokens;
+ int i;
+ apr_pool_t *subpool;
+ const char *path;
+ const char *full_path;
+ apr_array_header_t *log_paths;
+ const char *token;
+ svn_error_t *err = SVN_NO_ERROR, *write_err;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "bl", &break_lock,
+ &unlock_tokens));
+
+ /* Username required unless break_lock was specified. */
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, ! break_lock));
+
+ subpool = svn_pool_create(pool);
+
+ /* Loop through the unlock requests. */
+ log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path));
+ for (i = 0; i < unlock_tokens->nelts; i++)
+ {
+ svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
+ svn_ra_svn_item_t);
+
+ svn_pool_clear(subpool);
+
+ if (item->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ "Unlock request should be a list of lists");
+
+ SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
+ &token));
+
+ /* Allocate the full_path out of pool so it will survive for use
+ * by operational logging, after this loop. */
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, subpool),
+ pool);
+ APR_ARRAY_PUSH(log_paths, const char *) = full_path;
+
+ if (! lookup_access(subpool, b, conn, svn_authz_write, full_path,
+ ! break_lock))
+ return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
+ error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
+ NULL, NULL,
+ b, conn, pool),
+ NULL);
+
+ err = svn_repos_fs_unlock(b->repos, full_path, token, break_lock,
+ subpool);
+ if (err)
+ {
+ if (SVN_ERR_IS_UNLOCK_ERROR(err))
+ {
+ write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
+ svn_error_clear(err);
+ err = NULL;
+ SVN_ERR(write_err);
+ }
+ else
+ break;
+ }
+ else
+ SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
+ path));
+ }
+
+ svn_pool_destroy(subpool);
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__unlock(log_paths, break_lock, pool)));
+
+ /* NOTE: err might contain a fatal unlocking error from the loop above. */
+ write_err = svn_ra_svn__write_word(conn, pool, "done");
+ if (! write_err)
+ SVN_CMD_ERR(err);
+ svn_error_clear(err);
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path;
+ const char *full_path;
+ svn_lock_t *l;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c", &path));
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ SVN_ERR(must_have_access(conn, pool, b, svn_authz_read,
+ full_path, FALSE));
+ SVN_ERR(log_command(b, conn, pool, "get-lock %s",
+ svn_path_uri_encode(full_path, pool)));
+
+ SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool));
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
+ if (l)
+ SVN_ERR(write_lock(conn, pool, l));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path;
+ const char *full_path;
+ const char *depth_word;
+ svn_depth_t depth;
+ apr_hash_t *locks;
+ apr_hash_index_t *hi;
+ svn_error_t *err;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "c?(?w)", &path, &depth_word));
+
+ depth = depth_word ? svn_depth_from_word(depth_word) : svn_depth_infinity;
+ if ((depth != svn_depth_empty) &&
+ (depth != svn_depth_files) &&
+ (depth != svn_depth_immediates) &&
+ (depth != svn_depth_infinity))
+ {
+ err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ "Invalid 'depth' specified in get-locks request");
+ return log_fail_and_flush(err, b, conn, pool);
+ }
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ SVN_ERR(log_command(b, conn, pool, "get-locks %s",
+ svn_path_uri_encode(full_path, pool)));
+ SVN_CMD_ERR(svn_repos_fs_get_locks2(&locks, b->repos, full_path, depth,
+ authz_check_access_cb_func(b), &ab,
+ pool));
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w((!", "success"));
+ for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
+ {
+ svn_lock_t *l = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(write_lock(conn, pool, l));
+ }
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn,
+ server_baton_t *b,
+ svn_revnum_t rev,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ svn_fs_root_t *root;
+ svn_error_t *err;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ SVN_ERR(log_command(b, conn, pool,
+ svn_log__replay(b->fs_path->data, rev, pool)));
+
+ svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
+
+ err = svn_fs_revision_root(&root, b->fs, rev, pool);
+
+ if (! err)
+ err = svn_repos_replay2(root, b->fs_path->data, low_water_mark,
+ send_deltas, editor, edit_baton,
+ authz_check_access_cb_func(b), &ab, pool);
+
+ if (err)
+ svn_error_clear(editor->abort_edit(edit_baton, pool));
+ SVN_CMD_ERR(err);
+
+ return svn_ra_svn__write_cmd_finish_replay(conn, pool);
+}
+
+static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ svn_revnum_t rev, low_water_mark;
+ svn_boolean_t send_deltas;
+ server_baton_t *b = baton;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrb", &rev, &low_water_mark,
+ &send_deltas));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
+ send_deltas, pool));
+
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ apr_array_header_t *params, void *baton)
+{
+ svn_revnum_t start_rev, end_rev, rev, low_water_mark;
+ svn_boolean_t send_deltas;
+ server_baton_t *b = baton;
+ apr_pool_t *iterpool;
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "rrrb", &start_rev,
+ &end_rev, &low_water_mark,
+ &send_deltas));
+
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+
+ iterpool = svn_pool_create(pool);
+ for (rev = start_rev; rev <= end_rev; rev++)
+ {
+ apr_hash_t *props;
+
+ svn_pool_clear(iterpool);
+
+ SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev,
+ authz_check_access_cb_func(b),
+ &ab,
+ iterpool));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "revprops"));
+ SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, props));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!)"));
+
+ SVN_ERR(replay_one_revision(conn, b, rev, low_water_mark,
+ send_deltas, iterpool));
+
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_deleted_rev(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_array_header_t *params,
+ void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path, *full_path;
+ svn_revnum_t peg_revision;
+ svn_revnum_t end_revision;
+ svn_revnum_t revision_deleted;
+
+ SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "crr",
+ &path, &peg_revision, &end_revision));
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, pool), pool);
+ SVN_ERR(log_command(b, conn, pool, "get-deleted-rev"));
+ SVN_ERR(trivial_auth_request(conn, pool, b));
+ SVN_ERR(svn_repos_deleted_rev(b->fs, full_path, peg_revision, end_revision,
+ &revision_deleted, pool));
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "r", revision_deleted));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_inherited_props(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_array_header_t *params,
+ void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path, *full_path;
+ svn_revnum_t rev;
+ svn_fs_root_t *root;
+ apr_array_header_t *inherited_props;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ authz_baton_t ab;
+
+ ab.server = b;
+ ab.conn = conn;
+
+ /* Parse arguments. */
+ SVN_ERR(svn_ra_svn__parse_tuple(params, iterpool, "c(?r)", &path, &rev));
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, iterpool),
+ pool);
+
+ /* Check authorizations */
+ SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read,
+ full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_inherited_props(full_path, rev,
+ iterpool)));
+
+ /* Fetch the properties and a stream for the contents. */
+ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, iterpool));
+ SVN_CMD_ERR(get_props(NULL, &inherited_props, &ab, root, full_path, pool));
+
+ /* Send successful command response with revision and props. */
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "w(!", "success"));
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(?!"));
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!(c(!",
+ iprop->path_or_url));
+ SVN_ERR(svn_ra_svn__write_proplist(conn, iterpool, iprop->prop_hash));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))!",
+ iprop->path_or_url));
+ }
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, iterpool, "!))"));
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+static const svn_ra_svn_cmd_entry_t main_commands[] = {
+ { "reparent", reparent },
+ { "get-latest-rev", get_latest_rev },
+ { "get-dated-rev", get_dated_rev },
+ { "change-rev-prop", change_rev_prop },
+ { "change-rev-prop2",change_rev_prop2 },
+ { "rev-proplist", rev_proplist },
+ { "rev-prop", rev_prop },
+ { "commit", commit },
+ { "get-file", get_file },
+ { "get-dir", get_dir },
+ { "update", update },
+ { "switch", switch_cmd },
+ { "status", status },
+ { "diff", diff },
+ { "get-mergeinfo", get_mergeinfo },
+ { "log", log_cmd },
+ { "check-path", check_path },
+ { "stat", stat_cmd },
+ { "get-locations", get_locations },
+ { "get-location-segments", get_location_segments },
+ { "get-file-revs", get_file_revs },
+ { "lock", lock },
+ { "lock-many", lock_many },
+ { "unlock", unlock },
+ { "unlock-many", unlock_many },
+ { "get-lock", get_lock },
+ { "get-locks", get_locks },
+ { "replay", replay },
+ { "replay-range", replay_range },
+ { "get-deleted-rev", get_deleted_rev },
+ { "get-iprops", get_inherited_props },
+ { NULL }
+};
+
+/* Skip past the scheme part of a URL, including the tunnel specification
+ * if present. Return NULL if the scheme part is invalid for ra_svn. */
+static const char *skip_scheme_part(const char *url)
+{
+ if (strncmp(url, "svn", 3) != 0)
+ return NULL;
+ url += 3;
+ if (*url == '+')
+ url += strcspn(url, ":");
+ if (strncmp(url, "://", 3) != 0)
+ return NULL;
+ return url + 3;
+}
+
+/* Check that PATH is a valid repository path, meaning it doesn't contain any
+ '..' path segments.
+ NOTE: This is similar to svn_path_is_backpath_present, but that function
+ assumes the path separator is '/'. This function also checks for
+ segments delimited by the local path separator. */
+static svn_boolean_t
+repos_path_valid(const char *path)
+{
+ const char *s = path;
+
+ while (*s)
+ {
+ /* Scan for the end of the segment. */
+ while (*path && *path != '/' && *path != SVN_PATH_LOCAL_SEPARATOR)
+ ++path;
+
+ /* Check for '..'. */
+#ifdef WIN32
+ /* On Windows, don't allow sequences of more than one character
+ consisting of just dots and spaces. Win32 functions treat
+ paths such as ".. " and "......." inconsistently. Make sure
+ no one can escape out of the root. */
+ if (path - s >= 2 && strspn(s, ". ") == (size_t)(path - s))
+ return FALSE;
+#else /* ! WIN32 */
+ if (path - s == 2 && s[0] == '.' && s[1] == '.')
+ return FALSE;
+#endif
+
+ /* Skip all separators. */
+ while (*path && (*path == '/' || *path == SVN_PATH_LOCAL_SEPARATOR))
+ ++path;
+ s = path;
+ }
+
+ return TRUE;
+}
+
+/* Look for the repository given by URL, using ROOT as the virtual
+ * repository root. If we find one, fill in the repos, fs, cfg,
+ * repos_url, and fs_path fields of B. Set B->repos's client
+ * capabilities to CAPABILITIES, which must be at least as long-lived
+ * as POOL, and whose elements are SVN_RA_CAPABILITY_*.
+ */
+static svn_error_t *find_repos(const char *url, const char *root,
+ server_baton_t *b,
+ svn_ra_svn_conn_t *conn,
+ const apr_array_header_t *capabilities,
+ apr_pool_t *pool)
+{
+ const char *path, *full_path, *repos_root, *fs_path, *hooks_env;
+ svn_stringbuf_t *url_buf;
+
+ /* Skip past the scheme and authority part. */
+ path = skip_scheme_part(url);
+ if (path == NULL)
+ return svn_error_createf(SVN_ERR_BAD_URL, NULL,
+ "Non-svn URL passed to svn server: '%s'", url);
+
+ if (! b->vhost)
+ {
+ path = strchr(path, '/');
+ if (path == NULL)
+ path = "";
+ }
+ path = svn_relpath_canonicalize(path, pool);
+ path = svn_path_uri_decode(path, pool);
+
+ /* Ensure that it isn't possible to escape the root by disallowing
+ '..' segments. */
+ if (!repos_path_valid(path))
+ return svn_error_create(SVN_ERR_BAD_FILENAME, NULL,
+ "Couldn't determine repository path");
+
+ /* Join the server-configured root with the client path. */
+ full_path = svn_dirent_join(svn_dirent_canonicalize(root, pool),
+ path, pool);
+
+ /* Search for a repository in the full path. */
+ repos_root = svn_repos_find_root_path(full_path, pool);
+ if (!repos_root)
+ return svn_error_createf(SVN_ERR_RA_SVN_REPOS_NOT_FOUND, NULL,
+ "No repository found in '%s'", url);
+
+ /* Open the repository and fill in b with the resulting information. */
+ SVN_ERR(svn_repos_open2(&b->repos, repos_root, b->fs_config, pool));
+ SVN_ERR(svn_repos_remember_client_capabilities(b->repos, capabilities));
+ b->fs = svn_repos_fs(b->repos);
+ fs_path = full_path + strlen(repos_root);
+ b->fs_path = svn_stringbuf_create(*fs_path ? fs_path : "/", pool);
+ url_buf = svn_stringbuf_create(url, pool);
+ svn_path_remove_components(url_buf,
+ svn_path_component_count(b->fs_path->data));
+ b->repos_url = url_buf->data;
+ b->authz_repos_name = svn_dirent_is_child(root, repos_root, pool);
+ if (b->authz_repos_name == NULL)
+ b->repos_name = svn_dirent_basename(repos_root, pool);
+ else
+ b->repos_name = b->authz_repos_name;
+ b->repos_name = svn_path_uri_encode(b->repos_name, pool);
+
+ /* If the svnserve configuration has not been loaded then load it from the
+ * repository. */
+ if (NULL == b->cfg)
+ {
+ b->base = svn_repos_conf_dir(b->repos, pool);
+
+ SVN_ERR(svn_config_read3(&b->cfg, svn_repos_svnserve_conf(b->repos, pool),
+ FALSE, /* must_exist */
+ FALSE, /* section_names_case_sensitive */
+ FALSE, /* option_names_case_sensitive */
+ pool));
+ SVN_ERR(load_pwdb_config(b, conn, pool));
+ SVN_ERR(load_authz_config(b, conn, repos_root, pool));
+ }
+ /* svnserve.conf has been loaded via the --config-file option so need
+ * to load pwdb and authz. */
+ else
+ {
+ SVN_ERR(load_pwdb_config(b, conn, pool));
+ SVN_ERR(load_authz_config(b, conn, repos_root, pool));
+ }
+
+#ifdef SVN_HAVE_SASL
+ /* Should we use Cyrus SASL? */
+ SVN_ERR(svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL,
+ SVN_CONFIG_OPTION_USE_SASL, FALSE));
+#endif
+
+ /* Use the repository UUID as the default realm. */
+ SVN_ERR(svn_fs_get_uuid(b->fs, &b->realm, pool));
+ svn_config_get(b->cfg, &b->realm, SVN_CONFIG_SECTION_GENERAL,
+ SVN_CONFIG_OPTION_REALM, b->realm);
+
+ /* Make sure it's possible for the client to authenticate. Note
+ that this doesn't take into account any authz configuration read
+ above, because we can't know about access it grants until paths
+ are given by the client. */
+ if (get_access(b, UNAUTHENTICATED) == NO_ACCESS
+ && (get_access(b, AUTHENTICATED) == NO_ACCESS
+ || (!b->tunnel_user && !b->pwdb && !b->use_sasl)))
+ return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ "No access allowed to this repository",
+ b, conn, pool);
+
+ /* Configure hook script environment variables. */
+ svn_config_get(b->cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL,
+ SVN_CONFIG_OPTION_HOOKS_ENV, NULL);
+ if (hooks_env)
+ hooks_env = svn_dirent_internal_style(hooks_env, pool);
+ SVN_ERR(svn_repos_hooks_setenv(b->repos, hooks_env, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Compute the authentication name EXTERNAL should be able to get, if any. */
+static const char *get_tunnel_user(serve_params_t *params, apr_pool_t *pool)
+{
+ /* Only offer EXTERNAL for connections tunneled over a login agent. */
+ if (!params->tunnel)
+ return NULL;
+
+ /* If a tunnel user was provided on the command line, use that. */
+ if (params->tunnel_user)
+ return params->tunnel_user;
+
+ return svn_user_get_name(pool);
+}
+
+static void
+fs_warning_func(void *baton, svn_error_t *err)
+{
+ fs_warning_baton_t *b = baton;
+ log_server_error(err, b->server, b->conn, b->pool);
+ /* TODO: Keep log_pool in the server baton, cleared after every log? */
+ svn_pool_clear(b->pool);
+}
+
+/* Return the normalized repository-relative path for the given PATH
+ * (may be a URL, full path or relative path) and fs contained in the
+ * server baton BATON. Allocate the result in POOL.
+ */
+static const char *
+get_normalized_repo_rel_path(void *baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ server_baton_t *sb = baton;
+
+ if (svn_path_is_url(path))
+ {
+ /* This is a copyfrom URL. */
+ path = svn_uri_skip_ancestor(sb->repos_url, path, pool);
+ path = svn_fspath__canonicalize(path, pool);
+ }
+ else
+ {
+ /* This is a base-relative path. */
+ if ((path)[0] != '/')
+ /* Get an absolute path for use in the FS. */
+ path = svn_fspath__join(sb->fs_path->data, path, pool);
+ }
+
+ return path;
+}
+
+/* Get the revision root for REVISION in fs given by server baton BATON
+ * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM.
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+get_revision_root(svn_fs_root_t **fs_root,
+ void *baton,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ server_baton_t *sb = baton;
+
+ if (!SVN_IS_VALID_REVNUM(revision))
+ SVN_ERR(svn_fs_youngest_rev(&revision, sb->fs, pool));
+
+ SVN_ERR(svn_fs_revision_root(fs_root, sb->fs, revision, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_root_t *fs_root;
+ svn_error_t *err;
+
+ path = get_normalized_repo_rel_path(baton, path, scratch_pool);
+ SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
+
+ err = svn_fs_node_proplist(props, fs_root, path, result_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *props = apr_hash_make(result_pool);
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_root_t *fs_root;
+
+ path = get_normalized_repo_rel_path(baton, path, scratch_pool);
+ SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
+
+ SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *contents;
+ svn_stream_t *file_stream;
+ const char *tmp_filename;
+ svn_fs_root_t *fs_root;
+ svn_error_t *err;
+
+ path = get_normalized_repo_rel_path(baton, path, scratch_pool);
+ SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
+
+ err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+ SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
+
+ *filename = apr_pstrdup(result_pool, tmp_filename);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
+ apr_pool_t *pool)
+{
+ svn_error_t *err, *io_err;
+ apr_uint64_t ver;
+ const char *uuid, *client_url, *ra_client_string, *client_string;
+ apr_array_header_t *caplist, *cap_words;
+ server_baton_t b;
+ fs_warning_baton_t warn_baton;
+ svn_stringbuf_t *cap_log = svn_stringbuf_create_empty(pool);
+
+ b.tunnel = params->tunnel;
+ b.tunnel_user = get_tunnel_user(params, pool);
+ b.read_only = params->read_only;
+ b.user = NULL;
+ b.username_case = params->username_case;
+ b.authz_user = NULL;
+ b.base = params->base;
+ b.cfg = params->cfg;
+ b.pwdb = NULL;
+ b.authzdb = NULL;
+ b.realm = NULL;
+ b.log_file = params->log_file;
+ b.pool = pool;
+ b.use_sasl = FALSE;
+ b.vhost = params->vhost;
+
+ /* construct FS configuration parameters */
+ b.fs_config = apr_hash_make(pool);
+ svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
+ params->cache_txdeltas ? "1" :"0");
+ svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
+ params->cache_fulltexts ? "1" :"0");
+ svn_hash_sets(b.fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
+ params->cache_revprops ? "1" :"0");
+
+ /* Send greeting. We don't support version 1 any more, so we can
+ * send an empty mechlist. */
+ if (params->compression_level > 0)
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwwww)",
+ (apr_uint64_t) 2, (apr_uint64_t) 2,
+ SVN_RA_SVN_CAP_EDIT_PIPELINE,
+ SVN_RA_SVN_CAP_SVNDIFF1,
+ SVN_RA_SVN_CAP_ABSENT_ENTRIES,
+ SVN_RA_SVN_CAP_COMMIT_REVPROPS,
+ SVN_RA_SVN_CAP_DEPTH,
+ SVN_RA_SVN_CAP_LOG_REVPROPS,
+ SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
+ SVN_RA_SVN_CAP_PARTIAL_REPLAY,
+ SVN_RA_SVN_CAP_INHERITED_PROPS,
+ SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
+ SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
+ ));
+ else
+ SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, "nn()(wwwwwwwwww)",
+ (apr_uint64_t) 2, (apr_uint64_t) 2,
+ SVN_RA_SVN_CAP_EDIT_PIPELINE,
+ SVN_RA_SVN_CAP_ABSENT_ENTRIES,
+ SVN_RA_SVN_CAP_COMMIT_REVPROPS,
+ SVN_RA_SVN_CAP_DEPTH,
+ SVN_RA_SVN_CAP_LOG_REVPROPS,
+ SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
+ SVN_RA_SVN_CAP_PARTIAL_REPLAY,
+ SVN_RA_SVN_CAP_INHERITED_PROPS,
+ SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS,
+ SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE
+ ));
+
+ /* Read client response, which we assume to be in version 2 format:
+ * version, capability list, and client URL; then we do an auth
+ * request. */
+ SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "nlc?c(?c)",
+ &ver, &caplist, &client_url,
+ &ra_client_string,
+ &client_string));
+ if (ver != 2)
+ return SVN_NO_ERROR;
+
+ client_url = svn_uri_canonicalize(client_url, pool);
+ SVN_ERR(svn_ra_svn_set_capabilities(conn, caplist));
+
+ /* All released versions of Subversion support edit-pipeline,
+ * so we do not accept connections from clients that do not. */
+ if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
+ return SVN_NO_ERROR;
+
+ /* find_repos needs the capabilities as a list of words (eventually
+ they get handed to the start-commit hook). While we could add a
+ new interface to re-retrieve them from conn and convert the
+ result to a list, it's simpler to just convert caplist by hand
+ here, since we already have it and turning 'svn_ra_svn_item_t's
+ into 'const char *'s is pretty easy.
+
+ We only record capabilities we care about. The client may report
+ more (because it doesn't know what the server cares about). */
+ {
+ int i;
+ svn_ra_svn_item_t *item;
+
+ cap_words = apr_array_make(pool, 1, sizeof(const char *));
+ for (i = 0; i < caplist->nelts; i++)
+ {
+ item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t);
+ /* ra_svn_set_capabilities() already type-checked for us */
+ if (strcmp(item->u.word, SVN_RA_SVN_CAP_MERGEINFO) == 0)
+ {
+ APR_ARRAY_PUSH(cap_words, const char *)
+ = SVN_RA_CAPABILITY_MERGEINFO;
+ }
+ /* Save for operational log. */
+ if (cap_log->len > 0)
+ svn_stringbuf_appendcstr(cap_log, " ");
+ svn_stringbuf_appendcstr(cap_log, item->u.word);
+ }
+ }
+
+ err = find_repos(client_url, params->root, &b, conn, cap_words, pool);
+ if (!err)
+ {
+ SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
+ if (current_access(&b) == NO_ACCESS)
+ err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ "Not authorized for access",
+ &b, conn, pool);
+ }
+ if (err)
+ {
+ log_error(err, b.log_file, svn_ra_svn_conn_remote_host(conn),
+ b.user, NULL, pool);
+ io_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
+ svn_error_clear(err);
+ SVN_ERR(io_err);
+ return svn_ra_svn__flush(conn, pool);
+ }
+
+ /* Log the open. */
+ if (ra_client_string == NULL || ra_client_string[0] == '\0')
+ ra_client_string = "-";
+ else
+ ra_client_string = svn_path_uri_encode(ra_client_string, pool);
+ if (client_string == NULL || client_string[0] == '\0')
+ client_string = "-";
+ else
+ client_string = svn_path_uri_encode(client_string, pool);
+ SVN_ERR(log_command(&b, conn, pool,
+ "open %" APR_UINT64_T_FMT " cap=(%s) %s %s %s",
+ ver, cap_log->data,
+ svn_path_uri_encode(b.fs_path->data, pool),
+ ra_client_string, client_string));
+
+ warn_baton.server = &b;
+ warn_baton.conn = conn;
+ warn_baton.pool = svn_pool_create(pool);
+ svn_fs_set_warning_func(b.fs, fs_warning_func, &warn_baton);
+
+ SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool));
+
+ /* We can't claim mergeinfo capability until we know whether the
+ repository supports mergeinfo (i.e., is not a 1.4 repository),
+ but we don't get the repository url from the client until after
+ we've already sent the initial list of server capabilities. So
+ we list repository capabilities here, in our first response after
+ the client has sent the url. */
+ {
+ svn_boolean_t supports_mergeinfo;
+ SVN_ERR(svn_repos_has_capability(b.repos, &supports_mergeinfo,
+ SVN_REPOS_CAPABILITY_MERGEINFO, pool));
+
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(cc(!",
+ "success", uuid, b.repos_url));
+ if (supports_mergeinfo)
+ SVN_ERR(svn_ra_svn__write_word(conn, pool, SVN_RA_SVN_CAP_MERGEINFO));
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "!))"));
+ }
+
+ /* Set up editor shims. */
+ {
+ svn_delta_shim_callbacks_t *callbacks =
+ svn_delta_shim_callbacks_default(pool);
+
+ callbacks->fetch_base_func = fetch_base_func;
+ callbacks->fetch_props_func = fetch_props_func;
+ callbacks->fetch_kind_func = fetch_kind_func;
+ callbacks->fetch_baton = &b;
+
+ SVN_ERR(svn_ra_svn__set_shim_callbacks(conn, callbacks));
+ }
+
+ return svn_ra_svn__handle_commands2(conn, pool, main_commands, &b, FALSE);
+}
diff --git a/contrib/subversion/subversion/svnserve/server.h b/contrib/subversion/subversion/svnserve/server.h
new file mode 100644
index 0000000..926a96f
--- /dev/null
+++ b/contrib/subversion/subversion/svnserve/server.h
@@ -0,0 +1,186 @@
+/*
+ * svn_server.h : declarations for the svn server
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#ifndef SERVER_H
+#define SERVER_H
+
+#include <apr_network_io.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include "svn_config.h"
+#include "svn_repos.h"
+#include "svn_ra_svn.h"
+
+enum username_case_type { CASE_FORCE_UPPER, CASE_FORCE_LOWER, CASE_ASIS };
+
+typedef struct server_baton_t {
+ svn_repos_t *repos;
+ const char *repos_name; /* URI-encoded name of repository (not for authz) */
+ svn_fs_t *fs; /* For convenience; same as svn_repos_fs(repos) */
+ const char *base; /* Base directory for config files */
+ svn_config_t *cfg; /* Parsed repository svnserve.conf */
+ svn_config_t *pwdb; /* Parsed password database */
+ svn_authz_t *authzdb; /* Parsed authz rules */
+ const char *authz_repos_name; /* The name of the repository for authz */
+ const char *realm; /* Authentication realm */
+ const char *repos_url; /* URL to base of repository */
+ svn_stringbuf_t *fs_path;/* Decoded base in-repos path (w/ leading slash) */
+ apr_hash_t *fs_config; /* Additional FS configuration parameters */
+ const char *user; /* Authenticated username of the user */
+ enum username_case_type username_case; /* Case-normalize the username? */
+ const char *authz_user; /* Username for authz ('user' + 'username_case') */
+ svn_boolean_t tunnel; /* Tunneled through login agent */
+ const char *tunnel_user; /* Allow EXTERNAL to authenticate as this */
+ svn_boolean_t read_only; /* Disallow write access (global flag) */
+ svn_boolean_t use_sasl; /* Use Cyrus SASL for authentication;
+ always false if SVN_HAVE_SASL not defined */
+ apr_file_t *log_file; /* Log filehandle. */
+ svn_boolean_t vhost; /* Use virtual-host-based path to repo. */
+ apr_pool_t *pool;
+} server_baton_t;
+
+enum authn_type { UNAUTHENTICATED, AUTHENTICATED };
+enum access_type { NO_ACCESS, READ_ACCESS, WRITE_ACCESS };
+
+enum access_type get_access(server_baton_t *b, enum authn_type auth);
+
+typedef struct serve_params_t {
+ /* The virtual root of the repositories to serve. The client URL
+ path is interpreted relative to this root and is not allowed to
+ escape it. */
+ const char *root;
+
+ /* True if the connection is tunneled over an ssh-like transport,
+ such that the client may use EXTERNAL to authenticate as the
+ current uid's username. */
+ svn_boolean_t tunnel;
+
+ /* If tunnel is true, overrides the current uid's username as the
+ identity EXTERNAL authenticates as. */
+ const char *tunnel_user;
+
+ /* True if the read-only flag was specified on the command-line,
+ which forces all connections to be read-only. */
+ svn_boolean_t read_only;
+
+ /* The base directory for any relative configuration files. */
+ const char *base;
+
+ /* A parsed repository svnserve configuration file, ala
+ svnserve.conf. If this is NULL, then no configuration file was
+ specified on the command line. If this is non-NULL, then
+ per-repository svnserve.conf are not read. */
+ svn_config_t *cfg;
+
+ /* A filehandle open for writing logs to; possibly NULL. */
+ apr_file_t *log_file;
+
+ /* Username case normalization style. */
+ enum username_case_type username_case;
+
+ /* Enable text delta caching for all FSFS repositories. */
+ svn_boolean_t cache_txdeltas;
+
+ /* Enable full-text caching for all FSFS repositories. */
+ svn_boolean_t cache_fulltexts;
+
+ /* Enable revprop caching for all FSFS repositories. */
+ svn_boolean_t cache_revprops;
+
+ /* Size of the in-memory cache (used by FSFS only). */
+ apr_uint64_t memory_cache_size;
+
+ /* Data compression level to reduce for network traffic. If this
+ is 0, no compression should be applied and the protocol may
+ fall back to svndiff "version 0" bypassing zlib entirely.
+ Defaults to SVN_DELTA_COMPRESSION_LEVEL_DEFAULT. */
+ int compression_level;
+
+ /* Item size up to which we use the zero-copy code path to transmit
+ them over the network. 0 disables that code path. */
+ apr_size_t zero_copy_limit;
+
+ /* Amount of data to send between checks for cancellation requests
+ coming in from the client. */
+ apr_size_t error_check_interval;
+
+ /* Use virtual-host-based path to repo. */
+ svn_boolean_t vhost;
+} serve_params_t;
+
+/* Serve the connection CONN according to the parameters PARAMS. */
+svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params,
+ apr_pool_t *pool);
+
+/* Load the password database for the listening server based on the
+ entries in the SERVER struct.
+
+ SERVER and CONN must not be NULL. The real errors will be logged with
+ SERVER and CONN but return generic errors to the client. */
+svn_error_t *load_pwdb_config(server_baton_t *server,
+ svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool);
+
+/* Load the authz database for the listening server based on the
+ entries in the SERVER struct.
+
+ SERVER and CONN must not be NULL. The real errors will be logged with
+ SERVER and CONN but return generic errors to the client. */
+svn_error_t *load_authz_config(server_baton_t *server,
+ svn_ra_svn_conn_t *conn,
+ const char *repos_root,
+ apr_pool_t *pool);
+
+/* Initialize the Cyrus SASL library. POOL is used for allocations. */
+svn_error_t *cyrus_init(apr_pool_t *pool);
+
+/* Authenticate using Cyrus SASL. */
+svn_error_t *cyrus_auth_request(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ server_baton_t *b,
+ enum access_type required,
+ svn_boolean_t needs_username);
+
+/* Escape SOURCE into DEST where SOURCE is null-terminated and DEST is
+ size BUFLEN DEST will be null-terminated. Returns number of bytes
+ written, including terminating null byte. */
+apr_size_t escape_errorlog_item(char *dest, const char *source,
+ apr_size_t buflen);
+
+/* Log ERR to LOG_FILE if LOG_FILE is not NULL. Include REMOTE_HOST,
+ USER, and REPOS in the log if they are not NULL. Allocate temporary
+ char buffers in POOL (which caller can then clear or dispose of). */
+void
+log_error(svn_error_t *err, apr_file_t *log_file, const char *remote_host,
+ const char *user, const char *repos, apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SERVER_H */
diff --git a/contrib/subversion/subversion/svnserve/svnserve.8 b/contrib/subversion/subversion/svnserve/svnserve.8
new file mode 100644
index 0000000..deea846
--- /dev/null
+++ b/contrib/subversion/subversion/svnserve/svnserve.8
@@ -0,0 +1,138 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svnserve 8
+.SH NAME
+svnserve \- Server for the 'svn' repository access method
+.SH SYNOPSIS
+.TP
+\fBsvnserve\fP [\fIoptions\fP]
+.SH DESCRIPTION
+\fBsvnserve\fP allows access to Subversion repositories using the svn
+network protocol. It can both run as a standalone server process, or
+it can run out of inetd. You must choose a mode of operation when you
+start \fBsvnserve\fP. The following options are recognized:
+.PP
+.TP 5
+\fB\-d\fP, \fB\-\-daemon\fP
+Causes \fBsvnserve\fP to run in daemon mode. \fBsvnserve\fP
+backgrounds itself and accepts and serves TCP/IP connections on the
+svn port (3690, by default).
+.PP
+.TP 5
+\fB\-\-listen\-port\fP=\fIport\fP
+Causes \fBsvnserve\fP to listen on \fIport\fP when run in daemon mode.
+.PP
+.TP 5
+\fB\-\-listen\-host\fP=\fIhost\fP
+Causes \fBsvnserve\fP to listen on the interface specified by \fIhost\fP,
+which may be either a hostname or an IP address.
+.PP
+.TP 5
+\fB\-\-foreground\fP
+When used together with \fB\-d\fP, this option causes \fBsvnserve\fP
+to stay in the foreground. This option is mainly useful for
+debugging.
+.PP
+.TP 5
+\fB\-i\fP, \fB\-\-inetd\fP
+Causes \fBsvnserve\fP to use the stdin/stdout file descriptors, as is
+appropriate for a daemon running out of inetd.
+.PP
+.TP 5
+\fB\-h\fP, \fB\-\-help\fP
+Displays a usage summary and exits.
+.PP
+.TP 5
+\fB\-\-version\fP
+Print \fBsvnserve\fP's version and the repository filesystem
+back-end(s) a particular \fBsvnserve\fP supports.
+.PP
+.TP 5
+\fB\-r\fP \fIroot\fP, \fB\-\-root\fP=\fIroot\fP
+Sets the virtual root for repositories served by \fBsvnserve\fP. The
+pathname in URLs provided by the client will be interpreted relative
+to this root, and will not be allowed to escape this root.
+.PP
+.TP 5
+\fB\-R\fP \fB\-\-read\-only\fP
+Force all write operations through this \fBsvnserve\fP instance to be
+forbidden, overriding all other access policy configuration. Do not
+use this option to set general repository access policy - that is what
+the \fBconf/svnserve.conf\fP repository configuration file is for.
+This option should be used only to restrict access via a certain
+method of invoking \fBsvnserve\fP - for example, to allow write access
+via SSH, but not via a \fBsvnserve\fP daemon, or to create a
+restricted SSH key which is only capable of read access.
+.PP
+.TP 5
+\fB\-t\fP, \fB\-\-tunnel\fP
+Causes \fBsvnserve\fP to run in tunnel mode, which is just like the
+inetd mode of operation (serve one connection over stdin/stdout)
+except that the connection is considered to be pre-authenticated with
+the username of the current uid. This flag is selected by the client
+when running over a tunnel agent.
+.PP
+.TP 5
+\fB\-\-tunnel\-user\fP=\fIusername\fP
+When combined with \fB\-\-tunnel\fP, overrides the pre-authenticated
+username with the supplied \fIusername\fP. This is useful in
+combination with the ssh authorized_key file's "command" directive to
+allow a single system account to be used by multiple committers, each
+having a distinct ssh identity.
+.PP
+.TP 5
+\fB\-T\fP, \fB\-\-threads\fP
+When running in daemon mode, causes \fBsvnserve\fP to spawn a thread
+instead of a process for each connection. The \fBsvnserve\fP process
+still backgrounds itself at startup time.
+.PP
+.TP 5
+\fB\-\-config\-file\fP=\fIfilename\fP
+When specified, \fBsvnserve\fP reads \fIfilename\fP once at program
+startup and caches the \fBsvnserve\fP configuration. The password
+and authorization configurations referenced from \fIfilename\fP will
+be loaded on each connection. \fBsvnserve\fP will not read any
+per-repository \fBconf/svnserve.conf\fP files when this option is
+used. See the \fBsvnserve.conf\fP(5) man page for details of the
+file format for this option.
+.PP
+.TP 5
+\fB\-\-pid\-file\fP=\fIfilename\fP
+When specified, \fBsvnserve\fP will write its process ID to
+\fIfilename\fP.
+.PP
+.TP 5
+\fB\-X\fP, \fB\-\-listen\-once\fP
+Causes \fBsvnserve\fP to accept one connection on the svn port, serve
+it, and exit. This option is mainly useful for debugging.
+.PP
+Unless the \fB\-\-config\-file\fP option was specified on the command
+line, once the client has selected a repository by transmitting its
+URL, \fBsvnserve\fP reads a file named \fBconf/svnserve.conf\fP in the
+repository directory to determine repository-specific settings such as
+what authentication database to use and what authorization policies to
+apply. See the \fBsvnserve.conf\fP(5) man page for details of that
+file format.
+.SH SEE ALSO
+.BR svnserve.conf (5)
diff --git a/contrib/subversion/subversion/svnserve/svnserve.c b/contrib/subversion/subversion/svnserve/svnserve.c
new file mode 100644
index 0000000..7648ea8
--- /dev/null
+++ b/contrib/subversion/subversion/svnserve/svnserve.c
@@ -0,0 +1,1175 @@
+/*
+ * svnserve.c : Main control function for svnserve
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_getopt.h>
+#include <apr_network_io.h>
+#include <apr_signal.h>
+#include <apr_thread_proc.h>
+#include <apr_portable.h>
+
+#include <locale.h>
+
+#include "svn_cmdline.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_ra_svn.h"
+#include "svn_utf.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_opt.h"
+#include "svn_repos.h"
+#include "svn_string.h"
+#include "svn_cache_config.h"
+#include "svn_version.h"
+#include "svn_io.h"
+
+#include "svn_private_config.h"
+
+#include "private/svn_dep_compat.h"
+#include "private/svn_cmdline_private.h"
+#include "private/svn_atomic.h"
+
+#include "winservice.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* For getpid() */
+#endif
+
+#include "server.h"
+
+/* The strategy for handling incoming connections. Some of these may be
+ unavailable due to platform limitations. */
+enum connection_handling_mode {
+ connection_mode_fork, /* Create a process per connection */
+ connection_mode_thread, /* Create a thread per connection */
+ connection_mode_single /* One connection at a time in this process */
+};
+
+/* The mode in which to run svnserve */
+enum run_mode {
+ run_mode_unspecified,
+ run_mode_inetd,
+ run_mode_daemon,
+ run_mode_tunnel,
+ run_mode_listen_once,
+ run_mode_service
+};
+
+#if APR_HAS_FORK
+#if APR_HAS_THREADS
+
+#define CONNECTION_DEFAULT connection_mode_fork
+#define CONNECTION_HAVE_THREAD_OPTION
+
+#else /* ! APR_HAS_THREADS */
+
+#define CONNECTION_DEFAULT connection_mode_fork
+
+#endif /* ! APR_HAS_THREADS */
+#elif APR_HAS_THREADS /* and ! APR_HAS_FORK */
+
+#define CONNECTION_DEFAULT connection_mode_thread
+
+#else /* ! APR_HAS_THREADS and ! APR_HAS_FORK */
+
+#define CONNECTION_DEFAULT connection_mode_single
+
+#endif
+
+
+#ifdef WIN32
+static apr_os_sock_t winservice_svnserve_accept_socket = INVALID_SOCKET;
+
+/* The SCM calls this function (on an arbitrary thread, not the main()
+ thread!) when it wants to stop the service.
+
+ For now, our strategy is to close the listener socket, in order to
+ unblock main() and cause it to exit its accept loop. We cannot use
+ apr_socket_close, because that function deletes the apr_socket_t
+ structure, as well as closing the socket handle. If we called
+ apr_socket_close here, then main() will also call apr_socket_close,
+ resulting in a double-free. This way, we just close the kernel
+ socket handle, which causes the accept() function call to fail,
+ which causes main() to clean up the socket. So, memory gets freed
+ only once.
+
+ This isn't pretty, but it's better than a lot of other options.
+ Currently, there is no "right" way to shut down svnserve.
+
+ We store the OS handle rather than a pointer to the apr_socket_t
+ structure in order to eliminate any possibility of illegal memory
+ access. */
+void winservice_notify_stop(void)
+{
+ if (winservice_svnserve_accept_socket != INVALID_SOCKET)
+ closesocket(winservice_svnserve_accept_socket);
+}
+#endif /* _WIN32 */
+
+
+/* Option codes and descriptions for svnserve.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ *
+ * APR requires that options without abbreviations
+ * have codes greater than 255.
+ */
+#define SVNSERVE_OPT_LISTEN_PORT 256
+#define SVNSERVE_OPT_LISTEN_HOST 257
+#define SVNSERVE_OPT_FOREGROUND 258
+#define SVNSERVE_OPT_TUNNEL_USER 259
+#define SVNSERVE_OPT_VERSION 260
+#define SVNSERVE_OPT_PID_FILE 261
+#define SVNSERVE_OPT_SERVICE 262
+#define SVNSERVE_OPT_CONFIG_FILE 263
+#define SVNSERVE_OPT_LOG_FILE 264
+#define SVNSERVE_OPT_CACHE_TXDELTAS 265
+#define SVNSERVE_OPT_CACHE_FULLTEXTS 266
+#define SVNSERVE_OPT_CACHE_REVPROPS 267
+#define SVNSERVE_OPT_SINGLE_CONN 268
+#define SVNSERVE_OPT_CLIENT_SPEED 269
+#define SVNSERVE_OPT_VIRTUAL_HOST 270
+
+static const apr_getopt_option_t svnserve__options[] =
+ {
+ {"daemon", 'd', 0, N_("daemon mode")},
+ {"inetd", 'i', 0, N_("inetd mode")},
+ {"tunnel", 't', 0, N_("tunnel mode")},
+ {"listen-once", 'X', 0, N_("listen-once mode (useful for debugging)")},
+#ifdef WIN32
+ {"service", SVNSERVE_OPT_SERVICE, 0,
+ N_("Windows service mode (Service Control Manager)")},
+#endif
+ {"root", 'r', 1, N_("root of directory to serve")},
+ {"read-only", 'R', 0,
+ N_("force read only, overriding repository config file")},
+ {"config-file", SVNSERVE_OPT_CONFIG_FILE, 1,
+ N_("read configuration from file ARG")},
+ {"listen-port", SVNSERVE_OPT_LISTEN_PORT, 1,
+#ifdef WIN32
+ N_("listen port. The default port is 3690.\n"
+ " "
+ "[mode: daemon, service, listen-once]")},
+#else
+ N_("listen port. The default port is 3690.\n"
+ " "
+ "[mode: daemon, listen-once]")},
+#endif
+ {"listen-host", SVNSERVE_OPT_LISTEN_HOST, 1,
+#ifdef WIN32
+ N_("listen hostname or IP address\n"
+ " "
+ "By default svnserve listens on all addresses.\n"
+ " "
+ "[mode: daemon, service, listen-once]")},
+#else
+ N_("listen hostname or IP address\n"
+ " "
+ "By default svnserve listens on all addresses.\n"
+ " "
+ "[mode: daemon, listen-once]")},
+#endif
+ {"prefer-ipv6", '6', 0,
+ N_("prefer IPv6 when resolving the listen hostname\n"
+ " "
+ "[IPv4 is preferred by default. Using IPv4 and IPv6\n"
+ " "
+ "at the same time is not supported in daemon mode.\n"
+ " "
+ "Use inetd mode or tunnel mode if you need this.]")},
+ {"compression", 'c', 1,
+ N_("compression level to use for network transmissions\n"
+ " "
+ "[0 .. no compression, 5 .. default, \n"
+ " "
+ " 9 .. maximum compression]")},
+ {"memory-cache-size", 'M', 1,
+ N_("size of the extra in-memory cache in MB used to\n"
+ " "
+ "minimize redundant operations.\n"
+ " "
+ "Default is 128 for threaded and 16 for non-\n"
+ " "
+ "threaded mode.\n"
+ " "
+ "[used for FSFS repositories only]")},
+ {"cache-txdeltas", SVNSERVE_OPT_CACHE_TXDELTAS, 1,
+ N_("enable or disable caching of deltas between older\n"
+ " "
+ "revisions.\n"
+ " "
+ "Default is no.\n"
+ " "
+ "[used for FSFS repositories only]")},
+ {"cache-fulltexts", SVNSERVE_OPT_CACHE_FULLTEXTS, 1,
+ N_("enable or disable caching of file contents\n"
+ " "
+ "Default is yes.\n"
+ " "
+ "[used for FSFS repositories only]")},
+ {"cache-revprops", SVNSERVE_OPT_CACHE_REVPROPS, 1,
+ N_("enable or disable caching of revision properties.\n"
+ " "
+ "Consult the documentation before activating this.\n"
+ " "
+ "Default is no.\n"
+ " "
+ "[used for FSFS repositories only]")},
+ {"client-speed", SVNSERVE_OPT_CLIENT_SPEED, 1,
+ N_("Optimize network handling based on the assumption\n"
+ " "
+ "that most clients are connected with a bitrate of\n"
+ " "
+ "ARG Mbit/s.\n"
+ " "
+ "Default is 0 (optimizations disabled).")},
+#ifdef CONNECTION_HAVE_THREAD_OPTION
+ /* ### Making the assumption here that WIN32 never has fork and so
+ * ### this option never exists when --service exists. */
+ {"threads", 'T', 0, N_("use threads instead of fork "
+ "[mode: daemon]")},
+#endif
+ {"foreground", SVNSERVE_OPT_FOREGROUND, 0,
+ N_("run in foreground (useful for debugging)\n"
+ " "
+ "[mode: daemon]")},
+ {"single-thread", SVNSERVE_OPT_SINGLE_CONN, 0,
+ N_("handle one connection at a time in the parent process\n"
+ " "
+ "(useful for debugging)")},
+ {"log-file", SVNSERVE_OPT_LOG_FILE, 1,
+ N_("svnserve log file")},
+ {"pid-file", SVNSERVE_OPT_PID_FILE, 1,
+#ifdef WIN32
+ N_("write server process ID to file ARG\n"
+ " "
+ "[mode: daemon, listen-once, service]")},
+#else
+ N_("write server process ID to file ARG\n"
+ " "
+ "[mode: daemon, listen-once]")},
+#endif
+ {"tunnel-user", SVNSERVE_OPT_TUNNEL_USER, 1,
+ N_("tunnel username (default is current uid's name)\n"
+ " "
+ "[mode: tunnel]")},
+ {"help", 'h', 0, N_("display this help")},
+ {"virtual-host", SVNSERVE_OPT_VIRTUAL_HOST, 0,
+ N_("virtual host mode (look for repo in directory\n"
+ " "
+ "of provided hostname)")},
+ {"version", SVNSERVE_OPT_VERSION, 0,
+ N_("show program version information")},
+ {"quiet", 'q', 0,
+ N_("no progress (only errors) to stderr")},
+ {0, 0, 0, 0}
+ };
+
+
+static void usage(const char *progname, apr_pool_t *pool)
+{
+ if (!progname)
+ progname = "svnserve";
+
+ svn_error_clear(svn_cmdline_fprintf(stderr, pool,
+ _("Type '%s --help' for usage.\n"),
+ progname));
+ exit(1);
+}
+
+static void help(apr_pool_t *pool)
+{
+ apr_size_t i;
+
+#ifdef WIN32
+ svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X "
+ "| --service] [options]\n"
+ "\n"
+ "Valid options:\n"),
+ stdout, pool));
+#else
+ svn_error_clear(svn_cmdline_fputs(_("usage: svnserve [-d | -i | -t | -X] "
+ "[options]\n"
+ "\n"
+ "Valid options:\n"),
+ stdout, pool));
+#endif
+ for (i = 0; svnserve__options[i].name && svnserve__options[i].optch; i++)
+ {
+ const char *optstr;
+ svn_opt_format_option(&optstr, svnserve__options + i, TRUE, pool);
+ svn_error_clear(svn_cmdline_fprintf(stdout, pool, " %s\n", optstr));
+ }
+ svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
+ exit(0);
+}
+
+static svn_error_t * version(svn_boolean_t quiet, apr_pool_t *pool)
+{
+ const char *fs_desc_start
+ = _("The following repository back-end (FS) modules are available:\n\n");
+
+ svn_stringbuf_t *version_footer;
+
+ version_footer = svn_stringbuf_create(fs_desc_start, pool);
+ SVN_ERR(svn_fs_print_modules(version_footer, pool));
+
+#ifdef SVN_HAVE_SASL
+ svn_stringbuf_appendcstr(version_footer,
+ _("\nCyrus SASL authentication is available.\n"));
+#endif
+
+ return svn_opt_print_help4(NULL, "svnserve", TRUE, quiet, FALSE,
+ version_footer->data,
+ NULL, NULL, NULL, NULL, NULL, pool);
+}
+
+
+#if APR_HAS_FORK
+static void sigchld_handler(int signo)
+{
+ /* Nothing to do; we just need to interrupt the accept(). */
+}
+#endif
+
+/* Redirect stdout to stderr. ARG is the pool.
+ *
+ * In tunnel or inetd mode, we don't want hook scripts corrupting the
+ * data stream by sending data to stdout, so we need to redirect
+ * stdout somewhere else. Sending it to stderr is acceptable; sending
+ * it to /dev/null is another option, but apr doesn't provide a way to
+ * do that without also detaching from the controlling terminal.
+ */
+static apr_status_t redirect_stdout(void *arg)
+{
+ apr_pool_t *pool = arg;
+ apr_file_t *out_file, *err_file;
+ apr_status_t apr_err;
+
+ if ((apr_err = apr_file_open_stdout(&out_file, pool)))
+ return apr_err;
+ if ((apr_err = apr_file_open_stderr(&err_file, pool)))
+ return apr_err;
+ return apr_file_dup2(out_file, err_file, pool);
+}
+
+#if APR_HAS_THREADS
+/* The pool passed to apr_thread_create can only be released when both
+
+ A: the call to apr_thread_create has returned to the calling thread
+ B: the new thread has started running and reached apr_thread_start_t
+
+ So we set the atomic counter to 2 then both the calling thread and
+ the new thread decrease it and when it reaches 0 the pool can be
+ released. */
+struct shared_pool_t {
+ svn_atomic_t count;
+ apr_pool_t *pool;
+};
+
+static struct shared_pool_t *
+attach_shared_pool(apr_pool_t *pool)
+{
+ struct shared_pool_t *shared = apr_palloc(pool, sizeof(struct shared_pool_t));
+
+ shared->pool = pool;
+ svn_atomic_set(&shared->count, 2);
+
+ return shared;
+}
+
+static void
+release_shared_pool(struct shared_pool_t *shared)
+{
+ if (svn_atomic_dec(&shared->count) == 0)
+ svn_pool_destroy(shared->pool);
+}
+#endif
+
+/* "Arguments" passed from the main thread to the connection thread */
+struct serve_thread_t {
+ svn_ra_svn_conn_t *conn;
+ serve_params_t *params;
+ struct shared_pool_t *shared_pool;
+};
+
+#if APR_HAS_THREADS
+static void * APR_THREAD_FUNC serve_thread(apr_thread_t *tid, void *data)
+{
+ struct serve_thread_t *d = data;
+
+ svn_error_clear(serve(d->conn, d->params, d->shared_pool->pool));
+ release_shared_pool(d->shared_pool);
+
+ return NULL;
+}
+#endif
+
+/* Write the PID of the current process as a decimal number, followed by a
+ newline to the file FILENAME, using POOL for temporary allocations. */
+static svn_error_t *write_pid_file(const char *filename, apr_pool_t *pool)
+{
+ apr_file_t *file;
+ const char *contents = apr_psprintf(pool, "%" APR_PID_T_FMT "\n",
+ getpid());
+
+ SVN_ERR(svn_io_file_open(&file, filename,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE,
+ APR_OS_DEFAULT, pool));
+ SVN_ERR(svn_io_file_write_full(file, contents, strlen(contents), NULL,
+ pool));
+
+ SVN_ERR(svn_io_file_close(file, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Version compatibility check */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_repos", svn_repos_version },
+ { "svn_fs", svn_fs_version },
+ { "svn_delta", svn_delta_version },
+ { "svn_ra_svn", svn_ra_svn_version },
+ { NULL, NULL }
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+
+int main(int argc, const char *argv[])
+{
+ enum run_mode run_mode = run_mode_unspecified;
+ svn_boolean_t foreground = FALSE;
+ apr_socket_t *sock, *usock;
+ apr_file_t *in_file, *out_file;
+ apr_sockaddr_t *sa;
+ apr_pool_t *pool;
+ apr_pool_t *connection_pool;
+ svn_error_t *err;
+ apr_getopt_t *os;
+ int opt;
+ serve_params_t params;
+ const char *arg;
+ apr_status_t status;
+ svn_ra_svn_conn_t *conn;
+ apr_proc_t proc;
+#if APR_HAS_THREADS
+ apr_threadattr_t *tattr;
+ apr_thread_t *tid;
+ struct shared_pool_t *shared_pool;
+
+ struct serve_thread_t *thread_data;
+#endif
+ enum connection_handling_mode handling_mode = CONNECTION_DEFAULT;
+ apr_uint16_t port = SVN_RA_SVN_PORT;
+ const char *host = NULL;
+ int family = APR_INET;
+ apr_int32_t sockaddr_info_flags = 0;
+#if APR_HAVE_IPV6
+ svn_boolean_t prefer_v6 = FALSE;
+#endif
+ svn_boolean_t quiet = FALSE;
+ svn_boolean_t is_version = FALSE;
+ int mode_opt_count = 0;
+ int handling_opt_count = 0;
+ const char *config_filename = NULL;
+ const char *pid_filename = NULL;
+ const char *log_filename = NULL;
+ svn_node_kind_t kind;
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svnserve", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. */
+ pool = svn_pool_create(NULL);
+
+#ifdef SVN_HAVE_SASL
+ SVN_INT_ERR(cyrus_init(pool));
+#endif
+
+ /* Check library versions */
+ err = check_lib_versions();
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+
+ /* Initialize the FS library. */
+ err = svn_fs_initialize(pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+
+ err = svn_cmdline__getopt_init(&os, argc, argv, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+
+ params.root = "/";
+ params.tunnel = FALSE;
+ params.tunnel_user = NULL;
+ params.read_only = FALSE;
+ params.base = NULL;
+ params.cfg = NULL;
+ params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
+ params.log_file = NULL;
+ params.vhost = FALSE;
+ params.username_case = CASE_ASIS;
+ params.memory_cache_size = (apr_uint64_t)-1;
+ params.cache_fulltexts = TRUE;
+ params.cache_txdeltas = FALSE;
+ params.cache_revprops = FALSE;
+ params.zero_copy_limit = 0;
+ params.error_check_interval = 4096;
+
+ while (1)
+ {
+ status = apr_getopt_long(os, svnserve__options, &opt, &arg);
+ if (APR_STATUS_IS_EOF(status))
+ break;
+ if (status != APR_SUCCESS)
+ usage(argv[0], pool);
+ switch (opt)
+ {
+ case '6':
+#if APR_HAVE_IPV6
+ prefer_v6 = TRUE;
+#endif
+ /* ### Maybe error here if we don't have IPV6 support? */
+ break;
+
+ case 'h':
+ help(pool);
+ break;
+
+ case 'q':
+ quiet = TRUE;
+ break;
+
+ case SVNSERVE_OPT_VERSION:
+ is_version = TRUE;
+ break;
+
+ case 'd':
+ if (run_mode != run_mode_daemon)
+ {
+ run_mode = run_mode_daemon;
+ mode_opt_count++;
+ }
+ break;
+
+ case SVNSERVE_OPT_FOREGROUND:
+ foreground = TRUE;
+ break;
+
+ case SVNSERVE_OPT_SINGLE_CONN:
+ handling_mode = connection_mode_single;
+ handling_opt_count++;
+ break;
+
+ case 'i':
+ if (run_mode != run_mode_inetd)
+ {
+ run_mode = run_mode_inetd;
+ mode_opt_count++;
+ }
+ break;
+
+ case SVNSERVE_OPT_LISTEN_PORT:
+ {
+ apr_uint64_t val;
+
+ err = svn_cstring_strtoui64(&val, arg, 0, APR_UINT16_MAX, 10);
+ if (err)
+ return svn_cmdline_handle_exit_error(
+ svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
+ _("Invalid port '%s'"), arg),
+ pool, "svnserve: ");
+ port = (apr_uint16_t)val;
+ }
+ break;
+
+ case SVNSERVE_OPT_LISTEN_HOST:
+ host = arg;
+ break;
+
+ case 't':
+ if (run_mode != run_mode_tunnel)
+ {
+ run_mode = run_mode_tunnel;
+ mode_opt_count++;
+ }
+ break;
+
+ case SVNSERVE_OPT_TUNNEL_USER:
+ params.tunnel_user = arg;
+ break;
+
+ case 'X':
+ if (run_mode != run_mode_listen_once)
+ {
+ run_mode = run_mode_listen_once;
+ mode_opt_count++;
+ }
+ break;
+
+ case 'r':
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&params.root, arg, pool));
+
+ err = svn_io_check_resolved_path(params.root, &kind, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ if (kind != svn_node_dir)
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf
+ (stderr, pool,
+ _("svnserve: Root path '%s' does not exist "
+ "or is not a directory.\n"), params.root));
+ return EXIT_FAILURE;
+ }
+
+ params.root = svn_dirent_internal_style(params.root, pool);
+ SVN_INT_ERR(svn_dirent_get_absolute(&params.root, params.root, pool));
+ break;
+
+ case 'R':
+ params.read_only = TRUE;
+ break;
+
+ case 'T':
+ handling_mode = connection_mode_thread;
+ handling_opt_count++;
+ break;
+
+ case 'c':
+ params.compression_level = atoi(arg);
+ if (params.compression_level < SVN_DELTA_COMPRESSION_LEVEL_NONE)
+ params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
+ if (params.compression_level > SVN_DELTA_COMPRESSION_LEVEL_MAX)
+ params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_MAX;
+ break;
+
+ case 'M':
+ params.memory_cache_size = 0x100000 * apr_strtoi64(arg, NULL, 0);
+ break;
+
+ case SVNSERVE_OPT_CACHE_TXDELTAS:
+ params.cache_txdeltas
+ = svn_tristate__from_word(arg) == svn_tristate_true;
+ break;
+
+ case SVNSERVE_OPT_CACHE_FULLTEXTS:
+ params.cache_fulltexts
+ = svn_tristate__from_word(arg) == svn_tristate_true;
+ break;
+
+ case SVNSERVE_OPT_CACHE_REVPROPS:
+ params.cache_revprops
+ = svn_tristate__from_word(arg) == svn_tristate_true;
+ break;
+
+ case SVNSERVE_OPT_CLIENT_SPEED:
+ {
+ apr_size_t bandwidth = (apr_size_t)apr_strtoi64(arg, NULL, 0);
+
+ /* for slower clients, don't try anything fancy */
+ if (bandwidth >= 1000)
+ {
+ /* block other clients for at most 1 ms (at full bandwidth).
+ Note that the send buffer is 16kB anyways. */
+ params.zero_copy_limit = bandwidth * 120;
+
+ /* check for aborted connections at the same rate */
+ params.error_check_interval = bandwidth * 120;
+ }
+ }
+ break;
+
+#ifdef WIN32
+ case SVNSERVE_OPT_SERVICE:
+ if (run_mode != run_mode_service)
+ {
+ run_mode = run_mode_service;
+ mode_opt_count++;
+ }
+ break;
+#endif
+
+ case SVNSERVE_OPT_CONFIG_FILE:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&config_filename, arg, pool));
+ config_filename = svn_dirent_internal_style(config_filename, pool);
+ SVN_INT_ERR(svn_dirent_get_absolute(&config_filename, config_filename,
+ pool));
+ break;
+
+ case SVNSERVE_OPT_PID_FILE:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&pid_filename, arg, pool));
+ pid_filename = svn_dirent_internal_style(pid_filename, pool);
+ SVN_INT_ERR(svn_dirent_get_absolute(&pid_filename, pid_filename,
+ pool));
+ break;
+
+ case SVNSERVE_OPT_VIRTUAL_HOST:
+ params.vhost = TRUE;
+ break;
+
+ case SVNSERVE_OPT_LOG_FILE:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool));
+ log_filename = svn_dirent_internal_style(log_filename, pool);
+ SVN_INT_ERR(svn_dirent_get_absolute(&log_filename, log_filename,
+ pool));
+ break;
+
+ }
+ }
+
+ if (is_version)
+ {
+ SVN_INT_ERR(version(quiet, pool));
+ exit(0);
+ }
+
+ if (os->ind != argc)
+ usage(argv[0], pool);
+
+ if (mode_opt_count != 1)
+ {
+ svn_error_clear(svn_cmdline_fputs(
+#ifdef WIN32
+ _("You must specify exactly one of -d, -i, -t, "
+ "--service or -X.\n"),
+#else
+ _("You must specify exactly one of -d, -i, -t or -X.\n"),
+#endif
+ stderr, pool));
+ usage(argv[0], pool);
+ }
+
+ if (handling_opt_count > 1)
+ {
+ svn_error_clear(svn_cmdline_fputs(
+ _("You may only specify one of -T or --single-thread\n"),
+ stderr, pool));
+ usage(argv[0], pool);
+ }
+
+ /* If a configuration file is specified, load it and any referenced
+ * password and authorization files. */
+ if (config_filename)
+ {
+ params.base = svn_dirent_dirname(config_filename, pool);
+
+ SVN_INT_ERR(svn_config_read3(&params.cfg, config_filename,
+ TRUE, /* must_exist */
+ FALSE, /* section_names_case_sensitive */
+ FALSE, /* option_names_case_sensitive */
+ pool));
+ }
+
+ if (log_filename)
+ SVN_INT_ERR(svn_io_file_open(&params.log_file, log_filename,
+ APR_WRITE | APR_CREATE | APR_APPEND,
+ APR_OS_DEFAULT, pool));
+
+ if (params.tunnel_user && run_mode != run_mode_tunnel)
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf
+ (stderr, pool,
+ _("Option --tunnel-user is only valid in tunnel mode.\n")));
+ exit(1);
+ }
+
+ if (run_mode == run_mode_inetd || run_mode == run_mode_tunnel)
+ {
+ params.tunnel = (run_mode == run_mode_tunnel);
+ apr_pool_cleanup_register(pool, pool, apr_pool_cleanup_null,
+ redirect_stdout);
+ status = apr_file_open_stdin(&in_file, pool);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't open stdin"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+ status = apr_file_open_stdout(&out_file, pool);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't open stdout"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+ /* Use a subpool for the connection to ensure that if SASL is used
+ * the pool cleanup handlers that call sasl_dispose() (connection_pool)
+ * and sasl_done() (pool) are run in the right order. See issue #3664. */
+ connection_pool = svn_pool_create(pool);
+ conn = svn_ra_svn_create_conn3(NULL, in_file, out_file,
+ params.compression_level,
+ params.zero_copy_limit,
+ params.error_check_interval,
+ connection_pool);
+ svn_error_clear(serve(conn, &params, connection_pool));
+ exit(0);
+ }
+
+#ifdef WIN32
+ /* If svnserve needs to run as a Win32 service, then we need to
+ coordinate with the Service Control Manager (SCM) before
+ continuing. This function call registers the svnserve.exe
+ process with the SCM, waits for the "start" command from the SCM
+ (which will come very quickly), and confirms that those steps
+ succeeded.
+
+ After this call succeeds, the service is free to run. At some
+ point in the future, the SCM will send a message to the service,
+ requesting that it stop. This is translated into a call to
+ winservice_notify_stop(). The service is then responsible for
+ cleanly terminating.
+
+ We need to do this before actually starting the service logic
+ (opening files, sockets, etc.) because the SCM wants you to
+ connect *first*, then do your service-specific logic. If the
+ service process takes too long to connect to the SCM, then the
+ SCM will decide that the service is busted, and will give up on
+ it.
+ */
+ if (run_mode == run_mode_service)
+ {
+ err = winservice_start();
+ if (err)
+ {
+ svn_handle_error2(err, stderr, FALSE, "svnserve: ");
+
+ /* This is the most common error. It means the user started
+ svnserve from a shell, and specified the --service
+ argument. svnserve cannot be started, as a service, in
+ this way. The --service argument is valid only valid if
+ svnserve is started by the SCM. */
+ if (err->apr_err ==
+ APR_FROM_OS_ERROR(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT))
+ {
+ svn_error_clear(svn_cmdline_fprintf(stderr, pool,
+ _("svnserve: The --service flag is only valid if the"
+ " process is started by the Service Control Manager.\n")));
+ }
+
+ svn_error_clear(err);
+ exit(1);
+ }
+
+ /* The service is now in the "starting" state. Before the SCM will
+ consider the service "started", this thread must call the
+ winservice_running() function. */
+ }
+#endif /* WIN32 */
+
+ /* Make sure we have IPV6 support first before giving apr_sockaddr_info_get
+ APR_UNSPEC, because it may give us back an IPV6 address even if we can't
+ create IPV6 sockets. */
+
+#if APR_HAVE_IPV6
+#ifdef MAX_SECS_TO_LINGER
+ /* ### old APR interface */
+ status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, pool);
+#else
+ status = apr_socket_create(&sock, APR_INET6, SOCK_STREAM, APR_PROTO_TCP,
+ pool);
+#endif
+ if (status == 0)
+ {
+ apr_socket_close(sock);
+ family = APR_UNSPEC;
+
+ if (prefer_v6)
+ {
+ if (host == NULL)
+ host = "::";
+ sockaddr_info_flags = APR_IPV6_ADDR_OK;
+ }
+ else
+ {
+ if (host == NULL)
+ host = "0.0.0.0";
+ sockaddr_info_flags = APR_IPV4_ADDR_OK;
+ }
+ }
+#endif
+
+ status = apr_sockaddr_info_get(&sa, host, family, port,
+ sockaddr_info_flags, pool);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't get address info"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+
+#ifdef MAX_SECS_TO_LINGER
+ /* ### old APR interface */
+ status = apr_socket_create(&sock, sa->family, SOCK_STREAM, pool);
+#else
+ status = apr_socket_create(&sock, sa->family, SOCK_STREAM, APR_PROTO_TCP,
+ pool);
+#endif
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't create server socket"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+ /* Prevents "socket in use" errors when server is killed and quickly
+ * restarted. */
+ apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1);
+
+ status = apr_socket_bind(sock, sa);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't bind server socket"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+ apr_socket_listen(sock, 7);
+
+#if APR_HAS_FORK
+ if (run_mode != run_mode_listen_once && !foreground)
+ apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
+
+ apr_signal(SIGCHLD, sigchld_handler);
+#endif
+
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it, otherwise
+ * working with large files when compiled against an APR that doesn't have
+ * large file support will crash the program, which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ if (pid_filename)
+ SVN_INT_ERR(write_pid_file(pid_filename, pool));
+
+#ifdef WIN32
+ status = apr_os_sock_get(&winservice_svnserve_accept_socket, sock);
+ if (status)
+ winservice_svnserve_accept_socket = INVALID_SOCKET;
+
+ /* At this point, the service is "running". Notify the SCM. */
+ if (run_mode == run_mode_service)
+ winservice_running();
+#endif
+
+ /* Configure FS caches for maximum efficiency with svnserve.
+ * For pre-forked (i.e. multi-processed) mode of operation,
+ * keep the per-process caches smaller than the default.
+ * Also, apply the respective command line parameters, if given. */
+ {
+ svn_cache_config_t settings = *svn_cache_config_get();
+
+ if (params.memory_cache_size != -1)
+ settings.cache_size = params.memory_cache_size;
+
+ settings.single_threaded = TRUE;
+ if (handling_mode == connection_mode_thread)
+ {
+#if APR_HAS_THREADS
+ settings.single_threaded = FALSE;
+#else
+ /* No requests will be processed at all
+ * (see "switch (handling_mode)" code further down).
+ * But if they were, some other synchronization code
+ * would need to take care of securing integrity of
+ * APR-based structures. That would include our caches.
+ */
+#endif
+ }
+
+ svn_cache_config_set(&settings);
+ }
+
+ while (1)
+ {
+#ifdef WIN32
+ if (winservice_is_stopping())
+ return ERROR_SUCCESS;
+#endif
+
+ /* Non-standard pool handling. The main thread never blocks to join
+ the connection threads so it cannot clean up after each one. So
+ separate pools that can be cleared at thread exit are used. */
+
+ connection_pool
+ = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ status = apr_socket_accept(&usock, sock, connection_pool);
+ if (handling_mode == connection_mode_fork)
+ {
+ /* Collect any zombie child processes. */
+ while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT,
+ connection_pool) == APR_CHILD_DONE)
+ ;
+ }
+ if (APR_STATUS_IS_EINTR(status)
+ || APR_STATUS_IS_ECONNABORTED(status)
+ || APR_STATUS_IS_ECONNRESET(status))
+ {
+ svn_pool_destroy(connection_pool);
+ continue;
+ }
+ if (status)
+ {
+ err = svn_error_wrap_apr
+ (status, _("Can't accept client connection"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnserve: ");
+ }
+
+ /* Enable TCP keep-alives on the socket so we time out when
+ * the connection breaks due to network-layer problems.
+ * If the peer has dropped the connection due to a network partition
+ * or a crash, or if the peer no longer considers the connection
+ * valid because we are behind a NAT and our public IP has changed,
+ * it will respond to the keep-alive probe with a RST instead of an
+ * acknowledgment segment, which will cause svn to abort the session
+ * even while it is currently blocked waiting for data from the peer. */
+ status = apr_socket_opt_set(usock, APR_SO_KEEPALIVE, 1);
+ if (status)
+ {
+ /* It's not a fatal error if we cannot enable keep-alives. */
+ }
+
+ conn = svn_ra_svn_create_conn3(usock, NULL, NULL,
+ params.compression_level,
+ params.zero_copy_limit,
+ params.error_check_interval,
+ connection_pool);
+
+ if (run_mode == run_mode_listen_once)
+ {
+ err = serve(conn, &params, connection_pool);
+
+ if (err)
+ svn_handle_error2(err, stdout, FALSE, "svnserve: ");
+ svn_error_clear(err);
+
+ apr_socket_close(usock);
+ apr_socket_close(sock);
+ exit(0);
+ }
+
+ switch (handling_mode)
+ {
+ case connection_mode_fork:
+#if APR_HAS_FORK
+ status = apr_proc_fork(&proc, connection_pool);
+ if (status == APR_INCHILD)
+ {
+ apr_socket_close(sock);
+ err = serve(conn, &params, connection_pool);
+ log_error(err, params.log_file,
+ svn_ra_svn_conn_remote_host(conn),
+ NULL, NULL, /* user, repos */
+ connection_pool);
+ svn_error_clear(err);
+ apr_socket_close(usock);
+ exit(0);
+ }
+ else if (status == APR_INPARENT)
+ {
+ apr_socket_close(usock);
+ }
+ else
+ {
+ err = svn_error_wrap_apr(status, "apr_proc_fork");
+ log_error(err, params.log_file,
+ svn_ra_svn_conn_remote_host(conn),
+ NULL, NULL, /* user, repos */
+ connection_pool);
+ svn_error_clear(err);
+ apr_socket_close(usock);
+ }
+ svn_pool_destroy(connection_pool);
+#endif
+ break;
+
+ case connection_mode_thread:
+ /* Create a detached thread for each connection. That's not a
+ particularly sophisticated strategy for a threaded server, it's
+ little different from forking one process per connection. */
+#if APR_HAS_THREADS
+ shared_pool = attach_shared_pool(connection_pool);
+ status = apr_threadattr_create(&tattr, connection_pool);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't create threadattr"));
+ svn_handle_error2(err, stderr, FALSE, "svnserve: ");
+ svn_error_clear(err);
+ exit(1);
+ }
+ status = apr_threadattr_detach_set(tattr, 1);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't set detached state"));
+ svn_handle_error2(err, stderr, FALSE, "svnserve: ");
+ svn_error_clear(err);
+ exit(1);
+ }
+ thread_data = apr_palloc(connection_pool, sizeof(*thread_data));
+ thread_data->conn = conn;
+ thread_data->params = &params;
+ thread_data->shared_pool = shared_pool;
+ status = apr_thread_create(&tid, tattr, serve_thread, thread_data,
+ shared_pool->pool);
+ if (status)
+ {
+ err = svn_error_wrap_apr(status, _("Can't create thread"));
+ svn_handle_error2(err, stderr, FALSE, "svnserve: ");
+ svn_error_clear(err);
+ exit(1);
+ }
+ release_shared_pool(shared_pool);
+#endif
+ break;
+
+ case connection_mode_single:
+ /* Serve one connection at a time. */
+ svn_error_clear(serve(conn, &params, connection_pool));
+ svn_pool_destroy(connection_pool);
+ }
+ }
+
+ /* NOTREACHED */
+}
diff --git a/contrib/subversion/subversion/svnserve/svnserve.conf.5 b/contrib/subversion/subversion/svnserve/svnserve.conf.5
new file mode 100644
index 0000000..f86aeb3
--- /dev/null
+++ b/contrib/subversion/subversion/svnserve/svnserve.conf.5
@@ -0,0 +1,100 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svnserve.conf 5
+.SH NAME
+svnserve.conf \- Repository configuration file for svnserve
+.SH SYNOPSIS
+.TP
+\fIrepository-path\fP\fB/conf/svnserve.conf\fP
+.SH DESCRIPTION
+\fBsvnserve.conf\fP controls the behavior of the \fBsvnserve\fP daemon
+on a per-repository basis. It is located in the \fBconf\fP
+subdirectory of the repository.
+.PP
+The overall structure of the file is the same as the structure of
+Subversion user configuration files. At the top level are sections,
+which are specified by words in square brackets; inside each section
+are variable definitions of the form "variable = value". Lines
+beginning with '#' are ignored. \fBsvnserve.conf\fP currently uses
+only one section named "general", and supports the following
+variables:
+.PP
+.TP 5
+\fBanon-access\fP = \fBnone\fP|\fBread\fP|\fBwrite\fP
+Determines the access level for unauthenticated users. \fBwrite\fP
+access allows all repository operations. \fBread\fP access allows all
+operations except committing and changing revision properties.
+\fBnone\fP access allows no access. The default level is \fBread\fP.
+.PP
+.TP 5
+\fBauth-access\fP = \fBnone\fP|\fBread\fP|\fBwrite\fP
+Determines the access level for authenticated users, using the same
+access levels as above. The default level is \fBwrite\fP.
+.PP
+.TP 5
+\fBpassword-db\fP = \fIfilename\fP
+Sets the location of the password database. \fIfilename\fP may be
+relative to the repository conf directory. There is no default value.
+The password database has the same overall format as this file. It
+uses only one section "users"; each variable within the section is a
+username, and each value is a password.
+.PP
+.TP 5
+\fBauthz-db\fP = \fIpath\fP
+The authz-db option controls the location of the authorization
+rules for path-based access control. \fIpath\fP may be
+relative to the repository conf directory. \fIpath\fP may be a repository
+relative URL (^/) or absolute file:// URL to a text file in a Subversion
+repository. There is no default value. If you don't specify an authz-db,
+no path-based access control is done.
+.PP
+.TP 5
+\fBrealm\fP = \fIrealm\-name\fP
+Sets the authentication realm of the repository. If two repositories
+have the same password database, they should have the same realm, and
+vice versa; this association allows clients to use a single cached
+password for several repositories. The default realm value is the
+repository's uuid.
+.SH EXAMPLE
+The following example \fBsvnserve.conf\fP allows read access for
+authenticated users, no access for anonymous users, points to a passwd
+database in the same directory, and defines a realm name.
+.PP
+.nf
+ [general]
+ anon-access = none
+ auth-access = read
+ password-db = passwd
+ realm = My First Repository
+.fi
+.PP
+The file "passwd" would look like:
+.PP
+.nf
+ [users]
+ joeuser = joepassword
+ jayrandom = randomjay
+.fi
+.SH SEE ALSO
+.BR svnserve (8)
diff --git a/contrib/subversion/subversion/svnserve/winservice.c b/contrib/subversion/subversion/svnserve/winservice.c
new file mode 100644
index 0000000..dcb399e
--- /dev/null
+++ b/contrib/subversion/subversion/svnserve/winservice.c
@@ -0,0 +1,490 @@
+/*
+ * winservice.c : Implementation of Windows Service support
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_errno.h>
+
+#include "svn_error.h"
+
+#include "svn_private_config.h"
+#include "winservice.h"
+
+/*
+Design Notes
+------------
+
+The code in this file allows svnserve to run as a Windows service.
+Windows Services are only supported on operating systems derived
+from Windows NT, which is basically all modern versions of Windows
+(2000, XP, Server, Vista, etc.) and excludes the Windows 9x line.
+
+Windows Services are processes that are started and controlled by
+the Service Control Manager. When the SCM wants to start a service,
+it creates the process, then waits for the process to connect to
+the SCM, so that the SCM and service process can communicate.
+This is done using the StartServiceCtrlDispatcher function.
+
+In order to minimize changes to the svnserve startup logic, this
+implementation differs slightly from most service implementations.
+In most services, main() immediately calls StartServiceCtrlDispatcher,
+which does not return control to main() until the SCM sends the
+"stop" request to the service, and the service stops.
+
+
+Installing the Service
+----------------------
+
+Installation is beyond the scope of source code comments. There
+is a separate document that describes how to install and uninstall
+the service. Basically, you create a Windows Service, give it a
+binary path that points to svnserve.exe, and make sure that you
+specify --service on the command line.
+
+
+Starting the Service
+--------------------
+
+First, the SCM decides that it wants to start a service. It creates
+the process for the service, passing it the command-line that is
+stored in the service configuration (stored in the registry).
+
+Next, main() runs. The command-line should contain the --service
+argument, which is the hint that svnserve is running under the SCM,
+not as a standalone process. main() calls winservice_start().
+
+winservice_start() creates an event object (winservice_start_event),
+and creates and starts a separate thread, the "dispatcher" thread.
+winservice_start() then waits for either winservice_start_event
+to fire (meaning: "the dispatcher thread successfully connected
+to the SCM, and now the service is starting") or for the dispatcher
+thread to exit (meaning: "failed to connect to SCM").
+
+If the dispatcher thread quit, then winservice_start returns an error.
+If the start event fired, then winservice_start returns a success code
+(SVN_NO_ERROR). At this point, the service is now in the "starting"
+state, from the perspective of the SCM. winservice_start also registers
+an atexit handler, which handles cleaning up some of the service logic,
+as explained below in "Stopping the Service".
+
+Next, control returns to main(), which performs the usual startup
+logic for svnserve. Mostly, it creates the listener socket. If
+main() was able to start the service, then it calls the function
+winservice_running().
+
+winservice_running() informs the SCM that the service has finished
+starting, and is now in the "running" state. main() then does its
+work, accepting client sockets and processing SVN requests.
+
+Stopping the Service
+--------------------
+
+At some point, the SCM will decide to stop the service, either because
+an administrator chose to stop the service, or the system is shutting
+down. To do this, the SCM calls winservice_handler() with the
+SERVICE_CONTROL_STOP control code. When this happens,
+winservice_handler() will inform the SCM that the service is now
+in the "stopping" state, and will call winservice_notify_stop().
+
+winservice_notify_stop() is responsible for cleanly shutting down the
+svnserve logic (waiting for client requests to finish, stopping database
+access, etc.). Right now, all it does is close the listener socket,
+which causes the apr_socket_accept() call in main() to fail. main()
+then calls exit(), which processes all atexit() handlers, which
+results in winservice_stop() being called.
+
+winservice_stop() notifies the SCM that the service is now stopped,
+and then waits for the dispatcher thread to exit. Because all services
+in the process have now stopped, the call to StartServiceCtrlDispatcher
+(in the dispatcher thread) finally returns, and winservice_stop() returns,
+and the process finally exits.
+*/
+
+
+#ifdef WIN32
+
+#include <assert.h>
+#include <winsvc.h>
+
+/* This is just a placeholder, and doesn't actually constrain the
+ service name. You have to provide *some* service name to the SCM
+ API, but for services that are marked SERVICE_WIN32_OWN_PROCESS (as
+ is the case for svnserve), the service name is ignored. It *is*
+ relevant for service binaries that run more than one service in a
+ single process. */
+#define WINSERVICE_SERVICE_NAME "svnserve"
+
+
+/* Win32 handle to the dispatcher thread. */
+static HANDLE winservice_dispatcher_thread = NULL;
+
+/* Win32 event handle, used to notify winservice_start() that we have
+ successfully connected to the SCM. */
+static HANDLE winservice_start_event = NULL;
+
+/* RPC handle that allows us to notify the SCM of changes in our
+ service status. */
+static SERVICE_STATUS_HANDLE winservice_status_handle = NULL;
+
+/* Our current idea of the service status (stopped, running, controls
+ accepted, exit code, etc.) */
+static SERVICE_STATUS winservice_status;
+
+
+#ifdef SVN_DEBUG
+static void dbg_print(const char* text)
+{
+ OutputDebugStringA(text);
+}
+#else
+/* Make sure dbg_print compiles to nothing in release builds. */
+#define dbg_print(text)
+#endif
+
+
+static void winservice_atexit(void);
+
+/* Notifies the Service Control Manager of the current state of the
+ service. */
+static void
+winservice_update_state(void)
+{
+ if (winservice_status_handle != NULL)
+ {
+ if (!SetServiceStatus(winservice_status_handle, &winservice_status))
+ {
+ dbg_print("SetServiceStatus - FAILED\r\n");
+ }
+ }
+}
+
+
+/* This function cleans up state associated with the service support.
+ If the dispatcher thread handle is non-NULL, then this function
+ will wait for the dispatcher thread to exit. */
+static void
+winservice_cleanup(void)
+{
+ if (winservice_start_event != NULL)
+ {
+ CloseHandle(winservice_start_event);
+ winservice_start_event = NULL;
+ }
+
+ if (winservice_dispatcher_thread != NULL)
+ {
+ dbg_print("winservice_cleanup:"
+ " waiting for dispatcher thread to exit\r\n");
+ WaitForSingleObject(winservice_dispatcher_thread, INFINITE);
+ CloseHandle(winservice_dispatcher_thread);
+ winservice_dispatcher_thread = NULL;
+ }
+}
+
+
+/* The SCM invokes this function to cause state changes in the
+ service. */
+static void WINAPI
+winservice_handler(DWORD control)
+{
+ switch (control)
+ {
+ case SERVICE_CONTROL_INTERROGATE:
+ /* The SCM just wants to check our state. We are required to
+ call SetServiceStatus, but we don't need to make any state
+ changes. */
+ dbg_print("SERVICE_CONTROL_INTERROGATE\r\n");
+ winservice_update_state();
+ break;
+
+ case SERVICE_CONTROL_STOP:
+ dbg_print("SERVICE_CONTROL_STOP\r\n");
+ winservice_status.dwCurrentState = SERVICE_STOP_PENDING;
+ winservice_update_state();
+ winservice_notify_stop();
+ break;
+ }
+}
+
+
+/* This is the "service main" routine (in the Win32 terminology).
+
+ Normally, this function (thread) implements the "main" loop of a
+ service. However, in order to minimize changes to the svnserve
+ main() function, this function is running in a different thread,
+ and main() is blocked in winservice_start(), waiting for
+ winservice_start_event. So this function (thread) only needs to
+ signal that event to "start" the service.
+
+ If this function succeeds, it signals winservice_start_event, which
+ wakes up the winservice_start() frame that is blocked. */
+static void WINAPI
+winservice_service_main(DWORD argc, LPTSTR *argv)
+{
+ DWORD error;
+
+ assert(winservice_start_event != NULL);
+
+ winservice_status_handle =
+ RegisterServiceCtrlHandler(WINSERVICE_SERVICE_NAME, winservice_handler);
+ if (winservice_status_handle == NULL)
+ {
+ /* Ok, that's not fair. We received a request to start a service,
+ and now we cannot bind to the SCM in order to update status?
+ Bring down the app. */
+ error = GetLastError();
+ dbg_print("RegisterServiceCtrlHandler FAILED\r\n");
+ /* Put the error code somewhere where winservice_start can find it. */
+ winservice_status.dwWin32ExitCode = error;
+ SetEvent(winservice_start_event);
+ return;
+ }
+
+ winservice_status.dwCurrentState = SERVICE_START_PENDING;
+ winservice_status.dwWin32ExitCode = ERROR_SUCCESS;
+ winservice_update_state();
+
+ dbg_print("winservice_service_main: service is starting\r\n");
+ SetEvent(winservice_start_event);
+}
+
+
+static const SERVICE_TABLE_ENTRY winservice_service_table[] =
+ {
+ { WINSERVICE_SERVICE_NAME, winservice_service_main },
+ { NULL, NULL }
+ };
+
+
+/* This is the thread routine for the "dispatcher" thread. The
+ purpose of this thread is to connect this process with the Service
+ Control Manager, which allows this process to receive control
+ requests from the SCM, and allows this process to update the SCM
+ with status information.
+
+ The StartServiceCtrlDispatcher connects this process to the SCM.
+ If it succeeds, then it will not return until all of the services
+ running in this process have stopped. (In our case, there is only
+ one service per process.) */
+static DWORD WINAPI
+winservice_dispatcher_thread_routine(PVOID arg)
+{
+ dbg_print("winservice_dispatcher_thread_routine: starting\r\n");
+
+ if (!StartServiceCtrlDispatcher(winservice_service_table))
+ {
+ /* This is a common error. Usually, it means the user has
+ invoked the service with the --service flag directly. This
+ is incorrect. The only time the --service flag is passed is
+ when the process is being started by the SCM. */
+ DWORD error = GetLastError();
+
+ dbg_print("dispatcher: FAILED to connect to SCM\r\n");
+ return error;
+ }
+
+ dbg_print("dispatcher: SCM is done using this process -- exiting\r\n");
+ return ERROR_SUCCESS;
+}
+
+
+/* If svnserve needs to run as a Win32 service, then we need to
+ coordinate with the Service Control Manager (SCM) before
+ continuing. This function call registers the svnserve.exe process
+ with the SCM, waits for the "start" command from the SCM (which
+ will come very quickly), and confirms that those steps succeeded.
+
+ After this call succeeds, the service should perform whatever work
+ it needs to start the service, and then the service should call
+ winservice_running() (if no errors occurred) or winservice_stop()
+ (if something failed during startup). */
+svn_error_t *
+winservice_start(void)
+{
+ HANDLE handles[2];
+ DWORD thread_id;
+ DWORD error_code;
+ apr_status_t apr_status;
+ DWORD wait_status;
+
+ dbg_print("winservice_start: starting svnserve as a service...\r\n");
+
+ ZeroMemory(&winservice_status, sizeof(winservice_status));
+ winservice_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ winservice_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ winservice_status.dwCurrentState = SERVICE_STOPPED;
+
+ /* Create the event that will wake up this thread when the SCM
+ creates the ServiceMain thread. */
+ winservice_start_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (winservice_start_event == NULL)
+ {
+ apr_status = apr_get_os_error();
+ return svn_error_wrap_apr(apr_status,
+ _("Failed to create winservice_start_event"));
+ }
+
+ winservice_dispatcher_thread =
+ (HANDLE)CreateThread(NULL, 0, winservice_dispatcher_thread_routine,
+ NULL, 0, &thread_id);
+ if (winservice_dispatcher_thread == NULL)
+ {
+ apr_status = apr_get_os_error();
+ winservice_cleanup();
+ return svn_error_wrap_apr(apr_status,
+ _("The service failed to start"));
+ }
+
+ /* Next, we wait for the "start" event to fire (meaning the service
+ logic has successfully started), or for the dispatch thread to
+ exit (meaning the service logic could not start). */
+
+ handles[0] = winservice_start_event;
+ handles[1] = winservice_dispatcher_thread;
+ wait_status = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ switch (wait_status)
+ {
+ case WAIT_OBJECT_0:
+ dbg_print("winservice_start: service is now starting\r\n");
+
+ /* We no longer need the start event. */
+ CloseHandle(winservice_start_event);
+ winservice_start_event = NULL;
+
+ /* Register our cleanup logic. */
+ atexit(winservice_atexit);
+ return SVN_NO_ERROR;
+
+ case WAIT_OBJECT_0+1:
+ /* The dispatcher thread exited without starting the service.
+ This happens when the dispatcher fails to connect to the SCM. */
+ dbg_print("winservice_start: dispatcher thread has failed\r\n");
+
+ if (GetExitCodeThread(winservice_dispatcher_thread, &error_code))
+ {
+ dbg_print("winservice_start: dispatcher thread failed\r\n");
+
+ if (error_code == ERROR_SUCCESS)
+ error_code = ERROR_INTERNAL_ERROR;
+
+ }
+ else
+ {
+ error_code = ERROR_INTERNAL_ERROR;
+ }
+
+ CloseHandle(winservice_dispatcher_thread);
+ winservice_dispatcher_thread = NULL;
+
+ winservice_cleanup();
+
+ return svn_error_wrap_apr
+ (APR_FROM_OS_ERROR(error_code),
+ _("Failed to connect to Service Control Manager"));
+
+ default:
+ /* This should never happen! This indicates that our handles are
+ broken, or some other highly unusual error. There is nothing
+ rational that we can do to recover. */
+ apr_status = apr_get_os_error();
+ dbg_print("winservice_start: WaitForMultipleObjects failed!\r\n");
+
+ winservice_cleanup();
+ return svn_error_wrap_apr
+ (apr_status, _("The service failed to start; an internal error"
+ " occurred while starting the service"));
+ }
+}
+
+
+/* main() calls this function in order to inform the SCM that the
+ service has successfully started. This is required; otherwise, the
+ SCM will believe that the service is stuck in the "starting" state,
+ and management tools will also believe that the service is stuck. */
+void
+winservice_running(void)
+{
+ winservice_status.dwCurrentState = SERVICE_RUNNING;
+ winservice_update_state();
+ dbg_print("winservice_notify_running: service is now running\r\n");
+}
+
+
+/* main() calls this function in order to notify the SCM that the
+ service has stopped. This function also handles cleaning up the
+ dispatcher thread (the one that we created above in
+ winservice_start. */
+static void
+winservice_stop(DWORD exit_code)
+{
+ dbg_print("winservice_stop - notifying SCM that service has stopped\r\n");
+ winservice_status.dwCurrentState = SERVICE_STOPPED;
+ winservice_status.dwWin32ExitCode = exit_code;
+ winservice_update_state();
+
+ if (winservice_dispatcher_thread != NULL)
+ {
+ dbg_print("waiting for dispatcher thread to exit...\r\n");
+ WaitForSingleObject(winservice_dispatcher_thread, INFINITE);
+ dbg_print("dispatcher thread has exited.\r\n");
+
+ CloseHandle(winservice_dispatcher_thread);
+ winservice_dispatcher_thread = NULL;
+ }
+ else
+ {
+ /* There was no dispatcher thread. So we never started in
+ the first place. */
+ exit_code = winservice_status.dwWin32ExitCode;
+ dbg_print("dispatcher thread was not running\r\n");
+ }
+
+ if (winservice_start_event != NULL)
+ {
+ CloseHandle(winservice_start_event);
+ winservice_start_event = NULL;
+ }
+
+ dbg_print("winservice_stop - service has stopped\r\n");
+}
+
+
+/* This function is installed as an atexit-handler. This is done so
+ that we don't need to alter every exit() call in main(). */
+static void
+winservice_atexit(void)
+{
+ dbg_print("winservice_atexit - stopping\r\n");
+ winservice_stop(ERROR_SUCCESS);
+}
+
+
+svn_boolean_t
+winservice_is_stopping(void)
+{
+ return (winservice_status.dwCurrentState == SERVICE_STOP_PENDING);
+}
+
+#endif /* WIN32 */
diff --git a/contrib/subversion/subversion/svnserve/winservice.h b/contrib/subversion/subversion/svnserve/winservice.h
new file mode 100644
index 0000000..8e5ac52
--- /dev/null
+++ b/contrib/subversion/subversion/svnserve/winservice.h
@@ -0,0 +1,64 @@
+/*
+ * winservice.h : Public definitions for Windows Service support
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef WINSERVICE_H
+#define WINSERVICE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#ifdef WIN32
+
+/* Connects to the Windows Service Control Manager and allows this
+ process to run as a service. This function can only succeed if the
+ process was started by the SCM, not directly by a user. After this
+ call succeeds, the service should perform whatever work it needs to
+ start the service, and then the service should call
+ winservice_running() (if no errors occurred) or winservice_stop()
+ (if something failed during startup). */
+svn_error_t *winservice_start(void);
+
+/* Notifies the SCM that the service is now running. The caller must
+ already have called winservice_start successfully. */
+void winservice_running(void);
+
+/* This function is called by the SCM in an arbitrary thread when the
+ SCM wants the service to stop. The implementation of this function
+ can return immediately; all that is necessary is that the service
+ eventually stop in response. */
+void winservice_notify_stop(void);
+
+/* Evaluates to TRUE if the SCM has requested that the service stop.
+ This allows for the service to poll, in addition to being notified
+ in the winservice_notify_stop callback. */
+svn_boolean_t winservice_is_stopping(void);
+
+#endif /* WIN32 */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* WINSERVICE_H */
diff --git a/contrib/subversion/subversion/svnsync/svnsync.1 b/contrib/subversion/subversion/svnsync/svnsync.1
new file mode 100644
index 0000000..d9da6be
--- /dev/null
+++ b/contrib/subversion/subversion/svnsync/svnsync.1
@@ -0,0 +1,47 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svnsync 1
+.SH NAME
+svnsync \- Subversion repository synchronization tool
+.SH SYNOPSIS
+.TP
+\fBsvnsync\fP \fIcommand\fP \fIdest-url\fP [\fIoptions\fP] [\fIargs\fP]
+.SH OVERVIEW
+Subversion is a version control system, which allows you to keep old
+versions of files and directories (usually source code), keep a log of
+who, when, and why changes occurred, etc., like CVS, RCS or SCCS.
+\fBSubversion\fP keeps a single copy of the master sources. This copy
+is called the source ``repository''; it contains all the information
+to permit extracting previous versions of those files at any time.
+
+For more information about the Subversion project, visit
+http://subversion.apache.org.
+
+Documentation for Subversion and its tools, including detailed usage
+explanations of the \fBsvn\fP, \fBsvnadmin\fP, \fBsvnserve\fP and
+\fBsvnlook\fP programs, historical background, philosophical
+approaches and reasonings, etc., can be found at
+http://svnbook.red-bean.com/.
+
+Run `svnsync help' to access the built-in tool documentation.
diff --git a/contrib/subversion/subversion/svnsync/svnsync.c b/contrib/subversion/subversion/svnsync/svnsync.c
new file mode 100644
index 0000000..0bdc976
--- /dev/null
+++ b/contrib/subversion/subversion/svnsync/svnsync.c
@@ -0,0 +1,2305 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_config.h"
+#include "svn_pools.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_auth.h"
+#include "svn_opt.h"
+#include "svn_ra.h"
+#include "svn_utf.h"
+#include "svn_subst.h"
+#include "svn_string.h"
+#include "svn_version.h"
+
+#include "private/svn_opt_private.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_cmdline_private.h"
+
+#include "sync.h"
+
+#include "svn_private_config.h"
+
+#include <apr_signal.h>
+#include <apr_uuid.h>
+
+static svn_opt_subcommand_t initialize_cmd,
+ synchronize_cmd,
+ copy_revprops_cmd,
+ info_cmd,
+ help_cmd;
+
+enum svnsync__opt {
+ svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID,
+ svnsync_opt_force_interactive,
+ svnsync_opt_no_auth_cache,
+ svnsync_opt_auth_username,
+ svnsync_opt_auth_password,
+ svnsync_opt_source_username,
+ svnsync_opt_source_password,
+ svnsync_opt_sync_username,
+ svnsync_opt_sync_password,
+ svnsync_opt_config_dir,
+ svnsync_opt_config_options,
+ svnsync_opt_source_prop_encoding,
+ svnsync_opt_disable_locking,
+ svnsync_opt_version,
+ svnsync_opt_trust_server_cert,
+ svnsync_opt_allow_non_empty,
+ svnsync_opt_steal_lock
+};
+
+#define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \
+ svnsync_opt_force_interactive, \
+ svnsync_opt_no_auth_cache, \
+ svnsync_opt_auth_username, \
+ svnsync_opt_auth_password, \
+ svnsync_opt_trust_server_cert, \
+ svnsync_opt_source_username, \
+ svnsync_opt_source_password, \
+ svnsync_opt_sync_username, \
+ svnsync_opt_sync_password, \
+ svnsync_opt_config_dir, \
+ svnsync_opt_config_options
+
+static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] =
+ {
+ { "initialize", initialize_cmd, { "init" },
+ N_("usage: svnsync initialize DEST_URL SOURCE_URL\n"
+ "\n"
+ "Initialize a destination repository for synchronization from\n"
+ "another repository.\n"
+ "\n"
+ "If the source URL is not the root of a repository, only the\n"
+ "specified part of the repository will be synchronized.\n"
+ "\n"
+ "The destination URL must point to the root of a repository which\n"
+ "has been configured to allow revision property changes. In\n"
+ "the general case, the destination repository must contain no\n"
+ "committed revisions. Use --allow-non-empty to override this\n"
+ "restriction, which will cause svnsync to assume that any revisions\n"
+ "already present in the destination repository perfectly mirror\n"
+ "their counterparts in the source repository. (This is useful\n"
+ "when initializing a copy of a repository as a mirror of that same\n"
+ "repository, for example.)\n"
+ "\n"
+ "You should not commit to, or make revision property changes in,\n"
+ "the destination repository by any method other than 'svnsync'.\n"
+ "In other words, the destination repository should be a read-only\n"
+ "mirror of the source repository.\n"),
+ { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
+ svnsync_opt_allow_non_empty, svnsync_opt_disable_locking,
+ svnsync_opt_steal_lock } },
+ { "synchronize", synchronize_cmd, { "sync" },
+ N_("usage: svnsync synchronize DEST_URL [SOURCE_URL]\n"
+ "\n"
+ "Transfer all pending revisions to the destination from the source\n"
+ "with which it was initialized.\n"
+ "\n"
+ "If SOURCE_URL is provided, use that as the source repository URL,\n"
+ "ignoring what is recorded in the destination repository as the\n"
+ "source URL. Specifying SOURCE_URL is recommended in particular\n"
+ "if untrusted users/administrators may have write access to the\n"
+ "DEST_URL repository.\n"),
+ { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
+ svnsync_opt_disable_locking, svnsync_opt_steal_lock } },
+ { "copy-revprops", copy_revprops_cmd, { 0 },
+ N_("usage:\n"
+ "\n"
+ " 1. svnsync copy-revprops DEST_URL [SOURCE_URL]\n"
+ " 2. svnsync copy-revprops DEST_URL REV[:REV2]\n"
+ "\n"
+ "Copy the revision properties in a given range of revisions to the\n"
+ "destination from the source with which it was initialized. If the\n"
+ "revision range is not specified, it defaults to all revisions in\n"
+ "the DEST_URL repository. Note also that the 'HEAD' revision is the\n"
+ "latest in DEST_URL, not necessarily the latest in SOURCE_URL.\n"
+ "\n"
+ "If SOURCE_URL is provided, use that as the source repository URL,\n"
+ "ignoring what is recorded in the destination repository as the\n"
+ "source URL. Specifying SOURCE_URL is recommended in particular\n"
+ "if untrusted users/administrators may have write access to the\n"
+ "DEST_URL repository.\n"
+ "\n"
+ "Form 2 is deprecated syntax, equivalent to specifying \"-rREV[:REV2]\".\n"),
+ { SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 'r',
+ svnsync_opt_disable_locking, svnsync_opt_steal_lock } },
+ { "info", info_cmd, { 0 },
+ N_("usage: svnsync info DEST_URL\n"
+ "\n"
+ "Print information about the synchronization destination repository\n"
+ "located at DEST_URL.\n"),
+ { SVNSYNC_OPTS_DEFAULT } },
+ { "help", help_cmd, { "?", "h" },
+ N_("usage: svnsync help [SUBCOMMAND...]\n"
+ "\n"
+ "Describe the usage of this program or its subcommands.\n"),
+ { 0 } },
+ { NULL, NULL, { 0 }, NULL, { 0 } }
+ };
+
+static const apr_getopt_option_t svnsync_options[] =
+ {
+ {"quiet", 'q', 0,
+ N_("print as little as possible") },
+ {"revision", 'r', 1,
+ N_("operate on revision ARG (or range ARG1:ARG2)\n"
+ " "
+ "A revision argument can be one of:\n"
+ " "
+ " NUMBER revision number\n"
+ " "
+ " 'HEAD' latest in repository") },
+ {"allow-non-empty", svnsync_opt_allow_non_empty, 0,
+ N_("allow a non-empty destination repository") },
+ {"non-interactive", svnsync_opt_non_interactive, 0,
+ N_("do no interactive prompting (default is to prompt\n"
+ " "
+ "only if standard input is a terminal device)")},
+ {"force-interactive", svnsync_opt_force_interactive, 0,
+ N_("do interactive prompting even if standard input\n"
+ " "
+ "is not a terminal device")},
+ {"no-auth-cache", svnsync_opt_no_auth_cache, 0,
+ N_("do not cache authentication tokens") },
+ {"username", svnsync_opt_auth_username, 1,
+ N_("specify a username ARG (deprecated;\n"
+ " "
+ "see --source-username and --sync-username)") },
+ {"password", svnsync_opt_auth_password, 1,
+ N_("specify a password ARG (deprecated;\n"
+ " "
+ "see --source-password and --sync-password)") },
+ {"trust-server-cert", svnsync_opt_trust_server_cert, 0,
+ N_("accept SSL server certificates from unknown\n"
+ " "
+ "certificate authorities without prompting (but only\n"
+ " "
+ "with '--non-interactive')") },
+ {"source-username", svnsync_opt_source_username, 1,
+ N_("connect to source repository with username ARG") },
+ {"source-password", svnsync_opt_source_password, 1,
+ N_("connect to source repository with password ARG") },
+ {"sync-username", svnsync_opt_sync_username, 1,
+ N_("connect to sync repository with username ARG") },
+ {"sync-password", svnsync_opt_sync_password, 1,
+ N_("connect to sync repository with password ARG") },
+ {"config-dir", svnsync_opt_config_dir, 1,
+ N_("read user configuration files from directory ARG")},
+ {"config-option", svnsync_opt_config_options, 1,
+ N_("set user configuration option in the format:\n"
+ " "
+ " FILE:SECTION:OPTION=[VALUE]\n"
+ " "
+ "For example:\n"
+ " "
+ " servers:global:http-library=serf")},
+ {"source-prop-encoding", svnsync_opt_source_prop_encoding, 1,
+ N_("convert translatable properties from encoding ARG\n"
+ " "
+ "to UTF-8. If not specified, then properties are\n"
+ " "
+ "presumed to be encoded in UTF-8.")},
+ {"disable-locking", svnsync_opt_disable_locking, 0,
+ N_("Disable built-in locking. Use of this option can\n"
+ " "
+ "corrupt the mirror unless you ensure that no other\n"
+ " "
+ "instance of svnsync is running concurrently.")},
+ {"steal-lock", svnsync_opt_steal_lock, 0,
+ N_("Steal locks as necessary. Use, with caution,\n"
+ " "
+ "if your mirror repository contains stale locks\n"
+ " "
+ "and is not being concurrently accessed by another\n"
+ " "
+ "svnsync instance.")},
+ {"version", svnsync_opt_version, 0,
+ N_("show program version information")},
+ {"help", 'h', 0,
+ N_("show help on a subcommand")},
+ {NULL, '?', 0,
+ N_("show help on a subcommand")},
+ { 0, 0, 0, 0 }
+ };
+
+typedef struct opt_baton_t {
+ svn_boolean_t non_interactive;
+ svn_boolean_t trust_server_cert;
+ svn_boolean_t no_auth_cache;
+ svn_auth_baton_t *source_auth_baton;
+ svn_auth_baton_t *sync_auth_baton;
+ const char *source_username;
+ const char *source_password;
+ const char *sync_username;
+ const char *sync_password;
+ const char *config_dir;
+ apr_hash_t *config;
+ const char *source_prop_encoding;
+ svn_boolean_t disable_locking;
+ svn_boolean_t steal_lock;
+ svn_boolean_t quiet;
+ svn_boolean_t allow_non_empty;
+ svn_boolean_t version;
+ svn_boolean_t help;
+ svn_opt_revision_t start_rev;
+ svn_opt_revision_t end_rev;
+} opt_baton_t;
+
+
+
+
+/*** Helper functions ***/
+
+
+/* Global record of whether the user has requested cancellation. */
+static volatile sig_atomic_t cancelled = FALSE;
+
+
+/* Callback function for apr_signal(). */
+static void
+signal_handler(int signum)
+{
+ apr_signal(signum, SIG_IGN);
+ cancelled = TRUE;
+}
+
+
+/* Cancellation callback function. */
+static svn_error_t *
+check_cancel(void *baton)
+{
+ if (cancelled)
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+/* Check that the version of libraries in use match what we expect. */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_delta", svn_delta_version },
+ { "svn_ra", svn_ra_version },
+ { NULL, NULL }
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+
+/* Implements `svn_ra__lock_retry_func_t'. */
+static svn_error_t *
+lock_retry_func(void *baton,
+ const svn_string_t *reposlocktoken,
+ apr_pool_t *pool)
+{
+ return svn_cmdline_printf(pool,
+ _("Failed to get lock on destination "
+ "repos, currently held by '%s'\n"),
+ reposlocktoken->data);
+}
+
+/* Acquire a lock (of sorts) on the repository associated with the
+ * given RA SESSION. This lock is just a revprop change attempt in a
+ * time-delay loop. This function is duplicated by svnrdump in
+ * svnrdump/load_editor.c
+ */
+static svn_error_t *
+get_lock(const svn_string_t **lock_string_p,
+ svn_ra_session_t *session,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_boolean_t be_atomic;
+ const svn_string_t *stolen_lock;
+
+ SVN_ERR(svn_ra_has_capability(session, &be_atomic,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ pool));
+ if (! be_atomic)
+ {
+ /* Pre-1.7 server. Can't lock without a race condition.
+ See issue #3546.
+ */
+ err = svn_error_create(
+ SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Target server does not support atomic revision property "
+ "edits; consider upgrading it to 1.7 or using an external "
+ "locking program"));
+ svn_handle_warning2(stderr, err, "svnsync: ");
+ svn_error_clear(err);
+ }
+
+ err = svn_ra__get_operational_lock(lock_string_p, &stolen_lock, session,
+ SVNSYNC_PROP_LOCK, steal_lock,
+ 10 /* retries */, lock_retry_func, NULL,
+ check_cancel, NULL, pool);
+ if (!err && stolen_lock)
+ {
+ return svn_cmdline_printf(pool,
+ _("Stole lock previously held by '%s'\n"),
+ stolen_lock->data);
+ }
+ return err;
+}
+
+
+/* Baton for the various subcommands to share. */
+typedef struct subcommand_baton_t {
+ /* common to all subcommands */
+ apr_hash_t *config;
+ svn_ra_callbacks2_t source_callbacks;
+ svn_ra_callbacks2_t sync_callbacks;
+ svn_boolean_t quiet;
+ svn_boolean_t allow_non_empty;
+ const char *to_url;
+
+ /* initialize, synchronize, and copy-revprops only */
+ const char *source_prop_encoding;
+
+ /* initialize only */
+ const char *from_url;
+
+ /* synchronize only */
+ svn_revnum_t committed_rev;
+
+ /* copy-revprops only */
+ svn_revnum_t start_rev;
+ svn_revnum_t end_rev;
+
+} subcommand_baton_t;
+
+typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session,
+ subcommand_baton_t *baton,
+ apr_pool_t *pool);
+
+
+/* Lock the repository associated with RA SESSION, then execute the
+ * given FUNC/BATON pair while holding the lock. Finally, drop the
+ * lock once it finishes.
+ */
+static svn_error_t *
+with_locked(svn_ra_session_t *session,
+ with_locked_func_t func,
+ subcommand_baton_t *baton,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool)
+{
+ const svn_string_t *lock_string;
+ svn_error_t *err;
+
+ SVN_ERR(get_lock(&lock_string, session, steal_lock, pool));
+
+ err = func(session, baton, pool);
+ return svn_error_compose_create(err,
+ svn_ra__release_operational_lock(session, SVNSYNC_PROP_LOCK,
+ lock_string, pool));
+}
+
+
+/* Callback function for the RA session's open_tmp_file()
+ * requirements.
+ */
+static svn_error_t *
+open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool)
+{
+ return svn_io_open_unique_file3(fp, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ pool, pool);
+}
+
+
+/* Return SVN_NO_ERROR iff URL identifies the root directory of the
+ * repository associated with RA session SESS.
+ */
+static svn_error_t *
+check_if_session_is_at_repos_root(svn_ra_session_t *sess,
+ const char *url,
+ apr_pool_t *pool)
+{
+ const char *sess_root;
+
+ SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool));
+
+ if (strcmp(url, sess_root) == 0)
+ return SVN_NO_ERROR;
+ else
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Session is rooted at '%s' but the repos root is '%s'"),
+ url, sess_root);
+}
+
+
+/* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from
+ * revision REV of the repository associated with RA session SESSION.
+ *
+ * For REV zero, don't remove properties with the "svn:sync-" prefix.
+ *
+ * All allocations will be done in a subpool of POOL.
+ */
+static svn_error_t *
+remove_props_not_in_source(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ apr_hash_t *source_props,
+ apr_hash_t *target_props,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, target_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+
+ svn_pool_clear(subpool);
+
+ if (rev == 0 && !strncmp(propname, SVNSYNC_PROP_PREFIX,
+ sizeof(SVNSYNC_PROP_PREFIX) - 1))
+ continue;
+
+ /* Delete property if the name can't be found in SOURCE_PROPS. */
+ if (! svn_hash_gets(source_props, propname))
+ SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
+ NULL, subpool));
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Filter callback function.
+ * Takes a property name KEY, and is expected to return TRUE if the property
+ * should be filtered out (ie. not be copied to the target list), or FALSE if
+ * not.
+ */
+typedef svn_boolean_t (*filter_func_t)(const char *key);
+
+/* Make a new set of properties, by copying those properties in PROPS for which
+ * the filter FILTER returns FALSE.
+ *
+ * The number of properties not copied will be stored in FILTERED_COUNT.
+ *
+ * The returned set of properties is allocated from POOL.
+ */
+static apr_hash_t *
+filter_props(int *filtered_count, apr_hash_t *props,
+ filter_func_t filter,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_hash_t *filtered = apr_hash_make(pool);
+ *filtered_count = 0;
+
+ for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ void *propval = svn__apr_hash_index_val(hi);
+
+ /* Copy all properties:
+ - not matching the exclude pattern if provided OR
+ - matching the include pattern if provided */
+ if (!filter || !filter(propname))
+ {
+ svn_hash_sets(filtered, propname, propval);
+ }
+ else
+ {
+ *filtered_count += 1;
+ }
+ }
+
+ return filtered;
+}
+
+
+/* Write the set of revision properties REV_PROPS to revision REV to the
+ * repository associated with RA session SESSION.
+ * Omit any properties whose names are in the svnsync property name space,
+ * and set *FILTERED_COUNT to the number of properties thus omitted.
+ * REV_PROPS is a hash mapping (char *)propname to (svn_string_t *)propval.
+ *
+ * All allocations will be done in a subpool of POOL.
+ */
+static svn_error_t *
+write_revprops(int *filtered_count,
+ svn_ra_session_t *session,
+ svn_revnum_t rev,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_index_t *hi;
+
+ *filtered_count = 0;
+
+ for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ const svn_string_t *propval = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(subpool);
+
+ if (strncmp(propname, SVNSYNC_PROP_PREFIX,
+ sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0)
+ {
+ SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
+ propval, subpool));
+ }
+ else
+ {
+ *filtered_count += 1;
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+log_properties_copied(svn_boolean_t syncprops_found,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ if (syncprops_found)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Copied properties for revision %ld "
+ "(%s* properties skipped).\n"),
+ rev, SVNSYNC_PROP_PREFIX));
+ else
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Copied properties for revision %ld.\n"),
+ rev));
+
+ return SVN_NO_ERROR;
+}
+
+/* Print a notification that NORMALIZED_REV_PROPS_COUNT rev-props and
+ * NORMALIZED_NODE_PROPS_COUNT node-props were normalized to LF line
+ * endings, if either of those numbers is non-zero. */
+static svn_error_t *
+log_properties_normalized(int normalized_rev_props_count,
+ int normalized_node_props_count,
+ apr_pool_t *pool)
+{
+ if (normalized_rev_props_count > 0 || normalized_node_props_count > 0)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("NOTE: Normalized %s* properties "
+ "to LF line endings (%d rev-props, "
+ "%d node-props).\n"),
+ SVN_PROP_PREFIX,
+ normalized_rev_props_count,
+ normalized_node_props_count));
+ return SVN_NO_ERROR;
+}
+
+
+/* Copy all the revision properties, except for those that have the
+ * "svn:sync-" prefix, from revision REV of the repository associated
+ * with RA session FROM_SESSION, to the repository associated with RA
+ * session TO_SESSION.
+ *
+ * If SYNC is TRUE, then properties on the destination revision that
+ * do not exist on the source revision will be removed.
+ *
+ * If QUIET is FALSE, then log_properties_copied() is called to log that
+ * properties were copied for revision REV.
+ *
+ * Make sure the values of svn:* revision properties use only LF (\n)
+ * line ending style, correcting their values as necessary. The number
+ * of properties that were normalized is returned in *NORMALIZED_COUNT.
+ */
+static svn_error_t *
+copy_revprops(svn_ra_session_t *from_session,
+ svn_ra_session_t *to_session,
+ svn_revnum_t rev,
+ svn_boolean_t sync,
+ svn_boolean_t quiet,
+ const char *source_prop_encoding,
+ int *normalized_count,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *existing_props, *rev_props;
+ int filtered_count = 0;
+
+ /* Get the list of revision properties on REV of TARGET. We're only interested
+ in the property names, but we'll get the values 'for free'. */
+ if (sync)
+ SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool));
+ else
+ existing_props = NULL;
+
+ /* Get the list of revision properties on REV of SOURCE. */
+ SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool));
+
+ /* If necessary, normalize encoding and line ending style and return the count
+ of EOL-normalized properties in int *NORMALIZED_COUNT. */
+ SVN_ERR(svnsync_normalize_revprops(rev_props, normalized_count,
+ source_prop_encoding, pool));
+
+ /* Copy all but the svn:svnsync properties. */
+ SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props, pool));
+
+ /* Delete those properties that were in TARGET but not in SOURCE */
+ if (sync)
+ SVN_ERR(remove_props_not_in_source(to_session, rev,
+ rev_props, existing_props, pool));
+
+ if (! quiet)
+ SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool));
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return a subcommand baton allocated from POOL and populated with
+ data from the provided parameters, which include the global
+ OPT_BATON options structure and a handful of other options. Not
+ all parameters are used in all subcommands -- see
+ subcommand_baton_t's definition for details. */
+static subcommand_baton_t *
+make_subcommand_baton(opt_baton_t *opt_baton,
+ const char *to_url,
+ const char *from_url,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ apr_pool_t *pool)
+{
+ subcommand_baton_t *b = apr_pcalloc(pool, sizeof(*b));
+ b->config = opt_baton->config;
+ b->source_callbacks.open_tmp_file = open_tmp_file;
+ b->source_callbacks.auth_baton = opt_baton->source_auth_baton;
+ b->sync_callbacks.open_tmp_file = open_tmp_file;
+ b->sync_callbacks.auth_baton = opt_baton->sync_auth_baton;
+ b->quiet = opt_baton->quiet;
+ b->allow_non_empty = opt_baton->allow_non_empty;
+ b->to_url = to_url;
+ b->source_prop_encoding = opt_baton->source_prop_encoding;
+ b->from_url = from_url;
+ b->start_rev = start_rev;
+ b->end_rev = end_rev;
+ return b;
+}
+
+static svn_error_t *
+open_target_session(svn_ra_session_t **to_session_p,
+ subcommand_baton_t *baton,
+ apr_pool_t *pool);
+
+
+/*** `svnsync init' ***/
+
+/* Initialize the repository associated with RA session TO_SESSION,
+ * using information found in BATON, while the repository is
+ * locked. Implements `with_locked_func_t' interface.
+ */
+static svn_error_t *
+do_initialize(svn_ra_session_t *to_session,
+ subcommand_baton_t *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *from_session;
+ svn_string_t *from_url;
+ svn_revnum_t latest, from_latest;
+ const char *uuid, *root_url;
+ int normalized_rev_props_count;
+
+ /* First, sanity check to see that we're copying into a brand new
+ repos. If we aren't, and we aren't being asked to forcibly
+ complete this initialization, that's a bad news. */
+ SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool));
+ if ((latest != 0) && (! baton->allow_non_empty))
+ return svn_error_create
+ (APR_EINVAL, NULL,
+ _("Destination repository already contains revision history; consider "
+ "using --allow-non-empty if the repository's revisions are known "
+ "to mirror their respective revisions in the source repository"));
+
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
+ &from_url, pool));
+ if (from_url && (! baton->allow_non_empty))
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Destination repository is already synchronizing from '%s'"),
+ from_url->data);
+
+ /* Now fill in our bookkeeping info in the dest repository. */
+
+ SVN_ERR(svn_ra_open4(&from_session, NULL, baton->from_url, NULL,
+ &(baton->source_callbacks), baton,
+ baton->config, pool));
+ SVN_ERR(svn_ra_get_repos_root2(from_session, &root_url, pool));
+
+ /* If we're doing a partial replay, we have to check first if the server
+ supports this. */
+ if (strcmp(root_url, baton->from_url) != 0)
+ {
+ svn_boolean_t server_supports_partial_replay;
+ svn_error_t *err = svn_ra_has_capability(from_session,
+ &server_supports_partial_replay,
+ SVN_RA_CAPABILITY_PARTIAL_REPLAY,
+ pool);
+ if (err && err->apr_err != SVN_ERR_UNKNOWN_CAPABILITY)
+ return svn_error_trace(err);
+
+ if (err || !server_supports_partial_replay)
+ return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, err,
+ NULL);
+ }
+
+ /* If we're initializing a non-empty destination, we'll make sure
+ that it at least doesn't have more revisions than the source. */
+ if (latest != 0)
+ {
+ SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
+ if (from_latest < latest)
+ return svn_error_create
+ (APR_EINVAL, NULL,
+ _("Destination repository has more revisions than source "
+ "repository"));
+ }
+
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL,
+ svn_string_create(baton->from_url, pool),
+ pool));
+
+ SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool));
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL,
+ svn_string_create(uuid, pool), pool));
+
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
+ NULL, svn_string_createf(pool, "%ld", latest),
+ pool));
+
+ /* Copy all non-svnsync revprops from the LATEST rev in the source
+ repository into the destination, notifying about normalized
+ props, if any. When LATEST is 0, this serves the practical
+ purpose of initializing data that would otherwise be overlooked
+ by the sync process (which is going to begin with r1). When
+ LATEST is not 0, this really serves merely aesthetic and
+ informational purposes, keeping the output of this command
+ consistent while allowing folks to see what the latest revision is. */
+ SVN_ERR(copy_revprops(from_session, to_session, latest, FALSE, baton->quiet,
+ baton->source_prop_encoding, &normalized_rev_props_count,
+ pool));
+
+ SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
+
+ /* TODO: It would be nice if we could set the dest repos UUID to be
+ equal to the UUID of the source repos, at least optionally. That
+ way people could check out/log/diff using a local fast mirror,
+ but switch --relocate to the actual final repository in order to
+ make changes... But at this time, the RA layer doesn't have a
+ way to set a UUID. */
+
+ return SVN_NO_ERROR;
+}
+
+
+/* SUBCOMMAND: init */
+static svn_error_t *
+initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
+{
+ const char *to_url, *from_url;
+ svn_ra_session_t *to_session;
+ opt_baton_t *opt_baton = b;
+ apr_array_header_t *targets;
+ subcommand_baton_t *baton;
+
+ SVN_ERR(svn_opt__args_to_target_array(&targets, os,
+ apr_array_make(pool, 0,
+ sizeof(const char *)),
+ pool));
+ if (targets->nelts < 2)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+ to_url = APR_ARRAY_IDX(targets, 0, const char *);
+ from_url = APR_ARRAY_IDX(targets, 1, const char *);
+
+ if (! svn_path_is_url(to_url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' is not a URL"), to_url);
+ if (! svn_path_is_url(from_url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' is not a URL"), from_url);
+
+ baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
+ SVN_ERR(open_target_session(&to_session, baton, pool));
+ if (opt_baton->disable_locking)
+ SVN_ERR(do_initialize(to_session, baton, pool));
+ else
+ SVN_ERR(with_locked(to_session, do_initialize, baton,
+ opt_baton->steal_lock, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** `svnsync sync' ***/
+
+/* Implements `svn_commit_callback2_t' interface. */
+static svn_error_t *
+commit_callback(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ subcommand_baton_t *sb = baton;
+
+ if (! sb->quiet)
+ {
+ SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
+ commit_info->revision));
+ }
+
+ sb->committed_rev = commit_info->revision;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *FROM_SESSION to an RA session associated with the source
+ * repository of the synchronization. If FROM_URL is non-NULL, use it
+ * as the source repository URL; otherwise, determine the source
+ * repository URL by reading svn:sync- properties from the destination
+ * repository (associated with TO_SESSION). Set LAST_MERGED_REV to
+ * the value of the property which records the most recently
+ * synchronized revision.
+ *
+ * CALLBACKS is a vtable of RA callbacks to provide when creating
+ * *FROM_SESSION. CONFIG is a configuration hash.
+ */
+static svn_error_t *
+open_source_session(svn_ra_session_t **from_session,
+ svn_string_t **last_merged_rev,
+ const char *from_url,
+ svn_ra_session_t *to_session,
+ svn_ra_callbacks2_t *callbacks,
+ apr_hash_t *config,
+ void *baton,
+ apr_pool_t *pool)
+{
+ apr_hash_t *props;
+ svn_string_t *from_url_str, *from_uuid_str;
+
+ SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
+
+ from_url_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
+ from_uuid_str = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
+ *last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
+
+ if (! from_url_str || ! from_uuid_str || ! *last_merged_rev)
+ return svn_error_create
+ (APR_EINVAL, NULL,
+ _("Destination repository has not been initialized"));
+
+ /* ### TODO: Should we validate that FROM_URL_STR->data matches any
+ provided FROM_URL here? */
+ if (! from_url)
+ from_url = from_url_str->data;
+
+ /* Open the session to copy the revision data. */
+ SVN_ERR(svn_ra_open4(from_session, NULL, from_url, from_uuid_str->data,
+ callbacks, baton, config, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *TARGET_SESSION_P to an RA session associated with the target
+ * repository of the synchronization.
+ */
+static svn_error_t *
+open_target_session(svn_ra_session_t **target_session_p,
+ subcommand_baton_t *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *target_session;
+ SVN_ERR(svn_ra_open4(&target_session, NULL, baton->to_url, NULL,
+ &(baton->sync_callbacks), baton, baton->config, pool));
+ SVN_ERR(check_if_session_is_at_repos_root(target_session, baton->to_url, pool));
+
+ *target_session_p = target_session;
+ return SVN_NO_ERROR;
+}
+
+/* Replay baton, used during synchronization. */
+typedef struct replay_baton_t {
+ svn_ra_session_t *from_session;
+ svn_ra_session_t *to_session;
+ /* Extra 'backdoor' session for fetching data *from* the target repo. */
+ svn_ra_session_t *extra_to_session;
+ svn_revnum_t current_revision;
+ subcommand_baton_t *sb;
+ svn_boolean_t has_commit_revprops_capability;
+ int normalized_rev_props_count;
+ int normalized_node_props_count;
+ const char *to_root;
+} replay_baton_t;
+
+/* Return a replay baton allocated from POOL and populated with
+ data from the provided parameters. */
+static svn_error_t *
+make_replay_baton(replay_baton_t **baton_p,
+ svn_ra_session_t *from_session,
+ svn_ra_session_t *to_session,
+ subcommand_baton_t *sb, apr_pool_t *pool)
+{
+ replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb));
+ rb->from_session = from_session;
+ rb->to_session = to_session;
+ rb->sb = sb;
+
+ SVN_ERR(svn_ra_get_repos_root2(to_session, &rb->to_root, pool));
+
+#ifdef ENABLE_EV2_SHIMS
+ /* Open up the extra baton. Only needed for Ev2 shims. */
+ SVN_ERR(open_target_session(&rb->extra_to_session, sb, pool));
+#endif
+
+ *baton_p = rb;
+ return SVN_NO_ERROR;
+}
+
+/* Return TRUE iff KEY is the name of an svn:date or svn:author or any svnsync
+ * property. Implements filter_func_t. Use with filter_props() to filter out
+ * svn:date and svn:author and svnsync properties.
+ */
+static svn_boolean_t
+filter_exclude_date_author_sync(const char *key)
+{
+ if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0)
+ return TRUE;
+ else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0)
+ return TRUE;
+ else if (strncmp(key, SVNSYNC_PROP_PREFIX,
+ sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+/* Return FALSE iff KEY is the name of an svn:date or svn:author or any svnsync
+ * property. Implements filter_func_t. Use with filter_props() to filter out
+ * all properties except svn:date and svn:author and svnsync properties.
+ */
+static svn_boolean_t
+filter_include_date_author_sync(const char *key)
+{
+ return ! filter_exclude_date_author_sync(key);
+}
+
+
+/* Return TRUE iff KEY is the name of the svn:log property.
+ * Implements filter_func_t. Use with filter_props() to only exclude svn:log.
+ */
+static svn_boolean_t
+filter_exclude_log(const char *key)
+{
+ if (strcmp(key, SVN_PROP_REVISION_LOG) == 0)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+/* Return FALSE iff KEY is the name of the svn:log property.
+ * Implements filter_func_t. Use with filter_props() to only include svn:log.
+ */
+static svn_boolean_t
+filter_include_log(const char *key)
+{
+ return ! filter_exclude_log(key);
+}
+
+
+static svn_error_t *
+fetch_base_func(const char **filename,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct replay_baton_t *rb = baton;
+ svn_stream_t *fstream;
+ svn_error_t *err;
+
+ if (svn_path_is_url(path))
+ path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
+ else if (path[0] == '/')
+ path += 1;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = rb->current_revision - 1;
+
+ SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+
+ err = svn_ra_get_file(rb->extra_to_session, path, base_revision,
+ fstream, NULL, NULL, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ SVN_ERR(svn_stream_close(fstream));
+
+ *filename = NULL;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ SVN_ERR(svn_stream_close(fstream));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct replay_baton_t *rb = baton;
+ svn_node_kind_t node_kind;
+
+ if (svn_path_is_url(path))
+ path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
+ else if (path[0] == '/')
+ path += 1;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = rb->current_revision - 1;
+
+ SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
+ &node_kind, scratch_pool));
+
+ if (node_kind == svn_node_file)
+ {
+ SVN_ERR(svn_ra_get_file(rb->extra_to_session, path, base_revision,
+ NULL, NULL, props, result_pool));
+ }
+ else if (node_kind == svn_node_dir)
+ {
+ apr_array_header_t *tmp_props;
+
+ SVN_ERR(svn_ra_get_dir2(rb->extra_to_session, NULL, NULL, props, path,
+ base_revision, 0 /* Dirent fields */,
+ result_pool));
+ tmp_props = svn_prop_hash_to_array(*props, result_pool);
+ SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
+ result_pool));
+ *props = svn_prop_array_to_hash(tmp_props, result_pool);
+ }
+ else
+ {
+ *props = apr_hash_make(result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct replay_baton_t *rb = baton;
+
+ if (svn_path_is_url(path))
+ path = svn_uri_skip_ancestor(rb->to_root, path, scratch_pool);
+ else if (path[0] == '/')
+ path += 1;
+
+ if (! SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = rb->current_revision - 1;
+
+ SVN_ERR(svn_ra_check_path(rb->extra_to_session, path, base_revision,
+ kind, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_delta_shim_callbacks_t *
+get_shim_callbacks(replay_baton_t *rb,
+ apr_pool_t *result_pool)
+{
+ svn_delta_shim_callbacks_t *callbacks =
+ svn_delta_shim_callbacks_default(result_pool);
+
+ callbacks->fetch_props_func = fetch_props_func;
+ callbacks->fetch_kind_func = fetch_kind_func;
+ callbacks->fetch_base_func = fetch_base_func;
+ callbacks->fetch_baton = rb;
+
+ return callbacks;
+}
+
+
+/* Callback function for svn_ra_replay_range, invoked when starting to parse
+ * a replay report.
+ */
+static svn_error_t *
+replay_rev_started(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *commit_editor;
+ const svn_delta_editor_t *cancel_editor;
+ const svn_delta_editor_t *sync_editor;
+ void *commit_baton;
+ void *cancel_baton;
+ void *sync_baton;
+ replay_baton_t *rb = replay_baton;
+ apr_hash_t *filtered;
+ int filtered_count;
+ int normalized_count;
+
+ /* We set this property so that if we error out for some reason
+ we can later determine where we were in the process of
+ merging a revision. If we had committed the change, but we
+ hadn't finished copying the revprops we need to know that, so
+ we can go back and finish the job before we move on.
+
+ NOTE: We have to set this before we start the commit editor,
+ because ra_svn doesn't let you change rev props during a
+ commit. */
+ SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING,
+ NULL,
+ svn_string_createf(pool, "%ld", revision),
+ pool));
+
+ /* The actual copy is just a replay hooked up to a commit. Include
+ all the revision properties from the source repositories, except
+ 'svn:author' and 'svn:date', those are not guaranteed to get
+ through the editor anyway.
+ If we're syncing to an non-commit-revprops capable server, filter
+ out all revprops except svn:log and add them later in
+ revplay_rev_finished. */
+ filtered = filter_props(&filtered_count, rev_props,
+ (rb->has_commit_revprops_capability
+ ? filter_exclude_date_author_sync
+ : filter_include_log),
+ pool);
+
+ /* svn_ra_get_commit_editor3 requires the log message to be
+ set. It's possible that we didn't receive 'svn:log' here, so we
+ have to set it to at least the empty string. If there's a svn:log
+ property on this revision, we will write the actual value in the
+ replay_rev_finished callback. */
+ if (! svn_hash_gets(filtered, SVN_PROP_REVISION_LOG))
+ svn_hash_sets(filtered, SVN_PROP_REVISION_LOG,
+ svn_string_create_empty(pool));
+
+ /* If necessary, normalize encoding and line ending style. Add the number
+ of properties that required EOL normalization to the overall count
+ in the replay baton. */
+ SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
+ rb->sb->source_prop_encoding, pool));
+ rb->normalized_rev_props_count += normalized_count;
+
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->to_session,
+ get_shim_callbacks(rb, pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(rb->to_session, &commit_editor,
+ &commit_baton,
+ filtered,
+ commit_callback, rb->sb,
+ NULL, FALSE, pool));
+
+ /* There's one catch though, the diff shows us props we can't send
+ over the RA interface, so we need an editor that's smart enough
+ to filter those out for us. */
+ SVN_ERR(svnsync_get_sync_editor(commit_editor, commit_baton, revision - 1,
+ rb->sb->to_url, rb->sb->source_prop_encoding,
+ rb->sb->quiet, &sync_editor, &sync_baton,
+ &(rb->normalized_node_props_count), pool));
+
+ SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
+ sync_editor, sync_baton,
+ &cancel_editor,
+ &cancel_baton,
+ pool));
+ *editor = cancel_editor;
+ *edit_baton = cancel_baton;
+
+ rb->current_revision = revision;
+ return SVN_NO_ERROR;
+}
+
+/* Callback function for svn_ra_replay_range, invoked when finishing parsing
+ * a replay report.
+ */
+static svn_error_t *
+replay_rev_finished(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ replay_baton_t *rb = replay_baton;
+ apr_hash_t *filtered, *existing_props;
+ int filtered_count;
+ int normalized_count;
+
+ SVN_ERR(editor->close_edit(edit_baton, pool));
+
+ /* Sanity check that we actually committed the revision we meant to. */
+ if (rb->sb->committed_rev != revision)
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Commit created rev %ld but should have created %ld"),
+ rb->sb->committed_rev, revision);
+
+ SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props,
+ subpool));
+
+
+ /* Ok, we're done with the data, now we just need to copy the remaining
+ 'svn:date' and 'svn:author' revprops and we're all set.
+ If the server doesn't support revprops-in-a-commit, we still have to
+ set all revision properties except svn:log. */
+ filtered = filter_props(&filtered_count, rev_props,
+ (rb->has_commit_revprops_capability
+ ? filter_include_date_author_sync
+ : filter_exclude_log),
+ subpool);
+
+ /* If necessary, normalize encoding and line ending style, and add the number
+ of EOL-normalized properties to the overall count in the replay baton. */
+ SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
+ rb->sb->source_prop_encoding, pool));
+ rb->normalized_rev_props_count += normalized_count;
+
+ SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered,
+ subpool));
+
+ /* Remove all extra properties in TARGET. */
+ SVN_ERR(remove_props_not_in_source(rb->to_session, revision,
+ rev_props, existing_props, subpool));
+
+ svn_pool_clear(subpool);
+
+ /* Ok, we're done, bring the last-merged-rev property up to date. */
+ SVN_ERR(svn_ra_change_rev_prop2(
+ rb->to_session,
+ 0,
+ SVNSYNC_PROP_LAST_MERGED_REV,
+ NULL,
+ svn_string_create(apr_psprintf(pool, "%ld", revision),
+ subpool),
+ subpool));
+
+ /* And finally drop the currently copying prop, since we're done
+ with this revision. */
+ SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING,
+ NULL, NULL, subpool));
+
+ /* Notify the user that we copied revision properties. */
+ if (! rb->sb->quiet)
+ SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool));
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Synchronize the repository associated with RA session TO_SESSION,
+ * using information found in BATON, while the repository is
+ * locked. Implements `with_locked_func_t' interface.
+ */
+static svn_error_t *
+do_synchronize(svn_ra_session_t *to_session,
+ subcommand_baton_t *baton, apr_pool_t *pool)
+{
+ svn_string_t *last_merged_rev;
+ svn_revnum_t from_latest;
+ svn_ra_session_t *from_session;
+ svn_string_t *currently_copying;
+ svn_revnum_t to_latest, copying, last_merged;
+ svn_revnum_t start_revision, end_revision;
+ replay_baton_t *rb;
+ int normalized_rev_props_count = 0;
+
+ SVN_ERR(open_source_session(&from_session, &last_merged_rev,
+ baton->from_url, to_session,
+ &(baton->source_callbacks), baton->config,
+ baton, pool));
+
+ /* Check to see if we have revprops that still need to be copied for
+ a prior revision we didn't finish copying. But first, check for
+ state sanity. Remember, mirroring is not an atomic action,
+ because revision properties are copied separately from the
+ revision's contents.
+
+ So, any time that currently-copying is not set, then
+ last-merged-rev should be the HEAD revision of the destination
+ repository. That is, if we didn't fall over in the middle of a
+ previous synchronization, then our destination repository should
+ have exactly as many revisions in it as we've synchronized.
+
+ Alternately, if currently-copying *is* set, it must
+ be either last-merged-rev or last-merged-rev + 1, and the HEAD
+ revision must be equal to either last-merged-rev or
+ currently-copying. If this is not the case, somebody has meddled
+ with the destination without using svnsync.
+ */
+
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
+ &currently_copying, pool));
+
+ SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
+
+ last_merged = SVN_STR_TO_REV(last_merged_rev->data);
+
+ if (currently_copying)
+ {
+ copying = SVN_STR_TO_REV(currently_copying->data);
+
+ if ((copying < last_merged)
+ || (copying > (last_merged + 1))
+ || ((to_latest != last_merged) && (to_latest != copying)))
+ {
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Revision being currently copied (%ld), last merged revision "
+ "(%ld), and destination HEAD (%ld) are inconsistent; have you "
+ "committed to the destination without using svnsync?"),
+ copying, last_merged, to_latest);
+ }
+ else if (copying == to_latest)
+ {
+ if (copying > last_merged)
+ {
+ SVN_ERR(copy_revprops(from_session, to_session, to_latest, TRUE,
+ baton->quiet, baton->source_prop_encoding,
+ &normalized_rev_props_count, pool));
+ last_merged = copying;
+ last_merged_rev = svn_string_create
+ (apr_psprintf(pool, "%ld", last_merged), pool);
+ }
+
+ /* Now update last merged rev and drop currently changing.
+ Note that the order here is significant, if we do them
+ in the wrong order there are race conditions where we
+ end up not being able to tell if there have been bogus
+ (i.e. non-svnsync) commits to the dest repository. */
+
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ SVNSYNC_PROP_LAST_MERGED_REV,
+ NULL, last_merged_rev, pool));
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING,
+ NULL, NULL, pool));
+ }
+ /* If copying > to_latest, then we just fall through to
+ attempting to copy the revision again. */
+ }
+ else
+ {
+ if (to_latest != last_merged)
+ return svn_error_createf(APR_EINVAL, NULL,
+ _("Destination HEAD (%ld) is not the last "
+ "merged revision (%ld); have you "
+ "committed to the destination without "
+ "using svnsync?"),
+ to_latest, last_merged);
+ }
+
+ /* Now check to see if there are any revisions to copy. */
+ SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
+
+ if (from_latest < last_merged)
+ return SVN_NO_ERROR;
+
+ /* Ok, so there are new revisions, iterate over them copying them
+ into the destination repository. */
+ SVN_ERR(make_replay_baton(&rb, from_session, to_session, baton, pool));
+
+ /* For compatibility with older svnserve versions, check first if we
+ support adding revprops to the commit. */
+ SVN_ERR(svn_ra_has_capability(rb->to_session,
+ &rb->has_commit_revprops_capability,
+ SVN_RA_CAPABILITY_COMMIT_REVPROPS,
+ pool));
+
+ start_revision = last_merged + 1;
+ end_revision = from_latest;
+
+ SVN_ERR(check_cancel(NULL));
+
+ SVN_ERR(svn_ra_replay_range(from_session, start_revision, end_revision,
+ 0, TRUE, replay_rev_started,
+ replay_rev_finished, rb, pool));
+
+ SVN_ERR(log_properties_normalized(rb->normalized_rev_props_count
+ + normalized_rev_props_count,
+ rb->normalized_node_props_count,
+ pool));
+
+
+ return SVN_NO_ERROR;
+}
+
+
+/* SUBCOMMAND: sync */
+static svn_error_t *
+synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
+{
+ svn_ra_session_t *to_session;
+ opt_baton_t *opt_baton = b;
+ apr_array_header_t *targets;
+ subcommand_baton_t *baton;
+ const char *to_url, *from_url;
+
+ SVN_ERR(svn_opt__args_to_target_array(&targets, os,
+ apr_array_make(pool, 0,
+ sizeof(const char *)),
+ pool));
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+ to_url = APR_ARRAY_IDX(targets, 0, const char *);
+ if (! svn_path_is_url(to_url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' is not a URL"), to_url);
+
+ if (targets->nelts == 2)
+ {
+ from_url = APR_ARRAY_IDX(targets, 1, const char *);
+ if (! svn_path_is_url(from_url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' is not a URL"), from_url);
+ }
+ else
+ {
+ from_url = NULL; /* we'll read it from the destination repos */
+ }
+
+ baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
+ SVN_ERR(open_target_session(&to_session, baton, pool));
+ if (opt_baton->disable_locking)
+ SVN_ERR(do_synchronize(to_session, baton, pool));
+ else
+ SVN_ERR(with_locked(to_session, do_synchronize, baton,
+ opt_baton->steal_lock, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** `svnsync copy-revprops' ***/
+
+/* Copy revision properties to the repository associated with RA
+ * session TO_SESSION, using information found in BATON, while the
+ * repository is locked. Implements `with_locked_func_t' interface.
+ */
+static svn_error_t *
+do_copy_revprops(svn_ra_session_t *to_session,
+ subcommand_baton_t *baton, apr_pool_t *pool)
+{
+ svn_ra_session_t *from_session;
+ svn_string_t *last_merged_rev;
+ svn_revnum_t i;
+ svn_revnum_t step = 1;
+ int normalized_rev_props_count = 0;
+
+ SVN_ERR(open_source_session(&from_session, &last_merged_rev,
+ baton->from_url, to_session,
+ &(baton->source_callbacks), baton->config,
+ baton, pool));
+
+ /* An invalid revision means "last-synced" */
+ if (! SVN_IS_VALID_REVNUM(baton->start_rev))
+ baton->start_rev = SVN_STR_TO_REV(last_merged_rev->data);
+ if (! SVN_IS_VALID_REVNUM(baton->end_rev))
+ baton->end_rev = SVN_STR_TO_REV(last_merged_rev->data);
+
+ /* Make sure we have revisions within the valid range. */
+ if (baton->start_rev > SVN_STR_TO_REV(last_merged_rev->data))
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Cannot copy revprops for a revision (%ld) that has not "
+ "been synchronized yet"), baton->start_rev);
+ if (baton->end_rev > SVN_STR_TO_REV(last_merged_rev->data))
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Cannot copy revprops for a revision (%ld) that has not "
+ "been synchronized yet"), baton->end_rev);
+
+ /* Now, copy all the requested revisions, in the requested order. */
+ step = (baton->start_rev > baton->end_rev) ? -1 : 1;
+ for (i = baton->start_rev; i != baton->end_rev + step; i = i + step)
+ {
+ int normalized_count;
+ SVN_ERR(check_cancel(NULL));
+ SVN_ERR(copy_revprops(from_session, to_session, i, TRUE, baton->quiet,
+ baton->source_prop_encoding, &normalized_count,
+ pool));
+ normalized_rev_props_count += normalized_count;
+ }
+
+ /* Notify about normalized props, if any. */
+ SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *START_REVNUM to the revision number associated with
+ START_REVISION, or to SVN_INVALID_REVNUM if START_REVISION
+ represents "HEAD"; if END_REVISION is specified, set END_REVNUM to
+ the revision number associated with END_REVISION or to
+ SVN_INVALID_REVNUM if END_REVISION represents "HEAD"; otherwise set
+ END_REVNUM to the same value as START_REVNUM.
+
+ As a special case, if neither START_REVISION nor END_REVISION is
+ specified, set *START_REVNUM to 0 and set *END_REVNUM to
+ SVN_INVALID_REVNUM.
+
+ Freak out if either START_REVISION or END_REVISION represents an
+ explicit but invalid revision number. */
+static svn_error_t *
+resolve_revnums(svn_revnum_t *start_revnum,
+ svn_revnum_t *end_revnum,
+ svn_opt_revision_t start_revision,
+ svn_opt_revision_t end_revision)
+{
+ svn_revnum_t start_rev, end_rev;
+
+ /* Special case: neither revision is specified? This is like
+ -r0:HEAD. */
+ if ((start_revision.kind == svn_opt_revision_unspecified) &&
+ (end_revision.kind == svn_opt_revision_unspecified))
+ {
+ *start_revnum = 0;
+ *end_revnum = SVN_INVALID_REVNUM;
+ return SVN_NO_ERROR;
+ }
+
+ /* Get the start revision, which must be either HEAD or a number
+ (which is required to be a valid one). */
+ if (start_revision.kind == svn_opt_revision_head)
+ {
+ start_rev = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ start_rev = start_revision.value.number;
+ if (! SVN_IS_VALID_REVNUM(start_rev))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid revision number (%ld)"),
+ start_rev);
+ }
+
+ /* Get the end revision, which must be unspecified (meaning,
+ "same as the start_rev"), HEAD, or a number (which is
+ required to be a valid one). */
+ if (end_revision.kind == svn_opt_revision_unspecified)
+ {
+ end_rev = start_rev;
+ }
+ else if (end_revision.kind == svn_opt_revision_head)
+ {
+ end_rev = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ end_rev = end_revision.value.number;
+ if (! SVN_IS_VALID_REVNUM(end_rev))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid revision number (%ld)"),
+ end_rev);
+ }
+
+ *start_revnum = start_rev;
+ *end_revnum = end_rev;
+ return SVN_NO_ERROR;
+}
+
+
+/* SUBCOMMAND: copy-revprops */
+static svn_error_t *
+copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
+{
+ svn_ra_session_t *to_session;
+ opt_baton_t *opt_baton = b;
+ apr_array_header_t *targets;
+ subcommand_baton_t *baton;
+ const char *to_url = NULL;
+ const char *from_url = NULL;
+ svn_opt_revision_t start_revision, end_revision;
+ svn_revnum_t start_rev = 0, end_rev = SVN_INVALID_REVNUM;
+
+ /* There should be either one or two arguments left to parse. */
+ if (os->argc - os->ind > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+ if (os->argc - os->ind < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ /* If there are two args, the last one is either a revision range or
+ the source URL. */
+ if (os->argc - os->ind == 2)
+ {
+ const char *arg_str = os->argv[os->argc - 1];
+ const char *utf_arg_str;
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&utf_arg_str, arg_str, pool));
+
+ if (! svn_path_is_url(utf_arg_str))
+ {
+ /* This is the old "... TO_URL REV[:REV2]" syntax.
+ Revisions come only from this argument. (We effectively
+ pop that last argument from the end of the argument list
+ so svn_opt__args_to_target_array() can do its thang.) */
+ os->argc--;
+
+ if ((opt_baton->start_rev.kind != svn_opt_revision_unspecified)
+ || (opt_baton->end_rev.kind != svn_opt_revision_unspecified))
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Cannot specify revisions via both command-line arguments "
+ "and the --revision (-r) option"));
+
+ start_revision.kind = svn_opt_revision_unspecified;
+ end_revision.kind = svn_opt_revision_unspecified;
+ if (svn_opt_parse_revision(&start_revision, &end_revision,
+ arg_str, pool) != 0)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid revision range '%s' provided"),
+ arg_str);
+
+ SVN_ERR(resolve_revnums(&start_rev, &end_rev,
+ start_revision, end_revision));
+
+ SVN_ERR(svn_opt__args_to_target_array(
+ &targets, os,
+ apr_array_make(pool, 1, sizeof(const char *)), pool));
+ if (targets->nelts != 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ to_url = APR_ARRAY_IDX(targets, 0, const char *);
+ from_url = NULL;
+ }
+ }
+
+ if (! to_url)
+ {
+ /* This is the "... TO_URL SOURCE_URL" syntax. Revisions
+ come only from the --revision parameter. */
+ SVN_ERR(resolve_revnums(&start_rev, &end_rev,
+ opt_baton->start_rev, opt_baton->end_rev));
+
+ SVN_ERR(svn_opt__args_to_target_array(
+ &targets, os,
+ apr_array_make(pool, 2, sizeof(const char *)), pool));
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+ to_url = APR_ARRAY_IDX(targets, 0, const char *);
+ if (targets->nelts == 2)
+ from_url = APR_ARRAY_IDX(targets, 1, const char *);
+ else
+ from_url = NULL;
+ }
+
+ if (! svn_path_is_url(to_url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' is not a URL"), to_url);
+ if (from_url && (! svn_path_is_url(from_url)))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' is not a URL"), from_url);
+
+ baton = make_subcommand_baton(opt_baton, to_url, from_url,
+ start_rev, end_rev, pool);
+ SVN_ERR(open_target_session(&to_session, baton, pool));
+ if (opt_baton->disable_locking)
+ SVN_ERR(do_copy_revprops(to_session, baton, pool));
+ else
+ SVN_ERR(with_locked(to_session, do_copy_revprops, baton,
+ opt_baton->steal_lock, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** `svnsync info' ***/
+
+
+/* SUBCOMMAND: info */
+static svn_error_t *
+info_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool)
+{
+ svn_ra_session_t *to_session;
+ opt_baton_t *opt_baton = b;
+ apr_array_header_t *targets;
+ subcommand_baton_t *baton;
+ const char *to_url;
+ apr_hash_t *props;
+ svn_string_t *from_url, *from_uuid, *last_merged_rev;
+
+ SVN_ERR(svn_opt__args_to_target_array(&targets, os,
+ apr_array_make(pool, 0,
+ sizeof(const char *)),
+ pool));
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ if (targets->nelts > 1)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+ /* Get the mirror repository URL, and verify that it is URL-ish. */
+ to_url = APR_ARRAY_IDX(targets, 0, const char *);
+ if (! svn_path_is_url(to_url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' is not a URL"), to_url);
+
+ /* Open an RA session to the mirror repository URL. */
+ baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool);
+ SVN_ERR(open_target_session(&to_session, baton, pool));
+
+ SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
+
+ from_url = svn_hash_gets(props, SVNSYNC_PROP_FROM_URL);
+
+ if (! from_url)
+ return svn_error_createf
+ (SVN_ERR_BAD_URL, NULL,
+ _("Repository '%s' is not initialized for synchronization"), to_url);
+
+ from_uuid = svn_hash_gets(props, SVNSYNC_PROP_FROM_UUID);
+ last_merged_rev = svn_hash_gets(props, SVNSYNC_PROP_LAST_MERGED_REV);
+
+ /* Print the info. */
+ SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data));
+ if (from_uuid)
+ SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"),
+ from_uuid->data));
+ if (last_merged_rev)
+ SVN_ERR(svn_cmdline_printf(pool, _("Last Merged Revision: %s\n"),
+ last_merged_rev->data));
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** `svnsync help' ***/
+
+
+/* SUBCOMMAND: help */
+static svn_error_t *
+help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ opt_baton_t *opt_baton = baton;
+
+ const char *header =
+ _("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n"
+ "Type 'svnsync help <subcommand>' for help on a specific subcommand.\n"
+ "Type 'svnsync --version' to see the program version and RA modules.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ const char *ra_desc_start
+ = _("The following repository access (RA) modules are available:\n\n");
+
+ svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
+ pool);
+
+ SVN_ERR(svn_ra_print_modules(version_footer, pool));
+
+ SVN_ERR(svn_opt_print_help4(os, "svnsync",
+ opt_baton ? opt_baton->version : FALSE,
+ opt_baton ? opt_baton->quiet : FALSE,
+ /*###opt_state ? opt_state->verbose :*/ FALSE,
+ version_footer->data, header,
+ svnsync_cmd_table, svnsync_options, NULL,
+ NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Main ***/
+
+int
+main(int argc, const char *argv[])
+{
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+ apr_array_header_t *received_opts;
+ opt_baton_t opt_baton;
+ svn_config_t *config;
+ apr_status_t apr_err;
+ apr_getopt_t *os;
+ apr_pool_t *pool;
+ svn_error_t *err;
+ int opt_id, i;
+ const char *username = NULL, *source_username = NULL, *sync_username = NULL;
+ const char *password = NULL, *source_password = NULL, *sync_password = NULL;
+ apr_array_header_t *config_options = NULL;
+ const char *source_prop_encoding = NULL;
+ svn_boolean_t force_interactive = FALSE;
+
+ if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
+ {
+ return EXIT_FAILURE;
+ }
+
+ err = check_lib_versions();
+ if (err)
+ return svn_cmdline_handle_exit_error(err, NULL, "svnsync: ");
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ err = svn_ra_initialize(pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+
+ /* Initialize the option baton. */
+ memset(&opt_baton, 0, sizeof(opt_baton));
+ opt_baton.start_rev.kind = svn_opt_revision_unspecified;
+ opt_baton.end_rev.kind = svn_opt_revision_unspecified;
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ if (argc <= 1)
+ {
+ SVN_INT_ERR(help_cmd(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ err = svn_cmdline__getopt_init(&os, argc, argv, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+
+ os->interleave = 1;
+
+ for (;;)
+ {
+ const char *opt_arg;
+ svn_error_t* opt_err = NULL;
+
+ apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
+ if (APR_STATUS_IS_EOF(apr_err))
+ break;
+ else if (apr_err)
+ {
+ SVN_INT_ERR(help_cmd(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ APR_ARRAY_PUSH(received_opts, int) = opt_id;
+
+ switch (opt_id)
+ {
+ case svnsync_opt_non_interactive:
+ opt_baton.non_interactive = TRUE;
+ break;
+
+ case svnsync_opt_force_interactive:
+ force_interactive = TRUE;
+ break;
+
+ case svnsync_opt_trust_server_cert:
+ opt_baton.trust_server_cert = TRUE;
+ break;
+
+ case svnsync_opt_no_auth_cache:
+ opt_baton.no_auth_cache = TRUE;
+ break;
+
+ case svnsync_opt_auth_username:
+ opt_err = svn_utf_cstring_to_utf8(&username, opt_arg, pool);
+ break;
+
+ case svnsync_opt_auth_password:
+ opt_err = svn_utf_cstring_to_utf8(&password, opt_arg, pool);
+ break;
+
+ case svnsync_opt_source_username:
+ opt_err = svn_utf_cstring_to_utf8(&source_username, opt_arg, pool);
+ break;
+
+ case svnsync_opt_source_password:
+ opt_err = svn_utf_cstring_to_utf8(&source_password, opt_arg, pool);
+ break;
+
+ case svnsync_opt_sync_username:
+ opt_err = svn_utf_cstring_to_utf8(&sync_username, opt_arg, pool);
+ break;
+
+ case svnsync_opt_sync_password:
+ opt_err = svn_utf_cstring_to_utf8(&sync_password, opt_arg, pool);
+ break;
+
+ case svnsync_opt_config_dir:
+ {
+ const char *path_utf8;
+ opt_err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool);
+
+ if (!opt_err)
+ opt_baton.config_dir = svn_dirent_internal_style(path_utf8, pool);
+ }
+ break;
+ case svnsync_opt_config_options:
+ if (!config_options)
+ config_options =
+ apr_array_make(pool, 1,
+ sizeof(svn_cmdline__config_argument_t*));
+
+ err = svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool);
+ if (!err)
+ err = svn_cmdline__parse_config_option(config_options,
+ opt_arg, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+ break;
+
+ case svnsync_opt_source_prop_encoding:
+ opt_err = svn_utf_cstring_to_utf8(&source_prop_encoding, opt_arg,
+ pool);
+ break;
+
+ case svnsync_opt_disable_locking:
+ opt_baton.disable_locking = TRUE;
+ break;
+
+ case svnsync_opt_steal_lock:
+ opt_baton.steal_lock = TRUE;
+ break;
+
+ case svnsync_opt_version:
+ opt_baton.version = TRUE;
+ break;
+
+ case svnsync_opt_allow_non_empty:
+ opt_baton.allow_non_empty = TRUE;
+ break;
+
+ case 'q':
+ opt_baton.quiet = TRUE;
+ break;
+
+ case 'r':
+ if (svn_opt_parse_revision(&opt_baton.start_rev,
+ &opt_baton.end_rev,
+ opt_arg, pool) != 0)
+ {
+ const char *utf8_opt_arg;
+ err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
+ if (! err)
+ err = svn_error_createf(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error in revision argument '%s'"),
+ utf8_opt_arg);
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+ }
+
+ /* We only allow numbers and 'HEAD'. */
+ if (((opt_baton.start_rev.kind != svn_opt_revision_number) &&
+ (opt_baton.start_rev.kind != svn_opt_revision_head))
+ || ((opt_baton.end_rev.kind != svn_opt_revision_number) &&
+ (opt_baton.end_rev.kind != svn_opt_revision_head) &&
+ (opt_baton.end_rev.kind != svn_opt_revision_unspecified)))
+ {
+ err = svn_error_createf(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid revision range '%s' provided"), opt_arg);
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+ }
+ break;
+
+ case '?':
+ case 'h':
+ opt_baton.help = TRUE;
+ break;
+
+ default:
+ {
+ SVN_INT_ERR(help_cmd(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if(opt_err)
+ return svn_cmdline_handle_exit_error(opt_err, pool, "svnsync: ");
+ }
+
+ if (opt_baton.help)
+ subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
+
+ /* The --non-interactive and --force-interactive options are mutually
+ * exclusive. */
+ if (opt_baton.non_interactive && force_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--non-interactive and --force-interactive "
+ "are mutually exclusive"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+ }
+ else
+ opt_baton.non_interactive = !svn_cmdline__be_interactive(
+ opt_baton.non_interactive,
+ force_interactive);
+
+ /* Disallow the mixing --username/password with their --source- and
+ --sync- variants. Treat "--username FOO" as "--source-username
+ FOO --sync-username FOO"; ditto for "--password FOO". */
+ if ((username || password)
+ && (source_username || sync_username
+ || source_password || sync_password))
+ {
+ err = svn_error_create
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Cannot use --username or --password with any of "
+ "--source-username, --source-password, --sync-username, "
+ "or --sync-password.\n"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+ }
+ if (username)
+ {
+ source_username = username;
+ sync_username = username;
+ }
+ if (password)
+ {
+ source_password = password;
+ sync_password = password;
+ }
+ opt_baton.source_username = source_username;
+ opt_baton.source_password = source_password;
+ opt_baton.sync_username = sync_username;
+ opt_baton.sync_password = sync_password;
+
+ /* Disallow mixing of --steal-lock and --disable-locking. */
+ if (opt_baton.steal_lock && opt_baton.disable_locking)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--disable-locking and --steal-lock are "
+ "mutually exclusive"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+ }
+
+ /* --trust-server-cert can only be used with --non-interactive */
+ if (opt_baton.trust_server_cert && !opt_baton.non_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--trust-server-cert requires "
+ "--non-interactive"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+ }
+
+ err = svn_config_ensure(opt_baton.config_dir, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "synsync: ");
+
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_baton.version)
+ {
+ /* Use the "help" subcommand to handle "--version". */
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+ { "--version", help_cmd, {0}, "",
+ {svnsync_opt_version, /* must accept its own option */
+ 'q', /* --quiet */
+ } };
+
+ subcommand = &pseudo_cmd;
+ }
+ else
+ {
+ SVN_INT_ERR(help_cmd(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ const char *first_arg = os->argv[os->ind++];
+ subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
+ first_arg);
+ if (subcommand == NULL)
+ {
+ SVN_INT_ERR(help_cmd(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ for (i = 0; i < received_opts->nelts; ++i)
+ {
+ opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
+ {
+ const char *optstr;
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
+ pool);
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ {
+ SVN_INT_ERR(help_cmd(NULL, NULL, pool));
+ }
+ else
+ {
+ err = svn_error_createf
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svnsync help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name);
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+ }
+ }
+ }
+
+ err = svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+
+ /* Update the options in the config */
+ if (config_options)
+ {
+ svn_error_clear(
+ svn_cmdline__apply_config_options(opt_baton.config, config_options,
+ "svnsync: ", "--config-option"));
+ }
+
+ config = svn_hash_gets(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG);
+
+ opt_baton.source_prop_encoding = source_prop_encoding;
+
+ apr_signal(SIGINT, signal_handler);
+
+#ifdef SIGBREAK
+ /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
+ apr_signal(SIGBREAK, signal_handler);
+#endif
+
+#ifdef SIGHUP
+ apr_signal(SIGHUP, signal_handler);
+#endif
+
+#ifdef SIGTERM
+ apr_signal(SIGTERM, signal_handler);
+#endif
+
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it,
+ otherwise working with large files when compiled against an APR
+ that doesn't have large file support will crash the program,
+ which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ err = svn_cmdline_create_auth_baton(&opt_baton.source_auth_baton,
+ opt_baton.non_interactive,
+ opt_baton.source_username,
+ opt_baton.source_password,
+ opt_baton.config_dir,
+ opt_baton.no_auth_cache,
+ opt_baton.trust_server_cert,
+ config,
+ check_cancel, NULL,
+ pool);
+ if (! err)
+ err = svn_cmdline_create_auth_baton(&opt_baton.sync_auth_baton,
+ opt_baton.non_interactive,
+ opt_baton.sync_username,
+ opt_baton.sync_password,
+ opt_baton.config_dir,
+ opt_baton.no_auth_cache,
+ opt_baton.trust_server_cert,
+ config,
+ check_cancel, NULL,
+ pool);
+ if (! err)
+ err = (*subcommand->cmd_func)(os, &opt_baton, pool);
+ if (err)
+ {
+ /* For argument-related problems, suggest using the 'help'
+ subcommand. */
+ if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
+ || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Try 'svnsync help' for more info"));
+ }
+
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+ }
+
+ svn_pool_destroy(pool);
+
+ return EXIT_SUCCESS;
+}
diff --git a/contrib/subversion/subversion/svnsync/sync.c b/contrib/subversion/subversion/svnsync/sync.c
new file mode 100644
index 0000000..5d86ac2
--- /dev/null
+++ b/contrib/subversion/subversion/svnsync/sync.c
@@ -0,0 +1,643 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_config.h"
+#include "svn_pools.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_auth.h"
+#include "svn_opt.h"
+#include "svn_ra.h"
+#include "svn_utf.h"
+#include "svn_subst.h"
+#include "svn_string.h"
+
+#include "sync.h"
+
+#include "svn_private_config.h"
+
+#include <apr_network_io.h>
+#include <apr_signal.h>
+#include <apr_uuid.h>
+
+
+/* Normalize the encoding and line ending style of *STR, so that it contains
+ * only LF (\n) line endings and is encoded in UTF-8. After return, *STR may
+ * point at a new svn_string_t* allocated in RESULT_POOL.
+ *
+ * If SOURCE_PROP_ENCODING is NULL, then *STR is presumed to be encoded in
+ * UTF-8.
+ *
+ * *WAS_NORMALIZED is set to TRUE when *STR needed line ending normalization.
+ * Otherwise it is set to FALSE.
+ *
+ * SCRATCH_POOL is used for temporary allocations.
+ */
+static svn_error_t *
+normalize_string(const svn_string_t **str,
+ svn_boolean_t *was_normalized,
+ const char *source_prop_encoding,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_string_t *new_str;
+
+ *was_normalized = FALSE;
+
+ if (*str == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT((*str)->data != NULL);
+
+ if (source_prop_encoding == NULL)
+ source_prop_encoding = "UTF-8";
+
+ new_str = NULL;
+ SVN_ERR(svn_subst_translate_string2(&new_str, NULL, was_normalized,
+ *str, source_prop_encoding, TRUE,
+ result_pool, scratch_pool));
+ *str = new_str;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Normalize the encoding and line ending style of the values of properties
+ * in REV_PROPS that "need translation" (according to
+ * svn_prop_needs_translation(), which is currently all svn:* props) so that
+ * they are encoded in UTF-8 and contain only LF (\n) line endings.
+ *
+ * The number of properties that needed line ending normalization is returned in
+ * *NORMALIZED_COUNT.
+ *
+ * No re-encoding is performed if SOURCE_PROP_ENCODING is NULL.
+ */
+svn_error_t *
+svnsync_normalize_revprops(apr_hash_t *rev_props,
+ int *normalized_count,
+ const char *source_prop_encoding,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ *normalized_count = 0;
+
+ for (hi = apr_hash_first(pool, rev_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ const svn_string_t *propval = svn__apr_hash_index_val(hi);
+
+ if (svn_prop_needs_translation(propname))
+ {
+ svn_boolean_t was_normalized;
+ SVN_ERR(normalize_string(&propval, &was_normalized,
+ source_prop_encoding, pool, pool));
+
+ /* Replace the existing prop value. */
+ svn_hash_sets(rev_props, propname, propval);
+
+ if (was_normalized)
+ (*normalized_count)++; /* Count it. */
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/*** Synchronization Editor ***/
+
+/* This editor has a couple of jobs.
+ *
+ * First, it needs to filter out the propchanges that can't be passed over
+ * libsvn_ra.
+ *
+ * Second, it needs to adjust for the fact that we might not actually have
+ * permission to see all of the data from the remote repository, which means
+ * we could get revisions that are totally empty from our point of view.
+ *
+ * Third, it needs to adjust copyfrom paths, adding the root url for the
+ * destination repository to the beginning of them.
+ */
+
+
+/* Edit baton */
+typedef struct edit_baton_t {
+ const svn_delta_editor_t *wrapped_editor;
+ void *wrapped_edit_baton;
+ const char *to_url; /* URL we're copying into, for correct copyfrom URLs */
+ const char *source_prop_encoding;
+ svn_boolean_t called_open_root;
+ svn_boolean_t got_textdeltas;
+ svn_revnum_t base_revision;
+ svn_boolean_t quiet;
+ svn_boolean_t strip_mergeinfo; /* Are we stripping svn:mergeinfo? */
+ svn_boolean_t migrate_svnmerge; /* Are we converting svnmerge.py data? */
+ svn_boolean_t mergeinfo_stripped; /* Did we strip svn:mergeinfo? */
+ svn_boolean_t svnmerge_migrated; /* Did we convert svnmerge.py data? */
+ svn_boolean_t svnmerge_blocked; /* Was there any blocked svnmerge data? */
+ int *normalized_node_props_counter; /* Where to count normalizations? */
+} edit_baton_t;
+
+
+/* A dual-purpose baton for files and directories. */
+typedef struct node_baton_t {
+ void *edit_baton;
+ void *wrapped_node_baton;
+} node_baton_t;
+
+
+/*** Editor vtable functions ***/
+
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ edit_baton_t *eb = edit_baton;
+ return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
+ target_revision, pool);
+}
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ edit_baton_t *eb = edit_baton;
+ node_baton_t *dir_baton = apr_palloc(pool, sizeof(*dir_baton));
+
+ SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
+ base_revision, pool,
+ &dir_baton->wrapped_node_baton));
+
+ eb->called_open_root = TRUE;
+ dir_baton->edit_baton = edit_baton;
+ *root_baton = dir_baton;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t base_revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ node_baton_t *pb = parent_baton;
+ edit_baton_t *eb = pb->edit_baton;
+
+ return eb->wrapped_editor->delete_entry(path, base_revision,
+ pb->wrapped_node_baton, pool);
+}
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ node_baton_t *pb = parent_baton;
+ edit_baton_t *eb = pb->edit_baton;
+ node_baton_t *b = apr_palloc(pool, sizeof(*b));
+
+ /* if copyfrom_path is an fspath create a proper uri */
+ if (copyfrom_path && copyfrom_path[0] == '/')
+ copyfrom_path = svn_path_url_add_component2(eb->to_url,
+ copyfrom_path + 1, pool);
+
+ SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
+ copyfrom_path,
+ copyfrom_rev, pool,
+ &b->wrapped_node_baton));
+
+ b->edit_baton = eb;
+ *child_baton = b;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ node_baton_t *pb = parent_baton;
+ edit_baton_t *eb = pb->edit_baton;
+ node_baton_t *db = apr_palloc(pool, sizeof(*db));
+
+ SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_node_baton,
+ base_revision, pool,
+ &db->wrapped_node_baton));
+
+ db->edit_baton = eb;
+ *child_baton = db;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ node_baton_t *pb = parent_baton;
+ edit_baton_t *eb = pb->edit_baton;
+ node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
+
+ /* if copyfrom_path is an fspath create a proper uri */
+ if (copyfrom_path && copyfrom_path[0] == '/')
+ copyfrom_path = svn_path_url_add_component2(eb->to_url,
+ copyfrom_path + 1, pool);
+
+ SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_node_baton,
+ copyfrom_path, copyfrom_rev,
+ pool, &fb->wrapped_node_baton));
+
+ fb->edit_baton = eb;
+ *file_baton = fb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ node_baton_t *pb = parent_baton;
+ edit_baton_t *eb = pb->edit_baton;
+ node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
+
+ SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton,
+ base_revision, pool,
+ &fb->wrapped_node_baton));
+
+ fb->edit_baton = eb;
+ *file_baton = fb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ node_baton_t *fb = file_baton;
+ edit_baton_t *eb = fb->edit_baton;
+
+ if (! eb->quiet)
+ {
+ if (! eb->got_textdeltas)
+ SVN_ERR(svn_cmdline_printf(pool, _("Transmitting file data ")));
+ SVN_ERR(svn_cmdline_printf(pool, "."));
+ SVN_ERR(svn_cmdline_fflush(stdout));
+ }
+
+ eb->got_textdeltas = TRUE;
+ return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton,
+ base_checksum, pool,
+ handler, handler_baton);
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ node_baton_t *fb = file_baton;
+ edit_baton_t *eb = fb->edit_baton;
+ return eb->wrapped_editor->close_file(fb->wrapped_node_baton,
+ text_checksum, pool);
+}
+
+static svn_error_t *
+absent_file(const char *path,
+ void *file_baton,
+ apr_pool_t *pool)
+{
+ node_baton_t *fb = file_baton;
+ edit_baton_t *eb = fb->edit_baton;
+ return eb->wrapped_editor->absent_file(path, fb->wrapped_node_baton, pool);
+}
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ node_baton_t *db = dir_baton;
+ edit_baton_t *eb = db->edit_baton;
+ return eb->wrapped_editor->close_directory(db->wrapped_node_baton, pool);
+}
+
+static svn_error_t *
+absent_directory(const char *path,
+ void *dir_baton,
+ apr_pool_t *pool)
+{
+ node_baton_t *db = dir_baton;
+ edit_baton_t *eb = db->edit_baton;
+ return eb->wrapped_editor->absent_directory(path, db->wrapped_node_baton,
+ pool);
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ node_baton_t *fb = file_baton;
+ edit_baton_t *eb = fb->edit_baton;
+
+ /* only regular properties can pass over libsvn_ra */
+ if (svn_property_kind2(name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+
+ /* Maybe drop svn:mergeinfo. */
+ if (eb->strip_mergeinfo && (strcmp(name, SVN_PROP_MERGEINFO) == 0))
+ {
+ eb->mergeinfo_stripped = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Maybe drop (errantly set, as this is a file) svnmerge.py properties. */
+ if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-integrated") == 0))
+ {
+ eb->svnmerge_migrated = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Remember if we see any svnmerge-blocked properties. (They really
+ shouldn't be here, as this is a file, but whatever...) */
+ if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-blocked") == 0))
+ {
+ eb->svnmerge_blocked = TRUE;
+ }
+
+ /* Normalize svn:* properties as necessary. */
+ if (svn_prop_needs_translation(name))
+ {
+ svn_boolean_t was_normalized;
+ SVN_ERR(normalize_string(&value, &was_normalized,
+ eb->source_prop_encoding, pool, pool));
+ if (was_normalized)
+ (*(eb->normalized_node_props_counter))++;
+ }
+
+ return eb->wrapped_editor->change_file_prop(fb->wrapped_node_baton,
+ name, value, pool);
+}
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ node_baton_t *db = dir_baton;
+ edit_baton_t *eb = db->edit_baton;
+
+ /* Only regular properties can pass over libsvn_ra */
+ if (svn_property_kind2(name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+
+ /* Maybe drop svn:mergeinfo. */
+ if (eb->strip_mergeinfo && (strcmp(name, SVN_PROP_MERGEINFO) == 0))
+ {
+ eb->mergeinfo_stripped = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Maybe convert svnmerge-integrated data into svn:mergeinfo. (We
+ ignore svnmerge-blocked for now.) */
+ /* ### FIXME: Consult the mirror repository's HEAD prop values and
+ ### merge svn:mergeinfo, svnmerge-integrated, and svnmerge-blocked. */
+ if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-integrated") == 0))
+ {
+ if (value)
+ {
+ /* svnmerge-integrated differs from svn:mergeinfo in a pair
+ of ways. First, it can use tabs, newlines, or spaces to
+ delimit source information. Secondly, the source paths
+ are relative URLs, whereas svn:mergeinfo uses relative
+ paths (not URI-encoded). */
+ svn_error_t *err;
+ svn_stringbuf_t *mergeinfo_buf = svn_stringbuf_create_empty(pool);
+ svn_mergeinfo_t mergeinfo;
+ int i;
+ apr_array_header_t *sources =
+ svn_cstring_split(value->data, " \t\n", TRUE, pool);
+ svn_string_t *new_value;
+
+ for (i = 0; i < sources->nelts; i++)
+ {
+ const char *rel_path;
+ apr_array_header_t *path_revs =
+ svn_cstring_split(APR_ARRAY_IDX(sources, i, const char *),
+ ":", TRUE, pool);
+
+ /* ### TODO: Warn? */
+ if (path_revs->nelts != 2)
+ continue;
+
+ /* Append this source's mergeinfo data. */
+ rel_path = APR_ARRAY_IDX(path_revs, 0, const char *);
+ rel_path = svn_path_uri_decode(rel_path, pool);
+ svn_stringbuf_appendcstr(mergeinfo_buf, rel_path);
+ svn_stringbuf_appendcstr(mergeinfo_buf, ":");
+ svn_stringbuf_appendcstr(mergeinfo_buf,
+ APR_ARRAY_IDX(path_revs, 1,
+ const char *));
+ svn_stringbuf_appendcstr(mergeinfo_buf, "\n");
+ }
+
+ /* Try to parse the mergeinfo string we've created, just to
+ check for bogosity. If all goes well, we'll unparse it
+ again and use that as our property value. */
+ err = svn_mergeinfo_parse(&mergeinfo, mergeinfo_buf->data, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(svn_mergeinfo_to_string(&new_value, mergeinfo, pool));
+ value = new_value;
+ }
+ name = SVN_PROP_MERGEINFO;
+ eb->svnmerge_migrated = TRUE;
+ }
+
+ /* Remember if we see any svnmerge-blocked properties. */
+ if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-blocked") == 0))
+ {
+ eb->svnmerge_blocked = TRUE;
+ }
+
+ /* Normalize svn:* properties as necessary. */
+ if (svn_prop_needs_translation(name))
+ {
+ svn_boolean_t was_normalized;
+ SVN_ERR(normalize_string(&value, &was_normalized, eb->source_prop_encoding,
+ pool, pool));
+ if (was_normalized)
+ (*(eb->normalized_node_props_counter))++;
+ }
+
+ return eb->wrapped_editor->change_dir_prop(db->wrapped_node_baton,
+ name, value, pool);
+}
+
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ edit_baton_t *eb = edit_baton;
+
+ /* If we haven't opened the root yet, that means we're transfering
+ an empty revision, probably because we aren't allowed to see the
+ contents for some reason. In any event, we need to open the root
+ and close it again, before we can close out the edit, or the
+ commit will fail. */
+
+ if (! eb->called_open_root)
+ {
+ void *baton;
+ SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
+ eb->base_revision, pool,
+ &baton));
+ SVN_ERR(eb->wrapped_editor->close_directory(baton, pool));
+ }
+
+ if (! eb->quiet)
+ {
+ if (eb->got_textdeltas)
+ SVN_ERR(svn_cmdline_printf(pool, "\n"));
+ if (eb->mergeinfo_stripped)
+ SVN_ERR(svn_cmdline_printf(pool,
+ "NOTE: Dropped Subversion mergeinfo "
+ "from this revision.\n"));
+ if (eb->svnmerge_migrated)
+ SVN_ERR(svn_cmdline_printf(pool,
+ "NOTE: Migrated 'svnmerge-integrated' in "
+ "this revision.\n"));
+ if (eb->svnmerge_blocked)
+ SVN_ERR(svn_cmdline_printf(pool,
+ "NOTE: Saw 'svnmerge-blocked' in this "
+ "revision (but didn't migrate it).\n"));
+ }
+
+ return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
+}
+
+static svn_error_t *
+abort_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ edit_baton_t *eb = edit_baton;
+ return eb->wrapped_editor->abort_edit(eb->wrapped_edit_baton, pool);
+}
+
+
+/*** Editor factory function ***/
+
+svn_error_t *
+svnsync_get_sync_editor(const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_edit_baton,
+ svn_revnum_t base_revision,
+ const char *to_url,
+ const char *source_prop_encoding,
+ svn_boolean_t quiet,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ int *normalized_node_props_counter,
+ apr_pool_t *pool)
+{
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
+ edit_baton_t *eb = apr_pcalloc(pool, sizeof(*eb));
+
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->open_root = open_root;
+ tree_editor->delete_entry = delete_entry;
+ tree_editor->add_directory = add_directory;
+ tree_editor->open_directory = open_directory;
+ tree_editor->change_dir_prop = change_dir_prop;
+ tree_editor->close_directory = close_directory;
+ tree_editor->absent_directory = absent_directory;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->absent_file = absent_file;
+ tree_editor->close_edit = close_edit;
+ tree_editor->abort_edit = abort_edit;
+
+ eb->wrapped_editor = wrapped_editor;
+ eb->wrapped_edit_baton = wrapped_edit_baton;
+ eb->base_revision = base_revision;
+ eb->to_url = to_url;
+ eb->source_prop_encoding = source_prop_encoding;
+ eb->quiet = quiet;
+ eb->normalized_node_props_counter = normalized_node_props_counter;
+
+ if (getenv("SVNSYNC_UNSUPPORTED_STRIP_MERGEINFO"))
+ {
+ eb->strip_mergeinfo = TRUE;
+ }
+ if (getenv("SVNSYNC_UNSUPPORTED_MIGRATE_SVNMERGE"))
+ {
+ /* Current we can't merge property values. That's only possible
+ if all the properties to be merged were always modified in
+ exactly the same revisions, or if we allow ourselves to
+ lookup the current state of properties in the sync
+ destination. So for now, migrating svnmerge.py data implies
+ stripping pre-existing svn:mergeinfo. */
+ /* ### FIXME: Do a real migration by consulting the mirror
+ ### repository's HEAD propvalues and merging svn:mergeinfo,
+ ### svnmerge-integrated, and svnmerge-blocked together. */
+ eb->migrate_svnmerge = TRUE;
+ eb->strip_mergeinfo = TRUE;
+ }
+
+ *editor = tree_editor;
+ *edit_baton = eb;
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/contrib/subversion/subversion/svnsync/sync.h b/contrib/subversion/subversion/svnsync/sync.h
new file mode 100644
index 0000000..8afc263
--- /dev/null
+++ b/contrib/subversion/subversion/svnsync/sync.h
@@ -0,0 +1,85 @@
+/*
+ * sync.h : The synchronization editor for svnsync.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SYNC_H
+#define SYNC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#include "svn_types.h"
+#include "svn_delta.h"
+
+
+/* Normalize the encoding and line ending style of the values of properties
+ * in REV_PROPS that "need translation" (according to
+ * svn_prop_needs_translation(), which is currently all svn:* props) so that
+ * they are encoded in UTF-8 and contain only LF (\n) line endings.
+ *
+ * The number of properties that needed line ending normalization is returned in
+ * *NORMALIZED_COUNT.
+ *
+ * No re-encoding is performed if SOURCE_PROP_ENCODING is NULL.
+ */
+svn_error_t *
+svnsync_normalize_revprops(apr_hash_t *rev_props,
+ int *normalized_count,
+ const char *source_prop_encoding,
+ apr_pool_t *pool);
+
+
+/* Set WRAPPED_EDITOR and WRAPPED_EDIT_BATON to an editor/baton pair
+ * that wraps our own commit EDITOR/EDIT_BATON. BASE_REVISION is the
+ * revision on which the driver of this returned editor will be basing
+ * the commit. TO_URL is the URL of the root of the repository into
+ * which the commit is being made.
+ *
+ * If SOURCE_PROP_ENCODING is NULL, then property values are presumed to be
+ * encoded in UTF-8 and are not re-encoded. Otherwise, the property values are
+ * presumed to be encoded in SOURCE_PROP_ENCODING, and are normalized to UTF-8.
+ *
+ * As the sync editor encounters property values, it might see the need to
+ * normalize them (re-encode and/or change to LF line endings). Each carried-out
+ * line ending normalization adds 1 to the *NORMALIZED_NODE_PROPS_COUNTER
+ * (for notification).
+ */
+svn_error_t *
+svnsync_get_sync_editor(const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_edit_baton,
+ svn_revnum_t base_revision,
+ const char *to_url,
+ const char *source_prop_encoding,
+ svn_boolean_t quiet,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ int *normalized_node_props_counter,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SYNC_H */
diff --git a/contrib/subversion/subversion/svnversion/svnversion.1 b/contrib/subversion/subversion/svnversion/svnversion.1
new file mode 100644
index 0000000..20edaa5
--- /dev/null
+++ b/contrib/subversion/subversion/svnversion/svnversion.1
@@ -0,0 +1,47 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svnversion 1
+.SH NAME
+svnversion \- Produce a compact version identifier for a working copy.
+.SH SYNOPSIS
+.TP
+\fBsvnversion\fP [\fIoptions\fP] [\fIwc_path\fP [\fItrail_url\fP]]
+.SH OVERVIEW
+Subversion is a version control system, which allows you to keep old
+versions of files and directories (usually source code), keep a log of
+who, when, and why changes occurred, etc., like CVS, RCS or SCCS.
+\fBSubversion\fP keeps a single copy of the master sources. This copy
+is called the source ``repository''; it contains all the information
+to permit extracting previous versions of those files at any time.
+
+For more information about the Subversion project, visit
+http://subversion.apache.org.
+
+Documentation for Subversion and its tools, including detailed usage
+explanations of the \fBsvn\fP, \fBsvnadmin\fP, \fBsvnserve\fP and
+\fBsvnlook\fP programs, historical background, philosophical
+approaches and reasonings, etc., can be found at
+http://svnbook.red-bean.com/.
+
+Run `svnversion --help' to access the built-in tool documentation.
diff --git a/contrib/subversion/subversion/svnversion/svnversion.c b/contrib/subversion/subversion/svnversion/svnversion.c
new file mode 100644
index 0000000..ffee60c
--- /dev/null
+++ b/contrib/subversion/subversion/svnversion/svnversion.c
@@ -0,0 +1,300 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_cmdline.h"
+#include "svn_dirent_uri.h"
+#include "svn_pools.h"
+#include "svn_wc.h"
+#include "svn_utf.h"
+#include "svn_opt.h"
+#include "svn_version.h"
+
+#include "private/svn_opt_private.h"
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+#define SVNVERSION_OPT_VERSION SVN_OPT_FIRST_LONGOPT_ID
+
+
+static svn_error_t *
+version(svn_boolean_t quiet, apr_pool_t *pool)
+{
+ return svn_opt_print_help4(NULL, "svnversion", TRUE, quiet, FALSE,
+ NULL, NULL, NULL, NULL, NULL, NULL, pool);
+}
+
+static void
+usage(apr_pool_t *pool)
+{
+ svn_error_clear(svn_cmdline_fprintf
+ (stderr, pool, _("Type 'svnversion --help' for usage.\n")));
+ exit(1);
+}
+
+
+static void
+help(const apr_getopt_option_t *options, apr_pool_t *pool)
+{
+ svn_error_clear
+ (svn_cmdline_fprintf
+ (stdout, pool,
+ _("usage: svnversion [OPTIONS] [WC_PATH [TRAIL_URL]]\n\n"
+ " Produce a compact version identifier for the working copy path\n"
+ " WC_PATH. TRAIL_URL is the trailing portion of the URL used to\n"
+ " determine if WC_PATH itself is switched (detection of switches\n"
+ " within WC_PATH does not rely on TRAIL_URL). The version identifier\n"
+ " is written to standard output. For example:\n"
+ "\n"
+ " $ svnversion . /repos/svn/trunk\n"
+ " 4168\n"
+ "\n"
+ " The version identifier will be a single number if the working\n"
+ " copy is single revision, unmodified, not switched and with\n"
+ " a URL that matches the TRAIL_URL argument. If the working\n"
+ " copy is unusual the version identifier will be more complex:\n"
+ "\n"
+ " 4123:4168 mixed revision working copy\n"
+ " 4168M modified working copy\n"
+ " 4123S switched working copy\n"
+ " 4123P partial working copy, from a sparse checkout\n"
+ " 4123:4168MS mixed revision, modified, switched working copy\n"
+ "\n"
+ " If WC_PATH is an unversioned path, the program will output\n"
+ " 'Unversioned directory' or 'Unversioned file'. If WC_PATH is\n"
+ " an added or copied or moved path, the program will output\n"
+ " 'Uncommitted local addition, copy or move'.\n"
+ "\n"
+ " If invoked without arguments WC_PATH will be the current directory.\n"
+ "\n"
+ "Valid options:\n")));
+ while (options->description)
+ {
+ const char *optstr;
+ svn_opt_format_option(&optstr, options, TRUE, pool);
+ svn_error_clear(svn_cmdline_fprintf(stdout, pool, " %s\n", optstr));
+ ++options;
+ }
+ svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n"));
+ exit(0);
+}
+
+
+/* Version compatibility check */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_wc", svn_wc_version },
+ { NULL, NULL }
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+/*
+ * Why is this not an svn subcommand? I have this vague idea that it could
+ * be run as part of the build process, with the output embedded in the svn
+ * program. Obviously we don't want to have to run svn when building svn.
+ */
+int
+main(int argc, const char *argv[])
+{
+ const char *wc_path, *trail_url;
+ const char *local_abspath;
+ apr_pool_t *pool;
+ svn_wc_revision_status_t *res;
+ svn_boolean_t no_newline = FALSE, committed = FALSE;
+ svn_error_t *err;
+ apr_getopt_t *os;
+ svn_wc_context_t *wc_ctx;
+ svn_boolean_t quiet = FALSE;
+ svn_boolean_t is_version = FALSE;
+ const apr_getopt_option_t options[] =
+ {
+ {"no-newline", 'n', 0, N_("do not output the trailing newline")},
+ {"committed", 'c', 0, N_("last changed rather than current revisions")},
+ {"help", 'h', 0, N_("display this help")},
+ {"version", SVNVERSION_OPT_VERSION, 0,
+ N_("show program version information")},
+ {"quiet", 'q', 0,
+ N_("no progress (only errors) to stderr")},
+ {0, 0, 0, 0}
+ };
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svnversion", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ /* Check library versions */
+ err = check_lib_versions();
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnversion: ");
+
+#if defined(WIN32) || defined(__CYGWIN__)
+ /* Set the working copy administrative directory name. */
+ if (getenv("SVN_ASP_DOT_NET_HACK"))
+ {
+ err = svn_wc_set_adm_dir("_svn", pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnversion: ");
+ }
+#endif
+
+ err = svn_cmdline__getopt_init(&os, argc, argv, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnversion: ");
+
+ os->interleave = 1;
+ while (1)
+ {
+ int opt;
+ const char *arg;
+ apr_status_t status = apr_getopt_long(os, options, &opt, &arg);
+ if (APR_STATUS_IS_EOF(status))
+ break;
+ if (status != APR_SUCCESS)
+ usage(pool); /* this will exit() */
+
+ switch (opt)
+ {
+ case 'n':
+ no_newline = TRUE;
+ break;
+ case 'c':
+ committed = TRUE;
+ break;
+ case 'q':
+ quiet = TRUE;
+ break;
+ case 'h':
+ help(options, pool);
+ break;
+ case SVNVERSION_OPT_VERSION:
+ is_version = TRUE;
+ break;
+ default:
+ usage(pool); /* this will exit() */
+ }
+ }
+
+ if (is_version)
+ {
+ SVN_INT_ERR(version(quiet, pool));
+ exit(0);
+ }
+ if (os->ind > argc || os->ind < argc - 2)
+ usage(pool); /* this will exit() */
+
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&wc_path,
+ (os->ind < argc) ? os->argv[os->ind]
+ : ".",
+ pool));
+
+ SVN_INT_ERR(svn_opt__arg_canonicalize_path(&wc_path, wc_path, pool));
+ SVN_INT_ERR(svn_dirent_get_absolute(&local_abspath, wc_path, pool));
+ SVN_INT_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool));
+
+ if (os->ind+1 < argc)
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&trail_url, os->argv[os->ind+1],
+ pool));
+ else
+ trail_url = NULL;
+
+ err = svn_wc_revision_status2(&res, wc_ctx, local_abspath, trail_url,
+ committed, NULL, NULL, pool, pool);
+
+ if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
+ || err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY))
+ {
+ svn_node_kind_t kind;
+ svn_boolean_t special;
+
+ svn_error_clear(err);
+
+ SVN_INT_ERR(svn_io_check_special_path(local_abspath, &kind, &special,
+ pool));
+
+ if (special)
+ SVN_INT_ERR(svn_cmdline_printf(pool, _("Unversioned symlink%s"),
+ no_newline ? "" : "\n"));
+ else if (kind == svn_node_dir)
+ SVN_INT_ERR(svn_cmdline_printf(pool, _("Unversioned directory%s"),
+ no_newline ? "" : "\n"));
+ else if (kind == svn_node_file)
+ SVN_INT_ERR(svn_cmdline_printf(pool, _("Unversioned file%s"),
+ no_newline ? "" : "\n"));
+ else
+ {
+ SVN_INT_ERR(svn_cmdline_fprintf(stderr, pool,
+ kind == svn_node_none
+ ? _("'%s' doesn't exist\n")
+ : _("'%s' is of unknown type\n"),
+ svn_dirent_local_style(local_abspath,
+ pool)));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ svn_pool_destroy(pool);
+ return EXIT_SUCCESS;
+ }
+
+ SVN_INT_ERR(err);
+
+ if (! SVN_IS_VALID_REVNUM(res->min_rev))
+ {
+ /* Local uncommitted modifications, no revision info was found. */
+ SVN_INT_ERR(svn_cmdline_printf(pool, _("Uncommitted local addition, "
+ "copy or move%s"),
+ no_newline ? "" : "\n"));
+ svn_pool_destroy(pool);
+ return EXIT_SUCCESS;
+ }
+
+ /* Build compact '123[:456]M?S?' string. */
+ SVN_INT_ERR(svn_cmdline_printf(pool, "%ld", res->min_rev));
+ if (res->min_rev != res->max_rev)
+ SVN_INT_ERR(svn_cmdline_printf(pool, ":%ld", res->max_rev));
+ if (res->modified)
+ SVN_INT_ERR(svn_cmdline_fputs("M", stdout, pool));
+ if (res->switched)
+ SVN_INT_ERR(svn_cmdline_fputs("S", stdout, pool));
+ if (res->sparse_checkout)
+ SVN_INT_ERR(svn_cmdline_fputs("P", stdout, pool));
+
+ if (! no_newline)
+ SVN_INT_ERR(svn_cmdline_fputs("\n", stdout, pool));
+
+ svn_pool_destroy(pool);
+
+ /* Flush stdout to make sure that the user will see any printing errors. */
+ SVN_INT_ERR(svn_cmdline_fflush(stdout));
+
+ return EXIT_SUCCESS;
+}
diff --git a/contrib/subversion/win-tests.py b/contrib/subversion/win-tests.py
new file mode 100644
index 0000000..04723bd
--- /dev/null
+++ b/contrib/subversion/win-tests.py
@@ -0,0 +1,858 @@
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+"""
+Driver for running the tests on Windows.
+
+For a list of options, run this script with the --help option.
+"""
+
+# $HeadURL: http://svn.apache.org/repos/asf/subversion/branches/1.8.x/win-tests.py $
+# $LastChangedRevision: 1467191 $
+
+import os, sys, subprocess
+import filecmp
+import shutil
+import traceback
+try:
+ # Python >=3.0
+ import configparser
+except ImportError:
+ # Python <3.0
+ import ConfigParser as configparser
+import string
+import random
+
+import getopt
+try:
+ my_getopt = getopt.gnu_getopt
+except AttributeError:
+ my_getopt = getopt.getopt
+
+def _usage_exit():
+ "print usage, exit the script"
+
+ print("Driver for running the tests on Windows.")
+ print("Usage: python win-tests.py [option] [test-path]")
+ print("")
+ print("Valid options:")
+ print(" -r, --release : test the Release configuration")
+ print(" -d, --debug : test the Debug configuration (default)")
+ print(" --bin=PATH : use the svn binaries installed in PATH")
+ print(" -u URL, --url=URL : run ra_dav or ra_svn tests against URL;")
+ print(" will start svnserve for ra_svn tests")
+ print(" -v, --verbose : talk more")
+ print(" -q, --quiet : talk less")
+ print(" -f, --fs-type=type : filesystem type to use (fsfs is default)")
+ print(" -c, --cleanup : cleanup after running a test")
+ print(" -t, --test=TEST : Run the TEST test (all is default); use")
+ print(" TEST#n to run a particular test number,")
+ print(" multiples also accepted e.g. '2,4-7'")
+ print(" --log-level=LEVEL : Set log level to LEVEL (E.g. DEBUG)")
+ print(" --log-to-stdout : Write log results to stdout")
+
+ print(" --svnserve-args=list : comma-separated list of arguments for")
+ print(" svnserve")
+ print(" default is '-d,-r,<test-path-root>'")
+ print(" --asp.net-hack : use '_svn' instead of '.svn' for the admin")
+ print(" dir name")
+ print(" --httpd-dir : location where Apache HTTPD is installed")
+ print(" --httpd-port : port for Apache HTTPD; random port number")
+ print(" will be used, if not specified")
+ print(" --httpd-daemon : Run Apache httpd as daemon")
+ print(" --httpd-service : Run Apache httpd as Windows service (default)")
+ print(" --httpd-no-log : Disable httpd logging")
+ print(" --http-short-circuit : Use SVNPathAuthz short_circuit on HTTP server")
+ print(" --disable-http-v2 : Do not advertise support for HTTPv2 on server")
+ print(" --disable-bulk-updates : Disable bulk updates on HTTP server")
+ print(" --ssl-cert : Path to SSL server certificate to trust.")
+ print(" --javahl : Run the javahl tests instead of the normal tests")
+ print(" --list : print test doc strings only")
+ print(" --milestone-filter=RE : RE is a regular expression pattern that (when")
+ print(" used with --list) limits the tests listed to")
+ print(" those with an associated issue in the tracker")
+ print(" which has a target milestone that matches RE.")
+ print(" --mode-filter=TYPE : limit tests to expected TYPE = XFAIL, SKIP, PASS,")
+ print(" or 'ALL' (default)")
+ print(" --enable-sasl : enable Cyrus SASL authentication for")
+ print(" svnserve")
+ print(" -p, --parallel : run multiple tests in parallel")
+ print(" --server-minor-version : the minor version of the server being")
+ print(" tested")
+ print(" --config-file : Configuration file for tests")
+ print(" --fsfs-sharding : Specify shard size (for fsfs)")
+ print(" --fsfs-packing : Run 'svnadmin pack' automatically")
+
+ sys.exit(0)
+
+CMDLINE_TEST_SCRIPT_PATH = 'subversion/tests/cmdline/'
+CMDLINE_TEST_SCRIPT_NATIVE_PATH = CMDLINE_TEST_SCRIPT_PATH.replace('/', os.sep)
+
+sys.path.insert(0, os.path.join('build', 'generator'))
+sys.path.insert(1, 'build')
+
+import gen_win
+version_header = os.path.join('subversion', 'include', 'svn_version.h')
+cp = configparser.ConfigParser()
+cp.read('gen-make.opts')
+gen_obj = gen_win.GeneratorBase('build.conf', version_header,
+ cp.items('options'))
+all_tests = gen_obj.test_progs + gen_obj.bdb_test_progs \
+ + gen_obj.scripts + gen_obj.bdb_scripts
+client_tests = [x for x in all_tests if x.startswith(CMDLINE_TEST_SCRIPT_PATH)]
+
+svn_dlls = []
+for section in gen_obj.sections.values():
+ if section.options.get("msvc-export"):
+ dll_basename = section.name + "-" + str(gen_obj.version) + ".dll"
+ svn_dlls.append(os.path.join("subversion", section.name, dll_basename))
+
+opts, args = my_getopt(sys.argv[1:], 'hrdvqct:pu:f:',
+ ['release', 'debug', 'verbose', 'quiet', 'cleanup',
+ 'test=', 'url=', 'svnserve-args=', 'fs-type=', 'asp.net-hack',
+ 'httpd-dir=', 'httpd-port=', 'httpd-daemon',
+ 'httpd-server', 'http-short-circuit', 'httpd-no-log',
+ 'disable-http-v2', 'disable-bulk-updates', 'help',
+ 'fsfs-packing', 'fsfs-sharding=', 'javahl',
+ 'list', 'enable-sasl', 'bin=', 'parallel',
+ 'config-file=', 'server-minor-version=', 'log-level=',
+ 'log-to-stdout', 'mode-filter=', 'milestone-filter=',
+ 'ssl-cert='])
+if len(args) > 1:
+ print('Warning: non-option arguments after the first one will be ignored')
+
+# Interpret the options and set parameters
+base_url, fs_type, verbose, quiet, cleanup = None, None, None, None, None
+repo_loc = 'local repository.'
+objdir = 'Debug'
+log = 'tests.log'
+faillog = 'fails.log'
+run_svnserve = None
+svnserve_args = None
+run_httpd = None
+httpd_port = None
+httpd_service = None
+httpd_no_log = None
+http_short_circuit = False
+advertise_httpv2 = True
+http_bulk_updates = True
+list_tests = None
+milestone_filter = None
+test_javahl = None
+enable_sasl = None
+svn_bin = None
+parallel = None
+fsfs_sharding = None
+fsfs_packing = None
+server_minor_version = None
+config_file = None
+log_to_stdout = None
+mode_filter=None
+tests_to_run = []
+log_level = None
+ssl_cert = None
+
+for opt, val in opts:
+ if opt in ('-h', '--help'):
+ _usage_exit()
+ elif opt in ('-u', '--url'):
+ base_url = val
+ elif opt in ('-f', '--fs-type'):
+ fs_type = val
+ elif opt in ('-v', '--verbose'):
+ verbose = 1
+ elif opt in ('-q', '--quiet'):
+ quiet = 1
+ elif opt in ('-c', '--cleanup'):
+ cleanup = 1
+ elif opt in ('-t', '--test'):
+ tests_to_run.append(val)
+ elif opt in ['-r', '--release']:
+ objdir = 'Release'
+ elif opt in ['-d', '--debug']:
+ objdir = 'Debug'
+ elif opt == '--svnserve-args':
+ svnserve_args = val.split(',')
+ run_svnserve = 1
+ elif opt == '--asp.net-hack':
+ os.environ['SVN_ASP_DOT_NET_HACK'] = opt
+ elif opt == '--httpd-dir':
+ abs_httpd_dir = os.path.abspath(val)
+ run_httpd = 1
+ elif opt == '--httpd-port':
+ httpd_port = int(val)
+ elif opt == '--httpd-daemon':
+ httpd_service = 0
+ elif opt == '--httpd-service':
+ httpd_service = 1
+ elif opt == '--httpd-no-log':
+ httpd_no_log = 1
+ elif opt == '--http-short-circuit':
+ http_short_circuit = True
+ elif opt == '--disable-http-v2':
+ advertise_httpv2 = False
+ elif opt == '--disable-bulk-updates':
+ http_bulk_updates = False
+ elif opt == '--fsfs-sharding':
+ fsfs_sharding = int(val)
+ elif opt == '--fsfs-packing':
+ fsfs_packing = 1
+ elif opt == '--javahl':
+ test_javahl = 1
+ elif opt == '--list':
+ list_tests = 1
+ elif opt == '--milestone-filter':
+ milestone_filter = val
+ elif opt == '--mode-filter':
+ mode_filter = val
+ elif opt == '--enable-sasl':
+ enable_sasl = 1
+ base_url = "svn://localhost/"
+ elif opt == '--server-minor-version':
+ server_minor_version = val
+ elif opt == '--bin':
+ svn_bin = val
+ elif opt in ('-p', '--parallel'):
+ parallel = 1
+ elif opt in ('--config-file'):
+ config_file = val
+ elif opt == '--log-to-stdout':
+ log_to_stdout = 1
+ elif opt == '--log-level':
+ log_level = val
+ elif opt == '--ssl-cert':
+ ssl_cert = val
+
+# Calculate the source and test directory names
+abs_srcdir = os.path.abspath("")
+abs_objdir = os.path.join(abs_srcdir, objdir)
+if len(args) == 0:
+ abs_builddir = abs_objdir
+ create_dirs = 0
+else:
+ abs_builddir = os.path.abspath(args[0])
+ create_dirs = 1
+
+# Default to fsfs explicitly
+if not fs_type:
+ fs_type = 'fsfs'
+
+# Don't run bdb tests if they want to test fsfs
+if fs_type == 'fsfs':
+ all_tests = gen_obj.test_progs + gen_obj.scripts
+
+if run_httpd:
+ if not httpd_port:
+ httpd_port = random.randrange(1024, 30000)
+ if not base_url:
+ base_url = 'http://localhost:' + str(httpd_port)
+
+if base_url:
+ repo_loc = 'remote repository ' + base_url + '.'
+ if base_url[:4] == 'http':
+ log = 'dav-tests.log'
+ faillog = 'dav-fails.log'
+ elif base_url[:3] == 'svn':
+ log = 'svn-tests.log'
+ faillog = 'svn-fails.log'
+ run_svnserve = 1
+ else:
+ # Don't know this scheme, but who're we to judge whether it's
+ # correct or not?
+ log = 'url-tests.log'
+ faillog = 'url-fails.log'
+
+# Have to move the executables where the tests expect them to be
+copied_execs = [] # Store copied exec files to avoid the final dir scan
+
+def create_target_dir(dirname):
+ tgt_dir = os.path.join(abs_builddir, dirname)
+ if not os.path.exists(tgt_dir):
+ if verbose:
+ print("mkdir: %s" % tgt_dir)
+ os.makedirs(tgt_dir)
+
+def copy_changed_file(src, tgt):
+ if not os.path.isfile(src):
+ print('Could not find ' + src)
+ sys.exit(1)
+ if os.path.isdir(tgt):
+ tgt = os.path.join(tgt, os.path.basename(src))
+ if os.path.exists(tgt):
+ assert os.path.isfile(tgt)
+ if filecmp.cmp(src, tgt):
+ if verbose:
+ print("same: %s" % src)
+ print(" and: %s" % tgt)
+ return 0
+ if verbose:
+ print("copy: %s" % src)
+ print(" to: %s" % tgt)
+ shutil.copy(src, tgt)
+ return 1
+
+def copy_execs(baton, dirname, names):
+ copied_execs = baton
+ for name in names:
+ if not name.endswith('.exe'):
+ continue
+ src = os.path.join(dirname, name)
+ tgt = os.path.join(abs_builddir, dirname, name)
+ create_target_dir(dirname)
+ if copy_changed_file(src, tgt):
+ copied_execs.append(tgt)
+
+def locate_libs():
+ "Move DLLs to a known location and set env vars"
+
+ dlls = []
+
+ # look for APR 1.x dll's and use those if found
+ apr_test_path = os.path.join(gen_obj.apr_path, objdir, 'libapr-1.dll')
+ if os.path.exists(apr_test_path):
+ suffix = "-1"
+ else:
+ suffix = ""
+
+ if cp.has_option('options', '--with-static-apr'):
+ dlls.append(os.path.join(gen_obj.apr_path, objdir,
+ 'libapr%s.dll' % (suffix)))
+ dlls.append(os.path.join(gen_obj.apr_util_path, objdir,
+ 'libaprutil%s.dll' % (suffix)))
+
+ if gen_obj.libintl_path is not None:
+ dlls.append(os.path.join(gen_obj.libintl_path, 'bin', 'intl3_svn.dll'))
+
+ if gen_obj.bdb_lib is not None:
+ partial_path = os.path.join(gen_obj.bdb_path, 'bin', gen_obj.bdb_lib)
+ if objdir == 'Debug':
+ dlls.append(partial_path + 'd.dll')
+ else:
+ dlls.append(partial_path + '.dll')
+
+ if gen_obj.sasl_path is not None:
+ dlls.append(os.path.join(gen_obj.sasl_path, 'lib', 'libsasl.dll'))
+
+ for dll in dlls:
+ copy_changed_file(dll, abs_objdir)
+
+ # Copy the Subversion library DLLs
+ if not cp.has_option('options', '--disable-shared'):
+ for svn_dll in svn_dlls:
+ copy_changed_file(os.path.join(abs_objdir, svn_dll), abs_objdir)
+
+ # Copy the Apache modules
+ if run_httpd and cp.has_option('options', '--with-httpd'):
+ mod_dav_svn_path = os.path.join(abs_objdir, 'subversion',
+ 'mod_dav_svn', 'mod_dav_svn.so')
+ mod_authz_svn_path = os.path.join(abs_objdir, 'subversion',
+ 'mod_authz_svn', 'mod_authz_svn.so')
+ mod_dontdothat_path = os.path.join(abs_objdir, 'tools', 'server-side',
+ 'mod_dontdothat', 'mod_dontdothat.so')
+
+ copy_changed_file(mod_dav_svn_path, abs_objdir)
+ copy_changed_file(mod_authz_svn_path, abs_objdir)
+ copy_changed_file(mod_dontdothat_path, abs_objdir)
+
+ os.environ['PATH'] = abs_objdir + os.pathsep + os.environ['PATH']
+
+def fix_case(path):
+ path = os.path.normpath(path)
+ parts = path.split(os.path.sep)
+ drive = parts[0].upper()
+ parts = parts[1:]
+ path = drive + os.path.sep
+ for part in parts:
+ dirs = os.listdir(path)
+ for dir in dirs:
+ if dir.lower() == part.lower():
+ path = os.path.join(path, dir)
+ break
+ return path
+
+class Svnserve:
+ "Run svnserve for ra_svn tests"
+ def __init__(self, svnserve_args, objdir, abs_objdir, abs_builddir):
+ self.args = svnserve_args
+ self.name = 'svnserve.exe'
+ self.kind = objdir
+ self.path = os.path.join(abs_objdir,
+ 'subversion', 'svnserve', self.name)
+ self.root = os.path.join(abs_builddir, CMDLINE_TEST_SCRIPT_NATIVE_PATH)
+ self.proc_handle = None
+
+ def __del__(self):
+ "Stop svnserve when the object is deleted"
+ self.stop()
+
+ def _quote(self, arg):
+ if ' ' in arg:
+ return '"' + arg + '"'
+ else:
+ return arg
+
+ def start(self):
+ if not self.args:
+ args = [self.name, '-d', '-r', self.root]
+ else:
+ args = [self.name] + self.args
+ print('Starting %s %s' % (self.kind, self.name))
+ try:
+ import win32process
+ import win32con
+ args = ' '.join([self._quote(x) for x in args])
+ self.proc_handle = (
+ win32process.CreateProcess(self._quote(self.path), args,
+ None, None, 0,
+ win32con.CREATE_NEW_CONSOLE,
+ None, None, win32process.STARTUPINFO()))[0]
+ except ImportError:
+ os.spawnv(os.P_NOWAIT, self.path, args)
+
+ def stop(self):
+ if self.proc_handle is not None:
+ try:
+ import win32process
+ print('Stopping %s' % self.name)
+ win32process.TerminateProcess(self.proc_handle, 0)
+ return
+ except ImportError:
+ pass
+ print('Svnserve.stop not implemented')
+
+class Httpd:
+ "Run httpd for DAV tests"
+ def __init__(self, abs_httpd_dir, abs_objdir, abs_builddir, httpd_port,
+ service, no_log, httpv2, short_circuit, bulk_updates):
+ self.name = 'apache.exe'
+ self.httpd_port = httpd_port
+ self.httpd_dir = abs_httpd_dir
+
+ if httpv2:
+ self.httpv2_option = 'on'
+ else:
+ self.httpv2_option = 'off'
+
+ if bulk_updates:
+ self.bulkupdates_option = 'on'
+ else:
+ self.bulkupdates_option = 'off'
+
+ self.service = service
+ self.proc_handle = None
+ self.path = os.path.join(self.httpd_dir, 'bin', self.name)
+
+ if short_circuit:
+ self.path_authz_option = 'short_circuit'
+ else:
+ self.path_authz_option = 'on'
+
+ if not os.path.exists(self.path):
+ self.name = 'httpd.exe'
+ self.path = os.path.join(self.httpd_dir, 'bin', self.name)
+ if not os.path.exists(self.path):
+ raise RuntimeError("Could not find a valid httpd binary!")
+
+ self.root_dir = os.path.join(CMDLINE_TEST_SCRIPT_NATIVE_PATH, 'httpd')
+ self.root = os.path.join(abs_builddir, self.root_dir)
+ self.authz_file = os.path.join(abs_builddir,
+ CMDLINE_TEST_SCRIPT_NATIVE_PATH,
+ 'svn-test-work', 'authz')
+ self.dontdothat_file = os.path.join(abs_builddir,
+ CMDLINE_TEST_SCRIPT_NATIVE_PATH,
+ 'svn-test-work', 'dontdothat')
+ self.httpd_config = os.path.join(self.root, 'httpd.conf')
+ self.httpd_users = os.path.join(self.root, 'users')
+ self.httpd_mime_types = os.path.join(self.root, 'mime.types')
+ self.abs_builddir = abs_builddir
+ self.abs_objdir = abs_objdir
+ self.service_name = 'svn-test-httpd-' + str(httpd_port)
+
+ if self.service:
+ self.httpd_args = [self.name, '-n', self._quote(self.service_name),
+ '-f', self._quote(self.httpd_config)]
+ else:
+ self.httpd_args = [self.name, '-f', self._quote(self.httpd_config)]
+
+ create_target_dir(self.root_dir)
+
+ self._create_users_file()
+ self._create_mime_types_file()
+ self._create_dontdothat_file()
+
+ # Determine version.
+ if os.path.exists(os.path.join(self.httpd_dir,
+ 'modules', 'mod_access_compat.so')):
+ self.httpd_ver = 2.3
+ elif os.path.exists(os.path.join(self.httpd_dir,
+ 'modules', 'mod_auth_basic.so')):
+ self.httpd_ver = 2.2
+ else:
+ self.httpd_ver = 2.0
+
+ # Create httpd config file
+ fp = open(self.httpd_config, 'w')
+
+ # Limit the number of threads (default = 64)
+ fp.write('<IfModule mpm_winnt.c>\n')
+ fp.write('ThreadsPerChild 16\n')
+ fp.write('</IfModule>\n')
+
+ # Global Environment
+ fp.write('ServerRoot ' + self._quote(self.root) + '\n')
+ fp.write('DocumentRoot ' + self._quote(self.root) + '\n')
+ fp.write('ServerName localhost\n')
+ fp.write('PidFile pid\n')
+ fp.write('ErrorLog log\n')
+ fp.write('Listen ' + str(self.httpd_port) + '\n')
+
+ if not no_log:
+ fp.write('LogFormat "%h %l %u %t \\"%r\\" %>s %b" common\n')
+ fp.write('Customlog log common\n')
+ fp.write('LogLevel Debug\n')
+ else:
+ fp.write('LogLevel Crit\n')
+
+ # Write LoadModule for minimal system module
+ fp.write(self._sys_module('dav_module', 'mod_dav.so'))
+ if self.httpd_ver >= 2.3:
+ fp.write(self._sys_module('access_compat_module', 'mod_access_compat.so'))
+ fp.write(self._sys_module('authz_core_module', 'mod_authz_core.so'))
+ fp.write(self._sys_module('authz_user_module', 'mod_authz_user.so'))
+ fp.write(self._sys_module('authn_core_module', 'mod_authn_core.so'))
+ if self.httpd_ver >= 2.2:
+ fp.write(self._sys_module('auth_basic_module', 'mod_auth_basic.so'))
+ fp.write(self._sys_module('authn_file_module', 'mod_authn_file.so'))
+ else:
+ fp.write(self._sys_module('auth_module', 'mod_auth.so'))
+ fp.write(self._sys_module('alias_module', 'mod_alias.so'))
+ fp.write(self._sys_module('mime_module', 'mod_mime.so'))
+ fp.write(self._sys_module('log_config_module', 'mod_log_config.so'))
+
+ # Write LoadModule for Subversion modules
+ fp.write(self._svn_module('dav_svn_module', 'mod_dav_svn.so'))
+ fp.write(self._svn_module('authz_svn_module', 'mod_authz_svn.so'))
+
+ # And for mod_dontdothat
+ fp.write(self._svn_module('dontdothat_module', 'mod_dontdothat.so'))
+
+ # Don't handle .htaccess, symlinks, etc.
+ fp.write('<Directory />\n')
+ fp.write('AllowOverride None\n')
+ fp.write('Options None\n')
+ fp.write('</Directory>\n\n')
+
+ # Define two locations for repositories
+ fp.write(self._svn_repo('repositories'))
+ fp.write(self._svn_repo('local_tmp'))
+
+ # And two redirects for the redirect tests
+ fp.write('RedirectMatch permanent ^/svn-test-work/repositories/'
+ 'REDIRECT-PERM-(.*)$ /svn-test-work/repositories/$1\n')
+ fp.write('RedirectMatch ^/svn-test-work/repositories/'
+ 'REDIRECT-TEMP-(.*)$ /svn-test-work/repositories/$1\n')
+
+ fp.write('TypesConfig ' + self._quote(self.httpd_mime_types) + '\n')
+ fp.write('HostNameLookups Off\n')
+
+ fp.close()
+
+ def __del__(self):
+ "Stop httpd when the object is deleted"
+ self.stop()
+
+ def _quote(self, arg):
+ if ' ' in arg:
+ return '"' + arg + '"'
+ else:
+ return arg
+
+ def _create_users_file(self):
+ "Create users file"
+ htpasswd = os.path.join(self.httpd_dir, 'bin', 'htpasswd.exe')
+ # Create the cheapest to compare password form for our testsuite
+ os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bcp', self.httpd_users,
+ 'jrandom', 'rayjandom'])
+ os.spawnv(os.P_WAIT, htpasswd, ['htpasswd.exe', '-bp', self.httpd_users,
+ 'jconstant', 'rayjandom'])
+
+ def _create_mime_types_file(self):
+ "Create empty mime.types file"
+ fp = open(self.httpd_mime_types, 'w')
+ fp.close()
+
+ def _create_dontdothat_file(self):
+ "Create empty mime.types file"
+ fp = open(self.dontdothat_file, 'w')
+ fp.write('[recursive-actions]\n')
+ fp.write('/ = deny\n')
+ fp.close()
+
+ def _sys_module(self, name, path):
+ full_path = os.path.join(self.httpd_dir, 'modules', path)
+ return 'LoadModule ' + name + " " + self._quote(full_path) + '\n'
+
+ def _svn_module(self, name, path):
+ full_path = os.path.join(self.abs_objdir, path)
+ return 'LoadModule ' + name + ' ' + self._quote(full_path) + '\n'
+
+ def _svn_repo(self, name):
+ path = os.path.join(self.abs_builddir,
+ CMDLINE_TEST_SCRIPT_NATIVE_PATH,
+ 'svn-test-work', name)
+ location = '/svn-test-work/' + name
+ ddt_location = '/ddt-test-work/' + name
+ return \
+ '<Location ' + location + '>\n' \
+ ' DAV svn\n' \
+ ' SVNParentPath ' + self._quote(path) + '\n' \
+ ' SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
+ ' SVNPathAuthz ' + self.path_authz_option + '\n' \
+ ' SVNAllowBulkUpdates ' + self.bulkupdates_option + '\n' \
+ ' AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
+ ' AuthType Basic\n' \
+ ' AuthName "Subversion Repository"\n' \
+ ' AuthUserFile ' + self._quote(self.httpd_users) + '\n' \
+ ' Require valid-user\n' \
+ '</Location>\n' \
+ '<Location ' + ddt_location + '>\n' \
+ ' DAV svn\n' \
+ ' SVNParentPath ' + self._quote(path) + '\n' \
+ ' SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \
+ ' SVNPathAuthz ' + self.path_authz_option + '\n' \
+ ' SVNAllowBulkUpdates ' + self.bulkupdates_option + '\n' \
+ ' AuthzSVNAccessFile ' + self._quote(self.authz_file) + '\n' \
+ ' AuthType Basic\n' \
+ ' AuthName "Subversion Repository"\n' \
+ ' AuthUserFile ' + self._quote(self.httpd_users) + '\n' \
+ ' Require valid-user\n' \
+ ' DontDoThatConfigFile ' + self._quote(self.dontdothat_file) + '\n' \
+ '</Location>\n'
+
+ def start(self):
+ if self.service:
+ self._start_service()
+ else:
+ self._start_daemon()
+
+ def stop(self):
+ if self.service:
+ self._stop_service()
+ else:
+ self._stop_daemon()
+
+ def _start_service(self):
+ "Install and start HTTPD service"
+ print('Installing service %s' % self.service_name)
+ os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'install'])
+ print('Starting service %s' % self.service_name)
+ os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'start'])
+
+ def _stop_service(self):
+ "Stop and uninstall HTTPD service"
+ os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'stop'])
+ os.spawnv(os.P_WAIT, self.path, self.httpd_args + ['-k', 'uninstall'])
+
+ def _start_daemon(self):
+ "Start HTTPD as daemon"
+ print('Starting httpd as daemon')
+ print(self.httpd_args)
+ try:
+ import win32process
+ import win32con
+ args = ' '.join([self._quote(x) for x in self.httpd_args])
+ self.proc_handle = (
+ win32process.CreateProcess(self._quote(self.path), args,
+ None, None, 0,
+ win32con.CREATE_NEW_CONSOLE,
+ None, None, win32process.STARTUPINFO()))[0]
+ except ImportError:
+ os.spawnv(os.P_NOWAIT, self.path, self.httpd_args)
+
+ def _stop_daemon(self):
+ "Stop the HTTPD daemon"
+ if self.proc_handle is not None:
+ try:
+ import win32process
+ print('Stopping %s' % self.name)
+ win32process.TerminateProcess(self.proc_handle, 0)
+ return
+ except ImportError:
+ pass
+ print('Httpd.stop_daemon not implemented')
+
+# Move the binaries to the test directory
+locate_libs()
+if create_dirs:
+ old_cwd = os.getcwd()
+ try:
+ os.chdir(abs_objdir)
+ baton = copied_execs
+ for dirpath, dirs, files in os.walk('subversion'):
+ copy_execs(baton, dirpath, files)
+ for dirpath, dirs, files in os.walk('tools/server-side'):
+ copy_execs(baton, dirpath, files)
+ except:
+ os.chdir(old_cwd)
+ raise
+ else:
+ os.chdir(old_cwd)
+
+# Create the base directory for Python tests
+create_target_dir(CMDLINE_TEST_SCRIPT_NATIVE_PATH)
+
+# Ensure the tests directory is correctly cased
+abs_builddir = fix_case(abs_builddir)
+
+daemon = None
+# Run the tests
+
+# No need to start any servers if we are only listing the tests.
+if not list_tests:
+ if run_svnserve:
+ daemon = Svnserve(svnserve_args, objdir, abs_objdir, abs_builddir)
+
+ if run_httpd:
+ daemon = Httpd(abs_httpd_dir, abs_objdir, abs_builddir, httpd_port,
+ httpd_service, httpd_no_log,
+ advertise_httpv2, http_short_circuit,
+ http_bulk_updates)
+
+ # Start service daemon, if any
+ if daemon:
+ daemon.start()
+
+# Find the full path and filename of any test that is specified just by
+# its base name.
+if len(tests_to_run) != 0:
+ tests = []
+ for t in tests_to_run:
+ tns = None
+ if '#' in t:
+ t, tns = t.split('#')
+
+ test = [x for x in all_tests if x.split('/')[-1] == t]
+ if not test and not (t.endswith('-test.exe') or t.endswith('_tests.py')):
+ # The lengths of '-test.exe' and of '_tests.py' are both 9.
+ test = [x for x in all_tests if x.split('/')[-1][:-9] == t]
+
+ if not test:
+ print("Skipping test '%s', test not found." % t)
+ elif tns:
+ tests.append('%s#%s' % (test[0], tns))
+ else:
+ tests.extend(test)
+
+ tests_to_run = tests
+else:
+ tests_to_run = all_tests
+
+
+if list_tests:
+ print('Listing %s configuration on %s' % (objdir, repo_loc))
+else:
+ print('Testing %s configuration on %s' % (objdir, repo_loc))
+sys.path.insert(0, os.path.join(abs_srcdir, 'build'))
+
+if not test_javahl:
+ import run_tests
+ if log_to_stdout:
+ log_file = None
+ fail_log_file = None
+ else:
+ log_file = os.path.join(abs_builddir, log)
+ fail_log_file = os.path.join(abs_builddir, faillog)
+
+ th = run_tests.TestHarness(abs_srcdir, abs_builddir,
+ log_file,
+ fail_log_file,
+ base_url, fs_type, 'serf',
+ server_minor_version, not quiet,
+ cleanup, enable_sasl, parallel, config_file,
+ fsfs_sharding, fsfs_packing,
+ list_tests, svn_bin, mode_filter,
+ milestone_filter,
+ set_log_level=log_level, ssl_cert=ssl_cert)
+ old_cwd = os.getcwd()
+ try:
+ os.chdir(abs_builddir)
+ failed = th.run(tests_to_run)
+ except:
+ os.chdir(old_cwd)
+ raise
+ else:
+ os.chdir(old_cwd)
+else:
+ failed = False
+ args = (
+ 'java.exe',
+ '-Dtest.rootdir=' + os.path.join(abs_builddir, 'javahl'),
+ '-Dtest.srcdir=' + os.path.join(abs_srcdir,
+ 'subversion/bindings/javahl'),
+ '-Dtest.rooturl=',
+ '-Dtest.fstype=' + fs_type ,
+ '-Dtest.tests=',
+
+ '-Djava.library.path='
+ + os.path.join(abs_objdir,
+ 'subversion/bindings/javahl/native'),
+ '-classpath',
+ os.path.join(abs_srcdir, 'subversion/bindings/javahl/classes') +';' +
+ gen_obj.junit_path
+ )
+
+ sys.stderr.flush()
+ print('Running org.apache.subversion tests:')
+ sys.stdout.flush()
+
+ r = subprocess.call(args + tuple(['org.apache.subversion.javahl.RunTests']))
+ sys.stdout.flush()
+ sys.stderr.flush()
+ if (r != 0):
+ print('[Test runner reported failure]')
+ failed = True
+
+ print('Running org.tigris.subversion tests:')
+ sys.stdout.flush()
+ r = subprocess.call(args + tuple(['org.tigris.subversion.javahl.RunTests']))
+ sys.stdout.flush()
+ sys.stderr.flush()
+ if (r != 0):
+ print('[Test runner reported failure]')
+ failed = True
+
+# Stop service daemon, if any
+if daemon:
+ del daemon
+
+# Remove the execs again
+for tgt in copied_execs:
+ try:
+ if os.path.isfile(tgt):
+ if verbose:
+ print("kill: %s" % tgt)
+ os.unlink(tgt)
+ except:
+ traceback.print_exc(file=sys.stdout)
+ pass
+
+
+if failed:
+ sys.exit(1)
OpenPOWER on IntegriCloud